feat: move all ldf config to be in xml (#1482)

* feat: move all ldf config to be in xml
cleanup dev-tribute.xml
add comments to atm.xml
remove custom script tag in favor of ldfconfig for it

* replace sto* calls with tryParse's

* remove unesessary .has_value() calls and check for null_lot

* remove member variable naming that on on-member vars

* move max's vendor inventory to be configurable via vanity

* Consolidate triplecated vendor code

* don't write name if one is not given

* Updates to vanity xml's and demo for later docs

* rename vars
This commit is contained in:
Aaron Kimbrell 2024-02-28 17:16:47 -06:00 committed by GitHub
parent ef3fdba621
commit 43707952d2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 745 additions and 582 deletions

View File

@ -179,7 +179,7 @@ file(ARCHIVE_EXTRACT INPUT ${PROJECT_BINARY_DIR}/navmeshes.zip DESTINATION ${PRO
file(REMOVE ${PROJECT_BINARY_DIR}/navmeshes.zip)
# Copy vanity files on first build
set(VANITY_FILES "CREDITS.md" "INFO.md" "TESTAMENT.md" "root.xml" "dev-tribute.xml" "atm.xml")
set(VANITY_FILES "CREDITS.md" "INFO.md" "TESTAMENT.md" "root.xml" "dev-tribute.xml" "atm.xml" "demo.xml")
foreach(file ${VANITY_FILES})
configure_file("${CMAKE_SOURCE_DIR}/vanity/${file}" "${CMAKE_BINARY_DIR}/vanity/${file}" COPYONLY)

View File

@ -2191,9 +2191,3 @@ void Entity::SetRespawnRot(const NiQuaternion& rotation) {
auto* characterComponent = GetComponent<CharacterComponent>();
if (characterComponent) characterComponent->SetRespawnRot(rotation);
}
void Entity::SetScale(const float scale) {
if (scale == m_Scale) return;
m_Scale = scale;
Game::entityManager->SerializeEntity(this);
}

View File

@ -295,7 +295,8 @@ public:
void ProcessPositionUpdate(PositionUpdate& update);
void SetScale(const float scale);
// Scale will only be communicated to the client when the construction packet is sent
void SetScale(const float scale) { m_Scale = scale; };
protected:
LWOOBJID m_ObjectID;

View File

@ -40,9 +40,19 @@ void VendorComponent::RefreshInventory(bool isCreation) {
SetHasMultiCostItems(false);
m_Inventory.clear();
// Custom code for Max vanity NPC and Mr.Ree cameras
if(isCreation && m_Parent->GetLOT() == 9749 && Game::server->GetZoneID() == 1201) {
SetupMaxCustomVendor();
// Custom code for Vanity Vendor Invetory Override
if(m_Parent->HasVar(u"vendorInvOverride")) {
std::vector<std::string> items = GeneralUtils::SplitString(m_Parent->GetVarAsString(u"vendorInvOverride"), ',');
uint32_t sortPriority = -1;
for (auto& itemString : items) {
itemString.erase(remove_if(itemString.begin(), itemString.end(), isspace), itemString.end());
auto item = GeneralUtils::TryParse<uint32_t>(itemString);
if (!item) continue;
if (SetupItem(item.value())) {
sortPriority++;
m_Inventory.push_back(SoldItem(item.value(), sortPriority));
}
}
return;
}
@ -52,24 +62,12 @@ void VendorComponent::RefreshInventory(bool isCreation) {
if (lootMatrices.empty()) return;
auto* lootTableTable = CDClientManager::GetTable<CDLootTableTable>();
auto* itemComponentTable = CDClientManager::GetTable<CDItemComponentTable>();
auto* compRegistryTable = CDClientManager::GetTable<CDComponentsRegistryTable>();
for (const auto& lootMatrix : lootMatrices) {
auto vendorItems = lootTableTable->GetTable(lootMatrix.LootTableIndex);
if (lootMatrix.maxToDrop == 0 || lootMatrix.minToDrop == 0) {
for (const auto& item : vendorItems) {
if (!m_HasStandardCostItems || !m_HasMultiCostItems) {
auto itemComponentID = compRegistryTable->GetByIDAndType(item.itemid, eReplicaComponentType::ITEM, -1);
if (itemComponentID == -1) {
LOG("Attempted to add item %i with ItemComponent ID -1 to vendor %i inventory. Not adding item!", itemComponentID, m_Parent->GetLOT());
continue;
}
auto itemComponent = itemComponentTable->GetItemComponentByID(itemComponentID);
if (!m_HasStandardCostItems && itemComponent.baseValue != -1) SetHasStandardCostItems(true);
if (!m_HasMultiCostItems && !itemComponent.currencyCosts.empty()) SetHasMultiCostItems(true);
}
m_Inventory.push_back(SoldItem(item.itemid, item.sortPriority));
if (SetupItem(item.itemid)) m_Inventory.push_back(SoldItem(item.itemid, item.sortPriority));
}
} else {
auto randomCount = GeneralUtils::GenerateRandomNumber<int32_t>(lootMatrix.minToDrop, lootMatrix.maxToDrop);
@ -79,17 +77,7 @@ void VendorComponent::RefreshInventory(bool isCreation) {
auto randomItemIndex = GeneralUtils::GenerateRandomNumber<int32_t>(0, vendorItems.size() - 1);
const auto& randomItem = vendorItems.at(randomItemIndex);
vendorItems.erase(vendorItems.begin() + randomItemIndex);
if (!m_HasStandardCostItems || !m_HasMultiCostItems) {
auto itemComponentID = compRegistryTable->GetByIDAndType(randomItem.itemid, eReplicaComponentType::ITEM, -1);
if (itemComponentID == -1) {
LOG("Attempted to add item %i with ItemComponent ID -1 to vendor %i inventory. Not adding item!", itemComponentID, m_Parent->GetLOT());
continue;
}
auto itemComponent = itemComponentTable->GetItemComponentByID(itemComponentID);
if (!m_HasStandardCostItems && itemComponent.baseValue != -1) SetHasStandardCostItems(true);
if (!m_HasMultiCostItems && !itemComponent.currencyCosts.empty()) SetHasMultiCostItems(true);
}
m_Inventory.push_back(SoldItem(randomItem.itemid, randomItem.sortPriority));
if (SetupItem(randomItem.itemid)) m_Inventory.push_back(SoldItem(randomItem.itemid, randomItem.sortPriority));
}
}
}
@ -126,15 +114,6 @@ bool VendorComponent::SellsItem(const LOT item) const {
}) > 0;
}
void VendorComponent::SetupMaxCustomVendor(){
SetHasStandardCostItems(true);
m_Inventory.push_back(SoldItem(11909, 0)); // Top hat w frog
m_Inventory.push_back(SoldItem(7785, 0)); // Flash bulb
m_Inventory.push_back(SoldItem(12764, 0)); // Big fountain soda
m_Inventory.push_back(SoldItem(12241, 0)); // Hot cocoa (from fb)
}
void VendorComponent::HandleMrReeCameras(){
if (m_Parent->GetLOT() == 13569) {
SetHasStandardCostItems(true);
@ -211,5 +190,25 @@ void VendorComponent::Buy(Entity* buyer, LOT lot, uint32_t count) {
character->SetCoins(character->GetCoins() - (coinCost), eLootSourceType::VENDOR);
inventoryComponent->AddItem(lot, count, eLootSourceType::VENDOR);
GameMessages::SendVendorTransactionResult(buyer, buyer->GetSystemAddress(), eVendorTransactionResult::PURCHASE_SUCCESS);
}
bool VendorComponent::SetupItem(LOT item) {
auto* itemComponentTable = CDClientManager::GetTable<CDItemComponentTable>();
auto* compRegistryTable = CDClientManager::GetTable<CDComponentsRegistryTable>();
auto itemComponentID = compRegistryTable->GetByIDAndType(item, eReplicaComponentType::ITEM, -1);
if (itemComponentID == -1) {
LOG("Attempted to add item %i with ItemComponent ID -1 to vendor %i inventory. Not adding item!", itemComponentID, m_Parent->GetLOT());
return false;
}
if (!m_HasStandardCostItems || !m_HasMultiCostItems) {
auto itemComponent = itemComponentTable->GetItemComponentByID(itemComponentID);
if (!m_HasStandardCostItems && itemComponent.baseValue != -1) SetHasStandardCostItems(true);
if (!m_HasMultiCostItems && !itemComponent.currencyCosts.empty()) SetHasMultiCostItems(true);
}
return true;
}

View File

@ -50,8 +50,8 @@ public:
void Buy(Entity* buyer, LOT lot, uint32_t count);
private:
void SetupMaxCustomVendor();
void HandleMrReeCameras();
bool SetupItem(LOT item);
float m_BuyScalar = 0.0f;
float m_SellScalar = 0.0f;
float m_RefreshTimeSeconds = 0.0f;

View File

@ -22,9 +22,18 @@
#include <fstream>
std::vector<VanityObject> VanityUtilities::m_Objects = {};
std::set<std::string> VanityUtilities::m_LoadedFiles = {};
namespace {
std::vector<VanityObject> objects;
std::set<std::string> loadedFiles;
}
void SetupNPCTalk(Entity* npc);
void NPCTalk(Entity* npc);
void ParseXml(const std::string& file);
LWOOBJID SpawnSpawner(const VanityObject& object, const VanityObjectLocation& location);
Entity* SpawnObject(const VanityObject& object, const VanityObjectLocation& location);
VanityObject* GetObject(const std::string& name);
void VanityUtilities::SpawnVanity() {
const uint32_t zoneID = Game::server->GetZoneID();
@ -36,21 +45,19 @@ void VanityUtilities::SpawnVanity() {
info.pos = { 259.5f, 246.4f, -705.2f };
info.rot = { 0.0f, 0.0f, 1.0f, 0.0f };
info.spawnerID = Game::entityManager->GetZoneControlEntity()->GetObjectID();
info.settings = { new LDFData<bool>(u"hasCustomText", true),
new LDFData<std::string>(u"customText", ParseMarkdown((BinaryPathFinder::GetBinaryDir() / "vanity/TESTAMENT.md").string())) };
info.settings = {
new LDFData<bool>(u"hasCustomText", true),
new LDFData<std::string>(u"customText", ParseMarkdown((BinaryPathFinder::GetBinaryDir() / "vanity/TESTAMENT.md").string()))
};
auto* entity = Game::entityManager->CreateEntity(info);
Game::entityManager->ConstructEntity(entity);
}
}
if (Game::config->GetValue("disable_vanity") == "1") {
return;
}
if (Game::config->GetValue("disable_vanity") == "1") return;
for (const auto& npc : m_Objects) {
for (const auto& npc : objects) {
if (npc.m_ID == LWOOBJID_EMPTY) continue;
if (npc.m_LOT == 176){
Game::zoneManager->RemoveSpawner(npc.m_ID);
@ -61,13 +68,13 @@ void VanityUtilities::SpawnVanity() {
}
}
m_Objects.clear();
m_LoadedFiles.clear();
objects.clear();
loadedFiles.clear();
ParseXML((BinaryPathFinder::GetBinaryDir() / "vanity/root.xml").string());
ParseXml((BinaryPathFinder::GetBinaryDir() / "vanity/root.xml").string());
// Loop through all objects
for (auto& object : m_Objects) {
for (auto& object : objects) {
if (object.m_Locations.find(Game::server->GetZoneID()) == object.m_Locations.end()) continue;
const std::vector<VanityObjectLocation>& locations = object.m_Locations.at(Game::server->GetZoneID());
@ -79,12 +86,6 @@ void VanityUtilities::SpawnVanity() {
float rate = GeneralUtils::GenerateRandomNumber<float>(0, 1);
if (location.m_Chance < rate) continue;
if (object.m_Config.empty()) {
object.m_Config = {
new LDFData<std::vector<std::u16string>>(u"syncLDF", { u"custom_script_client" }),
new LDFData<std::u16string>(u"custom_script_client", u"scripts\\ai\\SPEC\\MISSION_MINIGAME_CLIENT.lua")
};
}
if (object.m_LOT == 176){
object.m_ID = SpawnSpawner(object, location);
} else {
@ -94,20 +95,13 @@ void VanityUtilities::SpawnVanity() {
object.m_ID = objectEntity->GetObjectID();
if (!object.m_Phrases.empty()){
objectEntity->SetVar<std::vector<std::string>>(u"chats", object.m_Phrases);
auto* scriptComponent = objectEntity->GetComponent<ScriptComponent>();
if (scriptComponent && !object.m_Script.empty()) {
scriptComponent->SetScript(object.m_Script);
scriptComponent->SetSerialized(false);
}
SetupNPCTalk(objectEntity);
}
}
}
}
LWOOBJID VanityUtilities::SpawnSpawner(const VanityObject& object, const VanityObjectLocation& location) {
LWOOBJID SpawnSpawner(const VanityObject& object, const VanityObjectLocation& location) {
SceneObject obj;
obj.lot = object.m_LOT;
// guratantee we have no collisions
@ -121,7 +115,7 @@ LWOOBJID VanityUtilities::SpawnSpawner(const VanityObject& object, const VanityO
return obj.id;
}
Entity* VanityUtilities::SpawnObject(const VanityObject& object, const VanityObjectLocation& location) {
Entity* SpawnObject(const VanityObject& object, const VanityObjectLocation& location) {
EntityInfo info;
info.lot = object.m_LOT;
info.pos = location.m_Position;
@ -131,18 +125,16 @@ Entity* VanityUtilities::SpawnObject(const VanityObject& object, const VanityObj
info.settings = object.m_Config;
auto* entity = Game::entityManager->CreateEntity(info);
entity->SetVar(u"npcName", object.m_Name);
if (!object.m_Name.empty()) entity->SetVar(u"npcName", object.m_Name);
if (entity->GetVar<bool>(u"noGhosting")) entity->SetIsGhostingCandidate(false);
auto* inventoryComponent = entity->GetComponent<InventoryComponent>();
if (inventoryComponent && !object.m_Equipment.empty()) {
inventoryComponent->SetNPCItems(object.m_Equipment);
}
auto* destroyableComponent = entity->GetComponent<DestroyableComponent>();
if (destroyableComponent != nullptr) {
if (destroyableComponent) {
destroyableComponent->SetIsGMImmune(true);
destroyableComponent->SetMaxHealth(0);
destroyableComponent->SetHealth(0);
@ -153,12 +145,12 @@ Entity* VanityUtilities::SpawnObject(const VanityObject& object, const VanityObj
return entity;
}
void VanityUtilities::ParseXML(const std::string& file) {
if (m_LoadedFiles.contains(file)){
void ParseXml(const std::string& file) {
if (loadedFiles.contains(file)){
LOG("Trying to load vanity file %s twice!!!", file.c_str());
return;
}
m_LoadedFiles.insert(file);
loadedFiles.insert(file);
// Read the entire file
std::ifstream xmlFile(file);
std::string xml((std::istreambuf_iterator<char>(xmlFile)), std::istreambuf_iterator<char>());
@ -176,24 +168,26 @@ void VanityUtilities::ParseXML(const std::string& file) {
if (enabled != "1") {
continue;
}
ParseXML((BinaryPathFinder::GetBinaryDir() / "vanity" / filename).string());
ParseXml((BinaryPathFinder::GetBinaryDir() / "vanity" / filename).string());
}
}
// Read the objects
auto* objects = doc.FirstChildElement("objects");
if (objects) {
for (auto* object = objects->FirstChildElement("object"); object != nullptr; object = object->NextSiblingElement("object")) {
auto* objectsElement = doc.FirstChildElement("objects");
const uint32_t currentZoneID = Game::server->GetZoneID();
if (objectsElement) {
for (auto* object = objectsElement->FirstChildElement("object"); object != nullptr; object = object->NextSiblingElement("object")) {
// for use later when adding to the vector of VanityObjects
bool useLocationsAsRandomSpawnPoint = false;
// Get the NPC name
auto* name = object->Attribute("name");
if (!name) name = "";
// Get the NPC lot
auto* lot = object->Attribute("lot");
auto lot = GeneralUtils::TryParse<LOT>(object->Attribute("lot")).value_or(LOT_NULL);
if (lot == nullptr) {
if (lot == LOT_NULL) {
LOG("Failed to parse object lot");
continue;
}
@ -211,17 +205,17 @@ void VanityUtilities::ParseXML(const std::string& file) {
std::vector<std::string> splitEquipment = GeneralUtils::SplitString(equipmentString, ',');
for (auto& item : splitEquipment) {
inventory.push_back(std::stoi(item));
// remove spaces for tryParse to work
item.erase(remove_if(item.begin(), item.end(), isspace), item.end());
auto itemInt = GeneralUtils::TryParse<uint32_t>(item);
if (itemInt) inventory.push_back(itemInt.value());
}
}
}
// Get the phrases
auto* phrases = object->FirstChildElement("phrases");
std::vector<std::string> phraseList = {};
if (phrases) {
for (auto* phrase = phrases->FirstChildElement("phrase"); phrase != nullptr;
phrase = phrase->NextSiblingElement("phrase")) {
@ -235,41 +229,34 @@ void VanityUtilities::ParseXML(const std::string& file) {
}
}
// Get the script
auto* scriptElement = object->FirstChildElement("script");
std::string scriptName = "";
if (scriptElement != nullptr) {
auto* scriptNameAttribute = scriptElement->Attribute("name");
if (scriptNameAttribute) scriptName = scriptNameAttribute;
}
auto* configElement = object->FirstChildElement("config");
std::vector<std::u16string> keys = {};
std::vector<LDFBaseData*> config = {};
if(configElement) {
for (auto* key = configElement->FirstChildElement("key"); key != nullptr;
key = key->NextSiblingElement("key")) {
// Get the config data
auto* data = key->Attribute("data");
auto* data = key->GetText();
if (!data) continue;
LDFBaseData* configData = LDFBaseData::DataFromString(data);
if (configData->GetKey() == u"useLocationsAsRandomSpawnPoint" && configData->GetValueType() == eLDFType::LDF_TYPE_BOOLEAN){
useLocationsAsRandomSpawnPoint = static_cast<bool>(configData);
continue;
}
keys.push_back(configData->GetKey());
config.push_back(configData);
}
}
if (!keys.empty()) config.push_back(new LDFData<std::vector<std::u16string>>(u"syncLDF", keys));
VanityObject objectData;
objectData.m_Name = name;
objectData.m_LOT = std::stoi(lot);
objectData.m_Equipment = inventory;
objectData.m_Phrases = phraseList;
objectData.m_Script = scriptName;
objectData.m_Config = config;
VanityObject objectData {
.m_Name = name,
.m_LOT = lot,
.m_Equipment = inventory,
.m_Phrases = phraseList,
.m_Config = config
};
// Get the locations
auto* locations = object->FirstChildElement("locations");
@ -283,64 +270,67 @@ void VanityUtilities::ParseXML(const std::string& file) {
location = location->NextSiblingElement("location")) {
// Get the location data
auto* zoneID = location->Attribute("zone");
auto* x = location->Attribute("x");
auto* y = location->Attribute("y");
auto* z = location->Attribute("z");
auto* rw = location->Attribute("rw");
auto* rx = location->Attribute("rx");
auto* ry = location->Attribute("ry");
auto* rz = location->Attribute("rz");
auto zoneID = GeneralUtils::TryParse<uint32_t>(location->Attribute("zone"));
auto x = GeneralUtils::TryParse<float>(location->Attribute("x"));
auto y = GeneralUtils::TryParse<float>(location->Attribute("y"));
auto z = GeneralUtils::TryParse<float>(location->Attribute("z"));
auto rw = GeneralUtils::TryParse<float>(location->Attribute("rw"));
auto rx = GeneralUtils::TryParse<float>(location->Attribute("rx"));
auto ry = GeneralUtils::TryParse<float>(location->Attribute("ry"));
auto rz = GeneralUtils::TryParse<float>(location->Attribute("rz"));
if (zoneID == nullptr || x == nullptr || y == nullptr || z == nullptr || rw == nullptr || rx == nullptr || ry == nullptr
|| rz == nullptr) {
if (!zoneID || !x || !y || !z || !rw || !rx || !ry || !rz) {
LOG("Failed to parse NPC location data");
continue;
}
VanityObjectLocation locationData;
locationData.m_Position = { std::stof(x), std::stof(y), std::stof(z) };
locationData.m_Rotation = { std::stof(rw), std::stof(rx), std::stof(ry), std::stof(rz) };
locationData.m_Chance = 1.0f;
if (zoneID.value() != currentZoneID) {
LOG_DEBUG("Skipping location because it is in %i and not the current zone (%i)", zoneID.value(), currentZoneID);
continue;
}
VanityObjectLocation locationData {
.m_Position = { x.value(), y.value(), z.value() },
.m_Rotation = { rw.value(), rx.value(), ry.value(), rz.value() },
};
if (location->Attribute("chance")) {
locationData.m_Chance = std::stof(location->Attribute("chance"));
locationData.m_Chance = GeneralUtils::TryParse<float>(location->Attribute("chance")).value_or(1.0f);
}
if (location->Attribute("scale")) {
locationData.m_Scale = std::stof(location->Attribute("scale"));
locationData.m_Scale = GeneralUtils::TryParse<float>(location->Attribute("scale")).value_or(1.0f);
}
const auto& it = objectData.m_Locations.find(std::stoi(zoneID));
const auto& it = objectData.m_Locations.find(zoneID.value());
if (it != objectData.m_Locations.end()) {
it->second.push_back(locationData);
} else {
std::vector<VanityObjectLocation> locations;
locations.push_back(locationData);
objectData.m_Locations.insert(std::make_pair(std::stoi(zoneID), locations));
objectData.m_Locations.insert(std::make_pair(zoneID.value(), locations));
}
if (!(std::find(keys.begin(), keys.end(), u"teleport") != keys.end())) {
m_Objects.push_back(objectData);
if (!useLocationsAsRandomSpawnPoint) {
objects.push_back(objectData);
objectData.m_Locations.clear();
}
}
if (std::find(keys.begin(), keys.end(), u"teleport") != keys.end()) {
m_Objects.push_back(objectData);
if (useLocationsAsRandomSpawnPoint && !objectData.m_Locations.empty()) {
objects.push_back(objectData);
}
}
}
}
VanityObject* VanityUtilities::GetObject(const std::string& name) {
for (size_t i = 0; i < m_Objects.size(); i++) {
if (m_Objects[i].m_Name == name) {
return &m_Objects[i];
for (size_t i = 0; i < objects.size(); i++) {
if (objects[i].m_Name == name) {
return &objects[i];
}
}
return nullptr;
}
@ -350,7 +340,7 @@ std::string VanityUtilities::ParseMarkdown(const std::string& file) {
// Read the file into a string
std::ifstream t(file);
std::stringstream output;
// If the file does not exist, return an empty string.
// If the file does not exist, return a useful error.
if (!t.good()) {
output << "File ";
output << file.substr(file.rfind("/") + 1);
@ -408,13 +398,13 @@ std::string VanityUtilities::ParseMarkdown(const std::string& file) {
return output.str();
}
void VanityUtilities::SetupNPCTalk(Entity* npc) {
void SetupNPCTalk(Entity* npc) {
npc->AddCallbackTimer(15.0f, [npc]() { NPCTalk(npc); });
npc->SetProximityRadius(20.0f, "talk");
}
void VanityUtilities::NPCTalk(Entity* npc) {
void NPCTalk(Entity* npc) {
auto* proximityMonitorComponent = npc->GetComponent<ProximityMonitorComponent>();
if (!proximityMonitorComponent->GetProximityObjects("talk").empty()) {

View File

@ -5,58 +5,30 @@
#include <map>
#include <set>
struct VanityObjectLocation
{
struct VanityObjectLocation {
float m_Chance = 1.0f;
NiPoint3 m_Position;
NiQuaternion m_Rotation;
float m_Scale = 1.0f;
};
struct VanityObject
{
struct VanityObject {
LWOOBJID m_ID = LWOOBJID_EMPTY;
std::string m_Name;
LOT m_LOT;
LOT m_LOT = LOT_NULL;
std::vector<LOT> m_Equipment;
std::vector<std::string> m_Phrases;
std::string m_Script;
std::map<uint32_t, std::vector<VanityObjectLocation>> m_Locations;
std::vector<LDFBaseData*> m_Config;
};
class VanityUtilities
{
public:
static void SpawnVanity();
namespace VanityUtilities {
void SpawnVanity();
static Entity* SpawnObject(
const VanityObject& object,
const VanityObjectLocation& location
);
VanityObject* GetObject(const std::string& name);
static LWOOBJID SpawnSpawner(
const VanityObject& object,
const VanityObjectLocation& location
);
static std::string ParseMarkdown(
std::string ParseMarkdown(
const std::string& file
);
static void ParseXML(
const std::string& file
);
static VanityObject* GetObject(const std::string& name);
private:
static void SetupNPCTalk(Entity* npc);
static void NPCTalk(Entity* npc);
static std::vector<VanityObject> m_Objects;
static std::set<std::string> m_LoadedFiles;
};

View File

@ -5,20 +5,17 @@
#include "RenderComponent.h"
void DLUVanityTeleportingObject::OnStartup(Entity* self) {
if (!self->HasVar(u"npcName") || !self->HasVar(u"teleport")) return;
m_Object = VanityUtilities::GetObject(self->GetVarAsString(u"npcName"));
if (!self->HasVar(u"npcName")) return;
m_Object = VanityUtilities::GetObject(self->GetVarAsString(u"npcName"));
if (!m_Object) return;
if (self->HasVar(u"teleportInterval")) m_TeleportInterval = self->GetVar<float>(u"teleportInterval");
if (self->GetVar<bool>(u"teleport")) {
self->AddTimer("setupTeleport", m_TeleportInterval);
}
}
void DLUVanityTeleportingObject::OnTimerDone(Entity* self, std::string timerName) {
if (timerName == "setupTeleport") {
RenderComponent::PlayAnimation(self, u"interact");
GameMessages::SendPlayFXEffect(self->GetObjectID(), 6478, u"teleportBeam", "teleportBeam");
GameMessages::SendPlayFXEffect(self->GetObjectID(), 6478, u"teleportRings", "teleportRings");
@ -40,7 +37,6 @@ void DLUVanityTeleportingObject::OnTimerDone(Entity* self, std::string timerName
self->SetPosition(newLocation.m_Position);
self->SetRotation(newLocation.m_Rotation);
self->SetScale(newLocation.m_Scale);
GameMessages::SendPlayFXEffect(self->GetObjectID(), 6478, u"teleportBeam", "teleportBeam");
GameMessages::SendPlayFXEffect(self->GetObjectID(), 6478, u"teleportRings", "teleportRings");
self->AddTimer("stopFX", 2.0f);

View File

@ -1,23 +1,40 @@
<objects>
<object lot="13538">
<config>
<key data="CheckPrecondition=0:168"/>
<!--Precondition 168 means the player must complete mission 1203 before being able to use an ATM-->
<key>CheckPrecondition=0:168</key>
</config>
<locations>
<location zone="1100" x="248.792" y="381.869" z="-181.114" rw="0.782761" rx="0.00" ry="-0.622322" rz="0.00" />
<location zone="1100" x="471.545" y="413.979" z="27.176" rw="0.874378" rx="0.00" ry="-0.485246" rz="0.00" />
<!--AG Sentinel Base Camp-->
<location zone="1100" x="248.792" y="381.781" z="-181.114" rw="0.782761" rx="0.00" ry="-0.622322" rz="0.00" />
<!--AG Picnic Area-->
<location zone="1100" x="471.545" y="413.719" z="27.176" rw="0.874378" rx="0.00" ry="-0.485246" rz="0.00" />
<!--NS Nimbus Plaza-->
<location zone="1200" x="51.663" y="291.371" z="-74.650" rw="-0.446235" rx="0.00" ry="0.894916" rz="0.00" />
<location zone="1200" x="203.348" y="259.136" z="-543.400" rw="0.481554" rx="0.00" ry="0.876416" rz="0.00" />
<location zone="1201" x="46.537" y="233.137" z="-311.395" rw="0.780747" rx="0.00" ry="-0.624847" rz="0.00" />
<!--NS Red Blocks-->
<location zone="1200" x="203.348" y="259.0" z="-543.400" rw="0.481554" rx="0.00" ry="0.876416" rz="0.00" />
<!--PC by Lighthouse-->
<location zone="1201" x="46.537" y="232.958" z="-311.395" rw="0.780747" rx="0.00" ry="-0.624847" rz="0.00" />
<!--Frostburgh-->
<location zone="1260" x="-255.991" y="535.731" z="322.299" rw="0.683777" rx="0.00" ry="-0.729691" rz="0.00" />
<location zone="1600" x="85.210" y="1526.810" z="314.816" rw="-0.159486" rx="0.00" ry="0.987200" rz="0.00" />
<!--Starbase 3001-->
<location zone="1600" x="93.572" y="1526.970" z="311.905" rw="-0.159486" rx="0.00" ry="0.987200" rz="0.00" />
<!--LEGO Club-->
<location zone="1700" x="-256.293" y="1035.092" z="109.761" rw="0.00" rx="0.00" ry="1" rz="0.00" />
<!--GF Ravine-->
<location zone="1300" x="-199.258" y="246.874" z="-101.174" rw="-0.219711" rx="0.00" ry="0.975565" rz="0.00" />
<!--GF Race Place-->
<location zone="1300" x="51.848" y="329.0" z="561.114" rw="-0.277656" rx="0.00" ry="0.960681" rz="0.00" />
<!--GF Pirate Camp-->
<location zone="1300" x="363.259" y="259.367" z="-210.834" rw="0.961918" rx="0.00" ry="-0.273340" rz="0.00" />
<!--FV Great Tree-->
<location zone="1400" x="-194.288" y="381.275" z="-93.292" rw="0.935135" rx="0.00" ry="0.354292" rz="0.00" />
<!--FV Paradox Refinery-->
<location zone="1400" x="-696.957" y="-3.206" z="-452.441" rw="0.884105" rx="0.00" ry="0.467288" rz="0.00" />
<location zone="1800" x="-222.634" y="92.693" z="568.392" rw="-0.435024" rx="0.00" ry="0.900419" rz="0.00" />
<!--CP Sentinel Point Zeta-->
<location zone="1800" x="-222.634" y="92.373" z="568.392" rw="-0.435024" rx="0.00" ry="0.900419" rz="0.00" />
<!--NJ Monastery Courtyard-->
<location zone="2000" x="-63.487" y="208.270" z="379.195" rw="0.00" rx="0.00" ry="1" rz="0.00" />
</locations>
</object>
</objects>

132
vanity/demo.xml Normal file
View File

@ -0,0 +1,132 @@
<objects>
<!--A tree spawned at two locations with different positions, rotations, and scales-->
<!--Positions and rotations are easily gotten by typing /loc or /pos, and /rot into the in-game chat-->
<object lot="3248">
<locations>
<location zone="1200" x="-15.0" y="288.8" z="-167.0" rw="0.984321" rx="0.00" ry="0.176388" rz="0.00" />
<location zone="1200" x="15.0" y="288.8" z="-158.0" rw="0.724628" rx="0.00" ry="-0.689141" rz="0.00" scale="0.30" />
</locations>
</object>
<!--A vendor who we will give GM-only items-->
<object name="Demo Fella - GM Items Vendor" lot="1867">
<equipment>7630, 1727, 7453, 7521</equipment>
<config>
<key>vendorInvOverride=0:1727,7292,16553,2243,14535,14538,14531,6730</key>
</config>
<locations>
<location zone="1200" x="35.935" y="288.896" z="-128.213" rw="0.882977" rx="0.00" ry="-0.469416" rz="0.00" />
</locations>
</object>
<!--Friendly Felix will choose one of the 3 locations, then have a 50% chance to spawn at one of them on world server startup-->
<object lot="10141">
<config>
<key>useLocationsAsRandomSpawnPoint=7:1</key>
</config>
<locations>
<location zone="1200" x="31.819" y="288.896" z="-117.095" rw="0.630659" rx="0.00" ry="-0.776060" rz="0.00" chance="0.50"/>
<location zone="1200" x="42.755" y="291.897" z="-144.385" rw="0.855306" rx="0.00" ry="-0.518124" rz="0.00" chance="0.50"/>
<location zone="1200" x="3.984" y="288.896" z="-165.947" rw="0.978508" rx="0.00" ry="-0.206210" rz="0.00" chance="0.50"/>
</locations>
</object>
<!--Spawner(s) for enemies, largely copy-pasted from Portabello in this case-->
<object lot="176">
<config>
<key>CheckPrecondition=0:</key>
<key>SmashableDoesNotCutNavmesh=7:0</key>
<key>add_to_navmesh=7:1</key>
<key>aggroRadius=3:15</key>
<key>camGradSnap=7:0</key>
<key>camPrefersToFadeObject=7:1</key>
<key>carver_only=7:0</key>
<key>create_physics=7:1</key>
<key>currency=5:0</key>
<key>custom_config_names=0:</key>
<key>explode_factor=3:1</key>
<key>fxpriority=1:0</key>
<key>gravFactor=3:1</key>
<key>grpNameQBShowBricks=0:</key>
<key>ignoreCameraCollision=7:0</key>
<key>interaction_distance=3:16</key>
<key>is_smashable=7:1</key>
<key>loadOnClientOnly=7:0</key>
<key>loadSrvrOnly=7:0</key>
<key>max_to_spawn=1:-1</key>
<key>navmesh_carver=7:0</key>
<key>no_auto_spawn=7:1</key>
<key>no_timed_spawn=7:1</key>
<key>override_faction=7:0</key>
<key>radius=3:0</key>
<key>renderAnimLODSkew=3:1</key>
<key>renderCullingGroup=5:0</key>
<key>renderOffscreenAnimEnabled=7:0</key>
<key>respawn=3:20</key>
<key>sceneIDOverride=1:255</key>
<key>sceneIDOverrideEnabled=7:0</key>
<key>sceneLayerIDOverride=5:0</key>
<key>set_faction=13:4</key>
<key>smashable_loot_matrix=1:486</key>
<key>smashable_loot_matrix_set=7:0</key>
<key>softtetherRadius=3:15</key>
<key>spawner_active_on_load=7:1</key>
<key>spawntemplate=1:12379</key>
<key>startsQBActivator=7:0</key>
<key>template=1:-1</key>
<key>tetherRadius=3:15</key>
<key>usetetherdb=7:0</key>
<key>usewanderdb=7:0</key>
<key>wanderRadius=3:15</key>
</config>
<locations>
<location zone="1200" x="-16.749" y="291.841" z="-122.349" rw="1.00" rx="0.00" ry="0.00" rz="0.00" />
<location zone="1200" x="-15.696" y="291.608" z="-136.902" rw="1.00" rx="0.00" ry="0.00" rz="0.00" />
</locations>
</object>
<!--Spawner for a crate-->
<object lot="176">
<config>
<key>CheckPrecondition=0:</key>
<key>SmashableDoesNotCutNavmesh=7:0</key>
<key>add_to_navmesh=7:1</key>
<key>bounding_radius_override=3:0</key>
<key>camGradSnap=7:0</key>
<key>camPrefersToFadeObject=7:1</key>
<key>carver_only=7:0</key>
<key>create_physics=7:1</key>
<key>custom_config_names=0:</key>
<key>explode_factor=3:1</key>
<key>friction=3:1.5</key>
<key>fxpriority=1:0</key>
<key>gravFactor=3:1</key>
<key>grpNameQBShowBricks=0:</key>
<key>ignoreCameraCollision=7:0</key>
<key>interaction_distance=3:16</key>
<key>is_smashable=7:1</key>
<key>loadOnClientOnly=7:0</key>
<key>max_to_spawn=1:-1</key>
<key>navmesh_carver=7:0</key>
<key>no_auto_spawn=7:1</key>
<key>no_timed_spawn=7:1</key>
<key>override_faction=7:0</key>
<key>radius=3:0</key>
<key>renderCullingGroup=5:0</key>
<key>respawn=5:20000</key>
<key>sceneIDOverride=1:255</key>
<key>sceneIDOverrideEnabled=7:0</key>
<key>sceneLayerIDOverride=5:0</key>
<key>set_faction=13:6 </key>
<key>smashable_loot_matrix=1:227</key>
<key>smashable_loot_matrix_set=7:0</key>
<key>spawner_active_on_load=7:1</key>
<key>spawntemplate=1:2295</key>
<key>startsQBActivator=7:0</key>
<key>template=1:-1</key>
</config>
<locations>
<location zone="1200" x="4.232" y="288.895" z="-85.846" rw="-0.205988" rx="0.00" ry="0.978555" rz="0.00" />
</locations>
</object>
</objects>

View File

@ -11,6 +11,9 @@
<phrase>Everything is awesome!</phrase>
<phrase>I hope my behaviors are behaving themselves.</phrase>
</phrases>
<config>
<key>custom_script_client=0:scripts\ai\SPEC\MISSION_MINIGAME_CLIENT.lua</key>
</config>
<locations>
<location zone="1200" x="-352.5" y="287.6" z="217.7" rw="-0.095" rx="0.0" ry="0.995" rz="0.0" />
</locations>
@ -22,6 +25,9 @@
<phrase>Be careful crossing the gap!</phrase>
<phrase>Have The Maelstrom stopped going invisible?</phrase>
</phrases>
<config>
<key>custom_script_client=0:scripts\ai\SPEC\MISSION_MINIGAME_CLIENT.lua</key>
</config>
<locations>
<location zone="1800" x="745.756" y="75.262" z="-207.989" rw="0.838565" rx="0.0" ry="0.544801" rz="0.0" />
</locations>
@ -33,26 +39,30 @@
<phrase>Have you heard about Darkflame?</phrase>
<phrase>I've traveled across this entire system, but nothing beats the view here.</phrase>
</phrases>
<config>
<key>custom_script_client=0:scripts\ai\SPEC\MISSION_MINIGAME_CLIENT.lua</key>
</config>
<locations>
<location zone="1200" x="163.835" y="330.756" z="-141.933" rw="0.774887" rx="0.0" ry="-0.6321" rz="0.0" />
</locations>
</object>
<object name="averysumner - Destroyer of Worlds" lot="11235">
<equipment></equipment>
<phrases>
<phrase>cmerw[acowipaejio;fawjioefasdl;kfjm;</phrase>
<phrase>I, for one, welcome our new robot overlords.</phrase>
<phrase>zxnpoasdfiopwemsadf'kawpfo[ekasdf;'s</phrase>
<phrase>*teleports behind you*</phrase>
</phrases>
<script name="scripts\02_server\DLU\DLUVanityTeleportingObject.lua" />
<config>
<key data="teleport=7:1" />
<key data="teleportInterval=3:3.0" />
<key>useLocationsAsRandomSpawnPoint=7:1</key>
<key>teleportInterval=3:15.0</key>
<key>custom_script_server=0:scripts\02_server\DLU\DLUVanityTeleportingObject.lua</key>
<key>custom_script_client=0:scripts\ai\SPEC\MISSION_MINIGAME_CLIENT.lua</key>
</config>
<locations>
<location zone="1200" x="-361.583" y="285.541" z="64.4695" rw="0.785518" rx="0.0" ry="0.618838" rz="0.0" />
<location zone="1200" x="178.188" y="354.528" z="-173.932" rw="0.734375" rx="0.0" ry="-0.678744" rz="0.0" />
<location zone="1200" x="-318.569" y="287.637695" z="226.728" rw="-0.289502" rx="0.0" ry="0.957178" rz="0.0" />
<location zone="1200" x="389.093" y="295.119" z="-647.583" rw="0.851229" rx="0.0" ry="-0.524795" rz="0.0" />
</locations>
</object>
@ -69,10 +79,11 @@
<phrase>I have been idle for 10 minutes, and will be returned to the login screen in 5 minutes.</phrase>
<phrase>Of what has one to be proud, if dnot one's friends?</phrase>
</phrases>
<config>
<key>custom_script_client=0:scripts\ai\SPEC\MISSION_MINIGAME_CLIENT.lua</key>
</config>
<locations>
<location zone="1100" x="287" y="451" z="-98" rw="-0.320221" rx="0" ry="0.947343" rz="0" />
</locations>
<locations>
<location zone="1800" x="798" y="93" z="325" rw="-0.095" rx="0.0" ry="0.995" rz="0.0" />
</locations>
</object>
@ -100,11 +111,13 @@
<phrase>Gather uh banana</phrase>
<phrase>I've done nothing all day. Why am I here?</phrase>
</phrases>
<config>
<key>custom_script_client=0:scripts\ai\SPEC\MISSION_MINIGAME_CLIENT.lua</key>
</config>
<locations>
<location zone="1100" x="444.318" y="421.154" z="54.4241" rw="0.877539" rx="0.0" ry="0.479506" rz="0.0" />
</locations>
</object>
<!-- Mick - Brick Market Broker -->
<object name="Mick - Brick Market Broker" lot="6876">
<equipment>2519, 4091, 5128, 7990</equipment>
<phrases>
@ -119,11 +132,13 @@
<phrase>I know a guy that sells jetpacks, it ain't cheap but everything has a price.</phrase>
<phrase>You know Dr. Overbuild? He keeps ignoring my calls.</phrase>
</phrases>
<config>
<key>custom_script_client=0:scripts\ai\SPEC\MISSION_MINIGAME_CLIENT.lua</key>
</config>
<locations>
<location zone="1100" x="449.725" y="414.926" z="180.539" rw="0.180645" rx="0.0" ry="0.983548" rz="0.0" />
</locations>
</object>
<!-- Knightoffaith - Sage of Wanderlust -->
<object name="Knightoffaith - Sage of Wanderlust" lot="12260">
<equipment>7359, 7368, 7380, 7392, 7403, 8456</equipment>
<phrases>
@ -139,6 +154,9 @@
<phrase>Don't look at me, it was Sam's turn to look after Burno!</phrase>
<phrase>The Universe is beautiful - take some time to enjoy it</phrase>
</phrases>
<config>
<key>custom_script_client=0:scripts\ai\SPEC\MISSION_MINIGAME_CLIENT.lua</key>
</config>
<locations>
<location zone="1100" x="-381.053" y="367.787" z="-60.1185" rw="-0.14307" rx="0" ry="0.989713" rz="0" />
</locations>
@ -155,6 +173,9 @@
<phrase>Hope you enjoy DLU as much as I have, explorer!</phrase>
<phrase>There are many more memories still to be made.</phrase>
</phrases>
<config>
<key>custom_script_client=0:scripts\ai\SPEC\MISSION_MINIGAME_CLIENT.lua</key>
</config>
<locations>
<location zone="1200" x="-429.296" y="291.058" z="212.918" rw="0.339487" rx="0" ry="0.940611" rz="0" />
</locations>
@ -169,6 +190,9 @@
<phrase>There are many amazing properties to explore across the universe. You can visit some of them from the launchpads right here in Brick Annex.</phrase>
<phrase>Don't stop believing! Uhh, I mean building. Don't stop building!</phrase>
</phrases>
<config>
<key>custom_script_client=0:scripts\ai\SPEC\MISSION_MINIGAME_CLIENT.lua</key>
</config>
<locations>
<location zone="1200" x="-317.509" y="287.652" z="191.86" rw="0.57124" rx="0" ry="-0.820783" rz="0" />
</locations>
@ -185,6 +209,9 @@
<phrase>Look at that view!</phrase>
<phrase>Oxidize!</phrase>
</phrases>
<config>
<key>custom_script_client=0:scripts\ai\SPEC\MISSION_MINIGAME_CLIENT.lua</key>
</config>
<locations>
<location zone="1300" x="283.986" y="261.208" z="-128.466" rw="0.70207" rx="0" ry="0.712108" rz="0" />
</locations>
@ -197,6 +224,9 @@
<phrase>When you do extra steps, you can always customize the steps without redoing the whole staircase</phrase>
<phrase>Who needs lamps, when you have Tiki Torches</phrase>
</phrases>
<config>
<key>custom_script_client=0:scripts\ai\SPEC\MISSION_MINIGAME_CLIENT.lua</key>
</config>
<locations>
<location zone="1300" x="204.93" y="294.784" z="471.537" rw="0.85015" rx="0" ry="-0.52654" rz="0" />
</locations>
@ -213,6 +243,9 @@
<phrase>There's treasure inside of all of us. It's called Imagination.</phrase>
<phrase>Stromlings! Why did it have to be Stromlings?</phrase>
</phrases>
<config>
<key>custom_script_client=0:scripts\ai\SPEC\MISSION_MINIGAME_CLIENT.lua</key>
</config>
<locations>
<location zone="1300" x="-47.6296" y="322.527" z="533.5" rw="0.145135" rx="0" ry="0.989412" rz="0" />
</locations>
@ -225,13 +258,15 @@
<phrase>Wait, did the game close yet?</phrase>
<phrase>... What year is it?</phrase>
</phrases>
<config>
<key>custom_script_client=0:scripts\ai\SPEC\MISSION_MINIGAME_CLIENT.lua</key>
</config>
<locations>
<location zone="1400" x="-490.608" y="51.9449" z="-347.747" rw="0.594978" rx="0" ry="-0.803742" rz="0" />
</locations>
</object>
<object name="Wicked" lot="7412">
<equipment>8287, 2957, 7523, 16613</equipment>
<phrases></phrases>
<locations>
<location zone="1400" x="264.506" y="339.931" z="54.1201" rw="0.715223" rx="0" ry="0.698896" rz="0" />
</locations>
@ -248,17 +283,22 @@
<phrase>What do you mean my title is misspelled?</phrase>
<phrase>When I was your age, numbers were illegal.</phrase>
</phrases>
<config>
<key>custom_script_client=0:scripts\ai\SPEC\MISSION_MINIGAME_CLIENT.lua</key>
</config>
<locations>
<location zone="1900" x="-493.724" y="1124.05" z="-76.6355" rw="0.1719" rx="0" ry="0.985114" rz="0" />
</locations>
</object>
12651
<object name="Matthew - ?????" lot="2279">
<equipment>9856, 7793, 6928, 6927</equipment>
<phrases>
<phrase>I think I have a migraine from staring at this for so long</phrase>
<phrase>Anything but Portabello</phrase>
</phrases>
<config>
<key>custom_script_client=0:scripts\ai\SPEC\MISSION_MINIGAME_CLIENT.lua</key>
</config>
<locations>
<location zone="1900" x="-227.621" y="1188.28" z="145.734" rw="-0.254353" rx="0" ry="0.967111" rz="0" />
</locations>
@ -268,6 +308,9 @@
<phrases>
<phrase>Per Aspera ad Astra</phrase>
</phrases>
<config>
<key>custom_script_client=0:scripts\ai\SPEC\MISSION_MINIGAME_CLIENT.lua</key>
</config>
<locations>
<location zone="1900" x="-190.673" y="1218.34" z="221.6" rw="0.972371" rx="0" ry="-0.233441" rz="0" />
</locations>
@ -280,6 +323,9 @@
<phrase>Have you tried those buttery croissants from “Farnham Spoon” they are delicious! I might just go and have another</phrase>
<phrase>Have fun!</phrase>
</phrases>
<config>
<key>custom_script_client=0:scripts\ai\SPEC\MISSION_MINIGAME_CLIENT.lua</key>
</config>
<locations>
<location zone="1800" x="-39.2259" y="96.8605" z="-550.077" rw="0.815145" rx="0" ry="-0.579257" rz="0" />
</locations>
@ -291,6 +337,9 @@
<phrase>Wu once told me, "The path we seek is never a straight line"."</phrase>
<phrase>Stop! I'm from the future to warn you that there will be snakes, ninja robots! More snakes, ghosts, sky pirates, snakes that make snakes! Bikers, dragon hunters... Wait what do you mean it hasn't happened yet?</phrase>
</phrases>
<config>
<key>custom_script_client=0:scripts\ai\SPEC\MISSION_MINIGAME_CLIENT.lua</key>
</config>
<locations>
<location zone="2000" x="-119.269" y="230.372" z="226.63" rw="0.381416" rx="0" ry="0.924404" rz="0" />
</locations>
@ -312,6 +361,9 @@
<phrase>Gonk.</phrase>
<phrase>There is a theory which states that if ever anyone discovers exactly what the Universe is for and why it is here, it will instantly disappear and be replaced by something even more bizarre and inexplicable. There is another theory, which states that this has already happened.</phrase>
</phrases>
<config>
<key>custom_script_client=0:scripts\ai\SPEC\MISSION_MINIGAME_CLIENT.lua</key>
</config>
<locations>
<location zone="1200" x="197.549" y="259.137" z="-587.759" rw="0.873694" rx="0" ry="0.486475" rz="0" />
</locations>
@ -335,13 +387,15 @@
<phrase>Excuse me, do you know the way to Frostburgh?</phrase>
<phrase>What are those Paradox scientists up to these days?</phrase>
</phrases>
<config>
<key>custom_script_client=0:scripts\ai\SPEC\MISSION_MINIGAME_CLIENT.lua</key>
</config>
<locations>
<location zone="1100" x="436.949" y="415.264" z="151.381" rw="0.528425" rx="0" ry="0.84898" rz="0" />
</locations>
</object>
<object name="Ben" lot="2279">
<equipment>8664, 4065, 2587</equipment>
<phrases></phrases>
<locations>
<location zone="1200" x="284.538" y="260.627" z="-506.692" rw="0.819721" rx="0" ry="0.572763" rz="0" />
</locations>
@ -354,6 +408,9 @@
<phrase>I think I may be lost...</phrase>
<phrase>I'm feeling a bit emotionally conflicted right now</phrase>
</phrases>
<config>
<key>custom_script_client=0:scripts\ai\SPEC\MISSION_MINIGAME_CLIENT.lua</key>
</config>
<locations>
<location zone="1300" x="-171.217" y="246.482" z="-147.05" rw="-0.203118" rx="0" ry="0.979154" rz="0" />
</locations>
@ -371,6 +428,10 @@
<phrase>I love cats, little meow meows</phrase>
<phrase>It's not perfect, but it works!</phrase>
</phrases>
<config>
<key>custom_script_client=0:scripts\ai\SPEC\MISSION_MINIGAME_CLIENT.lua</key>
<key>vendorInvOverride=0:11909,7785,12764,12241</key>
</config>
<locations>
<location zone="1201" x="197.709" y="179.335" z="-8.05284" rw="0.544424" rx="0" ry="-0.83881" rz="0" />
</locations>

View File

@ -1,4 +1,5 @@
<files>
<file name="dev-tribute.xml" enabled="1"/>
<file name="atm.xml" enabled="0"/>
<file name="demo.xml" enabled="0"/>
</files>