From d60a43e48258e52ab96ba09b20446c81090504c8 Mon Sep 17 00:00:00 2001 From: LucianoJacomeli Date: Mon, 1 Jul 2019 23:33:53 -0300 Subject: [PATCH 1/4] Add files via upload --- Source/NavSysPathsUpdate.py | 294 +++++++++++++++++++++++++++++ Source/ShooterClient.Target.cs | 28 +++ Source/ShooterGame.Target.cs | 87 ++------- Source/ShooterGameEditor.Target.cs | 24 +-- Source/ShooterGameServer.Target.cs | 54 +----- 5 files changed, 340 insertions(+), 147 deletions(-) create mode 100644 Source/NavSysPathsUpdate.py create mode 100644 Source/ShooterClient.Target.cs diff --git a/Source/NavSysPathsUpdate.py b/Source/NavSysPathsUpdate.py new file mode 100644 index 0000000..477b1cc --- /dev/null +++ b/Source/NavSysPathsUpdate.py @@ -0,0 +1,294 @@ +#!/usr/bin/env python + +import os +import re +import string +import sys +import xml.etree.ElementTree as ET +import shelve +import pickle +from time import * +import calendar +import fileinput +import stat + +# user-dependent + +# If set to True will make the changes in original source files. +# otherwise it will just print out the changes that need to be done +MAKE_CHANGES_IN_SOURCE_FILES = True + +# if left unchanged with recurrently parse the current location +SRC_PATH_OS = './' + +""" +Can be filled with specific subfolder names to narrow down the search. For example: +directories = ['YourGame/Source/', + 'YourGame/Config/' + ] +If empty (default) will parse all the C++, *.Build.cs and ini files found +""" +directories = [] + + +########################################## +# from this point on there be dragons +########################################## + +source_file = re.compile("^\w+\.(?:inl|cpp|h|ini)$", flags=re.I) +build_cs_file = re.compile(".*\.(Build.cs)$", flags=re.I) +ignore_dirs = re.compile('[\\\\/](?:Thirdparty|Intermediate)[\\\\/]*', flags=re.I) +engine_code_pattern = re.compile('[\\\\/]Engine[\\\\/]', flags=re.I) +include_pattern = re.compile("^#include\s+", flags=re.I) +script_ref_pattern = re.compile("\/Script\/Engine\.", flags=re.I) + +change_patterns = [ + ('AbstractNavData', '', 'NavigationSystem'), + ('CrowdManagerBase', '', 'NavigationSystem'), + ('NavArea', 'NavAreas/', 'NavigationSystem'), + ('NavAreaMeta_SwitchByAgent', 'NavAreas/', 'NavigationSystem'), + ('NavAreaMeta', 'NavAreas/', 'NavigationSystem'), + ('NavArea_Default', 'NavAreas/', 'NavigationSystem'), + ('NavArea_LowHeight', 'NavAreas/', 'NavigationSystem'), + ('NavArea_Null', 'NavAreas/', 'NavigationSystem'), + ('NavArea_Obstacle', 'NavAreas/', 'NavigationSystem'), + ('NavCollision', '', 'NavigationSystem'), + ('NavigationQueryFilter', 'NavFilters/', 'NavigationSystem'), + ('RecastFilter_UseDefaultArea', 'NavFilters/', 'NavigationSystem'), + ('NavigationGraph', 'NavGraph/', 'NavigationSystem'), + ('NavigationGraphNode', 'NavGraph/', 'NavigationSystem'), + ('NavigationGraphNodeComponent', 'NavGraph/', 'NavigationSystem'), + ('NavLinkComponent', '', 'NavigationSystem'), + ('NavLinkCustomComponent', '', 'NavigationSystem'), + ('NavLinkCustomInterface', '', 'NavigationSystem'), + ('NavLinkHostInterface', '', 'NavigationSystem'), + ('NavLinkRenderingComponent', '', 'NavigationSystem'), + ('NavLinkRenderingProxy', '', 'NavigationSystem'), + ('NavLinkTrivial', '', 'NavigationSystem'), + ('NavMeshBoundsVolume', 'NavMesh/', 'NavigationSystem'), + ('NavMeshPath', 'NavMesh/', 'NavigationSystem'), + ('NavMeshRenderingComponent', 'NavMesh/', 'NavigationSystem'), + ('NavTestRenderingComponent', 'NavMesh/', 'NavigationSystem'), + ('PImplRecastNavMesh', 'NavMesh/', 'NavigationSystem'), + ('RecastHelpers', 'NavMesh/', 'NavigationSystem'), + ('RecastNavMesh', 'NavMesh/', 'NavigationSystem'), + ('RecastNavMeshDataChunk', 'NavMesh/', 'NavigationSystem'), + ('RecastNavMeshGenerator', 'NavMesh/', 'NavigationSystem'), + ('RecastQueryFilter', 'NavMesh/', 'NavigationSystem'), + ('NavModifierComponent', '', 'NavigationSystem'), + ('NavModifierVolume', '', 'NavigationSystem'), + ('NavNodeInterface', '', 'NavigationSystem'), + ('NavRelevantComponent', '', 'NavigationSystem'), + ('NavigationData', '', 'NavigationSystem'), + ('NavigationInvokerComponent', '', 'NavigationSystem'), + ('NavigationOctree', '', 'NavigationSystem'), + ('NavigationPath', '', 'NavigationSystem'), + ('NavigationPathGenerator', '', 'NavigationSystem'), + ('NavigationSystem', '', 'NavigationSystem', 'NavigationSystemV1'), + ('NavigationSystemModule', '', 'NavigationSystem'), + ('NavigationSystemTypes', '', 'NavigationSystem'), + ('NavigationTestingActor', '', 'NavigationSystem'), + ('NavLinkProxy', 'Navigation/', 'AIModule'), +] + +for i in range(len(change_patterns)): + new_name_in_script = None + if len(change_patterns[i]) == 3: + file_name, include_dir, new_module = change_patterns[i] + elif len(change_patterns[i]) == 4: + file_name, include_dir, new_module, new_name_in_script = change_patterns[i] + + as_include_pattern = re.compile('\#include.*\W({}\.h)"'.format(file_name), flags=re.I) + script_pattern = re.compile('(/Script/)Engine(\.{})'.format(file_name), flags=re.I) + if new_name_in_script: + script_replace = r'\1{}.{}'.format(new_module, new_name_in_script) + else: + script_replace = r'\1{}\2'.format(new_module) + change_patterns[i] = as_include_pattern, include_dir, script_pattern, script_replace + +game_code_patterns = [ + (re.compile('UNavigationSystem::InitializeForWorld\('), r'FNavigationSystem::AddNavigationSystemToWorld(*'), + (re.compile('(\w+).GetNavigationSystem\(\)->'), r'FNavigationSystem::GetCurrent(&\1)->'), + (re.compile('(\w+(\(\))?)->GetNavigationSystem\(\)->'), r'FNavigationSystem::GetCurrent(\1)->'), + (re.compile('=\s*(\w+).GetNavigationSystem\(\)'), r'= FNavigationSystem::GetCurrent(&\1)'), + (re.compile('=\s*(\w+(\(\))?)->GetNavigationSystem\(\)'), r'= FNavigationSystem::GetCurrent(\1)'), + (re.compile('(\W*)UNavigationSystem(\W)'), r'\1UNavigationSystemV1\2'), + (re.compile('(\W*)GetMainNavData([\(\W]*)'), r'\1GetDefaultNavDataInstance\2'), + (re.compile('FNavigationSystem::ECreateIfEmpty'), r'FNavigationSystem::ECreateIfMissing'), +] + +build_cs_pattern = (re.compile('(\s*)("AIModule")\s*(,?)([\s\n]*)'), r'\1"NavigationSystem",\4\1\2\3\4') +navigation_system_module_pattern = re.compile(r'"NavigationSystem"') +aimodule_pattern = re.compile(r'"AIModule"') + + +if directories is None or len(directories) == 0: + # start with current dir + directories = [''] + + +def chomp(line): + line = str.split(line, '\n')[0] + return str.split(line, '\r')[0] + + +def check_line(line, engine_code=True): + # check if it's an include line + line = chomp(line) + if re.search(include_pattern, line): + for pattern, replace, _, _ in change_patterns: + match_object = re.search(pattern, line) + if match_object: + new_line = '#include "{}{}"'.format(replace, match_object.group(1)) + if new_line != line: + return line, new_line + elif re.search(script_ref_pattern, line): + for _, _, pattern, replace in change_patterns: + match_object = re.search(pattern, line) + if match_object: + new_line = re.sub(pattern, replace, line) + if new_line != line: + return line, new_line + elif not engine_code: + work_line = line + something_found = False + for pattern, replace in game_code_patterns: + match_object = re.search(pattern, work_line) + if match_object: + new_line = re.sub(pattern, replace, work_line) + if new_line != work_line: + something_found = True + work_line = new_line + + if something_found and work_line != line: + return line, work_line + + return None + + +def find_changes(full_file_path): + changes = [] + file = open(full_file_path, 'r', encoding='utf-8') + try: + lines = file.readlines() + except UnicodeDecodeError as e: + #print('Failed to parse {} ({})'.format(full_file_path, e)) + lines = [] + file.close() + is_engine_code = (re.search(engine_code_pattern, full_file_path) is not None) + line_number = 0 + for line in lines: + line_number += 1 + change = check_line(line, is_engine_code) + if change: + changes.append((line_number, *change)) + return changes + + +def replace_in_file(full_file_path): + changes = find_changes(full_file_path) + if changes is None or len(changes) == 0: + return [] + + error_message = None + line_number = 0 + changes = [] + with fileinput.FileInput(full_file_path, inplace=True, backup='.bak') as file: + is_engine_code = (re.search(engine_code_pattern, full_file_path) is not None) + try: + for line in file: + line_number += 1 + change = check_line(line, is_engine_code) + if change: + changes.append((line_number, *change)) + new_line = change[1] + line = new_line + '\n' + print(line, end='') + except PermissionError as e: + error_message = 'Unable to update file due to permission error {}: {}'.format(full_file_path, e) + pass + except UnicodeDecodeError as e: + error_message = 'Failed to parse {}'.format(full_file_path) + pass + if error_message: + print(error_message) + return changes + + +def fix_build_cs(full_file_path, apply_changes=False): + changes = [] + is_engine_code = (re.search(engine_code_pattern, full_file_path) is not None) + if not is_engine_code: + file = open(full_file_path, 'r', encoding='utf-8') + try: + lines = file.readlines() + except UnicodeDecodeError as e: + print('Failed to parse {} ({})'.format(full_file_path, e)) + lines = [] + file.close() + + all_lines = '\n'.join(lines) + change = None + if re.search(navigation_system_module_pattern, all_lines) is None\ + and re.search(aimodule_pattern, all_lines): + pattern, replace = build_cs_pattern + line_number = 0 + for line in lines: + line_number += 1 + if re.search(pattern, line) is not None: + new_line = re.sub(pattern, replace, line) + if new_line != line: + change = (line_number, line, new_line) + break; + + if change is not None and apply_changes: + error_message = None + with fileinput.FileInput(full_file_path, inplace=True, backup='.bak') as file: + try: + line_number = 0 + line_to_change, old_line, new_line = change + for line in file: + line_number += 1 + if line_number == line_to_change: + assert (line == old_line) + print(new_line, end='') + else: + print(line, end='') + except PermissionError as e: + error_message = 'Unable to update file due to permission error {}: {}'.format(full_file_path, e) + pass + except UnicodeDecodeError as e: + error_message = 'Failed to parse {}'.format(full_file_path) + pass + if error_message: + print(error_message) + + if change: + changes = [change] + + return changes + + +def traverse(dirName, apply_changes=False): + action = find_changes if not apply_changes else replace_in_file + for root, dirs, files in os.walk(dirName): + if re.search(ignore_dirs, root): + continue + for f in files: + changes = [] + if re.search(source_file, f) is not None: + full_path = os.path.join(root, f) + changes = action(full_path) + elif re.search(build_cs_file, f) is not None: + full_path = os.path.join(root, f) + changes = fix_build_cs(full_path, apply_changes) + + if len(changes): + print('===================================================================================\n{}\n-----------------------------------------------------------------------------------'.format(full_path)) + for line_num, line, new_line in changes: + print('{}: {} -> {}'.format(line_num, line, new_line)) + + +if __name__ == '__main__': + for dirName in directories: + traverse(SRC_PATH_OS + dirName, apply_changes=MAKE_CHANGES_IN_SOURCE_FILES) diff --git a/Source/ShooterClient.Target.cs b/Source/ShooterClient.Target.cs new file mode 100644 index 0000000..50393df --- /dev/null +++ b/Source/ShooterClient.Target.cs @@ -0,0 +1,28 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; +using System.Collections.Generic; + +public class ShooterClientTarget : TargetRules +{ + public ShooterClientTarget(TargetInfo Target) : base(Target) + { + Type = TargetType.Client; + bUsesSteam = true; + + ExtraModuleNames.Add("ShooterGame"); + + if (Target.Platform == UnrealTargetPlatform.PS4) + { + GlobalDefinitions.Add("GARLIC_HEAP_SIZE=(2600ULL * 1024 * 1024)"); + GlobalDefinitions.Add("ONION_HEAP_SIZE=(200ULL * 1024 * 1024)"); + GlobalDefinitions.Add("RESERVED_MEMORY_SIZE=(1ULL * 1024 * 1024)"); + GlobalDefinitions.Add("LIBC_MALLOC_SIZE=(32ULL * 1024 * 1024)"); + GlobalDefinitions.Add("LIBC_MALLOC_SIZE=(32ULL * 1024 * 1024)"); + + //for a real game these could be behind a call to a virtual function defined in a partial class in a protected folder also. + GlobalDefinitions.Add("UE4_PROJECT_NPTITLEID=NPXX51358_00"); + GlobalDefinitions.Add("UE4_PROJECT_NPTITLESECRET=81ae213eafbc64777574955bf921c9be3c60a3bddef70c357d8fe49ad64e0d0402d2249de390174832c5e4098114c93c33705b597cfbe9b1153d58fe9fae1f0de1466daf18ef25d06122cff7c95bde07bc060109e20c865305692dfbf9d7b726460893c4abd86dc9e8fd6b5db7dca4ffd4eefcb1771baccd576109bea862d6d4"); + } + } +} diff --git a/Source/ShooterGame.Target.cs b/Source/ShooterGame.Target.cs index 96adc3e..5513b16 100644 --- a/Source/ShooterGame.Target.cs +++ b/Source/ShooterGame.Target.cs @@ -1,93 +1,28 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. using UnrealBuildTool; using System.Collections.Generic; public class ShooterGameTarget : TargetRules { - public ShooterGameTarget(TargetInfo Target) + public ShooterGameTarget(TargetInfo Target) : base(Target) { Type = TargetType.Game; bUsesSteam = true; - } - - // - // TargetRules interface. - // - public override void SetupBinaries( - TargetInfo Target, - ref List OutBuildBinaryConfigurations, - ref List OutExtraModuleNames - ) - { - OutExtraModuleNames.Add("ShooterGame"); - } + ExtraModuleNames.Add("ShooterGame"); - public override void SetupGlobalEnvironment( - TargetInfo Target, - ref LinkEnvironmentConfiguration OutLinkEnvironmentConfiguration, - ref CPPEnvironmentConfiguration OutCPPEnvironmentConfiguration - ) - { - if (Target.Platform == UnrealTargetPlatform.PS4) + if (Target.Platform == UnrealTargetPlatform.PS4) { - OutCPPEnvironmentConfiguration.Definitions.Add("GARLIC_HEAP_SIZE=(2600ULL * 1024 * 1024)"); - OutCPPEnvironmentConfiguration.Definitions.Add("ONION_HEAP_SIZE=(200ULL * 1024 * 1024)"); - OutCPPEnvironmentConfiguration.Definitions.Add("RESERVED_MEMORY_SIZE=(1ULL * 1024 * 1024)"); - OutCPPEnvironmentConfiguration.Definitions.Add("LIBC_MALLOC_SIZE=(32ULL * 1024 * 1024)"); - OutCPPEnvironmentConfiguration.Definitions.Add("LIBC_MALLOC_SIZE=(32ULL * 1024 * 1024)"); + GlobalDefinitions.Add("GARLIC_HEAP_SIZE=(2600ULL * 1024 * 1024)"); + GlobalDefinitions.Add("ONION_HEAP_SIZE=(200ULL * 1024 * 1024)"); + GlobalDefinitions.Add("RESERVED_MEMORY_SIZE=(1ULL * 1024 * 1024)"); + GlobalDefinitions.Add("LIBC_MALLOC_SIZE=(32ULL * 1024 * 1024)"); + GlobalDefinitions.Add("LIBC_MALLOC_SIZE=(32ULL * 1024 * 1024)"); //for a real game these could be behind a call to a virtual function defined in a partial class in a protected folder also. - OutCPPEnvironmentConfiguration.Definitions.Add("UE4_PROJECT_NPTITLEID=NPXX51358_00"); - OutCPPEnvironmentConfiguration.Definitions.Add("UE4_PROJECT_NPTITLESECRET=81ae213eafbc64777574955bf921c9be3c60a3bddef70c357d8fe49ad64e0d0402d2249de390174832c5e4098114c93c33705b597cfbe9b1153d58fe9fae1f0de1466daf18ef25d06122cff7c95bde07bc060109e20c865305692dfbf9d7b726460893c4abd86dc9e8fd6b5db7dca4ffd4eefcb1771baccd576109bea862d6d4"); - } - } - - public override List GUBP_GetPlatforms_MonolithicOnly(UnrealTargetPlatform HostPlatform) - { - List Platforms = null; - - switch (HostPlatform) - { - case UnrealTargetPlatform.Mac: - Platforms = new List { HostPlatform }; - break; - - case UnrealTargetPlatform.Win64: - Platforms = new List { HostPlatform, UnrealTargetPlatform.Win32, UnrealTargetPlatform.XboxOne, UnrealTargetPlatform.PS4 }; - break; - - default: - Platforms = new List(); - break; - } - - return Platforms; - } - - public override List GUBP_GetConfigs_MonolithicOnly(UnrealTargetPlatform HostPlatform, UnrealTargetPlatform Platform) - { - return new List { UnrealTargetConfiguration.Development }; - } - public override List GUBP_GetConfigsForFormalBuilds_MonolithicOnly(UnrealTargetPlatform HostPlatform) - { - if (HostPlatform == UnrealTargetPlatform.Win64) - { - return new List - { - new GUBPFormalBuild(UnrealTargetPlatform.Linux, UnrealTargetConfiguration.Development), - new GUBPFormalBuild(UnrealTargetPlatform.Win64, UnrealTargetConfiguration.Development), - new GUBPFormalBuild(UnrealTargetPlatform.XboxOne, UnrealTargetConfiguration.Development), - new GUBPFormalBuild(UnrealTargetPlatform.PS4, UnrealTargetConfiguration.Development), - }; - } - else - { - return new List - { - new GUBPFormalBuild(UnrealTargetPlatform.Mac, UnrealTargetConfiguration.Development), - }; + GlobalDefinitions.Add("UE4_PROJECT_NPTITLEID=NPXX51358_00"); + GlobalDefinitions.Add("UE4_PROJECT_NPTITLESECRET=81ae213eafbc64777574955bf921c9be3c60a3bddef70c357d8fe49ad64e0d0402d2249de390174832c5e4098114c93c33705b597cfbe9b1153d58fe9fae1f0de1466daf18ef25d06122cff7c95bde07bc060109e20c865305692dfbf9d7b726460893c4abd86dc9e8fd6b5db7dca4ffd4eefcb1771baccd576109bea862d6d4"); } } } diff --git a/Source/ShooterGameEditor.Target.cs b/Source/ShooterGameEditor.Target.cs index b07e0b5..a20299b 100644 --- a/Source/ShooterGameEditor.Target.cs +++ b/Source/ShooterGameEditor.Target.cs @@ -1,32 +1,14 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. using UnrealBuildTool; using System.Collections.Generic; public class ShooterGameEditorTarget : TargetRules { - - public ShooterGameEditorTarget(TargetInfo Target) + public ShooterGameEditorTarget(TargetInfo Target) : base(Target) { Type = TargetType.Editor; - } - // - // TargetRules interface. - // - - public override void SetupBinaries( - TargetInfo Target, - ref List OutBuildBinaryConfigurations, - ref List OutExtraModuleNames - ) - { - OutExtraModuleNames.Add("ShooterGame"); + ExtraModuleNames.Add("ShooterGame"); } - public override GUBPProjectOptions GUBP_IncludeProjectInPromotedBuild_EditorTypeOnly(UnrealTargetPlatform HostPlatform) - { - var Result = new GUBPProjectOptions(); - Result.bIsPromotable = true; - return Result; - } } diff --git a/Source/ShooterGameServer.Target.cs b/Source/ShooterGameServer.Target.cs index f22dbf5..fe4eac7 100644 --- a/Source/ShooterGameServer.Target.cs +++ b/Source/ShooterGameServer.Target.cs @@ -1,62 +1,16 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. using UnrealBuildTool; using System.Collections.Generic; +[SupportedPlatforms(UnrealPlatformClass.Server)] public class ShooterGameServerTarget : TargetRules { - public ShooterGameServerTarget(TargetInfo Target) + public ShooterGameServerTarget(TargetInfo Target) : base(Target) { Type = TargetType.Server; bUsesSteam = true; - } - - // - // TargetRules interface. - // - public override bool GetSupportedPlatforms(ref List OutPlatforms) - { - // It is valid for only server platforms - return UnrealBuildTool.UnrealBuildTool.GetAllServerPlatforms(ref OutPlatforms, false); - } - - public override void SetupBinaries( - TargetInfo Target, - ref List OutBuildBinaryConfigurations, - ref List OutExtraModuleNames - ) - { - OutExtraModuleNames.Add("ShooterGame"); - } - public override List GUBP_GetPlatforms_MonolithicOnly(UnrealTargetPlatform HostPlatform) - { - List Platforms = null; - - switch (HostPlatform) - { - case UnrealTargetPlatform.Linux: - Platforms = new List { HostPlatform }; - break; - - case UnrealTargetPlatform.Win64: - Platforms = new List { HostPlatform, UnrealTargetPlatform.Linux }; - break; - - default: - Platforms = new List(); - break; - } - - return Platforms; - } - - public override List GUBP_GetConfigs_MonolithicOnly(UnrealTargetPlatform HostPlatform, UnrealTargetPlatform Platform) - { - return new List { UnrealTargetConfiguration.Test }; - } - public override List GUBP_GetConfigsForFormalBuilds_MonolithicOnly(UnrealTargetPlatform HostPlatform) - { - return new List(); + ExtraModuleNames.Add("ShooterGame"); } } From 748324cb31bdd9b4c0a5e46a408d7d74b6b49eaa Mon Sep 17 00:00:00 2001 From: LucianoJacomeli Date: Tue, 2 Jul 2019 00:14:39 -0300 Subject: [PATCH 2/4] 4.22.3 i have compile with success, but i change my source file engine to fit the public: and private: --- .../Private/Bots/BTDecorator_HasLoSTo.cpp | 14 +- .../Private/Bots/BTTask_FindNearestPoly.cpp | 36 +- .../Bots/BTTask_FindNearestPoly.cpp.bak | 167 +++ .../Private/Bots/BTTask_FindPickup.cpp | 2 +- .../Bots/BTTask_FindPointNearEnemy.cpp | 9 +- .../Private/Bots/ShooterAIController.cpp | 67 +- .../Private/Bots/ShooterAIController.cpp.bak | 1056 +++++++++++++++++ .../ShooterGame/Private/Bots/ShooterBot.cpp | 3 +- .../Private/EQS/AttackPositionTest.cpp | 14 +- .../Private/EQS/BehindObstacleTest.cpp | 6 +- .../EQS/CloserHighInfluencePositionsTest.cpp | 4 +- .../EQS/DistanceChosenPositionsTest.cpp | 4 +- .../Private/EQS/KeepFirstPositionTest.cpp | 4 +- .../EQS/MaxSpreadSearchPositionsTest.cpp | 4 +- .../EQS/MinDotProductChosenPositions.cpp | 4 +- .../Private/EQS/NearBehindObstacleTest.cpp | 8 +- .../Private/EQS/NearCoverAnnotationTest.cpp | 6 +- .../EQS/NearCoverAnnotationTest.cpp.bak | 62 + .../Private/EQS/PathfiningTest.cpp | 29 +- .../Private/EQS/PathfiningTest.cpp.bak | 254 ++++ .../Private/EQS/PlayerDistanceTest.cpp | 4 +- .../Private/EQS/PlayerVisibilityTest.cpp | 6 +- .../Effects/ShooterExplosionEffect.cpp | 2 +- .../Private/Effects/ShooterImpactEffect.cpp | 4 +- .../Private/Navigation/CubeComponent.cpp | 2 +- .../Private/Navigation/MyInfluenceMap.cpp | 8 +- .../Navigation/MyNavigationQueryFilter.cpp | 6 +- .../Private/Navigation/MyRecastNavMesh.cpp | 13 +- .../Private/Online/ShooterGameMode.cpp | 18 +- .../Private/Online/ShooterGameSession.cpp | 124 +- .../Private/Online/ShooterGameState.cpp | 2 +- .../Private/Online/ShooterGame_FreeForAll.cpp | 4 +- .../Private/Online/ShooterGame_Menu.cpp | 2 +- .../Online/ShooterGame_TeamDeathMatch.cpp | 4 +- .../Online/ShooterOnlineGameSettings.cpp | 2 +- .../Online/ShooterOnlineGameSettings.h | 2 +- .../Online/ShooterOnlineSessionClient.cpp | 60 + .../Private/Online/ShooterPlayerState.cpp | 12 +- .../Online/ShooterReplicationGraph.cpp | 863 ++++++++++++++ .../Private/Online/ShooterReplicationGraph.h | 143 +++ .../Private/Others/HelperMethods.cpp | 4 +- .../Private/Others/HelperMethods.cpp.bak | 338 ++++++ .../Private/Pickups/ShooterPickup.cpp | 10 +- .../Private/Pickups/ShooterPickup_Ammo.cpp | 2 +- .../Private/Pickups/ShooterPickup_Health.cpp | 2 +- .../Private/Player/ShooterCharacter.cpp | 453 ++++--- .../Player/ShooterCharacterMovement.cpp | 2 +- .../Private/Player/ShooterCheatManager.cpp | 4 +- .../Private/Player/ShooterDemoSpectator.cpp | 2 +- .../Private/Player/ShooterLocalPlayer.cpp | 22 +- .../Private/Player/ShooterPersistentUser.cpp | 23 +- .../Player/ShooterPlayerCameraManager.cpp | 2 +- .../Player/ShooterPlayerController.cpp | 262 +++- .../Player/ShooterPlayerController_Menu.cpp | 2 +- .../Private/Player/ShooterSpectatorPawn.cpp | 23 +- Source/ShooterGame/Private/ShooterEngine.cpp | 2 +- .../Private/ShooterGameDelegates.cpp | 132 ++- .../Private/ShooterGameDelegates.h | 2 +- .../Private/ShooterGameInstance.cpp | 536 +++++++-- .../ShooterGame/Private/ShooterGameModule.cpp | 2 +- .../Private/ShooterGameUserSettings.cpp | 8 +- .../Private/ShooterGameViewportClient.cpp | 24 +- .../ShooterGame/Private/ShooterLeaderboards.h | 2 +- .../ShooterGame/Private/ShooterTeamStart.cpp | 2 +- .../Private/Sound/SoundNodeLocalPlayer.cpp | 17 +- Source/ShooterGame/Private/TakeHitInfo.cpp | 65 + .../UI/Menu/ShooterDemoPlaybackMenu.cpp | 4 +- .../Private/UI/Menu/ShooterDemoPlaybackMenu.h | 2 +- .../Private/UI/Menu/ShooterFriends.cpp | 45 +- .../Private/UI/Menu/ShooterFriends.h | 4 +- .../Private/UI/Menu/ShooterIngameMenu.cpp | 24 +- .../Private/UI/Menu/ShooterIngameMenu.h | 2 +- .../Private/UI/Menu/ShooterMainMenu.cpp | 368 ++++-- .../Private/UI/Menu/ShooterMainMenu.h | 62 +- .../Private/UI/Menu/ShooterMessageMenu.cpp | 2 +- .../Private/UI/Menu/ShooterMessageMenu.h | 2 +- .../Private/UI/Menu/ShooterOptions.cpp | 52 +- .../Private/UI/Menu/ShooterOptions.h | 11 +- .../Private/UI/Menu/ShooterRecentlyMet.cpp | 6 +- .../Private/UI/Menu/ShooterRecentlyMet.h | 2 +- .../Private/UI/Menu/ShooterWelcomeMenu.cpp | 43 +- .../Private/UI/Menu/ShooterWelcomeMenu.h | 4 +- .../UI/Menu/Widgets/SShooterDemoList.cpp | 18 +- .../UI/Menu/Widgets/SShooterDemoList.h | 7 +- .../UI/Menu/Widgets/SShooterLeaderboard.cpp | 38 +- .../UI/Menu/Widgets/SShooterLeaderboard.h | 11 +- .../UI/Menu/Widgets/SShooterMenuItem.cpp | 2 +- .../UI/Menu/Widgets/SShooterMenuItem.h | 2 +- .../UI/Menu/Widgets/SShooterMenuWidget.cpp | 25 +- .../UI/Menu/Widgets/SShooterMenuWidget.h | 7 +- .../UI/Menu/Widgets/SShooterOnlineStore.cpp | 437 +++++++ .../UI/Menu/Widgets/SShooterOnlineStore.h | 127 ++ .../UI/Menu/Widgets/SShooterServerList.cpp | 50 +- .../UI/Menu/Widgets/SShooterServerList.h | 13 +- .../Private/UI/Menu/Widgets/ShooterMenuItem.h | 14 +- Source/ShooterGame/Private/UI/ShooterHUD.cpp | 39 +- .../Private/UI/ShooterHUDPCTrackerBase.cpp | 6 +- .../Private/UI/ShooterHUDPCTrackerBase.h | 2 +- .../Private/UI/ShooterUIHelpers.cpp | 12 +- .../ShooterGame/Private/UI/ShooterUIHelpers.h | 6 +- .../UI/Style/ShooterChatWidgetStyle.cpp | 2 +- .../Private/UI/Style/ShooterChatWidgetStyle.h | 2 +- .../UI/Style/ShooterMenuItemWidgetStyle.cpp | 2 +- .../UI/Style/ShooterMenuItemWidgetStyle.h | 2 +- .../UI/Style/ShooterMenuSoundsWidgetStyle.cpp | 2 +- .../UI/Style/ShooterMenuSoundsWidgetStyle.h | 2 +- .../UI/Style/ShooterMenuWidgetStyle.cpp | 2 +- .../Private/UI/Style/ShooterMenuWidgetStyle.h | 2 +- .../UI/Style/ShooterOptionsWidgetStyle.cpp | 2 +- .../UI/Style/ShooterOptionsWidgetStyle.h | 2 +- .../UI/Style/ShooterScoreboardWidgetStyle.cpp | 2 +- .../UI/Style/ShooterScoreboardWidgetStyle.h | 2 +- .../Private/UI/Style/ShooterStyle.cpp | 36 +- .../Private/UI/Style/ShooterStyle.h | 2 +- .../Private/UI/Widgets/SChatWidget.cpp | 12 +- .../Private/UI/Widgets/SChatWidget.h | 5 +- .../UI/Widgets/SShooterConfirmationDialog.cpp | 98 +- .../UI/Widgets/SShooterConfirmationDialog.h | 6 +- .../Private/UI/Widgets/SShooterDemoHUD.cpp | 10 +- .../Private/UI/Widgets/SShooterDemoHUD.h | 2 +- .../UI/Widgets/SShooterScoreboardWidget.cpp | 39 +- .../UI/Widgets/SShooterScoreboardWidget.h | 9 +- .../SShooterSplitScreenLobbyWidget.cpp | 188 ++- .../Widgets/SShooterSplitScreenLobbyWidget.h | 20 +- .../Private/Weapons/ShooterDamageType.cpp | 2 +- .../Private/Weapons/ShooterProjectile.cpp | 6 +- .../Private/Weapons/ShooterWeapon.cpp | 73 +- .../Private/Weapons/ShooterWeapon_Instant.cpp | 8 +- .../Weapons/ShooterWeapon_Projectile.cpp | 2 +- .../Public/Bots/BTDecorator_HasLoSTo.h | 2 +- .../Public/Bots/BTTask_FindPickup.h | 2 +- .../Public/Bots/BTTask_FindPointNearEnemy.h | 2 +- .../Public/Bots/ShooterAIController.h | 8 +- Source/ShooterGame/Public/Bots/ShooterBot.h | 35 +- .../ShooterGame/Public/EQS/PathfiningTest.h | 18 +- .../Public/EQS/PathfiningTest.h.bak | 57 + .../Public/Effects/ShooterExplosionEffect.h | 9 +- .../Public/Effects/ShooterImpactEffect.h | 2 +- .../Public/Navigation/MyInfluenceMap.h | 7 +- .../Navigation/MyNavigationQueryFilter.h | 6 +- .../Public/Navigation/MyRecastNavMesh.h | 6 +- .../Public/Navigation/MyTexture2D.h | 2 +- .../Public/Online/ShooterGameMode.h | 4 +- .../Public/Online/ShooterGameSession.h | 19 +- .../Public/Online/ShooterGameState.h | 2 +- .../Public/Online/ShooterGame_FreeForAll.h | 2 +- .../Public/Online/ShooterGame_Menu.h | 8 +- .../Online/ShooterGame_TeamDeathMatch.h | 2 +- .../Online/ShooterOnlineSessionClient.h | 26 + .../Public/Online/ShooterPlayerState.h | 2 +- .../Public/Pickups/ShooterPickup.h | 5 +- .../Public/Pickups/ShooterPickup_Ammo.h | 2 +- .../Public/Pickups/ShooterPickup_Health.h | 2 +- .../Public/Player/ShooterCharacter.h | 219 ++-- .../Public/Player/ShooterCharacterMovement.h | 2 +- .../Public/Player/ShooterCheatManager.h | 2 +- .../Public/Player/ShooterDemoSpectator.h | 2 +- .../Public/Player/ShooterLocalPlayer.h | 2 +- .../Public/Player/ShooterPersistentUser.h | 14 +- .../Player/ShooterPlayerCameraManager.h | 2 +- .../Public/Player/ShooterPlayerController.h | 39 +- .../Player/ShooterPlayerController_Menu.h | 2 +- .../Public/Player/ShooterSpectatorPawn.h | 2 +- Source/ShooterGame/Public/ShooterEngine.h | 2 +- Source/ShooterGame/Public/ShooterGame.h | 4 +- .../ShooterGame/Public/ShooterGameInstance.h | 91 +- .../Public/ShooterGameUserSettings.h | 16 +- .../Public/ShooterGameViewportClient.h | 13 +- Source/ShooterGame/Public/ShooterTeamStart.h | 2 +- Source/ShooterGame/Public/ShooterTypes.h | 67 +- .../Public/Sound/SoundNodeLocalPlayer.h | 12 +- Source/ShooterGame/Public/UI/ShooterHUD.h | 2 +- .../Public/Weapons/ShooterDamageType.h | 2 +- .../Public/Weapons/ShooterProjectile.h | 7 +- .../Public/Weapons/ShooterWeapon.h | 23 +- .../Public/Weapons/ShooterWeapon_Instant.h | 4 +- .../Public/Weapons/ShooterWeapon_Projectile.h | 3 +- Source/ShooterGame/Resources/Mac/Info.plist | 41 + .../Resources/Windows/ShooterGame.rc | 3 +- Source/ShooterGame/ShooterGame.Build.cs | 42 +- .../Private/ShooterGameLoadingScreen.cpp | 9 +- .../Public/ShooterGameLoadingScreen.h | 2 +- .../ShooterGameLoadingScreen.Build.cs | 8 +- 183 files changed, 6522 insertions(+), 1315 deletions(-) create mode 100644 Source/ShooterGame/Private/Bots/BTTask_FindNearestPoly.cpp.bak create mode 100644 Source/ShooterGame/Private/Bots/ShooterAIController.cpp.bak create mode 100644 Source/ShooterGame/Private/EQS/NearCoverAnnotationTest.cpp.bak create mode 100644 Source/ShooterGame/Private/EQS/PathfiningTest.cpp.bak create mode 100644 Source/ShooterGame/Private/Online/ShooterOnlineSessionClient.cpp create mode 100644 Source/ShooterGame/Private/Online/ShooterReplicationGraph.cpp create mode 100644 Source/ShooterGame/Private/Online/ShooterReplicationGraph.h create mode 100644 Source/ShooterGame/Private/Others/HelperMethods.cpp.bak create mode 100644 Source/ShooterGame/Private/TakeHitInfo.cpp create mode 100644 Source/ShooterGame/Private/UI/Menu/Widgets/SShooterOnlineStore.cpp create mode 100644 Source/ShooterGame/Private/UI/Menu/Widgets/SShooterOnlineStore.h create mode 100644 Source/ShooterGame/Public/EQS/PathfiningTest.h.bak create mode 100644 Source/ShooterGame/Public/Online/ShooterOnlineSessionClient.h create mode 100644 Source/ShooterGame/Resources/Mac/Info.plist diff --git a/Source/ShooterGame/Private/Bots/BTDecorator_HasLoSTo.cpp b/Source/ShooterGame/Private/Bots/BTDecorator_HasLoSTo.cpp index cd052d7..c5d7ccc 100644 --- a/Source/ShooterGame/Private/Bots/BTDecorator_HasLoSTo.cpp +++ b/Source/ShooterGame/Private/Bots/BTDecorator_HasLoSTo.cpp @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ShooterGame.h" #include "BTDecorator_HasLoSTo.h" @@ -34,7 +34,7 @@ bool UBTDecorator_HasLoSTo::CalculateRawConditionValue(class UBehaviorTreeCompon bool bHasPath = false; - const UNavigationSystem* NavSys = UNavigationSystem::GetCurrent(OwnerComp->GetWorld()); + const UNavigationSystemV1* NavSys = UNavigationSystemV1::GetCurrent(OwnerComp->GetWorld()); if (NavSys && bHasPointA && bHasPointB) { const AAIController* AIOwner = Cast(OwnerComp->GetOwner()); @@ -102,7 +102,6 @@ bool UBTDecorator_HasLoSTo::CalculateRawConditionValue(UBehaviorTreeComponent& O bool UBTDecorator_HasLoSTo::LOSTrace(AActor* InActor, AActor* InEnemyActor, const FVector& EndLocation) const { - static FName LosTag = FName(TEXT("AILosTrace")); AShooterAIController* MyController = Cast(InActor); AShooterBot* MyBot = MyController ? Cast(MyController->GetPawn()) : NULL; @@ -111,8 +110,7 @@ bool UBTDecorator_HasLoSTo::LOSTrace(AActor* InActor, AActor* InEnemyActor, cons if (MyBot != NULL) { // Perform trace to retrieve hit info - FCollisionQueryParams TraceParams(LosTag, true, InActor); - TraceParams.bTraceAsyncScene = true; + FCollisionQueryParams TraceParams(SCENE_QUERY_STAT(AILosTrace), true, InActor); TraceParams.bReturnPhysicalMaterial = true; TraceParams.AddIgnoredActor(MyBot); @@ -135,9 +133,9 @@ bool UBTDecorator_HasLoSTo::LOSTrace(AActor* InActor, AActor* InEnemyActor, cons // Check the team of us against the team of the actor we hit if we are able. If they dont match good to go. ACharacter* HitChar = Cast(HitActor); if ( (HitChar != NULL) - && (MyController->PlayerState != NULL) && (HitChar->PlayerState != NULL)) + && (MyController->PlayerState != NULL) && (HitChar->GetPlayerState() != NULL)) { - AShooterPlayerState* HitPlayerState = Cast(HitChar->PlayerState); + AShooterPlayerState* HitPlayerState = Cast(HitChar->GetPlayerState()); AShooterPlayerState* MyPlayerState = Cast(MyController->PlayerState); if ((HitPlayerState != NULL) && (MyPlayerState != NULL)) { @@ -156,7 +154,7 @@ bool UBTDecorator_HasLoSTo::LOSTrace(AActor* InActor, AActor* InEnemyActor, cons // We were not given an actor - so check of the distance between what we hit and the target. If what we hit is further away than the target we should be able to hit our target. FVector HitDelta = Hit.ImpactPoint - StartLocation; FVector TargetDelta = EndLocation - StartLocation; - if (TargetDelta.Size() < HitDelta.Size()) + if (TargetDelta.SizeSquared() < HitDelta.SizeSquared()) { bHasLOS = true; } diff --git a/Source/ShooterGame/Private/Bots/BTTask_FindNearestPoly.cpp b/Source/ShooterGame/Private/Bots/BTTask_FindNearestPoly.cpp index 8e33d41..258f233 100644 --- a/Source/ShooterGame/Private/Bots/BTTask_FindNearestPoly.cpp +++ b/Source/ShooterGame/Private/Bots/BTTask_FindNearestPoly.cpp @@ -7,6 +7,7 @@ #include "BehaviorTree/Blackboard/BlackboardKeyAllTypes.h" #include "Bots/ShooterAIController.h" #include "Bots/ShooterBot.h" +#include "NavigationSystem.h" #include "Public/Navigation/MyRecastNavMesh.h" UBTTask_FindNearestPoly::UBTTask_FindNearestPoly(const FObjectInitializer& ObjectInitializer) @@ -29,22 +30,23 @@ EBTNodeResult::Type UBTTask_FindNearestPoly::ExecuteTask(UBehaviorTreeComponent& return EBTNodeResult::Failed; } - const FNavAgentProperties* AgentProperties = MyBot->GetNavAgentProperties(); - - if (!AgentProperties) - { + UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent(GetWorld()); + if (!NavSys) { return EBTNodeResult::Failed; } - UNavigationSystem* NavSys = GetWorld()->GetNavigationSystem(); - if (!NavSys) { + //const FNavAgentProperties& AgentProperties = MyBot->GetNavAgentPropertiesRef(); + const ANavigationData* AgentProperties = NavSys->GetNavDataForProps(MyBot->GetNavAgentPropertiesRef()); + + if (!AgentProperties) + { return EBTNodeResult::Failed; } - //FNavigationSystem::ECreateIfEmpty CreateNewIfNoneFound; - const ANavigationData* NavData = NavSys->GetMainNavData(FNavigationSystem::DontCreate); + //FNavigationSystem::ECreateIfMissing CreateNewIfNoneFound; + const ANavigationData* NavData = NavSys->GetDefaultNavDataInstance(/*FNavigationSystem::DontCreate*/); - //const ANavigationData* NavData = (AgentProperties != NULL) ? GetNavDataForProps(*AgentProperties) : GetMainNavData(FNavigationSystem::DontCreate); + //const ANavigationData* NavData = (AgentProperties != NULL) ? GetNavDataForProps(*AgentProperties) : GetDefaultNavDataInstance(FNavigationSystem::DontCreate); const ARecastNavMesh* NavMesh = Cast(NavData); const AMyRecastNavMesh* MyNavMesh = Cast(NavMesh); @@ -78,15 +80,15 @@ EBTNodeResult::Type UBTTask_FindNearestPoly::ExecuteTask(UBehaviorTreeComponent& return EBTNodeResult::Failed; } - UNavigationSystem* NavSys = GetWorld()->GetNavigationSystem(); + UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent(GetWorld()); if (!NavSys) { return EBTNodeResult::Failed; } - FNavigationSystem::ECreateIfEmpty CreateNewIfNoneFound; - const ANavigationData* NavData = NavSys->GetMainNavData(CreateNewIfNoneFound); + FNavigationSystem::ECreateIfMissing CreateNewIfNoneFound; + const ANavigationData* NavData = NavSys->GetDefaultNavDataInstance(CreateNewIfNoneFound); - //const ANavigationData* NavData = (AgentProperties != NULL) ? GetNavDataForProps(*AgentProperties) : GetMainNavData(FNavigationSystem::DontCreate); + //const ANavigationData* NavData = (AgentProperties != NULL) ? GetNavDataForProps(*AgentProperties) : GetDefaultNavDataInstance(FNavigationSystem::DontCreate); const ARecastNavMesh* NavMesh = Cast(NavData); if (!NavMesh) @@ -133,15 +135,15 @@ EBTNodeResult::Type UBTTask_FindNearestPoly::ExecuteTask(UBehaviorTreeComponent& return EBTNodeResult::Failed; } - UNavigationSystem* NavSys = GetWorld()->GetNavigationSystem(); + UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent(GetWorld()); if (!NavSys) { return EBTNodeResult::Failed; } - FNavigationSystem::ECreateIfEmpty CreateNewIfNoneFound; - const ANavigationData* NavData = NavSys->GetMainNavData(CreateNewIfNoneFound); + FNavigationSystem::ECreateIfMissing CreateNewIfNoneFound; + const ANavigationData* NavData = NavSys->GetDefaultNavDataInstance(CreateNewIfNoneFound); - //const ANavigationData* NavData = (AgentProperties != NULL) ? GetNavDataForProps(*AgentProperties) : GetMainNavData(FNavigationSystem::DontCreate); + //const ANavigationData* NavData = (AgentProperties != NULL) ? GetNavDataForProps(*AgentProperties) : GetDefaultNavDataInstance(FNavigationSystem::DontCreate); const ARecastNavMesh* NavMesh = Cast(NavData); if (!NavMesh) diff --git a/Source/ShooterGame/Private/Bots/BTTask_FindNearestPoly.cpp.bak b/Source/ShooterGame/Private/Bots/BTTask_FindNearestPoly.cpp.bak new file mode 100644 index 0000000..0dc79e7 --- /dev/null +++ b/Source/ShooterGame/Private/Bots/BTTask_FindNearestPoly.cpp.bak @@ -0,0 +1,167 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#include "ShooterGame.h" +#include "Bots/BTTask_FindNearestPoly.h" +#include "BehaviorTree/BehaviorTreeComponent.h" +#include "BehaviorTree/BlackboardComponent.h" +#include "BehaviorTree/Blackboard/BlackboardKeyAllTypes.h" +#include "Bots/ShooterAIController.h" +#include "Bots/ShooterBot.h" +#include "Runtime/NavigationSystem/Public/NavigationSystem.h" +#include "Public/Navigation/MyRecastNavMesh.h" + +UBTTask_FindNearestPoly::UBTTask_FindNearestPoly(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +EBTNodeResult::Type UBTTask_FindNearestPoly::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) +{ + AShooterAIController* MyController = Cast(OwnerComp.GetAIOwner()); + AShooterBot* MyBot = MyController ? Cast(MyController->GetPawn()) : NULL; + if (MyBot == NULL) + { + return EBTNodeResult::Failed; + } + + AShooterGameMode* GameMode = MyBot->GetWorld()->GetAuthGameMode(); + if (GameMode == NULL) + { + return EBTNodeResult::Failed; + } + + const FNavAgentProperties* AgentProperties = MyBot->GetNavAgentProperties(); + + if (!AgentProperties) + { + return EBTNodeResult::Failed; + } + + UNavigationSystem* NavSys = GetWorld()->GetNavigationSystem(); + if (!NavSys) { + return EBTNodeResult::Failed; + } + + //FNavigationSystem::ECreateIfEmpty CreateNewIfNoneFound; + const ANavigationData* NavData = NavSys->GetMainNavData(/*FNavigationSystem::DontCreate*/); + + //const ANavigationData* NavData = (AgentProperties != NULL) ? GetNavDataForProps(*AgentProperties) : GetMainNavData(FNavigationSystem::DontCreate); + const ARecastNavMesh* NavMesh = Cast(NavData); + const AMyRecastNavMesh* MyNavMesh = Cast(NavMesh); + + //MyNavMesh->UpdateNavMesh(); + //OwnerComp.GetBlackboardComponent()->SetValue(BlackboardKey.GetSelectedKeyID(), true); + return EBTNodeResult::Succeeded; +} + +/* +EBTNodeResult::Type UBTTask_FindNearestPoly::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) +{ + AShooterAIController* MyController = Cast(OwnerComp.GetAIOwner()); + AShooterBot* MyBot = MyController ? Cast(MyController->GetPawn()) : NULL; + if (MyBot == NULL) + { + return EBTNodeResult::Failed; + } + + AShooterGameMode* GameMode = MyBot->GetWorld()->GetAuthGameMode(); + if (GameMode == NULL) + { + return EBTNodeResult::Failed; + } + + const FVector MyLoc = MyBot->GetActorLocation(); + + const FNavAgentProperties* AgentProperties = MyBot->GetNavAgentProperties(); + + if (!AgentProperties) + { + return EBTNodeResult::Failed; + } + + UNavigationSystem* NavSys = GetWorld()->GetNavigationSystem(); + if (!NavSys) { + return EBTNodeResult::Failed; + } + + FNavigationSystem::ECreateIfEmpty CreateNewIfNoneFound; + const ANavigationData* NavData = NavSys->GetMainNavData(CreateNewIfNoneFound); + + //const ANavigationData* NavData = (AgentProperties != NULL) ? GetNavDataForProps(*AgentProperties) : GetMainNavData(FNavigationSystem::DontCreate); + const ARecastNavMesh* NavMesh = Cast(NavData); + + if (!NavMesh) + { + return EBTNodeResult::Failed; + } + + NavNodeRef NearestPoly = NavMesh->FindNearestPoly(MyLoc, FVector(10.0f, 10.0f, 10.f), NavData->GetDefaultQueryFilter()); + + if (!NearestPoly) + { + return EBTNodeResult::Failed; + } + + //DrawDebugPoint(GetWorld(), MyLoc, 10.0, FColor(255, 0, 0), 10.0, 0.03); + + return EBTNodeResult::Succeeded; + + +} +*/ +/* +EBTNodeResult::Type UBTTask_FindNearestPoly::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) +{ + + AShooterAIController* MyController = Cast(OwnerComp.GetAIOwner()); + AShooterBot* MyBot = MyController ? Cast(MyController->GetPawn()) : NULL; + if (MyBot == NULL) + { + return EBTNodeResult::Failed; + } + + AShooterGameMode* GameMode = MyBot->GetWorld()->GetAuthGameMode(); + if (GameMode == NULL) + { + return EBTNodeResult::Failed; + } + + const FVector MyLoc = MyBot->GetActorLocation(); + const FNavAgentProperties* AgentProperties = MyBot->GetNavAgentProperties(); + + if (!AgentProperties) + { + return EBTNodeResult::Failed; + } + + UNavigationSystem* NavSys = GetWorld()->GetNavigationSystem(); + if (!NavSys) { + return EBTNodeResult::Failed; + } + + FNavigationSystem::ECreateIfEmpty CreateNewIfNoneFound; + const ANavigationData* NavData = NavSys->GetMainNavData(CreateNewIfNoneFound); + + //const ANavigationData* NavData = (AgentProperties != NULL) ? GetNavDataForProps(*AgentProperties) : GetMainNavData(FNavigationSystem::DontCreate); + const ARecastNavMesh* NavMesh = Cast(NavData); + + if (!NavMesh) + { + return EBTNodeResult::Failed; + } + + NavNodeRef NearestPoly = NavMesh->FindNearestPoly(MyLoc, FVector(10.0f, 10.0f, 10.f), NavData->GetDefaultQueryFilter()); + + if (!NearestPoly) + { + return EBTNodeResult::Failed; + } + + //DrawDebugPoint(GetWorld(), MyLoc, 10.0, FColor(255, 0, 0), 10.0, 0.03); + + return EBTNodeResult::Succeeded; + + + +} +*/ \ No newline at end of file diff --git a/Source/ShooterGame/Private/Bots/BTTask_FindPickup.cpp b/Source/ShooterGame/Private/Bots/BTTask_FindPickup.cpp index 957c7a6..a93fe96 100644 --- a/Source/ShooterGame/Private/Bots/BTTask_FindPickup.cpp +++ b/Source/ShooterGame/Private/Bots/BTTask_FindPickup.cpp @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ShooterGame.h" #include "Bots/BTTask_FindPickup.h" diff --git a/Source/ShooterGame/Private/Bots/BTTask_FindPointNearEnemy.cpp b/Source/ShooterGame/Private/Bots/BTTask_FindPointNearEnemy.cpp index 6627ea0..385f38e 100644 --- a/Source/ShooterGame/Private/Bots/BTTask_FindPointNearEnemy.cpp +++ b/Source/ShooterGame/Private/Bots/BTTask_FindPointNearEnemy.cpp @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ShooterGame.h" #include "Bots/BTTask_FindPointNearEnemy.h" @@ -6,6 +6,7 @@ #include "BehaviorTree/BehaviorTreeComponent.h" #include "BehaviorTree/BlackboardComponent.h" #include "BehaviorTree/Blackboard/BlackboardKeyAllTypes.h" +#include "NavigationSystem.h" UBTTask_FindPointNearEnemy::UBTTask_FindPointNearEnemy(const FObjectInitializer& ObjectInitializer) @@ -15,7 +16,6 @@ UBTTask_FindPointNearEnemy::UBTTask_FindPointNearEnemy(const FObjectInitializer& EBTNodeResult::Type UBTTask_FindPointNearEnemy::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) { - /* AShooterAIController* MyController = Cast(OwnerComp.GetAIOwner()); if (MyController == NULL) { @@ -28,13 +28,14 @@ EBTNodeResult::Type UBTTask_FindPointNearEnemy::ExecuteTask(UBehaviorTreeCompone { const float SearchRadius = 200.0f; const FVector SearchOrigin = Enemy->GetActorLocation() + 600.0f * (MyBot->GetActorLocation() - Enemy->GetActorLocation()).GetSafeNormal(); - const FVector Loc = UNavigationSystem::GetRandomReachablePointInRadius(MyController, SearchOrigin, SearchRadius); + FVector Loc(0); + UNavigationSystemV1::K2_GetRandomReachablePointInRadius(MyController, SearchOrigin, Loc, SearchRadius); if (Loc != FVector::ZeroVector) { OwnerComp.GetBlackboardComponent()->SetValue(BlackboardKey.GetSelectedKeyID(), Loc); return EBTNodeResult::Succeeded; } } - */ + return EBTNodeResult::Failed; } diff --git a/Source/ShooterGame/Private/Bots/ShooterAIController.cpp b/Source/ShooterGame/Private/Bots/ShooterAIController.cpp index a598010..86f30fb 100644 --- a/Source/ShooterGame/Private/Bots/ShooterAIController.cpp +++ b/Source/ShooterGame/Private/Bots/ShooterAIController.cpp @@ -30,6 +30,7 @@ AShooterAIController::AShooterAIController(const FObjectInitializer& ObjectIniti // Setup AIPerception component AIPerceptionComp = CreateDefaultSubobject(TEXT("AIPerception Component")); + AIPerceptionSightConfig = CreateDefaultSubobject(TEXT("Sight Config")); // Configure Sight perception component AIPerceptionSightConfig->SightRadius = 4500; @@ -48,16 +49,16 @@ AShooterAIController::AShooterAIController(const FObjectInitializer& ObjectIniti AIPerceptionHearingConfig->DetectionByAffiliation.bDetectNeutrals = true; AIPerceptionHearingConfig->DetectionByAffiliation.bDetectFriendlies = true; AIPerceptionComp->ConfigureSense(*AIPerceptionHearingConfig); - - AIPerceptionComp->OnPerceptionUpdated.AddDynamic(this, &AShooterAIController::OnPerceptionUpdated); + + //AIPerceptionComp->OnPerceptionUpdated.AddDynamic(this, &AShooterAIController::OnPerceptionUpdated); bWantsPlayerState = true; } -void AShooterAIController::Possess(APawn* InPawn) +void AShooterAIController::OnPossess(APawn* InPawn) { - Super::Possess(InPawn); + Super::OnPossess(InPawn); AShooterBot* Bot = Cast(InPawn); UAIPerceptionSystem::RegisterPerceptionStimuliSource(this, UAISense_Sight::StaticClass(), InPawn); @@ -89,11 +90,12 @@ void AShooterAIController::BeginInactiveState() { Super::BeginInactiveState(); - AGameState* GameState = GetWorld()->GameState; + AGameStateBase const* const GameState = GetWorld()->GetGameState(); - const float MinRespawnDelay = (GameState && GameState->GameModeClass) ? GetDefault(GameState->GameModeClass)->MinRespawnDelay : 1.0f; + const float MinRespawnDelay = GameState ? GameState->GetPlayerRespawnDelay(this) : 1.0f; GetWorldTimerManager().SetTimer(TimerHandle_Respawn, this, &AShooterAIController::Respawn, MinRespawnDelay); + } void AShooterAIController::Respawn() @@ -101,6 +103,25 @@ void AShooterAIController::Respawn() GetWorld()->GetAuthGameMode()->RestartPlayer(this); } +void AShooterAIController::SetEnemy(class APawn* InPawn) +{ + if (BlackboardComp) + { + BlackboardComp->SetValue(EnemyKeyID, InPawn); + SetFocus(InPawn); + } +} + +class AShooterCharacter* AShooterAIController::GetEnemy() const +{ + if (BlackboardComp) + { + return Cast(BlackboardComp->GetValue(EnemyKeyID)); + } + + return NULL; +} + bool AShooterAIController::HasWeaponLOSToEnemy(AActor* InEnemyActor, const bool bAnyEnemy) const { static FName LosTag = FName(TEXT("AIWeaponLosTrace")); @@ -110,7 +131,7 @@ bool AShooterAIController::HasWeaponLOSToEnemy(AActor* InEnemyActor, const bool bool bHasLOS = false; // Perform trace to retrieve hit info FCollisionQueryParams TraceParams(LosTag, true, GetPawn()); - TraceParams.bTraceAsyncScene = true; + //TraceParams.bTraceAsyncScene = true; TraceParams.bReturnPhysicalMaterial = true; FVector StartLocation = MyBot->GetActorLocation(); @@ -287,13 +308,13 @@ void AShooterAIController::SetAI_SearchLocations(ASearchLocations * SearchLocati void AShooterAIController::SetAI_NextSearchLocation(const FVector SearchLocation) { TMap * NewSearchPositions = GetAI_SearchLocations(); - APawn * Pawn = this->GetPawn(); + APawn* pawn = this->GetPawn(); - if (!Pawn) { + if (!pawn) { return; } - NewSearchPositions->Add(Pawn->GetName(), SearchLocation); + NewSearchPositions->Add(pawn->GetName(), SearchLocation); BlackboardComp->SetValueAsVector("AI_sNextLocation", SearchLocation); //SetFocalPoint(SearchLocation, EAIFocusPriority::Default); @@ -317,16 +338,16 @@ void AShooterAIController::LookAround(const float RotationAngle, const float Rot AActor * actor = GetFocusActor(); FVector Point = GetFocalPoint(); - APawn * Pawn = this->GetPawn(); + APawn * pawn = GetPawn(); - if (!Pawn) { + if (!pawn) { return; } //const FRotator Rotation = Pawn->GetActorRotation() + FRotator(0, RotationAngle, 0); //Pawn->FaceRotation(Rotation, RotationTime); //Pawn->AddActorLocalRotation(FRotator(0, RotationAngle, 0)); - Pawn->SetActorRelativeRotation(FRotator(0, RotationAngle, 0)); + pawn->SetActorRelativeRotation(FRotator(0, RotationAngle, 0)); //this->SetControlRotation(FRotator(0, RotationAngle, 0)); //this->ClientSetRotation(FRotator(0, RotationAngle, 0)); @@ -444,8 +465,8 @@ FVector AShooterAIController::GetAI_fNextAttackLocation() const { } void AShooterAIController::SetAI_fNextAttackLocation(const FVector AttackLocation) { - APawn * Pawn = this->GetPawn(); - if (!Pawn) { + APawn * pawn = this->GetPawn(); + if (!pawn) { return ; } @@ -453,7 +474,7 @@ void AShooterAIController::SetAI_fNextAttackLocation(const FVector AttackLocatio BlackboardComp->SetValueAsVector("AI_fNextAttackLocation", AttackLocation); TMap * AttackPositions = GetAI_fAttackLocations(); - AttackPositions->Add(Pawn->GetName(), AttackLocation); + AttackPositions->Add(pawn->GetName(), AttackLocation); } @@ -485,9 +506,9 @@ bool AShooterAIController::IsLineOfSightClearOfBots() const { FHitResult OutHit; FCollisionQueryParams CollisionParams; - APawn* Pawn = GetPawn(); + APawn* pawn = GetPawn(); - if (!Pawn) { + if (!pawn) { return true; } @@ -497,7 +518,7 @@ bool AShooterAIController::IsLineOfSightClearOfBots() const { return true; } - FVector ViewPoint = Pawn->GetActorLocation(); + FVector ViewPoint = pawn->GetActorLocation(); if (ViewPoint.IsZero()) { @@ -514,7 +535,7 @@ bool AShooterAIController::IsLineOfSightClearOfBots() const { static FName NAME_LineOfSight = FName(TEXT("LineOfSight")); FVector TargetLocation = GetFocalPoint(); - CollisionParams.AddIgnoredActor(Pawn); + CollisionParams.AddIgnoredActor(pawn); bool bHit = GetWorld()->LineTraceSingleByChannel(OutHit, ViewPoint, TargetLocation, COLLISION_WEAPON, CollisionParams); if (bHit && OutHit.Actor->GetName().Contains("Bot")) @@ -594,7 +615,7 @@ void AShooterAIController::ReloadWeapon() /************************* RUNTIME UPDATES **************************/ -void AShooterAIController::OnPerceptionUpdated(TArray updatedActors){ +void AShooterAIController::OnPerceptionUpdated(const TArray& updatedActors){ APawn* Player = NULL; UWorld * World = GetWorld(); @@ -787,7 +808,7 @@ void AShooterAIController::UpdateMyVisibility() { const FVector UpMyLocation = FVector(MyLocation.X, MyLocation.Y, HelperMethods::EYES_POS_Z); - FCollisionQueryParams CollisionParams; + //FCollisionQueryParams CollisionParams; UGameplayStatics::GetAllActorsOfClass(GetWorld(), AShooterCharacter::StaticClass(), ActorsToIgnore); CollisionParams.AddIgnoredActors(ActorsToIgnore); const bool BlockingHitFound = World->LineTraceSingleByChannel(OutHit, UpMyLocation, PlayerLastLocUp, ECollisionChannel::ECC_Visibility, CollisionParams); @@ -1004,7 +1025,7 @@ bool AShooterAIController::PositionIsGoodAttack(const FVector AttackPosition, co for (float X = AttackPosition.X - 250; X < AttackPosition.X + 250; X += 10) { for (float Y = AttackPosition.Y - 250; Y < AttackPosition.Y + 250; Y += 10) { const FVector CoverPosition = FVector(X, Y, HelperMethods::EYES_POS_Z); - //UNavigationSystem::ProjectPointToNavigation() + //UNavigationSystemV1::ProjectPointToNavigation() if (PositionIsSafeCover(CoverPosition, PlayerPosition)) { PositionIsGoodAttack = true; diff --git a/Source/ShooterGame/Private/Bots/ShooterAIController.cpp.bak b/Source/ShooterGame/Private/Bots/ShooterAIController.cpp.bak new file mode 100644 index 0000000..d593367 --- /dev/null +++ b/Source/ShooterGame/Private/Bots/ShooterAIController.cpp.bak @@ -0,0 +1,1056 @@ +// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. + +#include "ShooterGame.h" +#include "Bots/ShooterAIController.h" +#include "Bots/ShooterBot.h" +#include "Online/ShooterPlayerState.h" +#include "BehaviorTree/BehaviorTree.h" +#include "BehaviorTree/BehaviorTreeComponent.h" +#include "BehaviorTree/BlackboardComponent.h" +#include "BehaviorTree/Blackboard/BlackboardKeyType_Bool.h" +#include "BehaviorTree/Blackboard/BlackboardKeyType_Object.h" +#include "Weapons/ShooterWeapon.h" +#include "Navigation/CrowdFollowingComponent.h" +#include "Public/Others/HelperMethods.h" +#include "Public/EQS/CoverBaseClass.h" +#include "Perception/AISense_Sight.h" +#include "Perception/AISense_Hearing.h" +#include "Perception/AIPerceptionComponent.h" +#include "Perception/AIPerceptionListenerInterface.h" +#include "Public/Navigation/MyRecastNavMesh.h" + +AShooterAIController::AShooterAIController(const FObjectInitializer& ObjectInitializer) + //: Super(ObjectInitializer) { + : Super(ObjectInitializer.SetDefaultSubobjectClass(TEXT("PathFollowingComponent"))) { + + BlackboardComp = ObjectInitializer.CreateDefaultSubobject(this, TEXT("BlackBoardComp")); + + BrainComponent = BehaviorComp = ObjectInitializer.CreateDefaultSubobject(this, TEXT("BehaviorComp")); + + // Setup AIPerception component + AIPerceptionComp = CreateDefaultSubobject(TEXT("AIPerception Component")); + + AIPerceptionSightConfig = CreateDefaultSubobject(TEXT("Sight Config")); + // Configure Sight perception component + AIPerceptionSightConfig->SightRadius = 4500; + AIPerceptionSightConfig->LoseSightRadius = 4500; + AIPerceptionSightConfig->PeripheralVisionAngleDegrees = 80.0; + AIPerceptionSightConfig->DetectionByAffiliation.bDetectEnemies = true; + AIPerceptionSightConfig->DetectionByAffiliation.bDetectNeutrals = true; + AIPerceptionSightConfig->DetectionByAffiliation.bDetectFriendlies = true; + AIPerceptionComp->ConfigureSense(*AIPerceptionSightConfig); + + // Configure Hearing perception component + + AIPerceptionHearingConfig = CreateDefaultSubobject(TEXT("Hearing Config")); + AIPerceptionHearingConfig->HearingRange = 4500; + AIPerceptionHearingConfig->DetectionByAffiliation.bDetectEnemies = true; + AIPerceptionHearingConfig->DetectionByAffiliation.bDetectNeutrals = true; + AIPerceptionHearingConfig->DetectionByAffiliation.bDetectFriendlies = true; + AIPerceptionComp->ConfigureSense(*AIPerceptionHearingConfig); + + AIPerceptionComp->reinterpret_cast(OnPerceptionUpdated.AddDynamic(this, &AShooterAIController::OnPerceptionUpdated)); + + bWantsPlayerState = true; +} + + +void AShooterAIController::OnPossess(APawn* InPawn) +{ + Super::OnPossess(InPawn); + AShooterBot* Bot = Cast(InPawn); + + UAIPerceptionSystem::RegisterPerceptionStimuliSource(this, UAISense_Sight::StaticClass(), InPawn); + UAIPerceptionSystem::RegisterPerceptionStimuliSource(this, UAISense_Hearing::StaticClass(), InPawn); + + // Start behavior tree + if (Bot && Bot->BotBehavior) + { + if (Bot->BotBehavior->BlackboardAsset) + { + BlackboardComp->InitializeBlackboard(*Bot->BotBehavior->BlackboardAsset); + } + EnemyKeyID = BlackboardComp->GetKeyID("Enemy"); + NeedAmmoKeyID = BlackboardComp->GetKeyID("NeedAmmo"); + + // Start behavior tree + BehaviorComp->StartTree(*(Bot->BotBehavior)); + + AAttackPositions * AttackLocations = (AAttackPositions*) GetWorld()->SpawnActor(AAttackPositions::StaticClass()); + ASearchLocations * SearchLocations = (ASearchLocations*) GetWorld()->SpawnActor(ASearchLocations::StaticClass()); + + SetAI_fAttackLocations(AttackLocations); + SetAI_SearchLocations(SearchLocations); + } + +} + +void AShooterAIController::BeginInactiveState() +{ + Super::BeginInactiveState(); + + AGameStateBase const* const GameState = GetWorld()->GetGameState(); + + const float MinRespawnDelay = GameState ? GameState->GetPlayerRespawnDelay(this) : 1.0f; + + GetWorldTimerManager().SetTimer(TimerHandle_Respawn, this, &AShooterAIController::Respawn, MinRespawnDelay); + +} + +void AShooterAIController::Respawn() +{ + GetWorld()->GetAuthGameMode()->RestartPlayer(this); +} + +void AShooterAIController::SetEnemy(class APawn* InPawn) +{ + if (BlackboardComp) + { + BlackboardComp->SetValue(EnemyKeyID, InPawn); + SetFocus(InPawn); + } +} + +class AShooterCharacter* AShooterAIController::GetEnemy() const +{ + if (BlackboardComp) + { + return Cast(BlackboardComp->GetValue(EnemyKeyID)); + } + + return NULL; +} + +bool AShooterAIController::HasWeaponLOSToEnemy(AActor* InEnemyActor, const bool bAnyEnemy) const +{ + static FName LosTag = FName(TEXT("AIWeaponLosTrace")); + + AShooterBot* MyBot = Cast(GetPawn()); + + bool bHasLOS = false; + // Perform trace to retrieve hit info + FCollisionQueryParams TraceParams(LosTag, true, GetPawn()); + //TraceParams.bTraceAsyncScene = true; + + TraceParams.bReturnPhysicalMaterial = true; + FVector StartLocation = MyBot->GetActorLocation(); + StartLocation.Z += GetPawn()->BaseEyeHeight; //look from eyes + + FHitResult Hit(ForceInit); + const FVector EndLocation = InEnemyActor->GetActorLocation(); + GetWorld()->LineTraceSingleByChannel(Hit, StartLocation, EndLocation, COLLISION_WEAPON, TraceParams); + if (Hit.bBlockingHit == true) + { + // Theres a blocking hit - check if its our enemy actor + AActor* HitActor = Hit.GetActor(); + if (Hit.GetActor() != NULL) + { + if (HitActor == InEnemyActor) + { + bHasLOS = true; + } + else if (bAnyEnemy == true) + { + // Its not our actor, maybe its still an enemy ? + ACharacter* HitChar = Cast(HitActor); + if (HitChar != NULL) + { + AShooterPlayerState* HitPlayerState = Cast(HitChar->PlayerState); + AShooterPlayerState* MyPlayerState = Cast(PlayerState); + if ((HitPlayerState != NULL) && (MyPlayerState != NULL)) + { + if (HitPlayerState->GetTeamNum() != MyPlayerState->GetTeamNum()) + { + bHasLOS = true; + } + } + } + } + } + } + return bHasLOS; +} + +void AShooterAIController::CheckAmmo(const class AShooterWeapon* CurrentWeapon) +{ + if (CurrentWeapon && BlackboardComp) + { + const int32 Ammo = CurrentWeapon->GetCurrentAmmo(); + const int32 MaxAmmo = CurrentWeapon->GetMaxAmmo(); + const float Ratio = (float) Ammo / (float) MaxAmmo; + + BlackboardComp->SetValue(NeedAmmoKeyID, (Ratio <= 0.1f)); + } +} + +void AShooterAIController::UpdateControlRotation(float DeltaTime, bool bUpdatePawn) +{ + // Look toward focus + FVector FocalPoint = GetFocalPoint(); + if( !FocalPoint.IsZero() && GetPawn()) + { + FVector Direction = FocalPoint - GetPawn()->GetActorLocation(); + FRotator NewControlRotation = Direction.Rotation(); + + NewControlRotation.Yaw = FRotator::ClampAxis(NewControlRotation.Yaw); + + SetControlRotation(NewControlRotation); + + APawn* const P = GetPawn(); + if (P && bUpdatePawn) + { + P->FaceRotation(NewControlRotation, DeltaTime); + } + + } +} + +void AShooterAIController::GetActorEyesViewPoint(FVector & OutLocation, FRotator & OutRotation) const +{ + if (this->GetPawn()) { + OutLocation = FVector(this->GetPawn()->GetActorLocation().X, this->GetPawn()->GetActorLocation().Y, this->GetPawn()->GetActorLocation().Z + 60); + OutRotation = this->GetPawn()->GetViewRotation(); + } +} + +void AShooterAIController::GameHasEnded(AActor* EndGameFocus, bool bIsWinner) +{ + // Stop the behaviour tree/logic + BehaviorComp->StopTree(); + + // Stop any movement we already have + StopMovement(); + + // Cancel the repsawn timer + GetWorldTimerManager().ClearTimer(TimerHandle_Respawn); + + // Clear any enemy + SetPL_fPlayer(NULL); + + // Finally stop firing + AShooterBot* MyBot = Cast(GetPawn()); + AShooterWeapon* MyWeapon = MyBot ? MyBot->GetWeapon() : NULL; + if (MyWeapon == NULL) + { + return; + } + MyBot->StopWeaponFire(); +} + +//----------------------------------------------------------------------// +// Artificial Intelligence @ Mariano Trebino +//----------------------------------------------------------------------// + + +/************************* GENERAL **************************/ + +State AShooterAIController::GetAI_State() const { + return (State) BlackboardComp->GetValueAsEnum("AI_gState"); +} + +void AShooterAIController::SetAI_State(const State BotState) { + if (GetAI_State() != BotState) { + BlackboardComp->SetValueAsEnum("AI_gState", (uint8)BotState); + } +} + + +/************************* PATROL **************************/ + +void AShooterAIController::SetAI_pNextLocation() { + FVector NextPatrolPoint; + TArray AttachedActors; + TArray MyPatrolPoints; + + AShooterBot* Bot = Cast(GetPawn()); + Bot->GetAttachedActors(AttachedActors); + + for (auto ItAttachedActor = AttachedActors.CreateConstIterator(); ItAttachedActor; ++ItAttachedActor) { + AActor * AttachedActor = *ItAttachedActor; + UE_LOG(LogTemp, Warning, TEXT("PatrolPoint's Name is %s"), *AttachedActor->GetName()); + if (AttachedActor->GetName().Contains("Patrol")) { + MyPatrolPoints.Add(AttachedActor->GetActorLocation()); + } + } + + if (MyPatrolPoints.Num() > 0 && CurrentPatrolPointIndex == MyPatrolPoints.Num()) { + CurrentPatrolPointIndex = 0; + NextPatrolPoint = MyPatrolPoints[CurrentPatrolPointIndex]; + ++CurrentPatrolPointIndex; + } + else { + NextPatrolPoint = MyPatrolPoints[CurrentPatrolPointIndex]; + ++CurrentPatrolPointIndex; + } + BlackboardComp->SetValueAsVector("AI_pNextLocation", NextPatrolPoint); + +} + + +/************************* SEARCH **************************/ + +TMap * AShooterAIController::GetAI_SearchLocations() { + TMap * SearchPositions = NULL; + + if (BlackboardComp) { + UObject * Object = BlackboardComp->GetValueAsObject("AI_sLocations"); + if (Object) { + SearchPositions = Cast(Object)->SearchMap; + } + } + return SearchPositions; +} + +void AShooterAIController::SetAI_SearchLocations(ASearchLocations * SearchLocations) { + BlackboardComp->SetValueAsObject("AI_sLocations", SearchLocations); +} + +void AShooterAIController::SetAI_NextSearchLocation(const FVector SearchLocation) { + TMap * NewSearchPositions = GetAI_SearchLocations(); + APawn* pawn = this->GetPawn(); + + if (!pawn) { + return; + } + + NewSearchPositions->Add(pawn->GetName(), SearchLocation); + BlackboardComp->SetValueAsVector("AI_sNextLocation", SearchLocation); + //SetFocalPoint(SearchLocation, EAIFocusPriority::Default); + +} + +AMyInfluenceMap * AShooterAIController::GetAI_PredictionMap() { + return Cast(BlackboardComp->GetValueAsObject("AI_sPredictionMap")); + +} + +void AShooterAIController::SetAI_PredictionMap(AMyInfluenceMap * PredictionMap) { + BlackboardComp->SetValueAsObject("AI_sPredictionMap", PredictionMap); +} + + +void AShooterAIController::LookAround(const float RotationAngle, const float RotationTime) { + ClearFocus(EAIFocusPriority::Gameplay); + ClearFocus(EAIFocusPriority::Move); + ClearFocus(EAIFocusPriority::LastFocusPriority); + ClearFocus(EAIFocusPriority::Default); + + AActor * actor = GetFocusActor(); + FVector Point = GetFocalPoint(); + APawn * pawn = GetPawn(); + + if (!pawn) { + return; + } + + //const FRotator Rotation = Pawn->GetActorRotation() + FRotator(0, RotationAngle, 0); + //Pawn->FaceRotation(Rotation, RotationTime); + //Pawn->AddActorLocalRotation(FRotator(0, RotationAngle, 0)); + pawn->SetActorRelativeRotation(FRotator(0, RotationAngle, 0)); + //this->SetControlRotation(FRotator(0, RotationAngle, 0)); + //this->ClientSetRotation(FRotator(0, RotationAngle, 0)); + +} + +/************************* FIGHT **************************/ + +APawn* AShooterAIController::GetPL_fPlayer() const { + return Cast(BlackboardComp->GetValueAsObject("PL_fPlayer")); +} + +void AShooterAIController::SetPL_fPlayer(APawn* Player) { + BlackboardComp->SetValueAsObject("PL_fPlayer", Player); + if (Player != NULL) { + SetFocus(Player, EAIFocusPriority::Gameplay); + } + else { + ClearFocus(EAIFocusPriority::Gameplay); + SetFocalPoint(GetPL_fLocation(), EAIFocusPriority::Gameplay); + SetPL_fIamVisible(false); + } +} + +bool AShooterAIController::GetAI_fTakingDamage() const { + return BlackboardComp->GetValueAsBool("AI_fTakingDamage"); +} + +void AShooterAIController::SetAI_fTakingDamage(const bool ReceivingDamage) { + BlackboardComp->SetValueAsBool("AI_fTakingDamage", ReceivingDamage); +} + +FVector AShooterAIController::GetAI_fNextCoverLocation() const { + return BlackboardComp->GetValueAsVector("AI_fNextCoverLocation"); +} + +void AShooterAIController::SetAI_fNextCoverLocation(const FVector NextCoverLocation) { + BlackboardComp->SetValueAsVector("AI_fNextCoverLocation", NextCoverLocation); +} + +bool AShooterAIController::GetAI_fNextCoverLocationIsSafe() const { + return BlackboardComp->GetValueAsBool("AI_fNextCoverLocationIsSafe"); +} + +void AShooterAIController::SetAI_fNextCoverLocationIsSafe(const bool IsSafe) { + BlackboardComp->SetValueAsBool("AI_fNextCoverLocationIsSafe", IsSafe); +} + +void AShooterAIController::SetAI_fCurrentLocationIsSafe(const bool IsSafe) { + BlackboardComp->SetValueAsBool("AI_fCurrentLocationIsSafe", IsSafe); +} + +bool AShooterAIController::GetAI_fNeedReloadNow() const { + return BlackboardComp->GetValueAsBool("AI_fNeedReloadNow"); +} + +void AShooterAIController::SetAI_fNeedReloadNow(const bool NeedReloadNow) { + BlackboardComp->SetValueAsBool("AI_fNeedReloadNow", NeedReloadNow); +} + +bool AShooterAIController::GetAI_fNeedReloadSoon() const { + return BlackboardComp->GetValueAsBool("AI_fNeedReloadSoon"); +} + +void AShooterAIController::SetAI_fNeedReloadSoon(const bool NeedReloadSoon) { + BlackboardComp->SetValueAsBool("AI_fNeedReloadSoon", NeedReloadSoon); +} + +bool AShooterAIController::GetPL_fIsVisible() const { + return BlackboardComp->GetValueAsBool("PL_fIsVisible"); +} + +void AShooterAIController::SetPL_fIsVisible(const bool IsVisible) { + BlackboardComp->SetValueAsBool("PL_fIsVisible", IsVisible); +} + +bool AShooterAIController::GetPL_fLost() const { + return BlackboardComp->GetValueAsBool("PL_fLost"); +} + +void AShooterAIController::SetPL_fLost(const bool PlayerLost) { + BlackboardComp->SetValueAsBool("PL_fLost", PlayerLost); + +} + +bool AShooterAIController::GetPL_fIamVisible() const { + return BlackboardComp->GetValueAsBool("PL_fIamVisible"); +} + +void AShooterAIController::SetPL_fIamVisible(const bool PlayerCanSeeMe) { + BlackboardComp->SetValueAsBool("PL_fIamVisible", PlayerCanSeeMe); +} + +FVector AShooterAIController::GetPL_fLocation() { + const FVector LastLocation = BlackboardComp->GetValueAsVector("PL_fLocation"); + //SetFocalPoint(LastLocation, EAIFocusPriority::Move); + return LastLocation; +} + +void AShooterAIController::SetPL_fLocation(const FVector LastLocation) { + NeverSawPlayer = false; + BlackboardComp->SetValueAsVector("PL_fLocation", LastLocation); +} + +FVector AShooterAIController::GetPL_fForwardVector() { + return BlackboardComp->GetValueAsVector("PL_fForwardVector");; +} + +void AShooterAIController::SetPL_fForwardVector(const FVector ForwardVector) { + BlackboardComp->SetValueAsVector("PL_fForwardVector", ForwardVector); +} + + +FVector AShooterAIController::GetAI_fNextAttackLocation() const { + return BlackboardComp->GetValueAsVector("AI_fNextAttackLocation"); +} + +void AShooterAIController::SetAI_fNextAttackLocation(const FVector AttackLocation) { + APawn * pawn = this->GetPawn(); + if (!pawn) { + return ; + } + + NeverSawPlayer = false; + BlackboardComp->SetValueAsVector("AI_fNextAttackLocation", AttackLocation); + + TMap * AttackPositions = GetAI_fAttackLocations(); + AttackPositions->Add(pawn->GetName(), AttackLocation); + +} + +void AShooterAIController::SetAI_fNextAttackLocationIsOK(const bool NextLocationIsAttack) { + BlackboardComp->SetValueAsBool("AI_fNextAttackLocationIsOK", NextLocationIsAttack); +} + +void AShooterAIController::SetAI_fCurrentLocationIsAttack(const bool CurrentLocationIsGoodAttack) { + BlackboardComp->SetValueAsBool("AI_fCurrentLocationIsAttack", CurrentLocationIsGoodAttack); +} + +TMap * AShooterAIController::GetAI_fAttackLocations() { + TMap * AttackPositions = NULL; + + if (BlackboardComp) { + UObject * Object = BlackboardComp->GetValueAsObject("AI_fAttackLocations"); + if (Object) { + AttackPositions = Cast(Object)->AttackMap; + } + } + return AttackPositions; +} + +void AShooterAIController::SetAI_fAttackLocations(AAttackPositions * AttackPositions) { + BlackboardComp->SetValueAsObject("AI_fAttackLocations", AttackPositions); +} + +bool AShooterAIController::IsLineOfSightClearOfBots() const { + FHitResult OutHit; + FCollisionQueryParams CollisionParams; + + APawn* pawn = GetPawn(); + + if (!pawn) { + return true; + } + + UWorld * World = GetWorld(); + + if (!World) { + return true; + } + + FVector ViewPoint = pawn->GetActorLocation(); + + if (ViewPoint.IsZero()) + { + FRotator ViewRotation; + GetActorEyesViewPoint(ViewPoint, ViewRotation); + + // if we still don't have a view point we simply fail + if (ViewPoint.IsZero()) + { + return true; + } + } + + static FName NAME_LineOfSight = FName(TEXT("LineOfSight")); + FVector TargetLocation = GetFocalPoint(); + + CollisionParams.AddIgnoredActor(pawn); + + bool bHit = GetWorld()->LineTraceSingleByChannel(OutHit, ViewPoint, TargetLocation, COLLISION_WEAPON, CollisionParams); + if (bHit && OutHit.Actor->GetName().Contains("Bot")) + { + return false; + } + //AActor * actor = OutHit.GetActor(); + //UE_LOG(LogTemp, Warning, TEXT("MyCharacter's Name is %s"), *actor->GetName()); + + return true; +} + + +void AShooterAIController::SetAI_fIsClose(const bool PlayerIsClose) { + BlackboardComp->SetValueAsBool("AI_fIsClose", PlayerIsClose); +} + + +/************************* WEAPON **************************/ + +void AShooterAIController::StartWeaponFire() +{ + AShooterBot* MyBot = Cast(GetPawn()); + AShooterWeapon* MyWeapon = MyBot ? MyBot->GetWeapon() : NULL; + if (MyWeapon == NULL) + { + return; + } + + bool bCanShoot = false; + AShooterCharacter* Player = Cast(GetPL_fPlayer()); + //if (Player && (Player->IsAlive()) && (MyWeapon->GetCurrentAmmo() > 0) && (MyWeapon->CanFire() == true)) + if ((MyWeapon->GetCurrentAmmo() > 0) && (MyWeapon->CanFire() == true)) + { + if (IsLineOfSightClearOfBots()) { + bCanShoot = true; + } + //if (LineOfSightTo(Player, MyBot->GetActorLocation())) + //{ + // bCanShoot = true; + //} + } + + if (bCanShoot) + { + MyBot->StartWeaponFire(); + } + else + { + MyBot->StopWeaponFire(); + } +} + +void AShooterAIController::StopWeaponFire() +{ + AShooterBot* MyBot = Cast(GetPawn()); + AShooterWeapon* MyWeapon = MyBot ? MyBot->GetWeapon() : NULL; + if (MyWeapon == NULL) + { + return; + } + + MyBot->StopWeaponFire(); +} + +void AShooterAIController::ReloadWeapon() +{ + AShooterBot* MyBot = Cast(GetPawn()); + AShooterWeapon* MyWeapon = MyBot ? MyBot->GetWeapon() : NULL; + if (MyWeapon == NULL) + { + return; + } + MyWeapon->StartReload(); +} + + +/************************* RUNTIME UPDATES **************************/ + +void AShooterAIController::OnPerceptionUpdated(TArray updatedActors){ + APawn* Player = NULL; + + UWorld * World = GetWorld(); + if (World) { + Player = UGameplayStatics::GetPlayerPawn(World, 0); + } + + if (!Player) { + return; + } + FVector PlayerLocation = Player->GetActorLocation(); + + if (Player) { + for (int32 i = 0; i < updatedActors.Num(); ++i) { + if (Player == updatedActors[i]) { + // Player has been seen + if (!GetPL_fPlayer()) { + SetPL_fPlayer(Player); + } + else { + SetPL_fPlayer(NULL); + } + break; + } + else { + if (updatedActors[i]) { + AActor * Parent = updatedActors[i]->GetAttachParentActor(); + if ((Parent && Cast(Parent) && !Cast(Parent))) { + // Player has been heard + SetPL_fLocation(Player->GetActorLocation()); + } + } + } + } + } +} + +void AShooterAIController::UpdateData(const float DeltaSeconds) { + UpdateOwnData(DeltaSeconds); + UpdatePlayerRelatedData(DeltaSeconds); + UpdateEnvironmentRelatedData(DeltaSeconds); +} + +void AShooterAIController::UpdateOwnData(const float DeltaSeconds) { + UpdateMyVisibility(); + UpdateBotState(); + UpdateHealthSituation(DeltaSeconds); + UpdateWeaponStats(); +} + +void AShooterAIController::UpdatePlayerRelatedData(const float DeltaSeconds) { + UpdatePlayerVisibility(); + UpdatePlayerIsClose(); + UpdatePlayerLocationAndRotation(DeltaSeconds); + +} + +void AShooterAIController::UpdateEnvironmentRelatedData(const float DeltaSeconds) { + UpdateTacticalCoverSituation(); + UpdateTacticalAttackSituation(); +} + +void AShooterAIController::UpdateBotState() { + const AShooterBot* Bot = Cast(GetPawn()); + + if (GetPL_fPlayer() || !NeverSawPlayer) { + // Knows player or has seen him once at least + if (GetPL_fPlayer()) { + AShooterCharacter * PlayerCharacter = Cast(GetPL_fPlayer()); + if (PlayerCharacter->IsAlive()) { + // Knows player and is alive + SetAI_State(State::VE_Fight); + } + else { + // Knows player and is dead + SetAI_State(State::VE_Idle); + } + } + else if (!NeverSawPlayer){ + if (this->GetAI_State() == State::VE_Patrol || (GetAI_State() == State::VE_Fight && GetPL_fLost())) { + ClearFocus(EAIFocusPriority::Gameplay); + SetAI_State(State::VE_Search); + } + } + } + else if (NeverSawPlayer){ + SetAI_State(State::VE_Patrol); + } +} + +void AShooterAIController::UpdatePlayerIsClose() { + AShooterBot* Bot = Cast(GetPawn()); + bool PlayerIsClose = false; + if (Bot) { + const float PlayerDistance = FVector::Dist(Bot->GetActorLocation(), GetPL_fLocation()); + PlayerIsClose = (PlayerDistance < HelperMethods::MIN_DIST_PLAYER) ? true : false; + } + SetAI_fIsClose(PlayerIsClose); +} + +void AShooterAIController::UpdatePlayerLocationAndRotation(const float DeltaSeconds) { + const APawn * PlayerPawn = GetPL_fPlayer(); + const AShooterBot* Bot = Cast(GetPawn()); + if (Bot && PlayerPawn) { + SetPL_fLocation(PlayerPawn->GetActorLocation()); + SetPL_fForwardVector(PlayerPawn->GetActorForwardVector()); + AMyInfluenceMap * OldInfluenceMap = this->GetAI_PredictionMap(); + if (OldInfluenceMap) { + // We no longer need the map cuz we now exactly where is the player + GetWorld()->DestroyActor(Cast(OldInfluenceMap)); + } + } + else { + if (Temp_PlayerLastLocation != GetPL_fLocation()){ + // We have new information, se lets create a new Map + if (GetAI_PredictionMap()) { + GetWorld()->DestroyActor(Cast(GetAI_PredictionMap())); + } + + AMyInfluenceMap * NewInfluenceMap = GetWorld()->SpawnActor(); + NewInfluenceMap->CreateInfluenceMap(IM_MOMENTUM, IM_DECAY, IM_UPDATE_FREQ, IM_IMAGE_PATH + "_base", IM_IMAGE_PATH); + this->SetAI_PredictionMap(NewInfluenceMap); + NewInfluenceMap->SetInfluence(GetPL_fLocation(), 255.0f); + } + + AMyInfluenceMap * MyInfluenceMap = this->GetAI_PredictionMap(); + if (MyInfluenceMap) { + MyInfluenceMap->SetBotVisibility(Bot->GetName(),HelperMethods::CalculateVisibility(GetWorld(), GetPawn()->GetActorLocation(), GetPawn()->GetActorForwardVector())); + } + + //SetPL_fForwardVector(FVector(2, 2, 2)); + Temp_PlayerLastLocation = GetPL_fLocation(); + } + /* + if (GetAI_PredictionMap()) { + AMyInfluenceMap * InfluenceMap = this->GetAI_PredictionMap(); + + const FVector PlayerLocation = HelperMethods::GetPlayerPositionFromAI(GetWorld()); + const FVector PlayerForwardVector = HelperMethods::GetPlayerForwardVectorFromAI(GetWorld()); + + const TArray VisibleTriangles = HelperMethods::CalculateVisibility(GetWorld(), PlayerLocation, PlayerForwardVector); + + + InfluenceMap->SetBotVisibility(Bot->GetName(), VisibleTriangles); + + } + else { + const FVector PlayerLocation = HelperMethods::GetPlayerPositionFromAI(GetWorld()); + + AMyInfluenceMap * NewInfluenceMap = GetWorld()->SpawnActor(); + NewInfluenceMap->CreateInfluenceMap(IM_MOMENTUM, IM_DECAY, IM_UPDATE_FREQ, IM_IMAGE_PATH + "_base", IM_IMAGE_PATH); + + this->SetAI_PredictionMap(NewInfluenceMap); + NewInfluenceMap->SetInfluence(Bot->GetActorLocation(), 255.0f); + } + + */ +} + +void AShooterAIController::UpdateMyVisibility() { + + APawn * PlayerPawn = GetPL_fPlayer(); + AShooterBot* BotPawn = Cast(GetPawn()); + UWorld * World = GetWorld(); + FHitResult OutHit; + FCollisionQueryParams CollisionParams; + + if (PlayerPawn && BotPawn && World) { + const FVector PlayerLocation = PlayerPawn->GetActorLocation(); + const FVector UpPlayerLocation = FVector(PlayerLocation.X, PlayerLocation.Y, HelperMethods::EYES_POS_Z); + + const FVector MyLocation = BotPawn->GetActorLocation(); + const FVector UpMyLocation = FVector(MyLocation.X, MyLocation.Y, HelperMethods::EYES_POS_Z); + + + CollisionParams.AddIgnoredActor(BotPawn); + + const bool BlockingHitFound = World->LineTraceSingleByChannel(OutHit, UpMyLocation, UpPlayerLocation, ECollisionChannel::ECC_Visibility, CollisionParams); + SetPL_fIsVisible(BlockingHitFound && OutHit.Actor->GetName().Contains("Player")); + } + else if (BotPawn) { + State a = GetAI_State(); + if (GetAI_State() == State::VE_Fight ) { + SetPL_fIsVisible(false); + TArray ActorsToIgnore; + const FVector PlayerLastLoc = GetPL_fLocation(); + const FVector PlayerLastLocUp = FVector(PlayerLastLoc.X, PlayerLastLoc.Y, HelperMethods::EYES_POS_Z); + + const FVector MyLocation = BotPawn->GetActorLocation(); + const FVector UpMyLocation = FVector(MyLocation.X, MyLocation.Y, HelperMethods::EYES_POS_Z); + + + //FCollisionQueryParams CollisionParams; + UGameplayStatics::GetAllActorsOfClass(GetWorld(), AShooterCharacter::StaticClass(), ActorsToIgnore); + CollisionParams.AddIgnoredActors(ActorsToIgnore); + const bool BlockingHitFound = World->LineTraceSingleByChannel(OutHit, UpMyLocation, PlayerLastLocUp, ECollisionChannel::ECC_Visibility, CollisionParams); + SetPL_fLost(!BlockingHitFound); + } + } +} + +void AShooterAIController::UpdatePlayerVisibility() { + AShooterBot* Bot = Cast(GetPawn()); + APawn * PlayerPawn = GetPL_fPlayer(); + if (PlayerPawn) { + const FVector PlayerLocation = PlayerPawn->GetActorLocation(); + const FVector PlayerForwardVector = PlayerPawn->GetActorForwardVector(); + + const TArray VisibleTriangles = HelperMethods::CalculateVisibility(GetWorld(), PlayerLocation, PlayerForwardVector); + + // Update Navigation Mesh + dtQueryFilter_Example::SetPlayerVisibility(VisibleTriangles); + + // Check if I am Visible + bool IAmVisible = false; + FVector PlayerToBot = Bot->GetActorLocation() - PlayerPawn->GetActorLocation(); + PlayerToBot.Normalize(); + const float Angle = FMath::RadiansToDegrees(acosf(PlayerForwardVector.CosineAngle2D(PlayerToBot))); + if (Angle <= HelperMethods::PLAYER_FOV) { + IAmVisible = true; + } + + SetPL_fIamVisible(IAmVisible); + }else { + // @todo prediction + SetPL_fIamVisible(false); + TArray VisibleTriangles; + dtQueryFilter_Example::SetPlayerVisibility(VisibleTriangles); + /* + TArray VisibleTriangles; + if (GetAI_State() == State::VE_Fight) { + const TArray VisibleTriangles1 = HelperMethods::CalculateVisibility(GetWorld(), GetPL_fLocation(), FVector(0, -1, 0)); + const TArray VisibleTriangles2 = HelperMethods::CalculateVisibility(GetWorld(), GetPL_fLocation(), FVector(0, 1, 0)); + const TArray VisibleTriangles3 = HelperMethods::CalculateVisibility(GetWorld(), GetPL_fLocation(), FVector(-1, 0, 0)); + const TArray VisibleTriangles4 = HelperMethods::CalculateVisibility(GetWorld(), GetPL_fLocation(), FVector(1, 0, 0)); + + VisibleTriangles.Append(VisibleTriangles1); + VisibleTriangles.Append(VisibleTriangles2); + VisibleTriangles.Append(VisibleTriangles3); + VisibleTriangles.Append(VisibleTriangles4); + dtQueryFilter_Example::SetPlayerVisibility(VisibleTriangles, 1.001); + } + else { + dtQueryFilter_Example::SetPlayerVisibility(VisibleTriangles, 1.1); + }*/ + } +} + +void AShooterAIController::UpdateTacticalCoverSituation() { + bool NextCoverIsSafe = true; + bool CurrentCoverIsSafe = true; + const AShooterBot* Bot = Cast(GetPawn()); + if (Bot) { + + const FVector BotLocation = Bot->GetActorLocation(); + + if (GetPL_fPlayer()) { + CurrentCoverIsSafe = GetPL_fIamVisible() ? false: PositionIsSafeCover(BotLocation, GetPL_fLocation()); + NextCoverIsSafe = PositionIsSafeCover(GetAI_fNextCoverLocation(), GetPL_fLocation()); + } + else { + // @todo prediction + CurrentCoverIsSafe = PositionIsSafeCover(BotLocation, GetPL_fLocation()); + NextCoverIsSafe = PositionIsSafeCover(GetAI_fNextCoverLocation(), GetPL_fLocation()); + } + } + SetAI_fCurrentLocationIsSafe(CurrentCoverIsSafe); + SetAI_fNextCoverLocationIsSafe(NextCoverIsSafe); +} + +void AShooterAIController::UpdateTacticalAttackSituation() { + bool NextAttackIsGood = true; + bool CurrentAttackIsGood = true; + const AShooterBot* Bot = Cast(GetPawn()); + if (Bot) { + const FVector BotLocation = Bot->GetActorLocation(); + if (GetPL_fPlayer()) { + CurrentAttackIsGood = !GetPL_fIsVisible() ? false : PositionIsGoodAttack(BotLocation, GetPL_fLocation()); + NextAttackIsGood = PositionIsGoodAttack(GetAI_fNextAttackLocation(), GetPL_fLocation()); + } + else { + // @todo prediction + CurrentAttackIsGood = PositionIsGoodAttack(BotLocation, GetPL_fLocation()); + NextAttackIsGood = PositionIsGoodAttack(GetAI_fNextAttackLocation(), GetPL_fLocation()); + } + } + + SetAI_fCurrentLocationIsAttack(CurrentAttackIsGood); + SetAI_fNextAttackLocationIsOK(NextAttackIsGood); +} + +void AShooterAIController::UpdateWeaponStats() { + const AShooterBot* Bot = Cast(GetPawn()); + const AShooterWeapon * Weapon = Bot->GetWeapon(); + + // Weapon Checks + SetAI_fNeedReloadNow((float) (Weapon->GetCurrentAmmoInClip() / (float) Weapon->GetAmmoPerClip()) < 0.10); + SetAI_fNeedReloadSoon(((float) Weapon->GetCurrentAmmoInClip() / (float) Weapon->GetAmmoPerClip()) < 0.40); +} + +void AShooterAIController::UpdateHealthSituation(float DeltaSeconds) { + const AShooterBot* Bot = Cast(GetPawn()); + + if (Bot) { + if (GetAI_fTakingDamage() && Health_timer >= HelperMethods::WAIT_FOR_REGEN + FMath::RandRange(0, 7)) { + Health_timer = 0; + SetAI_fTakingDamage(false); + } + else if(GetAI_fTakingDamage()) { + Health_timer += DeltaSeconds; + } + else if (Health_lastValue != 0){ + SetAI_fTakingDamage(FMath::Abs(Bot->Health - Health_lastValue) > HelperMethods::RECEIVING_DMG); + + } + Health_lastValue = Bot->Health; + } + +} + +/************************* AUX PRIVATE **************************/ + +bool AShooterAIController::PositionIsSafeCover(const FVector CoverPosition, const FVector PlayerPosition) const { + bool PositionIsSafeCover = false; + + if (CoverPosition == FVector(0, 0, 0)) { + PositionIsSafeCover; + } + + AShooterBot* MyBot = Cast(GetPawn()); + + if (!MyBot) { + return PositionIsSafeCover; + } + + UWorld * World = GetWorld(); + if (!World) { + return PositionIsSafeCover; + } + + // Check Behind obstacle <- Player cant see me + FHitResult OutHit; + FCollisionQueryParams CollisionParams; + TArray ActorsToIgnore; + + UGameplayStatics::GetAllActorsOfClass(GetWorld(), AShooterBot::StaticClass(), ActorsToIgnore); + CollisionParams.AddIgnoredActors(ActorsToIgnore); + + const FVector UpPlayerLocation = FVector(PlayerPosition.X, PlayerPosition.Y, HelperMethods::EYES_POS_Z); + const FVector UpCoverPosition = FVector(CoverPosition.X, CoverPosition.Y, HelperMethods::EYES_POS_Z); + + const bool BlockingHitFound = World->LineTraceSingleByChannel(OutHit, UpCoverPosition, UpPlayerLocation, ECollisionChannel::ECC_Visibility, CollisionParams); + + if (BlockingHitFound && !OutHit.Actor->GetName().Contains("Player")) { + // Hit some obstacle + PositionIsSafeCover = true; + } + + return PositionIsSafeCover; +} + +bool AShooterAIController::PositionIsGoodAttack(const FVector AttackPosition, const FVector PlayerPosition) const { + bool PositionIsGoodAttack = false; + + if (AttackPosition == FVector(0, 0, 0)) { + return false; + } + + AShooterBot* MyBot = Cast(GetPawn()); + + if (!MyBot) { + return PositionIsGoodAttack; + } + + // Check Has Visibility + UWorld * World = GetWorld(); + if (!World) { + return PositionIsGoodAttack; + } + + FHitResult OutHit; + FCollisionQueryParams CollisionParams; + TArray ActorsToIgnore; + + //const FName TraceTag("VisibilityTrace"); + //World->DebugDrawTraceTag = TraceTag; + //CollisionParams.TraceTag = TraceTag; + + UGameplayStatics::GetAllActorsOfClass(GetWorld(), AShooterBot::StaticClass(), ActorsToIgnore); + CollisionParams.AddIgnoredActors(ActorsToIgnore); + + const FVector UpPlayerLocation = FVector(PlayerPosition.X, PlayerPosition.Y, HelperMethods::EYES_POS_Z); + const FVector UpAttackPosition = FVector(AttackPosition.X, AttackPosition.Y, HelperMethods::EYES_POS_Z); + /* + const bool BlockingHitFound = World->LineTraceSingleByChannel(OutHit, UpAttackPosition, UpPlayerLocation, ECollisionChannel::ECC_Visibility, CollisionParams); + + if (!BlockingHitFound || !OutHit.Actor->GetName().Contains("Player")) { + // Hit some obstacle + return PositionIsGoodAttack; + } + */ + + if (!GetPL_fIsVisible()) { + return PositionIsGoodAttack; + } + // Has Cover Position NEAR (Behind obstacle position) + for (float X = AttackPosition.X - 250; X < AttackPosition.X + 250; X += 10) { + for (float Y = AttackPosition.Y - 250; Y < AttackPosition.Y + 250; Y += 10) { + const FVector CoverPosition = FVector(X, Y, HelperMethods::EYES_POS_Z); + //UNavigationSystem::ProjectPointToNavigation() + + if (PositionIsSafeCover(CoverPosition, PlayerPosition)) { + PositionIsGoodAttack = true; + break; + } + + } + } + + /* + + const float MaxRadius = 500.0f; + const TArray LocationOfCoverAnnotations = HelperMethods::GetLocationOfCoverAnnotationsWithinRadius(World, AttackPosition, MaxRadius); + + for (auto It = LocationOfCoverAnnotations.CreateConstIterator(); It; ++It) { + const FVector CoverLocation = *It; + const FVector UpCoverLocation = FVector(CoverLocation.X, CoverLocation.Y, HelperMethods::EYES_POS_Z); + + const bool BlockingHitFound = World->LineTraceSingleByChannel(OutHit, UpCoverLocation, UpPlayerLocation, ECollisionChannel::ECC_Visibility, CollisionParams); + if (BlockingHitFound && !OutHit.Actor->GetName().Contains("Player")) { + // Hit some obstacle + PositionIsGoodAttack = true; + break; + } + } + */ + return PositionIsGoodAttack; +} + diff --git a/Source/ShooterGame/Private/Bots/ShooterBot.cpp b/Source/ShooterGame/Private/Bots/ShooterBot.cpp index ec56f1a..a230601 100644 --- a/Source/ShooterGame/Private/Bots/ShooterBot.cpp +++ b/Source/ShooterGame/Private/Bots/ShooterBot.cpp @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ShooterGame.h" #include "Bots/ShooterBot.h" @@ -12,7 +12,6 @@ AShooterBot::AShooterBot(const FObjectInitializer& ObjectInitializer) UpdatePawnMeshes(); bUseControllerRotationYaw = true; - CharacterMovement->bUseRVOAvoidance = false; } bool AShooterBot::IsFirstPerson() const diff --git a/Source/ShooterGame/Private/EQS/AttackPositionTest.cpp b/Source/ShooterGame/Private/EQS/AttackPositionTest.cpp index 783bdb2..5bebf1b 100644 --- a/Source/ShooterGame/Private/EQS/AttackPositionTest.cpp +++ b/Source/ShooterGame/Private/EQS/AttackPositionTest.cpp @@ -41,7 +41,7 @@ void UAttackPositionTest::RunTest(FEnvQueryInstance& QueryInstance) const } // Get all covers annotations within radius - UWorld * World = GEngine->GetWorldFromContextObject(QueryOwner); + UWorld * World = GEngine->GetWorldFromContextObjectChecked(QueryOwner); const TArray LocationOfCoverAnnotations = GetLocationOfCoverAnnotationsWithinRadius(World, ContextLocations[0]); @@ -51,7 +51,7 @@ void UAttackPositionTest::RunTest(FEnvQueryInstance& QueryInstance) const { float CurrentDistance; float MinDistance = 100000; - const FVector Location = GetItemLocation(QueryInstance, *It); + const FVector Location = GetItemLocation(QueryInstance, It); for (auto It2 = LocationOfCoverAnnotations.CreateConstIterator(); It2; ++It2) { const FVector CoverLocation = *It2; CurrentDistance = FVector::Dist(Location, CoverLocation); @@ -59,7 +59,7 @@ void UAttackPositionTest::RunTest(FEnvQueryInstance& QueryInstance) const } // Discard items that are far away of a cover annotation if (MinDistance > DiscardDistanceMax) { - //It.DiscardItem(); + It.ForceItemState(EEnvItemStatus::Failed); } else if (World) { FHitResult OutHit; @@ -77,7 +77,7 @@ void UAttackPositionTest::RunTest(FEnvQueryInstance& QueryInstance) const //BehindCoverLocations.Add(Location); } else { - It.DiscardItem(); + It.ForceItemState(EEnvItemStatus::Failed); } } } @@ -85,7 +85,7 @@ void UAttackPositionTest::RunTest(FEnvQueryInstance& QueryInstance) const // Check visibility of points to behind cover locations for (FEnvQueryInstance::ItemIterator It3(this, QueryInstance); It3; ++It3) { - const FVector Location = GetItemLocation(QueryInstance, *It3); + const FVector Location = GetItemLocation(QueryInstance, It3); FHitResult OutHit; FCollisionQueryParams CollisionParams; @@ -112,12 +112,12 @@ void UAttackPositionTest::RunTest(FEnvQueryInstance& QueryInstance) const } else { // Discard items that are far away of a cover annotation - It3.DiscardItem(); + It3.ForceItemState(EEnvItemStatus::Failed); } } else { // No visibility - It3.DiscardItem(); + It3.ForceItemState(EEnvItemStatus::Failed); } } } diff --git a/Source/ShooterGame/Private/EQS/BehindObstacleTest.cpp b/Source/ShooterGame/Private/EQS/BehindObstacleTest.cpp index 04046f2..d4d03ce 100644 --- a/Source/ShooterGame/Private/EQS/BehindObstacleTest.cpp +++ b/Source/ShooterGame/Private/EQS/BehindObstacleTest.cpp @@ -32,7 +32,7 @@ void UBehindObstacleTest::RunTest(FEnvQueryInstance& QueryInstance) const // Get all covers annotations within radius - UWorld * World = GEngine->GetWorldFromContextObject(QueryOwner); + UWorld * World = GEngine->GetWorldFromContextObjectChecked(QueryOwner); const FVector PlayerPosition = HelperMethods::GetPlayerPositionFromAI(World); const FVector EyesPosition = FVector(PlayerPosition.X, PlayerPosition.Y, HelperMethods::EYES_POS_Z); @@ -45,7 +45,7 @@ void UBehindObstacleTest::RunTest(FEnvQueryInstance& QueryInstance) const // Get all behind cover points for (FEnvQueryInstance::ItemIterator It(this, QueryInstance); It; ++It){ - const FVector ItemLocation = GetItemLocation(QueryInstance, *It); + const FVector ItemLocation = GetItemLocation(QueryInstance, It); const FVector UpItemLocation = FVector(ItemLocation.X, ItemLocation.Y, HelperMethods::EYES_POS_Z); if (World) { @@ -53,7 +53,7 @@ void UBehindObstacleTest::RunTest(FEnvQueryInstance& QueryInstance) const const bool BlockingHitFound = World->LineTraceSingleByChannel(OutHit, UpItemLocation, EyesPosition, ECollisionChannel::ECC_Visibility, CollisionParams); if (BlockingHitFound) { - FHitResult OutHit1, OutHit2, OutHit3, OutHit4; + //FHitResult OutHit1, OutHit2, OutHit3, OutHit4; const FVector UpItemLocation1 = FVector(UpItemLocation.X + 75, UpItemLocation.Y, UpItemLocation.Z); const FVector UpItemLocation2 = FVector(UpItemLocation.X - 75, UpItemLocation.Y, UpItemLocation.Z); const FVector UpItemLocation3 = FVector(UpItemLocation.X, UpItemLocation.Y + 75, UpItemLocation.Z); diff --git a/Source/ShooterGame/Private/EQS/CloserHighInfluencePositionsTest.cpp b/Source/ShooterGame/Private/EQS/CloserHighInfluencePositionsTest.cpp index f7e4029..b3261c4 100644 --- a/Source/ShooterGame/Private/EQS/CloserHighInfluencePositionsTest.cpp +++ b/Source/ShooterGame/Private/EQS/CloserHighInfluencePositionsTest.cpp @@ -33,7 +33,7 @@ void UCloserHighInfluencePositionsTest::RunTest(FEnvQueryInstance& QueryInstance // Get all covers annotations within radius - UWorld * World = GEngine->GetWorldFromContextObject(QueryOwner); + UWorld * World = GEngine->GetWorldFromContextObjectChecked(QueryOwner); APawn * Pawn = Cast(QueryOwner); @@ -56,7 +56,7 @@ void UCloserHighInfluencePositionsTest::RunTest(FEnvQueryInstance& QueryInstance } for (FEnvQueryInstance::ItemIterator It(this, QueryInstance); It; ++It) { - const FVector ItemLocation = GetItemLocation(QueryInstance, *It); + const FVector ItemLocation = GetItemLocation(QueryInstance, It); InfluenceTile * ItemTile = InfluenceMap->GetInfluence(ItemLocation); TArray Neighbors = InfluenceMap->GetWalkableNeighbors(ItemTile->Index); diff --git a/Source/ShooterGame/Private/EQS/DistanceChosenPositionsTest.cpp b/Source/ShooterGame/Private/EQS/DistanceChosenPositionsTest.cpp index 787f48e..41048f43 100644 --- a/Source/ShooterGame/Private/EQS/DistanceChosenPositionsTest.cpp +++ b/Source/ShooterGame/Private/EQS/DistanceChosenPositionsTest.cpp @@ -31,7 +31,7 @@ void UDistanceChosenPositionsTest::RunTest(FEnvQueryInstance& QueryInstance) con // Get all covers annotations within radius - UWorld * World = GEngine->GetWorldFromContextObject(QueryOwner); + UWorld * World = GEngine->GetWorldFromContextObjectChecked(QueryOwner); APawn * Pawn = Cast(QueryOwner); @@ -48,7 +48,7 @@ void UDistanceChosenPositionsTest::RunTest(FEnvQueryInstance& QueryInstance) con const TMap * AlreadyChosenAttackPositions = AIController->GetAI_fAttackLocations(); for (FEnvQueryInstance::ItemIterator It(this, QueryInstance); It; ++It) { - const FVector ItemLocation = GetItemLocation(QueryInstance, *It); + const FVector ItemLocation = GetItemLocation(QueryInstance, It); float MinDistance = 10000; for (auto& Elem : *AlreadyChosenAttackPositions) { diff --git a/Source/ShooterGame/Private/EQS/KeepFirstPositionTest.cpp b/Source/ShooterGame/Private/EQS/KeepFirstPositionTest.cpp index 4b550ef..a7c2d0d 100644 --- a/Source/ShooterGame/Private/EQS/KeepFirstPositionTest.cpp +++ b/Source/ShooterGame/Private/EQS/KeepFirstPositionTest.cpp @@ -30,7 +30,7 @@ void UKeepFirstPositionTest::RunTest(FEnvQueryInstance& QueryInstance) const bool bWantsHit = BoolValue.GetValue(); // Get all covers annotations within radius - UWorld * World = GEngine->GetWorldFromContextObject(QueryOwner); + UWorld * World = GEngine->GetWorldFromContextObjectChecked(QueryOwner); // @ todo many bots TArray ContextLocations; @@ -79,7 +79,7 @@ void UKeepFirstPositionTest::RunTest(FEnvQueryInstance& QueryInstance) const for (FEnvQueryInstance::ItemIterator It(this, QueryInstance); It; ++It) { - const FVector ItemLocation = GetItemLocation(QueryInstance, *It); + const FVector ItemLocation = GetItemLocation(QueryInstance, It); It.SetScore(TestPurpose, FilterType, FirstLocations.Contains(ItemLocation), bWantsHit); } } diff --git a/Source/ShooterGame/Private/EQS/MaxSpreadSearchPositionsTest.cpp b/Source/ShooterGame/Private/EQS/MaxSpreadSearchPositionsTest.cpp index 2532b05..b4dcfe2 100644 --- a/Source/ShooterGame/Private/EQS/MaxSpreadSearchPositionsTest.cpp +++ b/Source/ShooterGame/Private/EQS/MaxSpreadSearchPositionsTest.cpp @@ -35,7 +35,7 @@ void UMaxSpreadSearchPositionsTest::RunTest(FEnvQueryInstance& QueryInstance) co // Get all covers annotations within radius - UWorld * World = GEngine->GetWorldFromContextObject(QueryOwner); + UWorld * World = GEngine->GetWorldFromContextObjectChecked(QueryOwner); APawn * Pawn = Cast(QueryOwner); @@ -57,7 +57,7 @@ void UMaxSpreadSearchPositionsTest::RunTest(FEnvQueryInstance& QueryInstance) co for (FEnvQueryInstance::ItemIterator It(this, QueryInstance); It; ++It) { - const FVector ItemLocation = GetItemLocation(QueryInstance, *It); + const FVector ItemLocation = GetItemLocation(QueryInstance, It); float MinDistance = 9999; float DistanceFromMyOldPos = 0; diff --git a/Source/ShooterGame/Private/EQS/MinDotProductChosenPositions.cpp b/Source/ShooterGame/Private/EQS/MinDotProductChosenPositions.cpp index ffb5798..ae350fa 100644 --- a/Source/ShooterGame/Private/EQS/MinDotProductChosenPositions.cpp +++ b/Source/ShooterGame/Private/EQS/MinDotProductChosenPositions.cpp @@ -32,7 +32,7 @@ void UMinDotProductChosenPositions::RunTest(FEnvQueryInstance& QueryInstance) co // Get all covers annotations within radius - UWorld * World = GEngine->GetWorldFromContextObject(QueryOwner); + UWorld * World = GEngine->GetWorldFromContextObjectChecked(QueryOwner); APawn * Pawn = Cast(QueryOwner); @@ -50,7 +50,7 @@ void UMinDotProductChosenPositions::RunTest(FEnvQueryInstance& QueryInstance) co const FVector PlayerPosition = HelperMethods::GetPlayerPositionFromAI(World); for (FEnvQueryInstance::ItemIterator It(this, QueryInstance); It; ++It) { - const FVector ItemLocation = GetItemLocation(QueryInstance, *It); + const FVector ItemLocation = GetItemLocation(QueryInstance, It); FVector ItemToPlayer = PlayerPosition - ItemLocation; ItemToPlayer.Normalize(); diff --git a/Source/ShooterGame/Private/EQS/NearBehindObstacleTest.cpp b/Source/ShooterGame/Private/EQS/NearBehindObstacleTest.cpp index d9fbcd3..3cc46cc 100644 --- a/Source/ShooterGame/Private/EQS/NearBehindObstacleTest.cpp +++ b/Source/ShooterGame/Private/EQS/NearBehindObstacleTest.cpp @@ -31,7 +31,7 @@ void UNearBehindObstacleTest::RunTest(FEnvQueryInstance& QueryInstance) const // Get all covers annotations within radius - UWorld * World = GEngine->GetWorldFromContextObject(QueryOwner); + UWorld * World = GEngine->GetWorldFromContextObjectChecked(QueryOwner); // Get all behind cover points TArray LocationsBehindObstacles; @@ -107,7 +107,7 @@ void UNearBehindObstacleTest::RunTest(FEnvQueryInstance& QueryInstance) const // Score those positions that are nearer to behind obstacle points for (FEnvQueryInstance::ItemIterator It(this, QueryInstance); It; ++It){ - const FVector ItemLocation = GetItemLocation(QueryInstance, *It); + const FVector ItemLocation = GetItemLocation(QueryInstance, It); float CurrentDistance; float MinDistance = 100000; @@ -127,11 +127,11 @@ void UNearBehindObstacleTest::RunTest(FEnvQueryInstance& QueryInstance) const //It.SetScore(TestPurpose, FilterType, Distance , MinThresholdValue, MaxThresholdValue); } else { - It.DiscardItem(); + It.ForceItemState(EEnvItemStatus::Failed); } } else { - It.DiscardItem(); + It.ForceItemState(EEnvItemStatus::Failed); //It.SetScore(TestPurpose, FilterType, MaxThresholdValue + 1, MinThresholdValue, MaxThresholdValue); } } diff --git a/Source/ShooterGame/Private/EQS/NearCoverAnnotationTest.cpp b/Source/ShooterGame/Private/EQS/NearCoverAnnotationTest.cpp index db509b3..020a56e 100644 --- a/Source/ShooterGame/Private/EQS/NearCoverAnnotationTest.cpp +++ b/Source/ShooterGame/Private/EQS/NearCoverAnnotationTest.cpp @@ -28,15 +28,15 @@ void UNearCoverAnnotationTest::RunTest(FEnvQueryInstance& QueryInstance) const bool bWantsHit = BoolValue.GetValue(); // Get all covers annotations within radius - UWorld * World = GEngine->GetWorldFromContextObject(QueryOwner); - UNavigationSystem* NavSys = World->GetNavigationSystem(); + UWorld * World = GEngine->GetWorldFromContextObjectChecked(QueryOwner); + UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent(World); if (!NavSys) { return; } - ANavigationData* NavData = NavSys->GetMainNavData(FNavigationSystem::Create); + ANavigationData* NavData = NavSys->GetDefaultNavDataInstance(FNavigationSystem::Create); AMyRecastNavMesh* MyNavMesh = Cast(NavData); FRecastQueryFilter_Example* MyFRecastQueryFilter = MyNavMesh->GetCustomFilter(); diff --git a/Source/ShooterGame/Private/EQS/NearCoverAnnotationTest.cpp.bak b/Source/ShooterGame/Private/EQS/NearCoverAnnotationTest.cpp.bak new file mode 100644 index 0000000..31ceebd --- /dev/null +++ b/Source/ShooterGame/Private/EQS/NearCoverAnnotationTest.cpp.bak @@ -0,0 +1,62 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#include "ShooterGame.h" +#include "EnvironmentQuery/Contexts/EnvQueryContext_Querier.h" +#include "EnvironmentQuery/Items/EnvQueryItemType_VectorBase.h" +#include "Public/EQS/CoverBaseClass.h" +#include "Public/Bots/ShooterBot.h" +#include "Public/Others/HelperMethods.h" +#include "Public/EQS/NearCoverAnnotationTest.h" + +UNearCoverAnnotationTest::UNearCoverAnnotationTest(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) +{ + Cost = EEnvTestCost::Low; + ValidItemType = UEnvQueryItemType_VectorBase::StaticClass(); + SetWorkOnFloatValues(false); +} + +void UNearCoverAnnotationTest::RunTest(FEnvQueryInstance& QueryInstance) const +{ + //UE_LOG(LogTemp, Log, TEXT("F:RunTest")); + + UObject* QueryOwner = QueryInstance.Owner.Get(); + if (QueryOwner == nullptr) + { + return; + } + + bool bWantsHit = BoolValue.GetValue(); + + // Get all covers annotations within radius + UWorld * World = GEngine->GetWorldFromContextObjectChecked(QueryOwner); + UNavigationSystem* NavSys = World->GetNavigationSystem(); + + + if (!NavSys) { + return; + } + + ANavigationData* NavData = NavSys->GetMainNavData(FNavigationSystem::Create); + AMyRecastNavMesh* MyNavMesh = Cast(NavData); + FRecastQueryFilter_Example* MyFRecastQueryFilter = MyNavMesh->GetCustomFilter(); + + const FVector PlayerLocation = HelperMethods::GetPlayerPositionFromAI(World); + const FVector PlayerForwardVector = HelperMethods::GetPlayerForwardVectorFromAI(World); + const TArray VisibleTriangles = HelperMethods::CalculateVisibility(World, PlayerLocation, PlayerForwardVector); + + + + + // Get all behind cover points + for (FEnvQueryInstance::ItemIterator It(this, QueryInstance); It; ++It) + { + It.SetScore(TestPurpose, FilterType, true, bWantsHit); + } +} + +void UNearCoverAnnotationTest::PostLoad() +{ + Super::PostLoad(); +} + + diff --git a/Source/ShooterGame/Private/EQS/PathfiningTest.cpp b/Source/ShooterGame/Private/EQS/PathfiningTest.cpp index 9d39503..a941cfd 100644 --- a/Source/ShooterGame/Private/EQS/PathfiningTest.cpp +++ b/Source/ShooterGame/Private/EQS/PathfiningTest.cpp @@ -2,7 +2,7 @@ #include "ShooterGame.h" -#include "AI/Navigation/NavigationSystem.h" +#include "NavigationSystem.h" #include "AI/Navigation/NavAgentInterface.h" #include "Public/Navigation/MyNavigationQueryFilter.h" #include "EnvironmentQuery/Contexts/EnvQueryContext_Querier.h" @@ -41,7 +41,7 @@ void UPathfiningTest::RunTest(FEnvQueryInstance& QueryInstance) const float MinThresholdValue = FloatValueMin.GetValue(); float MaxThresholdValue = FloatValueMax.GetValue(); - UNavigationSystem* NavSys = QueryInstance.World->GetNavigationSystem(); + UNavigationSystemV1* NavSys = Cast(GetWorld()->GetNavigationSystem()); if (NavSys == nullptr) { return; @@ -181,7 +181,7 @@ void UPathfiningTest::PostLoad() SetWorkOnFloatValues(TestMode != EEnvTestPathfinding::PathExist); } -bool UPathfiningTest::TestPathFrom(const FVector& ItemPos, const FVector& ContextPos, EPathFindingMode::Type Mode, const ANavigationData& NavData, UNavigationSystem& NavSys, const UObject* PathOwner) const +bool UPathfiningTest::TestPathFrom(const FVector& ItemPos, const FVector& ContextPos, EPathFindingMode::Type Mode, const ANavigationData& NavData, UNavigationSystemV1& NavSys, const UObject* PathOwner) const { FPathFindingQuery Query(PathOwner, NavData, ItemPos, ContextPos, UNavigationQueryFilter::GetQueryFilter(NavData, FilterClass)); Query.SetAllowPartialPaths(false); @@ -190,11 +190,11 @@ bool UPathfiningTest::TestPathFrom(const FVector& ItemPos, const FVector& Contex return bPathExists; } -bool UPathfiningTest::TestPathTo(const FVector& ItemPos, const FVector& ContextPos, EPathFindingMode::Type Mode, const ANavigationData& NavData, UNavigationSystem& NavSys, const UObject* PathOwner) const +bool UPathfiningTest::TestPathTo(const FVector& ItemPos, const FVector& ContextPos, EPathFindingMode::Type Mode, const ANavigationData& NavData, UNavigationSystemV1& NavSys, const UObject* PathOwner) const { const AMyRecastNavMesh* MyNavData = Cast(&NavData); const FRecastQueryFilter_Example* MyFRecastQueryFilter = MyNavData->GetCustomFilter(); - + FPathFindingQuery Query(PathOwner, NavData, ContextPos, ItemPos, UNavigationQueryFilter::GetQueryFilter(NavData, FilterClass)); //FPathFindingQuery Query(PathOwner, NavData, ContextPos, ItemPos, MyFRecastQueryFilter); Query.SetAllowPartialPaths(false); @@ -203,7 +203,7 @@ bool UPathfiningTest::TestPathTo(const FVector& ItemPos, const FVector& ContextP return bPathExists; } -float UPathfiningTest::FindPathCostFrom(const FVector& ItemPos, const FVector& ContextPos, EPathFindingMode::Type Mode, const ANavigationData& NavData, UNavigationSystem& NavSys, const UObject* PathOwner) const +float UPathfiningTest::FindPathCostFrom(const FVector& ItemPos, const FVector& ContextPos, EPathFindingMode::Type Mode, const ANavigationData& NavData, UNavigationSystemV1& NavSys, const UObject* PathOwner) const { FPathFindingQuery Query(PathOwner, NavData, ItemPos, ContextPos, UNavigationQueryFilter::GetQueryFilter(NavData, FilterClass)); Query.SetAllowPartialPaths(false); @@ -212,9 +212,10 @@ float UPathfiningTest::FindPathCostFrom(const FVector& ItemPos, const FVector& C return (Result.IsSuccessful()) ? Result.Path->GetCost() : BIG_NUMBER; } -float UPathfiningTest::FindPathCostTo(const FVector& ItemPos, const FVector& ContextPos, EPathFindingMode::Type Mode, const ANavigationData& NavData, UNavigationSystem& NavSys, const UObject* PathOwner) const +float UPathfiningTest::FindPathCostTo(const FVector& ItemPos, const FVector& ContextPos, EPathFindingMode::Type Mode, const ANavigationData& NavData, UNavigationSystemV1& NavSys, const UObject* PathOwner) const { - TSharedPtr QueryFilter = UMyNavigationQueryFilter::GetQueryFilter(NavData, FilterClass); + TSharedPtr QueryFilter = UMyNavigationQueryFilter::GetQueryFilter(NavData, FilterClass); + //TSharedPtr QueryFilter = UMyNavigationQueryFilter::GetQueryFilter(NavData, FilterClass); FPathFindingQuery Query(PathOwner, NavData, ContextPos, ItemPos, QueryFilter); Query.SetAllowPartialPaths(false); @@ -222,7 +223,7 @@ float UPathfiningTest::FindPathCostTo(const FVector& ItemPos, const FVector& Con return (Result.IsSuccessful()) ? Result.Path->GetCost() : BIG_NUMBER; } -float UPathfiningTest::FindPathLengthFrom(const FVector& ItemPos, const FVector& ContextPos, EPathFindingMode::Type Mode, const ANavigationData& NavData, UNavigationSystem& NavSys, const UObject* PathOwner) const +float UPathfiningTest::FindPathLengthFrom(const FVector& ItemPos, const FVector& ContextPos, EPathFindingMode::Type Mode, const ANavigationData& NavData, UNavigationSystemV1& NavSys, const UObject* PathOwner) const { FPathFindingQuery Query(PathOwner, NavData, ItemPos, ContextPos, UNavigationQueryFilter::GetQueryFilter(NavData, FilterClass)); Query.SetAllowPartialPaths(false); @@ -231,7 +232,7 @@ float UPathfiningTest::FindPathLengthFrom(const FVector& ItemPos, const FVector& return (Result.IsSuccessful()) ? Result.Path->GetLength() : BIG_NUMBER; } -float UPathfiningTest::FindPathLengthTo(const FVector& ItemPos, const FVector& ContextPos, EPathFindingMode::Type Mode, const ANavigationData& NavData, UNavigationSystem& NavSys, const UObject* PathOwner) const +float UPathfiningTest::FindPathLengthTo(const FVector& ItemPos, const FVector& ContextPos, EPathFindingMode::Type Mode, const ANavigationData& NavData, UNavigationSystemV1& NavSys, const UObject* PathOwner) const { FPathFindingQuery Query(PathOwner, NavData, ContextPos, ItemPos, UNavigationQueryFilter::GetQueryFilter(NavData, FilterClass)); Query.SetAllowPartialPaths(false); @@ -240,7 +241,7 @@ float UPathfiningTest::FindPathLengthTo(const FVector& ItemPos, const FVector& C return (Result.IsSuccessful()) ? Result.Path->GetLength() : BIG_NUMBER; } -ANavigationData* UPathfiningTest::FindNavigationData(UNavigationSystem& NavSys, UObject* Owner) const +ANavigationData* UPathfiningTest::FindNavigationData(UNavigationSystemV1& NavSys, UObject* Owner) const { INavAgentInterface* NavAgent = Cast(Owner); if (NavAgent) @@ -248,9 +249,7 @@ ANavigationData* UPathfiningTest::FindNavigationData(UNavigationSystem& NavSys, return NavSys.GetNavDataForProps(NavAgent->GetNavAgentPropertiesRef()); } - return NavSys.GetMainNavData(FNavigationSystem::DontCreate); + return NavSys.GetDefaultNavDataInstance(FNavigationSystem::DontCreate); } -#undef LOCTEXT_NAMESPACE - - +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/ShooterGame/Private/EQS/PathfiningTest.cpp.bak b/Source/ShooterGame/Private/EQS/PathfiningTest.cpp.bak new file mode 100644 index 0000000..461a0fe --- /dev/null +++ b/Source/ShooterGame/Private/EQS/PathfiningTest.cpp.bak @@ -0,0 +1,254 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "ShooterGame.h" +#include "AI/Navigation/NavigationSystem.h" +#include "AI/Navigation/NavAgentInterface.h" +#include "Public/Navigation/MyNavigationQueryFilter.h" +#include "EnvironmentQuery/Contexts/EnvQueryContext_Querier.h" +#include "EnvironmentQuery/Items/EnvQueryItemType_VectorBase.h" +//#include "AIModulePrivate.h" +#include "Public/EQS/PathfiningTest.h" + +#define LOCTEXT_NAMESPACE "EnvQueryGenerator" + +UPathfiningTest::UPathfiningTest(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) +{ + Context = UEnvQueryContext_Querier::StaticClass(); + Cost = EEnvTestCost::High; + ValidItemType = UEnvQueryItemType_VectorBase::StaticClass(); + TestMode = EEnvTestPathfinding::PathExist; + PathFromContext.DefaultValue = true; + SkipUnreachable.DefaultValue = true; + FloatValueMin.DefaultValue = 1000.0f; + FloatValueMax.DefaultValue = 1000.0f; + + SetWorkOnFloatValues(TestMode != EEnvTestPathfinding::PathExist); +} + +void UPathfiningTest::RunTest(FEnvQueryInstance& QueryInstance) const +{ + UObject* QueryOwner = QueryInstance.Owner.Get(); + BoolValue.BindData(QueryOwner, QueryInstance.QueryID); + PathFromContext.BindData(QueryOwner, QueryInstance.QueryID); + SkipUnreachable.BindData(QueryOwner, QueryInstance.QueryID); + FloatValueMin.BindData(QueryOwner, QueryInstance.QueryID); + FloatValueMax.BindData(QueryOwner, QueryInstance.QueryID); + + bool bWantsPath = BoolValue.GetValue(); + bool bPathToItem = PathFromContext.GetValue(); + bool bDiscardFailed = SkipUnreachable.GetValue(); + float MinThresholdValue = FloatValueMin.GetValue(); + float MaxThresholdValue = FloatValueMax.GetValue(); + + UNavigationSystem* NavSys = QueryInstance.World->GetNavigationSystem(); + if (NavSys == nullptr) + { + return; + } + + + + + ANavigationData* DefaultNavData = FindNavigationData(*NavSys, QueryOwner); + if (DefaultNavData == nullptr) + { + return; + } + + AMyRecastNavMesh* NavData = Cast(DefaultNavData); + + if (NavData == nullptr) + { + return; + } + FRecastQueryFilter_Example* MyFRecastQueryFilter = NavData->GetCustomFilter(); + //AMyRecastNavMesh* MyNavMesh = Cast(DefaultNavData); + + + + TArray ContextLocations; + if (!QueryInstance.PrepareContext(Context, ContextLocations)) + { + return; + } + + EPathFindingMode::Type PFMode(EPathFindingMode::Regular); + + if (GetWorkOnFloatValues()) + { + FFindPathSignature FindPathFunc; + FindPathFunc.BindUObject(this, TestMode == EEnvTestPathfinding::PathLength ? + (bPathToItem ? &UPathfiningTest::FindPathLengthTo : &UPathfiningTest::FindPathLengthFrom) : + (bPathToItem ? &UPathfiningTest::FindPathCostTo : &UPathfiningTest::FindPathCostFrom)); + + NavData->BeginBatchQuery(); + for (FEnvQueryInstance::ItemIterator It(this, QueryInstance); It; ++It) + { + const FVector ItemLocation = GetItemLocation(QueryInstance, It.GetIndex()); + for (int32 ContextIndex = 0; ContextIndex < ContextLocations.Num(); ContextIndex++) + { + const float PathValue = FindPathFunc.Execute(ItemLocation, ContextLocations[ContextIndex], PFMode, *NavData, *NavSys, QueryOwner); + It.SetScore(TestPurpose, FilterType, PathValue, MinThresholdValue, MaxThresholdValue); + + if (bDiscardFailed && PathValue >= BIG_NUMBER) + { + It.ForceItemState(EEnvItemStatus::Failed); + } + } + } + NavData->FinishBatchQuery(); + } + else + { + NavData->BeginBatchQuery(); + if (bPathToItem) + { + for (FEnvQueryInstance::ItemIterator It(this, QueryInstance); It; ++It) + { + const FVector ItemLocation = GetItemLocation(QueryInstance, It.GetIndex()); + for (int32 ContextIndex = 0; ContextIndex < ContextLocations.Num(); ContextIndex++) + { + const bool bFoundPath = TestPathTo(ItemLocation, ContextLocations[ContextIndex], PFMode, *NavData, *NavSys, QueryOwner); + It.SetScore(TestPurpose, FilterType, bFoundPath, bWantsPath); + } + } + } + else + { + for (FEnvQueryInstance::ItemIterator It(this, QueryInstance); It; ++It) + { + const FVector ItemLocation = GetItemLocation(QueryInstance, It.GetIndex()); + for (int32 ContextIndex = 0; ContextIndex < ContextLocations.Num(); ContextIndex++) + { + const bool bFoundPath = TestPathFrom(ItemLocation, ContextLocations[ContextIndex], PFMode, *NavData, *NavSys, QueryOwner); + It.SetScore(TestPurpose, FilterType, bFoundPath, bWantsPath); + } + } + } + NavData->FinishBatchQuery(); + } +} + +FText UPathfiningTest::GetDescriptionTitle() const +{ + FString ModeDesc[] = { TEXT("PathExist"), TEXT("PathCost"), TEXT("PathLength") }; + + FString DirectionDesc = PathFromContext.IsDynamic() ? + FString::Printf(TEXT("%s, direction: %s"), *UEnvQueryTypes::DescribeContext(Context).ToString(), *PathFromContext.ToString()) : + FString::Printf(TEXT("%s %s"), PathFromContext.DefaultValue ? TEXT("from") : TEXT("to"), *UEnvQueryTypes::DescribeContext(Context).ToString()); + + return FText::FromString(FString::Printf(TEXT("%s: %s"), *ModeDesc[TestMode], *DirectionDesc)); +} + +FText UPathfiningTest::GetDescriptionDetails() const +{ + FText DiscardDesc = LOCTEXT("DiscardUnreachable", "discard unreachable"); + FText Desc2; + if (SkipUnreachable.IsDynamic()) + { + Desc2 = FText::Format(FText::FromString("{0}: {1}"), DiscardDesc, FText::FromString(SkipUnreachable.ToString())); + } + else if (SkipUnreachable.DefaultValue) + { + Desc2 = DiscardDesc; + } + + FText TestParamDesc = GetWorkOnFloatValues() ? DescribeFloatTestParams() : DescribeBoolTestParams("existing path"); + if (!Desc2.IsEmpty()) + { + return FText::Format(FText::FromString("{0}\n{1}"), Desc2, TestParamDesc); + } + + return TestParamDesc; +} + +#if WITH_EDITOR +void UPathfiningTest::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) +{ + Super::PostEditChangeProperty(PropertyChangedEvent); + if (PropertyChangedEvent.Property && PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(UPathfiningTest, TestMode)) + { + SetWorkOnFloatValues(TestMode != EEnvTestPathfinding::PathExist); + } +} +#endif + +void UPathfiningTest::PostLoad() +{ + Super::PostLoad(); + + SetWorkOnFloatValues(TestMode != EEnvTestPathfinding::PathExist); +} + +bool UPathfiningTest::TestPathFrom(const FVector& ItemPos, const FVector& ContextPos, EPathFindingMode::Type Mode, const ANavigationData& NavData, UNavigationSystem& NavSys, const UObject* PathOwner) const +{ + FPathFindingQuery Query(PathOwner, NavData, ItemPos, ContextPos, UNavigationQueryFilter::GetQueryFilter(NavData, FilterClass)); + Query.SetAllowPartialPaths(false); + + const bool bPathExists = NavSys.TestPathSync(Query, Mode); + return bPathExists; +} + +bool UPathfiningTest::TestPathTo(const FVector& ItemPos, const FVector& ContextPos, EPathFindingMode::Type Mode, const ANavigationData& NavData, UNavigationSystem& NavSys, const UObject* PathOwner) const +{ + const AMyRecastNavMesh* MyNavData = Cast(&NavData); + const FRecastQueryFilter_Example* MyFRecastQueryFilter = MyNavData->GetCustomFilter(); + + FPathFindingQuery Query(PathOwner, NavData, ContextPos, ItemPos, UNavigationQueryFilter::GetQueryFilter(NavData, FilterClass)); + //FPathFindingQuery Query(PathOwner, NavData, ContextPos, ItemPos, MyFRecastQueryFilter); + Query.SetAllowPartialPaths(false); + + const bool bPathExists = NavSys.TestPathSync(Query, Mode); + return bPathExists; +} + +float UPathfiningTest::FindPathCostFrom(const FVector& ItemPos, const FVector& ContextPos, EPathFindingMode::Type Mode, const ANavigationData& NavData, UNavigationSystem& NavSys, const UObject* PathOwner) const +{ + FPathFindingQuery Query(PathOwner, NavData, ItemPos, ContextPos, UNavigationQueryFilter::GetQueryFilter(NavData, FilterClass)); + Query.SetAllowPartialPaths(false); + + FPathFindingResult Result = NavSys.FindPathSync(Query, Mode); + return (Result.IsSuccessful()) ? Result.Path->GetCost() : BIG_NUMBER; +} + +float UPathfiningTest::FindPathCostTo(const FVector& ItemPos, const FVector& ContextPos, EPathFindingMode::Type Mode, const ANavigationData& NavData, UNavigationSystem& NavSys, const UObject* PathOwner) const +{ + TSharedPtr QueryFilter = UMyNavigationQueryFilter::GetQueryFilter(NavData, FilterClass); + FPathFindingQuery Query(PathOwner, NavData, ContextPos, ItemPos, QueryFilter); + Query.SetAllowPartialPaths(false); + + FPathFindingResult Result = NavSys.FindPathSync(Query, Mode); + return (Result.IsSuccessful()) ? Result.Path->GetCost() : BIG_NUMBER; +} + +float UPathfiningTest::FindPathLengthFrom(const FVector& ItemPos, const FVector& ContextPos, EPathFindingMode::Type Mode, const ANavigationData& NavData, UNavigationSystem& NavSys, const UObject* PathOwner) const +{ + FPathFindingQuery Query(PathOwner, NavData, ItemPos, ContextPos, UNavigationQueryFilter::GetQueryFilter(NavData, FilterClass)); + Query.SetAllowPartialPaths(false); + + FPathFindingResult Result = NavSys.FindPathSync(Query, Mode); + return (Result.IsSuccessful()) ? Result.Path->GetLength() : BIG_NUMBER; +} + +float UPathfiningTest::FindPathLengthTo(const FVector& ItemPos, const FVector& ContextPos, EPathFindingMode::Type Mode, const ANavigationData& NavData, UNavigationSystem& NavSys, const UObject* PathOwner) const +{ + FPathFindingQuery Query(PathOwner, NavData, ContextPos, ItemPos, UNavigationQueryFilter::GetQueryFilter(NavData, FilterClass)); + Query.SetAllowPartialPaths(false); + + FPathFindingResult Result = NavSys.FindPathSync(Query, Mode); + return (Result.IsSuccessful()) ? Result.Path->GetLength() : BIG_NUMBER; +} + +ANavigationData* UPathfiningTest::FindNavigationData(UNavigationSystem& NavSys, UObject* Owner) const +{ + INavAgentInterface* NavAgent = Cast(Owner); + if (NavAgent) + { + return NavSys.GetNavDataForProps(NavAgent->GetNavAgentPropertiesRef()); + } + + return NavSys.GetMainNavData(FNavigationSystem::DontCreate); +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/ShooterGame/Private/EQS/PlayerDistanceTest.cpp b/Source/ShooterGame/Private/EQS/PlayerDistanceTest.cpp index a7fe83e..b79e55c 100644 --- a/Source/ShooterGame/Private/EQS/PlayerDistanceTest.cpp +++ b/Source/ShooterGame/Private/EQS/PlayerDistanceTest.cpp @@ -32,11 +32,11 @@ void UPlayerDistanceTest::RunTest(FEnvQueryInstance& QueryInstance) const { float MaxThresholdValue = FloatValueMax.GetValue(); - UWorld * World = GEngine->GetWorldFromContextObject(QueryOwner); + UWorld * World = GEngine->GetWorldFromContextObjectChecked(QueryOwner); const FVector PlayerPosition = HelperMethods::GetPlayerPositionFromAI(World); for (FEnvQueryInstance::ItemIterator It(this, QueryInstance); It; ++It) { - const FVector ItemLocation = GetItemLocation(QueryInstance, *It); + const FVector ItemLocation = GetItemLocation(QueryInstance, It); const float Distance = FVector::Dist(PlayerPosition, ItemLocation); It.SetScore(TestPurpose, FilterType, Distance, MinThresholdValue, MaxThresholdValue); diff --git a/Source/ShooterGame/Private/EQS/PlayerVisibilityTest.cpp b/Source/ShooterGame/Private/EQS/PlayerVisibilityTest.cpp index 8464a67..e708e6c 100644 --- a/Source/ShooterGame/Private/EQS/PlayerVisibilityTest.cpp +++ b/Source/ShooterGame/Private/EQS/PlayerVisibilityTest.cpp @@ -29,7 +29,7 @@ void UPlayerVisibilityTest::RunTest(FEnvQueryInstance& QueryInstance) const return; } - UWorld * World = GEngine->GetWorldFromContextObject(QueryOwner); + UWorld * World = GEngine->GetWorldFromContextObjectChecked(QueryOwner); const FVector PlayerLocation = HelperMethods::GetPlayerPositionFromAI(World); const FVector EyesLocation = FVector(PlayerLocation.X, PlayerLocation.Y, HelperMethods::EYES_POS_Z); const FVector PlayerForwardVector = HelperMethods::GetPlayerForwardVectorFromAI(World); @@ -42,7 +42,7 @@ void UPlayerVisibilityTest::RunTest(FEnvQueryInstance& QueryInstance) const for (FEnvQueryInstance::ItemIterator It(this, QueryInstance); It; ++It) { bool IsVisible = false; - const FVector Location = GetItemLocation(QueryInstance, *It); + const FVector Location = GetItemLocation(QueryInstance, It); for (auto It2 = PlayerVisibility.CreateConstIterator(); It2; ++It2) { const Triangle Triangle = *It2; if (Triangle.PointInsideTriangle(FVector2D(Location.X, Location.Y))) { @@ -65,7 +65,7 @@ void UPlayerVisibilityTest::RunTest(FEnvQueryInstance& QueryInstance) const for (FEnvQueryInstance::ItemIterator It2(this, QueryInstance); It2; ++It2) { - const FVector Location = GetItemLocation(QueryInstance, *It2); + const FVector Location = GetItemLocation(QueryInstance, It2); const FVector UpLocation = FVector(Location.X, Location.Y, 150); const bool BlockingHitFound = World->LineTraceSingleByChannel(OutHit, EyesLocation, UpLocation, ECollisionChannel::ECC_Visibility, CollisionParams); diff --git a/Source/ShooterGame/Private/Effects/ShooterExplosionEffect.cpp b/Source/ShooterGame/Private/Effects/ShooterExplosionEffect.cpp index 02ef05d..938c43a 100644 --- a/Source/ShooterGame/Private/Effects/ShooterExplosionEffect.cpp +++ b/Source/ShooterGame/Private/Effects/ShooterExplosionEffect.cpp @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ShooterGame.h" #include "ShooterExplosionEffect.h" diff --git a/Source/ShooterGame/Private/Effects/ShooterImpactEffect.cpp b/Source/ShooterGame/Private/Effects/ShooterImpactEffect.cpp index e1c0398..7bd70cd 100644 --- a/Source/ShooterGame/Private/Effects/ShooterImpactEffect.cpp +++ b/Source/ShooterGame/Private/Effects/ShooterImpactEffect.cpp @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ShooterGame.h" #include "ShooterImpactEffect.h" @@ -34,7 +34,7 @@ void AShooterImpactEffect::PostInitializeComponents() FRotator RandomDecalRotation = SurfaceHit.ImpactNormal.Rotation(); RandomDecalRotation.Roll = FMath::FRandRange(-180.0f, 180.0f); - UGameplayStatics::SpawnDecalAttached(DefaultDecal.DecalMaterial, FVector(DefaultDecal.DecalSize, DefaultDecal.DecalSize, 1.0f), + UGameplayStatics::SpawnDecalAttached(DefaultDecal.DecalMaterial, FVector(1.0f, DefaultDecal.DecalSize, DefaultDecal.DecalSize), SurfaceHit.Component.Get(), SurfaceHit.BoneName, SurfaceHit.ImpactPoint, RandomDecalRotation, EAttachLocation::KeepWorldPosition, DefaultDecal.LifeSpan); diff --git a/Source/ShooterGame/Private/Navigation/CubeComponent.cpp b/Source/ShooterGame/Private/Navigation/CubeComponent.cpp index f6aa377..6acfdc2 100644 --- a/Source/ShooterGame/Private/Navigation/CubeComponent.cpp +++ b/Source/ShooterGame/Private/Navigation/CubeComponent.cpp @@ -9,7 +9,7 @@ UCubeComponent::UCubeComponent() { // Set this component to be initialized when the game starts, and to be ticked every frame. You can turn these features // off to improve performance if you don't need them. - bWantsBeginPlay = true; + //bWantsBeginPlay = true; PrimaryComponentTick.bCanEverTick = true; // ... diff --git a/Source/ShooterGame/Private/Navigation/MyInfluenceMap.cpp b/Source/ShooterGame/Private/Navigation/MyInfluenceMap.cpp index 154c10b..9b97fb3 100644 --- a/Source/ShooterGame/Private/Navigation/MyInfluenceMap.cpp +++ b/Source/ShooterGame/Private/Navigation/MyInfluenceMap.cpp @@ -153,10 +153,10 @@ void AMyInfluenceMap::PropagateInfluence() { const FVector2D CurrentTileWorld = UpdatedTexture->TextureToWorldSpace(CurrentTile->X, CurrentTile->Y); const FVector2D NeighborTileWorld = UpdatedTexture->TextureToWorldSpace(NeighborTile->X, NeighborTile->Y); const float Distance = FVector2D::Distance(CurrentTileWorld, NeighborTileWorld); - const float Influence = NeighborTile->Influence * expf(-Distance * Decay); + const float Influence = NeighborTile->Influence * expf(-Distance * Decayy); MaxInfluence = FMath::Max(Influence, MaxInfluence); } - const float NewInfluence = FMath::Lerp(CurrentTile->Influence, MaxInfluence, Momentum); + const float NewInfluence = FMath::Lerp(CurrentTile->Influence, MaxInfluence, Momentumm); SetLocalInfluence(Index, NewInfluence); } } @@ -181,8 +181,8 @@ AMyInfluenceMap::AMyInfluenceMap() } void AMyInfluenceMap::CreateInfluenceMap(const float Momentum, const float Decay, const float UpdateFreq, const FString BaseImagePath, const FString ImagePath) { - this->Momentum = Momentum; - this->Decay = Decay; + this->Momentumm = Momentum; + this->Decayy = Decay; this->UpdateFrequency = UpdateFreq; this->BaseTexture = new MyTexture2D(BaseImagePath); diff --git a/Source/ShooterGame/Private/Navigation/MyNavigationQueryFilter.cpp b/Source/ShooterGame/Private/Navigation/MyNavigationQueryFilter.cpp index 79a53ce..95cd64c 100644 --- a/Source/ShooterGame/Private/Navigation/MyNavigationQueryFilter.cpp +++ b/Source/ShooterGame/Private/Navigation/MyNavigationQueryFilter.cpp @@ -9,7 +9,7 @@ UMyNavigationQueryFilter::UMyNavigationQueryFilter(const FObjectInitializer& Obj } -void UMyNavigationQueryFilter::InitializeFilter(const ANavigationData& NavData, FNavigationQueryFilter& Filter) const +void UMyNavigationQueryFilter::InitializeFilter(const ANavigationData& NavData, const UObject* Querier, FNavigationQueryFilter& Filter) const { #if WITH_RECAST @@ -43,6 +43,6 @@ void UMyNavigationQueryFilter::InitializeFilter(const ANavigationData& NavData, } #endif // WITH_RECAST - - Super::InitializeFilter(NavData, Filter); + Super::InitializeFilter(NavData, Querier, Filter); + //Super::InitializeFilter(const NavData, const UObject* Querier, FNavigationQueryFilter& Filter); } \ No newline at end of file diff --git a/Source/ShooterGame/Private/Navigation/MyRecastNavMesh.cpp b/Source/ShooterGame/Private/Navigation/MyRecastNavMesh.cpp index a5e9201..12ad237 100644 --- a/Source/ShooterGame/Private/Navigation/MyRecastNavMesh.cpp +++ b/Source/ShooterGame/Private/Navigation/MyRecastNavMesh.cpp @@ -2,10 +2,10 @@ #include "ShooterGame.h" // recast includes -#include "AI/Navigation/NavFilters/NavigationQueryFilter.h" -#include "Runtime/Navmesh/Public/Detour/DetourNavMeshQuery.h" -#include "Runtime/Navmesh/Public/Detour/DetourNavMesh.h" -#include "Runtime/Navmesh/Public/Detour/DetourCommon.h" +#include "NavFilters/NavigationQueryFilter.h" +#include "Public/Detour/DetourNavMeshQuery.h" +#include "Public/Detour/DetourNavMesh.h" +#include "Public/Detour/DetourCommon.h" #include "Public/Navigation/MyRecastNavMesh.h" #include "Public/Navigation/CubeComponent.h" @@ -271,8 +271,7 @@ void AMyRecastNavMesh::SetupCustomNavFilter() { } } -FRecastQueryFilter_Example* AMyRecastNavMesh::GetCustomFilter() const { - FRecastQueryFilter_Example* MyFRecastQueryFilter = reinterpret_cast(DefaultQueryFilter.Get()->GetImplementation()); +FRecastQueryFilter_Example* AMyRecastNavMesh::GetCustomFilter() const { + FRecastQueryFilter_Example* MyFRecastQueryFilter = static_cast(DefaultQueryFilter.Get()->GetImplementation()); return MyFRecastQueryFilter; } - diff --git a/Source/ShooterGame/Private/Online/ShooterGameMode.cpp b/Source/ShooterGame/Private/Online/ShooterGameMode.cpp index f225d5b..c407c57 100644 --- a/Source/ShooterGame/Private/Online/ShooterGameMode.cpp +++ b/Source/ShooterGame/Private/Online/ShooterGameMode.cpp @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ShooterGame.h" #include "ShooterGameInstance.h" @@ -46,7 +46,7 @@ void AShooterGameMode::InitGame(const FString& MapName, const FString& Options, Super::InitGame(MapName, Options, ErrorMessage); const UGameInstance* GameInstance = GetGameInstance(); - if (GameInstance && Cast(GameInstance)->GetIsOnline()) + if (GameInstance && Cast(GameInstance)->GetOnlineMode() != EOnlineMode::Offline) { bPauseable = false; } @@ -123,6 +123,8 @@ void AShooterGameMode::DefaultTimer() void AShooterGameMode::HandleMatchIsWaitingToStart() { + Super::HandleMatchIsWaitingToStart(); + if (bNeedsBotCreation) { CreateBotControllers(); @@ -220,8 +222,8 @@ void AShooterGameMode::RequestFinishAndExitToMainMenu() if (!Controller->IsLocalController()) { - const FString RemoteReturnReason = NSLOCTEXT("NetworkErrors", "HostHasLeft", "Host has left the game.").ToString(); - Controller->ClientReturnToMainMenu(RemoteReturnReason); + const FText RemoteReturnReason = NSLOCTEXT("NetworkErrors", "HostHasLeft", "Host has left the game."); + Controller->ClientReturnToMainMenuWithTextReason(RemoteReturnReason); } else { @@ -246,7 +248,7 @@ bool AShooterGameMode::IsWinner(class AShooterPlayerState* PlayerState) const return false; } -void AShooterGameMode::PreLogin(const FString& Options, const FString& Address, const TSharedPtr& UniqueId, FString& ErrorMessage) +void AShooterGameMode::PreLogin(const FString& Options, const FString& Address, const FUniqueNetIdRepl& UniqueId, FString& ErrorMessage) { AShooterGameState* const MyGameState = Cast(GameState); const bool bMatchIsOver = MyGameState && MyGameState->HasMatchEnded(); @@ -306,7 +308,7 @@ float AShooterGameMode::ModifyDamage(float Damage, AActor* DamagedActor, struct AShooterCharacter* DamagedPawn = Cast(DamagedActor); if (DamagedPawn && EventInstigator) { - AShooterPlayerState* DamagedPlayerState = Cast(DamagedPawn->PlayerState); + AShooterPlayerState* DamagedPlayerState = Cast(DamagedPawn->GetPlayerState()); AShooterPlayerState* InstigatorPlayerState = Cast(EventInstigator->PlayerState); // disable friendly fire @@ -493,8 +495,6 @@ AShooterAIController* AShooterGameMode::CreateBot(int32 BotNum) void AShooterGameMode::StartBots() { // checking number of existing human player. - int32 NumPlayers = 0; - int32 NumBots = 0; UWorld* World = GetWorld(); for (FConstControllerIterator It = World->GetControllerIterator(); It; ++It) { @@ -513,7 +513,7 @@ void AShooterGameMode::InitBot(AShooterAIController* AIController, int32 BotNum) if (AIController->PlayerState) { FString BotName = FString::Printf(TEXT("Bot %d"), BotNum); - AIController->PlayerState->PlayerName = BotName; + AIController->PlayerState->SetPlayerName(BotName); } } } diff --git a/Source/ShooterGame/Private/Online/ShooterGameSession.cpp b/Source/ShooterGame/Private/Online/ShooterGameSession.cpp index aa5dfeb..f92daca 100644 --- a/Source/ShooterGame/Private/Online/ShooterGameSession.cpp +++ b/Source/ShooterGame/Private/Online/ShooterGameSession.cpp @@ -1,8 +1,9 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ShooterGame.h" #include "ShooterGameSession.h" #include "ShooterOnlineGameSettings.h" +#include "OnlineSubsystemSessionSettings.h" namespace { @@ -30,7 +31,7 @@ AShooterGameSession::AShooterGameSession(const FObjectInitializer& ObjectInitial * @param SessionName the name of the session this callback is for * @param bWasSuccessful true if the async action completed without error, false if there was an error */ -void AShooterGameSession::OnStartOnlineGameComplete(FName SessionName, bool bWasSuccessful) +void AShooterGameSession::OnStartOnlineGameComplete(FName InSessionName, bool bWasSuccessful) { IOnlineSubsystem* OnlineSub = IOnlineSubsystem::Get(); if (OnlineSub) @@ -64,11 +65,11 @@ void AShooterGameSession::HandleMatchHasStarted() if (OnlineSub) { IOnlineSessionPtr Sessions = OnlineSub->GetSessionInterface(); - if (Sessions.IsValid()) + if (Sessions.IsValid() && (Sessions->GetNamedSession(NAME_GameSession) != nullptr)) { - UE_LOG(LogOnlineGame, Log, TEXT("Starting session %s on server"), *GameSessionName.ToString()); + UE_LOG(LogOnlineGame, Log, TEXT("Starting session %s on server"), *FName(NAME_GameSession).ToString()); OnStartSessionCompleteDelegateHandle = Sessions->AddOnStartSessionCompleteDelegate_Handle(OnStartSessionCompleteDelegate); - Sessions->StartSession(GameSessionName); + Sessions->StartSession(NAME_GameSession); } } } @@ -79,12 +80,12 @@ void AShooterGameSession::HandleMatchHasStarted() */ void AShooterGameSession::HandleMatchHasEnded() { - // start online game locally and wait for completion + // end online game locally IOnlineSubsystem* OnlineSub = IOnlineSubsystem::Get(); if (OnlineSub) { IOnlineSessionPtr Sessions = OnlineSub->GetSessionInterface(); - if (Sessions.IsValid()) + if (Sessions.IsValid() && (Sessions->GetNamedSession(NAME_GameSession) != nullptr)) { // tell the clients to end for (FConstPlayerControllerIterator It = GetWorld()->GetPlayerControllerIterator(); It; ++It) @@ -97,8 +98,8 @@ void AShooterGameSession::HandleMatchHasEnded() } // server is handled here - UE_LOG(LogOnlineGame, Log, TEXT("Ending session %s on server"), *GameSessionName.ToString() ); - Sessions->EndSession(GameSessionName); + UE_LOG(LogOnlineGame, Log, TEXT("Ending session %s on server"), *FName(NAME_GameSession).ToString() ); + Sessions->EndSession(NAME_GameSession); } } } @@ -116,8 +117,8 @@ bool AShooterGameSession::IsBusy() const IOnlineSessionPtr Sessions = OnlineSub->GetSessionInterface(); if (Sessions.IsValid()) { - EOnlineSessionState::Type GameSessionState = Sessions->GetSessionState(GameSessionName); - EOnlineSessionState::Type PartySessionState = Sessions->GetSessionState(PartySessionName); + EOnlineSessionState::Type GameSessionState = Sessions->GetSessionState(NAME_GameSession); + EOnlineSessionState::Type PartySessionState = Sessions->GetSessionState(NAME_PartySession); if (GameSessionState != EOnlineSessionState::NoSession || PartySessionState != EOnlineSessionState::NoSession) { return true; @@ -163,9 +164,9 @@ const TArray & AShooterGameSession::GetSearchResults * @param SessionName the name of the session this callback is for * @param bWasSuccessful true if the async action completed without error, false if there was an error */ -void AShooterGameSession::OnCreateSessionComplete(FName SessionName, bool bWasSuccessful) +void AShooterGameSession::OnCreateSessionComplete(FName InSessionName, bool bWasSuccessful) { - UE_LOG(LogOnlineGame, Verbose, TEXT("OnCreateSessionComplete %s bSuccess: %d"), *SessionName.ToString(), bWasSuccessful); + UE_LOG(LogOnlineGame, Verbose, TEXT("OnCreateSessionComplete %s bSuccess: %d"), *InSessionName.ToString(), bWasSuccessful); IOnlineSubsystem* OnlineSub = IOnlineSubsystem::Get(); if (OnlineSub) @@ -174,7 +175,7 @@ void AShooterGameSession::OnCreateSessionComplete(FName SessionName, bool bWasSu Sessions->ClearOnCreateSessionCompleteDelegate_Handle(OnCreateSessionCompleteDelegateHandle); } - OnCreatePresenceSessionComplete().Broadcast(SessionName, bWasSuccessful); + OnCreatePresenceSessionComplete().Broadcast(InSessionName, bWasSuccessful); } /** @@ -183,9 +184,9 @@ void AShooterGameSession::OnCreateSessionComplete(FName SessionName, bool bWasSu * @param SessionName the name of the session this callback is for * @param bWasSuccessful true if the async action completed without error, false if there was an error */ -void AShooterGameSession::OnDestroySessionComplete(FName SessionName, bool bWasSuccessful) +void AShooterGameSession::OnDestroySessionComplete(FName InSessionName, bool bWasSuccessful) { - UE_LOG(LogOnlineGame, Verbose, TEXT("OnDestroySessionComplete %s bSuccess: %d"), *SessionName.ToString(), bWasSuccessful); + UE_LOG(LogOnlineGame, Verbose, TEXT("OnDestroySessionComplete %s bSuccess: %d"), *InSessionName.ToString(), bWasSuccessful); IOnlineSubsystem* OnlineSub = IOnlineSubsystem::Get(); if (OnlineSub) @@ -196,12 +197,12 @@ void AShooterGameSession::OnDestroySessionComplete(FName SessionName, bool bWasS } } -bool AShooterGameSession::HostSession(TSharedPtr UserId, FName SessionName, const FString& GameType, const FString& MapName, bool bIsLAN, bool bIsPresence, int32 MaxNumPlayers) +bool AShooterGameSession::HostSession(TSharedPtr UserId, FName InSessionName, const FString& GameType, const FString& MapName, bool bIsLAN, bool bIsPresence, int32 MaxNumPlayers) { IOnlineSubsystem* const OnlineSub = IOnlineSubsystem::Get(); if (OnlineSub) { - CurrentSessionParams.SessionName = SessionName; + CurrentSessionParams.SessionName = InSessionName; CurrentSessionParams.bIsLAN = bIsLAN; CurrentSessionParams.bIsPresence = bIsPresence; CurrentSessionParams.UserId = UserId; @@ -216,17 +217,26 @@ bool AShooterGameSession::HostSession(TSharedPtr UserId, FNa HostSettings->Set(SETTING_MATCHING_HOPPER, FString("TeamDeathmatch"), EOnlineDataAdvertisementType::DontAdvertise); HostSettings->Set(SETTING_MATCHING_TIMEOUT, 120.0f, EOnlineDataAdvertisementType::ViaOnlineService); HostSettings->Set(SETTING_SESSION_TEMPLATE_NAME, FString("GameSession"), EOnlineDataAdvertisementType::DontAdvertise); + +#if !PLATFORM_SWITCH + // On Switch, we don't have room for this in the session data (and it's not used anyway when searching), so there's no need to add it. + // Can be readded if the buffer size increases. HostSettings->Set(SEARCH_KEYWORDS, CustomMatchKeyword, EOnlineDataAdvertisementType::ViaOnlineService); +#endif OnCreateSessionCompleteDelegateHandle = Sessions->AddOnCreateSessionCompleteDelegate_Handle(OnCreateSessionCompleteDelegate); return Sessions->CreateSession(*CurrentSessionParams.UserId, CurrentSessionParams.SessionName, *HostSettings); } + else + { + OnCreateSessionComplete(InSessionName, false); + } } #if !UE_BUILD_SHIPPING else { // Hack workflow in development - OnCreatePresenceSessionComplete().Broadcast(GameSessionName, true); + OnCreatePresenceSessionComplete().Broadcast(NAME_GameSession, true); return true; } #endif @@ -234,6 +244,34 @@ bool AShooterGameSession::HostSession(TSharedPtr UserId, FNa return false; } +bool AShooterGameSession::HostSession(const TSharedPtr UserId, const FName InSessionName, const FOnlineSessionSettings& SessionSettings) +{ + bool bResult = false; + + IOnlineSubsystem* const OnlineSub = IOnlineSubsystem::Get(); + if (OnlineSub) + { + CurrentSessionParams.SessionName = InSessionName; + CurrentSessionParams.bIsLAN = SessionSettings.bIsLANMatch; + CurrentSessionParams.bIsPresence = SessionSettings.bUsesPresence; + CurrentSessionParams.UserId = UserId; + MaxPlayers = SessionSettings.NumPrivateConnections + SessionSettings.NumPublicConnections; + + IOnlineSessionPtr Sessions = OnlineSub->GetSessionInterface(); + if (Sessions.IsValid() && CurrentSessionParams.UserId.IsValid()) + { + OnCreateSessionCompleteDelegateHandle = Sessions->AddOnCreateSessionCompleteDelegate_Handle(OnCreateSessionCompleteDelegate); + bResult = Sessions->CreateSession(*UserId, InSessionName, SessionSettings); + } + else + { + OnCreateSessionComplete(InSessionName, false); + } + } + + return bResult; +} + void AShooterGameSession::OnFindSessionsComplete(bool bWasSuccessful) { UE_LOG(LogOnlineGame, Verbose, TEXT("OnFindSessionsComplete bSuccess: %d"), bWasSuccessful); @@ -310,12 +348,12 @@ void AShooterGameSession::OnNoMatchesAvailable() SearchSettings = NULL; } -void AShooterGameSession::FindSessions(TSharedPtr UserId, FName SessionName, bool bIsLAN, bool bIsPresence) +void AShooterGameSession::FindSessions(TSharedPtr UserId, FName InSessionName, bool bIsLAN, bool bIsPresence) { IOnlineSubsystem* OnlineSub = IOnlineSubsystem::Get(); if (OnlineSub) { - CurrentSessionParams.SessionName = SessionName; + CurrentSessionParams.SessionName = InSessionName; CurrentSessionParams.bIsLAN = bIsLAN; CurrentSessionParams.bIsPresence = bIsPresence; CurrentSessionParams.UserId = UserId; @@ -338,19 +376,19 @@ void AShooterGameSession::FindSessions(TSharedPtr UserId, FN } } -bool AShooterGameSession::JoinSession(TSharedPtr UserId, FName SessionName, int32 SessionIndexInSearchResults) +bool AShooterGameSession::JoinSession(TSharedPtr UserId, FName InSessionName, int32 SessionIndexInSearchResults) { bool bResult = false; if (SessionIndexInSearchResults >= 0 && SessionIndexInSearchResults < SearchSettings->SearchResults.Num()) { - bResult = JoinSession(UserId, SessionName, SearchSettings->SearchResults[SessionIndexInSearchResults]); + bResult = JoinSession(UserId, InSessionName, SearchSettings->SearchResults[SessionIndexInSearchResults]); } return bResult; } -bool AShooterGameSession::JoinSession(TSharedPtr UserId, FName SessionName, const FOnlineSessionSearchResult& SearchResult) +bool AShooterGameSession::JoinSession(TSharedPtr UserId, FName InSessionName, const FOnlineSessionSearchResult& SearchResult) { bool bResult = false; @@ -361,7 +399,7 @@ bool AShooterGameSession::JoinSession(TSharedPtr UserId, FNa if (Sessions.IsValid() && UserId.IsValid()) { OnJoinSessionCompleteDelegateHandle = Sessions->AddOnJoinSessionCompleteDelegate_Handle(OnJoinSessionCompleteDelegate); - bResult = Sessions->JoinSession(*UserId, SessionName, SearchResult); + bResult = Sessions->JoinSession(*UserId, InSessionName, SearchResult); } } @@ -374,11 +412,11 @@ bool AShooterGameSession::JoinSession(TSharedPtr UserId, FNa * @param SessionName the name of the session this callback is for * @param bWasSuccessful true if the async action completed without error, false if there was an error */ -void AShooterGameSession::OnJoinSessionComplete(FName SessionName, EOnJoinSessionCompleteResult::Type Result) +void AShooterGameSession::OnJoinSessionComplete(FName InSessionName, EOnJoinSessionCompleteResult::Type Result) { bool bWillTravel = false; - UE_LOG(LogOnlineGame, Verbose, TEXT("OnJoinSessionComplete %s bSuccess: %d"), *SessionName.ToString(), static_cast(Result)); + UE_LOG(LogOnlineGame, Verbose, TEXT("OnJoinSessionComplete %s bSuccess: %d"), *InSessionName.ToString(), static_cast(Result)); IOnlineSubsystem* OnlineSub = IOnlineSubsystem::Get(); IOnlineSessionPtr Sessions = NULL; @@ -391,14 +429,14 @@ void AShooterGameSession::OnJoinSessionComplete(FName SessionName, EOnJoinSessio OnJoinSessionComplete().Broadcast(Result); } -bool AShooterGameSession::TravelToSession(int32 ControllerId, FName SessionName) +bool AShooterGameSession::TravelToSession(int32 ControllerId, FName InSessionName) { IOnlineSubsystem* OnlineSub = IOnlineSubsystem::Get(); if (OnlineSub) { FString URL; IOnlineSessionPtr Sessions = OnlineSub->GetSessionInterface(); - if (Sessions.IsValid() && Sessions->GetResolvedConnectString(SessionName, URL)) + if (Sessions.IsValid() && Sessions->GetResolvedConnectString(InSessionName, URL)) { APlayerController* PC = UGameplayStatics::GetPlayerController(GetWorld(), ControllerId); if (PC) @@ -427,3 +465,31 @@ bool AShooterGameSession::TravelToSession(int32 ControllerId, FName SessionName) return false; } + +void AShooterGameSession::RegisterServer() +{ + IOnlineSubsystem* OnlineSub = IOnlineSubsystem::Get(); + if (OnlineSub) + { + IOnlineSessionPtr SessionInt = Online::GetSessionInterface(); + if (SessionInt.IsValid()) + { + TSharedPtr ShooterHostSettings = MakeShareable(new FShooterOnlineSessionSettings(false, false, 16)); + ShooterHostSettings->Set(SETTING_MATCHING_HOPPER, FString("TeamDeathmatch"), EOnlineDataAdvertisementType::DontAdvertise); + ShooterHostSettings->Set(SETTING_MATCHING_TIMEOUT, 120.0f, EOnlineDataAdvertisementType::ViaOnlineService); + ShooterHostSettings->Set(SETTING_SESSION_TEMPLATE_NAME, FString("GameSession"), EOnlineDataAdvertisementType::DontAdvertise); + ShooterHostSettings->Set(SETTING_GAMEMODE, FString("TeamDeathmatch"), EOnlineDataAdvertisementType::ViaOnlineService); + ShooterHostSettings->Set(SETTING_MAPNAME, GetWorld()->GetMapName(), EOnlineDataAdvertisementType::ViaOnlineService); + ShooterHostSettings->bAllowInvites = true; + ShooterHostSettings->bIsDedicated = true; + if (FParse::Param(FCommandLine::Get(), TEXT("forcelan"))) + { + UE_LOG(LogOnlineGame, Log, TEXT("Registering server as a LAN server")); + ShooterHostSettings->bIsLANMatch = true; + } + HostSettings = ShooterHostSettings; + OnCreateSessionCompleteDelegateHandle = SessionInt->AddOnCreateSessionCompleteDelegate_Handle(OnCreateSessionCompleteDelegate); + SessionInt->CreateSession(0, NAME_GameSession, *HostSettings); + } + } +} diff --git a/Source/ShooterGame/Private/Online/ShooterGameState.cpp b/Source/ShooterGame/Private/Online/ShooterGameState.cpp index 428d231..b733193 100644 --- a/Source/ShooterGame/Private/Online/ShooterGameState.cpp +++ b/Source/ShooterGame/Private/Online/ShooterGameState.cpp @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ShooterGame.h" #include "Online/ShooterPlayerState.h" diff --git a/Source/ShooterGame/Private/Online/ShooterGame_FreeForAll.cpp b/Source/ShooterGame/Private/Online/ShooterGame_FreeForAll.cpp index c285e11..c78c1f4 100644 --- a/Source/ShooterGame/Private/Online/ShooterGame_FreeForAll.cpp +++ b/Source/ShooterGame/Private/Online/ShooterGame_FreeForAll.cpp @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ShooterGame.h" #include "ShooterGame_FreeForAll.h" @@ -12,7 +12,7 @@ AShooterGame_FreeForAll::AShooterGame_FreeForAll(const FObjectInitializer& Objec void AShooterGame_FreeForAll::DetermineMatchWinner() { AShooterGameState const* const MyGameState = CastChecked(GameState); - float BestScore = MAX_FLT; + float BestScore = MIN_flt; int32 BestPlayer = -1; int32 NumBestPlayers = 0; diff --git a/Source/ShooterGame/Private/Online/ShooterGame_Menu.cpp b/Source/ShooterGame/Private/Online/ShooterGame_Menu.cpp index cfd1e28..3d6f34e 100644 --- a/Source/ShooterGame/Private/Online/ShooterGame_Menu.cpp +++ b/Source/ShooterGame/Private/Online/ShooterGame_Menu.cpp @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ShooterGame.h" #include "ShooterGame_Menu.h" diff --git a/Source/ShooterGame/Private/Online/ShooterGame_TeamDeathMatch.cpp b/Source/ShooterGame/Private/Online/ShooterGame_TeamDeathMatch.cpp index 0866bb0..e75b47d 100644 --- a/Source/ShooterGame/Private/Online/ShooterGame_TeamDeathMatch.cpp +++ b/Source/ShooterGame/Private/Online/ShooterGame_TeamDeathMatch.cpp @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ShooterGame.h" #include "ShooterTeamStart.h" @@ -81,7 +81,7 @@ int32 AShooterGame_TeamDeathMatch::ChooseTeam(AShooterPlayerState* ForPlayerStat void AShooterGame_TeamDeathMatch::DetermineMatchWinner() { AShooterGameState const* const MyGameState = Cast(GameState); - int32 BestScore = MAX_uint32; + int32 BestScore = MIN_uint32; int32 BestTeam = -1; int32 NumBestTeams = 1; diff --git a/Source/ShooterGame/Private/Online/ShooterOnlineGameSettings.cpp b/Source/ShooterGame/Private/Online/ShooterOnlineGameSettings.cpp index b8d1581..9d417a6 100644 --- a/Source/ShooterGame/Private/Online/ShooterOnlineGameSettings.cpp +++ b/Source/ShooterGame/Private/Online/ShooterOnlineGameSettings.cpp @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ShooterGame.h" #include "ShooterOnlineGameSettings.h" diff --git a/Source/ShooterGame/Private/Online/ShooterOnlineGameSettings.h b/Source/ShooterGame/Private/Online/ShooterOnlineGameSettings.h index 3bccbb3..5cfd450 100644 --- a/Source/ShooterGame/Private/Online/ShooterOnlineGameSettings.h +++ b/Source/ShooterGame/Private/Online/ShooterOnlineGameSettings.h @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once diff --git a/Source/ShooterGame/Private/Online/ShooterOnlineSessionClient.cpp b/Source/ShooterGame/Private/Online/ShooterOnlineSessionClient.cpp new file mode 100644 index 0000000..4bf9103 --- /dev/null +++ b/Source/ShooterGame/Private/Online/ShooterOnlineSessionClient.cpp @@ -0,0 +1,60 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "ShooterGame.h" +#include "ShooterOnlineSessionClient.h" +#include "ShooterGameInstance.h" + +UShooterOnlineSessionClient::UShooterOnlineSessionClient() +{ +} + +void UShooterOnlineSessionClient::OnSessionUserInviteAccepted( + const bool bWasSuccess, + const int32 ControllerId, + TSharedPtr< const FUniqueNetId > UserId, + const FOnlineSessionSearchResult & InviteResult +) +{ + UE_LOG(LogOnline, Verbose, TEXT("HandleSessionUserInviteAccepted: bSuccess: %d, ControllerId: %d, User: %s"), bWasSuccess, ControllerId, UserId.IsValid() ? *UserId->ToString() : TEXT("NULL")); + + if (!bWasSuccess) + { + return; + } + + if (!InviteResult.IsValid()) + { + UE_LOG(LogOnline, Warning, TEXT("Invite accept returned no search result.")); + return; + } + + if (!UserId.IsValid()) + { + UE_LOG(LogOnline, Warning, TEXT("Invite accept returned no user.")); + return; + } + + UShooterGameInstance* ShooterGameInstance = Cast(GetGameInstance()); + + if (ShooterGameInstance) + { + FShooterPendingInvite PendingInvite; + + // Set the pending invite, and then go to the initial screen, which is where we will process it + PendingInvite.ControllerId = ControllerId; + PendingInvite.UserId = UserId; + PendingInvite.InviteResult = InviteResult; + PendingInvite.bPrivilegesCheckedAndAllowed = false; + + ShooterGameInstance->SetPendingInvite(PendingInvite); + ShooterGameInstance->GotoState(ShooterGameInstanceState::PendingInvite); + } +} + +void UShooterOnlineSessionClient::OnPlayTogetherEventReceived(int32 UserIndex, TArray> UserIdList) +{ + if (UShooterGameInstance* const ShooterGameInstance = Cast(GetGameInstance())) + { + ShooterGameInstance->OnPlayTogetherEventReceived(UserIndex, UserIdList); + } +} \ No newline at end of file diff --git a/Source/ShooterGame/Private/Online/ShooterPlayerState.cpp b/Source/ShooterGame/Private/Online/ShooterPlayerState.cpp index f0ae612..b3d6279 100644 --- a/Source/ShooterGame/Private/Online/ShooterPlayerState.cpp +++ b/Source/ShooterGame/Private/Online/ShooterPlayerState.cpp @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ShooterGame.h" #include "ShooterPlayerState.h" @@ -141,7 +141,7 @@ void AShooterPlayerState::ScoreDeath(AShooterPlayerState* KilledBy, int32 Points void AShooterPlayerState::ScorePoints(int32 Points) { - AShooterGameState* const MyGameState = Cast(GetWorld()->GameState); + AShooterGameState* const MyGameState = GetWorld()->GetGameState(); if (MyGameState && TeamNumber >= 0) { if (TeamNumber >= MyGameState->TeamScores.Num()) @@ -168,7 +168,7 @@ void AShooterPlayerState::InformAboutKill_Implementation(class AShooterPlayerSta { // a local player might not have an ID if it was created with CreateDebugPlayer. ULocalPlayer* LocalPlayer = Cast(TestPC->Player); - TSharedPtr LocalID = LocalPlayer->GetCachedUniqueNetId(); + FUniqueNetIdRepl LocalID = LocalPlayer->GetCachedUniqueNetId(); if (LocalID.IsValid() && *LocalPlayer->GetCachedUniqueNetId() == *KillerPlayerState->UniqueId) { TestPC->OnKill(); @@ -202,9 +202,9 @@ void AShooterPlayerState::GetLifetimeReplicatedProps( TArray< FLifetimeProperty FString AShooterPlayerState::GetShortPlayerName() const { - if( PlayerName.Len() > MAX_PLAYER_NAME_LENGTH ) + if( GetPlayerName().Len() > MAX_PLAYER_NAME_LENGTH ) { - return PlayerName.Left(MAX_PLAYER_NAME_LENGTH) + "..."; + return GetPlayerName().Left(MAX_PLAYER_NAME_LENGTH) + "..."; } - return PlayerName; + return GetPlayerName(); } diff --git a/Source/ShooterGame/Private/Online/ShooterReplicationGraph.cpp b/Source/ShooterGame/Private/Online/ShooterReplicationGraph.cpp new file mode 100644 index 0000000..9152476 --- /dev/null +++ b/Source/ShooterGame/Private/Online/ShooterReplicationGraph.cpp @@ -0,0 +1,863 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +/** +* +* ===================== ShooterReplicationGraph Replication ===================== +* +* Overview +* +* This changes the way actor relevancy works. AActor::IsNetRelevantFor is NOT used in this system! +* +* Instead, The UShooterReplicationGraph contains UReplicationGraphNodes. These nodes are responsible for generating lists of actors to replicate for each connection. +* Most of these lists are persistent across frames. This enables most of the gathering work ("which actors should be considered for replication) to be shared/reused. +* Nodes may be global (used by all connections), connection specific (each connection gets its own node), or shared (e.g, teams: all connections on the same team share). +* Actors can be in multiple nodes! For example a pawn may be in the spatialization node but also in the always-relevant-for-team node. It will be returned twice for +* teammates. This is ok though should be minimized when possible. +* +* UShooterReplicationGraph is intended to not be directly used by the game code. That is, you should not have to include ShooterReplicationGraph.h anywhere else. +* Rather, UShooterReplicationGraph depends on the game code and registers for events that the game code broadcasts (e.g., events for players joining/leaving teams). +* This choice was made because it gives UShooterReplicationGraph a complete holistic view of actor replication. Rather than exposing generic public functions that any +* place in game code can invoke, all notifications are explicitly registered in UShooterReplicationGraph::InitGlobalActorClassSettings. +* +* ShooterGame Nodes +* +* These are the top level nodes currently used: +* +* UReplicationGraphNode_GridSpatialization2D: +* This is the spatialization node. All "distance based relevant" actors will be routed here. This node divides the map into a 2D grid. Each cell in the grid contains +* children nodes that hold lists of actors based on how they update/go dormant. Actors are put in multiple cells. Connections pull from the single cell they are in. +* +* UReplicationGraphNode_ActorList +* This is an actor list node that contains the always relevant actors. These actors are always relevant to every connection. +* +* UShooterReplicationGraphNode_AlwaysRelevant_ForConnection +* This is the node for connection specific always relevant actors. This node does not maintain a persistent list but builds it each frame. This is possible because (currently) +* these actors are all easily accessed from the PlayerController. A persistent list would require notifications to be broadcast when these actors change, which would be possible +* but currently not necessary. +* +* UShooterReplicationGraphNode_PlayerStateFrequencyLimiter +* A custom node for handling player state replication. This replicates a small rolling set of player states (currently 2/frame). This is so player states replicate +* to simulated connections at a low, steady frequency, and to take advantage of serialization sharing. Auto proxy player states are replicated at higher frequency (to the +* owning connection only) via UShooterReplicationGraphNode_AlwaysRelevant_ForConnection. +* +* UReplicationGraphNode_TearOff_ForConnection +* Connection specific node for handling tear off actors. This is created and managed in the base implementation of Replication Graph. +* +* Dependent Actors (AShooterWeapon) +* +* Replication Graph introduces a concept of dependent actor replication. This is an actor (AShooterWeapon) that only replicates when another actor replicates (Pawn). I.e, the weapon +* actor itself never goes into the Replication Graph. It is never gathered on its own and never prioritized. It just has a chance to replicate when the Pawn replicates. This keeps +* the graph leaner since no extra work has to be done for the weapon actors. +* +* See UShooterReplicationGraph::OnCharacterWeaponChange: this is how actors are added/removed from the dependent actor list. +* +* How To Use +* +* Making something always relevant: Please avoid if you can :) If you must, just setting AActor::bAlwaysRelevant = true in the class defaults will do it. +* +* Making something always relevant to connection: You will need to modify UShooterReplicationGraphNode_AlwaysRelevant_ForConnection::GatherActorListsForConnection. You will also want +* to make sure the actor does not get put in one of the other nodes. The safest way to do this is by setting its EClassRepNodeMapping to NotRouted in UShooterReplicationGraph::InitGlobalActorClassSettings. +* +* How To Debug +* +* Its a good idea to just disable rep graph to see if your problem is specific to this system or just general replication/game play problem. +* +* If it is replication graph related, there are several useful commands that can be used: see ReplicationGraph_Debugging.cpp. The most useful are below. Use the 'cheat' command to run these on the server from a client. +* +* "Net.RepGraph.PrintGraph" - this will print the graph to the log: each node and actor. +* "Net.RepGraph.PrintGraph class" - same as above but will group by class. +* "Net.RepGraph.PrintGraph nclass" - same as above but will group by native classes (hides blueprint noise) +* +* Net.RepGraph.PrintAll <"Class"/"Nclass"> - will print the entire graph, the gathered actors, and how they were prioritized for a given connection for X amount of frames. +* +* Net.RepGraph.PrintAllActorInfo - will print the class, global, and connection replication info associated with an actor/class. If MatchString is empty will print everything. Call directly from client. +* +* ShooterRepGraph.PrintRouting - will print the EClassRepNodeMapping for each class. That is, how a given actor class is routed (or not) in the Replication Graph. +* +*/ + +#include "ShooterGame.h" +#include "ShooterReplicationGraph.h" + +#include "Net/UnrealNetwork.h" +#include "Engine/LevelStreaming.h" +#include "EngineUtils.h" +#include "CoreGlobals.h" + +#if WITH_GAMEPLAY_DEBUGGER +#include "GameplayDebuggerCategoryReplicator.h" +#endif + +#include "GameFramework/GameModeBase.h" +#include "GameFramework/GameState.h" +#include "GameFramework/PlayerState.h" +#include "GameFramework/Pawn.h" +#include "Engine/LevelScriptActor.h" +#include "Player/ShooterCharacter.h" +#include "Online/ShooterPlayerState.h" +#include "Weapons/ShooterWeapon.h" +#include "Pickups/ShooterPickup.h" + +DEFINE_LOG_CATEGORY( LogShooterReplicationGraph ); + +float CVar_ShooterRepGraph_DestructionInfoMaxDist = 30000.f; +static FAutoConsoleVariableRef CVarShooterRepGraphDestructMaxDist(TEXT("ShooterRepGraph.DestructInfo.MaxDist"), CVar_ShooterRepGraph_DestructionInfoMaxDist, TEXT("Max distance (not squared) to rep destruct infos at"), ECVF_Default ); + +int32 CVar_ShooterRepGraph_DisplayClientLevelStreaming = 0; +static FAutoConsoleVariableRef CVarShooterRepGraphDisplayClientLevelStreaming(TEXT("ShooterRepGraph.DisplayClientLevelStreaming"), CVar_ShooterRepGraph_DisplayClientLevelStreaming, TEXT(""), ECVF_Default ); + +float CVar_ShooterRepGraph_CellSize = 10000.f; +static FAutoConsoleVariableRef CVarShooterRepGraphCellSize(TEXT("ShooterRepGraph.CellSize"), CVar_ShooterRepGraph_CellSize, TEXT(""), ECVF_Default ); + +// Essentially "Min X" for replication. This is just an initial value. The system will reset itself if actors appears outside of this. +float CVar_ShooterRepGraph_SpatialBiasX = -150000.f; +static FAutoConsoleVariableRef CVarShooterRepGraphSpatialBiasX(TEXT("ShooterRepGraph.SpatialBiasX"), CVar_ShooterRepGraph_SpatialBiasX, TEXT(""), ECVF_Default ); + +// Essentially "Min Y" for replication. This is just an initial value. The system will reset itself if actors appears outside of this. +float CVar_ShooterRepGraph_SpatialBiasY = -200000.f; +static FAutoConsoleVariableRef CVarShooterRepSpatialBiasY(TEXT("ShooterRepGraph.SpatialBiasY"), CVar_ShooterRepGraph_SpatialBiasY, TEXT(""), ECVF_Default ); + +// How many buckets to spread dynamic, spatialized actors across. High number = more buckets = smaller effective replication frequency. This happens before individual actors do their own NetUpdateFrequency check. +int32 CVar_ShooterRepGraph_DynamicActorFrequencyBuckets = 3; +static FAutoConsoleVariableRef CVarShooterRepDynamicActorFrequencyBuckets(TEXT("ShooterRepGraph.DynamicActorFrequencyBuckets"), CVar_ShooterRepGraph_DynamicActorFrequencyBuckets, TEXT(""), ECVF_Default ); + +int32 CVar_ShooterRepGraph_DisableSpatialRebuilds = 1; +static FAutoConsoleVariableRef CVarShooterRepDisableSpatialRebuilds(TEXT("ShooterRepGraph.DisableSpatialRebuilds"), CVar_ShooterRepGraph_DisableSpatialRebuilds, TEXT(""), ECVF_Default ); + +// ---------------------------------------------------------------------------------------------------------- + + +UShooterReplicationGraph::UShooterReplicationGraph() +{ +} + +void InitClassReplicationInfo(FClassReplicationInfo& Info, UClass* Class, bool bSpatialize, float ServerMaxTickRate) +{ + AActor* CDO = Class->GetDefaultObject(); + if (bSpatialize) + { + Info.CullDistanceSquared = CDO->NetCullDistanceSquared; + UE_LOG(LogShooterReplicationGraph, Log, TEXT("Setting cull distance for %s to %f (%f)"), *Class->GetName(), Info.CullDistanceSquared, FMath::Sqrt(Info.CullDistanceSquared)); + } + + Info.ReplicationPeriodFrame = FMath::Max( (uint32)FMath::RoundToFloat(ServerMaxTickRate / CDO->NetUpdateFrequency), 1); + + UClass* NativeClass = Class; + while(!NativeClass->IsNative() && NativeClass->GetSuperClass() && NativeClass->GetSuperClass() != AActor::StaticClass()) + { + NativeClass = NativeClass->GetSuperClass(); + } + + UE_LOG(LogShooterReplicationGraph, Log, TEXT("Setting replication period for %s (%s) to %d frames (%.2f)"), *Class->GetName(), *NativeClass->GetName(), Info.ReplicationPeriodFrame, CDO->NetUpdateFrequency); +} + +void UShooterReplicationGraph::ResetGameWorldState() +{ + Super::ResetGameWorldState(); + + AlwaysRelevantStreamingLevelActors.Empty(); + + for (UNetReplicationGraphConnection* ConnManager : Connections) + { + for (UReplicationGraphNode* ConnectionNode : ConnManager->GetConnectionGraphNodes()) + { + if (UShooterReplicationGraphNode_AlwaysRelevant_ForConnection* AlwaysRelevantConnectionNode = Cast(ConnectionNode)) + { + AlwaysRelevantConnectionNode->ResetGameWorldState(); + } + } + } + + for (UNetReplicationGraphConnection* ConnManager : PendingConnections) + { + for (UReplicationGraphNode* ConnectionNode : ConnManager->GetConnectionGraphNodes()) + { + if (UShooterReplicationGraphNode_AlwaysRelevant_ForConnection* AlwaysRelevantConnectionNode = Cast(ConnectionNode)) + { + AlwaysRelevantConnectionNode->ResetGameWorldState(); + } + } + } +} + +void UShooterReplicationGraph::InitGlobalActorClassSettings() +{ + Super::InitGlobalActorClassSettings(); + + // --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + // Programatically build the rules. + // --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + + auto AddInfo = [&]( UClass* Class, EClassRepNodeMapping Mapping) { ClassRepNodePolicies.Set(Class, Mapping); }; + + AddInfo( AShooterWeapon::StaticClass(), EClassRepNodeMapping::NotRouted); // Handled via DependantActor replication (Pawn) + AddInfo( ALevelScriptActor::StaticClass(), EClassRepNodeMapping::NotRouted); // Not needed + AddInfo( APlayerState::StaticClass(), EClassRepNodeMapping::NotRouted); // Special cased via UShooterReplicationGraphNode_PlayerStateFrequencyLimiter + AddInfo( AReplicationGraphDebugActor::StaticClass(), EClassRepNodeMapping::NotRouted); // Not needed. Replicated special case inside RepGraph + AddInfo( AInfo::StaticClass(), EClassRepNodeMapping::RelevantAllConnections); // Non spatialized, relevant to all + AddInfo( AShooterPickup::StaticClass(), EClassRepNodeMapping::Spatialize_Static); // Spatialized and never moves. Routes to GridNode. + +#if WITH_GAMEPLAY_DEBUGGER + AddInfo( AGameplayDebuggerCategoryReplicator::StaticClass(), EClassRepNodeMapping::NotRouted); // Replicated via UShooterReplicationGraphNode_AlwaysRelevant_ForConnection +#endif + + TArray AllReplicatedClasses; + + for (TObjectIterator It; It; ++It) + { + UClass* Class = *It; + AActor* ActorCDO = Cast(Class->GetDefaultObject()); + if (!ActorCDO || !ActorCDO->GetIsReplicated()) + { + continue; + } + + // Skip SKEL and REINST classes. + if (Class->GetName().StartsWith(TEXT("SKEL_")) || Class->GetName().StartsWith(TEXT("REINST_"))) + { + continue; + } + + // -------------------------------------------------------------------- + // This is a replicated class. Save this off for the second pass below + // -------------------------------------------------------------------- + + AllReplicatedClasses.Add(Class); + + // Skip if already in the map (added explicitly) + if (ClassRepNodePolicies.Contains(Class, false)) + { + continue; + } + + auto ShouldSpatialize = [](const AActor* CDO) + { + return CDO->GetIsReplicated() && (!(CDO->bAlwaysRelevant || CDO->bOnlyRelevantToOwner || CDO->bNetUseOwnerRelevancy)); + }; + + auto GetLegacyDebugStr = [](const AActor* CDO) + { + return FString::Printf(TEXT("%s [%d/%d/%d]"), *CDO->GetClass()->GetName(), CDO->bAlwaysRelevant, CDO->bOnlyRelevantToOwner, CDO->bNetUseOwnerRelevancy); + }; + + // Only handle this class if it differs from its super. There is no need to put every child class explicitly in the graph class mapping + UClass* SuperClass = Class->GetSuperClass(); + if (AActor* SuperCDO = Cast(SuperClass->GetDefaultObject())) + { + if ( SuperCDO->GetIsReplicated() == ActorCDO->GetIsReplicated() + && SuperCDO->bAlwaysRelevant == ActorCDO->bAlwaysRelevant + && SuperCDO->bOnlyRelevantToOwner == ActorCDO->bOnlyRelevantToOwner + && SuperCDO->bNetUseOwnerRelevancy == ActorCDO->bNetUseOwnerRelevancy + ) + { + continue; + } + + if (ShouldSpatialize(ActorCDO) == false && ShouldSpatialize(SuperCDO) == true) + { + UE_LOG(LogShooterReplicationGraph, Log, TEXT("Adding %s to NonSpatializedChildClasses. (Parent: %s)"), *GetLegacyDebugStr(ActorCDO), *GetLegacyDebugStr(SuperCDO)); + NonSpatializedChildClasses.Add(Class); + } + } + + if (ShouldSpatialize(ActorCDO)) + { + AddInfo(Class, EClassRepNodeMapping::Spatialize_Dynamic); + } + else if (ActorCDO->bAlwaysRelevant && !ActorCDO->bOnlyRelevantToOwner) + { + AddInfo(Class, EClassRepNodeMapping::RelevantAllConnections); + } + } + + // ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + // Setup FClassReplicationInfo. This is essentially the per class replication settings. Some we set explicitly, the rest we are setting via looking at the legacy settings on AActor. + // ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + + TArray ExplicitlySetClasses; + auto SetClassInfo = [&](UClass* Class, const FClassReplicationInfo& Info) { GlobalActorReplicationInfoMap.SetClassInfo(Class, Info); ExplicitlySetClasses.Add(Class); }; + + FClassReplicationInfo PawnClassRepInfo; + PawnClassRepInfo.DistancePriorityScale = 1.f; + PawnClassRepInfo.StarvationPriorityScale = 1.f; + PawnClassRepInfo.ActorChannelFrameTimeout = 4; + PawnClassRepInfo.CullDistanceSquared = 15000.f * 15000.f; // Yuck + SetClassInfo( APawn::StaticClass(), PawnClassRepInfo ); + + FClassReplicationInfo PlayerStateRepInfo; + PlayerStateRepInfo.DistancePriorityScale = 0.f; + PlayerStateRepInfo.ActorChannelFrameTimeout = 0; + SetClassInfo( APlayerState::StaticClass(), PlayerStateRepInfo ); + + UReplicationGraphNode_ActorListFrequencyBuckets::DefaultSettings.ListSize = 12; + + // Set FClassReplicationInfo based on legacy settings from all replicated classes + for (UClass* ReplicatedClass : AllReplicatedClasses) + { + if (ExplicitlySetClasses.FindByPredicate([&](const UClass* SetClass) { return ReplicatedClass->IsChildOf(SetClass); }) != nullptr) + { + continue; + } + + const bool bClassIsSpatialized = IsSpatialized(ClassRepNodePolicies.GetChecked(ReplicatedClass)); + + FClassReplicationInfo ClassInfo; + InitClassReplicationInfo(ClassInfo, ReplicatedClass, bClassIsSpatialized, NetDriver->NetServerMaxTickRate); + GlobalActorReplicationInfoMap.SetClassInfo( ReplicatedClass, ClassInfo ); + } + + + // Print out what we came up with + UE_LOG(LogShooterReplicationGraph, Log, TEXT("")); + UE_LOG(LogShooterReplicationGraph, Log, TEXT("Class Routing Map: ")); + UEnum* Enum = StaticEnum(); + for (auto ClassMapIt = ClassRepNodePolicies.CreateIterator(); ClassMapIt; ++ClassMapIt) + { + UClass* Class = CastChecked(ClassMapIt.Key().ResolveObjectPtr()); + const EClassRepNodeMapping Mapping = ClassMapIt.Value(); + + // Only print if different than native class + UClass* ParentNativeClass = GetParentNativeClass(Class); + const EClassRepNodeMapping* ParentMapping = ClassRepNodePolicies.Get(ParentNativeClass); + if (ParentMapping && Class != ParentNativeClass && Mapping == *ParentMapping) + { + continue; + } + + UE_LOG(LogShooterReplicationGraph, Log, TEXT(" %s (%s) -> %s"), *Class->GetName(), *GetNameSafe(ParentNativeClass), *Enum->GetNameStringByValue(static_cast(Mapping))); + } + + UE_LOG(LogShooterReplicationGraph, Log, TEXT("")); + UE_LOG(LogShooterReplicationGraph, Log, TEXT("Class Settings Map: ")); + FClassReplicationInfo DefaultValues; + for (auto ClassRepInfoIt = GlobalActorReplicationInfoMap.CreateClassMapIterator(); ClassRepInfoIt; ++ClassRepInfoIt) + { + UClass* Class = CastChecked(ClassRepInfoIt.Key().ResolveObjectPtr()); + const FClassReplicationInfo& ClassInfo = ClassRepInfoIt.Value(); + UE_LOG(LogShooterReplicationGraph, Log, TEXT(" %s (%s) -> %s"), *Class->GetName(), *GetNameSafe(GetParentNativeClass(Class)), *ClassInfo.BuildDebugStringDelta()); + } + + + // Rep destruct infos based on CVar value + DestructInfoMaxDistanceSquared = CVar_ShooterRepGraph_DestructionInfoMaxDist * CVar_ShooterRepGraph_DestructionInfoMaxDist; + + // ------------------------------------------------------- + // Register for game code callbacks. + // This could have been done the other way: E.g, AMyGameActor could do GetNetDriver()->GetReplicationDriver()->OnMyGameEvent etc. + // This way at least keeps the rep graph out of game code directly and allows rep graph to exist in its own module + // So for now, erring on the side of a cleaning dependencies between classes. + // ------------------------------------------------------- + + AShooterCharacter::NotifyEquipWeapon.AddUObject(this, &UShooterReplicationGraph::OnCharacterEquipWeapon); + AShooterCharacter::NotifyUnEquipWeapon.AddUObject(this, &UShooterReplicationGraph::OnCharacterUnEquipWeapon); + +#if WITH_GAMEPLAY_DEBUGGER + AGameplayDebuggerCategoryReplicator::NotifyDebuggerOwnerChange.AddUObject(this, &UShooterReplicationGraph::OnGameplayDebuggerOwnerChange); +#endif +} + +void UShooterReplicationGraph::InitGlobalGraphNodes() +{ + // Preallocate some replication lists. + PreAllocateRepList(3, 12); + PreAllocateRepList(6, 12); + PreAllocateRepList(128, 64); + PreAllocateRepList(512, 16); + + // ----------------------------------------------- + // Spatial Actors + // ----------------------------------------------- + + GridNode = CreateNewNode(); + GridNode->CellSize = CVar_ShooterRepGraph_CellSize; + GridNode->SpatialBias = FVector2D(CVar_ShooterRepGraph_SpatialBiasX, CVar_ShooterRepGraph_SpatialBiasY); + + if (CVar_ShooterRepGraph_DisableSpatialRebuilds) + { + GridNode->AddSpatialRebuildBlacklistClass(AActor::StaticClass()); // Disable All spatial rebuilding + } + + AddGlobalGraphNode(GridNode); + + // ----------------------------------------------- + // Always Relevant (to everyone) Actors + // ----------------------------------------------- + AlwaysRelevantNode = CreateNewNode(); + AddGlobalGraphNode(AlwaysRelevantNode); + + // ----------------------------------------------- + // Player State specialization. This will return a rolling subset of the player states to replicate + // ----------------------------------------------- + UShooterReplicationGraphNode_PlayerStateFrequencyLimiter* PlayerStateNode = CreateNewNode(); + AddGlobalGraphNode(PlayerStateNode); +} + +void UShooterReplicationGraph::InitConnectionGraphNodes(UNetReplicationGraphConnection* RepGraphConnection) +{ + Super::InitConnectionGraphNodes(RepGraphConnection); + + UShooterReplicationGraphNode_AlwaysRelevant_ForConnection* AlwaysRelevantConnectionNode = CreateNewNode(); + + // This node needs to know when client levels go in and out of visibility + RepGraphConnection->OnClientVisibleLevelNameAdd.AddUObject(AlwaysRelevantConnectionNode, &UShooterReplicationGraphNode_AlwaysRelevant_ForConnection::OnClientLevelVisibilityAdd); + RepGraphConnection->OnClientVisibleLevelNameRemove.AddUObject(AlwaysRelevantConnectionNode, &UShooterReplicationGraphNode_AlwaysRelevant_ForConnection::OnClientLevelVisibilityRemove); + + AddConnectionGraphNode(AlwaysRelevantConnectionNode, RepGraphConnection); +} + +EClassRepNodeMapping UShooterReplicationGraph::GetMappingPolicy(UClass* Class) +{ + EClassRepNodeMapping* PolicyPtr = ClassRepNodePolicies.Get(Class); + EClassRepNodeMapping Policy = PolicyPtr ? *PolicyPtr : EClassRepNodeMapping::NotRouted; + return Policy; +} + +void UShooterReplicationGraph::RouteAddNetworkActorToNodes(const FNewReplicatedActorInfo& ActorInfo, FGlobalActorReplicationInfo& GlobalInfo) +{ + EClassRepNodeMapping Policy = GetMappingPolicy(ActorInfo.Class); + switch(Policy) + { + case EClassRepNodeMapping::NotRouted: + { + break; + } + + case EClassRepNodeMapping::RelevantAllConnections: + { + if (ActorInfo.StreamingLevelName == NAME_None) + { + AlwaysRelevantNode->NotifyAddNetworkActor(ActorInfo); + } + else + { + FActorRepListRefView& RepList = AlwaysRelevantStreamingLevelActors.FindOrAdd(ActorInfo.StreamingLevelName); + RepList.PrepareForWrite(); + RepList.ConditionalAdd(ActorInfo.Actor); + } + break; + } + + case EClassRepNodeMapping::Spatialize_Static: + { + GridNode->AddActor_Static(ActorInfo, GlobalInfo); + break; + } + + case EClassRepNodeMapping::Spatialize_Dynamic: + { + GridNode->AddActor_Dynamic(ActorInfo, GlobalInfo); + break; + } + + case EClassRepNodeMapping::Spatialize_Dormancy: + { + GridNode->AddActor_Dormancy(ActorInfo, GlobalInfo); + break; + } + }; +} + +void UShooterReplicationGraph::RouteRemoveNetworkActorToNodes(const FNewReplicatedActorInfo& ActorInfo) +{ + EClassRepNodeMapping Policy = GetMappingPolicy(ActorInfo.Class); + switch(Policy) + { + case EClassRepNodeMapping::NotRouted: + { + break; + } + + case EClassRepNodeMapping::RelevantAllConnections: + { + if (ActorInfo.StreamingLevelName == NAME_None) + { + AlwaysRelevantNode->NotifyRemoveNetworkActor(ActorInfo); + } + else + { + FActorRepListRefView& RepList = AlwaysRelevantStreamingLevelActors.FindChecked(ActorInfo.StreamingLevelName); + if (RepList.Remove(ActorInfo.Actor) == false) + { + UE_LOG(LogShooterReplicationGraph, Warning, TEXT("Actor %s was not found in AlwaysRelevantStreamingLevelActors list. LevelName: %s"), *GetActorRepListTypeDebugString(ActorInfo.Actor), *ActorInfo.StreamingLevelName.ToString()); + } + } + break; + } + + case EClassRepNodeMapping::Spatialize_Static: + { + GridNode->RemoveActor_Static(ActorInfo); + break; + } + + case EClassRepNodeMapping::Spatialize_Dynamic: + { + GridNode->RemoveActor_Dynamic(ActorInfo); + break; + } + + case EClassRepNodeMapping::Spatialize_Dormancy: + { + GridNode->RemoveActor_Dormancy(ActorInfo); + break; + } + }; +} + +// Since we listen to global (static) events, we need to watch out for cross world broadcasts (PIE) +#if WITH_EDITOR +#define CHECK_WORLDS(X) if(X->GetWorld() != GetWorld()) return; +#else +#define CHECK_WORLDS(X) +#endif + +void UShooterReplicationGraph::OnCharacterEquipWeapon(AShooterCharacter* Character, AShooterWeapon* NewWeapon) +{ + if (Character && NewWeapon) + { + CHECK_WORLDS(Character); + + FGlobalActorReplicationInfo& ActorInfo = GlobalActorReplicationInfoMap.Get(Character); + ActorInfo.DependentActorList.PrepareForWrite(); + + if (!ActorInfo.DependentActorList.Contains(NewWeapon)) + { + ActorInfo.DependentActorList.Add(NewWeapon); + } + } +} + +void UShooterReplicationGraph::OnCharacterUnEquipWeapon(AShooterCharacter* Character, AShooterWeapon* OldWeapon) +{ + if (Character && OldWeapon) + { + CHECK_WORLDS(Character); + + FGlobalActorReplicationInfo& ActorInfo = GlobalActorReplicationInfoMap.Get(Character); + ActorInfo.DependentActorList.PrepareForWrite(); + + ActorInfo.DependentActorList.Remove(OldWeapon); + } +} + +#if WITH_GAMEPLAY_DEBUGGER +void UShooterReplicationGraph::OnGameplayDebuggerOwnerChange(AGameplayDebuggerCategoryReplicator* Debugger, APlayerController* OldOwner) +{ + auto GetAlwaysRelevantForConnectionNode = [&](APlayerController* Controller) -> UShooterReplicationGraphNode_AlwaysRelevant_ForConnection* + { + if (OldOwner) + { + if (UNetConnection* NetConnection = OldOwner->GetNetConnection()) + { + if (UNetReplicationGraphConnection* GraphConnection = FindOrAddConnectionManager(NetConnection)) + { + for (UReplicationGraphNode* ConnectionNode : GraphConnection->GetConnectionGraphNodes()) + { + if (UShooterReplicationGraphNode_AlwaysRelevant_ForConnection* AlwaysRelevantConnectionNode = Cast(ConnectionNode)) + { + return AlwaysRelevantConnectionNode; + } + } + + } + } + } + + return nullptr; + }; + + if (UShooterReplicationGraphNode_AlwaysRelevant_ForConnection* AlwaysRelevantConnectionNode = GetAlwaysRelevantForConnectionNode(OldOwner)) + { + AlwaysRelevantConnectionNode->GameplayDebugger = nullptr; + } + + if (UShooterReplicationGraphNode_AlwaysRelevant_ForConnection* AlwaysRelevantConnectionNode = GetAlwaysRelevantForConnectionNode(Debugger->GetReplicationOwner())) + { + AlwaysRelevantConnectionNode->GameplayDebugger = Debugger; + } +} +#endif + +// ------------------------------------------------------------------------------ + +void UShooterReplicationGraphNode_AlwaysRelevant_ForConnection::ResetGameWorldState() +{ + AlwaysRelevantStreamingLevelsNeedingReplication.Empty(); +} + +void UShooterReplicationGraphNode_AlwaysRelevant_ForConnection::GatherActorListsForConnection(const FConnectionGatherActorListParameters& Params) +{ + QUICK_SCOPE_CYCLE_COUNTER( UShooterReplicationGraphNode_AlwaysRelevant_ForConnection_GatherActorListsForConnection ); + + UShooterReplicationGraph* ShooterGraph = CastChecked(GetOuter()); + + ReplicationActorList.Reset(); + + ReplicationActorList.ConditionalAdd(Params.Viewer.InViewer); + ReplicationActorList.ConditionalAdd(Params.Viewer.ViewTarget); + + if (AShooterPlayerController* PC = Cast(Params.Viewer.InViewer)) + { + // 50% throttling of PlayerStates. + const bool bReplicatePS = (Params.ConnectionManager.ConnectionId % 2) == (Params.ReplicationFrameNum % 2); + if (bReplicatePS) + { + // Always return the player state to the owning player. Simulated proxy player states are handled by UShooterReplicationGraphNode_PlayerStateFrequencyLimiter + if (APlayerState* PS = PC->PlayerState) + { + if (!bInitializedPlayerState) + { + bInitializedPlayerState = true; + FConnectionReplicationActorInfo& ConnectionActorInfo = Params.ConnectionManager.ActorInfoMap.FindOrAdd( PS ); + ConnectionActorInfo.ReplicationPeriodFrame = 1; + } + + ReplicationActorList.ConditionalAdd(PS); + } + } + + if (AShooterCharacter* Pawn = Cast(PC->GetPawn())) + { + if (Pawn != LastPawn) + { + UE_LOG(LogShooterReplicationGraph, Verbose, TEXT("Setting connection pawn cull distance to 0. %s"), *Pawn->GetName()); + LastPawn = Pawn; + FConnectionReplicationActorInfo& ConnectionActorInfo = Params.ConnectionManager.ActorInfoMap.FindOrAdd( Pawn ); + ConnectionActorInfo.CullDistanceSquared = 0.f; + } + + if (Pawn != Params.Viewer.ViewTarget) + { + ReplicationActorList.ConditionalAdd(Pawn); + } + + int32 InventoryCount = Pawn->GetInventoryCount(); + for (int32 i = 0; i < InventoryCount; ++i) + { + AShooterWeapon* Weapon = Pawn->GetInventoryWeapon(i); + if (Weapon) + { + ReplicationActorList.ConditionalAdd(Weapon); + } + } + } + + if (Params.Viewer.ViewTarget != LastPawn) + { + if (AShooterCharacter* ViewTargetPawn = Cast(Params.Viewer.ViewTarget)) + { + UE_LOG(LogShooterReplicationGraph, Verbose, TEXT("Setting connection view target pawn cull distance to 0. %s"), *ViewTargetPawn->GetName()); + LastPawn = ViewTargetPawn; + FConnectionReplicationActorInfo& ConnectionActorInfo = Params.ConnectionManager.ActorInfoMap.FindOrAdd(ViewTargetPawn); + ConnectionActorInfo.CullDistanceSquared = 0.f; + } + } + } + + Params.OutGatheredReplicationLists.AddReplicationActorList(ReplicationActorList); + + // Always relevant streaming level actors. + FPerConnectionActorInfoMap& ConnectionActorInfoMap = Params.ConnectionManager.ActorInfoMap; + + TMap& AlwaysRelevantStreamingLevelActors = ShooterGraph->AlwaysRelevantStreamingLevelActors; + + for (int32 Idx=AlwaysRelevantStreamingLevelsNeedingReplication.Num()-1; Idx >= 0; --Idx) + { + const FName& StreamingLevel = AlwaysRelevantStreamingLevelsNeedingReplication[Idx]; + + FActorRepListRefView* Ptr = AlwaysRelevantStreamingLevelActors.Find(StreamingLevel); + if (Ptr == nullptr) + { + // No always relevant lists for that level + UE_CLOG(CVar_ShooterRepGraph_DisplayClientLevelStreaming > 0, LogShooterReplicationGraph, Display, TEXT("CLIENTSTREAMING Removing %s from AlwaysRelevantStreamingLevelActors because FActorRepListRefView is null. %s "), *StreamingLevel.ToString(), *Params.ConnectionManager.GetName()); + AlwaysRelevantStreamingLevelsNeedingReplication.RemoveAtSwap(Idx, 1, false); + continue; + } + + FActorRepListRefView& RepList = *Ptr; + + if (RepList.Num() > 0) + { + bool bAllDormant = true; + for (FActorRepListType Actor : RepList) + { + FConnectionReplicationActorInfo& ConnectionActorInfo = ConnectionActorInfoMap.FindOrAdd(Actor); + if (ConnectionActorInfo.bDormantOnConnection == false) + { + bAllDormant = false; + break; + } + } + + if (bAllDormant) + { + UE_CLOG(CVar_ShooterRepGraph_DisplayClientLevelStreaming > 0, LogShooterReplicationGraph, Display, TEXT("CLIENTSTREAMING All AlwaysRelevant Actors Dormant on StreamingLevel %s for %s. Removing list."), *StreamingLevel.ToString(), *Params.ConnectionManager.GetName()); + AlwaysRelevantStreamingLevelsNeedingReplication.RemoveAtSwap(Idx, 1, false); + } + else + { + UE_CLOG(CVar_ShooterRepGraph_DisplayClientLevelStreaming > 0, LogShooterReplicationGraph, Display, TEXT("CLIENTSTREAMING Adding always Actors on StreamingLevel %s for %s because it has at least one non dormant actor"), *StreamingLevel.ToString(), *Params.ConnectionManager.GetName()); + Params.OutGatheredReplicationLists.AddReplicationActorList(RepList); + } + } + else + { + UE_LOG(LogShooterReplicationGraph, Warning, TEXT("UShooterReplicationGraphNode_AlwaysRelevant_ForConnection::GatherActorListsForConnection - empty RepList %s"), *Params.ConnectionManager.GetName()); + } + + } + +#if WITH_GAMEPLAY_DEBUGGER + if (GameplayDebugger) + { + ReplicationActorList.ConditionalAdd(GameplayDebugger); + } +#endif +} + +void UShooterReplicationGraphNode_AlwaysRelevant_ForConnection::OnClientLevelVisibilityAdd(FName LevelName, UWorld* StreamingWorld) +{ + UE_CLOG(CVar_ShooterRepGraph_DisplayClientLevelStreaming > 0, LogShooterReplicationGraph, Display, TEXT("CLIENTSTREAMING ::OnClientLevelVisibilityAdd - %s"), *LevelName.ToString()); + AlwaysRelevantStreamingLevelsNeedingReplication.Add(LevelName); +} + +void UShooterReplicationGraphNode_AlwaysRelevant_ForConnection::OnClientLevelVisibilityRemove(FName LevelName) +{ + UE_CLOG(CVar_ShooterRepGraph_DisplayClientLevelStreaming > 0, LogShooterReplicationGraph, Display, TEXT("CLIENTSTREAMING ::OnClientLevelVisibilityRemove - %s"), *LevelName.ToString()); + AlwaysRelevantStreamingLevelsNeedingReplication.Remove(LevelName); +} + +void UShooterReplicationGraphNode_AlwaysRelevant_ForConnection::LogNode(FReplicationGraphDebugInfo& DebugInfo, const FString& NodeName) const +{ + DebugInfo.Log(NodeName); + DebugInfo.PushIndent(); + LogActorRepList(DebugInfo, NodeName, ReplicationActorList); + + for (const FName& LevelName : AlwaysRelevantStreamingLevelsNeedingReplication) + { + UShooterReplicationGraph* ShooterGraph = CastChecked(GetOuter()); + if (FActorRepListRefView* RepList = ShooterGraph->AlwaysRelevantStreamingLevelActors.Find(LevelName)) + { + LogActorRepList(DebugInfo, FString::Printf(TEXT("AlwaysRelevant StreamingLevel List: %s"), *LevelName.ToString()), *RepList); + } + } + + DebugInfo.PopIndent(); +} + +// ------------------------------------------------------------------------------ + +UShooterReplicationGraphNode_PlayerStateFrequencyLimiter::UShooterReplicationGraphNode_PlayerStateFrequencyLimiter() +{ + bRequiresPrepareForReplicationCall = true; +} + +void UShooterReplicationGraphNode_PlayerStateFrequencyLimiter::PrepareForReplication() +{ + QUICK_SCOPE_CYCLE_COUNTER( UShooterReplicationGraphNode_PlayerStateFrequencyLimiter_GlobalPrepareForReplication ); + + ReplicationActorLists.Reset(); + ForceNetUpdateReplicationActorList.Reset(); + + ReplicationActorLists.AddDefaulted(); + FActorRepListRefView* CurrentList = &ReplicationActorLists[0]; + CurrentList->PrepareForWrite(); + + // We rebuild our lists of player states each frame. This is not as efficient as it could be but its the simplest way + // to handle players disconnecting and keeping the lists compact. If the lists were persistent we would need to defrag them as players left. + + for (TActorIterator It(GetWorld()); It; ++It) + { + APlayerState* PS = *It; + if (IsActorValidForReplicationGather(PS) == false) + { + continue; + } + + if (CurrentList->Num() >= TargetActorsPerFrame) + { + ReplicationActorLists.AddDefaulted(); + CurrentList = &ReplicationActorLists.Last(); + CurrentList->PrepareForWrite(); + } + + CurrentList->Add(PS); + } +} + +void UShooterReplicationGraphNode_PlayerStateFrequencyLimiter::GatherActorListsForConnection(const FConnectionGatherActorListParameters& Params) +{ + const int32 ListIdx = Params.ReplicationFrameNum % ReplicationActorLists.Num(); + Params.OutGatheredReplicationLists.AddReplicationActorList(ReplicationActorLists[ListIdx]); + + if (ForceNetUpdateReplicationActorList.Num() > 0) + { + Params.OutGatheredReplicationLists.AddReplicationActorList(ForceNetUpdateReplicationActorList); + } +} + +void UShooterReplicationGraphNode_PlayerStateFrequencyLimiter::LogNode(FReplicationGraphDebugInfo& DebugInfo, const FString& NodeName) const +{ + DebugInfo.Log(NodeName); + DebugInfo.PushIndent(); + + int32 i=0; + for (const FActorRepListRefView& List : ReplicationActorLists) + { + LogActorRepList(DebugInfo, FString::Printf(TEXT("Bucket[%d]"), i++), List); + } + + DebugInfo.PopIndent(); +} + +// ------------------------------------------------------------------------------ + +void UShooterReplicationGraph::PrintRepNodePolicies() +{ + UEnum* Enum = StaticEnum(); + if (!Enum) + { + return; + } + + GLog->Logf(TEXT("====================================")); + GLog->Logf(TEXT("Shooter Replication Routing Policies")); + GLog->Logf(TEXT("====================================")); + + for (auto It = ClassRepNodePolicies.CreateIterator(); It; ++It) + { + FObjectKey ObjKey = It.Key(); + + EClassRepNodeMapping Mapping = It.Value(); + + GLog->Logf(TEXT("%-40s --> %s"), *GetNameSafe(ObjKey.ResolveObjectPtr()), *Enum->GetNameStringByValue(static_cast(Mapping))); + } +} + +FAutoConsoleCommandWithWorldAndArgs ShooterPrintRepNodePoliciesCmd(TEXT("ShooterRepGraph.PrintRouting"),TEXT("Prints how actor classes are routed to RepGraph nodes"), + FConsoleCommandWithWorldAndArgsDelegate::CreateLambda([](const TArray& Args, UWorld* World) + { + for (TObjectIterator It; It; ++It) + { + It->PrintRepNodePolicies(); + } + }) +); + +// ------------------------------------------------------------------------------ + +FAutoConsoleCommandWithWorldAndArgs ChangeFrequencyBucketsCmd(TEXT("ShooterRepGraph.FrequencyBuckets"), TEXT("Resets frequency bucket count."), FConsoleCommandWithWorldAndArgsDelegate::CreateLambda([](const TArray< FString >& Args, UWorld* World) +{ + int32 Buckets = 1; + if (Args.Num() > 0) + { + LexTryParseString(Buckets, *Args[0]); + } + + UE_LOG(LogShooterReplicationGraph, Display, TEXT("Setting Frequency Buckets to %d"), Buckets); + for (TObjectIterator It; It; ++It) + { + UReplicationGraphNode_ActorListFrequencyBuckets* Node = *It; + Node->SetNonStreamingCollectionSize(Buckets); + } +})); diff --git a/Source/ShooterGame/Private/Online/ShooterReplicationGraph.h b/Source/ShooterGame/Private/Online/ShooterReplicationGraph.h new file mode 100644 index 0000000..5fb7304 --- /dev/null +++ b/Source/ShooterGame/Private/Online/ShooterReplicationGraph.h @@ -0,0 +1,143 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "ReplicationGraph.h" +#include "ShooterReplicationGraph.generated.h" + +class AShooterCharacter; +class AShooterWeapon; +class UReplicationGraphNode_GridSpatialization2D; +class AGameplayDebuggerCategoryReplicator; + +DECLARE_LOG_CATEGORY_EXTERN( LogShooterReplicationGraph, Display, All ); + +// This is the main enum we use to route actors to the right replication node. Each class maps to one enum. +UENUM() +enum class EClassRepNodeMapping : uint32 +{ + NotRouted, // Doesn't map to any node. Used for special case actors that handled by special case nodes (UShooterReplicationGraphNode_PlayerStateFrequencyLimiter) + RelevantAllConnections, // Routes to an AlwaysRelevantNode or AlwaysRelevantStreamingLevelNode node + + // ONLY SPATIALIZED Enums below here! See UShooterReplicationGraph::IsSpatialized + + Spatialize_Static, // Routes to GridNode: these actors don't move and don't need to be updated every frame. + Spatialize_Dynamic, // Routes to GridNode: these actors mode frequently and are updated once per frame. + Spatialize_Dormancy, // Routes to GridNode: While dormant we treat as static. When flushed/not dormant dynamic. Note this is for things that "move while not dormant". +}; + +/** ShooterGame Replication Graph implementation. See additional notes in ShooterReplicationGraph.cpp! */ +UCLASS(transient, config=Engine) +class UShooterReplicationGraph :public UReplicationGraph +{ + GENERATED_BODY() + +public: + + UShooterReplicationGraph(); + + virtual void ResetGameWorldState() override; + + virtual void InitGlobalActorClassSettings() override; + virtual void InitGlobalGraphNodes() override; + virtual void InitConnectionGraphNodes(UNetReplicationGraphConnection* RepGraphConnection) override; + virtual void RouteAddNetworkActorToNodes(const FNewReplicatedActorInfo& ActorInfo, FGlobalActorReplicationInfo& GlobalInfo) override; + virtual void RouteRemoveNetworkActorToNodes(const FNewReplicatedActorInfo& ActorInfo) override; + + UPROPERTY() + TArray SpatializedClasses; + + UPROPERTY() + TArray NonSpatializedChildClasses; + + UPROPERTY() + TArray AlwaysRelevantClasses; + + UPROPERTY() + UReplicationGraphNode_GridSpatialization2D* GridNode; + + UPROPERTY() + UReplicationGraphNode_ActorList* AlwaysRelevantNode; + + TMap AlwaysRelevantStreamingLevelActors; + + void OnCharacterEquipWeapon(AShooterCharacter* Character, AShooterWeapon* NewWeapon); + void OnCharacterUnEquipWeapon(AShooterCharacter* Character, AShooterWeapon* OldWeapon); + +#if WITH_GAMEPLAY_DEBUGGER + void OnGameplayDebuggerOwnerChange(AGameplayDebuggerCategoryReplicator* Debugger, APlayerController* OldOwner); +#endif + + void PrintRepNodePolicies(); + +private: + + EClassRepNodeMapping GetMappingPolicy(UClass* Class); + + bool IsSpatialized(EClassRepNodeMapping Mapping) const { return Mapping >= EClassRepNodeMapping::Spatialize_Static; } + + TClassMap ClassRepNodePolicies; +}; + +UCLASS() +class UShooterReplicationGraphNode_AlwaysRelevant_ForConnection : public UReplicationGraphNode +{ + GENERATED_BODY() + +public: + + virtual void NotifyAddNetworkActor(const FNewReplicatedActorInfo& Actor) override { } + virtual bool NotifyRemoveNetworkActor(const FNewReplicatedActorInfo& ActorInfo, bool bWarnIfNotFound=true) override { return false; } + virtual void NotifyResetAllNetworkActors() override { } + + virtual void GatherActorListsForConnection(const FConnectionGatherActorListParameters& Params) override; + + virtual void LogNode(FReplicationGraphDebugInfo& DebugInfo, const FString& NodeName) const override; + + void OnClientLevelVisibilityAdd(FName LevelName, UWorld* StreamingWorld); + void OnClientLevelVisibilityRemove(FName LevelName); + + void ResetGameWorldState(); + +#if WITH_GAMEPLAY_DEBUGGER + AGameplayDebuggerCategoryReplicator* GameplayDebugger = nullptr; +#endif + +private: + + TArray > AlwaysRelevantStreamingLevelsNeedingReplication; + + FActorRepListRefView ReplicationActorList; + + UPROPERTY() + AActor* LastPawn = nullptr; + + bool bInitializedPlayerState = false; +}; + +/** This is a specialized node for handling PlayerState replication in a frequency limited fashion. It tracks all player states but only returns a subset of them to the replication driver each frame. */ +UCLASS() +class UShooterReplicationGraphNode_PlayerStateFrequencyLimiter : public UReplicationGraphNode +{ + GENERATED_BODY() + + UShooterReplicationGraphNode_PlayerStateFrequencyLimiter(); + + virtual void NotifyAddNetworkActor(const FNewReplicatedActorInfo& Actor) override { } + virtual bool NotifyRemoveNetworkActor(const FNewReplicatedActorInfo& ActorInfo, bool bWarnIfNotFound=true) override { return false; } + + virtual void GatherActorListsForConnection(const FConnectionGatherActorListParameters& Params) override; + + virtual void PrepareForReplication() override; + + virtual void LogNode(FReplicationGraphDebugInfo& DebugInfo, const FString& NodeName) const override; + + /** How many actors we want to return to the replication driver per frame. Will not suppress ForceNetUpdate. */ + int32 TargetActorsPerFrame = 2; + +private: + + TArray ReplicationActorLists; + FActorRepListRefView ForceNetUpdateReplicationActorList; +}; \ No newline at end of file diff --git a/Source/ShooterGame/Private/Others/HelperMethods.cpp b/Source/ShooterGame/Private/Others/HelperMethods.cpp index 7bc1ea2..5cea8a1 100644 --- a/Source/ShooterGame/Private/Others/HelperMethods.cpp +++ b/Source/ShooterGame/Private/Others/HelperMethods.cpp @@ -326,9 +326,9 @@ FRecastQueryFilter_Example* HelperMethods::GetCustomFilter() { FRecastQueryFilter_Example* MyFRecastQueryFilter = NULL; UWorld * World = GetWorld(); if (World) { - UNavigationSystem* NavSys = World->GetNavigationSystem(); + UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent(World); if (NavSys) { - ANavigationData* NavData = NavSys->GetMainNavData(FNavigationSystem::Create); + ANavigationData* NavData = NavSys->GetDefaultNavDataInstance(FNavigationSystem::Create); AMyRecastNavMesh* MyNavMesh = Cast(NavData); MyFRecastQueryFilter = MyNavMesh->GetCustomFilter(); } diff --git a/Source/ShooterGame/Private/Others/HelperMethods.cpp.bak b/Source/ShooterGame/Private/Others/HelperMethods.cpp.bak new file mode 100644 index 0000000..9f881b0 --- /dev/null +++ b/Source/ShooterGame/Private/Others/HelperMethods.cpp.bak @@ -0,0 +1,338 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#include "ShooterGame.h" +#include "Bots/ShooterBot.h" +#include "Bots/ShooterAIController.h" +#include "Public/EQS/CoverBaseClass.h" +#include "Public/Navigation/MyRecastNavMesh.h" +#include "Public/Others/HelperMethods.h" + +FVector HelperMethods::GetPlayerPositionFromAI(UWorld * World) { + FVector PlayerPosition; + TArray AIBots; + + UGameplayStatics::GetAllActorsOfClass(World, AShooterBot::StaticClass(), AIBots); + + if (AIBots.Num() == 0) { + return PlayerPosition; + } + + AShooterBot* AnyBot = Cast(AIBots[0]); + + AShooterAIController* BotController = Cast(AnyBot->GetController()); + if (BotController) { + PlayerPosition = BotController->GetPL_fLocation(); + } + else { + // Just to debug purposes + TArray AllShooterCharacters; + UGameplayStatics::GetAllActorsOfClass(World, AShooterCharacter::StaticClass(), AllShooterCharacters); + + AShooterCharacter* PlayerCharacter = NULL; + for (auto It = AllShooterCharacters.CreateConstIterator(); It; ++It) { + AActor * Actor = *It; + if (!Cast(Actor)) { + PlayerCharacter = Cast(Actor); + break; + } + } + if (!PlayerCharacter) { + PlayerPosition = FVector(0, 0, 0); + } + else { + PlayerPosition = PlayerCharacter->GetActorLocation(); + } + + } + + return PlayerPosition; +} + +FVector HelperMethods::GetPlayerForwardVectorFromAI(UWorld * World) { + FVector PlayerForwardVector; + TArray AIBots; + + UGameplayStatics::GetAllActorsOfClass(World, AShooterBot::StaticClass(), AIBots); + + if (AIBots.Num() == 0) { + return PlayerForwardVector; + } + + AShooterBot* AnyBot = Cast(AIBots[0]); + + AShooterAIController* BotController = Cast(AnyBot->GetController()); + if (BotController && BotController->GetPL_fPlayer()) { + // @todo + PlayerForwardVector = BotController->GetPL_fPlayer()->GetActorForwardVector(); + } + else { + // Just to debug purposes + TArray AllShooterCharacters; + UGameplayStatics::GetAllActorsOfClass(World, AShooterCharacter::StaticClass(), AllShooterCharacters); + + AShooterCharacter* PlayerCharacter = NULL; + for (auto It = AllShooterCharacters.CreateConstIterator(); It; ++It) { + AActor * Actor = *It; + if (!Cast(Actor)) { + PlayerCharacter = Cast(Actor); + break; + } + } + PlayerForwardVector = PlayerCharacter->GetActorForwardVector(); + } + + return PlayerForwardVector; +} + +// http://www.redblobgames.com/articles/visibility/ +// http://gamedev.stackexchange.com/questions/21897/quick-2d-sight-area-calculation-algorithm +TArray HelperMethods::CalculateVisibility(UWorld * World, const FVector Location, const FVector ForwardVector, const float ViewAngle, const float ViewDistance){ + TArray VisibleLocations; + + if (World) { + //APawn * PlayerPawn = World->GetFirstPlayerController()->GetPawn(); + + TArray VisibleTriangles; + + TArray CubeActors; + TArray VisibleVertexs; + + + const FVector EyesLocation = FVector(Location.X, Location.Y, HelperMethods::EYES_POS_Z); + //FVector ForwardVector = UGameplayStatics::GetPlayerCameraManager(World, 0)->GetActorForwardVector(); + const FVector NewForwardVector = FVector(ForwardVector.X, ForwardVector.Y, 0); + // Calculate max start and max end point of the player's FOV + const FVector EndOfFirstTrace = EyesLocation + FRotator(0, -ViewAngle, 0).RotateVector(NewForwardVector) * ViewDistance; + const FVector EndOfLastTrace = EyesLocation + FRotator(0, ViewAngle, 0).RotateVector(NewForwardVector) * ViewDistance; + + Vertex FirstTraceVertex; + FirstTraceVertex.V = EndOfFirstTrace; + FirstTraceVertex.LeftmostVertex = true; + FirstTraceVertex.RightmostVertex = false; + + Vertex LastTraceVertex; + LastTraceVertex.V = EndOfLastTrace; + LastTraceVertex.LeftmostVertex = false; + LastTraceVertex.RightmostVertex = true; + + // Add them to visible vertexes + VisibleVertexs = HelperMethods::GetVisibleObstaclesVertexs(World, EyesLocation, NewForwardVector, ViewAngle, ViewDistance); + VisibleVertexs.Add(FirstTraceVertex); + VisibleVertexs.Add(LastTraceVertex); + + // Sort the vertexs according to the angle between them and the camera, and the camera forward vector + HelperMethods::SortByAngle(VisibleVertexs, EyesLocation, EndOfFirstTrace); + VisibleLocations = HelperMethods::GetVisibleTriangles(VisibleVertexs, World, EyesLocation); + } + + return VisibleLocations; +} + +TArray HelperMethods::GetVisibleObstaclesVertexs(UWorld * World, const FVector EyesLocation, const FVector ForwardVector, const float ViewAngle, const float ViewDistance) { + //UE_LOG(LogTemp, Log, TEXT("F:GetVisibleObstaclesVertexs")); + TArray CubeActors; + TArray VisibleVertexs; + + // Get All Boxes that are inside the FOV of the player + UGameplayStatics::GetAllActorsOfClass(World, ACoverBaseClass::StaticClass(), CubeActors); + for (auto It = CubeActors.CreateConstIterator(); It; ++It) { + const AActor* Cube = *It; + // Calculate the bounding box that contains the actor (Cube) + FVector Origin; + FVector BoundsExtent; + Cube->GetActorBounds(false, Origin, BoundsExtent); + + // Ignore obstacles shorter than Eye pos + if (Origin.Z + BoundsExtent.Z >= HelperMethods::EYES_POS_Z) { + TArray Aux; + + // Get the four upper vertexs + const FVector UpperVertex1 = FVector(Origin.X + BoundsExtent.X, Origin.Y + BoundsExtent.Y, HelperMethods::EYES_POS_Z); + const FVector UpperVertex2 = FVector(Origin.X - BoundsExtent.X, Origin.Y + BoundsExtent.Y, HelperMethods::EYES_POS_Z); + const FVector UpperVertex3 = FVector(Origin.X + BoundsExtent.X, Origin.Y - BoundsExtent.Y, HelperMethods::EYES_POS_Z); + const FVector UpperVertex4 = FVector(Origin.X - BoundsExtent.X, Origin.Y - BoundsExtent.Y, HelperMethods::EYES_POS_Z); + + Aux.Add(UpperVertex1); + Aux.Add(UpperVertex2); + Aux.Add(UpperVertex3); + Aux.Add(UpperVertex4); + + Aux.Sort([=](const FVector &Pos1, const FVector &Pos2) { + FVector FirstTraceDirection = FRotator(0, -ViewAngle * 2, 0).RotateVector(ForwardVector); + FVector VPos1 = Pos1 - EyesLocation; // Vertex 1 to Camera + FVector VPos2 = Pos2 - EyesLocation; // Vertex 2 to Camera + FirstTraceDirection.Normalize(); + VPos1.Normalize(); + VPos2.Normalize(); + + const float Angle1 = FMath::RadiansToDegrees(acosf(FirstTraceDirection.CosineAngle2D(VPos1))); // Angle with First trace vector + const float Angle2 = FMath::RadiansToDegrees(acosf(FirstTraceDirection.CosineAngle2D(VPos2))); // Angle with First trace vector + + return (Angle1 < Angle2); + + }); + + int counter = 0; + for (auto It2 = Aux.CreateConstIterator(); It2; ++It2) { + FVector V = *It2; + const float Angle = FMath::RadiansToDegrees(acosf((V - EyesLocation).CosineAngle2D(ForwardVector))); + if (Angle < ViewAngle) { // Inside Player's FOV + const float Distance = FVector::Dist(V, EyesLocation); + if (Distance < ViewDistance) { // Inside Player's FOV Max DIstance + Vertex vertex; + vertex.V = V; + vertex.LeftmostVertex = counter == 0; + vertex.RightmostVertex = counter == 3; + + VisibleVertexs.Add(vertex); + } + } + ++counter; + } + } + } + + return VisibleVertexs; +} + +void HelperMethods::SortByAngle(TArray &FVectorArray, const FVector EyesLocation, const FVector EndOfFirstTrace) { + //UE_LOG(LogTemp, Log, TEXT("F:SortByAngle")); + + FVectorArray.Sort([=](const Vertex &Pos1, const Vertex &Pos2) { + FVector FirstTraceDirection = EndOfFirstTrace - EyesLocation; // Leftmost point to Camera + FVector VPos1 = Pos1.V - EyesLocation; // Vertex 1 to Camera + FVector VPos2 = Pos2.V - EyesLocation; // Vertex 2 to Camera + FirstTraceDirection.Normalize(); + VPos1.Normalize(); + VPos2.Normalize(); + + const float Angle1 = FMath::RadiansToDegrees(acosf(FirstTraceDirection.CosineAngle2D(VPos1))); // Angle with First trace vector + const float Angle2 = FMath::RadiansToDegrees(acosf(FirstTraceDirection.CosineAngle2D(VPos2))); // Angle with First trace vector + + return (Angle1 < Angle2); + }); +} + +TArray HelperMethods::GetVisibleTriangles(const TArray VisibleVertexs, UWorld * World, const FVector EyesLocation, const float ViewAngle, const float ViewDistance) { + //UE_LOG(LogTemp, Log, TEXT("F:CalculateVisibleTriangles")); + TArray VisibleTriangles; + FHitResult OutHit; + FCollisionQueryParams CollisionParams; + TArray ActorsToIgnore; + //@todo debug + //const FName TraceTag("VisibilityTrace"); + //World->DebugDrawTraceTag = TraceTag; + //CollisionParams.TraceTag = TraceTag; + UGameplayStatics::GetAllActorsOfClass(World, AShooterCharacter::StaticClass(), ActorsToIgnore); + CollisionParams.AddIgnoredActors(ActorsToIgnore); + + FVector PreviousVertex = FVector(0, 0, 0); + for (auto It = VisibleVertexs.CreateConstIterator(); It; ++It) { + const Vertex Vertex = *It; + FVector TraceDirection = Vertex.V - EyesLocation; + TraceDirection.Normalize(); + + const FVector Offset = TraceDirection * HelperMethods::OFFSET; + const FVector MaxProjectedVertex = EyesLocation + TraceDirection * ViewDistance; + const bool BlockingHitFound = World->LineTraceSingleByChannel(OutHit, EyesLocation, Vertex.V + Offset, ECollisionChannel::ECC_Visibility, CollisionParams); + // Only First Trace + if (PreviousVertex == FVector(0, 0, 0)) { + if (BlockingHitFound) { + PreviousVertex = OutHit.ImpactPoint; + } + else { + const bool ProjectedBlockingHitFound = World->LineTraceSingleByChannel(OutHit, EyesLocation, MaxProjectedVertex, ECollisionChannel::ECC_Visibility, CollisionParams); + if (ProjectedBlockingHitFound) { + PreviousVertex = OutHit.ImpactPoint; // NOPE + } + else { + PreviousVertex = OutHit.TraceEnd; + } + } + } + else { + Triangle triangle = Triangle(); + triangle.V2 = EyesLocation; + triangle.V3 = PreviousVertex; + + if (BlockingHitFound) { + triangle.V1 = OutHit.ImpactPoint; + PreviousVertex = triangle.V1; + } + else { + const bool ProjectedBlockingHitFound = World->LineTraceSingleByChannel(OutHit, EyesLocation, MaxProjectedVertex, ECollisionChannel::ECC_Visibility, CollisionParams); + FVector ProjectedVertex; + if (ProjectedBlockingHitFound) { + ProjectedVertex = OutHit.ImpactPoint; + } + else { + ProjectedVertex = OutHit.TraceEnd; + } + + if (Vertex.LeftmostVertex) { + triangle.V1 = ProjectedVertex; + PreviousVertex = Vertex.V; + } + else if (Vertex.RightmostVertex) { + triangle.V1 = Vertex.V; + PreviousVertex = ProjectedVertex; + } + else { + triangle.V1 = Vertex.V; + PreviousVertex = Vertex.V; + } + } + VisibleTriangles.Add(triangle); + //PaintTriangle(triangle); + } + } + return VisibleTriangles; +} +/* +TArray HelperMethods::GetLocationOfCoverAnnotationsWithinRadius(UWorld * World, const FVector ContextLocation, const float MaxRadius) { + TArray LocationOfCoverAnnotations; + TArray Actors; + + UGameplayStatics::GetAllActorsOfClass(World, ACoverBaseClass::StaticClass(), Actors); + for (auto It = Actors.CreateConstIterator(); It; ++It) { + const AActor* Cover = *It; + const float Distance = FVector::Dist(Cover->GetActorLocation(), ContextLocation); + if (Distance <= MaxRadius) { + TArray CoverAnnotations; + Cover->GetAttachedActors(CoverAnnotations); + for (auto It2 = CoverAnnotations.CreateConstIterator(); It2; ++It2) { + LocationOfCoverAnnotations.Add((*It2)->GetActorLocation()); + } + } + } + return LocationOfCoverAnnotations; +} +*/ +/* +void HelperMethods::PaintTriangle(const Triangle Triangle) { + //UE_LOG(LogTemp, Log, TEXT("F:PaintTriangle")); + const TArray PointsInsideTriangle = Triangle.GetPointsInsideTriangle(); + + //@todo GetCustomFilter()->GetInfluenceMap()->Initialize(); + + // Paint each point of each triangle + for (auto It = PointsInsideTriangle.CreateConstIterator(); It; ++It) { + const FVector CurrentPoint = *It; + GetCustomFilter()->GetInfluenceMap()->SetInfluence(CurrentPoint, 255.0); + } +} + +FRecastQueryFilter_Example* HelperMethods::GetCustomFilter() { + FRecastQueryFilter_Example* MyFRecastQueryFilter = NULL; + UWorld * World = GetWorld(); + if (World) { + UNavigationSystem* NavSys = World->GetNavigationSystem(); + if (NavSys) { + ANavigationData* NavData = NavSys->GetMainNavData(FNavigationSystem::Create); + AMyRecastNavMesh* MyNavMesh = Cast(NavData); + MyFRecastQueryFilter = MyNavMesh->GetCustomFilter(); + } + } + return MyFRecastQueryFilter; +} +*/ \ No newline at end of file diff --git a/Source/ShooterGame/Private/Pickups/ShooterPickup.cpp b/Source/ShooterGame/Private/Pickups/ShooterPickup.cpp index 44d298c..84f439d 100644 --- a/Source/ShooterGame/Private/Pickups/ShooterPickup.cpp +++ b/Source/ShooterGame/Private/Pickups/ShooterPickup.cpp @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ShooterGame.h" #include "Pickups/ShooterPickup.h" @@ -17,7 +17,7 @@ AShooterPickup::AShooterPickup(const FObjectInitializer& ObjectInitializer) : Su PickupPSC = ObjectInitializer.CreateDefaultSubobject(this, TEXT("PickupFX")); PickupPSC->bAutoActivate = false; PickupPSC->bAutoDestroy = false; - PickupPSC->AttachParent = RootComponent; + PickupPSC->SetupAttachment(RootComponent); RespawnTime = 10.0f; bIsActive = false; @@ -85,12 +85,12 @@ void AShooterPickup::RespawnPickup() PickedUpBy = NULL; OnRespawned(); - TArray OverlappingPawns; + TSet OverlappingPawns; GetOverlappingActors(OverlappingPawns, AShooterCharacter::StaticClass()); - for (int32 i = 0; i < OverlappingPawns.Num(); i++) + for (AActor* OverlappingPawn : OverlappingPawns) { - PickupOnTouch(Cast(OverlappingPawns[i])); + PickupOnTouch(CastChecked(OverlappingPawn)); } } diff --git a/Source/ShooterGame/Private/Pickups/ShooterPickup_Ammo.cpp b/Source/ShooterGame/Private/Pickups/ShooterPickup_Ammo.cpp index 0ffc6af..f4c3d82 100644 --- a/Source/ShooterGame/Private/Pickups/ShooterPickup_Ammo.cpp +++ b/Source/ShooterGame/Private/Pickups/ShooterPickup_Ammo.cpp @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ShooterGame.h" #include "Pickups/ShooterPickup_Ammo.h" diff --git a/Source/ShooterGame/Private/Pickups/ShooterPickup_Health.cpp b/Source/ShooterGame/Private/Pickups/ShooterPickup_Health.cpp index e493997..fe6a47f 100644 --- a/Source/ShooterGame/Private/Pickups/ShooterPickup_Health.cpp +++ b/Source/ShooterGame/Private/Pickups/ShooterPickup_Health.cpp @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ShooterGame.h" #include "Pickups/ShooterPickup_Health.h" diff --git a/Source/ShooterGame/Private/Player/ShooterCharacter.cpp b/Source/ShooterGame/Private/Player/ShooterCharacter.cpp index 6e5ca63..d82b961 100644 --- a/Source/ShooterGame/Private/Player/ShooterCharacter.cpp +++ b/Source/ShooterGame/Private/Player/ShooterCharacter.cpp @@ -1,24 +1,45 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ShooterGame.h" #include "Weapons/ShooterWeapon.h" #include "Weapons/ShooterDamageType.h" #include "UI/ShooterHUD.h" -#include "Perception/AISense_Hearing.h" #include "Online/ShooterPlayerState.h" -#include "Bots/ShooterBot.h" -#include "Public/EQS/CoverBaseClass.h" - -AShooterCharacter::AShooterCharacter(const FObjectInitializer& ObjectInitializer) +#include "Animation/AnimMontage.h" +#include "Animation/AnimInstance.h" +#include "Sound/SoundNodeLocalPlayer.h" +#include "AudioThread.h" + +static int32 NetVisualizeRelevancyTestPoints = 0; +FAutoConsoleVariableRef CVarNetVisualizeRelevancyTestPoints( + TEXT("p.NetVisualizeRelevancyTestPoints"), + NetVisualizeRelevancyTestPoints, + TEXT("") + TEXT("0: Disable, 1: Enable"), + ECVF_Cheat); + + +static int32 NetEnablePauseRelevancy = 1; +FAutoConsoleVariableRef CVarNetEnablePauseRelevancy( + TEXT("p.NetEnablePauseRelevancy"), + NetEnablePauseRelevancy, + TEXT("") + TEXT("0: Disable, 1: Enable"), + ECVF_Cheat); + +FOnShooterCharacterEquipWeapon AShooterCharacter::NotifyEquipWeapon; +FOnShooterCharacterUnEquipWeapon AShooterCharacter::NotifyUnEquipWeapon; + +AShooterCharacter::AShooterCharacter(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer.SetDefaultSubobjectClass(ACharacter::CharacterMovementComponentName)) { Mesh1P = ObjectInitializer.CreateDefaultSubobject(this, TEXT("PawnMesh1P")); - Mesh1P->AttachParent = GetCapsuleComponent(); + Mesh1P->SetupAttachment(GetCapsuleComponent()); Mesh1P->bOnlyOwnerSee = true; Mesh1P->bOwnerNoSee = false; Mesh1P->bCastDynamicShadow = false; Mesh1P->bReceivesDecals = false; - Mesh1P->MeshComponentUpdateFlag = EMeshComponentUpdateFlag::OnlyTickPoseWhenRendered; + Mesh1P->VisibilityBasedAnimTickOption = EVisibilityBasedAnimTickOption::OnlyTickPoseWhenRendered; Mesh1P->PrimaryComponentTick.TickGroup = TG_PrePhysics; Mesh1P->SetCollisionObjectType(ECC_Pawn); Mesh1P->SetCollisionEnabled(ECollisionEnabled::NoCollision); @@ -48,53 +69,6 @@ AShooterCharacter::AShooterCharacter(const FObjectInitializer& ObjectInitializer BaseLookUpRate = 45.f; } -/** Implementation should check whether from given ObserverLocation -* implementer can be seen. If so OutSeenLocation should contain -* first visible location -* Return sight strength for how well the target is seen. -*/ - -bool AShooterCharacter::CanBeSeenFrom(const FVector& ObserverLocation, FVector& OutSeenLocation, int32& NumberOfLoSChecksPerformed, float& OutSightStrength, const AActor* IgnoreActor) const { - UWorld * World = GetWorld(); - if (World) { - FCollisionQueryParams CollisionParams; - //const FName TraceTag("VisibilityTrace"); - //World->DebugDrawTraceTag = TraceTag; - //CollisionParams.TraceTag = TraceTag; - CollisionParams.AddIgnoredActor(this); - - const FVector HeadLocation = FVector(this->GetActorLocation().X, this->GetActorLocation().Y, this->GetActorLocation().Z + 60); - const FVector2D BoundsExtent = FVector2D(30, 30); - const FVector Vertex1 = FVector(ObserverLocation.X + BoundsExtent.X, ObserverLocation.Y + BoundsExtent.Y, ObserverLocation.Z); - const FVector Vertex2 = FVector(ObserverLocation.X - BoundsExtent.X, ObserverLocation.Y + BoundsExtent.Y, ObserverLocation.Z); - const FVector Vertex3 = FVector(ObserverLocation.X + BoundsExtent.X, ObserverLocation.Y - BoundsExtent.Y, ObserverLocation.Z); - const FVector Vertex4 = FVector(ObserverLocation.X - BoundsExtent.X, ObserverLocation.Y - BoundsExtent.Y, ObserverLocation.Z); - - TArray TestVisibilityPoints; - TestVisibilityPoints.Add(Vertex1); - TestVisibilityPoints.Add(Vertex2); - TestVisibilityPoints.Add(Vertex3); - TestVisibilityPoints.Add(Vertex4); - - for (auto It = TestVisibilityPoints.CreateConstIterator(); It; ++It) { - FVector CurrentVertex = *It; - FHitResult OutHit; - - const bool BlockingHitFound = World->LineTraceSingleByChannel(OutHit, HeadLocation, CurrentVertex, ECollisionChannel::ECC_Visibility, CollisionParams); - if (BlockingHitFound && Cast(OutHit.GetActor())) { - NumberOfLoSChecksPerformed = It.GetIndex() + 1; - OutSeenLocation = ObserverLocation; - OutSightStrength = FVector::Dist(ObserverLocation, OutHit.ImpactPoint); - return true; - } - } - } - - NumberOfLoSChecksPerformed = 0; - OutSightStrength = 0; - return false; -} - void AShooterCharacter::PostInitializeComponents() { Super::PostInitializeComponents(); @@ -107,7 +81,7 @@ void AShooterCharacter::PostInitializeComponents() // set initial mesh visibility (3rd person view) UpdatePawnMeshes(); - + // create material instance for setting team colors (3rd person view) for (int32 iMat = 0; iMat < GetMesh()->GetNumMaterials(); iMat++) { @@ -163,7 +137,7 @@ void AShooterCharacter::OnRep_PlayerState() Super::OnRep_PlayerState(); // [client] as soon as PlayerState is assigned, set team colors of this pawn for local player - if (PlayerState != NULL) + if (GetPlayerState() != NULL) { UpdateTeamColorsAllMIDs(); } @@ -186,12 +160,12 @@ bool AShooterCharacter::IsEnemyFor(AController* TestPC) const } AShooterPlayerState* TestPlayerState = Cast(TestPC->PlayerState); - AShooterPlayerState* MyPlayerState = Cast(PlayerState); + AShooterPlayerState* MyPlayerState = Cast(GetPlayerState()); bool bIsEnemy = true; - if (GetWorld()->GameState && GetWorld()->GameState->GameModeClass) + if (GetWorld()->GetGameState()) { - const AShooterGameMode* DefGame = GetWorld()->GameState->GameModeClass->GetDefaultObject(); + const AShooterGameMode* DefGame = GetWorld()->GetGameState()->GetDefaultGameMode(); if (DefGame && MyPlayerState && TestPlayerState) { bIsEnemy = DefGame->CanDealDamage(TestPlayerState, MyPlayerState); @@ -201,7 +175,6 @@ bool AShooterCharacter::IsEnemyFor(AController* TestPC) const return bIsEnemy; } - ////////////////////////////////////////////////////////////////////////// // Meshes @@ -209,10 +182,10 @@ void AShooterCharacter::UpdatePawnMeshes() { bool const bFirstPerson = IsFirstPerson(); - Mesh1P->MeshComponentUpdateFlag = !bFirstPerson ? EMeshComponentUpdateFlag::OnlyTickPoseWhenRendered : EMeshComponentUpdateFlag::AlwaysTickPoseAndRefreshBones; + Mesh1P->VisibilityBasedAnimTickOption = !bFirstPerson ? EVisibilityBasedAnimTickOption::OnlyTickPoseWhenRendered : EVisibilityBasedAnimTickOption::AlwaysTickPoseAndRefreshBones; Mesh1P->SetOwnerNoSee(!bFirstPerson); - GetMesh()->MeshComponentUpdateFlag = bFirstPerson ? EMeshComponentUpdateFlag::OnlyTickPoseWhenRendered : EMeshComponentUpdateFlag::AlwaysTickPoseAndRefreshBones; + GetMesh()->VisibilityBasedAnimTickOption = bFirstPerson ? EVisibilityBasedAnimTickOption::OnlyTickPoseWhenRendered : EVisibilityBasedAnimTickOption::AlwaysTickPoseAndRefreshBones; GetMesh()->SetOwnerNoSee(bFirstPerson); } @@ -220,7 +193,7 @@ void AShooterCharacter::UpdateTeamColors(UMaterialInstanceDynamic* UseMID) { if (UseMID) { - AShooterPlayerState* MyPlayerState = Cast(PlayerState); + AShooterPlayerState* MyPlayerState = Cast(GetPlayerState()); if (MyPlayerState != NULL) { float MaterialParam = (float)MyPlayerState->GetTeamNum(); @@ -281,51 +254,48 @@ void AShooterCharacter::KilledBy(APawn* EventInstigator) float AShooterCharacter::TakeDamage(float Damage, struct FDamageEvent const& DamageEvent, class AController* EventInstigator, class AActor* DamageCauser) { - if (this->GetName().Contains("Bot") && DamageCauser->GetAttachParentActor()->GetName().Contains("Bot")) { - return 0; - }else { - AShooterPlayerController* MyPC = Cast(Controller); - if (MyPC && MyPC->HasGodMode()) - { - return 0.f; - } + AShooterPlayerController* MyPC = Cast(Controller); + if (MyPC && MyPC->HasGodMode()) + { + return 0.f; + } - if (Health <= 0.f) - { - return 0.f; - } + if (Health <= 0.f) + { + return 0.f; + } - // Modify based on game rules. - AShooterGameMode* const Game = GetWorld()->GetAuthGameMode(); - Damage = Game ? Game->ModifyDamage(Damage, this, DamageEvent, EventInstigator, DamageCauser) : 0.f; + // Modify based on game rules. + AShooterGameMode* const Game = GetWorld()->GetAuthGameMode(); + Damage = Game ? Game->ModifyDamage(Damage, this, DamageEvent, EventInstigator, DamageCauser) : 0.f; - const float ActualDamage = Super::TakeDamage(Damage, DamageEvent, EventInstigator, DamageCauser); - if (ActualDamage > 0.f) + const float ActualDamage = Super::TakeDamage(Damage, DamageEvent, EventInstigator, DamageCauser); + if (ActualDamage > 0.f) + { + Health -= ActualDamage; + if (Health <= 0) { - Health -= ActualDamage; - if (Health <= 0) - { - Die(ActualDamage, DamageEvent, EventInstigator, DamageCauser); - } - else - { - PlayHit(ActualDamage, DamageEvent, EventInstigator ? EventInstigator->GetPawn() : NULL, DamageCauser); - } - - MakeNoise(1.0f, EventInstigator ? EventInstigator->GetPawn() : this); + Die(ActualDamage, DamageEvent, EventInstigator, DamageCauser); + } + else + { + PlayHit(ActualDamage, DamageEvent, EventInstigator ? EventInstigator->GetPawn() : NULL, DamageCauser); } - return ActualDamage; + + MakeNoise(1.0f, EventInstigator ? EventInstigator->GetPawn() : this); } + + return ActualDamage; } bool AShooterCharacter::CanDie(float KillingDamage, FDamageEvent const& DamageEvent, AController* Killer, AActor* DamageCauser) const { - if ( bIsDying // already dying + if (bIsDying // already dying || IsPendingKill() // already destroyed || Role != ROLE_Authority // not authority - || GetWorld()->GetAuthGameMode() == NULL - || GetWorld()->GetAuthGameMode()->GetMatchState() == MatchState::LeavingMap) // level transition occurring + || GetWorld()->GetAuthGameMode() == NULL + || GetWorld()->GetAuthGameMode()->GetMatchState() == MatchState::LeavingMap) // level transition occurring { return false; } @@ -366,21 +336,23 @@ void AShooterCharacter::OnDeath(float KillingDamage, struct FDamageEvent const& } bReplicateMovement = false; - bTearOff = true; + TearOff(); bIsDying = true; if (Role == ROLE_Authority) { - ReplicateHit(KillingDamage, DamageEvent, PawnInstigator, DamageCauser, true); + ReplicateHit(KillingDamage, DamageEvent, PawnInstigator, DamageCauser, true); // play the force feedback effect on the client player controller - APlayerController* PC = Cast(Controller); + AShooterPlayerController* PC = Cast(Controller); if (PC && DamageEvent.DamageTypeClass) { UShooterDamageType *DamageType = Cast(DamageEvent.DamageTypeClass->GetDefaultObject()); - if (DamageType && DamageType->KilledForceFeedback) + if (DamageType && DamageType->KilledForceFeedback && PC->IsVibrationEnabled()) { - PC->ClientPlayForceFeedback(DamageType->KilledForceFeedback, false, "Damage"); + FForceFeedbackParameters FFParams; + FFParams.Tag = "Damage"; + PC->ClientPlayForceFeedback(DamageType->KilledForceFeedback, FFParams); } } } @@ -393,7 +365,7 @@ void AShooterCharacter::OnDeath(float KillingDamage, struct FDamageEvent const& // remove all weapons DestroyInventory(); - + // switch back to 3rd person view UpdatePawnMeshes(); @@ -410,8 +382,6 @@ void AShooterCharacter::OnDeath(float KillingDamage, struct FDamageEvent const& RunLoopAC->Stop(); } - - if (GetMesh()) { static FName CollisionProfileName(TEXT("Ragdoll")); @@ -425,9 +395,16 @@ void AShooterCharacter::OnDeath(float KillingDamage, struct FDamageEvent const& // Ragdoll if (DeathAnimDuration > 0.f) { + // Trigger ragdoll a little before the animation early so the character doesn't + // blend back to its normal position. + const float TriggerRagdollTime = DeathAnimDuration - 0.7f; + + // Enable blend physics so the bones are properly blending against the montage. + GetMesh()->bBlendPhysics = true; + // Use a local timer handle as we don't need to store it for later but we don't need to look for something to clear FTimerHandle TimerHandle; - GetWorldTimerManager().SetTimer(TimerHandle, this, &AShooterCharacter::SetRagdollPhysics, FMath::Min(0.1f, DeathAnimDuration), false); + GetWorldTimerManager().SetTimer(TimerHandle, this, &AShooterCharacter::SetRagdollPhysics, FMath::Max(0.1f, TriggerRagdollTime), false); } else { @@ -446,13 +423,15 @@ void AShooterCharacter::PlayHit(float DamageTaken, struct FDamageEvent const& Da ReplicateHit(DamageTaken, DamageEvent, PawnInstigator, DamageCauser, false); // play the force feedback effect on the client player controller - APlayerController* PC = Cast(Controller); + AShooterPlayerController* PC = Cast(Controller); if (PC && DamageEvent.DamageTypeClass) { UShooterDamageType *DamageType = Cast(DamageEvent.DamageTypeClass->GetDefaultObject()); - if (DamageType && DamageType->HitForceFeedback) + if (DamageType && DamageType->HitForceFeedback && PC->IsVibrationEnabled()) { - PC->ClientPlayForceFeedback(DamageType->HitForceFeedback, false, "Damage"); + FForceFeedbackParameters FFParams; + FFParams.Tag = "Damage"; + PC->ClientPlayForceFeedback(DamageType->HitForceFeedback, FFParams); } } } @@ -461,7 +440,7 @@ void AShooterCharacter::PlayHit(float DamageTaken, struct FDamageEvent const& Da { ApplyDamageMomentum(DamageTaken, DamageEvent, PawnInstigator, DamageCauser); } - + AShooterPlayerController* MyPC = Cast(Controller); AShooterHUD* MyHUD = MyPC ? Cast(MyPC->GetHUD()) : NULL; if (MyHUD) @@ -496,7 +475,6 @@ void AShooterCharacter::SetRagdollPhysics() else { // initialize physics/etc - GetMesh()->SetAllBodiesSimulatePhysics(true); GetMesh()->SetSimulatePhysics(true); GetMesh()->WakeAllRigidBodies(); GetMesh()->bBlendPhysics = true; @@ -513,11 +491,11 @@ void AShooterCharacter::SetRagdollPhysics() // hide and set short lifespan TurnOff(); SetActorHiddenInGame(true); - SetLifeSpan( 1.0f ); + SetLifeSpan(1.0f); } else { - SetLifeSpan( 10.0f ); + SetLifeSpan(10.0f); } } @@ -544,7 +522,7 @@ void AShooterCharacter::ReplicateHit(float Damage, struct FDamageEvent const& Da LastTakeHitInfo.ActualDamage = Damage; LastTakeHitInfo.PawnInstigator = Cast(PawnInstigator); LastTakeHitInfo.DamageCauser = DamageCauser; - LastTakeHitInfo.SetDamageEvent(DamageEvent); + LastTakeHitInfo.SetDamageEvent(DamageEvent); LastTakeHitInfo.bKilled = bKilled; LastTakeHitInfo.EnsureReplication(); @@ -569,6 +547,10 @@ void AShooterCharacter::TornOff() SetLifeSpan(25.f); } +bool AShooterCharacter::IsMoving() +{ + return FMath::Abs(GetLastMovementInputVector().Size()) > 0.f; +} ////////////////////////////////////////////////////////////////////////// // Inventory @@ -580,7 +562,7 @@ void AShooterCharacter::SpawnDefaultInventory() return; } - int32 NumWeaponClasses = DefaultInventoryClasses.Num(); + int32 NumWeaponClasses = DefaultInventoryClasses.Num(); for (int32 i = 0; i < NumWeaponClasses; i++) { if (DefaultInventoryClasses[i]) @@ -681,8 +663,8 @@ void AShooterCharacter::OnRep_CurrentWeapon(AShooterWeapon* LastWeapon) void AShooterCharacter::SetCurrentWeapon(AShooterWeapon* NewWeapon, AShooterWeapon* LastWeapon) { - AShooterWeapon* LocalLastWeapon = NULL; - + AShooterWeapon* LocalLastWeapon = nullptr; + if (LastWeapon != NULL) { LocalLastWeapon = LastWeapon; @@ -784,7 +766,6 @@ void AShooterCharacter::SetRunning(bool bNewRunning, bool bToggle) { ServerSetRunning(bNewRunning, bToggle); } - UpdateRunSounds(bNewRunning); } bool AShooterCharacter::ServerSetRunning_Validate(bool bNewRunning, bool bToggle) @@ -797,32 +778,31 @@ void AShooterCharacter::ServerSetRunning_Implementation(bool bNewRunning, bool b SetRunning(bNewRunning, bToggle); } -void AShooterCharacter::UpdateRunSounds(bool bNewRunning) +void AShooterCharacter::UpdateRunSounds() { - if (bNewRunning) + const bool bIsRunSoundPlaying = RunLoopAC != nullptr && RunLoopAC->IsActive(); + const bool bWantsRunSoundPlaying = IsRunning() && IsMoving(); + + // Don't bother playing the sounds unless we're running and moving. + if (!bIsRunSoundPlaying && bWantsRunSoundPlaying) { - if (!RunLoopAC && RunLoopSound) + if (RunLoopAC != nullptr) + { + RunLoopAC->Play(); + } + else if (RunLoopSound != nullptr) { RunLoopAC = UGameplayStatics::SpawnSoundAttached(RunLoopSound, GetRootComponent()); - if (RunLoopAC) + if (RunLoopAC != nullptr) { RunLoopAC->bAutoDestroy = false; } - - } - else if (RunLoopAC) - { - RunLoopAC->Play(); } } - else + else if (bIsRunSoundPlaying && !bWantsRunSoundPlaying) { - if (RunLoopAC) - { - RunLoopAC->Stop(); - } - - if (RunStopSound) + RunLoopAC->Stop(); + if (RunStopSound != nullptr) { UGameplayStatics::SpawnSoundAttached(RunStopSound, GetRootComponent()); } @@ -832,7 +812,7 @@ void AShooterCharacter::UpdateRunSounds(bool bNewRunning) ////////////////////////////////////////////////////////////////////////// // Animations -float AShooterCharacter::PlayAnimMontage(class UAnimMontage* AnimMontage, float InPlayRate, FName StartSectionName) +float AShooterCharacter::PlayAnimMontage(class UAnimMontage* AnimMontage, float InPlayRate, FName StartSectionName) { USkeletalMeshComponent* UseMesh = GetPawnMesh(); if (AnimMontage && UseMesh && UseMesh->AnimScriptInstance) @@ -849,7 +829,7 @@ void AShooterCharacter::StopAnimMontage(class UAnimMontage* AnimMontage) if (AnimMontage && UseMesh && UseMesh->AnimScriptInstance && UseMesh->AnimScriptInstance->Montage_IsPlaying(AnimMontage)) { - UseMesh->AnimScriptInstance->Montage_Stop(AnimMontage->BlendOutTime); + UseMesh->AnimScriptInstance->Montage_Stop(AnimMontage->BlendOut.GetBlendTime(), AnimMontage); } } @@ -866,34 +846,34 @@ void AShooterCharacter::StopAllAnimMontages() ////////////////////////////////////////////////////////////////////////// // Input -void AShooterCharacter::SetupPlayerInputComponent(class UInputComponent* InputComponent) +void AShooterCharacter::SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) { - check(InputComponent); - InputComponent->BindAxis("MoveForward", this, &AShooterCharacter::MoveForward); - InputComponent->BindAxis("MoveRight", this, &AShooterCharacter::MoveRight); - InputComponent->BindAxis("MoveUp", this, &AShooterCharacter::MoveUp); - InputComponent->BindAxis("Turn", this, &APawn::AddControllerYawInput); - InputComponent->BindAxis("TurnRate", this, &AShooterCharacter::TurnAtRate); - InputComponent->BindAxis("LookUp", this, &APawn::AddControllerPitchInput); - InputComponent->BindAxis("LookUpRate", this, &AShooterCharacter::LookUpAtRate); + check(PlayerInputComponent); + PlayerInputComponent->BindAxis("MoveForward", this, &AShooterCharacter::MoveForward); + PlayerInputComponent->BindAxis("MoveRight", this, &AShooterCharacter::MoveRight); + PlayerInputComponent->BindAxis("MoveUp", this, &AShooterCharacter::MoveUp); + PlayerInputComponent->BindAxis("Turn", this, &APawn::AddControllerYawInput); + PlayerInputComponent->BindAxis("TurnRate", this, &AShooterCharacter::TurnAtRate); + PlayerInputComponent->BindAxis("LookUp", this, &APawn::AddControllerPitchInput); + PlayerInputComponent->BindAxis("LookUpRate", this, &AShooterCharacter::LookUpAtRate); - InputComponent->BindAction("Fire", IE_Pressed, this, &AShooterCharacter::OnStartFire); - InputComponent->BindAction("Fire", IE_Released, this, &AShooterCharacter::OnStopFire); + PlayerInputComponent->BindAction("Fire", IE_Pressed, this, &AShooterCharacter::OnStartFire); + PlayerInputComponent->BindAction("Fire", IE_Released, this, &AShooterCharacter::OnStopFire); - InputComponent->BindAction("Targeting", IE_Pressed, this, &AShooterCharacter::OnStartTargeting); - InputComponent->BindAction("Targeting", IE_Released, this, &AShooterCharacter::OnStopTargeting); + PlayerInputComponent->BindAction("Targeting", IE_Pressed, this, &AShooterCharacter::OnStartTargeting); + PlayerInputComponent->BindAction("Targeting", IE_Released, this, &AShooterCharacter::OnStopTargeting); - InputComponent->BindAction("NextWeapon", IE_Pressed, this, &AShooterCharacter::OnNextWeapon); - InputComponent->BindAction("PrevWeapon", IE_Pressed, this, &AShooterCharacter::OnPrevWeapon); + PlayerInputComponent->BindAction("NextWeapon", IE_Pressed, this, &AShooterCharacter::OnNextWeapon); + PlayerInputComponent->BindAction("PrevWeapon", IE_Pressed, this, &AShooterCharacter::OnPrevWeapon); - InputComponent->BindAction("Reload", IE_Pressed, this, &AShooterCharacter::OnReload); + PlayerInputComponent->BindAction("Reload", IE_Pressed, this, &AShooterCharacter::OnReload); - InputComponent->BindAction("Jump", IE_Pressed, this, &AShooterCharacter::OnStartJump); - InputComponent->BindAction("Jump", IE_Released, this, &AShooterCharacter::OnStopJump); + PlayerInputComponent->BindAction("Jump", IE_Pressed, this, &AShooterCharacter::OnStartJump); + PlayerInputComponent->BindAction("Jump", IE_Released, this, &AShooterCharacter::OnStopJump); - InputComponent->BindAction("Run", IE_Pressed, this, &AShooterCharacter::OnStartRunning); - InputComponent->BindAction("RunToggle", IE_Pressed, this, &AShooterCharacter::OnStartRunningToggle); - InputComponent->BindAction("Run", IE_Released, this, &AShooterCharacter::OnStopRunning); + PlayerInputComponent->BindAction("Run", IE_Pressed, this, &AShooterCharacter::OnStartRunning); + PlayerInputComponent->BindAction("RunToggle", IE_Pressed, this, &AShooterCharacter::OnStartRunningToggle); + PlayerInputComponent->BindAction("Run", IE_Released, this, &AShooterCharacter::OnStopRunning); } @@ -904,7 +884,7 @@ void AShooterCharacter::MoveForward(float Val) // Limit pitch when walking or falling const bool bLimitRotation = (GetCharacterMovement()->IsMovingOnGround() || GetCharacterMovement()->IsFalling()); const FRotator Rotation = bLimitRotation ? GetActorRotation() : Controller->GetControlRotation(); - const FVector Direction = FRotationMatrix(Rotation).GetScaledAxis( EAxis::X ); + const FVector Direction = FRotationMatrix(Rotation).GetScaledAxis(EAxis::X); AddMovementInput(Direction, Val); } } @@ -914,7 +894,7 @@ void AShooterCharacter::MoveRight(float Val) if (Val != 0.f) { const FQuat Rotation = GetActorQuat(); - const FVector Direction = FQuatRotationMatrix(Rotation).GetScaledAxis( EAxis::Y ); + const FVector Direction = FQuatRotationMatrix(Rotation).GetScaledAxis(EAxis::Y); AddMovementInput(Direction, Val); } } @@ -1055,12 +1035,12 @@ void AShooterCharacter::OnStopRunning() } bool AShooterCharacter::IsRunning() const -{ +{ if (!GetCharacterMovement()) { return false; } - + return (bWantsToRun || bWantsToRunToggled) && !GetVelocity().IsZero() && (GetVelocity().GetSafeNormal2D() | GetActorForwardVector()) > -0.1; } @@ -1072,42 +1052,81 @@ void AShooterCharacter::Tick(float DeltaSeconds) { SetRunning(false, false); } - else { - } AShooterPlayerController* MyPC = Cast(Controller); if (MyPC && MyPC->HasHealthRegen()) { if (this->Health < this->GetMaxHealth()) { - this->Health += 5 * DeltaSeconds; + this->Health += 5 * DeltaSeconds; if (Health > this->GetMaxHealth()) { Health = this->GetMaxHealth(); } } } - - if (LowHealthSound && GEngine->UseSound()) + + if (GEngine->UseSound()) { - if ((this->Health > 0 && this->Health < this->GetMaxHealth() * LowHealthPercentage) && (!LowHealthWarningPlayer || !LowHealthWarningPlayer->IsPlaying())) - { - LowHealthWarningPlayer = UGameplayStatics::SpawnSoundAttached(LowHealthSound, GetRootComponent(), - NAME_None, FVector(ForceInit), EAttachLocation::KeepRelativeOffset, true); - LowHealthWarningPlayer->SetVolumeMultiplier(0.0f); - } - else if ((this->Health > this->GetMaxHealth() * LowHealthPercentage || this->Health < 0) && LowHealthWarningPlayer && LowHealthWarningPlayer->IsPlaying()) + if (LowHealthSound) { - LowHealthWarningPlayer->Stop(); + if ((this->Health > 0 && this->Health < this->GetMaxHealth() * LowHealthPercentage) && (!LowHealthWarningPlayer || !LowHealthWarningPlayer->IsPlaying())) + { + LowHealthWarningPlayer = UGameplayStatics::SpawnSoundAttached(LowHealthSound, GetRootComponent(), + NAME_None, FVector(ForceInit), EAttachLocation::KeepRelativeOffset, true); + if (LowHealthWarningPlayer) + { + LowHealthWarningPlayer->SetVolumeMultiplier(0.0f); + } + } + else if ((this->Health > this->GetMaxHealth() * LowHealthPercentage || this->Health < 0) && LowHealthWarningPlayer && LowHealthWarningPlayer->IsPlaying()) + { + LowHealthWarningPlayer->Stop(); + } + if (LowHealthWarningPlayer && LowHealthWarningPlayer->IsPlaying()) + { + const float MinVolume = 0.3f; + const float VolumeMultiplier = (1.0f - (this->Health / (this->GetMaxHealth() * LowHealthPercentage))); + LowHealthWarningPlayer->SetVolumeMultiplier(MinVolume + (1.0f - MinVolume) * VolumeMultiplier); + } } - if (LowHealthWarningPlayer && LowHealthWarningPlayer->IsPlaying()) + + UpdateRunSounds(); + } + + const APlayerController* PC = Cast(GetController()); + const bool bLocallyControlled = (PC ? PC->IsLocalController() : false); + const uint32 UniqueID = GetUniqueID(); + FAudioThread::RunCommandOnAudioThread([UniqueID, bLocallyControlled]() + { + USoundNodeLocalPlayer::GetLocallyControlledActorCache().Add(UniqueID, bLocallyControlled); + }); + + TArray PointsToTest; + BuildPauseReplicationCheckPoints(PointsToTest); + + if (NetVisualizeRelevancyTestPoints == 1) + { + for (FVector PointToTest : PointsToTest) { - const float MinVolume = 0.3f; - const float VolumeMultiplier = (1.0f - (this->Health / (this->GetMaxHealth() * LowHealthPercentage))); - LowHealthWarningPlayer->SetVolumeMultiplier(MinVolume + (1.0f - MinVolume) * VolumeMultiplier); + DrawDebugSphere(GetWorld(), PointToTest, 10.0f, 8, FColor::Red); } } } +void AShooterCharacter::BeginDestroy() +{ + Super::BeginDestroy(); + + if (!GExitPurge) + { + const uint32 UniqueID = GetUniqueID(); + FAudioThread::RunCommandOnAudioThread([UniqueID]() + { + USoundNodeLocalPlayer::GetLocallyControlledActorCache().Remove(UniqueID); + }); + } +} + void AShooterCharacter::OnStartJump() { AShooterPlayerController* MyPC = Cast(Controller); @@ -1120,35 +1139,72 @@ void AShooterCharacter::OnStartJump() void AShooterCharacter::OnStopJump() { bPressedJump = false; + StopJumping(); } ////////////////////////////////////////////////////////////////////////// // Replication -void AShooterCharacter::PreReplication( IRepChangedPropertyTracker & ChangedPropertyTracker ) +void AShooterCharacter::PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) { - Super::PreReplication( ChangedPropertyTracker ); + Super::PreReplication(ChangedPropertyTracker); // Only replicate this property for a short duration after it changes so join in progress players don't get spammed with fx when joining late - DOREPLIFETIME_ACTIVE_OVERRIDE( AShooterCharacter, LastTakeHitInfo, GetWorld() && GetWorld()->GetTimeSeconds() < LastTakeHitTimeTimeout ); + DOREPLIFETIME_ACTIVE_OVERRIDE(AShooterCharacter, LastTakeHitInfo, GetWorld() && GetWorld()->GetTimeSeconds() < LastTakeHitTimeTimeout); } -void AShooterCharacter::GetLifetimeReplicatedProps( TArray< FLifetimeProperty > & OutLifetimeProps ) const +void AShooterCharacter::GetLifetimeReplicatedProps(TArray< FLifetimeProperty > & OutLifetimeProps) const { - Super::GetLifetimeReplicatedProps( OutLifetimeProps ); + Super::GetLifetimeReplicatedProps(OutLifetimeProps); // only to local owner: weapon change requests are locally instigated, other clients don't need it - DOREPLIFETIME_CONDITION( AShooterCharacter, Inventory, COND_OwnerOnly ); + DOREPLIFETIME_CONDITION(AShooterCharacter, Inventory, COND_OwnerOnly); // everyone except local owner: flag change is locally instigated - DOREPLIFETIME_CONDITION( AShooterCharacter, bIsTargeting, COND_SkipOwner ); - DOREPLIFETIME_CONDITION( AShooterCharacter, bWantsToRun, COND_SkipOwner ); + DOREPLIFETIME_CONDITION(AShooterCharacter, bIsTargeting, COND_SkipOwner); + DOREPLIFETIME_CONDITION(AShooterCharacter, bWantsToRun, COND_SkipOwner); - DOREPLIFETIME_CONDITION( AShooterCharacter, LastTakeHitInfo, COND_Custom ); + DOREPLIFETIME_CONDITION(AShooterCharacter, LastTakeHitInfo, COND_Custom); // everyone - DOREPLIFETIME( AShooterCharacter, CurrentWeapon ); - DOREPLIFETIME( AShooterCharacter, Health ); + DOREPLIFETIME(AShooterCharacter, CurrentWeapon); + DOREPLIFETIME(AShooterCharacter, Health); +} + +bool AShooterCharacter::IsReplicationPausedForConnection(const FNetViewer& ConnectionOwnerNetViewer) +{ + if (NetEnablePauseRelevancy == 1) + { + APlayerController* PC = Cast(ConnectionOwnerNetViewer.InViewer); + check(PC); + + FVector ViewLocation; + FRotator ViewRotation; + PC->GetPlayerViewPoint(ViewLocation, ViewRotation); + + FCollisionQueryParams CollisionParams(SCENE_QUERY_STAT(LineOfSight), true, PC->GetPawn()); + CollisionParams.AddIgnoredActor(this); + + TArray PointsToTest; + BuildPauseReplicationCheckPoints(PointsToTest); + + for (FVector PointToTest : PointsToTest) + { + if (!GetWorld()->LineTraceTestByChannel(PointToTest, ViewLocation, ECC_Visibility, CollisionParams)) + { + return false; + } + } + + return true; + } + + return false; +} + +void AShooterCharacter::OnReplicationPausedChanged(bool bIsReplicationPaused) +{ + GetMesh()->SetHiddenInGame(bIsReplicationPaused, true); } AShooterWeapon* AShooterCharacter::GetWeapon() const @@ -1171,9 +1227,9 @@ USkeletalMeshComponent* AShooterCharacter::GetPawnMesh() const return IsFirstPerson() ? Mesh1P : GetMesh(); } -USkeletalMeshComponent* AShooterCharacter::GetSpecifcPawnMesh( bool WantFirstPerson ) const +USkeletalMeshComponent* AShooterCharacter::GetSpecifcPawnMesh(bool WantFirstPerson) const { - return WantFirstPerson == true ? Mesh1P : GetMesh(); + return WantFirstPerson == true ? Mesh1P : GetMesh(); } FName AShooterCharacter::GetWeaponAttachPoint() const @@ -1227,4 +1283,21 @@ void AShooterCharacter::UpdateTeamColorsAllMIDs() { UpdateTeamColors(MeshMIDs[i]); } +} + +void AShooterCharacter::BuildPauseReplicationCheckPoints(TArray& RelevancyCheckPoints) +{ + FBoxSphereBounds Bounds = GetCapsuleComponent()->CalcBounds(GetCapsuleComponent()->GetComponentTransform()); + FBox BoundingBox = Bounds.GetBox(); + float XDiff = Bounds.BoxExtent.X * 2; + float YDiff = Bounds.BoxExtent.Y * 2; + + RelevancyCheckPoints.Add(BoundingBox.Min); + RelevancyCheckPoints.Add(FVector(BoundingBox.Min.X + XDiff, BoundingBox.Min.Y, BoundingBox.Min.Z)); + RelevancyCheckPoints.Add(FVector(BoundingBox.Min.X, BoundingBox.Min.Y + YDiff, BoundingBox.Min.Z)); + RelevancyCheckPoints.Add(FVector(BoundingBox.Min.X + XDiff, BoundingBox.Min.Y + YDiff, BoundingBox.Min.Z)); + RelevancyCheckPoints.Add(FVector(BoundingBox.Max.X - XDiff, BoundingBox.Max.Y, BoundingBox.Max.Z)); + RelevancyCheckPoints.Add(FVector(BoundingBox.Max.X, BoundingBox.Max.Y - YDiff, BoundingBox.Max.Z)); + RelevancyCheckPoints.Add(FVector(BoundingBox.Max.X - XDiff, BoundingBox.Max.Y - YDiff, BoundingBox.Max.Z)); + RelevancyCheckPoints.Add(BoundingBox.Max); } \ No newline at end of file diff --git a/Source/ShooterGame/Private/Player/ShooterCharacterMovement.cpp b/Source/ShooterGame/Private/Player/ShooterCharacterMovement.cpp index b804349..bcba178 100644 --- a/Source/ShooterGame/Private/Player/ShooterCharacterMovement.cpp +++ b/Source/ShooterGame/Private/Player/ShooterCharacterMovement.cpp @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ShooterGame.h" #include "Player/ShooterCharacterMovement.h" diff --git a/Source/ShooterGame/Private/Player/ShooterCheatManager.cpp b/Source/ShooterGame/Private/Player/ShooterCheatManager.cpp index 74252ce..fb3fe33 100644 --- a/Source/ShooterGame/Private/Player/ShooterCheatManager.cpp +++ b/Source/ShooterGame/Private/Player/ShooterCheatManager.cpp @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ShooterGame.h" #include "Player/ShooterCheatManager.h" @@ -29,7 +29,7 @@ void UShooterCheatManager::ToggleMatchTimer() { AShooterPlayerController* MyPC = GetOuterAShooterPlayerController(); - AShooterGameState* const MyGameState = Cast(MyPC->GetWorld()->GameState); + AShooterGameState* const MyGameState = MyPC->GetWorld()->GetGameState(); if (MyGameState && MyGameState->Role == ROLE_Authority) { MyGameState->bTimerPaused = !MyGameState->bTimerPaused; diff --git a/Source/ShooterGame/Private/Player/ShooterDemoSpectator.cpp b/Source/ShooterGame/Private/Player/ShooterDemoSpectator.cpp index f555d25..563c71b 100644 --- a/Source/ShooterGame/Private/Player/ShooterDemoSpectator.cpp +++ b/Source/ShooterGame/Private/Player/ShooterDemoSpectator.cpp @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ShooterGame.h" #include "Player/ShooterDemoSpectator.h" diff --git a/Source/ShooterGame/Private/Player/ShooterLocalPlayer.cpp b/Source/ShooterGame/Private/Player/ShooterLocalPlayer.cpp index 12a3703..0305cdd 100644 --- a/Source/ShooterGame/Private/Player/ShooterLocalPlayer.cpp +++ b/Source/ShooterGame/Private/Player/ShooterLocalPlayer.cpp @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ShooterGame.h" #include "Player/ShooterLocalPlayer.h" @@ -24,8 +24,15 @@ UShooterPersistentUser* UShooterLocalPlayer::GetPersistentUser() const void UShooterLocalPlayer::LoadPersistentUser() { + FString SaveGameName = GetNickname(); + +#if PLATFORM_SWITCH + // on Switch, the displayable nickname can change, so we can't use it as a save ID (explicitly stated in docs, so changing for pre-cert) + FPlatformMisc::GetUniqueStringNameForControllerId(GetControllerId(), SaveGameName); +#endif + // if we changed controllerid / user, then we need to load the appropriate persistent user. - if (PersistentUser != nullptr && ( GetControllerId() != PersistentUser->GetUserIndex() || GetNickname() != PersistentUser->GetName() ) ) + if (PersistentUser != nullptr && ( GetControllerId() != PersistentUser->GetUserIndex() || SaveGameName != PersistentUser->GetName() ) ) { PersistentUser->SaveIfDirty(); PersistentUser = nullptr; @@ -42,16 +49,23 @@ void UShooterLocalPlayer::LoadPersistentUser() PlatformId = Identity->GetPlatformUserIdFromUniqueNetId(*GetPreferredUniqueNetId()); } - PersistentUser = UShooterPersistentUser::LoadPersistentUser( GetNickname(), PlatformId ); + PersistentUser = UShooterPersistentUser::LoadPersistentUser(SaveGameName, PlatformId ); } } void UShooterLocalPlayer::SetControllerId(int32 NewControllerId) { + FString SaveGameName = GetNickname(); + +#if PLATFORM_SWITCH + // on Switch, the displayable nickname can change, so we can't use it as a save ID (explicitly stated in docs, so changing for pre-cert) + FPlatformMisc::GetUniqueStringNameForControllerId(GetControllerId(), SaveGameName); +#endif + ULocalPlayer::SetControllerId(NewControllerId); // if we changed controllerid / user, then we need to load the appropriate persistent user. - if (PersistentUser != nullptr && ( GetControllerId() != PersistentUser->GetUserIndex() || GetNickname() != PersistentUser->GetName() ) ) + if (PersistentUser != nullptr && ( GetControllerId() != PersistentUser->GetUserIndex() || SaveGameName != PersistentUser->GetName() ) ) { PersistentUser->SaveIfDirty(); PersistentUser = nullptr; diff --git a/Source/ShooterGame/Private/Player/ShooterPersistentUser.cpp b/Source/ShooterGame/Private/Player/ShooterPersistentUser.cpp index fac8ff9..86fa6b0 100644 --- a/Source/ShooterGame/Private/Player/ShooterPersistentUser.cpp +++ b/Source/ShooterGame/Private/Player/ShooterPersistentUser.cpp @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ShooterGame.h" #include "Player/ShooterPersistentUser.h" @@ -14,6 +14,7 @@ void UShooterPersistentUser::SetToDefaults() { bIsDirty = false; + bVibrationOpt = true; bInvertedYAxis = false; AimSensitivity = 1.0f; Gamma = 2.2f; @@ -110,14 +111,18 @@ UShooterPersistentUser* UShooterPersistentUser::LoadPersistentUser(FString SlotN // first set of player signins can happen before the UWorld exists, which means no OSS, which means no user names, which means no slotnames. // Persistent users aren't valid in this state. if (SlotName.Len() > 0) - { - Result = Cast(UGameplayStatics::LoadGameFromSlot(SlotName, UserIndex)); - if (Result == NULL) + { + if (!GIsBuildMachine) + { + Result = Cast(UGameplayStatics::LoadGameFromSlot(SlotName, UserIndex)); + } + + if (Result == nullptr) { // if failed to load, create a new one Result = Cast( UGameplayStatics::CreateSaveGameObject(UShooterPersistentUser::StaticClass()) ); } - check(Result != NULL); + check(Result != nullptr); Result->SlotName = SlotName; Result->UserIndex = UserIndex; @@ -202,6 +207,14 @@ int32 UShooterPersistentUser::GetUserIndex() const return UserIndex; } +void UShooterPersistentUser::SetVibration(bool bVibration) +{ + bIsDirty |= bVibrationOpt != bVibration; + + bVibrationOpt = bVibration; + +} + void UShooterPersistentUser::SetInvertedYAxis(bool bInvert) { bIsDirty |= bInvertedYAxis != bInvert; diff --git a/Source/ShooterGame/Private/Player/ShooterPlayerCameraManager.cpp b/Source/ShooterGame/Private/Player/ShooterPlayerCameraManager.cpp index 3f99143..2889867 100644 --- a/Source/ShooterGame/Private/Player/ShooterPlayerCameraManager.cpp +++ b/Source/ShooterGame/Private/Player/ShooterPlayerCameraManager.cpp @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ShooterGame.h" #include "Player/ShooterPlayerCameraManager.h" diff --git a/Source/ShooterGame/Private/Player/ShooterPlayerController.cpp b/Source/ShooterGame/Private/Player/ShooterPlayerController.cpp index d02a3db..4c9ec30 100644 --- a/Source/ShooterGame/Private/Player/ShooterPlayerController.cpp +++ b/Source/ShooterGame/Private/Player/ShooterPlayerController.cpp @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ShooterGame.h" #include "Player/ShooterPlayerController.h" @@ -13,12 +13,14 @@ #include "Online.h" #include "OnlineAchievementsInterface.h" #include "OnlineEventsInterface.h" +#include "OnlineStatsInterface.h" #include "OnlineIdentityInterface.h" #include "OnlineSessionInterface.h" #include "ShooterGameInstance.h" #include "ShooterLeaderboards.h" #include "ShooterGameViewportClient.h" - +#include "Sound/SoundNodeLocalPlayer.h" +#include "AudioThread.h" #define ACH_FRAG_SOMEONE TEXT("ACH_FRAG_SOMEONE") #define ACH_SOME_KILLS TEXT("ACH_SOME_KILLS") @@ -56,6 +58,11 @@ AShooterPlayerController::AShooterPlayerController(const FObjectInitializer& Obj ServerSayString = TEXT("Say"); ShooterFriendUpdateTimer = 0.0f; bHasSentStartEvents = false; + + StatMatchesPlayed = 0; + StatKills = 0; + StatDeaths = 0; + bHasFetchedPlatformData = false; } void AShooterPlayerController::SetupInputComponent() @@ -84,9 +91,17 @@ void AShooterPlayerController::PostInitializeComponents() ShooterFriendUpdateTimer = 0; } -void AShooterPlayerController::BeginPlay() +void AShooterPlayerController::ClearLeaderboardDelegate() { - Super::BeginPlay(); + IOnlineSubsystem* OnlineSub = IOnlineSubsystem::Get(); + if (OnlineSub) + { + IOnlineLeaderboardsPtr Leaderboards = OnlineSub->GetLeaderboardsInterface(); + if (Leaderboards.IsValid()) + { + Leaderboards->ClearOnLeaderboardReadCompleteDelegate_Handle(LeaderboardReadCompleteDelegateHandle); + } + } } void AShooterPlayerController::TickActor(float DeltaTime, enum ELevelTick TickType, FActorTickFunction& ThisTickFunction) @@ -107,7 +122,11 @@ void AShooterPlayerController::TickActor(float DeltaTime, enum ELevelTick TickTy { ShooterFriends->UpdateFriends(LocalPlayer->GetControllerId()); } - ShooterFriendUpdateTimer = 4; //make sure the time between calls is long enough that we won't trigger (0x80552C81) and not exceed the web api rate limit + + // Make sure the time between calls is long enough that we won't trigger (0x80552C81) and not exceed the web api rate limit + // That value is currently 75 requests / 15 minutes. + ShooterFriendUpdateTimer = 15; + } } @@ -128,18 +147,43 @@ void AShooterPlayerController::TickActor(float DeltaTime, enum ELevelTick TickTy } } } + + const bool bLocallyControlled = IsLocalController(); + const uint32 UniqueID = GetUniqueID(); + FAudioThread::RunCommandOnAudioThread([UniqueID, bLocallyControlled]() + { + USoundNodeLocalPlayer::GetLocallyControlledActorCache().Add(UniqueID, bLocallyControlled); + }); }; +void AShooterPlayerController::BeginDestroy() +{ + Super::BeginDestroy(); + ClearLeaderboardDelegate(); + + if (!GExitPurge) + { + const uint32 UniqueID = GetUniqueID(); + FAudioThread::RunCommandOnAudioThread([UniqueID]() + { + USoundNodeLocalPlayer::GetLocallyControlledActorCache().Remove(UniqueID); + }); + } +} + void AShooterPlayerController::SetPlayer( UPlayer* InPlayer ) { Super::SetPlayer( InPlayer ); - //Build menu only after game is initialized - ShooterIngameMenu = MakeShareable(new FShooterIngameMenu()); - ShooterIngameMenu->Construct(Cast(Player)); + if (ULocalPlayer* const LocalPlayer = Cast(Player)) + { + //Build menu only after game is initialized + ShooterIngameMenu = MakeShareable(new FShooterIngameMenu()); + ShooterIngameMenu->Construct(Cast(Player)); - FInputModeGameOnly InputMode; - SetInputMode(InputMode); + FInputModeGameOnly InputMode; + SetInputMode(InputMode); + } } void AShooterPlayerController::QueryAchievements() @@ -191,6 +235,77 @@ void AShooterPlayerController::OnQueryAchievementsComplete(const FUniqueNetId& P UE_LOG(LogOnline, Display, TEXT("AShooterPlayerController::OnQueryAchievementsComplete(bWasSuccessful = %s)"), bWasSuccessful ? TEXT("TRUE") : TEXT("FALSE")); } +void AShooterPlayerController::OnLeaderboardReadComplete(bool bWasSuccessful) +{ + if (ReadObject.IsValid() && ReadObject->ReadState == EOnlineAsyncTaskState::Done && !bHasFetchedPlatformData) + { + bHasFetchedPlatformData = true; + ClearLeaderboardDelegate(); + + // We should only have one stat. + if (bWasSuccessful && ReadObject->Rows.Num() == 1) + { + FOnlineStatsRow& RowData = ReadObject->Rows[0]; + if (const FVariantData* KillData = RowData.Columns.Find(LEADERBOARD_STAT_KILLS)) + { + KillData->GetValue(StatKills); + } + + if (const FVariantData* DeathData = RowData.Columns.Find(LEADERBOARD_STAT_DEATHS)) + { + DeathData->GetValue(StatDeaths); + } + + if (const FVariantData* MatchData = RowData.Columns.Find(LEADERBOARD_STAT_MATCHESPLAYED)) + { + MatchData->GetValue(StatMatchesPlayed); + } + + UE_LOG(LogOnline, Log, TEXT("Fetched player stat data. Kills %d Deaths %d Matches %d"), StatKills, StatDeaths, StatMatchesPlayed); + } + } +} + +void AShooterPlayerController::QueryStats() +{ + ULocalPlayer* LocalPlayer = Cast(Player); + if (LocalPlayer && LocalPlayer->GetControllerId() != -1) + { + IOnlineSubsystem* OnlineSub = IOnlineSubsystem::Get(); + if (OnlineSub) + { + IOnlineIdentityPtr Identity = OnlineSub->GetIdentityInterface(); + if (Identity.IsValid()) + { + TSharedPtr UserId = Identity->GetUniquePlayerId(LocalPlayer->GetControllerId()); + + if (UserId.IsValid()) + { + IOnlineLeaderboardsPtr Leaderboards = OnlineSub->GetLeaderboardsInterface(); + if (Leaderboards.IsValid() && !bHasFetchedPlatformData) + { + TArray> QueryPlayers; + QueryPlayers.Add(UserId.ToSharedRef()); + + LeaderboardReadCompleteDelegateHandle = Leaderboards->OnLeaderboardReadCompleteDelegates.AddUObject(this, &AShooterPlayerController::OnLeaderboardReadComplete); + ReadObject = MakeShareable(new FShooterAllTimeMatchResultsRead()); + FOnlineLeaderboardReadRef ReadObjectRef = ReadObject.ToSharedRef(); + if (Leaderboards->ReadLeaderboards(QueryPlayers, ReadObjectRef)) + { + UE_LOG(LogOnline, Log, TEXT("Started process to fetch stats for current user.")); + } + else + { + UE_LOG(LogOnline, Warning, TEXT("Could not start leaderboard fetch process. This will affect stat writes for this session.")); + } + + } + } + } + } + } +} + void AShooterPlayerController::UnFreeze() { ServerRestartPlayer(); @@ -219,10 +334,6 @@ void AShooterPlayerController::PawnPendingDestroy(APawn* P) void AShooterPlayerController::GameHasEnded(class AActor* EndGameFocus, bool bIsWinner) { - UpdateSaveFileOnGameEnd(bIsWinner); - UpdateAchievementsOnGameEnd(); - UpdateLeaderboardsOnGameEnd(); - Super::GameHasEnded(EndGameFocus, bIsWinner); } @@ -240,7 +351,7 @@ bool AShooterPlayerController::FindDeathCameraSpot(FVector& CameraLocation, FRot const float YawOffsets[] = { 0.0f, -180.0f, 90.0f, -90.0f, 45.0f, -45.0f, 135.0f, -135.0f }; const float CameraOffset = 600.0f; - FCollisionQueryParams TraceParams(TEXT("DeathCamera"), true, GetPawn()); + FCollisionQueryParams TraceParams(SCENE_QUERY_STAT(DeathCamera), true, GetPawn()); FHitResult HitResult; for (int32 i = 0; i < ARRAY_COUNT(YawOffsets); i++) @@ -298,10 +409,10 @@ void AShooterPlayerController::OnKill() TSharedPtr UniqueID = Identity->GetUniquePlayerId(UserIndex); if (UniqueID.IsValid()) { - AShooterCharacter* Pawn = Cast(GetCharacter()); + AShooterCharacter* ShooterChar = Cast(GetCharacter()); // If player is dead, use location stored during pawn cleanup. - FVector Location = Pawn ? Pawn->GetActorLocation() : LastDeathLocation; - AShooterWeapon* Weapon = Pawn ? Pawn->GetWeapon() : 0; + FVector Location = ShooterChar ? ShooterChar->GetActorLocation() : LastDeathLocation; + AShooterWeapon* Weapon = ShooterChar ? ShooterChar->GetWeapon() : 0; int32 WeaponType = Weapon ? (int32)Weapon->GetAmmoType() : 0; FOnlineEventParms Params; @@ -348,10 +459,10 @@ void AShooterPlayerController::OnDeathMessage(class AShooterPlayerState* KillerP TSharedPtr UniqueID = Identity->GetUniquePlayerId(UserIndex); if (UniqueID.IsValid()) { - AShooterCharacter* Pawn = Cast(GetCharacter()); - AShooterWeapon* Weapon = Pawn ? Pawn->GetWeapon() : NULL; + AShooterCharacter* ShooterChar = Cast(GetCharacter()); + AShooterWeapon* Weapon = ShooterChar ? ShooterChar->GetWeapon() : NULL; - FVector Location = Pawn ? Pawn->GetActorLocation() : FVector::ZeroVector; + FVector Location = ShooterChar ? ShooterChar->GetActorLocation() : FVector::ZeroVector; int32 WeaponType = Weapon ? (int32)Weapon->GetAmmoType() : 0; FOnlineEventParms Params; @@ -386,7 +497,7 @@ void AShooterPlayerController::UpdateAchievementProgress( const FString& Id, flo IOnlineIdentityPtr Identity = OnlineSub->GetIdentityInterface(); if (Identity.IsValid()) { - TSharedPtr UserId = LocalPlayer->GetCachedUniqueNetId(); + FUniqueNetIdRepl UserId = LocalPlayer->GetCachedUniqueNetId(); if (UserId.IsValid()) { @@ -428,6 +539,11 @@ void AShooterPlayerController::UpdateAchievementProgress( const FString& Id, flo void AShooterPlayerController::OnToggleInGameMenu() { + if( GEngine->GameViewport == nullptr ) + { + return; + } + // this is not ideal, but necessary to prevent both players from pausing at the same time on the same frame UWorld* GameWorld = GEngine->GameViewport->GetWorld(); @@ -515,6 +631,11 @@ void AShooterPlayerController::SetGodMode(bool bEnable) bGodMode = bEnable; } +void AShooterPlayerController::SetIsVibrationEnabled(bool bEnable) +{ + bIsVibrationEnabled = bEnable; +} + void AShooterPlayerController::ClientGameStarted_Implementation() { bAllowGameActions = true; @@ -532,6 +653,8 @@ void AShooterPlayerController::ClientGameStarted_Implementation() QueryAchievements(); + QueryStats(); + // Send round start event const auto Events = Online::GetEventsInterface(); ULocalPlayer* LocalPlayer = Cast(Player); @@ -558,7 +681,7 @@ void AShooterPlayerController::ClientGameStarted_Implementation() // Online matches require the MultiplayerRoundStart event as well UShooterGameInstance* SGI = GetWorld() != NULL ? Cast(GetWorld()->GetGameInstance()) : NULL; - if (SGI->GetIsOnline()) + if (SGI && (SGI->GetOnlineMode() == EOnlineMode::Online)) { FOnlineEventParms MultiplayerParams; @@ -589,7 +712,7 @@ void AShooterPlayerController::ClientStartOnlineGame_Implementation() if (OnlineSub) { IOnlineSessionPtr Sessions = OnlineSub->GetSessionInterface(); - if (Sessions.IsValid()) + if (Sessions.IsValid() && (Sessions->GetNamedSession(ShooterPlayerState->SessionName) != nullptr)) { UE_LOG(LogOnline, Log, TEXT("Starting session %s on client"), *ShooterPlayerState->SessionName.ToString() ); Sessions->StartSession(ShooterPlayerState->SessionName); @@ -616,7 +739,7 @@ void AShooterPlayerController::ClientEndOnlineGame_Implementation() if (OnlineSub) { IOnlineSessionPtr Sessions = OnlineSub->GetSessionInterface(); - if (Sessions.IsValid()) + if (Sessions.IsValid() && (Sessions->GetNamedSession(ShooterPlayerState->SessionName) != nullptr)) { UE_LOG(LogOnline, Log, TEXT("Ending session %s on client"), *ShooterPlayerState->SessionName.ToString() ); Sessions->EndSession(ShooterPlayerState->SessionName); @@ -688,6 +811,7 @@ void AShooterPlayerController::ClientGameEnded_Implementation(class AActor* EndG UpdateSaveFileOnGameEnd(bIsWinner); UpdateAchievementsOnGameEnd(); UpdateLeaderboardsOnGameEnd(); + UpdateStatsOnGameEnd(bIsWinner); // Flag that the game has just ended (if it's ended due to host loss we want to wait for ClientReturnToMainMenu_Implementation first, incase we don't want to process) bGameEndedFrame = true; @@ -722,7 +846,7 @@ void AShooterPlayerController::ClientSendRoundEndEvent_Implementation(bool bIsWi // Online matches require the MultiplayerRoundEnd event as well UShooterGameInstance* SGI = GetWorld() != NULL ? Cast(GetWorld()->GetGameInstance()) : NULL; - if (SGI->GetIsOnline()) + if (SGI && (SGI->GetOnlineMode() == EOnlineMode::Online)) { FOnlineEventParms MultiplayerParams; @@ -853,6 +977,11 @@ bool AShooterPlayerController::HasGodMode() const return bGodMode; } +bool AShooterPlayerController::IsVibrationEnabled() const +{ + return bIsVibrationEnabled; +} + bool AShooterPlayerController::IsGameInputAllowed() const { return bAllowGameActions && !bCinematicMode; @@ -894,7 +1023,7 @@ bool AShooterPlayerController::ServerSay_Validate( const FString& Msg ) void AShooterPlayerController::ServerSay_Implementation( const FString& Msg ) { - GetWorld()->GetAuthGameMode()->Broadcast(this, Msg, ServerSayString); + GetWorld()->GetAuthGameMode()->Broadcast(this, Msg, ServerSayString); } AShooterHUD* AShooterPlayerController::GetShooterHUD() const @@ -917,22 +1046,7 @@ bool AShooterPlayerController::SetPause(bool bPause, FCanUnpause CanUnpauseDeleg const auto PresenceInterface = Online::GetPresenceInterface(); const auto Events = Online::GetEventsInterface(); const auto LocalPlayer = Cast(Player); - TSharedPtr UserId = LocalPlayer ? LocalPlayer->GetCachedUniqueNetId() : nullptr; - - if(PresenceInterface.IsValid() && UserId.IsValid()) - { - FOnlineUserPresenceStatus PresenceStatus; - if(Result && bPause) - { - PresenceStatus.Properties.Add(DefaultPresenceKey, FString("Paused")); - } - else - { - PresenceStatus.Properties.Add(DefaultPresenceKey, FString("InGame")); - } - PresenceInterface->SetPresence(*UserId, PresenceStatus); - - } + FUniqueNetIdRepl UserId = LocalPlayer ? LocalPlayer->GetCachedUniqueNetId() : FUniqueNetIdRepl(); // Don't send pause events while online since the game doesn't actually pause if(GetNetMode() == NM_Standalone && Events.IsValid() && PlayerState->UniqueId.IsValid()) @@ -953,6 +1067,20 @@ bool AShooterPlayerController::SetPause(bool bPause, FCanUnpause CanUnpauseDeleg return Result; } +FVector AShooterPlayerController::GetFocalLocation() const +{ + const AShooterCharacter* ShooterCharacter = Cast(GetPawn()); + + // On death we want to use the player's death cam location rather than the location of where the pawn is at the moment + // This guarantees that the clients see their death cam correctly, as their pawns have delayed destruction. + if (ShooterCharacter && ShooterCharacter->bIsDying) + { + return GetSpawnLocation(); + } + + return Super::GetFocalLocation(); +} + void AShooterPlayerController::ShowInGameMenu() { AShooterHUD* ShooterHUD = GetShooterHUD(); @@ -1141,7 +1269,7 @@ void AShooterPlayerController::UpdateAchievementsOnGameEnd() void AShooterPlayerController::UpdateLeaderboardsOnGameEnd() { - ULocalPlayer* LocalPlayer = Cast(Player); + UShooterLocalPlayer* LocalPlayer = Cast(Player); if (LocalPlayer) { // update leaderboards - note this does not respect existing scores and overwrites them. We would first need to read the leaderboards if we wanted to do that. @@ -1161,11 +1289,20 @@ void AShooterPlayerController::UpdateLeaderboardsOnGameEnd() if (ShooterPlayerState) { FShooterAllTimeMatchResultsWrite ResultsWriteObject; + int32 MatchWriteData = 1; + int32 KillsWriteData = ShooterPlayerState->GetKills(); + int32 DeathsWriteData = ShooterPlayerState->GetDeaths(); - ResultsWriteObject.SetIntStat(LEADERBOARD_STAT_SCORE, ShooterPlayerState->GetKills()); - ResultsWriteObject.SetIntStat(LEADERBOARD_STAT_KILLS, ShooterPlayerState->GetKills()); - ResultsWriteObject.SetIntStat(LEADERBOARD_STAT_DEATHS, ShooterPlayerState->GetDeaths()); - ResultsWriteObject.SetIntStat(LEADERBOARD_STAT_MATCHESPLAYED, 1); +#if !PLATFORM_XBOXONE + StatMatchesPlayed = (MatchWriteData += StatMatchesPlayed); + StatKills = (KillsWriteData += StatKills); + StatDeaths = (DeathsWriteData += StatDeaths); +#endif + + ResultsWriteObject.SetIntStat(LEADERBOARD_STAT_SCORE, KillsWriteData); + ResultsWriteObject.SetIntStat(LEADERBOARD_STAT_KILLS, KillsWriteData); + ResultsWriteObject.SetIntStat(LEADERBOARD_STAT_DEATHS, DeathsWriteData); + ResultsWriteObject.SetIntStat(LEADERBOARD_STAT_MATCHESPLAYED, MatchWriteData); // the call will copy the user id and write object to its own memory Leaderboards->WriteLeaderboards(ShooterPlayerState->SessionName, *UserId, ResultsWriteObject); @@ -1178,6 +1315,35 @@ void AShooterPlayerController::UpdateLeaderboardsOnGameEnd() } } +void AShooterPlayerController::UpdateStatsOnGameEnd(bool bIsWinner) +{ + const auto Stats = Online::GetStatsInterface(); + ULocalPlayer* LocalPlayer = Cast(Player); + AShooterPlayerState* ShooterPlayerState = Cast(PlayerState); + + if (Stats.IsValid() && LocalPlayer != nullptr && ShooterPlayerState != nullptr) + { + auto UniqueId = LocalPlayer->GetCachedUniqueNetId(); + + if (UniqueId.IsValid() ) + { + TArray UpdatedUserStats; + + FOnlineStatsUserUpdatedStats& UpdatedStats = UpdatedUserStats.Emplace_GetRef( UniqueId.GetUniqueNetId().ToSharedRef() ); + UpdatedStats.Stats.Add( TEXT("Kills"), FOnlineStatUpdate( ShooterPlayerState->GetKills(), FOnlineStatUpdate::EOnlineStatModificationType::Sum ) ); + UpdatedStats.Stats.Add( TEXT("Deaths"), FOnlineStatUpdate( ShooterPlayerState->GetDeaths(), FOnlineStatUpdate::EOnlineStatModificationType::Sum ) ); + UpdatedStats.Stats.Add( TEXT("RoundsPlayed"), FOnlineStatUpdate( 1, FOnlineStatUpdate::EOnlineStatModificationType::Sum ) ); + if (bIsWinner) + { + UpdatedStats.Stats.Add( TEXT("RoundsWon"), FOnlineStatUpdate( 1, FOnlineStatUpdate::EOnlineStatModificationType::Sum ) ); + } + + Stats->UpdateStats( UniqueId.GetUniqueNetId().ToSharedRef(), UpdatedUserStats, FOnlineStatsUpdateStatsComplete() ); + } + } +} + + void AShooterPlayerController::UpdateSaveFileOnGameEnd(bool bIsWinner) { AShooterPlayerState* ShooterPlayerState = Cast(PlayerState); diff --git a/Source/ShooterGame/Private/Player/ShooterPlayerController_Menu.cpp b/Source/ShooterGame/Private/Player/ShooterPlayerController_Menu.cpp index 2fa30ca..075f1e2 100644 --- a/Source/ShooterGame/Private/Player/ShooterPlayerController_Menu.cpp +++ b/Source/ShooterGame/Private/Player/ShooterPlayerController_Menu.cpp @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ShooterGame.h" #include "Player/ShooterPlayerController_Menu.h" diff --git a/Source/ShooterGame/Private/Player/ShooterSpectatorPawn.cpp b/Source/ShooterGame/Private/Player/ShooterSpectatorPawn.cpp index e1764fb..02eb922 100644 --- a/Source/ShooterGame/Private/Player/ShooterSpectatorPawn.cpp +++ b/Source/ShooterGame/Private/Player/ShooterSpectatorPawn.cpp @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ShooterGame.h" @@ -6,20 +6,21 @@ AShooterSpectatorPawn::AShooterSpectatorPawn(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) -{ +{ + bReplicates = false; } -void AShooterSpectatorPawn::SetupPlayerInputComponent(UInputComponent* InputComponent) +void AShooterSpectatorPawn::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) { - check(InputComponent); + check(PlayerInputComponent); - InputComponent->BindAxis("MoveForward", this, &ADefaultPawn::MoveForward); - InputComponent->BindAxis("MoveRight", this, &ADefaultPawn::MoveRight); - InputComponent->BindAxis("MoveUp", this, &ADefaultPawn::MoveUp_World); - InputComponent->BindAxis("Turn", this, &ADefaultPawn::AddControllerYawInput); - InputComponent->BindAxis("TurnRate", this, &ADefaultPawn::TurnAtRate); - InputComponent->BindAxis("LookUp", this, &ADefaultPawn::AddControllerPitchInput); - InputComponent->BindAxis("LookUpRate", this, &AShooterSpectatorPawn::LookUpAtRate); + PlayerInputComponent->BindAxis("MoveForward", this, &ADefaultPawn::MoveForward); + PlayerInputComponent->BindAxis("MoveRight", this, &ADefaultPawn::MoveRight); + PlayerInputComponent->BindAxis("MoveUp", this, &ADefaultPawn::MoveUp_World); + PlayerInputComponent->BindAxis("Turn", this, &ADefaultPawn::AddControllerYawInput); + PlayerInputComponent->BindAxis("TurnRate", this, &ADefaultPawn::TurnAtRate); + PlayerInputComponent->BindAxis("LookUp", this, &ADefaultPawn::AddControllerPitchInput); + PlayerInputComponent->BindAxis("LookUpRate", this, &AShooterSpectatorPawn::LookUpAtRate); } void AShooterSpectatorPawn::LookUpAtRate(float Val) diff --git a/Source/ShooterGame/Private/ShooterEngine.cpp b/Source/ShooterGame/Private/ShooterEngine.cpp index af24314..ad70910 100644 --- a/Source/ShooterGame/Private/ShooterEngine.cpp +++ b/Source/ShooterGame/Private/ShooterEngine.cpp @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. /*============================================================================= ShooterEngine.cpp: ShooterEngine c++ code. diff --git a/Source/ShooterGame/Private/ShooterGameDelegates.cpp b/Source/ShooterGame/Private/ShooterGameDelegates.cpp index c08c056..2f05aa3 100644 --- a/Source/ShooterGame/Private/ShooterGameDelegates.cpp +++ b/Source/ShooterGame/Private/ShooterGameDelegates.cpp @@ -1,9 +1,34 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ShooterGame.h" #include "Online/ShooterPlayerState.h" #include "GameDelegates.h" +#include "IPlatformFilePak.h" +#include "UObject/PackageReload.h" + +//#include "Runtime/RHI/Public/RHICommandlist.h" + +// Global struct for registering delegates super early +struct FShooterGameGlobalDelegateInit +{ + FShooterGameGlobalDelegateInit() + { + FPakPlatformFile::GetPakChunkSignatureCheckFailedHandler().AddStatic(FShooterGameGlobalDelegateInit::HandlePakChunkSignatureCheckFailed); + FPakPlatformFile::GetPakMasterSignatureTableCheckFailureHandler().AddStatic(FShooterGameGlobalDelegateInit::HandlePakMasterSignatureTableCheckFailure); + } + + static void HandlePakChunkSignatureCheckFailed(const FPakChunkSignatureCheckFailedData& Data) + { + UE_LOG(LogShooter, Fatal, TEXT("Pak chunk signature check failed!")); + } + + static void HandlePakMasterSignatureTableCheckFailure(const FString& InPakFilename) + { + UE_LOG(LogShooter, Fatal, TEXT("Pak master signature table check failed for pak '%s'"), *InPakFilename); + } +} +GShooterGameGlobalDelegateInit; #if !UE_BUILD_SHIPPING @@ -48,7 +73,7 @@ static void WebServerDelegate(int32 UserIndex, const FString& Action, const FStr if (Player) { // get the shoter game - AShooterGameState* const GameState = Cast(Player->PlayerController->GetWorld()->GameState); + AShooterGameState* const GameState = Player->PlayerController->GetWorld()->GetGameState(); RankedPlayerMap Players; @@ -75,32 +100,6 @@ static void WebServerDelegate(int32 UserIndex, const FString& Action, const FStr } } -static void AssignStreamingChunk(const FString& PackageToAdd, const FString& LastLoadedMapName, const TArray& AssetRegistryChunkIDs, const TArray& ExistingChunkIds, TArray& OutChunkIndex) -{ - const int32 BasePak = 0; - const int32 SanctuaryPak = 1; - const int32 HighrisePak = 2; - - // Add assets to map paks unless they're engine packages or have already been added to the base (engine) pak. - if (!PackageToAdd.StartsWith("/Engine/")) - { - if ((LastLoadedMapName.Find(TEXT("Sanctuary")) >= 0 || PackageToAdd.Find(TEXT("Sanctuary")) >= 0) && - !ExistingChunkIds.Contains(BasePak)) - { - OutChunkIndex.Add(SanctuaryPak); - } - else if ((LastLoadedMapName.Find(TEXT("Highrise")) >= 0 || PackageToAdd.Find(TEXT("Highrise")) >= 0) && - !ExistingChunkIds.Contains(BasePak)) - { - OutChunkIndex.Add(HighrisePak); - } - } - if (OutChunkIndex.Num() == 0) - { - OutChunkIndex.Add(BasePak); - } -} - static void AssignLayerChunkDelegate(const FAssignLayerChunkMap* ChunkManifest, const FString& Platform, const int32 ChunkIndex, int32& OutChunkLayer) { OutChunkLayer = 0; @@ -145,10 +144,85 @@ static void ExtendedSaveGameInfoDelegate(const TCHAR* SaveName, const EGameDeleg } } +static void ReloadHandler( EPackageReloadPhase ReloadPhase, FPackageReloadedEvent* Event) +{ + if ( ReloadPhase == EPackageReloadPhase::PostPackageFixup) + { + // reinitialize allthe material instances + + + /*{ + // fixup uniform expressions + UMaterialInterface::RecacheAllMaterialUniformExpressions(); + }*/ + + /*for (TObjectIterator It; It; ++It) + { + UMaterialInstance* Material = *It; + //Material->InitResources(); + Material->RebuildResource(); + }*/ + } +} + +#define EXPERIMENTAL_ENABLEHOTRELOAD 0 +static void ReloadPackagesCallback( const TArray& PackageNames) +{ +#if EXPERIMENTAL_ENABLEHOTRELOAD + TArray PackagesToReload; + TArray MaterialPackagesToReload; + for (const FString& PackageName : PackageNames) + { + UPackage* Package = FindPackage(nullptr, *PackageName); + + if (Package == nullptr) + { + // UE_LOG(, Log, TEXT("Unable to find package in memory %s"), *PackageName); + } + else + { + if ( Package->HasAnyPackageFlags(PKG_ContainsMap || PKG_ContainsMap) ) + { + continue; + } + PackagesToReload.Add(Package); + } + } + + + // see what's in these packages + + if (PackagesToReload.Num()) + { + SortPackagesForReload(PackagesToReload); + + TArray PackagesToReloadData; + PackagesToReloadData.Empty(PackagesToReload.Num()); + for (UPackage* PackageToReload : PackagesToReload) + { + PackagesToReloadData.Emplace(PackageToReload, LOAD_None); + } + + TArray ReloadedPackages; + + FDelegateHandle Handle = FCoreUObjectDelegates::OnPackageReloaded.AddStatic(&ReloadHandler); + + FText ErrorMessage; + GShouldVerifyGCAssumptions = false; + GUObjectArray.DisableDisregardForGC(); + + ::ReloadPackages(PackagesToReloadData, ReloadedPackages, 500); + + FCoreUObjectDelegates::OnPackageReloaded.Remove(Handle); + } +#endif +} + void InitializeShooterGameDelegates() { FGameDelegates::Get().GetWebServerActionDelegate() = FWebServerActionDelegate::CreateStatic(WebServerDelegate); - FGameDelegates::Get().GetAssignStreamingChunkDelegate() = FAssignStreamingChunkDelegate::CreateStatic(AssignStreamingChunk); FGameDelegates::Get().GetAssignLayerChunkDelegate() = FAssignLayerChunkDelegate::CreateStatic(AssignLayerChunkDelegate); FGameDelegates::Get().GetExtendedSaveGameInfoDelegate() = FExtendedSaveGameInfoDelegate::CreateStatic(ExtendedSaveGameInfoDelegate); + + FCoreUObjectDelegates::NetworkFileRequestPackageReload.BindStatic(&ReloadPackagesCallback); } diff --git a/Source/ShooterGame/Private/ShooterGameDelegates.h b/Source/ShooterGame/Private/ShooterGameDelegates.h index 2c01c76..df1a0ca 100644 --- a/Source/ShooterGame/Private/ShooterGameDelegates.h +++ b/Source/ShooterGame/Private/ShooterGameDelegates.h @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once /** diff --git a/Source/ShooterGame/Private/ShooterGameInstance.cpp b/Source/ShooterGame/Private/ShooterGameInstance.cpp index 75bd3e5..93e821d 100644 --- a/Source/ShooterGame/Private/ShooterGameInstance.cpp +++ b/Source/ShooterGame/Private/ShooterGameInstance.cpp @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. /*============================================================================= ShooterGameInstance.cpp @@ -17,7 +17,9 @@ #include "Player/ShooterPlayerController_Menu.h" #include "Online/ShooterPlayerState.h" #include "Online/ShooterGameSession.h" +#include "Online/ShooterOnlineSessionClient.h" +FAutoConsoleVariable CVarShooterGameTestEncryption(TEXT("ShooterGame.TestEncryption"), 0, TEXT("If true, clients will send an encryption token with their request to join the server and attempt to encrypt the connection using a debug key. This is NOT SECURE and for demonstration purposes only.")); void SShooterWaitDialog::Construct(const FArguments& InArgs) { @@ -81,7 +83,7 @@ namespace ShooterGameInstanceState UShooterGameInstance::UShooterGameInstance(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) - , bIsOnline(true) // Default to online + , OnlineMode(EOnlineMode::Online) // Default to online , bIsLicensed(true) // Default to licensed (should have been checked by OS on boot) { CurrentState = ShooterGameInstanceState::None; @@ -94,14 +96,16 @@ void UShooterGameInstance::Init() IgnorePairingChangeForControllerId = -1; CurrentConnectionStatus = EOnlineServerConnectionStatus::Connected; + LocalPlayerOnlineStatus.InsertDefaulted(0, MAX_LOCAL_PLAYERS); + // game requires the ability to ID users. const auto OnlineSub = IOnlineSubsystem::Get(); check(OnlineSub); const auto IdentityInterface = OnlineSub->GetIdentityInterface(); check(IdentityInterface.IsValid()); - const auto SessionInterface = OnlineSub->GetSessionInterface(); - check(SessionInterface.IsValid()); + const auto SessionInterface = OnlineSub->GetSessionInterface(); + check(SessionInterface.IsValid()); // bind any OSS delegates we needs to handle for (int i = 0; i < MAX_LOCAL_PLAYERS; ++i) @@ -121,7 +125,7 @@ void UShooterGameInstance::Init() FCoreDelegates::ApplicationLicenseChange.AddUObject(this, &UShooterGameInstance::HandleAppLicenseUpdate); FCoreUObjectDelegates::PreLoadMap.AddUObject(this, &UShooterGameInstance::OnPreLoadMap); - FCoreUObjectDelegates::PostLoadMap.AddUObject(this, &UShooterGameInstance::OnPostLoadMap); + FCoreUObjectDelegates::PostLoadMapWithWorld.AddUObject(this, &UShooterGameInstance::OnPostLoadMap); FCoreUObjectDelegates::PostDemoPlay.AddUObject(this, &UShooterGameInstance::OnPostDemoPlay); @@ -129,13 +133,24 @@ void UShooterGameInstance::Init() OnlineSub->AddOnConnectionStatusChangedDelegate_Handle( FOnConnectionStatusChangedDelegate::CreateUObject( this, &UShooterGameInstance::HandleNetworkConnectionStatusChanged ) ); - SessionInterface->AddOnSessionFailureDelegate_Handle( FOnSessionFailureDelegate::CreateUObject( this, &UShooterGameInstance::HandleSessionFailure ) ); + if (SessionInterface.IsValid()) + { + SessionInterface->AddOnSessionFailureDelegate_Handle( FOnSessionFailureDelegate::CreateUObject( this, &UShooterGameInstance::HandleSessionFailure ) ); + } OnEndSessionCompleteDelegate = FOnEndSessionCompleteDelegate::CreateUObject(this, &UShooterGameInstance::OnEndSessionComplete); // Register delegate for ticker callback TickDelegate = FTickerDelegate::CreateUObject(this, &UShooterGameInstance::Tick); TickDelegateHandle = FTicker::GetCoreTicker().AddTicker(TickDelegate); + + // Initialize the debug key with a set value for AES256. This is not secure and for example purposes only. + DebugTestEncryptionKey.SetNum(32); + + for (int32 i = 0; i < DebugTestEncryptionKey.Num(); ++i) + { + DebugTestEncryptionKey[i] = uint8(i); + } } void UShooterGameInstance::Shutdown() @@ -146,52 +161,18 @@ void UShooterGameInstance::Shutdown() FTicker::GetCoreTicker().RemoveTicker(TickDelegateHandle); } -void UShooterGameInstance::OnSessionUserInviteAccepted( - const bool bWasSuccess, - const int32 ControllerId, - TSharedPtr< const FUniqueNetId > UserId, - const FOnlineSessionSearchResult & InviteResult -) -{ - UE_LOG( LogOnline, Verbose, TEXT( "HandleSessionUserInviteAccepted: bSuccess: %d, ControllerId: %d, User: %s" ), bWasSuccess, ControllerId, UserId.IsValid() ? *UserId->ToString() : TEXT( "NULL ") ); - - if ( !bWasSuccess ) - { - return; - } - - if ( !InviteResult.IsValid() ) - { - UE_LOG(LogOnline, Warning, TEXT( "Invite accept returned no search result." ) ); - return; - } - - if ( !UserId.IsValid() ) - { - UE_LOG(LogOnline, Warning, TEXT( "Invite accept returned no user." ) ); - return; - } - - // Set the pending invite, and then go to the initial screen, which is where we will process it - PendingInvite.ControllerId = ControllerId; - PendingInvite.UserId = UserId; - PendingInvite.InviteResult = InviteResult; - PendingInvite.bPrivilegesCheckedAndAllowed = false; - - GotoState( ShooterGameInstanceState::PendingInvite ); -} - -void UShooterGameInstance::HandleNetworkConnectionStatusChanged( EOnlineServerConnectionStatus::Type LastConnectionStatus, EOnlineServerConnectionStatus::Type ConnectionStatus ) +void UShooterGameInstance::HandleNetworkConnectionStatusChanged( const FString& ServiceName, EOnlineServerConnectionStatus::Type LastConnectionStatus, EOnlineServerConnectionStatus::Type ConnectionStatus ) { - UE_LOG( LogOnlineGame, Warning, TEXT( "UShooterGameInstance::HandleNetworkConnectionStatusChanged: %s" ), EOnlineServerConnectionStatus::ToString( ConnectionStatus ) ); + UE_LOG( LogOnlineGame, Log, TEXT( "UShooterGameInstance::HandleNetworkConnectionStatusChanged: %s" ), EOnlineServerConnectionStatus::ToString( ConnectionStatus ) ); #if SHOOTER_CONSOLE_UI // If we are disconnected from server, and not currently at (or heading to) the welcome screen // then display a message on consoles - if ( bIsOnline && + if ( OnlineMode != EOnlineMode::Offline && PendingState != ShooterGameInstanceState::WelcomeScreen && CurrentState != ShooterGameInstanceState::WelcomeScreen && - ConnectionStatus != EOnlineServerConnectionStatus::Connected ) + ConnectionStatus != EOnlineServerConnectionStatus::Connected && + ConnectionStatus != EOnlineServerConnectionStatus::Normal) { UE_LOG( LogOnlineGame, Log, TEXT( "UShooterGameInstance::HandleNetworkConnectionStatusChanged: Going to main menu" ) ); @@ -205,6 +186,13 @@ void UShooterGameInstance::HandleNetworkConnectionStatusChanged( EOnlineServerC #endif const FText OKButton = NSLOCTEXT( "DialogButtons", "OKAY", "OK" ); + UWorld* const World = GetWorld(); + AShooterGameMode* const GameMode = World != NULL ? Cast(World->GetAuthGameMode()) : NULL; + if (GameMode) + { + GameMode->AbortMatch(); + } + ShowMessageThenGotoState( ReturnReason, OKButton, FText::GetEmpty(), ShooterGameInstanceState::MainMenu ); } @@ -218,7 +206,7 @@ void UShooterGameInstance::HandleSessionFailure( const FUniqueNetId& NetId, ESes #if SHOOTER_CONSOLE_UI // If we are not currently at (or heading to) the welcome screen then display a message on consoles - if ( bIsOnline && + if ( OnlineMode != EOnlineMode::Offline && PendingState != ShooterGameInstanceState::WelcomeScreen && CurrentState != ShooterGameInstanceState::WelcomeScreen ) { @@ -239,23 +227,26 @@ void UShooterGameInstance::HandleSessionFailure( const FUniqueNetId& NetId, ESes #endif } -void UShooterGameInstance::OnPreLoadMap() +void UShooterGameInstance::OnPreLoadMap(const FString& MapName) { - if ( bPendingEnableSplitscreen ) + if (bPendingEnableSplitscreen) { // Allow splitscreen - GetGameViewportClient()->SetDisableSplitscreenOverride( false ); + UGameViewportClient* GameViewportClient = GetGameViewportClient(); + if (GameViewportClient != nullptr) + { + GameViewportClient->SetDisableSplitscreenOverride(false); - bPendingEnableSplitscreen = false; + bPendingEnableSplitscreen = false; + } } } -void UShooterGameInstance::OnPostLoadMap() +void UShooterGameInstance::OnPostLoadMap(UWorld*) { // Make sure we hide the loading screen when the level is done loading UShooterGameViewportClient * ShooterViewport = Cast(GetGameViewportClient()); - - if ( ShooterViewport != NULL ) + if (ShooterViewport != nullptr) { ShooterViewport->HideLoadingScreen(); } @@ -283,6 +274,28 @@ void UShooterGameInstance::OnUserCanPlayInvite(const FUniqueNetId& UserId, EUser } } +void UShooterGameInstance::OnUserCanPlayTogether(const FUniqueNetId& UserId, EUserPrivileges::Type Privilege, uint32 PrivilegeResults) +{ + CleanupOnlinePrivilegeTask(); + if (WelcomeMenuUI.IsValid()) + { + WelcomeMenuUI->LockControls(false); + } + + if (PrivilegeResults == (uint32)IOnlineIdentity::EPrivilegeResults::NoFailures) + { + if (WelcomeMenuUI.IsValid()) + { + WelcomeMenuUI->SetControllerAndAdvanceToMainMenu(PlayTogetherInfo.UserIndex); + } + } + else + { + DisplayOnlinePrivilegeFailureDialogs(UserId, Privilege, PrivilegeResults); + GotoState(ShooterGameInstanceState::WelcomeScreen); + } +} + void UShooterGameInstance::OnPostDemoPlay() { GotoState( ShooterGameInstanceState::Playing ); @@ -290,7 +303,13 @@ void UShooterGameInstance::OnPostDemoPlay() void UShooterGameInstance::HandleDemoPlaybackFailure( EDemoPlayFailure::Type FailureType, const FString& ErrorString ) { - ShowMessageThenGotoState( FText::Format( NSLOCTEXT("UShooterGameInstance", "DemoPlaybackFailedFmt", "Demo playback failed: {0}"), FText::FromString(ErrorString) ), NSLOCTEXT( "DialogButtons", "OKAY", "OK" ), FText::GetEmpty(), ShooterGameInstanceState::MainMenu ); + if (GetWorld() != nullptr && GetWorld()->WorldType == EWorldType::PIE) + { + UE_LOG(LogEngine, Warning, TEXT("Demo failed to play back correctly, got error %s"), *ErrorString); + return; + } + + ShowMessageThenGotoState(FText::Format(NSLOCTEXT("UShooterGameInstance", "DemoPlaybackFailedFmt", "Demo playback failed: {0}"), FText::FromString(ErrorString)), NSLOCTEXT("DialogButtons", "OKAY", "OK"), FText::GetEmpty(), ShooterGameInstanceState::MainMenu); } void UShooterGameInstance::StartGameInstance() @@ -311,6 +330,12 @@ void UShooterGameInstance::StartGameInstance() FURL URL(&DefaultURL, Parm, TRAVEL_Partial); + // If forcelan is set, we need to make sure to add the LAN flag to the travel url + if (FParse::Param(Cmd, TEXT("forcelan"))) + { + URL.AddOption(TEXT("bIsLanMatch")); + } + if (URL.Valid) { UEngine* const Engine = GetEngine(); @@ -476,7 +501,7 @@ AShooterGameSession* UShooterGameInstance::GetGameSession() const UWorld* const World = GetWorld(); if (World) { - AGameMode* const Game = World->GetAuthGameMode(); + AGameModeBase* const Game = World->GetAuthGameMode(); if (Game) { return Cast(Game->GameSession); @@ -507,6 +532,11 @@ void UShooterGameInstance::ShowMessageThenGoMain(const FText& Message, const FTe ShowMessageThenGotoState(Message, OKButtonString, CancelButtonString, ShooterGameInstanceState::MainMenu); } +void UShooterGameInstance::SetPendingInvite(const FShooterPendingInvite& InPendingInvite) +{ + PendingInvite = InPendingInvite; +} + void UShooterGameInstance::GotoState(FName NewState) { UE_LOG( LogOnline, Log, TEXT( "GotoState: NewState: %s" ), *NewState.ToString() ); @@ -607,7 +637,7 @@ void UShooterGameInstance::EndPendingInviteState() void UShooterGameInstance::BeginWelcomeScreenState() { //this must come before split screen player removal so that the OSS sets all players to not using online features. - SetIsOnline(false); + SetOnlineMode(EOnlineMode::Offline); // Remove any possible splitscren players RemoveSplitScreenPlayers(); @@ -634,18 +664,20 @@ void UShooterGameInstance::EndWelcomeScreenState() } } -void UShooterGameInstance::SetPresenceForLocalPlayers(const FVariantData& PresenceData) +void UShooterGameInstance::SetPresenceForLocalPlayers(const FString& StatusStr, const FVariantData& PresenceData) { const auto Presence = Online::GetPresenceInterface(); if (Presence.IsValid()) { for (int i = 0; i < LocalPlayers.Num(); ++i) { - const TSharedPtr UserId = LocalPlayers[i]->GetPreferredUniqueNetId(); + FUniqueNetIdRepl UserId = LocalPlayers[i]->GetPreferredUniqueNetId(); if (UserId.IsValid()) { FOnlineUserPresenceStatus PresenceStatus; + PresenceStatus.StatusStr = StatusStr; + PresenceStatus.State = EOnlinePresenceState::Online; PresenceStatus.Properties.Add(DefaultPresenceKey, PresenceData); Presence->SetPresence(*UserId, PresenceStatus); @@ -664,16 +696,21 @@ void UShooterGameInstance::BeginMainMenuState() ShooterViewport->HideLoadingScreen(); } - SetIsOnline(false); + SetOnlineMode(EOnlineMode::Offline); // Disallow splitscreen - GetGameViewportClient()->SetDisableSplitscreenOverride( true ); + UGameViewportClient* GameViewportClient = GetGameViewportClient(); + + if (GameViewportClient) + { + GetGameViewportClient()->SetDisableSplitscreenOverride(true); + } // Remove any possible splitscren players RemoveSplitScreenPlayers(); // Set presence to menu state for the owning player - SetPresenceForLocalPlayers(FVariantData(FString(TEXT("OnMenu")))); + SetPresenceForLocalPlayers(FString(TEXT("In Menu")), FVariantData(FString(TEXT("OnMenu")))); // load startup map LoadFrontEndMap(MainMenuMap); @@ -685,13 +722,21 @@ void UShooterGameInstance::BeginMainMenuState() MainMenuUI->Construct(this, Player); MainMenuUI->AddMenuToGameViewport(); + // It's possible that a play together event was sent by the system while the player was in-game or didn't + // have the application launched. The game will automatically go directly to the main menu state in those cases + // so this will handle Play Together if that is why we transitioned here. + if (PlayTogetherInfo.UserIndex != -1) + { + MainMenuUI->OnPlayTogetherEventReceived(); + } + #if !SHOOTER_CONSOLE_UI // The cached unique net ID is usually set on the welcome screen, but there isn't // one on PC/Mac, so do it here. if (Player != nullptr) { Player->SetControllerId(0); - Player->SetCachedUniqueNetId(Player->GetUniqueNetIdFromCachedControllerId()); + Player->SetCachedUniqueNetId(Player->GetUniqueNetIdFromCachedControllerId().GetUniqueNetId()); } #endif @@ -745,7 +790,7 @@ void UShooterGameInstance::BeginPlayingState() bPendingEnableSplitscreen = true; // Set presence for playing in a map - SetPresenceForLocalPlayers(FVariantData(FString(TEXT("InGame")))); + SetPresenceForLocalPlayers(FString(TEXT("In Game")), FVariantData(FString(TEXT("InGame")))); // Make sure viewport has focus FSlateApplication::Get().SetAllUserFocusToGameViewport(); @@ -757,7 +802,7 @@ void UShooterGameInstance::EndPlayingState() GetGameViewportClient()->SetDisableSplitscreenOverride( true ); // Clear the players' presence information - SetPresenceForLocalPlayers(FVariantData(FString(TEXT("OnMenu")))); + SetPresenceForLocalPlayers(FString(TEXT("In Menu")), FVariantData(FString(TEXT("OnMenu")))); UWorld* const World = GetWorld(); AShooterGameState* const GameState = World != NULL ? World->GetGameState() : NULL; @@ -815,32 +860,33 @@ void UShooterGameInstance::CleanupSessionOnReturnToMenu() if ( Sessions.IsValid() ) { - EOnlineSessionState::Type SessionState = Sessions->GetSessionState(GameSessionName); - UE_LOG(LogOnline, Log, TEXT("Session %s is '%s'"), *GameSessionName.ToString(), EOnlineSessionState::ToString(SessionState)); + FName GameSession(NAME_GameSession); + EOnlineSessionState::Type SessionState = Sessions->GetSessionState(NAME_GameSession); + UE_LOG(LogOnline, Log, TEXT("Session %s is '%s'"), *GameSession.ToString(), EOnlineSessionState::ToString(SessionState)); if ( EOnlineSessionState::InProgress == SessionState ) { - UE_LOG(LogOnline, Log, TEXT("Ending session %s on return to main menu"), *GameSessionName.ToString() ); + UE_LOG(LogOnline, Log, TEXT("Ending session %s on return to main menu"), *GameSession.ToString() ); OnEndSessionCompleteDelegateHandle = Sessions->AddOnEndSessionCompleteDelegate_Handle(OnEndSessionCompleteDelegate); - Sessions->EndSession(GameSessionName); + Sessions->EndSession(NAME_GameSession); bPendingOnlineOp = true; } else if ( EOnlineSessionState::Ending == SessionState ) { - UE_LOG(LogOnline, Log, TEXT("Waiting for session %s to end on return to main menu"), *GameSessionName.ToString() ); + UE_LOG(LogOnline, Log, TEXT("Waiting for session %s to end on return to main menu"), *GameSession.ToString() ); OnEndSessionCompleteDelegateHandle = Sessions->AddOnEndSessionCompleteDelegate_Handle(OnEndSessionCompleteDelegate); bPendingOnlineOp = true; } else if ( EOnlineSessionState::Ended == SessionState || EOnlineSessionState::Pending == SessionState ) { - UE_LOG(LogOnline, Log, TEXT("Destroying session %s on return to main menu"), *GameSessionName.ToString() ); + UE_LOG(LogOnline, Log, TEXT("Destroying session %s on return to main menu"), *GameSession.ToString() ); OnDestroySessionCompleteDelegateHandle = Sessions->AddOnDestroySessionCompleteDelegate_Handle(OnEndSessionCompleteDelegate); - Sessions->DestroySession(GameSessionName); + Sessions->DestroySession(NAME_GameSession); bPendingOnlineOp = true; } - else if ( EOnlineSessionState::Starting == SessionState ) + else if ( EOnlineSessionState::Starting == SessionState || EOnlineSessionState::Creating == SessionState) { - UE_LOG(LogOnline, Log, TEXT("Waiting for session %s to start, and then we will end it to return to main menu"), *GameSessionName.ToString() ); + UE_LOG(LogOnline, Log, TEXT("Waiting for session %s to start, and then we will end it to return to main menu"), *GameSession.ToString() ); OnStartSessionCompleteDelegateHandle = Sessions->AddOnStartSessionCompleteDelegate_Handle(OnEndSessionCompleteDelegate); bPendingOnlineOp = true; } @@ -879,10 +925,56 @@ void UShooterGameInstance::AddNetworkFailureHandlers() } } -// starts playing a game as the host +TSubclassOf UShooterGameInstance::GetOnlineSessionClass() +{ + return UShooterOnlineSessionClient::StaticClass(); +} + +bool UShooterGameInstance::HostQuickSession(ULocalPlayer& LocalPlayer, const FOnlineSessionSettings& SessionSettings) +{ + // This function is different from BeginHostingQuickMatch in that it creates a session and then starts a quick match, + // while BeginHostingQuickMatch assumes a session already exists + + if (AShooterGameSession* const GameSession = GetGameSession()) + { + // Add callback delegate for completion + OnCreatePresenceSessionCompleteDelegateHandle = GameSession->OnCreatePresenceSessionComplete().AddUObject(this, &UShooterGameInstance::OnCreatePresenceSessionComplete); + + TravelURL = GetQuickMatchUrl(); + + FOnlineSessionSettings HostSettings = SessionSettings; + + const FString GameType = UGameplayStatics::ParseOption(TravelURL, TEXT("game")); + + // Determine the map name from the travelURL + const FString MapNameSubStr = "/Game/Maps/"; + const FString ChoppedMapName = TravelURL.RightChop(MapNameSubStr.Len()); + const FString MapName = ChoppedMapName.LeftChop(ChoppedMapName.Len() - ChoppedMapName.Find("?game")); + + HostSettings.Set(SETTING_GAMEMODE, GameType, EOnlineDataAdvertisementType::ViaOnlineService); + HostSettings.Set(SETTING_MAPNAME, MapName, EOnlineDataAdvertisementType::ViaOnlineService); + HostSettings.NumPublicConnections = 16; + + if (GameSession->HostSession(LocalPlayer.GetPreferredUniqueNetId().GetUniqueNetId(), NAME_GameSession, SessionSettings)) + { + // If any error occurred in the above, pending state would be set + if (PendingState == CurrentState || PendingState == ShooterGameInstanceState::None) + { + // Go ahead and go into loading state now + // If we fail, the delegate will handle showing the proper messaging and move to the correct state + ShowLoadingScreen(); + GotoState(ShooterGameInstanceState::Playing); + return true; + } + } + } + + return false; +} + bool UShooterGameInstance::HostGame(ULocalPlayer* LocalPlayer, const FString& GameType, const FString& InTravelURL) { - if (!GetIsOnline()) + if (GetOnlineMode() == EOnlineMode::Offline) { // // Offline game, just go straight to map @@ -915,9 +1007,9 @@ bool UShooterGameInstance::HostGame(ULocalPlayer* LocalPlayer, const FString& Ga const FString& ChoppedMapName = TravelURL.RightChop(MapNameSubStr.Len()); const FString& MapName = ChoppedMapName.LeftChop(ChoppedMapName.Len() - ChoppedMapName.Find("?game")); - if (GameSession->HostSession(LocalPlayer->GetPreferredUniqueNetId(), GameSessionName, GameType, MapName, bIsLanMatch, true, AShooterGameSession::DEFAULT_NUM_PLAYERS)) + if (GameSession->HostSession(LocalPlayer->GetPreferredUniqueNetId().GetUniqueNetId(), NAME_GameSession, GameType, MapName, bIsLanMatch, true, AShooterGameSession::DEFAULT_NUM_PLAYERS)) { - // If any error occured in the above, pending state would be set + // If any error occurred in the above, pending state would be set if ( (PendingState == CurrentState) || (PendingState == ShooterGameInstanceState::None) ) { // Go ahead and go into loading state now @@ -942,7 +1034,7 @@ bool UShooterGameInstance::JoinSession(ULocalPlayer* LocalPlayer, int32 SessionI AddNetworkFailureHandlers(); OnJoinSessionCompleteDelegateHandle = GameSession->OnJoinSessionComplete().AddUObject(this, &UShooterGameInstance::OnJoinSessionComplete); - if (GameSession->JoinSession(LocalPlayer->GetPreferredUniqueNetId(), GameSessionName, SessionIndexInSearchResults)) + if (GameSession->JoinSession(LocalPlayer->GetPreferredUniqueNetId().GetUniqueNetId(), NAME_GameSession, SessionIndexInSearchResults)) { // If any error occured in the above, pending state would be set if ( (PendingState == CurrentState) || (PendingState == ShooterGameInstanceState::None) ) @@ -968,7 +1060,7 @@ bool UShooterGameInstance::JoinSession(ULocalPlayer* LocalPlayer, const FOnlineS AddNetworkFailureHandlers(); OnJoinSessionCompleteDelegateHandle = GameSession->OnJoinSessionComplete().AddUObject(this, &UShooterGameInstance::OnJoinSessionComplete); - if (GameSession->JoinSession(LocalPlayer->GetPreferredUniqueNetId(), GameSessionName, SearchResult)) + if (GameSession->JoinSession(LocalPlayer->GetPreferredUniqueNetId().GetUniqueNetId(), NAME_GameSession, SearchResult)) { // If any error occured in the above, pending state would be set if ( (PendingState == CurrentState) || (PendingState == ShooterGameInstanceState::None) ) @@ -1011,7 +1103,7 @@ void UShooterGameInstance::OnJoinSessionComplete(EOnJoinSessionCompleteResult::T auto Sessions = Online::GetSessionInterface(); if (Sessions.IsValid() && LocalPlayers[1]->GetPreferredUniqueNetId().IsValid()) { - Sessions->RegisterLocalPlayer(*LocalPlayers[1]->GetPreferredUniqueNetId(), GameSessionName, + Sessions->RegisterLocalPlayer(*LocalPlayers[1]->GetPreferredUniqueNetId(), NAME_GameSession, FOnRegisterLocalPlayerCompleteDelegate::CreateUObject(this, &UShooterGameInstance::OnRegisterJoiningLocalPlayerComplete)); } } @@ -1046,7 +1138,7 @@ void UShooterGameInstance::FinishJoinSession(EOnJoinSessionCompleteResult::Type return; } - InternalTravelToSession(GameSessionName); + InternalTravelToSession(NAME_GameSession); } void UShooterGameInstance::OnRegisterJoiningLocalPlayerComplete(const FUniqueNetId& PlayerId, EOnJoinSessionCompleteResult::Type Result) @@ -1091,6 +1183,14 @@ void UShooterGameInstance::InternalTravelToSession(const FName& SessionName) return; } + // Add debug encryption token if desired. + if (CVarShooterGameTestEncryption->GetInt() != 0) + { + // This is just a value for testing/debugging, the server will use the same key regardless of the token value. + // But the token could be a user ID and/or session ID that would be used to generate a unique key per user and/or session, if desired. + URL += TEXT("?EncryptionToken=1"); + } + PlayerController->ClientTravel(URL, TRAVEL_Absolute); } @@ -1108,7 +1208,7 @@ void UShooterGameInstance::OnCreatePresenceSessionComplete(FName SessionName, bo auto Sessions = Online::GetSessionInterface(); if (Sessions.IsValid() && LocalPlayers[1]->GetPreferredUniqueNetId().IsValid()) { - Sessions->RegisterLocalPlayer(*LocalPlayers[1]->GetPreferredUniqueNetId(), GameSessionName, + Sessions->RegisterLocalPlayer(*LocalPlayers[1]->GetPreferredUniqueNetId(), NAME_GameSession, FOnRegisterLocalPlayerCompleteDelegate::CreateUObject(this, &UShooterGameInstance::OnRegisterLocalPlayerComplete)); } } @@ -1121,7 +1221,7 @@ void UShooterGameInstance::OnCreatePresenceSessionComplete(FName SessionName, bo } /** Initiates the session searching */ -bool UShooterGameInstance::FindSessions(ULocalPlayer* PlayerOwner, bool bFindLAN) +bool UShooterGameInstance::FindSessions(ULocalPlayer* PlayerOwner, bool bIsDedicatedServer, bool bFindLAN) { bool bResult = false; @@ -1134,7 +1234,7 @@ bool UShooterGameInstance::FindSessions(ULocalPlayer* PlayerOwner, bool bFindLAN GameSession->OnFindSessionsComplete().RemoveAll(this); OnSearchSessionsCompleteDelegateHandle = GameSession->OnFindSessionsComplete().AddUObject(this, &UShooterGameInstance::OnSearchSessionsComplete); - GameSession->FindSessions(PlayerOwner->GetPreferredUniqueNetId(), GameSessionName, bFindLAN, true); + GameSession->FindSessions(PlayerOwner->GetPreferredUniqueNetId().GetUniqueNetId(), NAME_GameSession, bFindLAN, !bIsDedicatedServer); bResult = true; } @@ -1156,19 +1256,29 @@ void UShooterGameInstance::OnSearchSessionsComplete(bool bWasSuccessful) bool UShooterGameInstance::Tick(float DeltaSeconds) { // Dedicated server doesn't need to worry about game state - if (IsRunningDedicatedServer() == true) + if (IsDedicatedServerInstance() == true) { return true; } - MaybeChangeState(); + UShooterGameViewportClient* ShooterViewport = Cast(GetGameViewportClient()); + if (FSlateApplication::IsInitialized() && ShooterViewport != nullptr) + { + if (FSlateApplication::Get().GetGameViewport() != ShooterViewport->GetGameViewportWidget()) + { + return true; + } + } - UShooterGameViewportClient * ShooterViewport = Cast(GetGameViewportClient()); + // Because this takes place outside the normal UWorld tick, we need to register what world we're ticking/modifying here to avoid issues in the editor + FScopedConditionalWorldSwitcher WorldSwitcher(GetWorld()); - if (CurrentState != ShooterGameInstanceState::WelcomeScreen) + MaybeChangeState(); + + if (CurrentState != ShooterGameInstanceState::WelcomeScreen && ShooterViewport != nullptr) { // If at any point we aren't licensed (but we are after welcome screen) bounce them back to the welcome screen - if (!bIsLicensed && CurrentState != ShooterGameInstanceState::None && !ShooterViewport->IsShowingDialog()) + if (!bIsLicensed && CurrentState != ShooterGameInstanceState::None && ShooterViewport != nullptr && !ShooterViewport->IsShowingDialog()) { const FText ReturnReason = NSLOCTEXT( "ProfileMessages", "NeedLicense", "The signed in users do not have a license for this game. Please purchase ShooterGame from the Xbox Marketplace or sign in a user with a valid license." ); const FText OKButton = NSLOCTEXT( "DialogButtons", "OKAY", "OK" ); @@ -1177,8 +1287,7 @@ bool UShooterGameInstance::Tick(float DeltaSeconds) } // Show controller disconnected dialog if any local players have an invalid controller - if(ShooterViewport != NULL && - !ShooterViewport->IsShowingDialog()) + if (!ShooterViewport->IsShowingDialog()) { for (int i = 0; i < LocalPlayers.Num(); ++i) { @@ -1188,7 +1297,7 @@ bool UShooterGameInstance::Tick(float DeltaSeconds) LocalPlayers[i], EShooterDialogType::ControllerDisconnected, FText::Format(NSLOCTEXT("ProfileMessages", "PlayerReconnectControllerFmt", "Player {0}, please reconnect your controller."), FText::AsNumber(i + 1)), -#ifdef PLATFORM_PS4 +#if PLATFORM_PS4 NSLOCTEXT("DialogButtons", "PS4_CrossButtonContinue", "Cross Button - Continue"), #else NSLOCTEXT("DialogButtons", "AButtonContinue", "A - Continue"), @@ -1210,7 +1319,7 @@ bool UShooterGameInstance::Tick(float DeltaSeconds) if (Sessions.IsValid()) { - EOnlineSessionState::Type SessionState = Sessions->GetSessionState(GameSessionName); + EOnlineSessionState::Type SessionState = Sessions->GetSessionState(NAME_GameSession); if (SessionState == EOnlineSessionState::NoSession) { @@ -1220,12 +1329,21 @@ bool UShooterGameInstance::Tick(float DeltaSeconds) { NewPlayerOwner->SetControllerId(PendingInvite.ControllerId); NewPlayerOwner->SetCachedUniqueNetId(PendingInvite.UserId); - SetIsOnline(true); - JoinSession(NewPlayerOwner, PendingInvite.InviteResult); + SetOnlineMode(EOnlineMode::Online); + + const bool bIsLocalPlayerHost = PendingInvite.UserId.IsValid() && PendingInvite.InviteResult.Session.OwningUserId.IsValid() && *PendingInvite.UserId == *PendingInvite.InviteResult.Session.OwningUserId; + if (bIsLocalPlayerHost) + { + HostQuickSession(*NewPlayerOwner, PendingInvite.InviteResult.Session.SessionSettings); + } + else + { + JoinSession(NewPlayerOwner, PendingInvite.InviteResult); + } } PendingInvite.UserId.Reset(); - } + } } } @@ -1243,6 +1361,29 @@ bool UShooterGameInstance::HandleOpenCommand(const TCHAR* Cmd, FOutputDevice& Ar return bOpenSuccessful; } +bool UShooterGameInstance::HandleDisconnectCommand(const TCHAR* Cmd, FOutputDevice& Ar, UWorld* InWorld) +{ + bool const bDisconnectSuccessful = Super::HandleDisconnectCommand(Cmd, Ar, InWorld); + if (bDisconnectSuccessful) + { + GotoState(ShooterGameInstanceState::MainMenu); + } + + return bDisconnectSuccessful; +} + +bool UShooterGameInstance::HandleTravelCommand(const TCHAR* Cmd, FOutputDevice& Ar, UWorld* InWorld) +{ + bool const bTravelSuccessful = Super::HandleTravelCommand(Cmd, Ar, InWorld); + if (bTravelSuccessful) + { + GotoState(ShooterGameInstanceState::Playing); + } + + return bTravelSuccessful; +} + + void UShooterGameInstance::HandleSignInChangeMessaging() { // Master user signed out, go to initial state (if we aren't there already) @@ -1262,7 +1403,12 @@ void UShooterGameInstance::HandleSignInChangeMessaging() void UShooterGameInstance::HandleUserLoginChanged(int32 GameUserIndex, ELoginStatus::Type PreviousLoginStatus, ELoginStatus::Type LoginStatus, const FUniqueNetId& UserId) { - const bool bDowngraded = (LoginStatus == ELoginStatus::NotLoggedIn && !GetIsOnline()) || (LoginStatus != ELoginStatus::LoggedIn && GetIsOnline()); + // On Switch, accounts can play in LAN games whether they are signed in online or not. +#if PLATFORM_SWITCH + const bool bDowngraded = LoginStatus == ELoginStatus::NotLoggedIn || (GetOnlineMode() == EOnlineMode::Online && LoginStatus == ELoginStatus::UsingLocalProfile); +#else + const bool bDowngraded = (LoginStatus == ELoginStatus::NotLoggedIn && GetOnlineMode() == EOnlineMode::Offline) || (LoginStatus != ELoginStatus::LoggedIn && GetOnlineMode() != EOnlineMode::Offline); +#endif UE_LOG( LogOnline, Log, TEXT( "HandleUserLoginChanged: bDownGraded: %i" ), (int)bDowngraded ); @@ -1272,6 +1418,8 @@ void UShooterGameInstance::HandleUserLoginChanged(int32 GameUserIndex, ELoginSta // Find the local player associated with this unique net id ULocalPlayer * LocalPlayer = FindLocalPlayerFromUniqueNetId( UserId ); + LocalPlayerOnlineStatus[GameUserIndex] = LoginStatus; + // If this user is signed out, but was previously signed in, punt to welcome (or remove splitscreen if that makes sense) if ( LocalPlayer != NULL ) { @@ -1282,7 +1430,7 @@ void UShooterGameInstance::HandleUserLoginChanged(int32 GameUserIndex, ELoginSta LabelPlayerAsQuitter(LocalPlayer); // Check to see if this was the master, or if this was a split-screen player on the client - if ( LocalPlayer == GetFirstGamePlayer() || GetIsOnline() ) + if ( LocalPlayer == GetFirstGamePlayer() || GetOnlineMode() != EOnlineMode::Offline ) { HandleSignInChangeMessaging(); } @@ -1342,7 +1490,7 @@ void UShooterGameInstance::HandleAppSuspend() for (int i = 0; i < LocalPlayers.Num(); ++i) { auto ShooterPC = Cast(LocalPlayers[i]->PlayerController); - if (ShooterPC) + if (ShooterPC && GameState) { // Assuming you can't win if you quit early ShooterPC->ClientSendRoundEndEvent(false, GameState->ElapsedTime); @@ -1361,7 +1509,7 @@ void UShooterGameInstance::HandleAppResume() for ( int32 i = 0; i < LocalPlayers.Num(); ++i ) { - if ( LocalPlayers[i]->GetCachedUniqueNetId().IsValid() && !IsLocalPlayerOnline( LocalPlayers[i] ) ) + if ( LocalPlayers[i]->GetCachedUniqueNetId().IsValid() && LocalPlayerOnlineStatus[i] == ELoginStatus::LoggedIn && !IsLocalPlayerOnline( LocalPlayers[i] ) ) { UE_LOG( LogOnline, Log, TEXT( "UShooterGameInstance::HandleAppResume: Signed out during resume." ) ); HandleSignInChangeMessaging(); @@ -1546,9 +1694,14 @@ TSharedPtr< const FUniqueNetId > UShooterGameInstance::GetUniqueNetIdFromControl return nullptr; } -void UShooterGameInstance::SetIsOnline(bool bInIsOnline) +void UShooterGameInstance::SetOnlineMode(EOnlineMode InOnlineMode) +{ + OnlineMode = InOnlineMode; + UpdateUsingMultiplayerFeatures(InOnlineMode == EOnlineMode::Online); +} + +void UShooterGameInstance::UpdateUsingMultiplayerFeatures(bool bIsUsingMultiplayerFeatures) { - bIsOnline = bInIsOnline; IOnlineSubsystem* OnlineSub = IOnlineSubsystem::Get(); if (OnlineSub) @@ -1557,12 +1710,12 @@ void UShooterGameInstance::SetIsOnline(bool bInIsOnline) { ULocalPlayer* LocalPlayer = LocalPlayers[i]; - TSharedPtr PlayerId = LocalPlayer->GetPreferredUniqueNetId(); + FUniqueNetIdRepl PlayerId = LocalPlayer->GetPreferredUniqueNetId(); if (PlayerId.IsValid()) { - OnlineSub->SetUsingMultiplayerFeatures(*PlayerId, bIsOnline); + OnlineSub->SetUsingMultiplayerFeatures(*PlayerId, bIsUsingMultiplayerFeatures); } - } + } } } @@ -1607,6 +1760,30 @@ bool UShooterGameInstance::IsLocalPlayerOnline(ULocalPlayer* LocalPlayer) return false; } +bool UShooterGameInstance::IsLocalPlayerSignedIn(ULocalPlayer* LocalPlayer) +{ + if (LocalPlayer == NULL) + { + return false; + } + + const auto OnlineSub = IOnlineSubsystem::Get(); + if (OnlineSub) + { + const auto IdentityInterface = OnlineSub->GetIdentityInterface(); + if (IdentityInterface.IsValid()) + { + auto UniqueId = LocalPlayer->GetCachedUniqueNetId(); + if (UniqueId.IsValid()) + { + return true; + } + } + } + + return false; +} + bool UShooterGameInstance::ValidatePlayerForOnlinePlay(ULocalPlayer* LocalPlayer) { // Get the viewport @@ -1661,6 +1838,37 @@ bool UShooterGameInstance::ValidatePlayerForOnlinePlay(ULocalPlayer* LocalPlayer return true; } +bool UShooterGameInstance::ValidatePlayerIsSignedIn(ULocalPlayer* LocalPlayer) +{ + // Get the viewport + UShooterGameViewportClient * ShooterViewport = Cast(GetGameViewportClient()); + + if (!IsLocalPlayerSignedIn(LocalPlayer)) + { + // Don't let them play online if they aren't online + if (ShooterViewport != NULL) + { + const FText Msg = NSLOCTEXT("NetworkFailures", "MustBeSignedIn", "You must be signed in to play online"); + const FText OKButtonString = NSLOCTEXT("DialogButtons", "OKAY", "OK"); + + ShooterViewport->ShowDialog( + NULL, + EShooterDialogType::Generic, + Msg, + OKButtonString, + FText::GetEmpty(), + FOnClicked::CreateUObject(this, &UShooterGameInstance::OnConfirmGeneric), + FOnClicked::CreateUObject(this, &UShooterGameInstance::OnConfirmGeneric) + ); + } + + return false; + } + + return true; +} + + FReply UShooterGameInstance::OnConfirmGeneric() { UShooterGameViewportClient * ShooterViewport = Cast(GetGameViewportClient()); @@ -1713,7 +1921,7 @@ void UShooterGameInstance::DisplayOnlinePrivilegeFailureDialogs(const FUniqueNet { for (auto It = GEngine->GetLocalPlayerIterator(GetWorld()); It; ++It) { - TSharedPtr OtherId = (*It)->GetPreferredUniqueNetId(); + FUniqueNetIdRepl OtherId = (*It)->GetPreferredUniqueNetId(); if (OtherId.IsValid()) { if (UserId == (*OtherId)) @@ -1806,6 +2014,9 @@ void UShooterGameInstance::FinishSessionCreation(EOnJoinSessionCompleteResult::T { if (Result == EOnJoinSessionCompleteResult::Success) { + // This will send any Play Together invites if necessary, or do nothing. + SendPlayTogetherInvites(); + // Travel to the specified match URL GetWorld()->ServerTravel(TravelURL); } @@ -1817,11 +2028,122 @@ void UShooterGameInstance::FinishSessionCreation(EOnJoinSessionCompleteResult::T } } +FString UShooterGameInstance::GetQuickMatchUrl() +{ + static const FString QuickMatchUrl(TEXT("/Game/Maps/Highrise?game=TDM?listen")); + return QuickMatchUrl; +} + void UShooterGameInstance::BeginHostingQuickMatch() { ShowLoadingScreen(); GotoState(ShooterGameInstanceState::Playing); // Travel to the specified match URL - GetWorld()->ServerTravel(TEXT("/Game/Maps/Highrise?game=TDM?listen")); + GetWorld()->ServerTravel(GetQuickMatchUrl()); +} + +void UShooterGameInstance::OnPlayTogetherEventReceived(const int32 UserIndex, const TArray>& UserIdList) +{ + PlayTogetherInfo = FShooterPlayTogetherInfo(UserIndex, UserIdList); + + const IOnlineSubsystem* const OnlineSub = IOnlineSubsystem::Get(); + check(OnlineSub); + + const IOnlineSessionPtr SessionInterface = OnlineSub->GetSessionInterface(); + check(SessionInterface.IsValid()); + + // If we have available slots to accomedate the whole party in our current sessions, we should send invites to the existing one + // instead of a new one according to Sony's best practices. + const FNamedOnlineSession* const Session = SessionInterface->GetNamedSession(NAME_GameSession); + if (Session != nullptr && Session->NumOpenPrivateConnections + Session->NumOpenPublicConnections >= UserIdList.Num()) + { + SendPlayTogetherInvites(); + } + // Always handle Play Together in the main menu since the player has session customization options. + else if (CurrentState == ShooterGameInstanceState::MainMenu) + { + MainMenuUI->OnPlayTogetherEventReceived(); + } + else if (CurrentState == ShooterGameInstanceState::WelcomeScreen) + { + StartOnlinePrivilegeTask(IOnlineIdentity::FOnGetUserPrivilegeCompleteDelegate::CreateUObject(this, &UShooterGameInstance::OnUserCanPlayTogether), EUserPrivileges::CanPlayOnline, PendingInvite.UserId); + } + else + { + GotoState(ShooterGameInstanceState::MainMenu); + } +} + +void UShooterGameInstance::SendPlayTogetherInvites() +{ + const IOnlineSubsystem* const OnlineSub = IOnlineSubsystem::Get(); + check(OnlineSub); + + const IOnlineSessionPtr SessionInterface = OnlineSub->GetSessionInterface(); + check(SessionInterface.IsValid()); + + if (PlayTogetherInfo.UserIndex != -1) + { + for (const ULocalPlayer* LocalPlayer : LocalPlayers) + { + if (LocalPlayer->GetControllerId() == PlayTogetherInfo.UserIndex) + { + FUniqueNetIdRepl PlayerId = LocalPlayer->GetPreferredUniqueNetId(); + if (PlayerId.IsValid()) + { + // Automatically send invites to friends in the player's PS4 party to conform with Play Together requirements + for (const TSharedPtr& FriendId : PlayTogetherInfo.UserIdList) + { + SessionInterface->SendSessionInviteToFriend(*PlayerId, NAME_GameSession, *FriendId.ToSharedRef()); + } + } + + } + } + + PlayTogetherInfo = FShooterPlayTogetherInfo(); + } +} + +void UShooterGameInstance::ReceivedNetworkEncryptionToken(const FString& EncryptionToken, const FOnEncryptionKeyResponse& Delegate) +{ + // This is a simple implementation to demonstrate using encryption for game traffic using a hardcoded key. + // For a complete implementation, you would likely want to retrieve the encryption key from a secure source, + // such as from a web service over HTTPS. This could be done in this function, even asynchronously - just + // call the response delegate passed in once the key is known. The contents of the EncryptionToken is up to the user, + // but it will generally contain information used to generate a unique encryption key, such as a user and/or session ID. + + FEncryptionKeyResponse Response(EEncryptionResponse::Failure, TEXT("Unknown encryption failure")); + + if (EncryptionToken.IsEmpty()) + { + Response.Response = EEncryptionResponse::InvalidToken; + Response.ErrorMsg = TEXT("Encryption token is empty."); + } + else + { + Response.Response = EEncryptionResponse::Success; + Response.EncryptionKey = DebugTestEncryptionKey; + } + + Delegate.ExecuteIfBound(Response); + +} + +void UShooterGameInstance::ReceivedNetworkEncryptionAck(const FOnEncryptionKeyResponse& Delegate) +{ + // This is a simple implementation to demonstrate using encryption for game traffic using a hardcoded key. + // For a complete implementation, you would likely want to retrieve the encryption key from a secure source, + // such as from a web service over HTTPS. This could be done in this function, even asynchronously - just + // call the response delegate passed in once the key is known. + + FEncryptionKeyResponse Response; + + TArray FakeKey; + + Response.Response = EEncryptionResponse::Success; + Response.EncryptionKey = DebugTestEncryptionKey; + + Delegate.ExecuteIfBound(Response); } diff --git a/Source/ShooterGame/Private/ShooterGameModule.cpp b/Source/ShooterGame/Private/ShooterGameModule.cpp index cf90288..8fb63b2 100644 --- a/Source/ShooterGame/Private/ShooterGameModule.cpp +++ b/Source/ShooterGame/Private/ShooterGameModule.cpp @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ShooterGame.h" #include "ShooterGameDelegates.h" diff --git a/Source/ShooterGame/Private/ShooterGameUserSettings.cpp b/Source/ShooterGame/Private/ShooterGameUserSettings.cpp index 8994bde..ed8a968 100644 --- a/Source/ShooterGame/Private/ShooterGameUserSettings.cpp +++ b/Source/ShooterGame/Private/ShooterGameUserSettings.cpp @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ShooterGame.h" #include "ShooterGameUserSettings.h" @@ -15,6 +15,7 @@ void UShooterGameUserSettings::SetToDefaults() GraphicsQuality = 1; bIsLanMatch = true; + bIsDedicatedServer = false; } void UShooterGameUserSettings::ApplySettings(bool bCheckForCommandLineOverrides) @@ -29,9 +30,4 @@ void UShooterGameUserSettings::ApplySettings(bool bCheckForCommandLineOverrides) } Super::ApplySettings(bCheckForCommandLineOverrides); - - if (!GEngine) - { - return; - } } \ No newline at end of file diff --git a/Source/ShooterGame/Private/ShooterGameViewportClient.cpp b/Source/ShooterGame/Private/ShooterGameViewportClient.cpp index df137cf..ac77489 100644 --- a/Source/ShooterGame/Private/ShooterGameViewportClient.cpp +++ b/Source/ShooterGame/Private/ShooterGameViewportClient.cpp @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ShooterGame.h" #include "ShooterGameViewportClient.h" @@ -252,6 +252,28 @@ void UShooterGameViewportClient::DrawTransition(UCanvas* Canvas) } #endif //WITH_EDITOR +void UShooterGameViewportClient::BeginDestroy() +{ + ReleaseSlateResources(); + + Super::BeginDestroy(); +} + +void UShooterGameViewportClient::DetachViewportClient() +{ + Super::DetachViewportClient(); + + ReleaseSlateResources(); +} + +void UShooterGameViewportClient::ReleaseSlateResources() +{ + OldFocusWidget.Reset(); + LoadingScreenWidget.Reset(); + ViewportContentStack.Empty(); + HiddenViewportContentStack.Empty(); +} + void SShooterLoadingScreen::Construct(const FArguments& InArgs) { static const FName LoadingScreenName(TEXT("/Game/UI/Menu/LoadingScreen.LoadingScreen")); diff --git a/Source/ShooterGame/Private/ShooterLeaderboards.h b/Source/ShooterGame/Private/ShooterLeaderboards.h index c5246ac..49279a7 100644 --- a/Source/ShooterGame/Private/ShooterLeaderboards.h +++ b/Source/ShooterGame/Private/ShooterLeaderboards.h @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once #include "ShooterTypes.h" diff --git a/Source/ShooterGame/Private/ShooterTeamStart.cpp b/Source/ShooterGame/Private/ShooterTeamStart.cpp index e1e4060..451b282 100644 --- a/Source/ShooterGame/Private/ShooterTeamStart.cpp +++ b/Source/ShooterGame/Private/ShooterTeamStart.cpp @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ShooterGame.h" #include "ShooterTeamStart.h" diff --git a/Source/ShooterGame/Private/Sound/SoundNodeLocalPlayer.cpp b/Source/ShooterGame/Private/Sound/SoundNodeLocalPlayer.cpp index e0d2bc2..56e4437 100644 --- a/Source/ShooterGame/Private/Sound/SoundNodeLocalPlayer.cpp +++ b/Source/ShooterGame/Private/Sound/SoundNodeLocalPlayer.cpp @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ShooterGame.h" #include "Sound/SoundNodeLocalPlayer.h" @@ -6,21 +6,20 @@ #define LOCTEXT_NAMESPACE "SoundNodeLocalPlayer" +TMap USoundNodeLocalPlayer::LocallyControlledActorCache; + USoundNodeLocalPlayer::USoundNodeLocalPlayer(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { } void USoundNodeLocalPlayer::ParseNodes(FAudioDevice* AudioDevice, const UPTRINT NodeWaveInstanceHash, FActiveSound& ActiveSound, const FSoundParseParameters& ParseParams, TArray& WaveInstances) { - // The accesses to the Pawn will be unsafe once we thread audio, deal with this at that point - check(IsInGameThread()); - - UAudioComponent* AudioComponent = ActiveSound.GetAudioComponent(); - AActor* SoundOwner = AudioComponent ? AudioComponent->GetOwner() : NULL; - APlayerController* PCOwner = Cast(SoundOwner); - APawn* PawnOwner = (PCOwner ? PCOwner->GetPawn() : Cast(SoundOwner)); + bool bLocallyControlled = false; + if (bool* LocallyControlledPtr = LocallyControlledActorCache.Find(ActiveSound.GetOwnerID())) + { + bLocallyControlled = *LocallyControlledPtr; + } - const bool bLocallyControlled = PawnOwner && PawnOwner->IsLocallyControlled() && Cast(PawnOwner->Controller); const int32 PlayIndex = bLocallyControlled ? 0 : 1; if (PlayIndex < ChildNodes.Num() && ChildNodes[PlayIndex]) diff --git a/Source/ShooterGame/Private/TakeHitInfo.cpp b/Source/ShooterGame/Private/TakeHitInfo.cpp new file mode 100644 index 0000000..a5a3932 --- /dev/null +++ b/Source/ShooterGame/Private/TakeHitInfo.cpp @@ -0,0 +1,65 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "ShooterGame.h" +#include "ShooterTypes.h" +#include "ShooterCharacter.h" + +FTakeHitInfo::FTakeHitInfo() + : ActualDamage(0) + , DamageTypeClass(NULL) + , PawnInstigator(NULL) + , DamageCauser(NULL) + , DamageEventClassID(0) + , bKilled(false) + , EnsureReplicationByte(0) +{} + +FDamageEvent& FTakeHitInfo::GetDamageEvent() +{ + switch (DamageEventClassID) + { + case FPointDamageEvent::ClassID: + if (PointDamageEvent.DamageTypeClass == NULL) + { + PointDamageEvent.DamageTypeClass = DamageTypeClass ? DamageTypeClass : UDamageType::StaticClass(); + } + return PointDamageEvent; + + case FRadialDamageEvent::ClassID: + if (RadialDamageEvent.DamageTypeClass == NULL) + { + RadialDamageEvent.DamageTypeClass = DamageTypeClass ? DamageTypeClass : UDamageType::StaticClass(); + } + return RadialDamageEvent; + + default: + if (GeneralDamageEvent.DamageTypeClass == NULL) + { + GeneralDamageEvent.DamageTypeClass = DamageTypeClass ? DamageTypeClass : UDamageType::StaticClass(); + } + return GeneralDamageEvent; + } +} + +void FTakeHitInfo::SetDamageEvent(const FDamageEvent& DamageEvent) +{ + DamageEventClassID = DamageEvent.GetTypeID(); + switch (DamageEventClassID) + { + case FPointDamageEvent::ClassID: + PointDamageEvent = *((FPointDamageEvent const*)(&DamageEvent)); + break; + case FRadialDamageEvent::ClassID: + RadialDamageEvent = *((FRadialDamageEvent const*)(&DamageEvent)); + break; + default: + GeneralDamageEvent = DamageEvent; + } + + DamageTypeClass = DamageEvent.DamageTypeClass; +} + +void FTakeHitInfo::EnsureReplication() +{ + EnsureReplicationByte++; +} \ No newline at end of file diff --git a/Source/ShooterGame/Private/UI/Menu/ShooterDemoPlaybackMenu.cpp b/Source/ShooterGame/Private/UI/Menu/ShooterDemoPlaybackMenu.cpp index fd1525e..e763c64 100644 --- a/Source/ShooterGame/Private/UI/Menu/ShooterDemoPlaybackMenu.cpp +++ b/Source/ShooterGame/Private/UI/Menu/ShooterDemoPlaybackMenu.cpp @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ShooterGame.h" #include "ShooterDemoPlaybackMenu.h" @@ -21,7 +21,7 @@ void FShooterDemoPlaybackMenu::Construct( ULocalPlayer* _PlayerOwner ) if ( !GameMenuWidget.IsValid() ) { SAssignNew( GameMenuWidget, SShooterMenuWidget ) - .PlayerOwner( TWeakObjectPtr( PlayerOwner ) ) + .PlayerOwner( MakeWeakObjectPtr( PlayerOwner ) ) .Cursor( EMouseCursor::Default ) .IsGameMenu( true ); diff --git a/Source/ShooterGame/Private/UI/Menu/ShooterDemoPlaybackMenu.h b/Source/ShooterGame/Private/UI/Menu/ShooterDemoPlaybackMenu.h index cf594d4..2b11f56 100644 --- a/Source/ShooterGame/Private/UI/Menu/ShooterDemoPlaybackMenu.h +++ b/Source/ShooterGame/Private/UI/Menu/ShooterDemoPlaybackMenu.h @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once #include "SlateBasics.h" #include "SlateExtras.h" diff --git a/Source/ShooterGame/Private/UI/Menu/ShooterFriends.cpp b/Source/ShooterGame/Private/UI/Menu/ShooterFriends.cpp index 878a2fa..3186305 100644 --- a/Source/ShooterGame/Private/UI/Menu/ShooterFriends.cpp +++ b/Source/ShooterGame/Private/UI/Menu/ShooterFriends.cpp @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ShooterGame.h" #include "ShooterFriends.h" @@ -28,10 +28,8 @@ void FShooterFriends::Construct(ULocalPlayer* _PlayerOwner, int32 LocalUserNum_) FriendsItem = MenuHelper::AddMenuItem(FriendsRoot, LOCTEXT("Friends", "FRIENDS")); OnlineSub = IOnlineSubsystem::Get(); OnlineFriendsPtr = OnlineSub->GetFriendsInterface(); - if ( OnlineFriendsPtr.IsValid() ) - { - OnlineFriendsPtr->ReadFriendsList(LocalUserNum, EFriendsLists::ToString(EFriendsLists::OnlinePlayers)); //init read of the friends list with the current user - } + + UpdateFriends(LocalUserNum); UserSettings = CastChecked(GEngine->GetGameUserSettings()); } @@ -94,22 +92,33 @@ void FShooterFriends::UpdateFriends(int32 NewOwnerIndex) } LocalUserNum = NewOwnerIndex; - OnlineFriendsPtr->ReadFriendsList(LocalUserNum, EFriendsLists::ToString(EFriendsLists::OnlinePlayers)); + OnlineFriendsPtr->ReadFriendsList(LocalUserNum, EFriendsLists::ToString(EFriendsLists::OnlinePlayers), FOnReadFriendsListComplete::CreateSP(this, &FShooterFriends::OnFriendsUpdated)); +} + +void FShooterFriends::OnFriendsUpdated(int32 /*unused*/, bool bWasSuccessful, const FString& FriendListName, const FString& ErrorString) +{ + if (!bWasSuccessful) + { + UE_LOG(LogOnline, Warning, TEXT("Unable to update friendslist %s due to error=[%s]"), *FriendListName, *ErrorString); + return; + } + MenuHelper::ClearSubMenu(FriendsItem); + Friends.Reset(); if (OnlineFriendsPtr->GetFriendsList(LocalUserNum, EFriendsLists::ToString(EFriendsLists::OnlinePlayers), Friends)) { - for (int32 Idx = 0; Idx < Friends.Num(); ++Idx) + for (const TSharedRef Friend : Friends) { - const FString FriendUsername = Friends[Idx]->GetDisplayName(); - TSharedPtr FriendItem = MenuHelper::AddMenuItem(FriendsItem, FText::FromString(FriendUsername)); - //FriendItem->OnControllerFacebuttonLeftPressed.BindRaw(this, &FShooterFriends::InviteSelectedFriendToGame); - FriendItem->OnControllerDownInputPressed.BindRaw(this, &FShooterFriends::IncrementFriendsCounter); - FriendItem->OnControllerUpInputPressed.BindRaw(this, &FShooterFriends::DecrementFriendsCounter); - FriendItem->OnControllerFacebuttonDownPressed.BindRaw(this, &FShooterFriends::ViewSelectedFriendProfile); + TSharedRef FriendItem = MenuHelper::AddMenuItem(FriendsItem, FText::FromString(Friend->GetDisplayName())); + FriendItem->OnControllerFacebuttonDownPressed.BindSP(this, &FShooterFriends::ViewSelectedFriendProfile); + FriendItem->OnControllerDownInputPressed.BindSP(this, &FShooterFriends::IncrementFriendsCounter); + FriendItem->OnControllerUpInputPressed.BindSP(this, &FShooterFriends::DecrementFriendsCounter); } + MaxFriendIndex = Friends.Num() - 1; } + MenuHelper::AddMenuItemSP(FriendsItem, LOCTEXT("Close", "CLOSE"), this, &FShooterFriends::OnApplySettings); } @@ -137,17 +146,21 @@ void FShooterFriends::ViewSelectedFriendProfile() auto ExternalUI = Online::GetExternalUIInterface(); if (ExternalUI.IsValid() && Requestor.IsValid() && Requestee.IsValid()) { - ExternalUI->ShowProfileUI(*Requestor, *Requestee, IOnlineExternalUI::FOnProfileUIClosedDelegate()); + ExternalUI->ShowProfileUI(*Requestor, *Requestee, FOnProfileUIClosedDelegate()); } } } void FShooterFriends::InviteSelectedFriendToGame() { - if (OnlineFriendsPtr.IsValid() && Friends.IsValidIndex(CurrFriendIndex)) + // invite the user to the current gamesession + + IOnlineSessionPtr OnlineSessionInterface = OnlineSub->GetSessionInterface(); + if (OnlineSessionInterface.IsValid()) { - OnlineFriendsPtr->SendInvite(LocalUserNum, *Friends[CurrFriendIndex]->GetUserId(), EFriendsLists::ToString(EFriendsLists::Default)); + OnlineSessionInterface->SendSessionInviteToFriend(LocalUserNum, NAME_GameSession, *Friends[CurrFriendIndex]->GetUserId()); } } #undef LOCTEXT_NAMESPACE + diff --git a/Source/ShooterGame/Private/UI/Menu/ShooterFriends.h b/Source/ShooterGame/Private/UI/Menu/ShooterFriends.h index b086ac6..c0516a6 100644 --- a/Source/ShooterGame/Private/UI/Menu/ShooterFriends.h +++ b/Source/ShooterGame/Private/UI/Menu/ShooterFriends.h @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once #include "SlateBasics.h" #include "SlateExtras.h" @@ -88,6 +88,8 @@ class FShooterFriends : public TSharedFromThis IOnlineFriendsPtr OnlineFriendsPtr; protected: + void OnFriendsUpdated(int32 UpdatedPlayerIndex, bool bWasSuccessful, const FString& FriendListName, const FString& ErrorString); + /** User settings pointer */ UShooterGameUserSettings* UserSettings; diff --git a/Source/ShooterGame/Private/UI/Menu/ShooterIngameMenu.cpp b/Source/ShooterGame/Private/UI/Menu/ShooterIngameMenu.cpp index 2866936..607e2d9 100644 --- a/Source/ShooterGame/Private/UI/Menu/ShooterIngameMenu.cpp +++ b/Source/ShooterGame/Private/UI/Menu/ShooterIngameMenu.cpp @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ShooterGame.h" #include "ShooterIngameMenu.h" @@ -11,6 +11,13 @@ #define LOCTEXT_NAMESPACE "ShooterGame.HUD.Menu" +#if PLATFORM_SWITCH +# define FRIENDS_SUPPORTED 0 +#else +# define FRIENDS_SUPPORTED 1 +#endif + + void FShooterIngameMenu::Construct(ULocalPlayer* _PlayerOwner) { PlayerOwner = _PlayerOwner; @@ -31,7 +38,7 @@ void FShooterIngameMenu::Construct(ULocalPlayer* _PlayerOwner) if (!GameMenuWidget.IsValid()) { SAssignNew(GameMenuWidget, SShooterMenuWidget) - .PlayerOwner(TWeakObjectPtr(PlayerOwner)) + .PlayerOwner(MakeWeakObjectPtr(PlayerOwner)) .Cursor(EMouseCursor::Default) .IsGameMenu(true); @@ -52,7 +59,9 @@ void FShooterIngameMenu::Construct(ULocalPlayer* _PlayerOwner) MenuHelper::AddExistingMenuItem(RootMenuItem, ShooterOptions->CheatsItem.ToSharedRef()); MenuHelper::AddExistingMenuItem(RootMenuItem, ShooterOptions->OptionsItem.ToSharedRef()); - if (GameInstance && GameInstance->GetIsOnline()) + +#if FRIENDS_SUPPORTED + if (GameInstance && GameInstance->GetOnlineMode() == EOnlineMode::Online) { #if !PLATFORM_XBOXONE ShooterFriends = MakeShareable(new FShooterFriends()); @@ -71,10 +80,11 @@ void FShooterIngameMenu::Construct(ULocalPlayer* _PlayerOwner) #endif #if SHOOTER_CONSOLE_UI - TSharedPtr ShowInvitesItem = MenuHelper::AddMenuItem(RootMenuItem, LOCTEXT("Invite Players", "INVITE PLAYERS")); + TSharedPtr ShowInvitesItem = MenuHelper::AddMenuItem(RootMenuItem, LOCTEXT("Invite Players", "INVITE PLAYERS (via System UI)")); ShowInvitesItem->OnConfirmMenuItem.BindRaw(this, &FShooterIngameMenu::OnShowInviteUI); #endif } +#endif if (FSlateApplication::Get().SupportsSystemHelp()) { @@ -209,6 +219,9 @@ void FShooterIngameMenu::ToggleGameMenu() PCOwner->SetCinematicMode(true, false, false, true, true); PCOwner->SetPause(true); + + FInputModeGameAndUI InputMode; + PCOwner->SetInputMode(InputMode); } } else @@ -225,6 +238,9 @@ void FShooterIngameMenu::ToggleGameMenu() if( ( ShooterHUD != NULL ) && ( ShooterHUD->IsMatchOver() == false ) ) { PCOwner->SetCinematicMode(false,false,false,true,true); + + FInputModeGameOnly InputMode; + PCOwner->SetInputMode(InputMode); } } } diff --git a/Source/ShooterGame/Private/UI/Menu/ShooterIngameMenu.h b/Source/ShooterGame/Private/UI/Menu/ShooterIngameMenu.h index 44b0c87..15d483b 100644 --- a/Source/ShooterGame/Private/UI/Menu/ShooterIngameMenu.h +++ b/Source/ShooterGame/Private/UI/Menu/ShooterIngameMenu.h @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once #include "SlateBasics.h" #include "SlateExtras.h" diff --git a/Source/ShooterGame/Private/UI/Menu/ShooterMainMenu.cpp b/Source/ShooterGame/Private/UI/Menu/ShooterMainMenu.cpp index 678818d..29dfb06 100644 --- a/Source/ShooterGame/Private/UI/Menu/ShooterMainMenu.cpp +++ b/Source/ShooterGame/Private/UI/Menu/ShooterMainMenu.cpp @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ShooterGame.h" #include "ShooterMainMenu.h" @@ -10,6 +10,7 @@ #include "SlateExtras.h" #include "GenericPlatformChunkInstall.h" #include "Online/ShooterOnlineGameSettings.h" +#include "OnlineSubsystemSessionSettings.h" #include "SShooterConfirmationDialog.h" #include "ShooterMenuItemWidgetStyle.h" #include "ShooterGameUserSettings.h" @@ -32,12 +33,16 @@ static const float QuickmatchUIAnimationTimeDuration = 30.f; //Instead of this mapping we should really use the AssetRegistry to query for chunk mappings, but maps aren't members of the AssetRegistry yet. static const int ChunkMapping[] = { 1, 2 }; -#if PLATFORM_PS4 -# define QUICKMATCH_SUPPORTED 1 -#elif PLATFORM_XBOXONE -# define QUICKMATCH_SUPPORTED 1 +#if PLATFORM_SWITCH +# define LOGIN_REQUIRED_FOR_ONLINE_PLAY 1 #else -# define QUICKMATCH_SUPPORTED 1 +# define LOGIN_REQUIRED_FOR_ONLINE_PLAY 0 +#endif + +#if PLATFORM_SWITCH +# define CONSOLE_LAN_SUPPORTED 1 +#else +# define CONSOLE_LAN_SUPPORTED 0 #endif FShooterMainMenu::~FShooterMainMenu() @@ -67,10 +72,11 @@ void FShooterMainMenu::Construct(TWeakObjectPtr _GameInsta // read user settings #if SHOOTER_CONSOLE_UI - bIsLanMatch = false; + bIsLanMatch = FParse::Param(FCommandLine::Get(), TEXT("forcelan")); #else UShooterGameUserSettings* const UserSettings = CastChecked(GEngine->GetGameUserSettings()); bIsLanMatch = UserSettings->IsLanMatch(); + bIsDedicatedServer = UserSettings->IsDedicatedServer(); #endif BotsCountOpt = 1; @@ -114,6 +120,9 @@ void FShooterMainMenu::Construct(TWeakObjectPtr _GameInsta MenuWidget.Reset(); MenuWidgetContainer.Reset(); + TArray Keys; + GConfig->GetSingleLineArray(TEXT("/Script/SwitchRuntimeSettings.SwitchRuntimeSettings"), TEXT("LeaderboardMap"), Keys, GEngineIni); + if (GEngine && GEngine->GameViewport) { SAssignNew(MenuWidget, SShooterMenuWidget) @@ -178,7 +187,7 @@ void FShooterMainMenu::Construct(TWeakObjectPtr _GameInsta MenuItem = MenuHelper::AddMenuItemSP(RootMenuItem, LOCTEXT("HostCustom", "HOST CUSTOM"), this, &FShooterMainMenu::OnHostOnlineSelected); // submenu under "HOST ONLINE" - MenuHelper::AddMenuItemSP(MenuItem, LOCTEXT("TDM", "TEAM DEATHMATCH"), this, &FShooterMainMenu::OnSplitScreenSelected); + MenuHelper::AddMenuItemSP(MenuItem, LOCTEXT("TDMLong", "TEAM DEATHMATCH"), this, &FShooterMainMenu::OnSplitScreenSelected); TSharedPtr NumberOfBotsOption = MenuHelper::AddMenuOptionSP(MenuItem, LOCTEXT("NumberOfBots", "NUMBER OF BOTS"), BotsCountList, this, &FShooterMainMenu::BotCountOptionChanged); NumberOfBotsOption->SelectedMultiChoice = BotsCountOpt; @@ -195,19 +204,17 @@ void FShooterMainMenu::Construct(TWeakObjectPtr _GameInsta MenuHelper::AddCustomMenuItem(JoinServerItem,SAssignNew(ServerListWidget,SShooterServerList).OwnerWidget(MenuWidget).PlayerOwner(GetPlayerOwner())); } -#if QUICKMATCH_SUPPORTED // QUICK MATCH menu option { MenuHelper::AddMenuItemSP(RootMenuItem, LOCTEXT("QuickMatch", "QUICK MATCH"), this, &FShooterMainMenu::OnQuickMatchSelected); } -#endif // HOST OFFLINE menu option { MenuItem = MenuHelper::AddMenuItemSP(RootMenuItem, LOCTEXT("PlayOffline", "PLAY OFFLINE"),this, &FShooterMainMenu::OnHostOfflineSelected); // submenu under "HOST OFFLINE" - MenuHelper::AddMenuItemSP(MenuItem, LOCTEXT("TDM", "TEAM DEATHMATCH"), this, &FShooterMainMenu::OnSplitScreenSelected); + MenuHelper::AddMenuItemSP(MenuItem, LOCTEXT("TDMLong", "TEAM DEATHMATCH"), this, &FShooterMainMenu::OnSplitScreenSelected); TSharedPtr NumberOfBotsOption = MenuHelper::AddMenuOptionSP(MenuItem, LOCTEXT("NumberOfBots", "NUMBER OF BOTS"), BotsCountList, this, &FShooterMainMenu::BotCountOptionChanged); NumberOfBotsOption->SelectedMultiChoice = BotsCountOpt; @@ -219,15 +226,23 @@ void FShooterMainMenu::Construct(TWeakObjectPtr _GameInsta // HOST ONLINE menu option { - MenuItem = MenuHelper::AddMenuItemSP(RootMenuItem, LOCTEXT("HostOnline", "HOST ONLINE"), this, &FShooterMainMenu::OnHostOnlineSelected); + HostOnlineMenuItem = MenuHelper::AddMenuItemSP(RootMenuItem, LOCTEXT("HostOnline", "HOST ONLINE"), this, &FShooterMainMenu::OnHostOnlineSelected); // submenu under "HOST ONLINE" - MenuHelper::AddMenuItemSP(MenuItem, LOCTEXT("TDM", "TEAM DEATHMATCH"), this, &FShooterMainMenu::OnSplitScreenSelected); +#if LOGIN_REQUIRED_FOR_ONLINE_PLAY + MenuHelper::AddMenuItemSP(HostOnlineMenuItem, LOCTEXT("TDMLong", "TEAM DEATHMATCH"), this, &FShooterMainMenu::OnSplitScreenSelectedHostOnlineLoginRequired); +#else + MenuHelper::AddMenuItemSP(HostOnlineMenuItem, LOCTEXT("TDMLong", "TEAM DEATHMATCH"), this, &FShooterMainMenu::OnSplitScreenSelectedHostOnline); +#endif - TSharedPtr NumberOfBotsOption = MenuHelper::AddMenuOptionSP(MenuItem, LOCTEXT("NumberOfBots", "NUMBER OF BOTS"), BotsCountList, this, &FShooterMainMenu::BotCountOptionChanged); + TSharedPtr NumberOfBotsOption = MenuHelper::AddMenuOptionSP(HostOnlineMenuItem, LOCTEXT("NumberOfBots", "NUMBER OF BOTS"), BotsCountList, this, &FShooterMainMenu::BotCountOptionChanged); NumberOfBotsOption->SelectedMultiChoice = BotsCountOpt; - HostOnlineMapOption = MenuHelper::AddMenuOption(MenuItem, LOCTEXT("SELECTED_LEVEL", "Map"), MapList); + HostOnlineMapOption = MenuHelper::AddMenuOption(HostOnlineMenuItem, LOCTEXT("SELECTED_LEVEL", "Map"), MapList); +#if CONSOLE_LAN_SUPPORTED + HostLANItem = MenuHelper::AddMenuOptionSP(HostOnlineMenuItem, LOCTEXT("LanMatch", "LAN"), OnOffList, this, &FShooterMainMenu::LanMatchChanged); + HostLANItem->SelectedMultiChoice = bIsLanMatch; +#endif } // HOST OFFLINE menu option @@ -235,7 +250,7 @@ void FShooterMainMenu::Construct(TWeakObjectPtr _GameInsta MenuItem = MenuHelper::AddMenuItemSP(RootMenuItem, LOCTEXT("HostOffline", "HOST OFFLINE"),this, &FShooterMainMenu::OnHostOfflineSelected); // submenu under "HOST OFFLINE" - MenuHelper::AddMenuItemSP(MenuItem, LOCTEXT("TDM", "TEAM DEATHMATCH"), this, &FShooterMainMenu::OnSplitScreenSelected); + MenuHelper::AddMenuItemSP(MenuItem, LOCTEXT("TDMLong", "TEAM DEATHMATCH"), this, &FShooterMainMenu::OnSplitScreenSelected); TSharedPtr NumberOfBotsOption = MenuHelper::AddMenuOptionSP(MenuItem, LOCTEXT("NumberOfBots", "NUMBER OF BOTS"), BotsCountList, this, &FShooterMainMenu::BotCountOptionChanged); NumberOfBotsOption->SelectedMultiChoice = BotsCountOpt; @@ -243,24 +258,35 @@ void FShooterMainMenu::Construct(TWeakObjectPtr _GameInsta HostOfflineMapOption = MenuHelper::AddMenuOption(MenuItem, LOCTEXT("SELECTED_LEVEL", "Map"), MapList); } -#if QUICKMATCH_SUPPORTED // QUICK MATCH menu option { +#if LOGIN_REQUIRED_FOR_ONLINE_PLAY + MenuHelper::AddMenuItemSP(RootMenuItem, LOCTEXT("QuickMatch", "QUICK MATCH"), this, &FShooterMainMenu::OnQuickMatchSelectedLoginRequired); +#else MenuHelper::AddMenuItemSP(RootMenuItem, LOCTEXT("QuickMatch", "QUICK MATCH"), this, &FShooterMainMenu::OnQuickMatchSelected); - } #endif + } // JOIN menu option { // JOIN menu option - MenuItem = MenuHelper::AddMenuItem(RootMenuItem, LOCTEXT("Join", "JOIN")); + MenuItem = MenuHelper::AddMenuItemSP(RootMenuItem, LOCTEXT("Join", "JOIN"), this, &FShooterMainMenu::OnJoinSelected); // submenu under "join" +#if LOGIN_REQUIRED_FOR_ONLINE_PLAY + MenuHelper::AddMenuItemSP(MenuItem, LOCTEXT("Server", "SERVER"), this, &FShooterMainMenu::OnJoinServerLoginRequired); +#else MenuHelper::AddMenuItemSP(MenuItem, LOCTEXT("Server", "SERVER"), this, &FShooterMainMenu::OnJoinServer); +#endif JoinMapOption = MenuHelper::AddMenuOption(MenuItem, LOCTEXT("SELECTED_LEVEL", "Map"), JoinMapList); // Server list widget that will be called up if appropriate MenuHelper::AddCustomMenuItem(JoinServerItem,SAssignNew(ServerListWidget,SShooterServerList).OwnerWidget(MenuWidget).PlayerOwner(GetPlayerOwner())); + +#if CONSOLE_LAN_SUPPORTED + JoinLANItem = MenuHelper::AddMenuOptionSP(MenuItem, LOCTEXT("LanMatch", "LAN"), OnOffList, this, &FShooterMainMenu::LanMatchChanged); + JoinLANItem->SelectedMultiChoice = bIsLanMatch; +#endif } #else @@ -269,8 +295,8 @@ void FShooterMainMenu::Construct(TWeakObjectPtr _GameInsta MenuItem = MenuHelper::AddMenuItem(RootMenuItem, LOCTEXT("Host", "HOST")); // submenu under "host" - MenuHelper::AddMenuItemSP(MenuItem, LOCTEXT("FFA", "FREE FOR ALL"), this, &FShooterMainMenu::OnUIHostFreeForAll); - MenuHelper::AddMenuItemSP(MenuItem, LOCTEXT("TDM", "TEAM DEATHMATCH"), this, &FShooterMainMenu::OnUIHostTeamDeathMatch); + MenuHelper::AddMenuItemSP(MenuItem, LOCTEXT("FFALong", "FREE FOR ALL"), this, &FShooterMainMenu::OnUIHostFreeForAll); + MenuHelper::AddMenuItemSP(MenuItem, LOCTEXT("TDMLong", "TEAM DEATHMATCH"), this, &FShooterMainMenu::OnUIHostTeamDeathMatch); TSharedPtr NumberOfBotsOption = MenuHelper::AddMenuOptionSP(MenuItem, LOCTEXT("NumberOfBots", "NUMBER OF BOTS"), BotsCountList, this, &FShooterMainMenu::BotCountOptionChanged); NumberOfBotsOption->SelectedMultiChoice = BotsCountOpt; @@ -291,15 +317,23 @@ void FShooterMainMenu::Construct(TWeakObjectPtr _GameInsta JoinLANItem = MenuHelper::AddMenuOptionSP(MenuItem, LOCTEXT("LanMatch", "LAN"), OnOffList, this, &FShooterMainMenu::LanMatchChanged); JoinLANItem->SelectedMultiChoice = bIsLanMatch; + DedicatedItem = MenuHelper::AddMenuOptionSP(MenuItem, LOCTEXT("Dedicated", "Dedicated"), OnOffList, this, &FShooterMainMenu::DedicatedServerChanged); + DedicatedItem->SelectedMultiChoice = bIsDedicatedServer; + // Server list widget that will be called up if appropriate MenuHelper::AddCustomMenuItem(JoinServerItem,SAssignNew(ServerListWidget,SShooterServerList).OwnerWidget(MenuWidget).PlayerOwner(GetPlayerOwner())); #endif // Leaderboards -#if !SHOOTER_CONSOLE_UI MenuHelper::AddMenuItemSP(RootMenuItem, LOCTEXT("Leaderboards", "LEADERBOARDS"), this, &FShooterMainMenu::OnShowLeaderboard); MenuHelper::AddCustomMenuItem(LeaderboardItem,SAssignNew(LeaderboardWidget,SShooterLeaderboard).OwnerWidget(MenuWidget).PlayerOwner(GetPlayerOwner())); + // Purchases + MenuHelper::AddMenuItemSP(RootMenuItem, LOCTEXT("Store", "ONLINE STORE"), this, &FShooterMainMenu::OnShowOnlineStore); + MenuHelper::AddCustomMenuItem(OnlineStoreItem, SAssignNew(OnlineStoreWidget, SShooterOnlineStore).OwnerWidget(MenuWidget).PlayerOwner(GetPlayerOwner())); + +#if !SHOOTER_CONSOLE_UI + // Demos { MenuHelper::AddMenuItemSP(RootMenuItem, LOCTEXT("Demos", "DEMOS"), this, &FShooterMainMenu::OnShowDemoBrowser); @@ -335,8 +369,9 @@ void FShooterMainMenu::AddMenuToGameViewport() { if (GEngine && GEngine->GameViewport) { - UGameViewportClient* const GVC = GEngine->GameViewport; + UGameViewportClient* GVC = GEngine->GameViewport; GVC->AddViewportWidgetContent(MenuWidgetContainer.ToSharedRef()); + GVC->SetCaptureMouseOnClick(EMouseCaptureMode::NoCapture); } } @@ -405,7 +440,7 @@ void FShooterMainMenu::Tick(float DeltaSeconds) if (bUpdateText) { - if (GameInstance.IsValid() && GameInstance->GetIsOnline() && HostOnlineMapOption.IsValid()) + if (GameInstance.IsValid() && GameInstance->GetOnlineMode() != EOnlineMode::Offline && HostOnlineMapOption.IsValid()) { HostOnlineMapOption->SetText(UpdatedText); } @@ -417,21 +452,11 @@ void FShooterMainMenu::Tick(float DeltaSeconds) } } -bool FShooterMainMenu::IsTickable() const -{ - return true; -} - TStatId FShooterMainMenu::GetStatId() const { RETURN_QUICK_DECLARE_CYCLE_STAT(FShooterMainMenu, STATGROUP_Tickables); } -bool FShooterMainMenu::IsTickableWhenPaused() const -{ - return true; -} - void FShooterMainMenu::OnMenuHidden() { #if SHOOTER_CONSOLE_UI @@ -446,6 +471,23 @@ void FShooterMainMenu::OnMenuHidden() } +void FShooterMainMenu::OnQuickMatchSelectedLoginRequired() +{ + IOnlineIdentityPtr Identity = IOnlineSubsystem::Get()->GetIdentityInterface(); + + int32 ControllerId = GetPlayerOwner()->GetControllerId(); + + OnLoginCompleteDelegateHandle = Identity->AddOnLoginCompleteDelegate_Handle(ControllerId, FOnLoginCompleteDelegate::CreateRaw(this, &FShooterMainMenu::OnLoginCompleteQuickmatch)); + Identity->Login(ControllerId, FOnlineAccountCredentials()); +} + +void FShooterMainMenu::OnLoginCompleteQuickmatch(int32 LocalUserNum, bool bWasSuccessful, const FUniqueNetId& UserId, const FString& Error) +{ + IOnlineSubsystem::Get()->GetIdentityInterface()->ClearOnLoginCompleteDelegate_Handle(LocalUserNum, OnLoginCompleteDelegateHandle); + + OnQuickMatchSelected(); +} + void FShooterMainMenu::OnQuickMatchSelected() { bQuickmatchSearchRequestCanceled = false; @@ -467,16 +509,15 @@ void FShooterMainMenu::OnUserCanPlayOnlineQuickMatch(const FUniqueNetId& UserId, { if (GameInstance.IsValid()) { - GameInstance->SetIsOnline(true); + GameInstance->SetOnlineMode(EOnlineMode::Online); } MatchType = EMatchType::Quick; SplitScreenLobbyWidget->SetIsJoining(false); - SplitScreenLobbyWidget->SetIsOnline(true); // Skip splitscreen for PS4 -#if PLATFORM_PS4 +#if PLATFORM_PS4 || MAX_LOCAL_PLAYERS == 1 BeginQuickMatchSearch(); #else UGameViewportClient* const GVC = GEngine->GameViewport; @@ -541,22 +582,39 @@ void FShooterMainMenu::BeginQuickMatchSearch() // Perform matchmaking with all local players TArray> LocalPlayerIds; - for (int i = 0; i < GameInstance->GetNumLocalPlayers(); ++i) + for (int32 i = 0; i < GameInstance->GetNumLocalPlayers(); ++i) { - auto PlayerId = GameInstance->GetLocalPlayerByIndex(i)->GetPreferredUniqueNetId(); + FUniqueNetIdRepl PlayerId = GameInstance->GetLocalPlayerByIndex(i)->GetPreferredUniqueNetId(); if (PlayerId.IsValid()) { - LocalPlayerIds.Add(PlayerId.ToSharedRef()); + LocalPlayerIds.Add((*PlayerId).AsShared()); } } - if (!Sessions->StartMatchmaking(LocalPlayerIds, GameSessionName, SessionSettings, QuickMatchSearchSettingsRef)) + if (!Sessions->StartMatchmaking(LocalPlayerIds, NAME_GameSession, SessionSettings, QuickMatchSearchSettingsRef)) { - OnMatchmakingComplete(GameSessionName, false); + OnMatchmakingComplete(NAME_GameSession, false); } } +void FShooterMainMenu::OnSplitScreenSelectedHostOnlineLoginRequired() +{ + IOnlineIdentityPtr Identity = IOnlineSubsystem::Get()->GetIdentityInterface(); + int32 ControllerId = GetPlayerOwner()->GetControllerId(); + + if (bIsLanMatch) + { + Identity->Logout(ControllerId); + OnSplitScreenSelected(); + } + else + { + OnLoginCompleteDelegateHandle = Identity->AddOnLoginCompleteDelegate_Handle(ControllerId, FOnLoginCompleteDelegate::CreateRaw(this, &FShooterMainMenu::OnLoginCompleteHostOnline)); + Identity->Login(ControllerId, FOnlineAccountCredentials()); + } +} + void FShooterMainMenu::OnSplitScreenSelected() { if (!IsMapReady()) @@ -565,58 +623,50 @@ void FShooterMainMenu::OnSplitScreenSelected() } RemoveMenuFromGameViewport(); -#if PLATFORM_PS4 - if (GameInstance.IsValid()) - { - if (GameInstance->GetIsOnline()) - { - OnUIHostTeamDeathMatch(); - } - else - { - UGameViewportClient* const GVC = GEngine->GameViewport; - GVC->AddViewportWidgetContent(SplitScreenLobbyWidgetContainer.ToSharedRef()); - SplitScreenLobbyWidget->Clear(); - FSlateApplication::Get().SetKeyboardFocus(SplitScreenLobbyWidget); - } +#if PLATFORM_PS4 || MAX_LOCAL_PLAYERS == 1 + if (GameInstance.IsValid() && GameInstance->GetOnlineMode() == EOnlineMode::Online) + { + OnUIHostTeamDeathMatch(); } -#else - UGameViewportClient* const GVC = GEngine->GameViewport; - GVC->AddViewportWidgetContent(SplitScreenLobbyWidgetContainer.ToSharedRef()); - - SplitScreenLobbyWidget->Clear(); - FSlateApplication::Get().SetKeyboardFocus(SplitScreenLobbyWidget); + else #endif + { + UGameViewportClient* const GVC = GEngine->GameViewport; + GVC->AddViewportWidgetContent(SplitScreenLobbyWidgetContainer.ToSharedRef()); + + SplitScreenLobbyWidget->Clear(); + FSlateApplication::Get().SetKeyboardFocus(SplitScreenLobbyWidget); + } } void FShooterMainMenu::OnHostOnlineSelected() { #if SHOOTER_CONSOLE_UI - if ( !ValidatePlayerForOnlinePlay(GetPlayerOwner()) ) + if (!ValidatePlayerIsSignedIn(GetPlayerOwner())) { return; } #endif - StartOnlinePrivilegeTask(IOnlineIdentity::FOnGetUserPrivilegeCompleteDelegate::CreateSP(this, &FShooterMainMenu::OnUserCanPlayOnlineHost)); + MatchType = EMatchType::Custom; + + EOnlineMode NewOnlineMode = bIsLanMatch ? EOnlineMode::LAN : EOnlineMode::Online; + if (GameInstance.IsValid()) + { + GameInstance->SetOnlineMode(NewOnlineMode); + } + SplitScreenLobbyWidget->SetIsJoining(false); + MenuWidget->EnterSubMenu(); } -void FShooterMainMenu::OnUserCanPlayOnlineHost(const FUniqueNetId& UserId, EUserPrivileges::Type Privilege, uint32 PrivilegeResults) +void FShooterMainMenu::OnUserCanPlayHostOnline(const FUniqueNetId& UserId, EUserPrivileges::Type Privilege, uint32 PrivilegeResults) { CleanupOnlinePrivilegeTask(); MenuWidget->LockControls(false); if (PrivilegeResults == (uint32)IOnlineIdentity::EPrivilegeResults::NoFailures) { - MatchType = EMatchType::Custom; - - if (GameInstance.IsValid()) - { - GameInstance->SetIsOnline(true); - } - SplitScreenLobbyWidget->SetIsJoining(false); - SplitScreenLobbyWidget->SetIsOnline(true); - MenuWidget->EnterSubMenu(); + OnSplitScreenSelected(); } else if (GameInstance.IsValid()) { @@ -624,18 +674,36 @@ void FShooterMainMenu::OnUserCanPlayOnlineHost(const FUniqueNetId& UserId, EUser } } +void FShooterMainMenu::OnLoginCompleteHostOnline(int32 LocalUserNum, bool bWasSuccessful, const FUniqueNetId& UserId, const FString& Error) +{ + IOnlineSubsystem::Get()->GetIdentityInterface()->ClearOnLoginCompleteDelegate_Handle(LocalUserNum, OnLoginCompleteDelegateHandle); + + OnSplitScreenSelectedHostOnline(); +} + +void FShooterMainMenu::OnSplitScreenSelectedHostOnline() +{ +#if SHOOTER_CONSOLE_UI + if (!ValidatePlayerForOnlinePlay(GetPlayerOwner())) + { + return; + } +#endif + + StartOnlinePrivilegeTask(IOnlineIdentity::FOnGetUserPrivilegeCompleteDelegate::CreateSP(this, &FShooterMainMenu::OnUserCanPlayHostOnline)); +} void FShooterMainMenu::StartOnlinePrivilegeTask(const IOnlineIdentity::FOnGetUserPrivilegeCompleteDelegate& Delegate) { if (GameInstance.IsValid()) { // Lock controls for the duration of the async task MenuWidget->LockControls(true); - TSharedPtr UserId; + FUniqueNetIdRepl UserId; if (PlayerOwner.IsValid()) { UserId = PlayerOwner->GetPreferredUniqueNetId(); } - GameInstance->StartOnlinePrivilegeTask(Delegate, EUserPrivileges::CanPlayOnline, UserId); + GameInstance->StartOnlinePrivilegeTask(Delegate, EUserPrivileges::CanPlayOnline, UserId.GetUniqueNetId()); } } @@ -651,12 +719,16 @@ void FShooterMainMenu::OnHostOfflineSelected() { MatchType = EMatchType::Custom; +#if LOGIN_REQUIRED_FOR_ONLINE_PLAY + IOnlineSubsystem::Get()->GetIdentityInterface()->Logout(GetPlayerOwner()->GetControllerId()); +#endif + if (GameInstance.IsValid()) { - GameInstance->SetIsOnline(false); + GameInstance->SetOnlineMode(EOnlineMode::Offline); } SplitScreenLobbyWidget->SetIsJoining( false ); - SplitScreenLobbyWidget->SetIsOnline( false ); + MenuWidget->EnterSubMenu(); } @@ -686,13 +758,25 @@ FReply FShooterMainMenu::OnSplitScreenPlay() FSlateApplication::Get().SetKeyboardFocus(MenuWidget); + // Grab the map filter if there is one + FString SelectedMapFilterName = TEXT("ANY"); + if (JoinMapOption.IsValid()) + { + int32 FilterChoice = JoinMapOption->SelectedMultiChoice; + if (FilterChoice != INDEX_NONE) + { + SelectedMapFilterName = JoinMapOption->MultiChoice[FilterChoice].ToString(); + } + } + + MenuWidget->NextMenu = JoinServerItem->SubMenu; - ServerListWidget->BeginServerSearch(bIsLanMatch, TEXT("ANY")); + ServerListWidget->BeginServerSearch(bIsLanMatch, bIsDedicatedServer, SelectedMapFilterName); ServerListWidget->UpdateServerList(); MenuWidget->EnterSubMenu(); #else SplitScreenLobbyWidget->NextMenu = JoinServerItem->SubMenu; - ServerListWidget->BeginServerSearch(bIsLanMatch, TEXT("ANY")); + ServerListWidget->BeginServerSearch(bIsLanMatch, bIsDedicatedServer, SelectedMapFilterName); ServerListWidget->UpdateServerList(); SplitScreenLobbyWidget->EnterSubMenu(); #endif @@ -747,7 +831,7 @@ void FShooterMainMenu::HelperQuickMatchSearchingUICancel(bool bShouldRemoveSessi FSlateApplication::Get().SetKeyboardFocus(QuickMatchStoppingWidgetContainer); OnCancelMatchmakingCompleteDelegateHandle = Sessions->AddOnCancelMatchmakingCompleteDelegate_Handle(OnCancelMatchmakingCompleteDelegate); - Sessions->CancelMatchmaking(*PlayerOwner->GetPreferredUniqueNetId(), GameSessionName); + Sessions->CancelMatchmaking(*PlayerOwner->GetPreferredUniqueNetId(), NAME_GameSession); } } else @@ -815,7 +899,7 @@ void FShooterMainMenu::OnMatchmakingComplete(FName SessionName, bool bWasSuccess { if (PlayerOwner.IsValid() && PlayerOwner->GetPreferredUniqueNetId().IsValid()) { - Sessions->DestroySession(GameSessionName); + Sessions->DestroySession(NAME_GameSession); } } return; @@ -889,7 +973,7 @@ void FShooterMainMenu::OnMatchmakingComplete(FName SessionName, bool bWasSuccess FShooterMainMenu::EMap FShooterMainMenu::GetSelectedMap() const { - if (GameInstance.IsValid() && GameInstance->GetIsOnline() && HostOnlineMapOption.IsValid()) + if (GameInstance.IsValid() && GameInstance->GetOnlineMode() != EOnlineMode::Offline && HostOnlineMapOption.IsValid()) { return (EMap)HostOnlineMapOption->SelectedMultiChoice; } @@ -914,10 +998,17 @@ void FShooterMainMenu::OnMenuGoBack(MenuPtr Menu) ShooterOptions->RevertChanges(); } + // In case a Play Together event was received, don't act on it + // if the player changes their mind. + if (HostOnlineMenuItem.IsValid() && HostOnlineMenuItem->SubMenu == Menu) + { + GameInstance->ResetPlayTogetherInfo(); + } + // if we've backed all the way out we need to make sure online is false. if (MenuWidget->GetMenuLevel() == 1) { - GameInstance->SetIsOnline(false); + GameInstance->SetOnlineMode(EOnlineMode::Offline); } } @@ -943,6 +1034,21 @@ void FShooterMainMenu::LanMatchChanged(TSharedPtr MenuItem, in bIsLanMatch = MultiOptionIndex > 0; UShooterGameUserSettings* UserSettings = CastChecked(GEngine->GetGameUserSettings()); UserSettings->SetLanMatch(bIsLanMatch); + + EOnlineMode NewOnlineMode = bIsLanMatch ? EOnlineMode::LAN : EOnlineMode::Online; + if (GameInstance.IsValid()) + { + GameInstance->SetOnlineMode(NewOnlineMode); + } +} + +void FShooterMainMenu::DedicatedServerChanged(TSharedPtr MenuItem, int32 MultiOptionIndex) +{ + check(DedicatedItem.IsValid()); + DedicatedItem->SelectedMultiChoice = MultiOptionIndex; + bIsDedicatedServer = MultiOptionIndex > 0; + UShooterGameUserSettings* UserSettings = CastChecked(GEngine->GetGameUserSettings()); + UserSettings->SetDedicatedServer(bIsDedicatedServer); } void FShooterMainMenu::RecordDemoChanged(TSharedPtr MenuItem, int32 MultiOptionIndex) @@ -977,7 +1083,7 @@ void FShooterMainMenu::OnUIHostFreeForAll() #if !SHOOTER_CONSOLE_UI if (GameInstance.IsValid()) { - GameInstance->SetIsOnline(true); + GameInstance->SetOnlineMode(bIsLanMatch ? EOnlineMode::LAN : EOnlineMode::Online); } #endif @@ -1008,7 +1114,7 @@ void FShooterMainMenu::OnUIHostTeamDeathMatch() #if !SHOOTER_CONSOLE_UI if (GameInstance.IsValid()) { - GameInstance->SetIsOnline(true); + GameInstance->SetOnlineMode(bIsLanMatch ? EOnlineMode::LAN : EOnlineMode::Online); } #endif @@ -1027,7 +1133,7 @@ void FShooterMainMenu::HostGame(const FString& GameType) { if (ensure(GameInstance.IsValid()) && GetPlayerOwner() != NULL) { - FString const StartURL = FString::Printf(TEXT("/Game/Maps/%s?game=%s%s%s?%s=%d%s"), *GetMapName(), *GameType, GameInstance->GetIsOnline() ? TEXT("?listen") : TEXT(""), bIsLanMatch ? TEXT("?bIsLanMatch") : TEXT(""), *AShooterGameMode::GetBotsCountOptionName(), BotsCountOpt, bIsRecordingDemo ? TEXT("?DemoRec") : TEXT("") ); + FString const StartURL = FString::Printf(TEXT("/Game/Maps/%s?game=%s%s%s?%s=%d%s"), *GetMapName(), *GameType, GameInstance->GetOnlineMode() != EOnlineMode::Offline ? TEXT("?listen") : TEXT(""), GameInstance->GetOnlineMode() == EOnlineMode::LAN ? TEXT("?bIsLanMatch") : TEXT(""), *AShooterGameMode::GetBotsCountOptionName(), BotsCountOpt, bIsRecordingDemo ? TEXT("?DemoRec") : TEXT("") ); // Game instance will handle success, failure and dialogs GameInstance->HostGame(GetPlayerOwner(), GameType, StartURL); @@ -1036,12 +1142,12 @@ void FShooterMainMenu::HostGame(const FString& GameType) void FShooterMainMenu::HostFreeForAll() { - HostGame(LOCTEXT("FFA", "FFA").ToString()); + HostGame(TEXT("FFA")); } void FShooterMainMenu::HostTeamDeathMatch() -{ - HostGame(LOCTEXT("TDM", "TDM").ToString()); +{ + HostGame(TEXT("TDM")); } FReply FShooterMainMenu::OnConfirm() @@ -1070,6 +1176,52 @@ bool FShooterMainMenu::ValidatePlayerForOnlinePlay(ULocalPlayer* LocalPlayer) return GameInstance->ValidatePlayerForOnlinePlay(LocalPlayer); } +bool FShooterMainMenu::ValidatePlayerIsSignedIn(ULocalPlayer* LocalPlayer) +{ + if (!ensure(GameInstance.IsValid())) + { + return false; + } + + return GameInstance->ValidatePlayerIsSignedIn(LocalPlayer); +} + +void FShooterMainMenu::OnJoinServerLoginRequired() +{ + IOnlineIdentityPtr Identity = IOnlineSubsystem::Get()->GetIdentityInterface(); + int32 ControllerId = GetPlayerOwner()->GetControllerId(); + + if (bIsLanMatch) + { + Identity->Logout(ControllerId); + OnUserCanPlayOnlineJoin(*GetPlayerOwner()->GetCachedUniqueNetId(), EUserPrivileges::CanPlayOnline, (uint32)IOnlineIdentity::EPrivilegeResults::NoFailures); + } + else + { + OnLoginCompleteDelegateHandle = Identity->AddOnLoginCompleteDelegate_Handle(ControllerId, FOnLoginCompleteDelegate::CreateRaw(this, &FShooterMainMenu::OnLoginCompleteJoin)); + Identity->Login(ControllerId, FOnlineAccountCredentials()); + } +} + +void FShooterMainMenu::OnLoginCompleteJoin(int32 LocalUserNum, bool bWasSuccessful, const FUniqueNetId& UserId, const FString& Error) +{ + IOnlineSubsystem::Get()->GetIdentityInterface()->ClearOnLoginCompleteDelegate_Handle(LocalUserNum, OnLoginCompleteDelegateHandle); + + OnJoinServer(); +} + +void FShooterMainMenu::OnJoinSelected() +{ +#if SHOOTER_CONSOLE_UI + if (!ValidatePlayerIsSignedIn(GetPlayerOwner())) + { + return; + } +#endif + + MenuWidget->EnterSubMenu(); +} + void FShooterMainMenu::OnJoinServer() { #if SHOOTER_CONSOLE_UI @@ -1095,12 +1247,12 @@ void FShooterMainMenu::OnUserCanPlayOnlineJoin(const FUniqueNetId& UserId, EUser if (GameInstance.IsValid()) { - GameInstance->SetIsOnline(true); + GameInstance->SetOnlineMode(bIsLanMatch ? EOnlineMode::LAN : EOnlineMode::Online); } MatchType = EMatchType::Custom; // Grab the map filter if there is one - FString SelectedMapFilterName = MapNames[0]; + FString SelectedMapFilterName("Any"); if( JoinMapOption.IsValid()) { int32 FilterChoice = JoinMapOption->SelectedMultiChoice; @@ -1112,13 +1264,13 @@ void FShooterMainMenu::OnUserCanPlayOnlineJoin(const FUniqueNetId& UserId, EUser #if SHOOTER_CONSOLE_UI UGameViewportClient* const GVC = GEngine->GameViewport; -#if PLATFORM_PS4 +#if PLATFORM_PS4 || MAX_LOCAL_PLAYERS == 1 // Show server menu (skip splitscreen) AddMenuToGameViewport(); FSlateApplication::Get().SetKeyboardFocus(MenuWidget); MenuWidget->NextMenu = JoinServerItem->SubMenu; - ServerListWidget->BeginServerSearch(bIsLanMatch, SelectedMapFilterName); + ServerListWidget->BeginServerSearch(bIsLanMatch, bIsDedicatedServer, SelectedMapFilterName); ServerListWidget->UpdateServerList(); MenuWidget->EnterSubMenu(); #else @@ -1135,7 +1287,7 @@ void FShooterMainMenu::OnUserCanPlayOnlineJoin(const FUniqueNetId& UserId, EUser MenuWidget->NextMenu = JoinServerItem->SubMenu; //FString SelectedMapFilterName = JoinMapOption->MultiChoice[JoinMapOption->SelectedMultiChoice].ToString(); - ServerListWidget->BeginServerSearch(bIsLanMatch, SelectedMapFilterName); + ServerListWidget->BeginServerSearch(bIsLanMatch, bIsDedicatedServer, SelectedMapFilterName); ServerListWidget->UpdateServerList(); MenuWidget->EnterSubMenu(); #endif @@ -1149,7 +1301,21 @@ void FShooterMainMenu::OnUserCanPlayOnlineJoin(const FUniqueNetId& UserId, EUser void FShooterMainMenu::OnShowLeaderboard() { MenuWidget->NextMenu = LeaderboardItem->SubMenu; +#if LOGIN_REQUIRED_FOR_ONLINE_PLAY + LeaderboardWidget->ReadStatsLoginRequired(); +#else LeaderboardWidget->ReadStats(); +#endif + MenuWidget->EnterSubMenu(); +} + +void FShooterMainMenu::OnShowOnlineStore() +{ + MenuWidget->NextMenu = OnlineStoreItem->SubMenu; +#if LOGIN_REQUIRED_FOR_ONLINE_PLAY + UE_LOG(LogOnline, Warning, TEXT("You need to be logged in before using the store")); +#endif + OnlineStoreWidget->BeginGettingOffers(); MenuWidget->EnterSubMenu(); } @@ -1224,6 +1390,12 @@ UShooterPersistentUser* FShooterMainMenu::GetPersistentUser() const return ShooterLocalPlayer ? ShooterLocalPlayer->GetPersistentUser() : nullptr; } +UWorld* FShooterMainMenu::GetTickableGameObjectWorld() const +{ + ULocalPlayer* LocalPlayerOwner = GetPlayerOwner(); + return (LocalPlayerOwner ? LocalPlayerOwner->GetWorld() : nullptr); +} + ULocalPlayer* FShooterMainMenu::GetPlayerOwner() const { return PlayerOwner.Get(); @@ -1254,4 +1426,10 @@ void FShooterMainMenu::OnCancelMatchmakingComplete(FName SessionName, bool bWasS FSlateApplication::Get().SetKeyboardFocus(MenuWidget); } +void FShooterMainMenu::OnPlayTogetherEventReceived() +{ + HostOnlineMenuItem->Widget->SetMenuItemActive(true); + MenuWidget->ConfirmMenuItem(); +} + #undef LOCTEXT_NAMESPACE diff --git a/Source/ShooterGame/Private/UI/Menu/ShooterMainMenu.h b/Source/ShooterGame/Private/UI/Menu/ShooterMainMenu.h index b2663dc..c06e284 100644 --- a/Source/ShooterGame/Private/UI/Menu/ShooterMainMenu.h +++ b/Source/ShooterGame/Private/UI/Menu/ShooterMainMenu.h @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once #include "SlateBasics.h" #include "SlateExtras.h" @@ -7,6 +7,7 @@ #include "Widgets/SShooterServerList.h" #include "Widgets/SShooterDemoList.h" #include "Widgets/SShooterLeaderboard.h" +#include "Widgets/SShooterOnlineStore.h" #include "Widgets/SShooterSplitScreenLobbyWidget.h" #include "ShooterOptions.h" @@ -28,9 +29,10 @@ class FShooterMainMenu : public TSharedFromThis, public FTicka /** TickableObject Functions */ virtual void Tick(float DeltaTime) override; - virtual bool IsTickable() const override; + virtual ETickableTickType GetTickableTickType() const override { return ETickableTickType::Always; } virtual TStatId GetStatId() const override; - virtual bool IsTickableWhenPaused() const override; + virtual bool IsTickableWhenPaused() const override { return true; } + virtual UWorld* GetTickableGameObjectWorld() const override; /** Returns the player that owns the main menu. */ ULocalPlayer* GetPlayerOwner() const; @@ -41,6 +43,9 @@ class FShooterMainMenu : public TSharedFromThis, public FTicka /** Returns the string name of the currently selected map */ FString GetMapName() const; + /** Called if a play together invite is sent from the PS4 system */ + void OnPlayTogetherEventReceived(); + protected: enum class EMap @@ -86,12 +91,18 @@ class FShooterMainMenu : public TSharedFromThis, public FTicka /** leaderboard widget */ TSharedPtr LeaderboardWidget; + /** online store widget */ + TSharedPtr OnlineStoreWidget; + /** custom menu */ TSharedPtr JoinServerItem; /** yet another custom menu */ TSharedPtr LeaderboardItem; + /** yet another custom menu */ + TSharedPtr OnlineStoreItem; + /** Custom demo browser menu */ TSharedPtr DemoBrowserItem; @@ -99,6 +110,9 @@ class FShooterMainMenu : public TSharedFromThis, public FTicka TSharedPtr HostLANItem; TSharedPtr JoinLANItem; + /** Dedicated Server Option */ + TSharedPtr DedicatedItem; + /** Record demo option */ TSharedPtr RecordDemoItem; @@ -110,6 +124,9 @@ class FShooterMainMenu : public TSharedFromThis, public FTicka TSharedPtr HostOnlineMapOption; TSharedPtr JoinMapOption; + /** Host an onine session menu */ + TSharedPtr HostOnlineMenuItem; + /** Track if we are showing a map download pct or not. */ bool bShowingDownloadPct; @@ -130,6 +147,15 @@ class FShooterMainMenu : public TSharedFromThis, public FTicka /** called when user chooses to start matchmaking. */ void OnQuickMatchSelected(); + /** called when user chooses to start matchmaking, but a login is required first. */ + void OnQuickMatchSelectedLoginRequired(); + + /** Called when user chooses split screen for the "host online" mode. Does some validation before moving on the split screen menu widget. */ + void OnSplitScreenSelectedHostOnlineLoginRequired(); + + /** Called when user chooses split screen for the "host online" mode.*/ + void OnSplitScreenSelectedHostOnline(); + /** called when user chooses split screen. Goes to the split screen setup screen. Hides menu widget*/ void OnSplitScreenSelected(); @@ -154,6 +180,9 @@ class FShooterMainMenu : public TSharedFromThis, public FTicka /** lan match option changed callback */ void LanMatchChanged(TSharedPtr MenuItem, int32 MultiOptionIndex); + /** dedicated server option changed callback */ + void DedicatedServerChanged(TSharedPtr MenuItem, int32 MultiOptionIndex); + /** record demo option changed callback */ void RecordDemoChanged(TSharedPtr MenuItem, int32 MultiOptionIndex); @@ -178,12 +207,24 @@ class FShooterMainMenu : public TSharedFromThis, public FTicka /** Returns true if owning player is online. Displays proper messaging if the user can't play */ bool ValidatePlayerForOnlinePlay(ULocalPlayer* LocalPlayer); + /** Returns true if owning player is signed in to an account. Displays proper messaging if the user can't play */ + bool ValidatePlayerIsSignedIn(ULocalPlayer* LocalPlayer); + + /** Called when the join menu option is chosen */ + void OnJoinSelected(); + + /** Join server, but login necessary first. */ + void OnJoinServerLoginRequired(); + /** Join server */ void OnJoinServer(); /** Show leaderboard */ void OnShowLeaderboard(); + /** Show online store */ + void OnShowOnlineStore(); + /** Show demo browser */ void OnShowDemoBrowser(); @@ -224,7 +265,7 @@ class FShooterMainMenu : public TSharedFromThis, public FTicka void CleanupOnlinePrivilegeTask(); /** Delegate function executed after checking privileges for hosting an online game */ - void OnUserCanPlayOnlineHost(const FUniqueNetId& UserId, EUserPrivileges::Type Privilege, uint32 PrivilegeResults); + void OnUserCanPlayHostOnline(const FUniqueNetId& UserId, EUserPrivileges::Type Privilege, uint32 PrivilegeResults); /** Delegate function executed after checking privileges for joining an online game */ void OnUserCanPlayOnlineJoin(const FUniqueNetId& UserId, EUserPrivileges::Type Privilege, uint32 PrivilegeResults); @@ -238,6 +279,15 @@ class FShooterMainMenu : public TSharedFromThis, public FTicka /** Delegate function executed when the quick match async cancel operation is complete */ void OnCancelMatchmakingComplete(FName SessionName, bool bWasSuccessful); + /** Delegate function executed when login completes before an online match is created */ + void OnLoginCompleteHostOnline(int32 LocalUserNum, bool bWasSuccessful, const FUniqueNetId& UserId, const FString& Error); + + /** Delegate function executed when login completes before an online match is joined */ + void OnLoginCompleteJoin(int32 LocalUserNum, bool bWasSuccessful, const FUniqueNetId& UserId, const FString& Error); + + /** Delegate function executed when login completes before quickmatch is started */ + void OnLoginCompleteQuickmatch(int32 LocalUserNum, bool bWasSuccessful, const FUniqueNetId& UserId, const FString& Error); + /** Delegate for canceling matchmaking */ FOnCancelMatchmakingCompleteDelegate OnCancelMatchmakingCompleteDelegate; @@ -273,6 +323,9 @@ class FShooterMainMenu : public TSharedFromThis, public FTicka /** Was input used to cancel the search request for quickmatch? */ bool bUsedInputToCancelQuickmatchSearch; + /** Dedicated server? */ + bool bIsDedicatedServer; + /** used for displaying the quickmatch confirmation dialog when a quickmatch to join is not found */ TSharedPtr QuickMatchFailureWidget; @@ -298,4 +351,5 @@ class FShooterMainMenu : public TSharedFromThis, public FTicka FDelegateHandle OnMatchmakingCompleteDelegateHandle; FDelegateHandle OnCancelMatchmakingCompleteDelegateHandle; + FDelegateHandle OnLoginCompleteDelegateHandle; }; diff --git a/Source/ShooterGame/Private/UI/Menu/ShooterMessageMenu.cpp b/Source/ShooterGame/Private/UI/Menu/ShooterMessageMenu.cpp index 930c511..f053734 100644 --- a/Source/ShooterGame/Private/UI/Menu/ShooterMessageMenu.cpp +++ b/Source/ShooterGame/Private/UI/Menu/ShooterMessageMenu.cpp @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ShooterGame.h" #include "ShooterStyle.h" diff --git a/Source/ShooterGame/Private/UI/Menu/ShooterMessageMenu.h b/Source/ShooterGame/Private/UI/Menu/ShooterMessageMenu.h index 4a7246c..922e63e 100644 --- a/Source/ShooterGame/Private/UI/Menu/ShooterMessageMenu.h +++ b/Source/ShooterGame/Private/UI/Menu/ShooterMessageMenu.h @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once #include "SlateBasics.h" #include "SlateExtras.h" diff --git a/Source/ShooterGame/Private/UI/Menu/ShooterOptions.cpp b/Source/ShooterGame/Private/UI/Menu/ShooterOptions.cpp index ddf7628..86ec955 100644 --- a/Source/ShooterGame/Private/UI/Menu/ShooterOptions.cpp +++ b/Source/ShooterGame/Private/UI/Menu/ShooterOptions.cpp @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ShooterGame.h" #include "ShooterOptions.h" @@ -86,10 +86,15 @@ void FShooterOptions::Construct(ULocalPlayer* InPlayerOwner) GammaOption = MenuHelper::AddMenuOptionSP(OptionsItem,LOCTEXT("Gamma", "GAMMA CORRECTION"),GammaList, this, &FShooterOptions::GammaOptionChanged); AimSensitivityOption = MenuHelper::AddMenuOptionSP(OptionsItem,LOCTEXT("AimSensitivity", "AIM SENSITIVITY"),SensitivityList, this, &FShooterOptions::AimSensitivityOptionChanged); InvertYAxisOption = MenuHelper::AddMenuOptionSP(OptionsItem,LOCTEXT("InvertYAxis", "INVERT Y AXIS"),OnOffList, this, &FShooterOptions::InvertYAxisOptionChanged); + VibrationOption = MenuHelper::AddMenuOptionSP(OptionsItem, LOCTEXT("Vibration", "VIBRATION"), OnOffList, this, &FShooterOptions::ToggleVibration); + MenuHelper::AddMenuItemSP(OptionsItem,LOCTEXT("ApplyChanges", "APPLY CHANGES"), this, &FShooterOptions::OnApplySettings); //Do not allow to set aim sensitivity to 0 AimSensitivityOption->MinMultiChoiceIndex = MinSensitivity; + + //Default vibration to On. + VibrationOption->SelectedMultiChoice = 1; UserSettings = CastChecked(GEngine->GetGameUserSettings()); ResolutionOpt = UserSettings->GetScreenResolution(); @@ -102,13 +107,34 @@ void FShooterOptions::Construct(ULocalPlayer* InPlayerOwner) bInvertYAxisOpt = PersistentUser->GetInvertedYAxis(); SensitivityOpt = PersistentUser->GetAimSensitivity(); GammaOpt = PersistentUser->GetGamma(); + bVibrationOpt = PersistentUser->GetVibration(); } else { + bVibrationOpt = true; bInvertYAxisOpt = false; SensitivityOpt = 1.0f; GammaOpt = 2.2f; } + + if (ensure(PlayerOwner != nullptr)) + { + APlayerController* BaseController = Cast(UGameplayStatics::GetPlayerController(PlayerOwner->GetWorld(), GetOwnerUserIndex())); + AShooterPlayerController* ShooterPlayerController = Cast(UGameplayStatics::GetPlayerController(PlayerOwner->GetWorld(), GetOwnerUserIndex())); + ensure(BaseController); + if (BaseController) + { + if (ShooterPlayerController) + { + ShooterPlayerController->SetIsVibrationEnabled(bVibrationOpt); + } + else + { + // We are in the menus and therefore don't need to do anything as the controller is different + // and can't store the vibration setting. + } + } + } } void FShooterOptions::OnApplySettings() @@ -125,6 +151,7 @@ void FShooterOptions::ApplySettings() PersistentUser->SetAimSensitivity(SensitivityOpt); PersistentUser->SetInvertedYAxis(bInvertYAxisOpt); PersistentUser->SetGamma(GammaOpt); + PersistentUser->SetVibration(bVibrationOpt); PersistentUser->TellInputAboutKeybindings(); PersistentUser->SaveIfDirty(); @@ -251,6 +278,7 @@ void FShooterOptions::UpdateOptions() bInvertYAxisOpt = PersistentUser->GetInvertedYAxis(); SensitivityOpt = PersistentUser->GetAimSensitivity(); GammaOpt = PersistentUser->GetGamma(); + bVibrationOpt = PersistentUser->GetVibration(); } InvertYAxisOption->SelectedMultiChoice = GetCurrentMouseYAxisInvertedIndex(); @@ -300,7 +328,7 @@ void FShooterOptions::InfiniteClipOptionChanged(TSharedPtr Men void FShooterOptions::FreezeTimerOptionChanged(TSharedPtr MenuItem, int32 MultiOptionIndex) { UWorld* const World = PlayerOwner->GetWorld(); - AShooterGameState* const GameState = World ? Cast(World->GameState) : nullptr; + AShooterGameState* const GameState = World ? World->GetGameState() : nullptr; if (GameState) { GameState->bTimerPaused = MultiOptionIndex > 0 ? true : false; @@ -352,6 +380,26 @@ void FShooterOptions::GammaOptionChanged(TSharedPtr MenuItem, GEngine->DisplayGamma = GammaOpt; } +void FShooterOptions::ToggleVibration(TSharedPtr MenuItem, int32 MultiOptionIndex) +{ + bVibrationOpt = MultiOptionIndex > 0 ? true : false; + APlayerController* BaseController = Cast(UGameplayStatics::GetPlayerController(PlayerOwner->GetWorld(), GetOwnerUserIndex())); + AShooterPlayerController* ShooterPlayerController = Cast(UGameplayStatics::GetPlayerController(PlayerOwner->GetWorld(), GetOwnerUserIndex())); + ensure(BaseController); + if(BaseController) + { + if (ShooterPlayerController) + { + ShooterPlayerController->SetIsVibrationEnabled(bVibrationOpt); + } + else + { + // We are in the menus and therefore don't need to do anything as the controller is different + // and can't store the vibration setting. + } + } +} + void FShooterOptions::InvertYAxisOptionChanged(TSharedPtr MenuItem, int32 MultiOptionIndex) { bInvertYAxisOpt = MultiOptionIndex > 0 ? true : false; diff --git a/Source/ShooterGame/Private/UI/Menu/ShooterOptions.h b/Source/ShooterGame/Private/UI/Menu/ShooterOptions.h index ccf94b1..d81b455 100644 --- a/Source/ShooterGame/Private/UI/Menu/ShooterOptions.h +++ b/Source/ShooterGame/Private/UI/Menu/ShooterOptions.h @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once #include "SlateBasics.h" #include "SlateExtras.h" @@ -71,6 +71,9 @@ class FShooterOptions : public TSharedFromThis /** aim sensitivity option changed handler */ void AimSensitivityOptionChanged(TSharedPtr MenuItem, int32 MultiOptionIndex); + /** controller vibration toggle handler */ + void ToggleVibration(TSharedPtr MenuItem, int32 MultiOptionIndex); + /** invert y axis option changed handler */ void InvertYAxisOptionChanged(TSharedPtr MenuItem, int32 MultiOptionIndex); @@ -101,6 +104,9 @@ class FShooterOptions : public TSharedFromThis /** Owning player controller */ ULocalPlayer* PlayerOwner; + /** holds vibration option menu item */ + TSharedPtr VibrationOption; + /** holds invert y axis option menu item */ TSharedPtr InvertYAxisOption; @@ -134,6 +140,9 @@ class FShooterOptions : public TSharedFromThis /** full screen setting set in options */ EWindowMode::Type bFullScreenOpt; + /** controller vibration setting set in options */ + uint8 bVibrationOpt : 1; + /** invert mouse setting set in options */ uint8 bInvertYAxisOpt : 1; diff --git a/Source/ShooterGame/Private/UI/Menu/ShooterRecentlyMet.cpp b/Source/ShooterGame/Private/UI/Menu/ShooterRecentlyMet.cpp index d3421f6..960e347 100644 --- a/Source/ShooterGame/Private/UI/Menu/ShooterRecentlyMet.cpp +++ b/Source/ShooterGame/Private/UI/Menu/ShooterRecentlyMet.cpp @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ShooterGame.h" #include "ShooterRecentlyMet.h" @@ -79,7 +79,7 @@ void FShooterRecentlyMet::UpdateRecentlyMet(int32 NewOwnerIndex) MenuHelper::ClearSubMenu(RecentlyMetItem); MaxRecentlyMetIndex = 0; - AShooterGameState* const MyGameState = Cast(PlayerOwner->GetWorld()->GameState); + AShooterGameState* const MyGameState = PlayerOwner->GetWorld()->GetGameState(); if (MyGameState != nullptr) { PlayerArray = MyGameState->PlayerArray; @@ -133,7 +133,7 @@ void FShooterRecentlyMet::ViewSelectedUsersProfile() auto ExternalUI = Online::GetExternalUIInterface(); if (ExternalUI.IsValid() && Requestor.IsValid() && Requestee.IsValid()) { - ExternalUI->ShowProfileUI(*Requestor, *Requestee, IOnlineExternalUI::FOnProfileUIClosedDelegate()); + ExternalUI->ShowProfileUI(*Requestor, *Requestee, FOnProfileUIClosedDelegate()); } } } diff --git a/Source/ShooterGame/Private/UI/Menu/ShooterRecentlyMet.h b/Source/ShooterGame/Private/UI/Menu/ShooterRecentlyMet.h index 06410c9..24bbcff 100644 --- a/Source/ShooterGame/Private/UI/Menu/ShooterRecentlyMet.h +++ b/Source/ShooterGame/Private/UI/Menu/ShooterRecentlyMet.h @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once #include "SlateBasics.h" #include "SlateExtras.h" diff --git a/Source/ShooterGame/Private/UI/Menu/ShooterWelcomeMenu.cpp b/Source/ShooterGame/Private/UI/Menu/ShooterWelcomeMenu.cpp index 412480d..270b073 100644 --- a/Source/ShooterGame/Private/UI/Menu/ShooterWelcomeMenu.cpp +++ b/Source/ShooterGame/Private/UI/Menu/ShooterWelcomeMenu.cpp @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ShooterGame.h" #include "ShooterWelcomeMenu.h" @@ -21,6 +21,8 @@ class SShooterWelcomeMenuWidget : public SCompoundWidget /** The actual curve that animates the text. */ FCurveHandle TextColorCurve; + TSharedPtr PressPlayText; + SLATE_BEGIN_ARGS( SShooterWelcomeMenuWidget ) {} @@ -33,16 +35,6 @@ class SShooterWelcomeMenuWidget : public SCompoundWidget return true; } - /** - * Gets the text color based on the current state of the animation. - * - * @return The text color based on the current state of the animation. - */ - FSlateColor GetTextColor() const - { - return FSlateColor(FMath::Lerp(FLinearColor(0.0f,0.0f,0.0f,1.0f), FLinearColor(0.5f,0.5f,0.5f,1.0f), TextColorCurve.GetLerp())); - } - void Construct( const FArguments& InArgs ) { MenuOwner = InArgs._MenuOwner; @@ -58,14 +50,17 @@ class SShooterWelcomeMenuWidget : public SCompoundWidget .VAlign(VAlign_Center) .HAlign(HAlign_Center) [ - SNew( STextBlock ) + SAssignNew( PressPlayText, SRichTextBlock ) #if PLATFORM_PS4 .Text( LOCTEXT("PressStartPS4", "PRESS CROSS BUTTON TO PLAY" ) ) +#elif PLATFORM_SWITCH + .Text(LOCTEXT("PressStartSwitch", "PRESS TO PLAY")) #else .Text( LOCTEXT("PressStartXboxOne", "PRESS A TO PLAY" ) ) #endif - .ColorAndOpacity(this, &SShooterWelcomeMenuWidget::GetTextColor) .TextStyle( FShooterStyle::Get(), "ShooterGame.WelcomeScreen.WelcomeTextStyle" ) + .DecoratorStyleSet(&FShooterStyle::Get()) + + SRichTextBlock::ImageDecorator() ] ]; } @@ -83,6 +78,8 @@ class SShooterWelcomeMenuWidget : public SCompoundWidget TextAnimation.Play(this->AsShared()); } } + + PressPlayText->SetRenderOpacity(FMath::Lerp(0.5f, 1.0f, TextColorCurve.GetLerp())); } virtual FReply OnMouseButtonDown(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent) override @@ -95,9 +92,19 @@ class SShooterWelcomeMenuWidget : public SCompoundWidget const FKey Key = InKeyEvent.GetKey(); if (Key == EKeys::Enter) { - MenuOwner->HandleLoginUIClosed(TSharedPtr(), 0); + TSharedPtr UserId; + const auto OnlineSub = IOnlineSubsystem::Get(); + if (OnlineSub) + { + const auto IdentityInterface = OnlineSub->GetIdentityInterface(); + if (IdentityInterface.IsValid()) + { + UserId = IdentityInterface->GetUniquePlayerId(InKeyEvent.GetUserIndex()); + } + } + MenuOwner->HandleLoginUIClosed(UserId, InKeyEvent.GetUserIndex()); } - else if (!MenuOwner->GetControlsLocked() && Key == EKeys::Gamepad_FaceButton_Bottom) + else if (!MenuOwner->GetControlsLocked() && Key == EKeys::Virtual_Accept) { bool bSkipToMainMenu = true; @@ -118,7 +125,7 @@ class SShooterWelcomeMenuWidget : public SCompoundWidget const auto ExternalUI = OnlineSub->GetExternalUIInterface(); if (ExternalUI.IsValid()) { - ExternalUI->ShowLoginUI(InKeyEvent.GetUserIndex(), false, IOnlineExternalUI::FOnLoginUIClosedDelegate::CreateSP(MenuOwner, &FShooterWelcomeMenu::HandleLoginUIClosed)); + ExternalUI->ShowLoginUI(InKeyEvent.GetUserIndex(), false, true, FOnLoginUIClosedDelegate::CreateSP(MenuOwner, &FShooterWelcomeMenu::HandleLoginUIClosed)); bSkipToMainMenu = false; } } @@ -181,7 +188,7 @@ void FShooterWelcomeMenu::RemoveFromGameViewport() } } -void FShooterWelcomeMenu::HandleLoginUIClosed(TSharedPtr UniqueId, const int ControllerIndex) +void FShooterWelcomeMenu::HandleLoginUIClosed(TSharedPtr UniqueId, const int ControllerIndex, const FOnlineError& Error) { if ( !ensure( GameInstance.IsValid() ) ) { @@ -256,7 +263,7 @@ void FShooterWelcomeMenu::SetControllerAndAdvanceToMainMenu(const int Controller if ( NewPlayerOwner != nullptr && ControllerIndex != -1 ) { NewPlayerOwner->SetControllerId(ControllerIndex); - NewPlayerOwner->SetCachedUniqueNetId(NewPlayerOwner->GetUniqueNetIdFromCachedControllerId()); + NewPlayerOwner->SetCachedUniqueNetId(NewPlayerOwner->GetUniqueNetIdFromCachedControllerId().GetUniqueNetId()); // tell gameinstance to transition to main menu GameInstance->GotoState(ShooterGameInstanceState::MainMenu); diff --git a/Source/ShooterGame/Private/UI/Menu/ShooterWelcomeMenu.h b/Source/ShooterGame/Private/UI/Menu/ShooterWelcomeMenu.h index 753990f..8db5474 100644 --- a/Source/ShooterGame/Private/UI/Menu/ShooterWelcomeMenu.h +++ b/Source/ShooterGame/Private/UI/Menu/ShooterWelcomeMenu.h @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once #include "SlateBasics.h" #include "SlateExtras.h" @@ -21,7 +21,7 @@ class FShooterWelcomeMenu : public TSharedFromThis * @param UniqueId The unique Id of the user who just signed in. * @param ControllerIndex The controller index of the player who just signed in. */ - void HandleLoginUIClosed(TSharedPtr UniqueId, const int ControllerIndex); + void HandleLoginUIClosed(TSharedPtr UniqueId, const int ControllerIndex, const FOnlineError& Error = FOnlineError()); /** * Called when a user needs to advance from the welcome screen to the main menu. diff --git a/Source/ShooterGame/Private/UI/Menu/Widgets/SShooterDemoList.cpp b/Source/ShooterGame/Private/UI/Menu/Widgets/SShooterDemoList.cpp index 864900c..687d113 100644 --- a/Source/ShooterGame/Private/UI/Menu/Widgets/SShooterDemoList.cpp +++ b/Source/ShooterGame/Private/UI/Menu/Widgets/SShooterDemoList.cpp @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ShooterGame.h" #include "SShooterDemoList.h" @@ -96,13 +96,13 @@ void SShooterDemoList::Construct(const FArguments& InArgs) BuildDemoList(); } -void SShooterDemoList::OnEnumerateStreamsComplete(const TArray& Streams) +void SShooterDemoList::OnEnumerateStreamsComplete(const FEnumerateStreamsResult& Result ) { check(bUpdatingDemoList); // should not be called otherwise bool bFinished = true; - for ( const auto& StreamInfo : Streams ) + for ( const auto& StreamInfo : Result.FoundStreams ) { float SizeInKilobytes = StreamInfo.SizeInBytes / 1024.0f; @@ -166,9 +166,11 @@ void SShooterDemoList::OnShowAllReplaysChecked(ECheckBoxState NewCheckedState) { EnumerateStreamsVersion = FNetworkVersion::GetReplayVersion(); + // Always set CL to 0, we only want to ever check NetworkVersion (now that we have backwards compat) + EnumerateStreamsVersion.Changelist = 0; + if (NewCheckedState == ECheckBoxState::Checked) { - EnumerateStreamsVersion.Changelist = 0; EnumerateStreamsVersion.NetworkVersion = 0; } @@ -183,7 +185,7 @@ void SShooterDemoList::BuildDemoList() if ( ReplayStreamer.IsValid() ) { - ReplayStreamer->EnumerateStreams(EnumerateStreamsVersion, FString(), FString(), FOnEnumerateStreamsComplete::CreateSP(this, &SShooterDemoList::OnEnumerateStreamsComplete)); + ReplayStreamer->EnumerateStreams(EnumerateStreamsVersion, FString(), FString(), FEnumerateStreamsCallback::CreateSP(this, &SShooterDemoList::OnEnumerateStreamsComplete)); } } @@ -263,7 +265,7 @@ FReply SShooterDemoList::OnDemoDeleteConfirm() bUpdatingDemoList = true; DemoList.Empty(); - ReplayStreamer->DeleteFinishedStream(SelectedItem->StreamInfo.Name, FOnDeleteFinishedStreamComplete::CreateSP(this, &SShooterDemoList::OnDeleteFinishedStreamComplete)); + ReplayStreamer->DeleteFinishedStream(SelectedItem->StreamInfo.Name, FDeleteFinishedStreamCallback::CreateSP(this, &SShooterDemoList::OnDeleteFinishedStreamComplete)); } UShooterGameInstance* const GI = Cast(PlayerOwner->GetGameInstance()); @@ -298,7 +300,7 @@ FReply SShooterDemoList::OnDemoDeleteCancel() return FReply::Handled(); } -void SShooterDemoList::OnDeleteFinishedStreamComplete(bool bWasSuccessful) +void SShooterDemoList::OnDeleteFinishedStreamComplete(const FDeleteFinishedStreamResult& Result) { BuildDemoList(); } @@ -347,7 +349,7 @@ FReply SShooterDemoList::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& FReply Result = FReply::Unhandled(); const FKey Key = InKeyboardEvent.GetKey(); - if (Key == EKeys::Enter || Key == EKeys::Gamepad_FaceButton_Bottom) + if (Key == EKeys::Enter || Key == EKeys::Virtual_Accept) { PlayDemo(); Result = FReply::Handled(); diff --git a/Source/ShooterGame/Private/UI/Menu/Widgets/SShooterDemoList.h b/Source/ShooterGame/Private/UI/Menu/Widgets/SShooterDemoList.h index 2f9e56c..f074dce 100644 --- a/Source/ShooterGame/Private/UI/Menu/Widgets/SShooterDemoList.h +++ b/Source/ShooterGame/Private/UI/Menu/Widgets/SShooterDemoList.h @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once @@ -7,6 +7,7 @@ #include "ShooterGame.h" #include "SShooterMenuWidget.h" #include "NetworkReplayStreaming.h" +#include "Misc/NetworkVersion.h" struct FDemoEntry; @@ -56,7 +57,7 @@ class SShooterDemoList : public SShooterMenuWidget void OnBuildDemoListFinished(); /** Called when we get results from the replay streaming interface */ - void OnEnumerateStreamsComplete(const TArray& Streams); + void OnEnumerateStreamsComplete(const FEnumerateStreamsResult& Result); /** Play chosen demo */ void PlayDemo(); @@ -71,7 +72,7 @@ class SShooterDemoList : public SShooterMenuWidget FReply OnDemoDeleteCancel(); /** Called by delegate when the replay streaming interface has finished deleting */ - void OnDeleteFinishedStreamComplete(bool bWasSuccessful); + void OnDeleteFinishedStreamComplete(const FDeleteFinishedStreamResult& Result); /** selects item at current + MoveBy index */ void MoveSelection(int32 MoveBy); diff --git a/Source/ShooterGame/Private/UI/Menu/Widgets/SShooterLeaderboard.cpp b/Source/ShooterGame/Private/UI/Menu/Widgets/SShooterLeaderboard.cpp index 9b7aa44..0f99748 100644 --- a/Source/ShooterGame/Private/UI/Menu/Widgets/SShooterLeaderboard.cpp +++ b/Source/ShooterGame/Private/UI/Menu/Widgets/SShooterLeaderboard.cpp @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ShooterGame.h" #include "SShooterLeaderboard.h" @@ -7,6 +7,8 @@ #if PLATFORM_XBOXONE #define INTERACTIVE_LEADERBOARD 1 +#else +#define INTERACTIVE_LEADERBOARD 0 #endif FLeaderboardRow::FLeaderboardRow(const FOnlineStatsRow& Row) @@ -76,6 +78,32 @@ void SShooterLeaderboard::Construct(const FArguments& InArgs) ]; } +void SShooterLeaderboard::ReadStatsLoginRequired() +{ + if (!OnLoginCompleteDelegateHandle.IsValid()) + { + IOnlineSubsystem* const OnlineSub = IOnlineSubsystem::Get(); + if (OnlineSub) + { + IOnlineIdentityPtr Identity = OnlineSub->GetIdentityInterface(); + if (Identity.IsValid()) + { + OnLoginCompleteDelegateHandle = Identity->AddOnLoginCompleteDelegate_Handle(0, FOnLoginCompleteDelegate::CreateRaw(this, &SShooterLeaderboard::OnLoginCompleteReadStats)); + Identity->Login(0, FOnlineAccountCredentials()); + } + } + } +} + +void SShooterLeaderboard::OnLoginCompleteReadStats(int32 LocalUserNum, bool bWasSuccessful, const FUniqueNetId& UserId, const FString& Error) +{ + IOnlineSubsystem::Get()->GetIdentityInterface()->ClearOnLoginCompleteDelegate_Handle(LocalUserNum, OnLoginCompleteDelegateHandle); + if (bWasSuccessful) + { + ReadStats(); + } +} + /** Starts reading leaderboards for the game */ void SShooterLeaderboard::ReadStats() { @@ -171,12 +199,12 @@ bool SShooterLeaderboard::ProfileUIOpened() const if( IsPlayerSelectedAndValid() ) { check( PlayerOwner.IsValid() ); - const TSharedPtr OwnerNetId = PlayerOwner->GetPreferredUniqueNetId(); + FUniqueNetIdRepl OwnerNetId = PlayerOwner->GetPreferredUniqueNetId(); check( OwnerNetId.IsValid() ); const TSharedPtr& PlayerId = SelectedItem->PlayerId; check( PlayerId.IsValid() ); - return ShooterUIHelpers::Get().ProfileOpenedUI(*OwnerNetId.Get(), *PlayerId.Get(), NULL); + return ShooterUIHelpers::Get().ProfileOpenedUI(*OwnerNetId, *PlayerId.Get(), NULL); } return false; } @@ -205,7 +233,7 @@ FReply SShooterLeaderboard::OnKeyDown(const FGeometry& MyGeometry, const FKeyEve MoveSelection(1); Result = FReply::Handled(); } - else if (Key == EKeys::Escape || Key == EKeys::Gamepad_FaceButton_Right || Key == EKeys::Gamepad_Special_Left) + else if (Key == EKeys::Escape || Key == EKeys::Virtual_Back || Key == EKeys::Gamepad_Special_Left) { if (bReadingStats) { @@ -213,7 +241,7 @@ FReply SShooterLeaderboard::OnKeyDown(const FGeometry& MyGeometry, const FKeyEve bReadingStats = false; } } - else if (Key == EKeys::Enter || Key == EKeys::Gamepad_FaceButton_Bottom) + else if (Key == EKeys::Enter || Key == EKeys::Virtual_Accept) { // Open the profile UI of the selected item ProfileUIOpened(); diff --git a/Source/ShooterGame/Private/UI/Menu/Widgets/SShooterLeaderboard.h b/Source/ShooterGame/Private/UI/Menu/Widgets/SShooterLeaderboard.h index 842a56b..8a4e78b 100644 --- a/Source/ShooterGame/Private/UI/Menu/Widgets/SShooterLeaderboard.h +++ b/Source/ShooterGame/Private/UI/Menu/Widgets/SShooterLeaderboard.h @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once @@ -77,6 +77,12 @@ class SShooterLeaderboard : public SCompoundWidget /** Called on a particular leaderboard read */ void OnStatsRead(bool bWasSuccessful); + /** Called to login on relevant platforms first before making a leaderboard read */ + void ReadStatsLoginRequired(); + + /** Delegate after login has been been completed */ + void OnLoginCompleteReadStats(int32 LocalUserNum, bool bWasSuccessful, const FUniqueNetId& UserId, const FString& Error); + /** selects item at current + MoveBy index */ void MoveSelection(int32 MoveBy); @@ -114,6 +120,9 @@ class SShooterLeaderboard : public SCompoundWidget /** Handle to the registered LeaderboardReadComplete delegate */ FDelegateHandle LeaderboardReadCompleteDelegateHandle; + + /** Handle to the registered LoginComplete delegate */ + FDelegateHandle OnLoginCompleteDelegateHandle; }; diff --git a/Source/ShooterGame/Private/UI/Menu/Widgets/SShooterMenuItem.cpp b/Source/ShooterGame/Private/UI/Menu/Widgets/SShooterMenuItem.cpp index 992601c..7779546 100644 --- a/Source/ShooterGame/Private/UI/Menu/Widgets/SShooterMenuItem.cpp +++ b/Source/ShooterGame/Private/UI/Menu/Widgets/SShooterMenuItem.cpp @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ShooterGame.h" #include "SShooterMenuItem.h" diff --git a/Source/ShooterGame/Private/UI/Menu/Widgets/SShooterMenuItem.h b/Source/ShooterGame/Private/UI/Menu/Widgets/SShooterMenuItem.h index 208db81..76ad39c 100644 --- a/Source/ShooterGame/Private/UI/Menu/Widgets/SShooterMenuItem.h +++ b/Source/ShooterGame/Private/UI/Menu/Widgets/SShooterMenuItem.h @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once diff --git a/Source/ShooterGame/Private/UI/Menu/Widgets/SShooterMenuWidget.cpp b/Source/ShooterGame/Private/UI/Menu/Widgets/SShooterMenuWidget.cpp index f0094ee..a103e58 100644 --- a/Source/ShooterGame/Private/UI/Menu/Widgets/SShooterMenuWidget.cpp +++ b/Source/ShooterGame/Private/UI/Menu/Widgets/SShooterMenuWidget.cpp @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ShooterGame.h" #include "Engine/Console.h" @@ -11,11 +11,14 @@ #include "ShooterGameInstance.h" #include "Player/ShooterLocalPlayer.h" #include "ShooterGameUserSettings.h" +#include "Slate/SceneViewport.h" #define LOCTEXT_NAMESPACE "SShooterMenuWidget" #if PLATFORM_XBOXONE #define PROFILE_SWAPPING 1 +#else +#define PROFILE_SWAPPING 0 #endif void SShooterMenuWidget::Construct(const FArguments& InArgs) @@ -154,6 +157,8 @@ void SShooterMenuWidget::Construct(const FArguments& InArgs) .AutoWidth() [ SNew(SVerticalBox) + .Clipping(EWidgetClipping::ClipToBounds) + + SVerticalBox::Slot() .AutoHeight() .Padding(TAttribute(this,&SShooterMenuWidget::GetLeftMenuOffset)) @@ -167,13 +172,17 @@ void SShooterMenuWidget::Construct(const FArguments& InArgs) .HAlign(HAlign_Left) [ SAssignNew(LeftBox, SVerticalBox) + .Clipping(EWidgetClipping::ClipToBounds) ] ] ] + + SHorizontalBox::Slot() .AutoWidth() [ SNew(SVerticalBox) + .Clipping(EWidgetClipping::ClipToBounds) + + SVerticalBox::Slot() .Padding(TAttribute(this,&SShooterMenuWidget::GetSubMenuOffset)) .AutoHeight() @@ -187,6 +196,7 @@ void SShooterMenuWidget::Construct(const FArguments& InArgs) .HAlign(HAlign_Left) [ SAssignNew(RightBox, SVerticalBox) + .Clipping(EWidgetClipping::ClipToBounds) ] ] ] @@ -231,7 +241,7 @@ bool SShooterMenuWidget::ProfileUISwap(const int ControllerIndex) const { if(IsProfileSwapActive()) { - const IOnlineExternalUI::FOnLoginUIClosedDelegate Delegate = IOnlineExternalUI::FOnLoginUIClosedDelegate::CreateSP( this, &SShooterMenuWidget::HandleProfileUISwapClosed ); + const FOnLoginUIClosedDelegate Delegate = FOnLoginUIClosedDelegate::CreateSP( this, &SShooterMenuWidget::HandleProfileUISwapClosed ); if ( ShooterUIHelpers::Get().ProfileSwapUI(ControllerIndex, false, &Delegate) ) { UShooterGameInstance* GameInstance = PlayerOwner.IsValid() ? Cast< UShooterGameInstance >( PlayerOwner->GetGameInstance() ) : nullptr; @@ -246,7 +256,7 @@ bool SShooterMenuWidget::ProfileUISwap(const int ControllerIndex) const return false; } -void SShooterMenuWidget::HandleProfileUISwapClosed(TSharedPtr UniqueId, const int ControllerIndex) +void SShooterMenuWidget::HandleProfileUISwapClosed(TSharedPtr UniqueId, const int ControllerIndex, const FOnlineError& Error) { UShooterGameInstance * GameInstance = PlayerOwner.IsValid() ? Cast< UShooterGameInstance >( PlayerOwner->GetGameInstance() ) : nullptr; @@ -260,7 +270,7 @@ void SShooterMenuWidget::HandleProfileUISwapClosed(TSharedPtr OwnerId = PlayerOwner->GetCachedUniqueNetId(); + FUniqueNetIdRepl OwnerId = PlayerOwner->GetCachedUniqueNetId(); if( OwnerId.IsValid() && UniqueId.IsValid() && *OwnerId == *UniqueId) { return; @@ -636,7 +646,8 @@ void SShooterMenuWidget::Tick( const FGeometry& AllottedGeometry, const double I FViewport* Viewport = GEngine->GameViewport->ViewportFrame->GetViewport(); if (Viewport) { - ScreenRes = Viewport->GetSizeXY(); + const FVector2D Size = Viewport->GetSizeXY(); + ScreenRes = (Size / AllottedGeometry.Scale).IntPoint(); } } @@ -904,13 +915,13 @@ FReply SShooterMenuWidget::OnKeyDown(const FGeometry& MyGeometry, const FKeyEven ConfirmMenuItem(); Result = FReply::Handled(); } - else if (Key == EKeys::Gamepad_FaceButton_Bottom && !InKeyEvent.IsRepeat()) + else if (Key == EKeys::Virtual_Accept && !InKeyEvent.IsRepeat()) { ControllerFacebuttonDownPressed(); ConfirmMenuItem(); Result = FReply::Handled(); } - else if ((Key == EKeys::Escape || Key == EKeys::Gamepad_FaceButton_Right || Key == EKeys::Gamepad_Special_Left || Key == EKeys::Global_Back || Key == EKeys::Global_View) && !InKeyEvent.IsRepeat()) + else if ((Key == EKeys::Escape || Key == EKeys::Virtual_Back || Key == EKeys::Gamepad_Special_Left || Key == EKeys::Global_Back || Key == EKeys::Global_View) && !InKeyEvent.IsRepeat()) { MenuGoBack(); Result = FReply::Handled(); diff --git a/Source/ShooterGame/Private/UI/Menu/Widgets/SShooterMenuWidget.h b/Source/ShooterGame/Private/UI/Menu/Widgets/SShooterMenuWidget.h index a2c92a9..e48a191 100644 --- a/Source/ShooterGame/Private/UI/Menu/Widgets/SShooterMenuWidget.h +++ b/Source/ShooterGame/Private/UI/Menu/Widgets/SShooterMenuWidget.h @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once @@ -13,7 +13,8 @@ class SShooterMenuWidget : public SCompoundWidget SLATE_BEGIN_ARGS(SShooterMenuWidget) : _PlayerOwner() , _IsGameMenu(false) - {} + { + } /** weak pointer to the parent HUD base */ SLATE_ARGUMENT(TWeakObjectPtr, PlayerOwner) @@ -174,7 +175,7 @@ class SShooterMenuWidget : public SCompoundWidget bool ProfileUISwap(const int ControllerIndex) const; /** delegate for if the profile is swapped */ - void HandleProfileUISwapClosed(TSharedPtr UniqueId, const int ControllerIndex); + void HandleProfileUISwapClosed(TSharedPtr UniqueId, const int ControllerIndex, const FOnlineError& Error = FOnlineError()); /** this function starts the entire fade in process */ void FadeIn(); diff --git a/Source/ShooterGame/Private/UI/Menu/Widgets/SShooterOnlineStore.cpp b/Source/ShooterGame/Private/UI/Menu/Widgets/SShooterOnlineStore.cpp new file mode 100644 index 0000000..58f1c8e --- /dev/null +++ b/Source/ShooterGame/Private/UI/Menu/Widgets/SShooterOnlineStore.cpp @@ -0,0 +1,437 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "ShooterGame.h" +#include "SShooterOnlineStore.h" +#include "SHeaderRow.h" +#include "ShooterStyle.h" +#include "ShooterGameLoadingScreen.h" +#include "ShooterGameInstance.h" +#include "Online/ShooterGameSession.h" +#include "Interfaces/OnlineStoreInterfaceV2.h" +#include "Interfaces/OnlinePurchaseInterface.h" + +#define LOCTEXT_NAMESPACE "ShooterGame.HUD.Menu" + +void SShooterOnlineStore::Construct(const FArguments& InArgs) +{ + PlayerOwner = InArgs._PlayerOwner; + OwnerWidget = InArgs._OwnerWidget; + State = EStoreState::Browsing; + StatusText = FText::GetEmpty(); + BoxWidth = 125; + + ChildSlot + .VAlign(VAlign_Fill) + .HAlign(HAlign_Fill) + [ + SNew(SVerticalBox) + +SVerticalBox::Slot() + .AutoHeight() + [ + SNew(SBox) + .WidthOverride(600) + .HeightOverride(300) + [ + SAssignNew(OfferListWidget, SListView>) + .ItemHeight(20) + .ListItemsSource(&OfferList) + .SelectionMode(ESelectionMode::Single) + .OnGenerateRow(this, &SShooterOnlineStore::MakeListViewWidget) + .OnSelectionChanged(this, &SShooterOnlineStore::EntrySelectionChanged) + .OnMouseButtonDoubleClick(this,&SShooterOnlineStore::OnListItemDoubleClicked) + .HeaderRow( + SNew(SHeaderRow) + + SHeaderRow::Column("Title").FillWidth(3).DefaultLabel(NSLOCTEXT("OfferList", "OfferTitleColumn", "Offer Title")) + + SHeaderRow::Column("Description").FillWidth(6).DefaultLabel(NSLOCTEXT("OfferList", "OfferDescColumn", "Description")) + + SHeaderRow::Column("Price").FillWidth(2).HAlignCell(HAlign_Right).DefaultLabel(NSLOCTEXT("OfferList", "OfferPriceColumn", "Price")) + + SHeaderRow::Column("Purchased").FillWidth(2).HAlignCell(HAlign_Right).DefaultLabel(NSLOCTEXT("OfferList", "OfferPurchaseColumn", "Purchased?")) + ) + ] + ] + +SVerticalBox::Slot() + .AutoHeight() + [ + SNew(SOverlay) + +SOverlay::Slot() + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + [ + SNew(SRichTextBlock) + .Text(this, &SShooterOnlineStore::GetBottomText) + .TextStyle(FShooterStyle::Get(), "ShooterGame.MenuServerListTextStyle") + .DecoratorStyleSet(&FShooterStyle::Get()) + + SRichTextBlock::ImageDecorator() + ] + ] + + ]; +} + + +FText SShooterOnlineStore::GetBottomText() const +{ + return StatusText; +} + +/** + * Ticks this widget. Override in derived classes, but always call the parent implementation. + * + * @param InCurrentTime Current absolute real time + * @param InDeltaTime Real time passed since last tick + */ +void SShooterOnlineStore::Tick( const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime ) +{ + SCompoundWidget::Tick( AllottedGeometry, InCurrentTime, InDeltaTime ); +} + +/** Returns logged in user */ +TSharedPtr SShooterOnlineStore::GetLoggedInUser() +{ + TSharedPtr UserIdPtr; + IOnlineSubsystem* const OnlineSub = IOnlineSubsystem::Get(); + if (OnlineSub) + { + IOnlineIdentityPtr IdentityInt = OnlineSub->GetIdentityInterface(); + if (IdentityInt.IsValid()) + { + UserIdPtr = GetFirstSignedInUser(IdentityInt); + } + } + return UserIdPtr; +} + + +/** Starts searching for servers */ +void SShooterOnlineStore::BeginGettingOffers() +{ + if (State != EStoreState::Browsing) + { + UE_LOG(LogOnline, Warning, TEXT("We cannot start getting the offers, the store state is not Browsing (state = %d)"), static_cast(State)); + return; + } + + OfferList.Reset(); + + IOnlineSubsystem* const OnlineSub = IOnlineSubsystem::Get(); + if (OnlineSub) + { + IOnlineStoreV2Ptr StoreV2Int = OnlineSub->GetStoreV2Interface(); + IOnlinePurchasePtr PurchaseInt = OnlineSub->GetPurchaseInterface(); + if (StoreV2Int.IsValid()) + { + TSharedPtr LoggedInUser = GetLoggedInUser(); + if (!LoggedInUser.IsValid()) + { + UE_LOG(LogOnline, Warning, TEXT("There's no logged in user")); + return; + } + + TSharedRef UserId = LoggedInUser.ToSharedRef(); + + const FOnQueryOnlineStoreOffersComplete QueryStoreOffersDelegate = FOnQueryOnlineStoreOffersComplete::CreateLambda( + [this, StoreV2Int, PurchaseInt, UserId](bool bWasSuccessful, const TArray& OfferIds, const FString& ErrorString) + { + UE_LOG(LogOnline, Verbose, TEXT("Query store offers completed with status bWasSuccessful=[%d] Error=[%s] OfferIds=[%d]"), bWasSuccessful, ErrorString.IsEmpty() ? TEXT("None") : *ErrorString, OfferIds.Num()); + + if (bWasSuccessful) + { + TArray StoreOffers; + StoreV2Int->GetOffers(StoreOffers); + + UE_LOG(LogOnline, Verbose, TEXT("Found %d offers in cache"), StoreOffers.Num()); + + for (const FOnlineStoreOfferRef& OfferRef : StoreOffers) + { + UE_LOG(LogOnline, Verbose, TEXT(" Offer=[%s] CurrencyCode=[%s] PriceInt=[%d] DisplayPrice=[%s]"), *OfferRef->OfferId, *OfferRef->CurrencyCode, OfferRef->NumericPrice, *OfferRef->GetDisplayPrice().ToString()); + + TSharedPtr NewOffer = MakeShareable(new FStoreEntry());; + NewOffer->OnlineId = OfferRef->OfferId; + NewOffer->Title = OfferRef->Title.IsEmptyOrWhitespace() ? NSLOCTEXT("ShooterOnlineStore", "DefaultOfferTitle", "EmptyTitle") : OfferRef->Title; + NewOffer->Description = OfferRef->Description.IsEmptyOrWhitespace() ? NSLOCTEXT("ShooterOnlineStore", "DefaultOfferDescription", "EmptyDescription") : OfferRef->Description; + NewOffer->Price = OfferRef->GetDisplayPrice().IsEmptyOrWhitespace() ? NSLOCTEXT("ShooterOnlineStore", "DefaultOfferDescription", "EmptyPrice") : OfferRef->GetDisplayPrice(); + + OfferList.Add(NewOffer); + } + } + + OfferListWidget->RequestListRefresh(); + if (OfferList.Num() > 0) + { + OfferListWidget->UpdateSelectionSet(); + OfferListWidget->SetSelection(0, ESelectInfo::OnNavigation); + } + + if (PurchaseInt.IsValid()) + { + const FOnQueryReceiptsComplete QueryReceiptsDelegate = FOnQueryReceiptsComplete::CreateLambda( + [this, PurchaseInt, UserId](const FOnlineError& Result) + { + if (Result.bSucceeded) + { + TArray PurchasedReceipts; + PurchaseInt->GetReceipts(*UserId, PurchasedReceipts); + + TArray OffersPurchased; + for (const FPurchaseReceipt& Receipt : PurchasedReceipts) + { + for (const FPurchaseReceipt::FReceiptOfferEntry& ReceiptOffer : Receipt.ReceiptOffers) + { + OffersPurchased.Add(ReceiptOffer.OfferId); + } + } + + MarkAsPurchased(OffersPurchased); + } + + SetStoreState(EStoreState::Browsing); + }); + + PurchaseInt->QueryReceipts(*UserId, true, QueryReceiptsDelegate); + } + else + { + SetStoreState(EStoreState::Browsing); + } + }); + + SetStoreState(EStoreState::GettingOffers); + StoreV2Int->QueryOffersByFilter(*UserId, FOnlineStoreFilter(), QueryStoreOffersDelegate); + } + else + { + // just for test + for (int32 Idx = 0; Idx < 16; ++Idx) + { + TSharedPtr NewOffer = MakeShareable(new FStoreEntry());; + NewOffer->OnlineId = FUniqueOfferId(FString::Printf(TEXT("FakeOfferId%d"), Idx)); + NewOffer->Title = FText::FromString(FString::Printf(TEXT("Offer #%d"), Idx)); + NewOffer->Description = FText::FromString(FString::Printf(TEXT("Somewhat long description for the offer #%d"), Idx)); + NewOffer->Price = FText::FromString(FString::Printf(TEXT("$%d"), Idx * 2)); + + OfferList.Add(NewOffer); + } + + SetStoreState(EStoreState::Browsing); + + OfferListWidget->RequestListRefresh(); + if (OfferList.Num() > 0) + { + OfferListWidget->UpdateSelectionSet(); + OfferListWidget->SetSelection(0, ESelectInfo::OnNavigation); + } + } + } +} + +void SShooterOnlineStore::SetStoreState(EStoreState NewState) +{ + UE_LOG(LogOnline, Verbose, TEXT("Transitioning the store from state %d to state %d"), static_cast(State), static_cast(NewState)); + State = NewState; + + switch (State) + { + case EStoreState::PurchasingAnOffer: + StatusText = FText(NSLOCTEXT("ShooterOnlineStore", "Status", "Purchasing...")); + break; + + case EStoreState::GettingOffers: + StatusText = FText(NSLOCTEXT("ShooterOnlineStore", "Status", "Checking what's available...")); + break; + + case EStoreState::Browsing: + if (OfferList.Num() == 0) + { + StatusText = FText(NSLOCTEXT("ShooterOnlineStore", "Status", "No offers found - press Space to refresh")); + break; + } + // intended fall-through + default: + StatusText = FText::GetEmpty(); + break; + } +} + +void SShooterOnlineStore::PurchaseOffer() +{ + if (State != EStoreState::Browsing) + { + UE_LOG(LogOnline, Warning, TEXT("We cannot purchase an offer, the store state is not Browsing (state = %d)"), static_cast(State)); + return; + } + + if (SelectedItem.IsValid()) + { + IOnlineSubsystem* const OnlineSub = IOnlineSubsystem::Get(); + if (OnlineSub) + { + IOnlinePurchasePtr PurchaseInt = OnlineSub->GetPurchaseInterface(); + if (PurchaseInt.IsValid()) + { + TSharedPtr LoggedInUser = GetLoggedInUser(); + if (!LoggedInUser.IsValid()) + { + UE_LOG(LogOnline, Warning, TEXT("There's no logged in user")); + return; + } + + TSharedRef UserId = LoggedInUser.ToSharedRef(); + + FPurchaseCheckoutRequest CheckoutParams; + CheckoutParams.AddPurchaseOffer(FString(), SelectedItem->OnlineId, 1, false); + + + UE_LOG(LogOnline, Verbose, TEXT("Attempting to checkout OfferId %s"), *SelectedItem->OnlineId); + + SetStoreState(EStoreState::PurchasingAnOffer); + PurchaseInt->Checkout(*UserId, CheckoutParams, FOnPurchaseCheckoutComplete::CreateLambda( + [this](const FOnlineError& Result, const TSharedRef& Receipt) + { + UE_LOG(LogOnline, Verbose, TEXT("Checkout completed with status %s"), *Result.ToLogString()); + + TArray OffersPurchased; + for (const FPurchaseReceipt::FReceiptOfferEntry& ReceiptOffer : Receipt->ReceiptOffers) + { + OffersPurchased.Add(ReceiptOffer.OfferId); + } + MarkAsPurchased(OffersPurchased); + + SetStoreState(EStoreState::Browsing); + })); + } + else + { + UE_LOG(LogOnline, Warning, TEXT("IOnlinePurchase interface not available")); + } + } + } +} + +void SShooterOnlineStore::MarkAsPurchased(const TArray & Offers) +{ + for (TSharedPtr StoreOffer : OfferList) + { + for (const FUniqueOfferId& PurchasedOffer : Offers) + { + if (StoreOffer->OnlineId == PurchasedOffer) + { + StoreOffer->bPurchased = true; + } + } + } + + // otherwise the items won't be picked up + OfferListWidget->RebuildList(); +} + + +void SShooterOnlineStore::OnFocusLost( const FFocusEvent& InFocusEvent ) +{ + if (InFocusEvent.GetCause() != EFocusCause::SetDirectly) + { + FSlateApplication::Get().SetKeyboardFocus(SharedThis( this )); + } +} + +FReply SShooterOnlineStore::OnFocusReceived(const FGeometry& MyGeometry, const FFocusEvent& InFocusEvent) +{ + return FReply::Handled().SetUserFocus(OfferListWidget.ToSharedRef(), EFocusCause::SetDirectly).SetUserFocus(SharedThis(this), EFocusCause::SetDirectly, true); +} + +void SShooterOnlineStore::EntrySelectionChanged(TSharedPtr InItem, ESelectInfo::Type SelectInfo) +{ + SelectedItem = InItem; +} + +void SShooterOnlineStore::OnListItemDoubleClicked(TSharedPtr InItem) +{ + SelectedItem = InItem; + PurchaseOffer(); + FSlateApplication::Get().SetKeyboardFocus(SharedThis(this)); +} + +void SShooterOnlineStore::MoveSelection(int32 MoveBy) +{ + int32 SelectedItemIndex = OfferList.IndexOfByKey(SelectedItem); + + if (SelectedItemIndex+MoveBy > -1 && SelectedItemIndex+MoveBy < OfferList.Num()) + { + OfferListWidget->SetSelection(OfferList[SelectedItemIndex+MoveBy]); + } +} + +FReply SShooterOnlineStore::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) +{ + if (State != EStoreState::Browsing) // lock input + { + return FReply::Handled(); + } + + FReply Result = FReply::Unhandled(); + const FKey Key = InKeyEvent.GetKey(); + + if (Key == EKeys::Up || Key == EKeys::Gamepad_DPad_Up || Key == EKeys::Gamepad_LeftStick_Up) + { + MoveSelection(-1); + Result = FReply::Handled(); + } + else if (Key == EKeys::Down || Key == EKeys::Gamepad_DPad_Down || Key == EKeys::Gamepad_LeftStick_Down) + { + MoveSelection(1); + Result = FReply::Handled(); + } + else if (Key == EKeys::Enter || Key == EKeys::Virtual_Accept) + { + PurchaseOffer(); + Result = FReply::Handled(); + FSlateApplication::Get().SetKeyboardFocus(SharedThis(this)); + } + else if (Key == EKeys::SpaceBar || Key == EKeys::Gamepad_FaceButton_Left) + { + BeginGettingOffers(); + } + return Result; +} + +TSharedRef SShooterOnlineStore::MakeListViewWidget(TSharedPtr Item, const TSharedRef& OwnerTable) +{ + class SStoreEntryWidget : public SMultiColumnTableRow< TSharedPtr > + { + public: + SLATE_BEGIN_ARGS(SStoreEntryWidget){} + SLATE_END_ARGS() + + void Construct(const FArguments& InArgs, const TSharedRef& InOwnerTable, TSharedPtr InItem) + { + Item = InItem; + SMultiColumnTableRow< TSharedPtr >::Construct(FSuperRowType::FArguments(), InOwnerTable); + } + + TSharedRef GenerateWidgetForColumn(const FName& ColumnName) + { + FText ItemText = FText::GetEmpty(); + if (ColumnName == "Title") + { + ItemText = Item->Title; + } + else if (ColumnName == "Description") + { + ItemText = Item->Description; + } + else if (ColumnName == "Price") + { + ItemText = Item->Price; + } + else if (ColumnName == "Purchased") + { + ItemText = Item->bPurchased ? FText(NSLOCTEXT("OfferList", "PurchasedYes", "Yes")) : FText(NSLOCTEXT("OfferList", "PurchasedNo", "No")); + } + return SNew(STextBlock) + .Text(ItemText) + .TextStyle(FShooterStyle::Get(), "ShooterGame.MenuStoreListTextStyle"); + } + TSharedPtr Item; + }; + return SNew(SStoreEntryWidget, OwnerTable, Item); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Source/ShooterGame/Private/UI/Menu/Widgets/SShooterOnlineStore.h b/Source/ShooterGame/Private/UI/Menu/Widgets/SShooterOnlineStore.h new file mode 100644 index 0000000..d6dfb1f --- /dev/null +++ b/Source/ShooterGame/Private/UI/Menu/Widgets/SShooterOnlineStore.h @@ -0,0 +1,127 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "SlateBasics.h" +#include "SlateExtras.h" +#include "ShooterGame.h" +#include "SShooterMenuWidget.h" + +class AShooterGameSession; + +struct FStoreEntry +{ + FUniqueOfferId OnlineId; + FText Title; + FText Description; + FText Price; + bool bPurchased; +}; + +//class declare +class SShooterOnlineStore : public SShooterMenuWidget +{ +public: + SLATE_BEGIN_ARGS(SShooterOnlineStore) + {} + + SLATE_ARGUMENT(TWeakObjectPtr, PlayerOwner) + SLATE_ARGUMENT(TSharedPtr, OwnerWidget) + + SLATE_END_ARGS() + + /** needed for every widget */ + void Construct(const FArguments& InArgs); + + /** if we want to receive focus */ + virtual bool SupportsKeyboardFocus() const override { return true; } + + /** focus received handler - keep the ActionBindingsList focused */ + virtual FReply OnFocusReceived(const FGeometry& MyGeometry, const FFocusEvent& InFocusEvent) override; + + /** focus lost handler - keep the ActionBindingsList focused */ + virtual void OnFocusLost( const FFocusEvent& InFocusEvent ) override; + + /** key down handler */ + virtual FReply OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) override; + + /** SListView item double clicked */ + void OnListItemDoubleClicked(TSharedPtr InItem); + + /** creates single item widget, called for every list item */ + TSharedRef MakeListViewWidget(TSharedPtr Item, const TSharedRef& OwnerTable); + + /** selection changed handler */ + void EntrySelectionChanged(TSharedPtr InItem, ESelectInfo::Type SelectInfo); + + /** Starts getting the offers etc */ + void BeginGettingOffers(); + + /** Called when server search is finished */ + void OnGettingOffersFinished(); + + /** fill/update server list, should be called before showing this control */ + void UpdateServerList(); + + /** purchases the chose offer */ + void PurchaseOffer(); + + /** selects item at current + MoveBy index */ + void MoveSelection(int32 MoveBy); + + /** + * Ticks this widget. Override in derived classes, but always call the parent implementation. + * + * @param AllottedGeometry The space allotted for this widget + * @param InCurrentTime Current absolute real time + * @param InDeltaTime Real time passed since last tick + */ + void Tick( const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime ); + +protected: + + enum class EStoreState + { + Browsing, + GettingOffers, + PurchasingAnOffer, + }; + + /** Store state */ + EStoreState State; + + /** Transitions to new store state */ + void SetStoreState(EStoreState NewState); + + /** Marks offers as purchased */ + void MarkAsPurchased(const TArray & Offers); + + /** Returns logged in user */ + TSharedPtr GetLoggedInUser(); + + /** action bindings array */ + TArray< TSharedPtr > OfferList; + + /** action bindings list slate widget */ + TSharedPtr< SListView< TSharedPtr > > OfferListWidget; + + /** currently selected list item */ + TSharedPtr SelectedItem; + + /** get current status text */ + FText GetBottomText() const; + + /** current status text */ + FText StatusText; + + /** size of standard column in pixels */ + int32 BoxWidth; + + /** pointer to our owner PC */ + TWeakObjectPtr PlayerOwner; + + /** pointer to our parent widget */ + TSharedPtr OwnerWidget; +}; + + diff --git a/Source/ShooterGame/Private/UI/Menu/Widgets/SShooterServerList.cpp b/Source/ShooterGame/Private/UI/Menu/Widgets/SShooterServerList.cpp index c018516..4bf964e 100644 --- a/Source/ShooterGame/Private/UI/Menu/Widgets/SShooterServerList.cpp +++ b/Source/ShooterGame/Private/UI/Menu/Widgets/SShooterServerList.cpp @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ShooterGame.h" #include "SShooterServerList.h" @@ -19,6 +19,13 @@ void SShooterServerList::Construct(const FArguments& InArgs) bLANMatchSearch = false; StatusText = FText::GetEmpty(); BoxWidth = 125; + LastSearchTime = 0.0f; + +#if PLATFORM_SWITCH + MinTimeBetweenSearches = 6.0; +#else + MinTimeBetweenSearches = 0.0; +#endif ChildSlot .VAlign(VAlign_Fill) @@ -56,9 +63,11 @@ void SShooterServerList::Construct(const FArguments& InArgs) .VAlign(VAlign_Center) .HAlign(HAlign_Center) [ - SNew(STextBlock) + SNew(SRichTextBlock) .Text(this, &SShooterServerList::GetBottomText) .TextStyle(FShooterStyle::Get(), "ShooterGame.MenuServerListTextStyle") + .DecoratorStyleSet(&FShooterStyle::Get()) + + SRichTextBlock::ImageDecorator() ] ] @@ -107,6 +116,8 @@ void SShooterServerList::UpdateSearchStatus() StatusText = LOCTEXT("NoServersFound","NO SERVERS FOUND, PRESS SQUARE TO TRY AGAIN"); #elif PLATFORM_XBOXONE StatusText = LOCTEXT("NoServersFound","NO SERVERS FOUND, PRESS X TO TRY AGAIN"); +#elif PLATFORM_SWITCH + StatusText = LOCTEXT("NoServersFound", "NO SERVERS FOUND, PRESS TO TRY AGAIN"); #else StatusText = LOCTEXT("NoServersFound","NO SERVERS FOUND, PRESS SPACE TO TRY AGAIN"); #endif @@ -117,6 +128,8 @@ void SShooterServerList::UpdateSearchStatus() StatusText = LOCTEXT("ServersRefresh","PRESS SQUARE TO REFRESH SERVER LIST"); #elif PLATFORM_XBOXONE StatusText = LOCTEXT("ServersRefresh","PRESS X TO REFRESH SERVER LIST"); +#elif PLATFORM_SWITCH + StatusText = LOCTEXT("ServersRefresh", "PRESS TO REFRESH SERVER LIST"); #else StatusText = LOCTEXT("ServersRefresh","PRESS SPACE TO REFRESH SERVER LIST"); #endif @@ -185,17 +198,27 @@ void SShooterServerList::Tick( const FGeometry& AllottedGeometry, const double I } /** Starts searching for servers */ -void SShooterServerList::BeginServerSearch(bool bLANMatch, const FString& InMapFilterName) +void SShooterServerList::BeginServerSearch(bool bLANMatch, bool bIsDedicatedServer, const FString& InMapFilterName) { - bLANMatchSearch = bLANMatch; - MapFilterName = InMapFilterName; - bSearchingForServers = true; - ServerList.Empty(); - - UShooterGameInstance* const GI = Cast(PlayerOwner->GetGameInstance()); - if (GI) + double CurrentTime = FApp::GetCurrentTime(); + if (!bLANMatch && CurrentTime - LastSearchTime < MinTimeBetweenSearches) { - GI->FindSessions(PlayerOwner.Get(), bLANMatchSearch); + OnServerSearchFinished(); + } + else + { + bLANMatchSearch = bLANMatch; + bDedicatedServer = bIsDedicatedServer; + MapFilterName = InMapFilterName; + bSearchingForServers = true; + ServerList.Empty(); + LastSearchTime = CurrentTime; + + UShooterGameInstance* const GI = Cast(PlayerOwner->GetGameInstance()); + if (GI) + { + GI->FindSessions(PlayerOwner.Get(), bIsDedicatedServer, bLANMatchSearch); + } } } @@ -218,6 +241,7 @@ void SShooterServerList::UpdateServerList() if (ServerList[i]->MapName != MapFilterName) { ServerList.RemoveAt(i); + i--; } } } @@ -318,7 +342,7 @@ FReply SShooterServerList::OnKeyDown(const FGeometry& MyGeometry, const FKeyEven MoveSelection(1); Result = FReply::Handled(); } - else if (Key == EKeys::Enter || Key == EKeys::Gamepad_FaceButton_Bottom) + else if (Key == EKeys::Enter || Key == EKeys::Virtual_Accept) { ConnectToServer(); Result = FReply::Handled(); @@ -327,7 +351,7 @@ FReply SShooterServerList::OnKeyDown(const FGeometry& MyGeometry, const FKeyEven //hit space bar to search for servers again / refresh the list, only when not searching already else if (Key == EKeys::SpaceBar || Key == EKeys::Gamepad_FaceButton_Left) { - BeginServerSearch(bLANMatchSearch, MapFilterName); + BeginServerSearch(bLANMatchSearch, bDedicatedServer, MapFilterName); } return Result; } diff --git a/Source/ShooterGame/Private/UI/Menu/Widgets/SShooterServerList.h b/Source/ShooterGame/Private/UI/Menu/Widgets/SShooterServerList.h index 16a0be5..c41cb8e 100644 --- a/Source/ShooterGame/Private/UI/Menu/Widgets/SShooterServerList.h +++ b/Source/ShooterGame/Private/UI/Menu/Widgets/SShooterServerList.h @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once @@ -67,7 +67,7 @@ class SShooterServerList : public SShooterMenuWidget void UpdateSearchStatus(); /** Starts searching for servers */ - void BeginServerSearch(bool bLANMatch, const FString& InMapFilterName); + void BeginServerSearch(bool bLANMatch, bool bIsDedicatedServer, const FString& InMapFilterName); /** Called when server search is finished */ void OnServerSearchFinished(); @@ -95,9 +95,18 @@ class SShooterServerList : public SShooterMenuWidget /** Whether last searched for LAN (so spacebar works) */ bool bLANMatchSearch; + /** Whether last searched for Dedicated Server (so spacebar works) */ + bool bDedicatedServer; + /** Whether we're searching for servers */ bool bSearchingForServers; + /** Time the last search began */ + double LastSearchTime; + + /** Minimum time between searches (platform dependent) */ + double MinTimeBetweenSearches; + /** action bindings array */ TArray< TSharedPtr > ServerList; diff --git a/Source/ShooterGame/Private/UI/Menu/Widgets/ShooterMenuItem.h b/Source/ShooterGame/Private/UI/Menu/Widgets/ShooterMenuItem.h index 1bb57a0..f3995d9 100644 --- a/Source/ShooterGame/Private/UI/Menu/Widgets/ShooterMenuItem.h +++ b/Source/ShooterGame/Private/UI/Menu/Widgets/ShooterMenuItem.h @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once #include "SlateBasics.h" #include "SlateExtras.h" @@ -35,7 +35,7 @@ class FShooterMenuInfo { Menu = _Menu; SelectedIndex = _SelectedIndex; - MenuTitle = _MenuTitle; + MenuTitle = MoveTemp(_MenuTitle); } }; @@ -109,7 +109,7 @@ class FShooterMenuItem : public TSharedFromThis FShooterMenuItem(FText _text) { bVisible = true; - Text = _text; + Text = MoveTemp(_text); MenuItemType = EShooterMenuItemType::Standard; } @@ -125,9 +125,9 @@ class FShooterMenuItem : public TSharedFromThis FShooterMenuItem(FText _text, TArray _choices, int32 DefaultIndex=0) { bVisible = true; - Text = _text; + Text = MoveTemp(_text); MenuItemType = EShooterMenuItemType::MultiChoice; - MultiChoice = _choices; + MultiChoice = MoveTemp(_choices); MinMultiChoiceIndex = MaxMultiChoiceIndex = -1; SelectedMultiChoice = DefaultIndex; } @@ -137,9 +137,9 @@ class FShooterMenuItem : public TSharedFromThis return Text; } - void SetText(const FText& UpdatedText) + void SetText(FText UpdatedText) { - Text = UpdatedText; + Text = MoveTemp(UpdatedText); if (Widget.IsValid()) { Widget->UpdateItemText(Text); diff --git a/Source/ShooterGame/Private/UI/ShooterHUD.cpp b/Source/ShooterGame/Private/UI/ShooterHUD.cpp index 14d904f..3e6e6e4 100644 --- a/Source/ShooterGame/Private/UI/ShooterHUD.cpp +++ b/Source/ShooterGame/Private/UI/ShooterHUD.cpp @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ShooterGame.h" #include "UI/ShooterHUD.h" @@ -9,6 +9,7 @@ #include "Weapons/ShooterDamageType.h" #include "Weapons/ShooterWeapon_Instant.h" #include "Online/ShooterPlayerState.h" +#include "Misc/NetworkVersion.h" #define LOCTEXT_NAMESPACE "ShooterGame.HUD.Menu" @@ -32,14 +33,20 @@ AShooterHUD::AShooterHUD(const FObjectInitializer& ObjectInitializer) : Super(Ob static ConstructorHelpers::FObjectFinder HUDAssets02TextureOb(TEXT("/Game/UI/HUD/HUDAssets02")); static ConstructorHelpers::FObjectFinder LowHealthOverlayTextureOb(TEXT("/Game/UI/HUD/LowHealthOverlay")); - static ConstructorHelpers::FObjectFinder BigFontOb(TEXT("/Game/UI/HUD/Roboto51")); - static ConstructorHelpers::FObjectFinder NormalFontOb(TEXT("/Game/UI/HUD/Roboto18")); + // Fonts are not included in dedicated server builds. + #if !UE_SERVER + { + static ConstructorHelpers::FObjectFinder BigFontOb(TEXT("/Game/UI/HUD/Roboto51")); + static ConstructorHelpers::FObjectFinder NormalFontOb(TEXT("/Game/UI/HUD/Roboto18")); + BigFont = BigFontOb.Object; + NormalFont = NormalFontOb.Object; + } + #endif //!UE_SERVER + HitNotifyTexture = HitTextureOb.Object; HUDMainTexture = HUDMainTextureOb.Object; HUDAssets02Texture = HUDAssets02TextureOb.Object; LowHealthOverlayTexture = LowHealthOverlayTextureOb.Object; - BigFont = BigFontOb.Object; - NormalFont = NormalFontOb.Object; HitNotifyIcon[EShooterHudPosition::Left] = UCanvas::MakeIcon(HitNotifyTexture, 158, 831, 585, 392); HitNotifyIcon[EShooterHudPosition::FrontLeft] = UCanvas::MakeIcon(HitNotifyTexture, 369, 434, 460, 378); @@ -328,7 +335,7 @@ void AShooterHUD::DrawHealth() void AShooterHUD::DrawMatchTimerAndPosition() { - AShooterGameState* const MyGameState = Cast(GetWorld()->GameState); + AShooterGameState* const MyGameState = GetWorld()->GetGameState(); Canvas->SetDrawColor(FColor::White); const float TimerPosX = Canvas->ClipX - Canvas->OrgX - (TimePlaceBg.UL + Offset) * ScaleUI; const float TimerPosY = Canvas->OrgY + Offset * ScaleUI; @@ -542,17 +549,17 @@ void AShooterHUD::DrawHUD() IOnlineSessionPtr SessionSubsystem = OnlineSubsystem->GetSessionInterface(); if(SessionSubsystem.IsValid()) { - FNamedOnlineSession * Session = SessionSubsystem->GetNamedSession(GameSessionName); - if(Session) + FNamedOnlineSession * Session = SessionSubsystem->GetNamedSession(NAME_GameSession); + if(Session && Session->SessionInfo.IsValid()) { NetModeDesc += TEXT("\nSession: "); - NetModeDesc += Session->SessionInfo->GetSessionId().ToString(); + NetModeDesc += Session->GetSessionIdStr(); } } } - NetModeDesc += FString::Printf( TEXT( "\nVersion: %i, %s, %s" ), GEngineNetVersion, UTF8_TO_TCHAR(__DATE__), UTF8_TO_TCHAR(__TIME__) ); + NetModeDesc += FString::Printf( TEXT( "\nVersion: %i, %s, %s" ), FNetworkVersion::GetNetworkCompatibleChangelist(), UTF8_TO_TCHAR(__DATE__), UTF8_TO_TCHAR(__TIME__) ); DrawDebugInfoString(NetModeDesc, Canvas->OrgX + Offset*ScaleUI, Canvas->OrgY + 5*Offset*ScaleUI, true, true, HUDLight); } @@ -692,7 +699,7 @@ void AShooterHUD::DrawCrosshair() } } - if (Pawn->IsTargeting() && MyWeapon->UseLaserDot) + if (Pawn->IsTargeting() && MyWeapon && MyWeapon->UseLaserDot) { Canvas->SetDrawColor(255,0,0,192); Canvas->DrawIcon(*CurrentCrosshair[EShooterCrosshairDirection::Center], @@ -822,9 +829,9 @@ void AShooterHUD::ShowDeathMessage(class AShooterPlayerState* KillerPlayerState, const int32 MaxDeathMessages = 5; const float MessageDuration = 10.0f; - if (GetWorld()->GameState && GetWorld()->GameState->GameModeClass) + if (GetWorld()->GetGameState()) { - const AShooterGameMode* DefGame = GetWorld()->GameState->GameModeClass->GetDefaultObject(); + const AShooterGameMode* DefGame = GetWorld()->GetGameState()->GetDefaultGameMode(); AShooterPlayerState* MyPlayerState = PlayerOwner ? Cast(PlayerOwner->PlayerState) : NULL; if (DefGame && KillerPlayerState && VictimPlayerState && MyPlayerState) @@ -842,7 +849,7 @@ void AShooterHUD::ShowDeathMessage(class AShooterPlayerState* KillerPlayerState, NewMessage.bKillerIsOwner = MyPlayerState == KillerPlayerState; NewMessage.bVictimIsOwner = MyPlayerState == VictimPlayerState; - NewMessage.DamageType = Cast(KillerDamageType); + NewMessage.DamageType = MakeWeakObjectPtr(const_cast(Cast(KillerDamageType))); NewMessage.HideTime = GetWorld()->GetTimeSeconds() + MessageDuration; DeathMessages.Add(NewMessage); @@ -939,7 +946,7 @@ void AShooterHUD::OnPlayerTalkingStateChanged(TSharedRef Tal { if (bIsScoreBoardVisible) { - ScoreboardWidget->StoreTalkingPlayerData(TalkingPlayerId->ToString(), bIsTalking); + ScoreboardWidget->StoreTalkingPlayerData(TalkingPlayerId.Get(), bIsTalking); } } @@ -1007,7 +1014,7 @@ bool AShooterHUD::ShowScoreboard(bool bEnable, bool bFocus) .Padding(FMargin(50)) [ SAssignNew(ScoreboardWidget, SShooterScoreboardWidget) - .PCOwner(TWeakObjectPtr(PlayerOwner)) + .PCOwner(MakeWeakObjectPtr(PlayerOwner)) .MatchState(GetMatchState()) ]; diff --git a/Source/ShooterGame/Private/UI/ShooterHUDPCTrackerBase.cpp b/Source/ShooterGame/Private/UI/ShooterHUDPCTrackerBase.cpp index 94d7a72..a3bea5f 100644 --- a/Source/ShooterGame/Private/UI/ShooterHUDPCTrackerBase.cpp +++ b/Source/ShooterGame/Private/UI/ShooterHUDPCTrackerBase.cpp @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ShooterGame.h" #include "ShooterHUDPCTrackerBase.h" @@ -16,7 +16,7 @@ TWeakObjectPtr ShooterHUDPCTrackerBase::GetPlayerContr { APlayerController* PC = Context.GetPlayerController(); AShooterPlayerController* ShooterPC = Cast(PC); - return TWeakObjectPtr(ShooterPC); + return MakeWeakObjectPtr(ShooterPC); } else { @@ -41,7 +41,7 @@ AShooterGameState* ShooterHUDPCTrackerBase::GetGameState() const { if ( ensureMsgf( Context.IsValid(), TEXT("Game context must be initialized!") ) ) { - return Cast(Context.GetWorld()->GameState); + return Context.GetWorld()->GetGameState(); } else { diff --git a/Source/ShooterGame/Private/UI/ShooterHUDPCTrackerBase.h b/Source/ShooterGame/Private/UI/ShooterHUDPCTrackerBase.h index c844fb7..1f00f86 100644 --- a/Source/ShooterGame/Private/UI/ShooterHUDPCTrackerBase.h +++ b/Source/ShooterGame/Private/UI/ShooterHUDPCTrackerBase.h @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once diff --git a/Source/ShooterGame/Private/UI/ShooterUIHelpers.cpp b/Source/ShooterGame/Private/UI/ShooterUIHelpers.cpp index c6f3706..0a744e4 100644 --- a/Source/ShooterGame/Private/UI/ShooterUIHelpers.cpp +++ b/Source/ShooterGame/Private/UI/ShooterUIHelpers.cpp @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ShooterGame.h" #include "ShooterUIHelpers.h" @@ -16,7 +16,7 @@ FText ShooterUIHelpers::GetProfileOpenText() const #endif } -bool ShooterUIHelpers::ProfileOpenedUI(const FUniqueNetId& Requestor, const FUniqueNetId& Requestee, const IOnlineExternalUI::FOnProfileUIClosedDelegate* Delegate) const +bool ShooterUIHelpers::ProfileOpenedUI(const FUniqueNetId& Requestor, const FUniqueNetId& Requestee, const FOnProfileUIClosedDelegate* Delegate) const { const auto OnlineSub = IOnlineSubsystem::Get(); if (OnlineSub) @@ -33,7 +33,7 @@ bool ShooterUIHelpers::ProfileOpenedUI(const FUniqueNetId& Requestor, const FUni // do nothing } }; - return ExternalUI->ShowProfileUI(Requestor, Requestee, Delegate ? *Delegate : IOnlineExternalUI::FOnProfileUIClosedDelegate::CreateStatic(&Local::DummyOnProfileOpenedUIClosedDelegate) ); + return ExternalUI->ShowProfileUI(Requestor, Requestee, Delegate ? *Delegate : FOnProfileUIClosedDelegate::CreateStatic(&Local::DummyOnProfileOpenedUIClosedDelegate) ); } } return false; @@ -51,7 +51,7 @@ FText ShooterUIHelpers::GetProfileSwapText() const #endif*/ } -bool ShooterUIHelpers::ProfileSwapUI(const int ControllerIndex, bool bShowOnlineOnly, const IOnlineExternalUI::FOnLoginUIClosedDelegate* Delegate) const +bool ShooterUIHelpers::ProfileSwapUI(const int ControllerIndex, bool bShowOnlineOnly, const FOnLoginUIClosedDelegate* Delegate) const { const auto OnlineSub = IOnlineSubsystem::Get(); if (OnlineSub) @@ -63,12 +63,12 @@ bool ShooterUIHelpers::ProfileSwapUI(const int ControllerIndex, bool bShowOnline // Create a dummy delegate, if one wasn't specified struct Local { - static void DummyOnProfileSwapUIClosedDelegate(TSharedPtr UniqueId, const int InControllerIndex) + static void DummyOnProfileSwapUIClosedDelegate(TSharedPtr UniqueId, const int InControllerIndex, const FOnlineError& Error) { // do nothing } }; - return ExternalUI->ShowLoginUI(ControllerIndex, bShowOnlineOnly, Delegate ? *Delegate : IOnlineExternalUI::FOnLoginUIClosedDelegate::CreateStatic(&Local::DummyOnProfileSwapUIClosedDelegate) ); + return ExternalUI->ShowLoginUI(ControllerIndex, bShowOnlineOnly, false, Delegate ? *Delegate : FOnLoginUIClosedDelegate::CreateStatic(&Local::DummyOnProfileSwapUIClosedDelegate) ); } } return false; diff --git a/Source/ShooterGame/Private/UI/ShooterUIHelpers.h b/Source/ShooterGame/Private/UI/ShooterUIHelpers.h index 5e5dafe..54338a2 100644 --- a/Source/ShooterGame/Private/UI/ShooterUIHelpers.h +++ b/Source/ShooterGame/Private/UI/ShooterUIHelpers.h @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once @@ -21,11 +21,11 @@ class ShooterUIHelpers FText GetProfileOpenText() const; /** profile open ui handler */ - bool ProfileOpenedUI(const FUniqueNetId& Requestor, const FUniqueNetId& Requestee, const IOnlineExternalUI::FOnProfileUIClosedDelegate* Delegate) const; + bool ProfileOpenedUI(const FUniqueNetId& Requestor, const FUniqueNetId& Requestee, const FOnProfileUIClosedDelegate* Delegate) const; /** fetches the string for swapping the profile */ FText GetProfileSwapText() const; /** profile swap ui handler */ - bool ProfileSwapUI(const int ControllerIndex, bool bShowOnlineOnly, const IOnlineExternalUI::FOnLoginUIClosedDelegate* Delegate) const; + bool ProfileSwapUI(const int ControllerIndex, bool bShowOnlineOnly, const FOnLoginUIClosedDelegate* Delegate) const; }; \ No newline at end of file diff --git a/Source/ShooterGame/Private/UI/Style/ShooterChatWidgetStyle.cpp b/Source/ShooterGame/Private/UI/Style/ShooterChatWidgetStyle.cpp index 8811b8f..b815318 100644 --- a/Source/ShooterGame/Private/UI/Style/ShooterChatWidgetStyle.cpp +++ b/Source/ShooterGame/Private/UI/Style/ShooterChatWidgetStyle.cpp @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ShooterGame.h" #include "ShooterChatWidgetStyle.h" diff --git a/Source/ShooterGame/Private/UI/Style/ShooterChatWidgetStyle.h b/Source/ShooterGame/Private/UI/Style/ShooterChatWidgetStyle.h index 030aa6b..1a37bfc 100644 --- a/Source/ShooterGame/Private/UI/Style/ShooterChatWidgetStyle.h +++ b/Source/ShooterGame/Private/UI/Style/ShooterChatWidgetStyle.h @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once diff --git a/Source/ShooterGame/Private/UI/Style/ShooterMenuItemWidgetStyle.cpp b/Source/ShooterGame/Private/UI/Style/ShooterMenuItemWidgetStyle.cpp index 881553b..ed9332e 100644 --- a/Source/ShooterGame/Private/UI/Style/ShooterMenuItemWidgetStyle.cpp +++ b/Source/ShooterGame/Private/UI/Style/ShooterMenuItemWidgetStyle.cpp @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ShooterGame.h" #include "ShooterMenuItemWidgetStyle.h" diff --git a/Source/ShooterGame/Private/UI/Style/ShooterMenuItemWidgetStyle.h b/Source/ShooterGame/Private/UI/Style/ShooterMenuItemWidgetStyle.h index 808b349..6f22aec 100644 --- a/Source/ShooterGame/Private/UI/Style/ShooterMenuItemWidgetStyle.h +++ b/Source/ShooterGame/Private/UI/Style/ShooterMenuItemWidgetStyle.h @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once diff --git a/Source/ShooterGame/Private/UI/Style/ShooterMenuSoundsWidgetStyle.cpp b/Source/ShooterGame/Private/UI/Style/ShooterMenuSoundsWidgetStyle.cpp index a9ee2bb..658e3c7 100644 --- a/Source/ShooterGame/Private/UI/Style/ShooterMenuSoundsWidgetStyle.cpp +++ b/Source/ShooterGame/Private/UI/Style/ShooterMenuSoundsWidgetStyle.cpp @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ShooterGame.h" #include "ShooterMenuSoundsWidgetStyle.h" diff --git a/Source/ShooterGame/Private/UI/Style/ShooterMenuSoundsWidgetStyle.h b/Source/ShooterGame/Private/UI/Style/ShooterMenuSoundsWidgetStyle.h index e5cd5fc..56cd1ad 100644 --- a/Source/ShooterGame/Private/UI/Style/ShooterMenuSoundsWidgetStyle.h +++ b/Source/ShooterGame/Private/UI/Style/ShooterMenuSoundsWidgetStyle.h @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once diff --git a/Source/ShooterGame/Private/UI/Style/ShooterMenuWidgetStyle.cpp b/Source/ShooterGame/Private/UI/Style/ShooterMenuWidgetStyle.cpp index 226c597..05a2d2f 100644 --- a/Source/ShooterGame/Private/UI/Style/ShooterMenuWidgetStyle.cpp +++ b/Source/ShooterGame/Private/UI/Style/ShooterMenuWidgetStyle.cpp @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ShooterGame.h" #include "ShooterMenuWidgetStyle.h" diff --git a/Source/ShooterGame/Private/UI/Style/ShooterMenuWidgetStyle.h b/Source/ShooterGame/Private/UI/Style/ShooterMenuWidgetStyle.h index aa4dd85..45417d8 100644 --- a/Source/ShooterGame/Private/UI/Style/ShooterMenuWidgetStyle.h +++ b/Source/ShooterGame/Private/UI/Style/ShooterMenuWidgetStyle.h @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once diff --git a/Source/ShooterGame/Private/UI/Style/ShooterOptionsWidgetStyle.cpp b/Source/ShooterGame/Private/UI/Style/ShooterOptionsWidgetStyle.cpp index f5cca7f..e69f08a 100644 --- a/Source/ShooterGame/Private/UI/Style/ShooterOptionsWidgetStyle.cpp +++ b/Source/ShooterGame/Private/UI/Style/ShooterOptionsWidgetStyle.cpp @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ShooterGame.h" #include "ShooterOptionsWidgetStyle.h" diff --git a/Source/ShooterGame/Private/UI/Style/ShooterOptionsWidgetStyle.h b/Source/ShooterGame/Private/UI/Style/ShooterOptionsWidgetStyle.h index 0dc2b85..0bd3d46 100644 --- a/Source/ShooterGame/Private/UI/Style/ShooterOptionsWidgetStyle.h +++ b/Source/ShooterGame/Private/UI/Style/ShooterOptionsWidgetStyle.h @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once diff --git a/Source/ShooterGame/Private/UI/Style/ShooterScoreboardWidgetStyle.cpp b/Source/ShooterGame/Private/UI/Style/ShooterScoreboardWidgetStyle.cpp index 6da2341..a7cfe51 100644 --- a/Source/ShooterGame/Private/UI/Style/ShooterScoreboardWidgetStyle.cpp +++ b/Source/ShooterGame/Private/UI/Style/ShooterScoreboardWidgetStyle.cpp @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ShooterGame.h" #include "ShooterScoreboardWidgetStyle.h" diff --git a/Source/ShooterGame/Private/UI/Style/ShooterScoreboardWidgetStyle.h b/Source/ShooterGame/Private/UI/Style/ShooterScoreboardWidgetStyle.h index ed7c086..6ec6a04 100644 --- a/Source/ShooterGame/Private/UI/Style/ShooterScoreboardWidgetStyle.h +++ b/Source/ShooterGame/Private/UI/Style/ShooterScoreboardWidgetStyle.h @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once diff --git a/Source/ShooterGame/Private/UI/Style/ShooterStyle.cpp b/Source/ShooterGame/Private/UI/Style/ShooterStyle.cpp index 8dafa14..b15eac8 100644 --- a/Source/ShooterGame/Private/UI/Style/ShooterStyle.cpp +++ b/Source/ShooterGame/Private/UI/Style/ShooterStyle.cpp @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ShooterGame.h" #include "ShooterStyle.h" @@ -28,12 +28,13 @@ FName FShooterStyle::GetStyleSetName() return StyleSetName; } -#define IMAGE_BRUSH( RelativePath, ... ) FSlateImageBrush( FPaths::GameContentDir() / "Slate"/ RelativePath + TEXT(".png"), __VA_ARGS__ ) -#define BOX_BRUSH( RelativePath, ... ) FSlateBoxBrush( FPaths::GameContentDir() / "Slate"/ RelativePath + TEXT(".png"), __VA_ARGS__ ) -#define BORDER_BRUSH( RelativePath, ... ) FSlateBorderBrush( FPaths::GameContentDir() / "Slate"/ RelativePath + TEXT(".png"), __VA_ARGS__ ) -#define TTF_FONT( RelativePath, ... ) FSlateFontInfo( FPaths::GameContentDir() / "Slate"/ RelativePath + TEXT(".ttf"), __VA_ARGS__ ) -#define OTF_FONT( RelativePath, ... ) FSlateFontInfo( FPaths::GameContentDir() / "Slate"/ RelativePath + TEXT(".otf"), __VA_ARGS__ ) +#define IMAGE_BRUSH( RelativePath, ... ) FSlateImageBrush( FPaths::ProjectContentDir() / "Slate"/ RelativePath + TEXT(".png"), __VA_ARGS__ ) +#define BOX_BRUSH( RelativePath, ... ) FSlateBoxBrush( FPaths::ProjectContentDir() / "Slate"/ RelativePath + TEXT(".png"), __VA_ARGS__ ) +#define BORDER_BRUSH( RelativePath, ... ) FSlateBorderBrush( FPaths::ProjectContentDir() / "Slate"/ RelativePath + TEXT(".png"), __VA_ARGS__ ) +#define TTF_FONT( RelativePath, ... ) FSlateFontInfo( FPaths::ProjectContentDir() / "Slate"/ RelativePath + TEXT(".ttf"), __VA_ARGS__ ) +#define OTF_FONT( RelativePath, ... ) FSlateFontInfo( FPaths::ProjectContentDir() / "Slate"/ RelativePath + TEXT(".otf"), __VA_ARGS__ ) +PRAGMA_DISABLE_OPTIMIZATION TSharedRef< FSlateStyleSet > FShooterStyle::Create() { TSharedRef StyleRef = FSlateGameResources::New(FShooterStyle::GetStyleSetName(), "/Game/UI/Styles", "/Game/UI/Styles"); @@ -58,6 +59,12 @@ TSharedRef< FSlateStyleSet > FShooterStyle::Create() .SetShadowOffset(FIntPoint(-1,1)) ); + Style.Set("ShooterGame.MenuStoreListTextStyle", FTextBlockStyle() + .SetFont(TTF_FONT("Fonts/Roboto-Black", 14)) + .SetColorAndOpacity(FLinearColor::White) + .SetShadowOffset(FIntPoint(-1, 1)) + ); + Style.Set("ShooterGame.ScoreboardListTextStyle", FTextBlockStyle() .SetFont(TTF_FONT("Fonts/Roboto-Black", 14)) .SetColorAndOpacity(FLinearColor::White) @@ -112,8 +119,25 @@ TSharedRef< FSlateStyleSet > FShooterStyle::Create() .SetShadowOffset(FIntPoint(-1,1)) ); + Style.Set("ShooterGame.Switch.Left", FInlineTextImageStyle() + .SetImage(IMAGE_BRUSH("Images/SwitchButtonLeft", FVector2D(32, 32))) + ); + + Style.Set("ShooterGame.Switch.Right", FInlineTextImageStyle() + .SetImage(IMAGE_BRUSH("Images/SwitchButtonRight", FVector2D(32, 32))) + ); + + Style.Set("ShooterGame.Switch.Up", FInlineTextImageStyle() + .SetImage(IMAGE_BRUSH("Images/SwitchButtonUp", FVector2D(32, 32))) + ); + + Style.Set("ShooterGame.Switch.Down", FInlineTextImageStyle() + .SetImage(IMAGE_BRUSH("Images/SwitchButtonDown", FVector2D(32, 32))) + ); + return StyleRef; } +PRAGMA_ENABLE_OPTIMIZATION #undef IMAGE_BRUSH #undef BOX_BRUSH diff --git a/Source/ShooterGame/Private/UI/Style/ShooterStyle.h b/Source/ShooterGame/Private/UI/Style/ShooterStyle.h index f715248..bc5f2db 100644 --- a/Source/ShooterGame/Private/UI/Style/ShooterStyle.h +++ b/Source/ShooterGame/Private/UI/Style/ShooterStyle.h @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once diff --git a/Source/ShooterGame/Private/UI/Widgets/SChatWidget.cpp b/Source/ShooterGame/Private/UI/Widgets/SChatWidget.cpp index 429976c..435e435 100644 --- a/Source/ShooterGame/Private/UI/Widgets/SChatWidget.cpp +++ b/Source/ShooterGame/Private/UI/Widgets/SChatWidget.cpp @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ShooterGame.h" #include "SChatWidget.h" @@ -25,6 +25,12 @@ void SChatWidget::Construct(const FArguments& InArgs, const FLocalPlayerContext& //some constant values const int32 PaddingValue = 2; + // Copy the font we'll be using for chat, and limit the font fallback to localized only, for performance reasons + ChatFont = FShooterStyle::Get().GetFontStyle("ShooterGame.ChatFont"); + + ChatFont.FontFallback = EFontFallback::FF_LocalizedFallback; + + // Initialize Menu ChildSlot .HAlign(HAlign_Left) @@ -64,7 +70,7 @@ void SChatWidget::Construct(const FArguments& InArgs, const FLocalPlayerContext& .MinDesiredWidth(CHAT_BOX_WIDTH) .ClearKeyboardFocusOnCommit(false) .HintText(NSLOCTEXT("ChatWidget", "SaySomething", "Say Something...")) - .Font(FShooterStyle::Get().GetFontStyle("ShooterGame.ChatFont")) + .Font(ChatFont) .Style(&ChatStyle->TextEntryStyle) ] ] @@ -227,7 +233,7 @@ TSharedRef SChatWidget::GenerateChatRow(TSharedPtr ChatLin [ SNew(STextBlock) .Text(ChatLine->ChatString) - .Font(FShooterStyle::Get().GetFontStyle("ShooterGame.ChatFont")) + .Font(ChatFont) .ColorAndOpacity(this, &SChatWidget::GetChatLineColor) .WrapTextAt(CHAT_BOX_WIDTH - CHAT_BOX_PADDING) ]; diff --git a/Source/ShooterGame/Private/UI/Widgets/SChatWidget.h b/Source/ShooterGame/Private/UI/Widgets/SChatWidget.h index 178c85e..7dbe446 100644 --- a/Source/ShooterGame/Private/UI/Widgets/SChatWidget.h +++ b/Source/ShooterGame/Private/UI/Widgets/SChatWidget.h @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once @@ -123,6 +123,9 @@ class SChatWidget : public SCompoundWidget, public ShooterHUDPCTrackerBase /** Style to use for this chat widget */ const struct FShooterChatStyle *ChatStyle; + + /** Copy of the font used for chat, with the font fallback value modified */ + FSlateFontInfo ChatFont; }; diff --git a/Source/ShooterGame/Private/UI/Widgets/SShooterConfirmationDialog.cpp b/Source/ShooterGame/Private/UI/Widgets/SShooterConfirmationDialog.cpp index 66955b2..8390855 100644 --- a/Source/ShooterGame/Private/UI/Widgets/SShooterConfirmationDialog.cpp +++ b/Source/ShooterGame/Private/UI/Widgets/SShooterConfirmationDialog.cpp @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ShooterGame.h" #include "ShooterStyle.h" @@ -57,10 +57,11 @@ void SShooterConfirmationDialog::Construct( const FArguments& InArgs ) [ SNew( SButton ) .ContentPadding(100) - .OnClicked(InArgs._OnConfirmClicked) + .OnClicked(this, &SShooterConfirmationDialog::OnConfirmHandler) .Text(InArgs._ConfirmText) .TextStyle(FShooterStyle::Get(), "ShooterGame.MenuHeaderTextStyle") .ButtonStyle(ButtonStyle) + .IsFocusable(false) ] +SHorizontalBox::Slot() @@ -76,6 +77,7 @@ void SShooterConfirmationDialog::Construct( const FArguments& InArgs ) .TextStyle(FShooterStyle::Get(), "ShooterGame.MenuHeaderTextStyle") .ButtonStyle(ButtonStyle) .Visibility(InArgs._CancelText.IsEmpty() == false ? EVisibility::Visible : EVisibility::Collapsed) + .IsFocusable(false) ] ] ]; @@ -91,6 +93,11 @@ FReply SShooterConfirmationDialog::OnFocusReceived(const FGeometry& MyGeometry, return FReply::Handled().ReleaseMouseCapture().SetUserFocus(SharedThis(this), EFocusCause::SetDirectly, true); } +FReply SShooterConfirmationDialog::OnConfirmHandler() +{ + return ExecuteConfirm(FSlateApplication::Get().GetUserIndexForKeyboard()); +} + FReply SShooterConfirmationDialog::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& KeyEvent) { const FKey Key = KeyEvent.GetKey(); @@ -124,60 +131,67 @@ FReply SShooterConfirmationDialog::OnKeyDown(const FGeometry& MyGeometry, const } // For testing on PC - if ((Key == EKeys::Enter || Key == EKeys::Gamepad_FaceButton_Bottom) && !KeyEvent.IsRepeat()) + if ((Key == EKeys::Enter || Key == EKeys::Virtual_Accept) && !KeyEvent.IsRepeat()) + { + return ExecuteConfirm(UserIndex); + } + else if (Key == EKeys::Escape || Key == EKeys::Virtual_Back) { - if(OnConfirm.IsBound()) + if(OnCancel.IsBound()) { - //these two cases should be combined when we move to using PlatformUserIDs rather than ControllerIDs. + return OnCancel.Execute(); + } + } + + return FReply::Unhandled(); +} + +FReply SShooterConfirmationDialog::ExecuteConfirm(const int32 UserIndex) +{ + if (OnConfirm.IsBound()) + { + //these two cases should be combined when we move to using PlatformUserIDs rather than ControllerIDs. #if PLATFORM_PS4 - bool bExecute = false; - // For controller reconnection, bind the confirming controller to the owner of this dialog - if (DialogType == EShooterDialogType::ControllerDisconnected && PlayerOwner != nullptr) + bool bExecute = false; + // For controller reconnection, bind the confirming controller to the owner of this dialog + if (DialogType == EShooterDialogType::ControllerDisconnected && PlayerOwner != nullptr) + { + const auto OnlineSub = IOnlineSubsystem::Get(); + if (OnlineSub) { - const auto OnlineSub = IOnlineSubsystem::Get(); - if (OnlineSub) + const auto IdentityInterface = OnlineSub->GetIdentityInterface(); + if (IdentityInterface.IsValid()) { - const auto IdentityInterface = OnlineSub->GetIdentityInterface(); - if (IdentityInterface.IsValid()) + TSharedPtr IncomingUserId = IdentityInterface->GetUniquePlayerId(UserIndex); + FUniqueNetIdRepl DisconnectedId = PlayerOwner->GetCachedUniqueNetId(); + + if (*IncomingUserId == *DisconnectedId) { - TSharedPtr IncomingUserId = IdentityInterface->GetUniquePlayerId(UserIndex); - TSharedPtr DisconnectedId = PlayerOwner->GetCachedUniqueNetId(); - - if (*IncomingUserId == *DisconnectedId) - { - PlayerOwner->SetControllerId(UserIndex); - bExecute = true; - } + PlayerOwner->SetControllerId(UserIndex); + bExecute = true; } } } - else - { - bExecute = true; - } - - if (bExecute) - { - return OnConfirm.Execute(); - } -#else - // For controller reconnection, bind the confirming controller to the owner of this dialog - if (DialogType == EShooterDialogType::ControllerDisconnected && PlayerOwner != nullptr) - { - PlayerOwner->SetControllerId(UserIndex); - } + } + else + { + bExecute = true; + } + if (bExecute) + { return OnConfirm.Execute(); -#endif } - } - else if (Key == EKeys::Escape || Key == EKeys::Gamepad_FaceButton_Right) - { - if(OnCancel.IsBound()) +#else + // For controller reconnection, bind the confirming controller to the owner of this dialog + if (DialogType == EShooterDialogType::ControllerDisconnected && PlayerOwner != nullptr) { - return OnCancel.Execute(); + PlayerOwner->SetControllerId(UserIndex); } + + return OnConfirm.Execute(); +#endif } return FReply::Unhandled(); -} +} \ No newline at end of file diff --git a/Source/ShooterGame/Private/UI/Widgets/SShooterConfirmationDialog.h b/Source/ShooterGame/Private/UI/Widgets/SShooterConfirmationDialog.h index c16aa9b..2b92122 100644 --- a/Source/ShooterGame/Private/UI/Widgets/SShooterConfirmationDialog.h +++ b/Source/ShooterGame/Private/UI/Widgets/SShooterConfirmationDialog.h @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once #include "SlateBasics.h" @@ -43,4 +43,8 @@ class SShooterConfirmationDialog : public SCompoundWidget virtual FReply OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& KeyEvent) override; private: + + FReply OnConfirmHandler(); + FReply ExecuteConfirm(const int32 UserIndex); + }; diff --git a/Source/ShooterGame/Private/UI/Widgets/SShooterDemoHUD.cpp b/Source/ShooterGame/Private/UI/Widgets/SShooterDemoHUD.cpp index 6c69e41..af17b3e 100644 --- a/Source/ShooterGame/Private/UI/Widgets/SShooterDemoHUD.cpp +++ b/Source/ShooterGame/Private/UI/Widgets/SShooterDemoHUD.cpp @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ShooterGame.h" #include "SShooterDemoHUD.h" @@ -75,7 +75,7 @@ int32 SShooterReplayTimeline::OnPaint(const FPaintArgs& Args, const FGeometry& A if ((ImageBrush != nullptr) && (ImageBrush->DrawAs != ESlateBrushDrawType::NoDrawType) && DemoDriver.IsValid()) { const bool bIsEnabled = ShouldBeEnabled(bParentEnabled); - const uint32 DrawEffects = bIsEnabled ? ESlateDrawEffect::None : ESlateDrawEffect::DisabledEffect; + const ESlateDrawEffect DrawEffects = bIsEnabled ? ESlateDrawEffect::None : ESlateDrawEffect::DisabledEffect; const FLinearColor FinalColorAndOpacity( InWidgetStyle.GetColorAndOpacityTint() * ColorAndOpacity.Get() * ImageBrush->GetTint( InWidgetStyle ) ); @@ -96,7 +96,6 @@ int32 SShooterReplayTimeline::OnPaint(const FPaintArgs& Args, const FGeometry& A IndicatorLayerId, AllottedGeometry.ToPaintGeometry(Offset, ImageBrush->ImageSize), ImageBrush, - MyClippingRect, DrawEffects, FinalColorAndOpacity ); @@ -193,6 +192,7 @@ void SShooterDemoHUD::Construct(const FArguments& InArgs) .AutoHeight() [ SNew(SCheckBox) + .IsFocusable(false) .Style(FCoreStyle::Get(), "ToggleButtonCheckbox") .IsChecked(this, &SShooterDemoHUD::IsPauseChecked) .OnCheckStateChanged(this, &SShooterDemoHUD::OnPauseCheckStateChanged) @@ -221,7 +221,7 @@ FText SShooterDemoHUD::GetCurrentReplayTime() const return FText::GetEmpty(); } - return FText::AsTimespan(FTimespan(0, 0, static_cast(DemoDriver->DemoCurrentTime))); + return FText::AsTimespan(FTimespan::FromSeconds(DemoDriver->DemoCurrentTime)); } FText SShooterDemoHUD::GetTotalReplayTime() const @@ -238,7 +238,7 @@ FText SShooterDemoHUD::GetTotalReplayTime() const return FText::GetEmpty(); } - return FText::AsTimespan(FTimespan(0, 0, static_cast(DemoDriver->DemoTotalTime))); + return FText::AsTimespan(FTimespan::FromSeconds(DemoDriver->DemoTotalTime)); } FText SShooterDemoHUD::GetPlaybackSpeed() const diff --git a/Source/ShooterGame/Private/UI/Widgets/SShooterDemoHUD.h b/Source/ShooterGame/Private/UI/Widgets/SShooterDemoHUD.h index 6ad1003..706a606 100644 --- a/Source/ShooterGame/Private/UI/Widgets/SShooterDemoHUD.h +++ b/Source/ShooterGame/Private/UI/Widgets/SShooterDemoHUD.h @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once diff --git a/Source/ShooterGame/Private/UI/Widgets/SShooterScoreboardWidget.cpp b/Source/ShooterGame/Private/UI/Widgets/SShooterScoreboardWidget.cpp index e1f2197..15a72c9 100644 --- a/Source/ShooterGame/Private/UI/Widgets/SShooterScoreboardWidget.cpp +++ b/Source/ShooterGame/Private/UI/Widgets/SShooterScoreboardWidget.cpp @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ShooterGame.h" #include "SShooterScoreboardWidget.h" @@ -12,6 +12,8 @@ // @todo: prevent interaction on PC for now (see OnFocusReceived for reasons) #if PLATFORM_XBOXONE #define INTERACTIVE_SCOREBOARD 1 +#else +#define INTERACTIVE_SCOREBOARD 0 #endif #define NORM_PADDING (FMargin(5)) @@ -119,13 +121,13 @@ void SShooterScoreboardWidget::Construct(const FArguments& InArgs) ); } -void SShooterScoreboardWidget::StoreTalkingPlayerData(FString TalkingPlayerName, bool bIsTalking) +void SShooterScoreboardWidget::StoreTalkingPlayerData(const FUniqueNetId& PlayerId, bool bIsTalking) { bool bIsFound = false; int32 FoundIndex = -1; for (int32 i = 0; i < PlayersTalkingThisFrame.Num(); ++i) { - if (PlayersTalkingThisFrame[i].Key == TalkingPlayerName) + if (PlayersTalkingThisFrame[i].Key.Get() == PlayerId) { FoundIndex = i; bIsFound = true; @@ -141,7 +143,7 @@ void SShooterScoreboardWidget::StoreTalkingPlayerData(FString TalkingPlayerName, } else { - PlayersTalkingThisFrame.Add(TPairInitializer(TalkingPlayerName, bIsTalking)); + PlayersTalkingThisFrame.Emplace(PlayerId.AsShared(), bIsTalking); } } @@ -149,7 +151,7 @@ FText SShooterScoreboardWidget::GetMatchRestartText() const { if (PCOwner.IsValid() && (PCOwner->GetWorld() != NULL )) { - AShooterGameState* const GameState = Cast(PCOwner->GetWorld()->GameState); + AShooterGameState* const GameState = PCOwner->GetWorld()->GetGameState(); if (GameState) { if (GameState->RemainingTime > 0) @@ -263,7 +265,7 @@ void SShooterScoreboardWidget::UpdatePlayerStateMaps() { if (PCOwner.IsValid()) { - AShooterGameState* const GameState = Cast(PCOwner->GetWorld()->GameState); + AShooterGameState* const GameState = PCOwner->GetWorld()->GetGameState(); if (GameState) { bool bRequiresWidgetUpdate = false; @@ -338,7 +340,7 @@ FReply SShooterScoreboardWidget::OnKeyDown(const FGeometry& MyGeometry, const FK OnSelectedPlayerNext(); Result = FReply::Handled(); } - else if (Key == EKeys::Enter || Key == EKeys::Gamepad_FaceButton_Bottom) + else if (Key == EKeys::Enter || Key == EKeys::Virtual_Accept) { ProfileUIOpened(); Result = FReply::Handled(); @@ -576,10 +578,9 @@ EVisibility SShooterScoreboardWidget::SpeakerIconVisibility(const FTeamPlayer Te AShooterPlayerState* PlayerState = GetSortedPlayerState(TeamPlayer); if (PlayerState) { - FString PlayerName = PlayerState->GetShortPlayerName(); for (int32 i = 0; i < PlayersTalkingThisFrame.Num(); ++i) { - if (PlayerName == PlayersTalkingThisFrame[i].Key && PlayersTalkingThisFrame[i].Value) + if (PlayerState->UniqueId == PlayersTalkingThisFrame[i].Key && PlayersTalkingThisFrame[i].Value) { return EVisibility::Visible; } @@ -610,6 +611,13 @@ FText SShooterScoreboardWidget::GetPlayerName(const FTeamPlayer TeamPlayer) cons return FText::GetEmpty(); } +bool SShooterScoreboardWidget::ShouldPlayerBeDisplayed(const FTeamPlayer TeamPlayer) const +{ + const AShooterPlayerState* PlayerState = GetSortedPlayerState(TeamPlayer); + + return PlayerState != nullptr && !PlayerState->bOnlySpectator; +} + FSlateColor SShooterScoreboardWidget::GetPlayerColor(const FTeamPlayer TeamPlayer) const { // If this is the owner players row, tint the text color to show ourselves more clearly @@ -746,10 +754,15 @@ TSharedRef SShooterScoreboardWidget::MakePlayerRows(uint8 TeamNum) cons for (int32 PlayerIndex=0; PlayerIndex < PlayerStateMaps[TeamNum].Num(); PlayerIndex++ ) { - PlayerRows->AddSlot() .AutoHeight() - [ - MakePlayerRow(FTeamPlayer(TeamNum, PlayerIndex)) - ]; + FTeamPlayer TeamPlayer(TeamNum, PlayerIndex); + + if (ShouldPlayerBeDisplayed(TeamPlayer)) + { + PlayerRows->AddSlot().AutoHeight() + [ + MakePlayerRow(TeamPlayer) + ]; + } } return PlayerRows; diff --git a/Source/ShooterGame/Private/UI/Widgets/SShooterScoreboardWidget.h b/Source/ShooterGame/Private/UI/Widgets/SShooterScoreboardWidget.h index 15d140f..360488b 100644 --- a/Source/ShooterGame/Private/UI/Widgets/SShooterScoreboardWidget.h +++ b/Source/ShooterGame/Private/UI/Widgets/SShooterScoreboardWidget.h @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once @@ -101,7 +101,7 @@ class SShooterScoreboardWidget : public SBorder virtual FReply OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) override; /** Called when the scoreboard is displayed, Stores the name and whether or not a player is currently talking */ - void StoreTalkingPlayerData(FString PlayerName, bool bIsTalking); + void StoreTalkingPlayerData(const FUniqueNetId& PlayerId, bool bIsTalking); protected: @@ -138,6 +138,9 @@ class SShooterScoreboardWidget : public SBorder /** get player name */ FText GetPlayerName(const FTeamPlayer TeamPlayer) const; + /** get whether or not the player should be displayed on the scoreboard */ + bool ShouldPlayerBeDisplayed(const FTeamPlayer TeamPlayer) const; + /** get player color */ FSlateColor GetPlayerColor(const FTeamPlayer TeamPlayer) const; @@ -223,7 +226,7 @@ class SShooterScoreboardWidget : public SBorder TArray LastTeamPlayerCount; /** holds talking player data */ - TArray> PlayersTalkingThisFrame; + TArray, bool>> PlayersTalkingThisFrame; /** holds player info rows */ TSharedPtr ScoreboardData; diff --git a/Source/ShooterGame/Private/UI/Widgets/SShooterSplitScreenLobbyWidget.cpp b/Source/ShooterGame/Private/UI/Widgets/SShooterSplitScreenLobbyWidget.cpp index dbba30c..ac3217c 100644 --- a/Source/ShooterGame/Private/UI/Widgets/SShooterSplitScreenLobbyWidget.cpp +++ b/Source/ShooterGame/Private/UI/Widgets/SShooterSplitScreenLobbyWidget.cpp @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ShooterGame.h" #include "ShooterStyle.h" @@ -22,20 +22,26 @@ static FAutoConsoleVariableRef CVarShooterSplitScreenMax( void SShooterSplitScreenLobby::Construct( const FArguments& InArgs ) { -#if PLATFORM_XBOXONE - PressToPlayText = LOCTEXT("PressToPlay", "Press A to Play"); - PressToFindText = LOCTEXT("PressToFind", "Press A to Find Match"); - PressToStartMatchText = LOCTEXT("PressToStart", "Press A To Start Match"); -#else +#if PLATFORM_PS4 PressToPlayText = LOCTEXT("PressToPlay", "Press cross button to Play"); PressToFindText = LOCTEXT("PressToFind", "Press cross button to Find Match"); PressToStartMatchText = LOCTEXT("PressToStart", "Press cross button To Start Match"); +#else + PressToPlayText = LOCTEXT("PressToPlay", "Press A to Play"); + PressToFindText = LOCTEXT("PressToFind", "Press A to Find Match"); + PressToStartMatchText = LOCTEXT("PressToStart", "Press A To Start Match"); +#endif + +#if PLATFORM_SWITCH + PressToPlayText = LOCTEXT("PressToPlay", " Select User"); + PressToFindText = LOCTEXT("PressToFind", "Press to Find Match"); + PressToStartMatchText = LOCTEXT("PressToStart", " Start Match / Connect Controllers"); + PlayAsGuestText = LOCTEXT("PlayAsGuest", " Play As Guest"); #endif PlayerOwner = InArgs._PlayerOwner; bIsJoining = false; - bIsOnline = true; const FShooterMenuStyle* ItemStyle = &FShooterStyle::Get().GetWidgetStyle("DefaultShooterMenuStyle"); const FSlateBrush* SlotBrush = &ItemStyle->LeftBackgroundBrush; @@ -90,19 +96,23 @@ void SShooterSplitScreenLobby::Construct( const FArguments& InArgs ) .HAlign(HAlign_Center) [ //first slot needs to have some text to say 'press X to start match'. Only master user can start the match. - SAssignNew(UserTextWidgets[0], STextBlock) + SAssignNew(UserTextWidgets[0], SRichTextBlock) .TextStyle(FShooterStyle::Get(), "ShooterGame.MenuHeaderTextStyle") - .ColorAndOpacity(MenuTitleTextColor) +// .ColorAndOpacity(MenuTitleTextColor) .Text(PressToPlayText) + .DecoratorStyleSet(&FShooterStyle::Get()) + + SRichTextBlock::ImageDecorator() ] +SVerticalBox::Slot() .Padding(5.0f) .VAlign(VAlign_Bottom) .HAlign(HAlign_Center) [ - SNew(STextBlock) + SNew(SRichTextBlock) .TextStyle(FShooterStyle::Get(), "ShooterGame.SplitScreenLobby.StartMatchTextStyle") .Text(this, &SShooterSplitScreenLobby::GetPlayFindText) + .DecoratorStyleSet(&FShooterStyle::Get()) + + SRichTextBlock::ImageDecorator() ] ] ] @@ -114,6 +124,39 @@ void SShooterSplitScreenLobby::Construct( const FArguments& InArgs ) .HeightOverride(SlotHeight) .WidthOverride(SlotWidth) [ +#if PLATFORM_SWITCH + SNew(SBorder) + .Padding(0.0f) + .BorderImage(SlotBrush) + .BorderBackgroundColor(FLinearColor(1.0f, 1.0f, 1.0f, 1.0f)) + [ + SNew(SVerticalBox) + +SVerticalBox::Slot() + .Padding(0.0f) + .VAlign(VAlign_Bottom) + .HAlign(HAlign_Center) + [ + //first slot needs to have some text to say 'press X to start match'. Only master user can start the match. + SAssignNew(UserTextWidgets[1], SRichTextBlock) + .TextStyle(FShooterStyle::Get(), "ShooterGame.MenuHeaderTextStyle") + //.ColorAndOpacity(MenuTitleTextColor) + .Text(PressToPlayText) + .DecoratorStyleSet(&FShooterStyle::Get()) + + SRichTextBlock::ImageDecorator() + ] + +SVerticalBox::Slot() + .Padding(5.0f) + .VAlign(VAlign_Bottom) + .HAlign(HAlign_Center) + [ + SNew(SRichTextBlock) + .TextStyle(FShooterStyle::Get(), "ShooterGame.SplitScreenLobby.StartMatchTextStyle") + .Text(this, &SShooterSplitScreenLobby::GetPlayAsGuestText) + .DecoratorStyleSet(&FShooterStyle::Get()) + + SRichTextBlock::ImageDecorator() + ] + ] +#else SNew(SBorder) .Padding(0.0f) .VAlign(VAlign_Center) @@ -121,11 +164,14 @@ void SShooterSplitScreenLobby::Construct( const FArguments& InArgs ) .BorderImage(SlotBrush) .BorderBackgroundColor(FLinearColor(1.0f, 1.0f, 1.0f, 1.0f)) [ - SAssignNew(UserTextWidgets[1], STextBlock) + SAssignNew(UserTextWidgets[1], SRichTextBlock) .TextStyle(FShooterStyle::Get(), "ShooterGame.MenuHeaderTextStyle") - .ColorAndOpacity(MenuTitleTextColor) + //.ColorAndOpacity(MenuTitleTextColor) .Text(PressToPlayText) + .DecoratorStyleSet(&FShooterStyle::Get()) + + SRichTextBlock::ImageDecorator() ] +#endif ] ] ] @@ -150,10 +196,12 @@ void SShooterSplitScreenLobby::Construct( const FArguments& InArgs ) .BorderImage(SlotBrush) .BorderBackgroundColor(FLinearColor(1.0f, 1.0f, 1.0f, 1.0f)) [ - SAssignNew(UserTextWidgets[2], STextBlock) + SAssignNew(UserTextWidgets[2], SRichTextBlock) .TextStyle(FShooterStyle::Get(), "ShooterGame.MenuHeaderTextStyle") - .ColorAndOpacity(MenuTitleTextColor) + //.ColorAndOpacity(MenuTitleTextColor) .Text(PressToPlayText) + .DecoratorStyleSet(&FShooterStyle::Get()) + + SRichTextBlock::ImageDecorator() ] ] ] @@ -172,10 +220,12 @@ void SShooterSplitScreenLobby::Construct( const FArguments& InArgs ) .BorderImage(SlotBrush) .BorderBackgroundColor(FLinearColor(1.0f, 1.0f, 1.0f, 1.0f)) [ - SAssignNew(UserTextWidgets[3], STextBlock) + SAssignNew(UserTextWidgets[3], SRichTextBlock) .TextStyle(FShooterStyle::Get(), "ShooterGame.MenuHeaderTextStyle") - .ColorAndOpacity(MenuTitleTextColor) + //.ColorAndOpacity(MenuTitleTextColor) .Text(PressToPlayText) + .DecoratorStyleSet(&FShooterStyle::Get()) + + SRichTextBlock::ImageDecorator() ] ] ] @@ -223,7 +273,7 @@ void SShooterSplitScreenLobby::UpdateSlots() if ( LocalPlayer != NULL ) { - UserTextWidgets[i]->SetText( LocalPlayer->GetNickname() ); + UserTextWidgets[i]->SetText( FText::FromString(LocalPlayer->GetNickname()) ); } else { @@ -242,37 +292,45 @@ void SShooterSplitScreenLobby::UpdateSlots() void SShooterSplitScreenLobby::ConditionallyReadyPlayer( const int ControllerId, const bool bCanShowUI ) { - if (GetGameInstance() == nullptr) + UShooterGameInstance* const GameInstance = GetGameInstance(); + if (GameInstance == nullptr) { return; } - if ( GetGameInstance()->GetNumLocalPlayers() >= GetNumSupportedSlots() ) + if (GameInstance->GetNumLocalPlayers() >= GetNumSupportedSlots() ) { return; // Can't fit any more players at this point } // If we already have this controller id registered, ignore this request - if ( GetGameInstance()->FindLocalPlayerFromControllerId( ControllerId ) != NULL ) + if (GameInstance->FindLocalPlayerFromControllerId( ControllerId ) != NULL ) { return; } - TSharedPtr< const FUniqueNetId > UniqueNetId = GetGameInstance()->GetUniqueNetIdFromControllerId( ControllerId ); + TSharedPtr< const FUniqueNetId > UniqueNetId = GameInstance->GetUniqueNetIdFromControllerId( ControllerId ); const bool bIsPlayerOnline = ( UniqueNetId.IsValid() && IsUniqueIdOnline( *UniqueNetId ) ); - const bool bFoundExistingPlayer = GetGameInstance()->FindLocalPlayerFromUniqueNetId( UniqueNetId ) != nullptr; - + const bool bFoundExistingPlayer = GameInstance->FindLocalPlayerFromUniqueNetId( UniqueNetId ) != nullptr; + const EOnlineMode OnlineMode = GameInstance->GetOnlineMode(); // If this is an online game, reject and show sign-in if: // 1. We have already registered this FUniqueNetId // 2. This player is not currently signed in and online - if ( bIsOnline && ( bFoundExistingPlayer || !bIsPlayerOnline ) ) + // on Swtich, always show the login UI even if a Lan match + if ( +#if PLATFORM_SWITCH + bCanShowUI && +#else + OnlineMode == EOnlineMode::Online && +#endif + ( bFoundExistingPlayer || !bIsPlayerOnline ) ) { const auto ExternalUI = Online::GetExternalUIInterface(); if ( bCanShowUI && ExternalUI.IsValid() ) { - ExternalUI->ShowLoginUI( ControllerId, false, IOnlineExternalUI::FOnLoginUIClosedDelegate::CreateSP( this, &SShooterSplitScreenLobby::HandleLoginUIClosedAndReady ) ); + ExternalUI->ShowLoginUI( ControllerId, OnlineMode == EOnlineMode::Online, false, FOnLoginUIClosedDelegate::CreateSP( this, &SShooterSplitScreenLobby::HandleLoginUIClosedAndReady ) ); } return; @@ -289,7 +347,7 @@ void SShooterSplitScreenLobby::ConditionallyReadyPlayer( const int ControllerId, PendingControllerId = ControllerId; Identity->GetUserPrivilege( *UniqueNetId, - bIsOnline ? EUserPrivileges::CanPlayOnline : EUserPrivileges::CanPlay, + OnlineMode != EOnlineMode::Offline ? EUserPrivileges::CanPlayOnline : EUserPrivileges::CanPlay, IOnlineIdentity::FOnGetUserPrivilegeCompleteDelegate::CreateSP(this, &SShooterSplitScreenLobby::OnUserCanPlay)); } } @@ -435,7 +493,8 @@ bool SShooterSplitScreenLobby::IsUniqueIdOnline( const FUniqueNetId& UniqueId ) FReply SShooterSplitScreenLobby::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) { - if (GetGameInstance() == nullptr) + const UShooterGameInstance* GameInstance = GetGameInstance(); + if (GameInstance == nullptr) { return FReply::Unhandled(); } @@ -445,23 +504,23 @@ FReply SShooterSplitScreenLobby::OnKeyDown(const FGeometry& MyGeometry, const FK ULocalPlayer * ExistingPlayer = GEngine->GetLocalPlayerFromControllerId(GetGameInstance()->GetGameViewportClient(), UserIndex); - if ((Key == EKeys::Enter || Key == EKeys::Gamepad_FaceButton_Bottom) && !InKeyEvent.IsRepeat()) + if ((Key == EKeys::Enter || Key == EKeys::Virtual_Accept) && !InKeyEvent.IsRepeat()) { // See if we are already tracking this user if ( ExistingPlayer != NULL ) { // See if this is the initial user - if ( ExistingPlayer == GetGameInstance()->GetFirstGamePlayer() ) + if ( ExistingPlayer == GameInstance->GetFirstGamePlayer() ) { // If this is the initial user, start the game - if ( !bIsOnline || ConfirmSponsorsSatisfied() ) + if (GameInstance->GetOnlineMode() != EOnlineMode::Online || ConfirmSponsorsSatisfied() ) { return MasterUserPlay.Execute(); } else { // Show warning that the guest needs the sponsor - UShooterGameViewportClient * ShooterViewport = Cast(GetGameInstance()->GetGameViewportClient()); + UShooterGameViewportClient * ShooterViewport = Cast(GameInstance->GetGameViewportClient()); if ( ShooterViewport != NULL ) { @@ -487,7 +546,17 @@ FReply SShooterSplitScreenLobby::OnKeyDown(const FGeometry& MyGeometry, const FK return FReply::Handled(); } - else if (Key == EKeys::Escape || Key == EKeys::Gamepad_FaceButton_Right || Key == EKeys::Global_Back) +#if PLATFORM_SWITCH + else if ((Key == EKeys::Gamepad_FaceButton_Top) && !InKeyEvent.IsRepeat()) + { + GEngine->DeferredCommands.Add(TEXT("Npad.ConnectUI")); + } + else if (GameInstance->GetOnlineMode() != EOnlineMode::Online && (Key == EKeys::Gamepad_FaceButton_Left) && !InKeyEvent.IsRepeat()) + { + ConditionallyReadyPlayer(UserIndex, false); + } +#endif + else if (Key == EKeys::Escape || Key == EKeys::Virtual_Back || Key == EKeys::Global_Back) { if ( ExistingPlayer != NULL && ExistingPlayer == GetGameInstance()->GetFirstGamePlayer() ) { @@ -504,22 +573,58 @@ FReply SShooterSplitScreenLobby::OnKeyDown(const FGeometry& MyGeometry, const FK FReply SShooterSplitScreenLobby::OnFocusReceived(const FGeometry& MyGeometry, const FFocusEvent& InFocusEvent) { - return FReply::Handled().ReleaseMouseCapture().SetUserFocus(SharedThis( this ), EFocusCause::SetDirectly, true); + // focus all possible users + for (int Index = 0; Index < GShooterSplitScreenMax; Index++) + { + FSlateApplication::Get().SetUserFocus(Index, SharedThis(this), EFocusCause::SetDirectly); + } + return FReply::Handled().ReleaseMouseCapture(); + } void SShooterSplitScreenLobby::OnFocusLost( const FFocusEvent& InFocusEvent ) { } -void SShooterSplitScreenLobby::HandleLoginUIClosedAndReady( TSharedPtr UniqueId, const int UserIndex ) +void SShooterSplitScreenLobby::HandleLoginUIClosedAndReady( TSharedPtr UniqueId, const int UserIndex, const FOnlineError& Error ) { + const UShooterGameInstance* GameInstance = GetGameInstance(); + if (GameInstance == nullptr) + { + return; + } + + EOnlineMode OnlineMode = GameInstance->GetOnlineMode(); + // If a player signed in, UniqueId will be valid, and we can place him in the open slot. if ( UniqueId.IsValid() ) { - if ( !bIsOnline || IsUniqueIdOnline( *UniqueId ) ) + if ( OnlineMode != EOnlineMode::Online || IsUniqueIdOnline( *UniqueId ) ) { ConditionallyReadyPlayer( UserIndex, false ); } + else if (!IsUniqueIdOnline(*UniqueId)) + { + if (OnlineMode == EOnlineMode::Online) + { + OnLoginCompleteDelegateHandle = IOnlineSubsystem::Get()->GetIdentityInterface()->AddOnLoginCompleteDelegate_Handle(UserIndex, FOnLoginCompleteDelegate::CreateRaw(this, &SShooterSplitScreenLobby::OnLoginComplete)); + Online::GetIdentityInterface()->Login(UserIndex, FOnlineAccountCredentials()); + } + else + { + ConditionallyReadyPlayer(UserIndex, false); + } + } + } +} + +void SShooterSplitScreenLobby::OnLoginComplete(int32 LocalUserNum, bool bWasSuccessful, const FUniqueNetId& UserId, const FString& Error) +{ + IOnlineSubsystem::Get()->GetIdentityInterface()->ClearOnLoginCompleteDelegate_Handle(LocalUserNum, OnLoginCompleteDelegateHandle); + + if (bWasSuccessful && IsUniqueIdOnline(*GetGameInstance()->GetUniqueNetIdFromControllerId(LocalUserNum))) + { + ConditionallyReadyPlayer(LocalUserNum, false); } } @@ -540,4 +645,17 @@ FText SShooterSplitScreenLobby::GetPlayFindText() const return bIsJoining ? PressToFindText : PressToStartMatchText; } +#if PLATFORM_SWITCH +FText SShooterSplitScreenLobby::GetPlayAsGuestText() const +{ + const UShooterGameInstance* GameInstance = GetGameInstance(); + if (GameInstance == nullptr) + { + return FText(); + } + + return GameInstance->GetOnlineMode() != EOnlineMode::Offline ? FText() : PlayAsGuestText; +} +#endif + #undef LOCTEXT_NAMESPACE diff --git a/Source/ShooterGame/Private/UI/Widgets/SShooterSplitScreenLobbyWidget.h b/Source/ShooterGame/Private/UI/Widgets/SShooterSplitScreenLobbyWidget.h index d630245..fce333d 100644 --- a/Source/ShooterGame/Private/UI/Widgets/SShooterSplitScreenLobbyWidget.h +++ b/Source/ShooterGame/Private/UI/Widgets/SShooterSplitScreenLobbyWidget.h @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once @@ -33,8 +33,6 @@ class SShooterSplitScreenLobby : public SCompoundWidget void SetIsJoining( const bool _bIsJoining ) { bIsJoining =_bIsJoining; } - void SetIsOnline( const bool _bIsOnline ) { bIsOnline =_bIsOnline; } - private: bool IsUniqueIdOnline( const FUniqueNetId& ControllerId ) const; @@ -49,11 +47,14 @@ class SShooterSplitScreenLobby : public SCompoundWidget virtual void Tick( const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime ) override; - void HandleLoginUIClosedAndReady(TSharedPtr UniqueId, const int UserIndex); + void HandleLoginUIClosedAndReady(TSharedPtr UniqueId, const int UserIndex, const FOnlineError& Error = FOnlineError()); UShooterGameInstance* GetGameInstance() const; FText GetPlayFindText() const; +#if PLATFORM_SWITCH + FText GetPlayAsGuestText() const; +#endif FReply OnOkOrCancel(); bool ConfirmSponsorsSatisfied() const; @@ -67,7 +68,7 @@ class SShooterSplitScreenLobby : public SCompoundWidget FOnClicked MasterUserBack; FOnClicked MasterUserPlay; - TSharedPtr UserTextWidgets[MAX_POSSIBLE_SLOTS]; + TSharedPtr UserTextWidgets[MAX_POSSIBLE_SLOTS]; TSharedPtr UserSlots[MAX_POSSIBLE_SLOTS]; /** used for holding on to the splitscreen lobby widget so we can switch back to that UI after the LoginFailure UI pops up */ @@ -76,12 +77,15 @@ class SShooterSplitScreenLobby : public SCompoundWidget FText PressToPlayText; FText PressToFindText; FText PressToStartMatchText; +#if PLATFORM_SWITCH + FText PlayAsGuestText; +#endif + + void OnLoginComplete(int32 LocalUserNum, bool bWasSuccessful, const FUniqueNetId& UserId, const FString& Error); + FDelegateHandle OnLoginCompleteDelegateHandle; int PendingControllerId; /** True if we joining a match */ bool bIsJoining; - - /** True if we are online */ - bool bIsOnline; }; diff --git a/Source/ShooterGame/Private/Weapons/ShooterDamageType.cpp b/Source/ShooterGame/Private/Weapons/ShooterDamageType.cpp index e0772d8..f1e46b4 100644 --- a/Source/ShooterGame/Private/Weapons/ShooterDamageType.cpp +++ b/Source/ShooterGame/Private/Weapons/ShooterDamageType.cpp @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ShooterGame.h" #include "Weapons/ShooterDamageType.h" diff --git a/Source/ShooterGame/Private/Weapons/ShooterProjectile.cpp b/Source/ShooterGame/Private/Weapons/ShooterProjectile.cpp index 8155158..86ebab5 100644 --- a/Source/ShooterGame/Private/Weapons/ShooterProjectile.cpp +++ b/Source/ShooterGame/Private/Weapons/ShooterProjectile.cpp @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ShooterGame.h" #include "Weapons/ShooterProjectile.h" @@ -23,7 +23,7 @@ AShooterProjectile::AShooterProjectile(const FObjectInitializer& ObjectInitializ ParticleComp = ObjectInitializer.CreateDefaultSubobject(this, TEXT("ParticleComp")); ParticleComp->bAutoActivate = false; ParticleComp->bAutoDestroy = false; - ParticleComp->AttachParent = RootComponent; + ParticleComp->SetupAttachment(RootComponent); MovementComp = ObjectInitializer.CreateDefaultSubobject(this, TEXT("ProjectileComp")); MovementComp->UpdatedComponent = CollisionComp; @@ -124,7 +124,7 @@ void AShooterProjectile::OnRep_Exploded() const FVector EndTrace = GetActorLocation() + ProjDirection * 150; FHitResult Impact; - if (!GetWorld()->LineTraceSingleByChannel(Impact, StartTrace, EndTrace, COLLISION_PROJECTILE, FCollisionQueryParams(TEXT("ProjClient"), true, Instigator))) + if (!GetWorld()->LineTraceSingleByChannel(Impact, StartTrace, EndTrace, COLLISION_PROJECTILE, FCollisionQueryParams(SCENE_QUERY_STAT(ProjClient), true, Instigator))) { // failsafe Impact.ImpactPoint = GetActorLocation(); diff --git a/Source/ShooterGame/Private/Weapons/ShooterWeapon.cpp b/Source/ShooterGame/Private/Weapons/ShooterWeapon.cpp index 2509fd9..f857bb4 100644 --- a/Source/ShooterGame/Private/Weapons/ShooterWeapon.cpp +++ b/Source/ShooterGame/Private/Weapons/ShooterWeapon.cpp @@ -1,17 +1,17 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ShooterGame.h" #include "Weapons/ShooterWeapon.h" +#include "Player/ShooterCharacter.h" #include "Particles/ParticleSystemComponent.h" #include "Bots/ShooterAIController.h" #include "Online/ShooterPlayerState.h" -#include "Perception/AISense_Hearing.h" #include "UI/ShooterHUD.h" AShooterWeapon::AShooterWeapon(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { Mesh1P = ObjectInitializer.CreateDefaultSubobject(this, TEXT("WeaponMesh1P")); - Mesh1P->MeshComponentUpdateFlag = EMeshComponentUpdateFlag::OnlyTickPoseWhenRendered; + Mesh1P->VisibilityBasedAnimTickOption = EVisibilityBasedAnimTickOption::OnlyTickPoseWhenRendered; Mesh1P->bReceivesDecals = false; Mesh1P->CastShadow = false; Mesh1P->SetCollisionObjectType(ECC_WorldDynamic); @@ -20,7 +20,7 @@ AShooterWeapon::AShooterWeapon(const FObjectInitializer& ObjectInitializer) : Su RootComponent = Mesh1P; Mesh3P = ObjectInitializer.CreateDefaultSubobject(this, TEXT("WeaponMesh3P")); - Mesh3P->MeshComponentUpdateFlag = EMeshComponentUpdateFlag::OnlyTickPoseWhenRendered; + Mesh3P->VisibilityBasedAnimTickOption = EVisibilityBasedAnimTickOption::OnlyTickPoseWhenRendered; Mesh3P->bReceivesDecals = false; Mesh3P->CastShadow = true; Mesh3P->SetCollisionObjectType(ECC_WorldDynamic); @@ -29,7 +29,7 @@ AShooterWeapon::AShooterWeapon(const FObjectInitializer& ObjectInitializer) : Su Mesh3P->SetCollisionResponseToChannel(COLLISION_WEAPON, ECR_Block); Mesh3P->SetCollisionResponseToChannel(ECC_Visibility, ECR_Block); Mesh3P->SetCollisionResponseToChannel(COLLISION_PROJECTILE, ECR_Block); - Mesh3P->AttachParent = Mesh1P; + Mesh3P->SetupAttachment(Mesh1P); bLoopedMuzzleFX = false; bLoopedFireAnim = false; @@ -105,6 +105,8 @@ void AShooterWeapon::OnEquip(const AShooterWeapon* LastWeapon) { PlayWeaponSound(EquipSound); } + + AShooterCharacter::NotifyEquipWeapon.Broadcast(MyPawn, this); } void AShooterWeapon::OnEquipFinished() @@ -154,6 +156,8 @@ void AShooterWeapon::OnUnEquip() GetWorldTimerManager().ClearTimer(TimerHandle_OnEquipFinished); } + AShooterCharacter::NotifyUnEquipWeapon.Broadcast(MyPawn, this); + DetermineWeaponState(); } @@ -164,14 +168,14 @@ void AShooterWeapon::OnEnterInventory(AShooterCharacter* NewOwner) void AShooterWeapon::OnLeaveInventory() { - if (Role == ROLE_Authority) + if (IsAttachedToPawn()) { - SetOwningPawn(NULL); + OnUnEquip(); } - if (IsAttachedToPawn()) + if (Role == ROLE_Authority) { - OnUnEquip(); + SetOwningPawn(NULL); } } @@ -190,14 +194,14 @@ void AShooterWeapon::AttachMeshToPawn() USkeletalMeshComponent* PawnMesh3p = MyPawn->GetSpecifcPawnMesh(false); Mesh1P->SetHiddenInGame( false ); Mesh3P->SetHiddenInGame( false ); - Mesh1P->AttachTo(PawnMesh1p, AttachPoint); - Mesh3P->AttachTo(PawnMesh3p, AttachPoint); + Mesh1P->AttachToComponent(PawnMesh1p, FAttachmentTransformRules::KeepRelativeTransform, AttachPoint); + Mesh3P->AttachToComponent(PawnMesh3p, FAttachmentTransformRules::KeepRelativeTransform, AttachPoint); } else { USkeletalMeshComponent* UseWeaponMesh = GetWeaponMesh(); USkeletalMeshComponent* UsePawnMesh = MyPawn->GetPawnMesh(); - UseWeaponMesh->AttachTo(UsePawnMesh, AttachPoint); + UseWeaponMesh->AttachToComponent(UsePawnMesh, FAttachmentTransformRules::KeepRelativeTransform, AttachPoint); UseWeaponMesh->SetHiddenInGame( false ); } } @@ -205,10 +209,10 @@ void AShooterWeapon::AttachMeshToPawn() void AShooterWeapon::DetachMeshFromPawn() { - Mesh1P->DetachFromParent(); + Mesh1P->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); Mesh1P->SetHiddenInGame(true); - Mesh3P->DetachFromParent(); + Mesh3P->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); Mesh3P->SetHiddenInGame(true); } @@ -221,7 +225,7 @@ void AShooterWeapon::StartFire() if (Role < ROLE_Authority) { ServerStartFire(); - } + } if (!bWantsToFire) { @@ -232,7 +236,7 @@ void AShooterWeapon::StartFire() void AShooterWeapon::StopFire() { - if (Role < ROLE_Authority) + if ((Role < ROLE_Authority) && MyPawn && MyPawn->IsLocallyControlled()) { ServerStopFire(); } @@ -367,7 +371,7 @@ void AShooterWeapon::GiveAmmo(int AddAmount) // start reload if clip was empty if (GetCurrentAmmoInClip() <= 0 && CanReload() && - MyPawn->GetWeapon() == this) + MyPawn && (MyPawn->GetWeapon() == this)) { ClientStartReload(); } @@ -407,6 +411,21 @@ void AShooterWeapon::UseAmmo() } } +void AShooterWeapon::HandleReFiring() +{ + // Update TimerIntervalAdjustment + UWorld* MyWorld = GetWorld(); + + float SlackTimeThisFrame = FMath::Max(0.0f, (MyWorld->TimeSeconds - LastFireTime) - WeaponConfig.TimeBetweenShots); + + if (bAllowAutomaticWeaponCatchup) + { + TimerIntervalAdjustment -= SlackTimeThisFrame; + } + + HandleFiring(); +} + void AShooterWeapon::HandleFiring() { if ((CurrentAmmoInClip > 0 || HasInfiniteClip() || HasInfiniteAmmo()) && CanFire()) @@ -468,7 +487,8 @@ void AShooterWeapon::HandleFiring() bRefiring = (CurrentState == EWeaponState::Firing && WeaponConfig.TimeBetweenShots > 0.0f); if (bRefiring) { - GetWorldTimerManager().SetTimer(TimerHandle_HandleFiring, this, &AShooterWeapon::HandleFiring, WeaponConfig.TimeBetweenShots, false); + GetWorldTimerManager().SetTimer(TimerHandle_HandleFiring, this, &AShooterWeapon::HandleReFiring, FMath::Max(WeaponConfig.TimeBetweenShots + TimerIntervalAdjustment, SMALL_NUMBER), false); + TimerIntervalAdjustment = 0.f; } } @@ -576,7 +596,6 @@ void AShooterWeapon::OnBurstStarted() { HandleFiring(); } - UAISense_Hearing::ReportNoiseEvent(this, GetActorLocation(), 50000.0, this); } void AShooterWeapon::OnBurstFinished() @@ -589,10 +608,12 @@ void AShooterWeapon::OnBurstFinished() { StopSimulatingWeaponFire(); } - UAISense_Hearing::ReportNoiseEvent(this, GetActorLocation(), 50000.0, this); - + GetWorldTimerManager().ClearTimer(TimerHandle_HandleFiring); bRefiring = false; + + // reset firing interval adjustment + TimerIntervalAdjustment = 0.0f; } @@ -723,11 +744,9 @@ FVector AShooterWeapon::GetMuzzleDirection() const FHitResult AShooterWeapon::WeaponTrace(const FVector& StartTrace, const FVector& EndTrace) const { - static FName WeaponFireTag = FName(TEXT("WeaponTrace")); // Perform trace to retrieve hit info - FCollisionQueryParams TraceParams(WeaponFireTag, true, Instigator); - TraceParams.bTraceAsyncScene = true; + FCollisionQueryParams TraceParams(SCENE_QUERY_STAT(WeaponTrace), true, Instigator); TraceParams.bReturnPhysicalMaterial = true; FHitResult Hit(ForceInit); @@ -847,9 +866,11 @@ void AShooterWeapon::SimulateWeaponFire() { PC->ClientPlayCameraShake(FireCameraShake, 1); } - if (FireForceFeedback != NULL) + if (FireForceFeedback != NULL && PC->IsVibrationEnabled()) { - PC->ClientPlayForceFeedback(FireForceFeedback, false, "Weapon"); + FForceFeedbackParameters FFParams; + FFParams.Tag = "Weapon"; + PC->ClientPlayForceFeedback(FireForceFeedback, FFParams); } } } diff --git a/Source/ShooterGame/Private/Weapons/ShooterWeapon_Instant.cpp b/Source/ShooterGame/Private/Weapons/ShooterWeapon_Instant.cpp index d59bc76..3a757ae 100644 --- a/Source/ShooterGame/Private/Weapons/ShooterWeapon_Instant.cpp +++ b/Source/ShooterGame/Private/Weapons/ShooterWeapon_Instant.cpp @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ShooterGame.h" #include "Weapons/ShooterWeapon_Instant.h" @@ -31,12 +31,12 @@ void AShooterWeapon_Instant::FireWeapon() CurrentFiringSpread = FMath::Min(InstantConfig.FiringSpreadMax, CurrentFiringSpread + InstantConfig.FiringSpreadIncrement); } -bool AShooterWeapon_Instant::ServerNotifyHit_Validate(const FHitResult Impact, FVector_NetQuantizeNormal ShootDir, int32 RandomSeed, float ReticleSpread) +bool AShooterWeapon_Instant::ServerNotifyHit_Validate(const FHitResult& Impact, FVector_NetQuantizeNormal ShootDir, int32 RandomSeed, float ReticleSpread) { return true; } -void AShooterWeapon_Instant::ServerNotifyHit_Implementation(const FHitResult Impact, FVector_NetQuantizeNormal ShootDir, int32 RandomSeed, float ReticleSpread) +void AShooterWeapon_Instant::ServerNotifyHit_Implementation(const FHitResult& Impact, FVector_NetQuantizeNormal ShootDir, int32 RandomSeed, float ReticleSpread) { const float WeaponAngleDot = FMath::Abs(FMath::Sin(ReticleSpread * PI / 180.f)); @@ -192,7 +192,7 @@ bool AShooterWeapon_Instant::ShouldDealDamage(AActor* TestActor) const { if (GetNetMode() != NM_Client || TestActor->Role == ROLE_Authority || - TestActor->bTearOff) + TestActor->GetTearOff()) { return true; } diff --git a/Source/ShooterGame/Private/Weapons/ShooterWeapon_Projectile.cpp b/Source/ShooterGame/Private/Weapons/ShooterWeapon_Projectile.cpp index 4dc339c..d4dc6a5 100644 --- a/Source/ShooterGame/Private/Weapons/ShooterWeapon_Projectile.cpp +++ b/Source/ShooterGame/Private/Weapons/ShooterWeapon_Projectile.cpp @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ShooterGame.h" #include "Weapons/ShooterWeapon_Projectile.h" diff --git a/Source/ShooterGame/Public/Bots/BTDecorator_HasLoSTo.h b/Source/ShooterGame/Public/Bots/BTDecorator_HasLoSTo.h index 393b1cb..ba2a667 100644 --- a/Source/ShooterGame/Public/Bots/BTDecorator_HasLoSTo.h +++ b/Source/ShooterGame/Public/Bots/BTDecorator_HasLoSTo.h @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once #include "BehaviorTree/BTDecorator.h" diff --git a/Source/ShooterGame/Public/Bots/BTTask_FindPickup.h b/Source/ShooterGame/Public/Bots/BTTask_FindPickup.h index dc37f3e..7096ff0 100644 --- a/Source/ShooterGame/Public/Bots/BTTask_FindPickup.h +++ b/Source/ShooterGame/Public/Bots/BTTask_FindPickup.h @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once #include "BehaviorTree/Tasks/BTTask_BlackboardBase.h" diff --git a/Source/ShooterGame/Public/Bots/BTTask_FindPointNearEnemy.h b/Source/ShooterGame/Public/Bots/BTTask_FindPointNearEnemy.h index 9ad77e4..4413e44 100644 --- a/Source/ShooterGame/Public/Bots/BTTask_FindPointNearEnemy.h +++ b/Source/ShooterGame/Public/Bots/BTTask_FindPointNearEnemy.h @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once #include "BehaviorTree/BTNode.h" diff --git a/Source/ShooterGame/Public/Bots/ShooterAIController.h b/Source/ShooterGame/Public/Bots/ShooterAIController.h index b3ddd07..30aaccb 100644 --- a/Source/ShooterGame/Public/Bots/ShooterAIController.h +++ b/Source/ShooterGame/Public/Bots/ShooterAIController.h @@ -59,7 +59,7 @@ class AShooterAIController : public AAIController public: // Begin AController interface virtual void GameHasEnded(class AActor* EndGameFocus = NULL, bool bIsWinner = false) override; - virtual void Possess(class APawn* InPawn) override; + virtual void OnPossess(class APawn* InPawn) override; virtual void BeginInactiveState() override; // End APlayerController interface @@ -67,6 +67,10 @@ class AShooterAIController : public AAIController void CheckAmmo(const class AShooterWeapon* CurrentWeapon); + void SetEnemy(class APawn* InPawn); + + class AShooterCharacter* GetEnemy() const; + bool HasWeaponLOSToEnemy(AActor* InEnemyActor, const bool bAnyEnemy) const; // Begin AAIController interface @@ -209,7 +213,7 @@ class AShooterAIController : public AAIController /************************* RUNTIME UPDATES **************************/ UFUNCTION(BlueprintCallable, Category = "Update") - void OnPerceptionUpdated(TArray testActors); + void OnPerceptionUpdated(const TArray& testActors); UFUNCTION(BlueprintCallable, Category = "Update") void UpdateData(const float DeltaSeconds); diff --git a/Source/ShooterGame/Public/Bots/ShooterBot.h b/Source/ShooterGame/Public/Bots/ShooterBot.h index d35efd6..91c2f06 100644 --- a/Source/ShooterGame/Public/Bots/ShooterBot.h +++ b/Source/ShooterGame/Public/Bots/ShooterBot.h @@ -1,7 +1,8 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once +#include "ShooterCharacter.h" #include "ShooterBot.generated.h" UCLASS() @@ -15,36 +16,4 @@ class AShooterBot : public AShooterCharacter virtual bool IsFirstPerson() const override; virtual void FaceRotation(FRotator NewRotation, float DeltaTime = 0.f) override; - -public: - //UAISense_Hearing::ReportNoiseEvent(this, GetActorLocation(), 10.0, this); - //UPROPERTY(EditAnywhere, Category = "AI Behavior Tree") - //TArray PatrolPoints; - - - UPROPERTY(EditAnywhere, Category = "AI Behavior Tree") - APawn* Player; - UPROPERTY(EditAnywhere, Category = "AI Behavior Tree") - bool PlayerCanSeeMe = false; - UPROPERTY(EditAnywhere, Category = "AI Behavior Tree") - bool PlayerIsVisible = false; - UPROPERTY(EditAnywhere, Category = "AI Behavior Tree") - FVector PlayerLastLocation = FVector(); - - UPROPERTY(EditAnywhere, Category = "AI Behavior Tree") - FVector CurrentLocation = FVector(); - UPROPERTY(EditAnywhere, Category = "AI Behavior Tree") - bool IAmReloading = false; - UPROPERTY(EditAnywhere, Category = "AI Behavior Tree") - bool NeedReloadNow = false; - UPROPERTY(EditAnywhere, Category = "AI Behavior Tree") - bool NeedReloadSoon = false; - - UPROPERTY(EditAnywhere, Category = "AI Behavior Tree") - FVector NextLocation; - UPROPERTY(EditAnywhere, Category = "AI Behavior Tree") - bool NextLocationIsSafe = false; - //UPROPERTY(EditAnywhere, Category = "AI Behavior Tree") - //TArray PatrolPoints; - }; \ No newline at end of file diff --git a/Source/ShooterGame/Public/EQS/PathfiningTest.h b/Source/ShooterGame/Public/EQS/PathfiningTest.h index 77d000d..458fe45 100644 --- a/Source/ShooterGame/Public/EQS/PathfiningTest.h +++ b/Source/ShooterGame/Public/EQS/PathfiningTest.h @@ -43,15 +43,15 @@ class SHOOTERGAME_API UPathfiningTest : public UEnvQueryTest protected: - DECLARE_DELEGATE_RetVal_SixParams(bool, FTestPathSignature, const FVector&, const FVector&, EPathFindingMode::Type, const ANavigationData&, UNavigationSystem&, const UObject*); - DECLARE_DELEGATE_RetVal_SixParams(float, FFindPathSignature, const FVector&, const FVector&, EPathFindingMode::Type, const ANavigationData&, UNavigationSystem&, const UObject*); + DECLARE_DELEGATE_RetVal_SixParams(bool, FTestPathSignature, const FVector&, const FVector&, EPathFindingMode::Type, const ANavigationData&, UNavigationSystemV1&, const UObject*); + DECLARE_DELEGATE_RetVal_SixParams(float, FFindPathSignature, const FVector&, const FVector&, EPathFindingMode::Type, const ANavigationData&, UNavigationSystemV1&, const UObject*); - bool TestPathFrom(const FVector& ItemPos, const FVector& ContextPos, EPathFindingMode::Type Mode, const ANavigationData& NavData, UNavigationSystem& NavSys, const UObject* PathOwner) const; - bool TestPathTo(const FVector& ItemPos, const FVector& ContextPos, EPathFindingMode::Type Mode, const ANavigationData& NavData, UNavigationSystem& NavSys, const UObject* PathOwner) const; - float FindPathCostFrom(const FVector& ItemPos, const FVector& ContextPos, EPathFindingMode::Type Mode, const ANavigationData& NavData, UNavigationSystem& NavSys, const UObject* PathOwner) const; - float FindPathCostTo(const FVector& ItemPos, const FVector& ContextPos, EPathFindingMode::Type Mode, const ANavigationData& NavData, UNavigationSystem& NavSys, const UObject* PathOwner) const; - float FindPathLengthFrom(const FVector& ItemPos, const FVector& ContextPos, EPathFindingMode::Type Mode, const ANavigationData& NavData, UNavigationSystem& NavSys, const UObject* PathOwner) const; - float FindPathLengthTo(const FVector& ItemPos, const FVector& ContextPos, EPathFindingMode::Type Mode, const ANavigationData& NavData, UNavigationSystem& NavSys, const UObject* PathOwner) const; + bool TestPathFrom(const FVector& ItemPos, const FVector& ContextPos, EPathFindingMode::Type Mode, const ANavigationData& NavData, UNavigationSystemV1& NavSys, const UObject* PathOwner) const; + bool TestPathTo(const FVector& ItemPos, const FVector& ContextPos, EPathFindingMode::Type Mode, const ANavigationData& NavData, UNavigationSystemV1& NavSys, const UObject* PathOwner) const; + float FindPathCostFrom(const FVector& ItemPos, const FVector& ContextPos, EPathFindingMode::Type Mode, const ANavigationData& NavData, UNavigationSystemV1& NavSys, const UObject* PathOwner) const; + float FindPathCostTo(const FVector& ItemPos, const FVector& ContextPos, EPathFindingMode::Type Mode, const ANavigationData& NavData, UNavigationSystemV1& NavSys, const UObject* PathOwner) const; + float FindPathLengthFrom(const FVector& ItemPos, const FVector& ContextPos, EPathFindingMode::Type Mode, const ANavigationData& NavData, UNavigationSystemV1& NavSys, const UObject* PathOwner) const; + float FindPathLengthTo(const FVector& ItemPos, const FVector& ContextPos, EPathFindingMode::Type Mode, const ANavigationData& NavData, UNavigationSystemV1& NavSys, const UObject* PathOwner) const; - ANavigationData* FindNavigationData(UNavigationSystem& NavSys, UObject* Owner) const; + ANavigationData* FindNavigationData(UNavigationSystemV1& NavSys, UObject* Owner) const; }; diff --git a/Source/ShooterGame/Public/EQS/PathfiningTest.h.bak b/Source/ShooterGame/Public/EQS/PathfiningTest.h.bak new file mode 100644 index 0000000..4b8cee7 --- /dev/null +++ b/Source/ShooterGame/Public/EQS/PathfiningTest.h.bak @@ -0,0 +1,57 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "EnvironmentQuery/EnvQueryTest.h" +#include "EnvironmentQuery/Tests/EnvQueryTest_Pathfinding.h" +#include "PathfiningTest.generated.h" + +UCLASS() +class SHOOTERGAME_API UPathfiningTest : public UEnvQueryTest +{ + GENERATED_UCLASS_BODY() + /** testing mode */ + UPROPERTY(EditDefaultsOnly, Category = Pathfinding) + TEnumAsByte TestMode; + + /** context: other end of pathfinding test */ + UPROPERTY(EditDefaultsOnly, Category = Pathfinding) + TSubclassOf Context; + + /** pathfinding direction */ + UPROPERTY(EditDefaultsOnly, Category = Pathfinding) + FAIDataProviderBoolValue PathFromContext; + + /** if set, items with failed path will be invalidated (PathCost, PathLength) */ + UPROPERTY(EditDefaultsOnly, Category = Pathfinding, AdvancedDisplay) + FAIDataProviderBoolValue SkipUnreachable; + + /** navigation filter to use in pathfinding */ + UPROPERTY(EditDefaultsOnly, Category = Pathfinding) + TSubclassOf FilterClass; + + virtual void RunTest(FEnvQueryInstance& QueryInstance) const override; + + virtual FText GetDescriptionTitle() const override; + virtual FText GetDescriptionDetails() const override; + +#if WITH_EDITOR + /** update test properties after changing mode */ + virtual void PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) override; +#endif + virtual void PostLoad() override; + +protected: + + DECLARE_DELEGATE_RetVal_SixParams(bool, FTestPathSignature, const FVector&, const FVector&, EPathFindingMode::Type, const ANavigationData&, UNavigationSystem&, const UObject*); + DECLARE_DELEGATE_RetVal_SixParams(float, FFindPathSignature, const FVector&, const FVector&, EPathFindingMode::Type, const ANavigationData&, UNavigationSystem&, const UObject*); + + bool TestPathFrom(const FVector& ItemPos, const FVector& ContextPos, EPathFindingMode::Type Mode, const ANavigationData& NavData, UNavigationSystem& NavSys, const UObject* PathOwner) const; + bool TestPathTo(const FVector& ItemPos, const FVector& ContextPos, EPathFindingMode::Type Mode, const ANavigationData& NavData, UNavigationSystem& NavSys, const UObject* PathOwner) const; + float FindPathCostFrom(const FVector& ItemPos, const FVector& ContextPos, EPathFindingMode::Type Mode, const ANavigationData& NavData, UNavigationSystem& NavSys, const UObject* PathOwner) const; + float FindPathCostTo(const FVector& ItemPos, const FVector& ContextPos, EPathFindingMode::Type Mode, const ANavigationData& NavData, UNavigationSystem& NavSys, const UObject* PathOwner) const; + float FindPathLengthFrom(const FVector& ItemPos, const FVector& ContextPos, EPathFindingMode::Type Mode, const ANavigationData& NavData, UNavigationSystem& NavSys, const UObject* PathOwner) const; + float FindPathLengthTo(const FVector& ItemPos, const FVector& ContextPos, EPathFindingMode::Type Mode, const ANavigationData& NavData, UNavigationSystem& NavSys, const UObject* PathOwner) const; + + ANavigationData* FindNavigationData(UNavigationSystem& NavSys, UObject* Owner) const; +}; diff --git a/Source/ShooterGame/Public/Effects/ShooterExplosionEffect.h b/Source/ShooterGame/Public/Effects/ShooterExplosionEffect.h index 0a32fe9..b1b16af 100644 --- a/Source/ShooterGame/Public/Effects/ShooterExplosionEffect.h +++ b/Source/ShooterGame/Public/Effects/ShooterExplosionEffect.h @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once @@ -40,12 +40,13 @@ class AShooterExplosionEffect : public AActor UPROPERTY(BlueprintReadOnly, Category=Surface) FHitResult SurfaceHit; - /** spawn explosion */ - virtual void BeginPlay() override; - /** update fading light */ virtual void Tick(float DeltaSeconds) override; +protected: + /** spawn explosion */ + virtual void BeginPlay() override; + private: /** Point light component name */ diff --git a/Source/ShooterGame/Public/Effects/ShooterImpactEffect.h b/Source/ShooterGame/Public/Effects/ShooterImpactEffect.h index f2e38ae..1ed2f75 100644 --- a/Source/ShooterGame/Public/Effects/ShooterImpactEffect.h +++ b/Source/ShooterGame/Public/Effects/ShooterImpactEffect.h @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once diff --git a/Source/ShooterGame/Public/Navigation/MyInfluenceMap.h b/Source/ShooterGame/Public/Navigation/MyInfluenceMap.h index 884e111..286de3c 100644 --- a/Source/ShooterGame/Public/Navigation/MyInfluenceMap.h +++ b/Source/ShooterGame/Public/Navigation/MyInfluenceMap.h @@ -2,6 +2,7 @@ #pragma once #include "Public/Navigation/MyTexture2D.h" +#include "Public/Navigation/MyRecastNavMesh.h" #include "MyInfluenceMap.generated.h" struct InfluenceTile { @@ -17,9 +18,9 @@ class SHOOTERGAME_API AMyInfluenceMap : public AActor private: // How much bias the update towards the existing value compared to the new value? 1 to Historic, 0 to Prediction - float Momentum; + float Momentumm; // How quickly the value decay with distance. - float Decay; + float Decayy; float UpdateFrequency; @@ -33,7 +34,7 @@ class SHOOTERGAME_API AMyInfluenceMap : public AActor // Bitmap representation of influences MyTexture2D* BaseTexture; MyTexture2D* UpdatedTexture; - + // Temp TMap> * BotsVisibilities; diff --git a/Source/ShooterGame/Public/Navigation/MyNavigationQueryFilter.h b/Source/ShooterGame/Public/Navigation/MyNavigationQueryFilter.h index 84c9de3..57d2a8e 100644 --- a/Source/ShooterGame/Public/Navigation/MyNavigationQueryFilter.h +++ b/Source/ShooterGame/Public/Navigation/MyNavigationQueryFilter.h @@ -2,7 +2,7 @@ #pragma once -#include "AI/Navigation/NavFilters/NavigationQueryFilter.h" +#include "NavFilters/NavigationQueryFilter.h" #include "MyNavigationQueryFilter.generated.h" /** @@ -12,6 +12,6 @@ UCLASS() class SHOOTERGAME_API UMyNavigationQueryFilter : public UNavigationQueryFilter { GENERATED_UCLASS_BODY() - - virtual void InitializeFilter(const ANavigationData& NavData, FNavigationQueryFilter& Filter) const override; + //const ANavigationData& NavData, const UObject* Querier, FNavigationQueryFilter& Filter + virtual void InitializeFilter(const ANavigationData& NavData, const UObject* Querier, FNavigationQueryFilter& Filter) const override; }; diff --git a/Source/ShooterGame/Public/Navigation/MyRecastNavMesh.h b/Source/ShooterGame/Public/Navigation/MyRecastNavMesh.h index ee88746..be28878 100644 --- a/Source/ShooterGame/Public/Navigation/MyRecastNavMesh.h +++ b/Source/ShooterGame/Public/Navigation/MyRecastNavMesh.h @@ -1,11 +1,11 @@ // Fill out your copyright notice in the Description page of Project Settings. #pragma once -#include "Runtime/Engine/Classes/AI/Navigation/NavFilters/NavigationQueryFilter.h" +#include "NavFilters/NavigationQueryFilter.h" #include "Runtime/Navmesh/Public/Detour/DetourNavMeshQuery.h" #include "Runtime/Navmesh/Public/Detour/DetourNavMesh.h" -#include "AI/Navigation/PImplRecastNavMesh.h" -#include "AI/Navigation/RecastNavMesh.h" +#include "NavMesh/PImplRecastNavMesh.h" +#include "NavMesh/RecastNavMesh.h" #include "MyRecastNavMesh.generated.h" diff --git a/Source/ShooterGame/Public/Navigation/MyTexture2D.h b/Source/ShooterGame/Public/Navigation/MyTexture2D.h index 528352f..da9c062 100644 --- a/Source/ShooterGame/Public/Navigation/MyTexture2D.h +++ b/Source/ShooterGame/Public/Navigation/MyTexture2D.h @@ -1,7 +1,7 @@ // Fill out your copyright notice in the Description page of Project Settings. #pragma once -#include "AI/Navigation/RecastNavMesh.h" +#include "NavFilters/NavigationQueryFilter.h" #include "Runtime/Engine/Classes/Engine/Texture2D.h" /** * diff --git a/Source/ShooterGame/Public/Online/ShooterGameMode.h b/Source/ShooterGame/Public/Online/ShooterGameMode.h index b4b749d..1a98874 100644 --- a/Source/ShooterGame/Public/Online/ShooterGameMode.h +++ b/Source/ShooterGame/Public/Online/ShooterGameMode.h @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once @@ -28,7 +28,7 @@ class AShooterGameMode : public AGameMode virtual void InitGame(const FString& MapName, const FString& Options, FString& ErrorMessage) override; /** Accept or reject a player attempting to join the server. Fails login if you set the ErrorMessage to a non-empty string. */ - virtual void PreLogin(const FString& Options, const FString& Address, const TSharedPtr& UniqueId, FString& ErrorMessage) override; + virtual void PreLogin(const FString& Options, const FString& Address, const FUniqueNetIdRepl& UniqueId, FString& ErrorMessage) override; /** starts match warmup */ virtual void PostLogin(APlayerController* NewPlayer) override; diff --git a/Source/ShooterGame/Public/Online/ShooterGameSession.h b/Source/ShooterGame/Public/Online/ShooterGameSession.h index 3d044d5..10afacf 100644 --- a/Source/ShooterGame/Public/Online/ShooterGameSession.h +++ b/Source/ShooterGame/Public/Online/ShooterGameSession.h @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once @@ -117,6 +117,12 @@ class SHOOTERGAME_API AShooterGameSession : public AGameSession */ void OnNoMatchesAvailable(); + + /** + * Called when this instance is starting up as a dedicated server + */ + virtual void RegisterServer() override; + /* * Event triggered when a presence session is created * @@ -161,6 +167,17 @@ class SHOOTERGAME_API AShooterGameSession : public AGameSession */ bool HostSession(TSharedPtr UserId, FName SessionName, const FString& GameType, const FString& MapName, bool bIsLAN, bool bIsPresence, int32 MaxNumPlayers); + /** + * Host a new online session with specified settings + * + * @param UserId user that initiated the request + * @param SessionName name of session + * @param SessionSettings settings to create session with + * + * @return bool true if successful, false otherwise + */ + bool HostSession(const TSharedPtr UserId, const FName SessionName, const FOnlineSessionSettings& SessionSettings); + /** * Find an online session * diff --git a/Source/ShooterGame/Public/Online/ShooterGameState.h b/Source/ShooterGame/Public/Online/ShooterGameState.h index 10e4d73..a7b8057 100644 --- a/Source/ShooterGame/Public/Online/ShooterGameState.h +++ b/Source/ShooterGame/Public/Online/ShooterGameState.h @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once diff --git a/Source/ShooterGame/Public/Online/ShooterGame_FreeForAll.h b/Source/ShooterGame/Public/Online/ShooterGame_FreeForAll.h index f09816b..6c863fb 100644 --- a/Source/ShooterGame/Public/Online/ShooterGame_FreeForAll.h +++ b/Source/ShooterGame/Public/Online/ShooterGame_FreeForAll.h @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once diff --git a/Source/ShooterGame/Public/Online/ShooterGame_Menu.h b/Source/ShooterGame/Public/Online/ShooterGame_Menu.h index b41545d..44b7799 100644 --- a/Source/ShooterGame/Public/Online/ShooterGame_Menu.h +++ b/Source/ShooterGame/Public/Online/ShooterGame_Menu.h @@ -1,23 +1,23 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once #include "ShooterGame_Menu.generated.h" UCLASS() -class AShooterGame_Menu : public AGameMode +class AShooterGame_Menu : public AGameModeBase { GENERATED_UCLASS_BODY() public: - // Begin AGameMode interface + // Begin AGameModeBase interface /** skip it, menu doesn't require player start or pawn */ virtual void RestartPlayer(class AController* NewPlayer) override; /** Returns game session class to use */ virtual TSubclassOf GetGameSessionClass() const override; - // End AGameMode interface + // End AGameModeBase interface protected: diff --git a/Source/ShooterGame/Public/Online/ShooterGame_TeamDeathMatch.h b/Source/ShooterGame/Public/Online/ShooterGame_TeamDeathMatch.h index 41d1fa8..2a653d6 100644 --- a/Source/ShooterGame/Public/Online/ShooterGame_TeamDeathMatch.h +++ b/Source/ShooterGame/Public/Online/ShooterGame_TeamDeathMatch.h @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once diff --git a/Source/ShooterGame/Public/Online/ShooterOnlineSessionClient.h b/Source/ShooterGame/Public/Online/ShooterOnlineSessionClient.h new file mode 100644 index 0000000..0c2451c --- /dev/null +++ b/Source/ShooterGame/Public/Online/ShooterOnlineSessionClient.h @@ -0,0 +1,26 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "OnlineSessionClient.h" +#include "ShooterOnlineSessionClient.generated.h" + +UCLASS(Config = Game) +class UShooterOnlineSessionClient : public UOnlineSessionClient +{ + GENERATED_BODY() + +public: + /** Ctor */ + UShooterOnlineSessionClient(); + + virtual void OnSessionUserInviteAccepted( + const bool bWasSuccess, + const int32 ControllerId, + TSharedPtr< const FUniqueNetId > UserId, + const FOnlineSessionSearchResult & InviteResult + ) override; + + virtual void OnPlayTogetherEventReceived(int32 UserIndex, TArray> UserIdList) override; + +}; diff --git a/Source/ShooterGame/Public/Online/ShooterPlayerState.h b/Source/ShooterGame/Public/Online/ShooterPlayerState.h index bd6bb48..f9378fd 100644 --- a/Source/ShooterGame/Public/Online/ShooterPlayerState.h +++ b/Source/ShooterGame/Public/Online/ShooterPlayerState.h @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once diff --git a/Source/ShooterGame/Public/Pickups/ShooterPickup.h b/Source/ShooterGame/Public/Pickups/ShooterPickup.h index 7e59c60..7988aa7 100644 --- a/Source/ShooterGame/Public/Pickups/ShooterPickup.h +++ b/Source/ShooterGame/Public/Pickups/ShooterPickup.h @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once @@ -16,6 +16,7 @@ class AShooterPickup : public AActor /** check if pawn can use this pickup */ virtual bool CanBePickedUp(class AShooterCharacter* TestPawn) const; +protected: /** initial setup */ virtual void BeginPlay() override; @@ -23,8 +24,8 @@ class AShooterPickup : public AActor /** FX component */ UPROPERTY(VisibleDefaultsOnly, Category=Effects) UParticleSystemComponent* PickupPSC; -protected: +protected: /** FX of active pickup */ UPROPERTY(EditDefaultsOnly, Category=Effects) UParticleSystem* ActiveFX; diff --git a/Source/ShooterGame/Public/Pickups/ShooterPickup_Ammo.h b/Source/ShooterGame/Public/Pickups/ShooterPickup_Ammo.h index b0a79df..a0a26b8 100644 --- a/Source/ShooterGame/Public/Pickups/ShooterPickup_Ammo.h +++ b/Source/ShooterGame/Public/Pickups/ShooterPickup_Ammo.h @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once diff --git a/Source/ShooterGame/Public/Pickups/ShooterPickup_Health.h b/Source/ShooterGame/Public/Pickups/ShooterPickup_Health.h index 734a392..063627a 100644 --- a/Source/ShooterGame/Public/Pickups/ShooterPickup_Health.h +++ b/Source/ShooterGame/Public/Pickups/ShooterPickup_Health.h @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once diff --git a/Source/ShooterGame/Public/Player/ShooterCharacter.h b/Source/ShooterGame/Public/Player/ShooterCharacter.h index 9e5f68e..f5c0011 100644 --- a/Source/ShooterGame/Public/Player/ShooterCharacter.h +++ b/Source/ShooterGame/Public/Player/ShooterCharacter.h @@ -1,19 +1,20 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once #include "ShooterTypes.h" -#include "Perception/AISightTargetInterface.h" -#include "Public/Navigation/MyRecastNavMesh.h" #include "ShooterCharacter.generated.h" +DECLARE_MULTICAST_DELEGATE_TwoParams(FOnShooterCharacterEquipWeapon, AShooterCharacter*, AShooterWeapon* /* new */); +DECLARE_MULTICAST_DELEGATE_TwoParams(FOnShooterCharacterUnEquipWeapon, AShooterCharacter*, AShooterWeapon* /* old */); UCLASS(Abstract) -class AShooterCharacter : public ACharacter, public IAISightTargetInterface +class AShooterCharacter : public ACharacter { GENERATED_UCLASS_BODY() - virtual bool CanBeSeenFrom(const FVector& ObserverLocation, FVector& OutSeenLocation, int32& NumberOfLoSChecksPerformed, float& OutSightStrength, const AActor* IgnoreActor = NULL) const override; + virtual void BeginDestroy() override; + /** spawn inventory, setup initial variables */ virtual void PostInitializeComponents() override; @@ -32,54 +33,60 @@ class AShooterCharacter : public ACharacter, public IAISightTargetInterface /** [client] perform PlayerState related setup */ virtual void OnRep_PlayerState() override; - /** - * Add camera pitch to first person mesh. - * - * @param CameraLocation Location of the Camera. - * @param CameraRotation Rotation of the Camera. - */ + /** [server] called to determine if we should pause replication this actor to a specific player */ + virtual bool IsReplicationPausedForConnection(const FNetViewer& ConnectionOwnerNetViewer) override; + + /** [client] called when replication is paused for this actor */ + virtual void OnReplicationPausedChanged(bool bIsReplicationPaused) override; + + /** + * Add camera pitch to first person mesh. + * + * @param CameraLocation Location of the Camera. + * @param CameraRotation Rotation of the Camera. + */ void OnCameraUpdate(const FVector& CameraLocation, const FRotator& CameraRotation); /** get aim offsets */ - UFUNCTION(BlueprintCallable, Category="Game|Weapon") + UFUNCTION(BlueprintCallable, Category = "Game|Weapon") FRotator GetAimOffsets() const; - /** - * Check if pawn is enemy if given controller. - * - * @param TestPC Controller to check against. - */ + /** + * Check if pawn is enemy if given controller. + * + * @param TestPC Controller to check against. + */ bool IsEnemyFor(AController* TestPC) const; ////////////////////////////////////////////////////////////////////////// // Inventory - /** - * [server] add weapon to inventory - * - * @param Weapon Weapon to add. - */ + /** + * [server] add weapon to inventory + * + * @param Weapon Weapon to add. + */ void AddWeapon(class AShooterWeapon* Weapon); - /** - * [server] remove weapon from inventory - * - * @param Weapon Weapon to remove. - */ + /** + * [server] remove weapon from inventory + * + * @param Weapon Weapon to remove. + */ void RemoveWeapon(class AShooterWeapon* Weapon); - /** - * Find in inventory - * - * @param WeaponClass Class of weapon to find. - */ + /** + * Find in inventory + * + * @param WeaponClass Class of weapon to find. + */ class AShooterWeapon* FindWeapon(TSubclassOf WeaponClass); - /** - * [server + local] equips weapon from inventory - * - * @param Weapon Weapon to equip - */ + /** + * [server + local] equips weapon from inventory + * + * @param Weapon Weapon to equip + */ void EquipWeapon(class AShooterWeapon* Weapon); ////////////////////////////////////////////////////////////////////////// @@ -105,10 +112,10 @@ class AShooterCharacter : public ACharacter, public IAISightTargetInterface /** [server + local] change running state */ void SetRunning(bool bNewRunning, bool bToggle); - + ////////////////////////////////////////////////////////////////////////// // Animations - + /** play anim montage */ virtual float PlayAnimMontage(class UAnimMontage* AnimMontage, float InPlayRate = 1.f, FName StartSectionName = NAME_None) override; @@ -124,25 +131,25 @@ class AShooterCharacter : public ACharacter, public IAISightTargetInterface /** setup pawn specific input handlers */ virtual void SetupPlayerInputComponent(class UInputComponent* InputComponent) override; - /** - * Move forward/back - * - * @param Val Movment input to apply - */ + /** + * Move forward/back + * + * @param Val Movment input to apply + */ void MoveForward(float Val); - /** - * Strafe right/left - * - * @param Val Movment input to apply - */ + /** + * Strafe right/left + * + * @param Val Movment input to apply + */ void MoveRight(float Val); - /** - * Move Up/Down in allowed movement modes. - * - * @param Val Movment input to apply - */ + /** + * Move Up/Down in allowed movement modes. + * + * @param Val Movment input to apply + */ void MoveUp(float Val); /* Frame rate independent turn */ @@ -194,44 +201,50 @@ class AShooterCharacter : public ACharacter, public IAISightTargetInterface USkeletalMeshComponent* GetPawnMesh() const; /** get currently equipped weapon */ - UFUNCTION(BlueprintCallable, Category="Game|Weapon") + UFUNCTION(BlueprintCallable, Category = "Game|Weapon") class AShooterWeapon* GetWeapon() const; + /** Global notification when a character equips a weapon. Needed for replication graph. */ + SHOOTERGAME_API static FOnShooterCharacterEquipWeapon NotifyEquipWeapon; + + /** Global notification when a character un-equips a weapon. Needed for replication graph. */ + SHOOTERGAME_API static FOnShooterCharacterUnEquipWeapon NotifyUnEquipWeapon; + /** get weapon attach point */ FName GetWeaponAttachPoint() const; /** get total number of inventory items */ int32 GetInventoryCount() const; - /** - * get weapon from inventory at index. Index validity is not checked. - * - * @param Index Inventory index - */ + /** + * get weapon from inventory at index. Index validity is not checked. + * + * @param Index Inventory index + */ class AShooterWeapon* GetInventoryWeapon(int32 index) const; /** get weapon taget modifier speed */ - UFUNCTION(BlueprintCallable, Category="Game|Weapon") + UFUNCTION(BlueprintCallable, Category = "Game|Weapon") float GetTargetingSpeedModifier() const; /** get targeting state */ - UFUNCTION(BlueprintCallable, Category="Game|Weapon") + UFUNCTION(BlueprintCallable, Category = "Game|Weapon") bool IsTargeting() const; /** get firing state */ - UFUNCTION(BlueprintCallable, Category="Game|Weapon") + UFUNCTION(BlueprintCallable, Category = "Game|Weapon") bool IsFiring() const; /** get the modifier value for running speed */ - UFUNCTION(BlueprintCallable, Category=Pawn) + UFUNCTION(BlueprintCallable, Category = Pawn) float GetRunningSpeedModifier() const; /** get running state */ - UFUNCTION(BlueprintCallable, Category=Pawn) + UFUNCTION(BlueprintCallable, Category = Pawn) bool IsRunning() const; /** get camera view type */ - UFUNCTION(BlueprintCallable, Category=Mesh) + UFUNCTION(BlueprintCallable, Category = Mesh) virtual bool IsFirstPerson() const; /** get max health */ @@ -244,27 +257,27 @@ class AShooterCharacter : public ACharacter, public IAISightTargetInterface float GetLowHealthPercentage() const; /* - * Get either first or third person mesh. - * - * @param WantFirstPerson If true returns the first peron mesh, else returns the third - */ - USkeletalMeshComponent* GetSpecifcPawnMesh( bool WantFirstPerson ) const; + * Get either first or third person mesh. + * + * @param WantFirstPerson If true returns the first peron mesh, else returns the third + */ + USkeletalMeshComponent* GetSpecifcPawnMesh(bool WantFirstPerson) const; /** Update the team color of all player meshes. */ void UpdateTeamColorsAllMIDs(); private: /** pawn mesh: 1st person view */ - UPROPERTY(VisibleDefaultsOnly, Category=Mesh) + UPROPERTY(VisibleDefaultsOnly, Category = Mesh) USkeletalMeshComponent* Mesh1P; protected: /** socket or bone name for attaching weapon mesh */ - UPROPERTY(EditDefaultsOnly, Category=Inventory) + UPROPERTY(EditDefaultsOnly, Category = Inventory) FName WeaponAttachPoint; /** default inventory list */ - UPROPERTY(EditDefaultsOnly, Category=Inventory) + UPROPERTY(EditDefaultsOnly, Category = Inventory) TArray > DefaultInventoryClasses; /** weapons in inventory */ @@ -272,18 +285,18 @@ class AShooterCharacter : public ACharacter, public IAISightTargetInterface TArray Inventory; /** currently equipped weapon */ - UPROPERTY(Transient, ReplicatedUsing=OnRep_CurrentWeapon) + UPROPERTY(Transient, ReplicatedUsing = OnRep_CurrentWeapon) class AShooterWeapon* CurrentWeapon; /** Replicate where this pawn was last hit and damaged */ - UPROPERTY(Transient, ReplicatedUsing=OnRep_LastTakeHitInfo) + UPROPERTY(Transient, ReplicatedUsing = OnRep_LastTakeHitInfo) struct FTakeHitInfo LastTakeHitInfo; /** Time at which point the last take hit info for the actor times out and won't be replicated; Used to stop join-in-progress effects all over the screen */ float LastTakeHitTimeTimeout; /** modifier for max movement speed */ - UPROPERTY(EditDefaultsOnly, Category=Inventory) + UPROPERTY(EditDefaultsOnly, Category = Inventory) float TargetingSpeedModifier; /** current targeting state */ @@ -291,7 +304,7 @@ class AShooterCharacter : public ACharacter, public IAISightTargetInterface uint8 bIsTargeting : 1; /** modifier for max movement speed */ - UPROPERTY(EditDefaultsOnly, Category=Pawn) + UPROPERTY(EditDefaultsOnly, Category = Pawn) float RunningSpeedModifier; /** current running state */ @@ -318,35 +331,35 @@ class AShooterCharacter : public ACharacter, public IAISightTargetInterface TArray MeshMIDs; /** animation played on death */ - UPROPERTY(EditDefaultsOnly, Category=Animation) + UPROPERTY(EditDefaultsOnly, Category = Animation) UAnimMontage* DeathAnim; /** sound played on death, local player only */ - UPROPERTY(EditDefaultsOnly, Category=Pawn) + UPROPERTY(EditDefaultsOnly, Category = Pawn) USoundCue* DeathSound; /** effect played on respawn */ - UPROPERTY(EditDefaultsOnly, Category=Pawn) + UPROPERTY(EditDefaultsOnly, Category = Pawn) UParticleSystem* RespawnFX; /** sound played on respawn */ - UPROPERTY(EditDefaultsOnly, Category=Pawn) + UPROPERTY(EditDefaultsOnly, Category = Pawn) USoundCue* RespawnSound; /** sound played when health is low */ - UPROPERTY(EditDefaultsOnly, Category=Pawn) + UPROPERTY(EditDefaultsOnly, Category = Pawn) USoundCue* LowHealthSound; /** sound played when running */ - UPROPERTY(EditDefaultsOnly, Category=Pawn) + UPROPERTY(EditDefaultsOnly, Category = Pawn) USoundCue* RunLoopSound; /** sound played when stop running */ - UPROPERTY(EditDefaultsOnly, Category=Pawn) + UPROPERTY(EditDefaultsOnly, Category = Pawn) USoundCue* RunStopSound; /** sound played when targeting state changes */ - UPROPERTY(EditDefaultsOnly, Category=Pawn) + UPROPERTY(EditDefaultsOnly, Category = Pawn) USoundCue* TargetingSound; /** used to manipulate with run loop sound */ @@ -358,7 +371,7 @@ class AShooterCharacter : public ACharacter, public IAISightTargetInterface UAudioComponent* LowHealthWarningPlayer; /** handles sounds for running */ - void UpdateRunSounds(bool bNewRunning); + void UpdateRunSounds(); /** handle mesh visibility and updates */ void UpdatePawnMeshes(); @@ -369,17 +382,22 @@ class AShooterCharacter : public ACharacter, public IAISightTargetInterface /** Responsible for cleaning up bodies on clients. */ virtual void TornOff(); +private: + + /** Whether or not the character is moving (based on movement input). */ + bool IsMoving(); + ////////////////////////////////////////////////////////////////////////// // Damage & death public: /** Identifies if pawn is in its dying state */ - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category=Health) - uint32 bIsDying:1; + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Health) + uint32 bIsDying : 1; // Current health of the Pawn - UPROPERTY(EditAnywhere, BlueprintReadWrite, Replicated, Category=Health) + UPROPERTY(EditAnywhere, BlueprintReadWrite, Replicated, Category = Health) float Health; /** Take damage, handle death */ @@ -395,20 +413,20 @@ class AShooterCharacter : public ACharacter, public IAISightTargetInterface virtual bool CanDie(float KillingDamage, FDamageEvent const& DamageEvent, AController* Killer, AActor* DamageCauser) const; /** - * Kills pawn. Server/authority only. - * @param KillingDamage - Damage amount of the killing blow - * @param DamageEvent - Damage event of the killing blow - * @param Killer - Who killed this pawn - * @param DamageCauser - the Actor that directly caused the damage (i.e. the Projectile that exploded, the Weapon that fired, etc) - * @returns true if allowed - */ + * Kills pawn. Server/authority only. + * @param KillingDamage - Damage amount of the killing blow + * @param DamageEvent - Damage event of the killing blow + * @param Killer - Who killed this pawn + * @param DamageCauser - the Actor that directly caused the damage (i.e. the Projectile that exploded, the Weapon that fired, etc) + * @returns true if allowed + */ virtual bool Die(float KillingDamage, struct FDamageEvent const& DamageEvent, class AController* Killer, class AActor* DamageCauser); // Die when we fall out of the world. virtual void FellOutOfWorld(const class UDamageType& dmgType) override; /** Called on the actor right before replication occurs */ - virtual void PreReplication( IRepChangedPropertyTracker & ChangedPropertyTracker ) override; + virtual void PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) override; protected: /** notification when killed, for both the server and client. */ virtual void OnDeath(float KillingDamage, struct FDamageEvent const& DamageEvent, class APawn* InstigatingPawn, class AActor* DamageCauser); @@ -449,11 +467,14 @@ class AShooterCharacter : public ACharacter, public IAISightTargetInterface /** update targeting state */ UFUNCTION(reliable, server, WithValidation) void ServerSetTargeting(bool bNewTargeting); - + /** update targeting state */ UFUNCTION(reliable, server, WithValidation) void ServerSetRunning(bool bNewRunning, bool bToggle); + /** Builds list of points to check for pausing replication for a connection*/ + void BuildPauseReplicationCheckPoints(TArray& RelevancyCheckPoints); + protected: /** Returns Mesh1P subobject **/ FORCEINLINE USkeletalMeshComponent* GetMesh1P() const { return Mesh1P; } diff --git a/Source/ShooterGame/Public/Player/ShooterCharacterMovement.h b/Source/ShooterGame/Public/Player/ShooterCharacterMovement.h index fe6a992..3587d41 100644 --- a/Source/ShooterGame/Public/Player/ShooterCharacterMovement.h +++ b/Source/ShooterGame/Public/Player/ShooterCharacterMovement.h @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. /** * Movement component meant for use with Pawns. diff --git a/Source/ShooterGame/Public/Player/ShooterCheatManager.h b/Source/ShooterGame/Public/Player/ShooterCheatManager.h index 3366260..18ab785 100644 --- a/Source/ShooterGame/Public/Player/ShooterCheatManager.h +++ b/Source/ShooterGame/Public/Player/ShooterCheatManager.h @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once diff --git a/Source/ShooterGame/Public/Player/ShooterDemoSpectator.h b/Source/ShooterGame/Public/Player/ShooterDemoSpectator.h index 334f0bb..3b5d49d 100644 --- a/Source/ShooterGame/Public/Player/ShooterDemoSpectator.h +++ b/Source/ShooterGame/Public/Player/ShooterDemoSpectator.h @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once diff --git a/Source/ShooterGame/Public/Player/ShooterLocalPlayer.h b/Source/ShooterGame/Public/Player/ShooterLocalPlayer.h index 5627df8..8da169e 100644 --- a/Source/ShooterGame/Public/Player/ShooterLocalPlayer.h +++ b/Source/ShooterGame/Public/Player/ShooterLocalPlayer.h @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once diff --git a/Source/ShooterGame/Public/Player/ShooterPersistentUser.h b/Source/ShooterGame/Public/Player/ShooterPersistentUser.h index 29d71da..515f9bf 100644 --- a/Source/ShooterGame/Public/Player/ShooterPersistentUser.h +++ b/Source/ShooterGame/Public/Player/ShooterPersistentUser.h @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once #include "ShooterPersistentUser.generated.h" @@ -53,12 +53,21 @@ class UShooterPersistentUser : public USaveGame return RocketsFired; } + /** Is controller vibration turned on? */ + FORCEINLINE bool GetVibration() const + { + return bVibrationOpt; + } + /** Is the y axis inverted? */ FORCEINLINE bool GetInvertedYAxis() const { return bInvertedYAxis; } + /** Setter for controller vibration option */ + void SetVibration(bool bVibration); + /** Setter for inverted y axis */ void SetInvertedYAxis(bool bInvert); @@ -153,6 +162,9 @@ class UShooterPersistentUser : public USaveGame UPROPERTY() bool bInvertedYAxis; + UPROPERTY() + bool bVibrationOpt; + private: /** Internal. True if data is changed but hasn't been saved. */ bool bIsDirty; diff --git a/Source/ShooterGame/Public/Player/ShooterPlayerCameraManager.h b/Source/ShooterGame/Public/Player/ShooterPlayerCameraManager.h index 1111b25..97a7f9d 100644 --- a/Source/ShooterGame/Public/Player/ShooterPlayerCameraManager.h +++ b/Source/ShooterGame/Public/Player/ShooterPlayerCameraManager.h @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once diff --git a/Source/ShooterGame/Public/Player/ShooterPlayerController.h b/Source/ShooterGame/Public/Player/ShooterPlayerController.h index a6b523e..08d14d1 100644 --- a/Source/ShooterGame/Public/Player/ShooterPlayerController.h +++ b/Source/ShooterGame/Public/Player/ShooterPlayerController.h @@ -1,8 +1,9 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once #include "Online.h" +#include "ShooterLeaderboards.h" #include "ShooterPlayerController.generated.h" class AShooterHUD; @@ -96,6 +97,9 @@ class AShooterPlayerController : public APlayerController UFUNCTION(exec) void SetGodMode(bool bEnable); + /** sets the produce force feedback flag. */ + void SetIsVibrationEnabled(bool bEnable); + /** get infinite ammo cheat */ bool HasInfiniteAmmo() const; @@ -108,6 +112,9 @@ class AShooterPlayerController : public APlayerController /** get gode mode cheat */ bool HasGodMode() const; + /** should produce force feedback? */ + bool IsVibrationEnabled() const; + /** check if gameplay related actions (movement, weapon usage, etc) are allowed right now */ bool IsGameInputAllowed() const; @@ -124,6 +131,9 @@ class AShooterPlayerController : public APlayerController * @param bWasSuccessful true if the server responded successfully to the request */ void OnQueryAchievementsComplete(const FUniqueNetId& PlayerId, const bool bWasSuccessful ); + + UFUNCTION() + void OnLeaderboardReadComplete(bool bWasSuccessful); // Begin APlayerController interface @@ -141,6 +151,8 @@ class AShooterPlayerController : public APlayerController virtual bool SetPause(bool bPause, FCanUnpause CanUnpauseDelegate = FCanUnpause()) override; + virtual FVector GetFocalLocation() const override; + // End APlayerController interface // begin AShooterPlayerController-specific @@ -150,6 +162,11 @@ class AShooterPlayerController : public APlayerController */ void QueryAchievements(); + /** + * Reads backend stats to precache them before first use + */ + void QueryStats(); + /** * Writes a single achievement (unless another write is in progress). * @@ -195,6 +212,9 @@ class AShooterPlayerController : public APlayerController UPROPERTY(Transient, Replicated) uint8 bGodMode : 1; + /** should produce force feedback? */ + uint8 bIsVibrationEnabled : 1; + /** if set, gameplay related actions (movement, weapn usage, etc) are allowed */ uint8 bAllowGameActions : 1; @@ -213,13 +233,25 @@ class AShooterPlayerController : public APlayerController /** try to find spot for death cam */ bool FindDeathCameraSpot(FVector& CameraLocation, FRotator& CameraRotation); + virtual void BeginDestroy() override; + //Begin AActor interface /** after all game elements are created */ virtual void PostInitializeComponents() override; - virtual void BeginPlay() override; + /** Internal. Used to store stats from the online interface. These increment as matches are written */ + int32 StatMatchesPlayed; + int32 StatKills; + int32 StatDeaths; + bool bHasFetchedPlatformData; + /** Internal. Reads the stats from the platform backend to sync online status with local */ + FOnlineLeaderboardReadPtr ReadObject; + FDelegateHandle LeaderboardReadCompleteDelegateHandle; + void ClearLeaderboardDelegate(); + +public: virtual void TickActor(float DeltaTime, enum ELevelTick TickType, FActorTickFunction& ThisTickFunction) override; //End AActor interface @@ -266,6 +298,9 @@ class AShooterPlayerController : public APlayerController /** Updates leaderboard stats at the end of a round */ void UpdateLeaderboardsOnGameEnd(); + /** Updates stats at the end of a round */ + void UpdateStatsOnGameEnd(bool bIsWinner); + /** Updates the save file at the end of a round */ void UpdateSaveFileOnGameEnd(bool bIsWinner); diff --git a/Source/ShooterGame/Public/Player/ShooterPlayerController_Menu.h b/Source/ShooterGame/Public/Player/ShooterPlayerController_Menu.h index 89241e7..2ec08db 100644 --- a/Source/ShooterGame/Public/Player/ShooterPlayerController_Menu.h +++ b/Source/ShooterGame/Public/Player/ShooterPlayerController_Menu.h @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once diff --git a/Source/ShooterGame/Public/Player/ShooterSpectatorPawn.h b/Source/ShooterGame/Public/Player/ShooterSpectatorPawn.h index e4e99c5..a9bf124 100644 --- a/Source/ShooterGame/Public/Player/ShooterSpectatorPawn.h +++ b/Source/ShooterGame/Public/Player/ShooterSpectatorPawn.h @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once #include "ShooterSpectatorPawn.generated.h" diff --git a/Source/ShooterGame/Public/ShooterEngine.h b/Source/ShooterGame/Public/ShooterEngine.h index 0e564e0..f8dd4ae 100644 --- a/Source/ShooterGame/Public/ShooterEngine.h +++ b/Source/ShooterGame/Public/ShooterEngine.h @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once diff --git a/Source/ShooterGame/Public/ShooterGame.h b/Source/ShooterGame/Public/ShooterGame.h index fb20085..c8e56fd 100644 --- a/Source/ShooterGame/Public/ShooterGame.h +++ b/Source/ShooterGame/Public/ShooterGame.h @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once @@ -33,7 +33,7 @@ DECLARE_LOG_CATEGORY_EXTERN(LogShooterWeapon, Log, All); /** Set to 1 to pretend we're building for console even on a PC, for testing purposes */ #define SHOOTER_SIMULATE_CONSOLE_UI 0 -#if PLATFORM_PS4 || PLATFORM_XBOXONE || SHOOTER_SIMULATE_CONSOLE_UI +#if PLATFORM_PS4 || PLATFORM_XBOXONE || PLATFORM_SWITCH || SHOOTER_SIMULATE_CONSOLE_UI || PLATFORM_QUAIL #define SHOOTER_CONSOLE_UI 1 #else #define SHOOTER_CONSOLE_UI 0 diff --git a/Source/ShooterGame/Public/ShooterGameInstance.h b/Source/ShooterGame/Public/ShooterGameInstance.h index 35ecbaa..6e7a69d 100644 --- a/Source/ShooterGame/Public/ShooterGameInstance.h +++ b/Source/ShooterGame/Public/ShooterGameInstance.h @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once @@ -6,6 +6,7 @@ #include "OnlineIdentityInterface.h" #include "OnlineSessionInterface.h" #include "Engine/GameInstance.h" +#include "Engine/NetworkDelegates.h" #include "ShooterGameInstance.generated.h" class FVariantData; @@ -47,6 +48,19 @@ class FShooterPendingInvite bool bPrivilegesCheckedAndAllowed; }; +struct FShooterPlayTogetherInfo +{ + FShooterPlayTogetherInfo() : UserIndex(-1) {} + FShooterPlayTogetherInfo(int32 InUserIndex, const TArray>& InUserIdList) + : UserIndex(InUserIndex) + { + UserIdList.Append(InUserIdList); + } + + int32 UserIndex; + TArray> UserIdList; +}; + class SShooterWaitDialog : public SCompoundWidget { public: @@ -69,6 +83,13 @@ class SShooterWaitDialog : public SCompoundWidget FSlateColor GetTextColor() const; }; +UENUM() +enum class EOnlineMode : uint8 +{ + Offline, + LAN, + Online +}; UCLASS(config=Game) @@ -86,22 +107,27 @@ class UShooterGameInstance : public UGameInstance virtual void Init() override; virtual void Shutdown() override; virtual void StartGameInstance() override; - + virtual void ReceivedNetworkEncryptionToken(const FString& EncryptionToken, const FOnEncryptionKeyResponse& Delegate) override; + virtual void ReceivedNetworkEncryptionAck(const FOnEncryptionKeyResponse& Delegate) override; bool HostGame(ULocalPlayer* LocalPlayer, const FString& GameType, const FString& InTravelURL); bool JoinSession(ULocalPlayer* LocalPlayer, int32 SessionIndexInSearchResults); bool JoinSession(ULocalPlayer* LocalPlayer, const FOnlineSessionSearchResult& SearchResult); + void SetPendingInvite(const FShooterPendingInvite& InPendingInvite); bool PlayDemo(ULocalPlayer* LocalPlayer, const FString& DemoName); /** Travel directly to the named session */ void TravelToSession(const FName& SessionName); + /** Get the Travel URL for a quick match */ + static FString GetQuickMatchUrl(); + /** Begin a hosted quick match */ void BeginHostingQuickMatch(); /** Initiates the session searching */ - bool FindSessions(ULocalPlayer* PlayerOwner, bool bLANMatch); + bool FindSessions(ULocalPlayer* PlayerOwner, bool bIsDedicatedServer, bool bLANMatch); /** Sends the game to the specified state. */ void GotoState(FName NewState); @@ -129,10 +155,13 @@ class UShooterGameInstance : public UGameInstance TSharedPtr< const FUniqueNetId > GetUniqueNetIdFromControllerId( const int ControllerId ); /** Returns true if the game is in online mode */ - bool GetIsOnline() const { return bIsOnline; } + EOnlineMode GetOnlineMode() const { return OnlineMode; } /** Sets the online mode of the game */ - void SetIsOnline(bool bInIsOnline); + void SetOnlineMode(EOnlineMode InOnlineMode); + + /** Updates the status of using multiplayer features */ + void UpdateUsingMultiplayerFeatures(bool bIsUsingMultiplayerFeatures); /** Sets the controller to ignore for pairing changes. Useful when we are showing external UI for manual profile switching. */ void SetIgnorePairingChangeForControllerId( const int32 ControllerId ); @@ -140,9 +169,15 @@ class UShooterGameInstance : public UGameInstance /** Returns true if the passed in local player is signed in and online */ bool IsLocalPlayerOnline(ULocalPlayer* LocalPlayer); + /** Returns true if the passed in local player is signed in*/ + bool IsLocalPlayerSignedIn(ULocalPlayer* LocalPlayer); + /** Returns true if owning player is online. Displays proper messaging if the user can't play */ bool ValidatePlayerForOnlinePlay(ULocalPlayer* LocalPlayer); + /** Returns true if owning player is signed in. Displays proper messaging if the user can't play */ + bool ValidatePlayerIsSignedIn(ULocalPlayer* LocalPlayer); + /** Shuts down the session, and frees any net driver */ void CleanupSessionOnReturnToMenu(); @@ -162,6 +197,18 @@ class UShooterGameInstance : public UGameInstance /** Show approved dialogs for various privileges failures */ void DisplayOnlinePrivilegeFailureDialogs(const FUniqueNetId& UserId, EUserPrivileges::Type Privilege, uint32 PrivilegeResults); + /** @return OnlineSession class to use for this player */ + TSubclassOf GetOnlineSessionClass() override; + + /** Create a session with the default map and game-type with the selected online settings */ + bool HostQuickSession(ULocalPlayer& LocalPlayer, const FOnlineSessionSettings& SessionSettings); + + /** Called when we receive a Play Together system event on PS4 */ + void OnPlayTogetherEventReceived(const int32 UserIndex, const TArray>& UserIdList); + + /** Resets Play Together PS4 system event info after it's been handled */ + void ResetPlayTogetherInfo() { PlayTogetherInfo = FShooterPlayTogetherInfo(); } + private: UPROPERTY(config) @@ -181,8 +228,8 @@ class UShooterGameInstance : public UGameInstance /** URL to travel to after pending network operations */ FString TravelURL; - /** Whether the match is online or not */ - bool bIsOnline; + /** Current online mode of the game (offline, LAN, or online) */ + EOnlineMode OnlineMode; /** If true, enable splitscreen when map starts loading */ bool bPendingEnableSplitscreen; @@ -221,19 +268,21 @@ class UShooterGameInstance : public UGameInstance FDelegateHandle OnDestroySessionCompleteDelegateHandle; FDelegateHandle OnCreatePresenceSessionCompleteDelegateHandle; - virtual void OnSessionUserInviteAccepted( - const bool bWasSuccess, - const int32 ControllerId, - TSharedPtr< const FUniqueNetId > UserId, - const FOnlineSessionSearchResult & InviteResult - ) override; + /** Play Together on PS4 system event info */ + FShooterPlayTogetherInfo PlayTogetherInfo; - void HandleNetworkConnectionStatusChanged( EOnlineServerConnectionStatus::Type LastConnectionStatus, EOnlineServerConnectionStatus::Type ConnectionStatus ); + /** Local player login status when the system is suspended */ + TArray LocalPlayerOnlineStatus; + + /** A hard-coded encryption key used to try out the encryption code. This is NOT SECURE, do not use this technique in production! */ + TArray DebugTestEncryptionKey; + + void HandleNetworkConnectionStatusChanged(const FString& ServiceName, EOnlineServerConnectionStatus::Type LastConnectionStatus, EOnlineServerConnectionStatus::Type ConnectionStatus ); void HandleSessionFailure( const FUniqueNetId& NetId, ESessionFailure::Type FailureType ); - void OnPreLoadMap(); - void OnPostLoadMap(); + void OnPreLoadMap(const FString& MapName); + void OnPostLoadMap(UWorld*); void OnPostDemoPlay(); virtual void HandleDemoPlaybackFailure( EDemoPlayFailure::Type FailureType, const FString& ErrorString ) override; @@ -241,6 +290,9 @@ class UShooterGameInstance : public UGameInstance /** Delegate function executed after checking privileges for starting quick match */ void OnUserCanPlayInvite(const FUniqueNetId& UserId, EUserPrivileges::Type Privilege, uint32 PrivilegeResults); + /** Delegate function executed after checking privileges for Play Together on PS4 */ + void OnUserCanPlayTogether(const FUniqueNetId& UserId, EUserPrivileges::Type Privilege, uint32 PrivilegeResults); + /** Delegate for ending a session */ FOnEndSessionCompleteDelegate OnEndSessionCompleteDelegate; @@ -287,6 +339,9 @@ class UShooterGameInstance : public UGameInstance /** Called after all the local players are registered in a session we're joining */ void FinishJoinSession(EOnJoinSessionCompleteResult::Type Result); + /** Send all invites for the current game session if we've created it because Play Together on PS4 was initiated*/ + void SendPlayTogetherInvites(); + /** * Creates the message menu, clears other menus and sets the KingState to Message. * @@ -302,7 +357,7 @@ class UShooterGameInstance : public UGameInstance bool LoadFrontEndMap(const FString& MapName); /** Sets a rich presence string for all local players. */ - void SetPresenceForLocalPlayers(const FVariantData& PresenceData); + void SetPresenceForLocalPlayers(const FString& StatusStr, const FVariantData& PresenceData); /** Travel directly to the named session */ void InternalTravelToSession(const FName& SessionName); @@ -345,6 +400,8 @@ class UShooterGameInstance : public UGameInstance protected: bool HandleOpenCommand(const TCHAR* Cmd, FOutputDevice& Ar, UWorld* InWorld); + bool HandleDisconnectCommand(const TCHAR* Cmd, FOutputDevice& Ar, UWorld* InWorld); + bool HandleTravelCommand(const TCHAR* Cmd, FOutputDevice& Ar, UWorld* InWorld); }; diff --git a/Source/ShooterGame/Public/ShooterGameUserSettings.h b/Source/ShooterGame/Public/ShooterGameUserSettings.h index 7c77c64..6133e3a 100644 --- a/Source/ShooterGame/Public/ShooterGameUserSettings.h +++ b/Source/ShooterGame/Public/ShooterGameUserSettings.h @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once @@ -32,6 +32,16 @@ class UShooterGameUserSettings : public UGameUserSettings bIsLanMatch = InbIsLanMatch; } + bool IsDedicatedServer() const + { + return bIsDedicatedServer; + } + + void SetDedicatedServer(bool InbIsDedicatedServer) + { + bIsDedicatedServer = InbIsDedicatedServer; + } + // interface UGameUserSettings virtual void SetToDefaults() override; @@ -47,4 +57,8 @@ class UShooterGameUserSettings : public UGameUserSettings /** is lan match? */ UPROPERTY(config) bool bIsLanMatch; + + /** is dedicated server? */ + UPROPERTY(config) + bool bIsDedicatedServer; }; diff --git a/Source/ShooterGame/Public/ShooterGameViewportClient.h b/Source/ShooterGame/Public/ShooterGameViewportClient.h index f9d4e64..5829823 100644 --- a/Source/ShooterGame/Public/ShooterGameViewportClient.h +++ b/Source/ShooterGame/Public/ShooterGameViewportClient.h @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once @@ -12,15 +12,12 @@ struct FShooterGameLoadingScreenBrush : public FSlateDynamicImageBrush, public F FShooterGameLoadingScreenBrush( const FName InTextureName, const FVector2D& InImageSize ) : FSlateDynamicImageBrush( InTextureName, InImageSize ) { - ResourceObject = LoadObject( NULL, *InTextureName.ToString() ); + SetResourceObject(LoadObject( nullptr, *InTextureName.ToString() )); } virtual void AddReferencedObjects(FReferenceCollector& Collector) { - if( ResourceObject ) - { - Collector.AddReferencedObject(ResourceObject); - } + FSlateBrush::AddReferencedObjects(Collector); } }; @@ -70,6 +67,10 @@ class UShooterGameViewportClient : public UGameViewportClient //FTicker Funcs virtual void Tick(float DeltaSeconds) override; + virtual void BeginDestroy() override; + virtual void DetachViewportClient() override; + void ReleaseSlateResources(); + #if WITH_EDITOR virtual void DrawTransition(class UCanvas* Canvas) override; #endif //WITH_EDITOR diff --git a/Source/ShooterGame/Public/ShooterTeamStart.h b/Source/ShooterGame/Public/ShooterTeamStart.h index 687f3a2..e8bda57 100644 --- a/Source/ShooterGame/Public/ShooterTeamStart.h +++ b/Source/ShooterGame/Public/ShooterTeamStart.h @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once diff --git a/Source/ShooterGame/Public/ShooterTypes.h b/Source/ShooterGame/Public/ShooterTypes.h index e45d626..e9ccb47 100644 --- a/Source/ShooterGame/Public/ShooterTypes.h +++ b/Source/ShooterGame/Public/ShooterTypes.h @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ShooterTypes.generated.h" #pragma once @@ -98,7 +98,8 @@ struct FDecalData /** defaults */ FDecalData() - : DecalSize(256.f) + : DecalMaterial(nullptr) + , DecalSize(256.f) , LifeSpan(10.f) { } @@ -153,63 +154,9 @@ struct FTakeHitInfo FRadialDamageEvent RadialDamageEvent; public: - FTakeHitInfo() - : ActualDamage(0) - , DamageTypeClass(NULL) - , PawnInstigator(NULL) - , DamageCauser(NULL) - , DamageEventClassID(0) - , bKilled(false) - , EnsureReplicationByte(0) - {} - - FDamageEvent& GetDamageEvent() - { - switch (DamageEventClassID) - { - case FPointDamageEvent::ClassID: - if (PointDamageEvent.DamageTypeClass == NULL) - { - PointDamageEvent.DamageTypeClass = DamageTypeClass ? DamageTypeClass : UDamageType::StaticClass(); - } - return PointDamageEvent; - - case FRadialDamageEvent::ClassID: - if (RadialDamageEvent.DamageTypeClass == NULL) - { - RadialDamageEvent.DamageTypeClass = DamageTypeClass ? DamageTypeClass : UDamageType::StaticClass(); - } - return RadialDamageEvent; - - default: - if (GeneralDamageEvent.DamageTypeClass == NULL) - { - GeneralDamageEvent.DamageTypeClass = DamageTypeClass ? DamageTypeClass : UDamageType::StaticClass(); - } - return GeneralDamageEvent; - } - } + FTakeHitInfo(); - void SetDamageEvent(const FDamageEvent& DamageEvent) - { - DamageEventClassID = DamageEvent.GetTypeID(); - switch (DamageEventClassID) - { - case FPointDamageEvent::ClassID: - PointDamageEvent = *((FPointDamageEvent const*)(&DamageEvent)); - break; - case FRadialDamageEvent::ClassID: - RadialDamageEvent = *((FRadialDamageEvent const*)(&DamageEvent)); - break; - default: - GeneralDamageEvent = DamageEvent; - } - - DamageTypeClass = DamageEvent.DamageTypeClass; - } - - void EnsureReplication() - { - EnsureReplicationByte++; - } + FDamageEvent& GetDamageEvent(); + void SetDamageEvent(const FDamageEvent& DamageEvent); + void EnsureReplication(); }; \ No newline at end of file diff --git a/Source/ShooterGame/Public/Sound/SoundNodeLocalPlayer.h b/Source/ShooterGame/Public/Sound/SoundNodeLocalPlayer.h index 36c20d7..b2cffb3 100644 --- a/Source/ShooterGame/Public/Sound/SoundNodeLocalPlayer.h +++ b/Source/ShooterGame/Public/Sound/SoundNodeLocalPlayer.h @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once @@ -21,4 +21,14 @@ class USoundNodeLocalPlayer : public USoundNode virtual FText GetInputPinName(int32 PinIndex) const override; #endif // End USoundNode interface. + + static TMap& GetLocallyControlledActorCache() + { + check(IsInAudioThread()); + return LocallyControlledActorCache; + } + +private: + + static TMap LocallyControlledActorCache; }; diff --git a/Source/ShooterGame/Public/UI/ShooterHUD.h b/Source/ShooterGame/Public/UI/ShooterHUD.h index e4e2e8a..427ec70 100644 --- a/Source/ShooterGame/Public/UI/ShooterHUD.h +++ b/Source/ShooterGame/Public/UI/ShooterHUD.h @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once diff --git a/Source/ShooterGame/Public/Weapons/ShooterDamageType.h b/Source/ShooterGame/Public/Weapons/ShooterDamageType.h index f3f09dc..8732e59 100644 --- a/Source/ShooterGame/Public/Weapons/ShooterDamageType.h +++ b/Source/ShooterGame/Public/Weapons/ShooterDamageType.h @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once diff --git a/Source/ShooterGame/Public/Weapons/ShooterProjectile.h b/Source/ShooterGame/Public/Weapons/ShooterProjectile.h index e9e0160..c362f5c 100644 --- a/Source/ShooterGame/Public/Weapons/ShooterProjectile.h +++ b/Source/ShooterGame/Public/Weapons/ShooterProjectile.h @@ -1,11 +1,14 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once +#include "GameFramework/Actor.h" #include "ShooterWeapon_Projectile.h" - #include "ShooterProjectile.generated.h" +class UProjectileMovementComponent; +class USphereComponent; + // UCLASS(Abstract, Blueprintable) class AShooterProjectile : public AActor diff --git a/Source/ShooterGame/Public/Weapons/ShooterWeapon.h b/Source/ShooterGame/Public/Weapons/ShooterWeapon.h index 54368b8..b650a50 100644 --- a/Source/ShooterGame/Public/Weapons/ShooterWeapon.h +++ b/Source/ShooterGame/Public/Weapons/ShooterWeapon.h @@ -1,9 +1,19 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once +#include "GameFramework/Actor.h" +#include "Engine/Canvas.h" // for FCanvasIcon #include "ShooterWeapon.generated.h" +class UAnimMontage; +class AShooterCharacter; +class UAudioComponent; +class UParticleSystemComponent; +class UCameraShake; +class UForceFeedbackEffect; +class USoundCue; + namespace EWeaponState { enum Type @@ -242,6 +252,14 @@ class AShooterWeapon : public AActor UPROPERTY(EditDefaultsOnly, Category=HUD) bool bHideCrosshairWhileNotAiming; + /** Adjustment to handle frame rate affecting actual timer interval. */ + UPROPERTY(Transient) + float TimerIntervalAdjustment; + + /** Whether to allow automatic weapons to catch up with shorter refire cycles */ + UPROPERTY(Config) + bool bAllowAutomaticWeaponCatchup = true; + /** check if weapon has infinite ammo (include owner's cheats) */ bool HasInfiniteAmmo() const; @@ -453,6 +471,9 @@ class AShooterWeapon : public AActor UFUNCTION(reliable, server, WithValidation) void ServerHandleFiring(); + /** [local + server] handle weapon refire, compensating for slack time if the timer can't sample fast enough */ + void HandleReFiring(); + /** [local + server] handle weapon fire */ void HandleFiring(); diff --git a/Source/ShooterGame/Public/Weapons/ShooterWeapon_Instant.h b/Source/ShooterGame/Public/Weapons/ShooterWeapon_Instant.h index a55c66e..53aa720 100644 --- a/Source/ShooterGame/Public/Weapons/ShooterWeapon_Instant.h +++ b/Source/ShooterGame/Public/Weapons/ShooterWeapon_Instant.h @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once @@ -122,7 +122,7 @@ class AShooterWeapon_Instant : public AShooterWeapon /** server notified of hit from client to verify */ UFUNCTION(reliable, server, WithValidation) - void ServerNotifyHit(const FHitResult Impact, FVector_NetQuantizeNormal ShootDir, int32 RandomSeed, float ReticleSpread); + void ServerNotifyHit(const FHitResult& Impact, FVector_NetQuantizeNormal ShootDir, int32 RandomSeed, float ReticleSpread); /** server notified of miss to show trail FX */ UFUNCTION(unreliable, server, WithValidation) diff --git a/Source/ShooterGame/Public/Weapons/ShooterWeapon_Projectile.h b/Source/ShooterGame/Public/Weapons/ShooterWeapon_Projectile.h index f755c52..4e17377 100644 --- a/Source/ShooterGame/Public/Weapons/ShooterWeapon_Projectile.h +++ b/Source/ShooterGame/Public/Weapons/ShooterWeapon_Projectile.h @@ -1,8 +1,9 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once #include "ShooterWeapon.h" +#include "GameFramework/DamageType.h" // for UDamageType::StaticClass() #include "ShooterWeapon_Projectile.generated.h" USTRUCT() diff --git a/Source/ShooterGame/Resources/Mac/Info.plist b/Source/ShooterGame/Resources/Mac/Info.plist new file mode 100644 index 0000000..2573cee --- /dev/null +++ b/Source/ShooterGame/Resources/Mac/Info.plist @@ -0,0 +1,41 @@ + + + + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIconFile + ${ICON_NAME} + CFBundleIdentifier + ${APP_NAME} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${EXECUTABLE_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + ${BUNDLE_VERSION} + CFBundleSignature + ???? + CFBundleVersion + ${BUNDLE_VERSION} + LSMinimumSystemVersion + ${MACOSX_DEPLOYMENT_TARGET} + NSPrincipalClass + NSApplication + NSHighResolutionCapable + + NSHighResolutionMagnifyAllowed + + NSMicrophoneUsageDescription + ShooterGame requires microphone access for voice chat. + + diff --git a/Source/ShooterGame/Resources/Windows/ShooterGame.rc b/Source/ShooterGame/Resources/Windows/ShooterGame.rc index 73e2b10..d2c380a 100644 --- a/Source/ShooterGame/Resources/Windows/ShooterGame.rc +++ b/Source/ShooterGame/Resources/Windows/ShooterGame.rc @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #define APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// @@ -7,6 +7,7 @@ // #include #include "Runtime/Launch/Resources/Version.h" +#include "Runtime/Launch/Resources/Windows/resource.h" ///////////////////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS diff --git a/Source/ShooterGame/ShooterGame.Build.cs b/Source/ShooterGame/ShooterGame.Build.cs index 8e91343..ed73ddf 100644 --- a/Source/ShooterGame/ShooterGame.Build.cs +++ b/Source/ShooterGame/ShooterGame.Build.cs @@ -1,14 +1,15 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. using UnrealBuildTool; public class ShooterGame : ModuleRules { - public ShooterGame(TargetInfo Target) + public ShooterGame(ReadOnlyTargetRules Target) : base(Target) { + PrivatePCHHeaderFile = "Public/ShooterGame.h"; + PrivateIncludePaths.AddRange( new string[] { - "ShooterGame/Classes/Player", "ShooterGame/Private", "ShooterGame/Private/UI", "ShooterGame/Private/UI/Menu", @@ -25,10 +26,12 @@ public ShooterGame(TargetInfo Target) "OnlineSubsystem", "OnlineSubsystemUtils", "AssetRegistry", - "AIModule", + "NavigationSystem", + "AIModule", "GameplayTasks", - "NavMesh", - } + "Navmesh", + "ProceduralMeshComponent", + } ); PrivateDependencyModuleNames.AddRange( @@ -37,6 +40,10 @@ public ShooterGame(TargetInfo Target) "Slate", "SlateCore", "ShooterGameLoadingScreen", + "Json", + "ApplicationCore", + "ReplicationGraph", + "PakFile" } ); @@ -45,7 +52,8 @@ public ShooterGame(TargetInfo Target) "OnlineSubsystemNull", "NetworkReplayStreaming", "NullNetworkReplayStreaming", - "HttpNetworkReplayStreaming" + "HttpNetworkReplayStreaming", + "LocalFileNetworkReplayStreaming" } ); @@ -55,20 +63,14 @@ public ShooterGame(TargetInfo Target) } ); - if ((Target.Platform == UnrealTargetPlatform.Win32) || (Target.Platform == UnrealTargetPlatform.Win64) || (Target.Platform == UnrealTargetPlatform.Linux) || (Target.Platform == UnrealTargetPlatform.Mac)) - { - if (UEBuildConfiguration.bCompileSteamOSS == true) - { - DynamicallyLoadedModuleNames.Add("OnlineSubsystemSteam"); - } - } - else if (Target.Platform == UnrealTargetPlatform.PS4) - { - DynamicallyLoadedModuleNames.Add("OnlineSubsystemPS4"); - } - else if (Target.Platform == UnrealTargetPlatform.XboxOne) + if (Target.bBuildDeveloperTools || (Target.Configuration != UnrealTargetConfiguration.Shipping && Target.Configuration != UnrealTargetConfiguration.Test)) { - DynamicallyLoadedModuleNames.Add("OnlineSubsystemLive"); + PrivateDependencyModuleNames.Add("GameplayDebugger"); + PublicDefinitions.Add("WITH_GAMEPLAY_DEBUGGER=1"); } + else + { + PublicDefinitions.Add("WITH_GAMEPLAY_DEBUGGER=0"); + } } } diff --git a/Source/ShooterGameLoadingScreen/Private/ShooterGameLoadingScreen.cpp b/Source/ShooterGameLoadingScreen/Private/ShooterGameLoadingScreen.cpp index 276edde..18539e1 100644 --- a/Source/ShooterGameLoadingScreen/Private/ShooterGameLoadingScreen.cpp +++ b/Source/ShooterGameLoadingScreen/Private/ShooterGameLoadingScreen.cpp @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ShooterGameLoadingScreen.h" #include "GenericApplication.h" @@ -14,15 +14,12 @@ struct FShooterGameLoadingScreenBrush : public FSlateDynamicImageBrush, public F FShooterGameLoadingScreenBrush( const FName InTextureName, const FVector2D& InImageSize ) : FSlateDynamicImageBrush( InTextureName, InImageSize ) { - ResourceObject = LoadObject( NULL, *InTextureName.ToString() ); + SetResourceObject(LoadObject( NULL, *InTextureName.ToString() )); } virtual void AddReferencedObjects(FReferenceCollector& Collector) { - if( ResourceObject ) - { - Collector.AddReferencedObject(ResourceObject); - } + FSlateBrush::AddReferencedObjects(Collector); } }; diff --git a/Source/ShooterGameLoadingScreen/Public/ShooterGameLoadingScreen.h b/Source/ShooterGameLoadingScreen/Public/ShooterGameLoadingScreen.h index 6c2dbeb..6b058d2 100644 --- a/Source/ShooterGameLoadingScreen/Public/ShooterGameLoadingScreen.h +++ b/Source/ShooterGameLoadingScreen/Public/ShooterGameLoadingScreen.h @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #ifndef __SHOOTERGAMELOADINGSCREEN_H__ #define __SHOOTERGAMELOADINGSCREEN_H__ diff --git a/Source/ShooterGameLoadingScreen/ShooterGameLoadingScreen.Build.cs b/Source/ShooterGameLoadingScreen/ShooterGameLoadingScreen.Build.cs index 30e3977..2febbd4 100644 --- a/Source/ShooterGameLoadingScreen/ShooterGameLoadingScreen.Build.cs +++ b/Source/ShooterGameLoadingScreen/ShooterGameLoadingScreen.Build.cs @@ -1,4 +1,4 @@ -// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. using UnrealBuildTool; @@ -6,9 +6,11 @@ public class ShooterGameLoadingScreen : ModuleRules { - public ShooterGameLoadingScreen(TargetInfo Target) + public ShooterGameLoadingScreen(ReadOnlyTargetRules Target) : base(Target) { - PrivateIncludePaths.Add("../../ShooterGame/Source/ShooterGameLoadingScreen/Private"); + PrivatePCHHeaderFile = "Public/ShooterGameLoadingScreen.h"; + + PCHUsage = PCHUsageMode.UseSharedPCHs; PrivateDependencyModuleNames.AddRange( new string[] { From db511b4f41c56e355fb31a24880cb010b7184351 Mon Sep 17 00:00:00 2001 From: LucianoJacomeli Date: Tue, 2 Jul 2019 01:08:11 -0300 Subject: [PATCH 3/4] Fix Reference for editor in "PlayerState" to GetPlayerState() --- Source/ShooterGame/Private/Bots/ShooterAIController.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/ShooterGame/Private/Bots/ShooterAIController.cpp b/Source/ShooterGame/Private/Bots/ShooterAIController.cpp index 86f30fb..e15961b 100644 --- a/Source/ShooterGame/Private/Bots/ShooterAIController.cpp +++ b/Source/ShooterGame/Private/Bots/ShooterAIController.cpp @@ -156,7 +156,7 @@ bool AShooterAIController::HasWeaponLOSToEnemy(AActor* InEnemyActor, const bool ACharacter* HitChar = Cast(HitActor); if (HitChar != NULL) { - AShooterPlayerState* HitPlayerState = Cast(HitChar->PlayerState); + AShooterPlayerState* HitPlayerState = Cast(HitChar->GetPlayerState()); AShooterPlayerState* MyPlayerState = Cast(PlayerState); if ((HitPlayerState != NULL) && (MyPlayerState != NULL)) { From c957982d3a36b5e4df88bc6a7c677c6ea35e92b7 Mon Sep 17 00:00:00 2001 From: LucianoJacomeli Date: Tue, 2 Jul 2019 18:28:08 -0300 Subject: [PATCH 4/4] Changes in AIController reactivate OnPerceptionUpdate update TraceParams.bTraceComplex=true --- Source/ShooterGame/Private/Bots/ShooterAIController.cpp | 4 ++-- Source/ShooterGame/ShooterGame.Build.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/ShooterGame/Private/Bots/ShooterAIController.cpp b/Source/ShooterGame/Private/Bots/ShooterAIController.cpp index e15961b..efb0baa 100644 --- a/Source/ShooterGame/Private/Bots/ShooterAIController.cpp +++ b/Source/ShooterGame/Private/Bots/ShooterAIController.cpp @@ -50,7 +50,7 @@ AShooterAIController::AShooterAIController(const FObjectInitializer& ObjectIniti AIPerceptionHearingConfig->DetectionByAffiliation.bDetectFriendlies = true; AIPerceptionComp->ConfigureSense(*AIPerceptionHearingConfig); - //AIPerceptionComp->OnPerceptionUpdated.AddDynamic(this, &AShooterAIController::OnPerceptionUpdated); + AIPerceptionComp->OnPerceptionUpdated.AddDynamic(this, &AShooterAIController::OnPerceptionUpdated); bWantsPlayerState = true; } @@ -131,7 +131,7 @@ bool AShooterAIController::HasWeaponLOSToEnemy(AActor* InEnemyActor, const bool bool bHasLOS = false; // Perform trace to retrieve hit info FCollisionQueryParams TraceParams(LosTag, true, GetPawn()); - //TraceParams.bTraceAsyncScene = true; + TraceParams.bTraceComplex = true; TraceParams.bReturnPhysicalMaterial = true; FVector StartLocation = MyBot->GetActorLocation(); diff --git a/Source/ShooterGame/ShooterGame.Build.cs b/Source/ShooterGame/ShooterGame.Build.cs index ed73ddf..c0aee47 100644 --- a/Source/ShooterGame/ShooterGame.Build.cs +++ b/Source/ShooterGame/ShooterGame.Build.cs @@ -30,7 +30,7 @@ public ShooterGame(ReadOnlyTargetRules Target) : base(Target) "AIModule", "GameplayTasks", "Navmesh", - "ProceduralMeshComponent", + } );