Planting interaction blocker implemented, ui showing planting mode
This commit is contained in:
BIN
Content/ST_CommonWords.uasset
LFS
BIN
Content/ST_CommonWords.uasset
LFS
Binary file not shown.
Binary file not shown.
BIN
Content/UI/MainUI.uasset
LFS
BIN
Content/UI/MainUI.uasset
LFS
Binary file not shown.
@@ -21,6 +21,7 @@ void UInteractionSubsystem::Initialize(FSubsystemCollectionBase& Collection)
|
||||
void UInteractionSubsystem::Deinitialize()
|
||||
{
|
||||
InteractableActorList.Empty();
|
||||
InteractionUIBlockers.Empty();
|
||||
|
||||
if (UInteractionSubsystem* IS = GetWorld()->GetSubsystem<UInteractionSubsystem>())
|
||||
{
|
||||
@@ -104,19 +105,23 @@ AActor* UInteractionSubsystem::GetClosestInteractable(const UObject* WorldContex
|
||||
Closest = Actor.Get();
|
||||
}
|
||||
|
||||
if (IS->CurrentlyInteractableObject != Closest)
|
||||
// UI-only gate: blocking never changes what this function returns to gameplay code,
|
||||
// it only decides which actor is allowed to show the interaction UI.
|
||||
AActor* UIActor = IS->HasInteractionUIBlocks() ? nullptr : Closest;
|
||||
|
||||
if (IS->CurrentlyInteractableObject.Get() != UIActor)
|
||||
{
|
||||
if (IS->CurrentlyInteractableObject.IsValid())
|
||||
{
|
||||
// UE_LOG(LogTemp, Warning, TEXT("Turning off last interactable: %s"), *IS->CurrentlyInteractableObject.Get()->GetActorNameOrLabel())
|
||||
IInteractableActorInterface::Execute_ToggleInteractableUI(IS->CurrentlyInteractableObject.Get(), false);
|
||||
}
|
||||
if (Closest)
|
||||
if (UIActor)
|
||||
{
|
||||
// UE_LOG(LogTemp, Warning, TEXT("Turning ON interactable: %s"), *Closest->GetActorNameOrLabel())
|
||||
IInteractableActorInterface::Execute_ToggleInteractableUI(Closest, true);
|
||||
// UE_LOG(LogTemp, Warning, TEXT("Turning ON interactable: %s"), *UIActor->GetActorNameOrLabel())
|
||||
IInteractableActorInterface::Execute_ToggleInteractableUI(UIActor, true);
|
||||
}
|
||||
IS->CurrentlyInteractableObject = Closest;
|
||||
IS->CurrentlyInteractableObject = UIActor;
|
||||
}
|
||||
|
||||
return Closest;
|
||||
@@ -131,9 +136,104 @@ void UInteractionSubsystem::UpdateInteractableText(AActor* Interactable)
|
||||
|
||||
if (InteractionSubsystem->CurrentlyInteractableObject != Interactable) return;
|
||||
|
||||
// The interaction UI is being held hidden by a blocker - leave it alone.
|
||||
if (InteractionSubsystem->HasInteractionUIBlocks()) return;
|
||||
|
||||
IInteractableActorInterface::Execute_ToggleInteractableUI(InteractionSubsystem->CurrentlyInteractableObject.Get(), true);
|
||||
}
|
||||
|
||||
void UInteractionSubsystem::AddInteractionUIBlock(const UObject* WorldContextObject, UObject* Blocker)
|
||||
{
|
||||
if (!IsValid(WorldContextObject) || !IsValid(WorldContextObject->GetWorld()) || !IsValid(Blocker))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UInteractionSubsystem* IS = WorldContextObject->GetWorld()->GetSubsystem<UInteractionSubsystem>();
|
||||
if (!IS)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Drop dead entries before touching the array so a recycled weak pointer can never be "revived".
|
||||
IS->PruneInteractionUIBlockers();
|
||||
|
||||
const bool bAlreadyBlocking = IS->InteractionUIBlockers.ContainsByPredicate(
|
||||
[Blocker](const TWeakObjectPtr<UObject>& Entry) { return Entry.Get() == Blocker; });
|
||||
|
||||
if (bAlreadyBlocking)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const TWeakObjectPtr<UObject> BlockerRef(Blocker);
|
||||
IS->InteractionUIBlockers.Add(BlockerRef);
|
||||
|
||||
UE_LOG(LogTemp, Verbose, TEXT("Interaction UI blocked by %s (%d blocker(s) active)"),
|
||||
*Blocker->GetName(), IS->InteractionUIBlockers.Num());
|
||||
|
||||
// A widget might be on screen right now, take it down instead of waiting for the next tick.
|
||||
IS->HideCurrentInteractableUI();
|
||||
}
|
||||
|
||||
void UInteractionSubsystem::RemoveInteractionUIBlock(const UObject* WorldContextObject, UObject* Blocker)
|
||||
{
|
||||
if (!IsValid(WorldContextObject) || !IsValid(WorldContextObject->GetWorld()) || !IsValid(Blocker))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UInteractionSubsystem* IS = WorldContextObject->GetWorld()->GetSubsystem<UInteractionSubsystem>();
|
||||
if (!IS)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
IS->InteractionUIBlockers.RemoveAll(
|
||||
[Blocker](const TWeakObjectPtr<UObject>& Entry) { return Entry.Get() == Blocker; });
|
||||
|
||||
// Nothing else to do - the next Tick re-evaluates the closest interactable and brings the UI back.
|
||||
}
|
||||
|
||||
bool UInteractionSubsystem::IsInteractionUIBlocked(const UObject* WorldContextObject)
|
||||
{
|
||||
if (!IsValid(WorldContextObject) || !IsValid(WorldContextObject->GetWorld()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
UInteractionSubsystem* IS = WorldContextObject->GetWorld()->GetSubsystem<UInteractionSubsystem>();
|
||||
return IS ? IS->HasInteractionUIBlocks() : false;
|
||||
}
|
||||
|
||||
bool UInteractionSubsystem::HasInteractionUIBlocks() const
|
||||
{
|
||||
for (const TWeakObjectPtr<UObject>& Blocker : InteractionUIBlockers)
|
||||
{
|
||||
if (Blocker.IsValid())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void UInteractionSubsystem::PruneInteractionUIBlockers()
|
||||
{
|
||||
InteractionUIBlockers.RemoveAll([](const TWeakObjectPtr<UObject>& Entry) { return !Entry.IsValid(); });
|
||||
}
|
||||
|
||||
void UInteractionSubsystem::HideCurrentInteractableUI()
|
||||
{
|
||||
if (CurrentlyInteractableObject.IsValid())
|
||||
{
|
||||
IInteractableActorInterface::Execute_ToggleInteractableUI(CurrentlyInteractableObject.Get(), false);
|
||||
}
|
||||
|
||||
CurrentlyInteractableObject = nullptr;
|
||||
}
|
||||
|
||||
void UInteractionSubsystem::RescanWorld()
|
||||
{
|
||||
UWorld* World = GetWorld();
|
||||
|
||||
@@ -32,6 +32,22 @@ public:
|
||||
UFUNCTION(BlueprintCallable, Category = "Interactable")
|
||||
static void UpdateInteractableText(AActor* Interactable);
|
||||
|
||||
/**
|
||||
* Keeps the interaction UI hidden until the same Blocker calls RemoveInteractionUIBlock.
|
||||
* Only the UI is affected - GetClosestInteractable() and Execute_Interact() keep working normally,
|
||||
* so gameplay interaction is never disabled by this.
|
||||
* Any number of blockers can be registered at the same time; the UI only comes back once the
|
||||
* last one has released it.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Interactable|UI", meta = (WorldContext = "WorldContextObject"))
|
||||
static void AddInteractionUIBlock(const UObject* WorldContextObject, UObject* Blocker);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "Interactable|UI", meta = (WorldContext = "WorldContextObject"))
|
||||
static void RemoveInteractionUIBlock(const UObject* WorldContextObject, UObject* Blocker);
|
||||
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Interactable|UI", meta = (WorldContext = "WorldContextObject"))
|
||||
static bool IsInteractionUIBlocked(const UObject* WorldContextObject);
|
||||
|
||||
protected:
|
||||
|
||||
UFUNCTION()
|
||||
@@ -40,9 +56,26 @@ protected:
|
||||
void RegisterInteractableActor(AActor* InteractableActor);
|
||||
void UnregisterInteractableActor(AActor* InteractableActor);
|
||||
|
||||
/** True while at least one still-alive blocker is registered */
|
||||
bool HasInteractionUIBlocks() const;
|
||||
|
||||
/** Drops blockers that have been destroyed, so a stale entry can never keep the UI hidden forever */
|
||||
void PruneInteractionUIBlockers();
|
||||
|
||||
/** Immediately takes down whatever interaction UI is currently shown */
|
||||
void HideCurrentInteractableUI();
|
||||
|
||||
UPROPERTY()
|
||||
TArray<TWeakObjectPtr<AActor>> InteractableActorList;
|
||||
|
||||
UPROPERTY()
|
||||
TWeakObjectPtr<AActor> CurrentlyInteractableObject;
|
||||
|
||||
/**
|
||||
* Objects currently holding the interaction UI hidden.
|
||||
* Weak on purpose: if a blocker is destroyed without releasing, the entry stops counting by itself.
|
||||
* Deliberately not a UPROPERTY - TWeakObjectPtr needs no GC reflection and this array must never
|
||||
* keep a blocker alive.
|
||||
*/
|
||||
TArray<TWeakObjectPtr<UObject>> InteractionUIBlockers;
|
||||
};
|
||||
|
||||
@@ -153,13 +153,6 @@ void ASeedBedActor::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
||||
Super::EndPlay(EndPlayReason);
|
||||
}
|
||||
|
||||
void ASeedBedActor::OnConstruction(const FTransform& Transform)
|
||||
{
|
||||
Super::OnConstruction(Transform);
|
||||
|
||||
RefreshAllSlotVisuals();
|
||||
}
|
||||
|
||||
void ASeedBedActor::Tick(float DeltaTime)
|
||||
{
|
||||
Super::Tick(DeltaTime);
|
||||
|
||||
@@ -213,7 +213,6 @@ public:
|
||||
protected:
|
||||
virtual void BeginPlay() override;
|
||||
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
|
||||
virtual void OnConstruction(const FTransform& Transform) override;
|
||||
|
||||
/** Creates (or rebuilds) the components of a single slot and adds it to the slot map */
|
||||
FSeedBedSeedSlot& CreateSlot(FIntVector2 SlotCoordinate, bool bRebuildIfExisting);
|
||||
|
||||
@@ -190,11 +190,6 @@ void AEleriPlayerController::SetupInputComponent()
|
||||
PlayerEnhancedInputComponent->BindAction(WaterSeedAction, ETriggerEvent::Started, this, &AEleriPlayerController::OnHarvestSeed);
|
||||
}
|
||||
|
||||
if (PlantSeedAction)
|
||||
{
|
||||
PlayerEnhancedInputComponent->BindAction(PlantSeedAction, ETriggerEvent::Started, this, &AEleriPlayerController::OnPlantSeedAction);
|
||||
}
|
||||
|
||||
if (UiBackAction)
|
||||
{
|
||||
PlayerEnhancedInputComponent->BindAction(UiBackAction, ETriggerEvent::Started, this, &AEleriPlayerController::OnControllerBackAction);
|
||||
@@ -1132,7 +1127,10 @@ void AEleriPlayerController::OnInteract(const FInputActionValue &Value)
|
||||
return;
|
||||
|
||||
if (PlayerCharacter->GetIsPlanting())
|
||||
{
|
||||
TryPlantSelectedSeed();
|
||||
return;
|
||||
}
|
||||
|
||||
// Ask the InteractionSystem plugin's subsystem for the closest valid interactable
|
||||
// and fire the interaction on it if we have one.
|
||||
@@ -1149,7 +1147,11 @@ void AEleriPlayerController::OnInteractExit(const FInputActionValue &Value)
|
||||
if (!IsValid(PlayerCharacter))
|
||||
return;
|
||||
|
||||
// if (PlayerCharacter->GetIsPlanting()) PlayerCharacter->PlantSeed();
|
||||
if (PlayerCharacter->GetIsPlanting())
|
||||
{
|
||||
ClearSeedToPlant();
|
||||
return;
|
||||
}
|
||||
|
||||
PlayerCharacter->OnInteractExit.Broadcast();
|
||||
OnChangeInputContext.Broadcast(1);
|
||||
@@ -1200,22 +1202,6 @@ void AEleriPlayerController::SetPlantingMode(bool bActive)
|
||||
return;
|
||||
|
||||
PlayerCharacter->SetPlanting(bActive);
|
||||
|
||||
// Planting gets its own context so it can take a key over without touching the other contexts
|
||||
if (IsValid(PlantingContext))
|
||||
{
|
||||
if (UEnhancedInputLocalPlayerSubsystem *Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(GetLocalPlayer()))
|
||||
{
|
||||
if (bActive)
|
||||
{
|
||||
Subsystem->AddMappingContext(PlantingContext, 6);
|
||||
}
|
||||
else
|
||||
{
|
||||
Subsystem->RemoveMappingContext(PlantingContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AEleriPlayerController::TryPlantSelectedSeed()
|
||||
@@ -1260,14 +1246,6 @@ void AEleriPlayerController::TryPlantSelectedSeed()
|
||||
}
|
||||
}
|
||||
|
||||
void AEleriPlayerController::OnPlantSeedAction(const FInputActionValue &Value)
|
||||
{
|
||||
if (!IsValid(PlayerCharacter))
|
||||
return;
|
||||
|
||||
TryPlantSelectedSeed();
|
||||
}
|
||||
|
||||
void AEleriPlayerController::StartMinigame(EMinigameType MinigameType, const TSoftObjectPtr<UBaseMinigameDataAsset> &DataAsset)
|
||||
{
|
||||
if (!IsValid(PlayerCharacter) ||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "Components/StatForgeComponent.h"
|
||||
#include "GameFramework/SpringArmComponent.h"
|
||||
#include "ProjectEleri/Items/SeedBedActor.h"
|
||||
#include "Subsystem/InteractionSubsystem.h"
|
||||
|
||||
|
||||
#if WITH_EDITOR
|
||||
@@ -339,6 +340,10 @@ void AMyCharacter::SetPlanting(bool bActive)
|
||||
|
||||
if (bIsPlanting)
|
||||
{
|
||||
// Planting takes over the interact input, so keep the plugin's interaction UI out of the way.
|
||||
// The plugin knows nothing about planting - it just holds/releases a generic UI block.
|
||||
UInteractionSubsystem::AddInteractionUIBlock(this, this);
|
||||
|
||||
SeedBedActors.Empty();
|
||||
TArray<AActor*> OutActors;
|
||||
UGameplayStatics::GetAllActorsOfClass(this, ASeedBedActor::StaticClass(), OutActors);
|
||||
@@ -349,12 +354,16 @@ void AMyCharacter::SetPlanting(bool bActive)
|
||||
}
|
||||
else
|
||||
{
|
||||
UInteractionSubsystem::RemoveInteractionUIBlock(this, this);
|
||||
|
||||
if (LastClosestSeedBedActor)
|
||||
{
|
||||
LastClosestSeedBedActor->StopHighlightingSlot();
|
||||
LastClosestSeedBedActor = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
PlayerController->GetMainGameWidget()->SwitchKeybindsDisplay(bActive ? 6 : 1);
|
||||
}
|
||||
|
||||
void AMyCharacter::BeginWatering()
|
||||
|
||||
@@ -225,14 +225,6 @@ public:
|
||||
UPROPERTY(EditDefaultsOnly, Category = "Action Input")
|
||||
UInputAction* WaterSeedAction;
|
||||
|
||||
/** Action used to plant the selected seed into the closest seed bed slot */
|
||||
UPROPERTY(EditDefaultsOnly, Category = "Action Input")
|
||||
UInputAction* PlantSeedAction;
|
||||
|
||||
/** Mapping context pushed while planting, it may only contain actions that planting needs */
|
||||
UPROPERTY(EditDefaultsOnly, Category = "Action Input")
|
||||
UInputMappingContext* PlantingContext;
|
||||
|
||||
UPROPERTY(EditDefaultsOnly, Category = "Book Input")
|
||||
UInputAction* BookNextPageAction;
|
||||
UPROPERTY(EditDefaultsOnly, Category = "Book Input")
|
||||
|
||||
Reference in New Issue
Block a user