You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

278 lines
8.8 KiB

#include <gtest/gtest.h>
#ifdef USE_SDL3
#include <SDL3/SDL.h>
#else
#include <SDL.h>
#endif
#include "engine/assets.hpp"
#include "tables/townerdat.hpp"
#include "towners.h"
#include "utils/paths.h"
namespace devilution {
namespace {
void SetTestAssetsPath()
{
const std::string assetsPath = paths::BasePath() + "/assets/";
paths::SetAssetsPath(assetsPath);
}
void InitializeSDL()
{
#ifdef USE_SDL3
if (!SDL_Init(SDL_INIT_EVENTS)) {
// SDL_Init returns 0 on success in SDL3
return;
}
#elif !defined(USE_SDL1)
if (SDL_Init(SDL_INIT_EVENTS) >= 0) {
return;
}
#else
if (SDL_Init(0) >= 0) {
return;
}
#endif
// If we get here, SDL initialization failed
// In tests, we'll continue anyway as file operations might still work
}
/**
* @brief Helper to find a towner data entry by type.
*/
const TownerDataEntry *FindTownerDataByType(_talker_id type)
{
for (const auto &entry : TownersDataEntries) {
if (entry.type == type) {
return &entry;
}
}
return nullptr;
}
} // namespace
TEST(TownerDat, LoadTownerData)
{
InitializeSDL();
SetTestAssetsPath();
LoadTownerData();
// Verify we loaded the expected number of towners from assets
ASSERT_GE(TownersDataEntries.size(), 4u) << "Should load at least 4 towners from assets";
// Check Griswold (TOWN_SMITH)
const TownerDataEntry *smith = FindTownerDataByType(TOWN_SMITH);
ASSERT_NE(smith, nullptr) << "Should find TOWN_SMITH data";
EXPECT_EQ(smith->type, TOWN_SMITH);
EXPECT_EQ(smith->name, "Griswold the Blacksmith");
EXPECT_EQ(smith->position.x, 62);
EXPECT_EQ(smith->position.y, 63);
EXPECT_EQ(smith->direction, Direction::SouthWest);
EXPECT_EQ(smith->animWidth, 96);
EXPECT_EQ(smith->animPath, "towners\\smith\\smithn");
EXPECT_EQ(smith->animFrames, 16);
EXPECT_EQ(smith->animDelay, 3);
EXPECT_EQ(smith->gossipTexts.size(), 11u);
EXPECT_EQ(smith->gossipTexts[0], TEXT_GRISWOLD2);
EXPECT_EQ(smith->gossipTexts[10], TEXT_GRISWOLD13);
ASSERT_GE(smith->animOrder.size(), 4u);
EXPECT_EQ(smith->animOrder[0], 4);
EXPECT_EQ(smith->animOrder[3], 7);
// Check Pepin (TOWN_HEALER)
const TownerDataEntry *healer = FindTownerDataByType(TOWN_HEALER);
ASSERT_NE(healer, nullptr) << "Should find TOWN_HEALER data";
EXPECT_EQ(healer->type, TOWN_HEALER);
EXPECT_EQ(healer->name, "Pepin the Healer");
EXPECT_EQ(healer->position.x, 55);
EXPECT_EQ(healer->position.y, 79);
EXPECT_EQ(healer->direction, Direction::SouthEast);
EXPECT_EQ(healer->animFrames, 20);
EXPECT_EQ(healer->gossipTexts.size(), 9u);
ASSERT_GE(healer->animOrder.size(), 3u);
// Check Dead Guy (TOWN_DEADGUY) - has empty gossip texts and animOrder
const TownerDataEntry *deadguy = FindTownerDataByType(TOWN_DEADGUY);
ASSERT_NE(deadguy, nullptr) << "Should find TOWN_DEADGUY data";
EXPECT_EQ(deadguy->type, TOWN_DEADGUY);
EXPECT_EQ(deadguy->name, "Wounded Townsman");
EXPECT_EQ(deadguy->direction, Direction::North);
EXPECT_TRUE(deadguy->gossipTexts.empty()) << "Dead guy should have no gossip texts";
EXPECT_TRUE(deadguy->animOrder.empty()) << "Dead guy should have no custom anim order";
// Check Cow (TOWN_COW) - has empty animPath but animFrames and animDelay are set
const TownerDataEntry *cow = FindTownerDataByType(TOWN_COW);
ASSERT_NE(cow, nullptr) << "Should find TOWN_COW data";
EXPECT_EQ(cow->type, TOWN_COW);
EXPECT_EQ(cow->name, "Cow");
EXPECT_EQ(cow->position.x, 58);
EXPECT_EQ(cow->position.y, 16);
EXPECT_EQ(cow->direction, Direction::SouthWest);
EXPECT_EQ(cow->animWidth, 128);
EXPECT_TRUE(cow->animPath.empty()) << "Cow should have empty animPath";
EXPECT_EQ(cow->animFrames, 12);
EXPECT_EQ(cow->animDelay, 3);
EXPECT_TRUE(cow->gossipTexts.empty()) << "Cow should have no gossip texts";
EXPECT_TRUE(cow->animOrder.empty()) << "Cow should have no custom anim order";
}
TEST(TownerDat, LoadQuestDialogTable)
{
InitializeSDL();
SetTestAssetsPath();
LoadTownerData();
// Check Smith quest dialogs
EXPECT_EQ(GetTownerQuestDialog(TOWN_SMITH, Q_BUTCHER), TEXT_BUTCH5);
EXPECT_EQ(GetTownerQuestDialog(TOWN_SMITH, Q_LTBANNER), TEXT_BANNER6);
EXPECT_EQ(GetTownerQuestDialog(TOWN_SMITH, Q_SKELKING), TEXT_KING7);
EXPECT_EQ(GetTownerQuestDialog(TOWN_SMITH, Q_ROCK), TEXT_INFRA6);
// Check Healer quest dialogs
EXPECT_EQ(GetTownerQuestDialog(TOWN_HEALER, Q_BUTCHER), TEXT_BUTCH3);
EXPECT_EQ(GetTownerQuestDialog(TOWN_HEALER, Q_LTBANNER), TEXT_BANNER4);
EXPECT_EQ(GetTownerQuestDialog(TOWN_HEALER, Q_SKELKING), TEXT_KING5);
// Check Dead guy quest dialogs
EXPECT_EQ(GetTownerQuestDialog(TOWN_DEADGUY, Q_BUTCHER), TEXT_NONE);
EXPECT_EQ(GetTownerQuestDialog(TOWN_DEADGUY, Q_LTBANNER), TEXT_NONE);
}
TEST(TownerDat, SetTownerQuestDialog)
{
InitializeSDL();
SetTestAssetsPath();
LoadTownerData();
// Verify initial value from assets
EXPECT_EQ(GetTownerQuestDialog(TOWN_SMITH, Q_MUSHROOM), TEXT_MUSH6);
// Modify it
SetTownerQuestDialog(TOWN_SMITH, Q_MUSHROOM, TEXT_MUSH1);
// Verify it changed
EXPECT_EQ(GetTownerQuestDialog(TOWN_SMITH, Q_MUSHROOM), TEXT_MUSH1);
// Reset to original value for other tests
SetTownerQuestDialog(TOWN_SMITH, Q_MUSHROOM, TEXT_MUSH6);
}
TEST(TownerDat, GetQuestDialogInvalidType)
{
InitializeSDL();
SetTestAssetsPath();
LoadTownerData();
// Invalid towner type should return TEXT_NONE
// Use a value that's guaranteed to be invalid (beyond enum range)
_talker_id invalidType = static_cast<_talker_id>(255);
_speech_id result = GetTownerQuestDialog(invalidType, Q_BUTCHER);
EXPECT_EQ(result, TEXT_NONE) << "Should return TEXT_NONE for invalid towner type";
}
TEST(TownerDat, GetQuestDialogInvalidQuest)
{
InitializeSDL();
SetTestAssetsPath();
LoadTownerData();
// Invalid quest ID should return TEXT_NONE
_speech_id result = GetTownerQuestDialog(TOWN_SMITH, static_cast<quest_id>(-1));
EXPECT_EQ(result, TEXT_NONE) << "Should return TEXT_NONE for invalid quest ID";
result = GetTownerQuestDialog(TOWN_SMITH, static_cast<quest_id>(MAXQUESTS));
EXPECT_EQ(result, TEXT_NONE) << "Should return TEXT_NONE for out-of-range quest ID";
}
TEST(TownerDat, TownerLongNamesPopulated)
{
InitializeSDL();
SetTestAssetsPath();
LoadTownerData();
// Build TownerLongNames as InitTowners() does
TownerLongNames.clear();
for (const auto &entry : TownersDataEntries) {
TownerLongNames.try_emplace(entry.type, entry.name);
}
// Verify TownerLongNames is populated correctly
EXPECT_FALSE(TownerLongNames.empty()) << "TownerLongNames should not be empty after loading";
// Check specific entries
auto smithIt = TownerLongNames.find(TOWN_SMITH);
ASSERT_NE(smithIt, TownerLongNames.end()) << "Should find TOWN_SMITH in TownerLongNames";
EXPECT_EQ(smithIt->second, "Griswold the Blacksmith");
auto healerIt = TownerLongNames.find(TOWN_HEALER);
ASSERT_NE(healerIt, TownerLongNames.end()) << "Should find TOWN_HEALER in TownerLongNames";
EXPECT_EQ(healerIt->second, "Pepin the Healer");
}
TEST(TownerDat, GetNumTownerTypes)
{
InitializeSDL();
SetTestAssetsPath();
LoadTownerData();
// Build TownerLongNames as InitTowners() does
TownerLongNames.clear();
for (const auto &entry : TownersDataEntries) {
TownerLongNames.try_emplace(entry.type, entry.name);
}
// GetNumTownerTypes should return the number of unique towner types
size_t numTypes = GetNumTownerTypes();
EXPECT_GT(numTypes, 0u) << "Should have at least one towner type";
EXPECT_EQ(numTypes, TownerLongNames.size()) << "GetNumTownerTypes should match TownerLongNames size";
}
TEST(TownerDat, MultipleCowsOnlyOneType)
{
InitializeSDL();
SetTestAssetsPath();
LoadTownerData();
// Count how many TOWN_COW entries exist in the data
size_t cowCount = 0;
for (const auto &entry : TownersDataEntries) {
if (entry.type == TOWN_COW) {
cowCount++;
}
}
// There should be multiple cows but only one type entry
EXPECT_GT(cowCount, 1u) << "TSV should have multiple cow entries";
// Build TownerLongNames
TownerLongNames.clear();
for (const auto &entry : TownersDataEntries) {
TownerLongNames.try_emplace(entry.type, entry.name);
}
// But only one entry in TownerLongNames for TOWN_COW
auto cowIt = TownerLongNames.find(TOWN_COW);
ASSERT_NE(cowIt, TownerLongNames.end()) << "Should find TOWN_COW in TownerLongNames";
EXPECT_EQ(cowIt->second, "Cow");
}
TEST(TownerDat, QuestDialogOptionalColumns)
{
InitializeSDL();
SetTestAssetsPath();
LoadTownerData();
// Verify that missing quest columns default to TEXT_NONE
// Q_FARMER, Q_GIRL, Q_DEFILER, Q_NAKRUL, Q_CORNSTN, Q_JERSEY may not be in base TSV
// but the code should handle them gracefully
_speech_id result = GetTownerQuestDialog(TOWN_SMITH, Q_FARMER);
// Should be TEXT_NONE since TOWN_SMITH doesn't have farmer quest dialog
EXPECT_EQ(result, TEXT_NONE) << "Should return TEXT_NONE for unused quest columns";
}
} // namespace devilution