package squidpony.squidai;
import squidpony.squidmath.Coord;
import squidpony.squidmath.OrderedMap;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Set;
/**
* A simple struct-like class that stores various public fields which describe the targeting properties of a skill,
* spell, tech, or any other game-specific term for a targeted (typically offensive) ability we call a Technique.
*
* The typical usage of a Technique is:
* <ul>
* <li>Construct any AOE implementation the Technique would use (if the Technique affects multiple grid cells).</li>
* <li>Construct the Technique (passing the AOE as a parameter if needed).</li>
* <li>Call setMap() before considering the Technique if it has not been called yet, if the physical map (including
* doors and destructible objects) has changed since setMap() was last called, or simply on every Technique every time
* the map changes if there are few enemies with few Techniques. PERFORMING ANY SUBSEQUENT STEPS WITHOUT SETTING THE
* MAP TO THE CURRENT ACTUAL PHYSICAL MAP WILL HAVE BAD CONSEQUENCES FOR LOGIC AND MAY CAUSE CRASHING BUGS DUE TO
* ARRAY BOUNDS VIOLATIONS IF YOU HAVEN'T SET THE MAP ARRAY IN THE FIRST PLACE. The map should be bounded by wall chars
* ('#'), which is done automatically by squidpony.squidgrid.mapping.DungeonGenerator .</li>
* <li>When the Technique is being considered by an AI, call idealLocations() with the values of targets,
* lesserTargets, and/or priorityTargets set to beings that the AI can see (likely using FOV) and wants to affect with
* this Technique (enemies for offensive Techniques, allies for supporting ones), and requiredExclusions typically set
* to allies for offensive Techniques that can cause friendly-fire damage, or to null for supporting ones or Techniques
* that don't affect allies.</li>
* <li>When an ideal location has been determined from the previous step, and the AI decides (possibly randomly, by an
* aggression ("aggro") level tracked per-enemy, or by weights on Techniques for different situations) to use this
* Technique on a specific target point, call apply() with the user position as a Coord and the chosen Coord, and
* proceed to process the effects of the Technique as fitting for your game on the returned Map of Coord keys to Double
* values that describe the amount of effect (from 0.0 for none to 1.0 for full) that Coord receives.</li>
* </ul>
*
* A Technique always has an AOE implementation that it uses to determine which cells it actually affects, and
* Techniques that do not actually affect an area use the default single-cell "Area of Effect" implementation, PointAOE.
* You typically will need to construct the implementing class of the AOE interface in a different way for each
* implementation; BeamAOE, LineAOE, and ConeAOE depend on the user's position, BurstAOE and BlastAOE treat radii
* differently from BeamAOE and LineAOE, and CloudAOE has a random component that can be given a seed.
*
* A Technique has a String name, which typically should be in a form that can be presented to a user, and a
* String id, which defaults to the same value as name but can be given some value not meant for users that records
* any additional identifying characteristics the game needs for things like comparisons.
*
* Created by Tommy Ettinger on 7/27/2015.
*/
public class Technique {
public String name;
public String id;
public AOE aoe;
protected char[][] dungeon;
protected static final Coord DEFAULT_POINT = Coord.get(0, 0);
/**
* Creates a Technique that can target any adjacent single Coord, using
* Chebyshev (8-way square) distance.
*/
public Technique() {
name = "Default Technique";
id = name;
aoe = new PointAOE(DEFAULT_POINT);
}
/**
* Creates a Technique that can target any adjacent single Coord,
* using Chebyshev (8-way square) distance.
* @param name An identifier that may be displayed to the user. Also used for id.
*/
public Technique(String name) {
this.name = name;
id = name;
aoe = new PointAOE(DEFAULT_POINT);
}
/**
* Creates a Technique that can target a Coord at a range specified by the given AOE's minRange and maxRange,
* using a distance metric from the AOE, and use that target Coord for the given AOE.
* @param name An identifier that may be displayed to the user. Also used for id.
* @param aoe An implementation of the AOE interface; typically needs construction beforehand.
*/
public Technique(String name, AOE aoe) {
this.name = name;
id = name;
this.aoe = aoe;
}
/**
* Creates a Technique that can target a Coord at a range specified by the given AOE's minRange and maxRange,
* using a distance metric from the AOE, and use that target Coord for the given AOE. Takes an id parameter.
* @param name An identifier that may be displayed to the user.
* @param id An identifier that should always be internal, and will probably never be shown to the user.
* @param aoe An implementation of the AOE interface; typically needs construction beforehand.
*/
public Technique(String name, String id, AOE aoe) {
this.name = name;
this.id = id;
this.aoe = aoe;
}
/**
* VITAL: Call this method before any calls to idealLocations() or apply(), and call it again if the map changes.
*
* This simple method sets the map that this Technique can find targets in to a given char 2D array with '#' for
* walls and any other character (including characters for open and closed doors) treated as a floor for most
* purposes (certain AOE implementations may treat open and closed doors differently, specifically any that use
* FOV internally and can yield values other than 1.0 from their findArea() method, like BlastAOE and ConeAOE).
* @param map A char 2D array like one generated by squidpony.squidgrid.mapping.DungeonGenerator, with '#' for walls and bounded edges.
*/
public void setMap(char[][] map)
{
dungeon = map;
aoe.setMap(map);
}
/**
* Get a mapping of Coord keys representing locations to apply this Technique to, to ArrayList of Coord values
* representing which targets (by their location) are effected by choosing that Coord. All targets with this method
* are valued equally, and the ideal location affects as many as possible without hitting any requiredExclusions.
*
* YOU MUST CALL setMap() with the current map status at some point before using this method, and call it again if
* the map changes. Failure to do so can cause serious bugs, from logic errors where monsters consider a door
* closed when it is open or vice versa, to an ArrayIndexOutOfBoundsException being thrown if the player moved to a
* differently-sized map and the Technique tries to use the previous map with coordinates from the new one.
*
* @param user The location of the user of this Technique
* @param targets Set of Coord of desirable targets to include in the area of this Technique, as many as possible.
* @param requiredExclusions Set of Coord where each value is something this Technique will really try to avoid.
* @return OrderedMap of Coord keys representing target points to pass to apply, to ArrayList of Coord values representing what targets' locations will be affected.
*/
public OrderedMap<Coord, ArrayList<Coord>> idealLocations(Coord user, Collection<Coord> targets, Collection<Coord> requiredExclusions) {
aoe.setOrigin(user);
return aoe.idealLocations(targets, requiredExclusions);
}
/**
* Get a mapping of Coord keys representing locations to apply this Technique to, to ArrayList of Coord values
* representing which targets (by their location) are effected by choosing that Coord. This method will strongly
* prefer including priorityTargets in its area, especially multiple one if possible, and primarily uses
* lesserTargets as a tiebreaker if two locations have the same number of included priorityTargets but one has more
* lesserTargets in its area.
*
* YOU MUST CALL setMap() with the current map status at some point before using this method, and call it again if
* the map changes. Failure to do so can cause serious bugs, from logic errors where monsters consider a door
* closed when it is open or vice versa, to an ArrayIndexOutOfBoundsException being thrown if the player moved to a
* differently-sized map and the Technique tries to use the previous map with coordinates from the new one.
*
* @param user The location of the user of this Technique
* @param priorityTargets Set of Coord of important targets to include in the area of this Technique, preferring to target a single priorityTarget over four lesserTargets.
* @param lesserTargets Set of Coord of desirable targets to include in the area of this Technique, as many as possible without excluding priorityTargets.
* @param requiredExclusions Set of Coord where each value is something this Technique will really try to avoid.
* @return OrderedMap of Coord keys representing target points to pass to apply, to ArrayList of Coord values representing what targets' locations will be affected.
*/
public OrderedMap<Coord, ArrayList<Coord>> idealLocations(Coord user, Set<Coord> priorityTargets, Set<Coord> lesserTargets, Set<Coord> requiredExclusions) {
aoe.setOrigin(user);
return aoe.idealLocations(priorityTargets, lesserTargets, requiredExclusions);
}
/**
* This does one last validation of the location aimAt (checking that it is within the valid range for this
* Technique) before getting the area affected by the AOE targeting that cell. It considers the origin of the AOE
* to be the Coord parameter user, for purposes of directional limitations and for AOE implementations that need
* the user's location, such as ConeAOE and LineAOE.
*
* YOU MUST CALL setMap() with the current map status at some point before using this method, and call it again if
* the map changes. Failure to do so can cause serious bugs, from logic errors where monsters consider a door
* closed when it is open or vice versa, to an ArrayIndexOutOfBoundsException being thrown if the player moved to a
* differently-sized map and the Technique tries to use the previous map with coordinates from the new one.
*
* @param user The position of the Technique's user, x first, y second.
* @param aimAt A target Coord typically obtained from idealLocations that determines how to position the AOE.
* @return a HashMap of Coord keys to Double values from 1.0 (fully affected) to 0.0 (unaffected).
*/
public OrderedMap<Coord, Double> apply(Coord user, Coord aimAt)
{
aoe.setOrigin(user);
aoe.shift(aimAt);
return aoe.findArea();
}
}