#include "CharacterComponent.h" #include #include "tinyxml2.h" #include "Game.h" #include "dLogger.h" #include "GeneralUtils.h" #include "dServer.h" #include "dZoneManager.h" #include "CDClientManager.h" #include "InventoryComponent.h" #include "ControllablePhysicsComponent.h" #include "EntityManager.h" #include "PossessorComponent.h" #include "VehiclePhysicsComponent.h" CharacterComponent::CharacterComponent(Entity* parent, Character* character) : Component(parent) { m_Character = character; m_IsRacing = false; m_IsGM = false; m_IsLanding = false; m_IsLEGOClubMember = true; m_Level = 1; m_DirtyCurrentActivity = false; m_DirtyGMInfo = false; m_DirtySocialInfo = false; m_PvpEnabled = false; m_GMLevel = character->GetGMLevel(); m_EditorEnabled = false; m_EditorLevel = m_GMLevel; m_CurrentActivity = 0; m_CountryCode = 0; m_LastUpdateTimestamp = std::time(nullptr); LoadFromXML(); //Check to see if we're landing: if (character->GetZoneID() != Game::server->GetZoneID()) { m_IsLanding = true; } if (LandingAnimDisabled(character->GetZoneID()) || LandingAnimDisabled(Game::server->GetZoneID()) || m_LastRocketConfig.empty()) { m_IsLanding = false; //Don't make us land on VE/minigames lol } } bool CharacterComponent::LandingAnimDisabled(int zoneID) { switch (zoneID) { case 0: case 556: case 1001: case 1101: case 1202: case 1203: case 1204: case 1301: case 1302: case 1303: case 1401: case 1402: case 1403: case 1603: case 2001: return true; default: return false; } return false; } CharacterComponent::~CharacterComponent() { } void CharacterComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags) { outBitStream->Write(m_IsRacing); if (m_IsRacing) { outBitStream->Write1(); outBitStream->Write(m_VehicleObjectID); outBitStream->Write(0); } outBitStream->Write1(); outBitStream->Write(m_Level); outBitStream->Write0(); if (bIsInitialUpdate) { outBitStream->Write0(); outBitStream->Write0(); outBitStream->Write0(); outBitStream->Write0(); outBitStream->Write(m_Character->GetHairColor()); outBitStream->Write(m_Character->GetHairStyle()); outBitStream->Write(0); //Default "head" outBitStream->Write(m_Character->GetShirtColor()); outBitStream->Write(m_Character->GetPantsColor()); outBitStream->Write(m_Character->GetShirtStyle()); outBitStream->Write(0); //Default "head color" outBitStream->Write(m_Character->GetEyebrows()); outBitStream->Write(m_Character->GetEyes()); outBitStream->Write(m_Character->GetMouth()); outBitStream->Write(0); //AccountID, trying out if 0 works. outBitStream->Write(m_Character->GetLastLogin()); //Last login outBitStream->Write(0); //"prop mod last display time" outBitStream->Write(m_Uscore); //u-score outBitStream->Write0(); //Not free-to-play (disabled in DLU) //Stats: outBitStream->Write(m_CurrencyCollected); outBitStream->Write(m_BricksCollected); outBitStream->Write(m_SmashablesSmashed); outBitStream->Write(m_QuickBuildsCompleted); outBitStream->Write(m_EnemiesSmashed); outBitStream->Write(m_RocketsUsed); outBitStream->Write(m_MissionsCompleted); outBitStream->Write(m_PetsTamed); outBitStream->Write(m_ImaginationPowerUpsCollected); outBitStream->Write(m_LifePowerUpsCollected); outBitStream->Write(m_ArmorPowerUpsCollected); outBitStream->Write(m_MetersTraveled); outBitStream->Write(m_TimesSmashed); outBitStream->Write(m_TotalDamageTaken); outBitStream->Write(m_TotalDamageHealed); outBitStream->Write(m_TotalArmorRepaired); outBitStream->Write(m_TotalImaginationRestored); outBitStream->Write(m_TotalImaginationUsed); outBitStream->Write(m_DistanceDriven); outBitStream->Write(m_TimeAirborneInCar); outBitStream->Write(m_RacingImaginationPowerUpsCollected); outBitStream->Write(m_RacingImaginationCratesSmashed); outBitStream->Write(m_RacingCarBoostsActivated); outBitStream->Write(m_RacingTimesWrecked); outBitStream->Write(m_RacingSmashablesSmashed); outBitStream->Write(m_RacesFinished); outBitStream->Write(m_FirstPlaceRaceFinishes); outBitStream->Write0(); outBitStream->Write(m_IsLanding); if (m_IsLanding) { outBitStream->Write(uint16_t(m_LastRocketConfig.size())); for (uint16_t character : m_LastRocketConfig) { outBitStream->Write(character); } } } outBitStream->Write(m_DirtyGMInfo); if (m_DirtyGMInfo) { outBitStream->Write(m_PvpEnabled); outBitStream->Write(m_IsGM); outBitStream->Write(m_GMLevel); outBitStream->Write(m_EditorEnabled); outBitStream->Write(m_EditorLevel); } outBitStream->Write(m_DirtyCurrentActivity); if (m_DirtyCurrentActivity) outBitStream->Write(m_CurrentActivity); outBitStream->Write(m_DirtySocialInfo); if (m_DirtySocialInfo) { outBitStream->Write(m_GuildID); outBitStream->Write(static_cast(m_GuildName.size())); if (!m_GuildName.empty()) outBitStream->WriteBits(reinterpret_cast(m_GuildName.c_str()), static_cast(m_GuildName.size()) * sizeof(wchar_t) * 8); outBitStream->Write(m_IsLEGOClubMember); outBitStream->Write(m_CountryCode); } } bool CharacterComponent::GetPvpEnabled() const { return m_PvpEnabled; } void CharacterComponent::SetPvpEnabled(const bool value) { m_DirtyGMInfo = true; m_PvpEnabled = value; } void CharacterComponent::HandleLevelUp() { auto* rewardsTable = CDClientManager::Instance()->GetTable("Rewards"); const auto& rewards = rewardsTable->GetByLevelID(m_Level); auto* parent = m_Character->GetEntity(); if (parent == nullptr) { return; } auto* inventoryComponent = parent->GetComponent(); auto* controllablePhysicsComponent = parent->GetComponent(); if (inventoryComponent == nullptr || controllablePhysicsComponent == nullptr) { return; } for (auto* reward : rewards) { switch (reward->rewardType) { case 0: inventoryComponent->AddItem(reward->value, reward->count); break; case 4: { auto* items = inventoryComponent->GetInventory(ITEMS); items->SetSize(items->GetSize() + reward->value); } break; case 9: controllablePhysicsComponent->SetSpeedMultiplier(static_cast(reward->value) / 500.0f); break; case 11: break; case 12: break; default: break; } } } void CharacterComponent::SetGMLevel(int gmlevel) { m_DirtyGMInfo = true; if (gmlevel > 0) m_IsGM = true; else m_IsGM = false; m_GMLevel = gmlevel; } void CharacterComponent::LoadFromXML() { if (!m_Character) return; tinyxml2::XMLDocument* doc = m_Character->GetXMLDoc(); if (!doc) return; tinyxml2::XMLElement* character = doc->FirstChildElement("obj")->FirstChildElement("char"); if (!character) { Game::logger->Log("CharacterComponent", "Failed to find char tag while loading XML!\n"); return; } character->QueryInt64Attribute("ls", &m_Uscore); // Load the statistics const auto* statisticsAttribute = character->FindAttribute("stt"); if (statisticsAttribute) { InitializeStatisticsFromString(std::string(statisticsAttribute->Value())); } else { InitializeEmptyStatistics(); } // Load the zone statistics m_ZoneStatistics = {}; auto zoneStatistics = character->FirstChildElement("zs"); if (zoneStatistics) { auto child = zoneStatistics->FirstChildElement(); while (child) { ZoneStatistics statistics = {}; child->QueryUnsigned64Attribute("ac", &statistics.m_AchievementsCollected); child->QueryUnsigned64Attribute("bc", &statistics.m_BricksCollected); child->QueryUnsigned64Attribute("cc", &statistics.m_CoinsCollected); child->QueryUnsigned64Attribute("es", &statistics.m_EnemiesSmashed); child->QueryUnsigned64Attribute("qbc", &statistics.m_QuickBuildsCompleted); uint32_t mapID; child->QueryAttribute("map", &mapID); m_ZoneStatistics.insert({ (LWOMAPID) mapID, statistics }); child = child->NextSiblingElement(); } } const tinyxml2::XMLAttribute *rocketConfig = character->FindAttribute("lcbp"); if (rocketConfig) { m_LastRocketConfig = GeneralUtils::ASCIIToUTF16(std::string(rocketConfig->Value())); } else { m_LastRocketConfig = u""; } // // Begin custom attributes // // Load the last rocket item ID const tinyxml2::XMLAttribute *lastRocketItemID = character->FindAttribute("lrid"); if (lastRocketItemID) { m_LastRocketItemID = lastRocketItemID->Int64Value(); } // // End custom attributes // if (m_GMLevel > 0) { m_IsGM = true; m_DirtyGMInfo = true; m_EditorLevel = m_GMLevel; m_EditorEnabled = false; //We're not currently in HF if we're loading in } //Annoying guild bs: const tinyxml2::XMLAttribute *guildName = character->FindAttribute("gn"); if (guildName) { const char* gn = guildName->Value(); int64_t gid = 0; character->QueryInt64Attribute("gid", &gid); if (gid != 0) { std::string guildname(gn); m_GuildName = GeneralUtils::ASCIIToUTF16(guildname); m_GuildID = gid; m_DirtySocialInfo = true; } } tinyxml2::XMLElement* level = doc->FirstChildElement("obj")->FirstChildElement("lvl"); if (!level) { Game::logger->Log("CharacterComponent", "Failed to find lvl tag while loading XML!\n"); return; } level->QueryAttribute("l", &m_Level); if (character->FindAttribute("time")) { character->QueryUnsigned64Attribute("time", &m_TotalTimePlayed); } else { m_TotalTimePlayed = 0; } } void CharacterComponent::UpdateXml(tinyxml2::XMLDocument* doc) { tinyxml2::XMLElement* minifig = doc->FirstChildElement("obj")->FirstChildElement("mf"); if (!minifig) { Game::logger->Log("CharacterComponent", "Failed to find mf tag while updating XML!\n"); return; } // write minifig information that might have been changed by commands minifig->SetAttribute("es", m_Character->GetEyebrows()); minifig->SetAttribute("ess", m_Character->GetEyes()); minifig->SetAttribute("hc", m_Character->GetHairColor()); minifig->SetAttribute("hs", m_Character->GetHairStyle()); minifig->SetAttribute("l", m_Character->GetPantsColor()); minifig->SetAttribute("lh", m_Character->GetLeftHand()); minifig->SetAttribute("ms", m_Character->GetMouth()); minifig->SetAttribute("rh", m_Character->GetRightHand()); minifig->SetAttribute("t", m_Character->GetShirtColor()); // done with minifig tinyxml2::XMLElement* character = doc->FirstChildElement("obj")->FirstChildElement("char"); if (!character) { Game::logger->Log("CharacterComponent", "Failed to find char tag while updating XML!\n"); return; } character->SetAttribute("ls", m_Uscore); character->SetAttribute("stt", StatisticsToString().c_str()); // Set the zone statistics of the form ... auto zoneStatistics = character->FirstChildElement("zs"); if (!zoneStatistics) zoneStatistics = doc->NewElement("zs"); zoneStatistics->DeleteChildren(); for (auto pair : m_ZoneStatistics) { auto zoneStatistic = doc->NewElement("s"); zoneStatistic->SetAttribute("map", pair.first); zoneStatistic->SetAttribute("ac", pair.second.m_AchievementsCollected); zoneStatistic->SetAttribute("bc", pair.second.m_BricksCollected); zoneStatistic->SetAttribute("cc", pair.second.m_CoinsCollected); zoneStatistic->SetAttribute("es", pair.second.m_EnemiesSmashed); zoneStatistic->SetAttribute("qbc", pair.second.m_QuickBuildsCompleted); zoneStatistics->LinkEndChild(zoneStatistic); } character->LinkEndChild(zoneStatistics); if (!m_LastRocketConfig.empty()) { std::string config = GeneralUtils::UTF16ToWTF8(m_LastRocketConfig); character->SetAttribute("lcbp", config.c_str()); } else { character->DeleteAttribute("lcbp"); } // // Begin custom attributes // // Store last rocket item ID character->SetAttribute("lrid", m_LastRocketItemID); // // End custom attributes // tinyxml2::XMLElement* level = doc->FirstChildElement("obj")->FirstChildElement("lvl"); if (!level) { Game::logger->Log("CharacterComponent", "Failed to find lvl tag while updating XML!\n"); return; } level->SetAttribute("l", m_Level); auto newUpdateTimestamp = std::time(nullptr); Game::logger->Log("TotalTimePlayed", "Time since last save: %d\n", newUpdateTimestamp - m_LastUpdateTimestamp); m_TotalTimePlayed += newUpdateTimestamp - m_LastUpdateTimestamp; character->SetAttribute("time", m_TotalTimePlayed); m_LastUpdateTimestamp = newUpdateTimestamp; } void CharacterComponent::SetLastRocketConfig(std::u16string config) { m_IsLanding = !config.empty(); m_LastRocketConfig = config; } void CharacterComponent::TrackMissionCompletion(bool isAchievement) { UpdatePlayerStatistic(MissionsCompleted); // Achievements are tracked separately for the zone if (isAchievement) { const auto mapID = dZoneManager::Instance()->GetZoneID().GetMapID(); GetZoneStatisticsForMap(mapID).m_AchievementsCollected++; } } void CharacterComponent::TrackLOTCollection(LOT lot) { switch (lot) { // Handle all the imagination powerup lots case 935: // 1 point case 4035: // 2 points case 11910: // 3 points case 11911: // 5 points case 11918: // 10 points UpdatePlayerStatistic(ImaginationPowerUpsCollected); break; // Handle all the armor powerup lots case 6431: // 1 point case 11912: // 2 points case 11913: // 3 points case 11914: // 5 points case 11919: // 10 points UpdatePlayerStatistic(ArmorPowerUpsCollected); break; // Handle all the life powerup lots case 177: // 1 point case 11915: // 2 points case 11916: // 3 points case 11917: // 5 points case 11920: // 10 points UpdatePlayerStatistic(LifePowerUpsCollected); break; default: break; } } void CharacterComponent::TrackHealthDelta(int32_t health) { if (health > 0) { UpdatePlayerStatistic(TotalDamageHealed, health); } else { UpdatePlayerStatistic(TotalDamageTaken, -health); } } void CharacterComponent::TrackImaginationDelta(int32_t imagination) { if (imagination > 0) { UpdatePlayerStatistic(TotalImaginationRestored, imagination); } else { UpdatePlayerStatistic(TotalImaginationUsed, -imagination); } } void CharacterComponent::TrackArmorDelta(int32_t armor) { if (armor > 0) { UpdatePlayerStatistic(TotalArmorRepaired, armor); } } void CharacterComponent::TrackRebuildComplete() { UpdatePlayerStatistic(QuickBuildsCompleted); const auto mapID = dZoneManager::Instance()->GetZoneID().GetMapID(); GetZoneStatisticsForMap(mapID).m_QuickBuildsCompleted++; } void CharacterComponent::TrackRaceCompleted(bool won) { m_RacesFinished++; if (won) m_FirstPlaceRaceFinishes++; } void CharacterComponent::TrackPositionUpdate(const NiPoint3& newPosition) { const auto distance = NiPoint3::Distance(newPosition, m_Parent->GetPosition()); if (m_IsRacing) { UpdatePlayerStatistic(DistanceDriven, (uint64_t) distance); } else { UpdatePlayerStatistic(MetersTraveled, (uint64_t) distance); } } void CharacterComponent::HandleZoneStatisticsUpdate(LWOMAPID zoneID, const std::u16string &name, int32_t value) { auto zoneStatistics = &GetZoneStatisticsForMap(zoneID); if (name == u"BricksCollected") { m_BricksCollected += value; zoneStatistics->m_BricksCollected += value; } else if (name == u"CoinsCollected") { m_CurrencyCollected += value; zoneStatistics->m_CoinsCollected += value; } else if (name == u"EnemiesSmashed") { m_EnemiesSmashed += value; zoneStatistics->m_EnemiesSmashed += value; } } void CharacterComponent::UpdatePlayerStatistic(StatisticID updateID, uint64_t updateValue) { switch (updateID) { case CurrencyCollected: m_CurrencyCollected += updateValue; break; case BricksCollected: m_BricksCollected += updateValue; break; case SmashablesSmashed: m_SmashablesSmashed += updateValue; break; case QuickBuildsCompleted: m_QuickBuildsCompleted += updateValue; break; case EnemiesSmashed: m_EnemiesSmashed += updateValue; break; case RocketsUsed: m_RocketsUsed += updateValue; break; case MissionsCompleted: m_MissionsCompleted += updateValue; break; case PetsTamed: m_PetsTamed += updateValue; break; case ImaginationPowerUpsCollected: m_ImaginationPowerUpsCollected += updateValue; break; case LifePowerUpsCollected: m_LifePowerUpsCollected += updateValue; break; case ArmorPowerUpsCollected: m_ArmorPowerUpsCollected += updateValue; break; case MetersTraveled: m_MetersTraveled += updateValue; break; case TimesSmashed: m_TimesSmashed += updateValue; break; case TotalDamageTaken: m_TotalDamageTaken += updateValue; break; case TotalDamageHealed: m_TotalDamageHealed += updateValue; break; case TotalArmorRepaired: m_TotalArmorRepaired += updateValue; break; case TotalImaginationRestored: m_TotalImaginationRestored += updateValue; break; case TotalImaginationUsed: m_TotalImaginationUsed += updateValue; break; case DistanceDriven: m_DistanceDriven += updateValue; break; case TimeAirborneInCar: m_TimeAirborneInCar += updateValue; break; case RacingImaginationPowerUpsCollected: m_RacingImaginationPowerUpsCollected += updateValue; break; case RacingImaginationCratesSmashed: m_RacingImaginationCratesSmashed += updateValue; break; case RacingCarBoostsActivated: m_RacingCarBoostsActivated += updateValue; break; case RacingTimesWrecked: m_RacingTimesWrecked += updateValue; break; case RacingSmashablesSmashed: m_RacingSmashablesSmashed += updateValue; break; case RacesFinished: m_RacesFinished += updateValue; break; case FirstPlaceRaceFinishes: m_FirstPlaceRaceFinishes += updateValue; break; default: break; } } void CharacterComponent::InitializeStatisticsFromString(const std::string &statisticsString) { auto split = GeneralUtils::SplitString(statisticsString, ';'); m_CurrencyCollected = GetStatisticFromSplit(split, 0); m_BricksCollected = GetStatisticFromSplit(split, 1); m_SmashablesSmashed = GetStatisticFromSplit(split, 2); m_QuickBuildsCompleted = GetStatisticFromSplit(split, 3); m_EnemiesSmashed = GetStatisticFromSplit(split, 4); m_RocketsUsed = GetStatisticFromSplit(split, 5); m_MissionsCompleted = GetStatisticFromSplit(split, 6); m_PetsTamed = GetStatisticFromSplit(split, 7); m_ImaginationPowerUpsCollected = GetStatisticFromSplit(split, 8); m_LifePowerUpsCollected = GetStatisticFromSplit(split, 9); m_ArmorPowerUpsCollected = GetStatisticFromSplit(split, 10); m_MetersTraveled = GetStatisticFromSplit(split, 11); m_TimesSmashed = GetStatisticFromSplit(split, 12); m_TotalDamageTaken = GetStatisticFromSplit(split, 13); m_TotalDamageHealed = GetStatisticFromSplit(split, 14); m_TotalArmorRepaired = GetStatisticFromSplit(split, 15); m_TotalImaginationRestored = GetStatisticFromSplit(split, 16); m_TotalImaginationUsed = GetStatisticFromSplit(split, 17); m_DistanceDriven = GetStatisticFromSplit(split, 18); m_TimeAirborneInCar = GetStatisticFromSplit(split, 19); // WONTFIX m_RacingImaginationPowerUpsCollected = GetStatisticFromSplit(split, 20); m_RacingImaginationCratesSmashed = GetStatisticFromSplit(split, 21); m_RacingCarBoostsActivated = GetStatisticFromSplit(split, 22); m_RacingTimesWrecked = GetStatisticFromSplit(split, 23); m_RacingSmashablesSmashed = GetStatisticFromSplit(split, 24); m_RacesFinished = GetStatisticFromSplit(split, 25); m_FirstPlaceRaceFinishes = GetStatisticFromSplit(split, 26); } void CharacterComponent::InitializeEmptyStatistics() { m_CurrencyCollected = 0; m_BricksCollected = 0; m_SmashablesSmashed = 0; m_QuickBuildsCompleted = 0; m_EnemiesSmashed = 0; m_RocketsUsed = 0; m_MissionsCompleted = 0; m_PetsTamed = 0; m_ImaginationPowerUpsCollected = 0; m_LifePowerUpsCollected = 0; m_ArmorPowerUpsCollected = 0; m_MetersTraveled = 0; m_TimesSmashed = 0; m_TotalDamageTaken = 0; m_TotalDamageHealed = 0; m_TotalArmorRepaired = 0; m_TotalImaginationRestored = 0; m_TotalImaginationUsed = 0; m_DistanceDriven = 0; m_TimeAirborneInCar = 0; m_RacingImaginationPowerUpsCollected = 0; m_RacingImaginationCratesSmashed = 0; m_RacingCarBoostsActivated = 0; m_RacingTimesWrecked = 0; m_RacingSmashablesSmashed = 0; m_RacesFinished = 0; m_FirstPlaceRaceFinishes = 0; } std::string CharacterComponent::StatisticsToString() const { std::stringstream result; result << std::to_string(m_CurrencyCollected) << ';' << std::to_string(m_BricksCollected) << ';' << std::to_string(m_SmashablesSmashed) << ';' << std::to_string(m_QuickBuildsCompleted) << ';' << std::to_string(m_EnemiesSmashed) << ';' << std::to_string(m_RocketsUsed) << ';' << std::to_string(m_MissionsCompleted) << ';' << std::to_string(m_PetsTamed) << ';' << std::to_string(m_ImaginationPowerUpsCollected) << ';' << std::to_string(m_LifePowerUpsCollected) << ';' << std::to_string(m_ArmorPowerUpsCollected) << ';' << std::to_string(m_MetersTraveled) << ';' << std::to_string(m_TimesSmashed) << ';' << std::to_string(m_TotalDamageTaken) << ';' << std::to_string(m_TotalDamageHealed) << ';' << std::to_string(m_TotalArmorRepaired) << ';' << std::to_string(m_TotalImaginationRestored) << ';' << std::to_string(m_TotalImaginationUsed) << ';' << std::to_string(m_DistanceDriven) << ';' << std::to_string(m_TimeAirborneInCar) << ';' << std::to_string(m_RacingImaginationPowerUpsCollected) << ';' << std::to_string(m_RacingImaginationCratesSmashed) << ';' << std::to_string(m_RacingCarBoostsActivated) << ';' << std::to_string(m_RacingTimesWrecked) << ';' << std::to_string(m_RacingSmashablesSmashed) << ';' << std::to_string(m_RacesFinished) << ';' << std::to_string(m_FirstPlaceRaceFinishes) << ';'; return result.str(); } uint64_t CharacterComponent::GetStatisticFromSplit(std::vector split, uint32_t index) { return split.size() > index ? std::stoul(split.at(index)) : 0; } ZoneStatistics& CharacterComponent::GetZoneStatisticsForMap(LWOMAPID mapID) { auto stats = m_ZoneStatistics.find(mapID); if (stats == m_ZoneStatistics.end()) m_ZoneStatistics.insert({ mapID, {0, 0, 0, 0, 0 } }); return m_ZoneStatistics.at(mapID); }