More weapon tests
This commit is contained in:
parent
4f1e2217fe
commit
2aed09593c
@ -137,7 +137,7 @@ class ImpreciseFraction @JvmOverloads constructor(whole: BigInteger, decimal: Do
|
||||
}
|
||||
|
||||
// дробная часть равна нулю
|
||||
if (decimal == -0.0 || decimal == 0.0) {
|
||||
if (decimal == 0.0) {
|
||||
if (!isZero(whole)) {
|
||||
this.decimal = 0.0
|
||||
this.whole = whole
|
||||
|
@ -0,0 +1,456 @@
|
||||
package ru.dbotthepony.mc.otm.item.weapon
|
||||
|
||||
import com.mojang.math.Vector3f
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.client.model.HumanoidModel
|
||||
import net.minecraft.client.renderer.block.model.ItemTransforms
|
||||
import net.minecraft.nbt.CompoundTag
|
||||
import net.minecraft.network.FriendlyByteBuf
|
||||
import net.minecraft.world.entity.Entity
|
||||
import net.minecraft.world.entity.player.Player
|
||||
import net.minecraft.world.item.Item
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import net.minecraft.world.item.Rarity
|
||||
import net.minecraft.world.level.block.state.BlockState
|
||||
import net.minecraftforge.client.event.EntityViewRenderEvent
|
||||
import net.minecraftforge.client.event.InputEvent
|
||||
import net.minecraftforge.client.event.RenderHandEvent
|
||||
import net.minecraftforge.client.event.RenderPlayerEvent
|
||||
import net.minecraftforge.event.TickEvent
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent
|
||||
import net.minecraftforge.fml.LogicalSide
|
||||
import net.minecraftforge.network.NetworkEvent
|
||||
import ru.dbotthepony.kvector.util.linearInterpolation
|
||||
import ru.dbotthepony.kvector.vector.Angle3f
|
||||
import ru.dbotthepony.kvector.vector.ndouble.Vector3d
|
||||
import ru.dbotthepony.mc.otm.*
|
||||
import ru.dbotthepony.mc.otm.network.MatteryNetworking
|
||||
import java.util.*
|
||||
import java.util.function.Supplier
|
||||
import kotlin.math.PI
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.full.isSubclassOf
|
||||
import kotlin.reflect.full.primaryConstructor
|
||||
|
||||
private val ItemStack.weaponDataTable get() = tag?.let(::WeaponDataTable)
|
||||
|
||||
enum class WeaponScopePacket(val scope: Boolean) {
|
||||
SCOPE_IN(true), SCOPE_OUT(false);
|
||||
|
||||
fun write(buff: FriendlyByteBuf) {
|
||||
buff.writeBoolean(scope)
|
||||
}
|
||||
|
||||
fun play(supplier: Supplier<NetworkEvent.Context>) {
|
||||
supplier.get().packetHandled = true
|
||||
|
||||
// TODO: Manual synchronization
|
||||
supplier.get().enqueueWork {
|
||||
val stack = supplier.get().sender!!.mainHandItem
|
||||
val item = stack.item as? AbstractWeaponItem<*> ?: return@enqueueWork
|
||||
|
||||
item.dataTable(stack).wantsToScope = scope
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun read(buff: FriendlyByteBuf) = if (buff.readBoolean()) SCOPE_IN else SCOPE_OUT
|
||||
}
|
||||
}
|
||||
|
||||
enum class WeaponFireInputPacket(val primary: Boolean) {
|
||||
PRIMARY(true), SECONDARY(false);
|
||||
|
||||
fun write(buff: FriendlyByteBuf) {
|
||||
buff.writeBoolean(primary)
|
||||
}
|
||||
|
||||
fun play(supplier: Supplier<NetworkEvent.Context>) {
|
||||
supplier.get().packetHandled = true
|
||||
|
||||
// TODO: Manual synchronization
|
||||
supplier.get().enqueueWork {
|
||||
val stack = supplier.get().sender!!.mainHandItem
|
||||
val item = stack.item as? AbstractWeaponItem<*> ?: return@enqueueWork
|
||||
|
||||
// Listen server: client and server thread compete for lock
|
||||
// so it is very likely item is being in predicted context
|
||||
val predictedData = item.dataTable
|
||||
item.dataTable = null
|
||||
|
||||
if (primary)
|
||||
item.tryPrimaryFire(stack, supplier.get().sender!!)
|
||||
else
|
||||
item.trySecondaryFire(stack, supplier.get().sender!!)
|
||||
|
||||
(item as AbstractWeaponItem<WeaponDataTable>).dataTable = predictedData
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun read(buff: FriendlyByteBuf) = if (buff.readBoolean()) PRIMARY else SECONDARY
|
||||
}
|
||||
}
|
||||
|
||||
open class WeaponDataTable(val tag: CompoundTag) {
|
||||
var shotCooldown by tag.ints
|
||||
var wantsToScope by tag.booleans
|
||||
var scoped by tag.booleans
|
||||
var scopeTicks by tag.ints
|
||||
var nextPrimaryFire by tag.ints
|
||||
var nextSecondaryFire by tag.ints
|
||||
var uuid by tag.uuids
|
||||
}
|
||||
|
||||
abstract class AbstractWeaponItem<D : WeaponDataTable>(val tables: KClass<D>, rarity: Rarity = Rarity.UNCOMMON) : Item(
|
||||
Properties().tab(OverdriveThatMatters.INSTANCE.CREATIVE_TAB).stacksTo(1).rarity(rarity)) {
|
||||
fun makeDataTable(tag: CompoundTag) = tables.primaryConstructor!!.call(tag)
|
||||
|
||||
/**
|
||||
* Predicted data table, for client use only
|
||||
*/
|
||||
var dataTable: D? = null
|
||||
set(value) {
|
||||
field = if (value == null || value::class.isSubclassOf(tables)) {
|
||||
value
|
||||
} else {
|
||||
throw ClassCastException("Reified generics: ${value::class.qualifiedName} cannot be cast to ${tables.qualifiedName}")
|
||||
}
|
||||
}
|
||||
|
||||
fun compatibleDataTable(value: WeaponDataTable?) = value == null || value::class.isSubclassOf(tables)
|
||||
|
||||
@Suppress("unchecked_cast")
|
||||
fun dataTable(itemStack: ItemStack): D {
|
||||
if (dataTable != null) {
|
||||
return dataTable!!
|
||||
}
|
||||
|
||||
return makeDataTable(itemStack.tagNotNull)
|
||||
}
|
||||
|
||||
open fun holster(itemStack: ItemStack, player: Player, dt: D = dataTable(itemStack)) {
|
||||
dt.wantsToScope = false
|
||||
}
|
||||
|
||||
open fun deploy(itemStack: ItemStack, player: Player, dt: D = dataTable(itemStack)) {
|
||||
dt.wantsToScope = false
|
||||
|
||||
if (dt.uuid == EMPTY_UUID) {
|
||||
dt.uuid = UUID.randomUUID()
|
||||
}
|
||||
}
|
||||
|
||||
abstract val roundsPerMinute: Int
|
||||
val roundsPerSecond get() = roundsPerMinute / 60
|
||||
val fireCooldown get() = (1200 / roundsPerMinute).coerceAtLeast(1)
|
||||
|
||||
open val positionIdle get() = Vector3d(1.0, -0.5, -1.0)
|
||||
open val positionIronSights get() = Vector3d(0.0, -0.23, -1.0)
|
||||
open val rotIdle get() = Angle3f(PI.toFloat() / 36f, PI.toFloat() / 18f)
|
||||
open val rotIronSights get() = Angle3f.ZERO
|
||||
|
||||
open val primaryAutomatic = true
|
||||
open val secondaryAutomatic = true
|
||||
|
||||
open val ironSightsFOV: Double get() = 2.0
|
||||
|
||||
fun ironSightsProgress(itemStack: ItemStack, partialTicks: Double = 0.0, dt: D = dataTable(itemStack)): Double {
|
||||
if (!dt.wantsToScope)
|
||||
return (dt.scopeTicks.toDouble() - partialTicks).coerceAtLeast(0.0) / scopingTime.toDouble()
|
||||
|
||||
return (dt.scopeTicks.toDouble() + partialTicks) / scopingTime.toDouble()
|
||||
}
|
||||
|
||||
abstract val scopingTime: Int
|
||||
|
||||
open fun think(itemStack: ItemStack, player: Player, dt: D = dataTable(itemStack)) {
|
||||
if (dt.wantsToScope && dt.scopeTicks < scopingTime) {
|
||||
dt.scopeTicks++
|
||||
} else if (!dt.wantsToScope && dt.scopeTicks > 0) {
|
||||
dt.scopeTicks--
|
||||
}
|
||||
}
|
||||
|
||||
open fun thinkHolstered(itemStack: ItemStack, player: Player, dt: D = dataTable(itemStack)) {
|
||||
if (dt.shotCooldown > 0) {
|
||||
dt.shotCooldown--
|
||||
}
|
||||
}
|
||||
|
||||
open fun think2(itemStack: ItemStack, player: Player, dt: D = dataTable(itemStack)) {
|
||||
if (dt.nextPrimaryFire > 0) {
|
||||
dt.nextPrimaryFire--
|
||||
}
|
||||
|
||||
if (dt.nextSecondaryFire > 0) {
|
||||
dt.nextSecondaryFire--
|
||||
}
|
||||
}
|
||||
|
||||
open fun primaryFire(itemStack: ItemStack, player: Player, dt: D = dataTable(itemStack)): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
open fun secondaryFire(itemStack: ItemStack, player: Player, dt: D = dataTable(itemStack)): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
fun tryPrimaryFire(itemStack: ItemStack, player: Player, dt: D = dataTable(itemStack)): Boolean {
|
||||
if (dt.nextPrimaryFire <= 0 && primaryFire(itemStack, player, dt)) {
|
||||
dt.nextPrimaryFire = fireCooldown
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
fun trySecondaryFire(itemStack: ItemStack, player: Player, dt: D = dataTable(itemStack)): Boolean {
|
||||
if (dt.nextSecondaryFire <= 0 && secondaryFire(itemStack, player, dt)) {
|
||||
dt.nextSecondaryFire = fireCooldown
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
override fun getDestroySpeed(p_41425_: ItemStack, p_41426_: BlockState): Float {
|
||||
return 0f
|
||||
}
|
||||
|
||||
override fun onLeftClickEntity(stack: ItemStack?, player: Player?, entity: Entity?): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
companion object {
|
||||
// shared
|
||||
private val items = WeakHashMap<Player, ItemStack>()
|
||||
|
||||
// client only
|
||||
private var inRender = false
|
||||
var predictedData: WeaponDataTable? = null
|
||||
|
||||
@SubscribeEvent
|
||||
@Suppress("unused")
|
||||
fun fovHook(event: EntityViewRenderEvent.FieldOfView) {
|
||||
val player = event.camera.entity as? Player ?: return
|
||||
|
||||
(player.mainHandItem.item as? AbstractWeaponItem<*>)?.let {
|
||||
@Suppress("unchecked_cast")
|
||||
if (it.compatibleDataTable(predictedData))
|
||||
(it as AbstractWeaponItem<WeaponDataTable>).dataTable = predictedData
|
||||
|
||||
event.fov /= linearInterpolation(
|
||||
it.ironSightsProgress(
|
||||
player.mainHandItem,
|
||||
event.partialTicks
|
||||
) * 1.7 - 0.6, 1.0, it.ironSightsFOV
|
||||
)
|
||||
it.dataTable = null
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
@Suppress("unused")
|
||||
fun playerRenderHook(event: RenderPlayerEvent.Pre) {
|
||||
if (event.player.mainHandItem.item is AbstractWeaponItem<*> && event.player.offhandItem.isEmpty) {
|
||||
event.renderer.model.rightArmPose = HumanoidModel.ArmPose.BOW_AND_ARROW
|
||||
event.renderer.model.leftArmPose = HumanoidModel.ArmPose.EMPTY
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
@Suppress("unused")
|
||||
fun clickHook(event: InputEvent.ClickInputEvent) {
|
||||
val player = Minecraft.getInstance().player!!
|
||||
|
||||
if (player.mainHandItem.item is AbstractWeaponItem<*> && player.offhandItem.isEmpty && event.isAttack) {
|
||||
event.isCanceled = true
|
||||
event.setSwingHand(false)
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
@Suppress("unused", "unchecked_cast")
|
||||
fun renderViewModel(event: RenderHandEvent) {
|
||||
if (inRender) {
|
||||
return
|
||||
}
|
||||
|
||||
val stack = event.itemStack
|
||||
val item = stack.item as? AbstractWeaponItem<*> ?: return
|
||||
|
||||
inRender = true
|
||||
event.isCanceled = true
|
||||
|
||||
(item as AbstractWeaponItem<WeaponDataTable>).dataTable = predictedData
|
||||
|
||||
val player = Minecraft.getInstance().player!!
|
||||
val pose = event.poseStack
|
||||
val itemInHandRenderer = Minecraft.getInstance().itemInHandRenderer
|
||||
|
||||
pose.pushPose()
|
||||
|
||||
val progress = item.ironSightsProgress(stack, event.partialTicks.toDouble())
|
||||
|
||||
val (x, y, z) = Vector3d.bezier(
|
||||
progress,
|
||||
item.positionIdle,
|
||||
item.positionIdle,
|
||||
item.positionIdle,
|
||||
Vector3d(0.0, -1.0, -1.0),
|
||||
item.positionIronSights,
|
||||
item.positionIronSights,
|
||||
item.positionIronSights,
|
||||
)
|
||||
|
||||
pose.translate(x, y, z)
|
||||
|
||||
val (pitch, yaw, roll) = Angle3f.bezier(
|
||||
progress.toFloat(),
|
||||
item.rotIdle,
|
||||
item.rotIdle,
|
||||
item.rotIdle,
|
||||
item.rotIdle.copy(roll = -PI.toFloat() / 6f + item.rotIdle.roll),
|
||||
item.rotIronSights,
|
||||
item.rotIronSights,
|
||||
item.rotIronSights,
|
||||
item.rotIronSights,
|
||||
item.rotIronSights,
|
||||
)
|
||||
|
||||
pose.mulPose(Vector3f.ZP.rotation(roll))
|
||||
pose.mulPose(Vector3f.YP.rotation(yaw))
|
||||
pose.mulPose(Vector3f.XP.rotation(pitch))
|
||||
|
||||
itemInHandRenderer.renderItem(
|
||||
player,
|
||||
stack,
|
||||
ItemTransforms.TransformType.FIRST_PERSON_RIGHT_HAND,
|
||||
false,
|
||||
pose,
|
||||
event.multiBufferSource,
|
||||
event.packedLight
|
||||
)
|
||||
|
||||
pose.popPose()
|
||||
|
||||
item.dataTable = null
|
||||
inRender = false
|
||||
}
|
||||
|
||||
private val localPlayer: Player? get() {
|
||||
return Minecraft.getInstance().player
|
||||
}
|
||||
|
||||
private var firedPrimary = false
|
||||
private var firedSecondary = false
|
||||
|
||||
private fun processClientInputs(item: AbstractWeaponItem<*>, itemStack: ItemStack) {
|
||||
val minecraft = Minecraft.getInstance()
|
||||
val player = minecraft.player ?: return
|
||||
val dt = item.dataTable(itemStack)
|
||||
|
||||
if (!dt.wantsToScope && minecraft.options.keyUse.isDown) {
|
||||
dt.wantsToScope = true
|
||||
MatteryNetworking.CHANNEL.sendToServer(WeaponScopePacket.SCOPE_IN)
|
||||
} else if (dt.wantsToScope && !minecraft.options.keyUse.isDown) {
|
||||
dt.wantsToScope = false
|
||||
MatteryNetworking.CHANNEL.sendToServer(WeaponScopePacket.SCOPE_OUT)
|
||||
}
|
||||
|
||||
if (minecraft.options.keyAttack.isDown && (item.primaryAutomatic || !firedPrimary)) {
|
||||
firedPrimary = true
|
||||
|
||||
if (item.tryPrimaryFire(itemStack, player)) {
|
||||
MatteryNetworking.CHANNEL.sendToServer(WeaponFireInputPacket.PRIMARY)
|
||||
}
|
||||
} else if (!minecraft.options.keyAttack.isDown) {
|
||||
firedPrimary = false
|
||||
}
|
||||
|
||||
if (minecraft.options.keyAttack.isDown && (item.secondaryAutomatic || !firedSecondary)) {
|
||||
firedSecondary = true
|
||||
|
||||
if (item.trySecondaryFire(itemStack, player)) {
|
||||
MatteryNetworking.CHANNEL.sendToServer(WeaponFireInputPacket.SECONDARY)
|
||||
}
|
||||
} else if (!minecraft.options.keyAttack.isDown) {
|
||||
firedSecondary = false
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
@Suppress("unused", "unchecked_cast")
|
||||
fun tick(event: TickEvent.PlayerTickEvent) {
|
||||
if (event.phase != TickEvent.Phase.START)
|
||||
return
|
||||
|
||||
if (event.side == LogicalSide.CLIENT && event.player != localPlayer)
|
||||
return
|
||||
|
||||
val held = event.player.mainHandItem
|
||||
|
||||
for (stack in event.player.inventory.items) {
|
||||
if (held !== stack) {
|
||||
(stack.item as? AbstractWeaponItem<*>)?.thinkHolstered(stack, event.player)
|
||||
}
|
||||
|
||||
(stack.item as? AbstractWeaponItem<*>)?.let {
|
||||
if (event.side == LogicalSide.CLIENT) {
|
||||
(it as AbstractWeaponItem<WeaponDataTable>).dataTable = predictedData
|
||||
}
|
||||
|
||||
it.think2(stack, event.player)
|
||||
|
||||
if (event.side == LogicalSide.CLIENT) {
|
||||
it.dataTable = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val prev = items[event.player]
|
||||
val heldItem = held.item
|
||||
|
||||
if (held.weaponDataTable?.uuid != prev?.weaponDataTable?.uuid) {
|
||||
(prev?.item as? AbstractWeaponItem<*>)?.let {
|
||||
if (event.side == LogicalSide.CLIENT) {
|
||||
(it as AbstractWeaponItem<WeaponDataTable>).dataTable = predictedData
|
||||
}
|
||||
|
||||
it.holster(prev, event.player)
|
||||
|
||||
if (event.side == LogicalSide.CLIENT) {
|
||||
it.dataTable = null
|
||||
}
|
||||
}
|
||||
|
||||
(held.item as? AbstractWeaponItem<*>)?.deploy(held, event.player)
|
||||
|
||||
items[event.player] = held
|
||||
|
||||
if (event.side == LogicalSide.CLIENT && heldItem is AbstractWeaponItem<*>) {
|
||||
predictedData = heldItem.makeDataTable(held.tagNotNull.copy())
|
||||
|
||||
firedPrimary = false
|
||||
firedSecondary = false
|
||||
}
|
||||
}
|
||||
|
||||
if (heldItem is AbstractWeaponItem<*>) {
|
||||
// this can fail horribly, and die horribly, if weapon item changed,
|
||||
// but its UUID did not (compound tag fraud)
|
||||
if (event.side == LogicalSide.CLIENT) {
|
||||
(heldItem as AbstractWeaponItem<WeaponDataTable>).dataTable = predictedData
|
||||
processClientInputs(heldItem, held)
|
||||
}
|
||||
|
||||
heldItem.think(held, event.player)
|
||||
|
||||
if (event.side == LogicalSide.CLIENT) {
|
||||
heldItem.dataTable = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,628 +1,9 @@
|
||||
package ru.dbotthepony.mc.otm.item.weapon
|
||||
|
||||
import com.mojang.math.Vector3f
|
||||
import net.minecraft.ChatFormatting
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.client.model.HumanoidModel
|
||||
import net.minecraft.client.renderer.block.model.ItemTransforms
|
||||
import net.minecraft.core.Direction
|
||||
import net.minecraft.nbt.CompoundTag
|
||||
import net.minecraft.network.FriendlyByteBuf
|
||||
import net.minecraft.network.chat.Component
|
||||
import net.minecraft.network.chat.TranslatableComponent
|
||||
import net.minecraft.world.entity.Entity
|
||||
import net.minecraft.world.entity.player.Player
|
||||
import net.minecraft.world.entity.projectile.Arrow
|
||||
import net.minecraft.world.item.Item
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import net.minecraft.world.item.Rarity
|
||||
import net.minecraft.world.item.TooltipFlag
|
||||
import net.minecraft.world.level.Level
|
||||
import net.minecraft.world.level.block.state.BlockState
|
||||
import net.minecraftforge.client.event.EntityViewRenderEvent
|
||||
import net.minecraftforge.client.event.InputEvent
|
||||
import net.minecraftforge.client.event.RenderHandEvent
|
||||
import net.minecraftforge.client.event.RenderPlayerEvent
|
||||
import net.minecraftforge.common.capabilities.Capability
|
||||
import net.minecraftforge.common.capabilities.ICapabilityProvider
|
||||
import net.minecraftforge.common.util.INBTSerializable
|
||||
import net.minecraftforge.common.util.LazyOptional
|
||||
import net.minecraftforge.energy.CapabilityEnergy
|
||||
import net.minecraftforge.event.TickEvent
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent
|
||||
import net.minecraftforge.fml.LogicalSide
|
||||
import net.minecraftforge.network.NetworkEvent
|
||||
import ru.dbotthepony.kvector.util.linearInterpolation
|
||||
import ru.dbotthepony.kvector.vector.Angle3f
|
||||
import ru.dbotthepony.kvector.vector.ndouble.Vector3d
|
||||
import ru.dbotthepony.mc.otm.*
|
||||
import ru.dbotthepony.mc.otm.capability.*
|
||||
import ru.dbotthepony.mc.otm.core.ImpreciseFraction
|
||||
import ru.dbotthepony.mc.otm.menu.FormattingHelper
|
||||
import ru.dbotthepony.mc.otm.network.MatteryNetworking
|
||||
import java.util.*
|
||||
import java.util.function.Supplier
|
||||
import kotlin.math.PI
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.full.isSubclassOf
|
||||
import kotlin.reflect.full.primaryConstructor
|
||||
|
||||
enum class WeaponScopePacket(val scope: Boolean) {
|
||||
SCOPE_IN(true), SCOPE_OUT(false);
|
||||
|
||||
fun write(buff: FriendlyByteBuf) {
|
||||
buff.writeBoolean(scope)
|
||||
}
|
||||
|
||||
fun play(supplier: Supplier<NetworkEvent.Context>) {
|
||||
supplier.get().packetHandled = true
|
||||
|
||||
// TODO: Manual synchronization
|
||||
supplier.get().enqueueWork {
|
||||
val stack = supplier.get().sender!!.mainHandItem
|
||||
val item = stack.item as? AbstractWeaponItem<*> ?: return@enqueueWork
|
||||
|
||||
item.dataTable(stack).wantsToScope = scope
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun read(buff: FriendlyByteBuf) = if (buff.readBoolean()) SCOPE_IN else SCOPE_OUT
|
||||
}
|
||||
}
|
||||
|
||||
enum class WeaponFireInputPacket(val primary: Boolean) {
|
||||
PRIMARY(true), SECONDARY(false);
|
||||
|
||||
fun write(buff: FriendlyByteBuf) {
|
||||
buff.writeBoolean(primary)
|
||||
}
|
||||
|
||||
fun play(supplier: Supplier<NetworkEvent.Context>) {
|
||||
supplier.get().packetHandled = true
|
||||
|
||||
// TODO: Manual synchronization
|
||||
supplier.get().enqueueWork {
|
||||
val stack = supplier.get().sender!!.mainHandItem
|
||||
val item = stack.item as? AbstractWeaponItem<*> ?: return@enqueueWork
|
||||
|
||||
// Listen server: client and server thread compete for lock
|
||||
// so it is very likely item is being in predicted context
|
||||
val predictedData = item.dataTable
|
||||
item.dataTable = null
|
||||
|
||||
if (primary)
|
||||
item.tryPrimaryFire(stack, supplier.get().sender!!)
|
||||
else
|
||||
item.trySecondaryFire(stack, supplier.get().sender!!)
|
||||
|
||||
(item as AbstractWeaponItem<WeaponDataTable>).dataTable = predictedData
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun read(buff: FriendlyByteBuf) = if (buff.readBoolean()) PRIMARY else SECONDARY
|
||||
}
|
||||
}
|
||||
|
||||
open class WeaponDataTable(val tag: CompoundTag) {
|
||||
var shotCooldown by tag.ints
|
||||
var wantsToScope by tag.booleans
|
||||
var scoped by tag.booleans
|
||||
var scopeTicks by tag.ints
|
||||
var nextPrimaryFire by tag.ints
|
||||
var nextSecondaryFire by tag.ints
|
||||
var uuid by tag.uuids
|
||||
}
|
||||
|
||||
private val ItemStack.weaponDataTable get() = tag?.let(::WeaponDataTable)
|
||||
|
||||
abstract class AbstractWeaponItem<D : WeaponDataTable>(val tables: KClass<D>, rarity: Rarity = Rarity.UNCOMMON) : Item(Properties().tab(OverdriveThatMatters.INSTANCE.CREATIVE_TAB).stacksTo(1).rarity(rarity)) {
|
||||
fun makeDataTable(tag: CompoundTag) = tables.primaryConstructor!!.call(tag)
|
||||
|
||||
/**
|
||||
* Predicted data table, for client use only
|
||||
*/
|
||||
var dataTable: D? = null
|
||||
set(value) {
|
||||
field = if (value == null || value::class.isSubclassOf(tables)) {
|
||||
value
|
||||
} else {
|
||||
throw ClassCastException("Reified generics: ${value::class.qualifiedName} cannot be cast to ${tables.qualifiedName}")
|
||||
}
|
||||
}
|
||||
|
||||
fun compatibleDataTable(value: WeaponDataTable?) = value == null || value::class.isSubclassOf(tables)
|
||||
|
||||
@Suppress("unchecked_cast")
|
||||
fun dataTable(itemStack: ItemStack): D {
|
||||
if (dataTable != null) {
|
||||
return dataTable!!
|
||||
}
|
||||
|
||||
return makeDataTable(itemStack.tagNotNull)
|
||||
}
|
||||
|
||||
open fun holster(itemStack: ItemStack, player: Player, dt: D = dataTable(itemStack)) {
|
||||
dt.wantsToScope = false
|
||||
}
|
||||
|
||||
open fun deploy(itemStack: ItemStack, player: Player, dt: D = dataTable(itemStack)) {
|
||||
dt.wantsToScope = false
|
||||
|
||||
if (dt.uuid == EMPTY_UUID) {
|
||||
dt.uuid = UUID.randomUUID()
|
||||
}
|
||||
}
|
||||
|
||||
abstract val roundsPerMinute: Int
|
||||
val roundsPerSecond get() = roundsPerMinute / 60
|
||||
val fireCooldown get() = (1200 / roundsPerMinute).coerceAtLeast(1)
|
||||
|
||||
open val positionIdle get() = Vector3d(1.0, -0.5, -1.0)
|
||||
open val positionIronSights get() = Vector3d(0.0, -0.55, -1.0)
|
||||
open val rotIdle get() = Angle3f(PI.toFloat() / 36f, PI.toFloat() / 18f)
|
||||
open val rotIronSights get() = Angle3f.ZERO
|
||||
|
||||
open val primaryAutomatic = true
|
||||
open val secondaryAutomatic = true
|
||||
|
||||
open val ironSightsFOV: Double get() = 2.0
|
||||
|
||||
fun ironSightsProgress(itemStack: ItemStack, partialTicks: Double = 0.0, dt: D = dataTable(itemStack)): Double {
|
||||
if (!dt.wantsToScope)
|
||||
return (dt.scopeTicks.toDouble() - partialTicks).coerceAtLeast(0.0) / scopingTime.toDouble()
|
||||
|
||||
return (dt.scopeTicks.toDouble() + partialTicks) / scopingTime.toDouble()
|
||||
}
|
||||
|
||||
abstract val scopingTime: Int
|
||||
|
||||
open fun think(itemStack: ItemStack, player: Player, dt: D = dataTable(itemStack)) {
|
||||
if (dt.wantsToScope && dt.scopeTicks < scopingTime) {
|
||||
dt.scopeTicks++
|
||||
} else if (!dt.wantsToScope && dt.scopeTicks > 0) {
|
||||
dt.scopeTicks--
|
||||
}
|
||||
}
|
||||
|
||||
open fun thinkHolstered(itemStack: ItemStack, player: Player, dt: D = dataTable(itemStack)) {
|
||||
if (dt.shotCooldown > 0) {
|
||||
dt.shotCooldown--
|
||||
}
|
||||
}
|
||||
|
||||
open fun think2(itemStack: ItemStack, player: Player, dt: D = dataTable(itemStack)) {
|
||||
if (dt.nextPrimaryFire > 0) {
|
||||
dt.nextPrimaryFire--
|
||||
}
|
||||
|
||||
if (dt.nextSecondaryFire > 0) {
|
||||
dt.nextSecondaryFire--
|
||||
}
|
||||
}
|
||||
|
||||
open fun primaryFire(itemStack: ItemStack, player: Player, dt: D = dataTable(itemStack)): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
open fun secondaryFire(itemStack: ItemStack, player: Player, dt: D = dataTable(itemStack)): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
fun tryPrimaryFire(itemStack: ItemStack, player: Player, dt: D = dataTable(itemStack)): Boolean {
|
||||
if (dt.nextPrimaryFire <= 0 && primaryFire(itemStack, player, dt)) {
|
||||
dt.nextPrimaryFire = fireCooldown
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
fun trySecondaryFire(itemStack: ItemStack, player: Player, dt: D = dataTable(itemStack)): Boolean {
|
||||
if (dt.nextSecondaryFire <= 0 && secondaryFire(itemStack, player, dt)) {
|
||||
dt.nextSecondaryFire = fireCooldown
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
override fun getDestroySpeed(p_41425_: ItemStack, p_41426_: BlockState): Float {
|
||||
return 0f
|
||||
}
|
||||
|
||||
override fun onLeftClickEntity(stack: ItemStack?, player: Player?, entity: Entity?): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
companion object {
|
||||
// shared
|
||||
private val items = WeakHashMap<Player, ItemStack>()
|
||||
|
||||
// client only
|
||||
private var inRender = false
|
||||
var predictedData: WeaponDataTable? = null
|
||||
|
||||
@SubscribeEvent
|
||||
@Suppress("unused")
|
||||
fun fovHook(event: EntityViewRenderEvent.FieldOfView) {
|
||||
val player = event.camera.entity as? Player ?: return
|
||||
|
||||
(player.mainHandItem.item as? AbstractWeaponItem<*>)?.let {
|
||||
@Suppress("unchecked_cast")
|
||||
if (it.compatibleDataTable(predictedData))
|
||||
(it as AbstractWeaponItem<WeaponDataTable>).dataTable = predictedData
|
||||
|
||||
event.fov /= linearInterpolation(it.ironSightsProgress(player.mainHandItem, event.partialTicks), 1.0, it.ironSightsFOV)
|
||||
it.dataTable = null
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
@Suppress("unused")
|
||||
fun playerRenderHook(event: RenderPlayerEvent.Pre) {
|
||||
if (event.player.mainHandItem.item is AbstractWeaponItem<*> && event.player.offhandItem.isEmpty) {
|
||||
event.renderer.model.rightArmPose = HumanoidModel.ArmPose.BOW_AND_ARROW
|
||||
event.renderer.model.leftArmPose = HumanoidModel.ArmPose.BOW_AND_ARROW
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
@Suppress("unused")
|
||||
fun clickHook(event: InputEvent.ClickInputEvent) {
|
||||
val player = Minecraft.getInstance().player!!
|
||||
|
||||
if (player.mainHandItem.item is AbstractWeaponItem<*> && player.offhandItem.isEmpty && event.isAttack) {
|
||||
event.isCanceled = true
|
||||
event.setSwingHand(false)
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
@Suppress("unused", "unchecked_cast")
|
||||
fun renderViewModel(event: RenderHandEvent) {
|
||||
if (inRender) {
|
||||
return
|
||||
}
|
||||
|
||||
val stack = event.itemStack
|
||||
val item = stack.item as? AbstractWeaponItem<*> ?: return
|
||||
|
||||
inRender = true
|
||||
event.isCanceled = true
|
||||
|
||||
(item as AbstractWeaponItem<WeaponDataTable>).dataTable = predictedData
|
||||
|
||||
val player = Minecraft.getInstance().player!!
|
||||
val pose = event.poseStack
|
||||
val itemInHandRenderer = Minecraft.getInstance().itemInHandRenderer
|
||||
|
||||
pose.pushPose()
|
||||
|
||||
val progress = item.ironSightsProgress(stack, event.partialTicks.toDouble())
|
||||
|
||||
val (x, y, z) = Vector3d.bezier(
|
||||
progress,
|
||||
item.positionIdle,
|
||||
item.positionIdle,
|
||||
item.positionIdle,
|
||||
Vector3d(0.0, -1.0, -1.0),
|
||||
item.positionIronSights,
|
||||
item.positionIronSights,
|
||||
item.positionIronSights,
|
||||
)
|
||||
|
||||
pose.translate(x, y, z)
|
||||
|
||||
val (pitch, yaw, roll) = Angle3f.bezier(
|
||||
progress.toFloat(),
|
||||
item.rotIdle,
|
||||
item.rotIdle,
|
||||
item.rotIdle,
|
||||
item.rotIdle.copy(roll = -PI.toFloat() / 6f + item.rotIdle.roll),
|
||||
item.rotIronSights,
|
||||
item.rotIronSights,
|
||||
item.rotIronSights,
|
||||
item.rotIronSights,
|
||||
item.rotIronSights,
|
||||
)
|
||||
|
||||
pose.mulPose(Vector3f.ZP.rotation(roll))
|
||||
pose.mulPose(Vector3f.YP.rotation(yaw))
|
||||
pose.mulPose(Vector3f.XP.rotation(pitch))
|
||||
|
||||
itemInHandRenderer.renderItem(
|
||||
player,
|
||||
stack,
|
||||
ItemTransforms.TransformType.FIRST_PERSON_RIGHT_HAND,
|
||||
false,
|
||||
pose,
|
||||
event.multiBufferSource,
|
||||
event.packedLight
|
||||
)
|
||||
|
||||
pose.popPose()
|
||||
|
||||
item.dataTable = null
|
||||
inRender = false
|
||||
}
|
||||
|
||||
private val localPlayer: Player? get() {
|
||||
return Minecraft.getInstance().player
|
||||
}
|
||||
|
||||
private var firedPrimary = false
|
||||
private var firedSecondary = false
|
||||
|
||||
private fun processClientInputs(item: AbstractWeaponItem<*>, itemStack: ItemStack) {
|
||||
val minecraft = Minecraft.getInstance()
|
||||
val player = minecraft.player ?: return
|
||||
val dt = item.dataTable(itemStack)
|
||||
|
||||
if (!dt.wantsToScope && minecraft.options.keyUse.isDown) {
|
||||
dt.wantsToScope = true
|
||||
MatteryNetworking.CHANNEL.sendToServer(WeaponScopePacket.SCOPE_IN)
|
||||
} else if (dt.wantsToScope && !minecraft.options.keyUse.isDown) {
|
||||
dt.wantsToScope = false
|
||||
MatteryNetworking.CHANNEL.sendToServer(WeaponScopePacket.SCOPE_OUT)
|
||||
}
|
||||
|
||||
if (minecraft.options.keyAttack.isDown && (item.primaryAutomatic || !firedPrimary)) {
|
||||
firedPrimary = true
|
||||
|
||||
if (item.tryPrimaryFire(itemStack, player)) {
|
||||
MatteryNetworking.CHANNEL.sendToServer(WeaponFireInputPacket.PRIMARY)
|
||||
}
|
||||
} else if (!minecraft.options.keyAttack.isDown) {
|
||||
firedPrimary = false
|
||||
}
|
||||
|
||||
if (minecraft.options.keyAttack.isDown && (item.secondaryAutomatic || !firedSecondary)) {
|
||||
firedSecondary = true
|
||||
|
||||
if (item.trySecondaryFire(itemStack, player)) {
|
||||
MatteryNetworking.CHANNEL.sendToServer(WeaponFireInputPacket.SECONDARY)
|
||||
}
|
||||
} else if (!minecraft.options.keyAttack.isDown) {
|
||||
firedSecondary = false
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
@Suppress("unused", "unchecked_cast")
|
||||
fun tick(event: TickEvent.PlayerTickEvent) {
|
||||
if (event.phase != TickEvent.Phase.START)
|
||||
return
|
||||
|
||||
if (event.side == LogicalSide.CLIENT && event.player != localPlayer)
|
||||
return
|
||||
|
||||
val held = event.player.mainHandItem
|
||||
|
||||
for (stack in event.player.inventory.items) {
|
||||
if (held !== stack) {
|
||||
(stack.item as? AbstractWeaponItem<*>)?.thinkHolstered(stack, event.player)
|
||||
}
|
||||
|
||||
(stack.item as? AbstractWeaponItem<*>)?.let {
|
||||
if (event.side == LogicalSide.CLIENT) {
|
||||
(it as AbstractWeaponItem<WeaponDataTable>).dataTable = predictedData
|
||||
}
|
||||
|
||||
it.think2(stack, event.player)
|
||||
|
||||
if (event.side == LogicalSide.CLIENT) {
|
||||
it.dataTable = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val prev = items[event.player]
|
||||
val heldItem = held.item
|
||||
|
||||
if (held.weaponDataTable?.uuid != prev?.weaponDataTable?.uuid) {
|
||||
(prev?.item as? AbstractWeaponItem<*>)?.let {
|
||||
if (event.side == LogicalSide.CLIENT) {
|
||||
(it as AbstractWeaponItem<WeaponDataTable>).dataTable = predictedData
|
||||
}
|
||||
|
||||
it.holster(prev, event.player)
|
||||
|
||||
if (event.side == LogicalSide.CLIENT) {
|
||||
it.dataTable = null
|
||||
}
|
||||
}
|
||||
|
||||
(held.item as? AbstractWeaponItem<*>)?.deploy(held, event.player)
|
||||
|
||||
items[event.player] = held
|
||||
|
||||
if (event.side == LogicalSide.CLIENT && heldItem is AbstractWeaponItem<*>) {
|
||||
predictedData = heldItem.makeDataTable(held.tagNotNull.copy())
|
||||
|
||||
firedPrimary = false
|
||||
firedSecondary = false
|
||||
}
|
||||
}
|
||||
|
||||
if (heldItem is AbstractWeaponItem<*>) {
|
||||
// this can fail horribly, and die horribly, if weapon item changed,
|
||||
// but its UUID did not (compound tag fraud)
|
||||
if (event.side == LogicalSide.CLIENT) {
|
||||
(heldItem as AbstractWeaponItem<WeaponDataTable>).dataTable = predictedData
|
||||
processClientInputs(heldItem, held)
|
||||
}
|
||||
|
||||
heldItem.think(held, event.player)
|
||||
|
||||
if (event.side == LogicalSide.CLIENT) {
|
||||
heldItem.dataTable = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PlasmaWeaponEnergy(val itemStack: ItemStack, private val innerCapacity: ImpreciseFraction) :
|
||||
IMatteryEnergyStorage, ICapabilityProvider, INBTSerializable<CompoundTag> {
|
||||
private val energyResolver = LazyOptional.of { this }
|
||||
private var innerBatteryLevel = ImpreciseFraction.ZERO
|
||||
|
||||
var battery: ItemStack = ItemStack.EMPTY
|
||||
|
||||
override fun <T : Any> getCapability(cap: Capability<T>, side: Direction?): LazyOptional<T> {
|
||||
if (cap === MatteryCapability.ENERGY || cap === CapabilityEnergy.ENERGY) {
|
||||
return energyResolver.cast()
|
||||
}
|
||||
|
||||
return LazyOptional.empty()
|
||||
}
|
||||
|
||||
override fun serializeNBT(): CompoundTag {
|
||||
val nbt = CompoundTag()
|
||||
nbt["battery_level"] = innerBatteryLevel.serializeNBT()
|
||||
nbt["battery"] = battery.serializeNBT()
|
||||
return nbt
|
||||
}
|
||||
|
||||
override fun deserializeNBT(nbt: CompoundTag) {
|
||||
innerBatteryLevel = ImpreciseFraction.deserializeNBT(nbt["battery_level"])
|
||||
battery = ItemStack.of(nbt["battery"] as CompoundTag)
|
||||
}
|
||||
|
||||
override fun extractEnergyOuter(howMuch: ImpreciseFraction, simulate: Boolean): ImpreciseFraction {
|
||||
return ImpreciseFraction.ZERO
|
||||
}
|
||||
|
||||
override fun extractEnergyInner(howMuch: ImpreciseFraction, simulate: Boolean): ImpreciseFraction {
|
||||
if (!howMuch.isPositive)
|
||||
return ImpreciseFraction.ZERO
|
||||
|
||||
@Suppress("NAME_SHADOWING")
|
||||
var howMuch = howMuch
|
||||
var totalExtracted = ImpreciseFraction.ZERO
|
||||
|
||||
if (!battery.isEmpty) {
|
||||
battery.getCapability(CapabilityEnergy.ENERGY).ifPresentK {
|
||||
val extracted = it.extractEnergy(howMuch, simulate)
|
||||
|
||||
if (extracted >= howMuch) {
|
||||
return extracted
|
||||
}
|
||||
|
||||
howMuch -= extracted
|
||||
totalExtracted += extracted
|
||||
}
|
||||
}
|
||||
|
||||
val newEnergy = (innerBatteryLevel - howMuch).moreThanZero()
|
||||
val diff = innerBatteryLevel - newEnergy
|
||||
|
||||
if (!simulate) {
|
||||
innerBatteryLevel = newEnergy
|
||||
}
|
||||
|
||||
return diff + totalExtracted
|
||||
}
|
||||
|
||||
override fun receiveEnergyOuter(howMuch: ImpreciseFraction, simulate: Boolean): ImpreciseFraction {
|
||||
return receiveEnergyInner(howMuch, simulate)
|
||||
}
|
||||
|
||||
override fun receiveEnergyInner(howMuch: ImpreciseFraction, simulate: Boolean): ImpreciseFraction {
|
||||
if (!howMuch.isPositive)
|
||||
return ImpreciseFraction.ZERO
|
||||
|
||||
@Suppress("NAME_SHADOWING")
|
||||
var howMuch = howMuch
|
||||
var totalReceived = ImpreciseFraction.ZERO
|
||||
|
||||
if (!battery.isEmpty) {
|
||||
battery.getCapability(CapabilityEnergy.ENERGY).ifPresentK {
|
||||
val received = it.receiveEnergy(howMuch, simulate)
|
||||
|
||||
if (received >= howMuch) {
|
||||
return received
|
||||
}
|
||||
|
||||
howMuch -= received
|
||||
totalReceived += received
|
||||
}
|
||||
}
|
||||
|
||||
if (innerBatteryLevel >= innerCapacity) {
|
||||
return totalReceived
|
||||
}
|
||||
|
||||
val newEnergy = (innerBatteryLevel - howMuch).min(innerCapacity)
|
||||
val diff = newEnergy - innerBatteryLevel
|
||||
|
||||
if (!simulate) {
|
||||
innerBatteryLevel = newEnergy
|
||||
}
|
||||
|
||||
return diff + totalReceived
|
||||
}
|
||||
|
||||
fun think() {
|
||||
if (!battery.isEmpty && innerBatteryLevel < innerCapacity) {
|
||||
battery.getCapability(CapabilityEnergy.ENERGY).ifPresentK {
|
||||
innerBatteryLevel += it.extractEnergy(innerCapacity - innerBatteryLevel, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun canExtract(): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun canReceive(): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override val batteryLevel: ImpreciseFraction
|
||||
get() = (battery.energy?.energyStoredMattery ?: ImpreciseFraction.ZERO) + innerBatteryLevel
|
||||
|
||||
override val maxBatteryLevel: ImpreciseFraction
|
||||
get() = (battery.energy?.maxEnergyStoredMattery ?: ImpreciseFraction.ZERO) + innerCapacity
|
||||
}
|
||||
|
||||
abstract class PlasmaWeaponItem<D : WeaponDataTable>(tables: KClass<D>, private val energyCapacity: ImpreciseFraction) : AbstractWeaponItem<D>(tables) {
|
||||
override fun appendHoverText(
|
||||
itemStack: ItemStack,
|
||||
p_41422_: Level?,
|
||||
p_41423_: MutableList<Component>,
|
||||
p_41424_: TooltipFlag
|
||||
) {
|
||||
super.appendHoverText(itemStack, p_41422_, p_41423_, p_41424_)
|
||||
|
||||
itemStack.getCapability(MatteryCapability.ENERGY).ifPresentK {
|
||||
p_41423_.add(
|
||||
TranslatableComponent(
|
||||
"otm.item.power.normal.storage",
|
||||
FormattingHelper.formatPower(it.batteryLevel),
|
||||
FormattingHelper.formatPower(it.maxBatteryLevel)
|
||||
).withStyle(ChatFormatting.GRAY)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun energyData(itemStack: ItemStack) = itemStack.matteryEnergy as PlasmaWeaponEnergy
|
||||
|
||||
override fun think2(itemStack: ItemStack, player: Player, dt: D) {
|
||||
super.think2(itemStack, player, dt)
|
||||
|
||||
itemStack.getCapability(MatteryCapability.ENERGY).ifPresentK {
|
||||
it as PlasmaWeaponEnergy
|
||||
it.think()
|
||||
}
|
||||
}
|
||||
|
||||
override fun initCapabilities(stack: ItemStack, nbt: CompoundTag?): ICapabilityProvider {
|
||||
return PlasmaWeaponEnergy(stack, energyCapacity)
|
||||
}
|
||||
}
|
||||
|
||||
class PlasmaRifleItem : PlasmaWeaponItem<WeaponDataTable>(WeaponDataTable::class, ImpreciseFraction(200_000)) {
|
||||
override val roundsPerMinute: Int = 400
|
||||
@ -631,6 +12,14 @@ class PlasmaRifleItem : PlasmaWeaponItem<WeaponDataTable>(WeaponDataTable::class
|
||||
override fun primaryFire(itemStack: ItemStack, player: Player, dt: WeaponDataTable): Boolean {
|
||||
println("FIER! ${Thread.currentThread()}")
|
||||
|
||||
if (!player.level.isClientSide) {
|
||||
val arrow = Arrow(player.level, player)
|
||||
|
||||
arrow.shootFromRotation(player, player.xRot, player.yRot, 0f, 2f, 0f)
|
||||
|
||||
player.level.addFreshEntity(arrow)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,182 @@
|
||||
package ru.dbotthepony.mc.otm.item.weapon
|
||||
|
||||
import net.minecraft.ChatFormatting
|
||||
import net.minecraft.core.Direction
|
||||
import net.minecraft.nbt.CompoundTag
|
||||
import net.minecraft.network.chat.Component
|
||||
import net.minecraft.network.chat.TranslatableComponent
|
||||
import net.minecraft.world.entity.player.Player
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import net.minecraft.world.item.TooltipFlag
|
||||
import net.minecraft.world.level.Level
|
||||
import net.minecraftforge.common.capabilities.Capability
|
||||
import net.minecraftforge.common.capabilities.ICapabilityProvider
|
||||
import net.minecraftforge.common.util.INBTSerializable
|
||||
import net.minecraftforge.common.util.LazyOptional
|
||||
import net.minecraftforge.energy.CapabilityEnergy
|
||||
import ru.dbotthepony.mc.otm.capability.*
|
||||
import ru.dbotthepony.mc.otm.core.ImpreciseFraction
|
||||
import ru.dbotthepony.mc.otm.ifPresentK
|
||||
import ru.dbotthepony.mc.otm.menu.FormattingHelper
|
||||
import ru.dbotthepony.mc.otm.set
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
class PlasmaWeaponEnergy(val itemStack: ItemStack, private val innerCapacity: ImpreciseFraction) :
|
||||
IMatteryEnergyStorage, ICapabilityProvider, INBTSerializable<CompoundTag> {
|
||||
private val energyResolver = LazyOptional.of { this }
|
||||
private var innerBatteryLevel = ImpreciseFraction.ZERO
|
||||
|
||||
var battery: ItemStack = ItemStack.EMPTY
|
||||
|
||||
override fun <T : Any> getCapability(cap: Capability<T>, side: Direction?): LazyOptional<T> {
|
||||
if (cap === MatteryCapability.ENERGY || cap === CapabilityEnergy.ENERGY) {
|
||||
return energyResolver.cast()
|
||||
}
|
||||
|
||||
return LazyOptional.empty()
|
||||
}
|
||||
|
||||
override fun serializeNBT(): CompoundTag {
|
||||
val nbt = CompoundTag()
|
||||
nbt["battery_level"] = innerBatteryLevel.serializeNBT()
|
||||
nbt["battery"] = battery.serializeNBT()
|
||||
return nbt
|
||||
}
|
||||
|
||||
override fun deserializeNBT(nbt: CompoundTag) {
|
||||
innerBatteryLevel = ImpreciseFraction.deserializeNBT(nbt["battery_level"])
|
||||
battery = ItemStack.of(nbt["battery"] as CompoundTag)
|
||||
}
|
||||
|
||||
override fun extractEnergyOuter(howMuch: ImpreciseFraction, simulate: Boolean): ImpreciseFraction {
|
||||
return ImpreciseFraction.ZERO
|
||||
}
|
||||
|
||||
override fun extractEnergyInner(howMuch: ImpreciseFraction, simulate: Boolean): ImpreciseFraction {
|
||||
if (!howMuch.isPositive)
|
||||
return ImpreciseFraction.ZERO
|
||||
|
||||
@Suppress("NAME_SHADOWING")
|
||||
var howMuch = howMuch
|
||||
var totalExtracted = ImpreciseFraction.ZERO
|
||||
|
||||
if (!battery.isEmpty) {
|
||||
battery.getCapability(CapabilityEnergy.ENERGY).ifPresentK {
|
||||
val extracted = it.extractEnergy(howMuch, simulate)
|
||||
|
||||
if (extracted >= howMuch) {
|
||||
return extracted
|
||||
}
|
||||
|
||||
howMuch -= extracted
|
||||
totalExtracted += extracted
|
||||
}
|
||||
}
|
||||
|
||||
val newEnergy = (innerBatteryLevel - howMuch).moreThanZero()
|
||||
val diff = innerBatteryLevel - newEnergy
|
||||
|
||||
if (!simulate) {
|
||||
innerBatteryLevel = newEnergy
|
||||
}
|
||||
|
||||
return diff + totalExtracted
|
||||
}
|
||||
|
||||
override fun receiveEnergyOuter(howMuch: ImpreciseFraction, simulate: Boolean): ImpreciseFraction {
|
||||
return receiveEnergyInner(howMuch, simulate)
|
||||
}
|
||||
|
||||
override fun receiveEnergyInner(howMuch: ImpreciseFraction, simulate: Boolean): ImpreciseFraction {
|
||||
if (!howMuch.isPositive)
|
||||
return ImpreciseFraction.ZERO
|
||||
|
||||
@Suppress("NAME_SHADOWING")
|
||||
var howMuch = howMuch
|
||||
var totalReceived = ImpreciseFraction.ZERO
|
||||
|
||||
if (!battery.isEmpty) {
|
||||
battery.getCapability(CapabilityEnergy.ENERGY).ifPresentK {
|
||||
val received = it.receiveEnergy(howMuch, simulate)
|
||||
|
||||
if (received >= howMuch) {
|
||||
return received
|
||||
}
|
||||
|
||||
howMuch -= received
|
||||
totalReceived += received
|
||||
}
|
||||
}
|
||||
|
||||
if (innerBatteryLevel >= innerCapacity) {
|
||||
return totalReceived
|
||||
}
|
||||
|
||||
val newEnergy = (innerBatteryLevel - howMuch).min(innerCapacity)
|
||||
val diff = newEnergy - innerBatteryLevel
|
||||
|
||||
if (!simulate) {
|
||||
innerBatteryLevel = newEnergy
|
||||
}
|
||||
|
||||
return diff + totalReceived
|
||||
}
|
||||
|
||||
fun think() {
|
||||
if (!battery.isEmpty && innerBatteryLevel < innerCapacity) {
|
||||
battery.getCapability(CapabilityEnergy.ENERGY).ifPresentK {
|
||||
innerBatteryLevel += it.extractEnergy(innerCapacity - innerBatteryLevel, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun canExtract(): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun canReceive(): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override val batteryLevel: ImpreciseFraction
|
||||
get() = (battery.energy?.energyStoredMattery ?: ImpreciseFraction.ZERO) + innerBatteryLevel
|
||||
|
||||
override val maxBatteryLevel: ImpreciseFraction
|
||||
get() = (battery.energy?.maxEnergyStoredMattery ?: ImpreciseFraction.ZERO) + innerCapacity
|
||||
}
|
||||
|
||||
abstract class PlasmaWeaponItem<D : WeaponDataTable>(tables: KClass<D>, private val energyCapacity: ImpreciseFraction) : AbstractWeaponItem<D>(tables) {
|
||||
override fun appendHoverText(
|
||||
itemStack: ItemStack,
|
||||
p_41422_: Level?,
|
||||
p_41423_: MutableList<Component>,
|
||||
p_41424_: TooltipFlag
|
||||
) {
|
||||
super.appendHoverText(itemStack, p_41422_, p_41423_, p_41424_)
|
||||
|
||||
itemStack.getCapability(MatteryCapability.ENERGY).ifPresentK {
|
||||
p_41423_.add(
|
||||
TranslatableComponent(
|
||||
"otm.item.power.normal.storage",
|
||||
FormattingHelper.formatPower(it.batteryLevel),
|
||||
FormattingHelper.formatPower(it.maxBatteryLevel)
|
||||
).withStyle(ChatFormatting.GRAY)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun energyData(itemStack: ItemStack) = itemStack.matteryEnergy as PlasmaWeaponEnergy
|
||||
|
||||
override fun think2(itemStack: ItemStack, player: Player, dt: D) {
|
||||
super.think2(itemStack, player, dt)
|
||||
|
||||
itemStack.getCapability(MatteryCapability.ENERGY).ifPresentK {
|
||||
it as PlasmaWeaponEnergy
|
||||
it.think()
|
||||
}
|
||||
}
|
||||
|
||||
override fun initCapabilities(stack: ItemStack, nbt: CompoundTag?): ICapabilityProvider {
|
||||
return PlasmaWeaponEnergy(stack, energyCapacity)
|
||||
}
|
||||
}
|
@ -6,8 +6,159 @@ import net.minecraft.world.damagesource.DamageSource
|
||||
import net.minecraft.world.entity.Entity
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import net.minecraft.world.phys.Vec3
|
||||
|
||||
class EMPDamageSource(private val entity: Entity? = null) : DamageSource(MRegistry.DAMAGE_EMP_NAME) {
|
||||
class ImmutableDamageSource(private val parent: DamageSource) : DamageSource(parent.msgId) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return parent == other
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return parent.hashCode()
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return parent.toString()
|
||||
}
|
||||
|
||||
override fun isProjectile(): Boolean {
|
||||
return parent.isProjectile
|
||||
}
|
||||
|
||||
override fun setProjectile(): DamageSource {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun isExplosion(): Boolean {
|
||||
return parent.isExplosion
|
||||
}
|
||||
|
||||
override fun setExplosion(): DamageSource {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun isBypassArmor(): Boolean {
|
||||
return parent.isBypassArmor
|
||||
}
|
||||
|
||||
override fun isDamageHelmet(): Boolean {
|
||||
return parent.isDamageHelmet
|
||||
}
|
||||
|
||||
override fun getFoodExhaustion(): Float {
|
||||
return parent.foodExhaustion
|
||||
}
|
||||
|
||||
override fun isBypassInvul(): Boolean {
|
||||
return parent.isBypassInvul
|
||||
}
|
||||
|
||||
override fun isBypassMagic(): Boolean {
|
||||
return parent.isBypassMagic
|
||||
}
|
||||
|
||||
override fun getDirectEntity(): Entity? {
|
||||
return parent.directEntity
|
||||
}
|
||||
|
||||
override fun getEntity(): Entity? {
|
||||
return parent.entity
|
||||
}
|
||||
|
||||
override fun bypassArmor(): DamageSource {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun damageHelmet(): DamageSource {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun bypassInvul(): DamageSource {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun bypassMagic(): DamageSource {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun setIsFire(): DamageSource {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun setNoAggro(): DamageSource {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun getLocalizedDeathMessage(p_19343_: LivingEntity): Component {
|
||||
return super.getLocalizedDeathMessage(p_19343_)
|
||||
}
|
||||
|
||||
override fun isFire(): Boolean {
|
||||
return parent.isFire
|
||||
}
|
||||
|
||||
override fun isNoAggro(): Boolean {
|
||||
return parent.isNoAggro
|
||||
}
|
||||
|
||||
override fun getMsgId(): String {
|
||||
return parent.getMsgId()
|
||||
}
|
||||
|
||||
override fun setScalesWithDifficulty(): DamageSource {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun scalesWithDifficulty(): Boolean {
|
||||
return parent.scalesWithDifficulty()
|
||||
}
|
||||
|
||||
override fun isMagic(): Boolean {
|
||||
return parent.isMagic
|
||||
}
|
||||
|
||||
override fun setMagic(): DamageSource {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun isFall(): Boolean {
|
||||
return parent.isFall
|
||||
}
|
||||
|
||||
override fun setIsFall(): DamageSource {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun isCreativePlayer(): Boolean {
|
||||
return parent.isCreativePlayer
|
||||
}
|
||||
|
||||
override fun getSourcePosition(): Vec3? {
|
||||
return parent.sourcePosition
|
||||
}
|
||||
}
|
||||
|
||||
abstract class MatteryDamageSource(name: String, private val entity: Entity? = null, private val inflictor: ItemStack? = null) : DamageSource(name) {
|
||||
override fun getLocalizedDeathMessage(victim: LivingEntity): Component {
|
||||
val itemStack = inflictor ?: (entity as LivingEntity?)?.mainHandItem ?: ItemStack.EMPTY
|
||||
|
||||
if (!itemStack.isEmpty && itemStack.hasCustomHoverName()) {
|
||||
return TranslatableComponent("death.attack.$msgId.player.item", victim.displayName, entity!!.displayName, itemStack.displayName)
|
||||
}
|
||||
|
||||
if (entity != null) {
|
||||
return TranslatableComponent("death.attack.$msgId.player", victim.displayName, entity.displayName)
|
||||
}
|
||||
|
||||
return TranslatableComponent("death.attack.$msgId", victim.displayName)
|
||||
}
|
||||
|
||||
final override fun getEntity(): Entity? {
|
||||
return entity
|
||||
}
|
||||
}
|
||||
|
||||
class EMPDamageSource(entity: Entity? = null, inflictor: ItemStack? = null) : MatteryDamageSource(MRegistry.DAMAGE_EMP_NAME, entity, inflictor) {
|
||||
init {
|
||||
bypassArmor()
|
||||
bypassMagic()
|
||||
@ -16,22 +167,10 @@ class EMPDamageSource(private val entity: Entity? = null) : DamageSource(MRegist
|
||||
override fun scalesWithDifficulty(): Boolean {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
override fun getLocalizedDeathMessage(victim: LivingEntity): Component {
|
||||
val itemStack = (entity as LivingEntity?)?.mainHandItem ?: ItemStack.EMPTY
|
||||
|
||||
if (!itemStack.isEmpty && itemStack.hasCustomHoverName()) {
|
||||
return TranslatableComponent("death.attack.otm_emp.player.item", victim.displayName, entity!!.displayName, itemStack.displayName)
|
||||
}
|
||||
|
||||
if (entity != null) {
|
||||
return TranslatableComponent("death.attack.otm_emp.player", victim.displayName, entity.displayName)
|
||||
}
|
||||
|
||||
return TranslatableComponent("death.attack.otm_emp", victim.displayName)
|
||||
}
|
||||
|
||||
override fun getEntity(): Entity? {
|
||||
return entity
|
||||
class PlasmaDamageSource(entity: Entity? = null, inflictor: ItemStack? = null) : MatteryDamageSource(MRegistry.DAMAGE_PLASMA_NAME, entity, inflictor) {
|
||||
override fun scalesWithDifficulty(): Boolean {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ import ru.dbotthepony.mc.otm.item.weapon.PlasmaRifleItem
|
||||
|
||||
object MItems {
|
||||
private val DEFAULT_PROPERTIES = Item.Properties().stacksTo(64).tab(OverdriveThatMatters.INSTANCE.CREATIVE_TAB)
|
||||
private val registry = DeferredRegister.create(ForgeRegistries.ITEMS, OverdriveThatMatters.MOD_ID)
|
||||
private val registry: DeferredRegister<Item> = DeferredRegister.create(ForgeRegistries.ITEMS, OverdriveThatMatters.MOD_ID)
|
||||
|
||||
internal fun register() {
|
||||
registry.register(FMLJavaModLoadingContext.get().modEventBus)
|
||||
|
@ -171,6 +171,9 @@ object MNames {
|
||||
const val DAMAGE_ABSORBED = "damage_absorbed"
|
||||
const val HEALTH_REGENERATED = "health_regenerated"
|
||||
const val POWER_CONSUMED = "power_consumed"
|
||||
|
||||
// entities
|
||||
const val PLASMA = "plasma_projectile"
|
||||
}
|
||||
|
||||
object StatNames {
|
||||
|
@ -96,6 +96,7 @@ object MRegistry {
|
||||
|
||||
MBlocks.register()
|
||||
MBlockEntities.register()
|
||||
MEntityTypes.register()
|
||||
MMenus.register()
|
||||
MItems.register()
|
||||
AndroidFeatures.register()
|
||||
@ -116,18 +117,15 @@ object MRegistry {
|
||||
})
|
||||
}
|
||||
|
||||
val DAMAGE_BECOME_ANDROID = DamageSource("otm_become_android")
|
||||
val DAMAGE_BECOME_HUMANE = DamageSource("otm_become_humane")
|
||||
val DAMAGE_EVENT_HORIZON = DamageSource("otm_event_horizon")
|
||||
val DAMAGE_HAWKING_RADIATION = DamageSource("otm_hawking_radiation")
|
||||
val DAMAGE_BECOME_ANDROID = ImmutableDamageSource(DamageSource("otm_become_android").bypassArmor().bypassInvul().bypassMagic())
|
||||
val DAMAGE_BECOME_HUMANE = ImmutableDamageSource(DamageSource("otm_become_humane").bypassArmor().bypassInvul().bypassMagic())
|
||||
val DAMAGE_EVENT_HORIZON = ImmutableDamageSource(DamageSource("otm_event_horizon").bypassMagic().bypassArmor())
|
||||
val DAMAGE_HAWKING_RADIATION = ImmutableDamageSource(DamageSource("otm_hawking_radiation"))
|
||||
const val DAMAGE_EMP_NAME = "otm_emp"
|
||||
val DAMAGE_EMP: DamageSource = EMPDamageSource()
|
||||
const val DAMAGE_PLASMA_NAME = "otm_plasma"
|
||||
val DAMAGE_EMP: DamageSource = ImmutableDamageSource(EMPDamageSource())
|
||||
|
||||
init {
|
||||
DAMAGE_BECOME_ANDROID.bypassArmor().bypassInvul().bypassMagic()
|
||||
DAMAGE_BECOME_HUMANE.bypassArmor().bypassInvul().bypassMagic()
|
||||
DAMAGE_EVENT_HORIZON.bypassMagic().bypassArmor()
|
||||
DAMAGE_EMP.bypassMagic().bypassArmor()
|
||||
// DAMAGE_HAWKING_RADIATION.bypassMagic().bypassArmor();
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,6 @@ import net.minecraftforge.registries.IForgeRegistryEntry
|
||||
import net.minecraftforge.registries.RegistryObject
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
operator fun <T : IForgeRegistryEntry<T>> RegistryObject<T>.getValue(mItems: Any, property: KProperty<*>): T {
|
||||
operator fun <T : IForgeRegistryEntry<T>> RegistryObject<T>.getValue(thisRef: Any, property: KProperty<*>): T {
|
||||
return get()
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user