Tuesday, August 18, 2015

Magnum Torch

A magnum torch is an Extra Utilities block which prevents the spawning of hostile mobs within a certain space around it. How? Like this:

 public static boolean isInRangeOfTorch(Entity entity)
  {
    for (int[] coord : magnumTorchRegistry) {
      if ((coord[0] == entity.field_70170_p.field_73011_w.field_76574_g) &&
        (entity.field_70170_p.func_72899_e(coord[1], coord[2], coord[3])) && ((entity.field_70170_p.func_147438_o(coord[1], coord[2], coord[3]) instanceof IAntiMobTorch)))
      {
        TileEntity tile = entity.field_70170_p.func_147438_o(coord[1], coord[2], coord[3]);
        double dx = tile.field_145851_c + 0.5F - entity.field_70165_t;
        double dy = tile.field_145848_d + 0.5F - entity.field_70163_u;
        double dz = tile.field_145849_e + 0.5F - entity.field_70161_v;
        if ((dx * dx + dz * dz) / ((IAntiMobTorch)tile).getHorizontalTorchRangeSquared() + dy * dy / ((IAntiMobTorch)tile).getVerticalTorchRangeSquared() <= 1.0D) {
          return true;
        }
      }
    }
    return false;
  }


This is a method which supports both the chandelier and magnum torch. Each of those has the getHorizontalTorchRangeSquared and getVerticalTorchRangeSquared methods. It doesn't matter how this method gets called, or what the obfuscated functions are. The important bit is that line which works with dx, dy, and dz. Those are obviously the distance from the torch to the entity in question (the mob trying to spawn). The formula looks like this:

\[\frac{\Delta x ^2+\Delta z^2}{h^2}+\frac{\Delta y^2}{v^2} \le 1 \]

Rearranging slightly, we see the formula for the surface and interior of an ellipsoid:


\[\frac{\Delta x ^2}{h^2}+\frac{\Delta z^2}{h^2}+\frac{\Delta y^2}{v^2} \le 1 \]

The semi-axes in the horizontal direction are both \(h\), while the semi-axis in the vertical direction is \(v\). Looking at the implementation of these blocks we see:

public float getHorizontalTorchRangeSquared()
  {
    if ((func_145838_q() instanceof BlockMagnumTorch)) {
      return 16384.0F;
    }
    if ((func_145838_q() instanceof BlockChandelier)) {
      return 256.0F;
    }
    return -1.0F;
  }
 
  public float getVerticalTorchRangeSquared()
  {
    if ((func_145838_q() instanceof BlockMagnumTorch)) {
      return 1024.0F;
    }
    if ((func_145838_q() instanceof BlockChandelier)) {
      return 256.0F;
    }
    return -1.0F;
  }
 


So, the magnum torch clears a spheroid around itself with an equatorial radius of 128 meters and a polar radius of 32 meters. Similarly the chandelier clears a sphere around itself of radius 16 meters.

Monday, August 3, 2015

Gem of the Once-In-A-While: Ragel FSM generator

Yukari 3 ran a hand-built state machine to parse NMEA sentences coming from the GPS. It worked, but it was hard to maintain. I went looking for a tool that was a better solution. I was looking for something like a C++ template library that implements a state machine by abusing the template processor. What I found was Ragel.

This a nice, simple, clean program for generating finite state machines and weaving them into your programs. It does so in a way which I vastly prefer over Lex. It's main drawback is that since it is not a template library, it does require an external program to handle. Well, so do several other parts of my system. I use Perl without embarassment, so I will use Ragel similarly.

Ragel is structured very similarly to Yacc, in that it creates a program which executes user-chosen actions when a certain sentence form is encountered. However, it only handles part 1 languages, IE regular languages.

Ragel takes as input a source file written primarily in C/C++ (other languages are supported, but I don't use them) but which is marked with certain Ragel blocks. These blocks are used to define the machine and actions. The actions are written in the main language (C/C++ as I use it) and Ragel builds code which executes the state machine on an input stream and executes your actions as they come up.

It demonstrates a couple of interesting ideas, one of which is that you can just make up a new language and mix it into your existing source code.

Certain of the Ragel commands indicate where the state machine's tables should be written, where the state machine code should go, etc. Interfacing to the scanner is pretty simple.

A couple of examples below the fold: