Lab 8: Chameleon Robot

Objective

In lab 8, you will create an entity that disguises itself as a neighboring block when it is damaged (by anything). After a specific amount of hiding time, it will convert back to a normal entity and walk away as if nothing happened.

Notes

To add a new task with a specific priority

tasks.addTask (priority, EntityAIBase);

To spawn an entity in game

  1. While in the world, hit the "t" key and on the bottom left of the screen you should see a cursor.
  2. Type "/robot entity" and replace "entity" with the name of the registered entity. For this lab, it would be "/robot chameleon".

Robot Damage Events

void Robot.onEntityDamage (DamageSource source, float amount)

The above inherited Robot function is called every time your robot entity receives any damage. You won't care about the source or amount of damage; it just matters that the chameleon bot received some kind of damage.

Controlling entity visibility

entity.setInvisible(true);  // Hide entity entity.setInvisible(false); // Show entity

Getting and setting entity position and orientation

double positionX = robot.posX; // Entity position as double X,Y,Z double positionY = robot.posY; double positionZ = robot.posZ; entity.setLocationAndAngles ( double x, // X Coordinate Position double y, // Y Coordinate Position double z, // Z Coordinate Position float yaw, // Yaw Angle (left-right direction) float pitch // Pitch Angle (up-down) );

Getting and setting blocks in the world

World world = entity.worldObj; // Get the entity's world // Get the block at a given world position Block block = world.getBlock (positionX, positionY, positionZ); // Get the block metadata for a block at a given world position int blockMetadata = world.getBlockMetadata (positionX, positionY, positionZ); // Set a block at a given position in the world world.setBlock ( double positionX, // X Coordinate Position double positionY, // Y Coordinate Position double positionZ, // Z Coordinate Position Block block); // Block Object // Set block metadata at a given position in the world world.setBlockMetadataWithNotify ( positionX, positionY, positionZ, blockMetadata, 0);

If you're going to read a block and then later restore it, you'll need to get the block metadata as well as the block object itself (using the above functions).

System Time

long System.currentTimeMillis()

The above method gets the number of milliseconds since January 1, 1970. You can use this value to figure out the current time. For our purposes, where we need elapsed time, you will only care about the difference between timestamps.

For example: long timeThreeSecondsFromNow = (long)(3.0 * 1000) + System.currentTimeMillis(); boolean threeSecondsElapsed = System.currentTimeMillis() > timeThreeSecondsFromNow;

Tasks

Part 1 — Create an entity that wanders around the world

  1. Start by creating a new class called ChameleonBot that extends the Robot class.
  2. Implement the primary constructor. This takes in a net.minecraft.world.World parameter and calls the super constructor with the world parameter.
  3. Inside the constructor, add the EntityAIWander task with priority 1. When you create the new EntityAIWander task, pass in a reference to this new ChameleonBot (this) and the value SPEED_SLOW.
  4. Inside the EntitiesModule class, register the ChameleonBot. Use the name "chameleon", and paint it with the "rainbow_robot" texture.

  5. Test point. Launch the game, go into a world, and summon the chameleon bot. It should wander around slowly.

Part 2 — Modify the entity to convert into a block when harmed

  1. Create a new class called EntityAIDisguise that extends EntityAIBase (net.minecraft.entity.ai.EntityAIBase).
  2. In the EntityAIDisguise class, create a private field of type ChameleonBot named robot.
  3. Create a constructor that takes in a ChameleonBot parameter and assign that value to the private robot field.
  4. Implement the following stub methods:
    • public void startExecuting()
    • public boolean shouldExecute()
    • public void resetTask()
    • public boolean continueExecuting()
  5. For now, just return true from shouldExecute() and continueExecuting().
  6. Now back to the ChameleonBot class. You need to add the EntityAIDisguise task to your chameleon bot. You will also want to create a private field to keep a reference to this task for later. Add this task with priority 2.
  7. Time to test. Launch the game and summon a new chameleon bot. It should still just wander around slowly.

