PART THIRTEEN
WATER
CONCEPT
Expanding Gameplay Choice
Water isn't too interesting in our program. Let's expand ways for the user to make meaningful choices related to it:
We need to fill the bucket at a water tile
Each day has a chance of rain which waters all soil tiles
Players can purchase sprinklers which always water adjacent tiles
IMPROVED BUCKET
Updating Bucket
Add variables to track water and capacity and relevant accessors
Write a method called fill() that...
Sets water to the bucket's capacity
Sets the image to a full bucket
Write a method called use() that...
Decreases water by one
If water runs out, sets the image to an empty bucket
Filling The Bucket
In class Water
If the water is clicked by a bucket and the user has stamina
call the bucket's fill method
Expend stamina
Hint: You'll need to do an explicit cast
Using The Bucket
In class Dirt
Check if the bucket actually has water in it before changing soil
Add a line to call the bucket's use method at the appropriate time
Updating the Cursor
Currently the ItemBar manages our cursor changes, as that was the only way a cursor could change. However, you may notice that with this code we can run out of water without selecting an item via keypress.
The easiest solution to this is to modify ItemBar to have a mousePressed method. We'll give it all the proper parameters because later on we may actually allow the user to click on our bar.
In Game, we'll call this method *after* we update the world.
This allows us to call setCursor() each time the mouse is pressed, updating it on every click event.
In ItemBar
In Game
CHECKPOINT
Run your code and see...
Test the bucket extensively. Try and break your game. Can you water crops with a full bucket? Is the water capacity of your bucket accurate to your intended value?
RAIN
Creating Weather
Create a new subpackage of world called weather.
Create class Weather. It should contain:
A boolean variable called raining
An accessor method called isRaining()
A mutator method called nextDay(). When called, it has a 20% chance of rain and a 80% chance of clear skies. It should never rain in the first two days.
A render method that simply displays text that states if the program is RAINING or CLEAR.
This lets us focus on functionality before we add cool effects.
Adding Weather To Your World
In class World
Declare and initialize a static variable of class Weather named weather
Create a public static boolean method called isRaining() which uses the isRaining() method from the weather object.
Call weather's nextDay() method at the before calling nextDay on entities or cells.
Call weather's render() method after drawing everything else
Making The Soil Wet
In class Dirt
Write a method called setWeatherWet(). In this method, if it is raining set wet to true and false otherwise. By using this method we can reduce duplication.
In the constructor, call setWeatherWet().
In the nextDay() method, call setWeatherWet()
CHECKPOINT
Run your code and see...
Check and see if it is sometimes clear and sometimes raining.
Check that soil is wet when it is raining and dry otherwise
Check that this works properly even with newly created soil from tilling dirt that day
Rain Animation
In the Weather class, create a rain animation that displays when it is raining.
You may use supplemental classes which can be stored in the Weather class.
Don't get too sidetracked - this can always be improved later on!
Tips
Use transparency to create a dark overlay
Create a class to represent a single raindrop
Consider white or very light blue gradient lines
If you want to get fancy, give raindrops a chance to "hit the ground" and create circles which expand outward then disappear
White lines (above), Gradient Lines with splash zones (below)
BASIC SPRINKLER
Adding Functionality to the World
Sprinklers will automatically water adjacent tiles. To add this functionality, we'll need to update a few other parts of our code before adding them into our game.
In class World, write the following method:
public static Cell getCell(int x, int y)
This method returns the specified cell if it is bounds and null otherwise
In class Cell, write the following methods:
public ArrayList<Cell> addCellToList(ArrayList<Cell> list, int x, int y)
Uses World to get the cell at the specified location
If it is not null, add it to the given list
Return the list
public ArrayList<Cell> getFourNeighbors()
Returns an ArrayList containing the four orthagonal neighbors (north, south, east, and west). This method should call addCellToList.
In class Dirt
Write the following method
public void waterSoil()
Sets wet to true and calls setImage()
(above) Stardew Valley Sprinklers
By teaching our cells to detect neighbors, we can easily add sprinklers with different radius of effect.
Implementing Sprinklers
Create an abstract class for Sprinkler (extends Entity) and a subclass called BasicSprinkler.
In class Sprinkler, add the following methods:
public boolean isValid()
Always returns true
public void clicked()
No code in this method for now
public void waterCells(ArrayList<Cell> neighbors)
Waters each cell that is passed to the method
In class BasicSprinkler, implement a constructor and a method
public BasicSprinkler()
Sets an image
public void nextDay()
Using methods we've written, waters four adjacent cells
This can be done in a single line of code
In class World
Modify nextDay() to have a more specific order of operations that splits the activation time of crops and sprinklers.
First, apply it to weather
Then all crops
Then all cells
Finally, all sprinklers
Add a BasicSprinkler at the start of the programming for testing purposes.
Splitting up the entities can be implemented in two ways. You choose!
Option #1 - Loop through entities twice, using instanceof to decide which are permitted to call nextDay() each time
Option #2 - Rework code to seperately track Sprinklers and Crops each time they are added and removed in addition to a general list of entities, then loop through those respective lists.
CHECKPOINT
Run your code and see...
You program should have a functional sprinkler that makes the ground next to it wet every day.
BUYING AND PLACING SPRINKLERS
Sprinkler Items
Much like a Crop, we can't just add a Sprinkler to our Shop since it is an Entity and not an Item. We'll need to create a parallel item in the Shop, following what we did with the Seed and CropSeed classes.
A clever observer might notice that we'll need to replicate the system for makeCrop() with Sprinklers, and possibly other things too. We should have a better system for having any Item make a corresponding Entity.
In class CropSeed
Remove the code for the makeCrop method()
In class Item, write a the abstract method
public abstract Entity makeEntity()
In class Tool
Tools don't ever need to make entities, so we can set a simple default
Implement makeEntity(), returning null
In classes CornSeed and PotatoSeed
Update the return type and name of makeCrop() to match makeEntity()
In class Dirt
Update the reference to makeCrop() to makeEntity()
Make abstract class SprinklerItem
Make class BasicSprinklerItem
Sets initial data and implements makeEntity()
Tip: While you're in the item package, this may be a good time to divide up item into subpackages for organization.
In this part of the program, we're starting to see some limits of our program's core design.
(1) We've split items and entities up entirely, so we end up with duplicate classes sometimes. This made sense with seeds. But there's no way to simply put the same item in the shop then place it directly into the world. This is something you'd want to consider for future projects. Perhaps both of these classes should have a super class which handles some generic functionality.
(2) Early on we added a specific function to seeds that allowed them to place crops. Here we are taking time to refactor that code. Notice how much harder it is to make all these little changes than places where it was carefully mapped out from the start.
The big lesson here is to carefully plan your program's structure early on!
Shop and Usage
In class Shop
Add a BasicSprinklerItem to the list of items.
In class Cell, in the clicked() method
Add code to place the Sprinkler if the cell does not have another entity.
Make sure the item expires after being placed
Be careful that your logic does not mess up existing code!
This should only apply to SprinklerItems, not Seeds
It does not expend stamina
CHECKPOINT
Run your code and see...
Check that you can purchase a BasicSprinkler
Check that you can place a BasicSprinkler and that it is removed from your Itembar once placed
Test that all other tools and seeds still work properly
ADVANCED SPRINKLER
The Advanced Sprinkler
The Advanced Sprinkler affects up to eight cells
It should cost about 2.5 times as much as a BasicSprinkler, since it saves space and is easier to use
Implement the AdvancedSprinkler fully following the process we walked through with the BasicSprinkler
As an extension, you could:
Add in a MegaSprinkler, which affects an even larger radius of cells.
Add in a system to unlock more powerful Sprinklers as the game goes on, rather than having it available from the start.
CHECKPOINT
Run your code and see...
Test that the user can purchase, place, and fully utilize the Advanced Sprinkler