Jump to content

Recommended Posts

Posted

Even after I register my tile entity, classToNameMap comes up dry. What obvious thing am I missing?

 

Console Output:

[17:30:28] [server thread/INFO]: Saving and pausing game...
[17:30:28] [server thread/INFO]: Saving chunks for level 'Blower Test'/Overworld
[17:30:29] [server thread/ERROR] [FML]: A TileEntity type jrfinventions.classBlowerTEClient has throw an exception trying to write state. It will not persist. Report this to the mod author
java.lang.RuntimeException: class jrfinventions.classBlowerTEClient is missing a mapping! This is a bug!
at net.minecraft.tileentity.TileEntity.writeToNBT(TileEntity.java:94) ~[TileEntity.class:?]
at jrfinventions.classBlowerTE.writeToNBT(classBlowerTE.java:52) ~[classBlowerTE.class:?]
at net.minecraft.world.chunk.storage.AnvilChunkLoader.writeChunkToNBT(AnvilChunkLoader.java:410) [AnvilChunkLoader.class:?]
at net.minecraft.world.chunk.storage.AnvilChunkLoader.saveChunk(AnvilChunkLoader.java:193) [AnvilChunkLoader.class:?]
at net.minecraft.world.gen.ChunkProviderServer.saveChunkData(ChunkProviderServer.java:266) [ChunkProviderServer.class:?]
at net.minecraft.world.gen.ChunkProviderServer.saveChunks(ChunkProviderServer.java:332) [ChunkProviderServer.class:?]
at net.minecraft.world.WorldServer.saveAllChunks(WorldServer.java:976) [WorldServer.class:?]
at net.minecraft.server.MinecraftServer.saveAllWorlds(MinecraftServer.java:419) [MinecraftServer.class:?]
at net.minecraft.server.integrated.IntegratedServer.tick(IntegratedServer.java:147) [integratedServer.class:?]
at net.minecraft.server.MinecraftServer.run(MinecraftServer.java:540) [MinecraftServer.class:?]
at java.lang.Thread.run(Unknown Source) [?:1.7.0_55]
[17:30:29] [server thread/INFO]: Saving chunks for level 'Blower Test'/Nether
[17:30:29] [server thread/INFO]: Saving chunks for level 'Blower Test'/The End
[17:30:31] [server thread/INFO]: Stopping server
[17:30:31] [server thread/INFO]: Saving players
[17:30:31] [server thread/INFO]: Saving worlds
[17:30:31] [server thread/INFO]: Saving chunks for level 'Blower Test'/Overworld
[17:30:31] [server thread/INFO]: Saving chunks for level 'Blower Test'/Nether
[17:30:31] [server thread/INFO]: Saving chunks for level 'Blower Test'/The End

 

