#include #include "WeenieFactory.h" #include "Door.h" #include "Lifestone.h" #include "BindStone.h" #include "Portal.h" #include "Monster.h" #include "TownCrier.h" #include "Vendor.h" #include "World.h" #include "ClothingCache.h" #include "Player.h" #include "HotSpot.h" #include "PressurePlate.h" #include "Switch.h" #include "Book.h" #include "Food.h" #include "Caster.h" #include "Clothing.h" #include "Corpse.h" #include "Healer.h" #include "House.h" #include "Scroll.h" #include "ManaStone.h" #include "MeleeWeapon.h" #include "Missile.h" #include "MissileLauncher.h" #include "Config.h" #include "Key.h" #include "Lockpick.h" #include "Gem.h" #include "PKModifier.h" #include "Ammunition.h" #include "AttributeTransferDevice.h" #include "SkillAlterationDevice.h" #include "GameEventManager.h" #include "TreasureFactory.h" #include "AugmentationDevice.h" #include "Game.h" #include "GamePiece.h" #include "weenie/PetDevice.h" #include "WorldLandBlock.h" CWeenieFactory::CWeenieFactory() { } CWeenieFactory::~CWeenieFactory() { Reset(); } void CWeenieFactory::Reset() { for (auto entry : m_WeenieDefaults) delete entry.second; m_WeenieDefaults.clear(); m_WeenieDefaultsByName.clear(); m_ScrollWeenies.clear(); } void CWeenieFactory::Initialize() { LOG_PRIVATE(Data, Normal, "Loading weenies...\n"); SERVER_INFO << "Loading weenies...\n"; CStopWatch watch; double elapsed = 0; LoadLocalStorage(); elapsed = watch.GetElapsed(); LOG_PRIVATE(Data, Normal, "LocalStorage loaded in %fs\n", elapsed); SERVER_INFO << "LocalStorage loaded in" << elapsed; watch.Reset(); LoadLocalStorageIndexed(); elapsed = watch.GetElapsed(); LOG_PRIVATE(Data, Normal, "Cache.bin loaded in %fs\n", elapsed); SERVER_INFO << "Cache.bin loaded in" << elapsed; //LoadAvatarData(); MapScrollWCIDs(); LOG_PRIVATE(Data, Normal, "Loaded %d weenie defaults...\n", m_WeenieDefaults.size()); SERVER_INFO << "Loaded" << m_WeenieDefaults.size() << "weenie defaults..."; } uint32_t CWeenieFactory::GetScrollSpellForWCID(uint32_t wcid) { if (CWeenieDefaults *defaults = GetWeenieDefaults(wcid)) { if (defaults->m_Qualities.m_WeenieType == Scroll_WeenieType) { uint32_t spell_id = 0; if (defaults->m_Qualities.InqDataID(SPELL_DID, spell_id)) { return spell_id; } } } return 0; } uint32_t CWeenieFactory::GetWCIDForScrollSpell(uint32_t spell_id) { auto i = m_ScrollWeenies.find(spell_id); if (i != m_ScrollWeenies.end()) return i->second; return 0; } void CWeenieFactory::MapScrollWCIDs() { m_ScrollWeenies.clear(); for (auto &entry : m_WeenieDefaults) { if (entry.second->m_Qualities.m_WeenieType != Scroll_WeenieType) { continue; } uint32_t spell_id; if (entry.second->m_Qualities.InqDataID(SPELL_DID, spell_id)) { m_ScrollWeenies[spell_id] = entry.first; } } } std::list CWeenieFactory::GetWCIDsWithMotionTable(uint32_t mtable) { std::list results; for (auto &entry : m_WeenieDefaults) { uint32_t mid = 0; if (entry.second->m_Qualities.InqDataID(MOTION_TABLE_DID, mid)) { if (mtable == mid) { results.push_back(entry.first); } } } return results; } CWeenieDefaults *CWeenieFactory::GetWeenieDefaults(uint32_t wcid) { auto i = m_WeenieDefaults.find(wcid); if (i != m_WeenieDefaults.end()) return i->second; return NULL; } uint32_t CWeenieFactory::GetWCIDByName(const char *name, int index) { CWeenieDefaults *defaults = GetWeenieDefaults(name, index); if (!defaults) return 0; return defaults->m_Qualities.GetID(); } CWeenieDefaults *CWeenieFactory::GetWeenieDefaults(const char *name, int index) { std::string search = name; // convert name for easier matching, lowercase and remove any spaces std::transform(search.begin(), search.end(), search.begin(), ::tolower); search.erase(remove_if(search.begin(), search.end(), isspace), search.end()); std::pair< std::multimap::iterator, std::multimap::iterator> range = m_WeenieDefaultsByName.equal_range(search); for (std::multimap::iterator i = range.first; i != range.second; i++) { if (!index) return i->second; index--; } return NULL; } bool CWeenieFactory::UpdateWeenieBody(uint32_t wcid, Body* newBody) { auto i = m_WeenieDefaults.find(wcid); if (i != m_WeenieDefaults.end()) { i->second->m_Qualities._body = new Body(*newBody); } return false; } bool CWeenieFactory::ApplyWeenieDefaults(CWeenieObject *weenie, uint32_t wcid) { auto defaults = GetWeenieDefaults(wcid); if (defaults) { ApplyWeenieDefaults(weenie, defaults); return true; } else { LOG_PRIVATE(Data, Warning, "Failed to find defaults for WCID %u! This is bad!\n", wcid); SERVER_WARN << "Failed to find defaults for WCID " << wcid << "! This is bad!"; return false; } } void CWeenieFactory::ApplyWeenieDefaults(CWeenieObject *weenie, CWeenieDefaults *defaults) { weenie->m_Qualities.CopyFrom(&defaults->m_Qualities); weenie->m_Qualities.RemoveInt(PARENT_LOCATION_INT); int weaponSkill; if (weenie->m_Qualities.InqInt(WEAPON_SKILL_INT, weaponSkill, TRUE, FALSE)) weenie->m_Qualities.SetInt(WEAPON_SKILL_INT, SkillTable::OldToNewSkill((STypeSkill)weaponSkill)); int wieldReq; wieldReq = weenie->m_Qualities.GetInt(WIELD_REQUIREMENTS_INT, 0); if (wieldReq == 1 || wieldReq == 2 || wieldReq == 8) { if (weenie->m_Qualities.InqInt(WIELD_SKILLTYPE_INT, weaponSkill, TRUE, FALSE)) weenie->m_Qualities.SetInt(WIELD_SKILLTYPE_INT, SkillTable::OldToNewSkill((STypeSkill)weaponSkill)); } wieldReq = weenie->m_Qualities.GetInt(WIELD_REQUIREMENTS_2_INT, 0); if (wieldReq == 1 || wieldReq == 2 || wieldReq == 8) { if (weenie->m_Qualities.InqInt(WIELD_SKILLTYPE_2_INT, weaponSkill, TRUE, FALSE)) weenie->m_Qualities.SetInt(WIELD_SKILLTYPE_2_INT, SkillTable::OldToNewSkill((STypeSkill)weaponSkill)); } wieldReq = weenie->m_Qualities.GetInt(WIELD_REQUIREMENTS_3_INT, 0); if (wieldReq == 1 || wieldReq == 2 || wieldReq == 8) { if (weenie->m_Qualities.InqInt(WIELD_SKILLTYPE_3_INT, weaponSkill, TRUE, FALSE)) weenie->m_Qualities.SetInt(WIELD_SKILLTYPE_3_INT, SkillTable::OldToNewSkill((STypeSkill)weaponSkill)); } wieldReq = weenie->m_Qualities.GetInt(WIELD_REQUIREMENTS_4_INT, 0); if (wieldReq == 1 || wieldReq == 2 || wieldReq == 8) { if (weenie->m_Qualities.InqInt(WIELD_SKILLTYPE_4_INT, weaponSkill, TRUE, FALSE)) weenie->m_Qualities.SetInt(WIELD_SKILLTYPE_4_INT, SkillTable::OldToNewSkill((STypeSkill)weaponSkill)); } Skill uaSkill; if (weenie->m_Qualities.InqSkill(UNARMED_COMBAT_SKILL, uaSkill)) { Skill finSkill; if (!weenie->m_Qualities.InqSkill(LIGHT_WEAPONS_SKILL, finSkill)) { weenie->m_Qualities.SetSkill(LIGHT_WEAPONS_SKILL, uaSkill); } } if (weenie->m_Qualities._skillStatsTable) { for (auto &entry : *weenie->m_Qualities._skillStatsTable) { STypeSkill newSkill = SkillTable::OldToNewSkill((STypeSkill)entry.first); if (newSkill != entry.first) { weenie->m_Qualities.SetSkill(newSkill, entry.second); } } } std::string eventString; if (weenie->m_Qualities.InqString(GENERATOR_EVENT_STRING, eventString)) { eventString = g_pGameEventManager->NormalizeEventName(eventString.c_str()); weenie->m_Qualities.SetString(GENERATOR_EVENT_STRING, eventString); } weenie->m_ObjDescOverride = defaults->m_WorldObjDesc; weenie->m_WornObjDesc = defaults->m_WornObjDesc; } void CWeenieFactory::RefreshLocalStorage() { LoadLocalStorage(true); MapScrollWCIDs(); } bool CWeenieFactory::RefreshLocalWeenie(uint32_t wcid) { return LoadLocalWeenie(wcid); } void CWeenieFactory::LoadLocalStorage(bool refresh) { std::mutex mapLock; std::mutex multiMapLock; fs::path root = g_pGlobals->GetGameData("Data", "json"); PerformLoad(root / "weenies", [&](fs::path path) { std::ifstream fs(path); json jsonData; CWeenieDefaults *pDefaults = new CWeenieDefaults(); bool parsed = false; try { fs >> jsonData; parsed = pDefaults->m_Qualities.UnPackJson(jsonData); } catch (std::exception &ex) { LOG_PRIVATE(Data, Error, "Failed to parse Weenie file %s\n", path.string().c_str()); SERVER_ERROR << "Failed to parse Weenie file" << path.string(); } fs.close(); if (parsed) { pDefaults->m_WCID = pDefaults->m_Qualities.id; pDefaults->m_sourceFile = path.string(); bool bShouldInsert = true; bool bDuplicate = false; auto existing = m_WeenieDefaults.find(pDefaults->m_WCID); if (existing != m_WeenieDefaults.end()) { if (refresh) { } else { if (existing->second->m_bIsAutoGenerated) { if (pDefaults->m_bIsAutoGenerated) { bDuplicate = true; bShouldInsert = false; } } else { // existing was not auto-generated bShouldInsert = false; if (!pDefaults->m_bIsAutoGenerated) { bDuplicate = true; } } } } if (bShouldInsert) { CWeenieDefaults *toDelete = NULL; { std::scoped_lock lock(mapLock); if (existing != m_WeenieDefaults.end()) { toDelete = existing->second; } m_WeenieDefaults[pDefaults->m_WCID] = pDefaults; } std::string name; if (pDefaults->m_Qualities.InqString(NAME_STRING, name)) { // convert name for easier matching, lowercase and remove any spaces std::transform(name.begin(), name.end(), name.begin(), ::tolower); name.erase(remove_if(name.begin(), name.end(), isspace), name.end()); // remove if this WCID is already in here for (std::multimap::iterator i = m_WeenieDefaultsByName.begin(); i != m_WeenieDefaultsByName.end(); i++) { if (i->second->m_Qualities.GetID() == pDefaults->m_Qualities.GetID()) { std::scoped_lock lock(multiMapLock); m_WeenieDefaultsByName.erase(i); break; } } std::scoped_lock lock(multiMapLock); m_WeenieDefaultsByName.insert(std::pair(name, pDefaults)); } if (toDelete) { delete toDelete; } } else { if (bDuplicate) { LOG_PRIVATE(Data, Warning, "Duplicate WCID %d, ignoring WQF file: \"%s\" due to \"%s\"\n", pDefaults->m_WCID, path.string().c_str(), existing->second->m_sourceFile.c_str()); SERVER_WARN << "Duplicate WCID" << pDefaults->m_WCID << ", ignoring file \"" << path.string() << "\" due to \"" << existing->second->m_sourceFile << "\""; } delete pDefaults; } } }); } bool CWeenieFactory::LoadLocalWeenie(uint32_t wcid) { auto existing = m_WeenieDefaults.find(wcid); if (existing != m_WeenieDefaults.end()) { fs::path path = m_WeenieDefaults[wcid]->m_sourceFile.c_str(); if (path.extension().compare(".json") != 0) { path = g_pGlobals->GetGameData("Data", "json"); path = path / "weenies" / (std::to_string(wcid) + " - " + m_WeenieDefaults[wcid]->m_Description + ".json"); } std::mutex mapLock; std::mutex multiMapLock; std::ifstream fs(path); if (fs.is_open()) { json jsonData; CWeenieDefaults *pDefaults = new CWeenieDefaults(); bool parsed = false; try { fs >> jsonData; parsed = pDefaults->m_Qualities.UnPackJson(jsonData); } catch (std::exception &ex) { LOG_PRIVATE(Data, Error, "Failed to parse Weenie file %s\n", path.string().c_str()); SERVER_ERROR << "Failed to parse Weenie file" << path.string(); } fs.close(); if (parsed) { pDefaults->m_WCID = pDefaults->m_Qualities.id; pDefaults->m_sourceFile = path.string(); CWeenieDefaults *toDelete = NULL; { std::scoped_lock lock(mapLock); if (existing != m_WeenieDefaults.end()) { toDelete = existing->second; } m_WeenieDefaults[pDefaults->m_WCID] = pDefaults; } std::string name; if (pDefaults->m_Qualities.InqString(NAME_STRING, name)) { // convert name for easier matching, lowercase and remove any spaces std::transform(name.begin(), name.end(), name.begin(), ::tolower); name.erase(remove_if(name.begin(), name.end(), isspace), name.end()); // remove if this WCID is already in here for (std::multimap::iterator i = m_WeenieDefaultsByName.begin(); i != m_WeenieDefaultsByName.end(); i++) { if (i->second->m_Qualities.GetID() == pDefaults->m_Qualities.GetID()) { std::scoped_lock lock(multiMapLock); m_WeenieDefaultsByName.erase(i); break; } } std::scoped_lock lock(multiMapLock); m_WeenieDefaultsByName.insert(std::pair(name, pDefaults)); } if (toDelete) { delete toDelete; } return true; } else { delete pDefaults; } } } return false; } bool CWeenieFactory::LoadLocalWeenieByWcid(uint32_t wcid) { auto existing = m_WeenieDefaults.find(wcid); if (existing == m_WeenieDefaults.end()) { fs::path path = g_pGlobals->GetGameData("Data", "json"); path = path / "weenies" / (std::to_string(wcid) + ".json"); std::mutex mapLock; std::mutex multiMapLock; std::ifstream fs(path); if (fs.is_open()) { json jsonData; CWeenieDefaults *pDefaults = new CWeenieDefaults(); bool parsed = false; try { fs >> jsonData; parsed = pDefaults->m_Qualities.UnPackJson(jsonData); } catch (std::exception &ex) { LOG_PRIVATE(Data, Error, "Failed to parse Weenie file %s\n", path.string().c_str()); } fs.close(); if (parsed) { pDefaults->m_WCID = pDefaults->m_Qualities.id; pDefaults->m_sourceFile = path.string(); CWeenieDefaults *toDelete = NULL; { std::scoped_lock lock(mapLock); if (existing != m_WeenieDefaults.end()) { toDelete = existing->second; } m_WeenieDefaults[pDefaults->m_WCID] = pDefaults; } std::string name; if (pDefaults->m_Qualities.InqString(NAME_STRING, name)) { // convert name for easier matching, lowercase and remove any spaces std::transform(name.begin(), name.end(), name.begin(), ::tolower); name.erase(remove_if(name.begin(), name.end(), isspace), name.end()); // remove if this WCID is already in here for (std::multimap::iterator i = m_WeenieDefaultsByName.begin(); i != m_WeenieDefaultsByName.end(); i++) { if (i->second->m_Qualities.GetID() == pDefaults->m_Qualities.GetID()) { std::scoped_lock lock(multiMapLock); m_WeenieDefaultsByName.erase(i); break; } } std::scoped_lock lock(multiMapLock); m_WeenieDefaultsByName.insert(std::pair(name, pDefaults)); } if (toDelete) { delete toDelete; } return true; } else { delete pDefaults; } } } return false; } void CWeenieFactory::LoadLocalStorageIndexed() { BYTE *data = NULL; uint32_t length = 0; if (LoadDataFromPhatDataBin(9, &data, &length, 0xd8fd6b02, 0xa0427974)) { BinaryReader reader(data, length); uint32_t count = reader.Read(); for (uint32_t i = 0; i < count; i++) { uint32_t the_wcid = reader.Read(); CWeenieDefaults *pDefaults = new CWeenieDefaults(); if (pDefaults->UnPack(&reader)) { /* if (pDefaults->m_Qualities.m_IntStats) { for (auto &entry : *pDefaults->m_Qualities.m_IntStats) { if (entry.first == IMBUED_EFFECT_INT) { if (entry.second & ArmorRending_ImbuedEffectType) { DebugBreak(); } } } } */ bool bShouldInsert = true; bool bDuplicate = false; auto existing = m_WeenieDefaults.find(pDefaults->m_WCID); if (existing != m_WeenieDefaults.end()) { if (existing->second->m_bIsAutoGenerated) { if (pDefaults->m_bIsAutoGenerated) { bDuplicate = true; bShouldInsert = false; } } else { // existing was not auto-generated bShouldInsert = false; if (!pDefaults->m_bIsAutoGenerated) { bDuplicate = true; } } } if (bShouldInsert) { //auto existing = m_WeenieDefaults.find(pDefaults->m_WCID); if (existing != m_WeenieDefaults.end()) delete existing->second; //pDefaults->m_sourceFile = filePath; m_WeenieDefaults[pDefaults->m_WCID] = pDefaults; std::string name; if (pDefaults->m_Qualities.InqString(NAME_STRING, name)) { // convert name for easier matching, lowercase and remove any spaces std::transform(name.begin(), name.end(), name.begin(), ::tolower); name.erase(remove_if(name.begin(), name.end(), isspace), name.end()); // remove if this WCID is already in here /* for (std::multimap::iterator i = m_WeenieDefaultsByName.begin(); i != m_WeenieDefaultsByName.end(); i++) { if (i->second->m_Qualities.GetID() == pDefaults->m_Qualities.GetID()) { m_WeenieDefaultsByName.erase(i); break; } } */ m_WeenieDefaultsByName.insert(std::pair(name, pDefaults)); } } else { if (bDuplicate) { SERVER_ERROR << "Duplicate WCID" << pDefaults->m_WCID << ", ignoring due to" << m_WeenieDefaults[pDefaults->m_WCID]->m_sourceFile.c_str(); } delete pDefaults; } } else { SERVER_ERROR << "Error parsing weenie defaults file"; delete pDefaults; } } delete[] data; } } void CWeenieFactory::LoadAvatarData() { m_NumAvatars = 0; BYTE *data = NULL; uint32_t length = 0; if (LoadDataFromPhatDataBin(7, &data, &length, 0x591c34e9, 0x2250b020)) { BinaryReader reader(data, length); PackableHashTable avatarTable; avatarTable.UnPack(&reader); CWeenieDefaults *pRobeWithHood = GetWeenieDefaults(5851); uint32_t autoWcid = m_FirstAvatarWCID = 7000000; if (pRobeWithHood) { for (auto &avatar : avatarTable) { autoWcid++; m_NumAvatars++; CWeenieDefaults *pDefaults = new CWeenieDefaults(); pDefaults->m_Qualities.CopyFrom(&pRobeWithHood->m_Qualities); pDefaults->m_Qualities.SetString(NAME_STRING, csprintf("%s Jumpsuit", avatar.first.c_str())); pDefaults->m_Qualities.SetInt(CLOTHING_PRIORITY_INT, pDefaults->m_Qualities.GetInt(CLOTHING_PRIORITY_INT, 0) | HAND_WEAR_CLOTHING_PRIORITY); pDefaults->m_Qualities.SetInt(LOCATIONS_INT, pDefaults->m_Qualities.GetInt(LOCATIONS_INT, 0) | HAND_WEAR_LOC); pDefaults->m_Qualities.SetDataID(ICON_DID, 0x6001036); pDefaults->m_Qualities.SetBool(IGNORE_CLO_ICONS_BOOL, TRUE); pDefaults->m_WornObjDesc = avatar.second; pDefaults->m_WCID = autoWcid; pDefaults->m_Qualities.id = autoWcid; CWeenieDefaults *toDelete = NULL; auto existing = m_WeenieDefaults.find(pDefaults->m_WCID); if (existing != m_WeenieDefaults.end()) { toDelete = existing->second; } m_WeenieDefaults[pDefaults->m_WCID] = pDefaults; std::string name; if (pDefaults->m_Qualities.InqString(NAME_STRING, name)) { // convert name for easier matching, lowercase and remove any spaces std::transform(name.begin(), name.end(), name.begin(), ::tolower); name.erase(remove_if(name.begin(), name.end(), isspace), name.end()); // remove if this WCID is already in here /* for (std::multimap::iterator i = m_WeenieDefaultsByName.begin(); i != m_WeenieDefaultsByName.end(); i++) { if (i->second->m_Qualities.GetID() == pDefaults->m_Qualities.GetID()) { m_WeenieDefaultsByName.erase(i); break; } } */ m_WeenieDefaultsByName.insert(std::pair(name, pDefaults)); } if (toDelete) { delete toDelete; } } } delete[] data; } } CWeenieObject *CWeenieFactory::CreateWeenieByClassID(uint32_t wcid, const Position *pos, bool bSpawn) { if (!wcid) return NULL; CWeenieDefaults *defaults = GetWeenieDefaults(wcid); if (!defaults) return NULL; if (!g_pConfig->CreateTemplates() && (!strcmp(defaults->m_Qualities.GetString(NAME_STRING, "").c_str(), "Name Me Please") || !strcmp(defaults->m_Qualities.GetString(NAME_STRING, "").c_str(), "CreatureName"))) return NULL; return CreateWeenie(defaults, pos, bSpawn); } CWeenieObject *CWeenieFactory::CreateWeenieByName(const char *name, const Position *pos, bool bSpawn) { CWeenieDefaults *defaults = GetWeenieDefaults(name); if (!defaults) return NULL; if (!g_pConfig->CreateTemplates() && (!strcmp(defaults->m_Qualities.GetString(NAME_STRING, "").c_str(), "Name Me Please") || !strcmp(defaults->m_Qualities.GetString(NAME_STRING, "").c_str(), "CreatureName"))) return NULL; return CreateWeenie(defaults, pos, bSpawn); } CWeenieObject *CWeenieFactory::CreateBaseWeenieByType(int weenieType, unsigned int wcid, const char *weenieName) { //Disabled this as it's only used during character creation, and at that point we don't want to treat //the weenie as a CPlayerWeenie as that entails client communication and both the client and the weenie //are not ready for that at that moment. //if (wcid == 1) // return new CPlayerWeenie(NULL, 0, 0); CWeenieObject *weenie; switch (weenieType) { case Generic_WeenieType: case Admin_WeenieType: { weenie = new CWeenieObject(); break; } case Cow_WeenieType: case Creature_WeenieType: { if (!strcmp(weenieName, "Town Crier")) weenie = new CTownCrier(); else weenie = new CMonsterWeenie(); break; } case AttributeTransferDevice_WeenieType: { weenie = new CAttributeTransferDeviceWeenie(); break; } case SkillAlterationDevice_WeenieType: { weenie = new CSkillAlterationDeviceWeenie(); break; } case Container_WeenieType: { weenie = new CContainerWeenie(); break; } case Portal_WeenieType: { weenie = new CPortal(); break; } case LifeStone_WeenieType: { weenie = new CBaseLifestone(); break; } case AllegianceBindstone_WeenieType: { weenie = new CBindStone(); break; } case Door_WeenieType: { weenie = new CBaseDoor(); break; } case HotSpot_WeenieType: { weenie = new CHotSpotWeenie(); break; } case Switch_WeenieType: { weenie = new CSwitchWeenie(); break; } case PressurePlate_WeenieType: { weenie = new CPressurePlateWeenie(); break; } case Vendor_WeenieType: { /* if (wcid == 719) weenie = new CAvatarVendor(); // holtburg avatar vendor else */ weenie = new CVendor(); break; } case Clothing_WeenieType: { weenie = new CClothingWeenie(); break; } case Caster_WeenieType: { weenie = new CCasterWeenie(); break; } case Healer_WeenieType: { weenie = new CHealerWeenie(); break; } case House_WeenieType: { weenie = new CHouseWeenie(); break; } case SlumLord_WeenieType: { weenie = new CSlumLordWeenie(); break; } case Scroll_WeenieType: { weenie = new CScrollWeenie(); break; } case Ammunition_WeenieType: { weenie = new CAmmunitionWeenie(); break; } case Missile_WeenieType: { weenie = new CMissileWeenie(); break; } case ManaStone_WeenieType: { weenie = new CManaStoneWeenie(); break; } case Coin_WeenieType: { weenie = new CWeenieObject(); break; } case Book_WeenieType: { weenie = new CBookWeenie(); break; } case Food_WeenieType: { weenie = new CFoodWeenie(); break; } case PKModifier_WeenieType: { weenie = new CPKModifierWeenie(); break; } case Gem_WeenieType: { weenie = new CGemWeenie(); break; } case Key_WeenieType: { weenie = new CKeyWeenie(); break; } case Lockpick_WeenieType: { weenie = new CLockpickWeenie(); break; } case Corpse_WeenieType: { weenie = new CCorpseWeenie(); break; } case MeleeWeapon_WeenieType: { weenie = new CMeleeWeaponWeenie(); break; } case MissileLauncher_WeenieType: { weenie = new CMissileLauncherWeenie(); break; } case Hook_WeenieType: { weenie = new CHookWeenie(); break; } case Deed_WeenieType: { weenie = new CDeedWeenie(); break; } case BootSpot_WeenieType: { weenie = new CBootSpotWeenie(); break; } case HousePortal_WeenieType: { weenie = new CHousePortalWeenie(); break; } case Chest_WeenieType: { weenie = new CChestWeenie(); break; } case Storage_WeenieType: { weenie = new CStorageWeenie(); break; } case AugmentationDevice_WeenieType: { weenie = new CAugmentationDeviceWeenie(); break; } case Game_WeenieType: weenie = new GameWeenie(); break; case GamePiece_WeenieType: weenie = new GamePieceWeenie(); break; case Pet_WeenieType: weenie = new PetPassive(); break; case PetDevice_WeenieType: weenie = new PetDevice(); break; case CombatPet_WeenieType: weenie = new PetCombat(); break; default: weenie = new CWeenieObject(); break; } return weenie; } CWeenieObject *CWeenieFactory::CreateWeenie(CWeenieDefaults *defaults, const Position *pos, bool bSpawn) { CWeenieObject *weenie = CreateBaseWeenieByType( (defaults->m_Qualities.GetInt(ITEM_TYPE_INT, 0) & TYPE_VESTEMENTS) ? Clothing_WeenieType : defaults->m_Qualities.m_WeenieType, defaults->m_Qualities.GetID(), defaults->m_Qualities.GetString(NAME_STRING, "").c_str()); ApplyWeenieDefaults(weenie, defaults); weenie->ApplyQualityOverrides(); if (!weenie->IsAvatarJumpsuit()) TryToResolveAppearanceData(weenie); if (weenie->IsCreature()) weenie->SetMaxVitals(); if (pos) weenie->SetInitialPosition(*pos); if (bSpawn) { if (!g_pWorld->CreateEntity(weenie)) return NULL; } return weenie; } CWeenieObject *CWeenieFactory::CloneWeenie(CWeenieObject *weenie) { CWeenieObject *clone = CreateBaseWeenieByType( (weenie->m_Qualities.GetInt(ITEM_TYPE_INT, 0) & TYPE_VESTEMENTS) ? Clothing_WeenieType : weenie->m_Qualities.m_WeenieType, weenie->m_Qualities.GetID(), weenie->InqStringQuality(NAME_STRING, "").c_str()); clone->m_Qualities.CopyFrom(&weenie->m_Qualities); clone->m_bObjDescOverride = weenie->m_bObjDescOverride; clone->m_ObjDescOverride = weenie->m_ObjDescOverride; clone->m_WornObjDesc = weenie->m_WornObjDesc; return clone; } void CWeenieFactory::AddWeenieToDestination(CWeenieObject *weenie, CWeenieObject *parent, uint32_t destinationType, bool isRegenLocationType, const GeneratorProfile *profile) { if (!isRegenLocationType) { switch ((DestinationType)destinationType) { case Contain_DestinationType: destinationType = Contain_RegenLocationType; break; case ContainTreasure_DestinationType: destinationType = ContainTreasure_RegenLocationType; break; case Wield_DestinationType: destinationType = Wield_RegenLocationType; break; case WieldTreasure_DestinationType: destinationType = WieldTreasure_RegenLocationType; break; case Shop_DestinationType: destinationType = Shop_RegenLocationType; break; case ShopTreasure_DestinationType: destinationType = ShopTreasure_RegenLocationType; break; case Checkpoint_DestinationType: destinationType = Checkpoint_RegenLocationType; break; case Treasure_DestinationType: destinationType = Treasure_RegenLocationType; break; case HouseBuy_DestinationType: case HouseRent_DestinationType: return; } } Position pos = parent->GetPosition(); if (profile && profile->stackSize > 1 && !weenie->AsMonster()) { //Use stack size from generator profile so long as the weenie is NOT a creature. if (profile->stackSize <= weenie->m_Qualities.GetInt(MAX_STACK_SIZE_INT, 0)) weenie->SetStackSize(profile->stackSize); else weenie->SetStackSize(weenie->m_Qualities.GetInt(MAX_STACK_SIZE_INT, 0)); } switch ((RegenLocationType)destinationType) { case Contain_RegenLocationType: case ContainTreasure_RegenLocationType: case Treasure_RegenLocationType: if (CContainerWeenie *container = parent->AsContainer()) container->SpawnInContainer(weenie); break; case Wield_RegenLocationType: case WieldTreasure_RegenLocationType: if (CMonsterWeenie *creature = parent->AsMonster()) creature->SpawnWielded(weenie); else if (CContainerWeenie *container = parent->AsContainer()) container->SpawnInContainer(weenie); break; case Specific_RegenLocationType: case SpecificTreasure_RegenLocationType: if (!profile->pos_val.objcell_id) pos.frame.m_origin = pos.localtoglobal(profile->pos_val.frame.m_origin); else pos = profile->pos_val; weenie->SetInitialPosition(pos); if (!g_pWorld->CreateEntity(weenie)) { SERVER_ERROR << "Failed creating generated spawn" << GetWCIDName(profile->type); return; } break; case Scatter_RegenLocationType: case ScatterTreasure_RegenLocationType: { float genRadius = (float)parent->InqFloatQuality(GENERATOR_RADIUS_FLOAT, 0.0f); float random_x = Random::GenFloat(-genRadius, genRadius); float random_y = Random::GenFloat(-genRadius, genRadius); pos.frame.m_origin += Vector(random_x, random_y, 0.05f); weenie->SetInitialPosition(pos); if (!g_pWorld->CreateEntity(weenie)) { SERVER_ERROR << "Failed creating generated spawn" << GetWCIDName(profile->type); return; } } break; case OnTop_RegenLocationType: case OnTopTreasure_RegenLocationType: //untested if (!profile->pos_val.objcell_id) pos.frame.m_origin = pos.localtoglobal(profile->pos_val.frame.m_origin); else pos = profile->pos_val; if ((pos.objcell_id & 0xFFFF) < 0x100) //outdoors { // TODO: Get rid of calcsurfacez b/c it doesn't work properly pos.frame.m_origin.z = CalcSurfaceZ(pos.objcell_id, pos.frame.m_origin.x, pos.frame.m_origin.y, true); pos.frame.m_origin.z += 0.5f; // add a little fudge factor to ensure mobs don't spawn in ground. } weenie->SetInitialPosition(pos); if (!g_pWorld->CreateEntity(weenie)) { SERVER_ERROR << "Failed creating generated spawn" << GetWCIDName(profile->type); return; } break; case Shop_RegenLocationType: case ShopTreasure_RegenLocationType: break; case Checkpoint_RegenLocationType: break; } if (isRegenLocationType && profile != NULL) { weenie->m_Qualities.SetInstanceID(GENERATOR_IID, parent->GetID()); if (!weenie->IsContained() && !weenie->IsStuck() && weenie->m_Position.objcell_id && !weenie->cell) { weenie->MarkForDestroy(); return; } parent->GeneratorAddToRegistry(weenie, *profile); //SERVER_INFO << "Created Object" << weenie->GetName() << "(" << weenie->m_Qualities.id << ") at " << weenie->m_Position; //GeneratorRegistryNode node; //node.m_wcidOrTtype = profile->type; //node.slot = profile->slot; //node.ts = Timer::cur_time; //node.amount = weenie->InqIntQuality(STACK_SIZE_INT, 1); //node.checkpointed = 0; //node.m_bTreasureType = profile->IsTreasureType(); //node.shop = 0; //node.m_objectId = weenie->GetID(); //if (!parent->m_Qualities._generator_registry) // parent->m_Qualities._generator_registry = new GeneratorRegistry(); //parent->m_Qualities._generator_registry->_registry.add(weenie->GetID(), &node); //parent->m_GeneratorSpawns[weenie->GetID()] = weenie->m_Qualities.id; //weenie->ChanceExecuteEmoteSet(parent->GetID(), Generation_EmoteCategory); } } int CWeenieFactory::AddFromCreateList(CWeenieObject *parent, PackableListWithJson *createList, DestinationType validDestinationTypes) { int amountCreated = 0; if (!createList) return amountCreated; bool seekingNext = true; bool forceNext = true; double diceRoll; double accumulatedChance; for (auto i = createList->begin(); i != createList->end(); i++) { float probability = 0.0; bool isTreasureDestination = false; if (i->destination & Treasure_DestinationType) { isTreasureDestination = true; probability = i->shade * g_pConfig->DropRateMultiplier(); } // By virtue of the way the loot system works increasing the drop rate may cause some items to actually drop less often. // Items are grouped in "sets" and only one item per set will actually drop, if the drop chance of the first item is increased // the chance of the subsequent items decreases. Although this isn't used very often in the data. Most of the time there's just one item // per set. For a good use case see the Drudge Robber Baron. // If we wanted we could do this properly by checking the wcid == 0 entries and reallocating the chance of nothing dropping to the items. // This would increase the drop rate as much as it is possible without lowering other item drop rates. This would be best done as a modification // to the data and not on the fly. if (seekingNext) { if (forceNext || accumulatedChance >= 1.0) { // We've reached the end of this set. seekingNext = false; forceNext = false; diceRoll = Random::RollDice(0.0, 1.0); accumulatedChance = 0.0; } else { // Just keep going. accumulatedChance += probability; continue; } } if (!isTreasureDestination || diceRoll <= accumulatedChance + probability || probability <= 0.0) { // We're the one to spawn! if (i->wcid != 0) // Unless we're a big pile of nothing. { if (validDestinationTypes == DestinationType::Undef_DestinationType || (i->destination & validDestinationTypes)) // Or we're not of the correct destinationType. { if (CWeenieObject *newItem = g_pWeenieFactory->CreateWeenieByClassID(i->wcid, NULL, false)) { if (i->palette) newItem->m_Qualities.SetInt(PALETTE_TEMPLATE_INT, i->palette); if (!isTreasureDestination && i->shade > 0.0) // treasure destinations use the shade variable as a probability to spawn. newItem->m_Qualities.SetFloat(SHADE_FLOAT, i->shade); int maxStackSize = newItem->InqIntQuality(MAX_STACK_SIZE_INT, 1); if (i->amount > 1) { if (i->amount <= maxStackSize) newItem->SetStackSize(i->amount); else newItem->SetStackSize(maxStackSize); } AddWeenieToDestination(newItem, parent, i->destination, false); amountCreated++; } } } accumulatedChance += probability; seekingNext = true; if (!isTreasureDestination) forceNext = true; } else accumulatedChance += probability; if (isTreasureDestination && i->wcid == 0) { //we've reached the end of this set even if the probabilities didn't add up to 1.0. forceNext = true; seekingNext = true; } } return amountCreated; } int CWeenieFactory::AddFromGeneratorTable(CWeenieObject *parent, bool isInit) { int numSpawned = parent->m_GeneratorSpawns.size(); if (!parent->m_Qualities._generator_table) return 0; int maxSpawns = isInit ? parent->InqIntQuality(INIT_GENERATED_OBJECTS_INT, 0) : parent->InqIntQuality(MAX_GENERATED_OBJECTS_INT, 0); //if (!isInit) //{ // if (parent->m_Qualities._generator_registry) // numSpawned += (int)parent->m_Qualities._generator_registry->_registry.size(); //} if (numSpawned >= maxSpawns) return 0; std::list genList = isInit ? parent->m_Qualities._generator_table->GetInitialGenerationList() : parent->m_Qualities._generator_table->GetGenerationList(); // always spawn everything with zero or subzero values for (GeneratorProfile &entry : genList) { bool fullLoopWithNoValidEntries = true; for (GeneratorProfile &entry : genList) { if (!parent->IsGeneratorSlotReady(entry.slot)) continue; if (entry.probability > 0 || entry.IsPlaceHolder()) continue; fullLoopWithNoValidEntries = false; int maxCreate = isInit ? entry.initCreate : entry.maxNum; if (maxCreate < 0) //this is -1 for unlimited amount; maxCreate = maxSpawns; maxCreate = min(maxCreate, maxSpawns - numSpawned); //cap maxCreate to however many we can still spawn. int amountCreated; for (amountCreated = 0; amountCreated < maxCreate; amountCreated++) { int newAmount = g_pTreasureFactory->GenerateFromTypeOrWcid(parent, entry.whereCreate, true, entry.type, entry.ptid, entry.shade, &entry); //disabled the code below to make each treasure entry count as just 1 spawned item. //if (newAmount > 1) // amountCreated += newAmount - 1; } numSpawned += amountCreated; if (numSpawned >= maxSpawns) break; } if (fullLoopWithNoValidEntries) break; } while (numSpawned < maxSpawns) { float diceRoll = Random::RollDice(0.0f, 1.0f); bool fullLoopWithNoValidEntries = true; for (GeneratorProfile &entry : genList) { if (!parent->IsGeneratorSlotReady(entry.slot)) continue; if (entry.probability <= 0 || entry.IsPlaceHolder()) continue; if (diceRoll > entry.probability) continue; fullLoopWithNoValidEntries = false; int maxCreate = isInit ? entry.initCreate : entry.maxNum; if (maxCreate < 0) //this is -1 for unlimited amount; maxCreate = maxSpawns; maxCreate = min(maxCreate, maxSpawns - numSpawned); //cap maxCreate to however many we can still spawn. int amountCreated; for (amountCreated = 0; amountCreated < maxCreate; amountCreated++) { int newAmount = g_pTreasureFactory->GenerateFromTypeOrWcid(parent, entry.whereCreate, true, entry.type, entry.ptid, entry.shade, &entry); //disabled the code below to make each treasure entry count as just 1 spawned item. //if(newAmount > 1) // amountCreated += newAmount - 1; } numSpawned += amountCreated; //Found an item. Now we need a fresh roll, so stop iterating the list. break; } if (fullLoopWithNoValidEntries) break; } //if (isInit) //{ // int numSpawned = parent->m_Qualities._generator_registry ? (int)parent->m_Qualities._generator_registry->_registry.size() : 0; // int maxSpawn = parent->InqIntQuality(MAX_GENERATED_OBJECTS_INT, 0, TRUE); // bool queueEmpty = (!parent->m_Qualities._generator_queue || parent->m_Qualities._generator_queue->_queue.empty()); // if (numSpawned >= maxSpawn && (maxSpawn > 0 || queueEmpty)) // parent->_nextRegen = -1.0; // else // parent->_nextRegen = Timer::cur_time + (parent->InqFloatQuality(REGENERATION_INTERVAL_FLOAT, -1.0, TRUE) * g_pConfig->RespawnTimeMultiplier()); //} return numSpawned; } int CWeenieFactory::GenerateFromTypeOrWcid(CWeenieObject *parent, const GeneratorProfile *profile) { return g_pTreasureFactory->GenerateFromTypeOrWcid(parent, profile->whereCreate, true, profile->type, profile->ptid, profile->shade, profile); } int CWeenieFactory::GenerateFromTypeOrWcid(CWeenieObject *parent, RegenLocationType destinationType, uint32_t treasureTypeOrWcid, unsigned int ptid, float shade) { return g_pTreasureFactory->GenerateFromTypeOrWcid(parent, destinationType, true, treasureTypeOrWcid, ptid, shade); } int CWeenieFactory::GenerateFromTypeOrWcid(CWeenieObject *parent, DestinationType destinationType, uint32_t treasureTypeOrWcid, unsigned int ptid, float shade) { return g_pTreasureFactory->GenerateFromTypeOrWcid(parent, destinationType, false, treasureTypeOrWcid, ptid, shade); } int CWeenieFactory::GenerateRareItem(CWeenieObject *parent, CWeenieObject *killer) { return g_pTreasureFactory->GenerateRareItem(parent, killer); } bool CWeenieFactory::TryToResolveAppearanceData(CWeenieObject *weenie) { // this function is temporary if (weenie->GetItemType() & (ITEM_TYPE::TYPE_ARMOR | ITEM_TYPE::TYPE_CLOTHING)) { uint32_t iconID = weenie->GetIcon(); if (iconID) { uint32_t paletteKey; ClothingTable *pCT = g_ClothingCache.GetTableByIndexOfIconID(iconID, 0, &paletteKey); if (pCT) { weenie->m_WornObjDesc.Clear(); weenie->m_WornObjDesc.paletteID = 0x0400007E; // pObject->m_miBaseModel.dwBasePalette; //double shade = 1.0; ShadePackage shade(1.0); pCT->BuildObjDesc(0x02000001, paletteKey, &shade, &weenie->m_WornObjDesc); // use the original palettes specified? weenie->m_WornObjDesc.ClearSubpalettes(); Subpalette *pSPC = weenie->m_ObjDescOverride.firstSubpal; while (pSPC) { weenie->m_WornObjDesc.AddSubpalette(new Subpalette(*pSPC)); pSPC = pSPC->next; } ClothingTable::Release(pCT); return true; } } } return false; }