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.MatteryCapability;
import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability; import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability;
import ru.dbotthepony.mc.otm.capability.drive.DrivePool; 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.ClientEventHandlerKt;
import ru.dbotthepony.mc.otm.client.MatteryGUI; import ru.dbotthepony.mc.otm.client.MatteryGUI;
import ru.dbotthepony.mc.otm.client.model.GravitationStabilizerModel; 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.LOWEST, GlobalEventHandlerKt::onServerTick);
EVENT_BUS.addListener(EventPriority.NORMAL, MatteryPlayerCapability.Companion::onPlayerTick); 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.addListener(EventPriority.NORMAL, MatteryPlayerCapability.Companion::onHurtEvent);
EVENT_BUS.addGenericListener(Entity.class, EventPriority.NORMAL, MatteryPlayerCapability.Companion::onAttachCapabilityEvent); EVENT_BUS.addGenericListener(Entity.class, EventPriority.NORMAL, MatteryPlayerCapability.Companion::onAttachCapabilityEvent);
EVENT_BUS.addListener(EventPriority.NORMAL, MatteryPlayerCapability.Companion::onPlayerChangeDimensionEvent); EVENT_BUS.addListener(EventPriority.NORMAL, MatteryPlayerCapability.Companion::onPlayerChangeDimensionEvent);
@ -162,6 +164,9 @@ public final class OverdriveThatMatters {
EVENT_BUS.addListener(EventPriority.NORMAL, TooltipsKt::tooltipEvent); 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); event.enqueueWork(GlobalEventHandlerKt::recordClientThread);
TritaniumArmorModel.register(); TritaniumArmorModel.register();

View File

