From 40da26033dc64b9c5345791077f1676fa0b19853 Mon Sep 17 00:00:00 2001 From: DBotThePony Date: Mon, 13 Mar 2023 18:10:02 +0700 Subject: [PATCH] Essence storage, servo, capsules --- .../mc/otm/datagen/lang/English.kt | 19 ++ .../mc/otm/datagen/lang/Russian.kt | 19 ++ .../dbotthepony/mc/otm/block/MatteryBlock.kt | 2 +- .../entity/tech/EssenceStorageBlockEntity.kt | 56 ++++ .../mc/otm/block/tech/EssenceStorageBlock.kt | 39 +++ .../mc/otm/client/screen/panels/Label.kt | 15 +- .../panels/util/HorizontalStripPanel.kt | 52 ++++ .../screen/tech/EssenceStorageScreen.kt | 269 ++++++++++++++++++ .../mc/otm/core/util/ExperienceUtils.kt | 97 +++++++ .../mc/otm/core/util/Savetables.kt | 13 + .../mc/otm/item/EssenceCapsuleItem.kt | 48 ++++ .../mc/otm/item/EssenceServoItem.kt | 80 ++++++ .../ru/dbotthepony/mc/otm/menu/MatteryMenu.kt | 4 +- .../mc/otm/menu/tech/EssenceStorageMenu.kt | 93 ++++++ .../mc/otm/registry/CreativeTabs.kt | 2 + .../mc/otm/registry/MBlockEntities.kt | 1 + .../ru/dbotthepony/mc/otm/registry/MBlocks.kt | 2 + .../ru/dbotthepony/mc/otm/registry/MItems.kt | 16 +- .../ru/dbotthepony/mc/otm/registry/MMenus.kt | 4 + .../ru/dbotthepony/mc/otm/registry/MNames.kt | 1 + .../textures/gui/essence_storage.png | Bin 0 -> 1051 bytes .../textures/gui/essence_storage.xcf | Bin 0 -> 17933 bytes .../mc/otm/tests/ExperienceUtilsTest.kt | 124 ++++++++ 23 files changed, 951 insertions(+), 5 deletions(-) create mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/EssenceStorageBlockEntity.kt create mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/block/tech/EssenceStorageBlock.kt create mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/util/HorizontalStripPanel.kt create mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/EssenceStorageScreen.kt create mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/core/util/ExperienceUtils.kt create mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/item/EssenceCapsuleItem.kt create mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/item/EssenceServoItem.kt create mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/EssenceStorageMenu.kt create mode 100644 src/main/resources/assets/overdrive_that_matters/textures/gui/essence_storage.png create mode 100644 src/main/resources/assets/overdrive_that_matters/textures/gui/essence_storage.xcf create mode 100644 src/test/kotlin/ru/dbotthepony/mc/otm/tests/ExperienceUtilsTest.kt diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/English.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/English.kt index 80b330466..e558c82c3 100644 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/English.kt +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/English.kt @@ -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") } } diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/Russian.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/Russian.kt index 6054bcecf..08e3317bf 100644 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/Russian.kt +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/Russian.kt @@ -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", "Данный предмет может быть переработан внутри хранилища эссенции") } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/MatteryBlock.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/MatteryBlock.kt index dd0cb5212..b7f082f90 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/MatteryBlock.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/MatteryBlock.kt @@ -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) } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/EssenceStorageBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/EssenceStorageBlockEntity.kt new file mode 100644 index 000000000..5413f446a --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/EssenceStorageBlockEntity.kt @@ -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() + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/tech/EssenceStorageBlock.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/tech/EssenceStorageBlock.kt new file mode 100644 index 000000000..0d4faf442 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/tech/EssenceStorageBlock.kt @@ -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 getTicker(pLevel: Level, pState: BlockState, pBlockEntityType: BlockEntityType): BlockEntityTicker? { + 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) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/Label.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/Label.kt index 9c506ab41..16cf68597 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/Label.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/Label.kt @@ -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 @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 @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() { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/util/HorizontalStripPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/util/HorizontalStripPanel.kt new file mode 100644 index 000000000..64c81c1fa --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/util/HorizontalStripPanel.kt @@ -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( + screen: S, + parent: EditablePanel<*>?, + x: Float = 0f, + y: Float = 0f, + width: Float = 0f, + height: Float = 0f, +) : EditablePanel(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 + } + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/EssenceStorageScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/EssenceStorageScreen.kt new file mode 100644 index 000000000..feba92c9b --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/EssenceStorageScreen.kt @@ -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(menu, title) { + override fun makeMainFrame(): FramePanel> { + 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(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(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(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(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(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(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(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(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(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(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(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(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) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/ExperienceUtils.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/ExperienceUtils.kt new file mode 100644 index 000000000..5d979d0f4 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/ExperienceUtils.kt @@ -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 +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/Savetables.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/Savetables.kt index 000799c2c..acc1b2845 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/Savetables.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/Savetables.kt @@ -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 { .withDeserializer { it.asInt } } + fun long(prop: GetterSetter, name: String): Stateless { + return Stateless(prop, name, NumericTag::class.java) + .withSerializer { LongTag.valueOf(it) } + .withDeserializer { it.asLong } + } + + fun long(prop: KMutableProperty0, name: String = prop.name): Stateless { + return Stateless(prop, name, NumericTag::class.java) + .withSerializer { LongTag.valueOf(it) } + .withDeserializer { it.asLong } + } + fun bool(prop: GetterSetter, name: String): Stateless { return Stateless(prop, name, NumericTag::class.java) .withSerializer { ByteTag.valueOf(it) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/EssenceCapsuleItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/EssenceCapsuleItem.kt new file mode 100644 index 000000000..922a37662 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/EssenceCapsuleItem.kt @@ -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, 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 + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/EssenceServoItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/EssenceServoItem.kt new file mode 100644 index 000000000..f6417fdc8 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/EssenceServoItem.kt @@ -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, 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 + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatteryMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatteryMenu.kt index cf2c8d18b..be6448075 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatteryMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatteryMenu.kt @@ -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) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/EssenceStorageMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/EssenceStorageMenu.kt new file mode 100644 index 000000000..c3b8a6bf3 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/EssenceStorageMenu.kt @@ -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() + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/CreativeTabs.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/CreativeTabs.kt index 266c0cad7..42a065f10 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/CreativeTabs.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/CreativeTabs.kt @@ -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) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBlockEntities.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBlockEntities.kt index b0e1e28c5..68fa57c9c 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBlockEntities.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBlockEntities.kt @@ -56,6 +56,7 @@ object MBlockEntities { val MATTER_RECYCLER: BlockEntityType by registry.register(MNames.MATTER_RECYCLER) { BlockEntityType.Builder.of(::MatterRecyclerBlockEntity, MBlocks.MATTER_RECYCLER).build(null) } val ENERGY_SERVO: BlockEntityType by registry.register(MNames.ENERGY_SERVO) { BlockEntityType.Builder.of(::EnergyServoBlockEntity, MBlocks.ENERGY_SERVO).build(null) } val COBBLESTONE_GENERATOR: BlockEntityType by registry.register(MNames.COBBLESTONE_GENERATOR) { BlockEntityType.Builder.of(::CobblerBlockEntity, MBlocks.COBBLESTONE_GENERATOR).build(null) } + val ESSENCE_STORAGE: BlockEntityType by registry.register(MNames.ESSENCE_STORAGE) { BlockEntityType.Builder.of(::EssenceStorageBlockEntity, MBlocks.ESSENCE_STORAGE).build(null) } val STORAGE_BUS: BlockEntityType by registry.register(MNames.STORAGE_BUS) { BlockEntityType.Builder.of(::StorageBusBlockEntity, MBlocks.STORAGE_BUS).build(null) } val STORAGE_IMPORTER: BlockEntityType by registry.register(MNames.STORAGE_IMPORTER) { BlockEntityType.Builder.of(::StorageImporterBlockEntity, MBlocks.STORAGE_IMPORTER).build(null) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBlocks.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBlocks.kt index 416ea502f..b18d5965b 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBlocks.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBlocks.kt @@ -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() } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MItems.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MItems.kt index 12854b3d2..ed3e6a8bb 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MItems.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MItems.kt @@ -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, 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, diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MMenus.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MMenus.kt index ca87fe476..3c3d70b9e 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MMenus.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MMenus.kt @@ -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 by registry.register(MNames.HOLO_SIGN) { MenuType(::HoloSignMenu) } val COBBLESTONE_GENERATOR: MenuType by registry.register(MNames.COBBLESTONE_GENERATOR) { MenuType(::CobblerMenu) } + val ESSENCE_STORAGE: MenuType 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, ::EnergyServoScreen) MenuScreens.register(HOLO_SIGN, ::HoloSignScreen) MenuScreens.register(COBBLESTONE_GENERATOR, ::CobblerScreen) + MenuScreens.register(ESSENCE_STORAGE, ::EssenceStorageScreen) } } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MNames.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MNames.kt index 8425a98f6..02bfadbcd 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MNames.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MNames.kt @@ -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" // нужен рецепт diff --git a/src/main/resources/assets/overdrive_that_matters/textures/gui/essence_storage.png b/src/main/resources/assets/overdrive_that_matters/textures/gui/essence_storage.png new file mode 100644 index 0000000000000000000000000000000000000000..e98895aac5658293a46910a28c4025af3f6d71e8 GIT binary patch literal 1051 zcmV+$1mydPP)EX>4Tx04R}tkv&MmP!xqvQ>7v;9qb??n4vmZ5EXHhDi*;)X)CnqVDi#GXws0R zxHt-~1qXi?s}3&Cx;nTDg5VE`tBaGOih|h_~4Qi11k?XR{Z=6dG3p_JoWYhD+5n{2_#c~(3vY`@B6Gs(QqkJLf zvch?bvs$gQcAxx(!GgAu;X2hY5?DeKDTt6!M+H?_h|#W*Vj@lFaS#8H<4=)GCRYWF z91EyHh2;3b|KRs*&EnLgn-q)#y)U-?F$x5BfmXw|zmILZbprUGfh(=!uQh?$PtqG5 zEqVlmw}Ff6jwbH`mpj1VlP(#OBl)R>Vi9;hqi@Os1Ghl`nmf1lIZhvdEX`{91~@nb z#!8gE?(y!v?z#PYr#-(PG!k--GDy7v00006VoOIv0Nwyw09#YmGim?;010qNS#tmY z3ljhU3ljkVnw%H_000McNliru=K~E2A`@K1p}znC0vJg|K~#9!?VLSI13?sk-$+g% z7D6HjcCm>F2E2u(jUK|>!6qIemL9_{f@ot3Dx~FhLih|77=;5+NhX+lv|wRyC*Fo@^*8URU8pf zR2VH0dMT`EU!mvfijxYXeFdEhdGC0>)U8yQEumGCoGMP8(n4gpK2s*Ec45V0U?w(Z zX|TmvYagu+JW0EJp*A~t3VN=>UWQ z!Vm_{2=lFxK!pLq5C#YXgdq$7005vRCX1n-EQZmyrN{H7_C1zo+c+t{PcOGbGco@; zNu3a86NI!(g%x66uRs4T=>;N|4)|wV zqO1MU8fy?{TRKumi;=aOO;y$J(g(ah+_o{Ms4`|*rZdbYCPo%lIvRj25dZ*c;1}q} VZzz%Df*t?>002ovPDHLkV1j?5$MOIG literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/overdrive_that_matters/textures/gui/essence_storage.xcf b/src/main/resources/assets/overdrive_that_matters/textures/gui/essence_storage.xcf new file mode 100644 index 0000000000000000000000000000000000000000..e5f6ecdae18ff3124a7e4af547132154aa78353e GIT binary patch literal 17933 zcmeI4Pi)*r6~|}oY`1otx^+Ve+N5mK5ILJTtW(mURBMNXR0tW-YdDcQu`zO!Dh{Z* z;J8R|U=I`!aG|h>OA!)jMM?~6fJ@8X)*O(K;DC^P=#2wXWJO&*Z~pC!{oCN(-@%LY zoAujyGjHB_=Ka3&X7cl!%rCtBO6k?vxzg7wlaq?H2%Qs#^h4+gD3{}F3>t+F5AbzR z*T~nC(4*(RixNcI3+;klfc9Bl+I!op>O?Az`$x~`U2Zf)$#Wzn9l6vWF$ns?BP{8jG z_~C#r1^n)S9|?GwvGgxVz$Cx#FjRpGkjSL}PgtHz%XhU%@XtuS>~g-#KtUK}OZ!dezM*P$wZ2Y&>q=OKn5OjS+tc4V{XtHk`)d^G zZ?#cerN7k&1dKA3`4NOHg`r(2<~c_|BNeeg6`M8AlBaYGf0{GbnpU=@-aFv{sx=xbie zLIyzSx6p5)-`Y?t)K&|9)~l>pBnDp63jGLbTVQpx`6HayTLA0-3{6x|IPH z`W`AFbkKLmLf_#HeSl^Fra`rcL1c#!Up0!ay4Uh~`czYCPaVqiGyA8p zz)s_5x~f;voA0IiR8wic_hzP_**}d1b{ao+6}@;M)u)N5|z=$a9dqpONI=m&m)6ea&C+YU$ll^AEurrn}Or-`;r3i^5*tFpbEB z+G+oEoA=hRZ7PklDc;V=aQZaE2;tt1Xb(!5qPktL1?o z5Ns5^{gF38@-9W*{*1=aYSgF$@S-Dpj$`;+-`W{ z65G*l0V|TEeumJI*tp%6Bt~wJB^OaAW0jfc+4z__YCG~#44n_BW6Z?R`H(sWK7xiV5SU;&{HpKiDbPI_NWHy8YdgX`}iC2!;f{q(xCg_{^L1seV?Px<8x1WuV zC#1F`AH^5?P(#M716h4|tB*Vs39}*iwV+!_tRk}sBVd^gUPK4Of+r+iIbsX+kw@2% zL^CoIO(4yWS7t+Uai6jti@be~W3iiWa}D72(lh$z!P7L6C7g#O^^34#HLz_k7 z>Axh%s5)(!l!UWdc`8Iif=RlfUugFF%QdBNg|d3c3YHstQM{@KRLJ5BnJsuN<)j`pBbeh@Fmp z4FL*2R)0W@?nW`2#4@v~+pATK!m*55-?Z}gul!XkkuV!>XBBh{W>aT2;T5f*A7+EZ zD@SaBKJw@qVm9<^h}rnD`q@M`wU|v}nc29DUW~%Aj9K6N^0&eK#W0aD8}5n~bPHxv zWH#Z2v7jGjgTyOGY=J)V=o(@+^lOOO__6xg_{APB{LOsi2>dR?nEUte{?RR4n>oD8 zGIJ+)+0-QU)8=;A45{anyZhIO?PluxmXU1J<~H@~1Q}IR@4I@rMF|!(McyWG3*sf( zBz{5sf%pNvx%l-Ht?ytV%DQZmtVCIXvhuXPM{KmB0_e+1Xp>-HFfUk-H@67G{&bGONJab9C?sInEGM#X5?O%=~--M^L96x<>GAcgvl&k&UcyjpR zFzYrnyqmke>!jO7mqQc1xeX=o{7$-EtkGU$i{6|=4?M4vZWnp9@AcUJuZ=sGa@`L70L2Q>@T^WRF4QFZPCxC0G9F32LPnNAkS_cr wD7fic$Xxi7k-Z@1aLKiyeBqy_^1{EJaCum@0#eKQpS1##W+x%}&otD30d|Uql>h($ literal 0 HcmV?d00001 diff --git a/src/test/kotlin/ru/dbotthepony/mc/otm/tests/ExperienceUtilsTest.kt b/src/test/kotlin/ru/dbotthepony/mc/otm/tests/ExperienceUtilsTest.kt new file mode 100644 index 000000000..cf617fc77 --- /dev/null +++ b/src/test/kotlin/ru/dbotthepony/mc/otm/tests/ExperienceUtilsTest.kt @@ -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)) + } + } +}