mirror of
https://github.com/EQEmu/Server.git
synced 2026-05-16 22:58:34 +00:00
[Zone] Zone State Automated Testing and Improvements (#4808)
* [Zone] Zone State Automated Testing and Improvements * Spawn condition * Update zone.cpp * Remove redundant logic * Update zone_state.cpp * TestZLocationDrift * Protect NPC resumed NPC's from being able to die
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
#include "../../zone.h"
|
||||
|
||||
inline void RunTest(const std::string &test_name, const std::string &expected, const std::string &actual)
|
||||
{
|
||||
if (expected == actual) {
|
||||
std::cout << "[✅] " << test_name << " PASSED\n";
|
||||
} else {
|
||||
std::cerr << "[❌] " << test_name << " FAILED\n";
|
||||
std::cerr << " 📌 Expected: " << expected << "\n";
|
||||
std::cerr << " ❌ Got: " << actual << "\n";
|
||||
std::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
inline void RunTest(const std::string &test_name, bool expected, bool actual)
|
||||
{
|
||||
if (expected == actual) {
|
||||
std::cout << "[✅] " << test_name << " PASSED\n";
|
||||
}
|
||||
else {
|
||||
std::cerr << "[❌] " << test_name << " FAILED\n";
|
||||
std::cerr << " 📌 Expected: " << (expected ? "true" : "false") << "\n";
|
||||
std::cerr << " ❌ Got: " << (actual ? "true" : "false") << "\n";
|
||||
std::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
inline void RunTest(const std::string &test_name, int expected, int actual)
|
||||
{
|
||||
if (expected == actual) {
|
||||
std::cout << "[✅] " << test_name << " PASSED\n";
|
||||
}
|
||||
else {
|
||||
std::cerr << "[❌] " << test_name << " FAILED\n";
|
||||
std::cerr << " 📌 Expected: " << expected << "\n";
|
||||
std::cerr << " ❌ Got: " << actual << "\n";
|
||||
std::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
extern Zone *zone;
|
||||
|
||||
inline void SetupZone(std::string zone_short_name, uint32 instance_id = 0) {
|
||||
LogSys.SilenceConsoleLogging();
|
||||
|
||||
LogSys.log_settings[Logs::ZoneState].log_to_console = std::getenv("DEBUG") ? 3 : 0;
|
||||
LogSys.log_settings[Logs::Info].log_to_console = std::getenv("DEBUG") ? 3 : 0;
|
||||
LogSys.log_settings[Logs::Spawns].log_to_console = std::getenv("DEBUG") ? 3 : 0;
|
||||
|
||||
// boot shell zone for testing
|
||||
Zone::Bootup(ZoneID(zone_short_name), 0, false);
|
||||
zone->StopShutdownTimer();
|
||||
entity_list.Process();
|
||||
entity_list.MobProcess();
|
||||
|
||||
LogSys.EnableConsoleLogging();
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
#include "../../common/http/httplib.h"
|
||||
#include "../../common/eqemu_logsys.h"
|
||||
#include "../../common/platform.h"
|
||||
#include "../../zone.h"
|
||||
#include "../../client.h"
|
||||
#include "../../common/net/eqstream.h"
|
||||
|
||||
extern Zone *zone;
|
||||
|
||||
void ZoneCLI::TestDataBuckets(int argc, char **argv, argh::parser &cmd, std::string &description)
|
||||
{
|
||||
if (cmd[{"-h", "--help"}]) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint32 break_length = 50;
|
||||
int failed_count = 0;
|
||||
|
||||
LogSys.SilenceConsoleLogging();
|
||||
|
||||
// boot shell zone for testing
|
||||
Zone::Bootup(ZoneID("qrg"), 0, false);
|
||||
zone->StopShutdownTimer();
|
||||
|
||||
entity_list.Process();
|
||||
entity_list.MobProcess();
|
||||
|
||||
LogSys.EnableConsoleLogging();
|
||||
|
||||
std::cout << "===========================================\n";
|
||||
std::cout << "⚙\uFE0F> Running DataBuckets Tests...\n";
|
||||
std::cout << "===========================================\n\n";
|
||||
|
||||
Client *client = new Client();
|
||||
|
||||
// Basic Key-Value Set/Get
|
||||
client->DeleteBucket("basic_key");
|
||||
client->SetBucket("basic_key", "simple_value");
|
||||
std::string value = client->GetBucket("basic_key");
|
||||
RunTest("Basic Key-Value Set/Get", "simple_value", value);
|
||||
|
||||
// Overwriting a Key
|
||||
client->SetBucket("basic_key", "new_value");
|
||||
value = client->GetBucket("basic_key");
|
||||
RunTest("Overwriting a Key", "new_value", value);
|
||||
|
||||
// Deleting a Key
|
||||
client->DeleteBucket("basic_key");
|
||||
value = client->GetBucket("basic_key");
|
||||
RunTest("Deleting a Key", "", value);
|
||||
|
||||
// Setting a Key with an Expiration
|
||||
client->SetBucket("expiring_key", "expires_soon", "S1");
|
||||
value = client->GetBucket("expiring_key");
|
||||
RunTest("Setting a Key with an Expiration", "expires_soon", value);
|
||||
|
||||
// Ensure Expired Key is Deleted
|
||||
std::this_thread::sleep_for(std::chrono::seconds(2));
|
||||
value = client->GetBucket("expiring_key");
|
||||
RunTest("Ensure Expired Key is Deleted", "", value);
|
||||
|
||||
// Cache Read/Write Consistency
|
||||
client->SetBucket("cache_key", "cached_value");
|
||||
value = client->GetBucket("cache_key");
|
||||
RunTest("Cache Read/Write Consistency", "cached_value", value);
|
||||
|
||||
// Cache Clears on Key Deletion
|
||||
client->DeleteBucket("cache_key");
|
||||
value = client->GetBucket("cache_key");
|
||||
RunTest("Cache Clears on Key Deletion", "", value);
|
||||
|
||||
// Setting a Full JSON String
|
||||
client->SetBucket("json_key", R"({"key1":"value1","key2":"value2"})");
|
||||
value = client->GetBucket("json_key");
|
||||
RunTest("Setting a Full JSON String", R"({"key1":"value1","key2":"value2"})", value);
|
||||
|
||||
// Overwriting JSON with a Simple String
|
||||
client->SetBucket("json_key", "string_value");
|
||||
value = client->GetBucket("json_key");
|
||||
RunTest("Overwriting JSON with a Simple String", "string_value", value);
|
||||
|
||||
// Deleting Non-Existent Key
|
||||
client->DeleteBucket("non_existent_key");
|
||||
value = client->GetBucket("non_existent_key");
|
||||
RunTest("Deleting Non-Existent Key", "", value);
|
||||
|
||||
// Basic Key-Value Storage**
|
||||
client->DeleteBucket("simple_key"); // Reset
|
||||
client->SetBucket("simple_key", "simple_value");
|
||||
value = client->GetBucket("simple_key");
|
||||
RunTest("Basic Key-Value Set/Get", "simple_value", value);
|
||||
|
||||
// Nested Key Storage**
|
||||
client->DeleteBucket("nested");
|
||||
client->SetBucket("nested.test1", "value1");
|
||||
client->SetBucket("nested.test2", "value2");
|
||||
value = client->GetBucket("nested");
|
||||
RunTest("Nested Key Set/Get", R"({"test1":"value1","test2":"value2"})", value);
|
||||
|
||||
// Prevent Overwriting Objects**
|
||||
client->DeleteBucket("nested");
|
||||
client->SetBucket("nested.test1.a", "value1");
|
||||
client->SetBucket("nested.test2.a", "value2");
|
||||
client->SetBucket("nested.test2", "new_value"); // Should be **rejected**
|
||||
value = client->GetBucket("nested");
|
||||
RunTest("Prevent Overwriting Objects", R"({"test1":{"a":"value1"},"test2":{"a":"value2"}})", value);
|
||||
|
||||
// Deleting a Specific Nested Key**
|
||||
client->DeleteBucket("nested");
|
||||
client->SetBucket("nested.test1", "value1");
|
||||
client->SetBucket("nested.test2", "value2");
|
||||
client->DeleteBucket("nested.test1");
|
||||
value = client->GetBucket("nested");
|
||||
RunTest("Delete Nested Key", R"({"test2":"value2"})", value);
|
||||
|
||||
// Deleting the Entire Parent Key**
|
||||
client->DeleteBucket("nested");
|
||||
value = client->GetBucket("nested");
|
||||
RunTest("Delete Parent Key", "", value);
|
||||
|
||||
// Expiration is Ignored for Nested Keys**
|
||||
client->DeleteBucket("exp_test");
|
||||
client->SetBucket("exp_test.nested", "data", "S20"); // Expiration ignored
|
||||
value = client->GetBucket("exp_test");
|
||||
RunTest("Expiration Ignored for Nested Keys", R"({"nested":"data"})", value);
|
||||
|
||||
// Cache Behavior**
|
||||
client->DeleteBucket("cache_test");
|
||||
client->SetBucket("cache_test", "cache_value");
|
||||
value = client->GetBucket("cache_test");
|
||||
RunTest("Cache Read/Write Consistency", "cache_value", value);
|
||||
|
||||
// Ensure Deleting Parent Key Clears Cache**
|
||||
client->DeleteBucket("cache_test");
|
||||
value = client->GetBucket("cache_test");
|
||||
RunTest("Cache Clears on Parent Delete", "", value);
|
||||
|
||||
// Setting an Entire JSON Object**
|
||||
client->DeleteBucket("full_json");
|
||||
client->SetBucket("full_json", R"({"key1":"value1","key2":{"subkey":"subvalue"}})");
|
||||
value = client->GetBucket("full_json");
|
||||
RunTest("Set and Retrieve Full JSON Structure", R"({"key1":"value1","key2":{"subkey":"subvalue"}})", value);
|
||||
|
||||
// Partial Nested Key Deletion within JSON**
|
||||
client->DeleteBucket("full_json");
|
||||
client->SetBucket("full_json", R"({"key1":"value1","key2":{"subkey":"subvalue"}})");
|
||||
client->DeleteBucket("full_json.key2");
|
||||
value = client->GetBucket("full_json");
|
||||
RunTest("Delete Nested Key within JSON", R"({"key1":"value1"})", value);
|
||||
|
||||
// Ensure Object Protection on Overwrite Attempt**
|
||||
client->DeleteBucket("complex");
|
||||
client->SetBucket("complex.nested.obj1", "data1");
|
||||
client->SetBucket("complex.nested.obj2", "data2");
|
||||
client->SetBucket("complex.nested", "overwrite_attempt"); // Should be rejected
|
||||
value = client->GetBucket("complex");
|
||||
RunTest("Ensure Object Protection on Overwrite Attempt", R"({"nested":{"obj1":"data1","obj2":"data2"}})", value);
|
||||
|
||||
// Deleting Non-Existent Key Doesn't Break Existing Data**
|
||||
client->DeleteBucket("complex");
|
||||
client->SetBucket("complex.nested.obj1", "data1");
|
||||
client->SetBucket("complex.nested.obj2", "data2");
|
||||
client->DeleteBucket("does_not_exist"); // Should do nothing
|
||||
value = client->GetBucket("complex");
|
||||
RunTest("Deleting Non-Existent Key Doesn't Break Existing Data", R"({"nested":{"obj1":"data1","obj2":"data2"}})", value);
|
||||
|
||||
// Get nested key value one level up **
|
||||
client->DeleteBucket("complex");
|
||||
client->SetBucket("complex.nested.obj1", "data1");
|
||||
client->SetBucket("complex.nested.obj2", "data2");
|
||||
value = client->GetBucket("complex.nested");
|
||||
RunTest("Get nested key value", R"({"obj1":"data1","obj2":"data2"})", value);
|
||||
|
||||
// Get nested key value deep **
|
||||
client->DeleteBucket("complex");
|
||||
client->SetBucket("complex.nested.obj1", "data1");
|
||||
client->SetBucket("complex.nested.obj2", "data2");
|
||||
value = client->GetBucket("complex.nested.obj2");
|
||||
RunTest("Get nested key value deep", R"(data2)", value);
|
||||
|
||||
// Retrieve Nested Key from Plain String**
|
||||
client->DeleteBucket("plain_string");
|
||||
client->SetBucket("plain_string", "some_value");
|
||||
value = client->GetBucket("plain_string.nested");
|
||||
RunTest("Retrieve Nested Key from Plain String", "", value);
|
||||
|
||||
// Store and Retrieve JSON Array**
|
||||
client->DeleteBucket("json_array");
|
||||
client->SetBucket("json_array", R"(["item1", "item2"])");
|
||||
value = client->GetBucket("json_array");
|
||||
RunTest("Store and Retrieve JSON Array", R"(["item1", "item2"])", value);
|
||||
|
||||
// // Prevent Overwriting Array with Object**
|
||||
// client->DeleteBucket("json_array");
|
||||
// client->SetBucket("json_array", R"(["item1", "item2"])");
|
||||
// client->SetBucket("json_array.item", "new_value"); // Should be rejected
|
||||
// value = client->GetBucket("json_array");
|
||||
// RunTest("Prevent Overwriting Array with Object", R"(["item1", "item2"])", value);
|
||||
|
||||
// Retrieve Non-Existent Nested Key**
|
||||
client->DeleteBucket("nested_partial");
|
||||
client->SetBucket("nested_partial.level1", R"({"exists": "yes"})");
|
||||
value = client->GetBucket("nested_partial.level1.non_existent");
|
||||
RunTest("Retrieve Non-Existent Nested Key", "", value);
|
||||
|
||||
// Overwriting Parent Key Deletes Children**
|
||||
client->DeleteBucket("nested_override");
|
||||
client->SetBucket("nested_override.child", "data");
|
||||
client->SetBucket("nested_override", "new_parent_value"); // Should remove `child`
|
||||
value = client->GetBucket("nested_override");
|
||||
RunTest("Overwriting Parent Key Deletes Children", "new_parent_value", value);
|
||||
|
||||
// Store and Retrieve Empty JSON Object**
|
||||
client->DeleteBucket("empty_json");
|
||||
client->SetBucket("empty_json", R"({})");
|
||||
value = client->GetBucket("empty_json");
|
||||
RunTest("Store and Retrieve Empty JSON Object", R"({})", value);
|
||||
|
||||
// Store and Retrieve JSON String**
|
||||
client->DeleteBucket("json_string");
|
||||
client->SetBucket("json_string", R"("this is a string")");
|
||||
value = client->GetBucket("json_string");
|
||||
RunTest("Store and Retrieve JSON String", R"("this is a string")", value);
|
||||
|
||||
// Deeply Nested Key Retrieval**
|
||||
client->DeleteBucket("deep_nested");
|
||||
client->SetBucket("deep_nested.level1.level2.level3.level4.level5", "final_value");
|
||||
value = client->GetBucket("deep_nested.level1.level2.level3.level4.level5");
|
||||
RunTest("Deeply Nested Key Retrieval", "final_value", value);
|
||||
|
||||
// Setting a Key with an Expiration
|
||||
client->SetBucket("nested_expire.test.test", "shouldnt_expire", "S1");
|
||||
value = client->GetBucket("nested_expire");
|
||||
std::this_thread::sleep_for(std::chrono::seconds(2));
|
||||
RunTest("Setting a nested key with an expiration protection test", R"({"test":{"test":"shouldnt_expire"}})", value);
|
||||
|
||||
// Delete Deep Nested Key Keeps Parent**
|
||||
// client->DeleteBucket("deep_nested");
|
||||
// client->SetBucket("deep_nested.level1.level2.level3", R"({"key": "value"})");
|
||||
// client->DeleteBucket("deep_nested.level1.level2.level3.key");
|
||||
// value = client->GetBucket("deep_nested.level1.level2.level3");
|
||||
// RunTest("Delete Deep Nested Key Keeps Parent", "{}", value);
|
||||
|
||||
std::cout << "\n===========================================\n";
|
||||
std::cout << "✅ All DataBucket Tests Completed!\n";
|
||||
std::cout << "===========================================\n";
|
||||
}
|
||||
@@ -0,0 +1,536 @@
|
||||
#include "../../common/http/httplib.h"
|
||||
#include "../../common/eqemu_logsys.h"
|
||||
#include "../../common/platform.h"
|
||||
#include "../../zone.h"
|
||||
#include "../../client.h"
|
||||
#include "../../common/net/eqstream.h"
|
||||
#include "../../common/json/json.hpp"
|
||||
|
||||
extern Zone *zone;
|
||||
using json = nlohmann::json;
|
||||
|
||||
struct HandinEntry {
|
||||
std::string item_id = "0";
|
||||
uint32 count = 0;
|
||||
const EQ::ItemInstance *item = nullptr;
|
||||
bool is_multiquest_item = false; // state
|
||||
};
|
||||
|
||||
struct HandinMoney {
|
||||
uint32 platinum = 0;
|
||||
uint32 gold = 0;
|
||||
uint32 silver = 0;
|
||||
uint32 copper = 0;
|
||||
};
|
||||
|
||||
struct Handin {
|
||||
std::vector<HandinEntry> items = {}; // items can be removed from this set as successful handins are made
|
||||
HandinMoney money = {}; // money can be removed from this set as successful handins are made
|
||||
};
|
||||
|
||||
struct TestCase {
|
||||
std::string description;
|
||||
Handin hand_in;
|
||||
Handin required;
|
||||
Handin returned;
|
||||
bool handin_check_result;
|
||||
};
|
||||
|
||||
void RunSerializedTest(const std::string &test_name, const std::string &expected, const std::string &actual)
|
||||
{
|
||||
if (expected == actual) {
|
||||
std::cout << "[✅] " << test_name << " PASSED\n";
|
||||
}
|
||||
else {
|
||||
std::cerr << "[❌] " << test_name << " FAILED\n";
|
||||
std::cerr << " 📌 Expected: " << expected << "\n";
|
||||
std::cerr << " ❌ Got: " << actual << "\n";
|
||||
std::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
std::string SerializeHandin(const std::map<std::string, uint32> &items, const HandinMoney &money)
|
||||
{
|
||||
json j;
|
||||
j["items"] = items;
|
||||
j["money"] = {
|
||||
{"platinum", money.platinum},
|
||||
{"gold", money.gold},
|
||||
{"silver", money.silver},
|
||||
{"copper", money.copper}
|
||||
};
|
||||
return j.dump();
|
||||
}
|
||||
|
||||
void ZoneCLI::TestNpcHandins(int argc, char **argv, argh::parser &cmd, std::string &description)
|
||||
{
|
||||
if (cmd[{"-h", "--help"}]) {
|
||||
return;
|
||||
}
|
||||
|
||||
LogSys.SilenceConsoleLogging();
|
||||
|
||||
Zone::Bootup(ZoneID("qrg"), 0, false);
|
||||
zone->StopShutdownTimer();
|
||||
|
||||
entity_list.Process();
|
||||
entity_list.MobProcess();
|
||||
|
||||
std::cout << "===========================================\n";
|
||||
std::cout << "⚙\uFE0F> Running Hand-in Tests...\n";
|
||||
std::cout << "===========================================\n\n";
|
||||
|
||||
Client *c = new Client();
|
||||
auto npc_type = content_db.LoadNPCTypesData(754008);
|
||||
if (npc_type) {
|
||||
auto npc = new NPC(
|
||||
npc_type,
|
||||
nullptr,
|
||||
glm::vec4(0, 0, 0, 0),
|
||||
GravityBehavior::Water
|
||||
);
|
||||
|
||||
entity_list.AddNPC(npc);
|
||||
|
||||
std::vector<TestCase> test_cases = {
|
||||
TestCase{
|
||||
.description = "Test basic cloth-cap hand-in",
|
||||
.hand_in = {
|
||||
.items = {
|
||||
HandinEntry{.item_id = "1001", .count = 1},
|
||||
},
|
||||
},
|
||||
.required = {
|
||||
.items = {
|
||||
HandinEntry{.item_id = "1001", .count = 1},
|
||||
},
|
||||
},
|
||||
.returned = {},
|
||||
.handin_check_result = true,
|
||||
},
|
||||
TestCase{
|
||||
.description = "Test basic cloth-cap hand-in failure",
|
||||
.hand_in = {
|
||||
.items = {
|
||||
HandinEntry{.item_id = "9997", .count = 1},
|
||||
},
|
||||
},
|
||||
.required = {
|
||||
.items = {
|
||||
HandinEntry{.item_id = "1001", .count = 1},
|
||||
},
|
||||
},
|
||||
.returned = {
|
||||
.items = {
|
||||
HandinEntry{.item_id = "9997", .count = 1},
|
||||
},
|
||||
},
|
||||
.handin_check_result = false,
|
||||
},
|
||||
TestCase{
|
||||
.description = "Test basic cloth-cap hand-in failure from handing in too many",
|
||||
.hand_in = {
|
||||
.items = {
|
||||
HandinEntry{.item_id = "9997", .count = 1},
|
||||
HandinEntry{.item_id = "9997", .count = 1},
|
||||
},
|
||||
},
|
||||
.required = {
|
||||
.items = {
|
||||
HandinEntry{.item_id = "1001", .count = 1},
|
||||
},
|
||||
},
|
||||
.returned = {
|
||||
.items = {
|
||||
HandinEntry{.item_id = "9997", .count = 1},
|
||||
HandinEntry{.item_id = "9997", .count = 1},
|
||||
},
|
||||
},
|
||||
.handin_check_result = false,
|
||||
},
|
||||
TestCase{
|
||||
.description = "Test handing in money",
|
||||
.hand_in = {
|
||||
.items = {},
|
||||
.money = {.platinum = 1},
|
||||
},
|
||||
.required = {
|
||||
.items = {},
|
||||
.money = {.platinum = 1},
|
||||
},
|
||||
.returned = {},
|
||||
.handin_check_result = true,
|
||||
},
|
||||
TestCase{
|
||||
.description = "Test handing in money, but not enough",
|
||||
.hand_in = {
|
||||
.items = {},
|
||||
.money = {.platinum = 1},
|
||||
},
|
||||
.required = {
|
||||
.items = {},
|
||||
.money = {.platinum = 100},
|
||||
},
|
||||
.returned = {
|
||||
.items = {},
|
||||
.money = {.platinum = 1},
|
||||
},
|
||||
.handin_check_result = false,
|
||||
},
|
||||
TestCase{
|
||||
.description = "Test handing in money, but not enough of any type",
|
||||
.hand_in = {
|
||||
.items = {},
|
||||
.money = {.platinum = 1, .gold = 1, .silver = 1, .copper = 1},
|
||||
},
|
||||
.required = {
|
||||
.items = {},
|
||||
.money = {.platinum = 100, .gold = 100, .silver = 100, .copper = 100},
|
||||
},
|
||||
.returned = {
|
||||
.items = {},
|
||||
.money = {.platinum = 1, .gold = 1, .silver = 1, .copper = 1},
|
||||
},
|
||||
.handin_check_result = false,
|
||||
},
|
||||
TestCase{
|
||||
.description = "Test handing in money of all types",
|
||||
.hand_in = {
|
||||
.items = {},
|
||||
.money = {.platinum = 1, .gold = 1, .silver = 1, .copper = 1},
|
||||
},
|
||||
.required = {
|
||||
.items = {},
|
||||
.money = {.platinum = 1, .gold = 1, .silver = 1, .copper = 1},
|
||||
},
|
||||
.returned = {},
|
||||
.handin_check_result = true,
|
||||
},
|
||||
TestCase{
|
||||
.description = "Test handing in platinum with items with success",
|
||||
.hand_in = {
|
||||
.items = {
|
||||
HandinEntry{.item_id = "1001", .count = 1},
|
||||
},
|
||||
.money = {.platinum = 1},
|
||||
},
|
||||
.required = {
|
||||
.items = {
|
||||
HandinEntry{.item_id = "1001", .count = 1},
|
||||
},
|
||||
.money = {.platinum = 1},
|
||||
},
|
||||
.returned = {},
|
||||
.handin_check_result = true,
|
||||
},
|
||||
TestCase{
|
||||
.description = "Test handing in platinum with items with failure",
|
||||
.hand_in = {
|
||||
.items = {
|
||||
HandinEntry{.item_id = "1001", .count = 1},
|
||||
},
|
||||
.money = {.platinum = 1},
|
||||
},
|
||||
.required = {
|
||||
.items = {
|
||||
HandinEntry{.item_id = "1001", .count = 1},
|
||||
},
|
||||
.money = {.platinum = 100},
|
||||
},
|
||||
.returned = {
|
||||
.items = {
|
||||
HandinEntry{
|
||||
.item_id = "1001", .count = 0,
|
||||
},
|
||||
},
|
||||
.money = {.platinum = 1},
|
||||
},
|
||||
.handin_check_result = false,
|
||||
},
|
||||
TestCase{
|
||||
.description = "Test returning money and items",
|
||||
.hand_in = {
|
||||
.items = {
|
||||
HandinEntry{.item_id = "1007", .count = 1},
|
||||
},
|
||||
.money = {
|
||||
.platinum = 1,
|
||||
.gold = 666,
|
||||
.silver = 234,
|
||||
.copper = 444,
|
||||
},
|
||||
},
|
||||
.required = {
|
||||
.items = {
|
||||
HandinEntry{.item_id = "1001", .count = 1},
|
||||
},
|
||||
.money = {.platinum = 100},
|
||||
},
|
||||
.returned = {
|
||||
.items = {
|
||||
HandinEntry{.item_id = "1007", .count = 1},
|
||||
},
|
||||
.money = {
|
||||
.platinum = 1,
|
||||
.gold = 666,
|
||||
.silver = 234,
|
||||
.copper = 444,
|
||||
},
|
||||
},
|
||||
.handin_check_result = false,
|
||||
},
|
||||
TestCase{
|
||||
.description = "Test returning money",
|
||||
.hand_in = {
|
||||
.items = {},
|
||||
.money = {
|
||||
.platinum = 1,
|
||||
.gold = 666,
|
||||
.silver = 234,
|
||||
.copper = 444,
|
||||
},
|
||||
},
|
||||
.required = {
|
||||
.items = {},
|
||||
.money = {.platinum = 100},
|
||||
},
|
||||
.returned = {
|
||||
.items = {
|
||||
},
|
||||
.money = {
|
||||
.platinum = 1,
|
||||
.gold = 666,
|
||||
.silver = 234,
|
||||
.copper = 444,
|
||||
},
|
||||
},
|
||||
.handin_check_result = false,
|
||||
},
|
||||
TestCase{
|
||||
.description = "Test handing in many items of the same required item",
|
||||
.hand_in = {
|
||||
.items = {
|
||||
HandinEntry{.item_id = "1007", .count = 1},
|
||||
HandinEntry{.item_id = "1007", .count = 1},
|
||||
HandinEntry{.item_id = "1007", .count = 1},
|
||||
HandinEntry{.item_id = "1007", .count = 1},
|
||||
},
|
||||
.money = {},
|
||||
},
|
||||
.required = {
|
||||
.items = {
|
||||
HandinEntry{.item_id = "1007", .count = 1},
|
||||
},
|
||||
.money = {},
|
||||
},
|
||||
.returned = {
|
||||
.items = {
|
||||
HandinEntry{.item_id = "1007", .count = 1},
|
||||
HandinEntry{.item_id = "1007", .count = 1},
|
||||
HandinEntry{.item_id = "1007", .count = 1},
|
||||
},
|
||||
.money = {},
|
||||
},
|
||||
.handin_check_result = true,
|
||||
},
|
||||
TestCase{
|
||||
.description = "Test handing in item of a stack",
|
||||
.hand_in = {
|
||||
.items = {
|
||||
HandinEntry{.item_id = "13005", .count = 20},
|
||||
},
|
||||
.money = {},
|
||||
},
|
||||
.required = {
|
||||
.items = {
|
||||
HandinEntry{.item_id = "13005", .count = 20},
|
||||
},
|
||||
.money = {},
|
||||
},
|
||||
.returned = {
|
||||
.items = {},
|
||||
.money = {},
|
||||
},
|
||||
.handin_check_result = true,
|
||||
},
|
||||
TestCase{
|
||||
.description = "Test handing in item of a stack but not enough",
|
||||
.hand_in = {
|
||||
.items = {
|
||||
HandinEntry{.item_id = "13005", .count = 10},
|
||||
},
|
||||
.money = {},
|
||||
},
|
||||
.required = {
|
||||
.items = {
|
||||
HandinEntry{.item_id = "13005", .count = 20},
|
||||
},
|
||||
.money = {},
|
||||
},
|
||||
.returned = {
|
||||
.items = {
|
||||
HandinEntry{.item_id = "13005", .count = 10},
|
||||
},
|
||||
.money = {},
|
||||
},
|
||||
.handin_check_result = false,
|
||||
},
|
||||
TestCase{
|
||||
.description = "Test handing in 4 non-stacking helmets when 4 are required",
|
||||
.hand_in = {
|
||||
.items = {
|
||||
HandinEntry{.item_id = "29062", .count = 1},
|
||||
HandinEntry{.item_id = "29062", .count = 1},
|
||||
HandinEntry{.item_id = "29062", .count = 1},
|
||||
HandinEntry{.item_id = "29062", .count = 1},
|
||||
},
|
||||
.money = {},
|
||||
},
|
||||
.required = {
|
||||
.items = {
|
||||
HandinEntry{.item_id = "29062", .count = 4},
|
||||
},
|
||||
.money = {},
|
||||
},
|
||||
.returned = {
|
||||
.items = {
|
||||
},
|
||||
.money = {},
|
||||
},
|
||||
.handin_check_result = true,
|
||||
},
|
||||
TestCase{
|
||||
.description = "Test handing in Soulfire that has 5 charges and have it count as 1 item",
|
||||
.hand_in = {
|
||||
.items = {
|
||||
HandinEntry{.item_id = "5504", .count = 1},
|
||||
},
|
||||
.money = {},
|
||||
},
|
||||
.required = {
|
||||
.items = {
|
||||
HandinEntry{.item_id = "5504", .count = 1},
|
||||
},
|
||||
.money = {},
|
||||
},
|
||||
.returned = {
|
||||
.items = {
|
||||
},
|
||||
.money = {},
|
||||
},
|
||||
.handin_check_result = true,
|
||||
},
|
||||
};
|
||||
|
||||
std::map<std::string, uint32> hand_ins;
|
||||
std::map<std::string, uint32> required;
|
||||
std::vector<EQ::ItemInstance *> items;
|
||||
|
||||
LogSys.EnableConsoleLogging();
|
||||
|
||||
// turn this on to see debugging output
|
||||
LogSys.log_settings[Logs::NpcHandin].log_to_console = std::getenv("DEBUG") ? 3 : 0;
|
||||
|
||||
for (auto &test: test_cases) {
|
||||
hand_ins.clear();
|
||||
required.clear();
|
||||
items.clear();
|
||||
|
||||
for (auto &hand_in: test.hand_in.items) {
|
||||
auto item_id = Strings::ToInt(hand_in.item_id);
|
||||
EQ::ItemInstance *inst = database.CreateItem(item_id);
|
||||
if (inst->IsStackable()) {
|
||||
inst->SetCharges(hand_in.count);
|
||||
}
|
||||
|
||||
if (inst->GetItem()->MaxCharges > 0) {
|
||||
inst->SetCharges(inst->GetItem()->MaxCharges);
|
||||
}
|
||||
|
||||
hand_ins[hand_in.item_id] = inst->GetCharges();
|
||||
items.push_back(inst);
|
||||
}
|
||||
|
||||
// money
|
||||
if (test.hand_in.money.platinum > 0) {
|
||||
hand_ins["platinum"] = test.hand_in.money.platinum;
|
||||
}
|
||||
if (test.hand_in.money.gold > 0) {
|
||||
hand_ins["gold"] = test.hand_in.money.gold;
|
||||
}
|
||||
if (test.hand_in.money.silver > 0) {
|
||||
hand_ins["silver"] = test.hand_in.money.silver;
|
||||
}
|
||||
if (test.hand_in.money.copper > 0) {
|
||||
hand_ins["copper"] = test.hand_in.money.copper;
|
||||
}
|
||||
|
||||
for (auto &req: test.required.items) {
|
||||
required[req.item_id] = req.count;
|
||||
}
|
||||
|
||||
// money
|
||||
if (test.required.money.platinum > 0) {
|
||||
required["platinum"] = test.required.money.platinum;
|
||||
}
|
||||
if (test.required.money.gold > 0) {
|
||||
required["gold"] = test.required.money.gold;
|
||||
}
|
||||
if (test.required.money.silver > 0) {
|
||||
required["silver"] = test.required.money.silver;
|
||||
}
|
||||
if (test.required.money.copper > 0) {
|
||||
required["copper"] = test.required.money.copper;
|
||||
}
|
||||
|
||||
auto result = npc->CheckHandin(c, hand_ins, required, items);
|
||||
|
||||
RunTest(test.description, test.handin_check_result, result);
|
||||
|
||||
auto returned = npc->ReturnHandinItems(c);
|
||||
|
||||
std::map<std::string, uint32> returned_items;
|
||||
HandinMoney returned_money{};
|
||||
|
||||
// Serialize returned items
|
||||
for (const auto &ret: returned.items) {
|
||||
// if (ret.item->IsStackable() && ret.item->GetCharges() != ret.count) {
|
||||
// ret.item->SetCharges(ret.count);
|
||||
// }
|
||||
returned_items[ret.item_id] += ret.count;
|
||||
}
|
||||
|
||||
// Serialize returned money
|
||||
returned_money.platinum = returned.money.platinum;
|
||||
returned_money.gold = returned.money.gold;
|
||||
returned_money.silver = returned.money.silver;
|
||||
returned_money.copper = returned.money.copper;
|
||||
|
||||
// Serialize expected and actual return values for comparison
|
||||
std::map<std::string, uint32> expected_returned_items;
|
||||
for (const auto &entry: test.returned.items) {
|
||||
expected_returned_items[entry.item_id] += entry.count;
|
||||
}
|
||||
|
||||
std::string expected_serialized = SerializeHandin(
|
||||
expected_returned_items,
|
||||
test.returned.money
|
||||
);
|
||||
|
||||
std::string actual_serialized = SerializeHandin(returned_items, returned_money);
|
||||
|
||||
// Run serialization check test
|
||||
RunSerializedTest(test.description + " (Return Validation)", expected_serialized, actual_serialized);
|
||||
|
||||
npc->ResetHandin();
|
||||
|
||||
if (LogSys.log_settings[Logs::NpcHandin].log_to_console > 0) {
|
||||
std::cout << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "\n===========================================\n";
|
||||
std::cout << "✅ All NPC Hand-in Tests Completed!\n";
|
||||
std::cout << "===========================================\n";
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
#include "../../common/http/httplib.h"
|
||||
#include "../../common/eqemu_logsys.h"
|
||||
#include "../../common/platform.h"
|
||||
#include "../../zone.h"
|
||||
#include "../../client.h"
|
||||
#include "../../common/net/eqstream.h"
|
||||
|
||||
extern Zone *zone;
|
||||
|
||||
void ZoneCLI::TestNpcHandinsMultiQuest(int argc, char **argv, argh::parser &cmd, std::string &description)
|
||||
{
|
||||
if (cmd[{"-h", "--help"}]) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint32 break_length = 50;
|
||||
int failed_count = 0;
|
||||
|
||||
LogSys.SilenceConsoleLogging();
|
||||
|
||||
Zone::Bootup(ZoneID("qrg"), 0, false);
|
||||
zone->StopShutdownTimer();
|
||||
|
||||
entity_list.Process();
|
||||
entity_list.MobProcess();
|
||||
|
||||
std::cout << "===========================================\n";
|
||||
std::cout << "⚙\uFE0F> Running Hand-in Tests (Multi-Quest)...\n";
|
||||
std::cout << "===========================================\n\n";
|
||||
|
||||
Client *c = new Client();
|
||||
auto npc_type = content_db.LoadNPCTypesData(754008);
|
||||
if (npc_type) {
|
||||
auto npc = new NPC(
|
||||
npc_type,
|
||||
nullptr,
|
||||
glm::vec4(0, 0, 0, 0),
|
||||
GravityBehavior::Water
|
||||
);
|
||||
|
||||
entity_list.AddNPC(npc);
|
||||
npc->MultiQuestEnable();
|
||||
|
||||
struct HandinEntry {
|
||||
std::string item_id = "0";
|
||||
uint32 count = 0;
|
||||
const EQ::ItemInstance *item = nullptr;
|
||||
bool is_multiquest_item = false; // state
|
||||
};
|
||||
|
||||
struct HandinMoney {
|
||||
uint32 platinum = 0;
|
||||
uint32 gold = 0;
|
||||
uint32 silver = 0;
|
||||
uint32 copper = 0;
|
||||
};
|
||||
|
||||
struct Handin {
|
||||
std::vector<HandinEntry> items = {}; // items can be removed from this set as successful handins are made
|
||||
HandinMoney money = {}; // money can be removed from this set as successful handins are made
|
||||
};
|
||||
|
||||
struct TestCase {
|
||||
std::string description = "";
|
||||
Handin hand_in;
|
||||
Handin required;
|
||||
Handin returned;
|
||||
bool handin_check_result;
|
||||
};
|
||||
|
||||
std::vector<TestCase> test_cases = {
|
||||
TestCase{
|
||||
.description = "Journeyman's Boots",
|
||||
.hand_in = {
|
||||
.items = {
|
||||
HandinEntry{.item_id = "12268", .count = 1},
|
||||
HandinEntry{.item_id = "7100", .count = 1},
|
||||
},
|
||||
.money = {.platinum = 325},
|
||||
},
|
||||
.required = {
|
||||
.items = {
|
||||
HandinEntry{.item_id = "12268", .count = 1},
|
||||
HandinEntry{.item_id = "7100", .count = 1},
|
||||
},
|
||||
.money = {.platinum = 325},
|
||||
},
|
||||
.returned = {},
|
||||
.handin_check_result = true,
|
||||
},
|
||||
};
|
||||
|
||||
std::map<std::string, uint32> hand_ins;
|
||||
std::map<std::string, uint32> required;
|
||||
std::vector<EQ::ItemInstance *> items;
|
||||
|
||||
LogSys.EnableConsoleLogging();
|
||||
|
||||
// turn this on to see debugging output
|
||||
LogSys.log_settings[Logs::NpcHandin].log_to_console = std::getenv("DEBUG") ? 3 : 0;
|
||||
|
||||
for (auto &test: test_cases) {
|
||||
required.clear();
|
||||
|
||||
for (auto &hand_in: test.hand_in.items) {
|
||||
hand_ins.clear();
|
||||
items.clear();
|
||||
|
||||
auto item_id = Strings::ToInt(hand_in.item_id);
|
||||
EQ::ItemInstance *inst = database.CreateItem(item_id);
|
||||
if (inst->IsStackable()) {
|
||||
inst->SetCharges(hand_in.count);
|
||||
}
|
||||
|
||||
if (inst->GetItem()->MaxCharges > 0) {
|
||||
inst->SetCharges(inst->GetItem()->MaxCharges);
|
||||
}
|
||||
|
||||
hand_ins[hand_in.item_id] = inst->GetCharges();
|
||||
items.push_back(inst);
|
||||
|
||||
npc->CheckHandin(c, hand_ins, required, items);
|
||||
npc->ResetHandin();
|
||||
}
|
||||
|
||||
// money
|
||||
if (test.hand_in.money.platinum > 0) {
|
||||
hand_ins["platinum"] = test.hand_in.money.platinum;
|
||||
}
|
||||
if (test.hand_in.money.gold > 0) {
|
||||
hand_ins["gold"] = test.hand_in.money.gold;
|
||||
}
|
||||
if (test.hand_in.money.silver > 0) {
|
||||
hand_ins["silver"] = test.hand_in.money.silver;
|
||||
}
|
||||
if (test.hand_in.money.copper > 0) {
|
||||
hand_ins["copper"] = test.hand_in.money.copper;
|
||||
}
|
||||
|
||||
for (auto &req: test.required.items) {
|
||||
required[req.item_id] = req.count;
|
||||
}
|
||||
|
||||
// money
|
||||
if (test.required.money.platinum > 0) {
|
||||
required["platinum"] = test.required.money.platinum;
|
||||
}
|
||||
if (test.required.money.gold > 0) {
|
||||
required["gold"] = test.required.money.gold;
|
||||
}
|
||||
if (test.required.money.silver > 0) {
|
||||
required["silver"] = test.required.money.silver;
|
||||
}
|
||||
if (test.required.money.copper > 0) {
|
||||
required["copper"] = test.required.money.copper;
|
||||
}
|
||||
|
||||
auto result = npc->CheckHandin(c, hand_ins, required, items);
|
||||
|
||||
RunTest(test.description, test.handin_check_result, result);
|
||||
|
||||
auto returned = npc->ReturnHandinItems(c);
|
||||
|
||||
npc->ResetHandin();
|
||||
|
||||
if (LogSys.log_settings[Logs::NpcHandin].log_to_console > 0) {
|
||||
std::cout << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "\n===========================================\n";
|
||||
std::cout << "✅ All NPC Hand-in Tests Completed (Multi-Quest)!\n";
|
||||
std::cout << "===========================================\n";
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user