Objective
In lab 9, you will create a robot that wanders around the map looking for loose items like gold or diamond and collecting them. If the entity takes any damage (either by wandering into a bad situation, getting attacked by other entities, or getting attacked by other players), it drops all of its items and stumbles around for twenty seconds until it has recovered enough to continue collecting stuff. While it's dazed, nearby players then have a chance to grab the collector bot's loot before it comes to its senses.
Notes
To Spawn An Entity In Game
- Type "
/robot entity
", where entity is the name of the registered robot. - For this lab, it would be "/robot collector"
To Add a New Task with a Specific Priority
To Get A List<Entity> Of All Entities In An Entity's World
Each item in this list is of type Entity
.
Robot Navigation (Pathfinding)
For the float speed
parameter, use Robot.SPEED_FAST
,
Robot.SPEED_NORMAL
, or Robot.SPEED_SLOW
.
Getting Distance Between Two Entities
Adding & Removing Items In The World
Managing EntityItem
's and ItemStack
.
Hash Map Methods
Robot Damage Events
This method is called whenever the robot receives any kind of damage. You won't care about the type or amount of damage, but you will drop the robot's inventory and start a stun cycle whenever it happens.
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 — Stub In The New CollectorBot
Entity
- Start by creating a new class called
CollectorBot
that extends theRobot
class. Implement the default constructor that takes in anet.minecraft.world.World
object as its parameter. Call the super constructor with that world parameter. - In the constructor, add the
EntityAIWander
task with priority 1. When you create theEntityAIWander
task, pass in this new collector bot and a speed ofSPEED_NORMAL
. - Add the new robot to the
EntitiesModule
class. Register theCollectorBot
with a name of "collector", and a texture of "gold_robot". - Start up the game and issue the command "/robot collector". You should see a gold-colored robot that wanders around the map and does nothing else.
Part 2 — Stub In A New EntityAICollectLoot
Task
- Create a new class called
EntityAICollectLoot
that extendsEntityAIBase
. - The constructor takes a
CollectorBot
and afloat
search radius. Store both of these values in member variables of the new task. - Override the methods
public boolean shouldExecute()
andpublic boolean continueExecuting()
. The collection task will run all the time at first, so both of these methods should just return true. - Stub in the override method
public void updateTask()
. For now, just return immediately. - In the
CollectorBot
constructor, create a newEntityAICollectLoot
task. The collect-loot task's constructor will take the new collector bot's reference and a search radius. Use a search radius of 10. You'll need to have access to this task later on, so store a reference to it in a local class member variable. Finally, add this task with priority 2. - Verify your code so far by running the game and issuing the command "/robot
collector". You should see a gold-colored robot that still just wanders around the
map and does nothing else (since the
EntityAICollectLoot
task doesn't yet do anything).
Part 3 — Implement Basic CollectorBot
Functionality
Now it's time to implement the basic loot searching and collection task.
We want the robot to find the nearest piece of loot
(net.minecraft.entity.item.EntityItem
) within its search radius, make its way
to that item, and then pick it up.
- In the
EntityAICollectLoot
task class, we now need to implement the first phase ofupdateTask()
. To begin with, we'll first need a task class variable to track the currently targeted piece of loot: call ittargetLoot
. Loot items wil be of typeEntityItem
. Initialize it tonull
to begin with. - The
updateTask()
method has two parts: in the first part, the task is to choose a piece of loot if we don't currently have one targeted. In the second part, when we have a piece of loot chosen, the task is to move to and collect that item. - Create a function to select the next piece of loot. It will only be used by the
EntityAICollectLoot
task, so it should be private. It will return the selectedEntityItem
, and doesn't need any arguments. Call itselectNewTarget
. -
In the
selectNewTarget
function, get a list of all entities in the collector bot's world. You will then iterate through this list, and find the entity that matches the following criteria:- it must be an instance of
EntityItem
, - it must not be marked "dead" (
item.isDead
), and - it must be within the search radius of the collector bot's location.
- it must be the single closest entity
(Note that items are marked dead when when they are scheduled to be deleted or are destroyed by another entity.)
- it must be an instance of
- If you find an item that matches the above criteria, then set that item as the target loot, and return the new target.
- At the beginning of
updateTask()
, if we don't currently have a target loot item, get a new piece of target loot using theselectNewTarget()
function. If we couldn't find a new target item that meets our criteria, then return fromupdateTask()
. - After we've made sure that we have an item as our target loot, check to see if anything
has marked it as dead. If so, stop the robot's navigation, set
targetLoot
to null, and return fromupdateTask
. - Move towards the loot target at normal speed.
- If the collector bot is within a distance of 1.5 from the target loot, then it can pick it up. Stop the robot navigation, remove the target loot from the world, and set the targetLoot to null.
-
Test out the collector bot. To do this, load items in your inventory that you can drop for the collector bot to find. When you bring up the inventory in game, you can click on an item multiple times to create a "stack" of items (you'll see a count over the selected item). Drag this stack of items to an inventory slot, and hit escape to get back to the game. In game, select the item slot you want, and then drop these items by pressing the 'q' key. Now spawn the collector bot with the "/robot collector" command. It should run around and gather up the items you dropped.
Part 4 — Track items in the robot's inventory
Up to now, the robot really just removes items from the world; it just looks like it's been picking them up. Now we need to actually keep track of the items that the robot has gathered. We'll need this for the next part, where the robot drops all the items it has collected.
- The first task is to add an item inventory to the
CollectorBot
. We'll use aHashMap<Item,Integer>
for this. Individual items will be the keys for this inventory, and the values will be the total count for each item. Create a class member variable for this (name itinventory
), and initialize it to a new HashMap. -
Add a new
CollectorBot
method with the following signature:public void addItem (EntityItem entityItem) - In the new
addItem
function, as outlined in the notes section, you'll get the item stack from the given entityItem. From that, you'll get the item and item count. Compute the new total count for that item type. You will need to handle the case where the robot currently has no items of that type, and the case where it already has some number of those items. Once you've computed the new total, put that entry in theinventory
hash map. - It may help to create a small function to print the current robot inventory (using
System.out.printf()
) as you develop this part. See the notes section for a way to examine the contents of a hash map, and the tip for getting a printableItemName for a given item. You can use this for your inventory print routine, which you can then call every time you add a new item. - Now that you have a way to add and track items to the robot's inventory, we need to
have the
EntityAICollectLoot
task call this method when we pick up new loot. Do this inupdateTask()
, when we determine that we're within range of our target loot. Add the call torobot.addItem
when we pick up the target loot. - Is everything implemented correctly? Run the game to find out. Make sure that the robot picks up items. If you implemented the debug print function, verify that the inventory is growing as you expect.
Part 5 — Drop all items when the robot is damaged
Now we'll add the behavior that the robot drops all of its inventory whenever it receives any damage.
-
First, we'll provide an override of the
CollectorBot
'sonEntityDamage
method. The signature for this method ispublic void onEntityDamage (DamageSource source, float amount); The implementation of this method is simple: just call a new
CollectorBot
methoddropInventory()
with no arguments. - Now we need to implement
private void dropInventory()
. This method needs to iterate through all items in its inventory, and drop the correct count of each item. See the notes section fordropItem
. Once all items have been dropped, clear the robot's inventory. The notes section has the information you need on how to clear a hash map. - After dropping the robot's inventory, and if you implemented the
printInventory
debug method, you may want to call this to verify that nothing is left in the robot's inventory. - Time to test. Run the game, toss out a bunch of items for the robot to collect, and then smack it. You will probably see that the robot turns red when you hit it, but otherwise nothing else will look different. In particular, there won't be a bunch of objects laying around the robot. What happened?
- The
CollectorBot
is very industrious. Even if it properly dropped all of its inventory on the ground, theEntityAICollectLoot
task is still running, so it will just immediately think "look at all this loot!", and pick it right back up. You can keep thwackingCollectorBot
upside the head, and it will keep dropping and immediately picking back up all of its loot until it keels over, keeping everything it gathered. Now what?
Part 6 — CollectorBot stops collecting items when damaged
The key to making CollectorBot
more useful is adding a delay
before it picks up all items it dropped. We'll have the robot wander around stunned for a
while before the its loot collection task resumes. That will complete our
CollectorBot
implementation.
- In
EntityAICollectLoot
, we're going to add support for suspending the collection task for a period of time. We'll be modifying theshouldExecute
andcontinueExecuting
tasks to do this. Before we do that, we'll need a new private long field namedsuspendEnt
before we start. Initialize it to zero to start with. This field will store the time at which task suspension is over. - Reference the notes section to review system time methods.
-
Add a new method with the following signature:
public void suspend (float suspendSeconds) This method will take the specified number of seconds and the current time in milliseconds, and compute the time when the task suspension should end (also in milliseconds). Store that result in
suspendEnd
. - If
suspendEnd
is zero, then we are not suspended. Given that, update thecontinueExecuting
method to return true only if we're not currently suspended. - Update the
shouldExecute
method. Given the current system time and the suspendEnd variable, determine if we're still in the suspend time period. If we are still suspended, then return false (we should not execute the task). If we are either not suspended or are now past the suspension time period, setsuspendEnd
to zero to indicate that the collection task is not suspended, and return true (we should execute our main task). - Back in the
CollectorBot
class, and using the code we just wrote above, suspend the collection task for 20 seconds when we take any damage. - Time for the final test. Run the game, and feed the collector bot a bunch of items. Now smack the bot (from a distance), and you should notice that it drops all of its loot, and doesn't pick it back up. (Note that if you're too close, your player will end up picking up the items, which is why you want a bit of distance before striking the robot.) Wait 20 seconds, and verify that the robot starts picking things up again.
Part 7 — Fit & Finish
Take a look at all of your code. You've added a bunch, changed a bunch, and probably wrestled a bit with different parts. How can you make the code cleaner? Easier to read? Are you using magic numbers where you should instead have used class constants? Is your code well documented? Review the program logic and see if you can accomplish the same tasks in simpler ways. How much code can you simplify or reduce? Re-order your methods so that reviewers and other programmers can follow the logic easier. Are there places where you could use a variable to hold a common result? Are your variable names clear? These are just a few things to look at, but make a huge difference in your final result.