Merge branch '1.21' into worldgen-placement-providers

This commit is contained in:
DBotThePony 2025-03-07 21:31:07 +07:00
commit 0ef34a00cc
Signed by: DBot
GPG Key ID: DCC23B5715498507
37 changed files with 340 additions and 151 deletions

View File

@ -982,6 +982,15 @@ private fun androidFeatures(provider: MatteryLanguageProvider) {
private fun gui(provider: MatteryLanguageProvider) {
with(provider.english) {
gui("exopack.accept_wireless_charge", "Accept wireless charging")
gui("exopack.dont_accept_wireless_charge", "Do not accept wireless charging")
gui("charge_androids", "Charge Androids")
gui("dont_charge_androids", "Do not charge Androids")
gui("charge_exopacks", "Charge Exopacks")
gui("dont_charge_exopacks", "Do not charge Exopacks")
gui("multiblock.formed", "Multiblock is formed: %s")
gui("flywheel.current_loss_t", "Current energy loss per tick:")

View File

@ -975,6 +975,15 @@ private fun androidFeatures(provider: MatteryLanguageProvider) {
private fun gui(provider: MatteryLanguageProvider) {
with(provider.russian) {
gui("exopack.accept_wireless_charge", "Принимать заряд от беспроводных зарядников")
gui("exopack.dont_accept_wireless_charge", "Не принимать заряд от беспроводных зарядников")
gui("charge_androids", "Заряжать андроидов")
gui("dont_charge_androids", "Не заряжать андроидов")
gui("charge_exopacks", "Заряжать экзопаки")
gui("dont_charge_exopacks", "Не заряжать экзопаки")
gui("multiblock.formed", "Мультиблок сформирован: %s")
gui("flywheel.current_loss_t", "Текущая потеря энергии в тик:")

View File

@ -78,7 +78,7 @@ public class LoaderModel {
this.animate(entity.getIdleState(), LoaderAnimation.ATTACK, ageInTicks, 1.0F);
}
this.animateWalk(LoaderAnimation.MOVE, limbSwing, limbSwingAmount, 1.0F, 2.5F);
this.animateWalk(LoaderAnimation.MOVE, limbSwing, limbSwingAmount, 0.8F, 2.5F);
this.animate(entity.getIdleState(), LoaderAnimation.IDLE, ageInTicks, 1.0F);
}

View File

@ -7,6 +7,7 @@ import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.NbtOps
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.ComponentSerialization
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.MenuProvider
import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.entity.player.Player
@ -83,9 +84,11 @@ abstract class MatteryDeviceBlockEntity(blockEntityType: BlockEntityType<*>, blo
override fun setLevel(level: Level) {
super.setLevel(level)
level.once {
if (!isRemoved && this.level == level) {
redstoneControl.redstoneSignal = level.getBestNeighborSignal(blockPos)
if (level is ServerLevel) {
level.once(4) {
if (!isRemoved && this.level == level) {
redstoneControl.redstoneSignal = level.getBestNeighborSignal(blockPos)
}
}
}
}

View File

@ -165,7 +165,7 @@ abstract class EnergyCableBlockEntity(type: BlockEntityType<*>, blockPos: BlockP
if (!SERVER_IS_LIVE) return
val level = level
level?.once {
level?.once(4) {
if (!node.isValid) return@once
val newState = blockState

View File

@ -3,6 +3,7 @@ package ru.dbotthepony.mc.otm.block.entity.cable
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap
import it.unimi.dsi.fastutil.objects.ReferenceArraySet
import it.unimi.dsi.fastutil.objects.ReferenceLinkedOpenHashSet
import net.minecraft.core.Direction
import org.apache.logging.log4j.LogManager
import ru.dbotthepony.mc.otm.SERVER_IS_LIVE
import ru.dbotthepony.mc.otm.UNIVERSE_TICKS
@ -74,16 +75,20 @@ private class LinkedPriorityQueue<T : Comparable<T>> {
}
class EnergyCableGraph : GraphNodeList<EnergyCableBlockEntity.Node, EnergyCableGraph>() {
private val livelyNodes = HashSet<EnergyCableBlockEntity.Node>()
private val livelyNodesList = ArrayList<EnergyCableBlockEntity.Node>()
private val livelyNodes = HashSet<Pair<EnergyCableBlockEntity.Node, RelativeSide>>()
private val livelyNodesList = ArrayList<Pair<EnergyCableBlockEntity.Node, RelativeSide>>()
fun addLivelyNode(node: EnergyCableBlockEntity.Node) {
when (contains(node)) {
ContainsStatus.ABOUT_TO_BE_REMOVED, ContainsStatus.ABOUT_TO_BE_ADDED -> { } // do nothing
ContainsStatus.DOES_NOT_BELONG -> throw IllegalArgumentException("$node does not belong to $this")
ContainsStatus.CONTAINS -> {
if (livelyNodes.add(node)) {
livelyNodesList.add(node)
for (dir in RelativeSide.entries) {
val pair = node to dir
if (livelyNodes.add(pair)) {
livelyNodesList.add(pair)
}
}
}
}
@ -584,8 +589,12 @@ class EnergyCableGraph : GraphNodeList<EnergyCableBlockEntity.Node, EnergyCableG
}
override fun onNodeRemoved(node: EnergyCableBlockEntity.Node) {
if (livelyNodes.remove(node)) {
check(livelyNodesList.remove(node))
for (dir in RelativeSide.entries) {
val pair = node to dir
if (livelyNodes.remove(pair)) {
check(livelyNodesList.remove(pair))
}
}
val touchedSegments = HashSet<Segment>()
@ -608,8 +617,11 @@ class EnergyCableGraph : GraphNodeList<EnergyCableBlockEntity.Node, EnergyCableG
}
override fun onNodeAdded(node: EnergyCableBlockEntity.Node) {
check(livelyNodes.add(node))
livelyNodesList.add(node)
for (dir in RelativeSide.entries) {
val pair = node to dir
check(livelyNodes.add(pair))
livelyNodesList.add(pair)
}
notifyThroughputsChanged()
}
@ -622,98 +634,97 @@ class EnergyCableGraph : GraphNodeList<EnergyCableBlockEntity.Node, EnergyCableG
var residue = howMuch.coerceAtMost(fromNode.energyThroughput)
val snapshot = Reference2ObjectOpenHashMap<Segment, Decimal>()
for (node in itr) {
var hit = false
for (pair in itr) {
val (node, relSide) = pair
val side = node.sides[relSide]!!
for (side in node.sides.values) {
if (!side.isEnabled)
continue
else if (fromNode === node && side.side === fromSide) {
hit = true
continue
}
val it = side.neighbour.get() ?: continue
if (it is EnergyCableBlockEntity.CableSide) continue
val paths = getPath(fromNode, node, it.receiveEnergy(residue, true), !simulate)
hit = true
if (paths.size == 1) {
// Single path, fast scenario
val path = paths[0]
val pathTransferred = path.transfer(residue, simulate, snapshot)
if (pathTransferred <= Decimal.ZERO) continue
val thisReceived = it.receiveEnergy(pathTransferred, simulate)
// If cable transferred more than machine accepted, then "refund" energy
// so cables record actual value transferred through them
if (thisReceived != pathTransferred) {
path.refund(pathTransferred - thisReceived, simulate, snapshot)
if (!simulate && thisReceived != Decimal.ZERO) {
path.triggerBlockstateUpdates()
}
} else if (!simulate) {
path.triggerBlockstateUpdates()
}
received += thisReceived
residue -= thisReceived
if (!residue.isPositive) return received
} else if (paths.size >= 2) {
// Multiple paths, a bit more complicated
// Determine how much machine is likely to accept
val potentiallyAccepted = it.receiveEnergy(residue, true)
// Won't accept anything
if (potentiallyAccepted <= Decimal.ZERO) continue
// Now determine combined available throughput
// Make a copy of snapshot, so we can freely write into it
val copy = snapshot.clone()
var calcResidue = potentiallyAccepted
// TODO: Currently, all transfers cause Braess's paradox, because of greedy selection of "fastest" cable
// Need to implement heuristics to better distribute load across different paths/segments
for (path in paths) {
val passed = path.transfer(calcResidue, true, copy)
calcResidue -= passed
if (calcResidue <= Decimal.ZERO) break
}
if (calcResidue == potentiallyAccepted) {
// мда
continue
}
var thisReceived = it.receiveEnergy(potentiallyAccepted - calcResidue, simulate)
received += thisReceived
residue -= thisReceived
for (path in paths) {
val passed = path.transfer(thisReceived, simulate, snapshot)
if (!simulate)
path.triggerBlockstateUpdates()
thisReceived -= passed
if (thisReceived <= Decimal.ZERO) break
}
if (!residue.isPositive) return received
//check(thisReceived <= Decimal.ZERO) { "Путом, алло, Путом, какого чёрта Путом? Путом почему ты заблокировал логику, а Путом?" }
if (thisReceived > Decimal.ZERO) {
LOGGER.warn("Cable path from $fromNode to $node doesn't follow common sense, disabling.")
paths.forEach { it.shortCircuit = true }
}
}
if (!side.isEnabled) {
itr.remove()
check(livelyNodes.remove(pair)) { "Lively nodes Set does not contain $pair" }
continue
} else if (fromNode === node && side.side === fromSide) {
continue
}
if (!hit) {
val it = side.neighbour.get()
if (it == null || it is EnergyCableBlockEntity.CableSide) {
itr.remove()
check(livelyNodes.remove(node)) { "Lively nodes Set does not contain $node" }
check(livelyNodes.remove(pair)) { "Lively nodes Set does not contain $pair" }
continue
}
val paths = getPath(fromNode, node, it.receiveEnergy(residue, true), !simulate)
if (paths.size == 1) {
// Single path, fast scenario
val path = paths[0]
val pathTransferred = path.transfer(residue, simulate, snapshot)
if (pathTransferred <= Decimal.ZERO) continue
val thisReceived = it.receiveEnergy(pathTransferred, simulate)
// If cable transferred more than machine accepted, then "refund" energy
// so cables record actual value transferred through them
if (thisReceived != pathTransferred) {
path.refund(pathTransferred - thisReceived, simulate, snapshot)
if (!simulate && thisReceived != Decimal.ZERO) {
path.triggerBlockstateUpdates()
}
} else if (!simulate) {
path.triggerBlockstateUpdates()
}
received += thisReceived
residue -= thisReceived
if (!residue.isPositive) return received
} else if (paths.size >= 2) {
// Multiple paths, a bit more complicated
// Determine how much machine is likely to accept
val potentiallyAccepted = it.receiveEnergy(residue, true)
// Won't accept anything
if (potentiallyAccepted <= Decimal.ZERO) continue
// Now determine combined available throughput
// Make a copy of snapshot, so we can freely write into it
val copy = snapshot.clone()
var calcResidue = potentiallyAccepted
// TODO: Currently, all transfers cause Braess's paradox, because of greedy selection of "fastest" cable
// Need to implement heuristics to better distribute load across different paths/segments
for (path in paths) {
val passed = path.transfer(calcResidue, true, copy)
calcResidue -= passed
if (calcResidue <= Decimal.ZERO) break
}
if (calcResidue == potentiallyAccepted) {
// мда
continue
}
var thisReceived = it.receiveEnergy(potentiallyAccepted - calcResidue, simulate)
received += thisReceived
residue -= thisReceived
for (path in paths) {
val passed = path.transfer(thisReceived, simulate, snapshot)
if (!simulate)
path.triggerBlockstateUpdates()
thisReceived -= passed
if (thisReceived <= Decimal.ZERO) break
}
if (!residue.isPositive) return received
//check(thisReceived <= Decimal.ZERO) { "Путом, алло, Путом, какого чёрта Путом? Путом почему ты заблокировал логику, а Путом?" }
if (thisReceived > Decimal.ZERO) {
LOGGER.warn("Cable path from $fromNode to $node doesn't follow common sense, disabling.")
paths.forEach { it.shortCircuit = true }
}
}
}

View File

@ -103,7 +103,7 @@ class CargoCrateBlockEntity(
super.loadAdditional(nbt, registry)
if (nbt.contains(LOOT_TABLE_KEY, Tag.TAG_STRING.toInt())) {
lootTable = ResourceKey.create(Registries.LOOT_TABLE, ResourceLocation.tryParse(nbt.getString(LOOT_TABLE_KEY)))
lootTable = ResourceLocation.tryParse(nbt.getString(LOOT_TABLE_KEY))?.let { ResourceKey.create(Registries.LOOT_TABLE, it) }
lootTableSeed = if (nbt.contains(LOOT_TABLE_SEED_KEY, Tag.TAG_LONG.toInt())) nbt.getLong(LOOT_TABLE_SEED_KEY) else 0L
}
}

View File

@ -119,7 +119,7 @@ class MatterScannerBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) :
var findState: PatternState? = null
for (state in matterNode.graph.patterns.filter { it.item === stack.item }) {
if (state.researchPercent < 1.0) {
if (state.researchPercent < 1.0 && findState == null) {
findState = state
} else if (state.researchPercent >= 1.0) {
return JobContainer.noItem()

View File

@ -30,14 +30,28 @@ class AndroidChargerBlockEntity(blockPos: BlockPos, blockState: BlockState) : Ma
override val energy = ProfiledEnergyStorage(WorkerEnergyStorage(this::markDirtyFast, MachinesConfig.ANDROID_CHARGER))
val energyConfig = ConfigurableEnergy(energy, modesTop = FlowDirection.NONE)
var chargeAndroids = true
set(value) {
field = value
markDirtyFast()
}
var chargeExopacks = true
set(value) {
field = value
markDirtyFast()
}
init {
savetables.stateful(energyConfig::energy, ENERGY_KEY)
savetablesConfig.bool(::chargeAndroids)
savetablesConfig.bool(::chargeExopacks)
}
override fun tick() {
super.tick()
if (redstoneControl.isBlockedByRedstone) return
if (redstoneControl.isBlockedByRedstone || !chargeAndroids && !chargeExopacks) return
val level = level ?: return
var available = energyConfig.energy.extractEnergy(energyConfig.energy.batteryLevel, true)
if (!available.isPositive) return
@ -49,14 +63,14 @@ class AndroidChargerBlockEntity(blockPos: BlockPos, blockState: BlockState) : Ma
for ((ent) in ents) {
val ply = (ent as Player).matteryPlayer
if (ply.isAndroid) {
if (chargeAndroids && ply.isAndroid) {
val received = ply.androidEnergy.receiveEnergyChecked(available, false)
available -= received
energyConfig.energy.extractEnergy(received, false)
if (!available.isPositive) return
}
if (ply.hasExopack) {
if (chargeExopacks && ply.hasExopack && ply.acceptExopackChargeFromWirelessCharger) {
val received = ply.exopackEnergy.receiveEnergyChecked(available, false)
available -= received
energyConfig.energy.extractEnergy(received, false)

View File

@ -17,21 +17,26 @@ import ru.dbotthepony.mc.otm.capability.matteryPlayer
import ru.dbotthepony.mc.otm.capability.moveEnergy
import ru.dbotthepony.mc.otm.config.MachinesConfig
import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.mc.otm.core.otmRandom
import ru.dbotthepony.mc.otm.core.shuffle
import ru.dbotthepony.mc.otm.core.util.countingLazy
import ru.dbotthepony.mc.otm.menu.tech.AndroidStationMenu
import ru.dbotthepony.mc.otm.registry.game.MBlockEntities
class AndroidStationBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) :
MatteryPoweredBlockEntity(MBlockEntities.ANDROID_STATION, p_155229_, p_155230_), MenuProvider {
class AndroidStationBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : MatteryPoweredBlockEntity(MBlockEntities.ANDROID_STATION, p_155229_, p_155230_), MenuProvider {
override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu {
return AndroidStationMenu(containerID, inventory, this)
}
private val workerState by countingLazy(blockStateChangesCounter) {
blockState.getValue(WorkerState.SEMI_WORKER_STATE)
}
override val energy: ProfiledEnergyStorage<WorkerEnergyStorage> = ProfiledEnergyStorage(object : WorkerEnergyStorage(::markDirtyFast, MachinesConfig.AndroidStation.VALUES) {
override fun extractEnergy(howMuch: Decimal, simulate: Boolean): Decimal {
return super.extractEnergy(howMuch, simulate).also {
if (!simulate && this.batteryLevel.isZero) {
if (blockState.getValue(WorkerState.SEMI_WORKER_STATE) != WorkerState.IDLE) {
if (workerState != WorkerState.IDLE) {
level?.setBlock(blockPos, blockState.setValue(WorkerState.SEMI_WORKER_STATE, WorkerState.IDLE), Block.UPDATE_CLIENTS)
}
}
@ -41,7 +46,7 @@ class AndroidStationBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) :
override fun receiveEnergy(howMuch: Decimal, simulate: Boolean): Decimal {
return super.receiveEnergy(howMuch, simulate).also {
if (!simulate && it.isPositive) {
if (blockState.getValue(WorkerState.SEMI_WORKER_STATE) != WorkerState.WORKING) {
if (workerState != WorkerState.WORKING) {
level?.setBlock(blockPos, blockState.setValue(
WorkerState.SEMI_WORKER_STATE,
WorkerState.WORKING
@ -53,9 +58,15 @@ class AndroidStationBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) :
})
val energyConfig = ConfigurableEnergy(energy)
var chargeAndroids = true
set(value) {
field = value
markDirtyFast()
}
init {
savetables.stateful(::energy, ENERGY_KEY)
savetablesConfig.bool(::chargeAndroids)
}
private var tickedOnce = false
@ -66,20 +77,20 @@ class AndroidStationBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) :
if (!tickedOnce) {
tickedOnce = true
if (energy.batteryLevel.isPositive && blockState.getValue(WorkerState.SEMI_WORKER_STATE) != WorkerState.WORKING) {
if (energy.batteryLevel.isPositive && workerState != WorkerState.WORKING) {
level?.setBlock(blockPos, blockState.setValue(WorkerState.SEMI_WORKER_STATE, WorkerState.WORKING), Block.UPDATE_CLIENTS)
} else if (energy.batteryLevel.isZero && blockState.getValue(WorkerState.SEMI_WORKER_STATE) != WorkerState.IDLE) {
} else if (!energy.batteryLevel.isPositive && workerState != WorkerState.IDLE) {
level?.setBlock(blockPos, blockState.setValue(WorkerState.SEMI_WORKER_STATE, WorkerState.IDLE), Block.UPDATE_CLIENTS)
}
}
if (redstoneControl.isBlockedByRedstone) return
if (redstoneControl.isBlockedByRedstone || !chargeAndroids) return
val level = level ?: return
val x = blockPos.x.toDouble()
val y = blockPos.y.toDouble()
val z = blockPos.z.toDouble()
for (ent in level.getEntitiesOfClass(ServerPlayer::class.java, AABB(x, y, z, x + 1.0, y + 2.0, z + 1.0))) {
for (ent in level.getEntitiesOfClass(ServerPlayer::class.java, AABB(x, y, z, x + 1.0, y + 2.0, z + 1.0)).shuffle(level.otmRandom)) {
if (ent.matteryPlayer.isAndroid)
moveEnergy(energy, ent.matteryPlayer.androidEnergy, amount = energy.batteryLevel, simulate = false, ignoreFlowRestrictions = true)
}

View File

@ -565,6 +565,7 @@ class MatteryPlayer(val ply: Player) {
val exopackEnergy = ProfiledEnergyStorage(BatteryBackedEnergyStorage(ply, syncher, Decimal.ZERO, ExopackConfig.ENERGY_CAPACITY, false, onChange = { for (v in smelters) v.notify(MachineJobEventLoop.IdleReason.POWER) }))
val exopackChargeSlots = MatteryContainer(4)
var acceptExopackChargeFromWirelessCharger = true
init {
savetables.int(::ticksIExist)
@ -578,6 +579,7 @@ class MatteryPlayer(val ply: Player) {
savetables.bool(::isExopackVisible, "displayExoSuit")
savetables.bool(::isExopackCraftingUpgraded, "isExoSuitCraftingUpgraded")
savetables.bool(::isExopackEnderAccessInstalled, "isExopackEnderAccessUpgraded")
savetables.bool(::acceptExopackChargeFromWirelessCharger)
savetables.int(::nextDischargeHurt)
savetables.int(::nextHealTick)

View File

@ -43,6 +43,10 @@ object Widgets18 {
val CURIOS_INVENTORY = miscGrid.next()
val STATISTICS_TAB = miscGrid.next()
val SETTINGS_TAB = miscGrid.next()
val WIRELESS_CHARGING_ENABLED = miscGrid.next()
val WIRELESS_CHARGING_DISABLED = miscGrid.next()
val WIRELESS_CHARGING_ANDROIDS_ENABLED = miscGrid.next()
val WIRELESS_CHARGING_ANDROIDS_DISABLED = miscGrid.next()
private val slotBgGrid = WidgetLocation.SLOT_BACKGROUNDS.grid(4, 4)

View File

@ -1,4 +1,5 @@
package ru.dbotthepony.mc.otm.client.render.entity
import net.minecraft.client.model.HumanoidModel
import ru.dbotthepony.mc.otm.OverdriveThatMatters
import ru.dbotthepony.mc.otm.core.ResourceLocation
@ -6,17 +7,32 @@ import net.minecraft.client.model.PlayerModel
import net.minecraft.client.renderer.entity.EntityRendererProvider
import net.minecraft.client.renderer.entity.HumanoidMobRenderer
import net.minecraft.client.model.geom.ModelLayers
import net.minecraft.client.renderer.entity.layers.HumanoidArmorLayer
import net.minecraft.client.renderer.entity.layers.ItemInHandLayer
import net.minecraft.client.resources.model.ModelManager
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.entity.EntityType
import net.minecraft.world.entity.Mob
class RogueAndroidRenderer<T : Mob>(context: EntityRendererProvider.Context, private val entityType: EntityType<T>, private val androidTexture: String) :
class RogueAndroidRenderer<T : Mob>(context: EntityRendererProvider.Context, private val entityType: EntityType<T>, private val androidTexture: String,private val modelManager: ModelManager) :
HumanoidMobRenderer<T, PlayerModel<T>>(
context, PlayerModel(context.bakeLayer(ModelLayers.PLAYER), false), 0.5f
) {
init {
addLayer(ItemInHandLayer(this, context.itemInHandRenderer))
addLayer(
HumanoidArmorLayer(
this,
HumanoidModel(context.bakeLayer(ModelLayers.PLAYER_INNER_ARMOR)),
HumanoidModel(context.bakeLayer(ModelLayers.PLAYER_OUTER_ARMOR)),
modelManager
)
)
}
override fun getTextureLocation(entity: T): ResourceLocation {
return ResourceLocation(OverdriveThatMatters.MOD_ID, "textures/entity/android/$androidTexture.png")
}

View File

@ -24,6 +24,7 @@ import ru.dbotthepony.mc.otm.client.render.WidgetLocation
import ru.dbotthepony.mc.otm.client.render.Widgets18
import ru.dbotthepony.mc.otm.client.render.translation
import ru.dbotthepony.mc.otm.client.screen.panels.*
import ru.dbotthepony.mc.otm.client.screen.panels.button.BooleanButtonPanel
import ru.dbotthepony.mc.otm.client.screen.panels.button.DeviceControls
import ru.dbotthepony.mc.otm.client.screen.panels.slot.AbstractSlotPanel
import ru.dbotthepony.mc.otm.client.screen.panels.slot.BatterySlotPanel
@ -172,13 +173,25 @@ abstract class MatteryScreen<T : MatteryMenu>(menu: T, inventory: Inventory, tit
}
protected fun makeChargePanels(frame: EditablePanel<*>) {
val chargeWidth = HorizontalPowerGaugePanel.GAUGE_BACKGROUND_TALL.width + AbstractSlotPanel.SIZE + 6f + ProgressGaugePanel.GAUGE_BACKGROUND.width * 2f
val chargeWidth = HorizontalPowerGaugePanel.GAUGE_BACKGROUND_TALL.width + AbstractSlotPanel.SIZE * 2 + 8f + ProgressGaugePanel.GAUGE_BACKGROUND.width * 2f
val chargeStrip = BackgroundPanel.paddedCenter(this, frame, frame.width - chargeWidth - 6f, frame.height + 2f, chargeWidth, AbstractSlotPanel.SIZE)
val chargeStrip2 = BackgroundPanel.paddedCenter(this, frame, frame.width + 2f, frame.height - AbstractSlotPanel.SIZE * 3f + 2f, AbstractSlotPanel.SIZE, AbstractSlotPanel.SIZE * 4f)
chargeStrip.customDock { chargeStrip.setPos(frame.width - chargeWidth - 6f, frame.height + 2f) }
chargeStrip2.customDock { chargeStrip2.setPos(frame.width + 2f, frame.height - AbstractSlotPanel.SIZE * 3f + 2f) }
BooleanButtonPanel.square18(
this, chargeStrip,
prop = menu.acceptExopackChargeFromWirelessCharger,
iconActive = Widgets18.WIRELESS_CHARGING_ENABLED,
iconInactive = Widgets18.WIRELESS_CHARGING_DISABLED,
tooltipActive = TranslatableComponent("otm.gui.exopack.accept_wireless_charge"),
tooltipInactive = TranslatableComponent("otm.gui.exopack.dont_accept_wireless_charge"),
).also {
it.dock = Dock.LEFT
it.dockRight = 2f
}
BatterySlotPanel(this, chargeStrip, menu.exopackChargeSlots[0]).also {
it.dock = Dock.LEFT
}

View File

@ -2,6 +2,7 @@ package ru.dbotthepony.mc.otm.client.screen.tech
import net.minecraft.network.chat.Component
import net.minecraft.world.entity.player.Inventory
import ru.dbotthepony.mc.otm.client.render.Widgets18
import ru.dbotthepony.mc.otm.client.screen.MatteryScreen
import ru.dbotthepony.mc.otm.client.screen.panels.Dock
import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel
@ -10,8 +11,10 @@ import ru.dbotthepony.mc.otm.client.screen.panels.button.makeDeviceControls
import ru.dbotthepony.mc.otm.client.screen.panels.slot.AbstractSlotPanel
import ru.dbotthepony.mc.otm.client.screen.panels.slot.BatterySlotPanel
import ru.dbotthepony.mc.otm.client.screen.panels.SpritePanel
import ru.dbotthepony.mc.otm.client.screen.panels.button.BooleanButtonPanel
import ru.dbotthepony.mc.otm.client.screen.widget.ProgressGaugePanel
import ru.dbotthepony.mc.otm.client.screen.widget.TallHorizontalProfiledPowerGaugePanel
import ru.dbotthepony.mc.otm.core.TranslatableComponent
import ru.dbotthepony.mc.otm.menu.tech.AndroidChargerMenu
class AndroidChargerScreen(menu: AndroidChargerMenu, inventory: Inventory, title: Component) : MatteryScreen<AndroidChargerMenu>(menu, inventory, title) {
@ -40,7 +43,33 @@ class AndroidChargerScreen(menu: AndroidChargerMenu, inventory: Inventory, title
it.dockRight = 2f
}
makeDeviceControls(this, frame, redstoneConfig = menu.redstoneConfig, energyConfig = menu.energyConfig)
val controls = makeDeviceControls(this, frame, redstoneConfig = menu.redstoneConfig, energyConfig = menu.energyConfig)
if (menu.chargeExopacks != null) {
controls.addButton(
BooleanButtonPanel.square18(
this, frame,
prop = menu.chargeExopacks!!,
iconActive = Widgets18.WIRELESS_CHARGING_ENABLED,
iconInactive = Widgets18.WIRELESS_CHARGING_DISABLED,
tooltipActive = TranslatableComponent("otm.gui.charge_exopacks"),
tooltipInactive = TranslatableComponent("otm.gui.dont_charge_exopacks"),
)
)
}
if (menu.chargeAndroids != null) {
controls.addButton(
BooleanButtonPanel.square18(
this, frame,
prop = menu.chargeAndroids!!,
iconActive = Widgets18.WIRELESS_CHARGING_ANDROIDS_ENABLED,
iconInactive = Widgets18.WIRELESS_CHARGING_ANDROIDS_DISABLED,
tooltipActive = TranslatableComponent("otm.gui.charge_androids"),
tooltipInactive = TranslatableComponent("otm.gui.dont_charge_androids"),
)
)
}
return frame
}

View File

@ -34,6 +34,7 @@ import ru.dbotthepony.mc.otm.client.screen.widget.WideProfiledPowerGaugePanel
import ru.dbotthepony.mc.otm.config.MachinesConfig
import ru.dbotthepony.kommons.math.RGBAColor
import ru.dbotthepony.mc.otm.capability.matteryPlayer
import ru.dbotthepony.mc.otm.client.screen.panels.button.BooleanButtonPanel
import ru.dbotthepony.mc.otm.core.RandomSource2Generator
import ru.dbotthepony.mc.otm.menu.tech.AndroidStationMenu
import ru.dbotthepony.mc.otm.network.AndroidResearchRequestPacket
@ -717,7 +718,18 @@ class AndroidStationScreen(p_97741_: AndroidStationMenu, p_97742_: Inventory, p_
this.playerStrip = playerStrip
makeDeviceControls(this, frame, redstoneConfig = menu.redstoneConfig, energyConfig = menu.energyConfig)
val controls = makeDeviceControls(this, frame, redstoneConfig = menu.redstoneConfig, energyConfig = menu.energyConfig)
controls.addButton(
BooleanButtonPanel.square18(
this, frame,
prop = menu.chargeAndroids,
iconActive = Widgets18.WIRELESS_CHARGING_ANDROIDS_ENABLED,
iconInactive = Widgets18.WIRELESS_CHARGING_ANDROIDS_DISABLED,
tooltipActive = TranslatableComponent("otm.gui.charge_androids"),
tooltipInactive = TranslatableComponent("otm.gui.dont_charge_androids"),
)
)
return frame
}

View File

@ -188,17 +188,20 @@ fun <T : Enum<T>> T.prev(values: Array<out T>): T {
return values[next]
}
fun IntArray.shuffle(random: RandomSource) {
fun IntArray.shuffle(random: RandomSource): IntArray {
for (i in lastIndex downTo 1) {
val j = random.nextInt(i + 1)
val copy = this[i]
this[i] = this[j]
this[j] = copy
}
return this
}
fun <T> MutableList<T>.shuffle(random: RandomSource) {
return Util.shuffle(this, random)
fun <T, L : MutableList<T>> L.shuffle(random: RandomSource): L {
Util.shuffle(this, random)
return this
}
fun <T> List<T>.random(random: RandomGenerator): T {

View File

@ -6,7 +6,6 @@ import net.minecraft.util.RandomSource
import net.minecraft.world.level.levelgen.MarsagliaPolarGaussian
import net.minecraft.world.level.levelgen.PositionalRandomFactory
import net.minecraft.world.level.levelgen.RandomSupport
import java.lang.StringBuilder
import java.util.random.RandomGenerator
/**
@ -22,12 +21,12 @@ class Xoshiro256SSRandom private constructor(
private val gaussian = MarsagliaPolarGaussian(this)
init {
require(
s0 != 0L ||
s1 != 0L ||
s2 != 0L ||
s3 != 0L
) { "Xoshiro can't operate with seed being entirely zero" }
if (s0 or s1 or s2 or s3 == 0L) {
s0 = 0x73CF3D83FFF44FF3L
s1 = 0x6412312B70F3CD37L
s2 = -0X6BB4C4E1327BFDCFL
s3 = -0X4BE0F5BB5F3F5240L
}
}
constructor(s0: Long, s1: Long, s2: Long, s3: Long) : this(s0, s1, s2, s3, null) {
@ -129,4 +128,11 @@ class Xoshiro256SSRandom private constructor(
throw UnsupportedOperationException()
}
}
companion object {
@JvmStatic
fun raw(s0: Long, s1: Long, s2: Long, s3: Long): Xoshiro256SSRandom {
return Xoshiro256SSRandom(s0, s1, s2, s3, null)
}
}
}

View File

@ -6,7 +6,7 @@ import net.minecraft.world.entity.EntityType
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.attributes.AttributeSupplier
import net.minecraft.world.entity.ai.attributes.Attributes
import net.minecraft.world.entity.ai.goal.LeapAtTargetGoal
import net.minecraft.world.entity.ai.goal.AvoidEntityGoal
import net.minecraft.world.entity.ai.goal.LookAtPlayerGoal
import net.minecraft.world.entity.ai.goal.MeleeAttackGoal
import net.minecraft.world.entity.ai.goal.RandomLookAroundGoal
@ -15,7 +15,9 @@ import net.minecraft.world.entity.ai.goal.target.HurtByTargetGoal
import net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal
import net.minecraft.world.entity.ai.navigation.GroundPathNavigation
import net.minecraft.world.entity.ai.navigation.PathNavigation
import net.minecraft.world.entity.monster.Monster
import net.minecraft.world.entity.animal.frog.Frog
import net.minecraft.world.entity.monster.*
import net.minecraft.world.entity.npc.Villager
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.Level
@ -24,14 +26,19 @@ class AndroidMelee(type: EntityType<AndroidMelee>, level: Level) : Monster(type,
override fun registerGoals() {
goalSelector.addGoal(8, RandomLookAroundGoal(this))
goalSelector.addGoal(7, WaterAvoidingRandomStrollGoal(this, 0.8))
goalSelector.addGoal(7, WaterAvoidingRandomStrollGoal(this, 0.6))
goalSelector.addGoal(8, LookAtPlayerGoal(this, Player::class.java, 8f))
goalSelector.addGoal(3, NearestAttackableTargetGoal(this, LivingEntity::class.java , true, true))
goalSelector.addGoal(3, NearestAttackableTargetGoal(this, Player::class.java , true, true))
goalSelector.addGoal(3, NearestAttackableTargetGoal(this, LivingEntity::class.java, 10, true, true) { entity ->
entity is Player ||
entity is Villager ||
entity is AbstractIllager ||
entity is Zombie ||
entity is AbstractSkeleton
})
goalSelector.addGoal(1, LeapAtTargetGoal(this, 0.4f))
goalSelector.addGoal(2, MeleeAttackGoal(this, 1.3, true))
goalSelector.addGoal(1, AvoidEntityGoal(this, Frog::class.java, 8.0F, 1.2, 1.5))
goalSelector.addGoal(2, MeleeAttackGoal(this, 1.0, true))
targetSelector.addGoal(1, HurtByTargetGoal(this))
}
@ -40,7 +47,11 @@ class AndroidMelee(type: EntityType<AndroidMelee>, level: Level) : Monster(type,
}
override fun getHurtSound(damageSource: net.minecraft.world.damagesource.DamageSource): SoundEvent {
return SoundEvents.IRON_GOLEM_HURT
return SoundEvents.HEAVY_CORE_BREAK
}
override fun getDeathSound(): SoundEvent {
return SoundEvents.VAULT_BREAK
}
override fun createNavigation(level: Level): PathNavigation = GroundPathNavigation(this, level)
@ -48,9 +59,11 @@ class AndroidMelee(type: EntityType<AndroidMelee>, level: Level) : Monster(type,
companion object {
fun createAttributes() : AttributeSupplier.Builder {
return createMonsterAttributes()
.add(Attributes.MAX_HEALTH, 16.0)
.add(Attributes.MAX_HEALTH, 30.0)
.add(Attributes.ARMOR, 4.0)
.add(Attributes.SCALE, 1.1)
.add(Attributes.MOVEMENT_SPEED, 0.3)
.add(Attributes.ATTACK_DAMAGE, 3.0)
.add(Attributes.ATTACK_DAMAGE, 4.0)
}
}
}

View File

@ -14,7 +14,11 @@ import net.minecraft.world.entity.ai.goal.target.HurtByTargetGoal
import net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal
import net.minecraft.world.entity.ai.navigation.GroundPathNavigation
import net.minecraft.world.entity.ai.navigation.PathNavigation
import net.minecraft.world.entity.monster.AbstractIllager
import net.minecraft.world.entity.monster.AbstractSkeleton
import net.minecraft.world.entity.monster.Monster
import net.minecraft.world.entity.monster.Zombie
import net.minecraft.world.entity.npc.Villager
import net.minecraft.world.entity.player.Player
import net.minecraft.world.level.Level
@ -29,8 +33,10 @@ class BreadMonster(type: EntityType<BreadMonster>, level: Level) : Monster(type,
goalSelector.addGoal(8, RandomLookAroundGoal(this))
goalSelector.addGoal(7, WaterAvoidingRandomStrollGoal(this, 0.8))
goalSelector.addGoal(8, LookAtPlayerGoal(this, Player::class.java, 8f))
goalSelector.addGoal(3, NearestAttackableTargetGoal(this, LivingEntity::class.java , true, true))
goalSelector.addGoal(3, NearestAttackableTargetGoal(this, Player::class.java , true, true))
goalSelector.addGoal(3, NearestAttackableTargetGoal(this, LivingEntity::class.java, 10, true, true) { entity ->
entity !is BreadMonster
})
goalSelector.addGoal(1, LeapAtTargetGoal(this, 0.4f))
goalSelector.addGoal(2, MeleeAttackGoal(this, 1.3, true))

View File

@ -12,6 +12,7 @@ import net.minecraft.sounds.SoundEvents
import net.minecraft.world.entity.AnimationState
import net.minecraft.world.entity.Entity
import net.minecraft.world.entity.EntityType
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.attributes.AttributeSupplier
import net.minecraft.world.entity.ai.attributes.Attributes
import net.minecraft.world.entity.ai.goal.LookAtPlayerGoal
@ -22,7 +23,11 @@ import net.minecraft.world.entity.ai.goal.target.HurtByTargetGoal
import net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal
import net.minecraft.world.entity.ai.navigation.GroundPathNavigation
import net.minecraft.world.entity.ai.navigation.PathNavigation
import net.minecraft.world.entity.monster.AbstractIllager
import net.minecraft.world.entity.monster.AbstractSkeleton
import net.minecraft.world.entity.monster.Monster
import net.minecraft.world.entity.monster.Zombie
import net.minecraft.world.entity.npc.Villager
import net.minecraft.world.entity.player.Player
import net.minecraft.world.level.Level
import ru.dbotthepony.mc.otm.OverdriveThatMatters
@ -68,7 +73,13 @@ class Loader(type: EntityType<Loader>, level: Level) : Monster(type, level) {
goalSelector.addGoal(8, RandomLookAroundGoal(this))
goalSelector.addGoal(7, WaterAvoidingRandomStrollGoal(this, 0.8))
goalSelector.addGoal(8, LookAtPlayerGoal(this, Player::class.java, 8f))
goalSelector.addGoal(3, NearestAttackableTargetGoal(this, Player::class.java , true, true))
goalSelector.addGoal(3, NearestAttackableTargetGoal(this, LivingEntity::class.java, 10, true, true) { entity ->
entity is Player ||
entity is Villager ||
entity is AbstractIllager ||
entity is Zombie ||
entity is AbstractSkeleton
})
goalSelector.addGoal(2, MeleeAttackGoal(this, 1.0, true))

View File

@ -7,7 +7,14 @@ import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.Container
import net.minecraft.world.entity.ExperienceOrb
import net.minecraft.world.entity.player.Player
import net.minecraft.world.inventory.*
import net.minecraft.world.inventory.AbstractContainerMenu
import net.minecraft.world.inventory.ContainerSynchronizer
import net.minecraft.world.inventory.CraftingContainer
import net.minecraft.world.inventory.CraftingMenu
import net.minecraft.world.inventory.ResultContainer
import net.minecraft.world.inventory.ResultSlot
import net.minecraft.world.inventory.Slot
import net.minecraft.world.inventory.TransientCraftingContainer
import net.minecraft.world.item.ItemStack
import net.neoforged.neoforge.network.PacketDistributor
import ru.dbotthepony.mc.otm.capability.MatteryPlayer

View File

@ -44,6 +44,7 @@ import ru.dbotthepony.mc.otm.container.sortWithIndices
import ru.dbotthepony.mc.otm.core.ResourceLocation
import ru.dbotthepony.mc.otm.core.collect.ConditionalSet
import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.mc.otm.menu.input.BooleanInputWithFeedback
import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget
import ru.dbotthepony.mc.otm.network.MatteryStreamCodec
import ru.dbotthepony.mc.otm.network.MenuDataPacket
@ -207,7 +208,8 @@ abstract class MatteryMenu(
var sortInventoryInput: SortInput? = null
private set
val playerSortSettings = IItemStackSortingSettings.inputs(this, player.matteryPlayer?.sortingSettings)
val playerSortSettings = IItemStackSortingSettings.inputs(this, player.matteryPlayer.sortingSettings)
val acceptExopackChargeFromWirelessCharger = BooleanInputWithFeedback(this, true, player.matteryPlayer::acceptExopackChargeFromWirelessCharger)
var offhandSlot: InventorySlot? = null
protected set

View File

@ -63,10 +63,6 @@ open class MatterySlot(container: Container, index: Int, x: Int = 0, y: Int = 0)
return super.mayPickup(player) && (!ignoreSpectators || !player.isSpectator)
}
override fun mayPlace(itemStack: ItemStack): Boolean {
return super.mayPlace(itemStack) && (!ignoreSpectators || runOnClient(true) { minecraft.player?.isSpectator != true })
}
open fun canTakeItemForPickAll(): Boolean {
return true
}

View File

@ -5,6 +5,7 @@ import ru.dbotthepony.mc.otm.block.entity.tech.AndroidChargerBlockEntity
import ru.dbotthepony.mc.otm.block.entity.tech.AndroidChargerMiddleBlockEntity
import ru.dbotthepony.mc.otm.block.entity.tech.AndroidChargerTopBlockEntity
import ru.dbotthepony.mc.otm.menu.MatteryPoweredMenu
import ru.dbotthepony.mc.otm.menu.input.BooleanInputWithFeedback
import ru.dbotthepony.mc.otm.menu.input.EnergyConfigPlayerInput
import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget
import ru.dbotthepony.mc.otm.registry.game.MMenus
@ -13,6 +14,9 @@ class AndroidChargerMenu : MatteryPoweredMenu {
val energyConfig: EnergyConfigPlayerInput
val profiledEnergy: ProfiledLevelGaugeWidget<*>
val chargeAndroids: BooleanInputWithFeedback?
val chargeExopacks: BooleanInputWithFeedback?
constructor(
p_38852_: Int,
inventory: Inventory,
@ -20,6 +24,8 @@ class AndroidChargerMenu : MatteryPoweredMenu {
) : super(MMenus.ANDROID_CHARGER, p_38852_, inventory, tile) {
energyConfig = EnergyConfigPlayerInput(this, tile?.energyConfig)
profiledEnergy = ProfiledLevelGaugeWidget(this, tile?.energyConfig?.energy, energyWidget)
chargeAndroids = BooleanInputWithFeedback(this, tile?.let { it::chargeAndroids })
chargeExopacks = BooleanInputWithFeedback(this, tile?.let { it::chargeExopacks })
}
constructor(
@ -29,6 +35,8 @@ class AndroidChargerMenu : MatteryPoweredMenu {
) : super(MMenus.ANDROID_CHARGER, p_38852_, inventory, tile?.lastTileEntity) {
energyConfig = EnergyConfigPlayerInput(this, tile?.energyConfig)
profiledEnergy = ProfiledLevelGaugeWidget(this, tile?.lastTileEntity?.energyConfig?.energy, energyWidget)
chargeAndroids = null
chargeExopacks = null
}
constructor(
@ -38,6 +46,8 @@ class AndroidChargerMenu : MatteryPoweredMenu {
) : super(MMenus.ANDROID_CHARGER, p_38852_, inventory, tile?.lastTileEntity) {
energyConfig = EnergyConfigPlayerInput(this, tile?.energyConfig)
profiledEnergy = ProfiledLevelGaugeWidget(this, tile?.lastTileEntity?.energyConfig?.energy, energyWidget)
chargeAndroids = null
chargeExopacks = null
}
init {

View File

@ -13,6 +13,7 @@ import ru.dbotthepony.mc.otm.capability.matteryPlayer
import ru.dbotthepony.mc.otm.config.MachinesConfig
import ru.dbotthepony.mc.otm.menu.MatteryPoweredMenu
import ru.dbotthepony.mc.otm.menu.MatterySlot
import ru.dbotthepony.mc.otm.menu.input.BooleanInputWithFeedback
import ru.dbotthepony.mc.otm.menu.input.EnergyConfigPlayerInput
import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget
import ru.dbotthepony.mc.otm.registry.game.MMenus
@ -25,7 +26,7 @@ class AndroidStationMenu @JvmOverloads constructor(
) : MatteryPoweredMenu(MMenus.ANDROID_STATION, containerID, inventory, tile) {
private fun container(target: (MatteryPlayer) -> KMutableProperty0<ItemStack>): Container {
if (player is ServerPlayer)
return PartContainer(target.invoke(player.matteryPlayer ?: throw NullPointerException("OTM player capability is missing")))
return PartContainer(target.invoke(player.matteryPlayer))
else
return SimpleContainer(1)
}
@ -115,6 +116,7 @@ class AndroidStationMenu @JvmOverloads constructor(
val equipment = makeEquipmentSlots()
val energyConfig = EnergyConfigPlayerInput(this, tile?.energyConfig)
val profiledEnergy = ProfiledLevelGaugeWidget(this, tile?.energy, energyWidget)
val chargeAndroids = BooleanInputWithFeedback(this, tile?.let { it::chargeAndroids })
init {
addInventorySlots()
@ -122,6 +124,6 @@ class AndroidStationMenu @JvmOverloads constructor(
}
override fun stillValid(player: Player): Boolean {
return super.stillValid(player) && player.matteryPlayer?.isAndroid == true
return super.stillValid(player) && player.matteryPlayer.isAndroid
}
}

View File

@ -77,7 +77,7 @@ object MEntityTypes {
private fun registerAttributes(event: EntityAttributeCreationEvent) {
event.put(BREAD_MONSTER, BreadMonster.createAttributes().build())
event.put(LOADER, Loader.createAttributes().build())
event.put(ANDROID_MELEE, Loader.createAttributes().build())
event.put(ANDROID_MELEE, AndroidMelee.createAttributes().build())
}
@Suppress("unchecked_cast")
@ -94,7 +94,7 @@ object MEntityTypes {
EntityRenderers.register(LOADER, ::LoaderRenderer)
EntityRenderers.register(ANDROID_MELEE) { context ->
RogueAndroidRenderer(context, ANDROID_MELEE, "melee")
RogueAndroidRenderer(context, ANDROID_MELEE, "melee",context.modelManager)
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB