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