Posted March 3, 20223 yr Hi there! It is my first question in this forum, so excuse me if I ask very basic questions. And I don't have a very good English, so forgive me for that too. I am developing a mod (my first mod, have not experience in mc modding but have it in java programming). My mod is a Pyromaniac mod. I'll explain: the user executes a command (/pyro start) and clicks on some blocks. When finished executes '/pyro stop', and finally executes '/pyro fire' to start all fires at the same time. Ok. No problem with the commands, they work fine: Quote public class PyroCommand { private static final Logger LOGGER = LogManager.getLogger(); private PyromaniacMod pyro; public void register(CommandDispatcher<CommandSourceStack> dispatcher) { LiteralArgumentBuilder<CommandSourceStack> PyroCommand = Commands.literal("pyro"); PyroCommand.then(Commands.literal("start").executes((caca)->{return start(); })); PyroCommand.then(Commands.literal("stop").executes((caca)->{return stop(); })); PyroCommand.then(Commands.literal("fire").executes((caca)->{return fire(); })); dispatcher.register(PyroCommand); } private int start() { this.pyro.start(); return 1; } private int stop() { this.pyro.stop(); return 1; } private int fire() { this.pyro.fire(); return 1; } public PyromaniacMod getPyro() { return pyro; } public void setPyro(PyromaniacMod pyro) { this.pyro = pyro; } The problem comes when setting the fire blocks. In this test version, all fires start in the .above() block of the targeted position. I can see how fires start, but they don't interact with their fireable neightbours, and nothing of I have done is being saved. I know (or I think I know xD) that the problem is because I am not updating both sides correctly. Could you help me in doing that? Here is my extremely naïve fire function in the PyromaniacMod class: Quote public void fire() { player = Minecraft.getInstance().player; Level level = Minecraft.getInstance().level; if(!this.readyToFire) { player.sendMessage(new TextComponent("[Pyro] Run '/pyro fire' again to start the magic."), null); this.readyToFire = true; return; }else { player.sendMessage(new TextComponent("[Pyro] Firing. Muuaaaahahahahahaha :D"), null); for(BlockPos pos:blocs) { try { //level.setBlock(pos.above(), Blocks.FIRE.defaultBlockState(), 3); level.setBlockAndUpdate(pos.above(), Blocks.FIRE.defaultBlockState()); }catch(Exception e) { e.printStackTrace(); } } } } Tell me if you need any more code part. And be nice, please ;). Thaaaaaanks!
March 3, 20223 yr Author 2 hours ago, diesieben07 said: You are reaching across logical sides. Commands run server side (at least by default, and that is correct here), but this is accessing stuff that is client side. You must use the player from the command context and its level. Ok. Understood. Now I am saving the player who rightclicks the blocks to fire and finally it works :D. I'm sure there are other things that I'm doing the worst way, but it is easier to learn from a working code xD. 2 hours ago, diesieben07 said: This is not how you handle exceptions, ever. I used this as a java standard way of seeing what is crashing. Can you link me some documentation about mc forge exceptions handling? Thanks!
March 3, 20223 yr 4 hours ago, baku said: Ok. Understood. Now I am saving the player who rightclicks the blocks to fire and finally it works :D. I'm sure there are other things that I'm doing the worst way, but it is easier to learn from a working code xD. I used this as a java standard way of seeing what is crashing. Can you link me some documentation about mc forge exceptions handling? Thanks! the idea is to do checks instead of using a trycatch. an if statement testing if pos is an air block with a sub check to avoid waterlog / fluid (lava/lava) sources around the targeted block to place the fire would be the way to avoid any problems. Minecraft can handle derpy fire (like spawning in mid air & just vanishing after the fact), but being over/near fluid sources can lead to stupidity with how things interact.
March 6, 20223 yr Author Finally I have a first complete version of the mod. Here are all my classes for if someone considers them useful. I also add some questions below about how to improve it. Here it goes Quote // MAIN MOD CLASS @Mod("pyromaniac") public class PyromaniacMod { public PyromaniacMod() { MinecraftForge.EVENT_BUS.register(this); // FMLJavaModLoadingContext.get().getModEventBus().addListener(this::onRegisterCapabilities); FMLJavaModLoadingContext.get().getModEventBus().addListener(PyroCapability::register); MinecraftForge.EVENT_BUS.register(PyroCapabilityAttacher.class); } @SubscribeEvent public void onCommandsRegister(RegisterCommandsEvent event) { CommandDispatcher<CommandSourceStack> commandDispatcher = event.getDispatcher(); PyroCommand pyroCommand = new PyroCommand(); pyroCommand.register(commandDispatcher); } @SubscribeEvent public void onPlayerRightClickBlock(PlayerInteractEvent.RightClickBlock event){ if(PyroCapability.PYRO.isRegistered()) { PyroCapabilityInterface pyro = event.getPlayer().getCapability(PyroCapability.PYRO).orElse(null); if(pyro.isRunning()) { LOGGER.info("Click!"); event.setCanceled(true); BlockPos pos = event.getPos(); Direction dir = event.getFace(); Player p = event.getPlayer(); Level l = event.getWorld(); Objectiu obj = new Objectiu(l, p, pos, dir); if(!pyro.hasObjectiu(obj)) { pyro.addObjectiu(obj); LOGGER.info(pos.toShortString()); LOGGER.info(dir.toString()); } } } } } Quote //CUSTOM COMMAND CLASS public class PyroCommand { public static void register(CommandDispatcher<CommandSourceStack> dispatcher) { dispatcher.register(Commands.literal("pyro") .then(Commands.literal("start") .executes(PyroCommand::start)) .then(Commands.literal("stop") .executes(PyroCommand::stop)) .then(Commands.literal("fire") .executes(PyroCommand::fire)) ); } private static int start(CommandContext<CommandSourceStack> context) throws CommandSyntaxException { LOGGER.info(PREFIX+"START"); Player sender = context.getSource().getPlayerOrException(); if(PyroCapability.PYRO.isRegistered()) { PyroCapabilityInterface pyro = sender.getCapability(PyroCapability.PYRO).orElse(null); if(!pyro.isRunning()) { pyro.setRunning(true); pyro.setReadyToFire(false); pyro.clearBlocs(); sender.sendMessage(new TextComponent(PREFIX + "Pyromaniac selection started. Right click blocks to fire."), sender.getUUID()); }else { sender.sendMessage(new TextComponent(PREFIX + "Pyromaniac selection already started."), sender.getUUID()); } }else { sender.sendMessage(new TextComponent(PREFIX + "[ERROR] You don't have the pyromaniac capability :("), sender.getUUID()); } return 1; } private static int stop(CommandContext<CommandSourceStack> context) throws CommandSyntaxException { LOGGER.info(PREFIX+"STOP"); Player sender = context.getSource().getPlayerOrException(); if(PyroCapability.PYRO.isRegistered()) { PyroCapabilityInterface pyro = sender.getCapability(PyroCapability.PYRO).orElse(null); if(pyro.isRunning()) { pyro.setRunning(false); sender.sendMessage(new TextComponent(PREFIX + "Pyromaniac selection stopped. Please run twice '/pyro fire' to start fires."), sender.getUUID()); }else { sender.sendMessage(new TextComponent(PREFIX + "Pyromaniac selection not started. Please run /pyro start."), sender.getUUID()); } } return 1; } private static int fire(CommandContext<CommandSourceStack> context) throws CommandSyntaxException { LOGGER.info(PREFIX+"FIRE"); Player sender = context.getSource().getPlayerOrException(); if(PyroCapability.PYRO.isRegistered()) { PyroCapabilityInterface pyro = sender.getCapability(PyroCapability.PYRO).orElse(null); if(!pyro.isReadyToFire()) { sender.sendMessage(new TextComponent(PREFIX + "Run '/pyro fire' again to start the magic."), sender.getUUID()); pyro.setReadyToFire(true); }else { sender.sendMessage(new TextComponent(PREFIX + "Firing. Muuaaaahahahahahaha :D"), sender.getUUID()); for(Objectiu obj : pyro.getObjectius()) { Block bloc = Blocks.FIRE; Level l = obj.getLevel(); Player p = obj.getPlayer(); BlockPos pos = obj.getPos(); Direction dir = obj.getDir(); BlockPos pos2 = pos.relative(dir); if (BaseFireBlock.canBePlacedAt(l, pos2, dir)) { l.setBlock(pos2, bloc.defaultBlockState(), 3); bloc.setPlacedBy(l, pos2, bloc.defaultBlockState(), p, ItemStack.EMPTY); } } } } return 1; } } Quote //CUSTOM CAPABILITY INTERFACE public interface PyroCapabilityInterface extends INBTSerializable<CompoundTag> { boolean running = false; boolean readyToFire = false; static final ArrayList<Objectiu> blocs = new ArrayList<Objectiu>(); boolean isRunning(); void setRunning(boolean running); boolean isReadyToFire(); void setReadyToFire(boolean readyToFire); boolean hasObjectiu(Objectiu obj); void addObjectiu(Objectiu obj); ArrayList<Objectiu> getObjectius(); void clearBlocs(); } Quote //CUSTOM CAPABILITY IMPLEMENTATION public class PyroCapabilityImplementation implements PyroCapabilityInterface { private boolean running = false; private boolean readyToFire = false; private ArrayList<Objectiu> blocs; @Override public CompoundTag serializeNBT() { LOGGER.info(PREFIX+"SERIALIZE NBT"); final CompoundTag tag = new CompoundTag(); tag.putBoolean("RUNNING", this.running); tag.putBoolean("READYTOFIRE", this.readyToFire); // TODO // Serialize class Objectiu return tag; } @Override public void deserializeNBT(CompoundTag nbt) { LOGGER.info(PREFIX+"DESERIALIZE NBT"); this.running = nbt.getBoolean("RUNNING"); this.readyToFire = nbt.getBoolean("READYTOFIRE"); // TODO // Deserialize class Objectiu } @Override public boolean isRunning() { return running; } @Override public void setRunning(boolean running) { this.running = running; } @Override public boolean isReadyToFire() { return readyToFire; } @Override public void setReadyToFire(boolean readyToFire) { this.readyToFire = readyToFire; } @Override public boolean hasObjectiu(Objectiu obj) { return this.blocs.contains(obj); } @Override public void addObjectiu(Objectiu obj) { this.blocs.add(obj); } @Override public void clearBlocs() { this.blocs = new ArrayList<Objectiu>(); } @Override public ArrayList<Objectiu> getObjectius() { return this.blocs; } } Quote //CUSTOM CAPABILITY ATTACHER public class PyroCapabilityAttacher { private static class PyroCapabilityProvider implements ICapabilityProvider, INBTSerializable<CompoundTag> { public static final ResourceLocation IDENTIFIER = new ResourceLocation("pyromaniac", "pyro"); private final PyroCapabilityInterface backend = new PyroCapabilityImplementation(); private final LazyOptional<PyroCapabilityInterface> optionalData = LazyOptional.of(() -> backend); void invalidate() { this.optionalData.invalidate(); } @Nonnull @Override public <T> LazyOptional<T> getCapability(@Nonnull Capability<T> cap, @Nullable Direction side) { return PyroCapability.PYRO.orEmpty(cap, this.optionalData); } @Override public CompoundTag serializeNBT() { return this.backend.serializeNBT(); } @Override public void deserializeNBT(CompoundTag nbt) { this.backend.deserializeNBT(nbt); } } @SubscribeEvent public static void attach(final AttachCapabilitiesEvent<Entity> event) { if(event.getObject() instanceof Player) { final PyroCapabilityProvider provider = new PyroCapabilityProvider(); event.addCapability(PyroCapabilityProvider.IDENTIFIER, provider); event.addListener(provider::invalidate); } } private PyroCapabilityAttacher() { } } Quote //CUSTOM CAPABILITY public class PyroCapability{ public static final Capability<PyroCapabilityInterface> PYRO = CapabilityManager.get(new CapabilityToken<>() {}); public static void register(RegisterCapabilitiesEvent event) { event.register(PyroCapabilityInterface.class); } private PyroCapability() { } } Quote //OBJECTIU (target, in English) public class Objectiu { private Level level; private Player player; private BlockPos pos; private Direction dir; public Objectiu(Level level, Player player, BlockPos pos, Direction dir) { this.level = level; this.player = player; this.pos = pos; this.dir = dir; } public Level getLevel() { return level; } public void setLevel(Level level) { this.level = level; } public Player getPlayer() { return player; } public void setPlayer(Player player) { this.player = player; } public BlockPos getPos() { return pos; } public void setPos(BlockPos pos) { this.pos = pos; } public Direction getDir() { return dir; } public void setDir(Direction dir) { this.dir = dir; } // TODO // Create equals function // @Override // public boolean equals(Object o) { // } } Let's go with some questions: As I never created a capability, I used this https://gist.github.com/N1K-x/a17812bac9de4cf064baa789e9ccb96a as a basic example. Did I implement it well or is a best way of doing it? I am checking if the target seleccion is running from the PlayerRightClickEvent, and if so, I'm cancelling the event. Is there a way to tell Minecraft to always disable it for a certain player and not having to do it from inside itself? Easier explain of the question: can I tell MC that a player can't place blocks while the boolean "running" of the mod is true? I am coding the command execution from the command class. Shall I put the start/stop/fire funcions in the Capability implementation or it is ok to do it here? That's all. I hope my code would be useful as an example of implementing commands or capabilities. And thanks to whoever answers my questions :D. Edited March 6, 20223 yr by baku my bad English xD
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.