Jump to content

[1.7.10] A question about packets


Thornack

Recommended Posts

Hi everyone,

 

I want to update my players IEEP from a gui when the player does something. Specifically, I want to update my players party when the player swaps the position of the party members in his party. Example if i have a party member in slot 1 and a party memeber in slot 2 I want to swap them so that they are reversed. For simplicity I currently use the onItemRightClick method of a generic item to call my swap party members method. If i call my swap method server side then using the following packet everything updates beautifully and is persistent. Now I am not sure what to do when updating the other direction client to server.

 

the swap method in my IEEP class

public void swapPartyMembers(int slotOne, int slotTwo){
	NBTTagCompound tempSlot = partyNbt[slotOne];
	partyNbt[slotOne] = partyNbt[slotTwo];
	partyNbt[slotTwo] = tempSlot;
	PacketOverlord.sendTo(new PacketSyncPlayerParty((EntityPlayer) entityPlayer),(EntityPlayerMP) entityPlayer);
	}

An example of my right click method (pseudo code)

public ItemStack onItemRightClick(ItemStack itemStack, World world, EntityPlayer player) {
//if I use server player and I update my GUI with my packet everything works beautifully
if (!world.isRemote){
PlayerParty pp = PlayerParty.get(player);
pp.swapPartyMembers(firstSelectedSlot, secondSelectedSlot);	
}
//However I am unsure how to update from client to server (i get a crash) if I call the swapPartyMembers method as it is up top and the consol points to a problem with my packet
if (world.isRemote){
PlayerParty pp = PlayerParty.get(Minecraft.GetMinecraft.thePlayer);
pp.swapPartyMembers(firstSelectedSlot, secondSelectedSlot);	
}
}

My SyncPlayerParty PacketClass

public class PacketSyncPlayerParty extends AbstractClientMessage<PacketSyncPlayerParty>
{

	private NBTTagCompound data;

	public PacketSyncPlayerParty() {}

	public PacketSyncPlayerParty(EntityPlayer player) {
		System.out.println("Updating Player Party Sending a Packet");
		data = new NBTTagCompound();
		PlayerParty.get(player).saveNBTData(data);
	}

	@Override
	protected void read(PacketBuffer buffer) throws IOException {
		data = buffer.readNBTTagCompoundFromBuffer();
	}

	@Override
	protected void write(PacketBuffer buffer) throws IOException {
		buffer.writeNBTTagCompoundToBuffer(data);
	}

	@Override
	public void process(EntityPlayer player, Side side) {
	PlayerParty.get(player).loadNBTData(data);
	}
}

 

For updating client player I use the following method in my PacketOverlord class I am not sure how to do the reverse of this

/**
 * Sends message to the specified player's client-side counterpart. See
 * {@link SimpleNetworkWrapper#sendTo(IMessage, EntityPlayerMP)}
 */
public static final void sendTo(IMessage message, EntityPlayerMP player) {
	PacketOverlord.dispatcher.sendTo(message, player);
}

 

 

 

Link to comment
Share on other sites

Generally, you NEVER update the server from the client.

 

What the client does instead is say, 'Hey, server, I want to do X' (i.e. send a packet to the server requesting some action) and the server then decides a) if that is allowed, and b) what the result is; once that is all done, if the client needs to know about the result then the server sends the result of the action back to the client (i.e. another packet).

 

So if, in your GUI, you have a button that swaps party members, instead of having the client swap the party members, you tell the server that the client player pressed the 'swap party' button, then the server decides what happens from there.

 

