diff --git a/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java b/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java index e7d93be8b..b001f8e80 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java +++ b/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java @@ -18,6 +18,7 @@ import ru.dbotthepony.mc.otm.block.entity.blackhole.ExplosionQueue; import ru.dbotthepony.mc.otm.capability.MatteryCapability; import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability; import ru.dbotthepony.mc.otm.capability.drive.DrivePool; +import ru.dbotthepony.mc.otm.client.AndroidMenuKeyMapping; import ru.dbotthepony.mc.otm.client.ClientEventHandlerKt; import ru.dbotthepony.mc.otm.client.MatteryGUI; import ru.dbotthepony.mc.otm.client.model.GravitationStabilizerModel; @@ -105,6 +106,7 @@ public final class OverdriveThatMatters { EVENT_BUS.addListener(EventPriority.LOWEST, GlobalEventHandlerKt::onServerTick); EVENT_BUS.addListener(EventPriority.NORMAL, MatteryPlayerCapability.Companion::onPlayerTick); + EVENT_BUS.addListener(EventPriority.NORMAL, MatteryPlayerCapability.Companion::isMobEffectApplicable); EVENT_BUS.addListener(EventPriority.NORMAL, MatteryPlayerCapability.Companion::onHurtEvent); EVENT_BUS.addGenericListener(Entity.class, EventPriority.NORMAL, MatteryPlayerCapability.Companion::onAttachCapabilityEvent); EVENT_BUS.addListener(EventPriority.NORMAL, MatteryPlayerCapability.Companion::onPlayerChangeDimensionEvent); @@ -162,6 +164,9 @@ public final class OverdriveThatMatters { EVENT_BUS.addListener(EventPriority.NORMAL, TooltipsKt::tooltipEvent); } + FMLJavaModLoadingContext.get().getModEventBus().addListener(EventPriority.NORMAL, AndroidMenuKeyMapping.INSTANCE::register); + EVENT_BUS.addListener(EventPriority.NORMAL, AndroidMenuKeyMapping.INSTANCE::onRenderGuiEvent); + event.enqueueWork(GlobalEventHandlerKt::recordClientThread); TritaniumArmorModel.register(); diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/ServerConfig.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/ServerConfig.kt index 374abbdea..adda18850 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/ServerConfig.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/ServerConfig.kt @@ -82,7 +82,7 @@ object ServerConfig { init { specBuilder.comment("Serverside config, holds shared values that are required to be read by both client and server.").push("server") - specBuilder.comment("Energy batteries balance values").push("energy_batteries") + specBuilder.comment("Energy batteries balance values").push("energyBatteries") } val BATTERY_CRUDE = batteryValues(MNames.BATTERY_CRUDE, ImpreciseFraction(100_000), ImpreciseFraction(160), ImpreciseFraction(40), ImpreciseFraction(80_000)) @@ -99,7 +99,7 @@ object ServerConfig { init { specBuilder.pop() - specBuilder.comment("Matter capacitors and pattern drives balance values").push("matter_capacitors_and_drives") + specBuilder.comment("Matter capacitors and pattern drives balance values").push("matterCapacitorsAndDrives") } val MATTER_CAPACITOR_BASIC by specBuilder.defineImpreciseFraction(MNames.MATTER_CAPACITOR_BASIC, ImpreciseFraction(2), minimum = ImpreciseFraction.ONE_TENTH) @@ -131,16 +131,18 @@ object ServerConfig { init { specBuilder.pop() - specBuilder.comment("Tweaking of android players").push("android_player") + specBuilder.comment("Tweaking of android players").push("androidPlayer") } - val ANDROID_ENERGY_PER_HUNGER_POINT by specBuilder.defineImpreciseFraction("energy_per_hunger", ImpreciseFraction(1000), ImpreciseFraction.ZERO) + val ANDROID_ENERGY_PER_HUNGER_POINT by specBuilder.defineImpreciseFraction("energyPerHunger", ImpreciseFraction(1000), ImpreciseFraction.ZERO) val ANDROID_MAX_ENERGY by specBuilder.comment("Internal battery of every android has this much storage").defineImpreciseFraction("capacity", ImpreciseFraction(80_000), ImpreciseFraction.ZERO) + val NIGHT_VISION_POWER_DRAW by specBuilder.defineImpreciseFraction("nightVisionPowerDraw", ImpreciseFraction(8), ImpreciseFraction.ZERO) + init { specBuilder.pop() - specBuilder.comment("Tweaking of exosuits").push("exosuit_player") + specBuilder.comment("Tweaking of exosuits").push("exosuitPlayer") } val INFINITE_EXOSUIT_UPGRADES: Boolean by specBuilder.comment("Allows to apply the same upgrade over and over again.", "Obviously completely breaks balance.").define("infinite_upgrades", false) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchBuilder.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchBuilder.kt index 1ecf594fa..f6812b9d9 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchBuilder.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchBuilder.kt @@ -18,6 +18,11 @@ typealias ResearchCallback = ((research: AndroidResearch, feature: AndroidFeatur @Suppress("unused") class AndroidResearchBuilder( var experience: Int = 0, + var name: Component? = null, + var customDescription: MutableList? = null, + var hasDescription: Boolean = false, + var skinIcon: SkinElement? = null, + var iconText: Component? = null, ) { private val items = ArrayList() private val prerequisites = ArrayList ResourceLocation, Boolean>>() @@ -38,12 +43,6 @@ class AndroidResearchBuilder( val callbackResearched: ResearchCallback? ) - var name: Component? = null - var description: MutableList? = null - var hasDescription = false - var skinIcon: SkinElement? = null - var iconText: Component? = null - fun withIconText(icon: Component?): AndroidResearchBuilder { this.iconText = icon return this @@ -58,7 +57,7 @@ class AndroidResearchBuilder( fun withDescription(): AndroidResearchBuilder { this.hasDescription = true - this.description = null + this.customDescription = null return this } @@ -68,12 +67,12 @@ class AndroidResearchBuilder( } fun withDescription(vararg description: Component): AndroidResearchBuilder { - this.description = description.toMutableList() + this.customDescription = description.toMutableList() return this } fun withDescription(description: List): AndroidResearchBuilder { - this.description = ArrayList(description.size).also { it.addAll(description) } + this.customDescription = ArrayList(description.size).also { it.addAll(description) } return this } @@ -155,7 +154,7 @@ class AndroidResearchBuilder( val experience = this.experience val name = name?.copy() - val description = description?.let { + val description = customDescription?.let { val builder = ImmutableList.builder() for (component in it) { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidSwitchableFeature.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidSwitchableFeature.kt new file mode 100644 index 000000000..9b8568996 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidSwitchableFeature.kt @@ -0,0 +1,37 @@ +package ru.dbotthepony.mc.otm.android + +import com.mojang.blaze3d.vertex.PoseStack +import net.minecraft.nbt.CompoundTag +import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability +import ru.dbotthepony.mc.otm.core.set + +abstract class AndroidSwitchableFeature(type: AndroidFeatureType<*>, android: MatteryPlayerCapability) : AndroidFeature(type, android) { + var isActive by synchronizer.bool(setter = setter@{ value, access, setByRemote -> + if (value != access.read()) { + access.write(value) + + if (!setByRemote) { + if (value) { + applyModifiers() + } else { + removeModifiers() + } + } + } + }) + + open val allowToSwitchByPlayer: Boolean get() = true + + abstract fun renderIcon(stack: PoseStack, x: Float, y: Float, width: Float, height: Float) + + override fun serializeNBT(): CompoundTag { + return super.serializeNBT().also { + it["isActive"] = isActive + } + } + + override fun deserializeNBT(nbt: CompoundTag) { + super.deserializeNBT(nbt) + isActive = nbt.getBoolean("isActive") + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/NightVisionFeature.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/NightVisionFeature.kt new file mode 100644 index 000000000..58ecaf3a7 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/NightVisionFeature.kt @@ -0,0 +1,36 @@ +package ru.dbotthepony.mc.otm.android.feature + +import com.mojang.blaze3d.vertex.PoseStack +import net.minecraft.world.effect.MobEffectInstance +import net.minecraft.world.effect.MobEffects +import ru.dbotthepony.mc.otm.ServerConfig +import ru.dbotthepony.mc.otm.android.AndroidSwitchableFeature +import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability +import ru.dbotthepony.mc.otm.capability.extractEnergyInnerExact +import ru.dbotthepony.mc.otm.registry.AndroidFeatures +import ru.dbotthepony.mc.otm.registry.AndroidResearch + +class NightVisionFeature(android: MatteryPlayerCapability) : AndroidSwitchableFeature(AndroidFeatures.NIGHT_VISION, android) { + override fun tickServer() { + if (isActive) { + val effect = android.ply.activeEffectsMap[MobEffects.NIGHT_VISION] + + if ((effect == null || effect.duration < 220) && android.androidEnergy.extractEnergyInnerExact(ServerConfig.NIGHT_VISION_POWER_DRAW, true).isPositive) { + android.ply.addEffect(MobEffectInstance(MobEffects.NIGHT_VISION, 220)) + android.androidEnergy.extractEnergyInner(ServerConfig.NIGHT_VISION_POWER_DRAW, false) + } + } + } + + override fun removeModifiers() { + val effect = android.ply.activeEffectsMap[MobEffects.NIGHT_VISION] + + if (effect != null && effect.duration <= 222) { + android.ply.removeEffect(MobEffects.NIGHT_VISION) + } + } + + override fun renderIcon(stack: PoseStack, x: Float, y: Float, width: Float, height: Float) { + AndroidResearch.ICON_NIGHT_VISION.render(stack, x, y, width, height) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/MatteryPlayerCapability.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/MatteryPlayerCapability.kt index 076085147..278cf23db 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/MatteryPlayerCapability.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/MatteryPlayerCapability.kt @@ -31,6 +31,7 @@ import net.minecraftforge.event.entity.living.LivingDeathEvent import net.minecraftforge.event.entity.living.LivingDropsEvent import net.minecraftforge.event.entity.living.LivingHurtEvent import net.minecraftforge.event.entity.living.LivingSpawnEvent +import net.minecraftforge.event.entity.living.MobEffectEvent import net.minecraftforge.event.entity.player.EntityItemPickupEvent import net.minecraftforge.event.entity.player.PlayerEvent import net.minecraftforge.eventbus.api.Event @@ -49,6 +50,7 @@ import ru.dbotthepony.mc.otm.network.* import ru.dbotthepony.mc.otm.registry.AndroidFeatures import ru.dbotthepony.mc.otm.registry.MRegistry import java.util.* +import java.util.stream.Stream import kotlin.collections.ArrayDeque import kotlin.collections.ArrayList @@ -170,7 +172,7 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial private val deathLog = ArrayDeque>() - private val features = IdentityHashMap, AndroidFeature>() + private val featureMap = IdentityHashMap, AndroidFeature>() private val networkQueue = ArrayList() private val queuedTicks = ArrayList() private var tickedOnce = false @@ -252,7 +254,7 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial } } - val copy = ArrayList(features.values.size).also { it.addAll(features.values) } + val copy = ArrayList(featureMap.values.size).also { it.addAll(featureMap.values) } for (feature in copy) { removeFeature(feature.type) @@ -268,9 +270,11 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial } as T } + val features: Stream get() = featureMap.values.stream() + private fun addFeature(feature: AndroidFeature): Boolean { - if (features.containsKey(feature.type)) return false - features[feature.type] = feature + if (featureMap.containsKey(feature.type)) return false + featureMap[feature.type] = feature if (!ply.level.isClientSide) { queuedTicks.add(feature::applyModifiers) @@ -290,12 +294,12 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial @Suppress("unchecked_cast") fun addFeature(feature: AndroidFeatureType): T { - val get = features[feature] + val get = featureMap[feature] if (get != null) return get as T val factory = feature.create(this) - features[feature] = factory + featureMap[feature] = factory if (!ply.level.isClientSide) { queuedTicks.add(factory::applyModifiers) @@ -314,7 +318,7 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial } fun removeFeature(feature: AndroidFeatureType<*>): Boolean { - val removed = features.remove(feature) + val removed = featureMap.remove(feature) if (removed != null) { if (!ply.level.isClientSide) { @@ -332,21 +336,21 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial } fun hasFeature(feature: AndroidFeatureType<*>): Boolean { - return features.containsKey(feature) + return featureMap.containsKey(feature) } fun hasFeatureLevel(feature: AndroidFeatureType<*>, level: Int): Boolean { - val get = features[feature] ?: return false + val get = featureMap[feature] ?: return false return get.level >= level } @Suppress("unchecked_cast") fun getFeature(feature: AndroidFeatureType): T? { - return features[feature] as T? + return featureMap[feature] as T? } fun onHurt(event: LivingHurtEvent) { - for (feature in features.values) { + for (feature in featureMap.values) { feature.onHurt(event) } } @@ -401,7 +405,7 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial val featureList = ListTag() val researchList = ListTag() - for (feature in features.values) { + for (feature in featureMap.values) { featureList.add(feature.serializeNBT().also { it["id"] = feature.type.registryName!!.toString() }) @@ -449,7 +453,7 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial tag.map("androidEnergy", androidEnergy::deserializeNBT) - features.clear() + featureMap.clear() research.clear() for (featureTag in tag.getCompoundList("features")) { @@ -512,7 +516,7 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial } if (isAndroid) { - for (feature in features.values) { + for (feature in featureMap.values) { feature.tickClient() } } @@ -582,7 +586,7 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial stats.setExhaustion(stats.exhaustionLevel - (extracted / ServerConfig.ANDROID_ENERGY_PER_HUNGER_POINT).toFloat() * 4f) } - for (feature in features.values) { + for (feature in featureMap.values) { feature.tickServer() } } @@ -600,7 +604,7 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial instance.invalidateNetwork() } - for (feature in features.values) { + for (feature in featureMap.values) { feature.invalidateNetwork() } } @@ -629,7 +633,7 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial } } - for (instance in features.values) { + for (instance in featureMap.values) { val featurePayload = instance.collectNetworkPayload() if (featurePayload != null) { @@ -683,6 +687,14 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial } } + fun isMobEffectApplicable(event: MobEffectEvent.Applicable) { + event.entity.getCapability(MatteryCapability.MATTERY_PLAYER).ifPresentK { + if (it.isAndroid && ForgeRegistries.MOB_EFFECTS.tags()?.getTag(ANDROID_IMMUNE_EFFECTS)?.stream()?.anyMatch { it == event.effectInstance.effect } == true) { + event.result = Event.Result.DENY + } + } + } + fun onHurtEvent(event: LivingHurtEvent) { if (event.isCanceled) { return diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/AndroidMenuKeyMapping.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/AndroidMenuKeyMapping.kt new file mode 100644 index 000000000..22f5ab2a6 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/AndroidMenuKeyMapping.kt @@ -0,0 +1,161 @@ +package ru.dbotthepony.mc.otm.client + +import com.mojang.blaze3d.platform.InputConstants +import com.mojang.blaze3d.systems.RenderSystem +import net.minecraft.client.KeyMapping +import net.minecraftforge.client.event.RegisterKeyMappingsEvent +import net.minecraftforge.client.event.RenderGuiEvent +import net.minecraftforge.client.settings.KeyConflictContext +import org.lwjgl.glfw.GLFW.GLFW_CURSOR_DISABLED +import org.lwjgl.glfw.GLFW.GLFW_CURSOR_NORMAL +import ru.dbotthepony.mc.otm.OverdriveThatMatters +import ru.dbotthepony.mc.otm.android.AndroidFeatureType +import ru.dbotthepony.mc.otm.android.AndroidSwitchableFeature +import ru.dbotthepony.mc.otm.capability.matteryPlayer +import ru.dbotthepony.mc.otm.client.render.drawArc +import ru.dbotthepony.mc.otm.network.MatteryPlayerNetworkChannel +import ru.dbotthepony.mc.otm.network.SwitchAndroidFeaturePacket +import java.util.stream.Collectors +import kotlin.math.PI +import kotlin.math.acos +import kotlin.math.cos +import kotlin.math.pow +import kotlin.math.sin + +// TODO: forge's KeyMapping patch (ALL and MAP maps) is not thread safe... should we be worried? +object AndroidMenuKeyMapping : KeyMapping("key.otm.android_menu", KeyConflictContext.IN_GAME, InputConstants.Type.KEYSYM.getOrCreate(InputConstants.KEY_F), OverdriveThatMatters.MOD_ID) { + private var grabbedInput = false + private var selectedFeature: AndroidSwitchableFeature? = null + + override fun setDown(isDown: Boolean) { + val old = this.isDown + super.setDown(isDown) + + if (old != isDown) { + if ( + minecraft.screen == null && + isDown && + minecraft.player?.matteryPlayer?.isAndroid == true && + minecraft.player?.matteryPlayer?.features?.anyMatch { it is AndroidSwitchableFeature && it.allowToSwitchByPlayer } == true + ) { + grabbedInput = true + minecraft.mouseHandler.releaseMouse() + } else if (!isDown && grabbedInput) { + grabbedInput = false + + if (minecraft.screen == null) { + minecraft.mouseHandler.grabMouse() + + val selectedFeature = selectedFeature + if (selectedFeature != null) { + MatteryPlayerNetworkChannel.sendToServer(SwitchAndroidFeaturePacket(selectedFeature.type, !selectedFeature.isActive)) + } + } + } + + selectedFeature = null + } + } + + fun onRenderGuiEvent(event: RenderGuiEvent.Post) { + if (!grabbedInput) { + return + } + + val features = minecraft.player?.matteryPlayer?.features?.filter { it is AndroidSwitchableFeature && it.allowToSwitchByPlayer }?.collect(Collectors.toList()) as MutableList? ?: return + + if (features.isEmpty()) { + return + } + + RenderSystem.setShaderColor(0f, 0f, 0f, 0.75f) + + val size = minecraft.window.guiScaledHeight.coerceAtMost(minecraft.window.guiScaledWidth).toFloat() * 0.35f + + drawArc( + event.poseStack, + minecraft.window.guiScaledWidth / 2f, + minecraft.window.guiScaledHeight / 2f, + size, + size * 0.3f + ) + + RenderSystem.setShaderColor(85 / 255f, 197 / 255f, 255 / 255f, 0.5f) + + val degreePerSlice = PI * 2.0 / features.size + + var (mouseX, mouseY) = mousePos + + mouseX -= minecraft.window.width / 2.0 + mouseY -= minecraft.window.height / 2.0 + + //font.drawAligned(event.poseStack, mouseX.toString(), TextAlign.CENTER_CENTER, 100f, 40f, RGBAColor.WHITE) + //font.drawAligned(event.poseStack, mouseY.toString(), TextAlign.CENTER_CENTER, 100f, 60f, RGBAColor.WHITE) + + val length = (mouseX * mouseX + mouseY * mouseY).pow(0.5) + + if ((length / minecraft.window.guiScale) in (size * 0.3f) .. size) { + var deg = acos(mouseX / length) + + // opengl + // lower values are at bottom of screen + if (mouseY > 0.0) { + deg = -deg + } + + if (deg < 0.0) { + deg += PI * 2 + } + + //font.drawAligned(event.poseStack, deg.toString(), TextAlign.CENTER_CENTER, 100f, 20f, RGBAColor.WHITE) + + val index = (deg / degreePerSlice).toInt() + selectedFeature = features[index] + + //font.drawAligned(event.poseStack, index.toString(), TextAlign.CENTER_CENTER, 100f, 80f, RGBAColor.WHITE) + + RenderSystem.setShaderColor(85 / 255f, 197 / 255f, 255 / 255f, 0.3f) + + drawArc( + event.poseStack, + minecraft.window.guiScaledWidth / 2f, + minecraft.window.guiScaledHeight / 2f, + size * 1.2f, + size * 0.4f, + + startDegree = index * degreePerSlice, + endDegree = (index + 1) * degreePerSlice + ) + + //font.drawAligned(event.poseStack, (index * degreePerSlice).toString(), TextAlign.CENTER_CENTER, 100f, 100f, RGBAColor.WHITE) + } else { + selectedFeature = null + } + + RenderSystem.setShaderColor(1f, 1f, 1f, 1f) + + val iconSize = size * 0.25f + + event.poseStack.pushPose() + event.poseStack.translate(minecraft.window.guiScaledWidth.toDouble() / 2f, minecraft.window.guiScaledHeight.toDouble() / 2f, 0.0) + + if (features.size == 1) { + val feature = features.first() + + feature.renderIcon(event.poseStack, -iconSize / 2f, -size * 0.7f - iconSize / 2f, iconSize, iconSize) + } else { + for ((index, feature) in features.withIndex()) { + val sin = sin((index + 0.5) * degreePerSlice).toFloat() + val cos = cos((index + 0.5) * degreePerSlice).toFloat() + + feature.renderIcon(event.poseStack, -iconSize / 2f + size * 0.7f * cos, -size * 0.7f * sin - iconSize / 2f, iconSize, iconSize) + } + } + + event.poseStack.popPose() + } + + fun register(event: RegisterKeyMappingsEvent) { + event.register(this) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/Ext.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/Ext.kt index d28465c7e..8c0eb85e6 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/Ext.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/Ext.kt @@ -5,6 +5,7 @@ import net.minecraft.client.gui.Font import net.minecraft.client.resources.sounds.SimpleSoundInstance import net.minecraft.sounds.SoundEvents import org.lwjgl.glfw.GLFW +import org.lwjgl.glfw.GLFW.GLFW_CURSOR import java.nio.ByteBuffer import java.nio.ByteOrder import java.nio.DoubleBuffer @@ -35,6 +36,28 @@ fun setMousePosScaled(x: Float, y: Float) { private val cursorXPosBuf = ByteBuffer.allocateDirect(8).also { it.order(ByteOrder.LITTLE_ENDIAN) }.asDoubleBuffer() private val cursorYPosBuf = ByteBuffer.allocateDirect(8).also { it.order(ByteOrder.LITTLE_ENDIAN) }.asDoubleBuffer() +data class ScaledMousePos(val x: Double, val y: Double) { + fun set() { + setMousePos(x * minecraft.window.guiScale, y * minecraft.window.guiScale) + } + + fun move(x: Double = 0.0, y: Double = 0.0) { + GLFW.glfwSetCursorPos(minecraft.window.window, this.x * minecraft.window.guiScale + x, this.y * minecraft.window.guiScale + y) + } + + fun move(x: Float = 0.0f, y: Float = 0.0f) { + GLFW.glfwSetCursorPos(minecraft.window.window, this.x * minecraft.window.guiScale + x, this.y * minecraft.window.guiScale + y) + } + + fun moveScaled(x: Double = 0.0, y: Double = 0.0) { + GLFW.glfwSetCursorPos(minecraft.window.window, this.x * minecraft.window.guiScale + x * minecraft.window.guiScale, this.y * minecraft.window.guiScale + y * minecraft.window.guiScale) + } + + fun moveScaled(x: Float = 0.0f, y: Float = 0.0f) { + GLFW.glfwSetCursorPos(minecraft.window.window, this.x * minecraft.window.guiScale + x * minecraft.window.guiScale, this.y * minecraft.window.guiScale + y * minecraft.window.guiScale) + } +} + data class MousePos(val x: Double, val y: Double) { fun set() { setMousePos(x, y) @@ -69,6 +92,18 @@ val mousePos: MousePos get() { return MousePos(cursorXPosBuf.get(), cursorYPosBuf.get()) } +val scaledMousePos: ScaledMousePos get() { + cursorXPosBuf.position(0) + cursorYPosBuf.position(0) + + GLFW.glfwGetCursorPos(minecraft.window.window, cursorXPosBuf, cursorYPosBuf) + + cursorXPosBuf.position(0) + cursorYPosBuf.position(0) + + return ScaledMousePos(cursorXPosBuf.get() / minecraft.window.guiScale, cursorYPosBuf.get() / minecraft.window.guiScale) +} + fun moveMousePos(x: Double = 0.0, y: Double = 0.0) { val (currentX, currentY) = mousePos GLFW.glfwSetCursorPos(minecraft.window.window, currentX + x, currentY + y) @@ -88,3 +123,7 @@ fun moveMousePosScaled(x: Float = 0.0f, y: Float = 0.0f) { val (currentX, currentY) = mousePos GLFW.glfwSetCursorPos(minecraft.window.window, currentX + x * minecraft.window.guiScale, currentY + y * minecraft.window.guiScale) } + +var mouseInputMode: Int + get() = GLFW.glfwGetInputMode(minecraft.window.window, GLFW_CURSOR) + set(value) { GLFW.glfwSetInputMode(minecraft.window.window, GLFW_CURSOR, value) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/MatteryGUI.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/MatteryGUI.kt index 9dda57c4e..1c8ade729 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/MatteryGUI.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/MatteryGUI.kt @@ -97,7 +97,7 @@ object MatteryGUI { val deathLog = ArrayList>() - fun showIteration(event: RenderGuiEvent.Post) { + private fun showIteration(event: RenderGuiEvent.Post) { if (minecraft.player?.matteryPlayer?.isAndroid != true) { return } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/RenderHelper.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/RenderHelper.kt index 36592a228..6fccfd823 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/RenderHelper.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/RenderHelper.kt @@ -11,6 +11,7 @@ import org.lwjgl.opengl.GL11.GL_ALWAYS import org.lwjgl.opengl.GL11.GL_LESS import ru.dbotthepony.mc.otm.client.minecraft import ru.dbotthepony.mc.otm.core.RGBAColor +import kotlin.math.PI import kotlin.math.acos import kotlin.math.cos import kotlin.math.pow @@ -563,3 +564,93 @@ fun clearDepth(stack: PoseStack, x: Float, y: Float, width: Float, height: Float RenderSystem.setShader { oldShader } } } + +fun drawArc( + stack: PoseStack, + x: Float, + y: Float, + outerRadius: Float, + innerRadius: Float = 0f, + startDegree: Double = 0.0, + endDegree: Double = PI * 2.0, + steps: Int = (outerRadius / 8.0 * (endDegree - startDegree)).roundToInt().coerceAtLeast(12), + alignAtCenter: Boolean = true +) = drawArc(stack.last().pose(), x, y, outerRadius, innerRadius, startDegree, endDegree, steps, alignAtCenter) + +fun drawArc( + matrix: Matrix4f, + x: Float, + y: Float, + outerRadius: Float, + innerRadius: Float = 0f, + startDegree: Double = 0.0, + endDegree: Double = PI * 2.0, + steps: Int = (outerRadius / 8.0 * (endDegree - startDegree)).roundToInt().coerceAtLeast(12), + alignAtCenter: Boolean = true +) { + require(startDegree < endDegree) { "Invalid arc degree range: $startDegree - $endDegree" } + require(steps >= 0) { "Invalid amount of arc steps: $steps" } + require(innerRadius >= 0f) { "Invalid inner radius of arc: $innerRadius" } + + RenderSystem.setShader(GameRenderer::getPositionShader) + RenderSystem.enableBlend() + RenderSystem.defaultBlendFunc() + RenderSystem.depthFunc(GL_ALWAYS) + + @Suppress("name_shadowing") + var x = x + + @Suppress("name_shadowing") + var y = y + + @Suppress("name_shadowing") + val startDegree = startDegree + PI / 2.0 + + @Suppress("name_shadowing") + val endDegree = endDegree + PI / 2.0 + + if (!alignAtCenter) { + x += outerRadius + y += outerRadius + } + + val builder = tesselator.builder + + if (innerRadius == 0f) { + if (steps >= 1) { + val singleStep = (endDegree - startDegree) / steps + + builder.begin(VertexFormat.Mode.TRIANGLE_FAN, DefaultVertexFormat.POSITION) + + builder.vertex(matrix, x, y, zLevel).endVertex() + + for (i in 0 .. steps) { + val sin = sin(startDegree + i * singleStep).toFloat() + val cos = cos(startDegree + i * singleStep).toFloat() + + builder.vertex(matrix, x + outerRadius * sin, y + cos * outerRadius, zLevel).endVertex() + } + + BufferUploader.drawWithShader(builder.end()) + } + } else { + val singleStep = (endDegree - startDegree) / (steps + 1) + + builder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION) + + for (i in 0 .. steps) { + val sin = sin(startDegree + i * singleStep).toFloat() + val cos = cos(startDegree + i * singleStep).toFloat() + + val sin2 = sin(startDegree + (i + 1) * singleStep).toFloat() + val cos2 = cos(startDegree + (i + 1) * singleStep).toFloat() + + builder.vertex(matrix, x + outerRadius * sin, y + cos * outerRadius, zLevel).endVertex() + builder.vertex(matrix, x + outerRadius * sin2, y + cos2 * outerRadius, zLevel).endVertex() + builder.vertex(matrix, x + innerRadius * sin2, y + cos2 * innerRadius, zLevel).endVertex() + builder.vertex(matrix, x + innerRadius * sin, y + cos * innerRadius, zLevel).endVertex() + } + + BufferUploader.drawWithShader(builder.end()) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/network/MatteryNetworkChannel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/network/MatteryNetworkChannel.kt index 8e43c9c29..d238dc8b1 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/network/MatteryNetworkChannel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/network/MatteryNetworkChannel.kt @@ -72,6 +72,10 @@ abstract class MatteryNetworkChannel(val version: String, val name: String) { handler: BiConsumer>, direction: NetworkDirection? = null ) { + if (nextNetworkPacketID >= 256) { + throw IndexOutOfBoundsException("Network message ID overflow!") + } + @Suppress("INACCESSIBLE_TYPE") channel.registerMessage(nextNetworkPacketID++, packetClass, writer, reader, handler, if (direction == null) Optional.empty() else Optional.of(direction)) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/network/MatteryPlayerNetworkChannel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/network/MatteryPlayerNetworkChannel.kt index 4de9a66d0..c0ee07b09 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/network/MatteryPlayerNetworkChannel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/network/MatteryPlayerNetworkChannel.kt @@ -12,10 +12,12 @@ import ru.dbotthepony.mc.otm.android.AndroidFeature import ru.dbotthepony.mc.otm.android.AndroidFeatureType import ru.dbotthepony.mc.otm.android.AndroidResearch import ru.dbotthepony.mc.otm.android.AndroidResearchType +import ru.dbotthepony.mc.otm.android.AndroidSwitchableFeature import ru.dbotthepony.mc.otm.capability.matteryPlayer import ru.dbotthepony.mc.otm.client.MatteryGUI import ru.dbotthepony.mc.otm.client.minecraft import ru.dbotthepony.mc.otm.menu.AndroidStationMenu +import ru.dbotthepony.mc.otm.registry.AndroidFeatures import ru.dbotthepony.mc.otm.registry.MRegistry import java.io.ByteArrayInputStream import java.io.OutputStream @@ -320,6 +322,37 @@ object ExoSuitMenuClose : MatteryPacket { } } +class SwitchAndroidFeaturePacket(val type: AndroidFeatureType<*>, val newState: Boolean) : MatteryPacket { + override fun write(buff: FriendlyByteBuf) { + buff.writeInt(MRegistry.ANDROID_FEATURES.getID(type)) + buff.writeBoolean(newState) + } + + override fun play(context: Supplier) { + context.packetHandled = true + + context.enqueueWork { + val matteryPlayer = context.sender?.matteryPlayer ?: return@enqueueWork + + if (!matteryPlayer.isAndroid) { + return@enqueueWork + } + + val feature = matteryPlayer.getFeature(type) ?: return@enqueueWork + + if (feature is AndroidSwitchableFeature && feature.allowToSwitchByPlayer) { + feature.isActive = newState + } + } + } + + companion object { + fun read(buff: FriendlyByteBuf): SwitchAndroidFeaturePacket { + return SwitchAndroidFeaturePacket(MRegistry.ANDROID_FEATURES.getValue(buff.readInt()) ?: AndroidFeatures.AIR_BAGS, buff.readBoolean()) + } + } +} + object MatteryPlayerNetworkChannel : MatteryNetworkChannel( version = "1", name = "player" @@ -337,7 +370,10 @@ object MatteryPlayerNetworkChannel : MatteryNetworkChannel( add(ExoSuitCarriedPacket::class, ExoSuitCarriedPacket.Companion::read, PLAY_TO_CLIENT) add(ExoSuitSlotPacket::class, ExoSuitSlotPacket.Companion::read, PLAY_TO_CLIENT) add(ExoSuitMenuInitPacket::class, ExoSuitMenuInitPacket.Companion::read, PLAY_TO_CLIENT) + add(ExoSuitMenuOpen::class, { ExoSuitMenuOpen }, PLAY_TO_SERVER) add(ExoSuitMenuClose::class, { ExoSuitMenuClose }, PLAY_TO_SERVER) + + add(SwitchAndroidFeaturePacket::class, SwitchAndroidFeaturePacket.Companion::read, PLAY_TO_SERVER) } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/AndroidFeatures.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/AndroidFeatures.kt index 29a71af58..2fd3366f3 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/AndroidFeatures.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/AndroidFeatures.kt @@ -18,6 +18,7 @@ object AndroidFeatures { val NANOBOTS_REGENERATION: AndroidFeatureType<*> by registry.register(MNames.NANOBOTS_REGENERATION) { AndroidFeatureType(::NanobotsRegeneration) } val NANOBOTS_ARMOR: AndroidFeatureType<*> by registry.register(MNames.NANOBOTS_ARMOR) { AndroidFeatureType(::NanobotsArmor) } val EXTENDED_REACH: AndroidFeatureType<*> by registry.register(MNames.EXTENDED_REACH) { AndroidFeatureType(::ExtendedReach) } + val NIGHT_VISION: AndroidFeatureType<*> by registry.register(MNames.NIGHT_VISION) { AndroidFeatureType(::NightVisionFeature) } internal fun register(bus: IEventBus) { registry.register(bus) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/AndroidResearch.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/AndroidResearch.kt index 76e65a39c..33dcd99dd 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/AndroidResearch.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/AndroidResearch.kt @@ -118,6 +118,15 @@ object AndroidResearch { .build() } + val NIGHT_VISION: AndroidResearchType<*> by registry.register(MNames.NIGHT_VISION) { + AndroidResearchBuilder() + .withExperience(40) + .withDescription() + .withIcon(ICON_NIGHT_VISION) + .addFeatureResult(AndroidFeatures.NIGHT_VISION) + .build() + } + val NANOBOTS: AndroidResearchType<*> by registry.register(MNames.NANOBOTS) { AndroidResearchBuilder() .withExperience(15) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MNames.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MNames.kt index 5b4b22f4b..e359b674d 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MNames.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MNames.kt @@ -207,6 +207,7 @@ object MNames { const val HYDRAULICS_OVERLOAD_3 = "hydraulics_overload_3" const val EXTENDED_REACH = "extended_reach" + const val NIGHT_VISION = "night_vision" const val IMPROVED_LIMBS = "improved_limbs" // stats