Skip to content
Rowan Gray edited this page Oct 19, 2023 · 6 revisions

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.

Usage

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.

Example

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());
    }

    ...

}

Specialised Abstract Classes

Some additional abstract classes inheriting from Tool have been implemented to implement repeated logic across multiple tools.

PlacementTool

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);
    }

ReplacementTool

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);

Current Tools

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
Loading

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.

Clone this wiki locally