001package squidpony.squidai;
002
003import squidpony.squidmath.Coord;
004
005import java.util.ArrayList;
006import java.util.LinkedHashMap;
007import java.util.Set;
008
009/**
010 * A simple struct-like class that stores various public fields which describe the targeting properties of a skill,
011 * spell, tech, or any other game-specific term for a targeted (typically offensive) ability we call a Technique.
012 *
013 * The typical usage of a Technique is:
014 * <ul>
015 * <li>Construct any AOE implementation the Technique would use (if the Technique affects multiple grid cells).</li>
016 * <li>Construct the Technique (passing the AOE as a parameter if needed).</li>
017 * <li>Call setMap() before considering the Technique if it has not been called yet, if the physical map (including
018 * doors and destructible objects) has changed since setMap() was last called, or simply on every Technique every time
019 * the map changes if there are few enemies with few Techniques. PERFORMING ANY SUBSEQUENT STEPS WITHOUT SETTING THE
020 * MAP TO THE CURRENT ACTUAL PHYSICAL MAP WILL HAVE BAD CONSEQUENCES FOR LOGIC AND MAY CAUSE CRASHING BUGS DUE TO
021 * ARRAY BOUNDS VIOLATIONS IF YOU HAVEN'T SET THE MAP ARRAY IN THE FIRST PLACE. The map should be bounded by wall chars
022 * ('#'), which is done automatically by squidpony.squidgrid.mapping.DungeonGenerator .</li>
023 * <li>When the Technique is being considered by an AI, call idealLocations() with the values of targets,
024 * lesserTargets, and/or priorityTargets set to beings that the AI can see (likely using FOV) and wants to affect with
025 * this Technique (enemies for offensive Techniques, allies for supporting ones), and requiredExclusions typically set
026 * to allies for offensive Techniques that can cause friendly-fire damage, or to null for supporting ones or Techniques
027 * that don't affect allies.</li>
028 * <li>When an ideal location has been determined from the previous step, and the AI decides (possibly randomly, by an
029 * aggression ("aggro") level tracked per-enemy, or by weights on Techniques for different situations) to use this
030 * Technique on a specific target point, call apply() with the user position as a Coord and the chosen Coord, and
031 * proceed to process the effects of the Technique as fitting for your game on the returned Map of Coord keys to Double
032 * values that describe the amount of effect (from 0.0 for none to 1.0 for full) that Coord receives.</li>
033 * </ul>
034 *
035 * A Technique always has an AOE implementation that it uses to determine which cells it actually affects, and
036 * Techniques that do not actually affect an area use the default single-cell "Area of Effect" implementation, PointAOE.
037 * You typically will need to construct the implementing class of the AOE interface in a different way for each
038 * implementation; BeamAOE, LineAOE, and ConeAOE depend on the user's position, BurstAOE and BlastAOE treat radii
039 * differently from BeamAOE and LineAOE, and CloudAOE has a random component that can be given a seed.
040 *
041 * A Technique has a String  name, which typically should be in a form that can be presented to a user, and a
042 * String id, which defaults to the same value as name but can be given some value not meant for users that records
043 * any additional identifying characteristics the game needs for things like comparisons.
044 *
045 * Created by Tommy Ettinger on 7/27/2015.
046 */
047public class Technique {
048    public String name;
049    public String id;
050    public AOE aoe;
051    protected char[][] dungeon;
052    protected static final Coord DEFAULT_POINT = Coord.get(0, 0);
053
054    /**
055     * Creates a Technique that can target any adjacent single Coord, using
056     * Chebyshev (8-way square) distance.
057     */
058    public Technique() {
059        name = "Default Technique";
060        id = name;
061        aoe = new PointAOE(DEFAULT_POINT);
062    }
063
064    /**
065     * Creates a Technique that can target any adjacent single Coord,
066     * using Chebyshev (8-way square) distance.
067     * @param name An identifier that may be displayed to the user. Also used for id.
068     */
069    public Technique(String name) {
070        this.name = name;
071        id = name;
072        aoe = new PointAOE(DEFAULT_POINT);
073    }
074
075    /**
076     * Creates a Technique that can target a Coord at a range specified by the given AOE's minRange and maxRange,
077     * using a distance metric from the AOE, and use that target Coord for the given AOE.
078     * @param name An identifier that may be displayed to the user. Also used for id.
079     * @param aoe An implementation of the AOE interface; typically needs construction beforehand.
080     */
081    public Technique(String name, AOE aoe) {
082        this.name = name;
083        id = name;
084        this.aoe = aoe;
085    }
086
087
088    /**
089     * Creates a Technique that can target a Coord at a range specified by the given AOE's minRange and maxRange,
090     * using a distance metric from the AOE, and use that target Coord for the given AOE. Takes an id parameter.
091     * @param name An identifier that may be displayed to the user.
092     * @param id An identifier that should always be internal, and will probably never be shown to the user.
093     * @param aoe An implementation of the AOE interface; typically needs construction beforehand.
094     */
095    public Technique(String name, String id, AOE aoe) {
096        this.name = name;
097        this.id = id;
098        this.aoe = aoe;
099    }
100
101
102    /**
103     * VITAL: Call this method before any calls to idealLocations() or apply(), and call it again if the map changes.
104     *
105     * This simple method sets the map that this Technique can find targets in to a given char 2D array with '#' for
106     * walls and any other character (including characters for open and closed doors) treated as a floor for most
107     * purposes (certain AOE implementations may treat open and closed doors differently, specifically any that use
108     * FOV internally and can yield values other than 1.0 from their findArea() method, like BlastAOE and ConeAOE).
109     * @param map A char 2D array like one generated by squidpony.squidgrid.mapping.DungeonGenerator, with '#' for walls and bounded edges.
110     */
111    public void setMap(char[][] map)
112    {
113        dungeon = map;
114        aoe.setMap(map);
115    }
116
117    /**
118     * Get a mapping of Coord keys representing locations to apply this Technique to, to ArrayList of Coord values
119     * representing which targets (by their location) are effected by choosing that Coord. All targets with this method
120     * are valued equally, and the ideal location affects as many as possible without hitting any requiredExclusions.
121     *
122     * YOU MUST CALL setMap() with the current map status at some point before using this method, and call it again if
123     * the map changes. Failure to do so can cause serious bugs, from logic errors where monsters consider a door
124     * closed when it is open or vice versa, to an ArrayIndexOutOfBoundsException being thrown if the player moved to a
125     * differently-sized map and the Technique tries to use the previous map with coordinates from the new one.
126     *
127     * @param user The location of the user of this Technique
128     * @param targets Set of Coord of desirable targets to include in the area of this Technique, as many as possible.
129     * @param requiredExclusions Set of Coord where each value is something this Technique will really try to avoid.
130     * @return LinkedHashMap of Coord keys representing target points to pass to apply, to ArrayList of Coord values representing what targets' locations will be affected.
131     */
132    public LinkedHashMap<Coord, ArrayList<Coord>> idealLocations(Coord user, Set<Coord> targets, Set<Coord> requiredExclusions) {
133        aoe.setOrigin(user);
134        return aoe.idealLocations(targets, requiredExclusions);
135
136    }
137
138    /**
139     * Get a mapping of Coord keys representing locations to apply this Technique to, to ArrayList of Coord values
140     * representing which targets (by their location) are effected by choosing that Coord. This method will strongly
141     * prefer including priorityTargets in its area, especially multiple one if possible, and primarily uses
142     * lesserTargets as a tiebreaker if two locations have the same number of included priorityTargets but one has more
143     * lesserTargets in its area.
144     *
145     * YOU MUST CALL setMap() with the current map status at some point before using this method, and call it again if
146     * the map changes. Failure to do so can cause serious bugs, from logic errors where monsters consider a door
147     * closed when it is open or vice versa, to an ArrayIndexOutOfBoundsException being thrown if the player moved to a
148     * differently-sized map and the Technique tries to use the previous map with coordinates from the new one.
149     *
150     * @param user The location of the user of this Technique
151     * @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.
152     * @param lesserTargets Set of Coord of desirable targets to include in the area of this Technique, as many as possible without excluding priorityTargets.
153     * @param requiredExclusions Set of Coord where each value is something this Technique will really try to avoid.
154     * @return LinkedHashMap of Coord keys representing target points to pass to apply, to ArrayList of Coord values representing what targets' locations will be affected.
155     */
156    public LinkedHashMap<Coord, ArrayList<Coord>> idealLocations(Coord user, Set<Coord> priorityTargets, Set<Coord> lesserTargets, Set<Coord> requiredExclusions) {
157        aoe.setOrigin(user);
158        return aoe.idealLocations(priorityTargets, lesserTargets, requiredExclusions);
159    }
160
161    /**
162     * This does one last validation of the location aimAt (checking that it is within the valid range for this
163     * Technique) before getting the area affected by the AOE targeting that cell. It considers the origin of the AOE
164     * to be the Coord parameter user, for purposes of directional limitations and for AOE implementations that need
165     * the user's location, such as ConeAOE and LineAOE.
166     *
167     * YOU MUST CALL setMap() with the current map status at some point before using this method, and call it again if
168     * the map changes. Failure to do so can cause serious bugs, from logic errors where monsters consider a door
169     * closed when it is open or vice versa, to an ArrayIndexOutOfBoundsException being thrown if the player moved to a
170     * differently-sized map and the Technique tries to use the previous map with coordinates from the new one.
171     *
172     * @param user The position of the Technique's user, x first, y second.
173     * @param aimAt A target Coord typically obtained from idealLocations that determines how to position the AOE.
174     * @return a HashMap of Coord keys to Double values from 1.0 (fully affected) to 0.0 (unaffected).
175     */
176    public LinkedHashMap<Coord, Double> apply(Coord user, Coord aimAt)
177    {
178        aoe.setOrigin(user);
179        aoe.shift(aimAt);
180        return aoe.findArea();
181    }
182}