Jump to content

JoeStrout

Members
  • Posts

    39
  • Joined

  • Last visited

Posts posted by JoeStrout

  1. I too would like to know how to create custom particles.  Recent posts here have covered how not to do it (don't subclass ParticleCloud), but I haven't been able to find any docs at all on how one should do it.

    The official docs, under "Effects," have only a page for Sounds — nothing at all for particles that I can see.

    So.  How do you create a custom particle in the latest Forge?

     

  2. Well dang.  So I guess it's server-to-client packets for me then, since in this case the controlling/triggering logic really is on the server.

     

    Funny how building up modding skills/code feels a lot like playing Minecraft itself... I think I'm at about the stone-pickaxe stage now, but already dreaming of diamond gear.  :)

     

  3. I keep getting advice to do as much as possible on the server — which makes good sense, since the server (I thought) has the definitive copy of what's going on in the world, can notify all relevant players of the change, etc.

     

    But just now I have found that changing the player rotation (by, for example, player.rotationYaw = 0 where player is a PlayerEntity) works on the client but does not work on the server.  Grr.  Argh.

     

    What am I missing?  Surely there's some way the server can set a player's position/rotation, and have this both stick and propagate to all relevant clients?  Is there some extra call I should do to alert the server code to the change?

     

  4. Wow, I guess I had that all wrong.  But it explains a lot.

     

    But if they're spawned client-side... doesn't that mean that other players do not see them?

     

    Supposing I do want the particles to be seen by all clients, I guess that means I either get the non-overriding WorldServer versions, or send my own packets to all nearby clients to spawn their own particles?

  5. I've got server code spawning a bunch of particles in front of the player, but I see nothing.  What am I missing?

     

    Here's the code (caster is an EntityPlayer):

    			World world = caster.world;
    			Vec3d pos = ModUtils.PointAhead(caster, 1.5F);
    			Vec3d lookDir = caster.getLookVec();
    			for (int i = 0; i < 32; i++) {
    				Vec3d v = new Vec3d(lookDir.x + MathHelper.nextDouble(random, -0.5, 0.5),
    						lookDir.y + MathHelper.nextDouble(random, -0.5, 0.5),
    						lookDir.z + MathHelper.nextDouble(random, -0.5, 0.5));
    				v = v.scale(2);
    				world.spawnAlwaysVisibleParticle(EnumParticleTypes.SPELL_MOB.getParticleID(), 
    						pos.x, pos.y, pos.z, 
    						v.x, v.y, v.z, // velocity
    						// (optional particle arguments go here... maybe color?)
    						255, 0, 0);
    				System.out.println("Spawned particle at " + pos + " with velocity " + v);
    			}
    		}

     

     

    And this causes messages like the following to appear in the log:

    [08:49:09] [Server thread/INFO] [STDOUT]: [net.strout.wizarding.SpellEffects$Sparks:DoEffect:131]: Spawned particle at (-621.3208915739522, 64.58865155559033, 346.8873318756315) with velocity (1.2252315947908472, -0.24176556778545355, -0.10210638379011927)
    [08:49:09] [Server thread/INFO] [STDOUT]: [net.strout.wizarding.SpellEffects$Sparks:DoEffect:131]: Spawned particle at (-621.3208915739522, 64.58865155559033, 346.8873318756315) with velocity (1.2878165420981733, 0.6032242196690139, 0.04981358307420081)
    [08:49:09] [Server thread/INFO] [STDOUT]: [net.strout.wizarding.SpellEffects$Sparks:DoEffect:131]: Spawned particle at (-621.3208915739522, 64.58865155559033, 346.8873318756315) with velocity (1.6579566964412826, -0.5821367025405804, 0.6348263056580719)

     

    I have studied these coordinates compared to my position and rotation, and they are correct (1.5 blocks in front of my eyes).  I've used very similar code on the client, and then I do see particles appear.  But of course this should be done on the server, so that all players can see them (right?).

     

    While I'm using spawnAlwaysVisibleParticle in the code above, I've also tried other variations (spawnParticle) with the same result.  And I don't think it's an issue with my graphics settings, since particles spawned on the client do appear.

     

    So... what am I doing wrong?

  6. onImpact() is a common method if you've properly spawned your entity on the server.  I suspect he's spawning the projectile on the client, which will mostly appear to work, but of course isn't really working properly — including, onImpact fires only on the client, leading to the weirdness described above.

     

    I was in a very similar situation this evening, and solved it by sending a network packet to spawn the projectile on the server (described here).  My projectiles and explosions appear to be working correctly now (and by studying the logs, I've verified that both are happening on the server main thread).  Perhaps this will help the OP as well.

     

  7. Thank you.

    In case it's useful to anyone, here's the networking code I came up with (based on the docs) to create an explosion or spawn a projectile on the server.  (Unfortunately the "spawn a projectile" code is not generic — it's tied to a particular class, in this case a custom Throwable subclass called EntitySpell.  But perhaps it will be useful as sample code anyway.)

     

    package net.strout.wizarding;
    
    import io.netty.buffer.ByteBuf;
    import net.minecraft.entity.player.EntityPlayerMP;
    import net.minecraft.util.math.*;
    import net.minecraft.world.WorldServer;
    import net.minecraftforge.fml.common.network.NetworkRegistry;
    import net.minecraftforge.fml.common.network.simpleimpl.*;
    import net.minecraftforge.fml.relauncher.Side;
    
    public class ClientToServerBridge {
    
    	private static final SimpleNetworkWrapper INSTANCE = NetworkRegistry.INSTANCE.newSimpleChannel(ModMain.MODID);
    	private static int nextHandlerRegistrationID = 0;
    	private static boolean initialized;
    	
    	public static class TriggerExplosionMessage implements IMessage {
    		// A default constructor is always required
    		public TriggerExplosionMessage() {}
    
    		private BlockPos position;
    		private float strength;
    		private boolean smoke;
    		
    		public TriggerExplosionMessage(BlockPos position, float strength, boolean smoke) {
    			this.position = position;
    			this.strength = strength;
    			this.smoke = smoke;
    		}
    
    		@Override public void toBytes(ByteBuf buf) {
    			buf.writeInt(position.getX());
    			buf.writeInt(position.getY());
    			buf.writeInt(position.getZ());
    			buf.writeFloat(strength);
    			buf.writeBoolean(smoke);
    		}
    
    		@Override public void fromBytes(ByteBuf buf) {
    			position = new BlockPos(buf.readInt(), buf.readInt(), buf.readInt());
    			strength = buf.readFloat();
    			smoke = buf.readBoolean();
    		}
    	}
    	
    	// The params of the IMessageHandler are <REQUEST, REPLY>
    	// This means that the first param is the packet you are receiving, and the second is the packet you are returning.
    	// The returned packet can be used as a "response" from a sent packet.
    	public static class TriggerExplosionHandler implements IMessageHandler<TriggerExplosionMessage, IMessage> {
    		@Override public IMessage onMessage(TriggerExplosionMessage message, MessageContext ctx) {
    			System.out.println("Received TriggerExplosionMessage");
    			// This is the player the packet was sent to the server from
    			EntityPlayerMP serverPlayer = ctx.getServerHandler().player;
    			// Execute the action on the main server thread by adding it as a scheduled task
    			WorldServer world = serverPlayer.getServerWorld();
    			if (!world.isBlockLoaded(message.position)) return null;
    			System.out.println("Scheduling explosion on main thread");
    			world.addScheduledTask(() -> {
    				System.out.println("Creating explosion");
    				world.createExplosion(serverPlayer, 
    						message.position.getX(),
    						message.position.getY(),
    						message.position.getZ(),
    						message.strength, message.smoke);
    			});
    			// No response packet
    			return null;
    		}
    	}
    	
    	
    	public static class SpawnSpellMessage implements IMessage {
    		// A default constructor is always required
    		public SpawnSpellMessage() {}
    
    		private Vec3d position;
    		private Vec3d direction;
    		
    		public SpawnSpellMessage(Vec3d position, Vec3d direction) {
    			this.position = position;
    			this.direction = direction;
    		}
    
    		@Override public void toBytes(ByteBuf buf) {
    			buf.writeFloat((float)position.x);
    			buf.writeFloat((float)position.y);
    			buf.writeFloat((float)position.z);
    			buf.writeFloat((float)direction.x);
    			buf.writeFloat((float)direction.y);
    			buf.writeFloat((float)direction.z);
    		}
    
    		@Override public void fromBytes(ByteBuf buf) {
    			position = new Vec3d(buf.readFloat(), buf.readFloat(), buf.readFloat());
    			direction = new Vec3d(buf.readFloat(), buf.readFloat(), buf.readFloat());
    		}
    	}
    	
    	// The params of the IMessageHandler are <REQUEST, REPLY>
    	// This means that the first param is the packet you are receiving, and the second is the packet you are returning.
    	// The returned packet can be used as a "response" from a sent packet.
    	public static class SpawnSpellHandler implements IMessageHandler<SpawnSpellMessage, IMessage> {
    		@Override public IMessage onMessage(SpawnSpellMessage message, MessageContext ctx) {
    			System.out.println("Received SpawnSpellMessage");
    			// This is the player the packet was sent to the server from
    			EntityPlayerMP serverPlayer = ctx.getServerHandler().player;
    			// Execute the action on the main server thread by adding it as a scheduled task
    			WorldServer world = serverPlayer.getServerWorld();
    			if (!world.isBlockLoaded(new BlockPos(message.position))) return null;
    			System.out.println("Scheduling spawning on main thread");
    			world.addScheduledTask(() -> {
    				System.out.println("Spawning spell");
    				EntitySpell.Spawn(world, serverPlayer, message.position, message.direction);
    			});
    			// No response packet
    			return null;
    		}
    	}
    	
    	
    	//--------------------------------------------------------------------------------
    	// Public API
    	//--------------------------------------------------------------------------------
    	
    	// Initialize: call this once on startup (for example, in your pre-init handler)
    	// on both client and server.
    	public static void Initialize() {
    		assert(!initialized);
    		INSTANCE.registerMessage(TriggerExplosionHandler.class, TriggerExplosionMessage.class, nextHandlerRegistrationID++, Side.SERVER);
    		INSTANCE.registerMessage(SpawnSpellHandler.class, SpawnSpellMessage.class, nextHandlerRegistrationID++, Side.SERVER);
    		System.out.println("ClientToServer module registered " + nextHandlerRegistrationID + " message handlers");
    		initialized = true;
    	}
    	
    	// TriggerExplosion: call to trigger an explosion on the server from client code.
    	public static void TriggerExplosion(BlockPos position, float strength, boolean smoke) {
    		assert(initialized);
    		System.out.println("Sending TriggerExplosionMessage message to server");
    		INSTANCE.sendToServer(new TriggerExplosionMessage(position, strength, smoke));		
    	}
    	
    	// SpawnSpell: call to spawn an EntitySpell on the server from client code.
    	public static void SpawnSpell(Vec3d position, Vec3d direction) {
    		assert(initialized);
    		System.out.println("Sending SpawnSpell message to server");
    		INSTANCE.sendToServer(new SpawnSpellMessage(position, direction));				
    	}
    }

     

  8. Well as a concrete example of situation 1, I've got some custom mouse-button handling (and yes, I tried the built-in click handlers on the Item class, but (1) they didn't do what I need, and (2) those handlers also run on the client thread).  And in response to that mouse input — which obviously the server doesn't know anything about — I need to spawn a projectile.

    I find it hard to believe that this situation is unique.

    So I'm going to interpret your answer as "no" (there is no built-in support for that in the framework), and "no" (you don't know any standard open-source solution for it).

    No big deal — I've already got the necessary networking code implemented for one case, and it won't take long to add the others.  Just wanted to avoid reinventing the wheel if there was a well-oiled wheel already available.

     

    (But I do hear and respect your point that in most cases, Minecraft is already interpreting all the client input and sending higher-level events to the server, and we should our custom logic on the server in response to those events as much as possible.)

  9. It seems to me there are a couple of common situations that many mod developers may face, with respect to the whole client/server side issue:

    1. You have code on the client side that needs to spawn an entity (which must be done on the server side).
    2. You have code on the client side that wants to trigger an explosion (which should also be done on the server side).
    3. Or more generally, you have code on the client side that wants to modify the world, e.g. change blocks (which also must be done on the server).

    These are sufficiently general and common cases that a library could do it for us — that is, prepare a packet, send it to the server, and have the server catch it and do the appropriate thing.

    Before I start in on such utility code myself, are there any cases where the framework already does something like this for us?  Where?

    If not, are there any common open-source solutions to this already written?

     

  10. Thanks for those tips, that's really helpful — I couldn't find any docs on what all those parameters mean.

    (PointAhead just gets a point directly ahead of where the player is looking.)

     

    ...It looks like that sendVelocityUpdates=false was causing my problem; changing it to true makes everything work.  Hey cool, I have successfully spawned a particle!  :)

     

    Thanks again for your help.

  11. I knew this was purely client-sided — of course, since the server doesn't know anything about mice.  My goal with this exercise was only to properly process the mouse input.  I wouldn't expect that to be possible on the server.

     

    So yes, that means I may soon be asking a questions about how to pass things over to the server.  My understanding is that this usually involves a network packet, though I still cling to a bit of hope that in some cases, such as spawning a projectile, the framework might take care of that for me.

  12. OK, I decided to try skipping all those item-use and right-click Item methods, and instead just subscribe to mouse events.  This seems to work well:

     

    	@SubscribeEvent
    	public static void onMouseEvent(MouseEvent event) {
    		if (event.getButton() != 1) return;
    		EntityPlayerSP player = Minecraft.getMinecraft().player;
    		boolean isWand = (player.getHeldItemMainhand().getItem() == ModMain.itemWand);
    		if (!isWand) return;
    		if (event.isButtonstate()) {
    			// RMB down
    			Minecraft.getMinecraft().player.setActiveHand(EnumHand.MAIN_HAND);
    		} else {
    			// RMB up
    			Minecraft.getMinecraft().player.resetActiveHand();
    			DoBoom();
    		}
    	}

     

    Very simple and appears reliable.  It bothers me a little to have my code executing every time the mouse wiggles, but I make it bail out as quickly as I can, and probably the same amount of work was going on in Item anyway.

     

    If anybody can see a reason why this is not a good approach, please let me know!

  13. OK, thanks @Choonster, that gets me closer.  Right-clicks when I'm not pointed at any nearby block now work correctly.

     

    It turns out I still have a problem when a block is highlighted, though: I get no onItemRightClick call in this case, and onItemUseFirst gets invoked over and over, about 5 times per second while the button is held down.  I have tried returning both EnumActionResult.PASS and EnumActionResult.SUCCESS from both onItemUseFirst and onItemUse; it makes no difference as far as I can tell.

     

    [EDIT: my getMaxItemUseDuration returns 72000.]

     

    I'd be happy to have exactly the same behavior as a bow: regardless of whether a nearby block is highlighted or not, the bow draws back while you hold the right mouse button down, and does its action when you release it.  But so far it's still not clear to me how to do that.

  14. 6 minutes ago, Choonster said:

     

    Have you tried my suggestion?

    EDIT: no, I missed yours somehow and saw @Differentiation's, and then thought you were referring back to that one.

     

    I will catch up and try yours right now.

     

  15. Has anybody seen a weird effect where a spawned throwable, regardless of the velocity set, appears to drop straight down... yet the coordinates seen in onImpact appear to reflect the velocity given?

     

    This is my first attempt to spawn a throwable entity, and for the moment I haven't even defined a proper model for it, so it appears as a white cube.  The white cube appears in front of me where I expected, but then it appears to drop straight down and disappear.  Some short time later, onImpact fires with coordinates that are not straight down.  Here's the code for my Throwable subclass:

     

    package net.strout.wizarding;
    
    import net.minecraft.entity.projectile.EntityThrowable;
    import net.minecraft.util.math.RayTraceResult;
    import net.minecraft.world.World;
    
    public class EntitySpell extends EntityThrowable {
    
        public EntitySpell(World worldIn) {
        		super(worldIn);
        }
    
    	public static String GetRegistryName() {
    		return ModMain.MODID + ":" + "spell";
    	}
    	
    	@Override
    	protected void onImpact(RayTraceResult result) {
    		System.out.println("Spell hit something at " + result.hitVec + " side " + result.sideHit);
    		this.setDead();
    	}
    
    }

     

    Here's where I register it in my main class:

    	@SubscribeEvent
    	public static void registerEntities(RegistryEvent.Register<EntityEntry> event) {
    		ModelResourceLocation res = new ModelResourceLocation(EntitySpell.GetRegistryName(), "entity");
    		EntityEntry entry = EntityEntryBuilder.create()
    			    .entity(EntitySpell.class)
    			    .id(res, 1234)   // ?!?
    			    .name("wizarding_spell")
    			    .tracker(64, 20, false)
    			    .build();
    			event.getRegistry().register(entry);	
    		System.out.println("Registered entity: " + entry.getRegistryName());
    	}

     

    And finally, here's where I spawn it, for now in response to a chat message on the server:

    	@SubscribeEvent
    	public static void onChat(ServerChatEvent event) {
    		String msg = event.getMessage().toLowerCase();
    		// ...
    		if (msg.startsWith("spawn")) {
    			double speed = Double.parseDouble(msg.substring(6));
    			if (speed == 0) speed = 1;
    			WorldServer world = event.getPlayer().getServerWorld();
    			EntitySpell entity = new EntitySpell(world);
    			Vec3d pos = PointAhead(1F);
    			Vec3d dir = event.getPlayer().getLookVec();
    			entity.setPosition(pos.x, pos.y, pos.z);
    			entity.shoot(dir.x, dir.y, dir.z, (float)speed, 0.1F);
    			world.spawnEntity(entity);
    			SendStatus("Spawned spell at " + entity.getPosition() + " with velocity " + entity.motionX + ", " + entity.motionY + ", " + entity.motionZ);
    		}
    	}

     

    When I run this, I see the cube appear and drop straight down, regardless of which way I'm facing or what velocity I specify.  But the console shows stuff like:

    [21:17:23] [Server thread/INFO]: [CHAT] Spawned spell at BlockPos{x=-132, y=102, z=275} with velocity 1.3810391495309446, 1.4458082115081663, 0.03919115940359643
    [21:17:23] [Server thread/INFO]: <Player416> spawn 2
      ...
    [21:17:23] [main/INFO]: [CHAT] <Player416> spawn 2
    [21:17:28] [Server thread/INFO] [STDOUT]: [net.strout.wizarding.EntitySpell:onImpact:20]: Spell hit something at (-47.548026190592275, 92.0, 277.67980515187014) side up

     

    So you can see here that I was facing mostly in the +X direction, and also pitched up (+Y).  And when the thing landed, it had a major change in X and Y, but not Z.

     

    Any idea what I'm missing here?

  16. Sorry, I think I was unclear.  Your code only fires once until the player respawns — so says the comment, and so too would I expect from the code, as nothing ever sets isFirstTimeWeapon2 back to true.

     

    I'm trying to do something every time the right button goes down.  So: click, action fires; continue holding the button, nothing happens.  Release the button and click again, action fires again.

     

    The problem is that I don't know how to detect when the button is released.  I've considered registering for an update event and doing something with that, but it feels so hackish... surely there's a better way.

     

  17. 9 hours ago, Differentiation said:

    What is in this method? Please show the contents.

    Thanks!

    Sure, I'd be delighted to actually be on the helpful end around here for a change.  :)

    	// Get a RayTraceResult describing whatever the player's looking at (within 32 blocks).
    	private static RayTraceResult PointingAt(boolean stopOnLiquid) {
    		WorldClient world = Minecraft.getMinecraft().world;
    		EntityPlayerSP player = Minecraft.getMinecraft().player;
    		Vec3d posVec = new Vec3d(player.posX, player.posY + player.getEyeHeight(), player.posZ);
    		Vec3d lookVec = player.getLookVec();
    		return world.rayTraceBlocks(posVec, posVec.add(lookVec.scale(32)), stopOnLiquid);
    	}

     

    • Like 1
  18. @DragonFerocity, did you ever get this figured out (including the correct texture)?

     

    And @jabelar, any chance you're working on an up-to-date projectile tutorial?  I find your tutorials in general to be exceptionally clear, well-written, and helpful.  But I am more or less completely befuddled at the moment when it comes to projectiles — all the examples and tutorials I'm able to find are for the older system, and don't match the advice I'm finding here at all.

     

    So a tutorial that actually shows how to put all this together into a working 1.12 projectile would be really, really helpful.

  19. I've got a custom Item subclass that I want to do something when the user right-clicks — and I want it to behave the same way regardless of whether there is a block in reach or not.  (I do my own ray-casting to determine whether and where to do the effect.)

     

    So I overrode and logged all the likely methods, and found that if a block is highlighted, I get onItemUseFirst, optionally followed by onItemUse (depending on the status code I return from onItemUseFirst); and if I'm pointed at something further away (or nothing at all), then I get onItemRightClick called repeatedly.  In the onItemRightClick case, there is nothing that's called when the user releases the right mouse button, as far as I can tell.

     

    So there's the trouble: I don't want to trigger my effect over and over while the mouse button is held, but only on the initial mouse-down.  Easy enough if it goes through onItemUseFirst, but in the other case, how do I tell which onItemRightClick calls indicate a "first" call for that click?

     

    Or am I going about it all wrong?

  20. I've been modding for just a few days now, and am starting on my second mod (my first is just a random collection of tests and hacks).  I test by using ./gradlew runClient in the mod project directory (on a Mac, if that matters).  I created the second project by simply duplicating and modifying the first.

     

    So far so good, but when gradlew launches Minecraft, it has both my mods therein.  That'd be really handy if I were working on two or more mods and wanted to test their interactions, but as it happens, I really don't want or need that first one around anymore.  How do I get rid of it?

     

    I tried looking in ~/Library/Application Support/minecraft,  but there is no mods folder there.  I don't entirely understand what gradlew is doing, but I think it's made its own copy of Minecraft with Forge somewhere, and is launching that.  But I can't figure out where that is.  I tried poking around in various folders under my project (including bin), but don't see anything there that looks like my old mod.  Where do I uninstall mods in this test environment, or otherwise reset it so that I get only the mod I'm working on?

     

×
×
  • Create New...

Important Information

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