mirror of
				https://github.com/DarkflameUniverse/DarkflameServer.git
				synced 2025-11-03 22:21:59 +00:00 
			
		
		
		
	Updated how skills and upgrades work, more gui stuff
This commit is contained in:
		@@ -33,7 +33,7 @@ float nejlika::AdditionalEntityData::CalculateModifier(ModifierType type, Modifi
 | 
			
		||||
	return total;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float nejlika::AdditionalEntityData::CalculateModifier(ModifierType type, std::vector<ModifierInstance>& additionalModifiers, ModifierOperator op, bool resistance) const {
 | 
			
		||||
float nejlika::AdditionalEntityData::CalculateModifier(ModifierType type, const std::vector<ModifierInstance>& additionalModifiers, ModifierOperator op, bool resistance) const {
 | 
			
		||||
	float total = 0;
 | 
			
		||||
 | 
			
		||||
	for (const auto& modifier : additionalModifiers) {
 | 
			
		||||
@@ -71,8 +71,6 @@ float nejlika::AdditionalEntityData::CalculateModifier(ModifierType type, int32_
 | 
			
		||||
 | 
			
		||||
	float multiplicative = CalculateModifier(type, ModifierOperator::Multiplicative, false);
 | 
			
		||||
 | 
			
		||||
	std::cout << "Scaler: " << scaler << " Additive: " << additive << " Multiplicative: " << multiplicative << std::endl;
 | 
			
		||||
 | 
			
		||||
	return (scaler + additive) * (1 + multiplicative / 100);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -80,7 +78,7 @@ 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
 | 
			
		||||
float nejlika::AdditionalEntityData::CalculateFinalModifier(ModifierType type, const std::vector<ModifierInstance>& additionalModifiers, int32_t level) const
 | 
			
		||||
{
 | 
			
		||||
	const auto templateDataOpt = NejlikaData::GetEntityTemplate(lot);
 | 
			
		||||
 | 
			
		||||
@@ -106,6 +104,42 @@ float nejlika::AdditionalEntityData::CalculateModifier(ModifierType type, std::v
 | 
			
		||||
		ModifierType::Lightning
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	if (type == ModifierType::Health) {
 | 
			
		||||
		if (lot == 1) additive += 25;
 | 
			
		||||
		additive += CalculateModifier(ModifierType::Physique, additionalModifiers, ModifierOperator::Additive, false) * 2.5f;
 | 
			
		||||
		additive += CalculateModifier(ModifierType::Cunning, additionalModifiers, ModifierOperator::Additive, false) * 1.0f;
 | 
			
		||||
		additive += CalculateModifier(ModifierType::Spirit, additionalModifiers, ModifierOperator::Additive, false) * 1.0f;
 | 
			
		||||
	}
 | 
			
		||||
	else if (type == ModifierType::Imagination) {
 | 
			
		||||
		additive += CalculateModifier(ModifierType::Spirit, additionalModifiers, ModifierOperator::Additive, false) * 2.0f;
 | 
			
		||||
	}
 | 
			
		||||
	else if (type == ModifierType::Seperation || type == ModifierType::InternalDisassembly || type == ModifierType::Physical) {
 | 
			
		||||
		multiplicative += CalculateModifier(ModifierType::Cunning, additionalModifiers, ModifierOperator::Additive, false) * 0.33f;
 | 
			
		||||
	}
 | 
			
		||||
	else if (type == ModifierType::Pierce) {
 | 
			
		||||
		multiplicative += CalculateModifier(ModifierType::Cunning, additionalModifiers, ModifierOperator::Additive, false) * 0.285f;
 | 
			
		||||
	}
 | 
			
		||||
	else if (nejlika::IsOverTimeType(type) || nejlika::IsNormalDamageType(type)) {
 | 
			
		||||
		multiplicative += CalculateModifier(ModifierType::Spirit, additionalModifiers, ModifierOperator::Additive, false) * 0.33f;
 | 
			
		||||
	}
 | 
			
		||||
	else if (type == ModifierType::ImaginationRegen) {
 | 
			
		||||
		const auto spirit = CalculateModifier(ModifierType::Spirit, additionalModifiers, ModifierOperator::Additive, false);
 | 
			
		||||
		additive += spirit * 0.01f + 1;
 | 
			
		||||
		multiplicative += spirit * 0.25f;
 | 
			
		||||
	}
 | 
			
		||||
	else if (type == ModifierType::HealthRegen) {
 | 
			
		||||
		const auto physique = CalculateModifier(ModifierType::Physique, additionalModifiers, ModifierOperator::Additive, false);
 | 
			
		||||
		additive += physique * 0.04f;
 | 
			
		||||
	}
 | 
			
		||||
	else if (type == ModifierType::Defensive) {
 | 
			
		||||
		additive += CalculateModifier(ModifierType::Physique, additionalModifiers, ModifierOperator::Additive, false) * 0.5f;
 | 
			
		||||
		if (lot == 1) additive += level * 10;
 | 
			
		||||
	}
 | 
			
		||||
	else if (type == ModifierType::Offensive) {
 | 
			
		||||
		additive += CalculateModifier(ModifierType::Cunning, additionalModifiers, ModifierOperator::Additive, false) * 0.5f;
 | 
			
		||||
		if (lot == 1) additive += level * 10;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (elementalDamage.contains(type)) {
 | 
			
		||||
		additive += CalculateModifier(ModifierType::Elemental, additionalModifiers, ModifierOperator::Additive, false) / elementalDamage.size();
 | 
			
		||||
		multiplicative += CalculateModifier(ModifierType::Elemental, additionalModifiers, ModifierOperator::Multiplicative, false) / elementalDamage.size();
 | 
			
		||||
@@ -118,8 +152,6 @@ float nejlika::AdditionalEntityData::CalculateModifier(ModifierType type, std::v
 | 
			
		||||
 | 
			
		||||
	float total = (scaler + additive) * (1 + multiplicative / 100);
 | 
			
		||||
 | 
			
		||||
	std::cout << "Scaler: " << scaler << " Additive: " << additive << " Multiplicative: " << multiplicative << " Total: " << total << std::endl;
 | 
			
		||||
 | 
			
		||||
	return total;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -132,7 +164,7 @@ float nejlika::AdditionalEntityData::CalculateResistance(ModifierType type) cons
 | 
			
		||||
 | 
			
		||||
float nejlika::AdditionalEntityData::CalculateMultiplier(ModifierType type) const
 | 
			
		||||
{
 | 
			
		||||
	return 1 + CalculateModifier(type, ModifierOperator::Multiplicative, false);
 | 
			
		||||
	return 100 + CalculateModifier(type, ModifierOperator::Multiplicative, false);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<ModifierInstance> nejlika::AdditionalEntityData::TriggerUpgradeItems(UpgradeTriggerType triggerType, const TriggerParameters& params) {
 | 
			
		||||
@@ -270,6 +302,53 @@ void nejlika::AdditionalEntityData::RemoveSkills(LOT lot) {
 | 
			
		||||
	upgradeData.RemoveSkills(id);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<ModifierInstance> nejlika::AdditionalEntityData::CalculateMainWeaponDamage() {
 | 
			
		||||
	auto* entity = Game::entityManager->GetEntity(id);
 | 
			
		||||
 | 
			
		||||
	if (entity == nullptr) {
 | 
			
		||||
		return {};
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	auto* inventoryComponent = entity->GetComponent<InventoryComponent>();
 | 
			
		||||
 | 
			
		||||
	if (inventoryComponent == nullptr) {
 | 
			
		||||
		return {};
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Item* mainWeapon = nullptr;
 | 
			
		||||
 | 
			
		||||
	for (const auto& [location, item] : inventoryComponent->GetEquippedItems()) {
 | 
			
		||||
		if (location == "special_r") {
 | 
			
		||||
			mainWeapon = inventoryComponent->FindItemById(item.id);
 | 
			
		||||
			break;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (mainWeapon == nullptr) {
 | 
			
		||||
		return {};
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const auto additionalItemDataOpt = NejlikaData::GetAdditionalItemData(mainWeapon->GetId());
 | 
			
		||||
 | 
			
		||||
	if (!additionalItemDataOpt.has_value()) {
 | 
			
		||||
		return {};
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const auto& additionalItemData = *additionalItemDataOpt.value();
 | 
			
		||||
 | 
			
		||||
	std::vector<ModifierInstance> result;
 | 
			
		||||
 | 
			
		||||
	for (const auto& modifier : additionalItemData.GetModifierInstances()) {
 | 
			
		||||
		if (nejlika::IsNormalDamageType(modifier.GetType()) || nejlika::IsOverTimeType(modifier.GetType()) || nejlika::IsDurationType(modifier.GetType())) {
 | 
			
		||||
			if (modifier.GetOperator() == ModifierOperator::Additive && modifier.GetUpgradeName().empty()) {
 | 
			
		||||
				result.push_back(modifier);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void nejlika::AdditionalEntityData::RollStandardModifiers(int32_t level) {
 | 
			
		||||
	standardModifiers.clear();
 | 
			
		||||
 | 
			
		||||
@@ -296,8 +375,40 @@ void nejlika::AdditionalEntityData::RollStandardModifiers(int32_t level) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float nejlika::AdditionalEntityData::CalculateMultiplier(ModifierType type, std::vector<ModifierInstance>& additionalModifiers) const {
 | 
			
		||||
    return 1 + CalculateModifier(type, additionalModifiers, ModifierOperator::Multiplicative, false);
 | 
			
		||||
void nejlika::AdditionalEntityData::TriggerPassiveRegeneration() {
 | 
			
		||||
	auto* entity = Game::entityManager->GetEntity(id);
 | 
			
		||||
 | 
			
		||||
	if (entity == nullptr) {
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	auto* destroyable = entity->GetComponent<DestroyableComponent>();
 | 
			
		||||
 | 
			
		||||
	if (destroyable == nullptr) {
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const auto healthRegen = CalculateFinalModifier(ModifierType::HealthRegen, {}, level);
 | 
			
		||||
	const auto imaginationRegen = CalculateFinalModifier(ModifierType::ImaginationRegen, {}, level);
 | 
			
		||||
 | 
			
		||||
	if (healthRegen > 0) {
 | 
			
		||||
		destroyable->SetHealth(std::min(destroyable->GetHealth() + static_cast<int32_t>(healthRegen), static_cast<int32_t>(destroyable->GetMaxHealth())));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (imaginationRegen > 0) {
 | 
			
		||||
		destroyable->SetImagination(std::min(destroyable->GetImagination() + static_cast<int32_t>(imaginationRegen), static_cast<int32_t>(destroyable->GetMaxImagination())));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Game::entityManager->SerializeEntity(entity);
 | 
			
		||||
 | 
			
		||||
	// Trigger it again in 1 second
 | 
			
		||||
	entity->AddCallbackTimer(1.0f, [this]() {
 | 
			
		||||
		TriggerPassiveRegeneration();
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float nejlika::AdditionalEntityData::CalculateMultiplier(ModifierType type, const std::vector<ModifierInstance>& additionalModifiers) const {
 | 
			
		||||
    return 100 + CalculateModifier(type, additionalModifiers, ModifierOperator::Multiplicative, false);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::unordered_map<ModifierType, std::unordered_map<ModifierType, float>> nejlika::AdditionalEntityData::CalculateDamageConversion(std::vector<ModifierInstance>& additionalModifiers) const {
 | 
			
		||||
@@ -402,7 +513,15 @@ void nejlika::AdditionalEntityData::ApplyToEntity() {
 | 
			
		||||
 | 
			
		||||
			const auto& itemModifiers = itemData.GetModifierInstances();
 | 
			
		||||
 | 
			
		||||
			activeModifiers.insert(activeModifiers.end(), itemModifiers.begin(), itemModifiers.end());
 | 
			
		||||
			for (const auto& modifier : itemModifiers) {
 | 
			
		||||
				if (nejlika::IsNormalDamageType(modifier.GetType()) || nejlika::IsOverTimeType(modifier.GetType()) || nejlika::IsDurationType(modifier.GetType())) {
 | 
			
		||||
					if (modifier.GetOperator() == ModifierOperator::Additive && modifier.GetUpgradeName().empty()) {
 | 
			
		||||
						continue;
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				activeModifiers.push_back(modifier);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for (const auto& upgradeItem : upgradeItems) {
 | 
			
		||||
@@ -431,11 +550,11 @@ void nejlika::AdditionalEntityData::ApplyToEntity() {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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)));
 | 
			
		||||
	}
 | 
			
		||||
	destroyable->SetMaxHealth(static_cast<int32_t>(CalculateFinalModifier(ModifierType::Health, {}, level)));
 | 
			
		||||
	destroyable->SetMaxArmor(static_cast<int32_t>(CalculateFinalModifier(ModifierType::Armor, {}, level)));
 | 
			
		||||
	//if (!entity->IsPlayer()) {
 | 
			
		||||
		destroyable->SetMaxImagination(static_cast<int32_t>(CalculateFinalModifier(ModifierType::Imagination, {}, level)));
 | 
			
		||||
	//}
 | 
			
		||||
 | 
			
		||||
	if (initialized) {
 | 
			
		||||
		return;
 | 
			
		||||
@@ -444,15 +563,17 @@ void nejlika::AdditionalEntityData::ApplyToEntity() {
 | 
			
		||||
	destroyable->SetHealth(destroyable->GetMaxHealth());
 | 
			
		||||
	destroyable->SetArmor(destroyable->GetMaxArmor());
 | 
			
		||||
 | 
			
		||||
	if (!entity->IsPlayer()) {
 | 
			
		||||
	//if (!entity->IsPlayer()) {
 | 
			
		||||
		destroyable->SetImagination(destroyable->GetMaxImagination());
 | 
			
		||||
	}
 | 
			
		||||
	//}
 | 
			
		||||
 | 
			
		||||
	if (entity->IsPlayer()) {
 | 
			
		||||
		auto* controllablePhysicsComponent = entity->GetComponent<ControllablePhysicsComponent>();
 | 
			
		||||
		if (controllablePhysicsComponent) controllablePhysicsComponent->SetSpeedMultiplier(CalculateMultiplier(ModifierType::Speed));
 | 
			
		||||
		if (controllablePhysicsComponent) controllablePhysicsComponent->SetSpeedMultiplier(CalculateMultiplier(ModifierType::Speed) / 100.0f);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	TriggerPassiveRegeneration();
 | 
			
		||||
 | 
			
		||||
	initialized = true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -24,18 +24,18 @@ public:
 | 
			
		||||
 | 
			
		||||
	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, const 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 CalculateFinalModifier(ModifierType type, const std::vector<ModifierInstance>& additionalModifiers, int32_t level) const;
 | 
			
		||||
 | 
			
		||||
	float CalculateResistance(ModifierType type) const;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * @brief Calculate the multiplier for a given modifier type. With a base value of 1.0.
 | 
			
		||||
	 * @brief Calculate the multiplier for a given modifier type. With a base value of 100 (%).
 | 
			
		||||
	 * 
 | 
			
		||||
	 * @param type The modifier type.
 | 
			
		||||
	 * @return The multiplier.
 | 
			
		||||
@@ -43,13 +43,13 @@ public:
 | 
			
		||||
	float CalculateMultiplier(ModifierType type) const;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * @brief Calculate the multiplier for a given modifier type. With a base value of 1.0.
 | 
			
		||||
	 * @brief Calculate the multiplier for a given modifier type. With a base value of 100 (%).
 | 
			
		||||
	 * 
 | 
			
		||||
	 * @param type The modifier type.
 | 
			
		||||
	 * @param additionalModifiers Additional modifiers to apply.
 | 
			
		||||
	 * @return The multiplier.
 | 
			
		||||
	 */
 | 
			
		||||
	float CalculateMultiplier(ModifierType type, std::vector<ModifierInstance>& additionalModifiers) const;
 | 
			
		||||
	float CalculateMultiplier(ModifierType type, const std::vector<ModifierInstance>& additionalModifiers) const;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * @brief Calculate damage conversation mapping.
 | 
			
		||||
@@ -85,9 +85,15 @@ public:
 | 
			
		||||
	void AddSkills(LWOOBJID item);
 | 
			
		||||
	void RemoveSkills(LOT lot);
 | 
			
		||||
 | 
			
		||||
	const std::vector<ModifierInstance>& GetActiveModifiers() const { return activeModifiers; }
 | 
			
		||||
 | 
			
		||||
	std::vector<ModifierInstance> CalculateMainWeaponDamage();
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	void RollStandardModifiers(int32_t level);
 | 
			
		||||
 | 
			
		||||
	void TriggerPassiveRegeneration();
 | 
			
		||||
 | 
			
		||||
	bool initialized = false;
 | 
			
		||||
 | 
			
		||||
	std::vector<ModifierInstance> standardModifiers;
 | 
			
		||||
 
 | 
			
		||||
@@ -210,18 +210,31 @@ void nejlika::AdditionalItemData::RollModifiers(Item* item, int32_t level) {
 | 
			
		||||
 | 
			
		||||
	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());
 | 
			
		||||
	});
 | 
			
		||||
	std::vector<const ModifierNameTemplate*> availableObjects;
 | 
			
		||||
 | 
			
		||||
	if (itemTemplateIt != itemTemplateVec.end()) {
 | 
			
		||||
		const auto& itemTemplate = *itemTemplateIt;
 | 
			
		||||
	for (const auto& itemTemplate : itemTemplateVec) {
 | 
			
		||||
		if (itemTemplate.GetMinLevel() > level || itemTemplate.GetMaxLevel() < level) {
 | 
			
		||||
			continue;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		const auto itemModifiers = itemTemplate.GenerateModifiers(level);
 | 
			
		||||
		if (itemTemplate.GetLOT() != static_cast<int32_t>(item->GetLot())) {
 | 
			
		||||
			continue;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		modifierInstances.insert(modifierInstances.end(), itemModifiers.begin(), itemModifiers.end());
 | 
			
		||||
		availableObjects.push_back(&itemTemplate);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (availableObjects.empty()) {
 | 
			
		||||
		Save(item);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const auto& itemTemplate = *availableObjects[GeneralUtils::GenerateRandomNumber<uint32_t>() % availableObjects.size()];
 | 
			
		||||
 | 
			
		||||
	const auto itemModifiers = itemTemplate.GenerateModifiers(level);
 | 
			
		||||
 | 
			
		||||
	modifierInstances.insert(modifierInstances.end(), itemModifiers.begin(), itemModifiers.end());
 | 
			
		||||
 | 
			
		||||
	Save(item);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -22,6 +22,7 @@ set(DGAME_SOURCES "Character.cpp"
 | 
			
		||||
		"UpgradeTemplate.cpp"
 | 
			
		||||
		"UpgradeEffect.cpp"
 | 
			
		||||
		"Lookup.cpp"
 | 
			
		||||
		"NejlikaHelpers.cpp"
 | 
			
		||||
		)
 | 
			
		||||
 | 
			
		||||
include_directories(
 | 
			
		||||
 
 | 
			
		||||
@@ -67,7 +67,8 @@ std::vector<nejlika::ModifierInstance> nejlika::EntityTemplate::GenerateModifier
 | 
			
		||||
			ModifierCategory::Player,
 | 
			
		||||
			0,
 | 
			
		||||
			"",
 | 
			
		||||
			ModifierType::Invalid
 | 
			
		||||
			ModifierType::Invalid,
 | 
			
		||||
			""
 | 
			
		||||
		);
 | 
			
		||||
 | 
			
		||||
		modifiers.push_back(modifier);
 | 
			
		||||
 
 | 
			
		||||
@@ -51,11 +51,26 @@ std::string nejlika::ModifierInstance::GenerateHtmlString(const std::vector<Modi
 | 
			
		||||
	// target -> resistance -> op -> type -> value
 | 
			
		||||
	std::unordered_map<ModifierCategory, std::unordered_map<bool, std::unordered_map<ModifierOperator, std::unordered_map<ModifierType, float>>>> modifierMap;
 | 
			
		||||
 | 
			
		||||
	bool hasConvertTo = false;
 | 
			
		||||
	bool hasSkillModifier = false;
 | 
			
		||||
	
 | 
			
		||||
	for (const auto& modifier : modifiers) {
 | 
			
		||||
		if (modifier.type == ModifierType::Invalid) {
 | 
			
		||||
			continue;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (modifier.GetConvertTo() != ModifierType::Invalid)
 | 
			
		||||
		{
 | 
			
		||||
			hasConvertTo = true;
 | 
			
		||||
			continue;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (!modifier.GetUpgradeName().empty())
 | 
			
		||||
		{
 | 
			
		||||
			hasSkillModifier = true;
 | 
			
		||||
			continue;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		modifierMap[modifier.category][modifier.isResistance][modifier.op][modifier.type] = modifier.value;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -67,32 +82,89 @@ std::string nejlika::ModifierInstance::GenerateHtmlString(const std::vector<Modi
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		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 << "<font color=\"#FFFFFF\">";
 | 
			
		||||
					
 | 
			
		||||
					ss << magic_enum::enum_name<ModifierType>(modifier.first) << ": ";
 | 
			
		||||
					ss << ((modifier.second > 0) ? (math.first == ModifierOperator::Multiplicative ? "+" : "") : "-");
 | 
			
		||||
					
 | 
			
		||||
					ss << ((modifier.second > 0) ? "+" : "-");
 | 
			
		||||
					
 | 
			
		||||
					ss << std::fixed << std::setprecision(0) << std::abs(modifier.second);
 | 
			
		||||
					ss << std::fixed << std::setprecision(1) << std::abs(modifier.second);
 | 
			
		||||
 | 
			
		||||
					if (math.first == ModifierOperator::Multiplicative) {
 | 
			
		||||
						ss << "%";
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					ss << "</font> <font color=\"#D0AB62\">";
 | 
			
		||||
 | 
			
		||||
					ss << " " << nejlika::GetModifierTypeName(modifier.first);	
 | 
			
		||||
					
 | 
			
		||||
					if (resistance.first) {
 | 
			
		||||
						// If the ss now ends with 'Damage' remove it
 | 
			
		||||
						if (ss.str().substr(ss.str().size() - 7) == " Damage") {
 | 
			
		||||
							ss.seekp(-7, std::ios_base::end);
 | 
			
		||||
						}
 | 
			
		||||
 | 
			
		||||
						ss << " " << "Resistance";
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					ss << "</font>\n";
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	if (hasSkillModifier)
 | 
			
		||||
	{
 | 
			
		||||
		for (const auto& modifier : modifiers) {
 | 
			
		||||
			if (modifier.type != ModifierType::SkillModifier) {
 | 
			
		||||
				continue;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			ss << "<font color=\"" << GetModifierTypeColor(modifier.type) << "\">";
 | 
			
		||||
			
 | 
			
		||||
			ss << ((modifier.value > 0) ? "+" : "-");
 | 
			
		||||
			
 | 
			
		||||
			ss << std::fixed << std::setprecision(0) << std::abs(modifier.value);
 | 
			
		||||
 | 
			
		||||
			ss << " to ";
 | 
			
		||||
 | 
			
		||||
			ss << modifier.GetUpgradeName();
 | 
			
		||||
 | 
			
		||||
			ss << "</font>\n";
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (hasConvertTo)
 | 
			
		||||
	{
 | 
			
		||||
		for (const auto& modifier : modifiers) {
 | 
			
		||||
			if (modifier.GetConvertTo() == ModifierType::Invalid)
 | 
			
		||||
			{
 | 
			
		||||
				continue;
 | 
			
		||||
			}
 | 
			
		||||
			
 | 
			
		||||
			if (modifier.type == ModifierType::Invalid) {
 | 
			
		||||
				continue;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			ss << "<font color=\"#FFFFFF\">";
 | 
			
		||||
			
 | 
			
		||||
			// +xx/yy% of T1 converted to T2
 | 
			
		||||
			ss << ((modifier.value > 0) ? "" : "-");
 | 
			
		||||
 | 
			
		||||
			ss << std::fixed << std::setprecision(0) << std::abs(modifier.value);
 | 
			
		||||
 | 
			
		||||
			ss << "%</font> <font color=\"#D0AB62\">";
 | 
			
		||||
 | 
			
		||||
			ss << " of ";
 | 
			
		||||
 | 
			
		||||
			ss << nejlika::GetModifierTypeName(modifier.type);
 | 
			
		||||
 | 
			
		||||
			ss << " converted to ";
 | 
			
		||||
 | 
			
		||||
			ss << nejlika::GetModifierTypeName(modifier.GetConvertTo());
 | 
			
		||||
 | 
			
		||||
			ss << "</font>\n";
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return ss.str();
 | 
			
		||||
}
 | 
			
		||||
@@ -16,8 +16,11 @@ class ModifierInstance
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
	ModifierInstance(
 | 
			
		||||
		ModifierType type, float value, ModifierOperator op, bool isResistance, ModifierCategory category, uint32_t effectID, const std::string& effectType, ModifierType convertTo
 | 
			
		||||
	) : type(type), value(value), op(op), isResistance(isResistance), category(category), effectID(effectID), effectType(effectType), convertTo(convertTo) {}
 | 
			
		||||
		ModifierType type, float value, ModifierOperator op, bool isResistance, ModifierCategory category, uint32_t effectID, const std::string& effectType, ModifierType convertTo,
 | 
			
		||||
		const std::string& upgradeName
 | 
			
		||||
	) : type(type), value(value), op(op), isResistance(isResistance), category(category), effectID(effectID), effectType(effectType), convertTo(convertTo),
 | 
			
		||||
		upgradeName(upgradeName)
 | 
			
		||||
	{}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * @brief Construct a new Modifier Instance object from a json configuration.
 | 
			
		||||
@@ -59,6 +62,8 @@ public:
 | 
			
		||||
 | 
			
		||||
	std::string GetEffectType() const { return effectType; }
 | 
			
		||||
 | 
			
		||||
	std::string GetUpgradeName() const { return upgradeName; }
 | 
			
		||||
 | 
			
		||||
	void SetType(ModifierType type) { this->type = type; }
 | 
			
		||||
 | 
			
		||||
	void SetConvertTo(ModifierType convertTo) { this->convertTo = convertTo; }
 | 
			
		||||
@@ -75,6 +80,8 @@ public:
 | 
			
		||||
 | 
			
		||||
	void SetEffectType(const std::string& effectType) { this->effectType = effectType; }
 | 
			
		||||
 | 
			
		||||
	void SetUpgradeName(const std::string& upgradeName) { this->upgradeName = upgradeName; }
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	ModifierType type;
 | 
			
		||||
	ModifierType convertTo;
 | 
			
		||||
@@ -84,6 +91,7 @@ private:
 | 
			
		||||
	ModifierCategory category;
 | 
			
		||||
	uint32_t effectID;
 | 
			
		||||
	std::string effectType;
 | 
			
		||||
	std::string upgradeName;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -57,7 +57,14 @@ nejlika::ModifierNameTemplate::ModifierNameTemplate(const nlohmann::json & json)
 | 
			
		||||
	{
 | 
			
		||||
		for (const auto& modifier : json["modifiers"])
 | 
			
		||||
		{
 | 
			
		||||
			modifiers.push_back(ModifierTemplate(modifier));
 | 
			
		||||
			const auto modifierTemplate = ModifierTemplate(modifier);
 | 
			
		||||
 | 
			
		||||
			if (modifierTemplate.GetTypes().empty() || modifierTemplate.GetTypes().at(0) == ModifierType::Invalid)
 | 
			
		||||
			{
 | 
			
		||||
				continue;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			modifiers.push_back(modifierTemplate);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -68,6 +75,10 @@ nejlika::ModifierNameTemplate::ModifierNameTemplate(const nlohmann::json & json)
 | 
			
		||||
		if (levels.contains("min"))
 | 
			
		||||
		{
 | 
			
		||||
			minLevel = levels["min"].get<int32_t>();
 | 
			
		||||
 | 
			
		||||
			const auto rasio = 1; //45.0f / 85.0f;
 | 
			
		||||
 | 
			
		||||
			minLevel = std::max(1, static_cast<int32_t>(minLevel * rasio));
 | 
			
		||||
		}
 | 
			
		||||
		else
 | 
			
		||||
		{
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,10 @@ nejlika::ModifierScale::ModifierScale(const nlohmann::json & json)
 | 
			
		||||
{
 | 
			
		||||
	level = json["level"].get<int32_t>();
 | 
			
		||||
 | 
			
		||||
	const auto rasio = 1; //45.0f / 85.0f;
 | 
			
		||||
 | 
			
		||||
	level = std::max(1, static_cast<int32_t>(level * rasio));
 | 
			
		||||
 | 
			
		||||
	if (json.contains("min")) {
 | 
			
		||||
		min = json["min"].get<float>();
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,8 @@
 | 
			
		||||
#include <magic_enum.hpp>
 | 
			
		||||
#include <random>
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
#include <sstream>
 | 
			
		||||
#include <iostream>
 | 
			
		||||
 | 
			
		||||
using namespace nejlika;
 | 
			
		||||
 | 
			
		||||
@@ -133,6 +135,11 @@ nejlika::ModifierTemplate::ModifierTemplate(const nlohmann::json& config) {
 | 
			
		||||
			category = ModifierCategory::Player;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (config.contains("skill"))
 | 
			
		||||
	{
 | 
			
		||||
		upgradeName = config["skill"].get<std::string>();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
nlohmann::json nejlika::ModifierTemplate::ToJson() const {
 | 
			
		||||
@@ -191,6 +198,12 @@ nlohmann::json nejlika::ModifierTemplate::ToJson() const {
 | 
			
		||||
	config["resistance"] = isResistance;
 | 
			
		||||
	config["effect-id"] = effectID;
 | 
			
		||||
	config["effect-type"] = effectType;
 | 
			
		||||
	config["operator"] = magic_enum::enum_name(operatorType);
 | 
			
		||||
 | 
			
		||||
	if (!upgradeName.empty())
 | 
			
		||||
	{
 | 
			
		||||
		config["skill"] = upgradeName;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return config;
 | 
			
		||||
}
 | 
			
		||||
@@ -249,9 +262,24 @@ std::string nejlika::ModifierTemplate::GenerateHtmlString(const std::vector<Modi
 | 
			
		||||
	// target -> resistance -> op -> type -> (min, max)
 | 
			
		||||
	std::unordered_map<ModifierCategory, std::unordered_map<bool, std::unordered_map<ModifierOperator, std::unordered_map<ModifierType, std::pair<float, float>>>>> modifierMap;
 | 
			
		||||
 | 
			
		||||
	bool hasConvertTo = false;
 | 
			
		||||
	bool hasSkillModifier = false;
 | 
			
		||||
 | 
			
		||||
	for (const auto& modifier : modifiers) {
 | 
			
		||||
		if (modifier.GetConvertTo() != ModifierType::Invalid)
 | 
			
		||||
		{
 | 
			
		||||
			hasConvertTo = true;
 | 
			
		||||
			continue;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (!modifier.GetUpgradeName().empty())
 | 
			
		||||
		{
 | 
			
		||||
			hasSkillModifier = true;
 | 
			
		||||
			continue;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for (const auto& type : modifier.types) {
 | 
			
		||||
			if (type == ModifierType::Invalid) {
 | 
			
		||||
			if (type == ModifierType::Invalid || type == ModifierType::SkillModifier) {
 | 
			
		||||
				continue;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
@@ -299,42 +327,128 @@ std::string nejlika::ModifierTemplate::GenerateHtmlString(const std::vector<Modi
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		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 << "<font color=\"#FFFFFF\">";
 | 
			
		||||
 | 
			
		||||
					ss << magic_enum::enum_name<ModifierType>(modifier.first) << ": ";
 | 
			
		||||
					ss << ((modifier.second.first > 0) ? (math.first == ModifierOperator::Multiplicative ? "+" : "") : "-");
 | 
			
		||||
					
 | 
			
		||||
					ss << ((modifier.second.first > 0) ? "+" : "-");
 | 
			
		||||
					
 | 
			
		||||
					ss << std::fixed << std::setprecision(0) << std::abs(modifier.second.first);
 | 
			
		||||
					ss << std::fixed << std::setprecision(1) << std::abs(modifier.second.first);
 | 
			
		||||
 | 
			
		||||
					if (modifier.second.first != modifier.second.second)
 | 
			
		||||
					{
 | 
			
		||||
						ss << "/";
 | 
			
		||||
 | 
			
		||||
						ss << ((modifier.second.second > 0) ? "+" : "-");
 | 
			
		||||
						
 | 
			
		||||
						ss << std::fixed << std::setprecision(0) << std::abs(modifier.second.second);
 | 
			
		||||
						ss << std::fixed << std::setprecision(1) << std::abs(modifier.second.second);
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					if (math.first == ModifierOperator::Multiplicative) {
 | 
			
		||||
						ss << "%";
 | 
			
		||||
					}
 | 
			
		||||
					
 | 
			
		||||
					ss << "</font> <font color=\"#D0AB62\">";
 | 
			
		||||
 | 
			
		||||
					ss << " " << nejlika::GetModifierTypeName(modifier.first);
 | 
			
		||||
					
 | 
			
		||||
					if (resistance.first) {
 | 
			
		||||
						// If the ss now ends with 'Damage' remove it
 | 
			
		||||
						if (ss.str().substr(ss.str().size() - 6) == "Damage") {
 | 
			
		||||
							ss.seekp(-6, std::ios_base::end);
 | 
			
		||||
						}
 | 
			
		||||
 | 
			
		||||
						ss << " " << "Resistance";
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					ss << "</font>\n";
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (hasSkillModifier)
 | 
			
		||||
	{
 | 
			
		||||
		for (const auto& modifier : modifiers) {
 | 
			
		||||
			for (const auto& type : modifier.types) {
 | 
			
		||||
				if (type != ModifierType::SkillModifier) {
 | 
			
		||||
					continue;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				const auto& scalors = modifier.GetScales();
 | 
			
		||||
 | 
			
		||||
				if (scalors.empty())
 | 
			
		||||
				{
 | 
			
		||||
					continue;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				const auto& m = scalors[0];
 | 
			
		||||
 | 
			
		||||
				ss << "<font color=\"" << GetModifierTypeColor(type) << "\">";
 | 
			
		||||
				
 | 
			
		||||
				ss << ((m.GetMin() > 0) ? "+" : "-");
 | 
			
		||||
				
 | 
			
		||||
				ss << std::fixed << std::setprecision(0) << std::abs(m.GetMin());
 | 
			
		||||
 | 
			
		||||
				ss << " to ";
 | 
			
		||||
 | 
			
		||||
				ss << modifier.GetUpgradeName();
 | 
			
		||||
 | 
			
		||||
				ss << "</font>\n";
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (hasConvertTo)
 | 
			
		||||
	{
 | 
			
		||||
		for (const auto& modifier : modifiers) {
 | 
			
		||||
			if (modifier.GetConvertTo() == ModifierType::Invalid)
 | 
			
		||||
			{
 | 
			
		||||
				continue;
 | 
			
		||||
			}
 | 
			
		||||
			
 | 
			
		||||
			for (const auto& type : modifier.types) {
 | 
			
		||||
				if (type == ModifierType::Invalid) {
 | 
			
		||||
					continue;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				const auto& scalors = modifier.GetScales();
 | 
			
		||||
 | 
			
		||||
				auto m = scalors[0];
 | 
			
		||||
 | 
			
		||||
				for (const auto& s : scalors) {
 | 
			
		||||
					if (s.GetLevel() <= level && s.GetLevel() > m.GetLevel()) {
 | 
			
		||||
						m = s;
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				ss << "<font color=\"#FFFFFF\">";
 | 
			
		||||
 | 
			
		||||
				// +xx/yy% of T1 converted to T2
 | 
			
		||||
				ss << ((m.GetMin() > 0) ? "" : "-");
 | 
			
		||||
 | 
			
		||||
				ss << std::fixed << std::setprecision(0) << std::abs(m.GetMin());
 | 
			
		||||
 | 
			
		||||
				if (m.GetMin() != m.GetMax())
 | 
			
		||||
				{
 | 
			
		||||
					ss << "/";
 | 
			
		||||
 | 
			
		||||
					ss << std::fixed << std::setprecision(0) << std::abs(m.GetMax());
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				ss << "%</font> <font color=\"#D0AB62\">";
 | 
			
		||||
 | 
			
		||||
				ss << " of ";
 | 
			
		||||
 | 
			
		||||
				ss << nejlika::GetModifierTypeName(type);
 | 
			
		||||
 | 
			
		||||
				ss << " converted to ";
 | 
			
		||||
 | 
			
		||||
				ss << nejlika::GetModifierTypeName(modifier.GetConvertTo());
 | 
			
		||||
 | 
			
		||||
				ss << "</font>\n";
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return ss.str();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -354,12 +468,13 @@ std::optional<ModifierInstance> nejlika::ModifierTemplate::GenerateModifier(Modi
 | 
			
		||||
			power++;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return ModifierInstance(type, value, operatorType, isResistance, category, effectID, effectType, convertTo);
 | 
			
		||||
		return ModifierInstance(type, value, operatorType, isResistance, category, effectID, effectType, convertTo, upgradeName);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 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()) {
 | 
			
		||||
		if ((s.GetLevel() <= level) && (s.GetLevel() > scale.GetLevel())) {
 | 
			
		||||
			std::cout << "Found scale: " << s.GetMin() << " - " << s.GetMax() << " for level " << s.GetLevel() << std::endl;
 | 
			
		||||
			scale = s;
 | 
			
		||||
			found = true;
 | 
			
		||||
		}
 | 
			
		||||
@@ -369,7 +484,18 @@ std::optional<ModifierInstance> nejlika::ModifierTemplate::GenerateModifier(Modi
 | 
			
		||||
		return std::nullopt;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	float value = GeneralUtils::GenerateRandomNumber<float>(scale.GetMin(), scale.GetMax());
 | 
			
		||||
	float value = 0;
 | 
			
		||||
	
 | 
			
		||||
	return ModifierInstance(type, value, operatorType, isResistance, category, effectID, effectType, convertTo);
 | 
			
		||||
	if (scale.GetMax() == scale.GetMin())
 | 
			
		||||
	{
 | 
			
		||||
		value = scale.GetMin();
 | 
			
		||||
	}
 | 
			
		||||
	else
 | 
			
		||||
	{
 | 
			
		||||
		value = (GeneralUtils::GenerateRandomNumber<uint32_t>(0, 100) / 100.0f) * (scale.GetMax() - scale.GetMin()) + scale.GetMin();
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	std::cout << "Generated modifier: " << value << " with level " << level << " for type: " << magic_enum::enum_name(type) << std::endl;
 | 
			
		||||
 | 
			
		||||
	return ModifierInstance(type, value, operatorType, isResistance, category, effectID, effectType, convertTo, upgradeName);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -59,6 +59,8 @@ public:
 | 
			
		||||
 | 
			
		||||
	std::string GetEffectType() const { return effectType; }
 | 
			
		||||
 | 
			
		||||
	const std::string& GetUpgradeName() const { return upgradeName; }
 | 
			
		||||
 | 
			
		||||
	void SetTypes(const std::vector<ModifierType>& types) { this->types = types; }
 | 
			
		||||
 | 
			
		||||
	void SetConvertTo(ModifierType convertTo) { this->convertTo = convertTo; }
 | 
			
		||||
@@ -75,6 +77,8 @@ public:
 | 
			
		||||
 | 
			
		||||
	void SetEffectType(const std::string& effectType) { this->effectType = effectType; }
 | 
			
		||||
 | 
			
		||||
	void SetUpgradeName(const std::string& upgradeName) { this->upgradeName = upgradeName; }
 | 
			
		||||
	
 | 
			
		||||
	/**
 | 
			
		||||
	 * @brief Generate a HTML string representation of a set of modifier templates.
 | 
			
		||||
	 * 
 | 
			
		||||
@@ -98,6 +102,7 @@ private:
 | 
			
		||||
	bool isResistance;
 | 
			
		||||
	uint32_t effectID;
 | 
			
		||||
	std::string effectType;
 | 
			
		||||
	std::string upgradeName;
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -53,6 +53,27 @@ const std::unordered_map<ModifierType, std::string> nameMap = {
 | 
			
		||||
	{ModifierType::VitalityDecayDuration, "Vitality Decay Duration"},
 | 
			
		||||
	{ModifierType::Seperation, "Seperation"},
 | 
			
		||||
	{ModifierType::SeperationDuration, "Seperation Duration"},
 | 
			
		||||
	{ModifierType::Elemental, "Elemental Damage"},
 | 
			
		||||
	{ModifierType::Damage, "Damage"},
 | 
			
		||||
	{ModifierType::Speed, "Speed"},
 | 
			
		||||
	{ModifierType::AttackSpeed, "Attack Speed"},
 | 
			
		||||
	{ModifierType::BlockRecovery, "Block Recovery Speed"},
 | 
			
		||||
	{ModifierType::BlockChance, "Chance to Block"},
 | 
			
		||||
	{ModifierType::Block, "Damage to Block"},
 | 
			
		||||
	{ModifierType::CriticalDamage, "Critical-hit Damage"},
 | 
			
		||||
	{ModifierType::HealthDrain, "Damage converted to Health"},
 | 
			
		||||
	{ModifierType::ArmorPiercing, "Armor Piercing"},
 | 
			
		||||
	{ModifierType::ReducedStunDuration, "Reduced Stun Duration"},
 | 
			
		||||
	{ModifierType::SkillCooldownReduction, "Skill Cooldown Reduction"},
 | 
			
		||||
	{ModifierType::SkillRecharge, "Skill Recharge Time"},
 | 
			
		||||
	{ModifierType::Slow, "Slow"},
 | 
			
		||||
	{ModifierType::Physique, "Physique"},
 | 
			
		||||
	{ModifierType::Cunning, "Cunning"},
 | 
			
		||||
	{ModifierType::Spirit, "Imagination"},
 | 
			
		||||
	{ModifierType::AttacksPerSecond, "Attacks per Second"},
 | 
			
		||||
	{ModifierType::ImaginationCost, "Imagination Cost"},
 | 
			
		||||
	{ModifierType::MainWeaponDamage, "Main Weapon Damage"},
 | 
			
		||||
	{ModifierType::Stun, "Target Stun Duration"}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const std::unordered_map<ModifierType, ModifierType> resistanceMap = {
 | 
			
		||||
@@ -115,6 +136,16 @@ const std::unordered_set<ModifierType> isOverTimeMap = {
 | 
			
		||||
	ModifierType::Seperation
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const std::unordered_set<ModifierType> isDurationType = {
 | 
			
		||||
	ModifierType::InternalDisassemblyDuration,
 | 
			
		||||
	ModifierType::BurnDuration,
 | 
			
		||||
	ModifierType::FrostburnDuration,
 | 
			
		||||
	ModifierType::PoisonDuration,
 | 
			
		||||
	ModifierType::VitalityDecayDuration,
 | 
			
		||||
	ModifierType::ElectrocuteDuration,
 | 
			
		||||
	ModifierType::SeperationDuration
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const std::string& nejlika::GetModifierTypeColor(ModifierType type)
 | 
			
		||||
@@ -179,3 +210,7 @@ const ModifierType nejlika::GetDurationType(ModifierType type) {
 | 
			
		||||
const bool nejlika::IsOverTimeType(ModifierType type) {
 | 
			
		||||
	return isOverTimeMap.find(type) != isOverTimeMap.end();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const bool nejlika::IsDurationType(ModifierType type) {
 | 
			
		||||
	return isDurationType.find(type) != isDurationType.end();
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -58,9 +58,20 @@ enum class ModifierType : uint8_t
 | 
			
		||||
 | 
			
		||||
	Slow,
 | 
			
		||||
	ArmorPiercing,
 | 
			
		||||
	ReducedStunDuration,
 | 
			
		||||
	SkillCooldownReduction,
 | 
			
		||||
	SkillRecharge,
 | 
			
		||||
	BlockRecovery,
 | 
			
		||||
	BlockChance,
 | 
			
		||||
	Block,
 | 
			
		||||
	HealthRegen,
 | 
			
		||||
	ImaginationRegen,
 | 
			
		||||
	AttacksPerSecond,
 | 
			
		||||
	ImaginationCost,
 | 
			
		||||
	MainWeaponDamage,
 | 
			
		||||
	Stun,
 | 
			
		||||
 | 
			
		||||
	CriticalDamage,
 | 
			
		||||
	ChanceToBlock,
 | 
			
		||||
	HealthDrain,
 | 
			
		||||
 | 
			
		||||
	Invalid
 | 
			
		||||
@@ -76,6 +87,8 @@ const bool IsNormalDamageType(ModifierType type);
 | 
			
		||||
 | 
			
		||||
const bool IsOverTimeType(ModifierType type);
 | 
			
		||||
 | 
			
		||||
const bool IsDurationType(ModifierType type);
 | 
			
		||||
 | 
			
		||||
const ModifierType GetOverTimeType(ModifierType type);
 | 
			
		||||
 | 
			
		||||
const ModifierType GetDurationType(ModifierType type);
 | 
			
		||||
 
 | 
			
		||||
@@ -117,18 +117,39 @@ void nejlika::NejlikaData::LoadNejlikaData()
 | 
			
		||||
	modifierNameTemplates.clear();
 | 
			
		||||
 | 
			
		||||
	// Load data from json file
 | 
			
		||||
	const auto& filename = Game::config->GetValue("nejlika");
 | 
			
		||||
	const auto& directory_name = Game::config->GetValue("nejlika");
 | 
			
		||||
 | 
			
		||||
	if (filename.empty())
 | 
			
		||||
	if (directory_name.empty())
 | 
			
		||||
	{
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	std::ifstream file(filename);
 | 
			
		||||
	// Loop through all files in the directory
 | 
			
		||||
	for (const auto& entry : std::filesystem::directory_iterator(directory_name))
 | 
			
		||||
	{
 | 
			
		||||
		if (!entry.is_regular_file())
 | 
			
		||||
		{
 | 
			
		||||
			continue;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// It has to end on .mod.json
 | 
			
		||||
		const auto& path = entry.path().string();
 | 
			
		||||
 | 
			
		||||
		if (path.size() < 9 || path.substr(path.size() - 9) != ".mod.json")
 | 
			
		||||
		{
 | 
			
		||||
			continue;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		LoadNejlikaDataFile(entry.path().string());
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void nejlika::NejlikaData::LoadNejlikaDataFile(const std::string& path) {
 | 
			
		||||
	std::ifstream file(path);
 | 
			
		||||
 | 
			
		||||
	if (!file.is_open())
 | 
			
		||||
	{
 | 
			
		||||
		LOG("Failed to open nejlika data file: %s", filename.c_str());
 | 
			
		||||
		LOG("Failed to open nejlika data file: %s", path.c_str());
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -144,19 +165,21 @@ void nejlika::NejlikaData::LoadNejlikaData()
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (!json.contains("modifier-templates"))
 | 
			
		||||
	if (json.contains("modifier-templates"))
 | 
			
		||||
	{
 | 
			
		||||
		LOG("nejlika data file does not contain modifier-templates");
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
		const auto& modifierTemplates = json["modifier-templates"];
 | 
			
		||||
 | 
			
		||||
	const auto& modifierTemplates = json["modifier-templates"];
 | 
			
		||||
		for (const auto& value : modifierTemplates)
 | 
			
		||||
		{
 | 
			
		||||
			auto modifierTemplate = ModifierNameTemplate(value);
 | 
			
		||||
 | 
			
		||||
	for (const auto& value : modifierTemplates)
 | 
			
		||||
	{
 | 
			
		||||
		auto modifierTemplate = ModifierNameTemplate(value);
 | 
			
		||||
			if (modifierTemplate.GetModifiers().empty())
 | 
			
		||||
			{
 | 
			
		||||
				continue;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		modifierNameTemplates[modifierTemplate.GetType()].push_back(modifierTemplate);
 | 
			
		||||
			modifierNameTemplates[modifierTemplate.GetType()].push_back(modifierTemplate);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	LOG("Loaded %d modifier templates", modifierNameTemplates.size());
 | 
			
		||||
@@ -186,5 +209,7 @@ void nejlika::NejlikaData::LoadNejlikaData()
 | 
			
		||||
			upgradeTemplates[upgradeTemplate.GetLot()] = upgradeTemplate;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	LOG("Loaded %d upgrade templates", upgradeTemplates.size());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -35,6 +35,8 @@ void UnsetAdditionalEntityData(LWOOBJID id);
 | 
			
		||||
 | 
			
		||||
void LoadNejlikaData();
 | 
			
		||||
 | 
			
		||||
void LoadNejlikaDataFile(const std::string& path);
 | 
			
		||||
 | 
			
		||||
const Lookup& GetLookup();
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										33
									
								
								dGame/NejlikaHelpers.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								dGame/NejlikaHelpers.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,33 @@
 | 
			
		||||
#include "NejlikaHelpers.h"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
void nejlika::NejlikaHelpers::RenderDamageText(const std::string & text, Entity * attacker, Entity * damaged,
 | 
			
		||||
	float scale, int32_t fontSize, int32_t colorR, int32_t colorG, int32_t colorB, int32_t colorA)
 | 
			
		||||
{
 | 
			
		||||
	if (damaged == nullptr) {
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	auto damagedPosition = damaged->GetPosition();
 | 
			
		||||
 | 
			
		||||
	// Add a slight random offset to the damage position
 | 
			
		||||
	damagedPosition.x += (rand() % 10 - 5) / 5.0f;
 | 
			
		||||
	damagedPosition.y += (rand() % 10 - 5) / 5.0f;
 | 
			
		||||
	damagedPosition.z += (rand() % 10 - 5) / 5.0f;
 | 
			
		||||
 | 
			
		||||
	const auto& damageText = text;
 | 
			
		||||
 | 
			
		||||
	std::stringstream damageUIMessage;
 | 
			
		||||
 | 
			
		||||
	damageUIMessage << 0.0825 << ";" << 0.12 << ";" << damagedPosition.x << ";" << damagedPosition.y + 4.5f << ";" << damagedPosition.z << ";" << 0.1 << ";";
 | 
			
		||||
	damageUIMessage << 200 * scale << ";" << 200 * scale << ";" << 0.5 << ";" << 1.0 << ";" << damageText << ";" << 4 << ";" << fontSize << ";" << colorR << ";" << colorG << ";" << colorB << ";";
 | 
			
		||||
	damageUIMessage << colorA;
 | 
			
		||||
 | 
			
		||||
	const auto damageUIStr = damageUIMessage.str();
 | 
			
		||||
 | 
			
		||||
	if (damaged->IsPlayer()) {
 | 
			
		||||
		damaged->SetNetworkVar<std::string>(u"renderText", damageUIStr, UNASSIGNED_SYSTEM_ADDRESS);
 | 
			
		||||
	} else if (attacker != nullptr && attacker->IsPlayer()) {
 | 
			
		||||
		attacker->SetNetworkVar<std::string>(u"renderText", damageUIStr, UNASSIGNED_SYSTEM_ADDRESS);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										9
									
								
								dGame/NejlikaHelpers.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								dGame/NejlikaHelpers.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
			
		||||
#include <Entity.h>
 | 
			
		||||
 | 
			
		||||
namespace nejlika::NejlikaHelpers
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
void RenderDamageText(const std::string& text, Entity* attacker, Entity* damaged, 
 | 
			
		||||
	float scale = 1, int32_t fontSize = 4, int32_t colorR = 255, int32_t colorG = 255, int32_t colorB = 255, int32_t colorA = 0);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -22,11 +22,16 @@
 | 
			
		||||
#include <MissionComponent.h>
 | 
			
		||||
#include <eMissionState.h>
 | 
			
		||||
 | 
			
		||||
#include "NejlikaHelpers.h"
 | 
			
		||||
#include "NejlikaData.h"
 | 
			
		||||
 | 
			
		||||
using namespace nejlika;
 | 
			
		||||
using namespace nejlika::NejlikaData;
 | 
			
		||||
 | 
			
		||||
namespace {
 | 
			
		||||
	std::unordered_map<LWOOBJID, std::unordered_map<uint32_t, std::vector<ModifierInstance>>> uniqueSkillModifiers;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void nejlika::NejlikaHooks::InstallHooks() {
 | 
			
		||||
	Command itemDescriptionCommand{
 | 
			
		||||
		.help = "Special UI command, does nothing when used in chat.",
 | 
			
		||||
@@ -37,26 +42,38 @@ void nejlika::NejlikaHooks::InstallHooks() {
 | 
			
		||||
	};
 | 
			
		||||
	SlashCommandHandler::RegisterCommand(itemDescriptionCommand);
 | 
			
		||||
 | 
			
		||||
	Command modificationssCommand{
 | 
			
		||||
		.help = "Displays active modifications.",
 | 
			
		||||
		.info = "Displays active modifications.",
 | 
			
		||||
		.aliases = {"stats"},
 | 
			
		||||
		.handle = [](Entity* entity, const SystemAddress& sysAddr, const std::string args) {
 | 
			
		||||
			auto entityDataOpt = GetAdditionalEntityData(entity->GetObjectID());
 | 
			
		||||
 | 
			
		||||
			if (!entityDataOpt.has_value()) {
 | 
			
		||||
				return;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			auto& entityData = *entityDataOpt.value();
 | 
			
		||||
 | 
			
		||||
			std::vector<ModifierInstance> modifiers = entityData.GetActiveModifiers();
 | 
			
		||||
 | 
			
		||||
			std::stringstream ss;
 | 
			
		||||
 | 
			
		||||
			ss << "Active modifications: " << std::endl;
 | 
			
		||||
 | 
			
		||||
			ss << ModifierInstance::GenerateHtmlString(modifiers);
 | 
			
		||||
 | 
			
		||||
			ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16(ss.str()));
 | 
			
		||||
		},
 | 
			
		||||
		.requiredLevel = eGameMasterLevel::CIVILIAN
 | 
			
		||||
	};
 | 
			
		||||
	SlashCommandHandler::RegisterCommand(modificationssCommand);
 | 
			
		||||
 | 
			
		||||
	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;
 | 
			
		||||
		}
 | 
			
		||||
		*/
 | 
			
		||||
 | 
			
		||||
		// No to the Thinking Hat
 | 
			
		||||
		if (item->GetLot() == 6086) {
 | 
			
		||||
			return;
 | 
			
		||||
@@ -133,11 +150,10 @@ void nejlika::NejlikaHooks::InstallHooks() {
 | 
			
		||||
 | 
			
		||||
		auto& additionalData = *additionalDataOpt.value();
 | 
			
		||||
 | 
			
		||||
		additionalData.ApplyToEntity();
 | 
			
		||||
 | 
			
		||||
		auto* inventoryComponent = entity->GetComponent<InventoryComponent>();
 | 
			
		||||
 | 
			
		||||
		if (!inventoryComponent) {
 | 
			
		||||
			additionalData.ApplyToEntity();
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@@ -158,6 +174,8 @@ void nejlika::NejlikaHooks::InstallHooks() {
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		additionalData.ApplyToEntity();
 | 
			
		||||
 | 
			
		||||
		additionalData.InitializeSkills();
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
@@ -166,8 +184,6 @@ void nejlika::NejlikaHooks::InstallHooks() {
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		//GameMessages::SendAddSkill(entity, NejlikaData::GetLookup().GetValue("intro:skills:proxy:main"), BehaviorSlot::Head);
 | 
			
		||||
		//GameMessages::SendAddSkill(entity, NejlikaData::GetLookup().GetValue("intro:skills:proxy:secondary"), BehaviorSlot::Offhand);
 | 
			
		||||
		GameMessages::SendAddSkill(entity, NejlikaData::GetLookup().GetValue("intro:skills:proxy:tertiary"), BehaviorSlot::Neck);
 | 
			
		||||
 | 
			
		||||
		auto* missionComponent = entity->GetComponent<MissionComponent>();
 | 
			
		||||
@@ -300,7 +316,7 @@ void nejlika::NejlikaHooks::InstallHooks() {
 | 
			
		||||
		entityData.ApplyToEntity();
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
	SkillComponent::OnSkillCast += [](SkillComponent* skillComponent, uint32_t skillID, bool success) {
 | 
			
		||||
	SkillComponent::OnSkillCast += [](SkillComponent* skillComponent, uint32_t skillID, bool& success, uint32_t skillUID) {
 | 
			
		||||
		std::cout << "Skill cast: " << skillID << " - " << success << std::endl;
 | 
			
		||||
 | 
			
		||||
		auto* inventoryComponent = skillComponent->GetParent()->GetComponent<InventoryComponent>();
 | 
			
		||||
@@ -312,17 +328,155 @@ void nejlika::NejlikaHooks::InstallHooks() {
 | 
			
		||||
		auto* entity = skillComponent->GetParent();
 | 
			
		||||
 | 
			
		||||
		auto skills = inventoryComponent->GetSkills();
 | 
			
		||||
 | 
			
		||||
		const auto primaryTrigger = NejlikaData::GetLookup().GetValue("intro:skills:proxy:main");
 | 
			
		||||
		const auto secondaryTrigger = NejlikaData::GetLookup().GetValue("intro:skills:proxy:secondary");
 | 
			
		||||
		const auto tertiaryTrigger = NejlikaData::GetLookup().GetValue("intro:skills:proxy:tertiary");
 | 
			
		||||
 | 
			
		||||
		if (skillID == primaryTrigger || skillID == secondaryTrigger || skillID == tertiaryTrigger) {
 | 
			
		||||
		} else {
 | 
			
		||||
		if (skillID == tertiaryTrigger) {
 | 
			
		||||
			inventoryComponent->RotateSkills();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		const auto entityDataOpt = GetAdditionalEntityData(entity->GetObjectID());
 | 
			
		||||
 | 
			
		||||
		if (!entityDataOpt.has_value()) {
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		inventoryComponent->RotateSkills();
 | 
			
		||||
		auto& entityData = *entityDataOpt.value();
 | 
			
		||||
 | 
			
		||||
		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(entityData.GetLevel());
 | 
			
		||||
 | 
			
		||||
			modifiers.insert(modifiers.end(), skillModifiers.begin(), skillModifiers.end());
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		LOT itemLot = 0;
 | 
			
		||||
		LWOOBJID itemId = 0;
 | 
			
		||||
		BehaviorSlot itemSlot = BehaviorSlot::Invalid;
 | 
			
		||||
 | 
			
		||||
		if (inventoryComponent) {
 | 
			
		||||
			const auto& equipped = inventoryComponent->GetEquippedItems();
 | 
			
		||||
 | 
			
		||||
			// omg...
 | 
			
		||||
			for (const auto& [equippedSlot, itemDetails] : equipped) {
 | 
			
		||||
				std::cout << "Found equipped item: " << itemDetails.lot << std::endl;
 | 
			
		||||
 | 
			
		||||
				const auto info = Inventory::FindItemComponent(itemDetails.lot);
 | 
			
		||||
 | 
			
		||||
				const auto skill = InventoryComponent::FindSkill(itemDetails.lot);
 | 
			
		||||
 | 
			
		||||
				if (skill != skillID) {
 | 
			
		||||
					continue;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				const auto itemBehaviorSlot = InventoryComponent::FindBehaviorSlot(static_cast<eItemType>(info.itemType));
 | 
			
		||||
 | 
			
		||||
				itemLot = itemDetails.lot;
 | 
			
		||||
				itemId = itemDetails.id;
 | 
			
		||||
				itemSlot = itemBehaviorSlot;
 | 
			
		||||
 | 
			
		||||
				std::cout << "Found item: " << itemLot << std::endl;
 | 
			
		||||
 | 
			
		||||
				break;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		TriggerParameters params;
 | 
			
		||||
		params.SkillID = skillID;
 | 
			
		||||
		params.SelectedBehaviorSlot = itemSlot;
 | 
			
		||||
 | 
			
		||||
		// Upgrades
 | 
			
		||||
		const auto upgradeModifiers = entityData.TriggerUpgradeItems(UpgradeTriggerType::OnCast, params);
 | 
			
		||||
 | 
			
		||||
		auto unionModifiersUpgradeModifiers = modifiers;
 | 
			
		||||
		unionModifiersUpgradeModifiers.insert(unionModifiersUpgradeModifiers.end(), upgradeModifiers.begin(), upgradeModifiers.end());
 | 
			
		||||
 | 
			
		||||
		const auto& imaginationCost = entityData.CalculateFinalModifier(ModifierType::ImaginationCost, unionModifiersUpgradeModifiers, entityData.GetLevel());
 | 
			
		||||
 | 
			
		||||
		auto* destroyable = entity->GetComponent<DestroyableComponent>();
 | 
			
		||||
 | 
			
		||||
		if (destroyable != nullptr) {
 | 
			
		||||
			if (destroyable->GetImagination() < imaginationCost) {
 | 
			
		||||
				NejlikaHelpers::RenderDamageText("Insufficient imagination!", entity, entity, 4.0f);
 | 
			
		||||
			}
 | 
			
		||||
			else {
 | 
			
		||||
				destroyable->SetImagination(destroyable->GetImagination() - imaginationCost);
 | 
			
		||||
 | 
			
		||||
				Game::entityManager->SerializeEntity(entity);
 | 
			
		||||
 | 
			
		||||
				// Insert into the unique skill modifiers
 | 
			
		||||
				const auto& uniqueSkillModifiersIt = uniqueSkillModifiers.find(entity->GetObjectID());
 | 
			
		||||
 | 
			
		||||
				if (uniqueSkillModifiersIt != uniqueSkillModifiers.end()) {
 | 
			
		||||
					auto& uniqueSkillModifiersMap = uniqueSkillModifiersIt->second;
 | 
			
		||||
 | 
			
		||||
					uniqueSkillModifiersMap[skillID] = upgradeModifiers;
 | 
			
		||||
				}
 | 
			
		||||
				else {
 | 
			
		||||
					uniqueSkillModifiers[entity->GetObjectID()] = { { skillID, upgradeModifiers } };
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				entity->AddCallbackTimer(10.0f, [entity, skillID]() {
 | 
			
		||||
					const auto& uniqueSkillModifiersIt = uniqueSkillModifiers.find(entity->GetObjectID());
 | 
			
		||||
 | 
			
		||||
					if (uniqueSkillModifiersIt != uniqueSkillModifiers.end()) {
 | 
			
		||||
						auto& uniqueSkillModifiersMap = uniqueSkillModifiersIt->second;
 | 
			
		||||
 | 
			
		||||
						const auto& uniqueSkillModifiersSkillIt = uniqueSkillModifiersMap.find(skillID);
 | 
			
		||||
 | 
			
		||||
						if (uniqueSkillModifiersSkillIt != uniqueSkillModifiersMap.end()) {
 | 
			
		||||
							uniqueSkillModifiersMap.erase(uniqueSkillModifiersSkillIt);
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				});
 | 
			
		||||
 | 
			
		||||
				modifiers.insert(modifiers.end(), upgradeModifiers.begin(), upgradeModifiers.end());
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		float attackSpeed = 0;
 | 
			
		||||
 | 
			
		||||
		if (itemSlot == BehaviorSlot::Primary) {
 | 
			
		||||
			auto attacksPerSecond = entityData.CalculateFinalModifier(ModifierType::AttacksPerSecond, modifiers, entityData.GetLevel());
 | 
			
		||||
			attacksPerSecond = std::max(attacksPerSecond, 1.0f);
 | 
			
		||||
			const auto attackSpeedMod = entityData.CalculateMultiplier(ModifierType::AttackSpeed, modifiers);
 | 
			
		||||
			attackSpeed = (1.0f / attacksPerSecond) * ((attackSpeedMod / 100.0f)) / 2.0f; // 2.0f to account for animation times
 | 
			
		||||
			LOG("Attack speed: %f, attacks per second: %f, attack speed mod: %f", attackSpeed, attacksPerSecond, attackSpeedMod);
 | 
			
		||||
		}
 | 
			
		||||
		else {
 | 
			
		||||
			const auto skillRecharge = entityData.CalculateFinalModifier(ModifierType::SkillRecharge, modifiers, entityData.GetLevel());
 | 
			
		||||
			const auto skillCooldownMod = entityData.CalculateMultiplier(ModifierType::SkillCooldownReduction, modifiers);
 | 
			
		||||
			attackSpeed = skillRecharge * (skillCooldownMod / 100.0f);
 | 
			
		||||
			LOG("Skill recharge: %f, skill cooldown mod: %f", skillRecharge, skillCooldownMod);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (entity->IsPlayer()) {
 | 
			
		||||
			entity->AddCallbackTimer(0.0f, [entity, skillID, attackSpeed]() {
 | 
			
		||||
				CBITSTREAM;
 | 
			
		||||
				CMSGHEADER;
 | 
			
		||||
 | 
			
		||||
				const auto objectID = entity->GetObjectID();
 | 
			
		||||
 | 
			
		||||
				bitStream.Write(objectID);
 | 
			
		||||
				bitStream.Write(eGameMessageType::MODIFY_SKILL_COOLDOWN);
 | 
			
		||||
 | 
			
		||||
				bitStream.Write1();
 | 
			
		||||
				bitStream.Write<float>(attackSpeed);
 | 
			
		||||
				bitStream.Write<int32_t>(static_cast<int32_t>(skillID));
 | 
			
		||||
 | 
			
		||||
				LOG("Sending cooldown reduction for skill: %d: %f", skillID, attackSpeed);
 | 
			
		||||
 | 
			
		||||
				SEND_PACKET_BROADCAST;
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	DestroyableComponent::OnDamageCalculation += [](Entity* damaged, LWOOBJID offender, uint32_t skillID, uint32_t& damage) {
 | 
			
		||||
@@ -403,14 +557,24 @@ void nejlika::NejlikaHooks::InstallHooks() {
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		std::vector<ModifierInstance> modifiers;
 | 
			
		||||
 | 
			
		||||
		if (itemSlot == BehaviorSlot::Primary) {
 | 
			
		||||
			const auto mainWeaponDamage = offenderEntity.CalculateMainWeaponDamage();
 | 
			
		||||
 | 
			
		||||
			for (const auto& modifier : mainWeaponDamage) {
 | 
			
		||||
				std::cout << "Main weapon damage: " << magic_enum::enum_name(modifier.GetType()) << " - " << modifier.GetValue() << std::endl;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			modifiers.insert(modifiers.end(), mainWeaponDamage.begin(), mainWeaponDamage.end());
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		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;
 | 
			
		||||
 | 
			
		||||
@@ -428,6 +592,20 @@ void nejlika::NejlikaHooks::InstallHooks() {
 | 
			
		||||
 | 
			
		||||
		modifiers.insert(modifiers.end(), upgradeModifiers.begin(), upgradeModifiers.end());
 | 
			
		||||
 | 
			
		||||
		const auto& uniqueSkillModifiersIt = uniqueSkillModifiers.find(offender);
 | 
			
		||||
 | 
			
		||||
		if (uniqueSkillModifiersIt != uniqueSkillModifiers.end()) {
 | 
			
		||||
			const auto& uniqueSkillModifiersMap = uniqueSkillModifiersIt->second;
 | 
			
		||||
 | 
			
		||||
			const auto& uniqueSkillModifiersSkillIt = uniqueSkillModifiersMap.find(skillID);
 | 
			
		||||
 | 
			
		||||
			if (uniqueSkillModifiersSkillIt != uniqueSkillModifiersMap.end()) {
 | 
			
		||||
				const auto& uniqueSkillModifiersVec = uniqueSkillModifiersSkillIt->second;
 | 
			
		||||
 | 
			
		||||
				modifiers.insert(modifiers.end(), uniqueSkillModifiersVec.begin(), uniqueSkillModifiersVec.end());
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		std::unordered_set<ModifierType> damageTypes;
 | 
			
		||||
 | 
			
		||||
		for (const auto& modifier : modifiers) {
 | 
			
		||||
@@ -444,14 +622,28 @@ void nejlika::NejlikaHooks::InstallHooks() {
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		uint32_t totalDamage = 0;
 | 
			
		||||
		int32_t totalDamage = 0;
 | 
			
		||||
 | 
			
		||||
		std::unordered_map<ModifierType, std::pair<float, float>> durationTypes;
 | 
			
		||||
		std::unordered_map<ModifierType, float> tmpDamageValues;
 | 
			
		||||
		
 | 
			
		||||
		const auto mainWeaponDamageModifier = (offenderEntity.CalculateMultiplier(ModifierType::MainWeaponDamage, modifiers) - 100.0f) / 100.0f;
 | 
			
		||||
 | 
			
		||||
		if (mainWeaponDamageModifier > 0.0f) {
 | 
			
		||||
			const auto mainWeaponDamage = offenderEntity.CalculateMainWeaponDamage();
 | 
			
		||||
 | 
			
		||||
			for (auto modifier : mainWeaponDamage) {
 | 
			
		||||
				const auto damageValue = modifier.GetValue();
 | 
			
		||||
 | 
			
		||||
				modifier.SetValue(damageValue * mainWeaponDamageModifier);
 | 
			
		||||
 | 
			
		||||
				modifiers.push_back(modifier);	
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		for (const auto& type : damageTypes) {
 | 
			
		||||
			if (nejlika::IsOverTimeType(type)) {
 | 
			
		||||
				float damageValue = offenderEntity.CalculateModifier(type, modifiers, level);
 | 
			
		||||
				float damageValue = offenderEntity.CalculateFinalModifier(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);
 | 
			
		||||
@@ -460,7 +652,7 @@ void nejlika::NejlikaHooks::InstallHooks() {
 | 
			
		||||
 | 
			
		||||
				const auto durationType = nejlika::GetDurationType(type);
 | 
			
		||||
 | 
			
		||||
				const auto duration = offenderEntity.CalculateModifier(durationType, modifiers, level);
 | 
			
		||||
				const auto duration = offenderEntity.CalculateFinalModifier(durationType, modifiers, level);
 | 
			
		||||
 | 
			
		||||
				const auto durationResistance = std::max(1 - (damagedEntity.CalculateResistance(durationType) / 100), 0.2f);
 | 
			
		||||
 | 
			
		||||
@@ -475,7 +667,7 @@ void nejlika::NejlikaHooks::InstallHooks() {
 | 
			
		||||
				continue;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			float damageValue = offenderEntity.CalculateModifier(type, modifiers, level);
 | 
			
		||||
			float damageValue = offenderEntity.CalculateFinalModifier(type, modifiers, level);
 | 
			
		||||
 | 
			
		||||
			tmpDamageValues[type] = damageValue;
 | 
			
		||||
		}
 | 
			
		||||
@@ -485,7 +677,7 @@ void nejlika::NejlikaHooks::InstallHooks() {
 | 
			
		||||
 | 
			
		||||
		std::unordered_map<ModifierType, float> finalDamageValues;
 | 
			
		||||
 | 
			
		||||
		for (const auto& [typeA, typeBMap] : converationMap) {
 | 
			
		||||
		/*for (const auto& [typeA, typeBMap] : converationMap) {
 | 
			
		||||
			const auto& typeAValue = tmpDamageValues.find(typeA);
 | 
			
		||||
 | 
			
		||||
			if (typeAValue == tmpDamageValues.end()) {
 | 
			
		||||
@@ -508,6 +700,13 @@ void nejlika::NejlikaHooks::InstallHooks() {
 | 
			
		||||
				finalDamageValues[typeA] += typeAValueFloat - convertedValue;
 | 
			
		||||
				finalDamageValues[typeB] += typeBValueFloat + convertedValue;
 | 
			
		||||
			}
 | 
			
		||||
		}*/
 | 
			
		||||
 | 
			
		||||
		// Add the remaining values
 | 
			
		||||
		for (const auto& [type, value] : tmpDamageValues) {
 | 
			
		||||
			if (finalDamageValues.find(type) == finalDamageValues.end()) {
 | 
			
		||||
				finalDamageValues[type] = value;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for (const auto& [type, damage] : finalDamageValues) {
 | 
			
		||||
@@ -516,7 +715,7 @@ void nejlika::NejlikaHooks::InstallHooks() {
 | 
			
		||||
 | 
			
		||||
			float reductedDamage = damage * resistance;
 | 
			
		||||
 | 
			
		||||
			totalDamage += static_cast<uint32_t>(reductedDamage);
 | 
			
		||||
			totalDamage += static_cast<int32_t>(reductedDamage);
 | 
			
		||||
 | 
			
		||||
			std::cout << "Damage type: " << magic_enum::enum_name(type) << " - " << damage << std::endl;
 | 
			
		||||
			std::cout << "Resistance: " << resistance << " - " << reductedDamage << std::endl;
 | 
			
		||||
@@ -581,21 +780,25 @@ void nejlika::NejlikaHooks::InstallHooks() {
 | 
			
		||||
			if (damageMultiplier > 1.0f) {
 | 
			
		||||
				isCritical = true;
 | 
			
		||||
 | 
			
		||||
				damageMultiplier *= offenderEntity.CalculateMultiplier(ModifierType::CriticalDamage, modifiers);
 | 
			
		||||
				damageMultiplier *= (offenderEntity.CalculateMultiplier(ModifierType::CriticalDamage, modifiers) / 100.0f);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (isHit) {
 | 
			
		||||
			// Add a random +5% to the damage
 | 
			
		||||
			totalDamage += static_cast<uint32_t>(totalDamage * (GeneralUtils::GenerateRandomNumber<int32_t>(0, 5) / 100.0f));
 | 
			
		||||
			totalDamage += static_cast<int32_t>(totalDamage * (GeneralUtils::GenerateRandomNumber<int32_t>(0, 5) / 100.0f));
 | 
			
		||||
 | 
			
		||||
			damage = totalDamage;
 | 
			
		||||
		} else {
 | 
			
		||||
			damage = totalDamage = 0;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (totalDamage < 0 || damage < 0) {
 | 
			
		||||
			totalDamage = damage = 0;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (isCritical) {
 | 
			
		||||
			totalDamage = static_cast<uint32_t>(totalDamage * damageMultiplier);
 | 
			
		||||
			totalDamage = static_cast<int32_t>(totalDamage * damageMultiplier);
 | 
			
		||||
 | 
			
		||||
			const auto effectName = std::to_string(GeneralUtils::GenerateRandomNumber<uint32_t>());
 | 
			
		||||
			const auto damagedID = damaged->GetObjectID();
 | 
			
		||||
@@ -616,26 +819,15 @@ void nejlika::NejlikaHooks::InstallHooks() {
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		auto attackSpeed = offenderEntity.CalculateModifier(ModifierType::AttackSpeed, modifiers, level);
 | 
			
		||||
		if (damage == 0) {
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (offfendEntity->IsPlayer()) {
 | 
			
		||||
			offfendEntity->AddCallbackTimer(0.0f, [offfendEntity, skillID, attackSpeed]() {
 | 
			
		||||
				CBITSTREAM;
 | 
			
		||||
				CMSGHEADER;
 | 
			
		||||
		const auto stunDuration = offenderEntity.CalculateFinalModifier(ModifierType::Stun, modifiers, level);
 | 
			
		||||
 | 
			
		||||
				const auto entity = offfendEntity->GetObjectID();
 | 
			
		||||
 | 
			
		||||
				bitStream.Write(entity);
 | 
			
		||||
				bitStream.Write(eGameMessageType::MODIFY_SKILL_COOLDOWN);
 | 
			
		||||
 | 
			
		||||
				bitStream.Write1();
 | 
			
		||||
				bitStream.Write<float>(attackSpeed);
 | 
			
		||||
				bitStream.Write<int32_t>(static_cast<int32_t>(skillID));
 | 
			
		||||
 | 
			
		||||
				LOG("Sending cooldown reduction for skill: %d", skillID);
 | 
			
		||||
 | 
			
		||||
				SEND_PACKET_BROADCAST;
 | 
			
		||||
			});
 | 
			
		||||
		if (baseCombatAIComponent && stunDuration > 0) {
 | 
			
		||||
			LOG("Stunning for %f seconds", stunDuration);
 | 
			
		||||
			baseCombatAIComponent->Stun(stunDuration, true);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Apply over time damage.
 | 
			
		||||
@@ -651,7 +843,7 @@ void nejlika::NejlikaHooks::InstallHooks() {
 | 
			
		||||
				continue;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			const auto damagePerTick = static_cast<int32_t>(damageDuration.first / duration);
 | 
			
		||||
			const auto damagePerTick = static_cast<int32_t>(damageDuration.first);
 | 
			
		||||
 | 
			
		||||
			auto* destroyable = damaged->GetComponent<DestroyableComponent>();
 | 
			
		||||
 | 
			
		||||
@@ -661,54 +853,20 @@ void nejlika::NejlikaHooks::InstallHooks() {
 | 
			
		||||
			
 | 
			
		||||
			for (size_t i = 0; i < duration; i++)
 | 
			
		||||
			{
 | 
			
		||||
				damaged->AddCallbackTimer(i * 0.5f, [offender, damaged, damagePerTick]() {
 | 
			
		||||
				damaged->AddCallbackTimer(i * 0.5f, [type, offender, damaged, damagePerTick]() {
 | 
			
		||||
 | 
			
		||||
					auto* offenderEntity = Game::entityManager->GetEntity(offender);
 | 
			
		||||
 | 
			
		||||
					auto* destroyable = damaged->GetComponent<DestroyableComponent>();
 | 
			
		||||
 | 
			
		||||
					if (!destroyable) {
 | 
			
		||||
						return;
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					destroyable->Damage(offender, damagePerTick, 0, true, true);
 | 
			
		||||
					destroyable->Damage(damagePerTick, offender, 0, true, true);
 | 
			
		||||
				});
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/* Moved to DestroyableComponent
 | 
			
		||||
		std::stringstream damageUIMessage;
 | 
			
		||||
 | 
			
		||||
		auto damagedPosition = damaged->GetPosition();
 | 
			
		||||
 | 
			
		||||
		// Add a slight random offset to the damage position
 | 
			
		||||
		damagedPosition.x += (rand() % 10 - 5) / 5.0f;
 | 
			
		||||
		damagedPosition.y += (rand() % 10 - 5) / 5.0f;
 | 
			
		||||
		damagedPosition.z += (rand() % 10 - 5) / 5.0f;
 | 
			
		||||
 | 
			
		||||
		int colorR = 255;
 | 
			
		||||
		int colorG = 255;
 | 
			
		||||
		int colorB = 255;
 | 
			
		||||
		int colorA = 0;
 | 
			
		||||
 | 
			
		||||
		if (damaged->IsPlayer()) {
 | 
			
		||||
			// Make the damage red
 | 
			
		||||
			colorR = 0;
 | 
			
		||||
			colorG = 255;
 | 
			
		||||
			colorB = 0;
 | 
			
		||||
			colorA = 0;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		const auto damageText = isHit ? std::to_string(totalDamage) : "Miss";
 | 
			
		||||
 | 
			
		||||
		damageUIMessage << 0.0825 << ";" << 0.12 << ";" << damagedPosition.x << ";" << damagedPosition.y + 4.5f << ";" << damagedPosition.z << ";" << 0.1 << ";";
 | 
			
		||||
		damageUIMessage << 200 << ";" << 200 << ";" << 0.5 << ";" << 1.0 << ";" << damageText << ";" << 4 << ";" << 4 << ";" << colorR << ";" << colorG << ";" << colorB << ";";
 | 
			
		||||
		damageUIMessage << colorA;
 | 
			
		||||
 | 
			
		||||
		const auto damageUIStr = damageUIMessage.str();
 | 
			
		||||
 | 
			
		||||
		if (damaged->IsPlayer()) {
 | 
			
		||||
			damaged->SetNetworkVar<std::string>(u"renderText", damageUIStr, UNASSIGNED_SYSTEM_ADDRESS);
 | 
			
		||||
		} else if (offfendEntity->IsPlayer()) {
 | 
			
		||||
			offfendEntity->SetNetworkVar<std::string>(u"renderText", damageUIStr, UNASSIGNED_SYSTEM_ADDRESS);
 | 
			
		||||
		}*/
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -744,34 +902,110 @@ void nejlika::NejlikaHooks::ItemDescription(Entity* entity, const SystemAddress&
 | 
			
		||||
	std::stringstream name;
 | 
			
		||||
	std::stringstream desc;
 | 
			
		||||
 | 
			
		||||
	if (itemId == LWOOBJID_EMPTY) {
 | 
			
		||||
		ChatPackets::SendSystemMessage(sysAddr, u"Invalid item ID.");
 | 
			
		||||
	const auto& upgradeItemOpt = NejlikaData::GetUpgradeTemplate(lot);
 | 
			
		||||
 | 
			
		||||
	if (upgradeItemOpt.has_value()) {
 | 
			
		||||
		const auto& upgradeItem = *upgradeItemOpt.value();
 | 
			
		||||
 | 
			
		||||
		auto* inventoryComponent = entity->GetComponent<InventoryComponent>();
 | 
			
		||||
 | 
			
		||||
		if (!inventoryComponent) {
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		auto amount = inventoryComponent->GetLotCount(lot);
 | 
			
		||||
 | 
			
		||||
		if (itemId == LWOOBJID_EMPTY) {
 | 
			
		||||
			amount++;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		const auto& modifiers = upgradeItem.GenerateModifiers(amount);
 | 
			
		||||
 | 
			
		||||
		name << "<font color=\"#D0AB62\">NAME</font>";
 | 
			
		||||
 | 
			
		||||
		desc << "DESC" << "\n\n";
 | 
			
		||||
 | 
			
		||||
		desc << "<font color=\"#D0AB62\">Level " << amount << "/" << upgradeItem.GetMaxLevel() << "</font>\n";
 | 
			
		||||
 | 
			
		||||
		desc << ModifierInstance::GenerateHtmlString(modifiers);
 | 
			
		||||
 | 
			
		||||
		const auto& passives = upgradeItem.GetPassives();
 | 
			
		||||
 | 
			
		||||
		desc << UpgradeEffect::GenerateHtmlString(passives, amount);
 | 
			
		||||
	} else if (itemId == LWOOBJID_EMPTY) {
 | 
			
		||||
		const auto& itemTemplateVec = NejlikaData::GetModifierNameTemplates(ModifierNameType::Object);
 | 
			
		||||
		
 | 
			
		||||
		const auto itemTemplateIt = std::find_if(itemTemplateVec.begin(), itemTemplateVec.end(), [lot](const auto& it) {
 | 
			
		||||
			return it.GetLOT() == static_cast<int32_t>(lot);
 | 
			
		||||
			});
 | 
			
		||||
		auto* levelProgressionComponent = entity->GetComponent<LevelProgressionComponent>();
 | 
			
		||||
 | 
			
		||||
		if (itemTemplateIt == itemTemplateVec.end()) {
 | 
			
		||||
			name << "<font color=\"#D0AB62\">NAME</font>";
 | 
			
		||||
			desc << "DESC";
 | 
			
		||||
		} else {
 | 
			
		||||
			const auto& itemTemplate = *itemTemplateIt;
 | 
			
		||||
		if (!levelProgressionComponent) {
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
			const auto& modifiers = itemTemplate.GetModifiers();
 | 
			
		||||
		std::vector<const nejlika::ModifierNameTemplate*> availableTemplates;
 | 
			
		||||
		const nejlika::ModifierNameTemplate* lowestLevelTemplate = nullptr;
 | 
			
		||||
		std::vector<const nejlika::ModifierNameTemplate*> lowestLevelTemplates;
 | 
			
		||||
 | 
			
		||||
			// Get the entity level
 | 
			
		||||
			auto* levelProgressionComponent = entity->GetComponent<LevelProgressionComponent>();
 | 
			
		||||
		for (const auto& itemTemplate : itemTemplateVec) {
 | 
			
		||||
			auto level = static_cast<int32_t>(levelProgressionComponent->GetLevel());
 | 
			
		||||
 | 
			
		||||
			if (!levelProgressionComponent) {
 | 
			
		||||
				return;
 | 
			
		||||
			if (itemTemplate.GetLOT() != static_cast<int32_t>(lot)) {
 | 
			
		||||
				continue;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			auto level = levelProgressionComponent->GetLevel();
 | 
			
		||||
			if (lowestLevelTemplate == nullptr) {
 | 
			
		||||
				lowestLevelTemplate = &itemTemplate;
 | 
			
		||||
			}
 | 
			
		||||
			else if (lowestLevelTemplate->GetMinLevel() > level) {
 | 
			
		||||
				lowestLevelTemplate = &itemTemplate;
 | 
			
		||||
				lowestLevelTemplates.clear();
 | 
			
		||||
			}
 | 
			
		||||
			else if (lowestLevelTemplate->GetMinLevel() == level) {
 | 
			
		||||
				lowestLevelTemplates.push_back(&itemTemplate);
 | 
			
		||||
			}
 | 
			
		||||
			
 | 
			
		||||
			name << "<font color=\"#D0AB62\">NAME</font>";
 | 
			
		||||
			desc << ModifierTemplate::GenerateHtmlString(modifiers, level);
 | 
			
		||||
			if (itemTemplate.GetMinLevel() > level || itemTemplate.GetMaxLevel() < level) {
 | 
			
		||||
				continue;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			availableTemplates.push_back(&itemTemplate);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Add the lowest level template if no other template was found
 | 
			
		||||
		if (availableTemplates.empty() && lowestLevelTemplate != nullptr) {
 | 
			
		||||
			availableTemplates.push_back(lowestLevelTemplate);
 | 
			
		||||
 | 
			
		||||
			// Add all templates with the same level
 | 
			
		||||
			availableTemplates.insert(availableTemplates.end(), lowestLevelTemplates.begin(), lowestLevelTemplates.end());
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		name << "<font color=\"#D0AB62\">NAME</font>";
 | 
			
		||||
 | 
			
		||||
		if (availableTemplates.empty()) {
 | 
			
		||||
			desc << "DESC";
 | 
			
		||||
		} else {
 | 
			
		||||
			if (availableTemplates.size() > 1) {
 | 
			
		||||
				desc << "<font color=\"#D0AB62\">One of:</font>\n";
 | 
			
		||||
			}
 | 
			
		||||
			for (size_t i = 0; i < availableTemplates.size(); i++) {
 | 
			
		||||
				const auto& itemTemplate = *availableTemplates[i];
 | 
			
		||||
 | 
			
		||||
				const auto& modifiers = itemTemplate.GetModifiers();
 | 
			
		||||
 | 
			
		||||
				// Get the entity level
 | 
			
		||||
				auto* levelProgressionComponent = entity->GetComponent<LevelProgressionComponent>();
 | 
			
		||||
 | 
			
		||||
				if (!levelProgressionComponent) {
 | 
			
		||||
					return;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				auto level = std::max(static_cast<int32_t>(levelProgressionComponent->GetLevel()), itemTemplate.GetMinLevel());
 | 
			
		||||
 | 
			
		||||
				desc << ModifierTemplate::GenerateHtmlString(modifiers, level);
 | 
			
		||||
 | 
			
		||||
				if (i < availableTemplates.size() - 1) {
 | 
			
		||||
					desc << "\n<font color=\"#D0AB62\">or</font>\n";
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		const auto itemDataOpt = GetAdditionalItemData(itemId);
 | 
			
		||||
 
 | 
			
		||||
@@ -306,3 +306,50 @@ void nejlika::UpgradeEffect::RemoveSkill(LWOOBJID origin) const {
 | 
			
		||||
		inventory->RemoveSkill(equipSkillID);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::string nejlika::UpgradeEffect::GenerateHtmlString(const std::vector<UpgradeEffect>& effects, int32_t level) {
 | 
			
		||||
	std::stringstream ss;
 | 
			
		||||
 | 
			
		||||
	for (const auto& effect : effects) {
 | 
			
		||||
		const auto chance = effect.CalculateChance(level);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		for (const auto& condition : effect.conditions) {
 | 
			
		||||
			if (condition == UpgradeTriggerCondition::None || condition == UpgradeTriggerCondition::UseSkill) {
 | 
			
		||||
				continue;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			ss << "<font color=\"#D0AB62\">";
 | 
			
		||||
 | 
			
		||||
			switch (condition) {
 | 
			
		||||
			case UpgradeTriggerCondition::PrimaryAbility:
 | 
			
		||||
				ss << "On main-hand attack";
 | 
			
		||||
				break;
 | 
			
		||||
			case UpgradeTriggerCondition::Melee:
 | 
			
		||||
				ss << "Requires melee weapon";
 | 
			
		||||
				break;
 | 
			
		||||
			case UpgradeTriggerCondition::TwoHanded:
 | 
			
		||||
				ss << "Requires two-handed weapon";
 | 
			
		||||
				break;
 | 
			
		||||
			case UpgradeTriggerCondition::Shield:
 | 
			
		||||
				ss << "Requires a shield";
 | 
			
		||||
				break;
 | 
			
		||||
			case UpgradeTriggerCondition::Unarmed:
 | 
			
		||||
				ss << "Requires unarmed attack";
 | 
			
		||||
				break;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			ss << "</font>\n\n";
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		if (chance < 1.0f) { 
 | 
			
		||||
			ss << "<font color=\"#FFFFFF\">" << chance * 100 << "%</font><font color=\"#D0AB62\"> chance to trigger</font>\n";
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		std::cout << "Level " << level << " chance: " << chance << std::endl;
 | 
			
		||||
 | 
			
		||||
		ss << ModifierInstance::GenerateHtmlString(effect.GenerateModifiers(level));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return ss.str();
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -39,6 +39,15 @@ public:
 | 
			
		||||
	void AddSkill(LWOOBJID origin) const;
 | 
			
		||||
	void RemoveSkill(LWOOBJID origin) const;
 | 
			
		||||
	
 | 
			
		||||
	/**
 | 
			
		||||
	 * @brief Generate a HTML string representation of a set of upgrade effects.
 | 
			
		||||
	 * 
 | 
			
		||||
	 * @param modifiers The upgrade effects to generate the HTML string for.
 | 
			
		||||
	 * @param level The level of the upgrade effects.
 | 
			
		||||
	 * @return The HTML string.
 | 
			
		||||
	 */
 | 
			
		||||
	static std::string GenerateHtmlString(const std::vector<UpgradeEffect>& effects, int32_t level);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	struct UpgradeScale
 | 
			
		||||
	{
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,8 @@ enum class UpgradeTriggerType
 | 
			
		||||
	OnHit,
 | 
			
		||||
	Equip,
 | 
			
		||||
	UnEquip,
 | 
			
		||||
	Active
 | 
			
		||||
	Active,
 | 
			
		||||
	OnCast
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -767,8 +767,8 @@ void BaseCombatAIComponent::SetTetherSpeed(float value) {
 | 
			
		||||
	m_TetherSpeed = value;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BaseCombatAIComponent::Stun(const float time) {
 | 
			
		||||
	if (m_StunImmune || m_StunTime > time) {
 | 
			
		||||
void BaseCombatAIComponent::Stun(const float time, const bool ignoreImmunity) {
 | 
			
		||||
	if ((!ignoreImmunity && m_StunImmune) || m_StunTime > time) {
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -187,8 +187,9 @@ public:
 | 
			
		||||
	/**
 | 
			
		||||
	 * Stuns the entity for a certain amount of time, will not work if the entity is stun immune
 | 
			
		||||
	 * @param time the time to stun the entity, if stunnable
 | 
			
		||||
	 * @param ignoreImmunity whether to ignore the stun immunity of the entity
 | 
			
		||||
	 */
 | 
			
		||||
	void Stun(float time);
 | 
			
		||||
	void Stun(float time, bool ignoreImmunity = false);
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Gets the radius that will cause this entity to get aggro'd, causing a target chase
 | 
			
		||||
 
 | 
			
		||||
@@ -675,7 +675,7 @@ void DestroyableComponent::Damage(uint32_t damage, const LWOOBJID source, uint32
 | 
			
		||||
 | 
			
		||||
	if (m_Parent->IsPlayer()) {
 | 
			
		||||
		m_Parent->SetNetworkVar<std::string>(u"renderText", damageUIStr, UNASSIGNED_SYSTEM_ADDRESS);
 | 
			
		||||
	} else if (attacker->IsPlayer()) {
 | 
			
		||||
	} else if (attacker != nullptr && attacker->IsPlayer()) {
 | 
			
		||||
		attacker->SetNetworkVar<std::string>(u"renderText", damageUIStr, UNASSIGNED_SYSTEM_ADDRESS);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -31,7 +31,7 @@ ProjectileSyncEntry::ProjectileSyncEntry() {
 | 
			
		||||
 | 
			
		||||
std::unordered_map<uint32_t, uint32_t> SkillComponent::m_skillBehaviorCache = {};
 | 
			
		||||
 | 
			
		||||
Observable<SkillComponent*, uint32_t, bool> SkillComponent::OnSkillCast;
 | 
			
		||||
Observable<SkillComponent*, uint32_t, bool&, uint32_t> SkillComponent::OnSkillCast;
 | 
			
		||||
 | 
			
		||||
bool SkillComponent::CastPlayerSkill(const uint32_t behaviorId, const uint32_t skillUid, RakNet::BitStream& bitStream, const LWOOBJID target, uint32_t skillID) {
 | 
			
		||||
	auto* context = new BehaviorContext(this->m_Parent->GetObjectID());
 | 
			
		||||
@@ -50,7 +50,11 @@ bool SkillComponent::CastPlayerSkill(const uint32_t behaviorId, const uint32_t s
 | 
			
		||||
 | 
			
		||||
	context->ExecuteUpdates();
 | 
			
		||||
 | 
			
		||||
	OnSkillCast(this, skillID, !context->failed);
 | 
			
		||||
	bool success = !context->failed;
 | 
			
		||||
 | 
			
		||||
	OnSkillCast(this, skillID, success, skillUid);
 | 
			
		||||
 | 
			
		||||
	context->failed = !success;
 | 
			
		||||
 | 
			
		||||
	return !context->failed;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -185,8 +185,8 @@ public:
 | 
			
		||||
	 */
 | 
			
		||||
	uint32_t GetUniqueSkillId();
 | 
			
		||||
 | 
			
		||||
	// SkillComponent, SkillID, Success
 | 
			
		||||
	static Observable<SkillComponent*, uint32_t, bool> OnSkillCast;
 | 
			
		||||
	// SkillComponent, SkillID, Success, skillUID
 | 
			
		||||
	static Observable<SkillComponent*, uint32_t, bool&, uint32_t> OnSkillCast;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	/**
 | 
			
		||||
 
 | 
			
		||||
@@ -977,7 +977,7 @@ void GameMessages::SendResurrect(Entity* entity) {
 | 
			
		||||
 | 
			
		||||
				int32_t imaginationToRestore = levelComponent->GetLevel() >= 45 ? 20 : 6;
 | 
			
		||||
				if (imaginationToRestore > destroyableComponent->GetMaxImagination()) imaginationToRestore = destroyableComponent->GetMaxImagination();
 | 
			
		||||
				destroyableComponent->SetImagination(imaginationToRestore);
 | 
			
		||||
				destroyableComponent->SetImagination(destroyableComponent->GetMaxImagination());
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user