diff --git a/src/main/java/net/krituximon/stalinium/Stalinium.java b/src/main/java/net/krituximon/stalinium/Stalinium.java index db25ec7..69ddf03 100644 --- a/src/main/java/net/krituximon/stalinium/Stalinium.java +++ b/src/main/java/net/krituximon/stalinium/Stalinium.java @@ -1,8 +1,11 @@ package net.krituximon.stalinium; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; import net.krituximon.stalinium.block.ModBlocks; import net.krituximon.stalinium.block.entity.ModBlockEntities; import net.krituximon.stalinium.effect.ModEffects; +import net.krituximon.stalinium.event.ComradeHandler; import net.krituximon.stalinium.event.ModEvents; import net.krituximon.stalinium.item.ModCreativeModeTabs; import net.krituximon.stalinium.item.ModItems; @@ -15,8 +18,11 @@ import net.krituximon.stalinium.screen.custom.StaliniumPressScreen; import net.krituximon.stalinium.sound.ModSounds; import net.krituximon.stalinium.util.PlayerLogEvents; import net.krituximon.stalinium.worldgen.ModFeatures; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.network.chat.Component; import net.neoforged.neoforge.client.event.RegisterMenuScreensEvent; import net.neoforged.neoforge.client.event.RegisterParticleProvidersEvent; +import net.neoforged.neoforge.event.RegisterCommandsEvent; import org.slf4j.Logger; import com.mojang.logging.LogUtils; @@ -37,6 +43,8 @@ import net.neoforged.neoforge.common.NeoForge; import net.neoforged.neoforge.event.BuildCreativeModeTabContentsEvent; import net.neoforged.neoforge.event.server.ServerStartingEvent; +import java.util.function.Supplier; + // The value here should match an entry in the META-INF/neoforge.mods.toml file @Mod(Stalinium.MODID) public class Stalinium @@ -77,6 +85,7 @@ public class Stalinium ModMenuTypes.register(modEventBus); ModEffects.register(modEventBus); ModPotions.register(modEventBus); + NeoForge.EVENT_BUS.register(ComradeHandler.class); } private void commonSetup(final FMLCommonSetupEvent event) diff --git a/src/main/java/net/krituximon/stalinium/effect/StaliniumChargeEffect.java b/src/main/java/net/krituximon/stalinium/effect/StaliniumChargeEffect.java index 7e08f24..d344b9a 100644 --- a/src/main/java/net/krituximon/stalinium/effect/StaliniumChargeEffect.java +++ b/src/main/java/net/krituximon/stalinium/effect/StaliniumChargeEffect.java @@ -13,26 +13,26 @@ public class StaliniumChargeEffect extends MobEffect { this.addAttributeModifier( Attributes.MOVEMENT_SPEED, ResourceLocation.fromNamespaceAndPath(Stalinium.MODID, "stalinium_charge_speed"), - 0.15, + 0.1, AttributeModifier.Operation.ADD_MULTIPLIED_BASE ); this.addAttributeModifier( Attributes.KNOCKBACK_RESISTANCE, ResourceLocation.fromNamespaceAndPath(Stalinium.MODID, "stalinium_charge_knockback"), - 0.35, + 0.33, AttributeModifier.Operation.ADD_VALUE ); this.addAttributeModifier( Attributes.ATTACK_SPEED, ResourceLocation.fromNamespaceAndPath(Stalinium.MODID, "stalinium_charge_toughness"), - 0.1, + 0.075, AttributeModifier.Operation.ADD_MULTIPLIED_BASE ); this.addAttributeModifier( Attributes.ATTACK_DAMAGE, ResourceLocation.fromNamespaceAndPath(Stalinium.MODID, "stalinium_charge_damage"), - 0.1, - AttributeModifier.Operation.ADD_MULTIPLIED_BASE + 1, + AttributeModifier.Operation.ADD_VALUE ); } } \ No newline at end of file diff --git a/src/main/java/net/krituximon/stalinium/event/ComradeHandler.java b/src/main/java/net/krituximon/stalinium/event/ComradeHandler.java new file mode 100644 index 0000000..24ec89b --- /dev/null +++ b/src/main/java/net/krituximon/stalinium/event/ComradeHandler.java @@ -0,0 +1,312 @@ +package net.krituximon.stalinium.event; + +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import net.krituximon.stalinium.Stalinium; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.commands.arguments.EntityArgument; +import net.minecraft.network.chat.Component; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.player.Player; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.EventBusSubscriber; +import net.neoforged.neoforge.event.RegisterCommandsEvent; +import net.neoforged.neoforge.event.entity.player.AttackEntityEvent; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Party/”comrade” handler. + * + * Commands: + * /comrade party create + * /comrade invite + * /comrade accept + * /comrade remove + * /comrade list + * + * Only the party “leader” can remove others. Invites must be accepted by the target. + * If you invite someone but you aren’t yet in a party, you automatically become leader of a new party. + */ +@EventBusSubscriber( + modid = Stalinium.MODID, + bus = EventBusSubscriber.Bus.GAME +) +public class ComradeHandler { + /** Maps each party leader → Party object. Only the leader’s UUID is the key. */ + private static final Map PARTIES = new ConcurrentHashMap<>(); + + /** If player X has been invited by leader Y, pendingInvites maps X → Y. */ + private static final Map PENDING_INVITES = new ConcurrentHashMap<>(); + + /** A small helper class to store party data. */ + private static class Party { + final UUID leader; + final Set members = ConcurrentHashMap.newKeySet(); + + Party(UUID leader) { + this.leader = leader; + this.members.add(leader); + } + + boolean isMember(UUID uuid) { + return members.contains(uuid); + } + + void addMember(UUID uuid) { + members.add(uuid); + } + + void removeMember(UUID uuid) { + members.remove(uuid); + } + + boolean isLeader(UUID uuid) { + return leader.equals(uuid); + } + } + + private static Optional findPartyOf(UUID uuid) { + return PARTIES.values().stream() + .filter(p -> p.isMember(uuid)) + .findAny(); + } + + private static Optional findPartyByLeader(UUID leaderUuid) { + return Optional.ofNullable(PARTIES.get(leaderUuid)); + } + + @SubscribeEvent + public static void onRegisterCommands(RegisterCommandsEvent event) { + event.getDispatcher().register( + LiteralArgumentBuilder.literal("comrade") + .then(Commands.literal("party") + .then(Commands.literal("create") + .executes(ctx -> { + ServerPlayer sender = ctx.getSource().getPlayerOrException(); + UUID senderId = sender.getUUID(); + + // If sender is already in a party, do nothing. + if (findPartyOf(senderId).isPresent()) { + ctx.getSource().sendFailure( + Component.literal("§eYou are already in a party.") + ); + return 0; + } + Party newParty = new Party(senderId); + PARTIES.put(senderId, newParty); + ctx.getSource().sendSuccess( + () -> Component.literal("§aParty created. You are now the leader."), + false + ); + return 1; + }) + ) + ) + .then(Commands.literal("invite") + .then(Commands.argument("player", EntityArgument.player()) + .executes(ctx -> { + ServerPlayer sender = ctx.getSource().getPlayerOrException(); + ServerPlayer target = EntityArgument.getPlayer(ctx, "player"); + UUID senderId = sender.getUUID(); + UUID targetId = target.getUUID(); + if (senderId.equals(targetId)) { + ctx.getSource().sendFailure( + Component.literal("§eYou cannot invite yourself.") + ); + return 0; + } + Optional senderPartyOpt = findPartyOf(senderId); + if (senderPartyOpt.isPresent()) { + Party party = senderPartyOpt.get(); + if (party.isMember(targetId)) { + ctx.getSource().sendFailure( + Component.literal("§eThat player is already in your party.") + ); + return 0; + } + } else { + Party auto = new Party(senderId); + PARTIES.put(senderId, auto); + senderPartyOpt = Optional.of(auto); + ctx.getSource().sendSuccess( + () -> Component.literal("§aNo party found, so one was automatically created. You are now leader."), + false + ); + } + Party party = senderPartyOpt.get(); + if (!party.isLeader(senderId)) { + ctx.getSource().sendFailure( + Component.literal("§eOnly the party leader can invite new comrades.") + ); + return 0; + } + PENDING_INVITES.put(targetId, senderId); + target.sendSystemMessage( + Component.literal("§6" + sender.getName().getString() + + " §awants you to join their party. Type §e/comrade accept " + + sender.getName().getString() + " §ato accept.") + ); + ctx.getSource().sendSuccess( + () -> Component.literal("§aInvitation sent to §6" + target.getName().getString() + "§a."), + false + ); + return 1; + }) + ) + ) + // /comrade accept + .then(Commands.literal("accept") + .then(Commands.argument("leader", EntityArgument.player()) + .executes(ctx -> { + ServerPlayer accepter = ctx.getSource().getPlayerOrException(); + ServerPlayer leader = EntityArgument.getPlayer(ctx, "leader"); + UUID accepterId = accepter.getUUID(); + UUID leaderId = leader.getUUID(); + UUID expected = PENDING_INVITES.get(accepterId); + if (expected == null || !expected.equals(leaderId)) { + ctx.getSource().sendFailure( + Component.literal("§eNo invitation found from that leader.") + ); + return 0; + } + + // Find (or create) the leader’s party: + Party party = findPartyByLeader(leaderId) + .orElseGet(() -> { + Party brandNew = new Party(leaderId); + PARTIES.put(leaderId, brandNew); + return brandNew; + }); + + // Add this player to that party: + party.addMember(accepterId); + PENDING_INVITES.remove(accepterId); + + accepter.sendSystemMessage( + Component.literal("§aYou have joined §6" + leader.getName().getString() + "§a’s party.") + ); + leader.sendSystemMessage( + Component.literal("§a" + accepter.getName().getString() + " §ais now your comrade.") + ); + return 1; + }) + ) + ) + // /comrade remove + .then(Commands.literal("remove") + .then(Commands.argument("player", EntityArgument.player()) + .executes(ctx -> { + ServerPlayer sender = ctx.getSource().getPlayerOrException(); + ServerPlayer toRemove = EntityArgument.getPlayer(ctx, "player"); + UUID senderId = sender.getUUID(); + UUID removeId = toRemove.getUUID(); + Optional maybeParty = findPartyOf(senderId); + if (maybeParty.isEmpty()) { + ctx.getSource().sendFailure( + Component.literal("§eYou are not comrades with anyone.") + ); + return 0; + } + Party party = maybeParty.get(); + if (!party.isLeader(senderId)) { + ctx.getSource().sendFailure( + Component.literal("§eOnly the party leader may remove members.") + ); + return 0; + } + if (removeId.equals(senderId)) { + ctx.getSource().sendFailure( + Component.literal("§eYou cannot remove yourself from your own party.") + ); + return 0; + } + if (!party.isMember(removeId)) { + ctx.getSource().sendFailure( + Component.literal("§eThat player is not one of your comrades.") + ); + return 0; + } + + party.removeMember(removeId); + toRemove.sendSystemMessage( + Component.literal("§cYou are no longer " + + sender.getName().getString() + "'s comrade.") + ); + ctx.getSource().sendSuccess( + () -> Component.literal("§a“" + toRemove.getName().getString() + + "” is no longer your comrade."), + false + ); + if (party.members.size() == 1 && party.isLeader(senderId)) { + PARTIES.remove(senderId); + sender.sendSystemMessage( + Component.literal("§6Your party was disbanded (no more members).") + ); + } + return 1; + }) + ) + ) + .then(Commands.literal("list") + .executes(ctx -> { + ServerPlayer sender = ctx.getSource().getPlayerOrException(); + UUID senderId = sender.getUUID(); + Optional maybeParty = findPartyOf(senderId); + if (maybeParty.isEmpty()) { + ctx.getSource().sendSuccess( + () -> Component.literal("§eYou are not in a party."), + false + ); + return 0; + } + Party party = maybeParty.get(); + MinecraftServer server = ctx.getSource().getServer(); + List displayNames = new ArrayList<>(); + for (UUID memberUuid : party.members) { + ServerPlayer online = server.getPlayerList().getPlayer(memberUuid); + if (online != null) { + displayNames.add(online.getName().getString()); + } else { + displayNames.add(memberUuid.toString()); + } + } + String joined = String.join(", ", displayNames); + ctx.getSource().sendSuccess( + () -> Component.literal("§6Party members: §f" + joined), + false + ); + return 1; + }) + ) + ); + } + + @SubscribeEvent + public static void onPlayerAttackEntity(AttackEntityEvent event) { + LivingEntity source = event.getEntity(); + if (source.level().isClientSide) return; + if (!(source instanceof Player)) return; + + if (event.getTarget() instanceof ServerPlayer targetPlayer) { + UUID sourceId = source.getUUID(); + UUID targetId = targetPlayer.getUUID(); + + // If both are in the same party, cancel damage: + Optional maybeParty = findPartyOf(sourceId); + if (maybeParty.isPresent()) { + Party party = maybeParty.get(); + if (party.isMember(targetId)) { + event.setCanceled(true); + source.sendSystemMessage( + Component.literal("§cYou cannot hurt your comrade “" + + targetPlayer.getName().getString() + "”!") + ); + } + } + } + } +} \ No newline at end of file