haxis/Source/UnrealProject/GUI/Minimap/MiniMapWidget.cpp

393 lines
12 KiB
C++

// 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<ADefaultGameState>(GetWorld()->GetGameState());
if(IsValid(gameState))
{
for (TActorIterator<AMiniMapVolume> 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<ADefaultGameState>(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<ADefaultPlayerController>(controller);
localPlayer = Cast<ACharacterBase>(playerController->GetPawn());
if (playerController->PlayerState)
m_localTeam = Cast<ADefaultPlayerState>(playerController->PlayerState)->GetTeam();
}
// Failed to fetch local team
if (m_localTeam == -1)
return;
gameState->minimapQuadtree.Update();
map<int32, unordered_set<ACharacterBase*>> drawActors;
unordered_set<ACharacterBase*>& friendlyPlayerSet = drawActors.emplace(m_localTeam, unordered_set<ACharacterBase*>()).first->second;
// Fetch the friendly viewpoints
for (TActorIterator<ACharacterBase> 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<AKOTHSpawnerBase*> minionCamps;
AKOTHBossSpawner* bossCamp = nullptr;
FVector2D bossCampPos;
for (TActorIterator<AKOTHSpawnerBase> iter(world); iter; ++iter)
{
AKOTHSpawnerBase* camp = *iter;
if (camp->IsA<AKOTHBossSpawner>())
{
bossCamp = Cast<AKOTHBossSpawner>(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<UCanvasPanelSlot>(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<UCanvasPanelSlot>(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<int32> 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<UCanvasPanelSlot>(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<ABossBase>())
SetObjectIcon(bossIcon, widget);
else if (character->IsA<ANPCBase>())
SetObjectIcon(creatureIcon, widget);
else
{
trans.Angle = drawPlayerAngle;
SetObjectIcon(playerIcon, widget);
}
widget->image->SetRenderTransform(trans);
// Set the color
if (!(character->IsA<ANetworkPlayer>() && 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<UCanvasPanelSlot>(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<UMiniMapIconWidget>(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<UMiniMapIconWidget>(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<UMiniMapVisionCircle>(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
}