505 lines
15 KiB
C++
505 lines
15 KiB
C++
// Fill out your copyright notice in the Description page of Project Settings.
|
|
|
|
|
|
#include "BlasterPlayerController.h"
|
|
|
|
#include "Blaster/Character/BlasterCharacter.h"
|
|
#include "Blaster/Components/CombatComponent.h"
|
|
#include "Blaster/GameMode/BlasterGameMode.h"
|
|
#include "Blaster/GameState/BlasterGameState.h"
|
|
#include "Blaster/HUD/Announcement.h"
|
|
#include "Blaster/HUD/BlasterHUD.h"
|
|
#include "Blaster/HUD/CharacterOverlay.h"
|
|
#include "Blaster/HUD/DebugWidget.h"
|
|
#include "Blaster/PlayerState/BlasterPlayerState.h"
|
|
#include "Components/ProgressBar.h"
|
|
#include "Components/TextBlock.h"
|
|
#include "GameFramework/GameMode.h"
|
|
#include "Kismet/GameplayStatics.h"
|
|
#include "Net/UnrealNetwork.h"
|
|
|
|
|
|
void ABlasterPlayerController::BeginPlay()
|
|
{
|
|
Super::BeginPlay();
|
|
|
|
BlasterHUD = Cast<ABlasterHUD>(GetHUD());
|
|
ServerCheckMatchState();
|
|
}
|
|
|
|
void ABlasterPlayerController::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
|
|
{
|
|
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
|
|
|
|
DOREPLIFETIME(ABlasterPlayerController, MatchState);
|
|
}
|
|
|
|
void ABlasterPlayerController::Tick(float DeltaTime)
|
|
{
|
|
Super::Tick(DeltaTime);
|
|
|
|
SetHUDTime();
|
|
CheckTimeSync(DeltaTime);
|
|
PollInit();
|
|
|
|
if (DebugWidget)
|
|
{
|
|
SetDebugMsg1(TEXT("GetWorld()->GetTimeSeconds(): "), FString::Printf(TEXT("%02f"), GetWorld()->GetTimeSeconds()));
|
|
SetDebugMsg2(TEXT("GetServerTime(): "), FString::Printf(TEXT("%02f"), GetServerTime()));
|
|
SetDebugMsg3(TEXT("ClientServerDelta: "), FString::Printf(TEXT("%f"), ClientServerDelta));
|
|
SetDebugMsg4(TEXT("LevelStartingTime: "), FString::Printf(TEXT("%f"), LevelStartingTime));
|
|
SetDebugMsg5(TEXT("CountdownInt: "), FString::Printf(TEXT("%d"), CountdownInt));
|
|
}
|
|
}
|
|
|
|
void ABlasterPlayerController::SetDebugMsg1(FString Key, FString Value)
|
|
{
|
|
bool bHUDValid = BlasterHUD && BlasterHUD->DebugWidget && BlasterHUD->DebugWidget->DebugMsg1;
|
|
if (bHUDValid) BlasterHUD->DebugWidget->DebugMsg1->SetText(FText::FromString(Key.Append(Value)));
|
|
}
|
|
|
|
void ABlasterPlayerController::SetDebugMsg2(FString Key, FString Value)
|
|
{
|
|
bool bHUDValid = BlasterHUD && BlasterHUD->DebugWidget && BlasterHUD->DebugWidget->DebugMsg2;
|
|
if (bHUDValid) BlasterHUD->DebugWidget->DebugMsg2->SetText(FText::FromString(Key.Append(Value)));
|
|
}
|
|
|
|
void ABlasterPlayerController::SetDebugMsg3(FString Key, FString Value)
|
|
{
|
|
bool bHUDValid = BlasterHUD && BlasterHUD->DebugWidget && BlasterHUD->DebugWidget->DebugMsg3;
|
|
if (bHUDValid) BlasterHUD->DebugWidget->DebugMsg3->SetText(FText::FromString(Key.Append(Value)));
|
|
}
|
|
|
|
void ABlasterPlayerController::SetDebugMsg4(FString Key, FString Value)
|
|
{
|
|
bool bHUDValid = BlasterHUD && BlasterHUD->DebugWidget && BlasterHUD->DebugWidget->DebugMsg4;
|
|
if (bHUDValid) BlasterHUD->DebugWidget->DebugMsg4->SetText(FText::FromString(Key.Append(Value)));
|
|
}
|
|
|
|
void ABlasterPlayerController::SetDebugMsg5(FString Key, FString Value)
|
|
{
|
|
bool bHUDValid = BlasterHUD && BlasterHUD->DebugWidget && BlasterHUD->DebugWidget->DebugMsg5;
|
|
if (bHUDValid) BlasterHUD->DebugWidget->DebugMsg5->SetText(FText::FromString(Key.Append(Value)));
|
|
}
|
|
|
|
void ABlasterPlayerController::SetDebugMsg6(FString Key, FString Value)
|
|
{
|
|
bool bHUDValid = BlasterHUD && BlasterHUD->DebugWidget && BlasterHUD->DebugWidget->DebugMsg6;
|
|
if (bHUDValid) BlasterHUD->DebugWidget->DebugMsg6->SetText(FText::FromString(Key.Append(Value)));
|
|
}
|
|
|
|
void ABlasterPlayerController::SetDebugMsg7(FString Key, FString Value)
|
|
{
|
|
bool bHUDValid = BlasterHUD && BlasterHUD->DebugWidget && BlasterHUD->DebugWidget->DebugMsg7;
|
|
if (bHUDValid) BlasterHUD->DebugWidget->DebugMsg7->SetText(FText::FromString(Key.Append(Value)));
|
|
}
|
|
|
|
void ABlasterPlayerController::CheckTimeSync(float DeltaTime)
|
|
{
|
|
TimeSyncRunningTime += DeltaTime;
|
|
if (IsLocalController() && TimeSyncRunningTime > TimeSyncFrequency)
|
|
{
|
|
ServerRequestServerTime(GetWorld()->GetTimeSeconds());
|
|
TimeSyncRunningTime = 0.f;
|
|
}
|
|
}
|
|
|
|
void ABlasterPlayerController::ServerCheckMatchState_Implementation()
|
|
{
|
|
ABlasterGameMode* GameMode = Cast<ABlasterGameMode>(UGameplayStatics::GetGameMode(this));
|
|
if (GameMode)
|
|
{
|
|
WarmupTime = GameMode->WarmupTime;
|
|
MatchTime = GameMode->MatchTime;
|
|
CooldownTime = GameMode->CooldownTime;
|
|
LevelStartingTime = GameMode->LevelStartingTime;
|
|
MatchState = GameMode->GetMatchState();
|
|
ClientJoinMidgame(MatchState, WarmupTime, MatchTime, CooldownTime, LevelStartingTime);
|
|
}
|
|
}
|
|
|
|
void ABlasterPlayerController::ClientJoinMidgame_Implementation(FName StateOfMatch, float Warmup, float Match, float Cooldown, float StartingTime)
|
|
{
|
|
WarmupTime = Warmup;
|
|
MatchTime = Match;
|
|
CooldownTime = Cooldown;
|
|
LevelStartingTime = StartingTime;
|
|
MatchState = StateOfMatch;
|
|
OnMatchStateSet(MatchState);
|
|
if (BlasterHUD && MatchState == MatchState::WaitingToStart)
|
|
{
|
|
BlasterHUD->AddAnnouncementOverlay();
|
|
}
|
|
}
|
|
|
|
void ABlasterPlayerController::OnPossess(APawn* InPawn)
|
|
{
|
|
Super::OnPossess(InPawn);
|
|
ABlasterCharacter* BlasterCharacter = Cast<ABlasterCharacter>(InPawn);
|
|
if (BlasterCharacter)
|
|
{
|
|
SetHUDHealth(BlasterCharacter->GetHealth(), BlasterCharacter->GetMaxHealth());
|
|
}
|
|
}
|
|
|
|
void ABlasterPlayerController::SetHUDHealth(float Health, float MaxHealth)
|
|
{
|
|
BlasterHUD = BlasterHUD == nullptr ? Cast<ABlasterHUD>(GetHUD()) : BlasterHUD;
|
|
bool bHUDValid = BlasterHUD &&
|
|
BlasterHUD->CharacterOverlay &&
|
|
BlasterHUD->CharacterOverlay->HealthBar &&
|
|
BlasterHUD->CharacterOverlay->HealthText;
|
|
if (bHUDValid)
|
|
{
|
|
const float HealthPercent = Health / MaxHealth;
|
|
BlasterHUD->CharacterOverlay->HealthBar->SetPercent(HealthPercent);
|
|
FString HealthText = FString::Printf(TEXT("%d/%d"), FMath::CeilToInt(Health), FMath::CeilToInt(MaxHealth));
|
|
BlasterHUD->CharacterOverlay->HealthText->SetText(FText::FromString(HealthText));
|
|
}
|
|
else
|
|
{
|
|
bInitializeCharacterOverlay = true;
|
|
HUDHealth = Health;
|
|
HUDMaxHealth = MaxHealth;
|
|
}
|
|
}
|
|
|
|
void ABlasterPlayerController::SetHUDShield(float Shield, float MaxShield)
|
|
{
|
|
BlasterHUD = BlasterHUD == nullptr ? Cast<ABlasterHUD>(GetHUD()) : BlasterHUD;
|
|
bool bHUDValid = BlasterHUD &&
|
|
BlasterHUD->CharacterOverlay &&
|
|
BlasterHUD->CharacterOverlay->ShieldBar &&
|
|
BlasterHUD->CharacterOverlay->ShieldText;
|
|
if (bHUDValid)
|
|
{
|
|
const float ShieldPercent = Shield / MaxShield;
|
|
BlasterHUD->CharacterOverlay->ShieldBar->SetPercent(ShieldPercent);
|
|
FString ShieldText = FString::Printf(TEXT("%d/%d"), FMath::CeilToInt(Shield), FMath::CeilToInt(MaxShield));
|
|
BlasterHUD->CharacterOverlay->ShieldText->SetText(FText::FromString(ShieldText));
|
|
}
|
|
else
|
|
{
|
|
bInitializeCharacterOverlay = true;
|
|
HUDShield = Shield;
|
|
HUDMaxShield = MaxShield;
|
|
}
|
|
}
|
|
|
|
void ABlasterPlayerController::SetHUDScore(float Score)
|
|
{
|
|
BlasterHUD = BlasterHUD == nullptr ? Cast<ABlasterHUD>(GetHUD()) : BlasterHUD;
|
|
bool bHUDValid = BlasterHUD &&
|
|
BlasterHUD->CharacterOverlay &&
|
|
BlasterHUD->CharacterOverlay->ScoreValue;
|
|
|
|
if (bHUDValid)
|
|
{
|
|
FString ScoreText = FString::Printf(TEXT("%d"), FMath::FloorToInt(Score));
|
|
BlasterHUD->CharacterOverlay->ScoreValue->SetText(FText::FromString(ScoreText));
|
|
}
|
|
else
|
|
{
|
|
bInitializeCharacterOverlay = true;
|
|
HUDScore = Score;
|
|
}
|
|
}
|
|
|
|
void ABlasterPlayerController::SetHUDDefeats(int32 Defeats)
|
|
{
|
|
BlasterHUD = BlasterHUD == nullptr ? Cast<ABlasterHUD>(GetHUD()) : BlasterHUD;
|
|
bool bHUDValid = BlasterHUD &&
|
|
BlasterHUD->CharacterOverlay &&
|
|
BlasterHUD->CharacterOverlay->DefeatsValue;
|
|
if (bHUDValid)
|
|
{
|
|
FString DefeatsText = FString::Printf(TEXT("%d"), Defeats);
|
|
BlasterHUD->CharacterOverlay->DefeatsValue->SetText(FText::FromString(DefeatsText));
|
|
}
|
|
else
|
|
{
|
|
bInitializeCharacterOverlay = true;
|
|
HUDDefeats = Defeats;
|
|
}
|
|
}
|
|
|
|
void ABlasterPlayerController::SetHUDWeaponAmmo(int32 Ammo)
|
|
{
|
|
BlasterHUD = BlasterHUD == nullptr ? Cast<ABlasterHUD>(GetHUD()) : BlasterHUD;
|
|
bool bHUDValid = BlasterHUD &&
|
|
BlasterHUD->CharacterOverlay &&
|
|
BlasterHUD->CharacterOverlay->WeaponAmmoValue;
|
|
if (bHUDValid)
|
|
{
|
|
FString AmmoText = FString::Printf(TEXT("%d"), Ammo);
|
|
BlasterHUD->CharacterOverlay->WeaponAmmoValue->SetText(FText::FromString(AmmoText));
|
|
}
|
|
}
|
|
|
|
void ABlasterPlayerController::SetHUDCarriedAmmo(int32 Ammo)
|
|
{
|
|
BlasterHUD = BlasterHUD == nullptr ? Cast<ABlasterHUD>(GetHUD()) : BlasterHUD;
|
|
bool bHUDValid = BlasterHUD &&
|
|
BlasterHUD->CharacterOverlay &&
|
|
BlasterHUD->CharacterOverlay->CarriedAmmoValue;
|
|
if (bHUDValid)
|
|
{
|
|
FString AmmoText = FString::Printf(TEXT("%d"), Ammo);
|
|
BlasterHUD->CharacterOverlay->CarriedAmmoValue->SetText(FText::FromString(AmmoText));
|
|
}
|
|
}
|
|
|
|
void ABlasterPlayerController::SetHUDGrenades(int32 Grenades)
|
|
{
|
|
BlasterHUD = BlasterHUD == nullptr ? Cast<ABlasterHUD>(GetHUD()) : BlasterHUD;
|
|
bool bHUDValid = BlasterHUD &&
|
|
BlasterHUD->CharacterOverlay &&
|
|
BlasterHUD->CharacterOverlay->GrenadesAmount;
|
|
if (bHUDValid)
|
|
{
|
|
FString GrenadesText = FString::Printf(TEXT("%d"), Grenades);
|
|
BlasterHUD->CharacterOverlay->GrenadesAmount->SetText(FText::FromString(GrenadesText));
|
|
} else
|
|
{
|
|
HUDGrenades = Grenades;
|
|
}
|
|
}
|
|
|
|
void ABlasterPlayerController::SetHUDMatchCountdown(float CountdownTime)
|
|
{
|
|
BlasterHUD = BlasterHUD == nullptr ? Cast<ABlasterHUD>(GetHUD()) : BlasterHUD;
|
|
bool bHUDValid = BlasterHUD &&
|
|
BlasterHUD->CharacterOverlay &&
|
|
BlasterHUD->CharacterOverlay->MatchCountdownText;
|
|
if (bHUDValid)
|
|
{
|
|
if (CountdownTime < 0.f)
|
|
{
|
|
BlasterHUD->CharacterOverlay->MatchCountdownText->SetText(FText());
|
|
return;
|
|
}
|
|
|
|
int32 Minutes = FMath::FloorToInt(CountdownTime / 60.f);
|
|
int32 Seconds = CountdownTime - Minutes * 60;
|
|
|
|
FString CountdownText = FString::Printf(TEXT("%02d:%02d"), Minutes, Seconds);
|
|
BlasterHUD->CharacterOverlay->MatchCountdownText->SetText(FText::FromString(CountdownText));
|
|
}
|
|
}
|
|
|
|
void ABlasterPlayerController::SetHUDAnnouncementCountdown(float CountdownTime)
|
|
{
|
|
BlasterHUD = BlasterHUD == nullptr ? Cast<ABlasterHUD>(GetHUD()) : BlasterHUD;
|
|
bool bHUDValid = BlasterHUD &&
|
|
BlasterHUD->Announcement &&
|
|
BlasterHUD->Announcement->CountdownText;
|
|
if (bHUDValid)
|
|
{
|
|
if (CountdownTime < 0.f)
|
|
{
|
|
BlasterHUD->Announcement->CountdownText->SetText(FText());
|
|
return;
|
|
}
|
|
|
|
int32 Minutes = FMath::FloorToInt(CountdownTime / 60.f);
|
|
int32 Seconds = CountdownTime - Minutes * 60;
|
|
|
|
FString CountdownText = FString::Printf(TEXT("%02d:%02d"), Minutes, Seconds);
|
|
BlasterHUD->Announcement->CountdownText->SetText(FText::FromString(CountdownText));
|
|
}
|
|
}
|
|
|
|
void ABlasterPlayerController::SetHUDTime()
|
|
{
|
|
float TimeLeft = 0.f;
|
|
if (MatchState == MatchState::WaitingToStart) TimeLeft = WarmupTime - GetServerTime(); // + LevelStartingTime;
|
|
else if (MatchState == MatchState::InProgress) TimeLeft = WarmupTime + MatchTime - GetServerTime(); // + LevelStartingTime;
|
|
else if (MatchState == MatchState::Cooldown) TimeLeft = WarmupTime + MatchTime + CooldownTime - GetServerTime(); // + LevelStartingTime;
|
|
|
|
uint32 SecondsLeft = FMath::CeilToInt(TimeLeft);
|
|
|
|
if (HasAuthority())
|
|
{
|
|
BlasterGameMode = BlasterGameMode == nullptr ? Cast<ABlasterGameMode>(UGameplayStatics::GetGameMode(this)) : BlasterGameMode;
|
|
if (BlasterGameMode)
|
|
{
|
|
SecondsLeft = FMath::CeilToInt(BlasterGameMode->GetCountdownTime());
|
|
}
|
|
}
|
|
|
|
if (CountdownInt != SecondsLeft)
|
|
{
|
|
if (MatchState == MatchState::WaitingToStart || MatchState == MatchState::Cooldown)
|
|
{
|
|
SetHUDAnnouncementCountdown(TimeLeft);
|
|
}
|
|
if (MatchState == MatchState::InProgress)
|
|
{
|
|
SetHUDMatchCountdown(TimeLeft);
|
|
}
|
|
}
|
|
|
|
CountdownInt = SecondsLeft;
|
|
}
|
|
|
|
void ABlasterPlayerController::PollInit()
|
|
{
|
|
/*
|
|
if (BlasterHUD && BlasterHUD->DebugWidget == nullptr)
|
|
{
|
|
BlasterHUD->AddDebugWidget();
|
|
if (BlasterHUD->DebugWidget)
|
|
{
|
|
DebugWidget = BlasterHUD->DebugWidget;
|
|
}
|
|
}
|
|
*/
|
|
|
|
if (CharacterOverlay == nullptr)
|
|
{
|
|
if (BlasterHUD && BlasterHUD->CharacterOverlay)
|
|
{
|
|
CharacterOverlay = BlasterHUD->CharacterOverlay;
|
|
if (CharacterOverlay)
|
|
{
|
|
SetHUDHealth(HUDHealth, HUDMaxHealth);
|
|
SetHUDShield(HUDShield, HUDMaxShield);
|
|
SetHUDScore(HUDScore);
|
|
SetHUDDefeats(HUDDefeats);
|
|
ABlasterCharacter* BlasterCharacter = Cast<ABlasterCharacter>(GetPawn());
|
|
if (BlasterCharacter && BlasterCharacter->GetCombat())
|
|
{
|
|
SetHUDGrenades(BlasterCharacter->GetCombat()->GetGrenades());
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ABlasterPlayerController::ServerRequestServerTime_Implementation(float TimeOfClientRequest)
|
|
{
|
|
float ServerTimeOfReceipt = GetWorld()->GetTimeSeconds();
|
|
ClientReportServerTime(TimeOfClientRequest, ServerTimeOfReceipt);
|
|
}
|
|
|
|
void ABlasterPlayerController::ClientReportServerTime_Implementation(float TimeOfClientRequest, float TimeServerReceivedClientRequest)
|
|
{
|
|
float RoundTripTime = GetWorld()->GetTimeSeconds() - TimeOfClientRequest;
|
|
float CurrentServerTime = TimeServerReceivedClientRequest + 0.5f * RoundTripTime;
|
|
ClientServerDelta = CurrentServerTime - GetWorld()->GetTimeSeconds();
|
|
}
|
|
|
|
float ABlasterPlayerController::GetServerTime()
|
|
{
|
|
if (HasAuthority()) return GetWorld()->GetTimeSeconds();
|
|
return GetWorld()->GetTimeSeconds() + ClientServerDelta;
|
|
}
|
|
|
|
void ABlasterPlayerController::ReceivedPlayer()
|
|
{
|
|
Super::ReceivedPlayer();
|
|
if (IsLocalController())
|
|
{
|
|
ServerRequestServerTime(GetWorld()->GetTimeSeconds());
|
|
}
|
|
}
|
|
|
|
void ABlasterPlayerController::OnMatchStateSet(FName State)
|
|
{
|
|
MatchState = State;
|
|
|
|
if (MatchState == MatchState::InProgress)
|
|
{
|
|
HandleMatchHasStarted();
|
|
}
|
|
else if (MatchState == MatchState::Cooldown)
|
|
{
|
|
HandleCooldown();
|
|
}
|
|
}
|
|
|
|
void ABlasterPlayerController::OnRep_MatchState()
|
|
{
|
|
if (MatchState == MatchState::InProgress)
|
|
{
|
|
HandleMatchHasStarted();
|
|
}
|
|
else if (MatchState == MatchState::Cooldown)
|
|
{
|
|
HandleCooldown();
|
|
}
|
|
}
|
|
|
|
void ABlasterPlayerController::HandleMatchHasStarted()
|
|
{
|
|
BlasterHUD = BlasterHUD == nullptr ? Cast<ABlasterHUD>(GetHUD()) : BlasterHUD;
|
|
if (BlasterHUD)
|
|
{
|
|
if (BlasterHUD->CharacterOverlay == nullptr) BlasterHUD->AddCharacterOverlay();
|
|
if (BlasterHUD->Announcement)
|
|
{
|
|
BlasterHUD->Announcement->SetVisibility(ESlateVisibility::Hidden);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ABlasterPlayerController::HandleCooldown()
|
|
{
|
|
BlasterHUD = BlasterHUD == nullptr ? Cast<ABlasterHUD>(GetHUD()) : BlasterHUD;
|
|
if (BlasterHUD)
|
|
{
|
|
if (BlasterHUD->CharacterOverlay)
|
|
{
|
|
BlasterHUD->CharacterOverlay->RemoveFromParent();
|
|
}
|
|
|
|
bool bHUDValid = BlasterHUD->Announcement &&
|
|
BlasterHUD->Announcement->AnnouncementText &&
|
|
BlasterHUD->Announcement->AnnouncementMessage;
|
|
|
|
if (bHUDValid)
|
|
{
|
|
BlasterHUD->Announcement->SetVisibility(ESlateVisibility::Visible);
|
|
FString AnnouncementText("New match starts in:");
|
|
BlasterHUD->Announcement->AnnouncementText->SetText(FText::FromString(AnnouncementText));
|
|
|
|
ABlasterGameState* BlasterGameState = Cast<ABlasterGameState>(UGameplayStatics::GetGameState(this));
|
|
ABlasterPlayerState* BlasterPlayerState = GetPlayerState<ABlasterPlayerState>();
|
|
if (BlasterGameState && BlasterPlayerState)
|
|
{
|
|
TArray<ABlasterPlayerState*> TopPlayers = BlasterGameState->TopScoringPlayers;
|
|
FString InfoTextString;
|
|
if (TopPlayers.Num() == 0)
|
|
{
|
|
InfoTextString = FString("There is no winner.");
|
|
}
|
|
else if (TopPlayers.Num() == 1 && TopPlayers[0] == BlasterPlayerState)
|
|
{
|
|
InfoTextString = FString("You are the winner!");
|
|
}
|
|
else if (TopPlayers.Num() == 1)
|
|
{
|
|
InfoTextString = FString::Printf(TEXT("%s won!"), *TopPlayers[0]->GetPlayerName());
|
|
}
|
|
else if (TopPlayers.Num() > 1)
|
|
{
|
|
InfoTextString = FString("Players tied for the win: \n");
|
|
for (auto TiedPlayers : TopPlayers)
|
|
{
|
|
InfoTextString.Append(FString::Printf(TEXT("%s\n"), *TiedPlayers->GetPlayerName()));
|
|
}
|
|
}
|
|
|
|
BlasterHUD->Announcement->AnnouncementMessage->SetText(FText::FromString(InfoTextString));
|
|
}
|
|
}
|
|
}
|
|
ABlasterCharacter* BlasterCharacter = Cast<ABlasterCharacter>(GetPawn());
|
|
if (BlasterCharacter && BlasterCharacter->GetCombat())
|
|
{
|
|
BlasterCharacter->bDisableGameplay = true;
|
|
BlasterCharacter->GetCombat()->FireButtonPressed(false);
|
|
}
|
|
}
|