If whatever you are doing isn't very sensitive and you'd like to preview in the GUI (you can't wait more than a few milliseconds to see the results - this can be true when dealing with rendering), you can always swap the members on the client, too, in expectation of a successful transaction to the server, as long as the client data is overwritten by the results from the server.

Link to comment
Share on other sites

Ah ok I assume that is for security purposes, Another question, how would I send the request you mentioned? And I am not sure how to do the deciding bit on the server as I would have to do the deciding bit inside my IEEP class and am not sure how to access the server side player there.

Link to comment
Share on other sites

The player whose client sends the packet can be obtained from the message context passed with the message.

 

In terms of server "deciding" you just have to pass enough information or instruction in the packet payload. Like I have a GUI that gives an item when closed, so when the Done button is clicked in the GUI I send a packet with a special ID number (just the first int I put into the payload) and then when the server receives the packet it acts on that ID. So the client is telling the server what to do.

Check out my tutorials here: http://jabelarminecraft.blogspot.com/

Link to comment
Share on other sites

Ah ok I assume that is for security purposes, Another question, how would I send the request you mentioned? And I am not sure how to do the deciding bit on the server as I would have to do the deciding bit inside my IEEP class and am not sure how to access the server side player there.

... what? The 'request' is simply a packet, and whenever you process a packet you always have access to a player instance.

 

I understand it can seem daunting, but packets are really really simple: create the packet on one side (by create I mean you store some data in the packet class), send it over the network, and then read the same data back and do whatever you need to do with it. There is nothing magical going on.

Link to comment
Share on other sites

Once you figure out custom packets, like CoolAlias says it is actually pretty easy. Getting a solid packet handling system is a major step to becoming a serious modder, as there are many occasions where you will need it. So invest the time and figure it out.

 

diesieben07 has the basic packet setup using the simple network wrapper tutorial here: http://www.minecraftforge.net/forum/index.php/topic,20135.0.html

 

CoolAlias has a more comprehensive (but bit more complicated) system that overcomes a few of the limitations of the simple network wrapper here: http://www.minecraftforum.net/forums/mapping-and-modding/mapping-and-modding-tutorials/2137055-1-7-2-customizing-packet-handling-with

 

 

Check out my tutorials here: http://jabelarminecraft.blogspot.com/

Link to comment
Share on other sites

Ok so I have the following

 

public class PacketSyncSwapPartyMembers extends AbstractClientMessage<PacketSyncSwapPartyMembers> {
int slot1;
int slot2;
boolean wasPressed;
public PacketSyncSwapPartyMembers() {}
public PacketSyncSwapPartyMembers(int slot1, int slot2, boolean wasPressed) {
	this.slot1 = slot1;
	this.slot2 = slot2;
	this.wasPressed =wasPressed;
}

@Override
protected void read(PacketBuffer buffer) throws IOException {
slot1 = buffer.readInt();
slot2 = buffer.readInt();
wasPressed = buffer.readBoolean();
}

@Override
protected void write(PacketBuffer buffer) throws IOException {
	buffer.writeInt(slot1);
	buffer.writeInt(slot2);
	buffer.writeBoolean(wasPressed);
}

@Override
public void process(EntityPlayer player, Side side) {
if(wasPressed == true && !player.worldObj.isRemote){
	 if(slot1 != -1 && slot2 !=-1){
		 PlayerParty ppp = PlayerParty.get(player);
		 ppp.swapPartyMembers(slot1, slot2, wasPressed);
		 PacketOverlord.sendTo(new PacketSyncPlayerParty((EntityPlayer) entityPlayer),(EntityPlayerMP) entityPlayer);
	 }
}
}}

 

and

inside my GUI I have this code but the server doesnt seem to update the client, my process method doesnt get called not sure why (I have very little experience with packets)

else if(button.id == BUTTON_SWITCH_ID){
    			
    			if(switchBtnPressed == true && firstSelectedSlot != -1 && secondSelectedSlot !=-1){
    			this.switchSuccessful = true;
    			ppp.swapPartyMembers(firstSelectedSlot, secondSelectedSlot,this.switchSuccessful);	
    			
                                PacketOverlord.sendToServer(new PacketSyncSwapPartyMembers(firstSelectedSlot, secondSelectedSlot, switchSuccessful));
    			this.mc.displayGuiScreen((GuiScreen)null);
    			this.mc.displayGuiScreen(battleMenuGuiPP);
    			firstSelectedSlot = -1;	
    			secondSelectedSlot = -1;
    			switchBtnPressed = false;
    			this.switchSuccessful = false;
    			
    			} else if(switchBtnPressed == true && firstSelectedSlot == -1 && secondSelectedSlot ==-1){
	    		         }else if(switchBtnPressed == true && firstSelectedSlot != -1 && secondSelectedSlot ==-1){
    				    	}else{
    				switchBtnPressed = true;
    			}
    			pwButton.wasBtnPressed = switchBtnPressed;
    			
    			}

Link to comment
Share on other sites

I did a System.out.println( "was pressed? " + wasPressed + "slot? " + slot1 + "slot? " + slot2); inside my constructor and all data appears to equal what it should but the process method isnt called at all

 

in my packet overlord class I use this to send the packet containing my request that says this button was pressed and these were the results take those results. Then I try to validate on server and nothing

/**
 * Send this message to the server. See
 * {@link SimpleNetworkWrapper#sendToServer(IMessage)}
 */
public static final void sendToServer(IMessage message) {
	PacketOverlord.dispatcher.sendToServer(message);
}

Link to comment
Share on other sites

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.

Guest
Unfortunately, your content contains terms that we do not allow. Please edit your content to remove the highlighted words below.
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.



  • Recently Browsing

    • No registered users viewing this page.
  • Posts

    • My modded minecraft world don't go past 100% on the loading screen. My backup still work, but i've made a lot of progress from the point i was that save. i have already checked online and did some suggestions i found, but nothing worked. Here is my latest log + i don´t have crash reports: https://github.com/GmsantosPT/Minecraft-mod-problems/tree/main   Pls Help, Santos
    • Please share a link to your crash report, as explained in the FAQ
    • I have a block already registered in my mod, which I have used in some worlds. The problem arises when in code, I add a property called CURRENT_AGE, when running Minecraft it freezes. In the console it doesn't appear any excpetion except that it stays in this phase: [Render thread/DEBUG] [ne.mi.co.ca.CapabilityManager/CAPABILITIES]: Attempting to automatically register: Lnet/minecraftforge/items/IItemHandler; Does anyone have an idea what it could be? I show the block and its registration public class SoulLichenBlock extends MultifaceBlock implements SimpleWaterloggedBlock, EntityBlock { public static final BooleanProperty WATERLOGGED = BlockStateProperties.WATERLOGGED; public static final IntegerProperty SKILL_LEVEL = IntegerProperty.create("soullichen_level", 0, 30); public static final DirectionProperty FACE = DirectionProperty.create("soullichen_face"); public static final DirectionProperty DIRECTION = DirectionProperty.create("soullichen_direction"); public static final IntegerProperty CURRENT_AGE = BlockStateProperties.AGE_25; private final MultifaceSpreader spreader = new MultifaceSpreader(this); private final MultifaceSpreader.DefaultSpreaderConfig config = new MultifaceSpreader.DefaultSpreaderConfig(this); private LivingEntity owner; //private static final Integer MAX_AGE = 25; public SoulLichenBlock(Properties properties) { super(properties); this.registerDefaultState(this .defaultBlockState() .setValue(WATERLOGGED, Boolean.FALSE) .setValue(SKILL_LEVEL, 0) .setValue(FACE, Direction.DOWN) .setValue(DIRECTION, Direction.DOWN) .trySetValue(CURRENT_AGE, 0) ); } public static ToIntFunction<BlockState> emission(int p_181223_) { return (p_181221_) -> MultifaceBlock.hasAnyFace(p_181221_) ? p_181223_ : 0; } public static boolean hasFace(BlockState p_153901_, @NotNull Direction p_153902_) { BooleanProperty booleanproperty = getFaceProperty(p_153902_); return p_153901_.hasProperty(booleanproperty) && p_153901_.getValue(booleanproperty); } protected void createBlockStateDefinition(StateDefinition.@NotNull Builder<Block, BlockState> stateDefinition) { stateDefinition.add(WATERLOGGED).add(SKILL_LEVEL).add(FACE).add(DIRECTION).add(CURRENT_AGE); super.createBlockStateDefinition(stateDefinition); } public @NotNull BlockState updateShape(BlockState p_153302_, @NotNull Direction p_153303_, @NotNull BlockState p_153304_, @NotNull LevelAccessor p_153305_, @NotNull BlockPos p_153306_, @NotNull BlockPos p_153307_) { if (p_153302_.getValue(WATERLOGGED)) { p_153305_.scheduleTick(p_153306_, Fluids.WATER, Fluids.WATER.getTickDelay(p_153305_)); } return super.updateShape(p_153302_, p_153303_, p_153304_, p_153305_, p_153306_, p_153307_); } @SuppressWarnings("deprecation") public @NotNull FluidState getFluidState(BlockState fluidState) { return fluidState.getValue(WATERLOGGED) ? Fluids.WATER.getSource(false) : super.getFluidState(fluidState); } public boolean propagatesSkylightDown(BlockState p_181225_, @NotNull BlockGetter blockGetter, @NotNull BlockPos blockPos) { return p_181225_.getFluidState().isEmpty(); } public @NotNull MultifaceSpreader getSpreader() { return this.spreader; } public Optional<MultifaceSpreader.SpreadPos> spreadFromRandomFaceTowardRandomDirection( BlockState p_221620_, LevelAccessor p_221621_, BlockPos p_221622_, RandomSource p_221623_, int skillPoints, int age) { return Direction.allShuffled(p_221623_).stream().filter((p_221680_) -> { return this.config.canSpreadFrom(p_221620_, p_221680_); }).map((p_221629_) -> { return this.spreadFromFaceTowardRandomDirection(p_221620_, p_221621_, p_221622_, p_221629_, p_221623_, false, skillPoints, age); }).filter(Optional::isPresent).findFirst().orElse(Optional.empty()); } public Optional<MultifaceSpreader.SpreadPos> spreadFromFaceTowardRandomDirection( BlockState blockState, LevelAccessor levelAccessor, BlockPos blockPos, Direction face, RandomSource randomSource, boolean aBoolean, int skillPoints, int age) { return Direction.allShuffled(randomSource).stream().map((direction) -> spreadFromFaceTowardDirection(blockState, levelAccessor, blockPos, face, direction, aBoolean, skillPoints, age)) .filter(Optional::isPresent) .findFirst() .orElse(Optional.empty()); } public Optional<MultifaceSpreader.SpreadPos> spreadFromFaceTowardDirection( BlockState blockState, LevelAccessor levelAccessor, BlockPos blockPos, Direction face, Direction direction, boolean aBoolean, int skillPoints, int age) { //DevilRpg.LOGGER.debug("BEGIN ==================================== spreadFromFaceTowardDirection skillPoints {}", skillPoints); return skillPoints < 0 ? Optional.empty() : getSpreadFromFaceTowardDirection(blockState, levelAccessor, blockPos, face, direction, this::canSpreadInto) .flatMap((spreadPos) -> { //DevilRpg.LOGGER.debug("END ================================ spreadFromFaceTowardDirection spreadPos {}", spreadPos); return this.spreadToFace(levelAccessor, spreadPos, aBoolean, skillPoints, direction, age); }); } public boolean canSpreadInto(BlockGetter p_221685_, BlockPos p_221686_, MultifaceSpreader.SpreadPos p_221687_) { BlockState blockstate = p_221685_.getBlockState(p_221687_.pos()); return this.stateCanBeReplaced(p_221685_, p_221686_, p_221687_.pos(), p_221687_.face(), blockstate) && isValidStateForPlacement(p_221685_, blockstate, p_221687_.pos(), p_221687_.face()); } protected boolean stateCanBeReplaced(BlockGetter p_221688_, BlockPos p_221689_, BlockPos p_221690_, Direction p_221691_, BlockState p_221692_) { return p_221692_.isAir() || p_221692_.is(this) || p_221692_.is(Blocks.WATER) && p_221692_.getFluidState().isSource(); } public Optional<MultifaceSpreader.SpreadPos> getSpreadFromFaceTowardDirection(BlockState blockState, BlockGetter blockGetter, BlockPos blockPos, Direction face, Direction direction, MultifaceSpreader.SpreadPredicate spreadPredicate) { //DevilRpg.LOGGER.debug("--- getSpreadFromFaceTowardDirection direction.getAxis() == face.getAxis(): {}", direction.getAxis() == face.getAxis()); ArrayList<Direction> directions = new ArrayList<>(); directions.add(direction); if (direction.getAxis() == face.getAxis()) { if (direction.getAxis().isHorizontal()) { directions = Arrays.stream(Direction.values()).filter(dir -> dir.getAxis().isVertical()).collect(Collectors.toCollection(ArrayList::new)); } if (direction.getAxis().isVertical()) { directions = Arrays.stream(Direction.values()).filter(dir -> dir.getAxis().isHorizontal()).collect(Collectors.toCollection(ArrayList::new)); } } for (Direction directionElement : directions) { /*DevilRpg.LOGGER.debug("--->> getSpreadFromFaceTowardDirection config.isOtherBlockValidAsSource(blockState) {} || " + "hasFace(blockState, face) {} && " + "!hasFace(blockState, direction) {}", config.isOtherBlockValidAsSource(blockState), hasFace(blockState, face), !hasFace(blockState, directionElement));*/ if (config.isOtherBlockValidAsSource(blockState) || hasFace(blockState, face) && !hasFace(blockState, directionElement)) { for (MultifaceSpreader.SpreadType multifacespreader$spreadtype : config.getSpreadTypes()) { MultifaceSpreader.SpreadPos multifacespreader$spreadpos = multifacespreader$spreadtype.getSpreadPos(blockPos, directionElement, face); //DevilRpg.LOGGER.debug("--- test SpreadPos: {} direction {} face {} ", multifacespreader$spreadpos, directionElement, face); if (spreadPredicate.test(blockGetter, blockPos, multifacespreader$spreadpos)) { //DevilRpg.LOGGER.debug("--- spreadPredicate success:"); return Optional.of(multifacespreader$spreadpos); } } } } return Optional.empty(); } public boolean isValidStateForPlacement(@NotNull BlockGetter blockGetter, @NotNull BlockState blockState, @NotNull BlockPos blockPos, @NotNull Direction face) { //DevilRpg.LOGGER.debug("------ isValidStateForPlacement 1st condition: {} && ({} || {})", this.isFaceSupported(face), !blockState.is(this), !hasFace(blockState, face)); if (this.isFaceSupported(face) && (!blockState.is(this) || !hasFace(blockState, face))) { BlockPos blockpos = blockPos.relative(face); //DevilRpg.LOGGER.debug("------ isValidStateForPlacement 2nd condition: canAttachTo {} ", secondCondition); return canAttachTo(blockGetter, face, blockpos, blockGetter.getBlockState(blockpos)); } else { return false; } } @Nullable public BlockState getStateForPlacement(@NotNull BlockState blockState, @NotNull BlockGetter blockGetter, @NotNull BlockPos blockPos, @NotNull Direction face, int skillPoints, Direction direction, int age) { //DevilRpg.LOGGER.debug("--- getStateForPlacement"); boolean isNotValidStateForPlacement = !this.isValidStateForPlacement(blockGetter, blockState, blockPos, face); //DevilRpg.LOGGER.debug("------- isNotValidStateForPlacement: {}", isNotValidStateForPlacement); if (isNotValidStateForPlacement) { return null; } else { BlockState blockstate; if (blockState.is(this)) { blockstate = blockState; } else if (this.isWaterloggable() && blockState.getFluidState().isSourceOfType(Fluids.WATER)) { blockstate = this.defaultBlockState().setValue(BlockStateProperties.WATERLOGGED, Boolean.TRUE); } else { blockstate = this.defaultBlockState(); } //DevilRpg.LOGGER.debug("------- getStateForPlacement -> blockStateResult "); return blockstate .setValue(getFaceProperty(face), Boolean.TRUE) .setValue(SKILL_LEVEL, skillPoints).setValue(FACE, face) .setValue(DIRECTION, direction) .setValue(CURRENT_AGE,age) ; } } public Optional<MultifaceSpreader.SpreadPos> spreadToFace(LevelAccessor levelAccessor, MultifaceSpreader.SpreadPos spreadPos, boolean p_221596_, int skillPoints, Direction direction, int age) { BlockState blockstate = levelAccessor.getBlockState(spreadPos.pos()); //DevilRpg.LOGGER.debug("---> spreadToFace blockstate{} direction: {}", blockstate, direction); return this.placeBlock(levelAccessor, spreadPos, blockstate, p_221596_, skillPoints, direction, age) ? Optional.of(spreadPos) : Optional.empty(); } public boolean placeBlock(LevelAccessor p_221702_, MultifaceSpreader.SpreadPos p_221703_, BlockState p_221704_, boolean p_221705_, int skillPoints, Direction direction, int age) { //DevilRpg.LOGGER.debug("---> placeBlock {} direction {} ", p_221703_, direction); BlockState blockstate = this.getStateForPlacement(p_221704_, p_221702_, p_221703_.pos(), p_221703_.face(), skillPoints, direction, age); if (blockstate != null) { if (p_221705_) { p_221702_.getChunk(p_221703_.pos()).markPosForPostprocessing(p_221703_.pos()); } //DevilRpg.LOGGER.debug("------> setBlock"); return p_221702_.setBlock(p_221703_.pos(), blockstate, 2); } else { return false; } } public long spreadFromFaceTowardAllDirections( BlockState blockState, LevelAccessor levelAccessor, BlockPos blockPos, Direction face, boolean aBoolean, int skillPoints, int age) { return Direction.stream().map((p_221656_) -> spreadFromFaceTowardDirection(blockState, levelAccessor, blockPos, face, p_221656_, aBoolean, skillPoints, age)) .filter(Optional::isPresent).count(); } private boolean isWaterloggable() { return this.stateDefinition.getProperties().contains(BlockStateProperties.WATERLOGGED); } @Override public void setPlacedBy(@NotNull Level level, @NotNull BlockPos blockPos, @NotNull BlockState blockState, @Nullable LivingEntity livingEntity, @NotNull ItemStack itemStack) { super.setPlacedBy(level, blockPos, blockState, livingEntity, itemStack); this.setOwner(livingEntity); } public LivingEntity getOwner() { return this.owner; } private void setOwner(LivingEntity livingEntity) { this.owner = livingEntity; } @Deprecated @Override public void entityInside(@NotNull BlockState blockState, @NotNull Level level, @NotNull BlockPos blockPos, @NotNull Entity entity) { if (entity instanceof LivingEntity /*&& entity.getType() != EntityType.BEE*/ && entity.getType() != ModEntities.LICHEN_SEEDBALL.get()) { entity.makeStuckInBlock(blockState, new Vec3(0.8D, 0.75D, 0.8D)); if (!level.isClientSide /*&& (entity.xOld != entity.getX() || entity.zOld != entity.getZ())*/) { // double d0 = Math.abs(entity.getX() - entity.xOld); // double d1 = Math.abs(entity.getZ() - entity.zOld); // if (d0 >= (double) 0.003F || d1 >= (double) 0.003F) { entity.hurt(level.damageSources().playerAttack((Player) owner), 1.0F); // Aplicar aceleración al movimiento double speedBoost = -0.4; // Ajusta este valor según lo rápido que quieras que sea el impulso double motionX = entity.getX() - entity.xOld; double motionZ = entity.getZ() - entity.zOld; double speed = Math.sqrt(motionX * motionX + motionZ * motionZ); //if (speed > 0.0) { entity.setDeltaMovement(entity.getDeltaMovement().multiply( (motionX / speed) * speedBoost, 0.0, (motionZ / speed) * speedBoost )); // } //} } } } @Nullable @Override public BlockEntity newBlockEntity(@NotNull BlockPos pos, @NotNull BlockState state) { return ModEntityBlocks.SOUL_LICHEN_ENTITY_BLOCK.get().create(pos, state); } @Nullable @Override public <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level level, @NotNull BlockState blockState, @NotNull BlockEntityType<T> type) { return level.isClientSide ? null : (alevel, pos, aBlockstate, blockEntity) -> { if (blockEntity instanceof SoulLichenBlockEntity soulLichenBlockEntity && alevel.getGameTime() % 5 == 0) { soulLichenBlockEntity.tick(blockState, (ServerLevel) alevel, pos, alevel.getRandom()); //DevilRpg.LOGGER.info("-------->tick. this: {}", this.getClass().getSimpleName()); } }; } }   This is the registration:   public final class ModBlocks { public static final DeferredRegister<Block> BLOCKS = DeferredRegister.create(ForgeRegistries.BLOCKS, DevilRpg.MODID); ... public static final RegistryObject<SoulLichenBlock> SOUL_LICHEN_BLOCK = BLOCKS.register("soullichen", () -> new SoulLichenBlock( Block.Properties.copy(Blocks.GLOW_LICHEN).lightLevel(SoulLichenBlock.emission(7)).randomTicks() )); }  
    • If you are using AMD/ATI, check for driver updates on their website - do not update via system
    • Hi, Create a new class that extends "Block" class and you need json for it in resources/assets/modid/blockstates directory and resources/assets/modid/models. You can generate json for it using a tool like misodes model generator. Here, atleast, are blocks explained at forge docs.  Don't forge to look at vanilla code, like Magma Block is a good reference if you're trying a "green fire block".  Modid should be replaced with your actual forge mod namespace!
  • Topics

×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.