mirror of
				https://github.com/DarkflameUniverse/DarkflameServer.git
				synced 2025-10-31 12:41:55 +00:00 
			
		
		
		
	Refactor and combat changes
This commit is contained in:
		
							
								
								
									
										296
									
								
								dGame/AdditionalEntityData.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										296
									
								
								dGame/AdditionalEntityData.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,296 @@ | ||||
| #include "AdditionalEntityData.h" | ||||
|  | ||||
| #include "NejlikaData.h" | ||||
|  | ||||
| #include <DestroyableComponent.h> | ||||
| #include <LevelProgressionComponent.h> | ||||
| #include <InventoryComponent.h> | ||||
| #include <BaseCombatAIComponent.h> | ||||
| #include <TeamManager.h> | ||||
|  | ||||
| float nejlika::AdditionalEntityData::CalculateModifier(ModifierType type, ModifierOperator op, bool resistance) const | ||||
| { | ||||
| 	float total = 0; | ||||
|  | ||||
| 	for (const auto& modifier : activeModifiers) { | ||||
| 		if (modifier.GetType() != type || modifier.GetOperator() != op || modifier.IsResistance() != resistance) { | ||||
| 			continue; | ||||
| 		} | ||||
|  | ||||
| 		total += modifier.GetValue(); | ||||
| 	} | ||||
|  | ||||
| 	return total; | ||||
| } | ||||
|  | ||||
| float nejlika::AdditionalEntityData::CalculateModifier(ModifierType type, std::vector<ModifierInstance>& additionalModifiers, ModifierOperator op, bool resistance) const { | ||||
| 	float total = 0; | ||||
|  | ||||
| 	for (const auto& modifier : additionalModifiers) { | ||||
| 		if (modifier.GetType() != type || modifier.GetOperator() != op || modifier.IsResistance() != resistance) { | ||||
| 			continue; | ||||
| 		} | ||||
|  | ||||
| 		total += modifier.GetValue(); | ||||
| 	} | ||||
|  | ||||
| 	return total + CalculateModifier(type, op, resistance); | ||||
| } | ||||
|  | ||||
| float nejlika::AdditionalEntityData::CalculateModifier(ModifierType type, int32_t level) const | ||||
| { | ||||
| 	const auto templateDataOpt = NejlikaData::GetEntityTemplate(lot); | ||||
|  | ||||
| 	if (!templateDataOpt.has_value()) { | ||||
| 		return 0; | ||||
| 	} | ||||
|  | ||||
| 	const auto& templateData = *templateDataOpt.value(); | ||||
|  | ||||
| 	const auto scaler = templateData.GetScaler(type, false, level); | ||||
|  | ||||
| 	float additive = CalculateModifier(type, ModifierOperator::Additive, false); | ||||
|  | ||||
| 	if (scaler != 0 && additive >= scaler) { | ||||
| 		additive -= scaler; | ||||
| 	} | ||||
|  | ||||
| 	float multiplicative = CalculateModifier(type, ModifierOperator::Multiplicative, false); | ||||
|  | ||||
| 	std::cout << "Scaler: " << scaler << " Additive: " << additive << " Multiplicative: " << multiplicative << std::endl; | ||||
|  | ||||
| 	return (scaler + additive) * (1 + multiplicative / 100); | ||||
| } | ||||
|  | ||||
| float nejlika::AdditionalEntityData::CalculateModifier(ModifierType type) const { | ||||
| 	return CalculateModifier(type, level); | ||||
| } | ||||
|  | ||||
| float nejlika::AdditionalEntityData::CalculateModifier(ModifierType type, std::vector<ModifierInstance>& additionalModifiers, int32_t level) const | ||||
| { | ||||
| 	const auto templateDataOpt = NejlikaData::GetEntityTemplate(lot); | ||||
|  | ||||
| 	if (!templateDataOpt.has_value()) { | ||||
| 		return 0; | ||||
| 	} | ||||
|  | ||||
| 	const auto& templateData = *templateDataOpt.value(); | ||||
|  | ||||
| 	const auto scaler = templateData.GetScaler(type, false, level); | ||||
|  | ||||
| 	float additive = CalculateModifier(type, additionalModifiers, ModifierOperator::Additive, false); | ||||
| 	 | ||||
| 	if (scaler != 0 && additive >= scaler) { | ||||
| 		additive -= scaler; | ||||
| 	} | ||||
| 	 | ||||
| 	float multiplicative = CalculateModifier(type, additionalModifiers, ModifierOperator::Multiplicative, false); | ||||
|  | ||||
| 	std::cout << "Scaler: " << scaler << " Additive: " << additive << " Multiplicative: " << multiplicative << std::endl; | ||||
|  | ||||
| 	return (scaler + additive) * (1 + multiplicative / 100); | ||||
| } | ||||
|  | ||||
| float nejlika::AdditionalEntityData::CalculateResistance(ModifierType type) const | ||||
| { | ||||
| 	return CalculateModifier(type, ModifierOperator::Multiplicative, true); | ||||
| } | ||||
|  | ||||
| void nejlika::AdditionalEntityData::RollStandardModifiers(int32_t level) { | ||||
| 	standardModifiers.clear(); | ||||
|  | ||||
| 	const auto templateDataOpt = NejlikaData::GetEntityTemplate(lot); | ||||
|  | ||||
| 	if (templateDataOpt.has_value()) { | ||||
| 		const auto& templateData = *templateDataOpt.value(); | ||||
|  | ||||
| 		const auto modifiers = templateData.GenerateModifiers(level); | ||||
|  | ||||
| 		standardModifiers.insert(standardModifiers.end(), modifiers.begin(), modifiers.end()); | ||||
| 	} | ||||
|  | ||||
| 	const auto objectDataVec = NejlikaData::GetModifierNameTemplates(ModifierNameType::Object); | ||||
|  | ||||
| 	for (const auto& objectData : objectDataVec) { | ||||
| 		if (objectData.GetLOT() != lot) { | ||||
| 			continue; | ||||
| 		} | ||||
|  | ||||
| 		const auto modifiers = objectData.GenerateModifiers(level); | ||||
|  | ||||
| 		standardModifiers.insert(standardModifiers.end(), modifiers.begin(), modifiers.end()); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void nejlika::AdditionalEntityData::ApplyToEntity() { | ||||
| 	const auto templateDataOpt = NejlikaData::GetEntityTemplate(lot); | ||||
|  | ||||
| 	if (!templateDataOpt.has_value()) { | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	const auto& templateData = *templateDataOpt.value(); | ||||
|  | ||||
| 	auto* entity = Game::entityManager->GetEntity(id); | ||||
|  | ||||
| 	if (entity == nullptr) { | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	auto* destroyable = entity->GetComponent<DestroyableComponent>(); | ||||
|  | ||||
| 	if (destroyable == nullptr) { | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	auto* levelProgression = entity->GetComponent<LevelProgressionComponent>(); | ||||
|  | ||||
| 	if (levelProgression != nullptr) { | ||||
| 		this->level = levelProgression->GetLevel(); | ||||
| 	} | ||||
| 	else { | ||||
| 		this->level = templateData.GetMinLevel(); | ||||
| 	} | ||||
| 	 | ||||
| 	if (!initialized) { | ||||
| 		RollStandardModifiers(level); | ||||
| 	} | ||||
|  | ||||
| 	activeModifiers = standardModifiers; | ||||
|  | ||||
| 	auto* inventoryComponent = entity->GetComponent<InventoryComponent>(); | ||||
|  | ||||
| 	if (inventoryComponent) { | ||||
| 		for (const auto& [location, item] : inventoryComponent->GetEquippedItems()) { | ||||
| 			const auto itemDataOpt = NejlikaData::GetAdditionalItemData(item.id); | ||||
|  | ||||
| 			if (!itemDataOpt.has_value()) { | ||||
| 				continue; | ||||
| 			} | ||||
|  | ||||
| 			const auto& itemData = *itemDataOpt.value(); | ||||
|  | ||||
| 			const auto& itemModifiers = itemData.GetModifierInstances(); | ||||
|  | ||||
| 			activeModifiers.insert(activeModifiers.end(), itemModifiers.begin(), itemModifiers.end()); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	destroyable->SetMaxHealth(static_cast<int32_t>(CalculateModifier(ModifierType::Health, level))); | ||||
| 	destroyable->SetMaxArmor(static_cast<int32_t>(CalculateModifier(ModifierType::Armor, level))); | ||||
| 	if (!entity->IsPlayer()) { | ||||
| 		destroyable->SetMaxImagination(static_cast<int32_t>(CalculateModifier(ModifierType::Imagination, level))); | ||||
| 	} | ||||
|  | ||||
| 	if (initialized) { | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	destroyable->SetHealth(destroyable->GetMaxHealth()); | ||||
| 	destroyable->SetArmor(destroyable->GetMaxArmor()); | ||||
|  | ||||
| 	if (!entity->IsPlayer()) { | ||||
| 		destroyable->SetImagination(destroyable->GetMaxImagination()); | ||||
| 	} | ||||
|  | ||||
| 	initialized = true; | ||||
| } | ||||
|  | ||||
| void nejlika::AdditionalEntityData::CheckForRescale(AdditionalEntityData* other) { | ||||
| 	auto* entity = Game::entityManager->GetEntity(id); | ||||
|  | ||||
| 	if (entity == nullptr) { | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	if (entity->IsPlayer()) { | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	auto* baseCombat = entity->GetComponent<BaseCombatAIComponent>(); | ||||
|  | ||||
| 	if (baseCombat == nullptr) { | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	const auto& threats = baseCombat->GetThreats(); | ||||
|  | ||||
| 	int32_t totalThreats = 0; | ||||
| 	int32_t totalLevel = 0; | ||||
|  | ||||
| 	for (const auto& [threat, _] : threats) { | ||||
| 		const auto threatEntityOpt = NejlikaData::GetAdditionalEntityData(threat); | ||||
|  | ||||
| 		if (!threatEntityOpt.has_value()) { | ||||
| 			continue; | ||||
| 		} | ||||
|  | ||||
| 		const auto& threatEntity = *threatEntityOpt.value(); | ||||
|  | ||||
| 		if (other->id == threatEntity.id) { | ||||
| 			continue; | ||||
| 		} | ||||
|  | ||||
| 		totalLevel += threatEntity.level; | ||||
| 		totalThreats++; | ||||
| 	} | ||||
|  | ||||
| 	if (other != nullptr) { | ||||
| 		totalLevel += other->level; | ||||
| 		totalThreats++; | ||||
|  | ||||
| 		auto* team = TeamManager::Instance()->GetTeam(other->id); | ||||
|  | ||||
| 		if (team != nullptr) { | ||||
| 			for (const auto& member : team->members) { | ||||
| 				const auto memberEntityOpt = NejlikaData::GetAdditionalEntityData(member); | ||||
|  | ||||
| 				if (!memberEntityOpt.has_value()) { | ||||
| 					continue; | ||||
| 				} | ||||
|  | ||||
| 				const auto& memberEntity = *memberEntityOpt.value(); | ||||
|  | ||||
| 				if (other->id == memberEntity.id) { | ||||
| 					continue; | ||||
| 				} | ||||
|  | ||||
| 				totalLevel += memberEntity.level; | ||||
| 				totalThreats++; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if (totalThreats == 0) { | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	const auto averageLevel = totalLevel / totalThreats; | ||||
|  | ||||
| 	// Can't rescale to a lower level | ||||
| 	if (averageLevel <= level) { | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	level = averageLevel; | ||||
|  | ||||
| 	auto* destroyable = entity->GetComponent<DestroyableComponent>(); | ||||
|  | ||||
| 	if (destroyable == nullptr) { | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	float healthPercentage = destroyable->GetMaxHealth() == 0 ? 1 : static_cast<float>(destroyable->GetHealth()) / destroyable->GetMaxHealth(); | ||||
| 	float armorPercentage = destroyable->GetMaxArmor() == 0 ? 1 : static_cast<float>(destroyable->GetArmor()) / destroyable->GetMaxArmor(); | ||||
| 	float imaginationPercentage = destroyable->GetMaxImagination() == 0 ? 1 : static_cast<float>(destroyable->GetImagination()) / destroyable->GetMaxImagination(); | ||||
|  | ||||
| 	RollStandardModifiers(level); | ||||
|  | ||||
| 	ApplyToEntity(); | ||||
|  | ||||
| 	destroyable->SetHealth(static_cast<int32_t>(destroyable->GetMaxHealth() * healthPercentage)); | ||||
| 	destroyable->SetArmor(static_cast<int32_t>(destroyable->GetMaxArmor() * armorPercentage)); | ||||
| 	destroyable->SetImagination(static_cast<int32_t>(destroyable->GetMaxImagination() * imaginationPercentage)); | ||||
|  | ||||
| 	LOG("Rescaled entity %i to level %d", entity->GetLOT(), level); | ||||
| } | ||||
							
								
								
									
										57
									
								
								dGame/AdditionalEntityData.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								dGame/AdditionalEntityData.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,57 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include <cstdint> | ||||
| #include <vector> | ||||
|  | ||||
| #include "Entity.h" | ||||
|  | ||||
| #include "ModifierInstance.h" | ||||
| #include "EntityTemplate.h" | ||||
|  | ||||
| namespace nejlika | ||||
| { | ||||
|  | ||||
| class AdditionalEntityData | ||||
| { | ||||
| public: | ||||
| 	AdditionalEntityData() = default; | ||||
|  | ||||
| 	AdditionalEntityData(LWOOBJID id, LOT lot) : id(id), lot(lot) {} | ||||
|  | ||||
| 	float CalculateModifier(ModifierType type, ModifierOperator op, bool resistance) const; | ||||
|  | ||||
| 	float CalculateModifier(ModifierType type, std::vector<ModifierInstance>& additionalModifiers, ModifierOperator op, bool resistance) const; | ||||
|  | ||||
| 	float CalculateModifier(ModifierType type, int32_t level) const; | ||||
|  | ||||
| 	float CalculateModifier(ModifierType type) const; | ||||
|  | ||||
| 	float CalculateModifier(ModifierType type, std::vector<ModifierInstance>& additionalModifiers, int32_t level) const; | ||||
|  | ||||
| 	float CalculateResistance(ModifierType type) const; | ||||
|  | ||||
| 	void ApplyToEntity(); | ||||
|  | ||||
| 	void CheckForRescale(AdditionalEntityData* other); | ||||
|  | ||||
| 	int32_t GetLevel() const { return level; } | ||||
|  | ||||
| 	LWOOBJID GetID() const { return id; } | ||||
|  | ||||
| 	LOT GetLOT() const { return lot; } | ||||
|  | ||||
| private: | ||||
| 	void RollStandardModifiers(int32_t level); | ||||
|  | ||||
| 	bool initialized = false; | ||||
|  | ||||
| 	std::vector<ModifierInstance> standardModifiers; | ||||
| 	std::vector<ModifierInstance> activeModifiers; | ||||
|  | ||||
| 	LWOOBJID id; | ||||
| 	LOT lot; | ||||
|  | ||||
| 	int32_t level = 1; | ||||
| }; | ||||
|  | ||||
| } | ||||
							
								
								
									
										227
									
								
								dGame/AdditionalItemData.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										227
									
								
								dGame/AdditionalItemData.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,227 @@ | ||||
| #include "AdditionalItemData.h" | ||||
|  | ||||
| #include "Item.h" | ||||
| #include "eItemType.h" | ||||
| #include "NejlikaData.h" | ||||
|  | ||||
| using namespace nejlika; | ||||
|  | ||||
| nejlika::AdditionalItemData::AdditionalItemData(Item* item) | ||||
| { | ||||
| 	const auto& config = item->GetConfig(); | ||||
|  | ||||
| 	for (const auto& entry : config) | ||||
| 	{ | ||||
| 		if (entry->GetKey() != u"modifiers") | ||||
| 		{ | ||||
| 			continue; | ||||
| 		} | ||||
|  | ||||
| 		const auto str = entry->GetValueAsString(); | ||||
|  | ||||
| 		if (str.empty()) | ||||
| 		{ | ||||
| 			continue; | ||||
| 		} | ||||
|  | ||||
| 		try | ||||
| 		{ | ||||
| 			const auto json = nlohmann::json::parse(str); | ||||
|  | ||||
| 			Load(json); | ||||
| 		} | ||||
| 		catch (const nlohmann::json::exception& e) | ||||
| 		{ | ||||
| 			std::cout << "Failed to parse additional item data: " << e.what() << std::endl; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| nejlika::AdditionalItemData::AdditionalItemData(const nlohmann::json& json) { | ||||
| 	Load(json); | ||||
| } | ||||
|  | ||||
| void nejlika::AdditionalItemData::Load(const nlohmann::json& json) { | ||||
| 	if (json.contains("names")) | ||||
| 	{ | ||||
| 		for (const auto& name : json["names"]) | ||||
| 		{ | ||||
| 			modifierNames.emplace_back(name); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if (json.contains("instances")) | ||||
| 	{ | ||||
| 		for (const auto& instance : json["instances"]) | ||||
| 		{ | ||||
| 			modifierInstances.emplace_back(instance); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| nlohmann::json nejlika::AdditionalItemData::ToJson() const { | ||||
| 	nlohmann::json json; | ||||
|  | ||||
| 	json["names"] = nlohmann::json::array(); | ||||
|  | ||||
| 	for (const auto& name : modifierNames) | ||||
| 	{ | ||||
| 		json["names"].push_back(name.ToJson()); | ||||
| 	} | ||||
|  | ||||
| 	json["instances"] = nlohmann::json::array(); | ||||
|  | ||||
| 	for (const auto& instance : modifierInstances) | ||||
| 	{ | ||||
| 		json["instances"].push_back(instance.ToJson()); | ||||
| 	} | ||||
|  | ||||
| 	return json; | ||||
| } | ||||
|  | ||||
| void nejlika::AdditionalItemData::Save(Item* item) { | ||||
| 	auto& config = item->GetConfig(); | ||||
| 	 | ||||
| 	// Remove the old data | ||||
| 	for (size_t i = 0; i < config.size(); i++) | ||||
| 	{ | ||||
| 		if (config[i]->GetKey() == u"modifiers") | ||||
| 		{ | ||||
| 			config.erase(config.begin() + i); | ||||
| 			break; | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 	std::stringstream ss; | ||||
|  | ||||
| 	ss << ToJson().dump(); | ||||
|  | ||||
| 	std::cout << ss.str() << std::endl; | ||||
|  | ||||
| 	config.push_back(new LDFData<std::string>(u"modifiers", ToJson().dump())); | ||||
| } | ||||
|  | ||||
| void nejlika::AdditionalItemData::RollModifiers(Item* item, int32_t level) { | ||||
| 	modifierNames.clear(); | ||||
| 	modifierInstances.clear(); | ||||
|  | ||||
| 	const auto& info = item->GetInfo(); | ||||
|  | ||||
| 	const auto itemType = static_cast<eItemType>(info.itemType); | ||||
| 	const auto itemRarity = info.rarity == 0 ? 1 : info.rarity; | ||||
|  | ||||
| 	uint32_t rarityRollPrefix = 0; | ||||
| 	uint32_t rarityRollSuffix = 0; | ||||
|  | ||||
| 	// Generate (itemRarity) amout of names and modifiers rolls, take the highest rarity. 0-1000 | ||||
| 	for (int i = 0; i < itemRarity; i++) { | ||||
| 		auto roll = GeneralUtils::GenerateRandomNumber<uint32_t>() % 1000; | ||||
|  | ||||
| 		if (roll > rarityRollPrefix) { | ||||
| 			rarityRollPrefix = roll; | ||||
| 		} | ||||
| 		 | ||||
| 		roll = GeneralUtils::GenerateRandomNumber<uint32_t>() % 1000; | ||||
|  | ||||
| 		if (roll > rarityRollSuffix) { | ||||
| 			rarityRollSuffix = roll; | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 	const auto& templates = NejlikaData::GetModifierNameTemplates(); | ||||
|  | ||||
| 	std::vector<ModifierNameTemplate> availablePrefixes; | ||||
| 	std::vector<ModifierNameTemplate> availableSuffixes; | ||||
|  | ||||
| 	for (const auto& [type, nameTemplates] : templates) { | ||||
| 		for (const auto& nameTemplate : nameTemplates) { | ||||
| 			if (type != ModifierNameType::Prefix && type != ModifierNameType::Suffix) { | ||||
| 				continue; | ||||
| 			} | ||||
|  | ||||
| 			if (nameTemplate.GetMinLevel() > level || nameTemplate.GetMaxLevel() < level) { | ||||
| 				continue; | ||||
| 			} | ||||
|  | ||||
| 			const auto rarity = nameTemplate.GetRarity(); | ||||
|  | ||||
| 			if (rarity == ModifierRarity::Common) { | ||||
| 				continue; | ||||
| 			} | ||||
|  | ||||
| 			const auto& itemTypes = nameTemplate.GetItemTypes(); | ||||
|  | ||||
| 			if (std::find(itemTypes.begin(), itemTypes.end(), itemType) == itemTypes.end()) { | ||||
| 				continue; | ||||
| 			} | ||||
| 			 | ||||
| 			/* | ||||
| 				Uncommon: rarityRoll > 500, | ||||
| 				Rare: rarityRoll > 900, | ||||
| 				Epic: rarityRoll > 990, | ||||
| 				Legendary: rarityRoll = 999 | ||||
| 			*/ | ||||
| 			const auto roll = type == ModifierNameType::Prefix ? rarityRollPrefix : rarityRollSuffix; | ||||
|  | ||||
| 			if (rarity == ModifierRarity::Uncommon && roll > 900) { | ||||
| 				continue; | ||||
| 			} | ||||
| 			 | ||||
| 			if (rarity == ModifierRarity::Rare && (roll <= 900 || roll > 990)) { | ||||
| 				continue; | ||||
| 			} | ||||
|  | ||||
| 			if (rarity == ModifierRarity::Epic && (roll <= 990 || roll > 998)) { | ||||
| 				continue; | ||||
| 			} | ||||
|  | ||||
| 			if (rarity == ModifierRarity::Legendary && roll != 999) { | ||||
| 				continue; | ||||
| 			} | ||||
|  | ||||
| 			if (type == ModifierNameType::Prefix) { | ||||
| 				availablePrefixes.push_back(nameTemplate); | ||||
| 			} | ||||
| 			else { | ||||
| 				availableSuffixes.push_back(nameTemplate); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if (!availablePrefixes.empty()) { | ||||
| 		const auto& prefix = availablePrefixes[GeneralUtils::GenerateRandomNumber<uint32_t>() % availablePrefixes.size()]; | ||||
|  | ||||
| 		modifierNames.push_back(ModifierName(prefix)); | ||||
| 		 | ||||
| 		const auto modifiers = prefix.GenerateModifiers(level); | ||||
|  | ||||
| 		modifierInstances.insert(modifierInstances.end(), modifiers.begin(), modifiers.end()); | ||||
| 	} | ||||
|  | ||||
| 	if (!availableSuffixes.empty()) { | ||||
| 		const auto& suffix = availableSuffixes[GeneralUtils::GenerateRandomNumber<uint32_t>() % availableSuffixes.size()]; | ||||
|  | ||||
| 		modifierNames.push_back(ModifierName(suffix)); | ||||
|  | ||||
| 		const auto modifiers = suffix.GenerateModifiers(level); | ||||
|  | ||||
| 		modifierInstances.insert(modifierInstances.end(), modifiers.begin(), modifiers.end()); | ||||
| 	} | ||||
|  | ||||
| 	const auto& itemTemplateVec = NejlikaData::GetModifierNameTemplates(ModifierNameType::Object); | ||||
|  | ||||
| 	const auto itemTemplateIt = std::find_if(itemTemplateVec.begin(), itemTemplateVec.end(), [item](const auto& it) { | ||||
| 		return it.GetLOT() == static_cast<int32_t>(item->GetLot()); | ||||
| 	}); | ||||
|  | ||||
| 	if (itemTemplateIt != itemTemplateVec.end()) { | ||||
| 		const auto& itemTemplate = *itemTemplateIt; | ||||
|  | ||||
| 		const auto itemModifiers = itemTemplate.GenerateModifiers(level); | ||||
|  | ||||
| 		modifierInstances.insert(modifierInstances.end(), itemModifiers.begin(), itemModifiers.end()); | ||||
| 	} | ||||
|  | ||||
| 	Save(item); | ||||
| } | ||||
|  | ||||
							
								
								
									
										41
									
								
								dGame/AdditionalItemData.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								dGame/AdditionalItemData.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include <cstdint> | ||||
|  | ||||
| #include "ModifierName.h" | ||||
| #include "ModifierInstance.h" | ||||
|  | ||||
| #include "json.hpp" | ||||
|  | ||||
| class Item; | ||||
|  | ||||
| namespace nejlika | ||||
| { | ||||
|  | ||||
| class AdditionalItemData | ||||
| { | ||||
| public: | ||||
| 	AdditionalItemData() = default; | ||||
|  | ||||
| 	AdditionalItemData(Item* item); | ||||
| 	 | ||||
| 	AdditionalItemData(const nlohmann::json& json); | ||||
|  | ||||
| 	nlohmann::json ToJson() const; | ||||
|  | ||||
| 	void Load(const nlohmann::json& json); | ||||
|  | ||||
| 	void Save(Item* item); | ||||
|  | ||||
| 	void RollModifiers(Item* item, int32_t level); | ||||
|  | ||||
| 	const std::vector<ModifierName>& GetModifierNames() const { return modifierNames; } | ||||
| 	const std::vector<ModifierInstance>& GetModifierInstances() const { return modifierInstances; } | ||||
|  | ||||
| private: | ||||
| 	std::vector<ModifierName> modifierNames; | ||||
| 	std::vector<ModifierInstance> modifierInstances; | ||||
|  | ||||
| }; | ||||
|  | ||||
| } | ||||
| @@ -7,7 +7,19 @@ set(DGAME_SOURCES "Character.cpp" | ||||
| 		"TradingManager.cpp" | ||||
| 		"User.cpp" | ||||
| 		"UserManager.cpp" | ||||
| 		"Nejlika.cpp") | ||||
| 		"ModifierTemplate.cpp" | ||||
| 		"ModifierInstance.cpp" | ||||
| 		"ModifierRarity.cpp" | ||||
| 		"ModifierType.cpp" | ||||
| 		"ModifierScale.cpp" | ||||
| 		"ModifierName.cpp" | ||||
| 		"ModifierNameTemplate.cpp" | ||||
| 		"NejlikaData.cpp" | ||||
| 		"AdditionalItemData.cpp" | ||||
| 		"EntityTemplate.cpp" | ||||
| 		"AdditionalEntityData.cpp" | ||||
| 		"NejlikaHooks.cpp" | ||||
| 		) | ||||
|  | ||||
| include_directories( | ||||
| 	${PROJECT_SOURCE_DIR}/dScripts | ||||
|   | ||||
| @@ -26,6 +26,9 @@ | ||||
| #include "GhostComponent.h" | ||||
| #include <ranges> | ||||
|  | ||||
| Observable<Entity*> EntityManager::OnEntityCreated; | ||||
| Observable<Entity*> EntityManager::OnEntityDestroyed; | ||||
|  | ||||
| // Configure which zones have ghosting disabled, mostly small worlds. | ||||
| std::vector<LWOMAPID> EntityManager::m_GhostingExcludedZones = { | ||||
| 	// Small zones | ||||
| @@ -138,6 +141,9 @@ Entity* EntityManager::CreateEntity(EntityInfo info, User* user, Entity* parentE | ||||
| 		m_SpawnPoints.insert_or_assign(GeneralUtils::UTF16ToWTF8(spawnName), entity->GetObjectID()); | ||||
| 	} | ||||
|  | ||||
| 	// Notify observers that a new entity has been created | ||||
| 	OnEntityCreated(entity); | ||||
|  | ||||
| 	return entity; | ||||
| } | ||||
|  | ||||
| @@ -161,6 +167,9 @@ void EntityManager::DestroyEntity(Entity* entity) { | ||||
| 		DestructEntity(entity); | ||||
| 	} | ||||
|  | ||||
| 	// Notify observers that an entity is about to be destroyed | ||||
| 	OnEntityDestroyed(entity); | ||||
|  | ||||
| 	// Delete this entity at the end of the frame | ||||
| 	ScheduleForDeletion(id); | ||||
| } | ||||
|   | ||||
| @@ -7,6 +7,7 @@ | ||||
| #include <unordered_map> | ||||
|  | ||||
| #include "dCommonVars.h" | ||||
| #include "Observable.h" | ||||
|  | ||||
| class Entity; | ||||
| class EntityInfo; | ||||
| @@ -72,6 +73,9 @@ public: | ||||
| 	const bool GetHardcoreDropinventoryOnDeath() { return m_HardcoreDropinventoryOnDeath; }; | ||||
| 	const uint32_t GetHardcoreUscoreEnemiesMultiplier() { return m_HardcoreUscoreEnemiesMultiplier; }; | ||||
|  | ||||
| 	static Observable<Entity*> OnEntityCreated; | ||||
| 	static Observable<Entity*> OnEntityDestroyed; | ||||
|  | ||||
| private: | ||||
| 	void SerializeEntities(); | ||||
| 	void KillEntities(); | ||||
|   | ||||
							
								
								
									
										88
									
								
								dGame/EntityTemplate.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								dGame/EntityTemplate.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,88 @@ | ||||
| #include "EntityTemplate.h" | ||||
|  | ||||
| #include <magic_enum.hpp> | ||||
|  | ||||
| nejlika::EntityTemplate::EntityTemplate(const nlohmann::json& json) { | ||||
| 	lot = json["lot"].get<LOT>(); | ||||
| 	minLevel = json.contains("min-level") ? json["min-level"].get<int32_t>() : 1; | ||||
|  | ||||
| 	for (const auto& scaler : json["scaling"]) | ||||
| 	{ | ||||
| 		EntityTemplateScaler s; | ||||
|  | ||||
| 		s.type = magic_enum::enum_cast<ModifierType>(scaler["type"].get<std::string>()).value(); | ||||
| 		s.isResistance = scaler.contains("resistance") && scaler["resistance"].get<bool>(); | ||||
| 		s.polynomial = scaler["polynomial"].get<std::vector<float>>(); | ||||
|  | ||||
| 		scalers.push_back(s); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| nlohmann::json nejlika::EntityTemplate::ToJson() const { | ||||
| 	nlohmann::json json; | ||||
|  | ||||
| 	json["lot"] = lot; | ||||
| 	json["min-level"] = minLevel; | ||||
|  | ||||
| 	nlohmann::json scalersJson; | ||||
|  | ||||
| 	for (const auto& scaler : scalers) | ||||
| 	{ | ||||
| 		nlohmann::json s; | ||||
|  | ||||
| 		s["type"] = magic_enum::enum_name(scaler.type); | ||||
| 		s["resistance"] = scaler.isResistance; | ||||
| 		s["polynomial"] = scaler.polynomial; | ||||
|  | ||||
| 		scalersJson.push_back(s); | ||||
| 	} | ||||
|  | ||||
| 	json["scaling"] = scalersJson; | ||||
|  | ||||
| 	return json; | ||||
| } | ||||
|  | ||||
| float nejlika::EntityTemplate::GetScaler(ModifierType type, bool isResistance, int32_t level) const { | ||||
| 	for (const auto& scaler : scalers) | ||||
| 	{ | ||||
| 		if (scaler.type == type && scaler.isResistance == isResistance) | ||||
| 		{ | ||||
| 			return CalculateScaler(scaler, level); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return 0.0f; | ||||
| } | ||||
|  | ||||
| std::vector<nejlika::ModifierInstance> nejlika::EntityTemplate::GenerateModifiers(int32_t level) const { | ||||
| 	std::vector<ModifierInstance> modifiers; | ||||
|  | ||||
| 	for (const auto& scaler : scalers) | ||||
| 	{ | ||||
| 		ModifierInstance modifier( | ||||
| 			scaler.type, | ||||
| 			CalculateScaler(scaler, level), | ||||
| 			ModifierOperator::Additive, | ||||
| 			scaler.isResistance, | ||||
| 			ModifierCategory::Player, | ||||
| 			0, | ||||
| 			"" | ||||
| 		); | ||||
|  | ||||
| 		modifiers.push_back(modifier); | ||||
| 	} | ||||
|  | ||||
| 	return modifiers; | ||||
| } | ||||
|  | ||||
| float nejlika::EntityTemplate::CalculateScaler(const EntityTemplateScaler& scaler, int32_t level) const { | ||||
| 	float result = 0.0f; | ||||
|  | ||||
| 	for (size_t i = 0; i < scaler.polynomial.size(); ++i) | ||||
| 	{ | ||||
| 		result += scaler.polynomial[i] * std::pow(level, i); | ||||
| 	} | ||||
|  | ||||
| 	return result; | ||||
| } | ||||
|  | ||||
							
								
								
									
										47
									
								
								dGame/EntityTemplate.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								dGame/EntityTemplate.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include <cstdint> | ||||
| #include <vector> | ||||
|  | ||||
| #include "Entity.h" | ||||
| #include "ModifierType.h" | ||||
| #include "ModifierInstance.h" | ||||
|  | ||||
| #include "json.hpp" | ||||
|  | ||||
| namespace nejlika | ||||
| { | ||||
|  | ||||
| class EntityTemplate | ||||
| { | ||||
| public: | ||||
| 	EntityTemplate() = default; | ||||
|  | ||||
| 	EntityTemplate(const nlohmann::json& json); | ||||
|  | ||||
| 	nlohmann::json ToJson() const; | ||||
|  | ||||
| 	LOT GetLOT() const { return lot; } | ||||
|  | ||||
| 	int32_t GetMinLevel() const { return minLevel; } | ||||
|  | ||||
| 	float GetScaler(ModifierType type, bool isResistance, int32_t level) const; | ||||
|  | ||||
| 	std::vector<ModifierInstance> GenerateModifiers(int32_t level) const; | ||||
|  | ||||
| private: | ||||
| 	struct EntityTemplateScaler | ||||
| 	{ | ||||
| 		ModifierType type; | ||||
| 		bool isResistance; | ||||
| 		std::vector<float> polynomial; | ||||
| 	}; | ||||
| 	 | ||||
| 	float CalculateScaler(const EntityTemplateScaler& scaler, int32_t level) const; | ||||
|  | ||||
| 	LOT lot; | ||||
| 	std::vector<EntityTemplateScaler> scalers; | ||||
| 	int32_t minLevel; | ||||
| }; | ||||
|  | ||||
| } | ||||
							
								
								
									
										14
									
								
								dGame/ModifierCategory.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								dGame/ModifierCategory.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include <cstdint> | ||||
|  | ||||
| namespace nejlika | ||||
| { | ||||
|  | ||||
| enum class ModifierCategory : uint8_t | ||||
| { | ||||
| 	Player = 0 << 0, | ||||
| 	Pet = 1 << 0 | ||||
| }; | ||||
|  | ||||
| } | ||||
							
								
								
									
										96
									
								
								dGame/ModifierInstance.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								dGame/ModifierInstance.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,96 @@ | ||||
| #include "ModifierInstance.h" | ||||
|  | ||||
| #include <sstream> | ||||
| #include <magic_enum.hpp> | ||||
|  | ||||
| nejlika::ModifierInstance::ModifierInstance(const nlohmann::json& config) { | ||||
| 	type = magic_enum::enum_cast<ModifierType>(config["type"].get<std::string>()).value_or(ModifierType::Invalid); | ||||
| 	value = config["value"].get<float>(); | ||||
| 	 | ||||
| 	if (config.contains("op")) { | ||||
| 		op = magic_enum::enum_cast<ModifierOperator>(config["op"].get<std::string>()).value_or(ModifierOperator::Additive); | ||||
| 	} | ||||
| 	else { | ||||
| 		op = ModifierOperator::Additive; | ||||
| 	} | ||||
|  | ||||
| 	isResistance = config.contains("resistance") ? config["resistance"].get<bool>() : false; | ||||
| 	 | ||||
| 	if (config.contains("category")) { | ||||
| 		category = magic_enum::enum_cast<ModifierCategory>(config["category"].get<std::string>()).value_or(ModifierCategory::Player); | ||||
| 	} | ||||
| 	else { | ||||
| 		category = ModifierCategory::Player; | ||||
| 	} | ||||
|  | ||||
| 	effectID = config.contains("effect-id") ? config["effect-id"].get<uint32_t>() : 0; | ||||
| 	effectType = config.contains("effect-type") ? config["effect-type"].get<std::string>() : ""; | ||||
| } | ||||
|  | ||||
| nlohmann::json nejlika::ModifierInstance::ToJson() const | ||||
| { | ||||
| 	nlohmann::json config; | ||||
|  | ||||
| 	config["type"] = magic_enum::enum_name(type); | ||||
| 	config["value"] = value; | ||||
| 	config["op"] = magic_enum::enum_name(op); | ||||
| 	config["resistance"] = isResistance; | ||||
| 	config["category"] = magic_enum::enum_name(category); | ||||
| 	config["effect-id"] = effectID; | ||||
| 	config["effect-type"] = effectType; | ||||
|  | ||||
| 	return config; | ||||
| } | ||||
|  | ||||
| std::string nejlika::ModifierInstance::GenerateHtmlString(const std::vector<ModifierInstance>& modifiers) | ||||
| { | ||||
| 	std::stringstream ss; | ||||
|  | ||||
| 	// target -> resistance -> op -> type -> value | ||||
| 	std::unordered_map<ModifierCategory, std::unordered_map<bool, std::unordered_map<ModifierOperator, std::unordered_map<ModifierType, float>>>> modifierMap; | ||||
|  | ||||
| 	for (const auto& modifier : modifiers) { | ||||
| 		if (modifier.type == ModifierType::Invalid) { | ||||
| 			continue; | ||||
| 		} | ||||
|  | ||||
| 		modifierMap[modifier.category][modifier.isResistance][modifier.op][modifier.type] = modifier.value; | ||||
| 	} | ||||
|  | ||||
| 	// Resistances and addatives are not separated, pet and player are | ||||
| 	// Summarize the resistances and addatives | ||||
| 	for (const auto& target : modifierMap) { | ||||
| 		if (target.first == ModifierCategory::Pet) { | ||||
| 			ss << "\n<font color=\"#D0AB62\">Pets:</font>\n"; | ||||
| 		} | ||||
| 		 | ||||
| 		for (const auto& resistance : target.second) { | ||||
|  | ||||
| 			ss << "\n<font color=\"#D0AB62\">"; | ||||
|  | ||||
| 			ss << ((resistance.first) ? "Resistances" : "Modifiers"); | ||||
|  | ||||
| 			ss << ":</font>\n"; | ||||
|  | ||||
| 			for (const auto& math : resistance.second) { | ||||
| 				for (const auto& modifier : math.second) { | ||||
| 					ss << "<font color=\"" << GetModifierTypeColor(modifier.first) << "\">"; | ||||
| 					 | ||||
| 					ss << magic_enum::enum_name<ModifierType>(modifier.first) << ": "; | ||||
|  | ||||
| 					ss << ((modifier.second > 0) ? "+" : "-"); | ||||
| 					 | ||||
| 					ss << std::fixed << std::setprecision(0) << std::abs(modifier.second); | ||||
|  | ||||
| 					if (math.first == ModifierOperator::Multiplicative) { | ||||
| 						ss << "%"; | ||||
| 					} | ||||
|  | ||||
| 					ss << "</font>\n"; | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return ss.str(); | ||||
| } | ||||
							
								
								
									
										84
									
								
								dGame/ModifierInstance.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								dGame/ModifierInstance.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,84 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "ModifierType.h" | ||||
| #include "ModifierCategory.h" | ||||
| #include "ModifierOperator.h" | ||||
|  | ||||
| #include <cstdint> | ||||
| #include <string> | ||||
|  | ||||
| #include "json.hpp" | ||||
|  | ||||
| namespace nejlika | ||||
| { | ||||
|  | ||||
| class ModifierInstance | ||||
| { | ||||
| public: | ||||
| 	ModifierInstance( | ||||
| 		ModifierType type, float value, ModifierOperator op, bool isResistance, ModifierCategory category, uint32_t effectID, const std::string& effectType | ||||
| 	) : type(type), value(value), op(op), isResistance(isResistance), category(category), effectID(effectID), effectType(effectType) {} | ||||
|  | ||||
| 	/** | ||||
| 	 * @brief Construct a new Modifier Instance object from a json configuration. | ||||
| 	 *  | ||||
| 	 * @param config The json configuration. | ||||
| 	 */ | ||||
| 	ModifierInstance(const nlohmann::json& config); | ||||
|  | ||||
| 	/** | ||||
| 	 * @brief Convert the modifier instance to a json representation. | ||||
| 	 *  | ||||
| 	 * @return The json representation. | ||||
| 	 */ | ||||
| 	nlohmann::json ToJson() const; | ||||
|  | ||||
| 	/** | ||||
| 	 * @brief Generate a HTML string representation of a set of modifiers. | ||||
| 	 *  | ||||
| 	 * @param modifiers The modifiers to generate the HTML string for. | ||||
| 	 * @return The HTML string. | ||||
| 	 */ | ||||
| 	static std::string GenerateHtmlString(const std::vector<ModifierInstance>& modifiers); | ||||
|  | ||||
| 	// Getters and setters | ||||
|  | ||||
| 	ModifierType GetType() const { return type; } | ||||
|  | ||||
| 	float GetValue() const { return value; } | ||||
| 	 | ||||
| 	ModifierOperator GetOperator() const { return op; } | ||||
|  | ||||
| 	bool IsResistance() const { return isResistance; } | ||||
| 	 | ||||
| 	ModifierCategory GetCategory() const { return category; } | ||||
|  | ||||
| 	uint32_t GetEffectID() const { return effectID; } | ||||
|  | ||||
| 	std::string GetEffectType() const { return effectType; } | ||||
|  | ||||
| 	void SetType(ModifierType type) { this->type = type; } | ||||
|  | ||||
| 	void SetValue(float value) { this->value = value; } | ||||
|  | ||||
| 	void SetOperator(ModifierOperator op) { this->op = op; } | ||||
|  | ||||
| 	void SetIsResistance(bool isResistance) { this->isResistance = isResistance; } | ||||
|  | ||||
| 	void SetCategory(ModifierCategory category) { this->category = category; } | ||||
|  | ||||
| 	void SetEffectID(uint32_t effectID) { this->effectID = effectID; } | ||||
|  | ||||
| 	void SetEffectType(const std::string& effectType) { this->effectType = effectType; } | ||||
|  | ||||
| private: | ||||
| 	ModifierType type; | ||||
| 	float value; | ||||
| 	ModifierOperator op; | ||||
| 	bool isResistance; | ||||
| 	ModifierCategory category; | ||||
| 	uint32_t effectID; | ||||
| 	std::string effectType; | ||||
| }; | ||||
|  | ||||
| } | ||||
							
								
								
									
										83
									
								
								dGame/ModifierName.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								dGame/ModifierName.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,83 @@ | ||||
| #include "ModifierName.h" | ||||
|  | ||||
| #include <magic_enum.hpp> | ||||
|  | ||||
| using namespace nejlika; | ||||
|  | ||||
| nejlika::ModifierName::ModifierName(const nlohmann::json & json) | ||||
| { | ||||
| 	name = json["name"].get<std::string>(); | ||||
|  | ||||
| 	if (json.contains("type")) | ||||
| 	{ | ||||
| 		type = magic_enum::enum_cast<ModifierNameType>(json["type"].get<std::string>()).value_or(ModifierNameType::Prefix); | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		type = ModifierNameType::Prefix; | ||||
| 	} | ||||
|  | ||||
| 	if (json.contains("rarity")) | ||||
| 	{ | ||||
| 		rarity = magic_enum::enum_cast<ModifierRarity>(json["rarity"].get<std::string>()).value_or(ModifierRarity::Common); | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		rarity = ModifierRarity::Common; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| nejlika::ModifierName::ModifierName(const ModifierNameTemplate& templateData) { | ||||
| 	name = templateData.GetName(); | ||||
| 	type = templateData.GetType(); | ||||
| 	rarity = templateData.GetRarity(); | ||||
| } | ||||
|  | ||||
| nlohmann::json nejlika::ModifierName::ToJson() const | ||||
| { | ||||
| 	nlohmann::json json; | ||||
|  | ||||
| 	json["name"] = name; | ||||
| 	json["type"] = magic_enum::enum_name(type); | ||||
| 	json["rarity"] = magic_enum::enum_name(rarity); | ||||
|  | ||||
| 	return json; | ||||
| } | ||||
|  | ||||
| std::string nejlika::ModifierName::GenerateHtmlString() const { | ||||
| 	const auto& rarityColor = ModifierRarityHelper::GetModifierRarityColor(rarity); | ||||
|  | ||||
| 	std::stringstream ss; | ||||
|  | ||||
| 	ss << "<font color=\"" << rarityColor << "\">" << name << "</font>"; | ||||
|  | ||||
| 	return ss.str(); | ||||
| } | ||||
|  | ||||
| std::string nejlika::ModifierName::GenerateHtmlString(const std::vector<ModifierName>& names) | ||||
| { | ||||
| 	std::stringstream ss; | ||||
|  | ||||
| 	for (const auto& name : names) { | ||||
| 		if (name.GetType() == ModifierNameType::Prefix && !name.name.empty()) { | ||||
| 			ss << name.GenerateHtmlString() << "\n"; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	ss << "<font color=\"#D0AB62\">NAME</font>"; | ||||
|  | ||||
| 	for (const auto& name : names) { | ||||
| 		if (name.GetType() == ModifierNameType::Suffix && !name.name.empty()) { | ||||
| 			ss << "\n" << name.GenerateHtmlString(); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Remove the last newline | ||||
| 	auto str = ss.str(); | ||||
|  | ||||
| 	if (!str.empty() && str.back() == '\n') { | ||||
| 		str.pop_back(); | ||||
| 	} | ||||
|  | ||||
| 	return str; | ||||
| } | ||||
							
								
								
									
										57
									
								
								dGame/ModifierName.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								dGame/ModifierName.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,57 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include <cstdint> | ||||
| #include <string> | ||||
|  | ||||
| #include "ModifierNameType.h" | ||||
| #include "ModifierRarity.h" | ||||
| #include "ModifierNameTemplate.h" | ||||
|  | ||||
| #include "json.hpp" | ||||
|  | ||||
| namespace nejlika | ||||
| { | ||||
|  | ||||
| class ModifierName | ||||
| { | ||||
| public: | ||||
| 	ModifierName(const std::string& name, ModifierNameType type, ModifierRarity rarity) : | ||||
| 		name(name), type(type), rarity(rarity) {} | ||||
|  | ||||
| 	ModifierName(const nlohmann::json& json); | ||||
|  | ||||
| 	ModifierName(const ModifierNameTemplate& templateData); | ||||
|  | ||||
| 	nlohmann::json ToJson() const; | ||||
|  | ||||
| 	std::string GenerateHtmlString() const; | ||||
|  | ||||
| 	/** | ||||
| 	 * @brief Generate a HTML string representation of a set of names. | ||||
| 	 *  | ||||
| 	 * @param modifiers The names to generate the HTML string for. | ||||
| 	 * @return The HTML string. | ||||
| 	 */ | ||||
| 	static std::string GenerateHtmlString(const std::vector<ModifierName>& names); | ||||
|  | ||||
| 	// Getters and setters | ||||
|  | ||||
| 	const std::string& GetName() const { return name; } | ||||
|  | ||||
| 	ModifierNameType GetType() const { return type; } | ||||
|  | ||||
| 	ModifierRarity GetRarity() const { return rarity; } | ||||
|  | ||||
| 	void SetName(const std::string& name) { this->name = name; } | ||||
|  | ||||
| 	void SetType(ModifierNameType type) { this->type = type; } | ||||
|  | ||||
| 	void SetRarity(ModifierRarity rarity) { this->rarity = rarity; } | ||||
|  | ||||
| private: | ||||
| 	std::string name; | ||||
| 	ModifierNameType type; | ||||
| 	ModifierRarity rarity; | ||||
| }; | ||||
|  | ||||
| } | ||||
							
								
								
									
										155
									
								
								dGame/ModifierNameTemplate.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										155
									
								
								dGame/ModifierNameTemplate.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,155 @@ | ||||
| #include "ModifierNameTemplate.h" | ||||
|  | ||||
| #include <iostream> | ||||
|  | ||||
| #include "magic_enum.hpp" | ||||
|  | ||||
| using namespace nejlika; | ||||
|  | ||||
| nejlika::ModifierNameTemplate::ModifierNameTemplate(const nlohmann::json & json) | ||||
| { | ||||
| 	name = json["name"].get<std::string>(); | ||||
|  | ||||
| 	if (json.contains("lot")) | ||||
| 	{ | ||||
| 		lot = json["lot"].get<int32_t>(); | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		lot = 0; | ||||
| 	} | ||||
| 	 | ||||
| 	if (json.contains("type")) | ||||
| 	{ | ||||
| 		type = magic_enum::enum_cast<ModifierNameType>(json["type"].get<std::string>()).value_or(ModifierNameType::Prefix); | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		type = ModifierNameType::Prefix; | ||||
| 	} | ||||
|  | ||||
| 	if (json.contains("items")) | ||||
| 	{ | ||||
| 		for (const auto& itemType : json["items"]) | ||||
| 		{ | ||||
| 			std::string type = itemType.get<std::string>(); | ||||
|  | ||||
| 			// Make uppercase | ||||
| 			std::transform(type.begin(), type.end(), type.begin(), ::toupper); | ||||
|  | ||||
| 			// Replace spaces with underscores | ||||
| 			std::replace(type.begin(), type.end(), ' ', '_'); | ||||
|  | ||||
| 			const auto itemTypeEnum = magic_enum::enum_cast<eItemType>(type); | ||||
|  | ||||
| 			if (itemTypeEnum.has_value()) | ||||
| 			{ | ||||
| 				itemTypes.push_back(itemTypeEnum.value()); | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				std::cout << "Invalid item type: " << type << std::endl; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if (json.contains("modifiers")) | ||||
| 	{ | ||||
| 		for (const auto& modifier : json["modifiers"]) | ||||
| 		{ | ||||
| 			modifiers.push_back(ModifierTemplate(modifier)); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if (json.contains("levels")) | ||||
| 	{ | ||||
| 		auto levels = json["levels"]; | ||||
|  | ||||
| 		if (levels.contains("min")) | ||||
| 		{ | ||||
| 			minLevel = levels["min"].get<int32_t>(); | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			minLevel = 1; | ||||
| 		} | ||||
|  | ||||
| 		if (levels.contains("max")) | ||||
| 		{ | ||||
| 			maxLevel = levels["max"].get<int32_t>(); | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			maxLevel = 45; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if (json.contains("rarity")) | ||||
| 	{ | ||||
| 		rarity = magic_enum::enum_cast<ModifierRarity>(json["rarity"].get<std::string>()).value_or(ModifierRarity::Common); | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		rarity = ModifierRarity::Common; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| nlohmann::json nejlika::ModifierNameTemplate::ToJson() const { | ||||
| 	nlohmann::json json; | ||||
|  | ||||
| 	json["name"] = name; | ||||
| 	json["type"] = magic_enum::enum_name(type); | ||||
|  | ||||
| 	if (lot != 0) | ||||
| 	{ | ||||
| 		json["lot"] = lot; | ||||
| 	} | ||||
|  | ||||
| 	if (!itemTypes.empty()) | ||||
| 	{ | ||||
| 		nlohmann::json items; | ||||
|  | ||||
| 		for (const auto& itemType : itemTypes) | ||||
| 		{ | ||||
| 			items.push_back(magic_enum::enum_name(itemType)); | ||||
| 		} | ||||
|  | ||||
| 		json["items"] = items; | ||||
| 	} | ||||
|  | ||||
| 	if (!modifiers.empty()) | ||||
| 	{ | ||||
| 		nlohmann::json modifierTemplates; | ||||
|  | ||||
| 		for (const auto& modifier : modifiers) | ||||
| 		{ | ||||
| 			modifierTemplates.push_back(modifier.ToJson()); | ||||
| 		} | ||||
|  | ||||
| 		json["modifiers"] = modifierTemplates; | ||||
| 	} | ||||
|  | ||||
| 	nlohmann::json levels; | ||||
|  | ||||
| 	levels["min"] = minLevel; | ||||
| 	levels["max"] = maxLevel; | ||||
|  | ||||
| 	json["levels"] = levels; | ||||
| 	json["rarity"] = magic_enum::enum_name(rarity); | ||||
|  | ||||
| 	return json; | ||||
| } | ||||
|  | ||||
| std::vector<ModifierInstance> nejlika::ModifierNameTemplate::GenerateModifiers(int32_t level) const | ||||
| { | ||||
| 	std::vector<ModifierInstance> result; | ||||
|  | ||||
| 	for (const auto& modifierTemplate : modifiers) | ||||
| 	{ | ||||
| 		auto modifiers = modifierTemplate.GenerateModifiers(level); | ||||
|  | ||||
| 		result.insert(result.end(), modifiers.begin(), modifiers.end()); | ||||
| 	} | ||||
|  | ||||
| 	return result; | ||||
| } | ||||
							
								
								
									
										55
									
								
								dGame/ModifierNameTemplate.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								dGame/ModifierNameTemplate.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | ||||
| #pragma	once | ||||
|  | ||||
| #include <cstdint> | ||||
| #include <string> | ||||
| #include <vector> | ||||
|  | ||||
| #include "eItemType.h" | ||||
| #include "ModifierNameType.h" | ||||
| #include "ModifierRarity.h" | ||||
| #include "ModifierTemplate.h" | ||||
|  | ||||
| #include "json.hpp" | ||||
|  | ||||
| namespace nejlika | ||||
| { | ||||
|  | ||||
| class ModifierNameTemplate | ||||
| { | ||||
| public: | ||||
| 	ModifierNameTemplate(const nlohmann::json& json); | ||||
|  | ||||
| 	nlohmann::json ToJson() const; | ||||
|  | ||||
| 	std::vector<ModifierInstance> GenerateModifiers(int32_t level) const; | ||||
|  | ||||
| 	// Getters | ||||
|  | ||||
| 	const std::string& GetName() const { return name; } | ||||
|  | ||||
| 	ModifierNameType GetType() const { return type; } | ||||
|  | ||||
| 	const std::vector<ModifierTemplate>& GetModifiers() const { return modifiers; } | ||||
|  | ||||
| 	const std::vector<eItemType>& GetItemTypes() const { return itemTypes; } | ||||
|  | ||||
| 	int32_t GetMinLevel() const { return minLevel; } | ||||
|  | ||||
| 	int32_t GetMaxLevel() const { return maxLevel; } | ||||
|  | ||||
| 	ModifierRarity GetRarity() const { return rarity; } | ||||
|  | ||||
| 	int32_t GetLOT() const { return lot; } | ||||
| 	 | ||||
| private: | ||||
| 	std::string name; | ||||
| 	int32_t lot; | ||||
| 	ModifierNameType type; | ||||
| 	std::vector<ModifierTemplate> modifiers; | ||||
| 	std::vector<eItemType> itemTypes; | ||||
| 	int32_t minLevel; | ||||
| 	int32_t maxLevel; | ||||
| 	ModifierRarity rarity; | ||||
| }; | ||||
|  | ||||
| } | ||||
							
								
								
									
										16
									
								
								dGame/ModifierNameType.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								dGame/ModifierNameType.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include <cstdint> | ||||
|  | ||||
| namespace nejlika | ||||
| { | ||||
|  | ||||
| enum class ModifierNameType : uint8_t | ||||
| { | ||||
| 	Prefix, | ||||
| 	Suffix, | ||||
| 	Object, | ||||
| 	Skill | ||||
| }; | ||||
|  | ||||
| } | ||||
							
								
								
									
										14
									
								
								dGame/ModifierOperator.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								dGame/ModifierOperator.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include <cstdint> | ||||
|  | ||||
| namespace nejlika | ||||
| { | ||||
|  | ||||
| enum class ModifierOperator : uint8_t | ||||
| { | ||||
| 	Additive, | ||||
| 	Multiplicative | ||||
| }; | ||||
|  | ||||
| } | ||||
							
								
								
									
										31
									
								
								dGame/ModifierRarity.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								dGame/ModifierRarity.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | ||||
| #include "ModifierRarity.h" | ||||
| #include <unordered_map> | ||||
|  | ||||
| using namespace nejlika; | ||||
|  | ||||
| namespace | ||||
| { | ||||
|  | ||||
| const std::unordered_map<ModifierRarity, std::string> colorMap = { | ||||
| 	{ModifierRarity::Common, "#FFFFFF"}, | ||||
| 	{ModifierRarity::Uncommon, "#B5AC15"}, | ||||
| 	{ModifierRarity::Rare, "#3EEA4A"}, | ||||
| 	{ModifierRarity::Epic, "#2F83C1"}, | ||||
| 	{ModifierRarity::Legendary, "#852DCA"}, | ||||
| 	{ModifierRarity::Relic, "#00FFFF"} | ||||
| }; | ||||
|  | ||||
| } | ||||
|  | ||||
| const std::string& nejlika::ModifierRarityHelper::GetModifierRarityColor(ModifierRarity rarity) | ||||
| { | ||||
| 	const auto color = colorMap.find(rarity); | ||||
|  | ||||
| 	if (color != colorMap.end()) { | ||||
| 		return color->second; | ||||
| 	} | ||||
|  | ||||
| 	static const std::string white = "#FFFFFF"; | ||||
|  | ||||
| 	return white; | ||||
| } | ||||
							
								
								
									
										24
									
								
								dGame/ModifierRarity.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								dGame/ModifierRarity.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include <cstdint> | ||||
| #include <string> | ||||
|  | ||||
| namespace nejlika | ||||
| { | ||||
|  | ||||
| enum class ModifierRarity : uint8_t | ||||
| { | ||||
| 	Common, | ||||
| 	Uncommon, | ||||
| 	Rare, | ||||
| 	Epic, | ||||
| 	Legendary, | ||||
| 	Relic | ||||
| }; | ||||
|  | ||||
| namespace ModifierRarityHelper | ||||
| { | ||||
| 	const std::string& GetModifierRarityColor(ModifierRarity rarity); | ||||
| } | ||||
|  | ||||
| } | ||||
							
								
								
									
										19
									
								
								dGame/ModifierScale.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								dGame/ModifierScale.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | ||||
| #include "ModifierScale.h" | ||||
|  | ||||
| nejlika::ModifierScale::ModifierScale(const nlohmann::json & json) | ||||
| { | ||||
| 	level = json["level"].get<int32_t>(); | ||||
| 	min = json["min"].get<float>(); | ||||
| 	max = json["max"].get<float>(); | ||||
| } | ||||
|  | ||||
| nlohmann::json nejlika::ModifierScale::ToJson() const | ||||
| { | ||||
| 	nlohmann::json json; | ||||
|  | ||||
| 	json["level"] = level; | ||||
| 	json["min"] = min; | ||||
| 	json["max"] = max; | ||||
|  | ||||
| 	return json; | ||||
| } | ||||
							
								
								
									
										33
									
								
								dGame/ModifierScale.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								dGame/ModifierScale.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include <cstdint> | ||||
|  | ||||
| #include "json.hpp" | ||||
|  | ||||
| namespace nejlika | ||||
| { | ||||
|  | ||||
| class ModifierScale | ||||
| { | ||||
| public: | ||||
| 	ModifierScale() = default; | ||||
|  | ||||
| 	ModifierScale(int32_t level, float min, float max) : level(level), min(min), max(max) {} | ||||
|  | ||||
| 	ModifierScale(const nlohmann::json& json); | ||||
|  | ||||
| 	nlohmann::json ToJson() const; | ||||
|  | ||||
| 	int32_t GetLevel() const { return level; } | ||||
|  | ||||
| 	float GetMin() const { return min; } | ||||
|  | ||||
| 	float GetMax() const { return max; } | ||||
|  | ||||
| private: | ||||
| 	int32_t level = 0; | ||||
| 	float min = 0.0f; | ||||
| 	float max = 0.0f; | ||||
| }; | ||||
|  | ||||
| } | ||||
							
								
								
									
										231
									
								
								dGame/ModifierTemplate.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										231
									
								
								dGame/ModifierTemplate.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,231 @@ | ||||
| #include "ModifierTemplate.h" | ||||
|  | ||||
| #include <magic_enum.hpp> | ||||
| #include <random> | ||||
| #include <algorithm> | ||||
|  | ||||
| using namespace nejlika; | ||||
|  | ||||
| nejlika::ModifierTemplate::ModifierTemplate(const nlohmann::json& config) { | ||||
| 	if (config.contains("type")) | ||||
| 	{ | ||||
| 		selector = ModifierTemplateSelector::One; | ||||
|  | ||||
| 		const auto type = magic_enum::enum_cast<ModifierType>(config["type"].get<std::string>()); | ||||
|  | ||||
| 		if (type.has_value()) | ||||
| 		{ | ||||
| 			types = {type.value()}; | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			types = {}; | ||||
| 		} | ||||
| 	} | ||||
| 	else if (config.contains("all")) | ||||
| 	{ | ||||
| 		selector = ModifierTemplateSelector::All; | ||||
|  | ||||
| 		types = {}; | ||||
|  | ||||
| 		for (const auto& type : config["all"]) | ||||
| 		{ | ||||
| 			const auto t = magic_enum::enum_cast<ModifierType>(type.get<std::string>()); | ||||
|  | ||||
| 			if (t.has_value()) | ||||
| 			{ | ||||
| 				types.push_back(t.value()); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	else if (config.contains("two-of")) | ||||
| 	{ | ||||
| 		selector = ModifierTemplateSelector::Two; | ||||
|  | ||||
| 		types = {}; | ||||
|  | ||||
| 		for (const auto& type : config["two-of"]) | ||||
| 		{ | ||||
| 			const auto t = magic_enum::enum_cast<ModifierType>(type.get<std::string>()); | ||||
|  | ||||
| 			if (t.has_value()) | ||||
| 			{ | ||||
| 				types.push_back(t.value()); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		types = {}; | ||||
| 	} | ||||
|  | ||||
| 	if (!config.contains("scaling")) | ||||
| 	{ | ||||
| 		throw std::runtime_error("Modifier template is missing scaling."); | ||||
| 	} | ||||
| 	 | ||||
| 	const auto scaling = config["scaling"]; | ||||
|  | ||||
| 	for (const auto& scaler : scaling) | ||||
| 	{ | ||||
| 		scales.push_back(ModifierScale(scaler)); | ||||
| 	} | ||||
|  | ||||
| 	if (config.contains("category")) | ||||
| 	{ | ||||
| 		category = magic_enum::enum_cast<ModifierCategory>(config["category"].get<std::string>()).value_or(ModifierCategory::Player); | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		category = ModifierCategory::Player; | ||||
| 	} | ||||
|  | ||||
| 	isResistance = config.contains("resistance") ? config["resistance"].get<bool>() : false; | ||||
|  | ||||
| 	effectID = config.contains("effect-id") ? config["effect-id"].get<uint32_t>() : 0; | ||||
|  | ||||
| 	effectType = config.contains("effect-type") ? config["effect-type"].get<std::string>() : ""; | ||||
|  | ||||
| 	if (config.contains("operator")) | ||||
| 	{ | ||||
| 		operatorType = magic_enum::enum_cast<ModifierOperator>(config["operator"].get<std::string>()).value_or(ModifierOperator::Additive); | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		operatorType = ModifierOperator::Additive; | ||||
| 	} | ||||
|  | ||||
| 	// Old format | ||||
| 	if (config.contains("percentage")) | ||||
| 	{ | ||||
| 		if (config["percentage"].get<bool>()) { | ||||
| 			operatorType = ModifierOperator::Multiplicative; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if (config.contains("pet")) | ||||
| 	{ | ||||
| 		if (config["pet"].get<bool>()) { | ||||
| 			category = ModifierCategory::Pet; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if (config.contains("owner")) | ||||
| 	{ | ||||
| 		if (config["owner"].get<bool>()) { | ||||
| 			category = ModifierCategory::Player; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| nlohmann::json nejlika::ModifierTemplate::ToJson() const { | ||||
| 	nlohmann::json config; | ||||
|  | ||||
| 	if (selector == ModifierTemplateSelector::One) | ||||
| 	{ | ||||
| 		config["type"] = magic_enum::enum_name(types[0]); | ||||
| 	} | ||||
| 	else if (selector == ModifierTemplateSelector::All) | ||||
| 	{ | ||||
| 		config["all"] = true; | ||||
|  | ||||
| 		for (const auto& type : types) | ||||
| 		{ | ||||
| 			config["types"].push_back(magic_enum::enum_name(type)); | ||||
| 		} | ||||
| 	} | ||||
| 	else if (selector == ModifierTemplateSelector::Two) | ||||
| 	{ | ||||
| 		config["two-of"] = true; | ||||
|  | ||||
| 		for (const auto& type : types) | ||||
| 		{ | ||||
| 			config["two-of"].push_back(magic_enum::enum_name(type)); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	nlohmann::json scaling; | ||||
|  | ||||
| 	for (const auto& scale : scales) | ||||
| 	{ | ||||
| 		scaling.push_back(scale.ToJson()); | ||||
| 	} | ||||
|  | ||||
| 	config["scaling"] = scaling; | ||||
|  | ||||
| 	config["category"] = magic_enum::enum_name(category); | ||||
| 	config["resistance"] = isResistance; | ||||
| 	config["effect-id"] = effectID; | ||||
| 	config["effect-type"] = effectType; | ||||
|  | ||||
| 	return config; | ||||
| } | ||||
|  | ||||
| std::vector<ModifierInstance> nejlika::ModifierTemplate::GenerateModifiers(int32_t level) const { | ||||
| 	std::vector<ModifierInstance> modifiers; | ||||
|  | ||||
| 	std::vector<ModifierType> selectedTypes; | ||||
|  | ||||
| 	if (types.empty()) | ||||
| 	{ | ||||
| 		return modifiers; | ||||
| 	} | ||||
|  | ||||
| 	if (selector == ModifierTemplateSelector::One) | ||||
| 	{ | ||||
| 		selectedTypes = {types[0]}; | ||||
| 	} | ||||
| 	else if (selector == ModifierTemplateSelector::All) | ||||
| 	{ | ||||
| 		selectedTypes = types; | ||||
| 	} | ||||
| 	else if (selector == ModifierTemplateSelector::Two) | ||||
| 	{ | ||||
| 		if (types.size() < 2) | ||||
| 		{ | ||||
| 			selectedTypes = types; | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			// Randomly select two types | ||||
| 			selectedTypes = types; | ||||
|  | ||||
| 			std::shuffle(selectedTypes.begin(), selectedTypes.end(), std::mt19937(std::random_device()())); | ||||
|  | ||||
| 			selectedTypes.resize(2); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	for (const auto& selectedType : selectedTypes) | ||||
| 	{ | ||||
| 		auto modifierOpt = GenerateModifier(selectedType, level); | ||||
|  | ||||
| 		if (modifierOpt.has_value()) | ||||
| 		{ | ||||
| 			modifiers.push_back(modifierOpt.value()); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return modifiers; | ||||
| } | ||||
|  | ||||
| std::optional<ModifierInstance> nejlika::ModifierTemplate::GenerateModifier(ModifierType type, int32_t level) const { | ||||
| 	ModifierScale scale; | ||||
| 	bool found = false; | ||||
|  | ||||
| 	// Select the scale with the highest level that is less than or equal to the current level | ||||
| 	for (const auto& s : scales) { | ||||
| 		if (s.GetLevel() <= level && s.GetLevel() > scale.GetLevel()) { | ||||
| 			scale = s; | ||||
| 			found = true; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if (!found) { | ||||
| 		return std::nullopt; | ||||
| 	} | ||||
|  | ||||
| 	float value = GeneralUtils::GenerateRandomNumber<float>(scale.GetMin(), scale.GetMax()); | ||||
|  | ||||
| 	return ModifierInstance(type, value, operatorType, isResistance, category, effectID, effectType); | ||||
| } | ||||
							
								
								
									
										88
									
								
								dGame/ModifierTemplate.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								dGame/ModifierTemplate.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,88 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include <cstdint> | ||||
| #include <vector> | ||||
| #include <string> | ||||
| #include <optional> | ||||
|  | ||||
| #include "ModifierInstance.h" | ||||
| #include "ModifierScale.h" | ||||
|  | ||||
| namespace nejlika | ||||
| { | ||||
|  | ||||
| enum ModifierTemplateSelector : uint8_t | ||||
| { | ||||
| 	One, | ||||
| 	All, | ||||
| 	Two | ||||
| }; | ||||
|  | ||||
| class ModifierTemplate | ||||
| { | ||||
| public: | ||||
| 	ModifierTemplate( | ||||
| 		const std::vector<ModifierType>& types, ModifierTemplateSelector selector, ModifierCategory category, bool isResistance, uint32_t effectID, const std::string& effectType | ||||
| 	) : types(types), selector(selector), category(category), isResistance(isResistance), effectID(effectID), effectType(effectType) {} | ||||
|  | ||||
| 	/** | ||||
| 	 * @brief Construct a new Modifier Template object from a json configuration. | ||||
| 	 *  | ||||
| 	 * @param config The json configuration. | ||||
| 	 */ | ||||
| 	ModifierTemplate(const nlohmann::json& config); | ||||
|  | ||||
| 	/** | ||||
| 	 * @brief Convert the modifier template to a json representation. | ||||
| 	 *  | ||||
| 	 * @return The json representation. | ||||
| 	 */ | ||||
| 	nlohmann::json ToJson() const; | ||||
|  | ||||
| 	std::vector<ModifierInstance> GenerateModifiers(int32_t level) const; | ||||
|  | ||||
| 	// Getters and setters | ||||
|  | ||||
| 	const std::vector<ModifierType>& GetTypes() const { return types; } | ||||
|  | ||||
| 	ModifierTemplateSelector GetSelector() const { return selector; } | ||||
|  | ||||
| 	const std::vector<ModifierScale>& GetScales() const { return scales; } | ||||
|  | ||||
| 	ModifierCategory GetCategory() const { return category; } | ||||
|  | ||||
| 	bool IsResistance() const { return isResistance; } | ||||
|  | ||||
| 	uint32_t GetEffectID() const { return effectID; } | ||||
|  | ||||
| 	std::string GetEffectType() const { return effectType; } | ||||
|  | ||||
| 	void SetTypes(const std::vector<ModifierType>& types) { this->types = types; } | ||||
|  | ||||
| 	void SetSelector(ModifierTemplateSelector selector) { this->selector = selector; } | ||||
|  | ||||
| 	void SetScales(const std::vector<ModifierScale>& scales) { this->scales = scales; } | ||||
|  | ||||
| 	void SetCategory(ModifierCategory category) { this->category = category; } | ||||
|  | ||||
| 	void SetIsResistance(bool isResistance) { this->isResistance = isResistance; } | ||||
|  | ||||
| 	void SetEffectID(uint32_t effectID) { this->effectID = effectID; } | ||||
|  | ||||
| 	void SetEffectType(const std::string& effectType) { this->effectType = effectType; } | ||||
|  | ||||
| private: | ||||
| 	std::optional<ModifierInstance> GenerateModifier(ModifierType type, int32_t level) const; | ||||
|  | ||||
| 	std::vector<ModifierType> types; | ||||
| 	ModifierTemplateSelector selector; | ||||
| 	std::vector<ModifierScale> scales; | ||||
| 	ModifierCategory category; | ||||
| 	ModifierOperator operatorType; | ||||
| 	bool isResistance; | ||||
| 	uint32_t effectID; | ||||
| 	std::string effectType; | ||||
|  | ||||
| }; | ||||
|  | ||||
| } | ||||
							
								
								
									
										38
									
								
								dGame/ModifierType.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								dGame/ModifierType.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | ||||
| #include "ModifierType.h" | ||||
| #include <unordered_map> | ||||
|  | ||||
| using namespace nejlika; | ||||
|  | ||||
| namespace | ||||
| { | ||||
|  | ||||
| const std::unordered_map<ModifierType, std::string> colorMap = { | ||||
| 	{ModifierType::Health, "#750000"}, | ||||
| 	{ModifierType::Armor, "#525252"}, | ||||
| 	{ModifierType::Imagination, "#0077FF"}, | ||||
| 	{ModifierType::Offensive, "#71583B"}, | ||||
| 	{ModifierType::Defensive, "#71583B"}, | ||||
| 	{ModifierType::Slashing, "#666666"}, | ||||
| 	{ModifierType::Piercing, "#4f4f4f"}, | ||||
| 	{ModifierType::Bludgeoning, "#e84646"}, | ||||
| 	{ModifierType::Fire, "#ff0000"}, | ||||
| 	{ModifierType::Cold, "#94d0f2"}, | ||||
| 	{ModifierType::Lightning, "#00a2ff"}, | ||||
| 	{ModifierType::Corruption, "#3d00ad"}, | ||||
| 	{ModifierType::Psychic, "#4b0161"} | ||||
| }; | ||||
|  | ||||
| } | ||||
|  | ||||
| const std::string& nejlika::GetModifierTypeColor(ModifierType type) | ||||
| { | ||||
| 	const auto color = colorMap.find(type); | ||||
|  | ||||
| 	if (color != colorMap.end()) { | ||||
| 		return color->second; | ||||
| 	} | ||||
|  | ||||
| 	static const std::string white = "#FFFFFF"; | ||||
|  | ||||
| 	return white; | ||||
| } | ||||
							
								
								
									
										34
									
								
								dGame/ModifierType.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								dGame/ModifierType.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include <cstdint> | ||||
| #include <string> | ||||
|  | ||||
| namespace nejlika | ||||
| { | ||||
|  | ||||
| enum class ModifierType : uint8_t | ||||
| { | ||||
| 	Health, | ||||
| 	Armor, | ||||
| 	Imagination, | ||||
|  | ||||
| 	Offensive, | ||||
| 	Defensive, | ||||
| 	 | ||||
| 	Slashing, | ||||
| 	Piercing, | ||||
| 	Bludgeoning, | ||||
|  | ||||
| 	Fire, | ||||
| 	Cold, | ||||
| 	Lightning, | ||||
| 	Corruption, | ||||
|  | ||||
| 	Psychic, | ||||
|  | ||||
| 	Invalid | ||||
| }; | ||||
|  | ||||
| const std::string& GetModifierTypeColor(ModifierType type); | ||||
|  | ||||
| } | ||||
| @@ -1,307 +0,0 @@ | ||||
| #include "Nejlika.h" | ||||
|  | ||||
| #include "SlashCommandHandler.h" | ||||
|  | ||||
| #include <InventoryComponent.h> | ||||
| #include <Item.h> | ||||
| #include <ChatPackets.h> | ||||
| #include <Amf3.h> | ||||
| #include <iomanip> | ||||
|  | ||||
| void nejlika::Initalize() | ||||
| { | ||||
| 	Command itemDescriptionCommand{ | ||||
| 		.help = "Special UI command, does nothing when used in chat.", | ||||
| 		.info = "Special UI command, does nothing when used in chat.", | ||||
| 		.aliases = {"d"}, | ||||
| 		.handle = ItemDescription, | ||||
| 		.requiredLevel = eGameMasterLevel::CIVILIAN | ||||
| 	}; | ||||
| 	SlashCommandHandler::RegisterCommand(itemDescriptionCommand); | ||||
| } | ||||
|  | ||||
| void nejlika::ItemDescription(Entity* entity, const SystemAddress& sysAddr, const std::string args) { | ||||
| 	auto splitArgs = GeneralUtils::SplitString(args, ' '); | ||||
| 	if (splitArgs.empty()) return; | ||||
| 	 | ||||
| 	auto requestId = GeneralUtils::TryParse<int32_t>(splitArgs[0]); | ||||
| 	 | ||||
| 	if (!requestId.has_value()) { | ||||
| 		ChatPackets::SendSystemMessage(sysAddr, u"Invalid item ID."); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	auto itemId = GeneralUtils::TryParse<LWOOBJID>(splitArgs[1]); | ||||
|  | ||||
| 	if (!itemId.has_value()) { | ||||
| 		ChatPackets::SendSystemMessage(sysAddr, u"Invalid item ID."); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	auto* inventoryComponent = entity->GetComponent<InventoryComponent>(); | ||||
|  | ||||
| 	if (!inventoryComponent) { | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	auto* item = inventoryComponent->FindItemById(itemId.value()); | ||||
|  | ||||
| 	if (!item) { | ||||
| 		ChatPackets::SendSystemMessage(sysAddr, u"Item not found."); | ||||
| 		return; | ||||
| 	} | ||||
| 	 | ||||
| 	const auto& itemData = item->GetConfig(); | ||||
|  | ||||
| 	LDFBaseData* modifiersData = nullptr; | ||||
|  | ||||
| 	for (const auto& data : itemData) { | ||||
| 		if (data->GetKey() == u"modifiers") { | ||||
| 			modifiersData = data; | ||||
| 			break; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if (!modifiersData) { | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	const auto modifiersStr = dynamic_cast<LDFData<std::string>*>(modifiersData)->GetValueAsString(); | ||||
|  | ||||
| 	if (modifiersStr.empty()) { | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	std::stringstream name; | ||||
| 	std::stringstream desc; | ||||
|  | ||||
| 	auto parts = GeneralUtils::SplitString(modifiersStr, '&'); | ||||
|  | ||||
| 	if (parts.size() != 2) { | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	auto names = GeneralUtils::SplitString(parts[0], ';'); | ||||
|  | ||||
| 	std::vector<ItemName> itemNames; | ||||
|  | ||||
| 	for (const auto& name : names) { | ||||
| 		ItemName itemName(name); | ||||
|  | ||||
| 		itemNames.push_back(itemName); | ||||
| 	} | ||||
|  | ||||
| 	name << ItemName::GenerateHtmlString(itemNames) << "\n"; | ||||
|  | ||||
| 	auto modifiers = GeneralUtils::SplitString(parts[1], ';'); | ||||
|  | ||||
| 	for (const auto& modifier : modifiers) { | ||||
| 		ItemModifier itemModifier(modifier); | ||||
|  | ||||
| 		desc << itemModifier.GenerateHtmlString() << "\n"; | ||||
| 	} | ||||
|  | ||||
| 	std::stringstream messageName; | ||||
| 	messageName << "desc" << requestId.value(); | ||||
|  | ||||
| 	AMFArrayValue amfArgs; | ||||
|  | ||||
| 	amfArgs.Insert("t", true); | ||||
| 	amfArgs.Insert("d", desc.str()); | ||||
| 	amfArgs.Insert("n", name.str()); | ||||
|  | ||||
| 	GameMessages::SendUIMessageServerToSingleClient(entity, sysAddr, messageName.str(), amfArgs); | ||||
| } | ||||
|  | ||||
| nejlika::ItemModifier::ItemModifier(const std::string& config) { | ||||
| 	auto splitConfig = GeneralUtils::SplitString(config, ','); | ||||
|  | ||||
| 	if (splitConfig.size() != 3) { | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	type = static_cast<ItemModifierType>(GeneralUtils::TryParse<uint8_t>(splitConfig[0]).value()); | ||||
| 	value = GeneralUtils::TryParse<float>(splitConfig[1]).value(); | ||||
| 	isPercentage = GeneralUtils::TryParse<bool>(splitConfig[2]).value(); | ||||
| } | ||||
|  | ||||
| std::string nejlika::ItemModifier::ToString() const { | ||||
| 	std::stringstream ss; | ||||
| 	ss << static_cast<uint8_t>(type) << ',' << value << ',' << isPercentage; | ||||
| 	return ss.str(); | ||||
| } | ||||
|  | ||||
| std::string nejlika::ItemModifier::GenerateHtmlString() const { | ||||
| 	// "<font color=\"#38B6FF\">Physical: +20%</font>\n..." | ||||
|  | ||||
| 	std::stringstream ss; | ||||
| 	 | ||||
| 	ss << "<font color=\""; | ||||
|  | ||||
| 	/* | ||||
| 	 * Health: #750000 | ||||
| 	 * Armor: #525252 | ||||
| 	 * Imagination: #0077FF | ||||
| 	 * Slashing: #666666 | ||||
| 	 * Piercing: #4f4f4f | ||||
| 	 * Bludgeoning: #e84646 | ||||
| 	 * Fire: #ff0000 | ||||
| 	 * Cold: #94d0f2 | ||||
| 	 * Lightning: #00a2ff | ||||
| 	 * Corruption: #3d00ad | ||||
| 	 * Psychic: #4b0161 | ||||
| 	 */ | ||||
| 	static const std::unordered_map<ItemModifierType, std::string> colorMap = { | ||||
| 		{ItemModifierType::Health, "#750000"}, | ||||
| 		{ItemModifierType::Armor, "#525252"}, | ||||
| 		{ItemModifierType::Imagination, "#0077FF"}, | ||||
| 		{ItemModifierType::Slashing, "#666666"}, | ||||
| 		{ItemModifierType::Piercing, "#4f4f4f"}, | ||||
| 		{ItemModifierType::Bludgeoning, "#e84646"}, | ||||
| 		{ItemModifierType::Fire, "#ff0000"}, | ||||
| 		{ItemModifierType::Cold, "#94d0f2"}, | ||||
| 		{ItemModifierType::Lightning, "#00a2ff"}, | ||||
| 		{ItemModifierType::Corruption, "#3d00ad"}, | ||||
| 		{ItemModifierType::Psychic, "#4b0161"} | ||||
| 	}; | ||||
|  | ||||
| 	static const std::unordered_map<ItemModifierType, std::string> namesMap = { | ||||
| 		{ItemModifierType::Health, "Health"}, | ||||
| 		{ItemModifierType::Armor, "Armor"}, | ||||
| 		{ItemModifierType::Imagination, "Imagination"}, | ||||
| 		{ItemModifierType::Slashing, "Slashing"}, | ||||
| 		{ItemModifierType::Piercing, "Piercing"}, | ||||
| 		{ItemModifierType::Bludgeoning, "Bludgeoning"}, | ||||
| 		{ItemModifierType::Fire, "Fire"}, | ||||
| 		{ItemModifierType::Cold, "Cold"}, | ||||
| 		{ItemModifierType::Lightning, "Lightning"}, | ||||
| 		{ItemModifierType::Corruption, "Corruption"}, | ||||
| 		{ItemModifierType::Psychic, "Psychic"} | ||||
| 	}; | ||||
|  | ||||
| 	const auto color = colorMap.find(type); | ||||
|  | ||||
| 	if (color != colorMap.end()) { | ||||
| 		ss << color->second; | ||||
| 	} else { | ||||
| 		ss << "#FFFFFF"; | ||||
| 	} | ||||
|  | ||||
| 	ss << "\">"; | ||||
|  | ||||
| 	const auto name = namesMap.find(type); | ||||
|  | ||||
| 	if (name != namesMap.end()) { | ||||
| 		ss << name->second; | ||||
| 	} else { | ||||
| 		ss << "Unknown"; | ||||
| 	} | ||||
|  | ||||
| 	ss << ": "; | ||||
| 	 | ||||
| 	if (value > 0) { | ||||
| 		ss << "+"; | ||||
| 	} | ||||
| 	else if (value < 0) { | ||||
| 		ss << "-"; | ||||
| 	} | ||||
|  | ||||
| 	// Only show 2 decimal places | ||||
| 	ss << std::fixed << std::setprecision(2) << std::abs(value); | ||||
|  | ||||
| 	if (isPercentage) { | ||||
| 		ss << "%"; | ||||
| 	} | ||||
|  | ||||
| 	ss << "</font>"; | ||||
| 	 | ||||
| 	return ss.str(); | ||||
| } | ||||
|  | ||||
| nejlika::ItemName::ItemName(const std::string& config) { | ||||
| 	auto splitConfig = GeneralUtils::SplitString(config, ','); | ||||
|  | ||||
| 	if (splitConfig.size() != 3) { | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	type = static_cast<ItemNameType>(GeneralUtils::TryParse<uint8_t>(splitConfig[0]).value()); | ||||
| 	name = splitConfig[1]; | ||||
| 	prefix = GeneralUtils::TryParse<bool>(splitConfig[2]).value(); | ||||
| } | ||||
|  | ||||
| std::string nejlika::ItemName::ToString() const { | ||||
| 	std::stringstream ss; | ||||
| 	ss << static_cast<uint8_t>(type) << ',' << name << ',' << prefix; | ||||
| 	return ss.str(); | ||||
| } | ||||
|  | ||||
| std::string nejlika::ItemName::GenerateHtmlString() const { | ||||
| 	std::stringstream ss; | ||||
|  | ||||
| 	ss << "<font color=\""; | ||||
|  | ||||
| 	/* | ||||
| 	 * Common: #FFFFFF | ||||
| 	 * Uncommon: #00FF00 | ||||
| 	 * Rare: #0077FF | ||||
| 	 * Epic: #FF00FF | ||||
| 	 * Legendary: #FF7700 | ||||
| 	 * Relic: #FFC391 | ||||
| 	 */ | ||||
|  | ||||
| 	static const std::unordered_map<ItemNameType, std::string> colorMap = { | ||||
| 		{ItemNameType::Common, "#FFFFFF"}, | ||||
| 		{ItemNameType::Uncommon, "#00FF00"}, | ||||
| 		{ItemNameType::Rare, "#0077FF"}, | ||||
| 		{ItemNameType::Epic, "#FF00FF"}, | ||||
| 		{ItemNameType::Legendary, "#FF7700"}, | ||||
| 		{ItemNameType::Relic, "#FFC391"} | ||||
| 	}; | ||||
|  | ||||
| 	const auto color = colorMap.find(type); | ||||
|  | ||||
| 	if (color != colorMap.end()) { | ||||
| 		ss << color->second; | ||||
| 	} else { | ||||
| 		ss << "#FFFFFF"; | ||||
| 	} | ||||
|  | ||||
| 	ss << "\">"; | ||||
|  | ||||
| 	ss << name; | ||||
|  | ||||
| 	ss << "</font>"; | ||||
|  | ||||
| 	return ss.str(); | ||||
| } | ||||
|  | ||||
| std::string nejlika::ItemName::GenerateHtmlString(const std::vector<ItemName>& names) { | ||||
|     // Prefix-1 Prefix-2 NAME Suffix-1 Suffix-2 | ||||
| 	std::stringstream ss; | ||||
|  | ||||
| 	for (const auto& name : names) { | ||||
| 		if (name.prefix) { | ||||
| 			ss << name.GenerateHtmlString() << "\n"; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	ss << "<font color=\"#56B555\">NAME</font>"; | ||||
|  | ||||
| 	for (const auto& name : names) { | ||||
| 		if (!name.prefix && !name.name.empty()) { | ||||
| 			ss << name.GenerateHtmlString() << "\n"; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Remove the last newline | ||||
| 	auto str = ss.str(); | ||||
|  | ||||
| 	if (!str.empty() && str.back() == '\n') { | ||||
| 		str.pop_back(); | ||||
| 	} | ||||
|  | ||||
| 	return str; | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -1,67 +0,0 @@ | ||||
| #pragma once | ||||
|  | ||||
| namespace nejlika | ||||
| { | ||||
|  | ||||
| void Initalize(); | ||||
|  | ||||
| void ItemDescription(Entity* entity, const SystemAddress& sysAddr, const std::string args); | ||||
|  | ||||
| enum class ItemModifierType : uint8_t | ||||
| { | ||||
| 	Health, | ||||
| 	Armor, | ||||
| 	Imagination, | ||||
| 	Slashing, | ||||
| 	Piercing, | ||||
| 	Bludgeoning, | ||||
| 	Fire, | ||||
| 	Cold, | ||||
| 	Lightning, | ||||
| 	Corruption, | ||||
| 	Psychic | ||||
| }; | ||||
|  | ||||
| struct ItemModifier | ||||
| { | ||||
| 	ItemModifierType type; | ||||
| 	float value; | ||||
| 	bool isPercentage; | ||||
|  | ||||
| 	ItemModifier(ItemModifierType type, float value, bool isPercentage) : type(type), value(value), isPercentage(isPercentage) {} | ||||
|  | ||||
| 	ItemModifier(const std::string& config); | ||||
|  | ||||
| 	std::string ToString() const; | ||||
|  | ||||
| 	std::string GenerateHtmlString() const; | ||||
| }; | ||||
|  | ||||
| enum class ItemNameType : uint8_t | ||||
| { | ||||
| 	Common, | ||||
| 	Uncommon, | ||||
| 	Rare, | ||||
| 	Epic, | ||||
| 	Legendary, | ||||
| 	Relic | ||||
| }; | ||||
|  | ||||
| struct ItemName | ||||
| { | ||||
| 	ItemNameType type; | ||||
| 	std::string name; | ||||
| 	bool prefix; | ||||
|  | ||||
| 	ItemName(ItemNameType type, const std::string& name, bool prefix) : type(type), name(name), prefix(prefix) {} | ||||
|  | ||||
| 	ItemName(const std::string& config); | ||||
|  | ||||
| 	std::string ToString() const; | ||||
|  | ||||
| 	std::string GenerateHtmlString() const; | ||||
|  | ||||
| 	static std::string GenerateHtmlString(const std::vector<ItemName>& names); | ||||
| }; | ||||
|  | ||||
| } | ||||
							
								
								
									
										151
									
								
								dGame/NejlikaData.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										151
									
								
								dGame/NejlikaData.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,151 @@ | ||||
| #include "NejlikaData.h" | ||||
|  | ||||
| #include "Game.h" | ||||
| #include "dConfig.h" | ||||
| #include "json.hpp" | ||||
| #include "Logger.h" | ||||
|  | ||||
| #include <fstream> | ||||
|  | ||||
| using namespace nejlika; | ||||
|  | ||||
| namespace | ||||
| { | ||||
|  | ||||
| std::unordered_map<nejlika::ModifierNameType, std::vector<nejlika::ModifierNameTemplate>> modifierNameTemplates; | ||||
|  | ||||
| std::unordered_map<LWOOBJID, nejlika::AdditionalItemData> additionalItemData; | ||||
|  | ||||
| std::unordered_map<LWOOBJID, nejlika::AdditionalEntityData> additionalEntityData; | ||||
|  | ||||
| std::unordered_map<LOT, nejlika::EntityTemplate> entityTemplates; | ||||
|  | ||||
| } | ||||
|  | ||||
| const std::unordered_map<ModifierNameType,std::vector<ModifierNameTemplate>>& nejlika::NejlikaData::GetModifierNameTemplates() | ||||
| { | ||||
| 	return modifierNameTemplates; | ||||
| } | ||||
|  | ||||
| const std::vector<nejlika::ModifierNameTemplate>& nejlika::NejlikaData::GetModifierNameTemplates(ModifierNameType type) | ||||
| { | ||||
| 	const auto it = modifierNameTemplates.find(type); | ||||
|  | ||||
| 	if (it != modifierNameTemplates.end()) { | ||||
| 		return it->second; | ||||
| 	} | ||||
|  | ||||
| 	static const std::vector<nejlika::ModifierNameTemplate> empty; | ||||
| 	return empty; | ||||
| } | ||||
|  | ||||
| const std::optional<AdditionalItemData*> nejlika::NejlikaData::GetAdditionalItemData(LWOOBJID id) { | ||||
| 	const auto& it = additionalItemData.find(id); | ||||
|  | ||||
| 	if (it != additionalItemData.end()) { | ||||
| 		return std::optional<AdditionalItemData*>(&it->second); | ||||
| 	} | ||||
| 	 | ||||
| 	return std::nullopt; | ||||
| } | ||||
|  | ||||
| const std::optional<AdditionalEntityData*> nejlika::NejlikaData::GetAdditionalEntityData(LWOOBJID id) { | ||||
| 	const auto& it = additionalEntityData.find(id); | ||||
|  | ||||
| 	if (it != additionalEntityData.end()) { | ||||
| 		return std::optional<AdditionalEntityData*>(&it->second); | ||||
| 	} | ||||
|  | ||||
| 	return std::nullopt; | ||||
| } | ||||
|  | ||||
| const std::optional<EntityTemplate*> nejlika::NejlikaData::GetEntityTemplate(LOT lot) { | ||||
| 	const auto& it = entityTemplates.find(lot); | ||||
|  | ||||
| 	if (it != entityTemplates.end()) { | ||||
| 		return std::optional<EntityTemplate*>(&it->second); | ||||
| 	} | ||||
|  | ||||
| 	return std::nullopt; | ||||
| } | ||||
|  | ||||
| void nejlika::NejlikaData::SetAdditionalItemData(LWOOBJID id, AdditionalItemData data) { | ||||
| 	additionalItemData[id] = data; | ||||
| } | ||||
|  | ||||
| void nejlika::NejlikaData::SetAdditionalEntityData(LWOOBJID id, AdditionalEntityData data) { | ||||
| 	additionalEntityData[id] = data; | ||||
| } | ||||
|  | ||||
| void nejlika::NejlikaData::UnsetAdditionalItemData(LWOOBJID id) { | ||||
| 	additionalItemData.erase(id); | ||||
| } | ||||
|  | ||||
| void nejlika::NejlikaData::UnsetAdditionalEntityData(LWOOBJID id) { | ||||
| 	additionalEntityData.erase(id); | ||||
| } | ||||
|  | ||||
| void nejlika::NejlikaData::LoadNejlikaData() | ||||
| { | ||||
| 	modifierNameTemplates.clear(); | ||||
|  | ||||
| 	// Load data from json file | ||||
| 	const auto& filename = Game::config->GetValue("nejlika"); | ||||
|  | ||||
| 	if (filename.empty()) | ||||
| 	{ | ||||
| 		return; | ||||
| 	} | ||||
| 	 | ||||
| 	std::ifstream file(filename); | ||||
|  | ||||
| 	if (!file.is_open()) | ||||
| 	{ | ||||
| 		LOG("Failed to open nejlika data file: %s", filename.c_str()); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	nlohmann::json json; | ||||
| 	 | ||||
| 	try | ||||
| 	{ | ||||
| 		json = nlohmann::json::parse(file); | ||||
| 	} | ||||
| 	catch (const nlohmann::json::exception& e) | ||||
| 	{ | ||||
| 		LOG("Failed to parse nejlika data file: %s", e.what()); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	if (!json.contains("modifier-templates")) | ||||
| 	{ | ||||
| 		LOG("nejlika data file does not contain modifier-templates"); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	const auto& modifierTemplates = json["modifier-templates"]; | ||||
|  | ||||
| 	for (const auto& value : modifierTemplates) | ||||
| 	{ | ||||
| 		auto modifierTemplate = ModifierNameTemplate(value); | ||||
|  | ||||
| 		modifierNameTemplates[modifierTemplate.GetType()].push_back(modifierTemplate); | ||||
| 	} | ||||
|  | ||||
| 	LOG("Loaded %d modifier templates", modifierNameTemplates.size()); | ||||
|  | ||||
| 	if (json.contains("entity-templates")) | ||||
| 	{ | ||||
| 		const auto& entityTemplatesArray = json["entity-templates"]; | ||||
| 		 | ||||
| 		for (const auto& value : entityTemplatesArray) | ||||
| 		{ | ||||
| 			auto entityTemplate = EntityTemplate(value); | ||||
|  | ||||
| 			entityTemplates[entityTemplate.GetLOT()] = entityTemplate; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	LOG("Loaded %d entity templates", entityTemplates.size()); | ||||
| } | ||||
|  | ||||
							
								
								
									
										34
									
								
								dGame/NejlikaData.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								dGame/NejlikaData.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include <cstdint> | ||||
| #include <vector> | ||||
|  | ||||
| #include "ModifierNameTemplate.h" | ||||
| #include "EntityTemplate.h" | ||||
| #include "AdditionalItemData.h" | ||||
| #include "AdditionalEntityData.h" | ||||
|  | ||||
| namespace nejlika::NejlikaData | ||||
| { | ||||
|  | ||||
| const std::unordered_map<ModifierNameType, std::vector<ModifierNameTemplate>>& GetModifierNameTemplates(); | ||||
|  | ||||
| const std::vector<ModifierNameTemplate>& GetModifierNameTemplates(ModifierNameType type); | ||||
|  | ||||
| const std::optional<AdditionalItemData*> GetAdditionalItemData(LWOOBJID id); | ||||
|  | ||||
| const std::optional<AdditionalEntityData*> GetAdditionalEntityData(LWOOBJID id); | ||||
|  | ||||
| const std::optional<EntityTemplate*> GetEntityTemplate(LOT lot); | ||||
|  | ||||
| void SetAdditionalItemData(LWOOBJID id, AdditionalItemData data); | ||||
|  | ||||
| void SetAdditionalEntityData(LWOOBJID id, AdditionalEntityData data); | ||||
|  | ||||
| void UnsetAdditionalItemData(LWOOBJID id); | ||||
|  | ||||
| void UnsetAdditionalEntityData(LWOOBJID id); | ||||
|  | ||||
| void LoadNejlikaData(); | ||||
|  | ||||
| } | ||||
							
								
								
									
										426
									
								
								dGame/NejlikaHooks.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										426
									
								
								dGame/NejlikaHooks.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,426 @@ | ||||
| #include "NejlikaHooks.h" | ||||
|  | ||||
| #include "SlashCommandHandler.h" | ||||
|  | ||||
| #include <InventoryComponent.h> | ||||
| #include <Item.h> | ||||
| #include <ChatPackets.h> | ||||
| #include <Amf3.h> | ||||
| #include <iomanip> | ||||
| #include <dConfig.h> | ||||
| #include <magic_enum.hpp> | ||||
| #include <GeneralUtils.h> | ||||
| #include <LevelProgressionComponent.h> | ||||
| #include <DestroyableComponent.h> | ||||
| #include <unordered_set> | ||||
| #include <BaseCombatAIComponent.h> | ||||
| #include <PlayerManager.h> | ||||
| #include <eGameMessageType.h> | ||||
| #include <dServer.h> | ||||
|  | ||||
| #include "NejlikaData.h" | ||||
|  | ||||
| using namespace nejlika; | ||||
| using namespace nejlika::NejlikaData; | ||||
|  | ||||
| void nejlika::NejlikaHooks::InstallHooks() | ||||
| { | ||||
| 	Command itemDescriptionCommand{ | ||||
| 		.help = "Special UI command, does nothing when used in chat.", | ||||
| 		.info = "Special UI command, does nothing when used in chat.", | ||||
| 		.aliases = {"d"}, | ||||
| 		.handle = ItemDescription, | ||||
| 		.requiredLevel = eGameMasterLevel::CIVILIAN | ||||
| 	}; | ||||
| 	SlashCommandHandler::RegisterCommand(itemDescriptionCommand); | ||||
|  | ||||
| 	LoadNejlikaData(); | ||||
|  | ||||
| 	InventoryComponent::OnItemCreated += [](InventoryComponent* component, Item* item) { | ||||
| 		const auto& itemType = static_cast<eItemType>(item->GetInfo().itemType); | ||||
|  | ||||
| 		static const std::unordered_set<eItemType> listOfHandledItems { | ||||
| 			eItemType::HAT, | ||||
| 			eItemType::CHEST, | ||||
| 			eItemType::LEGS, | ||||
| 			eItemType::NECK, | ||||
| 			eItemType::LEFT_HAND, | ||||
| 			eItemType::RIGHT_HAND | ||||
| 		}; | ||||
|  | ||||
| 		if (listOfHandledItems.find(itemType) == listOfHandledItems.end()) { | ||||
| 			return; | ||||
| 		} | ||||
|  | ||||
| 		if (item->GetLot() == 6086) { | ||||
| 			return; | ||||
| 		} | ||||
|  | ||||
| 		auto* levelProgressionComponent = component->GetParent()->GetComponent<LevelProgressionComponent>(); | ||||
|  | ||||
| 		if (!levelProgressionComponent) { | ||||
| 			return; | ||||
| 		} | ||||
|  | ||||
| 		auto additionalData = AdditionalItemData(item); | ||||
|  | ||||
| 		LOG("Rolling modifiers for item: %d", item->GetLot()); | ||||
|  | ||||
| 		additionalData.RollModifiers(item, levelProgressionComponent->GetLevel()); | ||||
|  | ||||
| 		SetAdditionalItemData(item->GetId(), additionalData); | ||||
| 	}; | ||||
|  | ||||
| 	EntityManager::OnEntityCreated += [](Entity* entity) { | ||||
| 		auto* destroyable = entity->GetComponent<DestroyableComponent>(); | ||||
|  | ||||
| 		if (!destroyable) { | ||||
| 			return; | ||||
| 		} | ||||
| 		 | ||||
| 		SetAdditionalEntityData(entity->GetObjectID(), AdditionalEntityData(entity->GetObjectID(), entity->GetLOT())); | ||||
| 		 | ||||
| 		auto additionalDataOpt = GetAdditionalEntityData(entity->GetObjectID()); | ||||
|  | ||||
| 		if (!additionalDataOpt.has_value()) { | ||||
| 			return; | ||||
| 		} | ||||
|  | ||||
| 		auto& additionalData = *additionalDataOpt.value(); | ||||
|  | ||||
| 		additionalData.ApplyToEntity(); | ||||
| 	}; | ||||
|  | ||||
| 	EntityManager::OnEntityDestroyed += [](Entity* entity) { | ||||
| 		UnsetAdditionalEntityData(entity->GetObjectID()); | ||||
| 	}; | ||||
|  | ||||
| 	InventoryComponent::OnItemLoaded += [](InventoryComponent* component, Item* item) { | ||||
| 		SetAdditionalItemData(item->GetId(), AdditionalItemData(item)); | ||||
| 	}; | ||||
|  | ||||
| 	InventoryComponent::OnItemDestroyed += [](InventoryComponent* component, Item* item) { | ||||
| 		UnsetAdditionalItemData(item->GetId()); | ||||
| 	}; | ||||
|  | ||||
| 	InventoryComponent::OnItemEquipped += [](InventoryComponent* component, Item* item) { | ||||
| 		std::cout << "Item equipped: " << item->GetId() << std::endl; | ||||
|  | ||||
| 		const auto entityDataOpt = GetAdditionalEntityData(component->GetParent()->GetObjectID()); | ||||
|  | ||||
| 		if (!entityDataOpt.has_value()) { | ||||
| 			std::cout << "No entity data found for entity." << std::endl; | ||||
| 			return; | ||||
| 		} | ||||
|  | ||||
| 		auto& entityData = *entityDataOpt.value(); | ||||
|  | ||||
| 		entityData.ApplyToEntity(); | ||||
|  | ||||
| 		const auto itemDataOpt = GetAdditionalItemData(item->GetId()); | ||||
|  | ||||
| 		if (!itemDataOpt.has_value()) { | ||||
| 			std::cout << "No item data found for item." << std::endl; | ||||
| 			return; | ||||
| 		} | ||||
|  | ||||
| 		const auto& itemData = *itemDataOpt.value(); | ||||
|  | ||||
| 		const auto itemId = item->GetId(); | ||||
| 		 | ||||
| 		std::cout << "Sending effects for item: " << itemId << " with " << itemData.GetModifierInstances().size() << " modifiers." << std::endl; | ||||
|  | ||||
| 		for (const auto& modifier : itemData.GetModifierInstances()) { | ||||
| 			const auto effectID = modifier.GetEffectID(); | ||||
| 			const auto effectType = modifier.GetEffectType(); | ||||
|  | ||||
| 			component->GetParent()->AddCallbackTimer(0.1f, [itemId, effectID, effectType]() { | ||||
| 				std::cout << "Sending effect: " << effectID << " - " << effectType << std::endl; | ||||
| 				GameMessages::SendPlayFXEffect( | ||||
| 					itemId, | ||||
| 					static_cast<int32_t>(effectID), | ||||
| 					GeneralUtils::UTF8ToUTF16(effectType), | ||||
| 					std::to_string(GeneralUtils::GenerateRandomNumber<uint32_t>()) | ||||
| 				); | ||||
| 			}); | ||||
| 		} | ||||
| 	}; | ||||
|  | ||||
| 	InventoryComponent::OnItemUnequipped += [](InventoryComponent* component, Item* item) { | ||||
| 		const auto entityDataOpt = GetAdditionalEntityData(component->GetParent()->GetObjectID()); | ||||
|  | ||||
| 		if (!entityDataOpt.has_value()) { | ||||
| 			return; | ||||
| 		} | ||||
|  | ||||
| 		auto& entityData = *entityDataOpt.value(); | ||||
|  | ||||
| 		entityData.ApplyToEntity(); | ||||
| 	}; | ||||
|  | ||||
| 	DestroyableComponent::OnDamageCalculation += [](Entity* damaged, LWOOBJID offender, uint32_t skillID, uint32_t& damage) { | ||||
| 		std::cout << "Calculating damage with skill: " << skillID << std::endl; | ||||
|  | ||||
| 		const auto damagedEntityOpt = GetAdditionalEntityData(damaged->GetObjectID()); | ||||
|  | ||||
| 		if (!damagedEntityOpt.has_value()) { | ||||
| 			std::cout << "No entity data found for damaged entity." << std::endl; | ||||
| 			return; | ||||
| 		} | ||||
|  | ||||
| 		auto& damagedEntity = *damagedEntityOpt.value(); | ||||
|  | ||||
| 		const auto offenderEntityOpt = GetAdditionalEntityData(offender); | ||||
|  | ||||
| 		if (!offenderEntityOpt.has_value()) { | ||||
| 			std::cout << "No entity data found for offender entity." << std::endl; | ||||
| 			return; | ||||
| 		} | ||||
|  | ||||
| 		auto& offenderEntity = *offenderEntityOpt.value(); | ||||
|  | ||||
| 		auto* baseCombatAIComponent = damaged->GetComponent<BaseCombatAIComponent>(); | ||||
|  | ||||
| 		if (baseCombatAIComponent) { | ||||
| 			baseCombatAIComponent->SetThreat(offender, 1); | ||||
| 		} | ||||
| 		 | ||||
| 		damagedEntity.CheckForRescale(&offenderEntity); | ||||
| 		offenderEntity.CheckForRescale(&damagedEntity); | ||||
| 		 | ||||
| 		int32_t level = offenderEntity.GetLevel(); | ||||
|  | ||||
| 		auto* offfendEntity = Game::entityManager->GetEntity(offender); | ||||
|  | ||||
| 		if (offfendEntity == nullptr) { | ||||
| 			std::cout << "Offender entity not found." << std::endl; | ||||
| 			return; | ||||
| 		} | ||||
|  | ||||
| 		auto* levelProgressionComponent = offfendEntity->GetComponent<LevelProgressionComponent>(); | ||||
|  | ||||
| 		if (levelProgressionComponent) { | ||||
| 			level = levelProgressionComponent->GetLevel(); | ||||
| 		} | ||||
|  | ||||
| 		LOT itemLot = 0; | ||||
| 		LWOOBJID itemId = 0; | ||||
|  | ||||
| 		auto* inventoryComponent = offfendEntity->GetComponent<InventoryComponent>(); | ||||
|  | ||||
| 		if (inventoryComponent) { | ||||
| 			const auto& skills = inventoryComponent->GetSkills(); | ||||
|  | ||||
| 			std::cout << "Found " << skills.size() << " skills." << std::endl; | ||||
|  | ||||
| 			// omg... | ||||
| 			for (const auto& [slot, skill] : skills) { | ||||
| 				std::cout << "Found skill: " << skill << std::endl; | ||||
| 				 | ||||
| 				if (skill != skillID) { | ||||
| 					continue; | ||||
| 				} | ||||
|  | ||||
| 				const auto& equipped = inventoryComponent->GetEquippedItems(); | ||||
|  | ||||
| 				for (const auto& [equippedSlot, itemDetails] : equipped) { | ||||
| 					std::cout << "Found equipped item: " << itemDetails.lot << std::endl; | ||||
|  | ||||
| 					const auto info = Inventory::FindItemComponent(itemDetails.lot); | ||||
|  | ||||
| 					const auto itemBehaviorSlot = InventoryComponent::FindBehaviorSlot(static_cast<eItemType>(info.itemType)); | ||||
|  | ||||
| 					std::cout << "Comparing slots: " << static_cast<int32_t>(itemBehaviorSlot) << " - " << static_cast<int32_t>(slot) << std::endl; | ||||
|  | ||||
| 					if (itemBehaviorSlot == slot) { | ||||
| 						itemLot = itemDetails.lot; | ||||
| 						itemId = itemDetails.id; | ||||
|  | ||||
| 						std::cout << "Found item: " << itemLot << std::endl; | ||||
|  | ||||
| 						break; | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		const auto& skillTemplates = GetModifierNameTemplates(ModifierNameType::Skill); | ||||
|  | ||||
| 		const auto& skillTemplateIt = std::find_if(skillTemplates.begin(), skillTemplates.end(), [skillID](const auto& it) { | ||||
| 			return it.GetLOT() == skillID; | ||||
| 		}); | ||||
|  | ||||
| 		std::vector<ModifierInstance> modifiers; | ||||
|  | ||||
| 		if (skillTemplateIt != skillTemplates.end()) { | ||||
| 			const auto& skillTemplate = *skillTemplateIt; | ||||
|  | ||||
| 			const auto skillModifiers = skillTemplate.GenerateModifiers(level); | ||||
|  | ||||
| 			modifiers.insert(modifiers.end(), skillModifiers.begin(), skillModifiers.end()); | ||||
| 		} | ||||
|  | ||||
| 		std::unordered_set<ModifierType> damageTypes; | ||||
|  | ||||
| 		for (const auto& modifier : modifiers) { | ||||
| 			damageTypes.insert(modifier.GetType()); | ||||
| 		} | ||||
|  | ||||
| 		const auto itemDataOpt = GetAdditionalItemData(itemId); | ||||
|  | ||||
| 		if (itemDataOpt.has_value()) { | ||||
| 			const auto& itemData = *itemDataOpt.value(); | ||||
|  | ||||
| 			for (const auto& modifier : itemData.GetModifierInstances()) { | ||||
| 				damageTypes.insert(modifier.GetType()); | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		// Remove the following: Offensive, Defensive, Health, Armor, Imagination | ||||
| 		damageTypes.erase(ModifierType::Offensive); | ||||
| 		damageTypes.erase(ModifierType::Defensive); | ||||
| 		damageTypes.erase(ModifierType::Health); | ||||
| 		damageTypes.erase(ModifierType::Armor); | ||||
| 		damageTypes.erase(ModifierType::Imagination); | ||||
|  | ||||
| 		uint32_t totalDamage = 0; | ||||
|  | ||||
| 		for (const auto& type : damageTypes) { | ||||
| 			float damageValue = offenderEntity.CalculateModifier(type, modifiers, level); | ||||
|  | ||||
| 			// Calculate resistance, can't go below 20% of the original damage | ||||
| 			const auto resistance = std::max(1 - (damagedEntity.CalculateResistance(type) / 100), 0.2f); | ||||
|  | ||||
| 			damageValue *= resistance; | ||||
|  | ||||
| 			totalDamage += static_cast<uint32_t>(damageValue); | ||||
|  | ||||
| 			std::cout << "Damage type: " << magic_enum::enum_name(type) << " - " << damageValue << std::endl << " Resistance: " << resistance << std::endl; | ||||
| 			std::cout << "Heath left: " << damaged->GetComponent<DestroyableComponent>()->GetHealth() << std::endl; | ||||
| 		} | ||||
|  | ||||
| 		// Get the offenders Offensive modifier | ||||
| 		auto offenderModifiers = offenderEntity.CalculateModifier(ModifierType::Offensive, level); | ||||
|  | ||||
| 		// Get the defenders Defensive modifier | ||||
| 		auto defensiveModifiers = damagedEntity.CalculateModifier(ModifierType::Defensive, level); | ||||
|  | ||||
| 		if (offenderModifiers == 0) offenderModifiers = 1; | ||||
| 		if (defensiveModifiers == 0) defensiveModifiers = 1; | ||||
|  | ||||
| 		auto ratio = offenderModifiers / defensiveModifiers; | ||||
|  | ||||
| 		// Ratio can not ge below 1.05 | ||||
| 		ratio = std::max(ratio, 1.05f); | ||||
|  | ||||
| 		// Roll a number between 0 and ratio | ||||
| 		float roll = GeneralUtils::GenerateRandomNumber<size_t>() / static_cast<float>(std::numeric_limits<size_t>::max()); | ||||
|  | ||||
| 		roll *= ratio; | ||||
|  | ||||
| 		std::cout << "Offensive: " << offenderModifiers << " Defensive: " << defensiveModifiers << " Ratio: " << ratio << " Roll: " << roll << std::endl; | ||||
|  | ||||
| 		// If the roll is above 1, the damage is increased by 1+roll, to a maximum of 5x the damage | ||||
| 		if (roll > 1) { | ||||
| 			roll = std::min(roll, 5.0f); | ||||
| 			totalDamage += static_cast<uint32_t>(totalDamage * roll); | ||||
|  | ||||
| 			GameMessages::SendPlayFXEffect( | ||||
| 				damaged->GetObjectID(), | ||||
| 				20041, | ||||
| 				u"onhit", | ||||
| 				std::to_string(GeneralUtils::GenerateRandomNumber<uint32_t>()) | ||||
| 			); | ||||
| 		} | ||||
|  | ||||
| 		// Add a random +10% to the damage | ||||
| 		totalDamage += static_cast<uint32_t>(totalDamage * (GeneralUtils::GenerateRandomNumber<int32_t>(0, 10) / 100.0f)); | ||||
|  | ||||
| 		damage = totalDamage; | ||||
|  | ||||
| 		if (offfendEntity->IsPlayer()) { | ||||
| 			offfendEntity->AddCallbackTimer(0.0f, [offfendEntity, skillID]() { | ||||
| 				CBITSTREAM; | ||||
| 				CMSGHEADER; | ||||
|  | ||||
| 				const auto entity = offfendEntity->GetObjectID(); | ||||
|  | ||||
| 				bitStream.Write(entity); | ||||
| 				bitStream.Write(eGameMessageType::MODIFY_SKILL_COOLDOWN); | ||||
|  | ||||
| 				bitStream.Write1(); | ||||
| 				bitStream.Write<float>(-10.0f); | ||||
| 				bitStream.Write<int32_t>(static_cast<int32_t>(skillID)); | ||||
|  | ||||
| 				LOG("Sending cooldown reduction for skill: %d", skillID); | ||||
|  | ||||
| 				SEND_PACKET_BROADCAST; | ||||
| 			}); | ||||
| 		} | ||||
| 	}; | ||||
| } | ||||
|  | ||||
|  | ||||
| void nejlika::NejlikaHooks::ItemDescription(Entity* entity, const SystemAddress& sysAddr, const std::string args) { | ||||
| 	auto splitArgs = GeneralUtils::SplitString(args, ' '); | ||||
| 	if (splitArgs.size() < 2) { | ||||
| 		ChatPackets::SendSystemMessage(sysAddr, u"Invalid arguments."); | ||||
| 		return; | ||||
| 	} | ||||
| 	 | ||||
| 	auto requestId = GeneralUtils::TryParse<int32_t>(splitArgs[0]).value_or(-1); | ||||
| 	 | ||||
| 	if (requestId == -1) { | ||||
| 		ChatPackets::SendSystemMessage(sysAddr, u"Invalid item ID."); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	std::cout << "Request ID: " << requestId << std::endl; | ||||
|  | ||||
| 	auto itemId = GeneralUtils::TryParse<LWOOBJID>(splitArgs[1]).value_or(LWOOBJID_EMPTY); | ||||
|  | ||||
| 	if (itemId == LWOOBJID_EMPTY) { | ||||
| 		ChatPackets::SendSystemMessage(sysAddr, u"Invalid item ID."); | ||||
| 		return; | ||||
| 	} | ||||
| 	 | ||||
| 	const auto itemDataOpt = GetAdditionalItemData(itemId); | ||||
|  | ||||
| 	if (!itemDataOpt.has_value()) { | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	auto& itemDetails = *itemDataOpt.value(); | ||||
|  | ||||
| 	const auto& modifiers = itemDetails.GetModifierInstances(); | ||||
| 	const auto& names = itemDetails.GetModifierNames(); | ||||
|  | ||||
| 	if (modifiers.empty() && names.empty()) { | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	std::stringstream name; | ||||
| 	std::stringstream desc; | ||||
|  | ||||
| 	name << "NAME"; | ||||
|  | ||||
| 	desc << ModifierName::GenerateHtmlString(names) << "\n"; | ||||
| 	 | ||||
| 	desc << ModifierInstance::GenerateHtmlString(modifiers); | ||||
|  | ||||
| 	std::cout << "Sending item name: " << name.str() << std::endl; | ||||
| 	std::cout << "Sending item desc: " << desc.str() << std::endl; | ||||
|  | ||||
| 	std::stringstream messageName; | ||||
| 	messageName << "desc" << requestId; | ||||
|  | ||||
| 	AMFArrayValue amfArgs; | ||||
|  | ||||
| 	amfArgs.Insert("t", true); | ||||
| 	amfArgs.Insert("d", desc.str()); | ||||
| 	amfArgs.Insert("n", name.str()); | ||||
|  | ||||
| 	GameMessages::SendUIMessageServerToSingleClient(entity, sysAddr, messageName.str(), amfArgs); | ||||
|  | ||||
| 	std::cout << "Sent item description." << std::endl; | ||||
| } | ||||
							
								
								
									
										10
									
								
								dGame/NejlikaHooks.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								dGame/NejlikaHooks.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| #pragma once | ||||
|  | ||||
| namespace nejlika::NejlikaHooks | ||||
| { | ||||
|  | ||||
| void InstallHooks(); | ||||
|  | ||||
| void ItemDescription(Entity* entity, const SystemAddress& sysAddr, const std::string args); | ||||
|  | ||||
| } | ||||
| @@ -18,6 +18,8 @@ void PlayerManager::AddPlayer(Entity* player) { | ||||
|  | ||||
| 	if (iter == m_Players.end()) { | ||||
| 		m_Players.push_back(player); | ||||
|  | ||||
| 		OnPlayerAdded(player); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -27,6 +29,8 @@ bool PlayerManager::RemovePlayer(Entity* player) { | ||||
| 	const bool toReturn = iter != m_Players.end(); | ||||
| 	if (toReturn) { | ||||
| 		m_Players.erase(iter); | ||||
|  | ||||
| 		OnPlayerRemoved(player); | ||||
| 	} | ||||
|  | ||||
| 	return toReturn; | ||||
|   | ||||
| @@ -2,6 +2,7 @@ | ||||
| #define __PLAYERMANAGER__H__ | ||||
|  | ||||
| #include "dCommonVars.h" | ||||
| #include "Observable.h" | ||||
|  | ||||
| #include <string> | ||||
|  | ||||
| @@ -20,6 +21,9 @@ namespace PlayerManager { | ||||
| 	Entity* GetPlayer(LWOOBJID playerID); | ||||
|  | ||||
| 	const std::vector<Entity*>& GetAllPlayers(); | ||||
|  | ||||
| 	static Observable<Entity*> OnPlayerAdded; | ||||
| 	static Observable<Entity*> OnPlayerRemoved; | ||||
| }; | ||||
|  | ||||
| #endif  //!__PLAYERMANAGER__H__ | ||||
|   | ||||
| @@ -36,6 +36,7 @@ BaseCombatAIComponent::BaseCombatAIComponent(Entity* parent, const uint32_t id): | ||||
| 	m_Disabled = false; | ||||
| 	m_SkillEntries = {}; | ||||
| 	m_SoftTimer = 5.0f; | ||||
| 	m_StunImmune = true; | ||||
|  | ||||
| 	//Grab the aggro information from BaseCombatAI: | ||||
| 	auto componentQuery = CDClientDatabase::CreatePreppedStmt( | ||||
| @@ -369,9 +370,9 @@ void BaseCombatAIComponent::CalculateCombat(const float deltaTime) { | ||||
|  | ||||
| 			m_Timer = 0; | ||||
|  | ||||
| 			m_SkillTime = result.skillTime; | ||||
| 			m_SkillTime = result.skillTime / 2.0f; | ||||
|  | ||||
| 			entry.cooldown = entry.abilityCooldown + m_SkillTime; | ||||
| 			entry.cooldown = 0.1f; //entry.abilityCooldown + m_SkillTime; | ||||
|  | ||||
| 			m_SkillEntries[i] = entry; | ||||
|  | ||||
| @@ -619,6 +620,10 @@ void BaseCombatAIComponent::SetThreat(LWOOBJID offender, float threat) { | ||||
| 	m_DirtyThreat = true; | ||||
| } | ||||
|  | ||||
| const std::map<LWOOBJID, float>& BaseCombatAIComponent::GetThreats() const { | ||||
| 	return m_ThreatEntries; | ||||
| } | ||||
|  | ||||
| const NiPoint3& BaseCombatAIComponent::GetStartPosition() const { | ||||
| 	return m_StartPosition; | ||||
| } | ||||
| @@ -679,7 +684,7 @@ void BaseCombatAIComponent::OnAggro() { | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	m_MovementAI->SetHaltDistance(m_AttackRadius); | ||||
| 	m_MovementAI->SetHaltDistance(0); | ||||
|  | ||||
| 	NiPoint3 targetPos = target->GetPosition(); | ||||
| 	NiPoint3 currentPos = m_MovementAI->GetParent()->GetPosition(); | ||||
| @@ -713,7 +718,7 @@ void BaseCombatAIComponent::OnTether() { | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	m_MovementAI->SetHaltDistance(m_AttackRadius); | ||||
| 	m_MovementAI->SetHaltDistance(0); | ||||
|  | ||||
| 	NiPoint3 targetPos = target->GetPosition(); | ||||
| 	NiPoint3 currentPos = m_MovementAI->ApproximateLocation(); | ||||
|   | ||||
| @@ -114,6 +114,12 @@ public: | ||||
| 	 */ | ||||
| 	void SetThreat(LWOOBJID offender, float threat); | ||||
|  | ||||
| 	/** | ||||
| 	 * Get all threats for this entity | ||||
| 	 * @return all threats for this entity | ||||
| 	 */ | ||||
| 	const std::map<LWOOBJID, float>& GetThreats() const; | ||||
|  | ||||
| 	/** | ||||
| 	 * Gets the position where the entity spawned | ||||
| 	 * @return the position where the entity spawned | ||||
|   | ||||
| @@ -38,8 +38,7 @@ | ||||
|  | ||||
| #include "CDComponentsRegistryTable.h" | ||||
|  | ||||
| Implementation<bool, const Entity*> DestroyableComponent::IsEnemyImplentation; | ||||
| Implementation<bool, const Entity*> DestroyableComponent::IsFriendImplentation; | ||||
| Observable<Entity*, LWOOBJID, uint32_t, uint32_t&> DestroyableComponent::OnDamageCalculation; | ||||
|  | ||||
| DestroyableComponent::DestroyableComponent(Entity* parent) : Component(parent) { | ||||
| 	m_iArmor = 0; | ||||
| @@ -421,7 +420,6 @@ void DestroyableComponent::AddFaction(const int32_t factionID, const bool ignore | ||||
| } | ||||
|  | ||||
| bool DestroyableComponent::IsEnemy(const Entity* other) const { | ||||
| 	if (IsEnemyImplentation.ExecuteWithDefault(other, false)) return true; | ||||
| 	if (m_Parent->IsPlayer() && other->IsPlayer()) { | ||||
| 		auto* thisCharacterComponent = m_Parent->GetComponent<CharacterComponent>(); | ||||
| 		if (!thisCharacterComponent) return false; | ||||
| @@ -444,7 +442,6 @@ bool DestroyableComponent::IsEnemy(const Entity* other) const { | ||||
| } | ||||
|  | ||||
| bool DestroyableComponent::IsFriend(const Entity* other) const { | ||||
| 	if (IsFriendImplentation.ExecuteWithDefault(other, false)) return true; | ||||
| 	const auto* otherDestroyableComponent = other->GetComponent<DestroyableComponent>(); | ||||
| 	if (otherDestroyableComponent != nullptr) { | ||||
| 		for (const auto enemyFaction : m_EnemyFactionIDs) { | ||||
| @@ -575,6 +572,8 @@ void DestroyableComponent::Damage(uint32_t damage, const LWOOBJID source, uint32 | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	OnDamageCalculation(m_Parent, source, skillID, damage); | ||||
|  | ||||
| 	// If this entity has damage reduction, reduce the damage to a minimum of 1 | ||||
| 	if (m_DamageReduction > 0 && damage > 0) { | ||||
| 		if (damage > m_DamageReduction) { | ||||
|   | ||||
| @@ -7,7 +7,7 @@ | ||||
| #include "Entity.h" | ||||
| #include "Component.h" | ||||
| #include "eReplicaComponentType.h" | ||||
| #include "Implementation.h" | ||||
| #include "Observable.h" | ||||
|  | ||||
| namespace CppScripts { | ||||
| 	class Script; | ||||
| @@ -464,8 +464,8 @@ public: | ||||
| 	// handle hardcode mode drops | ||||
| 	void DoHardcoreModeDrops(const LWOOBJID source); | ||||
|  | ||||
| 	static Implementation<bool, const Entity*> IsEnemyImplentation; | ||||
| 	static Implementation<bool, const Entity*> IsFriendImplentation; | ||||
| 	// Damaged entity, offender, skillID, damage | ||||
| 	static Observable<Entity*, LWOOBJID, uint32_t, uint32_t&> OnDamageCalculation; | ||||
|  | ||||
| private: | ||||
| 	/** | ||||
|   | ||||
| @@ -38,6 +38,12 @@ | ||||
| #include "CDObjectSkillsTable.h" | ||||
| #include "CDSkillBehaviorTable.h" | ||||
|  | ||||
| Observable<InventoryComponent*, Item*> InventoryComponent::OnItemCreated; | ||||
| Observable<InventoryComponent*, Item*> InventoryComponent::OnItemDestroyed; | ||||
| Observable<InventoryComponent*, Item*> InventoryComponent::OnItemEquipped; | ||||
| Observable<InventoryComponent*, Item*> InventoryComponent::OnItemUnequipped; | ||||
| Observable<InventoryComponent*, Item*> InventoryComponent::OnItemLoaded; | ||||
|  | ||||
| InventoryComponent::InventoryComponent(Entity* parent) : Component(parent) { | ||||
| 	this->m_Dirty = true; | ||||
| 	this->m_Equipped = {}; | ||||
| @@ -287,6 +293,8 @@ void InventoryComponent::AddItem( | ||||
| 		} | ||||
| 		auto* item = new Item(lot, inventory, slot, size, {}, parent, showFlyingLoot, isModMoveAndEquip, subKey, false, lootSourceType); | ||||
|  | ||||
| 		OnItemCreated(this, item); | ||||
|  | ||||
| 		isModMoveAndEquip = false; | ||||
| 	} | ||||
|  | ||||
| @@ -571,6 +579,8 @@ void InventoryComponent::LoadXml(const tinyxml2::XMLDocument& document) { | ||||
| 			} | ||||
|  | ||||
| 			itemElement = itemElement->NextSiblingElement(); | ||||
|  | ||||
| 			OnItemLoaded(this, item); | ||||
| 		} | ||||
|  | ||||
| 		bag = bag->NextSiblingElement(); | ||||
| @@ -849,6 +859,8 @@ void InventoryComponent::EquipItem(Item* item, const bool skipChecks) { | ||||
|  | ||||
| 	EquipScripts(item); | ||||
|  | ||||
| 	OnItemEquipped(this, item); | ||||
|  | ||||
| 	Game::entityManager->SerializeEntity(m_Parent); | ||||
| } | ||||
|  | ||||
| @@ -879,6 +891,8 @@ void InventoryComponent::UnEquipItem(Item* item) { | ||||
|  | ||||
| 	UnequipScripts(item); | ||||
|  | ||||
| 	OnItemUnequipped(this, item); | ||||
|  | ||||
| 	Game::entityManager->SerializeEntity(m_Parent); | ||||
|  | ||||
| 	// Trigger property event | ||||
|   | ||||
| @@ -22,6 +22,7 @@ | ||||
| #include "eInventoryType.h" | ||||
| #include "eReplicaComponentType.h" | ||||
| #include "eLootSourceType.h" | ||||
| #include "Observable.h" | ||||
|  | ||||
| class Entity; | ||||
| class ItemSet; | ||||
| @@ -374,6 +375,12 @@ public: | ||||
|  | ||||
| 	~InventoryComponent() override; | ||||
|  | ||||
| 	static Observable<InventoryComponent*, Item*> OnItemCreated; | ||||
| 	static Observable<InventoryComponent*, Item*> OnItemDestroyed; | ||||
| 	static Observable<InventoryComponent*, Item*> OnItemEquipped; | ||||
| 	static Observable<InventoryComponent*, Item*> OnItemUnequipped; | ||||
| 	static Observable<InventoryComponent*, Item*> OnItemLoaded; | ||||
|  | ||||
| private: | ||||
| 	/** | ||||
| 	 * All the inventory this entity possesses | ||||
|   | ||||
| @@ -212,6 +212,8 @@ void Inventory::AddManagedItem(Item* item) { | ||||
| 	items.insert_or_assign(id, item); | ||||
|  | ||||
| 	free--; | ||||
|  | ||||
| 	component->OnItemLoaded(component, item); | ||||
| } | ||||
|  | ||||
| void Inventory::RemoveManagedItem(Item* item) { | ||||
| @@ -226,6 +228,8 @@ void Inventory::RemoveManagedItem(Item* item) { | ||||
| 	items.erase(id); | ||||
|  | ||||
| 	free++; | ||||
|  | ||||
| 	component->OnItemDestroyed(component, item); | ||||
| } | ||||
|  | ||||
| eInventoryType Inventory::FindInventoryTypeForLot(const LOT lot) { | ||||
|   | ||||
							
								
								
									
										24765
									
								
								dGame/json.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24765
									
								
								dGame/json.hpp
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -81,6 +81,8 @@ | ||||
| #include "eLoginResponse.h" | ||||
| #include "SlashCommandHandler.h" | ||||
|  | ||||
| #include "NejlikaHooks.h" | ||||
|  | ||||
| namespace Game { | ||||
| 	Logger* logger = nullptr; | ||||
| 	dServer* server = nullptr; | ||||
| @@ -315,6 +317,8 @@ int main(int argc, char** argv) { | ||||
| 	// Register slash commands if not in zone 0 | ||||
| 	if (zoneID != 0) SlashCommandHandler::Startup(); | ||||
|  | ||||
| 	nejlika::NejlikaHooks::InstallHooks(); | ||||
|  | ||||
| 	Game::logger->Flush(); // once immediately before the main loop | ||||
| 	while (true) { | ||||
| 		Metrics::StartMeasurement(MetricVariable::Frame); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 wincent
					wincent