The Meaning of Life, the Universe, and Everything.
Join Date:
6/11/2014
Posts:
59
Member Details
I agree with your approach with regards to the sound, but I'm finding particles only seem to spawn on the client. The particles should be based on the same conditional as the sound, thus I need a way to inform the client. Or is there something I'm missing with regards to spawn particles?
I'll look into the Datawatcher, but know nothing about it at the moment. I'm sure to have more questions. Again, if there is a more appropriate thread for this, just say so.
Ah, well then - http://www.minecraftforge.net/wiki/Datawatcher sums it up pretty thoroughly. Basically, you add new indexes to watch during the entityInit method, then you can update that index with a value at any time on the server and the client side value for that index will be updated automatically. Just make sure you add and update with the appropriate type (e.g. Integer for Integer).
I find it easiest to create getter and setter methods for the value I'm watching, rather than using the verbose syntax for getting the value each time. You can see a simple example here. Look for 'getType' and 'setType'.
The Meaning of Life, the Universe, and Everything.
Join Date:
6/11/2014
Posts:
59
Member Details
I'm adding my field to the inherited dataWatcher in EntityThrowingRock, and doing so from within the overridden entityInit().
@Override
public void entityInit() {
super.entityInit();
dataWatcher.addObject(3, Integer.valueOf(0));
}
I'm updating my field from the server upon setBlockToAir(), then reading my dataWatcher field from the client, all within the onImpact().
@Override
protected void onImpact(MovingObjectPosition mop) {
int x = mop.blockX;
int y = mop.blockY;
int z = mop.blockZ;
if(mop.entityHit != null) {
mop.entityHit.attackEntityFrom(DamageSource.causeThrownDamage(this, getThrower()), 0.0F);
}
else {
if(!worldObj.isRemote) {
boolean isAir = worldObj.setBlockToAir(x, y, z);
dataWatcher.updateObject(3, isAir ? Integer.valueOf(1) : Integer.valueOf(0));
System.out.println("Server: "+dataWatcher.getWatchableObjectInt(3));
}
else {
System.out.println("Client: "+dataWatcher.getWatchableObjectInt(3));
}
}
if(!worldObj.isRemote) {
setDead();
}
}
The resulting output to the console fails to produce the expected results. The onImpact() method doesn't just get called once by the server and once by the client, but instead it's erratic. It also doesn't appear as tho my dataWatcher field is being updated from the server to the client. Here is my output. I opened the main menu after each block destroyed, which helps delineate them.
[01:07:51] [Server thread/INFO]: Saving chunks for level 'New World'/Overworld
[01:07:52] [Server thread/INFO]: Saving chunks for level 'New World'/Nether
[01:07:52] [Server thread/INFO]: Saving chunks for level 'New World'/The End
[01:07:52] [Server thread/WARN]: Can't keep up! Did the system time change, or is the server overloaded? Running 2053ms behind, skipping 41 tick(s)
Server: 1
[01:08:50] [Server thread/INFO]: Saving and pausing game...
[01:08:50] [Server thread/INFO]: Saving chunks for level 'New World'/Overworld
[01:08:50] [Server thread/INFO]: Saving chunks for level 'New World'/Nether
[01:08:50] [Server thread/INFO]: Saving chunks for level 'New World'/The End
Server: 1
[01:09:28] [Server thread/INFO]: Saving and pausing game...
[01:09:28] [Server thread/INFO]: Saving chunks for level 'New World'/Overworld
[01:09:28] [Server thread/INFO]: Saving chunks for level 'New World'/Nether
[01:09:28] [Server thread/INFO]: Saving chunks for level 'New World'/The End
Server: 1
Client: 0
Client: 0
[01:10:07] [Server thread/INFO]: Saving and pausing game...
[01:10:07] [Server thread/INFO]: Saving chunks for level 'New World'/Overworld
[01:10:07] [Server thread/INFO]: Saving chunks for level 'New World'/Nether
[01:10:07] [Server thread/INFO]: Saving chunks for level 'New World'/The End
Server: 1
Client: 0
[01:10:57] [Server thread/INFO]: Saving and pausing game...
[01:10:57] [Server thread/INFO]: Saving chunks for level 'New World'/Overworld
[01:10:57] [Server thread/INFO]: Saving chunks for level 'New World'/Nether
[01:10:57] [Server thread/INFO]: Saving chunks for level 'New World'/The End
Client: 0
Server: 1
Client: 0
Client: 0
[01:11:42] [Server thread/INFO]: Saving and pausing game...
[01:11:42] [Server thread/INFO]: Saving chunks for level 'New World'/Overworld
[01:11:42] [Server thread/INFO]: Saving chunks for level 'New World'/Nether
[01:11:42] [Server thread/INFO]: Saving chunks for level 'New World'/The End
Server: 1
Client: 0
[01:12:05] [Server thread/INFO]: Saving and pausing game...
[01:12:05] [Server thread/INFO]: Saving chunks for level 'New World'/Overworld
[01:12:05] [Server thread/INFO]: Saving chunks for level 'New World'/Nether
[01:12:05] [Server thread/INFO]: Saving chunks for level 'New World'/The End
Client: 0
Server: 1
Client: 0
[01:13:13] [Server thread/INFO]: Saving and pausing game...
[01:13:13] [Server thread/INFO]: Saving chunks for level 'New World'/Overworld
[01:13:13] [Server thread/INFO]: Saving chunks for level 'New World'/Nether
[01:13:13] [Server thread/INFO]: Saving chunks for level 'New World'/The End
Obviously, it isn't working, and I couldn't be more confused.
if (!worldObj.isRemote) {
update datawatcher - sending update
} else {
print current datawatcher value, but what about that pending update?
}
How would it even be possible for the client to have the information at this point? You have to wait at least a tick, so what I recommend is to handle the on impact on the server and set your data watcher. Set ANOTHER datawatcher for 'hasImpacted', set it to (byte) 1. Do NOT set the entity to dead at this point.
Then, in your entity's update method, check if 'hasImpacted' is true - if so, don't do the regular update code; instead, on the server, set your entity to dead; on the client, check if your 'setAir' value is correct or not, and spawn particles accordingly.
The server is handling my onImpact(), both calling setBlockToAir() as well as updating the dataWatcher fields.
@Override
protected void onImpact(MovingObjectPosition mop) {
movObjPos = mop;
int x = mop.blockX;
int y = mop.blockY;
int z = mop.blockZ;
if(mop.entityHit != null) {
mop.entityHit.attackEntityFrom(DamageSource.causeThrownDamage
(this, getThrower()), 0.0F);
}
else {
if(!worldObj.isRemote) {
boolean isAir = worldObj.setBlockToAir(x, y, z);
dataWatcher.updateObject(3, isAir ?(byte)1 :(byte)0);
dataWatcher.updateObject(4, (byte)1); //isDie.
}
}
}
Then the server handles the setDead() while the client handles the spawnParticle() in the overridden "onUpdate" (also tried onEntityUpdate() with same results).
@Override
public void onUpdate() {
if((int)dataWatcher.getWatchableObjectByte(4) > 0) {
if(!worldObj.isRemote) {
System.out.println("Server: setDead()");
setDead();
}
else {
if((int)dataWatcher.getWatchableObjectByte(3) > 0) {
System.out.println("Client: spawnParticle()");
int x = movObjPos.blockX;
int y = movObjPos.blockY;
int z = movObjPos.blockZ;
//spawn particles here.
}
}
}
else {
super.onUpdate();
}
}
While inside the onUpdate(), I need a reference to onImpact's movingObjectPosition for the coordinates. I assigned a reference within the class, and sometimes it works, but within three or four block broken, it crashes due to a NullPointerException for movObjPos in the onUpdate() when I request the coordinate value "movObjPos.blockX".
Also, why do I need to use a dataWatcher field for the setDead() flag? Why not just a class field?
You need to use DataWatcher because the class fields exist as two separate instances - remember, there is a copy of the entity running on the server, and a DIFFERENT copy running on the client, and they just APPEAR to be the same. It's basically an illusion run by the network, where it tells the client that entity #101 has certain characteristics, and the client says sure, I can do that, and either creates a new entity #101 or assigns whatever values it was told to the current entity #101, and the client runs most of the same methods (onUpdate, etc.) on its version of the entity, at least until told otherwise.
So what's happening is, on the server, you set datawatcher 'isDie' to 1 on impact, and as soon as your entity has an update tick, which is likely the same tick because you call super.onUpdate first and then handle your 'isDie', the entity dies on the server.
Here's the rundown (for the sake of argument, we'll assume client and server are happening simultaneously)
TICK 1
1. Your entity's onUpdate method is called
2. It calls super.onUpdate
3. super.onUpdate determines that an impact happened
4. Your entity's onImpact method is called
5. The server determines that the block was set to air, sets variables | client may or may not also think it hit a block
6. onImpact finishes, and returns to your super.onUpdate method, which finishes, and returns to your onUpdate method
7. You check if 'isDie' is true on the server, which it is, and set the entity to dead | on the client, things are still hunky-dory
8. Setting the entity to dead sends a packet to the client, telling it the entity is dead
TICK 2
1. Server entity is dead. Client entity receives a packet telling it that it is dead.
2. onUpdate never happens, because the entity is dead.
As you can see, the order in which you call things is very important. Imagine if, in your onUpdate method, you first checked for your variables 'isDie' and 'isAir' etc., and only then, once you have processed those and if the entity is still alive, do you call the super.onUpdate. What do you think would happen then? What about if you set 'isDie' to 1, and on the update tick you incremented it to 2, and only if it was 2 would you set the entity to dead?
The same client | server schism affects your block coordinates, too - the client may or may not know which coordinates where struck. You can add them as a class field like EntityArrow (xTile, yTile, zTile) and make sure that your onImpact code allows them to be set on both sides; this usually works fine, as the client and server typically think they are in the same position, but may sometimes give you odd results. If you need the coordinates to always be perfect, you should use DataWatcher or an update packet of some kind.
The Meaning of Life, the Universe, and Everything.
Join Date:
6/11/2014
Posts:
59
Member Details
Okay.. I understand it now. I believe I have everything working perfectly, but I still have questions.
First, I declare some handy constants.
public class EntityThrowingRock extends EntityThrowable {
//index values for dataWatcher fields.
//reserved by super class: 0, 1.
private final int DW_DYING = 2;
private final int DW_X = 3;
private final int DW_Y = 4;
private final int DW_Z = 5;
private final int DW_BLOCKID = 6;
private final int DW_METADATA = 7;
//number of ticks to pause for the client.
private final int DELAY_SET_DEAD = 10;
I add my dataWatcher fields, of which I now need 6, and make the data as compact as possible.
In the onImpact(), the server sets the block to air, then updates my dataWatcher fields. The client doesn't do anything exclusive, but I've included an output statement for demonstration. Then at the end, both the server and the client update my dataWatcher field indicating the entity is dying. I know the client doesn't pass dataWatcher fields to the server, only server to client, but it can still serve as a useful client-side flag.
@Override
protected void onImpact(MovingObjectPosition mop) {
int x = mop.blockX;
int y = mop.blockY;
int z = mop.blockZ;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//server.
if(!worldObj.isRemote) {
System.out.println(hashCode()+" Server: onImpact()");
//entity hit.
if(mop.entityHit != null) {
mop.entityHit.attackEntityFrom(DamageSource.causeThrownDamage
(this, getThrower()), 0.0F);
}
//block hit.
else {
Block block = worldObj.getBlock(x, y, z);
int blockId = Block.getIdFromBlock(block);
int metadata = worldObj.getBlockMetadata(x, y, z);
String breakSound = block.stepSound.getBreakSound();
//destroy block.
if(worldObj.setBlockToAir(x, y, z)) {
worldObj.playSoundEffect(x, y, z, breakSound, 1.0F, 1.0F);
block.dropBlockAsItem(worldObj, x, y, z, blockId, 0);
//relay blockId and metadata.
dataWatcher.updateObject(DW_BLOCKID, (short)blockId);
dataWatcher.updateObject(DW_METADATA, (byte)metadata);
}
//relay x,y,z.
dataWatcher.updateObject(DW_X, Integer.valueOf(x));
dataWatcher.updateObject(DW_Y, Integer.valueOf(y));
dataWatcher.updateObject(DW_Z, Integer.valueOf(z));
}
System.out.println(hashCode()+" Server: dying = 1");
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//client.
else {
System.out.println(hashCode()+" Client: onImpact()");
//do nothing.
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//start dying.
dataWatcher.updateObject(DW_DYING, (byte)1);
}
The onUpdate is called each tick, but only runs if the entity is alive, and the super.onUpdate, which calls onImpact(), is only called if the entity isn't dying. This keeps both the server and the client from calling onImpact() more than once. Which is useful, because if either the client or the server falls behind, the other keeps ticking and will make additional calls to onUpdate(). So the last statement in onImpact() effectively locks the door behind them.
The server portion of onUpdate pauses a specified number of ticks to help ensure the client's tasks have finished before setting the entity dead. A lengthy pause will make the entity appear to hang in mid air at the point of impact before finally disappearing.
Instead of "isAir", I'm now using "blockId" as the indicator that a block was broken, because I need that anyway and dataWatcher has limited space. And again, the client locks the door behind itself with a client side reset to blockId.
@Override
public void onUpdate() {
//retrieve dataWatcher fields.
int dying = dataWatcher.getWatchableObjectByte(DW_DYING);
int x = dataWatcher.getWatchableObjectInt(DW_X);
int y = dataWatcher.getWatchableObjectInt(DW_Y);
int z = dataWatcher.getWatchableObjectInt(DW_Z);
int blockId = dataWatcher.getWatchableObjectShort(DW_BLOCKID);
int metadata = dataWatcher.getWatchableObjectByte(DW_METADATA);
if(isEntityAlive()) {
if(dying < 1) {
//calls onImpact().
super.onUpdate();
}
else {
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//server.
if(!worldObj.isRemote) {
if(dying < DELAY_SET_DEAD) {
//increment dying.
dataWatcher.updateObject(DW_DYING, (byte)++dying);
System.out.println(hashCode()+" Server: dying = "+dying);
}
else {
setDead();
System.out.println(hashCode()+" Server: setDead()");
}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//client.
else {
if(blockId > 0) {
//reset blockId, client-side only.
dataWatcher.updateObject(DW_BLOCKID, (short)0);
//spawn particles.
for(int i=0; i < 25; i++) {
worldObj.spawnParticle("blockcrack_"+blockId+"_"+metadata
, x, y, z, (double)i*0.5D, (double)i*0.5D, (double)i*0.5D);
}
System.out.println(hashCode()+" Client: spawnParticle()");
}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
}
}
}
I've tested it pretty thoroughly and the console output gives a nice clear picture. When things are running smoothly, the client typically calls spawnParticle() by the server's 2nd tick. When the client is laggy, it can take until the 7th tick, or more.
So what I'm noticing is that a lot of priority is given to the server. The fields in dataWatcher only pass from server to client, and ultimately, if the client can't keep up, then the server has to move on without it.
Here is my biggest question. A few days ago, I said I had already implemented a seemingly flawless solution that used a custom event and handler. It was far easier to implement with less concern for side and greater data flexibility. I was able to send the actual block reference and perform the dropBlockAsItem() from the client handler. What is the advantage of using the dataWatcher instead? Efficiency?
Other questions.. can this code be improved with @Side annotation or with a proxy?
The dataWatcher can accept String. Thus it occurs to me that it can hold any serializable object, including HashTable. Such a table could be useful. Is there a reason to avoid it?
Thank you for your excellent instruction. You are a tremendous resource and a credit to the community.
No, using @Side (did you mean @SideOnly?) annotation is not what you want here.
DataWatcher can hold any String, so sure you could pass a HashTable, but keep in mind that as the data watched grows in size, so does the size of the packet required every time it updates, and this is not the most efficient solution. Nor, in fact, is the current one - you required 6 datawatcher fields, meaning at the very least you are sending 6 packets to the client before your particles can spawn. This is not ideal at all. Using Events is similar, in that you then have to check ALL blocks or entity updates or whatever you were checking to find your special case, which is a lot of wasted time.
The best solution, therefore, is to simply send one single packet from the server upon impact, containing just the x/y/z/block/meta - 5 ints. Then you can immediately set your entity to 'dead', and when you receive the packet on the client(s) [send to all around for everyone to see!], spawn your particles around those coordinates. No DataWatcher involved, no funky checks, just one simple packet xD
The Meaning of Life, the Universe, and Everything.
Join Date:
6/11/2014
Posts:
59
Member Details
I've put together a projectile-tools tutorial, based in fair part on your tutorial here. If you have the time, I would really appreciate your peer review:
I'm having trouble with getting projectiles to render. I have a main EntityBullet class, and two other entities that extend off of the entitybullet, one for a pistol and one for a rifle. They both use the same model and render class.
If the only difference between your bullets is damage and/or speed, then they should be using the same class and a setDamage(value) type of call. Then you can also use the exact same render class, and probably even the same item class. This is the point of Object Oriented Programming - duplicating as little code as possible.
Anyway, part of your problem is that ResourceLocations are defined either as ResourceLocation("modid:textures/path/texture.png") (note the COLON in between the modid and the texture path) OR ResourceLocation("modid", "textures/path/texture.png"), which has a comma separating the two (this version will insert a colon for you.
I don't plan to at this time, but if I ever end up making a vehicle for Minecraft, I'll be sure to document the process
I'm having trouble making the translatef and stuff work for me. I have a custom projectile called EntityXBowBolt(it's the entity rendering for an xbow bolt, the ammo for the xbow weapon, from Carnivores 2).
EntityXBowBolt.java:
package com.caske2000.carnivores.entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.projectile.EntityThrowable;
import net.minecraft.util.DamageSource;
import net.minecraft.util.MovingObjectPosition;
import net.minecraft.util.MovingObjectPosition.MovingObjectType;
import net.minecraft.world.World;
public class EntityXBowBolt extends EntityThrowable {
private int lifeTime = 400;
private double speed = 2.01;
private final int damage = 8;
public EntityXBowBolt(World world) {
super(world);
setSize(0.5F, 0.5F);
}
public EntityXBowBolt(World world, EntityLivingBase entity) {
super(world, entity);
}
public EntityXBowBolt(World world, double var2, double var3, double var4) {
super(world, var2, var3, var4);
}
@Override
protected void onImpact(MovingObjectPosition movObjPos) {
if (movObjPos.typeOfHit == MovingObjectType.ENTITY) {
movObjPos.entityHit.attackEntityFrom(DamageSource.causeThrownDamage(this, this.getThrower()), damage);
} else if (movObjPos.typeOfHit == MovingObjectType.BLOCK) {
}
this.setDead();
}
/*
* @Override protected float getGravityVelocity() {
*
* this.motionX *= speed; this.motionY *= speed; this.motionZ *= speed;
* return 0;
*
* }
*/
public int getMaxLifetime() {
return 400;
}
public float getAirResistance() {
return 0.0F;
}
public float getGravity() {
return 0.0F;
}
public int getMaxArrowShake() {
return 0;
}
}
I'll look into the Datawatcher, but know nothing about it at the moment. I'm sure to have more questions. Again, if there is a more appropriate thread for this, just say so.
See that up arrow?
I find it easiest to create getter and setter methods for the value I'm watching, rather than using the verbose syntax for getting the value each time. You can see a simple example here. Look for 'getType' and 'setType'.
I'm updating my field from the server upon setBlockToAir(), then reading my dataWatcher field from the client, all within the onImpact().
The resulting output to the console fails to produce the expected results. The onImpact() method doesn't just get called once by the server and once by the client, but instead it's erratic. It also doesn't appear as tho my dataWatcher field is being updated from the server to the client. Here is my output. I opened the main menu after each block destroyed, which helps delineate them.
Obviously, it isn't working, and I couldn't be more confused.
See that up arrow?
How would it even be possible for the client to have the information at this point? You have to wait at least a tick, so what I recommend is to handle the on impact on the server and set your data watcher. Set ANOTHER datawatcher for 'hasImpacted', set it to (byte) 1. Do NOT set the entity to dead at this point.
Then, in your entity's update method, check if 'hasImpacted' is true - if so, don't do the regular update code; instead, on the server, set your entity to dead; on the client, check if your 'setAir' value is correct or not, and spawn particles accordingly.
Quantum entanglement. Would you like to see my Position class or my Velocity class? You'll have to pick one.
Your explanation makes sense, and I believe I've implemented it as per your directions, but sadly, I'm still running into issues.
I'm adding my dataWatcher fields in the entityInit().
The server is handling my onImpact(), both calling setBlockToAir() as well as updating the dataWatcher fields.
Then the server handles the setDead() while the client handles the spawnParticle() in the overridden "onUpdate" (also tried onEntityUpdate() with same results).
While inside the onUpdate(), I need a reference to onImpact's movingObjectPosition for the coordinates. I assigned a reference within the class, and sometimes it works, but within three or four block broken, it crashes due to a NullPointerException for movObjPos in the onUpdate() when I request the coordinate value "movObjPos.blockX".
Also, why do I need to use a dataWatcher field for the setDead() flag? Why not just a class field?
See that up arrow?
So what's happening is, on the server, you set datawatcher 'isDie' to 1 on impact, and as soon as your entity has an update tick, which is likely the same tick because you call super.onUpdate first and then handle your 'isDie', the entity dies on the server.
Here's the rundown (for the sake of argument, we'll assume client and server are happening simultaneously)
TICK 1
1. Your entity's onUpdate method is called
2. It calls super.onUpdate
3. super.onUpdate determines that an impact happened
4. Your entity's onImpact method is called
5. The server determines that the block was set to air, sets variables | client may or may not also think it hit a block
6. onImpact finishes, and returns to your super.onUpdate method, which finishes, and returns to your onUpdate method
7. You check if 'isDie' is true on the server, which it is, and set the entity to dead | on the client, things are still hunky-dory
8. Setting the entity to dead sends a packet to the client, telling it the entity is dead
TICK 2
1. Server entity is dead. Client entity receives a packet telling it that it is dead.
2. onUpdate never happens, because the entity is dead.
As you can see, the order in which you call things is very important. Imagine if, in your onUpdate method, you first checked for your variables 'isDie' and 'isAir' etc., and only then, once you have processed those and if the entity is still alive, do you call the super.onUpdate. What do you think would happen then? What about if you set 'isDie' to 1, and on the update tick you incremented it to 2, and only if it was 2 would you set the entity to dead?
The same client | server schism affects your block coordinates, too - the client may or may not know which coordinates where struck. You can add them as a class field like EntityArrow (xTile, yTile, zTile) and make sure that your onImpact code allows them to be set on both sides; this usually works fine, as the client and server typically think they are in the same position, but may sometimes give you odd results. If you need the coordinates to always be perfect, you should use DataWatcher or an update packet of some kind.
First, I declare some handy constants.
I add my dataWatcher fields, of which I now need 6, and make the data as compact as possible.
In the onImpact(), the server sets the block to air, then updates my dataWatcher fields. The client doesn't do anything exclusive, but I've included an output statement for demonstration. Then at the end, both the server and the client update my dataWatcher field indicating the entity is dying. I know the client doesn't pass dataWatcher fields to the server, only server to client, but it can still serve as a useful client-side flag.
The onUpdate is called each tick, but only runs if the entity is alive, and the super.onUpdate, which calls onImpact(), is only called if the entity isn't dying. This keeps both the server and the client from calling onImpact() more than once. Which is useful, because if either the client or the server falls behind, the other keeps ticking and will make additional calls to onUpdate(). So the last statement in onImpact() effectively locks the door behind them.
The server portion of onUpdate pauses a specified number of ticks to help ensure the client's tasks have finished before setting the entity dead. A lengthy pause will make the entity appear to hang in mid air at the point of impact before finally disappearing.
Instead of "isAir", I'm now using "blockId" as the indicator that a block was broken, because I need that anyway and dataWatcher has limited space. And again, the client locks the door behind itself with a client side reset to blockId.
I've tested it pretty thoroughly and the console output gives a nice clear picture. When things are running smoothly, the client typically calls spawnParticle() by the server's 2nd tick. When the client is laggy, it can take until the 7th tick, or more.
So what I'm noticing is that a lot of priority is given to the server. The fields in dataWatcher only pass from server to client, and ultimately, if the client can't keep up, then the server has to move on without it.
Here is my biggest question. A few days ago, I said I had already implemented a seemingly flawless solution that used a custom event and handler. It was far easier to implement with less concern for side and greater data flexibility. I was able to send the actual block reference and perform the dropBlockAsItem() from the client handler. What is the advantage of using the dataWatcher instead? Efficiency?
Other questions.. can this code be improved with @Side annotation or with a proxy?
The dataWatcher can accept String. Thus it occurs to me that it can hold any serializable object, including HashTable. Such a table could be useful. Is there a reason to avoid it?
Thank you for your excellent instruction. You are a tremendous resource and a credit to the community.
See that up arrow?
DataWatcher can hold any String, so sure you could pass a HashTable, but keep in mind that as the data watched grows in size, so does the size of the packet required every time it updates, and this is not the most efficient solution. Nor, in fact, is the current one - you required 6 datawatcher fields, meaning at the very least you are sending 6 packets to the client before your particles can spawn. This is not ideal at all. Using Events is similar, in that you then have to check ALL blocks or entity updates or whatever you were checking to find your special case, which is a lot of wasted time.
The best solution, therefore, is to simply send one single packet from the server upon impact, containing just the x/y/z/block/meta - 5 ints. Then you can immediately set your entity to 'dead', and when you receive the packet on the client(s) [send to all around for everyone to see!], spawn your particles around those coordinates. No DataWatcher involved, no funky checks, just one simple packet xD
How do I send a packet?
See that up arrow?
Actually, I was hoping you either already knew how or that you would try searching the vast wealth of knowledge that is the internet.
But since you asked: Using and Customizing SimpleNetworkWrapper
Oh I see, it was a setup. Clever...
Thanks for the link!
See that up arrow?
Could you make a tutorial for making vehicles?
I don't plan to at this time, but if I ever end up making a vehicle for Minecraft, I'll be sure to document the process
Custom Projectiles and Persistent Block Damage
See that up arrow?
ModelBullet.java
RenderBullet.java
I just took the Minecraft Noob test! Check out what I scored. Think you can beat me?!
To take the test, check out
https://minecraftnoobtest.com/test.php
Don't click this link, HE is haunting it...
EntityBullet.java:
EntityPistolBullet.java:
same as EntityBullet
EntityRifle.java
Also the same, except damage is 12.
I just took the Minecraft Noob test! Check out what I scored. Think you can beat me?!
To take the test, check out
https://minecraftnoobtest.com/test.php
Don't click this link, HE is haunting it...
Anyway, part of your problem is that ResourceLocations are defined either as ResourceLocation("modid:textures/path/texture.png") (note the COLON in between the modid and the texture path) OR ResourceLocation("modid", "textures/path/texture.png"), which has a comma separating the two (this version will insert a colon for you.
I'm having trouble making the translatef and stuff work for me. I have a custom projectile called EntityXBowBolt(it's the entity rendering for an xbow bolt, the ammo for the xbow weapon, from Carnivores 2).
EntityXBowBolt.java:
ModelXBowBolt.java:
RenderXBowBolt.java:
I just took the Minecraft Noob test! Check out what I scored. Think you can beat me?!
To take the test, check out
https://minecraftnoobtest.com/test.php
Don't click this link, HE is haunting it...