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
- While in the world, hit the "t" key and on the bottom left of the screen you should see a cursor.
- Type "/robot entity" and replace "entity" with the name of the registered entity. For this lab, it would be "/robot chameleon".
Robot Damage Events
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
Getting and setting entity position and orientation
Getting and setting blocks in the world
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
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:
Tasks
Part 1 — Create an entity that wanders around the world
- Start by creating a new class called
ChameleonBot
that extends theRobot
class. - Implement the primary constructor. This takes in a
net.minecraft.world.World
parameter and calls thesuper
constructor with the world parameter. - Inside the constructor, add the
EntityAIWander
task with priority 1. When you create the newEntityAIWander
task, pass in a reference to this newChameleonBot
(this
) and the valueSPEED_SLOW
. Inside the
EntitiesModule
class, register theChameleonBot
. Use the name "chameleon", and paint it with the "rainbow_robot" texture.- 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
- Create a new class called
EntityAIDisguise
that extendsEntityAIBase
(net.minecraft.entity.ai.EntityAIBase
). - In the
EntityAIDisguise
class, create a private field of typeChameleonBot
named robot. - Create a constructor that takes in a
ChameleonBot
parameter and assign that value to the privaterobot
field. - Implement the following stub methods:
public void startExecuting()
public boolean shouldExecute()
public void resetTask()
public boolean continueExecuting()
- For now, just return
true
fromshouldExecute()
andcontinueExecuting()
. - Now back to the
ChameleonBot
class. You need to add theEntityAIDisguise
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. - 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
- First off, we need to add a routine to our chameleon bot called
hasFreshDamage()
. It returnstrue
only if the robot just received fresh damage and this is the first timehasFreshDamage()
was called since then. In other words, if you damage chameleon bot, then the first call tohasFreshDamage()
will returntrue
, and every other call after that (until the next damage event) should returnfalse
. - Here's the general flow of the
EntityAIDisguise
task:shouldExecute()
returnstrue
if the robot has received fresh damage.- If
shouldExecute()
returnstrue
, then the game will automatically callstartExecuting()
, thenupdateTask()
, and thencontinueExecuting()
untilcontinueExecuting()
returnsfalse
. - The
startExecuting()
task needs to do the work to disguise the robot. - We don't need to implement the
updateTask()
function; it does nothing for this task. - 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 returntrue
to keep checking the time.
- Implement the
shouldExecute()
function. This will use the robot'shasFreshDamage()
function you just wrote. 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:
- First, hide the robot. This part's easy. Just set the robot to invisible, using the
setInvisible()
function from the notes above. - 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
.
- First, hide the robot. This part's easy. Just set the robot to invisible, using the
- 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
- 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 typelong
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 anotherprivate static final long
constant namedMILLIS_PER_SECOND
, which holds the number of milliseconds per second. Use this constant when calculating your hiding end time. - 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 adouble
and returns a roundedint
.Math.round()
will make this task easy. - Place a
Blocks.log
block at the robot's old location. - The
continueExecuting()
function should returntrue
as long as the robot is still in hiding time. When hiding time is over,continueExecuting()
should return false. - Now we'll start filling in
resetTask()
, which will be called whencontinueExecuting()
returns false. In this method, set the block at the hide position toBlocks.air
, and make the robot visible again. - 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.
- In the
startExecuting()
function, calculate the robot hide position (nearest integer world coordinates) as before. - 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. -
If the robot's hiding position is
hideX
,hideY
andhideZ
, then you'll look at neighbors likehideX - 1
,hideY + 1
, andhideZ
. 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'tBlocks.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.
- When checking neighboring blocks, if the current block is
Blocks.air
, then skip this block and continue looking at neighbors. If you find a neighboring block that isn't air, then perform the following steps:
- Save the neighbor's block and block metadata so we can restore it later (see notes).
- 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.
- Set the robot to invisible.
- Set the block (and its metadata) at the robot's position to the neighbor block information you want to impersonate.
- Once you're done setting up the imposter block, you can return from the
startExecuting()
function.
- 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.
- 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. - 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. - 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.