Move Android capability to kotlin and fix issues along the way

This commit is contained in:
DBotThePony 2021-12-31 18:09:16 +07:00
parent 0acbdc8c69
commit 9efb4d6176
Signed by: DBot
GPG Key ID: DCC23B5715498507
6 changed files with 548 additions and 706 deletions

View File

@ -126,8 +126,8 @@ public class OverdriveThatMatters {
// Register ourselves for server and other game events we are interested in
MinecraftForge.EVENT_BUS.register(this);
MinecraftForge.EVENT_BUS.register(AndroidCapabilityPlayer.class);
MinecraftForge.EVENT_BUS.register(AndroidCapability.class);
MinecraftForge.EVENT_BUS.register(AndroidCapabilityPlayer.Companion);
MinecraftForge.EVENT_BUS.register(AndroidCapability.Companion);
MinecraftForge.EVENT_BUS.register(MatterRegistry.class);
MinecraftForge.EVENT_BUS.register(BlockEntityBlackHole.BlackHoleExplosionQueue.class);

View File

@ -1,328 +1,285 @@
package ru.dbotthepony.mc.otm.capability.android;
package ru.dbotthepony.mc.otm.capability.android
import net.minecraft.ChatFormatting;
import net.minecraft.MethodsReturnNonnullByDefault;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.Tag;
import net.minecraft.network.chat.TranslatableComponent;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Player;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.event.AttachCapabilitiesEvent;
import net.minecraftforge.event.entity.player.PlayerEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import ru.dbotthepony.mc.otm.Registry;
import ru.dbotthepony.mc.otm.android.AndroidResearch;
import ru.dbotthepony.mc.otm.android.AndroidResearchType;
import ru.dbotthepony.mc.otm.capability.MatteryCapability;
import ru.dbotthepony.mc.otm.core.Fraction;
import ru.dbotthepony.mc.otm.network.android.AndroidResearchPacket;
import ru.dbotthepony.mc.otm.network.android.AndroidStatusPacket;
import net.minecraft.ChatFormatting
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.ListTag
import net.minecraft.nbt.Tag
import net.minecraft.network.chat.TranslatableComponent
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.entity.Entity
import net.minecraft.world.entity.player.Player
import net.minecraftforge.event.AttachCapabilitiesEvent
import net.minecraftforge.event.entity.player.PlayerEvent.Clone
import net.minecraftforge.event.entity.player.PlayerEvent.PlayerChangedDimensionEvent
import net.minecraftforge.eventbus.api.SubscribeEvent
import ru.dbotthepony.mc.otm.Registry
import ru.dbotthepony.mc.otm.android.AndroidResearch
import ru.dbotthepony.mc.otm.android.AndroidResearchType
import ru.dbotthepony.mc.otm.capability.MatteryCapability
import ru.dbotthepony.mc.otm.core.Fraction
import ru.dbotthepony.mc.otm.network.android.AndroidResearchPacket
import ru.dbotthepony.mc.otm.network.android.AndroidStatusPacket
import ru.dbotthepony.mc.otm.set
import java.util.*
import javax.annotation.Nonnull;
import javax.annotation.ParametersAreNonnullByDefault;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Objects;
class AndroidCapabilityPlayer(@JvmField val ply: Player) : AndroidCapability(ply) {
@JvmField
var is_android = false
private var network_is_android = false
@MethodsReturnNonnullByDefault
@ParametersAreNonnullByDefault
public class AndroidCapabilityPlayer extends AndroidCapability {
public boolean is_android = false;
private boolean network_is_android = false;
@JvmField
var will_become_android = false
private var network_will_become_android = false
private val research = ArrayList<AndroidResearch>()
public boolean will_become_android = false;
private boolean network_will_become_android = false;
override fun invalidateNetworkState() {
super.invalidateNetworkState()
private final ArrayList<AndroidResearch> research_list = new ArrayList<>();
network_is_android = false
network_will_become_android = false
@Override
public void invalidateNetworkState() {
super.invalidateNetworkState();
network_is_android = false;
network_will_become_android = false;
for (var instance : research_list) {
instance.dirty = true;
for (instance in research) {
instance.dirty = true
}
}
@Override
public boolean isAndroid() {
return is_android;
override fun isAndroid(): Boolean = is_android
fun isEverAndroid(): Boolean = is_android || will_become_android
fun becomeAndroidSoft() {
if (is_android || will_become_android) return
will_become_android = true
(ply as? ServerPlayer)?.displayClientMessage(TranslatableComponent("otm.pill.message").withStyle(ChatFormatting.GRAY), false)
}
public boolean isEverAndroid() {
return is_android || will_become_android;
fun becomeAndroid() {
if (is_android) return
is_android = true
will_become_android = false
energy_stored = Fraction(60000)
energy_stored_max = Fraction(60000)
}
public void becomeAndroidSoft() {
if (is_android || will_become_android)
return;
fun becomeAndroidAndKill() {
if (is_android) return
will_become_android = true;
if (ent instanceof ServerPlayer ply) {
ply.displayClientMessage(new TranslatableComponent("otm.pill.message").withStyle(ChatFormatting.GRAY), false);
}
becomeAndroid()
ply.hurt(Registry.DAMAGE_BECOME_ANDROID, ply.maxHealth * 2)
}
public void becomeAndroid() {
if (is_android)
return;
fun becomeHumane() {
if (will_become_android) will_become_android = false
if (!is_android) return
is_android = true;
will_become_android = false;
energy_stored = new Fraction(60_000);
energy_stored_max = new Fraction(60_000);
is_android = false
energy_stored = Fraction(0)
energy_stored_max = Fraction(60000)
dropBattery()
}
public void becomeAndroidAndKill() {
if (is_android)
return;
fun becomeHumaneAndKill() {
if (will_become_android) will_become_android = false
if (!is_android) return
becomeAndroid();
ply.hurt(Registry.DAMAGE_BECOME_ANDROID, ply.getMaxHealth() * 2);
becomeHumane()
ply.hurt(Registry.DAMAGE_BECOME_HUMANE, ply.maxHealth * 2)
}
public void becomeHumane() {
if (will_become_android)
will_become_android = false;
override fun deserializeNBT(compound: CompoundTag?) {
super.deserializeNBT(compound!!)
if (!is_android)
return;
is_android = compound.getBoolean("is_android")
will_become_android = compound.getBoolean("will_become_android")
research.clear()
is_android = false;
val list = compound.getList("research", Tag.TAG_COMPOUND.toInt())
energy_stored = new Fraction(0);
energy_stored_max = new Fraction(60_000);
dropBattery();
}
public void becomeHumaneAndKill() {
if (will_become_android)
will_become_android = false;
if (!is_android)
return;
becomeHumane();
ply.hurt(Registry.DAMAGE_BECOME_HUMANE, ply.getMaxHealth() * 2);
}
@Override
public void deserializeNBT(CompoundTag compound) {
super.deserializeNBT(compound);
is_android = compound.getBoolean("is_android");
will_become_android = compound.getBoolean("will_become_android");
research_list.clear();
var list = compound.getList("research", Tag.TAG_COMPOUND);
for (var _tag : list) {
if (_tag instanceof CompoundTag tag) {
var research = Registry.ANDROID_RESEARCH.getValue(new ResourceLocation(tag.getString("id")));
for (tag in list) {
if (tag is CompoundTag) {
val research = Registry.ANDROID_RESEARCH.getValue(ResourceLocation(tag.getString("id")))
if (research != null) {
var instance = research.factory(this);
instance.deserializeNBT(tag);
research_list.add(instance);
val instance = research.factory(this)
instance.deserializeNBT(tag)
this.research.add(instance)
}
}
}
}
@Nonnull
@Override
public CompoundTag serializeNBT() {
CompoundTag tag = super.serializeNBT();
tag.putBoolean("is_android", is_android);
tag.putBoolean("will_become_android", will_become_android);
override fun serializeNBT(): CompoundTag {
val tag = super.serializeNBT()
var list = new ListTag();
tag["is_android"] = is_android
tag["will_become_android"] = will_become_android
for (var instance : research_list) {
var _tag = new CompoundTag();
instance.serializeNBT(_tag);
_tag.putString("id", Objects.requireNonNull(instance.type.getRegistryName()).toString());
list.add(_tag);
val list = ListTag()
for (instance in research) {
val researchTag = CompoundTag()
instance.serializeNBT(researchTag)
researchTag["id"] = instance.type.registryName!!.toString()
list.add(researchTag)
}
tag.put("research", list);
return tag;
tag["research"] = list
return tag
}
@SuppressWarnings("unchecked")
public <T extends AndroidResearch> T getResearch(AndroidResearchType<T> type) {
for (var instance : research_list) {
if (instance.type == type) {
return (T) instance;
fun <T : AndroidResearch> getResearch(type: AndroidResearchType<T>): T {
for (instance in research) {
if (instance.type === type) {
return instance as T
}
}
var instance = type.factory(this);
research_list.add(instance);
return instance;
val instance = type.factory(this)
research.add(instance)
return instance
}
@SubscribeEvent
@SuppressWarnings("unused")
public static void onAttachCapabilityEvent(AttachCapabilitiesEvent<Entity> event) {
if (event.getObject() instanceof Player ply) {
event.addCapability(Registry.Names.ANDROID_CAPABILITY, new AndroidCapabilityPlayer(ply));
}
}
@SubscribeEvent
@SuppressWarnings("unused")
public static void onPlayerChangeDimensionEvent(PlayerEvent.PlayerChangedDimensionEvent event) {
event.getPlayer().getCapability(MatteryCapability.ANDROID).ifPresent(IAndroidCapability::invalidateNetworkState);
}
@SubscribeEvent
@SuppressWarnings("unused")
public static void onPlayerCloneEvent(PlayerEvent.Clone event) {
event.getPlayer().getCapability(MatteryCapability.ANDROID).ifPresent((cap) -> {
LazyOptional<IAndroidCapability> resolver = event.getOriginal().getCapability(MatteryCapability.ANDROID);
if (!resolver.isPresent() || resolver.resolve().isEmpty()) {
event.getOriginal().reviveCaps();
resolver = event.getOriginal().getCapability(MatteryCapability.ANDROID);
}
if (!resolver.isPresent() || resolver.resolve().isEmpty()) {
event.getOriginal().invalidateCaps();
return;
}
var original = (AndroidCapabilityPlayer) resolver.resolve().get();
if (original.will_become_android && event.isWasDeath()) {
original.becomeAndroid();
if (event.getPlayer() instanceof ServerPlayer ply) {
ply.displayClientMessage(new TranslatableComponent("otm.pill.message_finish").withStyle(ChatFormatting.DARK_RED), false);
}
}
cap.deserializeNBT(original.serializeNBT());
cap.invalidateNetworkState();
event.getOriginal().invalidateCaps();
});
}
public int last_jump_ticks = 14;
public final Player ply;
public AndroidCapabilityPlayer(Player ent) {
super(ent);
ply = ent;
}
@Override
protected void tickNetwork() {
super.tickNetwork();
@JvmField
var last_jump_ticks = 14
override fun tickNetwork() {
super.tickNetwork()
if (is_android != network_is_android) {
network_is_android = is_android;
sendNetwork(new AndroidStatusPacket(AndroidStatusPacket.Type.IS_ANDROID, is_android));
network_is_android = is_android
sendNetwork(AndroidStatusPacket(AndroidStatusPacket.Type.IS_ANDROID, is_android))
}
if (will_become_android != network_will_become_android) {
network_will_become_android = will_become_android;
sendNetwork(new AndroidStatusPacket(AndroidStatusPacket.Type.WILL_BECOME_ANDROID, will_become_android));
network_will_become_android = will_become_android
sendNetwork(AndroidStatusPacket(AndroidStatusPacket.Type.WILL_BECOME_ANDROID, will_become_android))
}
for (var instance : research_list) {
for (instance in research) {
if (instance.dirty) {
instance.dirty = false;
sendNetwork(new AndroidResearchPacket(instance));
instance.dirty = false
sendNetwork(AndroidResearchPacket(instance))
}
}
}
public static final Fraction ENERGY_FOR_HUNGER_POINT = new Fraction(1000);
@JvmField
var sleep_ticks = 0
public int sleep_ticks = 0;
public static final int SLEEP_TICKS_LIMIT = 80;
@Override
protected void tickInnerClientAlways() {
super.tickInnerClientAlways();
override fun tickInnerClientAlways() {
super.tickInnerClientAlways()
if (will_become_android) {
if (ent.isSleeping()) {
sleep_ticks++;
if (ply.isSleeping) {
sleep_ticks++
} else {
sleep_ticks = 0;
sleep_ticks = 0
}
}
}
@Override
protected void tickServerAlways() {
super.tickServerAlways();
override fun tickServerAlways() {
super.tickServerAlways()
if (will_become_android) {
if (ent.isSleeping()) {
sleep_ticks++;
if (ply.isSleeping) {
sleep_ticks++
if (sleep_ticks > SLEEP_TICKS_LIMIT) {
becomeAndroid();
sleep_ticks = 0;
becomeAndroid()
sleep_ticks = 0
if (ent instanceof ServerPlayer ply) {
ply.displayClientMessage(new TranslatableComponent("otm.pill.message_finish").withStyle(ChatFormatting.DARK_RED), false);
}
(ply as? ServerPlayer)?.displayClientMessage(TranslatableComponent("otm.pill.message_finish").withStyle(ChatFormatting.DARK_RED), false)
}
} else {
sleep_ticks = 0;
sleep_ticks = 0
}
}
}
@Override
public void tickServer() {
super.tickServer();
public override fun tickServer() {
super.tickServer()
// TODO: Maybe passive drain?
// extractEnergyInner(BigDecimal.valueOf(new Random().nextDouble()), false);
if (ply.isSwimming() && !hasFeature(Registry.AndroidFeatures.AIR_BAGS)) {
ply.setSwimming(false);
if (ply.isSwimming && !hasFeature(Registry.AndroidFeatures.AIR_BAGS)) {
ply.isSwimming = false
}
var stats = ply.getFoodData();
val stats = ply.foodData
while (stats.getFoodLevel() < 18 && this.extractEnergyInner(ENERGY_FOR_HUNGER_POINT, true).compareTo(ENERGY_FOR_HUNGER_POINT) == 0) {
this.extractEnergyInner(ENERGY_FOR_HUNGER_POINT, false);
stats.setFoodLevel(stats.getFoodLevel() + 1);
while (stats.foodLevel < 18 && extractEnergyInner(ENERGY_FOR_HUNGER_POINT, true) >= ENERGY_FOR_HUNGER_POINT) {
extractEnergyInner(ENERGY_FOR_HUNGER_POINT, false)
stats.foodLevel = stats.foodLevel + 1
}
// "block" quick regeneration
// also cause power to generate while in peaceful
while (stats.getFoodLevel() > 18 && this.receiveEnergyInner(ENERGY_FOR_HUNGER_POINT, true).compareTo(ENERGY_FOR_HUNGER_POINT) == 0) {
this.receiveEnergyInner(ENERGY_FOR_HUNGER_POINT, false);
stats.setFoodLevel(stats.getFoodLevel() - 1);
while (stats.foodLevel > 18 && receiveEnergyInner(ENERGY_FOR_HUNGER_POINT, true) >= ENERGY_FOR_HUNGER_POINT) {
receiveEnergyInner(ENERGY_FOR_HUNGER_POINT, false)
stats.foodLevel = stats.foodLevel - 1
}
var food_level = (float) stats.getFoodLevel();
val foodLevel = stats.foodLevel.toFloat()
if (stats.getSaturationLevel() < food_level) {
Fraction extracted = this.extractEnergyInner(ENERGY_FOR_HUNGER_POINT.times(food_level - stats.getSaturationLevel()), false);
stats.setSaturation(stats.getSaturationLevel() + extracted.div(ENERGY_FOR_HUNGER_POINT).toFloat());
if (stats.saturationLevel < foodLevel) {
val extracted = extractEnergyInner(ENERGY_FOR_HUNGER_POINT * (foodLevel - stats.saturationLevel), false)
stats.setSaturation(stats.saturationLevel + (extracted / ENERGY_FOR_HUNGER_POINT).toFloat())
}
if (stats.getExhaustionLevel() > 0f) {
Fraction extracted = this.extractEnergyInner(ENERGY_FOR_HUNGER_POINT.times(stats.getExhaustionLevel() / 4f), false);
stats.setExhaustion(stats.getExhaustionLevel() - extracted.div(ENERGY_FOR_HUNGER_POINT).toFloat() * 4f);
if (stats.exhaustionLevel > 0f) {
val extracted = extractEnergyInner(ENERGY_FOR_HUNGER_POINT * (stats.exhaustionLevel / 4f), false)
stats.setExhaustion(stats.exhaustionLevel - (extracted / ENERGY_FOR_HUNGER_POINT).toFloat() * 4f)
}
}
@Suppress("unused")
companion object {
@SubscribeEvent
fun onAttachCapabilityEvent(event: AttachCapabilitiesEvent<Entity?>) {
val ent = event.`object`
if (ent is Player) {
event.addCapability(Registry.Names.ANDROID_CAPABILITY, AndroidCapabilityPlayer(ent))
}
}
@SubscribeEvent
fun onPlayerChangeDimensionEvent(event: PlayerChangedDimensionEvent) {
event.player.getCapability(MatteryCapability.ANDROID)
.ifPresent { it.invalidateNetworkState() }
}
@SubscribeEvent
fun onPlayerCloneEvent(event: Clone) {
event.player.getCapability(MatteryCapability.ANDROID).ifPresent {
var resolver = event.original.getCapability(MatteryCapability.ANDROID)
if (!resolver.isPresent || resolver.resolve().isEmpty) {
event.original.reviveCaps()
resolver = event.original.getCapability(MatteryCapability.ANDROID)
}
if (!resolver.isPresent || resolver.resolve().isEmpty) {
event.original.invalidateCaps()
return@ifPresent
}
val original = resolver.resolve().get() as AndroidCapabilityPlayer
if (original.will_become_android && event.isWasDeath) {
original.becomeAndroid()
(event.player as? ServerPlayer)?.displayClientMessage(TranslatableComponent("otm.pill.message_finish").withStyle(ChatFormatting.DARK_RED), false)
}
it.deserializeNBT(original.serializeNBT())
it.invalidateNetworkState()
event.original.invalidateCaps()
}
}
val ENERGY_FOR_HUNGER_POINT = Fraction(1000)
const val SLEEP_TICKS_LIMIT = 80
}
}

View File

@ -1,51 +1,45 @@
package ru.dbotthepony.mc.otm.capability.android;
package ru.dbotthepony.mc.otm.capability.android
import net.minecraft.MethodsReturnNonnullByDefault;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.item.ItemStack;
import net.minecraftforge.common.util.INBTSerializable;
import net.minecraftforge.event.entity.living.LivingHurtEvent;
import ru.dbotthepony.mc.otm.android.AndroidFeature;
import ru.dbotthepony.mc.otm.android.AndroidFeatureType;
import ru.dbotthepony.mc.otm.capability.IMatteryEnergyStorage;
import ru.dbotthepony.mc.otm.core.Fraction;
import net.minecraft.nbt.CompoundTag
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.item.ItemStack
import net.minecraftforge.common.util.INBTSerializable
import net.minecraftforge.event.entity.living.LivingHurtEvent
import ru.dbotthepony.mc.otm.android.AndroidFeature
import ru.dbotthepony.mc.otm.android.AndroidFeatureType
import ru.dbotthepony.mc.otm.capability.IMatteryEnergyStorage
import ru.dbotthepony.mc.otm.core.Fraction
import java.util.*
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
import java.math.BigDecimal;
import java.util.Optional;
interface IAndroidCapability : IMatteryEnergyStorage, INBTSerializable<CompoundTag?> {
fun tick()
fun tickClient()
fun getEntity(): LivingEntity
fun <T : AndroidFeature> addFeature(feature: AndroidFeatureType<T>): T
fun removeFeature(feature: AndroidFeatureType<*>): Boolean
fun hasFeature(feature: AndroidFeatureType<*>): Boolean
fun hasFeatureLevel(feature: AndroidFeatureType<*>, level: Int): Boolean
@MethodsReturnNonnullByDefault
@ParametersAreNonnullByDefault
public interface IAndroidCapability extends IMatteryEnergyStorage, INBTSerializable<CompoundTag> {
void tick();
void tickClient();
LivingEntity getEntity();
fun <T : AndroidFeature> getFeature(feature: AndroidFeatureType<T>): T?
<T extends AndroidFeature> T addFeature(AndroidFeatureType<T> feature);
boolean removeFeature(AndroidFeatureType<?> feature);
boolean hasFeature(@Nullable AndroidFeatureType<?> feature);
boolean hasFeatureLevel(@Nullable AndroidFeatureType<?> feature, int level);
@Nullable
<T extends AndroidFeature> T getFeature(AndroidFeatureType<T> feature);
default <T extends AndroidFeature> Optional<T> getFeatureO(AndroidFeatureType<T> feature) {
var get = getFeature(feature);
return get != null ? Optional.of(get) : Optional.empty();
fun <T : AndroidFeature> getFeatureO(feature: AndroidFeatureType<T>): Optional<T> {
val get = getFeature(feature)
return if (get != null) Optional.of(get) else Optional.empty()
}
default boolean isAndroid() {
return true;
fun <T : AndroidFeature> ifFeature(feature: AndroidFeatureType<T>, consumer: (T) -> Unit) {
val get = getFeature(feature)
if (get != null) {
consumer(get)
}
}
default void onHurt(LivingHurtEvent event) {}
ItemStack getBatteryItemStack();
void setBatteryItemStack(ItemStack stack);
fun isAndroid(): Boolean = true
void invalidateNetworkState(); // tell capability that player forgot everything, and everything needs to be re-networked
void setEnergy(Fraction value);
void setMaxEnergy(Fraction value);
fun onHurt(event: LivingHurtEvent?) {}
var batteryItemStack: ItemStack
fun invalidateNetworkState() // tell capability that player forgot everything, and everything needs to be re-networked
fun setEnergy(value: Fraction?)
fun setMaxEnergy(value: Fraction?)
}

View File

@ -19,9 +19,7 @@ import ru.dbotthepony.mc.otm.client.screen.widget.PowerGaugePanel
import ru.dbotthepony.mc.otm.menu.AndroidStationMenu
import java.util.*
class AndroidStationScreen(p_97741_: AndroidStationMenu, p_97742_: Inventory, p_97743_: Component) :
class AndroidStationScreen constructor(p_97741_: AndroidStationMenu, p_97742_: Inventory, p_97743_: Component) :
MatteryScreen<AndroidStationMenu>(p_97741_, p_97742_, p_97743_) {
internal inner class AndroidResearchButton(parent: EditablePanel?, private val node: AndroidResearch) :

View File

@ -53,7 +53,7 @@ class ItemPill(val pillType: PillType) :
val cap = resolver.get() as AndroidCapabilityPlayer
if (pillType == PillType.BECOME_ANDROID && !cap.isEverAndroid || pillType == PillType.BECOME_HUMANE && cap.isEverAndroid) {
if (pillType == PillType.BECOME_ANDROID && !cap.isEverAndroid() || pillType == PillType.BECOME_HUMANE && cap.isEverAndroid()) {
ply.startUsingItem(hand)
return InteractionResultHolder.consume(ply.getItemInHand(hand))
}
@ -73,7 +73,7 @@ class ItemPill(val pillType: PillType) :
val cap = resolver.get() as AndroidCapabilityPlayer
if (pillType == PillType.BECOME_ANDROID && !cap.isEverAndroid) {
if (pillType == PillType.BECOME_ANDROID && !cap.isEverAndroid()) {
if (ent.abilities.instabuild) {
if (ent is ServerPlayer) {
cap.becomeAndroid()
@ -85,7 +85,7 @@ class ItemPill(val pillType: PillType) :
cap.becomeAndroidSoft()
}
}
} else if (pillType == PillType.BECOME_HUMANE && cap.isEverAndroid) {
} else if (pillType == PillType.BECOME_HUMANE && cap.isEverAndroid()) {
if (ent.abilities.instabuild) {
if (ent is ServerPlayer) {
cap.becomeHumane()