Compare commits
10 Commits
732fc86889
...
961c74de34
Author | SHA1 | Date |
---|---|---|
Shadowfacts | 961c74de34 | |
Shadowfacts | c278d137ef | |
Shadowfacts | 12e055d645 | |
Shadowfacts | 9d98481ba5 | |
Shadowfacts | e4662b0f6f | |
Shadowfacts | 4fa5a12746 | |
Shadowfacts | 7286efcfc2 | |
Shadowfacts | 7cb0168c2f | |
Shadowfacts | b435948ee3 | |
Shadowfacts | c15700bf5d |
|
@ -7,17 +7,17 @@ import net.minecraft.client.gui.screen.ingame.HandledScreen;
|
||||||
import net.minecraft.entity.player.PlayerInventory;
|
import net.minecraft.entity.player.PlayerInventory;
|
||||||
import net.minecraft.screen.slot.Slot;
|
import net.minecraft.screen.slot.Slot;
|
||||||
import net.minecraft.text.Text;
|
import net.minecraft.text.Text;
|
||||||
import net.shadowfacts.phycon.block.terminal.TerminalScreen;
|
import net.shadowfacts.phycon.block.terminal.AbstractTerminalScreen;
|
||||||
import net.shadowfacts.phycon.block.terminal.TerminalScreenHandler;
|
import net.shadowfacts.phycon.block.terminal.AbstractTerminalScreenHandler;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
@Mixin(TerminalScreen.class)
|
@Mixin(AbstractTerminalScreen.class)
|
||||||
public abstract class MixinTerminalScreen extends HandledScreen<TerminalScreenHandler> implements ISpecialScrollableScreen, IContainerScreen {
|
public abstract class MixinTerminalScreen extends HandledScreen<AbstractTerminalScreenHandler> implements ISpecialScrollableScreen, IContainerScreen {
|
||||||
|
|
||||||
private MixinTerminalScreen(TerminalScreenHandler screenHandler, PlayerInventory playerInventory, Text text) {
|
private MixinTerminalScreen(AbstractTerminalScreenHandler screenHandler, PlayerInventory playerInventory, Text text) {
|
||||||
super(screenHandler, playerInventory, text);
|
super(screenHandler, playerInventory, text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ import me.shedaniel.rei.api.plugins.REIPluginV0
|
||||||
import net.fabricmc.api.ClientModInitializer
|
import net.fabricmc.api.ClientModInitializer
|
||||||
import net.minecraft.client.MinecraftClient
|
import net.minecraft.client.MinecraftClient
|
||||||
import net.minecraft.util.Identifier
|
import net.minecraft.util.Identifier
|
||||||
import net.shadowfacts.phycon.block.terminal.TerminalScreen
|
import net.shadowfacts.phycon.block.terminal.AbstractTerminalScreen
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
|
@ -17,7 +17,7 @@ object PhyConPlugin: ClientModInitializer, REIPluginV0 {
|
||||||
const val MODID = "phycon_rei"
|
const val MODID = "phycon_rei"
|
||||||
|
|
||||||
override fun onInitializeClient() {
|
override fun onInitializeClient() {
|
||||||
TerminalScreen.registerClickHandler { mouseX, mouseY, button ->
|
AbstractTerminalScreen.registerClickHandler { mouseX, mouseY, button ->
|
||||||
REIHelper.getInstance().searchTextField?.also {
|
REIHelper.getInstance().searchTextField?.also {
|
||||||
if (it.bounds.contains(mouseX, mouseY)) {
|
if (it.bounds.contains(mouseX, mouseY)) {
|
||||||
this.terminalVC.searchField.resignFirstResponder()
|
this.terminalVC.searchField.resignFirstResponder()
|
||||||
|
@ -32,8 +32,8 @@ object PhyConPlugin: ClientModInitializer, REIPluginV0 {
|
||||||
override fun getPluginIdentifier() = Identifier(MODID, "rei_plugin")
|
override fun getPluginIdentifier() = Identifier(MODID, "rei_plugin")
|
||||||
|
|
||||||
override fun registerBounds(helper: DisplayHelper) {
|
override fun registerBounds(helper: DisplayHelper) {
|
||||||
BaseBoundsHandler.getInstance().registerExclusionZones(TerminalScreen::class.java) {
|
BaseBoundsHandler.getInstance().registerExclusionZones(AbstractTerminalScreen::class.java) {
|
||||||
val screen = MinecraftClient.getInstance().currentScreen as TerminalScreen
|
val screen = MinecraftClient.getInstance().currentScreen as AbstractTerminalScreen<*, *>
|
||||||
val view = screen.terminalVC.settingsView
|
val view = screen.terminalVC.settingsView
|
||||||
val rect = view.convert(view.bounds, to = null)
|
val rect = view.convert(view.bounds, to = null)
|
||||||
listOf(
|
listOf(
|
||||||
|
|
|
@ -6,8 +6,8 @@ import net.minecraft.client.render.item.ItemRenderer;
|
||||||
import net.minecraft.client.util.math.MatrixStack;
|
import net.minecraft.client.util.math.MatrixStack;
|
||||||
import net.minecraft.item.ItemStack;
|
import net.minecraft.item.ItemStack;
|
||||||
import net.minecraft.screen.slot.Slot;
|
import net.minecraft.screen.slot.Slot;
|
||||||
import net.shadowfacts.phycon.block.terminal.TerminalScreen;
|
import net.shadowfacts.phycon.block.terminal.AbstractTerminalScreen;
|
||||||
import net.shadowfacts.phycon.block.terminal.TerminalScreenHandler;
|
import net.shadowfacts.phycon.block.terminal.AbstractTerminalScreenHandler;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
@ -26,8 +26,8 @@ public class MixinHandledScreen {
|
||||||
at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/systems/RenderSystem;enableDepthTest()V")
|
at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/systems/RenderSystem;enableDepthTest()V")
|
||||||
)
|
)
|
||||||
private void drawSlotUnderlay(MatrixStack matrixStack, Slot slot, CallbackInfo ci) {
|
private void drawSlotUnderlay(MatrixStack matrixStack, Slot slot, CallbackInfo ci) {
|
||||||
if ((Object)this instanceof TerminalScreen) {
|
if ((Object)this instanceof AbstractTerminalScreen) {
|
||||||
TerminalScreen self = (TerminalScreen)(Object)this;
|
AbstractTerminalScreen<?, ?> self = (AbstractTerminalScreen<?, ?>)(Object)this;
|
||||||
self.drawSlotUnderlay(matrixStack, slot);
|
self.drawSlotUnderlay(matrixStack, slot);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,9 +37,9 @@ public class MixinHandledScreen {
|
||||||
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/item/ItemRenderer;renderGuiItemOverlay(Lnet/minecraft/client/font/TextRenderer;Lnet/minecraft/item/ItemStack;IILjava/lang/String;)V")
|
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/item/ItemRenderer;renderGuiItemOverlay(Lnet/minecraft/client/font/TextRenderer;Lnet/minecraft/item/ItemStack;IILjava/lang/String;)V")
|
||||||
)
|
)
|
||||||
private void drawSlotAmount(ItemRenderer itemRenderer, TextRenderer textRenderer, ItemStack stack, int x, int y, @Nullable String countLabel, MatrixStack matrixStack, Slot slot) {
|
private void drawSlotAmount(ItemRenderer itemRenderer, TextRenderer textRenderer, ItemStack stack, int x, int y, @Nullable String countLabel, MatrixStack matrixStack, Slot slot) {
|
||||||
if ((Object)this instanceof TerminalScreen) {
|
if ((Object)this instanceof AbstractTerminalScreen) {
|
||||||
TerminalScreen self = (TerminalScreen)(Object)this;
|
AbstractTerminalScreen<?, ?> self = (AbstractTerminalScreen<?, ?>)(Object)this;
|
||||||
TerminalScreenHandler handler = self.getScreenHandler();
|
AbstractTerminalScreenHandler<?> handler = self.getScreenHandler();
|
||||||
if (slot.id < handler.getNetworkSlotsEnd() && stack.getCount() > 1) {
|
if (slot.id < handler.getNetworkSlotsEnd() && stack.getCount() > 1) {
|
||||||
self.drawNetworkSlotAmount(stack, x, y, slot, matrixStack);
|
self.drawNetworkSlotAmount(stack, x, y, slot, matrixStack);
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package net.shadowfacts.cacao.view.button
|
package net.shadowfacts.cacao.view.button
|
||||||
|
|
||||||
import net.minecraft.client.util.math.MatrixStack
|
import net.minecraft.client.util.math.MatrixStack
|
||||||
|
import net.minecraft.text.Text
|
||||||
import net.shadowfacts.cacao.geometry.Point
|
import net.shadowfacts.cacao.geometry.Point
|
||||||
import net.shadowfacts.cacao.util.MouseButton
|
import net.shadowfacts.cacao.util.MouseButton
|
||||||
import net.shadowfacts.cacao.util.texture.NinePatchTexture
|
import net.shadowfacts.cacao.util.texture.NinePatchTexture
|
||||||
|
@ -8,6 +9,7 @@ import net.shadowfacts.cacao.util.RenderHelper
|
||||||
import net.shadowfacts.cacao.view.NinePatchView
|
import net.shadowfacts.cacao.view.NinePatchView
|
||||||
import net.shadowfacts.cacao.view.View
|
import net.shadowfacts.cacao.view.View
|
||||||
import net.shadowfacts.kiwidsl.dsl
|
import net.shadowfacts.kiwidsl.dsl
|
||||||
|
import kotlin.math.floor
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An abstract button class. Cannot be constructed directly, used for creating button implementations with their own
|
* An abstract button class. Cannot be constructed directly, used for creating button implementations with their own
|
||||||
|
@ -44,18 +46,38 @@ abstract class AbstractButton<Impl: AbstractButton<Impl>>(val content: View, val
|
||||||
* unless the background view is not fully opaque.
|
* unless the background view is not fully opaque.
|
||||||
*/
|
*/
|
||||||
var background: View? = NinePatchView(NinePatchTexture.BUTTON_BG)
|
var background: View? = NinePatchView(NinePatchTexture.BUTTON_BG)
|
||||||
|
set(value) {
|
||||||
|
field?.removeFromSuperview()
|
||||||
|
field = value
|
||||||
|
value?.also(::addBackground)
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* The background to draw when the button is hovered over by the mouse.
|
* The background to draw when the button is hovered over by the mouse.
|
||||||
* If `null`, the normal [background] will be used.
|
* If `null`, the normal [background] will be used.
|
||||||
* @see background
|
* @see background
|
||||||
*/
|
*/
|
||||||
var hoveredBackground: View? = NinePatchView(NinePatchTexture.BUTTON_HOVERED_BG)
|
var hoveredBackground: View? = NinePatchView(NinePatchTexture.BUTTON_HOVERED_BG)
|
||||||
|
set(value) {
|
||||||
|
field?.removeFromSuperview()
|
||||||
|
field = value
|
||||||
|
value?.also(::addBackground)
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* The background to draw when the button is [disabled].
|
* The background to draw when the button is [disabled].
|
||||||
* If `null`, the normal [background] will be used.
|
* If `null`, the normal [background] will be used.
|
||||||
* @see background
|
* @see background
|
||||||
*/
|
*/
|
||||||
var disabledBackground: View? = NinePatchView(NinePatchTexture.BUTTON_DISABLED_BG)
|
var disabledBackground: View? = NinePatchView(NinePatchTexture.BUTTON_DISABLED_BG)
|
||||||
|
set(value) {
|
||||||
|
field?.removeFromSuperview()
|
||||||
|
field = value
|
||||||
|
value?.also(::addBackground)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The tooltip text shown when this button is hovered.
|
||||||
|
*/
|
||||||
|
var tooltip: Text? = null
|
||||||
|
|
||||||
override fun wasAdded() {
|
override fun wasAdded() {
|
||||||
solver.dsl {
|
solver.dsl {
|
||||||
|
@ -67,22 +89,28 @@ abstract class AbstractButton<Impl: AbstractButton<Impl>>(val content: View, val
|
||||||
content.rightAnchor.greaterThanOrEqualTo(rightAnchor - padding, WEAK)
|
content.rightAnchor.greaterThanOrEqualTo(rightAnchor - padding, WEAK)
|
||||||
content.topAnchor.lessThanOrEqualTo(topAnchor + padding, WEAK)
|
content.topAnchor.lessThanOrEqualTo(topAnchor + padding, WEAK)
|
||||||
content.bottomAnchor.greaterThanOrEqualTo(bottomAnchor - padding, WEAK)
|
content.bottomAnchor.greaterThanOrEqualTo(bottomAnchor - padding, WEAK)
|
||||||
|
|
||||||
listOfNotNull(background, hoveredBackground, disabledBackground).forEach {
|
|
||||||
addSubview(it)
|
|
||||||
it.leftAnchor equalTo leftAnchor
|
|
||||||
it.rightAnchor equalTo rightAnchor
|
|
||||||
it.topAnchor equalTo topAnchor
|
|
||||||
it.bottomAnchor equalTo bottomAnchor
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
listOfNotNull(background, hoveredBackground, disabledBackground).forEach(::addBackground)
|
||||||
|
|
||||||
super.wasAdded()
|
super.wasAdded()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun addBackground(view: View) {
|
||||||
|
if (superview != null && hasSolver) {
|
||||||
|
addSubview(view)
|
||||||
|
solver.dsl {
|
||||||
|
view.leftAnchor equalTo leftAnchor
|
||||||
|
view.rightAnchor equalTo rightAnchor
|
||||||
|
view.topAnchor equalTo topAnchor
|
||||||
|
view.bottomAnchor equalTo bottomAnchor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun draw(matrixStack: MatrixStack, mouse: Point, delta: Float) {
|
override fun draw(matrixStack: MatrixStack, mouse: Point, delta: Float) {
|
||||||
RenderHelper.pushMatrix()
|
RenderHelper.pushMatrix()
|
||||||
RenderHelper.translate(frame.left, frame.top)
|
RenderHelper.translate(floor(frame.left), floor(frame.top))
|
||||||
|
|
||||||
RenderHelper.fill(matrixStack, bounds, backgroundColor)
|
RenderHelper.fill(matrixStack, bounds, backgroundColor)
|
||||||
|
|
||||||
|
@ -96,6 +124,10 @@ abstract class AbstractButton<Impl: AbstractButton<Impl>>(val content: View, val
|
||||||
// don't draw subviews, otherwise all background views + content will get drawn
|
// don't draw subviews, otherwise all background views + content will get drawn
|
||||||
|
|
||||||
RenderHelper.popMatrix()
|
RenderHelper.popMatrix()
|
||||||
|
|
||||||
|
if (tooltip != null && mouse in bounds) {
|
||||||
|
window!!.drawTooltip(listOf(tooltip!!))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun mouseClicked(point: Point, mouseButton: MouseButton): Boolean {
|
override fun mouseClicked(point: Point, mouseButton: MouseButton): Boolean {
|
||||||
|
|
|
@ -26,9 +26,10 @@ object PhysicalConnectivity: ModInitializer {
|
||||||
PhyItems.init()
|
PhyItems.init()
|
||||||
PhyScreens.init()
|
PhyScreens.init()
|
||||||
|
|
||||||
|
registerGlobalReceiver(C2SConfigureDevice)
|
||||||
|
registerGlobalReceiver(C2STerminalCraftingButton)
|
||||||
registerGlobalReceiver(C2STerminalRequestItem)
|
registerGlobalReceiver(C2STerminalRequestItem)
|
||||||
registerGlobalReceiver(C2STerminalUpdateDisplayedItems)
|
registerGlobalReceiver(C2STerminalUpdateDisplayedItems)
|
||||||
registerGlobalReceiver(C2SConfigureDevice)
|
|
||||||
|
|
||||||
for (it in FabricLoader.getInstance().getEntrypoints("phycon", PhyConPlugin::class.java)) {
|
for (it in FabricLoader.getInstance().getEntrypoints("phycon", PhyConPlugin::class.java)) {
|
||||||
it.initializePhyCon(PhyConAPIImpl)
|
it.initializePhyCon(PhyConAPIImpl)
|
||||||
|
|
|
@ -8,6 +8,7 @@ import net.fabricmc.fabric.api.renderer.v1.RendererAccess
|
||||||
import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial
|
import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial
|
||||||
import net.shadowfacts.phycon.block.inserter.InserterScreen
|
import net.shadowfacts.phycon.block.inserter.InserterScreen
|
||||||
import net.shadowfacts.phycon.block.redstone_emitter.RedstoneEmitterScreen
|
import net.shadowfacts.phycon.block.redstone_emitter.RedstoneEmitterScreen
|
||||||
|
import net.shadowfacts.phycon.block.terminal.CraftingTerminalScreen
|
||||||
import net.shadowfacts.phycon.init.PhyScreens
|
import net.shadowfacts.phycon.init.PhyScreens
|
||||||
import net.shadowfacts.phycon.block.terminal.TerminalScreen
|
import net.shadowfacts.phycon.block.terminal.TerminalScreen
|
||||||
import net.shadowfacts.phycon.client.PhyExtendedModelProvider
|
import net.shadowfacts.phycon.client.PhyExtendedModelProvider
|
||||||
|
@ -40,6 +41,7 @@ object PhysicalConnectivityClient: ClientModInitializer {
|
||||||
}
|
}
|
||||||
|
|
||||||
ScreenRegistry.register(PhyScreens.TERMINAL, ::TerminalScreen)
|
ScreenRegistry.register(PhyScreens.TERMINAL, ::TerminalScreen)
|
||||||
|
ScreenRegistry.register(PhyScreens.CRAFTING_TERMINAL, ::CraftingTerminalScreen)
|
||||||
ScreenRegistry.register(PhyScreens.INSERTER, ::InserterScreen)
|
ScreenRegistry.register(PhyScreens.INSERTER, ::InserterScreen)
|
||||||
ScreenRegistry.register(PhyScreens.REDSTONE_EMITTER, ::RedstoneEmitterScreen)
|
ScreenRegistry.register(PhyScreens.REDSTONE_EMITTER, ::RedstoneEmitterScreen)
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ import net.minecraft.world.World
|
||||||
import net.shadowfacts.phycon.api.packet.Packet
|
import net.shadowfacts.phycon.api.packet.Packet
|
||||||
import net.shadowfacts.phycon.init.PhyBlockEntities
|
import net.shadowfacts.phycon.init.PhyBlockEntities
|
||||||
import net.shadowfacts.phycon.block.DeviceBlockEntity
|
import net.shadowfacts.phycon.block.DeviceBlockEntity
|
||||||
import net.shadowfacts.phycon.block.terminal.TerminalBlockEntity
|
import net.shadowfacts.phycon.block.terminal.AbstractTerminalBlockEntity
|
||||||
import net.shadowfacts.phycon.component.*
|
import net.shadowfacts.phycon.component.*
|
||||||
import net.shadowfacts.phycon.packet.*
|
import net.shadowfacts.phycon.packet.*
|
||||||
import net.shadowfacts.phycon.util.ActivationMode
|
import net.shadowfacts.phycon.util.ActivationMode
|
||||||
|
@ -39,7 +39,7 @@ class MinerBlockEntity: DeviceBlockEntity(PhyBlockEntities.MINER),
|
||||||
private val invProxy = MinerInvProxy(this)
|
private val invProxy = MinerInvProxy(this)
|
||||||
|
|
||||||
override val pendingInsertions = mutableListOf<PendingInsertion>()
|
override val pendingInsertions = mutableListOf<PendingInsertion>()
|
||||||
override val dispatchStackTimeout = TerminalBlockEntity.INSERTION_TIMEOUT
|
override val dispatchStackTimeout = AbstractTerminalBlockEntity.INSERTION_TIMEOUT
|
||||||
override val controller = ActivationController(40L, this)
|
override val controller = ActivationController(40L, this)
|
||||||
override var providerPriority = 0
|
override var providerPriority = 0
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
package net.shadowfacts.phycon.block.terminal
|
||||||
|
|
||||||
|
import alexiil.mc.lib.attributes.AttributeList
|
||||||
|
import alexiil.mc.lib.attributes.AttributeProvider
|
||||||
|
import net.minecraft.block.Block
|
||||||
|
import net.minecraft.block.BlockState
|
||||||
|
import net.minecraft.block.Material
|
||||||
|
import net.minecraft.entity.player.PlayerEntity
|
||||||
|
import net.minecraft.item.ItemPlacementContext
|
||||||
|
import net.minecraft.sound.BlockSoundGroup
|
||||||
|
import net.minecraft.state.StateManager
|
||||||
|
import net.minecraft.state.property.Properties
|
||||||
|
import net.minecraft.util.ActionResult
|
||||||
|
import net.minecraft.util.Hand
|
||||||
|
import net.minecraft.util.Identifier
|
||||||
|
import net.minecraft.util.ItemScatterer
|
||||||
|
import net.minecraft.util.hit.BlockHitResult
|
||||||
|
import net.minecraft.util.math.BlockPos
|
||||||
|
import net.minecraft.util.math.Direction
|
||||||
|
import net.minecraft.world.BlockView
|
||||||
|
import net.minecraft.world.World
|
||||||
|
import net.minecraft.world.WorldAccess
|
||||||
|
import net.shadowfacts.phycon.PhysicalConnectivity
|
||||||
|
import net.shadowfacts.phycon.api.NetworkComponentBlock
|
||||||
|
import net.shadowfacts.phycon.block.DeviceBlock
|
||||||
|
import java.util.EnumSet
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author shadowfacts
|
||||||
|
*/
|
||||||
|
abstract class AbstractTerminalBlock<T: AbstractTerminalBlockEntity>: DeviceBlock<T>(
|
||||||
|
Settings.of(Material.METAL)
|
||||||
|
.strength(1.5f)
|
||||||
|
.sounds(BlockSoundGroup.METAL)
|
||||||
|
),
|
||||||
|
NetworkComponentBlock,
|
||||||
|
AttributeProvider {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val FACING = Properties.FACING
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getNetworkConnectedSides(state: BlockState, world: WorldAccess, pos: BlockPos): Collection<Direction> {
|
||||||
|
val set = EnumSet.allOf(Direction::class.java)
|
||||||
|
set.remove(state[FACING])
|
||||||
|
return set
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun appendProperties(builder: StateManager.Builder<Block, BlockState>) {
|
||||||
|
super.appendProperties(builder)
|
||||||
|
builder.add(FACING)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getPlacementState(context: ItemPlacementContext): BlockState {
|
||||||
|
return defaultState.with(FACING, context.playerLookDirection.opposite)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onUse(state: BlockState, world: World, pos: BlockPos, player: PlayerEntity, hand: Hand, hitResult: BlockHitResult): ActionResult {
|
||||||
|
getBlockEntity(world, pos)!!.onActivate(player)
|
||||||
|
return ActionResult.SUCCESS
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStateReplaced(state: BlockState, world: World, pos: BlockPos, newState: BlockState, moved: Boolean) {
|
||||||
|
if (!state.isOf(newState.block)) {
|
||||||
|
val be = getBlockEntity(world, pos)!!
|
||||||
|
be.dropItems()
|
||||||
|
|
||||||
|
super.onStateReplaced(state, world, pos, newState, moved)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun addAllAttributes(world: World, pos: BlockPos, state: BlockState, to: AttributeList<*>) {
|
||||||
|
to.offer(getBlockEntity(world, pos))
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,272 @@
|
||||||
|
package net.shadowfacts.phycon.block.terminal
|
||||||
|
|
||||||
|
import alexiil.mc.lib.attributes.item.GroupedItemInvView
|
||||||
|
import alexiil.mc.lib.attributes.item.ItemStackCollections
|
||||||
|
import alexiil.mc.lib.attributes.item.ItemStackUtil
|
||||||
|
import net.fabricmc.fabric.api.block.entity.BlockEntityClientSerializable
|
||||||
|
import net.fabricmc.fabric.api.screenhandler.v1.ExtendedScreenHandlerFactory
|
||||||
|
import net.minecraft.block.entity.BlockEntityType
|
||||||
|
import net.minecraft.entity.player.PlayerEntity
|
||||||
|
import net.minecraft.entity.player.PlayerInventory
|
||||||
|
import net.minecraft.inventory.Inventory
|
||||||
|
import net.minecraft.inventory.InventoryChangedListener
|
||||||
|
import net.minecraft.item.ItemStack
|
||||||
|
import net.minecraft.nbt.CompoundTag
|
||||||
|
import net.minecraft.network.PacketByteBuf
|
||||||
|
import net.minecraft.screen.ScreenHandler
|
||||||
|
import net.minecraft.server.network.ServerPlayerEntity
|
||||||
|
import net.minecraft.text.TranslatableText
|
||||||
|
import net.minecraft.util.ItemScatterer
|
||||||
|
import net.minecraft.util.Tickable
|
||||||
|
import net.minecraft.util.math.Direction
|
||||||
|
import net.shadowfacts.phycon.api.Interface
|
||||||
|
import net.shadowfacts.phycon.api.packet.Packet
|
||||||
|
import net.shadowfacts.phycon.api.util.IPAddress
|
||||||
|
import net.shadowfacts.phycon.init.PhyBlockEntities
|
||||||
|
import net.shadowfacts.phycon.block.DeviceBlockEntity
|
||||||
|
import net.shadowfacts.phycon.util.NetworkUtil
|
||||||
|
import net.shadowfacts.phycon.component.*
|
||||||
|
import net.shadowfacts.phycon.packet.*
|
||||||
|
import java.lang.ref.WeakReference
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.math.min
|
||||||
|
import kotlin.properties.Delegates
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author shadowfacts
|
||||||
|
*/
|
||||||
|
abstract class AbstractTerminalBlockEntity(type: BlockEntityType<*>): DeviceBlockEntity(type),
|
||||||
|
InventoryChangedListener,
|
||||||
|
BlockEntityClientSerializable,
|
||||||
|
Tickable,
|
||||||
|
ItemStackPacketHandler,
|
||||||
|
NetworkStackDispatcher<AbstractTerminalBlockEntity.PendingInsertion> {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
// the locate/insertion timeouts are only 1 tick because that's long enough to hear from every device on the network
|
||||||
|
// in a degraded state (when there's latency in the network), not handling interface priorities correctly is acceptable
|
||||||
|
val LOCATE_REQUEST_TIMEOUT: Long = 1 // ticks
|
||||||
|
val INSERTION_TIMEOUT: Long = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
protected val inventoryCache = mutableMapOf<IPAddress, GroupedItemInvView>()
|
||||||
|
val internalBuffer = TerminalBufferInventory(18)
|
||||||
|
|
||||||
|
protected val pendingRequests = LinkedList<StackLocateRequest>()
|
||||||
|
override val pendingInsertions = mutableListOf<PendingInsertion>()
|
||||||
|
override val dispatchStackTimeout = INSERTION_TIMEOUT
|
||||||
|
|
||||||
|
private var observers = 0
|
||||||
|
val cachedNetItems = ItemStackCollections.intMap()
|
||||||
|
|
||||||
|
// todo: multiple players could have the terminal open simultaneously
|
||||||
|
var netItemObserver: WeakReference<NetItemObserver>? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
internalBuffer.addListener(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun findDestination(): Interface? {
|
||||||
|
for (dir in Direction.values()) {
|
||||||
|
val itf = NetworkUtil.findConnectedInterface(world!!, pos, dir)
|
||||||
|
if (itf != null) {
|
||||||
|
return itf
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun handle(packet: Packet) {
|
||||||
|
when (packet) {
|
||||||
|
is ReadInventoryPacket -> handleReadInventory(packet)
|
||||||
|
is DeviceRemovedPacket -> handleDeviceRemoved(packet)
|
||||||
|
is StackLocationPacket -> handleStackLocation(packet)
|
||||||
|
is ItemStackPacket -> handleItemStack(packet)
|
||||||
|
is CapacityPacket -> handleCapacity(packet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleReadInventory(packet: ReadInventoryPacket) {
|
||||||
|
inventoryCache[packet.source] = packet.inventory
|
||||||
|
updateAndSync()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleDeviceRemoved(packet: DeviceRemovedPacket) {
|
||||||
|
inventoryCache.remove(packet.source)
|
||||||
|
updateAndSync()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleStackLocation(packet: StackLocationPacket) {
|
||||||
|
val request = pendingRequests.firstOrNull {
|
||||||
|
ItemStackUtil.areEqualIgnoreAmounts(it.stack, packet.stack)
|
||||||
|
}
|
||||||
|
if (request != null) {
|
||||||
|
request.results.add(packet.amount to packet.stackProvider)
|
||||||
|
if (request.isFinishable(counter)) {
|
||||||
|
stackLocateRequestCompleted(request)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun doHandleItemStack(packet: ItemStackPacket): ItemStack {
|
||||||
|
val mode =
|
||||||
|
if (packet.bounceCount > 0) {
|
||||||
|
// if this stack bounced from an inventory, that means we previously tried to send it to the network, so retry
|
||||||
|
TerminalBufferInventory.Mode.TO_NETWORK
|
||||||
|
} else {
|
||||||
|
TerminalBufferInventory.Mode.FROM_NETWORK
|
||||||
|
}
|
||||||
|
|
||||||
|
val remaining = internalBuffer.insert(packet.stack, mode)
|
||||||
|
|
||||||
|
// this happens outside the normal update loop because by receiving the item stack packet
|
||||||
|
// we "know" how much the count in the source inventory has changed
|
||||||
|
updateAndSync()
|
||||||
|
|
||||||
|
return remaining
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun updateAndSync() {
|
||||||
|
updateNetItems()
|
||||||
|
// syncs the internal buffer to the client
|
||||||
|
sync()
|
||||||
|
// syncs the open container (if any) to the client
|
||||||
|
netItemObserver?.get()?.netItemsChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateNetItems() {
|
||||||
|
cachedNetItems.clear()
|
||||||
|
for (inventory in inventoryCache.values) {
|
||||||
|
for (stack in inventory.storedStacks) {
|
||||||
|
val amount = inventory.getAmount(stack)
|
||||||
|
cachedNetItems.mergeInt(stack, amount) { a, b -> a + b }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun beginInsertions() {
|
||||||
|
if (world!!.isClient) return
|
||||||
|
|
||||||
|
for (slot in 0 until internalBuffer.size()) {
|
||||||
|
if (internalBuffer.getMode(slot) != TerminalBufferInventory.Mode.TO_NETWORK) continue
|
||||||
|
if (pendingInsertions.any { it.bufferSlot == slot }) continue
|
||||||
|
val stack = internalBuffer.getStack(slot)
|
||||||
|
dispatchItemStack(stack) { insertion ->
|
||||||
|
insertion.bufferSlot = slot
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun finishPendingRequests() {
|
||||||
|
if (world!!.isClient) return
|
||||||
|
if (pendingRequests.isEmpty()) return
|
||||||
|
|
||||||
|
val finishable = pendingRequests.filter { it.isFinishable(counter) }
|
||||||
|
// stackLocateRequestCompleted removes the object from pendingRequests
|
||||||
|
finishable.forEach(::stackLocateRequestCompleted)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun tick() {
|
||||||
|
super.tick()
|
||||||
|
|
||||||
|
if (!world!!.isClient) {
|
||||||
|
finishPendingRequests()
|
||||||
|
finishTimedOutPendingInsertions()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (counter % 20 == 0L && !world!!.isClient) {
|
||||||
|
beginInsertions()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun onActivate(player: PlayerEntity) {
|
||||||
|
}
|
||||||
|
|
||||||
|
fun requestItem(stack: ItemStack, amount: Int = stack.count) {
|
||||||
|
val request = StackLocateRequest(stack, amount, counter)
|
||||||
|
pendingRequests.add(request)
|
||||||
|
// locate packets are sent immediately instead of being added to a queue
|
||||||
|
// otherwise the terminal UI feels sluggish and unresponsive
|
||||||
|
sendPacket(LocateStackPacket(stack, ipAddress))
|
||||||
|
}
|
||||||
|
|
||||||
|
protected open fun stackLocateRequestCompleted(request: StackLocateRequest) {
|
||||||
|
pendingRequests.remove(request)
|
||||||
|
|
||||||
|
val sortedResults = request.results.toMutableList()
|
||||||
|
sortedResults.sortWith { a, b ->
|
||||||
|
// sort results first by provider priority, and then by the count that it can provide
|
||||||
|
if (a.second.providerPriority == b.second.providerPriority) {
|
||||||
|
b.first - a.first
|
||||||
|
} else {
|
||||||
|
b.second.providerPriority - a.second.providerPriority
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var amountRequested = 0
|
||||||
|
while (amountRequested < request.amount && sortedResults.isNotEmpty()) {
|
||||||
|
val (sourceAmount, sourceInterface) = sortedResults.removeAt(0)
|
||||||
|
val amountToRequest = min(sourceAmount, request.amount - amountRequested)
|
||||||
|
amountRequested += amountToRequest
|
||||||
|
sendPacket(ExtractStackPacket(request.stack, amountToRequest, ipAddress, sourceInterface.ipAddress))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createPendingInsertion(stack: ItemStack) = PendingInsertion(stack, counter)
|
||||||
|
|
||||||
|
override fun finishInsertion(insertion: PendingInsertion): ItemStack {
|
||||||
|
val remaining = super.finishInsertion(insertion)
|
||||||
|
internalBuffer.setStack(insertion.bufferSlot, remaining)
|
||||||
|
|
||||||
|
// as with extracting, we "know" the new amounts and so can update instantly without actually sending out packets
|
||||||
|
updateAndSync()
|
||||||
|
|
||||||
|
return remaining
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onInventoryChanged(inv: Inventory) {
|
||||||
|
if (inv == internalBuffer && world != null && !world!!.isClient) {
|
||||||
|
markDirty()
|
||||||
|
sync()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun dropItems() {
|
||||||
|
ItemScatterer.spawn(world, pos, internalBuffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toCommonTag(tag: CompoundTag) {
|
||||||
|
super.toCommonTag(tag)
|
||||||
|
tag.put("InternalBuffer", internalBuffer.toTag())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun fromCommonTag(tag: CompoundTag) {
|
||||||
|
super.fromCommonTag(tag)
|
||||||
|
internalBuffer.fromTag(tag.getCompound("InternalBuffer"))
|
||||||
|
}
|
||||||
|
|
||||||
|
interface NetItemObserver {
|
||||||
|
fun netItemsChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
class PendingInsertion(stack: ItemStack, timestamp: Long): NetworkStackDispatcher.PendingInsertion<PendingInsertion>(stack, timestamp) {
|
||||||
|
var bufferSlot by Delegates.notNull<Int>()
|
||||||
|
}
|
||||||
|
|
||||||
|
open class StackLocateRequest(
|
||||||
|
val stack: ItemStack,
|
||||||
|
val amount: Int,
|
||||||
|
val timestamp: Long,
|
||||||
|
) {
|
||||||
|
var results: MutableSet<Pair<Int, NetworkStackProvider>> = mutableSetOf()
|
||||||
|
|
||||||
|
val totalResultAmount: Int
|
||||||
|
get() = results.fold(0) { acc, (amount, _) -> acc + amount }
|
||||||
|
|
||||||
|
fun isFinishable(currentTimestamp: Long): Boolean {
|
||||||
|
// we can't check totalResultAmount >= amount because we need to hear back from all network stack providers to
|
||||||
|
// correctly sort by priority
|
||||||
|
return currentTimestamp - timestamp >= LOCATE_REQUEST_TIMEOUT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,200 @@
|
||||||
|
package net.shadowfacts.phycon.block.terminal
|
||||||
|
|
||||||
|
import com.mojang.blaze3d.systems.RenderSystem
|
||||||
|
import net.minecraft.client.MinecraftClient
|
||||||
|
import net.minecraft.client.gui.DrawableHelper
|
||||||
|
import net.minecraft.client.gui.Element
|
||||||
|
import net.minecraft.client.gui.widget.TextFieldWidget
|
||||||
|
import net.minecraft.client.render.Tessellator
|
||||||
|
import net.minecraft.client.render.VertexConsumerProvider
|
||||||
|
import net.minecraft.client.util.math.MatrixStack
|
||||||
|
import net.minecraft.entity.player.PlayerInventory
|
||||||
|
import net.minecraft.item.ItemStack
|
||||||
|
import net.minecraft.screen.slot.Slot
|
||||||
|
import net.minecraft.screen.slot.SlotActionType
|
||||||
|
import net.minecraft.text.LiteralText
|
||||||
|
import net.minecraft.text.Text
|
||||||
|
import net.minecraft.util.Identifier
|
||||||
|
import net.shadowfacts.cacao.CacaoHandledScreen
|
||||||
|
import net.shadowfacts.cacao.window.ScreenHandlerWindow
|
||||||
|
import net.shadowfacts.cacao.window.Window
|
||||||
|
import net.shadowfacts.phycon.networking.C2STerminalRequestItem
|
||||||
|
import net.shadowfacts.phycon.networking.C2STerminalUpdateDisplayedItems
|
||||||
|
import java.math.RoundingMode
|
||||||
|
import java.text.DecimalFormat
|
||||||
|
import java.util.LinkedList
|
||||||
|
import kotlin.math.ceil
|
||||||
|
import kotlin.math.min
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author shadowfacts
|
||||||
|
*/
|
||||||
|
abstract class AbstractTerminalScreen<BE: AbstractTerminalBlockEntity, T: AbstractTerminalScreenHandler<BE>>(
|
||||||
|
handler: T,
|
||||||
|
playerInv: PlayerInventory,
|
||||||
|
title: Text,
|
||||||
|
val terminalBackgroundWidth: Int,
|
||||||
|
val terminalBackgroundHeight: Int,
|
||||||
|
): CacaoHandledScreen<T>(handler, playerInv, title) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val clickHandlers = LinkedList<AbstractTerminalScreen<*, *>.(Double, Double, Int) -> Boolean?>()
|
||||||
|
|
||||||
|
fun registerClickHandler(handler: AbstractTerminalScreen<*, *>.(Double, Double, Int) -> Boolean?) {
|
||||||
|
clickHandlers.add(handler)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract val backgroundTexture: Identifier
|
||||||
|
|
||||||
|
val terminalVC: AbstractTerminalViewController<*, *, *>
|
||||||
|
var amountVC: TerminalRequestAmountViewController? = null
|
||||||
|
|
||||||
|
var searchQuery = ""
|
||||||
|
var scrollPosition = 0.0
|
||||||
|
|
||||||
|
init {
|
||||||
|
backgroundWidth = terminalBackgroundWidth
|
||||||
|
backgroundHeight = terminalBackgroundHeight
|
||||||
|
|
||||||
|
terminalVC = createViewController()
|
||||||
|
addWindow(ScreenHandlerWindow(handler, terminalVC))
|
||||||
|
|
||||||
|
requestUpdatedItems()
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract fun createViewController(): AbstractTerminalViewController<*, *, *>
|
||||||
|
|
||||||
|
fun requestItem(stack: ItemStack, amount: Int) {
|
||||||
|
val netHandler = MinecraftClient.getInstance().player!!.networkHandler
|
||||||
|
val packet = C2STerminalRequestItem(handler.terminal, stack, amount)
|
||||||
|
netHandler.sendPacket(packet)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun requestUpdatedItems() {
|
||||||
|
val player = MinecraftClient.getInstance().player!!
|
||||||
|
player.networkHandler.sendPacket(C2STerminalUpdateDisplayedItems(handler.terminal, searchQuery, scrollPosition.toFloat()))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showRequestAmountDialog(stack: ItemStack) {
|
||||||
|
val vc = TerminalRequestAmountViewController(this, stack)
|
||||||
|
addWindow(Window(vc))
|
||||||
|
amountVC = vc
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExperimentalUnsignedTypes
|
||||||
|
fun drawSlotUnderlay(matrixStack: MatrixStack, slot: Slot) {
|
||||||
|
if (!handler.isBufferSlot(slot.id)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val mode = handler.terminal.internalBuffer.getMode(slot.id - handler.bufferSlotsStart)
|
||||||
|
val color: UInt = when (mode) {
|
||||||
|
TerminalBufferInventory.Mode.TO_NETWORK -> 0xFFFF0000u
|
||||||
|
TerminalBufferInventory.Mode.FROM_NETWORK -> 0xFF00FF00u
|
||||||
|
else -> return
|
||||||
|
}
|
||||||
|
DrawableHelper.fill(matrixStack, slot.x, slot.y, slot.x + 16, slot.y + 16, color.toInt())
|
||||||
|
}
|
||||||
|
|
||||||
|
private val DECIMAL_FORMAT = DecimalFormat("#.#").apply { roundingMode = RoundingMode.HALF_UP }
|
||||||
|
private val FORMAT = DecimalFormat("##").apply { roundingMode = RoundingMode.HALF_UP }
|
||||||
|
|
||||||
|
fun drawNetworkSlotAmount(stack: ItemStack, x: Int, y: Int, slot: Slot, matrixStack: MatrixStack) {
|
||||||
|
val amount = stack.count
|
||||||
|
val s = when {
|
||||||
|
amount < 1_000 -> amount.toString()
|
||||||
|
amount < 1_000_000 -> {
|
||||||
|
val format = if (amount < 10_000) DECIMAL_FORMAT else FORMAT
|
||||||
|
format.format(amount / 1_000.0) + "K"
|
||||||
|
}
|
||||||
|
amount < 1_000_000_000 -> {
|
||||||
|
val format = if (amount < 10_000_000) DECIMAL_FORMAT else FORMAT
|
||||||
|
format.format(amount / 1_000_000.0) + "M"
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
DECIMAL_FORMAT.format(amount / 1000000000.0).toString() + "B"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// draw damage bar
|
||||||
|
// empty string for label because vanilla renders the count behind the damage bar
|
||||||
|
itemRenderer.renderGuiItemOverlay(textRenderer, stack, x, y, "")
|
||||||
|
|
||||||
|
matrixStack.push()
|
||||||
|
matrixStack.translate(x.toDouble(), y.toDouble(), itemRenderer.zOffset + 200.0)
|
||||||
|
val scale = 2 / 3f
|
||||||
|
matrixStack.scale(scale, scale, 1.0f)
|
||||||
|
val immediate = VertexConsumerProvider.immediate(Tessellator.getInstance().buffer)
|
||||||
|
val textX = (1 / scale * 18) - textRenderer.getWidth(s).toFloat() - 3
|
||||||
|
val textY = (1 / scale * 18) - 11
|
||||||
|
textRenderer.draw(s, textX, textY, 0xffffff, true, matrixStack.peek().model, immediate, false, 0, 0xF000F0)
|
||||||
|
immediate.draw()
|
||||||
|
matrixStack.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun drawBackground(matrixStack: MatrixStack, delta: Float, mouseX: Int, mouseY: Int) {
|
||||||
|
super.drawBackground(matrixStack, delta, mouseX, mouseY)
|
||||||
|
|
||||||
|
drawBackgroundTexture(matrixStack)
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun drawBackgroundTexture(matrixStack: MatrixStack) {
|
||||||
|
RenderSystem.color4f(1f, 1f, 1f, 1f)
|
||||||
|
client!!.textureManager.bindTexture(backgroundTexture)
|
||||||
|
val x = (width - backgroundWidth) / 2
|
||||||
|
val y = (height - backgroundHeight) / 2
|
||||||
|
drawTexture(matrixStack, x, y, 0, 0, backgroundWidth, backgroundHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun tick() {
|
||||||
|
super.tick()
|
||||||
|
|
||||||
|
if (amountVC != null) {
|
||||||
|
amountVC!!.field.tick()
|
||||||
|
} else {
|
||||||
|
terminalVC.searchField.tick()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
|
||||||
|
for (handler in clickHandlers) {
|
||||||
|
val res = handler(mouseX, mouseY, button)
|
||||||
|
if (res != null) {
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.mouseClicked(mouseX, mouseY, button)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMouseClick(slot: Slot?, invSlot: Int, clickData: Int, type: SlotActionType?) {
|
||||||
|
super.onMouseClick(slot, invSlot, clickData, type)
|
||||||
|
|
||||||
|
if (slot != null && !slot.stack.isEmpty && handler.isNetworkSlot(slot.id)) {
|
||||||
|
val stack = slot.stack
|
||||||
|
|
||||||
|
if (type == SlotActionType.QUICK_MOVE) {
|
||||||
|
// shift click, request full stack
|
||||||
|
requestItem(stack, min(stack.count, stack.maxCount))
|
||||||
|
} else if (type == SlotActionType.PICKUP) {
|
||||||
|
if (clickData == 1) {
|
||||||
|
// right click, request half stack
|
||||||
|
requestItem(stack, ceil(min(stack.count, stack.maxCount) / 2f).toInt())
|
||||||
|
} else {
|
||||||
|
showRequestAmountDialog(stack)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val fakeFocusedElement = TextFieldWidget(textRenderer, 0, 0, 0, 0, LiteralText(""))
|
||||||
|
override fun getFocused(): Element? {
|
||||||
|
return if (windows.last().firstResponder != null) {
|
||||||
|
fakeFocusedElement
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,254 @@
|
||||||
|
package net.shadowfacts.phycon.block.terminal
|
||||||
|
|
||||||
|
import net.minecraft.screen.slot.Slot
|
||||||
|
import net.minecraft.screen.slot.SlotActionType
|
||||||
|
import net.minecraft.entity.player.PlayerEntity
|
||||||
|
import net.minecraft.entity.player.PlayerInventory
|
||||||
|
import net.minecraft.item.ItemStack
|
||||||
|
import net.minecraft.network.PacketByteBuf
|
||||||
|
import net.minecraft.screen.ScreenHandler
|
||||||
|
import net.minecraft.screen.ScreenHandlerType
|
||||||
|
import net.minecraft.server.network.ServerPlayerEntity
|
||||||
|
import net.minecraft.util.Identifier
|
||||||
|
import net.minecraft.util.registry.Registry
|
||||||
|
import net.shadowfacts.phycon.DefaultPlugin
|
||||||
|
import net.shadowfacts.phycon.PhysicalConnectivity
|
||||||
|
import net.shadowfacts.phycon.init.PhyBlocks
|
||||||
|
import net.shadowfacts.phycon.init.PhyScreens
|
||||||
|
import net.shadowfacts.phycon.networking.S2CTerminalUpdateDisplayedItems
|
||||||
|
import net.shadowfacts.phycon.util.SortMode
|
||||||
|
import net.shadowfacts.phycon.util.TerminalSettings
|
||||||
|
import net.shadowfacts.phycon.util.copyWithCount
|
||||||
|
import java.lang.ref.WeakReference
|
||||||
|
import kotlin.math.ceil
|
||||||
|
import kotlin.math.max
|
||||||
|
import kotlin.math.min
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author shadowfacts
|
||||||
|
*/
|
||||||
|
abstract class AbstractTerminalScreenHandler<T: AbstractTerminalBlockEntity>(
|
||||||
|
handlerType: ScreenHandlerType<*>,
|
||||||
|
syncId: Int,
|
||||||
|
val playerInv: PlayerInventory,
|
||||||
|
val terminal: T,
|
||||||
|
): ScreenHandler(handlerType, syncId),
|
||||||
|
AbstractTerminalBlockEntity.NetItemObserver {
|
||||||
|
|
||||||
|
private val rowsDisplayed = 6
|
||||||
|
|
||||||
|
private val fakeInv = FakeInventory(this)
|
||||||
|
private var searchQuery: String = ""
|
||||||
|
private var settings = TerminalSettings()
|
||||||
|
var totalEntries = 0
|
||||||
|
private set
|
||||||
|
var scrollPosition = 0f
|
||||||
|
private var itemEntries = listOf<Entry>()
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
if (terminal.world!!.isClient) {
|
||||||
|
itemsForDisplay = value.map {
|
||||||
|
it.stack.copyWithCount(it.amount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var itemsForDisplay = listOf<ItemStack>()
|
||||||
|
private set
|
||||||
|
|
||||||
|
open val xOffset: Int = 0
|
||||||
|
|
||||||
|
init {
|
||||||
|
if (!terminal.world!!.isClient) {
|
||||||
|
assert(terminal.netItemObserver?.get() === null)
|
||||||
|
terminal.netItemObserver = WeakReference(this)
|
||||||
|
// intentionally don't call netItemsChanged immediately, we need to wait for the client to send us its settings
|
||||||
|
}
|
||||||
|
|
||||||
|
val xOffset = xOffset
|
||||||
|
|
||||||
|
// network
|
||||||
|
for (y in 0 until 6) {
|
||||||
|
for (x in 0 until 9) {
|
||||||
|
addSlot(TerminalFakeSlot(fakeInv, y * 9 + x, xOffset + 66 + x * 18, 18 + y * 18))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// internal buffer
|
||||||
|
for (y in 0 until 6) {
|
||||||
|
for (x in 0 until 3) {
|
||||||
|
addSlot(Slot(terminal.internalBuffer, y * 3 + x, xOffset + 8 + x * 18, 18 + y * 18))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// player inv
|
||||||
|
for (y in 0 until 3) {
|
||||||
|
for (x in 0 until 9) {
|
||||||
|
addSlot(Slot(playerInv, x + y * 9 + 9, xOffset + 66 + x * 18, 140 + y * 18))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// hotbar
|
||||||
|
for (x in 0 until 9) {
|
||||||
|
addSlot(Slot(playerInv, x, xOffset + 66 + x * 18, 198))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun netItemsChanged() {
|
||||||
|
val player = playerInv.player
|
||||||
|
assert(player is ServerPlayerEntity)
|
||||||
|
|
||||||
|
val filtered = terminal.cachedNetItems.object2IntEntrySet().filter {
|
||||||
|
if (searchQuery.isBlank()) return@filter true
|
||||||
|
if (searchQuery.startsWith('@')) {
|
||||||
|
val unprefixed = searchQuery.drop(1)
|
||||||
|
val key = Registry.ITEM.getKey(it.key.item)
|
||||||
|
if (key.isPresent && key.get().value.namespace.startsWith(unprefixed, true)) {
|
||||||
|
return@filter true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
it.key.name.string.contains(searchQuery, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
totalEntries = filtered.size
|
||||||
|
|
||||||
|
val sorted =
|
||||||
|
when (settings[DefaultPlugin.SORT_MODE]) {
|
||||||
|
SortMode.COUNT_HIGH_FIRST -> filtered.sortedByDescending { it.intValue }
|
||||||
|
SortMode.COUNT_LOW_FIRST -> filtered.sortedBy { it.intValue }
|
||||||
|
SortMode.ALPHABETICAL -> filtered.sortedBy { it.key.name.string }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
val offsetInItems = currentScrollOffsetInItems()
|
||||||
|
val end = min(offsetInItems + rowsDisplayed * 9, sorted.size)
|
||||||
|
itemEntries = sorted.subList(offsetInItems, end).map { Entry(it.key, it.intValue) }
|
||||||
|
|
||||||
|
// itemEntries = sorted.map { Entry(it.key, it.intValue) }
|
||||||
|
|
||||||
|
(player as ServerPlayerEntity).networkHandler.sendPacket(S2CTerminalUpdateDisplayedItems(terminal, itemEntries, searchQuery, settings, scrollPosition, totalEntries))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun totalRows(): Int {
|
||||||
|
return ceil(totalEntries / 9f).toInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun maxScrollOffsetInRows(): Int {
|
||||||
|
return totalRows() - rowsDisplayed
|
||||||
|
}
|
||||||
|
|
||||||
|
fun currentScrollOffsetInRows(): Int {
|
||||||
|
return max(0, (scrollPosition * maxScrollOffsetInRows()).roundToInt())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun currentScrollOffsetInItems(): Int {
|
||||||
|
return currentScrollOffsetInRows() * 9
|
||||||
|
}
|
||||||
|
|
||||||
|
fun sendUpdatedItemsToClient(player: ServerPlayerEntity, query: String, settings: TerminalSettings, scrollPosition: Float) {
|
||||||
|
this.searchQuery = query
|
||||||
|
this.settings = settings
|
||||||
|
this.scrollPosition = scrollPosition
|
||||||
|
netItemsChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun receivedUpdatedItemsFromServer(entries: List<Entry>, query: String, scrollPosition: Float, totalEntries: Int) {
|
||||||
|
assert(playerInv.player.world.isClient)
|
||||||
|
|
||||||
|
this.searchQuery = query
|
||||||
|
this.scrollPosition = scrollPosition
|
||||||
|
this.totalEntries = totalEntries
|
||||||
|
itemEntries = entries
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun canUse(player: PlayerEntity): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun close(player: PlayerEntity) {
|
||||||
|
super.close(player)
|
||||||
|
|
||||||
|
terminal.netItemObserver = null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSlotClick(slotId: Int, clickData: Int, actionType: SlotActionType, player: PlayerEntity): ItemStack {
|
||||||
|
if (isBufferSlot(slotId)) {
|
||||||
|
// todo: why does this think it's quick_craft sometimes?
|
||||||
|
if ((actionType == SlotActionType.PICKUP || actionType == SlotActionType.QUICK_CRAFT) && !player.inventory.cursorStack.isEmpty) {
|
||||||
|
// placing cursor stack into buffer
|
||||||
|
val bufferSlot = slotId - bufferSlotsStart // subtract 54 to convert the handler slot ID to a valid buffer index
|
||||||
|
terminal.internalBuffer.markSlot(bufferSlot, TerminalBufferInventory.Mode.TO_NETWORK)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.onSlotClick(slotId, clickData, actionType, player)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun transferSlot(player: PlayerEntity, slotId: Int): ItemStack {
|
||||||
|
if (isNetworkSlot(slotId)) {
|
||||||
|
return ItemStack.EMPTY;
|
||||||
|
}
|
||||||
|
|
||||||
|
val slot = slots[slotId]
|
||||||
|
if (!slot.hasStack()) {
|
||||||
|
return ItemStack.EMPTY
|
||||||
|
}
|
||||||
|
|
||||||
|
val result = slot.stack.copy()
|
||||||
|
|
||||||
|
if (isBufferSlot(slotId)) {
|
||||||
|
// last boolean param is fromLast
|
||||||
|
if (!insertItem(slot.stack, playerSlotsStart, playerSlotsEnd, true)) {
|
||||||
|
return ItemStack.EMPTY
|
||||||
|
}
|
||||||
|
if (slot.stack.isEmpty) {
|
||||||
|
terminal.internalBuffer.markSlot(slotId - bufferSlotsStart, TerminalBufferInventory.Mode.UNASSIGNED)
|
||||||
|
}
|
||||||
|
} else if (isPlayerSlot(slotId)) {
|
||||||
|
val slotsInsertedInto = tryInsertItem(slot.stack, bufferSlotsStart until playerSlotsStart) { terminal.internalBuffer.getMode(it - bufferSlotsStart) != TerminalBufferInventory.Mode.FROM_NETWORK }
|
||||||
|
slotsInsertedInto.forEach { terminal.internalBuffer.markSlot(it - bufferSlotsStart, TerminalBufferInventory.Mode.TO_NETWORK) }
|
||||||
|
if (slot.stack.isEmpty) {
|
||||||
|
return ItemStack.EMPTY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun tryInsertItem(stack: ItemStack, slots: IntRange, slotPredicate: (Int) -> Boolean): Collection<Int> {
|
||||||
|
val slotsInsertedInto = mutableListOf<Int>()
|
||||||
|
for (index in slots) {
|
||||||
|
if (stack.isEmpty) break
|
||||||
|
if (!slotPredicate(index)) continue
|
||||||
|
|
||||||
|
val slot = this.slots[index]
|
||||||
|
val slotStack = slot.stack
|
||||||
|
if (slotStack.isEmpty) {
|
||||||
|
slot.stack = stack.copy()
|
||||||
|
stack.count = 0
|
||||||
|
|
||||||
|
slot.markDirty()
|
||||||
|
slotsInsertedInto.add(index)
|
||||||
|
} else if (canStacksCombine(slotStack, stack) && slotStack.count < slotStack.maxCount) {
|
||||||
|
val maxToMove = slotStack.maxCount - slotStack.count
|
||||||
|
val toMove = min(maxToMove, stack.count)
|
||||||
|
slotStack.increment(toMove)
|
||||||
|
stack.decrement(toMove)
|
||||||
|
|
||||||
|
slot.markDirty()
|
||||||
|
slotsInsertedInto.add(index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return slotsInsertedInto
|
||||||
|
}
|
||||||
|
|
||||||
|
val networkSlotsStart = 0
|
||||||
|
val networkSlotsEnd = 54
|
||||||
|
val bufferSlotsStart = 54
|
||||||
|
val bufferSlotsEnd = 72
|
||||||
|
val playerSlotsStart = 72
|
||||||
|
val playerSlotsEnd = 72 + 36
|
||||||
|
fun isNetworkSlot(id: Int) = id in 0 until bufferSlotsStart
|
||||||
|
fun isBufferSlot(id: Int) = id in bufferSlotsStart until playerSlotsStart
|
||||||
|
fun isPlayerSlot(id: Int) = id >= playerSlotsStart
|
||||||
|
|
||||||
|
data class Entry(val stack: ItemStack, val amount: Int)
|
||||||
|
}
|
|
@ -0,0 +1,181 @@
|
||||||
|
package net.shadowfacts.phycon.block.terminal
|
||||||
|
|
||||||
|
import net.minecraft.text.TranslatableText
|
||||||
|
import net.minecraft.util.math.MathHelper
|
||||||
|
import net.shadowfacts.cacao.geometry.Axis
|
||||||
|
import net.shadowfacts.cacao.geometry.Point
|
||||||
|
import net.shadowfacts.cacao.util.Color
|
||||||
|
import net.shadowfacts.cacao.util.LayoutGuide
|
||||||
|
import net.shadowfacts.cacao.util.MouseButton
|
||||||
|
import net.shadowfacts.cacao.view.Label
|
||||||
|
import net.shadowfacts.cacao.view.StackView
|
||||||
|
import net.shadowfacts.cacao.view.View
|
||||||
|
import net.shadowfacts.cacao.view.textfield.TextField
|
||||||
|
import net.shadowfacts.cacao.viewcontroller.ViewController
|
||||||
|
import net.shadowfacts.kiwidsl.dsl
|
||||||
|
import net.shadowfacts.phycon.client.screen.ScrollTrackView
|
||||||
|
import net.shadowfacts.phycon.util.TerminalSettings
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author shadowfacts
|
||||||
|
*/
|
||||||
|
abstract class AbstractTerminalViewController<BE: AbstractTerminalBlockEntity, S: AbstractTerminalScreen<BE, H>, H: AbstractTerminalScreenHandler<BE>>(
|
||||||
|
val screen: S,
|
||||||
|
val handler: H,
|
||||||
|
val terminal: BE = handler.terminal,
|
||||||
|
): ViewController() {
|
||||||
|
|
||||||
|
private lateinit var scrollTrack: ScrollTrackView
|
||||||
|
lateinit var settingsView: View
|
||||||
|
private set
|
||||||
|
lateinit var searchField: TextField
|
||||||
|
private set
|
||||||
|
|
||||||
|
lateinit var pane: LayoutGuide
|
||||||
|
private set
|
||||||
|
lateinit var buffer: LayoutGuide
|
||||||
|
private set
|
||||||
|
lateinit var network: LayoutGuide
|
||||||
|
private set
|
||||||
|
lateinit var playerInv: LayoutGuide
|
||||||
|
private set
|
||||||
|
|
||||||
|
lateinit var networkLabel: View
|
||||||
|
private set
|
||||||
|
lateinit var playerInvLabel: View
|
||||||
|
private set
|
||||||
|
lateinit var bufferLabel: View
|
||||||
|
private set
|
||||||
|
|
||||||
|
override fun loadView() {
|
||||||
|
view = ScrollHandlingView(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
pane = view.addLayoutGuide()
|
||||||
|
view.solver.dsl {
|
||||||
|
pane.centerXAnchor equalTo view.centerXAnchor
|
||||||
|
pane.centerYAnchor equalTo view.centerYAnchor
|
||||||
|
pane.widthAnchor equalTo screen.terminalBackgroundWidth
|
||||||
|
pane.heightAnchor equalTo screen.terminalBackgroundHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer = view.addLayoutGuide()
|
||||||
|
view.solver.dsl {
|
||||||
|
buffer.leftAnchor equalTo (pane.leftAnchor + 7 + handler.xOffset)
|
||||||
|
buffer.topAnchor equalTo (pane.topAnchor + 17)
|
||||||
|
buffer.widthAnchor equalTo (18 * 3)
|
||||||
|
buffer.heightAnchor equalTo (18 * 6)
|
||||||
|
}
|
||||||
|
|
||||||
|
network = view.addLayoutGuide()
|
||||||
|
view.solver.dsl {
|
||||||
|
network.leftAnchor equalTo (pane.leftAnchor + 65 + handler.xOffset)
|
||||||
|
network.topAnchor equalTo buffer.topAnchor
|
||||||
|
network.widthAnchor equalTo (18 * 9)
|
||||||
|
network.heightAnchor equalTo (18 * 6)
|
||||||
|
}
|
||||||
|
|
||||||
|
playerInv = view.addLayoutGuide()
|
||||||
|
view.solver.dsl {
|
||||||
|
playerInv.leftAnchor equalTo network.leftAnchor
|
||||||
|
playerInv.topAnchor equalTo (pane.topAnchor + 139)
|
||||||
|
playerInv.widthAnchor equalTo (18 * 9)
|
||||||
|
playerInv.heightAnchor equalTo 76
|
||||||
|
}
|
||||||
|
|
||||||
|
networkLabel = view.addSubview(Label(TranslatableText("gui.phycon.terminal_network"))).apply {
|
||||||
|
textColor = Color.TEXT
|
||||||
|
}
|
||||||
|
playerInvLabel = view.addSubview(Label(handler.playerInv.displayName)).apply {
|
||||||
|
textColor = Color.TEXT
|
||||||
|
}
|
||||||
|
bufferLabel = view.addSubview(Label(TranslatableText("gui.phycon.terminal_buffer"))).apply {
|
||||||
|
textColor = Color.TEXT
|
||||||
|
}
|
||||||
|
|
||||||
|
searchField = view.addSubview(TerminalSearchField()).apply {
|
||||||
|
handler = ::searchFieldChanged
|
||||||
|
drawBackground = false
|
||||||
|
}
|
||||||
|
searchField.becomeFirstResponder()
|
||||||
|
|
||||||
|
scrollTrack = view.addSubview(ScrollTrackView(::scrollPositionChanged))
|
||||||
|
|
||||||
|
val settingsStack = view.addSubview(StackView(Axis.VERTICAL, spacing = 2.0))
|
||||||
|
settingsView = settingsStack
|
||||||
|
TerminalSettings.allKeys.forEach { key ->
|
||||||
|
val button = SettingButton(key)
|
||||||
|
button.handler = { settingsChanged() }
|
||||||
|
settingsStack.addArrangedSubview(button)
|
||||||
|
}
|
||||||
|
|
||||||
|
view.solver.dsl {
|
||||||
|
networkLabel.leftAnchor equalTo network.leftAnchor
|
||||||
|
networkLabel.topAnchor equalTo (pane.topAnchor + 6)
|
||||||
|
|
||||||
|
bufferLabel.leftAnchor equalTo buffer.leftAnchor
|
||||||
|
bufferLabel.topAnchor equalTo networkLabel.topAnchor
|
||||||
|
|
||||||
|
playerInvLabel.leftAnchor equalTo playerInv.leftAnchor
|
||||||
|
playerInvLabel.topAnchor equalTo (pane.topAnchor + 128)
|
||||||
|
|
||||||
|
searchField.leftAnchor equalTo (pane.leftAnchor + 138 + handler.xOffset)
|
||||||
|
searchField.topAnchor equalTo (pane.topAnchor + 5)
|
||||||
|
searchField.widthAnchor equalTo 80
|
||||||
|
searchField.heightAnchor equalTo 9
|
||||||
|
|
||||||
|
scrollTrack.leftAnchor equalTo (pane.leftAnchor + 232 + handler.xOffset)
|
||||||
|
scrollTrack.topAnchor equalTo (network.topAnchor + 1)
|
||||||
|
scrollTrack.bottomAnchor equalTo (network.bottomAnchor - 1)
|
||||||
|
scrollTrack.widthAnchor equalTo 12
|
||||||
|
|
||||||
|
settingsStack.leftAnchor equalTo (pane.rightAnchor + 4)
|
||||||
|
settingsStack.topAnchor equalTo pane.topAnchor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun searchFieldChanged(field: TextField) {
|
||||||
|
screen.searchQuery = field.text
|
||||||
|
screen.requestUpdatedItems()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun scrollPositionChanged(track: ScrollTrackView) {
|
||||||
|
val oldOffset = handler.currentScrollOffsetInRows()
|
||||||
|
|
||||||
|
handler.scrollPosition = track.scrollPosition.toFloat()
|
||||||
|
screen.scrollPosition = track.scrollPosition
|
||||||
|
|
||||||
|
if (handler.currentScrollOffsetInRows() != oldOffset) {
|
||||||
|
screen.requestUpdatedItems()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun settingsChanged() {
|
||||||
|
screen.requestUpdatedItems()
|
||||||
|
}
|
||||||
|
|
||||||
|
class TerminalSearchField: TextField("") {
|
||||||
|
override fun mouseClickedOutside(point: Point, mouseButton: MouseButton) {
|
||||||
|
// no-op
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ScrollHandlingView(val vc: AbstractTerminalViewController<*, *, *>): View() {
|
||||||
|
override fun mouseScrolled(point: Point, amount: Double): Boolean {
|
||||||
|
var newOffsetInRows = vc.handler.currentScrollOffsetInRows() - amount.toInt()
|
||||||
|
newOffsetInRows = MathHelper.clamp(newOffsetInRows, 0, vc.handler.maxScrollOffsetInRows())
|
||||||
|
if (newOffsetInRows != vc.handler.currentScrollOffsetInRows()) {
|
||||||
|
val newScrollPosition = newOffsetInRows / vc.handler.maxScrollOffsetInRows().toDouble()
|
||||||
|
vc.screen.scrollPosition = newScrollPosition
|
||||||
|
vc.scrollTrack.scrollPosition = newScrollPosition
|
||||||
|
vc.screen.requestUpdatedItems()
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package net.shadowfacts.phycon.block.terminal
|
||||||
|
|
||||||
|
import net.minecraft.util.Identifier
|
||||||
|
import net.minecraft.world.BlockView
|
||||||
|
import net.shadowfacts.phycon.PhysicalConnectivity
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author shadowfacts
|
||||||
|
*/
|
||||||
|
class CraftingTerminalBlock: AbstractTerminalBlock<CraftingTerminalBlockEntity>() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val ID = Identifier(PhysicalConnectivity.MODID, "crafting_terminal")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createBlockEntity(world: BlockView) = CraftingTerminalBlockEntity()
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,141 @@
|
||||||
|
package net.shadowfacts.phycon.block.terminal
|
||||||
|
|
||||||
|
import alexiil.mc.lib.attributes.item.ItemStackCollections
|
||||||
|
import alexiil.mc.lib.attributes.item.ItemStackUtil
|
||||||
|
import it.unimi.dsi.fastutil.objects.Object2IntMap
|
||||||
|
import net.fabricmc.fabric.api.screenhandler.v1.ExtendedScreenHandlerFactory
|
||||||
|
import net.minecraft.entity.player.PlayerEntity
|
||||||
|
import net.minecraft.entity.player.PlayerInventory
|
||||||
|
import net.minecraft.inventory.CraftingInventory
|
||||||
|
import net.minecraft.inventory.SimpleInventory
|
||||||
|
import net.minecraft.item.ItemStack
|
||||||
|
import net.minecraft.nbt.CompoundTag
|
||||||
|
import net.minecraft.network.PacketByteBuf
|
||||||
|
import net.minecraft.screen.ScreenHandler
|
||||||
|
import net.minecraft.server.network.ServerPlayerEntity
|
||||||
|
import net.minecraft.text.Text
|
||||||
|
import net.minecraft.text.TranslatableText
|
||||||
|
import net.shadowfacts.phycon.init.PhyBlockEntities
|
||||||
|
import net.shadowfacts.phycon.packet.ItemStackPacket
|
||||||
|
import net.shadowfacts.phycon.packet.LocateStackPacket
|
||||||
|
import net.shadowfacts.phycon.packet.RequestInventoryPacket
|
||||||
|
import net.shadowfacts.phycon.util.fromTag
|
||||||
|
import net.shadowfacts.phycon.util.toTag
|
||||||
|
import java.util.LinkedList
|
||||||
|
import kotlin.math.min
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author shadowfacts
|
||||||
|
*/
|
||||||
|
class CraftingTerminalBlockEntity: AbstractTerminalBlockEntity(PhyBlockEntities.CRAFTING_TERMINAL) {
|
||||||
|
|
||||||
|
val craftingInv = SimpleInventory(9)
|
||||||
|
|
||||||
|
private val completedCraftingStackRequests = LinkedList<CraftingStackLocateRequest>()
|
||||||
|
|
||||||
|
override fun onActivate(player: PlayerEntity) {
|
||||||
|
if (!world!!.isClient) {
|
||||||
|
updateAndSync()
|
||||||
|
|
||||||
|
inventoryCache.clear()
|
||||||
|
sendPacket(RequestInventoryPacket(ipAddress))
|
||||||
|
val factory = object: ExtendedScreenHandlerFactory {
|
||||||
|
override fun createMenu(syncId: Int, playerInv: PlayerInventory, player: PlayerEntity): ScreenHandler? {
|
||||||
|
return CraftingTerminalScreenHandler(syncId, playerInv, this@CraftingTerminalBlockEntity)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getDisplayName() = TranslatableText("block.phycon.crafting_terminal")
|
||||||
|
|
||||||
|
override fun writeScreenOpeningData(player: ServerPlayerEntity, buf: PacketByteBuf) {
|
||||||
|
buf.writeBlockPos(this@CraftingTerminalBlockEntity.pos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
player.openHandledScreen(factory)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun requestItemsForCrafting(maxAmount: Int) {
|
||||||
|
val amounts = ItemStackCollections.map<IntArray>()
|
||||||
|
|
||||||
|
for (i in 0 until craftingInv.size()) {
|
||||||
|
val craftingInvStack = craftingInv.getStack(i)
|
||||||
|
if (craftingInvStack.isEmpty) continue
|
||||||
|
if (craftingInvStack.count >= craftingInvStack.maxCount) continue
|
||||||
|
|
||||||
|
if (craftingInvStack !in amounts) amounts[craftingInvStack] = IntArray(9) { 0 }
|
||||||
|
amounts[craftingInvStack]!![i] = min(maxAmount, craftingInvStack.maxCount - craftingInvStack.count)
|
||||||
|
}
|
||||||
|
|
||||||
|
for ((stack, amountPerSlot) in amounts) {
|
||||||
|
val total = amountPerSlot.sum()
|
||||||
|
val request = CraftingStackLocateRequest(stack, total, counter, amountPerSlot)
|
||||||
|
pendingRequests.add(request)
|
||||||
|
sendPacket(LocateStackPacket(stack, ipAddress))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun stackLocateRequestCompleted(request: StackLocateRequest) {
|
||||||
|
if (request is CraftingStackLocateRequest) {
|
||||||
|
completedCraftingStackRequests.add(request)
|
||||||
|
}
|
||||||
|
|
||||||
|
super.stackLocateRequestCompleted(request)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun doHandleItemStack(packet: ItemStackPacket): ItemStack {
|
||||||
|
val craftingReq = completedCraftingStackRequests.find { ItemStackUtil.areEqualIgnoreAmounts(it.stack, packet.stack) }
|
||||||
|
if (craftingReq != null) {
|
||||||
|
var remaining = packet.stack.copy()
|
||||||
|
|
||||||
|
for (i in 0 until craftingInv.size()) {
|
||||||
|
val currentStack = craftingInv.getStack(i)
|
||||||
|
if (currentStack.count >= currentStack.maxCount) continue
|
||||||
|
if (!ItemStackUtil.areEqualIgnoreAmounts(currentStack, remaining)) continue
|
||||||
|
|
||||||
|
val toInsert = minOf(remaining.count, currentStack.maxCount - currentStack.count, craftingReq.amountPerSlot[i])
|
||||||
|
currentStack.count += toInsert
|
||||||
|
remaining.count -= toInsert
|
||||||
|
craftingReq.amountPerSlot[i] -= toInsert
|
||||||
|
craftingReq.received += toInsert
|
||||||
|
|
||||||
|
if (remaining.isEmpty) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (craftingReq.amountPerSlot.sum() == 0 || craftingReq.received >= craftingReq.totalResultAmount) {
|
||||||
|
completedCraftingStackRequests.remove(craftingReq)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!remaining.isEmpty) {
|
||||||
|
remaining = internalBuffer.insert(remaining, TerminalBufferInventory.Mode.FROM_NETWORK)
|
||||||
|
}
|
||||||
|
|
||||||
|
updateAndSync()
|
||||||
|
|
||||||
|
return remaining
|
||||||
|
} else {
|
||||||
|
return super.doHandleItemStack(packet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toCommonTag(tag: CompoundTag) {
|
||||||
|
super.toCommonTag(tag)
|
||||||
|
tag.put("CraftingInv", craftingInv.toTag())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun fromCommonTag(tag: CompoundTag) {
|
||||||
|
super.fromCommonTag(tag)
|
||||||
|
craftingInv.fromTag(tag.getList("CraftingInv", 10))
|
||||||
|
}
|
||||||
|
|
||||||
|
class CraftingStackLocateRequest(
|
||||||
|
stack: ItemStack,
|
||||||
|
amount: Int,
|
||||||
|
timestamp: Long,
|
||||||
|
val amountPerSlot: IntArray,
|
||||||
|
): StackLocateRequest(stack, amount, timestamp) {
|
||||||
|
var received: Int = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
package net.shadowfacts.phycon.block.terminal
|
||||||
|
|
||||||
|
import com.mojang.blaze3d.systems.RenderSystem
|
||||||
|
import net.minecraft.client.util.math.MatrixStack
|
||||||
|
import net.minecraft.entity.player.PlayerInventory
|
||||||
|
import net.minecraft.text.Text
|
||||||
|
import net.minecraft.util.Identifier
|
||||||
|
import net.shadowfacts.phycon.PhysicalConnectivity
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author shadowfacts
|
||||||
|
*/
|
||||||
|
class CraftingTerminalScreen(
|
||||||
|
handler: CraftingTerminalScreenHandler,
|
||||||
|
playerInv: PlayerInventory,
|
||||||
|
title: Text,
|
||||||
|
): AbstractTerminalScreen<CraftingTerminalBlockEntity, CraftingTerminalScreenHandler>(
|
||||||
|
handler,
|
||||||
|
playerInv,
|
||||||
|
title,
|
||||||
|
259,
|
||||||
|
252,
|
||||||
|
) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val BACKGROUND_1 = Identifier(PhysicalConnectivity.MODID, "textures/gui/crafting_terminal_1.png")
|
||||||
|
private val BACKGROUND_2 = Identifier(PhysicalConnectivity.MODID, "textures/gui/crafting_terminal_2.png")
|
||||||
|
}
|
||||||
|
|
||||||
|
override val backgroundTexture = BACKGROUND_1
|
||||||
|
|
||||||
|
override fun createViewController(): AbstractTerminalViewController<*, *, *> {
|
||||||
|
return CraftingTerminalViewController(this, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun drawBackgroundTexture(matrixStack: MatrixStack) {
|
||||||
|
RenderSystem.color4f(1f, 1f, 1f, 1f)
|
||||||
|
client!!.textureManager.bindTexture(BACKGROUND_1)
|
||||||
|
val x = (width - backgroundWidth) / 2
|
||||||
|
val y = (height - backgroundHeight) / 2
|
||||||
|
drawTexture(matrixStack, x, y, 0, 0, 256, 252)
|
||||||
|
|
||||||
|
client!!.textureManager.bindTexture(BACKGROUND_2)
|
||||||
|
drawTexture(matrixStack, x + 256, y, 0, 0, 3, 252)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,130 @@
|
||||||
|
package net.shadowfacts.phycon.block.terminal
|
||||||
|
|
||||||
|
import alexiil.mc.lib.attributes.item.ItemStackCollections
|
||||||
|
import net.minecraft.entity.player.PlayerInventory
|
||||||
|
import net.minecraft.inventory.CraftingInventory
|
||||||
|
import net.minecraft.inventory.CraftingResultInventory
|
||||||
|
import net.minecraft.inventory.Inventory
|
||||||
|
import net.minecraft.item.ItemStack
|
||||||
|
import net.minecraft.network.PacketByteBuf
|
||||||
|
import net.minecraft.network.packet.s2c.play.ScreenHandlerSlotUpdateS2CPacket
|
||||||
|
import net.minecraft.recipe.RecipeFinder
|
||||||
|
import net.minecraft.recipe.RecipeType
|
||||||
|
import net.minecraft.screen.slot.CraftingResultSlot
|
||||||
|
import net.minecraft.screen.slot.Slot
|
||||||
|
import net.minecraft.server.network.ServerPlayerEntity
|
||||||
|
import net.shadowfacts.phycon.init.PhyBlocks
|
||||||
|
import net.shadowfacts.phycon.init.PhyScreens
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author shadowfacts
|
||||||
|
*/
|
||||||
|
class CraftingTerminalScreenHandler(
|
||||||
|
syncId: Int,
|
||||||
|
playerInv: PlayerInventory,
|
||||||
|
terminal: CraftingTerminalBlockEntity,
|
||||||
|
): AbstractTerminalScreenHandler<CraftingTerminalBlockEntity>(PhyScreens.CRAFTING_TERMINAL, syncId, playerInv, terminal) {
|
||||||
|
|
||||||
|
val craftingInv = CraftingInv(this)
|
||||||
|
val result = CraftingResultInventory()
|
||||||
|
val resultSlot: CraftingResultSlot
|
||||||
|
|
||||||
|
override val xOffset: Int
|
||||||
|
get() = 5
|
||||||
|
|
||||||
|
constructor(syncId: Int, playerInv: PlayerInventory, buf: PacketByteBuf):
|
||||||
|
this(
|
||||||
|
syncId,
|
||||||
|
playerInv,
|
||||||
|
PhyBlocks.CRAFTING_TERMINAL.getBlockEntity(playerInv.player.world, buf.readBlockPos())!!
|
||||||
|
)
|
||||||
|
|
||||||
|
init {
|
||||||
|
for (y in 0 until 3) {
|
||||||
|
for (x in 0 until 3) {
|
||||||
|
this.addSlot(Slot(craftingInv, x + y * 3, 13 + x * 18, 140 + y * 18))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resultSlot = CraftingResultSlot(playerInv.player, craftingInv, result, 0, 31, 224)
|
||||||
|
addSlot(resultSlot)
|
||||||
|
|
||||||
|
updateCraftingResult()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onContentChanged(inventory: Inventory?) {
|
||||||
|
updateCraftingResult()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateCraftingResult() {
|
||||||
|
val world = playerInv.player.world
|
||||||
|
if (!world.isClient) {
|
||||||
|
val player = playerInv.player as ServerPlayerEntity
|
||||||
|
val recipe = world.server!!.recipeManager.getFirstMatch(RecipeType.CRAFTING, craftingInv, world)
|
||||||
|
val resultStack =
|
||||||
|
if (recipe.isPresent && result.shouldCraftRecipe(world, player, recipe.get())) {
|
||||||
|
recipe.get().craft(craftingInv)
|
||||||
|
} else {
|
||||||
|
ItemStack.EMPTY
|
||||||
|
}
|
||||||
|
result.setStack(0, resultStack)
|
||||||
|
player.networkHandler.sendPacket(ScreenHandlerSlotUpdateS2CPacket(syncId, resultSlot.id, resultStack))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearCraftingGrid() {
|
||||||
|
assert(!playerInv.player.world.isClient)
|
||||||
|
for (i in 0 until terminal.craftingInv.size()) {
|
||||||
|
val craftingInvStack = terminal.craftingInv.getStack(i)
|
||||||
|
if (craftingInvStack.isEmpty) continue
|
||||||
|
val remainder = terminal.internalBuffer.insert(craftingInvStack, TerminalBufferInventory.Mode.TO_NETWORK)
|
||||||
|
terminal.craftingInv.setStack(i, remainder)
|
||||||
|
}
|
||||||
|
updateCraftingResult()
|
||||||
|
sendContentUpdates()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun requestMoreCraftingIngredients(maxAmount: Int) {
|
||||||
|
assert(!playerInv.player.world.isClient)
|
||||||
|
terminal.requestItemsForCrafting(maxAmount)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RecipeType.CRAFTING wants a CraftingInventory, but we can't store a CraftingInventory on the BE without a screen handler, so...
|
||||||
|
class CraftingInv(val handler: CraftingTerminalScreenHandler): CraftingInventory(handler, 3, 3) {
|
||||||
|
private val backing = handler.terminal.craftingInv
|
||||||
|
|
||||||
|
override fun isEmpty(): Boolean {
|
||||||
|
return backing.isEmpty
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getStack(i: Int): ItemStack {
|
||||||
|
return backing.getStack(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun removeStack(i: Int): ItemStack {
|
||||||
|
return backing.removeStack(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun removeStack(i: Int, j: Int): ItemStack {
|
||||||
|
val res = backing.removeStack(i, j)
|
||||||
|
if (!res.isEmpty) {
|
||||||
|
handler.onContentChanged(this)
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setStack(i: Int, itemStack: ItemStack?) {
|
||||||
|
backing.setStack(i, itemStack)
|
||||||
|
handler.onContentChanged(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun clear() {
|
||||||
|
backing.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun provideRecipeInputs(finder: RecipeFinder) {
|
||||||
|
TODO()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,100 @@
|
||||||
|
package net.shadowfacts.phycon.block.terminal
|
||||||
|
|
||||||
|
import net.minecraft.client.MinecraftClient
|
||||||
|
import net.minecraft.client.util.InputUtil
|
||||||
|
import net.minecraft.text.TranslatableText
|
||||||
|
import net.minecraft.util.Identifier
|
||||||
|
import net.shadowfacts.cacao.geometry.Size
|
||||||
|
import net.shadowfacts.cacao.util.Color
|
||||||
|
import net.shadowfacts.cacao.util.LayoutGuide
|
||||||
|
import net.shadowfacts.cacao.util.texture.Texture
|
||||||
|
import net.shadowfacts.cacao.view.Label
|
||||||
|
import net.shadowfacts.cacao.view.TextureView
|
||||||
|
import net.shadowfacts.cacao.view.button.Button
|
||||||
|
import net.shadowfacts.kiwidsl.dsl
|
||||||
|
import net.shadowfacts.phycon.PhysicalConnectivity
|
||||||
|
import net.shadowfacts.phycon.networking.C2STerminalCraftingButton
|
||||||
|
import org.lwjgl.glfw.GLFW
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author shadowfacts
|
||||||
|
*/
|
||||||
|
class CraftingTerminalViewController(
|
||||||
|
screen: CraftingTerminalScreen,
|
||||||
|
handler: CraftingTerminalScreenHandler,
|
||||||
|
): AbstractTerminalViewController<CraftingTerminalBlockEntity, CraftingTerminalScreen, CraftingTerminalScreenHandler>(
|
||||||
|
screen,
|
||||||
|
handler,
|
||||||
|
) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val SMALL_BUTTON = Texture(Identifier(PhysicalConnectivity.MODID, "textures/gui/icons.png"), 0, 48)
|
||||||
|
val SMALL_BUTTON_HOVERED = Texture(Identifier(PhysicalConnectivity.MODID, "textures/gui/icons.png"), 16, 48)
|
||||||
|
val CLEAR_ICON = Texture(Identifier(PhysicalConnectivity.MODID, "textures/gui/icons.png"), 32, 48)
|
||||||
|
val PLUS_ICON = Texture(Identifier(PhysicalConnectivity.MODID, "textures/gui/icons.png"), 48, 48)
|
||||||
|
}
|
||||||
|
|
||||||
|
lateinit var craftingInv: LayoutGuide
|
||||||
|
|
||||||
|
override fun viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
craftingInv = view.addLayoutGuide()
|
||||||
|
view.solver.dsl {
|
||||||
|
craftingInv.leftAnchor equalTo buffer.leftAnchor
|
||||||
|
craftingInv.topAnchor equalTo playerInv.topAnchor
|
||||||
|
craftingInv.widthAnchor equalTo buffer.widthAnchor
|
||||||
|
craftingInv.heightAnchor equalTo 54
|
||||||
|
}
|
||||||
|
|
||||||
|
val craftingLabel = view.addSubview(Label(TranslatableText("gui.phycon.terminal_crafting"))).apply {
|
||||||
|
textColor = Color.TEXT
|
||||||
|
}
|
||||||
|
view.solver.dsl {
|
||||||
|
craftingLabel.leftAnchor equalTo craftingInv.leftAnchor
|
||||||
|
craftingLabel.topAnchor equalTo playerInvLabel.topAnchor
|
||||||
|
}
|
||||||
|
|
||||||
|
val clearIcon = TextureView(CLEAR_ICON).apply {
|
||||||
|
intrinsicContentSize = Size(3.0,3.0)
|
||||||
|
}
|
||||||
|
val clearButton = view.addSubview(Button(clearIcon, padding = 2.0, handler = ::clearPressed)).apply {
|
||||||
|
background = TextureView(SMALL_BUTTON)
|
||||||
|
hoveredBackground = TextureView(SMALL_BUTTON_HOVERED)
|
||||||
|
tooltip = TranslatableText("gui.phycon.terminal.clear_crafting")
|
||||||
|
}
|
||||||
|
view.solver.dsl {
|
||||||
|
clearButton.topAnchor equalTo craftingInv.topAnchor
|
||||||
|
clearButton.leftAnchor equalTo (pane.leftAnchor + 4)
|
||||||
|
}
|
||||||
|
|
||||||
|
val plusIcon = TextureView(PLUS_ICON).apply {
|
||||||
|
intrinsicContentSize = Size(3.0, 3.0)
|
||||||
|
}
|
||||||
|
val plusButton = view.addSubview(Button(plusIcon, padding = 2.0, handler = ::plusPressed)).apply {
|
||||||
|
background= TextureView(SMALL_BUTTON)
|
||||||
|
hoveredBackground = TextureView(SMALL_BUTTON_HOVERED)
|
||||||
|
tooltip = TranslatableText("gui.phycon.terminal.more_crafting")
|
||||||
|
}
|
||||||
|
view.solver.dsl {
|
||||||
|
plusButton.topAnchor equalTo (clearButton.bottomAnchor + 2)
|
||||||
|
plusButton.leftAnchor equalTo clearButton.leftAnchor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun clearPressed(button: Button) {
|
||||||
|
MinecraftClient.getInstance().player!!.networkHandler.sendPacket(C2STerminalCraftingButton(terminal, C2STerminalCraftingButton.Action.CLEAR_GRID))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun plusPressed(button: Button) {
|
||||||
|
val client = MinecraftClient.getInstance()
|
||||||
|
val action =
|
||||||
|
if (InputUtil.isKeyPressed(client.window.handle, GLFW.GLFW_KEY_LEFT_SHIFT) || InputUtil.isKeyPressed(client.window.handle, GLFW.GLFW_KEY_RIGHT_SHIFT)) {
|
||||||
|
C2STerminalCraftingButton.Action.REQUEST_MAX_MORE
|
||||||
|
} else {
|
||||||
|
C2STerminalCraftingButton.Action.REQUEST_ONE_MORE
|
||||||
|
}
|
||||||
|
client.player!!.networkHandler.sendPacket(C2STerminalCraftingButton(terminal, action))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,6 +1,5 @@
|
||||||
package net.shadowfacts.phycon.block.terminal
|
package net.shadowfacts.phycon.block.terminal
|
||||||
|
|
||||||
import net.minecraft.client.util.math.MatrixStack
|
|
||||||
import net.shadowfacts.cacao.geometry.Point
|
import net.shadowfacts.cacao.geometry.Point
|
||||||
import net.shadowfacts.cacao.geometry.Size
|
import net.shadowfacts.cacao.geometry.Size
|
||||||
import net.shadowfacts.cacao.util.EnumHelper
|
import net.shadowfacts.cacao.util.EnumHelper
|
||||||
|
@ -31,14 +30,15 @@ class SettingButton<E>(
|
||||||
get() = content as TextureView
|
get() = content as TextureView
|
||||||
|
|
||||||
init {
|
init {
|
||||||
updateTexture()
|
update()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateTexture() {
|
private fun update() {
|
||||||
textureView.texture = textureCache.getOrPut(key.value) {
|
textureView.texture = textureCache.getOrPut(key.value) {
|
||||||
val uv = key.value.uv
|
val uv = key.value.uv
|
||||||
Texture(key.value.iconTexture, uv[0], uv[1])
|
Texture(key.value.iconTexture, uv[0], uv[1])
|
||||||
}
|
}
|
||||||
|
tooltip = key.value.tooltip
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun mouseClicked(point: Point, mouseButton: MouseButton): Boolean {
|
override fun mouseClicked(point: Point, mouseButton: MouseButton): Boolean {
|
||||||
|
@ -53,20 +53,10 @@ class SettingButton<E>(
|
||||||
|
|
||||||
PhysicalConnectivityClient.terminalSettings[key] = newValue
|
PhysicalConnectivityClient.terminalSettings[key] = newValue
|
||||||
|
|
||||||
updateTexture()
|
update()
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.mouseClicked(point, mouseButton)
|
return super.mouseClicked(point, mouseButton)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun draw(matrixStack: MatrixStack, mouse: Point, delta: Float) {
|
|
||||||
super.draw(matrixStack, mouse, delta)
|
|
||||||
|
|
||||||
if (mouse in bounds) {
|
|
||||||
key.value.tooltip?.also {
|
|
||||||
window!!.drawTooltip(listOf(it))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,80 +1,18 @@
|
||||||
package net.shadowfacts.phycon.block.terminal
|
package net.shadowfacts.phycon.block.terminal
|
||||||
|
|
||||||
import alexiil.mc.lib.attributes.AttributeList
|
|
||||||
import alexiil.mc.lib.attributes.AttributeProvider
|
|
||||||
import net.minecraft.block.Block
|
|
||||||
import net.minecraft.block.BlockState
|
|
||||||
import net.minecraft.block.Material
|
|
||||||
import net.minecraft.entity.player.PlayerEntity
|
|
||||||
import net.minecraft.item.ItemPlacementContext
|
|
||||||
import net.minecraft.item.ItemStack
|
|
||||||
import net.minecraft.server.world.ServerWorld
|
|
||||||
import net.minecraft.sound.BlockSoundGroup
|
|
||||||
import net.minecraft.state.StateManager
|
|
||||||
import net.minecraft.state.property.Properties
|
|
||||||
import net.minecraft.util.ActionResult
|
|
||||||
import net.minecraft.util.Hand
|
|
||||||
import net.minecraft.util.Identifier
|
import net.minecraft.util.Identifier
|
||||||
import net.minecraft.util.ItemScatterer
|
|
||||||
import net.minecraft.util.hit.BlockHitResult
|
|
||||||
import net.minecraft.util.math.BlockPos
|
|
||||||
import net.minecraft.util.math.Direction
|
|
||||||
import net.minecraft.world.BlockView
|
import net.minecraft.world.BlockView
|
||||||
import net.minecraft.world.World
|
|
||||||
import net.minecraft.world.WorldAccess
|
|
||||||
import net.shadowfacts.phycon.PhysicalConnectivity
|
import net.shadowfacts.phycon.PhysicalConnectivity
|
||||||
import net.shadowfacts.phycon.api.NetworkComponentBlock
|
|
||||||
import net.shadowfacts.phycon.block.DeviceBlock
|
|
||||||
import java.util.EnumSet
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
class TerminalBlock: DeviceBlock<TerminalBlockEntity>(
|
class TerminalBlock: AbstractTerminalBlock<TerminalBlockEntity>() {
|
||||||
Settings.of(Material.METAL)
|
|
||||||
.strength(1.5f)
|
|
||||||
.sounds(BlockSoundGroup.METAL)
|
|
||||||
),
|
|
||||||
NetworkComponentBlock,
|
|
||||||
AttributeProvider {
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val ID = Identifier(PhysicalConnectivity.MODID, "terminal")
|
val ID = Identifier(PhysicalConnectivity.MODID, "terminal")
|
||||||
val FACING = Properties.FACING
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getNetworkConnectedSides(state: BlockState, world: WorldAccess, pos: BlockPos): Collection<Direction> {
|
|
||||||
val set = EnumSet.allOf(Direction::class.java)
|
|
||||||
set.remove(state[FACING])
|
|
||||||
return set
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun appendProperties(builder: StateManager.Builder<Block, BlockState>) {
|
|
||||||
super.appendProperties(builder)
|
|
||||||
builder.add(FACING)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getPlacementState(context: ItemPlacementContext): BlockState {
|
|
||||||
return defaultState.with(FACING, context.playerLookDirection.opposite)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun createBlockEntity(world: BlockView) = TerminalBlockEntity()
|
override fun createBlockEntity(world: BlockView) = TerminalBlockEntity()
|
||||||
|
|
||||||
override fun onUse(state: BlockState, world: World, pos: BlockPos, player: PlayerEntity, hand: Hand, hitResult: BlockHitResult): ActionResult {
|
|
||||||
getBlockEntity(world, pos)!!.onActivate(player)
|
|
||||||
return ActionResult.SUCCESS
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onStateReplaced(state: BlockState, world: World, pos: BlockPos, newState: BlockState, moved: Boolean) {
|
|
||||||
if (!state.isOf(newState.block)) {
|
|
||||||
val be = getBlockEntity(world, pos)!!
|
|
||||||
ItemScatterer.spawn(world, pos, be.internalBuffer)
|
|
||||||
|
|
||||||
super.onStateReplaced(state, world, pos, newState, moved)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun addAllAttributes(world: World, pos: BlockPos, state: BlockState, to: AttributeList<*>) {
|
|
||||||
to.offer(getBlockEntity(world, pos))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,175 +1,21 @@
|
||||||
package net.shadowfacts.phycon.block.terminal
|
package net.shadowfacts.phycon.block.terminal
|
||||||
|
|
||||||
import alexiil.mc.lib.attributes.item.GroupedItemInvView
|
|
||||||
import alexiil.mc.lib.attributes.item.ItemStackCollections
|
|
||||||
import alexiil.mc.lib.attributes.item.ItemStackUtil
|
|
||||||
import net.fabricmc.fabric.api.block.entity.BlockEntityClientSerializable
|
|
||||||
import net.fabricmc.fabric.api.screenhandler.v1.ExtendedScreenHandlerFactory
|
import net.fabricmc.fabric.api.screenhandler.v1.ExtendedScreenHandlerFactory
|
||||||
import net.minecraft.entity.player.PlayerEntity
|
import net.minecraft.entity.player.PlayerEntity
|
||||||
import net.minecraft.entity.player.PlayerInventory
|
import net.minecraft.entity.player.PlayerInventory
|
||||||
import net.minecraft.inventory.Inventory
|
|
||||||
import net.minecraft.inventory.InventoryChangedListener
|
|
||||||
import net.minecraft.item.ItemStack
|
|
||||||
import net.minecraft.nbt.CompoundTag
|
|
||||||
import net.minecraft.network.PacketByteBuf
|
import net.minecraft.network.PacketByteBuf
|
||||||
import net.minecraft.screen.ScreenHandler
|
import net.minecraft.screen.ScreenHandler
|
||||||
import net.minecraft.server.network.ServerPlayerEntity
|
import net.minecraft.server.network.ServerPlayerEntity
|
||||||
import net.minecraft.text.TranslatableText
|
import net.minecraft.text.TranslatableText
|
||||||
import net.minecraft.util.Tickable
|
|
||||||
import net.minecraft.util.math.Direction
|
|
||||||
import net.shadowfacts.phycon.api.Interface
|
|
||||||
import net.shadowfacts.phycon.api.packet.Packet
|
|
||||||
import net.shadowfacts.phycon.api.util.IPAddress
|
|
||||||
import net.shadowfacts.phycon.init.PhyBlockEntities
|
import net.shadowfacts.phycon.init.PhyBlockEntities
|
||||||
import net.shadowfacts.phycon.block.DeviceBlockEntity
|
import net.shadowfacts.phycon.packet.RequestInventoryPacket
|
||||||
import net.shadowfacts.phycon.util.NetworkUtil
|
|
||||||
import net.shadowfacts.phycon.component.*
|
|
||||||
import net.shadowfacts.phycon.packet.*
|
|
||||||
import java.lang.ref.WeakReference
|
|
||||||
import java.util.*
|
|
||||||
import kotlin.math.min
|
|
||||||
import kotlin.properties.Delegates
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL),
|
class TerminalBlockEntity: AbstractTerminalBlockEntity(PhyBlockEntities.TERMINAL) {
|
||||||
InventoryChangedListener,
|
|
||||||
BlockEntityClientSerializable,
|
|
||||||
Tickable,
|
|
||||||
ItemStackPacketHandler,
|
|
||||||
NetworkStackDispatcher<TerminalBlockEntity.PendingInsertion> {
|
|
||||||
|
|
||||||
companion object {
|
override fun onActivate(player: PlayerEntity) {
|
||||||
// the locate/insertion timeouts are only 1 tick because that's long enough to hear from every device on the network
|
|
||||||
// in a degraded state (when there's latency in the network), not handling interface priorities correctly is acceptable
|
|
||||||
val LOCATE_REQUEST_TIMEOUT: Long = 1 // ticks
|
|
||||||
val INSERTION_TIMEOUT: Long = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
private val inventoryCache = mutableMapOf<IPAddress, GroupedItemInvView>()
|
|
||||||
val internalBuffer = TerminalBufferInventory(18)
|
|
||||||
|
|
||||||
private val pendingRequests = LinkedList<StackLocateRequest>()
|
|
||||||
override val pendingInsertions = mutableListOf<PendingInsertion>()
|
|
||||||
override val dispatchStackTimeout = INSERTION_TIMEOUT
|
|
||||||
|
|
||||||
private var observers = 0
|
|
||||||
val cachedNetItems = ItemStackCollections.intMap()
|
|
||||||
|
|
||||||
// todo: multiple players could have the terminal open simultaneously
|
|
||||||
var netItemObserver: WeakReference<NetItemObserver>? = null
|
|
||||||
|
|
||||||
init {
|
|
||||||
internalBuffer.addListener(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun findDestination(): Interface? {
|
|
||||||
for (dir in Direction.values()) {
|
|
||||||
val itf = NetworkUtil.findConnectedInterface(world!!, pos, dir)
|
|
||||||
if (itf != null) {
|
|
||||||
return itf
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun handle(packet: Packet) {
|
|
||||||
when (packet) {
|
|
||||||
is ReadInventoryPacket -> handleReadInventory(packet)
|
|
||||||
is DeviceRemovedPacket -> handleDeviceRemoved(packet)
|
|
||||||
is StackLocationPacket -> handleStackLocation(packet)
|
|
||||||
is ItemStackPacket -> handleItemStack(packet)
|
|
||||||
is CapacityPacket -> handleCapacity(packet)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleReadInventory(packet: ReadInventoryPacket) {
|
|
||||||
inventoryCache[packet.source] = packet.inventory
|
|
||||||
updateAndSync()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleDeviceRemoved(packet: DeviceRemovedPacket) {
|
|
||||||
inventoryCache.remove(packet.source)
|
|
||||||
updateAndSync()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleStackLocation(packet: StackLocationPacket) {
|
|
||||||
val request = pendingRequests.firstOrNull {
|
|
||||||
ItemStackUtil.areEqualIgnoreAmounts(it.stack, packet.stack)
|
|
||||||
}
|
|
||||||
if (request != null) {
|
|
||||||
request.results.add(packet.amount to packet.stackProvider)
|
|
||||||
if (request.isFinishable(counter)) {
|
|
||||||
stackLocateRequestCompleted(request)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun doHandleItemStack(packet: ItemStackPacket): ItemStack {
|
|
||||||
val remaining = internalBuffer.insertFromNetwork(packet.stack)
|
|
||||||
|
|
||||||
// this happens outside the normal update loop because by receiving the item stack packet
|
|
||||||
// we "know" how much the count in the source inventory has changed
|
|
||||||
updateAndSync()
|
|
||||||
|
|
||||||
return remaining
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updateAndSync() {
|
|
||||||
updateNetItems()
|
|
||||||
// syncs the internal buffer to the client
|
|
||||||
sync()
|
|
||||||
// syncs the open container (if any) to the client
|
|
||||||
netItemObserver?.get()?.netItemsChanged()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updateNetItems() {
|
|
||||||
cachedNetItems.clear()
|
|
||||||
for (inventory in inventoryCache.values) {
|
|
||||||
for (stack in inventory.storedStacks) {
|
|
||||||
val amount = inventory.getAmount(stack)
|
|
||||||
cachedNetItems.mergeInt(stack, amount) { a, b -> a + b }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun beginInsertions() {
|
|
||||||
if (world!!.isClient) return
|
|
||||||
|
|
||||||
for (slot in 0 until internalBuffer.size()) {
|
|
||||||
if (internalBuffer.getMode(slot) != TerminalBufferInventory.Mode.TO_NETWORK) continue
|
|
||||||
if (pendingInsertions.any { it.bufferSlot == slot }) continue
|
|
||||||
val stack = internalBuffer.getStack(slot)
|
|
||||||
dispatchItemStack(stack) { insertion ->
|
|
||||||
insertion.bufferSlot = slot
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun finishPendingRequests() {
|
|
||||||
if (world!!.isClient) return
|
|
||||||
if (pendingRequests.isEmpty()) return
|
|
||||||
|
|
||||||
val finishable = pendingRequests.filter { it.isFinishable(counter) }
|
|
||||||
// stackLocateRequestCompleted removes the object from pendingRequests
|
|
||||||
finishable.forEach(::stackLocateRequestCompleted)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun tick() {
|
|
||||||
super.tick()
|
|
||||||
|
|
||||||
if (!world!!.isClient) {
|
|
||||||
finishPendingRequests()
|
|
||||||
finishTimedOutPendingInsertions()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (counter % 20 == 0L && !world!!.isClient) {
|
|
||||||
beginInsertions()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onActivate(player: PlayerEntity) {
|
|
||||||
if (!world!!.isClient) {
|
if (!world!!.isClient) {
|
||||||
updateAndSync()
|
updateAndSync()
|
||||||
|
|
||||||
|
@ -190,85 +36,4 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun requestItem(stack: ItemStack, amount: Int = stack.count) {
|
|
||||||
val request = StackLocateRequest(stack, amount, counter)
|
|
||||||
pendingRequests.add(request)
|
|
||||||
// locate packets are sent immediately instead of being added to a queue
|
|
||||||
// otherwise the terminal UI feels sluggish and unresponsive
|
|
||||||
sendPacket(LocateStackPacket(stack, ipAddress))
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun stackLocateRequestCompleted(request: StackLocateRequest) {
|
|
||||||
pendingRequests.remove(request)
|
|
||||||
|
|
||||||
val sortedResults = request.results.toMutableList()
|
|
||||||
sortedResults.sortWith { a, b ->
|
|
||||||
// sort results first by provider priority, and then by the count that it can provide
|
|
||||||
if (a.second.providerPriority == b.second.providerPriority) {
|
|
||||||
b.first - a.first
|
|
||||||
} else {
|
|
||||||
b.second.providerPriority - a.second.providerPriority
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var amountRequested = 0
|
|
||||||
while (amountRequested < request.amount && sortedResults.isNotEmpty()) {
|
|
||||||
val (sourceAmount, sourceInterface) = sortedResults.removeAt(0)
|
|
||||||
val amountToRequest = min(sourceAmount, request.amount - amountRequested)
|
|
||||||
amountRequested += amountToRequest
|
|
||||||
sendPacket(ExtractStackPacket(request.stack, amountToRequest, ipAddress, sourceInterface.ipAddress))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun createPendingInsertion(stack: ItemStack) = PendingInsertion(stack, counter)
|
|
||||||
|
|
||||||
override fun finishInsertion(insertion: PendingInsertion): ItemStack {
|
|
||||||
val remaining = super.finishInsertion(insertion)
|
|
||||||
internalBuffer.setStack(insertion.bufferSlot, remaining)
|
|
||||||
|
|
||||||
// as with extracting, we "know" the new amounts and so can update instantly without actually sending out packets
|
|
||||||
updateAndSync()
|
|
||||||
|
|
||||||
return remaining
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onInventoryChanged(inv: Inventory) {
|
|
||||||
if (inv == internalBuffer && world != null && !world!!.isClient) {
|
|
||||||
markDirty()
|
|
||||||
sync()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toCommonTag(tag: CompoundTag) {
|
|
||||||
super.toCommonTag(tag)
|
|
||||||
tag.put("InternalBuffer", internalBuffer.toTag())
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun fromCommonTag(tag: CompoundTag) {
|
|
||||||
super.fromCommonTag(tag)
|
|
||||||
internalBuffer.fromTag(tag.getCompound("InternalBuffer"))
|
|
||||||
}
|
|
||||||
interface NetItemObserver {
|
|
||||||
fun netItemsChanged()
|
|
||||||
}
|
|
||||||
|
|
||||||
class PendingInsertion(stack: ItemStack, timestamp: Long): NetworkStackDispatcher.PendingInsertion<PendingInsertion>(stack, timestamp) {
|
|
||||||
var bufferSlot by Delegates.notNull<Int>()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
data class StackLocateRequest(
|
|
||||||
val stack: ItemStack,
|
|
||||||
val amount: Int,
|
|
||||||
val timestamp: Long,
|
|
||||||
var results: MutableSet<Pair<Int, NetworkStackProvider>> = mutableSetOf()
|
|
||||||
) {
|
|
||||||
val totalResultAmount: Int
|
|
||||||
get() = results.fold(0) { acc, (amount, _) -> acc + amount }
|
|
||||||
|
|
||||||
fun isFinishable(currentTimestamp: Long): Boolean {
|
|
||||||
// we can't check totalResultAmount >= amount because we need to hear back from all network stack providers to
|
|
||||||
// correctly sort by priority
|
|
||||||
return currentTimestamp - timestamp >= TerminalBlockEntity.LOCATE_REQUEST_TIMEOUT
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,12 +34,12 @@ class TerminalBufferInventory(size: Int): SimpleInventory(size) {
|
||||||
tag.getIntArray("Modes").forEachIndexed { i, it -> modes[i] = Mode.values()[it] }
|
tag.getIntArray("Modes").forEachIndexed { i, it -> modes[i] = Mode.values()[it] }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun insertFromNetwork(stack: ItemStack): ItemStack {
|
fun insert(stack: ItemStack, mode: Mode): ItemStack {
|
||||||
var remaining = stack.copy()
|
var remaining = stack.copy()
|
||||||
for (slot in 0 until size()) {
|
for (slot in 0 until size()) {
|
||||||
if (modes[slot] == Mode.TO_NETWORK) continue
|
if (modes[slot] != mode && modes[slot] != Mode.UNASSIGNED) continue
|
||||||
|
|
||||||
remaining = insertFromNetwork(stack, slot)
|
remaining = tryInsert(stack, slot, mode)
|
||||||
|
|
||||||
if (remaining.isEmpty) {
|
if (remaining.isEmpty) {
|
||||||
break
|
break
|
||||||
|
@ -48,19 +48,17 @@ class TerminalBufferInventory(size: Int): SimpleInventory(size) {
|
||||||
return remaining
|
return remaining
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun insertFromNetwork(stack: ItemStack, slot: Int): ItemStack {
|
private fun tryInsert(stack: ItemStack, slot: Int, mode: Mode): ItemStack {
|
||||||
val mode = modes[slot]
|
|
||||||
if (mode == Mode.TO_NETWORK) return stack
|
|
||||||
val current = getStack(slot)
|
val current = getStack(slot)
|
||||||
if (current.isEmpty) {
|
if (current.isEmpty) {
|
||||||
setStack(slot, stack)
|
setStack(slot, stack)
|
||||||
modes[slot] = Mode.FROM_NETWORK
|
markSlot(slot, mode)
|
||||||
return ItemStack.EMPTY
|
return ItemStack.EMPTY
|
||||||
} else if (ItemStackUtil.areEqualIgnoreAmounts(stack, current)) {
|
} else if (ItemStackUtil.areEqualIgnoreAmounts(stack, current)) {
|
||||||
val toTransfer = min(current.maxCount - current.count, stack.count)
|
val toTransfer = min(current.maxCount - current.count, stack.count)
|
||||||
current.count += toTransfer
|
current.count += toTransfer
|
||||||
stack.count -= toTransfer
|
stack.count -= toTransfer
|
||||||
modes[slot] = Mode.FROM_NETWORK
|
markSlot(slot, mode)
|
||||||
return stack
|
return stack
|
||||||
} else {
|
} else {
|
||||||
return stack
|
return stack
|
||||||
|
|
|
@ -23,7 +23,7 @@ class TerminalFakeSlot(fakeInv: FakeInventory, slot: Int, x: Int, y: Int): Slot(
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class FakeInventory(val screenHandler: TerminalScreenHandler): Inventory {
|
class FakeInventory(val screenHandler: AbstractTerminalScreenHandler<*>): Inventory {
|
||||||
override fun getStack(slot: Int): ItemStack {
|
override fun getStack(slot: Int): ItemStack {
|
||||||
if (slot >= screenHandler.itemsForDisplay.size) return ItemStack.EMPTY
|
if (slot >= screenHandler.itemsForDisplay.size) return ItemStack.EMPTY
|
||||||
return screenHandler.itemsForDisplay[slot]
|
return screenHandler.itemsForDisplay[slot]
|
||||||
|
|
|
@ -19,7 +19,7 @@ import kotlin.math.floor
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
class TerminalRequestAmountViewController(
|
class TerminalRequestAmountViewController(
|
||||||
val screen: TerminalScreen,
|
val screen: AbstractTerminalScreen<*, *>,
|
||||||
val stack: ItemStack,
|
val stack: ItemStack,
|
||||||
): ViewController() {
|
): ViewController() {
|
||||||
|
|
||||||
|
|
|
@ -1,194 +1,33 @@
|
||||||
package net.shadowfacts.phycon.block.terminal
|
package net.shadowfacts.phycon.block.terminal
|
||||||
|
|
||||||
import com.mojang.blaze3d.systems.RenderSystem
|
|
||||||
import net.minecraft.client.MinecraftClient
|
|
||||||
import net.minecraft.client.gui.DrawableHelper
|
|
||||||
import net.minecraft.client.gui.Element
|
|
||||||
import net.minecraft.client.gui.widget.TextFieldWidget
|
|
||||||
import net.minecraft.client.render.Tessellator
|
|
||||||
import net.minecraft.client.render.VertexConsumerProvider
|
|
||||||
import net.minecraft.client.util.math.MatrixStack
|
|
||||||
import net.minecraft.entity.player.PlayerInventory
|
import net.minecraft.entity.player.PlayerInventory
|
||||||
import net.minecraft.item.ItemStack
|
|
||||||
import net.minecraft.screen.slot.Slot
|
|
||||||
import net.minecraft.screen.slot.SlotActionType
|
|
||||||
import net.minecraft.text.LiteralText
|
|
||||||
import net.minecraft.text.Text
|
import net.minecraft.text.Text
|
||||||
import net.minecraft.util.Identifier
|
import net.minecraft.util.Identifier
|
||||||
import net.shadowfacts.cacao.CacaoHandledScreen
|
|
||||||
import net.shadowfacts.cacao.window.ScreenHandlerWindow
|
|
||||||
import net.shadowfacts.cacao.window.Window
|
|
||||||
import net.shadowfacts.phycon.PhysicalConnectivity
|
import net.shadowfacts.phycon.PhysicalConnectivity
|
||||||
import net.shadowfacts.phycon.networking.C2STerminalRequestItem
|
|
||||||
import net.shadowfacts.phycon.networking.C2STerminalUpdateDisplayedItems
|
|
||||||
import net.shadowfacts.phycon.util.SortMode
|
|
||||||
import java.math.RoundingMode
|
|
||||||
import java.text.DecimalFormat
|
|
||||||
import java.util.LinkedList
|
|
||||||
import kotlin.math.ceil
|
|
||||||
import kotlin.math.min
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
class TerminalScreen(handler: TerminalScreenHandler, playerInv: PlayerInventory, title: Text): CacaoHandledScreen<TerminalScreenHandler>(handler, playerInv, title) {
|
class TerminalScreen(
|
||||||
|
handler: TerminalScreenHandler,
|
||||||
|
playerInv: PlayerInventory,
|
||||||
|
title: Text,
|
||||||
|
): AbstractTerminalScreen<TerminalBlockEntity, TerminalScreenHandler>(
|
||||||
|
handler,
|
||||||
|
playerInv,
|
||||||
|
title,
|
||||||
|
252,
|
||||||
|
222
|
||||||
|
) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val BACKGROUND = Identifier(PhysicalConnectivity.MODID, "textures/gui/terminal.png")
|
private val BACKGROUND = Identifier(PhysicalConnectivity.MODID, "textures/gui/terminal.png")
|
||||||
|
|
||||||
private val clickHandlers = LinkedList<TerminalScreen.(Double, Double, Int) -> Boolean?>()
|
|
||||||
|
|
||||||
fun registerClickHandler(handler: TerminalScreen.(Double, Double, Int) -> Boolean?) {
|
|
||||||
clickHandlers.add(handler)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val backgroundWidth: Int
|
override val backgroundTexture = BACKGROUND
|
||||||
get() = backgroundWidth
|
|
||||||
val backgroundHeight: Int
|
|
||||||
get() = backgroundHeight
|
|
||||||
|
|
||||||
val terminalVC = TerminalViewController(this, handler, handler.terminal)
|
override fun createViewController(): AbstractTerminalViewController<*, *, *> {
|
||||||
var amountVC: TerminalRequestAmountViewController? = null
|
return TerminalViewController(this, handler)
|
||||||
|
|
||||||
var searchQuery = ""
|
|
||||||
var scrollPosition = 0.0
|
|
||||||
|
|
||||||
init {
|
|
||||||
backgroundWidth = 252
|
|
||||||
backgroundHeight = 222
|
|
||||||
|
|
||||||
addWindow(ScreenHandlerWindow(handler, terminalVC))
|
|
||||||
|
|
||||||
requestUpdatedItems()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun requestItem(stack: ItemStack, amount: Int) {
|
|
||||||
val netHandler = MinecraftClient.getInstance().player!!.networkHandler
|
|
||||||
val packet = C2STerminalRequestItem(handler.terminal, stack, amount)
|
|
||||||
netHandler.sendPacket(packet)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun requestUpdatedItems() {
|
|
||||||
val player = MinecraftClient.getInstance().player!!
|
|
||||||
player.networkHandler.sendPacket(C2STerminalUpdateDisplayedItems(handler.terminal, searchQuery, scrollPosition.toFloat()))
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun showRequestAmountDialog(stack: ItemStack) {
|
|
||||||
val vc = TerminalRequestAmountViewController(this, stack)
|
|
||||||
addWindow(Window(vc))
|
|
||||||
amountVC = vc
|
|
||||||
}
|
|
||||||
|
|
||||||
@ExperimentalUnsignedTypes
|
|
||||||
fun drawSlotUnderlay(matrixStack: MatrixStack, slot: Slot) {
|
|
||||||
if (!handler.isBufferSlot(slot.id)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val mode = handler.terminal.internalBuffer.getMode(slot.id - handler.bufferSlotsStart)
|
|
||||||
val color: UInt = when (mode) {
|
|
||||||
TerminalBufferInventory.Mode.TO_NETWORK -> 0xFFFF0000u
|
|
||||||
TerminalBufferInventory.Mode.FROM_NETWORK -> 0xFF00FF00u
|
|
||||||
else -> return
|
|
||||||
}
|
|
||||||
DrawableHelper.fill(matrixStack, slot.x, slot.y, slot.x + 16, slot.y + 16, color.toInt())
|
|
||||||
}
|
|
||||||
|
|
||||||
private val DECIMAL_FORMAT = DecimalFormat("#.#").apply { roundingMode = RoundingMode.HALF_UP }
|
|
||||||
private val FORMAT = DecimalFormat("##").apply { roundingMode = RoundingMode.HALF_UP }
|
|
||||||
|
|
||||||
fun drawNetworkSlotAmount(stack: ItemStack, x: Int, y: Int, slot: Slot, matrixStack: MatrixStack) {
|
|
||||||
val amount = stack.count
|
|
||||||
val s = when {
|
|
||||||
amount < 1_000 -> amount.toString()
|
|
||||||
amount < 1_000_000 -> {
|
|
||||||
val format = if (amount < 10_000) DECIMAL_FORMAT else FORMAT
|
|
||||||
format.format(amount / 1_000.0) + "K"
|
|
||||||
}
|
|
||||||
amount < 1_000_000_000 -> {
|
|
||||||
val format = if (amount < 10_000_000) DECIMAL_FORMAT else FORMAT
|
|
||||||
format.format(amount / 1_000_000.0) + "M"
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
DECIMAL_FORMAT.format(amount / 1000000000.0).toString() + "B"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// draw damage bar
|
|
||||||
// empty string for label because vanilla renders the count behind the damage bar
|
|
||||||
itemRenderer.renderGuiItemOverlay(textRenderer, stack, x, y, "")
|
|
||||||
|
|
||||||
matrixStack.push()
|
|
||||||
matrixStack.translate(x.toDouble(), y.toDouble(), itemRenderer.zOffset + 200.0)
|
|
||||||
val scale = 2 / 3f
|
|
||||||
matrixStack.scale(scale, scale, 1.0f)
|
|
||||||
val immediate = VertexConsumerProvider.immediate(Tessellator.getInstance().buffer)
|
|
||||||
val textX = (1 / scale * 18) - textRenderer.getWidth(s).toFloat() - 3
|
|
||||||
val textY = (1 / scale * 18) - 11
|
|
||||||
textRenderer.draw(s, textX, textY, 0xffffff, true, matrixStack.peek().model, immediate, false, 0, 0xF000F0)
|
|
||||||
immediate.draw()
|
|
||||||
matrixStack.pop()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun drawBackground(matrixStack: MatrixStack, delta: Float, mouseX: Int, mouseY: Int) {
|
|
||||||
super.drawBackground(matrixStack, delta, mouseX, mouseY)
|
|
||||||
|
|
||||||
RenderSystem.color4f(1f, 1f, 1f, 1f)
|
|
||||||
client!!.textureManager.bindTexture(BACKGROUND)
|
|
||||||
val x = (width - backgroundWidth) / 2
|
|
||||||
val y = (height - backgroundHeight) / 2
|
|
||||||
drawTexture(matrixStack, x, y, 0, 0, backgroundWidth, backgroundHeight)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun tick() {
|
|
||||||
super.tick()
|
|
||||||
|
|
||||||
if (amountVC != null) {
|
|
||||||
amountVC!!.field.tick()
|
|
||||||
} else {
|
|
||||||
terminalVC.searchField.tick()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
|
|
||||||
for (handler in clickHandlers) {
|
|
||||||
val res = handler(mouseX, mouseY, button)
|
|
||||||
if (res != null) {
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return super.mouseClicked(mouseX, mouseY, button)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onMouseClick(slot: Slot?, invSlot: Int, clickData: Int, type: SlotActionType?) {
|
|
||||||
super.onMouseClick(slot, invSlot, clickData, type)
|
|
||||||
|
|
||||||
if (slot != null && !slot.stack.isEmpty && handler.isNetworkSlot(slot.id)) {
|
|
||||||
val stack = slot.stack
|
|
||||||
|
|
||||||
if (type == SlotActionType.QUICK_MOVE) {
|
|
||||||
// shift click, request full stack
|
|
||||||
requestItem(stack, min(stack.count, stack.maxCount))
|
|
||||||
} else if (type == SlotActionType.PICKUP) {
|
|
||||||
if (clickData == 1) {
|
|
||||||
// right click, request half stack
|
|
||||||
requestItem(stack, ceil(min(stack.count, stack.maxCount) / 2f).toInt())
|
|
||||||
} else {
|
|
||||||
showRequestAmountDialog(stack)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val fakeFocusedElement = TextFieldWidget(textRenderer, 0, 0, 0, 0, LiteralText(""))
|
|
||||||
override fun getFocused(): Element? {
|
|
||||||
return if (windows.last().firstResponder != null) {
|
|
||||||
fakeFocusedElement
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,62 +1,18 @@
|
||||||
package net.shadowfacts.phycon.block.terminal
|
package net.shadowfacts.phycon.block.terminal
|
||||||
|
|
||||||
import net.minecraft.screen.slot.Slot
|
|
||||||
import net.minecraft.screen.slot.SlotActionType
|
|
||||||
import net.minecraft.entity.player.PlayerEntity
|
|
||||||
import net.minecraft.entity.player.PlayerInventory
|
import net.minecraft.entity.player.PlayerInventory
|
||||||
import net.minecraft.item.ItemStack
|
|
||||||
import net.minecraft.network.PacketByteBuf
|
import net.minecraft.network.PacketByteBuf
|
||||||
import net.minecraft.screen.ScreenHandler
|
|
||||||
import net.minecraft.server.network.ServerPlayerEntity
|
|
||||||
import net.minecraft.util.Identifier
|
|
||||||
import net.minecraft.util.registry.Registry
|
|
||||||
import net.shadowfacts.phycon.DefaultPlugin
|
|
||||||
import net.shadowfacts.phycon.PhysicalConnectivity
|
|
||||||
import net.shadowfacts.phycon.init.PhyBlocks
|
import net.shadowfacts.phycon.init.PhyBlocks
|
||||||
import net.shadowfacts.phycon.init.PhyScreens
|
import net.shadowfacts.phycon.init.PhyScreens
|
||||||
import net.shadowfacts.phycon.networking.S2CTerminalUpdateDisplayedItems
|
|
||||||
import net.shadowfacts.phycon.util.SortMode
|
|
||||||
import net.shadowfacts.phycon.util.TerminalSettings
|
|
||||||
import net.shadowfacts.phycon.util.copyWithCount
|
|
||||||
import java.lang.ref.WeakReference
|
|
||||||
import kotlin.math.ceil
|
|
||||||
import kotlin.math.max
|
|
||||||
import kotlin.math.min
|
|
||||||
import kotlin.math.roundToInt
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
class TerminalScreenHandler(
|
class TerminalScreenHandler(
|
||||||
syncId: Int,
|
syncId: Int,
|
||||||
val playerInv: PlayerInventory,
|
playerInv: PlayerInventory,
|
||||||
val terminal: TerminalBlockEntity,
|
terminal: TerminalBlockEntity,
|
||||||
): ScreenHandler(PhyScreens.TERMINAL, syncId),
|
): AbstractTerminalScreenHandler<TerminalBlockEntity>(PhyScreens.TERMINAL, syncId, playerInv, terminal) {
|
||||||
TerminalBlockEntity.NetItemObserver {
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
val ID = Identifier(PhysicalConnectivity.MODID, "terminal")
|
|
||||||
}
|
|
||||||
|
|
||||||
private val rowsDisplayed = 6
|
|
||||||
|
|
||||||
private val fakeInv = FakeInventory(this)
|
|
||||||
private var searchQuery: String = ""
|
|
||||||
private var settings = TerminalSettings()
|
|
||||||
var totalEntries = 0
|
|
||||||
private set
|
|
||||||
var scrollPosition = 0f
|
|
||||||
private var itemEntries = listOf<Entry>()
|
|
||||||
set(value) {
|
|
||||||
field = value
|
|
||||||
if (terminal.world!!.isClient) {
|
|
||||||
itemsForDisplay = value.map {
|
|
||||||
it.stack.copyWithCount(it.amount)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var itemsForDisplay = listOf<ItemStack>()
|
|
||||||
private set
|
|
||||||
|
|
||||||
constructor(syncId: Int, playerInv: PlayerInventory, buf: PacketByteBuf):
|
constructor(syncId: Int, playerInv: PlayerInventory, buf: PacketByteBuf):
|
||||||
this(
|
this(
|
||||||
|
@ -65,195 +21,4 @@ class TerminalScreenHandler(
|
||||||
PhyBlocks.TERMINAL.getBlockEntity(playerInv.player.world, buf.readBlockPos())!!
|
PhyBlocks.TERMINAL.getBlockEntity(playerInv.player.world, buf.readBlockPos())!!
|
||||||
)
|
)
|
||||||
|
|
||||||
init {
|
|
||||||
if (!terminal.world!!.isClient) {
|
|
||||||
assert(terminal.netItemObserver?.get() === null)
|
|
||||||
terminal.netItemObserver = WeakReference(this)
|
|
||||||
// intentionally don't call netItemsChanged immediately, we need to wait for the client to send us its settings
|
|
||||||
}
|
|
||||||
|
|
||||||
// network
|
|
||||||
for (y in 0 until 6) {
|
|
||||||
for (x in 0 until 9) {
|
|
||||||
addSlot(TerminalFakeSlot(fakeInv, y * 9 + x, 66 + x * 18, 18 + y * 18))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// internal buffer
|
|
||||||
for (y in 0 until 6) {
|
|
||||||
for (x in 0 until 3) {
|
|
||||||
addSlot(Slot(terminal.internalBuffer, y * 3 + x, 8 + x * 18, 18 + y * 18))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// player inv
|
|
||||||
for (y in 0 until 3) {
|
|
||||||
for (x in 0 until 9) {
|
|
||||||
addSlot(Slot(playerInv, x + y * 9 + 9, 66 + x * 18, 140 + y * 18))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// hotbar
|
|
||||||
for (x in 0 until 9) {
|
|
||||||
addSlot(Slot(playerInv, x, 66 + x * 18, 198))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun netItemsChanged() {
|
|
||||||
val player = playerInv.player
|
|
||||||
assert(player is ServerPlayerEntity)
|
|
||||||
|
|
||||||
val filtered = terminal.cachedNetItems.object2IntEntrySet().filter {
|
|
||||||
if (searchQuery.isBlank()) return@filter true
|
|
||||||
if (searchQuery.startsWith('@')) {
|
|
||||||
val unprefixed = searchQuery.drop(1)
|
|
||||||
val key = Registry.ITEM.getKey(it.key.item)
|
|
||||||
if (key.isPresent && key.get().value.namespace.startsWith(unprefixed, true)) {
|
|
||||||
return@filter true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
it.key.name.string.contains(searchQuery, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
totalEntries = filtered.size
|
|
||||||
|
|
||||||
val sorted =
|
|
||||||
when (settings[DefaultPlugin.SORT_MODE]) {
|
|
||||||
SortMode.COUNT_HIGH_FIRST -> filtered.sortedByDescending { it.intValue }
|
|
||||||
SortMode.COUNT_LOW_FIRST -> filtered.sortedBy { it.intValue }
|
|
||||||
SortMode.ALPHABETICAL -> filtered.sortedBy { it.key.name.string }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
val offsetInItems = currentScrollOffsetInItems()
|
|
||||||
val end = min(offsetInItems + rowsDisplayed * 9, sorted.size)
|
|
||||||
itemEntries = sorted.subList(offsetInItems, end).map { Entry(it.key, it.intValue) }
|
|
||||||
|
|
||||||
// itemEntries = sorted.map { Entry(it.key, it.intValue) }
|
|
||||||
|
|
||||||
(player as ServerPlayerEntity).networkHandler.sendPacket(S2CTerminalUpdateDisplayedItems(terminal, itemEntries, searchQuery, settings, scrollPosition, totalEntries))
|
|
||||||
}
|
|
||||||
|
|
||||||
fun totalRows(): Int {
|
|
||||||
return ceil(totalEntries / 9f).toInt()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun maxScrollOffsetInRows(): Int {
|
|
||||||
return totalRows() - rowsDisplayed
|
|
||||||
}
|
|
||||||
|
|
||||||
fun currentScrollOffsetInRows(): Int {
|
|
||||||
return max(0, (scrollPosition * maxScrollOffsetInRows()).roundToInt())
|
|
||||||
}
|
|
||||||
|
|
||||||
fun currentScrollOffsetInItems(): Int {
|
|
||||||
return currentScrollOffsetInRows() * 9
|
|
||||||
}
|
|
||||||
|
|
||||||
fun sendUpdatedItemsToClient(player: ServerPlayerEntity, query: String, settings: TerminalSettings, scrollPosition: Float) {
|
|
||||||
this.searchQuery = query
|
|
||||||
this.settings = settings
|
|
||||||
this.scrollPosition = scrollPosition
|
|
||||||
netItemsChanged()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun receivedUpdatedItemsFromServer(entries: List<Entry>, query: String, scrollPosition: Float, totalEntries: Int) {
|
|
||||||
assert(playerInv.player.world.isClient)
|
|
||||||
|
|
||||||
this.searchQuery = query
|
|
||||||
this.scrollPosition = scrollPosition
|
|
||||||
this.totalEntries = totalEntries
|
|
||||||
itemEntries = entries
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun canUse(player: PlayerEntity): Boolean {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun close(player: PlayerEntity) {
|
|
||||||
super.close(player)
|
|
||||||
|
|
||||||
terminal.netItemObserver = null
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onSlotClick(slotId: Int, clickData: Int, actionType: SlotActionType, player: PlayerEntity): ItemStack {
|
|
||||||
if (isBufferSlot(slotId)) {
|
|
||||||
// todo: why does this think it's quick_craft sometimes?
|
|
||||||
if ((actionType == SlotActionType.PICKUP || actionType == SlotActionType.QUICK_CRAFT) && !player.inventory.cursorStack.isEmpty) {
|
|
||||||
// placing cursor stack into buffer
|
|
||||||
val bufferSlot = slotId - bufferSlotsStart // subtract 54 to convert the handler slot ID to a valid buffer index
|
|
||||||
terminal.internalBuffer.markSlot(bufferSlot, TerminalBufferInventory.Mode.TO_NETWORK)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return super.onSlotClick(slotId, clickData, actionType, player)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun transferSlot(player: PlayerEntity, slotId: Int): ItemStack {
|
|
||||||
if (isNetworkSlot(slotId)) {
|
|
||||||
return ItemStack.EMPTY;
|
|
||||||
}
|
|
||||||
|
|
||||||
val slot = slots[slotId]
|
|
||||||
if (!slot.hasStack()) {
|
|
||||||
return ItemStack.EMPTY
|
|
||||||
}
|
|
||||||
|
|
||||||
val result = slot.stack.copy()
|
|
||||||
|
|
||||||
if (isBufferSlot(slotId)) {
|
|
||||||
// last boolean param is fromLast
|
|
||||||
if (!insertItem(slot.stack, playerSlotsStart, playerSlotsEnd, true)) {
|
|
||||||
return ItemStack.EMPTY
|
|
||||||
}
|
|
||||||
if (slot.stack.isEmpty) {
|
|
||||||
terminal.internalBuffer.markSlot(slotId - bufferSlotsStart, TerminalBufferInventory.Mode.UNASSIGNED)
|
|
||||||
}
|
|
||||||
} else if (isPlayerSlot(slotId)) {
|
|
||||||
val slotsInsertedInto = tryInsertItem(slot.stack, bufferSlotsStart until playerSlotsStart) { terminal.internalBuffer.getMode(it - bufferSlotsStart) != TerminalBufferInventory.Mode.FROM_NETWORK }
|
|
||||||
slotsInsertedInto.forEach { terminal.internalBuffer.markSlot(it - bufferSlotsStart, TerminalBufferInventory.Mode.TO_NETWORK) }
|
|
||||||
if (slot.stack.isEmpty) {
|
|
||||||
return ItemStack.EMPTY
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun tryInsertItem(stack: ItemStack, slots: IntRange, slotPredicate: (Int) -> Boolean): Collection<Int> {
|
|
||||||
val slotsInsertedInto = mutableListOf<Int>()
|
|
||||||
for (index in slots) {
|
|
||||||
if (stack.isEmpty) break
|
|
||||||
if (!slotPredicate(index)) continue
|
|
||||||
|
|
||||||
val slot = this.slots[index]
|
|
||||||
val slotStack = slot.stack
|
|
||||||
if (slotStack.isEmpty) {
|
|
||||||
slot.stack = stack.copy()
|
|
||||||
stack.count = 0
|
|
||||||
|
|
||||||
slot.markDirty()
|
|
||||||
slotsInsertedInto.add(index)
|
|
||||||
} else if (canStacksCombine(slotStack, stack) && slotStack.count < slotStack.maxCount) {
|
|
||||||
val maxToMove = slotStack.maxCount - slotStack.count
|
|
||||||
val toMove = min(maxToMove, stack.count)
|
|
||||||
slotStack.increment(toMove)
|
|
||||||
stack.decrement(toMove)
|
|
||||||
|
|
||||||
slot.markDirty()
|
|
||||||
slotsInsertedInto.add(index)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return slotsInsertedInto
|
|
||||||
}
|
|
||||||
|
|
||||||
val networkSlotsStart = 0
|
|
||||||
val networkSlotsEnd = 54
|
|
||||||
val bufferSlotsStart = 54
|
|
||||||
val bufferSlotsEnd = 72
|
|
||||||
val playerSlotsStart = 72
|
|
||||||
val playerSlotsEnd = 72 + 36
|
|
||||||
fun isNetworkSlot(id: Int) = id in 0 until bufferSlotsStart
|
|
||||||
fun isBufferSlot(id: Int) = id in bufferSlotsStart until playerSlotsStart
|
|
||||||
fun isPlayerSlot(id: Int) = id >= playerSlotsStart
|
|
||||||
|
|
||||||
data class Entry(val stack: ItemStack, val amount: Int)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,164 +1,13 @@
|
||||||
package net.shadowfacts.phycon.block.terminal
|
package net.shadowfacts.phycon.block.terminal
|
||||||
|
|
||||||
import net.minecraft.text.TranslatableText
|
|
||||||
import net.minecraft.util.math.MathHelper
|
|
||||||
import net.shadowfacts.cacao.geometry.Axis
|
|
||||||
import net.shadowfacts.cacao.geometry.Point
|
|
||||||
import net.shadowfacts.cacao.util.Color
|
|
||||||
import net.shadowfacts.cacao.util.MouseButton
|
|
||||||
import net.shadowfacts.cacao.view.Label
|
|
||||||
import net.shadowfacts.cacao.view.StackView
|
|
||||||
import net.shadowfacts.cacao.view.View
|
|
||||||
import net.shadowfacts.cacao.view.textfield.TextField
|
|
||||||
import net.shadowfacts.cacao.viewcontroller.ViewController
|
|
||||||
import net.shadowfacts.kiwidsl.dsl
|
|
||||||
import net.shadowfacts.phycon.client.screen.ScrollTrackView
|
|
||||||
import net.shadowfacts.phycon.util.TerminalSettings
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
class TerminalViewController(
|
class TerminalViewController(
|
||||||
val screen: TerminalScreen,
|
screen: TerminalScreen,
|
||||||
val handler: TerminalScreenHandler,
|
handler: TerminalScreenHandler,
|
||||||
val terminal: TerminalBlockEntity,
|
): AbstractTerminalViewController<TerminalBlockEntity, TerminalScreen, TerminalScreenHandler>(
|
||||||
): ViewController() {
|
screen,
|
||||||
|
handler,
|
||||||
private lateinit var scrollTrack: ScrollTrackView
|
) {
|
||||||
lateinit var settingsView: View
|
|
||||||
private set
|
|
||||||
lateinit var searchField: TextField
|
|
||||||
private set
|
|
||||||
|
|
||||||
override fun loadView() {
|
|
||||||
view = ScrollHandlingView(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun viewDidLoad() {
|
|
||||||
super.viewDidLoad()
|
|
||||||
|
|
||||||
val pane = view.addLayoutGuide()
|
|
||||||
view.solver.dsl {
|
|
||||||
pane.centerXAnchor equalTo view.centerXAnchor
|
|
||||||
pane.centerYAnchor equalTo view.centerYAnchor
|
|
||||||
pane.widthAnchor equalTo screen.backgroundWidth
|
|
||||||
pane.heightAnchor equalTo screen.backgroundHeight
|
|
||||||
}
|
|
||||||
|
|
||||||
val buffer = view.addLayoutGuide()
|
|
||||||
view.solver.dsl {
|
|
||||||
buffer.leftAnchor equalTo (pane.leftAnchor + 7)
|
|
||||||
buffer.topAnchor equalTo (pane.topAnchor + 17)
|
|
||||||
buffer.widthAnchor equalTo (18 * 3)
|
|
||||||
buffer.heightAnchor equalTo (18 * 6)
|
|
||||||
}
|
|
||||||
|
|
||||||
val network = view.addLayoutGuide()
|
|
||||||
view.solver.dsl {
|
|
||||||
network.leftAnchor equalTo (pane.leftAnchor + 65)
|
|
||||||
network.topAnchor equalTo buffer.topAnchor
|
|
||||||
network.widthAnchor equalTo (18 * 9)
|
|
||||||
network.heightAnchor equalTo (18 * 6)
|
|
||||||
}
|
|
||||||
|
|
||||||
val playerInv = view.addLayoutGuide()
|
|
||||||
view.solver.dsl {
|
|
||||||
playerInv.leftAnchor equalTo network.leftAnchor
|
|
||||||
playerInv.topAnchor equalTo (pane.topAnchor + 139)
|
|
||||||
playerInv.widthAnchor equalTo (18 * 9)
|
|
||||||
playerInv.heightAnchor equalTo 76
|
|
||||||
}
|
|
||||||
|
|
||||||
val titleLabel = view.addSubview(Label(screen.title)).apply {
|
|
||||||
textColor = Color.TEXT
|
|
||||||
}
|
|
||||||
val playerInvLabel = view.addSubview(Label(handler.playerInv.displayName)).apply {
|
|
||||||
textColor = Color.TEXT
|
|
||||||
}
|
|
||||||
val bufferLabel = view.addSubview(Label(TranslatableText("gui.phycon.terminal_buffer"))).apply {
|
|
||||||
textColor = Color.TEXT
|
|
||||||
}
|
|
||||||
|
|
||||||
searchField = view.addSubview(TerminalSearchField()).apply {
|
|
||||||
handler = ::searchFieldChanged
|
|
||||||
drawBackground = false
|
|
||||||
}
|
|
||||||
searchField.becomeFirstResponder()
|
|
||||||
|
|
||||||
scrollTrack = view.addSubview(ScrollTrackView(::scrollPositionChanged))
|
|
||||||
|
|
||||||
val settingsStack = view.addSubview(StackView(Axis.VERTICAL, spacing = 2.0))
|
|
||||||
settingsView = settingsStack
|
|
||||||
TerminalSettings.allKeys.forEach { key ->
|
|
||||||
val button = SettingButton(key)
|
|
||||||
button.handler = { settingsChanged() }
|
|
||||||
settingsStack.addArrangedSubview(button)
|
|
||||||
}
|
|
||||||
|
|
||||||
view.solver.dsl {
|
|
||||||
titleLabel.leftAnchor equalTo network.leftAnchor
|
|
||||||
titleLabel.topAnchor equalTo (pane.topAnchor + 6)
|
|
||||||
|
|
||||||
bufferLabel.leftAnchor equalTo buffer.leftAnchor
|
|
||||||
bufferLabel.topAnchor equalTo titleLabel.topAnchor
|
|
||||||
|
|
||||||
playerInvLabel.leftAnchor equalTo playerInv.leftAnchor
|
|
||||||
playerInvLabel.topAnchor equalTo (pane.bottomAnchor - 94)
|
|
||||||
|
|
||||||
searchField.leftAnchor equalTo (pane.leftAnchor + 138)
|
|
||||||
searchField.topAnchor equalTo (pane.topAnchor + 5)
|
|
||||||
searchField.widthAnchor equalTo 80
|
|
||||||
searchField.heightAnchor equalTo 9
|
|
||||||
|
|
||||||
scrollTrack.leftAnchor equalTo (pane.leftAnchor + 232)
|
|
||||||
scrollTrack.topAnchor equalTo (network.topAnchor + 1)
|
|
||||||
scrollTrack.bottomAnchor equalTo (network.bottomAnchor - 1)
|
|
||||||
scrollTrack.widthAnchor equalTo 12
|
|
||||||
|
|
||||||
settingsStack.leftAnchor equalTo (pane.rightAnchor + 4)
|
|
||||||
settingsStack.topAnchor equalTo pane.topAnchor
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun searchFieldChanged(field: TextField) {
|
|
||||||
screen.searchQuery = field.text
|
|
||||||
screen.requestUpdatedItems()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun scrollPositionChanged(track: ScrollTrackView) {
|
|
||||||
val oldOffset = handler.currentScrollOffsetInRows()
|
|
||||||
|
|
||||||
handler.scrollPosition = track.scrollPosition.toFloat()
|
|
||||||
screen.scrollPosition = track.scrollPosition
|
|
||||||
|
|
||||||
if (handler.currentScrollOffsetInRows() != oldOffset) {
|
|
||||||
screen.requestUpdatedItems()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun settingsChanged() {
|
|
||||||
screen.requestUpdatedItems()
|
|
||||||
}
|
|
||||||
|
|
||||||
class TerminalSearchField: TextField("") {
|
|
||||||
override fun mouseClickedOutside(point: Point, mouseButton: MouseButton) {
|
|
||||||
// no-op
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ScrollHandlingView(val vc: TerminalViewController): View() {
|
|
||||||
override fun mouseScrolled(point: Point, amount: Double): Boolean {
|
|
||||||
var newOffsetInRows = vc.handler.currentScrollOffsetInRows() - amount.toInt()
|
|
||||||
newOffsetInRows = MathHelper.clamp(newOffsetInRows, 0, vc.handler.maxScrollOffsetInRows())
|
|
||||||
if (newOffsetInRows != vc.handler.currentScrollOffsetInRows()) {
|
|
||||||
val newScrollPosition = newOffsetInRows / vc.handler.maxScrollOffsetInRows().toDouble()
|
|
||||||
vc.screen.scrollPosition = newScrollPosition
|
|
||||||
vc.scrollTrack.scrollPosition = newScrollPosition
|
|
||||||
vc.screen.requestUpdatedItems()
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import net.minecraft.client.render.model.UnbakedModel
|
||||||
import net.minecraft.resource.ResourceManager
|
import net.minecraft.resource.ResourceManager
|
||||||
import net.minecraft.util.Identifier
|
import net.minecraft.util.Identifier
|
||||||
import net.shadowfacts.phycon.PhysicalConnectivity
|
import net.shadowfacts.phycon.PhysicalConnectivity
|
||||||
import net.shadowfacts.phycon.client.model.TerminalModel
|
import net.shadowfacts.phycon.client.model.ScreenDeviceModel
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
|
@ -15,11 +15,13 @@ class PhyExtendedModelProvider(resourceManager: ResourceManager): ModelResourceP
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val TERMINAL = Identifier(PhysicalConnectivity.MODID, "block/terminal")
|
val TERMINAL = Identifier(PhysicalConnectivity.MODID, "block/terminal")
|
||||||
|
val CRAFTING_TERMINAL = Identifier(PhysicalConnectivity.MODID, "block/crafting_terminal")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun loadModelResource(resourceId: Identifier, context: ModelProviderContext): UnbakedModel? {
|
override fun loadModelResource(resourceId: Identifier, context: ModelProviderContext): UnbakedModel? {
|
||||||
return when (resourceId) {
|
return when (resourceId) {
|
||||||
TERMINAL -> TerminalModel
|
TERMINAL -> ScreenDeviceModel(TERMINAL)
|
||||||
|
CRAFTING_TERMINAL -> ScreenDeviceModel(CRAFTING_TERMINAL)
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ import net.minecraft.util.math.Direction
|
||||||
import net.minecraft.world.BlockRenderView
|
import net.minecraft.world.BlockRenderView
|
||||||
import net.shadowfacts.phycon.PhysicalConnectivity
|
import net.shadowfacts.phycon.PhysicalConnectivity
|
||||||
import net.shadowfacts.phycon.PhysicalConnectivityClient
|
import net.shadowfacts.phycon.PhysicalConnectivityClient
|
||||||
import net.shadowfacts.phycon.block.terminal.TerminalBlock
|
import net.shadowfacts.phycon.block.terminal.AbstractTerminalBlock
|
||||||
import java.util.Random
|
import java.util.Random
|
||||||
import java.util.function.Function
|
import java.util.function.Function
|
||||||
import java.util.function.Supplier
|
import java.util.function.Supplier
|
||||||
|
@ -27,13 +27,18 @@ import java.util.function.Supplier
|
||||||
/**
|
/**
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
object TerminalModel: UnbakedModel, BakedModel, FabricBakedModel {
|
class ScreenDeviceModel(
|
||||||
|
screenTexture: Identifier,
|
||||||
|
): UnbakedModel, BakedModel, FabricBakedModel {
|
||||||
|
|
||||||
private val TERMINAL = SpriteIdentifier(SpriteAtlasTexture.BLOCK_ATLAS_TEXTURE, Identifier(PhysicalConnectivity.MODID, "block/terminal"))
|
companion object {
|
||||||
private val CASING = SpriteIdentifier(SpriteAtlasTexture.BLOCK_ATLAS_TEXTURE, Identifier(PhysicalConnectivity.MODID, "block/casing"))
|
private val CASING = SpriteIdentifier(SpriteAtlasTexture.BLOCK_ATLAS_TEXTURE, Identifier(PhysicalConnectivity.MODID, "block/casing"))
|
||||||
|
}
|
||||||
|
|
||||||
|
private val screenTexture = SpriteIdentifier(SpriteAtlasTexture.BLOCK_ATLAS_TEXTURE, screenTexture)
|
||||||
|
|
||||||
private lateinit var meshes: Array<Mesh>
|
private lateinit var meshes: Array<Mesh>
|
||||||
private lateinit var terminalSprite: Sprite
|
private lateinit var screenSprite: Sprite
|
||||||
|
|
||||||
override fun getModelDependencies(): Collection<Identifier> {
|
override fun getModelDependencies(): Collection<Identifier> {
|
||||||
return listOf()
|
return listOf()
|
||||||
|
@ -43,7 +48,7 @@ object TerminalModel: UnbakedModel, BakedModel, FabricBakedModel {
|
||||||
unbakedModelGetter: Function<Identifier, UnbakedModel>,
|
unbakedModelGetter: Function<Identifier, UnbakedModel>,
|
||||||
unresolvedTextureDependencies: MutableSet<Pair<String, String>>
|
unresolvedTextureDependencies: MutableSet<Pair<String, String>>
|
||||||
): Collection<SpriteIdentifier> {
|
): Collection<SpriteIdentifier> {
|
||||||
return listOf(TERMINAL, CASING)
|
return listOf(screenTexture, CASING)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun bake(
|
override fun bake(
|
||||||
|
@ -53,7 +58,7 @@ object TerminalModel: UnbakedModel, BakedModel, FabricBakedModel {
|
||||||
modelId: Identifier
|
modelId: Identifier
|
||||||
): BakedModel {
|
): BakedModel {
|
||||||
|
|
||||||
terminalSprite = textureGetter.apply(TERMINAL)
|
screenSprite = textureGetter.apply(screenTexture)
|
||||||
val casingSprite = textureGetter.apply(CASING)
|
val casingSprite = textureGetter.apply(CASING)
|
||||||
|
|
||||||
val renderer = RendererAccess.INSTANCE.renderer!!
|
val renderer = RendererAccess.INSTANCE.renderer!!
|
||||||
|
@ -66,26 +71,63 @@ object TerminalModel: UnbakedModel, BakedModel, FabricBakedModel {
|
||||||
|
|
||||||
for (dir in Direction.values()) {
|
for (dir in Direction.values()) {
|
||||||
if (dir == facing) {
|
if (dir == facing) {
|
||||||
emitter.square(facing, 0f, 0f, 1f, 1f, QuadEmitter.CULL_FACE_EPSILON * 10)
|
// screen border
|
||||||
emitter.spriteBake(0, terminalSprite, MutableQuadView.BAKE_LOCK_UV)
|
emitter.square(facing, 0f, 0f, 3/16f, 3/16f, 0f)
|
||||||
|
emitter.spriteBake(0, screenSprite, MutableQuadView.BAKE_LOCK_UV)
|
||||||
emitter.spriteColor(0, -1, -1, -1, -1)
|
emitter.spriteColor(0, -1, -1, -1, -1)
|
||||||
emitter.emit()
|
emitter.emit()
|
||||||
|
|
||||||
|
emitter.square(facing, 13/16f, 0f, 1f, 3/16f, 0f)
|
||||||
|
emitter.spriteBake(0, screenSprite, MutableQuadView.BAKE_LOCK_UV)
|
||||||
|
emitter.spriteColor(0, -1, -1, -1, -1)
|
||||||
|
emitter.emit()
|
||||||
|
|
||||||
|
emitter.square(facing, 13/16f, 13/16f, 1f, 1f, 0f)
|
||||||
|
emitter.spriteBake(0, screenSprite, MutableQuadView.BAKE_LOCK_UV)
|
||||||
|
emitter.spriteColor(0, -1, -1, -1, -1)
|
||||||
|
emitter.emit()
|
||||||
|
|
||||||
|
emitter.square(facing, 0f, 13/16f, 3/16f, 1f, 0f)
|
||||||
|
emitter.spriteBake(0, screenSprite, MutableQuadView.BAKE_LOCK_UV)
|
||||||
|
emitter.spriteColor(0, -1, -1, -1, -1)
|
||||||
|
emitter.emit()
|
||||||
|
|
||||||
|
emitter.square(facing, 3/16f, 0f, 13/16f, 2/16f, 0f)
|
||||||
|
emitter.spriteBake(0, screenSprite, MutableQuadView.BAKE_LOCK_UV)
|
||||||
|
emitter.spriteColor(0, -1, -1, -1, -1)
|
||||||
|
emitter.emit()
|
||||||
|
|
||||||
|
emitter.square(facing, 3/16f, 14/16f, 13/16f, 1f, 0f)
|
||||||
|
emitter.spriteBake(0, screenSprite, MutableQuadView.BAKE_LOCK_UV)
|
||||||
|
emitter.spriteColor(0, -1, -1, -1, -1)
|
||||||
|
emitter.emit()
|
||||||
|
|
||||||
|
emitter.square(facing, 0f, 3/16f, 2/16f, 13/16f, 0f)
|
||||||
|
emitter.spriteBake(0, screenSprite, MutableQuadView.BAKE_LOCK_UV)
|
||||||
|
emitter.spriteColor(0, -1, -1, -1, -1)
|
||||||
|
emitter.emit()
|
||||||
|
|
||||||
|
emitter.square(facing, 14/16f, 3/16f, 1f, 13/16f, 0f)
|
||||||
|
emitter.spriteBake(0, screenSprite, MutableQuadView.BAKE_LOCK_UV)
|
||||||
|
emitter.spriteColor(0, -1, -1, -1, -1)
|
||||||
|
emitter.emit()
|
||||||
|
|
||||||
|
// screen
|
||||||
emitter.material(PhysicalConnectivityClient.screenMaterial)
|
emitter.material(PhysicalConnectivityClient.screenMaterial)
|
||||||
emitter.square(facing, 3/16f, 2/16f, 13/16f, 3/16f, 0f)
|
emitter.square(facing, 3/16f, 2/16f, 13/16f, 3/16f, 0f)
|
||||||
emitter.spriteBake(0, terminalSprite, MutableQuadView.BAKE_LOCK_UV)
|
emitter.spriteBake(0, screenSprite, MutableQuadView.BAKE_LOCK_UV)
|
||||||
emitter.spriteColor(0, -1, -1, -1, -1)
|
emitter.spriteColor(0, -1, -1, -1, -1)
|
||||||
emitter.emit()
|
emitter.emit()
|
||||||
|
|
||||||
emitter.material(PhysicalConnectivityClient.screenMaterial)
|
emitter.material(PhysicalConnectivityClient.screenMaterial)
|
||||||
emitter.square(facing, 2/16f, 3/16f, 14/16f, 13/16f, 0f)
|
emitter.square(facing, 2/16f, 3/16f, 14/16f, 13/16f, 0f)
|
||||||
emitter.spriteBake(0, terminalSprite, MutableQuadView.BAKE_LOCK_UV)
|
emitter.spriteBake(0, screenSprite, MutableQuadView.BAKE_LOCK_UV)
|
||||||
emitter.spriteColor(0, -1, -1, -1, -1)
|
emitter.spriteColor(0, -1, -1, -1, -1)
|
||||||
emitter.emit()
|
emitter.emit()
|
||||||
|
|
||||||
emitter.material(PhysicalConnectivityClient.screenMaterial)
|
emitter.material(PhysicalConnectivityClient.screenMaterial)
|
||||||
emitter.square(facing, 3/16f, 13/16f, 13/16f, 14/16f, 0f)
|
emitter.square(facing, 3/16f, 13/16f, 13/16f, 14/16f, 0f)
|
||||||
emitter.spriteBake(0, terminalSprite, MutableQuadView.BAKE_LOCK_UV)
|
emitter.spriteBake(0, screenSprite, MutableQuadView.BAKE_LOCK_UV)
|
||||||
emitter.spriteColor(0, -1, -1, -1, -1)
|
emitter.spriteColor(0, -1, -1, -1, -1)
|
||||||
emitter.emit()
|
emitter.emit()
|
||||||
} else {
|
} else {
|
||||||
|
@ -112,7 +154,7 @@ object TerminalModel: UnbakedModel, BakedModel, FabricBakedModel {
|
||||||
randomSupplier: Supplier<Random>,
|
randomSupplier: Supplier<Random>,
|
||||||
context: RenderContext
|
context: RenderContext
|
||||||
) {
|
) {
|
||||||
val mesh = meshes[state[TerminalBlock.FACING].ordinal]
|
val mesh = meshes[state[AbstractTerminalBlock.FACING].ordinal]
|
||||||
context.meshConsumer().accept(mesh)
|
context.meshConsumer().accept(mesh)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,7 +171,7 @@ object TerminalModel: UnbakedModel, BakedModel, FabricBakedModel {
|
||||||
|
|
||||||
override fun isBuiltin() = false
|
override fun isBuiltin() = false
|
||||||
|
|
||||||
override fun getSprite() = terminalSprite
|
override fun getSprite() = screenSprite
|
||||||
|
|
||||||
override fun getTransformation() = null
|
override fun getTransformation() = null
|
||||||
|
|
|
@ -19,6 +19,8 @@ import net.shadowfacts.phycon.block.redstone_controller.RedstoneControllerBlock
|
||||||
import net.shadowfacts.phycon.block.redstone_controller.RedstoneControllerBlockEntity
|
import net.shadowfacts.phycon.block.redstone_controller.RedstoneControllerBlockEntity
|
||||||
import net.shadowfacts.phycon.block.redstone_emitter.RedstoneEmitterBlock
|
import net.shadowfacts.phycon.block.redstone_emitter.RedstoneEmitterBlock
|
||||||
import net.shadowfacts.phycon.block.redstone_emitter.RedstoneEmitterBlockEntity
|
import net.shadowfacts.phycon.block.redstone_emitter.RedstoneEmitterBlockEntity
|
||||||
|
import net.shadowfacts.phycon.block.terminal.CraftingTerminalBlock
|
||||||
|
import net.shadowfacts.phycon.block.terminal.CraftingTerminalBlockEntity
|
||||||
import net.shadowfacts.phycon.block.terminal.TerminalBlock
|
import net.shadowfacts.phycon.block.terminal.TerminalBlock
|
||||||
import net.shadowfacts.phycon.block.terminal.TerminalBlockEntity
|
import net.shadowfacts.phycon.block.terminal.TerminalBlockEntity
|
||||||
|
|
||||||
|
@ -29,6 +31,7 @@ object PhyBlockEntities {
|
||||||
|
|
||||||
val INTERFACE = create(::InterfaceBlockEntity, PhyBlocks.INTERFACE)
|
val INTERFACE = create(::InterfaceBlockEntity, PhyBlocks.INTERFACE)
|
||||||
val TERMINAL = create(::TerminalBlockEntity, PhyBlocks.TERMINAL)
|
val TERMINAL = create(::TerminalBlockEntity, PhyBlocks.TERMINAL)
|
||||||
|
val CRAFTING_TERMINAL = create(::CraftingTerminalBlockEntity, PhyBlocks.CRAFTING_TERMINAL)
|
||||||
val SWITCH = create(::SwitchBlockEntity, PhyBlocks.SWITCH)
|
val SWITCH = create(::SwitchBlockEntity, PhyBlocks.SWITCH)
|
||||||
val EXTRACTOR = create(::ExtractorBlockEntity, PhyBlocks.EXTRACTOR)
|
val EXTRACTOR = create(::ExtractorBlockEntity, PhyBlocks.EXTRACTOR)
|
||||||
val INSERTER = create(::InserterBlockEntity, PhyBlocks.INSERTER)
|
val INSERTER = create(::InserterBlockEntity, PhyBlocks.INSERTER)
|
||||||
|
@ -43,6 +46,7 @@ object PhyBlockEntities {
|
||||||
fun init() {
|
fun init() {
|
||||||
register(InterfaceBlock.ID, INTERFACE)
|
register(InterfaceBlock.ID, INTERFACE)
|
||||||
register(TerminalBlock.ID, TERMINAL)
|
register(TerminalBlock.ID, TERMINAL)
|
||||||
|
register(CraftingTerminalBlock.ID, CRAFTING_TERMINAL)
|
||||||
register(SwitchBlock.ID, SWITCH)
|
register(SwitchBlock.ID, SWITCH)
|
||||||
register(ExtractorBlock.ID, EXTRACTOR)
|
register(ExtractorBlock.ID, EXTRACTOR)
|
||||||
register(InserterBlock.ID, INSERTER)
|
register(InserterBlock.ID, INSERTER)
|
||||||
|
|
|
@ -13,6 +13,7 @@ import net.shadowfacts.phycon.block.netinterface.InterfaceBlock
|
||||||
import net.shadowfacts.phycon.block.netswitch.SwitchBlock
|
import net.shadowfacts.phycon.block.netswitch.SwitchBlock
|
||||||
import net.shadowfacts.phycon.block.redstone_controller.RedstoneControllerBlock
|
import net.shadowfacts.phycon.block.redstone_controller.RedstoneControllerBlock
|
||||||
import net.shadowfacts.phycon.block.redstone_emitter.RedstoneEmitterBlock
|
import net.shadowfacts.phycon.block.redstone_emitter.RedstoneEmitterBlock
|
||||||
|
import net.shadowfacts.phycon.block.terminal.CraftingTerminalBlock
|
||||||
import net.shadowfacts.phycon.block.terminal.TerminalBlock
|
import net.shadowfacts.phycon.block.terminal.TerminalBlock
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -24,6 +25,7 @@ object PhyBlocks {
|
||||||
|
|
||||||
val INTERFACE = InterfaceBlock()
|
val INTERFACE = InterfaceBlock()
|
||||||
val TERMINAL = TerminalBlock()
|
val TERMINAL = TerminalBlock()
|
||||||
|
val CRAFTING_TERMINAL = CraftingTerminalBlock()
|
||||||
val SWITCH = SwitchBlock()
|
val SWITCH = SwitchBlock()
|
||||||
val EXTRACTOR = ExtractorBlock()
|
val EXTRACTOR = ExtractorBlock()
|
||||||
val INSERTER = InserterBlock()
|
val INSERTER = InserterBlock()
|
||||||
|
@ -38,6 +40,7 @@ object PhyBlocks {
|
||||||
|
|
||||||
register(InterfaceBlock.ID, INTERFACE)
|
register(InterfaceBlock.ID, INTERFACE)
|
||||||
register(TerminalBlock.ID, TERMINAL)
|
register(TerminalBlock.ID, TERMINAL)
|
||||||
|
register(CraftingTerminalBlock.ID, CRAFTING_TERMINAL)
|
||||||
register(SwitchBlock.ID, SWITCH)
|
register(SwitchBlock.ID, SWITCH)
|
||||||
register(ExtractorBlock.ID, EXTRACTOR)
|
register(ExtractorBlock.ID, EXTRACTOR)
|
||||||
register(InserterBlock.ID, INSERTER)
|
register(InserterBlock.ID, INSERTER)
|
||||||
|
|
|
@ -7,7 +7,6 @@ import net.minecraft.util.registry.Registry
|
||||||
import net.shadowfacts.phycon.PhysicalConnectivity
|
import net.shadowfacts.phycon.PhysicalConnectivity
|
||||||
import net.shadowfacts.phycon.item.ConsoleItem
|
import net.shadowfacts.phycon.item.ConsoleItem
|
||||||
import net.shadowfacts.phycon.item.ScrewdriverItem
|
import net.shadowfacts.phycon.item.ScrewdriverItem
|
||||||
import net.shadowfacts.phycon.block.cable.CableBlock
|
|
||||||
import net.shadowfacts.phycon.block.extractor.ExtractorBlock
|
import net.shadowfacts.phycon.block.extractor.ExtractorBlock
|
||||||
import net.shadowfacts.phycon.block.inserter.InserterBlock
|
import net.shadowfacts.phycon.block.inserter.InserterBlock
|
||||||
import net.shadowfacts.phycon.block.miner.MinerBlock
|
import net.shadowfacts.phycon.block.miner.MinerBlock
|
||||||
|
@ -15,6 +14,7 @@ import net.shadowfacts.phycon.block.netinterface.InterfaceBlock
|
||||||
import net.shadowfacts.phycon.block.netswitch.SwitchBlock
|
import net.shadowfacts.phycon.block.netswitch.SwitchBlock
|
||||||
import net.shadowfacts.phycon.block.redstone_controller.RedstoneControllerBlock
|
import net.shadowfacts.phycon.block.redstone_controller.RedstoneControllerBlock
|
||||||
import net.shadowfacts.phycon.block.redstone_emitter.RedstoneEmitterBlock
|
import net.shadowfacts.phycon.block.redstone_emitter.RedstoneEmitterBlock
|
||||||
|
import net.shadowfacts.phycon.block.terminal.CraftingTerminalBlock
|
||||||
import net.shadowfacts.phycon.block.terminal.TerminalBlock
|
import net.shadowfacts.phycon.block.terminal.TerminalBlock
|
||||||
import net.shadowfacts.phycon.item.DeviceBlockItem
|
import net.shadowfacts.phycon.item.DeviceBlockItem
|
||||||
import net.shadowfacts.phycon.item.FaceDeviceBlockItem
|
import net.shadowfacts.phycon.item.FaceDeviceBlockItem
|
||||||
|
@ -30,6 +30,7 @@ object PhyItems {
|
||||||
|
|
||||||
val INTERFACE = FaceDeviceBlockItem(PhyBlocks.INTERFACE, Item.Settings())
|
val INTERFACE = FaceDeviceBlockItem(PhyBlocks.INTERFACE, Item.Settings())
|
||||||
val TERMINAL = DeviceBlockItem(PhyBlocks.TERMINAL, Item.Settings())
|
val TERMINAL = DeviceBlockItem(PhyBlocks.TERMINAL, Item.Settings())
|
||||||
|
val CRAFTING_TERMINAL = DeviceBlockItem(PhyBlocks.CRAFTING_TERMINAL, Item.Settings())
|
||||||
val SWITCH = BlockItem(PhyBlocks.SWITCH, Item.Settings())
|
val SWITCH = BlockItem(PhyBlocks.SWITCH, Item.Settings())
|
||||||
val EXTRACTOR = FaceDeviceBlockItem(PhyBlocks.EXTRACTOR, Item.Settings())
|
val EXTRACTOR = FaceDeviceBlockItem(PhyBlocks.EXTRACTOR, Item.Settings())
|
||||||
val INSERTER = FaceDeviceBlockItem(PhyBlocks.INSERTER, Item.Settings())
|
val INSERTER = FaceDeviceBlockItem(PhyBlocks.INSERTER, Item.Settings())
|
||||||
|
@ -53,6 +54,7 @@ object PhyItems {
|
||||||
|
|
||||||
register(InterfaceBlock.ID, INTERFACE)
|
register(InterfaceBlock.ID, INTERFACE)
|
||||||
register(TerminalBlock.ID, TERMINAL)
|
register(TerminalBlock.ID, TERMINAL)
|
||||||
|
register(CraftingTerminalBlock.ID, CRAFTING_TERMINAL)
|
||||||
register(SwitchBlock.ID, SWITCH)
|
register(SwitchBlock.ID, SWITCH)
|
||||||
register(ExtractorBlock.ID, EXTRACTOR)
|
register(ExtractorBlock.ID, EXTRACTOR)
|
||||||
register(InserterBlock.ID, INSERTER)
|
register(InserterBlock.ID, INSERTER)
|
||||||
|
|
|
@ -4,19 +4,25 @@ import net.fabricmc.fabric.api.screenhandler.v1.ScreenHandlerRegistry
|
||||||
import net.minecraft.screen.ScreenHandlerType
|
import net.minecraft.screen.ScreenHandlerType
|
||||||
import net.shadowfacts.phycon.block.inserter.InserterScreenHandler
|
import net.shadowfacts.phycon.block.inserter.InserterScreenHandler
|
||||||
import net.shadowfacts.phycon.block.redstone_emitter.RedstoneEmitterScreenHandler
|
import net.shadowfacts.phycon.block.redstone_emitter.RedstoneEmitterScreenHandler
|
||||||
|
import net.shadowfacts.phycon.block.terminal.CraftingTerminalBlock
|
||||||
|
import net.shadowfacts.phycon.block.terminal.CraftingTerminalScreenHandler
|
||||||
|
import net.shadowfacts.phycon.block.terminal.TerminalBlock
|
||||||
import net.shadowfacts.phycon.block.terminal.TerminalScreenHandler
|
import net.shadowfacts.phycon.block.terminal.TerminalScreenHandler
|
||||||
|
|
||||||
object PhyScreens {
|
object PhyScreens {
|
||||||
|
|
||||||
lateinit var TERMINAL: ScreenHandlerType<TerminalScreenHandler>
|
lateinit var TERMINAL: ScreenHandlerType<TerminalScreenHandler>
|
||||||
private set
|
private set
|
||||||
|
lateinit var CRAFTING_TERMINAL: ScreenHandlerType<CraftingTerminalScreenHandler>
|
||||||
|
private set
|
||||||
lateinit var INSERTER: ScreenHandlerType<InserterScreenHandler>
|
lateinit var INSERTER: ScreenHandlerType<InserterScreenHandler>
|
||||||
private set
|
private set
|
||||||
lateinit var REDSTONE_EMITTER: ScreenHandlerType<RedstoneEmitterScreenHandler>
|
lateinit var REDSTONE_EMITTER: ScreenHandlerType<RedstoneEmitterScreenHandler>
|
||||||
private set
|
private set
|
||||||
|
|
||||||
fun init() {
|
fun init() {
|
||||||
TERMINAL = ScreenHandlerRegistry.registerExtended(TerminalScreenHandler.ID, ::TerminalScreenHandler)
|
TERMINAL = ScreenHandlerRegistry.registerExtended(TerminalBlock.ID, ::TerminalScreenHandler)
|
||||||
|
CRAFTING_TERMINAL = ScreenHandlerRegistry.registerExtended(CraftingTerminalBlock.ID, ::CraftingTerminalScreenHandler)
|
||||||
INSERTER = ScreenHandlerRegistry.registerExtended(InserterScreenHandler.ID, ::InserterScreenHandler)
|
INSERTER = ScreenHandlerRegistry.registerExtended(InserterScreenHandler.ID, ::InserterScreenHandler)
|
||||||
REDSTONE_EMITTER = ScreenHandlerRegistry.registerExtended(RedstoneEmitterScreenHandler.ID, ::RedstoneEmitterScreenHandler)
|
REDSTONE_EMITTER = ScreenHandlerRegistry.registerExtended(RedstoneEmitterScreenHandler.ID, ::RedstoneEmitterScreenHandler)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
package net.shadowfacts.phycon.networking
|
||||||
|
|
||||||
|
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs
|
||||||
|
import net.fabricmc.fabric.api.networking.v1.PacketSender
|
||||||
|
import net.minecraft.network.Packet
|
||||||
|
import net.minecraft.network.PacketByteBuf
|
||||||
|
import net.minecraft.server.MinecraftServer
|
||||||
|
import net.minecraft.server.network.ServerPlayNetworkHandler
|
||||||
|
import net.minecraft.server.network.ServerPlayerEntity
|
||||||
|
import net.minecraft.util.Identifier
|
||||||
|
import net.minecraft.util.registry.Registry
|
||||||
|
import net.minecraft.util.registry.RegistryKey
|
||||||
|
import net.shadowfacts.phycon.PhysicalConnectivity
|
||||||
|
import net.shadowfacts.phycon.block.terminal.CraftingTerminalBlockEntity
|
||||||
|
import net.shadowfacts.phycon.block.terminal.CraftingTerminalScreenHandler
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author shadowfacts
|
||||||
|
*/
|
||||||
|
object C2STerminalCraftingButton: ServerReceiver {
|
||||||
|
override val CHANNEL = Identifier(PhysicalConnectivity.MODID, "terminal_crafting_button")
|
||||||
|
|
||||||
|
enum class Action {
|
||||||
|
CLEAR_GRID,
|
||||||
|
REQUEST_ONE_MORE,
|
||||||
|
REQUEST_MAX_MORE,
|
||||||
|
}
|
||||||
|
|
||||||
|
operator fun invoke(terminal: CraftingTerminalBlockEntity, action: Action): Packet<*> {
|
||||||
|
val buf = PacketByteBufs.create()
|
||||||
|
|
||||||
|
buf.writeIdentifier(terminal.world!!.registryKey.value)
|
||||||
|
buf.writeBlockPos(terminal.pos)
|
||||||
|
buf.writeByte(action.ordinal)
|
||||||
|
|
||||||
|
return createPacket(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun receive(server: MinecraftServer, player: ServerPlayerEntity, handler: ServerPlayNetworkHandler, buf: PacketByteBuf, responseSender: PacketSender) {
|
||||||
|
val dimID = buf.readIdentifier()
|
||||||
|
val pos = buf.readBlockPos()
|
||||||
|
val action = Action.values()[buf.readByte().toInt()]
|
||||||
|
|
||||||
|
server.execute {
|
||||||
|
val key = RegistryKey.of(Registry.DIMENSION, dimID)
|
||||||
|
val screenHandler = player.currentScreenHandler as? CraftingTerminalScreenHandler ?: return@execute
|
||||||
|
if (screenHandler.terminal.pos != pos || screenHandler.terminal.world!!.registryKey != key) return@execute
|
||||||
|
|
||||||
|
when (action) {
|
||||||
|
Action.CLEAR_GRID -> screenHandler.clearCraftingGrid()
|
||||||
|
Action.REQUEST_ONE_MORE -> screenHandler.requestMoreCraftingIngredients(1)
|
||||||
|
Action.REQUEST_MAX_MORE -> screenHandler.requestMoreCraftingIngredients(64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,8 +13,7 @@ import net.minecraft.util.Identifier
|
||||||
import net.minecraft.util.registry.Registry
|
import net.minecraft.util.registry.Registry
|
||||||
import net.minecraft.util.registry.RegistryKey
|
import net.minecraft.util.registry.RegistryKey
|
||||||
import net.shadowfacts.phycon.PhysicalConnectivity
|
import net.shadowfacts.phycon.PhysicalConnectivity
|
||||||
import net.shadowfacts.phycon.block.terminal.TerminalBlockEntity
|
import net.shadowfacts.phycon.block.terminal.AbstractTerminalBlockEntity
|
||||||
import net.shadowfacts.phycon.util.copyWithCount
|
|
||||||
import net.shadowfacts.phycon.util.readItemStackWithoutCount
|
import net.shadowfacts.phycon.util.readItemStackWithoutCount
|
||||||
import net.shadowfacts.phycon.util.writeItemStackWithoutCount
|
import net.shadowfacts.phycon.util.writeItemStackWithoutCount
|
||||||
|
|
||||||
|
@ -25,7 +24,7 @@ object C2STerminalRequestItem: ServerReceiver {
|
||||||
|
|
||||||
override val CHANNEL = Identifier(PhysicalConnectivity.MODID, "terminal_request_item")
|
override val CHANNEL = Identifier(PhysicalConnectivity.MODID, "terminal_request_item")
|
||||||
|
|
||||||
operator fun invoke(terminal: TerminalBlockEntity, stack: ItemStack, amount: Int): Packet<*> {
|
operator fun invoke(terminal: AbstractTerminalBlockEntity, stack: ItemStack, amount: Int): Packet<*> {
|
||||||
val buf = PacketByteBufs.create()
|
val buf = PacketByteBufs.create()
|
||||||
buf.writeIdentifier(terminal.world!!.registryKey.value)
|
buf.writeIdentifier(terminal.world!!.registryKey.value)
|
||||||
buf.writeBlockPos(terminal.pos)
|
buf.writeBlockPos(terminal.pos)
|
||||||
|
@ -48,7 +47,7 @@ object C2STerminalRequestItem: ServerReceiver {
|
||||||
server.execute {
|
server.execute {
|
||||||
val key = RegistryKey.of(Registry.DIMENSION, dimID)
|
val key = RegistryKey.of(Registry.DIMENSION, dimID)
|
||||||
val world = server.getWorld(key) ?: return@execute
|
val world = server.getWorld(key) ?: return@execute
|
||||||
val terminal = world.getBlockEntity(pos) as? TerminalBlockEntity ?: return@execute
|
val terminal = world.getBlockEntity(pos) as? AbstractTerminalBlockEntity ?: return@execute
|
||||||
terminal.requestItem(stack, amount)
|
terminal.requestItem(stack, amount)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,9 +11,8 @@ import net.minecraft.server.network.ServerPlayerEntity
|
||||||
import net.minecraft.util.Identifier
|
import net.minecraft.util.Identifier
|
||||||
import net.shadowfacts.phycon.PhysicalConnectivity
|
import net.shadowfacts.phycon.PhysicalConnectivity
|
||||||
import net.shadowfacts.phycon.PhysicalConnectivityClient
|
import net.shadowfacts.phycon.PhysicalConnectivityClient
|
||||||
import net.shadowfacts.phycon.block.terminal.TerminalBlockEntity
|
import net.shadowfacts.phycon.block.terminal.AbstractTerminalBlockEntity
|
||||||
import net.shadowfacts.phycon.block.terminal.TerminalScreenHandler
|
import net.shadowfacts.phycon.block.terminal.AbstractTerminalScreenHandler
|
||||||
import net.shadowfacts.phycon.util.SortMode
|
|
||||||
import net.shadowfacts.phycon.util.TerminalSettings
|
import net.shadowfacts.phycon.util.TerminalSettings
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -23,7 +22,7 @@ object C2STerminalUpdateDisplayedItems: ServerReceiver {
|
||||||
|
|
||||||
override val CHANNEL = Identifier(PhysicalConnectivity.MODID, "terminal_update_displayed")
|
override val CHANNEL = Identifier(PhysicalConnectivity.MODID, "terminal_update_displayed")
|
||||||
|
|
||||||
operator fun invoke(terminal: TerminalBlockEntity, query: String, scrollPosition: Float): Packet<*> {
|
operator fun invoke(terminal: AbstractTerminalBlockEntity, query: String, scrollPosition: Float): Packet<*> {
|
||||||
val buf = PacketByteBufs.create()
|
val buf = PacketByteBufs.create()
|
||||||
|
|
||||||
buf.writeIdentifier(terminal.world!!.registryKey.value)
|
buf.writeIdentifier(terminal.world!!.registryKey.value)
|
||||||
|
@ -47,7 +46,7 @@ object C2STerminalUpdateDisplayedItems: ServerReceiver {
|
||||||
server.execute {
|
server.execute {
|
||||||
if (player.world.registryKey.value != dimID) return@execute
|
if (player.world.registryKey.value != dimID) return@execute
|
||||||
val screenHandler = player.currentScreenHandler
|
val screenHandler = player.currentScreenHandler
|
||||||
if (screenHandler !is TerminalScreenHandler) return@execute
|
if (screenHandler !is AbstractTerminalScreenHandler<*>) return@execute
|
||||||
if (screenHandler.terminal.pos != pos) return@execute
|
if (screenHandler.terminal.pos != pos) return@execute
|
||||||
screenHandler.sendUpdatedItemsToClient(player, query, settings, scrollPosition)
|
screenHandler.sendUpdatedItemsToClient(player, query, settings, scrollPosition)
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,9 +8,8 @@ import net.minecraft.client.network.ClientPlayNetworkHandler
|
||||||
import net.minecraft.network.Packet
|
import net.minecraft.network.Packet
|
||||||
import net.minecraft.network.PacketByteBuf
|
import net.minecraft.network.PacketByteBuf
|
||||||
import net.shadowfacts.phycon.PhysicalConnectivityClient
|
import net.shadowfacts.phycon.PhysicalConnectivityClient
|
||||||
import net.shadowfacts.phycon.block.terminal.TerminalBlockEntity
|
import net.shadowfacts.phycon.block.terminal.AbstractTerminalBlockEntity
|
||||||
import net.shadowfacts.phycon.block.terminal.TerminalScreenHandler
|
import net.shadowfacts.phycon.block.terminal.AbstractTerminalScreenHandler
|
||||||
import net.shadowfacts.phycon.util.SortMode
|
|
||||||
import net.shadowfacts.phycon.util.TerminalSettings
|
import net.shadowfacts.phycon.util.TerminalSettings
|
||||||
import net.shadowfacts.phycon.util.readItemStackWithoutCount
|
import net.shadowfacts.phycon.util.readItemStackWithoutCount
|
||||||
import net.shadowfacts.phycon.util.writeItemStackWithoutCount
|
import net.shadowfacts.phycon.util.writeItemStackWithoutCount
|
||||||
|
@ -21,7 +20,7 @@ import net.shadowfacts.phycon.util.writeItemStackWithoutCount
|
||||||
object S2CTerminalUpdateDisplayedItems: ClientReceiver {
|
object S2CTerminalUpdateDisplayedItems: ClientReceiver {
|
||||||
override val CHANNEL = C2STerminalUpdateDisplayedItems.CHANNEL
|
override val CHANNEL = C2STerminalUpdateDisplayedItems.CHANNEL
|
||||||
|
|
||||||
operator fun invoke(terminal: TerminalBlockEntity, entries: List<TerminalScreenHandler.Entry>, query: String, settings: TerminalSettings, scrollPosition: Float, totalEntries: Int): Packet<*> {
|
operator fun invoke(terminal: AbstractTerminalBlockEntity, entries: List<AbstractTerminalScreenHandler.Entry>, query: String, settings: TerminalSettings, scrollPosition: Float, totalEntries: Int): Packet<*> {
|
||||||
val buf = PacketByteBufs.create()
|
val buf = PacketByteBufs.create()
|
||||||
|
|
||||||
buf.writeIdentifier(terminal.world!!.registryKey.value)
|
buf.writeIdentifier(terminal.world!!.registryKey.value)
|
||||||
|
@ -45,9 +44,9 @@ object S2CTerminalUpdateDisplayedItems: ClientReceiver {
|
||||||
val dimID = buf.readIdentifier()
|
val dimID = buf.readIdentifier()
|
||||||
val pos = buf.readBlockPos()
|
val pos = buf.readBlockPos()
|
||||||
val entryCount = buf.readVarInt()
|
val entryCount = buf.readVarInt()
|
||||||
val entries = ArrayList<TerminalScreenHandler.Entry>(entryCount)
|
val entries = ArrayList<AbstractTerminalScreenHandler.Entry>(entryCount)
|
||||||
for (i in 0 until entryCount) {
|
for (i in 0 until entryCount) {
|
||||||
entries.add(TerminalScreenHandler.Entry(buf.readItemStackWithoutCount(), buf.readVarInt()))
|
entries.add(AbstractTerminalScreenHandler.Entry(buf.readItemStackWithoutCount(), buf.readVarInt()))
|
||||||
}
|
}
|
||||||
val query = buf.readString()
|
val query = buf.readString()
|
||||||
PhysicalConnectivityClient.terminalSettings.fromTag(buf.readCompoundTag()!!)
|
PhysicalConnectivityClient.terminalSettings.fromTag(buf.readCompoundTag()!!)
|
||||||
|
@ -57,7 +56,7 @@ object S2CTerminalUpdateDisplayedItems: ClientReceiver {
|
||||||
client.execute {
|
client.execute {
|
||||||
if (client.player!!.world.registryKey.value != dimID) return@execute
|
if (client.player!!.world.registryKey.value != dimID) return@execute
|
||||||
val screenHandler = client.player!!.currentScreenHandler
|
val screenHandler = client.player!!.currentScreenHandler
|
||||||
if (screenHandler !is TerminalScreenHandler) return@execute
|
if (screenHandler !is AbstractTerminalScreenHandler<*>) return@execute
|
||||||
if (screenHandler.terminal.pos != pos) return@execute
|
if (screenHandler.terminal.pos != pos) return@execute
|
||||||
screenHandler.receivedUpdatedItemsFromServer(entries, query, scrollPosition, totalEntries)
|
screenHandler.receivedUpdatedItemsFromServer(entries, query, scrollPosition, totalEntries)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
{
|
||||||
|
"variants": {
|
||||||
|
"facing=down": {
|
||||||
|
"model": "phycon:block/crafting_terminal",
|
||||||
|
"x": 90
|
||||||
|
},
|
||||||
|
"facing=up": {
|
||||||
|
"model": "phycon:block/crafting_terminal",
|
||||||
|
"x": 270
|
||||||
|
},
|
||||||
|
"facing=north": {
|
||||||
|
"model": "phycon:block/crafting_terminal"
|
||||||
|
},
|
||||||
|
"facing=south": {
|
||||||
|
"model": "phycon:block/crafting_terminal",
|
||||||
|
"y": 180
|
||||||
|
},
|
||||||
|
"facing=west": {
|
||||||
|
"model": "phycon:block/crafting_terminal",
|
||||||
|
"y": 270
|
||||||
|
},
|
||||||
|
"facing=east": {
|
||||||
|
"model": "phycon:block/crafting_terminal",
|
||||||
|
"y": 90
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,7 @@
|
||||||
"block.phycon.switch": "Network Switch",
|
"block.phycon.switch": "Network Switch",
|
||||||
"block.phycon.network_interface": "Inventory Interface",
|
"block.phycon.network_interface": "Inventory Interface",
|
||||||
"block.phycon.terminal": "Terminal",
|
"block.phycon.terminal": "Terminal",
|
||||||
|
"block.phycon.crafting_terminal": "Crafting Terminal",
|
||||||
"block.phycon.cable_white": "White Cable",
|
"block.phycon.cable_white": "White Cable",
|
||||||
"block.phycon.cable_orange": "Orange Cable",
|
"block.phycon.cable_orange": "Orange Cable",
|
||||||
"block.phycon.cable_magenta": "Magenta Cable",
|
"block.phycon.cable_magenta": "Magenta Cable",
|
||||||
|
@ -34,6 +35,10 @@
|
||||||
"item.phycon.redstone_processor": "Redstone Processor",
|
"item.phycon.redstone_processor": "Redstone Processor",
|
||||||
|
|
||||||
"gui.phycon.terminal_buffer": "Buffer",
|
"gui.phycon.terminal_buffer": "Buffer",
|
||||||
|
"gui.phycon.terminal_network": "Network",
|
||||||
|
"gui.phycon.terminal_crafting": "Crafting",
|
||||||
|
"gui.phycon.terminal.clear_crafting": "Clear crafting table",
|
||||||
|
"gui.phycon.terminal.more_crafting": "Request more ingredients",
|
||||||
"gui.phycon.console.details": "Device Details",
|
"gui.phycon.console.details": "Device Details",
|
||||||
"gui.phycon.console.details.ip": "IP Address: %s",
|
"gui.phycon.console.details.ip": "IP Address: %s",
|
||||||
"gui.phycon.console.details.mac": "MAC Address: %s",
|
"gui.phycon.console.details.mac": "MAC Address: %s",
|
||||||
|
@ -75,6 +80,8 @@
|
||||||
"advancements.phycon.interface.description": "Place a Network Interface on a Cable to connect to a Chest",
|
"advancements.phycon.interface.description": "Place a Network Interface on a Cable to connect to a Chest",
|
||||||
"advancements.phycon.terminal.title": "Spooky Action",
|
"advancements.phycon.terminal.title": "Spooky Action",
|
||||||
"advancements.phycon.terminal.description": "Use a Terminal to interact with a Chest",
|
"advancements.phycon.terminal.description": "Use a Terminal to interact with a Chest",
|
||||||
|
"advancements.phycon.crafting_terminal.title": "Multi-Tool",
|
||||||
|
"advancements.phycon.crafting_terminal.description": "Construct a Crafting Terminal",
|
||||||
"advancements.phycon.switch.title": "Interchange",
|
"advancements.phycon.switch.title": "Interchange",
|
||||||
"advancements.phycon.switch.description": "Connect multiple devices with a Network Switch",
|
"advancements.phycon.switch.description": "Connect multiple devices with a Network Switch",
|
||||||
"advancements.phycon.console.title": "Console",
|
"advancements.phycon.console.title": "Console",
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"parent": "block/cube",
|
||||||
|
"textures": {
|
||||||
|
"side": "phycon:block/casing",
|
||||||
|
"front": "phycon:block/crafting_terminal",
|
||||||
|
"particle": "#side",
|
||||||
|
"down": "#side",
|
||||||
|
"up": "#side",
|
||||||
|
"north": "#front",
|
||||||
|
"south": "#side",
|
||||||
|
"west": "#side",
|
||||||
|
"east": "#side"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"parent": "block/cube",
|
||||||
|
"textures": {
|
||||||
|
"side": "phycon:block/casing",
|
||||||
|
"front": "phycon:block/crafting_terminal",
|
||||||
|
"down": "#side",
|
||||||
|
"up": "#side",
|
||||||
|
"north": "#side",
|
||||||
|
"south": "#side",
|
||||||
|
"west": "#side",
|
||||||
|
"east": "#front"
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 2.0 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.7 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
Binary file not shown.
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 3.5 KiB |
|
@ -0,0 +1,35 @@
|
||||||
|
{
|
||||||
|
"parent": "phycon:terminal",
|
||||||
|
"display": {
|
||||||
|
"icon": {
|
||||||
|
"item": "phycon:crafting_terminal"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"translate": "advancements.phycon.crafting_terminal.title"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"translate": "advancements.phycon.crafting_terminal.description"
|
||||||
|
},
|
||||||
|
"frame": "task",
|
||||||
|
"show_toast": true,
|
||||||
|
"announce_to_chat": true,
|
||||||
|
"hidden": false
|
||||||
|
},
|
||||||
|
"criteria": {
|
||||||
|
"crafting_terminal": {
|
||||||
|
"trigger": "minecraft:inventory_changed",
|
||||||
|
"conditions": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"item": "phycon:crafting_terminal"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"requirements": [
|
||||||
|
[
|
||||||
|
"crafting_terminal"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
{
|
||||||
|
"parent": "minecraft:recipes/root",
|
||||||
|
"rewards": {
|
||||||
|
"recipes": [
|
||||||
|
"phycon:crafting_terminal"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"criteria": {
|
||||||
|
"has_terminal": {
|
||||||
|
"trigger": "minecraft:inventory_changed",
|
||||||
|
"conditions": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"item": "phycon:terminal"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"has_the_recipe": {
|
||||||
|
"trigger": "minecraft:recipe_unlocked",
|
||||||
|
"conditions": {
|
||||||
|
"recipe": "phycon:crafting_terminal"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"requirements": [
|
||||||
|
[
|
||||||
|
"has_terminal",
|
||||||
|
"has_the_recipe"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"type": "minecraft:crafting_shapeless",
|
||||||
|
"ingredients": [
|
||||||
|
{
|
||||||
|
"item": "phycon:terminal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item": "phycon:item_processor"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item": "minecraft:crafting_table"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"result": {
|
||||||
|
"item": "phycon:crafting_terminal"
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue