Jump to content

[1.7.10] How would you sync a mobs inventory Server n Client side


Recommended Posts

Posted

Hi everyone, I have a method that gives my mob attacks which are basically customized items (it works). But I need to sync the mobs inventory so that the attack items also exist on the client. Im not sure how to do this. (And Dont just say "Packets" I know that it requires packets or use of a datawatcher but I am stumped how to do this) Currently I have tried the following see below).

 

This method is called server side only when my entity gets constructed and then again whenever my entity levels up (this works)

public void attacks(EntityCustom customEntity){
	if(customEntity instanceof EntityMyMob){
		int lvl = customEntity.getStats().level;
		if(lvl <= 5){
		customEntity.attacks[0] = new ItemStack(CommonProxy.itemAttack_Primary);
		PacketOverlord.sendToAll(new PacketSyncAttacks(customEntity)); <- need to update the attacks when they are given
		}
		if(lvl > 5){
		customEntity.attacks[1] = new ItemStack(CommonProxy.itemAttack_Secondary);
                         PacketOverlord.sendToAll(new PacketSyncAttacks(customEntity)); <- need to update the attacks when they are given
		  	}
		if (lvl >= 12){
		customEntity.attacks[2] = new ItemStack(CommonProxy.itemAttack_Tertiary);
                        PacketOverlord.sendToAll(new PacketSyncAttacks(customEntity)); <- need to update the attacks when they are given
		}
		if(lvl >= 18){
		customEntity.attacks[3] = new ItemStack(CommonProxy.itemAttack_Quaternary);
                        PacketOverlord.sendToAll(new PacketSyncAttacks(customEntity)); <- need to update the attacks when they are given
		}

	}
}

 

My packet class - The issue is my entity is null when I send the packet for some reason, So I am unsure how to update the entity on its client side so that it has the attacks on both sides. Currently the attacks are only present on server side

public class PacketSyncAttacks extends AbstractMessageToClient<PacketSyncAttacks> {

private NBTTagCompound data;
EntityCustom custom;

public PacketSyncAttacks() {}

public PacketSyncAttacks(EntityCustom custom) {

	this.custom= custom; <- Entity is not null here
	data = new NBTTagCompound();
	custom.writeEntityToNBT(data);
}

@Override
protected void read(PacketBuffer buffer) throws IOException {

	data = buffer.readNBTTagCompoundFromBuffer();
}

@Override
protected void write(PacketBuffer buffer) throws IOException {
	System.out.println("FUCK3");
	buffer.writeNBTTagCompoundToBuffer(data);
}

@Override
public void process(EntityPlayer player, Side side) {
//But my entity is null here. So I am unsure what to do to update my entities inventory
	if(data != null && custom!= null){ <- this is never called since entity is null here on client side
	this.custom.readFromNBT(data);
	}
}
}

 

My Packet Handler class

 

 

public class PacketOverlord {
private static byte packetId = 0;

private static final SimpleNetworkWrapper dispatcher = NetworkRegistry.INSTANCE
		.newSimpleChannel(CustomMod.CHANNEL);

/**
 * Register all packets and handlers here - this should be called during
 * {@link FMLPreInitializationEvent}
 */
public static final void preInit() {

           registerMessage(PacketSyncAttacks.class);

	}

/**
 * Register an {@link AbstractMessageOverlord} to the correct side
 */
private static final <T extends AbstractMessageOverlord<T> & IMessageHandler<T, IMessage>> void registerMessage(
		Class<T> clazz) {
	if (AbstractMessageOverlord.AbstractMessageToClient.class.isAssignableFrom(clazz)) {
		PacketOverlord.dispatcher.registerMessage(clazz, clazz,	packetId++, Side.CLIENT);
	} else if (AbstractMessageOverlord.AbstractMessageToServer.class.isAssignableFrom(clazz)) {
		PacketOverlord.dispatcher.registerMessage(clazz, clazz,	packetId++, Side.SERVER);
	} else {
		PacketOverlord.dispatcher.registerMessage(clazz, clazz, packetId,Side.CLIENT);
		PacketOverlord.dispatcher.registerMessage(clazz, clazz,	packetId++, Side.SERVER);
	}
}

/**
 * This message is sent 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);
}

public static final void sendToPlayers(IMessage message, Set<EntityPlayer> players) {
	for(EntityPlayer player : players) {
		//System.out.println("player"+player);
		//System.out.println("players"+players);
	 PacketOverlord.dispatcher.sendTo(message, (EntityPlayerMP) player);
	}
	}



/**This sends a message to everyone. See
 * {@link SimpleNetworkWrapper#sendToAll(IMessage)}
 */
public static void sendToAll(IMessage message) {
	PacketOverlord.dispatcher.sendToAll(message);
}

/**
 * This sends a message to everyone within a specified range of a specified point. See
 * {@link SimpleNetworkWrapper#sendToAllAround(IMessage, NetworkRegistry.TargetPoint)}
 */
public static final void sendToAllAround(IMessage message,
		NetworkRegistry.TargetPoint point) {
	PacketOverlord.dispatcher.sendToAllAround(message, point);
}

/**
 * This sends a message to everyone within a specified range of the passed in coordinates in
 * the same MC dimension. Shortcut to
 * {@link SimpleNetworkWrapper#sendToAllAround(IMessage, NetworkRegistry.TargetPoint)}
 */
public static final void sendToAllAroundCoordinates(IMessage message, int dimension, double x, double y, double z, double range) {
	PacketOverlord.sendToAllAround(message,	new NetworkRegistry.TargetPoint(dimension, x, y, z, range));
}

/**
 * This sends a message to everyone within a specified range of the passed in player
 * Shortcut to
 * {@link SimpleNetworkWrapper#sendToAllAround(IMessage, NetworkRegistry.TargetPoint)}
 */
public static final void sendToAllAround(IMessage message, EntityPlayer player, double range) {
	PacketOverlord.sendToAllAroundCoordinates(message,	player.worldObj.provider.dimensionId, player.posX, player.posY,	player.posZ, range);
}

/**
 * This sends a message to everyone within a specified range of the passed in pkmn
 * Shortcut to
 * {@link SimpleNetworkWrapper#sendToAllAround(IMessage, NetworkRegistry.TargetPoint)}
 */
public static final void sendToAllAround(IMessage message, EntityCustom entityCustom, double range) {
	PacketOverlord.sendToAllAroundCoordinates(message,	entityCustom.worldObj.provider.dimensionId, entityCustom.posX, entityCustom.posY, entityCustom.posZ, range);
}

/**
 * this sends a message to everyone within the passed in dimension that is denoted by its id. See
 * {@link SimpleNetworkWrapper#sendToDimension(IMessage, int)}
 */
public static final void sendToDimension(IMessage message, int dimensionId) {
	PacketOverlord.dispatcher.sendToDimension(message, dimensionId);
}

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

public static final SimpleNetworkWrapper getInstance()
{
	return dispatcher;
}
}

 

 

 

Anyone know of a good way to do this I am stumped atm

Posted

I don't think you need custom packets for this. Inventories can be synced by built in methods if you make them part of a Container along with an IGuiHandler. The IGuiHandler is what syncs up the client and server for you. I have a tutorial for inventory GUIs for blocks, but I'm fairly certain it would work same way for any inventory: http://jabelarminecraft.blogspot.com/p/minecraft-modding-blocks-with-guis.html

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

Posted

Thornack, you are misunderstanding how packets work.

 

A packet is created on one side, populating all the class fields as needed and then writing those to an output stream so it can be sent to the other side; when this data is received on the other side, a COMPLETELY NEW instance of the packet is created, meaning NONE of your class fields have any information in them yet.

 

The data is then parsed by the packet's read method, which is generally where you initialize class fields, and then those fields can be used when the handler processes the packet.

 

That said, Jabelar is correct - you shouldn't need to manually sync inventory information, as that is done by the Container class. If you don't have a Container class, you probably don't have a GUI, and if you don't have a GUI, then why does the client need to know the information anyway?

Posted

Ok Thanks for clarifying that up, coolAlias I think I understand packets a bit better now, but I still need to send my packet and still have the same problem. I dont know how to sync my attack items client and server side.. I dont have a gui for my entity, I need to make my items exist on client side also so that my mob entity can use them. Currently what I have is the following.

 

Entity is spawned -> Entities level is generated -> Entities attacks are calculated based on level -> This is done server side -> Entity can be added to players party -> Entity can then be spawned by player as an ownable entity -> Player can become this entity by morphing into it -> Player gets all entity stats, inventory and items in inventory -> Items are attacks that the player can then use.

 

All of that works great. Im trying ot now write an AI to use my entities attack items so that the untamed entity can attack other entities using the attack items (just like the player can).

 

my partial entity class (sorry  alot of the code is pretty complicated so I omitted the crazy stuff cause this entity can do a lot atm) Hopefully this is sufficient to help grasp the issue. I need this entity to be able to use my attack items with onItem Right Click and on Item Use -> and onUsingTick-> to spawn custom particles and deal custom damage from a custom DamageSource.

 

I left in every line related to my attack items. The attacks only exist on server side however for my entity. Currently My entity spawns the attack item particles but the attack does no damage (for some reason)

 

 

 

public class EntityCustom extends EntityAnimal implements IEntityAdditionalSpawnData
{

    public ItemStack[] attacks = new ItemStack[4];
    public int currentAttack;
    private ItemStack attackInUse;
    private int attackInUseCount;
    
    
public CustomStats stats;

public EntityCustom(World world)
{
	super(world);
	this.setSize(0.9F, 0.9F);

	this.setupAI();		

	this.stats = new CustomStats(this);
	if(!world.isRemote){
		stats.calculateNewStats();
		stats.recalculateStats(this);
	}		
}


public CustomStats getStats()
{
	return this.stats;
}



  public ItemStack getCurrentAttack()
    {
        return this.currentAttack < 4 && this.currentAttack >= 0 ? this.attacks[this.currentAttack] : null;
    }
  
  public void setAttackItemInUse(ItemStack itemStack, int maxItemUseDuration)
    {  
        if (itemStack != this.attackInUse)
        {  
            if (maxItemUseDuration <= 0){ 
            	return;
            }
            
            this.attackInUse = itemStack;
            this.attackInUseCount = maxItemUseDuration;
            
            if (!this.worldObj.isRemote)
            {
                this.setEating(true);
            }
        }
    }
  
  public void clearAttackInUse()
    {
        this.attackInUse = null;
        this.attackInUseCount = 0;

        if (!this.worldObj.isRemote)
        {
            this.setEating(false);
        }
    }
  
  protected void onAttackUseFinish()
    {
        if (this.attackInUse != null)
        {
            int i = this.attackInUse.stackSize;
            /**may cause problems*/
            ItemStack itemstack = this.attackInUse.onFoodEaten(this.worldObj, null);

           if (itemstack != this.attackInUse || itemstack != null && itemstack.stackSize != i)
            {
            	this.attacks[this.currentAttack] = itemstack;

                if (itemstack != null && itemstack.stackSize == 0)
                {
                    this.attacks[this.currentAttack] = null;
                }
            }

            this.clearAttackInUse();
        }
    }
  
  
  
@Override
 public void onLivingUpdate(){
	super.onLivingUpdate();
	///////////////////////////////////

	Attack att = (Attack)  attacks[0].getItem();
	att.onItemRightClick( attacks[0], this.worldObj, this);

        if (this.attackInUse != null)
        {
        	ItemStack itemstack = this.getCurrentAttack();

            if (itemstack == this.attackInUse)
            {
            	
            	if (attackInUseCount <= 0)
                {
            		this.onAttackUseFinish();
                }
                else
                {
                	
                	if(attackInUse.getItem() instanceof PWItem){
                		
                		PWItem attack = (PWItem) attackInUse.getItem();
                		attack.onUsingTick(attackInUse, this, attackInUseCount);
                	}
                	                      
                    if (--this.attackInUseCount == 0 && !this.worldObj.isRemote)
                    {
                    	this.onAttackUseFinish();
                    }
                }
            }
            else
            {            	
                this.clearAttackInUse();
            }
            
        }
        /////////////////////////////////////+

 }
   

@Override
protected void entityInit()
{
	super.entityInit();

}

@Override
public boolean attackEntityFrom(DamageSource damageSource, float damageAmount)
{
	if (!this.worldObj.isRemote)
	{
		if(this.stats.currentHp > 0)
		{
			this.stats.currentHp = (int) this.getHealth() - (int) damageAmount;
		}
		else
		{
			this.stats.currentHp = 0;
		}
		MessageUpdateCustomStats msg = new MessageUpdateCustomStats(this);
		msg.sendToAll();
	}
	return super.attackEntityFrom(damageSource, damageAmount);
}

@Override
public boolean isAIEnabled()
{
	return true;
}

@Override
protected void updateAITasks()
{
	super.updateAITasks();
}

private void setupAI()
{	
	this.getNavigator().setAvoidsWater(true);
	clearAITasks();
	double speed = 0.5F;
	//this.tasks.addTask(0, new EntityAISwimming(this));
	this.tasks.addTask(1, new EntityAIWander(this, speed));
	this.tasks.addTask(2, new EntityAIWatchClosest(this, EntityPlayer.class, 12.0F));
	this.tasks.addTask(3, new EntityAIPanic(this, speed)); // speed is the second parameter
	this.tasks.addTask(4, new EntityAILookIdle(this));
	this.tasks.addTask(5, new EntityAIWatchClosest(this, EntityCustom.class, 6.0F));
}

private void clearAITasks()
{
	tasks.taskEntries.clear();
	targetTasks.taskEntries.clear();
}

@Override
public void writeSpawnData(ByteBuf buffer)
{
	this.stats.toBytes(buffer);
}

@Override
public void readSpawnData(ByteBuf additionalData)
{
	this.stats.fromBytes(additionalData);
}

  /**
     * Returns the item that this EntityLiving is holding, if any.
     */
    public ItemStack getFirstAttack()
    {
        return this.attacks[0];
    }
    
    public ItemStack getAttackFromSlot(int slot)
    {
        return this.attacks[slot];
    }
    
    public ItemStack deleteAttackInSlot(int slot)
    {
        return this.attacks[slot] = null;
    }

    @Override
public void writeEntityToNBT(NBTTagCompound nbt)
{ 
	super.writeEntityToNBT(nbt);

	NBTTagList nbttaglist = new NBTTagList();
        NBTTagCompound attackNBT;
        
	  
	for (int i = 0; i < this.attacks.length; ++i) {
		attackNBT = new NBTTagCompound();

		if (this.attacks[i] != null) {
			this.attacks[i].writeToNBT(attackNBT);
		}

		nbttaglist.appendTag(attackNBT);
	}
	nbt.setTag("Attacks", nbttaglist);
	this.stats.saveNBT(nbt);

	}

@Override
public void readEntityFromNBT(NBTTagCompound nbt)
{   super.readEntityFromNBT(nbt);

	NBTTagList nbttaglist;
	 if (nbt.hasKey("Attacks", 9)){
            nbttaglist = nbt.getTagList("Attacks", 10);

            for (int i = 0; i < this.attacks.length; ++i)
            {
                this.attacks[i] = ItemStack.loadItemStackFromNBT(nbttaglist.getCompoundTagAt(i));
            }
        }
	this.stats.loadNBT(nbt);

	}



}

 

 

 

When I get the entities that the attack is supposed to target (they return correctly Sorry I also removed a lot of logic here cause its kinda huge atm) BUT for some reason

List<EntityLivingBase> targets = TargetingHelper.acquireAllLookTargets(customEntity, Math.round(damageRadius), 1.0F);
		for (EntityLivingBase target : targets) {
                                System.out.println("Damage Done: " + appliedDmg + " target " + target);
			target.attackEntityFrom(getDamageSource(customEntity), appliedDmg);

}

The damage isnt done since when I call

 target.attackEntityFrom(getDamageSource(customEntity), appliedDmg); 

the appliedDmg variable is there and calculated correctly, the entity is there, the damage source is there, but the attackEntityFrom bit is never called (I checked with a break point)!! But for the player (when player is morphed into the entity and gets the entities attack) everything is there and damage does get dealt...So I checked whether or not my item exists client side for my entity and it does not, I suspect this is the problem. The onLivingUpdate method crashes the game when I allow it to call the following on client side because the item is null when I call.

if(this.worldObj.isRemote){
	System.out.println(attacks[0]); <- this is null on client side but not null on server side
	Attack att = (Attack)  attacks[0].getItem();
	att.onItemRightClick( attacks[0], this.worldObj, this);
	}

 

So my item is only there on server side and this is probably why I am having issues.

 

Its basically driving me crazy how hard this "get mobs to use my items" is to implement ugh. The good thing though if I get this one class implemented then all of my attacks will work because they are all set up the same. Any ideas anybody?

 

I am basically trying to take all of the onItemRightClick stuff and onItemUse, and onUsingTick etc etc and implement it for my entity so that it can use my item. I believe the important classes/methods have already been posted

Posted

Why do you care if the code runs on the client side, though? Only the server side should care about whether an attack or action is performed - the only exception is if you need some animation or something to happen on the client, and then you can just send a packet or health update flag saying 'this action was done on the server, so do your thing now client'.

 

So the solution is to only run the code on the server side - there is no reason to run it on both sides for a non-player entity.

Posted

My customized onItemRightClick method from my attack item class that I changed to work for my custom entity (it works for the player). Currently just trying to get the else if(ball ==false){} attacks done and working as they represent the majority of my attacks (the last else statement in this method). The ball type attacks are kind of like a snowball projectile but they do damage and have a trailing particle behind them and use a custom model. For now the non ball attacks is what I am looking at.

 

 

public ItemStack onItemRightClick(ItemStack itemStack, World world, EntityCustom entityCustom) {
				int fatigueMultiplier = 1;

				if(ball == true && (getCooldown(itemStack) == 0)){
				 fatigueMultiplier = 2;

				EntityRangedAttackBase distanceAttack = new EntityRangedAttack(world, entityCustom).setType(attackType).setArea(damageRadius);
      				distanceAttack.setBaseDamageForProjectileAttack(baseDmg);
      				setCooldown(itemStack, 30);

      				if (!world.isRemote) {//spawn the bomb entity if your on the server
      					WorldHelper.playSoundAtEntity(entityCustom,BattleSoundsList.WHOOSH, 0.4F, 0.5F);
      					world.spawnEntityInWorld(distanceAttack);
      				}

      				
		      }	else if(ball ==false){	
		    	 //This is the bit that I care about atm as these are the main attacks
		    	  entityCustom.setAttackItemInUse(itemStack,getMaxItemUseDuration(itemStack));
			    }


	return itemStack;
}

 

 

 

My onUsingTickMethod inside my attack item class (atm I only care about the non stationary attacks (again since they represent the majority) (last else statement in this method

 

 

public void onUsingTick(ItemStack itemStack, EntityCustom entityCustom, int count) {

	if(entityCustom.worldObj.isRemote){
		System.out.println("NEVER CALLED"); //This is never called because my item is serverside only so the aiming logic for the attack doesnt work properly atm.
		MovingObjectPosition mop = entityCustom.rayTrace(15, 1F);
		if (mop != null && !entityCustom.worldObj.isAirBlock(mop.blockX, mop.blockY, mop.blockZ) && (mop.typeOfHit == MovingObjectPosition.MovingObjectType.BLOCK || mop.typeOfHit == MovingObjectPosition.MovingObjectType.ENTITY)) {
			PacketOverlord.sendToServer(new PacketUpdateOnUsingTickTargetCoord(mop.blockX, mop.blockY, mop.blockZ));
		}
	}

	if (!entityCustom.worldObj.isRemote) {

               int ticksInUse = getMaxItemUseDuration(itemStack) - count;
			// Spawn stationary particle attack on right click
			if (this == CommonProxy.itemAttack_Stationary) {

				if (ticksInUse % 10 == 0) {

				EntityStationary entityStationary = new EntityStationary(entityCustom.worldObj, xTargetCoord + 0.5, yTargetCoord + 1, zTargetCoord + 0.5).disableGriefing();
				entityStationary .thrower = entityCustom;
				entityStationary .setBaseDamageForProjectileAttack(baseDmg);
				entityCustom.worldObj.spawnEntityInWorld(entityStationary);
				}
			} else {
				//This is what I care about here because I want to deal with the non stationary attacks
				handleUpdateTick(itemStack, entityCustom.worldObj, entityCustom, ticksInUse);
			}

	}
}

 

 

 

The handleUpdateTick method  inside my attack item class that I use for my main attacks. The non ball non stationary attacks (these are the majority of my particle attacks and they behave like a flamethrower essentially but all have different particles that are set elsewhere. All particles spawn normally but damage isnt done ever

 

 

private void handleUpdateTick(ItemStack itemStack, World world, EntityCustom entityCustom, int ticksInUse) {
	PacketOverlord.sendToAllAround(new PacketISpawnParticles(entityCustom, this, (float) (damageRadius)), entityCustom, 64.0D); <- works to spawn my particles (and they do actually show up)
	if (ticksInUse % 4 == 3) {
		float radius = 1;
		if(entityCustom.entID != -1){
			 radius = 10;
			}		
		List<EntityLivingBase> targets = TargetingHelper.acquireAllLookTargets(entityCustom, Math.round(1*damageRadius), 1.0F);
		for (EntityLivingBase target : targets) {
		//////////DAMAGE LOGIC FOR NON BALL TYPE ATTACKS////////////
			int defense = 1;
			if(target instanceof EntityCustom){
				defense = ((EntityCustom) target).stats.currentDef;
			} else if (target instanceof EntityPlayer){
					defense = 1;
			  }

			if(entityCustom.entID != -1){

			float calcPartTwo = ((float)entityCustom.stats.currentAtt/defense);
			float modifier = 1.0F;
			float appliedDmg = ((calcPartTwo*baseDmg)+2.0F)*modifier;
			System.out.println("Damage Done: " + appliedDmg); <-This prints correctly
			target.attackEntityFrom(getDamageSource(entityCustom), appliedDmg); //BUT DAMAGE ISNT ACTUALLY DONE!!!!!!!!!!
			System.out.println("damage source "+getDamageSource(entityCustom)); <-This prints correctly
			System.out.println(entityCustom);<-This prints correctly
			}	
	         ////////////////////////////////////////////////////////////////////
		}
	}

}

 

 

Posted

Ok I got my damage working but now I still need the targeting to work. It was a simple fix ish where I passed in the the target as an entity player so that the player into the

target.attackEntityFrom(getDamageSource((EntityPlayer) target), appliedDmg); 

can take damage from the wild entities. Im not sure if the damage is the correct damage but the player can now be hurt by the entity so that seems to work good.

Posted

I still need to figure out how to get the aiming code working (that would require the invnetory to be on client side also so that the onItemRightClick method can be called on client and server just like the player one does

Posted

I still need to figure out how to get the aiming code working (that would require the invnetory to be on client side also so that the onItemRightClick method can be called on client and server just like the player one does

Is the aiming code the reason you think you need the code to run client side? If so, you don't. You can write aiming code that works just fine on the server - the only reason the player has code running on the client side is because that's where player input (i.e. mouse and keyboard) comes from, so it makes sense to say 'hey client, where is the player's mouse pointing right now?'.

 

In the context of a non-player entity, however, that doesn't make much sense. Much better to use the server side values for the entity to determine if it can attack the player or not, using the entity's look vector to determine what it is looking at, geometry based on the entity's rotation vs. the player's position, or even just proximity as most vanilla mobs do.

Posted

Its not just the aiming however but ok ill try youre suggestion and maybe see if i can only use server side values

Okay, so what other reasons do you have for wanting your code to run on the client side?

 

Keep in mind that the client is ONLY responsible for things like rendering the world, displaying GUIs on the screen, and taking input from the player and sending it to the server. Some methods run on both sides to make the interaction smoother, but the server is the final arbiter of the outcome.

 

E.g. when you (a player) attack an entity, the client also runs the code so that the player can immediately see the visible result of their action (the attacked entity appearing to take damage, the sword losing durability, etc.) without waiting for a response from the server, but that is only for visuals - the server is the one that actually determines if the entity takes damage, how much, etc., and the sword's new durability, etc. The client side values are irrelevant except for that immediate feedback to the player.

 

Look at every other mob's AI / attack code and you will see it only runs on the server. At most, the entity will send a packet to notify the client that it is attacking so that it can perform an animation or spawn particles, nothing more.

 

LivingAttackEvent, LivingHurtEvent, etc. all only fire on the server side, with the sole exception of when the aggressor is an EntityPlayer, in which case some of those events fire on both sides.

 

My point is that even if you allow the code to run on both sides, it sounds like you are using it wrong. 'Aiming' only occurs due to player input on the client - every other entity determines what it can strike on the server.

 

If you have animations or particles that you want to happen, use #setEntityState on the server at the appropriate time to send a custom flag to #handleHealthUpdate - open up the call hierarchy of both methods to see how they are used in many entities, e.g. EntityIronGolem, EntityVillager, etc.

Posted

One of the main reasons really is so I dont have to rewrite a ton of logic that works currently if it is called client and server side for the player. My attacks recalculate an effective distance based on a ray trace (that bit isnt shown but ray trace needs to be client side only), they can "affect blocks" ie set blocks on fire, or spawn ice blocks to freeze things... etc etc and they also have a few visual effects. The aiming code works for me atm I like the ray trace since it lets me control the max distance,  etc etc... But out of curiosity in case I do feel like rewriting a bunch of stuff how would you suggest I do the aiming bit

Posted

Ray tracing is available on the server side as well...

MovingObjectPosition mop = world.rayTraceBlocks(vec3OriginPosition, vec3TargetPosition);

Where both arguments are a Vec3, the first being the point of origin, and the second being some location off in the distance you are trying to reach - if the mop is null, there is nothing in the way; otherwise it will have information about the first block encountered between the two positions.

 

Like I mentioned above, you don't want any of your logic that is responsible for affecting the world to run on the client - this includes setting blocks on fire, spawning blocks, etc. Do ALL of that on the server.

 

For your visuals, if you don't need them to be super precise, you can do what the Explosion class does and allow the code to run on the client, too, for the sole purpose of spawning particles. The affected block positions will not necessarily be identical to what the server decides was affected, so there can/will be some mismatch. If you need the positions to be exact, then you will probably need to send a packet with each or all of the affected positions to notify the client where to spawn the particles.

 

Note how the above solution is 'server affects blocks, sends notification to client' rather than 'client determines affected blocks, sends list to server'. That is always the flow you should aim for.

 

As for targeting code, what exactly are you trying to do? What I mean is, non-player entities don't aim, they target whatever you tell them to target, so I'm not sure what problem you are trying to solve here. Do you only want your entity to be able to attack players in front of it? Are you having issues finding a player to attack? I need more information.

Posted

Currently i got my entity to "use the attack item" this works, it spawns a stream of particles infront of it and anything within the calculated radius of effect (directly in front of my entity) will take damage. that is working. But when my mob rotates the "aiming" for the attack is incorrect. currently the particles are spawned along one set direction and the attack happens in that direction only. Im looking for a way to get my attack to follow the entities rotation. Just like for my player, if my player rotates the attack follows his look vector and the particles get spawned along that vector. Currently this is what I am trying to fix. I am going to implement a targeting AI later that will determine the ttack frequency and all of that. but for now I just made it so the attack pulses as so.

 

@Override
 public void onLivingUpdate(){
	super.onLivingUpdate();

	////////////////////////The pulsing code///////////////////////////////////////////////////
	if(!this.worldObj.isRemote){

		if(attackInUse == null && this.ticksExisted % 20 == 0){
			Attack att = (Attack)  attacks[0].getItem();
			att.onItemRightClick( attacks[0], this.worldObj, this);
		} else {
			if(this.ticksExisted % 20 == 0){
				attackInUse = null;
			}
		    }
	}
	///////////////////////////////////////////////////////////////////////////
        if (this.attackInUse != null)
        {
        	ItemStack itemstack = this.getCurrentAttack();

            if (itemstack == this.attackInUse)
            {
            	
            	if (attackInUseCount <= 0)
                {
            		this.onAttackUseFinish();
                }
                else
                {
                	
                	if(attackInUse.getItem() instanceof PWItem){
                		
                		PWItem attack = (PWItem) attackInUse.getItem();
                		attack.onUsingTick(attackInUse, this, attackInUseCount);
                	}
                	                      
                    if (--this.attackInUseCount == 0 && !this.worldObj.isRemote)
                    {
                    	this.onAttackUseFinish();
                    }
                }
            }
            else
            {            	
                this.clearAttackInUse();
            }
            
        }
        /////////////////////////////////////+

 

This bit of code is the part I need to change since my method never gets called on client

if(customEntity.worldObj.isRemote){
		System.out.println("NEVER CALLED");
		MovingObjectPosition mop = customEntity.rayTrace(15, 1F);
		if (mop != null && !customEntity.worldObj.isAirBlock(mop.blockX, mop.blockY, mop.blockZ) && (mop.typeOfHit == MovingObjectPosition.MovingObjectType.BLOCK || mop.typeOfHit == MovingObjectPosition.MovingObjectType.ENTITY)) {
			PacketOverlord.sendToServer(new PacketUpdateOnUsingTickTargetCoord(mop.blockX, mop.blockY, mop.blockZ));
		}
	}

 

it is located inside my onUsingTick method. Basically I need the look vector of the entities model so that when the model rotates inside its collision box the attack also rotates accordingly.

Posted

I cant seem to just be able to use the entities look vector coordinates directly to determine my attack coordinates since the look vector coordinates are all really really small values like

 X is -1.2246468525851679E-16 Y is 0.0 Z is 1.0 

That is the output I get from

 

inside my onUsingTick method

System.out.println(" X is "+customEntity.getLookVec().xCoord + " Y is "+ customEntity.getLookVec().yCoord + " Z is "+customEntity.getLookVec().zCoord)

Posted

Entity#getLookVec returns a normalized vector, which means the 'length' of the vector should equal 1. X, Y, and Z should all be values between -1.0F and 1.0F, i.e. values on the unit circle.

 

The look vector is determined using the entity's head rotation, not their body rotation - these are separate values. If you want to use the entity's body rotation to determine the path of the attack, you will need to copy the Entity#getLook method and swap in this.rotationYaw in place of this.rotationYawHead:

// from Entity#getLook(float) when the parameter = 1.0F, which is what is called from #getLookVec
return this.getVectorForRotation(this.rotationPitch, this.rotationYawHead); // note it uses HEAD yaw

Using #getVectorForRotation with the entity's body rotation should work, I would imagine.

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.

×
×
  • Create New...

Important Information

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