Initial commit
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
package net.fretux.ascend;
|
||||
|
||||
import net.fretux.ascend.command.StatsCommand;
|
||||
import net.fretux.ascend.event.ActivityEvents;
|
||||
import net.fretux.ascend.event.PlayerEvents;
|
||||
import net.fretux.ascend.network.PacketHandler;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
import net.minecraftforge.event.RegisterCommandsEvent;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
|
||||
import net.minecraftforge.eventbus.api.IEventBus;
|
||||
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
|
||||
import net.minecraftforge.fml.loading.FMLEnvironment;
|
||||
|
||||
@Mod(AscendMod.MODID)
|
||||
public class AscendMod {
|
||||
public static final String MODID = "ascend";
|
||||
|
||||
public AscendMod() {
|
||||
IEventBus modBus = FMLJavaModLoadingContext.get().getModEventBus();
|
||||
modBus.addListener(this::onCommonSetup);
|
||||
MinecraftForge.EVENT_BUS.register(this);
|
||||
System.out.println("[Ascend] Mod loaded! Side=" + FMLEnvironment.dist);
|
||||
}
|
||||
|
||||
private void onCommonSetup(final FMLCommonSetupEvent event) {
|
||||
event.enqueueWork(() -> {
|
||||
PacketHandler.register();
|
||||
System.out.println("[Ascend] Registering packets... Side=" + FMLEnvironment.dist);
|
||||
});
|
||||
}
|
||||
|
||||
// ✅ Directly subscribe in this mod class instead of inner static
|
||||
@SubscribeEvent
|
||||
public void onRegisterCommands(RegisterCommandsEvent event) {
|
||||
StatsCommand.register(event.getDispatcher());
|
||||
System.out.println("[Ascend] Registered /stats command on side=" + FMLEnvironment.dist);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package net.fretux.ascend;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraftforge.common.ForgeConfigSpec;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import net.minecraftforge.fml.event.config.ModConfigEvent;
|
||||
import net.minecraftforge.registries.ForgeRegistries;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Mod.EventBusSubscriber(modid = AscendMod.MODID, bus = Mod.EventBusSubscriber.Bus.MOD)
|
||||
public class Config {
|
||||
private static final ForgeConfigSpec.Builder BUILDER = new ForgeConfigSpec.Builder();
|
||||
|
||||
private static final ForgeConfigSpec.BooleanValue LOG_DIRT_BLOCK = BUILDER
|
||||
.comment("Whether to log the dirt block on common setup")
|
||||
.define("logDirtBlock", true);
|
||||
|
||||
private static final ForgeConfigSpec.IntValue MAGIC_NUMBER = BUILDER
|
||||
.comment("A magic number")
|
||||
.defineInRange("magicNumber", 42, 0, Integer.MAX_VALUE);
|
||||
|
||||
public static final ForgeConfigSpec.ConfigValue<String> MAGIC_NUMBER_INTRODUCTION = BUILDER
|
||||
.comment("What you want the introduction message to be for the magic number")
|
||||
.define("magicNumberIntroduction", "The magic number is... ");
|
||||
|
||||
private static final ForgeConfigSpec.ConfigValue<List<? extends String>> ITEM_STRINGS = BUILDER
|
||||
.comment("A list of items to log on common setup.")
|
||||
.defineListAllowEmpty("items", List.of("minecraft:iron_ingot"), Config::validateItemName);
|
||||
|
||||
static final ForgeConfigSpec SPEC = BUILDER.build();
|
||||
|
||||
public static boolean logDirtBlock;
|
||||
public static int magicNumber;
|
||||
public static String magicNumberIntroduction;
|
||||
public static Set<Item> items;
|
||||
|
||||
private static boolean validateItemName(final Object obj) {
|
||||
return obj instanceof final String itemName && ForgeRegistries.ITEMS.containsKey(new ResourceLocation(itemName));
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
static void onLoad(final ModConfigEvent event) {
|
||||
logDirtBlock = LOG_DIRT_BLOCK.get();
|
||||
magicNumber = MAGIC_NUMBER.get();
|
||||
magicNumberIntroduction = MAGIC_NUMBER_INTRODUCTION.get();
|
||||
items = ITEM_STRINGS.get().stream()
|
||||
.map(itemName -> ForgeRegistries.ITEMS.getValue(new ResourceLocation(itemName)))
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package net.fretux.ascend.client;
|
||||
|
||||
import net.fretux.ascend.client.screen.StatsScreen;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
import net.minecraftforge.client.event.InputEvent;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import net.fretux.ascend.AscendMod;
|
||||
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
@Mod.EventBusSubscriber(modid = AscendMod.MODID, value = Dist.CLIENT)
|
||||
public class ClientEvents {
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onKeyInput(InputEvent.Key event) {
|
||||
if (KeyBindings.OPEN_STATS.consumeClick()) {
|
||||
Minecraft.getInstance().setScreen(new StatsScreen());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package net.fretux.ascend.client;
|
||||
|
||||
import com.mojang.blaze3d.platform.InputConstants;
|
||||
import net.minecraft.client.KeyMapping;
|
||||
import net.minecraftforge.client.event.RegisterKeyMappingsEvent;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import net.fretux.ascend.AscendMod;
|
||||
|
||||
@Mod.EventBusSubscriber(modid = AscendMod.MODID, bus = Mod.EventBusSubscriber.Bus.MOD, value = net.minecraftforge.api.distmarker.Dist.CLIENT)
|
||||
public class KeyBindings {
|
||||
public static final KeyMapping OPEN_STATS = new KeyMapping(
|
||||
"key.ascend.open_stats",
|
||||
InputConstants.KEY_V,
|
||||
"key.categories.inventory"
|
||||
);
|
||||
|
||||
@SubscribeEvent
|
||||
public static void register(RegisterKeyMappingsEvent event) {
|
||||
event.register(OPEN_STATS);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
package net.fretux.ascend.client.screen;
|
||||
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
import net.fretux.ascend.network.PacketHandler;
|
||||
import net.fretux.ascend.network.ServerboundSpendPointPacket;
|
||||
import net.fretux.ascend.player.PlayerStats;
|
||||
import net.fretux.ascend.player.PlayerStatsProvider;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
import net.minecraft.client.gui.components.Button;
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
import net.minecraft.network.chat.Component;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class StatsScreen extends Screen {
|
||||
|
||||
private static final int WIDTH = 220;
|
||||
private static final int HEIGHT = 210;
|
||||
|
||||
private int leftPos;
|
||||
private int topPos;
|
||||
|
||||
private final Minecraft mc = Minecraft.getInstance();
|
||||
|
||||
private static final String[] ATTRIBUTES = new String[]{
|
||||
"strength", "agility", "fortitude", "intelligence",
|
||||
"willpower", "charisma",
|
||||
"light_scaling", "medium_scaling", "heavy_scaling", "magic_scaling"
|
||||
};
|
||||
|
||||
// Track plus buttons so we can update enabled state & labels based on current stats
|
||||
private final Map<String, Button> plusButtons = new HashMap<>();
|
||||
|
||||
public StatsScreen() {
|
||||
super(Component.literal("Ascend Stats"));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void init() {
|
||||
super.init();
|
||||
|
||||
this.leftPos = (this.width - WIDTH) / 2;
|
||||
this.topPos = (this.height - HEIGHT) / 2;
|
||||
|
||||
this.plusButtons.clear();
|
||||
this.clearWidgets();
|
||||
|
||||
// Create + buttons for each attribute
|
||||
int y = topPos + 55;
|
||||
for (String attr : ATTRIBUTES) {
|
||||
final String key = attr;
|
||||
|
||||
Button plus = Button.builder(Component.literal("+"), (button) -> {
|
||||
// Send spend request to server for this attribute
|
||||
PacketHandler.INSTANCE.sendToServer(new ServerboundSpendPointPacket(key));
|
||||
})
|
||||
.bounds(leftPos + WIDTH - 40, y - 6, 30, 18)
|
||||
.build();
|
||||
|
||||
this.addRenderableWidget(plus);
|
||||
this.plusButtons.put(key, plus);
|
||||
|
||||
y += 16;
|
||||
}
|
||||
|
||||
// Done/close button
|
||||
Button done = Button.builder(Component.translatable("gui.done"), (btn) -> onClose())
|
||||
.bounds(leftPos + WIDTH / 2 - 40, topPos + HEIGHT - 24, 80, 20)
|
||||
.build();
|
||||
this.addRenderableWidget(done);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(GuiGraphics gui, int mouseX, int mouseY, float partialTicks) {
|
||||
this.renderBackground(gui);
|
||||
|
||||
// Panel background (simple rectangle; swap to texture if you have one)
|
||||
RenderSystem.enableBlend();
|
||||
gui.fill(leftPos - 4, topPos - 4, leftPos + WIDTH + 4, topPos + HEIGHT + 4, 0xAA000000);
|
||||
RenderSystem.disableBlend();
|
||||
|
||||
// Title
|
||||
gui.drawCenteredString(this.font, this.title, this.width / 2, topPos + 8, 0xFFFFFF);
|
||||
|
||||
if (mc.player != null) {
|
||||
mc.player.getCapability(PlayerStatsProvider.PLAYER_STATS).ifPresent(stats -> {
|
||||
// ==== Top Info: Level, XP, Points ====
|
||||
int level = stats.getLevel();
|
||||
int xp = stats.getAscendXP();
|
||||
int xpNext = stats.getXPForNextLevel(); // 0 if max
|
||||
int unspent = stats.getUnspentPoints();
|
||||
|
||||
// Level
|
||||
String levelStr = "Level: " + level + "/" + 20;
|
||||
gui.drawString(font, levelStr, leftPos + 10, topPos + 24, 0xFFFF55);
|
||||
|
||||
// Ascend XP
|
||||
if (level < 20) {
|
||||
String xpStr = "Ascend XP: " + xp + " / " + xpNext;
|
||||
gui.drawString(font, xpStr, leftPos + 10, topPos + 36, 0xAAAAAA);
|
||||
} else {
|
||||
gui.drawString(font, "Ascend XP: MAX", leftPos + 10, topPos + 36, 0xAAAAAA);
|
||||
}
|
||||
|
||||
// Unspent points (right aligned)
|
||||
String ptsStr = "Unspent Points: " + unspent;
|
||||
int ptsWidth = font.width(ptsStr);
|
||||
gui.drawString(font, ptsStr,
|
||||
leftPos + WIDTH - 10 - ptsWidth, topPos + 24, 0x55FF55);
|
||||
|
||||
// ==== Attributes List ====
|
||||
int y = topPos + 55;
|
||||
for (String key : ATTRIBUTES) {
|
||||
int attrLevel = stats.getAttributeLevel(key);
|
||||
int sourceXP = stats.getXP(key); // per-activity XP, just for feedback
|
||||
int cost = stats.getCostToUpgrade(key);
|
||||
|
||||
String niceName = formatAttributeName(key);
|
||||
|
||||
// Attribute name + level
|
||||
gui.drawString(font,
|
||||
niceName + ": " + attrLevel,
|
||||
leftPos + 10, y,
|
||||
0xFFFFFF);
|
||||
|
||||
// XP (source-based)
|
||||
gui.drawString(font,
|
||||
"XP: " + sourceXP,
|
||||
leftPos + 110, y,
|
||||
0x777777);
|
||||
|
||||
// Update + button (position, text, enabled)
|
||||
Button plus = plusButtons.get(key);
|
||||
if (plus != null) {
|
||||
plus.setX(leftPos + WIDTH - 40);
|
||||
plus.setY(y - 6);
|
||||
|
||||
// Always visible, but only active if enough points
|
||||
plus.active = (unspent >= cost);
|
||||
|
||||
// Show cost inside button
|
||||
// Keep it short; if too long for your font, switch to just "+"
|
||||
plus.setMessage(Component.literal("+" + cost));
|
||||
}
|
||||
|
||||
y += 16;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
super.render(gui, mouseX, mouseY, partialTicks);
|
||||
}
|
||||
|
||||
private String formatAttributeName(String key) {
|
||||
String withSpaces = key.replace('_', ' ');
|
||||
return withSpaces.substring(0, 1).toUpperCase() + withSpaces.substring(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPauseScreen() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package net.fretux.ascend.command;
|
||||
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import com.mojang.brigadier.arguments.StringArgumentType;
|
||||
import net.fretux.ascend.player.PlayerStatsProvider;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.commands.Commands;
|
||||
import net.minecraft.network.chat.Component;
|
||||
|
||||
public class StatsCommand {
|
||||
|
||||
public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
|
||||
dispatcher.register(Commands.literal("stats")
|
||||
.then(Commands.literal("show")
|
||||
.executes(ctx -> {
|
||||
var player = ctx.getSource().getPlayerOrException();
|
||||
player.getCapability(PlayerStatsProvider.PLAYER_STATS).ifPresent(stats -> {
|
||||
player.sendSystemMessage(Component.literal("Unspent points: " + stats.getUnspentPoints()));
|
||||
for (String key : stats.serializeNBT().getCompound("Attributes").getAllKeys()) {
|
||||
int lvl = stats.getAttributeLevel(key);
|
||||
int xp = stats.getXP(key);
|
||||
int needed = stats.getXPToNextLevel(key);
|
||||
player.sendSystemMessage(Component.literal(
|
||||
key + " → Lvl " + lvl + " | XP: " + xp + "/" + needed
|
||||
));
|
||||
}
|
||||
});
|
||||
return 1;
|
||||
}))
|
||||
.then(Commands.literal("spend")
|
||||
.then(Commands.argument("attribute", StringArgumentType.string())
|
||||
.executes(ctx -> {
|
||||
var player = ctx.getSource().getPlayerOrException();
|
||||
String attr = StringArgumentType.getString(ctx, "attribute");
|
||||
player.getCapability(PlayerStatsProvider.PLAYER_STATS).ifPresent(stats -> {
|
||||
int cost = stats.getXPToNextLevel(attr);
|
||||
boolean success = stats.spendPoints(attr);
|
||||
if (success) {
|
||||
player.sendSystemMessage(Component.literal("You invested a point in " + attr + "!"));
|
||||
} else {
|
||||
player.sendSystemMessage(Component.literal("Not enough points to upgrade " + attr + "."));
|
||||
}
|
||||
});
|
||||
return 1;
|
||||
})))
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
package net.fretux.ascend.event;
|
||||
|
||||
import net.fretux.ascend.AscendMod;
|
||||
import net.fretux.ascend.player.PlayerStatsProvider;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.effect.MobEffectCategory;
|
||||
import net.minecraft.world.entity.npc.Villager;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.level.LightLayer;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraftforge.event.TickEvent;
|
||||
import net.minecraftforge.event.entity.living.LivingDeathEvent;
|
||||
import net.minecraftforge.event.entity.living.LivingHurtEvent;
|
||||
import net.minecraftforge.event.entity.player.PlayerEvent;
|
||||
import net.minecraftforge.event.entity.player.PlayerInteractEvent;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import net.minecraftforge.event.level.BlockEvent;
|
||||
|
||||
@Mod.EventBusSubscriber(modid = AscendMod.MODID)
|
||||
public class ActivityEvents {
|
||||
|
||||
private static int agilityTickCounter = 0;
|
||||
private static int fortitudeTickCounter = 0;
|
||||
private static int willpowerTickCounter = 0;
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onMobKilled(LivingDeathEvent event) {
|
||||
if (event.getSource().getEntity() instanceof Player player && !player.level().isClientSide) {
|
||||
player.getCapability(PlayerStatsProvider.PLAYER_STATS).ifPresent(stats ->
|
||||
stats.addXP("strength", 5)
|
||||
);
|
||||
PlayerStatsProvider.sync(player);
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onBlockBreak(BlockEvent.BreakEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
if (player == null || player.level().isClientSide) return;
|
||||
Block block = event.getState().getBlock();
|
||||
String name = block.getName().getString().toLowerCase();
|
||||
player.getCapability(PlayerStatsProvider.PLAYER_STATS).ifPresent(stats -> {
|
||||
if (name.contains("log") || name.contains("ore") || name.contains("stone")) {
|
||||
stats.addXP("strength", 2);
|
||||
System.out.println("Added 2 strength XP");
|
||||
PlayerStatsProvider.sync(player);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onPlayerDamaged(LivingHurtEvent event) {
|
||||
if (event.getEntity() instanceof Player player && !player.level().isClientSide) {
|
||||
player.getCapability(PlayerStatsProvider.PLAYER_STATS).ifPresent(stats ->
|
||||
stats.addXP("fortitude", Math.min((int) event.getAmount() * 2, 10))
|
||||
);
|
||||
PlayerStatsProvider.sync(player);
|
||||
System.out.println("Added " + event.getAmount() * 2 + " fortitude XP");
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onPlayerTickFortitude(TickEvent.PlayerTickEvent event) {
|
||||
Player player = event.player;
|
||||
if (player.level().isClientSide || event.phase != TickEvent.Phase.END) return;
|
||||
fortitudeTickCounter++;
|
||||
if (fortitudeTickCounter % 100 == 0) {
|
||||
player.getCapability(PlayerStatsProvider.PLAYER_STATS).ifPresent(stats -> {
|
||||
boolean hasNegativeEffect = player.getActiveEffects().stream()
|
||||
.anyMatch(e -> e.getEffect().getCategory() == MobEffectCategory.HARMFUL);
|
||||
if (player.getFoodData().getFoodLevel() <= 6 || hasNegativeEffect) {
|
||||
stats.addXP("fortitude", 2);
|
||||
PlayerStatsProvider.sync(player);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onPlayerTickAgility(TickEvent.PlayerTickEvent event) {
|
||||
Player player = event.player;
|
||||
if (player.level().isClientSide || event.phase != TickEvent.Phase.END) return;
|
||||
agilityTickCounter++;
|
||||
if (agilityTickCounter % 20 != 0) return;
|
||||
player.getCapability(PlayerStatsProvider.PLAYER_STATS).ifPresent(stats -> {
|
||||
if (player.isSprinting()) stats.addXP("agility", 2);
|
||||
if (player.isCrouching()) stats.addXP("agility", 1);
|
||||
if (Math.abs(player.getDeltaMovement().y) > 0.25) stats.addXP("agility", 1);
|
||||
if (player.onClimbable()) stats.addXP("agility", 1);
|
||||
PlayerStatsProvider.sync(player);
|
||||
});
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onItemCrafted(PlayerEvent.ItemCraftedEvent event) {
|
||||
Player player = event.getEntity();
|
||||
if (player.level().isClientSide) return;
|
||||
player.getCapability(PlayerStatsProvider.PLAYER_STATS).ifPresent(stats -> {
|
||||
stats.addXP("intelligence", 3);
|
||||
PlayerStatsProvider.sync(player);
|
||||
});
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onItemSmelted(PlayerEvent.ItemSmeltedEvent event) {
|
||||
Player player = event.getEntity();
|
||||
if (player.level().isClientSide) return;
|
||||
player.getCapability(PlayerStatsProvider.PLAYER_STATS).ifPresent(stats -> {
|
||||
stats.addXP("intelligence", 2);
|
||||
PlayerStatsProvider.sync(player);
|
||||
});
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onPlayerTickWillpower(TickEvent.PlayerTickEvent event) {
|
||||
Player player = event.player;
|
||||
if (player.level().isClientSide || event.phase != TickEvent.Phase.END) return;
|
||||
willpowerTickCounter++;
|
||||
if (willpowerTickCounter % 100 == 0) { // every 5s
|
||||
player.getCapability(PlayerStatsProvider.PLAYER_STATS).ifPresent(stats -> {
|
||||
// XP for surviving under pressure
|
||||
boolean hasNegativeEffect = player.getActiveEffects().stream()
|
||||
.anyMatch(e -> e.getEffect().getCategory() == MobEffectCategory.HARMFUL);
|
||||
boolean lowHealth = player.getHealth() <= player.getMaxHealth() * 0.25f;
|
||||
|
||||
// Check darkness
|
||||
BlockPos pos = player.blockPosition();
|
||||
int light = player.level().getBrightness(LightLayer.BLOCK, pos);
|
||||
|
||||
if (hasNegativeEffect || lowHealth || light < 4) {
|
||||
stats.addXP("willpower", 2);
|
||||
PlayerStatsProvider.sync(player);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onVillagerTrade(PlayerInteractEvent.EntityInteract event) {
|
||||
if (!(event.getTarget() instanceof Villager villager)) return;
|
||||
Player player = event.getEntity();
|
||||
if (player.level().isClientSide) return;
|
||||
player.getCapability(PlayerStatsProvider.PLAYER_STATS).ifPresent(stats -> {
|
||||
stats.addXP("charisma", 5);
|
||||
PlayerStatsProvider.sync(player);
|
||||
});
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onWeaponUsed(LivingHurtEvent event) {
|
||||
if (!(event.getSource().getEntity() instanceof Player player)) return;
|
||||
if (player.level().isClientSide) return;
|
||||
if (event.getEntity() == player) return;
|
||||
|
||||
player.getCapability(PlayerStatsProvider.PLAYER_STATS).ifPresent(stats -> {
|
||||
var heldItem = player.getMainHandItem();
|
||||
if (heldItem.isEmpty()) return;
|
||||
|
||||
double attackSpeed = player.getAttribute(
|
||||
net.minecraft.world.entity.ai.attributes.Attributes.ATTACK_SPEED
|
||||
).getBaseValue();
|
||||
|
||||
String category = (attackSpeed >= 2.0) ? "light_scaling"
|
||||
: (attackSpeed >= 1.2) ? "medium_scaling"
|
||||
: "heavy_scaling";
|
||||
|
||||
stats.addXP(category, 3);
|
||||
PlayerStatsProvider.sync(player);
|
||||
});
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onPotionUse(PlayerInteractEvent.RightClickItem event) {
|
||||
Player player = event.getEntity();
|
||||
if (player.level().isClientSide) return;
|
||||
if (event.getItemStack().getItem().getDescriptionId().contains("potion")) {
|
||||
player.getCapability(PlayerStatsProvider.PLAYER_STATS).ifPresent(stats -> {
|
||||
stats.addXP("magic_scaling", 3);
|
||||
PlayerStatsProvider.sync(player);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onGainPotionEffect(net.minecraftforge.event.entity.living.MobEffectEvent.Added event) {
|
||||
if (!(event.getEntity() instanceof Player player)) return;
|
||||
if (player.level().isClientSide) return;
|
||||
if (event.getEffectInstance().getEffect().getCategory() == net.minecraft.world.effect.MobEffectCategory.BENEFICIAL) {
|
||||
player.getCapability(PlayerStatsProvider.PLAYER_STATS).ifPresent(stats -> {
|
||||
stats.addXP("magic_scaling", 2);
|
||||
PlayerStatsProvider.sync(player);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onItemSmeltedForMagic(PlayerEvent.ItemSmeltedEvent event) {
|
||||
Player player = event.getEntity();
|
||||
if (player.level().isClientSide) return;
|
||||
String itemName = event.getSmelting().getDisplayName().getString().toLowerCase();
|
||||
if (itemName.contains("potion") || itemName.contains("elixir")) {
|
||||
player.getCapability(PlayerStatsProvider.PLAYER_STATS).ifPresent(stats -> {
|
||||
stats.addXP("magic_scaling", 2);
|
||||
PlayerStatsProvider.sync(player);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package net.fretux.ascend.event;
|
||||
|
||||
import net.fretux.ascend.AscendMod;
|
||||
import net.fretux.ascend.player.PlayerStatsProvider;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
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;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
|
||||
@Mod.EventBusSubscriber(modid = AscendMod.MODID)
|
||||
public class PlayerEvents {
|
||||
|
||||
private static final ResourceLocation PLAYER_STATS_ID =
|
||||
new ResourceLocation(AscendMod.MODID, "player_stats");
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onAttachCapabilities(AttachCapabilitiesEvent<Entity> event) {
|
||||
if (event.getObject() instanceof Player player) {
|
||||
event.addCapability(PLAYER_STATS_ID, new PlayerStatsProvider());
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onPlayerClone(PlayerEvent.Clone event) {
|
||||
event.getOriginal().reviveCaps();
|
||||
event.getOriginal().getCapability(PlayerStatsProvider.PLAYER_STATS).ifPresent(oldStats ->
|
||||
event.getEntity().getCapability(PlayerStatsProvider.PLAYER_STATS).ifPresent(newStats ->
|
||||
newStats.deserializeNBT(oldStats.serializeNBT())));
|
||||
}
|
||||
|
||||
// Optional but very helpful: sync on login so client sees initial 15 points
|
||||
@SubscribeEvent
|
||||
public static void onPlayerLogin(PlayerEvent.PlayerLoggedInEvent event) {
|
||||
if (!event.getEntity().level().isClientSide) {
|
||||
PlayerStatsProvider.sync(event.getEntity());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package net.fretux.ascend.network;
|
||||
|
||||
import net.fretux.ascend.player.PlayerStats;
|
||||
import net.fretux.ascend.player.PlayerStatsProvider;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraftforge.network.NetworkEvent;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class ClientboundSyncStatsPacket {
|
||||
private final CompoundTag data;
|
||||
|
||||
public ClientboundSyncStatsPacket(CompoundTag data) { this.data = data; }
|
||||
public ClientboundSyncStatsPacket(FriendlyByteBuf buf) { this.data = buf.readNbt(); }
|
||||
public void toBytes(FriendlyByteBuf buf) { buf.writeNbt(data); }
|
||||
|
||||
public void handle(Supplier<NetworkEvent.Context> ctx) {
|
||||
System.out.println("[SERVER] Spend packet handle() called");
|
||||
ctx.get().enqueueWork(() -> {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
var player = mc.player;
|
||||
if (player != null) {
|
||||
player.getCapability(PlayerStatsProvider.PLAYER_STATS).ifPresent(stats -> {
|
||||
stats.deserializeNBT(data);
|
||||
System.out.println("[CLIENT] Before sync: points="
|
||||
+ player.getCapability(PlayerStatsProvider.PLAYER_STATS)
|
||||
.map(PlayerStats::getUnspentPoints).orElse(-1));
|
||||
stats.deserializeNBT(data);
|
||||
System.out.println("[CLIENT] After sync: points="
|
||||
+ player.getCapability(PlayerStatsProvider.PLAYER_STATS)
|
||||
.map(PlayerStats::getUnspentPoints).orElse(-1));
|
||||
|
||||
});
|
||||
}
|
||||
});
|
||||
ctx.get().setPacketHandled(true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package net.fretux.ascend.network;
|
||||
|
||||
import net.fretux.ascend.AscendMod;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraftforge.fml.loading.FMLEnvironment;
|
||||
import net.minecraftforge.network.NetworkDirection;
|
||||
import net.minecraftforge.network.NetworkRegistry;
|
||||
import net.minecraftforge.network.simple.SimpleChannel;
|
||||
|
||||
public class PacketHandler {
|
||||
private static final String PROTOCOL_VERSION = "1";
|
||||
public static SimpleChannel INSTANCE;
|
||||
private static int id = 0;
|
||||
|
||||
public static void register() {
|
||||
System.out.println("[Ascend] Registering network packets...");
|
||||
System.out.println("[Ascend] Registering packets on side: " +
|
||||
(FMLEnvironment.dist.isClient() ? "CLIENT" : "SERVER"));
|
||||
|
||||
INSTANCE = NetworkRegistry.newSimpleChannel(
|
||||
new ResourceLocation(AscendMod.MODID, "main"),
|
||||
() -> PROTOCOL_VERSION,
|
||||
PROTOCOL_VERSION::equals,
|
||||
PROTOCOL_VERSION::equals
|
||||
);
|
||||
|
||||
INSTANCE.messageBuilder(ServerboundSpendPointPacket.class, id++, NetworkDirection.PLAY_TO_SERVER)
|
||||
.encoder(ServerboundSpendPointPacket::toBytes)
|
||||
.decoder(ServerboundSpendPointPacket::new)
|
||||
.consumerMainThread(ServerboundSpendPointPacket::handle)
|
||||
.add();
|
||||
|
||||
INSTANCE.messageBuilder(ClientboundSyncStatsPacket.class, id++, NetworkDirection.PLAY_TO_CLIENT)
|
||||
.encoder(ClientboundSyncStatsPacket::toBytes)
|
||||
.decoder(ClientboundSyncStatsPacket::new)
|
||||
.consumerMainThread(ClientboundSyncStatsPacket::handle)
|
||||
.add();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package net.fretux.ascend.network;
|
||||
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraftforge.network.NetworkEvent;
|
||||
import net.fretux.ascend.player.PlayerStatsProvider;
|
||||
import net.minecraftforge.network.PacketDistributor;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class ServerboundSpendPointPacket {
|
||||
private final String attribute;
|
||||
|
||||
public ServerboundSpendPointPacket(String attribute) {
|
||||
this.attribute = attribute;
|
||||
}
|
||||
|
||||
public ServerboundSpendPointPacket(FriendlyByteBuf buf) {
|
||||
this.attribute = buf.readUtf();
|
||||
}
|
||||
|
||||
public void toBytes(FriendlyByteBuf buf) {
|
||||
buf.writeUtf(attribute);
|
||||
}
|
||||
|
||||
public void handle(Supplier<NetworkEvent.Context> ctx) {
|
||||
ctx.get().enqueueWork(() -> {
|
||||
ServerPlayer player = ctx.get().getSender();
|
||||
if (player == null) return;
|
||||
|
||||
System.out.println("[Ascend] Received spend packet '" + attribute + "' from " + player.getGameProfile().getName());
|
||||
|
||||
player.getCapability(PlayerStatsProvider.PLAYER_STATS).ifPresent(stats -> {
|
||||
boolean success = stats.spendPoints(attribute);
|
||||
if (success) {
|
||||
player.sendSystemMessage(Component.literal("You increased " + attribute + "!"));
|
||||
CompoundTag data = stats.serializeNBT();
|
||||
PacketHandler.INSTANCE.send(
|
||||
PacketDistributor.PLAYER.with(() -> player),
|
||||
new ClientboundSyncStatsPacket(data)
|
||||
);
|
||||
} else {
|
||||
int level = stats.getAttributeLevel(attribute);
|
||||
int cost = stats.getCostToUpgrade(attribute);
|
||||
int have = stats.getUnspentPoints();
|
||||
player.sendSystemMessage(Component.literal(
|
||||
"Not enough points (" + have + "/" + cost + ") to increase " + attribute + " (lvl " + level + ")."
|
||||
));
|
||||
}
|
||||
});
|
||||
});
|
||||
ctx.get().setPacketHandled(true);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
package net.fretux.ascend.player;
|
||||
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class PlayerStats {
|
||||
private final Map<String, Integer> attributes = new HashMap<>();
|
||||
private final Map<String, Integer> xp = new HashMap<>();
|
||||
private int unspentPoints = 15;
|
||||
private int ascendLevel = 1;
|
||||
private int ascendXP = 0;
|
||||
private static final int MAX_ASCEND_LEVEL = 20;
|
||||
|
||||
public PlayerStats() {
|
||||
for (String attr : new String[]{
|
||||
"strength", "agility", "fortitude", "intelligence",
|
||||
"willpower", "charisma",
|
||||
"light_scaling", "medium_scaling", "heavy_scaling", "magic_scaling"}) {
|
||||
attributes.put(attr, 0);
|
||||
xp.put(attr, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public void addUnspentPoints(int amount) {
|
||||
unspentPoints += amount;
|
||||
}
|
||||
|
||||
public int getUnspentPoints() {
|
||||
return unspentPoints;
|
||||
}
|
||||
|
||||
public boolean spendPoints(String attribute) {
|
||||
if (!attributes.containsKey(attribute)) return false;
|
||||
int cost = getCostToUpgrade(attribute);
|
||||
if (unspentPoints < cost) return false;
|
||||
unspentPoints -= cost;
|
||||
attributes.put(attribute, attributes.get(attribute) + 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
public int getCostToUpgrade(String attribute) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
public void addXP(String attribute, int amount) {
|
||||
if (!xp.containsKey(attribute) || amount <= 0) return;
|
||||
int currentXP = xp.get(attribute) + amount;
|
||||
int level = attributes.getOrDefault(attribute, 0);
|
||||
boolean changed = false;
|
||||
while (true) {
|
||||
int xpNeeded = getXPToNextAttributeLevel(attribute, level);
|
||||
if (xpNeeded <= 0 || currentXP < xpNeeded) break;
|
||||
currentXP -= xpNeeded;
|
||||
level++;
|
||||
changed = true;
|
||||
unspentPoints += 1;
|
||||
grantAscendXP(10);
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
attributes.put(attribute, level);
|
||||
}
|
||||
xp.put(attribute, currentXP);
|
||||
}
|
||||
|
||||
private int getXPToNextAttributeLevel(String attribute, int level) {
|
||||
return 20 + (level * 10);
|
||||
}
|
||||
|
||||
public int getXP(String attribute) {
|
||||
return xp.getOrDefault(attribute, 0);
|
||||
}
|
||||
|
||||
public int getXPToNextLevel(String attribute) {
|
||||
int level = attributes.getOrDefault(attribute, 0);
|
||||
return getXPToNextAttributeLevel(attribute, level);
|
||||
}
|
||||
|
||||
public int getAttributeLevel(String attribute) {
|
||||
return attributes.getOrDefault(attribute, 0);
|
||||
}
|
||||
|
||||
public void setAttributeLevel(String attribute, int level) {
|
||||
attributes.put(attribute, level);
|
||||
}
|
||||
|
||||
public int getAscendLevel() {
|
||||
return ascendLevel;
|
||||
}
|
||||
|
||||
public int getAscendXP() {
|
||||
return ascendXP;
|
||||
}
|
||||
|
||||
public int getXPToNextAscendLevel() {
|
||||
if (ascendLevel >= MAX_ASCEND_LEVEL) return Integer.MAX_VALUE;
|
||||
return 50 + (ascendLevel * 25);
|
||||
}
|
||||
|
||||
private void grantAscendXP(int amount) {
|
||||
if (amount <= 0 || ascendLevel >= MAX_ASCEND_LEVEL) return;
|
||||
ascendXP += amount;
|
||||
while (ascendLevel < MAX_ASCEND_LEVEL) {
|
||||
int needed = getXPToNextAscendLevel();
|
||||
if (ascendXP < needed) break;
|
||||
ascendXP -= needed;
|
||||
ascendLevel++;
|
||||
}
|
||||
if (ascendLevel >= MAX_ASCEND_LEVEL) {
|
||||
ascendXP = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public CompoundTag serializeNBT() {
|
||||
CompoundTag tag = new CompoundTag();
|
||||
tag.putInt("UnspentPoints", unspentPoints);
|
||||
tag.putInt("AscendLevel", ascendLevel);
|
||||
tag.putInt("AscendXP", ascendXP);
|
||||
CompoundTag attrTag = new CompoundTag();
|
||||
CompoundTag xpTag = new CompoundTag();
|
||||
for (String key : attributes.keySet()) {
|
||||
attrTag.putInt(key, attributes.get(key));
|
||||
xpTag.putInt(key, xp.get(key));
|
||||
}
|
||||
tag.put("Attributes", attrTag);
|
||||
tag.put("XP", xpTag);
|
||||
return tag;
|
||||
}
|
||||
|
||||
public void deserializeNBT(CompoundTag tag) {
|
||||
if (tag.contains("UnspentPoints")) {
|
||||
unspentPoints = tag.getInt("UnspentPoints");
|
||||
} else {
|
||||
unspentPoints = 15;
|
||||
}
|
||||
|
||||
if (tag.contains("AscendLevel")) {
|
||||
ascendLevel = tag.getInt("AscendLevel");
|
||||
} else {
|
||||
ascendLevel = 1;
|
||||
}
|
||||
|
||||
if (tag.contains("AscendXP")) {
|
||||
ascendXP = tag.getInt("AscendXP");
|
||||
} else {
|
||||
ascendXP = 0;
|
||||
}
|
||||
|
||||
if (tag.contains("Attributes")) {
|
||||
CompoundTag attrTag = tag.getCompound("Attributes");
|
||||
for (String key : attributes.keySet()) {
|
||||
attributes.put(key, attrTag.getInt(key));
|
||||
}
|
||||
}
|
||||
|
||||
if (tag.contains("XP")) {
|
||||
CompoundTag xpTag = tag.getCompound("XP");
|
||||
for (String key : xp.keySet()) {
|
||||
xp.put(key, xpTag.getInt(key));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package net.fretux.ascend.player;
|
||||
|
||||
import net.fretux.ascend.network.ClientboundSyncStatsPacket;
|
||||
import net.fretux.ascend.network.PacketHandler;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraftforge.common.capabilities.Capability;
|
||||
import net.minecraftforge.common.capabilities.CapabilityManager;
|
||||
import net.minecraftforge.common.capabilities.CapabilityToken;
|
||||
import net.minecraftforge.common.capabilities.ICapabilitySerializable;
|
||||
import net.minecraftforge.common.util.LazyOptional;
|
||||
import net.minecraftforge.network.PacketDistributor;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class PlayerStatsProvider implements ICapabilitySerializable<CompoundTag> {
|
||||
|
||||
public static final Capability<PlayerStats> PLAYER_STATS = CapabilityManager.get(new CapabilityToken<>(){});
|
||||
private final PlayerStats stats = new PlayerStats();
|
||||
private final LazyOptional<PlayerStats> optional = LazyOptional.of(() -> stats);
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public <T> LazyOptional<T> getCapability(@Nonnull Capability<T> cap, @Nullable Direction side) {
|
||||
return cap == PLAYER_STATS ? optional.cast() : LazyOptional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompoundTag serializeNBT() {
|
||||
return stats.serializeNBT();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deserializeNBT(CompoundTag nbt) {
|
||||
stats.deserializeNBT(nbt);
|
||||
}
|
||||
|
||||
public static void sync(Player player) {
|
||||
player.getCapability(PlayerStatsProvider.PLAYER_STATS).ifPresent(stats -> {
|
||||
CompoundTag data = stats.serializeNBT();
|
||||
PacketHandler.INSTANCE.send(PacketDistributor.PLAYER.with(() -> (ServerPlayer) player),
|
||||
new ClientboundSyncStatsPacket(data));
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user