Essence storage, servo, capsules

This commit is contained in:
DBotThePony 2023-03-13 18:10:02 +07:00
parent 5748908ac2
commit 40da26033d
Signed by: DBot
GPG Key ID: DCC23B5715498507
23 changed files with 951 additions and 5 deletions

View File

@ -362,6 +362,7 @@ private fun blocks(provider: MatteryLanguageProvider) {
add(MBlocks.DRIVE_VIEWER, "Drive Viewer")
add(MBlocks.BLACK_HOLE, "Local Anomalous Singular Gravitation Field")
add(MBlocks.COBBLESTONE_GENERATOR, "Cobblestone Generator")
add(MBlocks.ESSENCE_STORAGE, "Essence Storage")
add(MBlocks.ENGINE, "Ship Engine")
add(MBlocks.HOLO_SIGN, "Holo Sign")
@ -445,6 +446,12 @@ private fun items(provider: MatteryLanguageProvider) {
add(MItems.ExopackUpgrades.INVENTORY_UPGRADE_PROCEDURAL, "Indescribable Exopack Inventory Upgrade")
add(MItems.ExopackUpgrades.INVENTORY_UPGRADE_PROCEDURAL, "description", "They normally generate in dungeons with appropriate NBT tag attached")
add(MItems.ESSENCE_CAPSULE, "Essence Capsule")
add(MItems.ESSENCE_DRIVE, "Essence Memory Drive")
add(MItems.ESSENCE_SERVO, "Essence Servo")
add(MItems.ESSENCE_SERVO, "desc", "Allows to 'pump' essence involving fleshy humanoids")
add(MItems.ESSENCE_SERVO, "desc2", "Can be used standalone, or as tool inside Essence Servo")
add(MItems.NUTRIENT_PASTE, "Nutrient Paste")
add(MItems.BLACK_HOLE_SCANNER, "Singularity Scanner")
@ -657,6 +664,18 @@ private fun gui(provider: MatteryLanguageProvider) {
gui("matter_panel.matter_stored", "Matter stored: %s")
gui("matter_panel.matter_required", "Matter required: %s")
gui("matter_panel.complexity", "Total complexity: %s")
gui("experience", "%s experience points")
gui("experience_levels", "%s experience levels")
gui("experience.store", "Store %s experience levels")
gui("experience.store_all", "Store all experience levels")
gui("experience.dispense", "Dispense %s experience levels")
gui("experience.dispense_all", "Dispense all experience levels")
gui("experience.set_exact", "Set your experience level to %s")
gui("essence_capsule", "(Almost) Everything you ever knew is within")
gui("essence_capsule2", "This item can be recycled at Essence Servo")
}
}

View File

@ -369,6 +369,7 @@ private fun blocks(provider: MatteryLanguageProvider) {
add(MBlocks.DRIVE_VIEWER, "Просмотрщик дисков конденсации")
add(MBlocks.BLACK_HOLE, "Локализированное аномальное сингулярное гравитационное поле")
add(MBlocks.COBBLESTONE_GENERATOR, "Генератор булыжника")
add(MBlocks.ESSENCE_STORAGE, "Хранилище эссенции")
add(MBlocks.ENGINE, "Двигатель корабля")
add(MBlocks.HOLO_SIGN, "Голографическая табличка")
@ -450,6 +451,12 @@ private fun items(provider: MatteryLanguageProvider) {
add(MItems.ExopackUpgrades.INVENTORY_UPGRADE_PROCEDURAL, "Неописуемое обновление инвентаря экзопака")
add(MItems.ExopackUpgrades.INVENTORY_UPGRADE_PROCEDURAL, "description", "В нормальных условиях, они появляются в сундуках")
add(MItems.ESSENCE_CAPSULE, "Капсула эссенции")
add(MItems.ESSENCE_DRIVE, "Диск эссенции")
add(MItems.ESSENCE_SERVO, "Помпа эссенции")
add(MItems.ESSENCE_SERVO, "desc", "Позволяет 'перекачивать' эссенцию гуманоидов из плоти")
add(MItems.ESSENCE_SERVO, "desc2", "Может быть использовано напрямую, или как инструмент внутри хранилища эссенции")
add(MItems.NUTRIENT_PASTE, "Питательная паста")
add(MItems.BLACK_HOLE_SCANNER, "Сканер сингулярностей")
@ -659,6 +666,18 @@ private fun gui(provider: MatteryLanguageProvider) {
gui("matter_panel.matter_stored", "Материи хранится: %s")
gui("matter_panel.matter_required", "Материи требуется: %s")
gui("matter_panel.complexity", "Общая сложность: %s")
gui("experience", "%s очков опыта")
gui("experience_levels", "%s уровней опыта")
gui("experience.store", "Передать %s уровней опыта")
gui("experience.store_all", "Передать все уровни опыта")
gui("experience.dispense", "Вывести %s уровней опыта")
gui("experience.dispense_all", "Вывести все уровни опыта")
gui("experience.set_exact", "Установить уровень опыта в %s")
gui("essence_capsule", "(Почти) Всё, что вы знали, хранится внутри")
gui("essence_capsule2", "Данный предмет может быть переработан внутри хранилища эссенции")
}
}

View File

@ -225,7 +225,7 @@ abstract class MatteryBlock @JvmOverloads constructor(
}
companion object {
val DEFAULT_PROPERTIES: Properties = Properties.of(Material.STONE, MaterialColor.METAL).requiresCorrectToolForDrops().strength(1.5f, 25.0f)
val DEFAULT_PROPERTIES: Properties = Properties.of(Material.STONE, MaterialColor.METAL).requiresCorrectToolForDrops().destroyTime(1.5f).explosionResistance(25.0f)
}
}

View File

@ -0,0 +1,56 @@
package ru.dbotthepony.mc.otm.block.entity.tech
import net.minecraft.core.BlockPos
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.entity.player.Player
import net.minecraft.world.inventory.AbstractContainerMenu
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.block.state.BlockState
import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity
import ru.dbotthepony.mc.otm.container.HandlerFilter
import ru.dbotthepony.mc.otm.container.MatteryContainer
import ru.dbotthepony.mc.otm.item.EssenceCapsuleItem
import ru.dbotthepony.mc.otm.menu.tech.EssenceStorageMenu
import ru.dbotthepony.mc.otm.registry.MBlockEntities
class EssenceStorageBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryDeviceBlockEntity(MBlockEntities.ESSENCE_STORAGE, blockPos, blockState) {
var experienceStored = 0L
set(value) {
require(value >= 0L) { "Negative experience: $value" }
field = value
setChangedLight()
}
val capsuleContainer = MatteryContainer(::setChangedLight, 1)
val servoContainer = MatteryContainer(::setChangedLight, 1)
init {
savetables.long(::experienceStored)
savetables.stateful(::capsuleContainer)
savetables.stateful(::servoContainer)
}
override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu {
return EssenceStorageMenu(containerID, inventory, this)
}
val itemConfig = ConfigurableItemHandler(
input = capsuleContainer.handler(HandlerFilter.OnlyIn.and(object : HandlerFilter {
override fun canInsert(slot: Int, stack: ItemStack): Boolean {
return stack.item is EssenceCapsuleItem
}
}))
)
override fun tick() {
super.tick()
val capsule = capsuleContainer[0]
if (!capsule.isEmpty && capsule.item is EssenceCapsuleItem) {
experienceStored += EssenceCapsuleItem.experienceStored(capsule)
capsuleContainer.clearContent()
}
}
}

View File

@ -0,0 +1,39 @@
package ru.dbotthepony.mc.otm.block.tech
import net.minecraft.core.BlockPos
import net.minecraft.world.InteractionHand
import net.minecraft.world.InteractionResult
import net.minecraft.world.entity.player.Player
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.EntityBlock
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.entity.BlockEntityTicker
import net.minecraft.world.level.block.entity.BlockEntityType
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.phys.BlockHitResult
import ru.dbotthepony.mc.otm.block.RotatableMatteryBlock
import ru.dbotthepony.mc.otm.block.entity.tech.EssenceStorageBlockEntity
import ru.dbotthepony.mc.otm.registry.MItems
class EssenceStorageBlock : RotatableMatteryBlock(), EntityBlock {
override fun newBlockEntity(pPos: BlockPos, pState: BlockState): BlockEntity {
return EssenceStorageBlockEntity(pPos, pState)
}
override fun <T : BlockEntity?> getTicker(pLevel: Level, pState: BlockState, pBlockEntityType: BlockEntityType<T>): BlockEntityTicker<T>? {
if (!pLevel.isClientSide) {
return BlockEntityTicker { _, _, _, pBlockEntity -> if (pBlockEntity is EssenceStorageBlockEntity) pBlockEntity.tick() }
}
return null
}
@Suppress("OVERRIDE_DEPRECATION")
override fun use(blockState: BlockState, level: Level, blockPos: BlockPos, ply: Player, hand: InteractionHand, blockHitResult: BlockHitResult): InteractionResult {
if (ply.getItemInHand(hand).item == MItems.ESSENCE_SERVO) {
return MItems.ESSENCE_SERVO.useServo(ply, blockPos)
}
return super.use(blockState, level, blockPos, ply, hand, blockHitResult)
}
}

View File

@ -3,6 +3,8 @@ package ru.dbotthepony.mc.otm.client.screen.panels
import com.mojang.blaze3d.vertex.PoseStack
import net.minecraft.client.gui.screens.Screen
import net.minecraft.network.chat.Component
import ru.dbotthepony.mc.otm.client.render.TextAlign
import ru.dbotthepony.mc.otm.client.render.drawAligned
import ru.dbotthepony.mc.otm.core.TextComponent
import ru.dbotthepony.mc.otm.core.math.RGBAColor
@ -27,6 +29,7 @@ open class Label<out S : Screen> @JvmOverloads constructor(
}
var color = RGBAColor.SLATE_GRAY
var align = TextAlign.TOP_RIGHT
override fun innerRender(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float) {
clearDepth(stack)
@ -35,7 +38,17 @@ open class Label<out S : Screen> @JvmOverloads constructor(
font.draw(stack, text, shadowX, shadowY, shadowColor.toInt())
}
font.draw(stack, text, 0f, 0f, color.toInt())
when (align) {
TextAlign.TOP_LEFT -> font.drawAligned(stack, text, align, 0f, 0f, color.toInt())
TextAlign.TOP_CENTER -> font.drawAligned(stack, text, align, width / 2f, 0f, color.toInt())
TextAlign.TOP_RIGHT -> font.drawAligned(stack, text, align, width, 0f, color.toInt())
TextAlign.CENTER_LEFT -> font.drawAligned(stack, text, align, 0f, height / 2f, color.toInt())
TextAlign.CENTER_CENTER -> font.drawAligned(stack, text, align, width / 2f, height / 2f, color.toInt())
TextAlign.CENTER_RIGHT -> font.drawAligned(stack, text, align, width, height / 2f, color.toInt())
TextAlign.BOTTOM_LEFT -> font.drawAligned(stack, text, align, 0f, height, color.toInt())
TextAlign.BOTTOM_CENTER -> font.drawAligned(stack, text, align, width / 2f, height, color.toInt())
TextAlign.BOTTOM_RIGHT -> font.drawAligned(stack, text, align, width, height, color.toInt())
}
}
override fun sizeToContents() {

View File

@ -0,0 +1,52 @@
package ru.dbotthepony.mc.otm.client.screen.panels.util
import net.minecraft.client.gui.screens.Screen
import ru.dbotthepony.mc.otm.client.screen.panels.Dock
import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel
import kotlin.math.roundToInt
class HorizontalStripPanel<out S : Screen>(
screen: S,
parent: EditablePanel<*>?,
x: Float = 0f,
y: Float = 0f,
width: Float = 0f,
height: Float = 0f,
) : EditablePanel<S>(screen, parent, x, y, width, height) {
override fun sizeToContents() {
var w = 0f
var h = 0f
for (child in children) {
if (child.dock == Dock.NONE) {
w += child.width + child.dockMargin.left + child.dockMargin.right
h = h.coerceAtLeast(child.height)
}
}
width = w
height = h
}
override fun performLayout() {
super.performLayout()
var w = 0f
for (child in children) {
if (child.dock == Dock.NONE) {
w += child.width + child.dockMargin.left + child.dockMargin.right
}
}
w = width / 2f - w / 2f
for (child in children) {
if (child.dock == Dock.NONE) {
child.y = (height / 2f - child.height / 2f).roundToInt().toFloat()
child.x = (w + child.dockMargin.left).roundToInt().toFloat()
w += child.dockMargin.left + child.width + child.dockMargin.right
}
}
}
}

View File

@ -0,0 +1,269 @@
package ru.dbotthepony.mc.otm.client.screen.tech
import com.mojang.blaze3d.vertex.PoseStack
import net.minecraft.ChatFormatting
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.entity.player.Inventory
import ru.dbotthepony.mc.otm.OverdriveThatMatters
import ru.dbotthepony.mc.otm.client.isShiftDown
import ru.dbotthepony.mc.otm.client.render.MatteryAtlas
import ru.dbotthepony.mc.otm.client.render.TextAlign
import ru.dbotthepony.mc.otm.client.screen.MatteryScreen
import ru.dbotthepony.mc.otm.client.screen.panels.Dock
import ru.dbotthepony.mc.otm.client.screen.panels.DockProperty
import ru.dbotthepony.mc.otm.client.screen.panels.DockResizeMode
import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel
import ru.dbotthepony.mc.otm.client.screen.panels.FramePanel
import ru.dbotthepony.mc.otm.client.screen.panels.Label
import ru.dbotthepony.mc.otm.client.screen.panels.button.LargeRectangleButtonPanel
import ru.dbotthepony.mc.otm.client.screen.panels.input.TextInputPanel
import ru.dbotthepony.mc.otm.client.screen.panels.slot.SlotPanel
import ru.dbotthepony.mc.otm.client.screen.panels.util.HorizontalStripPanel
import ru.dbotthepony.mc.otm.core.TextComponent
import ru.dbotthepony.mc.otm.core.TranslatableComponent
import ru.dbotthepony.mc.otm.core.util.getLevelFromXp
import ru.dbotthepony.mc.otm.core.util.getTotalXpRequiredForLevel
import ru.dbotthepony.mc.otm.core.util.getXpRequiredForLevelUp
import ru.dbotthepony.mc.otm.menu.tech.EssenceStorageMenu
import ru.dbotthepony.mc.otm.registry.MItems
class EssenceStorageScreen(menu: EssenceStorageMenu, inventory: Inventory, title: Component) : MatteryScreen<EssenceStorageMenu>(menu, title) {
override fun makeMainFrame(): FramePanel<MatteryScreen<*>> {
val frame = FramePanel.padded(this, width = DEFAULT_FRAME_WIDTH, height = 18f * 2f + 36f + 25f, title)
val inputs = HorizontalStripPanel(this, frame, height = 18f)
inputs.dock = Dock.TOP
inputs.dockMargin = DockProperty(bottom = 3f, top = 3f)
SlotPanel(this, inputs, menu.capsuleSlot).also {
it.dock = Dock.RIGHT
it.tooltip = MItems.ESSENCE_CAPSULE.description.copy().withStyle(ChatFormatting.GRAY)
}
HorizontalStripPanel(this, frame, height = 18f).also {
it.dock = Dock.TOP
it.dockMargin = DockProperty(bottom = 3f)
object : EditablePanel<EssenceStorageScreen>(this@EssenceStorageScreen, it, width = 108f, height = 15f) {
init {
dockResize = DockResizeMode.NONE
dockMargin = DockProperty(bottom = 3f)
}
override fun innerRender(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float) {
BAR_BACKGROUND.render(stack, width = width, height = height)
val level = getLevelFromXp(menu.experienceStored)
val progress = (menu.experienceStored - getTotalXpRequiredForLevel(level)).toDouble() / getXpRequiredForLevelUp(level).toDouble()
BAR_FOREGROUND.renderPartial(stack, width = width * progress.toFloat(), height = height)
}
}
SlotPanel(this, it, menu.servoSlot).also {
it.dock = Dock.RIGHT
it.tooltip = MItems.ESSENCE_SERVO.description.copy().withStyle(ChatFormatting.GRAY)
}
}
object : Label<EssenceStorageScreen>(this@EssenceStorageScreen, frame, text = TextComponent("")) {
init {
dock = Dock.TOP
dockMargin = DockProperty(bottom = 3f)
align = TextAlign.TOP_CENTER
}
override fun tickInner() {
super.tickInner()
if (minecraft?.window?.isShiftDown == true) {
text = TranslatableComponent("otm.gui.experience", menu.experienceStored)
} else {
text = TranslatableComponent("otm.gui.experience_levels", getLevelFromXp(menu.experienceStored))
}
}
}
val outputs = HorizontalStripPanel(this, frame, width = 18f * 3 + 3f * 3, height = 18f)
outputs.dock = Dock.TOP
outputs.dockResize = DockResizeMode.NONE
outputs.dockMargin = DockProperty(bottom = 3f)
object : LargeRectangleButtonPanel<EssenceStorageScreen>(this@EssenceStorageScreen, inputs, skinElement = STORE_1) {
init {
dockRight = 3f
tooltip = TranslatableComponent("otm.gui.experience.store", 1)
}
override fun onClick(mouseButton: Int) {
menu.storeLevels.input(1)
}
override var isDisabled: Boolean
get() = !menu.storeLevels.test(minecraft?.player)
set(value) {}
}
object : LargeRectangleButtonPanel<EssenceStorageScreen>(this@EssenceStorageScreen, inputs, skinElement = STORE_10) {
init {
dockRight = 3f
tooltip = TranslatableComponent("otm.gui.experience.store", 10)
}
override fun onClick(mouseButton: Int) {
menu.storeLevels.input(10)
}
override var isDisabled: Boolean
get() = !menu.storeLevels.test(minecraft?.player)
set(value) {}
}
object : LargeRectangleButtonPanel<EssenceStorageScreen>(this@EssenceStorageScreen, inputs, skinElement = STORE_ALL) {
init {
dockRight = 3f
tooltip = TranslatableComponent("otm.gui.experience.store_all")
}
override fun onClick(mouseButton: Int) {
menu.storeLevels.input(minecraft?.player?.experienceLevel ?: 0)
}
override var isDisabled: Boolean
get() = !menu.storeLevels.test(minecraft?.player)
set(value) {}
}
object : LargeRectangleButtonPanel<EssenceStorageScreen>(this@EssenceStorageScreen, outputs, skinElement = DISPENSE_1) {
init {
dockRight = 3f
tooltip = TranslatableComponent("otm.gui.experience.dispense", 1)
}
override fun onClick(mouseButton: Int) {
menu.dispenseLevels.input(1)
}
override var isDisabled: Boolean
get() = !menu.dispenseLevels.test(minecraft?.player)
set(value) {}
}
object : LargeRectangleButtonPanel<EssenceStorageScreen>(this@EssenceStorageScreen, outputs, skinElement = DISPENSE_10) {
init {
dockRight = 3f
tooltip = TranslatableComponent("otm.gui.experience.dispense", 10)
}
override fun onClick(mouseButton: Int) {
menu.dispenseLevels.input(10)
}
override var isDisabled: Boolean
get() = !menu.dispenseLevels.test(minecraft?.player)
set(value) {}
}
object : LargeRectangleButtonPanel<EssenceStorageScreen>(this@EssenceStorageScreen, outputs, skinElement = DISPENSE_ALL) {
init {
dockRight = 3f
tooltip = TranslatableComponent("otm.gui.experience.dispense_all")
}
override fun onClick(mouseButton: Int) {
menu.dispenseLevels.input(getLevelFromXp(menu.experienceStored) + 1)
}
override var isDisabled: Boolean
get() = !menu.dispenseLevels.test(minecraft?.player)
set(value) {}
}
val customBar = HorizontalStripPanel(this, frame, height = 18f)
customBar.dock = Dock.TOP
object : LargeRectangleButtonPanel<EssenceStorageScreen>(this@EssenceStorageScreen, customBar, skinElement = STORE_CUSTOM) {
init {
tooltip = TranslatableComponent("otm.gui.experience.store", customDispense)
}
override fun onClick(mouseButton: Int) {
menu.storeLevels.input(customDispense)
}
override var isDisabled: Boolean
get() = !menu.storeLevels.test(minecraft?.player)
set(value) {}
}
object : TextInputPanel<EssenceStorageScreen>(this@EssenceStorageScreen, customBar, width = 60f) {
init {
dockMargin = DockProperty(left = 3f, right = 3f)
text = customDispense.toString()
}
override fun onTextChanged(old: String, new: String) {
customDispense = (new.toIntOrNull() ?: 30).coerceAtLeast(1)
}
}
object : LargeRectangleButtonPanel<EssenceStorageScreen>(this@EssenceStorageScreen, customBar, skinElement = DISPENSE_CUSTOM) {
init {
tooltip = TranslatableComponent("otm.gui.experience.dispense", customDispense)
}
override fun onClick(mouseButton: Int) {
menu.dispenseLevels.input(customDispense)
}
override var isDisabled: Boolean
get() = !menu.dispenseLevels.test(minecraft?.player)
set(value) {}
}
object : LargeRectangleButtonPanel<EssenceStorageScreen>(this@EssenceStorageScreen, customBar, skinElement = SET_EXACT) {
init {
tooltip = TranslatableComponent("otm.gui.experience.set_exact", customDispense)
dock = Dock.RIGHT
dockMargin = DockProperty(right = 4f)
}
override fun onClick(mouseButton: Int) {
val player = minecraft?.player ?: return
if (player.experienceLevel == customDispense) {
if (player.experienceProgress > 0f) {
menu.storeLevels.input(1)
}
} else if (player.experienceLevel > customDispense) {
menu.storeLevels.input(player.experienceLevel - customDispense)
} else {
menu.dispenseLevels.input(customDispense - player.experienceLevel)
}
}
override var isDisabled: Boolean
get() = !menu.dispenseLevels.test(minecraft?.player) && !menu.storeLevels.test(minecraft?.player)
set(value) {}
}
return frame
}
companion object {
private var customDispense = 30
val ATLAS = MatteryAtlas(ResourceLocation(OverdriveThatMatters.MOD_ID, "textures/gui/essence_storage.png"), 108f, 66f)
val STORE_1 = ATLAS.sprite(width = 18f, height = 18f)
val STORE_10 = ATLAS.sprite(x = 18f, width = 18f, height = 18f)
val STORE_ALL = ATLAS.sprite(x = 18f * 2, width = 18f, height = 18f)
val DISPENSE_1 = ATLAS.sprite(x = 18f * 3, width = 18f, height = 18f)
val DISPENSE_10 = ATLAS.sprite(x = 18f * 4, width = 18f, height = 18f)
val DISPENSE_ALL = ATLAS.sprite(x = 18f * 5, width = 18f, height = 18f)
val BAR_BACKGROUND = ATLAS.sprite(y = 18f, height = 15f)
val BAR_FOREGROUND = ATLAS.sprite(y = 18f + 15f, height = 15f)
val STORE_CUSTOM = ATLAS.sprite(width = 18f, height = 18f, y = 48f)
val DISPENSE_CUSTOM = ATLAS.sprite(width = 18f, height = 18f, y = 48f, x = 18f)
val SET_EXACT = ATLAS.sprite(width = 18f, height = 18f, y = 48f, x = 18f * 2f)
}
}

View File

@ -0,0 +1,97 @@
package ru.dbotthepony.mc.otm.core.util
import it.unimi.dsi.fastutil.longs.LongArrayList
import net.minecraft.world.entity.player.Player
private val levelsTable = LongArrayList()
private val totalLevelsTable = LongArrayList().also { it.add(0L) }
private fun calculate(level: Int): Long {
return if (level >= 30) {
112L + (level - 30) * 9L
} else {
if (level >= 15) 37L + (level - 15) * 5L else 7L + level * 2L
}
}
/**
* Experience points required for leveling **up from** specified [level].
*
* Works exactly like [Player.getXpNeededForNextLevel],
* meaning that to achieve `level + 1` player need to acquire [getXpRequiredForLevelUp] experience points
*/
fun getXpRequiredForLevelUp(level: Int): Long {
require(level >= 0) { "Negative level: $level" }
if (levelsTable.size <= level) {
synchronized(levelsTable) {
while (levelsTable.size <= level) {
levelsTable.add(calculate(levelsTable.size))
}
}
}
return levelsTable.getLong(level)
}
/**
* Experience points required for achieving **exact** [level] specified.
*
* Example:
* * 0 level -> 0 points (because player starts at level 0)
* * 1 level -> [getXpRequiredForLevelUp]`(0)`
* * 2 level -> [getXpRequiredForLevelUp]`(0)` + [getXpRequiredForLevelUp]`(1)`
* * and so on
*/
fun getTotalXpRequiredForLevel(level: Int): Long {
require(level >= 0) { "Negative level: $level" }
if (totalLevelsTable.size <= level) {
synchronized(totalLevelsTable) {
while (totalLevelsTable.size <= level) {
totalLevelsTable.add(totalLevelsTable.getLong(totalLevelsTable.size - 1) + getXpRequiredForLevelUp(totalLevelsTable.size - 1))
}
}
}
return totalLevelsTable.getLong(level)
}
/**
* Determines current level from [experience] points provided
*/
fun getLevelFromXp(experience: Long): Int {
require(experience >= 0L) { "Negative experience: $experience" }
if (totalLevelsTable.getLong(totalLevelsTable.size - 1) < experience) {
synchronized(totalLevelsTable) {
while (totalLevelsTable.getLong(totalLevelsTable.size - 1) < experience) {
totalLevelsTable.add(totalLevelsTable.getLong(totalLevelsTable.size - 1) + getXpRequiredForLevelUp(totalLevelsTable.size - 1))
}
}
}
var bottom = 0
var top = totalLevelsTable.size - 1
while (top - bottom > 4) {
val middle = bottom + (top - bottom) / 2
val middleValue = totalLevelsTable.getLong(middle)
if (middleValue == experience) {
return middle
} else if (middleValue > experience) {
top = middle - 1
} else {
bottom = middle + 1
}
}
for (i in bottom + 1 .. top) {
if (totalLevelsTable.getLong(i) > experience) {
return i - 1
}
}
return top
}

View File

@ -6,6 +6,7 @@ import net.minecraft.nbt.DoubleTag
import net.minecraft.nbt.FloatTag
import net.minecraft.nbt.IntTag
import net.minecraft.nbt.ListTag
import net.minecraft.nbt.LongTag
import net.minecraft.nbt.NumericTag
import net.minecraft.nbt.StringTag
import net.minecraft.nbt.Tag
@ -126,6 +127,18 @@ class Savetables : INBTSerializable<CompoundTag?> {
.withDeserializer { it.asInt }
}
fun long(prop: GetterSetter<Long>, name: String): Stateless<Long, NumericTag> {
return Stateless(prop, name, NumericTag::class.java)
.withSerializer { LongTag.valueOf(it) }
.withDeserializer { it.asLong }
}
fun long(prop: KMutableProperty0<Long>, name: String = prop.name): Stateless<Long, NumericTag> {
return Stateless(prop, name, NumericTag::class.java)
.withSerializer { LongTag.valueOf(it) }
.withDeserializer { it.asLong }
}
fun bool(prop: GetterSetter<Boolean>, name: String): Stateless<Boolean, NumericTag> {
return Stateless(prop, name, NumericTag::class.java)
.withSerializer { ByteTag.valueOf(it) }

View File

@ -0,0 +1,48 @@
package ru.dbotthepony.mc.otm.item
import net.minecraft.ChatFormatting
import net.minecraft.network.chat.Component
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Rarity
import net.minecraft.world.item.TooltipFlag
import net.minecraft.world.level.Level
import ru.dbotthepony.mc.otm.client.isShiftDown
import ru.dbotthepony.mc.otm.client.minecraft
import ru.dbotthepony.mc.otm.core.TranslatableComponent
import ru.dbotthepony.mc.otm.core.nbt.set
import ru.dbotthepony.mc.otm.core.tagNotNull
import ru.dbotthepony.mc.otm.core.util.getLevelFromXp
import ru.dbotthepony.mc.otm.runIfClient
class EssenceCapsuleItem : Item(Properties().stacksTo(1).rarity(Rarity.UNCOMMON)) {
override fun appendHoverText(pStack: ItemStack, pLevel: Level?, pTooltipComponents: MutableList<Component>, pIsAdvanced: TooltipFlag) {
super.appendHoverText(pStack, pLevel, pTooltipComponents, pIsAdvanced)
pTooltipComponents.add(TranslatableComponent("otm.gui.essence_capsule").withStyle(ChatFormatting.GRAY))
pTooltipComponents.add(TranslatableComponent("otm.gui.essence_capsule2").withStyle(ChatFormatting.GRAY))
if (runIfClient(false) { minecraft.window.isShiftDown }) {
pTooltipComponents.add(TranslatableComponent("otm.gui.experience", experienceStored(pStack)).withStyle(ChatFormatting.GRAY))
} else {
pTooltipComponents.add(TranslatableComponent("otm.gui.experience_levels", getLevelFromXp(experienceStored(pStack))).withStyle(ChatFormatting.GRAY))
}
}
fun make(experience: Long): ItemStack {
return ItemStack(this, 1).also {
setExperience(it, experience)
}
}
companion object {
@JvmStatic
fun setExperience(itemStack: ItemStack, experience: Long) {
itemStack.tagNotNull["experience"] = experience
}
@JvmStatic
fun experienceStored(itemStack: ItemStack): Long {
return (itemStack.tag?.getLong("experience") ?: 0L) * itemStack.count
}
}
}

View File

@ -0,0 +1,80 @@
package ru.dbotthepony.mc.otm.item
import net.minecraft.ChatFormatting
import net.minecraft.core.BlockPos
import net.minecraft.network.chat.Component
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.InteractionResult
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.TooltipFlag
import net.minecraft.world.item.context.UseOnContext
import net.minecraft.world.level.Level
import ru.dbotthepony.mc.otm.block.entity.tech.EssenceStorageBlockEntity
import ru.dbotthepony.mc.otm.core.TranslatableComponent
class EssenceServoItem : Item(Properties().stacksTo(64)) {
override fun appendHoverText(pStack: ItemStack, pLevel: Level?, pTooltipComponents: MutableList<Component>, pIsAdvanced: TooltipFlag) {
super.appendHoverText(pStack, pLevel, pTooltipComponents, pIsAdvanced)
pTooltipComponents.add(TranslatableComponent("$descriptionId.desc2").withStyle(ChatFormatting.GRAY))
pTooltipComponents.add(TranslatableComponent("$descriptionId.desc").withStyle(ChatFormatting.DARK_GRAY))
}
fun useServo(player: Player, pos: BlockPos): InteractionResult {
val block = player.level.getBlockEntity(pos) ?: return InteractionResult.FAIL
// TODO: опыт как жидкость
if (block is EssenceStorageBlockEntity) {
if (player.level.isClientSide) return InteractionResult.SUCCESS
if (player !is ServerPlayer) return InteractionResult.FAIL
if (player.isCrouching) { // выгружаем в блок
storeLevel(player, block)
} else { // выгружаем из блока
dispenseLevel(player, block)
}
return InteractionResult.SUCCESS
}
return InteractionResult.FAIL
}
override fun useOn(pContext: UseOnContext): InteractionResult {
val player = pContext.player ?: return InteractionResult.FAIL
return useServo(player, pContext.clickedPos)
}
companion object {
@JvmStatic
fun storeLevel(player: ServerPlayer, block: EssenceStorageBlockEntity) {
val fpoints = (player.experienceProgress * player.xpNeededForNextLevel).toInt()
if (fpoints > 0) {
block.experienceStored += fpoints
player.setExperiencePoints(0)
} else if (player.experienceLevel > 0) {
player.setExperienceLevels(player.experienceLevel - 1)
block.experienceStored += player.xpNeededForNextLevel
player.setExperiencePoints(0) // для надёжности
}
}
@JvmStatic
fun dispenseLevel(player: ServerPlayer, block: EssenceStorageBlockEntity) {
val fpoints = (player.experienceProgress * player.xpNeededForNextLevel).toInt()
val missingPoints = player.xpNeededForNextLevel - fpoints
val diff = block.experienceStored.coerceAtMost(missingPoints.toLong()).toInt()
if (diff == missingPoints) {
player.setExperienceLevels(player.experienceLevel + 1)
player.setExperiencePoints(0)
} else {
player.setExperiencePoints(fpoints + diff)
}
block.experienceStored -= diff
}
}
}

View File

@ -31,6 +31,7 @@ import ru.dbotthepony.mc.otm.core.util.BinaryStringCodec
import ru.dbotthepony.mc.otm.core.util.BooleanValueCodec
import ru.dbotthepony.mc.otm.core.util.IStreamCodec
import ru.dbotthepony.mc.otm.core.util.NullValueCodec
import ru.dbotthepony.mc.otm.core.util.VarIntValueCodec
import ru.dbotthepony.mc.otm.menu.widget.AbstractWidget
import ru.dbotthepony.mc.otm.network.FieldSynchronizer
import ru.dbotthepony.mc.otm.network.MatteryPacket
@ -92,7 +93,7 @@ abstract class MatteryMenu @JvmOverloads protected constructor(
context.enqueueWork {
val menu = context.sender?.containerMenu as? MatteryMenu ?: return@enqueueWork
if (menu.containerId != containerId) return@enqueueWork
if (menu.containerId != containerId || !menu.stillValid(context.sender!!)) return@enqueueWork
val input = menu.playerInputs.getOrNull(inputId) ?: return@enqueueWork
if (!input.test(context.sender)) return@enqueueWork
input.invoke(input.codec.read(DataInputStream(FastByteArrayInputStream(payload))))
@ -149,6 +150,7 @@ abstract class MatteryMenu @JvmOverloads protected constructor(
fun bigDecimalInput(allowSpectators: Boolean = false, handler: (BigDecimal) -> Unit) = PlayerInput(BigDecimalValueCodec, allowSpectators, handler)
fun booleanInput(allowSpectators: Boolean = false, handler: (Boolean) -> Unit) = PlayerInput(BooleanValueCodec, allowSpectators, handler)
fun stringInput(allowSpectators: Boolean = false, handler: (String) -> Unit) = PlayerInput(BinaryStringCodec, allowSpectators, handler)
fun intInput(allowSpectators: Boolean = false, handler: (Int) -> Unit) = PlayerInput(VarIntValueCodec, allowSpectators, handler)
/**
* inventory + exosuit + hotbar (in this order)

View File

@ -0,0 +1,93 @@
package ru.dbotthepony.mc.otm.menu.tech
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.SimpleContainer
import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.item.ItemStack
import ru.dbotthepony.mc.otm.block.entity.tech.EssenceStorageBlockEntity
import ru.dbotthepony.mc.otm.capability.itemsStream
import ru.dbotthepony.mc.otm.capability.matteryPlayer
import ru.dbotthepony.mc.otm.core.util.LongValueCodec
import ru.dbotthepony.mc.otm.core.util.getTotalXpRequiredForLevel
import ru.dbotthepony.mc.otm.item.EssenceCapsuleItem
import ru.dbotthepony.mc.otm.item.EssenceServoItem
import ru.dbotthepony.mc.otm.menu.MatteryMenu
import ru.dbotthepony.mc.otm.menu.MatterySlot
import ru.dbotthepony.mc.otm.registry.MItems
import ru.dbotthepony.mc.otm.registry.MMenus
class EssenceStorageMenu @JvmOverloads constructor(
containerID: Int,
inventory: Inventory,
tile: EssenceStorageBlockEntity? = null
) : MatteryMenu(MMenus.ESSENCE_STORAGE, containerID, inventory, tile) {
val experienceStored by mSynchronizer.ComputedField(getter = { tile?.experienceStored ?: 0L }, LongValueCodec)
val capsuleSlot = object : MatterySlot(tile?.capsuleContainer ?: SimpleContainer(1), 0) {
override fun mayPlace(itemStack: ItemStack): Boolean {
return itemStack.item is EssenceCapsuleItem && super.mayPlace(itemStack)
}
}
val servoSlot = object : MatterySlot(tile?.servoContainer ?: SimpleContainer(1), 0) {
override fun mayPlace(itemStack: ItemStack): Boolean {
return itemStack.item == MItems.ESSENCE_SERVO && super.mayPlace(itemStack)
}
}
val storeLevels = intInput {
if (it > 0) {
val ply = ply as ServerPlayer
tile!!
if (it == 1) {
EssenceServoItem.storeLevel(ply, tile)
} else {
if (ply.experienceProgress > 0f) {
EssenceServoItem.storeLevel(ply, tile)
}
if (ply.experienceLevel > 0) {
val old = ply.experienceLevel
val new = (old - it).coerceAtLeast(0)
tile.experienceStored += getTotalXpRequiredForLevel(old) - getTotalXpRequiredForLevel(new)
ply.setExperienceLevels(new)
}
}
}
}
val dispenseLevels = intInput {
if (it > 0) {
val ply = ply as ServerPlayer
tile!!
if (it == 1) {
EssenceServoItem.dispenseLevel(ply, tile)
} else {
var i = 0
while (i++ < it && tile.experienceStored > 0L) {
EssenceServoItem.dispenseLevel(ply, tile)
}
}
}
}
init {
storeLevels.filter {
it.isCreative || it.matteryPlayer?.isAndroid == true || servoSlot.item.item == MItems.ESSENCE_SERVO //|| it.itemsStream(true).anyMatch { it.item == MItems.ESSENCE_SERVO }
}
dispenseLevels.filter {
it.isCreative || it.matteryPlayer?.isAndroid == true || servoSlot.item.item == MItems.ESSENCE_SERVO //|| it.itemsStream(true).anyMatch { it.item == MItems.ESSENCE_SERVO }
}
dispenseLevels.filter { (tile?.experienceStored ?: experienceStored) > 0L }
addStorageSlot(capsuleSlot)
addStorageSlot(servoSlot)
addInventorySlots()
}
}

View File

@ -132,6 +132,8 @@ internal fun addMainCreativeTabItems(consumer: CreativeModeTab.Output) {
accept(MItems.BLACK_HOLE)
accept(MItems.GRAVITATIONAL_DISRUPTOR)
accept(MItems.ESSENCE_SERVO)
energized(MItems.ALL_BATTERIES)
mattery(MItems.MATTER_CAPACITORS)
accept(MItems.PATTERN_DRIVE_NORMAL)

View File

@ -56,6 +56,7 @@ object MBlockEntities {
val MATTER_RECYCLER: BlockEntityType<MatterRecyclerBlockEntity> by registry.register(MNames.MATTER_RECYCLER) { BlockEntityType.Builder.of(::MatterRecyclerBlockEntity, MBlocks.MATTER_RECYCLER).build(null) }
val ENERGY_SERVO: BlockEntityType<EnergyServoBlockEntity> by registry.register(MNames.ENERGY_SERVO) { BlockEntityType.Builder.of(::EnergyServoBlockEntity, MBlocks.ENERGY_SERVO).build(null) }
val COBBLESTONE_GENERATOR: BlockEntityType<CobblerBlockEntity> by registry.register(MNames.COBBLESTONE_GENERATOR) { BlockEntityType.Builder.of(::CobblerBlockEntity, MBlocks.COBBLESTONE_GENERATOR).build(null) }
val ESSENCE_STORAGE: BlockEntityType<EssenceStorageBlockEntity> by registry.register(MNames.ESSENCE_STORAGE) { BlockEntityType.Builder.of(::EssenceStorageBlockEntity, MBlocks.ESSENCE_STORAGE).build(null) }
val STORAGE_BUS: BlockEntityType<StorageBusBlockEntity> by registry.register(MNames.STORAGE_BUS) { BlockEntityType.Builder.of(::StorageBusBlockEntity, MBlocks.STORAGE_BUS).build(null) }
val STORAGE_IMPORTER: BlockEntityType<StorageImporterBlockEntity> by registry.register(MNames.STORAGE_IMPORTER) { BlockEntityType.Builder.of(::StorageImporterBlockEntity, MBlocks.STORAGE_IMPORTER).build(null) }

View File

@ -64,6 +64,7 @@ import ru.dbotthepony.mc.otm.block.storage.StorageExporterBlock
import ru.dbotthepony.mc.otm.block.storage.StorageImporterBlock
import ru.dbotthepony.mc.otm.block.storage.StoragePowerSupplierBlock
import ru.dbotthepony.mc.otm.block.tech.CobblerBlock
import ru.dbotthepony.mc.otm.block.tech.EssenceStorageBlock
import ru.dbotthepony.mc.otm.core.TranslatableComponent
import ru.dbotthepony.mc.otm.core.collect.SupplierList
import java.util.function.Supplier
@ -91,6 +92,7 @@ object MBlocks {
val MATTER_RECYCLER: Block by registry.register(MNames.MATTER_RECYCLER) { MatterRecyclerBlock() }
val ENERGY_SERVO: Block by registry.register(MNames.ENERGY_SERVO) { EnergyServoBlock() }
val COBBLESTONE_GENERATOR: Block by registry.register(MNames.COBBLESTONE_GENERATOR) { CobblerBlock() }
val ESSENCE_STORAGE: EssenceStorageBlock by registry.register(MNames.ESSENCE_STORAGE) { EssenceStorageBlock() }
val STORAGE_BUS: Block by registry.register(MNames.STORAGE_BUS) { StorageBusBlock() }
val STORAGE_IMPORTER: Block by registry.register(MNames.STORAGE_IMPORTER) { StorageImporterBlock() }

View File

@ -4,7 +4,6 @@ package ru.dbotthepony.mc.otm.registry
import net.minecraft.ChatFormatting
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
import net.minecraft.tags.BlockTags
import net.minecraft.world.entity.EquipmentSlot
import net.minecraft.world.food.FoodProperties
import net.minecraft.world.item.*
@ -101,13 +100,22 @@ object MItems {
}
}
val ESSENCE_STORAGE: BlockItem by registry.register(MNames.ESSENCE_STORAGE) {
object : BlockItem(MBlocks.ESSENCE_STORAGE, DEFAULT_PROPERTIES) {
override fun appendHoverText(p_40572_: ItemStack, p_40573_: Level?, p_40574_: MutableList<Component>, p_40575_: TooltipFlag) {
super.appendHoverText(p_40572_, p_40573_, p_40574_, p_40575_)
p_40574_.add(TranslatableComponent("$descriptionId.desc").withStyle(ChatFormatting.GRAY))
}
}
}
val MACHINES = SupplierList(
::ANDROID_STATION, ::BATTERY_BANK, ::MATTER_DECOMPOSER, ::MATTER_CAPACITOR_BANK, ::MATTER_CABLE, ::PATTERN_STORAGE,
::MATTER_SCANNER, ::MATTER_PANEL, ::MATTER_REPLICATOR, ::MATTER_BOTTLER, ::ENERGY_COUNTER, ::CHEMICAL_GENERATOR,
::PLATE_PRESS, ::MATTER_RECYCLER, ::STORAGE_BUS, ::STORAGE_IMPORTER, ::STORAGE_EXPORTER, ::DRIVE_VIEWER,
::DRIVE_RACK, ::ITEM_MONITOR, ::STORAGE_CABLE, ::STORAGE_POWER_SUPPLIER, ::ENERGY_SERVO,
::PHANTOM_ATTRACTOR,
::GRAVITATION_STABILIZER, ::COBBLESTONE_GENERATOR
::GRAVITATION_STABILIZER, ::COBBLESTONE_GENERATOR, ::ESSENCE_STORAGE
)
val DEBUG_EXPLOSION_SMALL: Item by registry.register(MNames.DEBUG_EXPLOSION_SMALL) { BlockItem(MBlocks.DEBUG_EXPLOSION_SMALL, Item.Properties().stacksTo(64)) }
@ -134,6 +142,10 @@ object MItems {
val TRITANIUM_INGOT_BLOCK: BlockItem by registry.register(MNames.TRITANIUM_INGOT_BLOCK) { BlockItem(MBlocks.TRITANIUM_INGOT_BLOCK, DEFAULT_PROPERTIES) }
val TRITANIUM_BARS: BlockItem by registry.register(MNames.TRITANIUM_BARS) { BlockItem(MBlocks.TRITANIUM_BARS, DEFAULT_PROPERTIES) }
val ESSENCE_SERVO: EssenceServoItem by registry.register("essence_servo") { EssenceServoItem() }
val ESSENCE_CAPSULE: EssenceCapsuleItem by registry.register("essence_capsule") { EssenceCapsuleItem() }
val ESSENCE_DRIVE: EssenceCapsuleItem by registry.register("essence_drive") { EssenceCapsuleItem() }
val TRITANIUM_COMPONENT: ForgeTier = ForgeTier(
Tiers.IRON.level,
3072,

View File

@ -31,6 +31,7 @@ import ru.dbotthepony.mc.otm.client.screen.tech.ChemicalGeneratorScreen
import ru.dbotthepony.mc.otm.client.screen.tech.CobblerScreen
import ru.dbotthepony.mc.otm.client.screen.tech.EnergyCounterScreen
import ru.dbotthepony.mc.otm.client.screen.tech.EnergyServoScreen
import ru.dbotthepony.mc.otm.client.screen.tech.EssenceStorageScreen
import ru.dbotthepony.mc.otm.client.screen.tech.PlatePressScreen
import ru.dbotthepony.mc.otm.menu.decorative.CargoCrateMenu
import ru.dbotthepony.mc.otm.menu.decorative.HoloSignMenu
@ -56,6 +57,7 @@ import ru.dbotthepony.mc.otm.menu.tech.ChemicalGeneratorMenu
import ru.dbotthepony.mc.otm.menu.tech.CobblerMenu
import ru.dbotthepony.mc.otm.menu.tech.EnergyCounterMenu
import ru.dbotthepony.mc.otm.menu.tech.EnergyServoMenu
import ru.dbotthepony.mc.otm.menu.tech.EssenceStorageMenu
import ru.dbotthepony.mc.otm.menu.tech.PlatePressMenu
object MMenus {
@ -82,6 +84,7 @@ object MMenus {
val ENERGY_SERVO: MenuType<*> by registry.register(MNames.ENERGY_SERVO) { MenuType(::EnergyServoMenu) }
val HOLO_SIGN: MenuType<HoloSignMenu> by registry.register(MNames.HOLO_SIGN) { MenuType(::HoloSignMenu) }
val COBBLESTONE_GENERATOR: MenuType<CobblerMenu> by registry.register(MNames.COBBLESTONE_GENERATOR) { MenuType(::CobblerMenu) }
val ESSENCE_STORAGE: MenuType<EssenceStorageMenu> by registry.register(MNames.ESSENCE_STORAGE) { MenuType(::EssenceStorageMenu) }
val STORAGE_BUS: MenuType<*> by registry.register(MNames.STORAGE_BUS) { MenuType(::StorageBusMenu) }
val STORAGE_EXPORTER: MenuType<*> by registry.register(MNames.STORAGE_EXPORTER) { MenuType(::StorageExporterMenu) }
@ -121,6 +124,7 @@ object MMenus {
MenuScreens.register(ENERGY_SERVO as MenuType<EnergyServoMenu>, ::EnergyServoScreen)
MenuScreens.register(HOLO_SIGN, ::HoloSignScreen)
MenuScreens.register(COBBLESTONE_GENERATOR, ::CobblerScreen)
MenuScreens.register(ESSENCE_STORAGE, ::EssenceStorageScreen)
}
}
}

View File

@ -32,6 +32,7 @@ object MNames {
const val MATTER_RECYCLER = "matter_recycler"
const val ENERGY_SERVO = "energy_servo"
const val COBBLESTONE_GENERATOR = "cobblestone_generator"
const val ESSENCE_STORAGE = "essence_storage"
const val TRITANIUM_ANVIL = "tritanium_anvil"
const val STORAGE_CABLE = "storage_cable" // нужен рецепт

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -0,0 +1,124 @@
package ru.dbotthepony.mc.otm.tests
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import ru.dbotthepony.mc.otm.core.util.getLevelFromXp
import ru.dbotthepony.mc.otm.core.util.getTotalXpRequiredForLevel
import ru.dbotthepony.mc.otm.core.util.getXpRequiredForLevelUp
object ExperienceUtilsTest {
@Test
@DisplayName("Experience utils xp required for level up")
fun levelUp() {
var i = 0
assertEquals(7L, getXpRequiredForLevelUp(i++))
assertEquals(9L, getXpRequiredForLevelUp(i++))
assertEquals(11L, getXpRequiredForLevelUp(i++))
assertEquals(13L, getXpRequiredForLevelUp(i++))
assertEquals(15L, getXpRequiredForLevelUp(i++))
assertEquals(17L, getXpRequiredForLevelUp(i++))
assertEquals(19L, getXpRequiredForLevelUp(i++))
assertEquals(21L, getXpRequiredForLevelUp(i++))
assertEquals(23L, getXpRequiredForLevelUp(i++))
assertEquals(25L, getXpRequiredForLevelUp(i++))
assertEquals(27L, getXpRequiredForLevelUp(i++))
assertEquals(29L, getXpRequiredForLevelUp(i++))
assertEquals(31L, getXpRequiredForLevelUp(i++))
assertEquals(33L, getXpRequiredForLevelUp(i++))
assertEquals(35L, getXpRequiredForLevelUp(i++))
assertEquals(37L, getXpRequiredForLevelUp(i++))
assertEquals(42L, getXpRequiredForLevelUp(i++))
assertEquals(47L, getXpRequiredForLevelUp(i++))
assertEquals(52L, getXpRequiredForLevelUp(i++))
assertEquals(57L, getXpRequiredForLevelUp(i++))
assertEquals(62L, getXpRequiredForLevelUp(i++))
assertEquals(67L, getXpRequiredForLevelUp(i++))
assertEquals(72L, getXpRequiredForLevelUp(i++))
assertEquals(77L, getXpRequiredForLevelUp(i++))
assertEquals(82L, getXpRequiredForLevelUp(i++))
assertEquals(87L, getXpRequiredForLevelUp(i++))
assertEquals(92L, getXpRequiredForLevelUp(i++))
assertEquals(97L, getXpRequiredForLevelUp(i++))
assertEquals(102L, getXpRequiredForLevelUp(i++))
assertEquals(107L, getXpRequiredForLevelUp(i++))
assertEquals(112L, getXpRequiredForLevelUp(i++))
assertEquals(121L, getXpRequiredForLevelUp(i++))
assertEquals(130L, getXpRequiredForLevelUp(i++))
assertEquals(139L, getXpRequiredForLevelUp(i++))
assertEquals(148L, getXpRequiredForLevelUp(i++))
assertEquals(157L, getXpRequiredForLevelUp(i++))
assertEquals(166L, getXpRequiredForLevelUp(i++))
assertEquals(175L, getXpRequiredForLevelUp(i++))
assertEquals(184L, getXpRequiredForLevelUp(i++))
assertEquals(193L, getXpRequiredForLevelUp(i++))
}
@Test
@DisplayName("Experience utils total xp required for level")
fun totalXp() {
var i = 1
assertEquals(7L, getTotalXpRequiredForLevel(i++)) // 1
assertEquals(16L, getTotalXpRequiredForLevel(i++)) // 2
assertEquals(27L, getTotalXpRequiredForLevel(i++)) // 3
assertEquals(40L, getTotalXpRequiredForLevel(i++)) // 4
assertEquals(55L, getTotalXpRequiredForLevel(i++)) // 5
assertEquals(72L, getTotalXpRequiredForLevel(i++)) // 6
assertEquals(91L, getTotalXpRequiredForLevel(i++)) // 7
assertEquals(112L, getTotalXpRequiredForLevel(i++)) // 8
assertEquals(135L, getTotalXpRequiredForLevel(i++)) // 9
assertEquals(160L, getTotalXpRequiredForLevel(i++)) // 10
assertEquals(187L, getTotalXpRequiredForLevel(i++)) // 11
assertEquals(216L, getTotalXpRequiredForLevel(i++)) // 12
assertEquals(247L, getTotalXpRequiredForLevel(i++)) // 13
assertEquals(280L, getTotalXpRequiredForLevel(i++)) // 14
assertEquals(315L, getTotalXpRequiredForLevel(i++)) // 15
assertEquals(352L, getTotalXpRequiredForLevel(i++)) // 16
assertEquals(394L, getTotalXpRequiredForLevel(i++)) // 17
assertEquals(441L, getTotalXpRequiredForLevel(i++)) // 18
assertEquals(493L, getTotalXpRequiredForLevel(i++)) // 19
assertEquals(550L, getTotalXpRequiredForLevel(i++)) // 20
assertEquals(612L, getTotalXpRequiredForLevel(i++)) // 21
assertEquals(679L, getTotalXpRequiredForLevel(i++)) // 22
assertEquals(751L, getTotalXpRequiredForLevel(i++)) // 23
assertEquals(828L, getTotalXpRequiredForLevel(i++)) // 24
assertEquals(910L, getTotalXpRequiredForLevel(i++)) // 25
assertEquals(997L, getTotalXpRequiredForLevel(i++)) // 26
assertEquals(1089L, getTotalXpRequiredForLevel(i++)) // 27
assertEquals(1186L, getTotalXpRequiredForLevel(i++)) // 28
assertEquals(1288L, getTotalXpRequiredForLevel(i++)) // 29
assertEquals(1395L, getTotalXpRequiredForLevel(i++)) // 30
assertEquals(1507L, getTotalXpRequiredForLevel(i++)) // 31
assertEquals(1628L, getTotalXpRequiredForLevel(i++)) // 32
assertEquals(1758L, getTotalXpRequiredForLevel(i++)) // 33
assertEquals(1897L, getTotalXpRequiredForLevel(i++)) // 34
assertEquals(2045L, getTotalXpRequiredForLevel(i++)) // 35
assertEquals(2202L, getTotalXpRequiredForLevel(i++)) // 36
assertEquals(2368L, getTotalXpRequiredForLevel(i++)) // 37
assertEquals(2543L, getTotalXpRequiredForLevel(i++)) // 38
assertEquals(2727L, getTotalXpRequiredForLevel(i++)) // 39
assertEquals(2920L, getTotalXpRequiredForLevel(i++)) // 40
}
@Test
@DisplayName("Experience utils reverse search xp -> level")
fun reverseSearch() {
assertEquals(0, getLevelFromXp(4L))
assertEquals(0, getLevelFromXp(6L))
assertEquals(1, getLevelFromXp(7L))
assertEquals(1, getLevelFromXp(8L))
assertEquals(19, getLevelFromXp(540L))
assertEquals(19, getLevelFromXp(545L))
assertEquals(20, getLevelFromXp(550L))
assertEquals(21, getLevelFromXp(551L))
assertEquals(21, getLevelFromXp(554L))
assertEquals(21, getLevelFromXp(556L))
for (i in 50 .. 100) {
assertEquals(i, getLevelFromXp(getTotalXpRequiredForLevel(i)))
assertEquals(i - 1, getLevelFromXp(getTotalXpRequiredForLevel(i) - 1L))
assertEquals(i, getLevelFromXp(getTotalXpRequiredForLevel(i) + 1L))
}
}
}