564 lines
18 KiB
C++
564 lines
18 KiB
C++
#include "EnemySpaceship.h"
|
|
#include "Kismet/GameplayStatics.h"
|
|
#include "GameFramework/ProjectileMovementComponent.h"
|
|
#include "Components/SphereComponent.h"
|
|
#include "Engine/World.h"
|
|
#include "DrawDebugHelpers.h"
|
|
#include "Math/UnrealMathUtility.h"
|
|
#include "EnemyProjectile.h"
|
|
|
|
// Include SpaceshipPawn to access player-specific functionality
|
|
#include "SpaceshipPawn.h"
|
|
|
|
AEnemySpaceship::AEnemySpaceship()
|
|
{
|
|
PrimaryActorTick.bCanEverTick = true;
|
|
|
|
// Create and setup the enemy mesh
|
|
EnemyMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("EnemyMesh"));
|
|
RootComponent = EnemyMesh;
|
|
|
|
// Create projectile spawn point
|
|
ProjectileSpawnPoint = CreateDefaultSubobject<USceneComponent>(TEXT("ProjectileSpawnPoint"));
|
|
ProjectileSpawnPoint->SetupAttachment(EnemyMesh);
|
|
ProjectileSpawnPoint->SetRelativeLocation(FVector(100.0f, 0.0f, 0.0f)); // Forward of the ship
|
|
|
|
// Disable gravity and physics simulation
|
|
EnemyMesh->SetSimulatePhysics(false);
|
|
EnemyMesh->SetEnableGravity(false);
|
|
|
|
// Set up collision for the enemy mesh
|
|
EnemyMesh->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
|
|
EnemyMesh->SetCollisionObjectType(ECC_Pawn);
|
|
EnemyMesh->SetCollisionResponseToAllChannels(ECR_Block);
|
|
EnemyMesh->SetCollisionResponseToChannel(ECC_Pawn, ECR_Ignore); // Ignore other pawns
|
|
EnemyMesh->SetCollisionResponseToChannel(ECC_GameTraceChannel1, ECR_Block); // Block player (for damage)
|
|
EnemyMesh->SetGenerateOverlapEvents(true);
|
|
|
|
CurrentVelocity = FVector::ZeroVector;
|
|
TargetVelocity = FVector::ZeroVector;
|
|
LastPosition = FVector::ZeroVector;
|
|
}
|
|
|
|
void AEnemySpaceship::BeginPlay()
|
|
{
|
|
Super::BeginPlay();
|
|
|
|
// Find the player pawn and cast to SpaceshipPawn
|
|
AActor* FoundPlayerPawn = UGameplayStatics::GetPlayerPawn(GetWorld(), 0);
|
|
PlayerPawn = Cast<ASpaceshipPawn>(FoundPlayerPawn);
|
|
|
|
// Initialize behavior state timer
|
|
GetWorldTimerManager().SetTimer(BehaviorTimerHandle, this, &AEnemySpaceship::ChangeBehaviorState, BehaviorChangeTime, true);
|
|
|
|
// Initialize with the ability to fire
|
|
bCanFire = true;
|
|
|
|
// Randomize initial strafe direction
|
|
StrafeDirection = FMath::RandBool() ? 1.0f : -1.0f;
|
|
|
|
LastPosition = GetActorLocation();
|
|
|
|
bInitializedFlank = false;
|
|
LastFlankUpdateTime = 0.0f;
|
|
CurrentFlankTarget = GetActorLocation();
|
|
}
|
|
|
|
// Modify Tick function to include flanking behavior
|
|
void AEnemySpaceship::Tick(float DeltaTime)
|
|
{
|
|
Super::Tick(DeltaTime);
|
|
|
|
if (!PlayerPawn)
|
|
{
|
|
AActor* FoundPlayerPawn = UGameplayStatics::GetPlayerPawn(GetWorld(), 0);
|
|
PlayerPawn = Cast<ASpaceshipPawn>(FoundPlayerPawn);
|
|
if (!PlayerPawn) return;
|
|
}
|
|
|
|
// Calculate distance to player
|
|
DistanceToPlayer = FVector::Dist(GetActorLocation(), PlayerPawn->GetActorLocation());
|
|
|
|
// Update behavior state based on distance
|
|
UpdateBehaviorState();
|
|
|
|
// Execute behavior based on current state
|
|
switch (CurrentBehaviorState)
|
|
{
|
|
case EEnemyBehaviorState::Chase:
|
|
PerformChase(DeltaTime);
|
|
break;
|
|
case EEnemyBehaviorState::Attack:
|
|
PerformAttack(DeltaTime);
|
|
break;
|
|
case EEnemyBehaviorState::Retreat:
|
|
PerformRetreat(DeltaTime);
|
|
break;
|
|
case EEnemyBehaviorState::Strafe:
|
|
PerformStrafe(DeltaTime);
|
|
break;
|
|
case EEnemyBehaviorState::Flank:
|
|
PerformFlank(DeltaTime);
|
|
break;
|
|
}
|
|
|
|
// Debug state information
|
|
if (GEngine)
|
|
{
|
|
FString StateString;
|
|
switch (CurrentBehaviorState)
|
|
{
|
|
case EEnemyBehaviorState::Chase: StateString = "Chase"; break;
|
|
case EEnemyBehaviorState::Attack: StateString = "Attack"; break;
|
|
case EEnemyBehaviorState::Retreat: StateString = "Retreat"; break;
|
|
case EEnemyBehaviorState::Strafe: StateString = "Strafe"; break;
|
|
case EEnemyBehaviorState::Flank: StateString = "Flank"; break;
|
|
}
|
|
|
|
GEngine->AddOnScreenDebugMessage(-1, 0.0f, FColor::Orange,
|
|
FString::Printf(TEXT("Enemy State: %s | Distance: %.1f"), *StateString, DistanceToPlayer));
|
|
}
|
|
}
|
|
|
|
|
|
void AEnemySpaceship::SmoothMove(const FVector& TargetLocation, float DeltaTime)
|
|
{
|
|
FVector CurrentLocation = GetActorLocation();
|
|
FVector DirectionToTarget = (TargetLocation - CurrentLocation);
|
|
float DistanceToTarget = DirectionToTarget.Size();
|
|
|
|
// Calculate target velocity
|
|
FVector NewTargetVelocity = DirectionToTarget;
|
|
if (DistanceToTarget > 1.0f)
|
|
{
|
|
NewTargetVelocity = DirectionToTarget.GetSafeNormal() * FMath::Min(MovementSpeed, DistanceToTarget / DeltaTime);
|
|
}
|
|
else
|
|
{
|
|
NewTargetVelocity = FVector::ZeroVector;
|
|
}
|
|
|
|
// Smoothly interpolate current velocity towards target velocity
|
|
CurrentVelocity = FMath::VInterpTo(CurrentVelocity, NewTargetVelocity, DeltaTime, InterpSpeed);
|
|
|
|
// Clamp acceleration
|
|
FVector Acceleration = (CurrentVelocity - (CurrentLocation - LastPosition) / DeltaTime);
|
|
if (Acceleration.SizeSquared() > MaxAcceleration * MaxAcceleration)
|
|
{
|
|
Acceleration = Acceleration.GetSafeNormal() * MaxAcceleration;
|
|
CurrentVelocity = ((CurrentLocation - LastPosition) / DeltaTime) + (Acceleration * DeltaTime);
|
|
}
|
|
|
|
// Update position
|
|
LastPosition = CurrentLocation;
|
|
FVector NewLocation = CurrentLocation + (CurrentVelocity * DeltaTime);
|
|
SetActorLocation(NewLocation);
|
|
}
|
|
|
|
void AEnemySpaceship::UpdateBehaviorState()
|
|
{
|
|
if (!PlayerPawn) return;
|
|
|
|
FVector DesiredPosition = CalculatePositionAwayFromOtherEnemies();
|
|
FVector PlayerVelocity = PlayerPawn->GetVelocity();
|
|
float PlayerSpeed = PlayerVelocity.Size();
|
|
|
|
float AggressionRoll = FMath::FRand();
|
|
|
|
if (DistanceToPlayer < MinDistanceToPlayer)
|
|
{
|
|
if (AggressionRoll < AggressionFactor * 0.3f)
|
|
{
|
|
CurrentBehaviorState = EEnemyBehaviorState::Attack;
|
|
}
|
|
else
|
|
{
|
|
CurrentBehaviorState = EEnemyBehaviorState::Retreat;
|
|
}
|
|
}
|
|
else if (DistanceToPlayer < AttackRange)
|
|
{
|
|
// Increased chance of flanking
|
|
if (AggressionRoll < AggressionFactor * FlankingFrequency)
|
|
{
|
|
// Only change to flank state if we're not already flanking
|
|
// This prevents unnecessary state changes that could cause position jumps
|
|
if (CurrentBehaviorState != EEnemyBehaviorState::Flank)
|
|
{
|
|
CurrentBehaviorState = EEnemyBehaviorState::Flank;
|
|
bIsFlankingRight = FMath::RandBool();
|
|
bInitializedFlank = false; // Will trigger new flank position calculation
|
|
LastFlankUpdateTime = GetWorld()->GetTimeSeconds();
|
|
}
|
|
}
|
|
else if (AggressionRoll < AggressionFactor * 0.8f) // Increased from 0.7f
|
|
{
|
|
CurrentBehaviorState = EEnemyBehaviorState::Attack;
|
|
}
|
|
else
|
|
{
|
|
CurrentBehaviorState = EEnemyBehaviorState::Strafe;
|
|
StrafeDirection = FMath::RandBool() ? 1.0f : -1.0f;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CurrentBehaviorState = EEnemyBehaviorState::Chase;
|
|
}
|
|
}
|
|
|
|
|
|
// Add this new function to handle enemy spacing
|
|
FVector AEnemySpaceship::CalculatePositionAwayFromOtherEnemies()
|
|
{
|
|
FVector AveragePosition = GetActorLocation();
|
|
int32 EnemyCount = 0;
|
|
|
|
// Find all enemy spaceships
|
|
TArray<AActor*> FoundEnemies;
|
|
UGameplayStatics::GetAllActorsOfClass(GetWorld(), AEnemySpaceship::StaticClass(), FoundEnemies);
|
|
|
|
for (AActor* Enemy : FoundEnemies)
|
|
{
|
|
if (Enemy != this)
|
|
{
|
|
float Distance = FVector::Dist(GetActorLocation(), Enemy->GetActorLocation());
|
|
if (Distance < MinDistanceToOtherEnemies)
|
|
{
|
|
AveragePosition += Enemy->GetActorLocation();
|
|
EnemyCount++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (EnemyCount > 0)
|
|
{
|
|
AveragePosition /= (EnemyCount + 1);
|
|
// Calculate position away from the cluster
|
|
FVector AwayFromCrowd = GetActorLocation() - AveragePosition;
|
|
AwayFromCrowd.Normalize();
|
|
return GetActorLocation() + (AwayFromCrowd * MinDistanceToOtherEnemies);
|
|
}
|
|
|
|
return GetActorLocation();
|
|
}
|
|
|
|
FVector AEnemySpaceship::CalculateFlankPosition()
|
|
{
|
|
if (!PlayerPawn) return GetActorLocation();
|
|
|
|
// Get player's forward vector and velocity
|
|
FVector PlayerForward = PlayerPawn->GetActorForwardVector();
|
|
FVector PlayerVelocity = PlayerPawn->GetVelocity();
|
|
|
|
// Use player's velocity direction if they're moving, otherwise use their forward vector
|
|
FVector BaseDirection = PlayerVelocity.SizeSquared() > 100.0f ?
|
|
PlayerVelocity.GetSafeNormal() : PlayerForward;
|
|
|
|
// Calculate the flanking angle
|
|
float AngleRadians = FMath::DegreesToRadians(FlankAngle * (bIsFlankingRight ? 1.0f : -1.0f));
|
|
|
|
// Rotate the vector for flanking
|
|
FVector FlankDirection = BaseDirection.RotateAngleAxis(AngleRadians, FVector::UpVector);
|
|
|
|
// Calculate the final position with some variation in distance
|
|
float VariedDistance = FlankDistance * FMath::RandRange(0.8f, 1.2f);
|
|
return PlayerPawn->GetActorLocation() + (FlankDirection * VariedDistance);
|
|
}
|
|
|
|
void AEnemySpaceship::PerformFlank(float DeltaTime)
|
|
{
|
|
if (!PlayerPawn) return;
|
|
|
|
float CurrentTime = GetWorld()->GetTimeSeconds();
|
|
|
|
// Initialize flank position if needed
|
|
if (!bInitializedFlank)
|
|
{
|
|
CurrentFlankTarget = CalculateFlankPosition();
|
|
bInitializedFlank = true;
|
|
LastFlankUpdateTime = CurrentTime;
|
|
}
|
|
|
|
// Update flank position periodically
|
|
if (CurrentTime - LastFlankUpdateTime >= FlankPositionUpdateInterval)
|
|
{
|
|
// Smoothly transition to new flank position
|
|
FVector NewFlankPosition = CalculateFlankPosition();
|
|
CurrentFlankTarget = FMath::VInterpTo(
|
|
CurrentFlankTarget,
|
|
NewFlankPosition,
|
|
DeltaTime,
|
|
2.0f // Interpolation speed
|
|
);
|
|
|
|
LastFlankUpdateTime = CurrentTime;
|
|
|
|
// Occasionally switch flanking direction
|
|
if (FMath::FRand() < 0.3f) // 30% chance to switch direction
|
|
{
|
|
bIsFlankingRight = !bIsFlankingRight;
|
|
}
|
|
}
|
|
|
|
// Calculate distance to current flank target
|
|
float DistanceToFlankTarget = FVector::Dist(GetActorLocation(), CurrentFlankTarget);
|
|
|
|
// Calculate movement speed based on distance
|
|
float CurrentFlankSpeed = FMath::Min(FlankingSpeed, DistanceToFlankTarget * 2.0f);
|
|
|
|
// Move towards flank position
|
|
FVector DirectionToFlank = (CurrentFlankTarget - GetActorLocation()).GetSafeNormal();
|
|
FVector TargetPosition = GetActorLocation() + DirectionToFlank * CurrentFlankSpeed * DeltaTime;
|
|
|
|
// Use smooth movement
|
|
SmoothMove(TargetPosition, DeltaTime);
|
|
|
|
// Face the player while flanking
|
|
FVector DirectionToPlayer = (PlayerPawn->GetActorLocation() - GetActorLocation()).GetSafeNormal();
|
|
FRotator TargetRotation = DirectionToPlayer.Rotation();
|
|
FRotator NewRotation = FMath::RInterpTo(GetActorRotation(), TargetRotation, DeltaTime, InterpSpeed);
|
|
SetActorRotation(NewRotation);
|
|
|
|
// Fire more frequently while flanking
|
|
if (bCanFire && DistanceToPlayer < AttackRange)
|
|
{
|
|
Fire();
|
|
}
|
|
}
|
|
|
|
void AEnemySpaceship::ChangeBehaviorState()
|
|
{
|
|
// Random chance to change behavior state
|
|
if (FMath::FRand() < StateChangeChance)
|
|
{
|
|
// Pick a random state
|
|
int32 NewState = FMath::RandRange(0, 3);
|
|
CurrentBehaviorState = static_cast<EEnemyBehaviorState>(NewState);
|
|
|
|
// Randomize strafe direction when entering strafe mode
|
|
if (CurrentBehaviorState == EEnemyBehaviorState::Strafe)
|
|
{
|
|
StrafeDirection = FMath::RandBool() ? 1.0f : -1.0f;
|
|
}
|
|
}
|
|
}
|
|
|
|
void AEnemySpaceship::PerformChase(float DeltaTime)
|
|
{
|
|
if (PlayerPawn)
|
|
{
|
|
// Calculate direction to player
|
|
FVector DirectionToPlayer = (PlayerPawn->GetActorLocation() - GetActorLocation()).GetSafeNormal();
|
|
|
|
// Calculate target position
|
|
FVector TargetPosition = GetActorLocation() + DirectionToPlayer * MovementSpeed * DeltaTime;
|
|
|
|
// Use smooth movement
|
|
SmoothMove(TargetPosition, DeltaTime);
|
|
|
|
// Smoothly rotate to face player
|
|
FRotator TargetRotation = DirectionToPlayer.Rotation();
|
|
FRotator NewRotation = FMath::RInterpTo(GetActorRotation(), TargetRotation, DeltaTime, InterpSpeed);
|
|
SetActorRotation(NewRotation);
|
|
|
|
// Fire if within range
|
|
if (DistanceToPlayer < AttackRange && bCanFire)
|
|
{
|
|
Fire();
|
|
}
|
|
}
|
|
}
|
|
|
|
void AEnemySpaceship::PerformAttack(float DeltaTime)
|
|
{
|
|
if (PlayerPawn)
|
|
{
|
|
// Face towards the player but don't move forward
|
|
FVector Direction = (PlayerPawn->GetActorLocation() - GetActorLocation()).GetSafeNormal();
|
|
FRotator NewRotation = Direction.Rotation();
|
|
SetActorRotation(NewRotation);
|
|
|
|
// Fire if ready
|
|
if (bCanFire)
|
|
{
|
|
Fire();
|
|
}
|
|
}
|
|
}
|
|
|
|
void AEnemySpaceship::PerformRetreat(float DeltaTime)
|
|
{
|
|
if (PlayerPawn)
|
|
{
|
|
// Calculate ideal retreat position
|
|
FVector DirectionFromPlayer = (GetActorLocation() - PlayerPawn->GetActorLocation()).GetSafeNormal();
|
|
FVector DesiredPosition = PlayerPawn->GetActorLocation() + (DirectionFromPlayer * OptimalCombatDistance);
|
|
|
|
// Consider other enemies
|
|
FVector AvoidCrowdingPosition = CalculatePositionAwayFromOtherEnemies();
|
|
FVector FinalTargetPosition = FMath::Lerp(DesiredPosition, AvoidCrowdingPosition, 0.3f);
|
|
|
|
// Use smooth movement
|
|
SmoothMove(FinalTargetPosition, DeltaTime);
|
|
|
|
// Smoothly rotate to face player
|
|
FVector DirectionToPlayer = (PlayerPawn->GetActorLocation() - GetActorLocation()).GetSafeNormal();
|
|
FRotator TargetRotation = DirectionToPlayer.Rotation();
|
|
FRotator NewRotation = FMath::RInterpTo(GetActorRotation(), TargetRotation, DeltaTime, InterpSpeed);
|
|
SetActorRotation(NewRotation);
|
|
|
|
// Fire while retreating if in range
|
|
if (bCanFire && DistanceToPlayer < AttackRange)
|
|
{
|
|
Fire();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void AEnemySpaceship::PerformStrafe(float DeltaTime)
|
|
{
|
|
if (PlayerPawn)
|
|
{
|
|
// Calculate direction to player
|
|
FVector DirectionToPlayer = (PlayerPawn->GetActorLocation() - GetActorLocation()).GetSafeNormal();
|
|
|
|
// Calculate ideal combat distance
|
|
float CurrentDistance = DistanceToPlayer;
|
|
float DistanceAdjustment = 0.0f;
|
|
|
|
if (CurrentDistance < OptimalCombatDistance)
|
|
{
|
|
DistanceAdjustment = -1.0f; // Move away
|
|
}
|
|
else if (CurrentDistance > OptimalCombatDistance + 200.0f)
|
|
{
|
|
DistanceAdjustment = 1.0f; // Move closer
|
|
}
|
|
|
|
// Calculate strafe direction
|
|
FVector StrafeVector = FVector::CrossProduct(DirectionToPlayer, FVector::UpVector) * StrafeDirection;
|
|
|
|
// Calculate target position
|
|
FVector DistanceAdjustmentVector = DirectionToPlayer * DistanceAdjustment * MovementSpeed * 0.5f;
|
|
FVector StrafeMovement = StrafeVector * StrafeSpeed;
|
|
FVector DesiredMovement = (StrafeMovement + DistanceAdjustmentVector) * DeltaTime;
|
|
|
|
// Get position avoiding other enemies
|
|
FVector CrowdAvoidancePosition = CalculatePositionAwayFromOtherEnemies();
|
|
|
|
// Blend between desired movement and crowd avoidance
|
|
FVector TargetPosition = GetActorLocation() + DesiredMovement;
|
|
FVector FinalTargetPosition = FMath::Lerp(TargetPosition, CrowdAvoidancePosition, 0.3f);
|
|
|
|
// Use smooth movement
|
|
SmoothMove(FinalTargetPosition, DeltaTime);
|
|
|
|
// Face towards the player
|
|
FRotator TargetRotation = DirectionToPlayer.Rotation();
|
|
FRotator NewRotation = FMath::RInterpTo(GetActorRotation(), TargetRotation, DeltaTime, InterpSpeed);
|
|
SetActorRotation(NewRotation);
|
|
|
|
// Fire while strafing if in range
|
|
if (bCanFire && DistanceToPlayer < AttackRange)
|
|
{
|
|
Fire();
|
|
}
|
|
}
|
|
}
|
|
|
|
void AEnemySpaceship::Fire()
|
|
{
|
|
if (!ProjectileClass) return;
|
|
|
|
UWorld* World = GetWorld();
|
|
if (World)
|
|
{
|
|
FVector SpawnLocation = ProjectileSpawnPoint->GetComponentLocation();
|
|
FRotator SpawnRotation = GetActorRotation();
|
|
|
|
// Less random spread during flanking for more accurate shots
|
|
float SpreadMultiplier = (CurrentBehaviorState == EEnemyBehaviorState::Flank) ? 0.9f : 1.0f;
|
|
float RandPitch = FMath::RandRange(-5.0f, 5.0f) * SpreadMultiplier;
|
|
float RandYaw = FMath::RandRange(-5.0f, 5.0f) * SpreadMultiplier;
|
|
|
|
SpawnRotation.Pitch += RandPitch;
|
|
SpawnRotation.Yaw += RandYaw;
|
|
|
|
FActorSpawnParameters SpawnParams;
|
|
SpawnParams.Owner = this;
|
|
SpawnParams.Instigator = GetInstigator();
|
|
|
|
AEnemyProjectile* Projectile = World->SpawnActor<AEnemyProjectile>(
|
|
ProjectileClass,
|
|
SpawnLocation,
|
|
SpawnRotation,
|
|
SpawnParams
|
|
);
|
|
|
|
// Start fire rate timer
|
|
bCanFire = false;
|
|
GetWorldTimerManager().SetTimer(FireTimerHandle, this, &AEnemySpaceship::ResetFire, FireRate, false);
|
|
}
|
|
}
|
|
|
|
void AEnemySpaceship::ResetFire()
|
|
{
|
|
bCanFire = true;
|
|
}
|
|
|
|
float AEnemySpaceship::TakeDamage(float DamageAmount, FDamageEvent const& DamageEvent,
|
|
AController* EventInstigator, AActor* DamageCauser)
|
|
{
|
|
float DamageToApply = Super::TakeDamage(DamageAmount, DamageEvent, EventInstigator, DamageCauser);
|
|
|
|
CurrentHealth -= DamageToApply;
|
|
|
|
// Debug message
|
|
if (GEngine)
|
|
{
|
|
GEngine->AddOnScreenDebugMessage(-1, 2.0f, FColor::Red,
|
|
FString::Printf(TEXT("Enemy Hit! Health: %f"), CurrentHealth));
|
|
}
|
|
|
|
// When damaged, prefer retreat or strafe behaviors temporarily
|
|
if (FMath::RandBool())
|
|
{
|
|
CurrentBehaviorState = EEnemyBehaviorState::Retreat;
|
|
}
|
|
else
|
|
{
|
|
CurrentBehaviorState = EEnemyBehaviorState::Strafe;
|
|
StrafeDirection = FMath::RandBool() ? 1.0f : -1.0f;
|
|
}
|
|
|
|
if (CurrentHealth <= 0)
|
|
{
|
|
Die();
|
|
}
|
|
|
|
return DamageToApply;
|
|
}
|
|
|
|
void AEnemySpaceship::Die()
|
|
{
|
|
// Play explosion effect
|
|
UGameplayStatics::SpawnEmitterAtLocation(
|
|
GetWorld(),
|
|
nullptr, // Add your explosion effect here
|
|
GetActorLocation(),
|
|
FRotator::ZeroRotator,
|
|
FVector(2.0f)
|
|
);
|
|
|
|
// Play explosion sound
|
|
UGameplayStatics::PlaySoundAtLocation(
|
|
this,
|
|
nullptr, // Add your explosion sound here
|
|
GetActorLocation()
|
|
);
|
|
|
|
// Destroy the enemy
|
|
Destroy();
|
|
} |