393 lines
13 KiB
Kotlin
393 lines
13 KiB
Kotlin
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, ItemStack>(EssentialSlot::class.java)
|
||
private val equipmentSlots = EnumMap<EquipmentSlot, ItemStack>(EquipmentSlot::class.java)
|
||
private val bags = ArrayList<AvatarBag>()
|
||
private val quests = Object2ObjectOpenHashMap<String, QuestInstance>()
|
||
|
||
var cursorItem = ItemStack.EMPTY
|
||
|
||
private val availableTechs = ObjectOpenHashSet<Registry.Entry<TechDefinition>>()
|
||
private val enabledTechs = ObjectOpenHashSet<Registry.Entry<TechDefinition>>()
|
||
private val equippedTechs = Object2ObjectOpenHashMap<String, Registry.Entry<TechDefinition>>()
|
||
|
||
private val knownBlueprints = ObjectOpenHashSet<ItemStack>()
|
||
// С подписью NEW
|
||
private val newBlueprints = ObjectOpenHashSet<ItemStack>()
|
||
|
||
private val currencies = Object2LongOpenHashMap<String>()
|
||
|
||
/**
|
||
* 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<String, Long> {
|
||
return mapOf()
|
||
}
|
||
|
||
fun itemsWithTag(): List<ItemStack> {
|
||
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<String, EssentialSlot> {
|
||
put("beamaxe", EssentialSlot.BEAM_AXE)
|
||
put("inspectiontool", EssentialSlot.INSPECTION_TOOL)
|
||
put("wiretool", EssentialSlot.WIRE_TOOL)
|
||
put("painttool", EssentialSlot.PAINT_TOOL)
|
||
}
|
||
|
||
private val equipmentSlotsMap = immutableMap<String, EquipmentSlot> {
|
||
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)
|
||
}
|
||
}
|
||
}
|