Android switchable features, android night vision

Achieved with cry engine two™️
This commit is contained in:
DBotThePony 2022-09-19 22:15:27 +07:00
parent ca70916331
commit 71f3ad133e
Signed by: DBot
GPG Key ID: DCC23B5715498507
15 changed files with 466 additions and 33 deletions

View File

@ -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();

View File

@ -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)

View File

@ -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<Component>? = null,
var hasDescription: Boolean = false,
var skinIcon: SkinElement? = null,
var iconText: Component? = null,
) {
private val items = ArrayList<ItemStack>()
private val prerequisites = ArrayList<Pair<() -> ResourceLocation, Boolean>>()
@ -38,12 +43,6 @@ class AndroidResearchBuilder(
val callbackResearched: ResearchCallback?
)
var name: Component? = null
var description: MutableList<Component>? = 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<Component>): AndroidResearchBuilder {
this.description = ArrayList<Component>(description.size).also { it.addAll(description) }
this.customDescription = ArrayList<Component>(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<Component>()
for (component in it) {

View File

@ -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")
}
}

View File

@ -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)
}
}

View File

@ -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<Pair<Int, Component>>()
private val features = IdentityHashMap<AndroidFeatureType<*>, AndroidFeature>()
private val featureMap = IdentityHashMap<AndroidFeatureType<*>, AndroidFeature>()
private val networkQueue = ArrayList<Any>()
private val queuedTicks = ArrayList<Runnable>()
private var tickedOnce = false
@ -252,7 +254,7 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
}
}
val copy = ArrayList<AndroidFeature>(features.values.size).also { it.addAll(features.values) }
val copy = ArrayList<AndroidFeature>(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<out AndroidFeature> 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 <T : AndroidFeature> addFeature(feature: AndroidFeatureType<T>): 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 <T : AndroidFeature> getFeature(feature: AndroidFeatureType<T>): 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

View File

@ -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<AndroidSwitchableFeature>? ?: 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)
}
}

View File

@ -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) }

View File

@ -97,7 +97,7 @@ object MatteryGUI {
val deathLog = ArrayList<Pair<Int, Component>>()
fun showIteration(event: RenderGuiEvent.Post) {
private fun showIteration(event: RenderGuiEvent.Post) {
if (minecraft.player?.matteryPlayer?.isAndroid != true) {
return
}

View File

@ -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())
}
}

View File

@ -72,6 +72,10 @@ abstract class MatteryNetworkChannel(val version: String, val name: String) {
handler: BiConsumer<T, Supplier<NetworkEvent.Context>>,
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))
}

View File

@ -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<NetworkEvent.Context>) {
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)
}
}

View File

@ -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)

View File

@ -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)

View File

@ -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