diff --git a/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java b/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java index 561d8d6d5..70f2e1e14 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java +++ b/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java @@ -56,6 +56,7 @@ public class OverdriveThatMatters { FMLJavaModLoadingContext.get().getModEventBus().register(Registry.Menus.class); FMLJavaModLoadingContext.get().getModEventBus().register(Registry.AndroidFeatures.class); FMLJavaModLoadingContext.get().getModEventBus().register(Registry.AndroidResearch.class); + FMLJavaModLoadingContext.get().getModEventBus().register(Registry.Stats.class); FMLJavaModLoadingContext.get().getModEventBus().addListener(AndroidCapability::registerEffects); diff --git a/src/main/java/ru/dbotthepony/mc/otm/Registry.java b/src/main/java/ru/dbotthepony/mc/otm/Registry.java index dbc0380d5..5d05cc08a 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/Registry.java +++ b/src/main/java/ru/dbotthepony/mc/otm/Registry.java @@ -4,6 +4,9 @@ import net.minecraft.client.gui.screens.MenuScreens; import net.minecraft.network.chat.TextComponent; import net.minecraft.network.chat.TranslatableComponent; import net.minecraft.resources.ResourceLocation; +import net.minecraft.stats.StatFormatter; +import net.minecraft.stats.StatType; +import net.minecraft.stats.Stats; import net.minecraft.world.damagesource.DamageSource; import net.minecraft.world.food.FoodProperties; import net.minecraft.world.inventory.MenuType; @@ -14,12 +17,15 @@ import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraftforge.event.RegistryEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; +import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; import net.minecraftforge.registries.ForgeRegistry; import net.minecraftforge.registries.RegistryBuilder; import ru.dbotthepony.mc.otm.android.AndroidFeature; import ru.dbotthepony.mc.otm.android.AndroidResearchBuilder; import ru.dbotthepony.mc.otm.android.AndroidResearchType; import ru.dbotthepony.mc.otm.android.feature.AndroidLimbOverclocking; +import ru.dbotthepony.mc.otm.android.feature.AndroidNanobotsArmor; +import ru.dbotthepony.mc.otm.android.feature.AndroidNanobotsRegeneration; import ru.dbotthepony.mc.otm.block.*; import ru.dbotthepony.mc.otm.block.entity.*; import ru.dbotthepony.mc.otm.android.AndroidFeatureType; @@ -109,6 +115,7 @@ public class Registry { // android features and research public static final ResourceLocation AIR_BAGS = new ResourceLocation(OverdriveThatMatters.MOD_ID, "air_bags"); + public static final ResourceLocation LIMB_OVERCLOCKING = new ResourceLocation(OverdriveThatMatters.MOD_ID, "limb_overclocking"); public static final ResourceLocation LIMB_OVERCLOCKING_1 = new ResourceLocation(OverdriveThatMatters.MOD_ID, "limb_overclocking_1"); public static final ResourceLocation LIMB_OVERCLOCKING_2 = new ResourceLocation(OverdriveThatMatters.MOD_ID, "limb_overclocking_2"); @@ -116,6 +123,45 @@ public class Registry { public static final ResourceLocation LIMB_OVERCLOCKING_4 = new ResourceLocation(OverdriveThatMatters.MOD_ID, "limb_overclocking_4"); public static final ResourceLocation[] LIMB_OVERCLOCKING_LIST = new ResourceLocation[] { LIMB_OVERCLOCKING_1, LIMB_OVERCLOCKING_2, LIMB_OVERCLOCKING_3, LIMB_OVERCLOCKING_4 }; + + public static final ResourceLocation NANOBOTS = new ResourceLocation(OverdriveThatMatters.MOD_ID, "nanobots"); + public static final ResourceLocation NANOBOTS_REGENERATION = new ResourceLocation(OverdriveThatMatters.MOD_ID, "nanobots_regeneration"); + public static final ResourceLocation NANOBOTS_REGENERATION_1 = new ResourceLocation(OverdriveThatMatters.MOD_ID, "nanobots_regeneration_1"); + public static final ResourceLocation NANOBOTS_REGENERATION_2 = new ResourceLocation(OverdriveThatMatters.MOD_ID, "nanobots_regeneration_2"); + public static final ResourceLocation NANOBOTS_REGENERATION_3 = new ResourceLocation(OverdriveThatMatters.MOD_ID, "nanobots_regeneration_3"); + public static final ResourceLocation NANOBOTS_REGENERATION_4 = new ResourceLocation(OverdriveThatMatters.MOD_ID, "nanobots_regeneration_4"); + + public static final ResourceLocation[] NANOBOTS_REGENERATION_LIST = new ResourceLocation[] { NANOBOTS_REGENERATION_1, NANOBOTS_REGENERATION_2, NANOBOTS_REGENERATION_3, NANOBOTS_REGENERATION_4 }; + + public static final ResourceLocation NANOBOTS_ARMOR = new ResourceLocation(OverdriveThatMatters.MOD_ID, "nanobots_armor"); + + public static final ResourceLocation NANOBOTS_ARMOR_STRENGTH_1 = new ResourceLocation(OverdriveThatMatters.MOD_ID, "nanobots_armor_strength_1"); + public static final ResourceLocation NANOBOTS_ARMOR_STRENGTH_2 = new ResourceLocation(OverdriveThatMatters.MOD_ID, "nanobots_armor_strength_2"); + public static final ResourceLocation NANOBOTS_ARMOR_STRENGTH_3 = new ResourceLocation(OverdriveThatMatters.MOD_ID, "nanobots_armor_strength_3"); + public static final ResourceLocation[] NANOBOTS_ARMOR_STRENGTH_LIST = new ResourceLocation[] { + NANOBOTS_ARMOR_STRENGTH_1, + NANOBOTS_ARMOR_STRENGTH_2, + NANOBOTS_ARMOR_STRENGTH_3, + }; + + public static final ResourceLocation NANOBOTS_ARMOR_SPEED_1 = new ResourceLocation(OverdriveThatMatters.MOD_ID, "nanobots_armor_speed_1"); + public static final ResourceLocation NANOBOTS_ARMOR_SPEED_2 = new ResourceLocation(OverdriveThatMatters.MOD_ID, "nanobots_armor_speed_2"); + public static final ResourceLocation NANOBOTS_ARMOR_SPEED_3 = new ResourceLocation(OverdriveThatMatters.MOD_ID, "nanobots_armor_speed_3"); + public static final ResourceLocation[] NANOBOTS_ARMOR_SPEED_LIST = new ResourceLocation[] { + NANOBOTS_ARMOR_SPEED_1, + NANOBOTS_ARMOR_SPEED_2, + NANOBOTS_ARMOR_SPEED_3, + }; + + public static final ResourceLocation HYDRAULICS_OVERLOAD = new ResourceLocation(OverdriveThatMatters.MOD_ID, "hydraulics_overload"); + public static final ResourceLocation HYDRAULICS_OVERLOAD_1 = new ResourceLocation(OverdriveThatMatters.MOD_ID, "hydraulics_overload_1"); + public static final ResourceLocation HYDRAULICS_OVERLOAD_2 = new ResourceLocation(OverdriveThatMatters.MOD_ID, "hydraulics_overload_2"); + public static final ResourceLocation HYDRAULICS_OVERLOAD_3 = new ResourceLocation(OverdriveThatMatters.MOD_ID, "hydraulics_overload_3"); + + // stats + public static final ResourceLocation DAMAGE_ABSORBED = new ResourceLocation(OverdriveThatMatters.MOD_ID, "damage_absorbed"); + public static final ResourceLocation HEALTH_REGENERATED = new ResourceLocation(OverdriveThatMatters.MOD_ID, "health_regenerated"); + public static final ResourceLocation POWER_CONSUMED = new ResourceLocation(OverdriveThatMatters.MOD_ID, "power_consumed"); } public static class Blocks { @@ -299,16 +345,22 @@ public class Registry { public static class AndroidFeatures { public static final AndroidFeatureType AIR_BAGS = new AndroidFeatureType<>(AndroidFeature::new); public static final AndroidFeatureType LIMB_OVERCLOCKING = new AndroidFeatureType<>(AndroidLimbOverclocking::new); + public static final AndroidFeatureType NANOBOTS_REGENERATION = new AndroidFeatureType<>(AndroidNanobotsRegeneration::new); + public static final AndroidFeatureType NANOBOTS_ARMOR = new AndroidFeatureType<>(AndroidNanobotsArmor::new); static { AIR_BAGS.setRegistryName(Names.AIR_BAGS); LIMB_OVERCLOCKING.setRegistryName(Names.LIMB_OVERCLOCKING); + NANOBOTS_REGENERATION.setRegistryName(Names.NANOBOTS_REGENERATION); + NANOBOTS_ARMOR.setRegistryName(Names.NANOBOTS_ARMOR); } @SubscribeEvent public static void register(final RegistryEvent.Register> event) { event.getRegistry().register(AIR_BAGS); event.getRegistry().register(LIMB_OVERCLOCKING); + event.getRegistry().register(NANOBOTS_REGENERATION); + event.getRegistry().register(NANOBOTS_ARMOR); } } @@ -326,7 +378,7 @@ public class Registry { public static final SkinElement ICON_ARC; public static final SkinElement ICON_ARROW; public static final SkinElement ICON_ARMOR; - public static final SkinElement ICON_REGENERATIVE; + public static final SkinElement ICON_NANOBOTS; public static final SkinElement ICON_NIGHT_VISION; public static final SkinElement ICON_OXYGEN_SUPPLY; @@ -357,7 +409,7 @@ public class Registry { ICON_ARC = new SkinElement(ICONS, x, y, 18, 18, 126, 126); x += 18; ICON_ARROW = new SkinElement(ICONS, x, y, 18, 18, 126, 126); x += 18; ICON_ARMOR = new SkinElement(ICONS, x, y, 18, 18, 126, 126); x += 18; - ICON_REGENERATIVE = new SkinElement(ICONS, x, y, 18, 18, 126, 126); x += 18; + ICON_NANOBOTS = new SkinElement(ICONS, x, y, 18, 18, 126, 126); x += 18; ICON_NIGHT_VISION = new SkinElement(ICONS, x, y, 18, 18, 126, 126); x += 18; ICON_OXYGEN_SUPPLY = new SkinElement(ICONS, x, y, 18, 18, 126, 126); @@ -382,40 +434,131 @@ public class Registry { .build(); public static final AndroidResearchType[] LIMB_OVERCLOCKING = new AndroidResearchType[4]; + public static final AndroidResearchType[] NANOBOTS_REGENERATION = new AndroidResearchType[4]; + public static final AndroidResearchType[] NANOBOTS_ARMOR_STRENGTH = new AndroidResearchType[3]; + public static final AndroidResearchType[] NANOBOTS_ARMOR_SPEED = new AndroidResearchType[3]; + + public static final AndroidResearchType NANOBOTS = + new AndroidResearchBuilder() + .setExperienceCost(15) + .withDescription() + .withIcon(ICON_NANOBOTS) + .build(); + + public static final AndroidResearchType NANOBOTS_ARMOR = + new AndroidResearchBuilder() + .setExperienceCost(25) + .withDescription() + .addPrerequisite(Names.NANOBOTS) + .addFeatureResult(Names.NANOBOTS_ARMOR) + .withIcon(ICON_ARMOR) + .build(); static { AIR_BAGS.setRegistryName(Names.AIR_BAGS); + NANOBOTS.setRegistryName(Names.NANOBOTS); + NANOBOTS_ARMOR.setRegistryName(Names.NANOBOTS_ARMOR); for (int i = 0; i < 4; i++) { - var builder = new AndroidResearchBuilder() - .withDescription() - .setExperienceCost(24 + i * 8) + var limbs = new AndroidResearchBuilder() + .setExperienceCost(18 + i * 8) .withIconText(new TextComponent(String.valueOf(i + 1))) .withIcon(ICON_LIMB_OVERCLOCKING) .withName(new TranslatableComponent("android_research.overdrive_that_matters.limb_overclocking", i + 1)) .withDescription(new TranslatableComponent("android_research.overdrive_that_matters.limb_overclocking.description", (i + 1) * 8, (i + 1) * 6)) .addFeatureResult(Names.LIMB_OVERCLOCKING, i); + var regeneration = new AndroidResearchBuilder() + .setExperienceCost(20 + i * 6) + .withIconText(new TextComponent(String.valueOf(i + 1))) + .withIcon(ICON_NANOBOTS) + .withName(new TranslatableComponent("android_research.overdrive_that_matters.nanobots_regeneration", i + 1)) + .withDescription( + i > 0 ? new TranslatableComponent("android_research.overdrive_that_matters.nanobots_regeneration.description_improve") : new TranslatableComponent("android_research.overdrive_that_matters.nanobots_regeneration.description")) + .addFeatureResult(Names.NANOBOTS_REGENERATION, i); + if (i > 0) { - builder.addPrerequisite(Names.LIMB_OVERCLOCKING_LIST[i - 1]); + limbs.addPrerequisite(Names.LIMB_OVERCLOCKING_LIST[i - 1]); + regeneration.addPrerequisite(Names.NANOBOTS_REGENERATION_LIST[i - 1]); + } else { + regeneration.addPrerequisite(Names.NANOBOTS); } - LIMB_OVERCLOCKING[i] = builder.build(); + LIMB_OVERCLOCKING[i] = limbs.build(); + NANOBOTS_REGENERATION[i] = regeneration.build(); + } + + for (int i = 0; i < 3; i++) { + final int level = i + 1; + + NANOBOTS_ARMOR_STRENGTH[i] = new AndroidResearchBuilder() + .setExperienceCost(20 + i * 8) + .withIconText(new TextComponent(String.valueOf(i + 1))) + .withIcon(ICON_ARMOR) + .addPrerequisite(i > 0 ? Names.NANOBOTS_ARMOR_STRENGTH_LIST[i - 1] : Names.NANOBOTS_ARMOR) + .withName(new TranslatableComponent("android_research.overdrive_that_matters.nanobots_armor_strength", i + 1)) + .withDescription(new TranslatableComponent("android_research.overdrive_that_matters.nanobots_armor_strength.description", (i + 1) * 8, (i + 1) * 6)) + .addFeatureResult(Names.NANOBOTS_ARMOR, 0, (feature) -> { + if (((AndroidNanobotsArmor) feature).getStrength() < level) + ((AndroidNanobotsArmor) feature).setStrength(level); + }) + .build(); + + NANOBOTS_ARMOR_SPEED[i] = new AndroidResearchBuilder() + .setExperienceCost(20 + i * 8) + .withIconText(new TextComponent(String.valueOf(i + 1))) + .withIcon(ICON_ARMOR) + .addPrerequisite(i > 0 ? Names.NANOBOTS_ARMOR_SPEED_LIST[i - 1] : Names.NANOBOTS_ARMOR) + .withName(new TranslatableComponent("android_research.overdrive_that_matters.nanobots_armor_speed", i + 1)) + .withDescription(new TranslatableComponent("android_research.overdrive_that_matters.nanobots_armor_speed.description", (i + 1) * 8, (i + 1) * 6)) + .addFeatureResult(Names.NANOBOTS_ARMOR, 0, (feature) -> { + if (((AndroidNanobotsArmor) feature).getSpeed() < level) + ((AndroidNanobotsArmor) feature).setSpeed(level); + }) + .build(); } LIMB_OVERCLOCKING[0].setRegistryName(Names.LIMB_OVERCLOCKING_1); LIMB_OVERCLOCKING[1].setRegistryName(Names.LIMB_OVERCLOCKING_2); LIMB_OVERCLOCKING[2].setRegistryName(Names.LIMB_OVERCLOCKING_3); LIMB_OVERCLOCKING[3].setRegistryName(Names.LIMB_OVERCLOCKING_4); + + NANOBOTS_ARMOR_SPEED[0].setRegistryName(Names.NANOBOTS_ARMOR_SPEED_1); + NANOBOTS_ARMOR_SPEED[1].setRegistryName(Names.NANOBOTS_ARMOR_SPEED_2); + NANOBOTS_ARMOR_SPEED[2].setRegistryName(Names.NANOBOTS_ARMOR_SPEED_3); + + NANOBOTS_ARMOR_STRENGTH[0].setRegistryName(Names.NANOBOTS_ARMOR_STRENGTH_1); + NANOBOTS_ARMOR_STRENGTH[1].setRegistryName(Names.NANOBOTS_ARMOR_STRENGTH_2); + NANOBOTS_ARMOR_STRENGTH[2].setRegistryName(Names.NANOBOTS_ARMOR_STRENGTH_3); + + NANOBOTS_REGENERATION[0].setRegistryName(Names.NANOBOTS_REGENERATION_1); + NANOBOTS_REGENERATION[1].setRegistryName(Names.NANOBOTS_REGENERATION_2); + NANOBOTS_REGENERATION[2].setRegistryName(Names.NANOBOTS_REGENERATION_3); + NANOBOTS_REGENERATION[3].setRegistryName(Names.NANOBOTS_REGENERATION_4); } @SubscribeEvent public static void register(final RegistryEvent.Register> event) { event.getRegistry().register(AIR_BAGS); + event.getRegistry().register(NANOBOTS); + event.getRegistry().register(NANOBOTS_ARMOR); + event.getRegistry().register(LIMB_OVERCLOCKING[0]); event.getRegistry().register(LIMB_OVERCLOCKING[1]); event.getRegistry().register(LIMB_OVERCLOCKING[2]); event.getRegistry().register(LIMB_OVERCLOCKING[3]); + + event.getRegistry().register(NANOBOTS_ARMOR_SPEED[0]); + event.getRegistry().register(NANOBOTS_ARMOR_SPEED[1]); + event.getRegistry().register(NANOBOTS_ARMOR_SPEED[2]); + event.getRegistry().register(NANOBOTS_ARMOR_STRENGTH[0]); + event.getRegistry().register(NANOBOTS_ARMOR_STRENGTH[1]); + event.getRegistry().register(NANOBOTS_ARMOR_STRENGTH[2]); + + event.getRegistry().register(NANOBOTS_REGENERATION[0]); + event.getRegistry().register(NANOBOTS_REGENERATION[1]); + event.getRegistry().register(NANOBOTS_REGENERATION[2]); + event.getRegistry().register(NANOBOTS_REGENERATION[3]); } } @@ -472,4 +615,17 @@ public class Registry { // OverdriveThatMatters.LOGGER.info("Registered screens"); } } + + public static class Stats { + @SubscribeEvent + public static void registerVanilla(final FMLCommonSetupEvent event) { + net.minecraft.core.Registry.register(net.minecraft.core.Registry.CUSTOM_STAT, Names.DAMAGE_ABSORBED, Names.DAMAGE_ABSORBED); + net.minecraft.core.Registry.register(net.minecraft.core.Registry.CUSTOM_STAT, Names.HEALTH_REGENERATED, Names.HEALTH_REGENERATED); + net.minecraft.core.Registry.register(net.minecraft.core.Registry.CUSTOM_STAT, Names.POWER_CONSUMED, Names.POWER_CONSUMED); + + net.minecraft.stats.Stats.CUSTOM.get(Names.DAMAGE_ABSORBED, StatFormatter.DIVIDE_BY_TEN); + net.minecraft.stats.Stats.CUSTOM.get(Names.HEALTH_REGENERATED, StatFormatter.DIVIDE_BY_TEN); + net.minecraft.stats.Stats.CUSTOM.get(Names.POWER_CONSUMED, StatFormatter.DIVIDE_BY_TEN); + } + } } diff --git a/src/main/java/ru/dbotthepony/mc/otm/android/AndroidFeature.java b/src/main/java/ru/dbotthepony/mc/otm/android/AndroidFeature.java index 4490dba80..018378324 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/android/AndroidFeature.java +++ b/src/main/java/ru/dbotthepony/mc/otm/android/AndroidFeature.java @@ -2,6 +2,7 @@ package ru.dbotthepony.mc.otm.android; import net.minecraft.nbt.CompoundTag; import net.minecraftforge.common.util.INBTSerializable; +import net.minecraftforge.event.entity.living.LivingHurtEvent; import ru.dbotthepony.mc.otm.capability.IAndroidCapability; public class AndroidFeature implements INBTSerializable { @@ -47,11 +48,11 @@ public class AndroidFeature implements INBTSerializable { } public void tickClient() { - + // override } public void tickServer() { - + // override } public void applyModifiers() {} @@ -61,6 +62,10 @@ public class AndroidFeature implements INBTSerializable { tag.putInt("level", level); } + public void onHurt(LivingHurtEvent event) { + // override + } + @Override public CompoundTag serializeNBT() { var tag = new CompoundTag(); diff --git a/src/main/java/ru/dbotthepony/mc/otm/android/AndroidResearchBuilder.java b/src/main/java/ru/dbotthepony/mc/otm/android/AndroidResearchBuilder.java index 2465a92a0..8e84c6edf 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/android/AndroidResearchBuilder.java +++ b/src/main/java/ru/dbotthepony/mc/otm/android/AndroidResearchBuilder.java @@ -15,19 +15,29 @@ import javax.annotation.Nullable; import java.util.ArrayList; import java.util.List; import java.util.Objects; +import java.util.function.Consumer; public class AndroidResearchBuilder { record DeferredItemStack(ResourceLocation id, int amount) { } - record DeferredFeature(ResourceLocation id, int level) { - DeferredFeature(ResourceLocation id, int level) { + record DeferredFeature(ResourceLocation id, int level, Consumer callback) { + DeferredFeature(ResourceLocation id, int level, Consumer callback) { this.id = id; this.level = level; + this.callback = callback; + } + + DeferredFeature(ResourceLocation id, int level) { + this(id, level, (f) -> {}); + } + + DeferredFeature(ResourceLocation id, Consumer callback) { + this(id, 0, (f) -> {}); } DeferredFeature(ResourceLocation id) { - this(id, 0); + this(id, 0, (f) -> {}); } } @@ -61,6 +71,16 @@ public class AndroidResearchBuilder { return this; } + public AndroidResearchBuilder addFeatureResult(ResourceLocation location, Consumer callback) { + feature_results.add(new DeferredFeature(location, callback)); + return this; + } + + public AndroidResearchBuilder addFeatureResult(AndroidFeatureType location, Consumer callback) { + feature_results.add(new DeferredFeature(Objects.requireNonNull(location.getRegistryName()), callback)); + return this; + } + public AndroidResearchBuilder addFeatureResult(ResourceLocation location, int level) { feature_results.add(new DeferredFeature(location, level)); return this; @@ -71,6 +91,16 @@ public class AndroidResearchBuilder { return this; } + public AndroidResearchBuilder addFeatureResult(ResourceLocation location, int level, Consumer callback) { + feature_results.add(new DeferredFeature(location, level, callback)); + return this; + } + + public AndroidResearchBuilder addFeatureResult(AndroidFeatureType location, int level, Consumer callback) { + feature_results.add(new DeferredFeature(Objects.requireNonNull(location.getRegistryName()), level, callback)); + return this; + } + public AndroidResearchBuilder addItem(ResourceLocation location) { items.add(new DeferredItemStack(location, 1)); return this; @@ -115,7 +145,7 @@ public class AndroidResearchBuilder { private final ArrayList resolved_stacks = new ArrayList<>(); private List> resolved_preq; - record ResolvedFeature(AndroidFeatureType type, int level) { + record ResolvedFeature(AndroidFeatureType type, int level, Consumer callback) { } private final ArrayList resolved_features = new ArrayList<>(); @@ -152,7 +182,7 @@ public class AndroidResearchBuilder { var get = Registry.ANDROID_FEATURES().getValue(entry.id); if (get != null) { - resolved_features.add(new ResolvedFeature(get, entry.level)); + resolved_features.add(new ResolvedFeature(get, entry.level, entry.callback)); } else { throw new IllegalArgumentException("Can not find android feature " + entry); } @@ -246,8 +276,12 @@ public class AndroidResearchBuilder { if (get == null) { get = capability.addFeature(feature.type); get.setLevel(feature.level); - } else if (get.getLevel() < feature.level) { - get.setLevel(feature.level); + feature.callback.accept(get); + } else { + if (get.getLevel() < feature.level) + get.setLevel(feature.level); + + feature.callback.accept(get); } } } diff --git a/src/main/java/ru/dbotthepony/mc/otm/android/AndroidResearchType.java b/src/main/java/ru/dbotthepony/mc/otm/android/AndroidResearchType.java index df38bf138..b9d8a4153 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/android/AndroidResearchType.java +++ b/src/main/java/ru/dbotthepony/mc/otm/android/AndroidResearchType.java @@ -18,6 +18,7 @@ public class AndroidResearchType extends ForgeRegistr } private List> blocking; + private List> unlocks; /** * @return List of research blocked once this research is unlocked @@ -38,6 +39,25 @@ public class AndroidResearchType extends ForgeRegistr return blocking; } + /** + * @return List of research unlocked once this research is unlocked + */ + public List> getUnlocks() { + if (unlocks == null) { + var list = new ArrayList>(); + + for (var type : Registry.ANDROID_RESEARCH()) { + if (type.getPrerequisites().contains(this)) { + list.add(type); + } + } + + unlocks = List.copyOf(list); + } + + return unlocks; + } + /** * @return List of research once unlocked blocks this research */ diff --git a/src/main/java/ru/dbotthepony/mc/otm/android/feature/AndroidNanobotsArmor.java b/src/main/java/ru/dbotthepony/mc/otm/android/feature/AndroidNanobotsArmor.java new file mode 100644 index 000000000..e369c9628 --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/android/feature/AndroidNanobotsArmor.java @@ -0,0 +1,113 @@ +package ru.dbotthepony.mc.otm.android.feature; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.level.ServerPlayer; +import net.minecraftforge.event.entity.living.LivingHurtEvent; +import ru.dbotthepony.mc.otm.OverdriveThatMatters; +import ru.dbotthepony.mc.otm.Registry; +import ru.dbotthepony.mc.otm.android.AndroidFeature; +import ru.dbotthepony.mc.otm.android.AndroidFeatureType; +import ru.dbotthepony.mc.otm.capability.IAndroidCapability; +import ru.dbotthepony.mc.otm.capability.MatteryCapability; + +import java.math.BigDecimal; + +public class AndroidNanobotsArmor extends AndroidFeature { + public AndroidNanobotsArmor(AndroidFeatureType type, IAndroidCapability capability) { + super(type, capability); + } + + public int getStrength() { + return strength; + } + + public void setStrength(int strength) { + this.strength = Math.max(0, Math.min(2, strength)); + } + + public int getSpeed() { + return speed; + } + + public void setSpeed(int speed) { + this.speed = Math.max(0, Math.min(2, speed)); + } + + protected int strength = 0; + protected int speed = 0; + + protected int ticks_passed = 0; + protected int layers = 0; + + protected static final BigDecimal ENERGY_PER_BUILT = new BigDecimal(200); + protected static final BigDecimal ENERGY_PER_HITPOINT = new BigDecimal(500); + + public static final int[] TICKS = new int[] { + 80, // 4 seconds to build a layer + 70, // 3.5 seconds to build a layer + 60, // 3 seconds to build a layer + 50, // 2.5 seconds to build a layer + }; + + public static final float[] SHIELD_STRENGTH = new float[] { + 0.1f, + 0.2f, + 0.3f, + 0.4f, + }; + + @Override + public void tickServer() { + if (layers < strength + 1 && capability.extractEnergyInner(ENERGY_PER_BUILT, true).compareTo(ENERGY_PER_BUILT) == 0) { + ticks_passed++; + + if (ticks_passed >= TICKS[speed]) { + layers++; + capability.extractEnergyInner(ENERGY_PER_BUILT, false); + } + } else { + ticks_passed = 0; + } + } + + @Override + public void onHurt(LivingHurtEvent event) { + ticks_passed = 0; + + if (!event.getSource().isBypassArmor() && layers > 0) { + var absorbed = event.getAmount() * SHIELD_STRENGTH[layers]; + + if (absorbed > 0.1f) { + var required = ENERGY_PER_HITPOINT.multiply(new BigDecimal(Float.toString(absorbed))); + var extracted = capability.extractEnergyInner(required, false); + var real_absorbed = absorbed * extracted.divide(required, MatteryCapability.ROUND_RULES).floatValue(); + event.setAmount(event.getAmount() - real_absorbed); + + if (capability.getEntity() instanceof ServerPlayer ply) { + ply.awardStat(Registry.Names.DAMAGE_ABSORBED, Math.round(real_absorbed * 10f)); + } + + layers--; + } + } + } + + @Override + public void serializeNBT(CompoundTag tag) { + super.serializeNBT(tag); + tag.putInt("ticks_passed", ticks_passed); + tag.putInt("layers", layers); + tag.putInt("strength", strength); + tag.putInt("speed", speed); + } + + @Override + public void deserializeNBT(CompoundTag tag) { + super.deserializeNBT(tag); + + ticks_passed = tag.getInt("ticks_passed"); + layers = tag.getInt("layers"); + strength = tag.getInt("strength"); + speed = tag.getInt("speed"); + } +} diff --git a/src/main/java/ru/dbotthepony/mc/otm/android/feature/AndroidNanobotsRegeneration.java b/src/main/java/ru/dbotthepony/mc/otm/android/feature/AndroidNanobotsRegeneration.java new file mode 100644 index 000000000..7c5d19d99 --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/android/feature/AndroidNanobotsRegeneration.java @@ -0,0 +1,81 @@ +package ru.dbotthepony.mc.otm.android.feature; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.level.ServerPlayer; +import net.minecraftforge.event.entity.living.LivingHurtEvent; +import ru.dbotthepony.mc.otm.Registry; +import ru.dbotthepony.mc.otm.android.AndroidFeature; +import ru.dbotthepony.mc.otm.android.AndroidFeatureType; +import ru.dbotthepony.mc.otm.capability.IAndroidCapability; +import ru.dbotthepony.mc.otm.capability.MatteryCapability; + +import java.math.BigDecimal; + +public class AndroidNanobotsRegeneration extends AndroidFeature { + public AndroidNanobotsRegeneration(AndroidFeatureType type, IAndroidCapability capability) { + super(type, capability); + } + + protected int ticks_passed = 0; + protected int heal_ticks = 0; + + protected static final BigDecimal ENERGY_PER_HITPOINT = new BigDecimal(800); + + protected static final int[] TICKS_BETWEEN_HEAL = new int[] { + 100, // 5 seconds + 80, // 4 seconds + 60, // 3 seconds + 40, // 2 seconds + }; + + @Override + public void tickServer() { + var ent = capability.getEntity(); + + if (ent.getHealth() > 0 && ent.getHealth() < ent.getMaxHealth()) { + ticks_passed++; + + var wait_time = heal_ticks >= TICKS_BETWEEN_HEAL.length ? TICKS_BETWEEN_HEAL[TICKS_BETWEEN_HEAL.length - 1] : TICKS_BETWEEN_HEAL[heal_ticks]; + + if (ticks_passed > wait_time) { + var missing = Math.min(1, ent.getMaxHealth() - ent.getHealth()); + var extract = capability.extractEnergyInner(ENERGY_PER_HITPOINT.multiply(new BigDecimal(Float.toString(missing))), false); + + if (extract.compareTo(BigDecimal.ZERO) > 0) { + heal_ticks = Math.min(heal_ticks + 1, level); + var heal = missing * extract.divide(ENERGY_PER_HITPOINT, MatteryCapability.ROUND_RULES).floatValue(); + ent.heal(heal); + + if (capability.getEntity() instanceof ServerPlayer ply) { + ply.awardStat(Registry.Names.HEALTH_REGENERATED, Math.round(heal * 10f)); + } + + ticks_passed = 0; + } + } + } else { + heal_ticks = 0; + ticks_passed = 0; + } + } + + @Override + public void onHurt(LivingHurtEvent event) { + heal_ticks = 0; + ticks_passed = 0; + } + + @Override + public void serializeNBT(CompoundTag tag) { + super.serializeNBT(tag); + tag.putInt("heal_ticks", heal_ticks); + tag.putInt("ticks_passed", ticks_passed); + } + + @Override + public void deserializeNBT(CompoundTag tag) { + super.deserializeNBT(tag); + heal_ticks = tag.getInt("heal_ticks"); + ticks_passed = tag.getInt("ticks_passed"); + } +} diff --git a/src/main/java/ru/dbotthepony/mc/otm/capability/AndroidCapability.java b/src/main/java/ru/dbotthepony/mc/otm/capability/AndroidCapability.java index bc7af82e4..68bd35ef3 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/capability/AndroidCapability.java +++ b/src/main/java/ru/dbotthepony/mc/otm/capability/AndroidCapability.java @@ -17,6 +17,8 @@ import net.minecraftforge.common.util.LazyOptional; import net.minecraftforge.energy.CapabilityEnergy; import net.minecraftforge.energy.IEnergyStorage; import net.minecraftforge.event.entity.living.LivingEvent; +import net.minecraftforge.event.entity.living.LivingHurtEvent; +import net.minecraftforge.eventbus.api.EventPriority; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; import net.minecraftforge.fmllegacy.network.PacketDistributor; @@ -69,11 +71,11 @@ public class AndroidCapability implements ICapabilityProvider, IAndroidCapabilit } @Override - public AndroidFeature addFeature(@Nonnull AndroidFeatureType feature) { + public T addFeature(@Nonnull AndroidFeatureType feature) { var get = features.get(feature); if (get != null) - return get; + return (T) get; var factory = feature.factory(this); features.put(feature, factory); @@ -125,8 +127,8 @@ public class AndroidCapability implements ICapabilityProvider, IAndroidCapabilit } @Nullable - public AndroidFeature getFeature(AndroidFeatureType feature) { - return features.get(feature); + public T getFeature(AndroidFeatureType feature) { + return (T) features.get(feature); } public static final Set UNAFFECTED_EFFECTS = new HashSet<>(); @@ -181,6 +183,21 @@ public class AndroidCapability implements ICapabilityProvider, IAndroidCapabilit ent.getCapability(MatteryCapability.ANDROID).ifPresent(ent.level.isClientSide ? IAndroidCapability::tickClient : IAndroidCapability::tick); } + @SubscribeEvent(priority = EventPriority.LOWEST) + public static void onHurtEvent(LivingHurtEvent event) { + if (event.isCanceled()) + return; + + var ent = event.getEntity(); + ent.getCapability(MatteryCapability.ANDROID).ifPresent((cap) -> cap.onHurt(event)); + } + + public void onHurt(LivingHurtEvent event) { + for (var feature : features.values()) { + feature.onHurt(event); + } + } + @Override @Nonnull public CompoundTag serializeNBT() { @@ -372,6 +389,10 @@ public class AndroidCapability implements ICapabilityProvider, IAndroidCapabilit howMuch = howMuch.subtract(changed, MatteryCapability.ROUND_RULES); if (howMuch.compareTo(BigDecimal.ZERO) <= 0) { + if (!simulate && ent instanceof ServerPlayer ply) { + ply.awardStat(Registry.Names.POWER_CONSUMED, drained.intValue() * 10); + } + return drained; } } @@ -388,6 +409,10 @@ public class AndroidCapability implements ICapabilityProvider, IAndroidCapabilit howMuch = howMuch.subtract(changed, MatteryCapability.ROUND_RULES); if (howMuch.compareTo(BigDecimal.ZERO) <= 0) { + if (!simulate && ent instanceof ServerPlayer ply) { + ply.awardStat(Registry.Names.POWER_CONSUMED, drained.intValue() * 10); + } + return drained; } } @@ -400,6 +425,10 @@ public class AndroidCapability implements ICapabilityProvider, IAndroidCapabilit if (!simulate) { energy_stored = new_energy; + + if (ent instanceof ServerPlayer ply) { + ply.awardStat(Registry.Names.POWER_CONSUMED, drained.intValue() * 10); + } } return drained; diff --git a/src/main/java/ru/dbotthepony/mc/otm/capability/AndroidCapabilityPlayer.java b/src/main/java/ru/dbotthepony/mc/otm/capability/AndroidCapabilityPlayer.java index 31aac5740..414880c66 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/capability/AndroidCapabilityPlayer.java +++ b/src/main/java/ru/dbotthepony/mc/otm/capability/AndroidCapabilityPlayer.java @@ -115,10 +115,10 @@ public class AndroidCapabilityPlayer extends AndroidCapability { return tag; } - public AndroidResearch getResearch(AndroidResearchType type) { + public T getResearch(AndroidResearchType type) { for (var instance : research_list) { if (instance.type == type) { - return instance; + return (T) instance; } } @@ -200,14 +200,14 @@ public class AndroidCapabilityPlayer extends AndroidCapability { var stats = ply.getFoodData(); - while (stats.getFoodLevel() < 17 && this.extractEnergyInner(ENERGY_FOR_HUNGER_POINT, true).compareTo(ENERGY_FOR_HUNGER_POINT) == 0) { + 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); } // "block" quick regeneration // also cause power to generate while in peaceful - while (stats.getFoodLevel() > 17 && this.receiveEnergyInner(ENERGY_FOR_HUNGER_POINT, true).compareTo(ENERGY_FOR_HUNGER_POINT) == 0) { + 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); } diff --git a/src/main/java/ru/dbotthepony/mc/otm/capability/IAndroidCapability.java b/src/main/java/ru/dbotthepony/mc/otm/capability/IAndroidCapability.java index 283f47e01..4c5526df4 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/capability/IAndroidCapability.java +++ b/src/main/java/ru/dbotthepony/mc/otm/capability/IAndroidCapability.java @@ -4,6 +4,7 @@ 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; @@ -18,14 +19,14 @@ public interface IAndroidCapability extends IMatteryEnergyStorage, INBTSerializa void tickClient(); LivingEntity getEntity(); - AndroidFeature addFeature(@Nonnull AndroidFeatureType feature); + T addFeature(@Nonnull AndroidFeatureType feature); boolean removeFeature(@Nonnull AndroidFeatureType feature); boolean hasFeature(@Nullable AndroidFeatureType feature); boolean hasFeatureLevel(@Nullable AndroidFeatureType feature, int level); - AndroidFeature getFeature(AndroidFeatureType feature); + T getFeature(AndroidFeatureType feature); @Nonnull - default Optional getFeatureO(AndroidFeatureType feature) { + default Optional getFeatureO(AndroidFeatureType feature) { var get = getFeature(feature); return get != null ? Optional.of(get) : Optional.empty(); } @@ -33,6 +34,7 @@ public interface IAndroidCapability extends IMatteryEnergyStorage, INBTSerializa default boolean isAndroid() { return true; } + default void onHurt(LivingHurtEvent event) {} @Nonnull ItemStack getBatteryItemStack(); diff --git a/src/main/java/ru/dbotthepony/mc/otm/network/android/AndroidResearchRequestPacket.java b/src/main/java/ru/dbotthepony/mc/otm/network/android/AndroidResearchRequestPacket.java index 1fae6aa6a..3ca48ff60 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/network/android/AndroidResearchRequestPacket.java +++ b/src/main/java/ru/dbotthepony/mc/otm/network/android/AndroidResearchRequestPacket.java @@ -8,6 +8,7 @@ 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.capability.AndroidCapabilityPlayer; +import ru.dbotthepony.mc.otm.menu.AndroidStationMenu; import java.util.Objects; import java.util.function.Supplier; @@ -34,7 +35,8 @@ public record AndroidResearchRequestPacket(AndroidResearchType research) { if (!cap.isAndroid()) return; - cap.getResearch(research).research(); + if (ply.containerMenu instanceof AndroidStationMenu) + cap.getResearch(research).research(); } public static AndroidResearchRequestPacket read(FriendlyByteBuf buffer) { diff --git a/src/main/java/ru/dbotthepony/mc/otm/screen/AndroidStationScreen.java b/src/main/java/ru/dbotthepony/mc/otm/screen/AndroidStationScreen.java index a2280990b..2cb3c49fe 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/screen/AndroidStationScreen.java +++ b/src/main/java/ru/dbotthepony/mc/otm/screen/AndroidStationScreen.java @@ -1,5 +1,6 @@ package ru.dbotthepony.mc.otm.screen; +import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.vertex.PoseStack; import net.minecraft.ChatFormatting; import net.minecraft.client.Minecraft; @@ -8,6 +9,7 @@ import net.minecraft.network.chat.TranslatableComponent; import net.minecraft.world.entity.player.Inventory; 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.capability.AndroidCapabilityPlayer; import ru.dbotthepony.mc.otm.menu.AndroidStationMenu; @@ -16,8 +18,7 @@ import ru.dbotthepony.mc.otm.menu.widget.GaugeWidget; import ru.dbotthepony.mc.otm.screen.panels.*; import javax.annotation.Nullable; -import java.util.ArrayList; -import java.util.List; +import java.util.*; public class AndroidStationScreen extends MatteryScreen implements MatteryScreen.IMatteryScreenGaugeGetter, MatteryScreen.IMatteryScreenBatteryGetter { class AndroidResearchButton extends EditablePanel { @@ -31,7 +32,7 @@ public class AndroidStationScreen extends MatteryScreen impl public AndroidResearchButton(@Nullable EditablePanel parent, AndroidResearch node) { super(AndroidStationScreen.this, parent, 0, 0, BUTTON_SIZE, BUTTON_SIZE); this.node = node; - setDockMargin(2, 2, 0, 0); + setDockMargin(2, 2, 2, 2); } @Override @@ -90,8 +91,7 @@ public class AndroidStationScreen extends MatteryScreen impl } public static final int FRAME_WIDTH = 210; - public static final int GRID_WIDTH = 6; - public static final int GRID_HEIGHT = 5; + public static final int GRID_WIDTH = 5; public static final int FRAME_HEIGHT = 120; public AndroidStationScreen(AndroidStationMenu p_97741_, Inventory p_97742_, Component p_97743_) { @@ -108,6 +108,144 @@ public class AndroidStationScreen extends MatteryScreen impl return List.of(menu.battery_slot); } + private final EditablePanel[] rows = new EditablePanel[100]; + private final Set> seen = new HashSet<>(); + private float next_x = 0; + private float[] rows_width = new float[100]; + private final AndroidResearchButton[][] created_buttons = new AndroidResearchButton[100][1000]; + private final int[] created_buttons_idx = new int[100]; + + private void dive(AndroidCapabilityPlayer cap, AndroidResearchType research, int level) { + if (seen.contains(research)) + return; + + seen.add(research); + + if (rows[level] == null) { + rows[level] = new EditablePanel(this, canvas, 0, level * 22, 10000, 22) { + @Override + protected boolean mouseClickedInner(double mouse_x, double mouse_y, int flag) { + return false; + } + + @Override + protected boolean mouseReleasedInner(double mouse_x, double mouse_y, int flag) { + return false; + } + + @Override + protected boolean mouseDraggedInner(double mouse_x, double mouse_y, int flag, double drag_x, double drag_y) { + return false; + } + }; + } + + var row = rows[level]; + var button = new AndroidResearchButton(row, cap.getResearch(research)); + button.setPos(next_x + rows_width[level], 2); + created_buttons[level][created_buttons_idx[level]] = button; + created_buttons_idx[level]++; + + rows_width[level] += 22; + + for (var _research : research.getUnlocks()) { + dive(cap, _research, level + 1); + } + + if (level > 0) { + for (var _research : research.getPrerequisites()) { + dive(cap, _research, level - 1); + } + } + } + + private DraggableCanvasPanel canvas; + private FramePanel research; + + private void openResearchTree() { + var window = this.minecraft.getWindow(); + research = new FramePanel(this, null, 0, 0, window.getGuiScaledWidth() * 0.8f, window.getGuiScaledHeight() * 0.8f, new TranslatableComponent("otm.gui.android_research")); + + canvas = new DraggableCanvasPanel(this, research, 0, 0, GRID_WIDTH * 22, 0) { + @Override + protected void innerRender(PoseStack stack, float mouse_x, float mouse_y, float flag) { + RenderHelper.setDrawColor(RGBAColor.BLACK); + RenderHelper.drawRect(stack, 0, 0, width, height); + } + }; + + minecraft.player.getCapability(MatteryCapability.ANDROID).ifPresent(_cap -> { + if (_cap instanceof AndroidCapabilityPlayer cap) { + Arrays.fill(rows, null); + next_x = 0; + + for (var research : Registry.ANDROID_RESEARCH().getValues()) { + if (research.getPrerequisites().size() == 0) { + dive(cap, research, 0); + float max = 0; + + for (float v : rows_width) + max = Math.max(max, v); + + for (var button_list : created_buttons) { + int count = 0; + + for (int i = 0; i < button_list.length; i++) { + if (button_list[i] == null) { + count = i; + break; + } + } + + if (count > 0) { + float this_x = next_x + max / 2f - (count * 22f) / 2f; + + for (int i = 0; i < count; i++) { + button_list[i].setPos(this_x, 2); + this_x += 22; + } + } + } + + for (var v : created_buttons) + Arrays.fill(v, null); + + next_x += max; + Arrays.fill(rows_width, 0); + Arrays.fill(created_buttons_idx, 0); + } + } + + seen.clear(); + } + }); + + canvas.setDock(Dock.FILL); + + var bottom = new EditablePanel(this, research, 0, 0, 0, 20); + var close = new ButtonPanel(this, bottom, 0, 0, 90, 20, new TranslatableComponent("otm.container.matter_panel.close")); + bottom.setDock(Dock.BOTTOM); + close.setDock(Dock.RIGHT); + close.bindOnPress(research::remove); + bottom.setDockMargin(0, 0, 4, 4); + + canvas.setDockMargin(4, 4, 4, 4); + + research.toScreenCenter(); + addPanel(research); + } + + @Override + public void resize(Minecraft minecraft, int p_96576_, int p_96577_) { + super.resize(minecraft, p_96576_, p_96577_); + + if (research != null) { + var window = minecraft.getWindow(); + research.setSize(window.getGuiScaledWidth() * 0.8f, window.getGuiScaledHeight() * 0.8f); + research.toScreenCenter(); + } + } + @Nullable @Override protected FramePanel makeMainFrame() { @@ -115,21 +253,10 @@ public class AndroidStationScreen extends MatteryScreen impl autoAttachToFrame(frame); - var grid = new GridPanel(this, frame, 0, 0, GRID_WIDTH * 18, 0, GRID_WIDTH, GRID_HEIGHT); - - minecraft.player.getCapability(MatteryCapability.ANDROID).ifPresent(_cap -> { - if (_cap instanceof AndroidCapabilityPlayer cap) { - for (var feature : Registry.ANDROID_RESEARCH().getValues()) { - var node = cap.getResearch(feature); - - if (node != null) { - new AndroidResearchButton(grid, node); - } - } - } - }); - - grid.setDock(Dock.RIGHT); + var button = new ButtonPanel(this, frame, 0, 0, 90, 20, new TranslatableComponent("otm.gui.android_research")); + button.setDock(Dock.BOTTOM); + button.bindOnPress(this::openResearchTree); + button.setDockMargin(10, 0, 10, 0); return frame; } diff --git a/src/main/java/ru/dbotthepony/mc/otm/screen/RGBAColor.java b/src/main/java/ru/dbotthepony/mc/otm/screen/RGBAColor.java index 5d1ef3d9f..9d6020e70 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/screen/RGBAColor.java +++ b/src/main/java/ru/dbotthepony/mc/otm/screen/RGBAColor.java @@ -10,6 +10,9 @@ public record RGBAColor(float r, float g, float b, float a) { this.a = a; } + public static final RGBAColor BLACK = new RGBAColor(0, 0, 0, 1f); + public static final RGBAColor WHITE = new RGBAColor(1f, 1f, 1f, 1f); + public RGBAColor(int r, int g, int b) { this(r / 255F, g / 255F, b / 255F, 1F); } diff --git a/src/main/java/ru/dbotthepony/mc/otm/screen/RenderHelper.java b/src/main/java/ru/dbotthepony/mc/otm/screen/RenderHelper.java index 4d752e202..c05e1ff63 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/screen/RenderHelper.java +++ b/src/main/java/ru/dbotthepony/mc/otm/screen/RenderHelper.java @@ -3,10 +3,13 @@ package ru.dbotthepony.mc.otm.screen; import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.vertex.*; import com.mojang.math.Matrix4f; +import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.GameRenderer; import net.minecraft.resources.ResourceLocation; import ru.dbotthepony.mc.otm.OverdriveThatMatters; +import java.util.Stack; + import static org.lwjgl.opengl.GL11.GL_ALWAYS; import static org.lwjgl.opengl.GL11.GL_LESS; @@ -553,4 +556,49 @@ public class RenderHelper { ) { drawRect(stack.last().pose(), x, y, width, height); } + + record ScissorRect(int x, int y, int width, int height) {} + + private static final Stack SCISSOR = new Stack<>(); + + public static void ensureScissorStackEmpty() { + if (!SCISSOR.empty()) { + throw new IllegalStateException("Unbalanced scissor rects: Popping less than pushing"); + } + } + + public static void pushScissorRect(int x, int y, int width, int height) { + if (!SCISSOR.empty()) { + final var peek = SCISSOR.peek(); + x = Math.max(x, peek.x); + y = Math.max(y, peek.y); + width = Math.min(width, peek.width); + height = Math.min(height, peek.height); + } + + SCISSOR.push(new ScissorRect(x, y, width, height)); + + final var window = Minecraft.getInstance().getWindow(); + y = window.getHeight() - y - height; + + RenderSystem.enableScissor(x, y, width, height); + } + + public static void popScissorRect() { + if (SCISSOR.empty()) { + throw new IllegalStateException("Unbalanced scissor rects: Popping more than pushing"); + } + + SCISSOR.pop(); + + if (SCISSOR.empty()) { + RenderSystem.disableScissor(); + return; + } + + final var value = SCISSOR.peek(); + final var window = Minecraft.getInstance().getWindow(); + final var y = window.getHeight() - value.y - value.height; + RenderSystem.enableScissor(value.x, y, value.width, value.height); + } } diff --git a/src/main/java/ru/dbotthepony/mc/otm/screen/panels/AbstractSlotPanel.java b/src/main/java/ru/dbotthepony/mc/otm/screen/panels/AbstractSlotPanel.java index a529f0193..d3ab1167f 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/screen/panels/AbstractSlotPanel.java +++ b/src/main/java/ru/dbotthepony/mc/otm/screen/panels/AbstractSlotPanel.java @@ -69,6 +69,8 @@ public abstract class AbstractSlotPanel extends EditablePanel { RenderSystem.depthFunc(GL_ALWAYS); screen.getItemRenderer().renderGuiItemDecorations(screen.getFont(), itemstack, 0, 0, count_override); + screen.getItemRenderer().blitOffset = 0; + // too big accumulations can lead to Z near clipping issues accumulate3DHeight(101); height = 101; diff --git a/src/main/java/ru/dbotthepony/mc/otm/screen/panels/DraggableCanvasPanel.java b/src/main/java/ru/dbotthepony/mc/otm/screen/panels/DraggableCanvasPanel.java new file mode 100644 index 000000000..370acaeb5 --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/screen/panels/DraggableCanvasPanel.java @@ -0,0 +1,51 @@ +package ru.dbotthepony.mc.otm.screen.panels; + +import ru.dbotthepony.mc.otm.screen.MatteryScreen; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class DraggableCanvasPanel extends EditablePanel { + public DraggableCanvasPanel(@Nonnull MatteryScreen screen, @Nullable EditablePanel parent, float x, float y, float width, float height) { + super(screen, parent, x, y, width, height); + scissor = true; + } + + public DraggableCanvasPanel(@Nonnull MatteryScreen screen, @Nullable EditablePanel parent) { + super(screen, parent); + scissor = true; + } + + protected boolean dragging = false; + protected double last_mouse_x; + protected double last_mouse_y; + + @Override + protected boolean mouseClickedInner(double mouse_x, double mouse_y, int flag) { + dragging = true; + last_mouse_x = mouse_x; + last_mouse_y = mouse_y; + setIgnoreMouseEventBoundaries(true); + return true; + } + + @Override + protected boolean mouseReleasedInner(double mouse_x, double mouse_y, int flag) { + dragging = false; + setIgnoreMouseEventBoundaries(false); + return true; + } + + @Override + protected boolean mouseDraggedInner(double mouse_x, double mouse_y, int flag, double drag_x, double drag_y) { + if (dragging) { + x_offset -= last_mouse_x - mouse_x; + y_offset -= last_mouse_y - mouse_y; + + last_mouse_x = mouse_x; + last_mouse_y = mouse_y; + } + + return true; + } +} diff --git a/src/main/java/ru/dbotthepony/mc/otm/screen/panels/EditablePanel.java b/src/main/java/ru/dbotthepony/mc/otm/screen/panels/EditablePanel.java index b8fd0c2a6..f8bde92ce 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/screen/panels/EditablePanel.java +++ b/src/main/java/ru/dbotthepony/mc/otm/screen/panels/EditablePanel.java @@ -2,8 +2,12 @@ package ru.dbotthepony.mc.otm.screen.panels; import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.math.Vector3f; +import com.mojang.math.Vector4f; +import net.minecraft.client.Minecraft; import net.minecraft.client.gui.components.events.GuiEventListener; import ru.dbotthepony.mc.otm.screen.MatteryScreen; +import ru.dbotthepony.mc.otm.screen.RenderHelper; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -56,6 +60,37 @@ public class EditablePanel implements GuiEventListener { protected float parent_x; protected float parent_y; + // allows to offset children by given amount of pixels + protected float x_offset; + protected float y_offset; + + // whenever is to scissor render bounds + protected boolean scissor = false; + + public float getXOffset() { + return x_offset; + } + + public void setXOffset(float x_offset) { + this.x_offset = x_offset; + } + + public float getYOffset() { + return y_offset; + } + + public void setYOffset(float y_offset) { + this.y_offset = y_offset; + } + + public boolean getEnableScissor() { + return scissor; + } + + public void setEnableScissor(boolean scissor) { + this.scissor = scissor; + } + protected float accumulated_depth = 0; public float getMost3DHeight() { @@ -151,6 +186,14 @@ public class EditablePanel implements GuiEventListener { is_hovered = parent.is_hovered && mouse_x >= parent_x && mouse_x < parent_x + width && mouse_y >= parent_y && mouse_y < parent_y + height; } + var scissor = this.scissor; + + if (scissor) { + var window = Minecraft.getInstance().getWindow(); + var scale = window.getGuiScale(); + RenderHelper.pushScissorRect((int) (parent_x * scale), (int) (parent_y * scale), (int) (width * scale), (int) (height * scale)); + } + stack.pushPose(); stack.translate(parent_x, parent_y, accumulated_depth); innerRender(stack, mouse_x, mouse_y, flag); @@ -161,14 +204,18 @@ public class EditablePanel implements GuiEventListener { for (var child : children) { if (child.getVisible()) { child.accumulated_depth = accumulated_depth + 1; - child.parent_x = parent_x + child.x; - child.parent_y = parent_y + child.y; + child.parent_x = parent_x + child.x + getXOffset(); + child.parent_y = parent_y + child.y + getYOffset(); RenderSystem.setShaderColor(1F, 1F, 1F, 1F); most_depth = Math.max(most_depth, child.render(stack, mouse_x, mouse_y, flag)); } } + if (scissor) { + RenderHelper.popScissorRect(); + } + return most_depth; } @@ -278,8 +325,8 @@ public class EditablePanel implements GuiEventListener { var get_parent = getParent(); while (get_parent != null) { - x += get_parent.getX(); - y += get_parent.getY(); + x += get_parent.getX() + get_parent.getXOffset(); + y += get_parent.getY() + get_parent.getYOffset(); get_parent = get_parent.getParent(); } @@ -741,7 +788,7 @@ public class EditablePanel implements GuiEventListener { } protected boolean mouseClickedInner(double mouse_x, double mouse_y, int flag) { - return false; + return true; } public boolean withinBounds(double mouse_x, double mouse_y) { @@ -781,8 +828,7 @@ public class EditablePanel implements GuiEventListener { if (getMouseInputEnabled() && parent == null) screen.popup(this); - mouseClicked(mouse_x, mouse_y, flag); - return true; + return mouseClicked(mouse_x, mouse_y, flag); } else if (withinExtendedBounds(mouse_x, mouse_y)) { for (var child : children) { if (child.mouseClickedChecked(mouse_x, mouse_y, flag)) { @@ -802,7 +848,7 @@ public class EditablePanel implements GuiEventListener { } protected boolean mouseReleasedInner(double mouse_x, double mouse_y, int flag) { - return false; + return true; } @Override @@ -827,8 +873,7 @@ public class EditablePanel implements GuiEventListener { return false; if (getIgnoreMouseEventBoundaries() || withinBounds(mouse_x, mouse_y)) { - mouseReleased(mouse_x, mouse_y, flag); - return true; + return mouseReleased(mouse_x, mouse_y, flag); } else if (withinExtendedBounds(mouse_x, mouse_y)) { for (var child : children) { if (child.mouseReleasedChecked(mouse_x, mouse_y, flag)) { @@ -841,7 +886,7 @@ public class EditablePanel implements GuiEventListener { } protected boolean mouseDraggedInner(double mouse_x, double mouse_y, int flag, double drag_x, double drag_y) { - return false; + return true; } @Override @@ -866,8 +911,7 @@ public class EditablePanel implements GuiEventListener { return false; if (getIgnoreMouseEventBoundaries() || withinBounds(mouse_x, mouse_y)) { - mouseDragged(mouse_x, mouse_y, flag, drag_x, drag_y); - return true; + return mouseDragged(mouse_x, mouse_y, flag, drag_x, drag_y); } else if (withinExtendedBounds(mouse_x, mouse_y)) { for (var child : children) { if (child.mouseDraggedChecked(mouse_x, mouse_y, flag, drag_x, drag_y)) { @@ -880,7 +924,7 @@ public class EditablePanel implements GuiEventListener { } protected boolean mouseScrolledInner(double mouse_x, double mouse_y, double scroll) { - return false; + return true; } @Override @@ -905,8 +949,7 @@ public class EditablePanel implements GuiEventListener { return false; if (getIgnoreMouseEventBoundaries() || withinBounds(mouse_x, mouse_y)) { - mouseScrolled(mouse_x, mouse_y, scroll); - return true; + return mouseScrolled(mouse_x, mouse_y, scroll); } else if (withinExtendedBounds(mouse_x, mouse_y)) { for (var child : children) { if (child.mouseScrolledChecked(mouse_x, mouse_y, scroll)) { @@ -1011,7 +1054,9 @@ public class EditablePanel implements GuiEventListener { } public void tick() { - + for (var child : children) { + child.tick(); + } } private boolean is_removed = false; diff --git a/src/main/resources/assets/overdrive_that_matters/lang/en_us.json b/src/main/resources/assets/overdrive_that_matters/lang/en_us.json index c655e7ca5..9f1f2e3ab 100644 --- a/src/main/resources/assets/overdrive_that_matters/lang/en_us.json +++ b/src/main/resources/assets/overdrive_that_matters/lang/en_us.json @@ -15,6 +15,8 @@ "otm.gui.matter.format": "Matter: %s", "otm.gui.matter.name": "MtU", + "otm.gui.android_research": "Android research tree", + "otm.gui.pattern.percentage_level": "Fill level: %s%%", "otm.gui.pattern.format": "Stored patterns: %s / %s", @@ -49,21 +51,36 @@ "otm.matter_bottler.switch_mode": "Switch work mode", "android_feature.overdrive_that_matters.air_bags": "Air bags", - "android_feature.overdrive_that_matters.limb_overclocking_1": "Limb overclocking 1", - "android_feature.overdrive_that_matters.limb_overclocking_2": "Limb overclocking 2", - "android_feature.overdrive_that_matters.limb_overclocking_3": "Limb overclocking 3", - "android_feature.overdrive_that_matters.limb_overclocking_4": "Limb overclocking 4", "android_research.overdrive_that_matters.limb_overclocking": "Limb overclocking %s", - "android_research.overdrive_that_matters.limb_overclocking.description": "Boosts unit mobility by %s%% and attack speed by %s%%", + "android_research.overdrive_that_matters.limb_overclocking.description": "Boosts unit's mobility by %s%% and attack speed by %s%%", "android_research.status.requires": "Requires %s to be researched", "android_research.status.blocks": "Locks %s", "android_research.status.blocked_by": "Locked by %s", "android_research.overdrive_that_matters.air_bags": "Air bags", + "android_research.overdrive_that_matters.nanobots": "Nanobots", "android_research.overdrive_that_matters.air_bags.description": "Allows unit to swim in water", + "android_research.overdrive_that_matters.nanobots.description": "Various useful nanobots for doing various tasks", + + "android_research.overdrive_that_matters.nanobots_regeneration": "Regeneration %s", + "android_research.overdrive_that_matters.nanobots_regeneration.description": "Nanobots get ability to repair unit's internal systems on the move", + "android_research.overdrive_that_matters.nanobots_regeneration.description_improve": "Improves regeneration speed", + + "android_research.overdrive_that_matters.nanobots_armor": "Nanobots armor", + "android_research.overdrive_that_matters.nanobots_armor.description": "Allows nanobots to align themselves in cell shape, reducing incoming damage by a %% by absorbing impacts", + + "android_research.overdrive_that_matters.nanobots_armor_speed": "Nanobots armor build speed %s", + "android_research.overdrive_that_matters.nanobots_armor_speed.description": "Reduces time required for nanobots to form protection layer", + + "android_research.overdrive_that_matters.nanobots_armor_strength": "Nanobots armor strength %s", + "android_research.overdrive_that_matters.nanobots_armor_strength.description": "Increases impact absorption strength of nanobots", + + "stat.overdrive_that_matters.health_regenerated": "Damage Regenerated by Nanobots", + "stat.overdrive_that_matters.damage_absorbed": "Damage Absorbed by Nanobots", + "stat.overdrive_that_matters.power_consumed": "MtE Burnt as Android", "otm.suffix.merge": "%s %s",