-
Notifications
You must be signed in to change notification settings - Fork 9
Tool
See also StructureToolPicker.
Tool
is an abstract class which can be inherited to create a new tool for the StructureToolPicker
. Tool
stores the cost associated with the tool. This could be a charge per use, a charge per structure placed, or any other charge in line with the requirements of the tool. It also defined an abstract function interact(Entity player, GridPoint2 position)
which is called by StructureToolPicker
when the tool is selected and interacted with. Tool
also stores a range, which limits the range from the player entity in which interact may be called and succeed.
To create a new tool, you should first include and inherit from Tool
. You must implement the abstract interact function in this subclass. You must also implement a public constructor, although this can just call the parents constructor.
include com.csse3200.game.components.structures.tools.Tool;
public class ExampleTool extends Tool {
protected ExampleTool(ObjectMap<String, Integer> cost, float range, String texture, int ordering) {
super(cost, range, texture, ordering);
}
/**
* Returns whether the player can interact at the given position.
* @param player - the player attempting to interact.
* @param position - the position to interact.
* @return whether the player can interact at the position.
*/
protected ToolResponse canInteract(Entity player, GridPoint2 position) {
// implement conditional logic here
return ToolResponse.valid();
}
/**
* Peforms the tools interaction at the given position.
* @param player - the player using the tool.
* @param position - the position to use the tool.
*/
@Override
protected abstract void performInteraction(Entity player, GridPoint2 position);
// implement interact logic here
}
}
Once a new tool has been implemented, you must add it to the structure_tools.json
config file in order to access it via the StructureToolPicker
. Instructions on how to do this can be found here.
Here is an example of a tool within the game. The HealTool is used to heal entities.
public class HealTool extends Tool {
...
public HealTool(ObjectMap<String, Integer> cost, float range, String texture, int ordering) {
super(cost, range, texture, ordering);
}
@Override
protected ToolResponse canInteract(Entity player, GridPoint2 position) {
var validity = super.canInteract(player, position);
if (!validity.isValid()) {
return validity;
}
// For checking whether the player has clicked on an entity
Entity clickedEntity = determineSelectedEntity(position);
// If no entity is clicked, return false
if (clickedEntity == null) {
return new ToolResponse(PlacementValidity.INVALID, "No structure to heal");
}
// For checking whether the clicked entity has a CombatStatsComponent
CombatStatsComponent combatStats = clickedEntity.getComponent(CombatStatsComponent.class);
// If the clicked entity does not have a CombatStatsComponent, return false
if (combatStats == null || combatStats.getMaxHealth() == 0) {
return new ToolResponse(PlacementValidity.INVALID, "Cannot be healed");
}
int healAmount = combatStats.getMaxHealth() - combatStats.getHealth();
float healPercent = (float) healAmount / combatStats.getMaxHealth();
if (healPercent == 0) {
return new ToolResponse(PlacementValidity.INVALID, "Already full health");
}
// Calculate the healing cost based on the current health deficit'
requiredResources = new HashMap<>();
// retrieve cost of component
var costComponent = clickedEntity.getComponent(CostComponent.class);
// if placed with tool, this should never evaluate to true, however better safe than sorry ;)
if (costComponent == null) {
return new ToolResponse(PlacementValidity.INVALID, "Cannot be healed");
}
for (var costEntry : costComponent.getCost()) {
requiredResources.put(Resource.valueOf(costEntry.key), (int)Math.ceil(costEntry.value * healPercent));
}
// Check if the player has enough resources for healing
if (!playerHasEnoughResources(requiredResources)) {
return new ToolResponse(PlacementValidity.INVALID, "Not enough resources");
}
return ToolResponse.valid();
}
@Override
protected void performInteraction(Entity player, GridPoint2 position) {// Deduct the required resources
deductResources(requiredResources);
Entity clickedEntity = determineSelectedEntity(position);
CombatStatsComponent combatStats = clickedEntity.getComponent(CombatStatsComponent.class);
// For setting the health of the clicked entity to 100
combatStats.setHealth(combatStats.getMaxHealth());
}
...
}
Some additional abstract classes inheriting from Tool have been implemented to implement repeated logic across multiple tools.
PlacementTool
is an abstract Tool
class which implements the performInteraction
and canInteract
methods to place a structure at the interacted position.
/**
* Places the structure returned by createStructure() at the given position.
*
* @param player - the player interacting with the tool.
* @param position - the position to place the structure.
*/
@Override
protected void performInteraction(Entity player, GridPoint2 position) {
PlaceableEntity newStructure = createStructure(player);
newStructure.addComponent(new CostComponent(cost));
ServiceLocator.getStructurePlacementService().placeStructureAt(newStructure, position);
}
/**
* Returns ToolResponse.valid() if the position is not occupied and the player
* has insufficient resources, otherwise returns an invalid response.
*
* @param player - the player attempting to interact.
* @param position - the position to interact.
* @return whether the position can be interacted with.
*/
@Override
protected ToolResponse canInteract(Entity player, GridPoint2 position) {
var validity = super.canInteract(player, position);
if (!validity.isValid()) {
return validity;
}
var positionValidity = isPositionValid(position);
if (!positionValidity.isValid()) {
return positionValidity;
}
var resourceValidity = hasEnoughResources();
if (!resourceValidity.isValid()) {
return resourceValidity;
}
var entity = createStructure(player);
var component = entity.getComponent(JoinableComponent.class);
if (component != null) {
var canPlace = component.canPlaceAt(position);
if (!canPlace) {
return new ToolResponse(PlacementValidity.INVALID_POSITION, "Invalid structure join");
}
}
return ToolResponse.valid();
}
In turn, it instead requires you to implement the abstract method createStructure(Entity player)
in a base class. This method should return a new instance of the structure to place. See this implementation in the BasicWallTool as an example.
@Override
public PlaceableEntity createStructure(Entity player) {
return BuildablesFactory.createWall(WallType.basic, player);
}
PlacementTool
is an abstract PlacementTool
class which override the performInteraction
and isPositionValid
methods to place a structure at the interacted position.
/**
* If a structure exists at the given position, replaces it, otherwise if the tool
* is configured to allow structures to be placed without replacement,
* places the new structure at the given position.
*
* @param player - the player interacting with the tool.
* @param position - the position to place the structure.
*/
@Override
public void performInteraction(Entity player, GridPoint2 position) {
PlaceableEntity newStructure = createStructure(player);
newStructure.addComponent(new CostComponent(cost));
// get the left-most and bottom-most position of the structure to replace
var existingStructure = structurePlacementService.getStructureAt(position);
if (existingStructure != null) {
var placePosition = structurePlacementService.getStructurePosition(existingStructure);
ServiceLocator.getStructurePlacementService().replaceStructureAt(newStructure, placePosition);
} else if (!mustReplace) {
ServiceLocator.getStructurePlacementService().placeStructureAt(newStructure, position);
}
}
/**
* Checks whether the structure can be placed at the given position.
* This only returns an invalid ToolResponse if the tool is configured to
* require a structure to replace.
*
* @param position - the position the structure is trying to be placed at.
* @return whether the structure can be placed at the given position.
*/
@Override
public ToolResponse isPositionValid(GridPoint2 position) {
if (!mustReplace) {
return ToolResponse.valid();
}
// can only place if clicked on a structure
var existingStructure = structurePlacementService.getStructureAt(position);
return existingStructure != null ? ToolResponse.valid()
: new ToolResponse(PlacementValidity.INVALID_POSITION, "No structure to replace");
}
To utilise the class, you must extend it and implement the createStructure
method.
/**
* Creates the structure to be placed. This must be implemented in subclasses to function.
*
* @param player - the player placing the structure.
* @return the structure to be placed.
*/
public abstract PlaceableEntity createStructure(Entity player);
Below is a UML class diagram showing all the different tools in the game and how they all relate to eachother.
classDiagram
class Comparable {
<<interface>>
}
class Tool {
<<abstract>>
cost : ObjectMap
-ordering : int
-range : float
-texture : String
Tool(cost : ObjectMap, range : float, texture : String, ordering : int)
+ interact(player : Entity, position : GridPoint2)
performInteraction(player : Entity, position : GridPoint2)
canInteract(player : Entity, position : GridPoint2) ToolResponse
+ getCost() ObjectMap
+ getTexture() String
+ getOrdering() int
+ equals(obj : Object) boolean
+ compareTo(o : Tool) int
}
Comparable <|.. Tool
class PlacementTool {
<<abstract>>
snapX : int
snapY : int
PlacementTool(cost : ObjectMap, range : float, texture : String, ordering : int)
+ interact(player : Entity, position : GridPoint2)
performInteraction(player : Entity, position : GridPoint2)
canInteract(player : Entity, position : GridPoint2) ToolResponse
createStructure(player : Entity) PlaceableEntity
+ getSnapPosition(position : GridPoint2) GridPoint2
+ isPositionValid(position : GridPoint2) ToolResponse
+ hasEnoughResources() ToolResponse
}
Tool <|-- PlacementTool
class HealTool {
+HealTool(cost : ObjectMap, range : float, texture : String, ordering : int)
performInteraction(player : Entity, position : GridPoint2)
canInteract(player : Entity, position : GridPoint2) ToolResponse
- playerHasEnoughResources(requiredResources : Map) boolean
- deductResources(requiredResources : map)
}
Tool <|-- HealTool
class ReplacementTool {
<<abstract>>
-mustReplace : boolean
ReplacementTool(cost : ObjectMap, range : float, texture : String, ordering : int)
ReplacementTool(cost : ObjectMap, range : float, texture : String, ordering : int, mustReplace : boolean)
performInteraction(player : Entity, position : GridPoint2)
canInteract(player : Entity, position : GridPoint2) ToolResponse
createStructure(player : Entity) PlaceableEntity
+ isPositionValid(position : GridPoint2) ToolResponse
}
PlacementTool <|-- ReplacementTool
class BasicWallTool {
+BasicWallTool(cost : ObjectMap, range : float, texture : String, ordering : int)
+createStructure(player : Entity) PlaceableEntity
}
PlacementTool <|-- BasicWallTool
class GateTool {
+GateTool(cost : ObjectMap, range : float, texture : String, ordering : int)
+createStructure(player : Entity) PlaceableEntity
}
PlacementTool <|-- GateTool
class TurretTool {
+TurretTool(cost : ObjectMap, range : float, texture : String, ordering : int)
+createStructure(player : Entity) PlaceableEntity
}
PlacementTool <|-- TurretTool
class AdvTurretTool {
+AdvTurretTool(cost : ObjectMap, range : float, texture : String, ordering : int)
+createStructure(player : Entity) PlaceableEntity
}
PlacementTool <|-- AdvTurretTool
class LandmineTool {
+AdvTurretTool(cost : ObjectMap, range : float, texture : String, ordering : int)
+createStructure(player : Entity) PlaceableEntity
}
PlacementTool <|-- LandmineTool
class ExplosiveBarrelTool {
+AdvTurretTool(cost : ObjectMap, range : float, texture : String, ordering : int)
+createStructure(player : Entity) PlaceableEntity
}
PlacementTool <|-- ExplosiveBarrelTool
class IntermediateWallTool {
+IntermediateWallTool(cost : ObjectMap, range : float, texture : String, ordering : int)
+createStructure(player : Entity) PlaceableEntity
+ isPositionValid(position : GridPoint2) ToolResponse
}
ReplacementTool <|-- IntermediateWallTool
class ExtractorTool {
+IntermediateWallTool(cost : ObjectMap, range : float, texture : String, ordering : int)
+createStructure(player : Entity) PlaceableEntity
+ isPositionValid(position : GridPoint2) ToolResponse
}
ReplacementTool <|-- ExtractorTool
From this diagram, you can see the root Tool
class being inherited by every other tool, and then the specialisation classes also being inherited by a subset of tools.
Escape Earth Game
Interaction Controller and Interactable Components
Game and Entity Configuration Files
Loading Game Configuration Files