@ -82,7 +82,7 @@ object ServerConfig {
init { init {
specBuilder.comment("Serverside config, holds shared values that are required to be read by both client and server.").push("server") 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)) val BATTERY_CRUDE = batteryValues(MNames.BATTERY_CRUDE, ImpreciseFraction(100_000), ImpreciseFraction(160), ImpreciseFraction(40), ImpreciseFraction(80_000))
@ -99,7 +99,7 @@ object ServerConfig {
init { init {
specBuilder.pop() 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) val MATTER_CAPACITOR_BASIC by specBuilder.defineImpreciseFraction(MNames.MATTER_CAPACITOR_BASIC, ImpreciseFraction(2), minimum = ImpreciseFraction.ONE_TENTH)
@ -131,16 +131,18 @@ object ServerConfig {
init { init {
specBuilder.pop() 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 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 { init {
specBuilder.pop() 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) 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") @Suppress("unused")
class AndroidResearchBuilder( class AndroidResearchBuilder(
var experience: Int = 0, 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 items = ArrayList<ItemStack>()
private val prerequisites = ArrayList<Pair<() -> ResourceLocation, Boolean>>() private val prerequisites = ArrayList<Pair<() -> ResourceLocation, Boolean>>()
@ -38,12 +43,6 @@ class AndroidResearchBuilder(
val callbackResearched: ResearchCallback? 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 { fun withIconText(icon: Component?): AndroidResearchBuilder {
this.iconText = icon this.iconText = icon
return this return this
@ -58,7 +57,7 @@ class AndroidResearchBuilder(
fun withDescription(): AndroidResearchBuilder { fun withDescription(): AndroidResearchBuilder {
this.hasDescription = true this.hasDescription = true
this.description = null this.customDescription = null
return this return this
} }
@ -68,12 +67,12 @@ class AndroidResearchBuilder(
} }
fun withDescription(vararg description: Component): AndroidResearchBuilder { fun withDescription(vararg description: Component): AndroidResearchBuilder {
this.description = description.toMutableList() this.customDescription = description.toMutableList()
return this return this
} }
fun withDescription(description: List<Component>): AndroidResearchBuilder { 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 return this
} }
@ -155,7 +154,7 @@ class AndroidResearchBuilder(
val experience = this.experience val experience = this.experience
val name = name?.copy() val name = name?.copy()
val description = description?.let { val description = customDescription?.let {
val builder = ImmutableList.builder<Component>() val builder = ImmutableList.builder<Component>()
for (component in it) { 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.LivingDropsEvent
import net.minecraftforge.event.entity.living.LivingHurtEvent import net.minecraftforge.event.entity.living.LivingHurtEvent
import net.minecraftforge.event.entity.living.LivingSpawnEvent 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.EntityItemPickupEvent
import net.minecraftforge.event.entity.player.PlayerEvent import net.minecraftforge.event.entity.player.PlayerEvent
import net.minecraftforge.eventbus.api.Event 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.AndroidFeatures
import ru.dbotthepony.mc.otm.registry.MRegistry import ru.dbotthepony.mc.otm.registry.MRegistry
import java.util.* import java.util.*
import java.util.stream.Stream
import kotlin.collections.ArrayDeque import kotlin.collections.ArrayDeque
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
@ -170,7 +172,7 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
private val deathLog = ArrayDeque<Pair<Int, Component>>() 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 networkQueue = ArrayList<Any>()
private val queuedTicks = ArrayList<Runnable>() private val queuedTicks = ArrayList<Runnable>()
private var tickedOnce = false 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) { for (feature in copy) {
removeFeature(feature.type) removeFeature(feature.type)
@ -268,9 +270,11 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
} as T } as T
} }
val features: Stream<out AndroidFeature> get() = featureMap.values.stream()
private fun addFeature(feature: AndroidFeature): Boolean { private fun addFeature(feature: AndroidFeature): Boolean {
if (features.containsKey(feature.type)) return false if (featureMap.containsKey(feature.type)) return false
features[feature.type] = feature featureMap[feature.type] = feature
if (!ply.level.isClientSide) { if (!ply.level.isClientSide) {
queuedTicks.add(feature::applyModifiers) queuedTicks.add(feature::applyModifiers)
@ -290,12 +294,12 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
@Suppress("unchecked_cast") @Suppress("unchecked_cast")
fun <T : AndroidFeature> addFeature(feature: AndroidFeatureType<T>): T { fun <T : AndroidFeature> addFeature(feature: AndroidFeatureType<T>): T {
val get = features[feature] val get = featureMap[feature]
if (get != null) return get as T if (get != null) return get as T
val factory = feature.create(this) val factory = feature.create(this)
features[feature] = factory featureMap[feature] = factory
if (!ply.level.isClientSide) { if (!ply.level.isClientSide) {
queuedTicks.add(factory::applyModifiers) queuedTicks.add(factory::applyModifiers)
@ -314,7 +318,7 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
} }
fun removeFeature(feature: AndroidFeatureType<*>): Boolean { fun removeFeature(feature: AndroidFeatureType<*>): Boolean {
val removed = features.remove(feature) val removed = featureMap.remove(feature)
if (removed != null) { if (removed != null) {
if (!ply.level.isClientSide) { if (!ply.level.isClientSide) {
@ -332,21 +336,21 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
} }
fun hasFeature(feature: AndroidFeatureType<*>): Boolean { fun hasFeature(feature: AndroidFeatureType<*>): Boolean {
return features.containsKey(feature) return featureMap.containsKey(feature)
} }
fun hasFeatureLevel(feature: AndroidFeatureType<*>, level: Int): Boolean { fun hasFeatureLevel(feature: AndroidFeatureType<*>, level: Int): Boolean {
val get = features[feature] ?: return false val get = featureMap[feature] ?: return false
return get.level >= level return get.level >= level
} }
@Suppress("unchecked_cast") @Suppress("unchecked_cast")
fun <T : AndroidFeature> getFeature(feature: AndroidFeatureType<T>): T? { fun <T : AndroidFeature> getFeature(feature: AndroidFeatureType<T>): T? {
return features[feature] as T? return featureMap[feature] as T?
} }
fun onHurt(event: LivingHurtEvent) { fun onHurt(event: LivingHurtEvent) {
for (feature in features.values) { for (feature in featureMap.values) {
feature.onHurt(event) feature.onHurt(event)
} }
} }
@ -401,7 +405,7 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
val featureList = ListTag() val featureList = ListTag()
val researchList = ListTag() val researchList = ListTag()
for (feature in features.values) { for (feature in featureMap.values) {
featureList.add(feature.serializeNBT().also { featureList.add(feature.serializeNBT().also {
it["id"] = feature.type.registryName!!.toString() it["id"] = feature.type.registryName!!.toString()
}) })
@ -449,7 +453,7 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
tag.map("androidEnergy", androidEnergy::deserializeNBT) tag.map("androidEnergy", androidEnergy::deserializeNBT)
features.clear() featureMap.clear()
research.clear() research.clear()
for (featureTag in tag.getCompoundList("features")) { for (featureTag in tag.getCompoundList("features")) {
@ -512,7 +516,7 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
} }
if (isAndroid) { if (isAndroid) {
for (feature in features.values) { for (feature in featureMap.values) {
feature.tickClient() 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) 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() feature.tickServer()
} }
} }
@ -600,7 +604,7 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
instance.invalidateNetwork() instance.invalidateNetwork()
} }
for (feature in features.values) { for (feature in featureMap.values) {
feature.invalidateNetwork() 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() val featurePayload = instance.collectNetworkPayload()
if (featurePayload != null) { 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) { fun onHurtEvent(event: LivingHurtEvent) {
if (event.isCanceled) { if (event.isCanceled) {
return 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.client.resources.sounds.SimpleSoundInstance
import net.minecraft.sounds.SoundEvents import net.minecraft.sounds.SoundEvents
import org.lwjgl.glfw.GLFW import org.lwjgl.glfw.GLFW
import org.lwjgl.glfw.GLFW.GLFW_CURSOR
import java.nio.ByteBuffer import java.nio.ByteBuffer
import java.nio.ByteOrder import java.nio.ByteOrder
import java.nio.DoubleBuffer 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 cursorXPosBuf = ByteBuffer.allocateDirect(8).also { it.order(ByteOrder.LITTLE_ENDIAN) }.asDoubleBuffer()
private val cursorYPosBuf = 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) { data class MousePos(val x: Double, val y: Double) {
fun set() { fun set() {
setMousePos(x, y) setMousePos(x, y)
@ -69,6 +92,18 @@ val mousePos: MousePos get() {
return MousePos(cursorXPosBuf.get(), cursorYPosBuf.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) { fun moveMousePos(x: Double = 0.0, y: Double = 0.0) {
val (currentX, currentY) = mousePos val (currentX, currentY) = mousePos
GLFW.glfwSetCursorPos(minecraft.window.window, currentX + x, currentY + y) 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 val (currentX, currentY) = mousePos
GLFW.glfwSetCursorPos(minecraft.window.window, currentX + x * minecraft.window.guiScale, currentY + y * minecraft.window.guiScale) 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>>() val deathLog = ArrayList<Pair<Int, Component>>()
fun showIteration(event: RenderGuiEvent.Post) { private fun showIteration(event: RenderGuiEvent.Post) {
if (minecraft.player?.matteryPlayer?.isAndroid != true) { if (minecraft.player?.matteryPlayer?.isAndroid != true) {
return return
} }

View File

@ -11,6 +11,7 @@ import org.lwjgl.opengl.GL11.GL_ALWAYS
import org.lwjgl.opengl.GL11.GL_LESS import org.lwjgl.opengl.GL11.GL_LESS
import ru.dbotthepony.mc.otm.client.minecraft import ru.dbotthepony.mc.otm.client.minecraft
import ru.dbotthepony.mc.otm.core.RGBAColor import ru.dbotthepony.mc.otm.core.RGBAColor
import kotlin.math.PI
import kotlin.math.acos import kotlin.math.acos
import kotlin.math.cos import kotlin.math.cos
import kotlin.math.pow import kotlin.math.pow
@ -563,3 +564,93 @@ fun clearDepth(stack: PoseStack, x: Float, y: Float, width: Float, height: Float
RenderSystem.setShader { oldShader } 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>>, handler: BiConsumer<T, Supplier<NetworkEvent.Context>>,
direction: NetworkDirection? = null direction: NetworkDirection? = null
) { ) {
if (nextNetworkPacketID >= 256) {
throw IndexOutOfBoundsException("Network message ID overflow!")
}
@Suppress("INACCESSIBLE_TYPE") @Suppress("INACCESSIBLE_TYPE")
channel.registerMessage(nextNetworkPacketID++, packetClass, writer, reader, handler, if (direction == null) Optional.empty() else Optional.of(direction)) 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.AndroidFeatureType
import ru.dbotthepony.mc.otm.android.AndroidResearch import ru.dbotthepony.mc.otm.android.AndroidResearch
import ru.dbotthepony.mc.otm.android.AndroidResearchType 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.capability.matteryPlayer
import ru.dbotthepony.mc.otm.client.MatteryGUI import ru.dbotthepony.mc.otm.client.MatteryGUI
import ru.dbotthepony.mc.otm.client.minecraft import ru.dbotthepony.mc.otm.client.minecraft
import ru.dbotthepony.mc.otm.menu.AndroidStationMenu import ru.dbotthepony.mc.otm.menu.AndroidStationMenu
import ru.dbotthepony.mc.otm.registry.AndroidFeatures
import ru.dbotthepony.mc.otm.registry.MRegistry import ru.dbotthepony.mc.otm.registry.MRegistry
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.io.OutputStream 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( object MatteryPlayerNetworkChannel : MatteryNetworkChannel(
version = "1", version = "1",
name = "player" name = "player"
@ -337,7 +370,10 @@ object MatteryPlayerNetworkChannel : MatteryNetworkChannel(
add(ExoSuitCarriedPacket::class, ExoSuitCarriedPacket.Companion::read, PLAY_TO_CLIENT) add(ExoSuitCarriedPacket::class, ExoSuitCarriedPacket.Companion::read, PLAY_TO_CLIENT)
add(ExoSuitSlotPacket::class, ExoSuitSlotPacket.Companion::read, PLAY_TO_CLIENT) add(ExoSuitSlotPacket::class, ExoSuitSlotPacket.Companion::read, PLAY_TO_CLIENT)
add(ExoSuitMenuInitPacket::class, ExoSuitMenuInitPacket.Companion::read, PLAY_TO_CLIENT) add(ExoSuitMenuInitPacket::class, ExoSuitMenuInitPacket.Companion::read, PLAY_TO_CLIENT)
add(ExoSuitMenuOpen::class, { ExoSuitMenuOpen }, PLAY_TO_SERVER) add(ExoSuitMenuOpen::class, { ExoSuitMenuOpen }, PLAY_TO_SERVER)
add(ExoSuitMenuClose::class, { ExoSuitMenuClose }, 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_REGENERATION: AndroidFeatureType<*> by registry.register(MNames.NANOBOTS_REGENERATION) { AndroidFeatureType(::NanobotsRegeneration) }
val NANOBOTS_ARMOR: AndroidFeatureType<*> by registry.register(MNames.NANOBOTS_ARMOR) { AndroidFeatureType(::NanobotsArmor) } 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 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) { internal fun register(bus: IEventBus) {
registry.register(bus) registry.register(bus)

View File

@ -118,6 +118,15 @@ object AndroidResearch {
.build() .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) { val NANOBOTS: AndroidResearchType<*> by registry.register(MNames.NANOBOTS) {
AndroidResearchBuilder() AndroidResearchBuilder()
.withExperience(15) .withExperience(15)

View File

@ -207,6 +207,7 @@ object MNames {
const val HYDRAULICS_OVERLOAD_3 = "hydraulics_overload_3" const val HYDRAULICS_OVERLOAD_3 = "hydraulics_overload_3"
const val EXTENDED_REACH = "extended_reach" const val EXTENDED_REACH = "extended_reach"
const val NIGHT_VISION = "night_vision"
const val IMPROVED_LIMBS = "improved_limbs" const val IMPROVED_LIMBS = "improved_limbs"
// stats // stats