From 67af75b53cb8c9d1d63d0f21bcf127b01ecc9b14 Mon Sep 17 00:00:00 2001 From: Stefan Stefanov Date: Sun, 12 Nov 2023 00:42:01 +0200 Subject: [PATCH] Base functionality of the OvenPawn and the cookspots --- Source/minicook/OvenPawn.cpp | 149 +++++++++++++++++++++++++++++++++++ Source/minicook/OvenPawn.h | 134 +++++++++++++++++++++++++++++++ 2 files changed, 283 insertions(+) create mode 100644 Source/minicook/OvenPawn.cpp create mode 100644 Source/minicook/OvenPawn.h diff --git a/Source/minicook/OvenPawn.cpp b/Source/minicook/OvenPawn.cpp new file mode 100644 index 0000000..84e5317 --- /dev/null +++ b/Source/minicook/OvenPawn.cpp @@ -0,0 +1,149 @@ +// Stefan Stefanov 2023 + + +#include "OvenPawn.h" + +auto FCookstove::Setup(const UDataTable *DataTablePtr, const int32 MealID, + const double InUndercookedMultiplier, + const double InOvercookedMultiplier, + const double InScoreMultiplier) -> bool +{ + // Set the current meal id + CurrentMealID = MealID; + CurrentlyCooking = true; + // Get the meal from the provided RecipesDataTable + Meal = GetMealFromTable(DataTablePtr, MealID); + if (Meal.ID == -1) + { + UE_LOG(LogTemp, Error, TEXT("Couldn't find a meal with ID: %d"), MealID); + return false; + } + + SetCookingTime(FTimespan::FromMilliseconds(Meal.TotalCookingTimeMS)); + TargetHeat = FMath::Clamp(Meal.TargetHeat, 0.0, 1.0); + HeatHalfRange = FMath::Clamp(Meal.HeatHalfRange, 0.0, 1.0); + + this->UndercookedMultiplier = InUndercookedMultiplier; + this->OvercookedMultiplier = InOvercookedMultiplier; + this->ScoreMultiplier = InScoreMultiplier; + return true; +} + +auto FCookstove::GetMealFromTable(const UDataTable *DataTablePtr, const int32 MealID) -> FMeal +{ + if (DataTablePtr == nullptr || MealID < 0) + return FMeal{}; + + const FName MealRowName = DataTablePtr->GetRowNames()[MealID]; + FMeal *Meal = DataTablePtr->FindRow(MealRowName, ""); + + return *Meal; +} + +void FCookstove::SetCookingTime(const FTimespan CookingTimespan) +{ + StartTime = FDateTime::Now(); + EndTime = StartTime + CookingTimespan; +} + +void FCookstove::Update(const float DeltaTime, int32 &PlayerScore) +{ + // If the id is not set, we have no work + if (CurrentMealID == -1 || CurrentlyCooking == false) + return; + + // HeatHalfRange can be used to calculate an acceptable range + if (CurrentHeat < TargetHeat - HeatHalfRange) + { + CookingStats.TotalTimeUndercookedMS += DeltaTime; + } + else if (CurrentHeat > TargetHeat + HeatHalfRange) + { + CookingStats.TotalTimeOvercookedMS += DeltaTime; + } + + if (IsMealDone()) + PlayerScore += FinishMeal(); +} + +auto FCookstove::GetRemainingCookingTime() const -> FTimespan +{ + const FDateTime Now = FDateTime::Now(); + if (Now >= EndTime) + return 0.0; + + return (EndTime - Now).GetTotalMilliseconds(); +} + +auto FCookstove::IsMealDone() const -> bool +{ + return GetRemainingCookingTime() == 0.0; +} + +auto FCookstove::FinishMeal() -> int32 +{ + CookingStats.TotalCookedMeals++; + CurrentlyCooking = false; + + // Calculate the score based on multipliers + int32 Score{0}; + Score += CookingStats.TotalTimeUndercookedMS * UndercookedMultiplier; + Score += CookingStats.TotalTimeOvercookedMS * OvercookedMultiplier; + Score += + (Meal.TotalCookingTimeMS - (CookingStats.TotalTimeOvercookedMS + CookingStats.TotalTimeUndercookedMS)) + * ScoreMultiplier; + return Score; +} + +AOvenPawn::AOvenPawn() +{ + PrimaryActorTick.bCanEverTick = true; +} + +void AOvenPawn::BeginPlay() +{ + Super::BeginPlay(); + + // Setup the cookstove related items + Cookstoves.AddDefaulted(CookstoveObjects.Num()); +} + +void AOvenPawn::Tick(float DeltaTime) +{ + Super::Tick(DeltaTime); + + int32 ScoreThisFrame{0}; + // Check the cooking stoves and update 'em + for (FCookstove Cookstove : Cookstoves) + { + Cookstove.Update(DeltaTime, ScoreThisFrame); + } + + CurrentPlayerScore += ScoreThisFrame; +} + +// Called to bind functionality to input +void AOvenPawn::SetupPlayerInputComponent(UInputComponent *PlayerInputComponent) +{ + Super::SetupPlayerInputComponent(PlayerInputComponent); + +} + +auto AOvenPawn::TrySpawningNewRandomMeal() -> bool +{ + bool bSuccess{false}; + for (FCookstove Cookstove : Cookstoves) + { + if (!Cookstove.CurrentlyCooking) + { + const int32 MealID = FMath::RandRange(0, RecipesDataTable->GetRowNames().Num()); + bSuccess = Cookstove + .Setup(RecipesDataTable, MealID, + UndercookedMultiplier, + OvercookedMultiplier, + ScoreMultiplier); + break; + } + } + return bSuccess; +} \ No newline at end of file diff --git a/Source/minicook/OvenPawn.h b/Source/minicook/OvenPawn.h new file mode 100644 index 0000000..2337145 --- /dev/null +++ b/Source/minicook/OvenPawn.h @@ -0,0 +1,134 @@ +// Stefan Stefanov 2023 + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/Pawn.h" +#include "Misc/DateTime.h" +#include "Misc/Timespan.h" +#include "Engine/DataTable.h" + +#include "OvenPawn.generated.h" + +USTRUCT() +struct FMeal : public FTableRowBase +{ + GENERATED_BODY() + +public: + int32 ID{-1}; + FString MealName; + double TargetHeat{0.0}; + double HeatHalfRange{0.0}; + double TotalCookingTimeMS{0.0}; +}; + +struct FCookingStats +{ + int32 TotalCookedMeals{0}; + int32 TotalTimeUndercookedMS{0}; + int32 TotalTimeOvercookedMS{0}; +}; + +struct FCookstove +{ + // MealID from the data table + int32 CurrentMealID{-1}; + FMeal Meal; + + // When the meal started cooking + FDateTime StartTime; + // and when it should be done cooking + FDateTime EndTime; + bool CurrentlyCooking{false}; + + // Should always be in the range [0.0, 1.0] + double TargetHeat{0.5}; + double HeatHalfRange{0.15}; + double CurrentHeat{0.0}; + + double UndercookedMultiplier{1.0}; + double OvercookedMultiplier{1.0}; + double ScoreMultiplier{1.0}; + + FCookingStats CookingStats{}; + + /** + * @brief Given a @param DataTablePtr and a @param MealID, configure the cookstove to start cooking the meal. + * @return Returns whether we found a meal with the provided @param MealID and successfully started cooking it. + */ + auto Setup(const UDataTable *DataTablePtr, + const int32 MealID, + const double InUndercookedMultiplier, + const double InOvercookedMultiplier, + const double InScoreMultiplier) -> bool; + + static auto GetMealFromTable(const UDataTable *DataTablePtr, int32 MealID) -> FMeal; + + void SetCookingTime(const FTimespan CookingTimespan); + + /** + * @brief Updates the cookstove for this tick, if a meal is cooking, updates the stats for it and if it finishes it calculates the score, etc... + * @param DeltaTime Time between N-1 and N-2 frames in milliseconds + * @param PlayerScore Takes in the current player score in case a meal gets finished and the score must be returned + */ + void Update(const float DeltaTime, int32 &PlayerScore); + + /** + * @brief Returns the remaining cooking time + */ + auto GetRemainingCookingTime() const -> FTimespan; + + /** + * @brief Check if the currently cooked meal is done cooking + */ + auto IsMealDone() const -> bool; + + /** + * @brief Finishes up cooking the current meal, setting up the necessary stats and state + * @return Returns the calculated score based on the stats of the cooked meal + */ + auto FinishMeal() -> int32; +}; + + +UCLASS() +class MINICOOK_API AOvenPawn : public APawn +{ + GENERATED_BODY() + +public: + AOvenPawn(); + +protected: + virtual void BeginPlay() override; + +public: + virtual void Tick(float DeltaTime) override; + + // Called to bind functionality to input + virtual void SetupPlayerInputComponent(class UInputComponent *PlayerInputComponent) override; + + bool TrySpawningNewRandomMeal(); + +public: + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Oven Settings") + double ScoreMultiplier{1.0}; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Oven Settings") + double UndercookedMultiplier{1.0}; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Oven Settings") + double OvercookedMultiplier{1.0}; + + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Oven Settings") + int32 CurrentPlayerScore{0}; + +private: + // An array of all the cookstoves, from left to right on the screen + TArray> CookstoveObjects; + TArray Cookstoves; + + // ReSharper disable once CppUE4ProbableMemoryIssuesWithUObject + TObjectPtr RecipesDataTable; +}; \ No newline at end of file