Part 3 — when damaged, the chameleon bot turns into a log

  1. First off, we need to add a routine to our chameleon bot called hasFreshDamage(). It returns true only if the robot just received fresh damage and this is the first time hasFreshDamage() was called since then. In other words, if you damage chameleon bot, then the first call to hasFreshDamage() will return true, and every other call after that (until the next damage event) should return false.
  2. Here's the general flow of the EntityAIDisguise task:
    1. shouldExecute() returns true if the robot has received fresh damage.
    2. If shouldExecute() returns true, then the game will automatically call startExecuting(), then updateTask(), and then continueExecuting() until continueExecuting() returns false.
    3. The startExecuting() task needs to do the work to disguise the robot.
    4. We don't need to implement the updateTask() function; it does nothing for this task.
    5. The continueExecuting() function will check the time. If the disguise period is done, it returns false (stop executing). If the robot is still hiding, then return true to keep checking the time.
  3. Implement the shouldExecute() function. This will use the robot's hasFreshDamage() function you just wrote.
  4. For part 3, the robot will just turn into a log forever when it receives damage (super powers don't come all at once). There are two steps to this:

    1. First, hide the robot. This part's easy. Just set the robot to invisible, using the setInvisible() function from the notes above.
    2. Secondly, we'll need to place a log block where the robot used to be. Get the robot's position, convert to the nearest integer coordinates, and set the block at that position to Blocks.log.
  5. Test it out. Launch the game, summon a chameleon bot, and smack it. It should turn into a log and remain that way.

Part 4 — Chameleon bot turns back into a chameleon bot after five seconds

  1. Now let's have the chameleon bot turn back after hiding for five seconds. To do this, we need to store the time we'll stop hiding. In the EntityAIDisguise task, create a private field of type long that holds the hiding end time. It's also good coding style to give all of your numerical constants descriptive names when possible, so create another private static final long constant named MILLIS_PER_SECOND, which holds the number of milliseconds per second. Use this constant when calculating your hiding end time.
  2. We want to restore the robot to its old location, so also save away its X, Y, and Z position coordinates. Though an entities position coordinates are normally double-precision floating-point values, you will need to save these as world integer coordinates. To assist in this conversion, create a private helper function named round that takes a double and returns a rounded int. Math.round() will make this task easy.
  3. Place a Blocks.log block at the robot's old location.
  4. The continueExecuting() function should return true as long as the robot is still in hiding time. When hiding time is over, continueExecuting() should return false.
  5. Now we'll start filling in resetTask(), which will be called when continueExecuting() returns false. In this method, set the block at the hide position to Blocks.air, and make the robot visible again.
  6. Now when you test your chameleon bot, it will hide as a log for five seconds, and then the log will disappear and the robot will become visible again.

Part 5 — Chameleon bot blends in with surrounding blocks

Now the chameleon bot will look around itself to figure out which block type it should choose to use to hide itself, so that it blends in with the surrounding scenery.

  1. In the startExecuting() function, calculate the robot hide position (nearest integer world coordinates) as before.
  2. Now have the chameleon look around to find a block to mimic (in the startExecuting() function). It will use that nearby block type as its disguise.
  3. If the robot's hiding position is hideX, hideY and hideZ, then you'll look at neighbors like hideX - 1, hideY + 1, and hideZ. Since we'll skip the block exactly at the robot's world coordinates (hideX, hideY, hideZ), that leaves 26 neighboring blocks total to examine. Find one that isn't Blocks.air — that will be the one we impersonate.

    There are several ways to examine all 26 neighboring blocks. You could use a two-dimensional array of offsets (-1, 0, +1) and then just check each of those offsets in a single loop. Or you could create a triply-nested for loop, which computes offsets in X, Y and Z. The implementation is up to you.

  4. When checking neighboring blocks, if the current block is Blocks.air, then skip this block and continue looking at neighbors.
  5. If you find a neighboring block that isn't air, then perform the following steps:

    1. Save the neighbor's block and block metadata so we can restore it later (see notes).
    2. If you found a good neighbor block to impersonate, you need to replace the block at the robot's position, so save off that block and its metadata as well.
    3. Set the robot to invisible.
    4. Set the block (and its metadata) at the robot's position to the neighbor block information you want to impersonate.
    5. Once you're done setting up the imposter block, you can return from the startExecuting() function.
  6. If you make it out of the search loop(s) without finding any neighbor blocks that aren't air, then the chameleon bot has nothing to mimic, so it can't disguise itself. In this case, set the hiding end time to the current time, and set the replaced block field to null.
  7. In the resetTask() function, you'll only restore the hiding position to the original block if the chameleon bot was able to hide (if the replacedBlock field isn't null). In this case, set the imposter block back to its original value, and also restore its original metadata.
  8. Finally (in resetTask()), set the chameleon bot's position to its original hiding position, and set its yaw and pitch angles to 0 (see notes). After that make the robot visible again.
  9. That's it! Perform a final test of your robot. When you damage it, it should change into a block of the same type as the surrounding landscape. After five seconds, the imposter block should disappear, the original landscape block should be restored, and the chameleon bot should reappear in the original location.