package ru.dbotthepony.kstarbound.player import com.google.gson.JsonElement import com.google.gson.JsonObject import com.google.gson.JsonPrimitive import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet import ru.dbotthepony.kommons.guava.immutableMap import ru.dbotthepony.kstarbound.Registries import ru.dbotthepony.kstarbound.Registry import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.defs.player.TechDefinition import ru.dbotthepony.kstarbound.lua.NewLuaState import ru.dbotthepony.kstarbound.lua.luaFunction import ru.dbotthepony.kstarbound.lua.luaFunction0String import ru.dbotthepony.kstarbound.lua.luaFunctionN import ru.dbotthepony.kstarbound.lua.luaStub import ru.dbotthepony.kstarbound.lua.set import ru.dbotthepony.kstarbound.util.ItemStack import java.util.* import kotlin.collections.ArrayList /** * Персонаж - как он есть. * * [Avatar] реализует Lua интерфейс `player`. */ class Avatar(val uniqueId: UUID) { enum class EssentialSlot { BEAM_AXE, WIRE_TOOL, PAINT_TOOL, INSPECTION_TOOL; } enum class EquipmentSlot { HEAD, CHEST, LEGS, BACK, HEAD_COSMETIC, CHEST_COSMETIC, LEGS_COSMETIC, BACK_COSMETIC; } private val essentialSlots = EnumMap(EssentialSlot::class.java) private val equipmentSlots = EnumMap(EquipmentSlot::class.java) private val bags = ArrayList() private val quests = Object2ObjectOpenHashMap() var cursorItem = ItemStack.EMPTY private val availableTechs = ObjectOpenHashSet>() private val enabledTechs = ObjectOpenHashSet>() private val equippedTechs = Object2ObjectOpenHashMap>() private val knownBlueprints = ObjectOpenHashSet() // С подписью NEW private val newBlueprints = ObjectOpenHashSet() private val currencies = Object2LongOpenHashMap() /** * Teaches the player any recipes which can be used to craft the specified item. */ fun giveBlueprint(name: String): Boolean { val item = Starbound.item(name).conciseToNull() ?: return false if (knownBlueprints.add(item)) { newBlueprints.add(item) return true } return false } /** * Returns `true` if the player knows one or more recipes to create the specified item and `false` otherwise. */ fun blueprintKnown(name: String): Boolean { return (Starbound.item(name).conciseToNull() ?: return false) in knownBlueprints } /** * Returns `true` if the player knows one or more recipes to create the specified item and `false` otherwise. */ private fun blueprintKnown(name: JsonElement): Boolean { if (name is JsonPrimitive) { return (Starbound.item(name.asString).conciseToNull() ?: return false) in knownBlueprints } else if (name is JsonObject) { return (Starbound.item(name).conciseToNull() ?: return false) in knownBlueprints } else { return false } } /** * Teaches the player any recipes which can be used to craft the specified item. */ private fun giveBlueprint(name: JsonElement): Boolean { val item: ItemStack if (name is JsonPrimitive) { item = Starbound.item(name.asString).conciseToNull() ?: return false } else if (name is JsonObject) { item = Starbound.item(name).conciseToNull() ?: return false } else { return false } if (knownBlueprints.add(item)) { newBlueprints.add(item) return true } return false } /** * Adds the specified tech to the player's list of available (unlockable) techs. */ fun makeTechAvailable(name: String): Boolean { return availableTechs.add(Registries.techs[name] ?: return false) } /** * Removes the specified tech from player's list of available (unlockable) techs. */ fun makeTechUnavailable(name: String): Boolean { val tech = Registries.techs[name] ?: return false if (availableTechs.remove(tech)) { enabledTechs.remove(tech) equippedTechs.remove(tech.value.type) return true } return false } /** * Unlocks the specified tech, allowing it to be equipped through the tech GUI. */ fun enableTech(name: String): Boolean { val tech = Registries.techs[name] ?: return false availableTechs.add(tech) return enabledTechs.add(tech) } /** * Equips the specified tech. */ fun equipTech(name: String): Boolean { val tech = Registries.techs[name] ?: return false availableTechs.add(tech) enabledTechs.add(tech) return equippedTechs.put(tech.value.type, tech) != tech } /** * Unequips the specified tech. */ fun unequipTech(name: String): Boolean { val tech = Registries.techs[name] ?: return false return equippedTechs.remove(tech.value.type) == tech } /** * Returns the player's current total reserves of the specified currency. */ fun currency(name: String): Long { return currencies.getLong(name) } /** * Increases the player's reserve of the specified currency by the specified amount. */ fun addCurrency(name: String, amount: Long) { check(amount >= 0L) { "Negative amount of currency: $amount (currency: $name)" } currencies.computeLong(name) { key, old -> (old ?: 0L) + amount } } /** * Attempts to consume the specified amount of the specified currency and returns `true` if successful and `false` otherwise. */ fun consumeCurrency(name: String, amount: Long): Boolean { check(amount >= 0L) { "Negative amount of currency: $amount (currency: $name)" } val current = currencies.getLong(name) if (current - amount >= 0L) { currencies[name] = current - amount return true } return false } /** * Triggers an immediate cleanup of the player's inventory, removing item stacks with 0 quantity. May rarely be required in special cases of making several sequential modifications to the player's inventory within a single tick. */ fun cleanupItems() { TODO() } /** * Adds the specified item to the player's inventory. */ fun giveItem(descriptor: ItemStack) { TODO() } /** * Returns `true` if the player's inventory contains an item matching the specified descriptor and `false` otherwise. If exactMatch is `true` then parameters as well as item name must match. */ fun hasItem(descriptor: ItemStack, exactMatch: Boolean = false): Boolean { return false } /** * Returns the total number of items in the player's inventory matching the specified descriptor. If exactMatch is `true` then parameters as well as item name must match. */ fun hasCountOfItem(descriptor: ItemStack, exactMatch: Boolean = false): Long { return 0L } /** * Attempts to consume the specified item from the player's inventory and returns the item consumed if successful. If consumePartial is `true`, matching stacks totalling fewer items than the requested count may be consumed, otherwise the operation will only be performed if the full count can be consumed. If exactMatch is `true` then parameters as well as item name must match. */ fun consumeItem(descriptor: ItemStack, allowPartial: Boolean = false, exactMatch: Boolean = false): ItemStack { return ItemStack.EMPTY } fun inventoryTags(): Map { return mapOf() } fun itemsWithTag(): List { return listOf() } fun consumeTaggedItem(tag: String): Long { return 0L } fun hasItemWithParameter(name: String, value: JsonElement): Boolean { return false } fun consumeItemWithParameter(name: String, value: JsonElement, count: Long): Long { return 0L } fun getItemWithParameter(name: String, value: JsonElement): ItemStack { return ItemStack.EMPTY } var primaryHandItem: ItemStack? = null var altHandItem: ItemStack? = null fun essentialItem(slotName: EssentialSlot): ItemStack? { return essentialSlots[slotName]?.conciseToNull() } fun giveEssentialItem(slotName: EssentialSlot, item: ItemStack) { } fun removeEssentialItem(slotName: EssentialSlot) { } fun equippedItem(slotName: EquipmentSlot): ItemStack { return equipmentSlots[slotName] ?: ItemStack.EMPTY } fun setEquippedItem(slotName: EquipmentSlot, item: ItemStack) { } fun addQuest(quest: QuestInstance): QuestInstance? { check(quest.avatar === this) { "$quest does not belong to $this" } return quests.put(quest.id, quest) } private fun startQuest(value: JsonElement, serverID: String?, worldID: String?): String { if (value is JsonPrimitive) { val quest = QuestInstance(this, descriptor = QuestDescriptor(value.asString), serverID = serverID?.let(UUID::fromString), worldID = worldID) addQuest(quest) return quest.id } else if (value is JsonObject) { val seed = value["seed"]?.asLong ?: QuestDescriptor.makeSeed() val questId = value["questId"]?.asString ?: throw IllegalArgumentException("Invalid 'questId' in quest descriptor") val templateId = value["templateId"]?.asString ?: questId val params = value["parameters"] as? JsonObject ?: JsonObject() val quest = QuestInstance(this, descriptor = QuestDescriptor(questId, templateId, seed, params), serverID = serverID?.let(UUID::fromString), worldID = worldID) addQuest(quest) return quest.id } else { throw IllegalArgumentException("Invalid quest descriptor: $value") } } fun provideBindings(lua: NewLuaState) { val table = lua.state.newTable() lua.env["player"] = table lua.env["uniqueId"] = luaFunction { it.returnBuffer.setTo(uniqueId.toString()) } lua.env["species"] = luaFunction { it.returnBuffer.setTo("human") } lua.env["gender"] = luaFunction { it.returnBuffer.setTo("male") } lua.env["giveBlueprint"] = luaFunction0String("giveBlueprint", ::giveBlueprint) lua.env["blueprintKnown"] = luaFunction0String("blueprintKnown", ::blueprintKnown) lua.env["makeTechAvailable"] = luaFunction0String("makeTechAvailable", ::makeTechAvailable) lua.env["makeTechUnavailable"] = luaFunction0String("makeTechUnavailable", ::makeTechUnavailable) lua.env["enableTech"] = luaFunction0String("enableTech", ::enableTech) lua.env["equipTech"] = luaFunction0String("equipTech", ::equipTech) lua.env["unequipTech"] = luaFunction0String("unequipTech", ::unequipTech) lua.env["availableTechs"] = luaFunction { val result = it.newTable(availableTechs.size, 0) for ((i, v) in availableTechs.withIndex()) { result[i + 1] = v } it.returnBuffer.setTo(result) } lua.env["enabledTechs"] = luaFunction { val result = it.newTable(enabledTechs.size, 0) for ((i, v) in enabledTechs.withIndex()) { result[i + 1] = v } it.returnBuffer.setTo(result) } lua.env["equippedTech"] = luaFunctionN("equippedTech") { it, args -> equippedTechs[args.nextString().decode()]?.key } lua.env["currency"] = luaFunction0String("currency", ::currency) lua.env["addCurrency"] = luaFunctionN("addCurrency") { it, args -> addCurrency(args.nextString().decode(), args.nextInteger()) } lua.env["consumeCurrency"] = luaFunctionN("consumeCurrency") { it, args -> consumeCurrency(args.nextString().decode(), args.nextInteger()) } lua.env["cleanupItems"] = luaFunction { cleanupItems() } lua.env["giveItem"] = luaStub("giveItem") lua.env["hasItem"] = luaStub("hasItem") lua.env["hasCountOfItem"] = luaStub("hasCountOfItem") lua.env["consumeItem"] = luaStub("consumeItem") lua.env["inventoryTags"] = luaStub("inventoryTags") lua.env["itemsWithTag"] = luaStub("itemsWithTag") lua.env["consumeTaggedItem"] = luaStub("consumeTaggedItem") lua.env["hasItemWithParameter"] = luaStub("hasItemWithParameter") lua.env["consumeItemWithParameter"] = luaStub("consumeItemWithParameter") lua.env["getItemWithParameter"] = luaStub("getItemWithParameter") lua.env["primaryHandItem"] = luaStub("primaryHandItem") lua.env["altHandItem"] = luaStub("altHandItem") lua.env["primaryHandItemTags"] = luaStub("primaryHandItemTags") lua.env["altHandItemTags"] = luaStub("altHandItemTags") lua.env["essentialItem"] = luaStub("essentialItem") lua.env["giveEssentialItem"] = luaStub("giveEssentialItem") lua.env["removeEssentialItem"] = luaStub("removeEssentialItem") lua.env["equippedItem"] = luaStub("equippedItem") lua.env["setEquippedItem"] = luaStub("setEquippedItem") lua.env["swapSlotItem"] = luaStub("swapSlotItem") lua.env["setSwapSlotItem"] = luaStub("setSwapSlotItem") lua.env["startQuest"] = luaStub("startQuest") lua.env["hasQuest"] = luaStub("hasQuest") lua.env["hasCompletedQuest"] = luaStub("hasCompletedQuest") } companion object { private val essentialSlotsMap = immutableMap { put("beamaxe", EssentialSlot.BEAM_AXE) put("inspectiontool", EssentialSlot.INSPECTION_TOOL) put("wiretool", EssentialSlot.WIRE_TOOL) put("painttool", EssentialSlot.PAINT_TOOL) } private val equipmentSlotsMap = immutableMap { put("head", EquipmentSlot.HEAD) put("chest", EquipmentSlot.CHEST) put("legs", EquipmentSlot.LEGS) put("back", EquipmentSlot.BACK) put("headCosmetic", EquipmentSlot.HEAD_COSMETIC) put("chestCosmetic", EquipmentSlot.CHEST_COSMETIC) put("legsCosmetic", EquipmentSlot.LEGS_COSMETIC) put("backCosmetic", EquipmentSlot.BACK_COSMETIC) } } }