Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // Copyright 2015 Dirt Productions. All rights reserved.
- #pragma once
- #include "DistantHome.h"
- #include "Public/Player/DHCharacterMovementComponent.h"
- #include "Public/Player/DHCharacter.h"
- #include "Public/Gameplay/Utility/DHUtility.h"
- // Sets default values
- ADHCharacter::ADHCharacter(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer.SetDefaultSubobjectClass<UDHCharacterMovementComponent>(ACharacter::CharacterMovementComponentName))
- {
- // Set player HP to defaults
- PlayerData.HP.MaxHitPoints, PlayerData.HP.CurrentHitPoints = 100.0f;
- // Create first person camera
- FirstPersonCamera = ObjectInitializer.CreateDefaultSubobject<UCameraComponent>(this, TEXT("First Person Camera"));
- FirstPersonCamera->AttachParent = GetCapsuleComponent();
- // Position the camera above BaseEyeHeight
- FirstPersonCamera->RelativeLocation = FVector(0.0f, 0.0f, 64.0f + BaseEyeHeight);
- // Allow pawn to control rotation
- FirstPersonCamera->bUsePawnControlRotation = true;
- // Create orbit view camera and attach to sprint arm
- OrbitViewCamera = ObjectInitializer.CreateDefaultSubobject<UCameraComponent>(this, TEXT("Orbit View Camera"));
- OrbitSpringArm = ObjectInitializer.CreateDefaultSubobject<USpringArmComponent>(this, TEXT("Orbit Spring Arm"));
- OrbitViewCamera->AttachParent = OrbitSpringArm;
- OrbitSpringArm->AttachParent = GetCapsuleComponent();
- // Position camera
- OrbitSpringArm->TargetArmLength = 300.0f;
- // Allow pawn to control rotation
- OrbitSpringArm->bUsePawnControlRotation = true;
- // Update camera view
- UpdateCameraView();
- // Set this character to call Tick() every frame
- PrimaryActorTick.bCanEverTick = true;
- // Allow pawn to control rotation
- GetCharacterMovement()->bUseControllerDesiredRotation = true;
- // Set rate of view (view speed) of character
- ViewRate = 45.0f;
- // Set movement speeds
- WalkingSpeed = 500.0f;
- AdditiveSprintingSpeed = 400.0f;
- }
- // Called when the game starts or when spawned
- void ADHCharacter::BeginPlay()
- {
- Super::BeginPlay();
- bSprintOnNextTick = false;
- if (GEngine)
- {
- GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Blue, TEXT("We are using DHCharacter!"));
- }
- // Spawn weapons
- /*
- MakeWeapon(PlayerData.Loadout.PrimaryWeapon, EWeaponSlot::PrimaryWeapon);
- MakeWeapon(PlayerData.Loadout.SecondaryWeapon, EWeaponSlot::SecondaryWeapon);
- MakeWeapon(PlayerData.Loadout.MeleeWeapon, EWeaponSlot::MeleeWeapon);
- MakeWeapon(PlayerData.Loadout.SpecialWeapon, EWeaponSlot::SpecialWeapon);
- */
- }
- // Called every frame
- void ADHCharacter::Tick(float DeltaTime)
- {
- Super::Tick(DeltaTime);
- // Update is airborne
- if (GetCharacterMovement()->IsFalling()) bIsAirborne = true;
- // Update is moving
- if (GetCharacterMovement()->IsMovingOnGround() || GetCharacterMovement()->IsFalling() || bIsAirborne) bIsMoving = true;
- // Call OnStartSprinting() if bSprintOnNextTick == true
- if (bSprintOnNextTick)
- {
- OnStartSprinting();
- bSprintOnNextTick = false;
- }
- }
- // Called to bind functionality to input
- void ADHCharacter::SetupPlayerInputComponent(class UInputComponent* InputComponent)
- {
- Super::SetupPlayerInputComponent(InputComponent);
- // Bind axes
- InputComponent->BindAxis("MoveForward", this, &ADHCharacter::MoveForward);
- InputComponent->BindAxis("MoveRight", this, &ADHCharacter::MoveRight);
- InputComponent->BindAxis("Turn", this, &ADHCharacter::Turn);
- InputComponent->BindAxis("LookUp", this, &ADHCharacter::LookUp);
- InputComponent->BindAxis("TurnRate", this, &ADHCharacter::TurnAtRate);
- InputComponent->BindAxis("LookUpRate", this, &ADHCharacter::LookUpAtRate);
- InputComponent->BindAxis("SwitchWeapon", this, &ADHCharacter::OnSwitchWeaponAxis);
- // Bind actions
- InputComponent->BindAction("MoveBackward", IE_Released, this, &ADHCharacter::OnStopMovingBackward);
- InputComponent->BindAction("PrimaryWeapon", IE_Pressed, this, &ADHCharacter::OnSwitchToPrimary);
- InputComponent->BindAction("SecondaryWeapon", IE_Pressed, this, &ADHCharacter::OnSwitchToSecondary);
- InputComponent->BindAction("MeleeWeapon", IE_Pressed, this, &ADHCharacter::OnSwitchToMelee);
- InputComponent->BindAction("SpecialWeapon", IE_Pressed, this, &ADHCharacter::OnSwitchToSpecial);
- InputComponent->BindAction("Jump", IE_Pressed, this, &ADHCharacter::OnStartJumping);
- InputComponent->BindAction("Sprint", IE_Pressed, this, &ADHCharacter::OnStartSprinting);
- InputComponent->BindAction("Sprint", IE_Released, this, &ADHCharacter::OnStopSprinting);
- InputComponent->BindAction("Crouch", IE_Pressed, this, &ADHCharacter::OnStartCrouching);
- InputComponent->BindAction("Crouch", IE_Released, this, &ADHCharacter::OnStopCrouching);
- InputComponent->BindAction("Fire", IE_Pressed, this, &ADHCharacter::OnStartFiring);
- InputComponent->BindAction("Fire", IE_Released, this, &ADHCharacter::OnStopFiring);
- InputComponent->BindAction("Reload", IE_Pressed, this, &ADHCharacter::OnStartReloading);
- InputComponent->BindAction("Reload", IE_Released, this, &ADHCharacter::OnStopReloading);
- }
- // Handles X axis movement
- void ADHCharacter::MoveForward(float Value)
- {
- if (Controller != NULL && Value != 0.0f)
- {
- bIsMoving = true;
- // Determine forward direction
- FRotator Rotation = Controller->GetControlRotation();
- // Zero out pitch and roll
- Rotation.Pitch, Rotation.Roll = 0.0f;
- // Add forward movement
- AddMovementInput(FRotationMatrix(Rotation).GetScaledAxis(EAxis::X), Value);
- if (Value > 0.0f) bIsMovingForward = true;
- else if (Value < 0.0f)
- {
- bIsMovingForward = false;
- OnStopSprinting();
- }
- else bIsMovingForward = false;
- }
- else bIsMoving, bIsMovingForward = false;
- }
- // Handles Y axis movement
- void ADHCharacter::MoveRight(float Value)
- {
- if (Controller != NULL && Value != 0.0f)
- {
- bIsMoving = true;
- // Determine right direction and add right movement
- AddMovementInput(FRotationMatrix(Controller->GetControlRotation()).GetScaledAxis(EAxis::Y), Value);
- }
- else bIsMoving = false;
- }
- // Handles mouse yaw
- void ADHCharacter::Turn(float Value)
- {
- if (Value != 0.0f) AddControllerYawInput(Value * GetWorld()->DeltaTimeSeconds * 15.0f);
- }
- // Handles mouse pitch
- void ADHCharacter::LookUp(float Value)
- {
- if (Value != 0.0f) AddControllerPitchInput(Value * GetWorld()->DeltaTimeSeconds * 15.0f);
- }
- // Handles stick yaw
- void ADHCharacter::TurnAtRate(float Value)
- {
- if (Value != 0.0f) AddControllerYawInput(Value * GetWorld()->DeltaTimeSeconds * ViewRate);
- }
- // Handles stick pitch
- void ADHCharacter::LookUpAtRate(float Value)
- {
- if (Value != 0.0f) AddControllerPitchInput(Value * GetWorld()->DeltaTimeSeconds * ViewRate);
- }
- // Called when MoveBackward released
- void ADHCharacter::OnStopMovingBackward()
- {
- if (bWantsToSprint) bSprintOnNextTick = true;
- }
- // Called when jump pressed
- void ADHCharacter::OnStartJumping()
- {
- if (bIsCrouching) OnStopCrouching();
- Jump();
- }
- // Called when sprint pressed
- void ADHCharacter::OnStartSprinting()
- {
- if (!bIsFiring) bIsSprinting = true;
- }
- // Called when sprint released
- void ADHCharacter::OnStopSprinting()
- {
- bIsSprinting = false;
- }
- // Called when crouch pressed
- void ADHCharacter::OnStartCrouching()
- {
- Crouch();
- bIsCrouching = true;
- }
- // Called when crouch released
- void ADHCharacter::OnStopCrouching()
- {
- UnCrouch();
- bIsCrouching = false;
- }
- // Called when aim pressed
- void ADHCharacter::OnStartAiming()
- {
- if (bIsSprinting) OnStopSprinting();
- bIsAiming = true;
- }
- // Called when aim released
- void ADHCharacter::OnStopAiming()
- {
- if (bWantsToSprint) bSprintOnNextTick = true;
- bIsAiming = false;
- }
- // Called when fire pressed
- void ADHCharacter::OnStartFiring()
- {
- // Set bWantsToFire to true
- bWantsToFire = true;
- // Determines fire capability
- if (CanFire(false, true))
- {
- // Stop sprinting if sprinitng
- if (bIsSprinting) OnStopSprinting();
- // Fire once if already firing
- if (bIsFirstFire) FireWeapon(PlayerData.CurrentWeapon);
- // Set bIsFiring to true and set timer for FireCurrentWeapon()
- bIsFiring = true;
- GetWorldTimerManager().SetTimer(this, &ADHCharacter::FireCurrentWeapon, GetCurrentFireDelay(), true);
- }
- else return;
- }
- // Called when fire released
- void ADHCharacter::OnStopFiring()
- {
- // Set bWantsToFire, bIsFiring to false then clear timer for FireCurrentWeapon()
- bWantsToFire = false;
- bIsFiring = false;
- GetWorldTimerManager().ClearTimer(this, &ADHCharacter::FireCurrentWeapon);
- }
- /**
- * Equips weapon from inventory
- * @param WeaponToEquip : Desired EWeaponSlot Value
- */
- void ADHCharacter::EquipWeapon(EWeaponSlot WeaponToEquip)
- {
- EWeaponSlot PreviousWeapon = PlayerData.CurrentWeapon;
- PlayerData.CurrentWeapon = WeaponToEquip;
- OnWeaponChanged(PreviousWeapon);
- }
- /**
- * Called when weapon changed
- * @param PreviousWeapon : Previous weapon as EWeaponSlot
- */
- void ADHCharacter::OnWeaponChanged(EWeaponSlot PreviousWeapon)
- {
- if (PreviousWeapon != PlayerData.CurrentWeapon)
- {
- return;
- }
- else return;
- }
- /**
- * Reloads weapon
- * @param WeaponToReload : EWeaponSlot value of desired weapon
- */
- void ADHCharacter::ReloadWeapon(EWeaponSlot WeaponToReload)
- {
- if (WeaponToReload == PlayerData.CurrentWeapon)
- {
- // Set is sprinting and stop opposing movement
- if (bIsSprinting) OnStopSprinting();
- if (bIsAiming) OnStopAiming();
- bIsReloading = true;
- // Set ammo data
- if (WeaponToReload == EWeaponSlot::PrimaryWeapon) PlayerData.WeaponInfo.PrimaryWeapon.Reload();
- else if (WeaponToReload == EWeaponSlot::SecondaryWeapon) PlayerData.WeaponInfo.SecondaryWeapon.Reload();
- else if (WeaponToReload == EWeaponSlot::MeleeWeapon) PlayerData.WeaponInfo.MeleeWeapon.Reload();
- else if (WeaponToReload == EWeaponSlot::SpecialWeapon) PlayerData.WeaponInfo.SpecialWeapon.Reload();
- else return;
- // Sprint or aim depending on input
- if (bWantsToSprint) OnStartSprinting();
- if (bWantsToAim) OnStartAiming();
- }
- else return;
- }
- // Called when reload pressed
- void ADHCharacter::OnStartReloading()
- {
- bWantsToReload = true;
- ReloadWeapon(PlayerData.CurrentWeapon);
- }
- // Called when reload released
- void ADHCharacter::OnStopReloading()
- {
- bWantsToReload = false;
- }
- // Calculates bullet spread, fires line trace, then spawns projectile to calculated destination
- void ADHCharacter::FireWeapon(EWeaponSlot WeaponToFire)
- {
- bool bRestoreWeapon = false;
- EWeaponSlot PreviousWeapon = PlayerData.CurrentWeapon;
- if (CanFire(false, true))
- {
- if (WeaponToFire == EWeaponSlot::PrimaryWeapon) PlayerData.WeaponInfo.PrimaryWeapon.DecrementAmmo();
- else if (WeaponToFire == EWeaponSlot::SecondaryWeapon) PlayerData.WeaponInfo.SecondaryWeapon.DecrementAmmo();
- else if (WeaponToFire == EWeaponSlot::MeleeWeapon) PlayerData.WeaponInfo.MeleeWeapon.DecrementAmmo();
- else if (WeaponToFire == EWeaponSlot::SpecialWeapon) PlayerData.WeaponInfo.SpecialWeapon.DecrementAmmo();
- else return;
- if (WeaponToFire == PlayerData.CurrentWeapon) bRestoreWeapon = false;
- else if (WeaponToFire != PlayerData.CurrentWeapon)
- {
- if (WeaponToFire != EWeaponSlot::NoWeapon)
- {
- // Store current weapon
- PreviousWeapon = PlayerData.CurrentWeapon;
- // Equip WeaponToFire
- EquipWeapon(WeaponToFire);
- // Set to restore weapon
- bRestoreWeapon = true;
- }
- else return;
- }
- // Get bullet spread from current weapon
- float BulletSpread = Cast<ADHWeapon>(GetWeaponInstance(WeaponToFire))->GetBulletSpread();
- // Modify bullet spread based on character state
- if (bIsMoving) BulletSpread = BulletSpread * 1.15f;
- if (bIsCrouching) BulletSpread = BulletSpread * 0.95f;
- if (bIsAiming) BulletSpread = BulletSpread * 0.95f;
- // Get perfect destination with 0 bullet spread
- FVector Destination = FRotationMatrix(GetControlRotation()).GetScaledAxis(EAxis::X) * 30000.0f;
- // Add bullet spread to destination
- float NegatedBulletSpread = BulletSpread * -1.0f;
- Destination.X = Destination.X + FMath::FRandRange(NegatedBulletSpread, BulletSpread);
- Destination.Y = Destination.Y + FMath::FRandRange(NegatedBulletSpread, BulletSpread);
- Destination.Z = Destination.Z + FMath::FRandRange(NegatedBulletSpread, BulletSpread);
- if (bRestoreWeapon) EquipWeapon(PreviousWeapon);
- }
- else if (WeaponToFire == PlayerData.CurrentWeapon) ReloadWeapon(WeaponToFire);
- }
- // Fires current weapon
- void ADHCharacter::FireCurrentWeapon()
- {
- FireWeapon(PlayerData.CurrentWeapon);
- }
- // Gets instance of weapon from EWeaponSlot
- ADHWeapon* ADHCharacter::GetWeaponInstance(EWeaponSlot SlotToGet)
- {
- if (SlotToGet == EWeaponSlot::PrimaryWeapon)
- {
- return PlayerData.Inventory.PrimaryWeapon;
- }
- else if (SlotToGet == EWeaponSlot::SecondaryWeapon)
- {
- return PlayerData.Inventory.SecondaryWeapon;
- }
- else if (SlotToGet == EWeaponSlot::MeleeWeapon)
- {
- return PlayerData.Inventory.MeleeWeapon;
- }
- else if (SlotToGet == EWeaponSlot::SpecialWeapon)
- {
- return PlayerData.Inventory.SpecialWeapon;
- }
- else return NULL;
- }
- /**
- * Spawns new weapon then adds to inventory
- * @param WeaponToSpawn : Weapon to spawn
- * @param SlotToUse : Slot to add weapon to
- */
- void ADHCharacter::MakeWeapon(ADHWeapon* WeaponToSpawn, EWeaponSlot SlotToUse)
- {
- // Declare and configure ActorSpawnParams
- FActorSpawnParameters WeaponSpawnParams;
- WeaponSpawnParams.bNoCollisionFail = false;
- // Determine weapon type and set WeaponSpawnParams.Template accordingly
- if (Cast<ADHProjectileWeapon>(WeaponToSpawn)) WeaponSpawnParams.Template = Cast<ADHProjectileWeapon>(WeaponToSpawn);
- else return;
- // Spawn weapon if character has network authority, otherwise request spawn of weapon
- if (HasAuthority())
- {
- if (SlotToUse == EWeaponSlot::PrimaryWeapon) PlayerData.Inventory.PrimaryWeapon = GetWorld()->SpawnActor<ADHWeapon>(ADHWeapon::StaticClass(), WeaponSpawnParams);
- else if (SlotToUse == EWeaponSlot::SecondaryWeapon) PlayerData.Inventory.SecondaryWeapon = GetWorld()->SpawnActor<ADHWeapon>(ADHWeapon::StaticClass(), WeaponSpawnParams);
- else if (SlotToUse == EWeaponSlot::MeleeWeapon) PlayerData.Inventory.MeleeWeapon = GetWorld()->SpawnActor<ADHWeapon>(ADHWeapon::StaticClass(), WeaponSpawnParams);
- else if (SlotToUse == EWeaponSlot::SpecialWeapon) PlayerData.Inventory.SpecialWeapon = GetWorld()->SpawnActor<ADHWeapon>(ADHWeapon::StaticClass(), WeaponSpawnParams);
- else return;
- }
- else
- {
- }
- // Sets weapon data of SlotToUse
- if (SlotToUse == EWeaponSlot::PrimaryWeapon)
- {
- const ADHWeapon* Weapon = Cast<ADHWeapon>(GetWeaponInstance(SlotToUse));
- PlayerData.WeaponInfo.PrimaryWeapon = Weapon->WeaponInfo;
- }
- else if (SlotToUse == EWeaponSlot::SecondaryWeapon)
- {
- const ADHWeapon* Weapon = Cast<ADHWeapon>(GetWeaponInstance(SlotToUse));
- PlayerData.WeaponInfo.SecondaryWeapon = Weapon->WeaponInfo;
- }
- else if (SlotToUse == EWeaponSlot::MeleeWeapon)
- {
- const ADHWeapon* Weapon = Cast<ADHWeapon>(GetWeaponInstance(SlotToUse));
- PlayerData.WeaponInfo.MeleeWeapon = Weapon->WeaponInfo;
- }
- else if (SlotToUse == EWeaponSlot::SpecialWeapon)
- {
- const ADHWeapon* Weapon = Cast<ADHWeapon>(GetWeaponInstance(SlotToUse));
- PlayerData.WeaponInfo.SpecialWeapon = Weapon->WeaponInfo;
- }
- }
- /**
- * Gets fire delay of EWeaponSlot value
- * @param WeaponToGet : EWeaponSlot value of desired weapon
- */
- float ADHCharacter::GetWeaponFireDelay(EWeaponSlot WeaponToGet)
- {
- float ReturnValue;
- if (WeaponToGet == EWeaponSlot::PrimaryWeapon) ReturnValue = PlayerData.WeaponInfo.PrimaryWeapon.FireDelay;
- else if (WeaponToGet == EWeaponSlot::SecondaryWeapon) ReturnValue = PlayerData.WeaponInfo.SecondaryWeapon.FireDelay;
- else if (WeaponToGet == EWeaponSlot::MeleeWeapon) ReturnValue = PlayerData.WeaponInfo.MeleeWeapon.FireDelay;
- else if (WeaponToGet == EWeaponSlot::SpecialWeapon) ReturnValue = PlayerData.WeaponInfo.SpecialWeapon.FireDelay;
- else ReturnValue = NULL;
- return ReturnValue;
- }
- // Gets fire delay of current weapon
- float ADHCharacter::GetCurrentFireDelay()
- {
- return GetWeaponFireDelay(PlayerData.CurrentWeapon);
- }
- // Handles SwitchWeapon axis input
- void ADHCharacter::OnSwitchWeaponAxis(float Value)
- {
- FDHUtil Util;
- float Modified = Util.Convert.EWeaponSlotToFloat(PlayerData.CurrentWeapon) + Value;
- if (Value != 0.0f && !(Modified > 1.0f || Modified < 4.0f))
- {
- EWeaponSlot NewWeaponSlot = Util.Convert.FloatToEWeaponSlot(Modified);
- if (NewWeaponSlot != PlayerData.CurrentWeapon) EquipWeapon(NewWeaponSlot);
- }
- }
- // Handles PrimaryWeapon switch input
- void ADHCharacter::OnSwitchToPrimary()
- {
- if (PlayerData.CurrentWeapon != EWeaponSlot::PrimaryWeapon) EquipWeapon(EWeaponSlot::PrimaryWeapon);
- }
- // Handles SecondaryWeapon switch input
- void ADHCharacter::OnSwitchToSecondary()
- {
- if (PlayerData.CurrentWeapon != EWeaponSlot::SecondaryWeapon) EquipWeapon(EWeaponSlot::SecondaryWeapon);
- }
- // Handles MeleeWeapon switch input
- void ADHCharacter::OnSwitchToMelee()
- {
- if (PlayerData.CurrentWeapon != EWeaponSlot::MeleeWeapon) EquipWeapon(EWeaponSlot::MeleeWeapon);
- }
- // Handles MeleeWeaponUse input
- void ADHCharacter::OnUseMelee()
- {
- if (PlayerData.CurrentWeapon == EWeaponSlot::MeleeWeapon) FireCurrentWeapon();
- else FireWeapon(EWeaponSlot::MeleeWeapon);
- }
- // Handles SpecialWeapon switch input
- void ADHCharacter::OnSwitchToSpecial()
- {
- if (PlayerData.CurrentWeapon != EWeaponSlot::SpecialWeapon) EquipWeapon(EWeaponSlot::SpecialWeapon);
- }
- // Handles SpecialWeaponUse input
- void ADHCharacter::OnUseSpecial()
- {
- if (PlayerData.CurrentWeapon == EWeaponSlot::SpecialWeapon) FireCurrentWeapon();
- else FireWeapon(EWeaponSlot::SpecialWeapon);
- }
- // Update camera view
- void ADHCharacter::UpdateCameraView()
- {
- const bool bLife = PlayerData.HP.IsAlive();
- FirstPersonCamera->SetActive(bLife, false);
- OrbitViewCamera->SetActive(!bLife, false);
- }
- // Returns firing state
- bool ADHCharacter::IsFiring() const
- {
- return bIsFiring;
- }
- // Returns sprinting state
- bool ADHCharacter::IsSprinting() const
- {
- return bIsSprinting;
- }
- // Returns crouching state
- bool ADHCharacter::IsCrouching() const
- {
- return bIsCrouching;
- }
- // Returns aiming state
- bool ADHCharacter::IsAiming() const
- {
- return bIsAiming;
- }
- // Returns moving state
- bool ADHCharacter::IsMoving() const
- {
- return bIsMoving;
- }
- // Returns is alive state
- bool ADHCharacter::IsAlive() const
- {
- return PlayerData.HP.IsAlive();
- }
- /** Check if character meets the requirements to fire
- * @param bCheckMovement : true to check movement, otherwise false
- * @param bFalseIfFiring : true to account for current fire state, otherwise false
- */
- bool ADHCharacter::CanFire(bool bCheckMovement, bool bFalseIfFiring) const
- {
- // Return false if check bCheckMovement && bIsSprinting, or bFalseIfFiring && bIsFiring
- if (!(bCheckMovement && bIsSprinting) && !(bFalseIfFiring && bIsFiring))
- {
- // Test each weapon for ammo, then return coresponding value
- if (PlayerData.CurrentWeapon == EWeaponSlot::PrimaryWeapon) return PlayerData.WeaponInfo.PrimaryWeapon.CanFire();
- else if (PlayerData.CurrentWeapon == EWeaponSlot::SecondaryWeapon) return PlayerData.WeaponInfo.SecondaryWeapon.CanFire();
- else if (PlayerData.CurrentWeapon == EWeaponSlot::MeleeWeapon) return PlayerData.WeaponInfo.MeleeWeapon.CanFire();
- else if (PlayerData.CurrentWeapon == EWeaponSlot::SpecialWeapon) return PlayerData.WeaponInfo.SpecialWeapon.CanFire();
- else return false;
- }
- else return false;
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement