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 155ac4ea9..4470af2fd 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 @@ -45,7 +45,22 @@ private fun sounds(provider: MatteryLanguageProvider) { private fun misc(provider: MatteryLanguageProvider) { with(provider.english) { - misc("gui.exosuit", "Exosuit Inventory") + gui("exosuit", "Exosuit Inventory") + + gui("exosuit.probe1", "This little device feels unnatural to touch, it is almost certainly resilient to any possible attempt to break it open.") + gui("exosuit.probe2", "There is fingerprint reader built into one of sides which gently glow when touched.") + gui("exosuit.probe3", "It seems this box will unlock once you strongly press fingerprint reader, and you feel what's inside will affect you without any way back!") + + gui("exosuit.already_activated", "You already have exosuit following you") + + misc("exosuit.granted1", "As you keep pressing fingerprint reader, you are getting hurt in finger.") + misc("exosuit.granted2", "After you raise your finger, fingerprint reader glows very bright.") + misc("exosuit.granted3", "Then, fingerprint reader fades, leaving faint trace not of your finger, but of your very soul.") + misc("exosuit.granted4", "The device opens... And whatever was inside it shroud you, yet you feel nothing, as it wasn't even there.") + misc("exosuit.granted5", "As whatever shrouded you takes final form, you feel it binds to your soul.") + misc("exosuit.granted6", "INITIALIZATION SEQUENCE COMPLETE. WELCOME, USER %s") + misc("exosuit.granted7", "You are not permanently equipped with four dimensional omni-present Exosuit.") + misc("exosuit.granted8", "As of now, this exosuit is not much, but it's built-in AI hints there are upgrade modules out there somewhere...") misc("iteration", "Iteration %s") misc("death_reason", "Decommissioned!") @@ -213,6 +228,9 @@ private fun death(provider: MatteryLanguageProvider) { death("otm_hawking_radiation.player", "%1\$s disintegrated whilst fighting %2\$s") death("otm_emp.player", "%1\$s blew fuzes of %2\$s") death("otm_emp.player.item", "%1\$s blew fuzes of %2\$s using %3\$s") + + death(MRegistry.DAMAGE_EXOSUIT_PROBE_ID, "%1\$s hit little finger against cupboard") + death("${MRegistry.DAMAGE_EXOSUIT_PROBE_ID}.player", "%1\$s hit little finger against cupboard whilst %2\$s tried to finish them off") } } @@ -275,6 +293,7 @@ private fun blocks(provider: MatteryLanguageProvider) { private fun items(provider: MatteryLanguageProvider) { with(provider.english) { + add(MItems.EXOSUIT_PROBE, "Exosuit Probe") add(MItems.NUTRIENT_PASTE, "Nutrient paste") add(MItems.BLACK_HOLE_SCANNER, "Singularity Scanner") diff --git a/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java b/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java index 8225044a5..ab93ceed1 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java +++ b/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java @@ -82,7 +82,7 @@ public final class OverdriveThatMatters { FMLJavaModLoadingContext.get().getModEventBus().addListener(this::setupClient); MinecraftForge.EVENT_BUS.register(this); - MinecraftForge.EVENT_BUS.register(TickerKt.class); + MinecraftForge.EVENT_BUS.register(GlobalEventHandlerKt.class); MinecraftForge.EVENT_BUS.register(MatteryPlayerCapability.Companion); MinecraftForge.EVENT_BUS.register(MatterRegistryKt.class); MinecraftForge.EVENT_BUS.register(MatterDataKt.class); @@ -120,6 +120,8 @@ public final class OverdriveThatMatters { MinecraftForge.EVENT_BUS.register(MatteryGUI.INSTANCE); MinecraftForge.EVENT_BUS.register(EventHandlerKt.class); + event.enqueueWork(GlobalEventHandlerKt::recordClientThread); + TritaniumArmorModel.register(); GravitationStabilizerModel.register(); } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/Ticker.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/GlobalEventHandler.kt similarity index 73% rename from src/main/kotlin/ru/dbotthepony/mc/otm/Ticker.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/GlobalEventHandler.kt index 3db4a1d11..076683ed5 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/Ticker.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/GlobalEventHandler.kt @@ -27,13 +27,127 @@ private val postWorldTick = WeakHashMap>( private val preWorldTickOnce = WeakHashMap>() private val postWorldTickOnce = WeakHashMap>() +private val preServerTickTimers = TimerQueue() +private val postServerTickTimers = TimerQueue() + +class TimerQueue { + private var ticks = 0 + private var head: Timer? = null + + inner class Timer(val timerTicks: Int, val runnable: Runnable) { + val ringAt = ticks + timerTicks + var finished = false + private set + + var next: Timer? = null + var prev: Timer? = null + + init { + if (head == null) { + head = this + } else { + var next = head + + while (next != null) { + if (next.ringAt >= this.ringAt) { + next.prev?.next = this + this.prev = next.prev + next.prev = this + this.next = next + break + } else if (next.next == null) { + next.next = this + this.prev = next + break + } else { + next = next.next + } + } + } + } + + fun execute() { + check(!finished) { "Already finished" } + runnable.run() + finished = true + } + } + + fun tick() { + ticks++ + + var head = head + + while (head != null) { + if (head.ringAt <= ticks) { + head.execute() + head = head.next + head?.prev = null + this.head = head + } else { + break + } + } + } + + fun clear() { + ticks = 0 + head = null + } +} + +fun addPreTickTimer(inTicks: Int, callback: Runnable): TimerQueue.Timer? { + if (SERVER_IS_DYING) { + LOGGER.error("Refusing to add timer $callback in ticks $inTicks while server is dying", IllegalStateException("Server is stopping")) + return null + } + + return preServerTickTimers.Timer(inTicks, callback) +} + +fun addPostTickTimer(inTicks: Int, callback: Runnable): TimerQueue.Timer? { + if (SERVER_IS_DYING) { + LOGGER.error("Refusing to add ticker $callback in ticks $inTicks while server is dying", IllegalStateException("Server is stopping")) + return null + } + + return postServerTickTimers.Timer(inTicks, callback) +} + private var _server: MinecraftServer? = null private var _serverThread: Thread? = null +private var _clientThread: Thread? = null + +fun recordClientThread() { + if (_clientThread != null) { + throw IllegalStateException("Already have client channel") + } + + _clientThread = Thread.currentThread() +} + +fun runIfClient(lambda: () -> Unit) { + if (_clientThread !== null) { + lambda.invoke() + } +} + +fun runIfClient(value: V, lambda: () -> V): V { + if (_clientThread !== null) { + return lambda.invoke() + } + + return value +} fun isServerThread(): Boolean { return Thread.currentThread() === _serverThread } +fun isClientThread(): Boolean { + return Thread.currentThread() === _clientThread +} + val MINECRAFT_SERVER: MinecraftServer get() { return _server ?: throw NullPointerException("Not running a server!") @@ -67,6 +181,8 @@ interface IConditionalTickable : ITickable { @SubscribeEvent(priority = EventPriority.LOWEST) fun onServerTick(event: ServerTickEvent) { if (event.phase === TickEvent.Phase.START) { + preServerTickTimers.tick() + for (i in preServerTick.size - 1 downTo 0) { val ticker = preServerTick[i] @@ -82,6 +198,8 @@ fun onServerTick(event: ServerTickEvent) { preServerTickOnce.removeAt(i) } } else { + postServerTickTimers.tick() + for (i in postServerTick.size - 1 downTo 0) { val ticker = postServerTick[i] @@ -223,6 +341,8 @@ fun addPostWorldTickerOnce(level: Level, ticker: ITickable) { } private fun clear() { + preServerTickTimers.clear() + postServerTickTimers.clear() preServerTick.clear() postServerTick.clear() preWorldTick.clear() diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/ExoSuitProbeItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/ExoSuitProbeItem.kt new file mode 100644 index 000000000..ca8a10461 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/ExoSuitProbeItem.kt @@ -0,0 +1,82 @@ +package ru.dbotthepony.mc.otm.item + +import net.minecraft.ChatFormatting +import net.minecraft.network.chat.Component +import net.minecraft.server.level.ServerPlayer +import net.minecraft.world.InteractionHand +import net.minecraft.world.InteractionResultHolder +import net.minecraft.world.entity.LivingEntity +import net.minecraft.world.entity.player.Player +import net.minecraft.world.item.* +import net.minecraft.world.level.Level +import ru.dbotthepony.mc.otm.OverdriveThatMatters +import ru.dbotthepony.mc.otm.TranslatableComponent +import ru.dbotthepony.mc.otm.addPostTickTimer +import ru.dbotthepony.mc.otm.capability.matteryPlayer +import ru.dbotthepony.mc.otm.client.minecraft +import ru.dbotthepony.mc.otm.registry.MRegistry +import ru.dbotthepony.mc.otm.runIfClient + +class ExoSuitProbeItem : Item(Properties().tab(OverdriveThatMatters.INSTANCE.CREATIVE_TAB).stacksTo(1).rarity(Rarity.EPIC)) { + override fun getUseDuration(p_41454_: ItemStack): Int { + return 24 + } + + override fun appendHoverText(p_41421_: ItemStack, p_41422_: Level?, tooltip: MutableList, p_41424_: TooltipFlag) { + super.appendHoverText(p_41421_, p_41422_, tooltip, p_41424_) + + val alreadyHas = runIfClient(false) { + return@runIfClient minecraft.player?.matteryPlayer?.hasExoSuit ?: false + } + + if (alreadyHas) { + tooltip.add(TranslatableComponent("otm.gui.exosuit.already_activated").withStyle(ChatFormatting.DARK_RED)) + } else { + tooltip.add(TranslatableComponent("otm.gui.exosuit.probe1").withStyle(ChatFormatting.GRAY)) + tooltip.add(TranslatableComponent("otm.gui.exosuit.probe2").withStyle(ChatFormatting.GRAY)) + tooltip.add(TranslatableComponent("otm.gui.exosuit.probe3").withStyle(ChatFormatting.GRAY)) + } + } + + override fun use(p_41432_: Level, player: Player, hand: InteractionHand): InteractionResultHolder { + if (player.matteryPlayer?.hasExoSuit == false) { + player.startUsingItem(hand) + return InteractionResultHolder.consume(player.getItemInHand(hand)) + } + + return super.use(p_41432_, player, hand) + } + + override fun finishUsingItem(itemStack: ItemStack, level: Level, player: LivingEntity): ItemStack { + val mattery = player.matteryPlayer ?: return super.finishUsingItem(itemStack, level, player) + + if (mattery.hasExoSuit) { + return super.finishUsingItem(itemStack, level, player) + } + + itemStack.shrink(1) + + if (player is ServerPlayer) { + mattery.hasExoSuit = true + + player.displayClientMessage(TranslatableComponent("otm.exosuit.granted1").withStyle(ChatFormatting.GRAY), false) + player.hurt(MRegistry.DAMAGE_EXOSUIT_PROBE, 1f) + + for (i in 2 .. 8) { + addPostTickTimer((i - 1) * 100) { + if (!player.hasDisconnected()) { + if (i == 6) { + player.displayClientMessage(TranslatableComponent("otm.exosuit.granted$i", player.displayName.copy().withStyle(ChatFormatting.DARK_PURPLE)).withStyle(ChatFormatting.DARK_AQUA), false) + } else { + player.displayClientMessage(TranslatableComponent("otm.exosuit.granted$i").withStyle(ChatFormatting.GRAY), false) + } + } + } + } + } + + return itemStack + } + + override fun getUseAnimation(p_41452_: ItemStack): UseAnim = UseAnim.BOW +} 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 0436cb08a..0cd5bfc60 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MItems.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MItems.kt @@ -74,6 +74,8 @@ object MItems { } } + val EXOSUIT_PROBE: Item by registry.register(MNames.EXOSUIT_PROBE, ::ExoSuitProbeItem) + val DEBUG_EXPLOSION_SMALL: Item by registry.register(MNames.DEBUG_EXPLOSION_SMALL) { BlockItem(MBlocks.DEBUG_EXPLOSION_SMALL, DEFAULT_PROPERTIES) } val DEBUG_SPHERE_POINTS: Item by registry.register(MNames.DEBUG_SPHERE_POINTS) { BlockItem(MBlocks.DEBUG_SPHERE_POINTS, DEFAULT_PROPERTIES) } 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 47a8bd7f8..fedfbbe83 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MNames.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MNames.kt @@ -92,6 +92,8 @@ object MNames { const val GRAVITATION_FIELD_SENSOR = "gravitation_field_sensor" const val PORTABLE_GRAVITATION_STABILIZER = "portable_gravitation_stabilizer" + const val EXOSUIT_PROBE = "exosuit_probe" + // armor const val TRITANIUM_HELMET = "tritanium_helmet" const val TRITANIUM_CHESTPLATE = "tritanium_chestplate" diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MRegistry.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MRegistry.kt index 02e5626c5..17a92fe66 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MRegistry.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MRegistry.kt @@ -181,10 +181,18 @@ object MRegistry { CRATE_PURPLE, ) - val DAMAGE_BECOME_ANDROID = ImmutableDamageSource(DamageSource("otm_become_android").bypassArmor().bypassInvul().bypassMagic()) - val DAMAGE_BECOME_HUMANE = ImmutableDamageSource(DamageSource("otm_become_humane").bypassArmor().bypassInvul().bypassMagic()) - val DAMAGE_EVENT_HORIZON = ImmutableDamageSource(DamageSource("otm_event_horizon").bypassMagic().bypassArmor()) - val DAMAGE_HAWKING_RADIATION = ImmutableDamageSource(DamageSource("otm_hawking_radiation")) + const val DAMAGE_BECOME_ANDROID_ID = "otm_become_android" + const val DAMAGE_BECOME_HUMANE_ID = "otm_become_humane" + const val DAMAGE_EVENT_HORIZON_ID = "otm_event_horizon" + const val DAMAGE_HAWKING_RADIATION_ID = "otm_hawking_radiation" + const val DAMAGE_EXOSUIT_PROBE_ID = "otm_exosuit_probe" + + val DAMAGE_EXOSUIT_PROBE = ImmutableDamageSource(DamageSource(DAMAGE_EXOSUIT_PROBE_ID).bypassArmor().bypassMagic()) + + val DAMAGE_BECOME_ANDROID = ImmutableDamageSource(DamageSource(DAMAGE_BECOME_ANDROID_ID).bypassArmor().bypassInvul().bypassMagic()) + val DAMAGE_BECOME_HUMANE = ImmutableDamageSource(DamageSource(DAMAGE_BECOME_HUMANE_ID).bypassArmor().bypassInvul().bypassMagic()) + val DAMAGE_EVENT_HORIZON = ImmutableDamageSource(DamageSource(DAMAGE_EVENT_HORIZON_ID).bypassMagic().bypassArmor()) + val DAMAGE_HAWKING_RADIATION = ImmutableDamageSource(DamageSource(DAMAGE_HAWKING_RADIATION_ID)) const val DAMAGE_EMP_NAME = "otm_emp" const val DAMAGE_PLASMA_NAME = "otm_plasma" val DAMAGE_EMP: DamageSource = ImmutableDamageSource(EMPDamageSource()) diff --git a/src/test/kotlin/ru/dbotthepony/mc/otm/tests/TimerQueueTests.kt b/src/test/kotlin/ru/dbotthepony/mc/otm/tests/TimerQueueTests.kt new file mode 100644 index 000000000..0b1adfb63 --- /dev/null +++ b/src/test/kotlin/ru/dbotthepony/mc/otm/tests/TimerQueueTests.kt @@ -0,0 +1,40 @@ +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.TimerQueue + +object TimerQueueTests { + @Test + @DisplayName("TimerQueue test") + fun test() { + val queue = TimerQueue() + + var state = 0 + + queue.Timer(1) { state = 1 } + queue.Timer(2) { state = 2 } + queue.Timer(3) { state = 3 } + queue.Timer(7) { state = 7 } + queue.Timer(6) { state = 6 } + queue.Timer(2) { state = 2 } + + queue.tick() // 1 + assertEquals(1, state) + queue.tick() // 2 + 2 + assertEquals(2, state) + queue.tick() // 3 + assertEquals(3, state) + queue.tick() // 4 + assertEquals(3, state) + queue.tick() // 5 + assertEquals(3, state) + queue.tick() // 6 + assertEquals(6, state) + queue.tick() // 7 + assertEquals(7, state) + queue.tick() // 8 + assertEquals(7, state) + } +}