Compare commits

...

3 Commits

15 changed files with 240 additions and 34 deletions

View File

@ -633,6 +633,11 @@ private fun blocks(provider: MatteryLanguageProvider) {
private fun items(provider: MatteryLanguageProvider) {
with(provider.english) {
add(MItems.REDSTONE_INTERACTOR, "Redstone Key")
add(MItems.REDSTONE_INTERACTOR, "desc", "Allows to 'send' redstone signal out of any block")
add(MItems.REDSTONE_INTERACTOR, "desc1", "In other words, allows to temporarily activate redstone mechanisms, such as opening Iron Doors")
add(MItems.REDSTONE_INTERACTOR, "desc2", "Use while sneaking to set redstone timer")
add(MItems.PROCEDURAL_BATTERY, "Mythical Battery")
add(MItems.PROCEDURAL_BATTERY, "desc", "These batteries are found in dungeons with randomized stats")

View File

@ -634,6 +634,11 @@ private fun blocks(provider: MatteryLanguageProvider) {
private fun items(provider: MatteryLanguageProvider) {
with(provider.russian) {
add(MItems.REDSTONE_INTERACTOR, "Ключ красного камня")
add(MItems.REDSTONE_INTERACTOR, "desc", "'Посылает' сигнал красного камня из блока, с которым взаимодействует")
add(MItems.REDSTONE_INTERACTOR, "desc1", "Другими словами, позволяет активировать механизмы красного камня, такие как железные двери")
add(MItems.REDSTONE_INTERACTOR, "desc2", "Для настройки таймера используйте будучи крадясь")
add(MItems.PROCEDURAL_BATTERY, "Загадочный аккумулятор")
add(MItems.PROCEDURAL_BATTERY, "desc", "Данные аккумуляторы можно найти в подземельях, со случайными характеристиками")

View File

@ -522,4 +522,9 @@ fun addCraftingTableRecipes(consumer: RecipeOutput) {
.rowB(MItemTags.TRITANIUM_PLATES)
.build(consumer, "grill_alt_c/${color?.name?.lowercase() ?: "default"}")
}
MatteryRecipe(MItems.REDSTONE_INTERACTOR, category = RecipeCategory.TOOLS)
.rowAB(Items.LEVER, Tags.Items.NUGGETS_IRON)
.rowB(Tags.Items.DUSTS_REDSTONE)
.build(consumer)
}

View File

@ -0,0 +1,36 @@
package ru.dbotthepony.mc.otm.mixin;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import ru.dbotthepony.mc.otm.item.tool.RedstoneInteractorItem;
@Mixin(BlockBehaviour.BlockStateBase.class)
public abstract class BlockStateBaseMixin {
@Shadow
protected abstract BlockState asState();
@Inject(
method = "getSignal(Lnet/minecraft/world/level/BlockGetter;Lnet/minecraft/core/BlockPos;Lnet/minecraft/core/Direction;)I",
at = @At("HEAD"),
cancellable = true,
remap = false
)
public void getSignal(BlockGetter p_60747_, BlockPos p_60748_, Direction p_60749_, CallbackInfoReturnable<Integer> info) {
if (p_60747_ instanceof Level level) {
int hookResult = RedstoneInteractorItem.Companion.getSignalHook(level, this.asState(), p_60748_, p_60749_);
if (hookResult != -1) {
info.setReturnValue(hookResult);
}
}
}
}

View File

@ -9,9 +9,6 @@ import net.minecraft.core.HolderLookup
import net.minecraft.server.MinecraftServer
import net.minecraft.world.level.Level
import net.neoforged.api.distmarker.Dist
import net.neoforged.bus.api.EventPriority
import net.neoforged.bus.api.SubscribeEvent
import net.neoforged.fml.common.EventBusSubscriber
import net.neoforged.fml.loading.FMLLoader
import net.neoforged.neoforge.event.server.ServerAboutToStartEvent
import net.neoforged.neoforge.event.server.ServerStoppedEvent
@ -19,7 +16,6 @@ import net.neoforged.neoforge.event.server.ServerStoppingEvent
import net.neoforged.neoforge.event.tick.LevelTickEvent
import net.neoforged.neoforge.event.tick.ServerTickEvent
import org.apache.logging.log4j.LogManager
import ru.dbotthepony.mc.otm.block.entity.MatteryBlockEntity
import ru.dbotthepony.mc.otm.capability.AbstractProfiledStorage
import ru.dbotthepony.mc.otm.client.minecraft
import ru.dbotthepony.mc.otm.core.collect.WeakHashSet
@ -226,6 +222,17 @@ fun tickServer(ticker: IConditionalTickable) {
postServerTick.add(ticker, SERVER_IS_LIVE, "Server is stopping")
}
fun Level.timer(time: Int, ticker: Runnable): TickList.Timer? {
if (this.isClientSide) return null
if (!SERVER_IS_LIVE) {
LOGGER.error("Refusing to add ticker $ticker while server is dying", IllegalStateException("Server is stopping"))
return null
}
return postWorldTick.computeIfAbsent(this) { TickList() }.timer(time, ticker)
}
fun Level.once(ticker: ITickable) {
if (this.isClientSide) return

View File

@ -56,6 +56,7 @@ import ru.dbotthepony.mc.otm.item.tool.ExplosiveHammerItem
import ru.dbotthepony.mc.otm.item.armor.TritaniumArmorItem
import ru.dbotthepony.mc.otm.item.QuantumBatteryItem
import ru.dbotthepony.mc.otm.item.PortableCondensationDriveItem
import ru.dbotthepony.mc.otm.item.tool.RedstoneInteractorItem
import ru.dbotthepony.mc.otm.matter.AbstractRegistryAction
import ru.dbotthepony.mc.otm.matter.IMatterFunction
import ru.dbotthepony.mc.otm.matter.MatterManager
@ -200,6 +201,8 @@ object OverdriveThatMatters {
FORGE_BUS.addListener(EventPriority.NORMAL, MCommands::register)
FORGE_BUS.addListener(EventPriority.NORMAL, RedstoneInteractorItem::onUse)
if (isCuriosLoaded) {
FORGE_BUS.addListener(EventPriority.NORMAL, ::onCuriosSlotModifiersUpdated)
}

View File

@ -32,6 +32,7 @@ import ru.dbotthepony.mc.otm.core.TranslatableComponent
import ru.dbotthepony.mc.otm.core.get
import ru.dbotthepony.mc.otm.core.math.plus
import ru.dbotthepony.mc.otm.core.set
import ru.dbotthepony.mc.otm.item.tool.RedstoneInteractorItem
import ru.dbotthepony.mc.otm.shapes.BlockShapes
class ComputerTerminalBlock(val color: DyeColor?) : RotatableMatteryBlock(
@ -44,7 +45,7 @@ class ComputerTerminalBlock(val color: DyeColor?) : RotatableMatteryBlock(
private val shapes = getShapeForEachState(rotationProperty) { BlockShapes.COMPUTER_TERMINAL.rotateFromNorth(it[rotationProperty]).computeShape() }
init {
registerDefaultState(defaultBlockState().set(BlockStateProperties.POWERED, false).set(TICKS, TickTimer.TICK_20))
registerDefaultState(defaultBlockState().set(BlockStateProperties.POWERED, false).set(TICKS, RedstoneInteractorItem.TickTimer.TICK_20))
}
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
@ -86,9 +87,9 @@ class ComputerTerminalBlock(val color: DyeColor?) : RotatableMatteryBlock(
): InteractionResult {
if (ply.isShiftKeyDown) {
val current = blockState[TICKS]
val next = TickTimer.entries.getOrElse(current.ordinal + 1) { TickTimer.TICK_2 }
val next = RedstoneInteractorItem.TickTimer.entries.getOrElse(current.ordinal + 1) { RedstoneInteractorItem.TickTimer.TICK_2 }
level.playSound(ply, blockPos, SoundEvents.DISPENSER_FAIL, SoundSource.BLOCKS, 1f, 1f)
level.playSound(ply, blockPos, SoundEvents.DISPENSER_FAIL, SoundSource.BLOCKS, 0.3f, 1.1f)
level.gameEvent(ply, GameEvent.BLOCK_ACTIVATE, blockPos)
if (!level.isClientSide) {
@ -141,21 +142,7 @@ class ComputerTerminalBlock(val color: DyeColor?) : RotatableMatteryBlock(
return shapes[state]!!
}
enum class TickTimer(val ticks: Int) : StringRepresentable {
TICK_2(2),
TICK_10(10),
TICK_20(20),
TICK_30(30),
TICK_40(40);
private val str = ticks.toString()
override fun getSerializedName(): String {
return str
}
}
companion object {
val TICKS: EnumProperty<TickTimer> = EnumProperty.create("ticks", TickTimer::class.java)
val TICKS: EnumProperty<RedstoneInteractorItem.TickTimer> = EnumProperty.create("ticks", RedstoneInteractorItem.TickTimer::class.java)
}
}

View File

@ -450,7 +450,11 @@ private enum class PreviewScrollers(
LEFT_TO_RIGHT(
init = { it, random ->
it.xOffset = it.width
it.yOffset = random.nextFloat(-it.childrenRectHeight + 18f, 0f)
if (it.childrenRectHeight == 0f)
it.yOffset = 0f
else
it.yOffset = random.nextFloat(-it.childrenRectHeight + 18f, 0f)
},
scroll = { it, _ ->
@ -462,7 +466,11 @@ private enum class PreviewScrollers(
RIGHT_TO_LEFT(
init = { it, random ->
it.xOffset = -it.childrenRectWidth
it.yOffset = random.nextFloat(-it.childrenRectHeight + 18f, 0f)
if (it.childrenRectHeight == 0f)
it.yOffset = 0f
else
it.yOffset = random.nextFloat(-it.childrenRectHeight + 18f, 0f)
},
scroll = { it, _ ->
@ -474,7 +482,11 @@ private enum class PreviewScrollers(
BOTTOM_TO_TOP(
init = { it, random ->
it.yOffset = -it.childrenRectHeight
it.xOffset = random.nextFloat(-it.childrenRectWidth + 18f, 0f)
if (it.childrenRectWidth == 0f)
it.xOffset = 0f
else
it.xOffset = random.nextFloat(-it.childrenRectWidth + 18f, 0f)
},
scroll = { it, _ ->
@ -486,7 +498,11 @@ private enum class PreviewScrollers(
TOP_TO_BOTTOM(
init = { it, random ->
it.yOffset = it.height
it.xOffset = random.nextFloat(-it.childrenRectWidth + 18f, 0f)
if (it.childrenRectWidth == 0f)
it.xOffset = 0f
else
it.xOffset = random.nextFloat(-it.childrenRectWidth + 18f, 0f)
},
scroll = { it, _ ->

View File

@ -36,6 +36,8 @@ class CondensedCreativeCompat : CondensedCreativeInitializer {
addByBase(MItems.COBBLESTONE_GENERATOR, MCreativeTabs.MAIN)
addByBase(MItems.ESSENCE_STORAGE, MCreativeTabs.MAIN)
addByBase(MItems.GRILL, MCreativeTabs.MAIN)
addByColor(MItems.TRITANIUM_STRIPED_BLOCK, DyeColor.YELLOW, MCreativeTabs.DECORATIVE)
addByColor(MItems.TRITANIUM_STRIPED_STAIRS, DyeColor.YELLOW, MCreativeTabs.DECORATIVE)
addByColor(MItems.TRITANIUM_STRIPED_SLAB, DyeColor.YELLOW, MCreativeTabs.DECORATIVE)

View File

@ -19,12 +19,9 @@ class TickList : ITickable {
private var nextSometime = 0
inner class Timer(val timerTicks: Int, val runnable: Runnable) : Comparable<Timer> {
inner class Timer(timerTicks: Int, private var runnable: Runnable?) : Comparable<Timer> {
val ringAt = ticks + timerTicks
var finished = false
private set
init {
timers.add(this)
}
@ -33,10 +30,14 @@ class TickList : ITickable {
return ringAt.compareTo(other.ringAt)
}
fun cancel() {
runnable = null
timers.remove(this)
}
fun execute() {
if (finished) return
runnable.run()
finished = true
runnable?.run()
runnable = null
}
}

View File

@ -0,0 +1,132 @@
package ru.dbotthepony.mc.otm.item.tool
import com.mojang.serialization.Codec
import net.minecraft.ChatFormatting
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.sounds.SoundEvents
import net.minecraft.sounds.SoundSource
import net.minecraft.util.StringRepresentable
import net.minecraft.world.InteractionHand
import net.minecraft.world.InteractionResult
import net.minecraft.world.InteractionResultHolder
import net.minecraft.world.ItemInteractionResult
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.context.UseOnContext
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.gameevent.GameEvent
import net.neoforged.neoforge.event.entity.player.UseItemOnBlockEvent
import ru.dbotthepony.mc.otm.core.TranslatableComponent
import ru.dbotthepony.mc.otm.core.math.component1
import ru.dbotthepony.mc.otm.core.math.component2
import ru.dbotthepony.mc.otm.core.math.component3
import ru.dbotthepony.mc.otm.core.math.plus
import ru.dbotthepony.mc.otm.core.util.TickList
import ru.dbotthepony.mc.otm.item.MatteryItem
import ru.dbotthepony.mc.otm.registry.MDataComponentTypes
import ru.dbotthepony.mc.otm.timer
import java.util.*
class RedstoneInteractorItem : MatteryItem(Properties().stacksTo(1)) {
init {
tooltips.add { TranslatableComponent("$descriptionId.desc").withStyle(ChatFormatting.GRAY) }
tooltips.add { TranslatableComponent("$descriptionId.desc1").withStyle(ChatFormatting.GRAY) }
tooltips.add { TranslatableComponent("$descriptionId.desc2").withStyle(ChatFormatting.GRAY) }
}
private fun updateTickTimer(level: Level, player: Player, itemStack: ItemStack) {
val current = itemStack[MDataComponentTypes.TICK_TIMER] ?: TickTimer.TICK_30
val next = TickTimer.entries.getOrElse(current.ordinal + 1) { RedstoneInteractorItem.TickTimer.TICK_2 }
if (level.isClientSide) {
player.sendSystemMessage(TranslatableComponent("otm.gui.tick_timer_set", next.ticks.toString()))
// play sound in front of player so it sounds properly with HRTF enabled
val (x, y, z) = player.getEyePosition(1f) + player.getViewVector(1f)
level.playSound(player, x, y, z, SoundEvents.DISPENSER_FAIL, SoundSource.PLAYERS, 0.1f, 1.1f)
}
itemStack[MDataComponentTypes.TICK_TIMER] = next
}
override fun use(level: Level, player: Player, hand: InteractionHand): InteractionResultHolder<ItemStack> {
if (player.isShiftKeyDown) {
val itemStack = player.getItemInHand(hand)
updateTickTimer(level, player, itemStack)
return InteractionResultHolder.success(itemStack)
}
return super.use(level, player, hand)
}
override fun useOn(context: UseOnContext): InteractionResult {
if (context.player?.isShiftKeyDown == true) {
updateTickTimer(context.level, context.player!!, context.itemInHand)
return InteractionResult.sidedSuccess(context.level.isClientSide())
}
context.level.playSound(context.player, context.clickedPos, SoundEvents.STONE_BUTTON_CLICK_ON, SoundSource.BLOCKS, 1f, 1f)
context.level.gameEvent(context.player, GameEvent.BLOCK_ACTIVATE, context.clickedPos)
if (context.level.isClientSide)
return InteractionResult.SUCCESS
val map = signals.computeIfAbsent(context.level) { HashMap() }
val positions = Direction.entries.map { context.clickedPos + it.normal }.toMutableList()
positions.add(context.clickedPos)
for (pos in positions) {
val shouldSendUpdate = pos !in map
val timer = context.level.timer(context.itemInHand.getOrDefault(MDataComponentTypes.TICK_TIMER, TickTimer.TICK_30).ticks) {
map.remove(pos)
context.level.updateNeighborsAt(pos, context.level.getBlockState(pos).block)
} ?: throw RuntimeException("Timer unexpectedly ended up being null")
map.put(pos, timer)?.cancel()
if (shouldSendUpdate) {
context.level.updateNeighborsAt(pos, context.level.getBlockState(pos).block)
}
}
return InteractionResult.CONSUME
}
enum class TickTimer(val ticks: Int) : StringRepresentable {
TICK_2(2),
TICK_10(10),
TICK_20(20),
TICK_30(30),
TICK_40(40);
private val str = ticks.toString()
override fun getSerializedName(): String {
return str
}
companion object {
val CODEC: Codec<TickTimer> = StringRepresentable.fromEnum(TickTimer::values)
}
}
companion object {
private val signals = WeakHashMap<Level, HashMap<BlockPos, TickList.Timer>>()
fun getSignalHook(level: Level, blockState: BlockState, blockPos: BlockPos, direction: Direction): Int {
if (signals[level]?.containsKey(blockPos) == true)
return 15
return -1
}
fun onUse(event: UseItemOnBlockEvent) {
if (event.itemStack.item is RedstoneInteractorItem) {
if ((event.itemStack.item as RedstoneInteractorItem).useOn(event.useOnContext).consumesAction())
event.cancelWithResult(ItemInteractionResult.sidedSuccess(event.level.isClientSide()))
}
}
}
}

View File

@ -167,6 +167,7 @@ private fun addMainCreativeTabItems(consumer: CreativeModeTab.Output) {
energized(MItems.ENERGY_SWORD)
accept(MItems.REDSTONE_INTERACTOR)
accept(MItems.EXPLOSIVE_HAMMER)
accept(ItemStack(MItems.EXPLOSIVE_HAMMER).also { MItems.EXPLOSIVE_HAMMER.prime(it) })

View File

@ -14,6 +14,7 @@ import ru.dbotthepony.mc.otm.capability.matter.PatternState
import ru.dbotthepony.mc.otm.container.ItemFilter
import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.mc.otm.data.DecimalCodec
import ru.dbotthepony.mc.otm.item.tool.RedstoneInteractorItem
import ru.dbotthepony.mc.otm.network.StreamCodecs
import java.util.UUID
@ -60,6 +61,7 @@ object MDataComponentTypes {
}
val ITEM_FILTER: DataComponentType<ItemFilter> by registry.register("item_filter") { DataComponentType.builder<ItemFilter>().persistent(ItemFilter.CODEC).build() }
val TICK_TIMER: DataComponentType<RedstoneInteractorItem.TickTimer> by registry.register("tick_timer") { DataComponentType.builder<RedstoneInteractorItem.TickTimer>().persistent(RedstoneInteractorItem.TickTimer.CODEC).build() }
val EXPERIENCE: DataComponentType<Long> by registry.register("experience") { DataComponentType.builder<Long>().persistent(Codec.LONG).networkSynchronized(StreamCodecs.LONG).build() }

View File

@ -68,6 +68,7 @@ import ru.dbotthepony.mc.otm.item.matter.MatterDustItem
import ru.dbotthepony.mc.otm.item.matter.PatternStorageItem
import ru.dbotthepony.mc.otm.item.tool.ExplosiveHammerItem
import ru.dbotthepony.mc.otm.item.tool.MatteryAxeItem
import ru.dbotthepony.mc.otm.item.tool.RedstoneInteractorItem
import ru.dbotthepony.mc.otm.item.weapon.EnergySwordItem
import java.util.function.Supplier
@ -320,6 +321,8 @@ 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 REDSTONE_INTERACTOR: RedstoneInteractorItem by registry.register("redstone_interactor") { RedstoneInteractorItem() }
val ESSENCE_SERVO: EssenceServoItem by registry.register("essence_servo") { EssenceServoItem() }
val ESSENCE_CAPSULE: EssenceCapsuleItem by registry.register("essence_capsule") { EssenceCapsuleItem(false) }
val ESSENCE_DRIVE: EssenceCapsuleItem by registry.register("essence_drive") { EssenceCapsuleItem(true) }

View File

@ -17,7 +17,8 @@
"MixinPlayer",
"HopperBlockEntityMixin",
"DispenserBlockEntityMixin",
"GuiGraphicsMixin"
"GuiGraphicsMixin",
"BlockStateBaseMixin"
],
"client": [
"MixinGameRenderer",