turns-00046.parquet:218
336e37ae4e83af5908040397
turn 1/2gpt-4o-mini-2024-07-18EnglishKazakhstan1367 words
degenerate_repetitionAbsentFinal dense release
USER
Проанализируй данный класс. Есть ли советы по улучшению? Как бы ты произвёл рефакторинг?
```kotlin
class VillagerQuestManager(private val plugin: QuestIntelligence) {
private val professionItems: MutableMap<Profession, Map<Material, Pair<Int, Int>>> = mutableMapOf()
init {
this.initializeProfessionItems()
}
private fun initializeProfessionItems() {
Registry.VILLAGER_PROFESSION.forEach { profession ->
if (profession == Profession.NONE)
return@forEach
// Создаем карту для хранения предметов
val items = mutableMapOf<Material, Pair<Int, Int>>()
// Извлекаем и обрабатываем список предметов
plugin.config.getStringList("profession.$profession.item-priority").forEach { line ->
val (materialName, amountRange) = line.split("~")
val (min, max) = amountRange.split("-").map(String::toInt).let { range ->
if (range.size == 1) range[0] to range[0] else range[0] to range[1]
}
val amount = min to max
if (materialName.contains('@')) {
Material.entries.filter { material: Material -> material.toString().contains(materialName.removePrefix("@")) }.forEach {
items[it] = amount
}
} else items[Material.valueOf(materialName)] = amount
}
professionItems[profession] = items
}
}
fun prepareQuest() {
plugin.debug("=== --- === --- === --- === --- === --- === --- === --- === --- ===")
plugin.debug("Time for a new quest! Generating...")
if (enabledWorlds.isEmpty()) {
plugin.logger.severe("No target worlds in config.yml! Disabling the plugin.")
plugin.server.pluginManager.disablePlugin(plugin)
}
plugin.debug("Looking for a random villager...")
val villager = this.getRandomVillager() ?: run {
plugin.debug("Can't find a villager. Cancelling.")
return
}
plugin.debug("Found a villager! Randomizing quest type...")
val questType = this.determineQuestType(villager)
plugin.debug("Quest type is $questType. Selecting the quest item!")
val questItem = when (questType) {
QuestType.DISC -> ItemStack(discs[Random.nextInt(discs.size)])
QuestType.PERSONAL_VILLAGER_DATA -> ItemStack(Material.AIR)
QuestType.OMINOUS_BANNER -> ominousBanner;
else -> villager.prioritizedItem
}
plugin.debug("Quest item is ${questItem.type}. Building a quest...")
val quest = this.buildQuest(questType, villager, questItem) ?: return
this.requestQuestInfo(villager, quest)
}
private fun buildQuest(questType: QuestType, villager: Villager, questItem: ItemStack): VillagerQuest.Builder? {
val quest = VillagerQuest.Builder().setQuestType(questType).setQuestItem(questItem)
plugin.debug("Generating a quest reward...")
val questReward = when (questType) {
QuestType.DISC, QuestType.PROFESSION_ITEM_GATHERING, QuestType.OMINOUS_BANNER -> {
val itemPrice = when (questType) {
QuestType.OMINOUS_BANNER -> plugin.configurationClip.promptsConfig.getInt("ominous-banner.reward-points")
else -> this.calculateItemPrice(questItem)
}
val villagerItems = villager.quillInventory.filterNotNull().filter { item -> this.calculateItemPrice(item) > 0 && !this.isProfessionItem(villager.profession, item) }
val inventoryPrice = this.calculateItemListPrice(villagerItems)
plugin.debug("Item price: $itemPrice.")
plugin.debug("Inventory price: $inventoryPrice.")
if (itemPrice > inventoryPrice) {
plugin.debug("Item price is bigger than inventory price. Cancelling quest generation.")
return null
}
// Если наград больше одной, используется мешочек
val barterItems = this.gatherBarterItems(questType, questItem, inventoryPrice, villagerItems)
if (barterItems.size > 1) this.bundle(barterItems) else {
// Если нет предметов, которые житель мог бы отдать в качестве награды за квест
if (barterItems.isEmpty()) {
plugin.debug("Villager have no items to use them as a reward. Cancelling quest generation.")
return null
}
else barterItems[0]
}
}
else -> ItemStack(Material.AIR)
}
plugin.debug("Quest item is: [${questReward.type}, amount is ${questReward.amount}]!")
return quest.setRewardItem(questReward)
}
private val ominousBanner: ItemStack by lazy {
CraftItemStack.asBukkitCopy(Raid.getLeaderBannerInstance((enabledWorlds[0] as CraftWorld).handle.registryAccess().lookupOrThrow(Registries.BANNER_PATTERN)))
}
private val enabledWorlds: List<World> by lazy {
mutableListOf<World>().apply {
plugin.config.getStringList("core-settings.enabled-worlds").forEach {
world -> add(Bukkit.getWorld(world)!!)
}
}
}
fun getRandomVillager(): Villager? {
val villagers = enabledWorlds.random().entities.filterIsInstance<Villager>().filter { this.canGenerateQuest(it) }
return villagers.randomOrNull()
}
private fun determineQuestType(villager: Villager): QuestType {
val availableQuests = arrayOf( /* QuestType.PROFESSION_ITEM_GATHERING, QuestType.DISC, */ QuestType.OMINOUS_BANNER)
return if (villager.personalData == null) {
QuestType.PERSONAL_VILLAGER_DATA
} else {
availableQuests.random()
}
}
private fun canGenerateQuest(villager: Villager): Boolean {
return villager.currentQuest == null && villager.profession != Villager.Profession.NONE
}
private fun requestQuestInfo(villager: Villager, quest: VillagerQuest.Builder) {
plugin.server.scheduler.runTaskAsynchronously(plugin) { _ ->
val placeholders = createPlaceholders(villager, quest.questItem, quest.rewardItem)
val filledPrompt = fillPromptWithPlaceholders(quest.questType, placeholders)
plugin.questGenerator.generateQuestData(villager, quest, filledPrompt)
}
}
private fun createPlaceholders(villager: Villager, questItem: ItemStack, rewardItem: ItemStack): Map<String, String> {
val args = if (villager.personality == VillagerPersonality.ANGRY || villager.personality == VillagerPersonality.DRUNKARD) ", '15% of words are swearing'" else ""
return mapOf(
"villagerName" to if (villager.customName() != null) (villager.customName() as TextComponent).content() else "Anonymous",
"villagerType" to "${villager.villagerType}",
"villagerProfession" to "${villager.profession}",
"villagerPersonality" to "${villager.personality}",
"villagerLevel" to "${villager.villagerLevel}",
"questItem" to questItem.type.name.replace('_', ' ').lowercase(),
"rewardItems" to "${if (rewardItem.type == Material.BUNDLE) (rewardItem.itemMeta as BundleMeta).items.format() else rewardItem.type}",
"promptRules" to "['Use ${plugin.config.getString("core-settings.language")} language'$args]",
"language" to "${plugin.config.getString("core-settings.language")}",
"randomLetter" to this.getRandomLetter()
)
}
private fun fillPromptWithPlaceholders(questType: QuestType, placeholders: Map<String, String>): String {
return placeholders.entries.fold(plugin.configurationClip.promptsConfig.getString(questType.promptConfigPath)!!) { acc, entry ->
acc.replace("{${entry.key}}", entry.value)
}
}
private fun getRandomLetter(): String {
val letters = 'A'..'Z'
val randomIndex = Random.nextInt(letters.count())
return letters.elementAt(randomIndex).toString()
}
private fun isProfessionItem(profession: Profession, item: ItemStack): Boolean =
item.type in professionItems[profession]?.keys.orEmpty()
/**
* Создает мешочек с заданным списком итемов внутри.
*
* @param items Список предметов, которые будут упакованы в мешочек.
*/
private fun bundle(items: List<ItemStack>): ItemStack {
return ItemStack(Material.BUNDLE, 1).apply {
itemMeta = (itemMeta as BundleMeta).apply {
items.forEach(::addItem)
}
}
}
private fun gatherBarterItems(
questType: QuestType,
questItem: ItemStack,
inventoryPrice: Int,
villagerItems: List<ItemStack>
): MutableList<ItemStack> {
val questItemPrice = when (questType) {
QuestType.OMINOUS_BANNER -> plugin.configurationClip.promptsConfig.getInt("ominous-banner.reward-points")
else -> this.getMaterialPrice(questItem.type)
}
// Emeralds are first
val sortedInventory = villagerItems.sortedBy { if (it.type == Material.EMERALD) 0 else 1 }
if (this.calculateItemPrice(questItem) <= 0) {
plugin.logger.severe("${questItem.type} price is zero! Fix it and reload the plugin!")
return mutableListOf()
}
val possibleAmount = inventoryPrice / questItemPrice
val requiredRewardPrice = minOf(questItem.amount, possibleAmount) * questItemPrice
val rewardItems = mutableListOf<ItemStack>()
// Проходимся по инвентарю с целью найти предметы которыми можно заплатить за questItem
for (iterableItem in sortedInventory) {
// Пропускаем не подходящие по цене предметы и исключаем "одинаковые сделки"
if (this.getMaterialPrice(iterableItem.type) > requiredRewardPrice || iterableItem.type == questItem.type)
continue
// Считаем поштучно
for (i in 1..iterableItem.amount) {
val currentRewardPrice = this.calculateItemListPrice(rewardItems)
if (currentRewardPrice >= requiredRewardPrice)
return rewardItems
val existingItem = rewardItems.find { it.type == iterableItem.type }
if (existingItem != null) {
existingItem.amount = i
} else {
rewardItems.add(iterableItem.clone().apply { amount = i })
}
}
}
return rewardItems
}
/**
* Вычисляет общую цену списка предметов.
*
* @param itemList список предметов.
* @return общая цена.
*/
private fun calculateItemListPrice(itemList: List<ItemStack>): Int {
return itemList.sumOf { this.calculateItemPrice(it) }
}
/**
* Вычисляет цену отдельного предмета. Учитывает количество предметов в стаке!
*
* @param item предмет, для которого вычисляется цена.
* @return цена предмета.
*/
private fun calculateItemPrice(item: ItemStack): Int {
return (this.getMaterialPrice(item.type) * item.amount)
}
private fun getMaterialPrice(material: Material, defaultPrice: Int = 50): Int {
val pricingConfig = plugin.configurationClip.pricesConfig
return if (pricingConfig.contains(material.name))
pricingConfig.getInt(material.name)
else defaultPrice.also {
plugin.logger.info("Price for material $material not found. Updating configuration... Default price is $defaultPrice.")
plugin.logger.info("Configuration reloaded automatically.")
pricingConfig.set(material.name, it)
pricingConfig.save(File(plugin.dataFolder, "prices.yml"))
plugin.reloadConfigurations()
}
}
companion object {
private val discs = Material.entries.filter { material: Material -> material.isRecord }
}
}
```ASSISTANT
Класс `VillagerQuestManager` является достаточно сложным и реализует множество функций, связанных с управлением квестами для жителей. Тем не менее, есть несколько аспектов и областей, где можно провести рефакторинг или улучшение:
### Советы по улучшению
1. **Разделение ответственности**:
- Класс выполняет много задач: создание квестов, инициализация профессий, управление материальными ценами и логика квестов. Рассмотрите возможность выделения отдельных классов для каждой ответственности. Например, создайте `QuestBuilder`, `ProfessionManager`, `PriceManager` и т.д.
2. **Устранение дублирования кода**:
- Некоторые участки кода могут быть вынесены в отдельные методы, чтобы решить проблемы с дублированием, особенно в методах, которые обрабатывают квесты и награды.
3. **Улучшение читаемости**:
- Названия некоторых методов и переменных могут быть более описательными. Например, вместо `gatherBarterItems` можно использовать `collectBarterableItems`, чтобы лучше отразить суть.
4. **Упрощение условий**:
- В некоторых местах условия можно упростить. Например, в `requestQuestInfo` используйте `let` или `run` в комбинации с операторами безопасного вызова (`?.`) для упрощения работы с объектами.
5. **Использование `when` вместо `if` для более четкой логики ветвления**:
- В методах, где у вас есть несколько условий, вместо `if` конструкций чаще используйте `when`. Это сделает код более понятным.
6. **Улучшение работы с конфигурациями**:
- Вместо многоуровневого доступа к настройкам можете создать отдельные методы доступа для каждого типа настройки, что улучшит читаемость.
7. **Использование типов данных**:
- Вместо использования `Pair<Int, Int>` можно рассмотреть создание собственного класса (например, `Range`) для более четкого представления диапазона.
8. **Логгирование**:
- Убедитесь, что логгирование произвоится не избыточно, а только в тех местах, где это действительно необходимо для отслеживания состояния кодовой базы. Это поможет уменьшить шум в логах.
### Пример рефакторинга:
Вот пример небольшого рефакторинга одного из методов для улучшения читаемости:
```kotlin
private fun determineQuestType(villager: Villager): QuestType {
return when {
villager.personalData == null -> QuestType.PERSONAL_VILLAGER_DATA
else -> availableQuestTypes.random()
}
}
// Определяем доступные типы квестов в сопроводительном поле класса
private val availableQuestTypes = arrayOf(QuestType.OMINOUS_BANNER)
```
### Структура:
#### 1. ProfessionManager
- Управляет всем, что связано с профессиями жителей.
#### 2. QuestBuilder
- Содержит логику для построения квестов, какую награду выдать и как она должна быть сформирована.
#### 3. PriceManager
- Отвечает за согласование цен на материалы и их получение из конфигурации.
#### 4. QuestInfoRequester
- Отвечает за запрос информации о квестах и обновление состояния квестов.
### Заключение
Рефакторинг данного класса может значительно улучшить его читаемость, поддержку и тестируемость. Также стоит уделить внимание производительности, учитывая логику работы с большими данными (нужно учитывать потенциальные узкие места в производительности, например, в циклах и повторном доступе к отдельным структурам данных).