diff --git a/Source/ALSV4_CPP/ALSV4_CPP.Build.cs b/Source/ALSV4_CPP/ALSV4_CPP.Build.cs index 90a5d820..9501562c 100644 --- a/Source/ALSV4_CPP/ALSV4_CPP.Build.cs +++ b/Source/ALSV4_CPP/ALSV4_CPP.Build.cs @@ -1,5 +1,4 @@ -// Copyright: Copyright (C) 2022 Doğa Can Yanıkoğlu -// Source Code: https://github.com/dyanikoglu/ALS-Community +// Copyright Epic Games, Inc. All Rights Reserved. using UnrealBuildTool; @@ -7,12 +6,27 @@ public class ALSV4_CPP : ModuleRules { public ALSV4_CPP(ReadOnlyTargetRules Target) : base(Target) { - PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; + PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; - PublicDependencyModuleNames.AddRange(new[] - {"Core", "CoreUObject", "Engine", "InputCore", "NavigationSystem", "AIModule", "GameplayTasks","PhysicsCore", "Niagara", "EnhancedInput" - }); + PublicIncludePaths.AddRange( + new string[] { + "ALSV4_CPP/Public", + "ALSV4_CPP/Public/Character", + "ALSV4_CPP/Public/Weapon", + "ALSV4_CPP/Public/Components", + "ALSV4_CPP/Public/AI", + "ALSV4_CPP/Public/Library" + } + ); - PrivateDependencyModuleNames.AddRange(new[] {"Slate", "SlateCore"}); + PublicDependencyModuleNames.AddRange( + new string[] { + "Core", + "CoreUObject", + "Engine", + "InputCore", + "EnhancedInput" + } + ); } -} \ No newline at end of file +} diff --git a/Source/ALSV4_CPP/Private/Character/HealthComponent.cpp b/Source/ALSV4_CPP/Private/Character/HealthComponent.cpp new file mode 100644 index 00000000..f04675bf --- /dev/null +++ b/Source/ALSV4_CPP/Private/Character/HealthComponent.cpp @@ -0,0 +1,128 @@ +// Copyright: Copyright (C) 2024 Combat System +// Source Code: https://github.com/Klebold2009/ALS-Community + +#include "Character/HealthComponent.h" +#include "Net/UnrealNetwork.h" +#include "GameFramework/Character.h" +#include "Particles/ParticleSystem.h" +#include "Kismet/GameplayStatics.h" + +UHealthComponent::UHealthComponent() +{ + PrimaryComponentTick.bCanEverTick = false; + bReplicateUsingRegisteredSubObjectList = true; + SetIsReplicatedEnabled(true); + + MaxHealth = 100.0f; + CurrentHealth = 100.0f; + bIsDead = false; + BloodEffectRadius = 100.0f; +} + +void UHealthComponent::BeginPlay() +{ + Super::BeginPlay(); + + if (GetOwnerRole() == ROLE_Authority) + { + CurrentHealth = MaxHealth; + } +} + +void UHealthComponent::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + FDOReplicateVariable(UHealthComponent, CurrentHealth); +} + +void UHealthComponent::TakeDamage(float DamageAmount, AActor* DamageInstigator) +{ + if (GetOwnerRole() == ROLE_AutonomousProxy) + { + Server_TakeDamage(DamageAmount, DamageInstigator); + } +} + +void UHealthComponent::Server_TakeDamage_Implementation(float DamageAmount, AActor* DamageInstigator) +{ + if (bIsDead) + { + return; + } + + CurrentHealth = FMath::Max(0.0f, CurrentHealth - DamageAmount); + + Multicast_OnDamaged(DamageAmount, GetOwner()->GetActorLocation()); + + OnHealthChanged.Broadcast(CurrentHealth, MaxHealth, DamageInstigator); + + if (CurrentHealth <= 0.0f) + { + Server_Die(DamageInstigator); + } +} + +void UHealthComponent::Multicast_OnDamaged_Implementation(float DamageAmount, FVector DamageLocation) +{ + SpawnBloodEffect(DamageLocation); +} + +void UHealthComponent::Heal(float HealAmount) +{ + if (GetOwnerRole() == ROLE_AutonomousProxy) + { + Server_Heal(HealAmount); + } +} + +void UHealthComponent::Server_Heal_Implementation(float HealAmount) +{ + if (bIsDead) + { + return; + } + + CurrentHealth = FMath::Min(MaxHealth, CurrentHealth + HealAmount); + OnHealthChanged.Broadcast(CurrentHealth, MaxHealth, nullptr); +} + +void UHealthComponent::Die(AActor* KillerActor) +{ + if (GetOwnerRole() == ROLE_AutonomousProxy) + { + Server_Die(KillerActor); + } +} + +void UHealthComponent::Server_Die_Implementation(AActor* KillerActor) +{ + if (bIsDead) + { + return; + } + + bIsDead = true; + CurrentHealth = 0.0f; + + Multicast_Die(KillerActor); +} + +void UHealthComponent::Multicast_Die_Implementation(AActor* KillerActor) +{ + ACharacter* OwnerCharacter = Cast(GetOwner()); + if (OwnerCharacter) + { + OwnerCharacter->GetCharacterMovement()->DisableMovement(); + OwnerCharacter->SetActorEnableCollision(false); + } + + OnDeath.Broadcast(GetOwner(), KillerActor); +} + +void UHealthComponent::SpawnBloodEffect(FVector Location) +{ + if (BloodFX) + { + UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), BloodFX, Location); + } +} diff --git a/Source/ALSV4_CPP/Private/Character/WeaponCharacter.cpp b/Source/ALSV4_CPP/Private/Character/WeaponCharacter.cpp new file mode 100644 index 00000000..87f70374 --- /dev/null +++ b/Source/ALSV4_CPP/Private/Character/WeaponCharacter.cpp @@ -0,0 +1,103 @@ +// Copyright: Copyright (C) 2024 Combat System +// Source Code: https://github.com/Klebold2009/ALS-Community + +#include "Character/WeaponCharacter.h" +#include "Character/HealthComponent.h" +#include "Weapon/BaseWeapon.h" +#include "InputActionValue.h" +#include "EnhancedInputComponent.h" +#include "EnhancedInputSubsystems.h" +#include "Net/UnrealNetwork.h" + +AWeaponCharacter::AWeaponCharacter() +{ + PrimaryActorTick.TickInterval = 0.016f; + PrimaryActorTick.bCanEverTick = true; + + // Create health component + HealthComponent = CreateDefaultSubobject(TEXT("HealthComponent")); + WeaponAttachSocket = FName("weapon_r"); + bIsInCombat = false; +} + +void AWeaponCharacter::BeginPlay() +{ + Super::BeginPlay(); +} + +void AWeaponCharacter::Tick(float DeltaTime) +{ + Super::Tick(DeltaTime); +} + +void AWeaponCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) +{ + Super::SetupPlayerInputComponent(PlayerInputComponent); +} + +void AWeaponCharacter::EquipWeapon(ABaseWeapon* NewWeapon) +{ + if (GetOwnerRole() != ROLE_Authority) + { + return; + } + + if (CurrentWeapon) + { + UnequipWeapon(); + } + + CurrentWeapon = NewWeapon; + + if (CurrentWeapon) + { + CurrentWeapon->SetOwner(this); + CurrentWeapon->AttachToComponent(GetMesh(), FAttachmentTransformRules::KeepRelativeTransform, WeaponAttachSocket); + + FWeaponData WeaponData; + CurrentWeapon->InitializeWeapon(this, WeaponData); + } +} + +void AWeaponCharacter::UnequipWeapon() +{ + if (CurrentWeapon) + { + CurrentWeapon->Destroy(); + CurrentWeapon = nullptr; + } +} + +void AWeaponCharacter::FireWeapon() +{ + if (CurrentWeapon && HealthComponent && HealthComponent->IsAlive()) + { + CurrentWeapon->StartFiring(); + } +} + +void AWeaponCharacter::StopFireWeapon() +{ + if (CurrentWeapon) + { + CurrentWeapon->StopFiring(); + } +} + +void AWeaponCharacter::ReloadWeapon() +{ + if (CurrentWeapon) + { + CurrentWeapon->Reload(); + } +} + +float AWeaponCharacter::TakeDamage(float Damage, const FDamageEvent& DamageEvent, AController* EventInstigator, AActor* DamageCauser) +{ + if (HealthComponent) + { + HealthComponent->Server_TakeDamage(Damage, DamageCauser); + } + + return Damage; +} diff --git a/Source/ALSV4_CPP/Private/Weapon/BaseWeapon.cpp b/Source/ALSV4_CPP/Private/Weapon/BaseWeapon.cpp new file mode 100644 index 00000000..b1ffe775 --- /dev/null +++ b/Source/ALSV4_CPP/Private/Weapon/BaseWeapon.cpp @@ -0,0 +1,241 @@ +// Copyright: Copyright (C) 2024 Combat System +// Source Code: https://github.com/Klebold2009/ALS-Community + +#include "Weapon/BaseWeapon.h" +#include "Character/ALSBaseCharacter.h" +#include "Character/WeaponCharacter.h" +#include "Character/HealthComponent.h" +#include "Kismet/GameplayStatics.h" +#include "Engine/World.h" +#include "Net/UnrealNetwork.h" +#include "GameFramework/CharacterMovementComponent.h" + +ABaseWeapon::ABaseWeapon() +{ + PrimaryActorTick.TickInterval = 0.016f; + PrimaryActorTick.bCanEverTick = true; + bReplicates = true; + bReplicateMovement = true; + + // Create weapon mesh + WeaponMesh = CreateDefaultSubobject(TEXT("WeaponMesh")); + WeaponMesh->SetCollisionEnabled(ECC_QueryOnly); + RootComponent = WeaponMesh; + + WeaponSocketName = FName("weapon_r"); + CurrentAmmo = 0; + StoredAmmo = 0; + bIsFiring = false; + bIsReloading = false; + LastFireTime = 0.0f; + ReloadEndTime = 0.0f; +} + +void ABaseWeapon::BeginPlay() +{ + Super::BeginPlay(); + if (GetOwnerRole() == ROLE_Authority) + { + CurrentAmmo = WeaponData.AmmoCapacity; + StoredAmmo = WeaponData.MaxAmmo - WeaponData.AmmoCapacity; + } +} + +void ABaseWeapon::Tick(float DeltaTime) +{ + Super::Tick(DeltaTime); + + if (GetOwnerRole() == ROLE_Authority) + { + if (bIsReloading && GetWorld()->TimeSeconds >= ReloadEndTime) + { + OnReloadComplete(); + } + } +} + +void ABaseWeapon::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + FDOReplicateVariable(ABaseWeapon, WeaponData); + FDOReplicateVariable(ABaseWeapon, CurrentAmmo); + FDOReplicateVariable(ABaseWeapon, StoredAmmo); + FDOReplicateVariable(ABaseWeapon, bIsReloading); + FDOReplicateVariable(ABaseWeapon, OwnerCharacter); +} + +void ABaseWeapon::InitializeWeapon(AALSBaseCharacter* InOwnerCharacter, FWeaponData InWeaponData) +{ + if (GetOwnerRole() != ROLE_Authority) + { + return; + } + + OwnerCharacter = InOwnerCharacter; + WeaponData = InWeaponData; + CurrentAmmo = WeaponData.AmmoCapacity; + StoredAmmo = WeaponData.MaxAmmo - WeaponData.AmmoCapacity; +} + +void ABaseWeapon::StartFiring() +{ + if (GetOwnerRole() == ROLE_AutonomousProxy) + { + Server_Fire(); + } + + bIsFiring = true; +} + +void ABaseWeapon::StopFiring() +{ + bIsFiring = false; +} + +void ABaseWeapon::Server_Fire_Implementation() +{ + if (!OwnerCharacter || !OwnerCharacter->IsValid()) + { + return; + } + + if (GetWorld()->TimeSeconds - LastFireTime < WeaponData.FireRate) + { + return; + } + + if (CurrentAmmo <= 0) + { + return; + } + + if (bIsReloading) + { + return; + } + + LastFireTime = GetWorld()->TimeSeconds; + CurrentAmmo--; + + FVector FireLocation = OwnerCharacter->GetActorLocation() + OwnerCharacter->GetActorForwardVector() * 100.0f; + FRotator FireRotation = OwnerCharacter->GetActorRotation(); + + Multicast_Fire(FireLocation, FireRotation); + + OnAmmoChanged.Broadcast(CurrentAmmo, WeaponData.MaxAmmo); +} + +void ABaseWeapon::Multicast_Fire_Implementation(FVector FireLocation, FRotator FireRotation) +{ + PlayFireAnimation(); + + for (int32 i = 0; i < WeaponData.BulletsPerShot; ++i) + { + FRotator SpreadRotation = FireRotation; + if (WeaponData.Spread > 0.0f) + { + SpreadRotation.Pitch += FMath::RandRange(-WeaponData.Spread, WeaponData.Spread); + SpreadRotation.Yaw += FMath::RandRange(-WeaponData.Spread, WeaponData.Spread); + } + + FVector TraceStart = FireLocation; + FVector TraceEnd = TraceStart + SpreadRotation.Vector() * WeaponData.Range; + + FHitResult HitResult; + FCollisionQueryParams QueryParams; + QueryParams.AddIgnoredActor(OwnerCharacter); + QueryParams.AddIgnoredActor(this); + + bool bHit = GetWorld()->LineTraceSingleByChannel(HitResult, TraceStart, TraceEnd, ECC_Pawn, QueryParams); + + if (bHit && HitResult.GetActor()) + { + if (GetOwnerRole() == ROLE_Authority) + { + ApplyDamage(HitResult.GetActor(), HitResult.ImpactPoint, SpreadRotation.Vector()); + } + } + } + + OnWeaponFired.Broadcast(); +} + +void ABaseWeapon::Reload() +{ + if (GetOwnerRole() == ROLE_AutonomousProxy) + { + Server_Reload(); + } +} + +void ABaseWeapon::Server_Reload_Implementation() +{ + if (bIsReloading || CurrentAmmo == WeaponData.AmmoCapacity) + { + return; + } + + bIsReloading = true; + ReloadEndTime = GetWorld()->TimeSeconds + WeaponData.ReloadTime; + + Multicast_Reload(); +} + +void ABaseWeapon::Multicast_Reload_Implementation() +{ + PlayReloadAnimation(); +} + +void ABaseWeapon::OnReloadComplete() +{ + if (GetOwnerRole() != ROLE_Authority) + { + return; + } + + int32 AmmoNeeded = WeaponData.AmmoCapacity - CurrentAmmo; + int32 AmmoToLoad = FMath::Min(AmmoNeeded, StoredAmmo); + + CurrentAmmo += AmmoToLoad; + StoredAmmo -= AmmoToLoad; + bIsReloading = false; + + OnWeaponReloaded.Broadcast(); + OnAmmoChanged.Broadcast(CurrentAmmo, WeaponData.MaxAmmo); +} + +void ABaseWeapon::ApplyDamage(AActor* HitActor, FVector HitLocation, FVector FireDirection) +{ + if (!HitActor || !OwnerCharacter) + { + return; + } + + UHealthComponent* TargetHealth = HitActor->FindComponentByClass(); + if (TargetHealth) + { + TargetHealth->Server_TakeDamage(WeaponData.Damage, OwnerCharacter); + } + else + { + FDamageEvent DamageEvent; + HitActor->TakeDamage(WeaponData.Damage, DamageEvent, OwnerCharacter->GetController(), OwnerCharacter); + } +} + +void ABaseWeapon::PlayFireAnimation() +{ + if (WeaponMesh) + { + // Play fire montage if available + } +} + +void ABaseWeapon::PlayReloadAnimation() +{ + if (WeaponMesh) + { + // Play reload montage if available + } +} diff --git a/Source/ALSV4_CPP/Private/Weapon/WeaponTypes.cpp b/Source/ALSV4_CPP/Private/Weapon/WeaponTypes.cpp new file mode 100644 index 00000000..f1874b49 --- /dev/null +++ b/Source/ALSV4_CPP/Private/Weapon/WeaponTypes.cpp @@ -0,0 +1,40 @@ +// Copyright: Copyright (C) 2024 Combat System +// Source Code: https://github.com/Klebold2009/ALS-Community + +#include "Weapon/WeaponTypes.h" + +AM9Pistol::AM9Pistol() +{ + WeaponData.WeaponType = TEXT("Pistol"); + WeaponData.Damage = 20.0f; + WeaponData.FireRate = 0.15f; + WeaponData.AmmoCapacity = 15; + WeaponData.MaxAmmo = 120; + WeaponData.ReloadTime = 1.5f; + WeaponData.Range = 5000.0f; + WeaponData.Spread = 3.0f; + WeaponData.BulletsPerShot = 1; +} + +void AM9Pistol::BeginPlay() +{ + Super::BeginPlay(); +} + +AM4A1Rifle::AM4A1Rifle() +{ + WeaponData.WeaponType = TEXT("Rifle"); + WeaponData.Damage = 35.0f; + WeaponData.FireRate = 0.1f; + WeaponData.AmmoCapacity = 30; + WeaponData.MaxAmmo = 300; + WeaponData.ReloadTime = 2.5f; + WeaponData.Range = 10000.0f; + WeaponData.Spread = 2.0f; + WeaponData.BulletsPerShot = 1; +} + +void AM4A1Rifle::BeginPlay() +{ + Super::BeginPlay(); +} diff --git a/Source/ALSV4_CPP/Public/Character/HealthComponent.h b/Source/ALSV4_CPP/Public/Character/HealthComponent.h new file mode 100644 index 00000000..126caf5c --- /dev/null +++ b/Source/ALSV4_CPP/Public/Character/HealthComponent.h @@ -0,0 +1,92 @@ +// Copyright: Copyright (C) 2024 Combat System +// Source Code: https://github.com/Klebold2009/ALS-Community + +#pragma once + +#include "CoreMinimal.h" +#include "Components/ActorComponent.h" +#include "HealthComponent.generated.h" + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FHealthChangedSignature, float, NewHealth, float, MaxHealth, AActor*, DamageInstigator); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FDeathSignature, AActor*, DeadActor, AActor*, KillerActor); + +/** + * Health component for characters - handles damage and death + */ +UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent)) +class ALSV4_CPP_API UHealthComponent : public UActorComponent +{ + GENERATED_BODY() + +public: + UHealthComponent(); + + virtual void BeginPlay() override; + virtual void GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const override; + + // Damage handling + UFUNCTION(BlueprintCallable, Category = "Health") + void TakeDamage(float DamageAmount, AActor* DamageInstigator = nullptr); + + UFUNCTION(Server, Reliable) + void Server_TakeDamage(float DamageAmount, AActor* DamageInstigator); + + UFUNCTION(NetMulticast, Reliable) + void Multicast_OnDamaged(float DamageAmount, FVector DamageLocation); + + // Healing + UFUNCTION(BlueprintCallable, Category = "Health") + void Heal(float HealAmount); + + UFUNCTION(Server, Reliable) + void Server_Heal(float HealAmount); + + // Health queries + UFUNCTION(BlueprintCallable, Category = "Health") + float GetHealth() const { return CurrentHealth; } + + UFUNCTION(BlueprintCallable, Category = "Health") + float GetMaxHealth() const { return MaxHealth; } + + UFUNCTION(BlueprintCallable, Category = "Health") + float GetHealthPercentage() const { return MaxHealth > 0.0f ? CurrentHealth / MaxHealth : 0.0f; } + + UFUNCTION(BlueprintCallable, Category = "Health") + bool IsAlive() const { return CurrentHealth > 0.0f; } + + // Death handling + UFUNCTION(BlueprintCallable, Category = "Health") + void Die(AActor* KillerActor = nullptr); + + UFUNCTION(Server, Reliable) + void Server_Die(AActor* KillerActor); + + UFUNCTION(NetMulticast, Reliable) + void Multicast_Die(AActor* KillerActor); + + // Delegates + UPROPERTY(BlueprintAssignable, Category = "Health") + FHealthChangedSignature OnHealthChanged; + + UPROPERTY(BlueprintAssignable, Category = "Health") + FDeathSignature OnDeath; + +protected: + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Health") + float MaxHealth; + + UPROPERTY(Replicated, BlueprintReadOnly, Category = "Health") + float CurrentHealth; + + UPROPERTY(BlueprintReadOnly, Category = "Health") + bool bIsDead; + + // Damage effects + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Health|Effects") + float BloodEffectRadius; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Health|Effects") + class UParticleSystem* BloodFX; + + void SpawnBloodEffect(FVector Location); +}; diff --git a/Source/ALSV4_CPP/Public/Character/WeaponCharacter.h b/Source/ALSV4_CPP/Public/Character/WeaponCharacter.h new file mode 100644 index 00000000..1959b2d1 --- /dev/null +++ b/Source/ALSV4_CPP/Public/Character/WeaponCharacter.h @@ -0,0 +1,72 @@ +// Copyright: Copyright (C) 2024 Combat System +// Source Code: https://github.com/Klebold2009/ALS-Community + +#pragma once + +#include "CoreMinimal.h" +#include "Character/ALSCharacter.h" +#include "Weapon/WeaponDataStruct.h" +#include "WeaponCharacter.generated.h" + +class ABaseWeapon; +class UHealthComponent; + +/** + * Character class with weapon and combat capabilities + */ +UCLASS() +class ALSV4_CPP_API AWeaponCharacter : public AALSCharacter +{ + GENERATED_BODY() + +public: + AWeaponCharacter(); + + virtual void BeginPlay() override; + virtual void Tick(float DeltaTime) override; + virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override; + + // Weapon management + UFUNCTION(BlueprintCallable, Category = "Combat") + void EquipWeapon(ABaseWeapon* NewWeapon); + + UFUNCTION(BlueprintCallable, Category = "Combat") + ABaseWeapon* GetCurrentWeapon() const { return CurrentWeapon; } + + UFUNCTION(BlueprintCallable, Category = "Combat") + void UnequipWeapon(); + + // Combat actions - input callbacks + UFUNCTION(BlueprintCallable, Category = "Combat") + void FireWeapon(); + + UFUNCTION(BlueprintCallable, Category = "Combat") + void StopFireWeapon(); + + UFUNCTION(BlueprintCallable, Category = "Combat") + void ReloadWeapon(); + + // Health component access + UFUNCTION(BlueprintCallable, Category = "Combat") + UHealthComponent* GetHealthComponent() const { return HealthComponent; } + +protected: + // Health component + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Combat") + UHealthComponent* HealthComponent; + + // Current equipped weapon + UPROPERTY(Replicated, BlueprintReadOnly, Category = "Combat") + ABaseWeapon* CurrentWeapon; + + // Weapon attachment socket + UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Combat") + FName WeaponAttachSocket; + + // Combat state + UPROPERTY(BlueprintReadOnly, Category = "Combat") + bool bIsInCombat; + + // Take damage override + virtual float TakeDamage(float Damage, const FDamageEvent& DamageEvent, AController* EventInstigator, AActor* DamageCauser) override; +}; diff --git a/Source/ALSV4_CPP/Public/Weapon/BaseWeapon.h b/Source/ALSV4_CPP/Public/Weapon/BaseWeapon.h new file mode 100644 index 00000000..c5e03b47 --- /dev/null +++ b/Source/ALSV4_CPP/Public/Weapon/BaseWeapon.h @@ -0,0 +1,131 @@ +// Copyright: Copyright (C) 2024 Combat System +// Source Code: https://github.com/Klebold2009/ALS-Community + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/Actor.h" +#include "WeaponDataStruct.h" +#include "BaseWeapon.generated.h" + +class AALSBaseCharacter; + +DECLARE_DYNAMIC_MULTICAST_DELEGATE(FWeaponFiredSignature); +DECLARE_DYNAMIC_MULTICAST_DELEGATE(FWeaponReloadedSignature); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FAmmoChangedSignature, int32, CurrentAmmo, int32, MaxAmmo); + +/** + * Base weapon class for all firearms + */ +UCLASS() +class ALSV4_CPP_API ABaseWeapon : public AActor +{ + GENERATED_BODY() + +public: + ABaseWeapon(); + + virtual void BeginPlay() override; + virtual void Tick(float DeltaTime) override; + virtual void GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const override; + + // Weapon initialization + UFUNCTION(BlueprintCallable, Category = "Weapon") + void InitializeWeapon(AALSBaseCharacter* OwnerCharacter, FWeaponData InWeaponData); + + // Firing + UFUNCTION(BlueprintCallable, Category = "Weapon") + void StartFiring(); + + UFUNCTION(BlueprintCallable, Category = "Weapon") + void StopFiring(); + + UFUNCTION(Server, Reliable) + void Server_Fire(); + + UFUNCTION(NetMulticast, Reliable) + void Multicast_Fire(FVector FireLocation, FRotator FireRotation); + + // Reloading + UFUNCTION(BlueprintCallable, Category = "Weapon") + void Reload(); + + UFUNCTION(Server, Reliable) + void Server_Reload(); + + UFUNCTION(NetMulticast, Reliable) + void Multicast_Reload(); + + // Ammunition + UFUNCTION(BlueprintCallable, Category = "Weapon") + int32 GetCurrentAmmo() const { return CurrentAmmo; } + + UFUNCTION(BlueprintCallable, Category = "Weapon") + int32 GetMaxAmmo() const { return WeaponData.MaxAmmo; } + + UFUNCTION(BlueprintCallable, Category = "Weapon") + int32 GetAmmoCapacity() const { return WeaponData.AmmoCapacity; } + + UFUNCTION(BlueprintCallable, Category = "Weapon") + bool HasAmmo() const { return CurrentAmmo > 0; } + + // Weapon data + UFUNCTION(BlueprintCallable, Category = "Weapon") + FWeaponData GetWeaponData() const { return WeaponData; } + + // Delegates + UPROPERTY(BlueprintAssignable, Category = "Weapon") + FWeaponFiredSignature OnWeaponFired; + + UPROPERTY(BlueprintAssignable, Category = "Weapon") + FWeaponReloadedSignature OnWeaponReloaded; + + UPROPERTY(BlueprintAssignable, Category = "Weapon") + FAmmoChangedSignature OnAmmoChanged; + +protected: + // Weapon skeletal mesh + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Weapon") + class USkeletalMeshComponent* WeaponMesh; + + // Owner character + UPROPERTY(Replicated, BlueprintReadOnly, Category = "Weapon") + AALSBaseCharacter* OwnerCharacter; + + // Weapon data + UPROPERTY(Replicated, BlueprintReadOnly, Category = "Weapon") + FWeaponData WeaponData; + + // Ammunition + UPROPERTY(Replicated, BlueprintReadOnly, Category = "Weapon") + int32 CurrentAmmo; + + UPROPERTY(Replicated, BlueprintReadOnly, Category = "Weapon") + int32 StoredAmmo; + + // Firing state + UPROPERTY(BlueprintReadOnly, Category = "Weapon") + bool bIsFiring; + + UPROPERTY(Replicated, BlueprintReadOnly, Category = "Weapon") + bool bIsReloading; + + float LastFireTime; + float ReloadEndTime; + + // Weapon socket names for attachment + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon") + FName WeaponSocketName; + + // Fire trace parameters + void PerformLineTrace(FVector& OutHitLocation, AActor*& OutHitActor); + + // Damage application + void ApplyDamage(AActor* HitActor, FVector HitLocation, FVector FireDirection); + + // Animation and effects + virtual void PlayFireAnimation(); + virtual void PlayReloadAnimation(); + + void OnReloadComplete(); +}; diff --git a/Source/ALSV4_CPP/Public/Weapon/WeaponDataStruct.h b/Source/ALSV4_CPP/Public/Weapon/WeaponDataStruct.h new file mode 100644 index 00000000..e5a12390 --- /dev/null +++ b/Source/ALSV4_CPP/Public/Weapon/WeaponDataStruct.h @@ -0,0 +1,50 @@ +// Copyright: Copyright (C) 2024 Combat System +// Source Code: https://github.com/Klebold2009/ALS-Community + +#pragma once + +#include "CoreMinimal.h" +#include "Engine/DataTable.h" +#include "WeaponDataStruct.generated.h" + +/** + * Weapon configuration structure + */ +USTRUCT(BlueprintType) +struct FWeaponData : public FTableRowBase +{ + GENERATED_BODY() + + /* Basic weapon stats */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon") + float Damage = 25.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon") + float FireRate = 0.1f; // Time between shots + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon") + int32 AmmoCapacity = 30; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon") + int32 MaxAmmo = 300; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon") + float ReloadTime = 2.5f; + + /* Ballistics */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon") + float MuzzleVelocity = 900.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon") + float Range = 10000.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon") + float Spread = 2.0f; // Bullet spread in degrees + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon") + int32 BulletsPerShot = 1; + + /* Weapon type identifier */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon") + FString WeaponType = TEXT("Rifle"); +}; diff --git a/Source/ALSV4_CPP/Public/Weapon/WeaponTypes.h b/Source/ALSV4_CPP/Public/Weapon/WeaponTypes.h new file mode 100644 index 00000000..a2b36e99 --- /dev/null +++ b/Source/ALSV4_CPP/Public/Weapon/WeaponTypes.h @@ -0,0 +1,36 @@ +// Copyright: Copyright (C) 2024 Combat System +// Source Code: https://github.com/Klebold2009/ALS-Community + +#pragma once + +#include "CoreMinimal.h" +#include "Weapon/BaseWeapon.h" +#include "WeaponTypes.generated.h" + +/** + * M9 Pistol + */ +UCLASS() +class ALSV4_CPP_API AM9Pistol : public ABaseWeapon +{ + GENERATED_BODY() + +public: + AM9Pistol(); + + virtual void BeginPlay() override; +}; + +/** + * M4A1 Assault Rifle + */ +UCLASS() +class ALSV4_CPP_API AM4A1Rifle : public ABaseWeapon +{ + GENERATED_BODY() + +public: + AM4A1Rifle(); + + virtual void BeginPlay() override; +};