The throw at line 94:

    public void writeToNBT(NBTTagCompound compound)
    {
        String s = (String)classToNameMap.get(this.getClass());

        if (s == null)
        {
            throw new RuntimeException(this.getClass() + " is missing a mapping! This is a bug!");

 

The mapping:

public class classBlockBlower extends BlockDirectional implements ITileEntityProvider {

  private boolean firstTime = true;                                // TODO: Remove debug var

// public static final PropertyDirection FACING = PropertyDirection.create ("facing");
  public static final PropertyInteger FANSPEED = PropertyInteger.create ("fanspeed", 0, 3);

  public static final int tickPeriod = 10;                  // Update block once every x ticks (at 20 ticks per second)
  protected final String mod;
  protected boolean clean;                                  // Debug: Send one tick warning

  public classBlockBlower(String n) {
    super (Material.iron);

    this.setDefaultState (this.blockState.getBaseState ().withProperty (FACING, EnumFacing.NORTH));
    this.setUnlocalizedName (n);                            // Used in regBlock
    this.setStepSound (soundTypeMetal);
    this.setHardness (0.5F);
    this.setCreativeTab (CreativeTabs.tabRedstone);         // May be superseded by regBlock
    classInventionsMod.regBlock (this);                     // Not ticking until speed fixed when placed.
    mod = classInventionsMod.MODID;
    GameRegistry.registerTileEntity (classBlowerTE.class, "classBlowerTE");
  }
...
}

 

I've stepped through in the debugger, and the registerTileEntity call (which adds to the map) is made before the classToNameMap.get. I am bamboozeled. What am I missing?

The debugger is a powerful and necessary tool in any IDE, so learn how to use it. You'll be able to tell us more and get better help here if you investigate your runtime problems in the debugger before posting.

Posted

Thanks, the debugger must have shown me the server doing its registration and then the client blowing up. I'm still wrapping my head around running client-server in the debugger, not always aware of which is which when I hit a break. I'll make sure that the client-side TE class is what gets registered in the client.

 

Yes, my naming convention is archaic. I like to keep classes very distinct from instances and variables so I instantly know what I am seeing wherever any are used to refer to static methods and members.

 

My tile entity is following the pattern of proxies, with the client-only TE making a continuous custom sound using classes that can't even be imported server-side. I couldn't exactly follow the minecart's sound example because it depends on World/WorldClient which doesn't even know I exist. I needed to create my own class that has a client version to do that. It starts the sound at itself during setPosition. I programmed the sound to terminate itself (donePlaying=true) when its TE becomes invalid during removal.

The debugger is a powerful and necessary tool in any IDE, so learn how to use it. You'll be able to tell us more and get better help here if you investigate your runtime problems in the debugger before posting.

Posted

It sounds like you are making things way more complicated than they need to be. Classes such as TileEntities that are included in both server and client jars can and do use @SideOnly(Side.CLIENT) methods, they just need to segregate them smartly.

 

In your case, you can simply check if the world is remote before doing any of your client-side sound stuff, or if you're really worried about it, funnel the call through your proxy via a NON-static method. Static methods and variables are typically design flaws, so if you find yourself using them a lot (which it sounds like you are), you may want to rethink how you're doing things.

 

Anyways, you most definitely should NOT have different classes for the same TileEntity depending on which side you expect it to be on. Not only is there no reason to ever do that, but Minecraft isn't set up to handle it. It's just asking for trouble.

Posted

It sounds like you are making things way more complicated than they need to be.

 

If I were developing inside minecraft, I might imitate minecart programming by having World class start my continuous sound for me. Alas, I don't have that option, so I must work around it and create something else that has World's server and client versions. Right now that's my TE class, but it may end up being a new proxy for my TE to call.

Classes such as TileEntities that are included in both server and client jars can and do use @SideOnly(Side.CLIENT) methods, they just need to segregate them smartly.

 

I am up against more than methods. I am facing a client-only class (PositionedSound), and that prevents me from even having its import statement on the server side.

 

In your case, you can simply check if the world is remote before doing any of your client-side sound stuff

 

That would be fine for execution, but it won't save my program when the import statement on the server throws a class-not-found exception.

 

or... funnel the call through your proxy

 

I thought about that, but then this mod would no longer be able to re-use the proxy-setup code I developed for my earlier mods. Rather than monkeying with that setup, I opted to add what is in effect a 2nd proxy pair.

 

Static methods and variables are typically design flaws, so if you find yourself using them a lot (which it sounds like you are), you may want to rethink how you're doing things.

 

Don't worry, I only use them where they make sense (or where Minecraft uses them). I mentioned static references as part of the reason for my idiosyncratic class naming -- I like to remind myself that a class is a class, not an instance. And if static references are uncommon, then I need the reminder even more.

 

Anyways, you most definitely should NOT have different classes for the same TileEntity depending on which side you expect it to be on. Not only is there no reason to ever do that, but Minecraft isn't set up to handle it. It's just asking for trouble.

 

When I find that trouble, I'll create a special proxy just for my TE to call. The TE class will again be unified, and a new pair of classes will handle server/client divergence. I'll also add a note here to tell future adventurers how much trouble I found. Thanks for the advance warning.

The debugger is a powerful and necessary tool in any IDE, so learn how to use it. You'll be able to tell us more and get better help here if you investigate your runtime problems in the debugger before posting.

Posted
Imports are a compiler fiction. The JVM does not deal with imports and neither does anyone else but javac. Everything past one of the very early compilation steps always deals with fully qualified names.

OK, Eclipse's fiction (and error message) must have tricked me. Some time ago I tried to simply bracket my sided method calls so they'd only execute on the correct side, but their mere presence caused problems when loading. That means that the "world is remote" suggestion (someone else's) was not sufficient.

 

I thought about that, but then this mod would no longer be able to re-use the proxy-setup code I developed for my earlier mods.

That... sounds like a horrible "proxy setup" whatever that may be, if you cannot easily add to it...

It's actually quite useful for giving me a great head start on each mod. The things needed in every mod are already written, so I can jump ahead to the "meat" of each mod. It's also not the source of my problem, so I won't belabor it.

 

You actually hit the nail on the head by noticing that it was the client-side TE that wasn't registered. When I registered that class on the client using the same string used with the server class on the server side, the tile entities worked as intended. Thanks.

 

This is what my TE handling ended up being:

 

BlockBlower.createNewTileEntity:

public class BlockBlower extends BlockDirectional implements ITileEntityProvider {

<snip>

  @Override
  public TileEntity createNewTileEntity(World worldIn, int meta) {
    BlowerTE te;                           // TE needed to hold continuous (whirring) positioned sound
    // Consequently, TE's purpose is to emit *all* block sounds
    if (JRFmod.side.isServer ()) {         // TE follows pattern of proxy common/client
      te = new BlowerTE ();
    } else {                                    // client-side
      ClassLoader mcl = Loader.instance ().getModClassLoader ();
      try {                                     // Reflection
        Class c = Class.forName (InventionsMod.MODID + ".BlowerTEClient", true, mcl);
        te = (BlowerTE) c.newInstance ();
      } catch (Exception e) {
        System.out.println ("BlockBlower failed to instantiate its Client TE because " + e.toString ());
        throw new LoaderException (e);
      }
    }
    return te;
  }
}

 

Server (common) tile entity:

public class BlowerTE extends TileEntity {

  protected static final String mod = InventionsMod.MODID;
  protected float speed = 0.0f;             // float used as pitch

//  public BlowerTE() {}

  public void setPos(BlockPos pos) {        // Vanilla calls immediately after construction
    super.setPos (pos);                     // Set TE pos
    this.pos.add (0.5f, 0.5f, 0.5f);        // Our purpose is sound, so center the floats
  }

  protected void playSFX(World w, String sndName) {
    w.playSoundEffect (pos.getX (), pos.getY (), pos.getZ (), mod + ':' + sndName, 1.0f, this.speed);
  }

  public void setSpeed(int speed) {
    this.speed = speed;                     // Integer converts to float
  }

  public void pwrUp(World w, int speed) {
    this.setSpeed (speed);                  // First set speed
    playSFX (w, "fanPwrUp");                // Then play extra sound effect
  }

  public void pwrDn(World w) {
    playSFX (w, "fanPwrDn");                // Sound
    this.setSpeed (0);                   // then zero
  }

  public void choke(World w) {
    playSFX (w, "fanChoke");                // First play sound
    this.pwrDn (w);                         // Then power down
  }

  @Override
  public void writeToNBT(NBTTagCompound compound) {
    super.writeToNBT (compound);
    compound.setFloat ("speed", speed);
  }

  @Override
  public void readFromNBT(NBTTagCompound compound) {
    super.readFromNBT (compound);
    this.speed = compound.getFloat ("speed");
  }
}

 

Client tile entity:

@SideOnly(Side.CLIENT)
public class BlowerTEClient extends BlowerTE {

// protected WorldClient w;
  static final SoundHandler sh = Minecraft.getMinecraft ().getSoundHandler ();
  private final PositionedSoundFan snd;
  private boolean playing = false;

  public BlowerTEClient() {
    snd = new PositionedSoundFan (this);       // Snd holds "this" to follow speed changes and eventual removal
  }

  public void setPos(BlockPos pos) {                // Vanilla calls immediately after construction
    super.setPos (pos);                             // Set TE pos
    snd.setPos ();                                  // Sound copies TE position
    if (!playing) {
      sh.playSound (snd);                           // Activate our tickable sound (at pos), which will update itself
      snd.update ();                                // Immediately adjust to fan speed
      playing = true;
    }
  }
}

The debugger is a powerful and necessary tool in any IDE, so learn how to use it. You'll be able to tell us more and get better help here if you investigate your runtime problems in the debugger before posting.

Posted

@jeffryfisher I play lots of sounds in my mod and I've NEVER had to do anything nearly as complicated as you are making it to get them working. As diesiben and I have both said: use your proxy.

 

Checking if the world is remote is usually sufficient, even when calling @SideOnly(Side.CLIENT) methods from within your class. If you try to create a reference to a client-only class, however, that's usually where you run into problems, and in your case it's the reference to the SoundHandler* that is ruining your day. Put it in your proxy, and then combine your TileEntity classes into one.

 

* SideOnly classes cannot be inline-instantiated unless as members of a class that is only present on that side, i.e. also has the @SideOnly annotation. By instantiating it inline, you force the code to run on both sides, causing it to fail on the one where the class isn't present.

 

This is the same issue people had when declaring multiple IIcons for their items and blocks, e.g. IIcon[] icons = new IIcon[4]; causes crashes, but just declaring it WITH the annotation, and then initializing it in the client-side only registerIcons method, works fine, and no one needed to create separate client- and server- versions of their Blocks and Items.

Posted

The JVM will try to load all classes it needs, and if a class is referenced at all inside a method and that method is executed, that class will be loaded. Hence @SidedProxy exists...

And that reflective class loading crap is horrible. Please, for the love of god, just make a call into your proxy for things like this.

 

That reflective class loading "crap" is only what @SidedProxy does in the background for common/client proxies. It was even crappier in an earlier version that had a BlockPos parameter in the constructor.

 

I'll look into the distinction between "side" and "isRemote". I've seen it mentioned in many threads, so it must be a common cause of confusion.

 

I'm already planning an upgrade to my abstract mod classes, so I'll see about adding a generalized method to start playing a custom PositionedSound.

The debugger is a powerful and necessary tool in any IDE, so learn how to use it. You'll be able to tell us more and get better help here if you investigate your runtime problems in the debugger before posting.

Posted
If you try to create a reference to a client-only class, however, that's usually where you run into problems, and in your case it's the reference to the SoundHandler* that is ruining your day.

 

That, and one of my sounds extends the side-only PositionedSound class in order to create a repeatable, continuous sound (I was told to imitate the minecart). PositionedSound is essentially a renderer for sound. I'm glad I only needed to set a couple predefined fields. After taking a peek lower down, I hope I never feel a need to dive in.

 

Someday, in a perfect future, the World class will have a method for starting a continuous sound by passing in its resource name and the TileEntity (containing position) to which it is anchored. Vanilla code and its messaging will take care of the rest.

The debugger is a powerful and necessary tool in any IDE, so learn how to use it. You'll be able to tell us more and get better help here if you investigate your runtime problems in the debugger before posting.

Posted
I'll look into the distinction between "side" and "isRemote". I've seen it mentioned in many threads, so it must be a common cause of confusion.

 

isRemote (a property) is used when you're performing logic updates, where the server has precedence and sends updates to the client.

 

side (an annotation) is used when you want to remove a method, class, or field entirely when it is loaded on the other side.

Apparently I'm a complete and utter jerk and come to this forum just like to make fun of people, be confrontational, and make your personal life miserable.  If you think this is the case, JUST REPORT ME.  Otherwise you're just going to get reported when you reply to my posts and point it out, because odds are, I was trying to be nice.

 

Exception: If you do not understand Java, I WILL NOT HELP YOU and your thread will get locked.

 

DO NOT PM ME WITH PROBLEMS. No help will be given.

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.