commit 510654f8a196640bc9da4036ac9b02937781e6bc Author: guus Date: Sat Aug 11 16:46:35 2018 +0200 HAxis sos diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..2299507 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +*.uasset filter=lfs diff=lfs merge=lfs -text +*.wmv filter=lfs diff=lfs merge=lfs -text +*.mp4 filter=lfs diff=lfs merge=lfs -text diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..895a382 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +*.dll +*.pch +*.lib +*.a +*.so + +*.obj +*.exp +*Intermediate/ +*Binaries/ +*.dylib +*.pdb +*.exe \ No newline at end of file diff --git a/Config/DefaultEditor.ini b/Config/DefaultEditor.ini new file mode 100644 index 0000000..c54030e --- /dev/null +++ b/Config/DefaultEditor.ini @@ -0,0 +1,8 @@ +[UnrealEd.SimpleMap] +SimpleMapName=/Game/ThirdPersonCPP/Maps/ThirdPersonExampleMap + +[EditoronlyBP] +bAllowClassAndBlueprintPinMatching=true +bReplaceBlueprintWithClass= true +bDontLoadBlueprintOutsideEditor= true +bBlueprintIsNotBlueprintType= true \ No newline at end of file diff --git a/Config/DefaultEditorPerProjectUserSettings.ini b/Config/DefaultEditorPerProjectUserSettings.ini new file mode 100644 index 0000000..0935f0f --- /dev/null +++ b/Config/DefaultEditorPerProjectUserSettings.ini @@ -0,0 +1,40 @@ +[ContentBrowser] +ContentBrowserTab1.SelectedPaths=/Game/ThirdPersonCPP + +[/Script/EditorStyle.EditorStyleSettings] +SelectionColor=(R=0.728000,G=0.364000,B=0.003000,A=1.000000) +PressedSelectionColor=(R=0.701000,G=0.225000,B=0.003000,A=1.000000) +InactiveSelectionColor=(R=0.250000,G=0.250000,B=0.250000,A=1.000000) +KeyboardFocusColor=(R=0.000000,G=0.000000,B=0.000000,A=0.000000) +ColorVisionDeficiencyPreviewType=CVD_NormalVision +bUseSmallToolBarIcons=False +bEnableWindowAnimations=False +bShowFriendlyNames=True +bExpandConfigurationMenus=False +bShowProjectMenus=True +bShowLaunchMenus=True +LogTimestampMode=None +bOpenAssetEditorTabsInNewWindow=True + +[/Script/UnrealEd.EditorPerProjectUserSettings] +bDisplayUIExtensionPoints=False +bDisplayDocumentationLink=False +bDisplayActionListItemRefIds=False +bAlwaysGatherBehaviorTreeDebuggerData=False +bDisplayEngineVersionInBadge=False +bShowFrameRateAndMemory=False +bThrottleCPUWhenNotForeground=True +bMonitorEditorPerformance=True +bAutomaticallyHotReloadNewClasses=False +bKeepAttachHierarchy=True +bUseCurvesForDistributions=False +PropertyMatrix_NumberOfPasteOperationsBeforeWarning=20 +bSCSEditorShowGrid=True +bSCSEditorShowFloor=False +SCSViewportCameraSpeed=4 +bAutoloadCheckedOutPackages=False +bSuppressFullyLoadPrompt=True +bAllowSelectTranslucent=True +MaterialQualityLevel=1 + + diff --git a/Config/DefaultEngine.ini b/Config/DefaultEngine.ini new file mode 100644 index 0000000..6b0f69d --- /dev/null +++ b/Config/DefaultEngine.ini @@ -0,0 +1,180 @@ +[/Script/EngineSettings.GameMapsSettings] +GameDefaultMap=/Game/Assets/Levels/Menu +EditorStartupMap=/Game/Assets/Levels/Map_KOTH_Darko +GlobalDefaultGameMode="/Script/UnrealProject.DefaultGameMode" +GameInstanceClass=/Script/UnrealProject.DefaultGameInstance + +[/Script/Engine.Engine] ++ActiveGameNameRedirects=(OldGameName="TP_ThirdPerson",NewGameName="/Script/UnrealProject") ++ActiveGameNameRedirects=(OldGameName="/Script/TP_ThirdPerson",NewGameName="/Script/UnrealProject") ++ActiveGameNameRedirects=(OldGameName="/Script/GameName",NewGameName="/Script/UnrealProject") + +[Core.Log] + LogNet=verbose + LogOnline=verbose + +[/Script/Engine.GameEngine] +!NetDriverDefinitions=ClearArray + +NetDriverDefinitions=(DefName="GameNetDriver",DriverClassName="OnlineSubsystemSteam.SteamNetDriver",DriverClassNameFallback="OnlineSubsystemUtils.IpNetDriver") + +[OnlineSubsystem] +DefaultPlatformService=Steam +PollingIntervalInMs=20 + +[OnlineSubsystemSteam] +bEnabled=true +;SteamDevAppId=233250 +SteamDevAppId=480 +GameServerQueryPort=27015 +bRelaunchInSteam=false +GameVersion=1.0.0.0 +bVACEnabled=1 +bAllowP2PPacketRelay=true +P2PConnectionTimeout=90 + +[/Script/OnlineSubsystemSteam.SteamNetDriver] +NetConnectionClassName="OnlineSubsystemSteam.SteamNetConnection" + +[/Script/HardwareTargeting.HardwareTargetingSettings] +TargetedHardwareClass=Desktop +AppliedTargetedHardwareClass=Desktop +DefaultGraphicsPerformance=Maximum +AppliedDefaultGraphicsPerformance=Maximum + +[/Script/Engine.CollisionProfile] +-Profiles=(Name="NoCollision",CollisionEnabled=NoCollision,ObjectTypeName="WorldStatic",CustomResponses=((Channel="Visibility",Response=ECR_Ignore),(Channel="Camera",Response=ECR_Ignore)),HelpMessage="No collision",bCanModify=False) +-Profiles=(Name="BlockAll",CollisionEnabled=QueryAndPhysics,ObjectTypeName="WorldStatic",CustomResponses=,HelpMessage="WorldStatic object that blocks all actors by default. All new custom channels will use its own default response. ",bCanModify=False) +-Profiles=(Name="OverlapAll",CollisionEnabled=QueryOnly,ObjectTypeName="WorldStatic",CustomResponses=((Channel="WorldStatic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility",Response=ECR_Overlap),(Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Overlap),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="WorldStatic object that overlaps all actors by default. All new custom channels will use its own default response. ",bCanModify=False) +-Profiles=(Name="BlockAllDynamic",CollisionEnabled=QueryAndPhysics,ObjectTypeName="WorldDynamic",CustomResponses=,HelpMessage="WorldDynamic object that blocks all actors by default. All new custom channels will use its own default response. ",bCanModify=False) +-Profiles=(Name="OverlapAllDynamic",CollisionEnabled=QueryOnly,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="WorldStatic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility",Response=ECR_Overlap),(Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Overlap),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="WorldDynamic object that overlaps all actors by default. All new custom channels will use its own default response. ",bCanModify=False) +-Profiles=(Name="IgnoreOnlyPawn",CollisionEnabled=QueryOnly,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="Pawn",Response=ECR_Ignore),(Channel="Vehicle",Response=ECR_Ignore)),HelpMessage="WorldDynamic object that ignores Pawn and Vehicle. All other channels will be set to default.",bCanModify=False) +-Profiles=(Name="OverlapOnlyPawn",CollisionEnabled=QueryOnly,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="Pawn",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Ignore)),HelpMessage="WorldDynamic object that overlaps Pawn, Camera, and Vehicle. All other channels will be set to default. ",bCanModify=False) +-Profiles=(Name="Pawn",CollisionEnabled=QueryAndPhysics,ObjectTypeName="Pawn",CustomResponses=((Channel="Visibility",Response=ECR_Ignore)),HelpMessage="Pawn object. Can be used for capsule of any playerable character or AI. ",bCanModify=False) +-Profiles=(Name="Spectator",CollisionEnabled=QueryOnly,ObjectTypeName="Pawn",CustomResponses=((Channel="WorldStatic",Response=ECR_Block),(Channel="Pawn",Response=ECR_Ignore),(Channel="Visibility",Response=ECR_Ignore),(Channel="WorldDynamic",Response=ECR_Ignore),(Channel="Camera",Response=ECR_Ignore),(Channel="PhysicsBody",Response=ECR_Ignore),(Channel="Vehicle",Response=ECR_Ignore),(Channel="Destructible",Response=ECR_Ignore)),HelpMessage="Pawn object that ignores all other actors except WorldStatic.",bCanModify=False) +-Profiles=(Name="CharacterMesh",CollisionEnabled=QueryOnly,ObjectTypeName="Pawn",CustomResponses=((Channel="Pawn",Response=ECR_Ignore),(Channel="Vehicle",Response=ECR_Ignore),(Channel="Visibility",Response=ECR_Ignore)),HelpMessage="Pawn object that is used for Character Mesh. All other channels will be set to default.",bCanModify=False) +-Profiles=(Name="PhysicsActor",CollisionEnabled=QueryAndPhysics,ObjectTypeName="PhysicsBody",CustomResponses=,HelpMessage="Simulating actors",bCanModify=False) +-Profiles=(Name="Destructible",CollisionEnabled=QueryAndPhysics,ObjectTypeName="Destructible",CustomResponses=,HelpMessage="Destructible actors",bCanModify=False) +-Profiles=(Name="InvisibleWall",CollisionEnabled=QueryAndPhysics,ObjectTypeName="WorldStatic",CustomResponses=((Channel="Visibility",Response=ECR_Ignore)),HelpMessage="WorldStatic object that is invisible.",bCanModify=False) +-Profiles=(Name="InvisibleWallDynamic",CollisionEnabled=QueryAndPhysics,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="Visibility",Response=ECR_Ignore)),HelpMessage="WorldDynamic object that is invisible.",bCanModify=False) +-Profiles=(Name="Trigger",CollisionEnabled=QueryOnly,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="WorldStatic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility",Response=ECR_Ignore),(Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Overlap),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="WorldDynamic object that is used for trigger. All other channels will be set to default.",bCanModify=False) +-Profiles=(Name="Ragdoll",CollisionEnabled=QueryAndPhysics,ObjectTypeName="PhysicsBody",CustomResponses=((Channel="Pawn",Response=ECR_Ignore)),HelpMessage="Simulating Skeletal Mesh Component. All other channels will be set to default.",bCanModify=False) +-Profiles=(Name="Vehicle",CollisionEnabled=QueryAndPhysics,ObjectTypeName="Vehicle",CustomResponses=,HelpMessage="Vehicle object that blocks Vehicle, WorldStatic, and WorldDynamic. All other channels will be set to default.",bCanModify=False) +-Profiles=(Name="UI",CollisionEnabled=QueryOnly,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="WorldStatic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility",Response=ECR_Block),(Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Overlap),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="WorldStatic object that overlaps all actors by default. All new custom channels will use its own default response. ",bCanModify=False) ++Profiles=(Name="NoCollision",CollisionEnabled=NoCollision,ObjectTypeName="WorldStatic",CustomResponses=((Channel="Visibility",Response=ECR_Ignore),(Channel="Camera",Response=ECR_Ignore)),HelpMessage="No collision",bCanModify=False) ++Profiles=(Name="BlockAll",CollisionEnabled=QueryAndPhysics,ObjectTypeName="WorldStatic",CustomResponses=,HelpMessage="WorldStatic object that blocks all actors by default. All new custom channels will use its own default response. ",bCanModify=False) ++Profiles=(Name="OverlapAll",CollisionEnabled=QueryOnly,ObjectTypeName="WorldStatic",CustomResponses=((Channel="WorldStatic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility",Response=ECR_Overlap),(Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Overlap),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="WorldStatic object that overlaps all actors by default. All new custom channels will use its own default response. ",bCanModify=False) ++Profiles=(Name="BlockAllDynamic",CollisionEnabled=QueryAndPhysics,ObjectTypeName="WorldDynamic",CustomResponses=,HelpMessage="WorldDynamic object that blocks all actors by default. All new custom channels will use its own default response. ",bCanModify=False) ++Profiles=(Name="OverlapAllDynamic",CollisionEnabled=QueryOnly,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="WorldStatic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility",Response=ECR_Overlap),(Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Overlap),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="WorldDynamic object that overlaps all actors by default. All new custom channels will use its own default response. ",bCanModify=False) ++Profiles=(Name="IgnoreOnlyPawn",CollisionEnabled=QueryOnly,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="Pawn",Response=ECR_Ignore),(Channel="Vehicle",Response=ECR_Ignore)),HelpMessage="WorldDynamic object that ignores Pawn and Vehicle. All other channels will be set to default.",bCanModify=False) ++Profiles=(Name="OverlapOnlyPawn",CollisionEnabled=QueryOnly,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="Pawn",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Ignore)),HelpMessage="WorldDynamic object that overlaps Pawn, Camera, and Vehicle. All other channels will be set to default. ",bCanModify=False) ++Profiles=(Name="Pawn",CollisionEnabled=QueryAndPhysics,ObjectTypeName="Pawn",CustomResponses=((Channel="Visibility",Response=ECR_Ignore)),HelpMessage="Pawn object. Can be used for capsule of any playerable character or AI. ",bCanModify=False) ++Profiles=(Name="Spectator",CollisionEnabled=QueryOnly,ObjectTypeName="Pawn",CustomResponses=((Channel="WorldStatic"),(Channel="Pawn",Response=ECR_Ignore),(Channel="Visibility",Response=ECR_Ignore),(Channel="WorldDynamic",Response=ECR_Ignore),(Channel="Camera",Response=ECR_Ignore),(Channel="PhysicsBody",Response=ECR_Ignore),(Channel="Vehicle",Response=ECR_Ignore),(Channel="Destructible",Response=ECR_Ignore)),HelpMessage="Pawn object that ignores all other actors except WorldStatic.",bCanModify=False) ++Profiles=(Name="CharacterMesh",CollisionEnabled=QueryOnly,ObjectTypeName="Pawn",CustomResponses=((Channel="Pawn",Response=ECR_Ignore),(Channel="Vehicle",Response=ECR_Ignore),(Channel="Visibility",Response=ECR_Ignore)),HelpMessage="Pawn object that is used for Character Mesh. All other channels will be set to default.",bCanModify=False) ++Profiles=(Name="PhysicsActor",CollisionEnabled=QueryAndPhysics,ObjectTypeName="PhysicsBody",CustomResponses=,HelpMessage="Simulating actors",bCanModify=False) ++Profiles=(Name="Destructible",CollisionEnabled=QueryAndPhysics,ObjectTypeName="Destructible",CustomResponses=,HelpMessage="Destructible actors",bCanModify=False) ++Profiles=(Name="InvisibleWall",CollisionEnabled=QueryAndPhysics,ObjectTypeName="WorldStatic",CustomResponses=((Channel="Visibility",Response=ECR_Ignore)),HelpMessage="WorldStatic object that is invisible.",bCanModify=False) ++Profiles=(Name="InvisibleWallDynamic",CollisionEnabled=QueryAndPhysics,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="Visibility",Response=ECR_Ignore)),HelpMessage="WorldDynamic object that is invisible.",bCanModify=False) ++Profiles=(Name="Trigger",CollisionEnabled=QueryOnly,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="WorldStatic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility",Response=ECR_Ignore),(Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Overlap),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="WorldDynamic object that is used for trigger. All other channels will be set to default.",bCanModify=False) ++Profiles=(Name="Ragdoll",CollisionEnabled=QueryAndPhysics,ObjectTypeName="PhysicsBody",CustomResponses=((Channel="Pawn",Response=ECR_Ignore)),HelpMessage="Simulating Skeletal Mesh Component. All other channels will be set to default.",bCanModify=False) ++Profiles=(Name="Vehicle",CollisionEnabled=QueryAndPhysics,ObjectTypeName="Vehicle",CustomResponses=,HelpMessage="Vehicle object that blocks Vehicle, WorldStatic, and WorldDynamic. All other channels will be set to default.",bCanModify=False) ++Profiles=(Name="UI",CollisionEnabled=QueryOnly,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="WorldStatic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility"),(Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Overlap),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="WorldStatic object that overlaps all actors by default. All new custom channels will use its own default response. ",bCanModify=False) ++Profiles=(Name="Triggers",CollisionEnabled=QueryOnly,ObjectTypeName="Trigger",CustomResponses=((Channel="WorldStatic",Response=ECR_Ignore),(Channel="WorldDynamic",Response=ECR_Ignore),(Channel="Pawn",Response=ECR_Ignore),(Channel="Visibility",Response=ECR_Ignore),(Channel="Camera",Response=ECR_Ignore),(Channel="PhysicsBody",Response=ECR_Ignore),(Channel="Vehicle",Response=ECR_Ignore),(Channel="Destructible",Response=ECR_Ignore),(Channel="EngineTraceChannel1",Response=ECR_Ignore),(Channel="EngineTraceChannel2",Response=ECR_Ignore),(Channel="EngineTraceChannel3",Response=ECR_Ignore),(Channel="EngineTraceChannel4",Response=ECR_Ignore),(Channel="EngineTraceChannel5",Response=ECR_Ignore),(Channel="EngineTraceChannel6",Response=ECR_Ignore),(Channel="Units",Response=ECR_Overlap),(Channel="Players",Response=ECR_Overlap),(Channel="Ghost",Response=ECR_Ignore),(Channel="GameTraceChannel7",Response=ECR_Ignore),(Channel="GameTraceChannel8",Response=ECR_Ignore),(Channel="GameTraceChannel9",Response=ECR_Ignore),(Channel="GameTraceChannel10",Response=ECR_Ignore),(Channel="GameTraceChannel11",Response=ECR_Ignore),(Channel="GameTraceChannel12",Response=ECR_Ignore),(Channel="GameTraceChannel13",Response=ECR_Ignore),(Channel="GameTraceChannel14",Response=ECR_Ignore),(Channel="GameTraceChannel15",Response=ECR_Ignore),(Channel="GameTraceChannel16",Response=ECR_Ignore),(Channel="GameTraceChannel17",Response=ECR_Ignore),(Channel="GameTraceChannel18",Response=ECR_Ignore)),HelpMessage="Needs description",bCanModify=True) ++Profiles=(Name="Units",CollisionEnabled=QueryAndPhysics,ObjectTypeName="Units",CustomResponses=((Channel="Trigger",Response=ECR_Overlap),(Channel="Projectile",Response=ECR_Overlap),(Channel="ProjectileUnitsOnly",Response=ECR_Overlap)),HelpMessage="Needs description",bCanModify=True) ++Profiles=(Name="Projectiles",CollisionEnabled=QueryAndPhysics,ObjectTypeName="Projectile",CustomResponses=((Channel="WorldDynamic",Response=ECR_Ignore),(Channel="Pawn",Response=ECR_Ignore),(Channel="Visibility",Response=ECR_Ignore),(Channel="Camera",Response=ECR_Ignore),(Channel="PhysicsBody",Response=ECR_Ignore),(Channel="Vehicle",Response=ECR_Ignore),(Channel="Destructible",Response=ECR_Ignore),(Channel="EngineTraceChannel1",Response=ECR_Ignore),(Channel="EngineTraceChannel2",Response=ECR_Ignore),(Channel="EngineTraceChannel3",Response=ECR_Ignore),(Channel="EngineTraceChannel4",Response=ECR_Ignore),(Channel="EngineTraceChannel5",Response=ECR_Ignore),(Channel="EngineTraceChannel6",Response=ECR_Ignore),(Channel="Units",Response=ECR_Overlap),(Channel="Players",Response=ECR_Overlap),(Channel="Ghost",Response=ECR_Ignore),(Channel="GameTraceChannel7",Response=ECR_Ignore),(Channel="GameTraceChannel8",Response=ECR_Ignore),(Channel="GameTraceChannel9",Response=ECR_Ignore),(Channel="GameTraceChannel10",Response=ECR_Ignore),(Channel="GameTraceChannel11",Response=ECR_Ignore),(Channel="GameTraceChannel12",Response=ECR_Ignore),(Channel="GameTraceChannel13",Response=ECR_Ignore),(Channel="GameTraceChannel14",Response=ECR_Ignore),(Channel="GameTraceChannel15",Response=ECR_Ignore),(Channel="GameTraceChannel16",Response=ECR_Ignore),(Channel="GameTraceChannel17",Response=ECR_Ignore),(Channel="GameTraceChannel18",Response=ECR_Ignore)),HelpMessage="for projectiles",bCanModify=True) ++Profiles=(Name="PlayerOverlap",CollisionEnabled=QueryOnly,ObjectTypeName="Units",CustomResponses=((Channel="WorldStatic",Response=ECR_Ignore),(Channel="WorldDynamic",Response=ECR_Ignore),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility",Response=ECR_Ignore),(Channel="Camera",Response=ECR_Ignore),(Channel="PhysicsBody",Response=ECR_Ignore),(Channel="Vehicle",Response=ECR_Ignore),(Channel="Destructible",Response=ECR_Ignore),(Channel="EngineTraceChannel1",Response=ECR_Ignore),(Channel="EngineTraceChannel2",Response=ECR_Ignore),(Channel="EngineTraceChannel3",Response=ECR_Ignore),(Channel="EngineTraceChannel4",Response=ECR_Ignore),(Channel="EngineTraceChannel5",Response=ECR_Ignore),(Channel="EngineTraceChannel6",Response=ECR_Ignore),(Channel="Units",Response=ECR_Overlap),(Channel="Players",Response=ECR_Overlap),(Channel="Ghost",Response=ECR_Ignore),(Channel="GameTraceChannel7",Response=ECR_Ignore),(Channel="GameTraceChannel8",Response=ECR_Ignore),(Channel="GameTraceChannel9",Response=ECR_Ignore),(Channel="GameTraceChannel10",Response=ECR_Ignore),(Channel="GameTraceChannel11",Response=ECR_Ignore),(Channel="GameTraceChannel12",Response=ECR_Ignore),(Channel="GameTraceChannel13",Response=ECR_Ignore),(Channel="GameTraceChannel14",Response=ECR_Ignore),(Channel="GameTraceChannel15",Response=ECR_Ignore),(Channel="GameTraceChannel16",Response=ECR_Ignore),(Channel="GameTraceChannel17",Response=ECR_Ignore),(Channel="GameTraceChannel18",Response=ECR_Ignore)),HelpMessage="For Player only Overlap",bCanModify=True) ++Profiles=(Name="Players",CollisionEnabled=QueryAndPhysics,ObjectTypeName="Players",CustomResponses=((Channel="Trigger",Response=ECR_Overlap),(Channel="Projectile",Response=ECR_Overlap),(Channel="ProjectileUnitsOnly",Response=ECR_Overlap)),HelpMessage="Players",bCanModify=True) ++Profiles=(Name="Ghosts",CollisionEnabled=QueryAndPhysics,ObjectTypeName="Ghost",CustomResponses=((Channel="Units",Response=ECR_Overlap),(Channel="Trigger",Response=ECR_Overlap),(Channel="Projectile",Response=ECR_Overlap),(Channel="Players",Response=ECR_Ignore)),HelpMessage="Ghosts",bCanModify=True) ++Profiles=(Name="GhostOverlap",CollisionEnabled=QueryOnly,ObjectTypeName="Units",CustomResponses=((Channel="WorldStatic",Response=ECR_Ignore),(Channel="WorldDynamic",Response=ECR_Ignore),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility",Response=ECR_Ignore),(Channel="Camera",Response=ECR_Ignore),(Channel="PhysicsBody",Response=ECR_Ignore),(Channel="Vehicle",Response=ECR_Ignore),(Channel="Destructible",Response=ECR_Ignore),(Channel="EngineTraceChannel1",Response=ECR_Ignore),(Channel="EngineTraceChannel2",Response=ECR_Ignore),(Channel="EngineTraceChannel3",Response=ECR_Ignore),(Channel="EngineTraceChannel4",Response=ECR_Ignore),(Channel="EngineTraceChannel5",Response=ECR_Ignore),(Channel="EngineTraceChannel6",Response=ECR_Ignore),(Channel="Units",Response=ECR_Overlap),(Channel="Players",Response=ECR_Ignore),(Channel="Ghost",Response=ECR_Overlap),(Channel="GameTraceChannel7",Response=ECR_Ignore),(Channel="GameTraceChannel8",Response=ECR_Ignore),(Channel="GameTraceChannel9",Response=ECR_Ignore),(Channel="GameTraceChannel10",Response=ECR_Ignore),(Channel="GameTraceChannel11",Response=ECR_Ignore),(Channel="GameTraceChannel12",Response=ECR_Ignore),(Channel="GameTraceChannel13",Response=ECR_Ignore),(Channel="GameTraceChannel14",Response=ECR_Ignore),(Channel="GameTraceChannel15",Response=ECR_Ignore),(Channel="GameTraceChannel16",Response=ECR_Ignore),(Channel="GameTraceChannel17",Response=ECR_Ignore),(Channel="GameTraceChannel18",Response=ECR_Ignore)),HelpMessage="For Ghost only Overlap",bCanModify=True) ++Profiles=(Name="ProjectileUnitsOnly",CollisionEnabled=QueryAndPhysics,ObjectTypeName="ProjectileUnitsOnly",CustomResponses=((Channel="WorldStatic",Response=ECR_Ignore),(Channel="WorldDynamic",Response=ECR_Ignore),(Channel="Pawn",Response=ECR_Ignore),(Channel="Visibility",Response=ECR_Ignore),(Channel="Camera",Response=ECR_Ignore),(Channel="PhysicsBody",Response=ECR_Ignore),(Channel="Vehicle",Response=ECR_Ignore),(Channel="Destructible",Response=ECR_Ignore),(Channel="EngineTraceChannel1",Response=ECR_Ignore),(Channel="EngineTraceChannel2",Response=ECR_Ignore),(Channel="EngineTraceChannel3",Response=ECR_Ignore),(Channel="EngineTraceChannel4",Response=ECR_Ignore),(Channel="EngineTraceChannel5",Response=ECR_Ignore),(Channel="EngineTraceChannel6",Response=ECR_Ignore),(Channel="Units",Response=ECR_Overlap),(Channel="Players",Response=ECR_Overlap),(Channel="Ghost",Response=ECR_Ignore),(Channel="GameTraceChannel9",Response=ECR_Ignore),(Channel="GameTraceChannel10",Response=ECR_Ignore),(Channel="GameTraceChannel11",Response=ECR_Ignore),(Channel="GameTraceChannel12",Response=ECR_Ignore),(Channel="GameTraceChannel13",Response=ECR_Ignore),(Channel="GameTraceChannel14",Response=ECR_Ignore),(Channel="GameTraceChannel15",Response=ECR_Ignore),(Channel="GameTraceChannel16",Response=ECR_Ignore),(Channel="GameTraceChannel17",Response=ECR_Ignore),(Channel="GameTraceChannel18",Response=ECR_Ignore)),HelpMessage="Needs description",bCanModify=True) ++DefaultChannelResponses=(Channel=ECC_GameTraceChannel1,Name="Units",DefaultResponse=ECR_Block,bTraceType=False,bStaticObject=False) ++DefaultChannelResponses=(Channel=ECC_GameTraceChannel2,Name="Trigger",DefaultResponse=ECR_Ignore,bTraceType=False,bStaticObject=False) ++DefaultChannelResponses=(Channel=ECC_GameTraceChannel3,Name="Projectile",DefaultResponse=ECR_Ignore,bTraceType=False,bStaticObject=False) ++DefaultChannelResponses=(Channel=ECC_GameTraceChannel4,Name="Players",DefaultResponse=ECR_Block,bTraceType=False,bStaticObject=False) ++DefaultChannelResponses=(Channel=ECC_GameTraceChannel5,Name="Dash",DefaultResponse=ECR_Ignore,bTraceType=True,bStaticObject=False) ++DefaultChannelResponses=(Channel=ECC_GameTraceChannel6,Name="Ghost",DefaultResponse=ECR_Block,bTraceType=False,bStaticObject=False) ++DefaultChannelResponses=(Channel=ECC_GameTraceChannel7,Name="Hover",DefaultResponse=ECR_Ignore,bTraceType=True,bStaticObject=False) ++DefaultChannelResponses=(Channel=ECC_GameTraceChannel8,Name="ProjectileUnitsOnly",DefaultResponse=ECR_Ignore,bTraceType=False,bStaticObject=False) ++EditProfiles=(Name="OverlapAll",CustomResponses=((Channel="Units",Response=ECR_Overlap),(Channel="Players",Response=ECR_Overlap),(Channel="Ghost",Response=ECR_Overlap))) ++EditProfiles=(Name="OverlapAllDynamic",CustomResponses=((Channel="Units",Response=ECR_Overlap),(Channel="Players",Response=ECR_Overlap),(Channel="Ghost",Response=ECR_Overlap))) ++EditProfiles=(Name="IgnoreOnlyPawn",CustomResponses=((Channel="Units",Response=ECR_Ignore),(Channel="Players",Response=ECR_Ignore),(Channel="Dash",Response=ECR_Ignore),(Channel="Ghost",Response=ECR_Ignore))) ++EditProfiles=(Name="OverlapOnlyPawn",CustomResponses=((Channel="Units",Response=ECR_Overlap),(Channel="Players",Response=ECR_Overlap),(Channel="Ghost",Response=ECR_Overlap))) ++EditProfiles=(Name="Spectator",CustomResponses=((Channel="Units",Response=ECR_Ignore),(Channel="Players",Response=ECR_Ignore),(Channel="Dash",Response=ECR_Ignore),(Channel="Ghost",Response=ECR_Ignore))) ++EditProfiles=(Name="CharacterMesh",CustomResponses=((Channel="Units",Response=ECR_Ignore),(Channel="Players",Response=ECR_Ignore),(Channel="Ghost",Response=ECR_Ignore))) ++EditProfiles=(Name="Trigger",CustomResponses=((Channel="Units",Response=ECR_Overlap),(Channel="Players",Response=ECR_Overlap),(Channel="Ghost",Response=ECR_Overlap))) ++EditProfiles=(Name="Ragdoll",CustomResponses=((Channel="Units",Response=ECR_Ignore),(Channel="Players",Response=ECR_Ignore),(Channel="Ghost",Response=ECR_Ignore))) ++EditProfiles=(Name="UI",CustomResponses=((Channel="Units",Response=ECR_Overlap),(Channel="Players",Response=ECR_Overlap),(Channel="Ghost",Response=ECR_Overlap))) ++EditProfiles=(Name="BlockAll",CustomResponses=((Channel="Projectile"),(Channel="Dash"),(Channel="Hover"))) +-ProfileRedirects=(OldName="BlockingVolume",NewName="InvisibleWall") +-ProfileRedirects=(OldName="InterpActor",NewName="IgnoreOnlyPawn") +-ProfileRedirects=(OldName="StaticMeshComponent",NewName="BlockAllDynamic") +-ProfileRedirects=(OldName="SkeletalMeshActor",NewName="PhysicsActor") +-ProfileRedirects=(OldName="InvisibleActor",NewName="InvisibleWallDynamic") ++ProfileRedirects=(OldName="BlockingVolume",NewName="InvisibleWall") ++ProfileRedirects=(OldName="InterpActor",NewName="IgnoreOnlyPawn") ++ProfileRedirects=(OldName="StaticMeshComponent",NewName="BlockAllDynamic") ++ProfileRedirects=(OldName="SkeletalMeshActor",NewName="PhysicsActor") ++ProfileRedirects=(OldName="InvisibleActor",NewName="InvisibleWallDynamic") ++ProfileRedirects=(OldName="UnitOverlap",NewName="PlayerOverlap") ++ProfileRedirects=(OldName="projectiles",NewName="Projectiles") ++ProfileRedirects=(OldName="Ghost",NewName="Ghosts") +-CollisionChannelRedirects=(OldName="Static",NewName="WorldStatic") +-CollisionChannelRedirects=(OldName="Dynamic",NewName="WorldDynamic") +-CollisionChannelRedirects=(OldName="VehicleMovement",NewName="Vehicle") +-CollisionChannelRedirects=(OldName="PawnMovement",NewName="Pawn") ++CollisionChannelRedirects=(OldName="Static",NewName="WorldStatic") ++CollisionChannelRedirects=(OldName="Dynamic",NewName="WorldDynamic") ++CollisionChannelRedirects=(OldName="VehicleMovement",NewName="Vehicle") ++CollisionChannelRedirects=(OldName="PawnMovement",NewName="Pawn") + +[/Script/Engine.UserInterfaceSettings] +RenderFocusRule=Never +DefaultCursor=None +TextEditBeamCursor=None +CrosshairsCursor=None +GrabHandCursor=None +GrabHandClosedCursor=None +SlashedCircleCursor=None +ApplicationScale=1.000000 +UIScaleRule=ShortestSide +CustomScalingRuleClass=None +UIScaleCurve=(EditorCurveData=(PreInfinityExtrap=RCCE_Constant,PostInfinityExtrap=RCCE_Constant,Keys=((Time=480.000000,Value=0.444000),(Time=720.000000,Value=0.666000),(Time=1080.000000,Value=1.000000),(Time=8640.000000,Value=8.000000)),DefaultValue=340282346638528859811704183484516925440.000000),ExternalCurve=None) + +[/Script/Engine.RendererSettings] +r.MobileHDR=True +r.MobileNumDynamicPointLights=4 +r.MobileDynamicPointLightsUseStaticBranch=True +r.DiscardUnusedQuality=False +r.AllowOcclusionQueries=True +r.MinScreenRadiusForLights=0.030000 +r.MinScreenRadiusForDepthPrepass=0.030000 +r.MinScreenRadiusForCSMDepth=0.010000 +r.PrecomputedVisibilityWarning=False +r.TextureStreaming=True +Compat.UseDXT5NormalMaps=False +r.AllowStaticLighting=True +r.NormalMapsForStaticLighting=False +r.GenerateMeshDistanceFields=True +r.GenerateLandscapeGIData=True +r.TessellationAdaptivePixelsPerTriangle=48.000000 +r.SeparateTranslucency=True +r.TranslucentSortPolicy=0 +TranslucentSortAxis=(X=0.000000,Y=-1.000000,Z=0.000000) +r.CustomDepth=3 +r.DefaultFeature.Bloom=True +r.DefaultFeature.AmbientOcclusion=True +r.DefaultFeature.AmbientOcclusionStaticFraction=True +r.DefaultFeature.AutoExposure=True +r.DefaultFeature.MotionBlur=True +r.DefaultFeature.LensFlare=True +r.DefaultFeature.AntiAliasing=2 +r.EarlyZPass=3 +r.EarlyZPassMovable=False +r.DBuffer=False +r.ClearSceneMethod=1 +r.BasePassOutputsVelocity=False +r.SelectiveBasePassOutputs=False +vr.InstancedStereo=False +r.WireframeCullThreshold=5.000000 +UIScaleRule=ShortestSide +UIScaleCurve=(EditorCurveData=(PreInfinityExtrap=RCCE_Constant,PostInfinityExtrap=RCCE_Constant,Keys=,DefaultValue=340282346638528859811704183484516925440.000000),ExternalCurve=None) + diff --git a/Config/DefaultGame.ini b/Config/DefaultGame.ini new file mode 100644 index 0000000..10e23d3 --- /dev/null +++ b/Config/DefaultGame.ini @@ -0,0 +1,16 @@ +[/Script/EngineSettings.GeneralProjectSettings] +ProjectID=D0D6694C4B67A9CEF3373094611BE2F1 +ProjectName= +CopyrightNotice=Project Lab - NHTV Igad + +[/Script/UnrealProject.DefaultPlayer] +RespawnDelay = 5.0f +[/Script/UnrealProject.KingOfTheHillGameMode] +MaxGameScore = 120.0f + +[/Script/UnrealProject.DefaultGameInstance] +MenuRevision = 123299 + +[/Script/UnrealEd.ProjectPackagingSettings] +IncludeCrashReporter=False + diff --git a/Config/DefaultInput.ini b/Config/DefaultInput.ini new file mode 100644 index 0000000..8c89de6 --- /dev/null +++ b/Config/DefaultInput.ini @@ -0,0 +1,43 @@ + +[/Script/Engine.InputSettings] +-AxisConfig=(AxisKeyName="Gamepad_LeftX",AxisProperties=(DeadZone=0.25,Exponent=1.f,Sensitivity=1.f)) +-AxisConfig=(AxisKeyName="Gamepad_LeftY",AxisProperties=(DeadZone=0.25,Exponent=1.f,Sensitivity=1.f)) +-AxisConfig=(AxisKeyName="Gamepad_RightX",AxisProperties=(DeadZone=0.25,Exponent=1.f,Sensitivity=1.f)) +-AxisConfig=(AxisKeyName="Gamepad_RightY",AxisProperties=(DeadZone=0.25,Exponent=1.f,Sensitivity=1.f)) +-AxisConfig=(AxisKeyName="MouseX",AxisProperties=(DeadZone=0.f,Exponent=1.f,Sensitivity=0.07f)) +-AxisConfig=(AxisKeyName="MouseY",AxisProperties=(DeadZone=0.f,Exponent=1.f,Sensitivity=0.07f)) ++AxisConfig=(AxisKeyName="Gamepad_LeftX",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Gamepad_LeftY",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Gamepad_RightX",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Gamepad_RightY",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MouseX",AxisProperties=(DeadZone=0.000000,Sensitivity=0.070000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MouseY",AxisProperties=(DeadZone=0.000000,Sensitivity=0.070000,Exponent=1.000000,bInvert=False)) +bAltEnterTogglesFullscreen=True +bUseMouseForTouch=False +bEnableMouseSmoothing=True +bEnableFOVScaling=True +FOVScale=0.011110 +DoubleClickTime=0.200000 ++ActionMappings=(ActionName="RightMouse",Key=RightMouseButton,bShift=False,bCtrl=False,bAlt=False,bCmd=False) ++ActionMappings=(ActionName="ToggleMenu",Key=Zero,bShift=False,bCtrl=False,bAlt=False,bCmd=False) ++ActionMappings=(ActionName="ToggleMenu",Key=Escape,bShift=False,bCtrl=False,bAlt=False,bCmd=False) ++ActionMappings=(ActionName="CastAbility1",Key=Q,bShift=False,bCtrl=False,bAlt=False,bCmd=False) ++ActionMappings=(ActionName="CastAbility2",Key=W,bShift=False,bCtrl=False,bAlt=False,bCmd=False) ++ActionMappings=(ActionName="CastAbility3",Key=E,bShift=False,bCtrl=False,bAlt=False,bCmd=False) ++ActionMappings=(ActionName="HoldPosition",Key=LeftShift,bShift=False,bCtrl=False,bAlt=False,bCmd=False) ++ActionMappings=(ActionName="CastAbility4",Key=R,bShift=False,bCtrl=False,bAlt=False,bCmd=False) ++ActionMappings=(ActionName="CastAbility5",Key=One,bShift=False,bCtrl=False,bAlt=False,bCmd=False) ++ActionMappings=(ActionName="CastAbility6",Key=Two,bShift=False,bCtrl=False,bAlt=False,bCmd=False) ++ActionMappings=(ActionName="CastAbility7",Key=Three,bShift=False,bCtrl=False,bAlt=False,bCmd=False) ++ActionMappings=(ActionName="CastAbility8",Key=Four,bShift=False,bCtrl=False,bAlt=False,bCmd=False) ++ActionMappings=(ActionName="LeftMouse",Key=LeftMouseButton,bShift=False,bCtrl=False,bAlt=False,bCmd=False) ++ActionMappings=(ActionName="ToggleSkillTree",Key=P,bShift=False,bCtrl=False,bAlt=False,bCmd=False) ++ActionMappings=(ActionName="Attack",Key=LeftMouseButton,bShift=False,bCtrl=False,bAlt=False,bCmd=False) +bAlwaysShowTouchInterface=False +bShowConsoleOnFourFingerTap=True +DefaultTouchInterface=None +ConsoleKey=None +-ConsoleKeys=Tilde ++ConsoleKeys=Tilde + + diff --git a/MatchMaking/matchmaking.hpp b/MatchMaking/matchmaking.hpp new file mode 100644 index 0000000..dac5082 --- /dev/null +++ b/MatchMaking/matchmaking.hpp @@ -0,0 +1,46 @@ +#ifndef _HEADER_MATCHMAKING +#define _HEADER_MATCHMAKING + +#include +using std::wstring; + +typedef unsigned long long NetID; +typedef signed int ChannelID; + +enum class ConnectionState +{ + Invalid = -1, + Disconnected = 0, + Connecting = 1, + Connected = 2, +}; + +class MatchMakingImpl; +class IMatchMakingInterface +{ +protected: + IMatchMakingInterface(NetID netId); + +public: + virtual ~IMatchMakingInterface(); + + void Tick(); + + ConnectionState GetState() const; + bool IsConnected() const; + + bool HostMatch(ChannelID channel); + bool DestroyMatch(); + + virtual void OnConnect() = 0; + virtual void OnDisconnect() = 0; + virtual void OnForceJoinSession(NetID netId, ChannelID channelID) = 0; + +private: + MatchMakingImpl* m_impl; + + IMatchMakingInterface(const IMatchMakingInterface& other) = delete; + IMatchMakingInterface& operator=(const IMatchMakingInterface& other) = delete; +}; + +#endif \ No newline at end of file diff --git a/Plugins/CustomBlueprints/CustomBlueprints.uplugin b/Plugins/CustomBlueprints/CustomBlueprints.uplugin new file mode 100644 index 0000000..fab1f10 --- /dev/null +++ b/Plugins/CustomBlueprints/CustomBlueprints.uplugin @@ -0,0 +1,24 @@ +{ + "FileVersion": 1, + "Version": 1, + "VersionName": "1.0", + "FriendlyName": "Custom Blueprints Plugin", + "Description": "A plugin for ability system blueprint nodes", + "Category": "Abilities", + "CreatedBy": "Guus Waals", + "CreatedByURL": "http://www.tdrz.nl/", + "DocsURL": "http://www.tdrz.nl/portfolio.html", + "MarketplaceURL": "", + "SupportURL": "http://www.tdrz.nl/portfolio.html", + "Modules": [ + { + "Name": "CustomBlueprintsEditor", + "Type": "Developer", + "LoadingPhase": "PreDefault" + } + ], + "EnabledByDefault": false, + "CanContainContent": true, + "IsBetaVersion": false, + "Installed": false +} \ No newline at end of file diff --git a/Plugins/CustomBlueprints/Resources/Icon128.png b/Plugins/CustomBlueprints/Resources/Icon128.png new file mode 100644 index 0000000..4669995 Binary files /dev/null and b/Plugins/CustomBlueprints/Resources/Icon128.png differ diff --git a/Plugins/CustomBlueprints/Source/CustomBlueprintsEditor/CustomBlueprintsEditor.build.cs b/Plugins/CustomBlueprints/Source/CustomBlueprintsEditor/CustomBlueprintsEditor.build.cs new file mode 100644 index 0000000..7675e9d --- /dev/null +++ b/Plugins/CustomBlueprints/Source/CustomBlueprintsEditor/CustomBlueprintsEditor.build.cs @@ -0,0 +1,39 @@ +namespace UnrealBuildTool.Rules +{ + public class CustomBlueprintsEditor : ModuleRules + { + public CustomBlueprintsEditor(TargetInfo Target) + { + PrivateIncludePaths.AddRange( + new string[] { + "Editor/BlueprintGraph/Private", + "Editor/KismetCompiler/Public", + } + ); + + PublicDependencyModuleNames.AddRange( + new string[] { + "Core", + "CoreUObject", + "Engine", + "InputCore", + "Slate", + "EditorStyle", + "AIModule", + } + ); + + PrivateDependencyModuleNames.AddRange( + new string[] { + "EditorStyle", + "KismetCompiler", + "UnrealEd", + "GraphEditor", + "SlateCore", + "Kismet", + "BlueprintGraph" + } + ); + } + } +} \ No newline at end of file diff --git a/Plugins/CustomBlueprints/Source/CustomBlueprintsEditor/CustomBlueprintsEditor.cpp b/Plugins/CustomBlueprints/Source/CustomBlueprintsEditor/CustomBlueprintsEditor.cpp new file mode 100644 index 0000000..5684f82 --- /dev/null +++ b/Plugins/CustomBlueprints/Source/CustomBlueprintsEditor/CustomBlueprintsEditor.cpp @@ -0,0 +1,45 @@ +#include "CustomBlueprintsEditor.h" + +class UClass* characterClass = nullptr; +class UClass* abilityTriggerClass = nullptr; +class UClass* abilityGroupClass = nullptr; +class UClass* projectileClass = nullptr; +class UClass* modifierClass = nullptr; +class UClass* libClass = nullptr; + +void LoadDependencies() +{ + libClass = FindObject(ANY_PACKAGE, L"/Script/UnrealProject.Lib"); + characterClass = FindObject(ANY_PACKAGE, L"/Script/UnrealProject.NetworkCharacter"); + abilityGroupClass = FindObject(ANY_PACKAGE, L"/Script/UnrealProject.AbilityEventGroup"); + abilityTriggerClass = FindObject(ANY_PACKAGE, L"/Script/UnrealProject.AbilityTriggerBase"); + projectileClass = FindObject(ANY_PACKAGE, L"/Script/UnrealProject.ProjectileBase"); + modifierClass = FindObject(ANY_PACKAGE, L"/Script/UnrealProject.Modifier"); + //printf("\nhoi %p\n%p\n%p\n%p\n%p\n%p\n", libClass, characterClass, abilityGroupClass, abilityTriggerClass, projectileClass, modifierClass); +} + +class FCustomBlueprintsEditor : public IModuleInterface +{ +public: + /** IModuleInterface implementation */ + virtual void StartupModule() override + { + auto module = FModuleManager::LoadModuleChecked("UnrealProject"); + FModuleManager& moduleManager = FModuleManager::Get(); + bool ok = moduleManager.DoesLoadedModuleHaveUObjects(L"UnrealProject"); + + LoadDependencies(); + // Class'/Script/UnrealProject.Lib' + // Class'/Script/UnrealProject.AbilityEventGroup' + // Class'/Script/UnrealProject.AbilityTriggerBase' + // Class'/Script/UnrealProject.ProjectileBase' + // Class'/Script/UnrealProject.Modifier' + + } + virtual void ShutdownModule() override + { + } +private: +}; + +IMPLEMENT_MODULE(FCustomBlueprintsEditor, CustomBlueprintsEditor) \ No newline at end of file diff --git a/Plugins/CustomBlueprints/Source/CustomBlueprintsEditor/CustomBlueprintsEditor.h b/Plugins/CustomBlueprints/Source/CustomBlueprintsEditor/CustomBlueprintsEditor.h new file mode 100644 index 0000000..b9dcb93 --- /dev/null +++ b/Plugins/CustomBlueprints/Source/CustomBlueprintsEditor/CustomBlueprintsEditor.h @@ -0,0 +1,7 @@ +// Project Lab - NHTV Igad + +#pragma once +#include "EngineMinimal.h" +#include "ModuleManager.h" + +void LoadDependencies(); \ No newline at end of file diff --git a/Plugins/CustomBlueprints/Source/CustomBlueprintsEditor/CustomBlueprintsEditorPCH.cpp b/Plugins/CustomBlueprints/Source/CustomBlueprintsEditor/CustomBlueprintsEditorPCH.cpp new file mode 100644 index 0000000..d59ae4d --- /dev/null +++ b/Plugins/CustomBlueprints/Source/CustomBlueprintsEditor/CustomBlueprintsEditorPCH.cpp @@ -0,0 +1 @@ +#include "CustomBlueprintsEditor.h" \ No newline at end of file diff --git a/Plugins/CustomBlueprints/Source/CustomBlueprintsEditor/Globals.h b/Plugins/CustomBlueprints/Source/CustomBlueprintsEditor/Globals.h new file mode 100644 index 0000000..d948693 --- /dev/null +++ b/Plugins/CustomBlueprints/Source/CustomBlueprintsEditor/Globals.h @@ -0,0 +1,11 @@ +// Project Lab - NHTV Igad + +#pragma once +#include "EngineMinimal.h" + +extern class UClass* characterClass; +extern class UClass* abilityTriggerClass; +extern class UClass* abilityGroupClass; +extern class UClass* projectileClass; +extern class UClass* modifierClass; +extern class UClass* libClass; \ No newline at end of file diff --git a/Plugins/CustomBlueprints/Source/CustomBlueprintsEditor/SpawnActorBaseNode.cpp b/Plugins/CustomBlueprints/Source/CustomBlueprintsEditor/SpawnActorBaseNode.cpp new file mode 100644 index 0000000..c03e95f --- /dev/null +++ b/Plugins/CustomBlueprints/Source/CustomBlueprintsEditor/SpawnActorBaseNode.cpp @@ -0,0 +1,560 @@ +#include "CustomBlueprintsEditor.h" +#include "SpawnActorBaseNode.h" +#include "Globals.h" +#include "BlueprintEditorUtils.h" +#include "BlueprintActionDatabaseRegistrar.h" +#include "BlueprintNodeSpawner.h" + +struct FK2Node_SpawnActorFromClassHelper +{ + static FString WorldContextPinName; + static FString ClassPinName; + static FString SpawnTransformPinName; + static FString TargetPinName; +}; + +FString FK2Node_SpawnActorFromClassHelper::WorldContextPinName(TEXT("WorldContextObject")); +FString FK2Node_SpawnActorFromClassHelper::ClassPinName(TEXT("Class")); +FString FK2Node_SpawnActorFromClassHelper::SpawnTransformPinName(TEXT("SpawnTransform")); +FString FK2Node_SpawnActorFromClassHelper::TargetPinName(TEXT("Target")); + +#define FMT_TEXT(__x) FText::FromString(FString() + __x) + +UK2Node_SpawnActorBaseNode::UK2Node_SpawnActorBaseNode(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + NodeTooltip = FMT_TEXT("Oh Nooooos"); +} +void UK2Node_SpawnActorBaseNode::AllocateDefaultPins() +{ + const UEdGraphSchema_K2* K2Schema = GetDefault(); + + // Add execution pins + CreatePin(EGPD_Input, K2Schema->PC_Exec, TEXT(""), NULL, false, false, K2Schema->PN_Execute); + CreatePin(EGPD_Output, K2Schema->PC_Exec, TEXT(""), NULL, false, false, K2Schema->PN_Then); + + // If required add the world context pin + if(GetBlueprint()->ParentClass->HasMetaData(FBlueprintMetadata::MD_ShowWorldContextPin)) + { + CreatePin(EGPD_Input, K2Schema->PC_Object, TEXT(""), UObject::StaticClass(), false, false, FK2Node_SpawnActorFromClassHelper::WorldContextPinName); + } + + // Add blueprint pin + UEdGraphPin* ClassPin = CreatePin(EGPD_Input, K2Schema->PC_Class, TEXT(""), GetActorClass(), false, false, FK2Node_SpawnActorFromClassHelper::ClassPinName); + K2Schema->ConstructBasicPinTooltip(*ClassPin, FMT_TEXT("The " + GetObjectName() + " you want to spawn"), ClassPin->PinToolTip); + + // Transform pin + if(SpawningFunctionHasTransform()) + { + UScriptStruct* TransformStruct = TBaseStructure::Get(); + UEdGraphPin* TransformPin = CreatePin(EGPD_Input, K2Schema->PC_Struct, TEXT(""), TransformStruct, false, false, FK2Node_SpawnActorFromClassHelper::SpawnTransformPinName); + K2Schema->ConstructBasicPinTooltip(*TransformPin, FMT_TEXT("The transform to spawn the " + GetObjectName() + " with"), TransformPin->PinToolTip); + } + + // Result pin + UEdGraphPin* ResultPin = CreatePin(EGPD_Output, K2Schema->PC_Object, TEXT(""), GetActorClass(), false, false, K2Schema->PN_ReturnValue); + K2Schema->ConstructBasicPinTooltip(*ResultPin, FMT_TEXT("The spawned " + GetObjectName()), ResultPin->PinToolTip); + + RegisterAdditionalPins(K2Schema); + + Super::AllocateDefaultPins(); +} +void UK2Node_SpawnActorBaseNode::CreatePinsForClass(UClass* InClass, TArray& OutClassPins) +{ + check(InClass != NULL); + + const UEdGraphSchema_K2* K2Schema = GetDefault(); + + const UObject* const ClassDefaultObject = InClass->GetDefaultObject(false); + + for(TFieldIterator PropertyIt(InClass, EFieldIteratorFlags::IncludeSuper); PropertyIt; ++PropertyIt) + { + UProperty* Property = *PropertyIt; + UClass* PropertyClass = CastChecked(Property->GetOuter()); + const bool bIsDelegate = Property->IsA(UMulticastDelegateProperty::StaticClass()); + const bool bIsExposedToSpawn = UEdGraphSchema_K2::IsPropertyExposedOnSpawn(Property); + const bool bIsSettableExternally = !Property->HasAnyPropertyFlags(CPF_DisableEditOnInstance); + + if(bIsExposedToSpawn && + !Property->HasAnyPropertyFlags(CPF_Parm) && + bIsSettableExternally && + Property->HasAllPropertyFlags(CPF_BlueprintVisible) && + !bIsDelegate && + (NULL == FindPin(Property->GetName()))) + { + UEdGraphPin* Pin = CreatePin(EGPD_Input, TEXT(""), TEXT(""), NULL, false, false, Property->GetName()); + const bool bPinGood = (Pin != NULL) && K2Schema->ConvertPropertyToPinType(Property, Pin->PinType); + OutClassPins.Add(Pin); + + if(ClassDefaultObject && Pin != NULL && K2Schema->PinDefaultValueIsEditable(*Pin)) + { + FString DefaultValueAsString; + const bool bDefaultValueSet = FBlueprintEditorUtils::PropertyValueToString(Property, reinterpret_cast(ClassDefaultObject), DefaultValueAsString); + check(bDefaultValueSet); + K2Schema->TrySetDefaultValue(*Pin, DefaultValueAsString); + } + + // Copy tooltip from the property. + if(Pin != nullptr) + { + K2Schema->ConstructBasicPinTooltip(*Pin, Property->GetToolTipText(), Pin->PinToolTip); + } + } + } + + // Change class of output pin + UEdGraphPin* ResultPin = GetResultPin(); + ResultPin->PinType.PinSubCategoryObject = InClass; +} +UClass* UK2Node_SpawnActorBaseNode::GetClassToSpawn(const TArray* InPinsToSearch) const +{ + UClass* UseSpawnClass = nullptr; + const TArray* PinsToSearch = InPinsToSearch ? InPinsToSearch : &Pins; + + UEdGraphPin* ClassPin = GetClassPin(PinsToSearch); + if(ClassPin && ClassPin->DefaultObject != NULL && ClassPin->LinkedTo.Num() == 0) + { + UseSpawnClass = CastChecked(ClassPin->DefaultObject); + } + else if(ClassPin && (1 == ClassPin->LinkedTo.Num())) + { + auto SourcePin = ClassPin->LinkedTo[0]; + UseSpawnClass = SourcePin ? Cast(SourcePin->PinType.PinSubCategoryObject.Get()) : nullptr; + } + + return UseSpawnClass; +} +void UK2Node_SpawnActorBaseNode::ReallocatePinsDuringReconstruction(TArray& OldPins) +{ + AllocateDefaultPins(); + UClass* UseSpawnClass = GetClassToSpawn(&OldPins); + + if(UseSpawnClass != NULL) + { + // Reassign class pin + UEdGraphPin* newClassPin = FindPinChecked(FK2Node_SpawnActorFromClassHelper::ClassPinName); + newClassPin->DefaultObject = UseSpawnClass; + + TArray ClassPins; + CreatePinsForClass(UseSpawnClass, ClassPins); + } +} +void UK2Node_SpawnActorBaseNode::PostPlacedNewNode() +{ + Super::PostPlacedNewNode(); + + UClass* UseSpawnClass = GetClassToSpawn(); + if(UseSpawnClass != NULL) + { + TArray ClassPins; + CreatePinsForClass(UseSpawnClass, ClassPins); + } +} +bool UK2Node_SpawnActorBaseNode::IsSpawnVarPin(UEdGraphPin* Pin) +{ + const UEdGraphSchema_K2* K2Schema = GetDefault(); + + UEdGraphPin* ParentPin = Pin->ParentPin; + while(ParentPin) + { + if(ParentPin->PinName == FK2Node_SpawnActorFromClassHelper::SpawnTransformPinName) + { + return false; + } + ParentPin = ParentPin->ParentPin; + } + + return(Pin->PinName != K2Schema->PN_Execute && + Pin->PinName != K2Schema->PN_Then && + Pin->PinName != K2Schema->PN_ReturnValue && + Pin->PinName != FK2Node_SpawnActorFromClassHelper::ClassPinName && + Pin->PinName != FK2Node_SpawnActorFromClassHelper::WorldContextPinName && + Pin->PinName != FK2Node_SpawnActorFromClassHelper::SpawnTransformPinName && + Pin->PinName != FK2Node_SpawnActorFromClassHelper::TargetPinName && + !IsAdditionalPin(Pin)); +} +void UK2Node_SpawnActorBaseNode::OnClassPinChanged() +{ + const UEdGraphSchema_K2* K2Schema = GetDefault(); + + // Remove all pins related to archetype variables + TArray OldPins = Pins; + TArray OldClassPins; + + for(int32 i = 0; i < OldPins.Num(); i++) + { + UEdGraphPin* OldPin = OldPins[i]; + if(IsSpawnVarPin(OldPin)) + { + Pins.Remove(OldPin); + OldClassPins.Add(OldPin); + } + } + + m_cachedNodeTitle.MarkDirty(); + + UClass* UseSpawnClass = GetClassToSpawn(); + TArray NewClassPins; + if(UseSpawnClass != NULL) + { + CreatePinsForClass(UseSpawnClass, NewClassPins); + } + + UEdGraphPin* ResultPin = GetResultPin(); + // Cache all the pin connections to the ResultPin, we will attempt to recreate them + TArray ResultPinConnectionList = ResultPin->LinkedTo; + // Because the archetype has changed, we break the output link as the output pin type will change + ResultPin->BreakAllPinLinks(); + + // Recreate any pin links to the Result pin that are still valid + for(UEdGraphPin* Connections : ResultPinConnectionList) + { + K2Schema->TryCreateConnection(ResultPin, Connections); + } + + K2Schema->ConstructBasicPinTooltip(*ResultPin, FMT_TEXT("The spawned " + GetObjectName()), ResultPin->PinToolTip); + + // Rewire the old pins to the new pins so connections are maintained if possible + RewireOldPinsToNewPins(OldClassPins, NewClassPins); + + // Destroy the old pins + DestroyPinList(OldClassPins); + + // Refresh the UI for the graph so the pin changes show up + UEdGraph* Graph = GetGraph(); + Graph->NotifyGraphChanged(); + + // Mark dirty + FBlueprintEditorUtils::MarkBlueprintAsModified(GetBlueprint()); +} +void UK2Node_SpawnActorBaseNode::PinConnectionListChanged(UEdGraphPin* ChangedPin) +{ + if(ChangedPin && (ChangedPin->PinName == FK2Node_SpawnActorFromClassHelper::ClassPinName)) + { + OnClassPinChanged(); + } +} +void UK2Node_SpawnActorBaseNode::PinDefaultValueChanged(UEdGraphPin* ChangedPin) +{ + if(ChangedPin && (ChangedPin->PinName == FK2Node_SpawnActorFromClassHelper::ClassPinName)) + { + OnClassPinChanged(); + } +} +FText UK2Node_SpawnActorBaseNode::GetTooltipText() const +{ + return NodeTooltip; +} +UEdGraphPin* UK2Node_SpawnActorBaseNode::GetThenPin()const +{ + const UEdGraphSchema_K2* K2Schema = GetDefault(); + + UEdGraphPin* Pin = FindPinChecked(K2Schema->PN_Then); + check(Pin->Direction == EGPD_Output); + return Pin; +} +UEdGraphPin* UK2Node_SpawnActorBaseNode::GetClassPin(const TArray* InPinsToSearch) const +{ + const TArray* PinsToSearch = InPinsToSearch ? InPinsToSearch : &Pins; + + UEdGraphPin* Pin = NULL; + for(auto PinIt = PinsToSearch->CreateConstIterator(); PinIt; ++PinIt) + { + UEdGraphPin* TestPin = *PinIt; + if(TestPin && TestPin->PinName == FK2Node_SpawnActorFromClassHelper::ClassPinName) + { + Pin = TestPin; + break; + } + } + check(Pin == NULL || Pin->Direction == EGPD_Input); + return Pin; +} +UEdGraphPin* UK2Node_SpawnActorBaseNode::GetSpawnTransformPin() const +{ + UEdGraphPin* Pin = FindPinChecked(FK2Node_SpawnActorFromClassHelper::SpawnTransformPinName); + check(Pin->Direction == EGPD_Input); + return Pin; +} +UEdGraphPin* UK2Node_SpawnActorBaseNode::GetWorldContextPin() const +{ + UEdGraphPin* Pin = FindPin(FK2Node_SpawnActorFromClassHelper::WorldContextPinName); + check(Pin == NULL || Pin->Direction == EGPD_Input); + return Pin; +} +UEdGraphPin* UK2Node_SpawnActorBaseNode::GetResultPin() const +{ + const UEdGraphSchema_K2* K2Schema = GetDefault(); + + UEdGraphPin* Pin = FindPinChecked(K2Schema->PN_ReturnValue); + check(Pin->Direction == EGPD_Output); + return Pin; +} +FLinearColor UK2Node_SpawnActorBaseNode::GetNodeTitleColor() const +{ + return Super::GetNodeTitleColor(); +} +FText UK2Node_SpawnActorBaseNode::GetNodeTitle(ENodeTitleType::Type TitleType) const +{ + FText NodeTitle = FMT_TEXT("Spawn " + GetObjectName() + " from Class"); + if(TitleType != ENodeTitleType::MenuTitle) + { + if(UEdGraphPin* ClassPin = FindPin(FK2Node_SpawnActorFromClassHelper::ClassPinName)) + { + if(ClassPin->LinkedTo.Num() > 0) + { + // Blueprint will be determined dynamically, so we don't have the name in this case + NodeTitle = FMT_TEXT("Spawn " + GetObjectName()); + } + else if(ClassPin->DefaultObject == nullptr) + { + NodeTitle = FMT_TEXT("Spawn " + GetObjectName() + " NONE"); + } + else + { + if(m_cachedNodeTitle.IsOutOfDate(this)) + { + FText ClassName; + if(UClass* PickedClass = Cast(ClassPin->DefaultObject)) + { + ClassName = PickedClass->GetDisplayNameText(); + } + + FFormatNamedArguments Args; + Args.Add(TEXT("ClassName"), ClassName); + + // FText::Format() is slow, so we cache this to save on performance + m_cachedNodeTitle.SetCachedText(FText::Format(FMT_TEXT("Spawn {ClassName}"), Args), this); + } + NodeTitle = m_cachedNodeTitle; + } + } + else + { + NodeTitle = FMT_TEXT(GetObjectName() + " NONE"); + } + } + return NodeTitle; +} +bool UK2Node_SpawnActorBaseNode::IsCompatibleWithGraph(const UEdGraph* TargetGraph) const +{ + UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraph(TargetGraph); + return Super::IsCompatibleWithGraph(TargetGraph) && (!Blueprint || FBlueprintEditorUtils::FindUserConstructionScript(Blueprint) != TargetGraph); +} +void UK2Node_SpawnActorBaseNode::GetNodeAttributes(TArray>& OutNodeAttributes) const +{ + UClass* ClassToSpawn = GetClassToSpawn(); + const FString ClassToSpawnStr = ClassToSpawn ? ClassToSpawn->GetName() : TEXT("InvalidClass"); + OutNodeAttributes.Add(TKeyValuePair(TEXT("Type"), FString() + "Spawn" + GetObjectName() + "FromClass")); + OutNodeAttributes.Add(TKeyValuePair(TEXT("Class"), GetClass()->GetName())); + OutNodeAttributes.Add(TKeyValuePair(TEXT("Name"), GetName())); + OutNodeAttributes.Add(TKeyValuePair(TEXT("ActorClass"), ClassToSpawnStr)); +} +void UK2Node_SpawnActorBaseNode::GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const +{ + // actions get registered under specific object-keys; the idea is that + // actions might have to be updated (or deleted) if their object-key is + // mutated (or removed)... here we use the node's class (so if the node + // type disappears, then the action should go with it) + UClass* ActionKey = GetClass(); + //// to keep from needlessly instantiating a UBlueprintNodeSpawner, first + //// check to make sure that the registrar is looking for actions of this type + //// (could be regenerating actions for a specific asset, and therefore the + //// registrar would only accept actions corresponding to that asset) + if(ActionRegistrar.IsOpenForRegistration(ActionKey)) + { + UBlueprintNodeSpawner* NodeSpawner = UBlueprintNodeSpawner::Create(GetClass()); + check(NodeSpawner != nullptr); + + ActionRegistrar.AddBlueprintAction(ActionKey, NodeSpawner); + } +} +FText UK2Node_SpawnActorBaseNode::GetMenuCategory() const +{ + return FMT_TEXT("Ability"); +} +FNodeHandlingFunctor* UK2Node_SpawnActorBaseNode::CreateNodeHandler(FKismetCompilerContext& CompilerContext) const +{ + return new FNodeHandlingFunctor(CompilerContext); +} +void UK2Node_SpawnActorBaseNode::ExpandNode(class FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph) +{ + Super::ExpandNode(CompilerContext, SourceGraph); + + if(!libClass) + { + LoadDependencies(); + if(!libClass) + { + CompilerContext.MessageLog.Error(L"Dependencies not initialized yet"); + return; + } + } + + FName BeginSpawningBlueprintFuncName = GetBeginSpawningFunction(); + static FString ActorClassParamName = FString(TEXT("ActorClass")); + static FString WorldContextParamName = FString(TEXT("WorldContextObject")); + + FName FinishSpawningFuncName = GetFinishSpawningFunction(); + static FString ActorParamName = FString(TEXT("Actor")); + static FString TransformParamName = FString(TEXT("SpawnTransform")); + static FString CollisionHandlingOverrideParamName = FString(TEXT("CollisionHandlingOverride")); + static FString OwnerParamName = FString(TEXT("Owner")); + + static FString ObjectParamName = FString(TEXT("Object")); + static FString ValueParamName = FString(TEXT("Value")); + static FString PropertyNameParamName = FString(TEXT("PropertyName")); + + UK2Node_SpawnActorBaseNode* SpawnNode = this; + UEdGraphPin* SpawnNodeExec = SpawnNode->GetExecPin(); + UEdGraphPin* SpawnWorldContextPin = SpawnNode->GetWorldContextPin(); + UEdGraphPin* SpawnClassPin = SpawnNode->GetClassPin(); + UEdGraphPin* SpawnNodeThen = SpawnNode->GetThenPin(); + UEdGraphPin* SpawnNodeResult = SpawnNode->GetResultPin(); + + UClass* SpawnClass = (SpawnClassPin != NULL) ? Cast(SpawnClassPin->DefaultObject) : NULL; + if((0 == SpawnClassPin->LinkedTo.Num()) && (NULL == SpawnClass)) + { + CompilerContext.MessageLog.Error(L"Spawn node @@ must have a class specified.", SpawnNode); + // we break exec links so this is the only error we get, don't want the SpawnActor node being considered and giving 'unexpected node' type warnings + SpawnNode->BreakAllNodeLinks(); + return; + } + + ////////////////////////////////////////////////////////////////////////// + // create 'begin spawn' call node + UK2Node_CallFunction* CallBeginSpawnNode = CompilerContext.SpawnIntermediateNode(SpawnNode, SourceGraph); + CallBeginSpawnNode->FunctionReference.SetExternalMember(BeginSpawningBlueprintFuncName, libClass); + CallBeginSpawnNode->AllocateDefaultPins(); + + UEdGraphPin* CallBeginExec = CallBeginSpawnNode->GetExecPin(); + UEdGraphPin* CallBeginWorldContextPin = CallBeginSpawnNode->FindPin(WorldContextParamName); + if(!CallBeginWorldContextPin) + { + CompilerContext.MessageLog.Error(L"World Context Pin not found"); + return; + } + UEdGraphPin* CallBeginActorClassPin = CallBeginSpawnNode->FindPinChecked(ActorClassParamName); + UEdGraphPin* CallBeginTransform = nullptr; + UEdGraphPin* CallBeginResult = CallBeginSpawnNode->GetReturnValuePin(); + + // Move 'exec' connection from spawn node to 'begin spawn' + CompilerContext.MovePinLinksToIntermediate(*SpawnNodeExec, *CallBeginExec); + + if(SpawnClassPin->LinkedTo.Num() > 0) + { + // Copy the 'blueprint' connection from the spawn node to 'begin spawn' + CompilerContext.MovePinLinksToIntermediate(*SpawnClassPin, *CallBeginActorClassPin); + } + else + { + // Copy blueprint literal onto begin spawn call + CallBeginActorClassPin->DefaultObject = SpawnClass; + } + + // Copy the world context connection from the spawn node to 'begin spawn' if necessary + if(SpawnWorldContextPin) + { + CompilerContext.MovePinLinksToIntermediate(*SpawnWorldContextPin, *CallBeginWorldContextPin); + } + + // Copy the 'transform' connection from the spawn node to 'begin spawn' + + if(SpawningFunctionHasTransform()) + { + UEdGraphPin* SpawnNodeTransform = SpawnNode->GetSpawnTransformPin(); + CallBeginTransform = CallBeginSpawnNode->FindPinChecked(TransformParamName); + CompilerContext.MovePinLinksToIntermediate(*SpawnNodeTransform, *CallBeginTransform); + } + + ConnectAdditionalPins(CompilerContext, nullptr, CallBeginSpawnNode); + + ////////////////////////////////////////////////////////////////////////// + // create 'finish spawn' call node + UK2Node_CallFunction* CallFinishSpawnNode = CompilerContext.SpawnIntermediateNode(SpawnNode, SourceGraph); + CallFinishSpawnNode->FunctionReference.SetExternalMember(FinishSpawningFuncName, libClass); + CallFinishSpawnNode->AllocateDefaultPins(); + + UEdGraphPin* CallFinishExec = CallFinishSpawnNode->GetExecPin(); + UEdGraphPin* CallFinishThen = CallFinishSpawnNode->GetThenPin(); + UEdGraphPin* CallFinishActor = CallFinishSpawnNode->FindPinChecked(ActorParamName); + UEdGraphPin* CallFinishResult = CallFinishSpawnNode->GetReturnValuePin(); + + // Move 'then' connection from spawn node to 'finish spawn' + CompilerContext.MovePinLinksToIntermediate(*SpawnNodeThen, *CallFinishThen); + + if(SpawningFunctionHasTransform()) + { + // Copy transform connection + UEdGraphPin* CallFinishTransform = CallFinishSpawnNode->FindPinChecked(TransformParamName); + CompilerContext.CopyPinLinksToIntermediate(*CallBeginTransform, *CallFinishTransform); + } + + ConnectAdditionalPins(CompilerContext, nullptr, CallFinishSpawnNode); + + // Connect output actor from 'begin' to 'finish' + CallBeginResult->MakeLinkTo(CallFinishActor); + + // Move result connection from spawn node to 'finish spawn' + CallFinishResult->PinType = SpawnNodeResult->PinType; // Copy type so it uses the right actor subclass + CompilerContext.MovePinLinksToIntermediate(*SpawnNodeResult, *CallFinishResult); + + ////////////////////////////////////////////////////////////////////////// + // create 'set var' nodes + + // Get 'result' pin from 'begin spawn', this is the actual actor we want to set properties on + UEdGraphPin* LastThen = FKismetCompilerUtilities::GenerateAssignmentNodes(CompilerContext, SourceGraph, CallBeginSpawnNode, SpawnNode, CallBeginResult, GetClassToSpawn()); + + // Make exec connection between 'then' on last node and 'finish' + LastThen->MakeLinkTo(CallFinishExec); + + // Break any links to the expanded node + SpawnNode->BreakAllNodeLinks(); +} +bool UK2Node_SpawnActorBaseNode::HasExternalDependencies(TArray* OptionalOutput) const +{ + UClass* SourceClass = GetClassToSpawn(); + const UBlueprint* SourceBlueprint = GetBlueprint(); + const bool bResult = (SourceClass != NULL) && (SourceClass->ClassGeneratedBy != SourceBlueprint); + if(bResult && OptionalOutput) + { + OptionalOutput->AddUnique(SourceClass); + } + const bool bSuperResult = Super::HasExternalDependencies(OptionalOutput); + return bSuperResult || bResult; +} + +UClass* USpawnGroupNode::GetActorClass() const +{ + return abilityGroupClass; +} + +UClass* USpawnTriggerNode::GetActorClass() const +{ + return abilityTriggerClass; +} + +UClass* USpawnModifierNode::GetActorClass() const +{ + return modifierClass; +} +void USpawnModifierNode::RegisterAdditionalPins(const class UEdGraphSchema_K2* K2Schema) +{ + UEdGraphPin* TargetPin = CreatePin(EGPD_Input, K2Schema->PC_Object, TEXT(""), characterClass, false, false, "Target"); +} +bool USpawnModifierNode::IsAdditionalPin(class UEdGraphPin* pin) +{ + return pin->PinName == "Target"; +} +void USpawnModifierNode::ConnectAdditionalPins(class FKismetCompilerContext& CompilerContext, UK2Node* last, UK2Node* dst) +{ + UEdGraphPin* dstPin = dst->FindPin("Target"); + UEdGraphPin* srcPin = FindPin("Target"); + if(dstPin && srcPin) + CompilerContext.CopyPinLinksToIntermediate(*srcPin, *dstPin); +} + +UClass* USpawnProjectileNode::GetActorClass() const +{ + return projectileClass; +} \ No newline at end of file diff --git a/Plugins/CustomBlueprints/Source/CustomBlueprintsEditor/SpawnActorBaseNode.h b/Plugins/CustomBlueprints/Source/CustomBlueprintsEditor/SpawnActorBaseNode.h new file mode 100644 index 0000000..55e650a --- /dev/null +++ b/Plugins/CustomBlueprints/Source/CustomBlueprintsEditor/SpawnActorBaseNode.h @@ -0,0 +1,119 @@ +// Project Lab - NHTV Igad +#pragma once + +#include "KismetCompiler.h" +#include "Runtime/Engine/Classes/Kismet/GameplayStatics.h" +#include "SpawnActorBaseNode.Generated.h" + +UCLASS() +class ATestObject : public AActor +{ + GENERATED_BODY() +}; + +UCLASS() +class UK2Node_SpawnActorBaseNode : public UK2Node +{ + GENERATED_UCLASS_BODY() + + // Begin UEdGraphNode interface. + virtual void AllocateDefaultPins() override; + virtual FLinearColor GetNodeTitleColor() const override; + virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override; + virtual void PinDefaultValueChanged(UEdGraphPin* Pin) override; + virtual void PinConnectionListChanged(UEdGraphPin* Pin) override; + virtual FText GetTooltipText() const override; + virtual void ExpandNode(class FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph) override; + virtual bool HasExternalDependencies(TArray* OptionalOutput) const override; + virtual FName GetPaletteIcon(FLinearColor& OutColor) const override{ return TEXT("GraphEditor.SpawnActor_16x"); } + virtual bool IsCompatibleWithGraph(const UEdGraph* TargetGraph) const override; + virtual void PostPlacedNewNode() override; + // End UEdGraphNode interface. + + // Begin UK2Node interface + virtual bool IsNodeSafeToIgnore() const override { return true; } + virtual void ReallocatePinsDuringReconstruction(TArray& OldPins) override; + virtual void GetNodeAttributes(TArray>& OutNodeAttributes) const override; + virtual void GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const override; + virtual FText GetMenuCategory() const override; + virtual class FNodeHandlingFunctor* CreateNodeHandler(class FKismetCompilerContext& CompilerContext) const override; + // End UK2Node interface + + + void CreatePinsForClass(UClass* InClass, TArray& OutClassPins); + bool IsSpawnVarPin(UEdGraphPin* Pin); + + UEdGraphPin* GetThenPin() const; + UEdGraphPin* GetClassPin(const TArray* InPinsToSearch = NULL) const; + UEdGraphPin* GetWorldContextPin() const; + UEdGraphPin* GetSpawnTransformPin() const; + UEdGraphPin* GetResultPin() const; + + UClass* GetClassToSpawn(const TArray* InPinsToSearch = NULL) const; + +protected: + virtual FName GetBeginSpawningFunction() const { return "BeginSpawning2"; } + virtual FName GetFinishSpawningFunction() const { return "FinishSpawning2"; } + virtual FString GetObjectName() const { return "Custom"; } + virtual UClass* GetActorClass() const { return AActor::StaticClass(); } + virtual bool SpawningFunctionHasTransform() const { return false; } + virtual void RegisterAdditionalPins(const class UEdGraphSchema_K2* K2Schema) {}; + virtual bool IsAdditionalPin(class UEdGraphPin* pin) { return false; }; + virtual void ConnectAdditionalPins(class FKismetCompilerContext& CompilerContext, UK2Node* last, UK2Node* dst) {}; + + void OnClassPinChanged(); + + FText NodeTooltip; + + FNodeTextCache m_cachedNodeTitle; +}; + +UCLASS() +class USpawnGroupNode : public UK2Node_SpawnActorBaseNode +{ + GENERATED_BODY() + + virtual FName GetBeginSpawningFunction() const { return "BeginSpawningGroup"; } + virtual FName GetFinishSpawningFunction() const { return "FinishSpawningGroup"; } + virtual FString GetObjectName() const { return "Group"; } + virtual UClass* GetActorClass() const; + virtual bool SpawningFunctionHasTransform() const { return false; } +}; +UCLASS() +class USpawnModifierNode : public UK2Node_SpawnActorBaseNode +{ + GENERATED_BODY() + + virtual FName GetBeginSpawningFunction() const { return "BeginSpawningModifier"; } + virtual FName GetFinishSpawningFunction() const { return "FinishSpawningModifier"; } + virtual FString GetObjectName() const { return "Modifier"; } + virtual UClass* GetActorClass() const; + virtual bool SpawningFunctionHasTransform() const { return false; } + + virtual void RegisterAdditionalPins(const class UEdGraphSchema_K2* K2Schema) override; + virtual bool IsAdditionalPin(class UEdGraphPin* pin) override; + virtual void ConnectAdditionalPins(class FKismetCompilerContext& CompilerContext, UK2Node* last, UK2Node* dst) override; +}; +UCLASS() +class USpawnTriggerNode : public UK2Node_SpawnActorBaseNode +{ + GENERATED_BODY() + + virtual FName GetBeginSpawningFunction() const { return "BeginSpawningTrigger"; } + virtual FName GetFinishSpawningFunction() const { return "FinishSpawningTrigger"; } + virtual FString GetObjectName() const { return "Trigger"; } + virtual UClass* GetActorClass() const; + virtual bool SpawningFunctionHasTransform() const { return true; } +}; + +UCLASS() +class USpawnProjectileNode : public UK2Node_SpawnActorBaseNode +{ + GENERATED_BODY() + + virtual FName GetBeginSpawningFunction() const { return "BeginSpawningProjectile"; } + virtual FName GetFinishSpawningFunction() const { return "FinishSpawningProjectile"; } + virtual FString GetObjectName() const { return "Projectile"; } + virtual UClass* GetActorClass() const; + virtual bool SpawningFunctionHasTransform() const { return true; } +}; \ No newline at end of file diff --git a/Plugins/FMODStudio/Content/AnimNotify_FMODPlayEvent.uasset b/Plugins/FMODStudio/Content/AnimNotify_FMODPlayEvent.uasset new file mode 100644 index 0000000..1bde713 --- /dev/null +++ b/Plugins/FMODStudio/Content/AnimNotify_FMODPlayEvent.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5c2640e344ea5943eb62950991b3140ae7158ed1e76f90c897391bbe4b327945 +size 213809 diff --git a/Plugins/FMODStudio/Docs/FMOD UE4 Integration.chm b/Plugins/FMODStudio/Docs/FMOD UE4 Integration.chm new file mode 100644 index 0000000..ec11896 Binary files /dev/null and b/Plugins/FMODStudio/Docs/FMOD UE4 Integration.chm differ diff --git a/Plugins/FMODStudio/Docs/Oculus Spatializer for FMOD Integration Guide.pdf b/Plugins/FMODStudio/Docs/Oculus Spatializer for FMOD Integration Guide.pdf new file mode 100644 index 0000000..f6ba890 Binary files /dev/null and b/Plugins/FMODStudio/Docs/Oculus Spatializer for FMOD Integration Guide.pdf differ diff --git a/Plugins/FMODStudio/FMODStudio.uplugin b/Plugins/FMODStudio/FMODStudio.uplugin new file mode 100644 index 0000000..e433964 --- /dev/null +++ b/Plugins/FMODStudio/FMODStudio.uplugin @@ -0,0 +1,29 @@ +{ + "FileVersion" : 3, + + "FriendlyName" : "FMOD Studio Integration", + "Version" : 10707, + "VersionName" : "1.07.07", + "CreatedBy" : "Firelight Technologies", + "CreatedByURL" : "http://fmod.com", + "EngineVersion" : "4.6.0", + "Description" : "FMOD Studio Integration.", + "Category" : "Audio", + "EnabledByDefault" : true, + + "Modules" : + [ + { + "Name" : "FMODStudio", + "Type" : "Runtime", + "LoadingPhase" : "PreDefault" + }, + { + "Name" : "FMODStudioEditor", + "Type" : "Editor" + } + ], + + "CanContainContent" : true, + "Installed" : true +} \ No newline at end of file diff --git a/Plugins/FMODStudio/Resources/Icon128.png b/Plugins/FMODStudio/Resources/Icon128.png new file mode 100644 index 0000000..fcec53d Binary files /dev/null and b/Plugins/FMODStudio/Resources/Icon128.png differ diff --git a/Plugins/FMODStudio/Source/FMODStudio/Classes/FMODAmbientSound.h b/Plugins/FMODStudio/Source/FMODStudio/Classes/FMODAmbientSound.h new file mode 100644 index 0000000..91c25bb --- /dev/null +++ b/Plugins/FMODStudio/Source/FMODStudio/Classes/FMODAmbientSound.h @@ -0,0 +1,26 @@ +// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2016. + +#pragma once + +#include "FMODAudioComponent.h" +#include "FMODAmbientSound.generated.h" + +/** FMOD Ambient Sound. +*/ +UCLASS(AutoExpandCategories=Audio, ClassGroup=Sounds, hidecategories(Collision, Input)) +class FMODSTUDIO_API AFMODAmbientSound : public AActor +{ + GENERATED_UCLASS_BODY() + + /** The Audio component for this actor */ + UPROPERTY(Category=Sound, VisibleAnywhere, BlueprintReadOnly,meta=(ExposeFunctionCategories="Sound")) + UFMODAudioComponent* AudioComponent; + + // Begin AActor interface. +#if WITH_EDITOR + virtual void CheckForErrors() override; + virtual bool GetReferencedContentObjects( TArray& Objects ) const override; +#endif + // End AActor interface. +}; + diff --git a/Plugins/FMODStudio/Source/FMODStudio/Classes/FMODAsset.h b/Plugins/FMODStudio/Source/FMODStudio/Classes/FMODAsset.h new file mode 100644 index 0000000..551c911 --- /dev/null +++ b/Plugins/FMODStudio/Source/FMODStudio/Classes/FMODAsset.h @@ -0,0 +1,37 @@ +// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2016. + +#pragma once + +#include "FMODAsset.generated.h" + +/* Purely for doxygen generation */ +#ifdef GENERATE_DOX + #define UCLASS(...) + #define UPROPERTY(...) public: +#endif + +/** + * FMOD Asset. + */ +UCLASS(BlueprintType) +class FMODSTUDIO_API UFMODAsset : public UObject +{ + GENERATED_UCLASS_BODY() + + /** The unique Guid, which matches the one exported from FMOD Studio */ + UPROPERTY() + FGuid AssetGuid; + + /** Whether to show in the content window */ + UPROPERTY() + bool bShowAsAsset; + + /** Force this to be an asset */ + virtual bool IsAsset() const override { return bShowAsAsset; } + + /** Get tags to show in content view */ + virtual void GetAssetRegistryTags(TArray& OutTags) const override; + +}; + + diff --git a/Plugins/FMODStudio/Source/FMODStudio/Classes/FMODAudioComponent.h b/Plugins/FMODStudio/Source/FMODStudio/Classes/FMODAudioComponent.h new file mode 100644 index 0000000..8bac974 --- /dev/null +++ b/Plugins/FMODStudio/Source/FMODStudio/Classes/FMODAudioComponent.h @@ -0,0 +1,218 @@ +// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2016. + +#pragma once + +#include "Map.h" +#include "Runtime/Launch/Resources/Version.h" +#include "FMODAudioComponent.generated.h" + +/** Used to store callback info from FMOD thread to our event */ +struct FTimelineMarkerProperties +{ + FString Name; + int32 Position; +}; + +/** Used to store callback info from FMOD thread to our event */ +struct FTimelineBeatProperties +{ + int32 Bar; + int32 Beat; + int32 Position; + float Tempo; + int32 TimeSignatureUpper; + int32 TimeSignatureLower; +}; + +/** called when an event stops, either because it played to completion or because a Stop() call turned it off early */ +DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnEventStopped); +/** called when we reach a named marker on the timeline */ +DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnTimelineMarker, FString, Name, int32, Position); +/** called when we reach a beat on the timeline */ +DECLARE_DYNAMIC_MULTICAST_DELEGATE_SixParams(FOnTimelineBeat, int32, Bar, int32, Beat, int32, Position, float, Tempo, int32, TimeSignatureUpper, int32, TimeSignatureLower); + +namespace FMOD +{ + class Sound; + + namespace Studio + { + class EventDescription; + class EventInstance; + } +} + +struct FMOD_STUDIO_TIMELINE_MARKER_PROPERTIES; +struct FMOD_STUDIO_TIMELINE_BEAT_PROPERTIES; + + +/* Purely for doxygen generation */ +#ifdef GENERATE_DOX + #define UCLASS(...) + #define UPROPERTY(...) public: +#endif + +/** + * Plays FMOD Studio events. + */ +UCLASS(ClassGroup = (Audio, Common), hidecategories = (Object, ActorComponent, Physics, Rendering, Mobility, LOD), ShowCategories = Trigger, meta = (BlueprintSpawnableComponent)) +class FMODSTUDIO_API UFMODAudioComponent : public USceneComponent +{ + GENERATED_UCLASS_BODY() + + /** The event asset to use for this sound */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Sound) + TAssetPtr Event; + + /** Stored parameters to apply next time we create an instance */ + TMap StoredParameters; + + /** Enable timeline callbacks for this sound, so that OnTimelineMarker and OnTimelineBeat can be used */ + UPROPERTY(EditAnywhere, Category=Callbacks) + uint32 bEnableTimelineCallbacks:1; + + /** Auto destroy this component on completion */ + UPROPERTY() + uint32 bAutoDestroy:1; + + /** Stop sound when owner is destroyed */ + UPROPERTY() + uint32 bStopWhenOwnerDestroyed:1; + + /** called when an event stops, either because it played to completion or because a Stop() call turned it off early */ + UPROPERTY(BlueprintAssignable) + FOnEventStopped OnEventStopped; + + /** called when we reach a named marker (if bEnableTimelineCallbacks is true) */ + UPROPERTY(BlueprintAssignable) + FOnTimelineMarker OnTimelineMarker; + + /** called when we reach a beat of a tempo (if bEnableTimelineCallbacks is true) */ + UPROPERTY(BlueprintAssignable) + FOnTimelineBeat OnTimelineBeat; + + UFUNCTION(BlueprintCallable, Category="Audio|FMOD|Components") + void SetEvent(UFMODEvent* NewEvent); + + /** Start a sound playing on an audio component */ + UFUNCTION(BlueprintCallable, Category="Audio|FMOD|Components") + void Play(); + + /** Stop an audio component playing its sound cue, issue any delegates if needed */ + UFUNCTION(BlueprintCallable, Category="Audio|FMOD|Components") + void Stop(); + + /** Trigger a cue in an event */ + UFUNCTION(BlueprintCallable, Category="Audio|FMOD|Components") + void TriggerCue(); + + /** @return true if this component is currently playing an event */ + UFUNCTION(BlueprintCallable, Category="Audio|FMOD|Components") + bool IsPlaying(); + + /** Set volume on an audio component */ + UFUNCTION(BlueprintCallable, Category="Audio|FMOD|Components") + void SetVolume(float volume); + + /** Set pitch on an audio component */ + UFUNCTION(BlueprintCallable, Category="Audio|FMOD|Components") + void SetPitch(float pitch); + + /** Pause/Unpause an audio component */ + UFUNCTION(BlueprintCallable, Category="Audio|FMOD|Components") + void SetPaused(bool paused); + + /** Set a parameter into the event */ + UFUNCTION(BlueprintCallable, Category="Audio|FMOD|Components") + void SetParameter(FName Name, float Value); + + /** Set a parameter into the event */ + UFUNCTION(BlueprintCallable, Category="Audio|FMOD|Components") + float GetParameter(FName Name); + + /** Set the timeline position in milliseconds */ + UFUNCTION(BlueprintCallable, Category="Audio|FMOD|Components") + void SetTimelinePosition(int32 Time); + + /** Get the timeline position in milliseconds */ + UFUNCTION(BlueprintCallable, Category="Audio|FMOD|Components") + int32 GetTimelinePosition(); + + /** Called when the event has finished stopping */ + void OnPlaybackCompleted(); + + /** Update gain and low-pass based on interior volumes */ + void UpdateInteriorVolumes(); + + /** Whether we apply gain and low-pass based on audio zones. */ + uint32 bApplyAmbientVolumes:1; + + /** Sound name used for programmer sound. Will look up the name in any loaded audio table. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Sound) + FString ProgrammerSoundName; + + /** Set the sound name to use for programmer sound. Will look up the name in any loaded audio table. */ + UFUNCTION(BlueprintCallable, Category="Audio|FMOD|Components") + void SetProgrammerSoundName(FString Value); + + /** Set a programmer sound to use for this audio component. Lifetime of sound must exceed that of the audio component. */ + void SetProgrammerSound(FMOD::Sound* Sound); + +public: + + /** Actual Studio instance handle */ + FMOD::Studio::EventInstance* StudioInstance; + + void EventCallbackAddMarker(struct FMOD_STUDIO_TIMELINE_MARKER_PROPERTIES* props); + void EventCallbackAddBeat(struct FMOD_STUDIO_TIMELINE_BEAT_PROPERTIES* props); + void EventCallbackCreateProgrammerSound(struct FMOD_STUDIO_PROGRAMMER_SOUND_PROPERTIES* props); + void EventCallbackDestroyProgrammerSound(struct FMOD_STUDIO_PROGRAMMER_SOUND_PROPERTIES* props); + + // Begin UObject interface. +#if WITH_EDITOR + virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; +#endif // WITH_EDITOR + virtual void PostLoad() override; + virtual FString GetDetailedInfoInternal() const override; + // End UObject interface. + // Begin USceneComponent Interface + virtual void Activate(bool bReset=false) override; + virtual void Deactivate() override; +#if ENGINE_MINOR_VERSION >= 9 + virtual void OnUpdateTransform(bool bSkipPhysicsMove, ETeleportType Teleport = ETeleportType::None) override; +#else + virtual void OnUpdateTransform(bool bSkipPhysicsMove) override; +#endif + // End USceneComponent Interface + +private: + + // Begin ActorComponent interface. +#if WITH_EDITORONLY_DATA + virtual void OnRegister() override; +#endif + virtual void OnUnregister() override; + virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override; + // End ActorComponent interface. + +#if WITH_EDITORONLY_DATA + void UpdateSpriteTexture(); +#endif + + // Settings for ambient volume effects + double InteriorLastUpdateTime; + float SourceInteriorVolume; + float SourceInteriorLPF; + float CurrentInteriorVolume; + float CurrentInteriorLPF; + + // Tempo and marker callbacks + FCriticalSection CallbackLock; + TArray CallbackMarkerQueue; + TArray CallbackBeatQueue; + + // Direct assignment of programmer sound from other C++ code + FMOD::Sound* ProgrammerSound; +}; + + diff --git a/Plugins/FMODStudio/Source/FMODStudio/Classes/FMODBank.h b/Plugins/FMODStudio/Source/FMODStudio/Classes/FMODBank.h new file mode 100644 index 0000000..a5ee262 --- /dev/null +++ b/Plugins/FMODStudio/Source/FMODStudio/Classes/FMODBank.h @@ -0,0 +1,30 @@ +// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2016. + +#pragma once + +#include "FMODAsset.h" +#include "FMODBank.generated.h" + +/* Purely for doxygen generation */ +#ifdef GENERATE_DOX + #define UCLASS(...) + #define UPROPERTY(...) public: +#endif + +/** + * FMOD Bank Asset. + */ +UCLASS() +class FMODSTUDIO_API UFMODBank : public UFMODAsset +{ + GENERATED_UCLASS_BODY() + + /** Get tags to show in content view */ + virtual void GetAssetRegistryTags(TArray& OutTags) const override; + + /** Descriptive name */ + virtual FString GetDesc() override; + +}; + + diff --git a/Plugins/FMODStudio/Source/FMODStudio/Classes/FMODBlueprintStatics.h b/Plugins/FMODStudio/Source/FMODStudio/Classes/FMODBlueprintStatics.h new file mode 100644 index 0000000..f625773 --- /dev/null +++ b/Plugins/FMODStudio/Source/FMODStudio/Classes/FMODBlueprintStatics.h @@ -0,0 +1,208 @@ +// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2016. + +#pragma once + +#include "UnrealString.h" +#include "FMODBlueprintStatics.generated.h" + +class UFMODAudioComponent; + +namespace FMOD +{ + namespace Studio + { + class EventDescription; + class EventInstance; + } +} + +class UFMODAsset; +class UFMODEvent; +class USceneComponent; + +USTRUCT() +struct FFMODEventInstance +{ + GENERATED_USTRUCT_BODY() + + FMOD::Studio::EventInstance* Instance; +}; + + +UCLASS() +class FMODSTUDIO_API UFMODBlueprintStatics : public UBlueprintFunctionLibrary +{ + GENERATED_UCLASS_BODY() + + /** Plays an event. This returns an FMOD Event Instance. The sound does not travel with any actor. + * @param Event - event to play + * @param bAutoPlay - Start the event automatically. + */ + UFUNCTION(BlueprintCallable, Category="Audio|FMOD", meta=(HidePin="WorldContextObject", DefaultToSelf="WorldContextObject", AdvancedDisplay = "2", bAutoPlay = "true", UnsafeDuringActorConstruction = "true")) + static FFMODEventInstance PlayEvent2D(UObject* WorldContextObject, UFMODEvent* Event, bool bAutoPlay); + + /** Plays an event at the given location. This returns an FMOD Event Instance. The sound does not travel with any actor. + * @param Event - event to play + * @param Location - World position to play event at + * @param bAutoPlay - Start the event automatically. + */ + UFUNCTION(BlueprintCallable, Category="Audio|FMOD", meta=(HidePin="WorldContextObject", DefaultToSelf="WorldContextObject", AdvancedDisplay = "2", bAutoPlay = "true", UnsafeDuringActorConstruction = "true")) + static FFMODEventInstance PlayEventAtLocation(UObject* WorldContextObject, UFMODEvent* Event, const FTransform& Location, bool bAutoPlay); + + /** Plays an event attached to and following the specified component. + * @param Event - event to play + * @param AttachComponent - Component to attach to. + * @param AttachPointName - Optional named point within the AttachComponent to play the sound at + * @param Location - Depending on the value of Location Type this is either a relative offset from the attach component/point or an absolute world position that will be translated to a relative offset + * @param LocationType - Specifies whether Location is a relative offset or an absolute world position + * @param bStopWhenAttachedToDestroyed - Specifies whether the sound should stop playing when the owner of the attach to component is destroyed. + * @param bAutoPlay - Start the event automatically. + */ + UFUNCTION(BlueprintCallable, Category="Audio|FMOD", meta=(AdvancedDisplay = "2", UnsafeDuringActorConstruction = "true", bAutoPlay = "true")) + static class UFMODAudioComponent* PlayEventAttached(UFMODEvent* Event, USceneComponent* AttachToComponent, FName AttachPointName, FVector Location, EAttachLocation::Type LocationType, bool bStopWhenAttachedToDestroyed, bool bAutoPlay); + + /** Find an asset by name. + * @param EventName - The asset name + */ + UFUNCTION(BlueprintCallable, Category="Audio|FMOD") + static UFMODAsset* FindAssetByName(const FString& Name); + + /** Find an event by name. + * @param EventName - The event name + */ + UFUNCTION(BlueprintCallable, Category="Audio|FMOD") + static UFMODEvent* FindEventByName(const FString& Name); + + /** Loads a bank. + * @param Bank - bank to load + * @param bBlocking - determines whether the bank will load synchronously + * @param bLoadSampleData - determines whether sample data will be preloaded immediately + */ + UFUNCTION(BlueprintCallable, Category = "Audio|FMOD", meta = (UnsafeDuringActorConstruction = "true")) + static void LoadBank(class UFMODBank* Bank, bool bBlocking, bool bLoadSampleData); + + /** Unloads a bank. + * @param Bank - bank to unload + */ + UFUNCTION(BlueprintCallable, Category = "Audio|FMOD", meta = (UnsafeDuringActorConstruction = "true")) + static void UnloadBank(class UFMODBank* Bank); + + /** Load bank sample data. + * @param Bank - bank to load sample data from + */ + UFUNCTION(BlueprintCallable, Category = "Audio|FMOD", meta = (UnsafeDuringActorConstruction = "true")) + static void LoadBankSampleData(class UFMODBank* Bank); + + /** Unload bank sample data. + * @param Bank - bank to unload sample data from + */ + UFUNCTION(BlueprintCallable, Category = "Audio|FMOD", meta = (UnsafeDuringActorConstruction = "true")) + static void UnloadBankSampleData(class UFMODBank* Bank); + + /** Load event sample data. This can be done ahead of time to avoid loading stalls. + * @param Event - event to load sample data from. + */ + UFUNCTION(BlueprintCallable, Category="Audio|FMOD", meta=(HidePin="WorldContextObject", DefaultToSelf="WorldContextObject", UnsafeDuringActorConstruction = "true")) + static void LoadEventSampleData(UObject* WorldContextObject, UFMODEvent* Event); + + /** Unload event sample data. + * @param Event - event to load sample data from. + */ + UFUNCTION(BlueprintCallable, Category="Audio|FMOD", meta=(HidePin="WorldContextObject", DefaultToSelf="WorldContextObject", UnsafeDuringActorConstruction = "true")) + static void UnloadEventSampleData(UObject* WorldContextObject, UFMODEvent* Event); + + /** Return a list of all event instances that are playing for this event. + Be careful using this function because it is possible to find and alter any playing sound, even ones owned by other audio components. + * @param Event - event to find instances from. + */ + UFUNCTION(BlueprintCallable, Category="Audio|FMOD", meta=(HidePin="WorldContextObject", DefaultToSelf="WorldContextObject", UnsafeDuringActorConstruction = "true")) + static TArray FindEventInstances(UObject* WorldContextObject, UFMODEvent* Event); + + /** Set fader level on a bus + * @param Bus - bus to use + * @param Level - fader level + */ + UFUNCTION(BlueprintCallable, Category = "Audio|FMOD|Bus", meta = (UnsafeDuringActorConstruction = "true")) + static void BusSetFaderLevel(class UFMODBus* Bus, float Level); + + /** Pause/Unpause all events going through this bus + * @param Bus - bus to use + * @param bPaused - paused + */ + UFUNCTION(BlueprintCallable, Category = "Audio|FMOD|Bus", meta = (UnsafeDuringActorConstruction = "true")) + static void BusSetPaused(class UFMODBus* Bus, bool bPaused); + + /** Mute/Unmute this bus + * @param Bus - bus to use + * @param bMute - mute + */ + UFUNCTION(BlueprintCallable, Category = "Audio|FMOD|Bus", meta = (UnsafeDuringActorConstruction = "true")) + static void BusSetMute(class UFMODBus* Bus, bool bMute); + + /** Returns whether this FMOD Event Instance is valid. The instance will be invalidated when the sound stops. + * @param EventInstance - Event instance + */ + UFUNCTION(BlueprintCallable, Category="Audio|FMOD|EventInstance", meta = (UnsafeDuringActorConstruction = "true")) + static bool EventInstanceIsValid(FFMODEventInstance EventInstance); + + /** Set volume on an FMOD Event Instance. + * @param EventInstance - Event instance + * @param Value - Volume to set + */ + UFUNCTION(BlueprintCallable, Category="Audio|FMOD|EventInstance", meta = (UnsafeDuringActorConstruction = "true")) + static void EventInstanceSetVolume(FFMODEventInstance EventInstance, float Volume); + + /** Set pitch on an FMOD Event Instance. + * @param EventInstance - Event instance + * @param Value - Volume to set + */ + UFUNCTION(BlueprintCallable, Category="Audio|FMOD|EventInstance", meta = (UnsafeDuringActorConstruction = "true")) + static void EventInstanceSetPitch(FFMODEventInstance EventInstance, float Pitch); + + /** Pause/Unpause an FMOD Event Instance. + * @param EventInstance - Event instance + * @param Paused - Whether to pause or unpause + */ + UFUNCTION(BlueprintCallable, Category="Audio|FMOD|EventInstance", meta = (UnsafeDuringActorConstruction = "true")) + static void EventInstanceSetPaused(FFMODEventInstance EventInstance, bool Paused); + + /** Set a parameter on an FMOD Event Instance. + * @param EventInstance - Event instance + * @param Name - Name of parameter + * @param Value - Value of parameter + */ + UFUNCTION(BlueprintCallable, Category="Audio|FMOD|EventInstance", meta = (UnsafeDuringActorConstruction = "true")) + static void EventInstanceSetParameter(FFMODEventInstance EventInstance, FName Name, float Value); + + /** Get a parameter on an FMOD Event Instance. + * @param EventInstance - Event instance + * @param Name - Name of parameter + */ + UFUNCTION(BlueprintCallable, Category="Audio|FMOD|EventInstance", meta = (UnsafeDuringActorConstruction = "true")) + static float EventInstanceGetParameter(FFMODEventInstance EventInstance, FName Name); + + /** Plays a FMOD Event Instance. + * @param EventInstance - Event instance + */ + UFUNCTION(BlueprintCallable, Category="Audio|FMOD|EventInstance", meta = (UnsafeDuringActorConstruction = "true")) + static void EventInstancePlay(FFMODEventInstance EventInstance); + + /** Stops a FMOD Event Instance. + * @param EventInstance - Event instance + */ + UFUNCTION(BlueprintCallable, Category="Audio|FMOD|EventInstance", meta = (UnsafeDuringActorConstruction = "true")) + static void EventInstanceStop(FFMODEventInstance EventInstance); + + /** Trigger a cue on an FMOD Event Instance. + * @param EventInstance - Event instance + */ + UFUNCTION(BlueprintCallable, Category="Audio|FMOD|EventInstance", meta = (UnsafeDuringActorConstruction = "true")) + static void EventInstanceTriggerCue(FFMODEventInstance EventInstance); + + /** Set transform on an FMOD Event Instance. + * @param EventInstance - Event instance + * @param Location - Location to place event + */ + UFUNCTION(BlueprintCallable, Category="Audio|FMOD|EventInstance", meta = (UnsafeDuringActorConstruction = "true")) + static void EventInstanceSetTransform(FFMODEventInstance EventInstance, const FTransform& Location); +}; diff --git a/Plugins/FMODStudio/Source/FMODStudio/Classes/FMODBus.h b/Plugins/FMODStudio/Source/FMODStudio/Classes/FMODBus.h new file mode 100644 index 0000000..57139ea --- /dev/null +++ b/Plugins/FMODStudio/Source/FMODStudio/Classes/FMODBus.h @@ -0,0 +1,30 @@ +// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2016. + +#pragma once + +#include "FMODAsset.h" +#include "FMODBus.generated.h" + +/* Purely for doxygen generation */ +#ifdef GENERATE_DOX + #define UCLASS(...) + #define UPROPERTY(...) public: +#endif + +/** + * FMOD Bus Asset. + */ +UCLASS() +class FMODSTUDIO_API UFMODBus : public UFMODAsset +{ + GENERATED_UCLASS_BODY() + + /** Get tags to show in content view */ + virtual void GetAssetRegistryTags(TArray& OutTags) const override; + + /** Descriptive name */ + virtual FString GetDesc() override; + +}; + + diff --git a/Plugins/FMODStudio/Source/FMODStudio/Classes/FMODEvent.h b/Plugins/FMODStudio/Source/FMODStudio/Classes/FMODEvent.h new file mode 100644 index 0000000..157c653 --- /dev/null +++ b/Plugins/FMODStudio/Source/FMODStudio/Classes/FMODEvent.h @@ -0,0 +1,30 @@ +// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2016. + +#pragma once + +#include "FMODAsset.h" +#include "FMODEvent.generated.h" + +/* Purely for doxygen generation */ +#ifdef GENERATE_DOX + #define UCLASS(...) + #define UPROPERTY(...) public: +#endif + +/** + * FMOD Event Asset. + */ +UCLASS() +class FMODSTUDIO_API UFMODEvent : public UFMODAsset +{ + GENERATED_UCLASS_BODY() + + /** Get tags to show in content view */ + virtual void GetAssetRegistryTags(TArray& OutTags) const override; + + /** Descriptive name */ + virtual FString GetDesc() override; + +}; + + diff --git a/Plugins/FMODStudio/Source/FMODStudio/Classes/FMODSettings.h b/Plugins/FMODStudio/Source/FMODStudio/Classes/FMODSettings.h new file mode 100644 index 0000000..6b91c58 --- /dev/null +++ b/Plugins/FMODStudio/Source/FMODStudio/Classes/FMODSettings.h @@ -0,0 +1,157 @@ +// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2016. + +#pragma once + +#include "FMODSettings.generated.h" + +UENUM() +namespace EFMODSpeakerMode +{ + enum Type + { + // The speakers are stereo + Stereo, + // 5.1 speaker setup + Surround_5_1, + // 7.1 speaker setup + Surround_7_1 + }; +} + + +UCLASS(config = Engine, defaultconfig) +class FMODSTUDIO_API UFMODSettings : public UObject +{ + GENERATED_UCLASS_BODY() + +public: + + /** + * Whether to load all banks at startup. + */ + UPROPERTY(config, EditAnywhere, Category = Basic) + bool bLoadAllBanks; + + /** + * Whether to load all bank sample data into memory at startup. + */ + UPROPERTY(config, EditAnywhere, Category = Basic) + bool bLoadAllSampleData; + + /** + * Enable live update in non-final builds. + */ + UPROPERTY(config, EditAnywhere, Category = Basic) + bool bEnableLiveUpdate; + + /** + * Path to find your studio bank output directory, relative to Content directory. + */ + UPROPERTY(config, EditAnywhere, Category = Basic, meta=(RelativeToGameContentDir)) + FDirectoryPath BankOutputDirectory; + + /** Project Output Format, should match the mode set up for the Studio project. */ + UPROPERTY(config, EditAnywhere, Category = Basic) + TEnumAsByte OutputFormat; + + /** + * Whether to enable vol0virtual, which means voices with low volume will automatically go virtual to save CPU. + */ + UPROPERTY(config, EditAnywhere, Category = InitSettings) + bool bVol0Virtual; + + /** + * If vol0virtual is enabled, the signal level at which to make channels virtual. + */ + UPROPERTY(config, EditAnywhere, Category = InitSettings) + float Vol0VirtualLevel; + + /** + * Sample rate to use, or 0 to match system rate. + */ + UPROPERTY(config, EditAnywhere, Category = InitSettings) + int32 SampleRate; + + /** + * Number of actual software voices that can be used at once. + */ + UPROPERTY(config, EditAnywhere, Category = InitSettings) + int32 RealChannelCount; + + /** + * Total number of voices available that can be either real or virtual. + */ + UPROPERTY(config, EditAnywhere, Category = InitSettings) + int32 TotalChannelCount; + + /** + * DSP mixer buffer length, or 0 for system default. + */ + UPROPERTY(config, EditAnywhere, Category = InitSettings) + int32 DSPBufferLength; + + /** + * DSP mixer buffer count, or 0 for system default. + */ + UPROPERTY(config, EditAnywhere, Category = InitSettings) + int32 DSPBufferCount; + + /** + * Studio update period in milliseconds, or 0 for default (which means 20ms). + */ + UPROPERTY(config, EditAnywhere, Category = InitSettings) + int32 StudioUpdatePeriod; + + /** + * Live update port to use, or 0 for default. + */ + UPROPERTY(config, EditAnywhere, Category = Advanced) + int32 LiveUpdatePort; + + /** + * Extra plugin files to load. + * The plugin files should sit alongside the FMOD dynamic libraries in the ThirdParty directory. + */ + UPROPERTY(config, EditAnywhere, Category = Advanced) + TArray PluginFiles; + + /** + * Directory for content to appear in content window. Be careful changing this! + */ + UPROPERTY(config, EditAnywhere, Category = Advanced) + FString ContentBrowserPrefix; + + /** + * Force platform directory name, or leave empty for automatic (Desktop/Mobile/PS4/XBoxOne) + */ + UPROPERTY(config, EditAnywhere, Category = Advanced) + FString ForcePlatformName; + + /** + * Name of master bank. The default in Studio is "Master Bank". + */ + UPROPERTY(config, EditAnywhere, Category = Advanced) + FString MasterBankName; + + /** + * Skip bank files of the given name. + * Can be used to load all banks except for a certain set, such as localization banks. + */ + UPROPERTY(config, EditAnywhere, Category = Advanced) + FString SkipLoadBankName; + + /** Is the bank path set up . */ + bool IsBankPathSet() const { return !BankOutputDirectory.Path.IsEmpty(); } + + /** Get the full bank path. Uses the game's content directory as a base. */ + FString GetFullBankPath() const; + + /** Get the master bank path. */ + FString GetMasterBankPath() const; + + /** Get the master strings bank path. */ + FString GetMasterStringsBankPath() const; + + /** Get all banks in our bank directory excluding the master and strings bank. */ + void GetAllBankPaths(TArray& Paths, bool IncludeMasterBank=false) const; +}; diff --git a/Plugins/FMODStudio/Source/FMODStudio/Classes/FMODSnapshot.h b/Plugins/FMODStudio/Source/FMODStudio/Classes/FMODSnapshot.h new file mode 100644 index 0000000..e236ef5 --- /dev/null +++ b/Plugins/FMODStudio/Source/FMODStudio/Classes/FMODSnapshot.h @@ -0,0 +1,27 @@ +// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2016. + +#pragma once + +#include "FMODEvent.h" +#include "FMODSnapshot.generated.h" + +/* Purely for doxygen generation */ +#ifdef GENERATE_DOX + #define UCLASS(...) + #define UPROPERTY(...) public: +#endif + +/** + * FMOD Snapshot Asset. + */ +UCLASS() +class FMODSTUDIO_API UFMODSnapshot : public UFMODEvent +{ + GENERATED_UCLASS_BODY() + + /** Descriptive name */ + virtual FString GetDesc() override; + +}; + + diff --git a/Plugins/FMODStudio/Source/FMODStudio/Classes/FMODSnapshotReverb.h b/Plugins/FMODStudio/Source/FMODStudio/Classes/FMODSnapshotReverb.h new file mode 100644 index 0000000..7636f6e --- /dev/null +++ b/Plugins/FMODStudio/Source/FMODStudio/Classes/FMODSnapshotReverb.h @@ -0,0 +1,34 @@ +// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2016. + +#pragma once + +#include "Sound/ReverbEffect.h" +#include "FMODSnapshotReverb.generated.h" + +/* Purely for doxygen generation */ +#ifdef GENERATE_DOX + #define UCLASS(...) + #define UPROPERTY(...) public: +#endif + +/** + * FMOD Event Asset. + */ +UCLASS() +class FMODSTUDIO_API UFMODSnapshotReverb : public UReverbEffect +{ + GENERATED_UCLASS_BODY() + + /** The unique Guid, which matches the one exported from FMOD Studio */ + UPROPERTY() + FGuid AssetGuid; + + /** Whether to show in the content window */ + UPROPERTY() + bool bShowAsAsset; + + /** Force this to be an asset */ + virtual bool IsAsset() const override { return bShowAsAsset; } +}; + + diff --git a/Plugins/FMODStudio/Source/FMODStudio/Classes/FMODVCA.h b/Plugins/FMODStudio/Source/FMODStudio/Classes/FMODVCA.h new file mode 100644 index 0000000..c767a15 --- /dev/null +++ b/Plugins/FMODStudio/Source/FMODStudio/Classes/FMODVCA.h @@ -0,0 +1,30 @@ +// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2016. + +#pragma once + +#include "FMODAsset.h" +#include "FMODVCA.generated.h" + +/* Purely for doxygen generation */ +#ifdef GENERATE_DOX + #define UCLASS(...) + #define UPROPERTY(...) public: +#endif + +/** + * FMOD VCA Asset. + */ +UCLASS() +class FMODSTUDIO_API UFMODVCA : public UFMODAsset +{ + GENERATED_UCLASS_BODY() + + /** Get tags to show in content view */ + virtual void GetAssetRegistryTags(TArray& OutTags) const override; + + /** Descriptive name */ + virtual FString GetDesc() override; + +}; + + diff --git a/Plugins/FMODStudio/Source/FMODStudio/FMODStudio.Build.cs b/Plugins/FMODStudio/Source/FMODStudio/FMODStudio.Build.cs new file mode 100644 index 0000000..d6e7cef --- /dev/null +++ b/Plugins/FMODStudio/Source/FMODStudio/FMODStudio.Build.cs @@ -0,0 +1,237 @@ +// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2016. + +namespace UnrealBuildTool.Rules +{ + public class FMODStudio : ModuleRules + { + public FMODStudio(TargetInfo Target) + { + bFasterWithoutUnity = true; + + PublicIncludePaths.AddRange( + new string[] { + } + ); + + PrivateIncludePaths.AddRange( + new string[] { + "FMODStudio/Private", + "FMODStudio/Public/FMOD", + "FMODStudioOculus/Public", + } + ); + + PublicDependencyModuleNames.AddRange( + new string[] + { + "Core", + "CoreUObject", + "Engine", + "Projects" + } + ); + + PrivateDependencyModuleNames.AddRange( + new string[] + { + } + ); + + if (UEBuildConfiguration.bBuildEditor == true) + { + PrivateDependencyModuleNames.Add("AssetRegistry"); + PrivateDependencyModuleNames.Add("UnrealEd"); + } + + DynamicallyLoadedModuleNames.AddRange( + new string[] + { + } + ); + + string configName = ""; + + if (Target.Configuration != UnrealTargetConfiguration.Shipping) + { + configName = "L"; + Definitions.Add("FMODSTUDIO_LINK_LOGGING=1"); + } + else + { + configName = ""; + Definitions.Add("FMODSTUDIO_LINK_RELEASE=1"); + } + + string platformName = Target.Platform.ToString(); + + string platformMidName = ""; + string linkExtension = ""; + string dllExtension = ""; + string libPrefix = ""; + + // ModuleDirectory points to FMODStudio\source\FMODStudio, need to get back to binaries directory for our libs + string BasePath = System.IO.Path.Combine(ModuleDirectory, "../../Binaries", platformName); + + string copyThirdPartyPath = ""; + bool bDynamicLibraries = true; + + switch (Target.Platform) + { + case UnrealTargetPlatform.Win32: + linkExtension = "_vc.lib"; + dllExtension = ".dll"; + break; + case UnrealTargetPlatform.Win64: + platformMidName = "64"; + linkExtension = "_vc.lib"; + dllExtension = ".dll"; + break; + case UnrealTargetPlatform.Mac: + linkExtension = dllExtension = ".dylib"; + libPrefix = "lib"; + break; + case UnrealTargetPlatform.XboxOne: + linkExtension = "_vc.lib"; + dllExtension = ".dll"; + copyThirdPartyPath = "../XBoxOne"; // XBoxOne still doesn't seem to support plugins with .dlls + break; + case UnrealTargetPlatform.PS4: + linkExtension = "_stub.a"; + dllExtension = ".prx"; + libPrefix = "lib"; + break; + case UnrealTargetPlatform.Android: + linkExtension = dllExtension = ".so"; + libPrefix = "lib"; + WriteAndroidDeploy(System.IO.Path.Combine(BasePath, "deploy.txt"), configName); + break; + case UnrealTargetPlatform.IOS: + linkExtension = "_iphoneos.a"; + libPrefix = "lib"; + bDynamicLibraries = false; + break; + case UnrealTargetPlatform.Linux: + BasePath = System.IO.Path.Combine(BasePath, "x86_64"); + linkExtension = ".so"; + dllExtension = ".so"; + libPrefix = "lib"; + break; + case UnrealTargetPlatform.WinRT: + case UnrealTargetPlatform.WinRT_ARM: + case UnrealTargetPlatform.HTML5: + //extName = ".a"; + throw new System.Exception(System.String.Format("Unsupported platform {0}", Target.Platform.ToString())); + //break; + } + + //System.Console.WriteLine("FMOD Current path: " + System.IO.Path.GetFullPath(".")); + //System.Console.WriteLine("FMOD Base path: " + BasePath); + + PublicLibraryPaths.Add(BasePath); + + string fmodLibName = System.String.Format("{0}fmod{1}{2}{3}", libPrefix, configName, platformMidName, linkExtension); + string fmodStudioLibName = System.String.Format("{0}fmodstudio{1}{2}{3}", libPrefix, configName, platformMidName, linkExtension); + + string fmodDllName = System.String.Format("{0}fmod{1}{2}{3}", libPrefix, configName, platformMidName, dllExtension); + string fmodStudioDllName = System.String.Format("{0}fmodstudio{1}{2}{3}", libPrefix, configName, platformMidName, dllExtension); + + string fmodLibPath = System.IO.Path.Combine(BasePath, fmodLibName); + string fmodStudioLibPath = System.IO.Path.Combine(BasePath, fmodStudioLibName); + + string fmodDllPath = System.IO.Path.Combine(BasePath, fmodDllName); + string fmodStudioDllPath = System.IO.Path.Combine(BasePath, fmodStudioDllName); + + System.Collections.Generic.List plugins = GetPlugins(BasePath); + + PublicAdditionalLibraries.Add(fmodLibPath); + PublicAdditionalLibraries.Add(fmodStudioLibPath); + if (bDynamicLibraries) + { + RuntimeDependencies.Add(new RuntimeDependency(fmodDllPath)); + RuntimeDependencies.Add(new RuntimeDependency(fmodStudioDllPath)); + foreach (string plugin in plugins) + { + string pluginPath = System.IO.Path.Combine(BasePath, plugin + dllExtension); + System.Console.WriteLine("Adding reference to FMOD plugin: " + pluginPath); + RuntimeDependencies.Add(new RuntimeDependency(pluginPath)); + } + } + + if (copyThirdPartyPath.Length != 0) + { + string destPath = System.IO.Path.Combine(UEBuildConfiguration.UEThirdPartyBinariesDirectory, copyThirdPartyPath); + System.IO.Directory.CreateDirectory(destPath); + + string fmodDllDest = System.IO.Path.Combine(destPath, fmodDllName); + string fmodStudioDllDest = System.IO.Path.Combine(destPath, fmodStudioDllName); + + CopyFile(fmodDllPath, fmodDllDest); + CopyFile(fmodStudioDllPath, fmodStudioDllDest); + } + + + if (Target.Platform == UnrealTargetPlatform.Win32 || Target.Platform == UnrealTargetPlatform.Win64 || Target.Platform == UnrealTargetPlatform.XboxOne) + { + PublicDelayLoadDLLs.AddRange( + new string[] { + fmodDllName, + fmodStudioDllName + } + ); + } + } + + private void CopyFile(string source, string dest) + { + //System.Console.WriteLine("Copying {0} to {1}", source, dest); + if (System.IO.File.Exists(dest)) + { + System.IO.File.SetAttributes(dest, System.IO.File.GetAttributes(dest) & ~System.IO.FileAttributes.ReadOnly); + } + try + { + System.IO.File.Copy(source, dest, true); + } + catch (System.Exception ex) + { + System.Console.WriteLine("Failed to copy file: {0}", ex.Message); + } + } + + private void WriteAndroidDeploy(string fileName, string configLetter) + { + string[] contents = new string[] + { + "fmod.jar", + System.String.Format("libfmod{0}.so", configLetter), + System.String.Format("libfmodstudio{0}.so", configLetter) + }; + System.IO.File.WriteAllLines(fileName, contents); + } + + private System.Collections.Generic.List GetPlugins(string BasePath) + { + System.Collections.Generic.List AllPlugins = new System.Collections.Generic.List(); + string PluginListName = System.IO.Path.Combine(BasePath, "plugins.txt"); + if (System.IO.File.Exists(PluginListName)) + { + try + { + foreach (string FullEntry in System.IO.File.ReadAllLines(PluginListName)) + { + string Entry = FullEntry.Trim(); + if (Entry.Length > 0) + { + AllPlugins.Add(Entry); + } + } + } + catch (System.Exception ex) + { + System.Console.WriteLine("Failed to read plugin list file: {0}", ex.Message); + } + } + return AllPlugins; + } + } +} \ No newline at end of file diff --git a/Plugins/FMODStudio/Source/FMODStudio/Private/FMODAmbientSound.cpp b/Plugins/FMODStudio/Source/FMODStudio/Private/FMODAmbientSound.cpp new file mode 100644 index 0000000..a76f4e9 --- /dev/null +++ b/Plugins/FMODStudio/Source/FMODStudio/Private/FMODAmbientSound.cpp @@ -0,0 +1,65 @@ +// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2016. + +#include "FMODStudioPrivatePCH.h" +#include "FMODAmbientSound.h" +#include "FMODEvent.h" +#include "MessageLog.h" +#include "UObjectToken.h" +#include "MapErrors.h" + +#define LOCTEXT_NAMESPACE "FMODAmbientSound" + +AFMODAmbientSound::AFMODAmbientSound(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + AudioComponent = ObjectInitializer.CreateDefaultSubobject(this, TEXT("FMODAudioComponent0")); + + AudioComponent->bAutoActivate = true; + AudioComponent->bStopWhenOwnerDestroyed = true; + AudioComponent->Mobility = EComponentMobility::Movable; + + RootComponent = AudioComponent; + + bReplicates = false; + bHidden = true; + bCanBeDamaged = false; +} + +#if WITH_EDITOR + +void AFMODAmbientSound::CheckForErrors( void ) +{ + Super::CheckForErrors(); + + if (!AudioComponent) + { + FFormatNamedArguments Arguments; + Arguments.Add(TEXT("ActorName"), FText::FromString(GetName())); + FMessageLog("MapCheck").Warning() + ->AddToken(FUObjectToken::Create(this)) + ->AddToken(FTextToken::Create(FText::Format( LOCTEXT( "MapCheck_Message_AudioComponentNull", "{ActorName} : Ambient sound actor has NULL AudioComponent property - please delete" ), Arguments ) )) + ->AddToken(FMapErrorToken::Create(FMapErrors::AudioComponentNull)); + } + else if (AudioComponent->Event == NULL) + { + FFormatNamedArguments Arguments; + Arguments.Add(TEXT("ActorName"), FText::FromString(GetName())); + FMessageLog("MapCheck").Warning() + ->AddToken(FUObjectToken::Create(this)) + ->AddToken(FTextToken::Create(FText::Format( LOCTEXT( "MapCheck_Message_EventNull", "{ActorName} : Ambient sound actor has NULL Event property" ), Arguments ) )) + ->AddToken(FMapErrorToken::Create(FMapErrors::SoundCueNull)); + } +} + +bool AFMODAmbientSound::GetReferencedContentObjects( TArray& Objects ) const +{ + if (AudioComponent->Event) + { + Objects.Add(AudioComponent->Event.Get()); + } + return true; +} + +#endif + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Plugins/FMODStudio/Source/FMODStudio/Private/FMODAsset.cpp b/Plugins/FMODStudio/Source/FMODStudio/Private/FMODAsset.cpp new file mode 100644 index 0000000..204efbb --- /dev/null +++ b/Plugins/FMODStudio/Source/FMODStudio/Private/FMODAsset.cpp @@ -0,0 +1,27 @@ +// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2016. + +#include "FMODStudioPrivatePCH.h" +#include "FMODAsset.h" +#include "FMODStudioModule.h" + +static const TCHAR* FMODAssetTypeStrings[] = +{ + TEXT("Bank"), + TEXT("Event"), + TEXT("VCA"), + TEXT("Bus"), + TEXT("Invalid") +}; + +UFMODAsset::UFMODAsset(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +/** Get tags to show in content view */ +void UFMODAsset::GetAssetRegistryTags(TArray& OutTags) const +{ + Super::GetAssetRegistryTags(OutTags); + OutTags.Add(UObject::FAssetRegistryTag("Guid", AssetGuid.ToString(EGuidFormats::DigitsWithHyphensInBraces), UObject::FAssetRegistryTag::TT_Alphabetical)); +} + diff --git a/Plugins/FMODStudio/Source/FMODStudio/Private/FMODAssetTable.cpp b/Plugins/FMODStudio/Source/FMODStudio/Private/FMODAssetTable.cpp new file mode 100644 index 0000000..c40b6c7 --- /dev/null +++ b/Plugins/FMODStudio/Source/FMODStudio/Private/FMODAssetTable.cpp @@ -0,0 +1,274 @@ +// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2016. + +#include "FMODStudioPrivatePCH.h" +#include "FMODAssetTable.h" +#include "FMODEvent.h" +#include "FMODSnapshot.h" +#include "FMODSnapshotReverb.h" +#include "FMODBank.h" +#include "FMODBus.h" +#include "FMODVCA.h" +#include "FMODUtils.h" +#include "FMODSettings.h" +#include "FMODFileCallbacks.h" +#include "fmod_studio.hpp" + +#if WITH_EDITOR +#include "AssetRegistryModule.h" +#endif + +FFMODAssetTable::FFMODAssetTable() +: StudioSystem(nullptr) +{ +} + +FFMODAssetTable::~FFMODAssetTable() +{ + Destroy(); +} + +void FFMODAssetTable::Create() +{ + Destroy(); + + // Create a sandbox system purely for loading and considering banks + verifyfmod(FMOD::Studio::System::create(&StudioSystem)); + FMOD::System* lowLevelSystem = nullptr; + verifyfmod(StudioSystem->getLowLevelSystem(&lowLevelSystem)); + verifyfmod(lowLevelSystem->setOutput(FMOD_OUTPUTTYPE_NOSOUND)); + verifyfmod(lowLevelSystem->setFileSystem(FMODOpen, FMODClose, FMODRead, FMODSeek, 0, 0, 2048)); + verifyfmod(StudioSystem->initialize(1, FMOD_STUDIO_INIT_ALLOW_MISSING_PLUGINS, FMOD_INIT_NORMAL, 0)); +} + +void FFMODAssetTable::Destroy() +{ + if (StudioSystem != nullptr) + { + verifyfmod(StudioSystem->release()); + } + StudioSystem = nullptr; +} + +UFMODAsset* FFMODAssetTable::FindByName(const FString& Name) +{ + TWeakObjectPtr* FoundAsset = FullNameLookup.Find(Name); + if (FoundAsset) + { + return FoundAsset->Get(); + } + return nullptr; +} + +void FFMODAssetTable::Refresh() +{ + if (StudioSystem == nullptr) + { + return; + } + + const UFMODSettings& Settings = *GetDefault(); + FString StringPath = Settings.GetMasterStringsBankPath(); + + UE_LOG(LogFMOD, Log, TEXT("Loading strings bank: %s"), *StringPath); + + FMOD::Studio::Bank* StudioStringBank; + FMOD_RESULT StringResult = StudioSystem->loadBankFile(TCHAR_TO_UTF8(*StringPath), FMOD_STUDIO_LOAD_BANK_NORMAL, &StudioStringBank); + if (StringResult == FMOD_OK) + { + TArray RawBuffer; + RawBuffer.SetNum(256); // Initial capacity + + int Count = 0; + verifyfmod(StudioStringBank->getStringCount(&Count)); + for (int StringIdx=0; StringIdxgetStringInfo(StringIdx, &Guid, RawBuffer.GetData(), RawBuffer.Num(), &ActualSize); + if (Result == FMOD_ERR_TRUNCATED) + { + RawBuffer.SetNum(ActualSize); + } + else + { + break; + } + } + verifyfmod(Result); + FString AssetName(UTF8_TO_TCHAR(RawBuffer.GetData())); + FGuid AssetGuid = FMODUtils::ConvertGuid(Guid); + if (!AssetName.IsEmpty()) + { + AddAsset(AssetGuid, AssetName); + } + } + verifyfmod(StudioStringBank->unload()); + verifyfmod(StudioSystem->update()); + } + else + { + UE_LOG(LogFMOD, Warning, TEXT("Failed to load strings bank: %s"), *StringPath); + } +} + +void FFMODAssetTable::AddAsset(const FGuid& AssetGuid, const FString& AssetFullName) +{ + FString AssetPath = AssetFullName; + FString AssetType = ""; + FString AssetShortName = "asset"; + + int DelimIndex; + if (AssetPath.FindChar(':', DelimIndex)) + { + AssetType = AssetPath.Left(DelimIndex); + AssetPath = AssetPath.Right(AssetPath.Len() - DelimIndex - 1); + } + + FString FormattedAssetType = ""; + UClass* AssetClass = UFMODAsset::StaticClass(); + if (AssetType.Equals(TEXT("event"))) + { + FormattedAssetType = TEXT("Events"); + AssetClass = UFMODEvent::StaticClass(); + } + else if (AssetType.Equals(TEXT("snapshot"))) + { + FormattedAssetType = TEXT("Snapshots"); + AssetClass = UFMODSnapshot::StaticClass(); + } + else if (AssetType.Equals(TEXT("bank"))) + { + FormattedAssetType = TEXT("Banks"); + AssetClass = UFMODBank::StaticClass(); + } + else if (AssetType.Equals(TEXT("bus"))) + { + FormattedAssetType = TEXT("Buses"); + AssetClass = UFMODBus::StaticClass(); + } + else if (AssetType.Equals(TEXT("vca"))) + { + FormattedAssetType = TEXT("VCAs"); + AssetClass = UFMODVCA::StaticClass(); + } + else + { + UE_LOG(LogFMOD, Warning, TEXT("Unknown asset type: %s"), *AssetType); + } + + if (AssetPath.FindLastChar('/', DelimIndex)) + { + AssetShortName = AssetPath.Right(AssetPath.Len() - DelimIndex - 1); + AssetPath = AssetPath.Left(AssetPath.Len() - AssetShortName.Len() - 1); + } + else + { + // No path part, all name + AssetShortName = AssetPath; + AssetPath = TEXT(""); + } + + if (AssetShortName.IsEmpty() || AssetShortName.Contains(TEXT(".strings"))) + { + UE_LOG(LogFMOD, Log, TEXT("Skipping asset: %s"), *AssetFullName); + return; + } + + AssetPath = AssetPath.Replace(TEXT(" "), TEXT("_")); + AssetShortName = AssetShortName.Replace(TEXT(" "), TEXT("_")); + + const UFMODSettings& Settings = *GetDefault(); + + FString FolderPath = Settings.ContentBrowserPrefix; + FolderPath += FormattedAssetType; + FolderPath += AssetPath; + + FString AssetPackagePath = FolderPath + TEXT("/") + AssetShortName; + + FName AssetPackagePathName(*AssetPackagePath); + + TWeakObjectPtr& ExistingNameAsset = NameMap.FindOrAdd(AssetPackagePathName); + TWeakObjectPtr& ExistingGuidAsset = GuidMap.FindOrAdd(AssetGuid); + TWeakObjectPtr& ExistingFullNameLookupAsset = FullNameLookup.FindOrAdd(AssetFullName); + + UFMODAsset* AssetNameObject = ExistingNameAsset.Get(); + if (AssetNameObject == nullptr) + { + UE_LOG(LogFMOD, Log, TEXT("Constructing asset: %s"), *AssetPackagePath); + + UPackage* NewPackage = CreatePackage(NULL, *AssetPackagePath); + if (NewPackage) + { +#if ENGINE_MINOR_VERSION >= 10 + NewPackage->SetPackageFlags(PKG_CompiledIn); +#else + NewPackage->PackageFlags |= PKG_CompiledIn; +#endif + + AssetNameObject = NewObject(NewPackage, AssetClass, FName(*AssetShortName), RF_Standalone | RF_Public /* | RF_Transient */); + AssetNameObject->AssetGuid = AssetGuid; + AssetNameObject->bShowAsAsset = true; + +#if WITH_EDITOR + FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); + AssetRegistryModule.Get().AddPath(*FolderPath); + FAssetRegistryModule::AssetCreated(AssetNameObject); +#endif + } + else + { + UE_LOG(LogFMOD, Warning, TEXT("Failed to construct package for asset %s"), *AssetPackagePath); + } + + if (AssetClass == UFMODSnapshot::StaticClass()) + { + FString ReverbFolderPath = Settings.ContentBrowserPrefix; + ReverbFolderPath += TEXT("Reverbs"); + ReverbFolderPath += AssetPath; + + FString ReverbAssetPackagePath = ReverbFolderPath + TEXT("/") + AssetShortName; + + UPackage* ReverbPackage = CreatePackage(NULL, *ReverbAssetPackagePath); + if (ReverbPackage) + { +#if ENGINE_MINOR_VERSION >= 10 + ReverbPackage->SetPackageFlags(PKG_CompiledIn); +#else + ReverbPackage->PackageFlags |= PKG_CompiledIn; +#endif + UFMODSnapshotReverb* AssetReverb = NewObject(ReverbPackage, UFMODSnapshotReverb::StaticClass(), FName(*AssetShortName), RF_Standalone | RF_Public /* | RF_Transient */); + AssetReverb->AssetGuid = AssetGuid; + AssetReverb->bShowAsAsset = true; + +#if WITH_EDITOR + FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); + AssetRegistryModule.Get().AddPath(*ReverbFolderPath); + FAssetRegistryModule::AssetCreated(AssetReverb); +#endif + + } + } + } + + UFMODAsset* AssetGuidObject = ExistingGuidAsset.Get(); + if (AssetGuidObject != nullptr && AssetGuidObject != AssetNameObject) + { + FString OldPath = AssetGuidObject->GetPathName(); + UE_LOG(LogFMOD, Log, TEXT("Hiding old asset '%s'"), *OldPath); + + // We had an asset with the same guid but it must have been renamed + // We just hide the old asset from the asset table + AssetGuidObject->bShowAsAsset = false; + +#if WITH_EDITOR + FAssetRegistryModule::AssetRenamed(AssetNameObject, OldPath); +#endif + } + + ExistingNameAsset = AssetNameObject; + ExistingGuidAsset = AssetNameObject; + ExistingFullNameLookupAsset = AssetNameObject; +} diff --git a/Plugins/FMODStudio/Source/FMODStudio/Private/FMODAssetTable.h b/Plugins/FMODStudio/Source/FMODStudio/Private/FMODAssetTable.h new file mode 100644 index 0000000..ca4d175 --- /dev/null +++ b/Plugins/FMODStudio/Source/FMODStudio/Private/FMODAssetTable.h @@ -0,0 +1,39 @@ +// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2016. + +#pragma once + +#include "FMODAsset.h" + +namespace FMOD +{ + namespace Studio + { + class System; + } +} + +class FFMODAssetTable +{ +public: + FFMODAssetTable(); + ~FFMODAssetTable(); + + void Create(); + void Destroy(); + + void Refresh(); + + UFMODAsset* FindByName(const FString& Name); + +private: + + void HandleBanksUpdated(); + void AddAsset(const FGuid& AssetGuid, const FString& AssetFullName); + +private: + FMOD::Studio::System* StudioSystem; + TMap< FGuid, TWeakObjectPtr > GuidMap; + TMap< FName, TWeakObjectPtr > NameMap; + TMap< FString, TWeakObjectPtr > FullNameLookup; +}; + diff --git a/Plugins/FMODStudio/Source/FMODStudio/Private/FMODAudioComponent.cpp b/Plugins/FMODStudio/Source/FMODStudio/Private/FMODAudioComponent.cpp new file mode 100644 index 0000000..7e0178c --- /dev/null +++ b/Plugins/FMODStudio/Source/FMODStudio/Private/FMODAudioComponent.cpp @@ -0,0 +1,647 @@ +// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2016. + +#include "FMODStudioPrivatePCH.h" +#include "FMODAudioComponent.h" +#include "FMODStudioModule.h" +#include "FMODUtils.h" +#include "FMODEvent.h" +#include "FMODListener.h" +#include "fmod_studio.hpp" + +UFMODAudioComponent::UFMODAudioComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + bAutoDestroy = false; + bAutoActivate = true; + bEnableTimelineCallbacks = false; // Default OFF for efficiency + bStopWhenOwnerDestroyed = true; + bNeverNeedsRenderUpdate = true; +#if WITH_EDITORONLY_DATA + bVisualizeComponent = true; +#endif + bApplyAmbientVolumes = false; + + PrimaryComponentTick.bCanEverTick = true; + PrimaryComponentTick.TickGroup = TG_PrePhysics; + + StudioInstance = nullptr; + ProgrammerSound = nullptr; + + InteriorLastUpdateTime = 0.0; + SourceInteriorVolume = 0.0f; + SourceInteriorLPF = 0.0f; + CurrentInteriorVolume = 0.0f; + CurrentInteriorLPF = 0.0f; +} + +FString UFMODAudioComponent::GetDetailedInfoInternal(void) const +{ + FString Result; + + if(Event) + { + Result = Event->GetPathName(NULL); + } + else + { + Result = TEXT("No_Event"); + } + + return Result; +} + +#if WITH_EDITORONLY_DATA +void UFMODAudioComponent::OnRegister() +{ + Super::OnRegister(); + + UpdateSpriteTexture(); +} + +void UFMODAudioComponent::UpdateSpriteTexture() +{ + if (SpriteComponent) + { + if (bAutoActivate) + { + SpriteComponent->SetSprite(LoadObject(NULL, TEXT("/Engine/EditorResources/AudioIcons/S_AudioComponent_AutoActivate.S_AudioComponent_AutoActivate"))); + } + else + { + SpriteComponent->SetSprite(LoadObject(NULL, TEXT("/Engine/EditorResources/AudioIcons/S_AudioComponent.S_AudioComponent"))); + } + } +} +#endif + +#if WITH_EDITOR +void UFMODAudioComponent::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) +{ + if (IsPlaying()) + { + Stop(); + Play(); + } + +#if WITH_EDITORONLY_DATA + UpdateSpriteTexture(); +#endif + + Super::PostEditChangeProperty(PropertyChangedEvent); +} +#endif // WITH_EDITOR + +#if ENGINE_MINOR_VERSION >= 9 +void UFMODAudioComponent::OnUpdateTransform(bool bSkipPhysicsMove, ETeleportType Teleport) +#else +void UFMODAudioComponent::OnUpdateTransform(bool bSkipPhysicsMove) +#endif +{ + Super::OnUpdateTransform(bSkipPhysicsMove); + if (StudioInstance) + { + FMOD_3D_ATTRIBUTES attr = {{0}}; + attr.position = FMODUtils::ConvertWorldVector(ComponentToWorld.GetLocation()); + attr.up = FMODUtils::ConvertUnitVector(ComponentToWorld.GetUnitAxis(EAxis::Z)); + attr.forward = FMODUtils::ConvertUnitVector(ComponentToWorld.GetUnitAxis(EAxis::X)); + + StudioInstance->set3DAttributes(&attr); + + if (bApplyAmbientVolumes) + { + UpdateInteriorVolumes(); + } + } +} + +// Taken mostly from ActiveSound.cpp +void UFMODAudioComponent::UpdateInteriorVolumes() +{ + // Result of the ambient calculations to apply to the instance + float AmbientVolumeMultiplier = 1.0f; + float AmbientHighFrequencyGain = 1.0f; + + FInteriorSettings Ambient; + const FVector& Location = GetOwner()->GetTransform().GetTranslation(); + AAudioVolume* AudioVolume = GetWorld()->GetAudioSettings(Location, NULL, &Ambient); + + const FFMODListener& Listener = IFMODStudioModule::Get().GetNearestListener(Location); + if( InteriorLastUpdateTime < Listener.InteriorStartTime ) + { + SourceInteriorVolume = CurrentInteriorVolume; + SourceInteriorLPF = CurrentInteriorLPF; + InteriorLastUpdateTime = FApp::GetCurrentTime(); + } + + + bool bAllowSpatialization = true; + if( Listener.Volume == AudioVolume || !bAllowSpatialization ) + { + // Ambient and listener in same ambient zone + CurrentInteriorVolume = ( SourceInteriorVolume * ( 1.0f - Listener.InteriorVolumeInterp ) ) + Listener.InteriorVolumeInterp; + AmbientVolumeMultiplier *= CurrentInteriorVolume; + + CurrentInteriorLPF = ( SourceInteriorLPF * ( 1.0f - Listener.InteriorLPFInterp ) ) + Listener.InteriorLPFInterp; + AmbientHighFrequencyGain *= CurrentInteriorLPF; + + //UE_LOG(LogFMOD, Verbose, TEXT( "Ambient in same volume. Volume *= %g LPF *= %g" ), CurrentInteriorVolume, CurrentInteriorLPF); + } + else + { + // Ambient and listener in different ambient zone + if( Ambient.bIsWorldSettings ) + { + // The ambient sound is 'outside' - use the listener's exterior volume + CurrentInteriorVolume = ( SourceInteriorVolume * ( 1.0f - Listener.ExteriorVolumeInterp ) ) + ( Listener.InteriorSettings.ExteriorVolume * Listener.ExteriorVolumeInterp ); + AmbientVolumeMultiplier *= CurrentInteriorVolume; + + CurrentInteriorLPF = ( SourceInteriorLPF * ( 1.0f - Listener.ExteriorLPFInterp ) ) + ( Listener.InteriorSettings.ExteriorLPF * Listener.ExteriorLPFInterp ); + AmbientHighFrequencyGain *= CurrentInteriorLPF; + + //UE_LOG(LogFMOD, Verbose, TEXT( "Ambient in diff volume, ambient outside. Volume *= %g LPF *= %g" ), CurrentInteriorVolume, CurrentInteriorLPF); + } + else + { + // The ambient sound is 'inside' - use the ambient sound's interior volume multiplied with the listeners exterior volume + CurrentInteriorVolume = (( SourceInteriorVolume * ( 1.0f - Listener.InteriorVolumeInterp ) ) + ( Ambient.InteriorVolume * Listener.InteriorVolumeInterp )) + * (( SourceInteriorVolume * ( 1.0f - Listener.ExteriorVolumeInterp ) ) + ( Listener.InteriorSettings.ExteriorVolume * Listener.ExteriorVolumeInterp )); + AmbientVolumeMultiplier *= CurrentInteriorVolume; + + CurrentInteriorLPF = (( SourceInteriorLPF * ( 1.0f - Listener.InteriorLPFInterp ) ) + ( Ambient.InteriorLPF * Listener.InteriorLPFInterp )) + * (( SourceInteriorLPF * ( 1.0f - Listener.ExteriorLPFInterp ) ) + ( Listener.InteriorSettings.ExteriorLPF * Listener.ExteriorLPFInterp )); + AmbientHighFrequencyGain *= CurrentInteriorLPF; + + //UE_LOG(LogFMOD, Verbose, TEXT( "Ambient in diff volume, ambient inside. Volume *= %g LPF *= %g" ), CurrentInteriorVolume, CurrentInteriorLPF); + } + } + + StudioInstance->setVolume(AmbientVolumeMultiplier); + + FMOD::ChannelGroup* ChanGroup = nullptr; + StudioInstance->getChannelGroup(&ChanGroup); + if (ChanGroup) + { + int NumDSP = 0; + ChanGroup->getNumDSPs(&NumDSP); + for (int Index=0; IndexgetDSP(Index, &ChanDSP); + if (ChanDSP) + { + FMOD_DSP_TYPE DSPType = FMOD_DSP_TYPE_UNKNOWN; + ChanDSP->getType(&DSPType); + if (DSPType == FMOD_DSP_TYPE_LOWPASS || DSPType == FMOD_DSP_TYPE_LOWPASS_SIMPLE) + { + static float MAX_FREQUENCY = 8000.0f; + float Frequency = MAX_FREQUENCY * AmbientHighFrequencyGain; + ChanDSP->setParameterFloat(FMOD_DSP_LOWPASS_CUTOFF, MAX_FREQUENCY * AmbientHighFrequencyGain); + break; + } + } + } + } +} + +void UFMODAudioComponent::OnUnregister() +{ + // Route OnUnregister event. + Super::OnUnregister(); + + // Don't stop audio and clean up component if owner has been destroyed (default behaviour). This function gets + // called from AActor::ClearComponents when an actor gets destroyed which is not usually what we want for one- + // shot sounds. + AActor* Owner = GetOwner(); + if (!Owner || bStopWhenOwnerDestroyed ) + { + Stop(); + } + + if (StudioInstance) + { + StudioInstance->setCallback(nullptr); + StudioInstance->release(); + StudioInstance = nullptr; + } +} + +void UFMODAudioComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) +{ + Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + + if (bIsActive) + { + if (bApplyAmbientVolumes && IFMODStudioModule::Get().HasListenerMoved()) + { + UpdateInteriorVolumes(); + } + + TArray LocalMarkerQueue; + TArray LocalBeatQueue; + { + FScopeLock Lock(&CallbackLock); + Swap(LocalMarkerQueue, CallbackMarkerQueue); + Swap(LocalBeatQueue, CallbackBeatQueue); + } + + for(const FTimelineMarkerProperties& EachProps : LocalMarkerQueue) + { + OnTimelineMarker.Broadcast(EachProps.Name, EachProps.Position); + } + for(const FTimelineBeatProperties& EachProps : LocalBeatQueue) + { + OnTimelineBeat.Broadcast(EachProps.Bar, EachProps.Beat, EachProps.Position, EachProps.Tempo, EachProps.TimeSignatureUpper, EachProps.TimeSignatureLower); + } + + FMOD_STUDIO_PLAYBACK_STATE state = FMOD_STUDIO_PLAYBACK_STOPPED; + StudioInstance->getPlaybackState(&state); + if (state == FMOD_STUDIO_PLAYBACK_STOPPED) + { + OnPlaybackCompleted(); + } + } +} + +void UFMODAudioComponent::SetEvent(UFMODEvent* NewEvent) +{ + const bool bPlay = IsPlaying(); + + Stop(); + Event = NewEvent; + + if (bPlay) + { + Play(); + } +} + +void UFMODAudioComponent::PostLoad() +{ + Super::PostLoad(); +} + +void UFMODAudioComponent::Activate(bool bReset) +{ + if (bReset || ShouldActivate()==true) + { + Play(); + } +} + +void UFMODAudioComponent::Deactivate() +{ + if (ShouldActivate()==false) + { + Stop(); + } +} + +FMOD_RESULT F_CALLBACK UFMODAudioComponent_EventCallback(FMOD_STUDIO_EVENT_CALLBACK_TYPE type, FMOD_STUDIO_EVENTINSTANCE *event, void *parameters) +{ + UFMODAudioComponent* Component = nullptr; + FMOD::Studio::EventInstance* Instance = (FMOD::Studio::EventInstance*)event; + if (Instance->getUserData((void**)&Component) == FMOD_OK && Component != nullptr) + { + if (type == FMOD_STUDIO_EVENT_CALLBACK_TIMELINE_MARKER && Component->bEnableTimelineCallbacks) + { + Component->EventCallbackAddMarker((FMOD_STUDIO_TIMELINE_MARKER_PROPERTIES*)parameters); + } + else if (type == FMOD_STUDIO_EVENT_CALLBACK_TIMELINE_BEAT && Component->bEnableTimelineCallbacks) + { + Component->EventCallbackAddBeat((FMOD_STUDIO_TIMELINE_BEAT_PROPERTIES*)parameters); + } + else if (type == FMOD_STUDIO_EVENT_CALLBACK_CREATE_PROGRAMMER_SOUND) + { + Component->EventCallbackCreateProgrammerSound((FMOD_STUDIO_PROGRAMMER_SOUND_PROPERTIES*)parameters); + } + else if (type == FMOD_STUDIO_EVENT_CALLBACK_DESTROY_PROGRAMMER_SOUND) + { + Component->EventCallbackDestroyProgrammerSound((FMOD_STUDIO_PROGRAMMER_SOUND_PROPERTIES*)parameters); + } + } + return FMOD_OK; +} + +void UFMODAudioComponent::EventCallbackAddMarker(FMOD_STUDIO_TIMELINE_MARKER_PROPERTIES* props) +{ + FScopeLock Lock(&CallbackLock); + FTimelineMarkerProperties info; + info.Name = props->name; + info.Position = props->position; + CallbackMarkerQueue.Push(info); +} + +void UFMODAudioComponent::EventCallbackAddBeat(FMOD_STUDIO_TIMELINE_BEAT_PROPERTIES* props) +{ + FScopeLock Lock(&CallbackLock); + FTimelineBeatProperties info; + info.Bar = props->bar; + info.Beat = props->beat; + info.Position = props->position; + info.Tempo = props->tempo; + info.TimeSignatureUpper = props->timeSignatureUpper; + info.TimeSignatureLower = props->timeSignatureLower; + CallbackBeatQueue.Push(info); +} + +void UFMODAudioComponent::EventCallbackCreateProgrammerSound(FMOD_STUDIO_PROGRAMMER_SOUND_PROPERTIES* props) +{ + // Make sure name isn't being changed as we are reading it + FString ProgrammerSoundNameCopy; + { + FScopeLock Lock(&CallbackLock); + ProgrammerSoundNameCopy = ProgrammerSoundName; + } + + if (ProgrammerSound) + { + props->sound = (FMOD_SOUND*)ProgrammerSound; + props->subsoundIndex = -1; + } + else if (ProgrammerSoundNameCopy.Len() || strlen(props->name) != 0) + { + FMOD::Studio::System* System = IFMODStudioModule::Get().GetStudioSystem(EFMODSystemContext::Runtime); + FMOD::System* LowLevelSystem = nullptr; + System->getLowLevelSystem(&LowLevelSystem); + FString SoundName = ProgrammerSoundNameCopy.Len() ? ProgrammerSoundNameCopy : UTF8_TO_TCHAR(props->name); + + if (SoundName.Contains(TEXT("."))) + { + // Load via file + FString SoundPath = SoundName; + if (FPaths::IsRelative(SoundPath)) + { + SoundPath = FPaths::GameContentDir() / SoundPath; + } + + FMOD::Sound* Sound = nullptr; + if (LowLevelSystem->createSound(TCHAR_TO_UTF8(*SoundPath), FMOD_DEFAULT, nullptr, &Sound) == FMOD_OK) + { + UE_LOG(LogFMOD, Verbose, TEXT("Creating programmer sound from file '%s'"), *SoundPath); + props->sound = (FMOD_SOUND*)Sound; + props->subsoundIndex = -1; + } + else + { + UE_LOG(LogFMOD, Warning, TEXT("Failed to load programmer sound file '%s'"), *SoundPath); + } + } + else + { + // Load via FMOD Studio asset table + FMOD_STUDIO_SOUND_INFO SoundInfo = {0}; + FMOD_RESULT Result = System->getSoundInfo(TCHAR_TO_UTF8(*SoundName), &SoundInfo); + if (Result == FMOD_OK) + { + FMOD::Sound* Sound = nullptr; + Result = LowLevelSystem->createSound(SoundInfo.name_or_data, SoundInfo.mode, &SoundInfo.exinfo, &Sound); + if (Result == FMOD_OK) + { + UE_LOG(LogFMOD, Verbose, TEXT("Creating programmer sound using audio entry '%s'"), *SoundName); + + props->sound = (FMOD_SOUND*)Sound; + props->subsoundIndex = SoundInfo.subsoundIndex; + } + else + { + UE_LOG(LogFMOD, Warning, TEXT("Failed to load FMOD audio entry '%s'"), *SoundName); + } + } + else + { + UE_LOG(LogFMOD, Warning, TEXT("Failed to find FMOD audio entry '%s'"), *SoundName); + } + } + } +} + +void UFMODAudioComponent::EventCallbackDestroyProgrammerSound(FMOD_STUDIO_PROGRAMMER_SOUND_PROPERTIES* props) +{ + if (props->sound && ProgrammerSound == nullptr) + { + UE_LOG(LogFMOD, Verbose, TEXT("Destroying programmer sound")); + FMOD_RESULT Result = ((FMOD::Sound*)props->sound)->release(); + verifyfmod(Result); + } +} + +void UFMODAudioComponent::SetProgrammerSoundName(FString Value) +{ + FScopeLock Lock(&CallbackLock); + ProgrammerSoundName = Value; +} + +void UFMODAudioComponent::SetProgrammerSound(FMOD::Sound* Sound) +{ + FScopeLock Lock(&CallbackLock); + ProgrammerSound = Sound; +} + +void UFMODAudioComponent::Play() +{ + Stop(); + + if (!FMODUtils::IsWorldAudible(GetWorld())) + { + return; + } + + UE_LOG(LogFMOD, Verbose, TEXT("UFMODAudioComponent %p Play"), this); + + // Only play events in PIE/game, not when placing them in the editor + FMOD::Studio::EventDescription* EventDesc = IFMODStudioModule::Get().GetEventDescription(Event.Get()); + if (EventDesc != nullptr) + { + FMOD_RESULT result = EventDesc->createInstance(&StudioInstance); + if (StudioInstance != nullptr) + { + FMOD_STUDIO_USER_PROPERTY UserProp = {0}; + if (EventDesc->getUserProperty("Ambient", &UserProp) == FMOD_OK) + { + if (UserProp.type == FMOD_STUDIO_USER_PROPERTY_TYPE_FLOAT) // All numbers are stored as float + { + bApplyAmbientVolumes = (UserProp.floatValue != 0.0f); + } + } + OnUpdateTransform(true); + // Set initial parameters + for (auto Kvp : StoredParameters) + { + FMOD_RESULT Result = StudioInstance->setParameterValue(TCHAR_TO_UTF8(*Kvp.Key.ToString()), Kvp.Value); + if (Result != FMOD_OK) + { + UE_LOG(LogFMOD, Warning, TEXT("Failed to set initial parameter %s"), *Kvp.Key.ToString()); + } + } + + verifyfmod(StudioInstance->setUserData(this)); + verifyfmod(StudioInstance->setCallback(UFMODAudioComponent_EventCallback)); + verifyfmod(StudioInstance->start()); + UE_LOG(LogFMOD, Verbose, TEXT("Playing component %p"), this); + bIsActive = true; + SetComponentTickEnabled(true); + } + } +} + +void UFMODAudioComponent::Stop() +{ + UE_LOG(LogFMOD, Verbose, TEXT("UFMODAudioComponent %p Stop"), this); + if (StudioInstance) + { + StudioInstance->stop(FMOD_STUDIO_STOP_ALLOWFADEOUT); + StudioInstance->setCallback(nullptr); + StudioInstance->release(); + StudioInstance = nullptr; + } + InteriorLastUpdateTime = 0.0; +} + +void UFMODAudioComponent::TriggerCue() +{ + UE_LOG(LogFMOD, Verbose, TEXT("UFMODAudioComponent %p TriggerCue"), this); + if (StudioInstance) + { + // Studio only supports a single cue so try to get it + FMOD::Studio::CueInstance* Cue = nullptr; + StudioInstance->getCueByIndex(0, &Cue); + if (Cue) + { + Cue->trigger(); + } + } +} + +void UFMODAudioComponent::OnPlaybackCompleted() +{ + // Mark inactive before calling destroy to avoid recursion + UE_LOG(LogFMOD, Verbose, TEXT("UFMODAudioComponent %p PlaybackCompleted"), this); + bIsActive = false; + SetComponentTickEnabled(false); + + OnEventStopped.Broadcast(); + + if (StudioInstance) + { + StudioInstance->setCallback(nullptr); + StudioInstance->release(); + StudioInstance = nullptr; + } + + // Auto destruction is handled via marking object for deletion. + if (bAutoDestroy) + { + DestroyComponent(); + } +} + +bool UFMODAudioComponent::IsPlaying( void ) +{ + return bIsActive; +} + + +void UFMODAudioComponent::SetVolume(float Volume) +{ + if (StudioInstance) + { + FMOD_RESULT Result = StudioInstance->setVolume(Volume); + if (Result != FMOD_OK) + { + UE_LOG(LogFMOD, Warning, TEXT("Failed to set volume")); + } + } +} + +void UFMODAudioComponent::SetPitch(float Pitch) +{ + if (StudioInstance) + { + FMOD_RESULT Result = StudioInstance->setPitch(Pitch); + if (Result != FMOD_OK) + { + UE_LOG(LogFMOD, Warning, TEXT("Failed to set pitch")); + } + } +} + +void UFMODAudioComponent::SetPaused(bool Paused) +{ + if (StudioInstance) + { + FMOD_RESULT Result = StudioInstance->setPaused(Paused); + if (Result != FMOD_OK) + { + UE_LOG(LogFMOD, Warning, TEXT("Failed to pause")); + } + } +} + +void UFMODAudioComponent::SetParameter(FName Name, float Value) +{ + if (StudioInstance) + { + FMOD_RESULT Result = StudioInstance->setParameterValue(TCHAR_TO_UTF8(*Name.ToString()), Value); + if (Result != FMOD_OK) + { + UE_LOG(LogFMOD, Warning, TEXT("Failed to set parameter %s"), *Name.ToString()); + } + } + StoredParameters.FindOrAdd(Name) = Value; +} + +void UFMODAudioComponent::SetTimelinePosition(int32 Time) +{ + if (StudioInstance) + { + FMOD_RESULT Result = StudioInstance->setTimelinePosition(Time); + if (Result != FMOD_OK) + { + UE_LOG(LogFMOD, Warning, TEXT("Failed to set timeline position")); + } + } +} + +int32 UFMODAudioComponent::GetTimelinePosition() +{ + int Time = 0; + if (StudioInstance) + { + FMOD_RESULT Result = StudioInstance->getTimelinePosition(&Time); + if (Result != FMOD_OK) + { + UE_LOG(LogFMOD, Warning, TEXT("Failed to get timeline position")); + } + } + return Time; +} + + +float UFMODAudioComponent::GetParameter(FName Name) +{ + float Value = 0.0f; + float* StoredParam = StoredParameters.Find(Name); + if (StoredParam) + { + Value = *StoredParam; + } + if (StudioInstance) + { + FMOD::Studio::ParameterInstance* ParamInst = nullptr; + FMOD_RESULT Result = StudioInstance->getParameter(TCHAR_TO_UTF8(*Name.ToString()), &ParamInst); + if (Result == FMOD_OK) + { + float QueryValue; + Result = ParamInst->getValue(&QueryValue); + if (Result == FMOD_OK) + { + Value = QueryValue; + } + } + if (Result != FMOD_OK) + { + UE_LOG(LogFMOD, Warning, TEXT("Failed to get parameter %s"), *Name.ToString()); + } + } + return Value; +} \ No newline at end of file diff --git a/Plugins/FMODStudio/Source/FMODStudio/Private/FMODBank.cpp b/Plugins/FMODStudio/Source/FMODStudio/Private/FMODBank.cpp new file mode 100644 index 0000000..a832d12 --- /dev/null +++ b/Plugins/FMODStudio/Source/FMODStudio/Private/FMODBank.cpp @@ -0,0 +1,22 @@ +// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2016. + +#include "FMODStudioPrivatePCH.h" +#include "FMODBank.h" +#include "FMODStudioModule.h" + +UFMODBank::UFMODBank(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +/** Get tags to show in content view */ +void UFMODBank::GetAssetRegistryTags(TArray& OutTags) const +{ + Super::GetAssetRegistryTags(OutTags); +} + +FString UFMODBank::GetDesc() +{ + return FString::Printf( TEXT( "Bank %s" ), *AssetGuid.ToString(EGuidFormats::DigitsWithHyphensInBraces) ); +} + diff --git a/Plugins/FMODStudio/Source/FMODStudio/Private/FMODBankUpdateNotifier.cpp b/Plugins/FMODStudio/Source/FMODStudio/Private/FMODBankUpdateNotifier.cpp new file mode 100644 index 0000000..282c97f --- /dev/null +++ b/Plugins/FMODStudio/Source/FMODStudio/Private/FMODBankUpdateNotifier.cpp @@ -0,0 +1,57 @@ +// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2016. + +#include "FMODStudioPrivatePCH.h" +#include "FMODBankUpdateNotifier.h" +#include "FMODSettings.h" + +FFMODBankUpdateNotifier::FFMODBankUpdateNotifier() +: bUpdateEnabled(true), + NextRefreshTime(FDateTime::MinValue()) +{ +} + +void FFMODBankUpdateNotifier::SetFilePath(const FString& InPath) +{ + FilePath = InPath; + NextRefreshTime = FDateTime::MinValue(); + FileTime = FDateTime::MinValue(); +} + +void FFMODBankUpdateNotifier::Update() +{ + if (bUpdateEnabled) + { + FDateTime CurTime = FDateTime::UtcNow(); + if (CurTime >= NextRefreshTime) + { + NextRefreshTime = CurTime + FTimespan(0, 0, 1); + Refresh(); + } + } +} + +void FFMODBankUpdateNotifier::EnableUpdate(bool bEnable) +{ + bUpdateEnabled = bEnable; + + if (bEnable) + { + // Refreshing right after update is enabled is not desirable + NextRefreshTime = FDateTime::UtcNow() + FTimespan(0, 0, 1); + } +} + +void FFMODBankUpdateNotifier::Refresh() +{ + if (!FilePath.IsEmpty()) + { + const FDateTime NewFileTime = IFileManager::Get().GetTimeStamp(*FilePath); + if (NewFileTime != FileTime) + { + FileTime = NewFileTime; + UE_LOG(LogFMOD, Log, TEXT("File has changed: %s"), *FilePath); + + BanksUpdatedEvent.Broadcast(); + } + } +} diff --git a/Plugins/FMODStudio/Source/FMODStudio/Private/FMODBankUpdateNotifier.h b/Plugins/FMODStudio/Source/FMODStudio/Private/FMODBankUpdateNotifier.h new file mode 100644 index 0000000..3b30364 --- /dev/null +++ b/Plugins/FMODStudio/Source/FMODStudio/Private/FMODBankUpdateNotifier.h @@ -0,0 +1,24 @@ +// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2016. + +#pragma once + +class FFMODBankUpdateNotifier +{ +public: + FFMODBankUpdateNotifier(); + + void SetFilePath(const FString& InPath); + void Update(); + + void EnableUpdate(bool bEnable); + + FSimpleMulticastDelegate BanksUpdatedEvent; + +private: + void Refresh(); + + bool bUpdateEnabled; + FString FilePath; + FDateTime NextRefreshTime; + FDateTime FileTime; +}; diff --git a/Plugins/FMODStudio/Source/FMODStudio/Private/FMODBlueprintStatics.cpp b/Plugins/FMODStudio/Source/FMODStudio/Private/FMODBlueprintStatics.cpp new file mode 100644 index 0000000..1dc8d01 --- /dev/null +++ b/Plugins/FMODStudio/Source/FMODStudio/Private/FMODBlueprintStatics.cpp @@ -0,0 +1,409 @@ +// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2016. + +#include "FMODStudioPrivatePCH.h" +#include "FMODBlueprintStatics.h" +#include "FMODAudioComponent.h" +#include "FMODSettings.h" +#include "FMODStudioModule.h" +#include "FMODUtils.h" +#include "FMODBank.h" +#include "FMODEvent.h" +#include "FMODBus.h" +#include "fmod_studio.hpp" + +///////////////////////////////////////////////////// +// UFMODBlueprintStatics + +UFMODBlueprintStatics::UFMODBlueprintStatics(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +FFMODEventInstance UFMODBlueprintStatics::PlayEvent2D(UObject* WorldContextObject, class UFMODEvent* Event, bool bAutoPlay) +{ + return PlayEventAtLocation(WorldContextObject, Event, FTransform(), bAutoPlay); +} + +FFMODEventInstance UFMODBlueprintStatics::PlayEventAtLocation(UObject* WorldContextObject, class UFMODEvent* Event, const FTransform& Location, bool bAutoPlay) +{ + FFMODEventInstance Instance; + Instance.Instance = nullptr; + + UWorld* ThisWorld = GEngine->GetWorldFromContextObject(WorldContextObject); + if (FMODUtils::IsWorldAudible(ThisWorld)) + { + FMOD::Studio::EventDescription* EventDesc = IFMODStudioModule::Get().GetEventDescription(Event); + if (EventDesc != nullptr) + { + FMOD::Studio::EventInstance* EventInst = nullptr; + EventDesc->createInstance(&EventInst); + if (EventInst != nullptr) + { + FMOD_3D_ATTRIBUTES EventAttr = { { 0 } }; + FMODUtils::Assign(EventAttr, Location); + EventInst->set3DAttributes(&EventAttr); + + if (bAutoPlay) + { + EventInst->start(); + EventInst->release(); + } + Instance.Instance = EventInst; + } + } + } + return Instance; +} + +class UFMODAudioComponent* UFMODBlueprintStatics::PlayEventAttached(class UFMODEvent* Event, class USceneComponent* AttachToComponent, FName AttachPointName, FVector Location, EAttachLocation::Type LocationType, bool bStopWhenAttachedToDestroyed, bool bAutoPlay) +{ + if (Event == nullptr) + { + return nullptr; + } + if (AttachToComponent == nullptr) + { + UE_LOG(LogFMOD, Warning, TEXT("UFMODBlueprintStatics::PlayEventAttached: NULL AttachComponent specified!")); + return nullptr; + } + + AActor* Actor = AttachToComponent->GetOwner(); + + // Avoid creating component if we're trying to play a sound on an already destroyed actor. + if (Actor && Actor->IsPendingKill()) + { + return nullptr; + } + + UFMODAudioComponent* AudioComponent; + if( Actor ) + { + // Use actor as outer if we have one. + AudioComponent = NewObject(Actor); + } + else + { + // Let engine pick the outer (transient package). + AudioComponent = NewObject(); + } + check( AudioComponent ); + AudioComponent->Event = Event; + AudioComponent->bAutoActivate = false; + AudioComponent->bAutoDestroy = true; + AudioComponent->bStopWhenOwnerDestroyed = bStopWhenAttachedToDestroyed; +#if WITH_EDITORONLY_DATA + AudioComponent->bVisualizeComponent = false; +#endif + AudioComponent->RegisterComponentWithWorld(AttachToComponent->GetWorld()); + + AudioComponent->AttachTo(AttachToComponent, AttachPointName); + if (LocationType == EAttachLocation::KeepWorldPosition) + { + AudioComponent->SetWorldLocation(Location); + } + else + { + AudioComponent->SetRelativeLocation(Location); + } + + if (bAutoPlay) + { + AudioComponent->Play(); + } + return AudioComponent; +} + +UFMODAsset* UFMODBlueprintStatics::FindAssetByName(const FString& Name) +{ + return IFMODStudioModule::Get().FindAssetByName(Name); +} + +UFMODEvent* UFMODBlueprintStatics::FindEventByName(const FString& Name) +{ + return IFMODStudioModule::Get().FindEventByName(Name); +} + +void UFMODBlueprintStatics::LoadBank(class UFMODBank* Bank, bool bBlocking, bool bLoadSampleData) +{ + FMOD::Studio::System* StudioSystem = IFMODStudioModule::Get().GetStudioSystem(EFMODSystemContext::Runtime); + if (StudioSystem != nullptr && Bank != nullptr) + { + UE_LOG(LogFMOD, Log, TEXT("LoadBank %s"), *Bank->GetName()); + + const UFMODSettings& Settings = *GetDefault(); + FString BankPath = Settings.GetFullBankPath() / (Bank->GetName() + TEXT(".bank")); + + FMOD::Studio::Bank* bank = nullptr; + FMOD_STUDIO_LOAD_BANK_FLAGS flags = bBlocking ? FMOD_STUDIO_LOAD_BANK_NORMAL : FMOD_STUDIO_LOAD_BANK_NONBLOCKING; + FMOD_RESULT result = StudioSystem->loadBankFile(TCHAR_TO_UTF8(*BankPath), flags, &bank); + if ( result == FMOD_OK && bank != nullptr && bLoadSampleData ) + { + // Make sure bank is ready to load sample data from + StudioSystem->flushCommands(); + bank->loadSampleData(); + } + } +} + +void UFMODBlueprintStatics::UnloadBank(class UFMODBank* Bank) +{ + FMOD::Studio::System* StudioSystem = IFMODStudioModule::Get().GetStudioSystem(EFMODSystemContext::Runtime); + if (StudioSystem != nullptr && Bank != nullptr) + { + UE_LOG(LogFMOD, Log, TEXT("UnloadBank %s"), *Bank->GetName()); + + FMOD::Studio::ID guid = FMODUtils::ConvertGuid(Bank->AssetGuid); + FMOD::Studio::Bank* bank = nullptr; + FMOD_RESULT result = StudioSystem->getBankByID(&guid, &bank); + if (result == FMOD_OK && bank != nullptr) + { + bank->unload(); + } + } +} + +void UFMODBlueprintStatics::LoadBankSampleData(class UFMODBank* Bank) +{ + FMOD::Studio::System* StudioSystem = IFMODStudioModule::Get().GetStudioSystem(EFMODSystemContext::Runtime); + if (StudioSystem != nullptr && Bank != nullptr) + { + FMOD::Studio::ID guid = FMODUtils::ConvertGuid(Bank->AssetGuid); + FMOD::Studio::Bank* bank = nullptr; + FMOD_RESULT result = StudioSystem->getBankByID(&guid, &bank); + if (result == FMOD_OK && bank != nullptr) + { + bank->loadSampleData(); + } + } +} + +void UFMODBlueprintStatics::UnloadBankSampleData(class UFMODBank* Bank) +{ + FMOD::Studio::System* StudioSystem = IFMODStudioModule::Get().GetStudioSystem(EFMODSystemContext::Runtime); + if (StudioSystem != nullptr && Bank != nullptr) + { + FMOD::Studio::ID guid = FMODUtils::ConvertGuid(Bank->AssetGuid); + FMOD::Studio::Bank* bank = nullptr; + FMOD_RESULT result = StudioSystem->getBankByID(&guid, &bank); + if (result == FMOD_OK && bank != nullptr) + { + bank->unloadSampleData(); + } + } +} + +void UFMODBlueprintStatics::LoadEventSampleData(UObject* WorldContextObject, class UFMODEvent* Event) +{ + FMOD::Studio::EventDescription* EventDesc = IFMODStudioModule::Get().GetEventDescription(Event); + if (EventDesc != nullptr) + { + EventDesc->loadSampleData(); + } +} + +void UFMODBlueprintStatics::UnloadEventSampleData(UObject* WorldContextObject, class UFMODEvent* Event) +{ + FMOD::Studio::EventDescription* EventDesc = IFMODStudioModule::Get().GetEventDescription(Event); + if (EventDesc != nullptr) + { + EventDesc->unloadSampleData(); + } +} + +TArray UFMODBlueprintStatics::FindEventInstances(UObject* WorldContextObject, UFMODEvent* Event) +{ + FMOD::Studio::EventDescription* EventDesc = IFMODStudioModule::Get().GetEventDescription(Event); + TArray Instances; + if (EventDesc != nullptr) + { + int Capacity = 0; + EventDesc->getInstanceCount(&Capacity); + TArray InstancePointers; + InstancePointers.SetNum(Capacity, true); + int Count = 0; + EventDesc->getInstanceList(InstancePointers.GetData(), Capacity, &Count); + Instances.SetNum(Count, true); + for (int i=0; iAssetGuid); + FMOD::Studio::Bus* bus = nullptr; + FMOD_RESULT result = StudioSystem->getBusByID(&guid, &bus); + if (result == FMOD_OK && bus != nullptr) + { + bus->setFaderLevel(Level); + } + } +} + +void UFMODBlueprintStatics::BusSetPaused(class UFMODBus* Bus, bool bPaused) +{ + FMOD::Studio::System* StudioSystem = IFMODStudioModule::Get().GetStudioSystem(EFMODSystemContext::Runtime); + if (StudioSystem != nullptr && Bus != nullptr) + { + FMOD::Studio::ID guid = FMODUtils::ConvertGuid(Bus->AssetGuid); + FMOD::Studio::Bus* bus = nullptr; + FMOD_RESULT result = StudioSystem->getBusByID(&guid, &bus); + if (result == FMOD_OK && bus != nullptr) + { + bus->setPaused(bPaused); + } + } +} + +void UFMODBlueprintStatics::BusSetMute(class UFMODBus* Bus, bool bMute) +{ + FMOD::Studio::System* StudioSystem = IFMODStudioModule::Get().GetStudioSystem(EFMODSystemContext::Runtime); + if (StudioSystem != nullptr && Bus != nullptr) + { + FMOD::Studio::ID guid = FMODUtils::ConvertGuid(Bus->AssetGuid); + FMOD::Studio::Bus* bus = nullptr; + FMOD_RESULT result = StudioSystem->getBusByID(&guid, &bus); + if (result == FMOD_OK && bus != nullptr) + { + bus->setMute(bMute); + } + } +} + +bool UFMODBlueprintStatics::EventInstanceIsValid(FFMODEventInstance EventInstance) +{ + if (EventInstance.Instance) + { + return EventInstance.Instance->isValid(); + } + return false; +} + +void UFMODBlueprintStatics::EventInstanceSetVolume(FFMODEventInstance EventInstance, float Volume) +{ + if (EventInstance.Instance) + { + FMOD_RESULT Result = EventInstance.Instance->setVolume(Volume); + if (Result != FMOD_OK) + { + UE_LOG(LogFMOD, Warning, TEXT("Failed to set event instance volume")); + } + } +} + +void UFMODBlueprintStatics::EventInstanceSetPitch(FFMODEventInstance EventInstance, float Pitch) +{ + if (EventInstance.Instance) + { + FMOD_RESULT Result = EventInstance.Instance->setPitch(Pitch); + if (Result != FMOD_OK) + { + UE_LOG(LogFMOD, Warning, TEXT("Failed to set event instance pitch")); + } + } +} + +void UFMODBlueprintStatics::EventInstanceSetPaused(FFMODEventInstance EventInstance, bool Paused) +{ + if (EventInstance.Instance) + { + FMOD_RESULT Result = EventInstance.Instance->setPaused(Paused); + if (Result != FMOD_OK) + { + UE_LOG(LogFMOD, Warning, TEXT("Failed to pause event instance")); + } + } +} + +void UFMODBlueprintStatics::EventInstanceSetParameter(FFMODEventInstance EventInstance, FName Name, float Value) +{ + if (EventInstance.Instance) + { + FMOD_RESULT Result = EventInstance.Instance->setParameterValue(TCHAR_TO_UTF8(*Name.ToString()), Value); + if (Result != FMOD_OK) + { + UE_LOG(LogFMOD, Warning, TEXT("Failed to set event instance parameter %s"), *Name.ToString()); + } + } +} + +float UFMODBlueprintStatics::EventInstanceGetParameter(FFMODEventInstance EventInstance, FName Name) +{ + float Value = 0.0f; + if (EventInstance.Instance) + { + FMOD::Studio::ParameterInstance* ParamInst = nullptr; + FMOD_RESULT Result = EventInstance.Instance->getParameter(TCHAR_TO_UTF8(*Name.ToString()), &ParamInst); + if (Result == FMOD_OK) + { + Result = ParamInst->getValue(&Value); + } + if (Result != FMOD_OK) + { + UE_LOG(LogFMOD, Warning, TEXT("Failed to get event instance parameter %s"), *Name.ToString()); + } + } + return Value; +} + +void UFMODBlueprintStatics::EventInstancePlay(FFMODEventInstance EventInstance) +{ + if (EventInstance.Instance) + { + FMOD_RESULT Result = EventInstance.Instance->start(); + if (Result != FMOD_OK) + { + UE_LOG(LogFMOD, Warning, TEXT("Failed to play event instance")); + } + // Once we start playing, allow instance to be cleaned up when it finishes + EventInstance.Instance->release(); + } +} + +void UFMODBlueprintStatics::EventInstanceStop(FFMODEventInstance EventInstance) +{ + if (EventInstance.Instance) + { + FMOD_RESULT Result = EventInstance.Instance->stop(FMOD_STUDIO_STOP_ALLOWFADEOUT); + if (Result != FMOD_OK) + { + UE_LOG(LogFMOD, Warning, TEXT("Failed to stop event instance")); + } + } +} + +void UFMODBlueprintStatics::EventInstanceTriggerCue(FFMODEventInstance EventInstance) +{ + if (EventInstance.Instance) + { + // Studio only supports a single cue so try to get it + FMOD::Studio::CueInstance* Cue = nullptr; + EventInstance.Instance->getCueByIndex(0, &Cue); + if (Cue) + { + Cue->trigger(); + } + } +} + +void UFMODBlueprintStatics::EventInstanceSetTransform(FFMODEventInstance EventInstance, const FTransform& Location) +{ + if (EventInstance.Instance) + { + FMOD_3D_ATTRIBUTES attr = {{0}}; + FMODUtils::Assign(attr, Location); + FMOD_RESULT Result = EventInstance.Instance->set3DAttributes(&attr); + if (Result != FMOD_OK) + { + UE_LOG(LogFMOD, Warning, TEXT("Failed to set transform on event instance")); + } + } + + +} diff --git a/Plugins/FMODStudio/Source/FMODStudio/Private/FMODBus.cpp b/Plugins/FMODStudio/Source/FMODStudio/Private/FMODBus.cpp new file mode 100644 index 0000000..12f2edf --- /dev/null +++ b/Plugins/FMODStudio/Source/FMODStudio/Private/FMODBus.cpp @@ -0,0 +1,22 @@ +// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2016. + +#include "FMODStudioPrivatePCH.h" +#include "FMODBus.h" +#include "FMODStudioModule.h" + +UFMODBus::UFMODBus(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +/** Get tags to show in content view */ +void UFMODBus::GetAssetRegistryTags(TArray& OutTags) const +{ + Super::GetAssetRegistryTags(OutTags); +} + +FString UFMODBus::GetDesc() +{ + return FString::Printf( TEXT( "Bus %s" ), *AssetGuid.ToString(EGuidFormats::DigitsWithHyphensInBraces) ); +} + diff --git a/Plugins/FMODStudio/Source/FMODStudio/Private/FMODEvent.cpp b/Plugins/FMODStudio/Source/FMODStudio/Private/FMODEvent.cpp new file mode 100644 index 0000000..28f1c69 --- /dev/null +++ b/Plugins/FMODStudio/Source/FMODStudio/Private/FMODEvent.cpp @@ -0,0 +1,38 @@ +// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2016. + +#include "FMODStudioPrivatePCH.h" +#include "FMODEvent.h" +#include "FMODStudioModule.h" +#include "fmod_studio.hpp" + +UFMODEvent::UFMODEvent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +/** Get tags to show in content view */ +void UFMODEvent::GetAssetRegistryTags(TArray& OutTags) const +{ + Super::GetAssetRegistryTags(OutTags); + FMOD::Studio::EventDescription* EventDesc = IFMODStudioModule::Get().GetEventDescription(this, EFMODSystemContext::Auditioning); + + bool bOneshot = false; + bool bStream = false; + bool b3D = false; + if (EventDesc) + { + EventDesc->isOneshot(&bOneshot); + EventDesc->isStream(&bStream); + EventDesc->is3D(&b3D); + } + + OutTags.Add(UObject::FAssetRegistryTag("Oneshot", bOneshot ? TEXT("True") : TEXT("False"), UObject::FAssetRegistryTag::TT_Alphabetical)); + OutTags.Add(UObject::FAssetRegistryTag("Streaming", bStream ? TEXT("True") : TEXT("False"), UObject::FAssetRegistryTag::TT_Alphabetical)); + OutTags.Add(UObject::FAssetRegistryTag("3D", b3D ? TEXT("True") : TEXT("False"), UObject::FAssetRegistryTag::TT_Alphabetical)); +} + +FString UFMODEvent::GetDesc() +{ + return FString::Printf( TEXT( "Event %s" ), *AssetGuid.ToString(EGuidFormats::DigitsWithHyphensInBraces) ); +} + diff --git a/Plugins/FMODStudio/Source/FMODStudio/Private/FMODFileCallbacks.cpp b/Plugins/FMODStudio/Source/FMODStudio/Private/FMODFileCallbacks.cpp new file mode 100644 index 0000000..52df4cb --- /dev/null +++ b/Plugins/FMODStudio/Source/FMODStudio/Private/FMODFileCallbacks.cpp @@ -0,0 +1,109 @@ +// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2016. + +#include "FMODStudioPrivatePCH.h" +#include "FMODFileCallbacks.h" +#include "FMODUtils.h" + +FMOD_RESULT F_CALLBACK FMODLogCallback(FMOD_DEBUG_FLAGS flags, const char *file, int line, const char *func, const char *message) +{ + if (flags & FMOD_DEBUG_LEVEL_ERROR) + { + UE_LOG(LogFMOD, Error, TEXT("%s(%d) - %s"), UTF8_TO_TCHAR(file), line, UTF8_TO_TCHAR(message)); + } + else if (flags & FMOD_DEBUG_LEVEL_WARNING) + { + FString Message = UTF8_TO_TCHAR(message); + UE_LOG(LogFMOD, Warning, TEXT("%s(%d) - %s"), UTF8_TO_TCHAR(file), line, *Message); + if (GIsEditor) + { + int32 StartIndex = Message.Find(TEXT("Missing DSP plugin '")); + if (StartIndex != INDEX_NONE) + { + int32 Len = FString(TEXT("Missing DSP plugin '")).Len(); + int32 EndIndex; + if (Message.FindLastChar('\'', EndIndex) && EndIndex != INDEX_NONE && StartIndex+Len < EndIndex) + { + FString PluginName = Message.Mid(StartIndex + Len, EndIndex - StartIndex - Len); + IFMODStudioModule::Get().AddRequiredPlugin(PluginName); + } + } + } + } + else + { + UE_LOG(LogFMOD, Verbose, TEXT("%s(%d) - %s"), UTF8_TO_TCHAR(file), line, UTF8_TO_TCHAR(message)); + } + return FMOD_OK; +} + +FMOD_RESULT F_CALLBACK FMODOpen(const char *name, unsigned int *filesize, void **handle, void * /*userdata*/) +{ + if (name) + { + FArchive* Archive = IFileManager::Get().CreateFileReader(UTF8_TO_TCHAR(name)); + UE_LOG(LogFMOD, Verbose, TEXT("FMODOpen Opening '%s' returned archive %p"), UTF8_TO_TCHAR(name), Archive); + if (!Archive) + { + return FMOD_ERR_FILE_NOTFOUND; + } + *filesize = Archive->TotalSize(); + *handle = Archive; + UE_LOG(LogFMOD, Verbose, TEXT(" TotalSize = %d"), *filesize); + } + + return FMOD_OK; +} + +FMOD_RESULT F_CALLBACK FMODClose(void *handle, void * /*userdata*/) +{ + if (!handle) + { + return FMOD_ERR_INVALID_PARAM; + } + + + FArchive* Archive = (FArchive*)handle; + UE_LOG(LogFMOD, Verbose, TEXT("FMODClose Closing archive %p"), Archive); + delete Archive; + + return FMOD_OK; +} + +FMOD_RESULT F_CALLBACK FMODRead(void *handle, void *buffer, unsigned int sizebytes, unsigned int *bytesread, void * /*userdata*/) +{ + if (!handle) + { + return FMOD_ERR_INVALID_PARAM; + } + + if (bytesread) + { + FArchive* Archive = (FArchive*)handle; + + int64 BytesLeft = Archive->TotalSize() - Archive->Tell(); + int64 ReadAmount = FMath::Min((int64)sizebytes, BytesLeft); + + Archive->Serialize(buffer, ReadAmount); + *bytesread = (unsigned int)ReadAmount; + if (ReadAmount < (int64)sizebytes) + { + UE_LOG(LogFMOD, Verbose, TEXT(" -> EOF ")); + return FMOD_ERR_FILE_EOF; + } + } + + return FMOD_OK; +} + +FMOD_RESULT F_CALLBACK FMODSeek(void *handle, unsigned int pos, void * /*userdata*/) +{ + if (!handle) + { + return FMOD_ERR_INVALID_PARAM; + } + + FArchive* Archive = (FArchive*)handle; + Archive->Seek(pos); + + return FMOD_OK; +} diff --git a/Plugins/FMODStudio/Source/FMODStudio/Private/FMODFileCallbacks.h b/Plugins/FMODStudio/Source/FMODStudio/Private/FMODFileCallbacks.h new file mode 100644 index 0000000..f89234c --- /dev/null +++ b/Plugins/FMODStudio/Source/FMODStudio/Private/FMODFileCallbacks.h @@ -0,0 +1,12 @@ +// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2016. + +#pragma once + +#include "fmod.h" + +FMOD_RESULT F_CALLBACK FMODLogCallback(FMOD_DEBUG_FLAGS flags, const char *file, int line, const char *func, const char *message); +FMOD_RESULT F_CALLBACK FMODOpen(const char *name, unsigned int *filesize, void **handle, void * /*userdata*/); +FMOD_RESULT F_CALLBACK FMODClose(void *handle, void * /*userdata*/); +FMOD_RESULT F_CALLBACK FMODRead(void *handle, void *buffer, unsigned int sizebytes, unsigned int *bytesread, void * /*userdata*/); +FMOD_RESULT F_CALLBACK FMODSeek(void *handle, unsigned int pos, void * /*userdata*/); + diff --git a/Plugins/FMODStudio/Source/FMODStudio/Private/FMODListener.cpp b/Plugins/FMODStudio/Source/FMODStudio/Private/FMODListener.cpp new file mode 100644 index 0000000..c38f412 --- /dev/null +++ b/Plugins/FMODStudio/Source/FMODStudio/Private/FMODListener.cpp @@ -0,0 +1,48 @@ +// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2016. + +#include "FMODStudioPrivatePCH.h" +#include "FMODListener.h" +#include + +float FFMODListener::Interpolate( const double EndTime ) +{ + if( FApp::GetCurrentTime() < InteriorStartTime ) + { + return( 0.0f ); + } + + if( FApp::GetCurrentTime() >= EndTime ) + { + return( 1.0f ); + } + + float InterpValue = ( float )( ( FApp::GetCurrentTime() - InteriorStartTime ) / ( EndTime - InteriorStartTime ) ); + return( InterpValue ); +} + +void FFMODListener::UpdateCurrentInteriorSettings() +{ + // Store the interpolation value, not the actual value + InteriorVolumeInterp = Interpolate( InteriorEndTime ); + ExteriorVolumeInterp = Interpolate( ExteriorEndTime ); + InteriorLPFInterp = Interpolate( InteriorLPFEndTime ); + ExteriorLPFInterp = Interpolate( ExteriorLPFEndTime ); +} + +void FFMODListener::ApplyInteriorSettings( class AAudioVolume* InVolume, const FInteriorSettings& Settings ) +{ + // Note: FInteriorSettings operator!= is not exported, so just do a memcmp instead + if( InVolume != Volume || (0 != memcmp(&Settings, &InteriorSettings, sizeof(FInteriorSettings))) ) + { + // Use previous/ current interpolation time if we're transitioning to the default worldsettings zone. + InteriorStartTime = FApp::GetCurrentTime(); + InteriorEndTime = InteriorStartTime + (Settings.bIsWorldSettings ? InteriorSettings.InteriorTime : Settings.InteriorTime); + ExteriorEndTime = InteriorStartTime + (Settings.bIsWorldSettings ? InteriorSettings.ExteriorTime : Settings.ExteriorTime); + InteriorLPFEndTime = InteriorStartTime + (Settings.bIsWorldSettings ? InteriorSettings.InteriorLPFTime : Settings.InteriorLPFTime); + ExteriorLPFEndTime = InteriorStartTime + (Settings.bIsWorldSettings ? InteriorSettings.ExteriorLPFTime : Settings.ExteriorLPFTime); + + Volume = InVolume; + InteriorSettings = Settings; + } +} + diff --git a/Plugins/FMODStudio/Source/FMODStudio/Private/FMODListener.h b/Plugins/FMODStudio/Source/FMODStudio/Private/FMODListener.h new file mode 100644 index 0000000..b6ac537 --- /dev/null +++ b/Plugins/FMODStudio/Source/FMODStudio/Private/FMODListener.h @@ -0,0 +1,61 @@ +// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2016. + +#pragma once + +/** A direct copy of FListener (which doesn't have external linkage, unfortunately) **/ +struct FFMODListener +{ + FTransform Transform; + FVector Velocity; + + struct FInteriorSettings InteriorSettings; + + /** The volume the listener resides in */ + class AAudioVolume* Volume; + + /** The times of interior volumes fading in and out */ + double InteriorStartTime; + double InteriorEndTime; + double ExteriorEndTime; + double InteriorLPFEndTime; + double ExteriorLPFEndTime; + float InteriorVolumeInterp; + float InteriorLPFInterp; + float ExteriorVolumeInterp; + float ExteriorLPFInterp; + + FVector GetUp() const { return Transform.GetUnitAxis(EAxis::Z); } + FVector GetFront() const { return Transform.GetUnitAxis(EAxis::Y); } + FVector GetRight() const { return Transform.GetUnitAxis(EAxis::X); } + + /** + * Works out the interp value between source and end + */ + float Interpolate( const double EndTime ); + + /** + * Gets the current state of the interior settings for the listener + */ + void UpdateCurrentInteriorSettings(); + + /** + * Apply the interior settings to ambient sounds + */ + void ApplyInteriorSettings( class AAudioVolume* Volume, const FInteriorSettings& Settings ); + + FFMODListener() + : Transform(FTransform::Identity) + , Velocity(ForceInit) + , Volume(NULL) + , InteriorStartTime(0.0) + , InteriorEndTime(0.0) + , ExteriorEndTime(0.0) + , InteriorLPFEndTime(0.0) + , ExteriorLPFEndTime(0.0) + , InteriorVolumeInterp(0.f) + , InteriorLPFInterp(0.f) + , ExteriorVolumeInterp(0.f) + , ExteriorLPFInterp(0.f) + { + } +}; diff --git a/Plugins/FMODStudio/Source/FMODStudio/Private/FMODPlatformLoadDll_Generic.h b/Plugins/FMODStudio/Source/FMODStudio/Private/FMODPlatformLoadDll_Generic.h new file mode 100644 index 0000000..8ad2772 --- /dev/null +++ b/Plugins/FMODStudio/Source/FMODStudio/Private/FMODPlatformLoadDll_Generic.h @@ -0,0 +1,12 @@ +// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2016. +#pragma once + +void* FMODPlatformLoadDll(const TCHAR* LibToLoad) +{ + return FPlatformProcess::GetDllHandle(LibToLoad); +} + +FMOD_RESULT FMODPlatformSystemSetup() +{ + return FMOD_OK; +} \ No newline at end of file diff --git a/Plugins/FMODStudio/Source/FMODStudio/Private/FMODSettings.cpp b/Plugins/FMODStudio/Source/FMODStudio/Private/FMODSettings.cpp new file mode 100644 index 0000000..9e9a5a3 --- /dev/null +++ b/Plugins/FMODStudio/Source/FMODStudio/Private/FMODSettings.cpp @@ -0,0 +1,91 @@ +// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2016. + +#include "FMODStudioPrivatePCH.h" +#include "FMODSettings.h" + +////////////////////////////////////////////////////////////////////////// +// UPaperRuntimeSettings + +UFMODSettings::UFMODSettings(const FObjectInitializer& ObjectInitializer) +: Super(ObjectInitializer) +{ + MasterBankName = TEXT("Master Bank"); + BankOutputDirectory.Path = TEXT("FMOD"); + OutputFormat = EFMODSpeakerMode::Surround_5_1; + ContentBrowserPrefix = TEXT("/Game/FMOD/"); + bLoadAllBanks = true; + bLoadAllSampleData = false; + bEnableLiveUpdate = true; + bVol0Virtual = true; + Vol0VirtualLevel = 0.0001f; + RealChannelCount = 64; + TotalChannelCount = 512; + DSPBufferLength = 0; + DSPBufferCount = 0; + StudioUpdatePeriod = 0; + LiveUpdatePort = 0; +} + +FString UFMODSettings::GetFullBankPath() const +{ + FString FullPath = BankOutputDirectory.Path; + if (FPaths::IsRelative(FullPath)) + { + FullPath = FPaths::GameContentDir() / FullPath; + } + + if (ForcePlatformName == TEXT(".")) + { + // Leave path without subdirectory + } + else if (!ForcePlatformName.IsEmpty()) + { + FullPath = FullPath / ForcePlatformName; + } + else + { +#if PLATFORM_IOS || PLATFORM_ANDROID + FString PlatformName = "Mobile"; +#elif PLATFORM_PS4 + FString PlatformName = "PS4"; +#elif PLATFORM_XBOXONE + FString PlatformName = "XboxOne"; +#else + FString PlatformName = "Desktop"; +#endif + FullPath = FullPath / PlatformName; + } + return FullPath; +} + +FString UFMODSettings::GetMasterBankPath() const +{ + return GetFullBankPath() / (MasterBankName + TEXT(".bank")); +} + +FString UFMODSettings::GetMasterStringsBankPath() const +{ + return GetFullBankPath() / (MasterBankName + TEXT(".strings.bank")); +} + +void UFMODSettings::GetAllBankPaths(TArray& Paths, bool IncludeMasterBank) const +{ + FString BankDir = GetFullBankPath(); + FString SearchDir = BankDir / FString(TEXT("*")); + + TArray AllFiles; + IFileManager::Get().FindFiles(AllFiles, *SearchDir, true, false); + for ( FString& CurFile : AllFiles ) + { + if (CurFile.EndsWith(".bank")) + { + bool IsMaster = (CurFile == MasterBankName + TEXT(".bank") || + CurFile == MasterBankName + TEXT(".strings.bank")); + if (IncludeMasterBank || !IsMaster) + { + Paths.Push(BankDir / CurFile); + } + } + } +} + diff --git a/Plugins/FMODStudio/Source/FMODStudio/Private/FMODSnapshot.cpp b/Plugins/FMODStudio/Source/FMODStudio/Private/FMODSnapshot.cpp new file mode 100644 index 0000000..47de79c --- /dev/null +++ b/Plugins/FMODStudio/Source/FMODStudio/Private/FMODSnapshot.cpp @@ -0,0 +1,16 @@ +// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2016. + +#include "FMODStudioPrivatePCH.h" +#include "FMODSnapshot.h" +#include "FMODStudioModule.h" + +UFMODSnapshot::UFMODSnapshot(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +FString UFMODSnapshot::GetDesc() +{ + return FString::Printf( TEXT( "Snapshot %s" ), *AssetGuid.ToString(EGuidFormats::DigitsWithHyphensInBraces) ); +} + diff --git a/Plugins/FMODStudio/Source/FMODStudio/Private/FMODSnapshotReverb.cpp b/Plugins/FMODStudio/Source/FMODStudio/Private/FMODSnapshotReverb.cpp new file mode 100644 index 0000000..71808c3 --- /dev/null +++ b/Plugins/FMODStudio/Source/FMODStudio/Private/FMODSnapshotReverb.cpp @@ -0,0 +1,9 @@ +// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2016. + +#include "FMODStudioPrivatePCH.h" +#include "FMODSnapshotReverb.h" + +UFMODSnapshotReverb::UFMODSnapshotReverb(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +} diff --git a/Plugins/FMODStudio/Source/FMODStudio/Private/FMODStudioModule.cpp b/Plugins/FMODStudio/Source/FMODStudio/Private/FMODStudioModule.cpp new file mode 100644 index 0000000..1224701 --- /dev/null +++ b/Plugins/FMODStudio/Source/FMODStudio/Private/FMODStudioModule.cpp @@ -0,0 +1,1080 @@ +// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2016. + +#include "FMODStudioPrivatePCH.h" + +#include "FMODSettings.h" +#include "FMODStudioModule.h" +#include "FMODAudioComponent.h" +#include "FMODBlueprintStatics.h" +#include "FMODAssetTable.h" +#include "FMODFileCallbacks.h" +#include "FMODBankUpdateNotifier.h" +#include "FMODUtils.h" +#include "FMODEvent.h" +#include "FMODListener.h" +#include "FMODSnapshotReverb.h" +#include "IPluginManager.h" +#include "fmod_studio.hpp" +#include "fmod_errors.h" + +#if PLATFORM_PS4 +#include "FMODPlatformLoadDll_PS4.h" +#elif PLATFORM_XBOXONE +#include "FMODPlatformLoadDll_XBoxOne.h" +#else +#include "FMODPlatformLoadDll_Generic.h" +#endif + +#define LOCTEXT_NAMESPACE "FMODStudio" + +DEFINE_LOG_CATEGORY(LogFMOD); + +const TCHAR* FMODSystemContextNames[EFMODSystemContext::Max] = +{ + TEXT("Auditioning"), + TEXT("Runtime"), +}; + +void* F_CALLBACK FMODMemoryAlloc(unsigned int size, FMOD_MEMORY_TYPE type, const char *sourcestr) +{ + return FMemory::Malloc(size); +} +void* F_CALLBACK FMODMemoryRealloc(void *ptr, unsigned int size, FMOD_MEMORY_TYPE type, const char *sourcestr) +{ + return FMemory::Realloc(ptr, size); +} +void F_CALLBACK FMODMemoryFree(void *ptr, FMOD_MEMORY_TYPE type, const char *sourcestr) +{ + FMemory::Free(ptr); +} + + +struct FFMODSnapshotEntry +{ + FFMODSnapshotEntry(UFMODSnapshotReverb* InSnapshot=nullptr, FMOD::Studio::EventInstance* InInstance=nullptr) + : Snapshot(InSnapshot), + Instance(InInstance), + StartTime(0.0), + FadeDuration(0.0f), + FadeIntensityStart(0.0f), + FadeIntensityEnd(0.0f) + { + } + + float CurrentIntensity() const + { + double CurrentTime = FApp::GetCurrentTime(); + if (StartTime + FadeDuration <= CurrentTime) + { + return FadeIntensityEnd; + } + else + { + float Factor = (CurrentTime - StartTime) / FadeDuration; + return FMath::Lerp(FadeIntensityStart, FadeIntensityEnd, Factor); + } + } + + void FadeTo(float Target, float Duration) + { + float StartIntensity = CurrentIntensity(); + + StartTime = FApp::GetCurrentTime(); + FadeDuration = Duration; + FadeIntensityStart = StartIntensity; + FadeIntensityEnd = Target; + } + + UFMODSnapshotReverb* Snapshot; + FMOD::Studio::EventInstance* Instance; + double StartTime; + float FadeDuration; + float FadeIntensityStart; + float FadeIntensityEnd; +}; + +class FFMODStudioModule : public IFMODStudioModule +{ +public: + /** IModuleInterface implementation */ + FFMODStudioModule() + : AuditioningInstance(nullptr), + ListenerCount(1), + bSimulating(false), + bIsInPIE(false), + bUseSound(true), + bListenerMoved(true), + bAllowLiveUpdate(true), + LowLevelLibHandle(nullptr), + StudioLibHandle(nullptr) + { + for (int i=0; i GetFailedBankLoads(EFMODSystemContext::Type Context) override + { + return FailedBankLoads[Context]; + } + + virtual TArray GetRequiredPlugins() override + { + return RequiredPlugins; + } + + virtual void AddRequiredPlugin(const FString& Plugin) + { + if (!RequiredPlugins.Contains(Plugin)) + { + RequiredPlugins.Add(Plugin); + } + } + + virtual bool UseSound() override + { + return bUseSound; + } + + virtual bool LoadPlugin(const TCHAR* ShortName) override; + + virtual void LogError(int result, const char* function) override; + + void ResetInterpolation(); + + /** The studio system handle. */ + FMOD::Studio::System* StudioSystem[EFMODSystemContext::Max]; + FMOD::Studio::EventInstance* AuditioningInstance; + + /** The delegate to be invoked when this profiler manager ticks. */ + FTickerDelegate OnTick; + + /** Handle for registered TickDelegate. */ + FDelegateHandle TickDelegateHandle; + + /** Table of assets with name and guid */ + FFMODAssetTable AssetTable; + + /** Periodically checks for updates of the strings.bank file */ + FFMODBankUpdateNotifier BankUpdateNotifier; + + /** List of failed bank files */ + TArray FailedBankLoads[EFMODSystemContext::Max]; + + /** List of required plugins we found when loading banks. */ + TArray RequiredPlugins; + + /** Listener information */ +#if FMOD_VERSION >= 0x00010600 + static const int MAX_LISTENERS = FMOD_MAX_LISTENERS; +#else + static const int MAX_LISTENERS = 1; +#endif + FFMODListener Listeners[MAX_LISTENERS]; + int ListenerCount; + + /** Current snapshot applied via reverb zones*/ + TArray ReverbSnapshots; + + /** True if simulating */ + bool bSimulating; + + /** True if in PIE */ + bool bIsInPIE; + + /** True if we want sound enabled */ + bool bUseSound; + + /** True if we the listener has moved and may have changed audio settings*/ + bool bListenerMoved; + + /** True if we allow live update */ + bool bAllowLiveUpdate; + + /** Dynamic library */ + FString BaseLibPath; + void* LowLevelLibHandle; + void* StudioLibHandle; +}; + +IMPLEMENT_MODULE( FFMODStudioModule, FMODStudio ) + +void FFMODStudioModule::LogError(int result, const char* function) +{ + FString ErrorStr(ANSI_TO_TCHAR(FMOD_ErrorString((FMOD_RESULT)result))); + FString FunctionStr(ANSI_TO_TCHAR(function)); + UE_LOG(LogFMOD, Error, TEXT("'%s' returned '%s'"), *FunctionStr, *ErrorStr); +} + +bool FFMODStudioModule::LoadPlugin(const TCHAR* ShortName) +{ + UE_LOG(LogFMOD, Log, TEXT("Loading plugin '%s'"), ShortName); + + static const int ATTEMPT_COUNT = 2; + static const TCHAR* AttemptPrefixes[ATTEMPT_COUNT] = + { + TEXT(""), +#if PLATFORM_64BITS + TEXT("64") +#else + TEXT("32") +#endif + }; + + FMOD::System* LowLevelSystem = nullptr; + verifyfmod(StudioSystem[EFMODSystemContext::Runtime]->getLowLevelSystem(&LowLevelSystem)); + + FMOD_RESULT PluginLoadResult; + for (int attempt=0; attempt<2; ++attempt) + { + FString AttemptName = FString(ShortName) + AttemptPrefixes[attempt]; + FString PluginPath = GetDllPath(*AttemptName); + + UE_LOG(LogFMOD, Log, TEXT("Trying to load plugin file at location: %s"), *PluginPath); + + unsigned int Handle = 0; + PluginLoadResult = LowLevelSystem->loadPlugin(TCHAR_TO_UTF8(*PluginPath), &Handle, 0); + if (PluginLoadResult == FMOD_OK) + { + UE_LOG(LogFMOD, Log, TEXT("Loaded plugin %s"), ShortName); + return true; + } + } + UE_LOG(LogFMOD, Error, TEXT("Failed to load plugin '%s', sounds may not play"), ShortName); + return false; +} + +void* FFMODStudioModule::LoadDll(const TCHAR* ShortName) +{ + FString LibPath = GetDllPath(ShortName); + + void* Handle = nullptr; + UE_LOG(LogFMOD, Log, TEXT("FFMODStudioModule::LoadDll: Loading %s"), *LibPath); + // Unfortunately Unreal's platform loading code hasn't been implemented on all platforms so we wrap it + Handle = FMODPlatformLoadDll(*LibPath); +#if WITH_EDITOR + if (!Handle) + { + FString Message = TEXT("Couldn't load FMOD DLL ") + LibPath; + FPlatformMisc::MessageBoxExt(EAppMsgType::Ok, *Message, TEXT("Error")); + } +#endif + if (!Handle) + { + UE_LOG(LogFMOD, Error, TEXT("Failed to load FMOD DLL '%s', FMOD sounds will not play!"), *LibPath); + } + return Handle; +} + +FString FFMODStudioModule::GetDllPath(const TCHAR* ShortName) +{ +#if PLATFORM_MAC + return FString::Printf(TEXT("%s/Mac/lib%s.dylib"), *BaseLibPath, ShortName); +#elif PLATFORM_PS4 + FString ShortLibPath = BaseLibPath.ToLower(); + while (ShortLibPath.StartsWith(TEXT("../"))) + { + ShortLibPath = ShortLibPath.RightChop(3); + } + return FString::Printf(TEXT("/app0/%s/ps4/lib%s.prx"), *ShortLibPath, ShortName); +#elif PLATFORM_XBOXONE + return FString::Printf(TEXT("%s.dll"), ShortName); +#elif PLATFORM_ANDROID + return FString::Printf(TEXT("lib%s.so"), ShortName); +#elif PLATFORM_LINUX + return FString::Printf(TEXT("lib%s.so"), ShortName); +#elif PLATFORM_WINDOWS + #if PLATFORM_64BITS + return FString::Printf(TEXT("%s/Win64/%s.dll"), *BaseLibPath, ShortName); + #else + return FString::Printf(TEXT("%s/Win32/%s.dll"), *BaseLibPath, ShortName); + #endif +#else + UE_LOG(LogFMOD, Error, TEXT("Unsupported platform for dynamic libs")); + return ""; +#endif +} + +bool FFMODStudioModule::LoadLibraries() +{ +#if PLATFORM_IOS || PLATFORM_ANDROID || PLATFORM_LINUX + return true; // Nothing to do on those platforms +#elif PLATFORM_HTML5 + UE_LOG(LogFMOD, Error, TEXT("FMOD Studio not supported on HTML5")); + return false; // Explicitly don't support this +#else + UE_LOG(LogFMOD, Verbose, TEXT("FFMODStudioModule::LoadLibraries")); + +#if defined(FMODSTUDIO_LINK_DEBUG) + FString ConfigName = TEXT("D"); +#elif defined(FMODSTUDIO_LINK_LOGGING) + FString ConfigName = TEXT("L"); +#elif defined(FMODSTUDIO_LINK_RELEASE) + FString ConfigName = TEXT(""); +#else + #error FMODSTUDIO_LINK not defined +#endif + +#if PLATFORM_WINDOWS && PLATFORM_64BITS + ConfigName += TEXT("64"); +#endif + + FString LowLevelName = FString(TEXT("fmod")) + ConfigName; + FString StudioName = FString(TEXT("fmodstudio")) + ConfigName; + LowLevelLibHandle = LoadDll(*LowLevelName); + StudioLibHandle = LoadDll(*StudioName); + return (LowLevelLibHandle != nullptr && StudioLibHandle != nullptr); +#endif +} + +void FFMODStudioModule::StartupModule() +{ + UE_LOG(LogFMOD, Log, TEXT("FFMODStudioModule startup")); + BaseLibPath = IPluginManager::Get().FindPlugin(TEXT("FMODStudio"))->GetBaseDir() + TEXT("/Binaries"); + UE_LOG(LogFMOD, Log, TEXT(" Lib path = '%s'"), *BaseLibPath); + + if(FParse::Param(FCommandLine::Get(),TEXT("nosound")) || FApp::IsBenchmarking() || IsRunningDedicatedServer() || IsRunningCommandlet()) + { + bUseSound = false; + } + + if(FParse::Param(FCommandLine::Get(),TEXT("noliveupdate"))) + { + bAllowLiveUpdate = false; + } + + if (LoadLibraries()) + { + verifyfmod(FMOD::Memory_Initialize(0, 0, FMODMemoryAlloc, FMODMemoryRealloc, FMODMemoryFree)); + + // Create sandbox system just for asset loading + AssetTable.Create(); + RefreshSettings(); + + if (GIsEditor) + { + CreateStudioSystem(EFMODSystemContext::Auditioning); + } + else + { + SetInPIE(true, false); + } + } + + OnTick = FTickerDelegate::CreateRaw( this, &FFMODStudioModule::Tick ); + TickDelegateHandle = FTicker::GetCoreTicker().AddTicker( OnTick ); + + if (GIsEditor) + { + BankUpdateNotifier.BanksUpdatedEvent.AddRaw(this, &FFMODStudioModule::HandleBanksUpdated); + } +} + +inline FMOD_SPEAKERMODE ConvertSpeakerMode(EFMODSpeakerMode::Type Mode) +{ + switch (Mode) + { + case EFMODSpeakerMode::Stereo: + return FMOD_SPEAKERMODE_STEREO; + case EFMODSpeakerMode::Surround_5_1: + return FMOD_SPEAKERMODE_5POINT1; + case EFMODSpeakerMode::Surround_7_1: + return FMOD_SPEAKERMODE_7POINT1; + default: + check(0); + return FMOD_SPEAKERMODE_DEFAULT; + }; +} + +void FFMODStudioModule::CreateStudioSystem(EFMODSystemContext::Type Type) +{ + DestroyStudioSystem(Type); + if (!bUseSound) + { + return; + } + + UE_LOG(LogFMOD, Verbose, TEXT("CreateStudioSystem for context %s"), FMODSystemContextNames[Type]); + + const UFMODSettings& Settings = *GetDefault(); + + FMOD_SPEAKERMODE OutputMode = ConvertSpeakerMode(Settings.OutputFormat); + FMOD_STUDIO_INITFLAGS StudioInitFlags = FMOD_STUDIO_INIT_NORMAL; + FMOD_INITFLAGS InitFlags = FMOD_INIT_NORMAL; + if (Type == EFMODSystemContext::Auditioning) + { + StudioInitFlags |= FMOD_STUDIO_INIT_ALLOW_MISSING_PLUGINS; + } + else if (Type == EFMODSystemContext::Runtime && Settings.bEnableLiveUpdate && bAllowLiveUpdate) + { +#if (defined(FMODSTUDIO_LINK_DEBUG) || defined(FMODSTUDIO_LINK_LOGGING)) + UE_LOG(LogFMOD, Verbose, TEXT("Enabling live update")); + StudioInitFlags |= FMOD_STUDIO_INIT_LIVEUPDATE; +#endif + } + + FMOD::Debug_Initialize(FMOD_DEBUG_LEVEL_WARNING, FMOD_DEBUG_MODE_CALLBACK, FMODLogCallback); + + verifyfmod(FMOD::Studio::System::create(&StudioSystem[Type])); + FMOD::System* lowLevelSystem = nullptr; + verifyfmod(StudioSystem[Type]->getLowLevelSystem(&lowLevelSystem)); + verifyfmod(lowLevelSystem->setSoftwareFormat(Settings.SampleRate, OutputMode, 0)); + verifyfmod(lowLevelSystem->setSoftwareChannels(Settings.RealChannelCount)); + verifyfmod(lowLevelSystem->setFileSystem(FMODOpen, FMODClose, FMODRead, FMODSeek, 0, 0, 2048)); + + if (Settings.DSPBufferLength > 0 && Settings.DSPBufferCount > 0) + { + verifyfmod(lowLevelSystem->setDSPBufferSize(Settings.DSPBufferLength, Settings.DSPBufferCount)); + } + + verifyfmod(FMODPlatformSystemSetup()); + + FMOD_ADVANCEDSETTINGS advSettings = {0}; + advSettings.cbSize = sizeof(advSettings); + if (Settings.bVol0Virtual) + { + advSettings.vol0virtualvol = Settings.Vol0VirtualLevel; + InitFlags |= FMOD_INIT_VOL0_BECOMES_VIRTUAL; + } +#if PLATFORM_IOS || PLATFORM_ANDROID + advSettings.maxADPCMCodecs = Settings.RealChannelCount; +#elif PLATFORM_PS4 + advSettings.maxAT9Codecs = Settings.RealChannelCount; +#elif PLATFORM_XBOXONE + advSettings.maxXMACodecs = Settings.RealChannelCount; +#else + advSettings.maxVorbisCodecs = Settings.RealChannelCount; +#endif + advSettings.profilePort = Settings.LiveUpdatePort; + advSettings.randomSeed = FMath::Rand(); + verifyfmod(lowLevelSystem->setAdvancedSettings(&advSettings)); + + FMOD_STUDIO_ADVANCEDSETTINGS advStudioSettings = {0}; + advStudioSettings.cbSize = sizeof(advStudioSettings); + advStudioSettings.studioUpdatePeriod = Settings.StudioUpdatePeriod; + verifyfmod(StudioSystem[Type]->setAdvancedSettings(&advStudioSettings)); + + verifyfmod(StudioSystem[Type]->initialize(Settings.TotalChannelCount, StudioInitFlags, InitFlags, 0)); + + // Don't bother loading plugins during editor, only during PIE or in game + if (Type == EFMODSystemContext::Runtime) + { + for (FString PluginName : Settings.PluginFiles) + { + LoadPlugin(*PluginName); + } + } +} + +void FFMODStudioModule::DestroyStudioSystem(EFMODSystemContext::Type Type) +{ + UE_LOG(LogFMOD, Verbose, TEXT("DestroyStudioSystem for context %s"), FMODSystemContextNames[Type]); + + if (StudioSystem[Type]) + { + verifyfmod(StudioSystem[Type]->release()); + StudioSystem[Type] = nullptr; + } +} + +bool FFMODStudioModule::Tick( float DeltaTime ) +{ + bListenerMoved = false; + + if (GIsEditor) + { + BankUpdateNotifier.Update(); + } + + if (StudioSystem[EFMODSystemContext::Auditioning]) + { + verifyfmod(StudioSystem[EFMODSystemContext::Auditioning]->update()); + } + if (StudioSystem[EFMODSystemContext::Runtime]) + { + UpdateViewportPosition(); + + verifyfmod(StudioSystem[EFMODSystemContext::Runtime]->update()); + } + + return true; +} + +void FFMODStudioModule::UpdateViewportPosition() +{ + int ListenerIndex = 0; + + UWorld* ViewportWorld = nullptr; + if(GEngine && GEngine->GameViewport) + { + ViewportWorld = GEngine->GameViewport->GetWorld(); + } + + bool bCameraCut = false; // Not sure how to get View->bCameraCut from here + float DeltaSeconds = ((bCameraCut || !ViewportWorld) ? 0.f : ViewportWorld->GetDeltaSeconds()); + + if (ViewportWorld) + { + for( FConstPlayerControllerIterator Iterator = ViewportWorld->GetPlayerControllerIterator(); Iterator; ++Iterator ) + { + APlayerController* PlayerController = *Iterator; + if( PlayerController ) + { + ULocalPlayer* LocalPlayer = Cast(PlayerController->Player); + if (LocalPlayer) + { + FVector Location; + FVector ProjFront; + FVector ProjRight; + PlayerController->GetAudioListenerPosition(/*out*/ Location, /*out*/ ProjFront, /*out*/ ProjRight); + FVector ProjUp = FVector::CrossProduct(ProjFront, ProjRight); + + FTransform ListenerTransform(FRotationMatrix::MakeFromXY(ProjFront, ProjRight)); + ListenerTransform.SetTranslation(Location); + ListenerTransform.NormalizeRotation(); + + SetListenerPosition(ListenerIndex, ViewportWorld, ListenerTransform, DeltaSeconds); + + ListenerIndex++; + } + } + } + FinishSetListenerPosition(ListenerIndex, DeltaSeconds); + } +} + +bool FFMODStudioModule::HasListenerMoved() +{ + return bListenerMoved; +} + +void FFMODStudioModule::ResetInterpolation() +{ + for (FFMODListener& Listener : Listeners) + { + Listener = FFMODListener(); + } +} + +const FFMODListener& FFMODStudioModule::GetNearestListener(const FVector& Location) +{ + float BestDistSq = FLT_MAX; + int BestListener = 0; + for (int i = 0; i < ListenerCount; ++i) + { + const float DistSq = FVector::DistSquared(Location, Listeners[i].Transform.GetTranslation()); + if (DistSq < BestDistSq) + { + BestListener = i; + BestDistSq = DistSq; + } + } + return Listeners[BestListener]; +} + +// Partially copied from FAudioDevice::SetListener +void FFMODStudioModule::SetListenerPosition(int ListenerIndex, UWorld* World, const FTransform& ListenerTransform, float DeltaSeconds) +{ + FMOD::Studio::System* System = IFMODStudioModule::Get().GetStudioSystem(EFMODSystemContext::Runtime); + if (System && ListenerIndex < MAX_LISTENERS) + { + FVector ListenerPos = ListenerTransform.GetTranslation(); + + FInteriorSettings InteriorSettings; + AAudioVolume* Volume = World->GetAudioSettings(ListenerPos, NULL, &InteriorSettings); + + Listeners[ListenerIndex].Velocity = DeltaSeconds > 0.f ? + (ListenerTransform.GetTranslation() - Listeners[ ListenerIndex ].Transform.GetTranslation()) / DeltaSeconds + : FVector::ZeroVector; + + Listeners[ListenerIndex].Transform = ListenerTransform; + + Listeners[ListenerIndex].ApplyInteriorSettings(Volume, InteriorSettings); + + // We are using a direct copy of the inbuilt transforms but the directions come out wrong. + // Several of the audio functions use GetFront() for right, so we do the same here. + const FVector Up = Listeners[ListenerIndex].GetUp(); + const FVector Right = Listeners[ListenerIndex].GetFront(); + const FVector Forward = Right ^ Up; + + FMOD_3D_ATTRIBUTES Attributes = {{0}}; + Attributes.position = FMODUtils::ConvertWorldVector(ListenerPos); + Attributes.forward = FMODUtils::ConvertUnitVector(Forward); + Attributes.up = FMODUtils::ConvertUnitVector(Up); + Attributes.velocity = FMODUtils::ConvertWorldVector(Listeners[ListenerIndex].Velocity); + +#if FMOD_VERSION >= 0x00010600 + // Expand number of listeners dynamically + if (ListenerIndex >= ListenerCount) + { + Listeners[ListenerIndex] = FFMODListener(); + ListenerCount = ListenerIndex+1; + verifyfmod(System->setNumListeners(ListenerCount)); + } + verifyfmod(System->setListenerAttributes(ListenerIndex, &Attributes)); +#else + verifyfmod(System->setListenerAttributes(&Attributes)); +#endif + + bListenerMoved = true; + } +} + +void FFMODStudioModule::FinishSetListenerPosition(int NumListeners, float DeltaSeconds) +{ + FMOD::Studio::System* System = IFMODStudioModule::Get().GetStudioSystem(EFMODSystemContext::Runtime); + if (!System) + { + return; + } + + // Shrink number of listeners if we have less than our current count + NumListeners = FMath::Max(NumListeners, 1); + if (System && NumListeners < ListenerCount) + { + ListenerCount = NumListeners; +#if FMOD_VERSION >= 0x00010600 + verifyfmod(System->setNumListeners(ListenerCount)); +#endif + } + + for (int i = 0; i < ListenerCount; ++i) + { + Listeners[i].UpdateCurrentInteriorSettings(); + } + + // Apply a reverb snapshot from the listener position(s) + AAudioVolume* BestVolume = nullptr; + for (int i = 0; i < ListenerCount; ++i) + { + AAudioVolume* CandidateVolume = Listeners[i].Volume; + if (BestVolume == nullptr || (CandidateVolume != nullptr && CandidateVolume->Priority > BestVolume->Priority)) + { + BestVolume = CandidateVolume; + } + } + UFMODSnapshotReverb* NewSnapshot = nullptr; + if (BestVolume && BestVolume->Settings.bApplyReverb) + { + NewSnapshot = Cast(BestVolume->Settings.ReverbEffect); + } + + if (NewSnapshot != nullptr) + { + FString NewSnapshotName = FMODUtils::LookupNameFromGuid(System, NewSnapshot->AssetGuid); + UE_LOG(LogFMOD, Verbose, TEXT("Starting new snapshot '%s'"), *NewSnapshotName); + + // Try to steal old entry + FFMODSnapshotEntry SnapshotEntry; + int SnapshotEntryIndex = -1; + for (int i=0; iAssetGuid); + FMOD::Studio::EventInstance* NewInstance = nullptr; + FMOD::Studio::EventDescription* EventDesc = nullptr; + System->getEventByID(&Guid, &EventDesc); + if (EventDesc) + { + EventDesc->createInstance(&NewInstance); + if (NewInstance) + { + NewInstance->setParameterValue("Intensity", 0.0f); + NewInstance->start(); + } + } + + SnapshotEntryIndex = ReverbSnapshots.Num(); + ReverbSnapshots.Push(FFMODSnapshotEntry(NewSnapshot, NewInstance)); + } + // Fade up + if (ReverbSnapshots[SnapshotEntryIndex].FadeIntensityEnd == 0.0f) + { + ReverbSnapshots[SnapshotEntryIndex].FadeTo(BestVolume->Settings.Volume, BestVolume->Settings.FadeTime); + } + } + // Fade out all other entries + for (int i=0; i %f"), ReverbSnapshots[i].FadeIntensityStart, ReverbSnapshots[i].FadeIntensityEnd, ReverbSnapshots[i].CurrentIntensity()); + ReverbSnapshots[i].Instance->setParameterValue("Intensity", 100.0f * ReverbSnapshots[i].CurrentIntensity()); + + if (ReverbSnapshots[i].Snapshot != NewSnapshot) + { + // Start fading out if needed + if (ReverbSnapshots[i].FadeIntensityEnd != 0.0f) + { + ReverbSnapshots[i].FadeTo(0.0f, ReverbSnapshots[i].FadeDuration); + } + // Finish fading out and remove + else if (ReverbSnapshots[i].CurrentIntensity() == 0.0f) + { + UE_LOG(LogFMOD, Verbose, TEXT("Removing snapshot")); + + ReverbSnapshots[i].Instance->stop(FMOD_STUDIO_STOP_ALLOWFADEOUT); + ReverbSnapshots[i].Instance->release(); + ReverbSnapshots.RemoveAt(i); + --i; // removed entry, redo current index for next one + } + } + } +} + + +void FFMODStudioModule::RefreshSettings() +{ + AssetTable.Refresh(); + if (GIsEditor) + { + const UFMODSettings& Settings = *GetDefault(); + BankUpdateNotifier.SetFilePath(Settings.GetMasterStringsBankPath()); + } +} + + +void FFMODStudioModule::SetInPIE(bool bInPIE, bool simulating) +{ + bIsInPIE = bInPIE; + bSimulating = simulating; + bListenerMoved = true; + ResetInterpolation(); + + if (GIsEditor) + { + BankUpdateNotifier.EnableUpdate(!bInPIE); + } + + if (bInPIE) + { + if (StudioSystem[EFMODSystemContext::Auditioning]) + { + // We currently don't tear down auditioning system but we do stop the playing event. + if (AuditioningInstance) + { + AuditioningInstance->stop(FMOD_STUDIO_STOP_IMMEDIATE); + AuditioningInstance = nullptr; + } + // Also make sure banks are finishing loading so they aren't grabbing file handles. + StudioSystem[EFMODSystemContext::Auditioning]->flushCommands(); + } + + UE_LOG(LogFMOD, Log, TEXT("Creating runtime Studio System")); + ListenerCount = 1; + CreateStudioSystem(EFMODSystemContext::Runtime); + + UE_LOG(LogFMOD, Log, TEXT("Loading Banks")); + LoadBanks(EFMODSystemContext::Runtime); + + } + else + { + ReverbSnapshots.Reset(); + DestroyStudioSystem(EFMODSystemContext::Runtime); + } +} + +UFMODAsset* FFMODStudioModule::FindAssetByName(const FString& Name) +{ + return AssetTable.FindByName(Name); +} + +UFMODEvent* FFMODStudioModule::FindEventByName(const FString& Name) +{ + UFMODAsset* Asset = AssetTable.FindByName(Name); + return Cast(Asset); +} + +void FFMODStudioModule::SetSystemPaused(bool paused) +{ + if (StudioSystem[EFMODSystemContext::Runtime]) + { + FMOD::System* LowLevelSystem = nullptr; + verifyfmod(StudioSystem[EFMODSystemContext::Runtime]->getLowLevelSystem(&LowLevelSystem)); + FMOD::ChannelGroup* MasterChannelGroup = nullptr; + verifyfmod(LowLevelSystem->getMasterChannelGroup(&MasterChannelGroup)); + verifyfmod(MasterChannelGroup->setPaused(paused)); + } +} + +void FFMODStudioModule::PostLoadCallback() +{ +} + +void FFMODStudioModule::ShutdownModule() +{ + UE_LOG(LogFMOD, Verbose, TEXT("FFMODStudioModule shutdown")); + + DestroyStudioSystem(EFMODSystemContext::Auditioning); + DestroyStudioSystem(EFMODSystemContext::Runtime); + + if (GIsEditor) + { + BankUpdateNotifier.BanksUpdatedEvent.RemoveAll(this); + } + + if (UObjectInitialized()) + { + // Unregister tick function. + FTicker::GetCoreTicker().RemoveTicker(TickDelegateHandle); + } + + UE_LOG(LogFMOD, Verbose, TEXT("FFMODStudioModule unloading dynamic libraries")); + if (StudioLibHandle) + { + FPlatformProcess::FreeDllHandle(StudioLibHandle); + StudioLibHandle = nullptr; + } + if (LowLevelLibHandle) + { + FPlatformProcess::FreeDllHandle(LowLevelLibHandle); + LowLevelLibHandle = nullptr; + } + UE_LOG(LogFMOD, Verbose, TEXT("FFMODStudioModule finished unloading")); +} + +struct NamedBankEntry +{ + NamedBankEntry() : Bank(nullptr) { } + NamedBankEntry(const FString& InName, FMOD::Studio::Bank* InBank, FMOD_RESULT InResult) : Name(InName), Bank(InBank), Result(InResult) { } + + FString Name; + FMOD::Studio::Bank* Bank; + FMOD_RESULT Result; +}; + +void FFMODStudioModule::LoadBanks(EFMODSystemContext::Type Type) +{ + const UFMODSettings& Settings = *GetDefault(); + + FailedBankLoads[Type].Reset(); + if (Type == EFMODSystemContext::Auditioning) + { + RequiredPlugins.Reset(); + } + + if (StudioSystem[Type] != nullptr && Settings.IsBankPathSet()) + { + UE_LOG(LogFMOD, Verbose, TEXT("LoadBanks for context %s"), FMODSystemContextNames[Type]); + + /* + Queue up all banks to load asynchronously then wait at the end. + */ + bool bLoadAllBanks = ((Type == EFMODSystemContext::Auditioning) || Settings.bLoadAllBanks); + bool bLoadSampleData = ((Type == EFMODSystemContext::Runtime) && Settings.bLoadAllSampleData); + FMOD_STUDIO_LOAD_BANK_FLAGS BankFlags = (bLoadSampleData ? FMOD_STUDIO_LOAD_BANK_NORMAL : FMOD_STUDIO_LOAD_BANK_NONBLOCKING); + + // Always load the master bank at startup + FString MasterBankPath = Settings.GetMasterBankPath(); + UE_LOG(LogFMOD, Verbose, TEXT("Loading master bank: %s"), *MasterBankPath); + + TArray BankEntries; + + FMOD::Studio::Bank* MasterBank = nullptr; + FMOD_RESULT Result; + Result = StudioSystem[Type]->loadBankFile(TCHAR_TO_UTF8(*MasterBankPath), BankFlags, &MasterBank); + BankEntries.Add(NamedBankEntry(MasterBankPath, MasterBank, Result)); + if (Result == FMOD_OK) + { + if (bLoadSampleData) + { + verifyfmod(MasterBank->loadSampleData()); + } + + // Auditioning needs string bank to get back full paths from events + // Runtime could do without it, but if we load it we can look up guids to names which is helpful + { + FString StringsBankPath = Settings.GetMasterStringsBankPath(); + UE_LOG(LogFMOD, Verbose, TEXT("Loading strings bank: %s"), *StringsBankPath); + FMOD::Studio::Bank* StringsBank = nullptr; + Result = StudioSystem[Type]->loadBankFile(TCHAR_TO_UTF8(*StringsBankPath), BankFlags, &StringsBank); + BankEntries.Add(NamedBankEntry(StringsBankPath, StringsBank, Result)); + } + + // Optionally load all banks in the directory + if (bLoadAllBanks) + { + UE_LOG(LogFMOD, Verbose, TEXT("Loading all banks")); + TArray BankFiles; + Settings.GetAllBankPaths(BankFiles); + for ( const FString& OtherFile : BankFiles ) + { + if (Settings.SkipLoadBankName.Len() && OtherFile.Contains(Settings.SkipLoadBankName)) + { + UE_LOG(LogFMOD, Log, TEXT("Skipping bank: %s"), *OtherFile); + continue; + } + UE_LOG(LogFMOD, Log, TEXT("Loading bank: %s"), *OtherFile); + + FMOD::Studio::Bank* OtherBank; + Result = StudioSystem[Type]->loadBankFile(TCHAR_TO_UTF8(*OtherFile), BankFlags, &OtherBank); + BankEntries.Add(NamedBankEntry(OtherFile, OtherBank, Result)); + if (Result == FMOD_OK) + { + if (bLoadSampleData) + { + verifyfmod(OtherBank->loadSampleData()); + } + } + } + } + } + // Wait for all banks to load. + StudioSystem[Type]->flushCommands(); + + for (NamedBankEntry& Entry : BankEntries) + { + if (Entry.Result == FMOD_OK) + { + FMOD_STUDIO_LOADING_STATE BankLoadingState = FMOD_STUDIO_LOADING_STATE_ERROR; + Entry.Result = Entry.Bank->getLoadingState(&BankLoadingState); + if (BankLoadingState == FMOD_STUDIO_LOADING_STATE_ERROR) + { + Entry.Bank->unload(); + Entry.Bank = nullptr; + } + } + if (Entry.Bank == nullptr || Entry.Result != FMOD_OK) + { + FString ErrorMessage; + if (!FPaths::FileExists(Entry.Name)) + { + ErrorMessage = "File does not exist"; + } + else + { + ErrorMessage = UTF8_TO_TCHAR(FMOD_ErrorString(Entry.Result)); + } + UE_LOG(LogFMOD, Warning, TEXT("Failed to bank: %s (%s)"), *Entry.Name, *ErrorMessage); + FailedBankLoads[Type].Add(FString::Printf(TEXT("%s (%s)"), *FPaths::GetBaseFilename(Entry.Name), *ErrorMessage)); + } + } + } +} + +void FFMODStudioModule::HandleBanksUpdated() +{ + UE_LOG(LogFMOD, Verbose, TEXT("Refreshing auditioning system")); + + DestroyStudioSystem(EFMODSystemContext::Auditioning); + + AssetTable.Refresh(); + + CreateStudioSystem(EFMODSystemContext::Auditioning); + LoadBanks(EFMODSystemContext::Auditioning); + + BanksReloadedDelegate.Broadcast(); + +} + +FMOD::Studio::System* FFMODStudioModule::GetStudioSystem(EFMODSystemContext::Type Context) +{ + if (Context == EFMODSystemContext::Max) + { + Context = (bIsInPIE ? EFMODSystemContext::Runtime : EFMODSystemContext::Auditioning); + } + return StudioSystem[Context]; +} + + +FMOD::Studio::EventDescription* FFMODStudioModule::GetEventDescription(const UFMODEvent* Event, EFMODSystemContext::Type Context) +{ + if (Context == EFMODSystemContext::Max) + { + Context = (bIsInPIE ? EFMODSystemContext::Runtime : EFMODSystemContext::Auditioning); + } + if (StudioSystem[Context] != nullptr && Event != nullptr && Event->AssetGuid.IsValid()) + { + FMOD::Studio::ID Guid = FMODUtils::ConvertGuid(Event->AssetGuid); + FMOD::Studio::EventDescription* EventDesc = nullptr; + StudioSystem[Context]->getEventByID(&Guid, &EventDesc); + return EventDesc; + } + return nullptr; +} + +FMOD::Studio::EventInstance* FFMODStudioModule::CreateAuditioningInstance(const UFMODEvent* Event) +{ + StopAuditioningInstance(); + + FMOD::Studio::EventDescription* EventDesc = GetEventDescription(Event, EFMODSystemContext::Auditioning); + if (EventDesc) + { + FMOD_RESULT Result = EventDesc->createInstance(&AuditioningInstance); + if (Result == FMOD_OK) + { + return AuditioningInstance; + } + } + return nullptr; +} + +void FFMODStudioModule::StopAuditioningInstance() +{ + if (AuditioningInstance) + { + // Don't bother checking for errors just in case auditioning is already shutting down + AuditioningInstance->stop(FMOD_STUDIO_STOP_ALLOWFADEOUT); + AuditioningInstance->release(); + AuditioningInstance = nullptr; + } +} diff --git a/Plugins/FMODStudio/Source/FMODStudio/Private/FMODStudioPrivatePCH.h b/Plugins/FMODStudio/Source/FMODStudio/Private/FMODStudioPrivatePCH.h new file mode 100644 index 0000000..76103c2 --- /dev/null +++ b/Plugins/FMODStudio/Source/FMODStudio/Private/FMODStudioPrivatePCH.h @@ -0,0 +1,11 @@ +// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2016. +#pragma once + +#include "Engine.h" +#include "Components/SceneComponent.h" +#include "Runtime/Launch/Resources/Version.h" +#if WITH_EDITOR +#include "UnrealEd.h" +#endif +DECLARE_LOG_CATEGORY_EXTERN(LogFMOD, Log, All); + diff --git a/Plugins/FMODStudio/Source/FMODStudio/Private/FMODVCA.cpp b/Plugins/FMODStudio/Source/FMODStudio/Private/FMODVCA.cpp new file mode 100644 index 0000000..4bdb6e3 --- /dev/null +++ b/Plugins/FMODStudio/Source/FMODStudio/Private/FMODVCA.cpp @@ -0,0 +1,22 @@ +// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2016. + +#include "FMODStudioPrivatePCH.h" +#include "FMODVCA.h" +#include "FMODStudioModule.h" + +UFMODVCA::UFMODVCA(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +/** Get tags to show in content view */ +void UFMODVCA::GetAssetRegistryTags(TArray& OutTags) const +{ + Super::GetAssetRegistryTags(OutTags); +} + +FString UFMODVCA::GetDesc() +{ + return FString::Printf( TEXT( "VCA %s" ), *AssetGuid.ToString(EGuidFormats::DigitsWithHyphensInBraces) ); +} + diff --git a/Plugins/FMODStudio/Source/FMODStudio/Public/FMOD/fmod.h b/Plugins/FMODStudio/Source/FMODStudio/Public/FMOD/fmod.h new file mode 100644 index 0000000..47d2de7 --- /dev/null +++ b/Plugins/FMODStudio/Source/FMODStudio/Public/FMOD/fmod.h @@ -0,0 +1,718 @@ +/*$ preserve start $*/ + +/* ======================================================================================== */ +/* FMOD Studio Low Level API - C header file. */ +/* Copyright (c), Firelight Technologies Pty, Ltd. 2012-2016. */ +/* */ +/* Use this header in conjunction with fmod_common.h (which contains all the constants / */ +/* callbacks) to develop using C interface. */ +/* ======================================================================================== */ + +#ifndef _FMOD_H +#define _FMOD_H + +#include "fmod_common.h" + +/* ========================================================================================== */ +/* FUNCTION PROTOTYPES */ +/* ========================================================================================== */ + +#ifdef __cplusplus +extern "C" +{ +#endif + +/* + FMOD global system functions (optional). +*/ + +FMOD_RESULT F_API FMOD_Memory_Initialize (void *poolmem, int poollen, FMOD_MEMORY_ALLOC_CALLBACK useralloc, FMOD_MEMORY_REALLOC_CALLBACK userrealloc, FMOD_MEMORY_FREE_CALLBACK userfree, FMOD_MEMORY_TYPE memtypeflags); +FMOD_RESULT F_API FMOD_Memory_GetStats (int *currentalloced, int *maxalloced, FMOD_BOOL blocking); +FMOD_RESULT F_API FMOD_Debug_Initialize (FMOD_DEBUG_FLAGS flags, FMOD_DEBUG_MODE mode, FMOD_DEBUG_CALLBACK callback, const char *filename); +FMOD_RESULT F_API FMOD_File_SetDiskBusy (int busy); +FMOD_RESULT F_API FMOD_File_GetDiskBusy (int *busy); + +/* + FMOD System factory functions. Use this to create an FMOD System Instance. below you will see FMOD_System_Init/Close to get started. +*/ + +FMOD_RESULT F_API FMOD_System_Create (FMOD_SYSTEM **system); +FMOD_RESULT F_API FMOD_System_Release (FMOD_SYSTEM *system); + +/*$ preserve end $*/ + +/* + 'System' API +*/ + +/* + Setup functions. +*/ + +FMOD_RESULT F_API FMOD_System_SetOutput (FMOD_SYSTEM *system, FMOD_OUTPUTTYPE output); +FMOD_RESULT F_API FMOD_System_GetOutput (FMOD_SYSTEM *system, FMOD_OUTPUTTYPE *output); +FMOD_RESULT F_API FMOD_System_GetNumDrivers (FMOD_SYSTEM *system, int *numdrivers); +FMOD_RESULT F_API FMOD_System_GetDriverInfo (FMOD_SYSTEM *system, int id, char *name, int namelen, FMOD_GUID *guid, int *systemrate, FMOD_SPEAKERMODE *speakermode, int *speakermodechannels); +FMOD_RESULT F_API FMOD_System_SetDriver (FMOD_SYSTEM *system, int driver); +FMOD_RESULT F_API FMOD_System_GetDriver (FMOD_SYSTEM *system, int *driver); +FMOD_RESULT F_API FMOD_System_SetSoftwareChannels (FMOD_SYSTEM *system, int numsoftwarechannels); +FMOD_RESULT F_API FMOD_System_GetSoftwareChannels (FMOD_SYSTEM *system, int *numsoftwarechannels); +FMOD_RESULT F_API FMOD_System_SetSoftwareFormat (FMOD_SYSTEM *system, int samplerate, FMOD_SPEAKERMODE speakermode, int numrawspeakers); +FMOD_RESULT F_API FMOD_System_GetSoftwareFormat (FMOD_SYSTEM *system, int *samplerate, FMOD_SPEAKERMODE *speakermode, int *numrawspeakers); +FMOD_RESULT F_API FMOD_System_SetDSPBufferSize (FMOD_SYSTEM *system, unsigned int bufferlength, int numbuffers); +FMOD_RESULT F_API FMOD_System_GetDSPBufferSize (FMOD_SYSTEM *system, unsigned int *bufferlength, int *numbuffers); +FMOD_RESULT F_API FMOD_System_SetFileSystem (FMOD_SYSTEM *system, FMOD_FILE_OPEN_CALLBACK useropen, FMOD_FILE_CLOSE_CALLBACK userclose, FMOD_FILE_READ_CALLBACK userread, FMOD_FILE_SEEK_CALLBACK userseek, FMOD_FILE_ASYNCREAD_CALLBACK userasyncread, FMOD_FILE_ASYNCCANCEL_CALLBACK userasynccancel, int blockalign); +FMOD_RESULT F_API FMOD_System_AttachFileSystem (FMOD_SYSTEM *system, FMOD_FILE_OPEN_CALLBACK useropen, FMOD_FILE_CLOSE_CALLBACK userclose, FMOD_FILE_READ_CALLBACK userread, FMOD_FILE_SEEK_CALLBACK userseek); +FMOD_RESULT F_API FMOD_System_SetAdvancedSettings (FMOD_SYSTEM *system, FMOD_ADVANCEDSETTINGS *settings); +FMOD_RESULT F_API FMOD_System_GetAdvancedSettings (FMOD_SYSTEM *system, FMOD_ADVANCEDSETTINGS *settings); +FMOD_RESULT F_API FMOD_System_SetCallback (FMOD_SYSTEM *system, FMOD_SYSTEM_CALLBACK callback, FMOD_SYSTEM_CALLBACK_TYPE callbackmask); + +/* + Plug-in support. +*/ + +FMOD_RESULT F_API FMOD_System_SetPluginPath (FMOD_SYSTEM *system, const char *path); +FMOD_RESULT F_API FMOD_System_LoadPlugin (FMOD_SYSTEM *system, const char *filename, unsigned int *handle, unsigned int priority); +FMOD_RESULT F_API FMOD_System_UnloadPlugin (FMOD_SYSTEM *system, unsigned int handle); +FMOD_RESULT F_API FMOD_System_GetNumPlugins (FMOD_SYSTEM *system, FMOD_PLUGINTYPE plugintype, int *numplugins); +FMOD_RESULT F_API FMOD_System_GetPluginHandle (FMOD_SYSTEM *system, FMOD_PLUGINTYPE plugintype, int index, unsigned int *handle); +FMOD_RESULT F_API FMOD_System_GetPluginInfo (FMOD_SYSTEM *system, unsigned int handle, FMOD_PLUGINTYPE *plugintype, char *name, int namelen, unsigned int *version); +FMOD_RESULT F_API FMOD_System_SetOutputByPlugin (FMOD_SYSTEM *system, unsigned int handle); +FMOD_RESULT F_API FMOD_System_GetOutputByPlugin (FMOD_SYSTEM *system, unsigned int *handle); +FMOD_RESULT F_API FMOD_System_CreateDSPByPlugin (FMOD_SYSTEM *system, unsigned int handle, FMOD_DSP **dsp); +FMOD_RESULT F_API FMOD_System_GetDSPInfoByPlugin (FMOD_SYSTEM *system, unsigned int handle, const FMOD_DSP_DESCRIPTION **description); +FMOD_RESULT F_API FMOD_System_RegisterCodec (FMOD_SYSTEM *system, FMOD_CODEC_DESCRIPTION *description, unsigned int *handle, unsigned int priority); +FMOD_RESULT F_API FMOD_System_RegisterDSP (FMOD_SYSTEM *system, const FMOD_DSP_DESCRIPTION *description, unsigned int *handle); +FMOD_RESULT F_API FMOD_System_RegisterOutput (FMOD_SYSTEM *system, const FMOD_OUTPUT_DESCRIPTION *description, unsigned int *handle); + +/* + Init/Close. +*/ + +FMOD_RESULT F_API FMOD_System_Init (FMOD_SYSTEM *system, int maxchannels, FMOD_INITFLAGS flags, void *extradriverdata); +FMOD_RESULT F_API FMOD_System_Close (FMOD_SYSTEM *system); + +/* + General post-init system functions. +*/ + +FMOD_RESULT F_API FMOD_System_Update (FMOD_SYSTEM *system); + +FMOD_RESULT F_API FMOD_System_SetSpeakerPosition (FMOD_SYSTEM *system, FMOD_SPEAKER speaker, float x, float y, FMOD_BOOL active); +FMOD_RESULT F_API FMOD_System_GetSpeakerPosition (FMOD_SYSTEM *system, FMOD_SPEAKER speaker, float *x, float *y, FMOD_BOOL *active); +FMOD_RESULT F_API FMOD_System_SetStreamBufferSize (FMOD_SYSTEM *system, unsigned int filebuffersize, FMOD_TIMEUNIT filebuffersizetype); +FMOD_RESULT F_API FMOD_System_GetStreamBufferSize (FMOD_SYSTEM *system, unsigned int *filebuffersize, FMOD_TIMEUNIT *filebuffersizetype); +FMOD_RESULT F_API FMOD_System_Set3DSettings (FMOD_SYSTEM *system, float dopplerscale, float distancefactor, float rolloffscale); +FMOD_RESULT F_API FMOD_System_Get3DSettings (FMOD_SYSTEM *system, float *dopplerscale, float *distancefactor, float *rolloffscale); +FMOD_RESULT F_API FMOD_System_Set3DNumListeners (FMOD_SYSTEM *system, int numlisteners); +FMOD_RESULT F_API FMOD_System_Get3DNumListeners (FMOD_SYSTEM *system, int *numlisteners); +FMOD_RESULT F_API FMOD_System_Set3DListenerAttributes (FMOD_SYSTEM *system, int listener, const FMOD_VECTOR *pos, const FMOD_VECTOR *vel, const FMOD_VECTOR *forward, const FMOD_VECTOR *up); +FMOD_RESULT F_API FMOD_System_Get3DListenerAttributes (FMOD_SYSTEM *system, int listener, FMOD_VECTOR *pos, FMOD_VECTOR *vel, FMOD_VECTOR *forward, FMOD_VECTOR *up); +FMOD_RESULT F_API FMOD_System_Set3DRolloffCallback (FMOD_SYSTEM *system, FMOD_3D_ROLLOFF_CALLBACK callback); +FMOD_RESULT F_API FMOD_System_MixerSuspend (FMOD_SYSTEM *system); +FMOD_RESULT F_API FMOD_System_MixerResume (FMOD_SYSTEM *system); +FMOD_RESULT F_API FMOD_System_GetDefaultMixMatrix (FMOD_SYSTEM *system, FMOD_SPEAKERMODE sourcespeakermode, FMOD_SPEAKERMODE targetspeakermode, float *matrix, int matrixhop); +FMOD_RESULT F_API FMOD_System_GetSpeakerModeChannels (FMOD_SYSTEM *system, FMOD_SPEAKERMODE mode, int *channels); + +/* + System information functions. +*/ + +FMOD_RESULT F_API FMOD_System_GetVersion (FMOD_SYSTEM *system, unsigned int *version); +FMOD_RESULT F_API FMOD_System_GetOutputHandle (FMOD_SYSTEM *system, void **handle); +FMOD_RESULT F_API FMOD_System_GetChannelsPlaying (FMOD_SYSTEM *system, int *channels); +FMOD_RESULT F_API FMOD_System_GetChannelsReal (FMOD_SYSTEM *system, int *realchannels); +FMOD_RESULT F_API FMOD_System_GetCPUUsage (FMOD_SYSTEM *system, float *dsp, float *stream, float *geometry, float *update, float *total); +FMOD_RESULT F_API FMOD_System_GetSoundRAM (FMOD_SYSTEM *system, int *currentalloced, int *maxalloced, int *total); + +/* + Sound/DSP/Channel/FX creation and retrieval. +*/ + +FMOD_RESULT F_API FMOD_System_CreateSound (FMOD_SYSTEM *system, const char *name_or_data, FMOD_MODE mode, FMOD_CREATESOUNDEXINFO *exinfo, FMOD_SOUND **sound); +FMOD_RESULT F_API FMOD_System_CreateStream (FMOD_SYSTEM *system, const char *name_or_data, FMOD_MODE mode, FMOD_CREATESOUNDEXINFO *exinfo, FMOD_SOUND **sound); +FMOD_RESULT F_API FMOD_System_CreateDSP (FMOD_SYSTEM *system, const FMOD_DSP_DESCRIPTION *description, FMOD_DSP **dsp); +FMOD_RESULT F_API FMOD_System_CreateDSPByType (FMOD_SYSTEM *system, FMOD_DSP_TYPE type, FMOD_DSP **dsp); +FMOD_RESULT F_API FMOD_System_CreateChannelGroup (FMOD_SYSTEM *system, const char *name, FMOD_CHANNELGROUP **channelgroup); +FMOD_RESULT F_API FMOD_System_CreateSoundGroup (FMOD_SYSTEM *system, const char *name, FMOD_SOUNDGROUP **soundgroup); +FMOD_RESULT F_API FMOD_System_CreateReverb3D (FMOD_SYSTEM *system, FMOD_REVERB3D **reverb); + +FMOD_RESULT F_API FMOD_System_PlaySound (FMOD_SYSTEM *system, FMOD_SOUND *sound, FMOD_CHANNELGROUP *channelgroup, FMOD_BOOL paused, FMOD_CHANNEL **channel); +FMOD_RESULT F_API FMOD_System_PlayDSP (FMOD_SYSTEM *system, FMOD_DSP *dsp, FMOD_CHANNELGROUP *channelgroup, FMOD_BOOL paused, FMOD_CHANNEL **channel); +FMOD_RESULT F_API FMOD_System_GetChannel (FMOD_SYSTEM *system, int channelid, FMOD_CHANNEL **channel); +FMOD_RESULT F_API FMOD_System_GetMasterChannelGroup (FMOD_SYSTEM *system, FMOD_CHANNELGROUP **channelgroup); +FMOD_RESULT F_API FMOD_System_GetMasterSoundGroup (FMOD_SYSTEM *system, FMOD_SOUNDGROUP **soundgroup); + +/* + Routing to ports. +*/ + +FMOD_RESULT F_API FMOD_System_AttachChannelGroupToPort (FMOD_SYSTEM *system, FMOD_PORT_TYPE portType, FMOD_PORT_INDEX portIndex, FMOD_CHANNELGROUP *channelgroup, FMOD_BOOL passThru); +FMOD_RESULT F_API FMOD_System_DetachChannelGroupFromPort(FMOD_SYSTEM *system, FMOD_CHANNELGROUP *channelgroup); + +/* + Reverb API. +*/ + +FMOD_RESULT F_API FMOD_System_SetReverbProperties (FMOD_SYSTEM *system, int instance, const FMOD_REVERB_PROPERTIES *prop); +FMOD_RESULT F_API FMOD_System_GetReverbProperties (FMOD_SYSTEM *system, int instance, FMOD_REVERB_PROPERTIES *prop); + +/* + System level DSP functionality. +*/ + +FMOD_RESULT F_API FMOD_System_LockDSP (FMOD_SYSTEM *system); +FMOD_RESULT F_API FMOD_System_UnlockDSP (FMOD_SYSTEM *system); + +/* + Recording API. +*/ + +FMOD_RESULT F_API FMOD_System_GetRecordNumDrivers (FMOD_SYSTEM *system, int *numdrivers, int *numconnected); +FMOD_RESULT F_API FMOD_System_GetRecordDriverInfo (FMOD_SYSTEM *system, int id, char *name, int namelen, FMOD_GUID *guid, int *systemrate, FMOD_SPEAKERMODE *speakermode, int *speakermodechannels, FMOD_DRIVER_STATE *state); +FMOD_RESULT F_API FMOD_System_GetRecordPosition (FMOD_SYSTEM *system, int id, unsigned int *position); +FMOD_RESULT F_API FMOD_System_RecordStart (FMOD_SYSTEM *system, int id, FMOD_SOUND *sound, FMOD_BOOL loop); +FMOD_RESULT F_API FMOD_System_RecordStop (FMOD_SYSTEM *system, int id); +FMOD_RESULT F_API FMOD_System_IsRecording (FMOD_SYSTEM *system, int id, FMOD_BOOL *recording); + +/* + Geometry API. +*/ + +FMOD_RESULT F_API FMOD_System_CreateGeometry (FMOD_SYSTEM *system, int maxpolygons, int maxvertices, FMOD_GEOMETRY **geometry); +FMOD_RESULT F_API FMOD_System_SetGeometrySettings (FMOD_SYSTEM *system, float maxworldsize); +FMOD_RESULT F_API FMOD_System_GetGeometrySettings (FMOD_SYSTEM *system, float *maxworldsize); +FMOD_RESULT F_API FMOD_System_LoadGeometry (FMOD_SYSTEM *system, const void *data, int datasize, FMOD_GEOMETRY **geometry); +FMOD_RESULT F_API FMOD_System_GetGeometryOcclusion (FMOD_SYSTEM *system, const FMOD_VECTOR *listener, const FMOD_VECTOR *source, float *direct, float *reverb); + +/* + Network functions. +*/ + +FMOD_RESULT F_API FMOD_System_SetNetworkProxy (FMOD_SYSTEM *system, const char *proxy); +FMOD_RESULT F_API FMOD_System_GetNetworkProxy (FMOD_SYSTEM *system, char *proxy, int proxylen); +FMOD_RESULT F_API FMOD_System_SetNetworkTimeout (FMOD_SYSTEM *system, int timeout); +FMOD_RESULT F_API FMOD_System_GetNetworkTimeout (FMOD_SYSTEM *system, int *timeout); + +/* + Userdata set/get. +*/ + +FMOD_RESULT F_API FMOD_System_SetUserData (FMOD_SYSTEM *system, void *userdata); +FMOD_RESULT F_API FMOD_System_GetUserData (FMOD_SYSTEM *system, void **userdata); + +/* + 'Sound' API +*/ + +FMOD_RESULT F_API FMOD_Sound_Release (FMOD_SOUND *sound); +FMOD_RESULT F_API FMOD_Sound_GetSystemObject (FMOD_SOUND *sound, FMOD_SYSTEM **system); + +/* + Standard sound manipulation functions. +*/ + +FMOD_RESULT F_API FMOD_Sound_Lock (FMOD_SOUND *sound, unsigned int offset, unsigned int length, void **ptr1, void **ptr2, unsigned int *len1, unsigned int *len2); +FMOD_RESULT F_API FMOD_Sound_Unlock (FMOD_SOUND *sound, void *ptr1, void *ptr2, unsigned int len1, unsigned int len2); +FMOD_RESULT F_API FMOD_Sound_SetDefaults (FMOD_SOUND *sound, float frequency, int priority); +FMOD_RESULT F_API FMOD_Sound_GetDefaults (FMOD_SOUND *sound, float *frequency, int *priority); +FMOD_RESULT F_API FMOD_Sound_Set3DMinMaxDistance (FMOD_SOUND *sound, float min, float max); +FMOD_RESULT F_API FMOD_Sound_Get3DMinMaxDistance (FMOD_SOUND *sound, float *min, float *max); +FMOD_RESULT F_API FMOD_Sound_Set3DConeSettings (FMOD_SOUND *sound, float insideconeangle, float outsideconeangle, float outsidevolume); +FMOD_RESULT F_API FMOD_Sound_Get3DConeSettings (FMOD_SOUND *sound, float *insideconeangle, float *outsideconeangle, float *outsidevolume); +FMOD_RESULT F_API FMOD_Sound_Set3DCustomRolloff (FMOD_SOUND *sound, FMOD_VECTOR *points, int numpoints); +FMOD_RESULT F_API FMOD_Sound_Get3DCustomRolloff (FMOD_SOUND *sound, FMOD_VECTOR **points, int *numpoints); +FMOD_RESULT F_API FMOD_Sound_GetSubSound (FMOD_SOUND *sound, int index, FMOD_SOUND **subsound); +FMOD_RESULT F_API FMOD_Sound_GetSubSoundParent (FMOD_SOUND *sound, FMOD_SOUND **parentsound); +FMOD_RESULT F_API FMOD_Sound_GetName (FMOD_SOUND *sound, char *name, int namelen); +FMOD_RESULT F_API FMOD_Sound_GetLength (FMOD_SOUND *sound, unsigned int *length, FMOD_TIMEUNIT lengthtype); +FMOD_RESULT F_API FMOD_Sound_GetFormat (FMOD_SOUND *sound, FMOD_SOUND_TYPE *type, FMOD_SOUND_FORMAT *format, int *channels, int *bits); +FMOD_RESULT F_API FMOD_Sound_GetNumSubSounds (FMOD_SOUND *sound, int *numsubsounds); +FMOD_RESULT F_API FMOD_Sound_GetNumTags (FMOD_SOUND *sound, int *numtags, int *numtagsupdated); +FMOD_RESULT F_API FMOD_Sound_GetTag (FMOD_SOUND *sound, const char *name, int index, FMOD_TAG *tag); +FMOD_RESULT F_API FMOD_Sound_GetOpenState (FMOD_SOUND *sound, FMOD_OPENSTATE *openstate, unsigned int *percentbuffered, FMOD_BOOL *starving, FMOD_BOOL *diskbusy); +FMOD_RESULT F_API FMOD_Sound_ReadData (FMOD_SOUND *sound, void *buffer, unsigned int lenbytes, unsigned int *read); +FMOD_RESULT F_API FMOD_Sound_SeekData (FMOD_SOUND *sound, unsigned int pcm); + +FMOD_RESULT F_API FMOD_Sound_SetSoundGroup (FMOD_SOUND *sound, FMOD_SOUNDGROUP *soundgroup); +FMOD_RESULT F_API FMOD_Sound_GetSoundGroup (FMOD_SOUND *sound, FMOD_SOUNDGROUP **soundgroup); + +/* + Synchronization point API. These points can come from markers embedded in wav files, and can also generate channel callbacks. +*/ + +FMOD_RESULT F_API FMOD_Sound_GetNumSyncPoints (FMOD_SOUND *sound, int *numsyncpoints); +FMOD_RESULT F_API FMOD_Sound_GetSyncPoint (FMOD_SOUND *sound, int index, FMOD_SYNCPOINT **point); +FMOD_RESULT F_API FMOD_Sound_GetSyncPointInfo (FMOD_SOUND *sound, FMOD_SYNCPOINT *point, char *name, int namelen, unsigned int *offset, FMOD_TIMEUNIT offsettype); +FMOD_RESULT F_API FMOD_Sound_AddSyncPoint (FMOD_SOUND *sound, unsigned int offset, FMOD_TIMEUNIT offsettype, const char *name, FMOD_SYNCPOINT **point); +FMOD_RESULT F_API FMOD_Sound_DeleteSyncPoint (FMOD_SOUND *sound, FMOD_SYNCPOINT *point); + +/* + Functions also in Channel class but here they are the 'default' to save having to change it in Channel all the time. +*/ + +FMOD_RESULT F_API FMOD_Sound_SetMode (FMOD_SOUND *sound, FMOD_MODE mode); +FMOD_RESULT F_API FMOD_Sound_GetMode (FMOD_SOUND *sound, FMOD_MODE *mode); +FMOD_RESULT F_API FMOD_Sound_SetLoopCount (FMOD_SOUND *sound, int loopcount); +FMOD_RESULT F_API FMOD_Sound_GetLoopCount (FMOD_SOUND *sound, int *loopcount); +FMOD_RESULT F_API FMOD_Sound_SetLoopPoints (FMOD_SOUND *sound, unsigned int loopstart, FMOD_TIMEUNIT loopstarttype, unsigned int loopend, FMOD_TIMEUNIT loopendtype); +FMOD_RESULT F_API FMOD_Sound_GetLoopPoints (FMOD_SOUND *sound, unsigned int *loopstart, FMOD_TIMEUNIT loopstarttype, unsigned int *loopend, FMOD_TIMEUNIT loopendtype); + +/* + For MOD/S3M/XM/IT/MID sequenced formats only. +*/ + +FMOD_RESULT F_API FMOD_Sound_GetMusicNumChannels (FMOD_SOUND *sound, int *numchannels); +FMOD_RESULT F_API FMOD_Sound_SetMusicChannelVolume (FMOD_SOUND *sound, int channel, float volume); +FMOD_RESULT F_API FMOD_Sound_GetMusicChannelVolume (FMOD_SOUND *sound, int channel, float *volume); +FMOD_RESULT F_API FMOD_Sound_SetMusicSpeed (FMOD_SOUND *sound, float speed); +FMOD_RESULT F_API FMOD_Sound_GetMusicSpeed (FMOD_SOUND *sound, float *speed); + +/* + Userdata set/get. +*/ + +FMOD_RESULT F_API FMOD_Sound_SetUserData (FMOD_SOUND *sound, void *userdata); +FMOD_RESULT F_API FMOD_Sound_GetUserData (FMOD_SOUND *sound, void **userdata); + +/* + 'Channel' API +*/ + +FMOD_RESULT F_API FMOD_Channel_GetSystemObject (FMOD_CHANNEL *channel, FMOD_SYSTEM **system); + +/* + General control functionality for Channels and ChannelGroups. +*/ + +FMOD_RESULT F_API FMOD_Channel_Stop (FMOD_CHANNEL *channel); +FMOD_RESULT F_API FMOD_Channel_SetPaused (FMOD_CHANNEL *channel, FMOD_BOOL paused); +FMOD_RESULT F_API FMOD_Channel_GetPaused (FMOD_CHANNEL *channel, FMOD_BOOL *paused); +FMOD_RESULT F_API FMOD_Channel_SetVolume (FMOD_CHANNEL *channel, float volume); +FMOD_RESULT F_API FMOD_Channel_GetVolume (FMOD_CHANNEL *channel, float *volume); +FMOD_RESULT F_API FMOD_Channel_SetVolumeRamp (FMOD_CHANNEL *channel, FMOD_BOOL ramp); +FMOD_RESULT F_API FMOD_Channel_GetVolumeRamp (FMOD_CHANNEL *channel, FMOD_BOOL *ramp); +FMOD_RESULT F_API FMOD_Channel_GetAudibility (FMOD_CHANNEL *channel, float *audibility); +FMOD_RESULT F_API FMOD_Channel_SetPitch (FMOD_CHANNEL *channel, float pitch); +FMOD_RESULT F_API FMOD_Channel_GetPitch (FMOD_CHANNEL *channel, float *pitch); +FMOD_RESULT F_API FMOD_Channel_SetMute (FMOD_CHANNEL *channel, FMOD_BOOL mute); +FMOD_RESULT F_API FMOD_Channel_GetMute (FMOD_CHANNEL *channel, FMOD_BOOL *mute); +FMOD_RESULT F_API FMOD_Channel_SetReverbProperties (FMOD_CHANNEL *channel, int instance, float wet); +FMOD_RESULT F_API FMOD_Channel_GetReverbProperties (FMOD_CHANNEL *channel, int instance, float *wet); +FMOD_RESULT F_API FMOD_Channel_SetLowPassGain (FMOD_CHANNEL *channel, float gain); +FMOD_RESULT F_API FMOD_Channel_GetLowPassGain (FMOD_CHANNEL *channel, float *gain); +FMOD_RESULT F_API FMOD_Channel_SetMode (FMOD_CHANNEL *channel, FMOD_MODE mode); +FMOD_RESULT F_API FMOD_Channel_GetMode (FMOD_CHANNEL *channel, FMOD_MODE *mode); +FMOD_RESULT F_API FMOD_Channel_SetCallback (FMOD_CHANNEL *channel, FMOD_CHANNELCONTROL_CALLBACK callback); +FMOD_RESULT F_API FMOD_Channel_IsPlaying (FMOD_CHANNEL *channel, FMOD_BOOL *isplaying); + +/* + Note all 'set' functions alter a final matrix, this is why the only get function is getMixMatrix, to avoid other get functions returning incorrect/obsolete values. +*/ + +FMOD_RESULT F_API FMOD_Channel_SetPan (FMOD_CHANNEL *channel, float pan); +FMOD_RESULT F_API FMOD_Channel_SetMixLevelsOutput (FMOD_CHANNEL *channel, float frontleft, float frontright, float center, float lfe, float surroundleft, float surroundright, float backleft, float backright); +FMOD_RESULT F_API FMOD_Channel_SetMixLevelsInput (FMOD_CHANNEL *channel, float *levels, int numlevels); +FMOD_RESULT F_API FMOD_Channel_SetMixMatrix (FMOD_CHANNEL *channel, float *matrix, int outchannels, int inchannels, int inchannel_hop); +FMOD_RESULT F_API FMOD_Channel_GetMixMatrix (FMOD_CHANNEL *channel, float *matrix, int *outchannels, int *inchannels, int inchannel_hop); + +/* + Clock based functionality. +*/ + +FMOD_RESULT F_API FMOD_Channel_GetDSPClock (FMOD_CHANNEL *channel, unsigned long long *dspclock, unsigned long long *parentclock); +FMOD_RESULT F_API FMOD_Channel_SetDelay (FMOD_CHANNEL *channel, unsigned long long dspclock_start, unsigned long long dspclock_end, FMOD_BOOL stopchannels); +FMOD_RESULT F_API FMOD_Channel_GetDelay (FMOD_CHANNEL *channel, unsigned long long *dspclock_start, unsigned long long *dspclock_end, FMOD_BOOL *stopchannels); +FMOD_RESULT F_API FMOD_Channel_AddFadePoint (FMOD_CHANNEL *channel, unsigned long long dspclock, float volume); +FMOD_RESULT F_API FMOD_Channel_SetFadePointRamp (FMOD_CHANNEL *channel, unsigned long long dspclock, float volume); +FMOD_RESULT F_API FMOD_Channel_RemoveFadePoints (FMOD_CHANNEL *channel, unsigned long long dspclock_start, unsigned long long dspclock_end); +FMOD_RESULT F_API FMOD_Channel_GetFadePoints (FMOD_CHANNEL *channel, unsigned int *numpoints, unsigned long long *point_dspclock, float *point_volume); + +/* + DSP effects. +*/ + +FMOD_RESULT F_API FMOD_Channel_GetDSP (FMOD_CHANNEL *channel, int index, FMOD_DSP **dsp); +FMOD_RESULT F_API FMOD_Channel_AddDSP (FMOD_CHANNEL *channel, int index, FMOD_DSP *dsp); +FMOD_RESULT F_API FMOD_Channel_RemoveDSP (FMOD_CHANNEL *channel, FMOD_DSP *dsp); +FMOD_RESULT F_API FMOD_Channel_GetNumDSPs (FMOD_CHANNEL *channel, int *numdsps); +FMOD_RESULT F_API FMOD_Channel_SetDSPIndex (FMOD_CHANNEL *channel, FMOD_DSP *dsp, int index); +FMOD_RESULT F_API FMOD_Channel_GetDSPIndex (FMOD_CHANNEL *channel, FMOD_DSP *dsp, int *index); +FMOD_RESULT F_API FMOD_Channel_OverridePanDSP (FMOD_CHANNEL *channel, FMOD_DSP *pan); + +/* + 3D functionality. +*/ + +FMOD_RESULT F_API FMOD_Channel_Set3DAttributes (FMOD_CHANNEL *channel, const FMOD_VECTOR *pos, const FMOD_VECTOR *vel, const FMOD_VECTOR *alt_pan_pos); +FMOD_RESULT F_API FMOD_Channel_Get3DAttributes (FMOD_CHANNEL *channel, FMOD_VECTOR *pos, FMOD_VECTOR *vel, FMOD_VECTOR *alt_pan_pos); +FMOD_RESULT F_API FMOD_Channel_Set3DMinMaxDistance (FMOD_CHANNEL *channel, float mindistance, float maxdistance); +FMOD_RESULT F_API FMOD_Channel_Get3DMinMaxDistance (FMOD_CHANNEL *channel, float *mindistance, float *maxdistance); +FMOD_RESULT F_API FMOD_Channel_Set3DConeSettings (FMOD_CHANNEL *channel, float insideconeangle, float outsideconeangle, float outsidevolume); +FMOD_RESULT F_API FMOD_Channel_Get3DConeSettings (FMOD_CHANNEL *channel, float *insideconeangle, float *outsideconeangle, float *outsidevolume); +FMOD_RESULT F_API FMOD_Channel_Set3DConeOrientation (FMOD_CHANNEL *channel, FMOD_VECTOR *orientation); +FMOD_RESULT F_API FMOD_Channel_Get3DConeOrientation (FMOD_CHANNEL *channel, FMOD_VECTOR *orientation); +FMOD_RESULT F_API FMOD_Channel_Set3DCustomRolloff (FMOD_CHANNEL *channel, FMOD_VECTOR *points, int numpoints); +FMOD_RESULT F_API FMOD_Channel_Get3DCustomRolloff (FMOD_CHANNEL *channel, FMOD_VECTOR **points, int *numpoints); +FMOD_RESULT F_API FMOD_Channel_Set3DOcclusion (FMOD_CHANNEL *channel, float directocclusion, float reverbocclusion); +FMOD_RESULT F_API FMOD_Channel_Get3DOcclusion (FMOD_CHANNEL *channel, float *directocclusion, float *reverbocclusion); +FMOD_RESULT F_API FMOD_Channel_Set3DSpread (FMOD_CHANNEL *channel, float angle); +FMOD_RESULT F_API FMOD_Channel_Get3DSpread (FMOD_CHANNEL *channel, float *angle); +FMOD_RESULT F_API FMOD_Channel_Set3DLevel (FMOD_CHANNEL *channel, float level); +FMOD_RESULT F_API FMOD_Channel_Get3DLevel (FMOD_CHANNEL *channel, float *level); +FMOD_RESULT F_API FMOD_Channel_Set3DDopplerLevel (FMOD_CHANNEL *channel, float level); +FMOD_RESULT F_API FMOD_Channel_Get3DDopplerLevel (FMOD_CHANNEL *channel, float *level); +FMOD_RESULT F_API FMOD_Channel_Set3DDistanceFilter (FMOD_CHANNEL *channel, FMOD_BOOL custom, float customLevel, float centerFreq); +FMOD_RESULT F_API FMOD_Channel_Get3DDistanceFilter (FMOD_CHANNEL *channel, FMOD_BOOL *custom, float *customLevel, float *centerFreq); + +/* + Userdata set/get. +*/ + +FMOD_RESULT F_API FMOD_Channel_SetUserData (FMOD_CHANNEL *channel, void *userdata); +FMOD_RESULT F_API FMOD_Channel_GetUserData (FMOD_CHANNEL *channel, void **userdata); + +/* + Channel specific control functionality. +*/ + +FMOD_RESULT F_API FMOD_Channel_SetFrequency (FMOD_CHANNEL *channel, float frequency); +FMOD_RESULT F_API FMOD_Channel_GetFrequency (FMOD_CHANNEL *channel, float *frequency); +FMOD_RESULT F_API FMOD_Channel_SetPriority (FMOD_CHANNEL *channel, int priority); +FMOD_RESULT F_API FMOD_Channel_GetPriority (FMOD_CHANNEL *channel, int *priority); +FMOD_RESULT F_API FMOD_Channel_SetPosition (FMOD_CHANNEL *channel, unsigned int position, FMOD_TIMEUNIT postype); +FMOD_RESULT F_API FMOD_Channel_GetPosition (FMOD_CHANNEL *channel, unsigned int *position, FMOD_TIMEUNIT postype); +FMOD_RESULT F_API FMOD_Channel_SetChannelGroup (FMOD_CHANNEL *channel, FMOD_CHANNELGROUP *channelgroup); +FMOD_RESULT F_API FMOD_Channel_GetChannelGroup (FMOD_CHANNEL *channel, FMOD_CHANNELGROUP **channelgroup); +FMOD_RESULT F_API FMOD_Channel_SetLoopCount (FMOD_CHANNEL *channel, int loopcount); +FMOD_RESULT F_API FMOD_Channel_GetLoopCount (FMOD_CHANNEL *channel, int *loopcount); +FMOD_RESULT F_API FMOD_Channel_SetLoopPoints (FMOD_CHANNEL *channel, unsigned int loopstart, FMOD_TIMEUNIT loopstarttype, unsigned int loopend, FMOD_TIMEUNIT loopendtype); +FMOD_RESULT F_API FMOD_Channel_GetLoopPoints (FMOD_CHANNEL *channel, unsigned int *loopstart, FMOD_TIMEUNIT loopstarttype, unsigned int *loopend, FMOD_TIMEUNIT loopendtype); + +/* + Information only functions. +*/ + +FMOD_RESULT F_API FMOD_Channel_IsVirtual (FMOD_CHANNEL *channel, FMOD_BOOL *isvirtual); +FMOD_RESULT F_API FMOD_Channel_GetCurrentSound (FMOD_CHANNEL *channel, FMOD_SOUND **sound); +FMOD_RESULT F_API FMOD_Channel_GetIndex (FMOD_CHANNEL *channel, int *index); + +/* + 'ChannelGroup' API +*/ + +FMOD_RESULT F_API FMOD_ChannelGroup_GetSystemObject (FMOD_CHANNELGROUP *channelgroup, FMOD_SYSTEM **system); + +/* + General control functionality for Channels and ChannelGroups. +*/ + +FMOD_RESULT F_API FMOD_ChannelGroup_Stop (FMOD_CHANNELGROUP *channelgroup); +FMOD_RESULT F_API FMOD_ChannelGroup_SetPaused (FMOD_CHANNELGROUP *channelgroup, FMOD_BOOL paused); +FMOD_RESULT F_API FMOD_ChannelGroup_GetPaused (FMOD_CHANNELGROUP *channelgroup, FMOD_BOOL *paused); +FMOD_RESULT F_API FMOD_ChannelGroup_SetVolume (FMOD_CHANNELGROUP *channelgroup, float volume); +FMOD_RESULT F_API FMOD_ChannelGroup_GetVolume (FMOD_CHANNELGROUP *channelgroup, float *volume); +FMOD_RESULT F_API FMOD_ChannelGroup_SetVolumeRamp (FMOD_CHANNELGROUP *channelgroup, FMOD_BOOL ramp); +FMOD_RESULT F_API FMOD_ChannelGroup_GetVolumeRamp (FMOD_CHANNELGROUP *channelgroup, FMOD_BOOL *ramp); +FMOD_RESULT F_API FMOD_ChannelGroup_GetAudibility (FMOD_CHANNELGROUP *channelgroup, float *audibility); +FMOD_RESULT F_API FMOD_ChannelGroup_SetPitch (FMOD_CHANNELGROUP *channelgroup, float pitch); +FMOD_RESULT F_API FMOD_ChannelGroup_GetPitch (FMOD_CHANNELGROUP *channelgroup, float *pitch); +FMOD_RESULT F_API FMOD_ChannelGroup_SetMute (FMOD_CHANNELGROUP *channelgroup, FMOD_BOOL mute); +FMOD_RESULT F_API FMOD_ChannelGroup_GetMute (FMOD_CHANNELGROUP *channelgroup, FMOD_BOOL *mute); +FMOD_RESULT F_API FMOD_ChannelGroup_SetReverbProperties (FMOD_CHANNELGROUP *channelgroup, int instance, float wet); +FMOD_RESULT F_API FMOD_ChannelGroup_GetReverbProperties (FMOD_CHANNELGROUP *channelgroup, int instance, float *wet); +FMOD_RESULT F_API FMOD_ChannelGroup_SetLowPassGain (FMOD_CHANNELGROUP *channelgroup, float gain); +FMOD_RESULT F_API FMOD_ChannelGroup_GetLowPassGain (FMOD_CHANNELGROUP *channelgroup, float *gain); +FMOD_RESULT F_API FMOD_ChannelGroup_SetMode (FMOD_CHANNELGROUP *channelgroup, FMOD_MODE mode); +FMOD_RESULT F_API FMOD_ChannelGroup_GetMode (FMOD_CHANNELGROUP *channelgroup, FMOD_MODE *mode); +FMOD_RESULT F_API FMOD_ChannelGroup_SetCallback (FMOD_CHANNELGROUP *channelgroup, FMOD_CHANNELCONTROL_CALLBACK callback); +FMOD_RESULT F_API FMOD_ChannelGroup_IsPlaying (FMOD_CHANNELGROUP *channelgroup, FMOD_BOOL *isplaying); + +/* + Note all 'set' functions alter a final matrix, this is why the only get function is getMixMatrix, to avoid other get functions returning incorrect/obsolete values. +*/ + +FMOD_RESULT F_API FMOD_ChannelGroup_SetPan (FMOD_CHANNELGROUP *channelgroup, float pan); +FMOD_RESULT F_API FMOD_ChannelGroup_SetMixLevelsOutput (FMOD_CHANNELGROUP *channelgroup, float frontleft, float frontright, float center, float lfe, float surroundleft, float surroundright, float backleft, float backright); +FMOD_RESULT F_API FMOD_ChannelGroup_SetMixLevelsInput (FMOD_CHANNELGROUP *channelgroup, float *levels, int numlevels); +FMOD_RESULT F_API FMOD_ChannelGroup_SetMixMatrix (FMOD_CHANNELGROUP *channelgroup, float *matrix, int outchannels, int inchannels, int inchannel_hop); +FMOD_RESULT F_API FMOD_ChannelGroup_GetMixMatrix (FMOD_CHANNELGROUP *channelgroup, float *matrix, int *outchannels, int *inchannels, int inchannel_hop); + +/* + Clock based functionality. +*/ + +FMOD_RESULT F_API FMOD_ChannelGroup_GetDSPClock (FMOD_CHANNELGROUP *channelgroup, unsigned long long *dspclock, unsigned long long *parentclock); +FMOD_RESULT F_API FMOD_ChannelGroup_SetDelay (FMOD_CHANNELGROUP *channelgroup, unsigned long long dspclock_start, unsigned long long dspclock_end, FMOD_BOOL stopchannels); +FMOD_RESULT F_API FMOD_ChannelGroup_GetDelay (FMOD_CHANNELGROUP *channelgroup, unsigned long long *dspclock_start, unsigned long long *dspclock_end, FMOD_BOOL *stopchannels); +FMOD_RESULT F_API FMOD_ChannelGroup_AddFadePoint (FMOD_CHANNELGROUP *channelgroup, unsigned long long dspclock, float volume); +FMOD_RESULT F_API FMOD_ChannelGroup_SetFadePointRamp (FMOD_CHANNELGROUP *channelgroup, unsigned long long dspclock, float volume); +FMOD_RESULT F_API FMOD_ChannelGroup_RemoveFadePoints (FMOD_CHANNELGROUP *channelgroup, unsigned long long dspclock_start, unsigned long long dspclock_end); +FMOD_RESULT F_API FMOD_ChannelGroup_GetFadePoints (FMOD_CHANNELGROUP *channelgroup, unsigned int *numpoints, unsigned long long *point_dspclock, float *point_volume); + +/* + DSP effects. +*/ + +FMOD_RESULT F_API FMOD_ChannelGroup_GetDSP (FMOD_CHANNELGROUP *channelgroup, int index, FMOD_DSP **dsp); +FMOD_RESULT F_API FMOD_ChannelGroup_AddDSP (FMOD_CHANNELGROUP *channelgroup, int index, FMOD_DSP *dsp); +FMOD_RESULT F_API FMOD_ChannelGroup_RemoveDSP (FMOD_CHANNELGROUP *channelgroup, FMOD_DSP *dsp); +FMOD_RESULT F_API FMOD_ChannelGroup_GetNumDSPs (FMOD_CHANNELGROUP *channelgroup, int *numdsps); +FMOD_RESULT F_API FMOD_ChannelGroup_SetDSPIndex (FMOD_CHANNELGROUP *channelgroup, FMOD_DSP *dsp, int index); +FMOD_RESULT F_API FMOD_ChannelGroup_GetDSPIndex (FMOD_CHANNELGROUP *channelgroup, FMOD_DSP *dsp, int *index); +FMOD_RESULT F_API FMOD_ChannelGroup_OverridePanDSP (FMOD_CHANNELGROUP *channelgroup, FMOD_DSP *pan); + +/* + 3D functionality. +*/ + +FMOD_RESULT F_API FMOD_ChannelGroup_Set3DAttributes (FMOD_CHANNELGROUP *channelgroup, const FMOD_VECTOR *pos, const FMOD_VECTOR *vel, const FMOD_VECTOR *alt_pan_pos); +FMOD_RESULT F_API FMOD_ChannelGroup_Get3DAttributes (FMOD_CHANNELGROUP *channelgroup, FMOD_VECTOR *pos, FMOD_VECTOR *vel, FMOD_VECTOR *alt_pan_pos); +FMOD_RESULT F_API FMOD_ChannelGroup_Set3DMinMaxDistance (FMOD_CHANNELGROUP *channelgroup, float mindistance, float maxdistance); +FMOD_RESULT F_API FMOD_ChannelGroup_Get3DMinMaxDistance (FMOD_CHANNELGROUP *channelgroup, float *mindistance, float *maxdistance); +FMOD_RESULT F_API FMOD_ChannelGroup_Set3DConeSettings (FMOD_CHANNELGROUP *channelgroup, float insideconeangle, float outsideconeangle, float outsidevolume); +FMOD_RESULT F_API FMOD_ChannelGroup_Get3DConeSettings (FMOD_CHANNELGROUP *channelgroup, float *insideconeangle, float *outsideconeangle, float *outsidevolume); +FMOD_RESULT F_API FMOD_ChannelGroup_Set3DConeOrientation(FMOD_CHANNELGROUP *channelgroup, FMOD_VECTOR *orientation); +FMOD_RESULT F_API FMOD_ChannelGroup_Get3DConeOrientation(FMOD_CHANNELGROUP *channelgroup, FMOD_VECTOR *orientation); +FMOD_RESULT F_API FMOD_ChannelGroup_Set3DCustomRolloff (FMOD_CHANNELGROUP *channelgroup, FMOD_VECTOR *points, int numpoints); +FMOD_RESULT F_API FMOD_ChannelGroup_Get3DCustomRolloff (FMOD_CHANNELGROUP *channelgroup, FMOD_VECTOR **points, int *numpoints); +FMOD_RESULT F_API FMOD_ChannelGroup_Set3DOcclusion (FMOD_CHANNELGROUP *channelgroup, float directocclusion, float reverbocclusion); +FMOD_RESULT F_API FMOD_ChannelGroup_Get3DOcclusion (FMOD_CHANNELGROUP *channelgroup, float *directocclusion, float *reverbocclusion); +FMOD_RESULT F_API FMOD_ChannelGroup_Set3DSpread (FMOD_CHANNELGROUP *channelgroup, float angle); +FMOD_RESULT F_API FMOD_ChannelGroup_Get3DSpread (FMOD_CHANNELGROUP *channelgroup, float *angle); +FMOD_RESULT F_API FMOD_ChannelGroup_Set3DLevel (FMOD_CHANNELGROUP *channelgroup, float level); +FMOD_RESULT F_API FMOD_ChannelGroup_Get3DLevel (FMOD_CHANNELGROUP *channelgroup, float *level); +FMOD_RESULT F_API FMOD_ChannelGroup_Set3DDopplerLevel (FMOD_CHANNELGROUP *channelgroup, float level); +FMOD_RESULT F_API FMOD_ChannelGroup_Get3DDopplerLevel (FMOD_CHANNELGROUP *channelgroup, float *level); +FMOD_RESULT F_API FMOD_ChannelGroup_Set3DDistanceFilter (FMOD_CHANNELGROUP *channelgroup, FMOD_BOOL custom, float customLevel, float centerFreq); +FMOD_RESULT F_API FMOD_ChannelGroup_Get3DDistanceFilter (FMOD_CHANNELGROUP *channelgroup, FMOD_BOOL *custom, float *customLevel, float *centerFreq); + +/* + Userdata set/get. +*/ + +FMOD_RESULT F_API FMOD_ChannelGroup_SetUserData (FMOD_CHANNELGROUP *channelgroup, void *userdata); +FMOD_RESULT F_API FMOD_ChannelGroup_GetUserData (FMOD_CHANNELGROUP *channelgroup, void **userdata); + +FMOD_RESULT F_API FMOD_ChannelGroup_Release (FMOD_CHANNELGROUP *channelgroup); + +/* + Nested channel groups. +*/ + +FMOD_RESULT F_API FMOD_ChannelGroup_AddGroup (FMOD_CHANNELGROUP *channelgroup, FMOD_CHANNELGROUP *group, FMOD_BOOL propagatedspclock, FMOD_DSPCONNECTION **connection); +FMOD_RESULT F_API FMOD_ChannelGroup_GetNumGroups (FMOD_CHANNELGROUP *channelgroup, int *numgroups); +FMOD_RESULT F_API FMOD_ChannelGroup_GetGroup (FMOD_CHANNELGROUP *channelgroup, int index, FMOD_CHANNELGROUP **group); +FMOD_RESULT F_API FMOD_ChannelGroup_GetParentGroup (FMOD_CHANNELGROUP *channelgroup, FMOD_CHANNELGROUP **group); + +/* + Information only functions. +*/ + +FMOD_RESULT F_API FMOD_ChannelGroup_GetName (FMOD_CHANNELGROUP *channelgroup, char *name, int namelen); +FMOD_RESULT F_API FMOD_ChannelGroup_GetNumChannels (FMOD_CHANNELGROUP *channelgroup, int *numchannels); +FMOD_RESULT F_API FMOD_ChannelGroup_GetChannel (FMOD_CHANNELGROUP *channelgroup, int index, FMOD_CHANNEL **channel); + +/* + 'SoundGroup' API +*/ + +FMOD_RESULT F_API FMOD_SoundGroup_Release (FMOD_SOUNDGROUP *soundgroup); +FMOD_RESULT F_API FMOD_SoundGroup_GetSystemObject (FMOD_SOUNDGROUP *soundgroup, FMOD_SYSTEM **system); + +/* + SoundGroup control functions. +*/ + +FMOD_RESULT F_API FMOD_SoundGroup_SetMaxAudible (FMOD_SOUNDGROUP *soundgroup, int maxaudible); +FMOD_RESULT F_API FMOD_SoundGroup_GetMaxAudible (FMOD_SOUNDGROUP *soundgroup, int *maxaudible); +FMOD_RESULT F_API FMOD_SoundGroup_SetMaxAudibleBehavior (FMOD_SOUNDGROUP *soundgroup, FMOD_SOUNDGROUP_BEHAVIOR behavior); +FMOD_RESULT F_API FMOD_SoundGroup_GetMaxAudibleBehavior (FMOD_SOUNDGROUP *soundgroup, FMOD_SOUNDGROUP_BEHAVIOR *behavior); +FMOD_RESULT F_API FMOD_SoundGroup_SetMuteFadeSpeed (FMOD_SOUNDGROUP *soundgroup, float speed); +FMOD_RESULT F_API FMOD_SoundGroup_GetMuteFadeSpeed (FMOD_SOUNDGROUP *soundgroup, float *speed); +FMOD_RESULT F_API FMOD_SoundGroup_SetVolume (FMOD_SOUNDGROUP *soundgroup, float volume); +FMOD_RESULT F_API FMOD_SoundGroup_GetVolume (FMOD_SOUNDGROUP *soundgroup, float *volume); +FMOD_RESULT F_API FMOD_SoundGroup_Stop (FMOD_SOUNDGROUP *soundgroup); + +/* + Information only functions. +*/ + +FMOD_RESULT F_API FMOD_SoundGroup_GetName (FMOD_SOUNDGROUP *soundgroup, char *name, int namelen); +FMOD_RESULT F_API FMOD_SoundGroup_GetNumSounds (FMOD_SOUNDGROUP *soundgroup, int *numsounds); +FMOD_RESULT F_API FMOD_SoundGroup_GetSound (FMOD_SOUNDGROUP *soundgroup, int index, FMOD_SOUND **sound); +FMOD_RESULT F_API FMOD_SoundGroup_GetNumPlaying (FMOD_SOUNDGROUP *soundgroup, int *numplaying); + +/* + Userdata set/get. +*/ + +FMOD_RESULT F_API FMOD_SoundGroup_SetUserData (FMOD_SOUNDGROUP *soundgroup, void *userdata); +FMOD_RESULT F_API FMOD_SoundGroup_GetUserData (FMOD_SOUNDGROUP *soundgroup, void **userdata); + +/* + 'DSP' API +*/ + +FMOD_RESULT F_API FMOD_DSP_Release (FMOD_DSP *dsp); +FMOD_RESULT F_API FMOD_DSP_GetSystemObject (FMOD_DSP *dsp, FMOD_SYSTEM **system); + +/* + Connection / disconnection / input and output enumeration. +*/ + +FMOD_RESULT F_API FMOD_DSP_AddInput (FMOD_DSP *dsp, FMOD_DSP *input, FMOD_DSPCONNECTION **connection, FMOD_DSPCONNECTION_TYPE type); +FMOD_RESULT F_API FMOD_DSP_DisconnectFrom (FMOD_DSP *dsp, FMOD_DSP *target, FMOD_DSPCONNECTION *connection); +FMOD_RESULT F_API FMOD_DSP_DisconnectAll (FMOD_DSP *dsp, FMOD_BOOL inputs, FMOD_BOOL outputs); +FMOD_RESULT F_API FMOD_DSP_GetNumInputs (FMOD_DSP *dsp, int *numinputs); +FMOD_RESULT F_API FMOD_DSP_GetNumOutputs (FMOD_DSP *dsp, int *numoutputs); +FMOD_RESULT F_API FMOD_DSP_GetInput (FMOD_DSP *dsp, int index, FMOD_DSP **input, FMOD_DSPCONNECTION **inputconnection); +FMOD_RESULT F_API FMOD_DSP_GetOutput (FMOD_DSP *dsp, int index, FMOD_DSP **output, FMOD_DSPCONNECTION **outputconnection); + +/* + DSP unit control. +*/ + +FMOD_RESULT F_API FMOD_DSP_SetActive (FMOD_DSP *dsp, FMOD_BOOL active); +FMOD_RESULT F_API FMOD_DSP_GetActive (FMOD_DSP *dsp, FMOD_BOOL *active); +FMOD_RESULT F_API FMOD_DSP_SetBypass (FMOD_DSP *dsp, FMOD_BOOL bypass); +FMOD_RESULT F_API FMOD_DSP_GetBypass (FMOD_DSP *dsp, FMOD_BOOL *bypass); +FMOD_RESULT F_API FMOD_DSP_SetWetDryMix (FMOD_DSP *dsp, float prewet, float postwet, float dry); +FMOD_RESULT F_API FMOD_DSP_GetWetDryMix (FMOD_DSP *dsp, float *prewet, float *postwet, float *dry); +FMOD_RESULT F_API FMOD_DSP_SetChannelFormat (FMOD_DSP *dsp, FMOD_CHANNELMASK channelmask, int numchannels, FMOD_SPEAKERMODE source_speakermode); +FMOD_RESULT F_API FMOD_DSP_GetChannelFormat (FMOD_DSP *dsp, FMOD_CHANNELMASK *channelmask, int *numchannels, FMOD_SPEAKERMODE *source_speakermode); +FMOD_RESULT F_API FMOD_DSP_GetOutputChannelFormat (FMOD_DSP *dsp, FMOD_CHANNELMASK inmask, int inchannels, FMOD_SPEAKERMODE inspeakermode, FMOD_CHANNELMASK *outmask, int *outchannels, FMOD_SPEAKERMODE *outspeakermode); +FMOD_RESULT F_API FMOD_DSP_Reset (FMOD_DSP *dsp); + +/* + DSP parameter control. +*/ + +FMOD_RESULT F_API FMOD_DSP_SetParameterFloat (FMOD_DSP *dsp, int index, float value); +FMOD_RESULT F_API FMOD_DSP_SetParameterInt (FMOD_DSP *dsp, int index, int value); +FMOD_RESULT F_API FMOD_DSP_SetParameterBool (FMOD_DSP *dsp, int index, FMOD_BOOL value); +FMOD_RESULT F_API FMOD_DSP_SetParameterData (FMOD_DSP *dsp, int index, void *data, unsigned int length); +FMOD_RESULT F_API FMOD_DSP_GetParameterFloat (FMOD_DSP *dsp, int index, float *value, char *valuestr, int valuestrlen); +FMOD_RESULT F_API FMOD_DSP_GetParameterInt (FMOD_DSP *dsp, int index, int *value, char *valuestr, int valuestrlen); +FMOD_RESULT F_API FMOD_DSP_GetParameterBool (FMOD_DSP *dsp, int index, FMOD_BOOL *value, char *valuestr, int valuestrlen); +FMOD_RESULT F_API FMOD_DSP_GetParameterData (FMOD_DSP *dsp, int index, void **data, unsigned int *length, char *valuestr, int valuestrlen); +FMOD_RESULT F_API FMOD_DSP_GetNumParameters (FMOD_DSP *dsp, int *numparams); +FMOD_RESULT F_API FMOD_DSP_GetParameterInfo (FMOD_DSP *dsp, int index, FMOD_DSP_PARAMETER_DESC **desc); +FMOD_RESULT F_API FMOD_DSP_GetDataParameterIndex (FMOD_DSP *dsp, int datatype, int *index); +FMOD_RESULT F_API FMOD_DSP_ShowConfigDialog (FMOD_DSP *dsp, void *hwnd, FMOD_BOOL show); + +/* + DSP attributes. +*/ + +FMOD_RESULT F_API FMOD_DSP_GetInfo (FMOD_DSP *dsp, char *name, unsigned int *version, int *channels, int *configwidth, int *configheight); +FMOD_RESULT F_API FMOD_DSP_GetType (FMOD_DSP *dsp, FMOD_DSP_TYPE *type); +FMOD_RESULT F_API FMOD_DSP_GetIdle (FMOD_DSP *dsp, FMOD_BOOL *idle); + +/* + Userdata set/get. +*/ + +FMOD_RESULT F_API FMOD_DSP_SetUserData (FMOD_DSP *dsp, void *userdata); +FMOD_RESULT F_API FMOD_DSP_GetUserData (FMOD_DSP *dsp, void **userdata); + +/* + Metering. +*/ + +FMOD_RESULT F_API FMOD_DSP_SetMeteringEnabled (FMOD_DSP *dsp, FMOD_BOOL inputEnabled, FMOD_BOOL outputEnabled); +FMOD_RESULT F_API FMOD_DSP_GetMeteringEnabled (FMOD_DSP *dsp, FMOD_BOOL *inputEnabled, FMOD_BOOL *outputEnabled); +FMOD_RESULT F_API FMOD_DSP_GetMeteringInfo (FMOD_DSP *dsp, FMOD_DSP_METERING_INFO *inputInfo, FMOD_DSP_METERING_INFO *outputInfo); + +/* + 'DSPConnection' API +*/ + +FMOD_RESULT F_API FMOD_DSPConnection_GetInput (FMOD_DSPCONNECTION *dspconnection, FMOD_DSP **input); +FMOD_RESULT F_API FMOD_DSPConnection_GetOutput (FMOD_DSPCONNECTION *dspconnection, FMOD_DSP **output); +FMOD_RESULT F_API FMOD_DSPConnection_SetMix (FMOD_DSPCONNECTION *dspconnection, float volume); +FMOD_RESULT F_API FMOD_DSPConnection_GetMix (FMOD_DSPCONNECTION *dspconnection, float *volume); +FMOD_RESULT F_API FMOD_DSPConnection_SetMixMatrix (FMOD_DSPCONNECTION *dspconnection, float *matrix, int outchannels, int inchannels, int inchannel_hop); +FMOD_RESULT F_API FMOD_DSPConnection_GetMixMatrix (FMOD_DSPCONNECTION *dspconnection, float *matrix, int *outchannels, int *inchannels, int inchannel_hop); +FMOD_RESULT F_API FMOD_DSPConnection_GetType (FMOD_DSPCONNECTION *dspconnection, FMOD_DSPCONNECTION_TYPE *type); + +/* + Userdata set/get. +*/ + +FMOD_RESULT F_API FMOD_DSPConnection_SetUserData (FMOD_DSPCONNECTION *dspconnection, void *userdata); +FMOD_RESULT F_API FMOD_DSPConnection_GetUserData (FMOD_DSPCONNECTION *dspconnection, void **userdata); + +/* + 'Geometry' API +*/ + +FMOD_RESULT F_API FMOD_Geometry_Release (FMOD_GEOMETRY *geometry); + +/* + Polygon manipulation. +*/ + +FMOD_RESULT F_API FMOD_Geometry_AddPolygon (FMOD_GEOMETRY *geometry, float directocclusion, float reverbocclusion, FMOD_BOOL doublesided, int numvertices, const FMOD_VECTOR *vertices, int *polygonindex); +FMOD_RESULT F_API FMOD_Geometry_GetNumPolygons (FMOD_GEOMETRY *geometry, int *numpolygons); +FMOD_RESULT F_API FMOD_Geometry_GetMaxPolygons (FMOD_GEOMETRY *geometry, int *maxpolygons, int *maxvertices); +FMOD_RESULT F_API FMOD_Geometry_GetPolygonNumVertices (FMOD_GEOMETRY *geometry, int index, int *numvertices); +FMOD_RESULT F_API FMOD_Geometry_SetPolygonVertex (FMOD_GEOMETRY *geometry, int index, int vertexindex, const FMOD_VECTOR *vertex); +FMOD_RESULT F_API FMOD_Geometry_GetPolygonVertex (FMOD_GEOMETRY *geometry, int index, int vertexindex, FMOD_VECTOR *vertex); +FMOD_RESULT F_API FMOD_Geometry_SetPolygonAttributes (FMOD_GEOMETRY *geometry, int index, float directocclusion, float reverbocclusion, FMOD_BOOL doublesided); +FMOD_RESULT F_API FMOD_Geometry_GetPolygonAttributes (FMOD_GEOMETRY *geometry, int index, float *directocclusion, float *reverbocclusion, FMOD_BOOL *doublesided); + +/* + Object manipulation. +*/ + +FMOD_RESULT F_API FMOD_Geometry_SetActive (FMOD_GEOMETRY *geometry, FMOD_BOOL active); +FMOD_RESULT F_API FMOD_Geometry_GetActive (FMOD_GEOMETRY *geometry, FMOD_BOOL *active); +FMOD_RESULT F_API FMOD_Geometry_SetRotation (FMOD_GEOMETRY *geometry, const FMOD_VECTOR *forward, const FMOD_VECTOR *up); +FMOD_RESULT F_API FMOD_Geometry_GetRotation (FMOD_GEOMETRY *geometry, FMOD_VECTOR *forward, FMOD_VECTOR *up); +FMOD_RESULT F_API FMOD_Geometry_SetPosition (FMOD_GEOMETRY *geometry, const FMOD_VECTOR *position); +FMOD_RESULT F_API FMOD_Geometry_GetPosition (FMOD_GEOMETRY *geometry, FMOD_VECTOR *position); +FMOD_RESULT F_API FMOD_Geometry_SetScale (FMOD_GEOMETRY *geometry, const FMOD_VECTOR *scale); +FMOD_RESULT F_API FMOD_Geometry_GetScale (FMOD_GEOMETRY *geometry, FMOD_VECTOR *scale); +FMOD_RESULT F_API FMOD_Geometry_Save (FMOD_GEOMETRY *geometry, void *data, int *datasize); + +/* + Userdata set/get. +*/ + +FMOD_RESULT F_API FMOD_Geometry_SetUserData (FMOD_GEOMETRY *geometry, void *userdata); +FMOD_RESULT F_API FMOD_Geometry_GetUserData (FMOD_GEOMETRY *geometry, void **userdata); + +/* + 'Reverb3D' API +*/ + +FMOD_RESULT F_API FMOD_Reverb3D_Release (FMOD_REVERB3D *reverb3d); + +/* + Reverb manipulation. +*/ + +FMOD_RESULT F_API FMOD_Reverb3D_Set3DAttributes (FMOD_REVERB3D *reverb3d, const FMOD_VECTOR *position, float mindistance, float maxdistance); +FMOD_RESULT F_API FMOD_Reverb3D_Get3DAttributes (FMOD_REVERB3D *reverb3d, FMOD_VECTOR *position, float *mindistance, float *maxdistance); +FMOD_RESULT F_API FMOD_Reverb3D_SetProperties (FMOD_REVERB3D *reverb3d, const FMOD_REVERB_PROPERTIES *properties); +FMOD_RESULT F_API FMOD_Reverb3D_GetProperties (FMOD_REVERB3D *reverb3d, FMOD_REVERB_PROPERTIES *properties); +FMOD_RESULT F_API FMOD_Reverb3D_SetActive (FMOD_REVERB3D *reverb3d, FMOD_BOOL active); +FMOD_RESULT F_API FMOD_Reverb3D_GetActive (FMOD_REVERB3D *reverb3d, FMOD_BOOL *active); + +/* + Userdata set/get. +*/ + +FMOD_RESULT F_API FMOD_Reverb3D_SetUserData (FMOD_REVERB3D *reverb3d, void *userdata); +FMOD_RESULT F_API FMOD_Reverb3D_GetUserData (FMOD_REVERB3D *reverb3d, void **userdata); + +/*$ preserve start $*/ + +#ifdef __cplusplus +} +#endif + +#endif /* _FMOD_H */ + +/*$ preserve end $*/ diff --git a/Plugins/FMODStudio/Source/FMODStudio/Public/FMOD/fmod.hpp b/Plugins/FMODStudio/Source/FMODStudio/Public/FMOD/fmod.hpp new file mode 100644 index 0000000..5facf97 --- /dev/null +++ b/Plugins/FMODStudio/Source/FMODStudio/Public/FMOD/fmod.hpp @@ -0,0 +1,604 @@ +/* ========================================================================================== */ +/* FMOD Studio - C++ header file. Copyright (c), Firelight Technologies Pty, Ltd. 2004-2016. */ +/* */ +/* Use this header in conjunction with fmod_common.h (which contains all the constants / */ +/* callbacks) to develop using C++ classes. */ +/* ========================================================================================== */ + +#ifndef _FMOD_HPP +#define _FMOD_HPP + +#include "fmod_common.h" +#include "fmod.h" + +/* + Constant and defines +*/ + +/* + FMOD Namespace +*/ +namespace FMOD +{ + class System; + class Sound; + class ChannelControl; + class Channel; + class ChannelGroup; + class SoundGroup; + class DSP; + class DSPConnection; + class Geometry; + class Reverb3D; + + /* + FMOD global system functions (optional). + */ + inline FMOD_RESULT Memory_Initialize (void *poolmem, int poollen, FMOD_MEMORY_ALLOC_CALLBACK useralloc, FMOD_MEMORY_REALLOC_CALLBACK userrealloc, FMOD_MEMORY_FREE_CALLBACK userfree, FMOD_MEMORY_TYPE memtypeflags = FMOD_MEMORY_ALL) { return FMOD_Memory_Initialize(poolmem, poollen, useralloc, userrealloc, userfree, memtypeflags); } + inline FMOD_RESULT Memory_GetStats (int *currentalloced, int *maxalloced, bool blocking = true) { return FMOD_Memory_GetStats(currentalloced, maxalloced, blocking); } + inline FMOD_RESULT Debug_Initialize (FMOD_DEBUG_FLAGS flags, FMOD_DEBUG_MODE mode = FMOD_DEBUG_MODE_TTY, FMOD_DEBUG_CALLBACK callback = 0, const char *filename = 0) { return FMOD_Debug_Initialize(flags, mode, callback, filename); } + inline FMOD_RESULT File_SetDiskBusy (int busy) { return FMOD_File_SetDiskBusy(busy); } + inline FMOD_RESULT File_GetDiskBusy (int *busy) { return FMOD_File_GetDiskBusy(busy); } + + /* + FMOD System factory functions. + */ + inline FMOD_RESULT System_Create (System **system) { return FMOD_System_Create((FMOD_SYSTEM **)system); } + + /* + 'System' API + */ + class System + { + private: + + // Constructor made private so user cannot statically instance a System class. System_Create must be used. + System(); + System(const System &); + + public: + + FMOD_RESULT F_API release (); + + // Setup functions. + FMOD_RESULT F_API setOutput (FMOD_OUTPUTTYPE output); + FMOD_RESULT F_API getOutput (FMOD_OUTPUTTYPE *output); + FMOD_RESULT F_API getNumDrivers (int *numdrivers); + FMOD_RESULT F_API getDriverInfo (int id, char *name, int namelen, FMOD_GUID *guid, int *systemrate, FMOD_SPEAKERMODE *speakermode, int *speakermodechannels); + FMOD_RESULT F_API setDriver (int driver); + FMOD_RESULT F_API getDriver (int *driver); + FMOD_RESULT F_API setSoftwareChannels (int numsoftwarechannels); + FMOD_RESULT F_API getSoftwareChannels (int *numsoftwarechannels); + FMOD_RESULT F_API setSoftwareFormat (int samplerate, FMOD_SPEAKERMODE speakermode, int numrawspeakers); + FMOD_RESULT F_API getSoftwareFormat (int *samplerate, FMOD_SPEAKERMODE *speakermode, int *numrawspeakers); + FMOD_RESULT F_API setDSPBufferSize (unsigned int bufferlength, int numbuffers); + FMOD_RESULT F_API getDSPBufferSize (unsigned int *bufferlength, int *numbuffers); + FMOD_RESULT F_API setFileSystem (FMOD_FILE_OPEN_CALLBACK useropen, FMOD_FILE_CLOSE_CALLBACK userclose, FMOD_FILE_READ_CALLBACK userread, FMOD_FILE_SEEK_CALLBACK userseek, FMOD_FILE_ASYNCREAD_CALLBACK userasyncread, FMOD_FILE_ASYNCCANCEL_CALLBACK userasynccancel, int blockalign); + FMOD_RESULT F_API attachFileSystem (FMOD_FILE_OPEN_CALLBACK useropen, FMOD_FILE_CLOSE_CALLBACK userclose, FMOD_FILE_READ_CALLBACK userread, FMOD_FILE_SEEK_CALLBACK userseek); + FMOD_RESULT F_API setAdvancedSettings (FMOD_ADVANCEDSETTINGS *settings); + FMOD_RESULT F_API getAdvancedSettings (FMOD_ADVANCEDSETTINGS *settings); + FMOD_RESULT F_API setCallback (FMOD_SYSTEM_CALLBACK callback, FMOD_SYSTEM_CALLBACK_TYPE callbackmask = FMOD_SYSTEM_CALLBACK_ALL); + + // Plug-in support. + FMOD_RESULT F_API setPluginPath (const char *path); + FMOD_RESULT F_API loadPlugin (const char *filename, unsigned int *handle, unsigned int priority = 0); + FMOD_RESULT F_API unloadPlugin (unsigned int handle); + FMOD_RESULT F_API getNumPlugins (FMOD_PLUGINTYPE plugintype, int *numplugins); + FMOD_RESULT F_API getPluginHandle (FMOD_PLUGINTYPE plugintype, int index, unsigned int *handle); + FMOD_RESULT F_API getPluginInfo (unsigned int handle, FMOD_PLUGINTYPE *plugintype, char *name, int namelen, unsigned int *version); + FMOD_RESULT F_API setOutputByPlugin (unsigned int handle); + FMOD_RESULT F_API getOutputByPlugin (unsigned int *handle); + FMOD_RESULT F_API createDSPByPlugin (unsigned int handle, DSP **dsp); + FMOD_RESULT F_API getDSPInfoByPlugin (unsigned int handle, const FMOD_DSP_DESCRIPTION **description); + FMOD_RESULT F_API registerCodec (FMOD_CODEC_DESCRIPTION *description, unsigned int *handle, unsigned int priority = 0); + FMOD_RESULT F_API registerDSP (const FMOD_DSP_DESCRIPTION *description, unsigned int *handle); + FMOD_RESULT F_API registerOutput (const FMOD_OUTPUT_DESCRIPTION *description, unsigned int *handle); + + // Init/Close. + FMOD_RESULT F_API init (int maxchannels, FMOD_INITFLAGS flags, void *extradriverdata); + FMOD_RESULT F_API close (); + + // General post-init system functions. + FMOD_RESULT F_API update (); /* IMPORTANT! CALL THIS ONCE PER FRAME! */ + + FMOD_RESULT F_API setSpeakerPosition (FMOD_SPEAKER speaker, float x, float y, bool active); + FMOD_RESULT F_API getSpeakerPosition (FMOD_SPEAKER speaker, float *x, float *y, bool *active); + FMOD_RESULT F_API setStreamBufferSize (unsigned int filebuffersize, FMOD_TIMEUNIT filebuffersizetype); + FMOD_RESULT F_API getStreamBufferSize (unsigned int *filebuffersize, FMOD_TIMEUNIT *filebuffersizetype); + FMOD_RESULT F_API set3DSettings (float dopplerscale, float distancefactor, float rolloffscale); + FMOD_RESULT F_API get3DSettings (float *dopplerscale, float *distancefactor, float *rolloffscale); + FMOD_RESULT F_API set3DNumListeners (int numlisteners); + FMOD_RESULT F_API get3DNumListeners (int *numlisteners); + FMOD_RESULT F_API set3DListenerAttributes (int listener, const FMOD_VECTOR *pos, const FMOD_VECTOR *vel, const FMOD_VECTOR *forward, const FMOD_VECTOR *up); + FMOD_RESULT F_API get3DListenerAttributes (int listener, FMOD_VECTOR *pos, FMOD_VECTOR *vel, FMOD_VECTOR *forward, FMOD_VECTOR *up); + FMOD_RESULT F_API set3DRolloffCallback (FMOD_3D_ROLLOFF_CALLBACK callback); + FMOD_RESULT F_API mixerSuspend (); + FMOD_RESULT F_API mixerResume (); + FMOD_RESULT F_API getDefaultMixMatrix (FMOD_SPEAKERMODE sourcespeakermode, FMOD_SPEAKERMODE targetspeakermode, float *matrix, int matrixhop); + FMOD_RESULT F_API getSpeakerModeChannels (FMOD_SPEAKERMODE mode, int *channels); + + // System information functions. + FMOD_RESULT F_API getVersion (unsigned int *version); + FMOD_RESULT F_API getOutputHandle (void **handle); + FMOD_RESULT F_API getChannelsPlaying (int *channels); + FMOD_RESULT F_API getChannelsReal (int *realchannels); + FMOD_RESULT F_API getCPUUsage (float *dsp, float *stream, float *geometry, float *update, float *total); + FMOD_RESULT F_API getSoundRAM (int *currentalloced, int *maxalloced, int *total); + + // Sound/DSP/Channel/FX creation and retrieval. + FMOD_RESULT F_API createSound (const char *name_or_data, FMOD_MODE mode, FMOD_CREATESOUNDEXINFO *exinfo, Sound **sound); + FMOD_RESULT F_API createStream (const char *name_or_data, FMOD_MODE mode, FMOD_CREATESOUNDEXINFO *exinfo, Sound **sound); + FMOD_RESULT F_API createDSP (const FMOD_DSP_DESCRIPTION *description, DSP **dsp); + FMOD_RESULT F_API createDSPByType (FMOD_DSP_TYPE type, DSP **dsp); + FMOD_RESULT F_API createChannelGroup (const char *name, ChannelGroup **channelgroup); + FMOD_RESULT F_API createSoundGroup (const char *name, SoundGroup **soundgroup); + FMOD_RESULT F_API createReverb3D (Reverb3D **reverb); + + FMOD_RESULT F_API playSound (Sound *sound, ChannelGroup *channelgroup, bool paused, Channel **channel); + FMOD_RESULT F_API playDSP (DSP *dsp, ChannelGroup *channelgroup, bool paused, Channel **channel); + FMOD_RESULT F_API getChannel (int channelid, Channel **channel); + FMOD_RESULT F_API getMasterChannelGroup (ChannelGroup **channelgroup); + FMOD_RESULT F_API getMasterSoundGroup (SoundGroup **soundgroup); + + // Routing to ports. + FMOD_RESULT F_API attachChannelGroupToPort (FMOD_PORT_TYPE portType, FMOD_PORT_INDEX portIndex, ChannelGroup *channelgroup, bool passThru = false); + FMOD_RESULT F_API detachChannelGroupFromPort (ChannelGroup *channelgroup); + + // Reverb API. + FMOD_RESULT F_API setReverbProperties (int instance, const FMOD_REVERB_PROPERTIES *prop); + FMOD_RESULT F_API getReverbProperties (int instance, FMOD_REVERB_PROPERTIES *prop); + + // System level DSP functionality. + FMOD_RESULT F_API lockDSP (); + FMOD_RESULT F_API unlockDSP (); + + // Recording API. + FMOD_RESULT F_API getRecordNumDrivers (int *numdrivers, int *numconnected); + FMOD_RESULT F_API getRecordDriverInfo (int id, char *name, int namelen, FMOD_GUID *guid, int *systemrate, FMOD_SPEAKERMODE *speakermode, int *speakermodechannels, FMOD_DRIVER_STATE *state); + FMOD_RESULT F_API getRecordPosition (int id, unsigned int *position); + FMOD_RESULT F_API recordStart (int id, Sound *sound, bool loop); + FMOD_RESULT F_API recordStop (int id); + FMOD_RESULT F_API isRecording (int id, bool *recording); + + // Geometry API. + FMOD_RESULT F_API createGeometry (int maxpolygons, int maxvertices, Geometry **geometry); + FMOD_RESULT F_API setGeometrySettings (float maxworldsize); + FMOD_RESULT F_API getGeometrySettings (float *maxworldsize); + FMOD_RESULT F_API loadGeometry (const void *data, int datasize, Geometry **geometry); + FMOD_RESULT F_API getGeometryOcclusion (const FMOD_VECTOR *listener, const FMOD_VECTOR *source, float *direct, float *reverb); + + // Network functions. + FMOD_RESULT F_API setNetworkProxy (const char *proxy); + FMOD_RESULT F_API getNetworkProxy (char *proxy, int proxylen); + FMOD_RESULT F_API setNetworkTimeout (int timeout); + FMOD_RESULT F_API getNetworkTimeout (int *timeout); + + // Userdata set/get. + FMOD_RESULT F_API setUserData (void *userdata); + FMOD_RESULT F_API getUserData (void **userdata); + }; + + /* + 'Sound' API + */ + class Sound + { + private: + + // Constructor made private so user cannot statically instance a Sound class. Appropriate Sound creation or retrieval function must be used. + Sound(); + Sound(const Sound &); + + public: + + FMOD_RESULT F_API release (); + FMOD_RESULT F_API getSystemObject (System **system); + + // Standard sound manipulation functions. + FMOD_RESULT F_API lock (unsigned int offset, unsigned int length, void **ptr1, void **ptr2, unsigned int *len1, unsigned int *len2); + FMOD_RESULT F_API unlock (void *ptr1, void *ptr2, unsigned int len1, unsigned int len2); + FMOD_RESULT F_API setDefaults (float frequency, int priority); + FMOD_RESULT F_API getDefaults (float *frequency, int *priority); + FMOD_RESULT F_API set3DMinMaxDistance (float min, float max); + FMOD_RESULT F_API get3DMinMaxDistance (float *min, float *max); + FMOD_RESULT F_API set3DConeSettings (float insideconeangle, float outsideconeangle, float outsidevolume); + FMOD_RESULT F_API get3DConeSettings (float *insideconeangle, float *outsideconeangle, float *outsidevolume); + FMOD_RESULT F_API set3DCustomRolloff (FMOD_VECTOR *points, int numpoints); + FMOD_RESULT F_API get3DCustomRolloff (FMOD_VECTOR **points, int *numpoints); + FMOD_RESULT F_API getSubSound (int index, Sound **subsound); + FMOD_RESULT F_API getSubSoundParent (Sound **parentsound); + FMOD_RESULT F_API getName (char *name, int namelen); + FMOD_RESULT F_API getLength (unsigned int *length, FMOD_TIMEUNIT lengthtype); + FMOD_RESULT F_API getFormat (FMOD_SOUND_TYPE *type, FMOD_SOUND_FORMAT *format, int *channels, int *bits); + FMOD_RESULT F_API getNumSubSounds (int *numsubsounds); + FMOD_RESULT F_API getNumTags (int *numtags, int *numtagsupdated); + FMOD_RESULT F_API getTag (const char *name, int index, FMOD_TAG *tag); + FMOD_RESULT F_API getOpenState (FMOD_OPENSTATE *openstate, unsigned int *percentbuffered, bool *starving, bool *diskbusy); + FMOD_RESULT F_API readData (void *buffer, unsigned int lenbytes, unsigned int *read); + FMOD_RESULT F_API seekData (unsigned int pcm); + + FMOD_RESULT F_API setSoundGroup (SoundGroup *soundgroup); + FMOD_RESULT F_API getSoundGroup (SoundGroup **soundgroup); + + // Synchronization point API. These points can come from markers embedded in wav files, and can also generate channel callbacks. + FMOD_RESULT F_API getNumSyncPoints (int *numsyncpoints); + FMOD_RESULT F_API getSyncPoint (int index, FMOD_SYNCPOINT **point); + FMOD_RESULT F_API getSyncPointInfo (FMOD_SYNCPOINT *point, char *name, int namelen, unsigned int *offset, FMOD_TIMEUNIT offsettype); + FMOD_RESULT F_API addSyncPoint (unsigned int offset, FMOD_TIMEUNIT offsettype, const char *name, FMOD_SYNCPOINT **point); + FMOD_RESULT F_API deleteSyncPoint (FMOD_SYNCPOINT *point); + + // Functions also in Channel class but here they are the 'default' to save having to change it in Channel all the time. + FMOD_RESULT F_API setMode (FMOD_MODE mode); + FMOD_RESULT F_API getMode (FMOD_MODE *mode); + FMOD_RESULT F_API setLoopCount (int loopcount); + FMOD_RESULT F_API getLoopCount (int *loopcount); + FMOD_RESULT F_API setLoopPoints (unsigned int loopstart, FMOD_TIMEUNIT loopstarttype, unsigned int loopend, FMOD_TIMEUNIT loopendtype); + FMOD_RESULT F_API getLoopPoints (unsigned int *loopstart, FMOD_TIMEUNIT loopstarttype, unsigned int *loopend, FMOD_TIMEUNIT loopendtype); + + // For MOD/S3M/XM/IT/MID sequenced formats only. + FMOD_RESULT F_API getMusicNumChannels (int *numchannels); + FMOD_RESULT F_API setMusicChannelVolume (int channel, float volume); + FMOD_RESULT F_API getMusicChannelVolume (int channel, float *volume); + FMOD_RESULT F_API setMusicSpeed (float speed); + FMOD_RESULT F_API getMusicSpeed (float *speed); + + // Userdata set/get. + FMOD_RESULT F_API setUserData (void *userdata); + FMOD_RESULT F_API getUserData (void **userdata); + }; + + + /* + 'ChannelControl API'. This is a base class for Channel and ChannelGroup so they can share the same functionality. This cannot be used or instansiated explicitly. + */ + class ChannelControl + { + private: + + // Constructor made private so user cannot statically instance a Control class. + ChannelControl(); + ChannelControl(const ChannelControl &); + + public: + + FMOD_RESULT F_API getSystemObject (System **system); + + // General control functionality for Channels and ChannelGroups. + FMOD_RESULT F_API stop (); + FMOD_RESULT F_API setPaused (bool paused); + FMOD_RESULT F_API getPaused (bool *paused); + FMOD_RESULT F_API setVolume (float volume); + FMOD_RESULT F_API getVolume (float *volume); + FMOD_RESULT F_API setVolumeRamp (bool ramp); + FMOD_RESULT F_API getVolumeRamp (bool *ramp); + FMOD_RESULT F_API getAudibility (float *audibility); + FMOD_RESULT F_API setPitch (float pitch); + FMOD_RESULT F_API getPitch (float *pitch); + FMOD_RESULT F_API setMute (bool mute); + FMOD_RESULT F_API getMute (bool *mute); + FMOD_RESULT F_API setReverbProperties (int instance, float wet); + FMOD_RESULT F_API getReverbProperties (int instance, float *wet); + FMOD_RESULT F_API setLowPassGain (float gain); + FMOD_RESULT F_API getLowPassGain (float *gain); + FMOD_RESULT F_API setMode (FMOD_MODE mode); + FMOD_RESULT F_API getMode (FMOD_MODE *mode); + FMOD_RESULT F_API setCallback (FMOD_CHANNELCONTROL_CALLBACK callback); + FMOD_RESULT F_API isPlaying (bool *isplaying); + + // Panning and level adjustment. + // Note all 'set' functions alter a final matrix, this is why the only get function is getMixMatrix, to avoid other get functions returning incorrect/obsolete values. + FMOD_RESULT F_API setPan (float pan); + FMOD_RESULT F_API setMixLevelsOutput (float frontleft, float frontright, float center, float lfe, float surroundleft, float surroundright, float backleft, float backright); + FMOD_RESULT F_API setMixLevelsInput (float *levels, int numlevels); + FMOD_RESULT F_API setMixMatrix (float *matrix, int outchannels, int inchannels, int inchannel_hop = 0); + FMOD_RESULT F_API getMixMatrix (float *matrix, int *outchannels, int *inchannels, int inchannel_hop = 0); + + // Clock based functionality. + FMOD_RESULT F_API getDSPClock (unsigned long long *dspclock, unsigned long long *parentclock); + FMOD_RESULT F_API setDelay (unsigned long long dspclock_start, unsigned long long dspclock_end, bool stopchannels = true); + FMOD_RESULT F_API getDelay (unsigned long long *dspclock_start, unsigned long long *dspclock_end, bool *stopchannels = 0); + FMOD_RESULT F_API addFadePoint (unsigned long long dspclock, float volume); + FMOD_RESULT F_API setFadePointRamp (unsigned long long dspclock, float volume); + FMOD_RESULT F_API removeFadePoints (unsigned long long dspclock_start, unsigned long long dspclock_end); + FMOD_RESULT F_API getFadePoints (unsigned int *numpoints, unsigned long long *point_dspclock, float *point_volume); + + // DSP effects. + FMOD_RESULT F_API getDSP (int index, DSP **dsp); + FMOD_RESULT F_API addDSP (int index, DSP *dsp); + FMOD_RESULT F_API removeDSP (DSP *dsp); + FMOD_RESULT F_API getNumDSPs (int *numdsps); + FMOD_RESULT F_API setDSPIndex (DSP *dsp, int index); + FMOD_RESULT F_API getDSPIndex (DSP *dsp, int *index); + FMOD_RESULT F_API overridePanDSP (DSP *pan); + + // 3D functionality. + FMOD_RESULT F_API set3DAttributes (const FMOD_VECTOR *pos, const FMOD_VECTOR *vel, const FMOD_VECTOR *alt_pan_pos = 0); + FMOD_RESULT F_API get3DAttributes (FMOD_VECTOR *pos, FMOD_VECTOR *vel, FMOD_VECTOR *alt_pan_pos = 0); + FMOD_RESULT F_API set3DMinMaxDistance (float mindistance, float maxdistance); + FMOD_RESULT F_API get3DMinMaxDistance (float *mindistance, float *maxdistance); + FMOD_RESULT F_API set3DConeSettings (float insideconeangle, float outsideconeangle, float outsidevolume); + FMOD_RESULT F_API get3DConeSettings (float *insideconeangle, float *outsideconeangle, float *outsidevolume); + FMOD_RESULT F_API set3DConeOrientation (FMOD_VECTOR *orientation); + FMOD_RESULT F_API get3DConeOrientation (FMOD_VECTOR *orientation); + FMOD_RESULT F_API set3DCustomRolloff (FMOD_VECTOR *points, int numpoints); + FMOD_RESULT F_API get3DCustomRolloff (FMOD_VECTOR **points, int *numpoints); + FMOD_RESULT F_API set3DOcclusion (float directocclusion, float reverbocclusion); + FMOD_RESULT F_API get3DOcclusion (float *directocclusion, float *reverbocclusion); + FMOD_RESULT F_API set3DSpread (float angle); + FMOD_RESULT F_API get3DSpread (float *angle); + FMOD_RESULT F_API set3DLevel (float level); + FMOD_RESULT F_API get3DLevel (float *level); + FMOD_RESULT F_API set3DDopplerLevel (float level); + FMOD_RESULT F_API get3DDopplerLevel (float *level); + FMOD_RESULT F_API set3DDistanceFilter (bool custom, float customLevel, float centerFreq); + FMOD_RESULT F_API get3DDistanceFilter (bool *custom, float *customLevel, float *centerFreq); + + // Userdata set/get. + FMOD_RESULT F_API setUserData (void *userdata); + FMOD_RESULT F_API getUserData (void **userdata); + }; + + /* + 'Channel' API. + */ + class Channel : public ChannelControl + { + private: + + // Constructor made private so user cannot statically instance a Channel class. Appropriate Channel creation or retrieval function must be used. + Channel(); + Channel(const Channel &); + + public: + + // Channel specific control functionality. + FMOD_RESULT F_API setFrequency (float frequency); + FMOD_RESULT F_API getFrequency (float *frequency); + FMOD_RESULT F_API setPriority (int priority); + FMOD_RESULT F_API getPriority (int *priority); + FMOD_RESULT F_API setPosition (unsigned int position, FMOD_TIMEUNIT postype); + FMOD_RESULT F_API getPosition (unsigned int *position, FMOD_TIMEUNIT postype); + FMOD_RESULT F_API setChannelGroup (ChannelGroup *channelgroup); + FMOD_RESULT F_API getChannelGroup (ChannelGroup **channelgroup); + FMOD_RESULT F_API setLoopCount (int loopcount); + FMOD_RESULT F_API getLoopCount (int *loopcount); + FMOD_RESULT F_API setLoopPoints (unsigned int loopstart, FMOD_TIMEUNIT loopstarttype, unsigned int loopend, FMOD_TIMEUNIT loopendtype); + FMOD_RESULT F_API getLoopPoints (unsigned int *loopstart, FMOD_TIMEUNIT loopstarttype, unsigned int *loopend, FMOD_TIMEUNIT loopendtype); + + // Information only functions. + FMOD_RESULT F_API isVirtual (bool *isvirtual); + FMOD_RESULT F_API getCurrentSound (Sound **sound); + FMOD_RESULT F_API getIndex (int *index); + }; + + /* + 'ChannelGroup' API + */ + class ChannelGroup : public ChannelControl + { + private: + + // Constructor made private so user cannot statically instance a ChannelGroup class. Appropriate ChannelGroup creation or retrieval function must be used. + ChannelGroup(); + ChannelGroup(const ChannelGroup &); + + public: + + FMOD_RESULT F_API release (); + + // Nested channel groups. + FMOD_RESULT F_API addGroup (ChannelGroup *group, bool propagatedspclock = true, DSPConnection **connection = 0); + FMOD_RESULT F_API getNumGroups (int *numgroups); + FMOD_RESULT F_API getGroup (int index, ChannelGroup **group); + FMOD_RESULT F_API getParentGroup (ChannelGroup **group); + + // Information only functions. + FMOD_RESULT F_API getName (char *name, int namelen); + FMOD_RESULT F_API getNumChannels (int *numchannels); + FMOD_RESULT F_API getChannel (int index, Channel **channel); + }; + + /* + 'SoundGroup' API + */ + class SoundGroup + { + private: + + // Constructor made private so user cannot statically instance a SoundGroup class. Appropriate SoundGroup creation or retrieval function must be used. + SoundGroup(); + SoundGroup(const SoundGroup &); + + public: + + FMOD_RESULT F_API release (); + FMOD_RESULT F_API getSystemObject (System **system); + + // SoundGroup control functions. + FMOD_RESULT F_API setMaxAudible (int maxaudible); + FMOD_RESULT F_API getMaxAudible (int *maxaudible); + FMOD_RESULT F_API setMaxAudibleBehavior (FMOD_SOUNDGROUP_BEHAVIOR behavior); + FMOD_RESULT F_API getMaxAudibleBehavior (FMOD_SOUNDGROUP_BEHAVIOR *behavior); + FMOD_RESULT F_API setMuteFadeSpeed (float speed); + FMOD_RESULT F_API getMuteFadeSpeed (float *speed); + FMOD_RESULT F_API setVolume (float volume); + FMOD_RESULT F_API getVolume (float *volume); + FMOD_RESULT F_API stop (); + + // Information only functions. + FMOD_RESULT F_API getName (char *name, int namelen); + FMOD_RESULT F_API getNumSounds (int *numsounds); + FMOD_RESULT F_API getSound (int index, Sound **sound); + FMOD_RESULT F_API getNumPlaying (int *numplaying); + + // Userdata set/get. + FMOD_RESULT F_API setUserData (void *userdata); + FMOD_RESULT F_API getUserData (void **userdata); + }; + + /* + 'DSP' API + */ + class DSP + { + private: + + // Constructor made private so user cannot statically instance a DSP class. Appropriate DSP creation or retrieval function must be used. + DSP(); + DSP(const DSP &); + + public: + + FMOD_RESULT F_API release (); + FMOD_RESULT F_API getSystemObject (System **system); + + // Connection / disconnection / input and output enumeration. + FMOD_RESULT F_API addInput (DSP *input, DSPConnection **connection = 0, FMOD_DSPCONNECTION_TYPE type = FMOD_DSPCONNECTION_TYPE_STANDARD); + FMOD_RESULT F_API disconnectFrom (DSP *target, DSPConnection *connection = 0); + FMOD_RESULT F_API disconnectAll (bool inputs, bool outputs); + FMOD_RESULT F_API getNumInputs (int *numinputs); + FMOD_RESULT F_API getNumOutputs (int *numoutputs); + FMOD_RESULT F_API getInput (int index, DSP **input, DSPConnection **inputconnection); + FMOD_RESULT F_API getOutput (int index, DSP **output, DSPConnection **outputconnection); + + // DSP unit control. + FMOD_RESULT F_API setActive (bool active); + FMOD_RESULT F_API getActive (bool *active); + FMOD_RESULT F_API setBypass (bool bypass); + FMOD_RESULT F_API getBypass (bool *bypass); + FMOD_RESULT F_API setWetDryMix (float prewet, float postwet, float dry); + FMOD_RESULT F_API getWetDryMix (float *prewet, float *postwet, float *dry); + FMOD_RESULT F_API setChannelFormat (FMOD_CHANNELMASK channelmask, int numchannels, FMOD_SPEAKERMODE source_speakermode); + FMOD_RESULT F_API getChannelFormat (FMOD_CHANNELMASK *channelmask, int *numchannels, FMOD_SPEAKERMODE *source_speakermode); + FMOD_RESULT F_API getOutputChannelFormat (FMOD_CHANNELMASK inmask, int inchannels, FMOD_SPEAKERMODE inspeakermode, FMOD_CHANNELMASK *outmask, int *outchannels, FMOD_SPEAKERMODE *outspeakermode); + FMOD_RESULT F_API reset (); + + // DSP parameter control. + FMOD_RESULT F_API setParameterFloat (int index, float value); + FMOD_RESULT F_API setParameterInt (int index, int value); + FMOD_RESULT F_API setParameterBool (int index, bool value); + FMOD_RESULT F_API setParameterData (int index, void *data, unsigned int length); + FMOD_RESULT F_API getParameterFloat (int index, float *value, char *valuestr, int valuestrlen); + FMOD_RESULT F_API getParameterInt (int index, int *value, char *valuestr, int valuestrlen); + FMOD_RESULT F_API getParameterBool (int index, bool *value, char *valuestr, int valuestrlen); + FMOD_RESULT F_API getParameterData (int index, void **data, unsigned int *length, char *valuestr, int valuestrlen); + FMOD_RESULT F_API getNumParameters (int *numparams); + FMOD_RESULT F_API getParameterInfo (int index, FMOD_DSP_PARAMETER_DESC **desc); + FMOD_RESULT F_API getDataParameterIndex (int datatype, int *index); + FMOD_RESULT F_API showConfigDialog (void *hwnd, bool show); + + // DSP attributes. + FMOD_RESULT F_API getInfo (char *name, unsigned int *version, int *channels, int *configwidth, int *configheight); + FMOD_RESULT F_API getType (FMOD_DSP_TYPE *type); + FMOD_RESULT F_API getIdle (bool *idle); + + // Userdata set/get. + FMOD_RESULT F_API setUserData (void *userdata); + FMOD_RESULT F_API getUserData (void **userdata); + + // Metering. + FMOD_RESULT F_API setMeteringEnabled (bool inputEnabled, bool outputEnabled); + FMOD_RESULT F_API getMeteringEnabled (bool *inputEnabled, bool *outputEnabled); + FMOD_RESULT F_API getMeteringInfo (FMOD_DSP_METERING_INFO *inputInfo, FMOD_DSP_METERING_INFO *outputInfo); + }; + + + /* + 'DSPConnection' API + */ + class DSPConnection + { + private: + + // Constructor made private so user cannot statically instance a DSPConnection class. Appropriate DSPConnection creation or retrieval function must be used. + DSPConnection(); + DSPConnection(const DSPConnection &); + + public: + + FMOD_RESULT F_API getInput (DSP **input); + FMOD_RESULT F_API getOutput (DSP **output); + FMOD_RESULT F_API setMix (float volume); + FMOD_RESULT F_API getMix (float *volume); + FMOD_RESULT F_API setMixMatrix (float *matrix, int outchannels, int inchannels, int inchannel_hop = 0); + FMOD_RESULT F_API getMixMatrix (float *matrix, int *outchannels, int *inchannels, int inchannel_hop = 0); + FMOD_RESULT F_API getType (FMOD_DSPCONNECTION_TYPE *type); + + // Userdata set/get. + FMOD_RESULT F_API setUserData (void *userdata); + FMOD_RESULT F_API getUserData (void **userdata); + }; + + + /* + 'Geometry' API + */ + class Geometry + { + private: + + // Constructor made private so user cannot statically instance a Geometry class. Appropriate Geometry creation or retrieval function must be used. + Geometry(); + Geometry(const Geometry &); + + public: + + FMOD_RESULT F_API release (); + + // Polygon manipulation. + FMOD_RESULT F_API addPolygon (float directocclusion, float reverbocclusion, bool doublesided, int numvertices, const FMOD_VECTOR *vertices, int *polygonindex); + FMOD_RESULT F_API getNumPolygons (int *numpolygons); + FMOD_RESULT F_API getMaxPolygons (int *maxpolygons, int *maxvertices); + FMOD_RESULT F_API getPolygonNumVertices (int index, int *numvertices); + FMOD_RESULT F_API setPolygonVertex (int index, int vertexindex, const FMOD_VECTOR *vertex); + FMOD_RESULT F_API getPolygonVertex (int index, int vertexindex, FMOD_VECTOR *vertex); + FMOD_RESULT F_API setPolygonAttributes (int index, float directocclusion, float reverbocclusion, bool doublesided); + FMOD_RESULT F_API getPolygonAttributes (int index, float *directocclusion, float *reverbocclusion, bool *doublesided); + + // Object manipulation. + FMOD_RESULT F_API setActive (bool active); + FMOD_RESULT F_API getActive (bool *active); + FMOD_RESULT F_API setRotation (const FMOD_VECTOR *forward, const FMOD_VECTOR *up); + FMOD_RESULT F_API getRotation (FMOD_VECTOR *forward, FMOD_VECTOR *up); + FMOD_RESULT F_API setPosition (const FMOD_VECTOR *position); + FMOD_RESULT F_API getPosition (FMOD_VECTOR *position); + FMOD_RESULT F_API setScale (const FMOD_VECTOR *scale); + FMOD_RESULT F_API getScale (FMOD_VECTOR *scale); + FMOD_RESULT F_API save (void *data, int *datasize); + + // Userdata set/get. + FMOD_RESULT F_API setUserData (void *userdata); + FMOD_RESULT F_API getUserData (void **userdata); + }; + + + /* + 'Reverb' API + */ + class Reverb3D + { + private: + + // Constructor made private so user cannot statically instance a Reverb3D class. Appropriate Reverb creation or retrieval function must be used. + Reverb3D(); + Reverb3D(const Reverb3D &); + + public: + + FMOD_RESULT F_API release (); + + // Reverb manipulation. + FMOD_RESULT F_API set3DAttributes (const FMOD_VECTOR *position, float mindistance, float maxdistance); + FMOD_RESULT F_API get3DAttributes (FMOD_VECTOR *position, float *mindistance,float *maxdistance); + FMOD_RESULT F_API setProperties (const FMOD_REVERB_PROPERTIES *properties); + FMOD_RESULT F_API getProperties (FMOD_REVERB_PROPERTIES *properties); + FMOD_RESULT F_API setActive (bool active); + FMOD_RESULT F_API getActive (bool *active); + + // Userdata set/get. + FMOD_RESULT F_API setUserData (void *userdata); + FMOD_RESULT F_API getUserData (void **userdata); + }; +} + +#endif diff --git a/Plugins/FMODStudio/Source/FMODStudio/Public/FMOD/fmod_codec.h b/Plugins/FMODStudio/Source/FMODStudio/Public/FMOD/fmod_codec.h new file mode 100644 index 0000000..d4d292c --- /dev/null +++ b/Plugins/FMODStudio/Source/FMODStudio/Public/FMOD/fmod_codec.h @@ -0,0 +1,178 @@ +/* ======================================================================================================== */ +/* FMOD Studio - codec development header file. Copyright (c), Firelight Technologies Pty, Ltd. 2004-2016. */ +/* */ +/* Use this header if you are wanting to develop your own file format plugin to use with */ +/* FMOD's codec system. With this header you can make your own fileformat plugin that FMOD */ +/* can register and use. See the documentation and examples on how to make a working plugin. */ +/* */ +/* ======================================================================================================== */ + +#ifndef _FMOD_CODEC_H +#define _FMOD_CODEC_H + +typedef struct FMOD_CODEC_STATE FMOD_CODEC_STATE; +typedef struct FMOD_CODEC_WAVEFORMAT FMOD_CODEC_WAVEFORMAT; + +/* + Codec callbacks +*/ +typedef FMOD_RESULT (F_CALLBACK *FMOD_CODEC_OPEN_CALLBACK) (FMOD_CODEC_STATE *codec_state, FMOD_MODE usermode, FMOD_CREATESOUNDEXINFO *userexinfo); +typedef FMOD_RESULT (F_CALLBACK *FMOD_CODEC_CLOSE_CALLBACK) (FMOD_CODEC_STATE *codec_state); +typedef FMOD_RESULT (F_CALLBACK *FMOD_CODEC_READ_CALLBACK) (FMOD_CODEC_STATE *codec_state, void *buffer, unsigned int samples_in, unsigned int *samples_out); +typedef FMOD_RESULT (F_CALLBACK *FMOD_CODEC_GETLENGTH_CALLBACK) (FMOD_CODEC_STATE *codec_state, unsigned int *length, FMOD_TIMEUNIT lengthtype); +typedef FMOD_RESULT (F_CALLBACK *FMOD_CODEC_SETPOSITION_CALLBACK) (FMOD_CODEC_STATE *codec_state, int subsound, unsigned int position, FMOD_TIMEUNIT postype); +typedef FMOD_RESULT (F_CALLBACK *FMOD_CODEC_GETPOSITION_CALLBACK) (FMOD_CODEC_STATE *codec_state, unsigned int *position, FMOD_TIMEUNIT postype); +typedef FMOD_RESULT (F_CALLBACK *FMOD_CODEC_SOUNDCREATE_CALLBACK) (FMOD_CODEC_STATE *codec_state, int subsound, FMOD_SOUND *sound); +typedef FMOD_RESULT (F_CALLBACK *FMOD_CODEC_METADATA_CALLBACK) (FMOD_CODEC_STATE *codec_state, FMOD_TAGTYPE tagtype, char *name, void *data, unsigned int datalen, FMOD_TAGDATATYPE datatype, int unique); +typedef FMOD_RESULT (F_CALLBACK *FMOD_CODEC_GETWAVEFORMAT_CALLBACK)(FMOD_CODEC_STATE *codec_state, int index, FMOD_CODEC_WAVEFORMAT *waveformat); + + +/* +[STRUCTURE] +[ + [DESCRIPTION] + When creating a codec, declare one of these and provide the relevant callbacks and name for FMOD to use when it opens and reads a file. + + [REMARKS] + Members marked with [r] mean the variable is modified by FMOD and is for reading purposes only. Do not change this value.
+ Members marked with [w] mean the variable can be written to. The user can set the value.
+ + [SEE_ALSO] + FMOD_CODEC_STATE + FMOD_CODEC_WAVEFORMAT +] +*/ +typedef struct FMOD_CODEC_DESCRIPTION +{ + const char *name; /* [w] Name of the codec. */ + unsigned int version; /* [w] Plugin writer's version number. */ + int defaultasstream; /* [w] Tells FMOD to open the file as a stream when calling System::createSound, and not a static sample. Should normally be 0 (FALSE), because generally the user wants to decode the file into memory when using System::createSound. Mainly used for formats that decode for a very long time, or could use large amounts of memory when decoded. Usually sequenced formats such as mod/s3m/xm/it/midi fall into this category. It is mainly to stop users that don't know what they're doing from getting FMOD_ERR_MEMORY returned from createSound when they should have in fact called System::createStream or used FMOD_CREATESTREAM in System::createSound. */ + FMOD_TIMEUNIT timeunits; /* [w] When setposition codec is called, only these time formats will be passed to the codec. Use bitwise OR to accumulate different types. */ + FMOD_CODEC_OPEN_CALLBACK open; /* [w] Open callback for the codec for when FMOD tries to open a sound using this codec. */ + FMOD_CODEC_CLOSE_CALLBACK close; /* [w] Close callback for the codec for when FMOD tries to close a sound using this codec. */ + FMOD_CODEC_READ_CALLBACK read; /* [w] Read callback for the codec for when FMOD tries to read some data from the file to the destination format (specified in the open callback). */ + FMOD_CODEC_GETLENGTH_CALLBACK getlength; /* [w] Callback to return the length of the song in whatever format required when Sound::getLength is called. */ + FMOD_CODEC_SETPOSITION_CALLBACK setposition; /* [w] Seek callback for the codec for when FMOD tries to seek within the file with Channel::setPosition. */ + FMOD_CODEC_GETPOSITION_CALLBACK getposition; /* [w] Tell callback for the codec for when FMOD tries to get the current position within the with Channel::getPosition. */ + FMOD_CODEC_SOUNDCREATE_CALLBACK soundcreate; /* [w] Sound creation callback for the codec when FMOD finishes creating the sound. (So the codec can set more parameters for the related created sound, ie loop points/mode or 3D attributes etc). */ + FMOD_CODEC_GETWAVEFORMAT_CALLBACK getwaveformat; /* [w] Callback to tell FMOD about the waveformat of a particular subsound. This is to save memory, rather than saving 1000 FMOD_CODEC_WAVEFORMAT structures in the codec, the codec might have a more optimal way of storing this information. */ +} FMOD_CODEC_DESCRIPTION; + + +/* +[STRUCTURE] +[ + [DESCRIPTION] + Set these values marked to tell fmod what sort of sound to create when the codec open callback is called.
+ The format, channels, frequency and lengthpcm tell FMOD what sort of sound buffer to create when you initialize your code.
+ If you wrote an MP3 codec that decoded to stereo 16bit integer PCM for a 44khz sound, you would specify FMOD_SOUND_FORMAT_PCM16, and channels would be equal to 2, and frequency would be 44100.
+ + [REMARKS] + Members marked with [r] mean the variable is modified by FMOD and is for reading purposes only. Do not change this value.
+ Members marked with [w] mean the variable can be written to. The user can set the value.
+
+ 1.07 Note. 'blockalign' member which was in bytes has been removed. 'pcmblocksize' is now the replacement, and is measured in PCM samples only, not bytes. This is purely to support buffering + internal to FMOD for codecs that are not sample accurate. +
+ Note: When registering a codec, format, channels, frequency and lengthpcm must be supplied, otherwise there will be an error.
+ This structure is optional if FMOD_CODEC_GETWAVEFORMAT_CALLBACK is specified.
+ An array of these structures may be needed if FMOD_CODEC_STATE::numsubsounds is larger than 1. + + + [SEE_ALSO] + FMOD_CODEC_STATE + FMOD_SOUND_FORMAT + FMOD_MODE + FMOD_CHANNELMASK + FMOD_CHANNELORDER + FMOD_CODEC_WAVEFORMAT_VERSION +] +*/ +struct FMOD_CODEC_WAVEFORMAT +{ + char name[256]; /* [w] Name of sound. Optional. */ + FMOD_SOUND_FORMAT format; /* [w] Format for (decompressed) codec output, ie FMOD_SOUND_FORMAT_PCM8, FMOD_SOUND_FORMAT_PCM16. Mandantory - Must be supplied. */ + int channels; /* [w] Number of channels used by codec, ie mono = 1, stereo = 2. Mandantory - Must be supplied. */ + int frequency; /* [w] Default frequency in hz of the codec, ie 44100. Mandantory - Must be supplied. */ + unsigned int lengthbytes; /* [w] Length in bytes of the source data. Used for FMOD_TIMEUNIT_RAWBYTES. Optional. Default = 0. */ + unsigned int lengthpcm; /* [w] Length in decompressed, PCM samples of the file, ie length in seconds * frequency. Used for Sound::getLength and for memory allocation of static decompressed sample data. Mandantory - Must be supplied. */ + unsigned int pcmblocksize; /* [w] Minimum, optimal number of decompressed PCM samples codec can handle. 0 or 1 = no buffering. Anything higher means FMOD will allocate a PCM buffer of this size to read in chunks. The codec read callback will be called in multiples of this value. Optional. */ + int loopstart; /* [w] Loopstart in decompressed, PCM samples of file. Optional. Default = 0. */ + int loopend; /* [w] Loopend in decompressed, PCM samples of file. Optional. Default = 0. */ + FMOD_MODE mode; /* [w] Mode to determine whether the sound should by default load as looping, non looping, 2d or 3d. Optional. Default = FMOD_DEFAULT. */ + FMOD_CHANNELMASK channelmask; /* [w] Defined channel bitmask to describe which speakers the channels in the codec map to, in order of channel count. See fmod_common.h. Optional. Leave at 0 to map to the speaker layout defined in FMOD_SPEAKER. */ + FMOD_CHANNELORDER channelorder; /* [w] Defined channel order type, to describe where each sound channel should pan for the number of channels specified. See fmod_common.h. Optional. Leave at 0 to play in default speaker order. */ + float peakvolume; /* [w] Peak volume of sound. Optional. Default = 0 if not used. */ +}; + + +/* +[DEFINE] +[ + [NAME] + FMOD_CODEC_WAVEFORMAT_VERSION + + [DESCRIPTION] + Version number of FMOD_CODEC_WAVEFORMAT structure. Should be set into FMOD_CODEC_STATE in the FMOD_CODEC_OPEN_CALLBACK. + + [REMARKS] + Use this for binary compatibility and for future expansion. + + [SEE_ALSO] + FMOD_CODEC_STATE + FMOD_CODEC_DESCRIPTION + FMOD_CODEC_OPEN_CALLBACK +] +*/ +#define FMOD_CODEC_WAVEFORMAT_VERSION 2 +/* [DEFINE_END] */ + + +/* +[STRUCTURE] +[ + [DESCRIPTION] + Codec plugin structure that is passed into each callback.
+
+ Optionally set the numsubsounds and waveformat members when called in FMOD_CODEC_OPEN_CALLBACK to tell fmod what sort of sound to create.
+ + [REMARKS] + Members marked with [r] mean the variable is modified by FMOD and is for reading purposes only. Do not change this value.
+ Members marked with [w] mean the variable can be written to. The user can set the value.
+
+ 'numsubsounds' should be 0 if the file is a normal single sound stream or sound. Examples of this would be .WAV, .WMA, .MP3, .AIFF.
+ 'numsubsounds' should be 1+ if the file is a container format, and does not contain wav data itself. Examples of these types would be FSB (contains multiple sounds), DLS (contain instruments).
+ The waveformat value should point to an arrays of information based on how many subsounds are in the format. If the number of subsounds is 0 then it should point to 1 waveformat, the same as if the number of subsounds was 1. If subsounds was 100 for example, there should be a pointer to an array of 100 waveformat structures.
+
+ The waveformat pointer is optional and could be 0, if using FMOD_CODEC_GETWAVEFORMAT_CALLBACK is preferred.
+
+ When a sound has 1 or more subsounds, the caller must play the individual sounds specified by first obtaining the subsound with Sound::getSubSound. + + [SEE_ALSO] + FMOD_CODEC_WAVEFORMAT + FMOD_FILE_READ_CALLBACK + FMOD_FILE_SEEK_CALLBACK + FMOD_CODEC_METADATA_CALLBACK + Sound::getSubSound + Sound::getNumSubSounds + FMOD_CODEC_WAVEFORMAT_VERSION +] +*/ +struct FMOD_CODEC_STATE +{ + int numsubsounds; /* [w] Number of 'subsounds' in this sound. Anything other than 0 makes it a 'container' format (ie DLS/FSB etc which contain 1 or more subsounds). For most normal, single sound codec such as WAV/AIFF/MP3, this should be 0 as they are not a container for subsounds, they are the sound by itself. */ + FMOD_CODEC_WAVEFORMAT *waveformat; /* [w] Pointer to an array of format structures containing information about each sample. Can be 0 or NULL if FMOD_CODEC_GETWAVEFORMAT_CALLBACK callback is preferred. The number of entries here must equal the number of subsounds defined in the subsound parameter. If numsubsounds = 0 then there should be 1 instance of this structure. */ + void *plugindata; /* [w] Plugin writer created data the codec author wants to attach to this object. */ + + void *filehandle; /* [r] This will return an internal FMOD file handle to use with the callbacks provided. */ + unsigned int filesize; /* [r] This will contain the size of the file in bytes. */ + FMOD_FILE_READ_CALLBACK fileread; /* [r] This will return a callable FMOD file function to use from codec. */ + FMOD_FILE_SEEK_CALLBACK fileseek; /* [r] This will return a callable FMOD file function to use from codec. */ + FMOD_CODEC_METADATA_CALLBACK metadata; /* [r] This will return a callable FMOD metadata function to use from codec. */ + + int waveformatversion; /* [w] Must be set to FMOD_CODEC_WAVEFORMAT_VERSION in the FMOD_CODEC_OPEN_CALLBACK. */ +}; + +#endif + + diff --git a/Plugins/FMODStudio/Source/FMODStudio/Public/FMOD/fmod_common.h b/Plugins/FMODStudio/Source/FMODStudio/Public/FMOD/fmod_common.h new file mode 100644 index 0000000..3af283e --- /dev/null +++ b/Plugins/FMODStudio/Source/FMODStudio/Public/FMOD/fmod_common.h @@ -0,0 +1,1687 @@ +/*$ preserve start $*/ + +/* ================================================================================================== */ +/* FMOD Studio - Common C/C++ header file. Copyright (c), Firelight Technologies Pty, Ltd. 2004-2016. */ +/* */ +/* This header is included by fmod.hpp (C++ interface) and fmod.h (C interface) therefore is the */ +/* base header for all FMOD headers. */ +/* ================================================================================================== */ + +#ifndef _FMOD_COMMON_H +#define _FMOD_COMMON_H + +/* + FMOD version number. Check this against FMOD::System::getVersion. + 0xaaaabbcc -> aaaa = major version number. bb = minor version number. cc = development version number. +*/ + +#define FMOD_VERSION 0x00010707 + +/* + Compiler specific settings. +*/ + +#if defined(__CYGWIN32__) || defined(__MINGW32__) + #define F_STDCALL __stdcall +#elif defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(_WIN64) + #define F_STDCALL _stdcall +#elif defined(__ANDROID__) && defined(__arm__) && !defined(__LP64__) && !defined(__clang__) + #define F_STDCALL __attribute__((pcs("aapcs"))) +#else + #define F_STDCALL +#endif + +#define F_DECLSPEC + +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(_WIN64) || defined(__CYGWIN32__) || defined(__MINGW32__) || defined(__ORBIS__) || defined(__psp2__) + #define F_DLLEXPORT __declspec(dllexport) +#elif defined(__MACH__) || defined(__ANDROID__) || defined(__linux__) + #define F_DLLEXPORT __attribute__((visibility("default"))) +#else + #define F_DLLEXPORT +#endif + +#ifdef DLL_EXPORTS + #define F_API F_DLLEXPORT F_STDCALL +#else + #define F_API F_STDCALL +#endif + +#define F_CALLBACK F_STDCALL + +/* + FMOD types. +*/ + +typedef int FMOD_BOOL; +typedef struct FMOD_SYSTEM FMOD_SYSTEM; +typedef struct FMOD_SOUND FMOD_SOUND; +typedef struct FMOD_CHANNELCONTROL FMOD_CHANNELCONTROL; +typedef struct FMOD_CHANNEL FMOD_CHANNEL; +typedef struct FMOD_CHANNELGROUP FMOD_CHANNELGROUP; +typedef struct FMOD_SOUNDGROUP FMOD_SOUNDGROUP; +typedef struct FMOD_REVERB3D FMOD_REVERB3D; +typedef struct FMOD_DSP FMOD_DSP; +typedef struct FMOD_DSPCONNECTION FMOD_DSPCONNECTION; +typedef struct FMOD_POLYGON FMOD_POLYGON; +typedef struct FMOD_GEOMETRY FMOD_GEOMETRY; +typedef struct FMOD_SYNCPOINT FMOD_SYNCPOINT; +typedef struct FMOD_ASYNCREADINFO FMOD_ASYNCREADINFO; +typedef unsigned int FMOD_MODE; +typedef unsigned int FMOD_TIMEUNIT; +typedef unsigned int FMOD_INITFLAGS; +typedef unsigned int FMOD_DEBUG_FLAGS; +typedef unsigned int FMOD_MEMORY_TYPE; +typedef unsigned int FMOD_SYSTEM_CALLBACK_TYPE; +typedef unsigned int FMOD_CHANNELMASK; +typedef unsigned int FMOD_DRIVER_STATE; +typedef unsigned int FMOD_PORT_TYPE; +typedef unsigned long long FMOD_PORT_INDEX; + +/*$ fmod result start $*/ +/* +[ENUM] +[ + [DESCRIPTION] + error codes. Returned from every function. + + [REMARKS] + + [SEE_ALSO] +] +*/ +typedef enum +{ + FMOD_OK, /* No errors. */ + FMOD_ERR_BADCOMMAND, /* Tried to call a function on a data type that does not allow this type of functionality (ie calling Sound::lock on a streaming sound). */ + FMOD_ERR_CHANNEL_ALLOC, /* Error trying to allocate a channel. */ + FMOD_ERR_CHANNEL_STOLEN, /* The specified channel has been reused to play another sound. */ + FMOD_ERR_DMA, /* DMA Failure. See debug output for more information. */ + FMOD_ERR_DSP_CONNECTION, /* DSP connection error. Connection possibly caused a cyclic dependency or connected dsps with incompatible buffer counts. */ + FMOD_ERR_DSP_DONTPROCESS, /* DSP return code from a DSP process query callback. Tells mixer not to call the process callback and therefore not consume CPU. Use this to optimize the DSP graph. */ + FMOD_ERR_DSP_FORMAT, /* DSP Format error. A DSP unit may have attempted to connect to this network with the wrong format, or a matrix may have been set with the wrong size if the target unit has a specified channel map. */ + FMOD_ERR_DSP_INUSE, /* DSP is already in the mixer's DSP network. It must be removed before being reinserted or released. */ + FMOD_ERR_DSP_NOTFOUND, /* DSP connection error. Couldn't find the DSP unit specified. */ + FMOD_ERR_DSP_RESERVED, /* DSP operation error. Cannot perform operation on this DSP as it is reserved by the system. */ + FMOD_ERR_DSP_SILENCE, /* DSP return code from a DSP process query callback. Tells mixer silence would be produced from read, so go idle and not consume CPU. Use this to optimize the DSP graph. */ + FMOD_ERR_DSP_TYPE, /* DSP operation cannot be performed on a DSP of this type. */ + FMOD_ERR_FILE_BAD, /* Error loading file. */ + FMOD_ERR_FILE_COULDNOTSEEK, /* Couldn't perform seek operation. This is a limitation of the medium (ie netstreams) or the file format. */ + FMOD_ERR_FILE_DISKEJECTED, /* Media was ejected while reading. */ + FMOD_ERR_FILE_EOF, /* End of file unexpectedly reached while trying to read essential data (truncated?). */ + FMOD_ERR_FILE_ENDOFDATA, /* End of current chunk reached while trying to read data. */ + FMOD_ERR_FILE_NOTFOUND, /* File not found. */ + FMOD_ERR_FORMAT, /* Unsupported file or audio format. */ + FMOD_ERR_HEADER_MISMATCH, /* There is a version mismatch between the FMOD header and either the FMOD Studio library or the FMOD Low Level library. */ + FMOD_ERR_HTTP, /* A HTTP error occurred. This is a catch-all for HTTP errors not listed elsewhere. */ + FMOD_ERR_HTTP_ACCESS, /* The specified resource requires authentication or is forbidden. */ + FMOD_ERR_HTTP_PROXY_AUTH, /* Proxy authentication is required to access the specified resource. */ + FMOD_ERR_HTTP_SERVER_ERROR, /* A HTTP server error occurred. */ + FMOD_ERR_HTTP_TIMEOUT, /* The HTTP request timed out. */ + FMOD_ERR_INITIALIZATION, /* FMOD was not initialized correctly to support this function. */ + FMOD_ERR_INITIALIZED, /* Cannot call this command after System::init. */ + FMOD_ERR_INTERNAL, /* An error occurred that wasn't supposed to. Contact support. */ + FMOD_ERR_INVALID_FLOAT, /* Value passed in was a NaN, Inf or denormalized float. */ + FMOD_ERR_INVALID_HANDLE, /* An invalid object handle was used. */ + FMOD_ERR_INVALID_PARAM, /* An invalid parameter was passed to this function. */ + FMOD_ERR_INVALID_POSITION, /* An invalid seek position was passed to this function. */ + FMOD_ERR_INVALID_SPEAKER, /* An invalid speaker was passed to this function based on the current speaker mode. */ + FMOD_ERR_INVALID_SYNCPOINT, /* The syncpoint did not come from this sound handle. */ + FMOD_ERR_INVALID_THREAD, /* Tried to call a function on a thread that is not supported. */ + FMOD_ERR_INVALID_VECTOR, /* The vectors passed in are not unit length, or perpendicular. */ + FMOD_ERR_MAXAUDIBLE, /* Reached maximum audible playback count for this sound's soundgroup. */ + FMOD_ERR_MEMORY, /* Not enough memory or resources. */ + FMOD_ERR_MEMORY_CANTPOINT, /* Can't use FMOD_OPENMEMORY_POINT on non PCM source data, or non mp3/xma/adpcm data if FMOD_CREATECOMPRESSEDSAMPLE was used. */ + FMOD_ERR_NEEDS3D, /* Tried to call a command on a 2d sound when the command was meant for 3d sound. */ + FMOD_ERR_NEEDSHARDWARE, /* Tried to use a feature that requires hardware support. */ + FMOD_ERR_NET_CONNECT, /* Couldn't connect to the specified host. */ + FMOD_ERR_NET_SOCKET_ERROR, /* A socket error occurred. This is a catch-all for socket-related errors not listed elsewhere. */ + FMOD_ERR_NET_URL, /* The specified URL couldn't be resolved. */ + FMOD_ERR_NET_WOULD_BLOCK, /* Operation on a non-blocking socket could not complete immediately. */ + FMOD_ERR_NOTREADY, /* Operation could not be performed because specified sound/DSP connection is not ready. */ + FMOD_ERR_OUTPUT_ALLOCATED, /* Error initializing output device, but more specifically, the output device is already in use and cannot be reused. */ + FMOD_ERR_OUTPUT_CREATEBUFFER, /* Error creating hardware sound buffer. */ + FMOD_ERR_OUTPUT_DRIVERCALL, /* A call to a standard soundcard driver failed, which could possibly mean a bug in the driver or resources were missing or exhausted. */ + FMOD_ERR_OUTPUT_FORMAT, /* Soundcard does not support the specified format. */ + FMOD_ERR_OUTPUT_INIT, /* Error initializing output device. */ + FMOD_ERR_OUTPUT_NODRIVERS, /* The output device has no drivers installed. If pre-init, FMOD_OUTPUT_NOSOUND is selected as the output mode. If post-init, the function just fails. */ + FMOD_ERR_PLUGIN, /* An unspecified error has been returned from a plugin. */ + FMOD_ERR_PLUGIN_MISSING, /* A requested output, dsp unit type or codec was not available. */ + FMOD_ERR_PLUGIN_RESOURCE, /* A resource that the plugin requires cannot be found. (ie the DLS file for MIDI playback) */ + FMOD_ERR_PLUGIN_VERSION, /* A plugin was built with an unsupported SDK version. */ + FMOD_ERR_RECORD, /* An error occurred trying to initialize the recording device. */ + FMOD_ERR_REVERB_CHANNELGROUP, /* Reverb properties cannot be set on this channel because a parent channelgroup owns the reverb connection. */ + FMOD_ERR_REVERB_INSTANCE, /* Specified instance in FMOD_REVERB_PROPERTIES couldn't be set. Most likely because it is an invalid instance number or the reverb doesn't exist. */ + FMOD_ERR_SUBSOUNDS, /* The error occurred because the sound referenced contains subsounds when it shouldn't have, or it doesn't contain subsounds when it should have. The operation may also not be able to be performed on a parent sound. */ + FMOD_ERR_SUBSOUND_ALLOCATED, /* This subsound is already being used by another sound, you cannot have more than one parent to a sound. Null out the other parent's entry first. */ + FMOD_ERR_SUBSOUND_CANTMOVE, /* Shared subsounds cannot be replaced or moved from their parent stream, such as when the parent stream is an FSB file. */ + FMOD_ERR_TAGNOTFOUND, /* The specified tag could not be found or there are no tags. */ + FMOD_ERR_TOOMANYCHANNELS, /* The sound created exceeds the allowable input channel count. This can be increased using the 'maxinputchannels' parameter in System::setSoftwareFormat. */ + FMOD_ERR_TRUNCATED, /* The retrieved string is too long to fit in the supplied buffer and has been truncated. */ + FMOD_ERR_UNIMPLEMENTED, /* Something in FMOD hasn't been implemented when it should be! contact support! */ + FMOD_ERR_UNINITIALIZED, /* This command failed because System::init or System::setDriver was not called. */ + FMOD_ERR_UNSUPPORTED, /* A command issued was not supported by this object. Possibly a plugin without certain callbacks specified. */ + FMOD_ERR_VERSION, /* The version number of this file format is not supported. */ + FMOD_ERR_EVENT_ALREADY_LOADED, /* The specified bank has already been loaded. */ + FMOD_ERR_EVENT_LIVEUPDATE_BUSY, /* The live update connection failed due to the game already being connected. */ + FMOD_ERR_EVENT_LIVEUPDATE_MISMATCH, /* The live update connection failed due to the game data being out of sync with the tool. */ + FMOD_ERR_EVENT_LIVEUPDATE_TIMEOUT, /* The live update connection timed out. */ + FMOD_ERR_EVENT_NOTFOUND, /* The requested event, bus or vca could not be found. */ + FMOD_ERR_STUDIO_UNINITIALIZED, /* The Studio::System object is not yet initialized. */ + FMOD_ERR_STUDIO_NOT_LOADED, /* The specified resource is not loaded, so it can't be unloaded. */ + FMOD_ERR_INVALID_STRING, /* An invalid string was passed to this function. */ + FMOD_ERR_ALREADY_LOCKED, /* The specified resource is already locked. */ + FMOD_ERR_NOT_LOCKED, /* The specified resource is not locked, so it can't be unlocked. */ + FMOD_ERR_RECORD_DISCONNECTED, /* The specified recording driver has been disconnected. */ + FMOD_ERR_TOOMANYSAMPLES, /* The length provided exceed the allowable limit. */ + + FMOD_RESULT_FORCEINT = 65536 /* Makes sure this enum is signed 32bit. */ +} FMOD_RESULT; +/*$ fmod result end $*/ + + +/* +[ENUM] +[ + [DESCRIPTION] + Used to distinguish if a FMOD_CHANNELCONTROL parameter is actually a channel or a channelgroup. + + [REMARKS] + Cast the FMOD_CHANNELCONTROL to an FMOD_CHANNEL/FMOD::Channel, or FMOD_CHANNELGROUP/FMOD::ChannelGroup if specific functionality is needed for either class. + Otherwise use as FMOD_CHANNELCONTROL/FMOD::ChannelControl and use that API. + + [SEE_ALSO] + Channel::setCallback + ChannelGroup::setCallback +] +*/ +typedef enum +{ + FMOD_CHANNELCONTROL_CHANNEL, + FMOD_CHANNELCONTROL_CHANNELGROUP, + + FMOD_CHANNELCONTROL_FORCEINT = 65536 /* Makes sure this enum is signed 32bit. */ +} FMOD_CHANNELCONTROL_TYPE; + + +/* +[STRUCTURE] +[ + [DESCRIPTION] + Structure describing a point in 3D space. + + [REMARKS] + FMOD uses a left handed co-ordinate system by default.
+ To use a right handed co-ordinate system specify FMOD_INIT_3D_RIGHTHANDED from FMOD_INITFLAGS in System::init. + + [SEE_ALSO] + System::set3DListenerAttributes + System::get3DListenerAttributes + Channel::set3DAttributes + Channel::get3DAttributes + Channel::set3DCustomRolloff + Channel::get3DCustomRolloff + Sound::set3DCustomRolloff + Sound::get3DCustomRolloff + Geometry::addPolygon + Geometry::setPolygonVertex + Geometry::getPolygonVertex + Geometry::setRotation + Geometry::getRotation + Geometry::setPosition + Geometry::getPosition + Geometry::setScale + Geometry::getScale + FMOD_INITFLAGS +] +*/ +typedef struct +{ + float x; /* X co-ordinate in 3D space. */ + float y; /* Y co-ordinate in 3D space. */ + float z; /* Z co-ordinate in 3D space. */ +} FMOD_VECTOR; + + +/* +[STRUCTURE] +[ + [DESCRIPTION] + Structure describing a position, velocity and orientation. + + [REMARKS] + + [SEE_ALSO] + FMOD_VECTOR + FMOD_DSP_PARAMETER_3DATTRIBUTES +] +*/ +typedef struct FMOD_3D_ATTRIBUTES +{ + FMOD_VECTOR position; + FMOD_VECTOR velocity; + FMOD_VECTOR forward; + FMOD_VECTOR up; +} FMOD_3D_ATTRIBUTES; + + +/* +[STRUCTURE] +[ + [DESCRIPTION] + Structure describing a globally unique identifier. + + [REMARKS] + + [SEE_ALSO] + System::getDriverInfo +] +*/ +typedef struct +{ + unsigned int Data1; /* Specifies the first 8 hexadecimal digits of the GUID */ + unsigned short Data2; /* Specifies the first group of 4 hexadecimal digits. */ + unsigned short Data3; /* Specifies the second group of 4 hexadecimal digits. */ + unsigned char Data4[8]; /* Array of 8 bytes. The first 2 bytes contain the third group of 4 hexadecimal digits. The remaining 6 bytes contain the final 12 hexadecimal digits. */ +} FMOD_GUID; + +typedef void (F_CALLBACK *FMOD_FILE_ASYNCDONE) (FMOD_ASYNCREADINFO *info, FMOD_RESULT result); + +/* +[STRUCTURE] +[ + [DESCRIPTION] + Structure that is passed into FMOD_FILE_ASYNCREAD_CALLBACK. Use the information in this structure to perform + + [REMARKS] + Members marked with [r] mean the variable is modified by FMOD and is for reading purposes only. Do not change this value.
+ Members marked with [w] mean the variable can be written to. The user can set the value.
+
+ Instructions: write to 'buffer', and 'bytesread' BEFORE calling 'done'.
+ As soon as done is called, FMOD will asynchronously continue internally using the data provided in this structure.
+
+ Set result in the 'done' function pointer to the result expected from a normal file read callback.
+ If the read was successful, set it to FMOD_OK.
+ If it read some data but hit the end of the file, set it to FMOD_ERR_FILE_EOF.
+ If a bad error occurred, return FMOD_ERR_FILE_BAD
+ If a disk was ejected, return FMOD_ERR_FILE_DISKEJECTED.
+ + [SEE_ALSO] + FMOD_FILE_ASYNCREAD_CALLBACK + FMOD_FILE_ASYNCCANCEL_CALLBACK + FMOD_FILE_ASYNCDONE +] +*/ +struct FMOD_ASYNCREADINFO +{ + void *handle; /* [r] The file handle that was filled out in the open callback. */ + unsigned int offset; /* [r] Seek position, make sure you read from this file offset. */ + unsigned int sizebytes; /* [r] how many bytes requested for read. */ + int priority; /* [r] 0 = low importance. 100 = extremely important (ie 'must read now or stuttering may occur') */ + + void *userdata; /* [r/w] User data pointer specific to this request. Initially 0, can be ignored or set by the user. Not related to the file's main userdata member. */ + + void *buffer; /* [w] Buffer to read file data into. */ + unsigned int bytesread; /* [w] Fill this in before setting result code to tell FMOD how many bytes were read. */ + + FMOD_FILE_ASYNCDONE done; /* [r] FMOD file system wake up function. Call this when user file read is finished. Pass result of file read as a parameter. */ +}; + + +/* +[ENUM] +[ + [DESCRIPTION] + These output types are used with System::setOutput / System::getOutput, to choose which output method to use. + + [REMARKS] + To pass information to the driver when initializing fmod use the *extradriverdata* parameter in System::init for the following reasons. + + - FMOD_OUTPUTTYPE_WAVWRITER - extradriverdata is a pointer to a char * file name that the wav writer will output to. + - FMOD_OUTPUTTYPE_WAVWRITER_NRT - extradriverdata is a pointer to a char * file name that the wav writer will output to. + - FMOD_OUTPUTTYPE_DSOUND - extradriverdata is cast to a HWND type, so that FMOD can set the focus on the audio for a particular window. + - FMOD_OUTPUTTYPE_PS3 - extradriverdata is a pointer to a FMOD_PS3_EXTRADRIVERDATA struct. This can be found in fmodps3.h. + - FMOD_OUTPUTTYPE_XBOX360 - extradriverdata is a pointer to a FMOD_360_EXTRADRIVERDATA struct. This can be found in fmodxbox360.h. + + Currently these are the only FMOD drivers that take extra information. Other unknown plugins may have different requirements. + + Note! If FMOD_OUTPUTTYPE_WAVWRITER_NRT or FMOD_OUTPUTTYPE_NOSOUND_NRT are used, and if the System::update function is being called + very quickly (ie for a non realtime decode) it may be being called too quickly for the FMOD streamer thread to respond to. + The result will be a skipping/stuttering output in the captured audio. + + To remedy this, disable the FMOD streamer thread, and use FMOD_INIT_STREAM_FROM_UPDATE to avoid skipping in the output stream, + as it will lock the mixer and the streamer together in the same thread. + + [SEE_ALSO] + System::setOutput + System::getOutput + System::init + System::update +] +*/ +typedef enum +{ + FMOD_OUTPUTTYPE_AUTODETECT, /* Picks the best output mode for the platform. This is the default. */ + + FMOD_OUTPUTTYPE_UNKNOWN, /* All - 3rd party plugin, unknown. This is for use with System::getOutput only. */ + FMOD_OUTPUTTYPE_NOSOUND, /* All - Perform all mixing but discard the final output. */ + FMOD_OUTPUTTYPE_WAVWRITER, /* All - Writes output to a .wav file. */ + FMOD_OUTPUTTYPE_NOSOUND_NRT, /* All - Non-realtime version of FMOD_OUTPUTTYPE_NOSOUND. User can drive mixer with System::update at whatever rate they want. */ + FMOD_OUTPUTTYPE_WAVWRITER_NRT, /* All - Non-realtime version of FMOD_OUTPUTTYPE_WAVWRITER. User can drive mixer with System::update at whatever rate they want. */ + + FMOD_OUTPUTTYPE_DSOUND, /* Win - Direct Sound. (Default on Windows XP and below) */ + FMOD_OUTPUTTYPE_WINMM, /* Win - Windows Multimedia. */ + FMOD_OUTPUTTYPE_WASAPI, /* Win/WinStore/XboxOne - Windows Audio Session API. (Default on Windows Vista and above, Xbox One and Windows Store Applications) */ + FMOD_OUTPUTTYPE_ASIO, /* Win - Low latency ASIO 2.0. */ + FMOD_OUTPUTTYPE_PULSEAUDIO, /* Linux - Pulse Audio. (Default on Linux if available) */ + FMOD_OUTPUTTYPE_ALSA, /* Linux - Advanced Linux Sound Architecture. (Default on Linux if PulseAudio isn't available) */ + FMOD_OUTPUTTYPE_COREAUDIO, /* Mac/iOS - Core Audio. (Default on Mac and iOS) */ + FMOD_OUTPUTTYPE_XBOX360, /* Xbox 360 - XAudio. (Default on Xbox 360) */ + FMOD_OUTPUTTYPE_PS3, /* PS3 - Audio Out. (Default on PS3) */ + FMOD_OUTPUTTYPE_AUDIOTRACK, /* Android - Java Audio Track. (Default on Android 2.2 and below) */ + FMOD_OUTPUTTYPE_OPENSL, /* Android - OpenSL ES. (Default on Android 2.3 and above) */ + FMOD_OUTPUTTYPE_WIIU, /* Wii U - AX. (Default on Wii U) */ + FMOD_OUTPUTTYPE_AUDIOOUT, /* PS4/PSVita - Audio Out. (Default on PS4 and PS Vita) */ + + FMOD_OUTPUTTYPE_MAX, /* Maximum number of output types supported. */ + FMOD_OUTPUTTYPE_FORCEINT = 65536 /* Makes sure this enum is signed 32bit. */ +} FMOD_OUTPUTTYPE; + + +/* +[ENUM] +[ + [DESCRIPTION] + Specify the destination of log output when using the logging version of FMOD. + + [REMARKS] + TTY destination can vary depending on platform, common examples include the + Visual Studio / Xcode output window, stderr and LogCat. + + [SEE_ALSO] + FMOD_Debug_Initialize +] +*/ +typedef enum +{ + FMOD_DEBUG_MODE_TTY, /* Default log location per platform, i.e. Visual Studio output window, stderr, LogCat, etc */ + FMOD_DEBUG_MODE_FILE, /* Write log to specified file path */ + FMOD_DEBUG_MODE_CALLBACK, /* Call specified callback with log information */ + + FMOD_DEBUG_MODE_FORCEINT = 65536 /* Makes sure this enum is signed 32bit. */ +} FMOD_DEBUG_MODE; + + +/* +[DEFINE] +[ + [NAME] + FMOD_DEBUG_FLAGS + + [DESCRIPTION] + Specify the requested information to be output when using the logging version of FMOD. + + [REMARKS] + + [SEE_ALSO] + FMOD_Debug_Initialize +] +*/ +#define FMOD_DEBUG_LEVEL_NONE 0x00000000 /* Disable all messages */ +#define FMOD_DEBUG_LEVEL_ERROR 0x00000001 /* Enable only error messages. */ +#define FMOD_DEBUG_LEVEL_WARNING 0x00000002 /* Enable warning and error messages. */ +#define FMOD_DEBUG_LEVEL_LOG 0x00000004 /* Enable informational, warning and error messages (default). */ +#define FMOD_DEBUG_TYPE_MEMORY 0x00000100 /* Verbose logging for memory operations, only use this if you are debugging a memory related issue. */ +#define FMOD_DEBUG_TYPE_FILE 0x00000200 /* Verbose logging for file access, only use this if you are debugging a file related issue. */ +#define FMOD_DEBUG_TYPE_CODEC 0x00000400 /* Verbose logging for codec initialization, only use this if you are debugging a codec related issue. */ +#define FMOD_DEBUG_TYPE_TRACE 0x00000800 /* Verbose logging for internal errors, use this for tracking the origin of error codes. */ +#define FMOD_DEBUG_DISPLAY_TIMESTAMPS 0x00010000 /* Display the time stamp of the log message in milliseconds. */ +#define FMOD_DEBUG_DISPLAY_LINENUMBERS 0x00020000 /* Display the source code file and line number for where the message originated. */ +#define FMOD_DEBUG_DISPLAY_THREAD 0x00040000 /* Display the thread ID of the calling function that generated the message. */ +/* [DEFINE_END] */ + + +/* +[DEFINE] +[ + [NAME] + FMOD_MEMORY_TYPE + + [DESCRIPTION] + Bit fields for memory allocation type being passed into FMOD memory callbacks. + + [REMARKS] + Remember this is a bitfield. You may get more than 1 bit set (ie physical + persistent) so do not simply switch on the types! You must check each bit individually or clear out the bits that you do not want within the callback.
+ Bits can be excluded if you want during Memory_Initialize so that you never get them. + + [SEE_ALSO] + FMOD_MEMORY_ALLOC_CALLBACK + FMOD_MEMORY_REALLOC_CALLBACK + FMOD_MEMORY_FREE_CALLBACK + Memory_Initialize +] +*/ +#define FMOD_MEMORY_NORMAL 0x00000000 /* Standard memory. */ +#define FMOD_MEMORY_STREAM_FILE 0x00000001 /* Stream file buffer, size controllable with System::setStreamBufferSize. */ +#define FMOD_MEMORY_STREAM_DECODE 0x00000002 /* Stream decode buffer, size controllable with FMOD_CREATESOUNDEXINFO::decodebuffersize. */ +#define FMOD_MEMORY_SAMPLEDATA 0x00000004 /* Sample data buffer. Raw audio data, usually PCM/MPEG/ADPCM/XMA data. */ +#define FMOD_MEMORY_DSP_BUFFER 0x00000008 /* DSP memory block allocated when more than 1 output exists on a DSP node. */ +#define FMOD_MEMORY_PLUGIN 0x00000010 /* Memory allocated by a third party plugin. */ +#define FMOD_MEMORY_XBOX360_PHYSICAL 0x00100000 /* Requires XPhysicalAlloc / XPhysicalFree. */ +#define FMOD_MEMORY_PERSISTENT 0x00200000 /* Persistent memory. Memory will be freed when System::release is called. */ +#define FMOD_MEMORY_SECONDARY 0x00400000 /* Secondary memory. Allocation should be in secondary memory. For example RSX on the PS3. */ +#define FMOD_MEMORY_ALL 0xFFFFFFFF +/* [DEFINE_END] */ + + +/* +[ENUM] +[ + [DESCRIPTION] + These are speaker types defined for use with the System::setSoftwareFormat command. + + [REMARKS] + Note below the phrase 'sound channels' is used. These are the subchannels inside a sound, they are not related and + have nothing to do with the FMOD class "Channel".
+ For example a mono sound has 1 sound channel, a stereo sound has 2 sound channels, and an AC3 or 6 channel wav file have 6 "sound channels".
+
+ FMOD_SPEAKERMODE_RAW
+ ---------------------
+ This mode is for output devices that are not specifically mono/stereo/quad/surround/5.1 or 7.1, but are multichannel.
+ Use System::setSoftwareFormat to specify the number of speakers you want to address, otherwise it will default to 2 (stereo).
+ Sound channels map to speakers sequentially, so a mono sound maps to output speaker 0, stereo sound maps to output speaker 0 & 1.
+ The user assumes knowledge of the speaker order. FMOD_SPEAKER enumerations may not apply, so raw channel indices should be used.
+ Multichannel sounds map input channels to output channels 1:1.
+ Channel::setPan and Channel::setPanLevels do not work.
+ Speaker levels must be manually set with Channel::setPanMatrix.
+
+ FMOD_SPEAKERMODE_MONO
+ ---------------------
+ This mode is for a 1 speaker arrangement.
+ Panning does not work in this speaker mode.
+ Mono, stereo and multichannel sounds have each sound channel played on the one speaker unity.
+ Mix behavior for multichannel sounds can be set with Channel::setPanMatrix.
+ Channel::setPanLevels does not work.
+
+ FMOD_SPEAKERMODE_STEREO
+ -----------------------
+ This mode is for 2 speaker arrangements that have a left and right speaker.
+
  • Mono sounds default to an even distribution between left and right. They can be panned with Channel::setPan.
    +
  • Stereo sounds default to the middle, or full left in the left speaker and full right in the right speaker. +
  • They can be cross faded with Channel::setPan.
    +
  • Multichannel sounds have each sound channel played on each speaker at unity.
    +
  • Mix behavior for multichannel sounds can be set with Channel::setPanMatrix.
    +
  • Channel::setPanLevels works but only front left and right parameters are used, the rest are ignored.
    +
    + FMOD_SPEAKERMODE_QUAD
    + ------------------------
    + This mode is for 4 speaker arrangements that have a front left, front right, surround left and a surround right speaker.
    +
  • Mono sounds default to an even distribution between front left and front right. They can be panned with Channel::setPan.
    +
  • Stereo sounds default to the left sound channel played on the front left, and the right sound channel played on the front right.
    +
  • They can be cross faded with Channel::setPan.
    +
  • Multichannel sounds default to all of their sound channels being played on each speaker in order of input.
    +
  • Mix behavior for multichannel sounds can be set with Channel::setPanMatrix.
    +
  • Channel::setPanLevels works but rear left, rear right, center and lfe are ignored.
    +
    + FMOD_SPEAKERMODE_SURROUND
    + ------------------------
    + This mode is for 5 speaker arrangements that have a left/right/center/surround left/surround right.
    +
  • Mono sounds default to the center speaker. They can be panned with Channel::setPan.
    +
  • Stereo sounds default to the left sound channel played on the front left, and the right sound channel played on the front right. +
  • They can be cross faded with Channel::setPan.
    +
  • Multichannel sounds default to all of their sound channels being played on each speaker in order of input. +
  • Mix behavior for multichannel sounds can be set with Channel::setPanMatrix.
    +
  • Channel::setPanLevels works but rear left / rear right are ignored.
    +
    + FMOD_SPEAKERMODE_5POINT1
    + ---------------------------------------------------------
    + This mode is for 5.1 speaker arrangements that have a left/right/center/surround left/surround right and a subwoofer speaker.
    +
  • Mono sounds default to the center speaker. They can be panned with Channel::setPan.
    +
  • Stereo sounds default to the left sound channel played on the front left, and the right sound channel played on the front right. +
  • They can be cross faded with Channel::setPan.
    +
  • Multichannel sounds default to all of their sound channels being played on each speaker in order of input. +
  • Mix behavior for multichannel sounds can be set with Channel::setPanMatrix.
    +
  • Channel::setPanLevels works but rear left / rear right are ignored.
    +
    + FMOD_SPEAKERMODE_7POINT1
    + ------------------------
    + This mode is for 7.1 speaker arrangements that have a left/right/center/surround left/surround right/rear left/rear right + and a subwoofer speaker.
    +
  • Mono sounds default to the center speaker. They can be panned with Channel::setPan.
    +
  • Stereo sounds default to the left sound channel played on the front left, and the right sound channel played on the front right. +
  • They can be cross faded with Channel::setPan.
    +
  • Multichannel sounds default to all of their sound channels being played on each speaker in order of input. +
  • Mix behavior for multichannel sounds can be set with Channel::setPanMatrix.
    +
  • Channel::setPanLevels works and every parameter is used to set the balance of a sound in any speaker.
    +
    + + [SEE_ALSO] + System::setSoftwareFormat + System::getSoftwareFormat + DSP::setChannelFormat +] +*/ +typedef enum +{ + FMOD_SPEAKERMODE_DEFAULT, /* Default speaker mode based on operating system/output mode. Windows = control panel setting, Xbox = 5.1, PS3 = 7.1 etc. */ + FMOD_SPEAKERMODE_RAW, /* There is no specific speakermode. Sound channels are mapped in order of input to output. Use System::setSoftwareFormat to specify speaker count. See remarks for more information. */ + FMOD_SPEAKERMODE_MONO, /* The speakers are monaural. */ + FMOD_SPEAKERMODE_STEREO, /* The speakers are stereo. */ + FMOD_SPEAKERMODE_QUAD, /* 4 speaker setup. This includes front left, front right, surround left, surround right. */ + FMOD_SPEAKERMODE_SURROUND, /* 5 speaker setup. This includes front left, front right, center, surround left, surround right. */ + FMOD_SPEAKERMODE_5POINT1, /* 5.1 speaker setup. This includes front left, front right, center, surround left, surround right and an LFE speaker. */ + FMOD_SPEAKERMODE_7POINT1, /* 7.1 speaker setup. This includes front left, front right, center, surround left, surround right, back left, back right and an LFE speaker. */ + + FMOD_SPEAKERMODE_MAX, /* Maximum number of speaker modes supported. */ + FMOD_SPEAKERMODE_FORCEINT = 65536 /* Makes sure this enum is signed 32bit. */ +} FMOD_SPEAKERMODE; + + +/* +[DEFINE] +[ + [NAME] + FMOD_MAX_CHANNEL_WIDTH + + [DESCRIPTION] + The maximum number of channels per frame of audio supported by audio files, buffers, connections and DSPs.
    + + [REMARKS] + + [SEE_ALSO] + FMOD_CHANNELORDER + FMOD_CREATESOUNDEXINFO + System::setSoftwareFormat + System::getDefaultMixMatrix + ChannelControl::setMixMatrix + ChannelControl::getMixMatrix + FMOD::DSP::setChannelFormat +] +*/ +#define FMOD_MAX_CHANNEL_WIDTH 32 +/* [DEFINE_END] */ + +/* +[DEFINE] +[ + [NAME] + FMOD_MAX_LISTENERS + + [DESCRIPTION] + The maximum number of listeners supported. + + [REMARKS] + + [SEE_ALSO] + System::set3DNumListeners + System::set3DListenerAttributes + System::get3DListenerAttributes +] +*/ +#define FMOD_MAX_LISTENERS 8 +/* [DEFINE_END] */ + + +/* +[ENUM] +[ + [DESCRIPTION] + Assigns an enumeration for a speaker index. + + [REMARKS] + + [SEE_ALSO] + System::setSpeakerPosition + System::getSpeakerPosition +] +*/ +typedef enum +{ + FMOD_SPEAKER_FRONT_LEFT, + FMOD_SPEAKER_FRONT_RIGHT, + FMOD_SPEAKER_FRONT_CENTER, + FMOD_SPEAKER_LOW_FREQUENCY, + FMOD_SPEAKER_SURROUND_LEFT, + FMOD_SPEAKER_SURROUND_RIGHT, + FMOD_SPEAKER_BACK_LEFT, + FMOD_SPEAKER_BACK_RIGHT, + + FMOD_SPEAKER_MAX, /* Maximum number of speaker types supported. */ + FMOD_SPEAKER_FORCEINT = 65536 /* Makes sure this enum is signed 32bit. */ +} FMOD_SPEAKER; + + +/* +[DEFINE] +[ + [NAME] + FMOD_CHANNELMASK + + [DESCRIPTION] + These are bitfields to describe for a certain number of channels in a signal, which channels are being represented.
    + For example, a signal could be 1 channel, but contain the LFE channel only.
    + + [REMARKS] + FMOD_CHANNELMASK_BACK_CENTER is not represented as an output speaker in fmod - but it is encountered in input formats and is down or upmixed appropriately to the nearest speakers.
    + + [SEE_ALSO] + DSP::setChannelFormat + DSP::getChannelFormat + FMOD_SPEAKERMODE +] +*/ +#define FMOD_CHANNELMASK_FRONT_LEFT 0x00000001 +#define FMOD_CHANNELMASK_FRONT_RIGHT 0x00000002 +#define FMOD_CHANNELMASK_FRONT_CENTER 0x00000004 +#define FMOD_CHANNELMASK_LOW_FREQUENCY 0x00000008 +#define FMOD_CHANNELMASK_SURROUND_LEFT 0x00000010 +#define FMOD_CHANNELMASK_SURROUND_RIGHT 0x00000020 +#define FMOD_CHANNELMASK_BACK_LEFT 0x00000040 +#define FMOD_CHANNELMASK_BACK_RIGHT 0x00000080 +#define FMOD_CHANNELMASK_BACK_CENTER 0x00000100 + +#define FMOD_CHANNELMASK_MONO (FMOD_CHANNELMASK_FRONT_LEFT) +#define FMOD_CHANNELMASK_STEREO (FMOD_CHANNELMASK_FRONT_LEFT | FMOD_CHANNELMASK_FRONT_RIGHT) +#define FMOD_CHANNELMASK_LRC (FMOD_CHANNELMASK_FRONT_LEFT | FMOD_CHANNELMASK_FRONT_RIGHT | FMOD_CHANNELMASK_FRONT_CENTER) +#define FMOD_CHANNELMASK_QUAD (FMOD_CHANNELMASK_FRONT_LEFT | FMOD_CHANNELMASK_FRONT_RIGHT | FMOD_CHANNELMASK_SURROUND_LEFT | FMOD_CHANNELMASK_SURROUND_RIGHT) +#define FMOD_CHANNELMASK_SURROUND (FMOD_CHANNELMASK_FRONT_LEFT | FMOD_CHANNELMASK_FRONT_RIGHT | FMOD_CHANNELMASK_FRONT_CENTER | FMOD_CHANNELMASK_SURROUND_LEFT | FMOD_CHANNELMASK_SURROUND_RIGHT) +#define FMOD_CHANNELMASK_5POINT1 (FMOD_CHANNELMASK_FRONT_LEFT | FMOD_CHANNELMASK_FRONT_RIGHT | FMOD_CHANNELMASK_FRONT_CENTER | FMOD_CHANNELMASK_LOW_FREQUENCY | FMOD_CHANNELMASK_SURROUND_LEFT | FMOD_CHANNELMASK_SURROUND_RIGHT) +#define FMOD_CHANNELMASK_5POINT1_REARS (FMOD_CHANNELMASK_FRONT_LEFT | FMOD_CHANNELMASK_FRONT_RIGHT | FMOD_CHANNELMASK_FRONT_CENTER | FMOD_CHANNELMASK_LOW_FREQUENCY | FMOD_CHANNELMASK_BACK_LEFT | FMOD_CHANNELMASK_BACK_RIGHT) +#define FMOD_CHANNELMASK_7POINT0 (FMOD_CHANNELMASK_FRONT_LEFT | FMOD_CHANNELMASK_FRONT_RIGHT | FMOD_CHANNELMASK_FRONT_CENTER | FMOD_CHANNELMASK_SURROUND_LEFT | FMOD_CHANNELMASK_SURROUND_RIGHT | FMOD_CHANNELMASK_BACK_LEFT | FMOD_CHANNELMASK_BACK_RIGHT) +#define FMOD_CHANNELMASK_7POINT1 (FMOD_CHANNELMASK_FRONT_LEFT | FMOD_CHANNELMASK_FRONT_RIGHT | FMOD_CHANNELMASK_FRONT_CENTER | FMOD_CHANNELMASK_LOW_FREQUENCY | FMOD_CHANNELMASK_SURROUND_LEFT | FMOD_CHANNELMASK_SURROUND_RIGHT | FMOD_CHANNELMASK_BACK_LEFT | FMOD_CHANNELMASK_BACK_RIGHT) +/* [DEFINE_END] */ + +/* +[ENUM] +[ + [DESCRIPTION] + When creating a multichannel sound, FMOD will pan them to their default speaker locations, for example a 6 channel sound will default to one channel per 5.1 output speaker.
    + Another example is a stereo sound. It will default to left = front left, right = front right.
    +
    + This is for sounds that are not 'default'. For example you might have a sound that is 6 channels but actually made up of 3 stereo pairs, that should all be located in front left, front right only. + + [REMARKS] + + [SEE_ALSO] + FMOD_CREATESOUNDEXINFO + FMOD_MAX_CHANNEL_WIDTH +] +*/ +typedef enum FMOD_CHANNELORDER +{ + FMOD_CHANNELORDER_DEFAULT, /* Left, Right, Center, LFE, Surround Left, Surround Right, Back Left, Back Right (see FMOD_SPEAKER enumeration) */ + FMOD_CHANNELORDER_WAVEFORMAT, /* Left, Right, Center, LFE, Back Left, Back Right, Surround Left, Surround Right (as per Microsoft .wav WAVEFORMAT structure master order) */ + FMOD_CHANNELORDER_PROTOOLS, /* Left, Center, Right, Surround Left, Surround Right, LFE */ + FMOD_CHANNELORDER_ALLMONO, /* Mono, Mono, Mono, Mono, Mono, Mono, ... (each channel all the way up to FMOD_MAX_CHANNEL_WIDTH channels are treated as if they were mono) */ + FMOD_CHANNELORDER_ALLSTEREO, /* Left, Right, Left, Right, Left, Right, ... (each pair of channels is treated as stereo all the way up to FMOD_MAX_CHANNEL_WIDTH channels) */ + FMOD_CHANNELORDER_ALSA, /* Left, Right, Surround Left, Surround Right, Center, LFE (as per Linux ALSA channel order) */ + + FMOD_CHANNELORDER_MAX, /* Maximum number of channel orderings supported. */ + FMOD_CHANNELORDER_FORCEINT = 65536 /* Makes sure this enum is signed 32bit. */ +} FMOD_CHANNELORDER; + + +/* +[ENUM] +[ + [DESCRIPTION] + These are plugin types defined for use with the System::getNumPlugins, + System::getPluginInfo and System::unloadPlugin functions. + + [REMARKS] + + [SEE_ALSO] + System::getNumPlugins + System::getPluginInfo + System::unloadPlugin +] +*/ +typedef enum +{ + FMOD_PLUGINTYPE_OUTPUT, /* The plugin type is an output module. FMOD mixed audio will play through one of these devices */ + FMOD_PLUGINTYPE_CODEC, /* The plugin type is a file format codec. FMOD will use these codecs to load file formats for playback. */ + FMOD_PLUGINTYPE_DSP, /* The plugin type is a DSP unit. FMOD will use these plugins as part of its DSP network to apply effects to output or generate sound in realtime. */ + + FMOD_PLUGINTYPE_MAX, /* Maximum number of plugin types supported. */ + FMOD_PLUGINTYPE_FORCEINT = 65536 /* Makes sure this enum is signed 32bit. */ +} FMOD_PLUGINTYPE; + + +/* +[DEFINE] +[ + [NAME] + FMOD_INITFLAGS + + [DESCRIPTION] + Initialization flags. Use them with System::init in the *flags* parameter to change various behavior. + + [REMARKS] + Use System::setAdvancedSettings to adjust settings for some of the features that are enabled by these flags. + + [SEE_ALSO] + System::init + System::update + System::setAdvancedSettings + Channel::set3DOcclusion +] +*/ +#define FMOD_INIT_NORMAL 0x00000000 /* Initialize normally */ +#define FMOD_INIT_STREAM_FROM_UPDATE 0x00000001 /* No stream thread is created internally. Streams are driven from System::update. Mainly used with non-realtime outputs. */ +#define FMOD_INIT_MIX_FROM_UPDATE 0x00000002 /* Win/PS3/Xbox 360 Only - FMOD Mixer thread is woken up to do a mix when System::update is called rather than waking periodically on its own timer. */ +#define FMOD_INIT_3D_RIGHTHANDED 0x00000004 /* FMOD will treat +X as right, +Y as up and +Z as backwards (towards you). */ +#define FMOD_INIT_CHANNEL_LOWPASS 0x00000100 /* All FMOD_3D based voices will add a software lowpass filter effect into the DSP chain which is automatically used when Channel::set3DOcclusion is used or the geometry API. This also causes sounds to sound duller when the sound goes behind the listener, as a fake HRTF style effect. Use System::setAdvancedSettings to disable or adjust cutoff frequency for this feature. */ +#define FMOD_INIT_CHANNEL_DISTANCEFILTER 0x00000200 /* All FMOD_3D based voices will add a software lowpass and highpass filter effect into the DSP chain which will act as a distance-automated bandpass filter. Use System::setAdvancedSettings to adjust the center frequency. */ +#define FMOD_INIT_PROFILE_ENABLE 0x00010000 /* Enable TCP/IP based host which allows FMOD Designer or FMOD Profiler to connect to it, and view memory, CPU and the DSP network graph in real-time. */ +#define FMOD_INIT_VOL0_BECOMES_VIRTUAL 0x00020000 /* Any sounds that are 0 volume will go virtual and not be processed except for having their positions updated virtually. Use System::setAdvancedSettings to adjust what volume besides zero to switch to virtual at. */ +#define FMOD_INIT_GEOMETRY_USECLOSEST 0x00040000 /* With the geometry engine, only process the closest polygon rather than accumulating all polygons the sound to listener line intersects. */ +#define FMOD_INIT_PREFER_DOLBY_DOWNMIX 0x00080000 /* When using FMOD_SPEAKERMODE_5POINT1 with a stereo output device, use the Dolby Pro Logic II downmix algorithm instead of the SRS Circle Surround algorithm. */ +#define FMOD_INIT_THREAD_UNSAFE 0x00100000 /* Disables thread safety for API calls. Only use this if FMOD low level is being called from a single thread, and if Studio API is not being used! */ +#define FMOD_INIT_PROFILE_METER_ALL 0x00200000 /* Slower, but adds level metering for every single DSP unit in the graph. Use DSP::setMeteringEnabled to turn meters off individually. */ +/* [DEFINE_END] */ + + +/* +[ENUM] +[ + [DESCRIPTION] + These definitions describe the type of song being played. + + [REMARKS] + + [SEE_ALSO] + Sound::getFormat +] +*/ +typedef enum +{ + FMOD_SOUND_TYPE_UNKNOWN, /* 3rd party / unknown plugin format. */ + FMOD_SOUND_TYPE_AIFF, /* AIFF. */ + FMOD_SOUND_TYPE_ASF, /* Microsoft Advanced Systems Format (ie WMA/ASF/WMV). */ + FMOD_SOUND_TYPE_DLS, /* Sound font / downloadable sound bank. */ + FMOD_SOUND_TYPE_FLAC, /* FLAC lossless codec. */ + FMOD_SOUND_TYPE_FSB, /* FMOD Sample Bank. */ + FMOD_SOUND_TYPE_IT, /* Impulse Tracker. */ + FMOD_SOUND_TYPE_MIDI, /* MIDI. */ + FMOD_SOUND_TYPE_MOD, /* Protracker / Fasttracker MOD. */ + FMOD_SOUND_TYPE_MPEG, /* MP2/MP3 MPEG. */ + FMOD_SOUND_TYPE_OGGVORBIS, /* Ogg vorbis. */ + FMOD_SOUND_TYPE_PLAYLIST, /* Information only from ASX/PLS/M3U/WAX playlists */ + FMOD_SOUND_TYPE_RAW, /* Raw PCM data. */ + FMOD_SOUND_TYPE_S3M, /* ScreamTracker 3. */ + FMOD_SOUND_TYPE_USER, /* User created sound. */ + FMOD_SOUND_TYPE_WAV, /* Microsoft WAV. */ + FMOD_SOUND_TYPE_XM, /* FastTracker 2 XM. */ + FMOD_SOUND_TYPE_XMA, /* Xbox360 XMA */ + FMOD_SOUND_TYPE_AUDIOQUEUE, /* iPhone hardware decoder, supports AAC, ALAC and MP3. */ + FMOD_SOUND_TYPE_AT9, /* PS4 / PSVita ATRAC 9 format */ + FMOD_SOUND_TYPE_VORBIS, /* Vorbis */ + FMOD_SOUND_TYPE_MEDIA_FOUNDATION,/* Windows Store Application built in system codecs */ + FMOD_SOUND_TYPE_MEDIACODEC, /* Android MediaCodec */ + FMOD_SOUND_TYPE_FADPCM, /* FMOD Adaptive Differential Pulse Code Modulation */ + + FMOD_SOUND_TYPE_MAX, /* Maximum number of sound types supported. */ + FMOD_SOUND_TYPE_FORCEINT = 65536 /* Makes sure this enum is signed 32bit. */ +} FMOD_SOUND_TYPE; + + +/* +[ENUM] +[ + [DESCRIPTION] + These definitions describe the native format of the hardware or software buffer that will be used. + + [REMARKS] + This is the format the native hardware or software buffer will be or is created in. + + [SEE_ALSO] + System::createSound + Sound::getFormat +] +*/ +typedef enum +{ + FMOD_SOUND_FORMAT_NONE, /* Unitialized / unknown. */ + FMOD_SOUND_FORMAT_PCM8, /* 8bit integer PCM data. */ + FMOD_SOUND_FORMAT_PCM16, /* 16bit integer PCM data. */ + FMOD_SOUND_FORMAT_PCM24, /* 24bit integer PCM data. */ + FMOD_SOUND_FORMAT_PCM32, /* 32bit integer PCM data. */ + FMOD_SOUND_FORMAT_PCMFLOAT, /* 32bit floating point PCM data. */ + FMOD_SOUND_FORMAT_BITSTREAM, /* Sound data is in its native compressed format. */ + + FMOD_SOUND_FORMAT_MAX, /* Maximum number of sound formats supported. */ + FMOD_SOUND_FORMAT_FORCEINT = 65536 /* Makes sure this enum is signed 32bit. */ +} FMOD_SOUND_FORMAT; + + +/* +[DEFINE] +[ + [NAME] + FMOD_MODE + + [DESCRIPTION] + Sound description bitfields, bitwise OR them together for loading and describing sounds. + + [REMARKS] + By default a sound will open as a static sound that is decompressed fully into memory to PCM. (ie equivalent of FMOD_CREATESAMPLE)
    + To have a sound stream instead, use FMOD_CREATESTREAM, or use the wrapper function System::createStream.
    + Some opening modes (ie FMOD_OPENUSER, FMOD_OPENMEMORY, FMOD_OPENMEMORY_POINT, FMOD_OPENRAW) will need extra information.
    + This can be provided using the FMOD_CREATESOUNDEXINFO structure. +
    + Specifying FMOD_OPENMEMORY_POINT will POINT to your memory rather allocating its own sound buffers and duplicating it internally.
    + This means you cannot free the memory while FMOD is using it, until after Sound::release is called. + With FMOD_OPENMEMORY_POINT, for PCM formats, only WAV, FSB, and RAW are supported. For compressed formats, only those formats supported by FMOD_CREATECOMPRESSEDSAMPLE are supported.
    + With FMOD_OPENMEMORY_POINT and FMOD_OPENRAW or PCM, if using them together, note that you must pad the data on each side by 16 bytes. This is so fmod can modify the ends of the data for looping/interpolation/mixing purposes. If a wav file, you will need to insert silence, and then reset loop points to stop the playback from playing that silence.
    +
    + Xbox 360 memory On Xbox 360 Specifying FMOD_OPENMEMORY_POINT to a virtual memory address will cause FMOD_ERR_INVALID_ADDRESS + to be returned. Use physical memory only for this functionality.
    +
    + FMOD_LOWMEM is used on a sound if you want to minimize the memory overhead, by having FMOD not allocate memory for certain + features that are not likely to be used in a game environment. These are :
    + 1. Sound::getName functionality is removed. 256 bytes per sound is saved.
    + + [SEE_ALSO] + System::createSound + System::createStream + Sound::setMode + Sound::getMode + Channel::setMode + Channel::getMode + Sound::set3DCustomRolloff + Channel::set3DCustomRolloff + Sound::getOpenState +] +*/ +#define FMOD_DEFAULT 0x00000000 /* Default for all modes listed below. FMOD_LOOP_OFF, FMOD_2D, FMOD_3D_WORLDRELATIVE, FMOD_3D_INVERSEROLLOFF */ +#define FMOD_LOOP_OFF 0x00000001 /* For non looping sounds. (DEFAULT). Overrides FMOD_LOOP_NORMAL / FMOD_LOOP_BIDI. */ +#define FMOD_LOOP_NORMAL 0x00000002 /* For forward looping sounds. */ +#define FMOD_LOOP_BIDI 0x00000004 /* For bidirectional looping sounds. (only works on software mixed static sounds). */ +#define FMOD_2D 0x00000008 /* Ignores any 3d processing. (DEFAULT). */ +#define FMOD_3D 0x00000010 /* Makes the sound positionable in 3D. Overrides FMOD_2D. */ +#define FMOD_CREATESTREAM 0x00000080 /* Decompress at runtime, streaming from the source provided (ie from disk). Overrides FMOD_CREATESAMPLE and FMOD_CREATECOMPRESSEDSAMPLE. Note a stream can only be played once at a time due to a stream only having 1 stream buffer and file handle. Open multiple streams to have them play concurrently. */ +#define FMOD_CREATESAMPLE 0x00000100 /* Decompress at loadtime, decompressing or decoding whole file into memory as the target sample format (ie PCM). Fastest for playback and most flexible. */ +#define FMOD_CREATECOMPRESSEDSAMPLE 0x00000200 /* Load MP2/MP3/IMAADPCM/Vorbis/AT9 or XMA into memory and leave it compressed. Vorbis/AT9 encoding only supported in the FSB file format. During playback the FMOD software mixer will decode it in realtime as a 'compressed sample'. Overrides FMOD_CREATESAMPLE. If the sound data is not one of the supported formats, it will behave as if it was created with FMOD_CREATESAMPLE and decode the sound into PCM. */ +#define FMOD_OPENUSER 0x00000400 /* Opens a user created static sample or stream. Use FMOD_CREATESOUNDEXINFO to specify format and/or read callbacks. If a user created 'sample' is created with no read callback, the sample will be empty. Use Sound::lock and Sound::unlock to place sound data into the sound if this is the case. */ +#define FMOD_OPENMEMORY 0x00000800 /* "name_or_data" will be interpreted as a pointer to memory instead of filename for creating sounds. Use FMOD_CREATESOUNDEXINFO to specify length. If used with FMOD_CREATESAMPLE or FMOD_CREATECOMPRESSEDSAMPLE, FMOD duplicates the memory into its own buffers. Your own buffer can be freed after open. If used with FMOD_CREATESTREAM, FMOD will stream out of the buffer whose pointer you passed in. In this case, your own buffer should not be freed until you have finished with and released the stream.*/ +#define FMOD_OPENMEMORY_POINT 0x10000000 /* "name_or_data" will be interpreted as a pointer to memory instead of filename for creating sounds. Use FMOD_CREATESOUNDEXINFO to specify length. This differs to FMOD_OPENMEMORY in that it uses the memory as is, without duplicating the memory into its own buffers. Cannot be freed after open, only after Sound::release. Will not work if the data is compressed and FMOD_CREATECOMPRESSEDSAMPLE is not used. */ +#define FMOD_OPENRAW 0x00001000 /* Will ignore file format and treat as raw pcm. Use FMOD_CREATESOUNDEXINFO to specify format. Requires at least defaultfrequency, numchannels and format to be specified before it will open. Must be little endian data. */ +#define FMOD_OPENONLY 0x00002000 /* Just open the file, dont prebuffer or read. Good for fast opens for info, or when sound::readData is to be used. */ +#define FMOD_ACCURATETIME 0x00004000 /* For System::createSound - for accurate Sound::getLength/Channel::setPosition on VBR MP3, and MOD/S3M/XM/IT/MIDI files. Scans file first, so takes longer to open. FMOD_OPENONLY does not affect this. */ +#define FMOD_MPEGSEARCH 0x00008000 /* For corrupted / bad MP3 files. This will search all the way through the file until it hits a valid MPEG header. Normally only searches for 4k. */ +#define FMOD_NONBLOCKING 0x00010000 /* For opening sounds and getting streamed subsounds (seeking) asyncronously. Use Sound::getOpenState to poll the state of the sound as it opens or retrieves the subsound in the background. */ +#define FMOD_UNIQUE 0x00020000 /* Unique sound, can only be played one at a time */ +#define FMOD_3D_HEADRELATIVE 0x00040000 /* Make the sound's position, velocity and orientation relative to the listener. */ +#define FMOD_3D_WORLDRELATIVE 0x00080000 /* Make the sound's position, velocity and orientation absolute (relative to the world). (DEFAULT) */ +#define FMOD_3D_INVERSEROLLOFF 0x00100000 /* This sound will follow the inverse rolloff model where mindistance = full volume, maxdistance = where sound stops attenuating, and rolloff is fixed according to the global rolloff factor. (DEFAULT) */ +#define FMOD_3D_LINEARROLLOFF 0x00200000 /* This sound will follow a linear rolloff model where mindistance = full volume, maxdistance = silence. */ +#define FMOD_3D_LINEARSQUAREROLLOFF 0x00400000 /* This sound will follow a linear-square rolloff model where mindistance = full volume, maxdistance = silence. */ +#define FMOD_3D_INVERSETAPEREDROLLOFF 0x00800000 /* This sound will follow the inverse rolloff model at distances close to mindistance and a linear-square rolloff close to maxdistance. */ +#define FMOD_3D_CUSTOMROLLOFF 0x04000000 /* This sound will follow a rolloff model defined by Sound::set3DCustomRolloff / Channel::set3DCustomRolloff. */ +#define FMOD_3D_IGNOREGEOMETRY 0x40000000 /* Is not affect by geometry occlusion. If not specified in Sound::setMode, or Channel::setMode, the flag is cleared and it is affected by geometry again. */ +/* Unused 0x01000000 Used to be FMOD_UNICODE */ +#define FMOD_IGNORETAGS 0x02000000 /* Skips id3v2/asf/etc tag checks when opening a sound, to reduce seek/read overhead when opening files (helps with CD performance). */ +#define FMOD_LOWMEM 0x08000000 /* Removes some features from samples to give a lower memory overhead, like Sound::getName. See remarks. */ +#define FMOD_LOADSECONDARYRAM 0x20000000 /* Load sound into the secondary RAM of supported platform. On PS3, sounds will be loaded into RSX/VRAM. */ +#define FMOD_VIRTUAL_PLAYFROMSTART 0x80000000 /* For sounds that start virtual (due to being quiet or low importance), instead of swapping back to audible, and playing at the correct offset according to time, this flag makes the sound play from the start. */ +/* [DEFINE_END] */ + + +/* +[ENUM] +[ + [DESCRIPTION] + These values describe what state a sound is in after FMOD_NONBLOCKING has been used to open it. + + [REMARKS] + With streams, if you are using FMOD_NONBLOCKING, note that if the user calls Sound::getSubSound, a stream will go into FMOD_OPENSTATE_SEEKING state and sound related commands will return FMOD_ERR_NOTREADY.
    + With streams, if you are using FMOD_NONBLOCKING, note that if the user calls Channel::getPosition, a stream will go into FMOD_OPENSTATE_SETPOSITION state and sound related commands will return FMOD_ERR_NOTREADY.
    + + [SEE_ALSO] + Sound::getOpenState + FMOD_MODE +] +*/ +typedef enum +{ + FMOD_OPENSTATE_READY = 0, /* Opened and ready to play. */ + FMOD_OPENSTATE_LOADING, /* Initial load in progress. */ + FMOD_OPENSTATE_ERROR, /* Failed to open - file not found, out of memory etc. See return value of Sound::getOpenState for what happened. */ + FMOD_OPENSTATE_CONNECTING, /* Connecting to remote host (internet sounds only). */ + FMOD_OPENSTATE_BUFFERING, /* Buffering data. */ + FMOD_OPENSTATE_SEEKING, /* Seeking to subsound and re-flushing stream buffer. */ + FMOD_OPENSTATE_PLAYING, /* Ready and playing, but not possible to release at this time without stalling the main thread. */ + FMOD_OPENSTATE_SETPOSITION, /* Seeking within a stream to a different position. */ + + FMOD_OPENSTATE_MAX, /* Maximum number of open state types. */ + FMOD_OPENSTATE_FORCEINT = 65536 /* Makes sure this enum is signed 32bit. */ +} FMOD_OPENSTATE; + + +/* +[ENUM] +[ + [DESCRIPTION] + These values are used with SoundGroup::setMaxAudibleBehavior to determine what happens when more sounds + are played than are specified with SoundGroup::setMaxAudible. + + [REMARKS] + When using FMOD_SOUNDGROUP_BEHAVIOR_MUTE, SoundGroup::setMuteFadeSpeed can be used to stop a sudden transition. + Instead, the time specified will be used to cross fade between the sounds that go silent and the ones that become audible. + + [SEE_ALSO] + SoundGroup::setMaxAudibleBehavior + SoundGroup::getMaxAudibleBehavior + SoundGroup::setMaxAudible + SoundGroup::getMaxAudible + SoundGroup::setMuteFadeSpeed + SoundGroup::getMuteFadeSpeed +] +*/ +typedef enum +{ + FMOD_SOUNDGROUP_BEHAVIOR_FAIL, /* Any sound played that puts the sound count over the SoundGroup::setMaxAudible setting, will simply fail during System::playSound. */ + FMOD_SOUNDGROUP_BEHAVIOR_MUTE, /* Any sound played that puts the sound count over the SoundGroup::setMaxAudible setting, will be silent, then if another sound in the group stops the sound that was silent before becomes audible again. */ + FMOD_SOUNDGROUP_BEHAVIOR_STEALLOWEST, /* Any sound played that puts the sound count over the SoundGroup::setMaxAudible setting, will steal the quietest / least important sound playing in the group. */ + + FMOD_SOUNDGROUP_BEHAVIOR_MAX, /* Maximum number of sound group behaviors. */ + FMOD_SOUNDGROUP_BEHAVIOR_FORCEINT = 65536 /* Makes sure this enum is signed 32bit. */ +} FMOD_SOUNDGROUP_BEHAVIOR; + + +/* +[ENUM] +[ + [DESCRIPTION] + These callback types are used with Channel::setCallback. + + [REMARKS] + Each callback has commanddata parameters passed as int unique to the type of callback.
    + See reference to FMOD_CHANNELCONTROL_CALLBACK to determine what they might mean for each type of callback.
    +
    + Note! Currently the user must call System::update for these callbacks to trigger! + + [SEE_ALSO] + Channel::setCallback + ChannelGroup::setCallback + FMOD_CHANNELCONTROL_CALLBACK + System::update +] +*/ +typedef enum +{ + FMOD_CHANNELCONTROL_CALLBACK_END, /* Called when a sound ends. */ + FMOD_CHANNELCONTROL_CALLBACK_VIRTUALVOICE, /* Called when a voice is swapped out or swapped in. */ + FMOD_CHANNELCONTROL_CALLBACK_SYNCPOINT, /* Called when a syncpoint is encountered. Can be from wav file markers. */ + FMOD_CHANNELCONTROL_CALLBACK_OCCLUSION, /* Called when the channel has its geometry occlusion value calculated. Can be used to clamp or change the value. */ + + FMOD_CHANNELCONTROL_CALLBACK_MAX, /* Maximum number of callback types supported. */ + FMOD_CHANNELCONTROL_CALLBACK_FORCEINT = 65536 /* Makes sure this enum is signed 32bit. */ +} FMOD_CHANNELCONTROL_CALLBACK_TYPE; + + +/* +[ENUM] +[ + [DESCRIPTION] + These enums denote special types of node within a DSP chain. + + [REMARKS] + + [SEE_ALSO] + Channel::getDSP + ChannelGroup::getDSP + ChannelControl::getNumDSPs +] +*/ +typedef enum +{ + FMOD_CHANNELCONTROL_DSP_HEAD = -1, /* Head of the DSP chain. Equivalent of index 0. */ + FMOD_CHANNELCONTROL_DSP_FADER = -2, /* Built in fader DSP. */ + FMOD_CHANNELCONTROL_DSP_PANNER = -3, /* Built in panner DSP. */ + FMOD_CHANNELCONTROL_DSP_TAIL = -4, /* Tail of the DSP chain. Equivalent of the number of dsps minus 1. */ + + FMOD_CHANNELCONTROL_DSP_FORCEINT = 65536 /* Makes sure this enum is signed 32bit. */ +} FMOD_CHANNELCONTROL_DSP_INDEX; + +/* +[ENUM] +[ + [DESCRIPTION] + Used to distinguish the instance type passed into FMOD_ERROR_CALLBACK. + + [REMARKS] + Cast the instance of FMOD_ERROR_CALLBACK to the appropriate class indicated by this enum. + + [SEE_ALSO] +] +*/ +typedef enum +{ + FMOD_ERRORCALLBACK_INSTANCETYPE_NONE, + FMOD_ERRORCALLBACK_INSTANCETYPE_SYSTEM, + FMOD_ERRORCALLBACK_INSTANCETYPE_CHANNEL, + FMOD_ERRORCALLBACK_INSTANCETYPE_CHANNELGROUP, + FMOD_ERRORCALLBACK_INSTANCETYPE_CHANNELCONTROL, + FMOD_ERRORCALLBACK_INSTANCETYPE_SOUND, + FMOD_ERRORCALLBACK_INSTANCETYPE_SOUNDGROUP, + FMOD_ERRORCALLBACK_INSTANCETYPE_DSP, + FMOD_ERRORCALLBACK_INSTANCETYPE_DSPCONNECTION, + FMOD_ERRORCALLBACK_INSTANCETYPE_GEOMETRY, + FMOD_ERRORCALLBACK_INSTANCETYPE_REVERB3D, + FMOD_ERRORCALLBACK_INSTANCETYPE_STUDIO_SYSTEM, + FMOD_ERRORCALLBACK_INSTANCETYPE_STUDIO_EVENTDESCRIPTION, + FMOD_ERRORCALLBACK_INSTANCETYPE_STUDIO_EVENTINSTANCE, + FMOD_ERRORCALLBACK_INSTANCETYPE_STUDIO_PARAMETERINSTANCE, + FMOD_ERRORCALLBACK_INSTANCETYPE_STUDIO_CUEINSTANCE, + FMOD_ERRORCALLBACK_INSTANCETYPE_STUDIO_BUS, + FMOD_ERRORCALLBACK_INSTANCETYPE_STUDIO_VCA, + FMOD_ERRORCALLBACK_INSTANCETYPE_STUDIO_BANK, + FMOD_ERRORCALLBACK_INSTANCETYPE_STUDIO_COMMANDREPLAY, + + FMOD_ERRORCALLBACK_INSTANCETYPE_FORCEINT = 65536 /* Makes sure this enum is signed 32bit. */ +} FMOD_ERRORCALLBACK_INSTANCETYPE; + +/* +[STRUCTURE] +[ + [DESCRIPTION] + Structure that is passed into FMOD_SYSTEM_CALLBACK for the FMOD_SYSTEM_CALLBACK_ERROR callback type. + + [REMARKS] + The instance pointer will be a type corresponding to the instanceType enum. + + [SEE_ALSO] + FMOD_ERRORCALLBACK_INSTANCETYPE +] +*/ +typedef struct +{ + FMOD_RESULT result; /* Error code result */ + FMOD_ERRORCALLBACK_INSTANCETYPE instancetype; /* Type of instance the error occurred on */ + void *instance; /* Instance pointer */ + const char *functionname; /* Function that the error occurred on */ + const char *functionparams; /* Function parameters that the error ocurred on */ +} FMOD_ERRORCALLBACK_INFO; + +/* +[DEFINE] +[ + [NAME] + FMOD_SYSTEM_CALLBACK_TYPE + + [DESCRIPTION] + These callback types are used with System::setCallback. + + [REMARKS] + Each callback has commanddata parameters passed as void* unique to the type of callback.
    + See reference to FMOD_SYSTEM_CALLBACK to determine what they might mean for each type of callback.
    +
    + Note! Using FMOD_SYSTEM_CALLBACK_DEVICELISTCHANGED (Windows only) will disable any automated device ejection/insertion handling by FMOD. Use this callback to control the behaviour yourself.
    +
    + Note! Using FMOD_SYSTEM_CALLBACK_DEVICELISTCHANGED (on Mac only) requires the application to be running an event loop which will allow external changes to device list to be detected by FMOD.
    +
    + Note! The 'system' object pointer will be null for FMOD_SYSTEM_CALLBACK_MEMORYALLOCATIONFAILED callback. + + [SEE_ALSO] + System::setCallback + System::update + DSP::addInput +] +*/ +#define FMOD_SYSTEM_CALLBACK_DEVICELISTCHANGED 0x00000001 /* Called from System::update when the enumerated list of devices has changed. */ +#define FMOD_SYSTEM_CALLBACK_DEVICELOST 0x00000002 /* Called from System::update when an output device has been lost due to control panel parameter changes and FMOD cannot automatically recover. */ +#define FMOD_SYSTEM_CALLBACK_MEMORYALLOCATIONFAILED 0x00000004 /* Called directly when a memory allocation fails somewhere in FMOD. (NOTE - 'system' will be NULL in this callback type.)*/ +#define FMOD_SYSTEM_CALLBACK_THREADCREATED 0x00000008 /* Called directly when a thread is created. */ +#define FMOD_SYSTEM_CALLBACK_BADDSPCONNECTION 0x00000010 /* Called when a bad connection was made with DSP::addInput. Usually called from mixer thread because that is where the connections are made. */ +#define FMOD_SYSTEM_CALLBACK_PREMIX 0x00000020 /* Called each tick before a mix update happens. */ +#define FMOD_SYSTEM_CALLBACK_POSTMIX 0x00000040 /* Called each tick after a mix update happens. */ +#define FMOD_SYSTEM_CALLBACK_ERROR 0x00000080 /* Called when each API function returns an error code, including delayed async functions. */ +#define FMOD_SYSTEM_CALLBACK_MIDMIX 0x00000100 /* Called each tick in mix update after clocks have been updated before the main mix occurs. */ +#define FMOD_SYSTEM_CALLBACK_THREADDESTROYED 0x00000200 /* Called directly when a thread is destroyed. */ +#define FMOD_SYSTEM_CALLBACK_PREUPDATE 0x00000400 /* Called at start of System::update function. */ +#define FMOD_SYSTEM_CALLBACK_POSTUPDATE 0x00000800 /* Called at end of System::update function. */ +#define FMOD_SYSTEM_CALLBACK_RECORDLISTCHANGED 0x00001000 /* Called from System::update when the enumerated list of recording devices has changed. */ +#define FMOD_SYSTEM_CALLBACK_ALL 0xFFFFFFFF /* Pass this mask to System::setCallback to receive all callback types. */ + +/* [DEFINE_END] */ + + +/* + FMOD Callbacks +*/ +typedef FMOD_RESULT (F_CALLBACK *FMOD_DEBUG_CALLBACK) (FMOD_DEBUG_FLAGS flags, const char *file, int line, const char *func, const char *message); + +typedef FMOD_RESULT (F_CALLBACK *FMOD_SYSTEM_CALLBACK) (FMOD_SYSTEM *system, FMOD_SYSTEM_CALLBACK_TYPE type, void *commanddata1, void *commanddata2, void *userdata); + +typedef FMOD_RESULT (F_CALLBACK *FMOD_CHANNELCONTROL_CALLBACK) (FMOD_CHANNELCONTROL *channelcontrol, FMOD_CHANNELCONTROL_TYPE controltype, FMOD_CHANNELCONTROL_CALLBACK_TYPE callbacktype, void *commanddata1, void *commanddata2); + +typedef FMOD_RESULT (F_CALLBACK *FMOD_SOUND_NONBLOCK_CALLBACK) (FMOD_SOUND *sound, FMOD_RESULT result); +typedef FMOD_RESULT (F_CALLBACK *FMOD_SOUND_PCMREAD_CALLBACK) (FMOD_SOUND *sound, void *data, unsigned int datalen); +typedef FMOD_RESULT (F_CALLBACK *FMOD_SOUND_PCMSETPOS_CALLBACK) (FMOD_SOUND *sound, int subsound, unsigned int position, FMOD_TIMEUNIT postype); + +typedef FMOD_RESULT (F_CALLBACK *FMOD_FILE_OPEN_CALLBACK) (const char *name, unsigned int *filesize, void **handle, void *userdata); +typedef FMOD_RESULT (F_CALLBACK *FMOD_FILE_CLOSE_CALLBACK) (void *handle, void *userdata); +typedef FMOD_RESULT (F_CALLBACK *FMOD_FILE_READ_CALLBACK) (void *handle, void *buffer, unsigned int sizebytes, unsigned int *bytesread, void *userdata); +typedef FMOD_RESULT (F_CALLBACK *FMOD_FILE_SEEK_CALLBACK) (void *handle, unsigned int pos, void *userdata); +typedef FMOD_RESULT (F_CALLBACK *FMOD_FILE_ASYNCREAD_CALLBACK) (FMOD_ASYNCREADINFO *info, void *userdata); +typedef FMOD_RESULT (F_CALLBACK *FMOD_FILE_ASYNCCANCEL_CALLBACK)(FMOD_ASYNCREADINFO *info, void *userdata); + +typedef void * (F_CALLBACK *FMOD_MEMORY_ALLOC_CALLBACK) (unsigned int size, FMOD_MEMORY_TYPE type, const char *sourcestr); +typedef void * (F_CALLBACK *FMOD_MEMORY_REALLOC_CALLBACK) (void *ptr, unsigned int size, FMOD_MEMORY_TYPE type, const char *sourcestr); +typedef void (F_CALLBACK *FMOD_MEMORY_FREE_CALLBACK) (void *ptr, FMOD_MEMORY_TYPE type, const char *sourcestr); + +typedef float (F_CALLBACK *FMOD_3D_ROLLOFF_CALLBACK) (FMOD_CHANNELCONTROL *channelcontrol, float distance); + + + + +/* +[ENUM] +[ + [DESCRIPTION] + List of interpolation types that the FMOD Studio software mixer supports. + + [REMARKS] + The default resampler type is FMOD_DSP_RESAMPLER_LINEAR.
    + Use System::setAdvancedSettings and the resamplerMethod member to tell FMOD the resampling quality you require for sample rate conversion during sound playback. + + [SEE_ALSO] + System::setAdvancedSettings + System::setAdvancedSettings + FMOD_ADVANCEDSETINGS +] +*/ +typedef enum +{ + FMOD_DSP_RESAMPLER_DEFAULT, /* Default interpolation method. Currently equal to FMOD_DSP_RESAMPLER_LINEAR. */ + FMOD_DSP_RESAMPLER_NOINTERP, /* No interpolation. High frequency aliasing hiss will be audible depending on the sample rate of the sound. */ + FMOD_DSP_RESAMPLER_LINEAR, /* Linear interpolation (default method). Fast and good quality, causes very slight lowpass effect on low frequency sounds. */ + FMOD_DSP_RESAMPLER_CUBIC, /* Cubic interpolation. Slower than linear interpolation but better quality. */ + FMOD_DSP_RESAMPLER_SPLINE, /* 5 point spline interpolation. Slowest resampling method but best quality. */ + + FMOD_DSP_RESAMPLER_MAX, /* Maximum number of resample methods supported. */ + FMOD_DSP_RESAMPLER_FORCEINT = 65536 /* Makes sure this enum is signed 32bit. */ +} FMOD_DSP_RESAMPLER; + + +/* +[ENUM] +[ + [DESCRIPTION] + List of connection types between 2 DSP nodes. + + [REMARKS] + FMOD_DSP_CONNECTION_TYPE_STANDARD
    + ----------------------------------
    + Default DSPConnection type. Audio is mixed from the input to the output DSP's audible buffer, meaning it will be part of the audible signal. A standard connection will execute its input DSP if it has not been executed before.
    +
    + FMOD_DSP_CONNECTION_TYPE_SIDECHAIN
    + ----------------------------------
    + Sidechain DSPConnection type. Audio is mixed from the input to the output DSP's sidechain buffer, meaning it will NOT be part of the audible signal. A sidechain connection will execute its input DSP if it has not been executed before.
    + The purpose of the seperate sidechain buffer in a DSP, is so that the DSP effect can privately access for analysis purposes. An example of use in this case, could be a compressor which analyzes the signal, to control its own effect parameters (ie a compression level or gain).
    +
    + For the effect developer, to accept sidechain data, the sidechain data will appear in the FMOD_DSP_STATE struct which is passed into the read callback of a DSP unit.
    + FMOD_DSP_STATE::sidechaindata and FMOD_DSP::sidechainchannels will hold the mixed result of any sidechain data flowing into it.
    +
    + FMOD_DSP_CONNECTION_TYPE_SEND
    + -----------------------------
    + Send DSPConnection type. Audio is mixed from the input to the output DSP's audible buffer, meaning it will be part of the audible signal. A send connection will NOT execute its input DSP if it has not been executed before.
    + A send connection will only read what exists at the input's buffer at the time of executing the output DSP unit (which can be considered the 'return')
    +
    + FMOD_DSP_CONNECTION_TYPE_SEND_SIDECHAIN
    + ---------------------------------------
    + Send sidechain DSPConnection type. Audio is mixed from the input to the output DSP's sidechain buffer, meaning it will NOT be part of the audible signal. A send sidechain connection will NOT execute its input DSP if it has not been executed before.
    + A send sidechain connection will only read what exists at the input's buffer at the time of executing the output DSP unit (which can be considered the 'sidechain return'). +
    + For the effect developer, to accept sidechain data, the sidechain data will appear in the FMOD_DSP_STATE struct which is passed into the read callback of a DSP unit.
    + FMOD_DSP_STATE::sidechaindata and FMOD_DSP::sidechainchannels will hold the mixed result of any sidechain data flowing into it. + + [SEE_ALSO] + DSP::addInput + DSPConnection::getType +] +*/ +typedef enum +{ + FMOD_DSPCONNECTION_TYPE_STANDARD, /* Default connection type. Audio is mixed from the input to the output DSP's audible buffer. */ + FMOD_DSPCONNECTION_TYPE_SIDECHAIN, /* Sidechain connection type. Audio is mixed from the input to the output DSP's sidechain buffer. */ + FMOD_DSPCONNECTION_TYPE_SEND, /* Send connection type. Audio is mixed from the input to the output DSP's audible buffer, but the input is NOT executed, only copied from. A standard connection or sidechain needs to make an input execute to generate data. */ + FMOD_DSPCONNECTION_TYPE_SEND_SIDECHAIN, /* Send sidechain connection type. Audio is mixed from the input to the output DSP's sidechain buffer, but the input is NOT executed, only copied from. A standard connection or sidechain needs to make an input execute to generate data. */ + + FMOD_DSPCONNECTION_TYPE_MAX, /* Maximum number of DSP connection types supported. */ + FMOD_DSPCONNECTION_TYPE_FORCEINT = 65536 /* Makes sure this enum is signed 32bit. */ +} FMOD_DSPCONNECTION_TYPE; + + +/* +[ENUM] +[ + [DESCRIPTION] + List of tag types that could be stored within a sound. These include id3 tags, metadata from netstreams and vorbis/asf data. + + [REMARKS] + + [SEE_ALSO] + Sound::getTag +] +*/ +typedef enum +{ + FMOD_TAGTYPE_UNKNOWN = 0, + FMOD_TAGTYPE_ID3V1, + FMOD_TAGTYPE_ID3V2, + FMOD_TAGTYPE_VORBISCOMMENT, + FMOD_TAGTYPE_SHOUTCAST, + FMOD_TAGTYPE_ICECAST, + FMOD_TAGTYPE_ASF, + FMOD_TAGTYPE_MIDI, + FMOD_TAGTYPE_PLAYLIST, + FMOD_TAGTYPE_FMOD, + FMOD_TAGTYPE_USER, + + FMOD_TAGTYPE_MAX, /* Maximum number of tag types supported. */ + FMOD_TAGTYPE_FORCEINT = 65536 /* Makes sure this enum is signed 32bit. */ +} FMOD_TAGTYPE; + + +/* +[ENUM] +[ + [DESCRIPTION] + List of data types that can be returned by Sound::getTag + + [REMARKS] + + [SEE_ALSO] + Sound::getTag +] +*/ +typedef enum +{ + FMOD_TAGDATATYPE_BINARY = 0, + FMOD_TAGDATATYPE_INT, + FMOD_TAGDATATYPE_FLOAT, + FMOD_TAGDATATYPE_STRING, + FMOD_TAGDATATYPE_STRING_UTF16, + FMOD_TAGDATATYPE_STRING_UTF16BE, + FMOD_TAGDATATYPE_STRING_UTF8, + FMOD_TAGDATATYPE_CDTOC, + + FMOD_TAGDATATYPE_MAX, /* Maximum number of tag datatypes supported. */ + FMOD_TAGDATATYPE_FORCEINT = 65536 /* Makes sure this enum is signed 32bit. */ +} FMOD_TAGDATATYPE; + + +/* +[STRUCTURE] +[ + [DESCRIPTION] + Structure describing a piece of tag data. + + [REMARKS] + Members marked with [r] mean the variable is modified by FMOD and is for reading purposes only. Do not change this value.
    + Members marked with [w] mean the variable can be written to. The user can set the value.
    + + [SEE_ALSO] + Sound::getTag + FMOD_TAGTYPE + FMOD_TAGDATATYPE +] +*/ +typedef struct FMOD_TAG +{ + FMOD_TAGTYPE type; /* [r] The type of this tag. */ + FMOD_TAGDATATYPE datatype; /* [r] The type of data that this tag contains */ + char *name; /* [r] The name of this tag i.e. "TITLE", "ARTIST" etc. */ + void *data; /* [r] Pointer to the tag data - its format is determined by the datatype member */ + unsigned int datalen; /* [r] Length of the data contained in this tag */ + FMOD_BOOL updated; /* [r] True if this tag has been updated since last being accessed with Sound::getTag */ +} FMOD_TAG; + + +/* +[DEFINE] +[ + [NAME] + FMOD_TIMEUNIT + + [DESCRIPTION] + List of time types that can be returned by Sound::getLength and used with Channel::setPosition or Channel::getPosition. + + [REMARKS] + Do not combine flags except FMOD_TIMEUNIT_BUFFERED. + + [SEE_ALSO] + Sound::getLength + Channel::setPosition + Channel::getPosition +] +*/ +#define FMOD_TIMEUNIT_MS 0x00000001 /* Milliseconds. */ +#define FMOD_TIMEUNIT_PCM 0x00000002 /* PCM samples, related to milliseconds * samplerate / 1000. */ +#define FMOD_TIMEUNIT_PCMBYTES 0x00000004 /* Bytes, related to PCM samples * channels * datawidth (ie 16bit = 2 bytes). */ +#define FMOD_TIMEUNIT_RAWBYTES 0x00000008 /* Raw file bytes of (compressed) sound data (does not include headers). Only used by Sound::getLength and Channel::getPosition. */ +#define FMOD_TIMEUNIT_PCMFRACTION 0x00000010 /* Fractions of 1 PCM sample. Unsigned int range 0 to 0xFFFFFFFF. Used for sub-sample granularity for DSP purposes. */ +#define FMOD_TIMEUNIT_MODORDER 0x00000100 /* MOD/S3M/XM/IT. Order in a sequenced module format. Use Sound::getFormat to determine the PCM format being decoded to. */ +#define FMOD_TIMEUNIT_MODROW 0x00000200 /* MOD/S3M/XM/IT. Current row in a sequenced module format. Sound::getLength will return the number of rows in the currently playing or seeked to pattern. */ +#define FMOD_TIMEUNIT_MODPATTERN 0x00000400 /* MOD/S3M/XM/IT. Current pattern in a sequenced module format. Sound::getLength will return the number of patterns in the song and Channel::getPosition will return the currently playing pattern. */ +#define FMOD_TIMEUNIT_BUFFERED 0x10000000 /* Time value as seen by buffered stream. This is always ahead of audible time, and is only used for processing. */ +/* [DEFINE_END] */ + +/* +[DEFINE] +[ + [NAME] + FMOD_PORT_INDEX + + [DESCRIPTION] + + [REMARKS] + + [SEE_ALSO] + System::AttachChannelGroupToPort +] +*/ +#define FMOD_PORT_INDEX_NONE -1ull /* Use when a port index is not required */ +/* [DEFINE_END] */ + + +/* +[STRUCTURE] +[ + [DESCRIPTION] + Use this structure with System::createSound when more control is needed over loading. + The possible reasons to use this with System::createSound are: + + - Loading a file from memory. + - Loading a file from within another larger (possibly wad/pak) file, by giving the loader an offset and length. + - To create a user created / non file based sound. + - To specify a starting subsound to seek to within a multi-sample sounds (ie FSB/DLS) when created as a stream. + - To specify which subsounds to load for multi-sample sounds (ie FSB/DLS) so that memory is saved and only a subset is actually loaded/read from disk. + - To specify 'piggyback' read and seek callbacks for capture of sound data as fmod reads and decodes it. Useful for ripping decoded PCM data from sounds as they are loaded / played. + - To specify a MIDI DLS sample set file to load when opening a MIDI file. + + See below on what members to fill for each of the above types of sound you want to create. + + [REMARKS] + This structure is optional! Specify 0 or NULL in System::createSound if you don't need it! + + Loading a file from memory. + + - Create the sound using the FMOD_OPENMEMORY flag. + - Mandatory. Specify 'length' for the size of the memory block in bytes. + - Other flags are optional. + + Loading a file from within another larger (possibly wad/pak) file, by giving the loader an offset and length. + + - Mandatory. Specify 'fileoffset' and 'length'. + - Other flags are optional. + + To create a user created / non file based sound. + + - Create the sound using the FMOD_OPENUSER flag. + - Mandatory. Specify 'defaultfrequency, 'numchannels' and 'format'. + - Other flags are optional. + + To specify a starting subsound to seek to and flush with, within a multi-sample stream (ie FSB/DLS). + + - Mandatory. Specify 'initialsubsound'. + + To specify which subsounds to load for multi-sample sounds (ie FSB/DLS) so that memory is saved and only a subset is actually loaded/read from disk. + + - Mandatory. Specify 'inclusionlist' and 'inclusionlistnum'. + + To specify 'piggyback' read and seek callbacks for capture of sound data as fmod reads and decodes it. Useful for ripping decoded PCM data from sounds as they are loaded / played. + + - Mandatory. Specify 'pcmreadcallback' and 'pcmseekcallback'. + + To specify a MIDI DLS sample set file to load when opening a MIDI file. + + - Mandatory. Specify 'dlsname'. + + Setting the 'decodebuffersize' is for cpu intensive codecs that may be causing stuttering, not file intensive codecs (ie those from CD or netstreams) which are normally + altered with System::setStreamBufferSize. As an example of cpu intensive codecs, an mp3 file will take more cpu to decode than a PCM wav file. + + If you have a stuttering effect, then it is using more cpu than the decode buffer playback rate can keep up with. Increasing the decode buffersize will most likely solve this problem. + + FSB codec. If inclusionlist and numsubsounds are used together, this will trigger a special mode where subsounds are shuffled down to save memory. (useful for large FSB + files where you only want to load 1 sound). There will be no gaps, ie no null subsounds. As an example, if there are 10,000 subsounds and there is an inclusionlist with only 1 entry, + and numsubsounds = 1, then subsound 0 will be that entry, and there will only be the memory allocated for 1 subsound. Previously there would still be 10,000 subsound pointers and other + associated codec entries allocated along with it multiplied by 10,000. + + Members marked with [r] mean the variable is modified by FMOD and is for reading purposes only. Do not change this value.
    + Members marked with [w] mean the variable can be written to. The user can set the value. + + [SEE_ALSO] + System::createSound + System::setStreamBufferSize + FMOD_MODE + FMOD_SOUND_FORMAT + FMOD_SOUND_TYPE + FMOD_CHANNELMASK + FMOD_CHANNELORDER + FMOD_MAX_CHANNEL_WIDTH +] +*/ +typedef struct FMOD_CREATESOUNDEXINFO +{ + int cbsize; /* [w] Size of this structure. This is used so the structure can be expanded in the future and still work on older versions of FMOD Studio. */ + unsigned int length; /* [w] Optional. Specify 0 to ignore. Number of bytes to load starting at 'fileoffset', or size of sound to create (if FMOD_OPENUSER is used). Required if loading from memory. If 0 is specified, then it will use the size of the file (unless loading from memory then an error will be returned). */ + unsigned int fileoffset; /* [w] Optional. Specify 0 to ignore. Offset from start of the file to start loading from. This is useful for loading files from inside big data files. */ + int numchannels; /* [w] Optional. Specify 0 to ignore. Number of channels in a sound mandatory if FMOD_OPENUSER or FMOD_OPENRAW is used. Can be specified up to FMOD_MAX_CHANNEL_WIDTH. */ + int defaultfrequency; /* [w] Optional. Specify 0 to ignore. Default frequency of sound in Hz, mandatory if FMOD_OPENUSER or FMOD_OPENRAW is used. Other formats use the frequency determined by the file format. */ + FMOD_SOUND_FORMAT format; /* [w] Optional. Specify 0 or FMOD_SOUND_FORMAT_NONE to ignore. Format of the sound, mandatory if FMOD_OPENUSER or FMOD_OPENRAW is used. Other formats use the format determined by the file format. */ + unsigned int decodebuffersize; /* [w] Optional. Specify 0 to ignore. For streams. This determines the size of the double buffer (in PCM samples) that a stream uses. Use this for user created streams if you want to determine the size of the callback buffer passed to you. Specify 0 to use FMOD's default size which is currently equivalent to 400ms of the sound format created/loaded. */ + int initialsubsound; /* [w] Optional. Specify 0 to ignore. In a multi-sample file format such as .FSB/.DLS, specify the initial subsound to seek to, only if FMOD_CREATESTREAM is used. */ + int numsubsounds; /* [w] Optional. Specify 0 to ignore or have no subsounds. In a sound created with FMOD_OPENUSER, specify the number of subsounds that are accessable with Sound::getSubSound. If not created with FMOD_OPENUSER, this will limit the number of subsounds loaded within a multi-subsound file. If using FSB, then if FMOD_CREATESOUNDEXINFO::inclusionlist is used, this will shuffle subsounds down so that there are not any gaps. It will mean that the indices of the sounds will be different. */ + int *inclusionlist; /* [w] Optional. Specify 0 to ignore. In a multi-sample format such as .FSB/.DLS it may be desirable to specify only a subset of sounds to be loaded out of the whole file. This is an array of subsound indices to load into memory when created. */ + int inclusionlistnum; /* [w] Optional. Specify 0 to ignore. This is the number of integers contained within the inclusionlist array. */ + FMOD_SOUND_PCMREAD_CALLBACK pcmreadcallback; /* [w] Optional. Specify 0 to ignore. Callback to 'piggyback' on FMOD's read functions and accept or even write PCM data while FMOD is opening the sound. Used for user sounds created with FMOD_OPENUSER or for capturing decoded data as FMOD reads it. */ + FMOD_SOUND_PCMSETPOS_CALLBACK pcmsetposcallback; /* [w] Optional. Specify 0 to ignore. Callback for when the user calls a seeking function such as Channel::setTime or Channel::setPosition within a multi-sample sound, and for when it is opened.*/ + FMOD_SOUND_NONBLOCK_CALLBACK nonblockcallback; /* [w] Optional. Specify 0 to ignore. Callback for successful completion, or error while loading a sound that used the FMOD_NONBLOCKING flag. Also called duing seeking, when setPosition is called or a stream is restarted. */ + const char *dlsname; /* [w] Optional. Specify 0 to ignore. Filename for a DLS sample set when loading a MIDI file. If not specified, on Windows it will attempt to open /windows/system32/drivers/gm.dls or /windows/system32/drivers/etc/gm.dls, on Mac it will attempt to load /System/Library/Components/CoreAudio.component/Contents/Resources/gs_instruments.dls, otherwise the MIDI will fail to open. Current DLS support is for level 1 of the specification. */ + const char *encryptionkey; /* [w] Optional. Specify 0 to ignore. Key for encrypted FSB file. Without this key an encrypted FSB file will not load. */ + int maxpolyphony; /* [w] Optional. Specify 0 to ignore. For sequenced formats with dynamic channel allocation such as .MID and .IT, this specifies the maximum voice count allowed while playing. .IT defaults to 64. .MID defaults to 32. */ + void *userdata; /* [w] Optional. Specify 0 to ignore. This is user data to be attached to the sound during creation. Access via Sound::getUserData. Note: This is not passed to FMOD_FILE_OPEN_CALLBACK - use fileuserdata for that. */ + FMOD_SOUND_TYPE suggestedsoundtype; /* [w] Optional. Specify 0 or FMOD_SOUND_TYPE_UNKNOWN to ignore. Instead of scanning all codec types, use this to speed up loading by making it jump straight to this codec. */ + FMOD_FILE_OPEN_CALLBACK fileuseropen; /* [w] Optional. Specify 0 to ignore. Callback for opening this file. */ + FMOD_FILE_CLOSE_CALLBACK fileuserclose; /* [w] Optional. Specify 0 to ignore. Callback for closing this file. */ + FMOD_FILE_READ_CALLBACK fileuserread; /* [w] Optional. Specify 0 to ignore. Callback for reading from this file. */ + FMOD_FILE_SEEK_CALLBACK fileuserseek; /* [w] Optional. Specify 0 to ignore. Callback for seeking within this file. */ + FMOD_FILE_ASYNCREAD_CALLBACK fileuserasyncread; /* [w] Optional. Specify 0 to ignore. Callback for seeking within this file. */ + FMOD_FILE_ASYNCCANCEL_CALLBACK fileuserasynccancel;/* [w] Optional. Specify 0 to ignore. Callback for seeking within this file. */ + void *fileuserdata; /* [w] Optional. Specify 0 to ignore. User data to be passed into the file callbacks. */ + FMOD_CHANNELORDER channelorder; /* [w] Optional. Specify 0 to ignore. Use this to differ the way fmod maps multichannel sounds to speakers. See FMOD_CHANNELORDER for more. */ + FMOD_CHANNELMASK channelmask; /* [w] Optional. Specify 0 to ignore. Use this to specify which channels map to which speakers. See FMOD_CHANNELMASK for more. */ + FMOD_SOUNDGROUP *initialsoundgroup; /* [w] Optional. Specify 0 to ignore. Specify a sound group if required, to put sound in as it is created. */ + unsigned int initialseekposition;/* [w] Optional. Specify 0 to ignore. For streams. Specify an initial position to seek the stream to. */ + FMOD_TIMEUNIT initialseekpostype; /* [w] Optional. Specify 0 to ignore. For streams. Specify the time unit for the position set in initialseekposition. */ + int ignoresetfilesystem;/* [w] Optional. Specify 0 to ignore. Set to 1 to use fmod's built in file system. Ignores setFileSystem callbacks and also FMOD_CREATESOUNEXINFO file callbacks. Useful for specific cases where you don't want to use your own file system but want to use fmod's file system (ie net streaming). */ + unsigned int audioqueuepolicy; /* [w] Optional. Specify 0 or FMOD_AUDIOQUEUE_CODECPOLICY_DEFAULT to ignore. Policy used to determine whether hardware or software is used for decoding, see FMOD_AUDIOQUEUE_CODECPOLICY for options (iOS >= 3.0 required, otherwise only hardware is available) */ + unsigned int minmidigranularity; /* [w] Optional. Specify 0 to ignore. Allows you to set a minimum desired MIDI mixer granularity. Values smaller than 512 give greater than default accuracy at the cost of more CPU and vice versa. Specify 0 for default (512 samples). */ + int nonblockthreadid; /* [w] Optional. Specify 0 to ignore. Specifies a thread index to execute non blocking load on. Allows for up to 5 threads to be used for loading at once. This is to avoid one load blocking another. Maximum value = 4. */ +} FMOD_CREATESOUNDEXINFO; + + +/* +[STRUCTURE] +[ + [DESCRIPTION] + Structure defining a reverb environment.
    + + [REMARKS] + Note the default reverb properties are the same as the FMOD_PRESET_GENERIC preset.
    + Note that integer values that typically range from -10,000 to 1000 are represented in decibels, + and are of a logarithmic scale, not linear, wheras float values are always linear.
    +
    + The numerical values listed below are the maximum, minimum and default values for each variable respectively.
    +
    + Hardware voice / Platform Specific reverb support.
    + WII See FMODWII.H for hardware specific reverb functionality.
    + 3DS See FMOD3DS.H for hardware specific reverb functionality.
    + PSP See FMODWII.H for hardware specific reverb functionality.
    +
    + Members marked with [r] mean the variable is modified by FMOD and is for reading purposes only. Do not change this value.
    + Members marked with [w] mean the variable can be written to. The user can set the value.
    + Members marked with [r/w] are either read or write depending on if you are using System::setReverbProperties (w) or System::getReverbProperties (r). + + [SEE_ALSO] + System::setReverbProperties + System::getReverbProperties + FMOD_REVERB_PRESETS +] +*/ +typedef struct FMOD_REVERB_PROPERTIES +{ /* MIN MAX DEFAULT DESCRIPTION */ + float DecayTime; /* [r/w] 0.0 20000.0 1500.0 Reverberation decay time in ms */ + float EarlyDelay; /* [r/w] 0.0 300.0 7.0 Initial reflection delay time */ + float LateDelay; /* [r/w] 0.0 100 11.0 Late reverberation delay time relative to initial reflection */ + float HFReference; /* [r/w] 20.0 20000.0 5000 Reference high frequency (hz) */ + float HFDecayRatio; /* [r/w] 10.0 100.0 50.0 High-frequency to mid-frequency decay time ratio */ + float Diffusion; /* [r/w] 0.0 100.0 100.0 Value that controls the echo density in the late reverberation decay. */ + float Density; /* [r/w] 0.0 100.0 100.0 Value that controls the modal density in the late reverberation decay */ + float LowShelfFrequency; /* [r/w] 20.0 1000.0 250.0 Reference low frequency (hz) */ + float LowShelfGain; /* [r/w] -36.0 12.0 0.0 Relative room effect level at low frequencies */ + float HighCut; /* [r/w] 20.0 20000.0 20000.0 Relative room effect level at high frequencies */ + float EarlyLateMix; /* [r/w] 0.0 100.0 50.0 Early reflections level relative to room effect */ + float WetLevel; /* [r/w] -80.0 20.0 -6.0 Room effect level (at mid frequencies) */ +} FMOD_REVERB_PROPERTIES; + + +/* +[DEFINE] +[ + [NAME] + FMOD_REVERB_PRESETS + + [DESCRIPTION] + A set of predefined environment PARAMETERS.
    + These are used to initialize an FMOD_REVERB_PROPERTIES structure statically.
    + i.e.
    + FMOD_REVERB_PROPERTIES prop = FMOD_PRESET_GENERIC; + + [REMARKS] + + [SEE_ALSO] + System::setReverbProperties + System::getReverbProperties +] +*/ +/* Decay LateDly HFDecay Densty LoGain E/L-Mix + EarlyDly HFRef Diffus LoFreq HiCut WetLvl */ +#define FMOD_PRESET_OFF { 1000, 7, 11, 5000, 100, 100, 100, 250, 0, 20, 96, -80.0f } +#define FMOD_PRESET_GENERIC { 1500, 7, 11, 5000, 83, 100, 100, 250, 0, 14500, 96, -8.0f } +#define FMOD_PRESET_PADDEDCELL { 170, 1, 2, 5000, 10, 100, 100, 250, 0, 160, 84, -7.8f } +#define FMOD_PRESET_ROOM { 400, 2, 3, 5000, 83, 100, 100, 250, 0, 6050, 88, -9.4f } +#define FMOD_PRESET_BATHROOM { 1500, 7, 11, 5000, 54, 100, 60, 250, 0, 2900, 83, 0.5f } +#define FMOD_PRESET_LIVINGROOM { 500, 3, 4, 5000, 10, 100, 100, 250, 0, 160, 58, -19.0f } +#define FMOD_PRESET_STONEROOM { 2300, 12, 17, 5000, 64, 100, 100, 250, 0, 7800, 71, -8.5f } +#define FMOD_PRESET_AUDITORIUM { 4300, 20, 30, 5000, 59, 100, 100, 250, 0, 5850, 64, -11.7f } +#define FMOD_PRESET_CONCERTHALL { 3900, 20, 29, 5000, 70, 100, 100, 250, 0, 5650, 80, -9.8f } +#define FMOD_PRESET_CAVE { 2900, 15, 22, 5000, 100, 100, 100, 250, 0, 20000, 59, -11.3f } +#define FMOD_PRESET_ARENA { 7200, 20, 30, 5000, 33, 100, 100, 250, 0, 4500, 80, -9.6f } +#define FMOD_PRESET_HANGAR { 10000, 20, 30, 5000, 23, 100, 100, 250, 0, 3400, 72, -7.4f } +#define FMOD_PRESET_CARPETTEDHALLWAY { 300, 2, 30, 5000, 10, 100, 100, 250, 0, 500, 56, -24.0f } +#define FMOD_PRESET_HALLWAY { 1500, 7, 11, 5000, 59, 100, 100, 250, 0, 7800, 87, -5.5f } +#define FMOD_PRESET_STONECORRIDOR { 270, 13, 20, 5000, 79, 100, 100, 250, 0, 9000, 86, -6.0f } +#define FMOD_PRESET_ALLEY { 1500, 7, 11, 5000, 86, 100, 100, 250, 0, 8300, 80, -9.8f } +#define FMOD_PRESET_FOREST { 1500, 162, 88, 5000, 54, 79, 100, 250, 0, 760, 94, -12.3f } +#define FMOD_PRESET_CITY { 1500, 7, 11, 5000, 67, 50, 100, 250, 0, 4050, 66, -26.0f } +#define FMOD_PRESET_MOUNTAINS { 1500, 300, 100, 5000, 21, 27, 100, 250, 0, 1220, 82, -24.0f } +#define FMOD_PRESET_QUARRY { 1500, 61, 25, 5000, 83, 100, 100, 250, 0, 3400, 100, -5.0f } +#define FMOD_PRESET_PLAIN { 1500, 179, 100, 5000, 50, 21, 100, 250, 0, 1670, 65, -28.0f } +#define FMOD_PRESET_PARKINGLOT { 1700, 8, 12, 5000, 100, 100, 100, 250, 0, 20000, 56, -19.5f } +#define FMOD_PRESET_SEWERPIPE { 2800, 14, 21, 5000, 14, 80, 60, 250, 0, 3400, 66, 1.2f } +#define FMOD_PRESET_UNDERWATER { 1500, 7, 11, 5000, 10, 100, 100, 250, 0, 500, 92, 7.0f } +/* [DEFINE_END] */ + + +/* +[STRUCTURE] +[ + [DESCRIPTION] + Settings for advanced features like configuring memory and cpu usage for the FMOD_CREATECOMPRESSEDSAMPLE feature. + + [REMARKS] + maxMPEGCodecs / maxADPCMCodecs / maxXMACodecs will determine the maximum cpu usage of playing realtime samples. Use this to lower potential excess cpu usage and also control memory usage.
    +
    + maxPCMCodecs is for use with PS3 only. It will determine the maximum number of PCM voices that can be played at once. This includes streams of any format and all sounds created + *without* the FMOD_CREATECOMPRESSEDSAMPLE flag. +
    + Memory will be allocated for codecs 'up front' (during System::init) if these values are specified as non zero. If any are zero, it allocates memory for the codec whenever a file of the type in question is loaded. So if maxMPEGCodecs is 0 for example, it will allocate memory for the mpeg codecs the first time an mp3 is loaded or an mp3 based .FSB file is loaded.
    +
    + Due to inefficient encoding techniques on certain .wav based ADPCM files, FMOD can can need an extra 29720 bytes per codec. This means for lowest memory consumption. Use FSB as it uses an optimal/small ADPCM block size.
    +
    + Members marked with [r] mean the variable is modified by FMOD and is for reading purposes only. Do not change this value.
    + Members marked with [w] mean the variable can be written to. The user can set the value.
    + Members marked with [r/w] are either read or write depending on if you are using System::setAdvancedSettings (w) or System::getAdvancedSettings (r). + + [SEE_ALSO] + System::setAdvancedSettings + System::getAdvancedSettings + System::init + FMOD_MODE +] +*/ +typedef struct FMOD_ADVANCEDSETTINGS +{ + int cbSize; /* [w] Size of this structure. Use sizeof(FMOD_ADVANCEDSETTINGS) NOTE: This must be set before calling System::getAdvancedSettings or System::setAdvancedSettings! */ + int maxMPEGCodecs; /* [r/w] Optional. Specify 0 to ignore. For use with FMOD_CREATECOMPRESSEDSAMPLE only. MPEG codecs consume 22,216 bytes per instance and this number will determine how many MPEG channels can be played simultaneously. Default = 32. */ + int maxADPCMCodecs; /* [r/w] Optional. Specify 0 to ignore. For use with FMOD_CREATECOMPRESSEDSAMPLE only. ADPCM codecs consume 2,480 bytes per instance and this number will determine how many ADPCM channels can be played simultaneously. Default = 32. */ + int maxXMACodecs; /* [r/w] Optional. Specify 0 to ignore. For use with FMOD_CREATECOMPRESSEDSAMPLE only. XMA codecs consume 6,263 bytes per instance and this number will determine how many XMA channels can be played simultaneously. Default = 32. */ + int maxVorbisCodecs; /* [r/w] Optional. Specify 0 to ignore. For use with FMOD_CREATECOMPRESSEDSAMPLE only. Vorbis codecs consume 16,512 bytes per instance and this number will determine how many Vorbis channels can be played simultaneously. Default = 32. */ + int maxAT9Codecs; /* [r/w] Optional. Specify 0 to ignore. For use with FMOD_CREATECOMPRESSEDSAMPLE only. AT9 codecs consume 20,664 bytes per instance and this number will determine how many AT9 channels can be played simultaneously. Default = 32. */ + int maxFADPCMCodecs; /* [r/w] Optional. Specify 0 to ignore. For use with FMOD_CREATECOMPRESSEDSAMPLE only. FADPCM codecs consume 2,232 bytes per instance and this number will determine how many FADPCM channels can be played simultaneously. Default = 32. */ + int maxPCMCodecs; /* [r/w] Optional. Specify 0 to ignore. For use with PS3 only. PCM codecs consume 2,536 bytes per instance and this number will determine how many streams and PCM voices can be played simultaneously. Default = 32. */ + int ASIONumChannels; /* [r/w] Optional. Specify 0 to ignore. Number of channels available on the ASIO device. */ + char **ASIOChannelList; /* [r/w] Optional. Specify 0 to ignore. Pointer to an array of strings (number of entries defined by ASIONumChannels) with ASIO channel names. */ + FMOD_SPEAKER *ASIOSpeakerList; /* [r/w] Optional. Specify 0 to ignore. Pointer to a list of speakers that the ASIO channels map to. This can be called after System::init to remap ASIO output. */ + float HRTFMinAngle; /* [r/w] Optional. For use with FMOD_INIT_HRTF_LOWPASS. The angle range (0-360) of a 3D sound in relation to the listener, at which the HRTF function begins to have an effect. 0 = in front of the listener. 180 = from 90 degrees to the left of the listener to 90 degrees to the right. 360 = behind the listener. Default = 180.0. */ + float HRTFMaxAngle; /* [r/w] Optional. For use with FMOD_INIT_HRTF_LOWPASS. The angle range (0-360) of a 3D sound in relation to the listener, at which the HRTF function has maximum effect. 0 = front of the listener. 180 = from 90 degrees to the left of the listener to 90 degrees to the right. 360 = behind the listener. Default = 360.0. */ + float HRTFFreq; /* [r/w] Optional. Specify 0 to ignore. For use with FMOD_INIT_HRTF_LOWPASS. The cutoff frequency of the HRTF's lowpass filter function when at maximum effect. (i.e. at HRTFMaxAngle). Default = 4000.0. */ + float vol0virtualvol; /* [r/w] Optional. Specify 0 to ignore. For use with FMOD_INIT_VOL0_BECOMES_VIRTUAL. If this flag is used, and the volume is below this, then the sound will become virtual. Use this value to raise the threshold to a different point where a sound goes virtual. */ + unsigned int defaultDecodeBufferSize; /* [r/w] Optional. Specify 0 to ignore. For streams. This determines the default size of the double buffer (in milliseconds) that a stream uses. Default = 400ms */ + unsigned short profilePort; /* [r/w] Optional. Specify 0 to ignore. For use with FMOD_INIT_PROFILE_ENABLE. Specify the port to listen on for connections by the profiler application. */ + unsigned int geometryMaxFadeTime; /* [r/w] Optional. Specify 0 to ignore. The maximum time in miliseconds it takes for a channel to fade to the new level when its occlusion changes. */ + float distanceFilterCenterFreq; /* [r/w] Optional. Specify 0 to ignore. For use with FMOD_INIT_DISTANCE_FILTERING. The default center frequency in Hz for the distance filtering effect. Default = 1500.0. */ + int reverb3Dinstance; /* [r/w] Optional. Specify 0 to ignore. Out of 0 to 3, 3d reverb spheres will create a phyical reverb unit on this instance slot. See FMOD_REVERB_PROPERTIES. */ + int DSPBufferPoolSize; /* [r/w] Optional. Specify 0 to ignore. Number of buffers in DSP buffer pool. Each buffer will be DSPBlockSize * sizeof(float) * SpeakerModeChannelCount. ie 7.1 @ 1024 DSP block size = 8 * 1024 * 4 = 32kb. Default = 8. */ + unsigned int stackSizeStream; /* [r/w] Optional. Specify 0 to ignore. Specify the stack size for the FMOD Stream thread in bytes. Useful for custom codecs that use excess stack. Default 49,152 (48kb) */ + unsigned int stackSizeNonBlocking; /* [r/w] Optional. Specify 0 to ignore. Specify the stack size for the FMOD_NONBLOCKING loading thread. Useful for custom codecs that use excess stack. Default 65,536 (64kb) */ + unsigned int stackSizeMixer; /* [r/w] Optional. Specify 0 to ignore. Specify the stack size for the FMOD mixer thread. Useful for custom dsps that use excess stack. Default 49,152 (48kb) */ + FMOD_DSP_RESAMPLER resamplerMethod; /* [r/w] Optional. Specify 0 to ignore. Resampling method used with fmod's software mixer. See FMOD_DSP_RESAMPLER for details on methods. */ + unsigned int commandQueueSize; /* [r/w] Optional. Specify 0 to ignore. Specify the command queue size for thread safe processing. Default 2048 (2kb) */ + unsigned int randomSeed; /* [r/w] Optional. Specify 0 to ignore. Seed value that FMOD will use to initialize its internal random number generators. */ +} FMOD_ADVANCEDSETTINGS; + + +/* +[DEFINE] +[ + [NAME] + FMOD_DRIVER_STATE + + [DESCRIPTION] + Flags that provide additional information about a particular driver. + + [REMARKS] + + [SEE_ALSO] + System::getRecordDriverInfo +] +*/ +#define FMOD_DRIVER_STATE_CONNECTED 0x00000001 /* Device is currently plugged in. */ +#define FMOD_DRIVER_STATE_DEFAULT 0x00000002 /* Device is the users preferred choice. */ +/* [DEFINE_END] */ + + +/*$ preserve start $*/ + +#include "fmod_codec.h" +#include "fmod_dsp.h" +#include "fmod_output.h" + +#endif + +/*$ preserve end $*/ diff --git a/Plugins/FMODStudio/Source/FMODStudio/Public/FMOD/fmod_dsp.h b/Plugins/FMODStudio/Source/FMODStudio/Public/FMOD/fmod_dsp.h new file mode 100644 index 0000000..6aaf9de --- /dev/null +++ b/Plugins/FMODStudio/Source/FMODStudio/Public/FMOD/fmod_dsp.h @@ -0,0 +1,850 @@ +/* ========================================================================================== */ +/* FMOD Studio - DSP header file. Copyright (c), Firelight Technologies Pty, Ltd. 2004-2016. */ +/* */ +/* Use this header if you are interested in delving deeper into the FMOD software mixing / */ +/* DSP engine. */ +/* Also use this header if you are wanting to develop your own DSP plugin to use with FMOD's */ +/* dsp system. With this header you can make your own DSP plugin that FMOD can */ +/* register and use. See the documentation and examples on how to make a working plugin. */ +/* */ +/* ========================================================================================== */ + +#ifndef _FMOD_DSP_H +#define _FMOD_DSP_H + +#include "fmod_dsp_effects.h" + +typedef struct FMOD_DSP_STATE FMOD_DSP_STATE; + +/* +[STRUCTURE] +[ + [DESCRIPTION] + Structure for FMOD_DSP_PROCESS_CALLBACK input and output buffers. + + [REMARKS] + Members marked with [r] mean the variable is modified by FMOD and is for reading purposes only. Do not change this value.
    + Members marked with [w] mean the variable can be written to. The user can set the value.
    + + [SEE_ALSO] + FMOD_DSP_DESCRIPTION +] +*/ +typedef struct FMOD_DSP_BUFFER_ARRAY +{ + int numbuffers; /* [r/w] number of buffers */ + int *buffernumchannels; /* [r/w] array of number of channels for each buffer */ + FMOD_CHANNELMASK *bufferchannelmask; /* [r/w] array of channel masks for each buffer */ + float **buffers; /* [r/w] array of buffers */ + FMOD_SPEAKERMODE speakermode; /* [r/w] speaker mode for all buffers in the array */ +} FMOD_DSP_BUFFER_ARRAY; + +/* +[ENUM] +[ + [DESCRIPTION] + Operation type for FMOD_DSP_PROCESS_CALLBACK. + + [REMARKS] + A process callback will be called twice per mix for a DSP unit. Once with the FMOD_DSP_PROCESS_QUERY command, then conditionally, FMOD_DSP_PROCESS_PERFORM.
    + FMOD_DSP_PROCESS_QUERY is to be handled only by filling out the outputarray information, and returning a relevant return code.
    + It should not really do any logic besides checking and returning one of the following codes:
    + - FMOD_OK - Meaning yes, it should execute the dsp process function with FMOD_DSP_PROCESS_PERFORM
    + - FMOD_ERR_DSP_DONTPROCESS - Meaning no, it should skip the process function and not call it with FMOD_DSP_PROCESS_PERFORM.
    + - FMOD_ERR_DSP_SILENCE - Meaning no, it should skip the process function and not call it with FMOD_DSP_PROCESS_PERFORM, AND, tell the signal chain to follow that it is now idle, so that no more processing happens down the chain.
    + If audio is to be processed, 'outbufferarray' must be filled with the expected output format, channel count and mask. Mask can be 0.
    +
    + FMOD_DSP_PROCESS_PROCESS is to be handled by reading the data from the input, processing it, and writing it to the output. Always write to the output buffer and fill it fully to avoid unpredictable audio output.
    + Always return FMOD_OK, the return value is ignored from the process stage. + + [SEE_ALSO] + FMOD_DSP_DESCRIPTION +] +*/ +typedef enum +{ + FMOD_DSP_PROCESS_PERFORM, /* Process the incoming audio in 'inbufferarray' and output to 'outbufferarray'. */ + FMOD_DSP_PROCESS_QUERY /* The DSP is being queried for the expected output format and whether it needs to process audio or should be bypassed. The function should return FMOD_OK, or FMOD_ERR_DSP_DONTPROCESS or FMOD_ERR_DSP_SILENCE if audio can pass through unprocessed. See remarks for more. If audio is to be processed, 'outbufferarray' must be filled with the expected output format, channel count and mask. */ +} FMOD_DSP_PROCESS_OPERATION; + +/* +[STRUCTURE] +[ + [DESCRIPTION] + Complex number structure used for holding FFT frequency domain-data for FMOD_FFTREAL and FMOD_IFFTREAL DSP callbacks. + + [REMARKS] + + [SEE_ALSO] + FMOD_DSP_STATE_SYSTEMCALLBACKS +] +*/ +typedef struct FMOD_COMPLEX +{ + float real; /* Real component */ + float imag; /* Imaginary component */ +} FMOD_COMPLEX; + +/* +[ENUM] +[ + [DESCRIPTION] + Flags for the FMOD_DSP_PAN_SUM_SURROUND_MATRIX callback. + + [REMARKS] + This functionality is experimental, please contact support@fmod.org for more information. + + [SEE_ALSO] + FMOD_DSP_STATE_PAN_CALLBACKS +] +*/ +typedef enum +{ + FMOD_DSP_PAN_SURROUND_DEFAULT = 0, + FMOD_DSP_PAN_SURROUND_ROTATION_NOT_BIASED = 1, + + FMOD_DSP_PAN_SURROUND_FLAGS_FORCEINT = 65536 /* Makes sure this enum is signed 32bit. */ +} FMOD_DSP_PAN_SURROUND_FLAGS; + +/* + DSP callbacks +*/ +typedef FMOD_RESULT (F_CALLBACK *FMOD_DSP_CREATE_CALLBACK) (FMOD_DSP_STATE *dsp_state); +typedef FMOD_RESULT (F_CALLBACK *FMOD_DSP_RELEASE_CALLBACK) (FMOD_DSP_STATE *dsp_state); +typedef FMOD_RESULT (F_CALLBACK *FMOD_DSP_RESET_CALLBACK) (FMOD_DSP_STATE *dsp_state); +typedef FMOD_RESULT (F_CALLBACK *FMOD_DSP_READ_CALLBACK) (FMOD_DSP_STATE *dsp_state, float *inbuffer, float *outbuffer, unsigned int length, int inchannels, int *outchannels); +typedef FMOD_RESULT (F_CALLBACK *FMOD_DSP_PROCESS_CALLBACK) (FMOD_DSP_STATE *dsp_state, unsigned int length, const FMOD_DSP_BUFFER_ARRAY *inbufferarray, FMOD_DSP_BUFFER_ARRAY *outbufferarray, FMOD_BOOL inputsidle, FMOD_DSP_PROCESS_OPERATION op); +typedef FMOD_RESULT (F_CALLBACK *FMOD_DSP_SETPOSITION_CALLBACK) (FMOD_DSP_STATE *dsp_state, unsigned int pos); +typedef FMOD_RESULT (F_CALLBACK *FMOD_DSP_SHOULDIPROCESS_CALLBACK) (FMOD_DSP_STATE *dsp_state, FMOD_BOOL inputsidle, unsigned int length, FMOD_CHANNELMASK inmask, int inchannels, FMOD_SPEAKERMODE speakermode); + +typedef FMOD_RESULT (F_CALLBACK *FMOD_DSP_SETPARAM_FLOAT_CALLBACK) (FMOD_DSP_STATE *dsp_state, int index, float value); +typedef FMOD_RESULT (F_CALLBACK *FMOD_DSP_SETPARAM_INT_CALLBACK) (FMOD_DSP_STATE *dsp_state, int index, int value); +typedef FMOD_RESULT (F_CALLBACK *FMOD_DSP_SETPARAM_BOOL_CALLBACK) (FMOD_DSP_STATE *dsp_state, int index, FMOD_BOOL value); +typedef FMOD_RESULT (F_CALLBACK *FMOD_DSP_SETPARAM_DATA_CALLBACK) (FMOD_DSP_STATE *dsp_state, int index, void *data, unsigned int length); +typedef FMOD_RESULT (F_CALLBACK *FMOD_DSP_GETPARAM_FLOAT_CALLBACK) (FMOD_DSP_STATE *dsp_state, int index, float *value, char *valuestr); +typedef FMOD_RESULT (F_CALLBACK *FMOD_DSP_GETPARAM_INT_CALLBACK) (FMOD_DSP_STATE *dsp_state, int index, int *value, char *valuestr); +typedef FMOD_RESULT (F_CALLBACK *FMOD_DSP_GETPARAM_BOOL_CALLBACK) (FMOD_DSP_STATE *dsp_state, int index, FMOD_BOOL *value, char *valuestr); +typedef FMOD_RESULT (F_CALLBACK *FMOD_DSP_GETPARAM_DATA_CALLBACK) (FMOD_DSP_STATE *dsp_state, int index, void **data, unsigned int *length, char *valuestr); + +typedef FMOD_RESULT (F_CALLBACK *FMOD_DSP_SYSTEM_REGISTER_CALLBACK) (FMOD_DSP_STATE *dsp_state); +typedef FMOD_RESULT (F_CALLBACK *FMOD_DSP_SYSTEM_DEREGISTER_CALLBACK) (FMOD_DSP_STATE *dsp_state); +typedef FMOD_RESULT (F_CALLBACK *FMOD_DSP_SYSTEM_MIX_CALLBACK) (FMOD_DSP_STATE *dsp_state, int stage); + +typedef FMOD_RESULT (F_CALLBACK *FMOD_DSP_SYSTEM_GETSAMPLERATE) (FMOD_DSP_STATE *dsp_state, int *rate); +typedef FMOD_RESULT (F_CALLBACK *FMOD_DSP_SYSTEM_GETBLOCKSIZE) (FMOD_DSP_STATE *dsp_state, unsigned int *blocksize); +typedef FMOD_RESULT (F_CALLBACK *FMOD_DSP_SYSTEM_GETSPEAKERMODE) (FMOD_DSP_STATE *dsp_state, FMOD_SPEAKERMODE *speakermode_mixer, FMOD_SPEAKERMODE *speakermode_output); + +typedef FMOD_RESULT (F_CALLBACK *FMOD_DSP_DFT_FFTREAL) (FMOD_DSP_STATE* thisdsp, int size, const float *signal, FMOD_COMPLEX* dft, const float *window, int signalhop); +typedef FMOD_RESULT (F_CALLBACK *FMOD_DSP_DFT_IFFTREAL) (FMOD_DSP_STATE* thisdsp, int size, const FMOD_COMPLEX *dft, float* signal, const float *window, int signalhop); + +typedef FMOD_RESULT (F_CALLBACK *FMOD_DSP_PAN_SUM_MONO_MATRIX) (FMOD_DSP_STATE *dsp_state, int sourceSpeakerMode, float lowFrequencyGain, float overallGain, float *matrix); +typedef FMOD_RESULT (F_CALLBACK *FMOD_DSP_PAN_SUM_STEREO_MATRIX) (FMOD_DSP_STATE *dsp_state, int sourceSpeakerMode, float pan, float lowFrequencyGain, float overallGain, int matrixHop, float *matrix); +typedef FMOD_RESULT (F_CALLBACK *FMOD_DSP_PAN_SUM_SURROUND_MATRIX) (FMOD_DSP_STATE *dsp_state, int sourceSpeakerMode, int targetSpeakerMode, float direction, float extent, float rotation, float lowFrequencyGain, float overallGain, int matrixHop, float *matrix, FMOD_DSP_PAN_SURROUND_FLAGS flags); +typedef FMOD_RESULT (F_CALLBACK *FMOD_DSP_PAN_SUM_MONO_TO_SURROUND_MATRIX) (FMOD_DSP_STATE *dsp_state, int targetSpeakerMode, float direction, float extent, float lowFrequencyGain, float overallGain, int matrixHop, float *matrix); +typedef FMOD_RESULT (F_CALLBACK *FMOD_DSP_PAN_SUM_STEREO_TO_SURROUND_MATRIX)(FMOD_DSP_STATE *dsp_state, int targetSpeakerMode, float direction, float extent, float rotation, float lowFrequencyGain, float overallGain, int matrixHop, float *matrix); +typedef FMOD_RESULT (F_CALLBACK *FMOD_DSP_PAN_3D_GET_ROLLOFF_GAIN) (FMOD_DSP_STATE *dsp_state, FMOD_DSP_PAN_3D_ROLLOFF_TYPE rolloff, float distance, float mindistance, float maxdistance, float *gain); + + +/* +[DEFINE] +[ + [NAME] + FMOD_DSP_GETPARAM_VALUESTR_LENGTH + + [DESCRIPTION] + Length in bytes of the buffer pointed to by the valuestr argument of FMOD_DSP_GETPARAM_XXXX_CALLBACK functions. + + [REMARKS] + DSP plugins should not copy more than this number of bytes into the buffer or memory corruption will occur. + + [SEE_ALSO] + FMOD_DSP_GETPARAM_FLOAT_CALLBACK + FMOD_DSP_GETPARAM_INT_CALLBACK + FMOD_DSP_GETPARAM_BOOL_CALLBACK + FMOD_DSP_GETPARAM_DATA_CALLBACK +] +*/ +#define FMOD_DSP_GETPARAM_VALUESTR_LENGTH 32 +/* [DEFINE_END] */ + +/* +[ENUM] +[ + [DESCRIPTION] + DSP parameter types. + + [REMARKS] + + [SEE_ALSO] + FMOD_DSP_PARAMETER_DESC +] +*/ +typedef enum +{ + FMOD_DSP_PARAMETER_TYPE_FLOAT, + FMOD_DSP_PARAMETER_TYPE_INT, + FMOD_DSP_PARAMETER_TYPE_BOOL, + FMOD_DSP_PARAMETER_TYPE_DATA, + + FMOD_DSP_PARAMETER_TYPE_MAX, /* Maximum number of DSP parameter types. */ + FMOD_DSP_PARAMETER_TYPE_FORCEINT = 65536 /* Makes sure this enum is signed 32bit. */ +} FMOD_DSP_PARAMETER_TYPE; + + +/* +[ENUM] +[ + [DESCRIPTION] + DSP float parameter mappings. These determine how values are mapped across dials and automation curves. + + [REMARKS] + FMOD_DSP_PARAMETER_FLOAT_MAPPING_TYPE_AUTO generates a mapping based on range and units. For example, if the units are in Hertz and the range is with-in the audio spectrum, a Bark scale will be chosen. Logarithmic scales may also be generated for ranges above zero spanning several orders of magnitude. + + [SEE_ALSO] + FMOD_DSP_PARAMETER_FLOAT_MAPPING +] +*/ +typedef enum +{ + FMOD_DSP_PARAMETER_FLOAT_MAPPING_TYPE_LINEAR, /* Values mapped linearly across range. */ + FMOD_DSP_PARAMETER_FLOAT_MAPPING_TYPE_AUTO, /* A mapping is automatically chosen based on range and units. See remarks. */ + FMOD_DSP_PARAMETER_FLOAT_MAPPING_TYPE_PIECEWISE_LINEAR, /* Values mapped in a piecewise linear fashion defined by FMOD_DSP_PARAMETER_FLOAT_MAPPING_PIECEWISE_LINEAR. */ + + FMOD_DSP_PARAMETER_FLOAT_MAPPING_TYPE_FORCEINT = 65536 /* Makes sure this enum is signed 32bit. */ +} FMOD_DSP_PARAMETER_FLOAT_MAPPING_TYPE; + +/* +[STRUCTURE] +[ + [DESCRIPTION] + Structure to define a piecewise linear mapping. + + [REMARKS] + Members marked with [r] mean the variable is modified by FMOD and is for reading purposes only. Do not change this value.
    + Members marked with [w] mean the variable can be written to. The user can set the value.
    + + [SEE_ALSO] + FMOD_DSP_PARAMETER_FLOAT_MAPPING_TYPE + FMOD_DSP_PARAMETER_FLOAT_MAPPING +] +*/ +typedef struct FMOD_DSP_PARAMETER_FLOAT_MAPPING_PIECEWISE_LINEAR +{ + int numpoints; /* [w] The number of pairs in the piecewise mapping (at least 2). */ + float *pointparamvalues; /* [w] The values in the parameter's units for each point */ + float *pointpositions; /* [w] The positions along the control's scale (e.g. dial angle) corresponding to each parameter value. The range of this scale is arbitrary and all positions will be relative to the minimum and maximum values (e.g. [0,1,3] is equivalent to [1,2,4] and [2,4,8]). If this array is zero, pointparamvalues will be distributed with equal spacing. */ +} FMOD_DSP_PARAMETER_FLOAT_MAPPING_PIECEWISE_LINEAR; + + +/* +[STRUCTURE] +[ + [DESCRIPTION] + Structure to define a mapping for a DSP unit's float parameter. + + [REMARKS] + Members marked with [r] mean the variable is modified by FMOD and is for reading purposes only. Do not change this value.
    + Members marked with [w] mean the variable can be written to. The user can set the value.
    + + [SEE_ALSO] + FMOD_DSP_PARAMETER_FLOAT_MAPPING_TYPE + FMOD_DSP_PARAMETER_FLOAT_MAPPING_PIECEWISE_LINEAR + FMOD_DSP_PARAMETER_DESC_FLOAT +] +*/ +typedef struct FMOD_DSP_PARAMETER_FLOAT_MAPPING +{ + FMOD_DSP_PARAMETER_FLOAT_MAPPING_TYPE type; + FMOD_DSP_PARAMETER_FLOAT_MAPPING_PIECEWISE_LINEAR piecewiselinearmapping; /* [w] Only required for FMOD_DSP_PARAMETER_FLOAT_MAPPING_TYPE_PIECEWISE_LINEAR type mapping. */ +} FMOD_DSP_PARAMETER_FLOAT_MAPPING; + + +/* +[STRUCTURE] +[ + [DESCRIPTION] + Structure to define a float parameter for a DSP unit. + + [REMARKS] + Members marked with [r] mean the variable is modified by FMOD and is for reading purposes only. Do not change this value.
    + Members marked with [w] mean the variable can be written to. The user can set the value.
    + + [SEE_ALSO] + System::createDSP + DSP::setParameterFloat + DSP::getParameterFloat + FMOD_DSP_PARAMETER_DESC + FMOD_DSP_PARAMETER_FLOAT_MAPPING +] +*/ +typedef struct FMOD_DSP_PARAMETER_DESC_FLOAT +{ + float min; /* [w] Minimum parameter value. */ + float max; /* [w] Maximum parameter value. */ + float defaultval; /* [w] Default parameter value. */ + FMOD_DSP_PARAMETER_FLOAT_MAPPING mapping; /* [w] How the values are distributed across dials and automation curves (e.g. linearly, exponentially etc). */ +} FMOD_DSP_PARAMETER_DESC_FLOAT; + + +/* +[STRUCTURE] +[ + [DESCRIPTION] + Structure to define a int parameter for a DSP unit. + + [REMARKS] + Members marked with [r] mean the variable is modified by FMOD and is for reading purposes only. Do not change this value.
    + Members marked with [w] mean the variable can be written to. The user can set the value.
    + + [SEE_ALSO] + System::createDSP + DSP::setParameterInt + DSP::getParameterInt + FMOD_DSP_PARAMETER_DESC +] +*/ +typedef struct FMOD_DSP_PARAMETER_DESC_INT +{ + int min; /* [w] Minimum parameter value. */ + int max; /* [w] Maximum parameter value. */ + int defaultval; /* [w] Default parameter value. */ + FMOD_BOOL goestoinf; /* [w] Whether the last value represents infiniy. */ + const char* const* valuenames; /* [w] Names for each value. There should be as many strings as there are possible values (max - min + 1). Optional. */ +} FMOD_DSP_PARAMETER_DESC_INT; + + +/* +[STRUCTURE] +[ + [DESCRIPTION] + Structure to define a boolean parameter for a DSP unit. + + [REMARKS] + Members marked with [r] mean the variable is modified by FMOD and is for reading purposes only. Do not change this value.
    + Members marked with [w] mean the variable can be written to. The user can set the value.
    + + [SEE_ALSO] + System::createDSP + DSP::setParameterBool + DSP::getParameterBool + FMOD_DSP_PARAMETER_DESC +] +*/ +typedef struct FMOD_DSP_PARAMETER_DESC_BOOL +{ + FMOD_BOOL defaultval; /* [w] Default parameter value. */ + const char* const* valuenames; /* [w] Names for false and true, respectively. There should be two strings. Optional. */ +} FMOD_DSP_PARAMETER_DESC_BOOL; + + +/* +[STRUCTURE] +[ + [DESCRIPTION] + Structure to define a data parameter for a DSP unit. Use 0 or above for custom types. This parameter will be treated specially by the system if set to one of the FMOD_DSP_PARAMETER_DATA_TYPE values. + + [REMARKS] + Members marked with [r] mean the variable is modified by FMOD and is for reading purposes only. Do not change this value.
    + Members marked with [w] mean the variable can be written to. The user can set the value.
    + + [SEE_ALSO] + System::createDSP + DSP::setParameterData + DSP::getParameterData + FMOD_DSP_PARAMETER_DATA_TYPE + FMOD_DSP_PARAMETER_DESC +] +*/ +typedef struct FMOD_DSP_PARAMETER_DESC_DATA +{ + int datatype; /* [w] The type of data for this parameter. Use 0 or above for custom types or set to one of the FMOD_DSP_PARAMETER_DATA_TYPE values. */ +} FMOD_DSP_PARAMETER_DESC_DATA; + + +/* +[STRUCTURE] +[ + [DESCRIPTION] + Base Structure for DSP parameter descriptions. + + [REMARKS] + Members marked with [r] mean the variable is modified by FMOD and is for reading purposes only. Do not change this value.
    + Members marked with [w] mean the variable can be written to. The user can set the value.
    + + [SEE_ALSO] + System::createDSP + DSP::setParameterFloat + DSP::getParameterFloat + DSP::setParameterInt + DSP::getParameterInt + DSP::setParameterBool + DSP::getParameterBool + DSP::setParameterData + DSP::getParameterData + FMOD_DSP_PARAMETER_DESC_FLOAT + FMOD_DSP_PARAMETER_DESC_INT + FMOD_DSP_PARAMETER_DESC_BOOL + FMOD_DSP_PARAMETER_DESC_DATA +] +*/ +typedef struct FMOD_DSP_PARAMETER_DESC +{ + FMOD_DSP_PARAMETER_TYPE type; /* [w] Type of this parameter. */ + char name[16]; /* [w] Name of the parameter to be displayed (ie "Cutoff frequency"). */ + char label[16]; /* [w] Short string to be put next to value to denote the unit type (ie "hz"). */ + const char *description; /* [w] Description of the parameter to be displayed as a help item / tooltip for this parameter. */ + + union + { + FMOD_DSP_PARAMETER_DESC_FLOAT floatdesc; /* [w] Struct containing information about the parameter in floating point format. Use when type is FMOD_DSP_PARAMETER_TYPE_FLOAT. */ + FMOD_DSP_PARAMETER_DESC_INT intdesc; /* [w] Struct containing information about the parameter in integer format. Use when type is FMOD_DSP_PARAMETER_TYPE_INT. */ + FMOD_DSP_PARAMETER_DESC_BOOL booldesc; /* [w] Struct containing information about the parameter in boolean format. Use when type is FMOD_DSP_PARAMETER_TYPE_BOOL. */ + FMOD_DSP_PARAMETER_DESC_DATA datadesc; /* [w] Struct containing information about the parameter in data format. Use when type is FMOD_DSP_PARAMETER_TYPE_DATA. */ + }; +} FMOD_DSP_PARAMETER_DESC; + + +/* +[ENUM] +[ + [DESCRIPTION] + Built-in types for the 'datatype' member of FMOD_DSP_PARAMETER_DESC_DATA. Data parameters of type other than FMOD_DSP_PARAMETER_DATA_TYPE_USER will be treated specially by the system. + + [REMARKS] + + [SEE_ALSO] + FMOD_DSP_PARAMETER_DESC_DATA + FMOD_DSP_PARAMETER_OVERALLGAIN + FMOD_DSP_PARAMETER_3DATTRIBUTES + FMOD_DSP_PARAMETER_3DATTRIBUTES_MULTI + FMOD_DSP_PARAMETER_SIDECHAIN +] +*/ +typedef enum +{ + FMOD_DSP_PARAMETER_DATA_TYPE_USER = 0, /* The default data type. All user data types should be 0 or above. */ + FMOD_DSP_PARAMETER_DATA_TYPE_OVERALLGAIN = -1, /* The data type for FMOD_DSP_PARAMETER_OVERALLGAIN parameters. There should a maximum of one per DSP. */ + FMOD_DSP_PARAMETER_DATA_TYPE_3DATTRIBUTES = -2, /* The data type for FMOD_DSP_PARAMETER_3DATTRIBUTES parameters. There should a maximum of one per DSP. */ + FMOD_DSP_PARAMETER_DATA_TYPE_SIDECHAIN = -3, /* The data type for FMOD_DSP_PARAMETER_SIDECHAIN parameters. There should a maximum of one per DSP. */ + FMOD_DSP_PARAMETER_DATA_TYPE_FFT = -4, /* The data type for FMOD_DSP_PARAMETER_FFT parameters. There should a maximum of one per DSP. */ + FMOD_DSP_PARAMETER_DATA_TYPE_3DATTRIBUTES_MULTI = -5, /* The data type for FMOD_DSP_PARAMETER_3DATTRIBUTES_MULTI parameters. There should a maximum of one per DSP. */ +} FMOD_DSP_PARAMETER_DATA_TYPE; + + +/* +[STRUCTURE] +[ + [DESCRIPTION] + Structure for data parameters of type FMOD_DSP_PARAMETER_DATA_TYPE_OVERALLGAIN. + A parameter of this type is used in effects that affect the overgain of the signal in a predictable way. + This parameter is read by the system to determine the effect's gain for voice virtualization. + + [REMARKS] + Members marked with [r] mean the variable is modified by FMOD and is for reading purposes only. Do not change this value.
    + Members marked with [w] mean the variable can be written to. The user can set the value.
    + + [SEE_ALSO] + FMOD_DSP_PARAMETER_DATA_TYPE + FMOD_DSP_PARAMETER_DESC +] +*/ +typedef struct FMOD_DSP_PARAMETER_OVERALLGAIN +{ + float linear_gain; /* [r] The overall linear gain of the effect on the direct signal path */ + float linear_gain_additive; /* [r] Additive gain, for parallel signal paths */ +} FMOD_DSP_PARAMETER_OVERALLGAIN; + + +/* +[STRUCTURE] +[ + [DESCRIPTION] + Structure for data parameters of type FMOD_DSP_PARAMETER_DATA_TYPE_3DATTRIBUTES. + A parameter of this type is used in effects that respond to a sound's 3D position. + The system will set this parameter automatically if a sound's position changes. + + [REMARKS] + Members marked with [r] mean the variable is modified by FMOD and is for reading purposes only. Do not change this value.
    + Members marked with [w] mean the variable can be written to. The user can set the value.
    + + [SEE_ALSO] + FMOD_DSP_PARAMETER_DATA_TYPE + FMOD_DSP_PARAMETER_DESC +] +*/ +typedef struct FMOD_DSP_PARAMETER_3DATTRIBUTES +{ + FMOD_3D_ATTRIBUTES relative; /* [w] The position of the sound relative to the listener. */ + FMOD_3D_ATTRIBUTES absolute; /* [w] The position of the sound in world coordinates. */ +} FMOD_DSP_PARAMETER_3DATTRIBUTES; + + +/* +[STRUCTURE] +[ + [DESCRIPTION] + Structure for data parameters of type FMOD_DSP_PARAMETER_DATA_TYPE_3DATTRIBUTES_MULTI. + A parameter of this type is used in effects that respond to a sound's 3D position, and + support multiple listeners. + The system will set this parameter automatically if a sound's position changes. + + [REMARKS] + Members marked with [r] mean the variable is modified by FMOD and is for reading purposes only. Do not change this value.
    + Members marked with [w] mean the variable can be written to. The user can set the value.
    + + [SEE_ALSO] + FMOD_DSP_PARAMETER_DATA_TYPE + FMOD_DSP_PARAMETER_DESC +] +*/ +typedef struct FMOD_DSP_PARAMETER_3DATTRIBUTES_MULTI +{ + int numlisteners; /* [w] The number of listeners. */ + FMOD_3D_ATTRIBUTES relative[FMOD_MAX_LISTENERS]; /* [w] The position of the sound relative to the listeners. */ + FMOD_3D_ATTRIBUTES absolute; /* [w] The position of the sound in world coordinates. */ +} FMOD_DSP_PARAMETER_3DATTRIBUTES_MULTI; + + +/* +[STRUCTURE] +[ + [DESCRIPTION] + Structure for data parameters of type FMOD_DSP_PARAMETER_DATA_TYPE_SIDECHAIN. + A parameter of this type is declared for effects which support sidechaining. + + [REMARKS] + Members marked with [r] mean the variable is modified by FMOD and is for reading purposes only. Do not change this value.
    + Members marked with [w] mean the variable can be written to. The user can set the value.
    + + [SEE_ALSO] + FMOD_DSP_PARAMETER_DATA_TYPE + FMOD_DSP_PARAMETER_DESC +] +*/ +typedef struct FMOD_DSP_PARAMETER_SIDECHAIN +{ + FMOD_BOOL sidechainenable; /* [r/w] Whether sidechains are enabled. */ +} FMOD_DSP_PARAMETER_SIDECHAIN; + + +/* +[STRUCTURE] +[ + [DESCRIPTION] + Structure for data parameters of type FMOD_DSP_PARAMETER_DATA_TYPE_FFT. + A parameter of this type is declared for the FMOD_DSP_TYPE_FFT effect. + + [REMARKS] + Members marked with [r] mean the variable is modified by FMOD and is for reading purposes only. Do not change this value.
    + Members marked with [w] mean the variable can be written to. The user can set the value.
    +
    + Notes on the spectrum data member. Values inside the float buffer are typically between 0 and 1.0.
    + Each top level array represents one PCM channel of data.
    + Address data as spectrum[channel][bin]. A bin is 1 fft window entry.
    + Only read/display half of the buffer typically for analysis as the 2nd half is usually the same data reversed due to the nature of the way FFT works.
    + + [SEE_ALSO] + FMOD_DSP_PARAMETER_DATA_TYPE + FMOD_DSP_PARAMETER_DESC + FMOD_DSP_PARAMETER_DATA_TYPE_FFT + FMOD_DSP_TYPE + FMOD_DSP_FFT +] +*/ +typedef struct FMOD_DSP_PARAMETER_FFT +{ + int length; /* [r] Number of entries in this spectrum window. Divide this by the output rate to get the hz per entry. */ + int numchannels; /* [r] Number of channels in spectrum. */ + float *spectrum[32]; /* [r] Per channel spectrum arrays. See remarks for more. */ +} FMOD_DSP_PARAMETER_FFT; + + +/* + Helpers for declaring parameters in custom DSPSs +*/ +#define FMOD_DSP_INIT_PARAMDESC_FLOAT(_paramstruct, _name, _label, _description, _min, _max, _defaultval) \ + memset(&(_paramstruct), 0, sizeof(_paramstruct)); \ + (_paramstruct).type = FMOD_DSP_PARAMETER_TYPE_FLOAT; \ + strncpy((_paramstruct).name, _name, 15); \ + strncpy((_paramstruct).label, _label, 15); \ + (_paramstruct).description = _description; \ + (_paramstruct).floatdesc.min = _min; \ + (_paramstruct).floatdesc.max = _max; \ + (_paramstruct).floatdesc.defaultval = _defaultval; \ + (_paramstruct).floatdesc.mapping.type = FMOD_DSP_PARAMETER_FLOAT_MAPPING_TYPE_AUTO; + +#define FMOD_DSP_INIT_PARAMDESC_FLOAT_WITH_MAPPING(_paramstruct, _name, _label, _description, _defaultval, _values, _positions); \ + memset(&(_paramstruct), 0, sizeof(_paramstruct)); \ + (_paramstruct).type = FMOD_DSP_PARAMETER_TYPE_FLOAT; \ + strncpy((_paramstruct).name, _name , 15); \ + strncpy((_paramstruct).label, _label, 15); \ + (_paramstruct).description = _description; \ + (_paramstruct).floatdesc.min = _values[0]; \ + (_paramstruct).floatdesc.max = _values[sizeof(_values) / sizeof(float) - 1]; \ + (_paramstruct).floatdesc.defaultval = _defaultval; \ + (_paramstruct).floatdesc.mapping.type = FMOD_DSP_PARAMETER_FLOAT_MAPPING_TYPE_PIECEWISE_LINEAR; \ + (_paramstruct).floatdesc.mapping.piecewiselinearmapping.numpoints = sizeof(_values) / sizeof(float); \ + (_paramstruct).floatdesc.mapping.piecewiselinearmapping.pointparamvalues = _values; \ + (_paramstruct).floatdesc.mapping.piecewiselinearmapping.pointpositions = _positions; + +#define FMOD_DSP_INIT_PARAMDESC_INT(_paramstruct, _name, _label, _description, _min, _max, _defaultval, _goestoinf, _valuenames) \ + memset(&(_paramstruct), 0, sizeof(_paramstruct)); \ + (_paramstruct).type = FMOD_DSP_PARAMETER_TYPE_INT; \ + strncpy((_paramstruct).name, _name , 15); \ + strncpy((_paramstruct).label, _label, 15); \ + (_paramstruct).description = _description; \ + (_paramstruct).intdesc.min = _min; \ + (_paramstruct).intdesc.max = _max; \ + (_paramstruct).intdesc.defaultval = _defaultval; \ + (_paramstruct).intdesc.goestoinf = _goestoinf; \ + (_paramstruct).intdesc.valuenames = _valuenames; + +#define FMOD_DSP_INIT_PARAMDESC_INT_ENUMERATED(_paramstruct, _name, _label, _description, _defaultval, _valuenames) \ + memset(&(_paramstruct), 0, sizeof(_paramstruct)); \ + (_paramstruct).type = FMOD_DSP_PARAMETER_TYPE_INT; \ + strncpy((_paramstruct).name, _name , 15); \ + strncpy((_paramstruct).label, _label, 15); \ + (_paramstruct).description = _description; \ + (_paramstruct).intdesc.min = 0; \ + (_paramstruct).intdesc.max = sizeof(_valuenames) / sizeof(char*) - 1; \ + (_paramstruct).intdesc.defaultval = _defaultval; \ + (_paramstruct).intdesc.goestoinf = false; \ + (_paramstruct).intdesc.valuenames = _valuenames; + +#define FMOD_DSP_INIT_PARAMDESC_BOOL(_paramstruct, _name, _label, _description, _defaultval, _valuenames) \ + memset(&(_paramstruct), 0, sizeof(_paramstruct)); \ + (_paramstruct).type = FMOD_DSP_PARAMETER_TYPE_BOOL; \ + strncpy((_paramstruct).name, _name , 15); \ + strncpy((_paramstruct).label, _label, 15); \ + (_paramstruct).description = _description; \ + (_paramstruct).booldesc.defaultval = _defaultval; \ + (_paramstruct).booldesc.valuenames = _valuenames; + +#define FMOD_DSP_INIT_PARAMDESC_DATA(_paramstruct, _name, _label, _description, _datatype) \ + memset(&(_paramstruct), 0, sizeof(_paramstruct)); \ + (_paramstruct).type = FMOD_DSP_PARAMETER_TYPE_DATA; \ + strncpy((_paramstruct).name, _name , 15); \ + strncpy((_paramstruct).label, _label, 15); \ + (_paramstruct).description = _description; \ + (_paramstruct).datadesc.datatype = _datatype; + +#define FMOD_PLUGIN_SDK_VERSION 107 + +/* +[STRUCTURE] +[ + [DESCRIPTION] + When creating a DSP unit, declare one of these and provide the relevant callbacks and name for FMOD to use when it creates and uses a DSP unit of this type. + + [REMARKS] + Members marked with [r] mean the variable is modified by FMOD and is for reading purposes only. Do not change this value.
    + Members marked with [w] mean the variable can be written to. The user can set the value.
    +
    + There are 2 different ways to change a parameter in this architecture.
    + One is to use DSP::setParameterFloat / DSP::setParameterInt / DSP::setParameterBool / DSP::setParameterData. This is platform independant and is dynamic, so new unknown plugins can have their parameters enumerated and used.
    + The other is to use DSP::showConfigDialog. This is platform specific and requires a GUI, and will display a dialog box to configure the plugin.
    + + [SEE_ALSO] + System::createDSP + DSP::setParameterFloat + DSP::setParameterInt + DSP::setParameterBool + DSP::setParameterData + FMOD_DSP_STATE + FMOD_DSP_CREATE_CALLBACK + FMOD_DSP_RELEASE_CALLBACK + FMOD_DSP_RESET_CALLBACK + FMOD_DSP_READ_CALLBACK + FMOD_DSP_PROCESS_CALLBACK + FMOD_DSP_SETPOSITION_CALLBACK + FMOD_DSP_PARAMETER_DESC + FMOD_DSP_SETPARAM_FLOAT_CALLBACK + FMOD_DSP_SETPARAM_INT_CALLBACK + FMOD_DSP_SETPARAM_BOOL_CALLBACK + FMOD_DSP_SETPARAM_DATA_CALLBACK + FMOD_DSP_GETPARAM_FLOAT_CALLBACK + FMOD_DSP_GETPARAM_INT_CALLBACK + FMOD_DSP_GETPARAM_BOOL_CALLBACK + FMOD_DSP_GETPARAM_DATA_CALLBACK + FMOD_DSP_SHOULDIPROCESS_CALLBACK + FMOD_DSP_SYSTEM_REGISTER_CALLBACK + FMOD_DSP_SYSTEM_DEREGISTER_CALLBACK + FMOD_DSP_SYSTEM_MIX_CALLBACK +] +*/ +typedef struct FMOD_DSP_DESCRIPTION +{ + unsigned int pluginsdkversion; /* [w] The plugin SDK version this plugin is built for. set to this to FMOD_PLUGIN_SDK_VERSION defined above. */ + char name[32]; /* [w] The identifier of the DSP. This will also be used as the name of DSP and shouldn't change between versions. */ + unsigned int version; /* [w] Plugin writer's version number. */ + int numinputbuffers; /* [w] Number of input buffers to process. Use 0 for DSPs that only generate sound and 1 for effects that process incoming sound. */ + int numoutputbuffers; /* [w] Number of audio output buffers. Only one output buffer is currently supported. */ + FMOD_DSP_CREATE_CALLBACK create; /* [w] Create callback. This is called when DSP unit is created. Can be null. */ + FMOD_DSP_RELEASE_CALLBACK release; /* [w] Release callback. This is called just before the unit is freed so the user can do any cleanup needed for the unit. Can be null. */ + FMOD_DSP_RESET_CALLBACK reset; /* [w] Reset callback. This is called by the user to reset any history buffers that may need resetting for a filter, when it is to be used or re-used for the first time to its initial clean state. Use to avoid clicks or artifacts. */ + FMOD_DSP_READ_CALLBACK read; /* [w] Read callback. Processing is done here. Can be null. */ + FMOD_DSP_PROCESS_CALLBACK process; /* [w] Process callback. Can be specified instead of the read callback if any channel format changes occur between input and output. This also replaces shouldiprocess and should return an error if the effect is to be bypassed. Can be null. */ + FMOD_DSP_SETPOSITION_CALLBACK setposition; /* [w] Set position callback. This is called if the unit wants to update its position info but not process data, or reset a cursor position internally if it is reading data from a certain source. Can be null. */ + + int numparameters; /* [w] Number of parameters used in this filter. The user finds this with DSP::getNumParameters */ + FMOD_DSP_PARAMETER_DESC **paramdesc; /* [w] Variable number of parameter structures. */ + FMOD_DSP_SETPARAM_FLOAT_CALLBACK setparameterfloat; /* [w] This is called when the user calls DSP::setParameterFloat. Can be null. */ + FMOD_DSP_SETPARAM_INT_CALLBACK setparameterint; /* [w] This is called when the user calls DSP::setParameterInt. Can be null. */ + FMOD_DSP_SETPARAM_BOOL_CALLBACK setparameterbool; /* [w] This is called when the user calls DSP::setParameterBool. Can be null. */ + FMOD_DSP_SETPARAM_DATA_CALLBACK setparameterdata; /* [w] This is called when the user calls DSP::setParameterData. Can be null. */ + FMOD_DSP_GETPARAM_FLOAT_CALLBACK getparameterfloat; /* [w] This is called when the user calls DSP::getParameterFloat. Can be null. */ + FMOD_DSP_GETPARAM_INT_CALLBACK getparameterint; /* [w] This is called when the user calls DSP::getParameterInt. Can be null. */ + FMOD_DSP_GETPARAM_BOOL_CALLBACK getparameterbool; /* [w] This is called when the user calls DSP::getParameterBool. Can be null. */ + FMOD_DSP_GETPARAM_DATA_CALLBACK getparameterdata; /* [w] This is called when the user calls DSP::getParameterData. Can be null. */ + FMOD_DSP_SHOULDIPROCESS_CALLBACK shouldiprocess; /* [w] This is called before processing. You can detect if inputs are idle and return FMOD_OK to process, or any other error code to avoid processing the effect. Use a count down timer to allow effect tails to process before idling! */ + void *userdata; /* [w] Optional. Specify 0 to ignore. This is user data to be attached to the DSP unit during creation. Access via DSP::getUserData. */ + + FMOD_DSP_SYSTEM_REGISTER_CALLBACK sys_register; /* [w] Register callback. This is called when DSP unit is loaded/registered. Useful for 'global'/per system object init for plugin. Can be null. */ + FMOD_DSP_SYSTEM_DEREGISTER_CALLBACK sys_deregister; /* [w] Deregister callback. This is called when DSP unit is unloaded/deregistered. Useful as 'global'/per system object shutdown for plugin. Can be null. */ + FMOD_DSP_SYSTEM_MIX_CALLBACK sys_mix; /* [w] System mix stage callback. This is called when the mixer starts to execute or is just finishing executing. Useful for 'global'/per system object once a mix update calls for a plugin. Can be null. */ + +} FMOD_DSP_DESCRIPTION; + + +/* +[STRUCTURE] +[ + [DESCRIPTION] + Struct containing DFT callbacks for plugins, to enable a plugin to perform optimized time-frequency domain conversion. + + [REMARKS] + Members marked with [r] mean the variable is modified by FMOD and is for reading purposes only. Do not change this value.
    + Members marked with [w] mean the variable can be written to. The user can set the value.
    + + [SEE_ALSO] + FMOD_DSP_STATE_SYSTEMCALLBACKS +] +*/ +typedef struct FMOD_DSP_STATE_DFTCALLBACKS +{ + FMOD_DSP_DFT_FFTREAL fftreal; /* [r] Callback for performing an FFT on a real signal. */ + FMOD_DSP_DFT_IFFTREAL inversefftreal; /* [r] Callback for performing an inverse FFT to get a real signal. */ +} FMOD_DSP_STATE_DFTCALLBACKS; + +/* +[STRUCTURE] +[ + [DESCRIPTION] + Struct containing panning helper callbacks for plugins. + + [REMARKS] + These are experimental, please contact support@fmod.org for more information. + + [SEE_ALSO] + FMOD_DSP_STATE_SYSTEMCALLBACKS + FMOD_DSP_PAN_SURROUND_FLAGS +] +*/ +typedef struct FMOD_DSP_STATE_PAN_CALLBACKS +{ + FMOD_DSP_PAN_SUM_MONO_MATRIX summonomatrix; + FMOD_DSP_PAN_SUM_STEREO_MATRIX sumstereomatrix; + FMOD_DSP_PAN_SUM_SURROUND_MATRIX sumsurroundmatrix; + FMOD_DSP_PAN_SUM_MONO_TO_SURROUND_MATRIX summonotosurroundmatrix; + FMOD_DSP_PAN_SUM_STEREO_TO_SURROUND_MATRIX sumstereotosurroundmatrix; + FMOD_DSP_PAN_3D_GET_ROLLOFF_GAIN getrolloffgain; +} FMOD_DSP_STATE_PAN_CALLBACKS; + +/* +[STRUCTURE] +[ + [DESCRIPTION] + Struct containing System level callbacks for plugins, to enable a plugin to query information about the system or allocate memory using FMOD's (and therefore possibly the game's) allocators. + + [REMARKS] + Members marked with [r] mean the variable is modified by FMOD and is for reading purposes only. Do not change this value.
    + Members marked with [w] mean the variable can be written to. The user can set the value.
    + + [SEE_ALSO] + FMOD_DSP_STATE + FMOD_DSP_STATE_DFTCALLBACKS + FMOD_DSP_STATE_PAN_CALLBACKS +] +*/ +typedef struct FMOD_DSP_STATE_SYSTEMCALLBACKS +{ + FMOD_MEMORY_ALLOC_CALLBACK alloc; /* [r] Memory allocation callback. Use this for all dynamic memory allocation within the plugin. */ + FMOD_MEMORY_REALLOC_CALLBACK realloc; /* [r] Memory reallocation callback. */ + FMOD_MEMORY_FREE_CALLBACK free; /* [r] Memory free callback. */ + FMOD_DSP_SYSTEM_GETSAMPLERATE getsamplerate; /* [r] Callback for getting the system samplerate. */ + FMOD_DSP_SYSTEM_GETBLOCKSIZE getblocksize; /* [r] Callback for getting the system's block size. DSPs will be requested to process blocks of varying length up to this size.*/ + FMOD_DSP_STATE_DFTCALLBACKS *dft; /* [r] Struct containing callbacks for performing FFTs and inverse FFTs. */ + FMOD_DSP_STATE_PAN_CALLBACKS *pancallbacks; /* [r] Pointer to a structure of callbacks for calculating pan, up-mix and down-mix matrices. */ + FMOD_DSP_SYSTEM_GETSPEAKERMODE getspeakermode; /* [r] Callback for getting the system's speaker modes. One is the mixer's default speaker mode, the other is the output mode the system is downmixing or upmixing to.*/ +} FMOD_DSP_STATE_SYSTEMCALLBACKS; + + +/* +[STRUCTURE] +[ + [DESCRIPTION] + DSP plugin structure that is passed into each callback. + + [REMARKS] + Members marked with [r] mean the variable is modified by FMOD and is for reading purposes only. Do not change this value.
    + Members marked with [w] mean the variable can be written to. The user can set the value.
    +
    + 'systemobject' is an integer that relates to the System object that created the DSP or registered the DSP plugin. If only 1 System object is created then it should be 0. A second object would be 1 and so on. + FMOD_DSP_STATE_SYSTEMCALLBACKS::getsamplerate/getblocksize/getspeakermode could return different results so it could be relevant to plugin developers to monitor which object is being used. + + [SEE_ALSO] + FMOD_DSP_DESCRIPTION + FMOD_DSP_STATE_SYSTEMCALLBACKS +] +*/ +struct FMOD_DSP_STATE +{ + FMOD_DSP *instance; /* [r] Handle to the FMOD_DSP object the callback is associated with. Not to be modified. C++ users cast to FMOD::DSP to use. */ + void *plugindata; /* [r/w] Plugin writer created data the output author wants to attach to this object. */ + FMOD_CHANNELMASK channelmask; /* [r] Specifies which speakers the DSP effect is active on */ + FMOD_SPEAKERMODE source_speakermode; /* [r] Specifies which speaker mode the signal originated for information purposes, ie in case panning needs to be done differently. */ + float *sidechaindata; /* [r] The mixed result of all incoming sidechains is stored at this pointer address. */ + int sidechainchannels; /* [r] The number of channels of pcm data stored within the sidechain buffer. */ + FMOD_DSP_STATE_SYSTEMCALLBACKS *callbacks; /* [r] Struct containing callbacks for system level functionality. */ + int systemobject; /* [r] FMOD::System object index, relating to the System object that created this DSP. */ +}; + + +/* + Macro helpers for accessing FMOD_DSP_STATE_SYSTEMCALLBACKS +*/ +#define FMOD_DSP_STATE_MEMALLOC(_state, _size, _type, _str) (_state)->callbacks->alloc (_size, _type, _str); /* Pass in the FMOD_DSP_STATE handle, size in bytes to alloc, FMOD_MEMORY_TYPE type and optional char * string to identify where the alloc came from. */ +#define FMOD_DSP_STATE_MEMREALLOC(_state, _ptr, _size, _type, _str) (_state)->callbacks->realloc (_ptr, _size, _type, _str); /* Pass in the FMOD_DSP_STATE handle, optional existing memory pointer, size in bytes to alloc, FMOD_MEMORY_TYPE type and optional char * string to identify where the alloc came from. */ +#define FMOD_DSP_STATE_MEMFREE(_state, _ptr, _type, _str) (_state)->callbacks->free (_ptr, _type, _str); /* Pass in the FMOD_DSP_STATE handle, existing memory pointer, FMOD_MEMORY_TYPE type and optional char * string to identify where the free came from. */ +#define FMOD_DSP_STATE_GETSAMPLERATE(_state, _rate) (_state)->callbacks->getsamplerate (_state, _rate); /* Pass in the FMOD_DSP_STATE handle, and the address of an int to receive the system DSP sample rate. */ +#define FMOD_DSP_STATE_GETBLOCKSIZE(_state, _blocksize) (_state)->callbacks->getblocksize (_state, _blocksize); /* Pass in the FMOD_DSP_STATE handle, and the address of an unsigned int to receive the system DSP block size. */ +#define FMOD_DSP_STATE_GETSPEAKERMODE(_state, _speakermode_mix, _speakermode_out) (_state)->callbacks->getspeakermode(_state, _speakermode_mix, _speakermode_out); /* Pass in the FMOD_DSP_STATE handle, and the address of an unsigned int to receive the FMOD_SPEAKERMODE for the mixer, and for the mode the system is set to. */ +#define FMOD_DSP_STATE_FFTREAL(_state, _size, _signal, _dft, _window, _signalhop) (_state)->callbacks->dft->fftreal (_state, _size, _signal, _dft, _window, _signalhop); /* Pass in the FMOD_DSP_STATE handle, size of the signal and its DFT, a float buffer containing the signal and an FMOD_COMPLEX buffer to store the calculated DFT. */ +#define FMOD_DSP_STATE_IFFTREAL(_state, _size, _dft, _signal, _window, _signalhop) (_state)->callbacks->dft->inversefftreal(_state, _size, _dft, _signal, _window, _signalhop); /* Pass in the FMOD_DSP_STATE handle, size of the DFT and its signal, an FMOD_COMPLEX buffer containing the DFT and a float buffer to store the calculated signal. */ + + +/* +[STRUCTURE] +[ + [DESCRIPTION] + DSP metering info used for retrieving metering info with DSP::getMeteringInfo + + [REMARKS] + Members marked with [r] mean the variable is modified by FMOD and is for reading purposes only. Do not change this value.
    + Members marked with [w] mean the variable can be written to. The user can set the value.
    + + [SEE_ALSO] + FMOD_SPEAKER + DSP::getMeteringInfo +] +*/ +typedef struct FMOD_DSP_METERING_INFO +{ + int numsamples; /* [r] The number of samples considered for this metering info. */ + float peaklevel[32]; /* [r] The peak level per channel. */ + float rmslevel[32]; /* [r] The rms level per channel. */ + short numchannels; /* [r] Number of channels. */ +} FMOD_DSP_METERING_INFO; + +#endif + diff --git a/Plugins/FMODStudio/Source/FMODStudio/Public/FMOD/fmod_dsp_effects.h b/Plugins/FMODStudio/Source/FMODStudio/Public/FMOD/fmod_dsp_effects.h new file mode 100644 index 0000000..f1d1596 --- /dev/null +++ b/Plugins/FMODStudio/Source/FMODStudio/Public/FMOD/fmod_dsp_effects.h @@ -0,0 +1,1118 @@ +/* ========================================================================================== */ +/* FMOD Studio - Built-in effects header file. */ +/* Copyright (c), Firelight Technologies Pty, Ltd. 2004-2016. */ +/* */ +/* In this header you can find parameter structures for FMOD system registered DSP effects */ +/* and generators. */ +/* */ +/* ========================================================================================== */ + +#ifndef _FMOD_DSP_EFFECTS_H +#define _FMOD_DSP_EFFECTS_H + +/* +[ENUM] +[ + [DESCRIPTION] + These definitions can be used for creating FMOD defined special effects or DSP units. + + [REMARKS] + To get them to be active, first create the unit, then add it somewhere into the DSP network, + either at the front of the network near the soundcard unit to affect the global output + (by using System::getDSPHead), or on a single channel (using Channel::getDSPHead). + + [SEE_ALSO] + System::createDSPByType +] +*/ +typedef enum +{ + FMOD_DSP_TYPE_UNKNOWN, /* This unit was created via a non FMOD plugin so has an unknown purpose. */ + FMOD_DSP_TYPE_MIXER, /* This unit does nothing but take inputs and mix them together then feed the result to the soundcard unit. */ + FMOD_DSP_TYPE_OSCILLATOR, /* This unit generates sine/square/saw/triangle or noise tones. */ + FMOD_DSP_TYPE_LOWPASS, /* This unit filters sound using a high quality, resonant lowpass filter algorithm but consumes more CPU time. */ + FMOD_DSP_TYPE_ITLOWPASS, /* This unit filters sound using a resonant lowpass filter algorithm that is used in Impulse Tracker, but with limited cutoff range (0 to 8060hz). */ + FMOD_DSP_TYPE_HIGHPASS, /* This unit filters sound using a resonant highpass filter algorithm. */ + FMOD_DSP_TYPE_ECHO, /* This unit produces an echo on the sound and fades out at the desired rate. */ + FMOD_DSP_TYPE_FADER, /* This unit pans and scales the volume of a unit. */ + FMOD_DSP_TYPE_FLANGE, /* This unit produces a flange effect on the sound. */ + FMOD_DSP_TYPE_DISTORTION, /* This unit distorts the sound. */ + FMOD_DSP_TYPE_NORMALIZE, /* This unit normalizes or amplifies the sound to a certain level. */ + FMOD_DSP_TYPE_LIMITER, /* This unit limits the sound to a certain level. */ + FMOD_DSP_TYPE_PARAMEQ, /* This unit attenuates or amplifies a selected frequency range. */ + FMOD_DSP_TYPE_PITCHSHIFT, /* This unit bends the pitch of a sound without changing the speed of playback. */ + FMOD_DSP_TYPE_CHORUS, /* This unit produces a chorus effect on the sound. */ + FMOD_DSP_TYPE_VSTPLUGIN, /* This unit allows the use of Steinberg VST plugins */ + FMOD_DSP_TYPE_WINAMPPLUGIN, /* This unit allows the use of Nullsoft Winamp plugins */ + FMOD_DSP_TYPE_ITECHO, /* This unit produces an echo on the sound and fades out at the desired rate as is used in Impulse Tracker. */ + FMOD_DSP_TYPE_COMPRESSOR, /* This unit implements dynamic compression (linked/unlinked multichannel, wideband) */ + FMOD_DSP_TYPE_SFXREVERB, /* This unit implements SFX reverb */ + FMOD_DSP_TYPE_LOWPASS_SIMPLE, /* This unit filters sound using a simple lowpass with no resonance, but has flexible cutoff and is fast. */ + FMOD_DSP_TYPE_DELAY, /* This unit produces different delays on individual channels of the sound. */ + FMOD_DSP_TYPE_TREMOLO, /* This unit produces a tremolo / chopper effect on the sound. */ + FMOD_DSP_TYPE_LADSPAPLUGIN, /* Unsupported / Deprecated. */ + FMOD_DSP_TYPE_SEND, /* This unit sends a copy of the signal to a return DSP anywhere in the DSP tree. */ + FMOD_DSP_TYPE_RETURN, /* This unit receives signals from a number of send DSPs. */ + FMOD_DSP_TYPE_HIGHPASS_SIMPLE, /* This unit filters sound using a simple highpass with no resonance, but has flexible cutoff and is fast. */ + FMOD_DSP_TYPE_PAN, /* This unit pans the signal, possibly upmixing or downmixing as well. */ + FMOD_DSP_TYPE_THREE_EQ, /* This unit is a three-band equalizer. */ + FMOD_DSP_TYPE_FFT, /* This unit simply analyzes the signal and provides spectrum information back through getParameter. */ + FMOD_DSP_TYPE_LOUDNESS_METER, /* This unit analyzes the loudness and true peak of the signal. */ + FMOD_DSP_TYPE_ENVELOPEFOLLOWER, /* This unit tracks the envelope of the input/sidechain signal. Format to be publicly disclosed soon. */ + FMOD_DSP_TYPE_CONVOLUTIONREVERB, /* This unit implements convolution reverb. */ + FMOD_DSP_TYPE_CHANNELMIX, /* This unit provides per signal channel gain, and output channel mapping to allow 1 multichannel signal made up of many groups of signals to map to a single output signal. */ + FMOD_DSP_TYPE_TRANSCEIVER, /* This unit 'sends' and 'receives' from a selection of up to 32 different slots. It is like a send/return but it uses global slots rather than returns as the destination. It also has other features. Multiple transceivers can receive from a single channel, or multiple transceivers can send to a single channel, or a combination of both. */ + + FMOD_DSP_TYPE_MAX, /* Maximum number of pre-defined DSP types. */ + FMOD_DSP_TYPE_FORCEINT = 65536 /* Makes sure this enum is signed 32bit. */ +} FMOD_DSP_TYPE; + +/* + =================================================================================================== + + FMOD built in effect parameters. + Use DSP::setParameter with these enums for the 'index' parameter. + + =================================================================================================== +*/ + +/* +[ENUM] +[ + [DESCRIPTION] + Parameter types for the FMOD_DSP_TYPE_OSCILLATOR filter. + + [REMARKS] + + [SEE_ALSO] + DSP::setParameterFloat + DSP::setParameterInt + DSP::getParameterFloat + DSP::getParameterInt + FMOD_DSP_TYPE +] +*/ +typedef enum +{ + FMOD_DSP_OSCILLATOR_TYPE, /* (Type:int) - Waveform type. 0 = sine. 1 = square. 2 = sawup. 3 = sawdown. 4 = triangle. 5 = noise. */ + FMOD_DSP_OSCILLATOR_RATE /* (Type:float) - Frequency of the sinewave in hz. 1.0 to 22000.0. Default = 220.0. */ +} FMOD_DSP_OSCILLATOR; + + +/* +[ENUM] +[ + [DESCRIPTION] + Parameter types for the FMOD_DSP_TYPE_LOWPASS filter. + + [REMARKS] + + [SEE_ALSO] + DSP::setParameterFloat + DSP::getParameterFloat + FMOD_DSP_TYPE +] +*/ +typedef enum +{ + FMOD_DSP_LOWPASS_CUTOFF, /* (Type:float) - Lowpass cutoff frequency in hz. 10.0 to 22000.0. Default = 5000.0. */ + FMOD_DSP_LOWPASS_RESONANCE /* (Type:float) - Lowpass resonance Q value. 1.0 to 10.0. Default = 1.0. */ +} FMOD_DSP_LOWPASS; + + +/* +[ENUM] +[ + [DESCRIPTION] + Parameter types for the FMOD_DSP_TYPE_ITLOWPASS filter.
    + This is different to the default FMOD_DSP_TYPE_ITLOWPASS filter in that it uses a different quality algorithm and is + the filter used to produce the correct sounding playback in .IT files.
    + FMOD Studio's .IT playback uses this filter.
    + + [REMARKS] + Note! This filter actually has a limited cutoff frequency below the specified maximum, due to its limited design, + so for a more open range filter use FMOD_DSP_LOWPASS or if you don't mind not having resonance, + FMOD_DSP_LOWPASS_SIMPLE.
    + The effective maximum cutoff is about 8060hz. + + [SEE_ALSO] + DSP::setParameterFloat + DSP::getParameterFloat + FMOD_DSP_TYPE +] +*/ +typedef enum +{ + FMOD_DSP_ITLOWPASS_CUTOFF, /* (Type:float) - Lowpass cutoff frequency in hz. 1.0 to 22000.0. Default = 5000.0/ */ + FMOD_DSP_ITLOWPASS_RESONANCE /* (Type:float) - Lowpass resonance Q value. 0.0 to 127.0. Default = 1.0. */ +} FMOD_DSP_ITLOWPASS; + + +/* +[ENUM] +[ + [DESCRIPTION] + Parameter types for the FMOD_DSP_TYPE_HIGHPASS filter. + + [REMARKS] + + [SEE_ALSO] + DSP::setParameterFloat + DSP::getParameterFloat + FMOD_DSP_TYPE +] +*/ +typedef enum +{ + FMOD_DSP_HIGHPASS_CUTOFF, /* (Type:float) - Highpass cutoff frequency in hz. 1.0 to output 22000.0. Default = 5000.0. */ + FMOD_DSP_HIGHPASS_RESONANCE /* (Type:float) - Highpass resonance Q value. 1.0 to 10.0. Default = 1.0. */ +} FMOD_DSP_HIGHPASS; + + +/* +[ENUM] +[ + [DESCRIPTION] + Parameter types for the FMOD_DSP_TYPE_ECHO filter. + + [REMARKS] + Note. Every time the delay is changed, the plugin re-allocates the echo buffer. This means the echo will dissapear at that time while it refills its new buffer.
    + Larger echo delays result in larger amounts of memory allocated.
    + + [SEE_ALSO] + DSP::setParameterFloat + DSP::getParameterFloat + FMOD_DSP_TYPE +] +*/ +typedef enum +{ + FMOD_DSP_ECHO_DELAY, /* (Type:float) - Echo delay in ms. 10 to 5000. Default = 500. */ + FMOD_DSP_ECHO_FEEDBACK, /* (Type:float) - Echo decay per delay. 0 to 100. 100.0 = No decay, 0.0 = total decay (ie simple 1 line delay). Default = 50.0. */ + FMOD_DSP_ECHO_DRYLEVEL, /* (Type:float) - Original sound volume in dB. -80.0 to 10.0. Default = 0. */ + FMOD_DSP_ECHO_WETLEVEL /* (Type:float) - Volume of echo signal to pass to output in dB. -80.0 to 10.0. Default = 0. */ +} FMOD_DSP_ECHO; + + +/* +[ENUM] +[ + [DESCRIPTION] + Parameter types for the FMOD_DSP_TYPE_FLANGE filter. + + [REMARKS] + Flange is an effect where the signal is played twice at the same time, and one copy slides back and forth creating a whooshing or flanging effect.
    + As there are 2 copies of the same signal, by default each signal is given 50% mix, so that the total is not louder than the original unaffected signal.
    +
    + Flange depth is a percentage of a 10ms shift from the original signal. Anything above 10ms is not considered flange because to the ear it begins to 'echo' so 10ms is the highest value possible.
    + + [SEE_ALSO] + DSP::setParameterFloat + DSP::getParameterFloat + FMOD_DSP_TYPE +] +*/ +typedef enum +{ + FMOD_DSP_FLANGE_MIX, /* (Type:float) - Percentage of wet signal in mix. 0 to 100. Default = 50. */ + FMOD_DSP_FLANGE_DEPTH, /* (Type:float) - Flange depth (percentage of 40ms delay). 0.01 to 1.0. Default = 1.0. */ + FMOD_DSP_FLANGE_RATE /* (Type:float) - Flange speed in hz. 0.0 to 20.0. Default = 0.1. */ +} FMOD_DSP_FLANGE; + + +/* +[ENUM] +[ + [DESCRIPTION] + Parameter types for the FMOD_DSP_TYPE_DISTORTION filter. + + [REMARKS] + + [SEE_ALSO] + DSP::setParameterFloat + DSP::getParameterFloat + FMOD_DSP_TYPE +] +*/ +typedef enum +{ + FMOD_DSP_DISTORTION_LEVEL /* (Type:float) - Distortion value. 0.0 to 1.0. Default = 0.5. */ +} FMOD_DSP_DISTORTION; + + +/* +[ENUM] +[ + [DESCRIPTION] + Parameter types for the FMOD_DSP_TYPE_NORMALIZE filter. + + [REMARKS] + Normalize amplifies the sound based on the maximum peaks within the signal.
    + For example if the maximum peaks in the signal were 50% of the bandwidth, it would scale the whole sound by 2.
    + The lower threshold value makes the normalizer ignores peaks below a certain point, to avoid over-amplification if a loud signal suddenly came in, and also to avoid amplifying to maximum things like background hiss.
    +
    + Because FMOD is a realtime audio processor, it doesn't have the luxury of knowing the peak for the whole sound (ie it can't see into the future), so it has to process data as it comes in.
    + To avoid very sudden changes in volume level based on small samples of new data, fmod fades towards the desired amplification which makes for smooth gain control. The fadetime parameter can control this.
    + + [SEE_ALSO] + DSP::setParameterFloat + DSP::getParameterFloat + FMOD_DSP_TYPE +] +*/ +typedef enum +{ + FMOD_DSP_NORMALIZE_FADETIME, /* (Type:float) - Time to ramp the silence to full in ms. 0.0 to 20000.0. Default = 5000.0. */ + FMOD_DSP_NORMALIZE_THRESHHOLD, /* (Type:float) - Lower volume range threshold to ignore. 0.0 to 1.0. Default = 0.1. Raise higher to stop amplification of very quiet signals. */ + FMOD_DSP_NORMALIZE_MAXAMP /* (Type:float) - Maximum amplification allowed. 1.0 to 100000.0. Default = 20.0. 1.0 = no amplifaction, higher values allow more boost. */ +} FMOD_DSP_NORMALIZE; + + +/* +[ENUM] +[ + [DESCRIPTION] + Parameter types for the FMOD_DSP_TYPE_LIMITER filter. + + [REMARKS] + + [SEE_ALSO] + DSP::setParameterFloat + DSP::getParameterFloat + FMOD_DSP_TYPE +] +*/ +typedef enum +{ + FMOD_DSP_LIMITER_RELEASETIME, /* (Type:float) - Time to ramp the silence to full in ms. 1.0 to 1000.0. Default = 10.0. */ + FMOD_DSP_LIMITER_CEILING, /* (Type:float) - Maximum level of the output signal in dB. -12.0 to 0.0. Default = 0.0. */ + FMOD_DSP_LIMITER_MAXIMIZERGAIN, /* (Type:float) - Maximum amplification allowed in dB. 0.0 to 12.0. Default = 0.0. 0.0 = no amplifaction, higher values allow more boost. */ + FMOD_DSP_LIMITER_MODE, /* (Type:float) - Channel processing mode. 0 or 1. Default = 0. 0 = Independent (limiter per channel), 1 = Linked. */ +} FMOD_DSP_LIMITER; + + +/* +[ENUM] +[ + [DESCRIPTION] + Parameter types for the FMOD_DSP_TYPE_PARAMEQ filter. + + [REMARKS] + Parametric EQ is a bandpass filter that attenuates or amplifies a selected frequency and its neighbouring frequencies.
    +
    + To create a multi-band EQ create multiple FMOD_DSP_TYPE_PARAMEQ units and set each unit to different frequencies, for example 1000hz, 2000hz, 4000hz, 8000hz, 16000hz with a range of 1 octave each.
    +
    + When a frequency has its gain set to 1.0, the sound will be unaffected and represents the original signal exactly.
    + + [SEE_ALSO] + DSP::setParameterFloat + DSP::getParameterFloat + FMOD_DSP_TYPE +] +*/ +typedef enum +{ + FMOD_DSP_PARAMEQ_CENTER, /* (Type:float) - Frequency center. 20.0 to 22000.0. Default = 8000.0. */ + FMOD_DSP_PARAMEQ_BANDWIDTH, /* (Type:float) - Octave range around the center frequency to filter. 0.2 to 5.0. Default = 1.0. */ + FMOD_DSP_PARAMEQ_GAIN /* (Type:float) - Frequency Gain in dB. -30 to 30. Default = 0. */ +} FMOD_DSP_PARAMEQ; + + + +/* +[ENUM] +[ + [DESCRIPTION] + Parameter types for the FMOD_DSP_TYPE_PITCHSHIFT filter. + + [REMARKS] + This pitch shifting unit can be used to change the pitch of a sound without speeding it up or slowing it down.
    + It can also be used for time stretching or scaling, for example if the pitch was doubled, and the frequency of the sound was halved, the pitch of the sound would sound correct but it would be twice as slow.
    +
    + Warning! This filter is very computationally expensive! Similar to a vocoder, it requires several overlapping FFT and IFFT's to produce smooth output, and can require around 440mhz for 1 stereo 48khz signal using the default settings.
    + Reducing the signal to mono will half the cpu usage.
    + Reducing this will lower audio quality, but what settings to use are largely dependant on the sound being played. A noisy polyphonic signal will need higher fft size compared to a speaking voice for example.
    +
    + This pitch shifter is based on the pitch shifter code at http://www.dspdimension.com, written by Stephan M. Bernsee.
    + The original code is COPYRIGHT 1999-2003 Stephan M. Bernsee .
    +
    + 'maxchannels' dictates the amount of memory allocated. By default, the maxchannels value is 0. If FMOD is set to stereo, the pitch shift unit will allocate enough memory for 2 channels. If it is 5.1, it will allocate enough memory for a 6 channel pitch shift, etc.
    + If the pitch shift effect is only ever applied to the global mix (ie it was added with ChannelGroup::addDSP), then 0 is the value to set as it will be enough to handle all speaker modes.
    + When the pitch shift is added to a channel (ie Channel::addDSP) then the channel count that comes in could be anything from 1 to 8 possibly. It is only in this case where you might want to increase the channel count above the output's channel count.
    + If a channel pitch shift is set to a lower number than the sound's channel count that is coming in, it will not pitch shift the sound.
    +
    + NOTE! Not supported on PlayStation 3.
    + + [SEE_ALSO] + DSP::setParameterFloat + DSP::getParameterFloat + ChannelGroup::addDSP + FMOD_DSP_TYPE +] +*/ +typedef enum +{ + FMOD_DSP_PITCHSHIFT_PITCH, /* (Type:float) - Pitch value. 0.5 to 2.0. Default = 1.0. 0.5 = one octave down, 2.0 = one octave up. 1.0 does not change the pitch. */ + FMOD_DSP_PITCHSHIFT_FFTSIZE, /* (Type:float) - FFT window size. 256, 512, 1024, 2048, 4096. Default = 1024. Increase this to reduce 'smearing'. This effect is a warbling sound similar to when an mp3 is encoded at very low bitrates. */ + FMOD_DSP_PITCHSHIFT_OVERLAP, /* (Type:float) - Removed. Do not use. FMOD now uses 4 overlaps and cannot be changed. */ + FMOD_DSP_PITCHSHIFT_MAXCHANNELS /* (Type:float) - Maximum channels supported. 0 to 16. 0 = same as fmod's default output polyphony, 1 = mono, 2 = stereo etc. See remarks for more. Default = 0. It is suggested to leave at 0! */ +} FMOD_DSP_PITCHSHIFT; + + +/* +[ENUM] +[ + [DESCRIPTION] + Parameter types for the FMOD_DSP_TYPE_CHORUS filter. + + [REMARKS] + Chorous is an effect where the sound is more 'spacious' due to 1 to 3 versions of the sound being played along side the original signal but with the pitch of each copy modulating on a sine wave.
    + + [SEE_ALSO] + DSP::setParameterFloat + DSP::getParameterFloat + FMOD_DSP_TYPE +] +*/ +typedef enum +{ + FMOD_DSP_CHORUS_MIX, /* (Type:float) - Volume of original signal to pass to output. 0.0 to 100.0. Default = 50.0. */ + FMOD_DSP_CHORUS_RATE, /* (Type:float) - Chorus modulation rate in Hz. 0.0 to 20.0. Default = 0.8 Hz. */ + FMOD_DSP_CHORUS_DEPTH, /* (Type:float) - Chorus modulation depth. 0.0 to 100.0. Default = 3.0. */ +} FMOD_DSP_CHORUS; + + +/* +[ENUM] +[ + [DESCRIPTION] + Parameter types for the FMOD_DSP_TYPE_ITECHO filter.
    + This is effectively a software based echo filter that emulates the DirectX DMO echo effect. Impulse tracker files can support this, and FMOD will produce the effect on ANY platform, not just those that support DirectX effects!
    + + [REMARKS] + Note. Every time the delay is changed, the plugin re-allocates the echo buffer. This means the echo will dissapear at that time while it refills its new buffer.
    + Larger echo delays result in larger amounts of memory allocated.
    +
    + As this is a stereo filter made mainly for IT playback, it is targeted for stereo signals.
    + With mono signals only the FMOD_DSP_ITECHO_LEFTDELAY is used.
    + For multichannel signals (>2) there will be no echo on those channels.
    + + [SEE_ALSO] + DSP::setParameterFloat + DSP::getParameterFloat + FMOD_DSP_TYPE +] +*/ +typedef enum +{ + FMOD_DSP_ITECHO_WETDRYMIX, /* (Type:float) - Ratio of wet (processed) signal to dry (unprocessed) signal. Must be in the range from 0.0 through 100.0 (all wet). Default = 50. */ + FMOD_DSP_ITECHO_FEEDBACK, /* (Type:float) - Percentage of output fed back into input, in the range from 0.0 through 100.0. Default = 50. */ + FMOD_DSP_ITECHO_LEFTDELAY, /* (Type:float) - Delay for left channel, in milliseconds, in the range from 1.0 through 2000.0. Default = 500 ms. */ + FMOD_DSP_ITECHO_RIGHTDELAY, /* (Type:float) - Delay for right channel, in milliseconds, in the range from 1.0 through 2000.0. Default = 500 ms. */ + FMOD_DSP_ITECHO_PANDELAY /* (Type:float) - Value that specifies whether to swap left and right delays with each successive echo. Ranges from 0.0 (equivalent to FALSE) to 1.0 (equivalent to TRUE), meaning no swap. Default = 0. CURRENTLY NOT SUPPORTED. */ +} FMOD_DSP_ITECHO; + +/* +[ENUM] +[ + [DESCRIPTION] + Parameter types for the FMOD_DSP_TYPE_COMPRESSOR unit. + This is a multichannel software limiter that is uniform across the whole spectrum. + + [REMARKS] + The limiter is not guaranteed to catch every peak above the threshold level, + because it cannot apply gain reduction instantaneously - the time delay is + determined by the attack time. However setting the attack time too short will + distort the sound, so it is a compromise. High level peaks can be avoided by + using a short attack time - but not too short, and setting the threshold a few + decibels below the critical level. +
    + + [SEE_ALSO] + DSP::setParameterFloat + DSP::getParameterFloat + DSP::setParameterBool + DSP::getParameterBool + FMOD_DSP_TYPE +] +*/ +typedef enum +{ + FMOD_DSP_COMPRESSOR_THRESHOLD, /* (Type:float) - Threshold level (dB) in the range from -80 through 0. Default = 0. */ + FMOD_DSP_COMPRESSOR_RATIO, /* (Type:float) - Compression Ratio (dB/dB) in the range from 1 to 50. Default = 2.5. */ + FMOD_DSP_COMPRESSOR_ATTACK, /* (Type:float) - Attack time (milliseconds), in the range from 0.1 through 1000. Default value is 20. */ + FMOD_DSP_COMPRESSOR_RELEASE, /* (Type:float) - Release time (milliseconds), in the range from 10 through 5000. Default value is 100 */ + FMOD_DSP_COMPRESSOR_GAINMAKEUP, /* (Type:float) - Make-up gain (dB) applied after limiting, in the range from 0 through 30. Default = 0. */ + FMOD_DSP_COMPRESSOR_USESIDECHAIN, /* (Type:data) - Data of type FMOD_DSP_PARAMETER_SIDECHAIN. Whether to analyse the sidechain signal instead of the input signal. Default is { false } */ + FMOD_DSP_COMPRESSOR_LINKED /* (Type:bool) - FALSE = Independent (compressor per channel), TRUE = Linked. Default = TRUE. */ +} FMOD_DSP_COMPRESSOR; + +/* +[ENUM] +[ + [DESCRIPTION] + Parameter types for the FMOD_DSP_TYPE_SFXREVERB unit.
    + + [REMARKS] + This is a high quality I3DL2 based reverb.
    + On top of the I3DL2 property set, "Dry Level" is also included to allow the dry mix to be changed.
    +
    + These properties can be set with presets in FMOD_REVERB_PRESETS. + + [SEE_ALSO] + DSP::setParameterFloat + DSP::getParameterFloat + FMOD_DSP_TYPE + FMOD_REVERB_PRESETS +] +*/ +typedef enum +{ + FMOD_DSP_SFXREVERB_DECAYTIME, /* (Type:float) - Decay Time : Reverberation decay time at low-frequencies in milliseconds. Ranges from 100.0 to 20000.0. Default is 1500. */ + FMOD_DSP_SFXREVERB_EARLYDELAY, /* (Type:float) - Early Delay : Delay time of first reflection in milliseconds. Ranges from 0.0 to 300.0. Default is 20. */ + FMOD_DSP_SFXREVERB_LATEDELAY, /* (Type:float) - Reverb Delay : Late reverberation delay time relative to first reflection in milliseconds. Ranges from 0.0 to 100.0. Default is 40. */ + FMOD_DSP_SFXREVERB_HFREFERENCE, /* (Type:float) - HF Reference : Reference frequency for high-frequency decay in Hz. Ranges from 20.0 to 20000.0. Default is 5000. */ + FMOD_DSP_SFXREVERB_HFDECAYRATIO, /* (Type:float) - Decay HF Ratio : High-frequency decay time relative to decay time in percent. Ranges from 10.0 to 100.0. Default is 50. */ + FMOD_DSP_SFXREVERB_DIFFUSION, /* (Type:float) - Diffusion : Reverberation diffusion (echo density) in percent. Ranges from 0.0 to 100.0. Default is 100. */ + FMOD_DSP_SFXREVERB_DENSITY, /* (Type:float) - Density : Reverberation density (modal density) in percent. Ranges from 0.0 to 100.0. Default is 100. */ + FMOD_DSP_SFXREVERB_LOWSHELFFREQUENCY, /* (Type:float) - Low Shelf Frequency : Transition frequency of low-shelf filter in Hz. Ranges from 20.0 to 1000.0. Default is 250. */ + FMOD_DSP_SFXREVERB_LOWSHELFGAIN, /* (Type:float) - Low Shelf Gain : Gain of low-shelf filter in dB. Ranges from -36.0 to 12.0. Default is 0. */ + FMOD_DSP_SFXREVERB_HIGHCUT, /* (Type:float) - High Cut : Cutoff frequency of low-pass filter in Hz. Ranges from 20.0 to 20000.0. Default is 20000. */ + FMOD_DSP_SFXREVERB_EARLYLATEMIX, /* (Type:float) - Early/Late Mix : Blend ratio of late reverb to early reflections in percent. Ranges from 0.0 to 100.0. Default is 50. */ + FMOD_DSP_SFXREVERB_WETLEVEL, /* (Type:float) - Wet Level : Reverb signal level in dB. Ranges from -80.0 to 20.0. Default is -6. */ + FMOD_DSP_SFXREVERB_DRYLEVEL /* (Type:float) - Dry Level : Dry signal level in dB. Ranges from -80.0 to 20.0. Default is 0. */ +} FMOD_DSP_SFXREVERB; + +/* +[ENUM] +[ + [DESCRIPTION] + Parameter types for the FMOD_DSP_TYPE_LOWPASS_SIMPLE filter.
    + This is a very simple low pass filter, based on two single-pole RC time-constant modules. + The emphasis is on speed rather than accuracy, so this should not be used for task requiring critical filtering.
    + + [REMARKS] + + [SEE_ALSO] + DSP::setParameterFloat + DSP::getParameterFloat + FMOD_DSP_TYPE +] +*/ +typedef enum +{ + FMOD_DSP_LOWPASS_SIMPLE_CUTOFF /* (Type:float) - Lowpass cutoff frequency in hz. 10.0 to 22000.0. Default = 5000.0 */ +} FMOD_DSP_LOWPASS_SIMPLE; + + +/* +[ENUM] +[ + [DESCRIPTION] + Parameter types for the FMOD_DSP_TYPE_DELAY filter. + + [REMARKS] + Note. Every time MaxDelay is changed, the plugin re-allocates the delay buffer. This means the delay will dissapear at that time while it refills its new buffer.
    + A larger MaxDelay results in larger amounts of memory allocated.
    + Channel delays above MaxDelay will be clipped to MaxDelay and the delay buffer will not be resized.
    +
    + NOTE! Not supported on PlayStation 3. + + [SEE_ALSO] + DSP::setParameterFloat + DSP::getParameterFloat + FMOD_DSP_TYPE +] +*/ +typedef enum +{ + FMOD_DSP_DELAY_CH0, /* (Type:float) - Channel #0 Delay in ms. 0 to 10000. Default = 0. */ + FMOD_DSP_DELAY_CH1, /* (Type:float) - Channel #1 Delay in ms. 0 to 10000. Default = 0. */ + FMOD_DSP_DELAY_CH2, /* (Type:float) - Channel #2 Delay in ms. 0 to 10000. Default = 0. */ + FMOD_DSP_DELAY_CH3, /* (Type:float) - Channel #3 Delay in ms. 0 to 10000. Default = 0. */ + FMOD_DSP_DELAY_CH4, /* (Type:float) - Channel #4 Delay in ms. 0 to 10000. Default = 0. */ + FMOD_DSP_DELAY_CH5, /* (Type:float) - Channel #5 Delay in ms. 0 to 10000. Default = 0. */ + FMOD_DSP_DELAY_CH6, /* (Type:float) - Channel #6 Delay in ms. 0 to 10000. Default = 0. */ + FMOD_DSP_DELAY_CH7, /* (Type:float) - Channel #7 Delay in ms. 0 to 10000. Default = 0. */ + FMOD_DSP_DELAY_CH8, /* (Type:float) - Channel #8 Delay in ms. 0 to 10000. Default = 0. */ + FMOD_DSP_DELAY_CH9, /* (Type:float) - Channel #9 Delay in ms. 0 to 10000. Default = 0. */ + FMOD_DSP_DELAY_CH10, /* (Type:float) - Channel #10 Delay in ms. 0 to 10000. Default = 0. */ + FMOD_DSP_DELAY_CH11, /* (Type:float) - Channel #11 Delay in ms. 0 to 10000. Default = 0. */ + FMOD_DSP_DELAY_CH12, /* (Type:float) - Channel #12 Delay in ms. 0 to 10000. Default = 0. */ + FMOD_DSP_DELAY_CH13, /* (Type:float) - Channel #13 Delay in ms. 0 to 10000. Default = 0. */ + FMOD_DSP_DELAY_CH14, /* (Type:float) - Channel #14 Delay in ms. 0 to 10000. Default = 0. */ + FMOD_DSP_DELAY_CH15, /* (Type:float) - Channel #15 Delay in ms. 0 to 10000. Default = 0. */ + FMOD_DSP_DELAY_MAXDELAY /* (Type:float) - Maximum delay in ms. 0 to 10000. Default = 10. */ +} FMOD_DSP_DELAY; + + +/* +[ENUM] +[ + [DESCRIPTION] + Parameter types for the FMOD_DSP_TYPE_TREMOLO filter. + + [REMARKS] + The tremolo effect varies the amplitude of a sound. Depending on the settings, this unit can produce a tremolo, chopper or auto-pan effect.
    +
    + The shape of the LFO (low freq. oscillator) can morphed between sine, triangle and sawtooth waves using the FMOD_DSP_TREMOLO_SHAPE and FMOD_DSP_TREMOLO_SKEW parameters.
    + FMOD_DSP_TREMOLO_DUTY and FMOD_DSP_TREMOLO_SQUARE are useful for a chopper-type effect where the first controls the on-time duration and second controls the flatness of the envelope.
    + FMOD_DSP_TREMOLO_SPREAD varies the LFO phase between channels to get an auto-pan effect. This works best with a sine shape LFO.
    + The LFO can be synchronized using the FMOD_DSP_TREMOLO_PHASE parameter which sets its instantaneous phase.
    + + [SEE_ALSO] + DSP::setParameterFloat + DSP::getParameterFloat + FMOD_DSP_TYPE +] +*/ +typedef enum +{ + FMOD_DSP_TREMOLO_FREQUENCY, /* (Type:float) - LFO frequency in Hz. 0.1 to 20. Default = 5. */ + FMOD_DSP_TREMOLO_DEPTH, /* (Type:float) - Tremolo depth. 0 to 1. Default = 1. */ + FMOD_DSP_TREMOLO_SHAPE, /* (Type:float) - LFO shape morph between triangle and sine. 0 to 1. Default = 0. */ + FMOD_DSP_TREMOLO_SKEW, /* (Type:float) - Time-skewing of LFO cycle. -1 to 1. Default = 0. */ + FMOD_DSP_TREMOLO_DUTY, /* (Type:float) - LFO on-time. 0 to 1. Default = 0.5. */ + FMOD_DSP_TREMOLO_SQUARE, /* (Type:float) - Flatness of the LFO shape. 0 to 1. Default = 0. */ + FMOD_DSP_TREMOLO_PHASE, /* (Type:float) - Instantaneous LFO phase. 0 to 1. Default = 0. */ + FMOD_DSP_TREMOLO_SPREAD /* (Type:float) - Rotation / auto-pan effect. -1 to 1. Default = 0. */ +} FMOD_DSP_TREMOLO; + + +/* +[ENUM] +[ + [DESCRIPTION] + Parameter types for the FMOD_DSP_TYPE_SEND DSP. + + [REMARKS] + + [SEE_ALSO] + DSP::setParameterInt + DSP::getParameterInt + DSP::setParameterFloat + DSP::getParameterFloat + FMOD_DSP_TYPE +] +*/ +typedef enum +{ + FMOD_DSP_SEND_RETURNID, /* (Type:int) - ID of the Return DSP this send is connected to (integer values only). -1 indicates no connected Return DSP. Default = -1. */ + FMOD_DSP_SEND_LEVEL, /* (Type:float) - Send level. 0.0 to 1.0. Default = 1.0 */ +} FMOD_DSP_SEND; + + +/* +[ENUM] +[ + [DESCRIPTION] + Parameter types for the FMOD_DSP_TYPE_RETURN DSP. + + [REMARKS] + + [SEE_ALSO] + DSP::setParameterInt + DSP::getParameterInt + FMOD_DSP_TYPE +] +*/ +typedef enum +{ + FMOD_DSP_RETURN_ID, /* (Type:int) - [r] ID of this Return DSP. Read-only. Default = -1. */ + FMOD_DSP_RETURN_INPUT_SPEAKER_MODE /* (Type:int) - [r/w] Input speaker mode of this return. Default = FMOD_SPEAKERMODE_DEFAULT. */ +} FMOD_DSP_RETURN; + + +/* +[ENUM] +[ + [DESCRIPTION] + Parameter types for the FMOD_DSP_TYPE_HIGHPASS_SIMPLE filter.
    + This is a very simple single-order high pass filter. + The emphasis is on speed rather than accuracy, so this should not be used for task requiring critical filtering.
    + + [REMARKS] + + [SEE_ALSO] + DSP::setParameterFloat + DSP::getParameterFloat + FMOD_DSP_TYPE +] +*/ +typedef enum +{ + FMOD_DSP_HIGHPASS_SIMPLE_CUTOFF /* (Type:float) - Highpass cutoff frequency in hz. 10.0 to 22000.0. Default = 1000.0 */ +} FMOD_DSP_HIGHPASS_SIMPLE; + + +/* +[ENUM] +[ + [DESCRIPTION] + Parameter values for the FMOD_DSP_PAN_SURROUND_FROM_STEREO_MODE parameter of the FMOD_DSP_TYPE_PAN DSP. + + [REMARKS] + + [SEE_ALSO] + FMOD_DSP_PAN +] +*/ +typedef enum +{ + FMOD_DSP_PAN_SURROUND_FROM_STEREO_MODE_DISTRIBUTED, + FMOD_DSP_PAN_SURROUND_FROM_STEREO_MODE_DISCRETE +} FMOD_DSP_PAN_SURROUND_FROM_STEREO_MODE_TYPE; + + +/* +[ENUM] +[ + [DESCRIPTION] + Parameter values for the FMOD_DSP_PAN_MODE parameter of the FMOD_DSP_TYPE_PAN DSP. + + [REMARKS] + + [SEE_ALSO] + FMOD_DSP_PAN +] +*/ +typedef enum +{ + FMOD_DSP_PAN_MODE_MONO, + FMOD_DSP_PAN_MODE_STEREO, + FMOD_DSP_PAN_MODE_SURROUND +} FMOD_DSP_PAN_MODE_TYPE; + + +/* +[ENUM] +[ + [DESCRIPTION] + Parameter values for the FMOD_DSP_PAN_3D_ROLLOFF parameter of the FMOD_DSP_TYPE_PAN DSP. + + [REMARKS] + + [SEE_ALSO] + FMOD_DSP_PAN +] +*/ +typedef enum +{ + FMOD_DSP_PAN_3D_ROLLOFF_LINEARSQUARED, + FMOD_DSP_PAN_3D_ROLLOFF_LINEAR, + FMOD_DSP_PAN_3D_ROLLOFF_INVERSE, + FMOD_DSP_PAN_3D_ROLLOFF_INVERSETAPERED, + FMOD_DSP_PAN_3D_ROLLOFF_CUSTOM +} FMOD_DSP_PAN_3D_ROLLOFF_TYPE; + + +/* +[ENUM] +[ + [DESCRIPTION] + Parameter values for the FMOD_DSP_PAN_3D_EXTENT_MODE parameter of the FMOD_DSP_TYPE_PAN DSP. + + [REMARKS] + + [SEE_ALSO] + FMOD_DSP_PAN +] +*/ +typedef enum +{ + FMOD_DSP_PAN_3D_EXTENT_MODE_AUTO, + FMOD_DSP_PAN_3D_EXTENT_MODE_USER, + FMOD_DSP_PAN_3D_EXTENT_MODE_OFF +} FMOD_DSP_PAN_3D_EXTENT_MODE_TYPE; + + +/* +[ENUM] +[ + [DESCRIPTION] + Parameter types for the FMOD_DSP_TYPE_PAN DSP. + + [REMARKS] + FMOD_DSP_PAN_3D_PAN_BLEND controls the percentage of the effect supplied by FMOD_DSP_PAN_SURROUND_DIRECTION and FMOD_DSP_PAN_SURROUND_EXTENT. + + [SEE_ALSO] + DSP::setParameterFloat + DSP::getParameterFloat + DSP::setParameterInt + DSP::getParameterInt + DSP::setParameterData + DSP::getParameterData + FMOD_DSP_TYPE +] +*/ +typedef enum +{ + FMOD_DSP_PAN_MODE, /* (Type:int) - Panner mode. FMOD_DSP_PAN_MODE_MONO for mono down-mix, FMOD_DSP_PAN_MODE_STEREO for stereo panning or FMOD_DSP_PAN_MODE_SURROUND for surround panning. Default = FMOD_DSP_PAN_MODE_SURROUND */ + FMOD_DSP_PAN_STEREO_POSITION, /* (Type:float) - Stereo pan position. -100.0 to 100.0. Default = 0.0. */ + FMOD_DSP_PAN_SURROUND_DIRECTION, /* (Type:float) - Surround pan direction. -180.0 (degrees) to 180.0 (degrees). Default = 0.0. */ + FMOD_DSP_PAN_SURROUND_EXTENT, /* (Type:float) - Surround pan extent. 0.0 (degrees) to 360.0 (degrees). Distance from center point of panning circle. Default = 360.0. */ + FMOD_DSP_PAN_SURROUND_ROTATION, /* (Type:float) - Surround pan rotation. -180.0 (degrees) to 180.0 (degrees). Default = 0.0. */ + FMOD_DSP_PAN_SURROUND_LFE_LEVEL, /* (Type:float) - Surround pan LFE level. -80.0 (db) to 20.0 (db). Default = 0.0. */ + FMOD_DSP_PAN_SURROUND_FROM_STEREO_MODE, /* (Type:int) - Stereo-To-Surround Mode. FMOD_DSP_PAN_SURROUND_FROM_STEREO_MODE_DISTRIBUTED to FMOD_DSP_PAN_SURROUND_FROM_STEREO_MODE_DISCRETE. Default = FMOD_DSP_PAN_SURROUND_FROM_STEREO_MODE_DISCRETE. */ + FMOD_DSP_PAN_SURROUND_STEREO_SEPARATION, /* (Type:float) - Stereo-To-Surround Stereo Separation. -180.0 (degrees) to +180.0 (degrees). Default = 60.0. */ + FMOD_DSP_PAN_SURROUND_STEREO_AXIS, /* (Type:float) - Stereo-To-Surround Stereo Axis. -180.0 (degrees) to +180.0 (degrees). Default = 0.0. */ + FMOD_DSP_PAN_ENABLED_SURROUND_SPEAKERS, /* (Type:int) - Surround Speakers Enabled. 0 to 0xFFF. Default = 0xFFF. */ + FMOD_DSP_PAN_3D_POSITION, /* (Type:data) - 3D Position. data of type FMOD_DSP_PARAMETER_3DATTRIBUTES_MULTI */ + FMOD_DSP_PAN_3D_ROLLOFF, /* (Type:int) - 3D Rolloff. FMOD_DSP_PAN_3D_ROLLOFF_LINEARSQUARED to FMOD_DSP_PAN_3D_ROLLOFF_CUSTOM. Default = FMOD_DSP_PAN_3D_ROLLOFF_LINEARSQUARED. */ + FMOD_DSP_PAN_3D_MIN_DISTANCE, /* (Type:float) - 3D Min Distance. 0.0 to 1e+19f. Default = 1.0. */ + FMOD_DSP_PAN_3D_MAX_DISTANCE, /* (Type:float) - 3D Max Distance. 0.0 to 1e+19f. Default = 20.0. */ + FMOD_DSP_PAN_3D_EXTENT_MODE, /* (Type:int) - 3D Extent Mode. FMOD_DSP_PAN_3D_EXTENT_MODE_AUTO to FMOD_DSP_PAN_3D_EXTENT_MODE_OFF. Default = FMOD_DSP_PAN_3D_EXTENT_MODE_AUTO. */ + FMOD_DSP_PAN_3D_SOUND_SIZE, /* (Type:float) - 3D Sound Size. 0.0 to 1e+19f. Default = 0.0. */ + FMOD_DSP_PAN_3D_MIN_EXTENT, /* (Type:float) - 3D Min Extent. 0.0 (degrees) to 360.0 (degrees). Default = 0.0. */ + FMOD_DSP_PAN_3D_PAN_BLEND, /* (Type:float) - 3D Pan Blend. 0.0 (fully 2D) to 1.0 (fully 3D). Default = 0.0. */ + FMOD_DSP_PAN_LFE_UPMIX_ENABLED, /* (Type:int) - LFE Upmix Enabled. 0 to 1. Default = 0. */ + FMOD_DSP_PAN_OVERALL_GAIN, /* (Type:data) - Overall gain. data of type FMOD_DSP_PARAMETER_DATA_TYPE_OVERALLGAIN */ + FMOD_DSP_PAN_SURROUND_SPEAKER_MODE /* (Type:int) - Surround speaker mode. Target speaker mode for surround panning. Default = FMOD_SPEAKERMODE_DEFAULT. */ +} FMOD_DSP_PAN; + + +/* +[ENUM] +[ + [DESCRIPTION] + Parameter values for the FMOD_DSP_THREE_EQ_CROSSOVERSLOPE parameter of the FMOD_DSP_TYPE_THREE_EQ DSP. + + [REMARKS] + + [SEE_ALSO] + FMOD_DSP_THREE_EQ +] +*/ +typedef enum +{ + FMOD_DSP_THREE_EQ_CROSSOVERSLOPE_12DB, + FMOD_DSP_THREE_EQ_CROSSOVERSLOPE_24DB, + FMOD_DSP_THREE_EQ_CROSSOVERSLOPE_48DB +} FMOD_DSP_THREE_EQ_CROSSOVERSLOPE_TYPE; + + +/* +[ENUM] +[ + [DESCRIPTION] + Parameter types for the FMOD_DSP_TYPE_THREE_EQ filter. + + [REMARKS] + + [SEE_ALSO] + DSP::setParameterFloat + DSP::getParameterFloat + DSP::setParameterInt + DSP::getParameterInt + FMOD_DSP_TYPE + FMOD_DSP_THREE_EQ_CROSSOVERSLOPE_TYPE +] +*/ +typedef enum +{ + FMOD_DSP_THREE_EQ_LOWGAIN, /* (Type:float) - Low frequency gain in dB. -80.0 to 10.0. Default = 0. */ + FMOD_DSP_THREE_EQ_MIDGAIN, /* (Type:float) - Mid frequency gain in dB. -80.0 to 10.0. Default = 0. */ + FMOD_DSP_THREE_EQ_HIGHGAIN, /* (Type:float) - High frequency gain in dB. -80.0 to 10.0. Default = 0. */ + FMOD_DSP_THREE_EQ_LOWCROSSOVER, /* (Type:float) - Low-to-mid crossover frequency in Hz. 10.0 to 22000.0. Default = 400.0. */ + FMOD_DSP_THREE_EQ_HIGHCROSSOVER, /* (Type:float) - Mid-to-high crossover frequency in Hz. 10.0 to 22000.0. Default = 4000.0. */ + FMOD_DSP_THREE_EQ_CROSSOVERSLOPE /* (Type:int) - Crossover Slope. 0 = 12dB/Octave, 1 = 24dB/Octave, 2 = 48dB/Octave. Default = 1 (24dB/Octave). */ +} FMOD_DSP_THREE_EQ; + + +/* +[ENUM] +[ + [DESCRIPTION] + List of windowing methods for the FMOD_DSP_TYPE_FFT unit. Used in spectrum analysis to reduce leakage / transient signals intefering with the analysis.
    + This is a problem with analysis of continuous signals that only have a small portion of the signal sample (the fft window size).
    + Windowing the signal with a curve or triangle tapers the sides of the fft window to help alleviate this problem. + + [REMARKS] + Cyclic signals such as a sine wave that repeat their cycle in a multiple of the window size do not need windowing.
    + I.e. If the sine wave repeats every 1024, 512, 256 etc samples and the FMOD fft window is 1024, then the signal would not need windowing.
    + Not windowing is the same as FMOD_DSP_FFT_WINDOW_RECT, which is the default.
    + If the cycle of the signal (ie the sine wave) is not a multiple of the window size, it will cause frequency abnormalities, so a different windowing method is needed.
    + +
    + FMOD_DSP_FFT_WINDOW_RECT.
    +
    +
    + FMOD_DSP_FFT_WINDOW_TRIANGLE.
    +
    +
    + FMOD_DSP_FFT_WINDOW_HAMMING.
    +
    +
    + FMOD_DSP_FFT_WINDOW_HANNING.
    +
    +
    + FMOD_DSP_FFT_WINDOW_BLACKMAN.
    +
    +
    + FMOD_DSP_FFT_WINDOW_BLACKMANHARRIS.
    + +
    + + [SEE_ALSO] + FMOD_DSP_FFT +] +*/ +typedef enum +{ + FMOD_DSP_FFT_WINDOW_RECT, /* w[n] = 1.0 */ + FMOD_DSP_FFT_WINDOW_TRIANGLE, /* w[n] = TRI(2n/N) */ + FMOD_DSP_FFT_WINDOW_HAMMING, /* w[n] = 0.54 - (0.46 * COS(n/N) ) */ + FMOD_DSP_FFT_WINDOW_HANNING, /* w[n] = 0.5 * (1.0 - COS(n/N) ) */ + FMOD_DSP_FFT_WINDOW_BLACKMAN, /* w[n] = 0.42 - (0.5 * COS(n/N) ) + (0.08 * COS(2.0 * n/N) ) */ + FMOD_DSP_FFT_WINDOW_BLACKMANHARRIS /* w[n] = 0.35875 - (0.48829 * COS(1.0 * n/N)) + (0.14128 * COS(2.0 * n/N)) - (0.01168 * COS(3.0 * n/N)) */ +} FMOD_DSP_FFT_WINDOW; + + +/* +[ENUM] +[ + [DESCRIPTION] + Parameter types for the FMOD_DSP_TYPE_FFT dsp effect. + + [REMARKS] + Set the attributes for the spectrum analysis with FMOD_DSP_FFT_WINDOWSIZE and FMOD_DSP_FFT_WINDOWTYPE, and retrieve the results with FMOD_DSP_FFT_SPECTRUM and FMOD_DSP_FFT_DOMINANT_FREQ. + FMOD_DSP_FFT_SPECTRUM stores its data in the FMOD_DSP_PARAMETER_DATA_TYPE_FFT. You will need to cast to this structure to get the right data. + + [SEE_ALSO] + DSP::setParameterFloat + DSP::getParameterFloat + DSP::setParameterInt + DSP::getParameterInt + DSP::setParameterData + DSP::getParameterData + FMOD_DSP_TYPE + FMOD_DSP_FFT_WINDOW +] +*/ +typedef enum +{ + FMOD_DSP_FFT_WINDOWSIZE, /* (Type:int) - [r/w] Must be a power of 2 between 128 and 16384. 128, 256, 512, 1024, 2048, 4096, 8192, 16384 are accepted. Default = 2048. */ + FMOD_DSP_FFT_WINDOWTYPE, /* (Type:int) - [r/w] Refer to FMOD_DSP_FFT_WINDOW enumeration. Default = FMOD_DSP_FFT_WINDOW_HAMMING. */ + FMOD_DSP_FFT_SPECTRUMDATA, /* (Type:data) - [r] Returns the current spectrum values between 0 and 1 for each 'fft bin'. Cast data to FMOD_DSP_PARAMETER_DATA_TYPE_FFT. Divide the niquist rate by the window size to get the hz value per entry. */ + FMOD_DSP_FFT_DOMINANT_FREQ /* (Type:float) - [r] Returns the dominant frequencies for each channel. */ +} FMOD_DSP_FFT; + + +/* +[ENUM] +[ + [DESCRIPTION] + Parameter types for the FMOD_DSP_TYPE_ENVELOPEFOLLOWER unit. + This is a simple envelope follower for tracking the signal level.
    + + [REMARKS] + This unit does not affect the incoming signal +
    + + [SEE_ALSO] + DSP::setParameterFloat + DSP::getParameterFloat + DSP::setParameterData + DSP::getParameterData + FMOD_DSP_TYPE +] +*/ +typedef enum +{ + FMOD_DSP_ENVELOPEFOLLOWER_ATTACK, /* (Type:float) [r/w] - Attack time (milliseconds), in the range from 0.1 through 1000. Default = 20. */ + FMOD_DSP_ENVELOPEFOLLOWER_RELEASE, /* (Type:float) [r/w] - Release time (milliseconds), in the range from 10 through 5000. Default = 100 */ + FMOD_DSP_ENVELOPEFOLLOWER_ENVELOPE, /* (Type:float) [r] - Current value of the envelope, in the range 0 to 1. Read-only. */ + FMOD_DSP_ENVELOPEFOLLOWER_USESIDECHAIN /* (Type:data) [r/w] - Data of type FMOD_DSP_PARAMETER_SIDECHAIN. Whether to analyse the sidechain signal instead of the input signal. Default is { false } */ +} FMOD_DSP_ENVELOPEFOLLOWER; + +/* +[ENUM] +[ + [DESCRIPTION] + Parameter types for the FMOD_DSP_TYPE_CONVOLUTIONREVERB filter. + + [REMARKS] + Convolution Reverb reverb IR.
    + + [SEE_ALSO] + DSP::setParameterFloat + DSP::getParameterFloat + DSP::setParameterData + DSP::getParameterData + FMOD_DSP_TYPE +] +*/ +typedef enum +{ + FMOD_DSP_CONVOLUTION_REVERB_PARAM_IR, /* (Type:data) - [w] 16-bit reverb IR (short*) with an extra sample prepended to the start which specifies the number of channels. */ + FMOD_DSP_CONVOLUTION_REVERB_PARAM_WET, /* (Type:float) - [r/w] Volume of echo signal to pass to output in dB. -80.0 to 10.0. Default = 0. */ + FMOD_DSP_CONVOLUTION_REVERB_PARAM_DRY /* (Type:float) - [r/w] Original sound volume in dB. -80.0 to 10.0. Default = 0. */ +} FMOD_DSP_CONVOLUTION_REVERB; + +/* +[ENUM] +[ + [DESCRIPTION] + Parameter types for the FMOD_DSP_CHANNELMIX_OUTPUTGROUPING parameter for FMOD_DSP_TYPE_CHANNELMIX effect. + + [REMARKS] + + [SEE_ALSO] + DSP::setParameterInt + DSP::getParameterInt + FMOD_DSP_TYPE +] +*/ +typedef enum +{ + FMOD_DSP_CHANNELMIX_OUTPUT_DEFAULT, /* Output channel count = input channel count. Mapping: See FMOD_SPEAKER enumeration. */ + FMOD_DSP_CHANNELMIX_OUTPUT_ALLMONO, /* Output channel count = 1. Mapping: Mono, Mono, Mono, Mono, Mono, Mono, ... (each channel all the way up to FMOD_MAX_CHANNEL_WIDTH channels are treated as if they were mono) */ + FMOD_DSP_CHANNELMIX_OUTPUT_ALLSTEREO, /* Output channel count = 2. Mapping: Left, Right, Left, Right, Left, Right, ... (each pair of channels is treated as stereo all the way up to FMOD_MAX_CHANNEL_WIDTH channels) */ + FMOD_DSP_CHANNELMIX_OUTPUT_ALLQUAD, /* Output channel count = 4. Mapping: Repeating pattern of Front Left, Front Right, Surround Left, Surround Right. */ + FMOD_DSP_CHANNELMIX_OUTPUT_ALL5POINT1, /* Output channel count = 6. Mapping: Repeating pattern of Front Left, Front Right, Center, LFE, Surround Left, Surround Right. */ + FMOD_DSP_CHANNELMIX_OUTPUT_ALL7POINT1, /* Output channel count = 8. Mapping: Repeating pattern of Front Left, Front Right, Center, LFE, Surround Left, Surround Right, Back Left, Back Right. */ + FMOD_DSP_CHANNELMIX_OUTPUT_ALLLFE /* Output channel count = 6. Mapping: Repeating pattern of LFE in a 5.1 output signal. */ +} FMOD_DSP_CHANNELMIX_OUTPUT; + +#define FMOD_DSP_CHANNELGAIN_OUTPUT_DEFAULT FMOD_DSP_CHANNELMIX_OUTPUT_DEFAULT // Deprecated. Please use FMOD_DSP_CHANNELMIX_OUTPUT_DEFAULT. +#define FMOD_DSP_CHANNELGAIN_OUTPUT_ALLMONO FMOD_DSP_CHANNELMIX_OUTPUT_ALLMONO // Deprecated. Please use FMOD_DSP_CHANNELMIX_OUTPUT_ALLMONO. +#define FMOD_DSP_CHANNELGAIN_OUTPUT_ALLSTEREO FMOD_DSP_CHANNELMIX_OUTPUT_ALLSTEREO // Deprecated. Please use FMOD_DSP_CHANNELMIX_OUTPUT_ALLSTEREO. +#define FMOD_DSP_CHANNELGAIN_OUTPUT_ALLQUAD FMOD_DSP_CHANNELMIX_OUTPUT_ALLQUAD // Deprecated. Please use FMOD_DSP_CHANNELMIX_OUTPUT_ALLQUAD. +#define FMOD_DSP_CHANNELGAIN_OUTPUT_ALL5POINT1 FMOD_DSP_CHANNELMIX_OUTPUT_ALL5POINT1 // Deprecated. Please use FMOD_DSP_CHANNELMIX_OUTPUT_ALL5POINT1. +#define FMOD_DSP_CHANNELGAIN_OUTPUT_ALL7POINT1 FMOD_DSP_CHANNELMIX_OUTPUT_ALL7POINT1 // Deprecated. Please use FMOD_DSP_CHANNELMIX_OUTPUT_ALL7POINT1. +#define FMOD_DSP_CHANNELGAIN_OUTPUT_ALLLFE FMOD_DSP_CHANNELMIX_OUTPUT_ALLLFE // Deprecated. Please use FMOD_DSP_CHANNELMIX_OUTPUT_ALLLFE. + +/* +[ENUM] +[ + [DESCRIPTION] + Parameter types for the FMOD_DSP_TYPE_CHANNELMIX filter. + + [REMARKS] + For FMOD_DSP_CHANNELMIX_OUTPUTGROUPING, this value will set the output speaker format for the DSP, and also map the incoming channels to the + outgoing channels in a round-robin fashion. Use this for example play a 32 channel input signal as if it were a repeating group of output signals. + Ie. + FMOD_DSP_CHANNELMIX_OUTPUT_ALLMONO = all incoming channels are mixed to a mono output. + FMOD_DSP_CHANNELMIX_OUTPUT_ALLSTEREO = all incoming channels are mixed to a stereo output, ie even incoming channels 0,2,4,6,etc are mixed to left, and odd incoming channels 1,3,5,7,etc are mixed to right. + FMOD_DSP_CHANNELMIX_OUTPUT_ALL5POINT1 = all incoming channels are mixed to a 5.1 output. If there are less than 6 coming in, it will just fill the first n channels in the 6 output channels. + If there are more, then it will repeat the input pattern to the output like it did with the stereo case, ie 12 incoming channels are mapped as 0-5 mixed to the + 5.1 output and 6 to 11 mapped to the 5.1 output. + FMOD_DSP_CHANNELMIX_OUTPUT_ALLLFE = all incoming channels are mixed to a 5.1 output but via the LFE channel only. + + [SEE_ALSO] + DSP::setParameterInt + DSP::getParameterInt + DSP::setParameterFloat + DSP::getParameterFloat + FMOD_DSP_TYPE +] +*/ +typedef enum +{ + FMOD_DSP_CHANNELMIX_OUTPUTGROUPING, /* (Type:int) - Refer to FMOD_DSP_CHANNELMIX_OUTPUT enumeration. Default = FMOD_DSP_CHANNELMIX_OUTPUT_DEFAULT. See remarks. */ + FMOD_DSP_CHANNELMIX_GAIN_CH0, /* (Type:float) - Channel #0 gain in dB. -80.0 to 10.0. Default = 0. */ + FMOD_DSP_CHANNELMIX_GAIN_CH1, /* (Type:float) - Channel #1 gain in dB. -80.0 to 10.0. Default = 0. */ + FMOD_DSP_CHANNELMIX_GAIN_CH2, /* (Type:float) - Channel #2 gain in dB. -80.0 to 10.0. Default = 0. */ + FMOD_DSP_CHANNELMIX_GAIN_CH3, /* (Type:float) - Channel #3 gain in dB. -80.0 to 10.0. Default = 0. */ + FMOD_DSP_CHANNELMIX_GAIN_CH4, /* (Type:float) - Channel #4 gain in dB. -80.0 to 10.0. Default = 0. */ + FMOD_DSP_CHANNELMIX_GAIN_CH5, /* (Type:float) - Channel #5 gain in dB. -80.0 to 10.0. Default = 0. */ + FMOD_DSP_CHANNELMIX_GAIN_CH6, /* (Type:float) - Channel #6 gain in dB. -80.0 to 10.0. Default = 0. */ + FMOD_DSP_CHANNELMIX_GAIN_CH7, /* (Type:float) - Channel #7 gain in dB. -80.0 to 10.0. Default = 0. */ + FMOD_DSP_CHANNELMIX_GAIN_CH8, /* (Type:float) - Channel #8 gain in dB. -80.0 to 10.0. Default = 0. */ + FMOD_DSP_CHANNELMIX_GAIN_CH9, /* (Type:float) - Channel #9 gain in dB. -80.0 to 10.0. Default = 0. */ + FMOD_DSP_CHANNELMIX_GAIN_CH10, /* (Type:float) - Channel #10 gain in dB. -80.0 to 10.0. Default = 0. */ + FMOD_DSP_CHANNELMIX_GAIN_CH11, /* (Type:float) - Channel #11 gain in dB. -80.0 to 10.0. Default = 0. */ + FMOD_DSP_CHANNELMIX_GAIN_CH12, /* (Type:float) - Channel #12 gain in dB. -80.0 to 10.0. Default = 0. */ + FMOD_DSP_CHANNELMIX_GAIN_CH13, /* (Type:float) - Channel #13 gain in dB. -80.0 to 10.0. Default = 0. */ + FMOD_DSP_CHANNELMIX_GAIN_CH14, /* (Type:float) - Channel #14 gain in dB. -80.0 to 10.0. Default = 0. */ + FMOD_DSP_CHANNELMIX_GAIN_CH15, /* (Type:float) - Channel #15 gain in dB. -80.0 to 10.0. Default = 0. */ + FMOD_DSP_CHANNELMIX_GAIN_CH16, /* (Type:float) - Channel #16 gain in dB. -80.0 to 10.0. Default = 0. */ + FMOD_DSP_CHANNELMIX_GAIN_CH17, /* (Type:float) - Channel #17 gain in dB. -80.0 to 10.0. Default = 0. */ + FMOD_DSP_CHANNELMIX_GAIN_CH18, /* (Type:float) - Channel #18 gain in dB. -80.0 to 10.0. Default = 0. */ + FMOD_DSP_CHANNELMIX_GAIN_CH19, /* (Type:float) - Channel #19 gain in dB. -80.0 to 10.0. Default = 0. */ + FMOD_DSP_CHANNELMIX_GAIN_CH20, /* (Type:float) - Channel #20 gain in dB. -80.0 to 10.0. Default = 0. */ + FMOD_DSP_CHANNELMIX_GAIN_CH21, /* (Type:float) - Channel #21 gain in dB. -80.0 to 10.0. Default = 0. */ + FMOD_DSP_CHANNELMIX_GAIN_CH22, /* (Type:float) - Channel #22 gain in dB. -80.0 to 10.0. Default = 0. */ + FMOD_DSP_CHANNELMIX_GAIN_CH23, /* (Type:float) - Channel #23 gain in dB. -80.0 to 10.0. Default = 0. */ + FMOD_DSP_CHANNELMIX_GAIN_CH24, /* (Type:float) - Channel #24 gain in dB. -80.0 to 10.0. Default = 0. */ + FMOD_DSP_CHANNELMIX_GAIN_CH25, /* (Type:float) - Channel #25 gain in dB. -80.0 to 10.0. Default = 0. */ + FMOD_DSP_CHANNELMIX_GAIN_CH26, /* (Type:float) - Channel #26 gain in dB. -80.0 to 10.0. Default = 0. */ + FMOD_DSP_CHANNELMIX_GAIN_CH27, /* (Type:float) - Channel #27 gain in dB. -80.0 to 10.0. Default = 0. */ + FMOD_DSP_CHANNELMIX_GAIN_CH28, /* (Type:float) - Channel #28 gain in dB. -80.0 to 10.0. Default = 0. */ + FMOD_DSP_CHANNELMIX_GAIN_CH29, /* (Type:float) - Channel #29 gain in dB. -80.0 to 10.0. Default = 0. */ + FMOD_DSP_CHANNELMIX_GAIN_CH30, /* (Type:float) - Channel #30 gain in dB. -80.0 to 10.0. Default = 0. */ + FMOD_DSP_CHANNELMIX_GAIN_CH31 /* (Type:float) - Channel #31 gain in dB. -80.0 to 10.0. Default = 0. */ +} FMOD_DSP_CHANNELMIX; + +/* +[ENUM] +[ + [DESCRIPTION] + Parameter types for the FMOD_DSP_TRANSCEIVER_SPEAKERMODE parameter for FMOD_DSP_TYPE_TRANSCEIVER effect. + + [REMARKS] + The speaker mode of a transceiver buffer (of which there are up to 32 of) is determined automatically depending on the signal flowing through the transceiver effect, or it can be forced. + Use a smaller fixed speaker mode buffer to save memory. + + Only relevant for transmitter dsps, as they control the format of the transceiver channel's buffer. + + If multiple transceivers transmit to a single buffer in different speaker modes, it will allocate memory for each speaker mode. This uses more memory than a single speaker mode. + If there are multiple receivers reading from a channel with multiple speaker modes, it will read them all and mix them together. + + If the system's speaker mode is stereo or mono, it will not create a 3rd buffer, it will just use the mono/stereo speaker mode buffer. + + [SEE_ALSO] + DSP::setParameterInt + DSP::getParameterInt + FMOD_DSP_TYPE +] +*/ +typedef enum +{ + FMOD_DSP_TRANSCEIVER_SPEAKERMODE_AUTO = -1, /* A transmitter will use whatever signal channel count coming in to the transmitter, to determine which speaker mode is allocated for the transceiver channel. */ + FMOD_DSP_TRANSCEIVER_SPEAKERMODE_MONO = 0, /* A transmitter will always downmix to a mono channel buffer. */ + FMOD_DSP_TRANSCEIVER_SPEAKERMODE_STEREO, /* A transmitter will always upmix or downmix to a stereo channel buffer. */ + FMOD_DSP_TRANSCEIVER_SPEAKERMODE_SURROUND, /* A transmitter will always upmix or downmix to a surround channel buffer. Surround is the speaker mode of the system above stereo, so could be quad/surround/5.1/7.1. */ +} FMOD_DSP_TRANSCEIVER_SPEAKERMODE; + + +/* +[ENUM] +[ + [DESCRIPTION] + Parameter types for the FMOD_DSP_TYPE_TRANSCEIVER filter. + + [REMARKS] + The transceiver only transmits and receives to a global array of 32 channels. The transceiver can be set to receiver mode (like a return) and can receive the signal at a variable gain (FMOD_DSP_TRANSCEIVER_GAIN). + The transceiver can also be set to transmit to a chnnel (like a send) and can transmit the signal with a variable gain (FMOD_DSP_TRANSCEIVER_GAIN). + + The FMOD_DSP_TRANSCEIVER_TRANSMITSPEAKERMODE is only applicable to the transmission format, not the receive format. This means this parameter is ignored in 'receive mode'. This allows receivers to receive at + the speaker mode of the user's choice. Receiving from a mono channel, is cheaper than receiving from a surround channel for example. + The 3 speaker modes FMOD_DSP_TRANSCEIVER_SPEAKERMODE_MONO, FMOD_DSP_TRANSCEIVER_SPEAKERMODE_STEREO, FMOD_DSP_TRANSCEIVER_SPEAKERMODE_SURROUND are stored as seperate buffers in memory for a tranmitter channel. + To save memory, use 1 common speaker mode for a transmitter. + + The transceiver is double buffered to avoid desyncing of transmitters and receivers. This means there will be a 1 block delay on a receiver, compared to the data sent from a transmitter. + + Multiple transmitters sending to the same channel will be mixed together. + + [SEE_ALSO] + DSP::setParameterFloat + DSP::getParameterFloat + DSP::setParameterInt + DSP::getParameterInt + DSP::setParameterBool + DSP::getParameterBool + FMOD_DSP_TYPE +] +*/ +typedef enum +{ + FMOD_DSP_TRANSCEIVER_TRANSMIT, /* (Type:bool) - [r/w] - FALSE = Transceiver is a 'receiver' (like a return) and accepts data from a channel. TRUE = Transceiver is a 'transmitter' (like a send). Default = FALSE. */ + FMOD_DSP_TRANSCEIVER_GAIN, /* (Type:float) - [r/w] - Gain to receive or transmit at in dB. -80.0 to 10.0. Default = 0. */ + FMOD_DSP_TRANSCEIVER_CHANNEL, /* (Type:int) - [r/w] - Integer to select current global slot, shared by all Transceivers, that can be transmitted to or received from. 0 to 31. Default = 0.*/ + FMOD_DSP_TRANSCEIVER_TRANSMITSPEAKERMODE /* (Type:int) - [r/w] - Speaker mode (transmitter mode only). Specifies either 0 (Auto) Default = 0.*/ +} FMOD_DSP_TRANSCEIVER; + + +#endif + diff --git a/Plugins/FMODStudio/Source/FMODStudio/Public/FMOD/fmod_errors.h b/Plugins/FMODStudio/Source/FMODStudio/Public/FMOD/fmod_errors.h new file mode 100644 index 0000000..2640e3c --- /dev/null +++ b/Plugins/FMODStudio/Source/FMODStudio/Public/FMOD/fmod_errors.h @@ -0,0 +1,113 @@ +/*$ preserve start $*/ + +/* ================================================================================================== */ +/* FMOD Studio - Error string header file. Copyright (c), Firelight Technologies Pty, Ltd. 2004-2016. */ +/* */ +/* Use this header if you want to store or display a string version / english explanation of */ +/* the FMOD error codes. */ +/* */ +/* ================================================================================================== */ + +#ifndef _FMOD_ERRORS_H +#define _FMOD_ERRORS_H + +#include "fmod.h" + +#ifdef __GNUC__ +static const char *FMOD_ErrorString(FMOD_RESULT errcode) __attribute__((unused)); +#endif + +static const char *FMOD_ErrorString(FMOD_RESULT errcode) +{ + switch (errcode) + { +/*$ preserve end $*/ + case FMOD_OK: return "No errors."; + case FMOD_ERR_BADCOMMAND: return "Tried to call a function on a data type that does not allow this type of functionality (ie calling Sound::lock on a streaming sound)."; + case FMOD_ERR_CHANNEL_ALLOC: return "Error trying to allocate a channel."; + case FMOD_ERR_CHANNEL_STOLEN: return "The specified channel has been reused to play another sound."; + case FMOD_ERR_DMA: return "DMA Failure. See debug output for more information."; + case FMOD_ERR_DSP_CONNECTION: return "DSP connection error. Connection possibly caused a cyclic dependency or connected dsps with incompatible buffer counts."; + case FMOD_ERR_DSP_DONTPROCESS: return "DSP return code from a DSP process query callback. Tells mixer not to call the process callback and therefore not consume CPU. Use this to optimize the DSP graph."; + case FMOD_ERR_DSP_FORMAT: return "DSP Format error. A DSP unit may have attempted to connect to this network with the wrong format, or a matrix may have been set with the wrong size if the target unit has a specified channel map."; + case FMOD_ERR_DSP_INUSE: return "DSP is already in the mixer's DSP network. It must be removed before being reinserted or released."; + case FMOD_ERR_DSP_NOTFOUND: return "DSP connection error. Couldn't find the DSP unit specified."; + case FMOD_ERR_DSP_RESERVED: return "DSP operation error. Cannot perform operation on this DSP as it is reserved by the system."; + case FMOD_ERR_DSP_SILENCE: return "DSP return code from a DSP process query callback. Tells mixer silence would be produced from read, so go idle and not consume CPU. Use this to optimize the DSP graph."; + case FMOD_ERR_DSP_TYPE: return "DSP operation cannot be performed on a DSP of this type."; + case FMOD_ERR_FILE_BAD: return "Error loading file."; + case FMOD_ERR_FILE_COULDNOTSEEK: return "Couldn't perform seek operation. This is a limitation of the medium (ie netstreams) or the file format."; + case FMOD_ERR_FILE_DISKEJECTED: return "Media was ejected while reading."; + case FMOD_ERR_FILE_EOF: return "End of file unexpectedly reached while trying to read essential data (truncated?)."; + case FMOD_ERR_FILE_ENDOFDATA: return "End of current chunk reached while trying to read data."; + case FMOD_ERR_FILE_NOTFOUND: return "File not found."; + case FMOD_ERR_FORMAT: return "Unsupported file or audio format."; + case FMOD_ERR_HEADER_MISMATCH: return "There is a version mismatch between the FMOD header and either the FMOD Studio library or the FMOD Low Level library."; + case FMOD_ERR_HTTP: return "A HTTP error occurred. This is a catch-all for HTTP errors not listed elsewhere."; + case FMOD_ERR_HTTP_ACCESS: return "The specified resource requires authentication or is forbidden."; + case FMOD_ERR_HTTP_PROXY_AUTH: return "Proxy authentication is required to access the specified resource."; + case FMOD_ERR_HTTP_SERVER_ERROR: return "A HTTP server error occurred."; + case FMOD_ERR_HTTP_TIMEOUT: return "The HTTP request timed out."; + case FMOD_ERR_INITIALIZATION: return "FMOD was not initialized correctly to support this function."; + case FMOD_ERR_INITIALIZED: return "Cannot call this command after System::init."; + case FMOD_ERR_INTERNAL: return "An error occurred that wasn't supposed to. Contact support."; + case FMOD_ERR_INVALID_FLOAT: return "Value passed in was a NaN, Inf or denormalized float."; + case FMOD_ERR_INVALID_HANDLE: return "An invalid object handle was used."; + case FMOD_ERR_INVALID_PARAM: return "An invalid parameter was passed to this function."; + case FMOD_ERR_INVALID_POSITION: return "An invalid seek position was passed to this function."; + case FMOD_ERR_INVALID_SPEAKER: return "An invalid speaker was passed to this function based on the current speaker mode."; + case FMOD_ERR_INVALID_SYNCPOINT: return "The syncpoint did not come from this sound handle."; + case FMOD_ERR_INVALID_THREAD: return "Tried to call a function on a thread that is not supported."; + case FMOD_ERR_INVALID_VECTOR: return "The vectors passed in are not unit length, or perpendicular."; + case FMOD_ERR_MAXAUDIBLE: return "Reached maximum audible playback count for this sound's soundgroup."; + case FMOD_ERR_MEMORY: return "Not enough memory or resources."; + case FMOD_ERR_MEMORY_CANTPOINT: return "Can't use FMOD_OPENMEMORY_POINT on non PCM source data, or non mp3/xma/adpcm data if FMOD_CREATECOMPRESSEDSAMPLE was used."; + case FMOD_ERR_NEEDS3D: return "Tried to call a command on a 2d sound when the command was meant for 3d sound."; + case FMOD_ERR_NEEDSHARDWARE: return "Tried to use a feature that requires hardware support."; + case FMOD_ERR_NET_CONNECT: return "Couldn't connect to the specified host."; + case FMOD_ERR_NET_SOCKET_ERROR: return "A socket error occurred. This is a catch-all for socket-related errors not listed elsewhere."; + case FMOD_ERR_NET_URL: return "The specified URL couldn't be resolved."; + case FMOD_ERR_NET_WOULD_BLOCK: return "Operation on a non-blocking socket could not complete immediately."; + case FMOD_ERR_NOTREADY: return "Operation could not be performed because specified sound/DSP connection is not ready."; + case FMOD_ERR_OUTPUT_ALLOCATED: return "Error initializing output device, but more specifically, the output device is already in use and cannot be reused."; + case FMOD_ERR_OUTPUT_CREATEBUFFER: return "Error creating hardware sound buffer."; + case FMOD_ERR_OUTPUT_DRIVERCALL: return "A call to a standard soundcard driver failed, which could possibly mean a bug in the driver or resources were missing or exhausted."; + case FMOD_ERR_OUTPUT_FORMAT: return "Soundcard does not support the specified format."; + case FMOD_ERR_OUTPUT_INIT: return "Error initializing output device."; + case FMOD_ERR_OUTPUT_NODRIVERS: return "The output device has no drivers installed. If pre-init, FMOD_OUTPUT_NOSOUND is selected as the output mode. If post-init, the function just fails."; + case FMOD_ERR_PLUGIN: return "An unspecified error has been returned from a plugin."; + case FMOD_ERR_PLUGIN_MISSING: return "A requested output, dsp unit type or codec was not available."; + case FMOD_ERR_PLUGIN_RESOURCE: return "A resource that the plugin requires cannot be found. (ie the DLS file for MIDI playback)"; + case FMOD_ERR_PLUGIN_VERSION: return "A plugin was built with an unsupported SDK version."; + case FMOD_ERR_RECORD: return "An error occurred trying to initialize the recording device."; + case FMOD_ERR_REVERB_CHANNELGROUP: return "Reverb properties cannot be set on this channel because a parent channelgroup owns the reverb connection."; + case FMOD_ERR_REVERB_INSTANCE: return "Specified instance in FMOD_REVERB_PROPERTIES couldn't be set. Most likely because it is an invalid instance number or the reverb doesn't exist."; + case FMOD_ERR_SUBSOUNDS: return "The error occurred because the sound referenced contains subsounds when it shouldn't have, or it doesn't contain subsounds when it should have. The operation may also not be able to be performed on a parent sound."; + case FMOD_ERR_SUBSOUND_ALLOCATED: return "This subsound is already being used by another sound, you cannot have more than one parent to a sound. Null out the other parent's entry first."; + case FMOD_ERR_SUBSOUND_CANTMOVE: return "Shared subsounds cannot be replaced or moved from their parent stream, such as when the parent stream is an FSB file."; + case FMOD_ERR_TAGNOTFOUND: return "The specified tag could not be found or there are no tags."; + case FMOD_ERR_TOOMANYCHANNELS: return "The sound created exceeds the allowable input channel count. This can be increased using the 'maxinputchannels' parameter in System::setSoftwareFormat."; + case FMOD_ERR_TRUNCATED: return "The retrieved string is too long to fit in the supplied buffer and has been truncated."; + case FMOD_ERR_UNIMPLEMENTED: return "Something in FMOD hasn't been implemented when it should be! contact support!"; + case FMOD_ERR_UNINITIALIZED: return "This command failed because System::init or System::setDriver was not called."; + case FMOD_ERR_UNSUPPORTED: return "A command issued was not supported by this object. Possibly a plugin without certain callbacks specified."; + case FMOD_ERR_VERSION: return "The version number of this file format is not supported."; + case FMOD_ERR_EVENT_ALREADY_LOADED: return "The specified bank has already been loaded."; + case FMOD_ERR_EVENT_LIVEUPDATE_BUSY: return "The live update connection failed due to the game already being connected."; + case FMOD_ERR_EVENT_LIVEUPDATE_MISMATCH: return "The live update connection failed due to the game data being out of sync with the tool."; + case FMOD_ERR_EVENT_LIVEUPDATE_TIMEOUT: return "The live update connection timed out."; + case FMOD_ERR_EVENT_NOTFOUND: return "The requested event, bus or vca could not be found."; + case FMOD_ERR_STUDIO_UNINITIALIZED: return "The Studio::System object is not yet initialized."; + case FMOD_ERR_STUDIO_NOT_LOADED: return "The specified resource is not loaded, so it can't be unloaded."; + case FMOD_ERR_INVALID_STRING: return "An invalid string was passed to this function."; + case FMOD_ERR_ALREADY_LOCKED: return "The specified resource is already locked."; + case FMOD_ERR_NOT_LOCKED: return "The specified resource is not locked, so it can't be unlocked."; + case FMOD_ERR_RECORD_DISCONNECTED: return "The specified recording driver has been disconnected."; + case FMOD_ERR_TOOMANYSAMPLES: return "The length provided exceed the allowable limit."; + default : return "Unknown error."; +/*$ preserve start $*/ + }; +} + +#endif +/*$ preserve end $*/ diff --git a/Plugins/FMODStudio/Source/FMODStudio/Public/FMOD/fmod_output.h b/Plugins/FMODStudio/Source/FMODStudio/Public/FMOD/fmod_output.h new file mode 100644 index 0000000..da80d3a --- /dev/null +++ b/Plugins/FMODStudio/Source/FMODStudio/Public/FMOD/fmod_output.h @@ -0,0 +1,88 @@ +/* ======================================================================================================== */ +/* FMOD Studio - output development header file. Copyright (c), Firelight Technologies Pty, Ltd. 2004-2016. */ +/* */ +/* Use this header if you are wanting to develop your own output plugin to use with */ +/* FMOD's output system. With this header you can make your own output plugin that FMOD */ +/* can register and use. See the documentation and examples on how to make a working plugin. */ +/* */ +/* ======================================================================================================== */ + +#ifndef _FMOD_OUTPUT_H +#define _FMOD_OUTPUT_H + +typedef struct FMOD_OUTPUT_STATE FMOD_OUTPUT_STATE; + +/* + Output callbacks +*/ +typedef FMOD_RESULT (F_CALLBACK *FMOD_OUTPUT_GETNUMDRIVERS_CALLBACK) (FMOD_OUTPUT_STATE *output_state, int *numdrivers); +typedef FMOD_RESULT (F_CALLBACK *FMOD_OUTPUT_GETDRIVERINFO_CALLBACK) (FMOD_OUTPUT_STATE *output, int id, char *name, int namelen, FMOD_GUID *guid, int *systemrate, FMOD_SPEAKERMODE *speakermode, int *speakermodechannels); +typedef FMOD_RESULT (F_CALLBACK *FMOD_OUTPUT_INIT_CALLBACK) (FMOD_OUTPUT_STATE *output_state, int selecteddriver, FMOD_INITFLAGS flags, int *outputrate, FMOD_SPEAKERMODE *speakermode, int *speakermodechannels, FMOD_SOUND_FORMAT *outputformat, int dspbufferlength, int dspnumbuffers, void *extradriverdata); +typedef FMOD_RESULT (F_CALLBACK *FMOD_OUTPUT_START_CALLBACK) (FMOD_OUTPUT_STATE *output_state); +typedef FMOD_RESULT (F_CALLBACK *FMOD_OUTPUT_STOP_CALLBACK) (FMOD_OUTPUT_STATE *output_state); +typedef FMOD_RESULT (F_CALLBACK *FMOD_OUTPUT_CLOSE_CALLBACK) (FMOD_OUTPUT_STATE *output_state); +typedef FMOD_RESULT (F_CALLBACK *FMOD_OUTPUT_UPDATE_CALLBACK) (FMOD_OUTPUT_STATE *output_state); +typedef FMOD_RESULT (F_CALLBACK *FMOD_OUTPUT_GETHANDLE_CALLBACK) (FMOD_OUTPUT_STATE *output_state, void **handle); +typedef FMOD_RESULT (F_CALLBACK *FMOD_OUTPUT_GETPOSITION_CALLBACK) (FMOD_OUTPUT_STATE *output_state, unsigned int *pcm); +typedef FMOD_RESULT (F_CALLBACK *FMOD_OUTPUT_LOCK_CALLBACK) (FMOD_OUTPUT_STATE *output_state, unsigned int offset, unsigned int length, void **ptr1, void **ptr2, unsigned int *len1, unsigned int *len2); +typedef FMOD_RESULT (F_CALLBACK *FMOD_OUTPUT_UNLOCK_CALLBACK) (FMOD_OUTPUT_STATE *output_state, void *ptr1, void *ptr2, unsigned int len1, unsigned int len2); + +typedef FMOD_RESULT (F_CALLBACK *FMOD_OUTPUT_READFROMMIXER) (FMOD_OUTPUT_STATE *output_state, void *buffer, unsigned int length); /* This one is called by plugin through FMOD_OUTPUT_STATE, not set by user as a callback. */ + + +/* +[STRUCTURE] +[ + [DESCRIPTION] + When creating an output, declare one of these and provide the relevant callbacks and name for FMOD to use when it opens and reads a file of this type. + + [REMARKS] + Members marked with [in] mean the variable can be written to. The user can set the value.
    + Members marked with [out] mean the variable is modified by FMOD and is for reading purposes only. Do not change this value.
    + + [SEE_ALSO] + FMOD_OUTPUT_STATE +] +*/ +typedef struct FMOD_OUTPUT_DESCRIPTION +{ + const char *name; /* [in] Name of the output. */ + unsigned int version; /* [in] Plugin writer's version number. */ + int polling; /* [in] If TRUE (non zero), this tells FMOD to start a thread and call getposition / lock / unlock for feeding data. If 0, the output is probably callback based, so all the plugin needs to do is call readfrommixer to the appropriate pointer. */ + FMOD_OUTPUT_GETNUMDRIVERS_CALLBACK getnumdrivers; /* [in] For sound device enumeration. This callback is to give System::getNumDrivers somthing to return. */ + FMOD_OUTPUT_GETDRIVERINFO_CALLBACK getdriverinfo; /* [in] For sound device enumeration. This callback is to give System::getDriverName somthing to return. */ + FMOD_OUTPUT_INIT_CALLBACK init; /* [in] Initialization function for the output device. This is called from System::init. */ + FMOD_OUTPUT_START_CALLBACK start; /* [in] Initialization function for the output device to start accepting audio data from the FMOD software mixer. This is called from System::init. */ + FMOD_OUTPUT_STOP_CALLBACK stop; /* [in] Initialization function for the output device to stop accepting audio data from FMOD the software mixer. This is called from System::close. */ + FMOD_OUTPUT_CLOSE_CALLBACK close; /* [in] Cleanup / close down function for the output device. This is called from System::close. */ + FMOD_OUTPUT_UPDATE_CALLBACK update; /* [in] Update function that is called once a frame by the user. This is called from System::update. */ + FMOD_OUTPUT_GETHANDLE_CALLBACK gethandle; /* [in] This is called from System::getOutputHandle. This is just to return a pointer to the internal system device object that the system may be using.*/ + FMOD_OUTPUT_GETPOSITION_CALLBACK getposition; /* [in] This is called from the FMOD software mixer thread if 'polling' = true. This returns a position value in samples so that FMOD knows where and when to fill its buffer. */ + FMOD_OUTPUT_LOCK_CALLBACK lock; /* [in] This is called from the FMOD software mixer thread if 'polling' = true. This function provides a pointer to data that FMOD can write to when software mixing. */ + FMOD_OUTPUT_UNLOCK_CALLBACK unlock; /* [in] This is called from the FMOD software mixer thread if 'polling' = true. This optional function accepts the data that has been mixed and copies it or does whatever it needs to before sending it to the hardware. */ +} FMOD_OUTPUT_DESCRIPTION; + + +/* +[STRUCTURE] +[ + [DESCRIPTION] + Output plugin structure that is passed into each callback. + + [REMARKS] + Members marked with [in] mean the variable can be written to. The user can set the value.
    + Members marked with [out] mean the variable is modified by FMOD and is for reading purposes only. Do not change this value.
    + + [SEE_ALSO] + FMOD_OUTPUT_DESCRIPTION +] +*/ +struct FMOD_OUTPUT_STATE +{ + void *plugindata; /* [in] Plugin writer created data the output author wants to attach to this object. */ + FMOD_OUTPUT_READFROMMIXER readfrommixer; /* [out] Function to update mixer and write the result to the provided pointer. Used from callback based output only. Polling based output uses lock/unlock/getposition. */ +}; + +#endif + + diff --git a/Plugins/FMODStudio/Source/FMODStudio/Public/FMOD/fmod_studio.h b/Plugins/FMODStudio/Source/FMODStudio/Public/FMOD/fmod_studio.h new file mode 100644 index 0000000..227350a --- /dev/null +++ b/Plugins/FMODStudio/Source/FMODStudio/Public/FMOD/fmod_studio.h @@ -0,0 +1,233 @@ +/*$ preserve start $*/ + +/* + fmod_studio.h - FMOD Studio API + Copyright (c), Firelight Technologies Pty, Ltd. 2016. + + This header defines the C API. If you are programming in C++ use fmod_studio.hpp. +*/ + +#ifndef FMOD_STUDIO_H +#define FMOD_STUDIO_H + +#include "fmod_studio_common.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +/* + Global +*/ +FMOD_RESULT F_API FMOD_Studio_ParseID(const char *idString, FMOD_GUID *id); +FMOD_RESULT F_API FMOD_Studio_System_Create(FMOD_STUDIO_SYSTEM **system, unsigned int headerVersion); + +/*$ preserve end $*/ + +/* + System +*/ +FMOD_BOOL F_API FMOD_Studio_System_IsValid(FMOD_STUDIO_SYSTEM *system); +FMOD_RESULT F_API FMOD_Studio_System_SetAdvancedSettings(FMOD_STUDIO_SYSTEM *system, FMOD_STUDIO_ADVANCEDSETTINGS *settings); +FMOD_RESULT F_API FMOD_Studio_System_GetAdvancedSettings(FMOD_STUDIO_SYSTEM *system, FMOD_STUDIO_ADVANCEDSETTINGS *settings); +FMOD_RESULT F_API FMOD_Studio_System_Initialize(FMOD_STUDIO_SYSTEM *system, int maxchannels, FMOD_STUDIO_INITFLAGS studioflags, FMOD_INITFLAGS flags, void *extradriverdata); +FMOD_RESULT F_API FMOD_Studio_System_Release(FMOD_STUDIO_SYSTEM *system); +FMOD_RESULT F_API FMOD_Studio_System_Update(FMOD_STUDIO_SYSTEM *system); +FMOD_RESULT F_API FMOD_Studio_System_GetLowLevelSystem(FMOD_STUDIO_SYSTEM *system, FMOD_SYSTEM **lowLevelSystem); +FMOD_RESULT F_API FMOD_Studio_System_GetEvent(FMOD_STUDIO_SYSTEM *system, const char *pathOrID, FMOD_STUDIO_EVENTDESCRIPTION **event); +FMOD_RESULT F_API FMOD_Studio_System_GetBus(FMOD_STUDIO_SYSTEM *system, const char *pathOrID, FMOD_STUDIO_BUS **bus); +FMOD_RESULT F_API FMOD_Studio_System_GetVCA(FMOD_STUDIO_SYSTEM *system, const char *pathOrID, FMOD_STUDIO_VCA **vca); +FMOD_RESULT F_API FMOD_Studio_System_GetBank(FMOD_STUDIO_SYSTEM *system, const char *pathOrID, FMOD_STUDIO_BANK **bank); +FMOD_RESULT F_API FMOD_Studio_System_GetEventByID(FMOD_STUDIO_SYSTEM *system, const FMOD_GUID *id, FMOD_STUDIO_EVENTDESCRIPTION **event); +FMOD_RESULT F_API FMOD_Studio_System_GetBusByID(FMOD_STUDIO_SYSTEM *system, const FMOD_GUID *id, FMOD_STUDIO_BUS **bus); +FMOD_RESULT F_API FMOD_Studio_System_GetVCAByID(FMOD_STUDIO_SYSTEM *system, const FMOD_GUID *id, FMOD_STUDIO_VCA **vca); +FMOD_RESULT F_API FMOD_Studio_System_GetBankByID(FMOD_STUDIO_SYSTEM *system, const FMOD_GUID *id, FMOD_STUDIO_BANK **bank); +FMOD_RESULT F_API FMOD_Studio_System_GetSoundInfo(FMOD_STUDIO_SYSTEM *system, const char *key, FMOD_STUDIO_SOUND_INFO *info); +FMOD_RESULT F_API FMOD_Studio_System_LookupID(FMOD_STUDIO_SYSTEM *system, const char *path, FMOD_GUID *id); +FMOD_RESULT F_API FMOD_Studio_System_LookupPath(FMOD_STUDIO_SYSTEM *system, const FMOD_GUID *id, char *path, int size, int *retrieved); +FMOD_RESULT F_API FMOD_Studio_System_GetNumListeners(FMOD_STUDIO_SYSTEM *system, int *numlisteners); +FMOD_RESULT F_API FMOD_Studio_System_SetNumListeners(FMOD_STUDIO_SYSTEM *system, int numlisteners); +FMOD_RESULT F_API FMOD_Studio_System_GetListenerAttributes(FMOD_STUDIO_SYSTEM *system, int index, FMOD_3D_ATTRIBUTES *attributes); +FMOD_RESULT F_API FMOD_Studio_System_SetListenerAttributes(FMOD_STUDIO_SYSTEM *system, int index, FMOD_3D_ATTRIBUTES *attributes); +FMOD_RESULT F_API FMOD_Studio_System_LoadBankFile(FMOD_STUDIO_SYSTEM *system, const char *filename, FMOD_STUDIO_LOAD_BANK_FLAGS flags, FMOD_STUDIO_BANK **bank); +FMOD_RESULT F_API FMOD_Studio_System_LoadBankMemory(FMOD_STUDIO_SYSTEM *system, const char *buffer, int length, FMOD_STUDIO_LOAD_MEMORY_MODE mode, FMOD_STUDIO_LOAD_BANK_FLAGS flags, FMOD_STUDIO_BANK **bank); +FMOD_RESULT F_API FMOD_Studio_System_LoadBankCustom(FMOD_STUDIO_SYSTEM *system, const FMOD_STUDIO_BANK_INFO *info, FMOD_STUDIO_LOAD_BANK_FLAGS flags, FMOD_STUDIO_BANK **bank); +FMOD_RESULT F_API FMOD_Studio_System_RegisterPlugin(FMOD_STUDIO_SYSTEM *system, const FMOD_DSP_DESCRIPTION *description); +FMOD_RESULT F_API FMOD_Studio_System_UnregisterPlugin(FMOD_STUDIO_SYSTEM *system, const char *name); +FMOD_RESULT F_API FMOD_Studio_System_UnloadAll(FMOD_STUDIO_SYSTEM *system); +FMOD_RESULT F_API FMOD_Studio_System_FlushCommands(FMOD_STUDIO_SYSTEM *system); +FMOD_RESULT F_API FMOD_Studio_System_StartCommandCapture(FMOD_STUDIO_SYSTEM *system, const char *filename, FMOD_STUDIO_COMMANDCAPTURE_FLAGS flags); +FMOD_RESULT F_API FMOD_Studio_System_StopCommandCapture(FMOD_STUDIO_SYSTEM *system); +FMOD_RESULT F_API FMOD_Studio_System_LoadCommandReplay(FMOD_STUDIO_SYSTEM *system, const char *filename, FMOD_STUDIO_COMMANDREPLAY_FLAGS flags, FMOD_STUDIO_COMMANDREPLAY **replay); +FMOD_RESULT F_API FMOD_Studio_System_GetBankCount(FMOD_STUDIO_SYSTEM *system, int *count); +FMOD_RESULT F_API FMOD_Studio_System_GetBankList(FMOD_STUDIO_SYSTEM *system, FMOD_STUDIO_BANK **array, int capacity, int *count); +FMOD_RESULT F_API FMOD_Studio_System_GetCPUUsage(FMOD_STUDIO_SYSTEM *system, FMOD_STUDIO_CPU_USAGE *usage); +FMOD_RESULT F_API FMOD_Studio_System_GetBufferUsage(FMOD_STUDIO_SYSTEM *system, FMOD_STUDIO_BUFFER_USAGE *usage); +FMOD_RESULT F_API FMOD_Studio_System_ResetBufferUsage(FMOD_STUDIO_SYSTEM *system); +FMOD_RESULT F_API FMOD_Studio_System_SetCallback(FMOD_STUDIO_SYSTEM *system, FMOD_STUDIO_SYSTEM_CALLBACK callback, FMOD_STUDIO_SYSTEM_CALLBACK_TYPE callbackmask); +FMOD_RESULT F_API FMOD_Studio_System_SetUserData(FMOD_STUDIO_SYSTEM *system, void *userdata); +FMOD_RESULT F_API FMOD_Studio_System_GetUserData(FMOD_STUDIO_SYSTEM *system, void **userdata); + +/* + EventDescription +*/ +FMOD_BOOL F_API FMOD_Studio_EventDescription_IsValid(FMOD_STUDIO_EVENTDESCRIPTION *eventdescription); +FMOD_RESULT F_API FMOD_Studio_EventDescription_GetID(FMOD_STUDIO_EVENTDESCRIPTION *eventdescription, FMOD_GUID *id); +FMOD_RESULT F_API FMOD_Studio_EventDescription_GetPath(FMOD_STUDIO_EVENTDESCRIPTION *eventdescription, char *path, int size, int *retrieved); +FMOD_RESULT F_API FMOD_Studio_EventDescription_GetParameterCount(FMOD_STUDIO_EVENTDESCRIPTION *eventdescription, int *count); +FMOD_RESULT F_API FMOD_Studio_EventDescription_GetParameterByIndex(FMOD_STUDIO_EVENTDESCRIPTION *eventdescription, int index, FMOD_STUDIO_PARAMETER_DESCRIPTION *parameter); +FMOD_RESULT F_API FMOD_Studio_EventDescription_GetParameter(FMOD_STUDIO_EVENTDESCRIPTION *eventdescription, const char *name, FMOD_STUDIO_PARAMETER_DESCRIPTION *parameter); +FMOD_RESULT F_API FMOD_Studio_EventDescription_GetUserPropertyCount(FMOD_STUDIO_EVENTDESCRIPTION *eventdescription, int *count); +FMOD_RESULT F_API FMOD_Studio_EventDescription_GetUserPropertyByIndex(FMOD_STUDIO_EVENTDESCRIPTION *eventdescription, int index, FMOD_STUDIO_USER_PROPERTY *property); +FMOD_RESULT F_API FMOD_Studio_EventDescription_GetUserProperty(FMOD_STUDIO_EVENTDESCRIPTION *eventdescription, const char *name, FMOD_STUDIO_USER_PROPERTY *property); +FMOD_RESULT F_API FMOD_Studio_EventDescription_GetLength(FMOD_STUDIO_EVENTDESCRIPTION *eventdescription, int *length); +FMOD_RESULT F_API FMOD_Studio_EventDescription_GetMinimumDistance(FMOD_STUDIO_EVENTDESCRIPTION *eventdescription, float *distance); +FMOD_RESULT F_API FMOD_Studio_EventDescription_GetMaximumDistance(FMOD_STUDIO_EVENTDESCRIPTION *eventdescription, float *distance); +FMOD_RESULT F_API FMOD_Studio_EventDescription_GetSoundSize(FMOD_STUDIO_EVENTDESCRIPTION *eventdescription, float *size); +FMOD_RESULT F_API FMOD_Studio_EventDescription_IsOneshot(FMOD_STUDIO_EVENTDESCRIPTION *eventdescription, FMOD_BOOL *oneshot); +FMOD_RESULT F_API FMOD_Studio_EventDescription_IsStream(FMOD_STUDIO_EVENTDESCRIPTION *eventdescription, FMOD_BOOL *isStream); +FMOD_RESULT F_API FMOD_Studio_EventDescription_Is3D(FMOD_STUDIO_EVENTDESCRIPTION *eventdescription, FMOD_BOOL *is3D); +FMOD_RESULT F_API FMOD_Studio_EventDescription_CreateInstance(FMOD_STUDIO_EVENTDESCRIPTION *eventdescription, FMOD_STUDIO_EVENTINSTANCE **instance); +FMOD_RESULT F_API FMOD_Studio_EventDescription_GetInstanceCount(FMOD_STUDIO_EVENTDESCRIPTION *eventdescription, int *count); +FMOD_RESULT F_API FMOD_Studio_EventDescription_GetInstanceList(FMOD_STUDIO_EVENTDESCRIPTION *eventdescription, FMOD_STUDIO_EVENTINSTANCE **array, int capacity, int *count); +FMOD_RESULT F_API FMOD_Studio_EventDescription_LoadSampleData(FMOD_STUDIO_EVENTDESCRIPTION *eventDesc); +FMOD_RESULT F_API FMOD_Studio_EventDescription_UnloadSampleData(FMOD_STUDIO_EVENTDESCRIPTION *eventDesc); +FMOD_RESULT F_API FMOD_Studio_EventDescription_GetSampleLoadingState(FMOD_STUDIO_EVENTDESCRIPTION *eventDesc, FMOD_STUDIO_LOADING_STATE *state); +FMOD_RESULT F_API FMOD_Studio_EventDescription_ReleaseAllInstances(FMOD_STUDIO_EVENTDESCRIPTION *eventdescription); +FMOD_RESULT F_API FMOD_Studio_EventDescription_SetCallback(FMOD_STUDIO_EVENTDESCRIPTION *eventdescription, FMOD_STUDIO_EVENT_CALLBACK callback, FMOD_STUDIO_EVENT_CALLBACK_TYPE callbackmask); +FMOD_RESULT F_API FMOD_Studio_EventDescription_GetUserData(FMOD_STUDIO_EVENTDESCRIPTION *eventdescription, void **userData); +FMOD_RESULT F_API FMOD_Studio_EventDescription_SetUserData(FMOD_STUDIO_EVENTDESCRIPTION *eventdescription, void *userData); + +/* + EventInstance +*/ +FMOD_BOOL F_API FMOD_Studio_EventInstance_IsValid(FMOD_STUDIO_EVENTINSTANCE *eventinstance); +FMOD_RESULT F_API FMOD_Studio_EventInstance_GetDescription(FMOD_STUDIO_EVENTINSTANCE *eventinstance, FMOD_STUDIO_EVENTDESCRIPTION **description); +FMOD_RESULT F_API FMOD_Studio_EventInstance_GetVolume(FMOD_STUDIO_EVENTINSTANCE *eventinstance, float *volume); +FMOD_RESULT F_API FMOD_Studio_EventInstance_SetVolume(FMOD_STUDIO_EVENTINSTANCE *eventinstance, float volume); +FMOD_RESULT F_API FMOD_Studio_EventInstance_GetPitch(FMOD_STUDIO_EVENTINSTANCE *eventinstance, float *pitch); +FMOD_RESULT F_API FMOD_Studio_EventInstance_SetPitch(FMOD_STUDIO_EVENTINSTANCE *eventinstance, float pitch); +FMOD_RESULT F_API FMOD_Studio_EventInstance_Get3DAttributes(FMOD_STUDIO_EVENTINSTANCE *eventinstance, FMOD_3D_ATTRIBUTES *attributes); +FMOD_RESULT F_API FMOD_Studio_EventInstance_Set3DAttributes(FMOD_STUDIO_EVENTINSTANCE *eventinstance, FMOD_3D_ATTRIBUTES *attributes); +FMOD_RESULT F_API FMOD_Studio_EventInstance_GetProperty(FMOD_STUDIO_EVENTINSTANCE *eventinstance, FMOD_STUDIO_EVENT_PROPERTY index, float *value); +FMOD_RESULT F_API FMOD_Studio_EventInstance_SetProperty(FMOD_STUDIO_EVENTINSTANCE *eventinstance, FMOD_STUDIO_EVENT_PROPERTY index, float value); +FMOD_RESULT F_API FMOD_Studio_EventInstance_GetPaused(FMOD_STUDIO_EVENTINSTANCE *eventinstance, FMOD_BOOL *paused); +FMOD_RESULT F_API FMOD_Studio_EventInstance_SetPaused(FMOD_STUDIO_EVENTINSTANCE *eventinstance, FMOD_BOOL paused); +FMOD_RESULT F_API FMOD_Studio_EventInstance_Start(FMOD_STUDIO_EVENTINSTANCE *eventinstance); +FMOD_RESULT F_API FMOD_Studio_EventInstance_Stop(FMOD_STUDIO_EVENTINSTANCE *eventinstance, FMOD_STUDIO_STOP_MODE mode); +FMOD_RESULT F_API FMOD_Studio_EventInstance_GetTimelinePosition(FMOD_STUDIO_EVENTINSTANCE *eventinstance, int *position); +FMOD_RESULT F_API FMOD_Studio_EventInstance_SetTimelinePosition(FMOD_STUDIO_EVENTINSTANCE *eventinstance, int position); +FMOD_RESULT F_API FMOD_Studio_EventInstance_GetPlaybackState(FMOD_STUDIO_EVENTINSTANCE *eventinstance, FMOD_STUDIO_PLAYBACK_STATE *state); +FMOD_RESULT F_API FMOD_Studio_EventInstance_GetChannelGroup(FMOD_STUDIO_EVENTINSTANCE *eventinstance, FMOD_CHANNELGROUP **group); +FMOD_RESULT F_API FMOD_Studio_EventInstance_Release(FMOD_STUDIO_EVENTINSTANCE *eventinstance); +FMOD_RESULT F_API FMOD_Studio_EventInstance_IsVirtual(FMOD_STUDIO_EVENTINSTANCE *eventinstance, FMOD_BOOL *virtualState); +FMOD_RESULT F_API FMOD_Studio_EventInstance_GetParameter(FMOD_STUDIO_EVENTINSTANCE *eventinstance, const char *name, FMOD_STUDIO_PARAMETERINSTANCE **parameter); +FMOD_RESULT F_API FMOD_Studio_EventInstance_GetParameterByIndex(FMOD_STUDIO_EVENTINSTANCE *eventinstance, int index, FMOD_STUDIO_PARAMETERINSTANCE **parameter); +FMOD_RESULT F_API FMOD_Studio_EventInstance_GetParameterCount(FMOD_STUDIO_EVENTINSTANCE *eventinstance, int *count); +FMOD_RESULT F_API FMOD_Studio_EventInstance_SetParameterValue(FMOD_STUDIO_EVENTINSTANCE *eventinstance, const char *name, float value); +FMOD_RESULT F_API FMOD_Studio_EventInstance_SetParameterValueByIndex(FMOD_STUDIO_EVENTINSTANCE *eventinstance, int index, float value); +FMOD_RESULT F_API FMOD_Studio_EventInstance_GetCue(FMOD_STUDIO_EVENTINSTANCE *eventinstance, const char *name, FMOD_STUDIO_CUEINSTANCE **cue); +FMOD_RESULT F_API FMOD_Studio_EventInstance_GetCueByIndex(FMOD_STUDIO_EVENTINSTANCE *eventinstance, int index, FMOD_STUDIO_CUEINSTANCE **cue); +FMOD_RESULT F_API FMOD_Studio_EventInstance_GetCueCount(FMOD_STUDIO_EVENTINSTANCE *eventinstance, int *count); +FMOD_RESULT F_API FMOD_Studio_EventInstance_SetCallback(FMOD_STUDIO_EVENTINSTANCE *eventinstance, FMOD_STUDIO_EVENT_CALLBACK callback, FMOD_STUDIO_EVENT_CALLBACK_TYPE callbackmask); +FMOD_RESULT F_API FMOD_Studio_EventInstance_GetUserData(FMOD_STUDIO_EVENTINSTANCE *eventinstance, void **userData); +FMOD_RESULT F_API FMOD_Studio_EventInstance_SetUserData(FMOD_STUDIO_EVENTINSTANCE *eventinstance, void *userData); + +/* + CueInstance +*/ +FMOD_BOOL F_API FMOD_Studio_CueInstance_IsValid(FMOD_STUDIO_CUEINSTANCE *cueinstance); +FMOD_RESULT F_API FMOD_Studio_CueInstance_Trigger(FMOD_STUDIO_CUEINSTANCE *cueinstance); + +/* + ParameterInstance +*/ +FMOD_BOOL F_API FMOD_Studio_ParameterInstance_IsValid(FMOD_STUDIO_PARAMETERINSTANCE *parameterinstance); +FMOD_RESULT F_API FMOD_Studio_ParameterInstance_GetDescription(FMOD_STUDIO_PARAMETERINSTANCE *parameterinstance, FMOD_STUDIO_PARAMETER_DESCRIPTION *description); +FMOD_RESULT F_API FMOD_Studio_ParameterInstance_GetValue(FMOD_STUDIO_PARAMETERINSTANCE *parameterinstance, float *value); +FMOD_RESULT F_API FMOD_Studio_ParameterInstance_SetValue(FMOD_STUDIO_PARAMETERINSTANCE *parameterinstance, float value); + +/* + Bus +*/ +FMOD_BOOL F_API FMOD_Studio_Bus_IsValid(FMOD_STUDIO_BUS *bus); +FMOD_RESULT F_API FMOD_Studio_Bus_GetID(FMOD_STUDIO_BUS *bus, FMOD_GUID *id); +FMOD_RESULT F_API FMOD_Studio_Bus_GetPath(FMOD_STUDIO_BUS *bus, char *path, int size, int *retrieved); +FMOD_RESULT F_API FMOD_Studio_Bus_GetFaderLevel(FMOD_STUDIO_BUS *bus, float *level); +FMOD_RESULT F_API FMOD_Studio_Bus_SetFaderLevel(FMOD_STUDIO_BUS *bus, float level); +FMOD_RESULT F_API FMOD_Studio_Bus_GetPaused(FMOD_STUDIO_BUS *bus, FMOD_BOOL *paused); +FMOD_RESULT F_API FMOD_Studio_Bus_SetPaused(FMOD_STUDIO_BUS *bus, FMOD_BOOL paused); +FMOD_RESULT F_API FMOD_Studio_Bus_GetMute(FMOD_STUDIO_BUS *bus, FMOD_BOOL *mute); +FMOD_RESULT F_API FMOD_Studio_Bus_SetMute(FMOD_STUDIO_BUS *bus, FMOD_BOOL mute); +FMOD_RESULT F_API FMOD_Studio_Bus_StopAllEvents(FMOD_STUDIO_BUS *bus, FMOD_STUDIO_STOP_MODE mode); +FMOD_RESULT F_API FMOD_Studio_Bus_LockChannelGroup(FMOD_STUDIO_BUS *bus); +FMOD_RESULT F_API FMOD_Studio_Bus_UnlockChannelGroup(FMOD_STUDIO_BUS *bus); +FMOD_RESULT F_API FMOD_Studio_Bus_GetChannelGroup(FMOD_STUDIO_BUS *bus, FMOD_CHANNELGROUP **group); + +/* + VCA +*/ +FMOD_BOOL F_API FMOD_Studio_VCA_IsValid(FMOD_STUDIO_VCA *vca); +FMOD_RESULT F_API FMOD_Studio_VCA_GetID(FMOD_STUDIO_VCA *vca, FMOD_GUID *id); +FMOD_RESULT F_API FMOD_Studio_VCA_GetPath(FMOD_STUDIO_VCA *vca, char *path, int size, int *retrieved); +FMOD_RESULT F_API FMOD_Studio_VCA_GetFaderLevel(FMOD_STUDIO_VCA *vca, float *level); +FMOD_RESULT F_API FMOD_Studio_VCA_SetFaderLevel(FMOD_STUDIO_VCA *vca, float level); + +/* + Bank +*/ +FMOD_BOOL F_API FMOD_Studio_Bank_IsValid(FMOD_STUDIO_BANK *bank); +FMOD_RESULT F_API FMOD_Studio_Bank_GetID(FMOD_STUDIO_BANK *bank, FMOD_GUID *id); +FMOD_RESULT F_API FMOD_Studio_Bank_GetPath(FMOD_STUDIO_BANK *bank, char *path, int size, int *retrieved); +FMOD_RESULT F_API FMOD_Studio_Bank_Unload(FMOD_STUDIO_BANK *bank); +FMOD_RESULT F_API FMOD_Studio_Bank_LoadSampleData(FMOD_STUDIO_BANK *bank); +FMOD_RESULT F_API FMOD_Studio_Bank_UnloadSampleData(FMOD_STUDIO_BANK *bank); +FMOD_RESULT F_API FMOD_Studio_Bank_GetLoadingState(FMOD_STUDIO_BANK *bank, FMOD_STUDIO_LOADING_STATE *state); +FMOD_RESULT F_API FMOD_Studio_Bank_GetSampleLoadingState(FMOD_STUDIO_BANK *bank, FMOD_STUDIO_LOADING_STATE *state); +FMOD_RESULT F_API FMOD_Studio_Bank_GetStringCount(FMOD_STUDIO_BANK *bank, int *count); +FMOD_RESULT F_API FMOD_Studio_Bank_GetStringInfo(FMOD_STUDIO_BANK *bank, int index, FMOD_GUID *id, char *path, int size, int *retrieved); +FMOD_RESULT F_API FMOD_Studio_Bank_GetEventCount(FMOD_STUDIO_BANK *bank, int *count); +FMOD_RESULT F_API FMOD_Studio_Bank_GetEventList(FMOD_STUDIO_BANK *bank, FMOD_STUDIO_EVENTDESCRIPTION **array, int capacity, int *count); +FMOD_RESULT F_API FMOD_Studio_Bank_GetBusCount(FMOD_STUDIO_BANK *bank, int *count); +FMOD_RESULT F_API FMOD_Studio_Bank_GetBusList(FMOD_STUDIO_BANK *bank, FMOD_STUDIO_BUS **array, int capacity, int *count); +FMOD_RESULT F_API FMOD_Studio_Bank_GetVCACount(FMOD_STUDIO_BANK *bank, int *count); +FMOD_RESULT F_API FMOD_Studio_Bank_GetVCAList(FMOD_STUDIO_BANK *bank, FMOD_STUDIO_VCA **array, int capacity, int *count); +FMOD_RESULT F_API FMOD_Studio_Bank_GetUserData(FMOD_STUDIO_BANK *bank, void **userData); +FMOD_RESULT F_API FMOD_Studio_Bank_SetUserData(FMOD_STUDIO_BANK *bank, void *userData); + +/* + Command playback information +*/ +FMOD_BOOL F_API FMOD_Studio_CommandReplay_IsValid(FMOD_STUDIO_COMMANDREPLAY *replay); +FMOD_RESULT F_API FMOD_Studio_CommandReplay_GetSystem(FMOD_STUDIO_COMMANDREPLAY *replay, FMOD_STUDIO_SYSTEM **system); +FMOD_RESULT F_API FMOD_Studio_CommandReplay_GetLength(FMOD_STUDIO_COMMANDREPLAY *replay, float *length); +FMOD_RESULT F_API FMOD_Studio_CommandReplay_GetCommandCount(FMOD_STUDIO_COMMANDREPLAY *replay, int *count); +FMOD_RESULT F_API FMOD_Studio_CommandReplay_GetCommandInfo(FMOD_STUDIO_COMMANDREPLAY *replay, int commandIndex, FMOD_STUDIO_COMMAND_INFO *info); +FMOD_RESULT F_API FMOD_Studio_CommandReplay_GetCommandString(FMOD_STUDIO_COMMANDREPLAY *replay, int commandIndex, char *description, int capacity); +FMOD_RESULT F_API FMOD_Studio_CommandReplay_GetCommandAtTime(FMOD_STUDIO_COMMANDREPLAY *replay, float time, int *commandIndex); +FMOD_RESULT F_API FMOD_Studio_CommandReplay_SetBankPath(FMOD_STUDIO_COMMANDREPLAY *replay, const char *bankPath); +FMOD_RESULT F_API FMOD_Studio_CommandReplay_Start(FMOD_STUDIO_COMMANDREPLAY *replay); +FMOD_RESULT F_API FMOD_Studio_CommandReplay_Stop(FMOD_STUDIO_COMMANDREPLAY *replay); +FMOD_RESULT F_API FMOD_Studio_CommandReplay_SeekToTime(FMOD_STUDIO_COMMANDREPLAY *replay, float time); +FMOD_RESULT F_API FMOD_Studio_CommandReplay_SeekToCommand(FMOD_STUDIO_COMMANDREPLAY *replay, int commandIndex); +FMOD_RESULT F_API FMOD_Studio_CommandReplay_GetPaused(FMOD_STUDIO_COMMANDREPLAY *replay, FMOD_BOOL *paused); +FMOD_RESULT F_API FMOD_Studio_CommandReplay_SetPaused(FMOD_STUDIO_COMMANDREPLAY *replay, FMOD_BOOL paused); +FMOD_RESULT F_API FMOD_Studio_CommandReplay_GetPlaybackState(FMOD_STUDIO_COMMANDREPLAY *replay, FMOD_STUDIO_PLAYBACK_STATE *state); +FMOD_RESULT F_API FMOD_Studio_CommandReplay_GetCurrentCommand(FMOD_STUDIO_COMMANDREPLAY *replay, int *commandIndex, float *currentTime); +FMOD_RESULT F_API FMOD_Studio_CommandReplay_Release(FMOD_STUDIO_COMMANDREPLAY *replay); +FMOD_RESULT F_API FMOD_Studio_CommandReplay_SetFrameCallback(FMOD_STUDIO_COMMANDREPLAY *replay, FMOD_STUDIO_COMMANDREPLAY_FRAME_CALLBACK callback); +FMOD_RESULT F_API FMOD_Studio_CommandReplay_SetLoadBankCallback(FMOD_STUDIO_COMMANDREPLAY *replay, FMOD_STUDIO_COMMANDREPLAY_LOAD_BANK_CALLBACK callback); +FMOD_RESULT F_API FMOD_Studio_CommandReplay_SetCreateInstanceCallback(FMOD_STUDIO_COMMANDREPLAY *replay, FMOD_STUDIO_COMMANDREPLAY_CREATE_INSTANCE_CALLBACK callback); +FMOD_RESULT F_API FMOD_Studio_CommandReplay_GetUserData(FMOD_STUDIO_COMMANDREPLAY *replay, void **userdata); +FMOD_RESULT F_API FMOD_Studio_CommandReplay_SetUserData(FMOD_STUDIO_COMMANDREPLAY *replay, void *userdata); + +/*$ preserve start $*/ + +#ifdef __cplusplus +} +#endif + +#endif /* FMOD_STUDIO_H */ + +/*$ preserve end $*/ diff --git a/Plugins/FMODStudio/Source/FMODStudio/Public/FMOD/fmod_studio.hpp b/Plugins/FMODStudio/Source/FMODStudio/Public/FMOD/fmod_studio.hpp new file mode 100644 index 0000000..e529860 --- /dev/null +++ b/Plugins/FMODStudio/Source/FMODStudio/Public/FMOD/fmod_studio.hpp @@ -0,0 +1,388 @@ +/* + fmod_studio.hpp - FMOD Studio API + Copyright (c), Firelight Technologies Pty, Ltd. 2016. + + This header defines the C++ API. If you are programming in C use fmod_studio.h. +*/ + +#ifndef FMOD_STUDIO_HPP +#define FMOD_STUDIO_HPP + +#include "fmod_studio_common.h" +#include "fmod_studio.h" + +#include "fmod.hpp" + +namespace FMOD +{ + +namespace Studio +{ + typedef FMOD_GUID ID; // Deprecated. Please use FMOD_GUID type. + + class System; + class EventDescription; + class EventInstance; + class CueInstance; + class ParameterInstance; + class Bus; + class VCA; + class Bank; + class CommandReplay; + + FMOD_RESULT F_API parseID(const char *idString, FMOD_GUID *id); + + class System + { + private: + // Constructor made private so user cannot statically instance a System class. System::create must be used. + System(); + System(const System &); + + public: + static FMOD_RESULT F_API create(System **system, unsigned int headerVersion = FMOD_VERSION); + FMOD_RESULT F_API setAdvancedSettings(FMOD_STUDIO_ADVANCEDSETTINGS *settings); + FMOD_RESULT F_API getAdvancedSettings(FMOD_STUDIO_ADVANCEDSETTINGS *settings); + FMOD_RESULT F_API initialize(int maxchannels, FMOD_STUDIO_INITFLAGS studioflags, FMOD_INITFLAGS flags, void *extradriverdata); + FMOD_RESULT F_API release(); + + // Handle validity + bool F_API isValid() const; + + // Update processing + FMOD_RESULT F_API update(); + FMOD_RESULT F_API flushCommands(); + + // Low-level API access + FMOD_RESULT F_API getLowLevelSystem(FMOD::System **system) const; + + // Asset retrieval + FMOD_RESULT F_API getEvent(const char *path, EventDescription **event) const; + FMOD_RESULT F_API getBus(const char *path, Bus **bus) const; + FMOD_RESULT F_API getVCA(const char *path, VCA **vca) const; + FMOD_RESULT F_API getBank(const char *path, Bank **bank) const; + FMOD_RESULT F_API getEventByID(const FMOD_GUID *id, EventDescription **event) const; + FMOD_RESULT F_API getBusByID(const FMOD_GUID *id, Bus **bus) const; + FMOD_RESULT F_API getVCAByID(const FMOD_GUID *id, VCA **vca) const; + FMOD_RESULT F_API getBankByID(const FMOD_GUID *id, Bank **bank) const; + FMOD_RESULT F_API getSoundInfo(const char *key, FMOD_STUDIO_SOUND_INFO *info) const; + + // Path lookup + FMOD_RESULT F_API lookupID(const char *path, FMOD_GUID *id) const; + FMOD_RESULT F_API lookupPath(const FMOD_GUID *id, char *path, int size, int *retrieved) const; + + // Listener control + FMOD_RESULT F_API getNumListeners(int *numlisteners); + FMOD_RESULT F_API setNumListeners(int numlisteners); + FMOD_RESULT F_API getListenerAttributes(int listener, FMOD_3D_ATTRIBUTES *attributes) const; + FMOD_RESULT F_API setListenerAttributes(int listener, const FMOD_3D_ATTRIBUTES *attributes); + + // Bank control + FMOD_RESULT F_API loadBankFile(const char *filename, FMOD_STUDIO_LOAD_BANK_FLAGS flags, Bank **bank); + FMOD_RESULT F_API loadBankMemory(const char *buffer, int length, FMOD_STUDIO_LOAD_MEMORY_MODE mode, FMOD_STUDIO_LOAD_BANK_FLAGS flags, Bank **bank); + FMOD_RESULT F_API loadBankCustom(const FMOD_STUDIO_BANK_INFO *info, FMOD_STUDIO_LOAD_BANK_FLAGS flags, Bank **bank); + FMOD_RESULT F_API unloadAll(); + + // General functionality + FMOD_RESULT F_API getCPUUsage(FMOD_STUDIO_CPU_USAGE *usage) const; + FMOD_RESULT F_API getBufferUsage(FMOD_STUDIO_BUFFER_USAGE *usage) const; + FMOD_RESULT F_API resetBufferUsage(); + FMOD_RESULT F_API registerPlugin(const FMOD_DSP_DESCRIPTION *description); + FMOD_RESULT F_API unregisterPlugin(const char *name); + + // Enumeration + FMOD_RESULT F_API getBankCount(int *count) const; + FMOD_RESULT F_API getBankList(Bank **array, int capacity, int *count) const; + + // Command capture and replay + FMOD_RESULT F_API startCommandCapture(const char *filename, FMOD_STUDIO_COMMANDCAPTURE_FLAGS flags); + FMOD_RESULT F_API stopCommandCapture(); + FMOD_RESULT F_API loadCommandReplay(const char *filename, FMOD_STUDIO_COMMANDREPLAY_FLAGS flags, CommandReplay **playback); + + // Callbacks + FMOD_RESULT F_API setCallback(FMOD_STUDIO_SYSTEM_CALLBACK callback, FMOD_STUDIO_SYSTEM_CALLBACK_TYPE callbackmask = FMOD_STUDIO_SYSTEM_CALLBACK_ALL); + FMOD_RESULT F_API getUserData(void **userData) const; + FMOD_RESULT F_API setUserData(void *userData); + + }; + + class EventDescription + { + private: + // Constructor made private so user cannot statically instance the class. + EventDescription(); + EventDescription(const EventDescription &); + + public: + // Handle validity + bool F_API isValid() const; + + // Property access + FMOD_RESULT F_API getID(FMOD_GUID *id) const; + FMOD_RESULT F_API getPath(char *path, int size, int *retrieved) const; + FMOD_RESULT F_API getParameterCount(int *count) const; + FMOD_RESULT F_API getParameterByIndex(int index, FMOD_STUDIO_PARAMETER_DESCRIPTION *parameter) const; + FMOD_RESULT F_API getParameter(const char *name, FMOD_STUDIO_PARAMETER_DESCRIPTION *parameter) const; + FMOD_RESULT F_API getUserPropertyCount(int *count) const; + FMOD_RESULT F_API getUserPropertyByIndex(int index, FMOD_STUDIO_USER_PROPERTY *property) const; + FMOD_RESULT F_API getUserProperty(const char *name, FMOD_STUDIO_USER_PROPERTY *property) const; + FMOD_RESULT F_API getLength(int *length) const; + FMOD_RESULT F_API getMinimumDistance(float *distance) const; + FMOD_RESULT F_API getMaximumDistance(float *distance) const; + FMOD_RESULT F_API getSoundSize(float *size) const; + + FMOD_RESULT F_API isOneshot(bool *oneshot) const; + FMOD_RESULT F_API isStream(bool *isStream) const; + FMOD_RESULT F_API is3D(bool *is3D) const; + + // Playback control + FMOD_RESULT F_API createInstance(EventInstance **instance) const; + FMOD_RESULT F_API getInstanceCount(int *count) const; + FMOD_RESULT F_API getInstanceList(EventInstance **array, int capacity, int *count) const; + + // Sample data loading control + FMOD_RESULT F_API loadSampleData(); + FMOD_RESULT F_API unloadSampleData(); + FMOD_RESULT F_API getSampleLoadingState(FMOD_STUDIO_LOADING_STATE *state) const; + + // Convenience functions + FMOD_RESULT F_API releaseAllInstances(); + + // Callbacks + FMOD_RESULT F_API setCallback(FMOD_STUDIO_EVENT_CALLBACK callback, FMOD_STUDIO_EVENT_CALLBACK_TYPE callbackmask = FMOD_STUDIO_EVENT_CALLBACK_ALL); + FMOD_RESULT F_API getUserData(void **userData) const; + FMOD_RESULT F_API setUserData(void *userData); + }; + + class EventInstance + { + private: + // Constructor made private so user cannot statically instance the class. + EventInstance(); + EventInstance(const EventInstance &); + + public: + // Handle validity + bool F_API isValid() const; + + // Property access + FMOD_RESULT F_API getDescription(EventDescription **description) const; + + // Playback control + FMOD_RESULT F_API getVolume(float *volume) const; + FMOD_RESULT F_API setVolume(float volume); + + FMOD_RESULT F_API getPitch(float *pitch) const; + FMOD_RESULT F_API setPitch(float pitch); + + FMOD_RESULT F_API get3DAttributes(FMOD_3D_ATTRIBUTES *attributes) const; + FMOD_RESULT F_API set3DAttributes(const FMOD_3D_ATTRIBUTES *attributes); + + FMOD_RESULT F_API getProperty(FMOD_STUDIO_EVENT_PROPERTY index, float* value) const; + FMOD_RESULT F_API setProperty(FMOD_STUDIO_EVENT_PROPERTY index, float value); + + FMOD_RESULT F_API getPaused(bool *paused) const; + FMOD_RESULT F_API setPaused(bool paused); + + FMOD_RESULT F_API start(); + FMOD_RESULT F_API stop(FMOD_STUDIO_STOP_MODE mode); + + FMOD_RESULT F_API getTimelinePosition(int *position) const; + FMOD_RESULT F_API setTimelinePosition(int position); + + FMOD_RESULT F_API getPlaybackState(FMOD_STUDIO_PLAYBACK_STATE *state) const; + + FMOD_RESULT F_API getChannelGroup(ChannelGroup **group) const; + + FMOD_RESULT F_API release(); + + FMOD_RESULT F_API isVirtual(bool *virtualState) const; + + FMOD_RESULT F_API getParameter(const char *name, ParameterInstance **parameter) const; + FMOD_RESULT F_API getParameterByIndex(int index, ParameterInstance **parameter) const; + FMOD_RESULT F_API getParameterCount(int *count) const; + + FMOD_RESULT F_API setParameterValue(const char *name, float value); + FMOD_RESULT F_API setParameterValueByIndex(int index, float value); + + FMOD_RESULT F_API getCue(const char *name, CueInstance **cue) const; + FMOD_RESULT F_API getCueByIndex(int index, CueInstance **cue) const; + FMOD_RESULT F_API getCueCount(int *count) const; + + // Callbacks + FMOD_RESULT F_API setCallback(FMOD_STUDIO_EVENT_CALLBACK callback, FMOD_STUDIO_EVENT_CALLBACK_TYPE callbackmask = FMOD_STUDIO_EVENT_CALLBACK_ALL); + FMOD_RESULT F_API getUserData(void **userData) const; + FMOD_RESULT F_API setUserData(void *userData); + }; + + class CueInstance + { + private: + // Constructor made private so user cannot statically instance the class. + CueInstance(); + CueInstance(const CueInstance &); + + public: + // Handle validity + bool F_API isValid() const; + + FMOD_RESULT F_API trigger(); + }; + + class ParameterInstance + { + private: + // Constructor made private so user cannot statically instance the class. + ParameterInstance(); + ParameterInstance(const ParameterInstance &); + + public: + // Handle validity + bool F_API isValid() const; + + // Property access + FMOD_RESULT F_API getDescription(FMOD_STUDIO_PARAMETER_DESCRIPTION *description) const; + + // Playback control + FMOD_RESULT F_API getValue(float *value) const; + FMOD_RESULT F_API setValue(float value); + }; + + class Bus + { + private: + // Constructor made private so user cannot statically instance the class. + Bus(); + Bus(const Bus &); + + public: + // Handle validity + bool F_API isValid() const; + + // Property access + FMOD_RESULT F_API getID(FMOD_GUID *id) const; + FMOD_RESULT F_API getPath(char *path, int size, int *retrieved) const; + + // Playback control + FMOD_RESULT F_API getFaderLevel(float *level) const; + FMOD_RESULT F_API setFaderLevel(float level); + + FMOD_RESULT F_API getPaused(bool *paused) const; + FMOD_RESULT F_API setPaused(bool paused); + + FMOD_RESULT F_API getMute(bool *paused) const; + FMOD_RESULT F_API setMute(bool paused); + + FMOD_RESULT F_API stopAllEvents(FMOD_STUDIO_STOP_MODE mode); + + // Low-level API access + FMOD_RESULT F_API lockChannelGroup(); + FMOD_RESULT F_API unlockChannelGroup(); + FMOD_RESULT F_API getChannelGroup(FMOD::ChannelGroup **channelgroup) const; + }; + + class VCA + { + private: + // Constructor made private so user cannot statically instance the class. + VCA(); + VCA(const VCA &); + + public: + // Handle validity + bool F_API isValid() const; + + // Property access + FMOD_RESULT F_API getID(FMOD_GUID *id) const; + FMOD_RESULT F_API getPath(char *path, int size, int *retrieved) const; + + // Playback control + FMOD_RESULT F_API getFaderLevel(float *level) const; + FMOD_RESULT F_API setFaderLevel(float level); + }; + + class Bank + { + private: + // Constructor made private so user cannot statically instance the class. + Bank(); + Bank(const Bank &); + + public: + // Handle validity + bool F_API isValid() const; + + // Property access + FMOD_RESULT F_API getID(FMOD_GUID *id) const; + FMOD_RESULT F_API getPath(char *path, int size, int *retrieved) const; + + // Loading control + FMOD_RESULT F_API unload(); + FMOD_RESULT F_API loadSampleData(); + FMOD_RESULT F_API unloadSampleData(); + + FMOD_RESULT F_API getLoadingState(FMOD_STUDIO_LOADING_STATE *state) const; + FMOD_RESULT F_API getSampleLoadingState(FMOD_STUDIO_LOADING_STATE *state) const; + + // Enumeration + FMOD_RESULT F_API getStringCount(int *count) const; + FMOD_RESULT F_API getStringInfo(int index, FMOD_GUID *id, char *path, int size, int *retrieved) const; + FMOD_RESULT F_API getEventCount(int *count) const; + FMOD_RESULT F_API getEventList(EventDescription **array, int capacity, int *count) const; + FMOD_RESULT F_API getBusCount(int *count) const; + FMOD_RESULT F_API getBusList(Bus **array, int capacity, int *count) const; + FMOD_RESULT F_API getVCACount(int *count) const; + FMOD_RESULT F_API getVCAList(VCA **array, int capacity, int *count) const; + + FMOD_RESULT F_API getUserData(void **userData) const; + FMOD_RESULT F_API setUserData(void *userData); + }; + + class CommandReplay + { + private: + // Constructor made private so user cannot statically instance the class. + CommandReplay(); + CommandReplay(const CommandReplay &); + + public: + // Handle validity + bool F_API isValid() const; + + // Information query + FMOD_RESULT F_API getSystem(System **system) const; + FMOD_RESULT F_API getLength(float *length) const; + + FMOD_RESULT F_API getCommandCount(int *count) const; + FMOD_RESULT F_API getCommandInfo(int commandIndex, FMOD_STUDIO_COMMAND_INFO *info) const; + FMOD_RESULT F_API getCommandString(int commandIndex, char *buffer, int length) const; + FMOD_RESULT F_API getCommandAtTime(float time, int *commandIndex) const; + + // Playback + FMOD_RESULT F_API setBankPath(const char *bankPath); + FMOD_RESULT F_API start(); + FMOD_RESULT F_API stop(); + FMOD_RESULT F_API seekToTime(float time); + FMOD_RESULT F_API seekToCommand(int commandIndex); + FMOD_RESULT F_API getPaused(bool *paused) const; + FMOD_RESULT F_API setPaused(bool paused); + FMOD_RESULT F_API getPlaybackState(FMOD_STUDIO_PLAYBACK_STATE *state) const; + FMOD_RESULT F_API getCurrentCommand(int *commandIndex, float *currentTime) const; + + // Release + FMOD_RESULT F_API release(); + + // Callbacks + FMOD_RESULT F_API setFrameCallback(FMOD_STUDIO_COMMANDREPLAY_FRAME_CALLBACK callback); + FMOD_RESULT F_API setLoadBankCallback(FMOD_STUDIO_COMMANDREPLAY_LOAD_BANK_CALLBACK callback); + FMOD_RESULT F_API setCreateInstanceCallback(FMOD_STUDIO_COMMANDREPLAY_CREATE_INSTANCE_CALLBACK callback); + + FMOD_RESULT F_API getUserData(void **userData) const; + FMOD_RESULT F_API setUserData(void *userData); + }; + +} // namespace Studio + +} // namespace FMOD + +#endif //FMOD_STUDIO_HPP diff --git a/Plugins/FMODStudio/Source/FMODStudio/Public/FMOD/fmod_studio_common.h b/Plugins/FMODStudio/Source/FMODStudio/Public/FMOD/fmod_studio_common.h new file mode 100644 index 0000000..f80b405 --- /dev/null +++ b/Plugins/FMODStudio/Source/FMODStudio/Public/FMOD/fmod_studio_common.h @@ -0,0 +1,754 @@ +/* + fmod_studio_common.h + Copyright (c), Firelight Technologies Pty, Ltd. 2016. + + This header defines common enumerations, structs and callbacks that are shared between the C and C++ interfaces. +*/ + +#ifndef FMOD_STUDIO_COMMON_H +#define FMOD_STUDIO_COMMON_H + +#include "fmod.h" + + +/* + FMOD Studio types. +*/ + +typedef struct FMOD_STUDIO_SYSTEM FMOD_STUDIO_SYSTEM; +typedef struct FMOD_STUDIO_EVENTDESCRIPTION FMOD_STUDIO_EVENTDESCRIPTION; +typedef struct FMOD_STUDIO_EVENTINSTANCE FMOD_STUDIO_EVENTINSTANCE; +typedef struct FMOD_STUDIO_CUEINSTANCE FMOD_STUDIO_CUEINSTANCE; +typedef struct FMOD_STUDIO_PARAMETERINSTANCE FMOD_STUDIO_PARAMETERINSTANCE; +typedef struct FMOD_STUDIO_BUS FMOD_STUDIO_BUS; +typedef struct FMOD_STUDIO_VCA FMOD_STUDIO_VCA; +typedef struct FMOD_STUDIO_BANK FMOD_STUDIO_BANK; +typedef struct FMOD_STUDIO_COMMANDREPLAY FMOD_STUDIO_COMMANDREPLAY; + + +/* +[DEFINE] +[ + [NAME] + FMOD_STUDIO_INITFLAGS + + [DESCRIPTION] + Studio System initialization flags. + Use them with Studio::System::initialize in the *studioflags* parameter to change various behavior. + + [REMARKS] + + [SEE_ALSO] + Studio::System::initialize +] +*/ +#define FMOD_STUDIO_INIT_NORMAL 0x00000000 /* Initialize normally. */ +#define FMOD_STUDIO_INIT_LIVEUPDATE 0x00000001 /* Enable live update. */ +#define FMOD_STUDIO_INIT_ALLOW_MISSING_PLUGINS 0x00000002 /* Load banks even if they reference plugins that have not been loaded. */ +#define FMOD_STUDIO_INIT_SYNCHRONOUS_UPDATE 0x00000004 /* Disable asynchronous processing and perform all processing on the calling thread instead. */ +#define FMOD_STUDIO_INIT_DEFERRED_CALLBACKS 0x00000008 /* Defer timeline callbacks until the main update. See Studio::EventInstance::setCallback for more information. */ +/* [DEFINE_END] */ + +typedef unsigned int FMOD_STUDIO_INITFLAGS; + + +/* +[ENUM] +[ + [DESCRIPTION] + These values describe the loading status of various objects. + + [REMARKS] + Calling Studio::System::loadBankFile, Studio::System::loadBankMemory or Studio::System::loadBankCustom + will trigger loading of metadata from the bank. + + Calling Studio::EventDescription::loadSampleData, Studio::EventDescription::createInstance + or Studio::Bank::loadSampleData may trigger asynchronous loading of sample data. + + [SEE_ALSO] + Studio::EventDescription::getSampleLoadingState + Studio::Bank::getLoadingState + Studio::Bank::getSampleLoadingState +] +*/ +typedef enum FMOD_STUDIO_LOADING_STATE +{ + FMOD_STUDIO_LOADING_STATE_UNLOADING, /* Currently unloading. */ + FMOD_STUDIO_LOADING_STATE_UNLOADED, /* Not loaded. */ + FMOD_STUDIO_LOADING_STATE_LOADING, /* Loading in progress. */ + FMOD_STUDIO_LOADING_STATE_LOADED, /* Loaded and ready to play. */ + FMOD_STUDIO_LOADING_STATE_ERROR, /* Failed to load and is now in error state. */ + + FMOD_STUDIO_LOADING_STATE_FORCEINT = 65536 /* Makes sure this enum is signed 32bit. */ +} FMOD_STUDIO_LOADING_STATE; + + +/* +[ENUM] +[ + [DESCRIPTION] + Specifies how to use the memory buffer passed to Studio::System::loadBankMemory. + + [REMARKS] + + [SEE_ALSO] + Studio::System::loadBankMemory + Studio::Bank::unload +] +*/ +typedef enum FMOD_STUDIO_LOAD_MEMORY_MODE +{ + FMOD_STUDIO_LOAD_MEMORY, /* When passed to Studio::System::loadBankMemory, FMOD duplicates the memory into its own buffers. Your buffer can be freed after Studio::System::loadBankMemory returns. */ + FMOD_STUDIO_LOAD_MEMORY_POINT, /* This differs from FMOD_STUDIO_LOAD_MEMORY in that FMOD uses the memory as is, without duplicating the memory into its own buffers. Cannot not be freed after load, only after calling Studio::Bank::unload. */ + + FMOD_STUDIO_LOAD_MEMORY_FORCEINT = 65536 /* Makes sure this enum is signed 32bit. */ +} FMOD_STUDIO_LOAD_MEMORY_MODE; + + +/* +[ENUM] +[ + [DESCRIPTION] + Describes the type of a parameter. + + [REMARKS] + There are two primary types of parameters: game controlled and automatic. + Game controlled parameters receive their value from the API using + Studio::ParameterInstance::setValue. Automatic parameters are updated inside + FMOD based on the positional information of the event and listener. + + **Horizontal angle** means the angle between vectors projected onto the + listener's XZ plane (for the EVENT_ORIENTATION and DIRECTION parameters) + or the global XZ plane (for the LISTENER_ORIENTATION parameter). + + [SEE_ALSO] + FMOD_STUDIO_PARAMETER_DESCRIPTION + Studio::ParameterInstance::setValue + Studio::EventInstance::set3DAttributes + Studio::System::setListenerAttributes +] +*/ +typedef enum FMOD_STUDIO_PARAMETER_TYPE +{ + FMOD_STUDIO_PARAMETER_GAME_CONTROLLED, /* Controlled via the API using Studio::ParameterInstance::setValue. */ + FMOD_STUDIO_PARAMETER_AUTOMATIC_DISTANCE, /* Distance between the event and the listener. */ + FMOD_STUDIO_PARAMETER_AUTOMATIC_EVENT_CONE_ANGLE, /* Angle between the event's forward vector and the vector pointing from the event to the listener (0 to 180 degrees). */ + FMOD_STUDIO_PARAMETER_AUTOMATIC_EVENT_ORIENTATION, /* Horizontal angle between the event's forward vector and listener's forward vector (-180 to 180 degrees). */ + FMOD_STUDIO_PARAMETER_AUTOMATIC_DIRECTION, /* Horizontal angle between the listener's forward vector and the vector pointing from the listener to the event (-180 to 180 degrees). */ + FMOD_STUDIO_PARAMETER_AUTOMATIC_ELEVATION, /* Angle between the listener's XZ plane and the vector pointing from the listener to the event (-90 to 90 degrees). */ + FMOD_STUDIO_PARAMETER_AUTOMATIC_LISTENER_ORIENTATION, /* Horizontal angle between the listener's forward vector and the global positive Z axis (-180 to 180 degrees). */ + + FMOD_STUDIO_PARAMETER_MAX, /* Maximum number of parameter types supported. */ + FMOD_STUDIO_PARAMETER_FORCEINT = 65536 /* Makes sure this enum is signed 32bit. */ +} FMOD_STUDIO_PARAMETER_TYPE; + + +/* +[STRUCTURE] +[ + [DESCRIPTION] + Information for loading a bank with Studio::System::loadBankCustom. + + [REMARKS] + + [SEE_ALSO] + Studio::System::loadBankCustom +] +*/ +typedef struct FMOD_STUDIO_BANK_INFO +{ + int size; /* The size of this struct (for binary compatibility) */ + void *userData; /* User data to be passed to the file callbacks */ + int userDataLength; /* If this is non-zero, userData will be copied internally */ + FMOD_FILE_OPEN_CALLBACK openCallback; /* Callback for opening this file. */ + FMOD_FILE_CLOSE_CALLBACK closeCallback; /* Callback for closing this file. */ + FMOD_FILE_READ_CALLBACK readCallback; /* Callback for reading from this file. */ + FMOD_FILE_SEEK_CALLBACK seekCallback; /* Callback for seeking within this file. */ +} FMOD_STUDIO_BANK_INFO; + + +/* +[STRUCTURE] +[ + [DESCRIPTION] + Structure describing an event parameter. + + [REMARKS] + + [SEE_ALSO] + Studio::EventDescription::getParameter + FMOD_STUDIO_PARAMETER_TYPE +] +*/ +typedef struct FMOD_STUDIO_PARAMETER_DESCRIPTION +{ + const char *name; /* Name of the parameter. */ + float minimum; /* Minimum parameter value. */ + float maximum; /* Maximum parameter value. */ + FMOD_STUDIO_PARAMETER_TYPE type; /* Type of the parameter */ +} FMOD_STUDIO_PARAMETER_DESCRIPTION; + + +/* +[ENUM] +[ + [DESCRIPTION] + These definitions describe a user property's type. + + [REMARKS] + + [SEE_ALSO] + FMOD_STUDIO_USER_PROPERTY +] +*/ +typedef enum FMOD_STUDIO_USER_PROPERTY_TYPE +{ + FMOD_STUDIO_USER_PROPERTY_TYPE_INTEGER, /* Integer property */ + FMOD_STUDIO_USER_PROPERTY_TYPE_BOOLEAN, /* Boolean property */ + FMOD_STUDIO_USER_PROPERTY_TYPE_FLOAT, /* Float property */ + FMOD_STUDIO_USER_PROPERTY_TYPE_STRING, /* String property */ + + FMOD_STUDIO_USER_PROPERTY_TYPE_FORCEINT = 65536 /* Makes sure this enum is signed 32bit. */ +} FMOD_STUDIO_USER_PROPERTY_TYPE; + + +/* +[ENUM] +[ + [DESCRIPTION] + These definitions describe built-in event properties. + + [REMARKS] + For FMOD_STUDIO_EVENT_PROPERTY_CHANNELPRIORITY, a value of -1 uses the priority + set in FMOD Studio, while other values override it. This property uses the same + system as Channel::setPriority; this means lower values are higher priority + (i.e. 0 is the highest priority while 256 is the lowest). + + [SEE_ALSO] + Studio::EventInstance::getProperty + Studio::EventInstance::setProperty +] +*/ +typedef enum FMOD_STUDIO_EVENT_PROPERTY +{ + FMOD_STUDIO_EVENT_PROPERTY_CHANNELPRIORITY, /* Priority to set on low-level channels created by this event instance (-1 to 256). */ + FMOD_STUDIO_EVENT_PROPERTY_SCHEDULE_DELAY, /* Schedule delay to synchronized playback for multiple tracks in DSP clocks, or -1 for default. */ + FMOD_STUDIO_EVENT_PROPERTY_SCHEDULE_LOOKAHEAD, /* Schedule look-ahead on the timeline in DSP clocks, or -1 for default. */ + FMOD_STUDIO_EVENT_PROPERTY_MINIMUM_DISTANCE, /* Override the event's 3D minimum distance, or -1 for default. */ + FMOD_STUDIO_EVENT_PROPERTY_MAXIMUM_DISTANCE, /* Override the event's 3D maximum distance, or -1 for default. */ + FMOD_STUDIO_EVENT_PROPERTY_MAX, /* Maximum number of event properties supported. */ + + FMOD_STUDIO_EVENT_PROPERTY_FORCEINT = 65536 /* Makes sure this enum is signed 32bit. */ +} FMOD_STUDIO_EVENT_PROPERTY; + + +/* +[STRUCTURE] +[ + [DESCRIPTION] + Structure describing a user property. + + [REMARKS] + + [SEE_ALSO] + Studio::EventDescription::getUserProperty +] +*/ +typedef struct FMOD_STUDIO_USER_PROPERTY +{ + const char *name; /* Name of the user property. */ + FMOD_STUDIO_USER_PROPERTY_TYPE type; /* Type of the user property. Use this to select one of the following values. */ + + union + { + int intValue; /* Value of the user property. Only valid when type is FMOD_STUDIO_USER_PROPERTY_TYPE_INTEGER. */ + FMOD_BOOL boolValue; /* Value of the user property. Only valid when type is FMOD_STUDIO_USER_PROPERTY_TYPE_BOOLEAN. */ + float floatValue; /* Value of the user property. Only valid when type is FMOD_STUDIO_USER_PROPERTY_TYPE_FLOAT. */ + const char *stringValue; /* Value of the user property. Only valid when type is FMOD_STUDIO_USER_PROPERTY_TYPE_STRING. */ + }; +} FMOD_STUDIO_USER_PROPERTY; + + +/* +[DEFINE] +[ + [NAME] + FMOD_STUDIO_SYSTEM_CALLBACK_TYPE + + [DESCRIPTION] + These callback types are used with Studio::System::setCallback. + + [REMARKS] + + [SEE_ALSO] + FMOD_STUDIO_SYSTEM_CALLBACK + Studio::System::setCallback +] +*/ +#define FMOD_STUDIO_SYSTEM_CALLBACK_PREUPDATE 0x00000001 /* Called at the start of the main Studio update. For async mode this will be on its own thread. */ +#define FMOD_STUDIO_SYSTEM_CALLBACK_POSTUPDATE 0x00000002 /* Called at the end of the main Studio update. For async mode this will be on its own thread. */ +#define FMOD_STUDIO_SYSTEM_CALLBACK_BANK_UNLOAD 0x00000004 /* Called when bank has just been unloaded, after all resources are freed. CommandData will be the bank handle.*/ +#define FMOD_STUDIO_SYSTEM_CALLBACK_ALL 0xFFFFFFFF /* Pass this mask to Studio::System::setCallback to receive all callback types. */ +/* [DEFINE_END] */ + +typedef unsigned int FMOD_STUDIO_SYSTEM_CALLBACK_TYPE; + + +/* +[DEFINE] +[ + [NAME] + FMOD_STUDIO_EVENT_CALLBACK_TYPE + + [DESCRIPTION] + These callback types are used with FMOD_STUDIO_EVENT_CALLBACK. + + [REMARKS] + The data passed to the event callback function in the *parameters* argument varies based on the callback type. + + FMOD_STUDIO_EVENT_CALLBACK_STARTED is called when: + + * Studio::EventInstance::start has been called on an event which was not already playing. + + FMOD_STUDIO_EVENT_CALLBACK_RESTARTED is called when: + + * Studio::EventInstance::start has been called on an event which was already playing. + + FMOD_STUDIO_EVENT_CALLBACK_STOPPED is called when: + + * The event has stopped due to Studio::EventInstance::stop being called with FMOD_STUDIO_STOP_IMMEDIATE. + * The event has finished fading out after Studio::EventInstance::stop was called with FMOD_STUDIO_STOP_ALLOWFADEOUT. + * The event has stopped naturally by reaching the end of the timeline, and no further sounds can be triggered due to + parameter changes. + + FMOD_STUDIO_EVENT_CALLBACK_CREATE_PROGRAMMER_SOUND is called when: + + * A programmer sound is about to play. FMOD expects the callback to provide an FMOD::Sound object for it to use. + + FMOD_STUDIO_EVENT_CALLBACK_DESTROY_PROGRAMMER_SOUND is called when: + + * A programmer sound has stopped playing. At this point it is safe to release the FMOD::Sound object that was used. + + [SEE_ALSO] + Studio::EventDescription::setCallback + Studio::EventInstance::setCallback + FMOD_STUDIO_EVENT_CALLBACK +] +*/ +#define FMOD_STUDIO_EVENT_CALLBACK_STARTED 0x00000001 /* Called when an instance starts. Parameters = unused. */ +#define FMOD_STUDIO_EVENT_CALLBACK_RESTARTED 0x00000002 /* Called when an instance is restarted. Parameters = unused. */ +#define FMOD_STUDIO_EVENT_CALLBACK_STOPPED 0x00000004 /* Called when an instance stops. Parameters = unused. */ +#define FMOD_STUDIO_EVENT_CALLBACK_CREATE_PROGRAMMER_SOUND 0x00000008 /* Called when a programmer sound needs to be created in order to play a programmer instrument. Parameters = FMOD_STUDIO_PROGRAMMER_SOUND_PROPERTIES. */ +#define FMOD_STUDIO_EVENT_CALLBACK_DESTROY_PROGRAMMER_SOUND 0x00000010 /* Called when a programmer sound needs to be destroyed. Parameters = FMOD_STUDIO_PROGRAMMER_SOUND_PROPERTIES. */ +#define FMOD_STUDIO_EVENT_CALLBACK_PLUGIN_CREATED 0x00000020 /* Called when a DSP plugin instance has just been created. Parameters = FMOD_STUDIO_PLUGIN_INSTANCE_PROPERTIES. */ +#define FMOD_STUDIO_EVENT_CALLBACK_PLUGIN_DESTROYED 0x00000040 /* Called when a DSP plugin instance is about to be destroyed. Parameters = FMOD_STUDIO_PLUGIN_INSTANCE_PROPERTIES. */ +#define FMOD_STUDIO_EVENT_CALLBACK_CREATED 0x00000080 /* Called when an instance is fully created. Parameters = unused. */ +#define FMOD_STUDIO_EVENT_CALLBACK_DESTROYED 0x00000100 /* Called when an instance is just about to be destroyed. Parameters = unused. */ +#define FMOD_STUDIO_EVENT_CALLBACK_START_FAILED 0x00000200 /* Called when an instance did not start, e.g. due to polyphony. Parameters = unused. */ +#define FMOD_STUDIO_EVENT_CALLBACK_TIMELINE_MARKER 0x00000400 /* Called when the timeline passes a named marker. Parameters = FMOD_STUDIO_TIMELINE_MARKER_PROPERTIES. */ +#define FMOD_STUDIO_EVENT_CALLBACK_TIMELINE_BEAT 0x00000800 /* Called when the timeline hits a beat in a tempo section. Parameters = FMOD_STUDIO_TIMELINE_BEAT_PROPERTIES. */ +#define FMOD_STUDIO_EVENT_CALLBACK_ALL 0xFFFFFFFF /* Pass this mask to Studio::EventDescription::setCallback or Studio::EventInstance::setCallback to receive all callback types. */ +/* [DEFINE_END] */ + +typedef unsigned int FMOD_STUDIO_EVENT_CALLBACK_TYPE; + +/* +[STRUCTURE] +[ + [DESCRIPTION] + This structure holds information about a programmer sound. + + [REMARKS] + This data is passed to the event callback function when type is FMOD_STUDIO_EVENT_CALLBACK_CREATE_PROGRAMMER_SOUND + or FMOD_STUDIO_EVENT_CALLBACK_DESTROY_PROGRAMMER_SOUND. + + To support non-blocking loading of FSB subsounds, you can specify the subsound you want to use by setting the + subsoundIndex field. This will cause FMOD to wait until the provided sound is ready and then get the specified + subsound from it. + + [SEE_ALSO] + FMOD_STUDIO_EVENT_CALLBACK + Studio::EventDescription::setCallback + Studio::EventInstance::setCallback +] +*/ +typedef struct FMOD_STUDIO_PROGRAMMER_SOUND_PROPERTIES +{ + const char *name; /* The name of the programmer instrument (set in FMOD Studio). */ + FMOD_SOUND *sound; /* The programmer-created sound. This should be filled in by the create callback, and cleaned up by the destroy callback. This can be cast to/from FMOD::Sound* type. */ + int subsoundIndex; /* The index of the subsound to use, or -1 if the provided sound should be used directly. Defaults to -1. */ +} FMOD_STUDIO_PROGRAMMER_SOUND_PROPERTIES; + + +/* +[STRUCTURE] +[ + [DESCRIPTION] + This structure holds information about a DSP plugin instance. + + [REMARKS] + This data is passed to the event callback function when type is FMOD_STUDIO_EVENT_CALLBACK_PLUGIN_CREATED + or FMOD_STUDIO_EVENT_CALLBACK_PLUGIN_DESTROYED. + + [SEE_ALSO] + FMOD_STUDIO_EVENT_CALLBACK + Studio::EventDescription::setCallback + Studio::EventInstance::setCallback +] +*/ +typedef struct FMOD_STUDIO_PLUGIN_INSTANCE_PROPERTIES +{ + const char *name; /* The name of the plugin effect or sound (set in FMOD Studio). */ + FMOD_DSP *dsp; /* The DSP plugin instance. This can be cast to FMOD::DSP* type. */ +} FMOD_STUDIO_PLUGIN_INSTANCE_PROPERTIES; + +/* +[STRUCTURE] +[ + [DESCRIPTION] + This structure holds information about a marker on the timeline. + + [REMARKS] + This data is passed to the event callback function when type is FMOD_STUDIO_EVENT_CALLBACK_TIMELINE_MARKER. + + [SEE_ALSO] + FMOD_STUDIO_EVENT_CALLBACK + Studio::EventDescription::setCallback + Studio::EventInstance::setCallback +] +*/ +typedef struct FMOD_STUDIO_TIMELINE_MARKER_PROPERTIES +{ + const char* name; /* The marker name */ + int position; /* The position of the marker on the timeline in milliseconds. */ +} FMOD_STUDIO_TIMELINE_MARKER_PROPERTIES; + +/* +[STRUCTURE] +[ + [DESCRIPTION] + This structure holds information about a beat on the timeline. + + [REMARKS] + This data is passed to the event callback function when type is FMOD_STUDIO_EVENT_CALLBACK_TIMELINE_BEAT. + + [SEE_ALSO] + FMOD_STUDIO_EVENT_CALLBACK + Studio::EventDescription::setCallback + Studio::EventInstance::setCallback +] +*/ +typedef struct FMOD_STUDIO_TIMELINE_BEAT_PROPERTIES +{ + int bar; /* The bar number (starting from 1). */ + int beat; /* The beat number within the bar (starting from 1). */ + int position; /* The position of the beat on the timeline in milliseconds. */ + float tempo; /* The current tempo in beats per minute. */ + int timeSignatureUpper; /* The current time signature upper number (beats per bar). */ + int timeSignatureLower; /* The current time signature lower number (beat unit). */ +} FMOD_STUDIO_TIMELINE_BEAT_PROPERTIES; + +/* +[ENUM] +[ + [DESCRIPTION] + These values describe the playback state of an event instance. + + [REMARKS] + + [SEE_ALSO] + Studio::EventInstance::getPlaybackState + Studio::EventInstance::start + Studio::EventInstance::stop + Studio::CueInstance::trigger + Studio::ParameterInstance::setValue +] +*/ +typedef enum FMOD_STUDIO_PLAYBACK_STATE +{ + FMOD_STUDIO_PLAYBACK_PLAYING, /* Currently playing. */ + FMOD_STUDIO_PLAYBACK_SUSTAINING, /* The timeline cursor is paused on a sustain point. */ + FMOD_STUDIO_PLAYBACK_STOPPED, /* Not playing. */ + FMOD_STUDIO_PLAYBACK_STARTING, /* Start has been called but the instance is not fully started yet. */ + FMOD_STUDIO_PLAYBACK_STOPPING, /* Stop has been called but the instance is not fully stopped yet. */ + + FMOD_STUDIO_PLAYBACK_FORCEINT = 65536 /* Makes sure this enum is signed 32bit. */ +} FMOD_STUDIO_PLAYBACK_STATE; + + +/* +[ENUM] +[ + [DESCRIPTION] + Controls how to stop playback of an event instance. + + [REMARKS] + + [SEE_ALSO] + Studio::EventInstance::stop + Studio::Bus::stopAllEvents +] +*/ +typedef enum FMOD_STUDIO_STOP_MODE +{ + FMOD_STUDIO_STOP_ALLOWFADEOUT, /* Allows AHDSR modulators to complete their release, and DSP effect tails to play out. */ + FMOD_STUDIO_STOP_IMMEDIATE, /* Stops the event instance immediately. */ + + FMOD_STUDIO_STOP_FORCEINT = 65536 /* Makes sure this enum is signed 32bit. */ +} FMOD_STUDIO_STOP_MODE; + + +/* +[DEFINE] +[ + [NAME] + FMOD_STUDIO_LOAD_BANK_FLAGS + + [DESCRIPTION] + Flags passed into Studio loadBank commands to control bank load behaviour. + + [REMARKS] + + [SEE_ALSO] + Studio::System::loadBankFile + Studio::System::loadBankMemory + Studio::System::loadBankCustom +] +*/ +#define FMOD_STUDIO_LOAD_BANK_NORMAL 0x00000000 /* Standard behaviour. */ +#define FMOD_STUDIO_LOAD_BANK_NONBLOCKING 0x00000001 /* Bank loading occurs asynchronously rather than occurring immediately. */ +#define FMOD_STUDIO_LOAD_BANK_DECOMPRESS_SAMPLES 0x00000002 /* Force samples to decompress into memory when they are loaded, rather than staying compressed. */ +/* [DEFINE_END] */ + +typedef unsigned int FMOD_STUDIO_LOAD_BANK_FLAGS; + + +/* +[STRUCTURE] +[ + [DESCRIPTION] + Settings for advanced features like configuring memory and cpu usage. + + [REMARKS] + Members marked with [r] mean the variable is modified by FMOD and is for reading purposes only. Do not change this value.
    + Members marked with [w] mean the variable can be written to. The user can set the value.
    + Members marked with [r/w] are either read or write depending on if you are using System::setAdvancedSettings (w) or System::getAdvancedSettings (r). + + [SEE_ALSO] + Studio::System::setAdvancedSettings + Studio::System::getAdvancedSettings + FMOD_MODE +] +*/ +typedef struct FMOD_STUDIO_ADVANCEDSETTINGS +{ + int cbSize; /* [w] Size of this structure. Use sizeof(FMOD_STUDIO_ADVANCEDSETTINGS) NOTE: This must be set before calling Studio::System::getAdvancedSettings or Studio::System::setAdvancedSettings! */ + unsigned int commandQueueSize; /* [r/w] Optional. Specify 0 to ignore. Specify the command queue size for studio async processing. Default 32kB. */ + unsigned int handleInitialSize; /* [r/w] Optional. Specify 0 to ignore. Specify the initial size to allocate for handles. Memory for handles will grow as needed in pages. Default 8192 * sizeof(void*) */ + int studioUpdatePeriod; /* [r/w] Optional. Specify 0 to ignore. Specify the update period of Studio when in async mode, in milliseconds. Will be quantised to the nearest multiple of mixer duration. Default is 20ms. */ +} FMOD_STUDIO_ADVANCEDSETTINGS; + + +/* +[STRUCTURE] +[ + [DESCRIPTION] + Performance information for FMOD Studio and low level systems. + + [REMARKS] + + [SEE_ALSO] + Studio::System::getCPUUsage +] +*/ +typedef struct FMOD_STUDIO_CPU_USAGE +{ + float dspUsage; /* Returns the % CPU time taken by DSP processing on the low level mixer thread. */ + float streamUsage; /* Returns the % CPU time taken by stream processing on the low level stream thread. */ + float geometryUsage; /* Returns the % CPU time taken by geometry processing on the low level geometry thread. */ + float updateUsage; /* Returns the % CPU time taken by low level update, called as part of the studio update. */ + float studioUsage; /* Returns the % CPU time taken by studio update, called from the studio thread. Does not include low level update time. */ +} FMOD_STUDIO_CPU_USAGE; + + +/* +[STRUCTURE] +[ + [DESCRIPTION] + Information for a single buffer in FMOD Studio. + + [REMARKS] + + [SEE_ALSO] + FMOD_STUDIO_BUFFER_USAGE +] +*/ +typedef struct FMOD_STUDIO_BUFFER_INFO +{ + int currentUsage; /* Current buffer usage in bytes. */ + int peakUsage; /* Peak buffer usage in bytes. */ + int capacity; /* Buffer capacity in bytes. */ + int stallCount; /* Cumulative number of stalls due to buffer overflow. */ + float stallTime; /* Cumulative amount of time stalled due to buffer overflow, in seconds. */ +} FMOD_STUDIO_BUFFER_INFO; + + +/* +[STRUCTURE] +[ + [DESCRIPTION] + Information for FMOD Studio buffer usage. + + [REMARKS] + + [SEE_ALSO] + Studio::System::getBufferUsage + Studio::System::resetBufferUsage + FMOD_STUDIO_BUFFER_INFO +] +*/ +typedef struct FMOD_STUDIO_BUFFER_USAGE +{ + FMOD_STUDIO_BUFFER_INFO studioCommandQueue; /* Information for the Studio Async Command buffer, controlled by FMOD_STUDIO_ADVANCEDSETTINGS commandQueueSize. */ + FMOD_STUDIO_BUFFER_INFO studioHandle; /* Information for the Studio handle table, controlled by FMOD_STUDIO_ADVANCEDSETTINGS handleInitialSize. */ +} FMOD_STUDIO_BUFFER_USAGE; + + +/* +[STRUCTURE] +[ + [DESCRIPTION] + Information for loading a sound from a sound table. + + [REMARKS] + The name_or_data member points into FMOD internal memory, which will become + invalid if the sound table bank is unloaded. + + If mode flags such as FMOD_CREATESTREAM or FMOD_NONBLOCKING are required, + they should be ORed together with the mode member when calling System::createSound. + + [SEE_ALSO] + Studio::System::getSoundInfo + System::createSound +] +*/ +typedef struct FMOD_STUDIO_SOUND_INFO +{ + const char* name_or_data; /* The filename or memory buffer that contains the sound. */ + FMOD_MODE mode; /* Mode flags required for loading the sound. */ + FMOD_CREATESOUNDEXINFO exinfo; /* Extra information required for loading the sound. */ + int subsoundIndex; /* Subsound index for loading the sound. */ +} FMOD_STUDIO_SOUND_INFO; + + +/* +[DEFINE] +[ + [NAME] + FMOD_STUDIO_COMMANDCAPTURE_FLAGS + + [DESCRIPTION] + Flags passed into Studio::System::startCommandCapture. + + [REMARKS] + + [SEE_ALSO] + Studio::System::startCommandCapture +] +*/ +#define FMOD_STUDIO_COMMANDCAPTURE_NORMAL 0x00000000 /* Standard behaviour. */ +#define FMOD_STUDIO_COMMANDCAPTURE_FILEFLUSH 0x00000001 /* Call file flush on every command. */ +#define FMOD_STUDIO_COMMANDCAPTURE_SKIP_INITIAL_STATE 0x00000002 /* Normally the initial state of banks and instances is captured, unless this flag is set. */ +/* [DEFINE_END] */ + +typedef unsigned int FMOD_STUDIO_COMMANDCAPTURE_FLAGS; + + +/* +[DEFINE] +[ + [NAME] + FMOD_STUDIO_COMMANDREPLAY_FLAGS + + [DESCRIPTION] + Flags passed into Studio::System::loadCommandReplay. + + [REMARKS] + + [SEE_ALSO] + Studio::System::loadCommandReplay +] +*/ +#define FMOD_STUDIO_COMMANDREPLAY_NORMAL 0x00000000 /* Standard behaviour. */ +#define FMOD_STUDIO_COMMANDREPLAY_SKIP_CLEANUP 0x00000001 /* Normally the playback will release any created resources when it stops, unless this flag is set. */ +/* [DEFINE_END] */ + +typedef unsigned int FMOD_STUDIO_COMMANDREPLAY_FLAGS; + + +/* +[ENUM] +[ + [DESCRIPTION] + Used to distinguish the types used in command replays. + + [REMARKS] + + [SEE_ALSO] +] +*/ +typedef enum FMOD_STUDIO_INSTANCETYPE +{ + FMOD_STUDIO_INSTANCETYPE_NONE, + FMOD_STUDIO_INSTANCETYPE_SYSTEM, + FMOD_STUDIO_INSTANCETYPE_EVENTDESCRIPTION, + FMOD_STUDIO_INSTANCETYPE_EVENTINSTANCE, + FMOD_STUDIO_INSTANCETYPE_PARAMETERINSTANCE, + FMOD_STUDIO_INSTANCETYPE_CUEINSTANCE, + FMOD_STUDIO_INSTANCETYPE_BUS, + FMOD_STUDIO_INSTANCETYPE_VCA, + FMOD_STUDIO_INSTANCETYPE_BANK, + FMOD_STUDIO_INSTANCETYPE_COMMANDREPLAY, + + FMOD_STUDIO_INSTANCETYPE_FORCEINT = 65536 /* Makes sure this enum is signed 32bit. */ +} FMOD_STUDIO_INSTANCETYPE; + + +/* +[STRUCTURE] +[ + [DESCRIPTION] + Information about a single command in a command replay file. + + [REMARKS] + This information has metadata about the command at the given index. Note that the handle fields are + from the recorded session, and will no longer correspond to any actual object type in the current + system. + + [SEE_ALSO] + Studio::CommandReplay::getCommandInfo +] +*/ +typedef struct FMOD_STUDIO_COMMAND_INFO +{ + const char* commandName; /* The full name of the API function for this command. */ + int parentCommandIndex; /* For commands that operate on an instance, this is the command that created the instance. */ + int frameNumber; /* The frame the command belongs to. */ + float frameTime; /* The playback time at which this command will be executed. */ + FMOD_STUDIO_INSTANCETYPE instanceType; /* The type of object that this command uses as an instance. */ + FMOD_STUDIO_INSTANCETYPE outputType; /* The type of object that this command outputs, if any. */ + unsigned int instanceHandle; /* The original handle value of the instance. This will no longer correspond to any actual object in playback. */ + unsigned int outputHandle; /* The original handle value of the command output. This will no longer correspond to any actual object in playback. */ +} FMOD_STUDIO_COMMAND_INFO; + + +/* + FMOD Studio callbacks. +*/ +typedef FMOD_RESULT (F_CALLBACK *FMOD_STUDIO_SYSTEM_CALLBACK) (FMOD_STUDIO_SYSTEM *system, FMOD_STUDIO_SYSTEM_CALLBACK_TYPE type, void *commanddata, void *userdata); +typedef FMOD_RESULT (F_CALLBACK *FMOD_STUDIO_EVENT_CALLBACK) (FMOD_STUDIO_EVENT_CALLBACK_TYPE type, FMOD_STUDIO_EVENTINSTANCE *event, void *parameters); +typedef FMOD_RESULT (F_CALLBACK *FMOD_STUDIO_COMMANDREPLAY_FRAME_CALLBACK) (FMOD_STUDIO_COMMANDREPLAY *replay, int commandIndex, float currentTime, void *userdata); +typedef FMOD_RESULT (F_CALLBACK *FMOD_STUDIO_COMMANDREPLAY_LOAD_BANK_CALLBACK) (FMOD_STUDIO_COMMANDREPLAY *replay, int commandIndex, const FMOD_GUID *bankGuid, const char *bankFilename, FMOD_STUDIO_LOAD_BANK_FLAGS flags, FMOD_STUDIO_BANK **bank, void *userdata); +typedef FMOD_RESULT (F_CALLBACK *FMOD_STUDIO_COMMANDREPLAY_CREATE_INSTANCE_CALLBACK) (FMOD_STUDIO_COMMANDREPLAY *replay, int commandIndex, FMOD_STUDIO_EVENTDESCRIPTION *eventDescription, FMOD_STUDIO_EVENTINSTANCE **instance, void *userdata); + +#endif // FMOD_STUDIO_COMMON_H diff --git a/Plugins/FMODStudio/Source/FMODStudio/Public/FMODStudioModule.h b/Plugins/FMODStudio/Source/FMODStudio/Public/FMODStudioModule.h new file mode 100644 index 0000000..82bb184 --- /dev/null +++ b/Plugins/FMODStudio/Source/FMODStudio/Public/FMODStudioModule.h @@ -0,0 +1,155 @@ +// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2016. + +#pragma once + +#include "ModuleManager.h" + +namespace FMOD +{ + namespace Studio + { + class System; + class EventDescription; + class EventInstance; + } +} + +class UFMODAsset; +class UFMODEvent; +class UWorld; +class AAudioVolume; +struct FInteriorSettings; +struct FFMODListener; // Currently only for private use, we don't export this type + +// Which FMOD Studio system to use +namespace EFMODSystemContext +{ + enum Type + { + // For use auditioning sounds within the editor + Auditioning, + + // For use in PIE and in-game + Runtime, + + // Max number of types + Max + }; +} + +/** + * The public interface to this module + */ +class IFMODStudioModule : public IModuleInterface +{ +public: + + /** + * Singleton-like access to this module's interface. This is just for convenience! + * Beware of calling this during the shutdown phase, though. Your module might have been unloaded already. + * + * @return Returns singleton instance, loading the module on demand if needed + */ + static inline IFMODStudioModule& Get() + { + return FModuleManager::LoadModuleChecked< IFMODStudioModule >( "FMODStudio" ); + } + + /** + * Checks to see if this module is loaded and ready. It is only valid to call Get() if IsAvailable() returns true. + * + * @return True if the module is loaded and ready to use + */ + static inline bool IsAvailable() + { + return FModuleManager::Get().IsModuleLoaded( "FMODStudio" ); + } + + /** + * Get a pointer to the runtime studio system (only valid in-game or in PIE) + */ + virtual FMOD::Studio::System* GetStudioSystem(EFMODSystemContext::Type Context) = 0; + + /** + * Set system paused (for PIE pause) + */ + virtual void SetSystemPaused(bool paused) = 0; + + /** + * Called when user changes any studio settings + */ + virtual void RefreshSettings() = 0; + + /** + * Called when we enter of leave PIE mode + */ + virtual void SetInPIE(bool bInPIE, bool bSimulating) = 0; + + /** + * Look up an asset given its name + */ + virtual UFMODAsset* FindAssetByName(const FString& Name) = 0; + + /** + * Look up an event given its name + */ + virtual UFMODEvent* FindEventByName(const FString& Name) = 0; + + /** + * Get an event description. + * The system type can control which Studio system to use, or leave it as System_Max for it to choose automatically. + */ + virtual FMOD::Studio::EventDescription* GetEventDescription(const UFMODEvent* Event, EFMODSystemContext::Type Context = EFMODSystemContext::Max) = 0; + + /** + * Create a single auditioning instance using the auditioning system + */ + virtual FMOD::Studio::EventInstance* CreateAuditioningInstance(const UFMODEvent* Event) = 0; + + /** + * Stop any auditioning instance + */ + virtual void StopAuditioningInstance() = 0; + + /** + * Return whether the listener(s) have moved + */ + virtual bool HasListenerMoved() = 0; + + /** + * Called to change the listener position for editor mode + */ + virtual void SetListenerPosition(int ListenerIndex, UWorld* World, const FTransform& ListenerTransform, float DeltaSeconds) = 0; + + /** + * Called to change the listener position for editor mode + */ + virtual void FinishSetListenerPosition(int NumListeners, float DeltaSeconds) = 0; + + /** + * Return the audio settings for the listener nearest the given location + */ + virtual const FFMODListener& GetNearestListener(const FVector& Location) = 0; + + /** This event is fired after all banks were reloaded */ + virtual FSimpleMulticastDelegate& BanksReloadedEvent() = 0; + + /** Return a list of banks that failed to load due to an error */ + virtual TArray GetFailedBankLoads(EFMODSystemContext::Type Context) = 0; + + /** Return a list of plugins that appear to be needed */ + virtual TArray GetRequiredPlugins() = 0; + + /** Register a plugin that is required */ + virtual void AddRequiredPlugin(const FString& Plugin) = 0; + + /** Returns whether sound is enabled for the game */ + virtual bool UseSound() = 0; + + /** Attempts to load a plugin by name */ + virtual bool LoadPlugin(const TCHAR* ShortName) = 0; + + /** Log a FMOD error */ + virtual void LogError(int result, const char* function) = 0; + +}; diff --git a/Plugins/FMODStudio/Source/FMODStudio/Public/FMODUtils.h b/Plugins/FMODStudio/Source/FMODStudio/Public/FMODUtils.h new file mode 100644 index 0000000..f51dd2a --- /dev/null +++ b/Plugins/FMODStudio/Source/FMODStudio/Public/FMODUtils.h @@ -0,0 +1,190 @@ +// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2016. + +#pragma once + +#include "fmod_studio.hpp" +#include "fmod.hpp" + +#include "FMODStudioModule.h" + +#define verifyfmod(fn) { FMOD_RESULT _result = (fn); if (_result != FMOD_OK) { FMODUtils::LogError(_result, #fn); } } + +namespace FMODUtils +{ + +// Unreal defines 1 unit == 1cm, so convert to metres for Studio automatically +#define FMOD_VECTOR_SCALE_DEFAULT 0.01f + +// Just call into module +inline void LogError(FMOD_RESULT result, const char* function) +{ + IFMODStudioModule::Get().LogError(result, function); +} + +inline void Assign(FMOD_VECTOR& Dest, const FVector& Src, float Scale=1.0f) +{ + Dest.x = Src.X * Scale; + Dest.y = Src.Y * Scale; + Dest.z = Src.Z * Scale; +} + +inline void Assign(FMOD_3D_ATTRIBUTES& Dest, const FTransform& Src) +{ + Assign(Dest.position, Src.GetTranslation(), FMOD_VECTOR_SCALE_DEFAULT); + Assign(Dest.forward, Src.GetUnitAxis(EAxis::X), 1.0f); + Assign(Dest.up, Src.GetUnitAxis(EAxis::Z), 1.0f); +} + +inline FMOD_VECTOR ConvertWorldVector(const FVector& Src) +{ + FMOD_VECTOR Dest; + Assign(Dest, Src, FMOD_VECTOR_SCALE_DEFAULT); + return Dest; +} + +inline FMOD_VECTOR ConvertUnitVector(const FVector& Src) +{ + FMOD_VECTOR Dest; + Dest.x = Src.X; + Dest.y = Src.Y; + Dest.z = Src.Z; + return Dest; +} + +inline float DistanceToUEScale(float FMODDistance) +{ + return FMODDistance / FMOD_VECTOR_SCALE_DEFAULT; +} + +inline bool IsWorldAudible(UWorld* World) +{ + if (GEngine && IFMODStudioModule::Get().UseSound()) + { + if (World == nullptr) + { + return true; + } + + if (World->bAllowAudioPlayback && World->GetNetMode() != NM_DedicatedServer) + { + if (World->IsGameWorld() || World->WorldType == EWorldType::Preview) + { + return true; + } + } + } + return false; +} + +inline FMOD::Studio::ID ConvertGuid(const FGuid& UnrealGuid) +{ + // Unreal doesn't follow the usual windows GUID format, instead it parses + // them as 4 integers + FMOD::Studio::ID StudioGuid; + FMemory::Memcpy(&StudioGuid, &UnrealGuid, sizeof(StudioGuid)); + Swap(StudioGuid.Data2, StudioGuid.Data3); + Swap(StudioGuid.Data4[0], StudioGuid.Data4[3]); + Swap(StudioGuid.Data4[1], StudioGuid.Data4[2]); + Swap(StudioGuid.Data4[4], StudioGuid.Data4[7]); + Swap(StudioGuid.Data4[5], StudioGuid.Data4[6]); + return StudioGuid; +} + +inline FGuid ConvertGuid(const FMOD::Studio::ID& StudioGuid) +{ + // Unreal doesn't follow the usual windows GUID format, instead it parses + // them as 4 integers + FMOD::Studio::ID CopiedGuid; + FMemory::Memcpy(&CopiedGuid, &StudioGuid, sizeof(StudioGuid)); + Swap(CopiedGuid.Data2, CopiedGuid.Data3); + Swap(CopiedGuid.Data4[0], CopiedGuid.Data4[3]); + Swap(CopiedGuid.Data4[1], CopiedGuid.Data4[2]); + Swap(CopiedGuid.Data4[4], CopiedGuid.Data4[7]); + Swap(CopiedGuid.Data4[5], CopiedGuid.Data4[6]); + FGuid UnrealGuid; + FMemory::Memcpy(&UnrealGuid, &CopiedGuid, sizeof(CopiedGuid)); + return UnrealGuid; +} + +template +inline FGuid GetID(StudioType* Instance) +{ + FMOD::Studio::ID StudioID = {0}; + verifyfmod(Instance->getID(&StudioID)); + return FMODUtils::ConvertGuid(StudioID); +} + +template +inline FString GetPath(StudioType* Instance) +{ + int ActualSize = 128; // Start with expected enough space + TArray RawBuffer; + FMOD_RESULT Result; + do + { + RawBuffer.SetNum(ActualSize); + Result = Instance->getPath(RawBuffer.GetData(), ActualSize, &ActualSize); + } + while (Result == FMOD_ERR_TRUNCATED); + + if (Result == FMOD_OK) + { + return FString(UTF8_TO_TCHAR(RawBuffer.GetData())); + } + else + { + return FString(); + } +} + +inline FString LookupNameFromGuid(FMOD::Studio::System* StudioSystem, const FMOD::Studio::ID& Guid) +{ + int ActualSize = 128; // Start with expected enough space + TArray RawBuffer; + FMOD_RESULT Result; + do + { + RawBuffer.SetNum(ActualSize); + Result = StudioSystem->lookupPath(&Guid, RawBuffer.GetData(), ActualSize, &ActualSize); + } + while (Result == FMOD_ERR_TRUNCATED); + + if (Result == FMOD_OK) + { + return FString(UTF8_TO_TCHAR(RawBuffer.GetData())); + } + else + { + return FString(); + } +} + +inline FString LookupNameFromGuid(FMOD::Studio::System* StudioSystem, const FGuid& Guid) +{ + return LookupNameFromGuid(StudioSystem, ConvertGuid(Guid)); +} + +inline FString ParameterTypeToString(FMOD_STUDIO_PARAMETER_TYPE Type) +{ + switch (Type) + { + case FMOD_STUDIO_PARAMETER_GAME_CONTROLLED: + return FString("Game Controlled"); + case FMOD_STUDIO_PARAMETER_AUTOMATIC_DISTANCE: + return FString("Distance (Auto)"); + case FMOD_STUDIO_PARAMETER_AUTOMATIC_EVENT_CONE_ANGLE: + return FString("Event Cone Angle (Auto)"); + case FMOD_STUDIO_PARAMETER_AUTOMATIC_EVENT_ORIENTATION: + return FString("Event Orientation (Auto)"); + case FMOD_STUDIO_PARAMETER_AUTOMATIC_DIRECTION: + return FString("Direction (Auto)"); + case FMOD_STUDIO_PARAMETER_AUTOMATIC_ELEVATION: + return FString("Elevation (Auto)"); + case FMOD_STUDIO_PARAMETER_AUTOMATIC_LISTENER_ORIENTATION: + return FString("Listener Orientation (Auto)"); + } + + return FString(); +} + +} \ No newline at end of file diff --git a/Plugins/FMODStudio/Source/FMODStudioEditor/Classes/FMODAmbientSoundActorFactory.h b/Plugins/FMODStudio/Source/FMODStudioEditor/Classes/FMODAmbientSoundActorFactory.h new file mode 100644 index 0000000..9d168e1 --- /dev/null +++ b/Plugins/FMODStudio/Source/FMODStudioEditor/Classes/FMODAmbientSoundActorFactory.h @@ -0,0 +1,23 @@ +// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2016. + +#pragma once + +#include "FMODAmbientSoundActorFactory.generated.h" + +/** FMOD Ambient Sound Actor Factory. +*/ +UCLASS(MinimalAPI, config=Editor, collapsecategories, hidecategories=Object) +class UFMODAmbientSoundActorFactory : public UActorFactory +{ + GENERATED_UCLASS_BODY() + + // Begin UActorFactory Interface + virtual void PostSpawnActor( UObject* Asset, AActor* NewActor ) override; + virtual void PostCreateBlueprint( UObject* Asset, AActor* CDO ) override; + virtual bool CanCreateActorFrom( const FAssetData& AssetData, FText& OutErrorMsg ) override; + virtual UObject* GetAssetFromActorInstance(AActor* ActorInstance) override; + // End UActorFactory Interface +}; + + + diff --git a/Plugins/FMODStudio/Source/FMODStudioEditor/FMODStudioEditor.Build.cs b/Plugins/FMODStudio/Source/FMODStudioEditor/FMODStudioEditor.Build.cs new file mode 100644 index 0000000..c420055 --- /dev/null +++ b/Plugins/FMODStudio/Source/FMODStudioEditor/FMODStudioEditor.Build.cs @@ -0,0 +1,58 @@ +// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2016. + +namespace UnrealBuildTool.Rules +{ + public class FMODStudioEditor : ModuleRules + { + public FMODStudioEditor(TargetInfo Target) + { + bFasterWithoutUnity = true; + + PublicIncludePaths.AddRange( + new string[] { + } + ); + + PrivateIncludePaths.AddRange( + new string[] { + "FMODStudioEditor/Private", + "FMODStudio/Private", + } + ); + + PublicDependencyModuleNames.AddRange( + new string[] + { + "Core", + "CoreUObject", + "Engine", + "FMODStudio" + } + ); + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "UnrealEd", + "Slate", + "SlateCore", + "InputCore", + "Settings", + "EditorStyle", + "LevelEditor", + "AssetTools", + "AssetRegistry", + "PropertyEditor", + "WorkspaceMenuStructure", + "Sockets" + } + ); + + DynamicallyLoadedModuleNames.AddRange( + new string[] + { + } + ); + } + } +} \ No newline at end of file diff --git a/Plugins/FMODStudio/Source/FMODStudioEditor/Private/AssetTypeActions_FMODEvent.cpp b/Plugins/FMODStudio/Source/FMODStudioEditor/Private/AssetTypeActions_FMODEvent.cpp new file mode 100644 index 0000000..43614fb --- /dev/null +++ b/Plugins/FMODStudio/Source/FMODStudioEditor/Private/AssetTypeActions_FMODEvent.cpp @@ -0,0 +1,152 @@ +// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2016. + +#include "FMODStudioEditorPrivatePCH.h" +#include "AssetTypeActions_Base.h" +#include "AssetTypeActions_FMODEvent.h" +#include "FMODEventEditor.h" +#include "FMODEvent.h" +#include "FMODUtils.h" +#include "FMODStudioModule.h" +#include "FMODStudioEditorModule.h" + +#define LOCTEXT_NAMESPACE "AssetTypeActions" + +FAssetTypeActions_FMODEvent::FAssetTypeActions_FMODEvent() + : CurrentPreviewEventInstance(nullptr) +{ + BeginPIEDelegateHandle = FEditorDelegates::BeginPIE.AddRaw(this, &FAssetTypeActions_FMODEvent::HandleBeginPIE); + IFMODStudioModule::Get().BanksReloadedEvent().AddRaw(this, &FAssetTypeActions_FMODEvent::HandleBanksReloaded); +} + +FAssetTypeActions_FMODEvent::~FAssetTypeActions_FMODEvent() +{ + FEditorDelegates::BeginPIE.Remove(BeginPIEDelegateHandle); + IFMODStudioModule::Get().BanksReloadedEvent().RemoveAll(this); + IFMODStudioModule::Get().StopAuditioningInstance(); +} + +UClass* FAssetTypeActions_FMODEvent::GetSupportedClass() const +{ + return UFMODEvent::StaticClass(); +} + +void FAssetTypeActions_FMODEvent::GetActions(const TArray& InObjects, FMenuBuilder& MenuBuilder) +{ + auto Events = GetTypedWeakObjectPtrs(InObjects); + + + MenuBuilder.AddMenuEntry( + LOCTEXT("FMODEvent_Play", "Play"), + LOCTEXT("FMODEvent_PlayTooltip", "Plays the selected FMOD event."), + FSlateIcon(FEditorStyle::GetStyleSetName(), "MediaAsset.AssetActions.Play"), + FUIAction( + FExecuteAction::CreateSP(this, &FAssetTypeActions_FMODEvent::ExecutePlay, Events), + FCanExecuteAction::CreateSP(this, &FAssetTypeActions_FMODEvent::CanExecutePlayCommand, Events) + ) + ); + + MenuBuilder.AddMenuEntry( + LOCTEXT("FMODEvent_Stop", "Stop"), + LOCTEXT("FMODEvent_StopTooltip", "Stops the currently playing FMOD event."), + FSlateIcon(FEditorStyle::GetStyleSetName(), "MediaAsset.AssetActions.Stop"), + FUIAction( + FExecuteAction::CreateSP(this, &FAssetTypeActions_FMODEvent::ExecuteStop, Events), + FCanExecuteAction() + ) + ); +} + +void FAssetTypeActions_FMODEvent::OpenAssetEditor(const TArray& InObjects, TSharedPtr EditWithinLevelEditor) +{ + EToolkitMode::Type Mode = EditWithinLevelEditor.IsValid() ? EToolkitMode::WorldCentric : EToolkitMode::Standalone; + + for (auto ObjIt = InObjects.CreateConstIterator(); ObjIt; ++ObjIt) + { + auto Event = Cast(*ObjIt); + if (Event != nullptr) + { + TSharedRef NewFMODEventEditor(new FFMODEventEditor()); + NewFMODEventEditor->InitFMODEventEditor(Mode, EditWithinLevelEditor, Event); + } + } +} + +bool FAssetTypeActions_FMODEvent::CanExecutePlayCommand(TArray> Objects) const +{ + return Objects.Num() == 1; +} + +void FAssetTypeActions_FMODEvent::AssetsActivated(const TArray& InObjects, EAssetTypeActivationMethod::Type ActivationType) +{ + if (ActivationType == EAssetTypeActivationMethod::Previewed) + { + for (auto ObjIt = InObjects.CreateConstIterator(); ObjIt; ++ObjIt) + { + UFMODEvent* Event = Cast(*ObjIt); + if (Event != nullptr) + { + // Only play the first valid event + PlayEvent(Event); + break; + } + } + } + else + { + FAssetTypeActions_Base::AssetsActivated(InObjects, ActivationType); + } +} + +void FAssetTypeActions_FMODEvent::ExecuteEdit(TArray> Objects) +{ + for (auto ObjIt = Objects.CreateConstIterator(); ObjIt; ++ObjIt) + { + auto Object = (*ObjIt).Get(); + if (Object != nullptr) + { + FAssetEditorManager::Get().OpenEditorForAsset(Object); + } + } +} + +void FAssetTypeActions_FMODEvent::ExecutePlay(TArray> Objects) +{ + for (auto ObjIt = Objects.CreateConstIterator(); ObjIt; ++ObjIt) + { + UFMODEvent* Event = (*ObjIt).Get(); + if (Event != nullptr) + { + // Only play the first valid event + PlayEvent(Event); + break; + } + } +} + +void FAssetTypeActions_FMODEvent::ExecuteStop(TArray> Objects) +{ + IFMODStudioModule::Get().StopAuditioningInstance(); +} + +void FAssetTypeActions_FMODEvent::PlayEvent(UFMODEvent* Event) +{ + CurrentPreviewEventInstance = IFMODStudioModule::Get().CreateAuditioningInstance(Event); + if (CurrentPreviewEventInstance != nullptr) + { + CurrentPreviewEventInstance->start(); + } +} + +void FAssetTypeActions_FMODEvent::HandleBeginPIE(bool bSimulating) +{ + // Studio module will handle its own auditioning, just clear the handle + CurrentPreviewEventInstance = nullptr; +} + +void FAssetTypeActions_FMODEvent::HandleBanksReloaded() +{ + // Studio module will handle its own auditioning, just clear the handle + CurrentPreviewEventInstance = nullptr; +} + +#undef LOCTEXT_NAMESPACE diff --git a/Plugins/FMODStudio/Source/FMODStudioEditor/Private/AssetTypeActions_FMODEvent.h b/Plugins/FMODStudio/Source/FMODStudioEditor/Private/AssetTypeActions_FMODEvent.h new file mode 100644 index 0000000..2319ef4 --- /dev/null +++ b/Plugins/FMODStudio/Source/FMODStudioEditor/Private/AssetTypeActions_FMODEvent.h @@ -0,0 +1,55 @@ +// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2016. + +#pragma once + +#include "AssetTypeActions_Base.h" + +namespace FMOD +{ + namespace Studio + { + class EventInstance; + } +} + +class UFMODEvent; + +class FAssetTypeActions_FMODEvent : public FAssetTypeActions_Base +{ +public: + FAssetTypeActions_FMODEvent(); + ~FAssetTypeActions_FMODEvent(); + + // IAssetTypeActions Implementation + virtual FText GetName() const override { return NSLOCTEXT("AssetTypeActions", "AssetTypeActions_FMODEvent", "FMOD Event"); } + virtual FColor GetTypeColor() const override { return FColor(0, 175, 255); } + virtual UClass* GetSupportedClass() const override; + virtual bool HasActions(const TArray& InObjects) const override { return true; } + virtual void GetActions(const TArray& InObjects, FMenuBuilder& MenuBuilder) override; + virtual void AssetsActivated(const TArray& InObjects, EAssetTypeActivationMethod::Type ActivationType) override; + virtual void OpenAssetEditor(const TArray& InObjects, TSharedPtr EditWithinLevelEditor = TSharedPtr()) override; + virtual bool CanFilter() override { return false; } + virtual uint32 GetCategories() override { return EAssetTypeCategories::Sounds; } + +private: + /** Returns true if only one event is selected to play */ + bool CanExecutePlayCommand(TArray> Objects) const; + + /** Handler for when Edit is selected */ + void ExecuteEdit(TArray> Objects); + + /** Handler for when Play is selected */ + void ExecutePlay(TArray> Objects); + + /** Handler for when Stop is selected */ + void ExecuteStop(TArray> Objects); + + /** Plays the event */ + void PlayEvent(UFMODEvent* Event); + + void HandleBeginPIE(bool bSimulating); + void HandleBanksReloaded(); + + FMOD::Studio::EventInstance* CurrentPreviewEventInstance; + FDelegateHandle BeginPIEDelegateHandle; +}; diff --git a/Plugins/FMODStudio/Source/FMODStudioEditor/Private/FMODAmbientSoundActorFactory.cpp b/Plugins/FMODStudio/Source/FMODStudioEditor/Private/FMODAmbientSoundActorFactory.cpp new file mode 100644 index 0000000..7efb35e --- /dev/null +++ b/Plugins/FMODStudio/Source/FMODStudioEditor/Private/FMODAmbientSoundActorFactory.cpp @@ -0,0 +1,66 @@ +// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2016. + +#include "FMODStudioEditorPrivatePCH.h" +#include "AssetData.h" +#include "FMODAmbientSoundActorFactory.h" +#include "FMODAmbientSound.h" +#include "FMODEvent.h" + +UFMODAmbientSoundActorFactory::UFMODAmbientSoundActorFactory(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + DisplayName = NSLOCTEXT("FMOD", "FMODAmbientSoundDisplayName", "FMOD Ambient Sound"); + NewActorClass = AFMODAmbientSound::StaticClass(); +} + +bool UFMODAmbientSoundActorFactory::CanCreateActorFrom( const FAssetData& AssetData, FText& OutErrorMsg ) +{ + //We allow creating AAmbientSounds without an existing sound asset + if ( UActorFactory::CanCreateActorFrom( AssetData, OutErrorMsg ) ) + { + return true; + } + + if ( AssetData.IsValid() && !AssetData.GetClass()->IsChildOf( UFMODEvent::StaticClass() ) ) + { + OutErrorMsg = NSLOCTEXT("FMOD", "CanCreateActorFrom_NoFMODEventAsset", "A valid FMOD Event asset must be specified."); + return false; + } + + return true; +} + +void UFMODAmbientSoundActorFactory::PostSpawnActor( UObject* Asset, AActor* NewActor) +{ + UFMODEvent* Event = Cast( Asset ); + + if ( Event != NULL ) + { + AFMODAmbientSound* NewSound = CastChecked( NewActor ); + FActorLabelUtilities::SetActorLabelUnique(NewSound, Event->GetName()); + NewSound->AudioComponent->Event = Event; + } +} + +UObject* UFMODAmbientSoundActorFactory::GetAssetFromActorInstance(AActor* Instance) +{ + check(Instance->IsA(NewActorClass)); + AFMODAmbientSound* SoundActor = CastChecked(Instance); + + check(SoundActor->AudioComponent); + return SoundActor->AudioComponent->Event.Get(); +} + +void UFMODAmbientSoundActorFactory::PostCreateBlueprint( UObject* Asset, AActor* CDO ) +{ + if (Asset != NULL && CDO != NULL) + { + UFMODEvent* Event = Cast(Asset); + + if (Event != NULL) + { + AFMODAmbientSound* NewSound = CastChecked(CDO); + NewSound->AudioComponent->Event = Event; + } + } +} \ No newline at end of file diff --git a/Plugins/FMODStudio/Source/FMODStudioEditor/Private/FMODAmbientSoundDetails.cpp b/Plugins/FMODStudio/Source/FMODStudioEditor/Private/FMODAmbientSoundDetails.cpp new file mode 100644 index 0000000..2379ece --- /dev/null +++ b/Plugins/FMODStudio/Source/FMODStudioEditor/Private/FMODAmbientSoundDetails.cpp @@ -0,0 +1,119 @@ +// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2016. + +#include "FMODStudioEditorPrivatePCH.h" +#include "FMODAmbientSoundDetails.h" +#include "Toolkits/AssetEditorManager.h" +#include "FMODAmbientSound.h" +#include "FMODStudioModule.h" +#include "FMODEvent.h" +#include "fmod_studio.hpp" + +#define LOCTEXT_NAMESPACE "FMODStudio" + +TSharedRef FFMODAmbientSoundDetails::MakeInstance() +{ + return MakeShareable( new FFMODAmbientSoundDetails ); +} + +void FFMODAmbientSoundDetails::CustomizeDetails( IDetailLayoutBuilder& DetailBuilder ) +{ + const TArray< TWeakObjectPtr >& SelectedObjects = DetailBuilder.GetDetailsView().GetSelectedObjects(); + + for( int32 ObjectIndex = 0; !AmbientSound.IsValid() && ObjectIndex < SelectedObjects.Num(); ++ObjectIndex ) + { + const TWeakObjectPtr& CurrentObject = SelectedObjects[ObjectIndex]; + if ( CurrentObject.IsValid() ) + { + AmbientSound = Cast(CurrentObject.Get()); + } + } + + DetailBuilder.EditCategory(TEXT("Sound")) + .AddCustomRow(FText::GetEmpty()) + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .Padding( 0, 2.0f, 0, 0 ) + .FillHeight(1.0f) + .VAlign( VAlign_Center ) + [ + SNew(SHorizontalBox) + +SHorizontalBox::Slot() + .AutoWidth() + .Padding( 2.0f, 0.0f ) + .VAlign(VAlign_Center) + .HAlign(HAlign_Left) + [ + SNew(SButton) + .VAlign(VAlign_Center) + .OnClicked( this, &FFMODAmbientSoundDetails::OnEditSoundClicked ) + .Text( LOCTEXT("EditAsset", "Edit") ) + .ToolTipText( LOCTEXT("EditAssetToolTip", "Edit this sound cue") ) + ] + +SHorizontalBox::Slot() + .AutoWidth() + .Padding( 2.0f, 0.0f ) + .VAlign(VAlign_Center) + .HAlign(HAlign_Left) + [ + SNew(SButton) + .VAlign(VAlign_Center) + .OnClicked( this, &FFMODAmbientSoundDetails::OnPlaySoundClicked ) + .Text( LOCTEXT("PlaySoundCue", "Play") ) + ] + +SHorizontalBox::Slot() + .AutoWidth() + .Padding( 2.0f, 0.0f ) + .VAlign(VAlign_Center) + .HAlign(HAlign_Left) + [ + SNew(SButton) + .VAlign(VAlign_Center) + .OnClicked( this, &FFMODAmbientSoundDetails::OnStopSoundClicked ) + .Text( LOCTEXT("StopSoundCue", "Stop") ) + ] + ] + ]; +} + + +FReply FFMODAmbientSoundDetails::OnEditSoundClicked() +{ + if( AmbientSound.IsValid() ) + { + UFMODEvent* Event = AmbientSound.Get()->AudioComponent->Event.Get(); + if (Event) + { + FAssetEditorManager::Get().OpenEditorForAsset(Event); + } + } + + return FReply::Handled(); +} + +FReply FFMODAmbientSoundDetails::OnPlaySoundClicked() +{ + if( AmbientSound.IsValid() ) + { + UFMODEvent* Event = AmbientSound.Get()->AudioComponent->Event.Get(); + if (Event) + { + FMOD::Studio::EventInstance* Instance = IFMODStudioModule::Get().CreateAuditioningInstance(Event); + if (Instance) + { + Instance->start(); + } + } + } + + return FReply::Handled(); +} + +FReply FFMODAmbientSoundDetails::OnStopSoundClicked() +{ + IFMODStudioModule::Get().StopAuditioningInstance(); + + return FReply::Handled(); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Plugins/FMODStudio/Source/FMODStudioEditor/Private/FMODAmbientSoundDetails.h b/Plugins/FMODStudio/Source/FMODStudioEditor/Private/FMODAmbientSoundDetails.h new file mode 100644 index 0000000..2a21e62 --- /dev/null +++ b/Plugins/FMODStudio/Source/FMODStudioEditor/Private/FMODAmbientSoundDetails.h @@ -0,0 +1,23 @@ +// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2016. + +#pragma once + +#include "PropertyEditing.h" +#include "PropertyCustomizationHelpers.h" + +class FFMODAmbientSoundDetails : public IDetailCustomization +{ +public: + /** Makes a new instance of this detail layout class for a specific detail view requesting it */ + static TSharedRef MakeInstance(); + +private: + /** IDetailCustomization interface */ + virtual void CustomizeDetails( IDetailLayoutBuilder& DetailBuilder ) override; + + FReply OnEditSoundClicked(); + FReply OnPlaySoundClicked(); + FReply OnStopSoundClicked(); + + TWeakObjectPtr AmbientSound; +}; \ No newline at end of file diff --git a/Plugins/FMODStudio/Source/FMODStudioEditor/Private/FMODAssetBroker.h b/Plugins/FMODStudio/Source/FMODStudioEditor/Private/FMODAssetBroker.h new file mode 100644 index 0000000..09ff26f --- /dev/null +++ b/Plugins/FMODStudio/Source/FMODStudioEditor/Private/FMODAssetBroker.h @@ -0,0 +1,43 @@ +// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2016. + +#pragma once + +#include "ComponentAssetBroker.h" +#include "FMODEvent.h" + +////////////////////////////////////////////////////////////////////////// +// FFMODAssetBroker + +class FFMODAssetBroker : public IComponentAssetBroker +{ +public: + UClass* GetSupportedAssetClass() override + { + return UFMODEvent::StaticClass(); + } + + virtual bool AssignAssetToComponent(UActorComponent* InComponent, UObject* InAsset) override + { + if (UFMODAudioComponent* AudioComp = Cast(InComponent)) + { + UFMODEvent* Event = Cast(InAsset); + + if ((Event != NULL) || (InAsset == NULL)) + { + AudioComp->Event = Event; + return true; + } + } + return false; + } + + virtual UObject* GetAssetFromComponent(UActorComponent* InComponent) override + { + if (UFMODAudioComponent* AudioComp = Cast(InComponent)) + { + return AudioComp->Event.Get(); + } + return NULL; + } +}; + diff --git a/Plugins/FMODStudio/Source/FMODStudioEditor/Private/FMODAudioComponentVisualizer.cpp b/Plugins/FMODStudio/Source/FMODStudioEditor/Private/FMODAudioComponentVisualizer.cpp new file mode 100644 index 0000000..5e96678 --- /dev/null +++ b/Plugins/FMODStudio/Source/FMODStudioEditor/Private/FMODAudioComponentVisualizer.cpp @@ -0,0 +1,45 @@ +// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2016. + +#include "FMODStudioEditorPrivatePCH.h" +#include "FMODAudioComponentVisualizer.h" +#include "FMODAudioComponent.h" +#include "FMODUtils.h" +#include "FMODEvent.h" +#include "fmod_studio.hpp" + +void FFMODAudioComponentVisualizer::DrawVisualization(const UActorComponent* Component, const FSceneView* View, FPrimitiveDrawInterface* PDI) +{ + if (View->Family->EngineShowFlags.AudioRadius) + { + const UFMODAudioComponent* AudioComp = Cast(Component); + if (AudioComp != nullptr && AudioComp->Event.IsValid()) + { + FMOD::Studio::EventDescription* EventDesc = IFMODStudioModule::Get().GetEventDescription(AudioComp->Event.Get(), EFMODSystemContext::Auditioning); + if (EventDesc != nullptr) + { + bool bIs3D = false; + EventDesc->is3D(&bIs3D); + if (bIs3D) + { + const FColor AudioOuterRadiusColor(255, 153, 0); + const FColor AudioInnerRadiusColor(216, 130, 0); + + const FTransform& Transform = AudioComp->ComponentToWorld; + + float MinDistance = 0.0f; + float MaxDistance = 0.0f; + EventDesc->getMinimumDistance(&MinDistance); + EventDesc->getMaximumDistance(&MaxDistance); + MinDistance = FMODUtils::DistanceToUEScale(MinDistance); + MaxDistance = FMODUtils::DistanceToUEScale(MaxDistance); + + DrawWireSphereAutoSides(PDI, Transform.GetTranslation(), AudioOuterRadiusColor, MinDistance, SDPG_World); + if (MaxDistance != MinDistance) + { + DrawWireSphereAutoSides(PDI, Transform.GetTranslation(), AudioInnerRadiusColor, MaxDistance, SDPG_World); + } + } + } + } + } +} diff --git a/Plugins/FMODStudio/Source/FMODStudioEditor/Private/FMODAudioComponentVisualizer.h b/Plugins/FMODStudio/Source/FMODStudioEditor/Private/FMODAudioComponentVisualizer.h new file mode 100644 index 0000000..c3dd007 --- /dev/null +++ b/Plugins/FMODStudio/Source/FMODStudioEditor/Private/FMODAudioComponentVisualizer.h @@ -0,0 +1,13 @@ +// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2016. + +#pragma once + +#include "ComponentVisualizer.h" + +class FFMODAudioComponentVisualizer : public FComponentVisualizer +{ +public: + // Begin FComponentVisualizer interface + virtual void DrawVisualization(const UActorComponent* Component, const FSceneView* View, FPrimitiveDrawInterface* PDI) override; + // End FComponentVisualizer interface +}; diff --git a/Plugins/FMODStudio/Source/FMODStudioEditor/Private/FMODEventEditor.cpp b/Plugins/FMODStudio/Source/FMODStudioEditor/Private/FMODEventEditor.cpp new file mode 100644 index 0000000..d8006c7 --- /dev/null +++ b/Plugins/FMODStudio/Source/FMODStudioEditor/Private/FMODEventEditor.cpp @@ -0,0 +1,187 @@ +// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2016. + +#include "FMODStudioEditorPrivatePCH.h" +#include "FMODEventEditor.h" +#include "FMODEvent.h" +#include "FMODStudioModule.h" +#include "FMODUtils.h" +#include "SFMODEventEditorPanel.h" +#include "SDockTab.h" +//#include "WorkspaceMenuStructureModule.h" +#include "fmod_studio.hpp" + +#define LOCTEXT_NAMESPACE "FMODEventEditor" + +DEFINE_LOG_CATEGORY_STATIC(LogFMODEventEditor, Log, All); + +const FName FFMODEventEditor::EventEditorTabId(TEXT("FFMODEventEditor_EventView")); +const FName FFMODEventEditor::FMODEventEditorAppIdentifier(TEXT("FMODEventEditorApp")); + +void FFMODEventEditor::RegisterTabSpawners(const TSharedRef& TabManager) +{ + WorkspaceMenuCategory = TabManager->AddLocalWorkspaceMenuCategory(LOCTEXT("WorkspaceMenu_FMODEventEditor", "FMOD Event Editor")); + auto WorkspaceMenuCategoryRef = WorkspaceMenuCategory.ToSharedRef(); + + FAssetEditorToolkit::RegisterTabSpawners(TabManager); + + TabManager->RegisterTabSpawner( + EventEditorTabId, + FOnSpawnTab::CreateSP(this, &FFMODEventEditor::SpawnTab_EventEditor)) + .SetDisplayName(LOCTEXT("EventTab", "FMOD Event")) + .SetGroup(WorkspaceMenuCategoryRef); +} + +void FFMODEventEditor::UnregisterTabSpawners(const TSharedRef& TabManager) +{ + FAssetEditorToolkit::UnregisterTabSpawners(TabManager); + + TabManager->UnregisterTabSpawner(EventEditorTabId); +} + +FFMODEventEditor::FFMODEventEditor() + : CurrentPreviewEventInstance(nullptr) +{ + IFMODStudioModule::Get().BanksReloadedEvent().AddRaw(this, &FFMODEventEditor::HandleBanksReloaded); + BeginPIEDelegateHandle = FEditorDelegates::BeginPIE.AddRaw(this, &FFMODEventEditor::HandleBeginPIE); +} + +FFMODEventEditor::~FFMODEventEditor() +{ + IFMODStudioModule::Get().BanksReloadedEvent().RemoveAll(this); + FEditorDelegates::BeginPIE.Remove(BeginPIEDelegateHandle); + + CurrentPreviewEventInstance = nullptr; +} + +UFMODEvent* FFMODEventEditor::GetEditedEvent() const +{ + return EditedEvent; +} + +FMOD::Studio::EventDescription* FFMODEventEditor::GetEventDescription() const +{ + return IFMODStudioModule::Get().GetEventDescription(EditedEvent, EFMODSystemContext::Auditioning); +} + +void FFMODEventEditor::PlayEvent() +{ + CurrentPreviewEventInstance = IFMODStudioModule::Get().CreateAuditioningInstance(EditedEvent); + if (CurrentPreviewEventInstance != nullptr) + { + for (int32 ParamIdx = 0; ParamIdx < ParameterValues.Num(); ParamIdx++) + { + CurrentPreviewEventInstance->setParameterValueByIndex(ParamIdx, ParameterValues[ParamIdx]); + } + + CurrentPreviewEventInstance->start(); + } +} + +void FFMODEventEditor::PauseEvent() +{ + if (CurrentPreviewEventInstance != nullptr) + { + bool bIsPaused = false; + CurrentPreviewEventInstance->getPaused(&bIsPaused); + CurrentPreviewEventInstance->setPaused(!bIsPaused); + } +} + +void FFMODEventEditor::StopEvent() +{ + IFMODStudioModule::Get().StopAuditioningInstance(); +} + +void FFMODEventEditor::SetParameterValue(int32 ParameterIdx, float Value) +{ + ParameterValues[ParameterIdx] = Value; + + if (CurrentPreviewEventInstance != nullptr) + { + CurrentPreviewEventInstance->setParameterValueByIndex(ParameterIdx, Value); + } +} + +TArray& FFMODEventEditor::GetParameterValues() +{ + return ParameterValues; +} + +void FFMODEventEditor::InitFMODEventEditor(const EToolkitMode::Type Mode, const TSharedPtr< class IToolkitHost >& InitToolkitHost, UFMODEvent* Event) +{ + EditedEvent = Event; + + TSharedRef StandaloneDefaultLayout = FTabManager::NewLayout("Standalone_FMODEventEditor_Layout") + ->AddArea + ( + FTabManager::NewPrimaryArea()->SetOrientation(Orient_Vertical) + ->Split + ( + FTabManager::NewStack() + ->AddTab(EventEditorTabId, ETabState::OpenedTab)->SetHideTabWell(true) + ) + ); + + const bool bCreateDefaultStandaloneMenu = true; + const bool bCreateDefaultToolbar = false; + FAssetEditorToolkit::InitAssetEditor(Mode, InitToolkitHost, FFMODEventEditor::FMODEventEditorAppIdentifier, StandaloneDefaultLayout, bCreateDefaultStandaloneMenu, bCreateDefaultToolbar, Event); +} + +FName FFMODEventEditor::GetToolkitFName() const +{ + return FName("FMODEventEditor"); +} + +FText FFMODEventEditor::GetBaseToolkitName() const +{ + return LOCTEXT("ToolkitName", "FMOD Event Editor"); +} + +FString FFMODEventEditor::GetWorldCentricTabPrefix() const +{ + return LOCTEXT("WorldCentricTabPrefix", "FMOD Event ").ToString(); +} + +FLinearColor FFMODEventEditor::GetWorldCentricTabColorScale() const +{ + return FLinearColor(0.0f, 0.0f, 0.5f, 0.5f); +} + +void FFMODEventEditor::CreateInternalWidgets() +{ + FMODEventEditorPanel = SNew(SFMODEventEditorPanel) + .FMODEventEditor(SharedThis(this)); +} + +TSharedRef FFMODEventEditor::SpawnTab_EventEditor(const FSpawnTabArgs& Args) +{ + check(Args.GetTabId().TabType == EventEditorTabId); + + CreateInternalWidgets(); + + return SAssignNew(OwnerTab, SDockTab) + .Label(LOCTEXT("EventEditorTitle", "FMOD Event")) + .TabColorScale(GetTabColorScale()) + [ + FMODEventEditorPanel.ToSharedRef() + ]; +} + +void FFMODEventEditor::HandleBanksReloaded() +{ + CurrentPreviewEventInstance = nullptr; + + CreateInternalWidgets(); + + if (OwnerTab.IsValid()) + { + OwnerTab->SetContent(FMODEventEditorPanel.ToSharedRef()); + } +} + +void FFMODEventEditor::HandleBeginPIE(bool bSimulating) +{ + CurrentPreviewEventInstance = nullptr; +} + +#undef LOCTEXT_NAMESPACE diff --git a/Plugins/FMODStudio/Source/FMODStudioEditor/Private/FMODEventEditor.h b/Plugins/FMODStudio/Source/FMODStudioEditor/Private/FMODEventEditor.h new file mode 100644 index 0000000..122b594 --- /dev/null +++ b/Plugins/FMODStudio/Source/FMODStudioEditor/Private/FMODEventEditor.h @@ -0,0 +1,80 @@ +// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2016. + +#pragma once + +#include "Toolkits/AssetEditorToolkit.h" +#include "fmod_studio_common.h" + +namespace FMOD +{ + namespace Studio + { + class EventDescription; + class EventInstance; + } +} + +class FFMODEventEditor : public FAssetEditorToolkit +{ +public: + virtual void RegisterTabSpawners(const TSharedRef& TabManager) override; + virtual void UnregisterTabSpawners(const TSharedRef& TabManager) override; + + /** + * Edits the specified event + * + * @param Mode Asset editing mode for this editor (standalone or world-centric) + * @param InitToolkitHost When Mode is WorldCentric, this is the level editor instance to spawn this editor within + * @param Event The event to edit + */ + void InitFMODEventEditor(const EToolkitMode::Type Mode, const TSharedPtr< class IToolkitHost >& InitToolkitHost, class UFMODEvent* Event); + + /** Constructor */ + FFMODEventEditor(); + + /** Destructor */ + virtual ~FFMODEventEditor(); + + UFMODEvent* GetEditedEvent() const; + FMOD::Studio::EventDescription* GetEventDescription() const; + void PlayEvent(); + void PauseEvent(); + void StopEvent(); + void SetParameterValue(int32 ParameterIdx, float Value); + TArray& GetParameterValues(); + + /** IToolkit interface */ + virtual FName GetToolkitFName() const override; + virtual FText GetBaseToolkitName() const override; + virtual FString GetWorldCentricTabPrefix() const override; + virtual FLinearColor GetWorldCentricTabColorScale() const override; + + TArray ParameterValues; + +private: + + FMOD::Studio::EventInstance* CurrentPreviewEventInstance; + + void HandlePreBanksReloaded(); + void HandleBanksReloaded(); + void HandleBeginPIE(bool bSimulating); + + /** Creates all internal widgets for the tabs to point at */ + void CreateInternalWidgets(); + + /** Spawns the tab with the FMOD event inside */ + TSharedRef SpawnTab_EventEditor(const FSpawnTabArgs& Args); + + TSharedPtr FMODEventEditorPanel; + TSharedPtr OwnerTab; + + /** The tab id for the event editor tab */ + static const FName EventEditorTabId; + + /** FMOD event editor app identifier string */ + static const FName FMODEventEditorAppIdentifier; + + class UFMODEvent* EditedEvent; + + FDelegateHandle BeginPIEDelegateHandle; +}; diff --git a/Plugins/FMODStudio/Source/FMODStudioEditor/Private/FMODStudioEditorModule.cpp b/Plugins/FMODStudio/Source/FMODStudioEditor/Private/FMODStudioEditorModule.cpp new file mode 100644 index 0000000..fc1d525 --- /dev/null +++ b/Plugins/FMODStudio/Source/FMODStudioEditor/Private/FMODStudioEditorModule.cpp @@ -0,0 +1,872 @@ +// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2016. + +#include "FMODStudioEditorPrivatePCH.h" + +#include "FMODStudioEditorModule.h" +#include "FMODStudioModule.h" +#include "FMODStudioStyle.h" +#include "FMODAudioComponent.h" +#include "FMODAssetBroker.h" +#include "FMODSettings.h" +#include "FMODUtils.h" + +#include "FMODEventEditor.h" +#include "FMODAudioComponentVisualizer.h" +#include "FMODAmbientSoundDetails.h" + +#include "SlateBasics.h" +#include "AssetTypeActions_FMODEvent.h" +#include "NotificationManager.h" +#include "SNotificationList.h" +#include "ISettingsModule.h" +#include "ISettingsSection.h" +#include "Editor.h" +#include "SceneViewport.h" +#include "LevelEditor.h" +#include "SocketSubsystem.h" +#include "Sockets.h" +#include "IPAddress.h" + +#include "fmod_studio.hpp" + +#define LOCTEXT_NAMESPACE "FMODStudio" + +DEFINE_LOG_CATEGORY(LogFMOD); + +class FFMODStudioLink +{ +public: + FFMODStudioLink() + : SocketSubsystem(nullptr), + Socket(nullptr) + { + SocketSubsystem = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM); + } + + ~FFMODStudioLink() + { + Disconnect(); + } + + bool Connect() + { + if (!SocketSubsystem) return false; + + Disconnect(); + Socket = SocketSubsystem->CreateSocket(NAME_Stream, TEXT("FMOD Studio Connection"), false); + + TSharedRef Addr = SocketSubsystem->CreateInternetAddr(); + bool Valid = false; + Addr->SetIp(TEXT("127.0.0.1"), Valid); + if (!Valid) return false; + + Addr->SetPort(3663); + return Socket->Connect(*Addr); + } + + void Disconnect() + { + if (SocketSubsystem && Socket) + { + SocketSubsystem->DestroySocket(Socket); + Socket = nullptr; + } + } + + bool Execute(const TCHAR* Message, FString& OutMessage) + { + OutMessage = TEXT(""); + if (!Socket) + { + return false; + } + + UE_LOG(LogFMOD, Log, TEXT("Sent studio message: %s"), Message); + + FTCHARToUTF8 MessageChars(Message); + int32 BytesSent = 0; + if (!Socket->Send((const uint8*)MessageChars.Get(), MessageChars.Length(), BytesSent)) + { + return false; + } + + while (1) + { + FString BackMessage; + if (!ReadMessage(BackMessage)) + { + return false; + } + UE_LOG(LogFMOD, Log, TEXT("Received studio message: %s"), *BackMessage); + if (BackMessage.StartsWith(TEXT("out(): "))) + { + OutMessage = BackMessage.Mid(7).TrimTrailing(); + break; + } + else + { + // Keep going + } + } + return true; + } +private: + bool ReadMessage(FString& OutMessage) + { + while (1) + { + for (int32 i=0; iWait(ESocketWaitConditions::WaitForRead, FTimespan::FromSeconds(10))) + { + return false; + } + else if (!Socket->Recv((uint8*)ReceivedMessage.GetData() + CurrentSize, ExtraSpace, ActualRead)) + { + return false; + } + ReceivedMessage.SetNum(CurrentSize + ActualRead); + } + } + + ISocketSubsystem* SocketSubsystem; + FSocket* Socket; + TArray ReceivedMessage; +}; + +class FFMODStudioEditorModule : public IFMODStudioEditorModule +{ +public: + /** IModuleInterface implementation */ + FFMODStudioEditorModule() : + bSimulating(false), + bIsInPIE(false), + bRegisteredComponentVisualizers(false) + { + } + + virtual void StartupModule() override; + virtual void PostLoadCallback() override; + virtual void ShutdownModule() override; + + bool HandleSettingsSaved(); + + /** Called after all banks were reloaded by the studio module */ + void HandleBanksReloaded(); + + /** Show notification */ + void ShowNotification(const FText& Text, SNotificationItem::ECompletionState State); + + void BeginPIE(bool simulating); + void EndPIE(bool simulating); + void PausePIE(bool simulating); + void ResumePIE(bool simulating); + + void ViewportDraw(UCanvas* Canvas, APlayerController*); + + bool Tick( float DeltaTime ); + + /** Add extensions to menu */ + void AddHelpMenuExtension(FMenuBuilder& MenuBuilder); + void AddFileMenuExtension(FMenuBuilder& MenuBuilder); + + /** Show FMOD version */ + void ShowVersion(); + /** Open CHM */ + void OpenCHM(); + /** Open web page to online docs */ + void OpenOnlineDocs(); + /** Open Video tutorials page */ + void OpenVideoTutorials(); + /** Set Studio build path */ + void ValidateFMOD(); + + /** Reload banks */ + void ReloadBanks(); + + TArray RegisteredComponentClassNames; + void RegisterComponentVisualizer(FName ComponentClassName, TSharedPtr Visualizer); + + /** The delegate to be invoked when this profiler manager ticks. */ + FTickerDelegate OnTick; + + /** Handle for registered delegates. */ + FDelegateHandle TickDelegateHandle; + FDelegateHandle BeginPIEDelegateHandle; + FDelegateHandle EndPIEDelegateHandle; + FDelegateHandle PausePIEDelegateHandle; + FDelegateHandle ResumePIEDelegateHandle; + FDelegateHandle HandleBanksReloadedDelegateHandle; + + /** Hook for drawing viewport */ + FDebugDrawDelegate ViewportDrawingDelegate; + FDelegateHandle ViewportDrawingDelegateHandle; + + TSharedPtr AssetBroker; + + /** The extender to pass to the level editor to extend it's window menu */ + TSharedPtr MainMenuExtender; + + /** Asset type actions for events (edit, play, stop) */ + TSharedPtr FMODEventAssetTypeActions; + + bool bSimulating; + bool bIsInPIE; + bool bRegisteredComponentVisualizers; +}; + +IMPLEMENT_MODULE( FFMODStudioEditorModule, FMODStudioEditor ) + +void FFMODStudioEditorModule::StartupModule() +{ + UE_LOG(LogFMOD, Log, TEXT("FFMODStudioEditorModule startup")); + + AssetBroker = MakeShareable(new FFMODAssetBroker); + FComponentAssetBrokerage::RegisterBroker(AssetBroker, UFMODAudioComponent::StaticClass(), true, true); + + if (ISettingsModule* SettingsModule = FModuleManager::GetModulePtr("Settings")) + { + ISettingsSectionPtr SettingsSection = SettingsModule->RegisterSettings("Project", "Plugins", "FMODStudio", + LOCTEXT("FMODStudioSettingsName", "FMOD Studio"), + LOCTEXT("FMODStudioDescription", "Configure the FMOD Studio plugin"), + GetMutableDefault() + ); + + if (SettingsSection.IsValid()) + { + SettingsSection->OnModified().BindRaw(this, &FFMODStudioEditorModule::HandleSettingsSaved); + } + } + + // Register the details customizations + { + FPropertyEditorModule& PropertyModule = FModuleManager::LoadModuleChecked("PropertyEditor"); + PropertyModule.RegisterCustomClassLayout("FMODAmbientSound", FOnGetDetailCustomizationInstance::CreateStatic(&FFMODAmbientSoundDetails::MakeInstance)); + PropertyModule.NotifyCustomizationModuleChanged(); + } + + // Need to load the editor module since it gets created after us, and we can't re-order ourselves otherwise our asset registration stops working! + // It only works if we are running the editor, not a commandlet + if (!IsRunningCommandlet()) + { + MainMenuExtender = MakeShareable(new FExtender); + MainMenuExtender->AddMenuExtension("HelpBrowse", EExtensionHook::After, NULL, FMenuExtensionDelegate::CreateRaw(this, &FFMODStudioEditorModule::AddHelpMenuExtension)); + MainMenuExtender->AddMenuExtension("FileLoadAndSave", EExtensionHook::After, NULL, FMenuExtensionDelegate::CreateRaw(this, &FFMODStudioEditorModule::AddFileMenuExtension)); + + FLevelEditorModule* LevelEditor = FModuleManager::LoadModulePtr(TEXT("LevelEditor")); + if (LevelEditor) + { + LevelEditor->GetMenuExtensibilityManager()->AddExtender(MainMenuExtender); + } + } + + // Register AssetTypeActions + IAssetTools& AssetTools = FModuleManager::GetModuleChecked("AssetTools").Get(); + + FMODEventAssetTypeActions = MakeShareable(new FAssetTypeActions_FMODEvent); + AssetTools.RegisterAssetTypeActions(FMODEventAssetTypeActions.ToSharedRef()); + + // Register slate style overrides + FFMODStudioStyle::Initialize(); + + BeginPIEDelegateHandle = FEditorDelegates::BeginPIE.AddRaw(this, &FFMODStudioEditorModule::BeginPIE); + EndPIEDelegateHandle = FEditorDelegates::EndPIE.AddRaw(this, &FFMODStudioEditorModule::EndPIE); + PausePIEDelegateHandle = FEditorDelegates::PausePIE.AddRaw(this, &FFMODStudioEditorModule::PausePIE); + ResumePIEDelegateHandle = FEditorDelegates::ResumePIE.AddRaw(this, &FFMODStudioEditorModule::ResumePIE); + + ViewportDrawingDelegate = FDebugDrawDelegate::CreateRaw(this, &FFMODStudioEditorModule::ViewportDraw); + ViewportDrawingDelegateHandle = UDebugDrawService::Register(TEXT("Editor"), ViewportDrawingDelegate); + + OnTick = FTickerDelegate::CreateRaw( this, &FFMODStudioEditorModule::Tick ); + TickDelegateHandle = FTicker::GetCoreTicker().AddTicker( OnTick ); + + // This module is loaded after FMODStudioModule + HandleBanksReloadedDelegateHandle = IFMODStudioModule::Get().BanksReloadedEvent().AddRaw(this, &FFMODStudioEditorModule::HandleBanksReloaded); + +} + +void FFMODStudioEditorModule::AddHelpMenuExtension(FMenuBuilder& MenuBuilder) +{ + MenuBuilder.BeginSection("FMODHelp", LOCTEXT("FMODHelpLabel", "FMOD Help")); + MenuBuilder.AddMenuEntry( + LOCTEXT("FMODVersionMenuEntryTitle", "About FMOD Studio"), + LOCTEXT("FMODVersionMenuEntryToolTip", "Shows the informationa about FMOD Studio."), + FSlateIcon(), + FUIAction(FExecuteAction::CreateRaw(this, &FFMODStudioEditorModule::ShowVersion))); + +#if PLATFORM_WINDOWS + MenuBuilder.AddMenuEntry( + LOCTEXT("FMODHelpCHMTitle", "FMOD Documentation..."), + LOCTEXT("FMODHelpCHMToolTip", "Opens the local FMOD documentation."), + FSlateIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.BrowseAPIReference"), + FUIAction(FExecuteAction::CreateRaw(this, &FFMODStudioEditorModule::OpenCHM))); +#endif + + MenuBuilder.AddMenuEntry( + LOCTEXT("FMODHelpOnlineTitle", "FMOD Online Documentation..."), + LOCTEXT("FMODHelpOnlineToolTip", "Go to the online FMOD documentation."), + FSlateIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.BrowseDocumentation"), + FUIAction(FExecuteAction::CreateRaw(this, &FFMODStudioEditorModule::OpenOnlineDocs))); + + MenuBuilder.AddMenuEntry( + LOCTEXT("FMODHelpVideosTitle", "FMOD Tutorial Videos..."), + LOCTEXT("FMODHelpVideosToolTip", "Go to the online FMOD tutorial videos."), + FSlateIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tutorials"), + FUIAction(FExecuteAction::CreateRaw(this, &FFMODStudioEditorModule::OpenVideoTutorials))); + + MenuBuilder.AddMenuEntry( + LOCTEXT("FMODSetStudioBuildTitle", "Validate FMOD"), + LOCTEXT("FMODSetStudioBuildToolTip", "Verifies that FMOD and FMOD Studio are working as expected."), + FSlateIcon(), + FUIAction(FExecuteAction::CreateRaw(this, &FFMODStudioEditorModule::ValidateFMOD))); + + MenuBuilder.EndSection(); +} + +void FFMODStudioEditorModule::AddFileMenuExtension(FMenuBuilder& MenuBuilder) +{ + MenuBuilder.BeginSection("FMODFile", LOCTEXT("FMODFileLabel", "FMOD")); + MenuBuilder.AddMenuEntry( + LOCTEXT("FMODFileMenuEntryTitle", "Reload Banks"), + LOCTEXT("FMODFileMenuEntryToolTip", "Force a manual reload of all FMOD Studio banks."), + FSlateIcon(), + FUIAction(FExecuteAction::CreateRaw(this, &FFMODStudioEditorModule::ReloadBanks))); + MenuBuilder.EndSection(); +} + +unsigned int GetDLLVersion() +{ + // Just grab it from the audition context which is always valid + unsigned int DLLVersion = 0; + FMOD::Studio::System* StudioSystem = IFMODStudioModule::Get().GetStudioSystem(EFMODSystemContext::Auditioning); + if (StudioSystem) + { + FMOD::System* LowLevelSystem = nullptr; + if (StudioSystem->getLowLevelSystem(&LowLevelSystem) == FMOD_OK) + { + LowLevelSystem->getVersion(&DLLVersion); + } + } + return DLLVersion; +} +unsigned int StripPatch(unsigned int FullVersion) +{ + return FullVersion & ~0xFF; +} +FString VersionToString(unsigned int FullVersion) +{ + return FString::Printf(TEXT("%x.%02x.%02x"), (FullVersion>>16), (FullVersion>>8) & 0xFF, FullVersion & 0xFF); +} + +void FFMODStudioEditorModule::ShowVersion() +{ + unsigned int HeaderVersion = FMOD_VERSION; + unsigned int DLLVersion = GetDLLVersion(); + + FText VersionMessage = FText::Format( + LOCTEXT("FMODStudio_About", "FMOD Studio\n\nBuilt Version: {0}\nDLL Version: {1}\n\nCopyright Firelight Technologies Pty Ltd"), + FText::FromString(VersionToString(HeaderVersion)), + FText::FromString(VersionToString(DLLVersion))); + FMessageDialog::Open(EAppMsgType::Ok, VersionMessage); +} + +void FFMODStudioEditorModule::OpenCHM() +{ + FString APIPath = FPaths::Combine(*FPaths::EngineDir(), TEXT("Plugins/FMODStudio/Docs/FMOD UE4 Integration.chm")); + if( IFileManager::Get().FileSize( *APIPath ) != INDEX_NONE ) + { + FString AbsoluteAPIPath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*APIPath); + FPlatformProcess::LaunchFileInDefaultExternalApplication(*AbsoluteAPIPath); + } + else + { + FMessageDialog::Open(EAppMsgType::Ok, NSLOCTEXT("Documentation", "CannotFindFMODIntegration", "Cannot open FMOD Studio Integration CHM reference; help file not found.")); + } +} + +void FFMODStudioEditorModule::OpenOnlineDocs() +{ + FPlatformProcess::LaunchFileInDefaultExternalApplication(TEXT("http://www.fmod.org/documentation")); +} + +void FFMODStudioEditorModule::OpenVideoTutorials() +{ + FPlatformProcess::LaunchFileInDefaultExternalApplication(TEXT("http://www.youtube.com/user/FMODTV")); +} + +void FFMODStudioEditorModule::ValidateFMOD() +{ + int ProblemsFound = 0; + + FFMODStudioLink StudioLink; + bool Connected = StudioLink.Connect(); + if (!Connected) + { + if (EAppReturnType::No == FMessageDialog::Open(EAppMsgType::YesNo, LOCTEXT("SetStudioBuildStudioNotRunning", "FMODStudio does not appear to be running. Only some validation will occur. Do you want to continue anyway?"))) + { + return; + } + } + unsigned int HeaderVersion = FMOD_VERSION; + unsigned int DLLVersion = GetDLLVersion(); + unsigned int StudioVersion = 0; + if (Connected) + { + FString StudioVersionString; + if (StudioLink.Execute(TEXT("studio.version"), StudioVersionString)) + { + int Super = 0; + int Major = 0; + int Minor = 0; + sscanf(TCHAR_TO_UTF8(*StudioVersionString), "Version %x.%x.%x", &Super, &Major, &Minor); + StudioVersion = (Super<<16) | (Major<<8) | Minor; + } + } + if (StripPatch(HeaderVersion) != StripPatch(DLLVersion)) + { + FText VersionMessage = FText::Format( + LOCTEXT("SetStudioBuildStudio_Status", "The FMOD DLL version is different to the version the integration was built against. This may cause problems running the game.\nBuilt Version: {0}\nDLL Version: {1}\n"), + FText::FromString(VersionToString(HeaderVersion)), + FText::FromString(VersionToString(DLLVersion))); + FMessageDialog::Open(EAppMsgType::Ok, VersionMessage); + ProblemsFound++; + } + if (StudioVersion > DLLVersion) + { + FText VersionMessage = FText::Format( + LOCTEXT("SetStudioBuildStudio_Version", "The Studio tool is newer than the version the integration was built against. The integration may not be able to load the banks that the tool builds.\n\nBuilt Version: {0}\nDLL Version: {1}\nStudio Version: {2}\n\nWe recommend using the Studio tool that matches the integration.\n\nDo you want to continue with the validation?"), + FText::FromString(VersionToString(HeaderVersion)), + FText::FromString(VersionToString(DLLVersion)), + FText::FromString(VersionToString(StudioVersion))); + if (EAppReturnType::No == FMessageDialog::Open(EAppMsgType::YesNo, VersionMessage)) + { + return; + } + ProblemsFound++; + } + + const UFMODSettings& Settings = *GetDefault(); + FString FullBankPath = Settings.BankOutputDirectory.Path; + if (FPaths::IsRelative(FullBankPath)) + { + FullBankPath = FPaths::GameContentDir() / FullBankPath; + } + FString PlatformBankPath = Settings.GetFullBankPath(); + FullBankPath = FPaths::ConvertRelativePathToFull(FullBankPath); + PlatformBankPath = FPaths::ConvertRelativePathToFull(PlatformBankPath); + + if (Connected) + { + // File path was added in FMOD Studio 1.07.00 + FString StudioProjectPath; + FString StudioProjectDir; + if (StudioVersion >= 0x00010700) + { + StudioLink.Execute(TEXT("studio.project.filePath"), StudioProjectPath); + if (StudioProjectPath.IsEmpty() || StudioProjectPath == TEXT("undefined")) + { + FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("SetStudioBuildStudio_NewProject", "FMOD Studio has an empty project. Please go to FMOD Studio, and press Save to create your new project.")); + // Just try to save anyway + FString Result; + StudioLink.Execute(TEXT("studio.project.save()"), Result); + } + StudioLink.Execute(TEXT("studio.project.filePath"), StudioProjectPath); + if (StudioProjectPath != TEXT("undefined")) + { + StudioProjectDir = FPaths::GetPath(StudioProjectPath); + } + } + + FString StudioPathString; + StudioLink.Execute(TEXT("studio.project.workspace.builtBanksOutputDirectory"), StudioPathString); + if (StudioPathString == TEXT("undefined")) + { + StudioPathString = TEXT(""); + } + + FString CanonicalBankPath = FullBankPath; + FPaths::CollapseRelativeDirectories(CanonicalBankPath); + FPaths::NormalizeDirectoryName(CanonicalBankPath); + FPaths::RemoveDuplicateSlashes(CanonicalBankPath); + FPaths::NormalizeDirectoryName(CanonicalBankPath); + + FString CanonicalStudioPath = StudioPathString; + if (FPaths::IsRelative(CanonicalStudioPath) && !StudioProjectDir.IsEmpty() && !StudioPathString.IsEmpty()) + { + CanonicalStudioPath = FPaths::Combine(*StudioProjectDir, *CanonicalStudioPath); + } + FPaths::CollapseRelativeDirectories(CanonicalStudioPath); + FPaths::NormalizeDirectoryName(CanonicalStudioPath); + FPaths::RemoveDuplicateSlashes(CanonicalStudioPath); + FPaths::NormalizeDirectoryName(CanonicalStudioPath); + if (!FPaths::IsSamePath(CanonicalBankPath, CanonicalStudioPath)) + { + FString BankPathToSet = FullBankPath; + // Extra logic - if we have put the studio project inside the game project, then make it relative + if (!StudioProjectDir.IsEmpty()) + { + FString GameBaseDir = FPaths::ConvertRelativePathToFull(FPaths::GameDir()); + FString BankPathFromGameProject = FullBankPath; + FString StudioProjectFromGameProject = StudioProjectDir; + if (FPaths::MakePathRelativeTo(BankPathFromGameProject, *GameBaseDir) && !BankPathFromGameProject.Contains(TEXT("..")) && + FPaths::MakePathRelativeTo(StudioProjectFromGameProject, *GameBaseDir) && !StudioProjectFromGameProject.Contains(TEXT(".."))) + { + FPaths::MakePathRelativeTo(BankPathToSet, *(StudioProjectDir + TEXT("/"))); + } + } + ProblemsFound++; + + FText AskMessage = FText::Format( + LOCTEXT("SetStudioBuildStudio_Ask", "FMOD Studio build path should be set up.\n\nCurrent Studio build path: {0}\nNew build path: {1}\n\nDo you want to fix up the project now?"), + FText::FromString(StudioPathString), + FText::FromString(BankPathToSet)); + if (EAppReturnType::Yes == FMessageDialog::Open(EAppMsgType::YesNo, AskMessage)) + { + FString Result; + StudioLink.Execute(*FString::Printf(TEXT("studio.project.workspace.builtBanksOutputDirectory = \"%s\";"), *BankPathToSet), Result); + StudioLink.Execute(TEXT("studio.project.workspace.builtBanksOutputDirectory"), Result); + if (Result != BankPathToSet) + { + FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("SetStudioBuildStudio_Save", "Failed to set bank directory. Please go to FMOD Studio, and set the bank path in FMOD Studio project settings.")); + } + FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("SetStudioBuildStudio_Save", "Please go to FMOD Studio, save your project and build banks.")); + // Just try to do it again anyway + StudioLink.Execute(TEXT("studio.project.save()"), Result); + StudioLink.Execute(TEXT("studio.project.build()"), Result); + // Pretend settings have changed which will force a reload + IFMODStudioModule::Get().RefreshSettings(); + } + } + } + bool AnyBankFiles = false; + + if (!FPaths::DirectoryExists(FullBankPath) || !FPaths::DirectoryExists(PlatformBankPath)) + { + FText DirMessage = FText::Format( + LOCTEXT("SetStudioBuildStudio_Dir", "The FMOD Content directory does not exist. Please make sure FMOD Studio is exporting banks into the correct location.\n\nBanks should be exported to: {0}\nBanks files should exist in: {1}\n"), + FText::FromString(FullBankPath), + FText::FromString(PlatformBankPath)); + FMessageDialog::Open(EAppMsgType::Ok, DirMessage); + ProblemsFound++; + } + else + { + TArray BankFiles; + Settings.GetAllBankPaths(BankFiles, true); + if (BankFiles.Num() != 0) + { + AnyBankFiles = true; + } + else + { + FText EmptyBankDirMessage = FText::Format( + LOCTEXT("SetStudioBuildStudio_EmptyBankDir", "The FMOD Content directory does not have any bank files in them. Please make sure FMOD Studio is exporting banks into the correct location.\n\nBanks should be exported to: {0}\nBanks files should exist in: {1}\n"), + FText::FromString(FullBankPath), + FText::FromString(PlatformBankPath)); + FMessageDialog::Open(EAppMsgType::Ok, EmptyBankDirMessage); + ProblemsFound++; + } + } + if (AnyBankFiles) + { + FMOD::Studio::System* StudioSystem = IFMODStudioModule::Get().GetStudioSystem(EFMODSystemContext::Auditioning); + int BankCount = 0; + StudioSystem->getBankCount(&BankCount); + TArray FailedBanks = IFMODStudioModule::Get().GetFailedBankLoads(EFMODSystemContext::Auditioning); + if (BankCount == 0 || FailedBanks.Num() != 0) + { + FString CombinedBanks; + for (auto Bank : FailedBanks) + { + CombinedBanks += Bank; + CombinedBanks += TEXT("\n"); + } + FText BankLoadMessage; + if (BankCount == 0 && FailedBanks.Num() == 0) + { + BankLoadMessage = LOCTEXT("SetStudioBuildStudio_BankLoad", "Failed to load banks\n"); + } + else if (BankCount == 0) + { + BankLoadMessage = FText::Format( + LOCTEXT("SetStudioBuildStudio_BankLoad", "Failed to load banks:\n{0}\n"), + FText::FromString(CombinedBanks)); + } + else + { + BankLoadMessage = FText::Format( + LOCTEXT("SetStudioBuildStudio_BankLoad", "Some banks failed to load:\n{0}\n"), + FText::FromString(CombinedBanks)); + } + FMessageDialog::Open(EAppMsgType::Ok, BankLoadMessage); + ProblemsFound++; + } + else + { + int TotalEventCount = 0; + TArray Banks; + Banks.SetNum(BankCount); + StudioSystem->getBankList(Banks.GetData(), BankCount, &BankCount); + for (FMOD::Studio::Bank* Bank : Banks) + { + int EventCount = 0; + Bank->getEventCount(&EventCount); + TotalEventCount += EventCount; + } + if (TotalEventCount == 0) + { + FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("SetStudioBuildStudio_NoEvents", "Banks have been loaded but they didn't have any events in them. Please make sure you have added some events to banks.")); + ProblemsFound++; + } + } + } + TArray RequiredPlugins = IFMODStudioModule::Get().GetRequiredPlugins(); + if (RequiredPlugins.Num() != 0 && Settings.PluginFiles.Num() == 0) + { + FString CombinedPlugins; + for (auto Name : RequiredPlugins) + { + CombinedPlugins += Name; + CombinedPlugins += TEXT("\n"); + } + FText PluginMessage = FText::Format( + LOCTEXT("SetStudioBuildStudio_Plugins", "The banks require the following plugins, but no plugin filenames are listed in the settings:\n{0}\n"), + FText::FromString(CombinedPlugins)); + FMessageDialog::Open(EAppMsgType::Ok, PluginMessage); + ProblemsFound++; + } + + if (ProblemsFound) + { + FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("SetStudioBuildStudio_FinishedBad", "Finished validation. Problems were detected.\n")); + } + else + { + FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("SetStudioBuildStudio_FinishedGood", "Finished validation. No problems detected.\n")); + } +} + +void FFMODStudioEditorModule::ReloadBanks() +{ + // Pretend settings have changed which will force a reload + IFMODStudioModule::Get().RefreshSettings(); +} + +bool FFMODStudioEditorModule::Tick( float DeltaTime ) +{ + if (!bRegisteredComponentVisualizers && GUnrealEd != nullptr) + { + // Register component visualizers (GUnrealED is required for this, but not initialized before this module loads, so we have to wait until GUnrealEd is available) + RegisterComponentVisualizer(UFMODAudioComponent::StaticClass()->GetFName(), MakeShareable(new FFMODAudioComponentVisualizer)); + + bRegisteredComponentVisualizers = true; + } + + return true; +} + +void FFMODStudioEditorModule::BeginPIE(bool simulating) +{ + UE_LOG(LogFMOD, Verbose, TEXT("FFMODStudioEditorModule BeginPIE: %d"), simulating); + bSimulating = simulating; + bIsInPIE = true; + IFMODStudioModule::Get().SetInPIE(true, simulating); +} + +void FFMODStudioEditorModule::EndPIE(bool simulating) +{ + UE_LOG(LogFMOD, Verbose, TEXT("FFMODStudioEditorModule EndPIE: %d"), simulating); + bSimulating = false; + bIsInPIE = false; + IFMODStudioModule::Get().SetInPIE(false, simulating); +} + +void FFMODStudioEditorModule::PausePIE(bool simulating) +{ + UE_LOG(LogFMOD, Verbose, TEXT("FFMODStudioEditorModule PausePIE%d")); + IFMODStudioModule::Get().SetSystemPaused(true); +} + +void FFMODStudioEditorModule::ResumePIE(bool simulating) +{ + UE_LOG(LogFMOD, Verbose, TEXT("FFMODStudioEditorModule ResumePIE")); + IFMODStudioModule::Get().SetSystemPaused(false); +} + +void FFMODStudioEditorModule::PostLoadCallback() +{ + UE_LOG(LogFMOD, Verbose, TEXT("FFMODStudioEditorModule PostLoadCallback")); +} + +void FFMODStudioEditorModule::ViewportDraw(UCanvas* Canvas, APlayerController*) +{ + // Only want to update based on viewport in simulate mode. + // In PIE/game, we update from the player controller instead. + if (!bSimulating) + { + return; + } + + const FSceneView* View = Canvas->SceneView; + + if (View->Drawer == GCurrentLevelEditingViewportClient) + { + UWorld* World = GCurrentLevelEditingViewportClient->GetWorld(); + const FVector& ViewLocation = GCurrentLevelEditingViewportClient->GetViewLocation(); + + FMatrix CameraToWorld = View->ViewMatrices.ViewMatrix.InverseFast(); + FVector ProjUp = CameraToWorld.TransformVector(FVector(0, 1000, 0)); + FVector ProjRight = CameraToWorld.TransformVector(FVector(1000, 0, 0)); + + FTransform ListenerTransform(FRotationMatrix::MakeFromZY(ProjUp, ProjRight)); + ListenerTransform.SetTranslation(ViewLocation); + ListenerTransform.NormalizeRotation(); + + IFMODStudioModule::Get().SetListenerPosition(0, World, ListenerTransform, 0.0f); + IFMODStudioModule::Get().FinishSetListenerPosition(1, 0.0f); + } +} + +void FFMODStudioEditorModule::ShutdownModule() +{ + UE_LOG(LogFMOD, Verbose, TEXT("FFMODStudioEditorModule shutdown")); + + if (UObjectInitialized()) + { + // Unregister tick function. + FTicker::GetCoreTicker().RemoveTicker(TickDelegateHandle); + + FEditorDelegates::BeginPIE.Remove(BeginPIEDelegateHandle); + FEditorDelegates::EndPIE.Remove(EndPIEDelegateHandle); + FEditorDelegates::PausePIE.Remove(PausePIEDelegateHandle); + FEditorDelegates::ResumePIE.Remove(ResumePIEDelegateHandle); + + if (ViewportDrawingDelegate.IsBound()) + { + UDebugDrawService::Unregister(ViewportDrawingDelegateHandle); + } + + FComponentAssetBrokerage::UnregisterBroker(AssetBroker); + + if (MainMenuExtender.IsValid()) + { + FLevelEditorModule* LevelEditorModule = FModuleManager::GetModulePtr( "LevelEditor" ); + if (LevelEditorModule) + { + LevelEditorModule->GetMenuExtensibilityManager()->RemoveExtender(MainMenuExtender); + } + } + } + + if (ISettingsModule* SettingsModule = FModuleManager::GetModulePtr("Settings")) + { + SettingsModule->UnregisterSettings("Project", "Plugins", "FMODStudio"); + } + + // Unregister AssetTypeActions + if (FModuleManager::Get().IsModuleLoaded("AssetTools")) + { + IAssetTools& AssetTools = FModuleManager::GetModuleChecked("AssetTools").Get(); + + AssetTools.UnregisterAssetTypeActions(FMODEventAssetTypeActions.ToSharedRef()); + } + + // Unregister component visualizers + if (GUnrealEd != nullptr) + { + // Iterate over all class names we registered for + for (FName ClassName : RegisteredComponentClassNames) + { + GUnrealEd->UnregisterComponentVisualizer(ClassName); + } + } + + IFMODStudioModule::Get().BanksReloadedEvent().Remove(HandleBanksReloadedDelegateHandle); +} + +bool FFMODStudioEditorModule::HandleSettingsSaved() +{ + IFMODStudioModule::Get().RefreshSettings(); + + return true; +} + +void FFMODStudioEditorModule::HandleBanksReloaded() +{ + // Show a reload notification + TArray FailedBanks = IFMODStudioModule::Get().GetFailedBankLoads(EFMODSystemContext::Auditioning); + FText Message; + SNotificationItem::ECompletionState State; + if (FailedBanks.Num() == 0) + { + Message = LOCTEXT("FMODBanksReloaded", "Reloaded FMOD Banks\n"); + State = SNotificationItem::CS_Success; + } + else + { + FString CombinedMessage = "Problem loading FMOD Banks:"; + for (auto Entry : FailedBanks) + { + CombinedMessage += TEXT("\n"); + CombinedMessage += Entry; + + UE_LOG(LogFMOD, Warning, TEXT("Problem loading FMOD Bank: %s"), *Entry); + } + + Message = FText::Format( + LOCTEXT("FMODBanksReloaded", "{0}"), + FText::FromString(CombinedMessage)); + State = SNotificationItem::CS_Fail; + } + ShowNotification(Message, State); +} + +void FFMODStudioEditorModule::ShowNotification(const FText& Text, SNotificationItem::ECompletionState State) +{ + FNotificationInfo Info(Text); + Info.Image = FEditorStyle::GetBrush(TEXT("NoBrush")); + Info.FadeInDuration = 0.1f; + Info.FadeOutDuration = 0.5f; + Info.ExpireDuration = State == SNotificationItem::CS_Fail ? 6.0f : 1.5f; + Info.bUseThrobber = false; + Info.bUseSuccessFailIcons = true; + Info.bUseLargeFont = true; + Info.bFireAndForget = false; + Info.bAllowThrottleWhenFrameRateIsLow = false; + auto NotificationItem = FSlateNotificationManager::Get().AddNotification(Info); + NotificationItem->SetCompletionState(State); + NotificationItem->ExpireAndFadeout(); + + if (GCurrentLevelEditingViewportClient) + { + // Refresh any 3d event visualization + GCurrentLevelEditingViewportClient->bNeedsRedraw = true; + } +} + +void FFMODStudioEditorModule::RegisterComponentVisualizer(FName ComponentClassName, TSharedPtr Visualizer) +{ + if (GUnrealEd != nullptr) + { + GUnrealEd->RegisterComponentVisualizer(ComponentClassName, Visualizer); + } + + RegisteredComponentClassNames.Add(ComponentClassName); + + if (Visualizer.IsValid()) + { + Visualizer->OnRegister(); + } +} diff --git a/Plugins/FMODStudio/Source/FMODStudioEditor/Private/FMODStudioEditorPrivatePCH.h b/Plugins/FMODStudio/Source/FMODStudioEditor/Private/FMODStudioEditorPrivatePCH.h new file mode 100644 index 0000000..1fe0eab --- /dev/null +++ b/Plugins/FMODStudio/Source/FMODStudioEditor/Private/FMODStudioEditorPrivatePCH.h @@ -0,0 +1,9 @@ +// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2016. +#pragma once + +#include "Engine.h" +#include "UnrealEd.h" +#include "Components/SceneComponent.h" + +DECLARE_LOG_CATEGORY_EXTERN(LogFMOD, Log, All); + diff --git a/Plugins/FMODStudio/Source/FMODStudioEditor/Private/FMODStudioStyle.cpp b/Plugins/FMODStudio/Source/FMODStudioEditor/Private/FMODStudioStyle.cpp new file mode 100644 index 0000000..b3a7f6a --- /dev/null +++ b/Plugins/FMODStudio/Source/FMODStudioEditor/Private/FMODStudioStyle.cpp @@ -0,0 +1,56 @@ +// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2016. + +#include "FMODStudioEditorPrivatePCH.h" +#include "FMODStudioStyle.h" +#include "SlateStyle.h" +#include "EditorStyle.h" + +#define IMAGE_BRUSH(RelativePath, ...) FSlateImageBrush(Style.RootToContentDir(RelativePath, TEXT(".png")), __VA_ARGS__) +#define BOX_BRUSH(RelativePath, ...) FSlateBoxBrush(Style.RootToContentDir(RelativePath, TEXT(".png")), __VA_ARGS__) + +////////////////////////////////////////////////////////////////////////// +// FFMODStudioStyle + +TSharedPtr FFMODStudioStyle::StyleInstance = NULL; + +void FFMODStudioStyle::Initialize() +{ + if (!StyleInstance.IsValid()) + { + StyleInstance = Create(); + } + + SetStyle(StyleInstance.ToSharedRef()); +} + +void FFMODStudioStyle::Shutdown() +{ + ResetToDefault(); + ensure(StyleInstance.IsUnique()); + StyleInstance.Reset(); +} + +TSharedRef FFMODStudioStyle::Create() +{ + IEditorStyleModule& EditorStyle = FModuleManager::LoadModuleChecked(TEXT("EditorStyle")); + TSharedRef< FSlateStyleSet > StyleRef = EditorStyle.CreateEditorStyleInstance(); + FSlateStyleSet& Style = StyleRef.Get(); + + const FVector2D Icon20x20(20.0f, 20.0f); + const FVector2D Icon40x40(40.0f, 40.0f); + + Style.Set( "ClassIcon.FMODAmbientSound", new IMAGE_BRUSH( "Icons/AssetIcons/AmbientSound_16x", FVector2D(16.0f, 16.0f) ) ); + Style.Set( "ClassThumbnail.FMODAmbientSound", new IMAGE_BRUSH( "Icons/AssetIcons/AmbientSound_64x", FVector2D(64.0f, 64.0f) ) ); + + Style.Set( "ClassIcon.FMODAudioComponent", new IMAGE_BRUSH( "Icons/ActorIcons/SoundActor_16x", FVector2D(16.0f, 16.0f) ) ); + //Style.Set( "ClassThumbnail.FMODAudioComponent", new IMAGE_BRUSH( "Icons/ActorIcons/SoundActor_64x", FVector2D(64.0f, 64.0f) ) ); + + Style.Set( "ClassIcon.FMODAsset", new IMAGE_BRUSH( "Icons/ActorIcons/SoundActor_16x", FVector2D(16.0f,16.0f) ) ); + //Style.Set( "ClassThumbnail.FMODAsset", new IMAGE_BRUSH( "Icons/ActorIcons/SoundActor_64x", FVector2D(64.0f, 64.0f) ) ); + + return StyleRef; +} + +////////////////////////////////////////////////////////////////////////// + +#undef IMAGE_BRUSH diff --git a/Plugins/FMODStudio/Source/FMODStudioEditor/Private/FMODStudioStyle.h b/Plugins/FMODStudio/Source/FMODStudioEditor/Private/FMODStudioStyle.h new file mode 100644 index 0000000..71e95ba --- /dev/null +++ b/Plugins/FMODStudio/Source/FMODStudioEditor/Private/FMODStudioStyle.h @@ -0,0 +1,20 @@ +// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2016. + +#pragma once + +class FFMODStudioStyle : public FEditorStyle +{ +public: + static void Initialize(); + + static void Shutdown(); + +private: + static TSharedRef Create(); + +private: + static TSharedPtr StyleInstance; + +private: + FFMODStudioStyle() {} +}; diff --git a/Plugins/FMODStudio/Source/FMODStudioEditor/Private/SFMODEventEditorPanel.cpp b/Plugins/FMODStudio/Source/FMODStudioEditor/Private/SFMODEventEditorPanel.cpp new file mode 100644 index 0000000..169a462 --- /dev/null +++ b/Plugins/FMODStudio/Source/FMODStudioEditor/Private/SFMODEventEditorPanel.cpp @@ -0,0 +1,375 @@ +// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2016. + +#include "FMODStudioEditorPrivatePCH.h" +#include "SFMODEventEditorPanel.h" +#include "FMODStudioModule.h" +#include "FMODUtils.h" +#include "Input/Reply.h" +#include "SNumericEntryBox.h" +#include "SExpandableArea.h" +#include "fmod_studio.hpp" + +#define LOCTEXT_NAMESPACE "FMODEventEditor" + +SFMODEventEditorPanel::~SFMODEventEditorPanel() +{ +} + +void SFMODEventEditorPanel::Construct(const FArguments& InArgs) +{ + FMODEventEditorPtr = InArgs._FMODEventEditor; + + FMOD::Studio::EventDescription* EventDescription = FMODEventEditorPtr.Pin()->GetEventDescription(); + + TSharedRef ToolbarBorder = ConstructToolbar(EventDescription); + TSharedRef InfoArea = ConstructInfo(EventDescription); + TSharedRef ParametersArea = ConstructParameters(EventDescription); + TSharedRef UserPropertiesArea = ConstructUserProperties(EventDescription); + + TSharedRef ChildWidget = + SNew(SVerticalBox) + + SVerticalBox::Slot() + .AutoHeight() + .Padding(0.0f, 3.0f) + [ + InfoArea + ] + + SVerticalBox::Slot() + .AutoHeight() + .Padding(0.0f, 3.0f) + [ + ParametersArea + ] + + SVerticalBox::Slot() + .AutoHeight() + .Padding(0.0f, 3.0f) + [ + UserPropertiesArea + ]; + + ChildSlot + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .AutoHeight() + .Padding(0.0f, 3.0f) + [ + ToolbarBorder + ] + + SVerticalBox::Slot() + .FillHeight(1.0f) + [ + SNew(SScrollBox) + + SScrollBox::Slot() + .Padding(0.0f) + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .AutoHeight() + .Padding(0.0f) + [ + ChildWidget + ] + ] + ] + ]; +} + +TSharedRef SFMODEventEditorPanel::ConstructToolbar(FMOD::Studio::EventDescription* EventDescription) +{ + float MinDistance = 0.0f; + float MaxDistance = 0.0f; + int32 EventLengthMS = 0; + bool bIsOneshot = false, bIsStream = false, bIs3D = false; + if (EventDescription != nullptr) + { + EventDescription->getMinimumDistance(&MinDistance); + EventDescription->getMaximumDistance(&MaxDistance); + EventDescription->getLength(&EventLengthMS); + EventDescription->isOneshot(&bIsOneshot); + EventDescription->isStream(&bIsStream); + EventDescription->is3D(&bIs3D); + } + + const FTimespan EventLength = FTimespan::FromMilliseconds((double)EventLengthMS); + const FString EventLengthString = EventLength.GetHours() <= 0 ? EventLength.ToString(TEXT("%m:%s.%f")) : EventLength.ToString(TEXT("%h:%m:%s.%f")); + + const FText RadiusText = FText::Format(LOCTEXT("RadiusFormat", "Distance Attenuation: {0}m to {1}m"), FText::AsNumber(MinDistance), FText::AsNumber(MaxDistance)); + const FText LengthText = FText::Format(LOCTEXT("LengthFormat", "Length: {0}"), FText::FromString(EventLengthString)); + + FText EventInfoText; + if (bIs3D && bIsOneshot) + { + EventInfoText = FText::Format(LOCTEXT("RadiusLengthFormat", "{0} - {1}"), RadiusText, LengthText); + } + else if (!bIs3D && bIsOneshot) + { + EventInfoText = LengthText; + } + else if (bIs3D && !bIsOneshot) + { + EventInfoText = RadiusText; + } + + return SNew(SBorder) + .BorderImage(FEditorStyle::Get().GetBrush("ToolPanel.GroupBorder")) + .Padding(6.0f) + .Content() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(0.0f, 0.0f, 2.0f, 0.0f) + .VAlign(VAlign_Center) + .HAlign(HAlign_Left) + [ + SNew(SButton) + .VAlign(VAlign_Center) + .Text(LOCTEXT("Play", "Play")) + .ContentPadding(4) + .OnClicked(this, &SFMODEventEditorPanel::OnClickedPlay) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + .HAlign(HAlign_Left) + [ + SNew(SButton) + .Text(LOCTEXT("Pause", "Pause")) + .ContentPadding(4) + .OnClicked(this, &SFMODEventEditorPanel::OnClickedPause) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + .HAlign(HAlign_Left) + [ + SNew(SButton) + .VAlign(VAlign_Center) + .Text(LOCTEXT("Stop", "Stop")) + .ContentPadding(4) + .OnClicked(this, &SFMODEventEditorPanel::OnClickedStop) + ] + + SHorizontalBox::Slot() + .FillWidth(1.0f) + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + .HAlign(HAlign_Right) + [ + SNew(STextBlock) + .Text(EventInfoText) + ] + ]; +} + +void AddTextField(TSharedRef& InfoBox, const TCHAR* Name, const FText& Value) +{ + InfoBox->AddSlot() + .Padding(4.0f, 3.0f) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(0.3f) + [ + SNew(STextBlock) + .Text(FText::FromString(Name)) + ] + + SHorizontalBox::Slot() + [ + SNew(SEditableText) + .Text(Value) + .IsReadOnly(true) + ] + ]; +} + +void AddBoolField(TSharedRef& InfoBox, const TCHAR* Name, bool bValue) +{ + AddTextField(InfoBox, Name, bValue ? LOCTEXT("True","True") : LOCTEXT("False","False")); +} + +void AddFloatField(TSharedRef& InfoBox, const TCHAR* Name, float Value) +{ + AddTextField(InfoBox, Name, FText::AsNumber(Value)); +} + +TSharedRef MakeBox(TSharedRef& InfoBox, const FText& Value) +{ + return SNew(SExpandableArea) + .AreaTitle(Value) + .InitiallyCollapsed(false) + .BodyContent() + [ + SNew(SBorder) + .BorderImage(FCoreStyle::Get().GetBrush("NoBorder")) + .Padding(4.0f) + .Content() + [ + InfoBox + ] + ]; +} + +TSharedRef SFMODEventEditorPanel::ConstructInfo(FMOD::Studio::EventDescription* EventDescription) +{ + TSharedRef InfoBox = SNew(SVerticalBox); + + if (EventDescription != nullptr) + { + FString EventPath = FMODUtils::GetPath(EventDescription); + FGuid Guid = FMODUtils::GetID(EventDescription); + + int Length = 0.0f; + float MinDist = 0.0f; + float MaxDist = 0.0f; + EventDescription->getLength(&Length); + EventDescription->getMinimumDistance(&MinDist); + EventDescription->getMaximumDistance(&MaxDist); + + bool bOneShot = false; + bool bStream = false; + bool b3D = false; + EventDescription->isOneshot(&bOneShot); + EventDescription->isStream(&bStream); + EventDescription->is3D(&b3D); + + AddTextField(InfoBox, TEXT("Path"), FText::FromString(EventPath)); + AddTextField(InfoBox, TEXT("Guid"), FText::FromString(Guid.ToString(EGuidFormats::DigitsWithHyphensInBraces))); + AddBoolField(InfoBox, TEXT("OneShot"), bOneShot); + AddBoolField(InfoBox, TEXT("Streaming"), bStream); + AddBoolField(InfoBox, TEXT("3D"), b3D); + + AddFloatField(InfoBox, TEXT("Length"), static_cast(Length)); + if (b3D) + { + AddFloatField(InfoBox, TEXT("Min Dist"), MinDist); + AddFloatField(InfoBox, TEXT("Max Dist"), MaxDist); + } + } + + return MakeBox(InfoBox, LOCTEXT("EventInfo", "Event Info")); +} + +TSharedRef SFMODEventEditorPanel::ConstructParameters(FMOD::Studio::EventDescription* EventDescription) +{ + auto EventEditor = FMODEventEditorPtr.Pin(); + TSharedRef ParametersBox = SNew(SVerticalBox); + + FNumberFormattingOptions Options; + Options.MinimumFractionalDigits = 1; + + if (EventDescription != nullptr) + { + int32 ParameterCount; + EventDescription->getParameterCount(&ParameterCount); + for (int32 ParamIdx = 0; ParamIdx < ParameterCount; ParamIdx++) + { + FMOD_STUDIO_PARAMETER_DESCRIPTION Parameter; + EventDescription->getParameterByIndex(ParamIdx, &Parameter); + + EventEditor->GetParameterValues().Add(Parameter.minimum); + + const FString ParameterName = Parameter.type == FMOD_STUDIO_PARAMETER_GAME_CONTROLLED ? FString(UTF8_TO_TCHAR(Parameter.name)) : FMODUtils::ParameterTypeToString(Parameter.type); + const FText ToolTipText = FText::Format(LOCTEXT("ParameterTooltipFormat", "{0} (Min Value: {1} - Max Value: {2})"), + FText::FromString(ParameterName), FText::AsNumber(Parameter.minimum, &Options), FText::AsNumber(Parameter.maximum, &Options)); + + ParametersBox->AddSlot() + .Padding(4.0f, 2.0f) + [ + SNew(SHorizontalBox) + .ToolTipText(ToolTipText) + + SHorizontalBox::Slot() + .FillWidth(0.3f) + [ + SNew(STextBlock) + .Text(FText::FromString(ParameterName)) + ] + + SHorizontalBox::Slot() + .MaxWidth(200.0f) + [ + SNew(SNumericEntryBox) + .Value(this, &SFMODEventEditorPanel::GetParameterValue, ParamIdx) + .OnValueChanged(this, &SFMODEventEditorPanel::OnParameterValueChanged, ParamIdx) + .AllowSpin(true) + .MinValue(Parameter.minimum) + .MaxValue(Parameter.maximum) + .MinSliderValue(Parameter.minimum) + .MaxSliderValue(Parameter.maximum) + .Delta(0.01f) + ] + ]; + } + } + + return MakeBox(ParametersBox, LOCTEXT("EventParameters", "Event Parameters")); +} + +TSharedRef SFMODEventEditorPanel::ConstructUserProperties(FMOD::Studio::EventDescription* EventDescription) +{ + TSharedRef UserPropertiesBox = SNew(SVerticalBox); + + if (EventDescription != nullptr) + { + int32 UserPropertyCount; + EventDescription->getUserPropertyCount(&UserPropertyCount); + for (int32 PropertyIdx = 0; PropertyIdx < UserPropertyCount; PropertyIdx++) + { + FMOD_STUDIO_USER_PROPERTY UserProperty; + EventDescription->getUserPropertyByIndex(PropertyIdx, &UserProperty); + + FText PropertyText; + switch (UserProperty.type) + { + case FMOD_STUDIO_USER_PROPERTY_TYPE_INTEGER: + PropertyText = FText::AsNumber(UserProperty.intValue); + break; + case FMOD_STUDIO_USER_PROPERTY_TYPE_BOOLEAN: + PropertyText = UserProperty.boolValue ? LOCTEXT("True", "True") : LOCTEXT("False", "False"); + break; + case FMOD_STUDIO_USER_PROPERTY_TYPE_FLOAT: + PropertyText = FText::AsNumber(UserProperty.floatValue); + break; + case FMOD_STUDIO_USER_PROPERTY_TYPE_STRING: + PropertyText = FText::FromString(UTF8_TO_TCHAR(UserProperty.stringValue)); + break; + } + + FString UserName(UTF8_TO_TCHAR(UserProperty.name)); + AddTextField(UserPropertiesBox, *UserName, PropertyText); + } + } + + return MakeBox(UserPropertiesBox, LOCTEXT("EventUserProperties", "Event User Properties")); +} + +FReply SFMODEventEditorPanel::OnClickedPlay() +{ + FMODEventEditorPtr.Pin()->PlayEvent(); + return FReply::Handled(); +} + +FReply SFMODEventEditorPanel::OnClickedStop() +{ + FMODEventEditorPtr.Pin()->StopEvent(); + return FReply::Handled(); +} + +FReply SFMODEventEditorPanel::OnClickedPause() +{ + FMODEventEditorPtr.Pin()->PauseEvent(); + return FReply::Handled(); +} + +void SFMODEventEditorPanel::OnParameterValueChanged(float NewValue, int32 ParameterIdx) +{ + FMODEventEditorPtr.Pin()->SetParameterValue(ParameterIdx, NewValue); +} + +TOptional SFMODEventEditorPanel::GetParameterValue(int32 ParameterIdx) const +{ + return FMODEventEditorPtr.Pin()->GetParameterValues()[ParameterIdx]; +} + +#undef LOC_NAMESPACE diff --git a/Plugins/FMODStudio/Source/FMODStudioEditor/Private/SFMODEventEditorPanel.h b/Plugins/FMODStudio/Source/FMODStudioEditor/Private/SFMODEventEditorPanel.h new file mode 100644 index 0000000..181d696 --- /dev/null +++ b/Plugins/FMODStudio/Source/FMODStudioEditor/Private/SFMODEventEditorPanel.h @@ -0,0 +1,45 @@ +// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2016. + +#pragma once + +#include "FMODEventEditor.h" + +namespace FMOD +{ + namespace Studio + { + class EventDescription; + } +} + +class SFMODEventEditorPanel : public SCompoundWidget +{ +public: + + SLATE_BEGIN_ARGS(SFMODEventEditorPanel) + {} + SLATE_ARGUMENT(TWeakPtr, FMODEventEditor) + SLATE_END_ARGS() + + ~SFMODEventEditorPanel(); + + /** SCompoundWidget interface */ + void Construct(const FArguments& InArgs); + +private: + + TSharedRef ConstructToolbar(FMOD::Studio::EventDescription* EventDescription); + TSharedRef ConstructInfo(FMOD::Studio::EventDescription* EventDescription); + TSharedRef ConstructParameters(FMOD::Studio::EventDescription* EventDescription); + TSharedRef ConstructUserProperties(FMOD::Studio::EventDescription* EventDescription); + + /** Editor that owns this panel */ + TWeakPtr FMODEventEditorPtr; + + FReply OnClickedPlay(); + FReply OnClickedStop(); + FReply OnClickedPause(); + + TOptional GetParameterValue(int32 ParameterIdx) const; + void OnParameterValueChanged(float NewValue, int32 ParameterIdx); +}; diff --git a/Plugins/FMODStudio/Source/FMODStudioEditor/Public/FMODStudioEditorModule.h b/Plugins/FMODStudio/Source/FMODStudioEditor/Public/FMODStudioEditorModule.h new file mode 100644 index 0000000..90da84e --- /dev/null +++ b/Plugins/FMODStudio/Source/FMODStudioEditor/Public/FMODStudioEditorModule.h @@ -0,0 +1,35 @@ +// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2016. + +#pragma once + +#include "ModuleManager.h" + +/** + * The public interface to this module + */ +class IFMODStudioEditorModule : public IModuleInterface +{ + +public: + + /** + * Singleton-like access to this module's interface. This is just for convenience! + * Beware of calling this during the shutdown phase, though. Your module might have been unloaded already. + * + * @return Returns singleton instance, loading the module on demand if needed + */ + static inline IFMODStudioEditorModule& Get() + { + return FModuleManager::LoadModuleChecked< IFMODStudioEditorModule >( "FMODStudioEditor" ); + } + + /** + * Checks to see if this module is loaded and ready. It is only valid to call Get() if IsAvailable() returns true. + * + * @return True if the module is loaded and ready to use + */ + static inline bool IsAvailable() + { + return FModuleManager::Get().IsModuleLoaded( "FMODStudioEditor" ); + } +}; diff --git a/Plugins/Mantis/Mantis.uplugin b/Plugins/Mantis/Mantis.uplugin new file mode 100644 index 0000000..ba8de99 --- /dev/null +++ b/Plugins/Mantis/Mantis.uplugin @@ -0,0 +1,21 @@ +{ + "FileVersion": 3, + "Version": 1, + "VersionName": "1.0", + "FriendlyName": "Mantis Plugin", + "Description": "A plugin to add an interface in Unreal for Mantis bug reports.", + "Category": "Bugs", + "CreatedBy": "Yoshi van Belkom", + "CreatedByURL": "http://www.yoshivanbelkom.com/", + "Modules": [ + { + "Name": "Mantis", + "Type": "Developer", + "LoadingPhase": "PreDefault" + } + ], + "EnabledByDefault": true, + "CanContainContent": true, + "IsBetaVersion": true, + "Installed": false +} \ No newline at end of file diff --git a/Plugins/Mantis/Resources/Icon128.png b/Plugins/Mantis/Resources/Icon128.png new file mode 100644 index 0000000..d0b4600 Binary files /dev/null and b/Plugins/Mantis/Resources/Icon128.png differ diff --git a/Plugins/Mantis/Source/Mantis/Classes/MantisSettings.h b/Plugins/Mantis/Source/Mantis/Classes/MantisSettings.h new file mode 100644 index 0000000..22551e7 --- /dev/null +++ b/Plugins/Mantis/Source/Mantis/Classes/MantisSettings.h @@ -0,0 +1,70 @@ +#pragma once +#include "Engine/EngineTypes.h" +#include "MantisSettings.generated.h" + +USTRUCT() +struct FBatchBinding +{ + GENERATED_BODY() + + /* Initialize the struct using a string from the config file. */ + FBatchBinding( FString& InString ) + { + FString CleanString = InString.Mid( 1, InString.Len() - 2 ); + + TArray VariableStrings; + CleanString.ParseIntoArray( VariableStrings, TEXT(",") ); + + TMap Variables; + for ( int32 i = 0; i < VariableStrings.Num(); i++ ) + { + TArray VariableString; + VariableStrings[i].ParseIntoArray( VariableString, TEXT( "=" ) ); + if ( VariableString.Num() != 2 ) continue; + Variables.Add( VariableString[0], VariableString[1].TrimQuotes() ); + } + + FString* FieldIDValue = Variables.Find( "FieldID" ); + if ( FieldIDValue != nullptr ) FieldID = *FieldIDValue; + FString* BatchPathValue = Variables.Find( "BatchPath" ); + if ( BatchPathValue != nullptr ) BatchPath = *BatchPathValue; + } + + /* Default constructor. */ + FBatchBinding() {} + + /* + * The ID of the field. + * e.g. category_id + */ + UPROPERTY( Config, EditAnywhere ) + FString FieldID; + + /* + * The path to the batch script. This is can be a relative path from the project folder (where the .uproject is located) or an absolute path. + * Relative e.g. /Scripts/GetCategory.bat + * Absolute e.g. C:/Perforce/Scripts/GetCategory.bat + */ + UPROPERTY( Config, EditAnywhere ) + FString BatchPath; +}; + +UCLASS(config=Engine) +class UMantisSettings : public UObject +{ + GENERATED_UCLASS_BODY() +public: + /* + * This is the root url of the Mantis. + * e.g. if the login url is: http://bugs.yourdomain.com/login_page.php + * this would be http://bugs.youdomain.com/ + */ + UPROPERTY( Config, EditAnywhere, Category = General ) + FString HostURL; + + /* + * A list of batch scrips you want to use to load data for certain fields in the crash report. + */ + UPROPERTY( Config, EditAnywhere, Category = Advanced ) + TArray ScriptBindings; +}; \ No newline at end of file diff --git a/Plugins/Mantis/Source/Mantis/Mantis.Build.cs b/Plugins/Mantis/Source/Mantis/Mantis.Build.cs new file mode 100644 index 0000000..fcbb659 --- /dev/null +++ b/Plugins/Mantis/Source/Mantis/Mantis.Build.cs @@ -0,0 +1,29 @@ +////////////////////////////////////////// +// Author: Yoshi van Belkom - 130118 +////////////////////////////////////////// +// The Mantis Editor Module Settings. +////////////////////////////////////////// + +namespace UnrealBuildTool.Rules +{ + public class Mantis : ModuleRules + { + public Mantis(TargetInfo Target) + { + PublicDependencyModuleNames.AddRange( + new string[] { + "Core", + "CoreUObject", + "Engine", + "UnrealEd" + } + ); + + PrivateIncludePathModuleNames.AddRange( + new string[] { + "Settings" + } + ); + } + } +} \ No newline at end of file diff --git a/Plugins/Mantis/Source/Mantis/Private/Mantis.cpp b/Plugins/Mantis/Source/Mantis/Private/Mantis.cpp new file mode 100644 index 0000000..7ced544 --- /dev/null +++ b/Plugins/Mantis/Source/Mantis/Private/Mantis.cpp @@ -0,0 +1,332 @@ +// Project Lab - NHTV IGAD +////////////////////////////////////////// +// Author: Yoshi van Belkom - 130118 +////////////////////////////////////////// + +#include "MantisPrivatePCH.h" +#include "ISettingsModule.h" +#include "MantisSettings.h" +#include "Runtime/Launch/Resources/Version.h" +#if PLATFORM_WINDOWS + #include + #pragma comment( lib, "version.lib" ) +#endif + +//defines +#define LOCTEXT_NAMESPACE "Mantis" +#define STRINGIZE_(x) #x +#define STRINGIZE(x) STRINGIZE_(x) +#define PlatformFile FPlatformFileManager::Get().GetPlatformFile() + +class FMantis : public IMantis +{ +public: + /* IModuleInterface implementation */ + virtual void StartupModule() override; + virtual void ShutdownModule() override; + /* End IModuleInterface implementation */ + FString GetEngineCrashClientPath(); + FString GetMantisCrashClientPath(); + void RegisterSettings(); + void UnregisterSettings(); + int32 IsMantisExe( FString FilePath ); + void UpdateMantis(); + bool MakeBackup( FString Original, FString Backup, bool bOverwrite = false ); +}; + +DEFINE_LOG_CATEGORY(LogMantis); + +//Get the path of the Engine Crash Report +FString FMantis::GetEngineCrashClientPath() +{ + //Get Engine root path. + FString EnginePath = FPaths::RootDir(); + FPaths::NormalizeDirectoryName(EnginePath); + TArray< FString > EnginePathDirs; + EnginePath.ParseIntoArray(EnginePathDirs, TEXT("/"), 1); + + //Get the path to the CrashReportClient.exe + FString EngineCrashClientPath; + EnginePathDirs.Push(TEXT("Engine")); + EnginePathDirs.Push(TEXT("Binaries")); + EnginePathDirs.Push(TEXT("Win64")); + EnginePathDirs.Push(TEXT("CrashReportClient.exe")); + EngineCrashClientPath = FString::Join(EnginePathDirs, TEXT("/")); + + //Check if file exists. + if (FPaths::FileExists(EngineCrashClientPath)) + { + UE_LOG(LogMantis, Log, TEXT("Engine Crash Report file found!")); + } + else + { + UE_LOG(LogMantis, Warning, TEXT("Engine Crash Report file not found!")); + } + + return EngineCrashClientPath; +} + +FString FMantis::GetMantisCrashClientPath() +{ + //Get the path of the Project File + FString ProjectPath = FPaths::GetPath( FPaths::GetProjectFilePath() ); + FPaths::NormalizeDirectoryName( ProjectPath ); + //Get the path to the Mantis Crash Report + TArray< FString > ProjectPathDirs; + ProjectPath.ParseIntoArray( ProjectPathDirs, TEXT( "/" ), 1 ); + FString MantisCrashClientPath; + ProjectPathDirs.Push( TEXT( "Plugins" ) ); + ProjectPathDirs.Push( TEXT( "Mantis" ) ); + ProjectPathDirs.Push( TEXT( "Resources" ) ); + ProjectPathDirs.Push( TEXT( "CrashReporter" ) ); + ProjectPathDirs.Push( TEXT( STRINGIZE( ENGINE_MAJOR_VERSION ) "." STRINGIZE( ENGINE_MINOR_VERSION ) ) ); +#if PLATFORM_64BITS + ProjectPathDirs.Push( TEXT( "Win64" ) ); +#else + ProjectPathDirs.Push( TEXT( "Win32" ) ); +#endif + ProjectPathDirs.Push( TEXT( "CrashReportClient.exe" ) ); + MantisCrashClientPath = FString::Join( ProjectPathDirs, TEXT( "/" ) ); + return MantisCrashClientPath; +} + +void FMantis::RegisterSettings() +{ + ISettingsModule* SettingsModule = FModuleManager::GetModulePtr("Settings"); + + if (SettingsModule != nullptr) + { + SettingsModule->RegisterSettings("Project", "Plugins", "MantisPlugin", + LOCTEXT("MantisSettingsName", "Mantis Plugin"), + LOCTEXT("MantisSettingsDescription", "Set the setting for the Mantis Plugin."), + GetMutableDefault() + ); + } + else + { + UE_LOG(LogMantis, Log, TEXT("SettingsModule is null!")); + } +} + +void FMantis::UnregisterSettings() +{ + ISettingsModule* SettingsModule = FModuleManager::GetModulePtr("Settings"); + if (SettingsModule != nullptr) + { + SettingsModule->UnregisterSettings("Project", "Plugins", "MantisPlugin"); + } +} + +void FMantis::UpdateMantis() +{ + //Get the path of the Engine Crash Report + FString EngineCrashClientPath = GetEngineCrashClientPath(); + FString EngineCrashClientBackupPath = EngineCrashClientPath + ".ue"; + FString EngineCrashClientTmpPath = EngineCrashClientPath + ".tmp"; + + //Get the path to the Mantis Crash Report + FString MantisCrashClientPath = GetMantisCrashClientPath(); + UE_LOG( LogMantis, Log, TEXT( "MantisCrashClientPath: %s" ), *MantisCrashClientPath ); + if ( FPaths::FileExists( MantisCrashClientPath ) ) + { + UE_LOG( LogMantis, Log, TEXT( "Mantis Crash Report file found!" ) ); + } + else + { + UE_LOG( LogMantis, Warning, TEXT( "Mantis Crash Report file not found!" ) ); + return; + } + + //Removing the read-only tag + PlatformFile.SetReadOnly( *EngineCrashClientPath, false ); + + if ( IsMantisExe( EngineCrashClientPath ) == 1 ) + { + FDateTime EngineTimestamp = PlatformFile.GetTimeStamp( *EngineCrashClientPath ); + FDateTime MantisTimestamp = PlatformFile.GetTimeStamp( *MantisCrashClientPath ); + if ( EngineTimestamp >= MantisTimestamp ) + { + UE_LOG( LogMantis, Log, TEXT( "Crash Report Client is up-to-date!" ) ); + return; + } + else + { + UE_LOG( LogMantis, Log, TEXT( "Crash Report Client is out-of-date!" ) ); + } + } + + //Make temporary backup + if ( !MakeBackup( EngineCrashClientPath, EngineCrashClientTmpPath, true ) ) + { + UE_LOG( LogMantis, Warning, TEXT( "Couldn't make temporary Crash Report Client backup!" ) ); + return; + } + + //Delete current file. + if ( !PlatformFile.DeleteFile( *EngineCrashClientPath ) ) + { + UE_LOG( LogMantis, Log, TEXT( "Failed to delete the current Crash Report Client." ) ); + return; + } + + //Copy file if out of date. + if ( PlatformFile.CopyFile( *EngineCrashClientPath, *MantisCrashClientPath ) ) + { + UE_LOG( LogMantis, Log, TEXT( "Crash Report Client is updated!" ) ); + } + else + { + int32 errorNum = FPlatformMisc::GetLastError(); + UE_LOG( LogMantis, Warning, TEXT( "Failed to update Crash Report Client! (Error: %i)" ), errorNum ); + + if ( PlatformFile.CopyFile( *EngineCrashClientPath, *EngineCrashClientPath ) ) + { + UE_LOG( LogMantis, Log, TEXT( "Restored Crash Report Client." ) ); + } + else + { + errorNum = FPlatformMisc::GetLastError(); + UE_LOG( LogMantis, Warning, TEXT( "Failed to restore Crash Report Client! (Error: %i)" ), errorNum ); + } + return; + } + + //Delete temporary backup file. + if ( !PlatformFile.DeleteFile( *EngineCrashClientTmpPath ) ) + { + UE_LOG( LogMantis, Log, TEXT( "Failed to delete the current Crash Report Client." ) ); + return; + } + + //Removing the read-only tag + PlatformFile.SetReadOnly( *EngineCrashClientPath, false ); +} + +bool FMantis::MakeBackup( FString Original, FString Backup, bool bOverwrite ) +{ + //Delete backup if it already exists. + if ( bOverwrite && FPaths::FileExists( Backup ) ) + { + PlatformFile.DeleteFile( *Backup ); + } + + //Make backup. + if ( PlatformFile.CopyFile( *Backup, *Original ) ) + { + UE_LOG( LogMantis, Log, TEXT( "Made a backup." ) ); + } + else + { + int32 errorNum = FPlatformMisc::GetLastError(); + UE_LOG( LogMantis, Warning, TEXT( "Original path: %s" ), *Original ); + UE_LOG( LogMantis, Warning, TEXT( "Backup path: %s" ), *Backup ); + UE_LOG( LogMantis, Warning, TEXT( "Failed to make a backup! (Error: %i)" ), errorNum ); + return false; + } + return true; +} + +void FMantis::StartupModule() +{ + //Register the settings in the editor. + RegisterSettings(); + +#if PLATFORM_WINDOWS + //Get the path of the Engine Crash Report + FString EngineCrashClientPath = GetEngineCrashClientPath(); + FString EngineCrashClientBackupPath = EngineCrashClientPath + ".ue"; + + int32 Result = IsMantisExe( EngineCrashClientPath ); + if ( Result == 1 ) + { + UpdateMantis(); + } + else if ( Result == 0 ) + { + MakeBackup( EngineCrashClientPath, EngineCrashClientBackupPath, true ); + UpdateMantis(); + } + +#endif +} + +void FMantis::ShutdownModule() +{ + //Unregister the settings in the editor. + UnregisterSettings(); + +#if PLATFORM_WINDOWS + //Get the path of the Engine Crash Report + FString EngineCrashClientPath = GetEngineCrashClientPath(); + FString EngineCrashClientBackupPath = EngineCrashClientPath + ".ue"; + + //Can't restore if there is no backup. + if ( !FPaths::FileExists( EngineCrashClientBackupPath ) ) + { + UE_LOG( LogMantis, Warning, TEXT( "Backup file not found!" ) ); + return; + } + + //Check if backup is valid + if ( IsMantisExe( EngineCrashClientBackupPath ) == 1 ) + { + UE_LOG( LogMantis, Warning, TEXT( "Backup file is not from Unreal!" ) ); + return; + } + + //Check if client is valid + int32 Result = IsMantisExe( EngineCrashClientPath ); + if ( Result == 1 ) + { + MakeBackup( EngineCrashClientBackupPath, EngineCrashClientPath, true ); + } +#endif + // This function may be called during shutdown to clean up the module. For modules that support dynamic reloading, + // this function gets called before unloading the module. +} + +int32 FMantis::IsMantisExe( FString FilePath ) +{ +#if PLATFORM_WINDOWS + // get the filename of the executable containing the version resource + LPWSTR FilePathW = (WCHAR*) *FilePath; + + // allocate a block of memory for the version info + uint32 dummy; + uint32 dwSize = GetFileVersionInfoSize( FilePathW, (LPDWORD)&dummy ); + if ( dwSize == 0 ) + { + UE_LOG( LogMantis, Log, TEXT( "GetFileVersionInfoSize failed with error %d\n" ), FPlatformMisc::GetLastError() ); + return -1; + } + + TArray data; + data.SetNum( dwSize ); + // load the version info + if ( !GetFileVersionInfo( FilePathW, NULL, dwSize, &data[0] ) ) + { + UE_LOG( LogMantis, Log, TEXT( "GetFileVersionInfo failed with error %d\n" ), FPlatformMisc::GetLastError() ); + return -1; + } + + LPVOID pvFileDescription = NULL; + uint32 FileDescriptionLen = 0; + + if ( !VerQueryValue( &data[0], _T( "\\StringFileInfo\\040904b0\\FileDescription" ), &pvFileDescription, &FileDescriptionLen ) ) + { + UE_LOG( LogMantis, Log, TEXT( "Can't obtain ProductName and ProductVersion from resources\n" ) ); + return -1; + } + + FString MantisClientName = TEXT( "MantisCrashReportClient" ); + TCHAR* ClientNameW = (TCHAR*)pvFileDescription; + FString ClientName = ClientNameW; + return ( ClientName == MantisClientName ) ? 1 : 0; +#else + return 0; +#endif +} + +IMPLEMENT_MODULE( FMantis, Mantis ) + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Plugins/Mantis/Source/Mantis/Private/MantisPrivatePCH.h b/Plugins/Mantis/Source/Mantis/Private/MantisPrivatePCH.h new file mode 100644 index 0000000..47aaf5e --- /dev/null +++ b/Plugins/Mantis/Source/Mantis/Private/MantisPrivatePCH.h @@ -0,0 +1,12 @@ +// Project Lab - NHTV IGAD +////////////////////////////////////////// +// Author: Yoshi van Belkom - 130118 +////////////////////////////////////////// +// The Mantis Module Pre Compiled Header. +////////////////////////////////////////// + +#pragma once + +#include "IMantis.h" + +DECLARE_LOG_CATEGORY_EXTERN(LogMantis, Log, All); \ No newline at end of file diff --git a/Plugins/Mantis/Source/Mantis/Private/MantisSettings.cpp b/Plugins/Mantis/Source/Mantis/Private/MantisSettings.cpp new file mode 100644 index 0000000..5c3c6d4 --- /dev/null +++ b/Plugins/Mantis/Source/Mantis/Private/MantisSettings.cpp @@ -0,0 +1,7 @@ +#pragma once + +#include "MantisPrivatePCH.h" +#include "MantisSettings.h" + +UMantisSettings::UMantisSettings(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer), HostURL(TEXT("")) +{} \ No newline at end of file diff --git a/Plugins/Mantis/Source/Mantis/Public/IMantis.h b/Plugins/Mantis/Source/Mantis/Public/IMantis.h new file mode 100644 index 0000000..d42c98b --- /dev/null +++ b/Plugins/Mantis/Source/Mantis/Public/IMantis.h @@ -0,0 +1,27 @@ +// Project Lab - NHTV IGAD +////////////////////////////////////////// +// Author: Yoshi van Belkom - 130118 +////////////////////////////////////////// +// The Mantis Editor Module Interface. +////////////////////////////////////////// + +#pragma once + +#include "ModuleManager.h" + +class IMantis : public IModuleInterface +{ +public: + //Function to Get the Singleton of this Module if it's loaded. + static inline IMantis& Get() + { + return FModuleManager::LoadModuleChecked< IMantis >( "Mantis" ); + } + + //Function to check if the Module is loaded. + static inline bool IsAvailable() + { + return FModuleManager::Get().IsModuleLoaded( "Mantis" ); + } +}; + diff --git a/Plugins/PolishBuildings/PolishBuildings.uplugin b/Plugins/PolishBuildings/PolishBuildings.uplugin new file mode 100644 index 0000000..210a8df --- /dev/null +++ b/Plugins/PolishBuildings/PolishBuildings.uplugin @@ -0,0 +1,24 @@ +{ + "FileVersion": 3, + "Version": 1, + "VersionName": "1.0", + "FriendlyName": "Polish Buildings", + "Description": "A plugin to polish the buildings.", + "Category": "2D", + "CreatedBy": "Yoshi van Belkom", + "CreatedByURL": "http://www.yoshivanbelkom.com/", + "DocsURL": "", + "MarketplaceURL": "", + "SupportURL": "", + "Modules": [ + { + "Name": "PolishBuildings", + "Type": "Editor", + "LoadingPhase": "PreDefault" + } + ], + "EnabledByDefault": false, + "CanContainContent": true, + "IsBetaVersion": false, + "Installed": false +} \ No newline at end of file diff --git a/Plugins/PolishBuildings/Resources/Icon128.png b/Plugins/PolishBuildings/Resources/Icon128.png new file mode 100644 index 0000000..afab71b Binary files /dev/null and b/Plugins/PolishBuildings/Resources/Icon128.png differ diff --git a/Plugins/PolishBuildings/Source/PolishBuildings.Build.cs b/Plugins/PolishBuildings/Source/PolishBuildings.Build.cs new file mode 100644 index 0000000..5494582 --- /dev/null +++ b/Plugins/PolishBuildings/Source/PolishBuildings.Build.cs @@ -0,0 +1,54 @@ +// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; + +public class PolishBuildings : ModuleRules +{ + public PolishBuildings(TargetInfo Target) + { + PrivateIncludePaths.AddRange( + new string[] { + "Private", + "Private/PolishBuildingsTool", + "Private/BuildingsProxyTool" + } + ); + + PrivateIncludePathModuleNames.AddRange( + new string[] { + "AssetRegistry", + "ContentBrowser", + "Documentation", + "LevelEditor", + "MeshUtilities", + "PropertyEditor", + "RawMesh", + "WorkspaceMenuStructure" + } + ); + + PrivateDependencyModuleNames.AddRange( + new string[] { + "Core", + "CoreUObject", + "Engine", + "InputCore", + "Slate", + "SlateCore", + "EditorStyle", + "UnrealEd", + } + ); + + DynamicallyLoadedModuleNames.AddRange( + new string[] { + "AssetRegistry", + "ContentBrowser", + "Documentation", + "LevelEditor", + "MeshUtilities", + "PropertyEditor" + } + ); + } +} diff --git a/Plugins/PolishBuildings/Source/Private/BuildingsProxyTool/BuildingsProxyTool.cpp b/Plugins/PolishBuildings/Source/Private/BuildingsProxyTool/BuildingsProxyTool.cpp new file mode 100644 index 0000000..74557ac --- /dev/null +++ b/Plugins/PolishBuildings/Source/Private/BuildingsProxyTool/BuildingsProxyTool.cpp @@ -0,0 +1,128 @@ +// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved. + +#include "PolishBuildingsPrivatePCH.h" +#include "BuildingsProxyTool.h" +#include "SBuildingsProxyDialog.h" +#include "MeshUtilities.h" +#include "ContentBrowserModule.h" +#include "AssetRegistryModule.h" +#include "Engine/StaticMeshActor.h" +#include "Engine/StaticMesh.h" +#include "Engine/Selection.h" + + +#define LOCTEXT_NAMESPACE "BuildingsProxyTool" + + +TSharedRef FBuildingsProxyTool::GetWidget() +{ + return SNew(SBuildingsProxyDialog, this); +} + + +FText FBuildingsProxyTool::GetTooltipText() const +{ + return LOCTEXT("BuildingsProxyToolTooltip", "Harvest geometry from selected actors and merge them into single mesh."); +} + + +FString FBuildingsProxyTool::GetDefaultPackageName() const +{ + FString PackageName; + + USelection* SelectedActors = GEditor->GetSelectedActors(); + + // Iterate through selected actors and find first static mesh asset + // Use this static mesh path as destination package name for a merged mesh + for (FSelectionIterator Iter(*SelectedActors); Iter; ++Iter) + { + AActor* Actor = Cast(*Iter); + if (Actor) + { + TInlineComponentArray SMComponets; + Actor->GetComponents(SMComponets); + for (UStaticMeshComponent* Component : SMComponets) + { + if (Component->StaticMesh) + { + PackageName = FPackageName::GetLongPackagePath(Component->StaticMesh->GetOutermost()->GetName()); + PackageName += FString(TEXT("/PROXY_")) + Component->StaticMesh->GetName(); + break; + } + } + } + + if (!PackageName.IsEmpty()) + { + break; + } + } + + if (PackageName.IsEmpty()) + { + PackageName = FPackageName::FilenameToLongPackageName(FPaths::GameContentDir() + TEXT("PROXY")); + PackageName = MakeUniqueObjectName(NULL, UPackage::StaticClass(), *PackageName).ToString(); + } + + return PackageName; +} + + +bool FBuildingsProxyTool::RunMerge(const FString& PackageName) +{ + TArray Actors; + TArray AssetsToSync; + + //Get the module for the proxy mesh utilities + IMeshUtilities& MeshUtilities = FModuleManager::Get().LoadModuleChecked("MeshUtilities"); + + USelection* SelectedActors = GEditor->GetSelectedActors(); + for (FSelectionIterator Iter(*SelectedActors); Iter; ++Iter) + { + AActor* Actor = Cast(*Iter); + if (Actor) + { + Actors.Add(Actor); + } + } + + if (Actors.Num()) + { + GWarn->BeginSlowTask(LOCTEXT("BuildingsProxy_CreatingProxy", "Creating Mesh Proxy"), true); + GEditor->BeginTransaction(LOCTEXT("BuildingsProxy_Create", "Creating Mesh Proxy")); + + FVector ProxyLocation = FVector::ZeroVector; + + FCreateProxyDelegate ProxyDelegate; + ProxyDelegate.BindLambda( + []( const FGuid Guid, TArray& InAssetsToSync ) + { + //Update the asset registry that a new static mash and material has been created + if ( InAssetsToSync.Num() ) + { + FAssetRegistryModule& AssetRegistry = FModuleManager::Get().LoadModuleChecked( "AssetRegistry" ); + int32 AssetCount = InAssetsToSync.Num(); + for ( int32 AssetIndex = 0; AssetIndex < AssetCount; AssetIndex++ ) + { + AssetRegistry.AssetCreated( InAssetsToSync[AssetIndex] ); + GEditor->BroadcastObjectReimported( InAssetsToSync[AssetIndex] ); + } + + //Also notify the content browser that the new assets exists + FContentBrowserModule& ContentBrowserModule = FModuleManager::Get().LoadModuleChecked( "ContentBrowser" ); + ContentBrowserModule.Get().SyncBrowserToAssets( InAssetsToSync, true ); + } + } ); + + FGuid JobGuid = FGuid::NewGuid(); + MeshUtilities.CreateProxyMesh( Actors, ProxySettings, NULL, PackageName, JobGuid, ProxyDelegate ); + + GEditor->EndTransaction(); + GWarn->EndSlowTask(); + } + + return true; +} + + +#undef LOCTEXT_NAMESPACE diff --git a/Plugins/PolishBuildings/Source/Private/BuildingsProxyTool/BuildingsProxyTool.h b/Plugins/PolishBuildings/Source/Private/BuildingsProxyTool/BuildingsProxyTool.h new file mode 100644 index 0000000..3596a20 --- /dev/null +++ b/Plugins/PolishBuildings/Source/Private/BuildingsProxyTool/BuildingsProxyTool.h @@ -0,0 +1,25 @@ +// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved. +#pragma once + +#include "IPolishBuildingsTool.h" + +/** +* Mesh Proxy Tool +*/ +class FBuildingsProxyTool : public IPolishBuildingsTool +{ + friend class SBuildingsProxyDialog; + +public: + + // IMergeActorsTool interface + virtual TSharedRef GetWidget() override; + virtual FName GetIconName() const override { return "PolishBuildings.BuildingsProxyTool"; } + virtual FText GetTooltipText() const override; + virtual FString GetDefaultPackageName() const override; + virtual bool RunMerge(const FString& PackageName) override; + +private: + + FMeshProxySettings ProxySettings; +}; diff --git a/Plugins/PolishBuildings/Source/Private/BuildingsProxyTool/SBuildingsProxyDialog.cpp b/Plugins/PolishBuildings/Source/Private/BuildingsProxyTool/SBuildingsProxyDialog.cpp new file mode 100644 index 0000000..8f011f7 --- /dev/null +++ b/Plugins/PolishBuildings/Source/Private/BuildingsProxyTool/SBuildingsProxyDialog.cpp @@ -0,0 +1,614 @@ +// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. + +#include "PolishBuildingsPrivatePCH.h" +#include "SBuildingsProxyDialog.h" +#include "Dialogs/DlgPickAssetPath.h" +#include "SNumericEntryBox.h" +#include "STextComboBox.h" +#include "BuildingsProxyTool/BuildingsProxyTool.h" + +#define LOCTEXT_NAMESPACE "SBuildingsProxyDialog" + + +void SBuildingsProxyDialog::Construct(const FArguments& InArgs, FBuildingsProxyTool* InTool) +{ + Tool = InTool; + check(Tool != nullptr); + + CuttingPlaneOptions.Add(MakeShareable(new FString(TEXT("+X")))); + CuttingPlaneOptions.Add(MakeShareable(new FString(TEXT("+Y")))); + CuttingPlaneOptions.Add(MakeShareable(new FString(TEXT("+Z")))); + CuttingPlaneOptions.Add(MakeShareable(new FString(TEXT("-X")))); + CuttingPlaneOptions.Add(MakeShareable(new FString(TEXT("-Y")))); + CuttingPlaneOptions.Add(MakeShareable(new FString(TEXT("-Z")))); + + TextureResolutionOptions.Add(MakeShareable(new FString(TEXT("64")))); + TextureResolutionOptions.Add(MakeShareable(new FString(TEXT("128")))); + TextureResolutionOptions.Add(MakeShareable(new FString(TEXT("256")))); + TextureResolutionOptions.Add(MakeShareable(new FString(TEXT("512")))); + TextureResolutionOptions.Add(MakeShareable(new FString(TEXT("1024")))); + TextureResolutionOptions.Add(MakeShareable(new FString(TEXT("2048")))); + + CreateLayout(); +} + +BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION +void SBuildingsProxyDialog::CreateLayout() +{ + int32 TextureResEntryIndex = FindTextureResolutionEntryIndex( Tool->ProxySettings.MaterialSettings.TextureSize.X ); + int32 LightMapResEntryIndex = FindTextureResolutionEntryIndex( Tool->ProxySettings.LightMapResolution ); + TextureResEntryIndex = FMath::Max( TextureResEntryIndex, 0 ); + LightMapResEntryIndex = FMath::Max( LightMapResEntryIndex, 0 ); + + this->ChildSlot + [ + SNew(SVerticalBox) + + +SVerticalBox::Slot() + .AutoHeight() + .HAlign(HAlign_Center) + .VAlign(VAlign_Center) + .Padding(10) + [ + // Simplygon logo + SNew(SImage) + .Image(FEditorStyle::GetBrush("BuildingsProxy.SimplygonLogo")) + ] + + +SVerticalBox::Slot() + .AutoHeight() + [ + SNew(SBorder) + .BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder")) + [ + // Proxy options + SNew(SVerticalBox) + + +SVerticalBox::Slot() + .AutoHeight() + .Padding(FEditorStyle::GetMargin("StandardDialog.ContentPadding")) + [ + SNew(SHorizontalBox) + +SHorizontalBox::Slot() + .FillWidth(0.5f) + .VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(LOCTEXT("OnScreenSizeLabel", "On Screen Size (pixels)")) + .Font(FEditorStyle::GetFontStyle("StandardDialog.SmallFont")) + .ToolTipText( GetPropertyToolTipText( GET_MEMBER_NAME_CHECKED( FMeshProxySettings, ScreenSize ) ) ) + ] + + SHorizontalBox::Slot() + .FillWidth(0.5f) + .HAlign(HAlign_Left) + .VAlign(VAlign_Center) + [ + SNew(SBox) + .HAlign(HAlign_Fill) + .MinDesiredWidth(100.0f) + .MaxDesiredWidth(100.0f) + [ + SNew(SNumericEntryBox) + .Font(FEditorStyle::GetFontStyle("StandardDialog.SmallFont")) + .MinValue(40) + .MaxValue(1200) + .MinSliderValue(40) + .MaxSliderValue(1200) + .AllowSpin(true) + .Value(this, &SBuildingsProxyDialog::GetScreenSize) + .OnValueChanged(this, &SBuildingsProxyDialog::ScreenSizeChanged) + ] + ] + ] + + +SVerticalBox::Slot() + .AutoHeight() + .Padding(FEditorStyle::GetMargin("StandardDialog.ContentPadding")) + [ + SNew(SHorizontalBox) + +SHorizontalBox::Slot() + .FillWidth(0.5f) + .VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(LOCTEXT("MergeDistanceLabel", "Merge Distance (pixels)")) + .Font(FEditorStyle::GetFontStyle("StandardDialog.SmallFont")) + .ToolTipText( GetPropertyToolTipText( GET_MEMBER_NAME_CHECKED( FMeshProxySettings, MergeDistance ) ) ) + ] + + SHorizontalBox::Slot() + .FillWidth(0.5f) + .HAlign(HAlign_Left) + .VAlign(VAlign_Center) + [ + SNew(SBox) + .HAlign(HAlign_Fill) + .MinDesiredWidth(100.0f) + .MaxDesiredWidth(100.0f) + [ + SNew(SNumericEntryBox) + .Font(FEditorStyle::GetFontStyle("StandardDialog.SmallFont")) + .MinValue(0) + .MaxValue(300) + .MinSliderValue(0) + .MaxSliderValue(300) + .AllowSpin(true) + .Value(this, &SBuildingsProxyDialog::GetMergeDistance) + .OnValueChanged(this, &SBuildingsProxyDialog::MergeDistanceChanged) + ] + ] + ] + + +SVerticalBox::Slot() + .AutoHeight() + .Padding(FEditorStyle::GetMargin("StandardDialog.ContentPadding")) + [ + SNew(SHorizontalBox) + +SHorizontalBox::Slot() + .FillWidth(0.5f) + .VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(LOCTEXT("TextureResolutionLabel", "Texture Resolution")) + .Font(FEditorStyle::GetFontStyle("StandardDialog.SmallFont")) + ] + + SHorizontalBox::Slot() + .FillWidth(0.5f) + .HAlign(HAlign_Left) + .VAlign(VAlign_Center) + [ + SNew(STextComboBox) + .Font(FEditorStyle::GetFontStyle("StandardDialog.SmallFont")) + .OptionsSource(&TextureResolutionOptions) + .InitiallySelectedItem( TextureResolutionOptions[TextureResEntryIndex] ) + .OnSelectionChanged(this, &SBuildingsProxyDialog::SetTextureResolution) + ] + ] + + +SVerticalBox::Slot() + .AutoHeight() + .Padding(FEditorStyle::GetMargin("StandardDialog.ContentPadding")) + [ + SNew( SHorizontalBox ) + + SHorizontalBox::Slot() + .FillWidth( 0.5f ) + .VAlign( VAlign_Center ) + [ + SNew(STextBlock) + .Text( LOCTEXT( "LightMapResolutionLabel", "LightMap Resolution" ) ) + .Font( FEditorStyle::GetFontStyle( "StandardDialog.SmallFont" ) ) + .ToolTipText( GetPropertyToolTipText( GET_MEMBER_NAME_CHECKED( FMeshProxySettings, LightMapResolution ) ) ) + ] + + SHorizontalBox::Slot() + .FillWidth( 0.5f ) + .HAlign( HAlign_Left ) + .VAlign( VAlign_Center ) + [ + SNew( STextComboBox ) + .Font( FEditorStyle::GetFontStyle( "StandardDialog.SmallFont" ) ) + .OptionsSource( &TextureResolutionOptions ) + .InitiallySelectedItem( TextureResolutionOptions[LightMapResEntryIndex] ) + .OnSelectionChanged( this, &SBuildingsProxyDialog::SetLightMapResolution ) + ] + ] + + +SVerticalBox::Slot() + .AutoHeight() + .Padding(FEditorStyle::GetMargin("StandardDialog.ContentPadding")) + [ + SNew(SHorizontalBox) + +SHorizontalBox::Slot() + .FillWidth(0.5f) + .VAlign(VAlign_Center) + .Padding(0.0, 0.0, 3.0, 0.0) + [ + SNew( STextBlock ) + .Text( LOCTEXT( "HardAngleLabel", "Hard Edge Angle" ) ) + .Font( FEditorStyle::GetFontStyle( "StandardDialog.SmallFont" ) ) + .ToolTipText( GetPropertyToolTipText( GET_MEMBER_NAME_CHECKED( FMeshProxySettings, HardAngleThreshold ) ) ) + ] + +SHorizontalBox::Slot() + .FillWidth(0.5f) + .HAlign(HAlign_Left) + .VAlign(VAlign_Center) + [ + SNew(SBox) + .HAlign(HAlign_Fill) + .MinDesiredWidth(100.0f) + .MaxDesiredWidth(100.0f) + [ + SNew(SNumericEntryBox) + .Font(FEditorStyle::GetFontStyle("StandardDialog.SmallFont")) + .MinValue(0.f) + .MaxValue(180.f) + .MinSliderValue(0.f) + .MaxSliderValue(180.f) + .AllowSpin(true) + .Value(this, &SBuildingsProxyDialog::GetHardAngleThreshold) + .OnValueChanged(this, &SBuildingsProxyDialog::HardAngleThresholdChanged) + .IsEnabled(this, &SBuildingsProxyDialog::HardAngleThresholdEnabled) + ] + ] + ] + + +SVerticalBox::Slot() + .AutoHeight() + .Padding(FEditorStyle::GetMargin("StandardDialog.ContentPadding")) + [ + SNew(SCheckBox) + .Type(ESlateCheckBoxType::CheckBox) + .IsChecked( this, &SBuildingsProxyDialog::GetRecalculateNormals ) + .OnCheckStateChanged( this, &SBuildingsProxyDialog::SetRecalculateNormals ) + .Content() + [ + SNew( STextBlock ) + .Text( LOCTEXT( "RecalcNormalsLabel", "Recalculate Normals" ) ) + .Font( FEditorStyle::GetFontStyle( "StandardDialog.SmallFont" ) ) + .ToolTipText( GetPropertyToolTipText( GET_MEMBER_NAME_CHECKED( FMeshProxySettings, bRecalculateNormals ) ) ) + ] + ] + + + SVerticalBox::Slot() + .AutoHeight() + .Padding( FEditorStyle::GetMargin( "StandardDialog.ContentPadding" ) ) + [ + SNew( SCheckBox ) + .Type( ESlateCheckBoxType::CheckBox ) + .IsChecked(this, &SBuildingsProxyDialog::GetUseClippingPlane) + .OnCheckStateChanged(this, &SBuildingsProxyDialog::SetUseClippingPlane) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("ClippingPlaneLabel", "Use Clipping Plane")) + .Font(FEditorStyle::GetFontStyle("StandardDialog.SmallFont")) + ] + ] + + +SVerticalBox::Slot() + .AutoHeight() + .Padding(FEditorStyle::GetMargin("StandardDialog.ContentPadding")) + [ + SNew(SHorizontalBox) + +SHorizontalBox::Slot() + .VAlign(VAlign_Center) + .FillWidth(0.5f) + [ + SNew(SHorizontalBox) + +SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SBox) + .MinDesiredWidth(30) + [ + SNullWidget::NullWidget + ] + ] + +SHorizontalBox::Slot() + .AutoWidth() + .HAlign(HAlign_Left) + [ + SNew(STextBlock) + .IsEnabled(this, &SBuildingsProxyDialog::UseClippingPlaneEnabled) + .Text(LOCTEXT("ClippingAxisLabel", "Clipping Axis")) + .Font(FEditorStyle::GetFontStyle("StandardDialog.SmallFont")) + .ToolTipText( GetPropertyToolTipText( GET_MEMBER_NAME_CHECKED( FMeshProxySettings, AxisIndex ) ) ) + ] + ] + +SHorizontalBox::Slot() + .FillWidth(0.5f) + .HAlign(HAlign_Left) + .VAlign(VAlign_Center) + .Padding(8.0, 0.0, 8.0, 0.0) + [ + SNew(STextComboBox) + .Font(FEditorStyle::GetFontStyle("StandardDialog.SmallFont")) + .OptionsSource(&CuttingPlaneOptions) + .InitiallySelectedItem(CuttingPlaneOptions[0]) + .OnSelectionChanged(this, &SBuildingsProxyDialog::SetClippingAxis) + .IsEnabled(this, &SBuildingsProxyDialog::UseClippingPlaneEnabled) + ] + ] + + +SVerticalBox::Slot() + .AutoHeight() + .Padding(FEditorStyle::GetMargin("StandardDialog.ContentPadding")) + [ + SNew(SHorizontalBox) + +SHorizontalBox::Slot() + .FillWidth(0.5f) + .VAlign(VAlign_Center) + .Padding(0.0, 0.0, 3.0, 0.0) + [ + SNew(SHorizontalBox) + +SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SBox) + .MinDesiredWidth(30) + [ + SNullWidget::NullWidget + ] + ] + +SHorizontalBox::Slot() + .HAlign(HAlign_Left) + .AutoWidth() + [ + SNew(STextBlock) + .Text(LOCTEXT("PlaneLevelLabel", "Plane level")) + .Font(FEditorStyle::GetFontStyle("StandardDialog.SmallFont")) + .IsEnabled(this, &SBuildingsProxyDialog::UseClippingPlaneEnabled) + .ToolTipText( GetPropertyToolTipText( GET_MEMBER_NAME_CHECKED( FMeshProxySettings, ClippingLevel ) ) ) + ] + ] + +SHorizontalBox::Slot() + .FillWidth(0.5f) + .HAlign(HAlign_Left) + [ + SNew(SBox) + .HAlign(HAlign_Fill) + .MinDesiredWidth(100.0f) + .MaxDesiredWidth(100.0f) + [ + SNew(SNumericEntryBox) + .Font(FEditorStyle::GetFontStyle("StandardDialog.SmallFont")) + .Value(this, &SBuildingsProxyDialog::GetClippingLevel) + .OnValueChanged(this, &SBuildingsProxyDialog::ClippingLevelChanged) + .IsEnabled(this, &SBuildingsProxyDialog::UseClippingPlaneEnabled) + ] + ] + ] + + +SVerticalBox::Slot() + .AutoHeight() + .Padding(FEditorStyle::GetMargin("StandardDialog.ContentPadding")) + [ + SNew(SCheckBox) + .Type(ESlateCheckBoxType::CheckBox) + .IsChecked(this, &SBuildingsProxyDialog::GetExportNormalMap) + .OnCheckStateChanged(this, &SBuildingsProxyDialog::SetExportNormalMap) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("ExportNormalMapLabel", "Export Normal Map")) + .Font(FEditorStyle::GetFontStyle("StandardDialog.SmallFont")) + ] + ] + + +SVerticalBox::Slot() + .AutoHeight() + .Padding(FEditorStyle::GetMargin("StandardDialog.ContentPadding")) + [ + SNew(SCheckBox) + .Type(ESlateCheckBoxType::CheckBox) + .IsChecked(this, &SBuildingsProxyDialog::GetExportMetallicMap) + .OnCheckStateChanged(this, &SBuildingsProxyDialog::SetExportMetallicMap) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("ExportMetallicMapLabel", "Export Metallic Map")) + .Font(FEditorStyle::GetFontStyle("StandardDialog.SmallFont")) + ] + ] + + +SVerticalBox::Slot() + .AutoHeight() + .Padding(FEditorStyle::GetMargin("StandardDialog.ContentPadding")) + [ + SNew(SCheckBox) + .Type(ESlateCheckBoxType::CheckBox) + .IsChecked(this, &SBuildingsProxyDialog::GetExportRoughnessMap) + .OnCheckStateChanged(this, &SBuildingsProxyDialog::SetExportRoughnessMap) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("ExportRoughnessMapLabel", "Export Roughness Map")) + .Font(FEditorStyle::GetFontStyle("StandardDialog.SmallFont")) + ] + ] + + +SVerticalBox::Slot() + .AutoHeight() + .Padding(FEditorStyle::GetMargin("StandardDialog.ContentPadding")) + [ + SNew(SCheckBox) + .Type(ESlateCheckBoxType::CheckBox) + .IsChecked(this, &SBuildingsProxyDialog::GetExportSpecularMap) + .OnCheckStateChanged(this, &SBuildingsProxyDialog::SetExportSpecularMap) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("ExportSpecularMapLabel", "Export Specular Map")) + .Font(FEditorStyle::GetFontStyle("StandardDialog.SmallFont")) + ] + ] + ] + ] + ]; +} +END_SLATE_FUNCTION_BUILD_OPTIMIZATION + +int32 SBuildingsProxyDialog::FindTextureResolutionEntryIndex( int32 InResolution ) const +{ + FString ResolutionStr = TTypeToString::ToString( InResolution ); + + int32 Result = TextureResolutionOptions.IndexOfByPredicate( [&]( const TSharedPtr& Entry ) + { + return ( ResolutionStr == *Entry ); + } ); + + return Result; +} + +FText SBuildingsProxyDialog::GetPropertyToolTipText( const FName& PropertyName ) const +{ + UProperty* Property = FMeshProxySettings::StaticStruct()->FindPropertyByName( PropertyName ); + if ( Property ) + { + return Property->GetToolTipText(); + } + + return FText::GetEmpty(); +} + +//Screen size +TOptional SBuildingsProxyDialog::GetScreenSize() const +{ + return Tool->ProxySettings.ScreenSize; +} + +void SBuildingsProxyDialog::ScreenSizeChanged(int32 NewValue) +{ + Tool->ProxySettings.ScreenSize = NewValue; +} + +//Recalculate normals +ECheckBoxState SBuildingsProxyDialog::GetRecalculateNormals() const +{ + return Tool->ProxySettings.bRecalculateNormals ? ECheckBoxState::Checked: ECheckBoxState::Unchecked; +} + +void SBuildingsProxyDialog::SetRecalculateNormals(ECheckBoxState NewValue) +{ + Tool->ProxySettings.bRecalculateNormals = (NewValue == ECheckBoxState::Checked); +} + +//Hard Angle Threshold +bool SBuildingsProxyDialog::HardAngleThresholdEnabled() const +{ + if(Tool->ProxySettings.bRecalculateNormals) + { + return true; + } + + return false; +} + +TOptional SBuildingsProxyDialog::GetHardAngleThreshold() const +{ + return Tool->ProxySettings.HardAngleThreshold; +} + +void SBuildingsProxyDialog::HardAngleThresholdChanged(float NewValue) +{ + Tool->ProxySettings.HardAngleThreshold = NewValue; +} + +//Merge Distance +TOptional SBuildingsProxyDialog::GetMergeDistance() const +{ + return Tool->ProxySettings.MergeDistance; +} + +void SBuildingsProxyDialog::MergeDistanceChanged(int32 NewValue) +{ + Tool->ProxySettings.MergeDistance = NewValue; +} + +//Clipping Plane +ECheckBoxState SBuildingsProxyDialog::GetUseClippingPlane() const +{ + return Tool->ProxySettings.bUseClippingPlane ? ECheckBoxState::Checked: ECheckBoxState::Unchecked; +} + +void SBuildingsProxyDialog::SetUseClippingPlane(ECheckBoxState NewValue) +{ + Tool->ProxySettings.bUseClippingPlane = (NewValue == ECheckBoxState::Checked); +} + +bool SBuildingsProxyDialog::UseClippingPlaneEnabled() const +{ + if(Tool->ProxySettings.bUseClippingPlane) + { + return true; + } + return false; +} + +void SBuildingsProxyDialog::SetClippingAxis(TSharedPtr NewSelection, ESelectInfo::Type SelectInfo ) +{ + //This is a ugly hack, but it solves the problem for now + int32 AxisIndex = CuttingPlaneOptions.Find(NewSelection); + //FMessageDialog::Debugf(*FString::Printf(TEXT("%d"), AxisIndex )); + if(AxisIndex < 3) + { + Tool->ProxySettings.bPlaneNegativeHalfspace = false; + Tool->ProxySettings.AxisIndex = AxisIndex; + return; + } + else + { + Tool->ProxySettings.bPlaneNegativeHalfspace = true; + Tool->ProxySettings.AxisIndex = AxisIndex - 3; + return; + } +} + +TOptional SBuildingsProxyDialog::GetClippingLevel() const +{ + return Tool->ProxySettings.ClippingLevel; +} + +void SBuildingsProxyDialog::ClippingLevelChanged(float NewValue) +{ + Tool->ProxySettings.ClippingLevel = NewValue; +} + +//Texture Resolution +void SBuildingsProxyDialog::SetTextureResolution(TSharedPtr NewSelection, ESelectInfo::Type SelectInfo) +{ + int32 Resolution = 512; + TTypeFromString::FromString(Resolution, **NewSelection); + FIntPoint TextureSize(Resolution, Resolution); + + Tool->ProxySettings.MaterialSettings.TextureSize = TextureSize; +} + +void SBuildingsProxyDialog::SetLightMapResolution( TSharedPtr NewSelection, ESelectInfo::Type SelectInfo ) +{ + int32 Resolution = 256; + TTypeFromString::FromString( Resolution, **NewSelection ); + + Tool->ProxySettings.LightMapResolution = Resolution; +} + +ECheckBoxState SBuildingsProxyDialog::GetExportNormalMap() const +{ + return Tool->ProxySettings.MaterialSettings.bNormalMap ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; +} + +void SBuildingsProxyDialog::SetExportNormalMap(ECheckBoxState NewValue) +{ + Tool->ProxySettings.MaterialSettings.bNormalMap = ( NewValue == ECheckBoxState::Checked ); +} + +ECheckBoxState SBuildingsProxyDialog::GetExportMetallicMap() const +{ + return Tool->ProxySettings.MaterialSettings.bMetallicMap ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; +} + +void SBuildingsProxyDialog::SetExportMetallicMap(ECheckBoxState NewValue) +{ + Tool->ProxySettings.MaterialSettings.bMetallicMap = ( NewValue == ECheckBoxState::Checked ); +} + +ECheckBoxState SBuildingsProxyDialog::GetExportRoughnessMap() const +{ + return Tool->ProxySettings.MaterialSettings.bRoughnessMap ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; +} + +void SBuildingsProxyDialog::SetExportRoughnessMap(ECheckBoxState NewValue) +{ + Tool->ProxySettings.MaterialSettings.bRoughnessMap = ( NewValue == ECheckBoxState::Checked ); +} + +ECheckBoxState SBuildingsProxyDialog::GetExportSpecularMap() const +{ + return Tool->ProxySettings.MaterialSettings.bSpecularMap ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; +} + +void SBuildingsProxyDialog::SetExportSpecularMap(ECheckBoxState NewValue) +{ + Tool->ProxySettings.MaterialSettings.bSpecularMap = ( NewValue == ECheckBoxState::Checked ); +} + + +#undef LOCTEXT_NAMESPACE diff --git a/Plugins/PolishBuildings/Source/Private/BuildingsProxyTool/SBuildingsProxyDialog.h b/Plugins/PolishBuildings/Source/Private/BuildingsProxyTool/SBuildingsProxyDialog.h new file mode 100644 index 0000000..0b27af7 --- /dev/null +++ b/Plugins/PolishBuildings/Source/Private/BuildingsProxyTool/SBuildingsProxyDialog.h @@ -0,0 +1,75 @@ +// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. + +#pragma once + +class FBuildingsProxyTool; + +/*----------------------------------------------------------------------------- + SBuildingsProxyDialog +-----------------------------------------------------------------------------*/ +class SBuildingsProxyDialog : public SCompoundWidget +{ +public: + SLATE_BEGIN_ARGS(SBuildingsProxyDialog) + { + } + SLATE_END_ARGS() + +public: + /** SWidget functions */ + void Construct(const FArguments& InArgs, FBuildingsProxyTool* InTool); + +protected: + /** ScreenSize accessors */ + TOptional GetScreenSize() const; + void ScreenSizeChanged(int32 NewValue); //used with editable text block (Simplygon) + + /** Recalculate Normals accessors */ + ECheckBoxState GetRecalculateNormals() const; + void SetRecalculateNormals(ECheckBoxState NewValue); + + /** Hard Angle Threshold accessors */ + TOptional GetHardAngleThreshold() const; + bool HardAngleThresholdEnabled() const; + void HardAngleThresholdChanged(float NewValue); + + /** Hole filling accessors */ + TOptional GetMergeDistance() const; + void MergeDistanceChanged(int32 NewValue); + + /** Clipping Plane accessors */ + ECheckBoxState GetUseClippingPlane() const; + void SetUseClippingPlane(ECheckBoxState NewValue); + bool UseClippingPlaneEnabled() const; + void SetClippingAxis(TSharedPtr NewSelection, ESelectInfo::Type SelectInfo); + TOptional GetClippingLevel() const; + void ClippingLevelChanged(float NewValue); + + /** TextureResolution accessors */ + void SetTextureResolution(TSharedPtr NewSelection, ESelectInfo::Type SelectInfo); + void SetLightMapResolution( TSharedPtr NewSelection, ESelectInfo::Type SelectInfo ); + + /** Export material properties acessors **/ + ECheckBoxState GetExportNormalMap() const; + void SetExportNormalMap(ECheckBoxState NewValue); + ECheckBoxState GetExportMetallicMap() const; + void SetExportMetallicMap(ECheckBoxState NewValue); + ECheckBoxState GetExportRoughnessMap() const; + void SetExportRoughnessMap(ECheckBoxState NewValue); + ECheckBoxState GetExportSpecularMap() const; + void SetExportSpecularMap(ECheckBoxState NewValue); + +private: + /** Creates the geometry mode controls */ + void CreateLayout(); + + int32 FindTextureResolutionEntryIndex( int32 InResolution ) const; + FText GetPropertyToolTipText( const FName& PropertyName ) const; + +private: + FBuildingsProxyTool* Tool; + + TArray< TSharedPtr > CuttingPlaneOptions; + TArray< TSharedPtr > TextureResolutionOptions; +}; + diff --git a/Plugins/PolishBuildings/Source/Private/PolishBuildingsModule.cpp b/Plugins/PolishBuildings/Source/Private/PolishBuildingsModule.cpp new file mode 100644 index 0000000..2c44958 --- /dev/null +++ b/Plugins/PolishBuildings/Source/Private/PolishBuildingsModule.cpp @@ -0,0 +1,162 @@ +// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved. + +#include "PolishBuildingsPrivatePCH.h" +#include "SPolishBuildingsToolbar.h" +#include "ModuleManager.h" +#include "WorkspaceMenuStructureModule.h" +#include "PolishBuildingsTool.h" +#include "BuildingsProxyTool.h" +#include "SDockTab.h" + +#define LOCTEXT_NAMESPACE "PolishBuildingsModule" + + +static const FName PolishBuildingsTabName = FName("PolishBuildings"); + +/** + * Merge Actors module + */ +class FPolishBuildingsModule : public IPolishBuildingsModule +{ +public: + + /** + * Called right after the module DLL has been loaded and the module object has been created + */ + virtual void StartupModule() override; + + /** + * Called before the module is unloaded, right before the module object is destroyed. + */ + virtual void ShutdownModule() override; + + /** + * Register an IPolishBuildingsTool with the module, passing ownership to it + */ + virtual bool RegisterPolishBuildingsTool(TUniquePtr Tool) override; + + /** + * Unregister an IPolishBuildingsTool with the module + */ + virtual bool UnregisterPolishBuildingsTool(IPolishBuildingsTool* Tool) override; + + +private: + + /** Creates the dock tab widget used by the tab manager */ + TSharedRef CreatePolishBuildingsTab(const FSpawnTabArgs& Args); + +private: + + /** Pointer to the toolbar widget */ + TWeakPtr PolishBuildingsToolbarPtr; + + /** List of registered PolishBuildingsTool instances */ + TArray> PolishBuildingsTools; +}; + +IMPLEMENT_MODULE(FPolishBuildingsModule, PolishBuildings); + + +TSharedRef FPolishBuildingsModule::CreatePolishBuildingsTab(const FSpawnTabArgs& Args) +{ + // Build array of PolishBuildingsTool raw pointers + TArray ToolsToRegister; + for (const auto& MergeActorTool : PolishBuildingsTools) + { + check(MergeActorTool.Get() != nullptr); + ToolsToRegister.Add(MergeActorTool.Get()); + } + + // Construct toolbar widget + TSharedRef PolishBuildingsToolbar = + SNew(SPolishBuildingsToolbar) + .ToolsToRegister(ToolsToRegister); + + // Construct dock tab + TSharedRef DockTab = + SNew(SDockTab) + .TabRole(ETabRole::NomadTab) + [ + PolishBuildingsToolbar + ]; + + // Keep weak pointer to toolbar widget + PolishBuildingsToolbarPtr = PolishBuildingsToolbar; + + return DockTab; +} + + +void FPolishBuildingsModule::StartupModule() +{ + FGlobalTabmanager::Get()->RegisterNomadTabSpawner(PolishBuildingsTabName, FOnSpawnTab::CreateRaw(this, &FPolishBuildingsModule::CreatePolishBuildingsTab)) + .SetDisplayName(LOCTEXT("TabTitle", "Polish Buildings")) + .SetTooltipText(LOCTEXT("TooltipText", "Open the Polish Buildings tab.")) + .SetGroup(WorkspaceMenu::GetMenuStructure().GetLevelEditorCategory()) + .SetIcon(FSlateIcon(FEditorStyle::GetStyleSetName(), "ClassViewer.TabIcon")); + // This is still experimental in the editor, so it's added specifically in FMainMenu for now. + // When no longer experimental, remove the below. + //.SetAutoGenerateMenuEntry(false); + + // Register built-in merging tools straight away + ensure(RegisterPolishBuildingsTool(MakeUnique())); + + IMeshUtilities* MeshUtilities = FModuleManager::Get().LoadModulePtr("MeshUtilities"); + if (MeshUtilities != nullptr && MeshUtilities->GetMeshMergingInterface() != nullptr) + { + // Only register BuildingsProxyTool if Simplygon is available + ensure(RegisterPolishBuildingsTool(MakeUnique())); + } +} + + +void FPolishBuildingsModule::ShutdownModule() +{ + if (FSlateApplication::IsInitialized()) + { + FGlobalTabmanager::Get()->UnregisterNomadTabSpawner(PolishBuildingsTabName); + } +} + + +bool FPolishBuildingsModule::RegisterPolishBuildingsTool(TUniquePtr Tool) +{ + if (Tool.Get() != nullptr && !PolishBuildingsTools.Contains(MoveTemp(Tool))) + { + PolishBuildingsTools.Add(MoveTemp(Tool)); + + // If a tool is added while the toolbar widget is active, update it accordingly + TSharedPtr PolishBuildingsToolbar = PolishBuildingsToolbarPtr.Pin(); + if (PolishBuildingsToolbar.IsValid()) + { + PolishBuildingsToolbar->AddTool(Tool.Get()); + } + + return true; + } + + return false; +} + + +bool FPolishBuildingsModule::UnregisterPolishBuildingsTool(IPolishBuildingsTool* Tool) +{ + if (Tool != nullptr) + { + if (PolishBuildingsTools.RemoveAll([=](const TUniquePtr& Ptr) { return Ptr.Get() == Tool; }) > 0) + { + TSharedPtr PolishBuildingsToolbar = PolishBuildingsToolbarPtr.Pin(); + if (PolishBuildingsToolbar.IsValid()) + { + PolishBuildingsToolbar->RemoveTool(Tool); + } + + return true; + } + } + return false; +} + + +#undef LOCTEXT_NAMESPACE diff --git a/Plugins/PolishBuildings/Source/Private/PolishBuildingsPrivatePCH.h b/Plugins/PolishBuildings/Source/Private/PolishBuildingsPrivatePCH.h new file mode 100644 index 0000000..0c6663e --- /dev/null +++ b/Plugins/PolishBuildings/Source/Private/PolishBuildingsPrivatePCH.h @@ -0,0 +1,6 @@ +// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "UnrealEd.h" +#include "IPolishBuildingsModule.h" diff --git a/Plugins/PolishBuildings/Source/Private/PolishBuildingsTool/PolishBuildingsTool.cpp b/Plugins/PolishBuildings/Source/Private/PolishBuildingsTool/PolishBuildingsTool.cpp new file mode 100644 index 0000000..ae20391 --- /dev/null +++ b/Plugins/PolishBuildings/Source/Private/PolishBuildingsTool/PolishBuildingsTool.cpp @@ -0,0 +1,149 @@ +// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved. + +#include "PolishBuildingsPrivatePCH.h" +#include "PolishBuildingsTool.h" +#include "SPolishBuildingsDialog.h" +#include "PropertyEditorModule.h" +#include "Engine/TextureLODSettings.h" +#include "RawMesh.h" +#include "Engine/StaticMeshActor.h" +#include "Engine/StaticMesh.h" +#include "Engine/Selection.h" +#include "SystemSettings.h" +#include "Engine/TextureLODSettings.h" +#include "ContentBrowserModule.h" +#include "AssetRegistryModule.h" +#include "ScopedTransaction.h" + +#define LOCTEXT_NAMESPACE "PolishBuildingsTool" + + +FPolishBuildingsTool::FPolishBuildingsTool() + : bReplaceSourceActors(false) + , bExportSpecificLOD(false) + , ExportLODIndex(0) +{} + + +TSharedRef FPolishBuildingsTool::GetWidget() +{ + return SNew(SPolishBuildingsDialog, this); +} + + +FText FPolishBuildingsTool::GetTooltipText() const +{ + return LOCTEXT("PolishBuildingsToolTooltip", "Harvest geometry from selected actors and merge grouping them by materials."); +} + + +FString FPolishBuildingsTool::GetDefaultPackageName() const +{ + FString PackageName = FPackageName::FilenameToLongPackageName(FPaths::GameContentDir() + TEXT("SM_MERGED")); + + USelection* SelectedActors = GEditor->GetSelectedActors(); + // Iterate through selected actors and find first static mesh asset + // Use this static mesh path as destination package name for a merged mesh + for (FSelectionIterator Iter(*SelectedActors); Iter; ++Iter) + { + AActor* Actor = Cast(*Iter); + if (Actor) + { + FString ActorName = Actor->GetName(); + PackageName = FString::Printf(TEXT("%s_%s"), *PackageName, *ActorName); + break; + } + } + + if (PackageName.IsEmpty()) + { + PackageName = MakeUniqueObjectName(NULL, UPackage::StaticClass(), *PackageName).ToString(); + } + + return PackageName; +} + + +bool FPolishBuildingsTool::RunMerge(const FString& PackageName) +{ + IMeshUtilities& MeshUtilities = FModuleManager::Get().LoadModuleChecked("MeshUtilities"); + USelection* SelectedActors = GEditor->GetSelectedActors(); + TArray Actors; + TArray UniqueLevels; + for (FSelectionIterator Iter(*SelectedActors); Iter; ++Iter) + { + AActor* Actor = Cast(*Iter); + if (Actor) + { + Actors.Add(Actor); + UniqueLevels.AddUnique(Actor->GetLevel()); + } + } + + // This restriction is only for replacement of selected actors with merged mesh actor + if (UniqueLevels.Num() > 1 && bReplaceSourceActors) + { + FText Message = NSLOCTEXT("UnrealEd", "FailedToPolishBuildingsSublevels_Msg", "The selected actors should be in the same level"); + OpenMsgDlgInt(EAppMsgType::Ok, Message, NSLOCTEXT("UnrealEd", "FailedToPolishBuildings_Title", "Unable to merge actors")); + return false; + } + + int32 TargetMeshLOD = bExportSpecificLOD ? ExportLODIndex : INDEX_NONE; + FVector MergedActorLocation; + TArray AssetsToSync; + // Merge... + { + FScopedSlowTask SlowTask(0, LOCTEXT("MergingActorsSlowTask", "Merging actors...")); + SlowTask.MakeDialog(); + + MeshUtilities.MergeActors(Actors, MergingSettings, NULL, PackageName, TargetMeshLOD, AssetsToSync, MergedActorLocation); + } + + if (AssetsToSync.Num()) + { + FAssetRegistryModule& AssetRegistry = FModuleManager::Get().LoadModuleChecked("AssetRegistry"); + int32 AssetCount = AssetsToSync.Num(); + for (int32 AssetIndex = 0; AssetIndex < AssetCount; AssetIndex++) + { + AssetRegistry.AssetCreated(AssetsToSync[AssetIndex]); + GEditor->BroadcastObjectReimported(AssetsToSync[AssetIndex]); + } + + //Also notify the content browser that the new assets exists + FContentBrowserModule& ContentBrowserModule = FModuleManager::Get().LoadModuleChecked("ContentBrowser"); + ContentBrowserModule.Get().SyncBrowserToAssets(AssetsToSync, true); + + // Place new mesh in the world + if (bReplaceSourceActors) + { + UStaticMesh* MergedMesh = nullptr; + if (AssetsToSync.FindItemByClass(&MergedMesh)) + { + const FScopedTransaction Transaction(LOCTEXT("PlaceMergedActor", "Place Merged Actor")); + UniqueLevels[0]->Modify(); + + UWorld* World = UniqueLevels[0]->OwningWorld; + FActorSpawnParameters Params; + Params.OverrideLevel = UniqueLevels[0]; + FRotator MergedActorRotation(ForceInit); + + AStaticMeshActor* MergedActor = World->SpawnActor(MergedActorLocation, MergedActorRotation, Params); + MergedActor->GetStaticMeshComponent()->StaticMesh = MergedMesh; + MergedActor->SetActorLabel(AssetsToSync[0]->GetName()); + + // Remove source actors + /* + for (AActor* Actor : Actors) + { + Actor->Destroy(); + } + */ + } + } + } + + return true; +} + + +#undef LOCTEXT_NAMESPACE diff --git a/Plugins/PolishBuildings/Source/Private/PolishBuildingsTool/PolishBuildingsTool.h b/Plugins/PolishBuildings/Source/Private/PolishBuildingsTool/PolishBuildingsTool.h new file mode 100644 index 0000000..673e64e --- /dev/null +++ b/Plugins/PolishBuildings/Source/Private/PolishBuildingsTool/PolishBuildingsTool.h @@ -0,0 +1,35 @@ +// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved. +#pragma once + +#include "IPolishBuildingsTool.h" +#include "MeshUtilities.h" + +/** + * Mesh Merging Tool + */ +class FPolishBuildingsTool : public IPolishBuildingsTool +{ + friend class SPolishBuildingsDialog; + +public: + + FPolishBuildingsTool(); + + // IPolishBuildingsTool interface + virtual TSharedRef GetWidget() override; + virtual FName GetIconName() const override { return "PolishBuildings.PolishBuildingsTool"; } + virtual FText GetTooltipText() const override; + virtual FString GetDefaultPackageName() const override; + virtual bool RunMerge(const FString& PackageName) override; + +private: + + /** Current mesh merging settings */ + FMeshMergingSettings MergingSettings; + + /** Whether to replace source actors with a merged actor in the world */ + bool bReplaceSourceActors; + + bool bExportSpecificLOD; + int32 ExportLODIndex; +}; diff --git a/Plugins/PolishBuildings/Source/Private/PolishBuildingsTool/SPolishBuildingsDialog.cpp b/Plugins/PolishBuildings/Source/Private/PolishBuildingsTool/SPolishBuildingsDialog.cpp new file mode 100644 index 0000000..d4316fb --- /dev/null +++ b/Plugins/PolishBuildings/Source/Private/PolishBuildingsTool/SPolishBuildingsDialog.cpp @@ -0,0 +1,520 @@ +// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. + +#include "PolishBuildingsPrivatePCH.h" +#include "SPolishBuildingsDialog.h" +#include "Dialogs/DlgPickAssetPath.h" +#include "STextComboBox.h" +#include "RawMesh.h" +#include "PolishBuildingsTool/PolishBuildingsTool.h" + +#define LOCTEXT_NAMESPACE "SPolishBuildingsDialog" + +////////////////////////////////////////////////////////////////////////// +// SPolishBuildingsDialog + +SPolishBuildingsDialog::SPolishBuildingsDialog() +{ +} + +BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION +void SPolishBuildingsDialog::Construct(const FArguments& InArgs, FPolishBuildingsTool* InTool) +{ + Tool = InTool; + check(Tool != nullptr); + + // Setup available resolutions for lightmap and merged materials + const int32 MinTexResolution = 1 << FTextureLODGroup().MinLODMipCount; + const int32 MaxTexResolution = 1 << FTextureLODGroup().MaxLODMipCount; + for (int32 TexRes = MinTexResolution; TexRes <= MaxTexResolution; TexRes*=2) + { + LightMapResolutionOptions.Add(MakeShareable(new FString(TTypeToString::ToString(TexRes)))); + MergedMaterialResolutionOptions.Add(MakeShareable(new FString(TTypeToString::ToString(TexRes)))); + } + + Tool->MergingSettings.TargetLightMapResolution = FMath::Clamp(Tool->MergingSettings.TargetLightMapResolution, MinTexResolution, MaxTexResolution); + Tool->MergingSettings.MaterialSettings.TextureSize.X = FMath::Clamp( Tool->MergingSettings.MaterialSettings.TextureSize.X, MinTexResolution, MaxTexResolution ); + Tool->MergingSettings.MaterialSettings.TextureSize.Y = FMath::Clamp( Tool->MergingSettings.MaterialSettings.TextureSize.Y, MinTexResolution, MaxTexResolution ); + + // Setup available UV channels for an atlased lightmap + for (int32 Index = 0; Index < MAX_MESH_TEXTURE_COORDS; Index++) + { + LightMapChannelOptions.Add(MakeShareable(new FString(TTypeToString::ToString(Index)))); + } + + for (int32 Index = 0; Index < MAX_STATIC_MESH_LODS; Index++) + { + ExportLODOptions.Add(MakeShareable(new FString(TTypeToString::ToString(Index)))); + } + + // Create widget layout + this->ChildSlot + [ + SNew(SVerticalBox) + + +SVerticalBox::Slot() + .AutoHeight() + .Padding(0,2,0,0) + [ + // Lightmap settings + SNew(SBorder) + .BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder")) + [ + SNew(SVerticalBox) + + // Enable atlasing + +SVerticalBox::Slot() + .AutoHeight() + .Padding(FEditorStyle::GetMargin("StandardDialog.ContentPadding")) + [ + SNew(SCheckBox) + .Type(ESlateCheckBoxType::CheckBox) + .IsChecked(this, &SPolishBuildingsDialog::GetGenerateLightmapUV) + .OnCheckStateChanged(this, &SPolishBuildingsDialog::SetGenerateLightmapUV) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("AtlasLightmapUVLabel", "Generate Lightmap UVs")) + .Font(FEditorStyle::GetFontStyle("StandardDialog.SmallFont")) + ] + ] + + // Target lightmap channel / Max lightmap resolution + +SVerticalBox::Slot() + .AutoHeight() + .Padding(FEditorStyle::GetMargin("StandardDialog.ContentPadding")) + [ + SNew(SHorizontalBox) + + +SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + [ + SNew(STextBlock) + .IsEnabled(this, &SPolishBuildingsDialog::IsLightmapChannelEnabled) + .Text(LOCTEXT("TargetLightMapChannelLabel", "Target Channel:")) + .Font(FEditorStyle::GetFontStyle("StandardDialog.SmallFont")) + ] + + +SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + .Padding(4,0,4,0) + [ + SNew(STextComboBox) + .IsEnabled( this, &SPolishBuildingsDialog::IsLightmapChannelEnabled ) + .OptionsSource(&LightMapChannelOptions) + .InitiallySelectedItem(LightMapChannelOptions[Tool->MergingSettings.TargetLightMapUVChannel]) + .OnSelectionChanged(this, &SPolishBuildingsDialog::SetTargetLightMapChannel) + .Font(FEditorStyle::GetFontStyle("StandardDialog.SmallFont")) + ] + + +SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + [ + SNew(STextBlock) + .IsEnabled(this, &SPolishBuildingsDialog::IsLightmapChannelEnabled) + .Text(LOCTEXT("TargetLightMapResolutionLabel", "Target Resolution:")) + .Font(FEditorStyle::GetFontStyle("StandardDialog.SmallFont")) + ] + + +SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + .Padding(4,0,4,0) + [ + SNew(STextComboBox) + .IsEnabled( this, &SPolishBuildingsDialog::IsLightmapChannelEnabled ) + .OptionsSource(&LightMapResolutionOptions) + .InitiallySelectedItem(LightMapResolutionOptions[FMath::FloorLog2(Tool->MergingSettings.TargetLightMapResolution)]) + .OnSelectionChanged(this, &SPolishBuildingsDialog::SetTargetLightMapResolution) + .Font(FEditorStyle::GetFontStyle("StandardDialog.SmallFont")) + ] + ] + ] + ] + + // Other merging settings + +SVerticalBox::Slot() + .AutoHeight() + .Padding(0,2,0,0) + [ + SNew(SBorder) + .BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder")) + [ + SNew(SVerticalBox) + + // LOD to export + +SVerticalBox::Slot() + .AutoHeight() + .Padding(FEditorStyle::GetMargin("StandardDialog.ContentPadding")) + [ + SNew(SHorizontalBox) + + +SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + [ + SNew(SCheckBox) + .Type(ESlateCheckBoxType::CheckBox) + .IsChecked(this, &SPolishBuildingsDialog::GetExportSpecificLODEnabled) + .OnCheckStateChanged(this, &SPolishBuildingsDialog::SetExportSpecificLODEnabled) + .Content() + [ + SNew(STextBlock) + .IsEnabled(this, &SPolishBuildingsDialog::IsExportSpecificLODEnabled) + .Text(LOCTEXT("TargetMeshLODIndexLabel", "Export specific LOD:")) + .Font(FEditorStyle::GetFontStyle("StandardDialog.SmallFont")) + ] + ] + + +SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + .Padding(4,0,4,0) + [ + SNew(STextComboBox) + .IsEnabled(this, &SPolishBuildingsDialog::IsExportSpecificLODEnabled) + .OptionsSource(&ExportLODOptions) + .InitiallySelectedItem(ExportLODOptions[Tool->ExportLODIndex]) + .OnSelectionChanged(this, &SPolishBuildingsDialog::SetExportSpecificLODIndex) + .Font(FEditorStyle::GetFontStyle("StandardDialog.SmallFont")) + ] + ] + + // Vertex colors + +SVerticalBox::Slot() + .AutoHeight() + .Padding(FEditorStyle::GetMargin("StandardDialog.ContentPadding")) + [ + SNew(SCheckBox) + .Type(ESlateCheckBoxType::CheckBox) + .IsChecked(this, &SPolishBuildingsDialog::GetImportVertexColors) + .OnCheckStateChanged(this, &SPolishBuildingsDialog::SetImportVertexColors) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("ImportVertexColorsLabel", "Import Vertex Colors")) + .Font(FEditorStyle::GetFontStyle("StandardDialog.SmallFont")) + ] + ] + + // Pivot at zero + +SVerticalBox::Slot() + .AutoHeight() + .Padding(FEditorStyle::GetMargin("StandardDialog.ContentPadding")) + [ + SNew(SCheckBox) + .Type(ESlateCheckBoxType::CheckBox) + .IsChecked(this, &SPolishBuildingsDialog::GetPivotPointAtZero) + .OnCheckStateChanged(this, &SPolishBuildingsDialog::SetPivotPointAtZero) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("PivotPointAtZeroLabel", "Pivot Point At (0,0,0)")) + .Font(FEditorStyle::GetFontStyle("StandardDialog.SmallFont")) + ] + ] + + // Replace source actors + +SVerticalBox::Slot() + .AutoHeight() + .Padding(FEditorStyle::GetMargin("StandardDialog.ContentPadding")) + [ + SNew(SCheckBox) + .Type(ESlateCheckBoxType::CheckBox) + .IsChecked(this, &SPolishBuildingsDialog::GetReplaceSourceActors) + .OnCheckStateChanged(this, &SPolishBuildingsDialog::SetReplaceSourceActors) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("ReplaceSourceActorsLabel", "Replace Source Actors")) + .Font(FEditorStyle::GetFontStyle("StandardDialog.SmallFont")) + ] + ] + + // Merge physics data + +SVerticalBox::Slot() + .AutoHeight() + .Padding(FEditorStyle::GetMargin("StandardDialog.ContentPadding")) + [ + SNew(SCheckBox) + .Type(ESlateCheckBoxType::CheckBox) + .IsChecked(this, &SPolishBuildingsDialog::GetMergePhyisicData) + .OnCheckStateChanged(this, &SPolishBuildingsDialog::SetMergePhyisicData) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("MergePhysicsDataLabel", "Merge Physics Data")) + .Font(FEditorStyle::GetFontStyle("StandardDialog.SmallFont")) + ] + ] + + // Merge materials + +SVerticalBox::Slot() + .AutoHeight() + .Padding(FEditorStyle::GetMargin("StandardDialog.ContentPadding")) + [ + SNew(SCheckBox) + .Type(ESlateCheckBoxType::CheckBox) + .IsChecked(this, &SPolishBuildingsDialog::GetMergeMaterials) + .OnCheckStateChanged(this, &SPolishBuildingsDialog::SetMergeMaterials) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("MergeMaterialsLabel", "Merge Materials")) + .Font(FEditorStyle::GetFontStyle("StandardDialog.SmallFont")) + ] + ] + // Export normal map + +SVerticalBox::Slot() + .AutoHeight() + .Padding(FEditorStyle::GetMargin("StandardDialog.ContentPadding")) + [ + SNew(SCheckBox) + .Type(ESlateCheckBoxType::CheckBox) + .IsChecked(this, &SPolishBuildingsDialog::GetExportNormalMap) + .OnCheckStateChanged(this, &SPolishBuildingsDialog::SetExportNormalMap) + .IsEnabled(this, &SPolishBuildingsDialog::IsMaterialMergingEnabled) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("ExportNormalMapLabel", "Export Normal Map")) + .Font(FEditorStyle::GetFontStyle("StandardDialog.SmallFont")) + ] + ] + // Export metallic map + +SVerticalBox::Slot() + .AutoHeight() + .Padding(FEditorStyle::GetMargin("StandardDialog.ContentPadding")) + [ + SNew(SCheckBox) + .Type(ESlateCheckBoxType::CheckBox) + .IsChecked(this, &SPolishBuildingsDialog::GetExportMetallicMap) + .OnCheckStateChanged(this, &SPolishBuildingsDialog::SetExportMetallicMap) + .IsEnabled(this, &SPolishBuildingsDialog::IsMaterialMergingEnabled) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("ExportMetallicMapLabel", "Export Metallic Map")) + .Font(FEditorStyle::GetFontStyle("StandardDialog.SmallFont")) + ] + ] + // Export roughness map + +SVerticalBox::Slot() + .AutoHeight() + .Padding(FEditorStyle::GetMargin("StandardDialog.ContentPadding")) + [ + SNew(SCheckBox) + .Type(ESlateCheckBoxType::CheckBox) + .IsChecked(this, &SPolishBuildingsDialog::GetExportRoughnessMap) + .OnCheckStateChanged(this, &SPolishBuildingsDialog::SetExportRoughnessMap) + .IsEnabled(this, &SPolishBuildingsDialog::IsMaterialMergingEnabled) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("ExportRoughnessMapLabel", "Export Roughness Map")) + .Font(FEditorStyle::GetFontStyle("StandardDialog.SmallFont")) + ] + ] + // Export specular map + +SVerticalBox::Slot() + .AutoHeight() + .Padding(FEditorStyle::GetMargin("StandardDialog.ContentPadding")) + [ + SNew(SCheckBox) + .Type(ESlateCheckBoxType::CheckBox) + .IsChecked(this, &SPolishBuildingsDialog::GetExportSpecularMap) + .OnCheckStateChanged(this, &SPolishBuildingsDialog::SetExportSpecularMap) + .IsEnabled(this, &SPolishBuildingsDialog::IsMaterialMergingEnabled) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("ExportSpecularMapLabel", "Export Specular Map")) + .Font(FEditorStyle::GetFontStyle("StandardDialog.SmallFont")) + ] + ] + + // Merged texture size + +SVerticalBox::Slot() + .AutoHeight() + .Padding(FEditorStyle::GetMargin("StandardDialog.ContentPadding")) + [ + SNew(SHorizontalBox) + +SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + [ + SNew(STextBlock) + .IsEnabled(this, &SPolishBuildingsDialog::IsMaterialMergingEnabled) + .Text(LOCTEXT("MergedMaterialAtlasResolutionLabel", "Merged Material Atlas Resolution:")) + .Font(FEditorStyle::GetFontStyle("StandardDialog.SmallFont")) + ] + + +SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + .Padding(4,0,4,0) + [ + SNew(STextComboBox) + .IsEnabled(this, &SPolishBuildingsDialog::IsMaterialMergingEnabled) + .OptionsSource(&MergedMaterialResolutionOptions) + .InitiallySelectedItem( MergedMaterialResolutionOptions[FMath::FloorLog2( Tool->MergingSettings.MaterialSettings.TextureSize.X )] ) + .OnSelectionChanged(this, &SPolishBuildingsDialog::SetMergedMaterialAtlasResolution) + .Font(FEditorStyle::GetFontStyle("StandardDialog.SmallFont")) + ] + ] + ] + ] + ]; +} +END_SLATE_FUNCTION_BUILD_OPTIMIZATION + +ECheckBoxState SPolishBuildingsDialog::GetGenerateLightmapUV() const +{ + return (Tool->MergingSettings.bGenerateLightMapUV ? ECheckBoxState::Checked : ECheckBoxState::Unchecked); +} + +void SPolishBuildingsDialog::SetGenerateLightmapUV(ECheckBoxState NewValue) +{ + Tool->MergingSettings.bGenerateLightMapUV = (ECheckBoxState::Checked == NewValue); +} + +bool SPolishBuildingsDialog::IsLightmapChannelEnabled() const +{ + return Tool->MergingSettings.bGenerateLightMapUV; +} + +void SPolishBuildingsDialog::SetTargetLightMapChannel(TSharedPtr NewSelection, ESelectInfo::Type SelectInfo) +{ + TTypeFromString::FromString(Tool->MergingSettings.TargetLightMapUVChannel, **NewSelection); +} + +void SPolishBuildingsDialog::SetTargetLightMapResolution(TSharedPtr NewSelection, ESelectInfo::Type SelectInfo) +{ + TTypeFromString::FromString(Tool->MergingSettings.TargetLightMapResolution, **NewSelection); +} + +ECheckBoxState SPolishBuildingsDialog::GetExportSpecificLODEnabled() const +{ + return (Tool->bExportSpecificLOD ? ECheckBoxState::Checked : ECheckBoxState::Unchecked); +} + +void SPolishBuildingsDialog::SetExportSpecificLODEnabled(ECheckBoxState NewValue) +{ + Tool->bExportSpecificLOD = (NewValue == ECheckBoxState::Checked); +} + +bool SPolishBuildingsDialog::IsExportSpecificLODEnabled() const +{ + return Tool->bExportSpecificLOD; +} + +void SPolishBuildingsDialog::SetExportSpecificLODIndex(TSharedPtr NewSelection, ESelectInfo::Type SelectInfo) +{ + TTypeFromString::FromString(Tool->ExportLODIndex, **NewSelection); +} + +ECheckBoxState SPolishBuildingsDialog::GetImportVertexColors() const +{ + return ( Tool->MergingSettings.bBakeVertexData ? ECheckBoxState::Checked : ECheckBoxState::Unchecked ); +} + +void SPolishBuildingsDialog::SetImportVertexColors(ECheckBoxState NewValue) +{ + Tool->MergingSettings.bBakeVertexData = ( ECheckBoxState::Checked == NewValue ); +} + +ECheckBoxState SPolishBuildingsDialog::GetPivotPointAtZero() const +{ + return (Tool->MergingSettings.bPivotPointAtZero ? ECheckBoxState::Checked : ECheckBoxState::Unchecked); +} + +void SPolishBuildingsDialog::SetPivotPointAtZero(ECheckBoxState NewValue) +{ + Tool->MergingSettings.bPivotPointAtZero = (ECheckBoxState::Checked == NewValue); +} + +ECheckBoxState SPolishBuildingsDialog::GetReplaceSourceActors() const +{ + return (Tool->bReplaceSourceActors ? ECheckBoxState::Checked : ECheckBoxState::Unchecked); +} + +void SPolishBuildingsDialog::SetReplaceSourceActors(ECheckBoxState NewValue) +{ + Tool->bReplaceSourceActors = (ECheckBoxState::Checked == NewValue); +} + +ECheckBoxState SPolishBuildingsDialog::GetMergePhyisicData() const +{ + return (Tool->MergingSettings.bMergePhysicsData ? ECheckBoxState::Checked : ECheckBoxState::Unchecked); +} + +void SPolishBuildingsDialog::SetMergePhyisicData(ECheckBoxState NewValue) +{ + Tool->MergingSettings.bMergePhysicsData = (ECheckBoxState::Checked == NewValue); +} + +bool SPolishBuildingsDialog::IsMaterialMergingEnabled() const +{ + return Tool->MergingSettings.bMergeMaterials; +} + +ECheckBoxState SPolishBuildingsDialog::GetMergeMaterials() const +{ + return (Tool->MergingSettings.bMergeMaterials ? ECheckBoxState::Checked : ECheckBoxState::Unchecked); +} + +void SPolishBuildingsDialog::SetMergeMaterials(ECheckBoxState NewValue) +{ + Tool->MergingSettings.bMergeMaterials = (ECheckBoxState::Checked == NewValue); +} + +ECheckBoxState SPolishBuildingsDialog::GetExportNormalMap() const +{ + return ( Tool->MergingSettings.MaterialSettings.bNormalMap ? ECheckBoxState::Checked : ECheckBoxState::Unchecked ); +} + +void SPolishBuildingsDialog::SetExportNormalMap(ECheckBoxState NewValue) +{ + Tool->MergingSettings.MaterialSettings.bNormalMap = ( ECheckBoxState::Checked == NewValue ); +} + +ECheckBoxState SPolishBuildingsDialog::GetExportMetallicMap() const +{ + return ( Tool->MergingSettings.MaterialSettings.bMetallicMap ? ECheckBoxState::Checked : ECheckBoxState::Unchecked ); +} + +void SPolishBuildingsDialog::SetExportMetallicMap(ECheckBoxState NewValue) +{ + Tool->MergingSettings.MaterialSettings.bMetallicMap = ( ECheckBoxState::Checked == NewValue ); +} + +ECheckBoxState SPolishBuildingsDialog::GetExportRoughnessMap() const +{ + return ( Tool->MergingSettings.MaterialSettings.bRoughnessMap ? ECheckBoxState::Checked : ECheckBoxState::Unchecked ); +} + +void SPolishBuildingsDialog::SetExportRoughnessMap(ECheckBoxState NewValue) +{ + Tool->MergingSettings.MaterialSettings.bRoughnessMap = ( ECheckBoxState::Checked == NewValue ); +} + +ECheckBoxState SPolishBuildingsDialog::GetExportSpecularMap() const +{ + return ( Tool->MergingSettings.MaterialSettings.bSpecularMap ? ECheckBoxState::Checked : ECheckBoxState::Unchecked ); +} + +void SPolishBuildingsDialog::SetExportSpecularMap(ECheckBoxState NewValue) +{ + Tool->MergingSettings.MaterialSettings.bSpecularMap = ( ECheckBoxState::Checked == NewValue ); +} + +void SPolishBuildingsDialog::SetMergedMaterialAtlasResolution(TSharedPtr NewSelection, ESelectInfo::Type SelectInfo) +{ + TTypeFromString::FromString( Tool->MergingSettings.MaterialSettings.TextureSize.X, **NewSelection ); + TTypeFromString::FromString( Tool->MergingSettings.MaterialSettings.TextureSize.Y, **NewSelection ); +} + + +#undef LOCTEXT_NAMESPACE diff --git a/Plugins/PolishBuildings/Source/Private/PolishBuildingsTool/SPolishBuildingsDialog.h b/Plugins/PolishBuildings/Source/Private/PolishBuildingsTool/SPolishBuildingsDialog.h new file mode 100644 index 0000000..0b397c2 --- /dev/null +++ b/Plugins/PolishBuildings/Source/Private/PolishBuildingsTool/SPolishBuildingsDialog.h @@ -0,0 +1,86 @@ +// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +#pragma once + +class FPolishBuildingsTool; + +/*----------------------------------------------------------------------------- + SPolishBuildingsDialog +-----------------------------------------------------------------------------*/ +class SPolishBuildingsDialog : public SCompoundWidget +{ +public: + SLATE_BEGIN_ARGS(SPolishBuildingsDialog) + { + } + + SLATE_END_ARGS() + +public: + /** **/ + SPolishBuildingsDialog(); + + /** SWidget functions */ + void Construct(const FArguments& InArgs, FPolishBuildingsTool* InTool); + +private: + + /** Called when the Merge button is clicked */ + FReply OnMergeClicked(); + + /** */ + ECheckBoxState GetGenerateLightmapUV() const; + void SetGenerateLightmapUV(ECheckBoxState NewValue); + + /** Target lightmap channel */ + bool IsLightmapChannelEnabled() const; + void SetTargetLightMapChannel(TSharedPtr NewSelection, ESelectInfo::Type SelectInfo); + void SetTargetLightMapResolution(TSharedPtr NewSelection, ESelectInfo::Type SelectInfo); + + /** */ + ECheckBoxState GetExportSpecificLODEnabled() const; + void SetExportSpecificLODEnabled(ECheckBoxState NewValue); + bool IsExportSpecificLODEnabled() const; + void SetExportSpecificLODIndex(TSharedPtr NewSelection, ESelectInfo::Type SelectInfo); + + /** */ + ECheckBoxState GetImportVertexColors() const; + void SetImportVertexColors(ECheckBoxState NewValue); + + /** */ + ECheckBoxState GetPivotPointAtZero() const; + void SetPivotPointAtZero(ECheckBoxState NewValue); + + ECheckBoxState GetReplaceSourceActors() const; + void SetReplaceSourceActors(ECheckBoxState NewValue); + + ECheckBoxState GetMergePhyisicData() const; + void SetMergePhyisicData(ECheckBoxState NewValue); + + /** Material merging */ + bool IsMaterialMergingEnabled() const; + ECheckBoxState GetMergeMaterials() const; + void SetMergeMaterials(ECheckBoxState NewValue); + + ECheckBoxState GetExportNormalMap() const; + void SetExportNormalMap(ECheckBoxState NewValue); + + ECheckBoxState GetExportMetallicMap() const; + void SetExportMetallicMap(ECheckBoxState NewValue); + + ECheckBoxState GetExportRoughnessMap() const; + void SetExportRoughnessMap(ECheckBoxState NewValue); + + ECheckBoxState GetExportSpecularMap() const; + void SetExportSpecularMap(ECheckBoxState NewValue); + + void SetMergedMaterialAtlasResolution(TSharedPtr NewSelection, ESelectInfo::Type SelectInfo); + +private: + + FPolishBuildingsTool* Tool; + + TArray> ExportLODOptions; + TArray> LightMapResolutionOptions; + TArray> LightMapChannelOptions; + TArray> MergedMaterialResolutionOptions; +}; diff --git a/Plugins/PolishBuildings/Source/Private/SPolishBuildingsToolbar.cpp b/Plugins/PolishBuildings/Source/Private/SPolishBuildingsToolbar.cpp new file mode 100644 index 0000000..9ad91a5 --- /dev/null +++ b/Plugins/PolishBuildings/Source/Private/SPolishBuildingsToolbar.cpp @@ -0,0 +1,219 @@ +// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved. + +#include "PolishBuildingsPrivatePCH.h" +#include "SPolishBuildingsToolbar.h" +#include "LevelEditor.h" +#include "IDocumentation.h" +#include "ContentBrowserModule.h" + +#define LOCTEXT_NAMESPACE "SPolishBuildingsToolbar" + + +////////////////////////////////////////////////////////////// + +void SPolishBuildingsToolbar::Construct(const FArguments& InArgs) +{ + // Important: We use raw bindings here because we are releasing our binding in our destructor (where a weak pointer would be invalid) + // It's imperative that our delegate is removed in the destructor for the level editor module to play nicely with reloading. + + FLevelEditorModule& LevelEditor = FModuleManager::GetModuleChecked("LevelEditor"); + LevelEditor.OnActorSelectionChanged().AddRaw(this, &SPolishBuildingsToolbar::OnActorSelectionChanged); + + RegisteredTools = InArgs._ToolsToRegister; + + ChildSlot + [ + SNew(SVerticalBox) + + + SVerticalBox::Slot() + .AutoHeight() + .HAlign(HAlign_Left) + .Padding(0, 0, 0, 0) + [ + SAssignNew(ToolbarContainer, SBorder) + .BorderImage(FEditorStyle::GetBrush("NoBorder")) + .Padding(FMargin(4, 0, 0, 0)) + ] + + + SVerticalBox::Slot() + .FillHeight(1.0f) + .Padding(2, 0, 0, 0) + [ + SNew(SBorder) + .BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder")) + .Padding(0.f) + .IsEnabled(this, &SPolishBuildingsToolbar::GetContentEnabledState) + [ + SNew(SVerticalBox) + + + SVerticalBox::Slot() + .FillHeight(1.0f) + .Padding(4, 4, 4, 4) + [ + SNew(SScrollBox) + +SScrollBox::Slot() + [ + SAssignNew(InlineContentHolder, SBox) + ] + ] + + + SVerticalBox::Slot() + .AutoHeight() + .HAlign(HAlign_Right) + .Padding(4, 4, 10, 4) + [ + SNew(SButton) + .Text(LOCTEXT("PolishBuildings", "Merge Actors")) + .OnClicked(this, &SPolishBuildingsToolbar::OnPolishBuildingsClicked) + ] + ] + ] + ]; + + UpdateToolbar(); + + // Update selected actor state for the first time + GUnrealEd->UpdateFloatingPropertyWindows(); +} + + +SPolishBuildingsToolbar::~SPolishBuildingsToolbar() +{ + FLevelEditorModule& LevelEditor = FModuleManager::GetModuleChecked("LevelEditor"); + LevelEditor.OnActorSelectionChanged().RemoveAll(this); +} + + +void SPolishBuildingsToolbar::OnActorSelectionChanged(const TArray& NewSelection, bool bForceRefresh) +{ + SelectedObjects = NewSelection; + bIsContentEnabled = (NewSelection.Num() > 0); +} + + +void SPolishBuildingsToolbar::OnToolSelectionChanged(const ECheckBoxState NewCheckedState, int32 ToolIndex) +{ + if (NewCheckedState == ECheckBoxState::Checked) + { + CurrentlySelectedTool = ToolIndex; + UpdateInlineContent(); + } +} + + +ECheckBoxState SPolishBuildingsToolbar::OnIsToolSelected(int32 ToolIndex) const +{ + return (CurrentlySelectedTool == ToolIndex) ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; +} + + +FReply SPolishBuildingsToolbar::OnPolishBuildingsClicked() +{ + if (CurrentlySelectedTool >= 0 && CurrentlySelectedTool < RegisteredTools.Num()) + { + const FString DefaultPackageName = RegisteredTools[CurrentlySelectedTool]->GetDefaultPackageName(); + const FString DefaultPath = FPackageName::GetLongPackagePath(DefaultPackageName); + const FString DefaultName = FPackageName::GetShortName(DefaultPackageName); + + // Initialize SaveAssetDialog config + FSaveAssetDialogConfig SaveAssetDialogConfig; + SaveAssetDialogConfig.DialogTitleOverride = LOCTEXT("CreateMergedActorTitle", "Create Merged Actor"); + SaveAssetDialogConfig.DefaultPath = DefaultPath; + SaveAssetDialogConfig.DefaultAssetName = DefaultName; + SaveAssetDialogConfig.ExistingAssetPolicy = ESaveAssetDialogExistingAssetPolicy::AllowButWarn; + + FContentBrowserModule& ContentBrowserModule = FModuleManager::LoadModuleChecked("ContentBrowser"); + FString SaveObjectPath = ContentBrowserModule.Get().CreateModalSaveAssetDialog(SaveAssetDialogConfig); + if (!SaveObjectPath.IsEmpty()) + { + const FString PackageName = FPackageName::ObjectPathToPackageName(SaveObjectPath); + + RegisteredTools[CurrentlySelectedTool]->RunMerge(PackageName); + } + } + + return FReply::Handled(); +} + + +bool SPolishBuildingsToolbar::GetContentEnabledState() const +{ + return bIsContentEnabled; +} + + +void SPolishBuildingsToolbar::AddTool(IPolishBuildingsTool* Tool) +{ + check(!RegisteredTools.Contains(Tool)); + RegisteredTools.Add(Tool); + UpdateToolbar(); +} + + +void SPolishBuildingsToolbar::RemoveTool(IPolishBuildingsTool* Tool) +{ + int32 IndexToRemove = RegisteredTools.Find(Tool); + if (IndexToRemove != INDEX_NONE) + { + RegisteredTools.RemoveAt(IndexToRemove); + + if (CurrentlySelectedTool > IndexToRemove) + { + CurrentlySelectedTool--; + } + UpdateToolbar(); + } +} + + +void SPolishBuildingsToolbar::UpdateToolbar() +{ + const ISlateStyle& StyleSet = FEditorStyle::Get(); + + TSharedRef HorizontalBox = + SNew(SHorizontalBox); + + for (int32 ToolIndex = 0; ToolIndex < RegisteredTools.Num(); ToolIndex++) + { + const IPolishBuildingsTool* Tool = RegisteredTools[ToolIndex]; + + HorizontalBox->AddSlot() + .Padding(StyleSet.GetMargin("EditorModesToolbar.SToolBarButtonBlock.Padding")) + [ + SNew(SCheckBox) + .Style(&StyleSet, "EditorModesToolbar.ToggleButton") + .OnCheckStateChanged(this, &SPolishBuildingsToolbar::OnToolSelectionChanged, ToolIndex) + .IsChecked(this, &SPolishBuildingsToolbar::OnIsToolSelected, ToolIndex) + .Padding(StyleSet.GetMargin("EditorModesToolbar.SToolBarButtonBlock.CheckBox.Padding")) + .ToolTip(IDocumentation::Get()->CreateToolTip(Tool->GetTooltipText(), nullptr, FString(), FString())) + [ + SNew(SImage) + .Image(StyleSet.GetBrush(Tool->GetIconName())) + ] + ]; + } + + TSharedRef ToolbarContent = + SNew(SBorder) + .Padding(0) + .BorderImage(StyleSet.GetBrush("NoBorder")) + [ + HorizontalBox + ]; + + ToolbarContainer->SetContent(ToolbarContent); + + UpdateInlineContent(); +} + + +void SPolishBuildingsToolbar::UpdateInlineContent() +{ + if (CurrentlySelectedTool >= 0 && CurrentlySelectedTool < RegisteredTools.Num()) + { + InlineContentHolder->SetContent(RegisteredTools[CurrentlySelectedTool]->GetWidget()); + } +} + + +#undef LOCTEXT_NAMESPACE diff --git a/Plugins/PolishBuildings/Source/Private/SPolishBuildingsToolbar.h b/Plugins/PolishBuildings/Source/Private/SPolishBuildingsToolbar.h new file mode 100644 index 0000000..18d5f3b --- /dev/null +++ b/Plugins/PolishBuildings/Source/Private/SPolishBuildingsToolbar.h @@ -0,0 +1,83 @@ +// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved. + +#pragma once + +class IPolishBuildingsTool; + +////////////////////////////////////////////////////////////////////////// +// SPolishBuildingsToolbar + +class SPolishBuildingsToolbar : public SCompoundWidget +{ +public: + + SLATE_BEGIN_ARGS(SPolishBuildingsToolbar) {} + SLATE_ARGUMENT(TArray, ToolsToRegister) + SLATE_END_ARGS() + + /** + * Construct the widget + * + * @param InArgs A declaration from which to construct the widget + */ + void Construct(const FArguments& InArgs); + + /** Constructor */ + SPolishBuildingsToolbar() + : CurrentlySelectedTool(0) + {} + + /** Destructor */ + virtual ~SPolishBuildingsToolbar(); + + /** Add a new tool to the toolbar */ + void AddTool(IPolishBuildingsTool* Tool); + + /** Remove an existing tool from the toolbar */ + void RemoveTool(IPolishBuildingsTool* Tool); + + +private: + + /** Called when the level actor selection changes */ + void OnActorSelectionChanged(const TArray& NewSelection, bool bForceRefresh); + + /** Called when the currently selected tool changes */ + void OnToolSelectionChanged(const ECheckBoxState NewCheckedState, int32 ToolIndex); + + /** Called to determine whether a toolbox widget is selected or not */ + ECheckBoxState OnIsToolSelected(int32 ToolIndex) const; + + /** Called when the Merge Actors button is clicked */ + FReply OnPolishBuildingsClicked(); + + /** Determine whether the widget content is enabled or not */ + bool GetContentEnabledState() const; + + /** Update the toolbar container based on the currently registered tools */ + void UpdateToolbar(); + + /** Updates the inline content widget for the current tool */ + void UpdateInlineContent(); + + +private: + + /** List of registered tool instances */ + TArray RegisteredTools; + + /** Index of currently selected tool */ + int32 CurrentlySelectedTool; + + /** List of currently selected objects */ + TArray SelectedObjects; + + /** Whether the merge actors tool panel is enabled or not */ + bool bIsContentEnabled; + + /** The container holding the toolbar */ + TSharedPtr ToolbarContainer; + + /** Inline content area for different tool modes */ + TSharedPtr InlineContentHolder; +}; diff --git a/Plugins/PolishBuildings/Source/Public/IPolishBuildingsModule.h b/Plugins/PolishBuildings/Source/Public/IPolishBuildingsModule.h new file mode 100644 index 0000000..b8cc32d --- /dev/null +++ b/Plugins/PolishBuildings/Source/Public/IPolishBuildingsModule.h @@ -0,0 +1,33 @@ +// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved. +#pragma once + +#include "ModuleManager.h" +#include "IPolishBuildingsTool.h" + + +/** + * Merge Actors module interface + */ +class IPolishBuildingsModule : public IModuleInterface +{ + +public: + + /** + * Get reference to the Merge Actors module instance + */ + static inline IPolishBuildingsModule& Get() + { + return FModuleManager::LoadModuleChecked("PolishBuildings"); + } + + /** + * Register an IPolishBuildingsTool with the module, passing ownership to it + */ + virtual bool RegisterPolishBuildingsTool(TUniquePtr Tool) = 0; + + /** + * Unregister an IPolishBuildingsTool with the module + */ + virtual bool UnregisterPolishBuildingsTool(IPolishBuildingsTool* Tool) = 0; +}; diff --git a/Plugins/PolishBuildings/Source/Public/IPolishBuildingsTool.h b/Plugins/PolishBuildings/Source/Public/IPolishBuildingsTool.h new file mode 100644 index 0000000..8434fcc --- /dev/null +++ b/Plugins/PolishBuildings/Source/Public/IPolishBuildingsTool.h @@ -0,0 +1,43 @@ +// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved. +#pragma once + +/** +* Merge Actors tool interface +*/ +class IPolishBuildingsTool +{ + +public: + + /** Virtual destructor */ + virtual ~IPolishBuildingsTool() {} + + /** + * Gets the widget instance associated with this tool + */ + virtual TSharedRef GetWidget() = 0; + + /** + * Get the name of the icon displayed in the Merge Actors toolbar + */ + virtual FName GetIconName() const = 0; + + /** + * Get Tooltip text displayed in the Merge Actors toolbar + */ + virtual FText GetTooltipText() const = 0; + + /** + * Get default name for the merged asset package + */ + virtual FString GetDefaultPackageName() const = 0; + + /** + * Perform merge operation + * + * @param PackageName Long package name of the asset to create + * @return true if the merge succeeded + */ + virtual bool RunMerge(const FString& PackageName) = 0; + +}; diff --git a/Plugins/SkillTree/Resources/Icon128.png b/Plugins/SkillTree/Resources/Icon128.png new file mode 100644 index 0000000..4669995 Binary files /dev/null and b/Plugins/SkillTree/Resources/Icon128.png differ diff --git a/Plugins/SkillTree/SkillTree.uplugin b/Plugins/SkillTree/SkillTree.uplugin new file mode 100644 index 0000000..7a96103 --- /dev/null +++ b/Plugins/SkillTree/SkillTree.uplugin @@ -0,0 +1,28 @@ +{ + "FileVersion": 3, + "Version": 1, + "VersionName": "1.0", + "FriendlyName": "Skill Tree Plugin", + "Description": "A plugin to design the skill tree.", + "Category": "2D", + "CreatedBy": "Yoshi van Belkom", + "CreatedByURL": "http://www.farsquad.com/", + "DocsURL": "http://iamfromtheinternet.nl/wiki/index.php/Programming_Documentation", + "MarketplaceURL": "", + "SupportURL": "http://iamfromtheinternet.nl/wiki/index.php/Programming_Documentation", + "Modules": [ + { + "Name": "SkillTree", + "Type": "Runtime", + "LoadingPhase": "PreDefault" + }, + { + "Name": "SkillTreeEditor", + "Type": "Editor" + } + ], + "EnabledByDefault": false, + "CanContainContent": true, + "IsBetaVersion": false, + "Installed": false +} \ No newline at end of file diff --git a/Plugins/SkillTree/Source/SkillTree/Classes/DynamicHexMap.h b/Plugins/SkillTree/Source/SkillTree/Classes/DynamicHexMap.h new file mode 100644 index 0000000..f6a3af1 --- /dev/null +++ b/Plugins/SkillTree/Source/SkillTree/Classes/DynamicHexMap.h @@ -0,0 +1,42 @@ +// Project Lab - NHTV IGAD +////////////////////////////////////////// +// Author: Yoshi van Belkom - 130118 +////////////////////////////////////////// +// A Hexagon Map with variable width and height. +////////////////////////////////////////// + +#pragma once + +#include "Engine/EngineTypes.h" +#include "HexMap.h" +#include "DynamicHexMap.generated.h" + +USTRUCT() +struct FDynamicHexMap : public FHexMap +{ + GENERATED_BODY() +protected: + //Returns true if x and y are valid positions on the hex map. + virtual bool IsPointValid( int32 a_x, int32 a_y ) override + { + bool result = !( a_x < 0 || a_x >= width || a_y < 0 || a_y >= height ); + if ( result && IsOdd( a_x ) ) + result = ( a_y < ( 15 ) ); + return result; + } +public: + UPROPERTY( Category = Skill, EditAnywhere, meta = ( ClampMin = "1", ClampMax = "13", UIMin = "1", UIMax = "13" ) ) + int32 width = 13; + UPROPERTY( Category = Skill, EditAnywhere, meta = ( ClampMin = "2", ClampMax = "16", UIMin = "2", UIMax = "16" ) ) + int32 height = 16; + + FDynamicHexMap() + {} + + FDynamicHexMap( FDynamicHexMap& a_v ) + { + for ( int i = 0; i < 13; i++ ) rawdata[i] = a_v.rawdata[i]; + width = a_v.width; + height = a_v.height; + } +}; \ No newline at end of file diff --git a/Plugins/SkillTree/Source/SkillTree/Classes/HexMap.h b/Plugins/SkillTree/Source/SkillTree/Classes/HexMap.h new file mode 100644 index 0000000..41aebfa --- /dev/null +++ b/Plugins/SkillTree/Source/SkillTree/Classes/HexMap.h @@ -0,0 +1,80 @@ +// Project Lab - NHTV IGAD +////////////////////////////////////////// +// Author: Yoshi van Belkom - 130118 +////////////////////////////////////////// +// A Map of Hexagons with a static width and height. +////////////////////////////////////////// + +#pragma once + +#include "Engine/EngineTypes.h" +#include "HexMap.generated.h" + +USTRUCT() +struct FHexMap +{ + GENERATED_BODY() +protected: + //Get bit at position. + bool GetBit( uint16 a_byte, int32 a_pos ) + { + return ( a_byte >> a_pos ) & 0x1; + } + //Set bit at position. + uint16 SetBit( uint16 a_byte, int32 a_pos, bool a_bit ) + { + if ( a_bit ) + a_byte |= 1 << a_pos; + else + a_byte &= ~( 1 << a_pos ); + return a_byte; + } + //True if i is odd. + bool IsOdd( uint16 a_i ) + { + return ( a_i % 2 != 0 ); + } + + //Returns true if x and y are valid positions on the map. + virtual bool IsPointValid( int32 a_x, int32 a_y ) + { + bool result = !( a_x < 0 || a_x >= 13 || a_y < 0 || a_y >= 16 ); + if ( result && IsOdd( a_x ) ) + result = ( a_y < ( 15 ) ); + return result; + } +public: + UPROPERTY() + uint16 rawdata[13]; + + //Get the state of a hex at given position. Returns false if position is not valid. + bool Get( int32 a_x, int32 a_y ) + { + if ( !IsPointValid( a_x, a_y ) ) return false; + return GetBit( rawdata[a_x], a_y ); + } + //Set the state of a hex at given position. + void Set( int32 a_x, int32 a_y, bool a_v ) + { + if ( !IsPointValid( a_x, a_y ) ) return; + rawdata[a_x] = SetBit( rawdata[a_x], a_y, a_v ); + } + //Invert the state of a hex at a given position. + void Invert( int32 a_x, int32 a_y ) + { + if ( !IsPointValid( a_x, a_y ) ) return; + unsigned short mask = 1 << a_y; + rawdata[a_x] ^= mask; + } + + FHexMap() + {} + + FHexMap( FHexMap& a_v ) + { + for ( int i = 0; i < 13; i++ ) rawdata[i] = a_v.rawdata[i]; + } + + virtual ~FHexMap() {} + +}; diff --git a/Plugins/SkillTree/Source/SkillTree/Classes/SkillObject.h b/Plugins/SkillTree/Source/SkillTree/Classes/SkillObject.h new file mode 100644 index 0000000..34539fb --- /dev/null +++ b/Plugins/SkillTree/Source/SkillTree/Classes/SkillObject.h @@ -0,0 +1,28 @@ +// Project Lab - NHTV IGAD +////////////////////////////////////////// +// Author: Yoshi van Belkom - 130118 +////////////////////////////////////////// +// The base Skill Object. +////////////////////////////////////////// + +#pragma once + +#include "Engine/EngineTypes.h" +#include "DynamicHexMap.h" +#include "SkillObject.generated.h" + +class USkillTreeObject; + +UCLASS( BlueprintType, meta = (DisplayThumbnail = "true") ) +class SKILLTREE_API USkillObject : public UObject +{ + GENERATED_BODY() +public: + //A Dynamic Map of Hexagons. + UPROPERTY( Category = Skill, EditAnywhere ) + FDynamicHexMap hexMap; + + //Pointer to the parent Skill Tree. + UPROPERTY() + USkillTreeObject* skillTree; +}; diff --git a/Plugins/SkillTree/Source/SkillTree/Classes/SkillTreeObject.h b/Plugins/SkillTree/Source/SkillTree/Classes/SkillTreeObject.h new file mode 100644 index 0000000..c0344e9 --- /dev/null +++ b/Plugins/SkillTree/Source/SkillTree/Classes/SkillTreeObject.h @@ -0,0 +1,25 @@ +// Project Lab - NHTV IGAD +////////////////////////////////////////// +// Author: Yoshi van Belkom - 130118 +////////////////////////////////////////// +// The base Skill Tree Object. +////////////////////////////////////////// + + +#pragma once + +#include "Engine/EngineTypes.h" +#include "HexMap.h" +#include "SkillTreeObject.generated.h" + +UCLASS( BlueprintType, meta = (DisplayThumbnail = "true") ) +class SKILLTREE_API USkillTreeObject : public UObject +{ + GENERATED_BODY() +public: + UPROPERTY() + FHexMap hexMap; + + UPROPERTY( Category = SkillTree, EditAnywhere, AssetRegistrySearchable ) + TArray> skills; +}; diff --git a/Plugins/SkillTree/Source/SkillTree/Private/SkillObject.cpp b/Plugins/SkillTree/Source/SkillTree/Private/SkillObject.cpp new file mode 100644 index 0000000..6f90916 --- /dev/null +++ b/Plugins/SkillTree/Source/SkillTree/Private/SkillObject.cpp @@ -0,0 +1,7 @@ +// Project Lab - NHTV IGAD +////////////////////////////////////////// +// Author: Yoshi van Belkom - 130118 +////////////////////////////////////////// + +#include "SkillTreePrivatePCH.h" +#include "../Classes/SkillObject.h" diff --git a/Plugins/SkillTree/Source/SkillTree/Private/SkillTree.cpp b/Plugins/SkillTree/Source/SkillTree/Private/SkillTree.cpp new file mode 100644 index 0000000..b6d663f --- /dev/null +++ b/Plugins/SkillTree/Source/SkillTree/Private/SkillTree.cpp @@ -0,0 +1,32 @@ +// Project Lab - NHTV IGAD +////////////////////////////////////////// +// Author: Yoshi van Belkom - 130118 +////////////////////////////////////////// + +#include "SkillTreePrivatePCH.h" + +#define LOCTEXT_NAMESPACE "SkillTree" + +class FSkillTree : public ISkillTree +{ +public: + /* IModuleInterface implementation */ + virtual void StartupModule() override; + virtual void ShutdownModule() override; + /* End IModuleInterface implementation */ +}; + +void FSkillTree::StartupModule() +{ + // This code will execute after the module is loaded into memory (but after global variables are initialized, of course.) +} + +void FSkillTree::ShutdownModule() +{ + // This function may be called during shutdown to clean up the module. For modules that support dynamic reloading, + // this function gets called before unloading the module. +} + +IMPLEMENT_MODULE( FSkillTree, SkillTree ) + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Plugins/SkillTree/Source/SkillTree/Private/SkillTreeObject.cpp b/Plugins/SkillTree/Source/SkillTree/Private/SkillTreeObject.cpp new file mode 100644 index 0000000..077b8bf --- /dev/null +++ b/Plugins/SkillTree/Source/SkillTree/Private/SkillTreeObject.cpp @@ -0,0 +1,8 @@ +// Project Lab - NHTV IGAD +////////////////////////////////////////// +// Author: Yoshi van Belkom - 130118 +////////////////////////////////////////// + +#include "SkillTreePrivatePCH.h" +#include "../Classes/SkillTreeObject.h" + diff --git a/Plugins/SkillTree/Source/SkillTree/Private/SkillTreePrivatePCH.h b/Plugins/SkillTree/Source/SkillTree/Private/SkillTreePrivatePCH.h new file mode 100644 index 0000000..659bc97 --- /dev/null +++ b/Plugins/SkillTree/Source/SkillTree/Private/SkillTreePrivatePCH.h @@ -0,0 +1,11 @@ +// Project Lab - NHTV IGAD +////////////////////////////////////////// +// Author: Yoshi van Belkom - 130118 +////////////////////////////////////////// +// The Skill Tree Module Pre Compiled Header. +////////////////////////////////////////// + +#pragma once + +#include "ISkillTree.h" + diff --git a/Plugins/SkillTree/Source/SkillTree/Public/ISkillTree.h b/Plugins/SkillTree/Source/SkillTree/Public/ISkillTree.h new file mode 100644 index 0000000..2cae9e2 --- /dev/null +++ b/Plugins/SkillTree/Source/SkillTree/Public/ISkillTree.h @@ -0,0 +1,27 @@ +// Project Lab - NHTV IGAD +////////////////////////////////////////// +// Author: Yoshi van Belkom - 130118 +////////////////////////////////////////// +// The Skill Tree Runtime Module Interface. +////////////////////////////////////////// + +#pragma once + +#include "ModuleManager.h" + +class ISkillTree : public IModuleInterface +{ +public: + //Function to Get the Singleton of this Module if it's loaded. + static inline ISkillTree& Get() + { + return FModuleManager::LoadModuleChecked< ISkillTree >( "SkillTree" ); + } + + //Function to check if the Module is loaded. + static inline bool IsAvailable() + { + return FModuleManager::Get().IsModuleLoaded( "SkillTree" ); + } +}; + diff --git a/Plugins/SkillTree/Source/SkillTree/SkillTree.Build.cs b/Plugins/SkillTree/Source/SkillTree/SkillTree.Build.cs new file mode 100644 index 0000000..c1ab81c --- /dev/null +++ b/Plugins/SkillTree/Source/SkillTree/SkillTree.Build.cs @@ -0,0 +1,34 @@ +////////////////////////////////////////// +// Author: Yoshi van Belkom - 130118 +////////////////////////////////////////// +// The Skill Tree Runtime Module Settings. +////////////////////////////////////////// + +namespace UnrealBuildTool.Rules +{ + public class SkillTree : ModuleRules + { + public SkillTree(TargetInfo Target) + { + PublicDependencyModuleNames.AddRange( + new string[] { + "Core", + "CoreUObject", + "Engine" + } + ); + + PrivateDependencyModuleNames.AddRange( + new string[] { + "Slate", + "Renderer", + } + ); + + if (UEBuildConfiguration.bBuildEditor == true) + { + PrivateDependencyModuleNames.Add("UnrealEd"); + } + } + } +} \ No newline at end of file diff --git a/Plugins/SkillTree/Source/SkillTreeEditor/Classes/SkillFactory.h b/Plugins/SkillTree/Source/SkillTreeEditor/Classes/SkillFactory.h new file mode 100644 index 0000000..828eb0b --- /dev/null +++ b/Plugins/SkillTree/Source/SkillTreeEditor/Classes/SkillFactory.h @@ -0,0 +1,21 @@ +// Project Lab - NHTV IGAD +////////////////////////////////////////// +// Author: Yoshi van Belkom - 130118 +////////////////////////////////////////// +// Factory for making a Skill asset. +////////////////////////////////////////// + +#pragma once + +#include "SkillFactory.generated.h" + +UCLASS() +class USkillFactory : public UFactory +{ + GENERATED_UCLASS_BODY() +public: + + // UFactory interface + virtual UObject* FactoryCreateNew( UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn ) override; + // End of UFactory interface +}; diff --git a/Plugins/SkillTree/Source/SkillTreeEditor/Classes/SkillTreeFactory.h b/Plugins/SkillTree/Source/SkillTreeEditor/Classes/SkillTreeFactory.h new file mode 100644 index 0000000..a65a8dd --- /dev/null +++ b/Plugins/SkillTree/Source/SkillTreeEditor/Classes/SkillTreeFactory.h @@ -0,0 +1,21 @@ +// Project Lab - NHTV IGAD +////////////////////////////////////////// +// Author: Yoshi van Belkom - 130118 +////////////////////////////////////////// +// Factory for making a Skill Tree asset. +////////////////////////////////////////// + +#pragma once + +#include "SkillTreeFactory.generated.h" + +UCLASS() +class USkillTreeFactory : public UFactory +{ + GENERATED_UCLASS_BODY() + +public: + // UFactory interface + virtual UObject* FactoryCreateNew( UClass* a_class, UObject* a_parent, FName a_name, EObjectFlags a_flags, UObject* a_context, FFeedbackContext* a_warn ) override; + // End of UFactory interface +}; diff --git a/Plugins/SkillTree/Source/SkillTreeEditor/Private/SSkillTreeEditorViewportToolbar.cpp b/Plugins/SkillTree/Source/SkillTreeEditor/Private/SSkillTreeEditorViewportToolbar.cpp new file mode 100644 index 0000000..0c9bf68 --- /dev/null +++ b/Plugins/SkillTree/Source/SkillTreeEditor/Private/SSkillTreeEditorViewportToolbar.cpp @@ -0,0 +1,33 @@ +// Project Lab - NHTV IGAD +////////////////////////////////////////// +// Author: Yoshi van Belkom - 130118 +////////////////////////////////////////// + +#include "SkillTreeEditorPrivatePCH.h" +#include "SSkillTreeEditorViewportToolbar.h" +#include "SkillTreeEditorCommands.h" +#include "SEditorViewport.h" + +#define LOCTEXT_NAMESPACE "SSkillTreeEditorViewportToolbar" + +void SSkillTreeEditorViewportToolbar::Construct( const FArguments& a_args, TSharedPtr a_infoProvider ) +{ + SCommonEditorViewportToolbarBase::Construct( SCommonEditorViewportToolbarBase::FArguments(), a_infoProvider ); +} + +TSharedRef SSkillTreeEditorViewportToolbar::GenerateShowMenu() const +{ + GetInfoProvider().OnFloatingButtonClicked(); + + TSharedRef viewportRef = GetInfoProvider().GetViewportWidget(); + + const bool shouldCloseWindowAfterMenuSelection = true; + FMenuBuilder showMenuBuilder( shouldCloseWindowAfterMenuSelection, viewportRef->GetCommandList() ); + { + showMenuBuilder.AddMenuEntry( FSkillTreeEditorCommands::Get().setShowGrid ); + } + + return showMenuBuilder.MakeWidget(); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Plugins/SkillTree/Source/SkillTreeEditor/Private/SSkillTreeEditorViewportToolbar.h b/Plugins/SkillTree/Source/SkillTreeEditor/Private/SSkillTreeEditorViewportToolbar.h new file mode 100644 index 0000000..c9bede1 --- /dev/null +++ b/Plugins/SkillTree/Source/SkillTreeEditor/Private/SSkillTreeEditorViewportToolbar.h @@ -0,0 +1,23 @@ +// Project Lab - NHTV IGAD +////////////////////////////////////////// +// Author: Yoshi van Belkom - 130118 +////////////////////////////////////////// +// In-viewport toolbar widgets used in the Skill Tree Editor. +////////////////////////////////////////// + +#pragma once + +#include "SCommonEditorViewportToolbarBase.h" + +class SSkillTreeEditorViewportToolbar : public SCommonEditorViewportToolbarBase +{ +public: + SLATE_BEGIN_ARGS( SSkillTreeEditorViewportToolbar ) {} + SLATE_END_ARGS() + + void Construct( const FArguments& a_args, TSharedPtr a_infoProvider ); + + // SCommonEditorViewportToolbarBase interface + virtual TSharedRef GenerateShowMenu() const override; + // End of SCommonEditorViewportToolbarBase +}; diff --git a/Plugins/SkillTree/Source/SkillTreeEditor/Private/SkillAsset.cpp b/Plugins/SkillTree/Source/SkillTreeEditor/Private/SkillAsset.cpp new file mode 100644 index 0000000..0275a51 --- /dev/null +++ b/Plugins/SkillTree/Source/SkillTreeEditor/Private/SkillAsset.cpp @@ -0,0 +1,63 @@ +// Project Lab - NHTV IGAD +////////////////////////////////////////// +// Author: Yoshi van Belkom - 130118 +////////////////////////////////////////// + +#include "SkillTreeEditorPrivatePCH.h" +#include "SkillAsset.h" +#include "AssetToolsModule.h" +#include "Classes/SkillObject.h" +#include "Classes/SkillTreeObject.h" +#include "SkillTreeEditorViewport.h" + +#define LOCTEXT_NAMESPACE "AssetTypeActions" + +FSkillAsset::FSkillAsset( EAssetTypeCategories::Type a_assetCategory ) + : m_assetCategory( a_assetCategory ) +{ +} + +//Gets the name of the asset. +FText FSkillAsset::GetName() const +{ + return LOCTEXT( "FSkillName", "Skill" ); +} + +//Gets the type color of the asset. +FColor FSkillAsset::GetTypeColor() const +{ + return FColor::Cyan; +} + +//Gets a pointer to the class this asset implements. +UClass* FSkillAsset::GetSupportedClass() const +{ + return USkillObject::StaticClass(); +} + +//Gets the category of this asset. +uint32 FSkillAsset::GetCategories() +{ + return m_assetCategory; +} + +//Opens an editor with this asset. +void FSkillAsset::OpenAssetEditor( const TArray& a_objects, TSharedPtr a_editWithinLevelEditor ) +{ + const EToolkitMode::Type mode = a_editWithinLevelEditor.IsValid() ? EToolkitMode::WorldCentric : EToolkitMode::Standalone; + + for ( auto i = a_objects.CreateConstIterator(); i; ++i ) + { + if ( USkillObject* skill = Cast( *i ) ) + { + USkillTreeObject* skillTree = skill->skillTree; + if( skillTree != NULL ) + { + TSharedRef newSkillTreeEditor( new FSkillTreeEditorViewport() ); + newSkillTreeEditor->InitSkillTreeEditor( mode, a_editWithinLevelEditor, skillTree, skill ); + } + } + } +}; + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Plugins/SkillTree/Source/SkillTreeEditor/Private/SkillAsset.h b/Plugins/SkillTree/Source/SkillTreeEditor/Private/SkillAsset.h new file mode 100644 index 0000000..4722ab5 --- /dev/null +++ b/Plugins/SkillTree/Source/SkillTreeEditor/Private/SkillAsset.h @@ -0,0 +1,29 @@ +// Project Lab - NHTV IGAD +////////////////////////////////////////// +// Author: Yoshi van Belkom - 130118 +////////////////////////////////////////// +// Skill asset class for adding extra +// functionality to the Skill asset. +////////////////////////////////////////// + +#pragma once + +#include "EditorStyle.h" +#include "AssetTypeActions_Base.h" + +class FSkillAsset : public FAssetTypeActions_Base +{ +public: + FSkillAsset( EAssetTypeCategories::Type a_assetCategory ); + + // IAssetTypeActions interface + virtual FText GetName() const override; + virtual FColor GetTypeColor() const override; + virtual UClass* GetSupportedClass() const override; + virtual void OpenAssetEditor( const TArray& a_objects, TSharedPtr a_editWithinLevelEditor = TSharedPtr() ) override; + virtual uint32 GetCategories() override; + // End of IAssetTypeActions interface + +private: + EAssetTypeCategories::Type m_assetCategory; +}; \ No newline at end of file diff --git a/Plugins/SkillTree/Source/SkillTreeEditor/Private/SkillFactory.cpp b/Plugins/SkillTree/Source/SkillTreeEditor/Private/SkillFactory.cpp new file mode 100644 index 0000000..96e430e --- /dev/null +++ b/Plugins/SkillTree/Source/SkillTreeEditor/Private/SkillFactory.cpp @@ -0,0 +1,29 @@ +// Project Lab - NHTV IGAD +////////////////////////////////////////// +// Author: Yoshi van Belkom - 130118 +////////////////////////////////////////// + +#include "SkillTreeEditorPrivatePCH.h" +#include "SkillFactory.h" +#include "SkillObject.h" +#include "AssetRegistryModule.h" +#include "PackageTools.h" + +#define LOCTEXT_NAMESPACE "SkillTree" + +USkillFactory::USkillFactory( const FObjectInitializer& ObjectInitializer ) + : Super( ObjectInitializer ) +{ + bCreateNew = true; + bEditAfterNew = true; + SupportedClass = USkillObject::StaticClass(); +} + +UObject* USkillFactory::FactoryCreateNew( UClass* a_class, UObject* a_parent, FName a_name, EObjectFlags a_flags, UObject* a_context, FFeedbackContext* a_warn ) +{ + USkillObject* newSkill = NewObject( a_parent, a_class, a_name, a_flags | RF_Transactional ); + + return newSkill; +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Plugins/SkillTree/Source/SkillTreeEditor/Private/SkillTreeAsset.cpp b/Plugins/SkillTree/Source/SkillTreeEditor/Private/SkillTreeAsset.cpp new file mode 100644 index 0000000..449e948 --- /dev/null +++ b/Plugins/SkillTree/Source/SkillTreeEditor/Private/SkillTreeAsset.cpp @@ -0,0 +1,58 @@ +// Project Lab - NHTV IGAD +////////////////////////////////////////// +// Author: Yoshi van Belkom - 130118 +////////////////////////////////////////// + +#include "SkillTreeEditorPrivatePCH.h" +#include "SkillTreeAsset.h" +#include "AssetToolsModule.h" +#include "Classes/SkillTreeObject.h" +#include "SkillTreeEditorViewport.h" + +#define LOCTEXT_NAMESPACE "AssetTypeActions" + +FSkillTreeAsset::FSkillTreeAsset( EAssetTypeCategories::Type a_assetCategory ) + : m_assetCategory( a_assetCategory ) +{ +} + +//Gets the name of the asset. +FText FSkillTreeAsset::GetName() const +{ + return LOCTEXT( "FSkillTreeName", "Skill Tree" ); +} + +//Gets the type color of the asset. +FColor FSkillTreeAsset::GetTypeColor() const +{ + return FColor::Cyan; +} + +//Gets a pointer to the class this asset implements. +UClass* FSkillTreeAsset::GetSupportedClass() const +{ + return USkillTreeObject::StaticClass(); +} + +//Gets the category of this asset. +uint32 FSkillTreeAsset::GetCategories() +{ + return m_assetCategory; +} + +//Opens an editor with this asset. +void FSkillTreeAsset::OpenAssetEditor( const TArray& a_objects, TSharedPtr a_editWithinLevelEditor ) +{ + const EToolkitMode::Type mode = a_editWithinLevelEditor.IsValid() ? EToolkitMode::WorldCentric : EToolkitMode::Standalone; + + for ( auto i = a_objects.CreateConstIterator(); i; ++i ) + { + if ( USkillTreeObject* skillTree = Cast( *i ) ) + { + TSharedRef newSkillTreeEditor( new FSkillTreeEditorViewport() ); + newSkillTreeEditor->InitSkillTreeEditor( mode, a_editWithinLevelEditor, skillTree ); + } + } +}; + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Plugins/SkillTree/Source/SkillTreeEditor/Private/SkillTreeAsset.h b/Plugins/SkillTree/Source/SkillTreeEditor/Private/SkillTreeAsset.h new file mode 100644 index 0000000..b369374 --- /dev/null +++ b/Plugins/SkillTree/Source/SkillTreeEditor/Private/SkillTreeAsset.h @@ -0,0 +1,29 @@ +// Project Lab - NHTV IGAD +////////////////////////////////////////// +// Author: Yoshi van Belkom - 130118 +////////////////////////////////////////// +// Skill Tree asset class for adding extra +// functionality to the Skill Tree asset. +////////////////////////////////////////// + +#pragma once + +#include "EditorStyle.h" +#include "AssetTypeActions_Base.h" + +class FSkillTreeAsset : public FAssetTypeActions_Base +{ +public: + FSkillTreeAsset( EAssetTypeCategories::Type a_assetCategory ); + + // IAssetTypeActions interface + virtual FText GetName() const override; + virtual FColor GetTypeColor() const override; + virtual UClass* GetSupportedClass() const override; + virtual void OpenAssetEditor( const TArray& a_objects, TSharedPtr a_editWithinLevelEditor = TSharedPtr() ) override; + virtual uint32 GetCategories() override; + // End of IAssetTypeActions interface + +private: + EAssetTypeCategories::Type m_assetCategory; +}; \ No newline at end of file diff --git a/Plugins/SkillTree/Source/SkillTreeEditor/Private/SkillTreeDetailsCustomization.cpp b/Plugins/SkillTree/Source/SkillTreeEditor/Private/SkillTreeDetailsCustomization.cpp new file mode 100644 index 0000000..2019a69 --- /dev/null +++ b/Plugins/SkillTree/Source/SkillTreeEditor/Private/SkillTreeDetailsCustomization.cpp @@ -0,0 +1,147 @@ +// Project Lab - NHTV IGAD +////////////////////////////////////////// +// Author: Yoshi van Belkom - 130118 +////////////////////////////////////////// + +#include "SkillTreeEditorPrivatePCH.h" +#include "SkillTreeDetailsCustomization.h" +#include "PhysicsEngine/BodySetup.h" +#include "Editor/Documentation/Public/IDocumentation.h" +#include "PropertyRestriction.h" +#include "PropertyCustomizationHelpers.h" +#include "SkillTreeObject.h" +#include "SkillObject.h" + +#define LOCTEXT_NAMESPACE "SkillTreeEditor" + +///////////////////////////////////////////////////// +// FSkillTreeDetailsCustomization + +// Makes a new instance of this detail layout class for a specific detail view requesting it. +TSharedRef FSkillTreeDetailsCustomization::MakeInstance() +{ + return MakeInstanceForSkillTreeEditor( ); +} + +// Makes a new instance of this detail layout class for a specific detail view requesting it. +TSharedRef FSkillTreeDetailsCustomization::MakeInstanceForSkillTreeEditor( ) +{ + return MakeShareable( new FSkillTreeDetailsCustomization( ) ); +} + +FSkillTreeDetailsCustomization::FSkillTreeDetailsCustomization( ) +{ +} + +FDetailWidgetRow& FSkillTreeDetailsCustomization::GenerateWarningRow( IDetailCategoryBuilder& a_warningCategory, bool a_experimental, const FText& a_warningText, const FText& a_tooltip, const FString& a_excerptLink, const FString& a_excerptName ) +{ + const FText searchString = a_warningText; + const FSlateBrush* warningIcon = FEditorStyle::GetBrush( a_experimental ? "PropertyEditor.ExperimentalClass" : "PropertyEditor.EarlyAccessClass" ); + + FDetailWidgetRow& warningRow = a_warningCategory.AddCustomRow( searchString ) + .WholeRowContent() + [ + SNew( SHorizontalBox ) + .ToolTip( IDocumentation::Get()->CreateToolTip( a_tooltip, nullptr, a_excerptLink, a_excerptName ) ) + .Visibility( EVisibility::Visible ) + + + SHorizontalBox::Slot() + .VAlign( VAlign_Center ) + .AutoWidth() + .Padding( 4.0f, 0.0f, 0.0f, 0.0f ) + [SNew( SImage ) + .Image( warningIcon ) + ] + + + SHorizontalBox::Slot() + .VAlign( VAlign_Center ) + .AutoWidth() + .Padding( 4.0f, 0.0f, 0.0f, 0.0f ) + [ + SNew( STextBlock ) + .Text( a_warningText ) + .Font( IDetailLayoutBuilder::GetDetailFont() ) + ] + ]; + + return warningRow; +} + +void FSkillTreeDetailsCustomization::CustomizeDetails( IDetailLayoutBuilder& a_detailLayout ) +{ + IDetailCategoryBuilder& skillTreeCategory = a_detailLayout.EditCategory( "SkillTree", FText::GetEmpty(), ECategoryPriority::Important ); + BuildSkillTreeSection( skillTreeCategory, a_detailLayout ); +} + +// Makes sure Skill Tree properties are near the top. +void FSkillTreeDetailsCustomization::BuildSkillTreeSection( IDetailCategoryBuilder& a_skillTreeCategory, IDetailLayoutBuilder& a_detailLayout ) +{ + a_skillTreeCategory.AddProperty( GET_MEMBER_NAME_CHECKED( USkillTreeObject, skills ) ); +} + +///////////////////////////////////////////////////// +// FSkillDetailsCustomization + +// Makes a new instance of this detail layout class for a specific detail view requesting it. +TSharedRef FSkillDetailsCustomization::MakeInstance() +{ + return MakeInstanceForSkillTreeEditor( ); +} + +// Makes a new instance of this detail layout class for a specific detail view requesting it. +TSharedRef FSkillDetailsCustomization::MakeInstanceForSkillTreeEditor( ) +{ + return MakeShareable( new FSkillDetailsCustomization( ) ); +} + +FSkillDetailsCustomization::FSkillDetailsCustomization( ) +{ +} + +FDetailWidgetRow& FSkillDetailsCustomization::GenerateWarningRow( IDetailCategoryBuilder& a_warningCategory, bool a_experimental, const FText& a_warningText, const FText& a_tooltip, const FString& a_excerptLink, const FString& a_excerptName ) +{ + const FText searchString = a_warningText; + const FSlateBrush* warningIcon = FEditorStyle::GetBrush( a_experimental ? "PropertyEditor.ExperimentalClass" : "PropertyEditor.EarlyAccessClass" ); + + FDetailWidgetRow& warningRow = a_warningCategory.AddCustomRow( searchString ) + .WholeRowContent() + [ + SNew( SHorizontalBox ) + .ToolTip( IDocumentation::Get()->CreateToolTip( a_tooltip, nullptr, a_excerptLink, a_excerptName ) ) + .Visibility( EVisibility::Visible ) + + + SHorizontalBox::Slot() + .VAlign( VAlign_Center ) + .AutoWidth() + .Padding( 4.0f, 0.0f, 0.0f, 0.0f ) + [SNew( SImage ) + .Image( warningIcon ) + ] + + + SHorizontalBox::Slot() + .VAlign( VAlign_Center ) + .AutoWidth() + .Padding( 4.0f, 0.0f, 0.0f, 0.0f ) + [ + SNew( STextBlock ) + .Text( a_warningText ) + .Font( IDetailLayoutBuilder::GetDetailFont() ) + ] + ]; + + return warningRow; +} + +void FSkillDetailsCustomization::CustomizeDetails( IDetailLayoutBuilder& a_detailLayout ) +{ + IDetailCategoryBuilder& skillCategory = a_detailLayout.EditCategory( "Skill", FText::GetEmpty(), ECategoryPriority::Important ); + BuildSkillSection( skillCategory, a_detailLayout ); +} + +// Makes sure Skill properties are near the top. +void FSkillDetailsCustomization::BuildSkillSection( IDetailCategoryBuilder& a_skillCategory, IDetailLayoutBuilder& a_detailLayout ) +{ + a_skillCategory.AddProperty( GET_MEMBER_NAME_CHECKED( USkillObject, hexMap ) ); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Plugins/SkillTree/Source/SkillTreeEditor/Private/SkillTreeDetailsCustomization.h b/Plugins/SkillTree/Source/SkillTreeEditor/Private/SkillTreeDetailsCustomization.h new file mode 100644 index 0000000..2d1c52b --- /dev/null +++ b/Plugins/SkillTree/Source/SkillTreeEditor/Private/SkillTreeDetailsCustomization.h @@ -0,0 +1,55 @@ +// Project Lab - NHTV IGAD +////////////////////////////////////////// +// Author: Yoshi van Belkom - 130118 +////////////////////////////////////////// + + +#pragma once + +#include "PropertyEditing.h" +#include "SkillTreeEditorViewportClient.h" + +////////////////////////////////////////// +// The Skill Tree Details viewport. +////////////////////////////////////////// +class FSkillTreeDetailsCustomization : public IDetailCustomization +{ +public: + static TSharedRef MakeInstance(); + + static TSharedRef MakeInstanceForSkillTreeEditor( ); + + // IDetailCustomization interface + virtual void CustomizeDetails( IDetailLayoutBuilder& DetailLayout ) override; + // End of IDetailCustomization interface + +protected: + FSkillTreeDetailsCustomization( ); + + static FDetailWidgetRow& GenerateWarningRow( IDetailCategoryBuilder& a_warningCategory, bool a_experimental, const FText& a_warningText, const FText& a_tooltip, const FString& a_excerptLink, const FString& a_excerptName ); + + void BuildSkillTreeSection( IDetailCategoryBuilder& a_skillTreeCategory, IDetailLayoutBuilder& a_detailLayout ); +}; + +////////////////////////////////////////// +// The Skill Details viewport. +////////////////////////////////////////// +class FSkillDetailsCustomization : public IDetailCustomization +{ +public: + static TSharedRef MakeInstance(); + + static TSharedRef MakeInstanceForSkillTreeEditor( ); + + // IDetailCustomization interface + virtual void CustomizeDetails( IDetailLayoutBuilder& DetailLayout ) override; + // End of IDetailCustomization interface + +protected: + FSkillDetailsCustomization( ); + + static FDetailWidgetRow& GenerateWarningRow( IDetailCategoryBuilder& a_warningCategory, bool a_experimental, const FText& a_warningText, const FText& a_tooltip, const FString& a_excerptLink, const FString& a_excerptName ); + + void BuildSkillSection( IDetailCategoryBuilder& a_skillCategory, IDetailLayoutBuilder& a_detailLayout ); +}; + diff --git a/Plugins/SkillTree/Source/SkillTreeEditor/Private/SkillTreeEditMode.cpp b/Plugins/SkillTree/Source/SkillTreeEditor/Private/SkillTreeEditMode.cpp new file mode 100644 index 0000000..5390b7f --- /dev/null +++ b/Plugins/SkillTree/Source/SkillTreeEditor/Private/SkillTreeEditMode.cpp @@ -0,0 +1,9 @@ +// Project Lab - NHTV IGAD +////////////////////////////////////////// +// Author: Yoshi van Belkom - 130118 +////////////////////////////////////////// + +#include "SkillTreeEditorPrivatePCH.h" +#include "SkillTreeEditMode.h" + +const FEditorModeID FSkillTreeEditMode::EM_skillTree( TEXT( "SkillTreeEditMode" ) ); diff --git a/Plugins/SkillTree/Source/SkillTreeEditor/Private/SkillTreeEditMode.h b/Plugins/SkillTree/Source/SkillTreeEditor/Private/SkillTreeEditMode.h new file mode 100644 index 0000000..2022849 --- /dev/null +++ b/Plugins/SkillTree/Source/SkillTreeEditor/Private/SkillTreeEditMode.h @@ -0,0 +1,20 @@ +// Project Lab - NHTV IGAD +////////////////////////////////////////// +// Author: Yoshi van Belkom - 130118 +////////////////////////////////////////// +// The Skill Tree Editor mode class. +////////////////////////////////////////// + +#include "UnrealEd.h" +#include "EditorModeRegistry.h" + +#pragma once + +class FSkillTreeEditMode : public FEdMode +{ +public: + //The ID of the Skill Tree Editor. + static const FEditorModeID EM_skillTree; +public: + FSkillTreeEditMode(){}; +}; \ No newline at end of file diff --git a/Plugins/SkillTree/Source/SkillTreeEditor/Private/SkillTreeEditor.cpp b/Plugins/SkillTree/Source/SkillTreeEditor/Private/SkillTreeEditor.cpp new file mode 100644 index 0000000..3240159 --- /dev/null +++ b/Plugins/SkillTree/Source/SkillTreeEditor/Private/SkillTreeEditor.cpp @@ -0,0 +1,72 @@ +// Project Lab - NHTV IGAD +////////////////////////////////////////// +// Author: Yoshi van Belkom - 130118 +////////////////////////////////////////// + +#include "SkillTreeEditorPrivatePCH.h" +#include "AssetToolsModule.h" +#include "ModuleManager.h" +#include "SkillTreeAsset.h" +#include "SkillAsset.h" +#include "SkillTreeEditMode.h" +#include "SkillTreeEditorCommands.h" + +#define LOCTEXT_NAMESPACE "SkillTreeEditor" + +EAssetTypeCategories::Type skillTreeAssetCategoryBit; + +class FSkillTreeEditor : public ISkillTreeEditor +{ +public: + /** IModuleInterface implementation */ + virtual void StartupModule() override; + virtual void ShutdownModule() override; + + void RegisterAssetTypeAction( IAssetTools& a_assetTools, TSharedRef a_action ); +private: + TArray< TSharedPtr > m_createdAssetTypeActions; +}; + + +void FSkillTreeEditor::RegisterAssetTypeAction( IAssetTools& a_assetTools, TSharedRef a_action ) +{ + a_assetTools.RegisterAssetTypeActions( a_action ); + m_createdAssetTypeActions.Add( a_action ); +} + +void FSkillTreeEditor::StartupModule() +{ + FSkillTreeEditorCommands::Register(); + + IAssetTools& assetTools = FModuleManager::LoadModuleChecked( "AssetTools" ).Get(); + + //Register the category of the assets. + skillTreeAssetCategoryBit = assetTools.RegisterAdvancedAssetCategory( FName( TEXT( "SkillTree" ) ), LOCTEXT( "SkillTreeAssetCategory", "Skill Tree" ) ); + + //Register the asset to the category. + RegisterAssetTypeAction( assetTools, MakeShareable( new FSkillTreeAsset( skillTreeAssetCategoryBit ) ) ); + + FEditorModeRegistry::Get().RegisterMode( FSkillTreeEditMode::EM_skillTree, LOCTEXT( "SkillTreeEditMode", "Skill Tree Editor" ), FSlateIcon(), false ); +} + +void FSkillTreeEditor::ShutdownModule() +{ + FEditorModeRegistry::Get().UnregisterMode( FSkillTreeEditMode::EM_skillTree ); + + //Unregister all the assets. + if ( FModuleManager::Get().IsModuleLoaded( "AssetTools" ) ) + { + IAssetTools& AssetTools = FModuleManager::GetModuleChecked( "AssetTools" ).Get(); + for ( int32 i = 0; i < m_createdAssetTypeActions.Num(); ++i ) + { + AssetTools.UnregisterAssetTypeActions( m_createdAssetTypeActions[i].ToSharedRef() ); + } + } + m_createdAssetTypeActions.Empty(); + + FSkillTreeEditorCommands::Unregister(); +} + +IMPLEMENT_MODULE( FSkillTreeEditor, SkillTreeEditor ) + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Plugins/SkillTree/Source/SkillTreeEditor/Private/SkillTreeEditorCommands.cpp b/Plugins/SkillTree/Source/SkillTreeEditor/Private/SkillTreeEditorCommands.cpp new file mode 100644 index 0000000..72f0d4c --- /dev/null +++ b/Plugins/SkillTree/Source/SkillTreeEditor/Private/SkillTreeEditorCommands.cpp @@ -0,0 +1,20 @@ +// Project Lab - NHTV IGAD +////////////////////////////////////////// +// Author: Yoshi van Belkom - 130118 +////////////////////////////////////////// + +#include "SkillTreeEditorPrivatePCH.h" +#include "SkillTreeEditorCommands.h" + +#define LOCTEXT_NAMESPACE "" + +void FSkillTreeEditorCommands::RegisterCommands() +{ + // Show toggles + UI_COMMAND( setShowGrid, "Grid", "Displays the viewport grid.", EUserInterfaceActionType::ToggleButton, FInputChord() ); + + // Editing modes + UI_COMMAND( enterViewMode, "View", "View the sprite.", EUserInterfaceActionType::ToggleButton, FInputChord() ); +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Plugins/SkillTree/Source/SkillTreeEditor/Private/SkillTreeEditorCommands.h b/Plugins/SkillTree/Source/SkillTreeEditor/Private/SkillTreeEditorCommands.h new file mode 100644 index 0000000..64de28c --- /dev/null +++ b/Plugins/SkillTree/Source/SkillTreeEditor/Private/SkillTreeEditorCommands.h @@ -0,0 +1,30 @@ +// Project Lab - NHTV IGAD +////////////////////////////////////////// +// Author: Yoshi van Belkom - 130118 +////////////////////////////////////////// +// The Skill Tree Editor commands class. +////////////////////////////////////////// + +#pragma once + +class FSkillTreeEditorCommands : public TCommands +{ +public: + FSkillTreeEditorCommands() + : TCommands( + TEXT( "SkillTreeEditor" ), + NSLOCTEXT( "Contexts", "SkillTreeEditor", "Skill Tree Editor" ), + NAME_None, + FName(TEXT("null")) + ) + { + } + + // TCommand<> interface + virtual void RegisterCommands() override; + // End of TCommand<> interface + + //TODO: Remove? + TSharedPtr setShowGrid; + TSharedPtr enterViewMode; +}; \ No newline at end of file diff --git a/Plugins/SkillTree/Source/SkillTreeEditor/Private/SkillTreeEditorPrivatePCH.h b/Plugins/SkillTree/Source/SkillTreeEditor/Private/SkillTreeEditorPrivatePCH.h new file mode 100644 index 0000000..b4f9cd2 --- /dev/null +++ b/Plugins/SkillTree/Source/SkillTreeEditor/Private/SkillTreeEditorPrivatePCH.h @@ -0,0 +1,13 @@ +// Project Lab - NHTV IGAD +////////////////////////////////////////// +// Author: Yoshi van Belkom - 130118 +////////////////////////////////////////// +// The Skill Tree Editor Module Pre Compiled Header. +////////////////////////////////////////// + + +#pragma once + +#include "UnrealEd.h" +#include "ISkillTreeEditor.h" + diff --git a/Plugins/SkillTree/Source/SkillTreeEditor/Private/SkillTreeEditorViewport.cpp b/Plugins/SkillTree/Source/SkillTreeEditor/Private/SkillTreeEditorViewport.cpp new file mode 100644 index 0000000..11bc705 --- /dev/null +++ b/Plugins/SkillTree/Source/SkillTreeEditor/Private/SkillTreeEditorViewport.cpp @@ -0,0 +1,532 @@ +// Project Lab - NHTV IGAD +////////////////////////////////////////// +// Author: Yoshi van Belkom - 130118 +////////////////////////////////////////// + +#include "SkillTreeEditorPrivatePCH.h" +#include "SkillTreeEditorViewport.h" +#include "SceneViewport.h" + +#include "GraphEditor.h" +#include "SDockTab.h" +#include "SEditorViewport.h" +#include "SCommonEditorViewportToolbarBase.h" +#include "ISkillTree.h" +#include "SkillTreeEditorViewportClient.h" +#include "SSkillTreeEditorViewportToolbar.h" +#include "SkillTreeObject.h" +#include "SkillObject.h" +#include "WorkspaceMenuStructureModule.h" + +#include "SkillTreeEditorCommands.h" +#include "SSingleObjectDetailsPanel.h" +#include "SkillTreeDetailsCustomization.h" + +#define LOCTEXT_NAMESPACE "SkillTreeEditor" + +const FName skillTreeEditorAppName = FName( TEXT( "SkillTreeEditorApp" ) ); + +// Tab identifiers. +struct FSkillTreeEditorTabs +{ + static const FName skillTreeViewportID; + static const FName skillTreeDetailViewportID; + static const FName skillViewportID; + static const FName skillDetailViewportID; +}; + +const FName FSkillTreeEditorTabs::skillTreeViewportID( TEXT( "STViewport" ) ); +const FName FSkillTreeEditorTabs::skillViewportID( TEXT( "SViewport" ) ); +const FName FSkillTreeEditorTabs::skillTreeDetailViewportID( TEXT( "STDViewport" ) ); +const FName FSkillTreeEditorTabs::skillDetailViewportID( TEXT( "SDViewport" ) ); + +//TODO: Make separate headers for these classes. +///////////////////////////////////////////////////// +// SSkillTreePropertiesTabBody + +class SSkillTreePropertiesTabBody : public SSingleObjectDetailsPanel +{ +public: + SLATE_BEGIN_ARGS( SSkillTreePropertiesTabBody ) {} + SLATE_END_ARGS() + +private: + // Pointer back to owning skill tree editor instance. + TWeakPtr m_skillTreeEditorPtr; + +public: + void Construct( const FArguments& a_args, TSharedPtr a_skillTreeEditor ) + { + m_skillTreeEditorPtr = a_skillTreeEditor; + + SSingleObjectDetailsPanel::Construct( SSingleObjectDetailsPanel::FArguments().HostCommandList( a_skillTreeEditor->GetToolkitCommands() ), true, true ); + + FOnGetDetailCustomizationInstance customizeSkillTreeForEditor = FOnGetDetailCustomizationInstance::CreateStatic( &FSkillTreeDetailsCustomization::MakeInstanceForSkillTreeEditor ); + PropertyView->RegisterInstancedCustomPropertyLayout( USkillTreeObject::StaticClass(), customizeSkillTreeForEditor ); + } + + // SSingleObjectDetailsPanel interface + virtual UObject* GetObjectToObserve() const override + { + return m_skillTreeEditorPtr.Pin()->GetSkillTreeBeingEdited(); + } + + virtual TSharedRef PopulateSlot( TSharedRef a_propertyEditorWidget ) override + { + return SNew( SVerticalBox ) + + SVerticalBox::Slot() + .FillHeight( 1 ) + [ + a_propertyEditorWidget + ]; + } + // End of SSingleObjectDetailsPanel interface +}; + +///////////////////////////////////////////////////// +// SSkillPropertiesTabBody + +class SSkillPropertiesTabBody : public SSingleObjectDetailsPanel +{ +public: + SLATE_BEGIN_ARGS( SSkillPropertiesTabBody ) {} + SLATE_END_ARGS() + +private: + // Pointer back to owning sprite editor instance (the keeper of state) + TWeakPtr m_skillTreeEditorPtr; + +public: + void Construct( const FArguments& InArgs, TSharedPtr a_skillTreeEditor ) + { + m_skillTreeEditorPtr = a_skillTreeEditor; + + SSingleObjectDetailsPanel::Construct( SSingleObjectDetailsPanel::FArguments().HostCommandList( a_skillTreeEditor->GetToolkitCommands() ), true, true ); + + FOnGetDetailCustomizationInstance customizeSkillTreeForEditor = FOnGetDetailCustomizationInstance::CreateStatic( &FSkillDetailsCustomization::MakeInstanceForSkillTreeEditor ); + PropertyView->RegisterInstancedCustomPropertyLayout( USkillObject::StaticClass(), customizeSkillTreeForEditor ); + } + + // SSingleObjectDetailsPanel interface + virtual UObject* GetObjectToObserve() const override + { + return m_skillTreeEditorPtr.Pin()->GetSkillBeingEdited(); + } + + virtual TSharedRef PopulateSlot( TSharedRef a_propertyEditorWidget ) override + { + return SNew( SVerticalBox ) + + SVerticalBox::Slot() + .FillHeight( 1 ) + [ + a_propertyEditorWidget + ]; + } + // End of SSingleObjectDetailsPanel interface +}; + +///////////////////////////////////////////////////// +// SSkillTreeEditorViewport + +class SSkillTreeEditorViewport : public SEditorViewport, public ICommonEditorViewportToolbarInfoProvider +{ +public: + SLATE_BEGIN_ARGS( SSkillTreeEditorViewport ) {} + SLATE_END_ARGS() + + void Construct( const FArguments& a_args, TSharedPtr a_spriteEditor ); + + // SEditorViewport interface + virtual void BindCommands() override; + virtual TSharedRef MakeEditorViewportClient() override; + virtual TSharedPtr MakeViewportToolbar() override; + virtual EVisibility GetTransformToolbarVisibility() const override; + virtual void OnFloatingButtonClicked() override; + // End of SEditorViewport interface + + // ICommonEditorViewportToolbarInfoProvider interface + virtual TSharedRef GetViewportWidget() override; + virtual TSharedPtr GetExtenders() const override; + // End of ICommonEditorViewportToolbarInfoProvider interface + + void ActivateEditMode() + { + m_editorViewportClient->ActivateEditMode(); + } + + void IsSkill( bool a_state ) + { + m_editorViewportClient->IsSkill( a_state ); + } + +private: + // Pointer back to owning Skill Tree editor instance (the keeper of state). + TWeakPtr m_skillTreeEditorPtr; + + // Viewport client + TSharedPtr m_editorViewportClient; +}; + +void SSkillTreeEditorViewport::Construct( const FArguments& a_args, TSharedPtr a_skillTreeEditor ) +{ + m_skillTreeEditorPtr = a_skillTreeEditor; + + SEditorViewport::Construct( SEditorViewport::FArguments() ); +} + +void SSkillTreeEditorViewport::BindCommands() +{ + SEditorViewport::BindCommands(); + + const FSkillTreeEditorCommands& commands = FSkillTreeEditorCommands::Get(); + + TSharedRef editorViewportClientRef = m_editorViewportClient.ToSharedRef(); + + // Show toggles + //TODO: Remove. + CommandList->MapAction( + commands.setShowGrid, + FExecuteAction::CreateSP( editorViewportClientRef, &FEditorViewportClient::SetShowGrid ), + FCanExecuteAction(), + FIsActionChecked::CreateSP( editorViewportClientRef, &FEditorViewportClient::IsSetShowGridChecked ) ); + + + // Editing modes + //TODO: Remove. + CommandList->MapAction( + commands.enterViewMode, + FExecuteAction::CreateSP( editorViewportClientRef, &FSkillTreeEditorViewportClient::EnterViewMode ), + FCanExecuteAction(), + FIsActionChecked::CreateSP( editorViewportClientRef, &FSkillTreeEditorViewportClient::IsInViewMode ) ); +} + +// Make a new client for the viewport. +TSharedRef SSkillTreeEditorViewport::MakeEditorViewportClient() +{ + m_editorViewportClient = MakeShareable( new FSkillTreeEditorViewportClient( m_skillTreeEditorPtr, SharedThis( this ) ) ); + + return m_editorViewportClient.ToSharedRef(); +} + +TSharedPtr SSkillTreeEditorViewport::MakeViewportToolbar() +{ + return SNew( SSkillTreeEditorViewportToolbar, SharedThis( this ) ); +} + +EVisibility SSkillTreeEditorViewport::GetTransformToolbarVisibility() const +{ + return EVisibility::Visible; +} + +TSharedRef SSkillTreeEditorViewport::GetViewportWidget() +{ + return SharedThis( this ); +} + +TSharedPtr SSkillTreeEditorViewport::GetExtenders() const +{ + TSharedPtr Result( MakeShareable( new FExtender ) ); + return Result; +} + +//Needed to be implemented but not used. +void SSkillTreeEditorViewport::OnFloatingButtonClicked() +{ +} + +///////////////////////////////////////////////////// +// FSkillTreeEditorViewport + +TSharedRef FSkillTreeEditorViewport::SpawnTab_SKViewport( const FSpawnTabArgs& a_args ) +{ + return SNew( SDockTab ) + .Label( LOCTEXT( "SKViewportTab_Title", "Skill Tree" ) ) + [ + SNew( SOverlay ) + + // The Skill Tree editor viewport + + SOverlay::Slot() + [ + m_skillTreeViewport.ToSharedRef() + ] + + // Bottom-right corner text indicating the preview nature of the viewport. + + SOverlay::Slot() + .Padding( 10 ) + .VAlign( VAlign_Bottom ) + .HAlign( HAlign_Right ) + [ + SNew( STextBlock ) + .Visibility( EVisibility::HitTestInvisible ) + .TextStyle( FEditorStyle::Get(), "Graph.CornerText" ) + .Text( LOCTEXT( "SKViewportTab_CornerText", "Edit Skill Tree" ) ) + ] + ]; +} + +TSharedRef FSkillTreeEditorViewport::SpawnTab_SViewport( const FSpawnTabArgs& a_args ) +{ + return SNew( SDockTab ) + .Label( LOCTEXT( "SViewportTab_Title", "Skill" ) ) + [ + SNew( SOverlay ) + + // The sprite editor viewport + + SOverlay::Slot() + [ + m_skillViewport.ToSharedRef() + ] + + // Bottom-right corner text indicating the preview nature of the viewport. + + SOverlay::Slot() + .Padding( 10 ) + .VAlign( VAlign_Bottom ) + .HAlign( HAlign_Right ) + [ + SNew( STextBlock ) + .Visibility( EVisibility::HitTestInvisible ) + .TextStyle( FEditorStyle::Get(), "Graph.CornerText" ) + .Text( LOCTEXT("SViewportTab_CornerText", "Edit Skill") ) + ] + ]; +} + +TSharedRef FSkillTreeEditorViewport::SpawnTab_SKDViewport( const FSpawnTabArgs& a_args ) +{ + TSharedPtr editorPtr = SharedThis( this ); + + // Spawn the tab + return SNew( SDockTab ) + .Label( LOCTEXT( "SKDViewportTab_Title", "Skill Tree Details" ) ) + [ + SNew( SSkillTreePropertiesTabBody, editorPtr ) + ]; +} + +TSharedRef FSkillTreeEditorViewport::SpawnTab_SDViewport( const FSpawnTabArgs& a_args ) +{ + TSharedPtr editorPtr = SharedThis( this ); + + // Spawn the tab + return SNew( SDockTab ) + .Label( LOCTEXT( "SDViewportTab_Title", "Skill Details" ) ) + [ + SNew( SSkillPropertiesTabBody, editorPtr ) + ]; +} + +void FSkillTreeEditorViewport::RegisterTabSpawners( const TSharedRef& a_tabManager ) +{ + WorkspaceMenuCategory = a_tabManager->AddLocalWorkspaceMenuCategory( LOCTEXT( "WorkspaceMenu_SpriteEditor", "Skill Tree Editor" ) ); + auto workspaceMenuCategoryRef = WorkspaceMenuCategory.ToSharedRef(); + + FAssetEditorToolkit::RegisterTabSpawners( a_tabManager ); + + a_tabManager->RegisterTabSpawner( FSkillTreeEditorTabs::skillTreeViewportID, FOnSpawnTab::CreateSP( this, &FSkillTreeEditorViewport::SpawnTab_SKViewport ) ) + .SetDisplayName( LOCTEXT( "SKViewportTab", "Skill Tree" ) ) + .SetGroup( workspaceMenuCategoryRef ) + .SetIcon( FSlateIcon( FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.Viewports" ) ); + + a_tabManager->RegisterTabSpawner( FSkillTreeEditorTabs::skillTreeDetailViewportID, FOnSpawnTab::CreateSP( this, &FSkillTreeEditorViewport::SpawnTab_SKDViewport ) ) + .SetDisplayName( LOCTEXT( "SKDViewportTab", "Skill Tree Details" ) ) + .SetGroup( workspaceMenuCategoryRef ) + .SetIcon( FSlateIcon( FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.Viewports" ) ); + + a_tabManager->RegisterTabSpawner( FSkillTreeEditorTabs::skillViewportID, FOnSpawnTab::CreateSP( this, &FSkillTreeEditorViewport::SpawnTab_SViewport ) ) + .SetDisplayName( LOCTEXT( "SViewportTab", "Skill" ) ) + .SetGroup( workspaceMenuCategoryRef ) + .SetIcon( FSlateIcon( FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.Viewports" ) ); + + a_tabManager->RegisterTabSpawner( FSkillTreeEditorTabs::skillDetailViewportID, FOnSpawnTab::CreateSP( this, &FSkillTreeEditorViewport::SpawnTab_SDViewport ) ) + .SetDisplayName( LOCTEXT( "SDViewportTab", "Skill Details" ) ) + .SetGroup( workspaceMenuCategoryRef ) + .SetIcon( FSlateIcon( FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.Viewports" ) ); +} + +void FSkillTreeEditorViewport::UnregisterTabSpawners( const TSharedRef& a_tabManager ) +{ + FAssetEditorToolkit::UnregisterTabSpawners( a_tabManager ); + + a_tabManager->UnregisterTabSpawner( FSkillTreeEditorTabs::skillTreeViewportID ); + a_tabManager->UnregisterTabSpawner( FSkillTreeEditorTabs::skillTreeDetailViewportID ); + a_tabManager->UnregisterTabSpawner( FSkillTreeEditorTabs::skillViewportID ); + a_tabManager->UnregisterTabSpawner( FSkillTreeEditorTabs::skillDetailViewportID ); +} + +FName FSkillTreeEditorViewport::GetToolkitFName() const +{ + return FName( "SkillTreeEditor" ); +} + +void FSkillTreeEditorViewport::SetSkillBeingEdited( USkillObject* a_skill ) +{ + if ( (a_skill != m_skillBeingEdited) && (a_skill != nullptr) ) + { + USkillObject* oldSkill = m_skillBeingEdited; + m_skillBeingEdited = a_skill; + + //No longer needed. + /* + RemoveEditingObject( oldSkill ); + AddEditingObject( a_skill ); + */ + } +} + +FText FSkillTreeEditorViewport::GetBaseToolkitName() const +{ + return LOCTEXT( "SkillTreeEditorAppLabel", "Skill Tree" ); +} + +FText FSkillTreeEditorViewport::GetToolkitName() const +{ + bool dirtyState = (m_skillTreeBeingEdited->GetOutermost()->IsDirty() || m_skillBeingEdited->GetOutermost()->IsDirty()); + + FFormatNamedArguments args; + args.Add( TEXT( "SkillTreeName" ), FText::FromString( m_skillTreeBeingEdited->GetName() ) ); + args.Add( TEXT( "DirtyState" ), dirtyState ? FText::FromString( TEXT( "*" ) ) : FText::GetEmpty() ); + return FText::Format( LOCTEXT( "SkillTreeEditorAppLabel", "{SkillTreeName}{DirtyState}" ), args ); +} + +FText FSkillTreeEditorViewport::GetToolkitToolTipText() const +{ + return LOCTEXT( "SkillTreeToolTip", "Test tooltip text" ); +} + +FString FSkillTreeEditorViewport::GetWorldCentricTabPrefix() const +{ + return TEXT( "SkillTreeEditor" ); +} + +FString FSkillTreeEditorViewport::GetDocumentationLink() const +{ + return TEXT( "http://iamfromtheinternet.nl/wiki/index.php/Skill_Tree" ); +} + +FLinearColor FSkillTreeEditorViewport::GetWorldCentricTabColorScale() const +{ + return FLinearColor::White; +} + +void FSkillTreeEditorViewport::CreateModeToolbarWidgets( FToolBarBuilder& a_ignoredBuilder ) +{ + FToolBarBuilder toolbarBuilder( m_skillTreeViewport->GetCommandList(), FMultiBoxCustomization::None ); + toolbarBuilder.AddToolBarButton( FSkillTreeEditorCommands::Get().enterViewMode ); + AddToolbarWidget( toolbarBuilder.MakeWidget() ); +} + +void FSkillTreeEditorViewport::InitSkillTreeEditor( const EToolkitMode::Type a_mode, const TSharedPtr< class IToolkitHost >& a_toolkitHost, class USkillTreeObject* a_skillTree, class USkillObject* a_skill ) +{ + m_skillTreeBeingEdited = a_skillTree; + m_tmpSkills = m_skillTreeBeingEdited->skills; + if ( !a_skill ) + { + m_skillBeingEdited = NewObject(); + m_skillBeingEdited->hexMap.width = 0; + m_skillBeingEdited->hexMap.height = 0; + } + else + { + m_skillBeingEdited = a_skill; + selectedY = m_tmpSkills.Find( TSubclassOf((UClass*)m_skillBeingEdited) ); + } + + FSkillTreeEditorCommands::Register(); + + TSharedPtr skillTreeEditorPtr = SharedThis( this ); + m_skillTreeViewport = SNew( SSkillTreeEditorViewport, skillTreeEditorPtr ); + m_skillViewport = SNew( SSkillTreeEditorViewport, skillTreeEditorPtr ); + m_skillViewport->IsSkill( true ); + + const TSharedRef defaultLayout = FTabManager::NewLayout( "SkillTreeEditor_Layout_v2" ) + ->AddArea + ( + FTabManager::NewPrimaryArea() + ->SetOrientation( Orient_Vertical ) + ->Split + ( + FTabManager::NewStack() + ->SetSizeCoefficient( 0.1f ) + ->SetHideTabWell( true ) + ->AddTab( GetToolbarTabId(), ETabState::OpenedTab ) + ) + ->Split + ( + FTabManager::NewSplitter() + ->SetOrientation( Orient_Horizontal ) + ->SetSizeCoefficient( 0.9f ) + ->Split + ( + FTabManager::NewStack() + ->SetSizeCoefficient( 0.35f ) + ->SetHideTabWell( true ) + ->AddTab( FSkillTreeEditorTabs::skillTreeViewportID, ETabState::OpenedTab ) + ) + ->Split + ( + FTabManager::NewStack() + ->SetSizeCoefficient( 0.15f ) + ->SetHideTabWell( true ) + ->AddTab( FSkillTreeEditorTabs::skillTreeDetailViewportID, ETabState::OpenedTab ) + ) + ->Split + ( + FTabManager::NewStack() + ->SetSizeCoefficient( 0.35f ) + ->SetHideTabWell( true ) + ->AddTab( FSkillTreeEditorTabs::skillViewportID, ETabState::OpenedTab ) + ) + ->Split + ( + FTabManager::NewStack() + ->SetSizeCoefficient( 0.15f ) + ->SetHideTabWell( true ) + ->AddTab( FSkillTreeEditorTabs::skillDetailViewportID, ETabState::OpenedTab ) + ) + ) + ); + + // Initialize the asset editor + InitAssetEditor( a_mode, a_toolkitHost, skillTreeEditorAppName, defaultLayout, true, true, a_skillTree ); + + m_skillTreeViewport->ActivateEditMode(); + m_skillViewport->ActivateEditMode(); + + // Extend things + RegenerateMenusAndToolbars(); +} + +void FSkillTreeEditorViewport::UpdateSkills() +{ + if ( m_skillTreeBeingEdited->skills != m_tmpSkills ) + { + for ( int i = 0; i < m_tmpSkills.Num(); i++ ) + { + if ( m_tmpSkills[i] != NULL && !m_skillTreeBeingEdited->skills.Contains( m_tmpSkills[i] ) ) + { + USkillObject* tmpSkill = (USkillObject*)*m_tmpSkills[i];//m_tmpSkills[i]->GetDefaultObject(); + if ( tmpSkill != NULL ) tmpSkill->skillTree = NULL; + } + } + for ( int i = 0; i < m_skillTreeBeingEdited->skills.Num(); i++ ) + { + if ( m_skillTreeBeingEdited->skills[i] != NULL && !m_tmpSkills.Contains( m_skillTreeBeingEdited->skills[i] ) ) + { + USkillObject* tmpSkill = (USkillObject*)*m_skillTreeBeingEdited->skills[i]; //m_skillTreeBeingEdited->skills[i]->GetDefaultObject(); + if ( tmpSkill != NULL ) tmpSkill->skillTree = m_skillTreeBeingEdited; + } + } + m_tmpSkills = m_skillTreeBeingEdited->skills; + } +} + +void FSkillTreeEditorViewport::SaveAsset_Execute() +{ + TArray< UPackage* > PackagesToSave; + PackagesToSave.Add( m_skillTreeBeingEdited->GetOutermost() ); + PackagesToSave.Add( m_skillBeingEdited->GetOutermost() ); + + FEditorFileUtils::PromptForCheckoutAndSave( PackagesToSave, bCheckDirtyOnAssetSave, /*bPromptToSave=*/ false ); +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Plugins/SkillTree/Source/SkillTreeEditor/Private/SkillTreeEditorViewport.h b/Plugins/SkillTree/Source/SkillTreeEditor/Private/SkillTreeEditorViewport.h new file mode 100644 index 0000000..27ce1bc --- /dev/null +++ b/Plugins/SkillTree/Source/SkillTreeEditor/Private/SkillTreeEditorViewport.h @@ -0,0 +1,71 @@ +// Project Lab - NHTV IGAD +////////////////////////////////////////// +// Author: Yoshi van Belkom - 130118 +////////////////////////////////////////// +// The Skill Tree Editor main class. +////////////////////////////////////////// + +#pragma once + +#include "Toolkits/AssetEditorToolkit.h" +#include "Toolkits/AssetEditorManager.h" + +class SSkillTreeEditorViewport; +class USkillTreeObject; +class USkillObject; + +class FSkillTreeEditorViewport : public FAssetEditorToolkit//, public FGCObject +{ +public: + //The ID of the selected Skill. + int32 selectedY = -1; + + // IToolkit interface + virtual void RegisterTabSpawners( const TSharedRef& a_tabManager ) override; + virtual void UnregisterTabSpawners( const TSharedRef& a_tabManager ) override; + // End of IToolkit interface + + // FAssetEditorToolkit + virtual FName GetToolkitFName() const override; + virtual FText GetBaseToolkitName() const override; + virtual FText GetToolkitName() const override; + virtual FText GetToolkitToolTipText() const override; + virtual FLinearColor GetWorldCentricTabColorScale() const override; + virtual FString GetWorldCentricTabPrefix() const override; + virtual FString GetDocumentationLink() const override; + virtual void SaveAsset_Execute() override; + // End of FAssetEditorToolkit + + //Set the Skill to Edit. + void SetSkillBeingEdited( USkillObject* a_skill ); + //Get the Current Skill Tree that is being edited. + USkillTreeObject* GetSkillTreeBeingEdited() const { return m_skillTreeBeingEdited; }; + //Get the Current Skill that is being edited. + USkillObject* GetSkillBeingEdited() const { return m_skillBeingEdited; }; + + void InitSkillTreeEditor( const EToolkitMode::Type a_mode, const TSharedPtr< class IToolkitHost >& a_toolkitHost, class USkillTreeObject* a_skillTree, class USkillObject* a_skill = NULL ); + + void UpdateSkills(); +protected: + // A temporary list of Skills to check agains to see if new Skills have been added. + TArray> m_tmpSkills; + + // The pointer to the current Skill Tree. + USkillTreeObject* m_skillTreeBeingEdited; + // The pointer to the current Skill. + USkillObject* m_skillBeingEdited = NULL; + + // The pointer to the Skill Tree Viewport. + TSharedPtr m_skillTreeViewport; + // The pointer to the Skill Viewport. + TSharedPtr m_skillViewport; + + // Functions to spawn the Viewports and Details + TSharedRef SpawnTab_SKViewport( const FSpawnTabArgs& a_args ); + TSharedRef SpawnTab_SViewport( const FSpawnTabArgs& a_args ); + TSharedRef SpawnTab_SKDViewport( const FSpawnTabArgs& a_args ); + TSharedRef SpawnTab_SDViewport( const FSpawnTabArgs& a_args ); + + // Creates the toolbar buttons. + void CreateModeToolbarWidgets( FToolBarBuilder& ToolbarBuilder ); +}; diff --git a/Plugins/SkillTree/Source/SkillTreeEditor/Private/SkillTreeEditorViewportClient.cpp b/Plugins/SkillTree/Source/SkillTreeEditor/Private/SkillTreeEditorViewportClient.cpp new file mode 100644 index 0000000..d8355a0 --- /dev/null +++ b/Plugins/SkillTree/Source/SkillTreeEditor/Private/SkillTreeEditorViewportClient.cpp @@ -0,0 +1,333 @@ +// Project Lab - NHTV IGAD +////////////////////////////////////////// +// Author: Yoshi van Belkom - 130118 +////////////////////////////////////////// + +#include "SkillTreeEditorPrivatePCH.h" +#include "SkillTreeEditorViewportClient.h" +#include "SceneViewport.h" + +#include "PreviewScene.h" +#include "ScopedTransaction.h" +#include "Runtime/Engine/Public/ComponentReregisterContext.h" + +#include "AssetToolsModule.h" +#include "AssetRegistryModule.h" +#include "CanvasTypes.h" +#include "CanvasItem.h" + +#include "PhysicsEngine/BodySetup2D.h" +#include "SEditorViewport.h" + +#include "SNotificationList.h" +#include "NotificationManager.h" + +#include "SkillTreeFactory.h" +#include "SkillTreeObject.h" +#include "SkillObject.h" + +#define LOCTEXT_NAMESPACE "SkillTreeEditor" + +FSkillTreeEditorViewportClient::FSkillTreeEditorViewportClient( TWeakPtr a_skillTreeEditor, TWeakPtr a_skillTreeEditorViewportPtr ) + : FEditorViewportClient( new FAssetEditorModeManager(), nullptr, a_skillTreeEditorViewportPtr ) + , m_skillTreeEditorPtr( a_skillTreeEditor ) + , m_skillTreeEditorViewportPtr( a_skillTreeEditorViewportPtr ) +{ + check( m_skillTreeEditorPtr.IsValid() && m_skillTreeEditorViewportPtr.IsValid() ); + + // The tile map editor fully supports mode tools and isn't doing any incompatible stuff with the Widget + Widget->SetUsesEditorModeTools( ModeTools ); + + PreviewScene = &m_ownedPreviewScene; + ((FAssetEditorModeManager*)ModeTools)->SetPreviewScene( PreviewScene ); + + SetRealtime( true ); + + DrawHelper.bDrawGrid = false; + + EngineShowFlags.DisableAdvancedFeatures(); + EngineShowFlags.CompositeEditorPrimitives = false; //REMINDER: Turned off. +} + +void FSkillTreeEditorViewportClient::Draw( const FSceneView* a_view, FPrimitiveDrawInterface* a_drawInterface ) +{ + FEditorViewportClient::Draw( a_view, a_drawInterface ); +} + +void FSkillTreeEditorViewportClient::DrawCanvas( FViewport& a_viewport, FSceneView& a_view, FCanvas& a_canvas ) +{ + const bool isHitTesting = a_canvas.IsHitTesting(); + if ( !isHitTesting ) + { + a_canvas.SetHitProxy( nullptr ); + } + + if ( !m_skillTreeEditorPtr.IsValid() ) + { + return; + } + + m_screenSize = a_viewport.GetSizeXY(); + + USkillTreeObject* skillTree = m_skillTreeEditorPtr.Pin()->GetSkillTreeBeingEdited(); + USkillObject* skill = m_skillTreeEditorPtr.Pin()->GetSkillBeingEdited(); + + int offsetY = -m_hexSize; + int height; + int maxY; + int width; + + if ( m_state ) + { + height = skill->hexMap.height; + if ( height % 2 != 0 ) offsetY = m_hexSize; + width = skill->hexMap.width; + maxY = height - 1; + } + else + { + height = 16; + width = 13; + maxY = 15; + } + + FLinearColor enabledColor = FLinearColor( 0.43f, 0.43f, 0.43f ); + FLinearColor disabledColor = FLinearColor( 0.10f, 0.10f, 0.10f ); + + for ( float x = 0; x < width; x++ ) + { + maxY = (maxY == (height - 1) ? height : (height - 1)); + offsetY = (offsetY == -m_hexSize ? 0 : -m_hexSize); + for ( int y = 0; y < maxY; y++ ) + { + bool test; + if ( m_state ) + { + test = skill->hexMap.Get( x, y ); + } + else + { + test = skillTree->hexMap.Get( x, y ); + } + FLinearColor hexColor = enabledColor; + if ( !test ) + { + hexColor = disabledColor; + } + float X = (x - (((float)width) / 2.0f)) * m_hexSize * 2; + float Y = ((y - (maxY / 2)) * m_hexSize * 2) + offsetY; + FCanvasNGonItem hexagonBorder( FVector2D( ( m_screenSize.X / 2 ) + X, ( m_screenSize.Y / 2 ) + Y ), FVector2D( m_hexSize, m_hexSize ), 6, hexColor ); + hexagonBorder.Draw( &a_canvas ); + + if ( m_state ) + { + FCanvasTextItem TextItem( FVector2D( ( m_screenSize.X / 2 ) + X, ( m_screenSize.Y / 2 ) + Y ), FText::Format( LOCTEXT( "PositionStr", "{0},{1}" ), FText::AsNumber( x ), FText::AsNumber( y ) ), GEngine->GetSmallFont(), FLinearColor::White ); + TextItem.EnableShadow( FLinearColor::Black ); + TextItem.bCentreX = true; + + TextItem.Draw( &a_canvas ); + } + } + } + + float hexmapWidth = ( m_screenSize.X / 2 ) + ( ( ( width - 1 ) - ( ( (float)width ) / 2.0f ) ) * m_hexSize * 2 ); + hexmapWidth += m_hexSize; + int boxWidth = ( ( m_screenSize.X - hexmapWidth ) ); + boxWidth = FMath::Clamp( boxWidth, 0, 150 ); + if ( !m_state ) + { + for ( int i = 0, y = 50; i < skillTree->skills.Num(); i++, y += 20 ) + { + if ( !skillTree->skills[i] ) continue; + if ( m_hoverY == i || m_skillTreeEditorPtr.Pin()->selectedY == i ) + { + int minboxX = m_screenSize.X - boxWidth; + int minboxY = y; + FCanvasBoxItem boxItem( FVector2D( minboxX, minboxY ), FVector2D( boxWidth, 20) ); + if ( m_hoverY == i ) boxItem.SetColor( FLinearColor::Black ); + if ( m_skillTreeEditorPtr.Pin()->selectedY == i ) boxItem.SetColor( FLinearColor::White ); + boxItem.Draw( &a_canvas ); + } + FFormatNamedArguments args; + args.Add( TEXT( "DirtyState" ), skillTree->skills[i]->GetOutermost()->IsDirty() ? FText::FromString( TEXT( "*" ) ) : FText::GetEmpty() ); + args.Add( TEXT( "SkillName" ), FText::AsCultureInvariant( skillTree->skills[i]->GetName() ) ); + const FText assetName = FText::Format( LOCTEXT( "SkillName", "{SkillName}{DirtyState}" ), args ); + FCanvasTextItem TextItem( FVector2D( m_screenSize.X - ( ( m_screenSize.X - hexmapWidth ) / 2 ), y ), assetName, GEngine->GetSmallFont(), FLinearColor::White ); + TextItem.EnableShadow( FLinearColor::Black ); + TextItem.bCentreX = true; + + TextItem.Draw( &a_canvas ); + } + } + + int minboxX = 0; + int minboxY = 50; + FCanvasTileItem enabledColorSquare( FVector2D( 0, 50 ), FVector2D( boxWidth, 20 ), enabledColor ); + FCanvasTileItem disabledColorSquare( FVector2D( 0, 70 ), FVector2D( boxWidth, 20 ), disabledColor ); + enabledColorSquare.Draw( &a_canvas ); + disabledColorSquare.Draw( &a_canvas ); + + FCanvasTextItem enabledTextItem( FVector2D( boxWidth / 2, 50 ), FText::FromString( TEXT( "Solid" ) ), GEngine->GetSmallFont(), FLinearColor::White ); + enabledTextItem.EnableShadow( FLinearColor::Black ); + enabledTextItem.bCentreX = true; + enabledTextItem.Draw( &a_canvas ); + + FCanvasTextItem disabledTextItem( FVector2D( boxWidth / 2, 70 ), FText::FromString( TEXT( "Empty" ) ), GEngine->GetSmallFont(), FLinearColor::White ); + disabledTextItem.EnableShadow( FLinearColor::Black ); + disabledTextItem.bCentreX = true; + disabledTextItem.Draw( &a_canvas ); + + FEditorViewportClient::DrawCanvas( a_viewport, a_view, a_canvas ); +} + +void FSkillTreeEditorViewportClient::MouseMove( FViewport* a_viewport, int32 a_mouseX, int32 a_mouseY ) +{ + if ( m_state ) return; + USkillTreeObject* skillTree = m_skillTreeEditorPtr.Pin()->GetSkillTreeBeingEdited(); + int width = 13; + + float hexmapWidth = ( m_screenSize.X / 2 ) + ( ( ( width - 1 ) - ( ( (float)width ) / 2.0f ) ) * m_hexSize * 2 ); + hexmapWidth += m_hexSize; + + int minboxX = m_screenSize.X - ( ( m_screenSize.X - hexmapWidth ) ); + int maxboxX = m_screenSize.X; + for ( int i = 0, y = 50; i < skillTree->skills.Num(); i++, y += 20 ) + { + int minboxY = y; + int maxboxY = y + 20; + if ( !skillTree->skills[i] ) continue; + if ( a_mouseX >= minboxX && a_mouseX <= maxboxX && a_mouseY >= minboxY && a_mouseY <= maxboxY ) + { + m_hoverY = i; + FEditorViewportClient::MouseMove( a_viewport, a_mouseX, a_mouseY ); + return; + } + } + m_hoverY = -1; + + FEditorViewportClient::MouseMove( a_viewport, a_mouseX, a_mouseY ); +} + +void FSkillTreeEditorViewportClient::InternalActivateNewMode( ) +{ + //m_currentMode = a_newMode; + Viewport->InvalidateHitProxy(); +} + +FLinearColor FSkillTreeEditorViewportClient::GetBackgroundColor() const +{ + return FLinearColor( 0.20f, 0.22f, 0.22f ); +} + +void FSkillTreeEditorViewportClient::ActivateEditMode() +{ + // Activate the skill tree edit mode + ModeTools->SetToolkitHost( m_skillTreeEditorPtr.Pin()->GetToolkitHost() ); + ModeTools->ActivateDefaultMode(); +} + +void FSkillTreeEditorViewportClient::Tick( float a_deltaSeconds ) +{ + FEditorViewportClient::Tick( a_deltaSeconds ); + + m_skillTreeEditorPtr.Pin()->UpdateSkills(); + + if ( !GIntraFrameDebuggingGameThread ) + { + m_ownedPreviewScene.GetWorld()->Tick( LEVELTICK_All, a_deltaSeconds ); + } +} + +void FSkillTreeEditorViewportClient::ProcessClick( FSceneView& a_view, HHitProxy* a_hitProxy, FKey a_key, EInputEvent a_event, uint32 a_hitX, uint32 a_hitY ) +{ + bool handled = false; + USkillTreeObject* skillTree = m_skillTreeEditorPtr.Pin()->GetSkillTreeBeingEdited(); + USkillObject* skill = m_skillTreeEditorPtr.Pin()->GetSkillBeingEdited(); + + int offsetY = -m_hexSize; + int height; + int maxY; + int width; + + if ( m_state ) + { + height = skill->hexMap.height; + width = skill->hexMap.width; + if ( height % 2 != 0 ) offsetY = m_hexSize; + maxY = height-1; + } + else + { + height = 16; + width = 13; + maxY = 15; + } + + FVector2D mpos( a_hitX, a_hitY ); + + for ( float x = 0; x < width; x++ ) + { + maxY = (maxY == (height - 1) ? height : (height - 1)); + offsetY = ( offsetY == -m_hexSize ? 0 : -m_hexSize ); + for ( int y = 0; y < maxY; y++ ) + { + float X = ( x - ( ( (float)width ) / 2.0f ) ) * m_hexSize * 2; + float Y = ( ( y - ( maxY / 2 ) ) * m_hexSize * 2 ) + offsetY; + FVector2D pos( ( m_screenSize.X / 2 ) + X, ( m_screenSize.Y / 2 ) + Y ); + float dist = FVector2D::Distance( pos, mpos ); + if ( dist <= m_hexSize ) + { + if ( !m_state ) + { + skillTree->hexMap.Invert( x, y ); + skillTree->Modify(); + skillTree->MarkPackageDirty(); + skillTree->PostEditChange(); + } + else + { + skill->hexMap.Invert( x, y ); + skill->Modify(); + skill->MarkPackageDirty(); + skill->PostEditChange(); + } + handled = true; + Invalidate(); + break; + } + } + if ( handled ) + { + break; + } + } + + if ( !handled && !m_state ) + { + float hexmapWidth = ( m_screenSize.X / 2 ) + ( ( ( width - 1 ) - ( ( (float)width ) / 2.0f ) ) * m_hexSize * 2 ); + hexmapWidth += m_hexSize; + + uint32 minboxX = m_screenSize.X - ( ( m_screenSize.X - hexmapWidth ) ); + uint32 maxboxX = m_screenSize.X; + for ( int i = 0, y = 50; i < skillTree->skills.Num(); i++, y += 20 ) + { + uint32 minboxY = y; + uint32 maxboxY = y + 20; + if ( !skillTree->skills[i] ) continue; + if ( a_hitX >= minboxX && a_hitX <= maxboxX && a_hitY >= minboxY && a_hitY <= maxboxY ) + { + m_skillTreeEditorPtr.Pin()->selectedY = i; + handled = true; + USkillObject* tmpCast = skillTree->skills[i]->GetDefaultObject(); + if ( tmpCast != NULL ) m_skillTreeEditorPtr.Pin()->SetSkillBeingEdited( tmpCast ); + } + } + } + + if ( !handled ) + { + FEditorViewportClient::ProcessClick( a_view, a_hitProxy, a_key, a_event, a_hitX, a_hitY ); + } +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Plugins/SkillTree/Source/SkillTreeEditor/Private/SkillTreeEditorViewportClient.h b/Plugins/SkillTree/Source/SkillTreeEditor/Private/SkillTreeEditorViewportClient.h new file mode 100644 index 0000000..77367bd --- /dev/null +++ b/Plugins/SkillTree/Source/SkillTreeEditor/Private/SkillTreeEditorViewportClient.h @@ -0,0 +1,78 @@ +// Project Lab - NHTV IGAD +////////////////////////////////////////// +// Author: Yoshi van Belkom - 130118 +////////////////////////////////////////// +// The Skill Tree Editor main viewport. +// Can be used for editing a Skill Tree +// and a Skill. +////////////////////////////////////////// + +#pragma once + +#include "SkillTreeEditorViewport.h" +#include "SceneViewport.h" +#include "AssetEditorModeManager.h" +#include "PreviewScene.h" +#include "ScopedTransaction.h" +#include "AssetData.h" +#include "SkillTreeObject.h" + +class FSkillTreeEditorViewportClient : public FEditorViewportClient +{ +public: + FSkillTreeEditorViewportClient( TWeakPtr a_skillTreeEditor, TWeakPtr a_skillTreeEditorViewportPtr ); + + // FViewportClient interface + virtual void Draw( const FSceneView* a_view, FPrimitiveDrawInterface* a_drawInterface ) override; + virtual void DrawCanvas( FViewport& a_viewport, FSceneView& a_view, FCanvas& a_canvas ) override; + virtual void Tick( float a_deltaSeconds ) override; + // End of FViewportClient interface + + // FEditorViewportClient interface + virtual void ProcessClick( FSceneView& a_view, HHitProxy* a_hitProxy, FKey a_key, EInputEvent a_event, uint32 a_hitX, uint32 a_hitY ) override; + virtual void MouseMove( FViewport* a_viewport, int32 a_mouseX, int32 a_mouseY ) override; + virtual FLinearColor GetBackgroundColor() const override; + // End of FEditorViewportClient interface + + //virtual void RequestFocusOnSelection( bool a_instant ); + + void EnterViewMode() { InternalActivateNewMode( ); } + + bool IsInViewMode() const { return true; } + + void UpdateRelatedSpritesList(); + + void ActivateEditMode(); + + // Set the current state of the Viewport. + void IsSkill( bool a_state ) + { + m_state = a_state; + } + +private: + // The ID of the current hovered Skill. + int32 m_hoverY = -1; + // The current state of the Viewport. + // True = Skill + // False = Skill Tree + bool m_state = false; + + // The Viewport screen size. + FIntPoint m_screenSize; + + // The preview scene. + FPreviewScene m_ownedPreviewScene; + + // Skill Tree Editor that owns this viewport. + TWeakPtr m_skillTreeEditorPtr; + + // Pointer back to the Skill Tree viewport control that owns this. + TWeakPtr m_skillTreeEditorViewportPtr; + + // The radius of the Hexagons. + float m_hexSize = 20; + + void InternalActivateNewMode(); + +}; diff --git a/Plugins/SkillTree/Source/SkillTreeEditor/Private/SkillTreeFactory.cpp b/Plugins/SkillTree/Source/SkillTreeEditor/Private/SkillTreeFactory.cpp new file mode 100644 index 0000000..cfd600c --- /dev/null +++ b/Plugins/SkillTree/Source/SkillTreeEditor/Private/SkillTreeFactory.cpp @@ -0,0 +1,29 @@ +// Project Lab - NHTV IGAD +////////////////////////////////////////// +// Author: Yoshi van Belkom - 130118 +////////////////////////////////////////// + +#include "SkillTreeEditorPrivatePCH.h" +#include "SkillTreeFactory.h" +#include "SkillTreeObject.h" +#include "AssetRegistryModule.h" +#include "PackageTools.h" + +#define LOCTEXT_NAMESPACE "SkillTree" + +USkillTreeFactory::USkillTreeFactory( const FObjectInitializer& ObjectInitializer ) + : Super( ObjectInitializer ) +{ + bCreateNew = true; + bEditAfterNew = true; + SupportedClass = USkillTreeObject::StaticClass(); +} + +UObject* USkillTreeFactory::FactoryCreateNew( UClass* a_class, UObject* a_parent, FName a_name, EObjectFlags a_flags, UObject* a_context, FFeedbackContext* a_warn ) +{ + USkillTreeObject* newSkillTree = NewObject( a_parent, a_class, a_name, a_flags | RF_Transactional ); + + return newSkillTree; +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Plugins/SkillTree/Source/SkillTreeEditor/Public/ISkillTreeEditor.h b/Plugins/SkillTree/Source/SkillTreeEditor/Public/ISkillTreeEditor.h new file mode 100644 index 0000000..b61d129 --- /dev/null +++ b/Plugins/SkillTree/Source/SkillTreeEditor/Public/ISkillTreeEditor.h @@ -0,0 +1,27 @@ +// Project Lab - NHTV IGAD +////////////////////////////////////////// +// Author: Yoshi van Belkom - 130118 +////////////////////////////////////////// +// The Skill Tree Editor Module Interface. +////////////////////////////////////////// + +#pragma once + +#include "ModuleManager.h" + +class ISkillTreeEditor : public IModuleInterface +{ +public: + //Function to Get the Singleton of this Module if it's loaded. + static inline ISkillTreeEditor& Get() + { + return FModuleManager::LoadModuleChecked< ISkillTreeEditor >( "SkillTreeEditor" ); + } + + //Function to check if the Module is loaded. + static inline bool IsAvailable() + { + return FModuleManager::Get().IsModuleLoaded( "SkillTreeEditor" ); + } +}; + diff --git a/Plugins/SkillTree/Source/SkillTreeEditor/SkillTreeEditor.Build.cs b/Plugins/SkillTree/Source/SkillTreeEditor/SkillTreeEditor.Build.cs new file mode 100644 index 0000000..03c5f3b --- /dev/null +++ b/Plugins/SkillTree/Source/SkillTreeEditor/SkillTreeEditor.Build.cs @@ -0,0 +1,23 @@ +////////////////////////////////////////// +// Author: Yoshi van Belkom - 130118 +////////////////////////////////////////// +// The Skill Tree Editor Module Settings. +////////////////////////////////////////// + +namespace UnrealBuildTool.Rules +{ + public class SkillTreeEditor : ModuleRules + { + public SkillTreeEditor(TargetInfo Target) + { + + PrivateIncludePaths.Add("SkillTreeEditor/Private"); + + PrivateIncludePathModuleNames.AddRange(new string[] { "AssetTools" }); + + PrivateDependencyModuleNames.AddRange(new string[] { "SkillTree", "Core", "CoreUObject", "Engine", "InputCore", "Slate", "UnrealEd", "SlateCore", "EditorStyle", "Kismet", "PropertyEditor", "KismetWidgets", "ContentBrowser", "WorkspaceMenuStructure", "EditorWidgets" }); + + DynamicallyLoadedModuleNames.AddRange(new string[] { "AssetTools" }); + } + } +} \ No newline at end of file diff --git a/Source/UnrealProject/Abilities/AbilityEventGroup.cpp b/Source/UnrealProject/Abilities/AbilityEventGroup.cpp new file mode 100644 index 0000000..e725273 --- /dev/null +++ b/Source/UnrealProject/Abilities/AbilityEventGroup.cpp @@ -0,0 +1,165 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "AbilityEventGroup.h" +#include "NetworkCharacter.h" +#include "NPCBase.h" + +AAbilityEventGroup::AAbilityEventGroup() +{ + // Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it. + PrimaryActorTick.bCanEverTick = true; + channelEvent = false; + stunWhileChannel = true; + progressionType = EAbilityEventGroupProgressionType::Continue; + duration = 1.0f; + m_endEventSend = false; + m_hasBeenDestroyed = false; + allowRotateWhileChannel = false; +} + +void AAbilityEventGroup::BeginPlay() +{ + check(Role == ROLE_Authority); + Super::BeginPlay(); + m_life = 0.0f; + + if(!character || character->IsActorBeingDestroyed()) + { + m_MoveToNextGroup(); + } + else + { + // Handle the destruction of the caster + character->OnDestroyed.AddDynamic(this, &AAbilityEventGroup::m_OnCharacterDestroyed); + } +} +void AAbilityEventGroup::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + check(Role == ROLE_Authority); + if(EndPlayReason == EEndPlayReason::Destroyed) + { + Super::EndPlay(EndPlayReason); + AAbilityEventGroup* nextGroup = nullptr; + if(m_nextGroups.Num() > 0) + { + //GWPRINT(L"Move next"); + nextGroup = SpawnSequence(character, abilityInfo, abilityState, m_nextGroups); + } + m_SendEndEvent(nextGroup); + } +} +void AAbilityEventGroup::Tick(float DeltaTime) +{ + Super::Tick(DeltaTime); + + if(IsPendingKill()) + return; + + m_life += DeltaTime; + if(progressionType == EAbilityEventGroupProgressionType::Continue) + { + if(m_life >= duration || m_continue) + m_MoveToNextGroup(); + } + else + { + if(m_continue) + m_MoveToNextGroup(); + } +} + +void AAbilityEventGroup::m_OnCharacterDestroyed() +{ + m_MoveToNextGroup(); +} +void AAbilityEventGroup::m_MoveToNextGroup() +{ + if(m_hasBeenDestroyed) + return; + m_hasBeenDestroyed = true; + if(IsValid(this) && !IsPendingKillPending()) + Destroy(); +} + +void AAbilityEventGroup::m_SendEndEvent(AAbilityEventGroup* next) +{ + if(!m_endEventSend) + { + onAbilityEventGroupEnded.Broadcast(abilityInfo, this, next); + m_endEventSend = true; + } +} + +void AAbilityEventGroup::Interrupt() +{ + //GPRINT("Interrupting ability"); + if(IsPendingKill()) + return; + m_nextGroups.Empty(); // Don't execute any follow up events + m_hasBeenDestroyed = true; +} + +void AAbilityEventGroup::TransitionTo(class AAbilityEventGroup* newGroup) +{ + check(newGroup); + check(abilityInfo); + check(abilityState); + newGroup->character = character; + newGroup->abilityInfo = abilityInfo; + newGroup->abilityState = abilityState; + newGroup->m_nextGroups = m_nextGroups; + m_nextGroups.Empty(); + + m_SendEndEvent(newGroup); + + //GWPRINT(L"TransitionTo next"); + m_MoveToNextGroup(); +} + +void AAbilityEventGroup::NextGroup() +{ + m_continue = true; +} + +ANetworkCharacter* AAbilityEventGroup::GetTarget() +{ + ANPCBase* creature = Cast(character); + if(creature) + { + return creature->target; + } + return nullptr; +} + +float AAbilityEventGroup::GetLifetimeRate() const +{ + float rate = m_life / duration; + return FMath::Fmod(rate, 1.0f + FLT_EPSILON); +} +float AAbilityEventGroup::GetLifetime() const +{ + return m_life; +} + +AAbilityEventGroup* AAbilityEventGroup::SpawnSequence(ANetworkCharacter* character, class UAbilityInfo* abilityInfo, class AAbilityState* abilityState, TArray> groups) +{ + UWorld* world = character->GetWorld(); + check(world); + check(groups.Num() > 0); + + AAbilityEventGroup* group = world->SpawnActorDeferred(groups[0], FTransform::Identity); + check(group); + check(abilityInfo); + check(abilityState); + + group->character = character; + group->abilityState = abilityState; + group->abilityInfo = abilityInfo; + groups.RemoveAt(0); + group->m_nextGroups = groups; + + UGameplayStatics::FinishSpawningActor(group, FTransform::Identity); + + return group; +} \ No newline at end of file diff --git a/Source/UnrealProject/Abilities/AbilityEventGroup.h b/Source/UnrealProject/Abilities/AbilityEventGroup.h new file mode 100644 index 0000000..95db905 --- /dev/null +++ b/Source/UnrealProject/Abilities/AbilityEventGroup.h @@ -0,0 +1,85 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "DealDamageProxy.h" +#include "AbilityEventGroup.generated.h" + +UENUM(BlueprintType) +enum class EAbilityEventGroupProgressionType : uint8 +{ + // Continues when the duration of the group has expired + Continue, + // Loops until the ability itself decides to continue, or is activated again + Loop +}; + + +UCLASS() +class UNREALPROJECT_API AAbilityEventGroup : public ADealDamageProxy +{ + GENERATED_BODY() + +public: + AAbilityEventGroup(); + + virtual void BeginPlay() override final; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason); + virtual void Tick(float DeltaSeconds) override final; + + // Used to construct an ability group sequence from an array of ability groups + static AAbilityEventGroup* SpawnSequence(class ANetworkCharacter* character, class UAbilityInfo* abilityInfo, class AAbilityState* abilityState, TArray> groups); + + // Try to interrupt this group chain if it's a channelEvent + void Interrupt(); + + // Changes the current group to execute this group now + void TransitionTo(class AAbilityEventGroup* newGroup); + + // Try to continue this group if it's progression type is not duration bound + UFUNCTION(BlueprintCallable, Category = "Ability") + void NextGroup(); + + // Value from 0 to 1 representing the life cycle of this event, loops back to 0 if the event is looping after the life exceeded the duration set in the group + UFUNCTION(BlueprintCallable, Category = "Ability Group") + float GetLifetimeRate() const; + UFUNCTION(BlueprintCallable, Category = "Ability Group") + float GetLifetime() const; + + UFUNCTION(BlueprintCallable, Category = "Ability") + class ANetworkCharacter* GetTarget(); + + UPROPERTY(BlueprintReadOnly, Category = "Ability Group") + class AAbilityState* abilityState; + UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Category = "Ability Group") + float duration; + UPROPERTY(EditDefaultsOnly, Category = "Ability Group") + bool channelEvent; + UPROPERTY(EditDefaultsOnly, Category = "Ability Group") + bool stunWhileChannel; + UPROPERTY(EditDefaultsOnly, Category = "Ability Group") + bool allowRotateWhileChannel; + + // Setting this to true makes the ability + UPROPERTY(EditDefaultsOnly, Category = "Ability Group") + EAbilityEventGroupProgressionType progressionType; + + // Called when the group's duration has expired or NextGroup is called + DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOnAbilityEventGroupEnded, class UAbilityInfo*, ability, class AAbilityEventGroup*, current, class AAbilityEventGroup*, next); + FOnAbilityEventGroupEnded onAbilityEventGroupEnded; + +private: + UFUNCTION() + void m_OnCharacterDestroyed(); + void m_MoveToNextGroup(); + void m_SendEndEvent(AAbilityEventGroup* next = nullptr); + + float m_life; + + UPROPERTY() + TArray> m_nextGroups; + + bool m_hasBeenDestroyed; + bool m_endEventSend; + bool m_continue; +}; diff --git a/Source/UnrealProject/Abilities/AbilityFilter.h b/Source/UnrealProject/Abilities/AbilityFilter.h new file mode 100644 index 0000000..11d8aa7 --- /dev/null +++ b/Source/UnrealProject/Abilities/AbilityFilter.h @@ -0,0 +1,17 @@ +// Project Lab - NHTV Igad + +#pragma once +#include "AbilityFilter.generated.h" + +#define ABILITY_FILTER_ENEMY 0x1 +#define ABILITY_FILTER_ALLY 0x2 +#define ABILITY_FILTER_SELF 0x10 + +UENUM(BlueprintType) +enum class EAbilityFilter : uint8 +{ + EnemyAll = 0x1, + AllyAll = 0x2, + All = 0x3, + Self = 0x10, +}; \ No newline at end of file diff --git a/Source/UnrealProject/Abilities/AbilityInfo.cpp b/Source/UnrealProject/Abilities/AbilityInfo.cpp new file mode 100644 index 0000000..a5e8cfb --- /dev/null +++ b/Source/UnrealProject/Abilities/AbilityInfo.cpp @@ -0,0 +1,56 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "AbilityInfo.h" +#include "AbilityState.h" + +namespace +{ + inline uint32 Hash32(const void* data, uint32 length) + { + const uint32 offset = 2166136261U; + const uint32 prime = 16777619U; + const unsigned char* data_bytes = (const unsigned char*)data; + + uint32 value = offset; + for (uint32 next = 0; next < length; ++next) + { + value ^= (uint32)data_bytes[next]; + value *= prime; + } + return value; + } +} + +UAbilityInfo::UAbilityInfo() +{ + name = L""; + description = L""; + cooldown = 1.0f; + AICastRange = 0.0f; + rotateTowardsPlayer = false; + abilityState = AAbilityState::StaticClass(); + + FString strName = GetPathName(); + m_hash = Hash32(strName.GetCharArray().GetData(), strName.Len() * sizeof(wchar_t)); +} + +uint32 UAbilityInfo::GetStaticHash() const +{ + return m_hash; +} + +void AAbilityState::NativeSetCooldown() +{ + SetCooldown(); + cooldownRate = (currentCooldownDuration > 0.0f) ? (onCooldownTimer / currentCooldownDuration) : 0.0f; +} +void AAbilityState::SetCooldown_Implementation() +{ + currentCooldownDuration = info->cooldown; + onCooldownTimer = currentCooldownDuration; +} +bool AAbilityState::IsMaxed() const +{ + return power >= 1.0f; +} \ No newline at end of file diff --git a/Source/UnrealProject/Abilities/AbilityInfo.h b/Source/UnrealProject/Abilities/AbilityInfo.h new file mode 100644 index 0000000..0386911 --- /dev/null +++ b/Source/UnrealProject/Abilities/AbilityInfo.h @@ -0,0 +1,96 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "Engine/DataAsset.h" +#include "Items/ItemBase.h" +#include "AbilityInfo.generated.h" + +UENUM(BlueprintType) +enum class EAbilityCategory : uint8 +{ + Unassigned = 0, + Ranged, + Melee +}; +UENUM(BlueprintType) +enum class EAbilityType : uint8 +{ + Basic, + Ability +}; +UENUM(BlueprintType) +enum class EAbilityActionType : uint8 +{ + Normal, + Toggle, + Hold +}; + + +USTRUCT() +struct FAbilityItem +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere) + USkeletalMesh* mesh; + UPROPERTY(EditAnywhere) + EItemTypeEnum type; +}; + +UCLASS(BlueprintType) +class UNREALPROJECT_API UAbilityInfo : public UDataAsset +{ + GENERATED_BODY() +public: + UAbilityInfo(); + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Ability") + FString name; + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Ability") + FString description; + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Ability") + float cooldown; + UPROPERTY(EditDefaultsOnly) + TArray> events; + UPROPERTY(EditDefaultsOnly) + TSubclassOf precastEvent; + UPROPERTY(EditDefaultsOnly) + TSubclassOf abilityState; + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Ability") + UTexture2D* icon; + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Ability") + float AICastRange; + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Ability") + bool passive; + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Ability") + bool rotateTowardsPlayer; + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Ability") + int32 mana; + + // The action type of the ability, Toggle, Hold or Normal + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Ability") + EAbilityActionType actionType; + // The type of the ability Ability(On AbilityBar) or Basic(Left Mouse Click). + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Ability") + EAbilityType abilityType; + // If the ability is Ranged or Melee + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Ability") + EAbilityCategory abilityCategory; + + UPROPERTY(EditAnywhere) + bool isVisible; + UPROPERTY(EditAnywhere, meta = (EditCondition = "isVisible")) + TArray itemsToEquip; + + bool IsHoldOrToggle() const + { + return actionType == EAbilityActionType::Hold || actionType == EAbilityActionType::Toggle; + } + + uint32 GetStaticHash() const; + +private: + uint32 m_hash; +}; diff --git a/Source/UnrealProject/Abilities/AbilityState.h b/Source/UnrealProject/Abilities/AbilityState.h new file mode 100644 index 0000000..7c5c22e --- /dev/null +++ b/Source/UnrealProject/Abilities/AbilityState.h @@ -0,0 +1,37 @@ +// Project Lab - NHTV Igad + +#pragma once +#include "AbilityState.generated.h" + +UCLASS() +class AAbilityState : public AActor +{ + GENERATED_BODY() +public: + // Sets the ability to be on cooldown + virtual void NativeSetCooldown(); + UFUNCTION(BlueprintNativeEvent, Category = "Ability State") + void SetCooldown(); + + UFUNCTION(BlueprintCallable, Category = "Ability State") + bool IsMaxed() const; + + // Total duration of cooldown + UPROPERTY(BlueprintReadWrite) + float currentCooldownDuration; + + // Current state of cooldown going from max->0 + UPROPERTY(BlueprintReadWrite) + float onCooldownTimer; + + // Current state of cooldown going from 1->0 + UPROPERTY(BlueprintReadOnly) + float cooldownRate; + + UPROPERTY(BlueprintReadOnly) + float power; + UPROPERTY(BlueprintReadOnly) + bool toggleState; + UPROPERTY(BlueprintReadOnly) + class UAbilityInfo* info; +}; diff --git a/Source/UnrealProject/Abilities/AbilityTriggerBase.cpp b/Source/UnrealProject/Abilities/AbilityTriggerBase.cpp new file mode 100644 index 0000000..e57b096 --- /dev/null +++ b/Source/UnrealProject/Abilities/AbilityTriggerBase.cpp @@ -0,0 +1,221 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "NetworkCharacter.h" +#include "AbilityTriggerBase.h" +#include "BlueprintAbilityLibrary.h" + + +AAbilityTriggerBase::AAbilityTriggerBase() +{ + PrimaryActorTick.bCanEverTick = true; + + filter = EAbilityFilter::EnemyAll; + autoDestroy = true; + bReplicateMovement = true; + bReplicates = true; + duration = 1.0f; + ticksPerSecond = 0.0f; + m_lifeTime = 0.0f; +} + +void AAbilityTriggerBase::BeginPlay() +{ + if (Role == ROLE_Authority) + { + if (!character) + { + GWERROR(L"No owner assigned to trigger " + GetName()); + this->SetActorEnableCollision(false); + Destroy(); + } + if (ticksPerSecond > 0) + { + m_tickTime = 1.0f / ticksPerSecond; + } + else + { + m_tickTime = 0.0f; + } + } + + //make sure the collision delegates are working + UShapeComponent* root = Cast(RootComponent); + if (root != nullptr && !delegatesSet) + { + root->OnComponentBeginOverlap.AddDynamic(this, &AAbilityTriggerBase::OnOverlapBegin); + root->OnComponentEndOverlap.AddDynamic(this, &AAbilityTriggerBase::OnOverlapEnd); + delegatesSet = true; + } + else + FWARNING("rootComponent is not a UShapeComponent"); + Super::BeginPlay(); + if (Role == ROLE_Authority) + UpdateOverlaps(); +} + + +void AAbilityTriggerBase::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + if (Role == ROLE_Authority) + { + while (m_actorsInside.Num()>0) + { + if (IsValid(m_actorsInside.GetData()[0])) + { + LeaveEvent(Cast(m_actorsInside.GetData()[0])); + } + } + } + Super::EndPlay(EndPlayReason); +} + +void AAbilityTriggerBase::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + DOREPLIFETIME_CONDITION(AAbilityTriggerBase, duration, COND_InitialOnly); +} + +void AAbilityTriggerBase::Tick(float DeltaTime) +{ + m_lifeTime += DeltaTime; + + Super::Tick(DeltaTime); + + if(Role != ROLE_Authority || !IsValid(this)) + return; + + UpdateOverlaps(); + if(autoDestroy && m_lifeTime >= duration) + { + Destroy(); + return; + } + + if(m_tickTime > 0) + { + m_tickTimer += DeltaTime; + while(m_tickTimer > m_tickTime) + { + m_tickTimer -= m_tickTime; + for(AActor* actorInside : m_actorsInside) + { + if (CollisionCheck(Cast(actorInside))) + { + OnTriggerTick(Cast(actorInside)); + NativeOnLocalTriggerTick(Cast(actorInside)); + } + } + } + } +} + +void AAbilityTriggerBase::OnOverlapBegin(class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult) +{ + if(Role != ROLE_Authority) + return; + + if(OtherActor == NULL) + { + FWPRINT(L"Error - wrong otherunit on OnOverlapBegin "); + return; + } + if(character == NULL) + { + FWPRINT(L"Error - m_character NULL on OnOverlapBegin "); + return; + } + + ANetworkCharacter* otherUnit = Cast(OtherActor); + if(!otherUnit->GetHitable()) + return; + if(!ULib::CheckAbilityFilter(filter, character, otherUnit)) + return; + if(hitOnce && m_CheckActorsHit(otherUnit)) + return; + + m_actorsInside.Add(OtherActor); + if (CollisionCheck(Cast(OtherActor))) + { + HitEvent(otherUnit); + } +} + + +void AAbilityTriggerBase::OnOverlapEnd(class AActor * OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex) +{ + if(Role == ROLE_Authority) + LeaveEvent(Cast(OtherActor)); +} + +void AAbilityTriggerBase::HitEvent(class ANetworkCharacter* otherUnit) +{ + if (hitOnce) + m_actorsHit.Add(otherUnit); + OnTriggerHit(otherUnit); +} +void AAbilityTriggerBase::LeaveEvent(class ANetworkCharacter* otherActor) +{ + if (Role == ROLE_Authority) + { + for (auto it = (m_actorsInside.CreateIterator()); it; it++) + { + if (*it == otherActor) + { + m_actorsInside.Remove(otherActor); + break; + } + } + + if (!ULib::CheckAbilityFilter(filter, character, otherActor)) + return; + OnTriggerLeave(otherActor); + } +} + +float AAbilityTriggerBase::GetLifeTime() const +{ + return m_lifeTime; +} + +float AAbilityTriggerBase::GetLifeTimeRate() const +{ + return FMath::Clamp(m_lifeTime / duration, 0.0f, 1.0f); +} + +bool AAbilityTriggerBase::m_CheckActorsHit(class ANetworkCharacter* otherUnit) +{ + for (ANetworkCharacter* character : m_actorsHit) + { + if (character == otherUnit) + return true; + } + return false; +} + +bool AAbilityTriggerBase::CollisionCheck(ANetworkCharacter* otheractor) +{ + return IsValid(otheractor); +} +void AAbilityTriggerBase::OnTriggerHit_Implementation(ANetworkCharacter* actor) +{ + +} +void AAbilityTriggerBase::OnTriggerTick_Implementation(ANetworkCharacter* actor) +{ + +} +void AAbilityTriggerBase::OnTriggerLeave_Implementation(ANetworkCharacter* actor) +{ + +} + +void AAbilityTriggerBase::OnLocalTriggerTick_Implementation(ANetworkCharacter* actor) +{ + +} + +void AAbilityTriggerBase::NativeOnLocalTriggerTick_Implementation(ANetworkCharacter* actor) +{ + OnLocalTriggerTick(actor); +} \ No newline at end of file diff --git a/Source/UnrealProject/Abilities/AbilityTriggerBase.h b/Source/UnrealProject/Abilities/AbilityTriggerBase.h new file mode 100644 index 0000000..91f727e --- /dev/null +++ b/Source/UnrealProject/Abilities/AbilityTriggerBase.h @@ -0,0 +1,71 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "DealDamageProxy.h" +#include "AbilityFilter.h" +#include "AbilityTriggerBase.generated.h" + + +UCLASS() +class UNREALPROJECT_API AAbilityTriggerBase : public ADealDamageProxy +{ + GENERATED_BODY() + +public: + AAbilityTriggerBase(); + + virtual void BeginPlay() override; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + virtual void Tick( float DeltaSeconds ) override; + virtual bool CollisionCheck(ANetworkCharacter* otheractor); + + UFUNCTION() + void OnOverlapBegin(class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult); + UFUNCTION() + void OnOverlapEnd(class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex); + + virtual void HitEvent(class ANetworkCharacter* otherActor); + virtual void LeaveEvent(class ANetworkCharacter* otherActor); + + UFUNCTION(BlueprintCallable, Category="Ability") + float GetLifeTime() const; + UFUNCTION(BlueprintCallable, Category="Ability") + float GetLifeTimeRate() const; + + UPROPERTY(BlueprintReadWrite, Replicated, Category = "Ability", meta = (ExposeOnSpawn)) + float duration; + UPROPERTY(BlueprintReadWrite, Category = "Ability", meta = (ExposeOnSpawn)) + bool hitOnce; + + UPROPERTY(BlueprintReadWrite, meta = (ExposeOnSpawn)) + EAbilityFilter filter; + + UFUNCTION(BlueprintNativeEvent) + void OnTriggerHit(ANetworkCharacter* actor); + UFUNCTION(BlueprintNativeEvent) + void OnTriggerTick(ANetworkCharacter* actor); + UFUNCTION(BlueprintCallable, NetMulticast, Reliable, Category = "trigger") + void NativeOnLocalTriggerTick(ANetworkCharacter* actor); + + UFUNCTION(BlueprintNativeEvent) + void OnLocalTriggerTick(ANetworkCharacter* actor); + UFUNCTION(BlueprintNativeEvent) + void OnTriggerLeave(ANetworkCharacter* actor); + + UPROPERTY(BlueprintReadWrite, VisibleAnywhere, Category = "Ability", meta = (ExposeOnSpawn)) + bool autoDestroy; + UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category = "Ability", meta = (ExposeOnSpawn)) + float ticksPerSecond; + bool m_CheckActorsHit(class ANetworkCharacter* otherUnit); + + + +protected: + TArray m_actorsInside; + TArray m_actorsHit; + float m_tickTime; + float m_lifeTime; + float m_tickTimer; + bool delegatesSet = false; +}; diff --git a/Source/UnrealProject/Abilities/AuraTrigger.cpp b/Source/UnrealProject/Abilities/AuraTrigger.cpp new file mode 100644 index 0000000..9f1ea32 --- /dev/null +++ b/Source/UnrealProject/Abilities/AuraTrigger.cpp @@ -0,0 +1,31 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "Modifier.h" +#include "NetworkCharacter.h" +#include "AuraTrigger.h" + + + + + +AModifier* AAuraTrigger::GetModifier(class ANetworkCharacter* targetCharacter) +{ + auto it = data.Find(targetCharacter); + if (it==nullptr) + return nullptr; + return *it; +} +void AAuraTrigger::SetModifier(class ANetworkCharacter* targetCharacter, AModifier* modifier) +{ + data.Add(targetCharacter, modifier); +} + +void AAuraTrigger::Tick(float DeltaSeconds) +{ + Super::Tick(DeltaSeconds); + if (Role == ROLE_Authority && IsValid(followObject)) + { + SetActorLocation(followObject->GetActorLocation()); + } +} \ No newline at end of file diff --git a/Source/UnrealProject/Abilities/AuraTrigger.h b/Source/UnrealProject/Abilities/AuraTrigger.h new file mode 100644 index 0000000..cae920e --- /dev/null +++ b/Source/UnrealProject/Abilities/AuraTrigger.h @@ -0,0 +1,26 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "Abilities/ConeTrigger.h" +#include "AuraTrigger.generated.h" + +/** + * + */ +UCLASS() +class UNREALPROJECT_API AAuraTrigger : public AConeTrigger +{ + GENERATED_BODY() + +public: + virtual void Tick(float DeltaSeconds) override; + UFUNCTION(BlueprintCallable, category = "Trigger") + class AModifier* GetModifier(class ANetworkCharacter* targetCharacter); + UFUNCTION(BlueprintCallable, category = "Trigger") + void SetModifier(class ANetworkCharacter* targetCharacter, AModifier* modifier); + UPROPERTY() + TMap data; + UPROPERTY(BlueprintReadWrite, Category = "Ability", meta = (ExposeOnSpawn)) + AActor* followObject; +}; diff --git a/Source/UnrealProject/Abilities/BlueprintAbilityLibrary.cpp b/Source/UnrealProject/Abilities/BlueprintAbilityLibrary.cpp new file mode 100644 index 0000000..91f4603 --- /dev/null +++ b/Source/UnrealProject/Abilities/BlueprintAbilityLibrary.cpp @@ -0,0 +1,326 @@ +#include "UnrealProject.h" +#include "BlueprintAbilityLibrary.h" + +#include "DealDamageProxy.h" +#include "AbilityEventGroup.h" +#include "AbilityTriggerBase.h" +#include "Modifier.h" +#include "NetworkCharacter.h" +#include "ProjectileBase.h" +#include "Class.h" + +ULib::ULib(const FObjectInitializer& init) + : Super(init) +{ +} +AActor* ULib::BeginSpawning2(UObject* WorldContextObject, TSubclassOf ActorClass) +{ + return UGameplayStatics::BeginDeferredActorSpawnFromClass(WorldContextObject, ActorClass, FTransform()); +} +AActor* ULib::FinishSpawning2(class AActor* Actor) +{ + if(!Actor) + return nullptr; + return UGameplayStatics::FinishSpawningActor(Actor, FTransform()); +} + +AActor* ULib::BeginSpawningGroup(UObject* WorldContextObject, TSubclassOf ActorClass) +{ + if(!WorldContextObject->GetWorld()->GetAuthGameMode()) + return nullptr; + AActor* actor = UGameplayStatics::BeginDeferredActorSpawnFromClass(WorldContextObject, ActorClass, FTransform()); + if(!actor) + return nullptr; + AAbilityEventGroup* newGroup = Cast(actor); + check(newGroup); + + AAbilityEventGroup* parent = Cast(WorldContextObject); + //GWPRINT(L"Parent = " + parent); + if(!parent) + { + GWERROR(L"Cannot call FinishSpawningGroup from any other source than AAbilityEventGroup"); + newGroup->Destroy(); + return nullptr; + } + parent->TransitionTo(newGroup); + return newGroup; +} +AActor* ULib::FinishSpawningGroup(class AActor* Actor) +{ + if(!Actor) + return nullptr; + return FinishSpawning2(Actor); +} + +AActor* ULib::BeginSpawningModifier(UObject* WorldContextObject, TSubclassOf ActorClass) +{ + if(!WorldContextObject->GetWorld()->GetAuthGameMode()) + return nullptr; + AActor* actor = UGameplayStatics::BeginDeferredActorSpawnFromClass(WorldContextObject, ActorClass, FTransform()); + if(!actor) + return nullptr; + AModifier* newModifier = Cast(actor); + check(newModifier); + + ADealDamageProxy* damageProxy = Cast(WorldContextObject); + if(damageProxy) + { + newModifier->character = damageProxy->character; + newModifier->abilityInfo = damageProxy->abilityInfo; + } + else + { + GWERROR(L"Cannot spawn modifier from a source that is not a ADealDamageProxy"); + newModifier->Destroy(); + return nullptr; + } + return newModifier; +} +AActor* ULib::FinishSpawningModifier(class AActor* Actor, class ANetworkCharacter* target) +{ + if(!IsValid(Actor)) + return nullptr; + AModifier* mod = Cast(Actor); + if(!IsValid(target)) + { + FString name = ""; + if(mod->abilityInfo) + name = mod->abilityInfo->GetName(); + GWERROR(L"Modifier needs a target (spawned by " + name + L")"); + Actor->Destroy(); + return nullptr; + } + //GWPRINT(L"Modifier Target = " + target); + ModifierManager* mm = target->GetModifierManager(); + if(!mm) + { + GWWARNING(L"Can't spawn modifier on a target that doesn't have a modifier manager. Might be in the process of destroying"); + Actor->Destroy(); + return nullptr; + } + mod->target = target; + check(mod); + mm->AddModifier(mod); + + return FinishSpawning2(Actor); +} + +AActor* ULib::BeginSpawningTrigger(UObject* WorldContextObject, TSubclassOf ActorClass, const FTransform& SpawnTransform) +{ + if(!WorldContextObject->GetWorld()->GetAuthGameMode()) + return nullptr; + AActor* actor = UGameplayStatics::BeginDeferredActorSpawnFromClass(WorldContextObject, ActorClass, SpawnTransform); + if(!actor) + return nullptr; + AAbilityTriggerBase* newTrigger = Cast(actor); + check(newTrigger); + ADealDamageProxy* damageProxy = Cast(WorldContextObject); + if(damageProxy) + { + newTrigger->character = damageProxy->character; + newTrigger->abilityInfo = damageProxy->abilityInfo; + } + else + { + GWERROR(L"Cannot spawn trigger from a source that is not a ADealDamageProxy"); + actor->Destroy(); + return nullptr; + } + return newTrigger; +} +AActor* ULib::FinishSpawningTrigger(class AActor* Actor, const FTransform & SpawnTransform) +{ + if(!Actor) + return nullptr; + return UGameplayStatics::FinishSpawningActor(Actor, SpawnTransform); +} + +AActor* ULib::BeginSpawningProjectile(UObject* WorldContextObject, TSubclassOf ActorClass, const FTransform& SpawnTransform) +{ + if(!WorldContextObject->GetWorld()->GetAuthGameMode()) + return nullptr; + AActor* actor = UGameplayStatics::BeginDeferredActorSpawnFromClass(WorldContextObject, ActorClass, SpawnTransform); + if(!actor) + return nullptr; + AProjectileBase* newProjectile = Cast(actor); + check(newProjectile); + ADealDamageProxy* damageProxy = Cast(WorldContextObject); + if(damageProxy) + { + newProjectile->character = damageProxy->character; + newProjectile->abilityInfo = damageProxy->abilityInfo; + } + else + { + GWERROR(L"Cannot spawn projectile from a source that is not a ADealDamageProxy"); + actor->Destroy(); + return nullptr; + } + return newProjectile; +} +AActor* ULib::FinishSpawningProjectile(class AActor* Actor, const FTransform& SpawnTransform) +{ + return UGameplayStatics::FinishSpawningActor(Actor, SpawnTransform); +} + +bool ULib::IsContainedInCone(FVector origin, FVector forward, float coneAngle, AActor* other) +{ + if(other->IsPendingKillPending()) + return false; + + //always hits at 360 degrees + if(coneAngle >= 360.0f) + return true; + + FVector dist = other->GetActorLocation() - origin; + dist.Normalize(); + forward.Normalize(); + float dot = FVector::DotProduct(dist, forward); + float angle = acosf(dot); + + float tempMaxAngle = coneAngle / 360; + tempMaxAngle *= PI; + + bool ret = tempMaxAngle > angle; + return ret; +} +int32 ULib::CharacterConeOverlap(UObject* WorldContextObject, FVector origin, float coneRadius, + FCharacterConeOverlapCallback callback, FVector forward, float coneAngle, EAbilityFilter filter) +{ + UWorld* world = GEngine->GetWorldFromContextObject(WorldContextObject); + check(world); + + if(world->GetAuthGameMode() == nullptr) + return 0; + m_overlapResultArray.Empty(); + // Get the character + ANetworkCharacter* character = Cast(WorldContextObject); + if(!character) + { + ADealDamageProxy* proxy = Cast(WorldContextObject); + if(!proxy) + { + GWERROR(L"CharacterConeOverlap failed, no caller character or proxy"); + return 0; + } + character = proxy->character; + } + + FCollisionShape shape; + shape.SetCapsule(coneRadius, 400.0f); + + TArray overlaps; + if(!world->OverlapMultiByObjectType( + overlaps, origin, FQuat::Identity, + FCollisionObjectQueryParams::AllDynamicObjects, shape, + FCollisionQueryParams::DefaultQueryParam)) + { + return 0; + } + TArray test; + if(callback.IsBound()) + { + for(int32 i = 0; i < overlaps.Num(); i++) + { + FOverlapResult& overlap = overlaps[i]; + ANetworkCharacter* target = Cast(overlap.GetActor()); + if (target && (overlap.Component->GetCollisionProfileName() == FName("Units") || overlap.Component->GetCollisionProfileName() == FName("Players")) && + IsContainedInCone(origin, forward, coneAngle, target) && + ULib::CheckAbilityFilter(filter, character, target)) + { + + callback.Execute(target); + m_overlapResultArray.Add(target); + } + } + } + // = test; + return overlaps.Num(); +} + +int32 ULib::ClosestCharacterConeOverlap(UObject* WorldContextObject, FVector origin, float coneRadius, + FClosestCharacterConeOverlapCallback callback, FVector forward, float coneAngle, EAbilityFilter filter) +{ + UWorld* world = GEngine->GetWorldFromContextObject(WorldContextObject); + check(world); + + if (world->GetAuthGameMode() == nullptr) + return 0; + + // Get the character + ANetworkCharacter* character = Cast(WorldContextObject); + if (!character) + { + ADealDamageProxy* proxy = Cast(WorldContextObject); + if (!proxy) + { + GWERROR(L"CharacterConeOverlap failed, no caller character or proxy"); + return 0; + } + character = proxy->character; + } + + FCollisionShape shape; + shape.SetCapsule(coneRadius, 400.0f); + + TArray overlaps; + if (!world->OverlapMultiByObjectType( + overlaps, origin, FQuat::Identity, + FCollisionObjectQueryParams::AllDynamicObjects, shape, + FCollisionQueryParams::DefaultQueryParam)) + { + return 0; + } + ANetworkCharacter* closest = nullptr; + float dist = 9999999999999.0f; + if (callback.IsBound()) + { + for (int32 i = 0; i < overlaps.Num(); i++) + { + FOverlapResult& overlap = overlaps[i]; + ANetworkCharacter* target = Cast(overlap.GetActor()); + if (target && IsContainedInCone(origin, forward, coneAngle, target) && ULib::CheckAbilityFilter(filter, character, target)) + { + if ((target->GetActorLocation() - origin).Size() < dist) + { + closest = target; + dist = (target->GetActorLocation() - origin).Size(); + } + } + } + } + if(closest) + callback.Execute(closest); + return overlaps.Num(); + +} +TArray ULib::m_overlapResultArray; +bool ULib::CheckAbilityFilter(EAbilityFilter filter, class ANetworkCharacter* self, class ANetworkCharacter* other) +{ + if(!self) + return false; + if(!other) + return false; + if(((uint8)filter & ABILITY_FILTER_ENEMY) != 0) + { + if(self->GetTeam() != other->GetTeam()) + return true; + } + if(((uint8)filter & ABILITY_FILTER_ALLY) != 0) + { + if(self->GetTeam() == other->GetTeam()) + return true; + } + return false; +} + +TArrayULib::GetCharacterOverlaps() +{ + TArray returnArray; + for (ANetworkCharacter* character : m_overlapResultArray) + { + if(IsValid(character)) + returnArray.Add(character); + } + return returnArray; +} \ No newline at end of file diff --git a/Source/UnrealProject/Abilities/BlueprintAbilityLibrary.h b/Source/UnrealProject/Abilities/BlueprintAbilityLibrary.h new file mode 100644 index 0000000..a7e53b3 --- /dev/null +++ b/Source/UnrealProject/Abilities/BlueprintAbilityLibrary.h @@ -0,0 +1,59 @@ +#pragma once +#include "Kismet/BlueprintFunctionLibrary.h" +#include "AbilityFilter.h" +#include "BlueprintAbilityLibrary.Generated.h" + +UCLASS() +class ULib : public UBlueprintFunctionLibrary +{ + GENERATED_UCLASS_BODY() + +public: + UFUNCTION(BlueprintCallable, Category = "Spawning", meta = (WorldContext = "WorldContextObject", UnsafeDuringActorConstruction = "true", BlueprintInternalUseOnly = "true")) + static AActor* BeginSpawning2(UObject* WorldContextObject, TSubclassOf ActorClass); + UFUNCTION(BlueprintCallable, Category = "Spawning", meta = (UnsafeDuringActorConstruction = "true", BlueprintInternalUseOnly = "true")) + static AActor* FinishSpawning2(class AActor* Actor); + + UFUNCTION(BlueprintCallable, Category = "Spawning", meta = (WorldContext = "WorldContextObject", UnsafeDuringActorConstruction = "true", BlueprintInternalUseOnly = "true")) + static AActor* BeginSpawningGroup(UObject* WorldContextObject, TSubclassOf ActorClass); + UFUNCTION(BlueprintCallable, Category = "Spawning", meta = (UnsafeDuringActorConstruction = "true", BlueprintInternalUseOnly = "true")) + static AActor* FinishSpawningGroup(class AActor* Actor); + + UFUNCTION(BlueprintCallable, Category = "Spawning", meta = (WorldContext = "WorldContextObject", UnsafeDuringActorConstruction = "true", BlueprintInternalUseOnly = "true")) + static AActor* BeginSpawningModifier(UObject* WorldContextObject, TSubclassOf ActorClass); + UFUNCTION(BlueprintCallable, Category = "Spawning", meta = (UnsafeDuringActorConstruction = "true", BlueprintInternalUseOnly = "true")) + static AActor* FinishSpawningModifier(class AActor* Actor, class ANetworkCharacter* Target); + + UFUNCTION(BlueprintCallable, Category = "Spawning", meta = (WorldContext = "WorldContextObject", UnsafeDuringActorConstruction = "true", BlueprintInternalUseOnly = "true")) + static AActor* BeginSpawningTrigger(UObject* WorldContextObject, TSubclassOf ActorClass, const FTransform& SpawnTransform); + UFUNCTION(BlueprintCallable, Category = "Spawning", meta = (UnsafeDuringActorConstruction = "true", BlueprintInternalUseOnly = "true")) + static AActor* FinishSpawningTrigger(class AActor* Actor, const FTransform& SpawnTransform); + + UFUNCTION(BlueprintCallable, Category = "Spawning", meta = (WorldContext = "WorldContextObject", UnsafeDuringActorConstruction = "true", BlueprintInternalUseOnly = "true")) + static AActor* BeginSpawningProjectile(UObject* WorldContextObject, TSubclassOf ActorClass, const FTransform& SpawnTransform); + UFUNCTION(BlueprintCallable, Category = "Spawning", meta = (UnsafeDuringActorConstruction = "true", BlueprintInternalUseOnly = "true")) + static AActor* FinishSpawningProjectile(class AActor* Actor, const FTransform& SpawnTransform); + + UFUNCTION(BlueprintCallable, Category = "Trigger") + static bool IsContainedInCone(FVector origin, FVector forward, float coneAngle, AActor* other); + + DECLARE_DYNAMIC_DELEGATE_OneParam(FCharacterConeOverlapCallback, class ANetworkCharacter*, character); + UFUNCTION(BlueprintCallable, Category = "Trigger", meta = (WorldContext = "WorldContextObject")) + static int32 CharacterConeOverlap(UObject* WorldContextObject, FVector origin, float coneRadius, + FCharacterConeOverlapCallback callback, FVector forward = FVector(1, 0, 0), float coneAngle = 360.0f, EAbilityFilter filter = EAbilityFilter::EnemyAll); + UFUNCTION(BlueprintCallable, Category = "Trigger", meta = (WorldContext = "WorldContextObject")) + static TArray GetCharacterOverlaps(); + + DECLARE_DYNAMIC_DELEGATE_OneParam(FClosestCharacterConeOverlapCallback, class ANetworkCharacter*, character); + UFUNCTION(BlueprintCallable, Category = "Trigger", meta = (WorldContext = "WorldContextObject")) + static int32 ClosestCharacterConeOverlap(UObject* WorldContextObject, FVector origin, float coneRadius, + FClosestCharacterConeOverlapCallback callback, FVector forward = FVector(1, 0, 0), float coneAngle = 360.0f, EAbilityFilter filter = EAbilityFilter::EnemyAll); + + UFUNCTION(BlueprintCallable, Category = "Damage Proxy") + static bool CheckAbilityFilter(EAbilityFilter filter, class ANetworkCharacter* self, class ANetworkCharacter* other); + + static TArray m_overlapResultArray; + +private: + +}; \ No newline at end of file diff --git a/Source/UnrealProject/Abilities/BombProjectile.cpp b/Source/UnrealProject/Abilities/BombProjectile.cpp new file mode 100644 index 0000000..3dc7f08 --- /dev/null +++ b/Source/UnrealProject/Abilities/BombProjectile.cpp @@ -0,0 +1,58 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "BombProjectile.h" +#include "NetworkCharacter.h" + +#include "Effect.h" +#include "EffectFunctionLibrary.h" +#include "BlueprintAbilityLibrary.h" + + +ABombProjectile::ABombProjectile() +{ + PrimaryActorTick.bCanEverTick = true; + + m_progress = 0.0f; + maxDistance = 100000.0f; +} + +void ABombProjectile::BeginPlay() +{ + Super::BeginPlay(); + travelTime = travelTime < 0.01f ? 0.01f : travelTime; + + float distance = FVector::Dist(source, target); +} + +void ABombProjectile::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME_CONDITION(ABombProjectile, source, COND_InitialOnly); + DOREPLIFETIME_CONDITION(ABombProjectile, target, COND_InitialOnly); + DOREPLIFETIME_CONDITION(ABombProjectile, travelTime, COND_InitialOnly); +} + +void ABombProjectile::NativeFixedProjectileTick(float DeltaTime) +{ + if(travelTime <= 0 || m_finished) + return; + + m_progress += DeltaTime; + if(m_progress >= travelTime) + m_progress = travelTime; + + const float lerp = m_progress / travelTime; + const float sin = FMath::Sin(lerp * PI); + FVector newLocation = FMath::Lerp(source, target, lerp) + FVector(0, 0, sin * travelHeight); + SetActorLocation(newLocation, true); + + float distToEnd = (target - newLocation).Size(); + + if(lerp >= 1.0f) + { + // Server only + Finish(); + } +} \ No newline at end of file diff --git a/Source/UnrealProject/Abilities/BombProjectile.h b/Source/UnrealProject/Abilities/BombProjectile.h new file mode 100644 index 0000000..b4aefe7 --- /dev/null +++ b/Source/UnrealProject/Abilities/BombProjectile.h @@ -0,0 +1,30 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "ProjectileBase.h" +#include "BombProjectile.generated.h" + +UCLASS() +class UNREALPROJECT_API ABombProjectile : public AProjectileBase +{ + GENERATED_BODY() + +public: + ABombProjectile(); + + virtual void BeginPlay() override; + + UPROPERTY(BlueprintReadWrite, Replicated, Category = "BombProjectile", meta = (ExposeOnSpawn)) + FVector source; + UPROPERTY(BlueprintReadWrite, Replicated, Category = "BombProjectile", meta = (ExposeOnSpawn)) + FVector target; + UPROPERTY(BlueprintReadWrite, Replicated, Category = "BombProjectile", meta = (ExposeOnSpawn)) + float travelTime; + UPROPERTY(BlueprintReadWrite, Replicated, Category = "BombProjectile", meta = (ExposeOnSpawn)) + float travelHeight; + virtual void NativeFixedProjectileTick(float DeltaTime) override; + +private: + float m_progress; +}; diff --git a/Source/UnrealProject/Abilities/BossBarageBunny.cpp b/Source/UnrealProject/Abilities/BossBarageBunny.cpp new file mode 100644 index 0000000..b739a1d --- /dev/null +++ b/Source/UnrealProject/Abilities/BossBarageBunny.cpp @@ -0,0 +1,97 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" + +#include "NPCBase.h" +#include "NetworkPlayer.h" +#include "BossBarageBunny.h" + + +ABossBarageBunny::ABossBarageBunny() +{ + PrimaryActorTick.bCanEverTick = true; + + bReplicates = true; + bReplicateMovement = true; + + boss = nullptr; + + BeamEmitter = CreateDefaultSubobject(TEXT("Visual")); + BeamEmitter->AttachTo(RootComponent); + + m_lifeTime = 1; +} + +void ABossBarageBunny::BeginPlay() +{ + Super::BeginPlay(); + + SpawnDefaultController(); +} + +void ABossBarageBunny::Tick( float DeltaTime ) +{ + Super::Tick( DeltaTime ); + + // Update the line material + if (IsValid(boss)) + { + BeamEmitter->SetBeamSourcePoint(0, GetActorLocation(), 0); + BeamEmitter->SetBeamTargetPoint(0, boss->GetActorLocation(), 0); + } + else + { + BeamEmitter->SetActive(false); + GetCharacterMovement()->MaxWalkSpeed = 0; + } + + // Server only + if (Role != ROLE_Authority) + return; + + m_lifeTime -= DeltaTime; + if (m_lifeTime <= 0) + { + Destroy(); + return; + } + + // Boss destroyed? + if (!IsValid(boss)) + return; + + UNavigationSystem* const nav = UNavigationSystem::GetCurrent(GetWorld()); + if (!IsValid(nav)) return; + + AController* const controller = GetController(); + if (!IsValid(controller)) + { + JERROR("BossBarageBase has no controller"); + return; + } + + ANPCBase* const asNPC = Cast(boss); + if (!IsValid(asNPC) || !IsValid(asNPC->target)) + return; + + nav->SimpleMoveToLocation(controller, asNPC->target->GetActorLocation()); +} + + +void ABossBarageBunny::Setup(AActor* boss, float lifeTime) +{ + this->boss = boss; + m_lifeTime = lifeTime; +} + +void ABossBarageBunny::ClearBoss() +{ + this->boss = nullptr; +} + +void ABossBarageBunny::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(ABossBarageBunny, boss); +} diff --git a/Source/UnrealProject/Abilities/BossBarageBunny.h b/Source/UnrealProject/Abilities/BossBarageBunny.h new file mode 100644 index 0000000..c1648b9 --- /dev/null +++ b/Source/UnrealProject/Abilities/BossBarageBunny.h @@ -0,0 +1,36 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "GameFramework/Actor.h" +#include "BossBarageBunny.generated.h" + +UCLASS() +class UNREALPROJECT_API ABossBarageBunny : public ACharacter +{ + GENERATED_BODY() + +public: + ABossBarageBunny(); + + virtual void BeginPlay() override; + + virtual void Tick( float DeltaSeconds ) override; + + + UPROPERTY(EditAnywhere, Category = "Bunny") + class UParticleSystemComponent* BeamEmitter; + + UFUNCTION(BlueprintCallable, Category = "Bunny") + void Setup(class AActor* boss, float lifeTime); + + UFUNCTION(BlueprintCallable, Category = "Bunny") + void ClearBoss(); + + + UPROPERTY(Replicated) + class AActor* boss; + +private: + float m_lifeTime; +}; diff --git a/Source/UnrealProject/Abilities/BossTargetBunny.cpp b/Source/UnrealProject/Abilities/BossTargetBunny.cpp new file mode 100644 index 0000000..bfcc245 --- /dev/null +++ b/Source/UnrealProject/Abilities/BossTargetBunny.cpp @@ -0,0 +1,43 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "BossTargetBunny.h" + + +ABossTargetBunny::ABossTargetBunny() +{ + PrimaryActorTick.bCanEverTick = true; + + bReplicates = true; + bReplicateMovement = true; + + m_lifeTime = 0; +} + +void ABossTargetBunny::BeginPlay() +{ + Super::BeginPlay(); + +} + +void ABossTargetBunny::Tick( float DeltaTime ) +{ + Super::Tick( DeltaTime ); + + // Server only + if (Role != ROLE_Authority) + return; + + m_lifeTime -= DeltaTime; + if (m_lifeTime <= 0) + { + Destroy(); + return; + } +} + + +void ABossTargetBunny::Setup(float lifeTime) +{ + m_lifeTime = lifeTime; +} \ No newline at end of file diff --git a/Source/UnrealProject/Abilities/BossTargetBunny.h b/Source/UnrealProject/Abilities/BossTargetBunny.h new file mode 100644 index 0000000..a289f1c --- /dev/null +++ b/Source/UnrealProject/Abilities/BossTargetBunny.h @@ -0,0 +1,26 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "GameFramework/Actor.h" +#include "BossTargetBunny.generated.h" + +UCLASS() +class UNREALPROJECT_API ABossTargetBunny : public AActor +{ + GENERATED_BODY() + +public: + ABossTargetBunny(); + + virtual void BeginPlay() override; + + virtual void Tick( float DeltaSeconds ) override; + + + UFUNCTION(BlueprintCallable, Category = "Bunny") + void Setup(float lifeTime); + +private: + float m_lifeTime; +}; diff --git a/Source/UnrealProject/Abilities/ConeComponent.cpp b/Source/UnrealProject/Abilities/ConeComponent.cpp new file mode 100644 index 0000000..b267915 --- /dev/null +++ b/Source/UnrealProject/Abilities/ConeComponent.cpp @@ -0,0 +1,110 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "ConeComponent.h" + +#define CONEARCVERTEXCOUNT 50 + +// Sets default values for this component's properties +UConeComponent::UConeComponent(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) +{ + ShapeColor = FColor(223, 149, 157, 255); + bUseEditorCompositing = true; +} + +void UConeComponent::BeginPlay() +{ + UpdateCapsule(); + Super::BeginPlay(); +} + +void UConeComponent::UpdateCapsule() +{ + SetCapsuleSize(coneRadius, 400); +} + +// Create sceneproxy to show the cone in the editor +FPrimitiveSceneProxy* UConeComponent::CreateSceneProxy() +{ + class FDrawConeSceneProxy : public FPrimitiveSceneProxy + { + public: + const UConeComponent* component; + FDrawConeSceneProxy(const UConeComponent* InComponent) + : FPrimitiveSceneProxy(InComponent) + , bDrawOnlyIfSelected(InComponent->bDrawOnlyIfSelected) + , component(InComponent) + { + bWillEverBeLit = false; + } + + + virtual void GetDynamicMeshElements(const TArray& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector) const + { + QUICK_SCOPE_CYCLE_COUNTER(STAT_GetDynamicMeshElements_DrawDynamicElements); + + for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++) + { + + if (VisibilityMap & (1 << ViewIndex)) + { + const FSceneView* View = Views[ViewIndex]; + const FLinearColor DrawCapsuleColor = GetViewSelectionColor(ShapeColor, *View, IsSelected(), IsHovered(), false, IsIndividuallySelected()); + + FPrimitiveDrawInterface* PDI = Collector.GetPDI(ViewIndex); + + FTransform transform = component->GetComponentTransform(); + FVector base = transform.GetLocation(); + + const float scale = 1.0f / 180.0f * PI; + float baseRot = (360.0f - transform.GetRotation().Euler().Z) * scale; + + FVector scaleVec = transform.GetScale3D(); + FVector forward = FVector(scaleVec.X, 0, 0); + FVector right = FVector(0, scaleVec.Y, 0); + float angle = (component->coneAngle) / 180.0f * PI; + + FVector linePoints[CONEARCVERTEXCOUNT]; + float anglestep = (angle) / (CONEARCVERTEXCOUNT-1); + + float rot = baseRot - angle * 0.5f; + for (int i = 0; i < CONEARCVERTEXCOUNT;i++) + { + float f = cosf(rot); + float r = sinf(rot); + linePoints[i] = base + (forward * f - right * r) * component->coneRadius; + rot += anglestep; + } + + for (int i = 0; i < CONEARCVERTEXCOUNT-1; i++) + { + + PDI->DrawLine(linePoints[i], linePoints[i + 1], ShapeColor, SDPG_World); + } + PDI->DrawLine(base, linePoints[0], ShapeColor, SDPG_World); + PDI->DrawLine(base, linePoints[CONEARCVERTEXCOUNT - 1], ShapeColor, SDPG_World); + } + } + } + + virtual FPrimitiveViewRelevance GetViewRelevance(const FSceneView* View) const override + { + const bool bVisible = !bDrawOnlyIfSelected || IsSelected(); + FPrimitiveViewRelevance Result; + Result.bDrawRelevance = IsShown(View) && bVisible; + Result.bDynamicRelevance = true; + Result.bShadowRelevance = IsShadowCast(View); + Result.bEditorPrimitiveRelevance = UseEditorCompositing(View); + return Result; + } + + virtual uint32 GetMemoryFootprint(void) const override { return(sizeof(*this) + GetAllocatedSize()); } + uint32 GetAllocatedSize(void) const { return(FPrimitiveSceneProxy::GetAllocatedSize()); } + + private: + const uint32 bDrawOnlyIfSelected : 1; + const FColor ShapeColor = FColor(255,0,0,255); + const FTransform transform; + }; + return new FDrawConeSceneProxy(this); +} diff --git a/Source/UnrealProject/Abilities/ConeComponent.h b/Source/UnrealProject/Abilities/ConeComponent.h new file mode 100644 index 0000000..ea0a977 --- /dev/null +++ b/Source/UnrealProject/Abilities/ConeComponent.h @@ -0,0 +1,26 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "Components/CapsuleComponent.h" +#include "ConeComponent.generated.h" + + +UCLASS(ClassGroup = "Collision", editinlinenew, hidecategories = (Object, LOD, Lighting, TextureStreaming), meta = (DisplayName = "Cone Collision", BlueprintSpawnableComponent)) +class UNREALPROJECT_API UConeComponent : public UCapsuleComponent +{ + GENERATED_UCLASS_BODY() + +public: + void BeginPlay() override; + + void UpdateCapsule(); + + UPROPERTY(EditAnywhere, export, Category = Shape, meta = (ClampMin = "0", UIMin = "0")) + float coneRadius = 500.0f; + + UPROPERTY(EditAnywhere, export, Category = Shape, meta = (ClampMin = "0", UIMin = "0")) + float coneAngle = 50; + + virtual FPrimitiveSceneProxy* CreateSceneProxy() override; +}; diff --git a/Source/UnrealProject/Abilities/ConeTrigger.cpp b/Source/UnrealProject/Abilities/ConeTrigger.cpp new file mode 100644 index 0000000..28ed5ee --- /dev/null +++ b/Source/UnrealProject/Abilities/ConeTrigger.cpp @@ -0,0 +1,50 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "ConeTrigger.h" +#include "ConeComponent.h" +#include "NetworkCharacter.h" +#include "BlueprintAbilityLibrary.h" // For Cone overlap code + +AConeTrigger::AConeTrigger() +{ + PrimaryActorTick.bCanEverTick = true; + + m_coneTrigger = CreateDefaultSubobject (TEXT("trigger")); + RootComponent = m_coneTrigger; + m_coneTrigger->SetCollisionProfileName(TEXT("Triggers")); +} + + +void AConeTrigger::BeginPlay() +{ + m_coneTrigger->SetWorldTransform(GetTransform()); + if (!delegatesSet) + { + m_coneTrigger->OnComponentBeginOverlap.AddDynamic(this, &AConeTrigger::OnOverlapBegin); + m_coneTrigger->OnComponentEndOverlap.AddDynamic(this, &AConeTrigger::OnOverlapEnd); + delegatesSet = true; + } + SetCone(radius, angle); + Super::BeginPlay(); +} + + +void AConeTrigger::Tick( float DeltaTime ) +{ + Super::Tick( DeltaTime ); +} + +void AConeTrigger::SetCone(float inputRadius, float inputAngle) +{ + m_coneTrigger->coneAngle = (inputAngle); + m_coneTrigger->coneRadius = (inputRadius); + m_coneTrigger->UpdateCapsule(); +} + + + +bool AConeTrigger::CollisionCheck(ANetworkCharacter* otheractor) +{ + return ULib::IsContainedInCone(GetActorLocation(), GetActorForwardVector(), m_coneTrigger->coneAngle, otheractor); +} \ No newline at end of file diff --git a/Source/UnrealProject/Abilities/ConeTrigger.h b/Source/UnrealProject/Abilities/ConeTrigger.h new file mode 100644 index 0000000..a629486 --- /dev/null +++ b/Source/UnrealProject/Abilities/ConeTrigger.h @@ -0,0 +1,27 @@ +// Project Lab - NHTV Igad + +#pragma once +#include "AbilityTriggerBase.h" +#include "GameFramework/Actor.h" +#include "ConeTrigger.generated.h" + +UCLASS() +class UNREALPROJECT_API AConeTrigger : public AAbilityTriggerBase +{ + GENERATED_BODY() + +public: + AConeTrigger(); + + virtual void BeginPlay() override; + virtual void Tick( float DeltaSeconds ) override; + virtual bool CollisionCheck(ANetworkCharacter* otheractor) override; + void SetCone(float radius, float angle); + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, meta = (ExposeOnSpawn), Category = "Trigger") + float radius; + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, meta = (ExposeOnSpawn), Category = "Trigger") + float angle; +private: + UPROPERTY(VisibleAnywhere, Category = "Trigger") + class UConeComponent* m_coneTrigger; +}; diff --git a/Source/UnrealProject/Abilities/DealDamageProxy.cpp b/Source/UnrealProject/Abilities/DealDamageProxy.cpp new file mode 100644 index 0000000..9592244 --- /dev/null +++ b/Source/UnrealProject/Abilities/DealDamageProxy.cpp @@ -0,0 +1,67 @@ +#include "UnrealProject.h" +#include "NetworkCharacter.h" +#include "DealDamageProxy.h" +#include "DefaultPlayerState.h" +#include "ScalingGraph.h" +#include "AbilityInfo.h" + +float ADealDamageProxy::GetAbilityPowerScale() const +{ + if(!character) + return 0.0f; + AAbilityState* state = character->GetAbilityState(abilityInfo); + if(!state) + return 0.0f; + return state->power; +} + +float ADealDamageProxy::ScaleGraphCurve(const UCurveFloat* val) +{ + if (!IsValid(val)) + { + GEngine->AddOnScreenDebugMessage((int32)abilityInfo->GetUniqueID(), 2.0f, FColor(255, 20, 20, 255), FString("Invalid curve argument in ") + GetName() + " [" + abilityInfo->GetName() + "]"); + JERROR("Invalid curve argument in " + GetName() + " [" + abilityInfo->GetName() + "]"); + return 0; + } + + if(abilityInfo->abilityType == EAbilityType::Basic) + { + return ScaleGraphCurveByLevel(val); + } + float f = GetAbilityPowerScale(); + f = FMath::Clamp(f, 0.0f, 1.0f); + return val->GetFloatValue(f); +} + +float ADealDamageProxy::ScaleGraphCurveByLevel(const UCurveFloat* val) +{ + if(!IsValid(val)) + { + GEngine->AddOnScreenDebugMessage((int32)abilityInfo->GetUniqueID(), 2.0f, FColor(255, 20, 20, 255), FString("Invalid curve argument in ") + GetName() + " [" + abilityInfo->GetName() + "]"); + JERROR("Invalid curve argument in " + GetName() + " [" + abilityInfo->GetName() + "]"); + return 0; + } + + float f = 0.0f; + if(character) + { + ADefaultPlayerState* ps = Cast(character->PlayerState); + if(ps) + { + f = FMath::Clamp((float)ps->GetLevel() / (float)ps->GetMaxLevel(), 0.0f, 1.0f); + } + } + return val->GetFloatValue(f); +} + +float ULevelScaleLibrary::ScaleGraphCurveFloat(float in, const UCurveFloat* val) +{ + if (!IsValid(val)) + { + JERROR("Invalid curve argument"); + return 0; + } + + in = FMath::Clamp(in, 0.0f, 1.0f); + return val->GetFloatValue(in); +} \ No newline at end of file diff --git a/Source/UnrealProject/Abilities/DealDamageProxy.h b/Source/UnrealProject/Abilities/DealDamageProxy.h new file mode 100644 index 0000000..3679fe4 --- /dev/null +++ b/Source/UnrealProject/Abilities/DealDamageProxy.h @@ -0,0 +1,28 @@ +#pragma once +#include "AbilityFilter.h" +#include "ScalingGraph.h" +#include "DealDamageProxy.Generated.h" + +UCLASS() +class ADealDamageProxy : public AActor +{ + GENERATED_BODY() + +public: + UFUNCTION(BlueprintCallable, Category = "Damage Scaling") + float GetAbilityPowerScale() const; + + // Scales samples the input curve anywhere from 0-1 based on the currently cast ability's power(or level for basic attacks) + UFUNCTION(BlueprintCallable, Category = "Scaling Graph") + float ScaleGraphCurve(const UCurveFloat* val); + // Same as ScaleGraphCurve but this one scales with the player level + UFUNCTION(BlueprintCallable, Category = "Scaling Graph") + float ScaleGraphCurveByLevel(const UCurveFloat* val); + + // The character that started this sequence of proxies and the dealer of the damage + UPROPERTY(BlueprintReadOnly, Category="Damage Proxy") + class ANetworkCharacter* character; + // The ability that was cast to spawn this object + UPROPERTY(BlueprintReadOnly, Category="Damage Proxy") + class UAbilityInfo* abilityInfo; +}; \ No newline at end of file diff --git a/Source/UnrealProject/Abilities/HomingProjectile.cpp b/Source/UnrealProject/Abilities/HomingProjectile.cpp new file mode 100644 index 0000000..d06805c --- /dev/null +++ b/Source/UnrealProject/Abilities/HomingProjectile.cpp @@ -0,0 +1,47 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "HomingProjectile.h" +#include "DefaultGameMode.h" +#include "NetworkPlayer.h" + +void AHomingProjectile::BeginPlay() +{ + Super::BeginPlay(); + + float maxdist = BIG_NUMBER; + if (Role == ROLE_Authority) + { + if (targetCreature == nullptr) + { + UWorld* const world = GetWorld(); + if (!world) return; + ADefaultGameMode* mode = Cast(world->GetAuthGameMode()); + if (!mode) return; + + TArray players = mode->GetPlayers(); + + for (ANetworkPlayer* player : players) + { + float dist = (player->GetActorLocation() - GetActorLocation()).SizeSquared(); + if (dist < maxdist) + { + maxdist = dist; + targetCreature = player; + } + } + } + } +} + +void AHomingProjectile::NativeMove(float DeltaTime) +{ + if (targetCreature == nullptr) + return; + FVector dir = targetCreature->GetActorLocation() - GetActorLocation(); + FRotator newrot = FRotationMatrix::MakeFromX(dir).Rotator(); + + SetActorRotation(newrot); + + Super::NativeMove(DeltaTime); +} diff --git a/Source/UnrealProject/Abilities/HomingProjectile.h b/Source/UnrealProject/Abilities/HomingProjectile.h new file mode 100644 index 0000000..d0726b9 --- /dev/null +++ b/Source/UnrealProject/Abilities/HomingProjectile.h @@ -0,0 +1,22 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "ProjectileBase.h" +#include "HomingProjectile.generated.h" + +/** + * + */ +UCLASS() +class UNREALPROJECT_API AHomingProjectile : public AProjectileBase +{ + GENERATED_BODY() + +public: + virtual void BeginPlay() override; + virtual void NativeMove(float DeltaTime) override; + + UPROPERTY(BlueprintReadWrite, category = "projectile", meta = (ExposeOnSpawn)) + class ANetworkCharacter* targetCreature; +}; diff --git a/Source/UnrealProject/Abilities/HoverProjectile.cpp b/Source/UnrealProject/Abilities/HoverProjectile.cpp new file mode 100644 index 0000000..a6b5f15 --- /dev/null +++ b/Source/UnrealProject/Abilities/HoverProjectile.cpp @@ -0,0 +1,22 @@ +// ProjeLab - NHTV Igad + +#include "UnrealProject.h" +#include "HoverProjectile.h" + + + + + +void AHoverProjectile::NativeMove(float DeltaTime) +{ + //moves with sweep to get collision + float distance = speed * DeltaTime; + FVector newpos = GetActorForwardVector() * distance + GetActorLocation(); + FHitResult outHit; + FVector offset; + if (GetWorld()->LineTraceSingleByChannel(outHit, newpos, newpos - FVector(0, 0, hoverHeight + 10), ECollisionChannel::ECC_GameTraceChannel7)) + offset = outHit.ImpactPoint + FVector(0, 0, hoverHeight) - GetActorLocation(); + else + offset = newpos - FVector(0, 0, 10) - GetActorLocation(); + AddActorWorldOffset(offset,true); +} \ No newline at end of file diff --git a/Source/UnrealProject/Abilities/HoverProjectile.h b/Source/UnrealProject/Abilities/HoverProjectile.h new file mode 100644 index 0000000..a6de365 --- /dev/null +++ b/Source/UnrealProject/Abilities/HoverProjectile.h @@ -0,0 +1,23 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "Abilities/ProjectileBase.h" +#include "HoverProjectile.generated.h" + +/** + * + */ +UCLASS() +class UNREALPROJECT_API AHoverProjectile : public AProjectileBase +{ + GENERATED_BODY() + + +public: + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Projectile", meta = (ExposeOnSpawn)) + float hoverHeight; + + virtual void NativeMove(float DeltaTime)override; + +}; diff --git a/Source/UnrealProject/Abilities/IngameSkillTree.cpp b/Source/UnrealProject/Abilities/IngameSkillTree.cpp new file mode 100644 index 0000000..6d65e47 --- /dev/null +++ b/Source/UnrealProject/Abilities/IngameSkillTree.cpp @@ -0,0 +1,103 @@ +#include "UnrealProject.h" +#include "IngameSkillTree.h" +#include "HexMap.h" +#include "BaseSkillObject.h" +#include "DefaultPlayerState.h" + +void AIngameSkillTree::BuildFromState(const FSkillTreeState& state) +{ + for(int32 i = 0; i < state.objects.Num(); i++) + { + const FSkillTreeStateObject& obj = state.objects[i]; + if(!obj.skillObject) + { + GWARNING("Invalid skill object found in skill tree state, please reset your build."); + continue; + } + + FIngameSkillTreeSkill igs; + igs.placedPoints = obj.placedPoints; + igs.skillObject = obj.skillObject->GetDefaultObject(); + igs.selectedEffectIndex = obj.selectedEffect; + + // Check valid effect index + if(igs.selectedEffectIndex < 0 || igs.selectedEffectIndex >= igs.skillObject->abilityEffects.Num()) + { + GWARNING("Ability effect out of range for ability " + obj.skillObject->GetName()); + continue; + } + + igs.selectedEffect = igs.skillObject->abilityEffects[igs.selectedEffectIndex]; + + igs.abilityType = -1; + switch (igs.skillObject->skillShapeType) + { + case ESkillShapeType::Active: + igs.abilityType = 0; + break; + case ESkillShapeType::Coop: + case ESkillShapeType::Sustain: + igs.abilityType = 1; + break; + case ESkillShapeType::Passive: + igs.abilityType = 2; + break; + } + + if(igs.abilityType == -1) + { + GWARNING("Ability type invalid for ability " + obj.skillObject->GetName()); + continue; + } + + m_skills.Add(igs); + } +} + +AIngameSkillTree::AIngameSkillTree(const FObjectInitializer& init) +{ +} + +AIngameSkillTree::~AIngameSkillTree() +{ +} + +void AIngameSkillTree::BeginPlay() +{ +} + +TArray AIngameSkillTree::GetSkillsForLevel(ADefaultPlayerState* player) +{ + float powerLevel = player->GetLevel() / (float)player->GetMaxLevel(); + return GetSkillsForLevel(powerLevel); +} + +TArray AIngameSkillTree::GetSkillsForLevel(float powerLevel) +{ + TArray res; + for (int32 i = 0; i < m_skills.Num(); i++) + { + UpdatePowerForSkill(m_skills[i], powerLevel); + if (m_skills[i].power > 0.0f) + res.Add(m_skills[i]); + } + return res; +} + +void AIngameSkillTree::UpdatePowerForSkill(FIngameSkillTreeSkill& skill, float level) +{ + float offset = (1.0f - level) * 16.0f; + + int32 hexcount = 0; + + for(int32 i = 0; i < skill.placedPoints.Num(); i++) + { + float YPos = (skill.placedPoints[i].X & 1) ? + (float(skill.placedPoints[i].Y) + 0.5f) : + (float(skill.placedPoints[i].Y)); + if(YPos >= offset) hexcount++; + } + + skill.level = hexcount; + skill.power = float(hexcount) / float(skill.placedPoints.Num()); +} diff --git a/Source/UnrealProject/Abilities/IngameSkillTree.h b/Source/UnrealProject/Abilities/IngameSkillTree.h new file mode 100644 index 0000000..4e70286 --- /dev/null +++ b/Source/UnrealProject/Abilities/IngameSkillTree.h @@ -0,0 +1,45 @@ +#pragma once +#include "SkillTreeState.h" +#include "IngameSkillTree.Generated.h" + +USTRUCT() +struct FIngameSkillTreeSkill +{ + GENERATED_BODY() +public: + UPROPERTY() + TArray placedPoints; + UPROPERTY(BlueprintReadOnly) + class UBaseSkillObject* skillObject; + UPROPERTY(BlueprintReadOnly) + class UAbilityInfo* selectedEffect; + UPROPERTY(BlueprintReadOnly) + int32 selectedEffectIndex; + UPROPERTY(BlueprintReadOnly) + float power; + UPROPERTY(BlueprintReadOnly) + int32 level; + + // Kind of ability on the button bar + UPROPERTY(BlueprintReadOnly) + int32 abilityType; +}; + + UCLASS() +class AIngameSkillTree : public AActor +{ + GENERATED_BODY() +public: + AIngameSkillTree(const FObjectInitializer& init); + ~AIngameSkillTree(); + virtual void BeginPlay() override; + + void BuildFromState(const FSkillTreeState& state); + TArray GetSkillsForLevel(class ADefaultPlayerState* player); + TArray GetSkillsForLevel(float level); + void UpdatePowerForSkill(FIngameSkillTreeSkill& skill, float level); + +private: + UPROPERTY() + TArray m_skills; +}; \ No newline at end of file diff --git a/Source/UnrealProject/Abilities/IntimidatingAuraTrigger.cpp b/Source/UnrealProject/Abilities/IntimidatingAuraTrigger.cpp new file mode 100644 index 0000000..d5e4821 --- /dev/null +++ b/Source/UnrealProject/Abilities/IntimidatingAuraTrigger.cpp @@ -0,0 +1,60 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "IntimidatingAuraTrigger.h" +#include "NetworkCharacter.h" +#include "NativeModifiers.h" +#include "Modifier.h" + + + +AIntimidatingAuraTrigger::AIntimidatingAuraTrigger() +{ + +} +void AIntimidatingAuraTrigger::BeginPlay() +{ + Super::BeginPlay(); +} +void AIntimidatingAuraTrigger::Tick(float deltaTime) +{ + Super::Tick(deltaTime); +} +void AIntimidatingAuraTrigger::HitEvent(ANetworkCharacter* otherActor) +{ + + ModifierManager* manager = otherActor->GetModifierManager(); + if (playerMap.Find(otherActor)== nullptr) + { + RERROR("2 modifiers in the intimidatin aura"); + } + if (manager) + { + AAttackSpeedModifierConstant* ASModifier = GetWorld()->SpawnActor(); + ASModifier->lifeTime = 0; + ASModifier->attackSpeedMultiplier = attackSpeedMultiplier; + ASModifier->target = otherActor; + manager->AddModifier(ASModifier); + + ASpeedModifier* SModifier = GetWorld()->SpawnActor(); + SModifier->lifeTime = 0; + SModifier->speedMultiplier = MovementSpeedMultiplier; + SModifier->target = otherActor; + manager->AddModifier(ASModifier); + // std::pair pair = std::pair ((AModifier*)ASModifier, (AModifier*)SModifier); + playerMap.Add(otherActor, FIntimidatingAuraPair(ASModifier,SModifier)); + + } + return Super::HitEvent(otherActor); +} +void AIntimidatingAuraTrigger::LeaveEvent(ANetworkCharacter* otherActor) +{ + + auto it =playerMap.Find(otherActor); + if (it == nullptr) + return Super::LeaveEvent(otherActor); + it->modifier0->ForceDestroy(); + it->modifier1->ForceDestroy(); + playerMap.Remove(otherActor); + return Super::LeaveEvent(otherActor); +} \ No newline at end of file diff --git a/Source/UnrealProject/Abilities/IntimidatingAuraTrigger.h b/Source/UnrealProject/Abilities/IntimidatingAuraTrigger.h new file mode 100644 index 0000000..aa6cb17 --- /dev/null +++ b/Source/UnrealProject/Abilities/IntimidatingAuraTrigger.h @@ -0,0 +1,42 @@ +// Project Lab - NHTV Igad + +#pragma once +#include "Abilities/ConeTrigger.h" +#include "IntimidatingAuraTrigger.generated.h" + +/** + * + */ +USTRUCT() +struct FIntimidatingAuraPair +{ + GENERATED_BODY() +public: + FIntimidatingAuraPair(){} + FIntimidatingAuraPair(class AModifier* m0, class AModifier*m1) { modifier0 = m0; modifier1 = m1; } + UPROPERTY() + class AModifier* modifier0; + UPROPERTY() + class AModifier* modifier1; +}; + +UCLASS() +class UNREALPROJECT_API AIntimidatingAuraTrigger : public AConeTrigger +{ + GENERATED_BODY() + public: + AIntimidatingAuraTrigger(); + + virtual void BeginPlay() override; + virtual void Tick(float DeltaSeconds) override; + virtual void HitEvent(class ANetworkCharacter* otherActor)override; + virtual void LeaveEvent(class ANetworkCharacter* otherActor)override; + UPROPERTY() + TMap playerMap; + UPROPERTY(meta = (ExposeOnSpawn), BlueprintReadWrite) + float attackSpeedMultiplier; + UPROPERTY(meta = (ExposeOnSpawn), BlueprintReadWrite) + float MovementSpeedMultiplier; + + +}; diff --git a/Source/UnrealProject/Abilities/Modifier.cpp b/Source/UnrealProject/Abilities/Modifier.cpp new file mode 100644 index 0000000..e1ab98e --- /dev/null +++ b/Source/UnrealProject/Abilities/Modifier.cpp @@ -0,0 +1,477 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "Modifier.h" + +#include "NetworkCharacter.h" +#include "NativeModifiers.h" + +ModifierManager::ModifierManager(class ANetworkCharacter* character) + : m_character(character) +{ + check(m_character); +} +ModifierManager::~ModifierManager() +{ + ClearModifiers(false); +} + +void ModifierManager::ClearModifiers(bool shouldCallEnd) +{ + for (AModifier* modifier : m_modifiers) + { + if (shouldCallEnd) + modifier->m_started = false; + modifier->Destroy(); + } + m_modifiers.SetNum(0); + RecalculateCharacter(); +} +void ModifierManager::Tick(float DeltaSeconds) +{ + for (int32 i = 0; i < m_modifiers.Num();) + { + AModifier* modifier = m_modifiers[i]; + if (modifier != nullptr && IsValid(modifier)) + { + if (modifier->ShouldBeRemoved() || modifier->m_forceDestroy) + { + modifier->Destroy(); + m_modifiers.RemoveAt(i); + RecalculateCharacter(); + continue; + } + else + { + modifier->Tick(DeltaSeconds); + } + } + else + { + m_modifiers.RemoveAt(i); + if (modifier != 0) + { + GERROR("Modifier \"" + modifier->GetName() + "\" was destroyed, but not in the usual way"); + } + RecalculateCharacter(); + continue; + } + i++; + } +} + +AModifier* ModifierManager::AddModifier(TSubclassOf buffClass, float duration) +{ + if (!buffClass) + { + FWERROR(L"Invalid modifier class"); + return nullptr; + } + UWorld* world = m_character->GetWorld(); + check(world); + AModifier* modifier = world->SpawnActor(buffClass); + modifier->m_Init(m_character, duration); + return AddModifier(modifier); +} +AModifier* ModifierManager::AddModifier(AModifier* modifierInstance) +{ + if (!modifierInstance) + { + FWERROR(L"Invalid buff added"); + return nullptr; + } + m_modifiers.Add(modifierInstance); + m_modifiers.Sort([](const AModifier& i, const AModifier& j)->bool { return i > j; }); + modifierInstance->m_Start(); + RecalculateCharacter(); + return modifierInstance; +} + +TArray ModifierManager::GetModifiersOfClass(TSubclassOf modifierClass) +{ + TArray ret; + for (AModifier* mod : m_modifiers) + { + if (mod->IsA(modifierClass)) + { + ret.Add(mod); + } + } + return ret; +} +void ModifierManager::BroadcastManaDrainFailed() +{ + for(AModifier* modifier : m_modifiers) + { + if (modifier != nullptr && IsValid(modifier)) + { + modifier->onManaDrainFailed.Broadcast(); + } + } +} + +//reset all modifiable stats so that modifiers can recalculate them +void ModifierManager::m_Reset() +{ + m_character->ResetModifiers(); +} +void ModifierManager::RecalculateCharacter() +{ + m_Reset(); + for (AModifier* it : m_modifiers) + { + it->ModifyCharacter(); + } + m_character->CheckStatsOverflow(); +} + + +AModifier::AModifier() +{ + PrimaryActorTick.bCanEverTick = true; + bReplicates = true; + lifeTime = 0; + ticksPerSecond = 0; + m_dontDestroy = false; + RootComponent = CreateDefaultSubobject("Root"); +} + +bool AModifier::operator < (const AModifier& other) const +{ + return priority < other.priority; +} +bool AModifier::operator > (const AModifier& other) const +{ + return priority > other.priority; +} + + +void AModifier::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + if (m_started) + ModifierEnd(); +} + +void AModifier::ModifyCharacter() +{ +} +bool AModifier::ShouldBeRemoved() const +{ + return (lifeTime <= 0.0f) && !m_dontDestroy; +} +void AModifier::ForceDestroy() +{ + m_forceDestroy = true; +} +void AModifier::ModifierTick() +{ +} + +void AModifier::ModifierStart() +{ + +} + +void AModifier::ModifierEnd() +{ + +} +bool AModifier::OnDamage(int32& damage, ANetworkCharacter* damageDealer) +{ + return true; +} +bool AModifier::OnDealDamage(int32& damage, ANetworkCharacter* damageTaker) +{ + return true; +} +void AModifier::OnStandardAttack(ANetworkCharacter* character) +{ + return; +} +void AModifier::AfterDamage(int32) +{ + return; +} + +float AModifier::m_MultiplyEffect(float value, EffectType effectType, bool positive) +{ + switch (effectType) + { + case EffectType::additive: + { + if (!positive) + return value * target->m_negativeEffectMultiplier; + else + return value * target->m_positiveEffectMultiplier; + } + case EffectType::multiplicative: + { + if (!positive) + return value * (1.0f/target->m_negativeEffectMultiplier); + else + return value * target->m_positiveEffectMultiplier; + } + default: + return 0.0f; + } +} + +//character modification functions +void AModifier::m_AddMaxMana(int32 maxMana) +{ + if (IsValid(target)) + { + maxMana = m_MultiplyEffect(maxMana, additive, maxMana>0); + target->m_maxMana += maxMana; + target->AddMana(maxMana); + } +} +void AModifier::m_MultiplyManaRegenMultiplier(float manaRegenMultiplier) +{ + if (IsValid(target)) + { + manaRegenMultiplier = m_MultiplyEffect(manaRegenMultiplier, multiplicative, manaRegenMultiplier>1); + target->m_manaRegenMultiplier *= manaRegenMultiplier; + } +} +void AModifier::m_MultiplyManaUsageMultiplier(float manaUsageMultiplier) +{ + if (IsValid(target)) + { + manaUsageMultiplier = m_MultiplyEffect(manaUsageMultiplier, multiplicative, manaUsageMultiplier>1); + target->m_manaUsageMultiplier *= manaUsageMultiplier; + } +} +void AModifier::m_AddBlockedMana(int32 blockedMana) +{ + if (IsValid(target)) + { + if (target->m_blockedMana + blockedMana > target->m_maxMana) + { + ModifierManager* mod = character->GetModifierManager(); + if (mod != nullptr) + mod->BroadcastManaDrainFailed(); + } + else + target->m_blockedMana += blockedMana; + } +} +void AModifier::m_AddDamageMultiplier(float multiplier) +{ + if (IsValid(target)) + { + multiplier = m_MultiplyEffect(multiplier, additive, multiplier > 0); + target->m_damageMultiplier += multiplier; + } +} +void AModifier::m_AddMaxHealth(int32 health) +{ + if (IsValid(target)) + { + health = m_MultiplyEffect(health, additive, health >0); + target->m_maxHealth += health; + } +} +void AModifier::m_AddMovementSpeedMultiplier(float multiplier) +{ + if (IsValid(target)) + { + multiplier = m_MultiplyEffect(multiplier, multiplicative, multiplier > 0); + target->GetCharacterMovement()->MaxWalkSpeed *= multiplier; + } +} +void AModifier::m_AddIgnoreArmor(float normalizedPercentage) +{ + if (IsValid(target)) + { + normalizedPercentage = m_MultiplyEffect(normalizedPercentage, additive, normalizedPercentage > 0); + target->m_ignoreArmor += normalizedPercentage; + } +} +void AModifier::m_AddArmor(float armor) +{ + if (IsValid(target)) + { + armor = m_MultiplyEffect(armor, additive, armor > 0); + target->m_armor += armor; + } +} + +void AModifier::m_MultiplyArmor(float armor) +{ + if (IsValid(target)) + { + armor = m_MultiplyEffect(armor, multiplicative, armor > 1); + target->m_armor *= armor; + } +} +void AModifier::m_MultiplyAttackDamage(float damageMultiplier) +{ + if (IsValid(target)) + { + damageMultiplier = m_MultiplyEffect(damageMultiplier, multiplicative, damageMultiplier>1); + FPRINT(damageMultiplier); + target->m_attackDamageMultiplier *= damageMultiplier; + } +} +void AModifier::m_AddMagicDamage(float damageMultiplier) +{ + if (IsValid(target)) + { + damageMultiplier = m_MultiplyEffect(damageMultiplier, multiplicative, damageMultiplier > 1); + target->m_magicDamageMultiplier *= damageMultiplier; + } +} +void AModifier::m_AddAttackSpeed(float amount) +{ + if (IsValid(target)) + { + amount = m_MultiplyEffect(amount, additive, amount> 0); + target->m_attackSpeed += amount; + } +} +void AModifier::m_AddAttackSpeedMultiplier(float multiplier) +{ + if (IsValid(target)) + { + multiplier = m_MultiplyEffect(multiplier, multiplicative, multiplier > 1); + target->m_attackSpeed *= multiplier; + } +} +void AModifier::m_AddCooldownReduction(float amount) +{ + if (IsValid(target)) + { + amount = m_MultiplyEffect(amount, additive, amount > 0); + target->m_cooldownReduction += amount; + } +} +void AModifier::m_AddCooldownReductionMultiplier(float multiplier) +{ + if (IsValid(target)) + { + multiplier = m_MultiplyEffect(multiplier, multiplicative, multiplier > 1); + target->m_cooldownReduction *= multiplier; + } +} +void AModifier::m_MultiplyDamageTakenMultiplier(float multiplier) +{ + if (IsValid(target)) + { + multiplier = m_MultiplyEffect(multiplier, multiplicative, multiplier < 0); + target->m_damageTakenMultiplier *= multiplier; + } +} +void AModifier::m_MultiplyPositiveEffectMultiplier(float multiplier) +{ + if (IsValid(target)) + target->m_positiveEffectMultiplier *= multiplier; +} +void AModifier::m_MultiplyNegativeEffectMultiplier(float multiplier) +{ + if (IsValid(target)) + target->m_negativeEffectMultiplier *= multiplier; +} +void AModifier::m_addChannelMovementSpeedMultiplier(float channelMovementSpeedMultiplier) +{ + if (IsValid(target)) + { + channelMovementSpeedMultiplier = m_MultiplyEffect(channelMovementSpeedMultiplier, additive, channelMovementSpeedMultiplier > 0); + target->m_castingMovementspeedMultiplier += channelMovementSpeedMultiplier; + } +} +void AModifier::m_MultiplyMagicDamageMultiplier(float multiplier) +{ + if (IsValid(target)) + { + multiplier = m_MultiplyEffect(multiplier, additive, multiplier> 0); + target->m_magicDamageMultiplier *= multiplier; + } +} + +void AModifier::m_AddStunnedState() +{ + if (IsValid(target)) + if (target->canBeStunned) + { + target->m_stunned = true; + target->m_InteruptSpellcasting(); + } +} + +void AModifier::m_AddSilencedState() +{ + if (IsValid(target)) + if (target->canBeSilenced) + { + target->m_silenceCount++; + target->m_silenced = true; + target->m_InteruptSpellcasting(); + } +} + +void AModifier::m_RemoveSilencedState() +{ + if (IsValid(target)) + { + target->m_silenceCount--; + if (target->m_silenceCount == 0) + { + target->m_silenced = false; + } + else if (target->m_silenceCount < 0) + { + FERROR("m_silenceCount <0 in removeSilenceState"); + } + } +} +void AModifier::m_Start() +{ + if (!m_started) + { + if (IsValid(target)) + { + if (ticksPerSecond <= 0) + m_tickRate = 0.0f; + else + m_tickRate = 1.0f / ticksPerSecond; + m_started = true; + if (lifeTime == 0) + { + m_dontDestroy = true; + } + ModifierStart(); + } + else + { + GWERROR(L"Can't start modifier " + GetName() + " target not set"); + } + } + else + { + GWERROR(L"modifier started twice"); + } +} +void AModifier::Tick(float DeltaTime) +{ + Super::Tick(DeltaTime); + if (!IsValid(target) || !m_started) + return; + if (ShouldBeRemoved() || m_forceDestroy) + return; + if (m_tickRate > 0.0f) + { + m_tickTimer += DeltaTime; + while (m_tickTimer >= m_tickRate) + { + ModifierTick(); + m_tickTimer -= m_tickRate; + } + } + lifeTime -= DeltaTime; +} +void AModifier::m_Init(class ANetworkCharacter* a_character, float duration) +{ + target = a_character; + lifeTime = duration; +} diff --git a/Source/UnrealProject/Abilities/Modifier.h b/Source/UnrealProject/Abilities/Modifier.h new file mode 100644 index 0000000..b94e54a --- /dev/null +++ b/Source/UnrealProject/Abilities/Modifier.h @@ -0,0 +1,122 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "DealDamageProxy.h" +#include +#include "AbilityInfo.h" +#include "Modifier.generated.h" + +class ModifierManager +{ +public: + ModifierManager(class ANetworkCharacter* character); + ~ModifierManager(); + + void ClearModifiers(bool shouldCallEnd = true); + void Tick(float DeltaSeconds); + + class AModifier* AddModifier(TSubclassOf modifierClass, float duration); + class AModifier* AddModifier(AModifier* modifierInstance); + TArray GetModifiersOfClass(TSubclassOf modifierClass); + void RecalculateCharacter(); + void BroadcastManaDrainFailed(); + +private: + void m_Reset(); + + friend class ANetworkCharacter; + TArray m_modifiers; + class ANetworkCharacter* m_character; +}; + +UCLASS() +class UNREALPROJECT_API AModifier : public ADealDamageProxy +{ + GENERATED_BODY() + + friend class ADefaultPlayerController; + friend class ModifierManager; + friend class ANetworkCharacter; + friend class AOnDamageModifier; + +public: + AModifier(); + virtual void Tick(float DeltaSeconds) override final; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + + virtual void ModifierTick(); + virtual void ModifierStart(); + virtual void ModifierEnd(); + virtual bool OnDamage(int32& damage, ANetworkCharacter* damageDealer); + virtual bool OnDealDamage(int32& damage, ANetworkCharacter* damageTaker); + virtual void OnStandardAttack(ANetworkCharacter* character); + virtual void AfterDamage(int32 damage); + + UFUNCTION(BlueprintCallable, Category = "modifiers") + virtual bool ShouldBeRemoved() const; + virtual void ModifyCharacter(); + UFUNCTION(BlueprintCallable, Category = "modifiers") + void ForceDestroy(); + + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "modifiers", meta=(ExposeOnSpawn)) + float lifeTime; + UPROPERTY() + ANetworkCharacter* target; + UPROPERTY(EditDefaultsOnly, Category = "modifiers") + int32 ticksPerSecond; + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "modifiers", meta = (ExposeOnSpawn)) + int32 priority; + + DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnManaDrainFailed); + + UPROPERTY(BlueprintAssignable, Category = "Mana Drain") + FOnManaDrainFailed onManaDrainFailed; + + bool operator < (const AModifier&) const; + bool operator > (const AModifier&) const; +protected: + enum EffectType + { + additive = 0, + multiplicative + }; + float m_MultiplyEffect(float value, EffectType effectType, bool positive); + + // Modification Functions + void m_AddMaxMana(int32 maxMana); + void m_MultiplyManaRegenMultiplier(float manaRegenMultiplier); + void m_AddBlockedMana(int32 blockedMana); + void m_AddDamageMultiplier(float multiplier); + void m_AddIgnoreArmor(float normalizedPercentage); + void m_AddArmor(float armor); + void m_MultiplyArmor(float armor); + void m_MultiplyDamageTakenMultiplier(float multiplier); + void m_AddMaxHealth(int32 health); + void m_AddMovementSpeedMultiplier(float multiplier); + void m_AddAttackSpeed(float amount); + void m_AddAttackSpeedMultiplier(float multiplier); + void m_AddCooldownReduction(float amount); + void m_AddCooldownReductionMultiplier(float multiplier); + void m_AddStunnedState(); + void m_AddSilencedState(); + void m_RemoveSilencedState(); + void m_MultiplyAttackDamage(float damageMultiplier); + void m_AddMagicDamage(float damageMultiplier); + void m_MultiplyManaUsageMultiplier(float manaUsageMultiplier); + void m_MultiplyNegativeEffectMultiplier(float effectMultiplier); + void m_MultiplyPositiveEffectMultiplier(float effectMultiplier); + void m_MultiplyMagicDamageMultiplier(float effectMultiplier); + void m_addChannelMovementSpeedMultiplier(float channelMovementSpeedMultiplier); + float m_tickRate; + +private: + void m_Start(); + void m_Init(class ANetworkCharacter* character, float duration); + + bool m_forceDestroy; + int m_debugWarning; + bool m_started; + float m_tickTimer; + bool m_dontDestroy; +}; \ No newline at end of file diff --git a/Source/UnrealProject/Abilities/NativeModifiers.cpp b/Source/UnrealProject/Abilities/NativeModifiers.cpp new file mode 100644 index 0000000..840ac69 --- /dev/null +++ b/Source/UnrealProject/Abilities/NativeModifiers.cpp @@ -0,0 +1,704 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" + +#include "NetworkCharacter.h" +#include "NetworkPlayer.h" +#include "NativeModifiers.h" +#include "EffectFunctionLibrary.h" +#include "Effect.h" + +#define standardTicksPerSecond 20 +//SpeedModifier +ASpeedModifier::ASpeedModifier() +{ + +} + +void ASpeedModifier::ModifyCharacter() +{ + UCharacterMovementComponent* characterMovement = target->GetCharacterMovement(); + characterMovement->MaxWalkSpeed *= speedMultiplier; +} + +//RegenModifier +ARegenModifier::ARegenModifier() +{ + ticksPerSecond = 5; +} + +bool ARegenModifier::ShouldBeRemoved() const +{ + return false; +} + +void ARegenModifier::ModifierTick() +{ + if ( m_noDamageTime < target->GetTimeSinceDamage()) + target->AddHealth(regenPerTick); +} + +//RegenModier +AManaRegenModifier::AManaRegenModifier() +{ + ticksPerSecond = standardTicksPerSecond; +} + +bool AManaRegenModifier::ShouldBeRemoved() const +{ + return false; +} + +void AManaRegenModifier::ModifierTick() +{ + target->AddMana(regenPerTick); +} + +//DOTModifier +ADOTModifier::ADOTModifier() +{ + ticksPerSecond = 3; +} + +void ADOTModifier::ModifierTick() +{ + if (IsValid(target)) + target->DealDamage(this, damagePerTick, 0.0f); +} + +//ADModifier +AADModifier::AADModifier() +{ + +} + +void AADModifier::ModifyCharacter() +{ + float add = ScaleGraphCurve(scalingGraph); + m_MultiplyAttackDamage(add); +} +//ADModifier +AAPModifier::AAPModifier() +{ + +} + +void AAPModifier::ModifyCharacter() +{ + float add = ScaleGraphCurve(scalingGraph); + m_AddMagicDamage(add); +} + +//MaxHealthModifier +AMaxHealthModifier::AMaxHealthModifier() +{ +} + +void AMaxHealthModifier::ModifyCharacter() +{ + //m_AddMaxHealth(bonusMaxHealth); + float add = ScaleGraphCurve(scalingGraph); + m_AddMaxHealth(add); +} + +//AConstMaxHealthModifier +AConstMaxHealthModifier::AConstMaxHealthModifier() +{ +} + +void AConstMaxHealthModifier::ModifyCharacter() +{ + m_AddMaxHealth(add); +} + +//BlockManaModifier +ABlockManaModifier::ABlockManaModifier() +{ + +} + +void ABlockManaModifier::ModifyCharacter() +{ + m_AddBlockedMana(Mana); +} + +//ManaPerSecModifier +AManaDrainModifier::AManaDrainModifier() +{ + ticksPerSecond = standardTicksPerSecond; +} + +void AManaDrainModifier::ModifierTick() +{ + if (ManaPerTick > target->GetMana()) + { + ModifierManager* mod = character->GetModifierManager(); + if (mod != nullptr) + mod->BroadcastManaDrainFailed(); + } + else + { + target->RemoveMana(ManaPerTick); + } +} + + +//ManaPerSecModifier +AManaDrainCurveModifier::AManaDrainCurveModifier() +{ + ticksPerSecond = standardTicksPerSecond; +} + +void AManaDrainCurveModifier::ModifierTick() +{ + float mana = ScaleGraphCurve(ManaPerTick); + if (mana > target->GetMana()) + { + ModifierManager* mod = character->GetModifierManager(); + if (mod != nullptr) + mod->BroadcastManaDrainFailed(); + } + else + { + target->RemoveMana(mana); + } +} + +//HealthModifier +AHealModifier::AHealModifier() +{ + ticksPerSecond = standardTicksPerSecond; +} + +void AHealModifier::ModifierTick() +{ + target->AddHealth(health); + m_shouldBeRemoved = true; +} + +bool AHealModifier::ShouldBeRemoved() const +{ + return m_shouldBeRemoved; +} + +//stunModifier +static TSubclassOf stunEffectType; +AStunModifier::AStunModifier() +{ + stunEffectType = ConstructorHelpers::FClassFinder(TEXT("/Game/Assets/Art/Effects/FX_Stunned")).Class; +} +void AStunModifier::ModifierStart() +{ + if (target->CanBeStunned() && lifeTime > 0.0f) + UEffectFunctionLibrary::CreateEffect(target, stunEffectType, target,0.0f, lifeTime); +} +void AStunModifier::ModifierEnd() +{ +} +void AStunModifier::ModifierTick() +{ +} +void AStunModifier::ModifyCharacter() +{ + m_AddStunnedState(); +} +//stunModifier + +ASilenceModifier::ASilenceModifier() +{ +} +void ASilenceModifier::ModifierStart() +{ + m_AddSilencedState(); +} + + +void ASilenceModifier::ModifierEnd() +{ + m_RemoveSilencedState(); +} +//ADamageTakenModifier +ADamageTakenModifier::ADamageTakenModifier() +{ + +} + +void ADamageTakenModifier::ModifyCharacter() +{ + m_MultiplyDamageTakenMultiplier(damageTakenMultiplier); +} + + +bool ADamageTakenModifier::ShouldBeRemoved() const +{ + return false; +} + +//AVisibilityModifier +AVisibilityModifier::AVisibilityModifier() +{ + +} + +bool AVisibilityModifier::ShouldBeRemoved() const +{ + return false; +} + +void AVisibilityModifier::ModifierTick() +{ + m_timer -= m_tickRate; + if (m_timer < 0 && visibleTime > 0 && visibleBreak> 0) + { + if (m_visible) + { + m_timer = visibleBreak; + target->m_visible = false; + } + else + { + m_timer = visibleTime; + target->m_visible = true; + } + + m_visible = !m_visible; + } + +} + +//ADodgeDeathModifier +ADodgeDeathModifier::ADodgeDeathModifier() +{ + +} + +bool ADodgeDeathModifier::ShouldBeRemoved() const +{ + return false; +} + + +void ADodgeDeathModifier::ModifierTick() +{ + m_timer += m_tickRate; +} +bool ADodgeDeathModifier::OnDamage(int32& damage, ANetworkCharacter* damageDealer) +{ + if (damage >= target->GetHealth() && m_timer > ScaleGraphCurve(cooldown)) + { + float chance = (float)(rand() % 100) / 100.0f; + if (ScaleGraphCurve(dodgeChance) > chance) + { + target->AddHealth(ScaleGraphCurve(heal)); + m_timer = 0; + return false; + } + } + return true; +} + +//ACooldownReductionModifier +ACooldownReductionModifier::ACooldownReductionModifier() +{ +} + +void ACooldownReductionModifier::ModifyCharacter() +{ + float add = ScaleGraphCurve(scalingGraph); + m_AddCooldownReduction(add); +} + +//AAttackSpeedModifier +AAttackSpeedModifier::AAttackSpeedModifier() +{ + +} + +void AAttackSpeedModifier::ModifyCharacter() +{ + float add = ScaleGraphCurve(scalingGraph); + m_AddAttackSpeedMultiplier(add); +} + +//AAttackSpeedModifierConstant +AAttackSpeedModifierConstant::AAttackSpeedModifierConstant() +{ + +} + +void AAttackSpeedModifierConstant::ModifyCharacter() +{ + m_AddAttackSpeedMultiplier(attackSpeedMultiplier); +} + +bool AAttackSpeedModifierConstant::ShouldBeRemoved() const +{ + return false; +} + +//AArmorIgnoreModifier +AArmorIgnoreModifier::AArmorIgnoreModifier() +{ + +} + +void AArmorIgnoreModifier::ModifyCharacter() +{ + float add = ScaleGraphCurve(scalingGraph); + m_AddIgnoreArmor(add); +} + + +//ADodgeDeathModifier +ABeserkModifier::ABeserkModifier() +{ + ticksPerSecond = standardTicksPerSecond; + m_active = false; +} + +bool ABeserkModifier::ShouldBeRemoved() const +{ + return false; +} + +void ABeserkModifier::AfterDamage(int32 damage) +{ + float deltahealth = (float)target->GetHealth() / (float)target->GetMaxHealth(); + + if (deltahealth < ScaleGraphCurve(healthThreshold)) + { + if (!m_active) + { + m_hasToActivate = true; + } + } +} + + + +void ABeserkModifier::ModifierTick() +{ + float deltahealth = (float)target->GetHealth() / (float)target->GetMaxHealth(); + if (m_hasToActivate) + { + m_hasToActivate = false; + m_active = true; + m_ASModifier = GetWorld()->SpawnActor(); + m_ASModifier->lifeTime = 0.0f; + m_ASModifier->target = target; + m_ASModifier->attackSpeedMultiplier = ScaleGraphCurve(attackSpeedMultiplier); + target->GetModifierManager()->AddModifier(m_ASModifier); + } + if (deltahealth > ScaleGraphCurve(healthThreshold)) + { + if (m_active) + { + m_active = false; + m_ASModifier->ForceDestroy(); + } + } +} + +//AArmorReductionModifier +AArmorReductionModifier::AArmorReductionModifier() +{ + +} + + +void AArmorReductionModifier::ModifyCharacter() +{ + m_AddArmor(armorReduction); +} + + +//AHealthRegenModifier +AHealthRegenModifier::AHealthRegenModifier() +{ + ticksPerSecond = standardTicksPerSecond; +} + + +void AHealthRegenModifier::ModifierTick() +{ + float add = ScaleGraphCurve(scalingGraph); + target->AddHealth(add); +} + +//AOnStandardAttackDOTModifier +AOnStandardAttackDOTModifier::AOnStandardAttackDOTModifier() +{ + ticksPerSecond = standardTicksPerSecond; +} + + +void AOnStandardAttackDOTModifier::ModifierTick() +{ + m_cooldownTimer += m_tickRate; +} + +void AOnStandardAttackDOTModifier::OnStandardAttack(ANetworkCharacter* targetcharacter) +{ + if (!IsValid(targetcharacter)) + return; + ADOTModifier* DOTModifier = GetWorld()->SpawnActor(); + DOTModifier->lifeTime = ScaleGraphCurve(damageDuration); + DOTModifier->damagePerTick = ScaleGraphCurve(damagePerTick); +// DOTModifier->target = targetcharacter; + DOTModifier->character = character; + DOTModifier->abilityInfo = abilityInfo; + DOTModifier->target = targetcharacter; + targetcharacter->GetModifierManager()->AddModifier(DOTModifier); + m_cooldownTimer = ScaleGraphCurve(cooldown); +} + +//AReturnDamageModifier +AReturnDamageModifier::AReturnDamageModifier() +{ + ticksPerSecond = standardTicksPerSecond; + +} +bool AReturnDamageModifier::OnDamage(int32& damage, ANetworkCharacter* damageDealer) +{ + if (damage >= 10 && IsValid(damageDealer)) + { + + float returnDamage = ScaleGraphCurve(scalingGraph); + damageDealer->DealDamage(this, damage * returnDamage, 0.0f); + } + return true; +} + + +//AReturnDamageModifier +ARedirectDamageModifier::ARedirectDamageModifier() +{ + ticksPerSecond = standardTicksPerSecond; +} + +bool ARedirectDamageModifier::OnDamage(int32& damage, ANetworkCharacter* damageDealer) +{ + if (damage >= 10 && IsValid(character)) + { + float redirect = ScaleGraphCurve(redirectScalingGraph); + if (redirect > 1) + { + FWARNING("redirect > 1, will act as if it is 0.9f"); + redirect = 0.9f; + } + if (redirect < 0) + { + FWARNING("redirect < 0, will act as if it is 0.0f"); + redirect = 0.0f; + } + float absorb = ScaleGraphCurve(absorbScalingGraph); + + if (absorb > 1) + { + FWARNING("absorb > 1, will act as if it is 1.0f"); + absorb = 1.0f; + } + if (absorb < 0.0f ) + { + FWARNING("absorb < 0, will act as if it is 0.0f"); + absorb = 0.0f; + } + character->DealDamage(this, damage * (redirect * absorb), 0.0f); + damage *= (1 - redirect); + DamageEvent(); + } + return true; +} + + +//ATrustModifier + +//set delegate in gamestate to know when the teammate respawns. +ATrustModifier::ATrustModifier() +{ + ticksPerSecond = 2; +} + +void ATrustModifier::ModifierTick() +{ + if (IsValid(character)) + { + target->GetModifierManager()->RecalculateCharacter(); + } + else + ForceDestroy(); +} + +void ATrustModifier::ModifyCharacter() +{ + if (IsValid(character)) + { + float add = ScaleGraphCurve(damageScalingGraph); + float deltahealth =1 + ( 1.0f - ((float)character->GetHealth() / (float)character->GetMaxHealth())); + m_MultiplyAttackDamage(add * deltahealth); + } +} + + +//AArmorIncreaseModifier +AArmorIncreaseModifier::AArmorIncreaseModifier() +{ + ticksPerSecond = standardTicksPerSecond; +} + +void AArmorIncreaseModifier::ModifyCharacter() +{ + float add = ScaleGraphCurve(armorScalingGraph); + m_AddArmor(add); +} + +//AMoveToModifier +AMoveToModifier::AMoveToModifier() +{ + ticksPerSecond = standardTicksPerSecond; +} +void AMoveToModifier::ModifierStart() +{ + m_movePerTick = (targetPos - target->GetActorLocation()) / (moveTime * ticksPerSecond); + +} + +void AMoveToModifier::ModifierTick() +{ + target->AddActorWorldOffset(m_movePerTick, true); +} + +//AHealthRegenPercentageModifier +AHealthRegenPercentageModifier::AHealthRegenPercentageModifier() +{ + ticksPerSecond = standardTicksPerSecond; +} + + +void AHealthRegenPercentageModifier::ModifierTick() +{ + float add = ScaleGraphCurve(scalingGraph); + add /= ticksPerSecond; + int32 health = character->GetMaxHealth(); + health *= add; + target->AddHealth(health); +} + +//AManaRegenMultiplierModifier +AManaRegenMultiplierModifier::AManaRegenMultiplierModifier() +{ + ticksPerSecond = standardTicksPerSecond; +} + +void AManaRegenMultiplierModifier::ModifyCharacter() +{ + float add = ScaleGraphCurve(manaRegenMultiplierScalingGraph); + m_MultiplyManaRegenMultiplier(add); +} + +//AManaCostMultiplierModifier +AManaCostMultiplierModifier::AManaCostMultiplierModifier() +{ + ticksPerSecond = standardTicksPerSecond; +} + +void AManaCostMultiplierModifier::ModifyCharacter() +{ + float add = ScaleGraphCurve(manaCostMultiplierScalingGraph); + m_MultiplyManaUsageMultiplier(add); +} + +//AManaCostMultiplierModifier +AEffectMultiplierModifier::AEffectMultiplierModifier() +{ + ticksPerSecond = standardTicksPerSecond; +} + +void AEffectMultiplierModifier::ModifyCharacter() +{ + float add = ScaleGraphCurve(effectMultiplierScalingGraph); + if (positive) + m_MultiplyPositiveEffectMultiplier(add); + else + m_MultiplyNegativeEffectMultiplier(add); +} + +//ACastingMovementSpeedMultiplierModifier +ACastingMovementSpeedMultiplierModifier::ACastingMovementSpeedMultiplierModifier() +{ + ticksPerSecond = standardTicksPerSecond; +} + +void ACastingMovementSpeedMultiplierModifier::ModifyCharacter() +{ + float add = ScaleGraphCurve(movementSpeedMultiplierScalingGraph); + m_addChannelMovementSpeedMultiplier(add); +} + +//AReaperModifier +AReaperModifier::AReaperModifier() +{ + ticksPerSecond = standardTicksPerSecond; +} + +bool AReaperModifier::OnDealDamage(int32& damage, ANetworkCharacter* damageTaker) +{ + float multiplier = ScaleGraphCurve(damagemultiplier); + float norm = ScaleGraphCurve(threshhold); + + float deltahealth = (float)damageTaker->GetHealth() / (float)damageTaker->GetMaxHealth(); + if ((deltahealth < norm && smaller) || (deltahealth > norm && !smaller)) + { + damage *= multiplier; + } + + return true; +} + +//AMagicDamageMultiplierModifier +AMagicDamageMultiplierModifier::AMagicDamageMultiplierModifier() +{ + ticksPerSecond = standardTicksPerSecond; +} + +void AMagicDamageMultiplierModifier::ModifyCharacter() +{ + float add = ScaleGraphCurve(magicDamageMultiplierScalingGraph); + m_MultiplyMagicDamageMultiplier(add); +} + +//AStandardMeleeModifier +AStandardMeleeModifier::AStandardMeleeModifier() +{ + count = 0; + ticksPerSecond = standardTicksPerSecond; +} + +void AStandardMeleeModifier::ModifierTick() +{ + m_timer += m_tickRate; + if (m_timer >= maxTime) + { + ForceDestroy(); + } +} + +bool AStandardMeleeModifier::AddCount() +{ + count++; + count%=maxCount; + m_timer = 0; + return count == 0; +} + +//ADamageToManaModifier +ADamageToManaModifier::ADamageToManaModifier() +{ + +} + +void ADamageToManaModifier::AfterDamage(int32 damage) +{ + float addMultiplier = ScaleGraphCurve(multiplier); + target->AddMana(damage * addMultiplier); +} \ No newline at end of file diff --git a/Source/UnrealProject/Abilities/NativeModifiers.h b/Source/UnrealProject/Abilities/NativeModifiers.h new file mode 100644 index 0000000..6cae2bc --- /dev/null +++ b/Source/UnrealProject/Abilities/NativeModifiers.h @@ -0,0 +1,683 @@ +#pragma once +#include "Abilities/Modifier.h" +#include "ScalingGraph.h" +#include "NativeModifiers.generated.h" + +// Increase/Decrease movement speed +UCLASS() +class UNREALPROJECT_API ASpeedModifier : public AModifier +{ + GENERATED_BODY() + +public: + ASpeedModifier(); + + virtual void ModifyCharacter(); + + //adjusts the movementspeed of the character + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, meta=(ExposeOnSpawn), Category = "Modifier") + float speedMultiplier; +}; + + +// Health Regen over time +UCLASS() +class UNREALPROJECT_API ARegenModifier : public AModifier +{ + GENERATED_BODY() + +public: + ARegenModifier(); + + virtual void ModifierTick() override; + virtual bool ShouldBeRemoved() const override; + + //the ammount of regen per tick (5 ticks per second) + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, meta=(ExposeOnSpawn), Category = "Modifier") + float regenPerTick = 5.0f; + //the total ammount of time the modifier should tick + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, meta=(ExposeOnSpawn), Category = "Modifier") + float regenTime; + +private: + FVector m_pos; + float m_noDamageTime = 5.0f; +}; + +// Mana regen over time +UCLASS() +class UNREALPROJECT_API AManaRegenModifier : public AModifier +{ + GENERATED_BODY() + +public: + AManaRegenModifier(); + + virtual void ModifierTick() override; + virtual bool ShouldBeRemoved() const override; + + //the ammount of mana per tick (20 ticks per second) + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, meta=(ExposeOnSpawn), Category = "Modifier") + float regenPerTick; +}; + +// Damage over time +UCLASS() +class UNREALPROJECT_API ADOTModifier : public AModifier +{ + GENERATED_BODY() + +public: + ADOTModifier(); + + virtual void ModifierTick() override; + + //damage per tick (3 ticks per second) + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, meta=(ExposeOnSpawn), Category = "Modifier") + float damagePerTick = 5.0f; +}; + +// Attack speed multiplier that scales with ability power +UCLASS() +class UNREALPROJECT_API AAttackSpeedModifier : public AModifier +{ + GENERATED_BODY() + +public: + AAttackSpeedModifier(); + virtual void ModifyCharacter(); + + //adjusts the attack speed of the character + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, meta=(ExposeOnSpawn), Category = "Modifier") + UCurveFloat* scalingGraph; +}; + +// Attack speed multiplier +UCLASS() +class UNREALPROJECT_API AAttackSpeedModifierConstant : public AModifier +{ + GENERATED_BODY() + +public: + AAttackSpeedModifierConstant(); + virtual void ModifyCharacter(); + bool ShouldBeRemoved() const override; + + //adjusts the attack speed of the character + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, meta = (ExposeOnSpawn), Category = "Modifier") + float attackSpeedMultiplier; +}; + +// Cooldown reduction +UCLASS() +class UNREALPROJECT_API ACooldownReductionModifier : public AModifier +{ + GENERATED_BODY() + +public: + ACooldownReductionModifier(); + virtual void ModifyCharacter(); + + //adjusts the cooldown of every ability of the character, except for that standard attack + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, meta=(ExposeOnSpawn), Category = "Modifier") + UCurveFloat* scalingGraph; +}; + +// Increase/Decrease Damage (multiplier) +UCLASS() +class UNREALPROJECT_API AADModifier : public AModifier +{ + GENERATED_BODY() + +public: + AADModifier(); + + virtual void ModifyCharacter() override; + + //adjusts the damage of the standard attack + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, meta = (ExposeOnSpawn), Category = "Modifier") + UCurveFloat* scalingGraph; +}; + + +// Increase/Decrease magic Damage (multiplier) +UCLASS() +class UNREALPROJECT_API AAPModifier : public AModifier +{ + GENERATED_BODY() + +public: + AAPModifier(); + + virtual void ModifyCharacter() override; + + //adjusts the damage of all abilities of the character except for the standard attack + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, meta = (ExposeOnSpawn), Category = "Modifier") + UCurveFloat* scalingGraph; +}; + +// Increase/Decrease max health +UCLASS() +class UNREALPROJECT_API AMaxHealthModifier : public AModifier +{ + GENERATED_BODY() + +public: + AMaxHealthModifier(); + virtual void ModifyCharacter(); + + //adjusts the max health of the character + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, meta = (ExposeOnSpawn), Category = "Modifier") + UCurveFloat* scalingGraph; +}; + +// Increase/Decrease max health with a int32 +UCLASS() +class UNREALPROJECT_API AConstMaxHealthModifier : public AModifier +{ + GENERATED_BODY() + +public: + AConstMaxHealthModifier(); + virtual void ModifyCharacter(); + + //adjusts the max health of the character + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, meta = (ExposeOnSpawn), Category = "Modifier") + int32 add; +}; + +// Increase/Decrease blocked mana +UCLASS() +class UNREALPROJECT_API ABlockManaModifier : public AModifier +{ + GENERATED_BODY() + +public: + ABlockManaModifier(); + + virtual void ModifyCharacter() override; + // mana blocked + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, meta=(ExposeOnSpawn), Category = "Modifier") + int32 Mana; +}; + +// Mana over time cost with a curve +UCLASS() +class UNREALPROJECT_API AManaDrainCurveModifier : public AModifier +{ + GENERATED_BODY() + +public: + AManaDrainCurveModifier(); + + virtual void ModifierTick() override; + + //mana drained per tick (20 ticks per second) + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, meta=(ExposeOnSpawn), Category = "Modifier") + UCurveFloat* ManaPerTick; +}; + + +// Mana over time cost +UCLASS() +class UNREALPROJECT_API AManaDrainModifier : public AModifier +{ + GENERATED_BODY() + +public: + AManaDrainModifier(); + + virtual void ModifierTick() override; + + //mana drained per tick (20 ticks per second) + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, meta = (ExposeOnSpawn), Category = "Modifier") + float ManaPerTick; +}; + +// Heal health 1 time +UCLASS() +class UNREALPROJECT_API AHealModifier : public AModifier +{ + GENERATED_BODY() + +public: + AHealModifier(); + + virtual void ModifierTick() override; + virtual bool ShouldBeRemoved() const override; + //the ammount of health that is healed + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, meta=(ExposeOnSpawn), Category = "Modifier") + float health; + +private: + bool m_shouldBeRemoved = false; +}; + +// Stun +UCLASS() +class UNREALPROJECT_API AStunModifier : public AModifier +{ + GENERATED_BODY() + +public: + AStunModifier(); + virtual void ModifierStart() override; + virtual void ModifierEnd() override; + virtual void ModifierTick() override; + virtual void ModifyCharacter() override; +}; + +// Stun +UCLASS() +class UNREALPROJECT_API ASilenceModifier : public AModifier +{ + GENERATED_BODY() + +public: + ASilenceModifier(); + virtual void ModifierStart() override; + virtual void ModifierEnd() override; +}; + +// Damage taken multiplier (only meant for bosses, will not work with a timer) +UCLASS() +class UNREALPROJECT_API ADamageTakenModifier : public AModifier +{ + GENERATED_BODY() + +public: + ADamageTakenModifier(); + + virtual void ModifyCharacter() override; + virtual bool ShouldBeRemoved() const override; + + //adjusts the damage taken. + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, meta=(ExposeOnSpawn), Category = "Modifier") + float damageTakenMultiplier; +}; + + + +//gives invisibility on the minimap with set breaks DEPRECATED +UCLASS() +class UNREALPROJECT_API AVisibilityModifier : public AModifier +{ + GENERATED_BODY() +public: + AVisibilityModifier(); + + virtual void ModifierTick() override; + virtual bool ShouldBeRemoved() const override; + //DEPRECATED + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, meta = (ExposeOnSpawn), Category = "Modifier") + float visibleBreak = 5.0f; + //DEPRECATED + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, meta = (ExposeOnSpawn), Category = "Modifier") + float visibleTime = 1.0f; + +private: + float m_timer; + bool m_visible; +}; + + +//gives a chance at healing instead of damage if the blow is the killing blow +UCLASS() +class UNREALPROJECT_API ADodgeDeathModifier : public AModifier +{ + GENERATED_BODY() + + float m_timer; +public: + ADodgeDeathModifier(); + + virtual bool ShouldBeRemoved() const override; + virtual void ModifierTick() override; + //the chance between 0 and 1 and the effect will be proced + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, meta = (ExposeOnSpawn), Category = "Modifier") + UCurveFloat* dodgeChance; + //the ammount of health that is healed on the effect + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, meta = (ExposeOnSpawn), Category = "Modifier") + UCurveFloat* heal; + + //the cooldown for the dodgedeath in seconds + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, meta = (ExposeOnSpawn), Category = "Modifier") + UCurveFloat* cooldown; + + + + virtual bool OnDamage(int32& damage, ANetworkCharacter* damageDealer) override; +}; + +//ignores a part of the enemy armor on all abilities +UCLASS() +class UNREALPROJECT_API AArmorIgnoreModifier : public AModifier +{ + GENERATED_BODY() + +public: + AArmorIgnoreModifier(); + virtual void ModifyCharacter(); + //the ammount of armor that is ignored (between 0 and 1) + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, meta = (ExposeOnSpawn), Category = "Modifier") + UCurveFloat* scalingGraph; +}; + +//gives a chance at healing instead of damage if the blow is the killing blow +UCLASS() +class UNREALPROJECT_API ABeserkModifier : public AModifier +{ + GENERATED_BODY() +public: + ABeserkModifier(); + + + virtual bool ShouldBeRemoved() const override; + + //the threshhold (between 0 and 1) where the effect will be proced + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, meta = (ExposeOnSpawn), Category = "Modifier") + UCurveFloat* healthThreshold; + //adjusts the attackspeed + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, meta = (ExposeOnSpawn), Category = "Modifier") + UCurveFloat* attackSpeedMultiplier; + + + void m_Start(); + virtual void ModifierTick() override; +private: + bool m_active; + bool m_hasToActivate; + class AAttackSpeedModifierConstant* m_ASModifier; + + virtual void AfterDamage(int32 damage) override; +}; + + +// Increase/Decrease Damage (multiplier) +UCLASS() +class UNREALPROJECT_API AArmorReductionModifier : public AModifier +{ + GENERATED_BODY() + +public: + AArmorReductionModifier(); + + virtual void ModifyCharacter() override; + //adjusts the armor + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, meta = (ExposeOnSpawn), Category = "Modifier") + float armorReduction; +}; + + +//regens health Per Tick +UCLASS() +class UNREALPROJECT_API AHealthRegenModifier : public AModifier +{ + GENERATED_BODY() + +public: + AHealthRegenModifier(); + //the ammount of health that is regened per tick (20 ticks per second) + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, meta = (ExposeOnSpawn), Category = "Modifier") + UCurveFloat* scalingGraph; + + virtual void ModifierTick() override; +}; + +//deals DOT on a standard attack +UCLASS() +class UNREALPROJECT_API AOnStandardAttackDOTModifier : public AModifier +{ + GENERATED_BODY() + +private: + float m_cooldownTimer; +public: + AOnStandardAttackDOTModifier(); + //damage per tick on standard attack hit(3 ticks per second) + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, meta = (ExposeOnSpawn), Category = "Modifier") + UCurveFloat* damagePerTick; + //the cooldown of the effect + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, meta = (ExposeOnSpawn), Category = "Modifier") + UCurveFloat* cooldown; + //the duration of the effect( of the DOT) + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, meta = (ExposeOnSpawn), Category = "Modifier") + UCurveFloat* damageDuration; + + virtual void ModifierTick() override; + virtual void OnStandardAttack(ANetworkCharacter* targetcharacter) override; +}; + +//returns a part of the damage +UCLASS() +class UNREALPROJECT_API AReturnDamageModifier : public AModifier +{ + GENERATED_BODY() +public: + AReturnDamageModifier(); + + //chance between 0 and 1 of procing the effect + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, meta = (ExposeOnSpawn), Category = "Modifier") + UCurveFloat* scalingGraph; + + virtual bool OnDamage(int32& damage, ANetworkCharacter* damageDealer) override; +}; + +//redirects a part of the damage to the ally +UCLASS() +class UNREALPROJECT_API ARedirectDamageModifier : public AModifier +{ + GENERATED_BODY() +public: + ARedirectDamageModifier(); + + //chance between 0 and 1, the ammount that is being redirected (how much goes from the ally to the player + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, meta = (ExposeOnSpawn), Category = "Modifier") + UCurveFloat* redirectScalingGraph; + //chance between 0 and 1, the ammount of what is redirected that is being absorbed + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, meta = (ExposeOnSpawn), Category = "Modifier") + UCurveFloat* absorbScalingGraph; + + UFUNCTION(BlueprintImplementableEvent, Category = "Modifier") + void DamageEvent(); + + virtual bool OnDamage(int32& damage, ANetworkCharacter* damageDealer); +}; + +//increases attack damage of ally based on missing health +UCLASS() +class UNREALPROJECT_API ATrustModifier : public AModifier +{ + GENERATED_BODY() + +public: + ATrustModifier(); + virtual void ModifierTick() override; + virtual void ModifyCharacter() override; + //the ammount of damage the ally gets multiplied by the normalised value of the missing health + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, meta = (ExposeOnSpawn), Category = "Modifier") + UCurveFloat* damageScalingGraph; +}; + + +//increases armor +UCLASS() +class UNREALPROJECT_API AArmorIncreaseModifier : public AModifier +{ + GENERATED_BODY() + +public: + AArmorIncreaseModifier(); + virtual void ModifyCharacter(); + + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, meta = (ExposeOnSpawn), Category = "Modifier") + UCurveFloat* armorScalingGraph; +}; + +//increases attack damage of ally based on missing health +UCLASS() +class UNREALPROJECT_API AMoveToModifier : public AModifier +{ + GENERATED_BODY() + +public: + AMoveToModifier(); + virtual void ModifierStart(); + virtual void ModifierTick() override; + + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, meta = (ExposeOnSpawn), Category = "Modifier") + FVector targetPos; + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, meta = (ExposeOnSpawn), Category = "Modifier") + float moveTime; +private: + FVector m_movePerTick; +}; + +//increases attack damage of ally based on missing health +UCLASS() +class UNREALPROJECT_API AHealthRegenPercentageModifier : public AModifier +{ + GENERATED_BODY() + +public: + AHealthRegenPercentageModifier(); + + //normalised percentage (0-1) + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, meta = (ExposeOnSpawn), Category = "Modifier") + UCurveFloat* scalingGraph; + + virtual void ModifierTick() override; +}; + +//increases mana regen multiplier +UCLASS() +class UNREALPROJECT_API AManaRegenMultiplierModifier : public AModifier +{ + GENERATED_BODY() + +public: + AManaRegenMultiplierModifier(); + virtual void ModifyCharacter(); + + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, meta = (ExposeOnSpawn), Category = "Modifier") + UCurveFloat* manaRegenMultiplierScalingGraph; +}; + + +//increases mana regen multiplier +UCLASS() +class UNREALPROJECT_API AManaCostMultiplierModifier : public AModifier +{ + GENERATED_BODY() + +public: + AManaCostMultiplierModifier(); + virtual void ModifyCharacter(); + + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, meta = (ExposeOnSpawn), Category = "Modifier") + UCurveFloat* manaCostMultiplierScalingGraph; +}; + +//debuffs positive effects +UCLASS() +class UNREALPROJECT_API AEffectMultiplierModifier : public AModifier +{ + GENERATED_BODY() + +public: + AEffectMultiplierModifier(); + virtual void ModifyCharacter(); + + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, meta = (ExposeOnSpawn), Category = "Modifier") + bool positive; + + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, meta = (ExposeOnSpawn), Category = "Modifier") + UCurveFloat* effectMultiplierScalingGraph; +}; + +//debuffs positive effects +UCLASS() +class UNREALPROJECT_API ACastingMovementSpeedMultiplierModifier : public AModifier +{ + GENERATED_BODY() + +public: + ACastingMovementSpeedMultiplierModifier(); + virtual void ModifyCharacter(); + + + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, meta = (ExposeOnSpawn), Category = "Modifier") + UCurveFloat* movementSpeedMultiplierScalingGraph; +}; + +// deals more damage depending on the enemies hp (black/white threshhold) +UCLASS() +class UNREALPROJECT_API AReaperModifier : public AModifier +{ + GENERATED_BODY() + +public: + AReaperModifier(); + virtual bool OnDealDamage(int32& damage, ANetworkCharacter* damageTaker) override; + + //true == more damage when current health is less than threshhold false == more damage when current health is more than threshhold + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, meta = (ExposeOnSpawn), Category = "Modifier") + bool smaller; + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, meta = (ExposeOnSpawn), Category = "Modifier") + UCurveFloat* damagemultiplier; + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, meta = (ExposeOnSpawn), Category = "Modifier") + UCurveFloat* threshhold; +}; + +//increases/decreases magic damage +UCLASS() +class UNREALPROJECT_API AMagicDamageMultiplierModifier : public AModifier +{ + GENERATED_BODY() + +public: + AMagicDamageMultiplierModifier(); + virtual void ModifyCharacter(); + + + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, meta = (ExposeOnSpawn), Category = "Modifier") + UCurveFloat* magicDamageMultiplierScalingGraph; +}; + +//increases/decreases magic damage +UCLASS() +class UNREALPROJECT_API AStandardMeleeModifier : public AModifier +{ + GENERATED_BODY() + + float m_timer; +public: + virtual void ModifierTick() override; + AStandardMeleeModifier(); + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, meta = (ExposeOnSpawn), Category = "Modifier") + float maxTime; + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, meta = (ExposeOnSpawn), Category = "Modifier") + int32 maxCount; + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Modifier") + int32 count; + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, meta = (ExposeOnSpawn), Category = "Modifier") + int32 team; + UFUNCTION(BlueprintCallable, Category = "Modifier") + bool AddCount(); +}; + +//converts damage recieved to mana +UCLASS() +class UNREALPROJECT_API ADamageToManaModifier : public AModifier +{ + GENERATED_BODY() +public: + ADamageToManaModifier(); + + //the threshhold (between 0 and 1) where the effect will be proced + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, meta = (ExposeOnSpawn), Category = "Modifier") + UCurveFloat* multiplier; +private: + + virtual void AfterDamage(int32 damage) override; +}; \ No newline at end of file diff --git a/Source/UnrealProject/Abilities/PreCastAbilityEventGroup.cpp b/Source/UnrealProject/Abilities/PreCastAbilityEventGroup.cpp new file mode 100644 index 0000000..229cc0d --- /dev/null +++ b/Source/UnrealProject/Abilities/PreCastAbilityEventGroup.cpp @@ -0,0 +1,69 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "AbilityInfo.h" +#include "NetworkCharacter.h" +#include "PreCastAbilityEventGroup.h" + + + + + +void APreCastAbilityEventGroup::BeginPlay() +{ + check(Role == ROLE_Authority); + Super::BeginPlay(); + + if (!character || character->IsActorBeingDestroyed()) + { + Destroy(); + } + else + { + // Handle the destruction of the caster + character->OnDestroyed.AddDynamic(this, &APreCastAbilityEventGroup::m_OnCharacterDestroyed); + } +} +void APreCastAbilityEventGroup::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + check(Role == ROLE_Authority); + if (EndPlayReason == EEndPlayReason::Destroyed) + { + Super::EndPlay(EndPlayReason); + StartAbility(); + } +} +void APreCastAbilityEventGroup::Tick(float DeltaTime) +{ + Super::Tick(DeltaTime); + + if (IsPendingKill()) + return; +} + +void APreCastAbilityEventGroup::m_OnCharacterDestroyed() +{ + Destroy(); +} + +void APreCastAbilityEventGroup::StartAbility() +{ + character->m_CastAbility_Server(abilityInfo); + Destroy(); +} + + +APreCastAbilityEventGroup* APreCastAbilityEventGroup::InitPreCast(UAbilityInfo* info, ANetworkCharacter* character) +{ + UWorld* world = character->GetWorld(); + check(world); + + APreCastAbilityEventGroup* group = world->SpawnActorDeferred(info->precastEvent, FTransform::Identity); + + group->character = character; + //group->abilityState = character->GetAbilityState(info); + group->abilityInfo = info; + + UGameplayStatics::FinishSpawningActor(group, FTransform::Identity); + return group; +} \ No newline at end of file diff --git a/Source/UnrealProject/Abilities/PreCastAbilityEventGroup.h b/Source/UnrealProject/Abilities/PreCastAbilityEventGroup.h new file mode 100644 index 0000000..8b5cd42 --- /dev/null +++ b/Source/UnrealProject/Abilities/PreCastAbilityEventGroup.h @@ -0,0 +1,26 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "Abilities/DealDamageProxy.h" +#include "PreCastAbilityEventGroup.generated.h" + +/** + * + */ +UCLASS() +class UNREALPROJECT_API APreCastAbilityEventGroup : public ADealDamageProxy +{ + GENERATED_BODY() +public: + + virtual void BeginPlay() override final; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason); + virtual void Tick(float DeltaSeconds) override final; + + void StartAbility(); + static APreCastAbilityEventGroup* InitPreCast(UAbilityInfo* info, ANetworkCharacter* character); +private: + UFUNCTION() + void m_OnCharacterDestroyed(); +}; diff --git a/Source/UnrealProject/Abilities/ProjectileBase.cpp b/Source/UnrealProject/Abilities/ProjectileBase.cpp new file mode 100644 index 0000000..8812a40 --- /dev/null +++ b/Source/UnrealProject/Abilities/ProjectileBase.cpp @@ -0,0 +1,174 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "NetworkCharacter.h" + +#include "ProjectileBase.h" +#include "BlueprintAbilityLibrary.h" + +#include "AbilityTriggerBase.h" + +AProjectileBase::AProjectileBase() +{ + m_finishTimer = 0.0f; + m_fixedTimer = 0.0f; + maxDistance = 1000.0f; + distanceTraveled = 0.0f; + keepAliveAfterFinish = 1.0f; + filter = EAbilityFilter::EnemyAll; + m_finished = false; + + //server client replication + bReplicateMovement = true; + bReplicates = true; + bAlwaysRelevant = true; + PrimaryActorTick.bCanEverTick = true; +} + +void AProjectileBase::BeginPlay() +{ + // Get collider component + collider = Cast(GetRootComponent()); + if(!collider) + { + GWERROR(L"Projectile does not have a collider root!"); + Destroy(); + return; + } + + + if(Role != ROLE_Authority) + { + collider->SetCollisionEnabled(ECollisionEnabled::NoCollision); + } + else + { + collider->OnComponentHit.AddDynamic(this, &AProjectileBase::m_OnHit); + collider->OnComponentBeginOverlap.AddDynamic(this, &AProjectileBase::m_OnOverlapBegin); + // collider->SetCollisionProfileName("Projectiles"); + } + Super::BeginPlay(); +} + +void AProjectileBase::NativeFixedProjectileTick(float DeltaTime) +{ + FixedProjectileTick(DeltaTime); +} + +//call this in inherited classes, will call move +void AProjectileBase::Tick(float DeltaTime) +{ + Super::Tick(DeltaTime); + + // Fixed timestep for projectiles + m_fixedTimer += DeltaTime; + FVector startPos = GetActorLocation(); + const float delta = 1.0f / 60.0f; + while(m_fixedTimer >= delta) + { + NativeFixedProjectileTick(delta); + if (autoMove) + { + NativeMove(DeltaTime); + + FVector newLocation = GetActorLocation(); + FVector movedVector = newLocation - startPos; + startPos = newLocation; + + distanceTraveled += movedVector.Size(); + } + if (distanceTraveled > maxDistance) + { + Finish(); + break; + } + m_fixedTimer -= delta; + } + + if(m_finished && autoDestroy) + { + if((m_finishTimer -= DeltaTime) <= 0.0f) + { + Destroy(); + } + } +} + + +void AProjectileBase::m_OnOverlapBegin(class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult) +{ + ANetworkCharacter* netChar = Cast(OtherActor); + if(netChar && ULib::CheckAbilityFilter(filter, character, netChar)) + { + OnProjectileHit(netChar); + } +} +void AProjectileBase::m_OnHit(class AActor* OtherActor, class UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit) +{ + Finish(); +} + +void AProjectileBase::Finish_Implementation() +{ + if(!m_finished) + { + if(Role == ROLE_Authority) + { + NativeServerOnFinish(); + } + NativeOnProjectileFinished(); + if(collider) + collider->SetCollisionEnabled(ECollisionEnabled::NoCollision); + m_finishTimer = keepAliveAfterFinish; + m_finished = true; + } +} + +void AProjectileBase::FixedProjectileTick_Implementation(float DeltaTime) +{ +} +void AProjectileBase::OnProjectileHit_Implementation(ANetworkCharacter* character) +{ + NativeOnProjectileHit(character); +} + +void AProjectileBase::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME_CONDITION(AProjectileBase, speed, COND_InitialOnly); + DOREPLIFETIME_CONDITION(AProjectileBase, filter, COND_InitialOnly); + DOREPLIFETIME_CONDITION(AProjectileBase, maxDistance, COND_InitialOnly); + DOREPLIFETIME_CONDITION(AProjectileBase, autoMove, COND_InitialOnly); + DOREPLIFETIME_CONDITION(AProjectileBase, autoDestroy, COND_InitialOnly); +} + +void AProjectileBase::Move(float DeltaTime) +{ + NativeMove(DeltaTime); +} + +void AProjectileBase::NativeMove(float DeltaTime) +{ + //moves with sweep to get collision + float distance = speed * DeltaTime; + AddActorWorldOffset(GetActorForwardVector() * distance,true); +} + +void AProjectileBase::OnProjectileFinished_Implementation() +{ + +} +void AProjectileBase::NativeOnProjectileFinished() +{ + OnProjectileFinished(); +} + +void AProjectileBase::NativeOnProjectileHit(ANetworkCharacter* hitCharacter) +{ +} + +void AProjectileBase::NativeServerOnFinish() +{ + ServerOnFinish(); +} diff --git a/Source/UnrealProject/Abilities/ProjectileBase.h b/Source/UnrealProject/Abilities/ProjectileBase.h new file mode 100644 index 0000000..65917f3 --- /dev/null +++ b/Source/UnrealProject/Abilities/ProjectileBase.h @@ -0,0 +1,78 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "DealDamageProxy.h" +#include "ProjectileBase.generated.h" + +/** + * + */ +UCLASS() +class UNREALPROJECT_API AProjectileBase : public ADealDamageProxy +{ + GENERATED_BODY() + +public: + AProjectileBase(); + virtual void BeginPlay() override; + virtual void Tick(float DeltaSeconds) override; + + UFUNCTION(BlueprintNativeEvent, Category = "Projectile") + void FixedProjectileTick(float DeltaTime); + virtual void NativeFixedProjectileTick(float DeltaTime); + + UFUNCTION(BlueprintCallable, Category = "Projectile") + void Move(float DeltaTime); + virtual void NativeMove(float DeltaTime); + + UFUNCTION(BlueprintCallable, Category = "Projectile") + bool IsFinished() const { return m_finished; } + + UFUNCTION(BlueprintNativeEvent, Category = "Projectile") + void OnProjectileFinished(); + virtual void NativeOnProjectileFinished(); + + UFUNCTION(BlueprintNativeEvent, Category = "Projectile") + void OnProjectileHit(ANetworkCharacter* hitCharacter); + virtual void NativeOnProjectileHit(ANetworkCharacter* hitCharacter); + + virtual void NativeServerOnFinish(); + UFUNCTION(BlueprintImplementableEvent, Category = "Project") + void ServerOnFinish(); + + // Transfers projectile to the finished state, disabling collision, but still updating ticks + UFUNCTION(BlueprintCallable, NetMulticast, Reliable, Category = "Projectile") + void Finish(); + + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Replicated, Category = "Projectile", meta = (ExposeOnSpawn)) + float speed; + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Replicated, Category = "Projectile", meta = (ExposeOnSpawn)) + EAbilityFilter filter; + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Replicated, Category = "Projectile", meta = (ExposeOnSpawn)) + float maxDistance; + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Replicated, Category = "Projectile", meta = (ExposeOnSpawn)) + bool autoMove = true; + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Replicated, Category = "Projectile", meta = (ExposeOnSpawn)) + bool autoDestroy = true; + + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Projectile") + float distanceTraveled; + + UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Category = "Projectile") + float keepAliveAfterFinish; + + UPROPERTY(BlueprintReadOnly, Category = "Projectile") + UPrimitiveComponent* collider; + +protected: + // Internal event handlers + UFUNCTION() + void m_OnOverlapBegin(class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult); + UFUNCTION() + void m_OnHit(class AActor* OtherActor, class UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit); + + bool m_finished; + float m_finishTimer; + float m_fixedTimer; +}; diff --git a/Source/UnrealProject/Abilities/ScalingGraph.h b/Source/UnrealProject/Abilities/ScalingGraph.h new file mode 100644 index 0000000..2e1b25a --- /dev/null +++ b/Source/UnrealProject/Abilities/ScalingGraph.h @@ -0,0 +1,12 @@ +#pragma once +#include "ScalingGraph.Generated.h" + + +UCLASS() +class ULevelScaleLibrary : public UBlueprintFunctionLibrary +{ + GENERATED_BODY() +public: + UFUNCTION(BlueprintCallable, Category = "Scaling Graph") + static float ScaleGraphCurveFloat(float in, const UCurveFloat* val); +}; diff --git a/Source/UnrealProject/Abilities/SpiralProjectile.cpp b/Source/UnrealProject/Abilities/SpiralProjectile.cpp new file mode 100644 index 0000000..8015cf8 --- /dev/null +++ b/Source/UnrealProject/Abilities/SpiralProjectile.cpp @@ -0,0 +1,34 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "NetworkCharacter.h" +#include "SpiralProjectile.h" + +#define SPIRALTIMESTEP (0.02f) + +void ASpiralProjectile::BeginPlay() +{ + Super::BeginPlay(); + if (Role != ROLE_Authority) + return; + // Set the rotation of the projectile so that it is facing away from the caster. + SetActorRotation(FRotator((this->GetActorLocation() - character->GetActorLocation()).Rotation())); + m_elapsedTime = 0; +} + +void ASpiralProjectile::NativeMove(float DeltaTime) +{ + m_elapsedTime += DeltaTime; + // Fixed update + while (m_elapsedTime >= SPIRALTIMESTEP) + { + m_elapsedTime -= SPIRALTIMESTEP; + float distance = speed * SPIRALTIMESTEP; + // Rotate the projectile according to how far it has traveled. + FRotator rotation = FRotator(0, distance * 0.25f, 0); + AddActorWorldRotation(rotation); + // Move the projectile forwards. + AddActorWorldOffset(GetActorForwardVector() * (distance * 2), true); + } +} + diff --git a/Source/UnrealProject/Abilities/SpiralProjectile.h b/Source/UnrealProject/Abilities/SpiralProjectile.h new file mode 100644 index 0000000..914f404 --- /dev/null +++ b/Source/UnrealProject/Abilities/SpiralProjectile.h @@ -0,0 +1,22 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "ProjectileBase.h" +#include "SpiralProjectile.generated.h" + +/** + * + */ +UCLASS() +class UNREALPROJECT_API ASpiralProjectile : public AProjectileBase +{ + GENERATED_BODY() + +public: + virtual void BeginPlay() override; + virtual void NativeMove(float DeltaTime) override; + +private: + float m_elapsedTime; +}; diff --git a/Source/UnrealProject/Abilities/TrailActor.cpp b/Source/UnrealProject/Abilities/TrailActor.cpp new file mode 100644 index 0000000..c8ff999 --- /dev/null +++ b/Source/UnrealProject/Abilities/TrailActor.cpp @@ -0,0 +1,28 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "TrailActor.h" + + +ATrailActor::ATrailActor() +{ + +} + +void ATrailActor::BeginPlay() +{ + Super::BeginPlay(); + +} + + + +void ATrailActor::DynamicDestroy() +{ + Destroy(); +} + +void ATrailActor::DynamicAdd() +{ + GetOwner()->OnDestroyed.AddDynamic(this, &ATrailActor::DynamicDestroy); +} \ No newline at end of file diff --git a/Source/UnrealProject/Abilities/TrailActor.h b/Source/UnrealProject/Abilities/TrailActor.h new file mode 100644 index 0000000..8319197 --- /dev/null +++ b/Source/UnrealProject/Abilities/TrailActor.h @@ -0,0 +1,21 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "GameFramework/Actor.h" +#include "TrailActor.generated.h" + +UCLASS() +class UNREALPROJECT_API ATrailActor : public AActor +{ + GENERATED_BODY() + +public: + ATrailActor(); + + virtual void BeginPlay() override; + + UFUNCTION() + void DynamicDestroy(); + void DynamicAdd(); +}; diff --git a/Source/UnrealProject/Abilities/TrailTrigger.cpp b/Source/UnrealProject/Abilities/TrailTrigger.cpp new file mode 100644 index 0000000..69a9633 --- /dev/null +++ b/Source/UnrealProject/Abilities/TrailTrigger.cpp @@ -0,0 +1,93 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "TrailTrigger.h" +#include "Networkcharacter.h" +#include "TrailActor.h" +#include "Effect.h" +#include "EffectFunctionLibrary.h" + +ATrailTrigger::ATrailTrigger() +{ +} + +void ATrailTrigger::BeginPlay() +{ + Super::BeginPlay(); + if (Role == ROLE_Authority) + { + if (character == NULL) + { + FERROR("m_character is NULL"); + Destroy(); + return; + } + m_lastPos = character->GetActorLocation(); + SpawnTrail(FVector(0, 0, 0), true); + } +} + +void ATrailTrigger::SpawnTrail(FVector offset, bool first) +{ + + m_lastPos += offset; + UCapsuleComponent* newobject = NewObject(this); + newobject->SetCollisionProfileName(TEXT("Triggers")); + newobject->SetCapsuleRadius(colliderRadius, false); + newobject->SetCapsuleHalfHeight(500, false); + newobject->SetWorldLocation(m_lastPos); + newobject->OnComponentBeginOverlap.AddDynamic(this, &AAbilityTriggerBase::OnOverlapBegin); + newobject->OnComponentEndOverlap.AddDynamic(this, &AAbilityTriggerBase::OnOverlapEnd); + newobject->RegisterComponent(); + if(first) + RootComponent = newobject; + + FTransform effectTransform; + effectTransform.SetTranslation(m_lastPos); + effectTransform.SetRotation(character->GetActorRotation().Quaternion()); + UEffectFunctionLibrary::CreateEffectAt(this, trailEffectClass, effectTransform,0.0f,lifeTime); + + //ATrailActor* newTrailActor = GetWorld()->SpawnActor(trailActors); + //if (newTrailActor == nullptr) + // return;6 + //newTrailActor->SetActorLocation(m_lastPos); + //newTrailActor->SetOwner(this); + //newTrailActor->DynamicAdd(); +} + +void ATrailTrigger::Tick(float DeltaTime) +{ + Super::Tick(DeltaTime); + if (Role != ROLE_Authority || !IsValid(this)) + return; + if (colliderSpawnDistance == 0) + { + FERROR("colliderSpawnDistance == 0"); + return; + } + //m_timer += DeltaTime; + if ( !character ) return; + FVector distvec = character->GetActorLocation() - m_lastPos; + distvec.Z = 0; + float dist = distvec.Size(); + m_totalTimer += DeltaTime; + lifeTime -= DeltaTime; + if (lifeTime < 0) + { + Destroy(); + return; + } + if (m_totalTimer > totalSpawnTime) + { + return; + } + while (dist > colliderSpawnDistance) + { + UCapsuleComponent* newobject = NewObject(this); + distvec.Normalize(); + dist -= colliderSpawnDistance; + + SpawnTrail(distvec *colliderSpawnDistance); + } + +} \ No newline at end of file diff --git a/Source/UnrealProject/Abilities/TrailTrigger.h b/Source/UnrealProject/Abilities/TrailTrigger.h new file mode 100644 index 0000000..b6648f7 --- /dev/null +++ b/Source/UnrealProject/Abilities/TrailTrigger.h @@ -0,0 +1,40 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "Abilities/AbilityTriggerBase.h" +#include "TrailTrigger.generated.h" + +/** + * + */ +UCLASS() +class UNREALPROJECT_API ATrailTrigger : public AAbilityTriggerBase +{ + GENERATED_BODY() +public: + virtual void BeginPlay() override; + + ATrailTrigger(); + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Trigger", meta = (ClampMin = "0", UIMin = "0")) + float totalSpawnTime = 1.5f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Trigger", meta = (ClampMin = "0", UIMin = "0")) + float lifeTime = 5.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Trigger", meta = (ClampMin = "0", UIMin = "0")) + float colliderSpawnDistance = 50.0f; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Trigger", meta = (ClampMin = "0", UIMin = "0")) + float colliderRadius = 50.0f; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Trigger", meta = (ClampMin = "0", UIMin = "0")) + TSubclassOf trailEffectClass; + + + virtual void Tick(float DeltaSeconds) override; + +private: + void SpawnTrail(FVector offset, bool first = false); + float m_tickTimer = 0.0f; + FVector m_lastPos; + float m_totalTimer = 0.0f; +}; diff --git a/Source/UnrealProject/Abilities/poisonAuraTrigger.cpp b/Source/UnrealProject/Abilities/poisonAuraTrigger.cpp new file mode 100644 index 0000000..67e952d --- /dev/null +++ b/Source/UnrealProject/Abilities/poisonAuraTrigger.cpp @@ -0,0 +1,52 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "poisonAuraTrigger.h" +#include "NetworkCharacter.h" +#include "NativeModifiers.h" +#include "Modifier.h" + + + +ApoisonAuraTrigger::ApoisonAuraTrigger() +{ + +} +void ApoisonAuraTrigger::BeginPlay() +{ + Super::BeginPlay(); +} +void ApoisonAuraTrigger::Tick(float deltaTime) +{ + Super::Tick(deltaTime); +} +void ApoisonAuraTrigger::HitEvent(ANetworkCharacter* otherActor) +{ + + ModifierManager* manager = otherActor->GetModifierManager(); + if (playerMap.Find(otherActor) == nullptr) + { + RERROR("2 modifiers in the intimidatin aura"); + } + if (manager) + { + ADOTModifier* ASModifier = GetWorld()->SpawnActor(); + ASModifier->lifeTime = 0; + ASModifier->damagePerTick = damage; + ASModifier->target = otherActor; + manager->AddModifier(ASModifier); + playerMap.Add(otherActor, ASModifier); + + } + return Super::HitEvent(otherActor); +} +void ApoisonAuraTrigger::LeaveEvent(ANetworkCharacter* otherActor) +{ + + auto it = playerMap.Find(otherActor); + if (it == nullptr) + return Super::LeaveEvent(otherActor); + (*it)->ForceDestroy(); + playerMap.Remove(otherActor); + return Super::LeaveEvent(otherActor); +} \ No newline at end of file diff --git a/Source/UnrealProject/Abilities/poisonAuraTrigger.h b/Source/UnrealProject/Abilities/poisonAuraTrigger.h new file mode 100644 index 0000000..c249a1a --- /dev/null +++ b/Source/UnrealProject/Abilities/poisonAuraTrigger.h @@ -0,0 +1,28 @@ +// Project Lab - NHTV Igad + +#pragma once +#include +#include "Abilities/ConeTrigger.h" +#include "poisonAuraTrigger.generated.h" + +/** + * + */ + + +UCLASS() +class UNREALPROJECT_API ApoisonAuraTrigger : public AConeTrigger +{ + GENERATED_BODY() +public: + ApoisonAuraTrigger(); + + virtual void BeginPlay() override; + virtual void Tick(float DeltaSeconds) override; + virtual void HitEvent(class ANetworkCharacter* otherActor)override; + virtual void LeaveEvent(class ANetworkCharacter* otherActor)override; + UPROPERTY() + TMap playerMap; + UPROPERTY(meta = (ExposeOnSpawn), BlueprintReadWrite) + float damage; +}; diff --git a/Source/UnrealProject/Creatures/AnimationProxy.cpp b/Source/UnrealProject/Creatures/AnimationProxy.cpp new file mode 100644 index 0000000..cbe9d56 --- /dev/null +++ b/Source/UnrealProject/Creatures/AnimationProxy.cpp @@ -0,0 +1,37 @@ +#include "UnrealProject.h" +#include "AnimationProxy.h" + +bool FMainAnimProxy::Evaluate(FPoseContext& pose) +{ + // Evalueate the animation pose according to the blueprint + if(GetRootNode() != NULL) + { + GetRootNode()->Evaluate(pose); + } + + // Now set bone scales for character customizations + SetBoneScale(boneNames[0], FVector(charCustomization.headScale), pose); + + SetBoneScale(boneNames[1], FVector(1, charCustomization.legThicknessYScale, charCustomization.legThicknessZScale), pose); + SetBoneScale(boneNames[2], FVector(1, charCustomization.legThicknessYScale, charCustomization.legThicknessZScale), pose); + + SetBoneScale(boneNames[3], FVector(charCustomization.leftArmScale), pose); + SetBoneScale(boneNames[4], FVector(charCustomization.rightArmScale), pose); + + SetBoneScale(boneNames[5], FVector(charCustomization.torsoScale), pose); + + SetBoneScale(boneNames[6], FVector(charCustomization.overallScale), pose); + + return true; +} + +void FMainAnimProxy::SetBoneScale(const FName boneName, const FVector scale, FPoseContext& pose) +{ + int32 boneIndex = this->GetSkelMeshComponent()->GetBoneIndex(boneName); + // Check if bone is in the skeleton. + if(boneIndex != INDEX_NONE) + { + FTransform& boneTransform = pose.Pose[FCompactPoseBoneIndex(boneIndex)]; + boneTransform.SetScale3D(scale); + } +} \ No newline at end of file diff --git a/Source/UnrealProject/Creatures/AnimationProxy.h b/Source/UnrealProject/Creatures/AnimationProxy.h new file mode 100644 index 0000000..5355381 --- /dev/null +++ b/Source/UnrealProject/Creatures/AnimationProxy.h @@ -0,0 +1,22 @@ +#pragma once +#include "Animation/AnimInstanceProxy.h" +#include "PlayerSetupState.h" + +// Animation instance proxy that sets bone scales for character customizations +// This animation proxy just calculates the animation blueprint's pose and then applies bone scaled to them based on character customizations +struct FMainAnimProxy : FAnimInstanceProxy +{ + // The parent animation + FCharacterCustomization charCustomization; + + // Head + // Thigh L + // Thigh R + // UpperArm L + // UpperArm R + // Root + FName boneNames[7]; + + virtual bool Evaluate(FPoseContext& pose) override; + void SetBoneScale(const FName boneName, const FVector scale, FPoseContext& pose); +}; diff --git a/Source/UnrealProject/Creatures/BehaviorTree/BTTaskNodeClimb.cpp b/Source/UnrealProject/Creatures/BehaviorTree/BTTaskNodeClimb.cpp new file mode 100644 index 0000000..926610b --- /dev/null +++ b/Source/UnrealProject/Creatures/BehaviorTree/BTTaskNodeClimb.cpp @@ -0,0 +1,64 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "BTTaskNodeClimb.h" +#include "EnemyAIController.h" +#include "BehaviorTree/BehaviorTree.h" +#include "BehaviorTree/BlackboardComponent.h" +#include "BehaviorTree/Blackboard/BlackboardKeyType_Object.h" +#include "BehaviorTree/Blackboard/BlackboardKeyType_Vector.h" + + +UBTTaskNodeClimb::UBTTaskNodeClimb() +{ + NodeName = "Climb"; + bNotifyTick = true; + // accept only actors and vectors + BlackboardKey.AddObjectFilter(this, GET_MEMBER_NAME_CHECKED(UBTTaskNodeClimb, BlackboardKey), AActor::StaticClass()); + BlackboardKey.AddVectorFilter(this, GET_MEMBER_NAME_CHECKED(UBTTaskNodeClimb, BlackboardKey)); + directionKey.AddVectorFilter(this, GET_MEMBER_NAME_CHECKED(UBTTaskNodeClimb, directionKey)); + directionKey.AddObjectFilter(this, GET_MEMBER_NAME_CHECKED(UBTTaskNodeClimb, directionKey), AActor::StaticClass()); +} +void UBTTaskNodeClimb::InitializeFromAsset(UBehaviorTree& Asset) +{ + Super::InitializeFromAsset(Asset); + + UBlackboardData &BBAsset = *GetBlackboardAsset(); + BlackboardKey.ResolveSelectedKey(BBAsset); + directionKey.ResolveSelectedKey(BBAsset); +} + +EBTNodeResult::Type UBTTaskNodeClimb::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) +{ + EBTNodeResult::Type NodeResult = EBTNodeResult::InProgress; + NodeResult = PeformClimbTask(OwnerComp, NodeMemory); + return NodeResult; +} +EBTNodeResult::Type UBTTaskNodeClimb::PeformClimbTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) +{ + const UBlackboardComponent* MyBlackboard = OwnerComp.GetBlackboardComponent(); + AAIController* MyController = OwnerComp.GetAIOwner(); + AEnemyAIController* cont = Cast(OwnerComp.GetAIOwner()); + EBTNodeResult::Type NodeResult = EBTNodeResult::Failed; + if (cont && MyBlackboard) + { + const FVector TargetLocation = MyBlackboard->GetValue(BlackboardKey.GetSelectedKeyID()); + FVector TargetDirection = FVector::ZeroVector; + MyBlackboard->GetLocationFromEntry(directionKey.GetSelectedKeyID(), TargetDirection); + + if (cont->VerticleMoveTo(TargetLocation, TargetDirection,AcceptableRadius )) + { + NodeResult = EBTNodeResult::Succeeded; + } + else NodeResult = EBTNodeResult::InProgress; + } + return NodeResult; +} +void UBTTaskNodeClimb::TickTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds) +{ + const EBTNodeResult::Type NodeResult = PeformClimbTask(OwnerComp, NodeMemory); + if (NodeResult != EBTNodeResult::InProgress) + { + FinishLatentTask(OwnerComp, NodeResult); + } +} \ No newline at end of file diff --git a/Source/UnrealProject/Creatures/BehaviorTree/BTTaskNodeClimb.h b/Source/UnrealProject/Creatures/BehaviorTree/BTTaskNodeClimb.h new file mode 100644 index 0000000..061d00e --- /dev/null +++ b/Source/UnrealProject/Creatures/BehaviorTree/BTTaskNodeClimb.h @@ -0,0 +1,36 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "BehaviorTree/Tasks/BTTask_BlackboardBase.h" +#include "BTTaskNodeClimb.generated.h" + +/** + * + */ +UCLASS(config = Game) +class UNREALPROJECT_API UBTTaskNodeClimb : public UBTTaskNode +{ + GENERATED_BODY() +public : + UPROPERTY(config, Category = Node, EditAnywhere, meta = (ClampMin = "0.0")) + float AcceptableRadius; + FName GetSelectedBlackboardKey() const; + UBTTaskNodeClimb(); + virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override; + virtual void TickTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds) override; + virtual void InitializeFromAsset(UBehaviorTree& Asset) override; + UPROPERTY(EditAnywhere, Category = Blackboard) + FBlackboardKeySelector directionKey; + UPROPERTY(EditAnywhere, Category = Blackboard) + FBlackboardKeySelector BlackboardKey; +protected: + EBTNodeResult::Type PeformClimbTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory); + + +}; + +FORCEINLINE FName UBTTaskNodeClimb::GetSelectedBlackboardKey() const +{ + return directionKey.SelectedKeyName; +} diff --git a/Source/UnrealProject/Creatures/BehaviorTree/BTTaskNodeJumpFence.cpp b/Source/UnrealProject/Creatures/BehaviorTree/BTTaskNodeJumpFence.cpp new file mode 100644 index 0000000..f1d7645 --- /dev/null +++ b/Source/UnrealProject/Creatures/BehaviorTree/BTTaskNodeJumpFence.cpp @@ -0,0 +1,56 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "BTTaskNodeJumpFence.h" +#include "EnemyAIController.h" +#include "BehaviorTree/BehaviorTree.h" +#include "BehaviorTree/BlackboardComponent.h" +#include "BehaviorTree/Blackboard/BlackboardKeyType_Object.h" +#include "BehaviorTree/Blackboard/BlackboardKeyType_Vector.h" + +UBTTaskNodeJumpFence::UBTTaskNodeJumpFence() +{ + NodeName = "Jump Fence"; + bNotifyTick = true; + BlackboardKey.AddObjectFilter(this, GET_MEMBER_NAME_CHECKED(UBTTaskNodeJumpFence, BlackboardKey), AActor::StaticClass()); + BlackboardKey.AddVectorFilter(this, GET_MEMBER_NAME_CHECKED(UBTTaskNodeJumpFence, BlackboardKey)); +} +void UBTTaskNodeJumpFence::InitializeFromAsset(UBehaviorTree& asset) +{ + Super::InitializeFromAsset(asset); + UBlackboardData& BBAsset = *GetBlackboardAsset(); + BlackboardKey.ResolveSelectedKey(BBAsset); +} +EBTNodeResult::Type UBTTaskNodeJumpFence::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) +{ + EBTNodeResult::Type NodeResult = EBTNodeResult::InProgress; + NodeResult = PefromJumpFenceTask(OwnerComp, NodeMemory); + return NodeResult; +} +EBTNodeResult::Type UBTTaskNodeJumpFence::PefromJumpFenceTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) +{ + const UBlackboardComponent* MyBlackboard = OwnerComp.GetBlackboardComponent(); + AAIController* MyController = OwnerComp.GetAIOwner(); + AEnemyAIController* cont = Cast(OwnerComp.GetAIOwner()); + EBTNodeResult::Type NodeResult = EBTNodeResult::Failed; + if (cont && MyBlackboard) + { + const FVector TargetLocation = MyBlackboard->GetValue(BlackboardKey.GetSelectedKeyID()); + FVector TargetDirection = FVector::ZeroVector; + if (cont->JumpFence(TargetLocation)) + { + NodeResult = EBTNodeResult::Succeeded; + } + else NodeResult = EBTNodeResult::InProgress; + } + return NodeResult; + +} +void UBTTaskNodeJumpFence::TickTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds) +{ + const EBTNodeResult::Type NodeResult = PefromJumpFenceTask(OwnerComp, NodeMemory); + if (NodeResult != EBTNodeResult::InProgress) + { + FinishLatentTask(OwnerComp, NodeResult); + } +} \ No newline at end of file diff --git a/Source/UnrealProject/Creatures/BehaviorTree/BTTaskNodeJumpFence.h b/Source/UnrealProject/Creatures/BehaviorTree/BTTaskNodeJumpFence.h new file mode 100644 index 0000000..63c52b8 --- /dev/null +++ b/Source/UnrealProject/Creatures/BehaviorTree/BTTaskNodeJumpFence.h @@ -0,0 +1,25 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "BehaviorTree/BTTaskNode.h" +#include "BTTaskNodeJumpFence.generated.h" + +/** + * + */ +UCLASS() +class UNREALPROJECT_API UBTTaskNodeJumpFence : public UBTTaskNode +{ + GENERATED_BODY() +public: + UBTTaskNodeJumpFence(); + virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override; + virtual void TickTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds) override; + virtual void InitializeFromAsset(UBehaviorTree& Asset) override; + UPROPERTY(EditAnywhere, Category = Blackboard) + FBlackboardKeySelector BlackboardKey; +protected: + EBTNodeResult::Type PefromJumpFenceTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory); + +}; diff --git a/Source/UnrealProject/Creatures/BehaviorTree/EnemyAIController.cpp b/Source/UnrealProject/Creatures/BehaviorTree/EnemyAIController.cpp new file mode 100644 index 0000000..99a051b --- /dev/null +++ b/Source/UnrealProject/Creatures/BehaviorTree/EnemyAIController.cpp @@ -0,0 +1,178 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "EnemyAIController.h" +#include "EnemyBase.h" +#include "NetworkCharacter.h" +#include "Navigation/CrowdFollowingComponent.h" + +#include "BehaviorTree/BehaviorTree.h" +#include "BehaviorTree/BlackboardComponent.h" + +#include "BehaviorTree/BehaviorTreeComponent.h" +#include "BehaviorTree/BlackBoard/BlackboardKeyAllTypes.h" + +#include "DrawDebugHelpers.h" + +AEnemyAIController::AEnemyAIController(const FObjectInitializer& PCIP) + //: Super(PCIP.SetDefaultSubobjectClass(TEXT("PathFollowingComponent"))) +{ + blackBoardComp = CreateDefaultSubobject(FName("BlackBoardComp")); + behaviorTreeComp = CreateDefaultSubobject(FName("BehaviorTreeComp")); + climbSpeed = 1.0f; +} +void AEnemyAIController::Tick(float deltaTime) +{ + Super::Tick(deltaTime); + +} +void AEnemyAIController::Possess(APawn* pawn) +{ + Super::Possess(pawn); + AEnemyBase* enemy = Cast(pawn); + if (enemy &&enemy->treeBehavior) + { + if (!enemy->treeBehavior->BlackboardAsset) + { + RERROR("There is no blackboard asset in the behavior tree"); + return; + } + blackBoardComp->InitializeBlackboard(*(enemy->treeBehavior->BlackboardAsset)); + behaviorTreeComp->StartTree(*enemy->treeBehavior); + } +} +bool AEnemyAIController::VerticleMoveTo(FVector destination, FVector direction,float acceptRadius) +{ + FVector location =GetPawn()->GetActorLocation(); + FVector dest = destination; + dest -= location; + + dest *= direction; + FVector newDest= dest; + float height = newDest.Size(); + + + + if (height < acceptRadius&&destination.Z GetCharacterMovement()->GravityScale = 1; + + return true; + } + + GetPawn()->SetActorLocation(FVector(location.X+(direction.X*10), location.Y+(direction.Y*10) , (location.Z)+(direction.Z*10))); + GetCharacter()->GetCharacterMovement()->GravityScale = 0; + + return false; + +} +bool AEnemyAIController::JumpFence(FVector destination) +{ + TArray result; + UNavigationSystem* navsys = UNavigationSystem::GetCurrent(GetWorld()); + if (!navsys) + { + RERROR("There is no navigation system in the current level, please check if there is one or contact a developer!"); + return false; + } + ANPCBase* character = Cast(GetPawn()); + FVector direction; + FNavLocation navLocation; + character->SetActorEnableCollision(false); + DrawDebugLine(GetWorld(), GetPawn()->GetActorLocation(), GetPawn()->GetActorLocation() + GetActorUpVector()*-10000.0f, FColor(255, 0, 0),false, -1, 0, 12.333); + FCollisionQueryParams collisionParams; + collisionParams.AddIgnoredActor(this->GetPawn()); + GetWorld()->LineTraceMultiByChannel(result, GetPawn()->GetActorLocation(), GetPawn()->GetActorLocation() + GetActorUpVector()*-10000.0f, ECollisionChannel::ECC_WorldStatic, collisionParams); + for (int i = 0; i < result.Num(); i++) + { + + if(result[i].ImpactPoint!=GetPawn()->GetActorLocation()) + { + if (!navsys->ProjectPointToNavigation(GetPawn()->GetActorLocation(), navLocation)) + { + continue; + } + GetCharacter()->GetCharacterMovement()->GravityScale = 1; + SetBool(false,FName("ClimbKey")); + navsys->SimpleMoveToLocation(this,destination); + character->PlayNormalAnimation(); + character->SetActorEnableCollision(true); + return true; + } + } + character->PlayFencAnimation(); + direction = destination - GetPawn()->GetActorLocation(); + direction.Normalize(); + GetPawn()->SetActorLocation(GetPawn()->GetActorLocation()+(direction * 10)); + return false; +} +void AEnemyAIController::BeginInactiveState() +{ + Super::BeginInactiveState(); +} +void AEnemyAIController::SetVector(FVector vector, FName name) +{ + if (blackBoardComp) + { + blackBoardComp->SetValueAsVector(name, (vector)); + } + +} +void AEnemyAIController::SetBool(bool boolean, FName name) +{ + if (blackBoardComp) + { + blackBoardComp->SetValueAsBool(name, (boolean)); + } + +} +void AEnemyAIController::SetTarget(ANetworkCharacter* player) +{ + if (blackBoardComp) + { + blackBoardComp->SetValueAsObject((FName("Target")), (player)); + } +} +ANetworkCharacter* AEnemyAIController::GetTarget() +{ + if (blackBoardComp) + { + return Cast(blackBoardComp->GetValue(FName("Target"))); + } + return nullptr; + +} + +FVector AEnemyAIController::GetLocationTarget() +{ + if (blackBoardComp) + { + return FVector(blackBoardComp->GetValue(FName("TargetLocation"))); + } + return FVector(); +} + +FVector AEnemyAIController::GetHomeLocation() +{ + if (blackBoardComp) + { + return FVector(blackBoardComp->GetValue(FName("HomeLocation"))); + } + return FVector(); +} +FVector AEnemyAIController::GetSelfLocation() +{ + if (blackBoardComp) + { + return FVector(blackBoardComp->GetValue(FName("SelfLocation"))); + } + return FVector(); +} +bool AEnemyAIController::GetClimbKey() +{ + if (blackBoardComp) + { + return blackBoardComp->GetValue((FName("ClimbKey"))); + } + else return false; +} \ No newline at end of file diff --git a/Source/UnrealProject/Creatures/BehaviorTree/EnemyAIController.h b/Source/UnrealProject/Creatures/BehaviorTree/EnemyAIController.h new file mode 100644 index 0000000..531c0c5 --- /dev/null +++ b/Source/UnrealProject/Creatures/BehaviorTree/EnemyAIController.h @@ -0,0 +1,56 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "AIController.h" +#include "EnemyAIController.generated.h" + +/** +* +*/ +UCLASS() +class UNREALPROJECT_API AEnemyAIController : public AAIController +{ + GENERATED_BODY() + +public: + AEnemyAIController(const FObjectInitializer& PCIP); + UPROPERTY(transient) + class UBlackboardComponent* blackBoardComp; + + UPROPERTY(transient) + class UBehaviorTreeComponent* behaviorTreeComp; + UPROPERTY(transient) + float climbSpeed; + virtual void Tick(float deltaTime)override; + virtual void Possess(APawn* pawn) override; + virtual void BeginInactiveState() override; + bool VerticleMoveTo(FVector destination,FVector direction, float acceptRadius); + bool JumpFence(FVector destination); + + + + UFUNCTION(BlueprintCallable, Category = Behavior) + void SetVector(FVector vector, FName name); + UFUNCTION(BlueprintCallable, Category = Behavior) + void SetBool(bool boolean, FName name); + UFUNCTION(BlueprintCallable, Category = Behavior) + void SetTarget(class ANetworkCharacter* player); + UFUNCTION(BlueprintCallable, Category = Behavior) + class ANetworkCharacter* GetTarget(); + UFUNCTION(BlueprintCallable, Category = Behavior) + FVector GetLocationTarget(); + UFUNCTION(BlueprintCallable, Category = Behavior) + FVector GetHomeLocation(); + UFUNCTION(BlueprintCallable, Category = Behavior) + FVector GetSelfLocation(); + // UFUNCTION(BlueprintCallable, Category = Behavior) + // bool GetEnableTree(); + UFUNCTION(BlueprintCallable, Category = Behavior) + bool GetClimbKey(); +protected: + uint8 targetkey; + + +}; + diff --git a/Source/UnrealProject/Creatures/BossBase.cpp b/Source/UnrealProject/Creatures/BossBase.cpp new file mode 100644 index 0000000..54b6560 --- /dev/null +++ b/Source/UnrealProject/Creatures/BossBase.cpp @@ -0,0 +1,96 @@ + // Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "BossBase.h" +#include "MusicPlayer.h" +#include "SpawnerBase.h" +#include "NetworkPlayer.h" +#include "KingOfTheHillGameMode.h" +#include "KOTHBossSpawner.h" + + +ABossBase::ABossBase() +{ + PrimaryActorTick.bCanEverTick = true; + +} + +void ABossBase::BeginPlay() +{ + Super::BeginPlay(); + + m_usesMana = false; + m_musicPlayer = nullptr; + + // Search for a music player in the world. + for (TActorIterator actorItr(GetWorld()); actorItr; ++actorItr) + { + m_musicPlayer = *actorItr; + break; + } + + if (Role != ROLE_Authority) + return; +} +void ABossBase::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + Super::EndPlay(EndPlayReason); + + if (Role != ROLE_Authority) + return; + + // Stop playing boss music. + if (m_musicPlayer != nullptr) + m_musicPlayer->SetInCombat(false); + +} +void ABossBase::Tick(float deltaTime) +{ + Super::Tick(deltaTime); + if (Role != ROLE_Authority) + return; + + if((!IsPendingKill() || !IsPendingKill()) && GetTeam() < 5) + { + AKOTHBossSpawner* bSpawner = Cast(m_spawn); + AKingOfTheHillGameMode* kothMode = Cast(GetWorld()->GetAuthGameMode()); + if(kothMode&&bSpawner) + kothMode->AddScore(GetTeam(), deltaTime); + } +} + + +void ABossBase::m_Engaged() +{ + // Set music to combat music. + if (m_musicPlayer != nullptr) + m_musicPlayer->SetInCombat(true); +} +void ABossBase::m_Disengaged() +{ + // Set music to regular music. + if (m_musicPlayer != nullptr) + m_musicPlayer->SetInCombat(false); +} +void ABossBase::NativeOnKilled(class ANetworkCharacter* killer, class UAbilityInfo* ability) +{ + Super::NativeOnKilled(killer, ability); + if (Role != ROLE_Authority) + return; + if (m_spawn &&m_spawn->possesable) + { + // m_spawn->CaptureCamp(killer->GetTeam()); + //AKOTHBossSpawner* bSpawner = Cast(m_spawn); + //AKingOfTheHillGameMode* kothMode = Cast(GetWorld()->GetAuthGameMode()); + //if (kothMode&&bSpawner) + //{ + /* for (TActorIteratoractorIt(GetWorld()); actorIt; ++actorIt) + { + + ASpawnerBase *spawn = *actorIt; + if(!spawn->possesable) + spawn->SetTeam(killer->GetTeam()); + } + }*/ + } +} \ No newline at end of file diff --git a/Source/UnrealProject/Creatures/BossBase.h b/Source/UnrealProject/Creatures/BossBase.h new file mode 100644 index 0000000..4e9d96f --- /dev/null +++ b/Source/UnrealProject/Creatures/BossBase.h @@ -0,0 +1,28 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "Creatures/NPCBase.h" +#include "BossBase.generated.h" + +/** + * + */ +UCLASS() +class UNREALPROJECT_API ABossBase : public ANPCBase +{ + GENERATED_BODY() +public: + ABossBase(); + virtual void BeginPlay() override; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + virtual void Tick(float deltaTime) override; + virtual void NativeOnKilled(class ANetworkCharacter* killer, class UAbilityInfo* ability) override; +protected: + virtual void m_Engaged(); + virtual void m_Disengaged(); + float timer; +private: + UPROPERTY() + class AMusicPlayer* m_musicPlayer; +}; diff --git a/Source/UnrealProject/Creatures/CharacterBase.cpp b/Source/UnrealProject/Creatures/CharacterBase.cpp new file mode 100644 index 0000000..6237a49 --- /dev/null +++ b/Source/UnrealProject/Creatures/CharacterBase.cpp @@ -0,0 +1,300 @@ +#include "UnrealProject.h" +#include "SpawnerBase.h" +#include "CharacterBase.h" +#include "ItemBase.h" +#include "BaseSkillObject.h" +#include "WiebertAnimation.h" +#include "DefaultGameState.h" +#include "MainCharacterAnimation.h" +#include "SkillTreeState.h" + + +ACharacterBase::ACharacterBase() +{ + m_team = 0; + bReplicates = true; + + visionRadius = 1000; +} + +void ACharacterBase::BeginPlay() +{ + Super::BeginPlay(); + + // If this actor is attached to a spawner base, it must be ignored + AActor* const parent = GetAttachParentActor(); + if (parent != nullptr && parent->IsA()) + return; + + // Register to the minimap + UWorld* const world = GetWorld(); + if (IsValid(world)) + { + ADefaultGameState* const gameState = Cast(world->GetGameState()); + if (IsValid(gameState)) + { + const FVector location = GetActorLocation(); + minimapHandle.position = FVector2D(location.X, location.Y); + minimapHandle.character = this; + gameState->minimapQuadtree.AddObject(minimapHandle); + } + } +} +void ACharacterBase::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + Super::EndPlay(EndPlayReason); + + // Unregister to the minimap + ADefaultGameState* const gameState = Cast(GetWorld()->GetGameState()); + if (IsValid(gameState) && minimapHandle.root) + gameState->minimapQuadtree.RemoveObject(minimapHandle); + + minimapHandle.root = nullptr; + minimapHandle.node = nullptr; + minimapHandle.character = nullptr; +} +void ACharacterBase::Tick(float deltaTime) +{ + Super::Tick(deltaTime); + + const FVector location = GetActorLocation(); + minimapHandle.position = FVector2D(location.X, location.Y); +} + +void ACharacterBase::m_EquipSkills_Implementation(const TArray& skills) +{ + for(UBaseSkillObject* skill : skills) + { + // Check if not yet equiped + if(!m_equipedSkills.Contains(skill)) + { + AItemBase::CreateItemsFromSkill(GetWorld(), skill, this); + m_equipedSkills.Add(skill); + } + } +} + +void ACharacterBase::EquipSkills(const TArray& skills) +{ + m_EquipSkills(skills); +} +void ACharacterBase::EquipSkillsFromSkillTreeState(const struct FSkillTreeState& skState) +{ + TArray skillObjects; + for(const FSkillTreeStateObject& obj : skState.objects) + { + UBaseSkillObject* skillObj = obj.skillObject->GetDefaultObject(); + if(!skillObj) + continue; + skillObjects.Add(skillObj); + } + m_EquipSkills(skillObjects); +} +void ACharacterBase::EquipItems_Implementation(const TArray>& itemClasses) +{ + for(auto& itemClass : itemClasses) + { + EquipItem_Implementation(itemClass); + } +} +void ACharacterBase::EquipItem_Implementation(TSubclassOf itemClass) +{ + if(itemClass == nullptr) + return; + + AItemBase* item = GetWorld()->SpawnActor(itemClass); + AItemBase* existing = AItemBase::CheckSlotOccupied(item->type, this); + if(existing) + existing->Destroy(); + if(item) + { + if(!item->AttachItemToCharacter(this)) + { + item->Destroy(); + } + } +} + +void ACharacterBase::SendInitialAppearance(APlayerController* pc) +{ + if(Role == ROLE_Authority) + { + m_ReceiveInitialAppearance(pc, GetEquipedSkills(), GetCharacterCustomization()); + } +} + +void ACharacterBase::m_ReceiveInitialAppearance_Implementation(APlayerController* pc, const TArray& itemClasses, const FCharacterCustomization& cc) +{ + APlayerController* localPC = GetGameInstance()->GetFirstLocalPlayerController(); + if(pc == localPC) + { + EquipSkills(itemClasses); + SetCustomizations_Implementation(cc); + } +} + +void ACharacterBase::Destroyed() +{ + auto destroyItems = m_items; + for(auto item : destroyItems) + { + if(IsValid(item)) + { + item->Destroy(); + } + } + Super::Destroyed(); +} + +TArray> ACharacterBase::GetEquipedItemClasses() const +{ + TArray> ret; + for(auto item : m_items) + { + ret.Add(item->GetClass()); + } + return ret; +} +const TArray& ACharacterBase::GetEquipedItems() const +{ + return m_items; +} + +const FCharacterCustomization& ACharacterBase::GetCharacterCustomization() const +{ + static FCharacterCustomization dummy; + USkeletalMeshComponent* skMesh = GetMesh(); + if(skMesh) + { + UWiebertAnimation* animBP = Cast(skMesh->GetAnimInstance()); + if(animBP) + { + return animBP->charCustomization; + } + else + { + UMainCharacterAnimation* charBP = Cast(skMesh->GetAnimInstance()); + if (charBP) + { + return charBP->charCustomization; + } + } + } + return dummy; +} + +const TArray& ACharacterBase::GetEquipedSkills() const +{ + return m_equipedSkills; +} + +void ACharacterBase::UnequipSkills_Implementation(const TArray& skills) +{ + for(auto skill : skills) + { + if(m_equipedSkills.Contains(skill)) + { + TArray items; + m_mappedItems.MultiFind(skill, items); + for(auto item : items) + item->Destroy(); + m_equipedSkills.Remove(skill); + } + } +} + +void ACharacterBase::ClearEquipment_Implementation() +{ + auto clearItems = m_items; + for(auto item : clearItems) + { + if(IsValid(item)) + { + item->Destroy(); + } + } + m_mappedItems.Reset(); + m_equipedSkills.SetNum(0); + m_items.SetNum(0); +} +void ACharacterBase::SetCustomizations_Implementation(const FCharacterCustomization& cc) +{ + USkeletalMeshComponent* skMesh = GetMesh(); + if(skMesh) + { + UWiebertAnimation* animBP = Cast(skMesh->GetAnimInstance()); + if(animBP) + { + animBP->SetCharacterCustomization(cc); + } + else + { + UMainCharacterAnimation* charBP = Cast(skMesh->GetAnimInstance()); + if (charBP) + { + charBP->SetCharacterCustomization(cc); + } + } + } + else + { + GERROR("Can't set customization, no skeletal mesh on character"); + } +} + +void ACharacterBase::m_OnItemAdded(AItemBase* item) +{ + // Add item to list of items assigned to given skill object + if(item->skillObject) + m_mappedItems.Add(item->skillObject, item); + m_items.Add(item); +} +void ACharacterBase::m_OnItemRemoved(AItemBase* item) +{ + // Remove mapped item + m_mappedItems.RemoveSingle(item->skillObject, item); + m_items.Remove(item); +} + +void ACharacterBase::m_OnRep_Team() +{ + +} + +int32 ACharacterBase::GetTeam() const +{ + return m_team; +} +void ACharacterBase::SetTeam(int32 team) +{ + check(Role == ROLE_Authority); // Server only call + m_team = team; + m_OnRep_Team(); +} + +void ACharacterBase::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(ACharacterBase, m_team); + DOREPLIFETIME(ACharacterBase, playerName); +} + +void ACharacterBase::RegisterEffect(class AEffect* effect) +{ + if(effect != nullptr) + m_effects.Add(effect); +} +void ACharacterBase::UnRegisterEffect(class AEffect* effect) +{ + if(effect == nullptr) + { + FWARNING("invalid effect is unregistering"); + return; + } + m_effects.Remove(effect); +} +TArray ACharacterBase::GetEffects() +{ + return m_effects; +} \ No newline at end of file diff --git a/Source/UnrealProject/Creatures/CharacterBase.h b/Source/UnrealProject/Creatures/CharacterBase.h new file mode 100644 index 0000000..2faa58e --- /dev/null +++ b/Source/UnrealProject/Creatures/CharacterBase.h @@ -0,0 +1,94 @@ +#pragma once +#include "GameFramework/Character.h" +#include "PlayerSetupState.h" +#include "MiniMap.h" +#include "CharacterBase.generated.h" + +UCLASS() +class ACharacterBase : public ACharacter +{ + GENERATED_BODY() + friend class AItemBase; +public: + ACharacterBase(); + + virtual void BeginPlay() override; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + virtual void Tick(float deltaTime) override; + + UFUNCTION(BlueprintCallable, Category = "Appearance") + void EquipSkills(const TArray& skills); + UFUNCTION(BlueprintCallable, Category = "Appearance") + void EquipSkillsFromSkillTreeState(const struct FSkillTreeState& skState); + UFUNCTION(NetMulticast, Reliable) + void EquipItems(const TArray>& itemClasses); + UFUNCTION(NetMulticast, Reliable) + void EquipItem(TSubclassOf itemClasses); + + void SendInitialAppearance(APlayerController* pc); + + void Destroyed() override; + + UFUNCTION(BlueprintCallable, NetMulticast, Reliable, Category = "Appearance") + void UnequipSkills(const TArray& skills); + UFUNCTION(BlueprintCallable, NetMulticast, Reliable, Category = "Appearance") + void ClearEquipment(); + UFUNCTION(BlueprintCallable, NetMulticast, Reliable, Category = "Appearance") + void SetCustomizations(const FCharacterCustomization& cc); + + UFUNCTION(BlueprintCallable, Category = "Appearance") + const TArray& GetEquipedSkills() const; + UFUNCTION(BlueprintCallable, Category = "Appearance") + const TArray& GetEquipedItems() const; + UFUNCTION(BlueprintCallable, Category = "Appearance") + TArray> GetEquipedItemClasses() const; + UFUNCTION(BlueprintCallable, Category = "Appearance") + const FCharacterCustomization& GetCharacterCustomization() const; + + UFUNCTION(BlueprintCallable, Category = "Team") + int32 GetTeam() const; + UFUNCTION(BlueprintCallable, Category = "Team") + virtual void SetTeam(int32 team); + + // Tracking functions for effects on characters + UFUNCTION(BlueprintCallable, Category = "Effect") + void RegisterEffect(class AEffect* effect); + UFUNCTION(BlueprintCallable, Category = "Effect") + void UnRegisterEffect(class AEffect* effect); + UFUNCTION(BlueprintCallable, Category = "Effect") + TArray GetEffects(); + + // The name of the player that controls this unit (Only for ghosts, players and illusions) + UPROPERTY(BlueprintReadOnly, Replicated) + FString playerName; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Vision") + float visionRadius; + + MiniMap::MinimapHandle minimapHandle; + +private: + // Trigerred from RequestInitialAppearance send back to the client + UFUNCTION(NetMulticast, Reliable) + void m_ReceiveInitialAppearance(APlayerController* pc, const TArray& itemClasses, const FCharacterCustomization& cc); + // Internal usage EquipSkills to allow multiple ways to call this function from blueprints + UFUNCTION(NetMulticast, Reliable) + void m_EquipSkills(const TArray& skills); + void m_OnItemAdded(AItemBase* item); + void m_OnItemRemoved(AItemBase* item); + +protected: + UFUNCTION() + virtual void m_OnRep_Team(); + + UPROPERTY(ReplicatedUsing=m_OnRep_Team) + int32 m_team; + + // Maps skills to the items it spawned + TMultiMap m_mappedItems; + TArray m_equipedSkills; + UPROPERTY() + TArray m_items; + + TArray m_effects; +}; diff --git a/Source/UnrealProject/Creatures/EnemyBase.cpp b/Source/UnrealProject/Creatures/EnemyBase.cpp new file mode 100644 index 0000000..fb31bc9 --- /dev/null +++ b/Source/UnrealProject/Creatures/EnemyBase.cpp @@ -0,0 +1,190 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "SpawnerBase.h" +#include "EnemyBase.h" +#include "AbilityInfo.h" +#include "NetworkPlayer.h" +#include "GeneralEnemy.h" +#include "BehaviorTree/EnemyAIController.h" +#include "BehaviorTree/BehaviorTree.h" +#include "MinionAnimInstance.h" + +AEnemyBase::AEnemyBase() +{ + PrimaryActorTick.bCanEverTick = true; + finnishedAttack = false; + informGeneralTime = 2.0f; + m_tempTarget = nullptr; + // AIControllerClass = AEnemyAIController::StaticClass(); +} +void AEnemyBase::BeginPlay() +{ + Super::BeginPlay(); + + specialAbilityChance = m_ReCalcChance(specialAbilityChance); + enemycontroller = Cast(GetController()); + if (enemycontroller && m_spawn) + { + enemycontroller->SetBool(true, FName("ClimbKey")); + enemycontroller->SetVector(FVector(0, 0, 1), FName("ClimbDirection")); + enemycontroller->SetVector(m_spawn->GetActorLocation() + FVector(0, 0, 100), FName("ClimbLocation")); + + } + else + { + + FActorSpawnParameters SpawnInfo; + SpawnInfo.Instigator = Instigator; + SpawnInfo.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; + SpawnInfo.OverrideLevel = GetLevel(); + SpawnInfo.ObjectFlags |= RF_Transient; // We never want to save AI controllers into a map + AController* NewController = GetWorld()->SpawnActor(AEnemyAIController::StaticClass(), GetActorLocation(), GetActorRotation(), SpawnInfo); + if (NewController != NULL) + { + // if successful will result in setting this->Controller + // as part of possession mechanics + NewController->Possess(this); + } + enemycontroller = Cast(GetController()); + if (enemycontroller && m_spawn) + { + if (m_spawn->SpawnLocation.Num() > 0) + { + PlayClimbingAnimation(); + enemycontroller->SetBool(true, FName("ClimbKey")); + enemycontroller->SetVector(FVector(0, 0, 1), FName("ClimbDirection")); + enemycontroller->SetVector(m_spawn->GetActorLocation() + FVector(0, 0, 100), FName("ClimbLocation")); + } + } + else + { + // RERROR("There is no valid controller on this enemy"); + } + } + hasSpawned = false; + +} +void AEnemyBase::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + Super::EndPlay(EndPlayReason); + general = nullptr; + if (m_spawn) + { + + if (m_spawn->hasGeneral) + { + m_spawn->formationEnemies.Remove(this); + } + } +} + +void AEnemyBase::Tick(float deltaTime) +{ + Super::Tick(deltaTime); + if (Role != ROLE_Authority) + { + return; + } + m_confusionTimer -= deltaTime; + if (!IsA(AGeneralEnemy::StaticClass())) + m_hasGeneral = hasGeneral; + if (!hasGeneral && enemycontroller && enemycontroller->IsA(AEnemyAIController::StaticClass())) + { + + enemycontroller->SetBool(false, FName("EnableTree")); + } + + if (IsValid(target)) + { + if (hasGeneral) + { + m_informGeneralTimer -= deltaTime; + if (m_informGeneralTimer < 0.0f&&IsValid(general)) + InformGeneral(); + } + else + { + SwitchAgro(); + } + } + else if(!hasGeneral) + { + FVector randpos = FMath::VRand(); + randpos.Z = 0.0f; + float randomRange = FMath::FRandRange(m_spawn->aggroRadius / 4, m_spawn->aggroRadius); + + randpos = randpos*randomRange; + + MoveToPointSlow(randpos + m_spawn->GetActorLocation(), 50.0f); + } +} +void AEnemyBase::InformGeneral() +{ + if (!IsValid(general) || !IsValid(target)) + return; + if (FVector::DistSquared(m_spawn->GetActorLocation(), target->GetActorLocation()) > m_spawn->deaggroRadius*m_spawn->deaggroRadius) + return; + if( !IsValid(general->target)) + { + general->target = m_tempTarget; + ChangeNPCAnimation((uint8)EMinionAnimState::MAS_Pointing); + //CHANGEANIMATION(EMinionAnimState::MAS_Pointing); + + m_informGeneralTimer = informGeneralTime; + m_tempTarget = nullptr; + } +} +void AEnemyBase::SwitchAgro() +{ + if (m_spawn->GetNearbyPlayers().Num() <= 1) + return; + float mostDamage =0.0f; + ANetworkCharacter* character = nullptr; + for (int i = 0; i < m_spawn->GetNearbyPlayers().Num(); i++) + { + if (m_spawn->GetNearbyPlayers()[i]->GetTotalDamage()>mostDamage) + { + mostDamage = m_spawn->GetNearbyPlayers()[i]->GetTotalDamage(); + character = m_spawn->GetNearbyPlayers()[i]; + } + } + if(IsValid(character)) + this->target = character; +} +void AEnemyBase::ConfusionBehavior() +{ + FVector randpos = FMath::VRand(); + randpos.Z = 0.0f; + float randomRange = FMath::FRandRange(m_spawn->aggroRadius / 4, m_spawn->aggroRadius); + + randpos = randpos*randomRange; + + MoveToPointSlow(randpos + m_spawn->GetActorLocation(), 50.0f); +} +void AEnemyBase::ChargeBehavior() +{ + +} +void AEnemyBase::OffensiveBehavior() +{ + + +} +void AEnemyBase::RangedBehavior() +{ +} +void AEnemyBase::ResetConfusionTimer(float confusionTime) +{ + m_confusionTimer = confusionTime; +} +float AEnemyBase::m_ReCalcChance(float chance) +{ + return (1000 - (chance * 10)); +} +int32 AEnemyBase::NativeDealDamage(class ANetworkCharacter* dealer, int32 damage, float armorPercentageIgnore, class UAbilityInfo* ability) +{ + m_tempTarget = dealer; + m_informGeneralTimer = informGeneralTime; + return Super::NativeDealDamage(dealer, damage, armorPercentageIgnore, ability); +} \ No newline at end of file diff --git a/Source/UnrealProject/Creatures/EnemyBase.h b/Source/UnrealProject/Creatures/EnemyBase.h new file mode 100644 index 0000000..2582df1 --- /dev/null +++ b/Source/UnrealProject/Creatures/EnemyBase.h @@ -0,0 +1,60 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "Creatures/NPCBase.h" +#include "EnemyBase.generated.h" + +/** + * + */ + +UCLASS() +class UNREALPROJECT_API AEnemyBase : public ANPCBase +{ + GENERATED_BODY() +public: + AEnemyBase(); + virtual void BeginPlay() override; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + virtual void Tick(float deltaTime) override; + virtual int32 NativeDealDamage(class ANetworkCharacter* dealer, int32 damage, float armorPercentageIgnore, class UAbilityInfo* ability) override; + + void OffensiveBehavior(); + void RangedBehavior(); + void ChargeBehavior(); + void InformGeneral(); + void SwitchAgro(); + void ConfusionBehavior(); + void ResetConfusionTimer(float confusionTime); + UPROPERTY(EditAnywhere, Category = Behavior) + class UBehaviorTree* treeBehavior; + UPROPERTY(EditAnywhere, Category = "Attack Settings") + float informGeneralTime; + UPROPERTY(EditAnywhere, Category = "Ability Settings") + class UAbilityInfo* specialAbility; + UPROPERTY(EditAnywhere, Category = "Ability Settings") + class UAbilityInfo* meleeAbility; + UPROPERTY(EditAnywhere, Category = "Ability Settings") + class UAbilityInfo* rangedAbility; + UPROPERTY(EditAnywhere, Category = "Ability Settings") + float specialAbilityChance; + FVector scatterLocation; + bool hasGeneral; + bool finnishedAttack; + bool hasSpawned; + UPROPERTY() + class AEnemyAIController* enemycontroller; + UPROPERTY() + class AGeneralEnemy* general; + UPROPERTY() + ANetworkCharacter* m_tempTarget; +protected: + float m_ReCalcChance(float chance); + bool m_rotateToPlayer; + float m_lastPercentage; + class UAbilityInfo* m_tempAbility; + float m_informGeneralTimer; + float m_confusionTimer; + +}; diff --git a/Source/UnrealProject/Creatures/GeneralEnemy.cpp b/Source/UnrealProject/Creatures/GeneralEnemy.cpp new file mode 100644 index 0000000..e8b6c34 --- /dev/null +++ b/Source/UnrealProject/Creatures/GeneralEnemy.cpp @@ -0,0 +1,237 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "GeneralEnemy.h" +#include "SpawnerBase.h" +#include "BehaviorTree/EnemyAIController.h" +#include "NetworkPlayer.h" +#include "OffensiveEnemy.h" +#include "MinionAnimInstance.h" + +AGeneralEnemy::AGeneralEnemy() +{ + + PrimaryActorTick.bCanEverTick = true; +} +void AGeneralEnemy::BeginPlay() +{ + Super::BeginPlay(); + if (Role != ROLE_Authority) + return; + if (m_spawn) + { + m_spawn->hasGeneral = true; + FVector addVector; + for (int i = 0; i < 4; i++) + { + addVector = m_spawn->GetActorForwardVector().RotateAngleAxis(i * 90, FVector::UpVector); + patrolLocations.Add(addVector); + } + } + if (distance == 0) + { + distance = 100.0f; + } +/* for (int i = 0; i < formationEnemies.Num(); i++) + { + formationEnemies[i]->general = this; + }*/ + + +} +void AGeneralEnemy::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + Super::EndPlay(EndPlayReason); + if (Role != ROLE_Authority) + return; + for (int i = 0; i < formationEnemies.Num(); i++) + { + formationEnemies[i]->ResetConfusionTimer(3.0f); + formationEnemies[i]->general = nullptr; + } + if (m_spawn != nullptr) + m_spawn->hasGeneral = false; + //formationEnemies.Empty(); +} +void AGeneralEnemy::Tick(float deltaTime) +{ + Super::Tick(deltaTime); + if (Role != ROLE_Authority) + return; + if (!IsValid(m_spawn)) + { + RERROR("There is no spawn for the general enemy"); + return; + } + for (int i = 0; i < formationEnemies.Num(); i++) + { + if (IsValid(formationEnemies[i]->general)) + continue; + formationEnemies[i]->general = this; + + } + + if (IsValid(target)) + { + + + FVector2D locationVec = FVector2D(target->GetActorLocation()) - FVector2D(GetActorLocation()); + locationVec.Normalize(); + if (FVector::DistSquaredXY(GetActorLocation(), target->GetActorLocation()) > m_spawn->formationDistance* m_spawn->formationDistance) + { + + MoveToPoint((target->GetActorLocation()));// +(FVector(locationVec, 0))* m_spawn->formationDistance)); + DefaultFormation(); + + } + else + { + GetCharacterMovement()->Velocity = FVector::ZeroVector; + MoveToPoint(GetActorLocation()); + GetCharacterMovement()->Velocity = FVector::ZeroVector; + SetActorRotation(FVector(target->GetActorLocation() - GetActorLocation()).Rotation()); + ChargeFormation(); + } + + } + else + { + IdleFormation(deltaTime); + } +} +void AGeneralEnemy::SingleTargetFormation(int enemyNum) +{ + if ((formationEnemies[enemyNum]->enemycontroller)->IsA(AEnemyAIController::StaticClass())) + { + formationEnemies[enemyNum]->target = target; + formationEnemies[enemyNum]->enemycontroller->SetBool(true, FName("EnableTree")); + formationEnemies[enemyNum]->SetActorRotation(FVector(target->GetActorLocation() - formationEnemies[enemyNum]->GetActorLocation()).Rotation()); + if (count < 3) + { + formationEnemies[enemyNum]->enemycontroller->SetBool(false, FName("GuardKey")); + formationEnemies[enemyNum]->enemycontroller->SetVector(target->GetActorLocation() + target->GetActorForwardVector() * 200, FName("FrontLoc")); + formationEnemies[enemyNum]->enemycontroller->SetVector(target->GetActorLocation() + target->GetActorForwardVector() * -200, FName("BacktLoc")); + formationEnemies[enemyNum]->enemycontroller->SetVector(target->GetActorLocation() + target->GetActorRightVector() *-200, FName("LeftLoc")); + formationEnemies[enemyNum]->enemycontroller->SetVector(target->GetActorLocation() + target->GetActorRightVector() * 200, FName("RightLoc")); + } + else + { + formationEnemies[enemyNum]->enemycontroller->SetBool(true, FName("GuardKey")); + formationEnemies[enemyNum]->enemycontroller->SetVector(GetActorLocation() + GetActorForwardVector() * 100, FName("HomeLocation")); + } + count++; + } + +} +void AGeneralEnemy::ChargeFormation() +{ + if (!IsValid(target)) + return; + count = 0; + TArray targetArray = m_spawn->GetNearbyPlayers(); + if (targetArray.Num() == 0) + return; + for (int i = 0; i < formationEnemies.Num(); i++) + { + if (targetArray.Num() == 1) + { + SingleTargetFormation(i); + continue; + } + if (Cast(formationEnemies[i])) + { + formationEnemies[i]->target = m_spawn->GetClosestPlayer(); + formationEnemies[i]->MoveToPoint(target->GetActorLocation()); + } + else + { + ANetworkPlayer* newTarget = nullptr; + for (int j = 0; j < targetArray.Num(); j++) + { + if(targetArray[j]==m_spawn->GetClosestPlayer()) + continue; + newTarget = targetArray[j]; + continue; + } + formationEnemies[i]->target = newTarget; + } + } +} +void AGeneralEnemy::DefaultFormation() +{ + FVector rotatedFormation; + for (int i = 0; i < formationEnemies.Num(); i++) + { + if (!IsValid(formationEnemies[i]) || !IsValid(m_spawn) || (m_spawn->formationPoints.Num() + 1) < formationEnemies.Num()) + { + continue; + } + + + formationEnemies[i]->target = target; + rotatedFormation = GetActorRotation().RotateVector(FVector(m_spawn->formationPoints[i + 1].X - m_spawn->formationPoints[0].X, m_spawn->formationPoints[i + 1].Y - m_spawn->formationPoints[0].Y, 0)); + + formationEnemies[i]->MoveToPoint(GetActorLocation() + rotatedFormation); + formationEnemies[i]->SetActorRotation(FRotator(0, m_spawn->formationRotation[i + 1], 0) + (GetActorRotation())); + + formationEnemies[i]->enemycontroller->SetBool(false, FName("EnableTree")); + formationEnemies[i]->enemycontroller->SetVector(formationEnemies[i]->GetActorLocation(), FName("HomeLocation")); + + + } +} +int AGeneralEnemy::NativeDealDamage(class ANetworkCharacter* dealer, int32 damage, float armorPercentageIgnore, class UAbilityInfo* ability) +{ + //CHANGEANIMATION(EMinionAnimState::MAS_Pointing); + ChangeNPCAnimation((uint8)EMinionAnimState::MAS_Pointing); + return Super::NativeDealDamage(dealer, damage, armorPercentageIgnore, ability); +} +void AGeneralEnemy::NativeOnKilled(class ANetworkCharacter* killer, class UAbilityInfo* ability) +{ + Super::NativeOnKilled(killer, ability); + if (Role != ROLE_Authority) + return; + if (m_spawn &&m_spawn->possesable) + { + int32 team = killer->GetTeam(); + m_spawn->CaptureCamp(team); + + } +} +void AGeneralEnemy::IdleFormation(float deltaTime) +{ + // Move to a random point near the spawn point; + + //controll enemies in the formation + int counter = 0; + for (int i = 0; i < formationEnemies.Num(); i++) + { + if (!IsValid(formationEnemies[i]) || !IsValid(m_spawn) || (m_spawn->formationPoints.Num() + 1) < formationEnemies.Num()) + { + continue; + } + + if (!formationEnemies[i]->m_tempTarget) + { + if (Cast(formationEnemies[i])&&counter<4) + { + formationEnemies[i]->PatrolBetweenLocations (patrolLocations[counter].RotateAngleAxis( 90, FVector::UpVector)*500+ patrolLocations[counter]* 500 + m_spawn->GetActorLocation(), patrolLocations[counter].RotateAngleAxis( 270, FVector::UpVector) * 500 + patrolLocations[counter] * 500 + m_spawn->GetActorLocation(), deltaTime); + counter++; + continue; + } + + formationEnemies[i]->MoveToPoint(FVector(m_spawn->formationPoints[i + 1],0)+m_spawn->GetActorLocation()); + //formationEnemies[i]->MoveToPointSlow(randpos + m_spawn->GetActorLocation(), 50.0f); + + + } + else + { + if(formationEnemies[i]->IsA(AOffensiveEnemy::StaticClass())) + Cast(formationEnemies[i])->BasicBehavior(); + if(formationEnemies[i]->IsA(ARangedEnemy::StaticClass())) + Cast(formationEnemies[i])->BasicBehavior(); + } + formationEnemies[i]->target = nullptr; + } +} \ No newline at end of file diff --git a/Source/UnrealProject/Creatures/GeneralEnemy.h b/Source/UnrealProject/Creatures/GeneralEnemy.h new file mode 100644 index 0000000..539db51 --- /dev/null +++ b/Source/UnrealProject/Creatures/GeneralEnemy.h @@ -0,0 +1,40 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "Creatures/RangedEnemy.h" +#include "GeneralEnemy.generated.h" + +/** + * + */ +UCLASS() +class UNREALPROJECT_API AGeneralEnemy : public ARangedEnemy +{ + GENERATED_BODY() + + + public: + AGeneralEnemy(); + virtual void BeginPlay() override; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + virtual void Tick(float deltaTime) override; + virtual void NativeOnKilled(class ANetworkCharacter* killer, class UAbilityInfo* ability) override; + virtual int NativeDealDamage(class ANetworkCharacter* dealer, int32 damage, float armorPercentageIgnore, class UAbilityInfo* ability)override; + void IdleFormation(float deltaTime); + void ChargeFormation(); + void DefaultFormation(); + void SingleTargetFormation(int enemyNum ); + TArray formationPoints; + TArray patrolLocations; + UPROPERTY() + TArray formationEnemies; + UPROPERTY(EditAnywhere, Category = "General settings") + float distance; + UPROPERTY(EditAnywhere, Category = "General settings") + float oFormationRadius; + private: + int count; + + +}; diff --git a/Source/UnrealProject/Creatures/IllusionCharacter.cpp b/Source/UnrealProject/Creatures/IllusionCharacter.cpp new file mode 100644 index 0000000..303f0d3 --- /dev/null +++ b/Source/UnrealProject/Creatures/IllusionCharacter.cpp @@ -0,0 +1,189 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "Creatures/NetworkCharacter.h" +#include "Creatures/NetworkPlayer.h" +#include "IllusionCharacter.h" +#include "BlueprintAbilityLibrary.h" +#include "AIController.h" +#include "DealDamageProxy.h" +#include "AbilityInfo.h" +#include "NativeModifiers.h" + +AIllusionCharacter::AIllusionCharacter() +{ + PrimaryActorTick.bCanEverTick = true; + aggroRange = 400; + acceptenceDistance = 200; + lifeTime = 30; +} + +void AIllusionCharacter::BeginPlay() +{ + Super::BeginPlay(); + + if (Role != ROLE_Authority) + { + return; + } + FActorSpawnParameters SpawnInfo; + SpawnInfo.Instigator = Instigator; + SpawnInfo.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; + SpawnInfo.OverrideLevel = GetLevel(); + SpawnInfo.ObjectFlags |= RF_Transient; // We never want to save AI controllers into a map + AController* NewController = GetWorld()->SpawnActor(AAIController::StaticClass(), GetActorLocation(), GetActorRotation(), SpawnInfo); + + if (NewController != NULL) + { + // if successful will result in setting this->Controller + // as part of possession mechanics + NewController->Possess(this); + } + + if (IsValid(illOwner)) + { + m_maxHealth = illOwner->GetMaxHealth(); + m_health = illOwner->GetHealth(); + } + else + { + FERROR("NO ILLOWNER IN ILLUSIONCHARACTER"); + } + if (illOwner) + { + abilities.Add(illOwner->abilities[0]); + m_attackSpeed =illOwner->m_attackSpeed; + } + m_isdying = false; +} +void AIllusionCharacter::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + Super::EndPlay(EndPlayReason); +} +void AIllusionCharacter::Tick(float deltaTime) +{ + Super::Tick(deltaTime); + if (Role != ROLE_Authority) + { + return; + } + lifeTime -= deltaTime; + if (lifeTime <= 0) + { + if(!m_isdying) + Kill(); + return; + } + UNavigationSystem* NavSys = UNavigationSystem::GetCurrent(GetWorld()); + if (!NavSys) + { + RERROR("there is no nav sys"); + } + if (!GetController()) + { + RERROR("there is no controller"); + } + if (!IsValid(illOwner)) + { + Kill(); + return; + } + if (!IsValid(target)) + { + attacking = false; + //targetList = ULib::GetCharacterOverlaps(); + target = GetClosestTarget(); + if (FVector::DistSquared(illOwner->GetActorLocation(), GetActorLocation()) > acceptenceDistance*acceptenceDistance) + { + MoveTo(illOwner->GetActorLocation()); + FPRINT("moving to the illOwner"); + } + else MoveTo(GetActorLocation()); + return; + } + if (!attacking) + { + attacking = IsAttacking(1.0f); + return; + } + Attack(); + +} +void AIllusionCharacter::Attack() +{ + int randdistance =0; + randdistance += ((int)abilities[0]->AICastRange / 2); + if (FVector::Dist(GetActorLocation(), target->GetActorLocation()) < randdistance) + { + MoveTo(GetActorLocation()); + SetActorRotation(FVector(target->GetActorLocation() - GetActorLocation()).Rotation()); + } + else MoveTo(target->GetActorLocation()); + for (int i = 0; i < abilities.Num(); i++) + { + if ((abilities[i])&&FVector::DistSquared(GetActorLocation(),target->GetActorLocation())AICastRange*abilities[i]->AICastRange) + { + CastAbility(abilities[i]); + } + } +} +ANetworkCharacter* AIllusionCharacter::GetClosestTarget() +{ + ANetworkCharacter* returnTarget = nullptr; + float distance = 1e34; + for (ANetworkCharacter* character : targetList) + { + if(!IsValid(character)||character==this || character->GetTeam()==GetTeam()) + continue; + float tempdistance = FVector::DistSquared(character->GetActorLocation(), GetActorLocation()); + if (distance > tempdistance) + { + distance = tempdistance; + returnTarget = character; + } + } + return returnTarget; +} +void AIllusionCharacter::MoveTo(FVector Location) +{ + UNavigationSystem* NavSys = UNavigationSystem::GetCurrent(GetWorld()); + if (IsPendingKill() || IsPendingKillPending()) + return; + if (!IsStunned()) + { + NavSys->SimpleMoveToLocation(this->Controller, Location); + } + else + { + NavSys->SimpleMoveToLocation(this->Controller, GetActorLocation()); + } +} +bool AIllusionCharacter::IsAttacking(float changePercentage) +{ + int num = rand() % 100; + if (num > (int)changePercentage) + { + return false; + } + return true; +} + +void AIllusionCharacter::Kill() +{ + if (IsPendingKill() || IsPendingKillPending()) + { + RERROR("the character is pending kill"); + return; + + } + ADOTModifier* deathMod = GetWorld()->SpawnActor(); + deathMod->damagePerTick = 500; + deathMod->lifeTime = 0; + deathMod->target = this; + deathMod->character = this; + deathMod->abilityInfo = abilityInfo; + m_modifierManager->AddModifier(deathMod); + m_isdying = true; + //m_health = 0; +// m_shouldBeDestroyed = true; +} \ No newline at end of file diff --git a/Source/UnrealProject/Creatures/IllusionCharacter.h b/Source/UnrealProject/Creatures/IllusionCharacter.h new file mode 100644 index 0000000..fb6e2a3 --- /dev/null +++ b/Source/UnrealProject/Creatures/IllusionCharacter.h @@ -0,0 +1,46 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "Creatures/NetworkCharacter.h" +#include "IllusionCharacter.generated.h" + +/** + * + */ +UCLASS() +class UNREALPROJECT_API AIllusionCharacter : public ANetworkCharacter +{ + GENERATED_BODY() +public: + AIllusionCharacter(); + virtual void BeginPlay() override; + virtual void Tick(float deltaTime)override; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + void MoveTo(FVector Location); + void Attack(); + UFUNCTION(BluePrintCallable, Category = "Illusion Properties") + void Kill(); + class ANetworkCharacter* target; + class ANetworkCharacter* GetClosestTarget(); + // float percentage is a range between 0 and 100 where 100 is always attacking and 0 never attacking + bool IsAttacking(float changePercentage); + UPROPERTY(EditAnywhere, BlueprintReadWrite,Category = "Illusion Properties", meta = (ExposeOnSpawn)) + class ANetworkPlayer* illOwner; + UPROPERTY(editAnywhere, Category = "Illusion Properties") + float aggroRange; + + UPROPERTY(editAnywhere, BlueprintReadWrite,Category = "Illusion Properties", meta = (ExposeOnSpawn)) + float lifeTime; + UPROPERTY(editAnywhere, BlueprintReadWrite, Category = "Illusion Properties", meta = (ExposeOnSpawn)) + class UAbilityInfo* abilityInfo; + UPROPERTY(editAnywhere, Category = "Illusion Properties") + float acceptenceDistance; + UPROPERTY(editAnywhere, BlueprintReadWrite, Category = "Illusion Properties", meta = (ExposeOnSpawn)) + TArray targetList; + bool attacking; + +private: + bool m_isdying; + +}; diff --git a/Source/UnrealProject/Creatures/MainCharacterAnimation.cpp b/Source/UnrealProject/Creatures/MainCharacterAnimation.cpp new file mode 100644 index 0000000..18e0d13 --- /dev/null +++ b/Source/UnrealProject/Creatures/MainCharacterAnimation.cpp @@ -0,0 +1,69 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "MainCharacterAnimation.h" +#include "NetworkCharacter.h" +#include "Animation/AnimNodeBase.h" +#include "AnimationProxy.h" + +UMainCharacterAnimation::UMainCharacterAnimation(const FObjectInitializer& init) + : Super(init) +{ + m_mainAnimProxy = nullptr; + character = nullptr; + attacking = false; + charCustomization = FCharacterCustomization(); + m_swingAnimationSequence = 0; +} +void UMainCharacterAnimation::NativeInitializeAnimation() +{ + attacking = false; + Super::NativeInitializeAnimation(); + character = Cast(this->GetOwningActor()); + if (character) + { + m_swingAnimationSequence = character->swingAnimationSequence; + } +} +void UMainCharacterAnimation::NativeUpdateAnimation(float DeltaSeconds) +{ + if(character && character->swingAnimationSequence > m_swingAnimationSequence) + { + attacking = true; + m_swingAnimationSequence = character->swingAnimationSequence; + } +} +void UMainCharacterAnimation::SetCharacterCustomization(const FCharacterCustomization& characterCustomization) +{ + charCustomization = characterCustomization; + if(m_mainAnimProxy) + { + m_mainAnimProxy->charCustomization = charCustomization; + } +} +FAnimInstanceProxy* UMainCharacterAnimation::CreateAnimInstanceProxy() +{ + check(!m_mainAnimProxy); + m_mainAnimProxy = new FMainAnimProxy(); + + m_mainAnimProxy->boneNames[0] = "Slave_Head_Ctrl_Jnt"; + m_mainAnimProxy->boneNames[1] = "Slave_L_Hip_Ctrl_Jnt"; + m_mainAnimProxy->boneNames[2] = "Slave_R_Hip_Ctrl_Jnt"; + m_mainAnimProxy->boneNames[3] = "Slave_L_Result_Shoulder_Ctrl_Jnt"; + m_mainAnimProxy->boneNames[4] = "Slave_R_Result_Shoulder_Ctrl_Jnt"; + m_mainAnimProxy->boneNames[5] = "Slave_Result_Spine06"; + m_mainAnimProxy->boneNames[6] = "skeleton"; + + + return m_mainAnimProxy; +} +void UMainCharacterAnimation::DestroyAnimInstanceProxy(FAnimInstanceProxy* InProxy) +{ + check(InProxy == m_mainAnimProxy); + delete InProxy; + m_mainAnimProxy = nullptr; +} +void UMainCharacterAnimation::OnSwingAnimation_Implementation() +{ +} + diff --git a/Source/UnrealProject/Creatures/MainCharacterAnimation.h b/Source/UnrealProject/Creatures/MainCharacterAnimation.h new file mode 100644 index 0000000..344d1ac --- /dev/null +++ b/Source/UnrealProject/Creatures/MainCharacterAnimation.h @@ -0,0 +1,43 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "Animation/AnimInstance.h" +#include "PlayerSetupState.h" +#include "MainCharacterAnimation.generated.h" + +/** + * + */ +UCLASS() +class UNREALPROJECT_API UMainCharacterAnimation : public UAnimInstance +{ + GENERATED_BODY() + +public: + UMainCharacterAnimation(const FObjectInitializer& init); + + virtual void NativeInitializeAnimation() override; + virtual void NativeUpdateAnimation(float DeltaSeconds) override; + + virtual FAnimInstanceProxy* CreateAnimInstanceProxy(); + virtual void DestroyAnimInstanceProxy(FAnimInstanceProxy* InProxy); + + UFUNCTION(BlueprintNativeEvent, Category = "Animation") + void OnSwingAnimation(); + + UFUNCTION(BlueprintCallable, Category = "Animation") + void SetCharacterCustomization(const FCharacterCustomization& characterCustomization); + + UPROPERTY(BlueprintReadOnly, Category = "Animation") + class ANetworkCharacter* character; + UPROPERTY(BlueprintReadWrite, Category = "Animation") + bool attacking; + FCharacterCustomization charCustomization; + +private: + // Keeps track of how many times an animation was triggered + int32 m_swingAnimationSequence; + struct FMainAnimProxy* m_mainAnimProxy; + +}; diff --git a/Source/UnrealProject/Creatures/MiniBossCreature.cpp b/Source/UnrealProject/Creatures/MiniBossCreature.cpp new file mode 100644 index 0000000..b1fb5f8 --- /dev/null +++ b/Source/UnrealProject/Creatures/MiniBossCreature.cpp @@ -0,0 +1,113 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "MiniBossCreature.h" +#include "NetworkPlayer.h" +#include "CreatureSpawn.h" +#include "AbilityInfo.h" +#include "Effect.h" + + +AMiniBossCreature::AMiniBossCreature() +{ + PrimaryActorTick.bCanEverTick = true; + m_usesMana = false; + + target = nullptr; + m_spawn = nullptr; + + m_castTimer = 0; + + keyDropped = PlayerKeyType::None; +} + +void AMiniBossCreature::BeginPlay() +{ + Super::BeginPlay(); + + if (Role != ROLE_Authority) + return; + + // Select two random abilities to cast + if (IsValid(inheritAbilities) && IsValid(inheritAbilities.GetDefaultObject())) + { + TArray& bossAbilities = inheritAbilities.GetDefaultObject()->abilities; + if (bossAbilities.Num() > 1) + { + TArray abilityIndices; + for (int32 i = 0; i < bossAbilities.Num(); i++) + abilityIndices.Add(i); + + while (m_bossAbilityPattern.Num() < 2) + { + const int32 index = FMath::Rand() % abilityIndices.Num(); + const int32 ability = abilityIndices[index]; + abilityIndices.RemoveAt(index); + m_bossAbilityPattern.Add(ability); + } + } + else if (bossAbilities.Num() == 1) + m_bossAbilityPattern.Add(0); + } +} +void AMiniBossCreature::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + Super::EndPlay(EndPlayReason); + + if (Role != ROLE_Authority) + return; + + // Drop a key + if (m_health <= 0) + { + ANetworkPlayer* const killedBy = Cast(GetLastPlayerDamage()); + if (IsValid(killedBy) && keyDropped != PlayerKeyType::None && IsValid(m_spawn) && !m_spawn->hasDroppedKey) + { + killedBy->AssignKey(keyDropped, m_spawn); + } + } +} + +void AMiniBossCreature::Tick(float deltaTime) +{ + Super::Tick(deltaTime); + + if (Role != ROLE_Authority) + return; + + // If the distance of the target to the spawn is greater than the aggro range of the spawner, + // then lose the target. + if (IsValid(target)) + { + float targetDistSqr = FVector::DistSquared(target->GetActorLocation(), m_spawn->SpawnResetPosition()); + float aggroSqr = m_spawn->deaggroRadius * m_spawn->deaggroRadius; + if (aggroSqr < targetDistSqr) + { + // Reset + target = nullptr; + m_castTimer = 0; + } + } + + if (!IsValid(target)) + return; + + // Boss casting logic + MoveToPoint(target->GetActorLocation()); + m_castTimer += deltaTime; + + if (m_castTimer > 2.5f && IsValid(inheritAbilities) && IsValid(inheritAbilities.GetDefaultObject())) + { + TArray& bossAbilities = inheritAbilities.GetDefaultObject()->abilities; + + if (bossAbilities.Num() > 0 && m_bossAbilityPattern.Num() > 0) + { + // Cast a random ability + UAbilityInfo* ability = bossAbilities[m_bossAbilityPattern[FMath::Rand() % m_bossAbilityPattern.Num()]]; + if (IsValid(ability)&& ability->AICastRange*ability->AICastRange>FVector::DistSquared(GetActorLocation(),target->GetActorLocation())) + CastAbility(ability); + } + + m_castTimer = 0; + } +} \ No newline at end of file diff --git a/Source/UnrealProject/Creatures/MiniBossCreature.h b/Source/UnrealProject/Creatures/MiniBossCreature.h new file mode 100644 index 0000000..314798b --- /dev/null +++ b/Source/UnrealProject/Creatures/MiniBossCreature.h @@ -0,0 +1,34 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "Creatures/BossBase.h" +#include "PlayerKeyType.h" +#include "MiniBossCreature.generated.h" + +/** + * + */ +UCLASS() +class UNREALPROJECT_API AMiniBossCreature : public ABossBase +{ + GENERATED_BODY() + +public: + AMiniBossCreature(); + virtual void BeginPlay() override; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + virtual void Tick(float deltaTime) override; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Miniboss") + TSubclassOf inheritAbilities; + + PlayerKeyType keyDropped; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Miniboss") + TSubclassOf keyEffect; +private: + TArray m_bossAbilityPattern; + float m_castTimer; + +}; diff --git a/Source/UnrealProject/Creatures/MinionAnimInstance.cpp b/Source/UnrealProject/Creatures/MinionAnimInstance.cpp new file mode 100644 index 0000000..a91d339 --- /dev/null +++ b/Source/UnrealProject/Creatures/MinionAnimInstance.cpp @@ -0,0 +1,61 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "MinionAnimInstance.h" + +UMinionAnimInstance::UMinionAnimInstance(const FObjectInitializer& init) + : Super(init) +{ + m_animationState = EMinionAnimState::MAS_Idle; +} + +void UMinionAnimInstance::NativeInitializeAnimation() +{ + Super::NativeInitializeAnimation(); + m_animationState = EMinionAnimState::MAS_Idle; +} + +void UMinionAnimInstance::NativeUpdateAnimation(float deltaSeconds) +{ + Super::NativeUpdateAnimation(deltaSeconds); + + APawn* ownerPawn = TryGetPawnOwner(); + + if (IsValid(ownerPawn)) + { + const FVector velocity = ownerPawn->GetVelocity(); + const FVector forward = ownerPawn->GetActorForwardVector().GetSafeNormal2D(); + + m_movementSpeed = velocity.Size(); + + float angleBetween = FMath::FindDeltaAngle(forward.HeadingAngle(), velocity.GetSafeNormal2D().HeadingAngle()); + m_movementDirectionRelative = FVector(cos(angleBetween), sin(angleBetween), 0); + } +} + +EMinionAnimState UMinionAnimInstance::GetAnimationState() +{ + return m_animationState; +} + +bool UMinionAnimInstance::IsInAnimationState(const EMinionAnimState minionStateCheck) +{ + return (m_animationState == minionStateCheck); +} + +void UMinionAnimInstance::ChangeAnimationStateTo(EMinionAnimState newState) +{ + m_animationState = newState; +} + +void UMinionAnimInstance::BPChangeAnimationStateTo(EMinionAnimState newState) +{ + m_animationState = newState; + OnAnimationStateChanged.Broadcast(newState); +} + +void UMinionAnimInstance::PlaySpecificAnimationByInt(int32 animationToPlay) +{ + m_specificAnimationIndex = animationToPlay; + ChangeAnimationStateTo(EMinionAnimState::MAS_Specific); +} \ No newline at end of file diff --git a/Source/UnrealProject/Creatures/MinionAnimInstance.h b/Source/UnrealProject/Creatures/MinionAnimInstance.h new file mode 100644 index 0000000..30acea5 --- /dev/null +++ b/Source/UnrealProject/Creatures/MinionAnimInstance.h @@ -0,0 +1,66 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "Animation/AnimInstance.h" +#include "MinionAnimInstance.generated.h" + +// Enum that shows in what state the Animation Instance is in. +UENUM(BlueprintType) +enum class EMinionAnimState : uint8 +{ + MAS_Idle UMETA(DisplayName = "Idle"), + MAS_Pointing UMETA(DisplayName = "Pointing"), + MAS_Attacking UMETA(DisplayName = "Attacking"), + MAS_Casting UMETA(DisplayName = "Casting"), + MAS_Guarding UMETA(DisplayName = "Guarding"), + MAS_Staggered UMETA(DisplayName = "Staggered"), + MAS_Stunned UMETA(DisplayName = "Stunned"), + MAS_Killed UMETA(DisplayName = "Killed"), + MAS_Specific UMETA(DisplayName = "Specific") +}; + +// Delegate declaration for when the Animation Instance changes its state from within. +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnAnimationStateChanged, EMinionAnimState, newState); + +/** + * Animation instance that handles all state changing, variable gathering and notifying the AI class. + */ +UCLASS() +class UNREALPROJECT_API UMinionAnimInstance : public UAnimInstance +{ + GENERATED_BODY() +public: + UMinionAnimInstance(const FObjectInitializer& init); + + virtual void NativeInitializeAnimation() override; + virtual void NativeUpdateAnimation(float deltaSeconds) override; + + // Function to get the animation state of the animation instance. + EMinionAnimState GetAnimationState(); + // Helper function for checking if the minion is in a certain state. + bool IsInAnimationState(const EMinionAnimState minionStateCheck); + // Function that changes the state of the minion. Does not trigger OnAnimationStateChanged. + UFUNCTION(BlueprintCallable, Category = "Animation State") + void ChangeAnimationStateTo(EMinionAnimState newState); + // Function that changes the state of the minion. Triggers OnAnimationStateChanged. + // Made specifically for use in the Animation Blueprint. + UFUNCTION(BlueprintCallable, Category = "Animation State") + void BPChangeAnimationStateTo(EMinionAnimState newState); + // Delegate for when the state of the minion changes. + UPROPERTY(BlueprintAssignable, Category = "Animation State") + FOnAnimationStateChanged OnAnimationStateChanged; + + UFUNCTION() + void PlaySpecificAnimationByInt(int32 animationToPlay); + +protected: // Member variables are protected. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Animation State") + EMinionAnimState m_animationState; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Animation State") + float m_movementSpeed; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Animation State") + FVector m_movementDirectionRelative; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Animation State") + int32 m_specificAnimationIndex; +}; diff --git a/Source/UnrealProject/Creatures/NPCBase.cpp b/Source/UnrealProject/Creatures/NPCBase.cpp new file mode 100644 index 0000000..47acc4e --- /dev/null +++ b/Source/UnrealProject/Creatures/NPCBase.cpp @@ -0,0 +1,349 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "NPCBase.h" +#include "MinionAnimInstance.h" +#include "SpawnerBase.h" +#include "modifier.h" +#include "NetworkPlayer.h" +#include "TeamData.h" +#include "BossBase.h" +#include "DrawDebugHelpers.h" +#include "AI/Navigation/NavigationPath.h" + +static UTeamData* teamData; + +ANPCBase::ANPCBase() +{ + teamData = ConstructorHelpers::FObjectFinder(L"/Game/Assets/TeamData").Object; + + PrimaryActorTick.bCanEverTick = true; + m_spawn = nullptr; + m_dead = false; + baseAttackSpeed = 2.0f; + baseMaxHealth = 200.0f; + baseArmor = 30.0f; + baseMaxMana = 1000.0f; + baseAttackSpeed = 1.0f; + maxAvoidanceForce = 200.0f; + m_reachedStart = false; + //visionTrigger = CreateDefaultSubobject(TEXT("Vision Trigger")); + +} +void ANPCBase::BeginPlay() +{ + Super::BeginPlay(); + if (Role != ROLE_Authority) + return; + + m_health = m_maxHealth = baseMaxHealth; + m_mana = m_maxMana = baseMaxMana; + if (m_spawn) + m_targetPoint = m_spawn->GetActorLocation(); + else m_targetPoint = GetActorLocation(); + collisionRadius = GetCapsuleComponent()->GetScaledCapsuleRadius(); + m_walkSpeed = GetCharacterMovement()->MaxWalkSpeed; + m_OnRep_Team(); + GetCapsuleComponent()->BodyInstance.bLockXRotation = true; + GetCapsuleComponent()->BodyInstance.bLockYRotation = true; + animInstance = Cast(GetMesh()->GetAnimInstance()); + patrolTime = 5.0f; +} +void ANPCBase::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + Super::EndPlay(EndPlayReason); + if (Role != ROLE_Authority) + return; + if(m_spawn && EndPlayReason == EEndPlayReason::Destroyed) + m_spawn->OnMobDie(this); + +} +void ANPCBase::Tick(float deltaTime) +{ + Super::Tick(deltaTime); + if (Role != ROLE_Authority) + return; + UNavigationSystem* navSys = UNavigationSystem::GetCurrent(GetWorld()); + if (!navSys || !GetController() || !m_spawn) + { + //RERROR("Boss base does not have a navigation system"); + return; + } + + if (!IsValid(target)) + target = nullptr; + + if (m_dead) + { + return; + } + if (!IsValid(target)) + { + m_spawn->GetNewTarget(this); + } + + if (!IsValid(target) && !m_hasGeneral) + { + MoveToPoint(m_targetPoint); + } + else + { + if (FVector::DistSquared(m_spawn->GetActorLocation(), GetActorLocation()) > m_spawn->deaggroRadius * m_spawn->deaggroRadius) + { + // Reset aggro radius + target = nullptr; + m_isPulled = false; + } + } + + + +} +int32 ANPCBase::NativeDealDamage(class ANetworkCharacter* dealer, int32 damage, float armorPercentageIgnore, class UAbilityInfo* ability) +{ + target = dealer; + m_OnRep_Team(); + return Super::NativeDealDamage(dealer, damage, armorPercentageIgnore, ability); +} +void ANPCBase::ResetModifiers() +{ + Super::ResetModifiers(); + m_maxHealth = baseMaxHealth; + m_maxMana = baseMaxMana; + m_attackSpeed = baseAttackSpeed; + m_armor = baseArmor; +} + +void ANPCBase::OnDeath(UAbilityInfo* ability) +{ + //should be removed later, this is in here untill the animations are actually in the game + Super::OnDeath(ability); + return; + target = nullptr; + m_dead = true; + m_modifierManager->ClearModifiers(); +} + +void ANPCBase::CalcAvoidance(const FVector& target) +{ + UNavigationSystem* NavSys = UNavigationSystem::GetCurrent(GetWorld()); +// NavSys->SimpleMoveToLocation(this->Controller, target); + if (Cast(this)) + { + NavSys->SimpleMoveToLocation(this->Controller, target); + return; + } + FVector newLoc = FVector::ZeroVector; + newLoc = GetClosestPathLocation(target) - GetActorLocation(); + newLoc.Normalize(); + newLoc *= GetCharacterMovement()->GetMaxSpeed(); + newLoc = newLoc + CalcAvoidanceVector(); //+ calc avoicance vector; + FVector vel = TruncateVector(GetCharacterMovement()->Velocity + newLoc, GetCharacterMovement()->GetMaxSpeed()); + vel += GetActorLocation(); + NavSys->SimpleMoveToLocation(this->Controller, vel); + SetActorRotation(FVector(FVector(vel.X, vel.Y, GetActorLocation().Z) - GetActorLocation()).Rotation()); + +} + +void ANPCBase::PatrolBetweenLocations(FVector startlocation, FVector endLocation, float deltaTime) +{ + FVector actorlocation = GetActorLocation(); + if (m_reachedStart) + { + MoveToPoint(endLocation); + enableVisonCone = false; + } + else + { + MoveToPoint(startlocation); + enableVisonCone = false; + } + //wait a few second to walk back + float dist = FVector::DistSquaredXY(startlocation, actorlocation); + if (dist < 50.0f*50.0f) + { + m_patrolTimer -= deltaTime; + enableVisonCone = true; + if (m_patrolTimer <= 0) + { + m_reachedStart = true; + m_patrolTimer = patrolTime; + + } + + + } + dist = FVector::DistSquaredXY(endLocation, actorlocation); + if (dist< 50.0f*50.0f) + { + m_patrolTimer -= deltaTime; + enableVisonCone = true; + if (m_patrolTimer <= 0) + { + m_reachedStart = false; + m_patrolTimer = patrolTime; + } + } + +} +void ANPCBase::MoveToPoint(const FVector& target) +{ + UNavigationSystem* NavSys = UNavigationSystem::GetCurrent(GetWorld()); + GetCharacterMovement()->MaxWalkSpeed = m_walkSpeed; + float distance = FVector::DistSquared(target, GetActorLocation()); + if (IsPendingKill() || IsPendingKillPending()) + return; + + if (!IsStunned()) + { + CalcAvoidance( target); + SetActorRotation(FVector(FVector(target.X, target.Y, GetActorLocation().Z) - GetActorLocation()).Rotation()); + + } + else + { + + NavSys->SimpleMoveToLocation(this->Controller, GetActorLocation()); + } + + + +} +void ANPCBase::MoveToPointSlow(const FVector& target, float dist) +{ + UNavigationSystem* NavSys = UNavigationSystem::GetCurrent(GetWorld()); + if (IsPendingKill() || IsPendingKillPending()) + return; + GetCharacterMovement()->MaxWalkSpeed = m_walkSpeed/2; + if (m_lastLoc != target) + { + if (m_lastLoc == FVector::ZeroVector || FVector::DistSquaredXY(m_lastLoc, GetActorLocation()) < dist*dist) + { + //RPRINT("test"); + m_lastLoc =UNavigationSystem::ProjectPointToNavigation(GetWorld(), target); + //m_lastLoc = target; + } + } + MoveToPoint(m_lastLoc); + +} +void ANPCBase::OnOverlapBegin(class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult) +{ + if (!IsValid(OtherActor)) + return; + if (!OtherActor->IsA(ANPCBase::StaticClass())) + return; + collisionList.Add(Cast(OtherActor)); + +} +void ANPCBase::OnOverlapEnd(class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex) +{ + if (!IsValid(OtherActor)) + return; + if (!OtherActor->IsA(ANPCBase::StaticClass())) + return; + collisionList.Remove(Cast(OtherActor)); +} +void ANPCBase::SetSpawn(class ASpawnerBase* spawn) +{ + check(Role == ROLE_Authority); + check(!m_spawn); + m_spawn = spawn; +} +void ANPCBase::SetControlPoint(FVector controlPoint) +{ + check(Role == ROLE_Authority); + m_targetPoint = controlPoint; +} + +void ANPCBase::m_OnRep_Team() +{ + Super::m_OnRep_Team(); + if(!m_materialInstance) + { + m_materialInstance = GetMesh()->CreateDynamicMaterialInstance(0); + } + if(m_materialInstance) + { + float scalar = (float)m_health / (float)m_maxHealth; + m_materialInstance->SetVectorParameterValue("TeamColor", teamData->GetTeamColor(GetTeam())); + m_materialInstance->SetScalarParameterValue("FlameEdgeSoftness", 100.0f - (100.0f*scalar)); + m_materialInstance->SetScalarParameterValue("FlameEffectIntensity", (scalar)); + m_materialInstance->SetScalarParameterValue("RandomOffset", FMath::FRand()); + } +} + +void ANPCBase::UnsetSpawn() +{ + check(Role == ROLE_Authority); + m_spawn = nullptr; +} +void ANPCBase::ChangeNPCAnimation_Implementation(uint8 state ) +{ + if (IsValid(animInstance)) + animInstance->ChangeAnimationStateTo((EMinionAnimState)(state)); +} +FVector ANPCBase::CalcAvoidanceVector() +{ + FVector retVector = FVector::ZeroVector; + float dynLenght = GetCharacterMovement()->Velocity.Size() / GetCharacterMovement()->GetMaxSpeed(); + //change getactorforward by velocity + FVector vel = GetCharacterMovement()->Velocity; + vel.Normalize(); + aheadvec1 =GetActorLocation()+ vel*dynLenght; + aheadvec2 = GetActorLocation() + vel*dynLenght*0.5; + // DrawDebugSphere(GetWorld(), aheadvec1, 24, 32, FColor(255, 255, 255)); + // DrawDebugSphere(GetWorld(), aheadvec2, 24, 32, FColor(255, 0, 0)); + ANPCBase* obstacle = GetClosestObstacle(); + if (IsValid(obstacle)) + { + retVector = aheadvec1 - obstacle->GetActorLocation(); + retVector.Z = 0.0f; + retVector.Normalize(); + retVector *= maxAvoidanceForce; + + } +// DrawDebugLine(GetWorld(), GetActorLocation(), GetActorLocation() + retVector, FColor(255, 255, 0)); + return retVector; +}/// +ANPCBase* ANPCBase::GetClosestObstacle() +{ + ANPCBase* obstacle = nullptr; + float distance = 1e34; + for (int i = 0; i < m_spawn->m_mobs.Num(); i++) + { + ANPCBase* current = m_spawn->m_mobs[i]; + if(!IsValid(current)||current==this) + continue; + float tempdistance = FVector::DistSquared(current->GetActorLocation(), this->GetActorLocation()); + if (tempdistance < distance) + { + distance = tempdistance; + obstacle = current; + } + } + if (!IsValid(obstacle)) + return nullptr; + if (FVector::DistSquared(aheadvec1, obstacle->GetActorLocation())>100 * 100 && FVector::DistSquared(aheadvec2, obstacle->GetActorLocation())>100 * 100) + obstacle = nullptr; + return obstacle; +} +FVector ANPCBase::TruncateVector(FVector inVector, float maxForce) +{ + if (inVector.Size() > maxForce) + { + inVector.Normalize(); + inVector *= maxForce; + } + return inVector; +} +FVector ANPCBase::GetClosestPathLocation(FVector endlocation) +{ + FVector returnVec = FVector(); + UNavigationPath *tpath; + UNavigationSystem* navsys = UNavigationSystem::GetCurrent(GetWorld()); + tpath = navsys->FindPathToLocationSynchronously(GetWorld(), GetActorLocation(), endlocation); + if (tpath == nullptr||tpath->PathPoints.Num()<=1) + return endlocation; + return tpath->PathPoints[tpath->PathPoints.Num()-1]; +} \ No newline at end of file diff --git a/Source/UnrealProject/Creatures/NPCBase.h b/Source/UnrealProject/Creatures/NPCBase.h new file mode 100644 index 0000000..4b80208 --- /dev/null +++ b/Source/UnrealProject/Creatures/NPCBase.h @@ -0,0 +1,109 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "Creatures/NetworkCharacter.h" +#include "NPCBase.generated.h" + +/** + * + */ + +UCLASS() +class UNREALPROJECT_API ANPCBase : public ANetworkCharacter +{ + GENERATED_BODY() +public: + + ANPCBase(); + virtual void BeginPlay() override; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + virtual void Tick(float deltaTime) override; + virtual int32 NativeDealDamage(class ANetworkCharacter* dealer, int32 damage, float armorPercentageIgnore, class UAbilityInfo* ability) override; + // Clears all the modifier stat modifications from this character + virtual void ResetModifiers(); + + void SetSpawn(class ASpawnerBase* spawn); + void UnsetSpawn(); + void SetControlPoint(FVector controlPoint); + + UPROPERTY(BlueprintAssignable, Category = "Collision") + FComponentBeginOverlapSignature OnComponentBeginOverlap; + UPROPERTY(BlueprintAssignable, Category = "Collision") + FComponentBeginOverlapSignature OnComponentExitOverlap; + + + UPROPERTY(VisibleAnywhere, Category = "Custom Collision") + float maxAvoidanceForce; + UPROPERTY(BlueprintReadWrite) + TArray< class ANPCBase*>collisionList; + UPROPERTY(BlueprintReadWrite) + class ANetworkCharacter* target; + UFUNCTION(BlueprintImplementableEvent, Category = "Animation") + void PlayFencAnimation(); + UFUNCTION(BlueprintImplementableEvent, Category = "Animation") + void PlayNormalAnimation(); + UFUNCTION(BlueprintImplementableEvent, Category = "Animation") + void PlayClimbingAnimation(); + UFUNCTION(BlueprintImplementableEvent, Category = "Animation") + void PlayPointAnimation(); + UFUNCTION(NetMulticast, Reliable, Category = "Animation") + void ChangeNPCAnimation(uint8 state); + //Trigger components for the vision of the npcs + float patrolTime; + float collisionRadius; + void CalcAvoidance(const FVector& target); + virtual void MoveToPoint(const FVector& target); + virtual void MoveToPointSlow(const FVector& target, float dist); + void PatrolBetweenLocations(FVector startlocation, FVector endLocation,float deltaTime); + ANPCBase* GetClosestObstacle(); + FVector CalcAvoidanceVector(); + FVector TruncateVector(FVector inVector, float maxForce); + FVector GetClosestPathLocation(FVector endlocation); + FVector aheadvec1; + FVector aheadvec2; +protected: + virtual void m_OnRep_Team() override; + + UFUNCTION(BlueprintImplementableEvent, category = "death") + void PlayDeathAnimation(UAbilityInfo* ability); + + virtual void OnDeath(UAbilityInfo* ability) override; + UFUNCTION() + void OnOverlapBegin(class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult); + UFUNCTION() + void OnOverlapEnd(class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex); + + UPROPERTY() + UMaterialInstanceDynamic* m_materialInstance; + + // Base Stats + UPROPERTY(EditDefaultsOnly, Category = "Stats") + int32 baseMaxHealth; + UPROPERTY(EditDefaultsOnly, Category = "Stats") + int32 baseMaxMana; + // AKA Attacks per second + UPROPERTY(EditDefaultsOnly, Category = "Stats") + float baseAttackSpeed; + UPROPERTY(BlueprintReadWrite) + bool enableVisonCone; + + bool m_dead; + + UPROPERTY() + class ASpawnerBase* m_spawn; + + FVector m_targetPoint; + bool m_isPulled; + bool m_hasGeneral; + class UMinionAnimInstance* animInstance; +private: + FVector m_lastLoc; + float m_walkSpeed; + bool lastcollision; + bool m_reachedStart; + FVector previousDirection; + float m_patrolTimer; + + +}; diff --git a/Source/UnrealProject/Creatures/NetworkCharacter.cpp b/Source/UnrealProject/Creatures/NetworkCharacter.cpp new file mode 100644 index 0000000..7018a15 --- /dev/null +++ b/Source/UnrealProject/Creatures/NetworkCharacter.cpp @@ -0,0 +1,1232 @@ +// Project Lab - NHTV Igad +#include "UnrealProject.h" +#if PLATFORM_SPECIFIC_WIN == 0 +#include "InputManager.hpp" +using namespace Input; +#endif + +#include "NetworkCharacter.h" + +#include "AbilityInfo.h" +#include "ItemBase.h" +#include "BaseSkillObject.h" +#include "IngameSkillTree.h" +#include "DefaultPlayerController.h" +#include "DefaultGameMode.h" +#include "NetworkPlayer.h" +#include "Modifier.h" +#include "AbilityEventGroup.h" +#include "PreCastAbilityEventGroup.h" +#include "NativeModifiers.h" +#include "DefaultGameState.h" +#include "DefaultPlayerState.h" +#include "Effect.h" + +TSubclassOf expGainEffect; + +ANetworkCharacter::ANetworkCharacter() +{ + expGainEffect = ConstructorHelpers::FClassFinder(L"/Game/Assets/Art/Effects/FX_ExpParticle").Class; + + // Set size for collision capsule + GetCapsuleComponent()->InitCapsuleSize(42.f, 96.0f); + + // Network init + bReplicates = true; + bAlwaysRelevant = true; + bReplicateMovement = true; + + // Configure character movement + GetCharacterMovement()->bOrientRotationToMovement = true; // Rotate character to moving direction + GetCharacterMovement()->RotationRate = FRotator(0.f, 640.f, 0.f); + GetCharacterMovement()->bConstrainToPlane = true; + GetCharacterMovement()->bSnapToPlaneAtStart = true; + baseMovementSpeed = GetCharacterMovement()->MaxWalkSpeed; + + experienceGainOnKill = 50; + m_maxHealth = 1000; + m_maxMana = 100; + canBeStunned = true; + m_visible = true; + m_usesMana = true; + + // Default to neutral + GetMesh()->bRenderCustomDepth = true; + GetMesh()->CustomDepthStencilValue = 0; + + PrimaryActorTick.bCanEverTick = true; + m_lastPlayerDamage = nullptr; + + + healthAccumTimer = 1.0f; + damageAccumTimer = 0.1f; + + teamDamageTimer = 5.0f; + + m_healAccumValue = 0; + m_healAccumTimer = 0; +} + +void ANetworkCharacter::BeginPlay() +{ + Super::BeginPlay(); + + m_cooldownReduction = 0.0f; + m_channelStun = false; + m_channelGroup = nullptr; + m_castingAbility = nullptr; + m_initialised = true; + m_hitable = true; + + m_silenceCount = 0; + m_silenced = false; + swingAnimationSequence = 0; + + // Filter out NULL abilities + TArray actualAbilities; + for (int32 i = 0; i < abilities.Num(); i++) + { + if (!IsValid(abilities[i])) + continue; + actualAbilities.Add(abilities[i]); + } + abilities = actualAbilities; + + + for (int32 i = 0; i < abilities.Num(); i++) + { + GetAbilityState(abilities[i]); + } + + RegisterHealthBar(); + + // Initialize the character modifiers on the server + if (Role == ROLE_Authority) + { + m_modifierManager = new ModifierManager(this); + m_SpawnModifiers(); + for(UAbilityInfo* info : passives) + { + CastAbility(info); + } + m_health = m_maxHealth; + m_mana = m_maxMana; + } +} + +void ANetworkCharacter::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + Super::EndPlay(EndPlayReason); + + if (m_modifierManager) + { + delete m_modifierManager; + m_modifierManager = nullptr; + } + +#if UE_INCLUDE_METRICS + // Record trivial player death + if(metricsHandle && EndPlayReason == EEndPlayReason::Type::Destroyed) + metricsHandle->OnPlayerDie(0); +#endif +} + +void ANetworkCharacter::Destroyed() +{ + UnregisterHealthBar(); + Super::Destroyed(); +} + +void ANetworkCharacter::RegisterHealthBar() +{ + // Notify UI of character spawn + ULocalPlayer* localPlayer = GetGameInstance()->GetFirstGamePlayer(); + if(localPlayer) + { + ADefaultPlayerController* localPlayerController = Cast(localPlayer->PlayerController); + if(localPlayerController) + { + localPlayerController->OnCharacterCreated(this); + + } + else + { + GWWARNING(L"No local player found to send OnCharacterCreated"); + } + } +} +void ANetworkCharacter::UnregisterHealthBar() +{ + // Notify UI of character destroy + UGameInstance* inst = GetGameInstance(); + if(!inst) + return; + ULocalPlayer* player = inst->GetFirstGamePlayer(); + if(player) + { + APlayerController* controller = player->PlayerController; + + ADefaultPlayerController* localPlayer = Cast(controller); + if(localPlayer) + { + localPlayer->OnCharacterDestroyed(this); + } + else + { + GWWARNING(L"No local player found to send OnCharacterDestroyed"); + } + } +} +void ANetworkCharacter::Tick(float DeltaSeconds) +{ + Super::Tick(DeltaSeconds); + + // Update the damage accumolation table + if (m_damageAccum.size() > 0) + { + for (auto iter = m_damageAccum.begin(); iter != m_damageAccum.end();) + { + // Controller no longer exists + if (!IsValid(iter->first)) + { + iter = m_damageAccum.erase(iter); + continue; + } + + DamagePeriod& value = iter->second; + value.timer -= DeltaSeconds; + + // Time has expired + if (value.timer <= 0) + { + if (value.damage > 0) + iter->first->SpawnArcingCombatText(GetActorLocation(), FString::FromInt(value.damage), FLinearColor::White); + m_totalDamage = value.damage; + iter = m_damageAccum.erase(iter); + continue; + } + + iter++; + } + } + + // Update the team damage dealt table + if (m_teamDamageDealt.size() > 0) + { + for (auto iter = m_teamDamageDealt.begin(); iter != m_teamDamageDealt.end();) + { + DamagePeriod& value = iter->second; + value.timer -= DeltaSeconds; + + // Time has expired, this team is no longer entitled for the experience + if (value.timer <= 0) + { + iter = m_teamDamageDealt.erase(iter); + continue; + } + + iter++; + } + } + + ADefaultPlayerController* const controller = Cast(GetController()); + + // Update the heal accumolation + if (IsValid(controller) && m_healAccumValue > 0) + { + m_healAccumTimer -= DeltaSeconds; + if (m_healAccumTimer <= 0) + { + m_healAccumTimer = 0; + controller->SpawnCombatText(GetActorLocation(), FString::FromInt(m_healAccumValue), FLinearColor::Green); + m_healAccumValue = 0; + } + } + + if(GetMesh()) + { + // Check if ally or player for silhouette material + // (0 = neutral, 1 = player, 2 = ally, 3 = enemy) + UWorld* const world = GetWorld(); + const int32 current = GetMesh()->CustomDepthStencilValue; + int32 target = 0; // Default to neutral + + if(controller == world->GetFirstPlayerController()) + target = 1; // Self + else + { + // Get the local team + int32 localTeam = -1; + ADefaultPlayerController* controller = Cast(world->GetFirstPlayerController()); + if(IsValid(controller)) + { + APlayerStateBase* playerState = Cast(controller->PlayerState); + if(IsValid(playerState)) + localTeam = playerState->GetTeam(); + } + if(localTeam != -1) + { + if(GetTeam() == localTeam) + target = 2; // Ally + else if(GetTeam() != 0) + target = 3; // Enemy + } + } + if(target != current) + { + GetMesh()->CustomDepthStencilValue = target; + GetMesh()->MarkRenderStateDirty(); + } + } + + m_TickAbilities(DeltaSeconds); + +#if UE_INCLUDE_METRICS + if (metricsHandle) + { + // Record player stats + metricsHandle->OnPlayerUpdate(GetActorLocation().X, GetActorLocation().Y); + ADefaultPlayerState* const state = Cast(PlayerState); + if(state) + metricsHandle->OnPlayerLevel(uint8(state->GetLevel())); + metricsHandle->OnPlayerMaxHealth(m_maxHealth); + metricsHandle->OnPlayerMaxMana(m_maxMana); + metricsHandle->OnPlayerHealth(m_health); + metricsHandle->OnPlayerMana(m_mana); + } +#endif + if (m_shouldBeDestroyed) + Destroy(); +} +void ANetworkCharacter::OnDeath(UAbilityInfo* ability) +{ + m_shouldBeDestroyed = true; +} +void ANetworkCharacter::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(ANetworkCharacter, basicAttack); + DOREPLIFETIME(ANetworkCharacter, m_health); + DOREPLIFETIME(ANetworkCharacter, m_maxHealth); + DOREPLIFETIME(ANetworkCharacter, m_mana); + DOREPLIFETIME(ANetworkCharacter, m_maxMana); + DOREPLIFETIME(ANetworkCharacter, m_stunned); + DOREPLIFETIME(ANetworkCharacter, m_silenced); + DOREPLIFETIME(ANetworkCharacter, m_channelStun); + DOREPLIFETIME(ANetworkCharacter, m_allowChannelRotation); + DOREPLIFETIME(ANetworkCharacter, m_castingAbility); + DOREPLIFETIME(ANetworkCharacter, m_cooldownReduction); + DOREPLIFETIME(ANetworkCharacter, m_attackSpeed); + DOREPLIFETIME(ANetworkCharacter, m_attackDamageMultiplier); + DOREPLIFETIME(ANetworkCharacter, m_magicDamageMultiplier); + DOREPLIFETIME(ANetworkCharacter, m_blockedMana); + DOREPLIFETIME(ANetworkCharacter, m_damageMultiplier); + DOREPLIFETIME(ANetworkCharacter, m_ignoreArmor); + DOREPLIFETIME(ANetworkCharacter, m_armor); + DOREPLIFETIME(ANetworkCharacter, m_manaRegenMultiplier); + DOREPLIFETIME(ANetworkCharacter, m_visible); + DOREPLIFETIME(ANetworkCharacter, m_hitable); + DOREPLIFETIME(ANetworkCharacter, m_manaUsageMultiplier); + DOREPLIFETIME(ANetworkCharacter, m_positiveEffectMultiplier); + DOREPLIFETIME(ANetworkCharacter, m_negativeEffectMultiplier); + DOREPLIFETIME(ANetworkCharacter, m_castingMovementspeedMultiplier); + DOREPLIFETIME(ANetworkCharacter, swingAnimationSequence); +} + +void ANetworkCharacter::TriggerSwingAnimation_Server() +{ + check(Role == ROLE_Authority); + swingAnimationSequence++; +} + +void ANetworkCharacter::LearnSkills_Implementation(const TArray& skills) +{ + ADefaultPlayerController* pc = Cast(GetController()); + + for(size_t i = 0; i < skills.Num(); i++) + { + const FIngameSkillTreeSkill& obj = skills[i]; + UAbilityInfo* ability = obj.selectedEffect; + + if(ability) + { + // Initialize ability power in the ability state + AAbilityState* aState = GetAbilityState(ability); + if(aState) + aState->power = obj.power; + + if(ability->passive) + { + if(!passives.Contains(ability)) + { + // Learn passive + passives.Add(obj.selectedEffect); + + // Only Cast the actual passive ability on the server + if(Role == ROLE_Authority) + { + CastAbility(ability); + } + } + } + else + { + // Learn ability + if(!abilities.Contains(ability)) + { + abilities.Add(ability); + } + } + } + } +} +void ANetworkCharacter::LearnSkills_Server(const TArray& skills) +{ + // Server side call + LearnSkills_Implementation(skills); + // Client side call + LearnSkills(skills); +} + +void ANetworkCharacter::m_SpawnModifiers() +{ + ARegenModifier* regenMod = Cast(m_modifierManager->AddModifier(ARegenModifier::StaticClass(), 0)); + regenMod->regenPerTick = passiveHealthRegen; + AManaRegenModifier* manaMod = Cast(m_modifierManager->AddModifier(AManaRegenModifier::StaticClass(), 0)); + manaMod->regenPerTick = passiveManaRegen; +} + +bool ANetworkCharacter::m_AllowedToMove() +{ + return !IsStunned(); +} +bool ANetworkCharacter::m_AllowedToRotate() +{ + return CanRotate(); +} + +void ANetworkCharacter::m_OnAbilityCastConfirm_Implementation(UAbilityInfo* ability, bool success, bool toggleState, float cooldown, float cooldownTime) +{ + if (!ability) + { + GWWARNING(L"Out of range value received for OnAbilityCastConfirm"); + return; + } + + AAbilityState* state = GetAbilityState(ability); + + //GWPRINT(L"Ability toggle " + ability->GetName() + L" toggles from " + state->toggleState + L" to " + toggleState); + + state->toggleState = toggleState; + + state->onCooldownTimer = cooldown; + state->currentCooldownDuration = cooldownTime; + state->cooldownRate = cooldown/cooldownTime; +} +bool ANetworkCharacter::m_CastAbility_Server_Validate(UAbilityInfo* ability) +{ + if(ability == nullptr) + return false; + if(ability->events.Num() == 0) + { + GWERROR(L"No events in ability->events for ability " + ability->GetName()); + return false; + } + // Validate AbilityInfo Array + for(int32 i = 0; i < ability->events.Num(); i++) + { + if(ability->events[i] == nullptr) + { + GWERROR(L"Null entry in ability->events[" + i + L"] for ability " + ability->GetName()); + return false; + } + } + return true; +} +void ANetworkCharacter::m_CastAbility_Server_Implementation(UAbilityInfo* ability) +{ + AAbilityState* state = GetAbilityState(ability); + + // Check if this is a toggle ability, and is toggled off + bool toggleOffAbility = ability->IsHoldOrToggle() && state->toggleState == true; + + // Check if we're not channeling (if this is not a toggle off ability) + if(IsPendingKill() || IsPendingKillPending()) + return; + + // Check if stunned or already casting an ability + // allow if this is a toggle ability and currently on + if(!toggleOffAbility && (IsStunned() || IsChanneling() )) + { + m_OnAbilityCastConfirm_Server(ability, false); + return; + } + + // Check if silenced, block all non-basic attacks in this case + // also, don't block passives + if (ability->abilityType != EAbilityType::Basic && IsSilenced() && !ability->passive) + { + m_OnAbilityCastConfirm_Server(ability, false); + return; + } + + // Check if ability is not on cooldown (if not toggling off) + if(state->onCooldownTimer > 0.1f && !toggleOffAbility) + { + m_OnAbilityCastConfirm_Server(ability, false); + return; + } + + // Use mana state for current ability / character mana usage chained together with ability state (toggle off ability?) + bool currentUsesMana = m_usesMana && !toggleOffAbility; + + UWorld* const world = GetWorld(); + if (ability && world && (ability->mana * m_manaUsageMultiplier <= m_mana || !currentUsesMana)) + { + if (currentUsesMana) + RemoveMana(ability->mana * m_manaUsageMultiplier); + + // Handle cooldown setting + if(ability->IsHoldOrToggle() && state->toggleState == false) + { + // No cooldown when casting toggle abilities + state->onCooldownTimer = 0.0f; + state->cooldownRate = 0.0f; + } + else + { + // Go on cooldown if it is a toggle ability, or a regular ability + m_SetAbilityCooldown(ability, state); + } + + if(ability->IsHoldOrToggle()) + { + AAbilityEventGroup** activeAbility = m_activeAbilities.Find(ability); + bool isAbilityActive = activeAbility && *activeAbility; + + // Check if ability is enabled + if(state->toggleState == false) + { + // Check if ability does not yet exist + if(!isAbilityActive) + { + state->toggleState = true; + + // Activate toggle ability + m_InitAbilitySequence(ability, state); + } + else + { + m_OnAbilityCastConfirm_Server(ability, false); + } + } + else + { + // Deactivate ability + if(isAbilityActive) + { + state->toggleState = false; + (*activeAbility)->NextGroup(); + *activeAbility = nullptr; + m_OnAbilityCastConfirm_Server(ability, true); + } + else + { + m_OnAbilityCastConfirm_Server(ability, false); + } + } + } + else + { + m_InitAbilitySequence(ability, state); + } + } + else + { + m_OnAbilityCastConfirm_Server(ability, false); + } +} +void ANetworkCharacter::m_InitAbilitySequence(class UAbilityInfo* ability, class AAbilityState* state) +{ + AAbilityEventGroup* group = AAbilityEventGroup::SpawnSequence(this, ability, state, ability->events); + check(group); + if(group->channelEvent) + { + //GWPRINT(L"Begin channel ability " + group->GetName()); + m_channelGroup = group; + m_castingAbility = ability; + m_allowChannelRotation = group->allowRotateWhileChannel; + if(m_castingMovementspeedMultiplier == 0.0f || ability->abilityType == EAbilityType::Basic) + m_channelStun = group->stunWhileChannel; + else + { + if (group->stunWhileChannel) + { + ASpeedModifier* sModifier = GetWorld()->SpawnActor(); + sModifier->lifeTime = group->duration; + sModifier->speedMultiplier = m_castingMovementspeedMultiplier; + sModifier->target = this; + GetModifierManager()->AddModifier(sModifier); + } + } + } + else + { + m_allowChannelRotation = false; + m_channelGroup = nullptr; + } + + group->onAbilityEventGroupEnded.AddDynamic(this, &ANetworkCharacter::m_OnAbilityEventGroupEnded); + m_activeAbilities.Add(ability, group); + + m_OnAbilityCastConfirm_Server(ability, true); +} + +void ANetworkCharacter::m_PreCast(UAbilityInfo* ability) +{ + if(ability->mana * m_manaUsageMultiplier <= m_mana) + m_currentPreCast = APreCastAbilityEventGroup::InitPreCast(ability, this); +} +void ANetworkCharacter::m_TickAbilities(float DeltaSeconds) +{ + for(auto it = m_abilityStates.CreateIterator(); it; ++it) + { + AAbilityState* state = it.Value(); + if((state->onCooldownTimer -= (DeltaSeconds * (1.0f + m_cooldownReduction))) <= 0.0f) + { + state->onCooldownTimer = 0.0f; + } + state->cooldownRate = state->onCooldownTimer / it.Key()->cooldown; + } + if(m_modifierManager) + m_modifierManager->Tick(DeltaSeconds); + m_timeSinceDamage += DeltaSeconds; +} + +void ANetworkCharacter::m_SetAbilityCooldown(class UAbilityInfo* ability, class AAbilityState* state) +{ + if(ability->abilityType == EAbilityType::Basic) + { + // This is the character's basic attack + state->onCooldownTimer = state->currentCooldownDuration = 1.0f / m_attackSpeed; + state->cooldownRate = (state->currentCooldownDuration > 0.0f) ? (state->onCooldownTimer / state->currentCooldownDuration) : 0.0f; + } + else + { + // This is an ability + state->NativeSetCooldown(); + } +} + +void ANetworkCharacter::m_OnAbilityCastConfirm_Server(UAbilityInfo* ability, bool success) +{ + AAbilityState* state = GetAbilityState(ability); + m_OnAbilityCastConfirm(ability, success, state->toggleState, state->onCooldownTimer, state->currentCooldownDuration); + +#if UE_INCLUDE_METRICS + if (metricsHandle && success && !ability->passive) + { + // Record ability cast + if (!ability->IsHoldOrToggle() || state->toggleState) + { + std::wstring abilityName = std::wstring() + ability->name; + if (abilityName.size() > 512) + abilityName.resize(512); + + Metrics::RegisterAbility(ability->GetStaticHash(), abilityName); + metricsHandle->OnPlayerCast(ability->GetStaticHash()); + } + } +#endif + + return; +} +void ANetworkCharacter::m_OnAbilityEventGroupEnded(class UAbilityInfo* ability, class AAbilityEventGroup* current, class AAbilityEventGroup* next) +{ + check(Role == ROLE_Authority); + check(ability); + AAbilityEventGroup** group = m_activeAbilities.Find(ability); + check(group); + *group = next; + + // Check end of channeling ability + if(m_channelGroup == current) + { + if(!next || !next->channelEvent) + { + //GWPRINT(L"End ability channel on ability " + m_castingAbility->GetName()); + m_castingAbility = nullptr; + m_channelGroup = nullptr; + m_channelStun = false; + } + else + { + m_channelGroup = next; + m_channelStun = m_channelGroup->stunWhileChannel; + } + } + + if(!next) + { + // Turn of toggle state if it was active + AAbilityState* state = GetAbilityState(ability); + if(state->toggleState == true) + { + state->toggleState = false; + m_SetAbilityCooldown(ability, state); + m_OnAbilityCastConfirm_Server(ability, true); + } + } + else + { + // Subscribe to next group ended event + next->onAbilityEventGroupEnded.AddDynamic(this, &ANetworkCharacter::m_OnAbilityEventGroupEnded); + } + //GWPRINT(L"Ability " + ability->GetName() + L" reached next state " + (next ? next->GetName() : FString(L"end"))); +} + +void ANetworkCharacter::CastAbility(int32 abilityIndex) +{ + if (abilityIndex < 0 || abilityIndex >= abilities.Num()) + { + GWERROR(L"Ability index out of range"); + return; + } + UAbilityInfo* ability = abilities[abilityIndex]; + if (!ability) + { + GWERROR(L"Null ability assigned and casted on character character[" + GetName() + L"] slot[" + abilityIndex + L"]"); + return; + } + CastAbility(ability); +} +void ANetworkCharacter::CastAbility(UAbilityInfo* ability) +{ + if (!ability) + { + GWERROR(L"Ability pointer invalid"); + return; + } + + if(IsPendingKill() || IsPendingKillPending()) + return; + + AAbilityState* state = GetAbilityState(ability); + if(state->onCooldownTimer <= 0.0f && ability->precastEvent == nullptr) + { + m_CastAbility_Server(ability); + } + else if(ability->precastEvent != nullptr && state->onCooldownTimer <= 0.0f) + { + m_PreCast(ability); + } +} + +class UAbilityInfo* ANetworkCharacter::GetCastingAbility() +{ + return m_castingAbility; +} + +AAbilityState* ANetworkCharacter::GetAbilityState(UAbilityInfo* abilityInfo) +{ + AAbilityState** state = m_abilityStates.Find(abilityInfo); + if (!state) + { + // Don't check anymore if this character is allowed to cast an ability + // TODO: check this only for players in CastAbility_Validate when the new character class hierarchy is implemented + //check(abilities.Contains(abilityInfo) || passives.Contains(abilityInfo)); + if (abilityInfo == nullptr) + { + FERROR("abilityInfo == nullptr"); + return nullptr; + } + if(!abilityInfo->abilityState) + { + GWERROR(L"Ability " + abilityInfo->GetName() + L" does not have an ability state class assigned"); + abilityInfo->abilityState = AAbilityState::StaticClass(); + } + AAbilityState* abs = GetWorld()->SpawnActor(abilityInfo->abilityState); + abs->onCooldownTimer = 0.0f; + abs->cooldownRate = 0.0f; + abs->power = 0.0f; + abs->info = abilityInfo; + return m_abilityStates.Add(abilityInfo, abs); + } + return *state; +} + +bool ANetworkCharacter::GetAbilityToggleState(class UAbilityInfo* abilityInfo) +{ + AAbilityState* state = GetAbilityState(abilityInfo); + if(state) + { + return state->toggleState; + } + return false; +} + +void ANetworkCharacter::m_InteruptSpellcasting() +{ + if (m_channelGroup) + { + m_channelGroup->Interrupt(); + m_channelGroup = nullptr; + m_castingAbility = nullptr; + m_channelStun = false; + } +} + +ModifierManager* ANetworkCharacter::GetModifierManager() +{ + return m_modifierManager; +} + +TArray ANetworkCharacter::GetModifiersOfClass(TSubclassOf modifierClass) +{ + if (m_modifierManager) + return m_modifierManager->GetModifiersOfClass(modifierClass); + return TArray(); +} +void ANetworkCharacter::ResetModifiers() +{ + m_blockedMana = 0; + m_attackDamageMultiplier = 1.0f; + m_magicDamageMultiplier = 1.0f; + m_damageMultiplier = 1.0f; + m_manaRegenMultiplier = 1.0f; + m_stunned = false; + m_stunned = false; + m_damageTakenMultiplier = 1.0f; + m_cooldownReduction = 0.0f; + m_ignoreArmor = 0.0f; + m_manaUsageMultiplier = 1.0f; + m_positiveEffectMultiplier = 1.0f; + m_negativeEffectMultiplier = 1.0f; + m_castingMovementspeedMultiplier = 0.0f; + m_armor = baseArmor; + GetCharacterMovement()->MaxWalkSpeed = baseMovementSpeed; + m_SetLevelStats(); +} + +void ANetworkCharacter::m_SetLevelStats() +{ + +} + +void ANetworkCharacter::CheckStatsOverflow() +{ + if (m_health > m_maxHealth || !m_initialised) + m_health = m_maxHealth; + if (m_mana > m_maxMana || !m_initialised) + m_mana = m_maxMana; +} + +int32 ANetworkCharacter::DealDamage(UObject* WorldContextObject, int32 damage, float armorPercentageIgnore) +{ + ADealDamageProxy* damageProxy = Cast(WorldContextObject); + if(!damageProxy) + { + GWERROR(L"DealDamage can not be called from an object that does not inherit from ADealDamageProxy"); + return 0; + } + + int32 damageDealth = NativeDealDamage(damageProxy->character, damage, armorPercentageIgnore, damageProxy->abilityInfo); + + return damageDealth; +} +int32 ANetworkCharacter::NativeDealDamage(class ANetworkCharacter* dealer, int32 damage, float armorPercentageIgnore, UAbilityInfo* ability) +{ + // Ensure that this is only called on the server + check(Role == ROLE_Authority); + if(dealer == NULL || dealer->IsPendingKill()) + { + FWARNING("character in dealdamage is null or pendingkill"); + return 0; + } + + + // Scale the damage done or taken by creatures and players + if (dealer->IsA() != this->IsA()) + { + ANetworkPlayer* const player = dealer->IsA() ? Cast(dealer) : Cast(this); + if (IsValid(player)) + { + ADefaultPlayerState* const state = Cast(player->PlayerState); + if (IsValid(state)) + { + UCurveFloat* const curve = player == this ? player->creatureDamageDealtCurve : player->creatureDamageTakenCurve; + if (curve) + { + const float scalar = curve->GetFloatValue((float)state->GetLevel()); + damage = int32((float)damage * (scalar < 0.0f ? 0.0f : scalar)); + } + } + } + } + + + + if(damage < 0) + { + GWERROR(L"Negative damage dealt by " + ability->GetName()); + return 0; + } + const int32 damageCap = 1000; + if(damage > damageCap) + { + GWERROR(L"A lot of damage (>" + damageCap + L") dealt by " + ability->GetName() + ", is this intended?"); + } + if(armorPercentageIgnore > 1.0f || armorPercentageIgnore < 0.0f) + { + GWERROR(L"Invalid armorPercentageIgnore given by " + ability->GetName() + "will use 0.0f"); + armorPercentageIgnore = 0.0f; + // return 0; + } + + float totalArmorPercentage = armorPercentageIgnore + dealer->m_ignoreArmor; + totalArmorPercentage = std::fmax(0.0f, std::fmin(totalArmorPercentage, 1.0f)); + + if(m_damageTakenMultiplier == 0.0f || dealer->m_damageMultiplier <= 0.0f) + return 0; + float divider = 1 + 0.01f * ((float)m_armor * (1 - totalArmorPercentage)); + float damageToDeal; + // The damage to deal + damageToDeal = damage / divider; + + damageToDeal *= dealer->m_damageMultiplier; + // Multiply by weakness aka damage taken multiplier + damageToDeal *= m_damageTakenMultiplier; + + // Magic damage has a different multiplier + if (ability->abilityType == EAbilityType::Ability) + damageToDeal *= dealer->m_magicDamageMultiplier; + + int32 iDamageToDeal = damageToDeal; + bool dealdamage = true; + for (AModifier* mod : m_modifierManager->m_modifiers) + { + if (!mod->OnDamage(iDamageToDeal, dealer)) + { + dealdamage = false; + } + } + if (dealer->GetModifierManager() == nullptr) + return 0; + for (AModifier* mod : dealer->GetModifierManager()->m_modifiers) + { + if (!mod->OnDealDamage(iDamageToDeal, this)) + { + dealdamage = false; + } + } + if(!dealdamage) + return 0; + +#if UE_INCLUDE_METRICS + if (dealer->metricsHandle && IsValid(dealer)) + { + // Record ability damage dealt + dealer->metricsHandle->OnPlayerDealDamage(ability->GetStaticHash(), iDamageToDeal); + } +#endif + + if(iDamageToDeal > m_health) + iDamageToDeal = m_health; + RemoveHealth(iDamageToDeal); + m_lastPlayerDamage = dealer; + + onDamageTaken.Broadcast(dealer, damage, ability); + + // Update the entry for this team in the damage dealt table + const int32 team = dealer->GetTeam(); + if (experienceGainOnKill > 0 && team < 5) + { + auto find = m_teamDamageDealt.find(team); + if (find != m_teamDamageDealt.end()) + { + find->second.damage += iDamageToDeal; + find->second.timer = teamDamageTimer; + } + else + m_teamDamageDealt.emplace(team, DamagePeriod(iDamageToDeal, teamDamageTimer)); + } + + // Spawn combat text + ADefaultPlayerController* const controller = Cast(dealer->GetController()); + if (IsValid(controller)) + { + auto find = m_damageAccum.find(controller); + if (find == m_damageAccum.end()) + m_damageAccum.emplace(controller, DamagePeriod(iDamageToDeal, damageAccumTimer)); + else + find->second.damage += iDamageToDeal; + } + + for (AModifier* mod : m_modifierManager->m_modifiers) + mod->AfterDamage(iDamageToDeal); + + m_timeSinceDamage = 0; + + if(m_health <= 0) + { + NativeOnKilled(dealer, ability); + OnDeath(ability); + } + return iDamageToDeal; +} + +void ANetworkCharacter::NativeOnKilled(class ANetworkCharacter* killer, class UAbilityInfo* ability) +{ + // Score counting + ANetworkPlayer* const thisPlayer = Cast(this); + if (IsValid(thisPlayer)) + { + ADefaultPlayerState* state = Cast(thisPlayer->PlayerState); + if (IsValid(state)) state->deaths++; + + ANetworkPlayer* const killerPlayer = Cast(killer); + if (IsValid(killerPlayer)) + { + state = Cast(killerPlayer->PlayerState); + if (IsValid(state)) state->kills++; + } + } + + if (experienceGainOnKill > 0 && !m_teamDamageDealt.empty()) + { + ADefaultGameState* gameState = Cast(GetWorld()->GetGameState()); + TArray players = gameState->GetPlayers(); + + // Fetch highest team + int32 highestTeam = -1; + int32 highestDamage = 0; + for (auto i : m_teamDamageDealt) + { + if (i.second.damage > highestDamage) + { + highestDamage = i.second.damage; + highestTeam = i.first; + } + } + m_teamDamageDealt.clear(); + + // Apply experience + if (highestTeam > 0) + { + for (size_t i = 0; i < players.Num(); i++) + { + if (!players[i]) + continue; + + // Award the experience + if(players[i]->GetTeam() == highestTeam) + { + players[i]->GainExperience(experienceGainOnKill); + + // Spawn EXP gain effect + FTransform t; + if(players[i]->character) + { + t.SetTranslation(players[i]->character->GetActorLocation()); + ATargetedEffect* fx = GetWorld()->SpawnActorDeferred(expGainEffect, t); + fx->Init(0.0f, players[i]->character); + fx->target = GetActorLocation(); + UGameplayStatics::FinishSpawningActor(fx, t); + } + } + } + } + } + + // Clear the damage accum table + if (!m_damageAccum.empty()) + { + for (auto iter = m_damageAccum.begin(); iter != m_damageAccum.end();) + { + // Controller no longer exists + if (IsValid(iter->first)) + { + DamagePeriod& value = iter->second; + + if (value.damage > 0) + iter->first->SpawnArcingCombatText(GetActorLocation(), FString::FromInt(value.damage), FLinearColor::White); + } + + iter = m_damageAccum.erase(iter); + } + } + +#if UE_INCLUDE_METRICS + if(metricsHandle && IsValid(killer)) + { + // Record player death + if(killer->metricsHandle) + metricsHandle->OnPlayerDie(killer->metricsHandle->Id()); + else + metricsHandle->OnPlayerDie(0); + } +#endif + + onCharacterKilled.Broadcast(killer, ability); +} + +void ANetworkCharacter::OnStandardAttack(ANetworkCharacter* targetCharacter) +{ + if (m_modifierManager==nullptr) + { + FWARNING("m_modifierManager==nullptr"); + return; + } + for (AModifier* mod : m_modifierManager->m_modifiers) + { + if(IsValid(mod)) + mod->OnStandardAttack(targetCharacter); + } +} +void ANetworkCharacter::AddHealth(int32 health) +{ + NativeAddHealth(health); +} +void ANetworkCharacter::NativeAddHealth(int32 health) +{ + check(Role == ROLE_Authority); // server call only + check(health >= 0); + + if (m_health < m_maxHealth) + { + // Spawn combat text + ADefaultPlayerController* const controller = Cast(GetController()); + if (IsValid(controller)) + { + m_healAccumValue += health; + if (m_healAccumTimer <= 0.0f) m_healAccumTimer += healthAccumTimer; + } + + m_health += health; + if (m_health > m_maxHealth) + m_health = m_maxHealth; + } +} + +void ANetworkCharacter::RemoveHealth(int32 health) +{ + check(Role == ROLE_Authority); // Server only call + m_health -= health; +} +void ANetworkCharacter::AddMana(float mana) +{ + check(Role == ROLE_Authority); // Server only call + m_mana += mana * m_manaRegenMultiplier; + if (m_mana > (m_maxMana-m_blockedMana)) + m_mana = (m_maxMana - m_blockedMana); +} +void ANetworkCharacter::RemoveMana(float mana) +{ + check(Role == ROLE_Authority); // Server only call + if(mana > m_mana) + { + mana = m_mana; // mana has to remain positive or 0 + } + m_mana -= mana; +} +int32 ANetworkCharacter::GetMana() const +{ + return m_mana; +} +int32 ANetworkCharacter::GetBlockedMana() const +{ + return m_blockedMana; +} +int32 ANetworkCharacter::GetMaxMana() const +{ + return m_maxMana; +} +int32 ANetworkCharacter::GetHealth() const +{ + return m_health; +} +int32 ANetworkCharacter::GetMaxHealth() const +{ + return m_maxHealth; +} +int32 ANetworkCharacter::GetArmor() const +{ + return m_armor; +} +float ANetworkCharacter::GetDamageMultiplier() const +{ + return m_damageMultiplier; +} +float ANetworkCharacter::GetCooldownReduction() const +{ + return m_cooldownReduction; +} +float ANetworkCharacter::GetAttackSpeed() const +{ + return m_attackSpeed; +} +float ANetworkCharacter::GetAttackDamage() const +{ + return m_attackDamageMultiplier; +} +float ANetworkCharacter::GetMagicDamage() const +{ + return m_magicDamageMultiplier; +} +float ANetworkCharacter::GetTotalDamage() const +{ + return m_totalDamage; +} +bool ANetworkCharacter::GetHitable() const +{ + return m_hitable; +} +void ANetworkCharacter::SetHitable(bool hittable) +{ + m_hitable = hittable; +} +bool ANetworkCharacter::IsStunned() const +{ + return m_stunned || m_channelStun; +} +bool ANetworkCharacter::CanRotate() const +{ + if(m_stunned) + return false; + if(m_channelStun && !m_allowChannelRotation) + return false; + return true; +} + +bool ANetworkCharacter::IsSilenced() const +{ + return m_silenced; +} +bool ANetworkCharacter::IsChanneling() const +{ + return m_castingAbility != nullptr; +} +bool ANetworkCharacter::IsRanged() const +{ + return m_isRanged; +} +ANetworkCharacter* ANetworkCharacter::GetLastPlayerDamage() const +{ + return IsValid(m_lastPlayerDamage) ? m_lastPlayerDamage : nullptr; +} + +bool ANetworkCharacter::CanBeStunned() const +{ + return canBeStunned; +} + +float ANetworkCharacter::GetTimeSinceDamage() const +{ + return m_timeSinceDamage; +} + +bool ANetworkCharacter::IsVisible() const +{ + return m_visible; +} \ No newline at end of file diff --git a/Source/UnrealProject/Creatures/NetworkCharacter.h b/Source/UnrealProject/Creatures/NetworkCharacter.h new file mode 100644 index 0000000..68cebe6 --- /dev/null +++ b/Source/UnrealProject/Creatures/NetworkCharacter.h @@ -0,0 +1,324 @@ +// Project Lab - NHTV Igad + +#pragma once +#include +using std::unordered_map; +#include "AbilityState.h" +#include "ScalingGraph.h" +#include "NetworkPossessable.h" +#include "IngameSkillTree.h" +#include "NetworkCharacter.generated.h" + +class ACreatureSpawn; + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FCharacterKilled, ANetworkCharacter*, killer, UAbilityInfo*, ability); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOnDamageTaken, ANetworkCharacter*, dealer, int32, damage, UAbilityInfo*, ability); + +UCLASS(config = Game) +class ANetworkCharacter : public ANetworkPossessable +{ + GENERATED_BODY() +public: + ANetworkCharacter(); + + virtual void BeginPlay() override; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + virtual void Destroyed() override; + virtual void OnDeath(UAbilityInfo* ability); + void RegisterHealthBar(); + void UnregisterHealthBar(); + + virtual void Tick(float DeltaSeconds) override; + + // This processes the learned abilities on the server and calls LearnSkills on the owning client + void LearnSkills_Server(const TArray& skills); + // This creates a list of all learned skills on the client's version of the character so that he gets an updated casting bar, etc. + UFUNCTION(Client, Reliable) + void LearnSkills(const TArray& skills); + + // The Buff/Modifier manager, for server side characters + class ModifierManager* GetModifierManager(); + UFUNCTION(BlueprintCallable, Category = "modifiers") + TArray GetModifiersOfClass(TSubclassOf modifierClass); + + virtual void ResetModifiers(); + void CheckStatsOverflow(); + + // Damage dealing + UFUNCTION(BlueprintCallable, Category = "Damage", meta = (WorldContext="WorldContextObject")) + int32 DealDamage(class UObject* WorldContextObject, int32 damage, float armorPercentageIgnore = 0.0f); + virtual int32 NativeDealDamage(class ANetworkCharacter* dealer, int32 damage, float armorPercentageIgnore = 0.0f, UAbilityInfo* ability = nullptr); + + virtual void NativeOnKilled(class ANetworkCharacter* killer, class UAbilityInfo* ability); + + UFUNCTION(BlueprintCallable, Category = "Damage") + void OnStandardAttack(ANetworkCharacter* targetCharacter); + + + + // Healing + UFUNCTION(BlueprintCallable, Category = "Healing") + void AddHealth(int32 health); + virtual void NativeAddHealth(int32 health); + + void RemoveHealth(int32 health); + void AddMana(float mana); + void RemoveMana(float mana); + UFUNCTION(BlueprintCallable, Category = "Damage") + float GetTimeSinceDamage() const; + + UFUNCTION(BlueprintCallable, Category = "Animation") + void TriggerSwingAnimation_Server(); + + UFUNCTION(BlueprintCallable, Category = "State") + int32 GetHealth() const; + UFUNCTION(BlueprintCallable, Category = "State") + int32 GetMana() const; + UFUNCTION(BlueprintCallable, Category = "State") + int32 GetBlockedMana() const; + UFUNCTION(BlueprintCallable, Category = "State") + int32 GetMaxHealth() const; + UFUNCTION(BlueprintCallable, Category = "State") + int32 GetMaxMana() const; + UFUNCTION(BlueprintCallable, Category = "State") + int32 GetArmor() const; + UFUNCTION(BlueprintCallable, Category = "State") + bool GetHitable() const; + UFUNCTION(BlueprintCallable, Category = "State") + void SetHitable(bool hitable); + UFUNCTION(BlueprintCallable, Category = "State") + float GetDamageMultiplier() const; + UFUNCTION(BlueprintCallable, Category = "State") + float GetCooldownReduction() const; + UFUNCTION(BlueprintCallable, Category = "State") + float GetAttackSpeed() const; + UFUNCTION(BlueprintCallable, Category = "State") + float GetAttackDamage() const; + UFUNCTION(BlueprintCallable, Category = "State") + float GetMagicDamage() const; + UFUNCTION(BlueprintCallable, Category = "State") + float GetTotalDamage() const; + UFUNCTION(BlueprintCallable, Category = "State") + bool IsStunned() const; + UFUNCTION(BlueprintCallable, Category = "State") + bool CanRotate() const; + UFUNCTION(BlueprintCallable, Category = "State") + bool IsSilenced() const; + UFUNCTION(BlueprintCallable, Category = "State") + bool IsChanneling() const; + UFUNCTION(BlueprintCallable, Category = "State") + bool IsRanged() const; + bool CanBeStunned() const; + + ANetworkCharacter* GetLastPlayerDamage() const; + + void CastAbility(int32 abilityIndex); + void CastAbility(class UAbilityInfo* abilityInfo); + + // Returns the ability that is currently being casted(channeling) + UFUNCTION(BlueprintCallable, Category = "Ability") + class UAbilityInfo* GetCastingAbility(); + + // Gets the persistent state of an ability between casts + class AAbilityState* GetAbilityState(class UAbilityInfo* abilityInfo); + + // Gets the toggle state for abilities that have isToggleAbility set + bool GetAbilityToggleState(class UAbilityInfo* abilityInfo); + bool m_initialised; + + // Minimap/FoW visibility + bool IsVisible() const; + + // The collection of abilities this character has, for players. + // The first ability in this array is always treated the character's basic attack, + // and thus is affected by the attack speed variable of the character + UPROPERTY(EditDefaultsOnly, Category = "Ability") + TArray abilities; + + UPROPERTY(BlueprintReadOnly, Replicated, Category = "Ability") + class UAbilityInfo* basicAttack; + + UPROPERTY(EditDefaultsOnly, Category = "Ability") + TArray passives; + + UPROPERTY(Replicated, BlueprintReadOnly, Category = "Animation") + int32 swingAnimationSequence; + + UPROPERTY(EditAnywhere, Category = "Experience Settings") + int32 experienceGainOnKill; + + // Intervals at which damage and healing combat text are spawned + UPROPERTY(EditAnywhere, Category = "Combat Text") + float healthAccumTimer; + UPROPERTY(EditAnywhere, Category = "Combat Text") + float damageAccumTimer; + + // Duration before a team's damage is no longer valid + // Gets refreshed when a team deals damage to the creature + UPROPERTY(EditAnywhere, Category = "Team Damage Table") + float teamDamageTimer; + + UPROPERTY(BlueprintAssignable, Category = "Character") + FCharacterKilled onCharacterKilled; + // SERVER ONLY + UPROPERTY(BlueprintAssignable, Category = "Character") + FOnDamageTaken onDamageTaken; + + METRICS_EXPR(Metrics::PlayerHandle* metricsHandle); + +protected: + // Called when the character is spawned or reset to set initial modifiers like fixed health/mana-regen + // overridable + virtual void m_SpawnModifiers(); + + // This checks stunn state, etc. to allow NetworkPossessable to move + virtual bool m_AllowedToMove() override; + virtual bool m_AllowedToRotate() override; + virtual void m_SetLevelStats(); + + UPROPERTY(EditDefaultsOnly, Category = "Stats") + int32 baseMovementSpeed; + UPROPERTY(EditDefaultsOnly, Category = "Stats") + int32 baseArmor; + UPROPERTY(EditDefaultsOnly, Category = "Stats") + int32 passiveManaRegen; + UPROPERTY(EditDefaultsOnly, Category = "Stats") + int32 passiveHealthRegen; + + UPROPERTY(Replicated) + int32 m_blockedMana; + UPROPERTY(Replicated) + float m_damageMultiplier; + UPROPERTY(Replicated) + float m_ignoreArmor; + UPROPERTY(Replicated) + float m_manaRegenMultiplier; + UPROPERTY(Replicated) + float m_manaUsageMultiplier; + UPROPERTY(Replicated) + float m_positiveEffectMultiplier; + UPROPERTY(Replicated) + float m_negativeEffectMultiplier; + UPROPERTY(Replicated) + float m_damageTakenMultiplier; + UPROPERTY(Replicated) + int32 m_armor; + UPROPERTY(Replicated) + bool m_stunned; + UPROPERTY(Replicated) + bool m_silenced; + //sileneCount is the ammount of silences by modifiers, silence by casting is not taking into account. + UPROPERTY(Replicated) + uint32 m_silenceCount; + UPROPERTY(Replicated) + bool m_visible; + UPROPERTY(Replicated) + bool m_hitable; + UPROPERTY(Replicated) + bool m_isRanged; + + UPROPERTY(EditDefaultsOnly, Category = "Stats") + bool canBeStunned; + UPROPERTY(EditDefaultsOnly, Category = "Stats") + bool canBeSilenced; + + UPROPERTY(Replicated) + int32 m_health; + UPROPERTY(Replicated) + int32 m_maxHealth; + UPROPERTY(Replicated) + float m_mana; + UPROPERTY(Replicated) + int32 m_maxMana; + UPROPERTY(Replicated) + float m_cooldownReduction; + UPROPERTY(Replicated) + float m_attackSpeed; + UPROPERTY(Replicated) + float m_attackDamageMultiplier; + UPROPERTY(Replicated) + float m_magicDamageMultiplier; + + UPROPERTY(Replicated) + bool m_usesMana; + + UPROPERTY(Replicated) + float m_castingMovementspeedMultiplier; + + friend class ModifierManager; + friend class APreCastAbilityEventGroup; + friend class AIllusionCharacter; + friend class ADefaultPlayerController; + class ModifierManager* m_modifierManager; + + bool m_shouldBeDestroyed = false; +private: + // Request to the server to cast an ability + UFUNCTION(Reliable, Server, WithValidation) + void m_CastAbility_Server(class UAbilityInfo* ability); + void m_InitAbilitySequence(class UAbilityInfo* ability, class AAbilityState* state); + + // Updates all the cooldown timers and ticks modifiers + void m_TickAbilities(float DeltaSeconds); + + void m_SetAbilityCooldown(class UAbilityInfo* ability, class AAbilityState* state); + + // Server only casting interupt + void m_InteruptSpellcasting(); + + // Called if we know an ability is successfully casted or not + UFUNCTION(Reliable, Client) + void m_OnAbilityCastConfirm(UAbilityInfo* ability, bool success, bool toggleState, float cooldown, float cooldownTime); + + void m_PreCast(UAbilityInfo* ability); + // Called if we know an ability is successfully casted or not (server-side) + virtual void m_OnAbilityCastConfirm_Server(UAbilityInfo* ability, bool success); + + // Callback for when an ability progresses to it's next state + UFUNCTION() + void m_OnAbilityEventGroupEnded(class UAbilityInfo* ability, class AAbilityEventGroup* current, class AAbilityEventGroup* next); + + friend class ADefaultPlayerState; + + // Active Stats + struct DamagePeriod + { + DamagePeriod(int32 damage, float timer) : damage(damage), timer(timer) {} + int32 damage; + float timer; + }; + unordered_map m_damageAccum; + int32 m_damageAccumValue; + float m_damageAccumTimer; + int32 m_healAccumValue; + float m_healAccumTimer; + float m_totalDamage; + unordered_map m_teamDamageDealt; + + UPROPERTY() + ANetworkCharacter* m_lastPlayerDamage; + + // Keeps ability persistent state between casts + UPROPERTY() + TMap m_abilityStates; + // Keeps ability execution states / active group (server-side) + UPROPERTY() + TMap m_activeAbilities; + + friend class AModifier; + friend class AVisibilityModifier; + float m_timeSinceDamage; + UPROPERTY() + class AAbilityEventGroup* m_channelGroup; + UPROPERTY(Replicated) + class UAbilityInfo* m_castingAbility; + UPROPERTY(Replicated) + bool m_channelStun; + UPROPERTY(Replicated) + bool m_allowChannelRotation; + + + class APreCastAbilityEventGroup* m_currentPreCast; + +}; + diff --git a/Source/UnrealProject/Creatures/NetworkGhost.cpp b/Source/UnrealProject/Creatures/NetworkGhost.cpp new file mode 100644 index 0000000..4b9fa33 --- /dev/null +++ b/Source/UnrealProject/Creatures/NetworkGhost.cpp @@ -0,0 +1,91 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "NetworkGhost.h" +#include "NetworkPlayer.h" +#include "DefaultPlayerState.h" + + +// Sets default values +ANetworkGhost::ANetworkGhost() +{ + bReplicates = true; + + postProcess = CreateDefaultSubobject(TEXT("PostProcess")); + postProcess->bEnabled = false; + postProcess->AttachTo(RootComponent); + + m_SetupCamera(); + + // Configure character movement + GetCharacterMovement()->bOrientRotationToMovement = true; // Rotate character to moving direction + GetCharacterMovement()->RotationRate = FRotator(0.f, 640.f, 0.f); + GetCharacterMovement()->bConstrainToPlane = true; + GetCharacterMovement()->bSnapToPlaneAtStart = true; + + shrinesInRange = 0; + respawnDuration = 15; + allyRespawnRange = 300; +} + +void ANetworkGhost::BeginPlay() +{ + Super::BeginPlay(); + + m_respawnTime = respawnDuration; +} + +void ANetworkGhost::Tick(float DeltaTime) +{ + Super::Tick(DeltaTime); + + // Update the respawn timer (server only) + if (Role == ROLE_Authority) + { + if (m_respawnTime > 0) + m_respawnTime -= DeltaTime; + } + + // Enable the post process on clients + if (IsValid(GetController())) + postProcess->bEnabled = GetController()->IsLocalController(); +} + + +bool ANetworkGhost::CanRespawn() const +{ + // Cannot respawn yet + if (m_respawnTime > 0) + return false; + + // If we're falling, were unable to respawn + if (!GetCharacterMovement()->IsMovingOnGround()) + return false; + + // We're at a shrine + if (shrinesInRange > 0) + return true; + + // Check if the ally is within the radius, so we can respawn next to him + ADefaultPlayerState* state = Cast(PlayerState); + bool inAllyRange = false; + if (IsValid(state) && IsValid(state->teamMate) && IsValid(state->teamMate->character)) + { + if (FVector::DistSquared(GetActorLocation(), state->teamMate->character->GetActorLocation()) <= (allyRespawnRange * allyRespawnRange)) + return true; + } + return false; +} + +float ANetworkGhost::RespawnTime() const +{ + return m_respawnTime; +} + + +void ANetworkGhost::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(ANetworkGhost, m_respawnTime); +} \ No newline at end of file diff --git a/Source/UnrealProject/Creatures/NetworkGhost.h b/Source/UnrealProject/Creatures/NetworkGhost.h new file mode 100644 index 0000000..876961c --- /dev/null +++ b/Source/UnrealProject/Creatures/NetworkGhost.h @@ -0,0 +1,44 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "Creatures/NetworkPossessable.h" +#include "NetworkGhost.generated.h" + +UCLASS() +class UNREALPROJECT_API ANetworkGhost : public ANetworkPossessable +{ + GENERATED_BODY() + +public: + ANetworkGhost(); + + virtual void BeginPlay() override; + + virtual void Tick(float DeltaSeconds) override; + + + bool CanRespawn() const; + + UPROPERTY(BlueprintReadonly, Category = "Respawn") + int32 shrinesInRange; + + // Range to ally for respawn + UPROPERTY(EditAnywhere, Category = "Respawn") + float allyRespawnRange; + + // Time it takes before the player is allowed to respawn + UPROPERTY(EditAnywhere, Category = "Respawn") + float respawnDuration; + + // Get time it takes before respawning + UFUNCTION(BlueprintCallable, Category = "Respawn") + float RespawnTime() const; + + UPROPERTY(EditAnywhere, Category = "PostProcess") + UPostProcessComponent* postProcess; + +private: + UPROPERTY(Replicated) + float m_respawnTime; +}; \ No newline at end of file diff --git a/Source/UnrealProject/Creatures/NetworkPlayer.cpp b/Source/UnrealProject/Creatures/NetworkPlayer.cpp new file mode 100644 index 0000000..6e1e8ea --- /dev/null +++ b/Source/UnrealProject/Creatures/NetworkPlayer.cpp @@ -0,0 +1,474 @@ +// Project Lab - NHTV Igad +#include "UnrealProject.h" +#include "NetworkPlayer.h" +#include "DefaultPlayerController.h" +#include "ItemBase.h" +#include "BaseSkillObject.h" +#include "AbilityInfo.h" +#include "NetworkSwitch.h" +#include "DefaultGameMode.h" +#include "DefaultPlayerState.h" +#include "IngameSkillTree.h" + +#include "Modifier.h" +#include "SpawnerBase.h" +#include "TeamData.h" + +static UTeamData* teamData; + +ANetworkPlayer::ANetworkPlayer() +{ + bReplicates = true; + + m_SetupCamera(); + + m_initialised = false; + m_keyState = 0; + + healthCurve = ConstructorHelpers::FObjectFinder(TEXT("/Game/ScalingCurves/CF_PlayerHealth")).Object; + healthRegenCurve = ConstructorHelpers::FObjectFinder(TEXT("/Game/ScalingCurves/CF_PlayerHealthRegen")).Object; + manaCurve = ConstructorHelpers::FObjectFinder(TEXT("/Game/ScalingCurves/CF_PlayerMana")).Object; + manaRegenCurve = ConstructorHelpers::FObjectFinder(TEXT("/Game/ScalingCurves/CF_PlayerManaRegen")).Object; + armorCurve = ConstructorHelpers::FObjectFinder(TEXT("/Game/ScalingCurves/CF_PlayerArmor")).Object; + attackSpeedCurve = ConstructorHelpers::FObjectFinder(TEXT("/Game/ScalingCurves/CF_PlayerAttackSpeed")).Object; + + creatureDamageTakenCurve = ConstructorHelpers::FObjectFinder(TEXT("/Game/ScalingCurves/CF_CreatureDamageTaken")).Object; + creatureDamageDealtCurve = ConstructorHelpers::FObjectFinder(TEXT("/Game/ScalingCurves/CF_CreatureDamageDealt")).Object; + teamData = ConstructorHelpers::FObjectFinder(TEXT("/Game/Assets/TeamData")).Object; + + playerCircle = CreateDefaultSubobject("Player Circle"); + playerCircle->AttachTo(RootComponent); + + visionRadius = 2500; +} +void ANetworkPlayer::BeginPlay() +{ + m_levelStatsInitialized = false; + Super::BeginPlay(); + m_initialised = false; +} +void ANetworkPlayer::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + Super::EndPlay(EndPlayReason); + + if (Role != ROLE_Authority) + return; + + // Return the keys + ANetworkPlayer* const killedBy = Cast(GetLastPlayerDamage()); + if (m_health <= 0 && IsValid(killedBy) && killedBy->GetTeam() != GetTeam()) + { + FText Source = FText::FromString(killedBy->playerName); + FText Target = FText::FromString(playerName); + onPlayerKilled.Broadcast(Source, Target, killedBy->GetTeam(), GetTeam()); + // Assign keys to the player that killed us + for (auto iter = m_keyOrigins.CreateIterator(); iter; ++iter) + { + ASpawnerBase** find = killedBy->m_keyOrigins.Find(iter->Key); + if (!find) + killedBy->m_keyOrigins.Add(iter->Key, iter->Value); + else + iter->Value->hasDroppedKey = false; + } + killedBy->m_keyState |= m_keyState; + } + else + { + // Return the keys we have + for (auto iter = m_keyOrigins.CreateIterator(); iter; ++iter) + { + if (IsValid(iter->Value)) + iter->Value->hasDroppedKey = false; + } + } + m_keyState = 0; + m_keyOrigins.Reset(); + + // Clean up screen effects and delegates. + m_filterTimelines.Empty(); + for (int32 i = 0; i < m_delegates.Num(); i++) + { + GetWorldTimerManager().ClearTimer(m_delegates[i]); + } + m_delegates.Empty(); +} +void ANetworkPlayer::Tick(float DeltaSeconds) +{ + Super::Tick(DeltaSeconds); + + for (auto& timeline : m_filterTimelines) + { + timeline.Value.TickTimeline(DeltaSeconds); + } + + // Server only + if(Role == ROLE_Authority) + { + ADefaultPlayerState* state = Cast(PlayerState); + if(!m_levelStatsInitialized) + { + m_modifierManager->RecalculateCharacter(); + CheckStatsOverflow(); + if(!m_initialised) + { + m_health = m_maxHealth; + m_mana = m_maxMana; + m_initialised = true; + } + m_levelStatsInitialized = true; + } + } + +} + +void ANetworkPlayer::PossessedBy(AController* NewController) +{ + Super::PossessedBy(NewController); + OnRep_PlayerState(); +} +void ANetworkPlayer::OnRep_PlayerState() +{ + Super::OnRep_PlayerState(); + + // Initialize player circle + UMaterialInstanceDynamic* mi = playerCircle->CreateDynamicMaterialInstance(0); + FLinearColor teamColor = teamData->GetTeamColor(Cast(PlayerState)->GetTeam()); + mi->SetVectorParameterValue("Color", teamColor); +} + +void ANetworkPlayer::ResetModifiers() +{ + // Learn abilities + m_SetLevelStats(); + Super::ResetModifiers(); +} + +void ANetworkPlayer::OnLevelUp_Server(int32 newLevel) +{ + check(Role == ROLE_Authority); + + // Learn abilities + //m_SetLevelStats(); + + // Recalculate modifiers for new level + if(m_modifierManager) + m_modifierManager->RecalculateCharacter(); + CheckStatsOverflow(); + + // Call blueprint level up event (server-side) + OnLevelUp(newLevel); +} + +class ANetworkPlayer* ANetworkPlayer::GetTeamMate() const +{ + ADefaultPlayerState* state = Cast(PlayerState); + if (state && state->teamMate) + return state->teamMate->character; + return nullptr; +} + +void ANetworkPlayer::m_SetLevelStats() +{ + Super::m_SetLevelStats(); + // Update stats + ADefaultPlayerState* state = Cast(PlayerState); + + float level = 1; + if(IsValid(state)) + level = (float)state->GetLevel(); + + float newHealth = SampleHealthCurve(level); + float newMana = SampleManaCurve(level); + float newArmor = SampleArmorCurve(level); + float newAttackSpeed = SampleAttackSpeedCurve(level); + + //GWPRINT(L"Setting level bases stats for level " + level + L"(" + levelScale + L")"); + //GWPRINT(L"Health = " + m_maxHealth + L"->" + newHealth); + //GWPRINT(L"Mana = " + m_maxMana + L"->" + newMana); + //GWPRINT(L"Armor = " + m_armor + L"->" + newArmor); + //GWPRINT(L"Attack Speed = " + m_attackSpeed + L"->" + newAs); + + if(!m_levelStatsInitialized) + { + m_health = newHealth; + m_mana = newMana; + } + + m_maxHealth = newHealth; + m_maxMana = newMana; + baseArmor = newArmor; + m_attackSpeed = newAttackSpeed; + + if(Role == ROLE_Authority) + { + // Send level-up/learn skill command + ADefaultPlayerController* controller = Cast(GetController()); + if(controller) + { + controller->LearnSkillsForLevel(); + } + } +} + +void ANetworkPlayer::OnLevelUp_Implementation(int32 newLevel) +{ + m_levelStatsInitialized = false; +} + +bool ANetworkPlayer::ToggleSwitch_Validate(ANetworkSwitch* networkSwitch) +{ + return networkSwitch && FVector::Dist(GetActorLocation(), networkSwitch->GetActorLocation()) < 600; +} +void ANetworkPlayer::ToggleSwitch_Implementation(ANetworkSwitch* networkSwitch) +{ + if(FVector::Dist(GetActorLocation(), networkSwitch->GetActorLocation()) < 300) + networkSwitch->Toggle(); +} + +float ANetworkPlayer::SampleHealthCurve(float in) +{ + if(GetCharacterClassProperties().healthCurve) + { + return GetCharacterClassProperties().healthCurve->GetFloatValue(in); + } + return healthCurve->GetFloatValue(in); +} +float ANetworkPlayer::SampleManaCurve(float in) +{ + if(GetCharacterClassProperties().manaCurve) + { + return GetCharacterClassProperties().manaCurve->GetFloatValue(in); + } + return manaCurve->GetFloatValue(in); +} +float ANetworkPlayer::SampleArmorCurve(float in) +{ + if(GetCharacterClassProperties().armorCurve) + { + return GetCharacterClassProperties().armorCurve->GetFloatValue(in); + } + return armorCurve->GetFloatValue(in); +} +float ANetworkPlayer::SampleAttackSpeedCurve(float in) +{ + if(GetCharacterClassProperties().attackSpeedCurve) + { + return GetCharacterClassProperties().attackSpeedCurve->GetFloatValue(in); + } + return attackSpeedCurve->GetFloatValue(in); +} + +bool ANetworkPlayer::HasKey(PlayerKeyType keyType) const +{ + if (keyType == PlayerKeyType::None) + return m_keyState == 0; + return ((1 << (int32)keyType) & m_keyState) != 0; +} +bool ANetworkPlayer::HasAnyKey() const +{ + return m_keyState != 0; +} +bool ANetworkPlayer::AssignKey(PlayerKeyType keyType, ASpawnerBase* origin) +{ + check(Role == ROLE_Authority); + + if (HasKey(keyType)) + return false; + + origin->hasDroppedKey = true; + + // Store the origin for later + m_keyOrigins.Add((uint8)keyType, origin); + m_keyState += (1 << (int32)keyType); + return true; +} +bool ANetworkPlayer::ClearKey(PlayerKeyType keyType) +{ + check(Role == ROLE_Authority); + + if (!HasKey(keyType)) + return false; + + // Allow the boss to drop keys again + ASpawnerBase** find = m_keyOrigins.Find((uint8)keyType); + if (find) + { + if(IsValid(*find)) (*find)->hasDroppedKey = false; + m_keyOrigins.Remove((uint8)keyType); + } + m_keyState -= (1 << (int32)keyType); + return true; +} + +void ANetworkPlayer::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(ANetworkPlayer, m_keyState); +} + +FCharacterClassProperty ANetworkPlayer::GetCharacterClassProperties() const +{ + static FCharacterClassProperty dummy; + if(!Controller) + return dummy; + return Cast(Controller)->GetCharacterClassProperties(); +} + +//Screen Filters +void ANetworkPlayer::SetFilterDuration(float duration, UPostProcessComponent* filter) +{ + FTimerHandle uniqueHandle; + FTimerDelegate stopFilterDelegate = FTimerDelegate::CreateUObject(this, &ANetworkPlayer::StopFilter, filter, uniqueHandle); + GetWorldTimerManager().SetTimer(uniqueHandle, stopFilterDelegate, duration, false); + m_delegates.Add(uniqueHandle); +} + +void ANetworkPlayer::SetTimelineDuration(float duration, UPostProcessComponent* filter) +{ + FTimerHandle uniqueHandle; + FTimerDelegate stopFilterDelegate = FTimerDelegate::CreateUObject(this, &ANetworkPlayer::StopTimeline, filter, uniqueHandle); + GetWorldTimerManager().SetTimer(uniqueHandle, stopFilterDelegate, duration, false); + m_delegates.Add(uniqueHandle); +} + +void ANetworkPlayer::StartFilter(float duration, UPostProcessComponent* filter, UCurveFloat* timelineCurve) +{ + if ( m_filterTimelines.Contains(filter) ) return; + if (timelineCurve) + { + FTimeline timeLine; + FOnTimelineFloatStatic timelineDelegate; + timelineDelegate.BindUObject(this, &ANetworkPlayer::FilterUpdate, filter); + timeLine.AddInterpFloat(timelineCurve, timelineDelegate); + timeLine.SetPlayRate(1.0f / duration); + timeLine.PlayFromStart(); + m_filterTimelines.Add(filter, timeLine); + SetFilterDuration(duration, filter); + } + filter->bEnabled = true; +} + +void ANetworkPlayer::StartFilterWithDelegate(float duration, UPostProcessComponent* filter, UCurveFloat* timelineCurve, FPostAction action) +{ + if ( m_filterTimelines.Contains(filter) ) return; + if (timelineCurve) + { + FTimeline timeLine; + FOnTimelineFloatStatic timelineDelegate; + timelineDelegate.BindUObject(this, &ANetworkPlayer::FilterUpdate, filter, action); + timeLine.AddInterpFloat(timelineCurve, timelineDelegate); + timeLine.SetPlayRate(1.0f / duration); + timeLine.PlayFromStart(); + m_filterTimelines.Add(filter, timeLine); + SetFilterDuration(duration, filter); + } + filter->bEnabled = true; +} + +void ANetworkPlayer::EnableFilter(float duration, UPostProcessComponent* filter, UCurveFloat* timelineCurve) +{ + if (m_filterTimelines.Contains(filter)) return; + if (!timelineCurve) + { + YERROR("No timelineCurve given."); + return; + } + + FTimeline timeLine; + FOnTimelineFloatStatic timelineDelegate; + timelineDelegate.BindUObject(this, &ANetworkPlayer::FilterUpdate, filter); + timeLine.AddInterpFloat(timelineCurve, timelineDelegate); + timeLine.SetPlayRate(1.0f / duration); + timeLine.PlayFromStart(); + m_filterTimelines.Add(filter, timeLine); + SetTimelineDuration(duration, filter); + filter->bEnabled = true; +} + +void ANetworkPlayer::DisableFilter(float duration, UPostProcessComponent* filter, UCurveFloat* timeline) +{ + StartFilter(duration, filter, timeline); +} + +void ANetworkPlayer::EnableFilterWithDelegate(float duration, UPostProcessComponent* filter, UCurveFloat* timelineCurve, FPostAction action) +{ + if (m_filterTimelines.Contains(filter)) return; + if (!timelineCurve) + { + YERROR("No timelineCurve given."); + return; + } + + FTimeline timeLine; + FOnTimelineFloatStatic timelineDelegate; + timelineDelegate.BindUObject(this, &ANetworkPlayer::FilterUpdate, filter, action); + timeLine.AddInterpFloat(timelineCurve, timelineDelegate); + timeLine.SetPlayRate(1.0f / duration); + timeLine.PlayFromStart(); + m_filterTimelines.Add(filter, timeLine); + SetTimelineDuration(duration, filter); + filter->bEnabled = true; +} + +void ANetworkPlayer::DisableFilterWithDelegate(float duration, UPostProcessComponent* filter, UCurveFloat* timeline, FPostAction action) +{ + StartFilterWithDelegate(duration, filter, timeline, action); +} + +void ANetworkPlayer::StopFilter(UPostProcessComponent* filter, FTimerHandle handle) +{ + filter->bEnabled = false; + m_delegates.Remove(handle); + if (!m_filterTimelines.Contains(filter)) + { + YERROR("Can't find filter in timeline."); + return; + } + m_filterTimelines.Remove(filter); +} + +void ANetworkPlayer::StopTimeline( UPostProcessComponent* filter, FTimerHandle handle ) +{ + m_delegates.Remove(handle); + if (!m_filterTimelines.Contains(filter)) + { + YERROR("Can't find filter in timeline."); + return; + } + m_filterTimelines.Remove(filter); +} + +void ANetworkPlayer::FilterUpdate(float value, UPostProcessComponent* filter) +{ + filter->BlendWeight = value; +} + +void ANetworkPlayer::FilterUpdate(float value, UPostProcessComponent* filter, FPostAction action) +{ + action.ExecuteIfBound(value, filter); +} + +void ANetworkPlayer::AddThreatLevel_Client_Implementation(const float a_NewThreat) +{ + threats.Add(a_NewThreat); + + threatLevel = a_NewThreat > threatLevel ? a_NewThreat : threatLevel; +} + +void ANetworkPlayer::RemoveThreatLevel_Client_Implementation(const float a_NewThreat) +{ + threats.RemoveSingle(a_NewThreat); + + if (a_NewThreat == threatLevel) + { + threatLevel = 0; + for (int32 i = 0; i < threats.Num(); i++) + { + if (threats[i] > threatLevel) + { + threatLevel = threats[i]; + } + } + } +} diff --git a/Source/UnrealProject/Creatures/NetworkPlayer.h b/Source/UnrealProject/Creatures/NetworkPlayer.h new file mode 100644 index 0000000..c2c9184 --- /dev/null +++ b/Source/UnrealProject/Creatures/NetworkPlayer.h @@ -0,0 +1,155 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "Creatures/NetworkCharacter.h" +#include "IngameSkillTree.h" +#include "PlayerKeyType.h" +#include "NetworkPlayer.generated.h" + +/** + * + */ +UCLASS() +class UNREALPROJECT_API ANetworkPlayer : public ANetworkCharacter +{ + GENERATED_BODY() + +public: + ANetworkPlayer(); + virtual void BeginPlay() override; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + virtual void Tick(float DeltaSeconds) override; + + // For detecting when the player is possessed + virtual void PossessedBy(AController* NewController) override; + virtual void OnRep_PlayerState() override; + + // Clears all the modifier stat modifications from this character + virtual void ResetModifiers(); + + UFUNCTION(Server, Reliable, WithValidation) + void ToggleSwitch(class ANetworkSwitch* networkSwitch); + + // Server-Side level up event + UFUNCTION(BlueprintNativeEvent, Category = "LevelUp") + void OnLevelUp(int32 newLevel); + // Called server-side when a character levels up + void OnLevelUp_Server(int32 newLevel); + + // Get the class properties of this player + UFUNCTION(BlueprintCallable, Category = "State") + FCharacterClassProperty GetCharacterClassProperties() const; + + UFUNCTION(BlueprintCallable, Category = "Team") + class ANetworkPlayer* GetTeamMate() const; + + DECLARE_DYNAMIC_MULTICAST_DELEGATE_FourParams(FOnPlayerKilled, FText, source, FText, target, int32, sourceNum, int32, targetNum); + UPROPERTY(BlueprintAssignable, Category = "Game") + FOnPlayerKilled onPlayerKilled; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Music") + float threatLevel; + TArray threats; + UFUNCTION(Client, Reliable) + void AddThreatLevel_Client(const float a_NewThreat); + UFUNCTION(Client, Reliable) + void RemoveThreatLevel_Client(const float a_NewThreat); + + // Base Stats for all characters + UPROPERTY(VisibleAnywhere, Category = "Stats") + UCurveFloat* healthCurve; + UPROPERTY(VisibleAnywhere, Category = "Stats") + UCurveFloat* healthRegenCurve; + UPROPERTY(VisibleAnywhere, Category = "Stats") + UCurveFloat* manaCurve; + UPROPERTY(VisibleAnywhere, Category = "Stats") + UCurveFloat* manaRegenCurve; + UPROPERTY(VisibleAnywhere, Category = "Stats") + UCurveFloat* armorCurve; + UPROPERTY(VisibleAnywhere, Category = "Stats") + UCurveFloat* attackSpeedCurve; + + // Use these functions to sample the above curves, these functions also check class specific overrides + float SampleHealthCurve(float in); + float SampleManaCurve(float in); + float SampleArmorCurve(float in); + float SampleAttackSpeedCurve(float in); + + // Creature level scaling logic + // Damage done to creatures + UPROPERTY(VisibleAnywhere, Category = "Stats") + UCurveFloat* creatureDamageTakenCurve; + // Damage dealt by creatures + UPROPERTY(VisibleAnywhere, Category = "Stats") + UCurveFloat* creatureDamageDealtCurve; + + UFUNCTION(BlueprintCallable, Category = "Key") + bool HasKey(PlayerKeyType keyType) const; + UFUNCTION(BlueprintCallable, Category = "Key") + bool HasAnyKey() const; + bool AssignKey(PlayerKeyType keyType, class ASpawnerBase* origin); + bool ClearKey(PlayerKeyType keyType); + + // Starts a filter for a duration with a timeline to control the blending. + UFUNCTION(BlueprintCallable, Category = "ScreenFilter") + void StartFilter(float duration, UPostProcessComponent* filter, UCurveFloat* timeline); + + DECLARE_DYNAMIC_DELEGATE_TwoParams(FPostAction, float, value, UPostProcessComponent*, filter); + + // Starts a filter for a duration with a timeline to control the blending. + UFUNCTION(BlueprintCallable, Category = "ScreenFilter") + void StartFilterWithDelegate(float duration, UPostProcessComponent* filter, UCurveFloat* timeline, FPostAction action); + + // Starts a filter with a timeline to control the blending. + UFUNCTION(BlueprintCallable, Category = "ScreenFilter") + void EnableFilter(float duration, UPostProcessComponent* filter, UCurveFloat* timeline); + + // Stops a filter after a duration with a timeline to control the blending. + UFUNCTION(BlueprintCallable, Category = "ScreenFilter") + void DisableFilter(float duration, UPostProcessComponent* filter, UCurveFloat* timeline); + + // Starts a filter with a timeline to control the blending. + UFUNCTION(BlueprintCallable, Category = "ScreenFilter") + void EnableFilterWithDelegate(float duration, UPostProcessComponent* filter, UCurveFloat* timeline, FPostAction action); + + // Stops a filter after a duration with a timeline to control the blending. + UFUNCTION(BlueprintCallable, Category = "ScreenFilter") + void DisableFilterWithDelegate(float duration, UPostProcessComponent* filter, UCurveFloat* timeline, FPostAction action); + + // The ring below the player + UPROPERTY(BlueprintReadOnly, VisibleAnywhere) + UStaticMeshComponent* playerCircle; + +protected: + void m_SetLevelStats() override; +private: + // Sets the duration of the filter. + // Creates a delagate for StopFilter. + void SetFilterDuration(float duration, UPostProcessComponent* filter); + + // Sets the duration of the timeline. + // Creates a delagate for StopTimeline. + void SetTimelineDuration(float duration, UPostProcessComponent* filter); + + // Disables the filter. + void StopFilter(UPostProcessComponent* filter, FTimerHandle handle); + + // Clears the timeline. + void StopTimeline(UPostProcessComponent* filter, FTimerHandle handle); + + // Updates the blending weight of the filter. + void FilterUpdate(float value, UPostProcessComponent* filter); + void FilterUpdate(float value, UPostProcessComponent* filter, FPostAction action); + + // TODO: Add filter queue. + + bool m_levelStatsInitialized; + + UPROPERTY(Replicated) + uint8 m_keyState; + TMap m_keyOrigins; + + TMap m_filterTimelines; + TArray m_delegates; +}; diff --git a/Source/UnrealProject/Creatures/NetworkPossessable.cpp b/Source/UnrealProject/Creatures/NetworkPossessable.cpp new file mode 100644 index 0000000..6916eee --- /dev/null +++ b/Source/UnrealProject/Creatures/NetworkPossessable.cpp @@ -0,0 +1,106 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "NetworkPossessable.h" +#include "DefaultPlayerController.h" + + +// Sets default values +ANetworkPossessable::ANetworkPossessable() +{ + +} + +// Called every frame +void ANetworkPossessable::Tick( float DeltaTime ) +{ + Super::Tick( DeltaTime ); + + m_UpdateCamera(); +} + + +void ANetworkPossessable::AddMovementInput(FVector WorldDirection, float ScaleValue /*= 1.0f*/, bool bForce /*= false*/) +{ + check(!WorldDirection.ContainsNaN()); + check(!FMath::IsNaN(ScaleValue)); + if(m_AllowedToMove()) + Super::AddMovementInput(WorldDirection, ScaleValue, bForce); +} +void ANetworkPossessable::SetCharacterRotation(FRotator rotator) +{ + if (m_AllowedToRotate()) + SetActorRotation(rotator); + if(Role != ROLE_Authority) + { + m_SetCharacterRotation_Server(rotator.Yaw); + } +} +void ANetworkPossessable::AddControllerYawInput(float Val) +{ + if(m_AllowedToRotate()) + Super::AddControllerYawInput(Val); +} + +bool ANetworkPossessable::m_SetCharacterRotation_Server_Validate(float rotation) +{ + return true; +} +void ANetworkPossessable::m_SetCharacterRotation_Server_Implementation(float rotation) +{ + SetActorRotation(FRotator(0.0f, rotation, 0.0f)); +} + + +bool ANetworkPossessable::m_AllowedToMove() +{ + return true; +} +bool ANetworkPossessable::m_AllowedToRotate() +{ + return true; +} + + +void ANetworkPossessable::m_SetupCamera() +{ + // Create a camera boom + CameraBoom = CreateDefaultSubobject(TEXT("CameraBoom")); + CameraBoom->AttachTo(RootComponent); + CameraBoom->bAbsoluteRotation = true; + CameraBoom->TargetArmLength = 1500.f; + CameraBoom->RelativeRotation = FRotator(-60.f, 45.f, 0.f); + CameraBoom->bDoCollisionTest = false; + CameraBoom->bInheritPitch = false; + CameraBoom->bInheritYaw = false; + CameraBoom->bInheritRoll = false; + + // Create a camera + TopDownCamera = CreateDefaultSubobject(TEXT("TopDownCamera")); + TopDownCamera->AttachTo(CameraBoom, USpringArmComponent::SocketName); + TopDownCamera->bUsePawnControlRotation = false; + + // Don't rotate character to camera direction + bUseControllerRotationPitch = false; + bUseControllerRotationYaw = false; + bUseControllerRotationRoll = false; +} +void ANetworkPossessable::m_UpdateCamera() +{ + if (!IsValid(CameraBoom)) return; + + ADefaultPlayerController* const controller = Cast(GetController()); + if (IsValid(controller)) + { + // Ensure the camera does not fall under the ground + const FVector playerCoord = GetActorLocation(); + if (playerCoord.Z < 0) + CameraBoom->SetRelativeLocation(FVector(0, 0, -playerCoord.Z)); + else if (CameraBoom->GetComponentLocation().Z < 0) + CameraBoom->SetRelativeLocation(FVector()); + + // Update last position + if (GetCharacterMovement()->IsMovingOnGround()) + controller->UpdatePlayerPosition(GetActorLocation(), GetActorRotation()); + } +} \ No newline at end of file diff --git a/Source/UnrealProject/Creatures/NetworkPossessable.h b/Source/UnrealProject/Creatures/NetworkPossessable.h new file mode 100644 index 0000000..f68e922 --- /dev/null +++ b/Source/UnrealProject/Creatures/NetworkPossessable.h @@ -0,0 +1,35 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "CharacterBase.h" +#include "NetworkPossessable.generated.h" + +UCLASS(Abstract) +class UNREALPROJECT_API ANetworkPossessable : public ACharacterBase +{ + GENERATED_BODY() +public: + ANetworkPossessable(); + + virtual void Tick( float DeltaSeconds ) override; + + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true")) + class USpringArmComponent* CameraBoom; + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true")) + class UCameraComponent* TopDownCamera; + + virtual void AddMovementInput(FVector WorldDirection, float ScaleValue = 1.0f, bool bForce = false) override; + virtual void SetCharacterRotation(FRotator rotator); + virtual void AddControllerYawInput(float Val) override; + +protected: + UFUNCTION(Server, Reliable, WithValidation) + void m_SetCharacterRotation_Server(float rotation); + + virtual bool m_AllowedToMove(); + virtual bool m_AllowedToRotate(); + + void m_SetupCamera(); + void m_UpdateCamera(); +}; diff --git a/Source/UnrealProject/Creatures/OffensiveEnemy.cpp b/Source/UnrealProject/Creatures/OffensiveEnemy.cpp new file mode 100644 index 0000000..6650b75 --- /dev/null +++ b/Source/UnrealProject/Creatures/OffensiveEnemy.cpp @@ -0,0 +1,140 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "OffensiveEnemy.h" +#include "AbilityInfo.h" +#include "SpawnerBase.h" +#include "NetworkPlayer.h" +#include "MinionAnimInstance.h" +#include "BehaviorTree/EnemyAIController.h" + + +AOffensiveEnemy::AOffensiveEnemy() +{ + PrimaryActorTick.bCanEverTick = true; +} +void AOffensiveEnemy::BeginPlay() +{ + Super::BeginPlay(); + if (Role != ROLE_Authority) + return; + +} +void AOffensiveEnemy::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + Super::EndPlay(EndPlayReason); + if (Role != ROLE_Authority) + return; +} +void AOffensiveEnemy::Tick(float deltaTime) +{ + Super::Tick(deltaTime); + if (Role != ROLE_Authority) + return; + + if (IsValid(target)) + { + if (hasGeneral) + { + GeneralBehavior(); + } + else + { + if (m_confusionTimer > 0.0f) + { + ConfusionBehavior(); + return; + } + BasicBehavior(); + } + // Move to target + + } + +} + +void AOffensiveEnemy::GeneralBehavior() +{ + if (!meleeAbility || !specialAbility) + { + //RERROR("there is no abilities"); + return; + } + + const FVector2D actorLocation = FVector2D(GetActorLocation().X, GetActorLocation().Y); + const FVector2D targetLocation = FVector2D(target->GetActorLocation().X, target->GetActorLocation().Y); + const float targetDistSqr = FVector2D::DistSquared(targetLocation, actorLocation); + m_tempAbility = meleeAbility; + if (((m_tempAbility->AICastRange + 150)*(m_tempAbility->AICastRange + 150)) < targetDistSqr) + return; + if (GetAbilityState(m_tempAbility)->onCooldownTimer <= 0) + { + CastAbility(m_tempAbility); + + UMinionAnimInstance* animInstance = Cast(GetMesh()->GetAnimInstance()); + if (IsValid(animInstance)) + { + animInstance->ChangeAnimationStateTo(EMinionAnimState::MAS_Attacking); + } + + //m_rotateToPlayer = m_tempAbility->rotateTowardsPlayer; + } +} +void AOffensiveEnemy::BasicBehavior() +{ + if (!IsValid(target)) + return; + if (FVector::DistSquared(target->GetActorLocation(), GetActorLocation()) > 200 * 200) + { + MoveToPoint(target->GetActorLocation()); + } + else + { + FVector addvec = GetActorLocation() - target->GetActorLocation(); + addvec.Normalize(); + MoveToPoint(target->GetActorLocation()+addvec*200.0f); + GetCharacterMovement()->Velocity = FVector::ZeroVector; + } + SetActorRotation(FVector(target->GetActorLocation() - GetActorLocation()).Rotation()); + if (!meleeAbility || !specialAbility) + { + //RERROR("there is no abilities"); + return; + } + const FVector2D actorLocation = FVector2D(GetActorLocation().X, GetActorLocation().Y); + const FVector2D targetLocation = FVector2D(target->GetActorLocation().X, target->GetActorLocation().Y); + const float targetDistSqr = FVector2D::DistSquared(targetLocation, actorLocation); + m_tempAbility = meleeAbility; + if ((rand() % 1000) > specialAbilityChance&&!m_isPulled) + { + m_tempAbility = specialAbility; + } + m_isPulled = true; + if (((m_tempAbility->AICastRange + 150)*(m_tempAbility->AICastRange + 150)) < targetDistSqr) + return; + if (GetAbilityState(m_tempAbility)->onCooldownTimer <= 0) + { + CastAbility(m_tempAbility); + + UMinionAnimInstance* animInstance = Cast(GetMesh()->GetAnimInstance()); + if (IsValid(animInstance)) + { + animInstance->ChangeAnimationStateTo(EMinionAnimState::MAS_Attacking); + } + + + } + +} +int32 AOffensiveEnemy::NativeDealDamage(class ANetworkCharacter* dealer, int32 damage, float armorPercentageIgnore, class UAbilityInfo* ability) +{ + if (hasGeneral) + { + float damageMultiplier = FVector::DotProduct(dealer->GetActorForwardVector(), GetActorForwardVector()); + damageMultiplier += 1.0f; + damageMultiplier *= 0.5f; + damageMultiplier += 0.5f; + return Super::NativeDealDamage(dealer, damage*damageMultiplier, armorPercentageIgnore, ability); + } + else return Super::NativeDealDamage(dealer, damage, armorPercentageIgnore, ability); +} \ No newline at end of file diff --git a/Source/UnrealProject/Creatures/OffensiveEnemy.h b/Source/UnrealProject/Creatures/OffensiveEnemy.h new file mode 100644 index 0000000..2fa1209 --- /dev/null +++ b/Source/UnrealProject/Creatures/OffensiveEnemy.h @@ -0,0 +1,24 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "Creatures/EnemyBase.h" +#include "OffensiveEnemy.generated.h" + +/** + * + */ +UCLASS() +class UNREALPROJECT_API AOffensiveEnemy : public AEnemyBase +{ + GENERATED_BODY() + +public: + AOffensiveEnemy(); + virtual void BeginPlay() override; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + virtual void Tick(float deltaTime) override; + virtual int32 NativeDealDamage(class ANetworkCharacter* dealer, int32 damage, float armorPercentageIgnore, class UAbilityInfo* ability) override; + void BasicBehavior(); + void GeneralBehavior(); +}; diff --git a/Source/UnrealProject/Creatures/PlayerKeyType.h b/Source/UnrealProject/Creatures/PlayerKeyType.h new file mode 100644 index 0000000..d530068 --- /dev/null +++ b/Source/UnrealProject/Creatures/PlayerKeyType.h @@ -0,0 +1,16 @@ +// Project Lab - NHTV Igad + +#pragma once + +UENUM(BlueprintType) +enum class PlayerKeyType : uint8 +{ + BossRoomKeyPart0 = 0, + BossRoomKeyPart1 = 1, + BossRoomKeyPart2 = 2, + TreasureRoomKey = 3, + None = 4, +}; + +constexpr int32 keyFragmentMin = (int32)PlayerKeyType::BossRoomKeyPart0; +constexpr int32 keyFragmentMax = (int32)PlayerKeyType::BossRoomKeyPart2; diff --git a/Source/UnrealProject/Creatures/RangedEnemy.cpp b/Source/UnrealProject/Creatures/RangedEnemy.cpp new file mode 100644 index 0000000..e99d253 --- /dev/null +++ b/Source/UnrealProject/Creatures/RangedEnemy.cpp @@ -0,0 +1,136 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "RangedEnemy.h" +#include "AbilityInfo.h" +#include "SpawnerBase.h" +#include "NetworkPlayer.h" +#include "MinionAnimInstance.h" + +ARangedEnemy::ARangedEnemy() +{ + PrimaryActorTick.bCanEverTick = true; +} +void ARangedEnemy::BeginPlay() +{ + Super::BeginPlay(); + if (Role != ROLE_Authority) + return; +} +void ARangedEnemy::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + Super::EndPlay(EndPlayReason); + if (Role != ROLE_Authority) + return; +} +void ARangedEnemy::Tick(float deltaTime) +{ + Super::Tick(deltaTime); + if (Role != ROLE_Authority) + return; + if (IsValid(target)) + { + + if (!hasGeneral) + { + if (m_confusionTimer > 0.0f) + { + ConfusionBehavior(); + return; + } + BasicBehavior(); + } + else + { + GeneralBehavior(); + } + // Move to target + + } + +} +void ARangedEnemy::BasicBehavior() +{ + if (!IsValid(target)) + return; + const FVector2D actorLocation = FVector2D(GetActorLocation().X, GetActorLocation().Y); + const FVector2D targetLocation = FVector2D(target->GetActorLocation().X, target->GetActorLocation().Y); + const FVector2D spawnLocation = FVector2D(m_spawn->GetActorLocation()); + const float targetDistSqr = FVector2D::DistSquared(targetLocation, actorLocation); + const float spawnTargetDistSqr = FVector2D::DistSquared(targetLocation, spawnLocation); + + if (!meleeAbility || !specialAbility) + { + RERROR("there is no abilities"); + return; + } + if (!rangedAbility) + { + RERROR("Ranged enemy does not have a ranged ability"); + } + float castRange = rangedAbility->AICastRange; + // walking code + float testPercentage = (spawnTargetDistSqr) / ((m_spawn->aggroRadius + castRange)*(m_spawn->aggroRadius + castRange)); + + FVector newLocation = (target->GetActorLocation() - m_spawn->GetActorLocation()); + newLocation.Normalize(); + newLocation *= m_spawn->aggroRadius*testPercentage; + FVector walkLocation = newLocation + m_spawn->GetActorLocation(); + if (m_lastPercentage < testPercentage) + MoveToPoint(walkLocation); + m_lastPercentage = testPercentage; + + m_tempAbility = rangedAbility; + if (targetDistSqr < (200 * 200)) + { + m_tempAbility = meleeAbility; + if ((rand() % 1000) > specialAbilityChance) + { + m_tempAbility = specialAbility; + } + } + if (((m_tempAbility->AICastRange + 100)*(m_tempAbility->AICastRange + 100)) < targetDistSqr) + return; + if (GetAbilityState(m_tempAbility)->onCooldownTimer <= 0) + { + ChangeNPCAnimation((uint8)EMinionAnimState::MAS_Casting); + CastAbility(m_tempAbility); + } + SetActorRotation(FVector(target->GetActorLocation() - GetActorLocation()).Rotation()); +} +void ARangedEnemy::GeneralBehavior() +{ + const FVector2D actorLocation = FVector2D(GetActorLocation().X, GetActorLocation().Y); + const FVector2D targetLocation = FVector2D(target->GetActorLocation().X, target->GetActorLocation().Y); + const FVector2D spawnLocation = FVector2D(m_spawn->GetActorLocation()); + const float targetDistSqr = FVector2D::DistSquared(targetLocation, actorLocation); + const float spawnTargetDistSqr = FVector2D::DistSquared(targetLocation, spawnLocation); + + if (!meleeAbility || !specialAbility) + { + RERROR("there is no abilities"); + return; + } + if (!rangedAbility) + { + RERROR("Ranged enemy does not have a ranged ability"); + } + float castRange = rangedAbility->AICastRange; + // walking code + + + m_tempAbility = rangedAbility; + if (targetDistSqr < (200 * 200)) + { + m_tempAbility = meleeAbility; + } + if (((m_tempAbility->AICastRange + 100)*(m_tempAbility->AICastRange + 100)) < targetDistSqr) + return; + if (GetAbilityState(m_tempAbility)->onCooldownTimer <= 0) + { + ChangeNPCAnimation((uint8)EMinionAnimState::MAS_Casting); + CastAbility(m_tempAbility); + m_rotateToPlayer = m_tempAbility->rotateTowardsPlayer; + } + SetActorRotation(FVector(target->GetActorLocation() - GetActorLocation()).Rotation()); +} \ No newline at end of file diff --git a/Source/UnrealProject/Creatures/RangedEnemy.h b/Source/UnrealProject/Creatures/RangedEnemy.h new file mode 100644 index 0000000..24afff7 --- /dev/null +++ b/Source/UnrealProject/Creatures/RangedEnemy.h @@ -0,0 +1,26 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "Creatures/EnemyBase.h" +#include "RangedEnemy.generated.h" + +/** + * + */ +UCLASS() +class UNREALPROJECT_API ARangedEnemy : public AEnemyBase +{ + GENERATED_BODY() + +public: + ARangedEnemy(); + virtual void BeginPlay() override; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + virtual void Tick(float deltaTime) override; + void BasicBehavior(); + void GeneralBehavior(); +private: + float m_lastPercentage; + class UAbilityInfo* m_tempAbility; +}; diff --git a/Source/UnrealProject/Creatures/TouhouBoss.cpp b/Source/UnrealProject/Creatures/TouhouBoss.cpp new file mode 100644 index 0000000..0443979 --- /dev/null +++ b/Source/UnrealProject/Creatures/TouhouBoss.cpp @@ -0,0 +1,175 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "TouhouBoss.h" +#include "CreatureSpawn.h" +#include "AbilityEventGroup.h" +#include "AbilityInfo.h" +#include "Modifier.h" +#include "NativeModifiers.h" +#include "DefaultGameMode.h" +#include "NetworkPlayer.h" + +ATouhouBoss::ATouhouBoss() +{ + PrimaryActorTick.bCanEverTick = true; + +/* AudioComp = CreateDefaultSubobject(TEXT("Boss Audio")); + if (AudioComp) + { + + AudioComp->bAutoActivate = false; // with this true the sounds play at spawn (game starts) + } + */ + outOfCombatRegen = 25.0f; + secondPhasePercentage = 0.5f; + rotationSpeed = 1; + cooldown = 2.5f; +} + +void ATouhouBoss::BeginPlay() +{ + Super::BeginPlay(); + if (Role != ROLE_Authority) + return; + + m_engaged = false; +} +void ATouhouBoss::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + Super::EndPlay(EndPlayReason); + + if (Role != ROLE_Authority) + return; + m_walkTimer = 0.0f; + + // Drop a key + if (m_health <= 0) + { + ANetworkPlayer* const killedBy = Cast(GetLastPlayerDamage()); + if (IsValid(killedBy) && IsValid(m_spawn)) + { + killedBy->AssignKey(PlayerKeyType::TreasureRoomKey, m_spawn); + onBossKilled.Broadcast(killedBy->GetTeam()); + } + } +} + +void ATouhouBoss::m_Engaged() +{ + Super::m_Engaged(); + m_engaged = true; + ModifierManager* mod = GetModifierManager(); + if (mod != nullptr && m_regenModifier != nullptr) + { + + // Stop regenerating health. + m_regenModifier->ForceDestroy(); + m_regenModifier = nullptr; + SetShield(false); + // Plays a sound indicating that the boss was engaged and start playing the boss music. + //PlayEngageSound(); + } +} + +void ATouhouBoss::m_Disengaged() +{ + Super::m_Disengaged(); + + ModifierManager* mod = GetModifierManager(); + if (mod != nullptr && m_regenModifier == nullptr) + { + m_engaged = false; + // Start regenerating health. + m_regenModifier = mod->AddModifier(ARegenModifier::StaticClass(), 0); + Cast(m_regenModifier)->regenPerTick = outOfCombatRegen; + // Remove invincibility if it's active. + if (m_invincibilityModifier) + { + m_invincibilityModifier->ForceDestroy(); + m_invincibilityModifier = nullptr; + SetShield(false); + } + } +} + +void ATouhouBoss::Tick(float deltaTime) +{ + Super::Tick(deltaTime); + + // Only execute boss code on the server. + if (Role != ROLE_Authority) + return; + + // Calculate health as percentage for deciding which phase the boss should be in. + float healthPerc = (float)m_health / (float)m_maxHealth; + + // If the distance of the target to the spawn is greater than the aggro range of the spawner, +// then lose the target. + if (IsValid(target)) + { + float targetDistSqr = FVector::DistSquared(target->GetActorLocation(), m_spawn->SpawnResetPosition()); + float aggroSqr = m_spawn->deaggroRadius * m_spawn->deaggroRadius; + if (aggroSqr < targetDistSqr) + { + target = nullptr; + } + } + + //disengage + if (!IsValid(target) && m_regenModifier == nullptr) + { + m_Disengaged(); + } + //engage + else if (target && !m_engaged) + { + m_Engaged(); + } + + if (m_engaged) + { + if (m_invincibilityModifier) + { + m_invincibilityModifier->ForceDestroy(); + m_invincibilityModifier = nullptr; + SetShield(false); + } + if (!IsValid(target)) + return; + WalkPhase(deltaTime); + } + +} + +void ATouhouBoss::WalkPhase(float deltaTime) +{ + MoveToPoint(target->GetActorLocation()); + m_walkTimer += deltaTime; + + // After 2.5 seconds, cast a random ability. + if (m_walkTimer > cooldown && abilities.Num() > 0 && target) + { + // Cast a random ability + UAbilityInfo* ability = abilities[FMath::Rand() % abilities.Num()]; + if(IsValid(ability)) + CastAbility(ability); + + m_walkTimer = 0; + } +} + +void ATouhouBoss::m_SpawnModifiers() +{ + /*ModifierManager* mod = GetModifierManager(); + if (mod != nullptr) + { + //m_regenModifier = mod->AddModifier(ARegenModifier::StaticClass(), 0); + }*/ +} + +void ATouhouBoss::SetShield_Implementation(bool on) +{ + FWARNING("shield implemtentation not set in ATouHouBoss"); + return; +} \ No newline at end of file diff --git a/Source/UnrealProject/Creatures/TouhouBoss.h b/Source/UnrealProject/Creatures/TouhouBoss.h new file mode 100644 index 0000000..33b5a51 --- /dev/null +++ b/Source/UnrealProject/Creatures/TouhouBoss.h @@ -0,0 +1,60 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "Creatures/BossBase.h" +#include "TouhouBoss.generated.h" + +/** + * + */ +UCLASS() +class UNREALPROJECT_API ATouhouBoss : public ABossBase +{ + GENERATED_BODY() + +public: + ATouhouBoss(); + virtual void BeginPlay() override; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + virtual void Tick(float deltaTime) override; + void WalkPhase(float deltaTime); + virtual void m_SpawnModifiers() override; + + //UFUNCTION(NetMulticast, Reliable) + //void PlayEngageSound(); + + //UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Audio Component") + //UAudioComponent* AudioComp; + + DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnBossKilled, int32, team); + UPROPERTY(BlueprintAssignable, Category = "Game") + FOnBossKilled onBossKilled; + + UPROPERTY(EditDefaultsOnly, Category = "Shoot Phase") + class UAbilityInfo* ability; + UPROPERTY(EditDefaultsOnly, Category = "Shoot Phase") + class UAbilityInfo* abilityChannel; + UPROPERTY(EditDefaultsOnly, Category = "Shoot Phase") + float rotationSpeed; + UPROPERTY(EditDefaultsOnly, Category = "Shoot Phase") + float cooldown; + UPROPERTY(EditAnywhere, Category = "Modifiers") + float outOfCombatRegen; + UPROPERTY(EditAnywhere, Category = "State Transition", meta = (ClampMin = "0.0", ClampMax = "1.0", UIMin = "0.0", UIMax = "1.0")) + float secondPhasePercentage; + + UFUNCTION(BlueprintNativeEvent) + void SetShield(bool on); +private: + virtual void m_Engaged(); + virtual void m_Disengaged(); + float m_walkTimer; + bool m_engaged; + + UPROPERTY() + class AModifier* m_regenModifier; + + UPROPERTY() + class AModifier* m_invincibilityModifier; +}; diff --git a/Source/UnrealProject/Creatures/WiebertAnimation.cpp b/Source/UnrealProject/Creatures/WiebertAnimation.cpp new file mode 100644 index 0000000..4295ade --- /dev/null +++ b/Source/UnrealProject/Creatures/WiebertAnimation.cpp @@ -0,0 +1,62 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "WiebertAnimation.h" +#include "NetworkCharacter.h" +#include "Animation/AnimNodeBase.h" +#include "AnimationProxy.h" + +UWiebertAnimation::UWiebertAnimation(const FObjectInitializer& init) + : Super(init) +{ + character = nullptr; + attacking = false; + charCustomization = FCharacterCustomization(); + m_swingAnimationSequence = 0; +} +void UWiebertAnimation::NativeInitializeAnimation() +{ + attacking = false; + Super::NativeInitializeAnimation(); + character = Cast(this->GetOwningActor()); + if (character) + { + m_swingAnimationSequence = character->swingAnimationSequence; + } +} +void UWiebertAnimation::NativeUpdateAnimation(float DeltaSeconds) +{ + if(character && character->swingAnimationSequence > m_swingAnimationSequence) + { + attacking = true; + m_swingAnimationSequence = character->swingAnimationSequence; + } +} +void UWiebertAnimation::SetCharacterCustomization(const FCharacterCustomization& characterCustomization) +{ + charCustomization = characterCustomization; +} +FAnimInstanceProxy* UWiebertAnimation::CreateAnimInstanceProxy() +{ + check(!m_mainAnimProxy); + m_mainAnimProxy = new FMainAnimProxy(); + + m_mainAnimProxy->boneNames[0] = "head"; + m_mainAnimProxy->boneNames[1] = "thigh_l"; + m_mainAnimProxy->boneNames[2] = "thigh_r"; + m_mainAnimProxy->boneNames[3] = "upperarm_l"; + m_mainAnimProxy->boneNames[4] = "upperarm_r"; + m_mainAnimProxy->boneNames[5] = "spine_03"; + m_mainAnimProxy->boneNames[6] = "root"; + + return m_mainAnimProxy; +} +void UWiebertAnimation::DestroyAnimInstanceProxy(FAnimInstanceProxy* InProxy) +{ + check(InProxy == m_mainAnimProxy); + delete InProxy; + m_mainAnimProxy = nullptr; +} +void UWiebertAnimation::OnSwingAnimation_Implementation() +{ +} diff --git a/Source/UnrealProject/Creatures/WiebertAnimation.h b/Source/UnrealProject/Creatures/WiebertAnimation.h new file mode 100644 index 0000000..6ff00c5 --- /dev/null +++ b/Source/UnrealProject/Creatures/WiebertAnimation.h @@ -0,0 +1,42 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "Animation/AnimInstance.h" +#include "PlayerSetupState.h" +#include "WiebertAnimation.generated.h" + +/** + * + */ +UCLASS() +class UNREALPROJECT_API UWiebertAnimation : public UAnimInstance +{ + GENERATED_BODY() + +public: + UWiebertAnimation(const FObjectInitializer& init); + + virtual void NativeInitializeAnimation() override; + virtual void NativeUpdateAnimation(float DeltaSeconds) override; + + virtual FAnimInstanceProxy* CreateAnimInstanceProxy(); + virtual void DestroyAnimInstanceProxy(FAnimInstanceProxy* InProxy); + + UFUNCTION(BlueprintNativeEvent, Category="Animation") + void OnSwingAnimation(); + + UFUNCTION(BlueprintCallable, Category = "Animation") + void SetCharacterCustomization(const FCharacterCustomization& characterCustomization); + + UPROPERTY(BlueprintReadOnly, Category="Animation") + class ANetworkCharacter* character; + UPROPERTY(BlueprintReadWrite, Category = "Animation") + bool attacking; + + FCharacterCustomization charCustomization; +private: + // Keeps track of how many times an animation was triggered + int32 m_swingAnimationSequence; + struct FMainAnimProxy* m_mainAnimProxy; +}; diff --git a/Source/UnrealProject/Doodads/BaseSkillObject.cpp b/Source/UnrealProject/Doodads/BaseSkillObject.cpp new file mode 100644 index 0000000..0f0fe9f --- /dev/null +++ b/Source/UnrealProject/Doodads/BaseSkillObject.cpp @@ -0,0 +1,15 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "BaseSkillObject.h" + +/* +void UBaseSkillObject::Serialize( FArchive& a_ar ) +{ + Super::Serialize( a_ar ); + + UE_LOG( LogTemp, Warning, TEXT( "UBaseSkillObject: Serializing..." ) ); + + a_ar << *this; +} +*/ \ No newline at end of file diff --git a/Source/UnrealProject/Doodads/BaseSkillObject.h b/Source/UnrealProject/Doodads/BaseSkillObject.h new file mode 100644 index 0000000..7a9805f --- /dev/null +++ b/Source/UnrealProject/Doodads/BaseSkillObject.h @@ -0,0 +1,70 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "SkillObject.h" +#include "Items/ItemBase.h" +#include "BaseSkillObject.generated.h" + +class AModifier; + +USTRUCT() +struct FSkillItem +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere) + USkeletalMesh* mesh; + UPROPERTY(EditAnywhere) + EItemTypeEnum type; +}; + +USTRUCT() +struct FSkillItemHolder +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere) + bool isVisible; + UPROPERTY(EditAnywhere, meta = (EditCondition = "isVisible")) + TArray itemsToEquip; + UPROPERTY(EditDefaultsOnly, meta = (EditCondition = "isVisible")) + UParticleSystem* particleEffect; +}; + +UENUM(BlueprintType) +enum class ESkillShapeType : uint8 +{ + Active = 0, + Passive, + Sustain, + Coop, +}; + +UCLASS(Blueprintable) +class UNREALPROJECT_API UBaseSkillObject : public USkillObject +{ + GENERATED_BODY() + +public: + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Skill") + TArray> modifiers; + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Skill") + TArray abilityEffects; + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Skill") + ESkillShapeType skillShapeType; + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Skill") + FSkillItemHolder visibleItems; + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Skill") + FIntPoint pivot; + + /* + virtual void Serialize( FArchive& a_ar ) override; + + friend FArchive& operator<<( FArchive& a_ar, UBaseSkillObject& a_v ) + { + UE_LOG( LogTemp, Warning, TEXT( "UBaseSkillObject: << Operator..." ) ); + return a_ar; + } + */ +}; diff --git a/Source/UnrealProject/Doodads/CharacterCarousel.cpp b/Source/UnrealProject/Doodads/CharacterCarousel.cpp new file mode 100644 index 0000000..742e777 --- /dev/null +++ b/Source/UnrealProject/Doodads/CharacterCarousel.cpp @@ -0,0 +1,256 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "CharacterCarousel.h" +#include "CharacterBase.h" +#include "DefaultGameInstance.h" + + + +ACharacterCarousel::ACharacterCarousel() +{ + PrimaryActorTick.bCanEverTick = true; + + selectedCharacterIndex = 0; + m_visualSelected = 0; + selected = false; + + m_zoom = 0; + + selectedCharacterOffset = FVector(30, -120, 0); +} + + +void ACharacterCarousel::BeginPlay() +{ + check(IsValid(characterBlueprint)); + + Super::BeginPlay(); + + // Fix the rotation + FRotator rotation = GetActorRotation(); + SetActorRotation(FRotator(0, rotation.Yaw, 0)); + + const FVector position = GetActorLocation(); + const FVector forward = GetActorForwardVector(); + const FVector right = GetActorRightVector(); + + UDefaultGameInstance* instance = Cast(GetGameInstance()); + check(instance); + UCharacterSettings* settings = instance->GetCharacterSettings(); + check(settings); + + for (int32 i = 0; i < settings->characterSaves.Num(); i++) + { + FActorSpawnParameters spawnParams; + spawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; + ACharacterBase* actor = GetWorld()->SpawnActor(characterBlueprint, GetActorLocation(), GetActorRotation(), spawnParams); + m_characters.Add(actor); + } +} +void ACharacterCarousel::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + Super::EndPlay(EndPlayReason); + + for (int32 i = 0; i < m_characters.Num(); i++) + { + if (IsValid(m_characters[i])) + m_characters[i]->Destroy(); + } + m_characters.SetNum(0); +} + + +inline float MoveTowardsTarget(float current, float target, float delta, float min = 0.0005f) +{ + const int32 moveDir = target > current ? 1 : -1; + float d = FMath::Abs((target - current) * delta); + if (d < 0.0005f) d = 0.0005f; + + current += d * moveDir; + + // Clamp + current = (moveDir == 1) ? + (current > target ? target : current) : + (current < target ? target : current); + + return current; +} +void ACharacterCarousel::Tick(float DeltaTime) +{ + Super::Tick(DeltaTime); + + if (m_characters.Num() == 0) + return; + + // Zoom towards the target + const float zoomTarget = selected ? 1 : 0; + m_zoom = MoveTowardsTarget(m_zoom, zoomTarget, DeltaTime * 10); + + const FVector position = GetActorLocation(); + const FVector forward = GetActorForwardVector(); + const FVector right = GetActorRightVector(); + + const float carouselRadius = 1200; + const float carouselAngle = 10; + const FVector pivot = position - forward * carouselRadius; + + // Clamp the selected index (may be altered from a blueprint) + selectedCharacterIndex = selectedCharacterIndex < 0 ? 0 : selectedCharacterIndex >= m_characters.Num() ? m_characters.Num() - 1 : selectedCharacterIndex; + + // Animate the carousel + const float target = float(selectedCharacterIndex); + m_visualSelected = MoveTowardsTarget(m_visualSelected, target, DeltaTime * (selected ? 10 : 5)); + + // Spawn and set the characters + const FRotator rotation = GetActorRotation(); + const FVector arm = forward * carouselRadius; + const FVector selectedOffset = rotation.RotateVector(selectedCharacterOffset) * m_zoom; + + for (int32 i = 0; i < m_characters.Num(); i++) + { + const int32 distance = i - selectedCharacterIndex; + if (distance < -2 || distance > 2) + { + // Dont render characters off screen + m_characters[i]->GetRootComponent()->SetVisibility(false, true); + } + else + { + m_characters[i]->GetRootComponent()->SetVisibility(true, true); + + // Set the position relative from the pivot + FRotator rotator(0, -carouselAngle * (float(i) - m_visualSelected), 0); + if (m_zoom > 0) rotator.Yaw *= (m_zoom + 1); + FVector offset = rotator.RotateVector(arm); + if (i == selectedCharacterIndex) + offset += selectedOffset; + m_characters[i]->SetActorLocation(pivot + offset); + m_characters[i]->SetActorRotation(rotator + FRotator(0, rotation.Yaw, 0)); + } + } +} + + +void ACharacterCarousel::Reset() +{ + selectedCharacterIndex = 0; + m_visualSelected = 0; + selected = false; + + m_zoom = 0; +} + +void ACharacterCarousel::Next() +{ + if(!selected && m_characters.Num() > 0 && selectedCharacterIndex < m_characters.Num() - 1) + selectedCharacterIndex++; +} +void ACharacterCarousel::Previous() +{ + if (!selected && selectedCharacterIndex > 0) + selectedCharacterIndex--; +} + +void ACharacterCarousel::ToggleSelect(bool enabled) +{ + selected = enabled; +} + +void ACharacterCarousel::CreateNewCharacter(const FString& name) +{ + UDefaultGameInstance* instance = Cast(GetGameInstance()); + check(instance); + UCharacterSettings* settings = instance->GetCharacterSettings(); + check(settings); + + // Create a scene actor + ACharacterBase* actor = GetWorld()->SpawnActor(characterBlueprint, GetActorLocation(), GetActorRotation()); + m_characters.Add(actor); + + // Select this character + selected = false; + selectedCharacterIndex = m_characters.Num() - 1; + m_zoom = 0; + + // Add to save + FCharacterSave save; + save.name = name; + settings->characterSaves.Add(save); + instance->SaveSettings(); +} +void ACharacterCarousel::DeleteCharacter(int32 index) +{ + UDefaultGameInstance* instance = Cast(GetGameInstance()); + check(instance); + UCharacterSettings* settings = instance->GetCharacterSettings(); + check(settings); + + if (index < 0 || index >= settings->characterSaves.Num()) + { + JERROR("Invalid character index"); + return; + } + + // Destroy scene actor + m_characters[index]->Destroy(); + m_characters.RemoveAt(index); + if (index == selectedCharacterIndex) + selectedCharacterIndex--; + + // Unselect + selected = false; + m_zoom = 0; + + // Remove from save + settings->characterSaves.RemoveAt(index); + instance->SaveSettings(); +} + +ACharacterBase* ACharacterCarousel::GetCharacterModel(int32 index) +{ + if (index < 0 || index >= m_characters.Num()) + { + JERROR("Invalid character index"); + return nullptr; + } + + return m_characters[index]; +} +int32 ACharacterCarousel::GetCharacterNum() +{ + return m_characters.Num(); +} + + +void ACharacterCarousel::SaveCharacterName(const FString& name, int32 index) +{ + UDefaultGameInstance* instance = Cast(GetGameInstance()); + check(instance); + UCharacterSettings* settings = instance->GetCharacterSettings(); + check(settings); + + if (index < 0 || index >= settings->characterSaves.Num()) + { + JERROR("Invalid character index"); + return; + } + + settings->characterSaves[index].name = name; + instance->SaveSettings(); +} +FString ACharacterCarousel::GetCharacterName(int32 index) +{ + UDefaultGameInstance* instance = Cast(GetGameInstance()); + check(instance); + UCharacterSettings* settings = instance->GetCharacterSettings(); + check(settings); + + if (index < 0 || index >= settings->characterSaves.Num()) + { + JERROR("Invalid character index"); + return FString(); + } + + return settings->characterSaves[index].name; +} \ No newline at end of file diff --git a/Source/UnrealProject/Doodads/CharacterCarousel.h b/Source/UnrealProject/Doodads/CharacterCarousel.h new file mode 100644 index 0000000..a9011aa --- /dev/null +++ b/Source/UnrealProject/Doodads/CharacterCarousel.h @@ -0,0 +1,70 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "GameFramework/Actor.h" +#include "CharacterSettings.h" +#include "CharacterCarousel.generated.h" + +UCLASS() +class UNREALPROJECT_API ACharacterCarousel : public AActor +{ + GENERATED_BODY() + +public: + // Sets default values for this actor's properties + ACharacterCarousel(); + + // Called when the game starts or when spawned + virtual void BeginPlay() override; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + + // Called every frame + virtual void Tick(float DeltaSeconds) override; + + + UFUNCTION(BlueprintCallable, Category = "Carousel") + void Reset(); + + UFUNCTION(BlueprintCallable, Category = "Carousel") + void Next(); + UFUNCTION(BlueprintCallable, Category = "Carousel") + void Previous(); + + UFUNCTION(BlueprintCallable, Category = "Carousel") + void ToggleSelect(bool enabled); + + UFUNCTION(BlueprintCallable, Category = "Carousel") + void CreateNewCharacter(const FString& name); + UFUNCTION(BlueprintCallable, Category = "Carousel") + void DeleteCharacter(int32 index); + + // Index of the current selected character + UPROPERTY(BlueprintReadWrite, Category = "Carousel") + int32 selectedCharacterIndex; + + // Wether the current character is also selected + UPROPERTY(BlueprintReadWrite, Category = "Carousel") + bool selected; + + UPROPERTY(EditAnywhere, Category = "Carousel") + TSubclassOf characterBlueprint; + + UFUNCTION(BlueprintCallable, Category = "Carousel") + class ACharacterBase* GetCharacterModel(int32 index); + UFUNCTION(BlueprintCallable, Category = "Carousel") + int32 GetCharacterNum(); + + UPROPERTY(EditAnywhere, Category = "Carousel") + FVector selectedCharacterOffset; + + UFUNCTION(BlueprintCallable, Category = "Carousel") + void SaveCharacterName(const FString& name, int32 index); + UFUNCTION(BlueprintCallable, Category = "Carousel") + FString GetCharacterName(int32 index); + +private: + TArray m_characters; + float m_visualSelected; + float m_zoom; +}; diff --git a/Source/UnrealProject/Doodads/CharacterSettings.cpp b/Source/UnrealProject/Doodads/CharacterSettings.cpp new file mode 100644 index 0000000..e0263f2 --- /dev/null +++ b/Source/UnrealProject/Doodads/CharacterSettings.cpp @@ -0,0 +1,66 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "CharacterSettings.h" +#include "SkillTreeWidget.h" +#include "UserWidget.h" + +#define SAVE_SLOT_NAME TEXT("WIEBERSAVE tm") + +static UClass* skillTreeWidgetClass; + +UCharacterSettings::UCharacterSettings(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + skillTreeWidgetClass = ConstructorHelpers::FClassFinder(TEXT("/Game/Assets/GUI/WEEGEE_SkillTree")).Class; +} + +UCharacterSettings* UCharacterSettings::Load() +{ + UCharacterSettings* settings = Cast(UGameplayStatics::LoadGameFromSlot(SAVE_SLOT_NAME, 0)); + if (!settings) + { + settings = Cast(UGameplayStatics::CreateSaveGameObject(UCharacterSettings::StaticClass())); + } + check(settings); + + for (auto& save : settings->characterSaves) + { + for (TFieldIterator PropIt(FCharacterCustomization::StaticStruct()); PropIt; ++PropIt) + { + UFloatProperty* fProperty = Cast(*PropIt); + + if (IsValid(fProperty)) + { + float* valPtr = fProperty->GetPropertyValuePtr_InContainer(&save.characterCustomization); + *valPtr = FMath::Clamp(*valPtr, 0.5f, 1.5f); + } + + } + } + + return settings; +} + +void UCharacterSettings::Save(UCharacterSettings* settings) +{ + UGameplayStatics::SaveGameToSlot(settings, SAVE_SLOT_NAME, 0); +} + +TArray UCharacterSettings::GetValidCharacters() const +{ + UCharacterSettings* settings = Cast(UGameplayStatics::LoadGameFromSlot(SAVE_SLOT_NAME, 0)); + if (!settings) + { + settings = Cast(UGameplayStatics::CreateSaveGameObject(UCharacterSettings::StaticClass())); + } + check(settings); + + TArray result; + for (FCharacterSave& save : settings->characterSaves) + { + if (save.skillTreeState.ValidateSkillTree()) + result.Add(save); + } + return result; +} \ No newline at end of file diff --git a/Source/UnrealProject/Doodads/CharacterSettings.h b/Source/UnrealProject/Doodads/CharacterSettings.h new file mode 100644 index 0000000..0474aea --- /dev/null +++ b/Source/UnrealProject/Doodads/CharacterSettings.h @@ -0,0 +1,47 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "GameFramework/SaveGame.h" +#include "SkillTreeState.h" +#include "PlayerSetupState.h" +#include "CharacterSettings.generated.h" + +USTRUCT(BlueprintType) +struct FCharacterSave +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintReadWrite, Category = "Save") + FString name; + + UPROPERTY(BlueprintReadWrite, Category = "Save") + FSkillTreeState skillTreeState; + + UPROPERTY(BlueprintReadWrite, Category = "Save") + FCharacterCustomization characterCustomization; + + UPROPERTY(BlueprintReadWrite, Category = "Save") + int32 characterClass; +}; + +UCLASS() +class UNREALPROJECT_API UCharacterSettings : public USaveGame +{ + GENERATED_BODY() +private: + +public: + UCharacterSettings(const FObjectInitializer& ObjectInitializer); + + UPROPERTY(BlueprintReadWrite, Category = "Basic") + TArray characterSaves; + + UFUNCTION(BlueprintCallable, Category = "Basic") + TArray GetValidCharacters() const; +public: + static UCharacterSettings* Load(); + static void Save(UCharacterSettings* settings); + +}; \ No newline at end of file diff --git a/Source/UnrealProject/Doodads/FixupUtility.cpp b/Source/UnrealProject/Doodads/FixupUtility.cpp new file mode 100644 index 0000000..2f4b8df --- /dev/null +++ b/Source/UnrealProject/Doodads/FixupUtility.cpp @@ -0,0 +1,74 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "FixupUtility.h" +#include "AbilityInfo.h" +#include "AssetRegistryInterface.h" +#include "AssetRegistryModule.h" +#include "SpawnerBase.h" + +void UFixupUtility::FixupAbilityInfos() +{ + FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); + TArray AssetData; + FARFilter Filter; + Filter.ClassNames.Add(UAbilityInfo::StaticClass()->GetFName()); + //Filter.PackagePaths.Add("/Content/Assets/Abilities"); + AssetRegistryModule.Get().GetAssets(Filter, AssetData); + + for(auto& d : AssetData) + { + UAbilityInfo* info = Cast(d.GetAsset()); + if(info) + { + GPRINT("Fixing up " + info->GetName()); + //info->actionType = info->isToggleAbility ? EAbilityActionType::Toggle : EAbilityActionType::Normal; + //info->abilityCategory = EAbilityCategory::Unassigned; + //info->Modify(); + } + } +} +void UFixupUtility::FixAICastRanges(const FString& filter, float setRange, bool evenIfNotZero) +{ + FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); + TArray AssetData; + FARFilter Filter; + Filter.ClassNames.Add(UAbilityInfo::StaticClass()->GetFName()); + //Filter.PackagePaths.Add("/Content/Assets/Abilities/Boss"); + if(filter.Len() > 0) + { + Filter.PackagePaths.Add(*filter); + } + AssetRegistryModule.Get().GetAssets(Filter, AssetData); + + for(auto& d : AssetData) + { + UAbilityInfo* info = Cast(d.GetAsset()); + if(info) + { + GPRINT("Fixing up AIRange on " + info->GetName()); + if(info->AICastRange == 0 || evenIfNotZero) + { + info->AICastRange = setRange; + info->Modify(); + } + } + } +} +void UFixupUtility::FixupSpawners() +{ + UWorld* world = GEngine->GetWorld(); + if(world) + { + for(TActorIterator it(world); it; ++it) + { + ASpawnerBase* sp = *it; + sp->displayMesh->SetRelativeLocation(FVector(0.0f, 0.0f, 0.0f)); + } + } +} +void UFixupUtility::CrashTheEditor() +{ + uint32* funPointer = (uint32*)0x1; + GPRINT(*funPointer); +} diff --git a/Source/UnrealProject/Doodads/FixupUtility.h b/Source/UnrealProject/Doodads/FixupUtility.h new file mode 100644 index 0000000..908e885 --- /dev/null +++ b/Source/UnrealProject/Doodads/FixupUtility.h @@ -0,0 +1,25 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "GlobalEditorUtilityBase.h" +#include "FixupUtility.generated.h" + +/** + * + */ +UCLASS() +class UNREALPROJECT_API UFixupUtility : public UGlobalEditorUtilityBase +{ + GENERATED_BODY() + +public: + UFUNCTION(BlueprintCallable, Category="HACKS") + void FixupAbilityInfos(); + UFUNCTION(BlueprintCallable, Category = "HACKS") + void FixAICastRanges(const FString& filter, float setRange, bool evenIfNotZero = false); + UFUNCTION(BlueprintCallable, Category = "HACKS") + void FixupSpawners(); + UFUNCTION(BlueprintCallable, Category = "HACKS") + void CrashTheEditor(); +}; diff --git a/Source/UnrealProject/Doodads/NetworkDoodad.cpp b/Source/UnrealProject/Doodads/NetworkDoodad.cpp new file mode 100644 index 0000000..8ecbde4 --- /dev/null +++ b/Source/UnrealProject/Doodads/NetworkDoodad.cpp @@ -0,0 +1,11 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "NetworkDoodad.h" + + +ANetworkDoodad::ANetworkDoodad() +{ + bReplicates = true; + PrimaryActorTick.bCanEverTick = true; +} \ No newline at end of file diff --git a/Source/UnrealProject/Doodads/NetworkDoodad.h b/Source/UnrealProject/Doodads/NetworkDoodad.h new file mode 100644 index 0000000..963b95f --- /dev/null +++ b/Source/UnrealProject/Doodads/NetworkDoodad.h @@ -0,0 +1,23 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "GameFramework/Actor.h" +#include "NetworkDoodad.generated.h" + +UENUM(BlueprintType) +enum class ToggleType : uint8 +{ + ToggleObject, + OpenObject, + CloseObject, +}; + +UCLASS() +class UNREALPROJECT_API ANetworkDoodad : public AActor +{ + GENERATED_BODY() + +public: + ANetworkDoodad(); +}; diff --git a/Source/UnrealProject/Doodads/NetworkDoor.cpp b/Source/UnrealProject/Doodads/NetworkDoor.cpp new file mode 100644 index 0000000..2a2dcb6 --- /dev/null +++ b/Source/UnrealProject/Doodads/NetworkDoor.cpp @@ -0,0 +1,91 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" + +#include "NetworkDoor.h" + + +ANetworkDoor::ANetworkDoor() +{ + PrimaryActorTick.bCanEverTick = true; + debugColorCode = FColor::Red; + + doorCollider = CreateDefaultSubobject(TEXT("Cube")); + doorCollider->SetCollisionProfileName(TEXT("BlockAll")); + RootComponent = doorCollider; + + meshComponent = CreateDefaultSubobject(TEXT("Mesh")); + meshComponent->AttachTo(doorCollider); + + isDoorClosed = true; + closeAfterTime = 0.0f; + + debugSinkDepth = 250; +} + + +void ANetworkDoor::BeginPlay() +{ + Super::BeginPlay(); + m_openTime = 0.0f; +} + +void ANetworkDoor::Tick(float DeltaTime) +{ + Super::Tick(DeltaTime); + + if (doorCollider) + { + // Enable or disable the door collider + doorCollider->SetCollisionEnabled(isDoorClosed ? ECollisionEnabled::QueryAndPhysics : ECollisionEnabled::NoCollision); + } + + if (meshComponent) + { + const FVector localPos = meshComponent->GetRelativeTransform().GetLocation(); + const FVector targetPos = FVector(0, 0, isDoorClosed ? 0.0f : -debugSinkDepth); + + const float distanceSqr = FVector::DistSquared(localPos, targetPos); + if (distanceSqr > 0.01f) + { + FVector dif = targetPos - localPos; + dif.Normalize(); + float speed = DeltaTime * 800 * (debugSinkDepth / 250.0f); + const float distance = FMath::Sqrt(distanceSqr); + if (speed > distance) speed = distance; + dif *= speed; + meshComponent->SetRelativeLocation(localPos + dif); + } + } + + if(Role == ROLE_Authority && !isDoorClosed && closeAfterTime > 0) + { + m_openTime += DeltaTime; + if(m_openTime >= closeAfterTime) + { + SetDoorState(true); + m_openTime = 0.0f; + } + } +} + +void ANetworkDoor::ToggleDoor() +{ + // Server only function + check(Role == ROLE_Authority); + isDoorClosed = !isDoorClosed; +} + +void ANetworkDoor::SetDoorState(bool NewState) +{ + // Server only function + check(Role == ROLE_Authority); + isDoorClosed = NewState; +} + +void ANetworkDoor::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(ANetworkDoor, isDoorClosed); +} \ No newline at end of file diff --git a/Source/UnrealProject/Doodads/NetworkDoor.h b/Source/UnrealProject/Doodads/NetworkDoor.h new file mode 100644 index 0000000..b28d632 --- /dev/null +++ b/Source/UnrealProject/Doodads/NetworkDoor.h @@ -0,0 +1,40 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "NetworkDoodad.h" +#include "NetworkDoor.generated.h" + + +UCLASS() +class UNREALPROJECT_API ANetworkDoor : public ANetworkDoodad +{ + GENERATED_BODY() + +public: + ANetworkDoor(); + + virtual void BeginPlay() override; + virtual void Tick(float DeltaSeconds) override; + + void ToggleDoor(); + void SetDoorState(bool NewState); + + UPROPERTY(Replicated, EditAnywhere, Category = "Switch Components") + bool isDoorClosed; + UPROPERTY(EditAnywhere, Category = "Switch Components") + float closeAfterTime; + UPROPERTY(EditAnywhere) + FColor debugColorCode; + + UPROPERTY(EditAnywhere, Category = "Switch Components") + float debugSinkDepth; + + UPROPERTY(EditAnywhere, Category = "Switch Components") + class UBoxComponent* doorCollider; + UPROPERTY(EditAnywhere) + class UStaticMeshComponent* meshComponent; + +private: + float m_openTime; +}; diff --git a/Source/UnrealProject/Doodads/NetworkSwitch.cpp b/Source/UnrealProject/Doodads/NetworkSwitch.cpp new file mode 100644 index 0000000..d33290e --- /dev/null +++ b/Source/UnrealProject/Doodads/NetworkSwitch.cpp @@ -0,0 +1,92 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "NetworkDoor.h" + +#include "NetworkCharacter.h" +#include "NetworkSwitchComponent.h" +#include "CreatureSpawn.h" +#include "NetworkSwitch.h" + + + +ANetworkSwitch::ANetworkSwitch() +{ + cooldown = m_elapsedTime = 0.0f; + cubeCollider = CreateDefaultSubobject(TEXT("Cube")); + cubeCollider->SetCollisionProfileName(TEXT("BlockAll")); + RootComponent = cubeCollider; + + meshComponent = CreateDefaultSubobject(TEXT("Static Mesh")); + meshComponent->AttachTo(RootComponent); + + visualizerComponent = CreateDefaultSubobject(TEXT("Visualizer")); + visualizerComponent->AttachTo(RootComponent); + + isSwitchClosed = false; + + toggles = false; +} + +void ANetworkSwitch::BeginPlay() +{ + Super::BeginPlay(); + m_elapsedTime = cooldown; + + if (Role != ROLE_Authority) + return; + + // Init the door states + if (!toggles) + { + for (int32 i = 0; i < doorsToOpen.Num(); i++) + { + if (doorsToOpen[i]) + doorsToOpen[i]->SetDoorState(isSwitchClosed); + } + } +} + +void ANetworkSwitch::Tick(float DeltaTime) +{ + Super::Tick(DeltaTime); + + // Animate the switch (must be replaced with an actual animated mesh in future) + meshComponent->SetRelativeRotation(FRotator(isSwitchClosed ? 30 : -30, 0, 0)); + + if (Role != ROLE_Authority) + return; + + if (cooldown > 0) + m_elapsedTime += DeltaTime; +} + +void ANetworkSwitch::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(ANetworkSwitch, isSwitchClosed); +} + +void ANetworkSwitch::Toggle() +{ + check(Role == ROLE_Authority); + + if (m_elapsedTime < cooldown) + return; + + m_elapsedTime = 0; + isSwitchClosed = !isSwitchClosed; + + // Toggle all linked doors + for (int32 i = 0; i < doorsToOpen.Num(); i++) + { + if (doorsToOpen[i]) + { + if (toggles) + doorsToOpen[i]->ToggleDoor(); + else + doorsToOpen[i]->SetDoorState(isSwitchClosed); + } + } +} \ No newline at end of file diff --git a/Source/UnrealProject/Doodads/NetworkSwitch.h b/Source/UnrealProject/Doodads/NetworkSwitch.h new file mode 100644 index 0000000..a6c880f --- /dev/null +++ b/Source/UnrealProject/Doodads/NetworkSwitch.h @@ -0,0 +1,41 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "NetworkDoodad.h" +#include "NetworkSwitch.generated.h" + + +UCLASS() +class UNREALPROJECT_API ANetworkSwitch : public ANetworkDoodad +{ + GENERATED_BODY() + +public: + ANetworkSwitch(); + + virtual void BeginPlay() override; + virtual void Tick(float DeltaSeconds) override; + + void Toggle(); + + UPROPERTY(EditAnywhere, Category = "Switch Components") + TArray doorsToOpen; + UPROPERTY(EditAnywhere, Category = "Switch Components") + float cooldown; + UPROPERTY(EditAnywhere, Category = "Switch Components") + bool toggles; + + UPROPERTY(Replicated, EditAnywhere, Category = "Switch Components") + bool isSwitchClosed; + +private: + float m_elapsedTime; + + UPROPERTY(EditAnywhere, Category = "Switch Components") + class UBoxComponent* cubeCollider; + UPROPERTY(EditAnywhere, Category = "Switch Components") + class UStaticMeshComponent* meshComponent; + UPROPERTY(EditAnywhere, Category = "Switch Components") + class UNetworkSwitchComponent* visualizerComponent; +}; diff --git a/Source/UnrealProject/Doodads/NetworkSwitchComponent.cpp b/Source/UnrealProject/Doodads/NetworkSwitchComponent.cpp new file mode 100644 index 0000000..86ce22f --- /dev/null +++ b/Source/UnrealProject/Doodads/NetworkSwitchComponent.cpp @@ -0,0 +1,108 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "NetworkSwitchComponent.h" +#include "NetworkDoor.h" +#include "NetworkSwitch.h" +#include "NetworkTrigger.h" +#include "Spawners/CreatureSpawn.h" + +UNetworkSwitchComponent::UNetworkSwitchComponent(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) +{ + ShapeColor = FColor(223, 149, 157, 255); + + bUseEditorCompositing = true; + + networkDoodad = Cast(GetAttachmentRootActor()); + creatureSpawn = Cast(GetAttachmentRootActor()); +} + +FPrimitiveSceneProxy* UNetworkSwitchComponent::CreateSceneProxy() +{ + class FDrawConeSceneProxy : public FPrimitiveSceneProxy + { + public: + const UNetworkSwitchComponent* component; + FDrawConeSceneProxy(const UNetworkSwitchComponent* InComponent) + : FPrimitiveSceneProxy(InComponent) + , networkDoodad(InComponent->networkDoodad) + , creatureSpawn(InComponent->creatureSpawn) + , bDrawOnlyIfSelected(InComponent->bDrawOnlyIfSelected) + , component(InComponent) + { + bWillEverBeLit = false; + } + + + virtual void GetDynamicMeshElements(const TArray& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector) const override + { + QUICK_SCOPE_CYCLE_COUNTER(STAT_GetDynamicMeshElements_DrawDynamicElements); + + const FMatrix& LocalToWorld = GetLocalToWorld(); + + for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++) + { + const FSceneView* View = Views[ViewIndex]; + + FPrimitiveDrawInterface* PDI = Collector.GetPDI(ViewIndex); + if (networkDoodad) + { + if (networkDoodad->IsA(ANetworkTrigger::StaticClass())) + { + const ANetworkTrigger* const obj = Cast(networkDoodad); + for (int32 i = 0; i < obj->doorsToOpen.Num(); i++) + { + if (obj->doorsToOpen[i]) + PDI->DrawLine(obj->GetActorLocation() + FVector(0, 0, 100), obj->doorsToOpen[i]->GetActorLocation() + FVector(0, 0, 100), obj->doorsToOpen[i]->debugColorCode, SDPG_World); + } + for (int32 i = 0; i < obj->creaturesToSpawn.Num(); i++) + { + if (obj->creaturesToSpawn[i]) + PDI->DrawLine(obj->GetActorLocation() + FVector(0, 0, 100), obj->creaturesToSpawn[i]->GetActorLocation() + FVector(0, 0, 100), obj->creaturesToSpawn[i]->debugColorCode, SDPG_World); + } + } + if (networkDoodad->IsA(ANetworkSwitch::StaticClass())) + { + const ANetworkSwitch* const obj = Cast(networkDoodad); + for (int32 i = 0; i < obj->doorsToOpen.Num(); i++) + { + if (obj->doorsToOpen[i]) + PDI->DrawLine(obj->GetActorLocation() + FVector(0, 0, 100), obj->doorsToOpen[i]->GetActorLocation() + FVector(0, 0, 100), obj->doorsToOpen[i]->debugColorCode, SDPG_World); + } + } + } + if (creatureSpawn) + { + + for (int32 i = 0; i < creatureSpawn->doorsToOpen.Num(); i++) + { + if (creatureSpawn->doorsToOpen[i]) + PDI->DrawLine(creatureSpawn->GetActorLocation() + FVector(0, 0, 100), creatureSpawn->doorsToOpen[i]->GetActorLocation() + FVector(0, 0, 100), creatureSpawn->doorsToOpen[i]->debugColorCode, SDPG_World); + } + } + } + } + + virtual FPrimitiveViewRelevance GetViewRelevance(const FSceneView* View) const override + { + FPrimitiveViewRelevance Result; + Result.bDrawRelevance = IsSelected(); + Result.bDynamicRelevance = true; + Result.bShadowRelevance = IsShadowCast(View); + Result.bEditorPrimitiveRelevance = UseEditorCompositing(View); + return Result; + } + + virtual uint32 GetMemoryFootprint(void) const override { return(sizeof(*this) + GetAllocatedSize()); } + uint32 GetAllocatedSize(void) const { return(FPrimitiveSceneProxy::GetAllocatedSize()); } + + private: + const uint32 bDrawOnlyIfSelected : 1; + const ANetworkDoodad* networkDoodad; + const ACreatureSpawn* creatureSpawn; + const FColor ShapeColor = FColor(255, 0, 0, 255); + const FTransform transform; + }; + + return new FDrawConeSceneProxy(this); +} diff --git a/Source/UnrealProject/Doodads/NetworkSwitchComponent.h b/Source/UnrealProject/Doodads/NetworkSwitchComponent.h new file mode 100644 index 0000000..409ddfd --- /dev/null +++ b/Source/UnrealProject/Doodads/NetworkSwitchComponent.h @@ -0,0 +1,21 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "Components/CapsuleComponent.h" +#include "NetworkSwitchComponent.generated.h" + +class ANetworkSwitch; + +UCLASS() +class UNREALPROJECT_API UNetworkSwitchComponent : public UCapsuleComponent +{ + GENERATED_UCLASS_BODY() + +public: + class ANetworkDoodad* networkDoodad; + class ACreatureSpawn* creatureSpawn; + + virtual FPrimitiveSceneProxy* CreateSceneProxy() override; + +}; diff --git a/Source/UnrealProject/Doodads/NetworkTrigger.cpp b/Source/UnrealProject/Doodads/NetworkTrigger.cpp new file mode 100644 index 0000000..2f77245 --- /dev/null +++ b/Source/UnrealProject/Doodads/NetworkTrigger.cpp @@ -0,0 +1,123 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "NetworkDoor.h" +#include "NetworkPlayer.h" +#include "NetworkSwitchComponent.h" +#include "CreatureSpawn.h" +#include "DefaultGameMode.h" +#include "NetworkTrigger.h" + + + +ANetworkTrigger::ANetworkTrigger() +{ + openMode = OpenMode::NoKeyRequired; + cooldown = m_elapsedTime = 0.0f; + cubeCollider = CreateDefaultSubobject(TEXT("Cube")); + RootComponent = cubeCollider; + + meshComponent = CreateDefaultSubobject(TEXT("Static Mesh")); + meshComponent->AttachTo(RootComponent); + + visualizerComponent = CreateDefaultSubobject(TEXT("Visualizer")); + visualizerComponent->AttachTo(RootComponent); + cubeCollider->OnComponentBeginOverlap.AddDynamic(this, &ANetworkTrigger::OnOverlapBegin); // set up a notification for when this component overlaps something +} + +void ANetworkTrigger::BeginPlay() +{ + Super::BeginPlay(); + m_elapsedTime = cooldown; +} + +void ANetworkTrigger::Tick(float DeltaTime) +{ + Super::Tick(DeltaTime); + + if (Role != ROLE_Authority) + return; + + if (cooldown > 0) + m_elapsedTime += DeltaTime; +} + +void ANetworkTrigger::OnOverlapBegin(class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult) +{ + if (Role != ROLE_Authority) + return; + + if (!(OtherActor && (OtherActor != this) && OtherComp)) + return; + if (!OtherActor->IsA(ANetworkPlayer::StaticClass())) + return; + if (m_elapsedTime < cooldown) + return; + ANetworkPlayer* player = Cast(OtherActor); + AController* const controller = player->GetController(); + if (!controller) + return; + + if (openMode != OpenMode::NoKeyRequired) + { + if (openMode == OpenMode::TreasureRoomKey) + { + if (!player->HasKey(PlayerKeyType::TreasureRoomKey)) + return; + ADefaultGameMode* gameMode = (ADefaultGameMode*)GetWorld()->GetAuthGameMode(); + gameMode->BossHasBeenKilled(player->GetTeam()); + } + else if (openMode == OpenMode::BossRoomKeyParts) + { + // Check if this team has all key fragments + ANetworkPlayer* const teamMate = player->GetTeamMate(); + int32 playerKeyCompletion = 0, requiredKeyCompletion = 0; + for (int32 i = keyFragmentMin; i <= keyFragmentMax; i++) + { + const int32 lsl = (1 << i); + requiredKeyCompletion += lsl; + playerKeyCompletion += player->HasKey(PlayerKeyType(i)) ? lsl : 0; + if (IsValid(teamMate)) + playerKeyCompletion |= teamMate->HasKey(PlayerKeyType(i)) ? lsl : 0; + } + + // Key is not complete + if (requiredKeyCompletion != playerKeyCompletion) + return; + + // Remove the key fragments + for (int32 i = keyFragmentMin; i <= keyFragmentMax; i++) + { + player->ClearKey(PlayerKeyType(i)); + if (IsValid(teamMate)) + teamMate->ClearKey(PlayerKeyType(i)); + } + } + } + + m_elapsedTime = 0; + + for (size_t i = 0; i < creaturesToSpawn.Num(); i++) + { + if (!creaturesToSpawn[i]->spawnContinuous) + creaturesToSpawn[i]->SpawnMobs(); + } + for (int32 i = 0; i < doorsToOpen.Num(); i++) + { + if (doorsToOpen[i]) + { + switch (toggleMode) + { + case ToggleType::CloseObject: + doorsToOpen[i]->SetDoorState(true); + break; + case ToggleType::OpenObject: + doorsToOpen[i]->SetDoorState(false); + break; + case ToggleType::ToggleObject: + doorsToOpen[i]->ToggleDoor(); + break; + } + } + } +} \ No newline at end of file diff --git a/Source/UnrealProject/Doodads/NetworkTrigger.h b/Source/UnrealProject/Doodads/NetworkTrigger.h new file mode 100644 index 0000000..b9ab8f7 --- /dev/null +++ b/Source/UnrealProject/Doodads/NetworkTrigger.h @@ -0,0 +1,52 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "NetworkDoodad.h" +#include "NetworkPlayer.h" +#include "NetworkTrigger.generated.h" + +UENUM(BlueprintType) +enum class OpenMode : uint8 +{ + NoKeyRequired = 0, + BossRoomKeyParts = 1, + TreasureRoomKey = 2, +}; + +UCLASS() +class UNREALPROJECT_API ANetworkTrigger : public ANetworkDoodad +{ + GENERATED_BODY() + +public: + ANetworkTrigger(); + virtual void BeginPlay() override; + + UPROPERTY(EditAnywhere, Category = "Switch Components") + TArray doorsToOpen; + UPROPERTY(EditAnywhere, Category = "Switch Components") + TArray creaturesToSpawn; + UPROPERTY(EditAnywhere, Category = "Switch Components") + float cooldown; + UPROPERTY(EditAnywhere, Category = "Switch Components") + OpenMode openMode; + + UFUNCTION() + void OnOverlapBegin(class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult); + + virtual void Tick(float DeltaSeconds) override; + +private: + float m_elapsedTime; + + UPROPERTY(EditAnywhere, Category = "Switch Components") + class UBoxComponent* cubeCollider; + + UPROPERTY(EditAnywhere, Category = "Switch Components") + class UStaticMeshComponent* meshComponent; + UPROPERTY(EditAnywhere, Category = "Switch Components") + class UNetworkSwitchComponent* visualizerComponent; + UPROPERTY(EditAnywhere, Category = "Switch Components") + ToggleType toggleMode; +}; diff --git a/Source/UnrealProject/Doodads/ParticleEffect.cpp b/Source/UnrealProject/Doodads/ParticleEffect.cpp new file mode 100644 index 0000000..c369151 --- /dev/null +++ b/Source/UnrealProject/Doodads/ParticleEffect.cpp @@ -0,0 +1,40 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "ParticleEffect.h" + + +AParticleEffect::AParticleEffect() +{ + // Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it. + PrimaryActorTick.bCanEverTick = true; + duration = 5.0f; + RootComponent = particleComponent = CreateDefaultSubobject("PSys"); + followActor = nullptr; +} + +void AParticleEffect::BeginPlay() +{ + Super::BeginPlay(); + +} + +void AParticleEffect::Tick( float DeltaTime ) +{ + Super::Tick( DeltaTime ); + duration -= DeltaTime; + if (followActor) + SetActorTransform(followActor->GetTransform()); + if (duration <= 0.0f) + { + Destroy(); + } +} + +void AParticleEffect::Init(UParticleSystem* particleSystem, float duration) +{ + this->duration = duration; + particleComponent->SetTemplate(particleSystem); + particleComponent->InitializeSystem(); +} + diff --git a/Source/UnrealProject/Doodads/ParticleEffect.h b/Source/UnrealProject/Doodads/ParticleEffect.h new file mode 100644 index 0000000..6a85b7f --- /dev/null +++ b/Source/UnrealProject/Doodads/ParticleEffect.h @@ -0,0 +1,29 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "GameFramework/Actor.h" +#include "ParticleEffect.generated.h" + +UCLASS() +class UNREALPROJECT_API AParticleEffect : public AActor +{ + GENERATED_BODY() + +public: + AParticleEffect(); + + virtual void BeginPlay() override; + virtual void Tick( float DeltaSeconds ) override; + + void Init(UParticleSystem* particleSystem, float duration); + + UPROPERTY() + UParticleSystemComponent* particleComponent; + + UPROPERTY(BlueprintReadWrite, Category = "Target") + AActor* followActor; + + float duration; + +}; diff --git a/Source/UnrealProject/Doodads/Prefs.cpp b/Source/UnrealProject/Doodads/Prefs.cpp new file mode 100644 index 0000000..b9a822e --- /dev/null +++ b/Source/UnrealProject/Doodads/Prefs.cpp @@ -0,0 +1,37 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "Prefs.h" + +#define PREFS_SLOT_NAME TEXT("WIEBERPREFS tm") + +UPrefs::UPrefs(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + useControllerName = ""; + + musicVolume = 1.0f; + sfxVolume = 1.0f; + + while (customGraphicsSettings.Num() < 6) + customGraphicsSettings.Add(3); + + usingCustomGraphicsSettings = false; +} + +UPrefs* UPrefs::Load() +{ + UPrefs* settings = Cast(UGameplayStatics::LoadGameFromSlot(PREFS_SLOT_NAME, 0)); + if (!settings) + { + settings = Cast(UGameplayStatics::CreateSaveGameObject(UPrefs::StaticClass())); + } + check(settings); + + return settings; +} + +void UPrefs::Save(UPrefs* settings) +{ + UGameplayStatics::SaveGameToSlot(settings, PREFS_SLOT_NAME, 0); +} diff --git a/Source/UnrealProject/Doodads/Prefs.h b/Source/UnrealProject/Doodads/Prefs.h new file mode 100644 index 0000000..e5f5295 --- /dev/null +++ b/Source/UnrealProject/Doodads/Prefs.h @@ -0,0 +1,33 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "GameFramework/SaveGame.h" +#include "Prefs.generated.h" + + +UCLASS() +class UNREALPROJECT_API UPrefs : public USaveGame +{ + GENERATED_BODY() + +public: + UPrefs(const FObjectInitializer& ObjectInitializer); + + UPROPERTY(BlueprintReadWrite, Category = "Basic") + FString useControllerName; + + UPROPERTY(BlueprintReadWrite, Category = "Basic") + float musicVolume; + UPROPERTY(BlueprintReadWrite, Category = "Basic") + float sfxVolume; + + UPROPERTY(BlueprintReadWrite, Category = "Graphics") + bool usingCustomGraphicsSettings; + UPROPERTY(BlueprintReadWrite, Category = "Graphics") + TArray customGraphicsSettings; + +public: + static UPrefs* Load(); + static void Save(UPrefs* settings); +}; \ No newline at end of file diff --git a/Source/UnrealProject/Doodads/Shrine.cpp b/Source/UnrealProject/Doodads/Shrine.cpp new file mode 100644 index 0000000..ee200c2 --- /dev/null +++ b/Source/UnrealProject/Doodads/Shrine.cpp @@ -0,0 +1,44 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "NetworkGhost.h" +#include "Shrine.h" + + +// Sets default values +AShrine::AShrine() +{ + // Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it. + PrimaryActorTick.bCanEverTick = true; + + displayMesh = CreateDefaultSubobject(TEXT("Mesh")); + displayMesh->bHiddenInGame = true; + displayMesh->bGenerateOverlapEvents = false; + displayMesh->SetCollisionEnabled(ECollisionEnabled::NoCollision); + RootComponent = displayMesh; + + shrineTrigger = CreateDefaultSubobject(TEXT("RespawnArea")); + shrineTrigger->SetCollisionProfileName(TEXT("GhostOverlap")); + shrineTrigger->AttachTo(RootComponent); + shrineTrigger->OnComponentBeginOverlap.AddDynamic(this, &AShrine::OnOverlapBegin); + shrineTrigger->OnComponentEndOverlap.AddDynamic(this, &AShrine::OnOverlapEnd); + shrineTrigger->SetSphereRadius(300); + shrineTrigger->AttachTo(RootComponent); +} + + +void AShrine::OnOverlapBegin(AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult) +{ + ANetworkGhost* ghost = Cast(OtherActor); + if (!IsValid(ghost)) return; + + ghost->shrinesInRange++; +} +void AShrine::OnOverlapEnd(AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex) +{ + ANetworkGhost* ghost = Cast(OtherActor); + if (!IsValid(ghost)) return; + + if (ghost->shrinesInRange > 0) + ghost->shrinesInRange--; +} \ No newline at end of file diff --git a/Source/UnrealProject/Doodads/Shrine.h b/Source/UnrealProject/Doodads/Shrine.h new file mode 100644 index 0000000..21ade2c --- /dev/null +++ b/Source/UnrealProject/Doodads/Shrine.h @@ -0,0 +1,28 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "GameFramework/Actor.h" +#include "Shrine.generated.h" + +UCLASS() +class UNREALPROJECT_API AShrine : public AActor +{ + GENERATED_BODY() + +public: + // Sets default values for this actor's properties + AShrine(); + + + UPROPERTY(VisibleAnywhere, Category = "Respawn") + class UStaticMeshComponent* displayMesh; + UPROPERTY(EditAnywhere, Category = "Respawn") + class USphereComponent* shrineTrigger; + + UFUNCTION() + void OnOverlapBegin(class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult); + UFUNCTION() + void OnOverlapEnd(class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex); + +}; diff --git a/Source/UnrealProject/Effects/AbilityIndicator.cpp b/Source/UnrealProject/Effects/AbilityIndicator.cpp new file mode 100644 index 0000000..08a639e --- /dev/null +++ b/Source/UnrealProject/Effects/AbilityIndicator.cpp @@ -0,0 +1,241 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "UnrealNetwork.h" +#include "AbilityIndicator.h" + + +// Sets default values +AAbilityIndicator::AAbilityIndicator() +{ + PrimaryActorTick.bCanEverTick = true; + m_followActor = nullptr; + m_duration = 0.0f; + bReplicates = true; + +} + +AFillingAbilityIndicator::AFillingAbilityIndicator() +{ + +} +void AAbilityIndicator::BeginPlay() +{ + FVector pos; + FRotator rot; + + if (m_followActor) + { + pos = m_followActor->GetActorLocation(); + rot = m_followActor->GetActorRotation() + m_rotationOffset; + } + else + { + pos = GetActorLocation(); + rot = GetActorRotation() + m_rotationOffset; + } + + m_outlineDecal = GetWorld()->SpawnActor(pos, rot); + m_outlineDecal->SetDecalMaterial(outlineMat); + m_outlineMat = m_outlineDecal->CreateDynamicMaterialInstance(); + m_outlineMat->SetTextureParameterValue(FName("OutlineTexture"), outlineTexture); + m_outlineDecal->SetActorScale3D(m_scale); + m_outlineDecal->AddActorLocalOffset(-m_offset); + m_outlineDecal->SetLifeSpan(m_duration); + m_outlineMat->SetVectorParameterValue("TeamColor", m_color); + Super::BeginPlay(); +} + + +void AAbilityIndicator::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + // m_outlineDecal->Destroy(); + + if (IsValid(m_outlineDecal)) + m_outlineDecal->Destroy(); + Super::EndPlay(EndPlayReason); +} +void AFillingAbilityIndicator::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + +// m_fillDecal->Destroy(); + + if (IsValid(m_fillDecal)) + m_fillDecal->Destroy(); + Super::EndPlay(EndPlayReason); +} + +void AFillingAbilityIndicator::BeginPlay() +{ + Super::BeginPlay(); + FVector pos ; + FRotator rot; + if (m_followActor) + { + pos = m_followActor->GetActorLocation(); + rot = m_followActor->GetActorRotation() + m_rotationOffset; + } + else + { + pos = GetActorLocation(); + rot = GetActorRotation() + m_rotationOffset; + } + m_fillDecal = GetWorld()->SpawnActor(pos, rot); + m_fillDecal->SetDecalMaterial(fillMat); + m_fillMat = m_fillDecal->CreateDynamicMaterialInstance(); + m_fillMat->SetTextureParameterValue(FName("FillTexture"), fillTexture); + + m_fillDecal->SetActorScale3D(m_scale); + m_fillDecal->AddActorLocalOffset(-m_offset); + m_fillDecal->SetLifeSpan(m_duration); + +} + +void AAbilityIndicator::CalculatePosition_Implementation() +{ + if (Role != ROLE_Authority) + return; + if (IsValid(m_followActor)) + { + FTransform newtrans = m_followActor->GetTransform(); + SetActorTransform(newtrans); + } + +} + +void AAbilityIndicator::ForceDestroy() +{ + if (Role != ROLE_Authority) + return; + m_lifeTime = m_duration; + m_duration += 0.1f; +} + + +void AAbilityIndicator::Init(float duration, FVector offset, FRotator rotationOffset, FVector scale, bool center, AActor* followActor, FLinearColor color) +{ + m_duration = duration; + m_followActor = followActor; + m_offset = offset; + m_rotationOffset = rotationOffset; + m_scale = scale; + m_color = color; + m_center = center; +} + +void AAbilityIndicator::Tick(float DeltaTime) +{ + Super::Tick(DeltaTime); + + CalculatePosition(); + SetDecalLocations(); + if (m_duration > 0.0f) // Only use timer if duration was set + { + m_lifeTime += DeltaTime; + if (m_lifeTime >= m_duration) + { + if (Role == ROLE_Authority) + { + Destroy(); + } + } + } +} + +void AAbilityIndicator::SetDecalLocations() +{ + + if (Role != ROLE_Authority) + return; + if (IsValid(m_followActor)) + { + if (m_center) + { + m_outlineDecal->SetActorLocation(m_followActor->GetActorLocation()); + } + else if (IsValid(m_followActor)) + { + + m_outlineDecal->SetActorLocation(m_followActor->GetActorLocation()); + m_outlineDecal->AddActorLocalOffset(-m_offset); + } + } + else + { + if (m_center) + { + m_outlineDecal->SetActorLocation(GetActorLocation()); + } + else + { + m_outlineDecal->SetActorLocation(GetActorLocation()); + m_outlineDecal->AddActorLocalOffset(-m_offset); + } + } +} + +void AFillingAbilityIndicator::Tick(float DeltaTime) +{ + Super::Tick(DeltaTime); + + if (m_duration > 0.0f) // Only use timer if duration was set + { + if (m_fillMat != nullptr) + m_fillMat->SetScalarParameterValue(FName("AlphaMultiplier"), m_lifeTime / m_duration); + if (m_lifeTime >= m_duration) + { + if (Role == ROLE_Authority) + { + } + } + } +} +void AAbilityIndicator::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME_CONDITION(AAbilityIndicator, m_followActor, COND_InitialOnly); + DOREPLIFETIME_CONDITION(AAbilityIndicator, m_duration, COND_InitialOnly); + DOREPLIFETIME_CONDITION(AAbilityIndicator, m_scale, COND_InitialOnly); + DOREPLIFETIME_CONDITION(AAbilityIndicator, m_offset, COND_InitialOnly); + DOREPLIFETIME_CONDITION(AAbilityIndicator, m_rotationOffset, COND_InitialOnly); + DOREPLIFETIME_CONDITION(AAbilityIndicator, m_color, COND_InitialOnly); + DOREPLIFETIME_CONDITION(AAbilityIndicator, m_center, COND_InitialOnly); +} +void AAbilityIndicator::SetOffset(FVector offset) +{ + m_offset = offset; +} +void AAbilityIndicator::SetRotationOffset(FRotator rotationOffset) +{ + m_rotationOffset = rotationOffset; +} +void AFillingAbilityIndicator::SetDecalLocations() +{ + Super::SetDecalLocations(); + + if (IsValid(m_followActor)) + { + if (m_center) + { + m_fillDecal->SetActorLocation(m_followActor->GetActorLocation()); + } + else if (IsValid(m_followActor)) + { + m_fillDecal->SetActorLocation(m_followActor->GetActorLocation()); + m_fillDecal->AddActorLocalOffset(-m_offset); + } + } + else + { + if (m_center) + { + m_fillDecal->SetActorLocation(GetActorLocation()); + } + else + { + m_fillDecal->SetActorLocation(GetActorLocation()); + m_fillDecal->AddActorLocalOffset(-m_offset); + } + } +} \ No newline at end of file diff --git a/Source/UnrealProject/Effects/AbilityIndicator.h b/Source/UnrealProject/Effects/AbilityIndicator.h new file mode 100644 index 0000000..210b6b0 --- /dev/null +++ b/Source/UnrealProject/Effects/AbilityIndicator.h @@ -0,0 +1,101 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "GameFramework/Actor.h" +#include "NetworkCharacter.h" +#include "TeamData.h" +#include "DealDamageProxy.h" +#include "AbilityIndicator.generated.h" + + UCLASS() +class UNREALPROJECT_API AAbilityIndicator : public AActor +{ + GENERATED_BODY() + +public: + // Sets default values for this actor's properties + AAbilityIndicator(); + + UFUNCTION() + void Init(float duration, FVector offset, FRotator rotationOffset, FVector scale, bool center, AActor* followActor = nullptr, FLinearColor color = FLinearColor(1,1,1,1)); + + virtual void BeginPlay() override; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + + virtual void Tick(float DeltaSeconds) override; + + UFUNCTION(BlueprintNativeEvent) + void CalculatePosition(); + UFUNCTION(BluePrintCallable, category = "AbilityIndicators") + void ForceDestroy(); + + UPROPERTY(EditDefaultsOnly, category = "AbilityIndicators") + UMaterial* outlineMat; + UPROPERTY(EditDefaultsOnly, category = "AbilityIndicators") + UTexture2D* outlineTexture; + + UPROPERTY(BlueprintReadWrite, Replicated, ReplicatedUsing = CalculatePosition, meta = (ExposeOnSpawn), category = "AbilityIndicators") + AActor* m_followActor; + + void SetOffset(FVector offset); + void SetRotationOffset(FRotator rotationOffset); + + UPROPERTY(EditDefaultsOnly) + UTeamData* teamData; +protected: + UPROPERTY() + UMaterialInstanceDynamic* m_outlineMat; + + virtual void SetDecalLocations(); + UPROPERTY() + ADecalActor* m_outlineDecal; + UPROPERTY(Replicated) + bool m_center; + + UPROPERTY(Replicated) + float m_duration; + UPROPERTY(Replicated) + FLinearColor m_color; + UPROPERTY(Replicated) + FVector m_offset; + UPROPERTY(Replicated) + FRotator m_rotationOffset; + UPROPERTY(Replicated) + FVector m_scale; + + UPROPERTY() + float m_lifeTime; +}; + +UCLASS() +class UNREALPROJECT_API AFillingAbilityIndicator : public AAbilityIndicator +{ + GENERATED_BODY() + +public: + // Sets default values for this actor's properties + AFillingAbilityIndicator(); + + virtual void BeginPlay() override; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + + + virtual void Tick(float DeltaSeconds) override; + + UPROPERTY(EditDefaultsOnly, category = "AbilityIndicators") + UMaterial* fillMat; + UPROPERTY(EditDefaultsOnly, category = "AbilityIndicators") + UTexture2D* fillTexture; + + + +private: + virtual void SetDecalLocations() override; + UPROPERTY() + UMaterialInstanceDynamic* m_fillMat; + + UPROPERTY() + ADecalActor* m_fillDecal; + +}; diff --git a/Source/UnrealProject/Effects/Effect.cpp b/Source/UnrealProject/Effects/Effect.cpp new file mode 100644 index 0000000..6e4f731 --- /dev/null +++ b/Source/UnrealProject/Effects/Effect.cpp @@ -0,0 +1,166 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" + +#include "NetworkCharacter.h" +#include "Effect.h" +#include "DefaultGameInstance.h" +#include "Prefs.h" + +AEffect::AEffect() +{ + // Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it. + PrimaryActorTick.bCanEverTick = true; + //RootComponent = m_particleComponent = CreateDefaultSubobject("PSys"); + m_followActor = nullptr; + m_duration = 0.0f; + deactivateParticlesBefore = 0; + bReplicates = true; + +} +void AEffect::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME_CONDITION(AEffect, m_followActor, COND_InitialOnly); + DOREPLIFETIME_CONDITION(AEffect, m_duration, COND_InitialOnly); +} +void AEffect::BeginPlay() +{ + m_particleSystems = GetComponentsByClass(UParticleSystemComponent::StaticClass()); + m_rootParticleSystem = Cast(RootComponent); + m_rootAudio = Cast(RootComponent); + + // Bind root component finished actions + if(m_rootParticleSystem) + { + if(m_duration == 0.0f) + m_rootParticleSystem->bAutoDestroy = true; + + float WarmupTimeRemember = m_rootParticleSystem->Template->WarmupTime; + m_rootParticleSystem->Template->WarmupTime = m_lifeTime; + m_rootParticleSystem->ActivateSystem(); + m_rootParticleSystem->Template->WarmupTime = WarmupTimeRemember; + m_rootParticleSystem->OnSystemFinished.AddDynamic(this, &AEffect::OnRootParticleSystemFinished); + } + else if(m_rootAudio) + { + m_rootAudio->OnAudioFinished.AddDynamic(this, &AEffect::OnRootAudioFinished); + } + else + { + GWWARNING(L"Effect root is not a particles system or an audio component [" + GetName() + L"]"); + } + + Super::BeginPlay(); + TArray audioComponents = GetComponentsByClass(UAudioComponent::StaticClass()); + float volume = Cast(GetGameInstance())->GetPrefs()->sfxVolume; + for (int32 i = 0; i < audioComponents.Num(); i++) + { + Cast(audioComponents[i])->SetVolumeMultiplier(volume); + } + + if (IsValid(m_followActor)) + { + ANetworkCharacter* networkcharacter = Cast(m_followActor); + if(networkcharacter) + networkcharacter->RegisterEffect(this); + } +} +void AEffect::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + if (IsValid(m_followActor)) + { + ANetworkCharacter* networkcharacter = Cast(m_followActor); + if (networkcharacter) + networkcharacter->UnRegisterEffect(this); + } +} +void AEffect::OnRootParticleSystemFinished(UParticleSystemComponent*) +{ + if(m_duration == 0.0f) // Only auto-destroy if duration was not set + Destroy(); +} +void AEffect::OnRootAudioFinished() +{ + if(m_duration == 0.0f) // Only auto-destroy if duration was not set + Destroy(); +} +AActor* AEffect::GetFollower() const +{ + return m_followActor; +} +void AEffect::DeactivateParticles() +{ + for(int32 i = 0; i < m_particleSystems.Num(); i++) + { + Cast(m_particleSystems[i])->DeactivateSystem(); + } + deactivateParticlesBefore = 0.0f; +} +void AEffect::CalculatePosition_Implementation() +{ + if (m_followActor) + { + SetActorLocation(m_followActor->GetActorLocation()); + SetActorRotation(m_followActor->GetActorRotation()); + } +} +void AEffect::BeginEffect_Implementation() +{ +} +void AEffect::Tick( float DeltaTime ) +{ + Super::Tick( DeltaTime ); + + CalculatePosition(); + + m_lifeTime += DeltaTime; + if(m_duration > 0.0f) // Only use timer if duration was set + { + if(deactivateParticlesBefore > 0.0f) + { + if((m_lifeTime + deactivateParticlesBefore) >= m_duration) + { + DeactivateParticles(); + } + } + + if(m_lifeTime >= m_duration) + { + Destroy(); + } + } + if (m_follow && !IsValid(m_followActor)) + DeactivateParticles(); +} +void AEffect::Init(float duration, AActor* followActor, bool clientSideOnly) +{ + if(clientSideOnly) + bReplicates = false; + m_duration = duration; + m_followActor = followActor; + BeginEffect(); + m_follow = IsValid(followActor); +} +void AEffect::End_Implementation() +{ + //check(Role == ROLE_Authority); + if(deactivateParticlesBefore > 0.0f) + { + m_duration = deactivateParticlesBefore; + m_lifeTime = 0.0f; + DeactivateParticles(); + check(m_duration > 0.0f); + } + else + { + Destroy(); + } +} + +void ATargetedEffect::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + DOREPLIFETIME(ATargetedEffect, target); +} diff --git a/Source/UnrealProject/Effects/Effect.h b/Source/UnrealProject/Effects/Effect.h new file mode 100644 index 0000000..45306d0 --- /dev/null +++ b/Source/UnrealProject/Effects/Effect.h @@ -0,0 +1,74 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "GameFramework/Actor.h" +#include "Effect.generated.h" + +UCLASS() +class UNREALPROJECT_API AEffect : public AActor +{ + GENERATED_BODY() + +public: + AEffect(); + + virtual void BeginPlay() override; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + + virtual void Tick( float DeltaSeconds ) override; + + UFUNCTION() + void Init(float duration, AActor* followActor = nullptr, bool clientSideOnly = false); + + UFUNCTION(BlueprintNativeEvent) + void BeginEffect(); + + // Destroys this particle system + waits for deactivateParticlesBefore seconds after deactivating particle systems + UFUNCTION(BlueprintCallable, NetMulticast, Reliable, Category="Effect") + void End(); + + // Event fired when end is called + UFUNCTION(BlueprintImplementableEvent) + void OnEnd(); + + // Function used to update the position of this effect + UFUNCTION(BlueprintNativeEvent) + void CalculatePosition(); + + UFUNCTION() + void OnRootParticleSystemFinished(UParticleSystemComponent* sys); + UFUNCTION() + void OnRootAudioFinished(); + + UFUNCTION(BlueprintCallable, Category="Effect") + AActor* GetFollower() const; + + // Time before the end of the duration in which the particle system is deactivated + UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Category="Particles") + float deactivateParticlesBefore; + + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Effect", meta = (ExposeOnSpawn)) + float m_lifeTime; +private: + void DeactivateParticles(); + + TArray m_particleSystems; + UParticleSystemComponent* m_rootParticleSystem; + UAudioComponent* m_rootAudio; + + UPROPERTY(Replicated, ReplicatedUsing = CalculatePosition) + AActor* m_followActor; + UPROPERTY(Replicated) + float m_duration; + bool m_follow; +}; + +UCLASS() +class ATargetedEffect : public AEffect +{ + GENERATED_BODY() +public: + UPROPERTY(Replicated, BlueprintReadOnly) + FVector target; +}; \ No newline at end of file diff --git a/Source/UnrealProject/Effects/EffectFunctionLibrary.cpp b/Source/UnrealProject/Effects/EffectFunctionLibrary.cpp new file mode 100644 index 0000000..288a6e2 --- /dev/null +++ b/Source/UnrealProject/Effects/EffectFunctionLibrary.cpp @@ -0,0 +1,185 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "DefaultGameInstance.h" +#include "Prefs.h" +#include "ItemBase.h" +#include "Effect.h" +#include "AbilityIndicator.h" +#include "NetworkCharacter.h" +#include "NetworkPlayer.h" +#include "EffectFunctionLibrary.h" + +AEffect* UEffectFunctionLibrary::CreateEffect(UObject* worldContextObject, TSubclassOf effectClass, + class AActor* followObject, float startTime, float durationOverride, bool clientSideOnly) +{ + UWorld* world = GEngine->GetWorldFromContextObject(worldContextObject); + if(!effectClass) + { + GWERROR(L"Can't create effect, class not set"); + return nullptr; + } + if(!world) + { + GWERROR(L"Can't create effect, world not set"); + return nullptr; + } + if(!world->GetAuthGameMode()) + return nullptr; // Only server can spawn replicated effects + + FTransform spawnTransform = FTransform::Identity; + if(followObject) + { + spawnTransform = followObject->GetTransform(); + } + + else + { + AActor* spawnActor = Cast(worldContextObject); + if(spawnActor) + spawnTransform = spawnActor->GetTransform(); + } + + spawnTransform.SetScale3D(FVector(1.0f, 1.0f, 1.0f)); // Reset scale, only inherit parent position + rotation + + AEffect* pfx = world->SpawnActorDeferred(effectClass, spawnTransform); + if(!pfx) + return nullptr; + pfx->m_lifeTime = startTime; + pfx->Init(durationOverride, followObject, clientSideOnly); + UGameplayStatics::FinishSpawningActor(pfx, spawnTransform); + return pfx; +} + + + AEffect* UEffectFunctionLibrary::CreateEffectAt(UObject* worldContextObject, TSubclassOf effectClass, + const FTransform& transform, float startTime, float durationOverride, bool clientSideOnly) + { + UWorld* world = GEngine->GetWorldFromContextObject(worldContextObject); + if (!effectClass) + { + GWERROR(L"Can't create effect, class not set"); + return nullptr; + } + if (!world) + { + GWERROR(L"Can't create effect, world not set"); + return nullptr; + } + if (!world->GetAuthGameMode()) + return nullptr; // Only server can spawn replicated effects + AEffect* pfx = world->SpawnActorDeferred(effectClass, transform); + check(pfx); + pfx->m_lifeTime = startTime; + pfx->Init(durationOverride, nullptr, clientSideOnly); + + UGameplayStatics::FinishSpawningActor(pfx, transform); + return pfx; + } + + AEffect* UEffectFunctionLibrary::CreateEffectAttached(UObject* worldContextObject, TSubclassOf effectClass, + const FTransform& transform, USceneComponent* InParent, FName InSocketName, EAttachLocation::Type AttachLocationType, float startTime, float durationOverride, bool clientSideOnly) + { + UWorld* world = GEngine->GetWorldFromContextObject(worldContextObject); + if (!effectClass) + { + GWERROR(L"Can't create effect, class not set"); + return nullptr; + } + if (!world) + { + GWERROR(L"Can't create effect, world not set"); + return nullptr; + } + if (!world->GetAuthGameMode()) + return nullptr; // Only server can spawn replicated effects + AEffect* pfx = world->SpawnActorDeferred(effectClass, transform); + check(pfx); + pfx->m_lifeTime = startTime; + pfx->Init(durationOverride, nullptr, clientSideOnly); + UGameplayStatics::FinishSpawningActor(pfx, transform); + pfx->AttachRootComponentTo(InParent, InSocketName, AttachLocationType); + return pfx; + } + +AAbilityIndicator * UEffectFunctionLibrary::CreateAbilityIndicator(TSubclassOf AAbilityIndicatorClass, ANetworkCharacter* spawner, AActor* followObject, FVector scale, float durationOverride, bool center) +{ + if (!spawner) + { + FWERROR(L"Can't create abilityIndicator, spawner not set"); + return nullptr; + } + //transforming from cm to m duo to 4.11 + FVector newscale = scale * 0.01f; + UWorld* world = spawner->GetWorld(); + FTransform spawnTransform = spawner->GetTransform(); + AAbilityIndicator* pai = world->SpawnActorDeferred(AAbilityIndicatorClass, spawnTransform); + check(pai); + if (center) + pai->Init(durationOverride, FVector(0, 0.0, 0), FRotator(0, 90, 0), newscale * 0.5f, center, followObject, pai->teamData->GetTeamColor(spawner->GetTeam())); + else + pai->Init(durationOverride, FVector(0, scale.Y, 0) + FVector(0,100,0), FRotator(0, 90, 0), newscale * 0.5f, center, followObject, pai->teamData->GetTeamColor(spawner->GetTeam())); + UGameplayStatics::FinishSpawningActor(pai, spawnTransform); + return pai; +} + +AAbilityIndicator * UEffectFunctionLibrary::CreateAbilityIndicatorAt(TSubclassOf AAbilityIndicatorClass, ANetworkCharacter * spawner, const FTransform& transform, FVector scale, float durationOverride, bool center) +{ + if (!spawner) + { + FWERROR(L"Can't create abilityIndicator, spawner not set"); + return nullptr; + } + FVector newscale = scale * 0.01f; + UWorld* world = spawner->GetWorld(); + FTransform spawnTransform = transform; + AAbilityIndicator* pai = world->SpawnActorDeferred(AAbilityIndicatorClass, spawnTransform); + check(pai); + if (center) + pai->Init(durationOverride, FVector(0, 0.0, 0), FRotator(0, 90, 0), newscale * 0.5f, center, nullptr, pai->teamData->GetTeamColor(spawner->GetTeam())); + else + pai->Init(durationOverride, FVector(0, scale.Y * 0.5f, 0), FRotator(0, 90, 0), newscale * 0.5f, center, nullptr, pai->teamData->GetTeamColor(spawner->GetTeam())); + UGameplayStatics::FinishSpawningActor(pai, spawnTransform); + return pai; +} + +void UEffectFunctionLibrary::PlaySoundEffect2D(UObject* worldContextObject, USoundBase* sound) +{ + UWorld* world = GEngine->GetWorldFromContextObject(worldContextObject); + float volume = Cast(world->GetGameInstance())->GetPrefs()->sfxVolume; + UGameplayStatics::PlaySound2D(worldContextObject, sound, volume); +} + +void UEffectFunctionLibrary::DuplicateAppearance(ACharacterBase* from, ACharacterBase* to) +{ + if (from == nullptr) + { + FWARNING("from == nullptr"); + return; + } + USkeletalMeshComponent* fromMesh = from->GetMesh(); + if (fromMesh == nullptr) + { + FWARNING("from->mesh == nullptr"); + return; + } + if (to == nullptr) + { + FWARNING("to == nullptr"); + return; + } + // Copy equiped items + TArray> itemsToSpawn; + for(auto i : from->GetEquipedItems()) + { + itemsToSpawn.Add(i->GetClass()); + } + to->EquipItems(itemsToSpawn); + + // Copy CUstomization + to->SetCustomizations(from->GetCharacterCustomization()); + + to->playerName = from->playerName; + + return; +} \ No newline at end of file diff --git a/Source/UnrealProject/Effects/EffectFunctionLibrary.h b/Source/UnrealProject/Effects/EffectFunctionLibrary.h new file mode 100644 index 0000000..7c6379a --- /dev/null +++ b/Source/UnrealProject/Effects/EffectFunctionLibrary.h @@ -0,0 +1,42 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "Kismet/BlueprintFunctionLibrary.h" +#include "EffectFunctionLibrary.generated.h" + +/** + * + */ +UCLASS() +class UNREALPROJECT_API UEffectFunctionLibrary : public UBlueprintFunctionLibrary +{ + GENERATED_BODY() + +public: + UFUNCTION(BlueprintCallable, Category = "Effect", meta = (WorldContext="worldContextObject")) + static class AEffect* CreateEffect(UObject* worldContextObject, TSubclassOf effectClass, + class AActor* followObject = nullptr, float startTime = 0.0f, float durationOverride = 0.0f, bool clientSideOnly = false); + + UFUNCTION(BlueprintCallable, Category = "Effect", meta = (WorldContext = "worldContextObject")) + static class AEffect* CreateEffectAt(UObject* worldContextObject, TSubclassOf effectClass, + const FTransform& transform, float startTime = 0.0f, float durationOverride = 0.0f, bool clientSideOnly = false); + + UFUNCTION(BlueprintCallable, Category = "Effect", meta = (WorldContext = "worldContextObject")) + static class AEffect* CreateEffectAttached(UObject* worldContextObject, TSubclassOf effectClass, + const FTransform& transform, USceneComponent* InParent, FName InSocketName, EAttachLocation::Type AttachLocationType, float startTime = 0.0f, float durationOverride = 0.0f, bool clientSideOnly = false); + + UFUNCTION(BlueprintCallable, Category = "Effect") + static class AAbilityIndicator* CreateAbilityIndicator(TSubclassOf AAbilityIndicatorClass, + class ANetworkCharacter* spawner, class AActor* followObject, FVector scale, float durationOverride = 0.0f, bool center = true); + + UFUNCTION(BlueprintCallable, Category = "Effect") + static class AAbilityIndicator* CreateAbilityIndicatorAt(TSubclassOf AAbilityIndicatorClass, ANetworkCharacter * spawner, const FTransform& transform, FVector scale, float durationOverride, bool center); + + UFUNCTION(BlueprintCallable, Category = "Effect", meta = (WorldContext="worldContextObject")) + static void PlaySoundEffect2D(UObject* worldContextObject, USoundBase* sound); + + //from is the character who's skeleton is cloned, to is who gets the cloned skeleton + UFUNCTION(BlueprintCallable, Category = "Effect") + static void DuplicateAppearance(class ACharacterBase* from, class ACharacterBase* to); +}; diff --git a/Source/UnrealProject/Effects/TeamData.cpp b/Source/UnrealProject/Effects/TeamData.cpp new file mode 100644 index 0000000..138c442 --- /dev/null +++ b/Source/UnrealProject/Effects/TeamData.cpp @@ -0,0 +1,20 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "TeamData.h" + + + + +FLinearColor UTeamData::GetTeamColor(int32 team) +{ + + for (FTeamDataStruct it : teamColors) + { + if (it.team == team) + { + return it.color; + } + } + return FLinearColor(1, 1, 1, 1); +} \ No newline at end of file diff --git a/Source/UnrealProject/Effects/TeamData.h b/Source/UnrealProject/Effects/TeamData.h new file mode 100644 index 0000000..3ec65d1 --- /dev/null +++ b/Source/UnrealProject/Effects/TeamData.h @@ -0,0 +1,36 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "Engine/DataAsset.h" +#include "TeamData.generated.h" + +/** + * + */ + +USTRUCT() +struct UNREALPROJECT_API FTeamDataStruct +{ + GENERATED_BODY() +public: + UPROPERTY(EditDefaultsOnly) + int32 team; + UPROPERTY(EditDefaultsOnly) + FLinearColor color; +}; + + +UCLASS(BlueprintType) +class UNREALPROJECT_API UTeamData : public UDataAsset +{ + GENERATED_BODY() + +public: + UPROPERTY(EditDefaultsOnly) + TArray teamColors; + + UFUNCTION(BlueprintCallable, Category="Team Data") + FLinearColor GetTeamColor(int32 team); + +}; diff --git a/Source/UnrealProject/External/Events.hpp b/Source/UnrealProject/External/Events.hpp new file mode 100644 index 0000000..128adb8 --- /dev/null +++ b/Source/UnrealProject/External/Events.hpp @@ -0,0 +1,171 @@ +#pragma once + + +#pragma pack(push, 1) +enum class EventCode : uint8_t +{ + PlayerSpawn = 0, + PlayerUpdate, + PlayerDie, + PlayerCast, + PlayerHealth, + PlayerMana, + PlayerMaxHealth, + PlayerMaxMana, + PlayerDealDamage, + PlayerLevel, + SessionEnd, + + MetaEvents = 64, + + MapData, + MapImage, + PlayerName, + PlayerTeam, + AbilityName, +}; + +struct PlayerSpawn +{ + PlayerSpawn() = default; + PlayerSpawn(uint32_t time, uint8_t player, float playerX, float playerY) : event(EventCode::PlayerSpawn), time(time), player(player), playerX(playerX), playerY(playerY) {} + EventCode event; + uint32_t time; + uint8_t player; + float playerX; + float playerY; +}; +struct PlayerUpdate +{ + PlayerUpdate() = default; + PlayerUpdate(uint32_t time, uint8_t player, float playerX, float playerY) : event(EventCode::PlayerUpdate), time(time), player(player), playerX(playerX), playerY(playerY) {} + EventCode event; + uint32_t time; + uint8_t player; + float playerX; + float playerY; +}; +struct PlayerDie +{ + PlayerDie() = default; + PlayerDie(uint32_t time, uint8_t player, uint8_t source) : event(EventCode::PlayerDie), time(time), player(player), source(source) {} + EventCode event; + uint32_t time; + uint8_t player; + uint8_t source; +}; +struct PlayerCast +{ + PlayerCast() = default; + PlayerCast(uint32_t time, uint8_t player, uint32_t ability) : event(EventCode::PlayerCast), time(time), player(player), ability(ability) {} + EventCode event; + uint32_t time; + uint8_t player; + uint32_t ability; +}; +struct PlayerHealth +{ + PlayerHealth() = default; + PlayerHealth(uint32_t time, uint8_t player, int32_t health) : event(EventCode::PlayerHealth), time(time), player(player), health(health) {} + EventCode event; + uint32_t time; + uint8_t player; + int32_t health; +}; +struct PlayerMana +{ + PlayerMana() = default; + PlayerMana(uint32_t time, uint8_t player, int32_t mana) : event(EventCode::PlayerMana), time(time), player(player), mana(mana) {} + EventCode event; + uint32_t time; + uint8_t player; + int32_t mana; +}; +struct PlayerMaxHealth +{ + PlayerMaxHealth() = default; + PlayerMaxHealth(uint32_t time, uint8_t player, int32_t maxHealth) : event(EventCode::PlayerMaxHealth), time(time), player(player), maxHealth(maxHealth) {} + EventCode event; + uint32_t time; + uint8_t player; + int32_t maxHealth; +}; +struct PlayerMaxMana +{ + PlayerMaxMana() = default; + PlayerMaxMana(uint32_t time, uint8_t player, int32_t maxMana) : event(EventCode::PlayerMaxMana), time(time), player(player), maxMana(maxMana) {} + EventCode event; + uint32_t time; + uint8_t player; + int32_t maxMana; +}; +struct PlayerDealDamage +{ + PlayerDealDamage() = default; + PlayerDealDamage(uint32_t time, uint8_t player, uint32_t ability, int32_t damage) : event(EventCode::PlayerDealDamage), time(time), player(player), ability(ability), damage(damage) {} + EventCode event; + uint32_t time; + uint8_t player; + uint32_t ability; + int32_t damage; +}; +struct PlayerLevel +{ + PlayerLevel() = default; + PlayerLevel(uint32_t time, uint8_t player, uint8_t level) : event(EventCode::PlayerLevel), time(time), player(player), level(level) {} + EventCode event; + uint32_t time; + uint8_t player; + uint8_t level; +}; +struct SessionEnd +{ + SessionEnd() = default; + SessionEnd(uint32_t time) : event(EventCode::SessionEnd), time(time) {} + EventCode event; + uint32_t time; +}; + +struct MapData +{ + MapData() = default; + MapData(float minX, float maxX, float minY, float maxY) : event(EventCode::MapData), minX(minX), maxX(maxX), minY(minY), maxY(maxY) {} + EventCode event; + float minX; + float maxX; + float minY; + float maxY; +}; +struct MapImage +{ + MapImage() = default; + MapImage(uint32_t width, uint32_t height) : event(EventCode::MapImage), width(width), height(height) {} + EventCode event; + uint32_t width; + uint32_t height; +}; +struct PlayerName +{ + PlayerName() = default; + PlayerName(uint8_t player, uint16_t length) : event(EventCode::PlayerName), player(player), length(length) {} + EventCode event; + uint8_t player; + uint16_t length; +}; +struct PlayerTeam +{ + PlayerTeam() = default; + PlayerTeam(uint8_t player, uint8_t team) : event(EventCode::PlayerTeam), player(player), team(team) {} + EventCode event; + uint8_t player; + uint8_t team; +}; +struct AbilityName +{ + AbilityName() = default; + AbilityName(uint32_t ability, uint16_t length) : event(EventCode::AbilityName), ability(ability), length(length) {} + EventCode event; + uint32_t ability; + uint16_t length; +}; +#pragma pack(pop) \ No newline at end of file diff --git a/Source/UnrealProject/External/HeatMapMetrics.cpp b/Source/UnrealProject/External/HeatMapMetrics.cpp new file mode 100644 index 0000000..5abdcb4 --- /dev/null +++ b/Source/UnrealProject/External/HeatMapMetrics.cpp @@ -0,0 +1,96 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" + +#if PLATFORM_SPECIFIC_WIN == 0 + +#include "HeatMapMetrics.h" + +#include +#include +#include +#include +#include + + +using namespace std; + +float HeatMapMetrics::s_timeMap[3][512][512]; + +void HeatMapMetrics::ResetMap() +{ + memset(&s_timeMap, 0, sizeof(s_timeMap)); +} + +void HeatMapMetrics::AddTime(FVector2D position, float time, size_t team) +{ + position *= 512.0f; + s_timeMap[team][(int)position.Y][(int)position.X] += time; +} + +void HeatMapMetrics::ExportToFile() +{ + char* envPath; + size_t envPathSize; + getenv_s(&envPathSize, NULL, 0, "APPDATA"); + if (envPathSize == 0) + { + TERROR("APPDATA does not exist."); + return; + } + + envPath = (char*)malloc(envPathSize * sizeof(char)); + if (!envPath) + { + TERROR("Failed to allocate memory."); + return; + } + + getenv_s(&envPathSize, envPath, envPathSize, "APPDATA"); + + + string path = envPath; + + free(envPath); + + path.append("\\Haxis\\HaxisHeatmap"); + path.append(m_GetCurrentDateTime()); + path.append(".HME"); + fstream fs; + fs.open(path.c_str(), fstream::out | fstream::binary); + if (!fs.is_open()) + { + TERROR("failed to export metrics"); + return; + } + + fs.write((char*)&s_timeMap[0][0][0], sizeof(s_timeMap)); + + fs.close(); +} + +void HeatMapMetrics::m_RetrieveCurrentTM(std::tm& a_TM) +{ + time_t currentTime = time(0); + localtime_s(&a_TM, ¤tTime); +} +string HeatMapMetrics::m_GetCurrentTime() +{ + tm tstruct; + char buffer[80]; + m_RetrieveCurrentTM(tstruct); + strftime(buffer, sizeof(buffer), "(%H:%M:%S) ", &tstruct); + + return buffer; +} +string HeatMapMetrics::m_GetCurrentDateTime() +{ + tm tstruct; + char buffer[80]; + m_RetrieveCurrentTM(tstruct); + strftime(buffer, sizeof(buffer), "%Y-%m-%d_%H-%M-%S", &tstruct); + + return buffer; +} + +#endif \ No newline at end of file diff --git a/Source/UnrealProject/External/HeatMapMetrics.h b/Source/UnrealProject/External/HeatMapMetrics.h new file mode 100644 index 0000000..cf2967a --- /dev/null +++ b/Source/UnrealProject/External/HeatMapMetrics.h @@ -0,0 +1,27 @@ +// Project Lab - NHTV Igad + +#pragma once + +#if PLATFORM_SPECIFIC_WIN == 0 + +#include +#include +/** + * + */ + +class UNREALPROJECT_API HeatMapMetrics +{ +public: + static void AddTime(struct FVector2D position, float time, size_t team); + static void ExportToFile(); + static void ResetMap(); +protected: + static void m_RetrieveCurrentTM(std::tm& a_TM); + static std::string m_GetCurrentTime(); + static std::string m_GetCurrentDateTime(); +private: + static float s_timeMap[3][512][512]; +}; + +#endif \ No newline at end of file diff --git a/Source/UnrealProject/External/InputDeviceBases.hpp b/Source/UnrealProject/External/InputDeviceBases.hpp new file mode 100644 index 0000000..9e82a50 --- /dev/null +++ b/Source/UnrealProject/External/InputDeviceBases.hpp @@ -0,0 +1,46 @@ +#ifndef _HEADER_INPUT_DEVICE_BASES +#define _HEADER_INPUT_DEVICE_BASES + +#include "InputEnums.hpp" +#include "TeaVector2.hpp" +#include "TeaVector3.hpp" + +namespace Input +{ + enum InputDeviceType + { + IDT_NONE, + IDT_NOTDEFINED, + IDT_XBOX, + IDT_DS4 + }; + + class InputDevice + { + public: + virtual void Update() = 0; + }; + + class Keyboard : public InputDevice + { + public: + virtual bool GetKey(const InputKey a_Key) = 0; + virtual bool GetKeyDown(const InputKey a_Key) = 0; + }; + class Joystick : public InputDevice + { + public: + virtual ~Joystick() {}; + virtual bool GetButton(const InputJoystickButton a_JoystickButton) = 0; + virtual bool GetButton(const InputJoystickPhysical a_JoystickButton) = 0; + virtual bool GetButtonDown(const InputJoystickButton a_JoystickButton) = 0; + virtual bool GetButtonDown(const InputJoystickPhysical a_JoystickButton) = 0; + virtual bool GetButtonUp(const InputJoystickButton a_JoystickButton) = 0; + virtual bool GetButtonUp(const InputJoystickPhysical a_JoystickButton) = 0; + virtual bool IsValid() = 0; + virtual TeaLib::Math::Vector2f GetDelta(const InputJoystickAxis a_InputStick) = 0; + virtual InputDeviceType GetDeviceType() = 0; + }; +} + +#endif diff --git a/Source/UnrealProject/External/InputEnums.hpp b/Source/UnrealProject/External/InputEnums.hpp new file mode 100644 index 0000000..f03811b --- /dev/null +++ b/Source/UnrealProject/External/InputEnums.hpp @@ -0,0 +1,285 @@ +#ifndef _HEADER_INPUT_ENUMS +#define _HEADER_INPUT_ENUMS + +namespace Input +{ + + // Keyboard input keys + enum InputKey : unsigned char + { + IK_ESCAPE = 0x01, + IK_1 = 0x02, + IK_2 = 0x03, + IK_3 = 0x04, + IK_4 = 0x05, + IK_5 = 0x06, + IK_6 = 0x07, + IK_7 = 0x08, + IK_8 = 0x09, + IK_9 = 0x0A, + IK_0 = 0x0B, + IK_MINUS = 0x0C, + IK_EQUALS = 0x0D, + IK_BACK = 0x0E, + IK_TAB = 0x0F, + IK_Q = 0x10, + IK_W = 0x11, + IK_E = 0x12, + IK_R = 0x13, + IK_T = 0x14, + IK_Y = 0x15, + IK_U = 0x16, + IK_I = 0x17, + IK_O = 0x18, + IK_P = 0x19, + IK_LBRACKET = 0x1A, + IK_RBRACKET = 0x1B, + IK_RETURN = 0x1C, + IK_LCONTROL = 0x1D, + IK_A = 0x1E, + IK_S = 0x1F, + IK_D = 0x20, + IK_F = 0x21, + IK_G = 0x22, + IK_H = 0x23, + IK_J = 0x24, + IK_K = 0x25, + IK_L = 0x26, + IK_SEMICOLON = 0x27, + IK_APOSTROPHE = 0x28, + IK_GRAVE = 0x29, + IK_LSHIFT = 0x2A, + IK_BACKSLASH = 0x2B, + IK_Z = 0x2C, + IK_X = 0x2D, + IK_C = 0x2E, + IK_V = 0x2F, + IK_B = 0x30, + IK_N = 0x31, + IK_M = 0x32, + IK_COMMA = 0x33, + IK_PERIOD = 0x34, + IK_SLASH = 0x35, + IK_RSHIFT = 0x36, + IK_MULTIPLY = 0x37, + IK_LMENU = 0x38, + IK_SPACE = 0x39, + IK_CAPITAL = 0x3A, + IK_F1 = 0x3B, + IK_F2 = 0x3C, + IK_F3 = 0x3D, + IK_F4 = 0x3E, + IK_F5 = 0x3F, + IK_F6 = 0x40, + IK_F7 = 0x41, + IK_F8 = 0x42, + IK_F9 = 0x43, + IK_F10 = 0x44, + IK_NUMLOCK = 0x45, + IK_SCROLL = 0x46, + IK_NUMPAD7 = 0x47, + IK_NUMPAD8 = 0x48, + IK_NUMPAD9 = 0x49, + IK_SUBTRACT = 0x4A, + IK_NUMPAD4 = 0x4B, + IK_NUMPAD5 = 0x4C, + IK_NUMPAD6 = 0x4D, + IK_ADD = 0x4E, + IK_NUMPAD1 = 0x4F, + IK_NUMPAD2 = 0x50, + IK_NUMPAD3 = 0x51, + IK_NUMPAD0 = 0x52, + IK_DECIMAL = 0x53, + IK_OEM_102 = 0x56, + IK_F11 = 0x57, + IK_F12 = 0x58, + IK_F13 = 0x64, + IK_F14 = 0x65, + IK_F15 = 0x66, + IK_KANA = 0x70, + IK_ABNT_C1 = 0x73, + IK_CONVERT = 0x79, + IK_NOCONVERT = 0x7B, + IK_YEN = 0x7D, + IK_ABNT_C2 = 0x7E, + IK_NUMPADEQUALS = 0x8D, + IK_PREVTRACK = 0x90, + IK_AT = 0x91, + IK_COLON = 0x92, + IK_UNDERLINE = 0x93, + IK_KANJI = 0x94, + IK_STOP = 0x95, + IK_AX = 0x96, + IK_UNLABELED = 0x97, + IK_NEXTTRACK = 0x99, + IK_NUMPADENTER = 0x9C, + IK_RCONTROL = 0x9D, + IK_MUTE = 0xA0, + IK_CALCULATOR = 0xA1, + IK_PLAYPAUSE = 0xA2, + IK_MEDIASTOP = 0xA4, + IK_VOLUMEDOWN = 0xAE, + IK_VOLUMEUP = 0xB0, + IK_WEBHOME = 0xB2, + IK_NUMPADCOMMA = 0xB3, + IK_DIVIDE = 0xB5, + IK_SYSRQ = 0xB7, + IK_RMENU = 0xB8, + IK_PAUSE = 0xC5, + IK_HOME = 0xC7, + IK_UP = 0xC8, + IK_PRIOR = 0xC9, + IK_LEFT = 0xCB, + IK_RIGHT = 0xCD, + IK_END = 0xCF, + IK_DOWN = 0xD0, + IK_NEXT = 0xD1, + IK_INSERT = 0xD2, + IK_DELETE = 0xD3, + IK_LWIN = 0xDB, + IK_RWIN = 0xDC, + IK_APPS = 0xDD, + IK_POWER = 0xDE, + IK_SLEEP = 0xDF, + IK_WAKE = 0xE3, + IK_WEBSEARCH = 0xE5, + IK_WEBFAVORITES = 0xE6, + IK_WEBREFRESH = 0xE7, + IK_WEBSTOP = 0xE8, + IK_WEBFORWARD = 0xE9, + IK_WEBBACK = 0xEA, + IK_MYCOMPUTER = 0xEB, + IK_MAIL = 0xEC, + IK_MEDIASELECT = 0xED, + IK_BACKSPACE = IK_BACK, + IK_NUMPADSTAR = IK_MULTIPLY, + IK_LALT = IK_LMENU, + IK_CAPSLOCK = IK_CAPITAL, + IK_NUMPADMINUS = IK_SUBTRACT, + IK_NUMPADPLUS = IK_ADD, + IK_NUMPADPERIOD = IK_DECIMAL, + IK_NUMPADSLASH = IK_DIVIDE, + IK_RALT = IK_RMENU, + IK_UPARROW = IK_UP, + IK_PGUP = IK_PRIOR, + IK_LEFTARROW = IK_LEFT, + IK_RIGHTARROW = IK_RIGHT, + IK_DOWNARROW = IK_DOWN, + IK_PGDN = IK_NEXT, + }; + + // Joystick axis + enum InputJoystickAxis + { + IJA_LSTICK, + IJA_RSTICK, + IJA_LRTRIGGER, + IJA_DPAD, + }; + + // Joystick buttons + enum InputJoystickButton : unsigned char + { + IJB_BUTTON0, + IJB_BUTTON1, + IJB_BUTTON2, + IJB_BUTTON3, + IJB_BUTTON4, + IJB_BUTTON5, + IJB_BUTTON6, + IJB_BUTTON7, + IJB_BUTTON8, + IJB_BUTTON9, + IJB_BUTTON10, + IJB_BUTTON11, + IJB_BUTTON12, + IJB_BUTTON13, + IJB_BUTTON14, + IJB_BUTTON15, + IJB_BUTTON16, + IJB_BUTTON17, + IJB_BUTTON18, + IJB_BUTTON19, + IJB_BUTTON20, + IJB_BUTTON21, + IJB_BUTTON22, + IJB_BUTTON23, + IJB_BUTTON24, + IJB_BUTTON25, + IJB_BUTTON26, + IJB_BUTTON27, + IJB_BUTTON28, + IJB_BUTTON29, + IJB_BUTTON30, + IJB_BUTTON31, + IJB_DPADUP = 200, + IJB_DPADRIGHT = 201, + IJB_DPADLEFT = 202, + IJB_DPADDOWN = 203, + // Xbox 360 buttons + IJB_XBOXA = IJB_BUTTON0, + IJB_XBOXB = IJB_BUTTON1, + IJB_XBOXX = IJB_BUTTON2, + IJB_XBOXY = IJB_BUTTON3, + IJB_XBOXLB = IJB_BUTTON4, + IJB_XBOXRB = IJB_BUTTON5, + IJB_XBOXBACK = IJB_BUTTON6, + IJB_XBOXSTART = IJB_BUTTON7, + IJB_XBOXLEFTSTICK = IJB_BUTTON8, + IJB_XBOXRIGHTSTICK = IJB_BUTTON9, + IJB_XBOXDPADUP = IJB_DPADUP, + IJB_XBOXDPADRIGHT = IJB_DPADRIGHT, + IJB_XBOXDPADLEFT = IJB_DPADLEFT, + IJB_XBOXDPADDOWN = IJB_DPADDOWN, + IJB_XBOXLT = 204, + IJB_XBOXRT = 205, + // Dualshock 4 buttons + IJB_DS4SQUARE = IJB_BUTTON0, + IJB_DS4CROSS = IJB_BUTTON1, + IJB_DS4CIRCLE = IJB_BUTTON2, + IJB_DS4TRIANGLE = IJB_BUTTON3, + IJB_DS4L1 = IJB_BUTTON4, + IJB_DS4R1 = IJB_BUTTON5, + IJB_DS4L2 = IJB_BUTTON6, + IJB_DS4R2 = IJB_BUTTON7, + IJB_DS4SHARE = IJB_BUTTON8, + IJB_DS4OPTIONS = IJB_BUTTON9, + IJB_DS4LEFTSTICK = IJB_BUTTON10, + IJB_DS4RIGHTSTICK = IJB_BUTTON11, + IJB_DS4PLAYSTATION = IJB_BUTTON12, + IJB_DS4TOUCH = IJB_BUTTON13, + IJB_DS4DPADUP = IJB_DPADUP, + IJB_DS4DPADRIGHT = IJB_DPADRIGHT, + IJB_DS4DPADLEFT = IJB_DPADLEFT, + IJB_DS4DPADDOWN = IJB_DPADDOWN, + }; + + enum InputJoystickPhysical : unsigned char + { + IJP_FACEDOWN = 0, + IJP_FACERIGHT = 1, + IJP_FACELEFT = 2, + IJP_FACEUP = 3, + IJP_DPADDOWN = 4, + IJP_DPADRIGHT = 5, + IJP_DPADLEFT = 6, + IJP_DPADUP = 7, + IJP_SHOULDERRIGHT = 8, + IJP_SHOULDERLEFT = 9, + IJP_TRIGGERRIGHT = 10, + IJP_TRIGGERLEFT = 11, + IJP_START = 12, + IJP_SELECT = 13, + IJP_STICKLEFT = 14, + IJP_STICKRIGHT = 15, + }; + + enum InputManagerEvent : unsigned char + { + IME_PRESSED = 0, + IME_RELEASED = 1, + IME_REPEAT = 2, + }; +} + +#endif diff --git a/Source/UnrealProject/External/InputManager.cpp b/Source/UnrealProject/External/InputManager.cpp new file mode 100644 index 0000000..45d4dc7 --- /dev/null +++ b/Source/UnrealProject/External/InputManager.cpp @@ -0,0 +1,688 @@ +#include "UnrealProject.h" +#if PLATFORM_SPECIFIC_WIN == 0 + +#define _SOURCE_INPUT_MANAGER +#include "InputManager.hpp" + +#include "AllowWindowsPlatformTypes.h" + +#pragma comment(lib, "dinput8.lib") +#pragma comment (lib, "dxguid.lib") + +#define DIRECTINPUT_VERSION 0x0800 +#include + +#include +#include + +using namespace std; + +#define DUALSHOCK4PRODUCTDATA (0x05C4054CUL) +#define DUALSHOCK4DEVTYPE (0x00010318UL) +#define WIREDXBOXDEVTYPE (0x00010215UL) +#define PI 3.14159265359f + +namespace Input +{ + struct GUID_Impl + { + GUID instance; + DWORD devType; + unsigned long productData1; + }; + + + LPDIRECTINPUT8 g_DirectInput; + HWND g_HWND; + InputManager* InputManager::s_instance; + bool InputManager::s_isInitialized = false; + vector g_GUIDs; + + bool InputManager::IsInitialized() + { + return s_isInitialized; + } + bool InputManager::Initialize() + { + if (!IsInitialized()) + { + g_HWND = GetActiveWindow(); + HRESULT hr; + HINSTANCE hInstance = GetModuleHandle(nullptr); + + hr = DirectInput8Create(hInstance, DIRECTINPUT_VERSION, IID_IDirectInput8, (void**)&g_DirectInput, NULL); + + if (FAILED(hr)) + { + return false; + } + + s_instance = new InputManager(); + + + s_isInitialized = true; + return true; + } + return false; + } + void InputManager::Cleanup() + { + if (IsInitialized()) + { + delete s_instance; + s_instance = nullptr; + g_DirectInput->Release(); + s_isInitialized = false; + } + } + InputManager* InputManager::GetInstance() + { + if (!s_isInitialized) + Initialize(); + return s_instance; + } + + + // Implementation of Input Devices + class Keyboard_Impl : public Keyboard + { + public: + Keyboard_Impl() + { + HRESULT hr; + + hr = g_DirectInput->CreateDevice(GUID_SysKeyboard, &m_Device, NULL); + if (FAILED(hr)) + { + } + hr = m_Device->SetDataFormat(&c_dfDIKeyboard); + if (FAILED(hr)) + { + } + hr = m_Device->SetCooperativeLevel(g_HWND, DISCL_BACKGROUND | DISCL_NONEXCLUSIVE); + if (FAILED(hr)) + { + } + } + ~Keyboard_Impl() + { + m_Device->Unacquire(); + } + virtual void Update() override final + { + HRESULT hr; + hr = m_Device->Acquire(); + memcpy(m_PreviousState, m_CurrentState, sizeof(BYTE) * 256); + m_Device->GetDeviceState(sizeof(m_CurrentState), (LPVOID)&m_CurrentState); + } + virtual bool GetKey(const InputKey a_Key) override final + { + return (m_CurrentState[a_Key] & 0x80) != 0; + } + virtual bool GetKeyDown(const InputKey a_Key) override final + { + return m_CurrentState[a_Key] & 0x80 && !(m_PreviousState[a_Key] & 0x80); + } + + private: + IDirectInputDevice8* m_Device; + BYTE m_CurrentState[256]; + BYTE m_PreviousState[256]; + }; + class Joystick_Impl : public Joystick + { + public: + Joystick_Impl(IDirectInputDevice8* a_Device) + { + HRESULT hr; + + m_Device = a_Device; + + hr = m_Device->SetDataFormat(&c_dfDIJoystick); + if (FAILED(hr)) + { + } + hr = m_Device->SetCooperativeLevel(g_HWND, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE); + if (FAILED(hr)) + { + } + } + virtual ~Joystick_Impl() + { + m_Device->Unacquire(); + m_Device->Release(); + } + virtual void Update() override final + { + HRESULT hr; + + m_PreviousState = m_CurrentState; + hr = m_Device->Acquire(); + if (FAILED(hr)) + { + m_IsValid = false; + return; + } + hr = m_Device->GetDeviceState(sizeof(DIJOYSTATE), (LPVOID)&m_CurrentState); + if (FAILED(hr)) + { + m_IsValid = false; + return; + } + m_IsValid = true; + } + virtual bool IsValid() override final + { + return m_IsValid; + } + virtual bool GetButton(const InputJoystickButton a_JoystickButton) + { + return (m_CurrentState.rgbButtons[a_JoystickButton] & 0x80) != 0; + } + virtual bool GetButton(const InputJoystickPhysical a_JoystickButton) + { + return false; + } + virtual bool GetButtonDown(const InputJoystickButton a_JoystickButton) + { + return m_CurrentState.rgbButtons[a_JoystickButton] & 0x80 && !(m_PreviousState.rgbButtons[a_JoystickButton] & 0x80); + } + virtual bool GetButtonDown(const InputJoystickPhysical a_JoystickButton) + { + return false; + } + virtual bool GetButtonUp(const InputJoystickButton a_JoystickButton) + { + return m_PreviousState.rgbButtons[a_JoystickButton] & 0x80 && !(m_CurrentState.rgbButtons[a_JoystickButton] & 0x80); + } + virtual bool GetButtonUp(const InputJoystickPhysical a_JoystickButton) + { + return false; + } + virtual TeaLib::Math::Vector2f GetDelta(const InputJoystickAxis a_InputStick) + { + if (IsValid()) + { + switch (a_InputStick) + { + case Input::IJA_LSTICK: + return TeaLib::Math::Vector2f((float)m_CurrentState.lX, (float)m_CurrentState.lY) / 32767.0f; + case Input::IJA_RSTICK: + return TeaLib::Math::Vector2f((float)m_CurrentState.lRx, (float)m_CurrentState.lRy) / 32767.0f; + case Input::IJA_LRTRIGGER: + return TeaLib::Math::Vector2f((float)m_CurrentState.lZ, (float)m_CurrentState.lRz) / 32767.0f; + default: + return TeaLib::Math::Vector2f(); + } + } + return TeaLib::Math::Vector2f(); + } + virtual InputDeviceType GetDeviceType() + { + return IDT_NOTDEFINED; + } + protected: + bool m_IsValid = false; + IDirectInputDevice8* m_Device; + DIJOYSTATE m_CurrentState, m_PreviousState; + }; + class JoystickDS4_Impl : public Joystick_Impl + { + public: + JoystickDS4_Impl(IDirectInputDevice8* a_Device) : Joystick_Impl(a_Device) + { + m_PhysicalBindings[IJP_FACEDOWN] = IJB_DS4CROSS; + m_PhysicalBindings[IJP_FACERIGHT] = IJB_DS4CIRCLE; + m_PhysicalBindings[IJP_FACELEFT] = IJB_BUTTON0; + m_PhysicalBindings[IJP_FACEUP] = IJB_BUTTON3; + m_PhysicalBindings[IJP_DPADDOWN] = IJB_DS4DPADDOWN; + m_PhysicalBindings[IJP_DPADRIGHT] = IJB_DS4DPADRIGHT; + m_PhysicalBindings[IJP_DPADLEFT] = IJB_DS4DPADLEFT; + m_PhysicalBindings[IJP_DPADUP] = IJB_DS4DPADUP; + m_PhysicalBindings[IJP_SHOULDERRIGHT] = IJB_DS4R1; + m_PhysicalBindings[IJP_SHOULDERLEFT] = IJB_DS4L1; + m_PhysicalBindings[IJP_TRIGGERRIGHT] = IJB_DS4R2; + m_PhysicalBindings[IJP_TRIGGERLEFT] = IJB_DS4L2; + m_PhysicalBindings[IJP_START] = IJB_DS4OPTIONS; + m_PhysicalBindings[IJP_SELECT] = IJB_DS4SHARE; + m_PhysicalBindings[IJP_STICKLEFT] = IJB_DS4LEFTSTICK; + m_PhysicalBindings[IJP_STICKRIGHT] = IJB_DS4RIGHTSTICK; + } + virtual bool GetButton(const InputJoystickButton a_JoystickButton) override final + { + if (a_JoystickButton < 200) + return (m_CurrentState.rgbButtons[a_JoystickButton] & 0x80) != 0; + else + { + switch (a_JoystickButton) + { + case Input::IJB_DS4DPADUP: + if (m_CurrentState.rgdwPOV[0] < 36000 && m_CurrentState.rgdwPOV[0] > 27000 || m_CurrentState.rgdwPOV[0] < 9000) + return true; + break; + case Input::IJB_DS4DPADRIGHT: + if (m_CurrentState.rgdwPOV[0] > 0 && m_CurrentState.rgdwPOV[0] < 18000) + return true; + break; + case Input::IJB_DS4DPADDOWN: + if (m_CurrentState.rgdwPOV[0] > 9000 && m_CurrentState.rgdwPOV[0] < 27000) + return true; + break; + case Input::IJB_DS4DPADLEFT: + if (m_CurrentState.rgdwPOV[0] > 18000 && m_CurrentState.rgdwPOV[0] < 36000) + return true; + break; + default: + break; + } + return false; + } + } + virtual bool GetButton(const InputJoystickPhysical a_JoystickButton) override final + { + return GetButton(m_PhysicalBindings[a_JoystickButton]); + } + virtual bool GetButtonDown(const InputJoystickButton a_JoystickButton) override final + { + if (a_JoystickButton < 200) + return m_CurrentState.rgbButtons[a_JoystickButton] & 0x80 && !(m_PreviousState.rgbButtons[a_JoystickButton] & 0x80); + else + { + switch (a_JoystickButton) + { + case Input::IJB_DS4DPADUP: + if (m_CurrentState.rgdwPOV[0] == 0 && m_PreviousState.rgdwPOV[0] != 0) + return true; + break; + case Input::IJB_DS4DPADRIGHT: + if (m_CurrentState.rgdwPOV[0] == 9000 && m_PreviousState.rgdwPOV[0] != 9000) + return true; + break; + case Input::IJB_DS4DPADDOWN: + if (m_CurrentState.rgdwPOV[0] == 18000 && m_PreviousState.rgdwPOV[0] != 18000) + return true; + break; + case Input::IJB_DS4DPADLEFT: + if (m_CurrentState.rgdwPOV[0] == 27000 && m_PreviousState.rgdwPOV[0] != 27000) + return true; + break; + default: + break; + } + return false; + } + } + virtual bool GetButtonDown(const InputJoystickPhysical a_JoystickButton) override final + { + return GetButtonDown(m_PhysicalBindings[a_JoystickButton]); + } + virtual bool GetButtonUp(const InputJoystickButton a_JoystickButton) override final + { + if (a_JoystickButton < 200) + return m_PreviousState.rgbButtons[a_JoystickButton] & 0x80 && !(m_CurrentState.rgbButtons[a_JoystickButton] & 0x80); + else + { + switch (a_JoystickButton) + { + case Input::IJB_DS4DPADUP: + if (m_PreviousState.rgdwPOV[0] == 0 && m_CurrentState.rgdwPOV[0] != 0) + return true; + break; + case Input::IJB_DS4DPADRIGHT: + if (m_PreviousState.rgdwPOV[0] == 9000 && m_CurrentState.rgdwPOV[0] != 9000) + return true; + break; + case Input::IJB_DS4DPADDOWN: + if (m_PreviousState.rgdwPOV[0] == 18000 && m_CurrentState.rgdwPOV[0] != 18000) + return true; + break; + case Input::IJB_DS4DPADLEFT: + if (m_PreviousState.rgdwPOV[0] == 27000 && m_CurrentState.rgdwPOV[0] != 27000) + return true; + break; + default: + break; + } + return false; + } + } + virtual bool GetButtonUp(const InputJoystickPhysical a_JoystickButton) override final + { + return GetButtonUp(m_PhysicalBindings[a_JoystickButton]); + } + virtual TeaLib::Math::Vector2f GetDelta(const InputJoystickAxis a_InputStick) override final + { + if (IsValid()) + { + float angle; + switch (a_InputStick) + { + case Input::IJA_LSTICK: + return TeaLib::Math::Vector2f((float)m_CurrentState.lX - 32767.0f, (float)m_CurrentState.lY - 32767.0f) / 32767.0f; + case Input::IJA_RSTICK: + return TeaLib::Math::Vector2f((float)m_CurrentState.lZ - 32767.0f, (float)m_CurrentState.lRz - 32767.0f) / 32767.0f; + case Input::IJA_LRTRIGGER: + return TeaLib::Math::Vector2f((float)m_CurrentState.lRx, (float)m_CurrentState.lRy) / 65534.0f; + case Input::IJA_DPAD: + if (m_PreviousState.rgdwPOV[0] < 90000) + { + angle = (((float)m_PreviousState.rgdwPOV[0] / 100.0f) * PI / 180.0f); + return TeaLib::Math::Vector2f(sinf(angle), -cosf(angle)); + } + return TeaLib::Math::Vector2f(); + default: + return TeaLib::Math::Vector2f(); + } + } + return TeaLib::Math::Vector2f(); + } + virtual InputDeviceType GetDeviceType() override final + { + return IDT_DS4; + } + private: + map m_PhysicalBindings; + }; + class JoystickXbox360_Impl : public Joystick_Impl + { + public: + JoystickXbox360_Impl(IDirectInputDevice8* a_Device) : Joystick_Impl(a_Device) + { + m_PhysicalBindings[IJP_FACEDOWN] = IJB_XBOXA; + m_PhysicalBindings[IJP_FACERIGHT] = IJB_XBOXB; + m_PhysicalBindings[IJP_FACELEFT] = IJB_XBOXX; + m_PhysicalBindings[IJP_FACEUP] = IJB_XBOXY; + m_PhysicalBindings[IJP_DPADDOWN] = IJB_XBOXDPADDOWN; + m_PhysicalBindings[IJP_DPADRIGHT] = IJB_XBOXDPADRIGHT; + m_PhysicalBindings[IJP_DPADLEFT] = IJB_XBOXDPADLEFT; + m_PhysicalBindings[IJP_DPADUP] = IJB_XBOXDPADUP; + m_PhysicalBindings[IJP_SHOULDERRIGHT] = IJB_XBOXRB; + m_PhysicalBindings[IJP_SHOULDERLEFT] = IJB_XBOXLB; + m_PhysicalBindings[IJP_TRIGGERRIGHT] = IJB_XBOXRT; + m_PhysicalBindings[IJP_TRIGGERLEFT] = IJB_XBOXLT; + m_PhysicalBindings[IJP_START] = IJB_XBOXSTART; + m_PhysicalBindings[IJP_SELECT] = IJB_XBOXBACK; + m_PhysicalBindings[IJP_STICKLEFT] = IJB_XBOXLEFTSTICK; + m_PhysicalBindings[IJP_STICKRIGHT] = IJB_XBOXRIGHTSTICK; + } + virtual TeaLib::Math::Vector2f GetDelta(const InputJoystickAxis a_InputStick) override final + { + if (IsValid()) + { + float result; + float angle; + switch (a_InputStick) + { + case Input::IJA_LSTICK: + return TeaLib::Math::Vector2f((float)m_CurrentState.lX - 32767.0f, (float)m_CurrentState.lY - 32767.0f) / 32767.0f; + case Input::IJA_RSTICK: + return TeaLib::Math::Vector2f((float)m_CurrentState.lRx - 32767.0f, (float)m_CurrentState.lRy - 32767.0f) / 32767.0f; + case Input::IJA_LRTRIGGER: + result = ((float)m_CurrentState.lZ - 32767.0f) / 32767.0f; + return TeaLib::Math::Vector2f(result, result); + case Input::IJA_DPAD: + if (m_PreviousState.rgdwPOV[0] < 90000) + { + angle = (((float)m_PreviousState.rgdwPOV[0] / 100.0f) * PI / 180.0f); + return TeaLib::Math::Vector2f(sinf(angle), -cosf(angle)); + } + return TeaLib::Math::Vector2f(); + default: + return TeaLib::Math::Vector2f(); + } + } + return TeaLib::Math::Vector2f(); + } + virtual bool GetButton(const InputJoystickButton a_JoystickButton) override final + { + if (a_JoystickButton < 200) + return (m_CurrentState.rgbButtons[a_JoystickButton] & 0x80) != 0; + else + { + float val; + switch (a_JoystickButton) + { + case Input::IJB_XBOXDPADUP: + if (m_CurrentState.rgdwPOV[0] < 36000 && m_CurrentState.rgdwPOV[0] > 27000 || m_CurrentState.rgdwPOV[0] < 9000) + return true; + break; + case Input::IJB_XBOXDPADRIGHT: + if (m_CurrentState.rgdwPOV[0] > 0 && m_CurrentState.rgdwPOV[0] < 18000) + return true; + break; + case Input::IJB_XBOXDPADDOWN: + if (m_CurrentState.rgdwPOV[0] > 9000 && m_CurrentState.rgdwPOV[0] < 27000) + return true; + break; + case Input::IJB_XBOXDPADLEFT: + if (m_CurrentState.rgdwPOV[0] > 18000 && m_CurrentState.rgdwPOV[0] < 36000) + return true; + break; + case Input::IJB_XBOXLT: + val = ((float)m_CurrentState.lZ - 32767.0f) / 32767.0f;; + return val > 0.1f; + case Input::IJB_XBOXRT: + val = ((float)m_CurrentState.lZ - 32767.0f) / 32767.0f;; + return val < -0.1f; + default: + break; + } + return false; + } + } + virtual bool GetButton(const InputJoystickPhysical a_JoystickButton) override final + { + return GetButton(m_PhysicalBindings[a_JoystickButton]); + } + virtual bool GetButtonDown(const InputJoystickButton a_JoystickButton) override final + { + if (a_JoystickButton < 200) + return m_CurrentState.rgbButtons[a_JoystickButton] & 0x80 && !(m_PreviousState.rgbButtons[a_JoystickButton] & 0x80); + else + { + float curr, prev; + switch (a_JoystickButton) + { + case Input::IJB_XBOXDPADUP: + if (m_CurrentState.rgdwPOV[0] == 0 && m_PreviousState.rgdwPOV[0] != 0) + return true; + break; + case Input::IJB_XBOXDPADRIGHT: + if (m_CurrentState.rgdwPOV[0] == 9000 && m_PreviousState.rgdwPOV[0] != 9000) + return true; + break; + case Input::IJB_XBOXDPADDOWN: + if (m_CurrentState.rgdwPOV[0] == 18000 && m_PreviousState.rgdwPOV[0] != 18000) + return true; + break; + case Input::IJB_XBOXDPADLEFT: + if (m_CurrentState.rgdwPOV[0] == 27000 && m_PreviousState.rgdwPOV[0] != 27000) + return true; + break; + case Input::IJB_XBOXLT: + curr = ((float)m_CurrentState.lZ - 32767.0f) / 32767.0f; + prev = ((float)m_PreviousState.lZ - 32767.0f) / 32767.0f; + return curr > 0.1f && prev <= 0.1f; + case Input::IJB_XBOXRT: + curr = ((float)m_CurrentState.lZ - 32767.0f) / 32767.0f; + prev = ((float)m_PreviousState.lZ - 32767.0f) / 32767.0f; + return curr < -0.1f && prev >= -0.1f; + default: + break; + } + return false; + } + } + virtual bool GetButtonDown(const InputJoystickPhysical a_JoystickButton) override final + { + if (m_PhysicalBindings.find(a_JoystickButton) != m_PhysicalBindings.end()) + return GetButtonDown(m_PhysicalBindings[a_JoystickButton]); + return false; + } + virtual bool GetButtonUp(const InputJoystickButton a_JoystickButton) override final + { + if (a_JoystickButton < 200) + return m_PreviousState.rgbButtons[a_JoystickButton] & 0x80 && !(m_CurrentState.rgbButtons[a_JoystickButton] & 0x80); + else + { + float curr, prev; + switch (a_JoystickButton) + { + case Input::IJB_XBOXDPADUP: + if (m_PreviousState.rgdwPOV[0] == 0 && m_CurrentState.rgdwPOV[0] != 0) + return true; + break; + case Input::IJB_XBOXDPADRIGHT: + if (m_PreviousState.rgdwPOV[0] == 9000 && m_CurrentState.rgdwPOV[0] != 9000) + return true; + break; + case Input::IJB_XBOXDPADDOWN: + if (m_PreviousState.rgdwPOV[0] == 18000 && m_CurrentState.rgdwPOV[0] != 18000) + return true; + break; + case Input::IJB_XBOXDPADLEFT: + if (m_PreviousState.rgdwPOV[0] == 27000 && m_CurrentState.rgdwPOV[0] != 27000) + return true; + break; + case Input::IJB_XBOXLT: + curr = ((float)m_CurrentState.lZ - 32767.0f) / 32767.0f; + prev = ((float)m_PreviousState.lZ - 32767.0f) / 32767.0f; + return curr <= 0.1f && prev > 0.1f; + case Input::IJB_XBOXRT: + curr = ((float)m_CurrentState.lZ - 32767.0f) / 32767.0f; + prev = ((float)m_PreviousState.lZ - 32767.0f) / 32767.0f; + return curr >= -0.1f && prev < -0.1f; + default: + break; + } + return false; + } + } + virtual bool GetButtonUp(const InputJoystickPhysical a_JoystickButton) override final + { + if (m_PhysicalBindings.find(a_JoystickButton) != m_PhysicalBindings.end()) + return GetButtonUp(m_PhysicalBindings[a_JoystickButton]); + return false; + } + virtual InputDeviceType GetDeviceType() override final + { + return IDT_XBOX; + } + private: + map m_PhysicalBindings; + }; + + InputManager::InputManager() : joystick(nullptr) + { + keyboard = new Keyboard_Impl(); + } + + void InputManager::Update() + { + keyboard->Update(); + if (joystick) + { + joystick->Update(); + + if (joystick->IsValid()) + { + for (auto it : m_joystickCallbacks) + { + switch (it.first.second) + { + case IME_PRESSED: + if (joystick->GetButtonDown(it.first.first)) + { + it.second->Call(); + } + break; + case IME_RELEASED: + if (joystick->GetButtonUp(it.first.first)) + { + it.second->Call(); + } + break; + case IME_REPEAT: + if (joystick->GetButton(it.first.first)) + { + it.second->Call(); + } + break; + } + } + } + } + } + BOOL __stdcall JoystickEnumCallback(LPCDIDEVICEINSTANCE lpddi, LPVOID pvRef) + { + InputManager* inputInstance = InputManager::GetInstance(); + + inputInstance->AddJoystickName(lpddi->tszProductName); + + GUID_Impl newGUID; + newGUID.devType = lpddi->dwDevType; + newGUID.instance = lpddi->guidInstance; + newGUID.productData1 = lpddi->guidProduct.Data1; + + g_GUIDs.push_back(newGUID); + + return DIENUM_CONTINUE; + } + + void InputManager::AddJoystickName(std::wstring a_name) + { + m_joystickNames.push_back(a_name); + } + + void InputManager::SearchForJoystick() + { + m_joystickNames.clear(); + g_GUIDs.clear(); + g_DirectInput->EnumDevices(DI8DEVCLASS_GAMECTRL, &JoystickEnumCallback, NULL, DIEDFL_ATTACHEDONLY); + } + + vector& InputManager::GetJoystickNames() + { + return m_joystickNames; + } + + void InputManager::ConnectJoystickByIndex(size_t a_index) + { + if (joystick) + delete joystick; + + if (a_index >= g_GUIDs.size()) + return; + + HRESULT hr; + IDirectInputDevice8* device; + hr = g_DirectInput->CreateDevice(g_GUIDs[a_index].instance, &device, NULL); + if (!FAILED(hr)) + { + if (g_GUIDs[a_index].devType == DUALSHOCK4DEVTYPE || g_GUIDs[a_index].productData1 == DUALSHOCK4PRODUCTDATA) + joystick = new JoystickDS4_Impl(device); + else if (g_GUIDs[a_index].devType == WIREDXBOXDEVTYPE) + joystick = new JoystickXbox360_Impl(device); + else + joystick = new Joystick_Impl(device); + } + } + + InputManager::~InputManager() + { + delete keyboard; + delete joystick; + ClearCallbacks(); + } + + void InputManager::ClearCallbacks() + { + for (auto it : m_joystickCallbacks) + { + delete it.second; + } + m_joystickCallbacks.clear(); + } +} + +#endif \ No newline at end of file diff --git a/Source/UnrealProject/External/InputManager.hpp b/Source/UnrealProject/External/InputManager.hpp new file mode 100644 index 0000000..b6777ad --- /dev/null +++ b/Source/UnrealProject/External/InputManager.hpp @@ -0,0 +1,95 @@ +#ifndef _HEADER_INPUT_MANAGER +#define _HEADER_INPUT_MANAGER + +#include "InputDeviceBases.hpp" +#include "TeaCallback.hpp" +#include +#include +#include + +namespace Input +{ + class InputManager + { + public: + static bool IsInitialized(); + static bool Initialize(); + static void Cleanup(); + static InputManager* GetInstance(); + + private: + static bool s_isInitialized; + static InputManager* s_instance; + + public: + InputManager(); + ~InputManager(); + + InputDeviceType GetConnectedType() + { + if (joystick) + return joystick->GetDeviceType(); + return IDT_NONE; + } + bool IsJoystickConnected(const unsigned int a_Channel); + + void SearchForJoystick(); + + std::vector& GetJoystickNames(); + void AddJoystickName(std::wstring a_name); + + void ConnectJoystickByIndex(size_t m_index); + + void Update(); + + void RegisterCallback(InputJoystickPhysical a_Button, InputManagerEvent a_Event, void(*a_CallbackFunction)()) + { + std::pair newEvent(a_Button, a_Event); + if (m_joystickCallbacks.find(newEvent) == m_joystickCallbacks.end()) + { + m_joystickCallbacks[newEvent] = new TeaLib::Utility::Callback<>(); + } + m_joystickCallbacks[newEvent]->Register(a_CallbackFunction); + } + template void RegisterCallback(InputJoystickPhysical a_Button, InputManagerEvent a_Event, ClassType* a_ClassInstance, void(ClassType::*a_CallbackFunction)()) + { + std::pair newEvent(a_Button, a_Event); + if (m_joystickCallbacks.find(newEvent) == m_joystickCallbacks.end()) + { + m_joystickCallbacks[newEvent] = new TeaLib::Utility::Callback<>(); + } + m_joystickCallbacks[newEvent]->Register(a_ClassInstance, a_CallbackFunction); + } + void DeregisterCallback(InputJoystickPhysical a_Button, InputManagerEvent a_Event, void(*a_CallbackFunction)()) + { + if (!this) + return; + std::pair newEvent(a_Button, a_Event); + if (m_joystickCallbacks.find(newEvent) != m_joystickCallbacks.end()) + { + m_joystickCallbacks[newEvent]->Deregister(a_CallbackFunction); + }; + } + template void DeregisterCallback(InputJoystickPhysical a_Button, InputManagerEvent a_Event, ClassType* a_ClassInstance, void(ClassType::*a_CallbackFunction)()) + { + if (!this) + return; + std::pair newEvent(a_Button, a_Event); + if (m_joystickCallbacks.find(newEvent) != m_joystickCallbacks.end()) + { + m_joystickCallbacks[newEvent]->Deregister(a_ClassInstance, a_CallbackFunction); + } + } + + void ClearCallbacks(); + + Keyboard* keyboard; + Joystick* joystick; + + private: + std::vector m_joystickNames; + std::map, TeaLib::Utility::Callback<>*> m_joystickCallbacks; + }; +} + +#endif diff --git a/Source/UnrealProject/External/Metrics.cpp b/Source/UnrealProject/External/Metrics.cpp new file mode 100644 index 0000000..ab7494e --- /dev/null +++ b/Source/UnrealProject/External/Metrics.cpp @@ -0,0 +1,362 @@ +#include "UnrealProject.h" + +#if WITH_EDITOR + +#include "Metrics.hpp" +//#include +#include + +#include +#include +#include +#include +#include + +#if PLATFORM_SPECIFIC_WIN == 0 +#include "AllowWindowsPlatformTypes.h" +#include +#endif + +using namespace jlib; +using namespace std; +using namespace std::chrono; + +#include "Events.hpp" + +#include + +namespace Metrics +{ + ofstream writeStream; + timer writeTime; + uint32_t lastWriteTime; + + unordered_map players; + + uint32_t CurrentTime() + { + return uint32_t(writeTime.milliseconds()); + } + uint32_t TimeOffset() + { + const uint32_t currentTime = uint32_t(writeTime.milliseconds()); + const uint32_t offset = currentTime - lastWriteTime; + lastWriteTime = currentTime; + return offset; + } + wstring FormatNumber(uint16_t number, size_t desiredLength) + { + wstring result = wstring() + number; + while (result.size() < desiredLength) + result = wstring(L"0") + result; + return result; + } + + template void WriteEvent(const Event& event) + { + writeStream.write((const char*)&event, streamsize(sizeof(Event))); + } + void WriteBinary(void* addr, size_t size) + { + writeStream.write((const char*)addr, size); + } + + void Clear() + { + for (auto iter = players.begin(); iter != players.end(); iter++) + delete iter->second; + players.clear(); + } +} + +bool Metrics::StartSession(const wstring& filePath) +{ + EndSession(); + + time_t timePoint = system_clock::to_time_t(std::chrono::system_clock::now()); + const char* timeData = ctime(&timePoint); + wstring dateTime = convert_cstring_type(timeData); + + wstring dateTimeFiltered; + for (size_t i = 0; i < dateTime.size(); i++) + { + if (dateTime[i] == L'\n' || dateTime[i] == L'\r') + continue; + dateTimeFiltered += dateTime[i]; + } + dateTime = dateTimeFiltered; + + wstring sessionName = L"MetricsSession"; +#if PLATFORM_SPECIFIC_WIN == 0 + sessionName.clear(); + sessionName.resize(256); + DWORD length = 256; + GetComputerName(&sessionName[0], &length); + sessionName.resize(length); + wstring sessionNameFiltered; + for (size_t i = 0; i < sessionName.size(); i++) + { + wchar_t character = sessionName[i]; + if ((character >= L'0' && character <= L'9') || (character >= L'a' && character <= L'z') || (character >= L'A' && character <= L'Z') || character == L'-' || character == L'_') + sessionNameFiltered += character; + } + sessionName = sessionNameFiltered; +#endif + + wstring fileName = sessionName + L" - " + dateTime + L".hms"; + replace(fileName.begin(), fileName.end(), L':', L'-'); + writeStream.open(convert_string_type(filePath + fileName), ios::out | ios::binary | ios::trunc); + writeTime.restart(); + lastWriteTime = 0; + + // Write the header + writeStream.write("HMS", 3); + + return writeStream.is_open(); +} +bool Metrics::EndSession() +{ + if (writeStream.is_open()) + { + WriteEvent(SessionEnd(TimeOffset())); + + Clear(); + writeStream.close(); + return true; + } + + Clear(); + return false; +} + +struct Metrics::PlayerHandleSet +{ + static void Enable(PlayerHandle& handle, uint8_t id, uint8_t team) + { + handle.m_id = id; + handle.m_isValid = true; + handle.m_team = team; + } +}; + +void Metrics::SetMapData(float minX, float maxX, float minY, float maxY) +{ + if (!writeStream.is_open()) + return; + + WriteEvent(MapData(minX, maxX, minY, maxY)); +} +void Metrics::SetMapImage(uint8_t* ptr, uint32_t width, uint32_t height) +{ + if (!writeStream.is_open()) + return; + + WriteEvent(MapImage(width, height)); + WriteBinary(ptr, width * height * 4); +} + +Metrics::PlayerHandle::PlayerHandle() : m_isValid(false), m_id(-1), m_team(-1), m_updateTime(0), m_alive(false), m_health(INT_MAX), m_mana(INT_MAX), m_maxHealth(INT_MAX), m_maxMana(INT_MAX), m_level(255), m_playerX(FLT_MAX), m_playerY(FLT_MAX) +{ + +} + +void Metrics::PlayerHandle::OnPlayerSpawn(float playerX, float playerY) +{ + if (!writeStream.is_open() || !m_isValid) + return; + + if (m_alive) + return; + m_alive = true; + + m_playerX = playerX; + m_playerY = playerY; + + WriteEvent(PlayerSpawn(TimeOffset(), m_id, playerX, playerY)); +} +void Metrics::PlayerHandle::OnPlayerUpdate(float playerX, float playerY) +{ + if (!writeStream.is_open() || !m_isValid) + return; + + if (!m_alive) + return; + + // No need to write an event if the player hasnt moved + if (fabsf(playerX - m_playerX) < 0.01f && fabsf(playerY - m_playerY) < 0.01f) + return; + + // Only update position 30 times per second + const uint32_t currentTime = CurrentTime(); + if (currentTime - m_updateTime < 33) + return; + m_updateTime = currentTime; + + m_playerX = playerX; + m_playerY = playerY; + + WriteEvent(PlayerUpdate(TimeOffset(), m_id, playerX, playerY)); +} +void Metrics::PlayerHandle::OnPlayerDie(uint8_t source) +{ + if (!writeStream.is_open() || !m_isValid) + return; + + if (!m_alive) + return; + m_alive = false; + + WriteEvent(PlayerDie(TimeOffset(), m_id, source)); +} +void Metrics::PlayerHandle::OnPlayerCast(uint32_t ability) +{ + if (!writeStream.is_open() || !m_isValid) + return; + + if (!m_alive) + return; + + WriteEvent(PlayerCast(TimeOffset(), m_id, ability)); +} +void Metrics::PlayerHandle::OnPlayerHealth(int32_t health) +{ + if (!writeStream.is_open() || !m_isValid) + return; + + if (!m_alive) + return; + + // No need to write an event if the player hasnt lost/gained health + if (m_health == health) + return; + + m_health = health; + + WriteEvent(PlayerHealth(TimeOffset(), m_id, health)); +} +void Metrics::PlayerHandle::OnPlayerMana(int32_t mana) +{ + if (!writeStream.is_open() || !m_isValid) + return; + + if (!m_alive) + return; + + // No need to write an event if the player hasnt lost/gained mana + if (m_mana == mana) + return; + + m_mana = mana; + + WriteEvent(PlayerMana(TimeOffset(), m_id, mana)); +} +void Metrics::PlayerHandle::OnPlayerMaxHealth(int32_t maxHealth) +{ + if (!writeStream.is_open() || !m_isValid) + return; + + if (!m_alive) + return; + + // No need to write an event if the player hasnt changed max health + if (m_maxHealth == maxHealth) + return; + + m_maxHealth = maxHealth; + + WriteEvent(PlayerMaxHealth(TimeOffset(), m_id, maxHealth)); +} +void Metrics::PlayerHandle::OnPlayerMaxMana(int32_t maxMana) +{ + if (!writeStream.is_open() || !m_isValid) + return; + + if (!m_alive) + return; + + // No need to write an event if the player hasnt changed max mana + if (m_maxMana == maxMana) + return; + + m_maxMana = maxMana; + + WriteEvent(PlayerMaxMana(TimeOffset(), m_id, maxMana)); +} +void Metrics::PlayerHandle::OnPlayerDealDamage(uint32_t ability, int32_t damage) +{ + if (!writeStream.is_open() || !m_isValid) + return; + + if (!m_alive) + return; + + WriteEvent(PlayerDealDamage(TimeOffset(), m_id, ability, damage)); +} +void Metrics::PlayerHandle::OnPlayerLevel(uint8_t level) +{ + if (!writeStream.is_open() || !m_isValid) + return; + + if (m_level == level) + return; + + m_level = level; + + WriteEvent(PlayerLevel(TimeOffset(), m_id, level)); +} + +bool Metrics::PlayerHandle::IsValid() const +{ + return m_isValid; +} +uint8_t Metrics::PlayerHandle::Id() const +{ + return m_id; +} +uint8_t Metrics::PlayerHandle::Team() const +{ + return m_team; +} + +Metrics::PlayerHandle& Metrics::RegisterPlayer(const wstring& name, uint8_t team) +{ + static PlayerHandle errorHandle; + if (!writeStream.is_open()) + return errorHandle; + + const uint8_t id = uint8_t(players.size() + 1); + + PlayerHandle* newHandle = new PlayerHandle(); + PlayerHandleSet::Enable(*newHandle, id, team); + + players.emplace(id, newHandle); + + wstring copy = name; + if (copy.size() > 128) + copy.resize(128); + else if (copy.empty()) + copy = L"UNKNOWN PLAYER"; + + WriteEvent(PlayerName(id, (uint16_t)copy.size())); + WriteBinary((void*)copy.data(), copy.size() * sizeof(wstring::value_type)); + + WriteEvent(PlayerTeam(id, team)); + + return *newHandle; +} +void Metrics::RegisterAbility(uint32_t ability, const std::wstring& name) +{ + if (!writeStream.is_open()) + return; + + wstring copy = name; + if (copy.size() > 128) + copy.resize(128); + else if (copy.empty()) + copy = L"UNKNOWN ABILITY"; + + WriteEvent(AbilityName(ability, (uint16_t)copy.size())); + WriteBinary((void*)copy.data(), copy.size() * sizeof(wstring::value_type)); +} + +#endif \ No newline at end of file diff --git a/Source/UnrealProject/External/Metrics.hpp b/Source/UnrealProject/External/Metrics.hpp new file mode 100644 index 0000000..2288251 --- /dev/null +++ b/Source/UnrealProject/External/Metrics.hpp @@ -0,0 +1,71 @@ +#pragma once + +#include + +#if WITH_EDITOR + +#define UE_INCLUDE_METRICS 1 + +#define METRICS_EXPR(expr) expr + +namespace Metrics +{ + bool StartSession(const std::wstring& filePath); + bool EndSession(); + + void SetMapData(float minX, float maxX, float minY, float maxY); + void SetMapImage(uint8_t* ptr, uint32_t width, uint32_t height); + + struct PlayerHandleSet; + struct PlayerHandle + { + public: + PlayerHandle(); + + void OnPlayerSpawn(float playerX, float playerY); + void OnPlayerUpdate(float playerX, float playerY); + void OnPlayerDie(uint8_t source); + void OnPlayerCast(uint32_t ability); + void OnPlayerHealth(int32_t health); + void OnPlayerMana(int32_t mana); + void OnPlayerMaxHealth(int32_t maxHealth); + void OnPlayerMaxMana(int32_t maxMana); + void OnPlayerDealDamage(uint32_t ability, int32_t damage); + void OnPlayerLevel(uint8_t level); + + bool IsValid() const; + uint8_t Id() const; + uint8_t Team() const; + + private: + PlayerHandle(const PlayerHandle& other) = delete; + PlayerHandle& operator=(const PlayerHandle& other) = delete; + + friend struct PlayerHandleSet; + + bool m_isValid; + uint8_t m_id; + uint8_t m_team; + uint32_t m_updateTime; + + bool m_alive; + int32_t m_health; + int32_t m_mana; + int32_t m_maxHealth; + int32_t m_maxMana; + uint8_t m_level; + float m_playerX; + float m_playerY; + }; + + PlayerHandle& RegisterPlayer(const std::wstring& name, uint8_t team); + void RegisterAbility(uint32_t ability, const std::wstring& name); +} + +#else + +#define UE_INCLUDE_METRICS 0 + +#define METRICS_EXPR(expr) + +#endif \ No newline at end of file diff --git a/Source/UnrealProject/External/TeaCallback.hpp b/Source/UnrealProject/External/TeaCallback.hpp new file mode 100644 index 0000000..f50ff53 --- /dev/null +++ b/Source/UnrealProject/External/TeaCallback.hpp @@ -0,0 +1,137 @@ +#ifndef _HEADER_TEA_CALLBACK +#define _HEADER_TEA_CALLBACK + +#include +#include + +namespace TeaLib +{ + namespace Utility + { +#define NOVOIDFUNC(FuncRetVal) template typename std::enable_if::value, FuncRetVal>::type + template + class ReturnCallback + { + private: + // Typedef for comparing function pointers. + typedef ReturnValue(ReturnCallback::*CompareHandle)(Arguments...); + struct CallbackStructBase + { + public: + virtual ~CallbackStructBase() {}; + virtual ReturnValue Call(Arguments...) = 0; + virtual bool Compare(ReturnValue(*a_CallbackFunction)(Arguments...)) + { + return false; + }; + virtual bool Compare(void* a_ClassInstance, CompareHandle a_CallbackFunction) + { + return false; + }; + }; + struct CallbackStruct : public CallbackStructBase + { + public: + virtual ReturnValue Call(Arguments... a_Arg) + { + return m_CallbackFunction(a_Arg...); + } + virtual bool Compare(ReturnValue(*a_CallbackFunction)(Arguments...)) + { + return (a_CallbackFunction == m_CallbackFunction); + }; + ReturnValue(*m_CallbackFunction)(Arguments...); + }; + template struct ClassCallbackStruct : public CallbackStructBase + { + public: + virtual ReturnValue Call(Arguments... a_Arg) + { + return (m_ClassInstance->*m_CallbackFunction)(a_Arg...); + } + virtual bool Compare(void* a_ClassInstance, CompareHandle a_CallbackFunction) + { + CompareHandle compareHandle; + memcpy(&compareHandle, &m_CallbackFunction, sizeof(CompareHandle)); + + return ((a_ClassInstance == m_ClassInstance) && (a_CallbackFunction == compareHandle)); + }; + ClassType* m_ClassInstance; + ReturnValue(ClassType::*m_CallbackFunction)(Arguments...); + }; + public: + virtual ~ReturnCallback() + { + for (int i = (int)m_CallbackFunctions.size() - 1; i >= 0; i--) + { + delete m_CallbackFunctions[i]; + } + m_CallbackFunctions.clear(); + } + void Call(Arguments... a_Arg) + { + for (auto it : m_CallbackFunctions) + { + it->Call(a_Arg...); + } + } + NOVOIDFUNC(void) Call(std::vector* a_ReturnValues, Arguments... a_Arg) + { + for (auto it : m_CallbackFunctions) + { + a_ReturnValues->push_back(it->Call(a_Arg...)); + } + } + void Register(ReturnValue(*a_CallbackFunction)(Arguments...)) + { + CallbackStruct* newCallback = new CallbackStruct(); + newCallback->m_CallbackFunction = a_CallbackFunction; + m_CallbackFunctions.push_back(newCallback); + } + template void Register(ClassType* a_ClassInstance, ReturnValue(ClassType::*a_CallbackFunction)(Arguments...)) + { + ClassCallbackStruct* newCallback = new ClassCallbackStruct(); + newCallback->m_ClassInstance = a_ClassInstance; + newCallback->m_CallbackFunction = a_CallbackFunction; + m_CallbackFunctions.push_back(newCallback); + } + // Removes the first registered callback that matches the function. + void Deregister(ReturnValue(*a_CallbackFunction)(Arguments...)) + { + for (unsigned int i = 0; i < m_CallbackFunctions.size(); i++) + { + if (m_CallbackFunctions[i]->Compare(a_CallbackFunction)) + { + delete m_CallbackFunctions[i]; + m_CallbackFunctions.erase(m_CallbackFunctions.begin() + i); + break; + } + } + } + // Removes the first registered callback that matches the class instance and the function. + template void Deregister(ClassType* a_ClassInstance, ReturnValue(ClassType::*a_CallbackFunction)(Arguments...)) + { + CompareHandle compareHandle; + memcpy(&compareHandle, &a_CallbackFunction, sizeof(CompareHandle)); + for (unsigned int i = 0; i < m_CallbackFunctions.size(); i++) + { + if (m_CallbackFunctions[i]->Compare(a_ClassInstance, compareHandle)) + { + delete m_CallbackFunctions[i]; + m_CallbackFunctions.erase(m_CallbackFunctions.begin() + i); + break; + } + } + } + private: + std::vector m_CallbackFunctions; + }; + // Alias template for callbacks that return void. + template + using Callback = ReturnCallback; +#undef NOVOIDFUNC + }; +}; + + +#endif \ No newline at end of file diff --git a/Source/UnrealProject/External/TeaVector2.hpp b/Source/UnrealProject/External/TeaVector2.hpp new file mode 100644 index 0000000..23f784b --- /dev/null +++ b/Source/UnrealProject/External/TeaVector2.hpp @@ -0,0 +1,91 @@ +#ifndef _HEADER_TEA_MATH_VECTOR2 +#define _HEADER_TEA_MATH_VECTOR2 + +namespace TeaLib +{ + namespace Math + { + template + class Vector2 + { + public: + Vector2() : x(0), y(0){}; + Vector2(const T a_X, const T a_Y) : x(a_X), y(a_Y){}; + + Vector2& operator+=(const Vector2& rhs) + { + this->x += rhs.x; + this->y += rhs.y; + return *this; + } + Vector2& operator-=(const Vector2& rhs) + { + this->x -= rhs.x; + this->y -= rhs.y; + return *this; + } + Vector2& operator*=(const float& rhs) + { + this->x *= rhs; + this->y *= rhs; + return *this; + } + Vector2& operator/=(const float& rhs) + { + this->x /= rhs; + this->y /= rhs; + return *this; + } + + Vector2 operator+(const Vector2& rhs) + { + Vector2 retVal; + retVal.x = this->x + rhs.x; + retVal.y = this->y + rhs.y; + return retVal; + } + Vector2 operator-(const Vector2& rhs) + { + Vector2 retVal; + retVal.x = this->x - rhs.x; + retVal.y = this->y - rhs.y; + return retVal; + } + Vector2 operator*(const float& rhs) + { + Vector2 retVal; + retVal.x = this->x * rhs; + retVal.y = this->y * rhs; + return retVal; + } + Vector2 operator/(const float& rhs) + { + Vector2 retVal; + retVal.x = this->x / rhs; + retVal.y = this->y / rhs; + return retVal; + } + + bool operator==(const Vector2& rhs) + { + return ((this->x == rhs.x) && (this->y == rhs.y)); + } + + float LengthSquared() + { + return ((x*x) + (y*y)); + } + + float Length() + { + return sqrtf(LengthSquared()); + } + + T x, y; + }; + typedef Vector2 Vector2f; + typedef Vector2 Vector2i; + typedef Vector2 Vector2u; + }; +}; +#endif \ No newline at end of file diff --git a/Source/UnrealProject/External/TeaVector3.hpp b/Source/UnrealProject/External/TeaVector3.hpp new file mode 100644 index 0000000..ef52e93 --- /dev/null +++ b/Source/UnrealProject/External/TeaVector3.hpp @@ -0,0 +1,149 @@ +#ifndef _HEADER_TEA_MATH_VECTOR3 +#define _HEADER_TEA_MATH_VECTOR3 + +namespace TeaLib +{ + namespace Math + { + template + class Vector3 + { + public: + Vector3() : x(0), y(0), z(0){}; + Vector3(const T a_X, const T a_Y, const T a_Z) : x(a_X), y(a_Y), z(a_Z){}; + + Vector3& operator+=(const Vector3& rhs) + { + this->x += rhs.x; + this->y += rhs.y; + this->z += rhs.z; + return *this; + } + Vector3& operator-=(const Vector3& rhs) + { + this->x -= rhs.x; + this->y -= rhs.y; + this->z -= rhs.z; + return *this; + } + Vector3& operator*=(const float& rhs) + { + this->x *= rhs; + this->y *= rhs; + this->z *= rhs; + return *this; + } + Vector3& operator/=(const float& rhs) + { + this->x /= rhs; + this->y /= rhs; + this->z /= rhs; + return *this; + } + + Vector3 operator+(const Vector3& rhs) + { + Vector3 retVal; + retVal.x = this->x + rhs.x; + retVal.y = this->y + rhs.y; + retVal.z = this->z + rhs.z; + return retVal; + } + Vector3 operator-(const Vector3& rhs) + { + Vector3 retVal; + retVal.x = this->x - rhs.x; + retVal.y = this->y - rhs.y; + retVal.z = this->z - rhs.z; + return retVal; + } + Vector3 operator*(const float& rhs) + { + Vector3 retVal; + retVal.x = this->x * rhs; + retVal.y = this->y * rhs; + retVal.z = this->z * rhs; + return retVal; + } + friend Vector3 operator*(const float& lhs, const Vector3& rhs) + { + Vector3 retVal; + retVal.x = rhs.x * lhs; + retVal.y = rhs.y * lhs; + retVal.z = rhs.z * lhs; + return retVal; + } + Vector3 operator/(const float& rhs) + { + Vector3 retVal; + retVal.x = this->x / rhs; + retVal.y = this->y / rhs; + retVal.z = this->z / rhs; + return retVal; + } + Vector3 operator-() + { + Vector3 retVal; + retVal.x = -this->x; + retVal.y = -this->y; + retVal.z = -this->z; + return retVal; + } + + bool operator==(const Vector3& rhs) + { + return ((this->x == rhs.x) && (this->y == rhs.y) && (this->z == rhs.z)); + } + + + float LengthSquared() + { + return ((x*x) + (y*y) + (z*z)); + } + float Length() + { + return sqrtf(LengthSquared()); + } + + Vector3 Normalized() + { + return ((*this) / Length()); + } + + T x, y, z; + + public: + static Vector3 Cross(Vector3& lhs, Vector3& rhs) + { + Vector3 retVal; + + retVal.x = lhs.y * rhs.z - lhs.z * rhs.y; + retVal.y = lhs.z * rhs.x - lhs.x * rhs.z; + retVal.z = lhs.x * rhs.y - lhs.y * rhs.x; + + return retVal; + } + static float Dot(Vector3& lhs, Vector3& rhs) + { + float retVal; + + retVal = lhs.x * rhs.x + lhs.y * rhs.y + lhs.z * rhs.z; + + return retVal; + } + static Vector3 Lerp(Vector3& a_Source, Vector3& a_Destination, float a_Scale) + { + Vector3 between = a_Destination - a_Source; + + return a_Source + between * a_Scale; + } + }; + + typedef Vector3 Vector3f; + typedef Vector3 Vector3i; + typedef Vector3 Vector3u; + typedef Vector3 Vector3l; + + }; +}; +#endif \ No newline at end of file diff --git a/Source/UnrealProject/External/append b/Source/UnrealProject/External/append new file mode 100644 index 0000000..352c6ce --- /dev/null +++ b/Source/UnrealProject/External/append @@ -0,0 +1,146 @@ +/////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// // +// Copyright (c) 2012-2015, Jan de Graaf (jan@jlib.nl) // +// // +// Permission to use, copy, modify, and/or distribute this software for any purpose with or // +// without fee is hereby granted, provided that the above copyright notice and this permission // +// notice appear in all copies. // +// // +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS // +// SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL // +// THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES // +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE // +// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. // +// // +/////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#ifndef _JLIB_HEADER_APPEND +#define _JLIB_HEADER_APPEND + +#include +#include +#include +#include + +namespace std +{ + // Stream operator + template inline basic_string& operator<<(basic_string& lhs, const input_type_t& rhs) + { + basic_ostringstream stream; + return lhs.append(static_cast*>(&(stream << rhs))->str()); + } + + // Parsing numbers + template inline bool operator>>(const basic_string& lhs, input_type_t& rhs) + { + return (bool)(basic_istringstream(lhs) >> rhs); + } + + // Addition operators + // Utilizes enable_if to prevent colliding with string's existing operators + template struct is_string_operand + { + static const bool value = std::is_same::type, char_type_t>::value || std::is_same::type, char_type_t*>::value || std::is_same::type, std::basic_string>::value; + }; + template inline typename std::enable_if::value, basic_string&>::type operator+=(basic_string& lhs, const input_type_t& rhs) + { + basic_ostringstream stream; + return lhs.append(static_cast*>(&(stream << rhs))->str()); + } + template inline typename std::enable_if::value, basic_string>::type operator+(const basic_string& lhs, const input_type_t& rhs) + { + basic_ostringstream stream; + return basic_string(lhs).append(static_cast*>(&(stream << rhs))->str()); + } +} + +template inline std::basic_string convert_cstring_type(const from_type_t* cstr) +{ + static_assert(std::is_same::value, "Invalid char overload"); + return std::basic_string(cstr); +} +template<> inline std::basic_string convert_cstring_type(const wchar_t* wcstr) +{ + std::string str; + size_t i = 0; + while (wcstr[i] != L'\0') + { + const uint32_t c = uint32_t(wcstr[i]); + str.resize(i + 1); + str[i++] = (c < 32 || c >= 126) ? '?' : char(c); + } + return str; +} +template<> inline std::basic_string convert_cstring_type(const char* cstr) +{ + std::wstring wstr; + size_t i = 0; + while (cstr[i] != L'\0') + { + const wchar_t c = wchar_t(cstr[i]); + wstr.resize(i + 1); + wstr[i++] = c; + } + return wstr; +} +template inline std::basic_string convert_string_type(const std::basic_string& from_string) +{ + static_assert(std::is_same::value, "Invalid char overload"); + return from_string; +} +template<> inline std::basic_string convert_string_type(const std::basic_string& wstr) +{ + return convert_cstring_type(wstr.data()); +} +template<> inline std::basic_string convert_string_type(const std::basic_string& str) +{ + return convert_cstring_type(str.data()); +} + +namespace std +{ +#ifndef _NOUNREAL + template inline basic_string operator+(const basic_string& lhs, const class FString& rhs) + { + if (rhs.GetCharArray().GetData()) + return basic_string(lhs).append((convert_cstring_type(rhs.GetCharArray().GetData()))); + return basic_string(lhs); + } + template inline basic_string operator+(const basic_string& lhs, const class FName& rhs) + { + return basic_string(lhs) + rhs.ToString(); + } + template inline basic_string operator+(const basic_string& lhs, const class FText& rhs) + { + return basic_string(lhs) + rhs.ToString(); + } + + template inline basic_string operator+(const basic_string& lhs, const struct FVector& rhs) + { + static const char __LogSpace = ' '; + static const char __FVectorOpen = '{'; + static const char __FVectorEnd = '}'; + static const char __LogSep = ','; + return lhs + (char_type_t)__FVectorOpen + rhs.X + (char_type_t)__LogSep + (char_type_t)__LogSpace + rhs.Y + (char_type_t)__LogSep + (char_type_t)__LogSpace + rhs.Z + (char_type_t)__FVectorEnd; + } + template inline basic_string operator+(const basic_string& lhs, const struct FVector2D& rhs) + { + static const char __LogSpace = ' '; + static const char __FVectorOpen = '{'; + static const char __FVectorEnd = '}'; + static const char __LogSep = ','; + return lhs + (char_type_t)__FVectorOpen + rhs.X + (char_type_t)__LogSep + (char_type_t)__LogSpace + rhs.Y + (char_type_t)__FVectorEnd; + } + template inline basic_string operator+(const basic_string& lhs, const struct FRotator& rhs) + { + static const char __LogSpace = ' '; + static const char __FVectorOpen = '{'; + static const char __FVectorEnd = '}'; + static const char __LogSep = ','; + return lhs + (char_type_t)__FVectorOpen + rhs.Pitch + (char_type_t)__LogSep + (char_type_t)__LogSpace + rhs.Yaw + (char_type_t)__LogSep + (char_type_t)__LogSpace + rhs.Roll + (char_type_t)__FVectorEnd; + } +#endif +} + +#endif \ No newline at end of file diff --git a/Source/UnrealProject/External/timer b/Source/UnrealProject/External/timer new file mode 100644 index 0000000..2bdbb3f --- /dev/null +++ b/Source/UnrealProject/External/timer @@ -0,0 +1,106 @@ +/////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// // +// Copyright (c) 2012-2015, Jan de Graaf (jan@jlib.nl) // +// // +// Permission to use, copy, modify, and/or distribute this software for any purpose with or // +// without fee is hereby granted, provided that the above copyright notice and this permission // +// notice appear in all copies. // +// // +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS // +// SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL // +// THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES // +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE // +// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. // +// // +/////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#ifndef _JLIB_HEADER_TIMER +#define _JLIB_HEADER_TIMER + +/* + +Template chrono timer class + +Wraps std chrono basic time functions in a timer object. + +*/ + +#include + +namespace jlib +{ + class timer + { + public: + // Constructors + timer() : m_start(std::chrono::high_resolution_clock::now()) {} + template timer(std::chrono::duration duration) : m_start(std::chrono::high_resolution_clock::now()) + { + m_start -= duration; + } + template timer& operator=(std::chrono::duration duration) + { + restart(); + m_start -= duration; + return *this; + } + + inline void restart() + { + m_start = std::chrono::high_resolution_clock::now(); + } + + template inline dur duration() const + { + return std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - m_start); + } + + // Simplified get functions + inline std::chrono::nanoseconds::rep nanoseconds() const + { + return duration().count(); + } + inline std::chrono::microseconds::rep microseconds() const + { + return duration().count(); + } + inline std::chrono::milliseconds::rep milliseconds() const + { + return duration().count(); + } + inline std::chrono::seconds::rep seconds() const + { + return duration().count(); + } + inline std::chrono::minutes::rep minutes() const + { + return duration().count(); + } + inline std::chrono::hours::rep hours() const + { + return duration().count(); + } + inline float seconds_float() const + { + return duration>().count(); + } + + // Addition or substraction + // Allows to add or substract time periods from the timer + template inline timer& operator+=(std::chrono::duration duration) + { + m_start -= duration; + return *this; + } + template inline timer& operator-=(std::chrono::duration duration) + { + m_start += duration; + return *this; + } + + private: + std::chrono::high_resolution_clock::time_point m_start; + }; +} + +#endif \ No newline at end of file diff --git a/Source/UnrealProject/GUI/CombatText/CombatTextText.cpp b/Source/UnrealProject/GUI/CombatText/CombatTextText.cpp new file mode 100644 index 0000000..c6ac189 --- /dev/null +++ b/Source/UnrealProject/GUI/CombatText/CombatTextText.cpp @@ -0,0 +1,22 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "CombatTextText.h" + + + +void UCombatTextText::NativeConstruct() +{ + Super::NativeConstruct(); + lifeTime = 0; +} + + +void UCombatTextText::SetText_Implementation(const FString& text) +{ + +} +void UCombatTextText::SetColor_Implementation(const FLinearColor& color) +{ + +} \ No newline at end of file diff --git a/Source/UnrealProject/GUI/CombatText/CombatTextText.h b/Source/UnrealProject/GUI/CombatText/CombatTextText.h new file mode 100644 index 0000000..1bd4a17 --- /dev/null +++ b/Source/UnrealProject/GUI/CombatText/CombatTextText.h @@ -0,0 +1,29 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "Blueprint/UserWidget.h" +#include "CombatTextText.generated.h" + +/** + * + */ + +UCLASS() +class UNREALPROJECT_API UCombatTextText : public UUserWidget +{ + GENERATED_BODY() + +public: + virtual void NativeConstruct(); + + UFUNCTION(BlueprintNativeEvent, Category = "CombatText") + void SetText(const FString& text); + + UFUNCTION(BlueprintNativeEvent, Category = "CombatText") + void SetColor(const FLinearColor& color); + + + float lifeTime; + FVector position; +}; diff --git a/Source/UnrealProject/GUI/CombatText/CombatTextWidget.cpp b/Source/UnrealProject/GUI/CombatText/CombatTextWidget.cpp new file mode 100644 index 0000000..11dc8d7 --- /dev/null +++ b/Source/UnrealProject/GUI/CombatText/CombatTextWidget.cpp @@ -0,0 +1,106 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "CombatTextWidget.h" +#include "CombatTextText.h" +#include "WidgetLayoutLibrary.h" + +#define TEXT_LIFETIME 1.0f + +void UCombatTextWidget::NativeConstruct() +{ + Super::NativeConstruct(); + + +} +void UCombatTextWidget::NativeDestruct() +{ + Super::NativeDestruct(); +} + +void UCombatTextWidget::NativeTick(const FGeometry& geometry, float deltaTime) +{ + Super::NativeTick(geometry, deltaTime); + + UWorld* const world = GetWorld(); + if (!IsValid(world)) return; + APlayerController* const controller = world->GetFirstPlayerController(); + if (!IsValid(controller)) return; + + const FVector2D screenSize = FVector2D(world->GetGameViewport()->Viewport->GetSizeXY().X, world->GetGameViewport()->Viewport->GetSizeXY().Y); + const float viewportScale = UWidgetLayoutLibrary::GetViewportScale(this); + + + for (int32 i = 0; i < m_combatTexts.Num();) + { + // Check the lifetime + CombatText& text = m_combatTexts[i]; + text.lifeTime += deltaTime; + if (text.lifeTime >= TEXT_LIFETIME) + { + text.textObject->RemoveFromParent(); + m_combatTexts.RemoveAt(i); + continue; + } + + // Set the color + UCanvasPanelSlot* asSlot = Cast(text.textObject->Slot); + if (asSlot) + { + const float progress = text.lifeTime / TEXT_LIFETIME; + text.textObject->SetColor(FLinearColor(text.color.R, text.color.G, text.color.B, text.color.A * (1.0f - progress))); + + // Update position + FVector2D position; + if (controller->ProjectWorldLocationToScreen(text.position + FVector(0, 0, progress * 200), position)) + { + if (text.arc) + { + position -= FVector2D(progress * text.arcDir.X, FMath::Sin(progress * PI) * text.arcDir.Y) * 100; + asSlot->SetPosition(position / viewportScale); + } + else + asSlot->SetPosition(position / viewportScale); + } + } + + i++; + } +} + + +void UCombatTextWidget::CreateCombatText(const FVector& position, const FString& text, const FLinearColor& color) +{ + CombatText combatText(position, text, color, false); + + combatText.textObject = m_CreateTextObject(text, color); + + m_combatTexts.Add(combatText); +} +void UCombatTextWidget::CreateArcingCombatText(const FVector& position, const FString& text, const FLinearColor& color) +{ + CombatText combatText(position, text, color, true); + + combatText.arcDir.X = FMath::SRand() * 0.5f + 0.3f; + combatText.arcDir.Y = FMath::SRand() * 0.5f + 0.3f; + if (FMath::Rand() % 2 == 0) + combatText.arcDir.X = -combatText.arcDir.X; + + combatText.textObject = m_CreateTextObject(text, color); + + m_combatTexts.Add(combatText); +} + +UCombatTextText* UCombatTextWidget::m_CreateTextObject(const FString& text, const FLinearColor& color) +{ + UCombatTextText* newObject = CreateWidget(GetWorld(), textWidget); + canvas->AddChild(newObject); + UCanvasPanelSlot* asSlot = Cast(newObject->Slot); + asSlot->SetSize(FVector2D(1000, 500)); + asSlot->SetAlignment(FVector2D(0.5f, 0.05f)); + + newObject->SetText(text); + newObject->SetColor(color); + + return newObject; +} \ No newline at end of file diff --git a/Source/UnrealProject/GUI/CombatText/CombatTextWidget.h b/Source/UnrealProject/GUI/CombatText/CombatTextWidget.h new file mode 100644 index 0000000..b7fec4c --- /dev/null +++ b/Source/UnrealProject/GUI/CombatText/CombatTextWidget.h @@ -0,0 +1,54 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "Blueprint/UserWidget.h" +#include "CombatTextWidget.generated.h" + +/** + * + */ + + + +UCLASS() +class UNREALPROJECT_API UCombatTextWidget : public UUserWidget +{ + GENERATED_BODY() + +public: + virtual void NativeConstruct(); + virtual void NativeDestruct(); + virtual void NativeTick(const FGeometry& geometry, float deltaTime) override; + + UPROPERTY(EditAnywhere, Category = "CombatText") + TSubclassOf textWidget; + + UPROPERTY(BlueprintReadWrite, Category = "CombatText") + class UCanvasPanel* canvas; + + UFUNCTION(BlueprintCallable, Category = "CombatText") + void CreateCombatText(const FVector& position, const FString& text, const FLinearColor& color); + + UFUNCTION(BlueprintCallable, Category = "CombatText") + void CreateArcingCombatText(const FVector& position, const FString& text, const FLinearColor& color); + + +private: + struct CombatText + { + CombatText(const FVector& position, const FString& text, const FLinearColor& color, bool arc) : position(position), text(text), color(color), lifeTime(0), arc(arc) {} + FVector position; + FString text; + FLinearColor color; + float lifeTime; + bool arc; + FVector2D arcDir; + + UCombatTextText* textObject; + }; + + TArray m_combatTexts; + + UCombatTextText* m_CreateTextObject(const FString& text, const FLinearColor& color); +}; diff --git a/Source/UnrealProject/GUI/EventHUD.cpp b/Source/UnrealProject/GUI/EventHUD.cpp new file mode 100644 index 0000000..00a8d3a --- /dev/null +++ b/Source/UnrealProject/GUI/EventHUD.cpp @@ -0,0 +1,19 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "EventHUD.h" + +void UEventHUD::NativeConstruct() +{ + Super::NativeConstruct(); +} + +void UEventHUD::NativeDestruct() +{ + Super::NativeDestruct(); +} + +void UEventHUD::AddEvent_Implementation( const FEventObject& event ) +{ + +} \ No newline at end of file diff --git a/Source/UnrealProject/GUI/EventHUD.h b/Source/UnrealProject/GUI/EventHUD.h new file mode 100644 index 0000000..6a3e733 --- /dev/null +++ b/Source/UnrealProject/GUI/EventHUD.h @@ -0,0 +1,47 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "Blueprint/UserWidget.h" +#include "EventHUD.generated.h" + +UENUM(BlueprintType) +enum class EEventType : uint8 +{ + ET_Kill UMETA(DisplayName = "Kill Event"), + ET_Capture UMETA(DisplayName = "Capture Event"), + ET_KOTH UMETA(DisplayName = "KOTH Event") +}; + +USTRUCT() +struct FEventObject +{ + GENERATED_USTRUCT_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Event Struct") + FText Source; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Event Struct") + FText Target; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Event Struct") + EEventType Type; + + UPROPERTY( EditAnywhere, BlueprintReadWrite, Category = "Event Struct" ) + int32 SourceNum; + + UPROPERTY( EditAnywhere, BlueprintReadWrite, Category = "Event Struct" ) + int32 TargetNum; +}; + +UCLASS() +class UNREALPROJECT_API UEventHUD : public UUserWidget +{ + GENERATED_BODY() +public: + virtual void NativeConstruct() override; + virtual void NativeDestruct() override; + + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "UI") + void AddEvent( const FEventObject& event ); +}; diff --git a/Source/UnrealProject/GUI/EventWidget.cpp b/Source/UnrealProject/GUI/EventWidget.cpp new file mode 100644 index 0000000..4585423 --- /dev/null +++ b/Source/UnrealProject/GUI/EventWidget.cpp @@ -0,0 +1,19 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "EventWidget.h" + +void UEventWidget::NativeConstruct() +{ + Super::NativeConstruct(); +} + +void UEventWidget::NativeDestruct() +{ + Super::NativeDestruct(); +} + +void UEventWidget::SetVariables_Implementation(const FEventObject& event) +{ + +} diff --git a/Source/UnrealProject/GUI/EventWidget.h b/Source/UnrealProject/GUI/EventWidget.h new file mode 100644 index 0000000..8166890 --- /dev/null +++ b/Source/UnrealProject/GUI/EventWidget.h @@ -0,0 +1,18 @@ +// Project Lab - NHTV Igad + +#pragma once +#include "Blueprint/UserWidget.h" +#include "EventHUD.h" +#include "EventWidget.generated.h" + +UCLASS() +class UNREALPROJECT_API UEventWidget : public UUserWidget +{ + GENERATED_BODY() +public: + virtual void NativeConstruct() override; + virtual void NativeDestruct() override; + + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "UI") + void SetVariables( const FEventObject& event ); +}; diff --git a/Source/UnrealProject/GUI/HUD/AbilityButton.cpp b/Source/UnrealProject/GUI/HUD/AbilityButton.cpp new file mode 100644 index 0000000..a95c1af --- /dev/null +++ b/Source/UnrealProject/GUI/HUD/AbilityButton.cpp @@ -0,0 +1,61 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "AbilityButton.h" +#include "NetworkCharacter.h" +#include "AbilityInfo.h" +#include "AbilityState.h" + +void UAbilityButton::NativeConstruct() +{ + m_globalEnable = true; + Super::NativeConstruct(); + SetCooldown(1.0f); + SetToggled(false); + SetupButton(nullptr, false); +} + +UAbilityInfo* UAbilityButton::GetAbility() +{ + return m_abilityInfo; +} + +void UAbilityButton::InitButton(class UAbilityInfo* info, int32 index, bool alt) +{ + if (info) + { + SetCooldown(1.0f); + SetToggled(false); + SetupButton(info, false); + SetIndex(index, alt); + SetAbilityAvailable(true); + m_index = index; + m_abilityInfo = info; + } +} + +bool UAbilityButton::IsAssigned() const +{ + return m_abilityInfo != nullptr; +} + +void UAbilityButton::UpdateState(class AAbilityState* state, ANetworkCharacter* character) +{ + SetToggled(state->toggleState); + SetCooldown(state->cooldownRate); + SetAbilityAvailable(character->GetMana() >= state->info->mana); +} + +void UAbilityButton::SetButtonEnabled(bool enabled) +{ + m_enabled = enabled; + if (m_abilityInfo) + { + SetupButton(m_abilityInfo, m_enabled && m_globalEnable); + } +} +void UAbilityButton::SetGlobalEnable(bool enabled) +{ + m_globalEnable = enabled; + SetupButton(m_abilityInfo, m_enabled && m_globalEnable); +} \ No newline at end of file diff --git a/Source/UnrealProject/GUI/HUD/AbilityButton.h b/Source/UnrealProject/GUI/HUD/AbilityButton.h new file mode 100644 index 0000000..ab4a4ca --- /dev/null +++ b/Source/UnrealProject/GUI/HUD/AbilityButton.h @@ -0,0 +1,42 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "Blueprint/UserWidget.h" +#include "AbilityButton.generated.h" + +/** + * + */ +UCLASS() +class UNREALPROJECT_API UAbilityButton : public UUserWidget +{ + GENERATED_BODY() + +public: + void NativeConstruct() override; + + UFUNCTION(BlueprintImplementableEvent, Category = "Button") + void SetupButton(class UAbilityInfo* info, bool enabled); + UFUNCTION(BlueprintImplementableEvent, Category = "Button") + void SetCooldown(float cooldown); + UFUNCTION(BlueprintImplementableEvent, Category = "Button") + void SetToggled(bool toggled); + UFUNCTION(BlueprintImplementableEvent, Category = "Button") + void SetIndex(int32 index, bool alt); + UFUNCTION(BlueprintImplementableEvent, Category = "Button") + void SetAbilityAvailable(bool available); + + class UAbilityInfo* GetAbility(); + + void InitButton(class UAbilityInfo* info, int32 index, bool alt); + bool IsAssigned() const; + void UpdateState(class AAbilityState* state, class ANetworkCharacter* character); + void SetButtonEnabled(bool enabled); + void SetGlobalEnable(bool enabled); +private: + int32 m_index; + bool m_enabled; + bool m_globalEnable; + class UAbilityInfo* m_abilityInfo; +}; diff --git a/Source/UnrealProject/GUI/HUD/ButtonBarSwitcher.cpp b/Source/UnrealProject/GUI/HUD/ButtonBarSwitcher.cpp new file mode 100644 index 0000000..e26f6f7 --- /dev/null +++ b/Source/UnrealProject/GUI/HUD/ButtonBarSwitcher.cpp @@ -0,0 +1,160 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "AbilityButton.h" +#include "ButtonBarSwitcher.h" +#include "BaseSkillObject.h" +#include "AbilityState.h" +#include "NetworkPlayer.h" +#include "IngameSkillTree.h" +#include "AbilityInfo.h" + +void UButtonBarSwitcher::NativeConstruct() +{ + Super::NativeConstruct(); + + m_globalEnable = true; + m_alternateSkillSet = false; + m_FindAndAddButtonBar("ButtonBarMain"); + m_FindAndAddButtonBar("ButtonBarAlt"); +} + +bool UButtonBarSwitcher::AssignSkillButton(const FIngameSkillTreeSkill& skill, bool isAlt) +{ + if (m_mappedButtons.Find(skill.selectedEffect)) + return true; + + int32 bar = isAlt ? 1 : 0; + if (bar >= m_buttonBars.Num()) + return false; + + UAbilityButton* targetButton; + if(!m_buttonBars[bar]->AssignSkillButton(skill, targetButton, isAlt)) + { + GERROR("Too many skills, can't assign " + skill.selectedEffect->GetName() + " to bar " + bar); + return false; + } + + m_mappedButtons.Add(skill.selectedEffect, targetButton); + return true; +} + +void UButtonBarSwitcher::UpdateCooldowns(TArray abilityStates, ANetworkPlayer* player) +{ + for (int32 i = 0; i < abilityStates.Num(); i++) + { + UAbilityButton** button = m_mappedButtons.Find(abilityStates[i]->info); + if (button) + { + (*button)->UpdateState(abilityStates[i], player); + } + } +} + +UButtonBar* UButtonBarSwitcher::m_FindAndAddButtonBar(const FName& name) +{ + UButtonBar* bar = Cast(WidgetTree->FindWidget(name)); + if (bar) + { + m_buttonBars.Add(bar); + } + return bar; +} + +void UButtonBarSwitcher::SetSkillButtonEnabled(const FIngameSkillTreeSkill& skill, bool enabled) +{ + UAbilityButton** button = m_mappedButtons.Find(skill.selectedEffect); + if (button) + { + (*button)->SetButtonEnabled(enabled); + } +} + +void UButtonBarSwitcher::SetGlobalEnable(bool enabled) +{ + for(UButtonBar* bb : m_buttonBars) + { + bb->SetGlobalEnable(enabled); + } +} + +class UAbilityInfo* UButtonBarSwitcher::GetAbilityMapping(int32 index) +{ + if(m_buttonBars.Num() != 2) + { + GERROR("m_buttonBars.Num() != 2"); + return nullptr; + } + UButtonBar* currentBar = m_buttonBars[m_alternateSkillSet ? 1 : 0]; + if(index < 0 || index >= currentBar->buttons.Num()) + return nullptr; + return currentBar->buttons[index]->GetAbility(); +} +class UAbilityInfo* UButtonBarSwitcher::GetAbilityMapping(int32 index, bool isAlt) +{ + if(m_buttonBars.Num() != 2) + { + GERROR("m_buttonBars.Num() != 2"); + return nullptr; + } + UButtonBar* currentBar = m_buttonBars[isAlt ? 1 : 0]; + if(index < 0 || index >= currentBar->buttons.Num()) + return nullptr; + return currentBar->buttons[index]->GetAbility(); +} + +void UButtonBarSwitcher::TransitionToAlternateSkillSet_Implementation() +{ +} +void UButtonBarSwitcher::TransitionToMainSkillSet_Implementation() +{ +} +void UButtonBarSwitcher::ToggleSkillSet(bool useAlternateSkillSet) +{ + if (m_alternateSkillSet != useAlternateSkillSet) + { + if(useAlternateSkillSet) + TransitionToAlternateSkillSet(); + else + TransitionToMainSkillSet(); + m_alternateSkillSet = useAlternateSkillSet; + } +} + +void UButtonBar::NativeConstruct() +{ + TArray widgets; + WidgetTree->GetAllWidgets(widgets); + for (int32 i = 0; i < widgets.Num(); i++) + { + UAbilityButton* button = Cast(widgets[i]); + if (button) + { + buttons.Add(button); + } + } +} + +bool UButtonBar::AssignSkillButton(const FIngameSkillTreeSkill& skill, class UAbilityButton*& buttonOut, bool alt) +{ + for (int32 i = 0; i < buttons.Num(); i++) + { + if (buttons[i]->IsAssigned()) + continue; + buttonOut = buttons[i]; + buttonOut->InitButton(skill.selectedEffect, i, alt); + return true; + } + return false; +} + +void UButtonBar::SetGlobalEnable(bool enabled) +{ + for(UAbilityButton* button : buttons) + { + if(button) + { + button->SetGlobalEnable(enabled); + } + } +} diff --git a/Source/UnrealProject/GUI/HUD/ButtonBarSwitcher.h b/Source/UnrealProject/GUI/HUD/ButtonBarSwitcher.h new file mode 100644 index 0000000..84e9293 --- /dev/null +++ b/Source/UnrealProject/GUI/HUD/ButtonBarSwitcher.h @@ -0,0 +1,58 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "Blueprint/UserWidget.h" +#include "ButtonBarSwitcher.generated.h" + +UCLASS() +class UNREALPROJECT_API UButtonBar : public UUserWidget +{ + GENERATED_BODY() +public: + virtual void NativeConstruct() override; + + bool AssignSkillButton(const struct FIngameSkillTreeSkill& skill, class UAbilityButton*& buttonOut, bool alt); + + void SetGlobalEnable(bool enabled); + + UPROPERTY() + TArray buttons; +}; + +UCLASS() +class UNREALPROJECT_API UButtonBarSwitcher : public UUserWidget +{ + GENERATED_BODY() +public: + virtual void NativeConstruct() override; + + bool AssignSkillButton(const struct FIngameSkillTreeSkill& skill, bool isAlt); + void UpdateCooldowns(TArray abilityStates, class ANetworkPlayer* player); + + void SetSkillButtonEnabled(const struct FIngameSkillTreeSkill& skill, bool enabled); + + // Set the global enabled button state + void SetGlobalEnable(bool enabled); + + class UAbilityInfo* GetAbilityMapping(int32 index); + class UAbilityInfo* GetAbilityMapping(int32 index, bool isAlt); + + UFUNCTION(BlueprintNativeEvent, Category="Animation") + void TransitionToAlternateSkillSet(); + UFUNCTION(BlueprintNativeEvent, Category="Animation") + void TransitionToMainSkillSet(); + + void ToggleSkillSet(bool useAlternateSkillSet); + +private: + UButtonBar* m_FindAndAddButtonBar(const FName& name); + + bool m_globalEnable; + bool m_alternateSkillSet; + + UPROPERTY() + TArray m_buttonBars; + UPROPERTY() + TMap m_mappedButtons; +}; diff --git a/Source/UnrealProject/GUI/HUD/HealthBar.cpp b/Source/UnrealProject/GUI/HUD/HealthBar.cpp new file mode 100644 index 0000000..ea68af0 --- /dev/null +++ b/Source/UnrealProject/GUI/HUD/HealthBar.cpp @@ -0,0 +1,126 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "HealthBar.h" +#include "StatBar.h" + +// 1 = Crown +// 2 = Mini-Boss +// 3 = Dot +// 4 = Camp +// 5 = PlayerIcon +static UTexture2D* barIcons[6]; +UHealthBar::UHealthBar(const FObjectInitializer& init) : Super(init) +{ + barIcons[0] = ConstructorHelpers::FObjectFinder(L"/Game/Assets/Art/UI/Minimap/T_IconCrown").Object; + barIcons[1] = ConstructorHelpers::FObjectFinder(L"/Game/Assets/Art/UI/Minimap/T_IconMini").Object; + barIcons[2] = ConstructorHelpers::FObjectFinder(L"/Game/Assets/Art/UI/Minimap/T_IconDot").Object; + barIcons[3] = ConstructorHelpers::FObjectFinder(L"/Game/Assets/Art/UI/Minimap/T_IconCamp").Object; + barIcons[4] = ConstructorHelpers::FObjectFinder(L"/Game/Assets/Art/UI/Minimap/T_IconPlayer").Object; +} + +void UHealthBar::NativeConstruct() +{ + Super::NativeConstruct(); + m_currentIcon = (EMinimapIcon)-1; + m_HP = Cast(WidgetTree->FindWidget("HP")); + m_mana = Cast(WidgetTree->FindWidget("Mana")); + m_styleSwitcher = Cast(WidgetTree->FindWidget("StyleSwitcher")); + m_HP1 = Cast(WidgetTree->FindWidget("HP1")); + m_icon = Cast(WidgetTree->FindWidget("Icon")); + m_name = Cast(WidgetTree->FindWidget("Name")); + SetName(""); + SetIcon(EMinimapIcon::None); + if(m_mana) + { + m_mana->SetBarColor(EBarColor::Blue); + } + m_style = 0; + m_friendly = false; +} + +void UHealthBar::NativeDestruct() +{ + Super::NativeDestruct(); +} + +void UHealthBar::SetIcon(EMinimapIcon _iconID) +{ + if(!m_icon) + return; + if(_iconID == m_currentIcon) + return; + int32 iconID = FMath::Clamp((int32)_iconID, 0, 5); + if(iconID == 0) + m_icon->SetVisibility(ESlateVisibility::Collapsed); + else + { + m_icon->SetBrushFromTexture(barIcons[iconID - 1]); + m_icon->SetVisibility(ESlateVisibility::SelfHitTestInvisible); + } +} + +void UHealthBar::SetStyle(int32 style) +{ + if(style == m_style) + return; + m_style = FMath::Clamp(style, 0, 1); + m_styleSwitcher->SetActiveWidgetIndex(m_style); +} +void UHealthBar::UpdateHealth(int32 curr, int32 max) +{ + m_updateCount++; + bool showAnimation = m_updateCount > 8; + if(m_style == 0) + { + m_HP->SetStat(curr, max, showAnimation); + } + else + { + m_HP1->SetStat(curr, max, showAnimation); + } +} + +void UHealthBar::UpdateMana(int32 curr, int32 max, float blocked) +{ + if(m_style == 0) + { + m_mana->SetStat(curr, max, true); + } +} + +void UHealthBar::UpdateAlignment(bool friendly) +{ + m_friendly = friendly; + + const FLinearColor colorFriendly = FLinearColor(0.18f, 0.8f, 0.23f); + const FLinearColor colorEnemy = FLinearColor(0.73f, 0.1f, 0.13f); + + if(m_style == 0) + { + m_HP->SetBarColor(friendly ? EBarColor::Green : EBarColor::Red); + } + else + { + m_HP1->SetBarColor(friendly ? EBarColor::Green : EBarColor::Red); + } +} + +void UHealthBar::SetName(FString name) +{ + if(!m_name) + return; + if(name.IsEmpty()) + { + m_name->SetVisibility(ESlateVisibility::Collapsed); + } + else + { + if(name.Len() > 16) + { + name = name.Left(13) + "..."; + } + m_name->SetText(FText::FromString(name)); + m_name->SetVisibility(ESlateVisibility::SelfHitTestInvisible); + } +} diff --git a/Source/UnrealProject/GUI/HUD/HealthBar.h b/Source/UnrealProject/GUI/HUD/HealthBar.h new file mode 100644 index 0000000..c7b8322 --- /dev/null +++ b/Source/UnrealProject/GUI/HUD/HealthBar.h @@ -0,0 +1,53 @@ +// Project Lab - NHTV Igad + +#pragma once +#include "Blueprint/UserWidget.h" +#include "HealthBar.generated.h" + +UENUM() +enum class EMinimapIcon : uint8 +{ + None = 0, + Crown, + MiniBoss, + Dot, + Camp, + Player +}; + +UCLASS() +class UNREALPROJECT_API UHealthBar : public UUserWidget +{ + GENERATED_BODY() + +public: + UHealthBar(const FObjectInitializer& init); + virtual void NativeConstruct() override; + virtual void NativeDestruct() override; + + // Icon ID's + // 0 = nothing + // 1 = Crown + // 2 = Mini-Boss + // 3 = Dot + // 4 = Camp + // 5 = PlayerIcon + void SetIcon(EMinimapIcon iconID); + void SetStyle(int32 style); + void UpdateHealth(int32 curr, int32 max); + void UpdateMana(int32 curr, int32 max, float blocked = 0.0f); + void UpdateAlignment(bool friendly); + void SetName(FString name); + +private: + int32 m_style; + bool m_friendly; + class UWidgetSwitcher* m_styleSwitcher; + class UStatBar* m_HP; + class UStatBar* m_mana; + class UStatBar* m_HP1; + UTextBlock* m_name; + UImage* m_icon; + EMinimapIcon m_currentIcon; + int32 m_updateCount = 0; +}; diff --git a/Source/UnrealProject/GUI/HUD/IngameHUD.cpp b/Source/UnrealProject/GUI/HUD/IngameHUD.cpp new file mode 100644 index 0000000..4549daf --- /dev/null +++ b/Source/UnrealProject/GUI/HUD/IngameHUD.cpp @@ -0,0 +1,347 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "DefaultPlayerController.h" +#include "IngameHUD.h" +#include "AbilityInfo.h" +#include "AbilityState.h" +#include "NetworkPlayer.h" +#include "HealthBar.h" +#include "CreatureSpawn.h" +#include "PlayerSlot.h" +#include "DefaultGameState.h" +#include "DefaultPlayerState.h" +#include "MiniMapWidget.h" +#include "ToolTipWidget.h" +#include "CombatText/CombatTextWidget.h" +#include "SpellInfoDisplay.h" +#include "ButtonBarSwitcher.h" +#include "BaseSkillObject.h" +#include "WidgetLayoutLibrary.h" +#include "NetworkGhost.h" +#include "TouhouBoss.h" +#include "MiniBossCreature.h" +#include "StatBar.h" +#include + +static UClass* healthBarWidgetClass; +static UClass* playerSlotWidgetClass; + +UIngameHUD::UIngameHUD(const FObjectInitializer& init) + :Super(init) +{ + ConstructorHelpers::FClassFinder HealthBarWidgetCF(TEXT("/Game/Assets/GUI/WEEGEE_HealthBar")); + healthBarWidgetClass = HealthBarWidgetCF.Class; + + miniMap = nullptr; +} + +void UIngameHUD::NativeConstruct() +{ + m_myTeam = 0; + m_character = nullptr; + Super::NativeConstruct(); + + m_spellInfoDisplay = Cast(WidgetTree->FindWidget("SpellInfoDisplay")); + miniMap = Cast(WidgetTree->FindWidget("Minimap")); + toolTips = Cast(WidgetTree->FindWidget("ToolTipLayer")); + combatText = Cast(WidgetTree->FindWidget("CombatTextLayer")); + buttonBarSwitcher = Cast(WidgetTree->FindWidget("ButtonBarSwitcher")); + healthBarLayer = Cast(WidgetTree->FindWidget("HealthBarLayer")); + + // Find stat display items + m_playerStatDisplay.hpBar = Cast(WidgetTree->FindWidget("HP")); + m_playerStatDisplay.manaBar = Cast(WidgetTree->FindWidget("Mana")); + m_playerStatDisplay.expBar = Cast(WidgetTree->FindWidget("Exp")); + m_allyStatDisplay.hpBar = Cast(WidgetTree->FindWidget("HP1")); + m_allyStatDisplay.manaBar = Cast(WidgetTree->FindWidget("Mana1")); + m_allyStatDisplay.name = Cast(WidgetTree->FindWidget("Name1")); + m_InitStatDisplay(m_playerStatDisplay); + m_InitStatDisplay(m_allyStatDisplay); + + check(healthBarLayer); +} +void UIngameHUD::NativeDestruct() +{ + Super::NativeDestruct(); +} +void UIngameHUD::NativeTick(const FGeometry& MyGeometry, float InDeltaTime) +{ + Super::NativeTick(MyGeometry, InDeltaTime); + auto p = m_healthBars.CreateIterator(); + while (p) + { + m_UpdateHealthBar(p.Key(), p.Value()); + ++p; + } + + ADefaultPlayerState* ps = Cast(GetOwningPlayer()->PlayerState); + if (IsValid(ps)) + { + m_UpdateStatDisplay(ps, m_playerStatDisplay); + m_UpdateStatDisplay(ps->teamMate, m_allyStatDisplay); + } + + //UWorld* world = GetWorld(); + //check(world); + //ADefaultGameState* state = Cast(world->GetGameState()); +} + +void UIngameHUD::NativeOnAssignCharacter(class ANetworkPossessable* pawn /*= nullptr*/, TArray skillsetPrototype /*= TArray()*/) +{ + m_character = pawn; + + // Cast to ghost or player + ANetworkPlayer* player = Cast(pawn); + ANetworkGhost* ghost = Cast(pawn); + if(ghost) + { + // Grey out buttons in ghost mode + buttonBarSwitcher->SetGlobalEnable(false); + } + else if(player) + { + // Enable buttons back in player mode + buttonBarSwitcher->SetGlobalEnable(true); + + // Set skill prototypes + if(buttonBarSwitcher) + { + for(int32 i = 0; i < skillsetPrototype.Num(); i++) + { + // Don't assign buttons for passive skills + if(skillsetPrototype[i].selectedEffect->passive) + continue; + + switch(skillsetPrototype[i].abilityType) + { + case 0: + buttonBarSwitcher->AssignSkillButton(skillsetPrototype[i], false); + break; + case 1: + buttonBarSwitcher->AssignSkillButton(skillsetPrototype[i], true); + break; + case 2: + if(skillsetPrototype[i].selectedEffect && !skillsetPrototype[i].selectedEffect->passive) + { + GERROR("Non-passive skill added to skill with shape type == passive, " + + skillsetPrototype[i].skillObject->GetName() + " <= " + + skillsetPrototype[i].selectedEffect->GetName()); + } + } + } + } + m_myTeam = player->GetTeam(); + } + + OnAssignCharacter(pawn); +} + +void UIngameHUD::OnLevelUp(TArray updatedSkills) +{ + if (m_spellInfoDisplay && GetPlayer()) + m_spellInfoDisplay->OnLevelUp(GetPlayer(), updatedSkills); + + if (buttonBarSwitcher) + { + for (int32 i = 0; i < updatedSkills.Num(); i++) + { + buttonBarSwitcher->SetSkillButtonEnabled(updatedSkills[i], true); + } + } +} + +void UIngameHUD::UpdateCooldowns(TArray abilityStates, ANetworkPlayer* player) +{ + if (buttonBarSwitcher) + buttonBarSwitcher->UpdateCooldowns(abilityStates, player); +} + +void UIngameHUD::OnCharacterCreated(ANetworkCharacter* character) +{ + if (m_healthBars.Find(character)) + { + // When OnCharacterCreated is called twice + GWWARNING(L"OnCharacterCreated called more thatn once"); + return; + } + UHealthBar* widget = CreateWidget(GetOwningPlayer(), healthBarWidgetClass); + check(widget); + healthBarLayer->AddChild(widget); + m_healthBars.Add(character, widget); + m_UpdateHealthBar(character, widget); +} +void UIngameHUD::OnCharacterDestroyed(ANetworkCharacter* character) +{ + auto it = m_healthBars.Find(character); + if (it) + { + (*it)->RemoveFromViewport(); + m_healthBars.Remove(character); + } + else + { + // When OnCharacterDestroyed is called twice + GWWARNING(L"OnCharacterDestroyed called more than once"); + } +} + +ANetworkPlayer* UIngameHUD::GetTeamMate() const +{ + if (Cast(m_character)) + { + ANetworkPlayer* teamMate = Cast(m_character)->GetTeamMate(); + return teamMate; + } + return nullptr; +} +ANetworkPlayer* UIngameHUD::GetPlayer() const +{ + return Cast(m_character); +} +ANetworkGhost* UIngameHUD::GetGhost() const +{ + return Cast(m_character); +} + +class ADefaultPlayerState* UIngameHUD::GetPlayerState() const +{ + if (!m_character) + return nullptr; + return Cast(m_character->PlayerState); +} + +void UIngameHUD::m_UpdateHealthBar(class ANetworkCharacter* character, class UHealthBar* bar) +{ + APlayerController* pc = GetOwningPlayer(); + if (!IsValid(pc)) return; + + FVector screenPos; + const float height = character->GetCapsuleComponent()->GetScaledCapsuleHalfHeight(); + const FVector position = character->GetActorLocation(); + const FVector worldLocation = FVector(position.X, position.Y, position.Z + height); + + // Check if is approximately in camera view + const FVector forward = pc->PlayerCameraManager->GetCameraRotation().RotateVector(FVector(1.0f, 0.0f, 0.0f)); + const FVector dir = (worldLocation - pc->PlayerCameraManager->GetCameraLocation()).GetSafeNormal(); + float dot = FVector::DotProduct(forward, dir); + if (dot < 0.01f) + { + bar->SetVisibility(ESlateVisibility::Hidden); + return; + } + bar->SetVisibility(ESlateVisibility::Visible); + + pc->ProjectWorldLocationToScreenWithDistance(FVector(position.X, position.Y, position.Z + height), screenPos); + const float viewportScale = UWidgetLayoutLibrary::GetViewportScale(healthBarLayer); + + uint32 otherTeam = character->GetTeam(); + + // Check witch bar style to use + bool useExtendedBarStyle = character->playerName.Len() > 0; + if(useExtendedBarStyle) + { + bar->SetStyle(0); + bar->UpdateMana(character->GetMana(), character->GetMaxMana(), character->GetBlockedMana()); + bar->SetName(character->playerName); + } + else + { + bar->SetStyle(1); + bar->SetName(""); + } + bar->UpdateAlignment(otherTeam == m_myTeam); + bar->UpdateHealth(character->GetHealth(), character->GetMaxHealth()); + + // Set the icon next to the bar + EMinimapIcon icon = EMinimapIcon::None; + if(character->IsA()) + { + icon = EMinimapIcon::Crown; + } + else if(character->IsA()) + { + icon = EMinimapIcon::MiniBoss; + } + bar->SetIcon(icon); + + // Set the on-screen position + UCanvasPanelSlot* asSlot = Cast(bar->Slot); + if (IsValid(asSlot)) + { + asSlot->SetAutoSize(true); + asSlot->SetAlignment(FVector2D(0.5f, 1)); + const FVector2D setPos = FVector2D(screenPos.X, screenPos.Y) / viewportScale; + asSlot->SetPosition(FVector2D(FPlatformMath::RoundToFloat(setPos.X), FPlatformMath::RoundToFloat(setPos.Y))); + } +} + +void UIngameHUD::m_UpdateStatDisplay(class ADefaultPlayerState* state, const FStatDisplay& sd) +{ + ANetworkPlayer* player = state ? state->character : nullptr; + if(sd.hpBar) + { + if(!player) + sd.hpBar->SetStat(0.0f, true); + else + { + sd.hpBar->SetStat(player->GetHealth(), player->GetMaxHealth(), true); + } + } + if(sd.manaBar) + { + if(!player) + { + sd.manaBar->SetBlocked(0.0f); + sd.manaBar->SetStat(0, 1, true); + } + else + { + sd.manaBar->SetStat(player->GetMana(), player->GetMaxMana(), true); + sd.manaBar->SetBlocked((float)player->GetBlockedMana() / (float)player->GetMaxMana()); + } + } + + if(sd.expBar) + { + if(state) + { + if(state->GetLevel() == state->GetMaxLevel()) + sd.expBar->Update(1.0f, state->GetMaxLevel(), true); + else + sd.expBar->Update((float)state->GetExperience() / (float)state->GetExperienceToLevel(), state->GetLevel(), false); + } + else + { + sd.expBar->Update(0.0f, 0, false); + } + } + if(sd.name) + { + if(state) + { + state->UpdatePersona(); + sd.name->SetText(FText::FromString(state->nickname)); + } + else + { + sd.name->SetText(FText()); + } + } + if(sd.avatar && state) + { + sd.avatar->SetBrushFromTexture(state->avatar); + } +} + +void UIngameHUD::m_InitStatDisplay(const FStatDisplay& sd) +{ + if(sd.hpBar) + { + sd.hpBar->SetBarColor(EBarColor::GreenGradient); + } + if(sd.manaBar) + { + sd.manaBar->SetBarColor(EBarColor::BlueGradient); + } +} diff --git a/Source/UnrealProject/GUI/HUD/IngameHUD.h b/Source/UnrealProject/GUI/HUD/IngameHUD.h new file mode 100644 index 0000000..a305f9a --- /dev/null +++ b/Source/UnrealProject/GUI/HUD/IngameHUD.h @@ -0,0 +1,93 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "Blueprint/UserWidget.h" +#include "IngameSkillTree.h" +#include +#include +#include "IngameHUD.generated.h" + +UCLASS() +class UExperienceBar : public UUserWidget +{ + GENERATED_BODY() +public: + UFUNCTION(BlueprintImplementableEvent) + void Update(float rate, int32 level, bool max); +}; + +struct FStatDisplay +{ + class UStatBar* hpBar; + class UStatBar* manaBar; + class UExperienceBar* expBar; + class UImage* avatar; + class UTextBlock* name; +}; + +UCLASS() +class UNREALPROJECT_API UIngameHUD : public UUserWidget +{ + GENERATED_BODY() + +public: + UIngameHUD(const FObjectInitializer& init); + virtual void NativeConstruct() override; + virtual void NativeDestruct() override; + virtual void NativeTick(const FGeometry& MyGeometry, float InDeltaTime) override; + + // Sets the player owned character + void NativeOnAssignCharacter(class ANetworkPossessable* pawn = nullptr, TArray skillsetPrototype = TArray()); + + UFUNCTION(BlueprintImplementableEvent, Category = "HUD") + void OnAssignCharacter(ANetworkPossessable* pawn); + + // Called when the character levels up + void OnLevelUp(TArray updatedSkills); + + // Updates the displayed cooldowns in the UI with the provided ability states + void UpdateCooldowns(TArray abilityStates, class ANetworkPlayer* player); + + // Called when a character spawns + // this makes it display a health-bar in the HUD + void OnCharacterCreated(class ANetworkCharacter* character); + // Called when a character despawns(destroyed, killed) + // this removes all the health-bars, etc. for this character + void OnCharacterDestroyed(class ANetworkCharacter* character); + + UFUNCTION(BlueprintCallable, Category = "UI") + class ANetworkPlayer* GetTeamMate() const; + UFUNCTION(BlueprintCallable, Category = "UI") + class ANetworkPlayer* GetPlayer() const; + UFUNCTION(BlueprintCallable, Category = "UI") + class ANetworkGhost* GetGhost() const; + UFUNCTION(BlueprintCallable, Category = "UI") + class ADefaultPlayerState* GetPlayerState() const; + + class UMiniMapWidget* miniMap; + class UToolTipWidget* toolTips; + class UCombatTextWidget* combatText; + class UButtonBarSwitcher* buttonBarSwitcher; + class UCanvasPanel* healthBarLayer; + +private: + // This updates an on screen health bar for a given network character + void m_UpdateHealthBar(class ANetworkCharacter* character, class UHealthBar* bar); + void m_UpdateStatDisplay(class ADefaultPlayerState* player, const FStatDisplay& statDisplay); + void m_InitStatDisplay(const FStatDisplay& statDisplay); + + FStatDisplay m_playerStatDisplay; + FStatDisplay m_allyStatDisplay; + + // Maps every character in the game to a health bar widget on-screen + UPROPERTY() + TMap m_healthBars; + + UPROPERTY() + class ANetworkPossessable* m_character; + UPROPERTY() + class USpellInfoDisplay* m_spellInfoDisplay; + + uint32 m_myTeam; +}; diff --git a/Source/UnrealProject/GUI/HUD/KOTHHUD.cpp b/Source/UnrealProject/GUI/HUD/KOTHHUD.cpp new file mode 100644 index 0000000..d42eec7 --- /dev/null +++ b/Source/UnrealProject/GUI/HUD/KOTHHUD.cpp @@ -0,0 +1,13 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "KOTHHUD.h" + + +void UKOTHHUD::NativeConstruct() +{ + Super::NativeConstruct(); +} +void UKOTHHUD::Update(TArray teamStates) +{ +} diff --git a/Source/UnrealProject/GUI/HUD/KOTHHUD.h b/Source/UnrealProject/GUI/HUD/KOTHHUD.h new file mode 100644 index 0000000..71e5eef --- /dev/null +++ b/Source/UnrealProject/GUI/HUD/KOTHHUD.h @@ -0,0 +1,16 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "Blueprint/UserWidget.h" +#include "KOTHTeamState.h" +#include "KOTHHUD.generated.h" + +UCLASS() +class UNREALPROJECT_API UKOTHHUD : public UUserWidget +{ + GENERATED_BODY() +public: + void NativeConstruct() override; + void Update(TArray teamStates); +}; diff --git a/Source/UnrealProject/GUI/HUD/KeyDisplay.cpp b/Source/UnrealProject/GUI/HUD/KeyDisplay.cpp new file mode 100644 index 0000000..efa4c95 --- /dev/null +++ b/Source/UnrealProject/GUI/HUD/KeyDisplay.cpp @@ -0,0 +1,14 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "KeyDisplay.h" + +void UKeyDisplay::NativeConstruct() +{ + Super::NativeConstruct(); +} + +void UKeyDisplay::NativeDestruct() +{ + Super::NativeDestruct(); +} \ No newline at end of file diff --git a/Source/UnrealProject/GUI/HUD/KeyDisplay.h b/Source/UnrealProject/GUI/HUD/KeyDisplay.h new file mode 100644 index 0000000..c9fe873 --- /dev/null +++ b/Source/UnrealProject/GUI/HUD/KeyDisplay.h @@ -0,0 +1,16 @@ +// Project Lab - NHTV Igad + +#pragma once +#include "Blueprint/UserWidget.h" +#include "KeyDisplay.generated.h" + + +UCLASS() +class UNREALPROJECT_API UKeyDisplay : public UUserWidget +{ + GENERATED_BODY() + +public: + virtual void NativeConstruct() override; + virtual void NativeDestruct() override; +}; diff --git a/Source/UnrealProject/GUI/HUD/StatBar.cpp b/Source/UnrealProject/GUI/HUD/StatBar.cpp new file mode 100644 index 0000000..fbb769f --- /dev/null +++ b/Source/UnrealProject/GUI/HUD/StatBar.cpp @@ -0,0 +1,192 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "StatBar.h" + +static UTexture2D* barTextures[6]; +static FLinearColor barColors[4]; +static UMaterial* textureBarMaterialClass; + +UStatBar::UStatBar(const FObjectInitializer& init) : Super(init) +{ + textureBarMaterialClass = ConstructorHelpers::FObjectFinder(L"/Game/Assets/Art/UI/M_HealthBar2.M_HealthBar2").Object; + + barColors[0] = FLinearColor::Green * 0.5f; + barColors[1] = FLinearColor::Red * 0.6f; + barColors[2] = FLinearColor::Blue * 0.8f; + barColors[3] = FLinearColor(1.0f, 0.0f, 1.0f, 1.0f); + barTextures[0] = ConstructorHelpers::FObjectFinder(L"/Game/Assets/Art/UI/HealthBar/UI_MainStatusBarHealth").Object; + barTextures[1] = ConstructorHelpers::FObjectFinder(L"/Game/Assets/Art/UI/HealthBar/UI_MainStatusBarMana").Object; + + m_lastValue = 1.0f; + m_currentValue = 1.0f; + damageAnimationDuration = 0.3f; + m_damageAnimation = 0.0f; + showText = false; +} + +void UStatBar::NativeConstruct() +{ + Super::NativeConstruct(); + m_base = Cast(WidgetTree->FindWidget("Base")); + m_blocked = Cast(WidgetTree->FindWidget("Blocked")); + m_damage = Cast(WidgetTree->FindWidget("Damage")); + m_text = Cast(WidgetTree->FindWidget("BarText")); + if(m_damage) + { + m_damage->SetVisibility(ESlateVisibility::Hidden); + UMaterialInstanceDynamic* damageMat = m_damage->GetDynamicMaterial(); + damageMat->SetScalarParameterValue("Brightness", 4.0f); + } + if(m_blocked) + m_blocked->SetVisibility(ESlateVisibility::Hidden); +} + +#pragma optimize("", off) +void UStatBar::NativeTick(const FGeometry& MyGeometry, float InDeltaTime) +{ + Super::NativeTick(MyGeometry, InDeltaTime); + + if(m_damageAnimation > 0.0f && m_damage) + { + float r = m_damageAnimation / damageAnimationDuration; + float damageSize = (m_currentValue - m_lastValue); + + UMaterialInstanceDynamic* matDamage = m_damage->GetDynamicMaterial(); + if(damageSize < 0.0f) + { + damageSize *= (1.0f - r); + matDamage->SetScalarParameterValue("Start", m_currentValue); + matDamage->SetScalarParameterValue("End", m_lastValue + damageSize); + } + else + { + damageSize *= (1.0f-r); + matDamage->SetScalarParameterValue("Start", m_lastValue + damageSize); + matDamage->SetScalarParameterValue("End", m_currentValue); + } + + m_damageAnimation -= InDeltaTime; + if(m_damageAnimation <= 0.0f) + { + m_damageAnimation = 0.0f; + m_damage->SetVisibility(ESlateVisibility::Hidden); + } + } +} + +#pragma optimize("", off) +void UStatBar::SetStat(float newStat, bool applyAnimation) +{ + if(!m_base) + return; + if(newStat == m_currentValue) + return; + + float delta = newStat - m_currentValue; + if(delta != 0 && m_damageAnimation > 0.0f) + { + // Append animation + float r = m_damageAnimation / damageAnimationDuration; + float damageSize = (m_currentValue - m_lastValue); + if(damageSize < 0.0f) + damageSize *= (1.0f - r); + else + damageSize *= (1.0f - r); + delta += damageSize; + m_lastValue = m_lastValue + damageSize; + } + else + { + m_lastValue = m_currentValue; + } + m_currentValue = newStat; + + // Apply animation + if(delta != 0.0f && applyAnimation && m_damage) + { + const FLinearColor deltaColor = FLinearColor(1.0f, 0.0f, 0.0f, 1.0f); + m_damageAnimation = damageAnimationDuration; + + UMaterialInstanceDynamic* matDamage = m_base->GetDynamicMaterial(); + if(delta < 0.0f) + { + matDamage->SetScalarParameterValue("Start", newStat); + matDamage->SetScalarParameterValue("End", m_lastValue); + } + else + { + matDamage->SetScalarParameterValue("Start", m_lastValue); + matDamage->SetScalarParameterValue("End", newStat); + } + m_damage->SetVisibility(ESlateVisibility::SelfHitTestInvisible); + } + + UMaterialInstanceDynamic* matBase = m_base->GetDynamicMaterial(); + matBase->SetScalarParameterValue("Start", 0.0f); + matBase->SetScalarParameterValue("End", newStat); +} + +void UStatBar::SetStat(int32 newStat, int32 max, bool applyAnimation) +{ + if(showText && m_text) + { + FText t = FText::FromString(FString::Printf(L"%d / %d", newStat, max)); + m_text->SetText(t); + m_text->SetVisibility(ESlateVisibility::SelfHitTestInvisible); + } + else + { + m_text->SetVisibility(ESlateVisibility::Hidden); + } + + float r = (float)newStat / (float)max; + SetStat(r, applyAnimation); +} + +void UStatBar::SetBlocked(float newBlocked) +{ + if(!m_blocked) + return; + UMaterialInstanceDynamic* mat = m_blocked->GetDynamicMaterial(); + if(newBlocked > 0.0f) + { + mat->SetScalarParameterValue("Start", 1.0f - newBlocked); + mat->SetScalarParameterValue("End", 1.0f); + m_blocked->SetVisibility(ESlateVisibility::SelfHitTestInvisible); + } + else + { + m_blocked->SetVisibility(ESlateVisibility::Hidden); + } +} + +void UStatBar::SetBarColor(EBarColor _colorID) +{ + int32 colorID = (int32)_colorID; + if(!m_base) + return; + + if((colorID & 0x80) != 0) + { + colorID = colorID & 0x7F; + colorID = FMath::Clamp(colorID, 0, (int32)(sizeof(barTextures) / sizeof(UTexture2D*))); + + m_base->SetBrushFromMaterial(textureBarMaterialClass); + m_blocked->SetBrushFromMaterial(textureBarMaterialClass); + m_damage->SetBrushFromMaterial(textureBarMaterialClass); + UMaterialInstanceDynamic* baseMat = m_base->GetDynamicMaterial(); + baseMat->SetTextureParameterValue("Texture", barTextures[colorID]); + baseMat = m_damage->GetDynamicMaterial(); + baseMat->SetTextureParameterValue("Texture", barTextures[colorID]); + baseMat = m_blocked->GetDynamicMaterial(); + baseMat->SetTextureParameterValue("Texture", barTextures[colorID]); + } + else + { + colorID = FMath::Clamp(colorID, 0, (int32)(sizeof(barColors) / sizeof(FLinearColor))); + + UMaterialInstanceDynamic* baseMat = m_base->GetDynamicMaterial(); + baseMat->SetVectorParameterValue("Color", barColors[colorID]); + } +} diff --git a/Source/UnrealProject/GUI/HUD/StatBar.h b/Source/UnrealProject/GUI/HUD/StatBar.h new file mode 100644 index 0000000..d520f3e --- /dev/null +++ b/Source/UnrealProject/GUI/HUD/StatBar.h @@ -0,0 +1,49 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "Blueprint/UserWidget.h" +#include "StatBar.generated.h" + +enum class EBarColor +{ + Green = 0, + Red, + Blue, + Purple, + GreenGradient = 0x80, + BlueGradient +}; + +/** + * + */ +UCLASS() +class UNREALPROJECT_API UStatBar : public UUserWidget +{ + GENERATED_BODY() + +public: + UStatBar(const FObjectInitializer& init); + void NativeConstruct() override; + void NativeTick(const FGeometry& MyGeometry, float InDeltaTime) override; + + void SetStat(int32 newStat, int32 max, bool applyAnimation); + void SetStat(float newStat, bool applyAnimation); + void SetBlocked(float newBlocked); + void SetBarColor(EBarColor colorID); + + UPROPERTY(EditDefaultsOnly, Category = "Stat Bar") + float damageAnimationDuration; + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Stat Bar") + bool showText; + +private: + float m_damageAnimation; + float m_lastValue; + float m_currentValue; + UImage* m_base; + UImage* m_blocked; + UImage* m_damage; + UTextBlock* m_text; +}; diff --git a/Source/UnrealProject/GUI/HUD/Timer.cpp b/Source/UnrealProject/GUI/HUD/Timer.cpp new file mode 100644 index 0000000..de5abe6 --- /dev/null +++ b/Source/UnrealProject/GUI/HUD/Timer.cpp @@ -0,0 +1,49 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "Timer.h" +#include "Kismet/KismetStringLibrary.h" + +void UTimer::NativeConstruct() +{ + m_duration = 0.0f; + m_text = Cast(WidgetTree->FindWidget("Text")); + m_timerText = Cast(WidgetTree->FindWidget("Time")); + m_clock = Cast(WidgetTree->FindWidget("Klokje")); + if(!m_text) + GWARNING("No \"Text\" text widget found in " + GetName()); + if(!m_text) + GWARNING("No \"Time\" text widget found in " + GetName()); + if(!m_clock) + GWARNING("No \"Klokje\" image widget found in " + GetName()); + Super::NativeConstruct(); +} + +void UTimer::NativeTick(const FGeometry& MyGeometry, float InDeltaTime) +{ + m_time = FMath::Clamp(m_time - InDeltaTime, 0.0f, m_duration); + if(m_duration == 0.0f) + OnSetTimer(0.0f); + else + OnSetTimer(m_time / m_duration); + + if(m_timerText) + { + FString msg = UKismetStringLibrary::TimeSecondsToString(m_time); + m_timerText->SetText(FText::FromString(msg)); + } + + Super::NativeTick(MyGeometry, InDeltaTime); +} + +void UTimer::SetTimer(float duration, bool reset) +{ + m_duration = duration; + if(reset) + m_time = duration; +} +void UTimer::SetText(const FString& text) +{ + if(m_text) + m_text->SetText(FText::FromString(text)); +} diff --git a/Source/UnrealProject/GUI/HUD/Timer.h b/Source/UnrealProject/GUI/HUD/Timer.h new file mode 100644 index 0000000..ebd13af --- /dev/null +++ b/Source/UnrealProject/GUI/HUD/Timer.h @@ -0,0 +1,44 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "Blueprint/UserWidget.h" +#include "Timer.generated.h" + +/** + * + */ +UCLASS() +class UNREALPROJECT_API UTimer : public UUserWidget +{ + GENERATED_BODY() + +public: + // Calbacks handling timer changes + UFUNCTION(BlueprintImplementableEvent, Category = "HUDTimer") + void OnSetTimer(float rate); + UFUNCTION(BlueprintImplementableEvent, Category = "HUDTimer") + void OnHide(); // Play animation + UFUNCTION(BlueprintImplementableEvent, Category = "HUDTimer") + void OnShow(); // Play animation + + void NativeConstruct(); + void NativeTick(const FGeometry& MyGeometry, float InDeltaTime) override; + + UFUNCTION(BlueprintCallable, Category = "HUDTimer") + void SetTimer(float duration, bool reset = true); + UFUNCTION(BlueprintCallable, Category = "HUDTimer") + void SetText(const FString& text); + UFUNCTION(BlueprintCallable, Category = "HUDTimer") + float GetTime() const + { + return m_time; + } + +private: + float m_time; + float m_duration; + UImage* m_clock; + UTextBlock* m_text; + UTextBlock* m_timerText; +}; diff --git a/Source/UnrealProject/GUI/Lobby/LobbyCharacterSelect.cpp b/Source/UnrealProject/GUI/Lobby/LobbyCharacterSelect.cpp new file mode 100644 index 0000000..23e0795 --- /dev/null +++ b/Source/UnrealProject/GUI/Lobby/LobbyCharacterSelect.cpp @@ -0,0 +1,285 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "DefaultGameInstance.h" +#include "MenuController.h" +#include "LobbySpawn.h" +#include "GameStateBase.h" +#include "PlayerStateBase.h" +#include "PlayerControllerBase.h" +#include "CharacterBase.h" +#include "LobbyCharacterSelect.h" +#include "MenuGameMode.h" + +void ULobbyCharacterSelect::m_OnPlayerJoined(APlayerController* pc) +{ + for(auto it = m_visualCharacters.CreateIterator(); it; ++it) + { + (it.Value()).character->SendInitialAppearance(pc); + } +} + +void ULobbyCharacterSelect::NativeConstruct() +{ + Super::NativeConstruct(); + + // Add GameMode callback + AMenuGameMode* gm = Cast(GetWorld()->GetAuthGameMode()); + if(gm) + { + m_onPlayerJoinedDH = gm->onPlayerJoined.AddUObject(this, &ULobbyCharacterSelect::m_OnPlayerJoined); + } + + UWorld* const world = GetWorld(); + for (TActorIterator iter(world); iter; ++iter) + { + TArray* find = m_spawnPoints.Find(iter->assignedTeam); + if (!find) + { + TArray arr; + arr.Add(*iter); + m_spawnPoints.Add(iter->assignedTeam, arr); + } + else + find->Add(*iter); + } + + m_requireUpdate = false; + m_updateInterval = 0; + + AGameStateBase* gameState = Cast(world->GetGameState()); + check(gameState); + gameState->OnPlayerStateChange.AddDynamic(this, &ULobbyCharacterSelect::UpdateVisualCharacters); +} +void ULobbyCharacterSelect::NativeDestruct() +{ + Super::NativeDestruct(); + + // Remove GameMode Callback + AMenuGameMode* gm = Cast(GetWorld()->GetAuthGameMode()); + if(gm) + { + gm->onPlayerJoined.Remove(m_onPlayerJoinedDH); + } + + UWorld* const world = GetWorld(); + check(world); + AGameStateBase* gameState = Cast(world->GetGameState()); + check(gameState); + gameState->OnPlayerStateChange.RemoveAll(this); +} +void ULobbyCharacterSelect::NativeTick(const FGeometry& MyGeometry, float InDeltaTime) +{ + // The updating is called once per frame + // This is to prevent double calls when two players leave in one cycle + + // Ensure that only the server runs this + if (!GetOwningPlayer()->HasAuthority()) + return; + + // No need to update this frame + if (!m_requireUpdate) + return; + + // Await the interval + m_updateInterval -= InDeltaTime; + if (m_updateInterval > 0) + return; + + m_requireUpdate = false; + m_updateInterval = 0.3f; + + UWorld* world = GetWorld(); + check(world); + AGameStateBase* gameState = Cast(world->GetGameState()); + check(gameState); + TArray teamSizes = gameState->GetTeamSizes(); + TArray> playersPerTeam = gameState->GetPlayersByTeam(); + + // Check if players have left + TMap outdatedCharacters = m_visualCharacters; + TArray players = gameState->GetPlayers(); + for (int32 i = 0; i < players.Num(); i++) + { + VisualSlot* find = outdatedCharacters.Find(players[i]); + if (find) + outdatedCharacters.Remove(players[i]); + } + for (auto iter = outdatedCharacters.CreateIterator(); iter; ++iter) + { + if (IsValid(iter->Value.character)) + iter->Value.character->Destroy(); + m_visualCharacters.Remove(iter->Key); + } + + // Check if players switched team + TMap newCharacters; + TMap newMap; + for (int32 i = 1; i < playersPerTeam.Num(); i++) + { + auto& set = playersPerTeam[i]; + for (int32 j = 0; j < set.Num(); j++) + { + APlayerStateBase* playerState = set[j]; + + VisualSlot* find = m_visualCharacters.Find(playerState); + const int32 team = i - 1; + if (!find || find->team != team || find->slot != j) + { + // Fetch the spawn coordinates if the spawnpoint exists + FVector position; + FRotator rotation; + if (team < m_spawnPoints.Num() && j < m_spawnPoints[team].Num()) + { + position = m_spawnPoints[team][j]->GetActorLocation(); + rotation = m_spawnPoints[team][j]->GetActorRotation(); + } + + // Check if we need to create a new character, else we just repurpose the previous character (like when the player switched slot) + ACharacterBase* character; + if (!find || !IsValid(find->character)) + { + FActorSpawnParameters params; + params.bNoFail = true; + params.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; + character = GetWorld()->SpawnActor(characterBlueprint, FTransform::Identity, params); + if (!IsValid(character)) + { + JERROR("Failed to spawn Actor"); + return; + } + } + else + { + find->character->ClearEquipment(); + character = find->character; + } + + // Set and add + character->SetActorLocation(position); + character->SetActorRotation(rotation.Quaternion()); + newMap.Add(playerState, VisualSlot(team, j, character)); + newCharacters.Add(playerState, character); + } + else + newMap.Add(playerState, *find); + } + } + m_visualCharacters = newMap; + + // Apply the customizations + TMap controllers = gameState->GetPlayersByController(); + for (auto iter = controllers.CreateIterator(); iter; ++iter) + { + ACharacterBase** character = newCharacters.Find(iter->Value); + if (!character || !IsValid(*character)) continue; + (*character)->SetCustomizations(iter->Key->setupState.customizations); + (*character)->EquipSkillsFromSkillTreeState(iter->Key->setupState.skills); + + // Set class specific equipment + if(characterClassProperties) + { + FCharacterClassProperty properties = characterClassProperties->GetCharacterClass(iter->Key->setupState.characterClass); + (*character)->EquipItems(properties.classItems); + } + } +} + +void ULobbyCharacterSelect::Init() +{ + UWorld* const world = GetWorld(); + check(world); + UDefaultGameInstance* const instance = Cast(world->GetGameInstance()); + check(instance); + UCharacterSettings* const settings = instance->GetCharacterSettings(); + check(settings); + + // Fetch the characters from the save + check(settings->characterSaves.Num() > 0); + m_playerCharacterSaves = settings->GetValidCharacters(); + + m_selection = 0; + + m_ApplyCharacter(); +} + +void ULobbyCharacterSelect::OnNextCharacter() +{ + if (m_selection == m_playerCharacterSaves.Num() - 1) + return; + m_selection++; + + m_ApplyCharacter(); +} +void ULobbyCharacterSelect::OnPreviousCharacter() +{ + if (m_selection == 0) + return; + m_selection--; + + m_ApplyCharacter(); +} + +void ULobbyCharacterSelect::UpdateCharacter_Implementation(FCharacterSave save) +{ + // Blueprint implementation +} + +void ULobbyCharacterSelect::m_ApplyCharacter() +{ + UpdateCharacter(m_playerCharacterSaves[m_selection]); + + // Apply the selected character to the player controller + UWorld* const world = GetWorld(); + check(world); + AMenuController* const controller = Cast(world->GetFirstPlayerController()); + check(controller); + + FPlayerSetupState state; + state.skills = m_playerCharacterSaves[m_selection].skillTreeState; + state.customizations = m_playerCharacterSaves[m_selection].characterCustomization; + state.characterClass = m_playerCharacterSaves[m_selection].characterClass; + controller->SetSetupState(state); +} + +void ULobbyCharacterSelect::UpdateVisualCharacters(APlayerControllerBase* playerController) +{ + if (!IsValid(characterBlueprint)) + { + JERROR("Character blueprint not assigned"); + return; + } + if (!IsValid(playerController) && playerController != nullptr) + return; + + // Ensure that only the server runs this + if (!GetOwningPlayer()->HasAuthority()) + return; + + if (playerController != nullptr) + { + // Just update the visuals on the specific player + APlayerStateBase* playerState = Cast(playerController->PlayerState); + if (IsValid(playerState)) + { + VisualSlot* slot = m_visualCharacters.Find(playerState); + if (slot && IsValid(slot->character)) + { + slot->character->SetCustomizations(playerController->setupState.customizations); + slot->character->ClearEquipment(); + slot->character->EquipSkillsFromSkillTreeState(playerController->setupState.skills); + + // Set class specific equipment + if(characterClassProperties) + { + FCharacterClassProperty properties = characterClassProperties->GetCharacterClass(playerController->setupState.characterClass); + slot->character->EquipItems(properties.classItems); + } + } + else // Failed to find the character, refresh all + m_requireUpdate = true; + } + } + else + m_requireUpdate = true; +} \ No newline at end of file diff --git a/Source/UnrealProject/GUI/Lobby/LobbyCharacterSelect.h b/Source/UnrealProject/GUI/Lobby/LobbyCharacterSelect.h new file mode 100644 index 0000000..8ec57e2 --- /dev/null +++ b/Source/UnrealProject/GUI/Lobby/LobbyCharacterSelect.h @@ -0,0 +1,65 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "GUI/Menu/SubMenu.h" +#include "CharacterSettings.h" +#include "LobbyCharacterSelect.generated.h" + +/** + * + */ +UCLASS() +class UNREALPROJECT_API ULobbyCharacterSelect : public USubMenu +{ + GENERATED_BODY() + +public: + virtual void NativeConstruct() override; + virtual void NativeDestruct() override; + virtual void NativeTick(const FGeometry& MyGeometry, float InDeltaTime) override; + + UFUNCTION(BlueprintCallable, Category = "Lobby") + void Init(); + + // On next/previous player character + UFUNCTION(BlueprintCallable, Category = "Lobby") + void OnNextCharacter(); + UFUNCTION(BlueprintCallable, Category = "Lobby") + void OnPreviousCharacter(); + + // Called to set the name of the selected character + UFUNCTION(BlueprintNativeEvent, Category = "Lobby") + void UpdateCharacter(FCharacterSave save); + + // Called to update all visual characters in the lobby (specify a controller if one player only requires a visual update, or nullptr if all characters need to be updated) + UFUNCTION(BlueprintCallable, Category = "Lobby") + void UpdateVisualCharacters(class APlayerControllerBase* playerController); + + UPROPERTY(EditAnywhere, Category = "Lobby") + TSubclassOf characterBlueprint; + + UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Category = "Character Sub Menu") + class UCharacterClassPropertySet* characterClassProperties; + +private: + void m_OnPlayerJoined(APlayerController* pc); + FDelegateHandle m_onPlayerJoinedDH; + + TArray m_playerCharacterSaves; + int32 m_selection; + + TMap> m_spawnPoints; + struct VisualSlot + { + VisualSlot(int32 team, int32 slot, ACharacterBase* character) : team(team), slot(slot), character(character) {} + int32 team; + int32 slot; + ACharacterBase* character; + }; + TMap m_visualCharacters; + + void m_ApplyCharacter(); + bool m_requireUpdate; + float m_updateInterval; +}; diff --git a/Source/UnrealProject/GUI/Lobby/LobbyMenu.cpp b/Source/UnrealProject/GUI/Lobby/LobbyMenu.cpp new file mode 100644 index 0000000..8075ae2 --- /dev/null +++ b/Source/UnrealProject/GUI/Lobby/LobbyMenu.cpp @@ -0,0 +1,204 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "LobbyMenu.h" +#include "LobbySlot.h" +#include "DefaultPlayer.h" +#include "PlayerStateBase.h" +#include "MenuController.h" +#include "DefaultGameInstance.h" +#include "MenuGameMode.h" +#include "GameStateBase.h" +#include "SubMenu.h" +#include "SessionManager.h" +#include "Engine.h" +#include "ScreenOverlay.h" +#include "CharacterSettings.h" + +ULobbyMenu::ULobbyMenu(const FObjectInitializer& init) + : Super(init) +{ +} + +void ULobbyMenu::NativeConstruct() +{ + m_teamContainer = Cast(WidgetTree->FindWidget("Container")); + Super::NativeConstruct(); +} +void ULobbyMenu::NativeDestruct() +{ + Super::NativeDestruct(); +} +void ULobbyMenu::NativeTick(const FGeometry& MyGeometry, float InDeltaTime) +{ + Super::NativeTick(MyGeometry, InDeltaTime); + + if(!m_myState) + { + // Try to aquire my local player state, this is in Tick because player states are not guaranteed during BeginPlay + UWorld* const world = GetWorld(); + check(world); + APlayerControllerBase* player = Cast(world->GetGameInstance()->GetFirstLocalPlayerController()); + check(player); + m_myState = Cast(player->PlayerState); + } + + UWorld* world = GetWorld(); + check(world); + AGameStateBase* gameState = Cast(world->GetGameState()); + check(gameState); + TArray teamSizes = gameState->GetTeamSizes(); + TArray> playersPerTeam = gameState->GetPlayersByTeam(); + + // Update team slots with their respective player arrays + for (int32 i = 0; i < (int32)m_numTeams; i++) + { + if((i + 1) < playersPerTeam.Num()) + { + m_lobbySlots[i]->UpdatePlayers(playersPerTeam[i+1]); + } + else + m_lobbySlots[i]->UpdatePlayers(TArray()); + + if (i > teamSizes.Num()) + { + m_lobbySlots[i]->SetState(false); + } + else + { + m_lobbySlots[i]->SetState(true); + } + } +} +void ULobbyMenu::Init() +{ + // Generate the selectable builds + UWorld* world = GetWorld(); + check(world); + UDefaultGameInstance* instance = Cast(world->GetGameInstance()); + check(instance); + UCharacterSettings* settings = instance->GetCharacterSettings(); + check(settings); + m_characters = settings->characterSaves; + + + // Remove existing lobby slots + for(ULobbySlot* s : m_lobbySlots) + { + s->RemoveFromParent(); + } + m_lobbySlots.SetNum(0); + + if(!m_teamContainer) + { + GWERROR("Container not set, can't initialize lobby menu"); + return; + } + + UPanelWidget* panel = Cast(m_teamContainer->WidgetTree->RootWidget); + if (!panel) + { + GWERROR(L"No UPanelWidget found"); + return; + } + + // Create team slots in this menu for the amount of selected teams in the game setup screen + AGameStateBase* gameState = Cast(world->GetGameState()); + check(gameState); + m_numTeams = gameState->GetMapTeamCount(); + for (uint32 i = 0; i < m_numTeams; i++) + { + ULobbySlot* slot = CreateWidget(GetWorld(), lobbySlotClass); + check(slot); + + panel->AddChild(slot); + slot->SetTeamIndex(i+1); + m_lobbySlots.Add(slot); + } + + m_teamContainer->RescanItems(); +} + +void ULobbyMenu::ToggleReadyState() +{ + // Toggle my player state's ready state + if(!m_myState) + { + UWorld* const world = GetWorld(); + check(world); + APlayerControllerBase* player = Cast(world->GetGameInstance()->GetFirstLocalPlayerController()); + check(player); + m_myState = Cast(player->PlayerState); + } + + if(m_myState) + m_myState->SetReadyState(!m_myState->GetReadyState()); +} +void ULobbyMenu::StartGame() +{ + UWorld* world = GetWorld(); + check(world); + AGameStateBase* gameState = Cast(world->GetGameState()); + check(gameState); + TArray players = gameState->GetPlayers(); + + // Check if we're the host + AMenuGameMode* gameMode = world->GetAuthGameMode(); + if(gameMode) + { + // Check if everyone's ready + for (size_t i = 0; i < players.Num(); i++) + { + if (!players[i]->GetReadyState()) + return; + } + + // Tell the game to start + GPRINT("Starting game"); + if(!gameMode->StartGame()) + { + GERROR("Failed to start game"); + TArray options; + options.Add("OK"); + Cast(GetOwningPlayer())->overlay->ShowMessageBox("Error", "Failed to start game", options); + } + } +} + +void ULobbyMenu::GotoTeamSelector() +{ + if (m_teamContainer) + { + OpenSubMenu(m_teamContainer); + } +} +void ULobbyMenu::BackToMenu() +{ + // Close active session + UDefaultGameInstance* inst = Cast(GetOwningPlayer()->GetGameInstance()); + inst->sessionManager->DestroySession(); + inst->sessionManager->CloseNetConnections(); + + // Goto menu + AMenuController* mc = Cast(GetOwningPlayer()); + mc->SwitchToMenu(); +} +bool ULobbyMenu::IsHost() const +{ + if (!GetOwningPlayer()) + return false; + return GetOwningPlayer()->Role == ROLE_Authority; +} + +void ULobbyMenu::OnLobbyEnter() +{ + // Register session callbacks + UDefaultGameInstance* inst = Cast(GetOwningPlayer()->GetGameInstance()); + Init(); +} + + +TArray ULobbyMenu::GetCharacterSaves() +{ + return m_characters; +} \ No newline at end of file diff --git a/Source/UnrealProject/GUI/Lobby/LobbyMenu.h b/Source/UnrealProject/GUI/Lobby/LobbyMenu.h new file mode 100644 index 0000000..91355d6 --- /dev/null +++ b/Source/UnrealProject/GUI/Lobby/LobbyMenu.h @@ -0,0 +1,63 @@ +// Project Lab - NHTV Igad + +#pragma once +#include "MenuScreenBase.h" +#include "CharacterSettings.h" +#include "LobbyMenu.generated.h" + + +UCLASS() +class UNREALPROJECT_API ULobbyMenu : public UMenuScreenBase +{ + GENERATED_BODY() + +public: + virtual void NativeConstruct() override; + virtual void NativeDestruct() override; + virtual void NativeTick(const FGeometry& MyGeometry, float InDeltaTime) override; + + // Must be called from blueprint to setup this element + void Init(); + + UFUNCTION(BlueprintCallable, Category = "Ready") + void ToggleReadyState(); + UFUNCTION(BlueprintCallable, Category = "Ready") + void StartGame(); + UFUNCTION(BlueprintCallable, Category = "Ready") + void GotoTeamSelector(); + + UFUNCTION(BlueprintCallable, Category = "Game") + void BackToMenu(); + UFUNCTION(BlueprintCallable, Category = "Game") + bool IsHost() const; + + // Called when the local player enters the lobby menu & state + void OnLobbyEnter(); + + // When this menu needs to be hidden + UFUNCTION(BlueprintImplementableEvent) + void OnShow(); + // When this menu needs to be shown + UFUNCTION(BlueprintImplementableEvent) + void OnHide(); + + UPROPERTY(EditDefaultsOnly, Category = "Lobby") + TSubclassOf lobbySlotClass; + + UFUNCTION(BlueprintCallable, Category = "Lobby") + TArray GetCharacterSaves(); + +private: + ULobbyMenu(const FObjectInitializer& init); + + UPROPERTY() + class APlayerStateBase* m_myState; + UPROPERTY() + TArray m_lobbySlots; + UPROPERTY() + class USubMenu* m_teamContainer; + + uint32 m_numTeams; + + TArray m_characters; +}; diff --git a/Source/UnrealProject/GUI/Lobby/LobbyPlayerSlot.cpp b/Source/UnrealProject/GUI/Lobby/LobbyPlayerSlot.cpp new file mode 100644 index 0000000..aade5c5 --- /dev/null +++ b/Source/UnrealProject/GUI/Lobby/LobbyPlayerSlot.cpp @@ -0,0 +1,4 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "LobbyPlayerSlot.h" \ No newline at end of file diff --git a/Source/UnrealProject/GUI/Lobby/LobbyPlayerSlot.h b/Source/UnrealProject/GUI/Lobby/LobbyPlayerSlot.h new file mode 100644 index 0000000..66fa7b7 --- /dev/null +++ b/Source/UnrealProject/GUI/Lobby/LobbyPlayerSlot.h @@ -0,0 +1,16 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "GUI/PlayerSlot.h" +#include "PlayerSlot.h" +#include "LobbyPlayerSlot.generated.h" + +/** + * + */ +UCLASS() +class UNREALPROJECT_API ULobbyPlayerSlot : public UPlayerSlot +{ + GENERATED_BODY() +}; diff --git a/Source/UnrealProject/GUI/Lobby/LobbySlot.cpp b/Source/UnrealProject/GUI/Lobby/LobbySlot.cpp new file mode 100644 index 0000000..bc1cb9f --- /dev/null +++ b/Source/UnrealProject/GUI/Lobby/LobbySlot.cpp @@ -0,0 +1,125 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "LobbySlot.h" +#include "LobbyPlayerSlot.h" +#include "PlayerStateBase.h" +#include "PlayerControllerBase.h" + +#include "SubMenu.h" +#include "MenuScreenBase.h" +#include "GameStateBase.h" + +ULobbySlot::ULobbySlot(const FObjectInitializer& init) + : Super(init) +{ +} + +void ULobbySlot::NativeConstruct() +{ + Super::NativeConstruct(); + m_open = false; +} +void ULobbySlot::NativeTick(const FGeometry& MyGeometry, float InDeltaTime) +{ + Super::NativeTick(MyGeometry, InDeltaTime); + this->bIsEnabled = m_open; +} + +void ULobbySlot::SetTeamIndex(uint32 idx) +{ + m_teamIndex = idx; + m_UpdateTeamTitle(); +} +void ULobbySlot::m_UpdateTeamTitle() +{ + // Set the title of this team slot + if (m_teamName) + { + std::wstring teamName = std::wstring() + L"Team " + m_teamIndex + L" (" + m_players.Num() + L"/2)"; + SetButtonText(FString(teamName.c_str())); + } +} + +void ULobbySlot::UpdatePlayers(TArray players) +{ + check(m_playerList); + check(m_playerSlots.Num() == 2); + + UWorld* const world = GetWorld(); + check(world); + APlayerControllerBase* player = Cast(world->GetGameInstance()->GetFirstLocalPlayerController()); + check(player); + APlayerStateBase* myState = Cast(player->PlayerState); + + // Set all the player slots in this team to show their respective player / no player placeholder + m_players = players; + for (int32 i = 0; i < 2; i++) + { + if (i < m_players.Num()) + { + APlayerStateBase* player = players[i]; + m_playerSlots[i]->Init(player, player == myState); + } + else + { + m_playerSlots[i]->Init(nullptr); + } + } + m_UpdateTeamTitle(); +} +bool ULobbySlot::OnSlotSelected() +{ + UWorld* const world = GetWorld(); + check(world); + APlayerControllerBase* player = Cast(world->GetGameInstance()->GetFirstLocalPlayerController()); + check(player); + APlayerStateBase* myState = Cast(player->PlayerState); + + AGameStateBase* gameState = Cast(world->GetGameState()); + + if (myState->GetReadyState()) + return false; + + if (gameState) + { + auto pbt = gameState->GetPlayersByTeam(); + if (m_teamIndex < (uint32)pbt.Num() && pbt[m_teamIndex].Num() == 2) + { + return false; + } + } + + // Try for the local player to join this team + if (myState) // Check the state cause it can be null + myState->RequestTeamEntry(m_teamIndex); + else + GWERROR(L"Player not set in " + GetName()); + + // Make sure this item is now selected + GetSubMenu()->SelectNewItem(this); + + return true; +} + +void ULobbySlot::Init(UTextBlock* teamName, UVerticalBox* playerList) +{ + m_teamName = teamName; + m_playerList = playerList; + + // Add fixed 2 player slots to this team + m_playerList->ClearChildren(); + for (int32 i = 0; i < 2; i++) + { + ULobbyPlayerSlot* playerSlot = CreateWidget(GetWorld(), lobbyPlayerSlotClass); + check(playerSlot); + playerSlot->Init(nullptr); + m_playerSlots.Add(playerSlot); + m_playerList->AddChild(playerSlot); + } +} + +void ULobbySlot::SetState(bool open) +{ + m_open = open; +} diff --git a/Source/UnrealProject/GUI/Lobby/LobbySlot.h b/Source/UnrealProject/GUI/Lobby/LobbySlot.h new file mode 100644 index 0000000..a33b5f7 --- /dev/null +++ b/Source/UnrealProject/GUI/Lobby/LobbySlot.h @@ -0,0 +1,55 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "Blueprint/UserWidget.h" +#include "MenuButton.h" +#include +#include "LobbySlot.generated.h" + +UCLASS() +class UNREALPROJECT_API ULobbySlot : public UMenuButton +{ + GENERATED_BODY() + +public: + virtual void NativeConstruct() override; + virtual void NativeTick(const FGeometry& MyGeometry, float InDeltaTime) override; + + void SetTeamIndex(uint32 idx); + + // Must be called from blueprint to setup this element + UFUNCTION(BlueprintCallable, Category = "Lobby") + void Init(UTextBlock* teamName, UVerticalBox* playerList); + + void UpdatePlayers(TArray players); + void SetState(bool open); + + UFUNCTION(BlueprintCallable, Category = "Lobby") + bool OnSlotSelected(); + + UPROPERTY(EditDefaultsOnly, Category = "Lobby") + UFont* font; + + UPROPERTY(EditDefaultsOnly, Category="Lobby") + TSubclassOf lobbyPlayerSlotClass; + +private: + ULobbySlot(const FObjectInitializer& init); + + void m_UpdateTeamTitle(); + + UPROPERTY() + UTextBlock* m_teamName; + UPROPERTY() + UVerticalBox* m_playerList; + UPROPERTY() + TArray m_playerSlots; + + UPROPERTY() + UButton* m_joinButton; + uint32 m_teamIndex; + UPROPERTY() + TArray m_players; + bool m_open; +}; diff --git a/Source/UnrealProject/GUI/Menu/ButtonHintBar.cpp b/Source/UnrealProject/GUI/Menu/ButtonHintBar.cpp new file mode 100644 index 0000000..addbf63 --- /dev/null +++ b/Source/UnrealProject/GUI/Menu/ButtonHintBar.cpp @@ -0,0 +1,4 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "ButtonHintBar.h" diff --git a/Source/UnrealProject/GUI/Menu/ButtonHintBar.h b/Source/UnrealProject/GUI/Menu/ButtonHintBar.h new file mode 100644 index 0000000..6b8a9d5 --- /dev/null +++ b/Source/UnrealProject/GUI/Menu/ButtonHintBar.h @@ -0,0 +1,13 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "UserWidget.h" +#include "ButtonHintBar.generated.h" + +UCLASS() +class UNREALPROJECT_API UButtonHintBar : public UUserWidget +{ + GENERATED_BODY() +public: +}; diff --git a/Source/UnrealProject/GUI/Menu/GameList.cpp b/Source/UnrealProject/GUI/Menu/GameList.cpp new file mode 100644 index 0000000..189df86 --- /dev/null +++ b/Source/UnrealProject/GUI/Menu/GameList.cpp @@ -0,0 +1,246 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "GameList.h" +#include "GameListItem.h" +#include "DefaultGameInstance.h" +#include "SessionManager.h" +#include "ScreenOverlay.h" +#include "PlayerControllerBase.h" + +static UClass* itemWidgetClass; + +UGameList::UGameList(const FObjectInitializer& init) + : Super(init) +{ + itemWidgetClass = ConstructorHelpers::FClassFinder(TEXT("/Game/Assets/GUI/Components/WEEGEE_GameListItem")).Class; + m_quickJoining = false; +} + +void UGameList::NativeConstruct() +{ + Super::NativeConstruct(); + m_searchingSessions = false; + m_joining = false; + + UWorld* world = this->GetWorld(); + if (!world) + return; + m_gameInstance = Cast(world->GetGameInstance()); + if (!m_gameInstance) + return; + + onItemSelectionConfirmed.AddDynamic(this, &UGameList::OnListItemPressed); +} +void UGameList::NativeDestruct() +{ + m_StopPingingGames(); + Super::NativeDestruct(); +} +void UGameList::NativeTick(const FGeometry& MyGeometry, float InDeltaTime) +{ + Super::NativeTick(MyGeometry, InDeltaTime); +} + +void UGameList::OnStartSearching_Implementation() +{ +} +void UGameList::OnEndSearching_Implementation(int32) +{ +} +void UGameList::SetInitialSearchMode_Implementation(bool useLan) +{ +} +void UGameList::OnQuickJoin() +{ + OnSearchButton(); + m_quickJoining = true; +} +void UGameList::JoinSession(const class FOnlineSessionSearchResult& result) +{ + m_StopPingingGames(); + + if(!m_joining) + { + APlayerControllerBase* pc = Cast(GetOwningPlayer()); + check(!m_joiningOverlay); + m_joiningOverlay = pc->overlay->ShowOverlay("Joining game"); + m_joining = true; + + UDefaultGameInstance* inst = Cast(GetGameInstance()); + inst->sessionManager->onJoinSessionComplete.AddUObject(this, &UGameList::m_OnJoinSessionComplete); + inst->sessionManager->JoinSession(result); + } +} +void UGameList::SetSearchMode(bool useLan) +{ +} +void UGameList::OnSearchButton() +{ + UDefaultGameInstance* inst = Cast(GetGameInstance()); + if (!m_searchingSessions) + { + check(!m_searchingOverlay); + inst->sessionManager->onFindSessionsComplete.AddUObject(this, &UGameList::m_OnFindSessionsComplete); + inst->sessionManager->FindSessions(); + m_searchingSessions = true; + container->ClearChildren(); + + m_StopPingingGames(); + m_gameItems.SetNum(0); + + RescanItems(); + OnStartSearching(); + + APlayerControllerBase* pc = Cast(GetOwningPlayer()); + m_searchingOverlay = pc->overlay->ShowOverlay("Searching for games"); + } +} + +void UGameList::OnListItemPressed(UMenuItemBase* item) +{ + GWPRINT(L"Pressed item " + item); + UGameListItem* listItem = Cast(item); + if(listItem) + { + JoinSession(*listItem->result); + } +} + +void UGameList::m_OnFindSessionsComplete(bool success) +{ + UDefaultGameInstance* inst = Cast(GetGameInstance()); + + // Hide searching overlay + m_searchingOverlay->Close(); + m_searchingOverlay = nullptr; + + m_searchingSessions = false; + + if(!success) + { + APlayerControllerBase* pc = Cast(GetOwningPlayer()); + TArray options; + options.Add("OK"); + pc->overlay->ShowMessageBox("Error", "Error occured while fining games", options); + return; + } + + if(m_quickJoining) + { + // Find session with most players in, that is not full + auto& results = inst->sessionManager->sessionSearch->SearchResults; + if(results.Num() > 0) + { + FOnlineSessionSearchResult* optimal = &results[0]; + int32 largest = 0; + for(int32 i = 1; i < results.Num(); i++) + { + const int32 available = results[i].Session.NumOpenPublicConnections; + if(available == 0) continue; + const int32 max = results[i].Session.SessionSettings.NumPublicConnections; + const int32 current = max - available; + if(current > largest) + { + optimal = &results[i]; + largest = current; + } + } + JoinSession(*optimal); + } + m_quickJoining = false; + } + else + { + // Get session results + auto& results = inst->sessionManager->sessionSearch->SearchResults; + for(int32 i = 0; i < results.Num(); i++) + { + // Create a selectable slot in the game list + UGameListItem* childItem = CreateWidget(GetWorld(), itemWidgetClass); + check(container); + container->AddChild(childItem); + childItem->SetItem(this, results[i]); + m_gameItems.Add(childItem); + } + + OnEndSearching(results.Num()); + RescanItems(); + + // Give ping data for games + m_StartPingingGames(); + } + + m_searchingSessions = false; + + // Remove callback + inst->sessionManager->onFindSessionsComplete.RemoveAll(this); +} +void UGameList::m_OnJoinSessionComplete(int32 result) +{ + UDefaultGameInstance* inst = Cast(GetGameInstance()); + APlayerControllerBase* pc = Cast(GetOwningPlayer()); + + m_joining = false; + + // Remove callback + inst->sessionManager->onJoinSessionComplete.RemoveAll(this); + + if(result != EOnJoinSessionCompleteResult::Success) + { + FString error = "Unknown"; + switch(result) + { + case EOnJoinSessionCompleteResult::AlreadyInSession: + error = "Already in session"; + break; + case EOnJoinSessionCompleteResult::CouldNotRetrieveAddress: + error = "Could not retrieve address"; + break; + case EOnJoinSessionCompleteResult::SessionDoesNotExist: + error = "Session does not exist"; + break; + case EOnJoinSessionCompleteResult::SessionIsFull: + error = "Session is full"; + break; + } + + // Close joining overlay + m_joiningOverlay->Close(); + m_joiningOverlay = nullptr; + + // Show error message + TArray options; + options.Add("OK"); + pc->overlay->ShowMessageBox("Error", FString("Failed to join session:\n") + error, options); + } + else + { + // Travel to destination player lobby + pc->ClientTravel(inst->sessionManager->joinConnectionString, ETravelType::TRAVEL_Absolute, false); + //UWorld* world = GetWorld(); + //world->SeamlessTravel(inst->sessionManager->joinConnectionString, true); + //world->ServerTravel() + //AMenuGameMode* mgm = Cast(world->GetAuthGameMode()); + } +} + +void UGameList::m_PingGames() +{ + for(auto& g : m_gameItems) + { + g->Ping(); + } +} +void UGameList::m_StartPingingGames() +{ + GetGameInstance()->GetTimerManager().SetTimer(m_pinger, this, &UGameList::m_PingGames, 1.0f, true); +} +void UGameList::m_StopPingingGames() +{ + GetGameInstance()->GetTimerManager().ClearTimer(m_pinger); + for(UGameListItem* i : m_gameItems) + { + i->Ping(); + } +} diff --git a/Source/UnrealProject/GUI/Menu/GameList.h b/Source/UnrealProject/GUI/Menu/GameList.h new file mode 100644 index 0000000..02faade --- /dev/null +++ b/Source/UnrealProject/GUI/Menu/GameList.h @@ -0,0 +1,75 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "MenuScreen.h" +#include "OnlineSessionInterface.h" +#include "GameList.generated.h" +using namespace std; + +struct SessionItemData +{ + FString name; + int32 index; + FOnlineSessionSearchResult* result; +}; + +UCLASS() +class UNREALPROJECT_API UGameList : public UPersistentSideView +{ + GENERATED_BODY() + +public: + UGameList(const FObjectInitializer& init); + + virtual void NativeConstruct() override; + virtual void NativeDestruct() override; + virtual void NativeTick(const FGeometry& MyGeometry, float InDeltaTime) override; + + UFUNCTION(BlueprintNativeEvent, Category = "UI") + void OnStartSearching(); + UFUNCTION(BlueprintNativeEvent, Category = "UI") + void OnEndSearching(int32 gamesFound); + UFUNCTION(BlueprintNativeEvent, Category = "UI") + void SetInitialSearchMode(bool useLan); + UFUNCTION(BlueprintCallable, Category = "UI") + void OnQuickJoin(); + + void JoinSession(const class FOnlineSessionSearchResult& result); + UFUNCTION(BlueprintCallable, Category = "UI") + void SetSearchMode(bool useLan); + UFUNCTION(BlueprintCallable, Category = "UI") + void OnSearchButton(); + + UFUNCTION() + void OnListItemPressed(UMenuItemBase* item); + + UFUNCTION(BlueprintCallable, Category = "UI") + int32 GetNumGames() const { return m_gameItems.Num(); } + UFUNCTION(BlueprintCallable, Category = "UI") + TArray GetGames() const { return m_gameItems; } + +private: + UFUNCTION() + void m_OnFindSessionsComplete(bool success); + UFUNCTION() + void m_OnJoinSessionComplete(int32 result); + + void m_PingGames(); + void m_StartPingingGames(); + void m_StopPingingGames(); + FTimerHandle m_pinger; + + TArray m_gameItems; + + bool m_searchingSessions; + bool m_quickJoining; + bool m_joining; + + UPROPERTY() + class UOverlayInfo* m_searchingOverlay; + UPROPERTY() + class UOverlayInfo* m_joiningOverlay; + UPROPERTY() + class UDefaultGameInstance* m_gameInstance; +}; diff --git a/Source/UnrealProject/GUI/Menu/GameListItem.cpp b/Source/UnrealProject/GUI/Menu/GameListItem.cpp new file mode 100644 index 0000000..2efe42d --- /dev/null +++ b/Source/UnrealProject/GUI/Menu/GameListItem.cpp @@ -0,0 +1,53 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" + +#include "GameListItem.h" +#include "GameList.h" +#include "DefaultGameInstance.h" +#include "SessionManager.h" + +UGameListItem::UGameListItem(const FObjectInitializer& init) + : Super(init) +{ + valid = false; +} + +void UGameListItem::NativeConstruct() +{ + Super::NativeConstruct(); + OnGameInfoUpdated(); +} + +void UGameListItem::SetItem(UGameList* parent, const FOnlineSessionSearchResult& _result) +{ + // Set the sesion result to be displayed in this item + check(parent); + m_parent = parent; + result = &_result; + valid = result != nullptr; + if(valid) + { + UWorld* world = GetWorld(); + check(world); + UDefaultGameInstance* inst = Cast(world->GetGameInstance()); + FString nickname = inst->sessionManager->GetPlayerName(*result->Session.OwningUserId); + // Game Name = Online platform nickname of host + gameHost = nickname; + gamePing = result->PingInMs; + if(!result->Session.SessionSettings.Get(SETTING_MAPNAME, gameMapName)) + { + GWWARNING(L"Game does not have a map name set"); + } + gamePlayersMax = result->Session.SessionSettings.NumPublicConnections; + gamePlayers = gamePlayersMax - result->Session.NumOpenPublicConnections; + } + OnGameInfoUpdated(); +} + +void UGameListItem::Ping() +{ + UDefaultGameInstance* inst = Cast(GetWorld()->GetGameInstance()); + inst->sessionManager->PingResult(*result); + OnGameInfoUpdated(); +} \ No newline at end of file diff --git a/Source/UnrealProject/GUI/Menu/GameListItem.h b/Source/UnrealProject/GUI/Menu/GameListItem.h new file mode 100644 index 0000000..41a2694 --- /dev/null +++ b/Source/UnrealProject/GUI/Menu/GameListItem.h @@ -0,0 +1,41 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "MenuButton.h" +#include "GameListItem.generated.h" + +UCLASS() +class UNREALPROJECT_API UGameListItem : public UMenuButton +{ + GENERATED_BODY() + +public: + UGameListItem(const FObjectInitializer& init); + virtual void NativeConstruct() override; + + void SetItem(class UGameList* parent, const FOnlineSessionSearchResult& result); + + UFUNCTION(BlueprintImplementableEvent, Category = "UI") + void OnGameInfoUpdated(); + + void Ping(); + + UPROPERTY(BlueprintReadOnly, Category = "Game") + bool valid; + UPROPERTY(BlueprintReadOnly, Category = "Game") + FString gameHost; + UPROPERTY(BlueprintReadOnly, Category = "Game") + FString gameMapName; + UPROPERTY(BlueprintReadOnly, Category = "Game") + int32 gamePlayers; + UPROPERTY(BlueprintReadOnly, Category = "Game") + int32 gamePlayersMax; + UPROPERTY(BlueprintReadOnly, Category = "Game") + int32 gamePing; + + const class FOnlineSessionSearchResult* result; +private: + UPROPERTY() + class UGameList* m_parent; +}; diff --git a/Source/UnrealProject/GUI/Menu/GraphicsMenu.cpp b/Source/UnrealProject/GUI/Menu/GraphicsMenu.cpp new file mode 100644 index 0000000..d74afa9 --- /dev/null +++ b/Source/UnrealProject/GUI/Menu/GraphicsMenu.cpp @@ -0,0 +1,103 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "SelectButton.h" +#include "DefaultGameInstance.h" +#include "Prefs.h" +#include "GraphicsMenu.h" + + + +void UGraphicsMenu::NativeTick(const FGeometry& MyGeometry, float InDeltaTime) +{ + Super::NativeTick(MyGeometry, InDeltaTime); + + m_Update(); +} + + +void UGraphicsMenu::InitButtons(USelectButton* templateButton, TArray settingButtons) +{ + m_templateButton = templateButton; + m_settingButtons = settingButtons; + + if (!IsValid(m_templateButton) || m_settingButtons.Num() != 6) + { + JERROR("InitButton arguments invalid"); + m_templateButton = nullptr; + return; + } + + UDefaultGameInstance* instance = Cast(GetGameInstance()); + if (!IsValid(instance)) + return; + + UPrefs* const prefs = instance->GetPrefs(); + int32 fetchPreset = instance->GetScalabilityQuality(); + templateButton->selected = prefs->usingCustomGraphicsSettings ? 0 : fetchPreset + 1; + + m_previousSelection = -5; + m_Update(); +} + + +void UGraphicsMenu::Apply() +{ + UDefaultGameInstance* instance = Cast(GetGameInstance()); + if (!IsValid(m_templateButton) || !IsValid(instance)) + return; + + const int32 current = m_templateButton->selected; + if (current > 0) + { + // Store the template setting + instance->SetScalabilityQuality(current - 1); + } + else + { + // Store the individual sub settings + instance->SetScalabilityQualityValues( + m_settingButtons[0]->selected, + m_settingButtons[1]->selected, + m_settingButtons[2]->selected, + m_settingButtons[3]->selected, + m_settingButtons[4]->selected, + m_settingButtons[5]->selected); + } +} + +void UGraphicsMenu::m_Update() +{ + UDefaultGameInstance* instance = Cast(GetGameInstance()); + if (!IsValid(m_templateButton) || !IsValid(instance)) + return; + + const int32 current = m_templateButton->selected; + if (m_previousSelection != current) // Only update when the value has changed + { + if (current == 0) + { + // Enable the sub setting buttons + for (int32 i = 0; i < m_settingButtons.Num(); i++) + m_settingButtons[i]->SetIsEnabled(true); + + // Fetch the stored sub settings from prefs + TArray customSettings = instance->GetPrefs()->customGraphicsSettings; + if (customSettings.Num() <= m_settingButtons.Num()) + { + for (int32 i = 0; i < customSettings.Num(); i++) + m_settingButtons[i]->selected = customSettings[i]; + } + } + else + { + // Disable the subsetting buttons and set them to the template + for (int32 i = 0; i < m_settingButtons.Num(); i++) + { + m_settingButtons[i]->SetIsEnabled(false); + m_settingButtons[i]->selected = current - 1; + } + } + m_previousSelection = current; + } +} \ No newline at end of file diff --git a/Source/UnrealProject/GUI/Menu/GraphicsMenu.h b/Source/UnrealProject/GUI/Menu/GraphicsMenu.h new file mode 100644 index 0000000..a7a8332 --- /dev/null +++ b/Source/UnrealProject/GUI/Menu/GraphicsMenu.h @@ -0,0 +1,32 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "MenuScreen.h" +#include "GraphicsMenu.generated.h" + +/** + * + */ +UCLASS() +class UNREALPROJECT_API UGraphicsMenu : public USideView +{ + GENERATED_BODY() + +public: + virtual void NativeTick(const FGeometry& MyGeometry, float InDeltaTime) override; + + UFUNCTION(BlueprintCallable, Category = "Graphics") + void InitButtons(class USelectButton* templateButton, TArray settingButtons); + + UFUNCTION(BlueprintCallable, Category = "Graphics") + void Apply(); + +private: + void m_Update(); + + USelectButton* m_templateButton; + TArray m_settingButtons; + + int32 m_previousSelection; +}; diff --git a/Source/UnrealProject/GUI/Menu/MapSelectionScreen.cpp b/Source/UnrealProject/GUI/Menu/MapSelectionScreen.cpp new file mode 100644 index 0000000..ad900c8 --- /dev/null +++ b/Source/UnrealProject/GUI/Menu/MapSelectionScreen.cpp @@ -0,0 +1,161 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "MapData.h" +#include "MapSelectionScreen.h" +#include "AssetRegistryModule.h" +#include "DefaultGameInstance.h" +#include "MapSlot.h" +#include "SessionManager.h" +#include "MenuController.h" +#include "ScreenOverlay.h" +#include "MenuGameMode.h" + +static UClass* mapSlotWidgetClass; + +UMapSelectionScreen::UMapSelectionScreen(const FObjectInitializer& init) + : Super(init) +{ + mapSlotWidgetClass = ConstructorHelpers::FClassFinder(TEXT("/Game/Assets/GUI/Components/WEEGEE_MapListItem")).Class; +} + +void UMapSelectionScreen::NativeConstruct() +{ + Super::NativeConstruct(); + if(container) + { + // Get all the available maps and create selection buttons for them + UWorld* world = GetWorld(); + check(world); + auto& maps = Cast(world->GetGameInstance())->maps; + for(int32 i = 0; i < maps.Num(); i++) + { + UMapSlot* mapSlot = CreateWidget(GetWorld(), mapSlotWidgetClass); + check(mapSlot); + mapSlot->mapData = maps[i]; + mapSlot->mapScreen = this; + mapSlot->OnMapSet(); + m_mapSlots.Add(mapSlot); + container->AddChild(mapSlot); + } + + onItemSelected.RemoveAll(this); + onItemSelected.AddDynamic(this, &UMapSelectionScreen::OnSelectionChanged); + RescanItems(); + SelectMap(0); + } +} + +void UMapSelectionScreen::OnSelectionChanged(UMenuItemBase* item) +{ + UMapSlot* mapSlot = Cast(item); + if(mapSlot) + { + //SelectMap(mapSlot->index); + } +} +void UMapSelectionScreen::SelectMap(int32 index) +{ + UWorld* world = GetWorld(); + check(world); + UDefaultGameInstance* inst = Cast(world->GetGameInstance()); + + TArray& maps = inst->maps; + if(index < 0 || index > maps.Num()) + { + GWERROR(L"Selected map out of range"); + return; + } + + mapData = maps[index]; + check(mapData); + + // Set the asset path to the selected map in the session manager + AMenuGameMode* gameMode = Cast(world->GetAuthGameMode()); + if(gameMode) + { + gameMode->SetMap(mapData->pathToAsset); + GWPRINT(L"Selected map: " + maps[index]->friendlyName); + } + else + { + GWARNING(L"Can't select map, no game mode!"); + } +} + +void UMapSelectionScreen::CreateGame() +{ + SelectMap(GetSelectedItem()); + + if(!mapData) + { + GERROR("No map set"); + return; + } + + UWorld* world = GetWorld(); + check(world); + UDefaultGameInstance* inst = Cast(world->GetGameInstance()); + + AMenuGameMode* gameMode = Cast(world->GetAuthGameMode()); + if(!gameMode) + { + GERROR("Can't start game, no menu game mode set"); + return; + } + + APlayerControllerBase* pc = Cast(GetOwningPlayer()); + if(m_sessionCreateOverlay) + return; // Already creating session + m_sessionCreateOverlay = pc->overlay->ShowOverlay("Creating session"); + + // Set the map setting in the game mode + gameMode->AssignMapData(mapData); + + // Make sure there is no connection already + inst->sessionManager->CloseNetConnections(); + + // Set host settings + FOnlineSessionSettings& sessionSettings = inst->sessionManager->sessionSettings; + sessionSettings.NumPublicConnections = mapData->maxTeamCount * 2; // Team count setting translated to max connected players (x2) + sessionSettings.Set(SETTING_MAPNAME, mapData->pathToAsset, EOnlineDataAdvertisementType::ViaOnlineServiceAndPing); + + // Create a new session + inst->sessionManager->onCreateSessionComplete.AddUObject(this, &UMapSelectionScreen::m_OnCreateSessionCompleted); + inst->sessionManager->CreateSession(); +} + +void UMapSelectionScreen::m_OnCreateSessionCompleted(bool success) +{ + if(m_sessionCreateOverlay) + { + m_sessionCreateOverlay->Close(); + m_sessionCreateOverlay = nullptr; + } + + UDefaultGameInstance* inst = Cast(GetWorld()->GetGameInstance()); + inst->sessionManager->onCreateSessionComplete.RemoveAll(this); + + AMenuController* mc = Cast(GetOwningPlayer()); + if(success) + { + // Goto the lobby + mc->OnEnterLobby(); + + // Start the session, @note not sure if this is needed + //inst->sessionManager->StartSession(); + } + else + { + TArray options; + options.Add("OK"); + mc->overlay->ShowMessageBox("Error", "Failed to create game session", options); + } +} + +UMapData::UMapData() +{ + maxTeamCount = 3; + friendlyName = "Map Name"; + description = ""; +} \ No newline at end of file diff --git a/Source/UnrealProject/GUI/Menu/MapSelectionScreen.h b/Source/UnrealProject/GUI/Menu/MapSelectionScreen.h new file mode 100644 index 0000000..968a657 --- /dev/null +++ b/Source/UnrealProject/GUI/Menu/MapSelectionScreen.h @@ -0,0 +1,40 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "MenuScreen.h" +#include "MapSelectionScreen.generated.h" + +UCLASS() +class UNREALPROJECT_API UMapSelectionScreen : public USideView +{ + GENERATED_BODY() + +public: + UMapSelectionScreen(const FObjectInitializer& init); + virtual void NativeConstruct() override; + + UFUNCTION() + void OnSelectionChanged(UMenuItemBase* item); + + UFUNCTION(BlueprintCallable, Category = "Level") + void SelectMap(int32 mapIndex); + UFUNCTION(BlueprintCallable, Category = "Level") + void CreateGame(); + + // Currently Selected map data + UPROPERTY(BlueprintReadOnly, Category = "Level") + class UMapData* mapData; + UPROPERTY() + class UMapSlot* selectedMapSlot; + +private: + UPROPERTY() + class UOverlayInfo* m_sessionCreateOverlay; + + UFUNCTION() + void m_OnCreateSessionCompleted(bool success); + + UPROPERTY() + TArray m_mapSlots; +}; diff --git a/Source/UnrealProject/GUI/Menu/MapSlot.cpp b/Source/UnrealProject/GUI/Menu/MapSlot.cpp new file mode 100644 index 0000000..79cb98b --- /dev/null +++ b/Source/UnrealProject/GUI/Menu/MapSlot.cpp @@ -0,0 +1,14 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "MapSlot.h" +#include "MapData.h" + +void UMapSlot::NativeConstruct() +{ + Super::NativeConstruct(); +} + +void UMapSlot::OnMapSet_Implementation() +{ +} \ No newline at end of file diff --git a/Source/UnrealProject/GUI/Menu/MapSlot.h b/Source/UnrealProject/GUI/Menu/MapSlot.h new file mode 100644 index 0000000..c7c6715 --- /dev/null +++ b/Source/UnrealProject/GUI/Menu/MapSlot.h @@ -0,0 +1,26 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "MenuButton.h" +#include "MapSlot.generated.h" + +/** + * + */ +UCLASS() +class UNREALPROJECT_API UMapSlot : public UMenuButton +{ + GENERATED_BODY() + +public: + virtual void NativeConstruct() override; + + UFUNCTION(BlueprintNativeEvent, Category = "Slot") + void OnMapSet(); + + UPROPERTY(BlueprintReadOnly, Category = "Slot") + class UMapSelectionScreen* mapScreen; + UPROPERTY(BlueprintReadOnly, Category = "Slot") + class UMapData* mapData; +}; diff --git a/Source/UnrealProject/GUI/Menu/MenuAction.h b/Source/UnrealProject/GUI/Menu/MenuAction.h new file mode 100644 index 0000000..4f6a912 --- /dev/null +++ b/Source/UnrealProject/GUI/Menu/MenuAction.h @@ -0,0 +1,32 @@ +#pragma once + +#include "MenuAction.Generated.h" + +UENUM(BlueprintType) +enum class EMenuActionBinding : uint8 +{ + Confirm, + Back, + Opt1, + Opt2, + Start, + Up, + Down, + Left, + Right, + Repeat_Left, + Repeat_Right, + Trigger_Left, + Trigger_Right, + Shoulder_Left, + Shoulder_Right, + Options, + + // These are actually just for button hint icons + // don't use these for actual actions as they wont get used + LeftStick, + RightStick, + DPad, + ShoulderButtons, + Triggers +}; \ No newline at end of file diff --git a/Source/UnrealProject/GUI/Menu/MenuButton.cpp b/Source/UnrealProject/GUI/Menu/MenuButton.cpp new file mode 100644 index 0000000..5615c82 --- /dev/null +++ b/Source/UnrealProject/GUI/Menu/MenuButton.cpp @@ -0,0 +1,147 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "MenuButton.h" +#include "SubMenu.h" + + +FLinearColor uiIdleColor = FLinearColor(1.0f, 1.0f, 1.0f, 0.3f); +FLinearColor uiSelectedColor = FLinearColor(0.8f, 1.0f, 0.8f, 0.4f); +FLinearColor uiSelectedFocusedColor = FLinearColor(0.2f, 1.0f, 0.2f, 0.8f); + +TSubclassOf defaultButtonClass; + +UMenuButton::UMenuButton(const FObjectInitializer& init) + : Super(init) +{ + defaultButtonClass = ConstructorHelpers::FClassFinder(L"/Game/Assets/GUI/Components/WEEGEE_Button").Class; +} +void UMenuButton::NativeConstruct() +{ + m_button = WidgetTree->FindWidget("Button"); + m_label = WidgetTree->FindWidget("Label"); + m_textOverlay = WidgetTree->FindWidget("TextOverlay"); + if(!m_button) + { + GWERROR(L"Widget " + GetName() + L" does not contain a Button componenet"); + } + else + { + m_button->OnClicked.AddDynamic(this, &UMenuButton::m_OnPressed); + } + + + NativeOnSelectionChanged(false, true); + Super::NativeConstruct(); + + if(m_textOverlay && m_label) + { + m_blurOverlay = NewObject(GetWorld()); + UOverlaySlot* slot = m_textOverlay->AddChildToOverlay(m_blurOverlay); + slot->SetHorizontalAlignment(EHorizontalAlignment::HAlign_Fill); + slot->SetVerticalAlignment(EVerticalAlignment::VAlign_Fill); + + UOverlaySlot* labelSlot = Cast(m_label->Slot); + EHorizontalAlignment textAlignH = labelSlot ? labelSlot->HorizontalAlignment.GetValue() : HAlign_Fill; + EVerticalAlignment textAlignV = labelSlot ? labelSlot->VerticalAlignment.GetValue() : VAlign_Fill; + + m_label->RemoveFromParent(); + + static int32 quality = 16; + static float distance = 2.0f; + for(int32 i = 0; i < quality; i++) + { + float r = (float)i / (float)(quality - 1); + FVector2D offset = FVector2D(cos(r*PI), sin(r*PI)) * distance; + // Create Glow effect + UTextBlock* blurText = NewObject(GetWorld()); + UOverlaySlot* textSlot = m_blurOverlay->AddChildToOverlay(blurText); + textSlot->SetHorizontalAlignment(textAlignH); + textSlot->SetVerticalAlignment(textAlignV); + + FSlateFontInfo font = m_label->Font; + blurText->SetFont(font); + blurText->SetColorAndOpacity(FSlateColor(FLinearColor(0.6f, 0.6f, 1.0f, 0.5f))); + blurText->SetRenderTranslation(offset); + blurText->SetText(m_label->GetText()); + } + m_blurOverlay->SetVisibility(ESlateVisibility::Hidden); + + labelSlot = m_textOverlay->AddChildToOverlay(m_label); + labelSlot->SetHorizontalAlignment(textAlignH); + labelSlot->SetVerticalAlignment(textAlignV); + } +} + +void UMenuButton::FocusTick(float DeltaTime) +{ + Super::FocusTick(DeltaTime); + if(m_button && m_button->IsHovered() && GetSubMenu()->GetSelectedItem() != index) + { + GetSubMenu()->SelectNewItemByIndex(index); + } +} + +void UMenuButton::m_OnPressed() +{ + if(HasFocus()) + { + NativeOnPressed(false); + } +} +void UMenuButton::NativeOnMenuAction(EMenuActionBinding binding) +{ + Super::NativeOnMenuAction(binding); + if(binding == EMenuActionBinding::Confirm) + NativeOnPressed(true); +} + +void UMenuButton::NativeOnSelectionChanged(bool selected, bool controller) +{ + Super::NativeOnSelectionChanged(selected, controller); + if(!m_button) + return; + + UScrollBox* sb = Cast(Slot->Parent); + if(sb && selected) + { + sb->ScrollWidgetIntoView(this, true); + } + + if(m_blurOverlay) + { + if(selected) + { + m_blurOverlay->SetVisibility(ESlateVisibility::SelfHitTestInvisible); + } + else + { + m_blurOverlay->SetVisibility(ESlateVisibility::Hidden); + } + } +} + +void UMenuButton::NativeOnPressed(bool controllerInput) +{ + // Simulate OnSelectionConfirmed event if this button is pressed by a mouse + GetSubMenu()->NativeOnSelectionConfirmed(this); + onPressed.Broadcast(); +} + +void UMenuButton::SetButtonText(FString textStr) +{ + FText text = FText::FromString(textStr); + if(m_label) + m_label->SetText(text); + if(m_blurOverlay) + { + for(int32 i = 0; i < m_blurOverlay->GetChildrenCount(); i++) + { + UTextBlock* tb = Cast(m_blurOverlay->GetChildAt(i)); + if(tb) + { + tb->SetText(text); + } + } + } +} diff --git a/Source/UnrealProject/GUI/Menu/MenuButton.h b/Source/UnrealProject/GUI/Menu/MenuButton.h new file mode 100644 index 0000000..c60214b --- /dev/null +++ b/Source/UnrealProject/GUI/Menu/MenuButton.h @@ -0,0 +1,48 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "GUI/Menu/MenuItemBase.h" +#include "MenuButton.generated.h" + +extern FLinearColor uiIdleColor; +extern FLinearColor uiSelectedColor; + +/** + * + */ +UCLASS() +class UNREALPROJECT_API UMenuButton : public UMenuItemBase +{ + GENERATED_BODY() +public: + UMenuButton(const FObjectInitializer& init); + void NativeConstruct() override; + void FocusTick(float DeltaTime) override; + + DECLARE_DYNAMIC_MULTICAST_DELEGATE(FButtonPressed); + UPROPERTY(BlueprintAssignable, Category = "MenuButton") + FButtonPressed onPressed; + + virtual void NativeOnMenuAction(EMenuActionBinding binding) override; + virtual void NativeOnSelectionChanged(bool selected, bool controller) override; + + // Called when this button is pressed, and if it was a controller or a mouse + virtual void NativeOnPressed(bool controllerInput); + + // Updates button text + glow + UFUNCTION(BlueprintCallable, Category="MenuButton") + void SetButtonText(FString text); + + void SimulatePressed() { m_OnPressed(); } + +protected: + UFUNCTION() + virtual void m_OnPressed(); + + UOverlay* m_textOverlay; + UOverlay* m_blurOverlay; + UPROPERTY() + UTextBlock* m_label; + UButton* m_button; +}; diff --git a/Source/UnrealProject/GUI/Menu/MenuEnum.h b/Source/UnrealProject/GUI/Menu/MenuEnum.h new file mode 100644 index 0000000..89e10f2 --- /dev/null +++ b/Source/UnrealProject/GUI/Menu/MenuEnum.h @@ -0,0 +1,9 @@ +#pragma once + +UENUM(BlueprintType) +enum class MenuButtonID : uint8 +{ + OptionsGeneral, + OptionsGraphics, + Options, +}; \ No newline at end of file diff --git a/Source/UnrealProject/GUI/Menu/MenuItemBase.cpp b/Source/UnrealProject/GUI/Menu/MenuItemBase.cpp new file mode 100644 index 0000000..6f1eed6 --- /dev/null +++ b/Source/UnrealProject/GUI/Menu/MenuItemBase.cpp @@ -0,0 +1,92 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "MenuItemBase.h" +#include "SubMenu.h" +#include "PlayerControllerBase.h" +#include "ScreenOverlay.h" + +void UMenuItemBase::NativeConstruct() +{ + m_selected = false; + blockInput = false; + m_subMenu = nullptr; + priority = 0; + Super::NativeConstruct(); +} +void UMenuItemBase::NativeDestruct() +{ + Super::NativeDestruct(); +} + +void UMenuItemBase::FocusTick(float DeltaTime) +{ + +} + +UMsgBoxInfo* UMenuItemBase::ShowMessageBox(FOverlayItemClosed cb, FString caption, FString message, TArray options, FString defaultOption) +{ + UScreenOverlay* overlay = Cast(GetOwningPlayer())->overlay; + if(!overlay) + return nullptr; + return overlay->ShowMessageBoxCallback(cb, caption, message, options, defaultOption); +} + +void UMenuItemBase::NativeOnSelectionChanged(bool selected, bool controller) +{ + if(selected != m_selected) + { + m_selected = selected; + onSelectionChanged.Broadcast(selected); + } +} + +bool UMenuItemBase::IsSelected() const +{ + return m_selected; +} + +class USubMenu* UMenuItemBase::GetSubMenu() const +{ + return m_subMenu; +} + +bool UMenuItemBase::HasFocus() const +{ + return GetSubMenu() && GetSubMenu()->HasFocus(); +} + +void UMenuItemBase::AddActionBinding(EMenuActionBinding binding, FMenuAction action) +{ + m_actionBindings.Add(binding, action); +} + +void UMenuItemBase::ClearActionBinding(EMenuActionBinding binding) +{ + m_actionBindings.Remove(binding); +} + +void UMenuItemBase::OnMenuAction_Implementation(EMenuActionBinding binding) +{ +} +void UMenuItemBase::NativeOnMenuAction(EMenuActionBinding binding) +{ + OnMenuAction(binding); + + TArray actions; + m_actionBindings.MultiFind(binding, actions); + for(int32 i = 0; i < actions.Num(); i++) + { + actions[i].Execute(); + } +} + +FText UMenuItemBase::GetDescription_Implementation() +{ + return FText(); +} + +FText UMenuItemBase::GetTitle_Implementation() +{ + return FText(); +} diff --git a/Source/UnrealProject/GUI/Menu/MenuItemBase.h b/Source/UnrealProject/GUI/Menu/MenuItemBase.h new file mode 100644 index 0000000..fe115bd --- /dev/null +++ b/Source/UnrealProject/GUI/Menu/MenuItemBase.h @@ -0,0 +1,83 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "MenuAction.h" +#include "Blueprint/UserWidget.h" +#include "MenuItemBase.generated.h" + +DECLARE_DYNAMIC_DELEGATE_OneParam(FOverlayItemClosed, int32, choice); + +extern TSubclassOf defaultButtonClass; +extern TSubclassOf defaultSliderClass; + +/** + * Base class for a menu item with controller navigation/input support + */ +UCLASS() +class UNREALPROJECT_API UMenuItemBase : public UUserWidget +{ + GENERATED_BODY() + +public: + void NativeConstruct() override; + void NativeDestruct() override; + + // Called when the item's menu has focus every frame + virtual void FocusTick(float DeltaTime); + + UFUNCTION(BlueprintCallable, Category="MenuItem") + virtual void NativeOnSelectionChanged(bool selected, bool controller); + + // Shows a message box and fires a delegate when the user chooses an option + UFUNCTION(BlueprintCallable, Category = "SubMenu") + class UMsgBoxInfo* ShowMessageBox(FOverlayItemClosed cb, FString caption, FString message, TArray options, FString defaultOption); + + DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FSelectionChanged, bool, selected); + UPROPERTY(BlueprintAssignable, Category = "MenuItem") + FSelectionChanged onSelectionChanged; + +// UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "MenuItem") + int32 priority; + + UPROPERTY(BlueprintReadOnly) + int32 index; + + UFUNCTION(BlueprintCallable, Category = "MenuItem") + bool IsSelected() const; + UFUNCTION(BlueprintCallable, Category = "MenuItem") + class USubMenu* GetSubMenu() const; + + UFUNCTION(BlueprintCallable, Category = "MenuItem") + bool HasFocus() const; + + DECLARE_DYNAMIC_DELEGATE(FMenuAction); + UFUNCTION(BlueprintCallable, Category = "MenuItem") + void AddActionBinding(EMenuActionBinding binding, FMenuAction action); + UFUNCTION(BlueprintCallable, Category = "MenuItem") + void ClearActionBinding(EMenuActionBinding binding); + + UFUNCTION(BlueprintNativeEvent, Category = "MenuItem") + void OnMenuAction(EMenuActionBinding binding); + virtual void NativeOnMenuAction(EMenuActionBinding binding); + + UFUNCTION(BlueprintNativeEvent, Category = "MenuItem") + FText GetDescription(); + UFUNCTION(BlueprintNativeEvent, Category = "MenuItem") + FText GetTitle(); + + UPROPERTY(BlueprintReadOnly, Category="MenuItem") + bool blockInput; + +private: + + bool m_selected; + friend class APlayerControllerBase; + friend class USubMenu; + + TMultiMap m_actionBindings; + +protected: + UPROPERTY() + class USubMenu* m_subMenu; +}; diff --git a/Source/UnrealProject/GUI/Menu/MenuScreen.cpp b/Source/UnrealProject/GUI/Menu/MenuScreen.cpp new file mode 100644 index 0000000..26913f5 --- /dev/null +++ b/Source/UnrealProject/GUI/Menu/MenuScreen.cpp @@ -0,0 +1,337 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "MenuScreen.h" +#include "ButtonHintBar.h" +#include "SubMenu.h" +#include "MenuButton.h" + +void UMenuScreen::NativeConstruct() +{ + Super::NativeConstruct(); + m_sideViewActive = false; + m_buttonHintBar = Cast(WidgetTree->FindWidget("ButtonHintBar")); + m_menuContainer = Cast(WidgetTree->FindWidget("MenuContainer")); + m_sideViewContainer = Cast(WidgetTree->FindWidget("SideViewContainer")); + m_menuTitle = Cast(WidgetTree->FindWidget("MenuTitle")); + m_sideViewTitle = Cast(WidgetTree->FindWidget("SideViewTitle")); +} + +UMenuPanel* UMenuScreen::OpenScreenMenu(TSubclassOf cls) +{ + UMenuPanel* menu = CreateWidget(GetWorld(), cls); + if(!menu) + { + GERROR("Failed to create menu for screen " + GetName()); + return nullptr; + } + + m_menuContainer->AddChildToOverlay(menu); + OpenSubMenu(menu); + + menu->FadeIn(); + + return menu; +} +USideView* UMenuScreen::OpenSideView(TSubclassOf cls) +{ + USideView* view = CreateWidget(GetWorld(), cls); + if(!view) + { + GERROR("Failed to create side view for screen " + GetName()); + return nullptr; + } + UPersistentSideView* pview = Cast(view); + + UOverlaySlot* slot = m_sideViewContainer->AddChildToOverlay(view); + slot->SetHorizontalAlignment(HAlign_Fill); + slot->SetVerticalAlignment(VAlign_Fill); + + if(pview) + m_OpenSubMenu(view, false); + else + OpenSubMenu(view); + + view->FadeIn(); + + // Switch side view active event + if(!m_sideViewActive) + { + SwitchSideViewVisible(true); + m_sideViewActive = true; + } + + return view; +} + +void UMenuScreen::CloseAllSideViews() +{ + TArray viewsToRemove; + for(int32 i = 0; i < m_sideViewContainer->GetChildrenCount(); i++) + { + USideView* sv = Cast(m_sideViewContainer->GetChildAt(i)); + if(sv && !sv->IsA()) + viewsToRemove.Add(sv); + } + + for(USideView* sv : viewsToRemove) + { + CloseSubMenu(sv); + } +} + +void UMenuScreen::OpenPersistentSideView(UPersistentSideView* sideView) +{ + UMenuScreen::OpenSubMenu(sideView); +} +void UMenuScreen::ClosePersistentSideView(UPersistentSideView* sideView) +{ + m_CloseSubMenu(sideView, true); +} + +void UMenuScreen::OpenSubMenu(class USubMenu* submenu) +{ + m_OpenSubMenu(submenu); +} +void UMenuScreen::CloseSubMenu(class USubMenu* submenu) +{ + m_CloseSubMenu(submenu); +} + +void UMenuScreen::m_OpenSubMenu(class USubMenu* submenu, bool transferFocus) +{ + CloseAllSideViews(); + + if(transferFocus) + Super::OpenSubMenu(submenu); + UMenuScreenSubMenu* mssm = Cast(submenu); + if(!m_menuTitle || !mssm) + return; + + // Update titles and sub-titles + UMenuPanel* panel = Cast(submenu); + if(panel) + m_menuTitle->SetText(FText::FromString(mssm->menuName)); + USideView* view = Cast(submenu); + if(view) + { + m_sideViewTitle->SetText(FText::FromString(mssm->menuName)); + m_currentSideViews.Add(view); + } +} +void UMenuScreen::m_CloseSubMenu(class USubMenu* submenu, bool persist) +{ + UMenuScreenSubMenu* mssm = Cast(submenu); + if(mssm) + mssm->m_isBeingClosed = true; + UMenuPanel* panel = Cast(submenu); + if(panel) + { + CloseAllSideViews(); + panel->onAnimationEnded.RemoveAll(this); + panel->onAnimationEnded.AddUObject(this, &UMenuScreen::m_OnFadeOutComplete); + panel->FadeOut(); + } + USideView* view = Cast(submenu); + if(view && !persist) + { + view->onAnimationEnded.RemoveAll(this); + view->onAnimationEnded.AddUObject(this, &UMenuScreen::m_OnFadeOutComplete); + view->FadeOut(); + + // Switch side view active event + if(m_currentSideViews.Num() > 0 && m_sideViewActive) + { + SwitchSideViewVisible(false); + m_sideViewActive = false; + } + + m_currentSideViews.Remove(view); + } + + Super::CloseSubMenu(submenu); + + // Update titles and sub-titles + if(panel) + { + if(m_subMenus.Num() > 0) + { + UMenuPanel* newMenu = Cast(m_subMenus.Last()); + if(newMenu) + m_menuTitle->SetText(FText::FromString(newMenu->menuName)); + else + goto _set_no_panel_text; + } + else + { + _set_no_panel_text: + m_menuTitle->SetText(FText()); + } + } + else if(view && !persist) + { + if(m_subMenus.Num() > 0) + { + USideView* newMenu = Cast(m_subMenus.Last()); + if(newMenu) + m_sideViewTitle->SetText(FText::FromString(newMenu->menuName)); + else + goto _set_no_view_text; + } + else + { + _set_no_view_text: + m_sideViewTitle->SetText(FText()); + } + } + if(mssm) + mssm->m_isBeingClosed = false; +} + +void UMenuScreen::m_OnFadeOutComplete(UMenuScreenSubMenu* panel, int32 type) +{ + if(type == 2) // Fade Out + { + panel->RemoveFromParent(); + } +} + +static float animationDuration = 0.35f; +void UMenuScreenSubMenu::NativeConstruct() +{ + m_animationType = 0; + m_animationTime = 0.0f; + m_isBeingClosed = false; + Super::NativeConstruct(); +} +UMenuScreenSubMenu::UMenuScreenSubMenu(const FObjectInitializer& init) + : Super(init) +{ + menuName = ""; +} +void UMenuScreenSubMenu::NativeTick(const FGeometry& MyGeometry, float InDeltaTime) +{ + Super::NativeTick(MyGeometry, InDeltaTime); + + float r = FMath::Clamp(m_animationTime / animationDuration, 0.0f, 1.0f); + if(m_animationType == 1) // Fade In + { + SetRenderTranslation(FVector2D(0.0f - ((1.0f - r)*(1.0f - r)) * 100.0f, 0.0f)); + FLinearColor color = FLinearColor(1.0f, 1.0f, 1.0f, r); + SetColorAndOpacity(color); + } + else if(m_animationType == 2) // Fade Out + { + SetRenderTranslation(FVector2D(0.0f + (r*r) * 100.0f, 0.0f)); + FLinearColor color = FLinearColor(1.0f, 1.0f, 1.0f, (1.0f-r)); + SetColorAndOpacity(color); + } + + if(m_animationType != 0) + { + if(r >= 1.0f) + { + onAnimationEnded.Broadcast(this, m_animationType); + m_animationType = 0; + } + m_animationTime += InDeltaTime; + } +} + +void UMenuScreenSubMenu::OnSuspend(USubMenu* newSubMenu, bool loseFocus) +{ + // Don't fade out / lose focus when side view is opened + USideView* sv = Cast(newSubMenu); + + if(sv) + loseFocus = false; + else + FadeOut(); + Super::OnSuspend(newSubMenu, loseFocus); +} +void UMenuScreenSubMenu::OnRestore(USubMenu* removedMenu) +{ + // To prevent playing the fade in animation again when we receive focus from closing side views when we are actually closing this menu + if(!m_isBeingClosed && !Cast(removedMenu)) + FadeIn(); + Super::OnRestore(removedMenu); +} + +void UMenuScreenSubMenu::FadeIn() +{ + m_animationTime = 0.0f; + m_animationType = 1; +} +void UMenuScreenSubMenu::FadeOut() +{ + m_animationTime = 0.0f; + m_animationType = 2; +} +UMenuPanel* UMenuScreenSubMenu::OpenScreenMenu(TSubclassOf cls) +{ + UMenuScreen* screen = Cast(GetScreen()); + if(screen) + { + return screen->OpenScreenMenu(cls); + } + return nullptr; +} +USideView* UMenuScreenSubMenu::OpenSideView(TSubclassOf cls) +{ + UMenuScreen* screen = Cast(GetScreen()); + if(screen) + { + screen->CloseAllSideViews(); + return screen->OpenSideView(cls); + } + return nullptr; +} + +class UMenuButton* UMenuScreenSubMenu::AddButton(FString label) +{ + if(!container) + return nullptr; + + UMenuButton* button = CreateWidget(GetWorld(), defaultButtonClass); + if(!button) + return nullptr; + + container->AddChild(button); + RescanItems(); + + button->SetButtonText(label); + + return button; +} + +void UMenuScreenSubMenu::RescanItems() +{ + m_backButton = nullptr; + + Super::RescanItems(); + + for(UMenuItemBase* item : m_items) + { + UMenuButton* button = Cast(item); + if(button) + { + if(button->GetName() == "Back") + { + m_backButton = button; + break; + } + } + } +} +void UMenuScreenSubMenu::NativeOnMenuAction(EMenuActionBinding binding) +{ + Super::NativeOnMenuAction(binding); + if(binding == EMenuActionBinding::Back) + { + if(m_backButton) + { + m_backButton->SimulatePressed(); + } + } +} + diff --git a/Source/UnrealProject/GUI/Menu/MenuScreen.h b/Source/UnrealProject/GUI/Menu/MenuScreen.h new file mode 100644 index 0000000..5784039 --- /dev/null +++ b/Source/UnrealProject/GUI/Menu/MenuScreen.h @@ -0,0 +1,135 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "GUI/Menu/MenuScreenBase.h" +#include "SubMenu.h" +#include "MenuScreen.generated.h" + +UCLASS() +class UMenuScreenSubMenu : public USubMenu +{ + GENERATED_BODY() +public: + UMenuScreenSubMenu(const FObjectInitializer& init); + virtual void NativeConstruct() override; + virtual void NativeTick(const FGeometry& MyGeometry, float InDeltaTime) override; + + virtual void OnSuspend(USubMenu* newSubMenu, bool loseFocus); + virtual void OnRestore(USubMenu* removedMenu); + + void FadeIn(); + void FadeOut(); + + UFUNCTION(BlueprintCallable, Category = "Menu Screen") + UMenuPanel* OpenScreenMenu(TSubclassOf cls); + UFUNCTION(BlueprintCallable, Category = "Menu Screen") + USideView* OpenSideView(TSubclassOf cls); + + class UMenuButton* AddButton(FString label); + + virtual void RescanItems() override; + virtual void NativeOnMenuAction(EMenuActionBinding binding) override; + + // Assigns a button to be the back button, this button will receive a pressed event when the player presses the circle button + void SetBackButton(UMenuButton* button) { m_backButton = button; } + + DECLARE_MULTICAST_DELEGATE_TwoParams(FOnAnimationEnded, UMenuScreenSubMenu*, int32) + FOnAnimationEnded onAnimationEnded; + + UPROPERTY(EditDefaultsOnly, Category = "SubMenu") + FString menuName; + +private: + UPROPERTY() + UMenuButton* m_backButton; + + int32 m_animationType; + float m_animationTime; + bool m_isBeingClosed; + friend class UMenuScreen; +}; + +UCLASS() +class USideView : public UMenuScreenSubMenu +{ + GENERATED_BODY() +public: + virtual ~USideView() = default; +}; + +UCLASS() +class UPersistentSideView : public USideView +{ + GENERATED_BODY() +public: + virtual ~UPersistentSideView() = default; +}; + +UCLASS() +class UMenuPanel : public UMenuScreenSubMenu +{ + GENERATED_BODY() +public: +}; + +/** + * + */ +UCLASS() +class UNREALPROJECT_API UMenuScreen : public UMenuScreenBase +{ + GENERATED_BODY() +public: + void NativeConstruct() override; + + UFUNCTION(BlueprintCallable, Category = "Menu Screen") + UMenuPanel* OpenScreenMenu(TSubclassOf cls); + UFUNCTION(BlueprintCallable, Category = "Menu Screen") + USideView* OpenSideView(TSubclassOf cls); + UFUNCTION(BlueprintCallable, Category = "Menu Screen") + void CloseAllSideViews(); + + UFUNCTION(BlueprintImplementableEvent, Category="Menu Screen") + void SwitchSideViewVisible(bool visible); + + UFUNCTION(BlueprintCallable, Category = "Menu Screen") + void OpenPersistentSideView(UPersistentSideView* sideView); + UFUNCTION(BlueprintCallable, Category = "Menu Screen") + void ClosePersistentSideView(UPersistentSideView* sideView); + + // When this menu needs to be hidden + UFUNCTION(BlueprintImplementableEvent) + void OnShow(); + // When this menu needs to be shown + UFUNCTION(BlueprintImplementableEvent) + void OnHide(); + + virtual void OpenSubMenu(class USubMenu* submenu) override; + virtual void CloseSubMenu(class USubMenu* submenu) override; + +private: + void m_OpenSubMenu(class USubMenu* submenu, bool transferFocus = true); + void m_CloseSubMenu(class USubMenu* submenu, bool persist = false); + void m_OnFadeOutComplete(UMenuScreenSubMenu* panel, int32 type); + + bool m_sideViewActive; + + UButtonHintBar* m_buttonHintBar; + UOverlay* m_menuContainer; + UOverlay* m_sideViewContainer; + UTextBlock* m_menuTitle; + UTextBlock* m_sideViewTitle; + + UPROPERTY() + TArray m_currentSideViews; + + friend class UMenuScreenSubMenu; +}; + +UCLASS() +class UMainMenuScreen : public UMenuScreen +{ + GENERATED_BODY() +public: +}; \ No newline at end of file diff --git a/Source/UnrealProject/GUI/Menu/MenuScreenBase.cpp b/Source/UnrealProject/GUI/Menu/MenuScreenBase.cpp new file mode 100644 index 0000000..b48e3bb --- /dev/null +++ b/Source/UnrealProject/GUI/Menu/MenuScreenBase.cpp @@ -0,0 +1,122 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "MenuScreenBase.h" +#include "PlayerControllerBase.h" +#include "SubMenu.h" +#include "WidgetBlueprintLibrary.h" + +UMenuScreenBase::UMenuScreenBase(const FObjectInitializer& init) + : Super(init) +{ + registerMenuActions = false; +} +void UMenuScreenBase::NativeConstruct() +{ + Super::NativeConstruct(); + APlayerControllerBase* pcb = Cast(GetWorld()->GetFirstPlayerController()); + if(registerMenuActions && pcb) + { + pcb->AddMenuInputItem(this); + } +} +void UMenuScreenBase::NativeDestruct() +{ + Super::NativeDestruct(); + APlayerControllerBase* pcb = Cast(GetWorld()->GetFirstPlayerController()); + if(registerMenuActions && pcb) + { + pcb->RemoveMenuInputItem(this); + } +} + +void UMenuScreenBase::OpenSubMenu(class USubMenu* submenu) +{ + if(m_subMenus.Contains(submenu)) + { + GWARNING("Submenu already active"); + return; + } + + if(!submenu) + { + GWWARNING(L"Invalid submenu passed to OpenSubMenu"); + return; + } + + submenu->OnEnter(this); + if(m_subMenus.Num() > 0) + { + m_subMenus.Last()->OnSuspend(submenu, true); + } + m_subMenus.Add(submenu); + + UWidgetBlueprintLibrary::SetFocusToGameViewport(); +} +void UMenuScreenBase::CloseActiveSubMenu() +{ + if(m_subMenus.Num() > 0) + { + CloseSubMenu(m_subMenus.Last()); + } + else + { + GWWARNING("No SubMenus to close"); + return; + } +} + +void UMenuScreenBase::CloseSubMenu(class USubMenu* submenu) +{ + if(!m_subMenus.Contains(submenu)) + return; + + submenu->OnLeave(); + + // Restore underlying menus if that was the top-most menu + if(m_subMenus.Num() > 1) + { + bool wasLast = m_subMenus.Last() == submenu; + m_subMenus.Remove(submenu); + if(wasLast) + { + m_subMenus.Last()->OnRestore(submenu); + } + } + else + { + // Remove last menu + m_subMenus.Remove(submenu); + } + + UWidgetBlueprintLibrary::SetFocusToGameViewport(); +} + +TArray UMenuScreenBase::GetSubMenus() +{ + return m_subMenus; +} +class USubMenu* UMenuScreenBase::GetActiveSubMenu() +{ + if(m_subMenus.Num() == 0) + return nullptr; + return m_subMenus.Last(); +} + +void UMenuScreenBase::SetButtonHintBar(class UButtonHintBar* bar) +{ + m_buttonHintBar = bar; +} +class UButtonHintBar* UMenuScreenBase::GetButtonHintBar() const +{ + return m_buttonHintBar; +} + +void UMenuScreenBase::NativeOnMenuAction(EMenuActionBinding action) +{ + if(m_subMenus.Num() > 0) + { + m_subMenus.Last()->NativeOnMenuAction(action); + } + Super::NativeOnMenuAction(action); +} \ No newline at end of file diff --git a/Source/UnrealProject/GUI/Menu/MenuScreenBase.h b/Source/UnrealProject/GUI/Menu/MenuScreenBase.h new file mode 100644 index 0000000..c767c0a --- /dev/null +++ b/Source/UnrealProject/GUI/Menu/MenuScreenBase.h @@ -0,0 +1,52 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "MenuItemBase.h" +#include "Blueprint/UserWidget.h" +#include "MenuScreenBase.generated.h" + +/** + * Base class for a menu item with controller navigation/input support + */ +UCLASS() +class UNREALPROJECT_API UMenuScreenBase : public UMenuItemBase +{ + GENERATED_BODY() + +public: + UMenuScreenBase(const FObjectInitializer& init); + void NativeConstruct() override; + void NativeDestruct() override; + + UFUNCTION(BlueprintCallable, Category = "MenuScreen") + virtual void OpenSubMenu(class USubMenu* submenu); + UFUNCTION(BlueprintCallable, Category = "MenuScreen") + void CloseActiveSubMenu(); + UFUNCTION(BlueprintCallable, Category = "MenuScreen") + virtual void CloseSubMenu(class USubMenu* submenu); + + UFUNCTION(BlueprintCallable, Category = "MenuScreen") + TArray GetSubMenus(); + UFUNCTION(BlueprintCallable, Category = "MenuScreen") + class USubMenu* GetActiveSubMenu(); + + UFUNCTION(BlueprintCallable, Category = "MenuScreen") + void SetButtonHintBar(class UButtonHintBar* bar); + UFUNCTION(BlueprintCallable, Category = "MenuScreen") + class UButtonHintBar* GetButtonHintBar() const; + + void OnItemSelected(); + + virtual void NativeOnMenuAction(EMenuActionBinding action) override; + + UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Category = "MenuScreen") + bool registerMenuActions; + +protected: + UPROPERTY() + TArray m_subMenus; + + UPROPERTY() + class UButtonHintBar* m_buttonHintBar; +}; diff --git a/Source/UnrealProject/GUI/Menu/MenuSlider.cpp b/Source/UnrealProject/GUI/Menu/MenuSlider.cpp new file mode 100644 index 0000000..4fb0a09 --- /dev/null +++ b/Source/UnrealProject/GUI/Menu/MenuSlider.cpp @@ -0,0 +1,139 @@ +#include "UnrealProject.h" +#include "MenuSlider.h" +#include "SizeBorder.h" + +void UMenuSliderButton::NativeConstruct() +{ + m_button = Cast(WidgetTree->FindWidget("Button")); + m_dragBorder = Cast(WidgetTree->FindWidget("DragBorder")); + m_text = Cast(WidgetTree->FindWidget("Text")); + m_button->OnPressed.AddDynamic(this, &UMenuSliderButton::m_OnPressed); + m_panel = Cast(GetParent()); + + m_dragging = false; + + Super::NativeConstruct(); + SetSelected(false); +} +void UMenuSliderButton::NativeTick(const FGeometry& MyGeometry, float InDeltaTime) +{ + UCanvasPanelSlot* canvasSlot = Cast(Slot); + canvasSlot->SetAlignment(FVector2D(0.0f, 0.0f)); + + if(m_parent) + { + float totalPixelWidth = m_parent->m_sizeBorder->pixelSize.X; + float totalWidth = m_parent->m_sizeBorder->viewportSize.X; + float totalHeight = m_parent->m_sizeBorder->viewportSize.Y; + + float buttonSize = totalWidth * 0.2f; + totalPixelWidth -= totalPixelWidth * 0.2f; + totalWidth -= buttonSize; + + if(m_dragging) + { + float mouseX, mouseY; + GetOwningPlayer()->GetMousePosition(mouseX, mouseY); + float delta = (mouseX - m_dragStart.X) / (totalPixelWidth); + m_parent->value = FMath::Clamp(m_dragStartValue + delta, 0.0f, 1.0f); + + if(!m_dragBorder->IsHovered() && !m_button->IsHovered()) + { + m_EndDrag(); + } + UpdateText(); + } + + canvasSlot->SetPosition(FVector2D(m_parent->value * totalWidth, 0.0f)); + canvasSlot->SetSize(FVector2D(buttonSize, totalHeight)); + } + Super::NativeTick(MyGeometry, InDeltaTime); +} + +void UMenuSliderButton::UpdateText() +{ + FString txt = FString::FromInt((int)(m_parent->value * 100)) + "%"; + m_text->SetText(FText::FromString(txt)); +} + +void UMenuSliderButton::SetSelected(bool selected) +{ + if(selected) + { + m_button->SetBackgroundColor(FLinearColor(0.3f, 0.3f, 1.0f, 0.8f)); + } + else + { + m_button->SetBackgroundColor(FLinearColor(0.7f, 0.7f, 0.7f, 0.8f)); + } +} + +void UMenuSliderButton::m_OnPressed() +{ + float mouseX, mouseY; + if(m_parent) + { + GetOwningPlayer()->GetMousePosition(mouseX, mouseY); + m_dragStart = FVector2D(mouseX, mouseY); + m_dragStartValue = m_parent->value; + m_dragging = true; + m_dragBorder->SetVisibility(ESlateVisibility::Visible); + } +} + +void UMenuSliderButton::m_EndDrag() +{ + m_dragging = false; + m_dragBorder->SetVisibility(ESlateVisibility::SelfHitTestInvisible); +} + +FReply UMenuSliderButton::NativeOnMouseButtonUp(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent) +{ + m_EndDrag(); + return FReply::Unhandled(); +} + +UMenuSlider::UMenuSlider(const FObjectInitializer& init) + : Super(init) +{ + stepGranularity = 1.0f / 25; + value = 0.5f; +} + +void UMenuSlider::NativeConstruct() +{ + Super::NativeConstruct(); + + m_sizeBorder = Cast(WidgetTree->FindWidget("SizeBorder")); + m_sliderButton = Cast(WidgetTree->FindWidget("SliderButton")); + m_sliderButton->m_parent = this; +} + +void UMenuSlider::SetValue(float _value) +{ + value = FMath::Clamp(_value, 0.0f, 1.0f); + m_sliderButton->UpdateText(); +} + +void UMenuSlider::NativeOnMenuAction(EMenuActionBinding binding) +{ + switch(binding) + { + case EMenuActionBinding::Left: + case EMenuActionBinding::Repeat_Left: + SetValue(value - stepGranularity); + break; + case EMenuActionBinding::Right: + case EMenuActionBinding::Repeat_Right: + SetValue(value + stepGranularity); + break; + } + Super::NativeOnMenuAction(binding); +} + +void UMenuSlider::NativeOnSelectionChanged(bool selected, bool controller) +{ + Super::NativeOnSelectionChanged(selected, controller); + if(m_sliderButton) + m_sliderButton->SetSelected(selected); +} diff --git a/Source/UnrealProject/GUI/Menu/MenuSlider.h b/Source/UnrealProject/GUI/Menu/MenuSlider.h new file mode 100644 index 0000000..d93ed26 --- /dev/null +++ b/Source/UnrealProject/GUI/Menu/MenuSlider.h @@ -0,0 +1,61 @@ +#pragma once + +#include "MenuButton.h" +#include "MenuSlider.generated.h" + +UCLASS() +class UMenuSliderButton : public UMenuItemBase +{ + GENERATED_BODY() +public: + void NativeConstruct() override ; + void NativeTick(const FGeometry& MyGeometry, float InDeltaTime) override; + + void UpdateText(); + + void SetSelected(bool selected); + +private: + UFUNCTION() + void m_OnPressed(); + void m_EndDrag(); + + FReply NativeOnMouseButtonUp(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent) override; + + friend class UMenuSlider; + class UMenuSlider* m_parent; + UBorder* m_dragBorder; + UButton* m_button; + UTextBlock* m_text; + UCanvasPanel* m_panel; + FVector2D m_dragStart; + float m_dragStartValue; + bool m_dragging; +}; + +UCLASS() +class UMenuSlider : public UMenuButton +{ + GENERATED_BODY() +public: + UMenuSlider(const FObjectInitializer& init); + void NativeConstruct() override; + void NativeOnMenuAction(EMenuActionBinding binding) override; + + void NativeOnSelectionChanged(bool selected, bool controller) override; + + UFUNCTION(BlueprintCallable, Category="Slider") + void SetValue(float value); + + UPROPERTY(BlueprintReadOnly, Category = "Slider") + float value; + + UPROPERTY(EditAnywhere, Category = "Slider") + float stepGranularity; + +private: + friend class UMenuSliderButton; + class USizeBorder* m_sizeBorder; + class UMenuSliderButton* m_sliderButton; + +}; \ No newline at end of file diff --git a/Source/UnrealProject/GUI/Menu/OverlayMessageBox.cpp b/Source/UnrealProject/GUI/Menu/OverlayMessageBox.cpp new file mode 100644 index 0000000..5a4fe39 --- /dev/null +++ b/Source/UnrealProject/GUI/Menu/OverlayMessageBox.cpp @@ -0,0 +1,78 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "OverlayMessageBox.h" +#include "ScreenOverlay.h" + +void UOverlayMessageBoxItem::NativeConstruct() +{ + m_label = Cast(WidgetTree->FindWidget("Label")); + onPressed.AddDynamic(this, &UOverlayMessageBoxItem::m_OnPressed); + Super::NativeConstruct(); +} +void UOverlayMessageBoxItem::SetText(const FString& text) +{ + if(!m_label) + { + GERROR("UOverlayMessageBoxItem Label not set on " + GetName()); + return; + } + m_label->SetText(FText::FromString(text)); +} + +void UOverlayMessageBoxItem::m_OnPressed() +{ + //Cast(GetSubMenu())->returnCode = index; + //UMenuScreenBase* screen = GetSubMenu()->GetScreen(); + //check(m_info); + m_info->Close(index); + //if(screen) + // screen->CloseActiveSubMenu(); + //else + // GERROR("screen == nullptr, should not happen"); +} + +void UOverlayMessageBox::NativeConstruct() +{ + Super::NativeConstruct(); + + m_buttonContainer = Cast(WidgetTree->FindWidget("ButtonContainer")); + m_caption = Cast(WidgetTree->FindWidget("Caption")); + m_message = Cast(WidgetTree->FindWidget("Message")); +} + +void UOverlayMessageBox::SetMessageBoxInfo(class UMsgBoxInfo* info) +{ + if(!buttonItemClass) + { + GERROR("Button item class not set on " + GetName()); + return; + } + + m_buttonContainer->ClearChildren(); + + m_info = info; + + UOverlayMessageBoxItem* selectItem = 0; + for(FString option : info->options) + { + UOverlayMessageBoxItem* item = CreateWidget(GetWorld(), buttonItemClass); + item->m_info = info; + + UHorizontalBoxSlot* slot = Cast(m_buttonContainer->AddChild(item)); + slot->SetHorizontalAlignment(HAlign_Fill); + slot->SetVerticalAlignment(VAlign_Fill); + slot->SetSize(FSlateChildSize(ESlateSizeRule::Fill)); + item->SetButtonText(option); + if(option == info->defaultOption) + selectItem = item; + else if(!selectItem) + selectItem = item; + } + + m_caption->SetText(FText::FromString(info->caption)); + m_message->SetText(FText::FromString(info->message)); + + RescanItems(); + SelectNewItem(selectItem); +} \ No newline at end of file diff --git a/Source/UnrealProject/GUI/Menu/OverlayMessageBox.h b/Source/UnrealProject/GUI/Menu/OverlayMessageBox.h new file mode 100644 index 0000000..87b603b --- /dev/null +++ b/Source/UnrealProject/GUI/Menu/OverlayMessageBox.h @@ -0,0 +1,46 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "SubMenu.h" +#include "MenuButton.h" +#include "OverlayMessageBox.generated.h" + +UCLASS() +class UOverlayMessageBoxItem : public UMenuButton +{ + GENERATED_BODY() +public: + virtual void NativeConstruct() override; + void SetText(const FString& text); +private: + void m_OnPressed(); + + friend class UOverlayMessageBox; + UTextBlock* m_label; + class UMsgBoxInfo* m_info; +}; + +UCLASS() +class UOverlayMessageBox : public USubMenu +{ + GENERATED_BODY() +public: + virtual void NativeConstruct() override; + + void SetMessageBoxInfo(class UMsgBoxInfo* info); + + UPROPERTY(BlueprintReadWrite, Category = "MsgBox") + int32 returnCode; + UPROPERTY(EditDefaultsOnly, Category = "MsgBox") + TSubclassOf buttonItemClass; + +private: + UPROPERTY() + UHorizontalBox* m_buttonContainer; + UPROPERTY() + UTextBlock* m_caption; + UPROPERTY() + UTextBlock* m_message; + class UMsgBoxInfo* m_info; +}; diff --git a/Source/UnrealProject/GUI/Menu/OverlayProgressBar.cpp b/Source/UnrealProject/GUI/Menu/OverlayProgressBar.cpp new file mode 100644 index 0000000..1d56f9c --- /dev/null +++ b/Source/UnrealProject/GUI/Menu/OverlayProgressBar.cpp @@ -0,0 +1,24 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "OverlayProgressBar.h" +#include "ScreenOverlay.h" + +void UOverlayProgressBar::NativeConstruct() +{ + m_text = Cast(WidgetTree->FindWidget("Text")); + if(!m_text) + { + GERROR("No \"Text\" element in overlay progress bar"); + } + + Super::NativeConstruct(); +} + +void UOverlayProgressBar::SetOverlayInfo(class UOverlayInfo* info) +{ + if(m_text) + { + m_text->SetText(FText::FromString(info->message)); + } +} diff --git a/Source/UnrealProject/GUI/Menu/OverlayProgressBar.h b/Source/UnrealProject/GUI/Menu/OverlayProgressBar.h new file mode 100644 index 0000000..3a24cfc --- /dev/null +++ b/Source/UnrealProject/GUI/Menu/OverlayProgressBar.h @@ -0,0 +1,23 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "GUI/Menu/SubMenu.h" +#include "OverlayProgressBar.generated.h" + +/** + * + */ +UCLASS() +class UNREALPROJECT_API UOverlayProgressBar : public USubMenu +{ + GENERATED_BODY() +public: + virtual void NativeConstruct(); + + // Updates the text,etc. on the progress bar overlay + void SetOverlayInfo(class UOverlayInfo* info); + +private: + UTextBlock* m_text; +}; diff --git a/Source/UnrealProject/GUI/Menu/ScoreBoard.cpp b/Source/UnrealProject/GUI/Menu/ScoreBoard.cpp new file mode 100644 index 0000000..ce3181f --- /dev/null +++ b/Source/UnrealProject/GUI/Menu/ScoreBoard.cpp @@ -0,0 +1,78 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "ScoreBoardSlot.h" +#include "DefaultPlayerState.h" +#include "GameStateBase.h" +#include "TeamData.h" +#include "ScoreBoard.h" + + +UScoreBoard::UScoreBoard(const FObjectInitializer& init) : Super(init) +{ + teamData = ConstructorHelpers::FObjectFinder(TEXT("/Game/Assets/TeamData")).Object; +} + +void UScoreBoard::Init(UVerticalBox* container) +{ + m_container = container; + + // Precreate the widgets so that we can utilize them later + if (IsValid(scoreBoardSlot) && IsValid(m_container)) + { + for (int32 i = 0; i < 8; i++) + { + UScoreBoardSlot* widget = CreateWidget(GetWorld(), scoreBoardSlot); + m_container->AddChild(widget); + m_slots.Add(widget); + widget->SetVisibility(ESlateVisibility::Hidden); + } + } +} + +void UScoreBoard::UpdateScoreBoard(int32 sort) +{ + UWorld* world = GetWorld(); + if (!IsValid(world)) return; + AGameStateBase* gameState = Cast(world->GetGameState()); + if (!IsValid(gameState)) return; + + TArray players = gameState->GetPlayers(); + + // Fetch and update + TArray playersSorted; + for (int32 i = 0; i < players.Num(); i++) + { + ADefaultPlayerState* const state = Cast(players[i]); + if (!IsValid(state)) continue; + state->UpdatePersona(); + playersSorted.Add(state); + } + + // Sort based on category + if(sort == 0) + playersSorted.Sort([](const ADefaultPlayerState& i, const ADefaultPlayerState& j)->bool { return i.nickname < j.nickname; }); + else if(sort == 1) + playersSorted.Sort([](const ADefaultPlayerState& i, const ADefaultPlayerState& j)->bool { return i.kills > j.kills; }); + else if (sort == 2) + playersSorted.Sort([](const ADefaultPlayerState& i, const ADefaultPlayerState& j)->bool { return i.deaths > j.deaths; }); + else if (sort == 3) + playersSorted.Sort([](const ADefaultPlayerState& i, const ADefaultPlayerState& j)->bool { return float(i.kills) / float(i.deaths) > float(j.kills) / float(j.deaths); }); + + // Activate the slots and set them to the person + for (int32 i = 0; i < m_slots.Num(); i++) + { + if (i >= playersSorted.Num()) + { + m_slots[i]->SetVisibility(ESlateVisibility::Hidden); + continue; + } + else + m_slots[i]->SetVisibility(ESlateVisibility::Visible); + + ADefaultPlayerState* const state = playersSorted[i]; + + m_slots[i]->SetVisibility(ESlateVisibility::Visible); + m_slots[i]->Update(state->avatar, FText::FromString(state->nickname), state->kills, state->deaths, teamData->GetTeamColor(state->GetTeam())); + } +} \ No newline at end of file diff --git a/Source/UnrealProject/GUI/Menu/ScoreBoard.h b/Source/UnrealProject/GUI/Menu/ScoreBoard.h new file mode 100644 index 0000000..a038d97 --- /dev/null +++ b/Source/UnrealProject/GUI/Menu/ScoreBoard.h @@ -0,0 +1,34 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "Blueprint/UserWidget.h" +#include "ScoreBoard.generated.h" + +/** + * + */ +UCLASS() +class UNREALPROJECT_API UScoreBoard : public UUserWidget +{ + GENERATED_BODY() + + +public: + UScoreBoard(const FObjectInitializer& init); + + UPROPERTY(EditAnywhere, Category = "Score Board") + TSubclassOf scoreBoardSlot; + + UFUNCTION(BlueprintCallable, Category = "Score Board") + void Init(class UVerticalBox* container); + + UFUNCTION(BlueprintCallable, Category = "Score Board") + void UpdateScoreBoard(int32 sort); + + UPROPERTY(BlueprintReadonly, Category = "SCore Board") + class UTeamData* teamData; +private: + class UVerticalBox* m_container; + TArray m_slots; +}; diff --git a/Source/UnrealProject/GUI/Menu/ScoreBoardSlot.cpp b/Source/UnrealProject/GUI/Menu/ScoreBoardSlot.cpp new file mode 100644 index 0000000..86c0db5 --- /dev/null +++ b/Source/UnrealProject/GUI/Menu/ScoreBoardSlot.cpp @@ -0,0 +1,4 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "ScoreBoardSlot.h" diff --git a/Source/UnrealProject/GUI/Menu/ScoreBoardSlot.h b/Source/UnrealProject/GUI/Menu/ScoreBoardSlot.h new file mode 100644 index 0000000..8a4622e --- /dev/null +++ b/Source/UnrealProject/GUI/Menu/ScoreBoardSlot.h @@ -0,0 +1,19 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "Blueprint/UserWidget.h" +#include "ScoreBoardSlot.generated.h" + +/** + * + */ +UCLASS() +class UNREALPROJECT_API UScoreBoardSlot : public UUserWidget +{ + GENERATED_BODY() + +public: + UFUNCTION(BlueprintImplementableEvent, Category = "Score Board Slot") + void Update(UTexture2D* image, const FText& name, int32 kills, int32 deaths, const FLinearColor& color); +}; diff --git a/Source/UnrealProject/GUI/Menu/ScreenOverlay.cpp b/Source/UnrealProject/GUI/Menu/ScreenOverlay.cpp new file mode 100644 index 0000000..f9f32ae --- /dev/null +++ b/Source/UnrealProject/GUI/Menu/ScreenOverlay.cpp @@ -0,0 +1,195 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "ScreenOverlay.h" +#include "OverlayMessageBox.h" +#include "OverlayProgressBar.h" + +UScreenOverlay::UScreenOverlay(const FObjectInitializer& init) + : Super(init) +{ +} +void UScreenOverlay::NativeConstruct() +{ + Super::NativeConstruct(); + m_msgBox = Cast(WidgetTree->FindWidget("MessageBox")); + m_progressBar = Cast(WidgetTree->FindWidget("ProgressBar")); + m_background = Cast(WidgetTree->FindWidget("Background")); + check(m_msgBox); + check(m_progressBar); + check(m_background); + UpdateOverlays(); + UpdateMsgBoxes(); + UpdateVisiblity(); + m_msgBox->onClosed.AddDynamic(this, &UScreenOverlay::OnMessageBoxClosed); +} +void UScreenOverlay::NativeTick(const FGeometry& MyGeometry, float InDeltaTime) +{ + Super::NativeTick(MyGeometry, InDeltaTime); + + // Check all active screen overlays, remove the inactive ones + for(int32 i = 0; i < m_overlayItems.Num();) + { + if(m_overlayItems[i]->m_remove) + { + m_overlayItems.RemoveAt(i); + UpdateOverlays(); + UpdateVisiblity(); + } + else + { + i++; + } + } + + // Check the currently shown message box, remove it if inactive + UMsgBoxInfo* msgBox = GetMessageBoxItem(); + if(msgBox && msgBox->m_remove) + { + if(msgBox->onClosed.IsBound()) + { + msgBox->onClosed.Execute(msgBox->m_closeResult); + } + m_messageBoxItems.Remove(msgBox); + UpdateMsgBoxes(); + UpdateVisiblity(); + + // Show remaining message boxes + if(m_messageBoxItems.Num() > 0) + { + OpenSubMenu(m_msgBox); + } + } +} + +UOverlayInfo* UScreenOverlay::ShowOverlay(FString message) +{ + // Add a new overlay item to the list + UOverlayInfo* info = NewObject(); + info->message = message; + m_overlayItems.Add(info); + + // Update Widget blueprint + UpdateOverlays(); + UpdateVisiblity(); + return info; +} + +UMsgBoxInfo* UScreenOverlay::ShowMessageBox(FString caption, FString message, TArray options, FString def /*= FString()*/) +{ + // Add a new message box at the end of the queue + UMsgBoxInfo* info = NewObject(); + info->message = message; + info->caption = caption; + info->options = options; + info->defaultOption = def; + m_messageBoxItems.Add(info); + + // Update Widget blueprint + UpdateMsgBoxes(); + UpdateVisiblity(); + + OpenSubMenu(m_msgBox); + + return info; +} +UMsgBoxInfo* UScreenOverlay::ShowMessageBoxCallback(FOverlayItemClosed cb, FString caption, FString message, TArray options, FString def /*= ""*/) +{ + // Add a new message box at the end of the queue + UMsgBoxInfo* info = NewObject(); + info->message = message; + info->caption = caption; + info->options = options; + info->onClosed = cb; + info->defaultOption = def; + m_messageBoxItems.Add(info); + + // Update Widget blueprint + UpdateMsgBoxes(); + UpdateVisiblity(); + + OpenSubMenu(m_msgBox); + + return info; +} + +void UScreenOverlay::OnMessageBoxClosed() +{ + m_messageBoxItems.Last()->Close(m_msgBox->returnCode); +} + +TArray& UScreenOverlay::GetOverlayItems() +{ + return m_overlayItems; +} + +UMsgBoxInfo* UScreenOverlay::GetMessageBoxItem() +{ + if(m_messageBoxItems.Num() == 0) + { + return nullptr; + } + return m_messageBoxItems[0]; +} +void UScreenOverlay::UpdateOverlays() +{ + if(m_overlayItems.Num() != 0) + { + m_progressBar->SetVisibility(ESlateVisibility::Visible); + m_progressBar->SetOverlayInfo(m_overlayItems.Last()); + } + else + { + m_progressBar->SetVisibility(ESlateVisibility::Hidden); + } +} +void UScreenOverlay::UpdateMsgBoxes() +{ + if(m_messageBoxItems.Num() != 0) + { + m_msgBox->SetMessageBoxInfo(GetMessageBoxItem()); + m_msgBox->SetVisibility(ESlateVisibility::Visible); + } + else + { + m_msgBox->SetVisibility(ESlateVisibility::Hidden); + } +} +void UScreenOverlay::UpdateVisiblity() +{ + if(m_messageBoxItems.Num() != 0 || m_overlayItems.Num() != 0) + { + m_background->SetVisibility(ESlateVisibility::Visible); + blockInput = true; + } + else + { + m_background->SetVisibility(ESlateVisibility::Hidden); + blockInput = false; + } +} + +UOverlayQueueItem::UOverlayQueueItem(const FObjectInitializer& init) + : Super(init) +{ + m_remove = false; +} +void UOverlayQueueItem::Close(int32 result) +{ + m_remove = true; + m_closeResult = result; +} + +UMsgBoxInfo::UMsgBoxInfo(const FObjectInitializer& init) + : Super(init) +{ + message = "nothing"; + options.Add("OK"); +} + +UOverlayInfo::UOverlayInfo(const FObjectInitializer& init) + : Super(init) +{ + blockInput = true; + message = "nothing"; +} \ No newline at end of file diff --git a/Source/UnrealProject/GUI/Menu/ScreenOverlay.h b/Source/UnrealProject/GUI/Menu/ScreenOverlay.h new file mode 100644 index 0000000..fe96324 --- /dev/null +++ b/Source/UnrealProject/GUI/Menu/ScreenOverlay.h @@ -0,0 +1,104 @@ +// Project Lab - NHTV Igad + +#pragma once +#include "MenuScreenBase.h" +#include "ScreenOverlay.generated.h" + +UENUM(BlueprintType) +enum class EMessageBoxType : uint8 +{ + Ok, + OkCancel, + YesNo +}; + +UCLASS() +class UOverlayQueueItem : public UObject +{ + GENERATED_BODY() +public: + UOverlayQueueItem(const FObjectInitializer& init); + // Close this overlay item + UFUNCTION(BlueprintCallable, Category = "Overlay") + void Close(int32 result = 0); + + UPROPERTY() + FOverlayItemClosed onClosed; + +private: + friend class UScreenOverlay; + bool m_remove; + int32 m_closeResult; +}; + +UCLASS() +class UMsgBoxInfo : public UOverlayQueueItem +{ + GENERATED_BODY() +public: + UMsgBoxInfo(const FObjectInitializer& init); + UPROPERTY(BlueprintReadOnly, Category = "MsgBox") + FString caption; + UPROPERTY(BlueprintReadOnly, Category = "MsgBox") + FString message; + UPROPERTY(BlueprintReadOnly, Category = "MsgBox") + TArray options; + UPROPERTY(BlueprintReadOnly, Category = "MsgBox") + FString defaultOption; + +}; +UCLASS() +class UOverlayInfo : public UOverlayQueueItem +{ + GENERATED_BODY() +public: + UOverlayInfo(const FObjectInitializer& init); + UPROPERTY(BlueprintReadOnly, Category = "MsgBox") + FString message; + UPROPERTY(BlueprintReadOnly, Category = "Overlay") + bool blockInput; + +}; + +UCLASS() +class UNREALPROJECT_API UScreenOverlay : public UMenuScreenBase +{ + GENERATED_BODY() +public: + UScreenOverlay(const FObjectInitializer& init); + virtual void NativeConstruct() override; + virtual void NativeTick(const FGeometry& MyGeometry, float InDeltaTime) override; + + UFUNCTION(BlueprintCallable, Category = "Overlay") + UOverlayInfo* ShowOverlay(FString message); + UMsgBoxInfo* ShowMessageBox(FString caption, FString message, TArray options, FString def = ""); + UMsgBoxInfo* ShowMessageBoxCallback(FOverlayItemClosed cb, FString caption, FString message, TArray options, FString def); + //UFUNCTION(BlueprintCallable, Category = "Overlay") + //UMsgBoxInfo* ShowMessageBox(FOnOverlayItemClosedSingle cb, FString caption, FString message, TArray options, FString def = ""); + + UFUNCTION() + void OnMessageBoxClosed(); + + UFUNCTION(BlueprintCallable, Category = "Overlay") + TArray& GetOverlayItems(); + UFUNCTION(BlueprintCallable, Category = "Overlay") + UMsgBoxInfo* GetMessageBoxItem(); + +private: + void UpdateOverlays(); + void UpdateMsgBoxes(); + void UpdateVisiblity(); + + UPROPERTY() + class UOverlayMessageBox* m_msgBox; + UPROPERTY() + class UOverlayProgressBar* m_progressBar; + UPROPERTY() + class UBorder* m_background; + + UPROPERTY() + TArray m_overlayItems; + UPROPERTY() + TArray m_messageBoxItems; + +}; diff --git a/Source/UnrealProject/GUI/Menu/SelectButton.cpp b/Source/UnrealProject/GUI/Menu/SelectButton.cpp new file mode 100644 index 0000000..d07bea2 --- /dev/null +++ b/Source/UnrealProject/GUI/Menu/SelectButton.cpp @@ -0,0 +1,10 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "SelectButton.h" + + +USelectButton::USelectButton(const FObjectInitializer& init) : UMenuButton(init) +{ + selected = 0; +} \ No newline at end of file diff --git a/Source/UnrealProject/GUI/Menu/SelectButton.h b/Source/UnrealProject/GUI/Menu/SelectButton.h new file mode 100644 index 0000000..6f508e3 --- /dev/null +++ b/Source/UnrealProject/GUI/Menu/SelectButton.h @@ -0,0 +1,22 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "MenuButton.h" +#include "SelectButton.generated.h" + +/** + * + */ +UCLASS() +class UNREALPROJECT_API USelectButton : public UMenuButton +{ + GENERATED_BODY() + +public: + USelectButton(const FObjectInitializer& init); + + UPROPERTY(BlueprintReadWrite, Category = "SelectButton") + int32 selected; + +}; diff --git a/Source/UnrealProject/GUI/Menu/SkillSelector.cpp b/Source/UnrealProject/GUI/Menu/SkillSelector.cpp new file mode 100644 index 0000000..dd59a3b --- /dev/null +++ b/Source/UnrealProject/GUI/Menu/SkillSelector.cpp @@ -0,0 +1,130 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "SkillSelector.h" +#include "SkillTreeObject.h" +#include "SkillObject.h" +#include "SkillWidget.h" +#include "SkillSelectorItem.h" +#include "SkillTreeWidget.h" +#include "BaseSkillObject.h" +#include "AbilityInfo.h" + +TSubclassOf defaultSkillSelector; + +USkillSelector::USkillSelector(const FObjectInitializer& init) + : Super(init) +{ + defaultSkillSelector = ConstructorHelpers::FClassFinder(L"/Game/Assets/GUI/Components/WEEGEE_SkillSelector").Class; +} +void USkillSelector::NativeConstruct() +{ + Super::NativeConstruct(); + + m_selectedItem = 0; + + TArray swidgets; + m_scrollBox = WidgetTree->FindWidget("ScrollBox"); + m_border = WidgetTree->FindWidget("Border"); + m_sizeRoot = WidgetTree->FindWidget("SizeRoot"); + if(!m_sizeRoot) + { + GWERROR(L"USkillSelector doesn't have a SizeRoot set"); + } + if(!m_scrollBox) + { + GWERROR(L"USkillSelector doesn't have a ScrollBox set"); + } + if(!ItemWidgetTemplate) + { + GWERROR(L"USkillSelector doesn't have a Item Widget Template set"); + return; + } +} + +void USkillSelector::NativeOnMenuAction(EMenuActionBinding binding) +{ + switch(binding) + { + case EMenuActionBinding::Left: + SetSelection(m_selectedItem - 1); + break; + case EMenuActionBinding::Right: + SetSelection(m_selectedItem + 1); + break; + default: + Super::NativeOnMenuAction(binding); + break; + } +} + +void USkillSelector::SetSelection(int32 idx) +{ + if(idx < 0 || idx >= m_scrollBox->GetChildrenCount()) + return; + + if(m_selectedItem < m_scrollBox->GetChildrenCount()) + SetItemSelected(m_selectedItem, false); + SetItemSelected(idx, true); + + m_selectedItem = idx; + + if(m_sizeRoot && m_scrollBox) + { + m_scrollBox->ScrollWidgetIntoView(m_items[m_selectedItem], true); + } +} + +void USkillSelector::SetItemSelected(int32 item, bool selected) +{ + m_items[item]->NativeOnSelectionChanged(selected, false); + if(selected) + onSkillSelectionChanged.Broadcast(m_items[item]); +} + +void USkillSelector::SetSkillTree(class USkillTreeWidget* skillTree) +{ + if(!skillTree) + return; + this->skillTree = skillTree; + USkillTreeObject* treeObject = skillTree->skillTreeAsset; + if(!m_scrollBox) + return; + if(!ItemWidgetTemplate) + return; + + m_scrollBox->ClearChildren(); + m_items.SetNum(0); + + uint32 itemIndex = 0; + for(int32 i = 0; i < treeObject->skills.Num(); i++) + { + UBaseSkillObject* skill = treeObject->skills[i]->GetDefaultObject(); + if(!skillShapeFilter.Contains(skill->skillShapeType)) + continue; + + USkillSelectorItem* widget = CreateWidget(GetWorld(), ItemWidgetTemplate); + widget->baseSkillObject = skill; + widget->index = itemIndex++; + widget->parent = this; + widget->skillTree = skillTree; + m_scrollBox->AddChild(widget); + m_items.Add(widget); + } + + SetSelection(FMath::Min(m_selectedItem, m_items.Num() - 1)); +} + +USkillWidget* USkillSelector::GetSelectedSkillWidget() +{ + if(m_selectedItem >= m_items.Num()) + return nullptr; + return m_items[m_selectedItem]->GetSkillWidget(); +} + +UBaseSkillObject* USkillSelector::GetSelectedSkillAsset() +{ + if(m_selectedItem >= m_items.Num()) + return nullptr; + return m_items[m_selectedItem]->baseSkillObject; +} \ No newline at end of file diff --git a/Source/UnrealProject/GUI/Menu/SkillSelector.h b/Source/UnrealProject/GUI/Menu/SkillSelector.h new file mode 100644 index 0000000..226a7df --- /dev/null +++ b/Source/UnrealProject/GUI/Menu/SkillSelector.h @@ -0,0 +1,54 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "GUI/Menu/MenuButton.h" +#include "BaseSkillObject.h" +#include "SkillSelector.generated.h" + +extern TSubclassOf defaultSkillSelector; + +UCLASS() +class UNREALPROJECT_API USkillSelector : public UMenuButton +{ + GENERATED_BODY() + +public: + USkillSelector(const FObjectInitializer& init); + void NativeConstruct() override; + virtual void NativeOnMenuAction(EMenuActionBinding binding) override; + + UFUNCTION(BlueprintCallable, Category="Skill Selector") + void SetSkillTree(class USkillTreeWidget* skillTree); + + UPROPERTY(BlueprintReadOnly, Category = "UI") + class USkillTreeWidget* skillTree; + + UPROPERTY(EditAnywhere, Category = "UI") + TArray skillShapeFilter; + + int32 GetSelection() const { return m_selectedItem; } + void SetSelection(int32 idx); + void SetItemSelected(int32 item, bool selected); + + DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnSkillSelectionChanged, USkillSelectorItem*, item); + UPROPERTY(BlueprintAssignable) + FOnSkillSelectionChanged onSkillSelectionChanged; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "UI") + TSubclassOf ItemWidgetTemplate; + + UFUNCTION(BlueprintCallable, Category = UI) + class USkillWidget* GetSelectedSkillWidget(); + + UFUNCTION(BlueprintCallable, Category = UI) + class UBaseSkillObject* GetSelectedSkillAsset(); +private: + UPROPERTY() + TArray m_items; + int32 m_selectedItem; + + USizeBox* m_sizeRoot; + UScrollBox* m_scrollBox; + UBorder* m_border; +}; diff --git a/Source/UnrealProject/GUI/Menu/SkillSelectorItem.cpp b/Source/UnrealProject/GUI/Menu/SkillSelectorItem.cpp new file mode 100644 index 0000000..5b1c40d --- /dev/null +++ b/Source/UnrealProject/GUI/Menu/SkillSelectorItem.cpp @@ -0,0 +1,76 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "SkillSelectorItem.h" +#include "SkillSelector.h" +#include "SkillWidget.h" +#include "SkillTreeWidget.h" + +void USkillSelectorItem::NativeConstruct() +{ + Super::NativeConstruct(); + + m_skillWidget = WidgetTree->FindWidget("InnerSkillWidget"); + if(!m_skillWidget) + { + GWERROR(L"InnerSkillWidget not found on " + GetName()); + return; + } + else + { + m_skillWidget->skillAsset = baseSkillObject; + m_skillWidget->parent = skillTree; + m_skillWidget->UpdateSkill(); + } + + if(m_button) + { + m_button->OnPressed.AddDynamic(this, &USkillSelectorItem::m_OnPressed1); + } + else + { + GWERROR(L"No button found on " + GetName()); + } +} + +void USkillSelectorItem::NativeTick(const FGeometry& MyGeometry, float InDeltaTime) +{ + if(skillTree->IsUsingSkill(baseSkillObject)) + { + this->SetIsEnabled(false); + } + else + { + this->SetIsEnabled(true); + } +} + +void USkillSelectorItem::NativeOnSelectionChanged(bool selected, bool controller) +{ + if(selected) + { + m_button->SetBackgroundColor(FLinearColor(1.0f, 1.0f, 1.0f, 0.5f)); + } + else + { + m_button->SetBackgroundColor(FLinearColor(1.0f, 1.0f, 1.0f, 0.0f)); + } +} + +void USkillSelectorItem::m_OnPressed1() +{ + if(!GetIsEnabled()) + return; + + if(parent) + { + USubMenu* menu = parent->GetSubMenu(); + if(menu) + menu->SelectNewItem(parent); + + //if(parent->GetSelection() == index) + skillTree->NewSkill(m_skillWidget); + parent->SetSelection(index); + //else + } +} diff --git a/Source/UnrealProject/GUI/Menu/SkillSelectorItem.h b/Source/UnrealProject/GUI/Menu/SkillSelectorItem.h new file mode 100644 index 0000000..04a74f5 --- /dev/null +++ b/Source/UnrealProject/GUI/Menu/SkillSelectorItem.h @@ -0,0 +1,50 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "MenuButton.h" +#include "SkillSelectorItem.generated.h" + +/** + * + */ +UCLASS() +class UNREALPROJECT_API USkillSelectorItem : public UMenuButton +{ + GENERATED_BODY() + +public: + virtual void NativeConstruct() override; + virtual void NativeTick(const FGeometry& MyGeometry, float InDeltaTime) override; + + virtual void NativeOnSelectionChanged(bool selected, bool controller) override; + //virtual FReply NativeOnMouseButtonDown(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent) override; + + int32 index; + class USkillSelector* parent; + class UBaseSkillObject* baseSkillObject; + + UPROPERTY(BlueprintReadOnly, Category = UI) + class USkillTreeWidget* skillTree; + + UPROPERTY(EditDefaultsOnly, Category = UI) + TSubclassOf skillWidget; + + UFUNCTION(BlueprintCallable, Category = UI) + USkillSelector* GetSelector() const + { + return parent; + } + + UFUNCTION(BlueprintCallable, Category = UI) + class USkillWidget* GetSkillWidget() + { + return m_skillWidget; + } + +private: + UFUNCTION() + void m_OnPressed1(); + + class USkillWidget* m_skillWidget; +}; diff --git a/Source/UnrealProject/GUI/Menu/SplashScreen.cpp b/Source/UnrealProject/GUI/Menu/SplashScreen.cpp new file mode 100644 index 0000000..9f46290 --- /dev/null +++ b/Source/UnrealProject/GUI/Menu/SplashScreen.cpp @@ -0,0 +1,96 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "SplashScreen.h" +#include "SplashScreenItem.h" +#include "DefaultGameInstance.h" +#include "SessionManager.h" + +USplashScreen::USplashScreen(const FObjectInitializer& init) + : Super(init) +{ +} + +void USplashScreen::NativeConstruct() +{ + m_currentItem = -1; + Super::NativeConstruct(); +} +void USplashScreen::NativeTick(const FGeometry& MyGeometry, float InDeltaTime) +{ + Super::NativeTick(MyGeometry, InDeltaTime); + if(m_currentItem == -1) + return; + + if(m_currentItem >= m_activeScreens.Num()) + { + OnEnd(); + // Send done event + onDone.Broadcast(); + m_currentItem = -1; + } + else + { + m_switcher->SetActiveWidget(m_activeScreens[m_currentItem]); + if(m_activeScreens[m_currentItem]->GetRate() >= 1.0f) + { + m_activeScreens[m_currentItem]->End(); + m_currentItem++; + if(m_currentItem < m_activeScreens.Num()) + { + m_activeScreens[m_currentItem]->Start(); + } + } + } +} + +void USplashScreen::NativeOnMenuAction(EMenuActionBinding binding) +{ + if(binding == EMenuActionBinding::Start) + { + if(m_currentItem >= 0 && m_currentItem < m_activeScreens.Num()) + { + m_activeScreens[m_currentItem]->Skip(); + } + } +} + +void USplashScreen::OnEnd_Implementation() +{ + +} + +void USplashScreen::Init(UWidgetSwitcher* witcher) +{ + m_switcher = witcher; + m_currentItem = 0; + + for(int32 i = 0; i < splashItems.Num(); i++) + { + USplashScreenItem* item = CreateWidget(GetWorld(), splashItems[i]); + m_activeScreens.Add(item); + m_switcher->AddChild(item); + if(i == 0) + { + item->Start(); + if((i + 1) == splashItems.Num()) + item->fadeEnabled = false; + m_switcher->SetActiveWidget(item); + } + } +} + +float USplashScreen::GetFade() const +{ + if(m_currentItem == -1) + return 0.0f; + if(m_currentItem >= m_activeScreens.Num()) + { + return 0.0f; + } + if((m_currentItem + 1) == m_activeScreens.Num()) + { + return m_activeScreens[m_currentItem]->GetFadeOut(); + } + return 1.0f; +} diff --git a/Source/UnrealProject/GUI/Menu/SplashScreen.h b/Source/UnrealProject/GUI/Menu/SplashScreen.h new file mode 100644 index 0000000..f7057f7 --- /dev/null +++ b/Source/UnrealProject/GUI/Menu/SplashScreen.h @@ -0,0 +1,45 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "MenuScreenBase.h" +#include "SplashScreen.generated.h" + +/** + * + */ +UCLASS() +class UNREALPROJECT_API USplashScreen : public UMenuScreenBase +{ + GENERATED_BODY() + +public: + USplashScreen(const FObjectInitializer& init); + + virtual void NativeConstruct() override; + virtual void NativeTick(const FGeometry& MyGeometry, float InDeltaTime) override; + + virtual void NativeOnMenuAction(EMenuActionBinding binding) override; + + UFUNCTION(BlueprintNativeEvent, Category = "Splash") + void OnEnd(); + + UFUNCTION(BlueprintCallable, Category="Splash") + void Init(UWidgetSwitcher* witcher); + + UFUNCTION(BlueprintCallable, Category="Splash") + float GetFade() const; + + UPROPERTY(BlueprintReadWrite, EditDefaultsOnly, Category="Splash") + TArray> splashItems; + + DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnSplashScreenDone); + UPROPERTY(BlueprintAssignable, Category = "Splash") + FOnSplashScreenDone onDone; + +public: + UPROPERTY() + TArray m_activeScreens; + int32 m_currentItem; + UWidgetSwitcher* m_switcher; +}; diff --git a/Source/UnrealProject/GUI/Menu/SplashScreenItem.cpp b/Source/UnrealProject/GUI/Menu/SplashScreenItem.cpp new file mode 100644 index 0000000..88a1c53 --- /dev/null +++ b/Source/UnrealProject/GUI/Menu/SplashScreenItem.cpp @@ -0,0 +1,104 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "SplashScreenItem.h" +#include "MediaTexture.h" + +USplashScreenItem::USplashScreenItem(const FObjectInitializer& init) + : Super(init) +{ + duration = 2.0f; + fadeDuration = 0.5f; + m_lifetime = 0.0f; + fadeEnabled = true; +} + +void USplashScreenItem::NativeConstruct() +{ + if(duration <= 0.0f) + { + GWWARNING(L"Splash screen duration is zero! " + GetName()); + duration = 1.0f; + } + if(fadeDuration * 2.0f > duration) + { + GWWARNING(L"Splash screen fade time is greater than duration! " + GetName()); + fadeDuration = duration * 0.25f; + } + + m_started = false; + Super::NativeConstruct(); +} +void USplashScreenItem::NativeTick(const FGeometry& MyGeometry, float InDeltaTime) +{ + if(m_started) + m_lifetime += InDeltaTime; + Super::NativeTick(MyGeometry, InDeltaTime); +} + +void USplashScreenItem::OnStarted_Implementation() +{ + +} +void USplashScreenItem::OnEnd_Implementation() +{ + +} +void USplashScreenItem::Skip() +{ + if((m_lifetime + fadeDuration) < duration) + { + m_lifetime = duration - fadeDuration; + } +} + +void USplashScreenItem::Start() +{ + if(!m_started) + { + m_started = true; + m_lifetime = 0.0f; + OnStarted(); + } +} +void USplashScreenItem::End() +{ + if(m_started) + { + OnEnd(); + m_started = false; + } +} + +FVector2D USplashScreenItem::GetMediaSize(UMediaTexture* mediaTexture) const +{ + if(!mediaTexture) + return FVector2D(); + return FVector2D(mediaTexture->GetSurfaceWidth(), mediaTexture->GetSurfaceHeight()); +} + +float USplashScreenItem::GetFade() const +{ + float base = GetFadeOut(); + if(m_lifetime < fadeDuration) + { + base *= m_lifetime / fadeDuration; + } + return base; +} +float USplashScreenItem::GetFadeOut() const +{ + if((duration - m_lifetime) < fadeDuration) + { + return FMath::Min((duration - m_lifetime) / fadeDuration, 1.0f); + } + else + { + return 1.0f; + } +} + +float USplashScreenItem::GetRate() const +{ + return FMath::Clamp(m_lifetime / duration, 0.0f, 1.0f); +} diff --git a/Source/UnrealProject/GUI/Menu/SplashScreenItem.h b/Source/UnrealProject/GUI/Menu/SplashScreenItem.h new file mode 100644 index 0000000..58d1cf1 --- /dev/null +++ b/Source/UnrealProject/GUI/Menu/SplashScreenItem.h @@ -0,0 +1,52 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "MenuItemBase.h" +#include "SplashScreenItem.generated.h" + +/** + * + */ +UCLASS() +class UNREALPROJECT_API USplashScreenItem : public UMenuItemBase +{ + GENERATED_BODY() + +public: + USplashScreenItem(const FObjectInitializer& init); + + virtual void NativeConstruct() override; + virtual void NativeTick(const FGeometry& MyGeometry, float InDeltaTime) override; + + UFUNCTION(BlueprintNativeEvent, Category = "SplashItem") + void OnStarted(); + UFUNCTION(BlueprintNativeEvent, Category = "SplashItem") + void OnEnd(); + UFUNCTION(BlueprintCallable, Category = "SplashItem") + void Skip(); + + void Start(); + void End(); + + UFUNCTION(BlueprintCallable, Category="Media") + FVector2D GetMediaSize(class UMediaTexture* mediaTexture) const; + + UFUNCTION(BlueprintCallable, Category="SplashItem") + float GetFade() const; + UFUNCTION(BlueprintCallable, Category = "SplashItem") + float GetFadeOut() const; + UFUNCTION(BlueprintCallable, Category="SplashItem") + float GetRate() const; + + UPROPERTY(BlueprintReadWrite, EditDefaultsOnly, Category = "SplashItem") + float fadeDuration; + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="SplashItem") + float duration; + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "SplashItem") + bool fadeEnabled; + +private: + bool m_started; + float m_lifetime; +}; diff --git a/Source/UnrealProject/GUI/Menu/StartPromptScreen.cpp b/Source/UnrealProject/GUI/Menu/StartPromptScreen.cpp new file mode 100644 index 0000000..1d5c3a6 --- /dev/null +++ b/Source/UnrealProject/GUI/Menu/StartPromptScreen.cpp @@ -0,0 +1,10 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "StartPromptScreen.h" + +void UStartPromptScreen::Close() +{ + onClosed.Broadcast(); + OnClose(); +} diff --git a/Source/UnrealProject/GUI/Menu/StartPromptScreen.h b/Source/UnrealProject/GUI/Menu/StartPromptScreen.h new file mode 100644 index 0000000..4d300a7 --- /dev/null +++ b/Source/UnrealProject/GUI/Menu/StartPromptScreen.h @@ -0,0 +1,26 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "GUI/Menu/MenuScreenBase.h" +#include "StartPromptScreen.generated.h" + + +/** + * + */ +UCLASS() +class UNREALPROJECT_API UStartPromptScreen : public UMenuScreenBase +{ + GENERATED_BODY() + +public: + UFUNCTION(BlueprintCallable, Category="Start Prompt") + void Close(); + + UFUNCTION(BlueprintImplementableEvent) + void OnClose(); + + DECLARE_MULTICAST_DELEGATE(FClosedEvent) + FClosedEvent onClosed; +}; diff --git a/Source/UnrealProject/GUI/Menu/SubMenu.cpp b/Source/UnrealProject/GUI/Menu/SubMenu.cpp new file mode 100644 index 0000000..5d4e2ed --- /dev/null +++ b/Source/UnrealProject/GUI/Menu/SubMenu.cpp @@ -0,0 +1,319 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "SubMenu.h" +#include "PlayerControllerBase.h" +#include "MenuButton.h" +#include "DefaultGameInstance.h" +#include "MenuScreenBase.h" +#include "EffectFunctionLibrary.h" +#include "ScreenOverlay.h" + +USoundBase* confirmAudioClass; +USoundBase* backAudioClass; +USoundBase* selectionChangedAudioClass; +USoundBase* clickAudioClass; + +USubMenu::USubMenu(const FObjectInitializer& init) +{ + selectionChangedAudioClass = ConstructorHelpers::FObjectFinder(TEXT("/Game/Assets/GUI/Components/InterfaceSounds/Select2")).Object; + backAudioClass = ConstructorHelpers::FObjectFinder(TEXT("/Game/Assets/GUI/Components/InterfaceSounds/Select0")).Object; + confirmAudioClass = ConstructorHelpers::FObjectFinder(TEXT("/Game/Assets/GUI/Components/InterfaceSounds/Select1")).Object; + clickAudioClass = ConstructorHelpers::FObjectFinder(TEXT("/Game/Assets/Sound/T_SkillDrop")).Object; +} + +void USubMenu::NativeConstruct() +{ + m_focus = false; + m_layoutDirection = 0; + container = Cast(WidgetTree->FindWidget("SubMenuContainer")); + if(!container) + { + container = Cast(GetRootWidget()); + if(!container) + { + GERROR("No \"Container\" found in widget " + GetName() + ", root widget was not a PanelWidget"); + } + } + Super::NativeConstruct(); + + m_selectedItem = 0; + RescanItems(); +} +void USubMenu::NativeDestruct() +{ + Super::NativeDestruct(); +} + +void USubMenu::NativeTick(const FGeometry& MyGeometry, float InDeltaTime) +{ + Super::NativeTick(MyGeometry, InDeltaTime); + if(HasFocus()) + { + for(UMenuItemBase* item : m_items) + { + item->FocusTick(InDeltaTime); + } + } +} + +void USubMenu::m_ScanContainer(UWidget* widget, int32 depth /*= 0*/) +{ + // Loop through panel widgets + UPanelWidget* panel = Cast(widget); + if(panel) + { + for(int32 i = 0; i < panel->GetChildrenCount(); i++) + { + m_ScanContainer(panel->GetChildAt(i), depth+1); + } + return; + } + + // Process Menu items + UMenuItemBase* item = Cast(widget); + if(item) + { + bool hidden = item->GetVisibility() == ESlateVisibility::Hidden || item->GetVisibility() == ESlateVisibility::Collapsed; + if(!hidden) + { + m_items.Add(item); + + // Assign item to a submenu + item->m_subMenu = this; + } + return; + } + + UPanelUserWidget* puw = Cast(widget); + if(puw) + { + m_ScanContainer(puw->GetRootWidget()); + return; + } +} + +void USubMenu::RescanItems() +{ + m_items.SetNum(0); + if(container) + { + if(Cast(container)) + { + m_layoutDirection = 1; + } + else + { + m_layoutDirection = 0; + } + + // Scan for buttons + m_ScanContainer(container); + + int32 childCount = m_items.Num(); + + // Clamp selection + m_selectedItem = FMath::Clamp(m_selectedItem, 0, childCount - 1); + + // Set active selection + for(int32 i = 0; i < childCount; i++) + { + UMenuItemBase* item = m_items[i]; + item->index = i; + + if(i == m_selectedItem) + { + item->NativeOnSelectionChanged(true, true); + } + else + { + item->NativeOnSelectionChanged(false, true); + } + } + + // Reselect single items + if(childCount == 1) + { + m_items[0]->NativeOnSelectionChanged(false, true); + m_items[0]->NativeOnSelectionChanged(true, true); + } + + if(childCount > 0) + { + // Selection event + onItemSelected.Broadcast(m_items[m_selectedItem]); + } + } + else + { + GWWARNING(L"SubMenu Container not set"); + return; + } +} + +void USubMenu::CloseSubMenu() +{ + if(m_screen) + m_screen->CloseSubMenu(this); +} + +void USubMenu::OnEnter(class UMenuScreenBase* screen) +{ + check(screen); + m_screen = screen; + SetButtonHints(); + onOpened.Broadcast(); + m_SetFocus(true); +} +void USubMenu::OnLeave() +{ + onClosed.Broadcast(); + m_screen = nullptr; + m_SetFocus(false); +} + +bool USubMenu::HasFocus() const +{ + return m_focus; +} + +void USubMenu::OnSuspend(USubMenu* newSubMenu, bool loseFocus) +{ + if(loseFocus) + m_SetFocus(false); + onSuspend.Broadcast(); +} +void USubMenu::OnRestore(USubMenu* removedMenu) +{ + SetButtonHints(); + m_SetFocus(true); + onRestore.Broadcast(); +} + +class UButtonHintBar* USubMenu::GetButtonHintBar() const +{ + return GetScreen()->GetButtonHintBar(); +} + +class UCharacterSettings* USubMenu::GetCharacterSettings() const +{ + return GetGameInstance()->GetCharacterSettings(); +} +class UPrefs* USubMenu::GetPrefs() const +{ + return GetGameInstance()->GetPrefs(); +} +class UDefaultGameInstance* USubMenu::GetGameInstance() const +{ + return Cast(GetWorld()->GetGameInstance()); +} +void USubMenu::NativeOnMenuAction(EMenuActionBinding binding) +{ + if(m_items.Num() > 0) + m_items[m_selectedItem]->NativeOnMenuAction(binding); + + switch(binding) + { + case EMenuActionBinding::Down: + if(m_items.Num() > 0 && m_layoutDirection == 0) + SelectNewItemByIndex(FMath::Clamp(m_selectedItem + 1, 0, GetNumItems()-1), true); + break; + case EMenuActionBinding::Up: + if(m_items.Num() > 0 && m_layoutDirection == 0) + SelectNewItemByIndex(FMath::Clamp(m_selectedItem - 1, 0, GetNumItems()-1), true); + break; + case EMenuActionBinding::Left: + if(m_items.Num() > 0 && m_layoutDirection == 1) + SelectNewItemByIndex(FMath::Clamp(m_selectedItem - 1, 0, GetNumItems() - 1), true); + break; + case EMenuActionBinding::Right: + if(m_items.Num() > 0 && m_layoutDirection == 1) + SelectNewItemByIndex(FMath::Clamp(m_selectedItem + 1, 0, GetNumItems() - 1), true); + break; + case EMenuActionBinding::Confirm: + break; + case EMenuActionBinding::Back: + onBack.Broadcast(); + UEffectFunctionLibrary::PlaySoundEffect2D(GetWorld(), backAudioClass); + break; + } + + Super::NativeOnMenuAction(binding); +} + +void USubMenu::NativeOnSelectionConfirmed(UMenuItemBase* item) +{ + UEffectFunctionLibrary::PlaySoundEffect2D(GetWorld(), clickAudioClass); + onItemSelectionConfirmed.Broadcast(m_items[m_selectedItem]); +} + +UMenuItemBase* USubMenu::GetItem(int32 index) const +{ + if(index >= m_items.Num() || index < 0) + { + GWWARNING(L"Item index out of range"); + return nullptr; + } + return m_items[index]; +} +int32 USubMenu::GetNumItems() const +{ + return m_items.Num(); +} +int32 USubMenu::GetSelectedItem() const +{ + return m_selectedItem; +} +void USubMenu::SelectNewItemByIndex(int32 idx) +{ + SelectNewItemByIndex(idx, false); +} +void USubMenu::SelectNewItemByIndex(int32 idx, bool controller) +{ + if(idx >= m_items.Num() || idx < 0) + { + GWWARNING(L"Item index out of range"); + return; + } + if(m_selectedItem != idx) + { + check(m_selectedItem < m_items.Num()); + m_items[m_selectedItem]->NativeOnSelectionChanged(false, controller); + m_items[idx]->NativeOnSelectionChanged(true, controller); + m_selectedItem = idx; + onItemSelected.Broadcast(m_items[m_selectedItem]); + SetButtonHints(); + UEffectFunctionLibrary::PlaySoundEffect2D(GetWorld(), selectionChangedAudioClass); + } +} +void USubMenu::SelectNewItem(UMenuItemBase* item) +{ + int32 idx = 0; + if (m_items.Find(item, idx)) + { + SelectNewItemByIndex(idx); + } +} +int32 USubMenu::GetItemIndex(UMenuItemBase* item) +{ + int32 idx = 0; + if (m_items.Find(item, idx)) + return idx; + return -1; +} +UMenuScreenBase* USubMenu::GetScreen() const +{ + return m_screen; +} + +void USubMenu::m_SetFocus(bool focus) +{ + m_focus = focus; + + // Get Selected button + UMenuItemBase* item = GetItem(GetSelectedItem()); + if (item) + { + item->NativeOnSelectionChanged(true, false); + } +} diff --git a/Source/UnrealProject/GUI/Menu/SubMenu.h b/Source/UnrealProject/GUI/Menu/SubMenu.h new file mode 100644 index 0000000..b80c82e --- /dev/null +++ b/Source/UnrealProject/GUI/Menu/SubMenu.h @@ -0,0 +1,127 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "GUI/Menu/MenuItemBase.h" +#include "SubMenu.generated.h" + +DECLARE_DYNAMIC_MULTICAST_DELEGATE(FMenuChange); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FSubMenuItemSelected, UMenuItemBase*, item); + +UCLASS() +class UNREALPROJECT_API UPanelUserWidget : public UUserWidget +{ + GENERATED_BODY() +public: +}; + +UCLASS() +class UNREALPROJECT_API USubMenu : public UMenuItemBase +{ + GENERATED_BODY() +public: + USubMenu(const FObjectInitializer& init); + virtual void NativeConstruct() override; + virtual void NativeDestruct() override; + virtual void NativeTick(const FGeometry& MyGeometry, float InDeltaTime) override; + + UFUNCTION(BlueprintCallable, Category = "SubMenu") + virtual void RescanItems(); + + UFUNCTION(BlueprintCallable, Category = "SubMenu") + void CloseSubMenu(); + + virtual void OnEnter(class UMenuScreenBase* screen); + virtual void OnLeave(); + + UFUNCTION(BlueprintCallable, Category = "SubMenu") + bool HasFocus() const; + + virtual void OnSuspend(USubMenu* newSubMenu, bool loseFocus); + virtual void OnRestore(USubMenu* removedMenu); + + // Callback that gets called whenever the button hits should update when showing this menu + UFUNCTION(BlueprintImplementableEvent, Category = "SubMenu") + void SetButtonHints(); + + UFUNCTION(BlueprintCallable, Category = "SubMenu") + class UButtonHintBar* GetButtonHintBar() const; + + UFUNCTION(BlueprintCallable, Category = "SubMenu") + class UCharacterSettings* GetCharacterSettings() const; + UFUNCTION(BlueprintCallable, Category = "SubMenu") + class UPrefs* GetPrefs() const; + UFUNCTION(BlueprintCallable, Category = "SubMenu") + class UDefaultGameInstance* GetGameInstance() const; + + virtual void NativeOnMenuAction(EMenuActionBinding binding) override; + virtual void NativeOnSelectionConfirmed(UMenuItemBase* item); + + UFUNCTION(BlueprintCallable, Category="SubMenu") + UMenuItemBase* GetItem(int32 itemIndex) const; + UFUNCTION(BlueprintCallable, Category = "SubMenu") + int32 GetNumItems() const; + UFUNCTION(BlueprintCallable, Category = "SubMenu") + int32 GetSelectedItem() const; + + UFUNCTION(BlueprintCallable, Category = "SubMenu") + void SelectNewItemByIndex(int32 idx); + UFUNCTION(BlueprintCallable, Category = "SubMenu") + void SelectNewItem(UMenuItemBase* item); + void SelectNewItemByIndex(int32 idx, bool controller); + + UFUNCTION(BlueprintCallable, Category = "SubMenu") + int32 GetItemIndex(UMenuItemBase* item); + + UFUNCTION(BlueprintCallable, Category = "SubMenu") + UMenuScreenBase* GetScreen() const; + + // Called when an item is selected + UPROPERTY(BlueprintAssignable, Category="SubMenu") + FSubMenuItemSelected onItemSelected; + // Called when a button is pressed + UPROPERTY(BlueprintAssignable, Category="SubMenu") + FSubMenuItemSelected onItemSelectionConfirmed; + + // Called when the back button is pressed + UPROPERTY(BlueprintAssignable, Category = "SubMenu") + FMenuChange onBack; + + UPROPERTY(BlueprintAssignable, Category = "SubMenu") + FMenuChange onClosed; + UPROPERTY(BlueprintAssignable, Category = "SubMenu") + FMenuChange onOpened; + + // Called when another menu is opened on top of this menu + UPROPERTY(BlueprintAssignable, Category = "SubMenu") + FMenuChange onSuspend; + // Called when this menu is displayed again after a menu was closed on top of this + UPROPERTY(BlueprintAssignable, Category = "SubMenu") + FMenuChange onRestore; + + // The panel widget that contains all the buttons selectable in this menu + UPROPERTY(BlueprintReadWrite, Category="SubMenu") + UPanelWidget* container; + +protected: + void m_ScanContainer(UWidget* widget, int32 depth = 0); + // Set the allowance of mouse focus & presses of the buttons in this menu + void m_SetFocus(bool focus); + + // The list of selectable items in the menu + UPROPERTY() + TArray m_items; + +private: + friend class UMenuItemBase; + int32 m_selectedItem; + + // Horizontal or vertical input handling + int m_layoutDirection; + + UPROPERTY() + class UMenuScreenBase* m_screen; + + // True if this is the topmost screen and if the buttons in this screen are allowed to be pressed. + bool m_focus; +}; diff --git a/Source/UnrealProject/GUI/Minimap/MiniMap.cpp b/Source/UnrealProject/GUI/Minimap/MiniMap.cpp new file mode 100644 index 0000000..ee7864c --- /dev/null +++ b/Source/UnrealProject/GUI/Minimap/MiniMap.cpp @@ -0,0 +1,20 @@ +#include "UnrealProject.h" +#include "MiniMap.h" + + +namespace MiniMap +{ + void NodeBase::CircleOverlap(const FVector2D& position, float radius_sqr, TArray& out_objects) + { + if (objects.empty()) + return; + + for (auto iter = objects.begin(); iter != objects.end(); iter++) + { + MinimapHandle& obj = **iter; + if (FVector2D::DistSquared(obj.position, position) <= radius_sqr) + out_objects.Add(&obj); + } + } +} + diff --git a/Source/UnrealProject/GUI/Minimap/MiniMap.h b/Source/UnrealProject/GUI/Minimap/MiniMap.h new file mode 100644 index 0000000..cd2048f --- /dev/null +++ b/Source/UnrealProject/GUI/Minimap/MiniMap.h @@ -0,0 +1,325 @@ +#pragma once + +#include "UnrealProject.h" +#include +using namespace std; + + +class ACharacterBase; +namespace MiniMap +{ + struct Rect + { + Rect() = default; + Rect(float x, float y, float width, float height) : x(x), y(y), width(width), height(height) {} + Rect(const FVector2D& position, const FVector2D& size) : x(position.X), y(position.Y), width(size.X), height(size.Y) {} + float x; + float y; + float width; + float height; + + inline FVector2D position() const + { + return FVector2D(x, y); + } + inline FVector2D size() const + { + return FVector2D(width, height); + } + inline bool contains(const FVector2D& pos) const + { + return pos.X >= x && pos.X <= (x + width) && pos.Y >= y && pos.Y <= (y + height); + } + }; + + + inline Rect GetSubArea(const Rect& area, size_t idx) + { + const FVector2D half_size = area.size() / 2; + const FVector2D position = area.position(); + switch (idx) + { + case 0: return Rect(position, half_size); + case 1: return Rect(position + FVector2D(half_size.X, 0), half_size); + case 2: return Rect(position + half_size, half_size); + case 3: return Rect(position + FVector2D(0, half_size.Y), half_size); + } + return area; + } + inline bool RectCircleOverlapSquared(const Rect& area, const FVector2D& position, float radius_sqr) + { + const FVector2D pos = area.position(); + const float left = area.x; + const float bottom = area.y; + const float right = area.x + area.width; + const float top = area.y + area.height; + + const float x = position.X > right ? right : position.X < left ? left : position.X; + const float y = position.Y > top ? top : position.Y < bottom ? bottom : position.Y; + + return FVector2D::DistSquared(position, FVector2D(x, y)) <= radius_sqr; + } + + class NonCopyable + { + public: + NonCopyable() = default; + NonCopyable(const NonCopyable& other) = delete; + NonCopyable& operator=(const NonCopyable& other) = delete; + }; + + class MinimapHandle; + class NodeBase : private NonCopyable + { + protected: + NodeBase(const Rect& area, NodeBase* parent) : area(area), parent(parent) {} + + public: + virtual void AddObject(MinimapHandle& obj) = 0; + virtual void RemoveObject(MinimapHandle& obj) = 0; + virtual void ClearRecursive() + { + objects.clear(); + } + virtual void ResizeRecursive(const Rect& area) + { + this->area = area; + } + + virtual void CircleOverlap(const FVector2D& position, float radius_sqr, TArray& out_objects); + + unordered_set objects; + Rect area; + NodeBase* parent; + }; + + class MinimapHandle : private NonCopyable + { + public: + MinimapHandle() : character(nullptr), node(nullptr), root(nullptr) {} + MinimapHandle(const FVector2D& position) : character(nullptr), position(position), node(nullptr), root(nullptr) {} + ~MinimapHandle() + { + + } + + FVector2D position; + class ::ACharacterBase* character; + NodeBase* node; + NodeBase* root; + }; + + template class TreeNode : public NodeBase + { + public: + TreeNode(const Rect& area, NodeBase* parent) : NodeBase(area, parent), child0(GetSubArea(area, 0), this), child1(GetSubArea(area, 1), this), child2(GetSubArea(area, 2), this), child3(GetSubArea(area, 3), this) {} + + typedef TreeNode child_type; + child_type child0; + child_type child1; + child_type child2; + child_type child3; + unordered_set child_objects; + + virtual void AddObject(MinimapHandle& obj) override + { + child_type* children = &child0; + for (size_t i = 0; i < 4; i++) + { + if (children[i].area.contains(obj.position)) + { + // Register to child + child_objects.emplace(&obj); + children[i].AddObject(obj); + return; + } + } + + // Register to this node + objects.emplace(&obj); + obj.node = this; + } + virtual void RemoveObject(MinimapHandle& obj) override + { + // Check if present in children + auto find_child = child_objects.find(&obj); + if (find_child != child_objects.end()) + { + child_type* children = &child0; + for (size_t i = 0; i < 4; i++) + children[i].RemoveObject(obj); + + child_objects.erase(find_child); + } + else + { + // Remove from node + auto find = objects.find(&obj); + if (find != objects.end()) + objects.erase(find); + } + } + virtual void ClearRecursive() override + { + child_type* children = &child0; + for (size_t i = 0; i < 4; i++) + children[i].ClearRecursive(); + + NodeBase::ClearRecursive(); + child_objects.clear(); + } + virtual void ResizeRecursive(const Rect& area) override + { + child_type* children = &child0; + for (size_t i = 0; i < 4; i++) + children[i].ResizeRecursive(GetSubArea(area, i)); + + NodeBase::ResizeRecursive(area); + } + + virtual void CircleOverlap(const FVector2D& position, float radius_sqr, TArray& out_objects) override + { + if (!child_objects.empty()) + { + child_type* children = &child0; + for (size_t i = 0; i < 4; i++) + { + child_type& child = children[i]; + if (RectCircleOverlapSquared(child.area, position, radius_sqr)) + child.CircleOverlap(position, radius_sqr, out_objects); + } + } + + NodeBase::CircleOverlap(position, radius_sqr, out_objects); + } + }; + template class RootNode : public TreeNode + { + public: + RootNode(const Rect& area, NodeBase* parent) : TreeNode(area, parent) + { + + } + + virtual void AddObject(MinimapHandle& obj) override + { + TreeNode::AddObject(obj); + all_objects.emplace(&obj); + } + virtual void RemoveObject(MinimapHandle& obj) override + { + TreeNode::RemoveObject(obj); + auto find = all_objects.find(&obj); + if (find != all_objects.end()) + all_objects.erase(find); + } + virtual void ClearRecursive() override + { + for (auto iter = all_objects.begin(); iter != all_objects.end(); iter++) + { + (*iter)->node = nullptr; + (*iter)->root = nullptr; + } + all_objects.clear(); + + TreeNode::ClearRecursive(); + } + + void Reparent(MinimapHandle& obj) + { + TreeNode::RemoveObject(obj); + TreeNode::AddObject(obj); + } + + unordered_set all_objects; + }; + template<> class TreeNode<0> : public NodeBase + { + public: + TreeNode(const Rect& area, NodeBase* parent) : NodeBase(area, parent) + { + + } + + void AddObject(MinimapHandle& obj) override + { + objects.emplace(&obj); + obj.node = this; + } + void RemoveObject(MinimapHandle& obj) override + { + auto find = objects.find(&obj); + if (find != objects.end()) + objects.erase(find); + } + }; + + template class QuadTree : private NonCopyable + { + public: + QuadTree() : m_root_node(Rect(0, 0, 1, 1), nullptr) {} + QuadTree(const Rect& area) : m_root_node(area, nullptr) {} + ~QuadTree() + { + m_root_node.ClearRecursive(); + } + + void AddObject(MinimapHandle& obj) + { + if (obj.root == &m_root_node) + return; + if (obj.root != nullptr) + obj.root->RemoveObject(obj); + + // Add object to root + m_root_node.AddObject(obj); + obj.root = &m_root_node; + } + void RemoveObject(MinimapHandle& obj) + { + if (obj.root == nullptr) + return; + if (obj.root != &m_root_node) + return; + + // Remove from root + m_root_node.RemoveObject(obj); + obj.node = nullptr; + obj.root = nullptr; + } + + void Update() + { + // Update positioning + for (auto iter = m_root_node.all_objects.begin(); iter != m_root_node.all_objects.end(); iter++) + { + MinimapHandle& obj = **iter; + + if (!obj.node->area.contains(obj.position) && obj.node->parent) + m_root_node.Reparent(obj); + } + } + void Clear() + { + m_root_node->ClearRecursive(); + } + void Resize(const Rect& area) + { + m_root_node.ResizeRecursive(area); + } + + int32 Num() const + { + return int32(m_root_node.all_objects.size()); + } + + bool CircleOverlap(const FVector2D& position, float radius, TArray& out_objects) + { + out_objects.SetNum(0); + m_root_node.CircleOverlap(position, radius * radius, out_objects); + return out_objects.Num() > 0; + } + + RootNode m_root_node; + }; +} \ No newline at end of file diff --git a/Source/UnrealProject/GUI/Minimap/MiniMapIconWidget.cpp b/Source/UnrealProject/GUI/Minimap/MiniMapIconWidget.cpp new file mode 100644 index 0000000..adb560f --- /dev/null +++ b/Source/UnrealProject/GUI/Minimap/MiniMapIconWidget.cpp @@ -0,0 +1,10 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "MiniMapIconWidget.h" + + +void UMiniMapIconWidget::SetObjectIcon_Implementation(UTexture2D* texture) +{ + // No implementation +} \ No newline at end of file diff --git a/Source/UnrealProject/GUI/Minimap/MiniMapIconWidget.h b/Source/UnrealProject/GUI/Minimap/MiniMapIconWidget.h new file mode 100644 index 0000000..0edb9b7 --- /dev/null +++ b/Source/UnrealProject/GUI/Minimap/MiniMapIconWidget.h @@ -0,0 +1,22 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "Blueprint/UserWidget.h" +#include "MiniMapIconWidget.generated.h" + +/** + * + */ +UCLASS() +class UNREALPROJECT_API UMiniMapIconWidget : public UUserWidget +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintReadWrite, Category = "Image") + UImage* image; + + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "MiniMap") + void SetObjectIcon(class UTexture2D* texture); +}; diff --git a/Source/UnrealProject/GUI/Minimap/MiniMapVisionCircle.cpp b/Source/UnrealProject/GUI/Minimap/MiniMapVisionCircle.cpp new file mode 100644 index 0000000..5447d6c --- /dev/null +++ b/Source/UnrealProject/GUI/Minimap/MiniMapVisionCircle.cpp @@ -0,0 +1,15 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "MiniMapVisionCircle.h" + + +UMiniMapVisionCircle::UMiniMapVisionCircle(const FObjectInitializer& init) : Super(init) +{ + +} + +void UMiniMapVisionCircle::NativeConstruct() +{ + Super::NativeConstruct(); +} \ No newline at end of file diff --git a/Source/UnrealProject/GUI/Minimap/MiniMapVisionCircle.h b/Source/UnrealProject/GUI/Minimap/MiniMapVisionCircle.h new file mode 100644 index 0000000..a2aca79 --- /dev/null +++ b/Source/UnrealProject/GUI/Minimap/MiniMapVisionCircle.h @@ -0,0 +1,26 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "Blueprint/UserWidget.h" +#include "MiniMapVisionCircle.generated.h" + +/** + * + */ +UCLASS() +class UNREALPROJECT_API UMiniMapVisionCircle : public UUserWidget +{ + GENERATED_BODY() + +public: + UMiniMapVisionCircle(const FObjectInitializer& init); + + virtual void NativeConstruct() override; + + UFUNCTION(BlueprintImplementableEvent, Category = "Vision") + void SetTexture(UTexture2D* texture); + + UFUNCTION(BlueprintImplementableEvent, Category = "Vision") + void SetUV(const FVector2D& uv, const FVector2D& scale); +}; diff --git a/Source/UnrealProject/GUI/Minimap/MiniMapWidget.cpp b/Source/UnrealProject/GUI/Minimap/MiniMapWidget.cpp new file mode 100644 index 0000000..e05e005 --- /dev/null +++ b/Source/UnrealProject/GUI/Minimap/MiniMapWidget.cpp @@ -0,0 +1,393 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "MiniMapVolume.h" +#include "MiniMapIconWidget.h" +#include "MiniMapVisionCircle.h" +#include "NetworkPlayer.h" +#include "BossBase.h" +#include "NPCBase.h" +#include "KOTHBossSpawner.h" +#include "KOTHMinionSpawner.h" +#include "NetworkCharacter.h" +#include "DefaultGameState.h" +#include "DefaultPlayerState.h" +#include "DefaultPlayerController.h" +#include "MiniMapWidget.h" +#include "WidgetLayoutLibrary.h" + +#if PLATFORM_SPECIFIC_WIN == 0 +#include "HeatMapMetrics.h" +#endif + + +float AngleTowards(const FVector2D& from, const FVector2D& to) +{ + const float angle_rad = FMath::Atan2(from.X - to.X, to.Y - from.Y) + PI; + return FMath::RadiansToDegrees(angle_rad); +} + +UMiniMapWidget::UMiniMapWidget(const FObjectInitializer& init) : Super(init) +{ + viewRadius = 2000; +} + +void UMiniMapWidget::NativeConstruct() +{ + Super::NativeConstruct(); + + // Find the minimap volume + ADefaultGameState* const gameState = Cast(GetWorld()->GetGameState()); + if(IsValid(gameState)) + { + for (TActorIterator iter(GetWorld()); iter; ++iter) + { + AMiniMapVolume* const volume = *iter; + volume->SetActorRotation(FRotator(0, 0, 0)); + + const FVector pos = volume->GetActorLocation(); + const FVector area = volume->area->GetScaledBoxExtent(); + m_size = FMath::Max(area.X, area.Y); + m_min.X = -m_size + pos.X; + m_min.Y = -m_size + pos.Y; + m_max.X = m_size + pos.X; + m_max.Y = m_size + pos.Y; + m_size *= 2; + + m_viewRadius = (viewRadius / (area.X * 2)); + + gameState->minimapQuadtree.Resize(MiniMap::Rect(FVector2D(m_min), FVector2D(m_size, m_size))); + + return; + } + } + + JERROR("Failed to locate Mini Map volume"); +} + +void UMiniMapWidget::NativeTick(const FGeometry& geometry, float deltaTime) +{ + Super::NativeTick(geometry, deltaTime); + + m_arrowAnim = FMath::Fmod(m_arrowAnim + deltaTime, 1.0f); + + // Reset the state of all current widgets + { + // Set widgets hidden + for (int32 i = 0; i < m_miniMapIconWidgetPool.Num(); i++) + { + m_miniMapIconWidgetPool[i]->SetVisibility(ESlateVisibility::Hidden); + // Reset rotations + FWidgetTransform trans = m_miniMapIconWidgetPool[i]->image->RenderTransform; + trans.Angle = 0; + m_miniMapIconWidgetPool[i]->image->SetRenderTransform(trans); + } + + // Set existing arrows hidden + for (int32 i = 0; i < m_arrowMapIconWidgetPool.Num(); i++) + m_arrowMapIconWidgetPool[i]->SetVisibility(ESlateVisibility::Hidden); + + // Allocate the neccesairy vision circles + for (int32 i = 0; i < m_viewCircleWidgetPool.Num(); i++) + m_viewCircleWidgetPool[i]->SetVisibility(ESlateVisibility::Hidden); + } + + // Begin the rendering + UWorld* const world = GetWorld(); + if (!IsValid(world)) return; + ADefaultGameState* const gameState = Cast(world->GetGameState()); + if (!IsValid(gameState)) + return; + + // Get the local team + m_localTeam = -1; + AController* controller = world->GetGameInstance()->GetFirstLocalPlayerController(); + ACharacterBase* localPlayer = nullptr; + if (IsValid(controller)) + { + ADefaultPlayerController* const playerController = Cast(controller); + localPlayer = Cast(playerController->GetPawn()); + if (playerController->PlayerState) + m_localTeam = Cast(playerController->PlayerState)->GetTeam(); + } + + // Failed to fetch local team + if (m_localTeam == -1) + return; + + gameState->minimapQuadtree.Update(); + + map> drawActors; + unordered_set& friendlyPlayerSet = drawActors.emplace(m_localTeam, unordered_set()).first->second; + + // Fetch the friendly viewpoints + for (TActorIterator iter(world); iter; ++iter) + { + ACharacterBase* const character = *iter; + if (IsValid(character)) + { + if (character->GetTeam() == m_localTeam) + friendlyPlayerSet.emplace(character); + } + } + + // Fetch all the visible units around the friendly viewpoints + for (auto iter = friendlyPlayerSet.begin(); iter != friendlyPlayerSet.end(); iter++) + { + ACharacterBase* const character = (*iter); + FVector2D position = FVector2D(character->GetActorLocation().X, character->GetActorLocation().Y); + if (gameState->minimapQuadtree.CircleOverlap(position, character->visionRadius, m_handleBuffer)) + { + for (int32 i = 0; i < m_handleBuffer.Num(); i++) + { + ACharacterBase* const visibleCharacter = m_handleBuffer[i]->character; + if(IsValid(visibleCharacter)) + drawActors[visibleCharacter->GetTeam()].emplace(visibleCharacter); + } + } + } + + // Fetch the minion camps + TArray minionCamps; + AKOTHBossSpawner* bossCamp = nullptr; + FVector2D bossCampPos; + for (TActorIterator iter(world); iter; ++iter) + { + AKOTHSpawnerBase* camp = *iter; + if (camp->IsA()) + { + bossCamp = Cast(camp); + const FVector2D charPos = FVector2D(camp->GetActorLocation().X, camp->GetActorLocation().Y); + bossCampPos = ConvertWorldToMinimap(charPos); + } + minionCamps.Add(camp); + } + + // Draw the minion camps + m_widgetIndex = 0; + m_arrowIndex = 0; + m_circleIndex = 0; + for (int32 i = 0; i < minionCamps.Num(); i++) + { + UMiniMapIconWidget* const widget = m_AllocateWidget(); + UCanvasPanelSlot* const slot = Cast(widget->Slot); + widget->SetVisibility(ESlateVisibility::Visible); + + AKOTHSpawnerBase* const camp = minionCamps[i]; + const FVector2D charPos = FVector2D(camp->GetActorLocation().X, camp->GetActorLocation().Y); + FVector2D relativePos = ConvertWorldToMinimap(charPos); + + // Set sprite position + FVector2D spriteSize = geometry.GetLocalSize() * 0.07f; + + slot->SetPosition(geometry.GetLocalSize() * relativePos - spriteSize * 0.5f); + slot->SetSize(spriteSize); + SetObjectIcon(campIcon, widget); + + FLinearColor color = enemyColor; + if (camp->team >= NPCTeam::Team1) // NPC Controlled + color = playerColor; + else if (int32(camp->team) == int32(m_localTeam)) // Friendly + color = allyColor; + else if (int32(camp->team) == 0) // Contested + color = neutralColor; + widget->image->Brush.TintColor = color; + + // Draw arrows + if (bossCamp && int32(camp->team) > 0 && camp->team < NPCTeam::Team1 && camp != bossCamp) + { + const int32 arrowCount = int32(FVector2D::Distance(relativePos, bossCampPos) * 25); + if (arrowCount > 0) + { + const float step = 1.0f / float(arrowCount); + for (int32 i = 0; i < arrowCount; i++) + { + UMiniMapIconWidget* arrow = m_AllocateArrow(); + arrow->SetVisibility(ESlateVisibility::Visible); + UCanvasPanelSlot* const slot = Cast(arrow->Slot); + + // Lerp towards the target + color.A = arrowCount > 1 ? (i == 0 ? m_arrowAnim : i == arrowCount - 1 ? (1.f - m_arrowAnim) : 1) : 1; + const FVector2D setPos = FMath::Lerp(relativePos, bossCampPos, step * float(i) + step * m_arrowAnim); + FWidgetTransform trans = arrow->image->RenderTransform; + trans.Angle = AngleTowards(setPos, bossCampPos); + arrow->image->SetRenderTransform(trans); + arrow->image->Brush.TintColor = color; + + const FVector2D spriteSize = geometry.GetLocalSize() * 0.04f; + slot->SetSize(spriteSize); + slot->SetPosition(geometry.GetLocalSize() * setPos - spriteSize * 0.5f); + } + } + } + } + + // Generate draw order (Draw local team last) + TArray teamDrawOrder; + for (auto iter = drawActors.rbegin(); iter != drawActors.rend(); iter++) + { + if (iter->first == m_localTeam) + continue; + teamDrawOrder.Add(iter->first); + } + teamDrawOrder.Add(m_localTeam); + + // Draw the actual characters to the minimap + int32 circleIndex = 0; + for (size_t i = 0; i < teamDrawOrder.Num(); i++) + { + const int32 team = teamDrawOrder[i]; + auto& set = drawActors[team]; + for (auto charIter = set.begin(); charIter != set.end(); charIter++) + { + ACharacterBase* const character = *charIter; + if (character == localPlayer) + continue; + + UMiniMapIconWidget* const widget = m_AllocateWidget(); + m_DrawCreature(geometry, widget, character); + } + } + + // Draw the local player last + if (localPlayer) + m_DrawCreature(geometry, m_AllocateWidget(), localPlayer); +} + +void UMiniMapWidget::m_DrawCreature(const FGeometry& geometry, UMiniMapIconWidget* widget, class ACharacterBase* character) +{ + widget->SetVisibility(ESlateVisibility::Visible); + UCanvasPanelSlot* const slot = Cast(widget->Slot); + + const int32 actorTeam = character->GetTeam(); + const FVector2D charPos = FVector2D(character->GetActorLocation().X, character->GetActorLocation().Y); + FVector2D relativePos = ConvertWorldToMinimap(charPos); + + // Rotation + const float drawPlayerAngle = character->GetActorRotation().Yaw; + + // Set sprite position + FVector2D spriteSize = geometry.GetLocalSize() * 0.07f; + + slot->SetPosition(geometry.GetLocalSize() * relativePos - spriteSize * 0.5f); + slot->SetSize(spriteSize); + + FWidgetTransform trans = widget->image->RenderTransform; + trans.Angle = 0; + + // Set icon based on the object type + if (character->IsA()) + SetObjectIcon(bossIcon, widget); + else if (character->IsA()) + SetObjectIcon(creatureIcon, widget); + else + { + trans.Angle = drawPlayerAngle; + SetObjectIcon(playerIcon, widget); + } + widget->image->SetRenderTransform(trans); + + // Set the color + if (!(character->IsA() && character->IsLocallyControlled())) + { + if (actorTeam != m_localTeam) + { + // Neutral or enemy? + if (actorTeam == 0) + widget->image->Brush.TintColor = neutralColor; + else + widget->image->Brush.TintColor = enemyColor; + } + else + widget->image->Brush.TintColor = allyColor; + } + else + widget->image->Brush.TintColor = playerColor; + + // Update the view circle + if (actorTeam == m_localTeam) + { + UMiniMapVisionCircle* const circle = m_AllocateVisionCircle(); + circle->SetVisibility(ESlateVisibility::Visible); + + UCanvasPanelSlot* const circleSlot = Cast(circle->Slot); + if (IsValid(circleSlot)) + { + const float diameter = (character->visionRadius * 2) / m_size; + FVector2D size = geometry.GetLocalSize() * diameter; + size = FVector2D(FMath::RoundToFloat(size.X), FMath::RoundToFloat(size.Y)); + FVector2D pos = geometry.GetLocalSize() * relativePos - size * 0.5f; + pos = FVector2D(FMath::RoundToFloat(pos.X), FMath::RoundToFloat(pos.Y)); + + circleSlot->SetPosition(pos); + circleSlot->SetSize(size); + circle->SetUV(pos / geometry.GetLocalSize(), size / geometry.GetLocalSize()); + } + } +} +UMiniMapIconWidget* UMiniMapWidget::m_AllocateWidget() +{ + UMiniMapIconWidget* widget = nullptr; + if (m_widgetIndex >= m_miniMapIconWidgetPool.Num()) + { + widget = CreateWidget(GetWorld(), iconWidget); + iconLayer->AddChild(widget); + m_miniMapIconWidgetPool.Add(widget); + } + else + widget = m_miniMapIconWidgetPool[m_widgetIndex]; + + m_widgetIndex++; + return widget; +} +UMiniMapIconWidget* UMiniMapWidget::m_AllocateArrow() +{ + UMiniMapIconWidget* arrow = nullptr; + if (m_arrowIndex >= m_arrowMapIconWidgetPool.Num()) + { + arrow = CreateWidget(GetWorld(), iconWidget); + arrowLayer->AddChild(arrow); + m_arrowMapIconWidgetPool.Add(arrow); + SetObjectIcon(arrowIcon, arrow); + } + else + arrow = m_arrowMapIconWidgetPool[m_arrowIndex]; + + m_arrowIndex++; + return arrow; +} +UMiniMapVisionCircle* UMiniMapWidget::m_AllocateVisionCircle() +{ + UMiniMapVisionCircle* circle = nullptr; + if (m_circleIndex >= m_viewCircleWidgetPool.Num()) + { + circle = CreateWidget(GetWorld(), visionCircleWidget); + visionLayer->AddChild(circle); + m_viewCircleWidgetPool.Add(circle); + circle->SetTexture(backgroundTexture); + } + else + circle = m_viewCircleWidgetPool[m_circleIndex]; + + m_circleIndex++; + return circle; +} + +FVector2D UMiniMapWidget::ConvertWorldToMinimap(const FVector2D& pos) +{ + const FVector2D scale = FVector2D(m_max.X - m_min.X, m_max.Y - m_min.Y); + FVector2D relativePos = (pos - m_min) / scale; + FVector2D returnPos = FVector2D(relativePos.Y, relativePos.X); + returnPos.Y = 1.0f - returnPos.Y; + return returnPos; +} + +void UMiniMapWidget::SetObjectIcon_Implementation(UTexture2D* texture, UMiniMapIconWidget* widget) +{ + // No implementation +} + +void UMiniMapWidget::SetMinimapInfo_Implementation(FVector2D player0, FVector2D player1, float radius) +{ + // No implementation +} \ No newline at end of file diff --git a/Source/UnrealProject/GUI/Minimap/MiniMapWidget.h b/Source/UnrealProject/GUI/Minimap/MiniMapWidget.h new file mode 100644 index 0000000..b172e6e --- /dev/null +++ b/Source/UnrealProject/GUI/Minimap/MiniMapWidget.h @@ -0,0 +1,89 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "Blueprint/UserWidget.h" +#include +#include "Minimap.h" +#include "MiniMapWidget.generated.h" + +/** + * + */ +UCLASS() +class UNREALPROJECT_API UMiniMapWidget : public UUserWidget +{ + GENERATED_BODY() + +public: + UMiniMapWidget(const FObjectInitializer& init); + + UFUNCTION(BlueprintNativeEvent, Category = "MiniMap") + void SetObjectIcon(class UTexture2D* texture, class UMiniMapIconWidget* widget); + + virtual void NativeConstruct() override; + virtual void NativeTick(const FGeometry& geometry, float deltaTime) override; + + UPROPERTY(EditAnywhere, Category = "MiniMap") + float viewRadius; + + UPROPERTY(EditAnywhere, Category = "Minimap") + FLinearColor playerColor; + UPROPERTY(EditAnywhere, Category = "Minimap") + FLinearColor allyColor; + UPROPERTY(EditAnywhere, Category = "Minimap") + FLinearColor enemyColor; + UPROPERTY(EditAnywhere, Category = "Minimap") + FLinearColor neutralColor; + + UPROPERTY(EditAnywhere, Category = "MiniMap") + UTexture2D* playerIcon; + UPROPERTY(EditAnywhere, Category = "MiniMap") + UTexture2D* creatureIcon; + UPROPERTY(EditAnywhere, Category = "MiniMap") + UTexture2D* bossIcon; + UPROPERTY(EditAnywhere, Category = "MiniMap") + UTexture2D* campIcon; + UPROPERTY(EditAnywhere, Category = "MiniMap") + UTexture2D* arrowIcon; + + UFUNCTION(BlueprintNativeEvent, Category = "MiniMap") + void SetMinimapInfo(FVector2D player0, FVector2D player1, float radius); + + FVector2D ConvertWorldToMinimap(const FVector2D& pos); + + UPROPERTY(EditAnywhere, Category = "MiniMap") + TSubclassOf iconWidget; + UPROPERTY(EditAnywhere, Category = "MiniMap") + TSubclassOf visionCircleWidget; + UPROPERTY(BlueprintReadWrite, Category = "MiniMap") + UCanvasPanel* iconLayer; + UPROPERTY(BlueprintReadWrite, Category = "MiniMap") + UCanvasPanel* arrowLayer; + UPROPERTY(BlueprintReadWrite, Category = "MiniMap") + UTexture2D* backgroundTexture; + UPROPERTY(BlueprintReadWrite, Category = "MiniMap") + UCanvasPanel* visionLayer; + +private: + void m_DrawCreature(const FGeometry& geometry, UMiniMapIconWidget* widget, class ACharacterBase* character); + UMiniMapIconWidget* m_AllocateWidget(); + UMiniMapIconWidget* m_AllocateArrow(); + UMiniMapVisionCircle* m_AllocateVisionCircle(); + + TArray m_miniMapIconWidgetPool; + TArray m_arrowMapIconWidgetPool; + TArray m_viewCircleWidgetPool; + TArray m_handleBuffer; + + FVector2D m_min; + FVector2D m_max; + float m_size; + float m_viewRadius; + float m_arrowAnim; + int32 m_localTeam; + + int32 m_widgetIndex; + int32 m_arrowIndex; + int32 m_circleIndex; +}; \ No newline at end of file diff --git a/Source/UnrealProject/GUI/PlayerSlot.cpp b/Source/UnrealProject/GUI/PlayerSlot.cpp new file mode 100644 index 0000000..e5a733d --- /dev/null +++ b/Source/UnrealProject/GUI/PlayerSlot.cpp @@ -0,0 +1,58 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "PlayerSlot.h" +#include "DefaultGameInstance.h" +#include "PlayerStateBase.h" + +UPlayerSlot::UPlayerSlot(const FObjectInitializer& init) + : Super(init) +{ +} +void UPlayerSlot::NativeConstruct() +{ + Super::NativeConstruct(); + currentPlayerState = nullptr; + isLocal = false; + OnChanged(); +} +void UPlayerSlot::Init(APlayerStateBase* playerState, bool isLocal) +{ + if(!playerState && m_playerStateSet) + { + // Clear this slot + m_playerStateSet = false; + OnChanged(); + return; + } + if (currentPlayerState == playerState) + return; + + this->isLocal = isLocal; + if (playerState) + { + // Get the player info to display in this player slot + const TSharedPtr netID = playerState->UniqueId.GetUniqueNetId(); + if (netID.IsValid()) + { + UWorld* const world = GetWorld(); + check(world); + UDefaultGameInstance* gameInstance = Cast(world->GetGameInstance()); + playerState->UpdatePersona(); + } + else + { + // Invalid NetID somehow + GWWARNING(L"NetID is invalid"); + return; + } + m_playerStateSet = true; + } + currentPlayerState = playerState; + + OnChanged(); +} + +void UPlayerSlot::OnChanged_Implementation() +{ +} \ No newline at end of file diff --git a/Source/UnrealProject/GUI/PlayerSlot.h b/Source/UnrealProject/GUI/PlayerSlot.h new file mode 100644 index 0000000..796649a --- /dev/null +++ b/Source/UnrealProject/GUI/PlayerSlot.h @@ -0,0 +1,33 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "Blueprint/UserWidget.h" +#include "PlayerSlot.generated.h" + +/** + * + */ +UCLASS() +class UNREALPROJECT_API UPlayerSlot : public UUserWidget +{ + GENERATED_BODY() + +public: + UPlayerSlot(const FObjectInitializer& init); + virtual void NativeConstruct() override; + void Init(class APlayerStateBase* playerState, bool isLocal = false); + + UFUNCTION(BlueprintNativeEvent, Category = "Player") + void OnChanged(); + + UPROPERTY(BlueprintReadOnly, Category = "Player") + class APlayerStateBase* currentPlayerState; + UPROPERTY(BlueprintReadOnly, Category = "Player") + bool isLocal; + +private: + // Keep this boolean to detect currentPlayerState becoming invalid(nullptr) + bool m_playerStateSet; + +}; diff --git a/Source/UnrealProject/GUI/SizeBorder.cpp b/Source/UnrealProject/GUI/SizeBorder.cpp new file mode 100644 index 0000000..9d8441e --- /dev/null +++ b/Source/UnrealProject/GUI/SizeBorder.cpp @@ -0,0 +1,16 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "SizeBorder.h" +#include "SlateBlueprintLibrary.h" + +void USizeBorder::NativeTick(const FGeometry& MyGeometry, float DeltaTime) +{ + USlateBlueprintLibrary::LocalToViewport(this, MyGeometry, MyGeometry.GetLocalSize(), + pixelSize, viewportSize); + USlateBlueprintLibrary::LocalToViewport(this, MyGeometry, FVector2D(0.0f, 0.0f), + pixelPosition, viewportPosition); + pixelSize -= pixelPosition; + viewportSize -= viewportPosition; + Super::NativeTick(MyGeometry, DeltaTime); +} diff --git a/Source/UnrealProject/GUI/SizeBorder.h b/Source/UnrealProject/GUI/SizeBorder.h new file mode 100644 index 0000000..277d0f7 --- /dev/null +++ b/Source/UnrealProject/GUI/SizeBorder.h @@ -0,0 +1,28 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "Blueprint/UserWidget.h" +#include "SizeBorder.generated.h" + +/** + * + */ +UCLASS() +class UNREALPROJECT_API USizeBorder : public UUserWidget +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintReadOnly, Category = "Size") + FVector2D pixelPosition; + UPROPERTY(BlueprintReadOnly, Category = "Size") + FVector2D pixelSize; + UPROPERTY(BlueprintReadOnly, Category = "Size") + FVector2D viewportPosition; + UPROPERTY(BlueprintReadOnly, Category = "Size") + FVector2D viewportSize; + + void NativeTick(const FGeometry& MyGeometry, float DeltaTime) override; + +}; diff --git a/Source/UnrealProject/GUI/SkillTree/EffectSelector.cpp b/Source/UnrealProject/GUI/SkillTree/EffectSelector.cpp new file mode 100644 index 0000000..190dc5f --- /dev/null +++ b/Source/UnrealProject/GUI/SkillTree/EffectSelector.cpp @@ -0,0 +1,130 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "EffectSelector.h" +#include "EffectSlot.h" +#include "BaseSkillObject.h" +#include "AbilityInfo.h" +#include "SkillWidget.h" + +TSubclassOf defaultEffectSelectorClass; + +UEffectSelector::UEffectSelector(const FObjectInitializer& init) + : Super(init) +{ + defaultEffectSelectorClass = ConstructorHelpers::FClassFinder(L"/Game/Assets/GUI/WEEGEE_SkillEffectSelector").Class; +} +void UEffectSelector::NativeConstruct() +{ + m_container = Cast(WidgetTree->FindWidget("EffectSlotContainer")); + if(!m_container) + { + GERROR("\"Container\" not found in effect selector widget " + GetName()); + } + + onItemSelected.AddDynamic(this, &UEffectSelector::m_OnItemSelected); + onItemSelectionConfirmed.AddDynamic(this, &UEffectSelector::m_OnItemSelectionConfirmed); + + m_nameField = Cast(WidgetTree->FindWidget("NameField")); + m_descriptionField = Cast(WidgetTree->FindWidget("DescField")); + + Super::NativeConstruct(); +} + +void UEffectSelector::Init(class USkillWidget* widget) +{ + this->skill = widget->skillAsset; + this->widget = widget; + + // Remove old items + for(UEffectSlot* slot : m_effectSlots) + { + if(slot) + slot->RemoveFromParent(); + } + + if(!skill) + { + m_effectSlots.SetNum(0); + RescanItems(); + return; + } + + // Add effect slot buttons + for(UAbilityInfo* abilityEffect : skill->abilityEffects) + { + if(abilityEffect) + { + UEffectSlot* slot = CreateWidget(GetWorld(), effectSlotClass); + m_container->AddChild(slot); + slot->Init(abilityEffect); + m_effectSlots.Add(slot); + } + else + { + GERROR("Didn't set ability on skill " + skill->GetName()); + } + } + + RescanItems(); +} + +void UEffectSelector::Init2(class TArray abilities) +{ + this->skill = nullptr; + this->widget = nullptr; + // Remove old items + for(UEffectSlot* slot : m_effectSlots) + { + if(slot) + slot->RemoveFromParent(); + } + + // Add effect slot buttons + for(UAbilityInfo* abilityEffect : abilities) + { + if(abilityEffect) + { + UEffectSlot* slot = CreateWidget(GetWorld(), effectSlotClass); + m_container->AddChild(slot); + slot->Init(abilityEffect); + m_effectSlots.Add(slot); + } + else + { + GERROR("Didn't set ability on skill " + skill->GetName()); + } + } + + RescanItems(); +} + +void UEffectSelector::m_OnItemSelected(UMenuItemBase* item) +{ + UEffectSlot* slot = Cast(item); + if(slot && slot->ability) + { + if(m_nameField) + m_nameField->SetText(FText::FromString(slot->ability->name)); + if(m_descriptionField) + m_descriptionField->SetText(FText::FromString(slot->ability->description)); + } +} + +void UEffectSelector::m_OnItemSelectionConfirmed(UMenuItemBase* item) +{ + UEffectSlot* slot = Cast(item); + if(slot && slot->ability) + { + if(widget) + { + widget->SetSelectedEffect(slot->index); + } + onEffectSelected.Broadcast(slot->index); + OnSelectionConfirmed(slot); + } + else + { + GERROR(L"Invalid ability selected"); + } +} diff --git a/Source/UnrealProject/GUI/SkillTree/EffectSelector.h b/Source/UnrealProject/GUI/SkillTree/EffectSelector.h new file mode 100644 index 0000000..fc11655 --- /dev/null +++ b/Source/UnrealProject/GUI/SkillTree/EffectSelector.h @@ -0,0 +1,52 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "GUI/Menu/SubMenu.h" +#include "EffectSelector.generated.h" + +extern TSubclassOf defaultEffectSelectorClass; + +UCLASS() +class UNREALPROJECT_API UEffectSelector : public USubMenu +{ + GENERATED_BODY() +public: + UEffectSelector(const FObjectInitializer& init); + virtual void NativeConstruct(); + + UFUNCTION(BlueprintImplementableEvent) + void OnSelectionConfirmed(UEffectSlot* effect); + + UFUNCTION(BlueprintCallable, Category="EffectSelector") + void Init(class USkillWidget* widget); + UFUNCTION(BlueprintCallable, Category = "EffectSelector") + void Init2(TArray abilities); + + UPROPERTY(EditDefaultsOnly) + TSubclassOf effectSlotClass; + + UPROPERTY(BlueprintReadOnly) + class UBaseSkillObject* skill; + UPROPERTY(BlueprintReadOnly) + class USkillWidget* widget; + + DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnEffectSelected, int32, selected); + UPROPERTY(BlueprintAssignable) + FOnEffectSelected onEffectSelected; + +private: + UFUNCTION() + void m_OnItemSelected(UMenuItemBase* item); + UFUNCTION() + void m_OnItemSelectionConfirmed(UMenuItemBase* item); + + UPROPERTY() + UTextBlock* m_nameField; + UPROPERTY() + UMultiLineEditableTextBox* m_descriptionField; + UPROPERTY() + UPanelWidget* m_container; + UPROPERTY() + TArray m_effectSlots; +}; diff --git a/Source/UnrealProject/GUI/SkillTree/EffectSlot.cpp b/Source/UnrealProject/GUI/SkillTree/EffectSlot.cpp new file mode 100644 index 0000000..d7a36ee --- /dev/null +++ b/Source/UnrealProject/GUI/SkillTree/EffectSlot.cpp @@ -0,0 +1,31 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "EffectSlot.h" +#include "AbilityInfo.h" + +void UEffectSlot::NativeConstruct() +{ + m_name = Cast(WidgetTree->FindWidget("NameField")); + m_image = Cast(WidgetTree->FindWidget("Icon")); + + Super::NativeConstruct(); +} +void UEffectSlot::Init(class UAbilityInfo* ability) +{ + this->ability = ability; + + if(!ability) + return; + + if(m_name) + { + m_name->SetText(FText::FromString(ability->name)); + } + if(m_image) + { + m_image->SetBrushFromTexture(ability->icon); + } + + OnInit(ability); +} diff --git a/Source/UnrealProject/GUI/SkillTree/EffectSlot.h b/Source/UnrealProject/GUI/SkillTree/EffectSlot.h new file mode 100644 index 0000000..971f39f --- /dev/null +++ b/Source/UnrealProject/GUI/SkillTree/EffectSlot.h @@ -0,0 +1,31 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "GUI/Menu/MenuButton.h" +#include "EffectSlot.generated.h" + +/** + * + */ +UCLASS() +class UNREALPROJECT_API UEffectSlot : public UMenuButton +{ + GENERATED_BODY() + +public: + virtual void NativeConstruct(); + + UFUNCTION(BlueprintCallable, Category = "EffectSelector") + void Init(class UAbilityInfo* ability); + + UFUNCTION(BlueprintImplementableEvent, Category = "EffectSelector") + void OnInit(class UAbilityInfo* ability); + + UPROPERTY(BlueprintReadOnly) + class UAbilityInfo* ability; + +private: + UImage* m_image; + UTextBlock* m_name; +}; diff --git a/Source/UnrealProject/GUI/SkillTree/HexagonTile.cpp b/Source/UnrealProject/GUI/SkillTree/HexagonTile.cpp new file mode 100644 index 0000000..b983c42 --- /dev/null +++ b/Source/UnrealProject/GUI/SkillTree/HexagonTile.cpp @@ -0,0 +1,22 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "HexagonTile.h" + +void UHexagonTile::NativeConstruct() +{ + TArray widgets; + WidgetTree->GetAllWidgets(widgets); + + for (int32 i = 0; i < widgets.Num(); i++) + { + UImage* tmp = Cast(widgets[i]); + if (tmp) + { + material = tmp->GetDynamicMaterial(); + break; + } + } +} + + diff --git a/Source/UnrealProject/GUI/SkillTree/HexagonTile.h b/Source/UnrealProject/GUI/SkillTree/HexagonTile.h new file mode 100644 index 0000000..b6d0151 --- /dev/null +++ b/Source/UnrealProject/GUI/SkillTree/HexagonTile.h @@ -0,0 +1,30 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "Blueprint/UserWidget.h" +#include "HexagonTile.generated.h" + +/** + * + */ +UCLASS() +class UNREALPROJECT_API UHexagonTile : public UUserWidget +{ + GENERATED_BODY() +public: + UPROPERTY( EditAnywhere, Category = "UI" ) + int32 x; + + UPROPERTY( EditAnywhere, Category = "UI" ) + int32 y; + + FMargin GetMargins() + { + return GetFullScreenOffset(); + } + + class UMaterialInstanceDynamic* material; + + virtual void NativeConstruct() override; +}; diff --git a/Source/UnrealProject/GUI/SkillTree/SkillTreeState.h b/Source/UnrealProject/GUI/SkillTree/SkillTreeState.h new file mode 100644 index 0000000..5b0475a --- /dev/null +++ b/Source/UnrealProject/GUI/SkillTree/SkillTreeState.h @@ -0,0 +1,155 @@ +#pragma once +#include "SkillTreeState.Generated.h" + +USTRUCT(BlueprintType) +struct FSkillTreeStateObject +{ + GENERATED_BODY(); + UPROPERTY() + FVector2D gridIndex; + UPROPERTY() + float rotation; + UPROPERTY() + TArray placedPoints; + UPROPERTY() + UClass* skillObject; + UPROPERTY() + int32 selectedEffect; + + bool ContainsRoot(FIntPoint root) + { + for (int32 i = 0; i < placedPoints.Num(); i++) + { + if (placedPoints[i] == root) return true; + } + return false; + } + TArray ConnectedSkills( TMap& skillsMap ) + { + TArray result; + const FIntPoint offsets[2][6]{ + { FIntPoint(0, -1), FIntPoint(1, -1), FIntPoint(1, 0), FIntPoint(0, 1), FIntPoint(-1, 0), FIntPoint(-1, -1) }, + { FIntPoint(0, -1), FIntPoint(1, 0), FIntPoint(1, 1), FIntPoint(0, 1), FIntPoint(-1, 1), FIntPoint(-1, 0) } }; + for (int32 i = 0; i < placedPoints.Num(); i++) + { + int32 k = placedPoints[i].X & 1; + for (int32 j = 0; j < 6; j++) + { + FIntPoint checkPoint = placedPoints[i] + offsets[k][j]; + if (placedPoints.Contains(checkPoint)) continue; + if (skillsMap.Contains(checkPoint)) + { + FSkillTreeStateObject* skill = *skillsMap.Find(checkPoint); + if (result.Contains(skill)) continue; + result.Push(skill); + } + } + } + return result; + } +}; + +USTRUCT(BlueprintType) +struct FSkillTreeState +{ + GENERATED_BODY(); + + UPROPERTY(BlueprintReadOnly) + TArray objects; + +private: + void WalkTree(TMap>* a_map, TArray* a_checked, TArray a_nodes) + { + for (int32 i = 0; i < a_nodes.Num(); i++) + { + if (a_checked->Contains(a_nodes[i])) continue; + a_checked->Push(a_nodes[i]); + TArray* connected = a_map->Find(a_nodes[i]); + if (!connected) + { + YERROR("Could not find the connected skills."); + continue; + } + WalkTree(a_map, a_checked, *connected); + } + } + +public: + bool ValidateSkillTree() + { + bool rootResult = false; + bool connectResult = true; + + if (objects.Num() == 0) return true; + + + TMap> connectedSkillsMap; + TMap skillsMap; + FSkillTreeStateObject* rootSkill = NULL; + for (int32 i = 0; i < objects.Num(); i++) + { + TArray points = objects[i].placedPoints; + for (int32 j = 0; j < points.Num(); j++) + { + skillsMap.Add(points[j], &objects[i]); + } + } + + for (int32 i = 0; i < objects.Num(); i++) + { + TArray points = objects[i].placedPoints; + if (!rootResult) + { + bool rootTest = objects[i].ContainsRoot(FIntPoint(6,15)); + if (rootTest) + { + rootResult = true; + rootSkill = &objects[i]; + } + } + TArray connectedSkills = objects[i].ConnectedSkills(skillsMap); + connectedSkillsMap.Add(&objects[i], connectedSkills); + if (connectedSkills.Num() == 0) + { + connectResult = false; + break; + } + } + + if (connectResult && rootResult) + { + if (!rootSkill) + { + YERROR("Root Skill is NULL!"); + return false; + } + TArray checkedSkills; + checkedSkills.Push(rootSkill); + TArray* connected = connectedSkillsMap.Find(rootSkill); + if (!connected) + { + YPRINT("Root Skill is not attached to any skills!"); + return false; + } + WalkTree(&connectedSkillsMap, &checkedSkills, *connected); + if (objects.Num() != checkedSkills.Num()) + connectResult = false; + else + { + for (int32 i = 0; i < objects.Num(); i++) + { + if (!checkedSkills.Contains(&objects[i])) + { + connectResult = false; + } + } + } + } + bool isValid = false; + if (objects.Num() == 1 && rootResult) + isValid = true; + else + isValid = (rootResult && connectResult); + return isValid; + } +}; \ No newline at end of file diff --git a/Source/UnrealProject/GUI/SkillTree/SkillTreeWidget.cpp b/Source/UnrealProject/GUI/SkillTree/SkillTreeWidget.cpp new file mode 100644 index 0000000..598cd3d --- /dev/null +++ b/Source/UnrealProject/GUI/SkillTree/SkillTreeWidget.cpp @@ -0,0 +1,678 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "SkillTreeWidget.h" +#include "SkillTreeObject.h" +#include "BaseSkillObject.h" +#include "HexagonTile.h" +#include "ScrollBox.h" +#include "SkillWidget.h" +#include "CanvasPanel.h" +#include "PlayerControllerBase.h" +#include "DefaultGameInstance.h" +#include "CharacterSettings.h" +#include "SlateBlueprintLibrary.h" +#include "ToolTipWidget.h" +#include "AbilityInfo.h" +#include "SizeBorder.h" +#include "WidgetLayoutLibrary.h" + +int32 roundToNearestOdd1(float a_in) +{ + return 2 * FMath::FloorToInt((a_in / 2) + 0.5f) - 2; +} + +int32 roundToNearestEven1(float a_in) +{ + return 2 * FMath::FloorToInt(a_in / 2); +} + +void USkillTreeWidget::NativeOnSkillTreeChanged(ESkillTreeChangeEvent event, UBaseSkillObject* relevantObject) +{ + // Recalculate dominantDamageType + int32_t categoryCounters[] = { 0,0,0 }; + int32_t largest = 0; + for(USkillWidget* skill : m_skillWidgets) + { + if(skill->selectedEffect >= 0 && skill->skillAsset) + { + if(skill->selectedEffect >= skill->skillAsset->abilityEffects.Num()) + { + GERROR("CORRUPT SKILL TREE"); + continue; + } + + UAbilityInfo* info = skill->skillAsset->abilityEffects[skill->selectedEffect]; + int32_t& myCounter = categoryCounters[(size_t)info->abilityCategory]; + myCounter++; + + if(myCounter >= largest) + { + largest = myCounter; + dominantAbilityCategory = info->abilityCategory; + } + } + } + + onSkillTreeChanged.Broadcast(event, relevantObject); +} + +USkillTreeWidget::USkillTreeWidget(const FObjectInitializer& init) + : Super( init ) +{ + m_interactive = true; + m_shown = false; + m_lastFocusedWidget = nullptr; + isValid = false; + placedSkillHexMap = new FHexMap(); + for (int32 x = 0; x < 13; x++) + { + placedSkillHexMap->rawdata[x] = 0; + } +} + +void USkillTreeWidget::NativeConstruct() +{ + Super::NativeConstruct(); + + UpdateVisibility(); + + // Get the tooltip widget + m_toolTipWidget = Cast(WidgetTree->FindWidget("ToolTipSWidget")); + + m_sizeBorder = Cast(WidgetTree->FindWidget("SizeBorder")); + + if ( !skillTreeAsset ) return; + TArray widgets; + WidgetTree->GetAllWidgets( widgets ); + UScrollBox* scrollBox = NULL; + m_skillTreeCanvas = WidgetTree->FindWidget("Main_SkillTree"); + //m_focusBorder = WidgetTree->FindWidget("FocusBorder"); + m_inputCapture = WidgetTree->FindWidget("InputCapture"); + m_inputCapture->SetVisibility(ESlateVisibility::SelfHitTestInvisible); + for ( int32 i = 0; i < widgets.Num(); i++ ) + { + UHexagonTile* tmpHex = Cast( widgets[i] ); + UScrollBox* tmpScrollBox = Cast( widgets[i] ); + //USkillWidget* tmpSkill = Cast( widgets[i] ); + if ( tmpScrollBox ) scrollBox = tmpScrollBox; + //if ( tmpSkill ) m_selectedSkill = tmpSkill; + if ( !tmpHex ) continue; + tiles.Push( tmpHex ); + bool test = skillTreeAsset->hexMap.Get( tmpHex->x, tmpHex->y ); + if ( test ) tmpHex->SetVisibility( ESlateVisibility::Hidden ); + } + if ( m_selectedSkill ) + { + m_selectedSkill->parent = this; + m_selectedSkill->SetDragable( true ); + } + else + { + YWARNING( "No Selected Skill found!" ); + } +} +void USkillTreeWidget::NativeDestruct() +{ + delete placedSkillHexMap; + Super::NativeDestruct(); +} + +void USkillTreeWidget::NativeTick(const FGeometry& MyGeometry, float DeltaTime) +{ + Super::NativeTick(MyGeometry, DeltaTime); + + //if ((m_focusBorder && m_focusBorder->IsHovered()) || draggingWidget) + //{ + // //m_inputCapture->SetVisibility(ESlateVisibility::Visible); + //} + //else + //{ + // //m_inputCapture->SetVisibility(ESlateVisibility::SelfHitTestInvisible); + //} + + if (m_toolTipWidget) + { + m_toolTipWidget->showTooltip = false; + + FVector2D mousePos; + UWidgetLayoutLibrary::GetMousePositionScaledByDPI(GetOwningPlayer(), mousePos.X, mousePos.Y); + FIntPoint mouseGridPos = GetGridIndex(mousePos); + USkillWidget** sw = tileMap.Find(mouseGridPos); + USkillWidget* skill = sw ? *sw : nullptr; + if (skill) + { + // Reset hovered state + UAbilityInfo* info = skill->GetSelectedEffectAbility(); + if (info) + { + m_toolTipWidget->SetTitle(info->name); + m_toolTipWidget->SetText(info->description); + m_toolTipWidget->position = skill->tooltipAreaPosition; + m_toolTipWidget->size = skill->tooltipAreaSize; + m_toolTipWidget->showTooltip = true; + } + skill->hovered = true; + } + if (m_lastFocusedWidget && m_lastFocusedWidget != skill) + { + m_lastFocusedWidget->hovered = false; + } + m_lastFocusedWidget = skill; + } +} + +FReply USkillTreeWidget::NativeOnMouseButtonUp(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent) +{ + if(!IsInteractive()) + return FReply::Handled(); + //GWPRINT(L"Skill tree up"); + if(draggingWidget && !draggingWidget->IsUsingController()) + { + draggingWidget->StopDragging(); + return FReply::Handled(); + } + return FReply::Handled(); +} +FReply USkillTreeWidget::NativeOnMouseButtonDown(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent) +{ + if(!IsInteractive()) + return FReply::Handled(); + //GWPRINT(L"Skill tree down"); + FKey button = InMouseEvent.GetEffectingButton(); + if(button == EKeys::LeftMouseButton) + { + if(draggingWidget && !draggingWidget->IsUsingController()) + { + draggingWidget->StopDragging(); + return FReply::Handled(); + } + else + { + if(m_lastFocusedWidget) + { + if(m_lastFocusedWidget->hovered) + m_lastFocusedWidget->StartDragging(); + } + } + } + else if(button == EKeys::RightMouseButton) + { + if(!draggingWidget && m_lastFocusedWidget) + { + if(m_lastFocusedWidget->hovered) + { + m_lastFocusedWidget->OnRequireSkillEffect(); + } + } + } + + return FReply::Handled(); +} +FReply USkillTreeWidget::NativeOnMouseWheel(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent) +{ + //GWPRINT(L"USkillTreeWidget MouseWheel " + InMouseEvent.GetWheelDelta()); + if (draggingWidget) + { + float add = 60.0f * FMath::FloorToFloat(InMouseEvent.GetWheelDelta()); + draggingWidget->SetSkillRotation(add + draggingWidget->GetSkillRotation()); + } + return FReply::Unhandled(); +} + +void USkillTreeWidget::SetIsInteractive(bool interactive) +{ + m_interactive = interactive; + UpdateVisibility(); +} +void USkillTreeWidget::UpdateVisibility() +{ + if (m_shown) + { + SetVisibility(ESlateVisibility::SelfHitTestInvisible); + } + else + { + SetVisibility(ESlateVisibility::Hidden); + } + UImage* validImage = WidgetTree->FindWidget("Validation_Image"); + if (m_interactive) + { + validImage->SetVisibility(ESlateVisibility::Visible); + } + else + { + validImage->SetVisibility(ESlateVisibility::Hidden); + } +} + +void WalkTree(TMap>* a_map, TArray* a_checked, TArray a_nodes) +{ + for (int32 i = 0; i < a_nodes.Num(); i++) + { + if (a_checked->Contains(a_nodes[i])) continue; + a_checked->Push(a_nodes[i]); + TArray* connected = a_map->Find(a_nodes[i]); + if (!connected) + { + YERROR("Could not find the connected skills."); + continue; + } + WalkTree(a_map, a_checked, *connected); + } +} + +bool USkillTreeWidget::ValidateSkillTree() +{ + bool rootResult = false; + bool connectResult = true; + if (m_skillWidgets.Num() == 0) return true; + TMap> connectedSkillsMap; + USkillWidget* rootSkill = NULL; + for (int32 i = 0; i < m_skillWidgets.Num(); i++) + { + TArray points = m_skillWidgets[i]->GetPoints(); + if (!rootResult) + { + bool rootTest = m_skillWidgets[i]->ContainsRoot(points); + if (rootTest) + { + rootResult = true; + rootSkill = m_skillWidgets[i]; + } + } + TArray connectedSkills = m_skillWidgets[i]->ConnectedSkills(points); + connectedSkillsMap.Add(m_skillWidgets[i], connectedSkills); + if ( connectedSkills.Num() == 0 ) + { + connectResult = false; + break; + } + } + if (connectResult && rootResult) + { + if (!rootSkill) + { + YERROR("Root Skill is NULL!"); + return false; + } + TArray checkedSkills; + checkedSkills.Push(rootSkill); + TArray* connected = connectedSkillsMap.Find(rootSkill); + if (!connected) + { + YPRINT("Root Skill is not attached to any skills!"); + return false; + } + WalkTree(&connectedSkillsMap, &checkedSkills, *connected); + if (m_skillWidgets.Num() != checkedSkills.Num()) + connectResult = false; + else + { + for (int32 i = 0; i < m_skillWidgets.Num(); i++) + { + if (!checkedSkills.Contains(m_skillWidgets[i])) + { + connectResult = false; + } + } + } + } + if (m_skillWidgets.Num() == 1 && rootResult) + isValid = true; + else + isValid = (rootResult && connectResult); + if (isValid) + { + UpdateLevel(10.0f); + } + else + { + UpdateLevel(0.0f); + } + return isValid; +} + +FSkillTreeState USkillTreeWidget::GetState() +{ + FSkillTreeState state; + for(int32 i = 0; i < m_skillWidgets.Num(); i++) + { + FSkillTreeStateObject obj; + USkillWidget* skill = m_skillWidgets[i]; + obj.gridIndex = skill->GetGridIndex() /*- FVector2D(1.5,4) * m_skillTreeCanvas->RenderTransform.Scale.X*/; + obj.placedPoints = skill->placedPoints; + obj.skillObject = skill->skillAsset->GetClass(); + obj.rotation = skill->GetSkillRotation(); + obj.selectedEffect = skill->selectedEffect; + state.objects.Add(obj); + + //GWPRINT(L"Saving gridIndex " + obj.gridIndex); + //for(int32 i = 0; i < obj.placedPoints.Num(); i++) + //{ + // GWPRINT(L"Saving Placed point " + i + L" " + obj.placedPoints[i].X + L", " + obj.placedPoints[i].Y); + //} + } + + return state; +} + +TArray USkillTreeWidget::GetApearanceState() +{ + return m_skillClasses; +} + +void USkillTreeWidget::Clear() +{ + // Remove all placed hexes + auto arrayCopy = m_skillWidgets; + for(int32 i = 0; i < arrayCopy.Num(); i++) + { + arrayCopy[i]->RemoveFromParent(); + RemoveSkill(arrayCopy[i], arrayCopy[i]->placedPoints); + } + m_skillWidgets.SetNum(0); + m_skillClasses.SetNum(0); + tileMap.Reset(); + + // Clear Hex map + for(int32 x = 0; x < 13; x++) + { + placedSkillHexMap->rawdata[x] = 0; + } + + isValid = true; + + // Trigger Callbacks + NativeOnSkillTreeChanged(ESkillTreeChangeEvent::Cleared, nullptr); +} +void USkillTreeWidget::BuildFromState(const FSkillTreeState& state) +{ + Clear(); + for(int32 i = 0; i < state.objects.Num(); i++) + { + const FSkillTreeStateObject& obj = state.objects[i]; + UBaseSkillObject* baseSkill = obj.skillObject->GetDefaultObject(); + + if(!baseSkill) + continue; + + //GWPRINT(L"gridIndex " + obj.gridIndex); + //for(int32 i = 0; i < obj.placedPoints.Num(); i++) + //{ + // GWPRINT(L"Placed point " + i + L" " + obj.placedPoints[i].X + L", " + obj.placedPoints[i].Y); + //} + + m_selectedSkill = CreateWidget(GetWorld(), WidgetTemplate); + m_selectedSkill->skillAsset = baseSkill; + m_selectedSkill->parent = this; + if(m_skillTreeCanvas) + m_selectedSkill->SetRenderScale(m_skillTreeCanvas->RenderTransform.Scale); + m_selectedSkill->SetPlaced(); + if(m_skillTreeCanvas) + m_skillTreeCanvas->GetParent()->AddChild(m_selectedSkill); + UCanvasPanelSlot* slot = Cast(m_selectedSkill->Slot); + if(slot) + { + slot->SetAutoSize(true); + slot->SetAlignment(FVector2D(-0.5f, -0.5f)); + } + m_selectedSkill->SetSelectedEffect(obj.selectedEffect); + m_selectedSkill->placedPoints = obj.placedPoints; + m_selectedSkill->SetSkillRotation(obj.rotation); + m_selectedSkill->PlaceOnGridIndex(obj.gridIndex); + AddSkill(m_selectedSkill, obj.placedPoints); + } +} + +void USkillTreeWidget::RemoveSkill(UBaseSkillObject* skillObject) +{ + int32 skillID = m_skillClasses.Find(skillObject); + if (skillID == INDEX_NONE) return; + CancelSkill(m_skillWidgets[skillID]); + m_skillWidgets[skillID]->Remove(); +} + +bool USkillTreeWidget::IsUsingSkill(UBaseSkillObject* skillObject) const +{ + return m_skillClasses.Contains(skillObject); +} + +USkillWidget* USkillTreeWidget::GetUsedSkill(class UBaseSkillObject* skillObject) +{ + for (int32 i = 0; i < m_skillWidgets.Num(); i++) + { + if (m_skillWidgets[i]->skillAsset == skillObject) + return m_skillWidgets[i]; + } + return nullptr; +} + +void USkillTreeWidget::Save(int32 saveSlot) +{ + UDefaultGameInstance* inst = Cast(GetGameInstance()); + UCharacterSettings* settings = inst->GetCharacterSettings(); + + if (saveSlot < 0 || saveSlot >= settings->characterSaves.Num()) + { + JERROR("Invalid save slot"); + return; + } + + settings->characterSaves[saveSlot].skillTreeState = GetState(); + inst->SaveSettings(); +} +void USkillTreeWidget::Load(int32 saveSlot) +{ + UDefaultGameInstance* inst = Cast(GetGameInstance()); + UCharacterSettings* settings = inst->GetCharacterSettings(); + + if (saveSlot < 0 || saveSlot >= settings->characterSaves.Num()) + { + JERROR("Invalid save slot"); + return; + } + + BuildFromState(settings->characterSaves[saveSlot].skillTreeState); + ValidateSkillTree(); +} + +FIntPoint USkillTreeWidget::GetGridIndex(FVector2D in) const +{ + FVector2D result; + UCanvasPanelSlot* tmpSlot = UWidgetLayoutLibrary::SlotAsCanvasSlot(m_skillTreeCanvas); + float viewportScale = UWidgetLayoutLibrary::GetViewportScale(const_cast(this)); + FVector2D topLeft; + float scale = 32.0f * (m_skillTreeCanvas->RenderTransform.Scale.X / 2); + float XScale = (scale * 1.73205f); + float YScale = scale; + if (tmpSlot) + { + FVector2D screenTopRight = FVector2D(GEngine->GameViewport->Viewport->GetSizeXY().X * tmpSlot->GetAnchors().Minimum.X, GEngine->GameViewport->Viewport->GetSizeXY().Y * tmpSlot->GetAnchors().Minimum.Y); + FVector2D offset = tmpSlot->GetPosition() * viewportScale; + FVector2D widgetTopLeft = ((tmpSlot->GetSize() * m_skillTreeCanvas->RenderTransform.Scale.X * viewportScale) / 2); + topLeft = screenTopRight + offset - widgetTopLeft; + topLeft /= viewportScale; + //topLeft.X -= 16.0f; + } + + float XPreFloor = (in.X - topLeft.X) / XScale; + float YPreFloor = (in.Y - topLeft.Y) / YScale; + int32 XIndex = FMath::FloorToInt(XPreFloor); + int32 YIndex = FMath::FloorToInt(YPreFloor); + /*FVector2D magicOffset = FVector2D(1.5, 4) * m_skillTreeCanvas->RenderTransform.Scale.X;*/ + bool odd = (XIndex % 2) == 1; + if (odd) + YIndex = roundToNearestOdd1(YIndex); + else + YIndex = roundToNearestEven1(YIndex); + return FIntPoint(XIndex, (YIndex) / 2.0f); +} + +void USkillTreeWidget::StartDragging(USkillWidget* widget) +{ + check(draggingWidget == nullptr); + draggingWidget = widget; + m_inputCapture->SetVisibility(ESlateVisibility::Visible); +} + +void USkillTreeWidget::StopDragging(USkillWidget* widget) +{ + if(draggingWidget != widget) + { + GERROR("Calling StopDragging twice"); + } + draggingWidget = nullptr; + m_inputCapture->SetVisibility(ESlateVisibility::SelfHitTestInvisible); +} + +void USkillTreeWidget::UpdateTextInfo_Implementation() +{ +} + +void USkillTreeWidget::AddSkill(USkillWidget* a_skill, const TArray& a_points) +{ + for ( int32 i = 0; i < a_points.Num(); i++ ) + { + placedSkillHexMap->Set( a_points[i].X, a_points[i].Y, true ); + + tileMap.Add(a_points[i], a_skill); + } + + UWorld* const world = GetWorld(); + if ( !world ) + { + YWARNING( "Couldn't get the World." ); + return; + } + APlayerControllerBase* const controller = Cast( GetOwningPlayer() ); + if ( !controller ) + { + YWARNING( "Couldn't get the Player." ); + return; + } + controller->OnLearnSkill( a_skill->skillAsset ); + m_skillWidgets.Add(a_skill); + m_skillClasses.Add(a_skill->skillAsset); + + // Trigger Callbacks + NativeOnSkillTreeChanged(ESkillTreeChangeEvent::Added, a_skill->skillAsset); + + ValidateSkillTree(); +} + +void USkillTreeWidget::RemoveSkill( USkillWidget* a_skill, const TArray& a_points ) +{ + m_skillClasses.Remove(a_skill->skillAsset); + m_skillWidgets.Remove(a_skill); + for ( int32 i = 0; i < a_points.Num(); i++ ) + { + placedSkillHexMap->Set( a_points[i].X, a_points[i].Y, false ); + tileMap.Remove(a_points[i]); + } + + UWorld* const world = GetWorld(); + if ( !world ) + { + YWARNING( "Couldn't get the World." ); + return; + } + APlayerControllerBase* const controller = Cast( GetOwningPlayer() ); + if ( !controller ) + { + YWARNING( "Couldn't get the Player." ); + return; + } + controller->OnUnlearnSkill( a_skill->skillAsset ); + + // Trigger Callbacks + NativeOnSkillTreeChanged(ESkillTreeChangeEvent::Removed, a_skill->skillAsset); +} + +USkillWidget* USkillTreeWidget::NewSkill( USkillWidget* a_skill, bool controller ) +{ + return NewSkillFromAsset(a_skill->skillAsset, controller); +} + +class USkillWidget* USkillTreeWidget::NewSkillFromAsset(UBaseSkillObject* skillAsset, bool controller /*= false*/) +{ + if(draggingWidget) + return NULL; + //a_skill->Disable(); + m_selectedSkill = CreateWidget(GetWorld(), WidgetTemplate); + m_selectedSkill->skillAsset = skillAsset; + m_selectedSkill->parent = this; + m_selectedSkill->SetVisibility(ESlateVisibility::SelfHitTestInvisible); + m_selectedSkill->SetDragable(true, controller); + m_selectedSkill->SetRenderScale(m_skillTreeCanvas->RenderTransform.Scale); + m_skillTreeCanvas->GetParent()->AddChild(m_selectedSkill); + m_selectedSkill->SetUserFocus(GetOwningPlayer()); + UCanvasPanelSlot* slot = Cast(m_selectedSkill->Slot); + if(slot) + { + slot->SetAutoSize(true); + slot->SetAlignment(FVector2D(-0.5f, -0.5f)); + } + + if(controller) + { + // Set initial position + m_selectedSkill->MoveSkillAbsolute(lastGridIndex); + } + + return m_selectedSkill; +} + +void USkillTreeWidget::CancelSkill(USkillWidget* a_skill) +{ + a_skill->RemoveFromParent(); + ValidateSkillTree(); +} + +void USkillTreeWidget::Show() +{ + m_shown = true; + UpdateVisibility(); +} + +void USkillTreeWidget::Hide() +{ + m_shown = false; + UpdateVisibility(); +} + +void USkillTreeWidget::UpdateLevel(float level) +{ + for (int32 i = 0; i < m_skillWidgets.Num(); i++) + { + m_skillWidgets[i]->UpdateLevel(level); + } + if (IsInteractable()) + { + UpdateTextInfo(); + } +} + +void USkillTreeWidget::UpdateInfo(float skilltreeHeight, float skilltreeOffset) +{ + for (int32 i = 0; i < m_skillWidgets.Num(); i++) + { + m_skillWidgets[i]->UpdateInfo(skilltreeHeight, skilltreeOffset); + } +} + +/* +void USkillTreeWidget::NativeTick(const FGeometry& MyGeometry, float DeltaTime) +{ + FVector2D minPos; + FVector2D maxPos; + FVector2D viewportPos; + USlateBlueprintLibrary::LocalToViewport(this, MyGeometry, MyGeometry.GetLocalSize(), maxPos, viewportPos); + USlateBlueprintLibrary::LocalToViewport(this, MyGeometry, FVector2D(0,0), minPos, viewportPos); + FVector2D viewportSize = maxPos - minPos; + for (int32 i = 0; i < m_skillWidgets.Num(); i++) + { + m_skillWidgets[i]->UpdateInfo(viewportSize.Y, minPos.Y); + } +} +*/ \ No newline at end of file diff --git a/Source/UnrealProject/GUI/SkillTree/SkillTreeWidget.h b/Source/UnrealProject/GUI/SkillTree/SkillTreeWidget.h new file mode 100644 index 0000000..bb19880 --- /dev/null +++ b/Source/UnrealProject/GUI/SkillTree/SkillTreeWidget.h @@ -0,0 +1,155 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "../Menu/SubMenu.h" +#include "SkillTreeState.h" +#include "AbilityInfo.h" +#include "SkillTreeWidget.generated.h" + +class USkillTreeObject; +class USkillWidget; +class UCanvasPanel; +class UHexagonTile; +class ADefaultPlayerController; +struct FHexMap; + +UENUM(BlueprintType) +enum class ESkillTreeChangeEvent : uint8 +{ + Added, + Removed, + Cleared, +}; + +/* + Yosho's skill tree? +*/ +UCLASS() +class UNREALPROJECT_API USkillTreeWidget : public USubMenu +{ + GENERATED_BODY() +private: + TArray m_skillWidgets; + TArray m_skillClasses; + USkillWidget* m_selectedSkill; + UCanvasPanel* m_skillTreeCanvas; + UBorder* m_inputCapture; + UBorder* m_focusBorder; + class USizeBorder* m_sizeBorder; + class UToolTipWidget* m_toolTipWidget; + USkillWidget* m_lastFocusedWidget; + + bool m_interactive; + bool m_shown; + +public: + UPROPERTY(BlueprintReadOnly, Category = "UI") + bool isValid; + + TMap tileMap; + TArray tiles; + UPROPERTY( EditAnywhere, Category = "UI" ) + USkillTreeObject* skillTreeAsset; + UPROPERTY( EditAnywhere, Category = "UI" ) + FIntPoint rootPoint; + FVector2D lastGridIndex; + + class UToolTipWidget* GetToolTipWidget() const { + return m_toolTipWidget; + } + + UFUNCTION(BlueprintCallable, Category = "UI") + void SetIsInteractive(bool interactive); + void UpdateVisibility(); + bool IsShow() const { return m_shown; } + UFUNCTION(BlueprintCallable, Category = "UI") + bool IsInteractive() const { return m_interactive; } + class USizeBorder* GetSizeBorder() const { return m_sizeBorder; } + + UPROPERTY(BlueprintReadOnly) + EAbilityCategory dominantAbilityCategory; + + // Returns the complete state to reconstruct this skilltree later with BuildFromState() + UFUNCTION(BlueprintCallable, Category = "Skill Tree") + FSkillTreeState GetState(); + // Returns the array of used skill objects for visual usage + UFUNCTION(BlueprintCallable, Category = "Skill Tree") + TArray GetApearanceState(); + // Removes all skills from this skill tree widget + UFUNCTION(BlueprintCallable, Category = "UI") + void Clear(); + // Restores a given skill tree setup from a state struct + UFUNCTION(BlueprintCallable, Category = "Skill Tree") + void BuildFromState(const FSkillTreeState& state); + + UFUNCTION(BlueprintCallable, Category = "UI") + bool IsUsingSkill(class UBaseSkillObject* skillObject) const; + UFUNCTION(BlueprintCallable, Category = "UI") + USkillWidget* GetUsedSkill(class UBaseSkillObject* skillObject); + + UFUNCTION(BlueprintCallable, Category = "UI") + void Save(int32 saveSlot); + UFUNCTION(BlueprintCallable, Category = "UI") + void Load(int32 saveSlot); + UFUNCTION(BlueprintCallable, Category = "UI") + void RemoveSkill(class UBaseSkillObject* skillObject); + + void StartDragging(USkillWidget* widget); + UFUNCTION(BlueprintCallable, Category = "UI") + void StopDragging(USkillWidget* widget); + + FIntPoint GetGridIndex(FVector2D in) const; + + UPROPERTY(BlueprintReadOnly, Category = "UI") + USkillWidget* draggingWidget; + UFUNCTION(BlueprintNativeEvent, Category = "UI") + void UpdateTextInfo(); + + FHexMap* placedSkillHexMap; + ADefaultPlayerController* parent; + + UFUNCTION(BlueprintCallable, Category = "UI") + bool ValidateSkillTree(); + + UCanvasPanel* GetCanvas() + { + return m_skillTreeCanvas; + } + void AddSkill( USkillWidget* a_skill, const TArray& a_points ); + void RemoveSkill( USkillWidget* a_skill, const TArray& a_points ); + UFUNCTION(BlueprintCallable, Category = "UI") + class USkillWidget* NewSkill(USkillWidget* a_skill, bool controller = false); + UFUNCTION(BlueprintCallable, Category = "UI") + class USkillWidget* NewSkillFromAsset(UBaseSkillObject* skillAsset, bool controller = false); + UFUNCTION(BlueprintCallable, Category = "UI") + void CancelSkill( USkillWidget* a_skill ); + + void UpdateLevel( float level ); + UFUNCTION(BlueprintCallable, Category = "UI") + void UpdateInfo( float skilltreeHeight, float skilltreeOffset ); + + UFUNCTION(BlueprintCallable, Category="UI") + void Show(); + UFUNCTION(BlueprintCallable, Category="UI") + void Hide(); + + UPROPERTY( EditDefaultsOnly, BlueprintReadOnly, Category = UI ) + TSubclassOf WidgetTemplate; + + // Callback for when the skill tree state changes + DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnSkillTreeChanged, ESkillTreeChangeEvent, event, UBaseSkillObject*, object); + UPROPERTY(BlueprintAssignable) + FOnSkillTreeChanged onSkillTreeChanged; + + virtual void NativeOnSkillTreeChanged(ESkillTreeChangeEvent event, UBaseSkillObject* relevantObject); + + USkillTreeWidget( const FObjectInitializer& init ); + + virtual void NativeConstruct() override; + virtual void NativeDestruct() override; + virtual void NativeTick(const FGeometry& MyGeometry, float DeltaTime) override; + virtual FReply NativeOnMouseButtonUp(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent) override; + virtual FReply NativeOnMouseButtonDown(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent) override; + virtual FReply NativeOnMouseWheel(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent) override; +}; diff --git a/Source/UnrealProject/GUI/SkillTree/SkillWidget.cpp b/Source/UnrealProject/GUI/SkillTree/SkillWidget.cpp new file mode 100644 index 0000000..41fe7f9 --- /dev/null +++ b/Source/UnrealProject/GUI/SkillTree/SkillWidget.cpp @@ -0,0 +1,843 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "SkillWidget.h" +#include "BaseSkillObject.h" +#include "SkillTreeObject.h" +#include "HexagonTile.h" +#include "CanvasPanel.h" +#include "Border.h" +#include "SkillTreeWidget.h" +#include "HexagonTile.h" +#include "WidgetLayoutLibrary.h" +#include "AbilityInfo.h" +#include "SizeBorder.h" +#include "MenuScreenBase.h" + +int32 roundToNearestOdd(float a_in) +{ + return 2 * FMath::FloorToInt(a_in / 2) + 1; +} + +int32 roundToNearestEven(float a_in) +{ + return 2 * FMath::FloorToInt(a_in / 2); +} + +//Convert from Odd-q coordinate system to Cube. +FIntVector Oddq2Cube( FIntPoint in ) +{ + int32 x = in.X; + int32 z = int32( float( in.Y ) - float( in.X - ( in.X & 1 ) ) / 2.0f ); + int32 y = -x - z; + return FIntVector( x, y, z ); +} + +//Convert from Cube coordinate system to Odd-q. +FIntPoint Cube2Oddq( FIntVector in ) +{ + int32 x = in.X; + int32 y = int32( float( in.Z ) + float( in.X - ( in.X & 1 ) ) / 2.0f ); + return FIntPoint( x, y ); +} + +//Convert from Even-q coordinate system to Cube. +FIntVector Evenq2Cube( FIntPoint in ) +{ + int32 x = in.X; + int32 z = int32( float( in.Y ) - float( in.X + ( in.X & 1 ) ) / 2.0f ); + int32 y = -x - z; + return FIntVector( x, y, z ); +} + +//Convert from Cube coordinate system to Even-q. +FIntPoint Cube2Evenq( FIntVector in ) +{ + int32 x = in.X; + int32 y = int32( float( in.Z ) + float( in.X + ( in.X & 1 ) ) / 2.0f ); + return FIntPoint( x, y ); +} + +//Rotates a hexoganal position by the angle. +//The position must be in Cube coordinates. +//The angle must be in degrees. +FIntVector RotateHexPos( FIntVector in, float angle ) +{ + while ( angle < 0 ) + { + angle += 360; + } + while ( angle > 360 ) + { + angle -= 360; + } + int32 steps = FMath::FloorToInt( angle / 60 ); + FIntVector result = in; + for ( int32 i = 0; i < steps; i++ ) + { + FIntVector tmp = FIntVector( -result.Z, -result.X, -result.Y ); + result = tmp; + } + return result; +} + +//Rotates a hexoganal position by the angle. +//The position must be in Odd-q or Even-q coordinates. +//The OddQ boolean decides what coordinate system is used. +//The angle must be in degrees. +FIntPoint RotateHexPos( FIntPoint in, float angle, bool oddq ) +{ + FIntVector cubeIn; + if ( oddq ) + cubeIn = Oddq2Cube( in ); + else + cubeIn = Evenq2Cube( in ); + + FIntVector cubeOut = RotateHexPos( cubeIn, angle ); + + FIntPoint Out; + if ( oddq ) + Out = Cube2Oddq( cubeOut ); + else + Out = Cube2Evenq( cubeOut ); + return Out; +} + +void USkillWidget::PlaceOnGridIndex(FVector2D index) +{ + m_lastGridIndex = index /*+ FVector2D(1.5, 4) * RenderTransform.Scale.X*/; + UCanvasPanelSlot* tmpSlot = UWidgetLayoutLibrary::SlotAsCanvasSlot(parent->GetCanvas()); + float viewportScale = UWidgetLayoutLibrary::GetViewportScale(parent); + FVector2D topLeft = GetSkillTreePos(); + float scale = 32.0f * (RenderTransform.Scale.X / 2); + float XScale = (scale * 1.73205f); + float YScale = scale; + if(tmpSlot) + { + FVector2D result; + result.X = index.X * XScale; + result.X += topLeft.X; + //result.X -= 2; + result.Y = index.Y * YScale; + result.Y += topLeft.Y; + //result.Y += 14.5f; + + UCanvasPanelSlot* selfSlot = UWidgetLayoutLibrary::SlotAsCanvasSlot(this); + selfSlot->SetPosition(result); + + placedPoints.SetNum(0); + CheckPosition(m_lastGridIndex, &placedPoints); + ChangeColor(GetShapeTypeColor()); + } +} + +void USkillWidget::SetPlaced() +{ + m_dragable = true; + m_placed = true; +} + +void USkillWidget::UpdateSkill() +{ + m_hexagons.Empty(); + if ( !skillAsset ) + { + //YWARNING( "No Skill Asset assigned to the Skill Widget." ); + SetVisibility( ESlateVisibility::Hidden ); + return; + } + SetVisibility(ESlateVisibility::SelfHitTestInvisible); + TArray widgets; + WidgetTree->GetAllWidgets( widgets ); + UCanvasPanel* canvasPanel = NULL; + + FLinearColor color = GetShapeTypeColor(); + + for ( int32 i = 0; i < widgets.Num(); i++ ) + { + UHexagonTile* tmp = Cast( widgets[i] ); + UCanvasPanel* tmpPanel = Cast( widgets[i] ); + if ( tmpPanel ) canvasPanel = tmpPanel; + if ( !tmp ) continue; + bool test = skillAsset->hexMap.Get( tmp->x, tmp->y ); + //tmp->SetColorAndOpacity( skillColor ); + m_hexagons.Push( tmp ); + if ( !test ) + { + tmp->SetVisibility(ESlateVisibility::Hidden); + } + else + { + tmp->SetVisibility(ESlateVisibility::SelfHitTestInvisible); + } + } + if (!m_dragable) + { + UpdateLevel(0); + } + if ( canvasPanel ) + { + m_canvasPanel = canvasPanel; + UCanvasPanelSlot* slot = Cast( canvasPanel->Slot ); + if ( slot ) + { + m_slot = slot; + float offset = 1.0f; + float width = ( skillAsset->hexMap.width * 27.7128f ) + 4.2871f; + FVector2D newSize = FVector2D(width, ( (skillAsset->hexMap.height * 32.0f) - offset )); + m_slot->SetSize(newSize); + //GWPRINT(L"Size of " + skillAsset->GetName() + L"(placed=" + m_placed + L") = " + newSize); + } + if ( m_dragable ) + { + FVector2D transformPivot; + transformPivot.X = ( float( skillAsset->pivot.X ) + 0.5f ) / float( skillAsset->hexMap.width ); + transformPivot.Y = ( float( skillAsset->pivot.Y ) + ( ( skillAsset->pivot.X & 1 ) ? 1.0f : 0.5f ) ) / float( skillAsset->hexMap.height ); + canvasPanel->SetRenderTransformPivot( transformPivot ); + //YPRINT( "Pivot: X=" + transformPivot.X + ", Y=" + transformPivot.Y ); + } + } + UCanvasPanel* mainCanvas = WidgetTree->FindWidget("Main_Canvas"); + if ( m_dragable ) + { + //m_spacer->SetVisibility( ESlateVisibility::Hidden ); + SetRenderScale( parent->WidgetTree->FindWidget("Main_SkillTree")->RenderTransform.Scale ); + } + else + { + //m_spacer->SetVisibility( ESlateVisibility::Visible ); + SetRenderScale( FVector2D( 1, 1 ) ); + } + //float scale = RenderTransform.Scale.X - 1.0f; + //float alignment = ( m_dragable ) ? (-scale * 0.5f) : 0.5f; + //FVector2D alignment2D ( alignment, alignment ); + //tmpSlot->SetAlignment( alignment2D ); + + if ( m_dragable && !m_controller ) + { + FVector2D mousePos; + UWidgetLayoutLibrary::GetMousePositionScaledByDPI( GetOwningPlayer(), mousePos.X, mousePos.Y ); + FVector2D skillPos; + skillPos = mousePos - ( m_slot->GetSize() / 2 ); + UCanvasPanelSlot* tmpSlot2 = Cast( Slot ); + if ( tmpSlot2 ) + { + tmpSlot2->SetPosition( skillPos ); + } + if(!m_placed) + StartDragging(); + else + { + FVector2D position = GetViewportPos(); + FVector2D mousePos; + UWidgetLayoutLibrary::GetMousePositionScaledByDPI(GetOwningPlayer(), mousePos.X, mousePos.Y); + m_offset = position - mousePos; + } + m_offset = -( ( m_slot->GetSize() * (RenderTransform.Scale.X / 2) ) / 2); + } + else if (m_controller) + { + StartDragging(); + } + + // Update color of hexagons + UpdateSkillIcon(); + ChangeColor(color); +} + +void USkillWidget::NativeConstruct() +{ + selectedEffect = -1; + hovered = false; + UpdateSkill(); + FIntPoint point( 0, 1 ); + FIntPoint a = RotateHexPos( point, 60, false ); + //YPRINT( "60: X:" + a.X + ", Y:" + a.Y ); + /* + FIntPoint b = RotateHexPos( point, 120 ); + YPRINT( "120: X:" + b.X + ", Y:" + b.Y ); + FIntPoint c = RotateHexPos( point, 180 ); + YPRINT( "180: X:" + c.X + ", Y:" + c.Y ); + FIntPoint d = RotateHexPos( point, 240 ); + YPRINT( "240: X:" + d.X + ", Y:" + d.Y ); + FIntPoint e = RotateHexPos( point, 300 ); + YPRINT( "300: X:" + e.X + ", Y:" + e.Y ); + FIntPoint f = RotateHexPos( point, -60 ); + YPRINT( "-60: X:" + f.X + ", Y:" + f.Y ); + FIntPoint g = RotateHexPos( point, -120 ); + YPRINT( "-120: X:" + g.X + ", Y:" + g.Y ); + FIntPoint h = RotateHexPos( point, -180 ); + YPRINT( "-180: X:" + h.X + ", Y:" + h.Y ); + */ + + //m_border = WidgetTree->FindWidget("Border"); + //m_border->SetVisibility(ESlateVisibility::Hidden); + + Super::NativeConstruct(); +} + +void USkillWidget::NativeDestruct() +{ + Super::NativeDestruct(); +} + +void USkillWidget::UpdateSkillColor() +{ + FLinearColor color; + if(m_dragging) + { + bool hover = CheckPosition(m_lastGridIndex); + if(hover) + { + m_hovering = true; + color = FLinearColor(0, 1, 0, 1); + if(!GetSelectedEffectAbility()) + color = FLinearColor::Yellow; + } + else + { + m_hovering = false; + color = FLinearColor(1, 0, 0, 1); + } + + } + else + { + color = GetShapeTypeColor(); + } + ChangeColor(color); +} + +void USkillWidget::UpdateSkillIcon() +{ + for(int32 i = 0; i < m_hexagons.Num(); i++) + { + if(m_hexagons[i]->material) + { + m_hexagons[i]->material->SetTextureParameterValue("Skill_Texture", m_skillIcon); + } + } +} + +void USkillWidget::SetSelectedEffect(int32 effect) +{ + //UpdateSkill(); + + if(effect >= 0 && effect < skillAsset->abilityEffects.Num()) + { + selectedEffect = effect; + + // Set icon on hexagons + UAbilityInfo* ability = skillAsset->abilityEffects[selectedEffect]; + if(ability) + { + m_skillIcon = ability->icon; + } + } + + // Update material + UpdateSkillIcon(); + UpdateSkillColor(); +} + +class UAbilityInfo* USkillWidget::GetSelectedEffectAbility() const +{ + if(selectedEffect >= 0 && selectedEffect < skillAsset->abilityEffects.Num()) + { + return skillAsset->abilityEffects[selectedEffect]; + } + return nullptr; +} + +void USkillWidget::Select() +{ + //if ( !m_spacer ) return; + //m_spacer->SetBrushColor( FLinearColor( 1, 1, 1, 0.68f ) ); + //parent->DeselectOther( this ); + //m_selected = true; + //UWidgetLayoutLibrary::GetMousePositionScaledByDPI( GetOwningPlayer(), m_mousedownPos.X, m_mousedownPos.Y ); +} + +void USkillWidget::Deselect() +{ + //if ( !m_spacer ) return; + //m_spacer->SetBrushColor( FLinearColor( 0, 0, 0, 0.68f ) ); + //m_selected = false; +} + +void USkillWidget::Remove() +{ + parent->RemoveSkill(this, placedPoints); +} + +bool USkillWidget::RestartDragging() +{ + if (parent->draggingWidget) + return false; + + parent->RemoveSkill(this, placedPoints); + SetDragable(true, true); + SetUserFocus(GetOwningPlayer()); + + parent->draggingWidget = this; + + m_hovering = true; + ChangeColor(FLinearColor(0, 1, 0, 1)); + return true; +} + +void USkillWidget::StartDragging() +{ + if(!m_dragging && parent->draggingWidget == nullptr && parent->IsInteractive()) + { + m_dragging = true; + FVector2D position = GetViewportPos(); + FVector2D mousePos; + UWidgetLayoutLibrary::GetMousePositionScaledByDPI(GetOwningPlayer(), mousePos.X, mousePos.Y); + m_offset = position - mousePos; + if(m_placed) parent->RemoveSkill(this, placedPoints); + m_placed = false; + parent->StartDragging(this); + OnStartDragging(); + } + //YPRINT( "Start... (X: " + position.X + ", Y: " + position.Y + ")" ); +} + +bool USkillWidget::PlaceSkill() +{ + // Check if we have selected a valid effect + bool canPlace = true; + bool effectAssigned = GetSelectedEffectAbility() != nullptr; + //if(m_controller && !effectAssigned) + // canPlace = false; + if(skillAsset->abilityEffects.Num() == 0) + { + GERROR("Skill has no ability effects assigned, " + skillAsset->GetName()); + canPlace = false; + } + + TArray points; + placedPoints.Empty(); + if(!CheckPosition(m_lastGridIndex, &points)) + canPlace = false; + + if(canPlace) + { + placedPoints = points; + parent->AddSkill(this, points); + m_placed = true; + ChangeColor(GetShapeTypeColor()); + parent->StopDragging(this); + + if(m_controller) + { + GetScreen()->CloseSubMenu(this); + } + + m_dragging = false; + if(!effectAssigned) + { + OnRequireSkillEffect(); + } + + OnPlaceDone(true); + return true; + } + else if(!m_controller) + { + // Using mouse + parent->CancelSkill(this); + parent->StopDragging(this); + OnPlaceDone(false); + return false; + } + else + { + OnPlaceDone(false); + return false; + } +} + +void USkillWidget::StopDragging() +{ + if(m_dragging) + { + FVector2D mousePos; + UWidgetLayoutLibrary::GetMousePositionScaledByDPI(GetOwningPlayer(), mousePos.X, mousePos.Y); + FVector2D newPos = mousePos + m_offset; + FVector4 gridresult = GetGridPos(newPos); + FVector2D gridPos = FVector2D(gridresult.X, gridresult.Y); + FVector2D gridIndex = FVector2D(gridresult.Z, gridresult.W); + m_dragging = false; + PlaceSkill(); + } +} + +FVector2D USkillWidget::GetPositionFromIndex(FIntPoint in) +{ + FVector2D result; + UCanvasPanelSlot* tmpSlot = UWidgetLayoutLibrary::SlotAsCanvasSlot(parent->GetCanvas()); + float viewportScale = UWidgetLayoutLibrary::GetViewportScale(parent); + FVector2D topLeft = GetSkillTreePos(); + float scale = 32.0f * ( RenderTransform.Scale.X / 2); + float XScale = (scale * 1.73205f); + float YScale = scale; + //FVector2D magicOffset = FVector2D(1.5, 4) * RenderTransform.Scale.X; + float XIndex = (float)in.X;// -magicOffset.X; + float YIndex = (float)in.Y;// -magicOffset.Y; + result.X = XIndex * XScale + topLeft.X; + result.Y = YIndex * YScale + topLeft.Y; + return result; +} + +FVector2D USkillWidget::GetSkillTreePos() +{ + UCanvasPanelSlot* tmpSlot = UWidgetLayoutLibrary::SlotAsCanvasSlot(parent->GetCanvas()); + float viewportScale = UWidgetLayoutLibrary::GetViewportScale(parent->GetCanvas()); + FVector2D topLeft; + if (tmpSlot) + { + FVector2D screenTopRight = FVector2D(GEngine->GameViewport->Viewport->GetSizeXY().X * tmpSlot->GetAnchors().Minimum.X, GEngine->GameViewport->Viewport->GetSizeXY().Y * tmpSlot->GetAnchors().Minimum.Y); + FVector2D offset = tmpSlot->GetPosition() * viewportScale; + FVector2D widgetTopLeft = ((tmpSlot->GetSize() * RenderTransform.Scale.X * viewportScale) / 2) ; + topLeft = screenTopRight + offset - widgetTopLeft; + topLeft /= viewportScale; + //topLeft.X -= 16.0f; + } + return topLeft; +} + +FVector2D USkillWidget::GetViewportPos() +{ + FVector2D result; + FVector2D position; + UCanvasPanelSlot* tmpSlot = Cast( Slot ); + if ( tmpSlot ) + { + position = tmpSlot->GetPosition( ); + } + result.X = position.X; + result.Y = position.Y; + return result; +} + +FVector4 USkillWidget::GetGridPos( FVector2D in ) +{ + FVector2D result; + FVector2D topLeft = GetSkillTreePos(); + float scale = 32.0f * (RenderTransform.Scale.X / 2); + float XScale = (scale * 1.73205f); + float YScale = scale; + int32 XIndex = FMath::FloorToInt( ( in.X - topLeft.X ) / XScale ); + int32 YIndex = FMath::FloorToInt( ( in.Y - topLeft.Y ) / YScale ); + if ( XIndex % 2 == 0 ) + YIndex = roundToNearestOdd( YIndex ); + else + YIndex = roundToNearestEven(YIndex); + result.X = XIndex * XScale; + result.X += topLeft.X; + result.Y = YIndex * YScale; + result.Y += topLeft.Y; + m_lastGridIndex = FVector2D(XIndex, YIndex) - FVector2D(1, 0);// Store last grid position + return FVector4(result.X , result.Y , XIndex, YIndex); +} + +bool USkillWidget::CheckPosition(FVector2D in, TArray* points) +{ + bool result = true; + TArray localPoints; + //Check if positions are valid and available. + for ( int32 x1 = 0; x1 < skillAsset->hexMap.width; x1++ ) + { + for ( int32 y1 = 0; y1 < skillAsset->hexMap.height; y1++ ) + { + if ( skillAsset->hexMap.Get( x1, y1 ) ) + { + int32 x2 = FMath::FloorToInt( in.X ); + int32 y2; + int32 angle = FMath::FloorToInt( m_canvasPanel->RenderTransform.Angle ); + FIntPoint pos1 = RotateHexPos( FIntPoint( x1 - skillAsset->pivot.X, y1 - skillAsset->pivot.Y ), angle, !(skillAsset->pivot.X & 1) ); + pos1.X += skillAsset->pivot.X; + pos1.Y += skillAsset->pivot.Y; + if ( FMath::FloorToInt( in.X ) % 2 != 0 && pos1.X % 2 != 0 ) + y2 = FMath::FloorToInt( ( in.Y + 1 ) / 2 ); + else + y2 = FMath::FloorToInt( in.Y / 2 ); + FIntPoint pos2 = pos1 + FIntPoint( x2, y2 ); + localPoints.Push( pos2 ); + + if (pos2.X < 0 || pos2.X >= 13 || pos2.Y < 0 || pos2.Y >= ( (pos2.X & 1) ? 15 : 16 ) || parent->skillTreeAsset->hexMap.Get(pos2.X, pos2.Y) || parent->placedSkillHexMap->Get(pos2.X, pos2.Y)) + { + result = false; + if ( !points ) + { + break; + } + } + } + } + if ( !result && !points ) break; + } + if ( points ) ( *points ) = localPoints; + + /* + for ( int32 i = 0; i < parent->tiles.Num(); i++ ) + { + bool test = true; + for ( int32 j = 0; j < points.Num(); j++ ) + { + if ( parent->tiles[i]->x == points[j].X && parent->tiles[i]->y == points[j].Y ) + { + parent->tiles[i]->SetColorAndOpacity( FLinearColor::Yellow ); + parent->tiles[i]->SetVisibility( ESlateVisibility::Visible ); + test = false; + break; + } + } + if ( test ) + { + if ( !parent->skillTreeAsset->hexMap.Get( parent->tiles[i]->x, parent->tiles[i]->y ) ) + { + parent->tiles[i]->SetColorAndOpacity( FLinearColor::White ); + parent->tiles[i]->SetVisibility( ESlateVisibility::Visible ); + } + else + { + parent->tiles[i]->SetColorAndOpacity( FLinearColor::White ); + parent->tiles[i]->SetVisibility( ESlateVisibility::Hidden ); + } + } + } + */ + return result; +} + +void USkillWidget::SetSkillRotation(float angle) +{ + if(m_canvasPanel) m_canvasPanel->SetRenderAngle(angle); + else YWARNING("No canvas panel found!"); + NativeOnSkillMove(); + OnRotate(); +} + +void USkillWidget::MoveSkill(FVector2D offset) +{ + //Check if odd + FVector2D tmpOffset = offset; + if (((int32)m_lastGridIndex.X) & 1 && tmpOffset.X != 0) tmpOffset.Y -= 1; + if (!(((int32)m_lastGridIndex.X) & 1) && tmpOffset.X != 0) tmpOffset.Y += 1; + m_lastGridIndex += tmpOffset; + + if(parent->lastGridIndex != m_lastGridIndex) + { + parent->lastGridIndex = m_lastGridIndex; // Store last index + NativeOnSkillMove(); + } +} + +void USkillWidget::MoveSkillAbsolute(FVector2D gridIndex) +{ + m_lastGridIndex = gridIndex; + parent->lastGridIndex = m_lastGridIndex; // Store last index + NativeOnSkillMove(); +} + +void USkillWidget::ChangeColor(FLinearColor color) +{ + for ( int32 i = 0; i < m_hexagons.Num(); i++ ) + { + if (m_hexagons[i]->material) m_hexagons[i]->material->SetVectorParameterValue("Skill_Color", color); + } +} + +void USkillWidget::UpdateLevel( float level ) +{ + if (!m_dragable) return; + for (int32 i = 0; i < m_hexagons.Num(); i++) + { + if( m_hexagons[i]->material ) m_hexagons[i]->material->SetScalarParameterValue("Skill_Level", level); + } + + float offset = 1.0f - level; + float skillHeight = 13.0f * offset; + float height = FMath::CeilToFloat(skillHeight); + int32 hexcount = 0; + + for (int32 i = 0; i < placedPoints.Num(); i++) + { + float YPos = ( placedPoints[i].X & 1 ) ? (float(placedPoints[i].Y) + 0.5f) : (float(placedPoints[i].Y)); + if ( YPos < height ) hexcount++; + } + + m_power = float(placedPoints.Num()) / float(hexcount); +} + +void USkillWidget::UpdateInfo(float skilltreeHeight, float skilltreeOffset) +{ + for (int32 i = 0; i < m_hexagons.Num(); i++) + { + if (m_hexagons[i]->material) + { + m_hexagons[i]->material->SetScalarParameterValue("Skill_Tree_Height", skilltreeHeight); + m_hexagons[i]->material->SetScalarParameterValue("Skill_Offset", skilltreeOffset); + } + } +} + +TArray USkillWidget::ConnectedSkills(TArray& points) +{ + //Check if surrounding positions contain another skill. + //if ( !PointsCointainsRoot(localPoints)) + //{ + TArray result; + const FIntPoint offsets[2][6]{ + { FIntPoint(0, -1), FIntPoint(1, -1), FIntPoint(1, 0), FIntPoint(0, 1), FIntPoint(-1, 0), FIntPoint(-1, -1) }, + { FIntPoint(0, -1), FIntPoint(1, 0), FIntPoint(1, 1), FIntPoint(0, 1), FIntPoint(-1, 1), FIntPoint(-1, 0) } }; + for (int32 i = 0; i < points.Num(); i++) + { + int32 k = points[i].X & 1; + for (int32 j = 0; j < 6; j++) + { + FIntPoint checkPoint = points[i] + offsets[k][j]; + if ( points.Contains( checkPoint ) ) continue; + if (parent->skillTreeAsset->hexMap.Get(checkPoint.X, checkPoint.Y)) continue; + if (parent->placedSkillHexMap->Get(checkPoint.X, checkPoint.Y)) + { + USkillWidget* widget = *parent->tileMap.Find(checkPoint); + if (result.Contains(widget)) continue; + result.Push(widget); + } + } + } + return result; + //} +} + +bool USkillWidget::ContainsRoot(TArray& points) +{ + return PointsCointainsRoot(points); +} + +TArray USkillWidget::GetPoints() +{ + TArray points; + CheckPosition(m_lastGridIndex, &points); + return points; +} + +bool USkillWidget::PointsCointainsRoot( TArray& points ) +{ + for ( int32 i = 0; i < points.Num(); i++ ) + { + if ( points[i] == parent->rootPoint ) return true; + } + return false; +} + +void USkillWidget::NativeOnSkillMove() +{ + UpdateSkillColor(); + + OnMove(); +} + +void USkillWidget::NativeTick( const FGeometry& geometry, float deltaTime ) +{ + FVector2D mousePos; + UWidgetLayoutLibrary::GetMousePositionScaledByDPI(GetOwningPlayer(), mousePos.X, mousePos.Y); + + if ( m_dragging && !m_controller ) + { + FVector2D newPos = mousePos + m_offset; + + UCanvasPanelSlot* tmpSlot = Cast( Slot ); + if ( tmpSlot ) + { + FVector4 gridresult = GetGridPos( newPos ); + FVector2D gridPos = FVector2D( gridresult.X, gridresult.Y ); + FVector2D gridIndex = FVector2D( gridresult.Z, gridresult.W ); + if(gridPos != m_lastGridPos) + { + NativeOnSkillMove(); + m_lastGridPos = gridPos; + } + } + else + YWARNING( "Couldn't get Slot." ); + } + else if( !m_dragging ) + { + if (hovered) + { + ChangeColor(GetShapeTypeColor() * 2.0f); + } + else + { + ChangeColor(GetShapeTypeColor() * 0.5f); + } + if(parent->draggingWidget == this) + parent->StopDragging(this); + } + if ( m_selected && m_enabled) + { + FVector2D mousePos; + UWidgetLayoutLibrary::GetMousePositionScaledByDPI( GetOwningPlayer(), mousePos.X, mousePos.Y ); + FVector2D distanceVect = mousePos - m_mousedownPos; + if ( distanceVect.SizeSquared() > 100 ) + { + parent->NewSkill( this ); + m_selected = false; + } + } + + UCanvasPanelSlot* tmpSlot = UWidgetLayoutLibrary::SlotAsCanvasSlot(parent->GetCanvas()); + if (m_dragable) + { + float scale = 32.0f * (RenderTransform.Scale.X / 2); + float XScale = (scale * 1.73205f); + float YScale = scale; + + FVector2D topLeft = GetSkillTreePos(); + FVector2D result; + result.X = (m_lastGridIndex.X) * XScale; + result.X += topLeft.X; + result.Y = (m_lastGridIndex.Y) * YScale; + result.Y += topLeft.Y; + + UCanvasPanelSlot* selfSlot = UWidgetLayoutLibrary::SlotAsCanvasSlot(this); + if (selfSlot) + { + selfSlot->SetPosition(result); + } + } + + Super::NativeTick( geometry, deltaTime ); +} + +FLinearColor USkillWidget::GetShapeTypeColor() const +{ + if(selectedEffect == -1) + return FLinearColor(1.0f, 1.0f, 1.0f) * 1.0f; // Hwhite + + static FLinearColor shapeTypeColors[] = + { + FLinearColor(0.1f, 1.0f, 0.3f) * 0.75f, // Green + FLinearColor(1.0f, 0.3f, 0.1f) * 0.6f, // Red-Ish + FLinearColor(0.0f, 0.0f, 1.0f) * 0.6f, // Blue + }; + + int32 idx = 0; + if(skillAsset) + { + idx = (int32)skillAsset->abilityEffects[selectedEffect]->abilityCategory; + } + idx = FMath::Clamp(idx, 0, 2); + return FLinearColor(shapeTypeColors[idx].R, shapeTypeColors[idx].G, shapeTypeColors[idx].B, 1.0f); +} + +void USkillWidget::SetDragable(bool dragable, bool controller /*= false */) +{ + m_controller = controller; + m_dragable = dragable; + UpdateSkill(); +} diff --git a/Source/UnrealProject/GUI/SkillTree/SkillWidget.h b/Source/UnrealProject/GUI/SkillTree/SkillWidget.h new file mode 100644 index 0000000..dc7cd80 --- /dev/null +++ b/Source/UnrealProject/GUI/SkillTree/SkillWidget.h @@ -0,0 +1,191 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "SubMenu.h" +#include "SkillWidget.generated.h" + +/** + * + */ +class UBaseSkillObject; +class UBorder; +class USkillTreeWidget; +class UHexagonTile; + +UCLASS() +class UNREALPROJECT_API USkillWidget : public USubMenu +{ + GENERATED_BODY() +private: + //Variables + + //Skill State + bool m_dragable = false; + bool m_dragging = false; + bool m_selected = false; + bool m_enabled = true; + bool m_hovering = false; + bool m_placed = false; + bool m_controller = false; + //SKill Widget pointers + UCanvasPanelSlot* m_slot; + UCanvasPanel* m_canvasPanel; + //Positions + FVector2D m_mousedownPos; + FVector2D m_offset; + FVector2D m_lastGridIndex; + FVector2D m_lastGridPos; + FVector2D m_hexSize; + + UPROPERTY() + UTexture2D* m_skillIcon; + + //Hexagons + TArray m_hexagons; + float m_power; + + //Functions + bool PointsCointainsRoot( TArray& points ); +public: + //Variables + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "UI" ) + UBaseSkillObject* skillAsset; + + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "UI") + USkillTreeWidget* parent; + + UPROPERTY(BlueprintReadOnly, Category = "UI") + bool hovered; + + UPROPERTY(BlueprintReadOnly) + int32 selectedEffect; + + UPROPERTY(BlueprintReadWrite, Category = "UI") + FVector2D tooltipAreaPosition; + UPROPERTY(BlueprintReadWrite, Category = "UI") + FVector2D tooltipAreaSize; + + TArray placedPoints; + + UFUNCTION(BlueprintCallable, Category = "UI") + bool IsUsingController() const { return m_controller; } + + void UpdateSkillColor(); + void UpdateSkillIcon(); + + // Checks if a point is contained in on of the hexagons of this skill + bool ContainsPoint(FVector2D screenPoint); + // Checks if the mouse is contained within one of the hexagons + bool ContainsMouse(); + + // Updates the selected effect for this skill widget + void SetSelectedEffect(int32 effect); + // Return the ability info class selected by this skill widget + class UAbilityInfo* GetSelectedEffectAbility() const; + + UFUNCTION(BlueprintImplementableEvent, Category = "UI") + void OnRequireSkillEffect(); + UFUNCTION(BlueprintImplementableEvent, Category = "UI") + void OnPlaceDone(bool successful); + UFUNCTION(BlueprintImplementableEvent, Category = "UI") + void OnStartDragging(); + UFUNCTION(BlueprintImplementableEvent, Category = "UI") + void OnRotate(); + UFUNCTION(BlueprintImplementableEvent, Category = "UI") + void OnMove(); + + UFUNCTION( BlueprintCallable, Category = "UI" ) + void Select(); + UFUNCTION( BlueprintCallable, Category = "UI" ) + void Release() + { + m_selected = false; + }; + void Deselect(); + void Enable() + { + m_enabled = true; + ChangeColor( FLinearColor( 0.1216f, 0.9569f, 1.0f ) ); + } + void Disable() + { + m_enabled = false; + ChangeColor( FLinearColor::Gray ); + } + UFUNCTION(BlueprintCallable, Category = "UI") + void UpdateSkill(); + UFUNCTION(BlueprintCallable, Category = "UI") + bool GetEnabled() + { + return m_enabled; + } + FVector2D GetSkillTreePos(); + FVector2D GetViewportPos(); + FVector4 GetGridPos( FVector2D in ); + FVector2D GetPositionFromIndex(FIntPoint in); + bool CheckPosition( FVector2D in, TArray* points = NULL ); + UFUNCTION( BlueprintCallable, Category = "UI" ) + void StartDragging(); + UFUNCTION( BlueprintCallable, Category = "UI" ) + void StopDragging(); + + FVector2D GetGridIndex() const + { + return m_lastGridIndex; + } + void PlaceOnGridIndex(FVector2D index); + void SetPlaced(); + void NativeOnSkillMove(); + UFUNCTION(BlueprintCallable, Category = "UI") + bool PlaceSkill(); + UFUNCTION(BlueprintCallable, Category = "UI") + void Remove(); + UFUNCTION(BlueprintCallable, Category = "UI") + bool RestartDragging(); + + FLinearColor GetShapeTypeColor() const; + + UFUNCTION(BlueprintCallable, Category = "UI") + void SetDragable( bool dragable, bool controller = false ); + UFUNCTION( BlueprintCallable, Category = "UI" ) + bool GetDragable() + { + return m_dragable; + } + UFUNCTION( BlueprintCallable, Category = "UI" ) + bool GetDragging() + { + return m_dragging; + } + UFUNCTION( BlueprintCallable, Category = "UI" ) + void SetSkillRotation( float angle ); + UFUNCTION( BlueprintCallable, Category = "UI" ) + float GetSkillRotation() + { + if ( m_canvasPanel ) return m_canvasPanel->RenderTransform.Angle; + else YWARNING("No canvas panel found!"); + return 0; + } + UFUNCTION(BlueprintCallable, Category = "UI") + void MoveSkill(FVector2D offset); + UFUNCTION(BlueprintCallable, Category = "UI") + void MoveSkillAbsolute(FVector2D gridIndex); + + float GetPower() + { + return m_power; + } + + void ChangeColor( FLinearColor color ); + void UpdateLevel( float level ); + UFUNCTION(BlueprintCallable, Category = "UI") + void UpdateInfo( float skilltreeHeight, float skilltreeOffset ); + bool ContainsRoot(TArray& points); + TArray ConnectedSkills(TArray& points); + TArray GetPoints(); + + virtual void NativeConstruct() override; + virtual void NativeDestruct() override; + virtual void NativeTick( const FGeometry& geometry, float deltaTime ) override; +}; diff --git a/Source/UnrealProject/GUI/SkillTree/SpellInfo.cpp b/Source/UnrealProject/GUI/SkillTree/SpellInfo.cpp new file mode 100644 index 0000000..5d21371 --- /dev/null +++ b/Source/UnrealProject/GUI/SkillTree/SpellInfo.cpp @@ -0,0 +1,65 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "SpellInfo.h" +#include "DefaultPlayerController.h" + +USpellInfo::USpellInfo(const FObjectInitializer& init) + : Super(init) +{ + showDuration = 1.0f; +} + +void USpellInfo::NativeTick(const FGeometry& MyGeometry, float InDeltaTime) +{ + m_life -= InDeltaTime; + if(m_life <= 0.0f) + { + RemoveFromParent(); + } + + Super::NativeTick(MyGeometry, InDeltaTime); +} + +void USpellInfo::Set(class UAbilityInfo* info, int32 level, float power, class USpellInfoDisplay* parent) +{ + m_ability = info; + m_parent = parent; + m_life = showDuration; + m_level = level; + m_power = power; + + ADefaultPlayerController* pc = Cast(GetOwningPlayer()); + int32 slot; + bool alt; + if(pc && pc->GetAbilityButtonLocation(info, slot, alt)) + { + SetButtonHint(slot, alt); + } + + int32 lastLevel = pc->GetCurrentAbilityLevel(m_ability); + + OnSetAbility(info, lastLevel == 0); +} + +float USpellInfo::GetLifeTimeRate() const +{ + return (float)m_life / (float)showDuration; +} + +int32 USpellInfo::GetLevel() const +{ + return m_level; +} +float USpellInfo::GetPower() const +{ + return m_power; +} + +void USpellInfo::SetButtonHint_Implementation(int32 buttonSlot, bool alt) +{ +} + +void USpellInfo::OnSetAbility_Implementation(class UAbilityInfo* info, bool isNew) +{ +} diff --git a/Source/UnrealProject/GUI/SkillTree/SpellInfo.h b/Source/UnrealProject/GUI/SkillTree/SpellInfo.h new file mode 100644 index 0000000..f51d5f5 --- /dev/null +++ b/Source/UnrealProject/GUI/SkillTree/SpellInfo.h @@ -0,0 +1,42 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "Blueprint/UserWidget.h" +#include "SpellInfo.generated.h" + +/** + * + */ +UCLASS() +class UNREALPROJECT_API USpellInfo : public UUserWidget +{ + GENERATED_BODY() +public: + USpellInfo(const FObjectInitializer& init); + void NativeTick(const FGeometry& MyGeometry, float InDeltaTime) override; + + UFUNCTION(BlueprintCallable, Category="GUI") + void Set(class UAbilityInfo* info, int32 level, float power, class USpellInfoDisplay* parent); + UFUNCTION(BlueprintCallable, Category = "GUI") + float GetLifeTimeRate() const; + UFUNCTION(BlueprintCallable, Category = "Ability") + int32 GetLevel() const; + UFUNCTION(BlueprintCallable, Category = "Ability") + float GetPower() const; + + UFUNCTION(BlueprintNativeEvent) + void OnSetAbility(class UAbilityInfo* info, bool isNew); + UFUNCTION(BlueprintNativeEvent) + void SetButtonHint(int32 buttonSlot, bool alt); + + UPROPERTY(EditDefaultsOnly) + float showDuration; + +private: + UAbilityInfo* m_ability; + class USpellInfoDisplay* m_parent; + float m_life; + int32 m_level; + float m_power; +}; diff --git a/Source/UnrealProject/GUI/SkillTree/SpellInfoDisplay.cpp b/Source/UnrealProject/GUI/SkillTree/SpellInfoDisplay.cpp new file mode 100644 index 0000000..48c17b0 --- /dev/null +++ b/Source/UnrealProject/GUI/SkillTree/SpellInfoDisplay.cpp @@ -0,0 +1,36 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "SpellInfoDisplay.h" +#include "BaseSkillObject.h" +#include "SpellInfo.h" + +void USpellInfoDisplay::NativeConstruct() +{ + m_container = Cast(WidgetTree->FindWidget("Container")); +} + +void USpellInfoDisplay::OnLevelUp(class ANetworkPlayer* _player, TArray updatedSkills) +{ + if (!m_container) + return; + + player = _player; + + for (int32 i = 0; i < updatedSkills.Num(); i++) + { + USpellInfo* info = CreateWidget(GetWorld(), spellInfoClass); + UPanelSlot* slot = m_container->AddChild(info); + if (Cast(slot)) + { + Cast(slot)->SetHorizontalAlignment(HAlign_Right); + } + else if(Cast(slot)) + { + UVerticalBoxSlot* vslot = Cast(slot); + vslot->SetHorizontalAlignment(HAlign_Left); + Cast(slot)->SetVerticalAlignment(VAlign_Fill); + } + info->Set(updatedSkills[i].selectedEffect, updatedSkills[i].level, updatedSkills[i].power, this); + } +} diff --git a/Source/UnrealProject/GUI/SkillTree/SpellInfoDisplay.h b/Source/UnrealProject/GUI/SkillTree/SpellInfoDisplay.h new file mode 100644 index 0000000..f2cfdc8 --- /dev/null +++ b/Source/UnrealProject/GUI/SkillTree/SpellInfoDisplay.h @@ -0,0 +1,29 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "Blueprint/UserWidget.h" +#include "IngameSkillTree.h" +#include "SpellInfoDisplay.generated.h" + +/** + * + */ +UCLASS() +class UNREALPROJECT_API USpellInfoDisplay : public UUserWidget +{ + GENERATED_BODY() + +public: + void NativeConstruct() override; + void OnLevelUp(class ANetworkPlayer* player, TArray updatedSkills); + + UPROPERTY(BlueprintReadWrite) + ANetworkPlayer* player; + + UPROPERTY(EditDefaultsOnly) + TSubclassOf spellInfoClass; + +private: + UPanelWidget* m_container; +}; diff --git a/Source/UnrealProject/GUI/Submenus/CharacterDependentSubMenus.cpp b/Source/UnrealProject/GUI/Submenus/CharacterDependentSubMenus.cpp new file mode 100644 index 0000000..39ed129 --- /dev/null +++ b/Source/UnrealProject/GUI/Submenus/CharacterDependentSubMenus.cpp @@ -0,0 +1,61 @@ +#include "UnrealProject.h" +#include "CharacterDependentSubMenus.h" +#include "DefaultGameInstance.h" +#include "CharacterSubMenu.h" +#include "CharacterCarousel.h" +#include "EffectSelector.h" +#include "SkillWidget.h" + +void UCharacterDependentSubMenu::OnEnter(class UMenuScreenBase* screen) +{ + // Find character menu + auto submenus = screen->GetSubMenus(); + for(int32 i = submenus.Num() - 1; i >= 0; i--) + { + if(submenus[i]->IsA()) + { + characterSubMenu = Cast(submenus[i]); + characterCarousel = characterSubMenu->GetCarousel(); + displayCharacter = characterCarousel->GetCharacterModel(characterCarousel->selectedCharacterIndex); + } + } + + Super::OnEnter(screen); +} + +void UCharacterDependentSubMenu::OnSuspend(USubMenu* newSubMenu, bool loseFocus) +{ + USkillWidget* sw = Cast(newSubMenu); + UEffectSelector* es = Cast(newSubMenu); + if(es || sw) + USubMenu::OnSuspend(newSubMenu, loseFocus); // Prevent menu panel animations + else + Super::OnSuspend(newSubMenu, loseFocus); +} +void UCharacterDependentSubMenu::OnRestore(USubMenu* removedMenu) +{ + USkillWidget* sw = Cast(removedMenu); + UEffectSelector* es = Cast(removedMenu); + if(es || sw) + USubMenu::OnRestore(removedMenu); // Prevent menu panel animations + else + Super::OnRestore(removedMenu); +} + +FCharacterSave UCharacterDependentSubMenu::GetCharacterSave() const +{ + static FCharacterSave dummy; + if(!characterCarousel) + return dummy; + UDefaultGameInstance* inst = Cast(GetGameInstance()); + UCharacterSettings* settings = inst->GetCharacterSettings(); + return settings->characterSaves[characterCarousel->selectedCharacterIndex]; +} +void UCharacterDependentSubMenu::SetCharacterSave(const FCharacterSave& save) +{ + if(!characterCarousel) + return; + UDefaultGameInstance* inst = Cast(GetGameInstance()); + UCharacterSettings* settings = inst->GetCharacterSettings(); + settings->characterSaves[characterCarousel->selectedCharacterIndex] = save; +} diff --git a/Source/UnrealProject/GUI/Submenus/CharacterDependentSubMenus.h b/Source/UnrealProject/GUI/Submenus/CharacterDependentSubMenus.h new file mode 100644 index 0000000..fa0b4b0 --- /dev/null +++ b/Source/UnrealProject/GUI/Submenus/CharacterDependentSubMenus.h @@ -0,0 +1,50 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "MenuScreen.h" +#include "CharacterSettings.h" +#include "CharacterDependentSubMenus.generated.h" + +UCLASS() +class UCharacterDependentSubMenu : public UMenuPanel +{ + GENERATED_BODY() +public: + virtual void OnEnter(class UMenuScreenBase* screen) override; + + virtual void OnSuspend(USubMenu* newSubMenu, bool loseFocus) override; + virtual void OnRestore(USubMenu* removedMenu) override; + + UPROPERTY(BlueprintReadOnly, Category = "Skill Tree Sub Menu") + class UCharacterSubMenu* characterSubMenu; + UPROPERTY(BlueprintReadOnly, Category = "Skill Tree Sub Menu") + class ACharacterCarousel* characterCarousel; + // Curently displayed character in the menu + UPROPERTY(BlueprintReadOnly, Category = "Skill Tree Sub Menu") + class ACharacterBase* displayCharacter; + + UFUNCTION(BlueprintCallable, Category = "Skill Tree Sub Menu") + FCharacterSave GetCharacterSave() const; + UFUNCTION(BlueprintCallable, Category = "Skill Tree Sub Menu") + void SetCharacterSave(const FCharacterSave& save); +}; + +UCLASS() +class UCustomizationSubMenu : public UCharacterDependentSubMenu +{ + GENERATED_BODY() +public: +}; +UCLASS() +class USkillTreeSubMenu : public UCharacterDependentSubMenu +{ + GENERATED_BODY() +public: +}; +UCLASS() +class USkillSelectByAbilitySubMenu : public UCharacterDependentSubMenu +{ + GENERATED_BODY() +public: +}; \ No newline at end of file diff --git a/Source/UnrealProject/GUI/Submenus/CharacterSubMenu.cpp b/Source/UnrealProject/GUI/Submenus/CharacterSubMenu.cpp new file mode 100644 index 0000000..bd781ed --- /dev/null +++ b/Source/UnrealProject/GUI/Submenus/CharacterSubMenu.cpp @@ -0,0 +1,164 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "CharacterSubMenu.h" +#include "MenuButton.h" +#include "CharacterCarousel.h" +#include "MenuController.h" +#include "DefaultGameInstance.h" +#include "CharacterSettings.h" +#include "SkillTreeWidget.h" +#include "CharacterBase.h" + +TSubclassOf carouselClass; + +UCharacterSubMenu::UCharacterSubMenu(const FObjectInitializer& init) +{ + carouselClass = ConstructorHelpers::FClassFinder(L"/Game/Assets/Blueprints/CharacterCarousel/BP_CharacterCarousel").Class; +} + +void UCharacterSubMenu::NativeConstruct() +{ + // Get carousel spawn pos + FTransform carouselSpawnTransform; + for(TActorIterator it(GetWorld()); it; ++it) + { + if((*it)->PlayerStartTag == "Carousel") + carouselSpawnTransform = (*it)->GetTransform(); + } + + m_carousel = GetWorld()->SpawnActor(carouselClass, carouselSpawnTransform); + + // Initialize the characters + for(int32 i = 0; i < m_carousel->GetCharacterNum(); i++) + m_UpdateCharacter(i); + + Super::NativeConstruct(); + + m_nextButton = AddButton("Next"); + m_nextButton->onPressed.AddDynamic(this, &UCharacterSubMenu::m_Next); + m_prevButton = AddButton("Previous"); + m_prevButton->onPressed.AddDynamic(this, &UCharacterSubMenu::m_Prev); + m_editButton = AddButton("Edit"); + m_editButton->onPressed.AddDynamic(this, &UCharacterSubMenu::m_Edit); + m_newButton = AddButton("New"); + m_newButton->onPressed.AddDynamic(this, &UCharacterSubMenu::m_New); + m_deleteButton = AddButton("Delete"); + m_deleteButton->onPressed.AddDynamic(this, &UCharacterSubMenu::m_Delete); + m_backButton = AddButton("Back"); + m_backButton->onPressed.AddDynamic(this, &UCharacterSubMenu::m_Back); + SetBackButton(m_backButton); +} + +void UCharacterSubMenu::NativeDestruct() +{ + Super::NativeDestruct(); + m_carousel->Destroy(); +} + +void UCharacterSubMenu::UpdateCurrentCharacter() +{ + if(m_carousel) + { + m_UpdateCharacter(m_carousel->selectedCharacterIndex); + } +} + +FCharacterClassProperty UCharacterClassPropertySet::GetCharacterClass(int32 classID) const +{ + static FCharacterClassProperty dummy; + if(classes.Num() > 0) + { + return classes[FMath::Clamp(classID, 0, classes.Num() - 1)]; + } + return dummy; +} + +FCharacterClassProperty UCharacterSubMenu::GetCharacterClass(int32 classID) const +{ + return characterClassProperties->GetCharacterClass(classID); +} + +TArray UCharacterSubMenu::GetClassAbilities() const +{ + TArray res; + if(characterClassProperties) + { + for(auto cls : characterClassProperties->classes) + { + res.Add(cls.basicAttack); + } + } + return res; +} + +void UCharacterSubMenu::m_Next() +{ + m_carousel->Next(); +} +void UCharacterSubMenu::m_Prev() +{ + m_carousel->Previous(); +} +void UCharacterSubMenu::m_New() +{ + m_carousel->CreateNewCharacter(FString("New Character ") + FString::FromInt(m_carousel->GetCharacterNum()+1)); + m_UpdateCharacter(m_carousel->GetCharacterNum() - 1); +} +void UCharacterSubMenu::m_Delete() +{ + if(m_carousel->GetCharacterNum() > 0) + m_carousel->DeleteCharacter(m_carousel->selectedCharacterIndex); +} +void UCharacterSubMenu::m_Edit() +{ + TArray options; options.Add("OK"); + if(m_carousel->GetCharacterNum() > 0) + { + // Make this the active character + AMenuController* controller = Cast(GetOwningPlayer()); + UDefaultGameInstance* inst = Cast(GetGameInstance()); + + // Get save game + FCharacterSave& save = inst->GetCharacterSettings()->characterSaves[m_carousel->selectedCharacterIndex]; + controller->skillTree->BuildFromState(save.skillTreeState); + + OnEditCharacter(); + } + else + { + ShowMessageBox(FOverlayItemClosed(), "Error", "There are no characters", options, ""); + } +} +void UCharacterSubMenu::m_Back() +{ + CloseSubMenu(); +} + +void UCharacterSubMenu::m_UpdateCharacter(int32 index) +{ + // Make this the active character + AMenuController* controller = Cast(GetOwningPlayer()); + UDefaultGameInstance* inst = Cast(GetGameInstance()); + + // Get save game + FCharacterSave& save = inst->GetCharacterSettings()->characterSaves[index]; + + ACharacterBase* character = m_carousel->GetCharacterModel(index); + if(character) + { + character->SetCustomizations(save.characterCustomization); + character->EquipSkillsFromSkillTreeState(save.skillTreeState); + + // Set class specific equipment + if(characterClassProperties) + { + FCharacterClassProperty properties = characterClassProperties->GetCharacterClass(save.characterClass); + character->EquipItems(properties.classItems); + } + } + else + { + GERROR("Can't update character " + index + ", it does not have a character model."); + } +} diff --git a/Source/UnrealProject/GUI/Submenus/CharacterSubMenu.h b/Source/UnrealProject/GUI/Submenus/CharacterSubMenu.h new file mode 100644 index 0000000..ec2b6fa --- /dev/null +++ b/Source/UnrealProject/GUI/Submenus/CharacterSubMenu.h @@ -0,0 +1,65 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "GUI/Menu/MenuScreen.h" +#include "PlayerSetupState.h" +#include "CharacterSubMenu.generated.h" + +/** + * + */ +UCLASS() +class UNREALPROJECT_API UCharacterSubMenu : public UMenuPanel +{ + GENERATED_BODY() + +public: + UCharacterSubMenu(const FObjectInitializer& init); + virtual void NativeConstruct() override; + virtual void NativeDestruct() override; + + UFUNCTION(BlueprintCallable, Category = "Character Sub Menu") + void UpdateCharacter(int32 charIndex) { m_UpdateCharacter(index); } + UFUNCTION(BlueprintCallable, Category = "Character Sub Menu") + void UpdateCurrentCharacter(); + + UFUNCTION(BlueprintImplementableEvent, Category = "Character Sub Menu") + void OnEditCharacter(); + + UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Category = "Character Sub Menu") + class UCharacterClassPropertySet* characterClassProperties; + + UFUNCTION(BlueprintCallable, Category = "Character Class") + FCharacterClassProperty GetCharacterClass(int32 classID) const; + + UFUNCTION(BlueprintCallable, Category = "Character Class") + TArray GetClassAbilities() const; + + class ACharacterCarousel* GetCarousel() const { return m_carousel; } + +private: + UFUNCTION() + void m_Next(); + UFUNCTION() + void m_Prev(); + UFUNCTION() + void m_New(); + UFUNCTION() + void m_Delete(); + UFUNCTION() + void m_Back(); + UFUNCTION() + void m_Edit(); + + void m_UpdateCharacter(int32 index); + + class UMenuButton* m_nextButton; + class UMenuButton* m_prevButton; + class UMenuButton* m_newButton; + class UMenuButton* m_deleteButton; + class UMenuButton* m_backButton; + class UMenuButton* m_editButton; + + class ACharacterCarousel* m_carousel; +}; diff --git a/Source/UnrealProject/GUI/Submenus/CustomizationSubMenu.cpp b/Source/UnrealProject/GUI/Submenus/CustomizationSubMenu.cpp new file mode 100644 index 0000000..e1a6387 --- /dev/null +++ b/Source/UnrealProject/GUI/Submenus/CustomizationSubMenu.cpp @@ -0,0 +1,5 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "CharacterDependentSubMenus.h" + diff --git a/Source/UnrealProject/GUI/Submenus/SkillBrowserSubMenu.cpp b/Source/UnrealProject/GUI/Submenus/SkillBrowserSubMenu.cpp new file mode 100644 index 0000000..89d8214 --- /dev/null +++ b/Source/UnrealProject/GUI/Submenus/SkillBrowserSubMenu.cpp @@ -0,0 +1,114 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "SkillBrowserSubMenu.h" +#include "MenuController.h" +#include "SkillTreeWidget.h" +#include "SkillTreeObject.h" +#include "BaseSkillObject.h" +#include "SkillWidget.h" + +void USkillBrowserItem::NativeConstruct() +{ + Super::NativeConstruct(); +} + +void USkillBrowserItem::NativeTick(const FGeometry& MyGeometry, float InDeltaTime) +{ + Super::NativeTick(MyGeometry, InDeltaTime); + + if(skillTree->IsUsingSkill(skillObject)) + { + this->SetIsEnabled(false); + } + else + { + this->SetIsEnabled(true); + } +} +void USkillBrowserItem::Init(UAbilityInfo* info) +{ + ability = info; + OnInit(); +} + +void USkillBrowserItem::NativeOnSelectionChanged(bool selected, bool controller) +{ + Super::NativeOnSelectionChanged(selected, controller); + if(selected && category && controller) + { + UScrollBox* sb = Cast(category->Slot->Parent); + if(sb) + { + sb->ScrollWidgetIntoView(category); + } + } +} + +void USkillBrowserItem::NativeOnPressed(bool controllerInput) +{ + Super::NativeOnPressed(controllerInput); + + if(!GetIsEnabled()) + return; + + USkillWidget* wdg = skillTree->NewSkillFromAsset(skillObject, controllerInput); + wdg->SetSelectedEffect(skillObject->abilityEffects.IndexOfByKey(ability)); + if(controllerInput) + GetSubMenu()->GetScreen()->OpenSubMenu(wdg); +} + +void USkillBrowserSubMenu::NativeConstruct() +{ + m_skillItemContainer = Cast(WidgetTree->FindWidget("ItemContainer")); + AMenuController* menuController = Cast(GetOwningPlayer()); + USkillTreeWidget* skillTree = menuController->skillTree; + USkillTreeObject* skObject = skillTree->skillTreeAsset; + + for(int32 i = 0; i < skObject->skills.Num(); i++) + { + UBaseSkillObject* skill = skObject->skills[i]->GetDefaultObject(); + + // Create a category item + USkillBrowserItemCategory* categoryItem = CreateWidget(GetWorld(), itemCategoryClass); + if(!categoryItem) + { + GERROR("Failed to create skill browser item category"); + continue; + } + + m_skillItemContainer->AddChild(categoryItem); + categoryItem->skillObject = skill; + + // Add all the abilities to this category + for(int32 j = 0; j < skill->abilityEffects.Num(); j++) + { + UAbilityInfo* effect = skill->abilityEffects[j]; + if(!effect) + continue; + USkillBrowserItem* item = CreateWidget(GetWorld(), itemClass); + if(!item) + { + GERROR("Failed to create skill browser item"); + continue; + } + item->skillObject = skill; + item->skillTree = skillTree; + item->category = categoryItem; + categoryItem->itemContainer->AddChild(item); + + // Call init on ability item + item->Init(effect); + } + + // Call init on category + categoryItem->OnInit(); + } + + Super::NativeConstruct(); +} + +void USkillBrowserItemCategory::NativeConstruct() +{ + itemContainer = Cast(WidgetTree->FindWidget("ItemContainer")); +} diff --git a/Source/UnrealProject/GUI/Submenus/SkillBrowserSubMenu.h b/Source/UnrealProject/GUI/Submenus/SkillBrowserSubMenu.h new file mode 100644 index 0000000..11ab81e --- /dev/null +++ b/Source/UnrealProject/GUI/Submenus/SkillBrowserSubMenu.h @@ -0,0 +1,67 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "CharacterDependentSubMenus.h" +#include "MenuButton.h" +#include "SkillBrowserSubMenu.generated.h" + +UCLASS() +class UNREALPROJECT_API USkillBrowserItemCategory : public UPanelUserWidget +{ + GENERATED_BODY() +public: + void NativeConstruct() override; + + UFUNCTION(BlueprintImplementableEvent) + void OnInit(); + + UPROPERTY(BlueprintReadOnly) + class UBaseSkillObject* skillObject; + + UPanelWidget* itemContainer; +}; + +UCLASS() +class UNREALPROJECT_API USkillBrowserItem : public UMenuButton +{ + GENERATED_BODY() +public: + void NativeConstruct() override; + void NativeTick(const FGeometry& MyGeometry, float InDeltaTime) override; + void Init(UAbilityInfo* info); + void NativeOnSelectionChanged(bool selected, bool controller) override; + + virtual void NativeOnPressed(bool controllerInput) override; + + UFUNCTION(BlueprintImplementableEvent) + void OnInit(); + + UPROPERTY(BlueprintReadOnly) + class USkillBrowserItemCategory* category; + UPROPERTY(BlueprintReadOnly) + class USkillTreeWidget* skillTree; + UPROPERTY(BlueprintReadOnly) + class UAbilityInfo* ability; + UPROPERTY(BlueprintReadOnly) + class UBaseSkillObject* skillObject; + +private: +}; + +UCLASS() +class UNREALPROJECT_API USkillBrowserSubMenu : public UCharacterDependentSubMenu +{ + GENERATED_BODY() +public: + void NativeConstruct() override; + + UPROPERTY(EditDefaultsOnly) + TSubclassOf itemClass; + + UPROPERTY(EditDefaultsOnly) + TSubclassOf itemCategoryClass; + +private: + UPanelWidget* m_skillItemContainer; +}; diff --git a/Source/UnrealProject/GUI/Submenus/SkillTreeSubMenu.cpp b/Source/UnrealProject/GUI/Submenus/SkillTreeSubMenu.cpp new file mode 100644 index 0000000..e1a6387 --- /dev/null +++ b/Source/UnrealProject/GUI/Submenus/SkillTreeSubMenu.cpp @@ -0,0 +1,5 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "CharacterDependentSubMenus.h" + diff --git a/Source/UnrealProject/GUI/ToolTip/ToolTipText.cpp b/Source/UnrealProject/GUI/ToolTip/ToolTipText.cpp new file mode 100644 index 0000000..9916ae3 --- /dev/null +++ b/Source/UnrealProject/GUI/ToolTip/ToolTipText.cpp @@ -0,0 +1,14 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "ToolTipText.h" + + +void UToolTipText::SetTitle_Implementation(const FString& title) +{ + +} +void UToolTipText::SetText_Implementation(const FString& text) +{ + +} \ No newline at end of file diff --git a/Source/UnrealProject/GUI/ToolTip/ToolTipText.h b/Source/UnrealProject/GUI/ToolTip/ToolTipText.h new file mode 100644 index 0000000..5cfe77c --- /dev/null +++ b/Source/UnrealProject/GUI/ToolTip/ToolTipText.h @@ -0,0 +1,27 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "Blueprint/UserWidget.h" +#include "ToolTipText.generated.h" + +/** + * + */ +UCLASS() +class UNREALPROJECT_API UToolTipText : public UUserWidget +{ + GENERATED_BODY() + + +public: + UPROPERTY(BlueprintReadWrite, Category = "ToolTip") + class USizeBox* sizeBox; + + UFUNCTION(BlueprintNativeEvent, Category = "ToolTip") + void SetTitle(const FString& title); + + UFUNCTION(BlueprintNativeEvent, Category = "ToolTip") + void SetText(const FString& text); + +}; diff --git a/Source/UnrealProject/GUI/ToolTip/ToolTipWidget.cpp b/Source/UnrealProject/GUI/ToolTip/ToolTipWidget.cpp new file mode 100644 index 0000000..00c1f4b --- /dev/null +++ b/Source/UnrealProject/GUI/ToolTip/ToolTipWidget.cpp @@ -0,0 +1,108 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "ToolTipWidget.h" +#include "ToolTipText.h" +#include "WidgetLayoutLibrary.h" + + +void UToolTipWidget::NativeConstruct() +{ + Super::NativeConstruct(); + + m_toolTip = CreateWidget(GetWorld(), textWidget); + canvas->AddChild(m_toolTip); + m_toolTip->SetVisibility(ESlateVisibility::Hidden); + + showTooltip = false; +} +void UToolTipWidget::NativeDestruct() +{ + Super::NativeDestruct(); +} + +void UToolTipWidget::NativeTick(const FGeometry& geometry, float deltaTime) +{ + Super::NativeTick(geometry, deltaTime); + + if (!showTooltip) + { + m_toolTip->SetVisibility(ESlateVisibility::Hidden); + } + else + { + UWorld* const world = GetWorld(); + if (!IsValid(world)) return; + + const FVector2D screenSize = FVector2D(world->GetGameViewport()->Viewport->GetSizeXY().X, world->GetGameViewport()->Viewport->GetSizeXY().Y); + const float viewportScale = UWidgetLayoutLibrary::GetViewportScale(this); + + m_toolTip->SetVisibility(ESlateVisibility::Visible); + + FVector2D currentPos = position + size + FVector2D(5, 5); + + if ((currentPos.X + m_toolTip->GetDesiredSize().X) * viewportScale >= screenSize.X) + currentPos.X = position.X - 5 - m_toolTip->GetDesiredSize().X; + if ((currentPos.Y + m_toolTip->GetDesiredSize().Y) * viewportScale >= screenSize.Y) + currentPos.Y = position.Y - 5 - m_toolTip->GetDesiredSize().Y; + + UCanvasPanelSlot* const slot = Cast(m_toolTip->Slot); + slot->SetPosition(currentPos); + slot->SetSize(m_toolTip->GetDesiredSize()); + } + + //UWorld* const world = GetWorld(); + //if (!IsValid(world)) return; + // + //FVector2D mousePos; + //const FVector2D screenSize = FVector2D(world->GetGameViewport()->Viewport->GetSizeXY().X, world->GetGameViewport()->Viewport->GetSizeXY().Y); + //const float viewportScale = UWidgetLayoutLibrary::GetViewportScale(this); + //if (UWidgetLayoutLibrary::GetMousePositionScaledByDPI(GetOwningPlayer(), mousePos.X, mousePos.Y)) + //{ + // for (int32 i = 0; i < m_handles.Num(); i++) + // { + // FToolTipHandle& handle = *m_handles[i]; + // UToolTipText& toolTip = *handle.m_toolTip; + // + // FVector2D triggerTopLeft; + // FVector2D triggerSize; + // const FVector2D scaledMouse = mousePos * viewportScale; + // const FVector2D scaledSize = handle.size * viewportScale; + // const FVector2D scaledPosition = handle.position * viewportScale; + // + // triggerTopLeft = handle.position; + // triggerSize = handle.size; + // + // mousePos = (scaledMouse - scaledPosition) / scaledSize; + // + // if (!(mousePos.X >= 0 && mousePos.X <= 1 && mousePos.Y >= 0 && mousePos.Y <= 1)) + // { + // toolTip.SetVisibility(ESlateVisibility::Hidden); + // return; + // } + // + // UCanvasPanelSlot* const slot = Cast(toolTip.Slot); + // + // toolTip.SetVisibility(ESlateVisibility::Visible); + // + // FVector2D currentPos = triggerTopLeft + triggerSize + FVector2D(5, 5); + // + // if ((currentPos.X + toolTip.GetDesiredSize().X) * viewportScale >= screenSize.X) + // currentPos.X = triggerTopLeft.X - 5 - toolTip.GetDesiredSize().X; + // if ((currentPos.Y + toolTip.GetDesiredSize().Y) * viewportScale >= screenSize.Y) + // currentPos.Y = triggerTopLeft.Y - 5 - toolTip.GetDesiredSize().Y; + // + // slot->SetPosition(currentPos); + // slot->SetSize(toolTip.GetDesiredSize()); + // } + //} +} + +void UToolTipWidget::SetTitle(const FString& title) +{ + m_toolTip->SetTitle(title); +} +void UToolTipWidget::SetText(const FString& text) +{ + m_toolTip->SetText(text); +} \ No newline at end of file diff --git a/Source/UnrealProject/GUI/ToolTip/ToolTipWidget.h b/Source/UnrealProject/GUI/ToolTip/ToolTipWidget.h new file mode 100644 index 0000000..784e54f --- /dev/null +++ b/Source/UnrealProject/GUI/ToolTip/ToolTipWidget.h @@ -0,0 +1,37 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "Blueprint/UserWidget.h" +#include "ToolTipWidget.generated.h" + +/** + * + */ + +UCLASS() +class UNREALPROJECT_API UToolTipWidget : public UUserWidget +{ + GENERATED_BODY() + +public: + virtual void NativeConstruct(); + virtual void NativeDestruct(); + virtual void NativeTick(const FGeometry& geometry, float deltaTime) override; + + UPROPERTY(EditAnywhere, Category = "ToolTip") + TSubclassOf textWidget; + + UPROPERTY(BlueprintReadWrite, Category = "ToolTip") + class UCanvasPanel* canvas; + + bool showTooltip; + FVector2D position; + FVector2D size; + + void SetTitle(const FString& title); + void SetText(const FString& text); + +private: + class UToolTipText* m_toolTip; +}; diff --git a/Source/UnrealProject/GUI/TransitionScreen.cpp b/Source/UnrealProject/GUI/TransitionScreen.cpp new file mode 100644 index 0000000..9975c38 --- /dev/null +++ b/Source/UnrealProject/GUI/TransitionScreen.cpp @@ -0,0 +1,8 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "TransitionScreen.h" + + + + diff --git a/Source/UnrealProject/GUI/TransitionScreen.h b/Source/UnrealProject/GUI/TransitionScreen.h new file mode 100644 index 0000000..42cd1b9 --- /dev/null +++ b/Source/UnrealProject/GUI/TransitionScreen.h @@ -0,0 +1,24 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "Blueprint/UserWidget.h" +#include "TransitionScreen.generated.h" + +/** + * + */ +UCLASS() +class UNREALPROJECT_API UTransitionScreen : public UUserWidget +{ + GENERATED_BODY() +public: + // When this menu needs to be hidden + UFUNCTION(BlueprintImplementableEvent) + void OnShow(); + // When this menu needs to be shown + UFUNCTION(BlueprintImplementableEvent) + void OnHide(); + + +}; diff --git a/Source/UnrealProject/GameState/AbilityBindings.h b/Source/UnrealProject/GameState/AbilityBindings.h new file mode 100644 index 0000000..5928954 --- /dev/null +++ b/Source/UnrealProject/GameState/AbilityBindings.h @@ -0,0 +1,24 @@ +#ifdef ABILITY_OP +ABILITY_ATTACK(0, slot0) +ABILITY_OP(1, slot1) +ABILITY_OP(2, slot2) +ABILITY_OP(3, slot3) +ABILITY_OP(4, slot4) +ABILITY_OP(5, slot5) +ABILITY_OP(6, slot6) +ABILITY_OP(7, slot7) +ABILITY_OP(8, slot8) +#undef ABILITY_OP +#endif +#ifdef ABILITY_KEYBINDING_OP +ABILITY_KEYBINDING_OP("Attack", 0) +ABILITY_KEYBINDING_OP("CastAbility1",1) +ABILITY_KEYBINDING_OP("CastAbility2", 2) +ABILITY_KEYBINDING_OP("CastAbility3", 3) +ABILITY_KEYBINDING_OP("CastAbility4", 4) +ABILITY_KEYBINDING_OP("CastAbility5", 5) +ABILITY_KEYBINDING_OP("CastAbility6", 6) +ABILITY_KEYBINDING_OP("CastAbility7", 7) +ABILITY_KEYBINDING_OP("CastAbility8", 8) +#undef ABILITY_KEYBINDING_OP +#endif \ No newline at end of file diff --git a/Source/UnrealProject/GameState/DefaultGameInstance.cpp b/Source/UnrealProject/GameState/DefaultGameInstance.cpp new file mode 100644 index 0000000..a94a7cd --- /dev/null +++ b/Source/UnrealProject/GameState/DefaultGameInstance.cpp @@ -0,0 +1,557 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "Runtime/Engine/Classes/Engine/LocalPlayer.h" + +#if PLATFORM_SPECIFIC_WIN == 0 +// Include input manager for windows to allow DS4 controllers +#include "InputManager.hpp" +using namespace Input; +#endif + +#include "DefaultGameInstance.h" +#include "CharacterSettings.h" +#include "Prefs.h" +#include "PlayerControllerBase.h" +#include "ScreenOverlay.h" +#include "SessionManager.h" +#include "ScreenOverlay.h" + +#include "MapData.h" +#include "MapList.h" +#include + +// Replicates the exact same data structure as FWindowsCursor except with a public cursor handle member +#if PLATFORM_SPECIFIC_WIN == 0 +class FWindowsCursor1 : public ICursor +{ +public: + EMouseCursor::Type CurrentType; + HCURSOR CursorHandles[EMouseCursor::TotalCursorCount]; +}; +HCURSOR originalCursor; +void InitWindowsCursors() +{ + // Store original application cursor for editor + FWindowsCursor1* windowsCursor = (FWindowsCursor1*)FSlateApplication::Get().GetPlatformCursor().Get(); + originalCursor = windowsCursor->CursorHandles[EMouseCursor::Default]; + +} +void ShutdownWindowsCursors() +{ + // Reset original cursor, mainly for editor + FWindowsCursor1* windowsCursor = (FWindowsCursor1*)FSlateApplication::Get().GetPlatformCursor().Get(); + windowsCursor->CursorHandles[EMouseCursor::Default] = originalCursor; +} +HCURSOR LoadCustomCursor(const FString& path) +{ + FString parentPath = FPaths::GameDir(); + FString cursorPath = parentPath + "\\" + path; + + HCURSOR cursorHandle = LoadCursorFromFileW(*cursorPath); + return cursorHandle; +} +void SetApplicationCursor(HCURSOR cursor) +{ + FWindowsCursor1* windowsCursor = (FWindowsCursor1*)FSlateApplication::Get().GetPlatformCursor().Get(); + if(windowsCursor) + { + HCURSOR handleDst; + if(cursor != INVALID_HANDLE_VALUE) + { + handleDst = cursor; + } + else + { + handleDst = LoadCursor(GetModuleHandle(0), IDC_ARROW); + } + + // Overwrite default cursor that unreal sets hack + windowsCursor->CursorHandles[EMouseCursor::Default] = handleDst; + } +} +#endif + +static UMapList* mapList; +UDefaultGameInstance::UDefaultGameInstance() +{ + mapList = ConstructorHelpers::FObjectFinder(TEXT("/Game/Assets/Levels/MapList")).Object; + menuLevelPath = "/Game/Assets/Levels/Menu"; + splashScreenShown = false; + +} + +void UDefaultGameInstance::Init() +{ + m_characterSettings = nullptr; + LoadSettings(); + +#if PLATFORM_SPECIFIC_WIN == 0 + InitWindowsCursors(); + SetApplicationCursor(LoadCustomCursor("cursor.cur")); +#endif + Super::Init(); + + m_prefs = nullptr; + LoadPrefs(); + + m_LoadMaps(); + +#if PLATFORM_SPECIFIC_WIN == 0 + // Initialize TEA Input + Input::InputManager::Initialize(); + Input::InputManager::GetInstance()->SearchForJoystick(); + m_tickDelegateHandle = FTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateUObject(this, &UDefaultGameInstance::m_Tick), 0.016666667f); +#endif + + m_localPlayer = nullptr; + + // Create session manager + sessionManager = NewObject(this); + sessionManager->m_instance = this; +} + +bool UDefaultGameInstance::HandleOpenCommand(const TCHAR* Cmd, FOutputDevice& Ar, UWorld* InWorld) +{ + GPRINT("Open command -> " + FString(Cmd) + " [" + InWorld->GetFullName() + "]"); + APlayerControllerBase* pcb = Cast(GetFirstLocalPlayerController()); + if(pcb && Super::HandleOpenCommand(Cmd, Ar, InWorld)) + { + FString msg = FString("Opening level [") + Cmd + "]"; + pcb->overlay->ShowOverlay(msg); + return true; + } + return false; +} + +bool UDefaultGameInstance::m_Tick(float DeltaSeconds) +{ +#if PLATFORM_SPECIFIC_WIN == 0 + InputManager* inputManager = Input::InputManager::GetInstance(); + + inputManager->Update(); + if(!inputManager->joystick) + { + std::vector names = inputManager->GetJoystickNames(); + for(size_t i = 0; i < names.size(); i++) + { + inputManager->ConnectJoystickByIndex(i); + if(inputManager->joystick) + break; + } + } +#endif + return true; +} + + +void UDefaultGameInstance::Shutdown() +{ + FTicker::GetCoreTicker().RemoveTicker(m_tickDelegateHandle); + Super::Shutdown(); + +#if PLATFORM_SPECIFIC_WIN == 0 + ShutdownWindowsCursors(); + Input::InputManager::Cleanup(); +#endif + + sessionManager->Shutdown(); +} + +const FString levelPath = TEXT("/Game/Assets/Levels"); +void UDefaultGameInstance::m_LoadMaps() +{ + if (mapList) + { + for (int32 i = 0; i < mapList->mapList.Num(); i++) + { + UMapData* mapData = mapList->mapList[i]; + + FString name = mapData->GetName(); + wchar_t* nameStrPtr = name.GetCharArray().GetData(); + std::wstring nameStr = std::wstring(nameStrPtr, nameStrPtr + name.Len()); + + //GWPRINT(mapData->GetName() + L"\n" + mapData->GetPathName() + L"\n" + mapData->GetFullGroupName(false)); + + static std::wregex nameRx = std::wregex(L"I_([_a-zA-Z0-9]+)"); + std::wsmatch match; + + if (std::regex_match(nameStr, match, nameRx)) + { + std::wstring levelNameStr = match[1]; + FString matchingLevelName = FString(levelNameStr.c_str()); + mapData->pathToAsset = levelPath + FString(L"/") + matchingLevelName + FString(L".") + matchingLevelName; + mapData->pathToAsset = levelPath + FString(L"/") + matchingLevelName; + //GWPRINT(L"Level asset path: " + mapData->pathToAsset); + maps.Add(mapData); + mapsByLevelName.Add(matchingLevelName, mapData); + mapsByLevelPath.Add(mapData->pathToAsset, mapData); + } + else + { + GWERROR(L"Can't extract map name from level asset " + mapData->GetPathName()); + } + } + } +} + +ULocalPlayer* UDefaultGameInstance::CreateInitialPlayer(FString& OutError) +{ + m_localPlayer = Super::CreateInitialPlayer(OutError); + check(m_localPlayer); + //m_localNetID = m_localPlayer->GetPreferredUniqueNetId(); + //GWPRINT(L"Created local player " + m_localPlayer->GetName() + + // ", NetID = " + m_localNetID->ToDebugString()); + //check(m_localNetID.IsValid()); + + // Retrieve player name + //IOnlineIdentityPtr identity = m_onlineSystem->GetIdentityInterface(); + //playerID = identity->GetPlayerNickname(*m_localNetID); + //GWPRINT(L"Assigned player ID: " + playerID); + + //IOnlineFriendsPtr friends = m_onlineSystem->GetFriendsInterface(); + //if (friends.IsValid()) + //{ + // onReadFriendsListCompleteDelegate = FOnReadFriendsListComplete::CreateUObject(this, &UDefaultGameInstance::m_OnReadFriendsListCompleteDelegate); + // friends->ReadFriendsList(0, TEXT("Friends"), onReadFriendsListCompleteDelegate); + //} + + //ISteamFriends* steamFriends = steamIdentity->GetSteamFriendsPtr(); + //steamIdentity + //friends->GetLargeFriendAvatar() + //ISteamUser* user; + + //FOnlineIdentitySteam* steam = (FOnlineIdentitySteam*)*identity; + //class ISteamUser* steamUser = identity->Get + + return m_localPlayer; +} + +void UDefaultGameInstance::OnSessionUserInviteAccepted(const bool bWasSuccess, const int32 ControllerId, TSharedPtr Us, const FOnlineSessionSearchResult& res) +{ + sessionManager->AcceptUserInvite(bWasSuccess, ControllerId, Us, res); +} + +void UDefaultGameInstance::SaveSettings() +{ + if (m_characterSettings) + { + UCharacterSettings::Save(m_characterSettings); + } +} + +void UDefaultGameInstance::LoadSettings() +{ + m_characterSettings = UCharacterSettings::Load(); +} + +UCharacterSettings* UDefaultGameInstance::GetCharacterSettings() +{ + return m_characterSettings; +} + +void UDefaultGameInstance::SavePrefs() +{ + if (m_prefs) + { + UPrefs::Save(m_prefs); + } +} + +void UDefaultGameInstance::LoadPrefs() +{ + m_prefs = UPrefs::Load(); +} + +UPrefs* UDefaultGameInstance::GetPrefs() +{ + return m_prefs; +} + +UMapData* UDefaultGameInstance::GetCurrentLevelMapData() +{ + UWorld* world = GetWorld(); + check(world); + FString mapName = world->GetMapName(); + FString name1 = world->GetName(); + UMapData** res = mapsByLevelName.Find(name1); + if (res) + return *res; + return nullptr; +} + +class UMapData* UDefaultGameInstance::GetMapData(FString levelPath) +{ + FString first; + FString last; + if(!levelPath.Split("/", &first, &last, ESearchCase::IgnoreCase, ESearchDir::FromEnd)) + return nullptr; + + FString la, lb; + if(last.Split(".", &la, &lb)) + { + last = lb; + } + + levelPath = first + "/" + last; + + UMapData** res = mapsByLevelPath.Find(levelPath); + if(res) + return *res; + return nullptr; +} + +void UDefaultGameInstance::ShowNetworkError(const FString& msg) +{ + APlayerControllerBase* controller = Cast(GetFirstLocalPlayerController()); + check(controller); + + TArray options; + options.Add("OK"); + controller->overlay->ShowMessageBox("Network Error", msg, options); +} + +int32 UDefaultGameInstance::GetScalabilityQuality() +{ + const Scalability::FQualityLevels current = GEngine->GetGameUserSettings()->ScalabilityQuality; + + // Check if any we know + Scalability::FQualityLevels setting; + for (int32 i = 0; i < 4; i++) + { + setting.SetFromSingleQualityLevel(i); + if (current == setting) + return i; + } + + // Custom + return -1; +} +void UDefaultGameInstance::SetScalabilityQuality(int32 scalability) +{ + GEngine->GetGameUserSettings()->ScalabilityQuality.SetFromSingleQualityLevel(scalability); + GEngine->GetGameUserSettings()->ApplySettings(false); + GEngine->GetGameUserSettings()->SaveSettings(); + + m_prefs->usingCustomGraphicsSettings = false; + SavePrefs(); + + //if (GConfig) + //{ + // GConfig->SetInt( + // TEXT("ScalabilityGroups"), + // TEXT("sg.Preset"), + // scalability, + // GGameUserSettingsIni); + // GConfig->Flush(false, GGameUserSettingsIni); + //} +} + +static int32 GenerateResolutionQuality(int32 value) +{ + Scalability::FQualityLevels setting; + setting.SetFromSingleQualityLevel(value); + return setting.ResolutionQuality; +} +static int32 RevertResolutionQuality(int32 value) +{ + Scalability::FQualityLevels setting; + for (int32 i = 0; i < 4; i++) + { + setting.SetFromSingleQualityLevel(i); + if (setting.ResolutionQuality == value) + return i; + } + + return 0; +} +void UDefaultGameInstance::SetScalabilityQualityValues(int32 ResolutionQuality, int32 AntiAliasingQuality, int32 ShadowQuality, int32 PostProcessQuality, int32 TextureQuality, int32 EffectsQuality) +{ + Scalability::FQualityLevels setting; + setting.SetFromSingleQualityLevel(ResolutionQuality); + + setting.AntiAliasingQuality = AntiAliasingQuality; + setting.ShadowQuality = ShadowQuality; + setting.PostProcessQuality = PostProcessQuality; + setting.TextureQuality = TextureQuality; + setting.EffectsQuality = EffectsQuality; + + setting.ViewDistanceQuality = TextureQuality; + + GEngine->GetGameUserSettings()->ScalabilityQuality = setting; + GEngine->GetGameUserSettings()->ApplySettings(false); + GEngine->GetGameUserSettings()->SaveSettings(); + + m_prefs->usingCustomGraphicsSettings = true; + m_prefs->customGraphicsSettings[0] = ResolutionQuality; + m_prefs->customGraphicsSettings[1] = AntiAliasingQuality; + m_prefs->customGraphicsSettings[2] = ShadowQuality; + m_prefs->customGraphicsSettings[3] = PostProcessQuality; + m_prefs->customGraphicsSettings[4] = TextureQuality; + m_prefs->customGraphicsSettings[5] = EffectsQuality; + SavePrefs(); +} + +int32 UDefaultGameInstance::GetScalabilityQualityValue(EGraphicsSetting setting) +{ + const Scalability::FQualityLevels current = GEngine->GetGameUserSettings()->ScalabilityQuality; + + switch (int32(setting)) + { + case 0: return RevertResolutionQuality(current.ResolutionQuality); + case 1: return current.AntiAliasingQuality; + case 2: return current.ShadowQuality; + case 3: return current.PostProcessQuality; + case 4: return current.TextureQuality; + case 5: return current.EffectsQuality; + } + + return 0; +} +void UDefaultGameInstance::SetScalabilityQualityValue(EGraphicsSetting setting, int32 value) +{ + if (int32(setting) >= 6) + return; + + Scalability::FQualityLevels current = GEngine->GetGameUserSettings()->ScalabilityQuality; + + switch (int32(setting)) + { + case 0: current.ResolutionQuality = GenerateResolutionQuality(value); break; + case 1: current.AntiAliasingQuality = value; break; + case 2: current.ShadowQuality = value; break; + case 3: current.PostProcessQuality = value; break; + case 4: current.TextureQuality = value; break; + case 5: current.EffectsQuality = value; break; + } + + GEngine->GetGameUserSettings()->ScalabilityQuality = current; + GEngine->GetGameUserSettings()->ApplySettings(false); + GEngine->GetGameUserSettings()->SaveSettings(); + + m_prefs->usingCustomGraphicsSettings = true; + m_prefs->customGraphicsSettings[int32(setting)] = value; + SavePrefs(); +} + +void UDefaultGameInstance::SetupLoadingScreen() +{ + m_loadingScreen.bAutoCompleteWhenLoadingCompletes = true; + m_loadingScreen.WidgetLoadingScreen = FLoadingScreenAttributes::NewTestLoadingScreenWidget(); // <-- test screen that comes with + m_loadingScreen.MinimumLoadingScreenDisplayTime = 1.0f; + GetMoviePlayer()->SetupLoadingScreen(m_loadingScreen); +} + +/* +void UDefaultGameInstance::OnSessionUserInviteAccepted(const bool bWasSuccess, const int32 ControllerId, TSharedPtr< const FUniqueNetId > Us, const FOnlineSessionSearchResult& res) +{ + if (bWasSuccess) + { + m_JoinOnlineSession(res); + } + else + { + GWWARNING(L"OnSessionUserInviteAccepted was not successful"); + } +} +void UDefaultGameInstance::m_OnCreateSessionComplete(FName sessionName, bool successful) +{ + JWPRINT(L"OnCreateSessionComplete " + sessionName.ToString().GetCharArray().GetData() + L", " + successful); + + m_session->ClearOnCreateSessionCompleteDelegate_Handle(onCreateSessionCompleteDelegateHandle); + if (successful) + { + //onStartSessionCompleteDelegateHandle = m_session->AddOnStartSessionCompleteDelegate_Handle(onStartSessionCompleteDelegate); + //m_session->StartSession(sessionName); + FString options = FString("listen"); + + // Add additional game settings here + options += FString("?teamCount=") + FString::FromInt(hostMaxTeamCount); + + SetupLoadingScreen(); + UGameplayStatics::OpenLevel(GetWorld(), FName(hostLoadMap.GetCharArray().GetData()), true, options); + } + else + { + ShowNetworkError("Failed to create session"); + } +} +void UDefaultGameInstance::m_OnStartOnlineGameComplete(FName sessionName, bool successful) +{ + JWPRINT(L"OnStartOnlineGameComplete " + sessionName.ToString().GetCharArray().GetData() + L", " + successful); + + m_session->ClearOnStartSessionCompleteDelegate_Handle(onStartSessionCompleteDelegateHandle); + + if (successful) + { + FString options = FString("listen"); + + // Add additional game settings here + options += FString("?teamCount=") + FString::FromInt(hostMaxTeamCount); + + SetupLoadingScreen(); + UGameplayStatics::OpenLevel(GetWorld(), FName(hostLoadMap.GetCharArray().GetData()), true, options); + } + else + { + ShowNetworkError("Failed to start session"); + } +} +void UDefaultGameInstance::m_OnFindSessionsComplete(bool successful) //__attribute__((optnone)) +{ + + JWPRINT(L"OnFindSessionsComplete " + successful); + + m_operationInProgress = false; + if(m_findingOverlay) + { + m_findingOverlay->Close(); + m_findingOverlay = nullptr; + } + + m_session->ClearOnFindSessionsCompleteDelegate_Handle(onFindSessionsCompleteDelegateHandle); + + JPRINT("OnFindSessionsComplete: " + sessionSearch->SearchResults.Num() + " sessions"); + + if (sessionSearch->SearchResults.Num() > 0) + { + for (int32 SearchIdx = 0; SearchIdx < sessionSearch->SearchResults.Num(); SearchIdx++) + { + JWPRINT(SearchIdx + L", " + sessionSearch->SearchResults[SearchIdx].Session.OwningUserName); + } + } +} +void UDefaultGameInstance::m_OnJoinSessionComplete(FName sessionName, EOnJoinSessionCompleteResult::Type result) +{ + JWPRINT(L"OnStartOnlineGameComplete " + sessionName.ToString().GetCharArray().GetData() + L", " + int32(result)); + m_session->ClearOnJoinSessionCompleteDelegate_Handle(onJoinSessionCompleteDelegateHandle); + m_operationInProgress = false; + + if(result != EOnJoinSessionCompleteResult::Success) + { + ShowNetworkError(FString("Failed to join session, Error Code ") + FString::FromInt(result)); + SetGameJoined(); + return; + } + + APlayerController* const playerController = GetFirstLocalPlayerController(); + check(playerController); + + SetupLoadingScreen(); + playerController->ClientTravel(m_travelURL, ETravelType::TRAVEL_Absolute); +} +void UDefaultGameInstance::m_OnDestroySessionComplete(FName sessionName, bool successful) +{ + JWPRINT(L"OnDestroySessionComplete " + sessionName.ToString().GetCharArray().GetData() + L", " + successful); + + m_session->ClearOnDestroySessionCompleteDelegate_Handle(onDestroySessionCompleteDelegateHandle); + if(m_travelOnSessionDestroy) + { + UGameplayStatics::OpenLevel(GetWorld(), "/Game/Assets/Levels/Menu", true); + } +} +void UDefaultGameInstance::m_OnReadFriendsListCompleteDelegate(int32, bool, const FString&, const FString&) +{ + GPRINT("Got friends list"); +} +*/ \ No newline at end of file diff --git a/Source/UnrealProject/GameState/DefaultGameInstance.h b/Source/UnrealProject/GameState/DefaultGameInstance.h new file mode 100644 index 0000000..f25a965 --- /dev/null +++ b/Source/UnrealProject/GameState/DefaultGameInstance.h @@ -0,0 +1,107 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "Engine/GameInstance.h" +#include "MoviePlayer.h" +#include "DefaultGameInstance.generated.h" + + +UENUM(BlueprintType) +enum class EGraphicsSetting : uint8 +{ + ResolutionQuality = 0, + AntiAliasingQuality, + ShadowQuality, + PostProcessQuality, + TextureQuality, + EffectsQuality, +}; + + +UCLASS() +class UNREALPROJECT_API UDefaultGameInstance : public UGameInstance +{ + GENERATED_BODY() + friend class USessionManager; +public: + UDefaultGameInstance(); + + virtual void Init() override; + virtual void Shutdown() override; + virtual bool HandleOpenCommand(const TCHAR* Cmd, FOutputDevice& Ar, UWorld* InWorld); + + virtual ULocalPlayer* CreateInitialPlayer(FString& OutError) override; + virtual void OnSessionUserInviteAccepted(const bool bWasSuccess, const int32 ControllerId, + TSharedPtr Us, const FOnlineSessionSearchResult& res) override; + + void ShowNetworkError(const FString& msg); + void SetupLoadingScreen(); + + UFUNCTION(BlueprintCallable, Category = "Save") + void SaveSettings(); + UFUNCTION(BlueprintCallable, Category = "Save") + void LoadSettings(); + UFUNCTION(BlueprintCallable, Category = "Save") + UCharacterSettings* GetCharacterSettings(); + UFUNCTION(BlueprintCallable, Category = "Prefs") + void SavePrefs(); + UFUNCTION(BlueprintCallable, Category = "Prefs") + void LoadPrefs(); + UFUNCTION(BlueprintCallable, Category = "Prefs") + UPrefs* GetPrefs(); + UFUNCTION(BlueprintCallable, Category = "Levels") + class UMapData* GetCurrentLevelMapData(); + UFUNCTION(BlueprintCallable, Category = "Levels") + class UMapData* GetMapData(FString levelPath); + + // Get overall graphics settings, returns -1 if the user is using custom + UFUNCTION(BlueprintCallable, Category = "Quality") + int32 GetScalabilityQuality(); + // Set overall graphics settings + UFUNCTION(BlueprintCallable, Exec, Category = "Quality") + void SetScalabilityQuality(int32 scalability); + // Set graphics settings from custom values, calling this will enable custom + UFUNCTION(BlueprintCallable, Category = "Quality") + void SetScalabilityQualityValues(int32 ResolutionQuality, int32 AntiAliasingQuality, int32 ShadowQuality, int32 PostProcessQuality, int32 TextureQuality, int32 EffectsQuality); + + // Get individual graphics value + UFUNCTION(BlueprintCallable, Exec, Category = "Quality") + int32 GetScalabilityQualityValue(EGraphicsSetting setting); + // Set individual graphics value + UFUNCTION(BlueprintCallable, Category = "Quality") + void SetScalabilityQualityValue(EGraphicsSetting setting, int32 value); + + // The class that manages online sessions and establishing connections to other clients + UPROPERTY(BlueprintReadOnly, Category = "Network") + class USessionManager* sessionManager; + + UPROPERTY(BlueprintReadOnly, Category = "Levels") + TArray maps; + UPROPERTY() + TMap mapsByLevelName; + UPROPERTY() + TMap mapsByLevelPath; + + UPROPERTY(BlueprintReadOnly, Category = "Player") + bool splashScreenShown; + + UPROPERTY(EditDefaultsOnly) + FString menuLevelPath; + + UPROPERTY(BlueprintReadOnly, Category = "Player") + FString playerID; +private: + virtual bool m_Tick(float DeltaSeconds); + + void m_LoadMaps(); + + FLoadingScreenAttributes m_loadingScreen; + FDelegateHandle m_tickDelegateHandle; + ULocalPlayer* m_localPlayer; + + UPROPERTY() + class UCharacterSettings* m_characterSettings; + UPROPERTY() + class UPrefs* m_prefs; +}; diff --git a/Source/UnrealProject/GameState/DefaultGameMode.cpp b/Source/UnrealProject/GameState/DefaultGameMode.cpp new file mode 100644 index 0000000..a73f07b --- /dev/null +++ b/Source/UnrealProject/GameState/DefaultGameMode.cpp @@ -0,0 +1,381 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "DefaultGameMode.h" +#include "DefaultGameState.h" +#include "DefaultGameInstance.h" +#include "PlayerSpawn.h" +#include "NetworkPlayer.h" +#include "CreatureSpawn.h" +#include "DefaultPlayer.h" +#include "DefaultPlayerController.h" +#include "DefaultPlayerState.h" +#include "GameStateBase.h" +#include "SessionManager.h" + +#if PLATFORM_SPECIFIC_WIN == 0 +#include "HeatMapMetrics.h" +#endif + +#if UE_INCLUDE_METRICS +#include "MiniMapVolume.h" +#include "IngameHUD.h" +#include "MiniMapWidget.h" +#endif + +ADefaultGameMode::ADefaultGameMode() +{ + PrimaryActorTick.bCanEverTick = true; + + // Lobby system starts the match + bDelayedStart = true; + + PlayerControllerClass = ADefaultPlayerController::StaticClass(); + DefaultPawnClass = ConstructorHelpers::FClassFinder(TEXT("/Game/Assets/Blueprints/BP_DefaultPlayer")).Class; + SpectatorClass = ConstructorHelpers::FClassFinder(TEXT("/Game/Assets/Blueprints/BP_SpectatorPawn")).Class; + + GameStateClass = ADefaultGameState::StaticClass(); + PlayerStateClass = ADefaultPlayerState::StaticClass(); +} + +void ADefaultGameMode::PreInitializeComponents() +{ + Super::PreInitializeComponents(); + + ADefaultGameState* gameState = GetGameState(); + check(gameState); + m_playerSpawns.AddZeroed(4); + //FString str = UGameplayStatics::ParseOption(OptionsString, FString("teamCount")); + //int32 teamCountSetting = FCString::Atoi(*str); + //if(teamCountSetting > 1) + //{ + // gameState->m_mapTeamCount = teamCountSetting; + // GWPRINT(L"Set map team count to " + gameState->m_mapTeamCount); + //} +} + +void ADefaultGameMode::InitGame(const FString& MapName, const FString& Options, FString& ErrorMessage) +{ + Super::InitGame(MapName, Options, ErrorMessage); + + // Start game immediately in editor since we can't use seamless travel to obtain the players + UWorld* world = GetWorld(); + if(world->WorldType == EWorldType::PIE) + { + m_StartGameStartTimer(); + } + + // Find lobby state blueprint + for(TActorIterator it(world); it; ++it) + { + m_lobbyState = *it; + } + + if(m_lobbyState) + { + GPRINT("Received traveled lobby state " + m_lobbyState->GetName()); + m_totalPlayerCount = m_lobbyState->players.Num(); + } + else + { + m_totalPlayerCount = 1; + } +} +void ADefaultGameMode::BeginPlay() +{ + Super::BeginPlay(); + +#if PLATFORM_SPECIFIC_WIN == 0 + HeatMapMetrics::ResetMap(); +#endif +} +void ADefaultGameMode::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ +#if UE_INCLUDE_METRICS + if (Metrics::EndSession()) + JPRINT("Metrics session ended"); + else + JERROR("Failed to close metrics session"); +#endif + + Super::EndPlay(EndPlayReason); + +#if PLATFORM_SPECIFIC_WIN == 0 + HeatMapMetrics::ExportToFile(); +#endif +} + +void ADefaultGameMode::PostSeamlessTravel() +{ + GPRINT("PostSeamlessTravel"); + Super::PostSeamlessTravel(); +} + +void ADefaultGameMode::HandleSeamlessTravelPlayer(AController*& C) +{ + Super::HandleSeamlessTravelPlayer(C); + + UDefaultGameInstance* inst = Cast(GetGameInstance()); + + // Register new player + ADefaultPlayerController* newPC = Cast(C); + if(!newPC) + { + GERROR("Invalid player genereted by HandleSeamlessTravelPlayer"); + } + else + { + GPRINT("Transfering logged in player " + C->GetName()); + AGameStateBase* gameState = Cast(GameState); + gameState->RegisterPlayer(newPC); + } + + TSharedPtr netID = newPC->PlayerState->UniqueId.GetUniqueNetId(); + if(m_lobbyState) + { + m_lobbyState->players.RemoveAll([netID](FLobbyStatePlayer& item) + { + return item.netID.IsValid() && netID.IsValid() && (*item.netID == *netID); + }); + } + + m_numTravelledPlayers++; + if(m_numTravelledPlayers >= m_totalPlayerCount) + { + // Set a timer to start the game + m_StartGameStartTimer(); + } + else + { + int32 left = m_totalPlayerCount - m_numTravelledPlayers; + GPRINT("Still waiting for " + left + " more players"); + for(auto& p : m_lobbyState->players) + { + FString name = inst->sessionManager->GetPlayerName(*p.netID); + GPRINT("--" + name + " [" + p.netID->ToDebugString() + "]"); + } + } +} + +void ADefaultGameMode::PreLogin(const FString& Options, const FString& Address, const TSharedPtr& UniqueId, FString& ErrorMessage) +{ + Super::PreLogin(Options, Address, UniqueId, ErrorMessage); + FString username = "Invalid"; + + if(GameState->GetMatchState() == MatchState::InProgress) + { + // Check if player is reconnecting but was previously connected + FString uniqueIDString = UniqueId->ToString(); + if (m_connectedPlayersNetID.Contains(uniqueIDString)) + { + GWPRINT(L"Reconnecting player " + username + " / " + uniqueIDString); + } + else + { + ErrorMessage = "Game has already started"; + } + } +} +APlayerController* ADefaultGameMode::Login(class UPlayer* NewPlayer, ENetRole InRemoteRole, const FString& Portal, const FString& Options, const TSharedPtr& UniqueId, FString& ErrorMessage) +{ + // Register players that ever connected to the game so they can reconnect + APlayerController* controller = Super::Login(NewPlayer, InRemoteRole, Portal, Options, UniqueId, ErrorMessage); + if (controller) + { + FString uniqueIDString = UniqueId->ToString(); + m_connectedPlayersNetID.Add(uniqueIDString); + GWPRINT(L"Registering connected player ID " + uniqueIDString); + } + + // Register logged on player + // Note: this should only happen when running a PIE instance or standalone with a startup level + // normal login procedure happens in the menu and players are then transfered to the game using seamless travel + ADefaultPlayerController* defPC = Cast(controller); + if(!defPC) + { + GERROR("Invalid player genereted by HandleSeamlessTravelPlayer"); + } + else + { + GPRINT("Logging in new player " + defPC->GetName()); + AGameStateBase* gameState = Cast(GameState); + gameState->RegisterPlayer(defPC); + } + + return controller; +} + +void ADefaultGameMode::PostLogin(APlayerController* NewPlayer) +{ + Super::PostLogin(NewPlayer); + + // Auto assign team when in PIE + UWorld* world = GetWorld(); + if(world->WorldType == EWorldType::PIE) + { + AGameStateBase* gameState = Cast(GameState); + APlayerStateBase* state = Cast(NewPlayer->PlayerState); + state->AutoAssignTeam(); + } +} +void ADefaultGameMode::Logout(AController* Exiting) +{ + GWPRINT(L"Player left the game"); + + // Unregister players if not playing + if (GameState->GetMatchState() != MatchState::InProgress) + { + if (Exiting->PlayerState) + { + FString uniqueIDString = Exiting->PlayerState->UniqueId->ToString(); + GWPRINT(L"Removing connected player ID " + uniqueIDString); + m_connectedPlayersNetID.Remove(uniqueIDString); + } + } + Super::Logout(Exiting); +} + +void ADefaultGameMode::Tick(float DeltaTime) +{ + Super::Tick(DeltaTime); + m_gameTimer += DeltaTime; +} + +void ADefaultGameMode::HandleMatchHasStarted() +{ + GWPRINT(L"Match Started"); + GetGameState()->gameEnded = false; + + // Assign team mates + ADefaultGameState* gameState = GetWorld()->GetGameState(); + TArray> playersByTeam = gameState->GetPlayersByTeam(); + + for (int32 i = 0; i < playersByTeam.Num(); i++) + { + auto& arr = playersByTeam[i]; + if (arr.Num() == 2) + { + arr[0]->teamMate = arr[1]; + arr[1]->teamMate = arr[0]; + } + } + + Super::HandleMatchHasStarted(); +} +void ADefaultGameMode::HandleMatchHasEnded() +{ + GWPRINT(L"Match Ended"); + GetGameState()->gameEnded = true; + Super::HandleMatchHasStarted(); +} + +void ADefaultGameMode::RegisterPlayerSpawn(APlayerSpawn& spawn) +{ + if (spawn.assignedTeam > m_playerSpawns.Num() - 1) + m_playerSpawns.SetNumZeroed(spawn.assignedTeam + 1); + + m_playerSpawns[spawn.assignedTeam] = &spawn; +} + +void ADefaultGameMode::RegisterPlayer(class ANetworkPlayer& player) +{ + if(m_players.Contains(&player)) + { + GWARNING("Player already registered with game mode " + player.GetName()); + return; + } + + m_players.Add(&player); +} +void ADefaultGameMode::UnregisterPlayer(class ANetworkPlayer& player) +{ + m_players.Remove(&player); +} + +void ADefaultGameMode::StartGame() +{ +#if UE_INCLUDE_METRICS + // Start a new metrics session, and output the file in the Metrics folder + FString filePath = FPaths::GameDir() + FString(L"Metrics/"); + if (Metrics::StartSession(filePath.GetCharArray().GetData())) + { + // Retrieve the active map size + for (TActorIterator iter(GetWorld()); iter; ++iter) + { + AMiniMapVolume* const volume = *iter; + volume->SetActorRotation(FRotator(0, 0, 0)); + FVector area = volume->area->GetScaledBoxExtent(); + const FVector pos = volume->GetActorLocation(); + FVector2D min, max; + min.X = -area.X + pos.X; + min.Y = -area.Y + pos.Y; + max.X = area.X + pos.X; + max.Y = area.Y + pos.Y; + Metrics::SetMapData(min.X, max.X, min.Y, max.Y); + break; + } + JPRINT("New metrics session started"); + } + else + JERROR("Failed to open new metrics session"); +#endif + + GPRINT("Starting Game"); + SetMatchState(MatchState::InProgress); + m_gameTimer = 0; +} + +APlayerSpawn* ADefaultGameMode::GetOptimalSpawn(int32 team) +{ + check(team > 0); + + if (m_playerSpawns.Num() == 0) + return nullptr; + if (m_playerSpawns.Num() == 1) + return m_playerSpawns[0]; + + if (team - 1 < m_playerSpawns.Num()) + return m_playerSpawns[team - 1]; + + // Either no players yet, or something went wrong + // Pick random spawn + return m_playerSpawns[FMath::Rand() % m_playerSpawns.Num()]; +} + +TArray ADefaultGameMode::GetPlayers() +{ + return m_players; +} + +void ADefaultGameMode::BossHasBeenKilled(int32 team) +{ + TArray players(m_players); + + // Set winning team in gamestate (replicated) + GetGameState()->gameWinningTeam = team; + + EndMatch(); + + // Destroy all player pawns so that they respawn as spectators + for (int32 i = 0; i < players.Num(); i++) + { + players[i]->Destroy(true); + } + + TPRINT("Boss was killed. (ADefaultGameMode) the team = " + team); +} + +float ADefaultGameMode::GetGameTime() +{ + return m_gameTimer; +} + +void ADefaultGameMode::m_StartGameStartTimer() +{ + GPRINT("Starting game in 2 seconds"); + GetWorld()->GetTimerManager().SetTimer(m_gameStartTimer, this, &ADefaultGameMode::m_GameStart, 2.0f, false); +} +void ADefaultGameMode::m_GameStart() +{ + StartGame(); +} diff --git a/Source/UnrealProject/GameState/DefaultGameMode.h b/Source/UnrealProject/GameState/DefaultGameMode.h new file mode 100644 index 0000000..b4eed62 --- /dev/null +++ b/Source/UnrealProject/GameState/DefaultGameMode.h @@ -0,0 +1,65 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "GameFramework/GameMode.h" +#include "DefaultGameMode.generated.h" + +UCLASS(minimalapi) +class ADefaultGameMode : public AGameMode +{ + GENERATED_BODY() + +public: + ADefaultGameMode(); + + virtual void PreInitializeComponents() override; + virtual void InitGame(const FString& MapName, const FString& Options, FString& ErrorMessage) override; + virtual void BeginPlay() override; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + + virtual void PostSeamlessTravel() override; + virtual void HandleSeamlessTravelPlayer(AController*& C) override; + virtual void PreLogin(const FString& Options, const FString& Address, const TSharedPtr& UniqueId, FString& ErrorMessage) override; + virtual APlayerController* Login(class UPlayer* NewPlayer, ENetRole InRemoteRole, const FString& Portal, const FString& Options, const TSharedPtr& UniqueId, FString& ErrorMessage) override; + virtual void PostLogin(APlayerController* NewPlayer) override; + virtual void Logout(AController* Exiting); + + virtual void Tick(float DeltaTime) override; + + virtual void HandleMatchHasStarted() override; + virtual void HandleMatchHasEnded() override; + + void RegisterPlayerSpawn(class APlayerSpawn& spawn); + + void RegisterPlayer(class ANetworkPlayer& player); + void UnregisterPlayer(class ANetworkPlayer& player); + + void StartGame(); + + APlayerSpawn* GetOptimalSpawn(int32 team); + + TArray GetPlayers(); + + void BossHasBeenKilled(int32 team); + float GetGameTime(); +protected: + void m_StartGameStartTimer(); + void m_GameStart(); + float m_gameTimer; + float m_gameRestartTimer; + FTimerHandle m_gameStartTimer; + + UPROPERTY() + class ALobbyState* m_lobbyState; + + int32 m_numTravelledPlayers; + int32 m_totalPlayerCount; + + TArray m_connectedPlayersNetID; + + UPROPERTY() + TArray m_playerSpawns; + UPROPERTY() + TArray m_players; +}; \ No newline at end of file diff --git a/Source/UnrealProject/GameState/DefaultGameState.cpp b/Source/UnrealProject/GameState/DefaultGameState.cpp new file mode 100644 index 0000000..d3d5528 --- /dev/null +++ b/Source/UnrealProject/GameState/DefaultGameState.cpp @@ -0,0 +1,32 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" + +#include "DefaultGameState.h" +#include "MapData.h" + +ADefaultGameState::ADefaultGameState() +{ + PrimaryActorTick.bCanEverTick = true; + + bReplicates = true; +} + +void ADefaultGameState::BeginPlay() +{ + Super::BeginPlay(); +} + +void ADefaultGameState::Tick(float DeltaTime) +{ + Super::Tick(DeltaTime); +} + +void ADefaultGameState::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(ADefaultGameState, gameEnded); + DOREPLIFETIME(ADefaultGameState, gameWinningTeam); + DOREPLIFETIME(ADefaultGameState, gameRestartingIn); +} \ No newline at end of file diff --git a/Source/UnrealProject/GameState/DefaultGameState.h b/Source/UnrealProject/GameState/DefaultGameState.h new file mode 100644 index 0000000..85c2929 --- /dev/null +++ b/Source/UnrealProject/GameState/DefaultGameState.h @@ -0,0 +1,27 @@ +// Project Lab - NHTV Igad +#pragma once +#include "GameStateBase.h" +#include "MiniMap.h" +#include "DefaultGameState.generated.h" + +UCLASS() +class UNREALPROJECT_API ADefaultGameState : public AGameStateBase +{ + GENERATED_BODY() + friend class ADefaultGameMode; +public: + ADefaultGameState(); + virtual void BeginPlay() override; + virtual void Tick(float DeltaSeconds) override; + + class ASoundEffect* CreateSoundEffect(class USoundBase* sndClass, const FVector& position); + + UPROPERTY(Replicated, BlueprintReadOnly, Category = "GameState") + bool gameEnded; + UPROPERTY(Replicated, BlueprintReadOnly, Category = "GameState") + int32 gameWinningTeam; + UPROPERTY(Replicated, BlueprintReadOnly, Category = "GameState") + float gameRestartingIn; + + MiniMap::QuadTree<6> minimapQuadtree; +}; diff --git a/Source/UnrealProject/GameState/DefaultPlayer.cpp b/Source/UnrealProject/GameState/DefaultPlayer.cpp new file mode 100644 index 0000000..8773055 --- /dev/null +++ b/Source/UnrealProject/GameState/DefaultPlayer.cpp @@ -0,0 +1,119 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "DefaultGameInstance.h" +#include "DefaultGameMode.h" +#include "PlayerSpawn.h" +#include "DefaultGameState.h" +#include "DefaultGameInstance.h" +#include "NetworkCharacter.h" + +#include "DefaultPlayer.h" +#include "KingOfTheHillGameMode.h" +#include "KOTHBossSpawner.h" +#if PLATFORM_SPECIFIC_WIN == 0 +#include "InputManager.hpp" +using namespace Input; +#endif +#include "DefaultPlayerController.h" + +ADefaultPlayer::ADefaultPlayer() +{ + PrimaryActorTick.bCanEverTick = true; + + + RootComponent = CreateDefaultSubobject(TEXT("Root")); + + // Create a camera boom + CameraBoom = CreateDefaultSubobject(TEXT("CameraBoom")); + CameraBoom->AttachTo(RootComponent); + CameraBoom->bAbsoluteRotation = true; + CameraBoom->TargetArmLength = 1500.f; + CameraBoom->RelativeRotation = FRotator(-60.f, 45.f, 0.f); + CameraBoom->bDoCollisionTest = false; + CameraBoom->bInheritPitch = false; + CameraBoom->bInheritYaw = false; + CameraBoom->bInheritRoll = false; + + // Create a camera + TopDownCamera = CreateDefaultSubobject(TEXT("TopDownCamera")); + TopDownCamera->AttachTo(CameraBoom, USpringArmComponent::SocketName); + TopDownCamera->bUsePawnControlRotation = false; + + // Don't rotate character to camera direction + bUseControllerRotationPitch = false; + bUseControllerRotationYaw = false; + bUseControllerRotationRoll = false; + + + respawnDelay = 0.25f; + respawnTimer = 0.0f; + isRespawning = false; + bReplicates = true; + bAlwaysRelevant = true; + bReplicateMovement = false; +} +void ADefaultPlayer::BeginPlay() +{ + Super::BeginPlay(); + if(Role == ROLE_Authority) + isRespawning = true; +} + +void ADefaultPlayer::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + Super::EndPlay(EndPlayReason); +} +void ADefaultPlayer::Tick(float DeltaSeconds) +{ + Super::Tick(DeltaSeconds); + + // Spawn the player + if(Role == ROLE_Authority) + { + if(isRespawning) + { + respawnTimer -= DeltaSeconds; + m_OnRep_RespawnTimer(); + bool respawn = true; + AKingOfTheHillGameMode* gameMode = Cast(GetWorld()->GetAuthGameMode()); + if (gameMode) + { + respawn = gameMode->bossSpawner->IsBossAlive(); + } + if(respawnTimer <= 0.0f &&respawn) + { + ADefaultPlayerController* controller = Cast(GetController()); + if(IsValid(controller)) + { + controller->SpawnCharacterForClient(); + isRespawning = false; + } + else + { + isRespawning = false; + } + } + } + } +} + +void ADefaultPlayer::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + DOREPLIFETIME(ADefaultPlayer, respawnTimer); + DOREPLIFETIME(ADefaultPlayer, isRespawning); +} + +void ADefaultPlayer::OnSpawn() +{ + check(Role == ROLE_Authority); + respawnTimer = respawnDelay; + isRespawning = true; + m_OnRep_RespawnTimer(); +} + +void ADefaultPlayer::m_OnRep_RespawnTimer() +{ + onRespawnTimerSet.Broadcast(respawnTimer); +} \ No newline at end of file diff --git a/Source/UnrealProject/GameState/DefaultPlayer.h b/Source/UnrealProject/GameState/DefaultPlayer.h new file mode 100644 index 0000000..829d738 --- /dev/null +++ b/Source/UnrealProject/GameState/DefaultPlayer.h @@ -0,0 +1,47 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "GameFramework/Pawn.h" +#include "DefaultPlayer.generated.h" + + +UCLASS(Config=Game) +class UNREALPROJECT_API ADefaultPlayer : public APawn +{ + GENERATED_BODY() + +public: + ADefaultPlayer(); + + virtual void BeginPlay() override; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + virtual void Tick(float DeltaSeconds) override; + + // Resets the respawn timer + void OnSpawn(); + + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true")) + class UCameraComponent* camera; + + UPROPERTY(ReplicatedUsing=m_OnRep_RespawnTimer, BlueprintReadOnly, Category = "RespawnTimer") + float respawnTimer; + UPROPERTY(Replicated, BlueprintReadOnly, Category = "RespawnTimer") + bool isRespawning; + + UPROPERTY(Config, BlueprintReadOnly) + float respawnDelay; + + DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnRespawnTimerSet, float, time); + UPROPERTY(BlueprintAssignable) + FOnRespawnTimerSet onRespawnTimerSet; + + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true")) + class USpringArmComponent* CameraBoom; + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true")) + class UCameraComponent* TopDownCamera; + +private: + UFUNCTION() + void m_OnRep_RespawnTimer(); +}; diff --git a/Source/UnrealProject/GameState/DefaultPlayerController.cpp b/Source/UnrealProject/GameState/DefaultPlayerController.cpp new file mode 100644 index 0000000..fbaec2a --- /dev/null +++ b/Source/UnrealProject/GameState/DefaultPlayerController.cpp @@ -0,0 +1,1335 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "DefaultGameInstance.h" +#include "DefaultGameState.h" +#include "KingOfTheHillGameMode.h" +#include "DefaultPlayer.h" +#include "DefaultPlayerState.h" +#include "DefaultPlayerController.h" +#include "CombatTextWidget.h" +#include "CharacterSettings.h" +#include "NetworkPlayer.h" +#include "NetworkGhost.h" +#include "NetworkSwitch.h" +#include "BaseSkillObject.h" +#include "PlayerSpawn.h" +#include "MiniMapWidget.h" +#include "IngameHUD.h" +#include "CombatTextWidget.h" +#include "AbilityInfo.h" +#include "HealthBar.h" +#include "LobbyMenu.h" +#include "ItemBase.h" +#include "SkillTreeWidget.h" +#include "IngameSkillTree.h" +#include "ButtonBarSwitcher.h" +#include "MenuScreenBase.h" + +#include "SessionManager.h" +#include "PreCastAbilityEventGroup.h" +#include "MenuScreen.h" +#include "WidgetBlueprintLibrary.h" + +// Use Inputmanager when not on playstation +#if PLATFORM_SPECIFIC_WIN == 0 +#include "InputManager.hpp" +using namespace Input; +#endif + +static UClass* HUDWidgetClass; +static UClass* characterClass; +static UClass* ghostClass; +static UClass* respawnWidgetClass; +static UClass* skilltreeWidgetClass; +static TSubclassOf menuWidgetClass; +static TSubclassOf ingameMenuPanelClass; + +ADefaultPlayerController::ADefaultPlayerController(const FObjectInitializer& init) + : Super(init) +{ + menuWidgetClass = ConstructorHelpers::FClassFinder(TEXT("/Game/Assets/GUI/WEEGEE_Menu")).Class; + ingameMenuPanelClass = ConstructorHelpers::FClassFinder(TEXT("/Game/Assets/GUI/Menus/WEEGEE_IngameSubMenu")).Class; + + characterClassProperties = ConstructorHelpers::FObjectFinder(TEXT("/Game/Assets/Abilities/Classes")).Object; + respawnWidgetClass = ConstructorHelpers::FClassFinder(TEXT("/Game/Assets/GUI/WEEGEE_Respawn")).Class; + HUDWidgetClass = ConstructorHelpers::FClassFinder(TEXT("/Game/Assets/GUI/WEEGEE_HUD")).Class; + characterClass = ConstructorHelpers::FClassFinder(TEXT("/Game/Assets/Blueprints/Creatures/BP_NetworkPlayer")).Class; + ghostClass = ConstructorHelpers::FClassFinder (TEXT("/Game/Assets/Blueprints/Creatures/BP_NetworkGhost")).Class; + skilltreeWidgetClass = ConstructorHelpers::FClassFinder (TEXT("/Game/Assets/GUI/WEEGEE_SkillTree")).Class; + PrimaryActorTick.bCanEverTick = true; + + m_framesSinceMenuFocus = 0; + m_rightMouseDown = false; + m_holdPosition = false; + m_skillTreeInitialized = false; + m_holdDirection = FVector(); + m_coopModifier = false; +} + +void ADefaultPlayerController::BeginPlay() +{ + Super::BeginPlay(); + + if(IsLocalController()) + { + m_hud = CreateWidget(this, HUDWidgetClass); + m_hud->AddToViewport(); + m_OnPawnChanged(); + + // Create skill tree widget + m_skillTreeWidget = CreateWidget(this, skilltreeWidgetClass); + check(m_skillTreeWidget); + m_skillTreeWidget->AddToViewport(); + + // Build initial tree + m_skillTreeWidget->BuildFromState(setupState.skills); + + // Load the skill tree + m_skillTreeWidget->SetIsInteractive(false); + m_skillTreeWidget->SetIsEnabled(true); + } + + if(GetWorld()->IsPlayInEditor() && Role == ROLE_Authority) + { + // Load setup state from settings (slot 0) + UCharacterSettings* settings = Cast(GetGameInstance())->GetCharacterSettings(); + if(settings->characterSaves.Num() > 0) + { + const FCharacterSave& save = settings->characterSaves[0]; + setupState.customizations = save.characterCustomization; + setupState.skills = save.skillTreeState; + setupState.characterClass = save.characterClass; + } + + // Handle setupState when not comming from seamless travel + OnRep_Setup(); + } + + this->bShowMouseCursor = true; +} +void ADefaultPlayerController::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + Super::EndPlay(EndPlayReason); + + if (m_hud) + m_hud->RemoveFromViewport(); + + if(IsValid(m_defaultPlayer)) + { + m_defaultPlayer->Destroy(); + } + if(m_respawnWidget) + { + m_respawnWidget->RemoveFromParent(); + } +} + +void ADefaultPlayerController::Destroyed() +{ + Super::Destroyed(); + +#if PLATFORM_SPECIFIC_WIN == 0 + GPRINT("Unbinding Input manager bindings on " + GetName()); + InputManager* inputManager = InputManager::GetInstance(); + + inputManager->DeregisterCallback(IJP_TRIGGERRIGHT, IME_PRESSED, this, &ADefaultPlayerController::CastAbility0); + inputManager->DeregisterCallback(IJP_FACEDOWN, IME_PRESSED, this, &ADefaultPlayerController::CastAbility1); + inputManager->DeregisterCallback(IJP_FACELEFT, IME_PRESSED, this, &ADefaultPlayerController::CastAbility2); + inputManager->DeregisterCallback(IJP_FACEUP, IME_PRESSED, this, &ADefaultPlayerController::CastAbility3); + inputManager->DeregisterCallback(IJP_FACERIGHT, IME_PRESSED, this, &ADefaultPlayerController::CastAbility4); + inputManager->DeregisterCallback(IJP_TRIGGERRIGHT, IME_RELEASED, this, &ADefaultPlayerController::UnCastAbility0); + inputManager->DeregisterCallback(IJP_FACEDOWN, IME_RELEASED, this, &ADefaultPlayerController::UnCastAbility1); + inputManager->DeregisterCallback(IJP_FACELEFT, IME_RELEASED, this, &ADefaultPlayerController::UnCastAbility2); + inputManager->DeregisterCallback(IJP_FACEUP, IME_RELEASED, this, &ADefaultPlayerController::UnCastAbility3); + inputManager->DeregisterCallback(IJP_FACERIGHT, IME_RELEASED, this, &ADefaultPlayerController::UnCastAbility4); + inputManager->DeregisterCallback(IJP_START, IME_PRESSED, this, &ADefaultPlayerController::ToggleMenu); + inputManager->DeregisterCallback(IJP_SHOULDERLEFT, IME_PRESSED, this, &ADefaultPlayerController::ToggleSkillTree); + inputManager->DeregisterCallback(IJP_SHOULDERLEFT, IME_RELEASED, this, &ADefaultPlayerController::ToggleSkillTree); + inputManager->DeregisterCallback(IJP_TRIGGERLEFT, IME_PRESSED, this, &ADefaultPlayerController::CoopModifierOn); + inputManager->DeregisterCallback(IJP_TRIGGERLEFT, IME_RELEASED, this, &ADefaultPlayerController::CoopModifierOff); +#endif +} + +void ADefaultPlayerController::SetupInputComponent() +{ + if (GetWorld()->WorldType == EWorldType::Preview) + return; + + Super::SetupInputComponent(); + +#if PLATFORM_SPECIFIC_WIN == 0 + GPRINT("Creating Input manager bindings on " + GetName()); + InputManager* inputManager = InputManager::GetInstance(); + + inputManager->RegisterCallback(IJP_TRIGGERRIGHT, IME_PRESSED, this, &ADefaultPlayerController::CastAbility0); + inputManager->RegisterCallback(IJP_FACEDOWN, IME_PRESSED, this, &ADefaultPlayerController::CastAbility1); + inputManager->RegisterCallback(IJP_FACELEFT, IME_PRESSED, this, &ADefaultPlayerController::CastAbility2); + inputManager->RegisterCallback(IJP_FACEUP, IME_PRESSED, this, &ADefaultPlayerController::CastAbility3); + inputManager->RegisterCallback(IJP_FACERIGHT, IME_PRESSED, this, &ADefaultPlayerController::CastAbility4); + inputManager->RegisterCallback(IJP_TRIGGERRIGHT, IME_RELEASED, this, &ADefaultPlayerController::UnCastAbility0); + inputManager->RegisterCallback(IJP_FACEDOWN, IME_RELEASED, this, &ADefaultPlayerController::UnCastAbility1); + inputManager->RegisterCallback(IJP_FACELEFT, IME_RELEASED, this, &ADefaultPlayerController::UnCastAbility2); + inputManager->RegisterCallback(IJP_FACEUP, IME_RELEASED, this, &ADefaultPlayerController::UnCastAbility3); + inputManager->RegisterCallback(IJP_FACERIGHT, IME_RELEASED, this, &ADefaultPlayerController::UnCastAbility4); + + inputManager->RegisterCallback(IJP_START, IME_PRESSED, this, &ADefaultPlayerController::ToggleMenu); + inputManager->RegisterCallback(IJP_SHOULDERLEFT, IME_PRESSED, this, &ADefaultPlayerController::ToggleSkillTree); + inputManager->RegisterCallback(IJP_SHOULDERLEFT, IME_RELEASED, this, &ADefaultPlayerController::ToggleSkillTree); + inputManager->RegisterCallback(IJP_TRIGGERLEFT, IME_PRESSED, this, &ADefaultPlayerController::CoopModifierOn); + inputManager->RegisterCallback(IJP_TRIGGERLEFT, IME_RELEASED, this, &ADefaultPlayerController::CoopModifierOff); +#endif + + InputComponent->BindAction("ToggleMenu", IE_Pressed, this, &ADefaultPlayerController::ToggleMenu); + InputComponent->BindAction("ToggleSkillTree", IE_Pressed, this, &ADefaultPlayerController::ToggleSkillTree); + InputComponent->BindAction("ToggleSkillTree", IE_Released, this, &ADefaultPlayerController::ToggleSkillTree); + InputComponent->BindAction("CoopModifier", IE_Pressed, this, &ADefaultPlayerController::CoopModifierOn); + InputComponent->BindAction("CoopModifier", IE_Released, this, &ADefaultPlayerController::CoopModifierOff); + + +#if PLATFORM_SPECIFIC_WIN == 0 +#define ABILITY_KEYBINDING_OP(__name, __id) \ + InputComponent->BindAction(__name, IE_Pressed, this, &ADefaultPlayerController::CastAbility##__id);\ + InputComponent->BindAction(__name, IE_Released, this, &ADefaultPlayerController::UnCastAbility##__id); + +#include "AbilityBindings.h" +#else // On playstation, have repeating attack button + InputComponent->BindAction("Attack", IE_Pressed, this, &ADefaultPlayerController::CastAbility0); + InputComponent->BindAction("CastAbility1", IE_Pressed, this, &ADefaultPlayerController::CastAbility1); + InputComponent->BindAction("CastAbility2", IE_Pressed, this, &ADefaultPlayerController::CastAbility2); + InputComponent->BindAction("CastAbility3", IE_Pressed, this, &ADefaultPlayerController::CastAbility3); + InputComponent->BindAction("CastAbility4", IE_Pressed, this, &ADefaultPlayerController::CastAbility4); + InputComponent->BindAction("Attack", IE_Released, this, &ADefaultPlayerController::UnCastAbility0); + InputComponent->BindAction("CastAbility1", IE_Released, this, &ADefaultPlayerController::UnCastAbility1); + InputComponent->BindAction("CastAbility2", IE_Released, this, &ADefaultPlayerController::UnCastAbility2); + InputComponent->BindAction("CastAbility3", IE_Released, this, &ADefaultPlayerController::UnCastAbility3); + InputComponent->BindAction("CastAbility4", IE_Released, this, &ADefaultPlayerController::UnCastAbility4); +#endif + + // We have 2 versions of the rotation bindings to handle different kinds of devices differently + // "turn" handles devices that provide an absolute delta, such as a mouse. + // "turnrate" is for devices that we choose to treat as a rate of change, such as an analog joystick + + InputComponent->BindAxis("MoveForward", this, &ADefaultPlayerController::MoveForward); + InputComponent->BindAxis("MoveRight", this, &ADefaultPlayerController::MoveRight); + + InputComponent->BindAxis("TurnRate", this, &ADefaultPlayerController::TurnAtRate); + InputComponent->BindAxis("LookUp", this, &ADefaultPlayerController::LookUp); + InputComponent->BindAxis("LookUpRate", this, &ADefaultPlayerController::LookUpAtRate); + + + InputComponent->BindAction("RightMouse", IE_Pressed, this, &ADefaultPlayerController::RightMouseDown); + InputComponent->BindAction("RightMouse", IE_Released, this, &ADefaultPlayerController::RightMouseUp); + InputComponent->BindAction("LeftMouse", IE_Pressed, this, &ADefaultPlayerController::LeftMouseDown); + InputComponent->BindAction("LeftMouse", IE_Released, this, &ADefaultPlayerController::LeftMouseUp); + + InputComponent->BindAction("HoldPosition", IE_Pressed, this, &ADefaultPlayerController::HoldDown); + InputComponent->BindAction("HoldPosition", IE_Released, this, &ADefaultPlayerController::HoldUp); + +} + +void ADefaultPlayerController::Tick(float DeltaSeconds) +{ + APawn* pawn = GetPawnOrSpectator(); + + if (IsLocalController() && IsValid(m_defaultPlayer) && IsValid(pawn) && pawn != m_defaultPlayer) + m_defaultPlayer->SetActorLocation(pawn->GetActorLocation()); + + // Tick basic attacks while the user keeps the button held + if(m_usingBasicAttack) + { + m_TickBasicAttack(); + } + + // Set the menu focus state + if(!m_ingameMenuPanel) + m_framesSinceMenuFocus++; + else + m_framesSinceMenuFocus = 0; + + + ANetworkGhost* const asGhost = Cast(pawn); + if (IsValid(asGhost) && asGhost->CanRespawn()) + { + // Respawn the player character when we are done spooking + SpawnCharacterForClient(asGhost->GetActorLocation(), asGhost->GetActorRotation()); + asGhost->Destroy(); + } + + // Get possessed character, if any + ANetworkPossessable* const character = Cast(pawn); + if(IsValid(character) && IsLocalController() && PlayerState && HasGameFocus()) + { + // Do UI / Input updates on this controller + m_UpdateUI(); + +#if PLATFORM_SPECIFIC_WIN == 0 + InputManager* inputManager = InputManager::GetInstance(); + + Joystick* joystick = inputManager->joystick; + if (joystick) + { + TeaLib::Math::Vector2f lStick = joystick->GetDelta(IJA_LSTICK); + if (lStick.Length() > 0.2f) + { + FVector forward = (character->CameraBoom->GetForwardVector() * -lStick.y) + (character->CameraBoom->GetRightVector() * lStick.x); + FRotator current = character->GetActorRotation(); + FRotator difference = forward.Rotation(); + character->SetActorRotation(FRotator(current.Pitch, difference.Yaw, current.Roll)); + { + MoveForward(-lStick.y); + MoveRight(lStick.x); + } + } + } +#endif + + FVector2D mousePos; + if(GetMousePosition(mousePos.X, mousePos.Y)) + { + FVector location; + FVector direction; + if(DeprojectMousePositionToWorld(location, direction)) + { + const FVector origin = character->TopDownCamera->GetComponentLocation(); + const FVector position = pawn->GetActorLocation(); + const FVector normal = FVector(0, 0, 1); + float entry = FVector::DotProduct(position - origin, normal) / FVector::DotProduct(direction, normal); + + const FVector lookatPos = origin + direction * entry; + + FVector dif = lookatPos - pawn->GetActorLocation(); + dif.Normalize(); + if(!m_rightMouseDown) + { + // Only update lookat if the mouse had moved + if(m_mouseLast != mousePos) + { + FRotator current = pawn->GetActorRotation(); + FRotator difference = dif.Rotation(); + character->SetCharacterRotation(FRotator(current.Pitch, difference.Yaw, current.Roll)); + m_mouseLast = mousePos; + } + } + else + character->AddMovementInput(dif, 1); + } + } + } +} + +void ADefaultPlayerController::Possess(APawn* pawn) +{ + Super::Possess(pawn); + + // Store the initialy posesesed player, since it is the default player. + if (!IsValid(m_defaultPlayer)) + m_defaultPlayer = Cast(pawn); + + // Get possessed character, if any + ANetworkPlayer* character = Cast(pawn); + + // Get possessed ghost, if any + ANetworkGhost* ghost = Cast(pawn); + + // Assign player name to character + ACharacterBase* charBase = Cast(pawn); + UDefaultGameInstance* inst = Cast(GetGameInstance()); + if(charBase) + charBase->playerName = inst->sessionManager->GetPlayerName(*PlayerState->UniqueId); + + // Set the character on the player state + if(Role == ROLE_Authority) + { + if(PlayerState) + { + // Customize ghost character + if(ghost) + { + TArray skills = m_skillTree->GetSkillsForLevel(Cast(PlayerState)); + TArray equip; + for(FIngameSkillTreeSkill& s : skills) + { + equip.Add(s.skillObject); + } + ghost->EquipSkills(equip); + ghost->SetCustomizations(setupState.customizations); + } + + Cast(PlayerState)->character = character; + } + + // Host should handle possess events directly instead of throug OnRep_Pawn + m_OnPawnChanged(); + } +} + +void ADefaultPlayerController::SeamlessTravelFrom(class APlayerController* OldPC) +{ + Super::SeamlessTravelFrom(OldPC); + m_seamlessTravelDone = true; + // Handle receiving the setup state + OnRep_Setup(); +} + +void ADefaultPlayerController::PawnPendingDestroy(APawn* inPawn) +{ + Super::PawnPendingDestroy(inPawn); + + ANetworkPlayer* asPlayer = Cast(inPawn); + if(IsValid(asPlayer)) // The player is destroyed? + { + // Clear learned skills array + m_alreadyLearnedSkills.Reset(); + } + + bool isKoth = false; + UWorld* world = GetWorld(); + if(!IsValid(world)) + return; + if(world->bIsTearingDown) + return; + AGameMode* gm = world->GetAuthGameMode(); + if(gm && gm->IsA()) + { + isKoth = true; + } + + + if (!isKoth && inPawn->IsA(ANetworkPossessable::StaticClass())) + { + // Spawn a ghost + FTransform f; + f.SetScale3D(FVector(1, 1, 1)); + ANetworkPossessable* asPossessable = Cast(inPawn); + if (asPossessable->GetCharacterMovement()->IsMovingOnGround()) + { + // We can spawn the ghost at the location of the dead player + f.SetTranslation(asPossessable->GetActorLocation()); + f.SetRotation(asPossessable->GetActorRotation().Quaternion()); + } + else if (playerLocationHistory.Num() > 0) + { + // The player was falling, we must refer to the location history for a valid spawn spot + f.SetTranslation(playerLocationHistory[0].location); + f.SetRotation(playerLocationHistory[0].rotation.Quaternion()); + } + + FActorSpawnParameters params; + params.bNoFail = true; + params.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; + ANetworkGhost* ghost = GetWorld()->SpawnActor(ghostClass, f, params); + ADefaultPlayerState* playerState = Cast(PlayerState); + check(playerState); + ghost->SetTeam(playerState->GetTeam()); + check(ghost); + Possess(ghost); + } + + + // Handle reposessing the default player + if(IsValid(m_defaultPlayer)) + { + // Posess the default player if nothing is possessed + if (isKoth) + { + Possess(m_defaultPlayer); + m_defaultPlayer->OnSpawn(); + } + } + else + { + GERROR("Can't reposses default player, it hasen't previously been set"); + } + + // Update possessed character on player state + if(PlayerState) + { + Cast(PlayerState)->character = nullptr; + } + + // Host should handle possess events directly instead of throug OnRep_Pawn + m_OnPawnChanged(); +} + +void ADefaultPlayerController::UpdatePlayerPosition(const FVector& location, const FRotator& rotator) +{ + if (playerLocationHistory.Num() == 0) + { + playerLocationHistory.Add(ADefaultPlayerController::Location(location, rotator)); + } + else + { + // Add only if distance to last point is larger than 100 + ADefaultPlayerController::Location& mostRecent = playerLocationHistory.Last(); + const FVector2D thisPos2D = FVector2D(location.X, location.Y); + if (FVector2D::DistSquared(FVector2D(mostRecent.location.X, mostRecent.location.Y), thisPos2D) > 10000) + playerLocationHistory.Add(ADefaultPlayerController::Location(location, rotator)); + + // Ensure that we dont exceed more than 3 + if (playerLocationHistory.Num() > 3) + playerLocationHistory.RemoveAt(0, playerLocationHistory.Num() - 3); + } +} + +void ADefaultPlayerController::OnRep_Pawn() +{ + Super::OnRep_Pawn(); + m_OnPawnChanged(); +} +void ADefaultPlayerController::OnRep_Setup() +{ + Super::OnRep_Setup(); + if(IsLocalController()) + { + if(!m_skillTreeInitialized) + { + // Request the server to initialize the skilltree + GWPRINT(L"Loading skill tree for " + GetName()); + UCharacterSettings* settings = Cast(GetGameInstance())->GetCharacterSettings(); + + // Update state on the widget + if(m_skillTreeWidget) + { + m_skillTreeWidget->BuildFromState(setupState.skills); + } + + m_skillTreeInitialized = true; + } + } + + // Create a the ingame skilltree on clients and server for ability unlock calculation + if(!m_skillTree) + { + m_CreateIngameSkilltree(); + m_skillTree->BuildFromState(setupState.skills); + } + + // Assign basic attack + if(characterClassProperties) + { + if(characterClassProperties->classes.Num() > 0) + { + setupState.characterClass = FMath::Clamp(setupState.characterClass, 0, characterClassProperties->classes.Num()-1); + m_classProperties = characterClassProperties->classes[setupState.characterClass]; + } + } + else + { + GERROR("characterClassProperties is not assigned"); + } +} + +FCharacterClassProperty ADefaultPlayerController::GetCharacterClassProperties() const +{ + return m_classProperties; +} + +class UIngameHUD* ADefaultPlayerController::GetHUD() +{ + return m_hud; +} + +void ADefaultPlayerController::OnLevelUpClient_Implementation(const TArray& updatedSkills, const TArray& oldSkills) +{ + m_alreadyLearnedSkills = oldSkills; + + // Show level up hud + if (m_hud) + m_hud->OnLevelUp(updatedSkills); + UpdateLevel(); + + // Add abilities to the list of learned abilities + for (int32 i = 0; i < updatedSkills.Num(); i++) + { + UAbilityInfo* info = updatedSkills[i].selectedEffect; + if (info) + { + m_learnedAbilities.Add(info); + } + } +} +void ADefaultPlayerController::LearnSkillsForLevel() +{ + ANetworkPlayer* player = Cast(GetPawn()); + if(!player) + return; + if(!m_skillTree) + return; + + TArray skills = m_skillTree->GetSkillsForLevel(Cast(PlayerState)); + + TArray skillsToEquip; + + // Calculate skill delta + TArray updatedSkills; + for (int32 i = 0; i < skills.Num(); i++) + { + FIngameSkillTreeSkill& skillNew = skills[i]; + bool found = false; + for (int32 j = 0; j < m_alreadyLearnedSkills.Num(); j++) + { + FIngameSkillTreeSkill& skillOld = m_alreadyLearnedSkills[j]; + if (skillOld.skillObject == skillNew.skillObject) + { + if(skillOld.level < skillNew.level) + { + updatedSkills.Add(skills[i]); + } + found = true; + break; + } + } + if (!found) + updatedSkills.Add(skills[i]); + + skillsToEquip.Add(skillNew.skillObject); + } + + // Override player basic attack by class + if(player->abilities.Num() < 1) + player->abilities.SetNumZeroed(1); + player->abilities[0] = player->basicAttack = m_classProperties.basicAttack; + + // Equip skill items on all replicas of this player + player->EquipSkills(skillsToEquip); + + // Update customizations + player->SetCustomizations(setupState.customizations); + + // Learn abilities + player->LearnSkills_Server(updatedSkills); + + // Equip class specific items, overrides equipment set by the skill tree + if(m_classProperties.classItems.Num() > 0) + player->EquipItems(m_classProperties.classItems); + + // Equip more skill items + if(PlayerState) + { + FString steamID = Cast(GetGameInstance())->sessionManager->GetSteamID(*PlayerState->UniqueId); + if(steamID == "76561198008045751") + { + GPRINT("Hoi frank"); + } + } + + OnLevelUpClient(updatedSkills, m_alreadyLearnedSkills); + + // Update already learned skills + for(int32 i = 0; i < m_alreadyLearnedSkills.Num(); i++) + { + bool found = false; + for(int32 j = 0; j < skills.Num(); j++) + { + if(skills[j].skillObject == m_alreadyLearnedSkills[i].skillObject) + { + found = true; + break; + } + } + if(!found) + { + m_alreadyLearnedSkills[i].power = 0.0f; + m_alreadyLearnedSkills[i].level = 0; + skills.Add(m_alreadyLearnedSkills[i]); + } + } + + m_alreadyLearnedSkills = skills; +} +int32 ADefaultPlayerController::GetCurrentAbilityLevel(class UAbilityInfo* ability) +{ + for(int32 i = 0; i < m_alreadyLearnedSkills.Num(); i++) + { + if(m_alreadyLearnedSkills[i].selectedEffect == ability) + { + return m_alreadyLearnedSkills[i].level; + } + } + return 0; +} + +bool ADefaultPlayerController::GetAbilityButtonLocation(UAbilityInfo* ability, int32& slot, bool& isAlt) +{ + isAlt = false; + for(slot = 0; slot < m_mainSkillMapping.Num(); slot++) + { + if(m_mainSkillMapping[slot] == ability) + return true; + } + isAlt = true; + for(slot = 0; slot < m_altSkillMapping.Num(); slot++) + { + if(m_altSkillMapping[slot] == ability) + return true; + } + return false; +} + +void ADefaultPlayerController::ToggleSkillTree() +{ + if(IsLocalController() && HasGameFocus()) + { + if(!m_skillTreeWidget->IsShow()) + { + m_skillTreeWidget->Show(); + } + else + { + m_skillTreeWidget->Hide(); + } + } +} +void ADefaultPlayerController::ToggleMenu() +{ + if(!m_ingameMenu) + { + m_ingameMenu = CreateWidget(GetWorld(), menuWidgetClass); + m_ingameMenu->AddToViewport(50); + } + if(!m_ingameMenuPanel) + { + m_ingameMenu->OnShow(); + m_ingameMenuPanel = m_ingameMenu->OpenScreenMenu(ingameMenuPanelClass); + } + else + { + OnHideUI(); + } + UWidgetBlueprintLibrary::SetFocusToGameViewport(); +} +void ADefaultPlayerController::OnHideUI() +{ + if(!m_ingameMenuPanel) + return; + m_ingameMenu->OnHide(); + m_ingameMenu->CloseSubMenu(m_ingameMenuPanel); + m_ingameMenuPanel = nullptr; + UWidgetBlueprintLibrary::SetFocusToGameViewport(); +} + +void ADefaultPlayerController::m_OnPawnChanged() +{ + APawn* pawn = GetPawnOrSpectator(); + ANetworkPlayer* player = Cast(pawn); + ANetworkPossessable* possessable = Cast(pawn); + const bool isValid = IsValid(pawn) && pawn->IsA(); + + if (m_hud) + { + // Calculate skill prototypes + if (m_skillTree) + { + TArray skillsetPrototype = m_skillTree->GetSkillsForLevel(1.0f); + m_hud->NativeOnAssignCharacter(possessable, skillsetPrototype); + + // Set Skill bindings + m_mainSkillMapping.SetNum(0); + m_altSkillMapping.SetNum(0); + for (int32 i = 0; i < skillsetPrototype.Num(); i++) + { + switch (skillsetPrototype[i].abilityType) + { + case 0: + m_mainSkillMapping.Add(skillsetPrototype[i].selectedEffect); + break; + case 1: + m_altSkillMapping.Add(skillsetPrototype[i].selectedEffect); + break; + } + } + } + m_hud->SetVisibility(isValid ? ESlateVisibility::Visible : ESlateVisibility::Hidden); + } + + // Create respawn widget on local player + if(IsLocalController()) + { + if (isValid) + { + if (m_respawnWidget) + { + m_respawnWidget->RemoveFromViewport(); + m_respawnWidget = nullptr; + } + } + else // Assume default player + { + // Create respawn widget + if(!m_respawnWidget) + { + m_respawnWidget = CreateWidget(GetWorld(), respawnWidgetClass); + m_respawnWidget->AddToViewport(30); + } + } + } +} +void ADefaultPlayerController::m_UpdateUI() +{ + ANetworkPlayer* character = Cast(GetPawn()); + if(m_hud && character) + { + ADefaultPlayerState* state = Cast(PlayerState); + + TArray abilityStates; + for(int32 i = 0; i < character->abilities.Num(); ++i) + { + UAbilityInfo* ability = character->abilities[i]; + AAbilityState* state = character->GetAbilityState(ability); + state->cooldownRate = 1.0f - (state->onCooldownTimer / ability->cooldown); + if (ability->cooldown < 0.1f) + state->cooldownRate = 1.0f; + abilityStates.Add(state); + } + m_hud->UpdateCooldowns(abilityStates, character); + } +} + +void ADefaultPlayerController::m_CreateIngameSkilltree() +{ + if (!m_skillTree) + { + // Create logic-only skilltree + m_skillTree = GetWorld()->SpawnActor(AIngameSkillTree::StaticClass()); + check(m_skillTree); + } +} + +void ADefaultPlayerController::OnCharacterCreated(ANetworkCharacter* character) +{ + if (m_hud) + { + m_hud->OnCharacterCreated(character); + } + else + { + GWWARNING(L"OnCharacterCreated called on a controller without a HUD"); + } +} +void ADefaultPlayerController::OnCharacterDestroyed(ANetworkCharacter* character) +{ + if (m_hud) + { + m_hud->OnCharacterDestroyed(character); + } + else + { + GWWARNING(L"OnCharacterDestroyed called on a controller without a HUD"); + } +} + +const TArray& ADefaultPlayerController::GetAbilities(APawn* targetPawn) +{ + if (!IsValid(targetPawn)) + targetPawn = GetPawn(); + ANetworkPlayer* netPlayer = Cast(targetPawn); + if (netPlayer) + { + return netPlayer->abilities; + } + static TArray dummy; + return dummy; +} + +bool ADefaultPlayerController::HasGameFocus() +{ + return m_framesSinceMenuFocus > 10; +} + +void ADefaultPlayerController::CastAbility(int32 index) +{ + if(!HasGameFocus()) + return; + + // Basic Attack? + if(index == 0) + { + m_StartBasicAttack(); + m_usingBasicAttack = true; + } + + ANetworkPlayer* netPlayer = Cast(GetPawn()); + UAbilityInfo* info = GetMappedAbility(index); + if(netPlayer) + { + // Obtain assigned basic attack ability + if(index == 0) + info = netPlayer->basicAttack; + if(info) + { + netPlayer->CastAbility(info); + } + } +} +void ADefaultPlayerController::UnCastAbility(int32 index) +{ + if(!HasGameFocus()) + return; + + // Basic Attack? + if(index == 0) + { + m_StopBasicAttack(); + m_usingBasicAttack = false; + } + + // Only main-bar abilities and basic attack + if(index >= 5) + return; + + + // This casts abilities again which are hold abilities to turn them off + ANetworkPlayer* netPlayer = Cast(GetPawn()); + UAbilityInfo* info = GetMappedAbility(index); + + if (info && info->precastEvent && netPlayer && IsValid(netPlayer->m_currentPreCast)) + { + netPlayer->m_currentPreCast->StartAbility(); + return; + } + if(netPlayer && info) + { + if(info->actionType == EAbilityActionType::Hold) + { + AAbilityState* state = netPlayer->GetAbilityState(info); + if(state->toggleState == 1) + netPlayer->CastAbility(info); + } + } +} +UAbilityInfo* ADefaultPlayerController::GetMappedAbility(int32 index) +{ + ANetworkPlayer* netPlayer = Cast(GetPawn()); + if(netPlayer) + { + if(index == 0) + { + if(netPlayer->abilities.Num() > 0) + return netPlayer->abilities[0]; + } + else + { + bool useAlt = index > 4; + index = index - (useAlt ? 5 : 1); + + UAbilityInfo* ability = m_hud->buttonBarSwitcher->GetAbilityMapping(index, useAlt); + if(ability) + { + if(m_learnedAbilities.Contains(ability)) + { + return ability; + } + } + } + } + return nullptr; +} + +void ADefaultPlayerController::TurnAtRate(float Rate) +{ + if(!HasGameFocus()) + return; + + APawn* pawn = GetPawn(); + if (!pawn) + return; + // calculate delta for this frame from the rate information + pawn->AddControllerYawInput(Rate * 45.f * GetWorld()->GetDeltaSeconds()); +} +void ADefaultPlayerController::LookUp(float Rate) +{ + if(!HasGameFocus()) + return; + + APawn* pawn = GetPawn(); + if (!pawn) + return; + pawn->AddControllerPitchInput(Rate); +} +void ADefaultPlayerController::LookUpAtRate(float Rate) +{ + if(!HasGameFocus()) + return; + + APawn* pawn = GetPawn(); + if (!pawn) + return; + // calculate delta for this frame from the rate information + pawn->AddControllerPitchInput(Rate * 45.f * GetWorld()->GetDeltaSeconds()); +} +void ADefaultPlayerController::MoveForward(float Value) +{ + if(!HasGameFocus()) + return; + + ANetworkPossessable* pawn = Cast(GetPawn()); + if (!pawn) + return; + if (Value != 0.0f) + { + FVector forward = pawn->CameraBoom->GetForwardVector(); + forward.Z = 0; + forward.Normalize(); + pawn->AddMovementInput(forward, Value); + } +} +void ADefaultPlayerController::MoveRight(float Value) +{ + if(!HasGameFocus()) + return; + + ANetworkPossessable* pawn = Cast(GetPawn()); + if (!pawn) + return; + if (Value != 0.0f) + { + FVector right = pawn->CameraBoom->GetRightVector(); + right.Z = 0; + right.Normalize(); + pawn->AddMovementInput(right, Value); + } +} + +#define ABILITY_ATTACK(__id, __name) \ +void ADefaultPlayerController::CastAbility##__id()\ +{\ + CastAbility(__id);\ +}\ +void ADefaultPlayerController::UnCastAbility##__id()\ +{\ + UnCastAbility(__id);\ +} +#define ABILITY_OP(__id, __name) \ +void ADefaultPlayerController::CastAbility##__id()\ +{\ + if(m_coopModifier)\ + CastAbility(__id + 4);\ + else\ + CastAbility(__id);\ +}\ +void ADefaultPlayerController::UnCastAbility##__id()\ +{\ + UnCastAbility(__id);\ +} +#include "AbilityBindings.h" + +void ADefaultPlayerController::RightMouseDown() +{ + m_rightMouseDown = true; +} +void ADefaultPlayerController::RightMouseUp() +{ + m_rightMouseDown = false; +} +void ADefaultPlayerController::LeftMouseDown() +{ + if(!HasGameFocus()) + return; + + // Check for switches + FHitResult result; + if (GetHitResultUnderCursor(ECollisionChannel::ECC_WorldDynamic, false, result)) + { + ANetworkPlayer* pawn = Cast(GetPawn()); + if (result.Actor.Get() && result.Actor.Get()->IsA(ANetworkSwitch::StaticClass()) && pawn) + { + pawn->ToggleSwitch(Cast(result.Actor.Get())); + } + } +} + +void ADefaultPlayerController::ToggleSwitch() +{ + ANetworkPlayer* pawn = Cast(GetPawn()); + if (pawn) + { + TArray hits; + + FCollisionQueryParams TraceParams(FName(TEXT("OverlapMulti Trace")), false); + TraceParams.bTraceComplex = false; + TraceParams.bReturnPhysicalMaterial = false; + FVector position = pawn->GetActorLocation(); + FQuat rotation = FQuat(); + FCollisionShape colShape = FCollisionShape::MakeSphere(300); + + GetWorld()->OverlapMultiByObjectType(hits, position, rotation, FCollisionObjectQueryParams::AllDynamicObjects, colShape, TraceParams); + + for (size_t i = 0; i < hits.Num(); i++) + { + if (hits[i].GetActor()->IsA(ANetworkSwitch::StaticClass())) + { + pawn->ToggleSwitch(Cast(hits[i].GetActor())); + break; + } + } + + } +} + +void ADefaultPlayerController::LeftMouseUp() +{ + +} +void ADefaultPlayerController::HoldDown() +{ + m_holdPosition = true; +} +void ADefaultPlayerController::HoldUp() +{ + m_holdPosition = false; +} + +void ADefaultPlayerController::SpawnCharacterForClient() +{ + check(Role == ROLE_Authority); + UWorld* const world = GetWorld(); + check(world); + + ADefaultPlayerState* playerState = Cast(PlayerState); + check(playerState); + ADefaultGameMode* gameMode = world->GetAuthGameMode(); + check(gameMode); + if(gameMode->GetMatchState() != MatchState::InProgress) + return; + + int32 team = playerState->GetTeam(); + + ADefaultGameMode* mode = Cast(world->GetAuthGameMode()); + check(mode); + + // Get the spawns and spawn randomly + APlayerSpawn* const spawn = mode->GetOptimalSpawn(team); + if (IsValid(spawn)) + { + SpawnCharacterForClient(spawn->GetActorLocation() + FVector(0, 0, 120), spawn->GetActorRotation()); + } + else + JERROR("No spawn points available"); +} +void ADefaultPlayerController::SpawnCharacterForClient(const FVector& location, const FRotator& rotation) +{ + check(Role == ROLE_Authority); + UWorld* const world = GetWorld(); + check(world); + + ADefaultPlayerState* playerState = Cast(PlayerState); + check(playerState); + ADefaultGameMode* gameMode = world->GetAuthGameMode(); + check(gameMode); + if (gameMode->GetMatchState() != MatchState::InProgress) + return; + + int32 team = playerState->GetTeam(); + + ADefaultGameMode* mode = Cast(world->GetAuthGameMode()); + check(mode); + + FTransform f; + f.SetTranslation(location + FVector(0, 0, 120)); + f.SetRotation(rotation.Quaternion()); + ANetworkPlayer* character = world->SpawnActor(characterClass, f); + if (!character) + { + GWERROR(L"Failed to spawn character for client, update client??"); + return; + } + character->SetTeam(team); + + mode->RegisterPlayer(*character); + Possess(character); + +#if UE_INCLUDE_METRICS + if (Role == ROLE_Authority) + { + // Register a new player, with name + std::wstring playerName = std::wstring(L"Player ") + GetUniqueID(); + if (IsValid(playerState)) + { + playerName = std::wstring() + playerState->PlayerName; + if (playerName.length() > 512) + playerName.resize(512); + } + + // Assign metrics handle + if (!metricsHandle) + metricsHandle = &Metrics::RegisterPlayer(playerName, uint8(team)); + character->metricsHandle = metricsHandle; + metricsHandle->OnPlayerSpawn(location.X, location.Y); + } +#endif +} + +bool ADefaultPlayerController::LearnSkillOnServer_Validate(UBaseSkillObject* object) +{ + return object != nullptr && (m_skills.Find(object) == INDEX_NONE); +} +void ADefaultPlayerController::LearnSkillOnServer_Implementation(UBaseSkillObject* object) +{ + m_skills.Push(object); +} +bool ADefaultPlayerController::UnlearnSkillOnServer_Validate( UBaseSkillObject* object ) +{ + return object != nullptr && (m_skills.Find( object ) > INDEX_NONE); +} +void ADefaultPlayerController::UnlearnSkillOnServer_Implementation( UBaseSkillObject* object ) +{ + m_skills.Remove( object ); +} + +void ADefaultPlayerController::OnLearnSkill(UBaseSkillObject* object) +{ + //JPRINT("Learned skill " + object ); + //LearnSkillOnServer( object ); +} +void ADefaultPlayerController::OnUnlearnSkill(UBaseSkillObject* object) +{ + //JPRINT("Unlearned skill " + object ); + //UnlearnSkillOnServer( object ); +} + +TArray& ADefaultPlayerController::GetSkills() +{ + return m_skills; +} + +void ADefaultPlayerController::SpawnCombatText_Implementation(const FVector& position, const FString& string, const FLinearColor& color) +{ + if (IsValid(m_hud) && IsValid(m_hud->combatText)) + m_hud->combatText->CreateCombatText(position, string, color); +} +void ADefaultPlayerController::SpawnArcingCombatText_Implementation(const FVector& position, const FString& string, const FLinearColor& color) +{ + if (IsValid(m_hud) && IsValid(m_hud->combatText)) + m_hud->combatText->CreateArcingCombatText(position, string, color); +} + +void ADefaultPlayerController::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(ADefaultPlayerController, m_skills); + DOREPLIFETIME(ADefaultPlayerController, m_defaultPlayer); +} + +void ADefaultPlayerController::UpdateLevel() +{ + if(IsLocalController()) + { + ADefaultPlayerState* state = Cast(PlayerState); + if(m_skillTreeWidget && state) + { + m_skillTreeWidget->UpdateLevel(state->GetLevel() / (float)state->GetMaxLevel()); + } + } +} + +void ADefaultPlayerController::SetLevel(int32 level) +{ + SetLevel_Server(level); +} + +void ADefaultPlayerController::Suicide() +{ + Suicide_Server(); +} +bool ADefaultPlayerController::Suicide_Server_Validate() +{ + return true; +} +void ADefaultPlayerController::Suicide_Server_Implementation() +{ +#if WITH_EDITORONLY_DATA == 1 + ANetworkPlayer* player = Cast(GetPawn()); + if(player && player->abilities[0]) + { + player->NativeDealDamage(player, 9999999, 1.0f, player->abilities[0]); + } +#endif +} + +bool ADefaultPlayerController::SetLevel_Server_Validate(int32 level) +{ + return true; +} +void ADefaultPlayerController::SetLevel_Server_Implementation(int32 level) +{ +#if WITH_EDITORONLY_DATA == 1 + ADefaultPlayerState* state = Cast(PlayerState); + if(state) + { + state->SetLevel(level); + } +#endif +} + +void ADefaultPlayerController::TestWinGame(int32 team) +{ + if(Role == ROLE_Authority) + { + ADefaultGameMode* gm = Cast(GetWorld()->GetAuthGameMode()); + AKingOfTheHillGameMode* kgm = Cast(GetWorld()->GetAuthGameMode()); + if(kgm) + { + kgm->WinGame(team); + } + else if(gm) + { + gm->EndMatch(); + + auto players = gm->GetPlayers(); + // Destroy all player pawns so that they respawn as spectators + for(int32 i = 0; i < players.Num(); i++) + { + if(IsValid(players[i])) + { + players[i]->Destroy(true); + } + } + } + } +} + +void ADefaultPlayerController::CoopModifierOn() +{ + if(!HasGameFocus()) + return; + + m_coopModifier = true; + if(m_hud && m_hud->buttonBarSwitcher) + { + m_hud->buttonBarSwitcher->ToggleSkillSet(m_coopModifier); + } +} +void ADefaultPlayerController::CoopModifierOff() +{ + if(!HasGameFocus()) + return; + + m_coopModifier = false; + if(m_hud && m_hud->buttonBarSwitcher) + { + m_hud->buttonBarSwitcher->ToggleSkillSet(m_coopModifier); + } +} + +void ADefaultPlayerController::m_StartBasicAttack() +{ +} +void ADefaultPlayerController::m_TickBasicAttack() +{ + ANetworkPlayer* netPlayer = Cast(GetPawn()); + if(netPlayer) + { + UAbilityInfo* info = netPlayer->basicAttack; + + // Tick normal basic attacks + if(info && info->actionType == EAbilityActionType::Normal) + { + netPlayer->CastAbility(info); + } + } +} +void ADefaultPlayerController::m_StopBasicAttack() +{ +} \ No newline at end of file diff --git a/Source/UnrealProject/GameState/DefaultPlayerController.h b/Source/UnrealProject/GameState/DefaultPlayerController.h new file mode 100644 index 0000000..422ee18 --- /dev/null +++ b/Source/UnrealProject/GameState/DefaultPlayerController.h @@ -0,0 +1,212 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "GameFramework/PlayerController.h" +#include "IngameSkillTree.h" +#include "PlayerControllerBase.h" +#include "PlayerSetupState.h" +#include "DefaultPlayerController.generated.h" + +UCLASS() +class UNREALPROJECT_API ADefaultPlayerController : public APlayerControllerBase +{ + GENERATED_BODY() + +public: + ADefaultPlayerController(const FObjectInitializer& init); + virtual void BeginPlay() override; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + virtual void Destroyed() override; + virtual void SetupInputComponent() override; + virtual void Tick(float DeltaSeconds) override; + + virtual void Possess(APawn* aPawn) override; + + virtual void SeamlessTravelFrom(class APlayerController* OldPC) override; + + // This is used to handle the player being destroyed + virtual void PawnPendingDestroy(APawn* inPawn) override; + + void UpdatePlayerPosition(const FVector& location, const FRotator& rotator); + + // Called when the possesed pawn is replicated + virtual void OnRep_Pawn() override; + + UFUNCTION(Reliable, Client) + void OnLevelUpClient(const TArray& updatedSkills, const TArray& oldSkills); + + // Only called on server, called to learn skills on the currently owned pawn + void LearnSkillsForLevel(); + + bool GetAbilityButtonLocation(class UAbilityInfo* ability, int32& slot, bool& isAlt); + int32 GetCurrentAbilityLevel(class UAbilityInfo* ability); + + void ToggleSkillTree(); + void ToggleMenu(); + UFUNCTION(BlueprintCallable, Category="UI") + void OnHideUI(); + void OnCharacterCreated(class ANetworkCharacter* character); + void OnCharacterDestroyed(class ANetworkCharacter* character); + const TArray& GetAbilities(APawn* targetPawn = nullptr); + + bool HasGameFocus(); + + // Ability cast + void CastAbility(int32 index); + // Called for hold abilities to turn them off + void UnCastAbility(int32 index); + + UAbilityInfo* GetMappedAbility(int32 index); + + // Ability Button-Down events + void CastAbility0(); + void CastAbility1(); + void CastAbility2(); + void CastAbility3(); + void CastAbility4(); + void CastAbility5(); + void CastAbility6(); + void CastAbility7(); + void CastAbility8(); + // Ability Button-Up events + void UnCastAbility0(); + void UnCastAbility1(); + void UnCastAbility2(); + void UnCastAbility3(); + void UnCastAbility4(); + void UnCastAbility5(); + void UnCastAbility6(); + void UnCastAbility7(); + void UnCastAbility8(); + + void CoopModifierOn(); + void CoopModifierOff(); + void MoveForward(float Value); + void MoveRight(float Value); + void TurnAtRate(float Rate); + void LookUp(float Rate); + void LookUpAtRate(float Rate); + + void RightMouseDown(); + void RightMouseUp(); + void LeftMouseDown(); + void ToggleSwitch(); + void LeftMouseUp(); + void HoldDown(); + void HoldUp(); + + void SpawnCharacterForClient(); + void SpawnCharacterForClient(const FVector& location, const FRotator& rotation); + + UFUNCTION(Reliable, Server, WithValidation) + void LearnSkillOnServer(class UBaseSkillObject* object); + UFUNCTION( Reliable, Server, WithValidation ) + void UnlearnSkillOnServer(class UBaseSkillObject* object); + + void OnLearnSkill(class UBaseSkillObject* object); + void OnUnlearnSkill(class UBaseSkillObject* object); + + TArray& GetSkills(); + + void UpdateLevel(); + + UFUNCTION(Exec) + void SetLevel(int32 level); + UFUNCTION(Reliable, Server, WithValidation) + void SetLevel_Server(int32 level); + + UFUNCTION(Exec) + void Suicide(); + UFUNCTION(Reliable, Server, WithValidation) + void Suicide_Server(); + + UFUNCTION(Exec) + void TestWinGame(int32 team); + + UFUNCTION(Client, Reliable) + void SpawnCombatText(const FVector& position, const FString& string, const FLinearColor& color); + UFUNCTION(Client, Reliable) + void SpawnArcingCombatText(const FVector& position, const FString& string, const FLinearColor& color); + + // Called when setupState is replicated + UFUNCTION() + virtual void OnRep_Setup() override; + + UFUNCTION(BlueprintCallable, Category="State") + FCharacterClassProperty GetCharacterClassProperties() const; + + UFUNCTION(BlueprintCallable, Category = "State") + class UIngameHUD* GetHUD(); + + // Player location history + struct Location + { + Location(const FVector& location, const FRotator& rotation) : location(location), rotation(rotation) {} + FVector location; + FRotator rotation; + }; + TArray playerLocationHistory; + + UPROPERTY(BlueprintReadOnly, EditAnywhere) + class UCharacterClassPropertySet* characterClassProperties; + + METRICS_EXPR(Metrics::PlayerHandle* metricsHandle); + +protected: + // Called when the player possesed pawn has changed, on client or server(players) + virtual void m_OnPawnChanged(); + +private: + void m_UpdateUI(); + void m_CreateIngameSkilltree(); + void m_StartBasicAttack(); + void m_TickBasicAttack(); + void m_StopBasicAttack(); + + // To check editor play + bool m_seamlessTravelDone; + + // Selected class properties the player chose for his character build + FCharacterClassProperty m_classProperties; + + UPROPERTY(Replicated) + TArray m_skills; + + // Check for recent mouse/controller usage + FVector2D m_mouseLast; + + FVector m_holdDirection; + + bool m_rightMouseDown; + bool m_coopModifier; + bool m_holdPosition; + bool m_skillTreeInitialized; + bool m_usingBasicAttack; + + TArray m_mainSkillMapping; + TArray m_altSkillMapping; + TSet m_learnedAbilities; + + TArray m_alreadyLearnedSkills; + + uint32 m_framesSinceMenuFocus; + UPROPERTY() + class UMenuScreen* m_ingameMenu; + UPROPERTY() + class UMenuPanel* m_ingameMenuPanel; + UPROPERTY() + class UIngameHUD* m_hud; + UPROPERTY() + class UUserWidget* m_respawnWidget; + UPROPERTY() + class USkillTreeWidget* m_skillTreeWidget; + friend class ABuff; + + UPROPERTY() + class AIngameSkillTree* m_skillTree; + + UPROPERTY(Replicated) + class ADefaultPlayer* m_defaultPlayer; + +}; diff --git a/Source/UnrealProject/GameState/DefaultPlayerState.cpp b/Source/UnrealProject/GameState/DefaultPlayerState.cpp new file mode 100644 index 0000000..5c61f46 --- /dev/null +++ b/Source/UnrealProject/GameState/DefaultPlayerState.cpp @@ -0,0 +1,121 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" + +#include "DefaultPlayerState.h" +#include "DefaultGameState.h" +#include "DefaultGameInstance.h" +#include "NetworkPlayer.h" + +int32 maxLevel = 16; + +ADefaultPlayerState::ADefaultPlayerState() +{ + // Set the default values for the constant formula variables. + // BaseXP + (VarXP * (Level ^ Exp)); + m_baseExperience = 200; + m_variableExperience = 20; + m_exponentExperience = 1.2f; + + // Default values for the player. + m_level = 1; + m_experience = 0; + character = nullptr; + + // Calculate the experience to level up. + m_experienceToLevel = m_CalculateExperienceToLevel(m_level); + + kills = 0; + deaths = 0; +} + +void ADefaultPlayerState::BeginPlay() +{ + Super::BeginPlay(); +} + +void ADefaultPlayerState::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(ADefaultPlayerState, m_level); + DOREPLIFETIME(ADefaultPlayerState, m_experience); + DOREPLIFETIME(ADefaultPlayerState, m_experienceToLevel); + DOREPLIFETIME(ADefaultPlayerState, character); + DOREPLIFETIME(ADefaultPlayerState, teamMate); + DOREPLIFETIME(ADefaultPlayerState, kills); + DOREPLIFETIME(ADefaultPlayerState, deaths); +} + +// The player level gets converted to an int32, because this function is BlueprintCallable, +// which does not allow int8 values. +int32 ADefaultPlayerState::GetLevel() const +{ + return (int32)m_level; +} + +int32 ADefaultPlayerState::GetMaxLevel() const +{ + return maxLevel; +} + +int32 ADefaultPlayerState::GetExperience() const +{ + return m_experience; +} +int32 ADefaultPlayerState::GetExperienceToLevel() const +{ + return m_experienceToLevel; +} + +void ADefaultPlayerState::GainExperience(int32 gainedExperience) +{ + check(gainedExperience >= 0); + m_experience += gainedExperience; + + if (m_level == maxLevel) + return; + + // Check if a player has leveled up (multiple times). + while (m_experience >= m_experienceToLevel) + { + // Level up. + ++m_level; + m_experience -= m_experienceToLevel; + // Calculate the experience to level up. + m_experienceToLevel = m_CalculateExperienceToLevel(m_level); + if (character) + character->OnLevelUp_Server(m_level); + + if (m_level == maxLevel) + { + m_experience = 0; + break; + } + } +} + +void ADefaultPlayerState::SetLevel(int32 level) +{ + if (Role == ROLE_Authority) + { + GPRINT("Setting level to " + level); + m_level = FMath::Clamp(level, 1, maxLevel); + m_experienceToLevel = m_CalculateExperienceToLevel(m_level + 1); + m_experience = 0; + if(character) + { + character->OnLevelUp_Server(level); + } + } +} + +// Refer to the google/excel sheet for the exact value this will generate. +// Google sheet: https://docs.google.com/spreadsheets/d/1iuSg5efQSg0l2iQ5RLv3wF44RX2-i-gF-1QUB896bVE/edit#gid=0 +// Excel sheet: I:/Y2015A-Y3-BountyMaze/Programming/Documentation/Experience required to level Sheet.xlsx +int32 ADefaultPlayerState::m_CalculateExperienceToLevel(int32 level) +{ + // BaseXP + (VarXP * (Level ^ Exp)); + return m_baseExperience + m_variableExperience * (pow(level, m_exponentExperience)); +} + diff --git a/Source/UnrealProject/GameState/DefaultPlayerState.h b/Source/UnrealProject/GameState/DefaultPlayerState.h new file mode 100644 index 0000000..c0e1c35 --- /dev/null +++ b/Source/UnrealProject/GameState/DefaultPlayerState.h @@ -0,0 +1,68 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "PlayerStateBase.h" +#include "DefaultPlayerState.generated.h" + +/* + In-game state of a player. +*/ +UCLASS() +class UNREALPROJECT_API ADefaultPlayerState : public APlayerStateBase +{ + GENERATED_BODY() +public: + ADefaultPlayerState(); + virtual void BeginPlay() override; + + // Get Functions + // Integer level value from 1-max + UFUNCTION(BlueprintCallable, Category = "Level Functions") + int32 GetLevel() const; + UFUNCTION(BlueprintCallable, Category = "Level Functions") + int32 GetMaxLevel() const; + // Current experience in-level + UFUNCTION(BlueprintCallable, Category = "Level Functions") + int32 GetExperience() const; + // Totale experience needed for the next level + UFUNCTION(BlueprintCallable, Category = "Level Functions") + int32 GetExperienceToLevel() const; + + // DEBUG functionality for setting a player level + void SetLevel(int32 level); + + // Call the function to grant a player with experience, this function will also handle leveling up. + UFUNCTION(BlueprintCallable, Category = "Level Functions") + void GainExperience(int32 gainedExperience); + + // The character the player currently controls, or null when dead + UPROPERTY(BlueprintReadOnly, Replicated, Category = "Character") + class ANetworkPlayer* character; + + // The person that is in the same team as this player, or null when dead or there is none + UPROPERTY(BlueprintReadOnly, Replicated, Category = "Character") + class ADefaultPlayerState* teamMate; + + UPROPERTY(BlueprintReadonly, Replicated, Category = "Score") + int32 kills; + UPROPERTY(BlueprintReadonly, Replicated, Category = "Score") + int32 deaths; + +private: + // Calculates the experience needed to level up to a given level + int32 m_CalculateExperienceToLevel(int32 level); + + // Variables to keep track of what level/experience the player has. + UPROPERTY(Replicated) + int8 m_level; + UPROPERTY(Replicated) + int32 m_experience; + // Variable to check if a player has leveled up. + UPROPERTY(Replicated) + int32 m_experienceToLevel; + // Variables for calculating the experience to level up. + int32 m_baseExperience; + int32 m_variableExperience; + float m_exponentExperience; +}; diff --git a/Source/UnrealProject/GameState/GameStateBase.cpp b/Source/UnrealProject/GameState/GameStateBase.cpp new file mode 100644 index 0000000..109089d --- /dev/null +++ b/Source/UnrealProject/GameState/GameStateBase.cpp @@ -0,0 +1,201 @@ +#include "UnrealProject.h" +#include "GameStateBase.h" + +#include "PlayerStateBase.h" +#include "DefaultPlayerController.h" +#include "DefaultGameInstance.h" +#include "MapData.h" + +AGameStateBase::AGameStateBase() +{ + m_mapTeamCount = 4; + m_hasWarnedFrank = false; +} +void AGameStateBase::BeginPlay() +{ + Super::BeginPlay(); + mapPath = GetWorld()->GetPathName(); + m_LoadActiveMapInfo(); +} +void AGameStateBase::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + Super::EndPlay(EndPlayReason); + OnPlayerStateChange.Clear(); +} +void AGameStateBase::RegisterPlayer(APlayerControllerBase* player) +{ + check(Role == ROLE_Authority); + check(player); + APlayerStateBase* state = Cast(player->PlayerState); + check(state); + m_playersByController.Add(player, state); + m_players.Add(state); + + // More than 1 player in editor means multiplayer in PIE mode + if(m_players.Num() > 1) + { + EnterLobby(); + } + + OnPlayerStateChange.Broadcast(nullptr); +} +void AGameStateBase::DeregisterPlayer(APlayerControllerBase* player) +{ + check(Role == ROLE_Authority); + m_playersByController.Remove(player); + + m_players.SetNum(0); + for(auto it = m_playersByController.CreateIterator(); it; ++it) + { + m_players.Add(it->Value); + } + + OnPlayerStateChange.Broadcast(nullptr); +} +void AGameStateBase::EnterLobby() +{ + check(Role == ROLE_Authority); + for(auto it = m_playersByController.CreateIterator(); it; ++it) + { + it->Key->EnterLobby(); + } +} + +void AGameStateBase::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(AGameStateBase, m_players); + DOREPLIFETIME(AGameStateBase, m_mapTeamCount); + DOREPLIFETIME(AGameStateBase, mapPath); +} + +TArray& AGameStateBase::GetPlayers() +{ + return m_players; +} +TArray> AGameStateBase::GetPlayersByTeam() +{ + TArray> ret; + for(int32 i = 0; i < m_players.Num(); i++) + { + APlayerStateBase* player = m_players[i]; + if(!player) + continue; + int32 team = player->GetTeam(); + if(team >= ret.Num()) + { + ret.SetNum(team + 1); + } + TArray& dstArray = ret[team]; + + dstArray.Add(player); + } + return ret; +} + +TArray AGameStateBase::GetTeamSizes() +{ + TArray teamSizes; + teamSizes.SetNumZeroed(m_mapTeamCount); + + for(int32 i = 0; i < m_players.Num(); i++) + { + if(!m_players[i]) + continue; + int32 team = m_players[i]->GetTeam() - 1; + if(team < 0 || team >= m_mapTeamCount) + continue; + teamSizes[team]++; + } + + return TArray(teamSizes); +} +int32 AGameStateBase::GetOptimalAutoJoinTeam() +{ + if(GetWorld()->IsPlayInEditor()) + { + // PIE uses different assignment, default to 2 players per team and then move to the next team + TArray teamSize = GetTeamSizes(); + int32 optimalTeam = -1; + + for(int32 i = 0; i < m_mapTeamCount; i++) + { + if(teamSize[i] < 2) + { + optimalTeam = i; + break; + } + } + + return optimalTeam + 1; + } + else + { + TArray teamSize = GetTeamSizes(); + int32 smallestSize = 2; + int32 optimalTeam = -1; + + for(int32 i = 0; i < m_mapTeamCount; i++) + { + if(teamSize[i] < smallestSize) + { + optimalTeam = i; + smallestSize = teamSize[i]; + } + } + + return optimalTeam + 1; + } +} +int32 AGameStateBase::GetMapTeamCount() const +{ + return m_mapTeamCount; +} + +UMapData* AGameStateBase::GetMapData() const +{ + return m_mapData; +} +void AGameStateBase::m_OnRep_MapPath() +{ + GPRINT("Map changed to " + mapPath); + m_LoadActiveMapInfo(); + if(m_mapData) + GPRINT("Found attached map data " + m_mapData->GetName()); + else + GERROR("No attached map data found."); +} + +void AGameStateBase::OnPlayerStateChange_Multi_Implementation() +{ + if (m_playersByController.Num() > 0) + OnPlayerStateChange.Broadcast(nullptr); +} + +TMap AGameStateBase::GetPlayersByController() +{ + return m_playersByController; +} + +bool AGameStateBase::m_LoadActiveMapInfo() +{ + UDefaultGameInstance* gameInstance = Cast(GetGameInstance()); + if(!gameInstance) + return false; + m_mapData = gameInstance->GetMapData(mapPath); + if(!m_mapData) + { + if(!m_hasWarnedFrank) + { + GWERROR(L"Failed to load map info, Create a map info asset with the name formated as I_ + map name in the same folder and add the I_ file to the map list frank!!!"); + m_hasWarnedFrank = true; + } + m_mapTeamCount = 4; + } + else + { + m_mapTeamCount = m_mapData->maxTeamCount; + } + return true; +} \ No newline at end of file diff --git a/Source/UnrealProject/GameState/GameStateBase.h b/Source/UnrealProject/GameState/GameStateBase.h new file mode 100644 index 0000000..ca36632 --- /dev/null +++ b/Source/UnrealProject/GameState/GameStateBase.h @@ -0,0 +1,106 @@ +#pragma once +#include "GameFramework/GameState.h" +#include "GameStateBase.Generated.h" + +/* + Lobby state +*/ + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnPlayerStateChange, class APlayerControllerBase*, playerController); + +USTRUCT() +struct FLobbyStatePlayer +{ + GENERATED_USTRUCT_BODY() +public: + int32 team; + TSharedPtr netID; +}; + +UCLASS() +class ALobbyState : public AInfo +{ + GENERATED_BODY() +public: + UPROPERTY() + TArray players; +}; + +/* + Base game state, keeping all the players in the current game and keeping their assigned teams +*/ +UCLASS() +class AGameStateBase : public AGameState +{ + GENERATED_BODY() + friend class AMenuGameMode; + +public: + AGameStateBase(); + virtual void BeginPlay() override; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason); + + virtual void RegisterPlayer(class APlayerControllerBase* player); + virtual void DeregisterPlayer(class APlayerControllerBase* player); + + // Tells all the players to go to the lobby screen + void EnterLobby(); + + TArray& GetPlayers(); + template TArray GetPlayers() + { + return reinterpret_cast&>(GetPlayers()); + } + TArray> GetPlayersByTeam(); + template TArray> GetPlayersByTeam() + { + auto r = GetPlayersByTeam(); + return reinterpret_cast>&>(r); + } + + UFUNCTION(BlueprintCallable, Category = "Players") + TArray GetPlayerControllers() + { + TArray result; + m_playersByController.GenerateKeyArray(result); + return result; + } + + TArray GetTeamSizes(); + int32 GetOptimalAutoJoinTeam(); + + UFUNCTION(BlueprintCallable, Category="Team") + int32 GetMapTeamCount() const; + + UFUNCTION(BlueprintCallable, Category = "Map") + UMapData* GetMapData() const; + + UPROPERTY(Replicated, ReplicatedUsing = m_OnRep_MapPath, BlueprintReadOnly, Category = "Map") + FString mapPath; + + // Called when a player has joined/left or switched team + UFUNCTION(NetMulticast, Reliable) + void OnPlayerStateChange_Multi(); + UPROPERTY(BlueprintAssignable, Category = Events) + FOnPlayerStateChange OnPlayerStateChange; + + TMap GetPlayersByController(); + +private: + // Tries to load the current map data + bool m_LoadActiveMapInfo(); + UFUNCTION() + void m_OnRep_MapPath(); + + UPROPERTY(Replicated) + int32 m_mapTeamCount; + UPROPERTY() + class UMapData* m_mapData; + // For debugging + bool m_hasWarnedFrank; + + UPROPERTY() + TMap m_playersByController; + UPROPERTY(Replicated) + TArray m_players; +}; \ No newline at end of file diff --git a/Source/UnrealProject/GameState/KOTHGameState.cpp b/Source/UnrealProject/GameState/KOTHGameState.cpp new file mode 100644 index 0000000..78d77df --- /dev/null +++ b/Source/UnrealProject/GameState/KOTHGameState.cpp @@ -0,0 +1,31 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "KOTHGameState.h" + + +void AKOTHGameState::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + DOREPLIFETIME(AKOTHGameState, kothTeams); + DOREPLIFETIME(AKOTHGameState, maxGameScore); + DOREPLIFETIME(AKOTHGameState, overtime); +} + +FKOTHTeamState AKOTHGameState::GetTeam(int32 team) const +{ + // Team to array index [1-5] -> [0-4] + team -= 1; + if(team >= 0 && team < kothTeams.Num()) + return kothTeams[team]; + return FKOTHTeamState(); +} + +void AKOTHGameState::BroadcastOnHillCaptured_Implementation(int32 team) +{ + onHillCaptured.Broadcast(team); +} +void AKOTHGameState::m_OnRep_Overtime() +{ + GPRINT("Overtime = " + overtime); +} diff --git a/Source/UnrealProject/GameState/KOTHGameState.h b/Source/UnrealProject/GameState/KOTHGameState.h new file mode 100644 index 0000000..6f7ff5a --- /dev/null +++ b/Source/UnrealProject/GameState/KOTHGameState.h @@ -0,0 +1,38 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "DefaultGameState.h" +#include "KOTHTeamState.h" +#include "KOTHGameState.generated.h" + +UCLASS() +class AKOTHGameState : public ADefaultGameState +{ + GENERATED_BODY() +public: + void GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const; + + UFUNCTION(BlueprintCallable, Category="Game") + FKOTHTeamState GetTeam(int32 team) const; + + UFUNCTION(NetMulticast, Reliable) + void BroadcastOnHillCaptured(int32 team); + + DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnHillCaptured, int32, team); + UPROPERTY(BlueprintAssignable, Category="Game") + FOnHillCaptured onHillCaptured; + + UPROPERTY(Replicated, ReplicatedUsing=m_OnRep_Overtime, BlueprintReadOnly) + bool overtime; + UPROPERTY(Replicated, BlueprintReadOnly) + TArray kothTeams; + UPROPERTY(Replicated, BlueprintReadOnly) + float maxGameScore; + +private: + UFUNCTION() + void m_OnRep_Overtime(); + + friend class AKingOfTheHillGameMode; +}; diff --git a/Source/UnrealProject/GameState/KOTHPlayerController.cpp b/Source/UnrealProject/GameState/KOTHPlayerController.cpp new file mode 100644 index 0000000..4b53ac4 --- /dev/null +++ b/Source/UnrealProject/GameState/KOTHPlayerController.cpp @@ -0,0 +1,47 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "KOTHPlayerController.h" +#include "NetworkPlayer.h" +#include "KOTHHUD.h" +#include "KOTHGameState.h" + +void AKOTHPlayerController::BeginPlay() +{ + Super::BeginPlay(); +} + +void AKOTHPlayerController::Tick(float DeltaSeconds) +{ + Super::Tick(DeltaSeconds); + + AKOTHGameState* gs = GetGameState(); + if(gs && m_kothhud) + { + m_kothhud->Update(gs->kothTeams); + } +} + +void AKOTHPlayerController::m_OnPawnChanged() +{ + Super::m_OnPawnChanged(); + + class ANetworkPlayer* character = Cast(GetPawn()); + if(character) + { + if(IsLocalController()) + { + if(!m_kothhud) + { + m_kothhud = CreateWidget(GetWorld(), kothHUD); + if(m_kothhud) + { + m_kothhud->AddToViewport(2); + } + } + } + } + + if(m_kothhud) + m_kothhud->SetVisibility(character ? ESlateVisibility::Visible : ESlateVisibility::Hidden); +} diff --git a/Source/UnrealProject/GameState/KOTHPlayerController.h b/Source/UnrealProject/GameState/KOTHPlayerController.h new file mode 100644 index 0000000..18049f4 --- /dev/null +++ b/Source/UnrealProject/GameState/KOTHPlayerController.h @@ -0,0 +1,23 @@ +// Project Lab - NHTV Igad + +#pragma once +#include "DefaultPlayerController.h" +#include "KOTHPlayerController.generated.h" + +UCLASS() +class UNREALPROJECT_API AKOTHPlayerController : public ADefaultPlayerController +{ + GENERATED_BODY() +public: + virtual void BeginPlay() override; + virtual void Tick(float DeltaSeconds) override; + + UPROPERTY(EditDefaultsOnly) + TSubclassOf kothHUD; + +protected: + virtual void m_OnPawnChanged(); + +private: + class UKOTHHUD* m_kothhud; +}; diff --git a/Source/UnrealProject/GameState/KOTHTeamState.h b/Source/UnrealProject/GameState/KOTHTeamState.h new file mode 100644 index 0000000..f2d9e29 --- /dev/null +++ b/Source/UnrealProject/GameState/KOTHTeamState.h @@ -0,0 +1,18 @@ +#pragma once +#include "KOTHTeamState.Generated.h" + +USTRUCT(BlueprintType) +struct FKOTHTeamState +{ + GENERATED_USTRUCT_BODY() +public: + UPROPERTY(BlueprintReadOnly) + float score; + UPROPERTY(BlueprintReadOnly) + float maxScore; + // If this is greater than 0, the team has recently received score + UPROPERTY(BlueprintReadOnly) + int32 active; + UPROPERTY(BlueprintReadOnly) + bool containsMembers; +}; diff --git a/Source/UnrealProject/GameState/KingOfTheHillGameMode.cpp b/Source/UnrealProject/GameState/KingOfTheHillGameMode.cpp new file mode 100644 index 0000000..275bd1a --- /dev/null +++ b/Source/UnrealProject/GameState/KingOfTheHillGameMode.cpp @@ -0,0 +1,131 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "KingOfTheHillGameMode.h" +#include "DefaultGameState.h" +#include "DefaultGameState.h" +#include "DefaultGameInstance.h" +#include "PlayerSpawn.h" +#include "NetworkPlayer.h" +#include "CreatureSpawn.h" +#include "DefaultPlayer.h" +#include "DefaultPlayerState.h" +#include "KOTHPlayerController.h" +#include "KOTHGameState.h" +#include "KOTHBossSpawner.h" + +AKingOfTheHillGameMode::AKingOfTheHillGameMode() +{ + PrimaryActorTick.bCanEverTick = true; + + // Lobby system starts the match + bDelayedStart = true; + + PlayerControllerClass = AKOTHPlayerController::StaticClass(); + DefaultPawnClass = ConstructorHelpers::FClassFinder(TEXT("/Game/Assets/Blueprints/BP_DefaultPlayer")).Class; + SpectatorClass = ConstructorHelpers::FClassFinder(TEXT("/Game/Assets/Blueprints/BP_SpectatorPawn")).Class; + + GameStateClass = AKOTHGameState::StaticClass(); + PlayerStateClass = ADefaultPlayerState::StaticClass(); + maxGameScore = 60.0f; + m_currentTeamWithHill = -1; +} +void AKingOfTheHillGameMode::InitGame(const FString& MapName, const FString& Options, FString& ErrorMessage) +{ + Super::InitGame(MapName, Options, ErrorMessage); + + auto spawnerIt = TActorIterator(GetWorld(), AKOTHBossSpawner::StaticClass()); + if(!spawnerIt) + { + GERROR("No instance of AKOTHBossSpawner found in the level, you need one when using the KOTH game mode!"); + } + else + { + bossSpawner = *spawnerIt; + } +} +void AKingOfTheHillGameMode::HandleMatchHasStarted() +{ + AKOTHGameState* gameState = GetGameState(); + gameState->maxGameScore = maxGameScore; + TArray teamsize = gameState->GetTeamSizes(); + for (int i = 0; i < teamsize.Num(); i++) + { + FKOTHTeamState state; + state.active = 0; + state.score = 0.0f; + state.maxScore = maxGameScore; + state.containsMembers = teamsize[i] > 0; + gameState->kothTeams.Add(state); + } + + Super::HandleMatchHasStarted(); +} +void AKingOfTheHillGameMode::Tick(float deltaTime) +{ + Super::Tick(deltaTime); + + AKOTHGameState* gameState = GetGameState(); + for (int i = 0; i < gameState->kothTeams.Num(); i++) + { + FKOTHTeamState& state = gameState->kothTeams[i]; + if(state.score >= maxGameScore) + { + // Check overtime condition + if(bossSpawner) + { + // Check overtime conditions / convert index to team index [0-4] -> [1-5] + if(!bossSpawner->IsContested(i + 1) && bossSpawner->IsBossAlive()) + { + WinGame(i); + } + else + { + gameState->overtime = true; + } + } + } + + if(state.active > 0) + { + state.active--; + } + } + +} +void AKingOfTheHillGameMode::WinGame(int team) +{ + TArray players(m_players); + + // Set winning team in gamestate (replicated) + GetGameState()->gameWinningTeam = team; + + EndMatch(); + + // Destroy all player pawns so that they respawn as spectators + for (int32 i = 0; i < players.Num(); i++) + { + if(IsValid(players[i])) + { + players[i]->Destroy(true); + } + } + + //TPRINT("Boss was killed. (ADefaultGameMode) the team = " + team); +} + +void AKingOfTheHillGameMode::AddScore(int team, float duration) +{ + // team to array index [1-5] -> [0-4] + team -= 1; + AKOTHGameState* gameState = GetGameState(); + if(team < 0 || team >= gameState->kothTeams.Num()) + return; + gameState->kothTeams[team].score += duration; + gameState->kothTeams[team].active = 2; + if(m_currentTeamWithHill != team) + { + gameState->BroadcastOnHillCaptured(team); + m_currentTeamWithHill = team; + } +} \ No newline at end of file diff --git a/Source/UnrealProject/GameState/KingOfTheHillGameMode.h b/Source/UnrealProject/GameState/KingOfTheHillGameMode.h new file mode 100644 index 0000000..78bea4a --- /dev/null +++ b/Source/UnrealProject/GameState/KingOfTheHillGameMode.h @@ -0,0 +1,35 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "GameState/DefaultGameMode.h" +#include "KOTHTeamState.h" +#include "KingOfTheHillGameMode.generated.h" + +/** + * + */ +UCLASS(Config=Game) +class UNREALPROJECT_API AKingOfTheHillGameMode : public ADefaultGameMode +{ + GENERATED_BODY() + +public: + AKingOfTheHillGameMode(); + virtual void InitGame(const FString& MapName, const FString& Options, FString& ErrorMessage) override; + virtual void HandleMatchHasStarted() override; + virtual void Tick(float DeltaTime) override; + + void AddScore(int team, float duration); + void WinGame(int team); + + UPROPERTY(Config) + float maxGameScore; + + UPROPERTY(BlueprintReadOnly) + class AKOTHBossSpawner* bossSpawner; + +private: + int32 m_currentTeamWithHill; + friend class AKOTHGameState; +}; diff --git a/Source/UnrealProject/GameState/MapData.h b/Source/UnrealProject/GameState/MapData.h new file mode 100644 index 0000000..ed20ae6 --- /dev/null +++ b/Source/UnrealProject/GameState/MapData.h @@ -0,0 +1,26 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "Blueprint/UserWidget.h" +#include "MapData.generated.h" + +UCLASS(BlueprintType) +class UMapData : public UDataAsset +{ + GENERATED_BODY() + +public: + UMapData(); + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Level") + FString friendlyName; + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Level") + FString description; + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Level", + meta = (ClampMin = "2", ClampMax = "4", UIMin = "2", UIMax = "4")) + int32 maxTeamCount; + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Level") + UTexture2D* icon; + UPROPERTY(BlueprintReadOnly, Category = "Level") + FString pathToAsset; +}; \ No newline at end of file diff --git a/Source/UnrealProject/GameState/MapList.h b/Source/UnrealProject/GameState/MapList.h new file mode 100644 index 0000000..21388bb --- /dev/null +++ b/Source/UnrealProject/GameState/MapList.h @@ -0,0 +1,11 @@ +#pragma once +#include "MapList.generated.h" + +UCLASS(BlueprintType) +class UMapList : public UDataAsset +{ + GENERATED_BODY() +public: + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "MapList") + TArray mapList; +}; \ No newline at end of file diff --git a/Source/UnrealProject/GameState/MatchMaking.cpp b/Source/UnrealProject/GameState/MatchMaking.cpp new file mode 100644 index 0000000..6517983 --- /dev/null +++ b/Source/UnrealProject/GameState/MatchMaking.cpp @@ -0,0 +1,78 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "MatchMaking.h" +#include "Online.h" + +/* +const uint8* FUniqueNetIdMatchMaking::GetBytes() const +{ + return (uint8*)&UniqueNetId; +} +int32 FUniqueNetIdMatchMaking::GetSize() const +{ + return sizeof(uint64); +} + +#if PLATFORM_SPECIFIC_WIN == 0 +#pragma warning(push) +#pragma warning(disable:4996) +#include "steam/steam_api.h" +bool FUniqueNetIdMatchMaking::IsValid() const +{ + return UniqueNetId != 0 && CSteamID(UniqueNetId).IsValid(); +} +#pragma warning(pop) +#else +bool FUniqueNetIdMatchMaking::IsValid() const +{ + return UniqueNetId != 0; +} +#endif + +FString FUniqueNetIdMatchMaking::ToString() const +{ + return FString::Printf(TEXT("%llu"), UniqueNetId); +} +FString FUniqueNetIdMatchMaking::ToDebugString() const +{ + return ToString(); +} + + +inline uint64 GetNetId(FNamedOnlineSession* session) +{ + FOnlineSessionInfoMatchMaking* SessionInfo = (FOnlineSessionInfoMatchMaking*)(session->SessionInfo.Get()); + FInternetAddrMatchMaking* addr = (FInternetAddrMatchMaking*)SessionInfo->P2PAddr.Get(); + return addr->NetId.UniqueNetId; +} +inline uint32 GetChannelId(FNamedOnlineSession* session) +{ + FOnlineSessionInfoMatchMaking* SessionInfo = (FOnlineSessionInfoMatchMaking*)(session->SessionInfo.Get()); + FInternetAddrMatchMaking* addr = (FInternetAddrMatchMaking*)SessionInfo->P2PAddr.Get(); + return addr->ChannelId; +} + +MatchMaking::MatchMaking(FNamedOnlineSession* session) : IMatchMakingInterface(GetNetId(session), GetChannelId(session)) +{ + +} + +MatchMaking::~MatchMaking() +{ +} + + +void MatchMaking::OnConnect() +{ + JPRINT("Connected to matchmaking server"); +} +void MatchMaking::OnDisconnect() +{ + JERROR("Disconnected from matchmaking server"); +} +void MatchMaking::OnForceJoinSession(NetID netID, ChannelID channelID) +{ + +} +*/ \ No newline at end of file diff --git a/Source/UnrealProject/GameState/MatchMaking.h b/Source/UnrealProject/GameState/MatchMaking.h new file mode 100644 index 0000000..cdec7f1 --- /dev/null +++ b/Source/UnrealProject/GameState/MatchMaking.h @@ -0,0 +1,53 @@ +// Project Lab - NHTV Igad + +/* +#pragma once +#include "MatchMaking.hpp" +#include "Networking.h" + + +class FOnlineSessionInfoMatchMaking : public FOnlineSessionInfo +{ +public: + virtual ~FOnlineSessionInfoMatchMaking() {} + + enum PH {}; + PH SessionType; + TSharedPtr HostAddr; + TSharedPtr P2PAddr; +}; + +class FUniqueNetIdMatchMaking : public FUniqueNetId +{ +public: + ~FUniqueNetIdMatchMaking() {} + + uint64 UniqueNetId; + + virtual const uint8* GetBytes() const override; + virtual int32 GetSize() const override; + virtual bool IsValid() const override; + virtual FString ToString() const override; + virtual FString ToDebugString() const override; +}; + +class FInternetAddrMatchMaking : public FInternetAddr +{ +public: + virtual ~FInternetAddrMatchMaking() {} + + FUniqueNetIdMatchMaking NetId; + int32 ChannelId; +}; + +class UNREALPROJECT_API MatchMaking : public IMatchMakingInterface +{ +public: + MatchMaking(class FNamedOnlineSession* session); + ~MatchMaking(); + + virtual void OnConnect() override; + virtual void OnDisconnect() override; + virtual void OnForceJoinSession(NetID netID, ChannelID channelID) override; +}; +*/ \ No newline at end of file diff --git a/Source/UnrealProject/GameState/MenuController.cpp b/Source/UnrealProject/GameState/MenuController.cpp new file mode 100644 index 0000000..39925df --- /dev/null +++ b/Source/UnrealProject/GameState/MenuController.cpp @@ -0,0 +1,332 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "DefaultGameInstance.h" +#include "MenuController.h" +#include "SplashScreen.h" +#include "LobbyMenu.h" +#include "CharacterSettings.h" +#include "StartPromptScreen.h" +#include "PlayerStateBase.h" +#include "GameStateBase.h" +#include "SessionManager.h" +#include "TransitionScreen.h" +#include "ScreenOverlay.h" +#include "SkillTreeWidget.h" +#include "DefaultPlayerController.h" +#include "MenuScreen.h" +#include "MenuGameMode.h" + +static UClass* menuWidgetClass = nullptr; +static UClass* lobbyWidgetClass = nullptr; +static UClass* splashScreenClass = nullptr; +static UClass* startScreenClass = nullptr; +static UClass* skillTreeClass = nullptr; +static TSubclassOf mainSubMenuClass; + +AMenuController::AMenuController(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + menuWidgetClass = ConstructorHelpers::FClassFinder(TEXT("/Game/Assets/GUI/WEEGEE_Menu")).Class; + lobbyWidgetClass = ConstructorHelpers::FClassFinder(TEXT("/Game/Assets/GUI/WEEGEE_Lobby")).Class; + splashScreenClass = ConstructorHelpers::FClassFinder(TEXT("/Game/Assets/GUI/WEEGEE_Splash")).Class; + startScreenClass = ConstructorHelpers::FClassFinder(TEXT("/Game/Assets/GUI/WEEGEE_StartPrompt")).Class; + skillTreeClass = ConstructorHelpers::FClassFinder(TEXT("/Game/Assets/GUI/WEEGEE_SkillTree")).Class; + mainSubMenuClass = ConstructorHelpers::FClassFinder(TEXT("/Game/Assets/GUI/Menus/WEEGEE_MainSubMenu")).Class; + + this->bShowMouseCursor = true; + m_inLobby = false; + PrimaryActorTick.bCanEverTick = true; +} + +void AMenuController::BeginPlay() +{ + Super::BeginPlay(); + + if(IsLocalController()) + { + // Create skill tree widget + skillTree = CreateWidget(GetWorld(), skillTreeClass); + skillTree->AddToViewport(5); + + UDefaultGameInstance* inst = Cast(GetGameInstance()); + UWorld* world = GetWorld(); + bool showSplash = !inst->splashScreenShown; + if(world->WorldType == EWorldType::PIE) + showSplash = false; + if(inst->sessionManager->HasNetConnection()) + showSplash = false; + + if(showSplash) + { + m_splash = CreateWidget(this, splashScreenClass); + m_splash->onDone.AddDynamic(this, &AMenuController::m_OnSplashScreenEnded); + check(m_splash); + m_splash->AddToViewport(); + } + else + { + m_OnStartPromptClosed(); + } + + inst->splashScreenShown = true; + + inst->sessionManager->onAcceptInvite.AddUObject(this, &AMenuController::m_OnInviteAccepted); + } +} +void AMenuController::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + Super::EndPlay(EndPlayReason); + + UDefaultGameInstance* inst = Cast(GetGameInstance()); + inst->sessionManager->onAcceptInvite.RemoveAll(this); +} + +void AMenuController::NotifyLoadedWorld(FName WorldPackageName, bool bFinalDest) +{ + Super::NotifyLoadedWorld(WorldPackageName, bFinalDest); + if(bFinalDest) + { + GPRINT("Final level loaded for player controller " + GetName()); + if(transitionScreen) + { + // Hide transition screen + transitionScreen->OnHide(); + } + } +} + +void AMenuController::Destroyed() +{ + GPRINT("Menu controller destroyed " + GetName()); + Super::Destroyed(); +} + +void AMenuController::PreClientTravel(const FString& PendingURL, ETravelType TravelType, bool bIsSeamlessTravel) +{ + if(bIsSeamlessTravel) + { + // Hide lobby UI + if(lobbyMenu) + lobbyMenu->OnHide(); + + // Close all the menu + if(m_splash) + m_splash->RemoveFromViewport(); + if(m_startScreen) + m_startScreen->RemoveFromViewport(); + if(lobbyMenu) + lobbyMenu->RemoveFromViewport(); + if(mainMenu) + mainMenu->RemoveFromViewport(); + if(skillTree) + skillTree->RemoveFromViewport(); + mainMenu = nullptr; + lobbyMenu = nullptr; + skillTree = nullptr; + m_startScreen = nullptr; + m_splash = nullptr; + + // Show transition screen + transitionScreen->OnShow(); + + GPRINT("Seamless travel on player " + GetName() + " to \"" + PendingURL + "\""); + } + else + { + GPRINT("Regular travel on player to " + GetName() + " to \"" + PendingURL + "\""); + } + Super::PreClientTravel(PendingURL, TravelType, bIsSeamlessTravel); +} + +void AMenuController::SeamlessTravelFrom(class APlayerController* OldPC) +{ + Super::SeamlessTravelFrom(OldPC); + GPRINT("Seamless travel to menu [" + GetName() + "]<-[" + OldPC->GetName() + "]"); +} +void AMenuController::SeamlessTravelTo(class APlayerController* NewPC) +{ + Super::SeamlessTravelTo(NewPC); + GPRINT("Seamless travel to game level [" + GetName() + "]->[" + NewPC->GetName() + "]"); + APlayerStateBase* pcbn = Cast(NewPC->PlayerState); + APlayerStateBase* pcbo = GetPlayerState(); + + check(pcbn && pcbo); + pcbn->Transfer(pcbo); + + ADefaultPlayerController* newDPC = Cast(NewPC); + if(newDPC) + { + newDPC->setupState = setupState; + } +} + +void AMenuController::OnLearnSkill(class UBaseSkillObject* object) +{ +} +void AMenuController::OnUnlearnSkill(class UBaseSkillObject* object) +{ +} + +void AMenuController::OnEnterLobby() +{ + if(m_inLobby) + return; + + if(mainMenu && lobbyMenu) + { + mainMenu->OnHide(); + lobbyMenu->OnShow(); + lobbyMenu->OnLobbyEnter(); + m_inLobby = true; + OnRep_PlayerState(); + } + else + { + GERROR("wat"); + } + + // Set Default character slot + /// TODO: Add selector widget + //UCharacterSettings* settings = Cast(GetGameInstance())->GetCharacterSettings(); + //if(settings->characterSaves.Num() > 0) + //{ + // const FCharacterSave& save = settings->characterSaves[0]; + // FPlayerSetupState pss; + // pss.customizations = save.characterCustomization; + // pss.skills = save.skillTreeState; + // pss.characterClass = save.characterClass; + // SetSetupState(pss); + //} +} + +/// TODO, validate skill tree / customization here +bool AMenuController::SetSetupState_Validate(const FPlayerSetupState& state) +{ + return true; +} +void AMenuController::SetSetupState_Implementation(const FPlayerSetupState& state) +{ + setupState = state; + + // Resync the lobby + UWorld* const world = GetWorld(); + if (IsValid(world)) + { + AGameStateBase* const gameState = Cast(GetWorld()->GetGameState()); + if (IsValid(gameState)) + gameState->OnPlayerStateChange.Broadcast(this); + } +} + +void AMenuController::OnRep_PlayerState() +{ + // Request a team if we're in a lobby + if(m_inLobby) + { + m_AutoRequestTeam(); + } + + // Send to the server that we got our player state + m_OnClientInitialized(); +} + +void AMenuController::SwitchToMenu() +{ + mainMenu->OnShow(); + lobbyMenu->OnHide(); + m_inLobby = false; +} + +bool AMenuController::m_OnClientInitialized_Validate() +{ + return true; +} +void AMenuController::m_OnClientInitialized_Implementation() +{ + AMenuGameMode* gm = Cast(GetWorld()->GetAuthGameMode()); + if(gm) + { + gm->onPlayerJoined.Broadcast(this); + } +} + +void AMenuController::m_OnInviteAccepted(bool success, const FOnlineSessionSearchResult& result) +{ + if(success) + { + GPRINT("Accepted an invite"); + UDefaultGameInstance* inst = Cast(GetGameInstance()); + + inst->sessionManager->onJoinSessionComplete.AddUObject(this, &AMenuController::m_OnSessionJoined); + inst->sessionManager->JoinSession(result); + } +} +void AMenuController::m_OnSessionJoined(int32 result) +{ + UDefaultGameInstance* inst = Cast(GetGameInstance()); + inst->sessionManager->onJoinSessionComplete.RemoveAll(this); + + if(result == EOnJoinSessionCompleteResult::Success) + { + ClientTravel(inst->sessionManager->joinConnectionString, ETravelType::TRAVEL_Absolute); + } + else + { + TArray options; + options.Add("OK"); + overlay->ShowMessageBox("Error", FString("Failed to accept invite (") + FString::FromInt(result) + ")", options); + GERROR("Failed to join session invite"); + SwitchToMenu(); + } +} + +void AMenuController::m_AutoRequestTeam() +{ + UWorld* world = GetWorld(); + APlayerStateBase* state = GetPlayerState(); + if(state) + { + state->AutoAssignTeam(); + if(world->IsPlayInEditor()) + state->SetReadyState(true); + //GetWorld()->GetTimerManager().ClearTimer(m_teamRequestTimer); + } +} +void AMenuController::m_OnSplashScreenEnded() +{ + m_splash = nullptr; + + // Create start prompt + m_startScreen = CreateWidget(this, startScreenClass); + check(m_startScreen); + m_startScreen->onClosed.AddUObject(this, &AMenuController::m_OnStartPromptClosed); + m_startScreen->AddToViewport(); +} +void AMenuController::m_OnStartPromptClosed() +{ + m_startScreen = nullptr; + + // Open main menu + lobbyMenu = CreateWidget(this, lobbyWidgetClass); + lobbyMenu->AddToViewport(); + mainMenu = CreateWidget(this, menuWidgetClass); + mainMenu->AddToViewport(); + mainMenu->OnShow(); + mainMenu->OpenScreenMenu(mainSubMenuClass); + + // Enter lobby when initially connected + UWorld* world = GetWorld(); + UDefaultGameInstance* inst = Cast(world->GetGameInstance()); + if(inst->sessionManager->HasNetConnection()) + { + OnEnterLobby(); + } +} + +bool AMenuController::WorldIsPIE() const +{ + UWorld* const world = GetWorld(); + if (!IsValid(world)) return false; + return world->WorldType == EWorldType::PIE; +} \ No newline at end of file diff --git a/Source/UnrealProject/GameState/MenuController.h b/Source/UnrealProject/GameState/MenuController.h new file mode 100644 index 0000000..8af02d1 --- /dev/null +++ b/Source/UnrealProject/GameState/MenuController.h @@ -0,0 +1,71 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "GameFramework/PlayerController.h" +#include "PlayerControllerBase.h" +#include "MenuController.generated.h" + +UCLASS() +class UNREALPROJECT_API AMenuController : public APlayerControllerBase +{ + GENERATED_BODY() + +public: + AMenuController(const FObjectInitializer& ObjectInitializer); + + virtual void BeginPlay() override; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + virtual void NotifyLoadedWorld(FName WorldPackageName, bool bFinalDest) override; + virtual void Destroyed() override; + + virtual void PreClientTravel(const FString& PendingURL, ETravelType TravelType, bool bIsSeamlessTravel) override; + virtual void SeamlessTravelFrom(class APlayerController* OldPC) override; + virtual void SeamlessTravelTo(class APlayerController* NewPC) override; + + virtual void OnLearnSkill(class UBaseSkillObject* object); + virtual void OnUnlearnSkill(class UBaseSkillObject* object); + virtual void OnEnterLobby(); + + UFUNCTION(Server, WithValidation, Reliable) + virtual void SetSetupState(const FPlayerSetupState& state); + virtual void OnRep_PlayerState() override; + + void SwitchToMenu(); + + UPROPERTY(BlueprintReadOnly) + class UMenuScreen* mainMenu; + UPROPERTY(BlueprintReadOnly) + class ULobbyMenu* lobbyMenu; + UPROPERTY(BlueprintReadOnly) + class USkillTreeWidget* skillTree; + + UFUNCTION(BlueprintCallable, Category = "PIE") + bool WorldIsPIE() const; + +private: + UFUNCTION(Server, WithValidation, Reliable) + void m_OnClientInitialized(); + + void m_OnInviteAccepted(bool success, const FOnlineSessionSearchResult& result); + UFUNCTION() + void m_OnSessionJoined(int32 result); + + void m_AutoRequestTeam(); + FTimerHandle m_teamRequestTimer; + + // Called when the splash screens end + UFUNCTION() + void m_OnSplashScreenEnded(); + // Called when the start prompt closes + UFUNCTION() + void m_OnStartPromptClosed(); + + UPROPERTY() + class USplashScreen* m_splash; + UPROPERTY() + class UStartPromptScreen* m_startScreen; + + // True if we are currently in the lobby screen, this can be checked if it is set before the menu widgets are created + bool m_inLobby; +}; diff --git a/Source/UnrealProject/GameState/MenuGameMode.cpp b/Source/UnrealProject/GameState/MenuGameMode.cpp new file mode 100644 index 0000000..a716b34 --- /dev/null +++ b/Source/UnrealProject/GameState/MenuGameMode.cpp @@ -0,0 +1,160 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "MenuGameMode.h" +#include "MenuController.h" +#include "DefaultGameInstance.h" +#include "MapData.h" +#include "GameStateBase.h" +#include "PlayerStateBase.h" +#include "PlayerControllerBase.h" + +AMenuGameMode::AMenuGameMode() +{ + static ConstructorHelpers::FClassFinder MenuPawnCF(TEXT("/Game/Assets/Blueprints/BP_MenuPawn")); + + PlayerControllerClass = AMenuController::StaticClass(); + DefaultPawnClass = MenuPawnCF.Class; + GameStateClass = AGameStateBase::StaticClass(); + PlayerStateClass = APlayerStateBase::StaticClass(); + + bUseSeamlessTravel = true; +} +void AMenuGameMode::PreInitializeComponents() +{ + Super::PreInitializeComponents(); + GWPRINT(L"Menu arguments = " + OptionsString); + + AGameStateBase* gameState = GetGameState(); + check(gameState); + + // Set initial map + UWorld* world = GetWorld(); + if(world) + { + UDefaultGameInstance* inst = Cast(world->GetGameInstance()); + if(inst->maps.Num() > 0) + { + m_mapData = inst->maps[0]; + m_mapPath = gameState->mapPath = m_mapData->pathToAsset; + GPRINT("Setting default map to \"" + m_mapData->friendlyName + "\" [" + m_mapPath + "]"); + gameState->m_LoadActiveMapInfo(); + } + } +} +void AMenuGameMode::BeginPlay() +{ + Super::BeginPlay(); + UWorld* world = GetWorld(); + m_lobbyState = world->SpawnActor(ALobbyState::StaticClass()); +} + +void AMenuGameMode::GetSeamlessTravelActorList(bool bToEntry, TArray& ActorList) +{ + Super::GetSeamlessTravelActorList(bToEntry, ActorList); + ActorList.Add(m_lobbyState); +} + +APlayerController* AMenuGameMode::Login(class UPlayer* NewPlayer, ENetRole InRemoteRole, const FString& Portal, const FString& Options, const TSharedPtr& UniqueId, FString& ErrorMessage) +{ + // Aquire the player controller by calling the super function + APlayerController* player = Super::Login(NewPlayer, InRemoteRole, Portal, Options, UniqueId, ErrorMessage); + APlayerControllerBase* pc = Cast(player); + + // Update persona + APlayerStateBase* state = Cast(pc->PlayerState); + FString name = ""; + if(state) + { + state->UpdatePersona(); + name = state->nickname; + } + + GWARNING("Player " + name + " logged in [options=" + Options + "][portal=" + Portal + "]"); + return player; +} +FString AMenuGameMode::InitNewPlayer(class APlayerController* NewPlayerController, const TSharedPtr& UniqueId, const FString& Options, const FString& Portal /* = TEXT("") */) +{ + FString r = Super::InitNewPlayer(NewPlayerController, UniqueId, Options, Portal); + return r; +} + +void AMenuGameMode::Logout(AController* existing) +{ + // Get name of disconnected player + APlayerStateBase* state = Cast(existing->PlayerState); + FString name = ""; + if(state) + name = state->nickname; + GWARNING("Player " + name + " left the game"); + + // Deregister the player + APlayerControllerBase* pc = Cast(existing); + Cast(GameState)->DeregisterPlayer(pc); + + Super::Logout(existing); +} + +void AMenuGameMode::PostLogin(APlayerController* NewPlayer) +{ + Super::PostLogin(NewPlayer); + AGameStateBase* gs = GetGameState(); + APlayerControllerBase* pc = Cast(NewPlayer); + + // Register the player + Cast(GameState)->RegisterPlayer(pc); +} + +void AMenuGameMode::SetMap(FString mapPath) +{ + m_mapPath = mapPath; + Cast(GameState)->mapPath = m_mapPath; +} +bool AMenuGameMode::StartGame() +{ + UWorld* world = GetWorld(); + if(!world) + { + GERROR("No world, cant start game"); + return false; + } + + UDefaultGameInstance* inst = Cast(world->GetGameInstance()); + m_mapData = inst->GetMapData(m_mapPath); + if(!m_mapData) + { + GERROR("Can't start game, no map data found for map [" + m_mapPath + "]"); + return false; + } + + // Inject all the players into the lobby state + AGameStateBase* state = Cast(GameState); + m_lobbyState->players.SetNum(0); + for(APlayerState* ps : state->PlayerArray) + { + APlayerStateBase* psb = Cast(ps); + if(psb) + { + FLobbyStatePlayer lsp; + lsp.netID = psb->UniqueId.GetUniqueNetId(); + lsp.team = psb->GetTeam(); + m_lobbyState->players.Add(lsp); + } + } + + GWARNING("Performing server travel to " + m_mapPath); + world->ServerTravel(m_mapPath, true); + return true; +} + +void AMenuGameMode::AssignMapData(class UMapData* mapData) +{ + if(!mapData) + { + GERROR("Invalid map passed to AssignMapData"); + return; + } + AGameStateBase* gameState = GetGameState(); + gameState->mapPath = m_mapPath = mapData->pathToAsset; + gameState->m_LoadActiveMapInfo(); +} diff --git a/Source/UnrealProject/GameState/MenuGameMode.h b/Source/UnrealProject/GameState/MenuGameMode.h new file mode 100644 index 0000000..338ccdd --- /dev/null +++ b/Source/UnrealProject/GameState/MenuGameMode.h @@ -0,0 +1,42 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "GameFramework/GameMode.h" +#include "MenuGameMode.generated.h" + +class AGameNameCharacter; +class AGameNamePlayerController; +class AGameNameSpectator; + +UCLASS(minimalapi) +class AMenuGameMode : public AGameMode +{ + GENERATED_BODY() + friend class AGameStateBase; + friend class APlayerStateBase; +public: + AMenuGameMode(); + virtual void PreInitializeComponents() override; + virtual void BeginPlay() override; + virtual void GetSeamlessTravelActorList(bool bToEntry, TArray& ActorList) override; + + virtual APlayerController* Login(class UPlayer* NewPlayer, ENetRole InRemoteRole, const FString& Portal, const FString& Options, const TSharedPtr& UniqueId, FString& ErrorMessage) override; + virtual FString InitNewPlayer(class APlayerController* NewPlayerController, const TSharedPtr& UniqueId, const FString& Options, const FString& Portal /* = TEXT("") */) override; + virtual void Logout(AController* existing) override; + virtual void PostLogin(APlayerController* NewPlayer) override; + + // Player joined callback + DECLARE_MULTICAST_DELEGATE_OneParam(FOnPlayerJoined, APlayerController*) + FOnPlayerJoined onPlayerJoined; + + void SetMap(FString mapPath); + bool StartGame(); + void AssignMapData(class UMapData* mapData); +private: + UPROPERTY() + class ALobbyState* m_lobbyState; + + FString m_mapPath; + class UMapData* m_mapData; +}; \ No newline at end of file diff --git a/Source/UnrealProject/GameState/MiniMapVolume.cpp b/Source/UnrealProject/GameState/MiniMapVolume.cpp new file mode 100644 index 0000000..31816ed --- /dev/null +++ b/Source/UnrealProject/GameState/MiniMapVolume.cpp @@ -0,0 +1,14 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "MiniMapVolume.h" + + +AMiniMapVolume::AMiniMapVolume() +{ + area = CreateDefaultSubobject(TEXT("Area")); + area->SetCollisionProfileName(UCollisionProfile::NoCollision_ProfileName); + area->bGenerateOverlapEvents = false; + area->Mobility = EComponentMobility::Static; + RootComponent = area; +} \ No newline at end of file diff --git a/Source/UnrealProject/GameState/MiniMapVolume.h b/Source/UnrealProject/GameState/MiniMapVolume.h new file mode 100644 index 0000000..2aff272 --- /dev/null +++ b/Source/UnrealProject/GameState/MiniMapVolume.h @@ -0,0 +1,18 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "GameFramework/Actor.h" +#include "MiniMapVolume.generated.h" + +UCLASS() +class UNREALPROJECT_API AMiniMapVolume : public AActor +{ + GENERATED_BODY() + +public: + AMiniMapVolume(); + + UPROPERTY(EditAnywhere, Category = "Area") + class UBoxComponent* area; +}; diff --git a/Source/UnrealProject/GameState/PlayerControllerBase.cpp b/Source/UnrealProject/GameState/PlayerControllerBase.cpp new file mode 100644 index 0000000..fb7e39f --- /dev/null +++ b/Source/UnrealProject/GameState/PlayerControllerBase.cpp @@ -0,0 +1,397 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "DefaultGameInstance.h" +#include "PlayerControllerBase.h" +#include "ScreenOverlay.h" +#include "Prefs.h" +#include "TransitionScreen.h" +#include "SessionManager.h" +#include "Engine.h" + +#if PLATFORM_SPECIFIC_WIN == 0 +#include "InputManager.hpp" +using namespace Input; +#endif + +UClass* overlayWidgetClass; +UClass* transitionWidgetClass; +APlayerControllerBase::APlayerControllerBase(const FObjectInitializer& init) + : Super(init) +{ + overlayWidgetClass = ConstructorHelpers::FClassFinder(TEXT("/Game/Assets/GUI/WEEGEE_Overlay")).Class; + transitionWidgetClass = ConstructorHelpers::FClassFinder(TEXT("/Game/Assets/GUI/WEEGEE_Transition")).Class; +} + +void APlayerControllerBase::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + DOREPLIFETIME(APlayerControllerBase, setupState); +} + +void APlayerControllerBase::BeginPlay() +{ + UDefaultGameInstance* game = Cast(GetGameInstance()); + + if(IsLocalController()) + { + transitionScreen = CreateWidget(GetWorld(), transitionWidgetClass); + transitionScreen->AddToViewport(800); + + overlay = CreateWidget(GetWorld(), overlayWidgetClass); + overlay->AddToViewport(1000); + AddMenuInputItem(overlay, 10); + check(overlay); + + UWorld* world = GetWorld(); + AGameMode* gameMode = nullptr; + if(world) + gameMode = GetWorld()->GetAuthGameMode(); + //if(!game->errorMessage.IsEmpty()) + //{ + // game->DestroySession(false); + // game->ShowNetworkError(FString("Disconnected from game, ") + game->errorMessage); + // game->errorMessage.Empty(); + //} + //else if(gameMode && UGameplayStatics::HasOption(gameMode->OptionsString, "closed")) + //{ + // game->DestroySession(false); + // game->ShowNetworkError(FString("Disconnected from game")); + //} + } + + Super::BeginPlay(); +} +void APlayerControllerBase::SetupInputComponent() +{ + if (GetWorld()->WorldType == EWorldType::Preview) + return; + + Super::SetupInputComponent(); + + +#if PLATFORM_SPECIFIC_WIN == 0 + GPRINT("Binding Menu Input manager bindings on " + GetName()); + InputManager* inputManager = InputManager::GetInstance(); + + UPrefs* prefs = Cast(GetGameInstance())->GetPrefs(); + + // Connect initial controller + inputManager->SearchForJoystick(); + ScanForJoysticks(); + int32 connect = 0; + for(int32 i = 0; i < m_recentJoystickScan.Num(); i++) + { + if(m_recentJoystickScan[i] == prefs->useControllerName) + { + connect = i; + break; + } + } + SelectJoystick(connect); + + inputManager->RegisterCallback(IJP_FACEDOWN, IME_PRESSED, this, &APlayerControllerBase::m_OnMenuConfirm); + inputManager->RegisterCallback(IJP_FACERIGHT,IME_PRESSED, this, &APlayerControllerBase::m_OnMenuBack); + inputManager->RegisterCallback(IJP_START, IME_PRESSED, this, &APlayerControllerBase::m_OnMenuStart); + inputManager->RegisterCallback(IJP_DPADUP, IME_PRESSED, this, &APlayerControllerBase::m_OnMenuUp); + inputManager->RegisterCallback(IJP_DPADDOWN, IME_PRESSED, this, &APlayerControllerBase::m_OnMenuDown); + inputManager->RegisterCallback(IJP_DPADLEFT, IME_PRESSED, this, &APlayerControllerBase::m_OnMenuLeft); + inputManager->RegisterCallback(IJP_DPADRIGHT, IME_PRESSED, this, &APlayerControllerBase::m_OnMenuRight); + inputManager->RegisterCallback(IJP_TRIGGERLEFT, IME_PRESSED, this, &APlayerControllerBase::m_OnMenuLeftPressed); + inputManager->RegisterCallback(IJP_TRIGGERRIGHT, IME_PRESSED, this, &APlayerControllerBase::m_OnMenuRightPressed); + inputManager->RegisterCallback(IJP_TRIGGERLEFT, IME_RELEASED, this, &APlayerControllerBase::m_OnMenuLeftReleased); + inputManager->RegisterCallback(IJP_TRIGGERRIGHT, IME_RELEASED, this, &APlayerControllerBase::m_OnMenuRightReleased); + inputManager->RegisterCallback(IJP_SHOULDERLEFT, IME_PRESSED, this, &APlayerControllerBase::m_OnMenuShoulderLeft); + inputManager->RegisterCallback(IJP_SHOULDERRIGHT, IME_PRESSED, this, &APlayerControllerBase::m_OnMenuShoulderRight); + inputManager->RegisterCallback(IJP_START, IME_PRESSED, this, &APlayerControllerBase::m_OnMenuOptionsButton); + inputManager->RegisterCallback(IJP_FACELEFT, IME_PRESSED, this, &APlayerControllerBase::m_OnMenuOpt1); + inputManager->RegisterCallback(IJP_FACEUP, IME_PRESSED, this, &APlayerControllerBase::m_OnMenuOpt2); +#endif + + InputComponent->BindAction("MenuSelect", EInputEvent::IE_Pressed, this, &APlayerControllerBase::m_OnMenuConfirm); + InputComponent->BindAction("MenuBack", EInputEvent::IE_Pressed, this, &APlayerControllerBase::m_OnMenuBack); + InputComponent->BindAction("MenuStart", EInputEvent::IE_Pressed, this, &APlayerControllerBase::m_OnMenuStart); + InputComponent->BindAction("MenuUp", EInputEvent::IE_Pressed, this, &APlayerControllerBase::m_OnMenuUp); + InputComponent->BindAction("MenuDown", EInputEvent::IE_Pressed, this, &APlayerControllerBase::m_OnMenuDown); + InputComponent->BindAction("MenuLeft", EInputEvent::IE_Pressed, this, &APlayerControllerBase::m_OnMenuLeftPressed); + InputComponent->BindAction("MenuRight", EInputEvent::IE_Pressed, this, &APlayerControllerBase::m_OnMenuRightPressed); + InputComponent->BindAction("MenuLeftUp", EInputEvent::IE_Released, this, &APlayerControllerBase::m_OnMenuLeftReleased); + InputComponent->BindAction("MenuRightUp", EInputEvent::IE_Released, this, &APlayerControllerBase::m_OnMenuRightReleased); + InputComponent->BindAction("MenuOpt1", EInputEvent::IE_Pressed, this, &APlayerControllerBase::m_OnMenuOpt1); + InputComponent->BindAction("MenuOpt2", EInputEvent::IE_Pressed, this, &APlayerControllerBase::m_OnMenuOpt2); + InputComponent->BindAction("MenuShoulderLeft", EInputEvent::IE_Pressed, this, &APlayerControllerBase::m_OnMenuShoulderLeft); + InputComponent->BindAction("MenuShoulderRight", EInputEvent::IE_Pressed, this, &APlayerControllerBase::m_OnMenuShoulderRight); +} +void APlayerControllerBase::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + if(IsLocalController()) + { + if(transitionScreen) + { + transitionScreen->OnHide(); + transitionScreen->RemoveFromViewport(); + } + if(overlay) + { + overlay->RemoveFromViewport(); + } + } + Super::EndPlay(EndPlayReason); +} + +void APlayerControllerBase::PlayerTick(float DeltaTime) +{ + Super::PlayerTick(DeltaTime); + if(m_repeatLeft) + OnMenuAction(EMenuActionBinding::Repeat_Left); + if(m_repeatRight) + OnMenuAction(EMenuActionBinding::Repeat_Right); +} + +void APlayerControllerBase::Destroyed() +{ + Super::Destroyed(); +#if PLATFORM_SPECIFIC_WIN == 0 + GPRINT("Unbinding Menu Input manager bindings on " + GetName()); + InputManager* inputManager = InputManager::GetInstance(); + + inputManager->DeregisterCallback(IJP_FACEDOWN, IME_PRESSED, this, &APlayerControllerBase::m_OnMenuConfirm); + inputManager->DeregisterCallback(IJP_FACERIGHT, IME_PRESSED, this, &APlayerControllerBase::m_OnMenuBack); + inputManager->DeregisterCallback(IJP_START, IME_PRESSED, this, &APlayerControllerBase::m_OnMenuStart); + inputManager->DeregisterCallback(IJP_DPADUP, IME_PRESSED, this, &APlayerControllerBase::m_OnMenuUp); + inputManager->DeregisterCallback(IJP_DPADDOWN, IME_PRESSED, this, &APlayerControllerBase::m_OnMenuDown); + inputManager->DeregisterCallback(IJP_DPADLEFT, IME_PRESSED, this, &APlayerControllerBase::m_OnMenuLeft); + inputManager->DeregisterCallback(IJP_DPADRIGHT, IME_PRESSED, this, &APlayerControllerBase::m_OnMenuRight); + inputManager->DeregisterCallback(IJP_TRIGGERLEFT, IME_PRESSED, this, &APlayerControllerBase::m_OnMenuLeftPressed); + inputManager->DeregisterCallback(IJP_TRIGGERRIGHT, IME_PRESSED, this, &APlayerControllerBase::m_OnMenuRightPressed); + inputManager->DeregisterCallback(IJP_TRIGGERLEFT, IME_RELEASED, this, &APlayerControllerBase::m_OnMenuLeftReleased); + inputManager->DeregisterCallback(IJP_TRIGGERRIGHT, IME_RELEASED, this, &APlayerControllerBase::m_OnMenuRightReleased); + inputManager->DeregisterCallback(IJP_SHOULDERLEFT, IME_PRESSED, this, &APlayerControllerBase::m_OnMenuShoulderLeft); + inputManager->DeregisterCallback(IJP_SHOULDERRIGHT, IME_PRESSED, this, &APlayerControllerBase::m_OnMenuShoulderRight); + inputManager->DeregisterCallback(IJP_START, IME_PRESSED, this, &APlayerControllerBase::m_OnMenuOptionsButton); + inputManager->DeregisterCallback(IJP_FACELEFT, IME_PRESSED, this, &APlayerControllerBase::m_OnMenuOpt1); + inputManager->DeregisterCallback(IJP_FACEUP, IME_PRESSED, this, &APlayerControllerBase::m_OnMenuOpt2); +#endif +} + +void APlayerControllerBase::OnLearnSkill(class UBaseSkillObject* object) +{ +} +void APlayerControllerBase::OnUnlearnSkill(class UBaseSkillObject* object) +{ +} + +void APlayerControllerBase::AddMenuInputItem(class UMenuItemBase* item, int32 priority) +{ + if(!item) + return; + if(m_menuItemStack.Contains(item)) + return; + item->priority = priority; + + int32 at = m_menuItemStack.Num(); + if(at == -1) + { + m_menuItemStack.Add(item); + return; + } + + while(at > 0 && m_menuItemStack[at-1]->priority > priority) + { + at--; + } + m_menuItemStack.Insert(item, at); +} +void APlayerControllerBase::RemoveMenuInputItem(class UMenuItemBase* item) +{ + m_menuItemStack.Remove(item); +} + +EInputMethod APlayerControllerBase::GetInputMethod() const +{ +#if PLATFORM_SPECIFIC_WIN == 0 + InputManager* inputManager = InputManager::GetInstance(); + switch(inputManager->GetConnectedType()) + { + default: + case InputDeviceType::IDT_NONE: + case InputDeviceType::IDT_NOTDEFINED: + return EInputMethod::PC; + break; + case InputDeviceType::IDT_XBOX: + return EInputMethod::X360; + break; + case InputDeviceType::IDT_DS4: + return EInputMethod::DS4; + break; + } +#else + return EInputMethod::DS4; +#endif +} + +TArray APlayerControllerBase::ScanForJoysticks() +{ +#if PLATFORM_SPECIFIC_WIN == 0 + m_recentJoystickScan.SetNum(0); + InputManager* inputManager = InputManager::GetInstance(); + inputManager->SearchForJoystick(); + for(std::wstring s : inputManager->GetJoystickNames()) + { + m_recentJoystickScan.Add(FString(s.c_str())); + } +#endif + return m_recentJoystickScan; +} +void APlayerControllerBase::SelectJoystick(int32 joystick) +{ +#if PLATFORM_SPECIFIC_WIN == 0 + if(joystick >= m_recentJoystickScan.Num()) + return; + + InputManager* inputManager = InputManager::GetInstance(); + GWPRINT(L"Connecting joystick [" + joystick + L"] " + m_recentJoystickScan[joystick]); + inputManager->ConnectJoystickByIndex(joystick); +#endif +} + +void APlayerControllerBase::PreClientTravel(const FString& PendingURL, ETravelType TravelType, bool bIsSeamlessTravel) +{ + GPRINT("PRE TRAVEL: " + PendingURL); + Super::PreClientTravel(PendingURL, TravelType, bIsSeamlessTravel); +} + +void APlayerControllerBase::OnEnterLobby() +{ + GWARNING("PC: Entering lobby " + GetName()); +} +void APlayerControllerBase::ReturnToMenu() +{ + GWARNING("PC: Leaving game " + GetName()); + UDefaultGameInstance* inst = Cast(GetGameInstance()); + inst->sessionManager->DestroySession(); + inst->sessionManager->CloseNetConnections(); + + // Load menu level + //FString error; + //FWorldContext* worldContext = GEngine->GetWorldContextFromWorld(GetWorld()); + //FURL target = FURL(*inst->menuLevelPath); + //int32 ret = GEngine->Browse(*worldContext, target, error); + //if(ret == EBrowseReturnVal::Failure) + //{ + // GERROR("Failed to load menu level: " + error); + //} + GetWorld()->ServerTravel(inst->menuLevelPath, true); +} + +void APlayerControllerBase::OnRep_PlayerState() +{ + Super::OnRep_PlayerState(); + GPRINT("[" + GetName() + "]Got player state"); +} +void APlayerControllerBase::OnRep_Pawn() +{ + Super::OnRep_Pawn(); + if(GetPawn()) + { + GPRINT("[" + GetName() + "]Got pawn: " + GetPawn()->GetName()); + } + else + { + GPRINT("[" + GetName() + "]Unpossessed pawn"); + } +} + +void APlayerControllerBase::EnterLobby_Implementation() +{ + OnEnterLobby(); +} + +void APlayerControllerBase::OnRep_Setup() +{ + GPRINT("GOT SETUP STATE ON " + GetName()); + m_onSetupStateSet = true; +} +void APlayerControllerBase::m_OnMenuConfirm() +{ + OnMenuAction(EMenuActionBinding::Confirm); +} +void APlayerControllerBase::m_OnMenuBack() +{ + OnMenuAction(EMenuActionBinding::Back); +} +void APlayerControllerBase::m_OnMenuStart() +{ + OnMenuAction(EMenuActionBinding::Start); +} +void APlayerControllerBase::m_OnMenuUp() +{ + OnMenuAction(EMenuActionBinding::Up); +} +void APlayerControllerBase::m_OnMenuDown() +{ + OnMenuAction(EMenuActionBinding::Down); +} +void APlayerControllerBase::m_OnMenuLeft() +{ + OnMenuAction(EMenuActionBinding::Left); +} +void APlayerControllerBase::m_OnMenuRight() +{ + OnMenuAction(EMenuActionBinding::Right); +} +void APlayerControllerBase::m_OnMenuOpt1() +{ + OnMenuAction(EMenuActionBinding::Opt1); +} +void APlayerControllerBase::m_OnMenuOpt2() +{ + OnMenuAction(EMenuActionBinding::Opt2); +} +void APlayerControllerBase::m_OnMenuLeftPressed() +{ + m_repeatLeft = true; + m_OnMenuLeft(); + OnMenuAction(EMenuActionBinding::Repeat_Left); + OnMenuAction(EMenuActionBinding::Trigger_Left); +} +void APlayerControllerBase::m_OnMenuRightPressed() +{ + m_repeatRight = true; + m_OnMenuRight(); + OnMenuAction(EMenuActionBinding::Repeat_Right); + OnMenuAction(EMenuActionBinding::Trigger_Right); +} +void APlayerControllerBase::m_OnMenuLeftReleased() +{ + m_repeatLeft = false; +} +void APlayerControllerBase::m_OnMenuRightReleased() +{ + m_repeatRight = false; +} +void APlayerControllerBase::m_OnMenuShoulderLeft() +{ + OnMenuAction(EMenuActionBinding::Shoulder_Left); +} +void APlayerControllerBase::m_OnMenuShoulderRight() +{ + OnMenuAction(EMenuActionBinding::Shoulder_Right); +} +void APlayerControllerBase::m_OnMenuOptionsButton() +{ + OnMenuAction(EMenuActionBinding::Options); +} + + +void APlayerControllerBase::OnMenuAction(EMenuActionBinding action) +{ + m_menuItemStack.Remove(nullptr); + for(int32 i = m_menuItemStack.Num(); i > 0;) + { + i--; + m_menuItemStack[i]->NativeOnMenuAction(action); + if(m_menuItemStack[i]->blockInput) + break; + } +} diff --git a/Source/UnrealProject/GameState/PlayerControllerBase.h b/Source/UnrealProject/GameState/PlayerControllerBase.h new file mode 100644 index 0000000..00cf41f --- /dev/null +++ b/Source/UnrealProject/GameState/PlayerControllerBase.h @@ -0,0 +1,122 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "MenuAction.h" +#include "GameFramework/PlayerController.h" +#include "PlayerSetupState.h" +#include "PlayerControllerBase.generated.h" + +// Input method for local players +UENUM(BlueprintType) +enum class EInputMethod : uint8 +{ + PC, + X360, + DS4 +}; + +/** + * + */ +UCLASS() +class UNREALPROJECT_API APlayerControllerBase : public APlayerController +{ + GENERATED_BODY() + +public: + APlayerControllerBase(const FObjectInitializer& init); + + virtual void BeginPlay() override; + virtual void SetupInputComponent() override; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + virtual void PlayerTick(float DeltaTime) override; + virtual void Destroyed() override; + + virtual void OnLearnSkill(class UBaseSkillObject* object); + virtual void OnUnlearnSkill(class UBaseSkillObject* object); + + UFUNCTION(BlueprintCallable, Category = "MenuStack") + void AddMenuInputItem(class UMenuItemBase* item, int32 priority = 0); + UFUNCTION(BlueprintCallable, Category = "MenuStack") + void RemoveMenuInputItem(class UMenuItemBase* item); + + UFUNCTION(BlueprintCallable, Category = "Input") + EInputMethod GetInputMethod() const; + UFUNCTION(BlueprintCallable, Category = "Input") + TArray ScanForJoysticks(); + UFUNCTION(BlueprintCallable, Category = "Input") + void SelectJoystick(int32 joystick); + + virtual void PreClientTravel(const FString& PendingURL, ETravelType TravelType, bool bIsSeamlessTravel); + + // Called from the game when we should move to the lobby + UFUNCTION(Client, Reliable) + void EnterLobby(); + virtual void OnEnterLobby(); + + // Call to return to the main menu / leave current game + UFUNCTION(BlueprintCallable, Category = "Game") + void ReturnToMenu(); + + // Replication callbacks + virtual void OnRep_PlayerState() override; + virtual void OnRep_Pawn() override; + + // Template state acquisition functions + template T* GetPlayerState() + { + return Cast(PlayerState); + } + template T* GetGameState() + { + if(!GetWorld()) + return nullptr; + return Cast(GetWorld()->GameState); + } + + // Called when the setupState is replicated + UFUNCTION() + virtual void OnRep_Setup(); + + // Lobby Character selection + UPROPERTY(replicatedUsing=OnRep_Setup) + FPlayerSetupState setupState; + + UPROPERTY(BlueprintReadOnly, Category = "Overlay") + class UScreenOverlay* overlay; + + UPROPERTY(BlueprintReadOnly, Category = "Overlay") + class UTransitionScreen* transitionScreen; + +protected: + virtual void OnMenuAction(EMenuActionBinding action); +private: + + void m_OnMenuConfirm(); + void m_OnMenuBack(); + void m_OnMenuStart(); + void m_OnMenuUp(); + void m_OnMenuDown(); + void m_OnMenuLeft(); + void m_OnMenuRight(); + void m_OnMenuOpt1(); + void m_OnMenuOpt2(); + void m_OnMenuLeftPressed(); + void m_OnMenuRightPressed(); + void m_OnMenuLeftReleased(); + void m_OnMenuRightReleased(); + void m_OnMenuShoulderLeft(); + void m_OnMenuShoulderRight(); + void m_OnMenuOptionsButton(); + + bool m_onSetupStateSet; + + bool m_repeatLeft; + bool m_repeatRight; + + TArray m_recentJoystickScan; + + UPROPERTY() + TArray m_menuItemStack; +}; diff --git a/Source/UnrealProject/GameState/PlayerSetupState.h b/Source/UnrealProject/GameState/PlayerSetupState.h new file mode 100644 index 0000000..7b4063e --- /dev/null +++ b/Source/UnrealProject/GameState/PlayerSetupState.h @@ -0,0 +1,79 @@ +#pragma once +#include "SkillTreeState.h" +#include "PlayerSetupState.generated.h" + +// Properties belonging to a character class +USTRUCT() +struct FCharacterClassProperty +{ + GENERATED_BODY() +public: + UPROPERTY(BlueprintReadOnly, EditAnywhere) + FString name; + UPROPERTY(BlueprintReadOnly, EditAnywhere) + class UAbilityInfo* basicAttack; + UPROPERTY(BlueprintReadOnly, EditAnywhere) + TArray> classItems; + + // Class Specific Curves, if these are not set the character defaults are used + UPROPERTY(BlueprintReadOnly, EditAnywhere) + UCurveFloat* healthCurve; + UPROPERTY(BlueprintReadOnly, EditAnywhere) + UCurveFloat* manaCurve; + UPROPERTY(BlueprintReadOnly, EditAnywhere) + UCurveFloat* armorCurve; + UPROPERTY(BlueprintReadOnly, EditAnywhere) + UCurveFloat* attackSpeedCurve; +}; + +// A data asset that provides a list of character classes +UCLASS() +class UCharacterClassPropertySet : public UDataAsset +{ + GENERATED_BODY() +public: + UPROPERTY(BlueprintReadOnly, EditAnywhere) + TArray classes; + + UFUNCTION(BlueprintCallable, Category = "Character Class") + FCharacterClassProperty GetCharacterClass(int32 classID) const; +}; + +// Setings for character bone scales +USTRUCT(BlueprintType) +struct FCharacterCustomization +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintReadWrite, Category = "Save") + float overallScale = 1.0f; + UPROPERTY(BlueprintReadWrite, Category = "Save") + float headScale = 1.0f; + UPROPERTY(BlueprintReadWrite, Category = "Save") + float torsoScale = 1.0f; + UPROPERTY(BlueprintReadWrite, Category = "Save") + float leftArmScale = 1.0f; + UPROPERTY(BlueprintReadWrite, Category = "Save") + float rightArmScale = 1.0f; + UPROPERTY(BlueprintReadWrite, Category = "Save") + float legThicknessYScale = 1.0f; + UPROPERTY(BlueprintReadWrite, Category = "Save") + float legThicknessZScale = 1.0f; + +}; + +// All player settings, from the lobby +// this includes selected skill set and character customizations +USTRUCT() +struct FPlayerSetupState +{ + GENERATED_BODY() +public: + UPROPERTY() + FSkillTreeState skills; + UPROPERTY() + FCharacterCustomization customizations; + UPROPERTY() + int32 characterClass; +}; \ No newline at end of file diff --git a/Source/UnrealProject/GameState/PlayerStateBase.cpp b/Source/UnrealProject/GameState/PlayerStateBase.cpp new file mode 100644 index 0000000..e1b12ea --- /dev/null +++ b/Source/UnrealProject/GameState/PlayerStateBase.cpp @@ -0,0 +1,110 @@ +#include "UnrealProject.h" +#include "PlayerStateBase.h" + +#include "GameStateBase.h" +#include "DefaultGameInstance.h" +#include "SessionManager.h" + +APlayerStateBase::APlayerStateBase() +{ + bReplicates = true; + bAlwaysRelevant = true; +} +void APlayerStateBase::BeginPlay() +{ + Super::BeginPlay(); +} + +void APlayerStateBase::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + DOREPLIFETIME(APlayerStateBase, m_team); + DOREPLIFETIME(APlayerStateBase, m_readyToStart); +} + +bool APlayerStateBase::SetReadyState_Validate(bool state) +{ + return true; +} +void APlayerStateBase::SetReadyState_Implementation(bool state) +{ + m_readyToStart = state; +} +bool APlayerStateBase::GetReadyState() const +{ + return m_readyToStart; +} + +void APlayerStateBase::Transfer(APlayerStateBase* oldState) +{ + m_team = oldState->m_team; + UpdatePersona(); +} + +int32 APlayerStateBase::GetTeam() const +{ + return m_team; +} +bool APlayerStateBase::AutoAssignTeam_Validate() +{ + return true; +} +void APlayerStateBase::AutoAssignTeam_Implementation() +{ + if(m_team > 0) + return; // Already has a team + + AGameStateBase* const state = Cast(GetWorld()->GetGameState()); + check(state); + RequestTeamEntry(state->GetOptimalAutoJoinTeam()); +} +void APlayerStateBase::UpdatePersona() +{ + UDefaultGameInstance* inst = Cast(GetGameInstance()); + if (inst && UniqueId.IsValid()) + { + avatar = inst->sessionManager->GetPlayerAvatar(*UniqueId); + nickname = inst->sessionManager->GetPlayerName(*UniqueId); + } +} + +bool APlayerStateBase::RequestTeamEntry_Validate(uint8 team) +{ + UWorld* const world = GetWorld(); + check(world); + AGameStateBase* const state = Cast(world->GetGameState()); + check(state); + + return (team > 0 && team <= state->GetMapTeamCount()); +} +void APlayerStateBase::RequestTeamEntry_Implementation(uint8 team) +{ + if(team == m_team) + return; + + if(m_readyToStart && !(m_team == 0)) + return; + + UWorld* const world = GetWorld(); + if(world) + { + AGameStateBase* const state = Cast(world->GetGameState()); + if(state) + { + TArray players = state->GetPlayers(); + int32 teamSize = 0; + for(int32 i = 0; i < players.Num(); i++) + { + if(players[i] == nullptr) + continue; + if(players[i]->GetTeam() == team) + teamSize++; + } + if (teamSize < 2) + { + m_team = team; + state->OnPlayerStateChange_Multi(); + } + } + } +} \ No newline at end of file diff --git a/Source/UnrealProject/GameState/PlayerStateBase.h b/Source/UnrealProject/GameState/PlayerStateBase.h new file mode 100644 index 0000000..ea8ec49 --- /dev/null +++ b/Source/UnrealProject/GameState/PlayerStateBase.h @@ -0,0 +1,52 @@ +#pragma once +#include "PlayerStateBase.Generated.h" + +/* + Base player state, keeping the team the player is in + this class also keeps the ready state of players in the lobby and contains some functionality for joining/switching teams +*/ +UCLASS() +class APlayerStateBase : public APlayerState +{ + GENERATED_BODY() + friend class AGameStateBase; + friend class AMenuGameMode; +public: + APlayerStateBase(); + virtual void BeginPlay() override; + + UFUNCTION(Server, Reliable, WithValidation, Category = "Lobby") + void SetReadyState(bool state); + UFUNCTION(BlueprintCallable, Category = "Lobby") + bool GetReadyState() const; + + // Used for seamless travel, this copies the previous player state into the current one + void Transfer(APlayerStateBase* oldState); + + // Requests entry to a team + UFUNCTION(Server, Reliable, WithValidation, Category = "Lobby") + void RequestTeamEntry(uint8 team); + // Automatically join the prefered team by the server + UFUNCTION(Server, Reliable, WithValidation, BlueprintCallable, Category = "Lobby") + void AutoAssignTeam(); + + UFUNCTION(BlueprintCallable, Category = "Lobby") + int32 GetTeam() const; + + // Updates the nickname and the avatar of the player using the online subsystem + UFUNCTION(BlueprintCallable, Category = "Character") + void UpdatePersona(); + + UPROPERTY(BlueprintReadOnly, Category = "Character") + UTexture2D* avatar; + + UPROPERTY(BlueprintReadOnly, Category = "Character") + FString nickname; + +private: + UPROPERTY(Replicated) + bool m_readyToStart; + + UPROPERTY(Replicated) + int32 m_team; +}; \ No newline at end of file diff --git a/Source/UnrealProject/GameState/SessionManager.cpp b/Source/UnrealProject/GameState/SessionManager.cpp new file mode 100644 index 0000000..b54bda7 --- /dev/null +++ b/Source/UnrealProject/GameState/SessionManager.cpp @@ -0,0 +1,488 @@ +#include "UnrealProject.h" +#include "SessionManager.h" +#include "DefaultGameInstance.h" +#include "MatchMaking.h" + +#if PLATFORM_SPECIFIC_WIN == 0 +// Steam includes + disable warnings for unsafe functions +#pragma warning(push) +#pragma warning(disable:4996) +#include "steam/steam_api.h" +#include "steam/isteamutils.h" +#include "steam/isteamfriends.h" + +#pragma warning(pop) +#endif + +// For avatar to UTexture2D +#include "ImageUtils.h" + +#include + +#define POST_DESTROY_CREATE 1 +#define POST_DESTROY_JOIN 2 +#define POST_DESTROY_FIND 3 + + +namespace +{ + uint64 NetIDToInt(const FUniqueNetId& id) + { + std::wstring str = id.ToString().GetCharArray().GetData(); + uint64 asInt = 0; + str >> asInt; + return asInt; + } +} + + +static UTexture2D* defaultAvatar; +USessionManager::USessionManager() +{ + onCreateSessionCompleteDelegate = FOnCreateSessionCompleteDelegate::CreateUObject(this, &USessionManager::m_OnCreateSessionComplete); + onDestroySessionCompleteDelegate = FOnDestroySessionCompleteDelegate::CreateUObject(this, &USessionManager::m_OnDestroySessionComplete); + onStartSessionCompleteDelegate = FOnStartSessionCompleteDelegate::CreateUObject(this, &USessionManager::m_OnStartOnlineGameComplete); + onFindSessionsCompleteDelegate = FOnFindSessionsCompleteDelegate::CreateUObject(this, &USessionManager::m_OnFindSessionsComplete); + onJoinSessionCompleteDelegate = FOnJoinSessionCompleteDelegate::CreateUObject(this, &USessionManager::m_OnJoinSessionComplete); + onPingSearchResultsCompleteDelegate = FOnPingSearchResultsCompleteDelegate::CreateUObject(this, &USessionManager::m_OnPingSearchResultsComplete); + + // Default avatar in case some doesn't have an avatar or the online subsystem does not provide one + defaultAvatar = ConstructorHelpers::FObjectFinder(TEXT("/Game/Assets/GUI/564e176e1a279230")).Object; + + m_postDestroyAction = 0; + + sessionSearch = MakeShareable(new FOnlineSessionSearch()); + sessionSearch->bIsLanQuery = false; + sessionSearch->MaxSearchResults = 100; + sessionSearch->PingBucketSize = 50; + sessionSearch->QuerySettings.Set(SEARCH_PRESENCE, true, EOnlineComparisonOp::Equals); + + // Cached online settings + m_onlineSystem = IOnlineSubsystem::Get(); + check(m_onlineSystem); + m_session = m_onlineSystem->GetSessionInterface(); + check(m_session.IsValid()); + + GPRINT("Session manager created with subsystem: " + m_onlineSystem->GetInstanceName()); + + uint32 sessionCount = m_session->GetNumSessions(); + GPRINT(sessionCount + " initial sessions found."); + + // Set default session settings + sessionSettings.bIsLANMatch = false; + sessionSettings.bUsesPresence = true; + sessionSettings.NumPublicConnections = 4; + sessionSettings.NumPrivateConnections = 0; + sessionSettings.bAllowInvites = true; + sessionSettings.bAllowJoinInProgress = true; + sessionSettings.bShouldAdvertise = true; + sessionSettings.bAllowJoinViaPresence = true; + sessionSettings.bAllowJoinViaPresenceFriendsOnly = false; + + sessionSettings.Set(SETTING_MAPNAME, FString("No Map"), EOnlineDataAdvertisementType::ViaOnlineServiceAndPing); + + // Register ping callback + onPingSearchResultsCompleteDelegateHandle = m_session->AddOnPingSearchResultsCompleteDelegate_Handle(onPingSearchResultsCompleteDelegate); +} +void USessionManager::Shutdown() +{ + m_session->ClearOnPingSearchResultsCompleteDelegate_Handle(onPingSearchResultsCompleteDelegateHandle); + + // Clean up sessions + uint32 sessionCount = m_session->GetNumSessions(); + if(sessionCount > 0) + { + DestroySession(); + } + + // Close net connections + CloseNetConnections(); +} + +void USessionManager::CreateSession(bool destroyOld) +{ + if(destroyOld && SessionExists()) + { + m_postDestroyAction = POST_DESTROY_CREATE; + DestroySession(); + return; + } + + APlayerController* pc = m_instance->GetFirstLocalPlayerController(); + if(!pc) + { + GERROR("There is no player controller!!"); + onCreateSessionComplete.Broadcast(false); + return; + } + + // Register delegate + onCreateSessionCompleteDelegateHandle = m_session->AddOnCreateSessionCompleteDelegate_Handle(onCreateSessionCompleteDelegate); + + const FUniqueNetId& netID = *pc->PlayerState->UniqueId; + if(!m_session->CreateSession(netID, GameSessionName, sessionSettings)) + { + GERROR("m_session->CreateSession call failed!"); + onCreateSessionComplete.Broadcast(false); + m_session->ClearOnCreateSessionCompleteDelegate_Handle(onCreateSessionCompleteDelegateHandle); + } +} +void USessionManager::FindSessions(bool destroyOld) +{ + APlayerController* pc = m_instance->GetFirstLocalPlayerController(); + if(!pc) + { + GERROR("There is no player controller!!"); + onFindSessionsComplete.Broadcast(false); + return; + } + + if(destroyOld && SessionExists()) + { + m_postDestroyAction = POST_DESTROY_FIND; + DestroySession(); + return; + } + + // Register delegate + onFindSessionsCompleteDelegateHandle = m_session->AddOnFindSessionsCompleteDelegate_Handle(onFindSessionsCompleteDelegate); + + const FUniqueNetId& netID = *pc->PlayerState->UniqueId; + if(!m_session->FindSessions(netID, sessionSearch.ToSharedRef())) + { + GERROR("m_session->CreateSession call failed!"); + onCreateSessionComplete.Broadcast(false); + m_session->ClearOnCreateSessionCompleteDelegate_Handle(onCreateSessionCompleteDelegateHandle); + } +} +void USessionManager::JoinSession(const FOnlineSessionSearchResult& searchResult, bool destroyOld) +{ + joinSessionResult = searchResult; + + if(destroyOld && SessionExists()) + { + m_postDestroyAction = POST_DESTROY_JOIN; + DestroySession(); + return; + } + + APlayerController* pc = m_instance->GetFirstLocalPlayerController(); + if(!pc) + { + GERROR("There is no player controller!!"); + onFindSessionsComplete.Broadcast(false); + return; + } + + // Register delegate + onJoinSessionCompleteDelegateHandle = m_session->AddOnJoinSessionCompleteDelegate_Handle(onJoinSessionCompleteDelegate); + + const FUniqueNetId& netID = *pc->PlayerState->UniqueId; + if(!m_session->JoinSession(netID, GameSessionName, joinSessionResult)) + { + GERROR("m_session->CreateSession call failed!"); + onCreateSessionComplete.Broadcast(false); + m_session->ClearOnJoinSessionCompleteDelegate_Handle(onJoinSessionCompleteDelegateHandle); + } +} +void USessionManager::DestroySession() +{ + onDestroySessionCompleteDelegateHandle = m_session->AddOnDestroySessionCompleteDelegate_Handle(onDestroySessionCompleteDelegate); + + if(!m_session->DestroySession(GameSessionName)) + { + GERROR("m_session->DestroySession call failed!"); + onDestroySessionComplete.Broadcast(false); + m_session->ClearOnDestroySessionCompleteDelegate_Handle(onDestroySessionCompleteDelegateHandle); + } +} +void USessionManager::PingResult(const FOnlineSessionSearchResult& result) +{ + if(!m_session->PingSearchResults(result)) + { + onPingComplete.Broadcast(false); + } +} + +bool USessionManager::SessionExists() const +{ + return m_session->GetNumSessions() > 0; +} + +void USessionManager::StartSession() +{ + m_session->StartSession(GameSessionName); +} +void USessionManager::EndSession() +{ + m_session->EndSession(GameSessionName); +} + +bool USessionManager::HasNetConnection() +{ + // Server? + UWorld* world = m_instance->GetWorld(); + if(world->GetNetDriver()) + return true; + + // Client? + APlayerController* pc = m_instance->GetFirstLocalPlayerController(); + if(pc->GetNetConnection()) + return true; + return false; +} + +void USessionManager::CloseNetConnections() +{ + if(!m_instance) + { + GERROR("INSTANCE NOT SET/INVALID??"); + return; + } + + // Close the network connection + UWorld* world = m_instance->GetWorld(); + if(!world) + { + GERROR("Can't close net connections, No world set"); + return; + } + ENetMode netMode = world->GetNetMode(); + GPRINT("Leaving client's net mode = " + netMode); + + UNetDriver* netDriver = world->GetNetDriver(); + + APlayerController* pc = m_instance->GetFirstLocalPlayerController(); + UNetConnection* netConnection = pc ? pc->GetNetConnection() : nullptr; + if(netConnection) + { + check(netMode == NM_Client); + // Close the client connection + netConnection->Close(); + } + else if(netDriver) + { + check(netMode == NM_ListenServer); + // Shutdown the net driver + world->SetNetDriver(nullptr); + GEngine->DestroyNamedNetDriver(world, NAME_GameNetDriver); + } +} + +void USessionManager::AcceptUserInvite(const bool bWasSuccess, const int32 ControllerId, TSharedPtr Us, const FOnlineSessionSearchResult& res) +{ + onAcceptInvite.Broadcast(bWasSuccess, res); +} + +FString USessionManager::GetPlayerName(const FUniqueNetId& netID) +{ + FString ret; +#if PLATFORM_SPECIFIC_WIN == 0 + ISteamFriends* friends = SteamFriends(); + if(friends) + { + FString steamIDString = netID.ToString(); + CSteamID steamID((uint64)_wtoi64(steamIDString.GetCharArray().GetData())); + const char* name = SteamFriends()->GetFriendPersonaName(steamID); + if(!name) + ret = FString(TEXT("")); + else + ret = FString(name); + } + else +#endif + { + IOnlineIdentityPtr identity = m_onlineSystem->GetIdentityInterface(); + ret = identity->GetPlayerNickname(netID); + } + return ret; +} + +FString USessionManager::GetSteamID(const FUniqueNetId& netID) +{ + FString ret; +#if PLATFORM_SPECIFIC_WIN == 0 + ISteamFriends* friends = SteamFriends(); + if(friends) + { + FString steamIDString = netID.ToString(); + CSteamID steamID((uint64)_wtoi64(steamIDString.GetCharArray().GetData())); + return steamIDString; + } +#endif + return ret; +} + +UTexture2D* USessionManager::GetPlayerAvatar(const FUniqueNetId& netID) +{ +#if PLATFORM_SPECIFIC_WIN == 0 + FString idString = netID.GetHexEncodedString(); + if(m_profilePictures.Contains(idString)) + { + UTexture2D* ret = m_profilePictures[idString]; + if(ret) + { + return ret; + } + else + { + m_profilePictures.Remove(idString); + } + } + + FString steamIDString = netID.ToString(); + CSteamID steamID((uint64)_wtoi64(steamIDString.GetCharArray().GetData())); + + ISteamFriends* steamFriends = SteamFriends(); + if(!steamFriends) + return defaultAvatar; + int imgId = steamFriends->GetMediumFriendAvatar(steamID); + if(imgId == -1) + return defaultAvatar; + + uint32 w, h; + if(!SteamUtils()->GetImageSize(imgId, &w, &h)) + return defaultAvatar; + TArray imagePixels; + imagePixels.SetNumUninitialized(w * h); + if(!SteamUtils()->GetImageRGBA(imgId, (uint8*)imagePixels.GetData(), w * h * 4)) + return defaultAvatar; + + for(uint32 i = 0; i < (w*h); i++) + { + imagePixels[i] = FColor(imagePixels[i].B, imagePixels[i].G, imagePixels[i].R, imagePixels[i].A); + } + + FString imageName = FString("T_Avatar_") + netID.ToString(); + FCreateTexture2DParameters params; + params.bDeferCompression = false; + params.bSRGB = false; + params.bUseAlpha = true; + UTexture2D* tex = m_CreateTexture2D(w, h, imagePixels, true); + //FImageUtils::CreateTexture2D(w, h, imagePixels, GetWorld(), imageName, EObjectFlags)0, params); + check(tex); + m_profilePictures.Add(idString, tex); + return tex; +#else + return defaultAvatar; +#endif +} + +// Solution to ConstructTexture2D not being supported on consoles. +// From: https://answers.unrealengine.com/questions/33571/fimageutilscreatetexture2d-causes-brief-hang-is-th.html +UTexture2D* USessionManager::m_CreateTexture2D(const int32 srcWidth, const int32 srcHeight, const TArray& srcColor, const bool useAlpha) +{ + UTexture2D* texture = UTexture2D::CreateTransient(srcWidth, srcHeight, PF_B8G8R8A8); + + uint8* MipData = static_cast(texture->PlatformData->Mips[0].BulkData.Lock(LOCK_READ_WRITE)); + + // Create base mip. + uint8* DestPtr = NULL; + const FColor* SrcPtr = NULL; + for(int32 y = 0; y < srcHeight; y++) + { + DestPtr = &MipData[(srcHeight - 1 - y) * srcWidth * sizeof(FColor)]; + SrcPtr = const_cast(&srcColor[(srcHeight - 1 - y) * srcWidth]); + for(int32 x = 0; x < srcWidth; x++) + { + *DestPtr++ = SrcPtr->B; + *DestPtr++ = SrcPtr->G; + *DestPtr++ = SrcPtr->R; + if(useAlpha) + { + *DestPtr++ = SrcPtr->A; + } + else + { + *DestPtr++ = 0xFF; + } + SrcPtr++; + } + } + + // Unlock the texture + texture->PlatformData->Mips[0].BulkData.Unlock(); + texture->UpdateResource(); + + return texture; +} + +void USessionManager::m_OnCreateSessionComplete(FName sessionName, bool successful) +{ + GWARNING("OnCreateSessionComplete " + sessionName.ToString().GetCharArray().GetData() + ", " + successful); + + m_session->ClearOnCreateSessionCompleteDelegate_Handle(onCreateSessionCompleteDelegateHandle); + + // Start listen server + UWorld* world = m_instance->GetWorld(); + if(world->Listen(m_listenURL)) + { + GPRINT("Started listen server on " + m_listenURL.ToString()); + } + else + { + successful = false; + GERROR("Failed to start listen server!"); + } + + onCreateSessionComplete.Broadcast(successful); +} +void USessionManager::m_OnStartOnlineGameComplete(FName sessionName, bool successful) +{ + GWARNING("OnStartOnlineGameComplete " + sessionName.ToString().GetCharArray().GetData() + ", " + successful); + m_session->ClearOnStartSessionCompleteDelegate_Handle(onStartSessionCompleteDelegateHandle); +} +void USessionManager::m_OnFindSessionsComplete(bool successful) +{ + GWARNING("OnFindSessionsComplete " + successful + + " with " + sessionSearch->SearchResults.Num() + " sessions"); + + m_session->ClearOnFindSessionsCompleteDelegate_Handle(onFindSessionsCompleteDelegateHandle); + onFindSessionsComplete.Broadcast(true); +} +void USessionManager::m_OnJoinSessionComplete(FName sessionName, EOnJoinSessionCompleteResult::Type result) +{ + GWARNING("OnJoinSessionComplete " + sessionName.ToString().GetCharArray().GetData() + ", " + int32(result)); + + m_session->ClearOnJoinSessionCompleteDelegate_Handle(onJoinSessionCompleteDelegateHandle); + if(result == EOnJoinSessionCompleteResult::Success) + { + if(!m_session->GetResolvedConnectString(joinSessionResult, GamePort, joinConnectionString)) + result = EOnJoinSessionCompleteResult::CouldNotRetrieveAddress; + else if(joinConnectionString.IsEmpty()) + result = EOnJoinSessionCompleteResult::CouldNotRetrieveAddress; + else + GPRINT("Resolved joined session address to: " + joinConnectionString); + } + onJoinSessionComplete.Broadcast(result); +} +void USessionManager::m_OnDestroySessionComplete(FName sessionName, bool successful) +{ + GWARNING("OnDestroySessionComplete " + sessionName.ToString().GetCharArray().GetData() + ", " + successful); + + m_session->ClearOnDestroySessionCompleteDelegate_Handle(onDestroySessionCompleteDelegateHandle); + + if(m_postDestroyAction != 0) + { + if(m_postDestroyAction == POST_DESTROY_CREATE) + { + CreateSession(false); + } + else if(m_postDestroyAction == POST_DESTROY_JOIN) + { + JoinSession(joinSessionResult, false); + } + else if(m_postDestroyAction == POST_DESTROY_FIND) + { + FindSessions(false); + } + m_postDestroyAction = 0; + return; + } + + onDestroySessionComplete.Broadcast(successful); +} +void USessionManager::m_OnPingSearchResultsComplete(bool successful) +{ + onPingComplete.Broadcast(successful); +} diff --git a/Source/UnrealProject/GameState/SessionManager.h b/Source/UnrealProject/GameState/SessionManager.h new file mode 100644 index 0000000..1a9c9bc --- /dev/null +++ b/Source/UnrealProject/GameState/SessionManager.h @@ -0,0 +1,115 @@ +#pragma once + +#include "Online.h" +#include "SessionManager.Generated.h" + +/* + A Class for managing the online subsystem and sessions +*/ +UCLASS() +class USessionManager : public UObject +{ + GENERATED_BODY() + friend class UDefaultGameInstance; + UPROPERTY() + class UDefaultGameInstance* m_instance; +public: + USessionManager(); + void Shutdown(); + + void CreateSession(bool destroyOld = true); + void FindSessions(bool destroyOld = true); + void JoinSession(const class FOnlineSessionSearchResult& searchResult, bool destroyOld = true); + void DestroySession(); + void PingResult(const class FOnlineSessionSearchResult& result); + + // Checks if a session is active + bool SessionExists() const; + + // Mark session as in progress + void StartSession(); + // Mark session as ended + void EndSession(); + + // Check if we currently are connected to a other clients, client or server + bool HasNetConnection(); + + // Closes all network connections, client or server + void CloseNetConnections(); + + // Routed from the game instance to signal the user has accepted an invite through the online subsystem + void AcceptUserInvite(const bool bWasSuccess, const int32 ControllerId, + TSharedPtr Us, const FOnlineSessionSearchResult& res); + + // Get a player's nickname for a NetId in the current session + //UFUNCTION(BlueprintCallable, Category = "Friends") + FString GetPlayerName(const FUniqueNetId& netID); + // Get the SteamID of a player or an empty string if not connected to steam + FString GetSteamID(const FUniqueNetId& netID); + // Get a player's avatar for a NetId in the current session + //UFUNCTION(BlueprintCallable, Category="Friends") + UTexture2D* GetPlayerAvatar(const FUniqueNetId& netID); + + // Public delegates + DECLARE_MULTICAST_DELEGATE_OneParam(FSessionCallback, bool); + FSessionCallback onDestroySessionComplete; + FSessionCallback onCreateSessionComplete; + FSessionCallback onFindSessionsComplete; + DECLARE_MULTICAST_DELEGATE_OneParam(FOnJoinSessionComplete, int32); + FOnJoinSessionComplete onJoinSessionComplete; + DECLARE_MULTICAST_DELEGATE_TwoParams(FOnAcceptInvite, bool, const FOnlineSessionSearchResult&); + FOnAcceptInvite onAcceptInvite; + DECLARE_MULTICAST_DELEGATE_OneParam(FOnPingComplete, bool) + FOnPingComplete onPingComplete; + + // The settings used when creating new sessions + FOnlineSessionSettings sessionSettings; + FOnlineSessionSearchResult joinSessionResult; + FString joinConnectionString; + + // An ongoing session search + TSharedPtr sessionSearch; + +private: + // Session action callbacks + void m_OnCreateSessionComplete(FName sessionName, bool successful); + void m_OnStartOnlineGameComplete(FName sessionName, bool successful); + void m_OnFindSessionsComplete(bool successful); + void m_OnJoinSessionComplete(FName sessionName, EOnJoinSessionCompleteResult::Type result); + void m_OnDestroySessionComplete(FName sessionName, bool successful); + void m_OnPingSearchResultsComplete(bool successful); + + // Used to create a texture for the player avatars + class UTexture2D* m_CreateTexture2D(const int32 srcWidth, const int32 srcHeight, const TArray& srcColor, const bool useAlpha); + + // Session action callback delegate handles + FOnCreateSessionCompleteDelegate onCreateSessionCompleteDelegate; + FOnStartSessionCompleteDelegate onStartSessionCompleteDelegate; + FOnFindSessionsCompleteDelegate onFindSessionsCompleteDelegate; + FOnJoinSessionCompleteDelegate onJoinSessionCompleteDelegate; + FOnDestroySessionCompleteDelegate onDestroySessionCompleteDelegate; + FOnPingSearchResultsCompleteDelegate onPingSearchResultsCompleteDelegate; + FDelegateHandle onCreateSessionCompleteDelegateHandle; + FDelegateHandle onStartSessionCompleteDelegateHandle; + FDelegateHandle onFindSessionsCompleteDelegateHandle; + FDelegateHandle onJoinSessionCompleteDelegateHandle; + FDelegateHandle onDestroySessionCompleteDelegateHandle; + FDelegateHandle onPingSearchResultsCompleteDelegateHandle; + + // Action to perform after destroy event finished + int32 m_postDestroyAction; + + // Online subsystem handles + IOnlineSubsystem* m_onlineSystem; + IOnlineSessionPtr m_session; + + // The local player's NetId + TSharedPtr m_localNetID; + + // A list of cached profile pictures + UPROPERTY() + TMap m_profilePictures; + + // URL for the listen server + FURL m_listenURL; +}; \ No newline at end of file diff --git a/Source/UnrealProject/Items/ItemBase.cpp b/Source/UnrealProject/Items/ItemBase.cpp new file mode 100644 index 0000000..92cb91e --- /dev/null +++ b/Source/UnrealProject/Items/ItemBase.cpp @@ -0,0 +1,125 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "ItemBase.h" +#include "BaseSkillObject.h" +#include "CharacterBase.h" + +// Sets default values +AItemBase::AItemBase() +{ + // Create a mesh subcomponent without collision. + Mesh = CreateDefaultSubobject(TEXT("Mesh")); + RootComponent = Mesh; + Mesh->SetCollisionEnabled(ECollisionEnabled::NoCollision); +} + +void AItemBase::CreateItemsFromSkill(UWorld* world, class UBaseSkillObject* skillObject, class ACharacterBase* character) +{ + + // Check if passed arguments are valid. + if (world && skillObject && character) + { + for (size_t i = 0; i < skillObject->visibleItems.itemsToEquip.Num(); i++) + { + if (!skillObject->visibleItems.itemsToEquip[i].mesh) + { + TERROR("Skill item does not have a mesh attached to it!"); + continue; + } + + // Get the Skeletal Mesh of the character. + USkeletalMeshComponent* charMesh = character->GetMesh(); + + // Check if the socket has already been taken by another item. + bool hasBeenTaken = false; + for (auto SceneComp : charMesh->AttachChildren) + { + AItemBase* item = Cast(SceneComp->GetOwner()); + if (item) + { + if (item->type == skillObject->visibleItems.itemsToEquip[i].type) + { + hasBeenTaken = true; + break; + } + } + } + if (!hasBeenTaken) + { + // Spawn a new item. + AItemBase* createdItem = Cast(world->SpawnActor(StaticClass())); + // Set the item with the correct settings and mesh. + createdItem->type = skillObject->visibleItems.itemsToEquip[i].type; + createdItem->SetMesh(skillObject->visibleItems.itemsToEquip[i].mesh); + createdItem->skillObject = skillObject; + createdItem->AttachItemToCharacter(character); + } + } + return; + } + TERROR("Either world (" + world + "), skillObject (" + skillObject + ") or character (" + character + ") passed to AItemBase::CreateItemFromSkill was a nullptr."); +} + +AItemBase* AItemBase::CheckSlotOccupied(EItemTypeEnum slotType, class ACharacterBase* character) +{ + + // Check if passed arguments are valid. + if(character) + { + UWorld* world = character->GetWorld(); + + // Get the Skeletal Mesh of the character. + USkeletalMeshComponent* charMesh = character->GetMesh(); + // Check if the socket has already been taken by another item. + bool hasBeenTaken = false; + for(auto SceneComp : charMesh->AttachChildren) + { + AItemBase* item = Cast(SceneComp->GetOwner()); + if (item) + { + if(item->type == slotType) + return item; + } + } + } + return nullptr; +} + +void AItemBase::Destroyed() +{ + ACharacterBase* attachedParent = Cast(this->GetAttachParentActor()); + if(IsValid(attachedParent)) + { + attachedParent->m_OnItemRemoved(this); + } + Super::Destroyed(); +} + +void AItemBase::SetMesh(USkeletalMesh* newMesh) +{ + Mesh->SetSkeletalMesh(newMesh); +} + +bool AItemBase::AttachItemToCharacter(class ACharacterBase* networkCharacter) +{ + // Variable used to retrieve names of the EItemTypeEnum values. + const static UEnum* EnumPtr = FindObject(ANY_PACKAGE, TEXT("EItemTypeEnum"), true); + // Retrieve the name of the type the item was assigned to. This is to attach the item to a socket. + FName socketName = FName(*EnumPtr->GetEnumName((int32)type)); + // Get the Skeletal Mesh of the character. + USkeletalMeshComponent* charMesh = networkCharacter->GetMesh(); + + // Check if the socket actually exists. + if(!charMesh->DoesSocketExist(socketName)) + { + return false; + } + + // Attach the item to the socket. + RootComponent->AttachTo(charMesh, NAME_None, EAttachLocation::SnapToTargetIncludingScale); + Mesh->SetMasterPoseComponent(charMesh); + // Notify character + networkCharacter->m_OnItemAdded(this); + return true; +} diff --git a/Source/UnrealProject/Items/ItemBase.h b/Source/UnrealProject/Items/ItemBase.h new file mode 100644 index 0000000..673b75b --- /dev/null +++ b/Source/UnrealProject/Items/ItemBase.h @@ -0,0 +1,44 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "GameFramework/Actor.h" +#include "ItemBase.generated.h" + +UENUM(BlueprintType) +enum class EItemTypeEnum : uint8 +{ + Hat UMETA(DisplayName = "Hat") , + MainWeapon UMETA(DisplayName = "Main Weapon") , + OffWeapon UMETA(DisplayName = "Off Weapon") , + Chest UMETA(DisplayName = "Chest"), + Ammo UMETA(DisplayName = "Ammo"), + Feet UMETA(DisplayName = "Feet"), +}; + +UCLASS() +class UNREALPROJECT_API AItemBase : public AActor +{ + GENERATED_BODY() + friend class ACharacterBase; +public: + AItemBase(); + // Creating an item using a Skill Object's data. + static void CreateItemsFromSkill(UWorld* world, class UBaseSkillObject* skillObject, class ACharacterBase* character); + + // Returns the item currently equiped in a given slot + static AItemBase* CheckSlotOccupied(EItemTypeEnum slotType, class ACharacterBase* character); + + virtual void Destroyed() override; + + void SetMesh(class USkeletalMesh* newMesh); + // Attaches this item to a character. + bool AttachItemToCharacter(class ACharacterBase* networkCharacter); + + // Type of the item. This variable also correlates to the socket the item attaches to. + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Item) + EItemTypeEnum type; + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Components) + class USkeletalMeshComponent* Mesh; + class UBaseSkillObject* skillObject; +}; diff --git a/Source/UnrealProject/Sound/MusicPlayer.cpp b/Source/UnrealProject/Sound/MusicPlayer.cpp new file mode 100644 index 0000000..3f5d85b --- /dev/null +++ b/Source/UnrealProject/Sound/MusicPlayer.cpp @@ -0,0 +1,127 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "DefaultGameInstance.h" +#include "Prefs.h" +#include "MusicPlayer.h" + + +AMusicPlayer::AMusicPlayer() +{ + PrimaryActorTick.bCanEverTick = true; + + RootComponent = audioComponent = CreateDefaultSubobject(TEXT("Audio")); + + bReplicates = true; + + m_setInCombat = false; + m_isInCombat = false; +} + +void AMusicPlayer::BeginPlay() +{ + Super::BeginPlay(); + + float volume = Cast(GetGameInstance())->GetPrefs()->musicVolume; + audioComponent->SetVolumeMultiplier(volume); + + if (generalMusic.Num() > 0) + { + m_currentTrack = FMath::Rand() % generalMusic.Num(); + + audioComponent->SetSound(generalMusic[m_currentTrack]); + audioComponent->Play(); + } +} + +void AMusicPlayer::Tick( float DeltaTime ) +{ + Super::Tick( DeltaTime ); + + // Change the music combat state + if (m_setInCombat != m_isInCombat) + { + // Fade out the volume + m_changeTime += DeltaTime * 1.5f; + if (m_changeTime >= 1) + { + // Set next state + m_isInCombat = m_setInCombat; + audioComponent->Stop(); + m_changeTime = 0; + } + } + + float volume = Cast(GetGameInstance())->GetPrefs()->musicVolume; + audioComponent->SetVolumeMultiplier(volume * (1.0f - m_changeTime)); + + if (!audioComponent->IsPlaying()) + m_SelectNextTrack(); +} + +void AMusicPlayer::m_SelectNextTrack() +{ + // Regenerate the array of music (in case any tracks are NULL) + TArray& sampleFrom = (m_isInCombat ? combatMusic : generalMusic); + if (sampleFrom.Num() == 0) + return; + TArray availableTracks; + for (int32 i = 0; i < sampleFrom.Num(); i++) + { + if (sampleFrom[i]) + availableTracks.Add(sampleFrom[i]); + } + if (availableTracks.Num() == 0) + return; + + // Only select a random playlist if there are plenty of tracks + const int32 trackCount = availableTracks.Num(); + if (trackCount > 5) + { + // Add the current track to the recently played list + m_lastTracks.Add(m_currentTrack); + while (m_lastTracks.Num() > 3) + m_lastTracks.RemoveAt(0); + + // Select a random number + m_currentTrack = FMath::Rand() % availableTracks.Num(); + while (true) + { + // Check in the array of recently played to see if we already played this + bool found = false; + for (int32 i = 0; i < m_lastTracks.Num(); i++) + { + if (m_currentTrack == m_lastTracks[i]) + { + found = true; + break; + } + } + if (!found) + break; + + // Pick the next track and see if we havent already played that + m_currentTrack = (m_currentTrack + 1) % availableTracks.Num(); + } + } + else + m_currentTrack = (m_currentTrack + 1) % availableTracks.Num(); + + // Set the new track + audioComponent->SetSound(availableTracks[m_currentTrack]); + audioComponent->Play(); +} + +void AMusicPlayer::SetInCombat(bool inCombat) +{ + check(Role == ROLE_Authority); + m_setInCombat = inCombat; + m_changeTime = 0; +} + +void AMusicPlayer::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(AMusicPlayer, m_setInCombat); +} \ No newline at end of file diff --git a/Source/UnrealProject/Sound/MusicPlayer.h b/Source/UnrealProject/Sound/MusicPlayer.h new file mode 100644 index 0000000..b9abc0b --- /dev/null +++ b/Source/UnrealProject/Sound/MusicPlayer.h @@ -0,0 +1,41 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "GameFramework/Actor.h" +#include "MusicPlayer.generated.h" + +UCLASS() +class UNREALPROJECT_API AMusicPlayer : public AActor +{ + GENERATED_BODY() + +public: + AMusicPlayer(); + + virtual void BeginPlay() override; + + virtual void Tick( float DeltaSeconds ) override; + + UPROPERTY(EditAnywhere) + TArray generalMusic; + UPROPERTY(EditAnywhere) + TArray combatMusic; + + void SetInCombat(bool inCombat); + + UPROPERTY(VisibleAnywhere, BlueprintReadOnly) + class UAudioComponent* audioComponent; + +private: + int32 m_currentTrack; + TArray m_lastTracks; + + UPROPERTY(Replicated) + bool m_setInCombat; + + bool m_isInCombat; + float m_changeTime; + + void m_SelectNextTrack(); +}; diff --git a/Source/UnrealProject/Sound/SoundEffect.cpp b/Source/UnrealProject/Sound/SoundEffect.cpp new file mode 100644 index 0000000..65b3cb6 --- /dev/null +++ b/Source/UnrealProject/Sound/SoundEffect.cpp @@ -0,0 +1,59 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" + +#include "SoundEffect.h" +#include "Prefs.h" +#include "DefaultGameInstance.h" + + +// Sets default values +ASoundEffect::ASoundEffect() +{ + PrimaryActorTick.bCanEverTick = true; + + bReplicates = true; + RootComponent = m_audio = CreateDefaultSubobject(TEXT("Audio")); +} + +void ASoundEffect::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + DOREPLIFETIME_CONDITION(ASoundEffect, m_audio, COND_InitialOnly); +} + +// Called when the game starts or when spawned +void ASoundEffect::BeginPlay() +{ + Super::BeginPlay(); + m_audio->OnAudioFinished.AddDynamic(this, &ASoundEffect::OnAudioFinished); + + if (!m_audio->Sound) + Destroy(); + + // Initial volume + float volume = Cast(GetGameInstance())->GetPrefs()->sfxVolume; + m_audio->SetVolumeMultiplier(volume); + + m_audio->Play(); +} + +// Called every frame +void ASoundEffect::Tick( float DeltaTime ) +{ + Super::Tick( DeltaTime ); + + // Update volume settings + float volume = Cast(GetGameInstance())->GetPrefs()->sfxVolume; + m_audio->SetVolumeMultiplier(volume); +} + +void ASoundEffect::OnAudioFinished() +{ + Destroy(); +} + +void ASoundEffect::Init(USoundBase* sound) +{ + m_audio->SetSound(sound); +} \ No newline at end of file diff --git a/Source/UnrealProject/Sound/SoundEffect.h b/Source/UnrealProject/Sound/SoundEffect.h new file mode 100644 index 0000000..874e557 --- /dev/null +++ b/Source/UnrealProject/Sound/SoundEffect.h @@ -0,0 +1,27 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "GameFramework/Actor.h" +#include "SoundEffect.generated.h" + +UCLASS() +class UNREALPROJECT_API ASoundEffect : public AActor +{ + GENERATED_BODY() +public: + ASoundEffect(); + + virtual void BeginPlay() override; + virtual void Tick( float DeltaSeconds ) override; + + void Init(USoundBase* sound); + + UFUNCTION() + void OnAudioFinished(); + +private: + UPROPERTY(Replicated) + class UAudioComponent* m_audio; + +}; diff --git a/Source/UnrealProject/Sound/SoundFunctionLibrary.cpp b/Source/UnrealProject/Sound/SoundFunctionLibrary.cpp new file mode 100644 index 0000000..2781ccf --- /dev/null +++ b/Source/UnrealProject/Sound/SoundFunctionLibrary.cpp @@ -0,0 +1,50 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "SoundEffect.h" +#include "DefaultGameInstance.h" +#include "Prefs.h" +#include "SoundFunctionLibrary.h" + +ASoundEffect* USoundFunctionLibrary::CreateSoundEffect(USoundBase* sndClass, AActor* spawner) +{ + if (!sndClass) + { + JERROR("Sound class was not assigned when spawning Sound Effect"); + return nullptr; + } + if (!spawner) + { + JERROR("Spawner was not assigned when spawning Sound Effect"); + return nullptr; + } + + UWorld* const world = spawner->GetWorld(); + + FTransform transform; + transform.SetIdentity(); + transform.TransformPosition(spawner->GetActorLocation()); + ASoundEffect* sound = world->SpawnActorDeferred(ASoundEffect::StaticClass(), transform); + if (sound) + { + sound->Init(sndClass); + UGameplayStatics::FinishSpawningActor(sound, transform); + } + else + { + GWERROR(L"Failed to CreateSoundEffect()"); + } + return sound; +} + + +float USoundFunctionLibrary::GetSFXVolume(UDefaultGameInstance* gameInstance) +{ + if (!gameInstance || !gameInstance->IsA(UDefaultGameInstance::StaticClass())) { JERROR("GameInstance was not assigned getting Sound Volume"); return 0; } + return Cast(gameInstance)->GetPrefs()->sfxVolume; +} +float USoundFunctionLibrary::GetMusicVolume(UDefaultGameInstance* gameInstance) +{ + if (!gameInstance || !gameInstance->IsA(UDefaultGameInstance::StaticClass())) { JERROR("GameInstance was not assigned getting Music Volume"); return 0; } + return Cast(gameInstance)->GetPrefs()->musicVolume; +} \ No newline at end of file diff --git a/Source/UnrealProject/Sound/SoundFunctionLibrary.h b/Source/UnrealProject/Sound/SoundFunctionLibrary.h new file mode 100644 index 0000000..f8bbf9b --- /dev/null +++ b/Source/UnrealProject/Sound/SoundFunctionLibrary.h @@ -0,0 +1,25 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "Kismet/BlueprintFunctionLibrary.h" +#include "SoundFunctionLibrary.generated.h" + +/** + * + */ +UCLASS() +class UNREALPROJECT_API USoundFunctionLibrary : public UBlueprintFunctionLibrary +{ + GENERATED_BODY() + + +public: + UFUNCTION(BlueprintCallable, Category = "Sound") + static class ASoundEffect* CreateSoundEffect(class USoundBase* sndClass, class AActor* spawner); + + UFUNCTION(BlueprintCallable, Category = "Sound") + static float GetSFXVolume(class UDefaultGameInstance* gameInstance); + UFUNCTION(BlueprintCallable, Category = "Sound") + static float GetMusicVolume(class UDefaultGameInstance* gameInstance); +}; diff --git a/Source/UnrealProject/Spawners/CreatureSpawn.cpp b/Source/UnrealProject/Spawners/CreatureSpawn.cpp new file mode 100644 index 0000000..f1f2200 --- /dev/null +++ b/Source/UnrealProject/Spawners/CreatureSpawn.cpp @@ -0,0 +1,354 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "NPCBase.h" +#include "NetworkDoor.h" +#include "DefaultGameMode.h" +#include "CreatureSpawn.h" +#include "Modifier.h" +#include "NativeModifiers.h" +#include "NetworkPlayer.h" +#include "TouhouBoss.h" + +ACreatureSpawn::ACreatureSpawn() +{ + respawnTime = 5; + m_respawnTimer = 0; + + spawnContinuous = true; + + spawnTrigger = CreateDefaultSubobject(TEXT("Sphere")); + spawnTrigger->SetCollisionProfileName(TEXT("PlayerOverlap")); + spawnTrigger->AttachTo(RootComponent); + spawnTrigger->OnComponentBeginOverlap.AddDynamic(this, &ACreatureSpawn::OnOverlapBegin); + spawnTrigger->OnComponentEndOverlap.AddDynamic(this, &ACreatureSpawn::OnOverlapEnd); + spawnTrigger->SetSphereRadius(3000); + spawnTrigger->SetVisibility(false); +} + +void ACreatureSpawn::BeginPlay() +{ + Super::BeginPlay(); + + if (Role != ROLE_Authority) + return; + + if (aggroRadius < 0) aggroRadius = 0; + if (respawnTime < 0) respawnTime = 0; + + if (spawns.Num() == 0) + { + JWARNING("Empty spawner in scene"); + return; + } + else if (spawns.Num() != 1 && isBoss) + { + FWARNING("Unexpected amount of spawners for boss"); + } + + // Calculate spawn sub positions + if (spawns.Num() > 1) + { + float angle = (360.0f) / 180.0f * PI; + float anglestep = (angle) / (spawns.Num() + 1); + float rot = angle * 0.5f; + for (int32 i = 0; i < spawns.Num(); i++) + { + float f = cosf(rot); + float r = sinf(rot); + rot += anglestep; + if (hasGeneral) + { + m_resetPoints.Add(GetActorLocation() + FVector(f * 200, r * 200, 0)); + } + else m_resetPoints.Add(FVector(formationPoints[i].X,formationPoints[i].Y,0) + GetActorLocation()); + } + } + else if (spawns.Num() == 1) + m_resetPoints.Add(GetActorLocation()); + + m_respawnTimer = -1.0f; + +} +void ACreatureSpawn::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + Super::EndPlay(EndPlayReason); + +} + +void ACreatureSpawn::Tick(float DeltaTime) +{ + Super::Tick(DeltaTime); + + if (Role != ROLE_Authority) + return; + + UWorld* const world = GetWorld(); + if (!world) return; + + m_respawnTimer = m_respawnTimer > 0 ? (m_respawnTimer - DeltaTime) : 0; + + /*if (m_nearbyPlayers.empty()) + { + // Despawn if there are no players nearby and the timer is depleted + if (m_respawnTimer <= 0) + { + // m_DespawnMobs(); + } + } + else + {*/ + // Respawn time mobs if the timer has ran out and we are missing mobs + if (m_respawnTimer <= 0 && m_mobCount != m_mobs.Num() && spawnContinuous) + m_RespawnMobs(); +// } + + // No mobs to update + if (m_mobs.Num() == 0) + return; + + // Decrement respawntime + bool anyIdle = false; + for (int32 i = 0; i < spawns.Num(); i++) + { + if (!spawns[i] || !m_mobs[i]) + continue; + // Check if any mob is idle + const FVector2D mobPos = FVector2D(m_mobs[i]->GetActorLocation().X, m_mobs[i]->GetActorLocation().Y); + const FVector2D targetPos = FVector2D(SpawnResetPosition().X, SpawnResetPosition().Y); + const float distSqr = FVector2D::DistSquared(mobPos, targetPos); + if (!m_mobs[i]->target && distSqr < deaggroRadius*deaggroRadius) + anyIdle = true; + } + + //// We can start attacking if any mob is idle and there is a player nearby + //if (anyIdle) + //{ + // for (int32 i = 0; i < spawns.Num(); i++) + // { + // if (m_mobs[i] == nullptr) + // continue; + // //find the biggest threat of the enemies + // ANetworkCharacter* threat = m_GetBiggestThreat(i); + // if (m_mobs[i]->target != nullptr) + // continue; + // const FVector2D mobPos = FVector2D(m_mobs[i]->GetActorLocation().X, m_mobs[i]->GetActorLocation().Y); + // const FVector2D targetPos = FVector2D(m_resetPoints[i].X, m_resetPoints[i].Y); + // const float distSqr = FVector2D::DistSquared(mobPos, targetPos); + // if (distSqr < aggroRadius * aggroRadius)// (m_mobs[i]->collisionRadius*m_mobs[i]->collisionRadius)*collisionScaler) + // { + // FPRINT("idledistsettarget"); + // m_mobs[i]->target = threat; + // } + // + // } + //} + + if (isBoss && m_mobs.Num() == 1) + { + UWorld* const world = GetWorld(); + if (!world) return; + ADefaultGameMode* mode = Cast(world->GetAuthGameMode()); + if (!mode) return; + + //check if it is more than 1 team + int teamcount = 0; + int32 team = 0; + for (auto iter = m_nearbyPlayers.begin(); iter != m_nearbyPlayers.end(); iter++) + { + ANetworkCharacter* player = *iter; + if ((player->GetActorLocation() - GetActorLocation()).Size() < aggroRadius) + { + if (teamcount == 0) + { + team = player->GetTeam(); + teamcount++; + } + else if (team != player->GetTeam()) + { + teamcount++; + break; + } + } + } + if (teamcount > 1 && m_invulnerableModifier == nullptr) + { + if (m_mobs[0] != nullptr) + { + ModifierManager* manager = m_mobs[0]->GetModifierManager(); + TSubclassOf subclass = ADamageTakenModifier::StaticClass(); + m_invulnerableModifier = GetWorld()->SpawnActor(subclass); + if (m_invulnerableModifier != nullptr) + { + Cast(m_invulnerableModifier)->damageTakenMultiplier = 0.0f; + m_invulnerableModifier->character = m_mobs[0]; + manager->AddModifier(m_invulnerableModifier); + Cast(m_mobs[0])->SetShield(true); + } + else + { + FPRINT("failed to spawn m_invulnerableModifier in creaturespawn"); + } + } + } + else if (teamcount <2 && m_invulnerableModifier != nullptr) + { + m_invulnerableModifier->ForceDestroy(); + m_invulnerableModifier = nullptr; + } + } +} + +int32 ACreatureSpawn::OnMobDie(class ANPCBase* mob) +{ + const int32 idx = Super::OnMobDie(mob); + + m_respawnTimer = respawnTime; + + if (m_mobCount == 0) + { + for (int32 i = 0; i < doorsToOpen.Num(); i++) + { + if (doorsToOpen[i]) + doorsToOpen[i]->SetDoorState(false); + } + } + + return idx; +} + +void ACreatureSpawn::SpawnMobs() +{ + m_RespawnMobs(); +} + +void ACreatureSpawn::OnOverlapBegin(AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult) +{ + if (!OtherActor) + return; + if (!OtherActor->IsA(ANetworkCharacter::StaticClass())) + return; + + ANetworkCharacter* player = Cast(OtherActor); + if(player) + m_OnPlayerEnterOverlap(*player); +} +void ACreatureSpawn::OnOverlapEnd(AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex) +{ + if (!OtherActor) + return; + if (!OtherActor->IsA(ANetworkCharacter::StaticClass())) + return; + + ANetworkCharacter* player = Cast(OtherActor); + if(player) + m_OnPlayerExitOverlap(*player); +} + +void ACreatureSpawn::m_DespawnMobs() +{ + for (int32 i = 0; i < m_mobs.Num(); i++) + { + if (m_mobs[i]) + { + m_mobs[i]->UnsetSpawn(); + m_mobs[i]->Destroy(); + m_mobs[i] = nullptr; + } + } + + for (int32 i = 0; i < doorsToOpen.Num(); i++) + { + if (doorsToOpen[i]) + doorsToOpen[i]->SetDoorState(false); + } + formationEnemies.Empty(); + m_mobCount = 0; +} +void ACreatureSpawn::m_RespawnMobs() +{ + UWorld* const world = GetWorld(); + if (!world) + return; + for (int32 i = 0; i < m_mobs.Num(); i++) + { + if (!spawns[i]) + continue; + if (m_mobs[i] == nullptr) + { + // Respawn! + FTransform spawnTransform = GetTransform(); + FVector spawnVector = FVector(); + spawnTransform.SetLocation(m_resetPoints[i] + FVector(0, 0, 120)); + if (SpawnLocation.Num()>0) + { + int randnum = rand() % SpawnLocation.Num(); + spawnTransform = SpawnLocation[randnum]->GetTransform(); + FVector rotation = FVector(GetActorLocation() - spawnTransform.GetLocation()); + spawnTransform.SetRotation(FQuat(rotation.Rotation())); + //spawnVector = SpawnLocation->GetActorLocation() - GetActorLocation(); + //spawnTransform.SetLocation(spawnVector); + } + + ANPCBase* character = world->SpawnActorDeferred(spawns[i], spawnTransform, nullptr, nullptr, ESpawnActorCollisionHandlingMethod::AlwaysSpawn); + + character->SetSpawn(this); + character->SetTeam((int32)team); + m_mobs[i] = character; + m_mobCount++; + m_OnMobSpawn(i); + UGameplayStatics::FinishSpawningActor(character, spawnTransform); + character->SpawnDefaultController(); + } + } + + m_respawnTimer = respawnTime; +} + + +void ACreatureSpawn::GetNewTarget(class ANPCBase* mob) +{ + if (mob == nullptr) + return; + + int32 index = -1; + for (ANPCBase* currentMob : m_mobs) + { + index++; + if (mob != currentMob) + continue; + + + ANetworkCharacter* threat = nullptr; + bool end = false; + //keeps looping to make sure if the biggest threat is not in aggrorange it still works; + int32 biggestThreat = -1; + float dist = 1e34; + for (ANetworkCharacter *character:m_nearbyPlayers) + { + if (character->GetTeam() == (int)team) + continue; + + //check if it is bigger than the last biggestThreat + + const FVector2D spawnpost = FVector2D(GetActorLocation().X, GetActorLocation().Y); + const FVector2D targetPos = FVector2D(character->GetActorLocation().X, character->GetActorLocation().Y); + const float distSqr = FVector2D::DistSquared(spawnpost, targetPos); + //making sure the new biggest thread is withing the aggroRadius + if (distSqr < aggroRadius * aggroRadius&&dist>distSqr) + { + dist = distSqr; + threat = character; + + } + + } + + + m_resetTimer = resetTimer; + currentMob->target = threat; + return; + } + + return; +} \ No newline at end of file diff --git a/Source/UnrealProject/Spawners/CreatureSpawn.h b/Source/UnrealProject/Spawners/CreatureSpawn.h new file mode 100644 index 0000000..89c7261 --- /dev/null +++ b/Source/UnrealProject/Spawners/CreatureSpawn.h @@ -0,0 +1,54 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "SpawnerBase.h" +#include "CreatureSpawn.generated.h" + +UCLASS() +class ACreatureSpawn : public ASpawnerBase +{ + GENERATED_BODY() + +public: + ACreatureSpawn(); + + virtual void BeginPlay() override; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + + virtual void Tick(float DeltaTime) override; + + UPROPERTY(EditAnywhere, Category = "Switch Components") + TArray doorsToOpen; + + UPROPERTY(EditAnywhere) + float respawnTime; + + UPROPERTY(EditAnywhere, Category = "Switch Components") + class USphereComponent* spawnTrigger; + + UPROPERTY(EditAnywhere, Category = "Gamestate Components") + bool isBoss; + UPROPERTY(EditAnywhere, Category = "Gamestate Components") + bool spawnContinuous; + + UFUNCTION() + void OnOverlapBegin(class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult); + UFUNCTION() + void OnOverlapEnd(class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex); + + virtual int32 OnMobDie(class ANPCBase* mob) override; + virtual void GetNewTarget(class ANPCBase* mob) override; + void SpawnMobs(); + +protected: + virtual void m_RespawnMobs(); +private: + TArray m_resetPoints; + float m_respawnTimer; + + class AModifier* m_invulnerableModifier; + + void m_DespawnMobs(); + +}; diff --git a/Source/UnrealProject/Spawners/CreatureSpawnComponent.cpp b/Source/UnrealProject/Spawners/CreatureSpawnComponent.cpp new file mode 100644 index 0000000..5899453 --- /dev/null +++ b/Source/UnrealProject/Spawners/CreatureSpawnComponent.cpp @@ -0,0 +1,244 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "CreatureSpawnComponent.h" +#include "SpawnerBase.h" +#include "GeneralEnemy.h" +#include "OffensiveEnemy.h" +#include "RangedEnemy.h" +#include "EnemyBase.h" + +#define CONEARCVERTEXCOUNT 50 + +UCreatureSpawnComponent::UCreatureSpawnComponent(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) +{ + ShapeColor = FColor(223, 149, 157, 255); + spawnerBase = Cast(GetAttachmentRootActor()); + bUseEditorCompositing = true; +} + +FPrimitiveSceneProxy* UCreatureSpawnComponent::CreateSceneProxy() +{ + class FDrawConeSceneProxy : public FPrimitiveSceneProxy + { + public: + const UCreatureSpawnComponent* component; + FDrawConeSceneProxy(const UCreatureSpawnComponent* InComponent) + : FPrimitiveSceneProxy(InComponent) + , spawnerBase(InComponent->spawnerBase) + , bDrawOnlyIfSelected(InComponent->bDrawOnlyIfSelected) + , component(InComponent) + { + bWillEverBeLit = false; + } + + + virtual void GetDynamicMeshElements(const TArray& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector) const override + { + QUICK_SCOPE_CYCLE_COUNTER(STAT_GetDynamicMeshElements_DrawDynamicElements); + + const FMatrix& LocalToWorld = GetLocalToWorld(); + + for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++) + { + const FSceneView* View = Views[ViewIndex]; + + FPrimitiveDrawInterface* PDI = Collector.GetPDI(ViewIndex); + if (spawnerBase) + { + + FTransform transform = component->GetAttachParent()->GetComponentTransform(); + FVector base = transform.GetLocation(); + + + const float scale = 1.0f / 180.0f * PI; + float baseRot = (360.0f - transform.GetRotation().Euler().Z) * scale; + + //FVector forward = FVector(1, 0, 0).RotateAngleAxis(0.0f, FVector(0, 0, 1)); + //FVector right = FVector(0, 1, 0).RotateAngleAxis(0.0f, FVector(0, 0, 1)); + FVector forward = FVector(1, 0, 0); + FVector right = FVector(0, 1, 0); + float angle = (360.0f) / 180.0f * PI; + + FVector linePoints[CONEARCVERTEXCOUNT]; + float anglestep = (angle) / (CONEARCVERTEXCOUNT - 1); + + float rot = baseRot - angle * 0.5f; + for (int i = 0; i < CONEARCVERTEXCOUNT; i++) + { + float f = cosf(rot); + float r = sinf(rot); + linePoints[i] = base + (forward * f - right * r) * spawnerBase->aggroRadius; + rot += anglestep; + } + + for (int i = 0; i < CONEARCVERTEXCOUNT - 1; i++) + { + + PDI->DrawLine(linePoints[i], linePoints[i + 1], ShapeColor, SDPG_World); + } + for (int i = 0; i < CONEARCVERTEXCOUNT; i++) + { + float f = cosf(rot); + float r = sinf(rot); + linePoints[i] = base + (forward * f - right * r) * spawnerBase->deaggroRadius; + rot += anglestep; + } + + for (int i = 0; i < CONEARCVERTEXCOUNT - 1; i++) + { + + PDI->DrawLine(linePoints[i], linePoints[i + 1], FColor(0,0,255), SDPG_World); + } + FVector last = spawnerBase->GetActorLocation(); + FVector first; + for (int32 i = 0; i < spawnerBase->controlPoints.Num(); i++) + { + + FTransform transform = component->GetAttachParent()->GetComponentTransform(); + FVector base = transform.GetLocation() + FVector(spawnerBase->controlPoints[i].X, spawnerBase->controlPoints[i].Y, 0); + if (i == 0) + first = base; + + const float scale = 1.0f / 180.0f * PI; + float baseRot = (360.0f - transform.GetRotation().Euler().Z) * scale; + + //FVector forward = FVector(1, 0, 0).RotateAngleAxis(0.0f, FVector(0, 0, 1)); + //FVector right = FVector(0, 1, 0).RotateAngleAxis(0.0f, FVector(0, 0, 1)); + FVector forward = FVector(1, 0, 0); + FVector right = FVector(0, 1, 0); + float angle = (360.0f) / 180.0f * PI; + + FVector linePoints[CONEARCVERTEXCOUNT]; + float anglestep = (angle) / (CONEARCVERTEXCOUNT - 1); + + float rot = baseRot - angle * 0.5f; + for (int i = 0; i < CONEARCVERTEXCOUNT; i++) + { + float f = cosf(rot); + float r = sinf(rot); + linePoints[i] = base + (forward * f - right * r) * spawnerBase->drawingRadius; + rot += anglestep; + } + + for (int i = 0; i < CONEARCVERTEXCOUNT - 1; i++) + { + + PDI->DrawLine(linePoints[i], linePoints[i + 1], ShapeColor, SDPG_World); + } + PDI->DrawLine(base, last, ShapeColor, SDPG_World); + last = base; + //PDI->DrawLine(base, linePoints[CONEARCVERTEXCOUNT - 1], ShapeColor, SDPG_World); + } + if (spawnerBase->isConnected) + PDI->DrawLine(first, last, ShapeColor, SDPG_World); + for (int32 i = 0; i < spawnerBase->formationPoints.Num(); i++) + { + FColor formationColor = FColor(100, 100, 100); + //AEnemyBase *creature = (dynamic_cast(spawnerBase->spawns[i])); + FTransform transform = component->GetAttachParent()->GetComponentTransform(); + FVector base = transform.GetLocation() + spawnerBase->GetActorRotation().RotateVector( FVector(spawnerBase->formationPoints[i].X, spawnerBase->formationPoints[i].Y, 0))*spawnerBase->formationScale; + if (i == 0) + first = base; + + const float scale = 1.0f / 180.0f * PI; + float baseRot = (360.0f - transform.GetRotation().Euler().Z) * scale; + + //FVector forward = FVector(1, 0, 0).RotateAngleAxis(0.0f, FVector(0, 0, 1)); + //FVector right = FVector(0, 1, 0).RotateAngleAxis(0.0f, FVector(0, 0, 1)); + FVector forward = FVector(1, 0, 0); + FVector right = FVector(0, 1, 0); + float angle = (360.0f) / 180.0f * PI; + + FVector linePoints[CONEARCVERTEXCOUNT]; + float anglestep = (angle) / (CONEARCVERTEXCOUNT - 1); + + float rot = baseRot - angle * 0.5f; + for (int j = 0; j < CONEARCVERTEXCOUNT; j++) + { + float f = cosf(rot); + float r = sinf(rot); + linePoints[j] = base + (forward * f - right * r) * spawnerBase->drawingRadius; + rot += anglestep; + } + if (i >= spawnerBase->spawns.Num()) + { + formationColor.B = 10; + formationColor.G = 10; + formationColor.R = 10; + + } + else + { + + if ((spawnerBase->spawns[i])->IsChildOf(AGeneralEnemy::StaticClass())) + { + formationColor.B = 255; + formationColor.G = 255; + formationColor.R = 255; + } + else + { + formationColor.B = 42; + formationColor.G = 42; + formationColor.R = 42; + } + if ((spawnerBase->spawns[i])->IsChildOf(ARangedEnemy::StaticClass())&& !spawnerBase->spawns[i]->IsChildOf(AGeneralEnemy::StaticClass())) + { + formationColor.B = 0; + formationColor.G = 255; + formationColor.R = 0; + } + + if ((spawnerBase->spawns[i])->IsChildOf(AOffensiveEnemy::StaticClass())) + { + formationColor.B = 0; + formationColor.G = 0; + formationColor.R = 255; + } + + } + for (int j = 0; j < CONEARCVERTEXCOUNT - 1; j++) + { + + PDI->DrawLine(linePoints[j], linePoints[j + 1], formationColor, SDPG_World); + } + if (i < spawnerBase->formationRotation.Num()) + { + + PDI->DrawLine(base, base+(FRotator(0, spawnerBase->formationRotation[i],0).RotateVector( spawnerBase->GetActorForwardVector())* spawnerBase->drawingRadius), formationColor, SDPG_World); + } + else PDI->DrawLine(base, base + (spawnerBase->GetActorForwardVector())* spawnerBase->drawingRadius, formationColor, SDPG_World); + // PDI->DrawLine(base, last, formationColor, SDPG_World); + last = base; + //PDI->DrawLine(base, linePoints[CONEARCVERTEXCOUNT - 1], ShapeColor, SDPG_World); + } + //if (spawnerBase->isConnected) + // PDI->DrawLine(first, last, ShapeColor, SDPG_World); + + } + } + } + + virtual FPrimitiveViewRelevance GetViewRelevance(const FSceneView* View) const override + { + FPrimitiveViewRelevance Result; + Result.bDrawRelevance = IsSelected(); + Result.bDynamicRelevance = true; + Result.bShadowRelevance = IsShadowCast(View); + Result.bEditorPrimitiveRelevance = UseEditorCompositing(View); + return Result; + } + + virtual uint32 GetMemoryFootprint(void) const override { return(sizeof(*this) + GetAllocatedSize()); } + uint32 GetAllocatedSize(void) const { return(FPrimitiveSceneProxy::GetAllocatedSize()); } + + private: + const uint32 bDrawOnlyIfSelected : 1; + const ASpawnerBase* spawnerBase; + const FColor ShapeColor = FColor(255, 0, 0, 255); + const FTransform transform; + }; + + return new FDrawConeSceneProxy(this); +} \ No newline at end of file diff --git a/Source/UnrealProject/Spawners/CreatureSpawnComponent.h b/Source/UnrealProject/Spawners/CreatureSpawnComponent.h new file mode 100644 index 0000000..2c98e53 --- /dev/null +++ b/Source/UnrealProject/Spawners/CreatureSpawnComponent.h @@ -0,0 +1,22 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "Components/CapsuleComponent.h" +#include "CreatureSpawnComponent.generated.h" + +/** + * + */ +class ASpawnerBase; +UCLASS() +class UNREALPROJECT_API UCreatureSpawnComponent : public UCapsuleComponent +{ + GENERATED_UCLASS_BODY() + +public: + ASpawnerBase* spawnerBase; + + virtual FPrimitiveSceneProxy* CreateSceneProxy() override; + +}; diff --git a/Source/UnrealProject/Spawners/KOTHBossSpawner.cpp b/Source/UnrealProject/Spawners/KOTHBossSpawner.cpp new file mode 100644 index 0000000..e9b2f9c --- /dev/null +++ b/Source/UnrealProject/Spawners/KOTHBossSpawner.cpp @@ -0,0 +1,41 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "KOTHBossSpawner.h" +#include "NPCBase.h" + +void AKOTHBossSpawner::BeginPlay() +{ + Super::BeginPlay(); + for (TActorIteratoractorIt(GetWorld()); actorIt; ++actorIt) + { + ASpawnerBase *spawn = *actorIt; + if (!spawn->possesable) + spawn->SetTeam((int)team); + } +} +void AKOTHBossSpawner::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + Super::EndPlay(EndPlayReason); +} +void AKOTHBossSpawner::Tick(float deltaTime) +{ + Super::Tick(deltaTime); +} +bool AKOTHBossSpawner::IsBossAlive() const +{ + if(m_mobs.Num() > 0) + { + if(IsValid(m_mobs[0])) + return true; + } + return false; +} +void AKOTHBossSpawner::m_RespawnMobs() +{ + if (IsContested(m_currentTeam) && m_currentTeam<5) + { + return; + } + Super::m_RespawnMobs(); +} \ No newline at end of file diff --git a/Source/UnrealProject/Spawners/KOTHBossSpawner.h b/Source/UnrealProject/Spawners/KOTHBossSpawner.h new file mode 100644 index 0000000..5c21597 --- /dev/null +++ b/Source/UnrealProject/Spawners/KOTHBossSpawner.h @@ -0,0 +1,26 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "Spawners/KOTHSpawnerBase.h" +#include "KOTHBossSpawner.generated.h" + +/** + * + */ +UCLASS() +class UNREALPROJECT_API AKOTHBossSpawner : public AKOTHSpawnerBase +{ + GENERATED_BODY() +public: + virtual void BeginPlay() override; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + virtual void Tick(float deltaTime)override; + // Check if the KOTH boss is currently alive + bool IsBossAlive() const; + +protected: + virtual void m_RespawnMobs()override; +private: + +}; diff --git a/Source/UnrealProject/Spawners/KOTHMinionSpawner.cpp b/Source/UnrealProject/Spawners/KOTHMinionSpawner.cpp new file mode 100644 index 0000000..43d7bb0 --- /dev/null +++ b/Source/UnrealProject/Spawners/KOTHMinionSpawner.cpp @@ -0,0 +1,78 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "NPCBase.h" +#include "NetworkDoor.h" +#include "DefaultGameMode.h" +#include "KOTHMinionSpawner.h" +#include "KOTHBossSpawner.h" +#include "NetworkCharacter.h" + + + +AKOTHMinionSpawner::AKOTHMinionSpawner(const FObjectInitializer& init) + : Super(init) +{ + respawnTime = 5; + // m_respawnTimer = 0; + + spawnContinuous = true; +} +void AKOTHMinionSpawner::BeginPlay() +{ + Super::BeginPlay(); + if (Role == ROLE_Authority) + { + kothagroRadius = aggroRadius; + kothdeagroRadius = deaggroRadius; + + for (TActorIteratorbSpawn(GetWorld()); bSpawn; ++bSpawn) + { + if (bSpawn) + bossSpawner = *bSpawn; + } + Super::m_RespawnMobs(); + } +} +void AKOTHMinionSpawner::Tick(float DeltaTime) +{ + Super::Tick(DeltaTime); + + if (!HasAuthority()) + return; + + // Reset the camp to the NPC team after an amount of time(captureTime) + m_captureTimer -= DeltaTime; + if (m_captureTimer < 0 && (int)team < 5) + { + SetTeam((int)(NPCTeam::Team1)); + m_reset = true; + } +} +void AKOTHMinionSpawner::GetNewTarget(ANPCBase* mob) +{ + + if (team == bossSpawner->team || (int)team>5) + { + aggroRadius = kothagroRadius; + deaggroRadius = kothdeagroRadius; + Super::GetNewTarget(mob); + return; + } + for (ANPCBase* bMob : bossSpawner->m_mobs) + { + aggroRadius = 1e34; + deaggroRadius = 1e34; + ANetworkCharacter* threat = Cast(bMob); + if (threat) + mob->target = threat; + + } +} +void AKOTHMinionSpawner::m_RespawnMobs() +{ + if (((int)team < 5 || m_reset)) + { + Super::m_RespawnMobs(); + } +} diff --git a/Source/UnrealProject/Spawners/KOTHMinionSpawner.h b/Source/UnrealProject/Spawners/KOTHMinionSpawner.h new file mode 100644 index 0000000..20fb467 --- /dev/null +++ b/Source/UnrealProject/Spawners/KOTHMinionSpawner.h @@ -0,0 +1,30 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "Spawners/KOTHSpawnerBase.h" +#include "KOTHMinionSpawner.generated.h" + +/** + * + */ +UCLASS() +class UNREALPROJECT_API AKOTHMinionSpawner : public AKOTHSpawnerBase +{ + GENERATED_BODY() +public: + AKOTHMinionSpawner(const FObjectInitializer& init); + virtual void GetNewTarget(class ANPCBase* mob)override; + virtual void BeginPlay()override; + virtual void Tick(float deltaTime) override; + + + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "KOTH Spawner Properties") + FText CampName; + +private: + virtual void m_RespawnMobs()override; + float kothagroRadius; + float kothdeagroRadius; +}; diff --git a/Source/UnrealProject/Spawners/KOTHSpawnerBase.cpp b/Source/UnrealProject/Spawners/KOTHSpawnerBase.cpp new file mode 100644 index 0000000..3c6252f --- /dev/null +++ b/Source/UnrealProject/Spawners/KOTHSpawnerBase.cpp @@ -0,0 +1,132 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "KOTHSpawnerBase.h" +#include "NetworkPlayer.h" +#include "KOTHBossSpawner.h" +AKOTHSpawnerBase::AKOTHSpawnerBase(const FObjectInitializer& init) +{ + captureRadius = 500; + captureTime = 30; + possesionTime = 1; + + captureParticle = CreateDefaultSubobject("PossessParticle"); + captureParticle->AttachTo(RootComponent); +} + +void AKOTHSpawnerBase::BeginPlay() +{ + Super::BeginPlay(); + + // Assign team + m_currentTeam = (int)team; + + beingCaptured = false; +} +void AKOTHSpawnerBase::Tick(float DeltaTime) +{ + Super::Tick(DeltaTime); + + // Run capture polling code on host + if(Role == ROLE_Authority) + { + if(beingCaptured) + { + bool capture = false; + auto nearbyPlayers = GetNearbyPlayers(); + // Check contested condition + + if (m_mobCount == 0 && (nearbyPlayers.Num() <= 2)) + { + + if (nearbyPlayers.Num() > 0 && !IsContested(nearbyPlayers[0]->GetTeam()) && (int)team != nearbyPlayers[0]->GetTeam()) + { + capture = true; + + if (bossSpawner) + { + if ((int)bossSpawner->team == nearbyPlayers[0]->GetTeam() || (int)team < 5) + { + capture = false; + } + } + } + } + + if(capture) + { + m_possesionTimer += DeltaTime; + if(m_possesionTimer > possesionTime) + { + // Nearby team captures camp + m_ExeCaptureCamp(nearbyPlayers[0]->GetTeam()); + } + } + else + { + m_possesionTimer = 0; + } + } + } +} + +void AKOTHSpawnerBase::m_ExeCaptureCamp_Implementation(int32 targetTeam) +{ + if(Role == ROLE_Authority) + { + SetTeam(targetTeam); + onCampCaptured.Broadcast(targetTeam); + m_captureTimer = captureTime; + m_currentTeam = (int)NPCTeam::Team1; + beingCaptured = false; + m_reset = false; + } + + // Callback + OnEndCaptureCamp(targetTeam); + onEndCapture.Broadcast(targetTeam); + + // Deactivate capture particle + captureParticle->DeactivateSystem(); +} + +void AKOTHSpawnerBase::m_OnCampCleared() +{ + Super::m_OnCampCleared(); + + if(Role == ROLE_Authority) + { + beingCaptured = true; + } + + // Callback + OnBeginCaptureCamp(); + onBeginCapture.Broadcast(); + + // Enable capture particle + if(captureParticle->Template) + { + captureParticle->bAutoDestroy = false; + captureParticle->ResetParticles(); + captureParticle->ActivateSystem(); + } +} + +void AKOTHSpawnerBase::CaptureCamp(int targetTeam) +{ + Super::CaptureCamp(targetTeam); + + m_currentTeam = targetTeam; + m_possesionTimer = 0; +} + +bool AKOTHSpawnerBase::IsContested(int32 targetTeam) const +{ + for(ANetworkCharacter* character : m_nearbyPlayers) + { + ANetworkPlayer* player = Cast(character); + if(player && player->GetTeam() != targetTeam && FVector::DistSquared(GetActorLocation(), player->GetActorLocation()) < captureRadius * captureRadius) + return true; + } + return false; +} \ No newline at end of file diff --git a/Source/UnrealProject/Spawners/KOTHSpawnerBase.h b/Source/UnrealProject/Spawners/KOTHSpawnerBase.h new file mode 100644 index 0000000..ac88496 --- /dev/null +++ b/Source/UnrealProject/Spawners/KOTHSpawnerBase.h @@ -0,0 +1,70 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "Spawners/CreatureSpawn.h" +#include "KOTHSpawnerBase.generated.h" + +/** + * + */ +UCLASS() +class UNREALPROJECT_API AKOTHSpawnerBase : public ACreatureSpawn +{ + GENERATED_BODY() +public: + AKOTHSpawnerBase(const FObjectInitializer& init); + void BeginPlay() override; + void Tick(float DeltaTime) override; + + // Overrided capture camp behaviour + virtual void CaptureCamp(int team) override; + + DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnBeginCapture); + DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnEndCapture, int32, targetTeam); + + UFUNCTION(BlueprintImplementableEvent, Category = "KOTH Spawner") + void OnBeginCaptureCamp(); + UFUNCTION(BlueprintImplementableEvent, Category = "KOTH Spawner") + void OnEndCaptureCamp(int32 targetTeam); + UPROPERTY(BlueprintAssignable) + FOnBeginCapture onBeginCapture; + UPROPERTY(BlueprintAssignable) + FOnEndCapture onEndCapture; + + UFUNCTION(BlueprintCallable, Category = "KOTH Spawner") + bool IsContested(int32 targetTeam) const; + + DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnCampCaptured, int32, targetTeam); + UPROPERTY(BlueprintAssignable, Category = "Game") + FOnCampCaptured onCampCaptured; + + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Capture Particle") + UParticleSystemComponent* captureParticle; + + UPROPERTY(BlueprintReadOnly, Category = "KOTH Spawner Properties") + bool beingCaptured; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "KOTH Spawner Properties") + float captureRadius; + // The duration the camp is captured for + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "KOTH Spawner Properties") + float captureTime; + // The time it takes to capture a camp + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "KOTH Spawner Properties") + float possesionTime; + class AKOTHBossSpawner* bossSpawner; + +protected: + // Handle to start the capture process + void m_OnCampCleared() override; + + // Called when a camp is no longer contested and should be captured by the target team + UFUNCTION(NetMulticast, Reliable) + void m_ExeCaptureCamp(int32 targetTeam); + + float m_captureTimer; + float m_possesionTimer; + int m_currentTeam; + bool m_reset; +}; diff --git a/Source/UnrealProject/Spawners/LobbySpawn.cpp b/Source/UnrealProject/Spawners/LobbySpawn.cpp new file mode 100644 index 0000000..b60a247 --- /dev/null +++ b/Source/UnrealProject/Spawners/LobbySpawn.cpp @@ -0,0 +1,20 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "LobbySpawn.h" + + +ALobbySpawn::ALobbySpawn() +{ + displayMesh = CreateDefaultSubobject(TEXT("Mesh")); + displayMesh->bHiddenInGame = true; + displayMesh->bGenerateOverlapEvents = false; + displayMesh->SetCollisionEnabled(ECollisionEnabled::NoCollision); + displayArrow = CreateDefaultSubobject(TEXT("Arrow")); + assignedTeam = 0; +} + +void ALobbySpawn::BeginPlay() +{ + Super::BeginPlay(); +} \ No newline at end of file diff --git a/Source/UnrealProject/Spawners/LobbySpawn.h b/Source/UnrealProject/Spawners/LobbySpawn.h new file mode 100644 index 0000000..621e115 --- /dev/null +++ b/Source/UnrealProject/Spawners/LobbySpawn.h @@ -0,0 +1,25 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "GameFramework/Actor.h" +#include "LobbySpawn.generated.h" + +UCLASS() +class UNREALPROJECT_API ALobbySpawn : public AActor +{ + GENERATED_BODY() + +public: + ALobbySpawn(); + + virtual void BeginPlay() override; + + UPROPERTY(EditAnywhere, Category = "Gamestate Components") + int32 assignedTeam; + + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Display) + class UStaticMeshComponent* displayMesh; + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Display) + class UArrowComponent* displayArrow; +}; diff --git a/Source/UnrealProject/Spawners/PatrolSpawn.cpp b/Source/UnrealProject/Spawners/PatrolSpawn.cpp new file mode 100644 index 0000000..22853d0 --- /dev/null +++ b/Source/UnrealProject/Spawners/PatrolSpawn.cpp @@ -0,0 +1,259 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "PatrolSpawn.h" +#include "NPCBase.h" +#include "DefaultGameMode.h" +#include "NetworkPlayer.h" + +APatrolSpawn::APatrolSpawn() +{ + respawnTime = 5; + m_respawnTimer = 0; + + spawnContinuous = true; +} + +void APatrolSpawn::BeginPlay() +{ + Super::BeginPlay(); + if (Role != ROLE_Authority) + return; + FName name; + + if (aggroRadius < 0) aggroRadius = 0; + if (respawnTime < 0) respawnTime = 0; + + if (spawns.Num() == 0) + { + JWARNING("Empty spawner in scene"); + return; + } + + for (int i = 0; i < controlPoints.Num(); i++) + m_controlPoints.Add(FVector(controlPoints[i].X + this->GetActorLocation().X, controlPoints[i].Y + this->GetActorLocation().Y, this->GetActorLocation().Z)); + + m_controlPointSign = 1; + m_isPulled = false; + + m_currentControlPoint = 0; + m_lastPosition = GetActorLocation(); + + //SpawnMobs(); +} + +void APatrolSpawn::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + Super::EndPlay(EndPlayReason); + +} + +void APatrolSpawn::Tick(float DeltaTime) +{ + Super::Tick(DeltaTime); + + + + if (Role != ROLE_Authority) + return; + + UWorld* const world = GetWorld(); + if (!world) return; + + check(m_mobs.Num() == spawns.Num()); + + m_respawnTimer = m_respawnTimer > 0 ? (m_respawnTimer - DeltaTime) : 0; + if (m_respawnTimer <= 0 && m_mobCount != m_mobs.Num() && spawnContinuous) + m_RespawnMobs(); + if (m_mobs.Num() == 0) + return; + // Respawn the mobs + /* if (spawnContinuous) + m_RespawnMobs();*/ + if (m_controlPoints.Num() <= 0) + { + RWARNING("There are no control points"); + return; + } + + bool anyIdle = false; + for (int32 i = 0; i < spawns.Num(); i++) + { + if (!spawns[i]) + continue; + + if (i == 0 && m_mobs[i] != nullptr) + { + if (m_isPulled == false) + { + RPRINT("testing"); + m_lastPosition = m_mobs[i]->GetActorLocation(); + } + else if (!m_mobs[i]->target) + { + const FVector2D mobPos = FVector2D(m_mobs[i]->GetActorLocation().X, m_mobs[i]->GetActorLocation().Y); + const float distSqr = FVector2D::DistSquared(mobPos, FVector2D(m_lastPosition.X, m_lastPosition.Y)); + if (distSqr < (m_mobs[i]->collisionRadius*m_mobs[i]->collisionRadius)*collisionScaler) + m_isPulled = false; + } + + + // Any of the mobs not doing anything? + const FVector2D mobPos = FVector2D(m_mobs[i]->GetActorLocation().X, m_mobs[i]->GetActorLocation().Y); + const float distSqr = FVector2D::DistSquared(mobPos, FVector2D(m_lastPosition.X, m_lastPosition.Y)); + + ////// Regeneration (hardcoded, remove in future) + if (!m_mobs[i]->target && distSqr < aggroRadius*aggroRadius) + { + anyIdle = true; + } + } + } + + if (m_mobs[0] != nullptr && m_isPulled) + { + m_mobs[0]->SetControlPoint(m_lastPosition); + for (int i = 1; i < m_mobs.Num(); i++) + { + m_mobs[i]->SetControlPoint(m_lastPosition); + } + } + ADefaultGameMode* const mode = Cast(world->GetAuthGameMode()); + if (mode) + { + FVector controlLocation = GetActorLocation(); + // check if there are some controll points + if (m_mobs[0] != nullptr && !m_isPulled) + { + if (FVector2D::DistSquared(FVector2D(m_mobs[0]->GetActorLocation().X, m_mobs[0]->GetActorLocation().Y), FVector2D(m_controlPoints[m_currentControlPoint].X, m_controlPoints[m_currentControlPoint].Y)) < 10000) + { + m_currentControlPoint += m_controlPointSign; + if (m_currentControlPoint >= m_controlPoints.Num() || m_currentControlPoint < 0) + { + m_controlPointSign *= -1; + m_currentControlPoint += m_controlPointSign; + if (isConnected) + { + m_currentControlPoint = 0; + m_controlPointSign *= -1; + } + } + } + // set new control location + controlLocation = m_controlPoints[m_currentControlPoint]; + + m_mobs[0]->SetControlPoint(controlLocation); + if (m_mobs.Num() > 1) + { + + for (int i = 1; i < m_mobs.Num(); i++) + { + m_mobs[i]->SetControlPoint(m_lastPosition); + } + } + } + + if (anyIdle && !m_isPulled) + { + /*// Find the closest player (in aggroRadius) + ANetworkCharacter* closest = nullptr; + float closestDist = BIG_NUMBER; + TArray players = mode->GetPlayers(); + const float aggroSqr = aggroRadius * aggroRadius; + for (int32 i = 0; i < players.Num(); i++) + { + const float distSqr = FVector::DistSquared(m_mobs[0]->GetActorLocation(), players[i]->GetActorLocation()); + if (distSqr < closestDist && distSqr <= aggroSqr) + { + closest = players[i]; + closestDist = distSqr; + } + }*/ + ANetworkPlayer* closest = m_GetClosestPlayer(); + if (closest) + { + // Attack the closest player + for (int32 i = 0; i < spawns.Num(); i++) + { + if (m_mobs[i] == nullptr) + continue; + + m_mobs[i]->target = (closest); + m_isPulled = true; + } + } + } + + } +} + +FVector APatrolSpawn::SpawnResetPosition() +{ + return m_lastPosition; +} + +void APatrolSpawn::SpawnMobs() +{ + m_RespawnMobs(); +} +void APatrolSpawn::m_RespawnMobs() +{ + UWorld* const world = GetWorld(); + if (!world) + return; + for (int32 i = 0; i < m_mobs.Num(); i++) + { + if (!spawns[i]) + continue; + if (m_mobs[i] == nullptr) + { + // Respawn! + FTransform spawnTransform = GetTransform(); + if (hasGeneral) + { + spawnTransform.SetLocation(FVector(formationPoints[i].X, formationPoints[i].Y,0) + GetActorLocation()); + } + else + { + spawnTransform.SetLocation(GetActorLocation() + FVector(0, 0, 120)); + } + spawnTransform.SetRotation(FRotator(GetActorRotation() + FRotator(0, formationRotation[i], 0)).Quaternion()); + ANPCBase* character = world->SpawnActorDeferred(spawns[i], spawnTransform, nullptr, nullptr, ESpawnActorCollisionHandlingMethod()); + character->SetSpawn(this); + character->SetTeam((int32)team); + m_mobs[i] = character; + m_mobCount++; + m_OnMobSpawn(i); + UGameplayStatics::FinishSpawningActor(character, spawnTransform); + character->SpawnDefaultController(); + } + } + + m_respawnTimer = respawnTime; +} +class ANetworkPlayer* APatrolSpawn::m_GetClosestPlayer() +{ + UWorld* const world = GetWorld(); + if (!world) + return nullptr; + ADefaultGameMode* const mode = Cast(world->GetAuthGameMode()); + if (!mode) + return nullptr; + + // Find the closest player (in aggroRadius) + ANetworkPlayer* closest = nullptr; + float closestDist = BIG_NUMBER; + TArray players = mode->GetPlayers(); + const float aggroSqr = aggroRadius * aggroRadius; + for (int32 i = 0; i < players.Num(); i++) + { + const float distSqr = FVector2D::DistSquared(FVector2D(m_mobs[0]->GetActorLocation()), FVector2D(players[i]->GetActorLocation())); + if (distSqr < closestDist && distSqr <= aggroSqr) + { + closest = players[i]; + closestDist = distSqr; + } + } + + return closest; +} \ No newline at end of file diff --git a/Source/UnrealProject/Spawners/PatrolSpawn.h b/Source/UnrealProject/Spawners/PatrolSpawn.h new file mode 100644 index 0000000..856c16d --- /dev/null +++ b/Source/UnrealProject/Spawners/PatrolSpawn.h @@ -0,0 +1,39 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "SpawnerBase.h" +#include "PatrolSpawn.generated.h" + +/** + * + */ +UCLASS() +class UNREALPROJECT_API APatrolSpawn : public ASpawnerBase +{ + GENERATED_BODY() + +public: + APatrolSpawn(); + virtual void BeginPlay() override; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + virtual void Tick(float DeltaTime) override; + + virtual FVector SpawnResetPosition() override; + UPROPERTY(EditAnywhere) + float respawnTime; + UPROPERTY(EditAnywhere, Category = "Gamestate Components") + bool spawnContinuous; + void SpawnMobs(); + +private: + float m_respawnTimer; + TArray m_controlPoints; + int32 m_controlPointSign; + int32 m_currentControlPoint; + TArray m_respawnTimers; + FVector m_lastPosition; + bool m_isPulled; + void m_RespawnMobs(); + class ANetworkPlayer* m_GetClosestPlayer(); +}; diff --git a/Source/UnrealProject/Spawners/PlayerSpawn.cpp b/Source/UnrealProject/Spawners/PlayerSpawn.cpp new file mode 100644 index 0000000..90f7631 --- /dev/null +++ b/Source/UnrealProject/Spawners/PlayerSpawn.cpp @@ -0,0 +1,26 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "DefaultGameMode.h" +#include "PlayerSpawn.h" + + +APlayerSpawn::APlayerSpawn() +{ + displayMesh = CreateDefaultSubobject(TEXT("Mesh")); + displayMesh->bHiddenInGame = true; + displayMesh->bGenerateOverlapEvents = false; + displayMesh->SetCollisionEnabled(ECollisionEnabled::NoCollision); + displayArrow = CreateDefaultSubobject(TEXT("Arrow")); + assignedTeam = 0; +} + +void APlayerSpawn::BeginPlay() +{ + if (Role != ROLE_Authority) + return; + + ADefaultGameMode* mode = Cast(GetWorld()->GetAuthGameMode()); + if (mode) + mode->RegisterPlayerSpawn(*this); +} \ No newline at end of file diff --git a/Source/UnrealProject/Spawners/PlayerSpawn.h b/Source/UnrealProject/Spawners/PlayerSpawn.h new file mode 100644 index 0000000..f2acb74 --- /dev/null +++ b/Source/UnrealProject/Spawners/PlayerSpawn.h @@ -0,0 +1,25 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "GameFramework/Actor.h" +#include "PlayerSpawn.generated.h" + +UCLASS() +class UNREALPROJECT_API APlayerSpawn : public AActor +{ + GENERATED_BODY() + +public: + APlayerSpawn(); + virtual void BeginPlay() override; + + UPROPERTY(EditAnywhere, Category = "Gamestate Components") + int32 assignedTeam; + + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Display) + class UStaticMeshComponent* displayMesh; + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Display) + class UArrowComponent* displayArrow; + +}; diff --git a/Source/UnrealProject/Spawners/SpawnerBase.cpp b/Source/UnrealProject/Spawners/SpawnerBase.cpp new file mode 100644 index 0000000..331820a --- /dev/null +++ b/Source/UnrealProject/Spawners/SpawnerBase.cpp @@ -0,0 +1,476 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" +#include "NetworkSwitchComponent.h" +#include "CreatureSpawnComponent.h" +#include "DefaultGameMode.h" +#include "SpawnerBase.h" +#include "NPCBase.h" +#include "NetworkCharacter.h" +#include "NetworkPlayer.h" +#include "EnemyBase.h" +#include "GeneralEnemy.h" +#include "MiniBossCreature.h" + + +ASpawnerBase::ASpawnerBase() +{ + PrimaryActorTick.bCanEverTick = true; + + USceneComponent* spawnerRoot = CreateDefaultSubobject("Spawner Root"); + RootComponent = spawnerRoot; + + displayMesh = CreateDefaultSubobject(TEXT("Mesh")); + displayMesh->bHiddenInGame = true; + displayMesh->bGenerateOverlapEvents = false; + displayMesh->SetCollisionEnabled(ECollisionEnabled::NoCollision); + displayMesh->AttachTo(RootComponent); + + // Kill particle effect + killParticle = CreateDefaultSubobject(TEXT("Kill Particle1")); + killParticle->AttachTo(RootComponent); + + displayArrow = CreateDefaultSubobject(TEXT("Arrow")); + displayArrow->AttachTo(displayMesh); + + displayDoors = CreateDefaultSubobject(TEXT("Visualizer")); + displayDoors->AttachTo(displayMesh); + + visualizerComponent = CreateDefaultSubobject(TEXT("Control points visualizer")); + + visualizerComponent->AttachTo(displayMesh); + + team = NPCTeam::Team1; + aggroRadius = 800; + deaggroRadius = aggroRadius; + m_mobCount = 0; + formationScale = 1.0f; + formationDistance = 100; + formationRadius = 100; + resetTimer = 30; + dropKeyFragmentIndex = 0; + hasDroppedKey = false; + + bReplicates = true; + bAlwaysRelevant = true; +} + +void ASpawnerBase::BeginPlay() +{ + Super::BeginPlay(); + + TArray test; + GetAttachedActors(test); + for (int i = 0; i < test.Num(); i++) + { + + if (Cast(test[i])) + { + spawns.Add(test[i]->GetClass()); + formationPoints.Add(FVector2D(test[i]->GetActorLocation() - GetActorLocation())); + formationRotation.Add(test[i]->GetActorRotation().Yaw); + //RPRINT("mobs" + spawns[i]->GetName()); + if (Role == ROLE_Authority) + { + Cast(test[i])->UnsetSpawn(); + } + (test[i])->Destroy(); + (test[i]) = nullptr; + } + else + { + SpawnLocation.Add(test[i]); + + } + + } + for (int32 i = 0; i < spawns.Num(); i++) + m_mobs.Add(nullptr); + + m_threatMap.SetNum(m_mobs.Num()); + for (int i = 0; i < spawns.Num(); i++) + { + if (i != 0 && spawns[i]->IsChildOf(AGeneralEnemy::StaticClass())) + { + Swap(spawns[i], spawns[0]); + Swap(formationPoints[i], formationPoints[0]); + Swap(formationRotation[i], formationRotation[0]); + } + } + TArray hitResult; + GetWorld()->LineTraceMultiByChannel(hitResult, GetActorLocation(), GetActorLocation() + GetActorUpVector()*-10000.0f, ECollisionChannel::ECC_WorldStatic, FCollisionQueryParams(FName(L"spawnerPlacementTrace"),true)); + bool hit = false; + float distance = BIG_NUMBER; + FVector impactLocation; + for (int i = 0; i < hitResult.Num(); i++) + { + if (FVector::Dist(FVector(hitResult[i].ImpactPoint), GetActorLocation()) < distance&&FVector(hitResult[i].ImpactPoint)!= GetActorLocation()) + { + distance = FVector::Dist(FVector(hitResult[i].ImpactPoint), GetActorLocation()); + impactLocation = FVector(hitResult[i].ImpactPoint); + hit = true; + } + } + + // Correct spawner position when it can move to the ground below it + if (hit) + { + this->SetActorLocation(FVector(GetActorLocation().X, GetActorLocation().Y, impactLocation.Z)); + } + + for (int i = 0; i < formationPoints.Num(); i++) + { + formationPoints[i] = FVector2D(GetActorRotation().RotateVector(FVector(formationPoints[i].X, formationPoints[i].Y, 0))); + formationPoints[i] *= formationScale; + } + if (spawns.Num() > formationPoints.Num()) + { + int difference = spawns.Num() - formationPoints.Num(); + for (int i = 0; i < difference; i++) + { + formationPoints.Add(FVector2D()); + } + } + if (spawns.Num() > formationRotation.Num()) + { + int difference = spawns.Num() - formationRotation.Num(); + for (int i = 0; i < difference; i++) + { + formationRotation.Add(0); + } + } + +} + +void ASpawnerBase::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + Super::EndPlay(EndPlayReason); + + if (Role != ROLE_Authority) + return; + + for (int32 i = 0; i < m_mobs.Num(); i++) + { + if (m_mobs[i]) + { + m_mobs[i]->UnsetSpawn(); + m_mobs[i] = nullptr; + } + } + formationEnemies.Empty(); + m_nearbyPlayers.clear(); +} + +void ASpawnerBase::Tick(float DeltaTime) +{ + Super::Tick(DeltaTime); +// if (formationEnemies.Num() > spawns.Num()) +// formationEnemies.Empty(); + bool reset = false; + m_resetTimer -= DeltaTime; + if (m_resetTimer <= 0) + { + reset = true; + } + for (int i = 0; i < m_mobs.Num(); i++) + { + if (m_mobs[i] == nullptr) + continue; + if (reset) + { + ForceResetTarget(m_mobs[i]); + } + if (m_mobs[i]->IsA(AEnemyBase::StaticClass())) + { + Cast(m_mobs[i])->hasGeneral = hasGeneral; + } + if (m_mobs[i]->IsA(AGeneralEnemy::StaticClass())) + { + AGeneralEnemy* asGeneral = Cast(m_mobs[i]); + if (formationEnemies != asGeneral->formationEnemies) + { + asGeneral->formationEnemies = formationEnemies; + } + } + } +} + +int32 ASpawnerBase::OnMobDie(class ANPCBase* mob) +{ + check(m_mobCount > 0); + check(Role == ROLE_Authority); + + int32 idx = -1; + for (int32 i = 0; i < spawns.Num(); i++) + { + if (m_mobs[i] == mob) + { + if (m_mobs[i]->IsA(AGeneralEnemy::StaticClass())) + { + formationEnemies.Empty(); + } + if (m_mobs[i]->IsA(AEnemyBase::StaticClass())) + { + AEnemyBase* asEnemyBase = Cast(m_mobs[i]); + if (asEnemyBase->hasGeneral) + formationEnemies.Remove(asEnemyBase); + } + + m_mobCount--; + + if(m_mobCount == 0) + { + OnCampCleared(); + } + + m_mobs[i] = nullptr; + + idx = i; + + m_threatMap[i].clear(); + + break; + } + } + + check(idx >= 0); + return idx; +} + +void ASpawnerBase::OnCampCleared_Implementation() +{ + if(killParticle->Template) + { + killParticle->ResetParticles(); + killParticle->ActivateSystem(); + } + m_OnCampCleared(); +} + +int32 ASpawnerBase::GetAliveMobCount() +{ + return m_mobCount; +} + +FVector ASpawnerBase::SpawnResetPosition() +{ + return GetActorLocation(); +} + +void ASpawnerBase::m_OnPlayerEnterOverlap(class ANetworkCharacter& player) +{ + auto find = m_nearbyPlayers.find(&player); + if (find != m_nearbyPlayers.end()) + return; + + ANetworkPlayer* playerCast = Cast(&player); + if (IsValid(playerCast)) + { + playerCast->AddThreatLevel_Client(threatLevel); + } + + // Player was succesfully added + m_nearbyPlayers.emplace(&player); + + //adding the threat to all mobs + +/* for (int32 i = 0; i < m_mobs.Num(); i++) + { + if (m_mobs[i] == nullptr) + continue; + // auto ret = m_threatMap[i].emplace(&player, 0); + if (!ret.second) + { + FPRINT("failed to add player to creaturemap "); + } + }*/ +} +void ASpawnerBase::m_OnPlayerExitOverlap(class ANetworkCharacter& player) +{ + auto find = m_nearbyPlayers.find(&player); + if (find == m_nearbyPlayers.end()) + { + FERROR("leaving player was not found in the nearbyPlayers"); + return; + } + + ANetworkPlayer* playerCast = Cast(&player); + if (IsValid(playerCast)) + { + playerCast->RemoveThreatLevel_Client(threatLevel); + } + + // Player was succesfully removed + m_nearbyPlayers.erase(find); +} + +class ANetworkPlayer* ASpawnerBase::m_GetBiggestThreat(int32 index) +{ + ANetworkPlayer* ret = nullptr; + int32 biggestThreat = -1; + for (auto cpair : m_threatMap[index]) + { + if (cpair.second > biggestThreat) + { + biggestThreat = cpair.second; + ret = cpair.first; + } + } + return ret; +} +void ASpawnerBase::m_OnMobSpawn(int32 index) +{ + ANPCBase* creature = m_mobs[index]; + if (creature == nullptr) + { + FWARNING("creature is null at spawn"); + return; + } + if (creature->IsA(AGeneralEnemy::StaticClass())) + { + AGeneralEnemy* asGeneral = Cast(creature); + asGeneral->formationPoints = formationPoints; + } + else + { + if (creature->IsA(AEnemyBase::StaticClass())) + formationEnemies.Add(Cast(creature)); + } + m_threatMap[index].clear(); + +/* for (ANetworkPlayer* player : m_nearbyPlayers) + { + auto ret = m_threatMap[index].insert(std::pair(player, 0)); + if (!ret.second) + { + FPRINT("adding failed"); + } + }*/ + + if (creature->IsA(AMiniBossCreature::StaticClass()) && dropKeyFragmentIndex >= keyFragmentMin && dropKeyFragmentIndex <= keyFragmentMax) + Cast(creature)->keyDropped = PlayerKeyType(dropKeyFragmentIndex); +} + +class ANetworkPlayer* ASpawnerBase::GetClosestPlayer() +{ + if (m_nearbyPlayers.empty()) + return nullptr; + UWorld* const world = GetWorld(); + if (!world) + return nullptr; + ADefaultGameMode* const mode = Cast(world->GetAuthGameMode()); + if (!mode) + return nullptr; + + // Find the closest player (in aggroRadius) + ANetworkPlayer* closest = nullptr; + float closestDist = BIG_NUMBER; + const float aggroSqr = aggroRadius * aggroRadius; + for (ANetworkCharacter* character :m_nearbyPlayers) + { + + ANetworkPlayer *player = Cast(character); + if (player) + { + + // ANetworkPlayer* player = Cast(*iter); + const float distSqr = FVector2D::DistSquared(FVector2D(this->GetActorLocation()), FVector2D(player->GetActorLocation())); + if (distSqr < closestDist && distSqr <= aggroSqr) + { + closest = player; + closestDist = distSqr; + } + } + } + + return closest; +} + +void ASpawnerBase::AddThreat(class ANPCBase* creature, class ANetworkPlayer* character, int32 threat) +{ + if (creature == nullptr || creature->IsPendingKill() || character == nullptr || character->IsPendingKill()) + return; + //find the creaturemap + + int32 index = 0; + bool found = false; + for (ANPCBase* mob : m_mobs) + { + if (creature == mob) + { + found = true; + break; + } + index++; + } + if (!found) + { + FERROR("creature not part of threatMap"); + return; + } + + //find the player + map::iterator it = m_threatMap[index].find(character); + + if (it == m_threatMap[index].end()) + { + FERROR("character not part of creatureMap"); + return; + } + it->second += threat; +} + +void ASpawnerBase::GetNewTarget(class ANPCBase* mob) +{ + return; +} +void ASpawnerBase::ForceResetTarget(class ANPCBase* mob) +{ + mob->target = nullptr; +} +void ASpawnerBase::ForceSetTarget(class ANPCBase* mob,class ANetworkPlayer* target) +{ + mob->target = target; +} +void ASpawnerBase::SetTeam(int newTeam) +{ + team = (NPCTeam)newTeam; + + for (int i = 0; i < m_mobs.Num(); i++) + { + if (!IsValid(m_mobs[i])) + continue; + + m_mobs[i]->SetTeam(newTeam); + + if (!m_mobs[i]->target) + continue; + if(m_mobs[i]->target->GetTeam() == newTeam) + m_mobs[i]->target = nullptr; + } +} +void ASpawnerBase::CaptureCamp(int team) +{ + SetTeam(team); +} + +TArray ASpawnerBase::GetNearbyPlayers() +{ + TArray players; + for(ANetworkCharacter* character : m_nearbyPlayers) + { + ANetworkPlayer* player = Cast(character); + if(player) + { + players.Add(player); + } + } + return players; +} + +void ASpawnerBase::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(ASpawnerBase, team); +} \ No newline at end of file diff --git a/Source/UnrealProject/Spawners/SpawnerBase.h b/Source/UnrealProject/Spawners/SpawnerBase.h new file mode 100644 index 0000000..f326fc5 --- /dev/null +++ b/Source/UnrealProject/Spawners/SpawnerBase.h @@ -0,0 +1,126 @@ +// Project Lab - NHTV Igad + +#pragma once + +#include "GameFramework/Actor.h" +#include +#include +using std::unordered_set; +using std::map; +#include "SpawnerBase.generated.h" + +UENUM(BlueprintType) +enum class NPCTeam : uint8 +{ + Team1 = 50, +}; + +UCLASS() +class ASpawnerBase : public AActor +{ + GENERATED_BODY() + +public: + ASpawnerBase(); + + virtual void BeginPlay() override; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + virtual void Tick(float DeltaTime) override; + virtual void GetNewTarget(class ANPCBase* mob); + UFUNCTION(BlueprintCallable, Category="Capture") + virtual void CaptureCamp(int team); + void ForceSetTarget(class ANPCBase* mob,class ANetworkPlayer* target); + void ForceResetTarget(class ANPCBase* mob); + void SetTeam(int team); + class ANetworkPlayer* GetClosestPlayer(); + // Call when the camp is killed entirely + UFUNCTION(NetMulticast, Reliable) + void OnCampCleared(); + + void AddThreat(class ANPCBase* creature, class ANetworkPlayer* character, int32 threat); + + virtual int32 OnMobDie(class ANPCBase* mob); + + virtual FVector SpawnResetPosition(); + + int32 GetAliveMobCount(); + + // m_nearbyPlayers but as ANetworkPlayer array instead of ANetworkCharacter's + TArray GetNearbyPlayers(); + + UPROPERTY(EditAnywhere) + float aggroRadius; + UPROPERTY(EditAnywhere) + float deaggroRadius; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "FMod") + float threatLevel; + + UPROPERTY(EditAnywhere, Replicated) + NPCTeam team; + + UPROPERTY(EditAnywhere) + FColor debugColorCode; + + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Display) + class UStaticMeshComponent* displayMesh; + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Display) + class UArrowComponent* displayArrow; + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Display) + class UNetworkSwitchComponent* displayDoors; + + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Kill Particle") + UParticleSystemComponent* killParticle; + + UPROPERTY(EditAnywhere, BlueprintReadWrite) + TArray> spawns; + UPROPERTY(EditAnywhere, Category = "Spawn Settings") + float drawingRadius; + UPROPERTY(EditAnywhere, Category = "Spawn Settings") + float collisionScaler; + UPROPERTY(EditAnywhere, Category = "Spawn Settings") + float resetTimer; + UPROPERTY(EditAnywhere, Category = "Spawn Settings") + bool possesable; + UPROPERTY(EditAnywhere, Category = "Spawn Settings") + TArray SpawnLocation; + + bool isConnected; + + UPROPERTY(EditAnywhere, Category = "Switch Components") + class UCreatureSpawnComponent* visualizerComponent; + UPROPERTY(EditAnywhere, Category = "ControlPoints Settings") + TArray controlPoints; + UPROPERTY(EditAnywhere, Category = "Formation Settings") + float formationDistance; + UPROPERTY(EditAnywhere, Category = "Formation Settings") + float formationRadius; + TArray formationPoints; + + TArray formationRotation; + UPROPERTY(EditAnywhere, Category = "Formation Settings") + float formationScale; + TArray formationEnemies; + bool hasGeneral; + + UPROPERTY(EditAnywhere, Category = "Miniboss") + int32 dropKeyFragmentIndex; + bool hasDroppedKey; + TArray m_mobs; +protected: + virtual void m_OnCampCleared() {}; + virtual void m_OnPlayerEnterOverlap(class ANetworkCharacter& player); + virtual void m_OnPlayerExitOverlap(class ANetworkCharacter& player); + class ANetworkPlayer* m_GetBiggestThreat(int32 index); + + + + void m_OnMobSpawn(int32 index); + + int32 m_mobCount; + + unordered_set m_nearbyPlayers; + TArray> m_threatMap; + float m_resetTimer; + //float formationDistance = 0; + //float formationRadius = 0; +}; \ No newline at end of file diff --git a/Source/UnrealProject/UnrealProject.Build.cs b/Source/UnrealProject/UnrealProject.Build.cs new file mode 100644 index 0000000..c733d1b --- /dev/null +++ b/Source/UnrealProject/UnrealProject.Build.cs @@ -0,0 +1,68 @@ +// Project Lab - NHTV Igad + +using UnrealBuildTool; +using System.IO; +using System; + +public class UnrealProject : ModuleRules +{ + private string ModulePath + { + get { return Path.GetDirectoryName(RulesCompiler.GetModuleFilename(this.GetType().Name)); } + } + + private string RootPath + { + get { return Path.GetFullPath(Path.Combine(ModulePath, "..\\..\\")); } + } + private string ThirdPartyPath + { + get { return Path.GetFullPath(Path.Combine(ModulePath, "..\\..\\ThirdParty\\")); } + } + + private string FilterName(string name) + { + return "..\\Source\\UnrealProject\\" + name + "\\"; + } + + public UnrealProject(TargetInfo Target) + { + MinFilesUsingPrecompiledHeaderOverride = 1; + bFasterWithoutUnity = true; + + PublicDependencyModuleNames.AddRange(new string[] { + "Core", "CoreUObject", "Engine", "InputCore", + "UMG", "Slate", "SlateCore", + "OnlineSubsystem", "OnlineSubsystemUtils", + "MoviePlayer", "MediaAssets", "Blutility" }); + + // Add steam, external library and metrics for PC + if(Target.Platform == UnrealTargetPlatform.Win64) + { + // Matchmaking + //PublicIncludePaths.Add(RootPath + "MatchMaking\\"); + //PublicAdditionalLibraries.Add(RootPath + "MatchMaking\\MatchMakingClient_x64_Release.lib"); + + DynamicallyLoadedModuleNames.Add("OnlineSubsystemSteam"); + PublicDependencyModuleNames.Add("Steamworks"); + PrivateDependencyModuleNames.Add("Networking"); + } + + // Add steam, external library and metrics for PC + if (Target.Platform == UnrealTargetPlatform.Mac) + { + DynamicallyLoadedModuleNames.Add("OnlineSubsystemSteam"); + PublicDependencyModuleNames.Add("Steamworks"); + } + DynamicallyLoadedModuleNames.Add("OnlineSubsystemNull"); + + // Fetch all subdirectories, and add them as filters + string[] directories = Directory.GetDirectories(ModuleDirectory, "*", SearchOption.AllDirectories); + for (int i = 0; i < directories.Length; i++) + { + string dir = directories[i]; + string subdir = dir.Substring(ModuleDirectory.Length + 1, dir.Length - ModuleDirectory.Length - 1); + PublicIncludePaths.Add(FilterName(subdir)); + } + } +} diff --git a/Source/UnrealProject/UnrealProject.cpp b/Source/UnrealProject/UnrealProject.cpp new file mode 100644 index 0000000..23c508f --- /dev/null +++ b/Source/UnrealProject/UnrealProject.cpp @@ -0,0 +1,77 @@ +// Project Lab - NHTV Igad + +#include "UnrealProject.h" + + +IMPLEMENT_PRIMARY_GAME_MODULE( FDefaultGameModuleImpl, UnrealProject, "UnrealProject" ); + +DEFINE_LOG_CATEGORY(FRANK); +DEFINE_LOG_CATEGORY(JAN); +DEFINE_LOG_CATEGORY(TED); +DEFINE_LOG_CATEGORY(YOSHI); +DEFINE_LOG_CATEGORY(GUUS); +DEFINE_LOG_CATEGORY(RAUL); + +#include +using namespace std; + +void Output::WriteOut(char name, char type, const string& message) +{ + WriteWOut(wchar_t(name), wchar_t(type), (convert_cstring_type(message.data()))); +} +void Output::WriteWOut(wchar_t name, wchar_t type, const wstring& message) +{ + // Retrieve the line number and file name + wstring content = message; + const size_t end = content.find(L']'); + if (end == wstring::npos) + return; + + wstring path_content = content.substr(0, end + 1); + const size_t seperator = path_content.find_last_of(L':'); + wstring path = path_content.substr(1, seperator - 1); + replace(path.begin(), path.end(), L'\\', L'/'); + const size_t slash = path.find_last_of(L'/'); + if (slash != wstring::npos) path = path.substr(slash + 1, path.size()); + wstring line = path_content.substr(seperator + 1, end - seperator - 1); + + content = L"(" + path + L":" + line + L") " + content.substr(end + 1, content.size()); + + // Log to the proper output + if (type == L'L') + { + switch (name) + { + case L'F': UE_LOG(FRANK, Log, TEXT("%s"), content.data()); break; + case L'J': UE_LOG(JAN, Log, TEXT("%s"), content.data()); break; + case L'T': UE_LOG(TED, Log, TEXT("%s"), content.data()); break; + case L'Y': UE_LOG(YOSHI, Log, TEXT("%s"), content.data()); break; + case L'G': UE_LOG(GUUS, Log, TEXT("%s"), content.data()); break; + case L'R': UE_LOG(RAUL, Log, TEXT("%s"), content.data()); break; + } + } + if (type == L'W') + { + switch (name) + { + case L'F': UE_LOG(FRANK, Warning, TEXT("%s"), content.data()); break; + case L'J': UE_LOG(JAN, Warning, TEXT("%s"), content.data()); break; + case L'T': UE_LOG(TED, Warning, TEXT("%s"), content.data()); break; + case L'Y': UE_LOG(YOSHI, Warning, TEXT("%s"), content.data()); break; + case L'G': UE_LOG(GUUS, Warning, TEXT("%s"), content.data()); break; + case L'R': UE_LOG(RAUL, Warning, TEXT("%s"), content.data()); break; + } + } + if (type == L'E') + { + switch (name) + { + case L'F': UE_LOG(FRANK, Error, TEXT("%s"), content.data()); break; + case L'J': UE_LOG(JAN, Error, TEXT("%s"), content.data()); break; + case L'T': UE_LOG(TED, Error, TEXT("%s"), content.data()); break; + case L'Y': UE_LOG(YOSHI, Error, TEXT("%s"), content.data()); break; + case L'G': UE_LOG(GUUS, Error, TEXT("%s"), content.data()); break; + case L'R': UE_LOG(RAUL, Error, TEXT("%s"), content.data()); break; + } + } +} \ No newline at end of file diff --git a/Source/UnrealProject/UnrealProject.h b/Source/UnrealProject/UnrealProject.h new file mode 100644 index 0000000..ff20fb4 --- /dev/null +++ b/Source/UnrealProject/UnrealProject.h @@ -0,0 +1,73 @@ +// Project Lab - NHTV Igad + +#ifndef __UNREALPROJECT_H__ +#define __UNREALPROJECT_H__ + +#include "EngineMinimal.h" +#include +#include "Metrics.hpp" +#include "Runtime/UMG/Public/UMG.h" +#include "Runtime/UMG/Public/UMGStyle.h" +#include "Runtime/UMG/Public/Slate/SObjectWidget.h" +#include "Runtime/UMG/Public/IUMGModule.h" +#include "Runtime/UMG/Public/Blueprint/UserWidget.h" +#include "UnrealNetwork.h" + +#define PLATFORM_SPECIFIC_WIN (PLATFORM_PS4 + PLATFORM_MAC + PLATFORM_APPLE) + +DECLARE_LOG_CATEGORY_EXTERN(FRANK, Log, All); +DECLARE_LOG_CATEGORY_EXTERN(JAN, Log, All); +DECLARE_LOG_CATEGORY_EXTERN(TED, Log, All); +DECLARE_LOG_CATEGORY_EXTERN(YOSHI, Log, All); +DECLARE_LOG_CATEGORY_EXTERN(GUUS, Log, All); +DECLARE_LOG_CATEGORY_EXTERN(RAUL, Log, All); + +namespace Output +{ + void WriteOut(char name, char type, const std::string& message); + void WriteWOut(wchar_t name, wchar_t type, const std::wstring& message); +} + +#define _WRITE_OUT(name, type, msg) Output::WriteOut(name, type, (std::string() + "[" + __FILE__ + ":" + __LINE__ + "]" + msg)) +#define _WWRITE_OUT(name, type, msg) Output::WriteWOut(name, type, (std::wstring() + L"[" + (convert_cstring_type(__FILE__)) + L":" + __LINE__ + L"]" + msg)) + +#define FPRINT(msg) _WRITE_OUT('F', 'L', msg) +#define FWPRINT(msg) _WWRITE_OUT(L'F', L'L', msg) +#define JPRINT(msg) _WRITE_OUT('J', 'L', msg) +#define JWPRINT(msg) _WWRITE_OUT(L'J', L'L', msg) +#define TPRINT(msg) _WRITE_OUT('T', 'L', msg) +#define TWPRINT(msg) _WWRITE_OUT(L'T', L'L', msg) +#define YPRINT(msg) _WRITE_OUT('Y', 'L', msg) +#define YWPRINT(msg) _WWRITE_OUT(L'Y', L'L', msg) +#define GPRINT(msg) _WRITE_OUT('G', 'L', msg) +#define GWPRINT(msg) _WWRITE_OUT(L'G', L'L', msg) +#define RPRINT(msg) _WRITE_OUT('R', 'L', msg) +#define RWPRINT(msg) _WWRITE_OUT(L'R', L'L', msg) + +#define FWARNING(msg) _WRITE_OUT('F', 'W', msg) +#define FWWARNING(msg) _WWRITE_OUT(L'F', L'W', msg) +#define JWARNING(msg) _WRITE_OUT('J', 'W', msg) +#define JWWARNING(msg) _WWRITE_OUT(L'J', L'W', msg) +#define TWARNING(msg) _WRITE_OUT('T', 'W', msg) +#define TWWARNING(msg) _WWRITE_OUT(L'T', L'W', msg) +#define YWARNING(msg) _WRITE_OUT('Y', 'W', msg) +#define YWWARNING(msg) _WWRITE_OUT(L'Y', L'W', msg) +#define GWARNING(msg) _WRITE_OUT('G', 'W', msg) +#define GWWARNING(msg) _WWRITE_OUT(L'G', L'W', msg) +#define RWARNING(msg) _WRITE_OUT('R', 'W', msg) +#define RWWARNING(msg) _WWRITE_OUT(L'R', L'W', msg) + +#define FERROR(msg) _WRITE_OUT('F', 'E', msg) +#define FWERROR(msg) _WWRITE_OUT(L'F', L'E', msg) +#define JERROR(msg) _WRITE_OUT('J', 'E', msg) +#define JWERROR(msg) _WWRITE_OUT(L'J', L'E', msg) +#define TERROR(msg) _WRITE_OUT('T', 'E', msg) +#define TWERROR(msg) _WWRITE_OUT(L'T', L'E', msg) +#define YERROR(msg) _WRITE_OUT('Y', 'E', msg) +#define YWERROR(msg) _WWRITE_OUT(L'Y', L'E', msg) +#define GERROR(msg) _WRITE_OUT('G', 'E', msg) +#define GWERROR(msg) _WWRITE_OUT(L'G', L'E', msg) +#define RERROR(msg) _WRITE_OUT('R', 'E', msg) +#define RWERROR(msg) _WWRITE_OUT(L'R', L'E', msg) + +#endif diff --git a/Source/UnrealProjectDev.Target.cs b/Source/UnrealProjectDev.Target.cs new file mode 100644 index 0000000..98eb3ca --- /dev/null +++ b/Source/UnrealProjectDev.Target.cs @@ -0,0 +1,22 @@ +// Project Lab - NHTV Igad + +using UnrealBuildTool; +using System.Collections.Generic; + +public class UnrealProjectDevTarget : TargetRules +{ + public UnrealProjectDevTarget(TargetInfo Target) + { + Type = TargetType.Game; + } + + + public override void SetupBinaries( + TargetInfo Target, + ref List OutBuildBinaryConfigurations, + ref List OutExtraModuleNames + ) + { + OutExtraModuleNames.Add("UnrealProject"); + } +} diff --git a/Source/UnrealProjectEditor.Target.cs b/Source/UnrealProjectEditor.Target.cs new file mode 100644 index 0000000..8284f59 --- /dev/null +++ b/Source/UnrealProjectEditor.Target.cs @@ -0,0 +1,25 @@ +// Project Lab - NHTV Igad + +using UnrealBuildTool; +using System.Collections.Generic; + +public class UnrealProjectEditorTarget : TargetRules +{ + public UnrealProjectEditorTarget(TargetInfo Target) + { + Type = TargetType.Editor; + } + + // + // TargetRules interface. + // + + public override void SetupBinaries( + TargetInfo Target, + ref List OutBuildBinaryConfigurations, + ref List OutExtraModuleNames + ) + { + OutExtraModuleNames.Add("UnrealProject"); + } +} diff --git a/UnrealProjectDev.uproject b/UnrealProjectDev.uproject new file mode 100644 index 0000000..c8b8134 --- /dev/null +++ b/UnrealProjectDev.uproject @@ -0,0 +1,29 @@ +{ + "FileVersion": 3, + "EngineAssociation": "4.11", + "Category": "", + "Description": "", + "Modules": [ + { + "Name": "UnrealProject", + "Type": "Runtime", + "LoadingPhase": "Default", + "AdditionalDependencies": [ + "Engine", + "UMG", + "Slate", + "SlateCore", + "CoreUObject", + "SkillTree", + "AIModule" + ] + } + ], + "Plugins": [ + { + "Name": "FMODStudio", + "Enabled": true, + "LoadingPhase": "PreDefault" + } + ] +} \ No newline at end of file diff --git a/cursor.cur b/cursor.cur new file mode 100644 index 0000000..89777d6 Binary files /dev/null and b/cursor.cur differ