001package squidpony.squidgrid.gui.gdx;
002
003import com.badlogic.gdx.Gdx;
004import com.badlogic.gdx.graphics.Color;
005import com.badlogic.gdx.graphics.g2d.Batch;
006import com.badlogic.gdx.graphics.g2d.TextureRegion;
007import com.badlogic.gdx.math.MathUtils;
008import com.badlogic.gdx.scenes.scene2d.Action;
009import com.badlogic.gdx.scenes.scene2d.Actor;
010import com.badlogic.gdx.scenes.scene2d.Group;
011import com.badlogic.gdx.scenes.scene2d.actions.Actions;
012import com.badlogic.gdx.utils.Align;
013import squidpony.ArrayTools;
014import squidpony.IColorCenter;
015import squidpony.StringKit;
016import squidpony.panel.IColoredString;
017import squidpony.squidgrid.Direction;
018import squidpony.squidmath.Coord;
019import squidpony.squidmath.OrderedSet;
020import squidpony.squidmath.StatefulRNG;
021
022import java.util.Collection;
023
024import static com.badlogic.gdx.math.MathUtils.clamp;
025
026/**
027 * Displays text and images in a grid pattern. Supports basic animations.
028 * 
029 * Grid width and height settings are in terms of number of cells. Cell width and height
030 * are in terms of number of pixels.
031 *
032 * When text is placed, the background color is set separately from the foreground character. When moved, only the
033 * foreground character is moved.
034 *
035 * @author Eben Howard - http://squidpony.com - howard@squidpony.com
036 */
037public class SquidPanel extends Group implements IPackedColorPanel {
038
039    public float DEFAULT_ANIMATION_DURATION = 0.12F;
040    protected int animationCount;
041    protected Color defaultForeground = Color.WHITE;
042    protected IColorCenter<Color> scc;
043    protected int cellWidth, cellHeight;
044    protected int gridWidth, gridHeight, gridOffsetX, gridOffsetY;
045    /**
046     * The 2D array of chars that this will render, using x,y indexing.
047     * Full-block cells that are completely filled with their color will be the char at Unicode codepoint 0,
048     * usually represented with {@code '\0'}.
049     */
050    public char[][] contents;
051    /**
052     * The 2D array of floats representing colors in a way that libGDX can efficiently use, ABGR-packed.
053     * Most use won't directly involve this field, but there are various techniques SquidLib uses to boost
054     * performance by treating a color as a float. This is mainly advantageous when many colors are involved
055     * and objects shouldn't be instantiated many times. You may want to consider using
056     * {@link SColor#lerpFloatColors(float, float, float)} if you expect to smoothly mix these float colors,
057     * which avoids creating intermediate Color objects. There are more methods like that in SColor.
058     */
059    public float[][] colors;
060    protected Color lightingColor = SColor.WHITE, tmpColor = new Color();
061    protected TextCellFactory textFactory;
062    protected float xOffset, yOffset, lightingFloat = SColor.FLOAT_WHITE;
063    public OrderedSet<AnimatedEntity> animatedEntities;
064    public OrderedSet<Actor> autoActors;
065    /**
066     * For thin-wall maps, where only cells where x and y are both even numbers have backgrounds displayed.
067     * Should be false when using this SquidPanel for anything that isn't specifically a background of a map
068     * that uses the thin-wall method from ThinDungeonGenerator or something similar. Even the foregrounds of
069     * thin-wall maps should have this false, since ThinDungeonGenerator (in conjunction with DungeonUtility's
070     * hashesToLines method) makes thin lines for walls that should be displayed as between the boundaries of
071     * other cells. The overlap behavior needed for some "thin enough" cells to be displayed between the cells
072     * can be accomplished by using {@link #setTextSize(float, float)} to double the previously-given cell width
073     * and height.
074     */
075    public boolean onlyRenderEven;
076
077    /**
078     * Creates a bare-bones panel with all default values for text rendering.
079     * <br>
080     * This uses a default font that is not supplied in the JAR library of SquidLib; you need two files to use it if it
081     * does not render correctly:
082     * <ul>
083     *     <li>https://github.com/SquidPony/SquidLib/blob/master/assets/Zodiac-Square-12x12.fnt</li>
084     *     <li>https://github.com/SquidPony/SquidLib/blob/master/assets/Zodiac-Square-12x12.png</li>
085     * </ul>
086     * @param gridWidth the number of cells horizontally
087     * @param gridHeight the number of cells vertically
088     */
089    public SquidPanel(int gridWidth, int gridHeight) {
090        this(gridWidth, gridHeight, new TextCellFactory().defaultSquareFont());
091    }
092
093    /**
094     * Creates a panel with the given grid and cell size. Uses a default square font.
095     * <br>
096     * This uses a default font that is not supplied in the JAR library of SquidLib; you need two files to use it if it
097     * does not render correctly:
098     * <ul>
099     *     <li>https://github.com/SquidPony/SquidLib/blob/master/assets/Zodiac-Square-12x12.fnt</li>
100     *     <li>https://github.com/SquidPony/SquidLib/blob/master/assets/Zodiac-Square-12x12.png</li>
101     * </ul>
102     * @param gridWidth the number of cells horizontally
103     * @param gridHeight the number of cells vertically
104     * @param cellWidth the number of horizontal pixels in each cell
105     * @param cellHeight the number of vertical pixels in each cell
106     */
107    public SquidPanel(int gridWidth, int gridHeight, int cellWidth, int cellHeight) {
108        this(gridWidth, gridHeight, new TextCellFactory().defaultSquareFont().width(cellWidth).height(cellHeight).initBySize());
109    }
110
111    /**
112     * Builds a panel with the given grid size and all other parameters determined by the factory. Even if sprite images
113     * are being used, a TextCellFactory is still needed to perform sizing and other utility functions.
114     *
115     * If the TextCellFactory has not yet been initialized, then it will be sized at 12x12 px per cell. If it is null
116     * then a default one will be created and initialized.
117     *
118     * @param gridWidth the number of cells horizontally
119     * @param gridHeight the number of cells vertically
120     * @param factory the factory to use for cell rendering
121     */
122    public SquidPanel(int gridWidth, int gridHeight, TextCellFactory factory) {
123        this(gridWidth, gridHeight, factory, DefaultResources.getSCC());
124    }
125
126    /**
127     * Builds a panel with the given grid size and all other parameters determined by the factory. Even if sprite images
128     * are being used, a TextCellFactory is still needed to perform sizing and other utility functions.
129     *
130     * If the TextCellFactory has not yet been initialized, then it will be sized at 12x12 px per cell. If it is null
131     * then a default one will be created and initialized.
132     *
133     * @param gridWidth the number of cells horizontally
134     * @param gridHeight the number of cells vertically
135     * @param factory the factory to use for cell rendering
136     * @param center the color center to use. Can be {@code null}, which will use a default
137     */
138    public SquidPanel(int gridWidth, int gridHeight, TextCellFactory factory, IColorCenter<Color> center) {
139        this(gridWidth, gridHeight, factory, center, 0f, 0f);
140    }
141
142    /**
143     * Builds a panel with the given grid size and all other parameters determined by the factory. Even if sprite images
144     * are being used, a TextCellFactory is still needed to perform sizing and other utility functions.
145     * <br>
146     * If the TextCellFactory has not yet been initialized, then it will be sized at 12x12 px per cell. If it is null
147     * then a default one will be created and initialized. The xOffset and yOffset arguments are measured in pixels or
148     * whatever sub-cell unit of measure your game uses (world coordinates, in libGDX parlance), and change where the
149     * SquidPanel starts drawing by simply adding to the initial x and y coordinates. 0 and 0 are usually fine.
150     *
151     * @param gridWidth the number of cells horizontally
152     * @param gridHeight the number of cells vertically
153     * @param factory the factory to use for cell rendering
154     * @param center the color center to use. Can be {@code null}, which will use a default
155     * @param xOffset the x offset to start rendering at, in pixels (or some other sub-cell measurement your game uses)
156     * @param yOffset the y offset to start rendering at, in pixels (or some other sub-cell measurement your game uses)
157     */
158    public SquidPanel(int gridWidth, int gridHeight, TextCellFactory factory, IColorCenter<Color> center,
159                      float xOffset, float yOffset) {
160        this(gridWidth, gridHeight, factory, center, xOffset, yOffset, null);
161    }
162    /**
163     * Builds a panel with the given grid size and all other parameters determined by the factory. Even if sprite images
164     * are being used, a TextCellFactory is still needed to perform sizing and other utility functions. Importantly,
165     * this constructor takes a 2D char array argument that can be sized differently than the displayed area. The
166     * displayed area is gridWidth by gridHeight in cells, but the actualMap argument can be much larger, and only a
167     * portion will be displayed at a time. This requires some special work with the Camera and Viewports to get working
168     * correctly; in the squidlib module's examples, EverythingDemo may be a good place to see how this can be done.
169     * You can pass null for actualMap, which will simply create a char array to use internally that is exactly
170     * gridWidth by gridHeight, in cells.
171     * <br>
172     * If the TextCellFactory has not yet been initialized, then it will be sized at 12x12 px per cell. If it is null
173     * then a default one will be created and initialized. The xOffset and yOffset arguments are measured in pixels or
174     * whatever sub-cell unit of measure your game uses (world coordinates, in libGDX parlance), and change where the
175     * SquidPanel starts drawing by simply adding to the initial x and y coordinates. 0 and 0 are usually fine.
176     *
177     * @param gridWidth the number of cells horizontally
178     * @param gridHeight the number of cells vertically
179     * @param factory the factory to use for cell rendering
180     * @param center the color center to use. Can be {@code null}, which will use a default
181     * @param xOffset the x offset to start rendering at, in pixels (or some other sub-cell measurement your game uses)
182     * @param yOffset the y offset to start rendering at, in pixels (or some other sub-cell measurement your game uses)
183     * @param actualMap will often be a different size than gridWidth by gridHeight, which enables camera scrolling
184     */
185    public SquidPanel(int gridWidth, int gridHeight, TextCellFactory factory, IColorCenter<Color> center,
186                      float xOffset, float yOffset, char[][] actualMap) {
187        this.setTransform(false);
188        this.gridWidth = gridWidth;
189        this.gridHeight = gridHeight;
190        if(center == null)
191            scc = DefaultResources.getSCC();
192        else
193            scc = center;
194        if (factory == null) {
195            textFactory = new TextCellFactory();
196        }
197        else
198            textFactory = factory;
199        if (!textFactory.initialized()) {
200            textFactory.initByFont();
201        }
202
203        cellWidth = MathUtils.round(textFactory.actualCellWidth);
204        cellHeight = MathUtils.round(textFactory.actualCellHeight);
205
206        if(actualMap == null || actualMap.length <= 0)
207            contents = ArrayTools.fill('\0', gridWidth, gridHeight);
208        else
209            contents = actualMap;
210        colors = ArrayTools.fill(-0x1.0p125F, contents.length, contents[0].length);
211
212        int w = gridWidth * cellWidth;
213        int h = gridHeight * cellHeight;
214        this.xOffset = xOffset;
215        this.yOffset = yOffset;
216        setSize(w, h);
217        animatedEntities = new OrderedSet<>();
218        autoActors = new OrderedSet<>();
219    }
220
221    /**
222     * Places the given characters into the grid starting at 0,0.
223     *
224     * @param chars
225     */
226    public void put(char[][] chars) {
227        put(0, 0, chars);
228    }
229
230        @Override
231        public void put(/* @Nullable */char[][] chars, Color[][] foregrounds) {
232                if (chars == null) {
233                        /* Only colors to put */
234                        final int width = foregrounds.length;
235                        final int height = width == 0 ? 0 : foregrounds[0].length;
236                        for (int x = 0; x < width; x++) {
237                                for (int y = 0; y < height; y++)
238                                        put(x, y, foregrounds[x][y]);
239                        }
240                } else
241                        put(0, 0, chars, foregrounds);
242        }
243
244    public void put(int xOffset, int yOffset, char[][] chars) {
245        put(xOffset, yOffset, chars, defaultForeground);
246    }
247
248    public void put(int xOffset, int yOffset, char[][] chars, Color[][] foregrounds) {
249        for (int x = xOffset; x < xOffset + chars.length; x++) {
250            for (int y = yOffset; y < yOffset + chars[0].length; y++) {
251                if (x >= 0 && y >= 0 && x < gridWidth && y < gridHeight) {//check for valid input
252                    put(x, y, chars[x - xOffset][y - yOffset], foregrounds[x - xOffset][y - yOffset]);
253                }
254            }
255        }
256    }
257
258    public void put(int xOffset, int yOffset, Color[][] foregrounds) {
259        for (int x = xOffset; x < xOffset + foregrounds.length; x++) {
260            for (int y = yOffset; y < yOffset + foregrounds[0].length; y++) {
261                if (x >= 0 && y >= 0 && x < gridWidth && y < gridHeight) {//check for valid input
262                    put(x, y, '\0', foregrounds[x - xOffset][y - yOffset]);
263                }
264            }
265        }
266    }
267
268    public void put(int xOffset, int yOffset, char[][] chars, Color foreground) {
269        for (int x = xOffset; x < xOffset + chars.length; x++) {
270            for (int y = yOffset; y < yOffset + chars[0].length; y++) {
271                if (x >= 0 && y >= 0 && x < gridWidth && y < gridHeight) {//check for valid input
272                    put(x, y, chars[x - xOffset][y - yOffset], foreground);
273                }
274            }
275        }
276    }
277
278    /**
279     * Puts the given string horizontally with the first character at the given offset.
280     *
281     * Does not word wrap. Characters that are not renderable (due to being at negative offsets or offsets greater than
282     * the grid size) will not be shown but will not cause any malfunctions.
283     *
284     * Will use the default color for this component to draw the characters.
285     *
286     * @param xOffset the x coordinate of the first character
287     * @param yOffset the y coordinate of the first character
288     * @param string the characters to be displayed
289     */
290    public void put(int xOffset, int yOffset, String string) {
291        put(xOffset, yOffset, string, defaultForeground);
292    }
293
294        @Override
295        public void put(int xOffset, int yOffset, IColoredString<? extends Color> cs) {
296                int x = xOffset;
297                for (IColoredString.Bucket<? extends Color> fragment : cs) {
298                        final String s = fragment.getText();
299                        final Color color = fragment.getColor();
300                        put(x, yOffset, s, color == null ? getDefaultForegroundColor() : scc.filter(color));
301                        x += s.length();
302                }
303        }
304
305    @Override
306    public void put(int xOffset, int yOffset, String string, Color foreground) {
307        if(string == null || string.isEmpty())
308            return;
309        if (string.length() == 1) {
310            put(xOffset, yOffset, string.charAt(0), scc.filter(foreground).toFloatBits());
311        }
312        else
313        {
314            float enc = scc.filter(foreground).toFloatBits();
315            for (int i = 0; i < string.length(); i++) {
316                put(xOffset + i, yOffset, string.charAt(i), enc);
317            }
318        }
319    }
320    public void put(int xOffset, int yOffset, String string, float encodedColor) {
321        if(string == null || string.isEmpty())
322            return;
323        if (string.length() == 1) {
324            put(xOffset, yOffset, string.charAt(0), encodedColor);
325        }
326        else
327        {
328            for (int i = 0; i < string.length(); i++) {
329                put(xOffset + i, yOffset, string.charAt(i), encodedColor);
330            }
331        }
332    }
333
334    public void put(int xOffset, int yOffset, String string, Color foreground, float colorMultiplier) {
335        if (string.length() == 1) {
336            put(xOffset, yOffset, string.charAt(0), foreground, colorMultiplier);
337        }
338        else
339        {
340            for (int i = 0; i < string.length(); i++) {
341                put(xOffset + i, yOffset, string.charAt(i), foreground, colorMultiplier);
342            }
343        }
344    }
345
346    public void put(int xOffset, int yOffset, String string, float encodedColor, float colorMultiplier) {
347        if (string.length() == 1) {
348            put(xOffset, yOffset, string.charAt(0), encodedColor, colorMultiplier);
349        }
350        else
351        {
352            for (int i = 0; i < string.length(); i++) {
353                put(xOffset + i, yOffset, string.charAt(i), encodedColor, colorMultiplier);
354            }
355        }
356    }
357
358    public void put(int xOffset, int yOffset, char[][] chars, Color foreground, float colorMultiplier) {
359        for (int x = xOffset; x < xOffset + chars.length; x++) {
360            for (int y = yOffset; y < yOffset + chars[0].length; y++) {
361                if (x >= 0 && y >= 0 && x < gridWidth && y < gridHeight) {//check for valid input
362                    put(x, y, chars[x - xOffset][y - yOffset], foreground, colorMultiplier);
363                }
364            }
365        }
366    }
367
368    /**
369     * Puts the given string horizontally or optionally vertically, with the first character at the given offset.
370     *
371     * Does not word wrap. Characters that are not renderable (due to being at negative offsets or offsets greater than
372     * the grid size) will not be shown but will not cause any malfunctions.
373     *
374     * Will use the default color for this component to draw the characters.
375     *
376     * @param xOffset the x coordinate of the first character
377     * @param yOffset the y coordinate of the first character
378     * @param string the characters to be displayed
379     * @param vertical true if the text should be written vertically, from top to bottom
380     */
381    public void put(int xOffset, int yOffset, String string, boolean vertical) {
382        put(xOffset, yOffset, string, defaultForeground, vertical);
383    }
384
385    /**
386     * Puts the given string horizontally or optionally vertically, with the first character at the given offset.
387     *
388     * Does not word wrap. Characters that are not renderable (due to being at negative offsets or offsets greater than
389     * the grid size) will not be shown but will not cause any malfunctions.
390     *
391     * @param xOffset the x coordinate of the first character
392     * @param yOffset the y coordinate of the first character
393     * @param string the characters to be displayed
394     * @param foreground the color to draw the characters
395     * @param vertical true if the text should be written vertically, from top to bottom
396     */
397    public void put(int xOffset, int yOffset, String string, Color foreground, boolean vertical) {
398        if (vertical) {
399            for (int i = 0; i < string.length(); i++) {
400                put(xOffset, yOffset + i, string.charAt(i), foreground);
401            }
402        } else {
403            put(xOffset, yOffset, string, foreground);
404        }
405    }
406
407    /**
408     * Puts the given string in the chosen direction, with the first character shown (not necessarily the first in the
409     * string) at the given offset. If you use {@link Direction#LEFT}, then this effectively reverses the String and
410     * prints it with the last character of the String at the minimum-x position, which is the same position that the
411     * first character would be if you printed normally or if you gave this RIGHT (x is xOffset, y is yOffset). Giving
412     * UP acts similarly to LEFT, but has the last character at the minimum-y position and has the first character below
413     * it. The diagonals act as you would expect, combining the behavior of one of UP or DOWN with one of LEFT or RIGHT.
414     * <br>
415     * Does not word wrap. Characters that are not renderable (due to being at negative offsets or offsets greater than
416     * the grid size) will not be shown but will not cause any malfunctions.
417     *
418     * @param xOffset the x coordinate of the first character
419     * @param yOffset the y coordinate of the first character
420     * @param string the characters to be displayed
421     * @param foreground the color to draw the characters
422     * @param direction the direction the text should be written in, such as {@link Direction#RIGHT} for normal layout
423     */
424    public void put(int xOffset, int yOffset, String string, Color foreground, Direction direction) {
425        float enc = scc.filter(foreground).toFloatBits();
426        switch (direction)
427        {
428            case DOWN:
429                for (int i = 0; i < string.length(); i++) {
430                    put(xOffset, yOffset + i, string.charAt(i), enc);
431                }
432                break;
433            case UP:
434                for (int i = 0, p = string.length() - 1; i < string.length(); i++, p--) {
435                    put(xOffset, yOffset + p, string.charAt(i), enc);
436                }
437                break;
438            case LEFT:
439                for (int i = 0, p = string.length() - 1; i < string.length(); i++, p--) {
440                    put(xOffset + p, yOffset, string.charAt(i), enc);
441                }
442                break;
443            case DOWN_RIGHT:
444                for (int i = 0; i < string.length(); i++) {
445                    put(xOffset + i, yOffset + i, string.charAt(i), enc);
446                }
447                break;
448            case UP_RIGHT:
449                for (int i = 0, p = string.length() - 1; i < string.length(); i++, p--) {
450                    put(xOffset + i, yOffset + p, string.charAt(i), enc);
451                }
452                break;
453            case UP_LEFT:
454                for (int i = 0, p = string.length() - 1; i < string.length(); i++, p--) {
455                    put(xOffset + p, yOffset + p, string.charAt(i), enc);
456                }
457                break;
458            case DOWN_LEFT:
459                for (int i = 0, p = string.length() - 1; i < string.length(); i++, p--) {
460                    put(xOffset + p, yOffset + i, string.charAt(i), enc);
461                }
462                break;
463            default:
464                for (int i = 0; i < string.length(); i++) {
465                    put(xOffset + i, yOffset, string.charAt(i), enc);
466                }
467        }
468    }
469
470    /**
471     * Changes the chars at the edge of the SquidPanel to be a border drawn with box drawing characters in white.
472     */
473    public void putBorders()
474    {
475        putBorders(SColor.FLOAT_WHITE, null);
476    }
477    /**
478     * Changes the chars at the edge of the SquidPanel to be a border drawn with box drawing characters in the given
479     * Color, which will be run through any IColorCenter this has for filtering. If caption is non-null, then this puts
480     * that String starting at x=1, y=0.
481     * @param color a libGDX Color to use for the borders
482     * @param caption an optional caption that will be drawn at (1, 0). May be null.
483     * @see #putBordersCaptioned(Color, IColoredString) Another method that takes an IColoredString caption
484     */
485    public void putBorders(Color color, String caption)
486    {
487        putBorders(scc.filter(color).toFloatBits(), caption);
488    }
489    /**
490     * Changes the chars at the edge of the SquidPanel to be a border drawn with box drawing characters in the given
491     * Color, which will be run through any IColorCenter this has for filtering.
492     * @param color a libGDX Color to use for the borders
493     */
494    public void putBorders(Color color)
495    {
496        putBorders(color, null);
497    }
498    /**
499     * Changes the chars at the edge of the SquidPanel to be a border drawn with box drawing characters in the given
500     * color as a packed float.
501     * @param encodedColor a packed float color to use for the borders, as from {@link Color#toFloatBits()}
502     */
503    public void putBorders(float encodedColor)
504    {
505        putBorders(encodedColor, null);
506    }
507    /**
508     * Changes the chars at the edge of the SquidPanel to be a border drawn with box drawing characters in the given
509     * color as a packed float. If caption is non-null, then this puts that String starting at x=1, y=0.
510     * @param encodedColor a packed float color to use for the borders, as from {@link Color#toFloatBits()}
511     * @param caption an optional caption that will be drawn at (1, 0). May be null to have no caption.
512     * @see #putBordersCaptioned(float, IColoredString) Another method that takes an IColoredString caption
513     */
514    public void putBorders(float encodedColor, String caption)
515    {
516        contents[0][0] = '┌';
517        contents[gridWidth - 1][0] = '┐';
518        contents[0][gridHeight - 1] = '└';
519        contents[gridWidth - 1][gridHeight - 1] = '┘';
520        for (int i = 1; i < gridWidth - 1; i++) {
521            contents[i][0] = '─';
522            contents[i][gridHeight - 1] = '─';
523        }
524        for (int y = 1; y < gridHeight - 1; y++) {
525            contents[0][y] = '│';
526            contents[gridWidth - 1][y] = '│';
527            colors[0][y] = encodedColor;
528            colors[gridWidth - 1][y] = encodedColor;
529        }
530        for (int y = 1; y < gridHeight - 1; y++) {
531            for (int x = 1; x < gridWidth - 1; x++) {
532                contents[x][y] = ' ';
533                contents[x][y] = ' ';
534            }
535        }
536        for (int i = 0; i < gridWidth; i++) {
537            colors[i][0] = encodedColor;
538            colors[i][gridHeight - 1] = encodedColor;
539        }
540
541        if (caption != null) {
542            put(1, 0, caption, encodedColor);
543        }
544    }
545
546    /**
547     * Changes the chars at the edge of the SquidPanel to be a border drawn with box drawing characters in the given
548     * libGDX Color. If caption is non-null, then this puts that IColoredString starting at x=1, y=0.
549     * @param color a libGDX Color to use for the borders
550     * @param caption an optional caption as an IColoredString that will be drawn at (1, 0). May be null to have no
551     *                caption. Will be colored independently from the border lines.
552     */
553    public void putBordersCaptioned(Color color, IColoredString<Color> caption)
554    {
555        putBordersCaptioned(scc.filter(color).toFloatBits(), caption);
556    }
557
558    /**
559     * Changes the chars at the edge of the SquidPanel to be a border drawn with box drawing characters in the given
560     * color as a packed float. If caption is non-null, then this puts that IColoredString starting at x=1, y=0.
561     * @param encodedColor a packed float color to use for the borders, as from {@link Color#toFloatBits()}
562     * @param caption an optional caption as an IColoredString that will be drawn at (1, 0). May be null to have no
563     *                caption. Will be colored independently from the border lines.
564     */
565    public void putBordersCaptioned(float encodedColor, IColoredString<Color> caption)
566
567    {
568        contents[0][0] = '┌';
569        contents[gridWidth - 1][0] = '┐';
570        contents[0][gridHeight - 1] = '└';
571        contents[gridWidth - 1][gridHeight - 1] = '┘';
572        for (int i = 1; i < gridWidth - 1; i++) {
573            contents[i][0] = '─';
574            contents[i][gridHeight - 1] = '─';
575        }
576        for (int y = 1; y < gridHeight - 1; y++) {
577            contents[0][y] = '│';
578            contents[gridWidth - 1][y] = '│';
579            colors[0][y] = encodedColor;
580            colors[gridWidth - 1][y] = encodedColor;
581        }
582        for (int y = 1; y < gridHeight - 1; y++) {
583            for (int x = 1; x < gridWidth - 1; x++) {
584                contents[x][y] = ' ';
585                contents[x][y] = ' ';
586            }
587        }
588        for (int i = 0; i < gridWidth; i++) {
589            colors[i][0] = encodedColor;
590            colors[i][gridHeight - 1] = encodedColor;
591        }
592
593        if (caption != null) {
594            put(1, 0, caption);
595        }
596
597    }
598
599    /**
600     * Erases the entire panel, leaving only a transparent space.
601     */
602    public void erase() {
603        ArrayTools.fill(contents, ' ');
604        ArrayTools.fill(colors, 0f);
605    }
606
607    @Override
608        public void clear(int x, int y) {
609        put(x, y, ' ', 0f);
610    }
611
612    @Override
613    public void put(int x, int y, Color color) {
614        put(x, y, '\0', color);
615    }
616
617    public void put(int x, int y, float encodedColor) {
618        put(x, y, '\0', encodedColor);
619    }
620
621    public void put(int x, int y, float encodedColor, float colorMultiplier) {
622        put(x, y, '\0', encodedColor, colorMultiplier);
623    }
624
625    public void put(int x, int y, float encodedColor, float colorMultiplier, float mixColor) {
626        put(x, y, '\0', encodedColor, colorMultiplier, mixColor);
627    }
628    @Override
629    public void blend(int x, int y, float color, float mixBy)
630    {
631        colors[x][y] = SColor.lerpFloatColorsBlended(colors[x][y], color, mixBy);
632    }
633
634    public void put(int x, int y, Color color, float colorMultiplier) {
635        put(x, y, '\0', color, colorMultiplier);
636    }
637
638    public void put(int x, int y, Color color, float mixAmount, Color mix) {
639        put(x, y, '\0', color, mixAmount, mix);
640    }
641
642    @Override
643        public void put(int x, int y, char c) {
644        put(x, y, c, defaultForeground);
645    }
646
647    /**
648     * Takes a unicode codepoint for input.
649     *
650     * @param x
651     * @param y
652     * @param code
653     */
654    public void put(int x, int y, int code) {
655        put(x, y, code, defaultForeground);
656    }
657
658    public void put(int x, int y, int c, Color color) {
659        put(x, y, (char) c, color);
660    }
661
662    /**
663     * Takes a unicode char for input.
664     *
665     * @param x
666     * @param y
667     * @param c
668     * @param color
669     */
670    @Override
671    public void put(int x, int y, char c, Color color) {
672        if (x < 0 || x >= contents.length || y < 0 || y >= contents[0].length) {
673            return;//skip if out of bounds
674        }
675        contents[x][y] = c;
676        colors[x][y] = scc.filter(color).toFloatBits();
677    }
678    /**
679     * Takes a unicode char for input.
680     *
681     * @param x
682     * @param y
683     * @param c
684     * @param encodedColor a float color as produced by {@link SColor#floatGet(float, float, float, float)}
685     */
686    public void put(int x, int y, char c, float encodedColor) {
687        if (x < 0 || x >= contents.length || y < 0 || y >= contents[0].length) {
688            return;//skip if out of bounds
689        }
690        contents[x][y] = c;
691        colors[x][y] = encodedColor;
692    }
693
694    /**
695     * Takes a unicode char for input and a color multiplier that determines how much of {@link #lightingColor} will
696     * affect the given encodedColor. The encodedColor is a float that might be produced by {@link Color#toFloatBits()}
697     * or by mixing multiple such floats with {@link SColor#lerpFloatColors(float, float, float)}.
698     *
699     * @param x
700     * @param y
701     * @param c
702     * @param encodedColor a float color as produced by {@link SColor#floatGet(float, float, float, float)}
703     * @param colorMultiplier how much of {@link #lightingColor} to use in place of encodedColor, from 0.0 to 1.0
704     */
705    public void put(int x, int y, char c, float encodedColor, float colorMultiplier) {
706        if (x < 0 || x >= contents.length || y < 0 || y >= contents[0].length) {
707            return;//skip if out of bounds
708        }
709        contents[x][y] = c;
710        colors[x][y] = SColor.lerpFloatColors(encodedColor, lightingFloat, colorMultiplier);
711    }
712
713    /**
714     * Intended for colored lighting; takes a unicode char for input and a color multiplier that determines how much of
715     * mixColor will affect encodedColor. Both encodedColor and mixColor are floats that might be produced by
716     * {@link Color#toFloatBits()} or by mixing multiple such floats with
717     * {@link SColor#lerpFloatColors(float, float, float)}; colorMultiplier is a normal float between 0.0f and 1.0f .
718     *
719     * @param x
720     * @param y
721     * @param c
722     * @param encodedColor a float color as produced by {@link SColor#floatGet(float, float, float, float)}
723     * @param colorMultiplier how much of mixColor to use in place of encodedColor, from 0.0 to 1.0
724     * @param mixColor a color to mix with encodedColor, typically as part of colored lighting
725     */
726    public void put(int x, int y, char c, float encodedColor, float colorMultiplier, float mixColor) {
727        if (x < 0 || x >= contents.length || y < 0 || y >= contents[0].length) {
728            return;//skip if out of bounds
729        }
730        contents[x][y] = c;
731        colors[x][y] = SColor.lerpFloatColors(encodedColor, mixColor, colorMultiplier);
732    }
733
734    /**
735     * Puts the given character at position x, y, with its color determined by the given color interpolated with
736     * this SquidPanel's lightingColor (default is white light) by the amount specified by colorMultiplier (0.0
737     * causes no change to the given color, 1.0 uses the lightingColor only, and anything between 0 and 1 will
738     * produce some tint to color, and probably cache the produced color in the IColorCenter this uses).
739     */
740    public void put(int x, int y, char c, Color color, float colorMultiplier) {
741        if (x < 0 || x >= contents.length || y < 0 || y >= contents[0].length) {
742            return;//skip if out of bounds
743        }
744        contents[x][y] = c;
745        colors[x][y] = scc.lerp(color, lightingColor, colorMultiplier).toFloatBits();
746    }
747
748    /**
749     * Puts the given character at position x, y, with its color determined by the given color interpolated with
750     * the given mix color by the amount specified by mixAmount (0.0 causes no change to the given color, 1.0 uses mix
751     * only, and anything between 0 and 1 will produce some tint to color, and probably cache the produced color in the
752     * IColorCenter this uses).
753     * <br>
754     * Note, unlike {@link #put(int, int, char, float, float, float)}, this will use the IColorCenter to produce the
755     * finished color, which may be slightly slower if you don't need any of IColorCenter's features, and will use
756     * more memory if many colors are cached, but has the advantage of being able to adjust colors with filters.
757     */
758    public void put(int x, int y, char c, Color color, float mixAmount, Color mix) {
759        if (x < 0 || x >= contents.length || y < 0 || y >= contents[0].length) {
760            return;//skip if out of bounds
761        }
762        contents[x][y] = c;
763        colors[x][y] = scc.lerp(color, mix, mixAmount).toFloatBits();
764    }
765
766    @Override
767        public int cellWidth() {
768        return cellWidth;
769    }
770
771    @Override
772        public int cellHeight() {
773        return cellHeight;
774    }
775
776    @Override
777        public int gridHeight() {
778        return gridHeight;
779    }
780
781    @Override
782        public int gridWidth() {
783        return gridWidth;
784    }
785
786        /**
787         * @return The {@link TextCellFactory} backing {@code this}.
788         */
789        public TextCellFactory getTextCellFactory() {
790                return textFactory;
791        }
792
793    /**
794     * Sets the size of the text in this SquidPanel (but not the size of the cells) to the given width and height in
795     * pixels (which may be stretched by viewports later on, if your program uses them).
796     * @param wide the width of a glyph in pixels
797     * @param high the height of a glyph in pixels
798     * @return this for chaining
799     */
800    public SquidPanel setTextSize(float wide, float high)
801    {
802        textFactory.tweakHeight(high).tweakWidth(wide).initBySize();
803        //textFactory.setSmoothingMultiplier((3f + Math.max(cellWidth * 1f / wide, cellHeight * 1f / high)) / 4f);
804        return this;
805    }
806
807    /**
808     * Draws this SquidPanel and any {@link #autoActors} it has, and calls {@link Actor#act(float)} for each
809     * AnimatedEntity this contains in {@link #animatedEntities} or {@link #autoActors}.
810     * <br>
811     * This will set the shader of {@code batch} if using a distance field or MSDF font and the shader is currently not
812     * configured for such a font; it does not reset the shader to the default so that multiple Actors can all use the
813     * same shader and so specific extra glyphs or other items can be rendered after calling draw(). If you need to draw
814     * both a distance field font and full-color art, you should set the shader on the Batch to null when you want to
815     * draw full-color art, and end the Batch between drawing this object and the other art.
816     * @param batch a Batch such as a {@link FilterBatch} that must be between a begin() and end() call; usually done by Stage
817     * @param parentAlpha only used when drawing children of this SquidPanel
818     */
819    @Override
820    public void draw(Batch batch, float parentAlpha) {
821        textFactory.configureShader(batch);
822        int inc = onlyRenderEven ? 2 : 1, widthInc = inc * cellWidth, heightInc = inc * cellHeight;
823        float screenX = xOffset - (gridOffsetX <= 0 ? 0 : cellWidth) + getX(),
824                screenY_base = 1f + yOffset + (gridOffsetY <= 0 ? 0 : cellHeight) + gridHeight * cellHeight + getY(), screenY;
825        for (int x = Math.max(0, gridOffsetX-1), xx = (gridOffsetX <= 0) ? 0 : -1; xx <= gridWidth && x < contents.length; x += inc, xx += inc, screenX += widthInc) {
826            screenY = screenY_base;
827            for (int y = Math.max(0, gridOffsetY-1), yy = (gridOffsetY <= 0) ? 0 : -1; yy <= gridHeight && y < contents[x].length; y += inc, yy += inc, screenY -= heightInc) {
828                textFactory.draw(batch, contents[x][y],
829                        colors[x][y],
830                        screenX,// xOffset + /*- getX() + 1f * */ x * cellWidth,
831                        screenY // yOffset + /*- getY() + 1f * */ (gridHeight - y) * cellHeight + 1f
832                );
833            }
834        }
835        super.draw(batch, parentAlpha);
836        int len = animatedEntities.size();
837        for (int i = 0; i < len; i++) {
838            animatedEntities.getAt(i).actor.act(Gdx.graphics.getDeltaTime());
839        }
840        len = autoActors.size();
841        Actor a;
842        for (int i = 0; i < len; i++) {
843            a = autoActors.getAt(i);
844            if(a == null) continue;
845            if(a.isVisible())
846                drawActor(batch, parentAlpha, a);
847            a.act(Gdx.graphics.getDeltaTime());
848        }
849    }
850
851    /**
852     * Draws one AnimatedEntity, specifically the Actor it contains. Batch must be between start() and end()
853     * @param batch Must have start() called already but not stop() yet during this frame.
854     * @param parentAlpha This can be assumed to be 1.0f if you don't know it
855     * @param ae The AnimatedEntity to draw; the position to draw ae is stored inside it.
856     */
857    public void drawActor(Batch batch, float parentAlpha, AnimatedEntity ae)
858    {
859        drawActor(batch, parentAlpha, ae.actor);
860        /*
861        float prevX = ae.actor.getX(), prevY = ae.actor.getY();
862        ae.actor.setPosition(prevX - gridOffsetX * cellWidth, prevY + gridOffsetY * cellHeight);
863        ae.actor.draw(batch, parentAlpha);
864        ae.actor.setPosition(prevX, prevY);
865        */
866    }
867
868    /**
869     * Draws one AnimatedEntity, specifically the Actor it contains. Batch must be between start() and end()
870     * @param batch Must have start() called already but not stop() yet during this frame.
871     * @param parentAlpha This can be assumed to be 1.0f if you don't know it
872     * @param ac The Actor to draw; the position to draw ac is modified and reset based on some fields of this object
873     */
874    public void drawActor(Batch batch, float parentAlpha, Actor ac)
875    {
876        float prevX = ac.getX(), prevY = ac.getY();
877        ac.setPosition(prevX - gridOffsetX * cellWidth, prevY + gridOffsetY * cellHeight);
878        ac.draw(batch, parentAlpha);
879        ac.setPosition(prevX, prevY);
880    }
881
882    @Override
883        public void setDefaultForeground(Color defaultForeground) {
884        this.defaultForeground = defaultForeground;
885    }
886
887        @Override
888        public Color getDefaultForegroundColor() {
889                return defaultForeground;
890        }
891
892    public AnimatedEntity getAnimatedEntityByCell(int x, int y) {
893        for(AnimatedEntity ae : animatedEntities)
894        {
895            if(ae.gridX == x && ae.gridY == y)
896                return ae;
897        }
898        return  null;
899    }
900
901    /**
902     * Create an AnimatedEntity at position x, y, using the char c in the given color.
903     * @param x
904     * @param y
905     * @param c
906     * @param color
907     * @return
908     */
909    public AnimatedEntity animateActor(int x, int y, char c, Color color)
910    {
911        return animateActor(x, y, false, c, color);
912        /*
913        Actor a = textFactory.makeActor("" + c, color);
914        a.setName("" + c);
915        a.setPosition(x * cellWidth + getX(), (gridHeight - y - 1) * cellHeight - textFactory.getDescent() + getY());
916
917        AnimatedEntity ae = new AnimatedEntity(a, x, y);
918        animatedEntities.add(ae);
919        return ae;
920        */
921    }
922
923    /**
924     * Create an AnimatedEntity at position x, y, using the char c in the given color. If doubleWidth is true, treats
925     * the char c as the left char to be placed in a grid of 2-char cells.
926     * @param x
927     * @param y
928     * @param doubleWidth
929     * @param c
930     * @param color
931     * @return
932     */
933    public AnimatedEntity animateActor(int x, int y, boolean doubleWidth, char c, Color color)
934    {
935        Actor a = textFactory.makeActor(c, color);
936        a.setName(String.valueOf(c));
937        a.setPosition(adjustX(x, doubleWidth), adjustY(y));
938        AnimatedEntity ae = new AnimatedEntity(a, x, y, doubleWidth);
939        animatedEntities.add(ae);
940        return ae;
941
942        /*
943        Actor a = textFactory.makeActor("" + c, color);
944        a.setName("" + c);
945        if(doubleWidth)
946            a.setPosition(x * 2 * cellWidth + getX(), (gridHeight - y - 1) * cellHeight - textFactory.getDescent() + getY());
947        else
948            a.setPosition(x * cellWidth + getX(), (gridHeight - y - 1) * cellHeight - textFactory.getDescent() + getY());
949
950        AnimatedEntity ae = new AnimatedEntity(a, x, y, doubleWidth);
951        animatedEntities.add(ae);
952        return ae;
953        */
954    }
955
956    /**
957     * Create an AnimatedEntity at position x, y, using the String s in the given color.
958     * @param x
959     * @param y
960     * @param s
961     * @param color
962     * @return
963     */
964    public AnimatedEntity animateActor(int x, int y, String s, Color color)
965    {
966        return animateActor(x, y, false, s, color);
967    }
968
969    /**
970     * Create an AnimatedEntity at position x, y, using the String s in the given color. If doubleWidth is true, treats
971     * the String s as starting in the left cell of a pair to be placed in a grid of 2-char cells.
972     * @param x
973     * @param y
974     * @param doubleWidth
975     * @param s
976     * @param color
977     * @return
978     */
979    public AnimatedEntity animateActor(int x, int y, boolean doubleWidth, String s, Color color)
980    {
981        Actor a = textFactory.makeActor(s, color);
982        a.setName(s);
983        a.setPosition(adjustX(x, doubleWidth), adjustY(y));
984        AnimatedEntity ae = new AnimatedEntity(a, x, y, doubleWidth);
985        animatedEntities.add(ae);
986        return ae;
987    }
988    /**
989     * Create an AnimatedEntity at position x, y, using the String s in the given color. If doubleWidth is true, treats
990     * the String s as starting in the left cell of a pair to be placed in a grid of 2-char cells.
991     * @param x
992     * @param y
993     * @param doubleWidth
994     * @param s
995     * @param colors
996     * @return
997     */
998    public AnimatedEntity animateActor(int x, int y, boolean doubleWidth, String s, Collection<Color> colors)
999    {
1000        return animateActor(x, y, doubleWidth, s, colors, 2f);
1001    }
1002    /**
1003     * Create an AnimatedEntity at position x, y, using the String s in the given color. If doubleWidth is true, treats
1004     * the String s as starting in the left cell of a pair to be placed in a grid of 2-char cells.
1005     * @param x
1006     * @param y
1007     * @param doubleWidth
1008     * @param s
1009     * @param colors
1010     * @param loopTime
1011     * @return
1012     */
1013    public AnimatedEntity animateActor(int x, int y, boolean doubleWidth, String s, Collection<Color> colors, float loopTime)
1014    {
1015        Actor a = textFactory.makeActor(s, colors, loopTime, doubleWidth);
1016        a.setName(s);
1017        a.setPosition(adjustX(x, doubleWidth), adjustY(y));
1018        AnimatedEntity ae = new AnimatedEntity(a, x, y, doubleWidth);
1019        animatedEntities.add(ae);
1020        return ae;
1021    }
1022    /**
1023     * Create an AnimatedEntity at position x, y, using '^' as its contents, but as an image so it can be rotated.
1024     * Uses the given colors in a looping pattern, that doesn't count as an animation. If doubleWidth is true, treats
1025     * the '^' as starting in the middle of a 2-char cell.
1026     * @param x
1027     * @param y
1028     * @param doubleWidth
1029     * @param colors
1030     * @param loopTime
1031     * @return
1032     */
1033    public AnimatedEntity directionMarker(int x, int y, boolean doubleWidth, Collection<Color> colors, float loopTime)
1034    {
1035        Actor a = textFactory.makeDirectionMarker(colors, loopTime, doubleWidth);
1036        a.setName("^");
1037        a.setPosition(adjustX(x, doubleWidth), adjustY(y));
1038        AnimatedEntity ae = new AnimatedEntity(a, x, y, doubleWidth);
1039        animatedEntities.add(ae);
1040        return ae;
1041    }
1042    public AnimatedEntity directionMarker(int x, int y, boolean doubleWidth, Color color)
1043    {
1044        Actor a = textFactory.makeDirectionMarker(color);
1045        a.setName("^");
1046        a.setPosition(adjustX(x, doubleWidth), adjustY(y));
1047        AnimatedEntity ae = new AnimatedEntity(a, x, y, doubleWidth);
1048        animatedEntities.add(ae);
1049        return ae;
1050    }
1051
1052    /**
1053     * Create an AnimatedEntity at position x, y, using a TextureRegion with no color modifications, which will be
1054     * stretched to fit one cell.
1055     * @param x
1056     * @param y
1057     * @param texture
1058     * @return
1059     */
1060    public AnimatedEntity animateActor(int x, int y, TextureRegion texture)
1061    {
1062        return animateActor(x, y, false, texture, Color.WHITE);
1063        /*
1064        Actor a = textFactory.makeActor(texture, Color.WHITE);
1065        a.setName("");
1066        a.setPosition(x * cellWidth + getX(), (gridHeight - y - 1) * cellHeight - textFactory.getDescent() + getY());
1067
1068        AnimatedEntity ae = new AnimatedEntity(a, x, y);
1069        animatedEntities.add(ae);
1070        return ae;
1071        */
1072    }
1073
1074    /**
1075     * Create an AnimatedEntity at position x, y, using a TextureRegion with the given color, which will be
1076     * stretched to fit one cell.
1077     * @param x
1078     * @param y
1079     * @param texture
1080     * @param color
1081     * @return
1082     */
1083    public AnimatedEntity animateActor(int x, int y, TextureRegion texture, Color color)
1084    {
1085        return animateActor(x, y, false, texture, color);
1086        /*
1087        Actor a = textFactory.makeActor(texture, color);
1088        a.setName("");
1089        a.setPosition(x * cellWidth + getX(), (gridHeight - y - 1) * cellHeight - textFactory.getDescent() + getY());
1090
1091        AnimatedEntity ae = new AnimatedEntity(a, x, y);
1092        animatedEntities.add(ae);
1093        return ae;
1094        */
1095    }
1096
1097    /**
1098     * Create an AnimatedEntity at position x, y, using a TextureRegion with no color modifications, which will be
1099     * stretched to fit one cell, or two cells if doubleWidth is true.
1100     * @param x
1101     * @param y
1102     * @param doubleWidth
1103     * @param texture
1104     * @return
1105     */
1106    public AnimatedEntity animateActor(int x, int y, boolean doubleWidth, TextureRegion texture)
1107    {
1108        return animateActor(x, y, doubleWidth, texture, Color.WHITE);
1109        /*
1110        Actor a = textFactory.makeActor(texture, Color.WHITE, (doubleWidth ? 2 : 1) * cellWidth, cellHeight);
1111
1112        a.setName("");
1113        if(doubleWidth)
1114            a.setPosition(x * 2 * cellWidth + getX(), (gridHeight - y - 1) * cellHeight - textFactory.getDescent() + getY());
1115        else
1116            a.setPosition(x * cellWidth + getX(), (gridHeight - y - 1) * cellHeight - textFactory.getDescent() + getY());
1117
1118        AnimatedEntity ae = new AnimatedEntity(a, x, y, doubleWidth);
1119        animatedEntities.add(ae);
1120        return ae;
1121        */
1122    }
1123
1124    /**
1125     * Create an AnimatedEntity at position x, y, using a TextureRegion with the given color, which will be
1126     * stretched to fit one cell, or two cells if doubleWidth is true.
1127     * @param x
1128     * @param y
1129     * @param doubleWidth
1130     * @param texture
1131     * @param color
1132     * @return
1133     */
1134    public AnimatedEntity animateActor(int x, int y, boolean doubleWidth, TextureRegion texture, Color color) {
1135        Actor a = textFactory.makeActor(texture, color, (doubleWidth ? 2 : 1) * cellWidth, cellHeight);
1136        a.setName("");
1137        a.setPosition(adjustX(x, doubleWidth), adjustY(y));
1138        /*
1139        if (doubleWidth)
1140            a.setPosition(x * 2 * cellWidth + getX(), (gridHeight - y - 1) * cellHeight - textFactory.getDescent() + getY());
1141        else
1142            a.setPosition(x * cellWidth + getX(), (gridHeight - y - 1) * cellHeight - textFactory.getDescent() + getY());
1143        */
1144        AnimatedEntity ae = new AnimatedEntity(a, x, y, doubleWidth);
1145        animatedEntities.add(ae);
1146        return ae;
1147    }
1148
1149    /**
1150     * Create an AnimatedEntity at position x, y, using a TextureRegion with the given color, which will be
1151     * stretched to fit one cell, or two cells if doubleWidth is true.
1152     * @param x
1153     * @param y
1154     * @param doubleWidth
1155     * @param texture
1156     * @param colors
1157     * @return
1158     */
1159    public AnimatedEntity animateActor(int x, int y, boolean doubleWidth, TextureRegion texture, Collection<Color> colors){
1160        return animateActor(x, y, doubleWidth, texture, colors, 2f);
1161    }
1162    /**
1163     * Create an AnimatedEntity at position x, y, using a TextureRegion with the given color, which will be
1164     * stretched to fit one cell, or two cells if doubleWidth is true.
1165     * @param x
1166     * @param y
1167     * @param doubleWidth
1168     * @param texture
1169     * @param colors
1170     * @return
1171     */
1172    public AnimatedEntity animateActor(int x, int y, boolean doubleWidth, TextureRegion texture, Collection<Color> colors, float loopTime) {
1173        Actor a = textFactory.makeActor(texture, colors, loopTime, doubleWidth, (doubleWidth ? 2 : 1) * cellWidth, cellHeight);
1174        a.setName("");
1175        a.setPosition(adjustX(x, doubleWidth), adjustY(y));
1176        /*
1177        if (doubleWidth)
1178            a.setPosition(x * 2 * cellWidth + getX(), (gridHeight - y - 1) * cellHeight - textFactory.getDescent() + getY());
1179        else
1180            a.setPosition(x * cellWidth + getX(), (gridHeight - y - 1) * cellHeight - textFactory.getDescent() + getY());
1181        */
1182        AnimatedEntity ae = new AnimatedEntity(a, x, y, doubleWidth);
1183        animatedEntities.add(ae);
1184        return ae;
1185    }
1186
1187    /**
1188     * Create an AnimatedEntity at position x, y, using a TextureRegion with no color modifications, which, if and only
1189     * if stretch is true, will be stretched to fit one cell, or two cells if doubleWidth is true. If stretch is false,
1190     * this will preserve the existing size of texture.
1191     * @param x
1192     * @param y
1193     * @param doubleWidth
1194     * @param stretch
1195     * @param texture
1196     * @return
1197     */
1198    public AnimatedEntity animateActor(int x, int y, boolean doubleWidth, boolean stretch, TextureRegion texture)
1199    {
1200        Actor a = (stretch)
1201                ? textFactory.makeActor(texture, Color.WHITE, (doubleWidth ? 2 : 1) * cellWidth, cellHeight)
1202                : textFactory.makeActor(texture, Color.WHITE, texture.getRegionWidth(), texture.getRegionHeight());
1203        a.setName("");
1204        a.setPosition(adjustX(x, doubleWidth), adjustY(y));
1205        /*
1206        if(doubleWidth)
1207            a.setPosition(x * 2 * cellWidth + getX(), (gridHeight - y - 1) * cellHeight - textFactory.getDescent() + getY());
1208        else
1209            a.setPosition(x * cellWidth + getX(), (gridHeight - y - 1) * cellHeight  - textFactory.getDescent() + getY());
1210        */
1211        AnimatedEntity ae = new AnimatedEntity(a, x, y, doubleWidth);
1212        animatedEntities.add(ae);
1213        return ae;
1214    }
1215
1216    /**
1217     * Create an AnimatedEntity at position x, y, using a TextureRegion with the given color, which, if and only
1218     * if stretch is true, will be stretched to fit one cell, or two cells if doubleWidth is true. If stretch is false,
1219     * this will preserve the existing size of texture.
1220     * @param x
1221     * @param y
1222     * @param doubleWidth
1223     * @param stretch
1224     * @param texture
1225     * @param color
1226     * @return
1227     */
1228    public AnimatedEntity animateActor(int x, int y, boolean doubleWidth, boolean stretch, TextureRegion texture, Color color) {
1229
1230        Actor a = (stretch)
1231                ? textFactory.makeActor(texture, color, (doubleWidth ? 2 : 1) * cellWidth, cellHeight)
1232                : textFactory.makeActor(texture, color, texture.getRegionWidth(), texture.getRegionHeight());
1233        a.setName("");
1234        a.setPosition(adjustX(x, doubleWidth), adjustY(y));
1235        /*
1236        if (doubleWidth)
1237            a.setPosition(x * 2 * cellWidth + getX(), (gridHeight - y - 1) * cellHeight  - textFactory.getDescent() + getY());
1238        else
1239            a.setPosition(x * cellWidth + getX(), (gridHeight - y - 1) * cellHeight  - textFactory.getDescent() + getY());
1240            */
1241        AnimatedEntity ae = new AnimatedEntity(a, x, y, doubleWidth);
1242        animatedEntities.add(ae);
1243        return ae;
1244    }
1245
1246    /**
1247     * Created an Actor from the contents of the given x,y position on the grid.
1248     * @param x
1249     * @param y
1250     * @return
1251     */
1252    public TextCellFactory.Glyph cellToActor(int x, int y)
1253    {
1254        return cellToActor(x, y, false);
1255    }
1256
1257    /**
1258     * Created an Actor from the contents of the given x,y position on the grid; deleting
1259     * the grid's String content at this cell.
1260     * 
1261     * @param x
1262     * @param y
1263     * @param doubleWidth
1264     * @return A fresh {@link Actor}, that has just been added to {@code this}.
1265     */
1266    public TextCellFactory.Glyph cellToActor(int x, int y, boolean doubleWidth)
1267    {
1268        return createActor(x, y, contents[x][y], colors[x][y], doubleWidth);
1269    }
1270
1271    /**
1272     * Used internally to go between grid positions and world positions.
1273     * @param gridX x on the grid
1274     * @return x in the world
1275     */
1276    public float worldX(int gridX, boolean doubleWidth)
1277    {
1278        return getX() + (gridX << (doubleWidth ? 1 : 0)) * textFactory.actualCellWidth;
1279    }
1280    /**
1281     * Used internally to go between grid positions and world positions.
1282     * @param gridY y on the grid
1283     * @return y in the world
1284     */
1285    public float worldY(int gridY)
1286    {
1287        return getY() + (gridHeight - gridY) * textFactory.actualCellHeight;
1288    }
1289
1290
1291    protected /* @Nullable */ TextCellFactory.Glyph createActor(int x, int y, char name, Color color, boolean doubleWidth) {
1292        //final Actor a = textFactory.makeActor(name, scc.filter(color));
1293        final TextCellFactory.Glyph a = textFactory.glyph(name, scc.filter(color), worldX(x, doubleWidth), worldY(y));
1294        //a.setName(String.valueOf(name));
1295        //a.setPosition(adjustX(x, doubleWidth), adjustY(y));
1296        autoActors.add(a);
1297        return a;
1298    }
1299
1300    protected /* @Nullable */ TextCellFactory.Glyph createActor(int x, int y, char name, float encodedColor, boolean doubleWidth) {
1301        //final Actor a = textFactory.makeActor(name, encodedColor);
1302        final TextCellFactory.Glyph a = textFactory.glyph(name, encodedColor, worldX(x, doubleWidth), worldY(y));
1303        //a.setName(String.valueOf(name));
1304        //a.setPosition(adjustX(x, doubleWidth), adjustY(y));
1305        autoActors.add(a);
1306        return a;
1307    }
1308
1309    public float adjustX(float x, boolean doubleWidth)
1310    {
1311        if(doubleWidth)
1312            return x * 2 * cellWidth + getX(); // may need (x - gridOffsetX) instead of x
1313        else
1314            return (x) * cellWidth + getX();
1315    }
1316
1317    public float adjustY(float y)
1318    {
1319        return (gridHeight - y - 1) * cellHeight + getY() + (textFactory.msdf ? -textFactory.descent : 1 + cellHeight - textFactory.actualCellHeight); // - textFactory.lineHeight //textFactory.lineTweak * 3f
1320        //return (gridHeight - y - 1) * cellHeight + textFactory.getDescent() * 3 / 2f + getY();
1321    }
1322
1323    /*
1324    public void startAnimation(Actor a, int oldX, int oldY)
1325    {
1326        Coord tmp = Coord.get(oldX, oldY);
1327
1328        tmp.x = Math.round(a.getX() / cellWidth);
1329        tmp.y = gridHeight - Math.round(a.getY() / cellHeight) - 1;
1330        if(tmp.x >= 0 && tmp.x < gridWidth && tmp.y > 0 && tmp.y < gridHeight)
1331        {
1332        }
1333    }
1334    */
1335    public void recallActor(Actor a, boolean restoreSym)
1336    {
1337        recallActor(a, restoreSym, Math.round((a.getX() - getX()) / cellWidth) + gridOffsetX,
1338            gridHeight - (int)(a.getY() / cellHeight) - 1 + gridOffsetY);
1339        /*
1340        animationCount--;
1341        int x = Math.round((a.getX() - getX()) / cellWidth) + gridOffsetX,
1342                y = gridHeight - (int)(a.getY() / cellHeight) - 1 + gridOffsetY;
1343        if(onlyRenderEven)
1344        {
1345            // this just sets the least significant bit to 0, making any odd numbers even (decrementing)
1346            x &= -2;
1347            y &= -2;
1348        }
1349        String n;
1350        if(restoreSym && x >= 0 && y >= 0 && x < contents.length && y < contents[x].length
1351                && (n = a.getName()) != null && !n.isEmpty())
1352        {
1353                contents[x][y] = n.charAt(0);
1354        }
1355        removeActor(a);
1356        autoActors.remove(a);
1357        */
1358    }
1359
1360    public void recallActor(Actor a, boolean restoreSym, int nextX, int nextY)
1361    {
1362        animationCount--;
1363        if(onlyRenderEven)
1364        {
1365            // this just sets the least significant bit to 0, making any odd numbers even (decrementing)
1366            nextX &= -2;
1367            nextY &= -2;
1368        }
1369        String n;
1370        if(restoreSym && nextX >= 0 && nextY >= 0 && nextX < contents.length && nextY < contents[nextX].length
1371                && (n = a.getName()) != null && !n.isEmpty())
1372        {
1373            contents[nextX][nextY] = n.charAt(0);
1374        }
1375        removeActor(a);
1376        autoActors.remove(a);
1377    }
1378
1379    public void recallActor(TextCellFactory.Glyph a, boolean restoreSym, int nextX, int nextY)
1380    {
1381        animationCount--;
1382        if(a == null) return; // if something has already removed the Glyph, we still reduce animationCount but do nothing more
1383        if(onlyRenderEven)
1384        {
1385            // this just sets the least significant bit to 0, making any odd numbers even (decrementing)
1386            nextX &= -2;
1387            nextY &= -2;
1388        }
1389        if(restoreSym && nextX >= 0 && nextY >= 0 && nextX < contents.length && nextY < contents[nextX].length)
1390        {
1391            contents[nextX][nextY] = a.shown;
1392        }
1393        removeActor(a);
1394        autoActors.remove(a);
1395    }
1396
1397    public void recallActor(AnimatedEntity ae)
1398    {
1399        recallActor(ae, ae.doubleWidth
1400                ? Math.round((ae.actor.getX() - getX()) / (2 * cellWidth)) + gridOffsetX 
1401                : Math.round((ae.actor.getX() - getX()) / cellWidth) + gridOffsetY,
1402                gridHeight - (int)((ae.actor.getY() - getY()) / cellHeight) - 1 + gridOffsetY);
1403//        if(ae.doubleWidth)
1404//            ae.gridX = Math.round((ae.actor.getX() - getX()) / (2 * cellWidth)) + gridOffsetX;
1405//        else
1406//            ae.gridX = Math.round((ae.actor.getX() - getX()) / cellWidth) + gridOffsetY;
1407//        ae.gridY = gridHeight - (int)((ae.actor.getY() - getY()) / cellHeight) - 1 + gridOffsetY;
1408//        if(onlyRenderEven)
1409//        {
1410//            // this just sets the least significant bit to 0, making any odd numbers even (decrementing)
1411//            ae.gridX &= -2;
1412//            ae.gridY &= -2;
1413//        }
1414//        ae.animating = false;
1415//        animationCount--;
1416    }
1417    public void recallActor(AnimatedEntity ae, int nextX, int nextY)
1418    {
1419        ae.gridX = nextX;
1420        ae.gridY = nextY;
1421        if(onlyRenderEven)
1422        {
1423            // this just sets the least significant bit to 0, making any odd numbers even (decrementing)
1424            ae.gridX &= -2;
1425            ae.gridY &= -2;
1426        }
1427        //fixPosition(ae);
1428        ae.animating = false;
1429        animationCount--;
1430    }
1431
1432    public void fixPosition(AnimatedEntity ae)
1433    {
1434        ae.actor.setPosition(adjustX(ae.gridX, ae.doubleWidth), adjustY(ae.gridY));
1435    }
1436    public void fixPositions()
1437    {
1438        for (int i = 0; i < animatedEntities.size(); i++) {
1439            AnimatedEntity ae = animatedEntities.getAt(i);
1440            ae.actor.setPosition(adjustX(ae.gridX, ae.doubleWidth), adjustY(ae.gridY));
1441        }
1442    }
1443
1444    /**
1445     * Start a bumping animation in the given direction that will last duration seconds.
1446     * @param ae an AnimatedEntity returned by animateActor()
1447     * @param direction
1448     * @param duration a float, measured in seconds, for how long the animation should last; commonly 0.12f
1449     */
1450    public void bump(final AnimatedEntity ae, Direction direction, float duration)
1451    {
1452        final Actor a = ae.actor;
1453        final float x = adjustX(ae.gridX, ae.doubleWidth),
1454                y = adjustY(ae.gridY);
1455        if(a == null || ae.animating) return;
1456        duration = clampDuration(duration);
1457        animationCount++;
1458        ae.animating = true;
1459        a.addAction(Actions.sequence(
1460                Actions.moveToAligned(x + direction.deltaX * cellWidth * 0.35f, y - direction.deltaY * cellHeight * 0.35f,
1461                        Align.bottomLeft, duration * 0.35F),
1462                Actions.moveToAligned(x, y, Align.bottomLeft, duration * 0.65F),
1463                Actions.delay(duration, Actions.run(new Runnable() {
1464                    @Override
1465                    public void run() {
1466                        recallActor(ae, ae.gridX, ae.gridY);
1467                    }
1468                }))));
1469
1470    }
1471    /**
1472     * Start a bumping animation in the given direction that will last duration seconds.
1473     * @param x
1474     * @param y
1475     * @param direction
1476     * @param duration a float, measured in seconds, for how long the animation should last; commonly 0.12f
1477     */
1478    public void bump(final int x, final int y, Direction direction, float duration)
1479    {
1480        final Actor a = cellToActor(x, y);
1481        if(a == null) return;
1482        duration = clampDuration(duration);
1483        animationCount++;
1484        float nextX = adjustX(x, false), nextY = adjustY(y);
1485        /*
1486        x *= cellWidth;
1487        y = (gridHeight - y - 1);
1488        y *= cellHeight;
1489        y -= 1;
1490        x +=  getX();
1491        y +=  getY();
1492        */
1493        a.addAction(Actions.sequence(
1494                Actions.moveToAligned(nextX + direction.deltaX * cellWidth * 0.35f, nextY + direction.deltaY * cellHeight * 0.35f,
1495                        Align.bottomLeft, duration * 0.35F),
1496                Actions.moveToAligned(nextX, nextY, Align.bottomLeft, duration * 0.65F),
1497                Actions.delay(duration, Actions.run(new Runnable() {
1498                    @Override
1499                    public void run() {
1500                        recallActor(a, true, x, y);
1501                    }
1502                }))));
1503
1504    }
1505
1506    /**
1507     * Starts a bumping animation in the direction provided.
1508     *
1509     * @param x
1510     * @param y
1511     * @param direction
1512     */
1513    public void bump(int x, int y, Direction direction) {
1514        bump(x, y, direction, DEFAULT_ANIMATION_DURATION);
1515    }
1516    /**
1517     * Starts a bumping animation in the direction provided.
1518     *
1519     * @param location
1520     * @param direction
1521     */
1522    public void bump(Coord location, Direction direction) {
1523        bump(location.x, location.y, direction, DEFAULT_ANIMATION_DURATION);
1524    }
1525    /**
1526     * Start a movement animation for the object at the grid location x, y and moves it to newX, newY over a number of
1527     * seconds given by duration (often 0.12f or somewhere around there).
1528     * @param ae an AnimatedEntity returned by animateActor()
1529     * @param newX
1530     * @param newY
1531     * @param duration
1532     */
1533    public void slide(final AnimatedEntity ae, final int newX, final int newY, float duration)
1534    {
1535        final Actor a = ae.actor;
1536        final float nextX = adjustX(newX, ae.doubleWidth), nextY = adjustY(newY);
1537        if(a == null || ae.animating) return;
1538        duration = clampDuration(duration);
1539        animationCount++;
1540        ae.animating = true;
1541        ae.gridX = newX;
1542        ae.gridY = newY;
1543        a.addAction(Actions.sequence(
1544                Actions.moveToAligned(nextX, nextY, Align.bottomLeft, duration),
1545                Actions.delay(duration, Actions.run(new Runnable() {
1546                    @Override
1547                    public void run() {
1548                        recallActor(ae, newX, newY);
1549                    }
1550                }))));
1551    }
1552
1553    /**
1554     * Start a movement animation for the object at the grid location x, y and moves it to newX, newY over a number of
1555     * seconds given by duration (often 0.12f or somewhere around there).
1556     * @param x
1557     * @param y
1558     * @param newX
1559     * @param newY
1560     * @param duration
1561     */
1562    public void slide(int x, int y, final int newX, final int newY, float duration)
1563    {
1564        final Actor a = cellToActor(x, y);
1565        if(a == null) return;
1566        duration = clampDuration(duration);
1567        animationCount++;
1568        float nextX = adjustX(newX, false), nextY = adjustY(newY);
1569
1570        /*
1571        newX *= cellWidth;
1572        newY = (gridHeight - newY - 1);
1573        newY *= cellHeight;
1574        newY -= 1;
1575        x += getX();
1576        y += getY();
1577        */
1578        a.addAction(Actions.sequence(
1579                Actions.moveToAligned(nextX, nextY, Align.bottomLeft, duration),
1580                Actions.delay(duration, Actions.run(new Runnable() {
1581                    @Override
1582                    public void run() {
1583                        recallActor(a, true, newX, newY);
1584                    }
1585                }))));
1586    }
1587
1588        /**
1589         * Slides {@code name} from {@code (x,y)} to {@code (newx, newy)}. If
1590         * {@code name} or {@code
1591         * color} is {@code null}, it is picked from this panel (hereby removing the
1592         * current name, if any).
1593         * 
1594         * @param x
1595         *            Where to start the slide, horizontally.
1596         * @param y
1597         *            Where to start the slide, vertically.
1598         * @param name
1599         *            The name to slide, or {@code null} to pick it from this
1600         *            panel's {@code (x,y)} cell.
1601         * @param color
1602         *            The color to use, or {@code null} to pick it from this panel's
1603         *            {@code (x,y)} cell.
1604         * @param newX
1605         *            Where to end the slide, horizontally.
1606         * @param newY
1607         *            Where to end the slide, vertically.
1608         * @param duration
1609         *            The animation's duration.
1610         */
1611        public void slide(int x, int y, final /* @Nullable */ String name, /* @Nullable */ Color color, int newX,
1612                        int newY, float duration) {
1613            slide(x, y, name, color, newX, newY, duration, null);
1614        }
1615
1616    /**
1617     * Slides {@code name} from {@code (x,y)} to {@code (newx, newy)}. If
1618     * {@code name} or {@code color} is {@code null}, it is picked from this
1619     * panel (thereby removing the current name, if any). This also allows
1620     * a Runnable to be given as {@code postRunnable} to be run after the
1621     * slide completes.
1622     *
1623     * @param x
1624     *            Where to start the slide, horizontally.
1625     * @param y
1626     *            Where to start the slide, vertically.
1627     * @param name
1628     *            The name to slide, or {@code null} to pick it from this
1629     *            panel's {@code (x,y)} cell.
1630     * @param color
1631     *            The color to use, or {@code null} to pick it from this panel's
1632     *            {@code (x,y)} cell.
1633     * @param newX
1634     *            Where to end the slide, horizontally.
1635     * @param newY
1636     *            Where to end the slide, vertically.
1637     * @param duration
1638     *            The animation's duration.
1639     * @param postRunnable a Runnable to execute after the slide completes; may be null to do nothing.
1640     */
1641    public void slide(int x, int y, final /* @Nullable */ String name, /* @Nullable */ Color color, final int newX,
1642                      final int newY, float duration, /* @Nullable */ Runnable postRunnable) {
1643        if(name != null && name.isEmpty())
1644            return;
1645        final TextCellFactory.Glyph a = createActor(x, y, name == null ? contents[x][y] : name.charAt(0),
1646                color == null ? colors[x][y] : color.toFloatBits(), false);
1647        if (a == null)
1648            return;
1649
1650        duration = clampDuration(duration);
1651        animationCount++;
1652
1653        final int nbActions = 2 + (postRunnable == null ? 0 : 1);
1654
1655        int index = 0;
1656        final Action[] sequence = new Action[nbActions];
1657        final float nextX = adjustX(newX, false);
1658        final float nextY = adjustY(newY);
1659        sequence[index++] = Actions.moveToAligned(nextX, nextY, Align.bottomLeft, duration);
1660        if(postRunnable != null)
1661        {
1662            sequence[index++] = Actions.run(postRunnable);
1663        }
1664                /* Do this one last, so that hasActiveAnimations() returns true during 'postRunnables' */
1665        sequence[index] = Actions.delay(duration, Actions.run(new Runnable() {
1666            @Override
1667            public void run() {
1668                recallActor(a, name == null, newX, newY);
1669            }
1670        }));
1671
1672        a.addAction(Actions.sequence(sequence));
1673    }
1674
1675
1676    /**
1677     * Starts a movement animation for the object at the given grid location at the default speed.
1678     *
1679     * @param start Coord to pick up a tile from and slide
1680     * @param end Coord to end the slide on
1681     */
1682    public void slide(Coord start, Coord end) {
1683        slide(start.x, start.y, end.x, end.y, DEFAULT_ANIMATION_DURATION);
1684    }
1685
1686    /**
1687     * Starts a movement animation for the object at the given grid location at the default speed for one grid square in
1688     * the direction provided.
1689     *
1690     * @param start Coord to pick up a tile from and slide
1691     * @param direction Direction enum that indicates which way the slide should go
1692     */
1693    public void slide(Coord start, Direction direction) {
1694        slide(start.x, start.y, start.x + direction.deltaX, start.y + direction.deltaY, DEFAULT_ANIMATION_DURATION);
1695    }
1696
1697    /**
1698     * Starts a sliding movement animation for the object at the given location at the provided speed. The duration is
1699     * how many seconds should pass for the entire animation.
1700     *
1701     * @param start Coord to pick up a tile from and slide
1702     * @param end Coord to end the slide on
1703     * @param duration in seconds, as a float
1704     */
1705    public void slide(Coord start, Coord end, float duration) {
1706        slide(start.x, start.y, end.x, end.y, duration);
1707    }
1708
1709    /**
1710     * Starts an wiggling animation for the object at the given location for the given duration in seconds.
1711     *
1712     * @param ae an AnimatedEntity returned by animateActor()
1713     * @param duration in seconds, as a float
1714     */
1715    public void wiggle(final AnimatedEntity ae, float duration) {
1716
1717        final Actor a = ae.actor;
1718        final float x = adjustX(ae.gridX, ae.doubleWidth), y = adjustY(ae.gridY);
1719        //final int x = ae.gridX * cellWidth * ((ae.doubleWidth) ? 2 : 1) + (int)getX(), y = (gridHeight - ae.gridY - 1) * cellHeight - 1 + (int)getY();
1720        if(a == null || ae.animating)
1721            return;
1722        duration = clampDuration(duration);
1723        ae.animating = true;
1724        animationCount++;
1725        StatefulRNG gRandom = DefaultResources.getGuiRandom();
1726        a.addAction(Actions.sequence(
1727                Actions.moveToAligned(x + (gRandom.nextFloat() - 0.5F) * cellWidth * 0.4f,
1728                        y + (gRandom.nextFloat() - 0.5F) * cellHeight * 0.4f,
1729                        Align.bottomLeft, duration * 0.2F),
1730                Actions.moveToAligned(x + (gRandom.nextFloat() - 0.5F) * cellWidth * 0.4f,
1731                        y + (gRandom.nextFloat() - 0.5F) * cellHeight * 0.4f,
1732                        Align.bottomLeft, duration * 0.2F),
1733                Actions.moveToAligned(x + (gRandom.nextFloat() - 0.5F) * cellWidth * 0.4f,
1734                        y + (gRandom.nextFloat() - 0.5F) * cellHeight * 0.4f,
1735                        Align.bottomLeft, duration * 0.2F),
1736                Actions.moveToAligned(x + (gRandom.nextFloat() - 0.5F) * cellWidth * 0.4f,
1737                        y + (gRandom.nextFloat() - 0.5F) * cellHeight * 0.4f,
1738                        Align.bottomLeft, duration * 0.2F),
1739                Actions.moveToAligned(x, y, Align.bottomLeft, duration * 0.2F),
1740                Actions.delay(duration, Actions.run(new Runnable() {
1741                    @Override
1742                    public void run() {
1743                        recallActor(ae, ae.gridX, ae.gridY);
1744                    }
1745                }))));
1746    }
1747    /**
1748     * Starts an wiggling animation for the object at the given location for the given duration in seconds.
1749     *
1750     * @param x
1751     * @param y
1752     * @param duration
1753     */
1754    public void wiggle(final int x, final int y, float duration) {
1755        final Actor a = cellToActor(x, y);
1756        if(a == null) return;
1757        duration = clampDuration(duration);
1758        animationCount++;
1759        float nextX = adjustX(x, false), nextY = adjustY(y);
1760        /*
1761        x *= cellWidth;
1762        y = (gridHeight - y - 1);
1763        y *= cellHeight;
1764        y -= 1;
1765        x +=  getX();
1766        y +=  getY();
1767        */
1768        StatefulRNG gRandom = DefaultResources.getGuiRandom();
1769        a.addAction(Actions.sequence(
1770                Actions.moveToAligned(nextX + (gRandom.nextFloat() - 0.5F) * cellWidth * 0.4f,
1771                        nextY + (gRandom.nextFloat() - 0.5F) * cellHeight * 0.4f,
1772                        Align.bottomLeft, duration * 0.2F),
1773                Actions.moveToAligned(nextX + (gRandom.nextFloat() - 0.5F) * cellWidth * 0.4f,
1774                        nextY + (gRandom.nextFloat() - 0.5F) * cellHeight * 0.4f,
1775                        Align.bottomLeft, duration * 0.2F),
1776                Actions.moveToAligned(nextX + (gRandom.nextFloat() - 0.5F) * cellWidth * 0.4f,
1777                        nextY + (gRandom.nextFloat() - 0.5F) * cellHeight * 0.4f,
1778                        Align.bottomLeft, duration * 0.2F),
1779                Actions.moveToAligned(nextX + (gRandom.nextFloat() - 0.5F) * cellWidth * 0.4f,
1780                        nextY + (gRandom.nextFloat() - 0.5F) * cellHeight * 0.4f,
1781                        Align.bottomLeft, duration * 0.2F),
1782                Actions.moveToAligned(nextX, nextY, Align.bottomLeft, duration * 0.2F),
1783                Actions.delay(duration, Actions.run(new Runnable() {
1784                    @Override
1785                    public void run() {
1786                        recallActor(a, true, x, y);
1787                    }
1788                }))));
1789    }
1790
1791    /**
1792     * Starts a tint animation for {@code ae} for the given {@code duration} in seconds.
1793     *
1794     * @param ae an AnimatedEntity returned by animateActor()
1795     * @param color what to transition ae's color towards, and then transition back from
1796     * @param duration how long the total "round-trip" transition should take in seconds
1797     */
1798    public void tint(final AnimatedEntity ae, Color color, float duration) {
1799        final Actor a = ae.actor;
1800        if(a == null)
1801            return;
1802        duration = clampDuration(duration);
1803        ae.animating = true;
1804        animationCount++;
1805        Color ac = scc.filter(a.getColor());
1806        a.addAction(Actions.sequence(
1807                Actions.color(color, duration * 0.3f),
1808                Actions.color(ac, duration * 0.7f),
1809                Actions.delay(duration, Actions.run(new Runnable() {
1810                    @Override
1811                    public void run() {
1812                        recallActor(ae, ae.gridX, ae.gridY);
1813                    }
1814                }))));
1815    }
1816
1817    /**
1818         * Like {@link #tint(int, int, Color, float)}, but waits for {@code delay}
1819         * (in seconds) before performing it.
1820     * @param delay how long to wait in seconds before starting the effect
1821     * @param x the x-coordinate of the cell to tint
1822     * @param y the y-coordinate of the cell to tint
1823     * @param color what to transition ae's color towards, and then transition back from
1824     * @param duration how long the total "round-trip" transition should take in seconds
1825     */
1826    public void tint(float delay, int x, int y, Color color, float duration) {
1827        tint(delay, x, y, color, duration, null);
1828    }
1829
1830    /**
1831     * Like {@link #tint(int, int, Color, float)}, but waits for {@code delay}
1832     * (in seconds) before performing it. Additionally, enqueue {@code postRunnable}
1833     * for running after the created action ends.
1834     * @param delay how long to wait in seconds before starting the effect
1835     * @param x the x-coordinate of the cell to tint
1836     * @param y the y-coordinate of the cell to tint
1837     * @param color what to transition ae's color towards, and then transition back from
1838     * @param duration how long the total "round-trip" transition should take in seconds
1839     * @param postRunnable a Runnable to execute after the tint completes; may be null to do nothing.
1840     */
1841
1842    public void tint(float delay, final int x, final int y, Color color, float duration, Runnable postRunnable) {
1843        final TextCellFactory.Glyph a = cellToActor(x, y);
1844        if(a == null) return;
1845        duration = clampDuration(duration);
1846        animationCount++;
1847
1848        float ac = a.getPackedColor(), c = color.toFloatBits();
1849        final int nbActions = 3 + (0 < delay ? 1 : 0) + (postRunnable == null ? 0 : 1);
1850        final Action[] sequence = new Action[nbActions];
1851        int index = 0;
1852        if (0 < delay)
1853            sequence[index++] = Actions.delay(delay);
1854        sequence[index++] = PackedColorAction.color(c, duration * 0.3f);
1855        sequence[index++] = PackedColorAction.color(ac, duration * 0.7f);
1856        if(postRunnable != null)
1857        {
1858            sequence[index++] = Actions.run(postRunnable);
1859        }
1860        /* Do this one last, so that hasActiveAnimations() returns true during 'postRunnable' */
1861        sequence[index] = Actions.run(new Runnable() {
1862            @Override
1863            public void run() {
1864                recallActor(a, true, x, y);
1865            }
1866        });
1867
1868        a.addAction(Actions.sequence(sequence));
1869    }
1870
1871    /**
1872         * Starts a tint animation for the object at {@code (x,y)} for the given
1873         * {@code duration} (in seconds).
1874     * @param x the x-coordinate of the cell to tint
1875     * @param y the y-coordinate of the cell to tint
1876     * @param color
1877     * @param duration
1878     */
1879    public void tint(int x, int y, Color color, float duration) {
1880        tint(0f, x, y, color, duration);
1881    }
1882
1883        /**
1884         * Fade the cell at {@code (x,y)} to {@code color}. Contrary to
1885         * {@link #tint(int, int, Color, float)}, this action does not restore the
1886         * cell's color at the end of its execution. This is for example useful to
1887         * fade the game screen when the rogue dies.
1888         *
1889     * @param x the x-coordinate of the cell to tint
1890     * @param y the y-coordinate of the cell to tint
1891         * @param color
1892         *            The color at the end of the fadeout.
1893         * @param duration
1894         *            The fadeout's duration.
1895         */
1896        public void fade(final int x, final int y, Color color, float duration) {
1897                final Actor a = cellToActor(x, y);
1898                if (a == null)
1899                        return;
1900        duration = clampDuration(duration);
1901                animationCount++;
1902                final Color c = scc.filter(color);
1903                a.addAction(Actions.sequence(Actions.color(c, duration), Actions.run(new Runnable() {
1904                        @Override
1905                        public void run() {
1906                                recallActor(a, true, x, y);
1907                        }
1908                })));
1909        }
1910
1911    /**
1912     * Create a new Actor at (x, y) that looks like glyph but can rotate, and immediately starts changing color from
1913     * startColor to endColor and changing rotation from startRotation to endRotation, taking duration seconds to
1914     * complete before removing the Actor.
1915     * @param x the x position in cells; doesn't change
1916     * @param y the y position in cells; doesn't change
1917     * @param glyph the char to show (the same char throughout the effect, but it can rotate)
1918     * @param startColor the starting Color
1919     * @param endColor the Color to transition to
1920     * @param startRotation the amount of rotation, in degrees, the glyph should start at
1921     * @param endRotation the amount of rotation, in degrees, the glyph should end at
1922     * @param duration the duration in seconds for the effect
1923     */
1924    public void summon(int x, int y, char glyph, Color startColor, Color endColor,
1925                       float startRotation, float endRotation, float duration)
1926    {
1927        summon(x, y, x, y, glyph, startColor, endColor, false, startRotation, endRotation, duration);
1928    }
1929    /**
1930     * Create a new Actor at (startX, startY) that looks like glyph but can rotate, sets its color, and immediately
1931     * starts changing position so it ends on the cell (endX, endY) and changing rotation from startRotation to
1932     * endRotation, taking duration seconds to complete before removing the Actor.
1933     * @param startX the starting x position in cells
1934     * @param startY the starting y position in cells
1935     * @param endX the ending x position in cells
1936     * @param endY the ending y position in cells
1937     * @param glyph the char to show (the same char throughout the effect, but it can rotate)
1938     * @param color the Color of the glyph throughout the effect
1939     * @param startRotation the amount of rotation, in degrees, the glyph should start at
1940     * @param endRotation the amount of rotation, in degrees, the glyph should end at
1941     * @param duration the duration in seconds for the effect
1942     */
1943    public void summon(int startX, int startY, int endX, int endY, char glyph, Color color,
1944                       float startRotation, float endRotation, float duration)
1945    {
1946        summon(startX, startY, endX, endY, glyph, color, color, false, startRotation, endRotation, duration);
1947    }
1948    /**
1949     * Create a new Actor at (startX, startY) that looks like glyph but has the given rotation, and immediately starts
1950     * changing color from startColor to endColor, and changing position so it ends on the cell (endX, endY), taking
1951     * duration seconds to complete before removing the Actor.
1952     * @param startX the starting x position in cells
1953     * @param startY the starting y position in cells
1954     * @param endX the ending x position in cells
1955     * @param endY the ending y position in cells
1956     * @param glyph the char to show (the same char throughout the effect, but it can rotate)
1957     * @param startColor the starting Color
1958     * @param endColor the Color to transition to
1959     * @param rotation the amount of rotation, in degrees, the glyph should have throughout the effect
1960     * @param duration the duration in seconds for the effect
1961     */
1962    public void summon(int startX, int startY, int endX, int endY, char glyph, Color startColor, Color endColor,
1963                       float rotation, float duration)
1964    {
1965        summon(startX, startY, endX, endY, glyph, startColor, endColor, false, rotation, rotation, duration);
1966    }
1967    /**
1968     * Create a new Actor at (startX, startY) that looks like glyph but can rotate, and immediately starts changing
1969     * color from startColor to endColor, changing position so it ends on the cell (endX, endY), and changing rotation
1970     * from startRotation to endRotation, taking duration seconds to complete before removing the Actor.
1971     * @param startX the starting x position in cells
1972     * @param startY the starting y position in cells
1973     * @param endX the ending x position in cells
1974     * @param endY the ending y position in cells
1975     * @param glyph the char to show (the same char throughout the effect, but it can rotate)
1976     * @param startColor the starting Color
1977     * @param endColor the Color to transition to
1978     * @param startRotation the amount of rotation, in degrees, the glyph should start at
1979     * @param endRotation the amount of rotation, in degrees, the glyph should end at
1980     * @param duration the duration in seconds for the effect
1981     */
1982    public void summon(int startX, int startY, int endX, int endY, char glyph, Color startColor, Color endColor,
1983                       float startRotation, float endRotation, float duration)
1984    {
1985        summon(startX, startY, endX, endY, glyph, startColor, endColor, false, startRotation, endRotation, duration);
1986    }
1987    /**
1988     * Create a new Actor at (startX, startY) that looks like glyph but can rotate, and immediately starts changing
1989     * color from startColor to endColor, changing position so it ends on the cell (endX, endY), and changing rotation
1990     * from startRotation to endRotation, taking duration seconds to complete before removing the Actor. Allows
1991     * setting doubleWidth, which centers the created Actor in the space between the two glyphs in a cell.
1992     * @param startX the starting x position in cells
1993     * @param startY the starting y position in cells
1994     * @param endX the ending x position in cells
1995     * @param endY the ending y position in cells
1996     * @param glyph the char to show (the same char throughout the effect, but it can rotate)
1997     * @param startColor the starting Color
1998     * @param endColor the Color to transition to
1999     * @param doubleWidth true if this uses double-width cells, false in most cases
2000     * @param startRotation the amount of rotation, in degrees, the glyph should start at
2001     * @param endRotation the amount of rotation, in degrees, the glyph should end at
2002     * @param duration the duration in seconds for the effect
2003     */
2004    public void summon(int startX, int startY, int endX, int endY, char glyph, Color startColor, Color endColor, boolean doubleWidth,
2005                       float startRotation, float endRotation, float duration)
2006    {
2007        summon(0f, startX, startY, endX, endY, glyph, startColor, endColor, doubleWidth, startRotation, endRotation, duration);
2008    }
2009    /**
2010     * Create a new Actor at (startX, startY) that looks like glyph but can rotate, and immediately starts changing
2011     * color from startColor to endColor, changing position so it ends on the cell (endX, endY), and changing rotation
2012     * from startRotation to endRotation, taking duration seconds to complete before removing the Actor. Allows
2013     * setting doubleWidth, which centers the created Actor in the space between the two glyphs in a cell.
2014     * @param delay amount of time, in seconds, to wait before starting the effect
2015     * @param startX the starting x position in cells
2016     * @param startY the starting y position in cells
2017     * @param endX the ending x position in cells
2018     * @param endY the ending y position in cells
2019     * @param glyph the char to show (the same char throughout the effect, but it can rotate)
2020     * @param startColor the starting Color
2021     * @param endColor the Color to transition to
2022     * @param doubleWidth true if this uses double-width cells, false in most cases
2023     * @param startRotation the amount of rotation, in degrees, the glyph should start at
2024     * @param endRotation the amount of rotation, in degrees, the glyph should end at
2025     * @param duration the duration in seconds for the effect
2026     */
2027    public void summon(float delay, final int startX, final int startY, final int endX, final int endY,
2028                       final char glyph, final Color startColor, final Color endColor, final boolean doubleWidth,
2029                       final float startRotation, final float endRotation, float duration)
2030
2031    {
2032        final float dur = clampDuration(duration);
2033        animationCount++;
2034        final Action[] sequence = new Action[2];
2035        if (0 < delay) {
2036            addAction(Actions.delay(delay, Actions.run(new Runnable() {
2037                @Override
2038                public void run() {
2039                    final ColorChangeImage
2040                            gi = textFactory.makeGlyphImage(glyph, scc.gradient(startColor, endColor, (int) (dur * 40)), dur * 1.1f, doubleWidth);
2041                    gi.setPosition(adjustX(startX, doubleWidth), adjustY(startY));
2042                    gi.setRotation(startRotation);
2043                    autoActors.add(gi);
2044                    sequence[0] = Actions.parallel(
2045                            Actions.moveTo(adjustX(endX, doubleWidth), adjustY(endY), dur),
2046                            Actions.rotateTo(endRotation, dur));
2047                    sequence[1] = Actions.run(new Runnable() {
2048                        @Override
2049                        public void run() {
2050                            recallActor(gi, false);
2051                        }
2052                    });
2053
2054                    gi.addAction(Actions.sequence(sequence));
2055
2056                }
2057            })));
2058        }
2059        else {
2060            final ColorChangeImage
2061                    gi = textFactory.makeGlyphImage(glyph, scc.gradient(startColor, endColor, (int) (dur * 40)), dur * 1.1f, doubleWidth);
2062            gi.setPosition(adjustX(startX, doubleWidth), adjustY(startY));
2063            gi.setRotation(startRotation);
2064            autoActors.add(gi);
2065            sequence[0] = Actions.parallel(
2066                    Actions.moveTo(adjustX(endX, doubleWidth), adjustY(endY), dur),
2067                    Actions.rotateTo(endRotation, dur));
2068            sequence[1] = Actions.run(new Runnable() {
2069                @Override
2070                public void run() {
2071                    recallActor(gi, false);
2072                }
2073            });
2074            gi.addAction(Actions.sequence(sequence));
2075        }
2076    }
2077    /**
2078     * Convenience method to produce an explosion, splash, or burst effect. Calls
2079     * {@link #summon(float, int, int, int, int, char, Color, Color, boolean, float, float, float)} repeatedly with
2080     * different parameters. As with summon(), this creates temporary Actors that change color, position, and rotation.
2081     * This overload always moves Actors 1 cell away, which is a safe default, uses a "normal" amount of rotation for
2082     * for all of the actors (a value of 1f if you used another overload), and always uses an end color that is a
2083     * modified copy of startColor with 0 alpha (making the Actors all fade to transparent). The parameter
2084     * eightWay determines whether this produces 4 (cardinal) or 8 (cardinal and diagonal) rotations and directions.
2085     * @param x the starting, center, x-position to create all Actors at
2086     * @param y the starting, center, y-position to create all Actors at
2087     * @param eightWay if true, creates 8 Actors and moves them away in a square, otherwise, 4 Actors in a diamond
2088     * @param glyph the char to make a rotate-able Actor of; should definitely be visible
2089     * @param startColor the color to start the effect with
2090     * @param duration how long, in seconds, the effect should last
2091     */
2092
2093    public void burst(int x, int y, boolean eightWay, char glyph,
2094                      Color startColor,
2095                      float duration)
2096    {
2097        burst(0f, x, y, 1, eightWay, glyph, startColor, startColor.cpy().sub(0,0,0,1), false, 1f, duration);
2098    }
2099
2100
2101    /**
2102     * Convenience method to produce an explosion, splash, or burst effect. Calls
2103     * {@link #summon(float, int, int, int, int, char, Color, Color, boolean, float, float, float)} repeatedly with
2104     * different parameters. As with summon(), this creates temporary Actors that change color, position, and rotation.
2105     * This overload always moves Actors 1 cell away, which is a safe default, and uses a "normal" amount of rotation
2106     * for all of the actors (a value of 1f if you used another overload). The parameter
2107     * eightWay determines whether this produces 4 (cardinal) or 8 (cardinal and diagonal) rotations and directions.
2108     * @param x the starting, center, x-position to create all Actors at
2109     * @param y the starting, center, y-position to create all Actors at
2110     * @param eightWay if true, creates 8 Actors and moves them away in a square, otherwise, 4 Actors in a diamond
2111     * @param glyph the char to make a rotate-able Actor of; should definitely be visible
2112     * @param startColor the color to start the effect with
2113     * @param endColor the color to end the effect on
2114     * @param duration how long, in seconds, the effect should last
2115     */
2116    public void burst(int x, int y, boolean eightWay, char glyph,
2117                      Color startColor, Color endColor,
2118                      float duration)
2119    {
2120        burst(0f, x, y, 1, eightWay, glyph, startColor, endColor, false, 1f, duration);
2121    }
2122
2123    /**
2124     * Convenience method to produce an explosion, splash, or burst effect. Calls
2125     * {@link #summon(float, int, int, int, int, char, Color, Color, boolean, float, float, float)} repeatedly with
2126     * different parameters. As with summon(), this creates temporary Actors that change color, position, and rotation.
2127     * This overload always moves Actors 1 cell away, which is a safe default. Some parameters need explanation:
2128     * eightWay determines whether this produces 4 (cardinal) or 8 (cardinal and diagonal) rotations and directions;
2129     * rotationStrength can default to 1 if you want some rotation (which looks good) or 0 if you want the Actors to
2130     * start at the correct rotation and not change that rotation over the course of the effect, but can be between 0
2131     * and 1 or higher than 1 (negative values may also work).
2132     * @param x the starting, center, x-position to create all Actors at
2133     * @param y the starting, center, y-position to create all Actors at
2134     * @param eightWay if true, creates 8 Actors and moves them away in a square, otherwise, 4 Actors in a diamond
2135     * @param glyph the char to make a rotate-able Actor of; should definitely be visible
2136     * @param startColor the color to start the effect with
2137     * @param endColor the color to end the effect on
2138     * @param rotationStrength how strongly to rotate the Actors; 0 is no rotation, 1 is a normal rotation
2139     * @param duration how long, in seconds, the effect should last
2140     */
2141    public void burst(int x, int y, boolean eightWay, char glyph,
2142                      Color startColor, Color endColor,
2143                      float rotationStrength, float duration)
2144    {
2145        burst(0f, x, y, 1, eightWay, glyph, startColor, endColor, false, rotationStrength, duration);
2146    }
2147
2148
2149    /**
2150     * Convenience method to produce an explosion, splash, or burst effect. Calls
2151     * {@link #summon(float, int, int, int, int, char, Color, Color, boolean, float, float, float)} repeatedly with
2152     * different parameters. As with summon(), this creates temporary Actors that change color, position, and rotation.
2153     * Some parameters need explanation: distance is how many cells away to move the created Actors away from (x,y);
2154     * eightWay determines whether this produces 4 (cardinal) or 8 (cardinal and diagonal) rotations and directions;
2155     * rotationStrength can default to 1 if you want some rotation (which looks good) or 0 if you want the Actors to
2156     * start at the correct rotation and not change that rotation over the course of the effect, but can be between 0
2157     * and 1 or higher than 1 (negative values may also work).
2158     * @param x the starting, center, x-position to create all Actors at
2159     * @param y the starting, center, y-position to create all Actors at
2160     * @param distance how far away, in cells, to move each actor from the center (Chebyshev distance, forming a square)
2161     * @param eightWay if true, creates 8 Actors and moves them away in a square, otherwise, 4 Actors in a diamond
2162     * @param glyph the char to make a rotate-able Actor of; should definitely be visible
2163     * @param startColor the color to start the effect with
2164     * @param endColor the color to end the effect on
2165     * @param rotationStrength how strongly to rotate the Actors; 0 is no rotation, 1 is a normal rotation
2166     * @param duration how long, in seconds, the effect should last
2167     */
2168    public void burst(int x, int y, int distance, boolean eightWay, char glyph,
2169                      Color startColor, Color endColor,
2170                      float rotationStrength, float duration)
2171    {
2172        burst(0f, x, y, distance, eightWay, glyph, startColor, endColor, false, rotationStrength, duration);
2173    }
2174
2175
2176    /**
2177     * Convenience method to produce an explosion, splash, or burst effect. Calls
2178     * {@link #summon(float, int, int, int, int, char, Color, Color, boolean, float, float, float)} repeatedly with
2179     * different parameters. As with summon(), this creates temporary Actors that change color, position, and rotation.
2180     * This overload always moves Actors 1 cell away, which is a safe default. Some parameters need explanation:
2181     * eightWay determines whether this produces 4 (cardinal) or 8 (cardinal and diagonal) rotations and directions;
2182     * rotationStrength can default to 1 if you want some rotation (which looks good) or 0 if you want the Actors to
2183     * start at the correct rotation and not change that rotation over the course of the effect, but can be between 0
2184     * and 1 or higher than 1 (negative values may also work).
2185     * @param x the starting, center, x-position to create all Actors at
2186     * @param y the starting, center, y-position to create all Actors at
2187     * @param eightWay if true, creates 8 Actors and moves them away in a square, otherwise, 4 Actors in a diamond
2188     * @param glyph the char to make a rotate-able Actor of; should definitely be visible
2189     * @param startColor the color to start the effect with
2190     * @param endColor the color to end the effect on
2191     * @param doubleWidth true if this should use the double-width-cell technique, false in most cases
2192     * @param rotationStrength how strongly to rotate the Actors; 0 is no rotation, 1 is a normal rotation
2193     * @param duration how long, in seconds, the effect should last
2194     */
2195    public void burst(int x, int y, boolean eightWay, char glyph,
2196                      Color startColor, Color endColor, boolean doubleWidth,
2197                      float rotationStrength, float duration)
2198    {
2199        burst(0f, x, y, 1, eightWay, glyph, startColor, endColor, doubleWidth, rotationStrength, duration);
2200    }
2201
2202    /**
2203     * Convenience method to produce an explosion, splash, or burst effect. Calls
2204     * {@link #summon(float, int, int, int, int, char, Color, Color, boolean, float, float, float)} repeatedly with
2205     * different parameters. As with summon(), this creates temporary Actors that change color, position, and rotation.
2206     * Some parameters need explanation: distance is how many cells away to move the created Actors away from (x,y);
2207     * eightWay determines whether this produces 4 (cardinal) or 8 (cardinal and diagonal) rotations and directions;
2208     * rotationStrength can default to 1 if you want some rotation (which looks good) or 0 if you want the Actors to
2209     * start at the correct rotation and not change that rotation over the course of the effect, but can be between 0
2210     * and 1 or higher than 1 (negative values may also work).
2211     * @param x the starting, center, x-position to create all Actors at
2212     * @param y the starting, center, y-position to create all Actors at
2213     * @param distance how far away, in cells, to move each actor from the center (Chebyshev distance, forming a square)
2214     * @param eightWay if true, creates 8 Actors and moves them away in a square, otherwise, 4 Actors in a diamond
2215     * @param glyph the char to make a rotate-able Actor of; should definitely be visible
2216     * @param startColor the color to start the effect with
2217     * @param endColor the color to end the effect on
2218     * @param doubleWidth true if this should use the double-width-cell technique, false in most cases
2219     * @param rotationStrength how strongly to rotate the Actors; 0 is no rotation, 1 is a normal rotation
2220     * @param duration how long, in seconds, the effect should last
2221     */
2222    public void burst(int x, int y, int distance, boolean eightWay, char glyph,
2223                      Color startColor, Color endColor, boolean doubleWidth,
2224                      float rotationStrength, float duration)
2225    {
2226        burst(0f, x, y, distance, eightWay, glyph, startColor, endColor, doubleWidth, rotationStrength, duration);
2227    }
2228
2229    /**
2230     * Convenience method to produce an explosion, splash, or burst effect. Calls
2231     * {@link #summon(float, int, int, int, int, char, Color, Color, boolean, float, float, float)} repeatedly with
2232     * different parameters. As with summon(), this creates temporary Actors that change color, position, and rotation.
2233     * Some parameters need explanation: distance is how many cells away to move the created Actors away from (x,y);
2234     * eightWay determines whether this produces 4 (cardinal) or 8 (cardinal and diagonal) rotations and directions;
2235     * rotationStrength can default to 1 if you want some rotation (which looks good) or 0 if you want the Actors to
2236     * start at the correct rotation and not change that rotation over the course of the effect, but can be between 0
2237     * and 1 or higher than 1 (negative values may also work).
2238     * @param delay amount of time, in seconds, to wait before starting the effect
2239     * @param x the starting, center, x-position to create all Actors at
2240     * @param y the starting, center, y-position to create all Actors at
2241     * @param distance how far away, in cells, to move each actor from the center (Chebyshev distance, forming a square)
2242     * @param eightWay if true, creates 8 Actors and moves them away in a square, otherwise, 4 Actors in a diamond
2243     * @param glyph the char to make a rotate-able Actor of; should definitely be visible
2244     * @param startColor the color to start the effect with
2245     * @param endColor the color to end the effect on
2246     * @param doubleWidth true if this should use the double-width-cell technique, false in most cases
2247     * @param rotationStrength how strongly to rotate the Actors; 0 is no rotation, 1 is a normal rotation
2248     * @param duration how long, in seconds, the effect should last
2249     */
2250    public void burst(float delay, int x, int y, int distance, boolean eightWay, char glyph,
2251                      Color startColor, Color endColor, boolean doubleWidth,
2252                      float rotationStrength, float duration)
2253    {
2254        Direction d;
2255        if(eightWay)
2256        {
2257            for (int i = 0; i < 8; i++) {
2258                d = Direction.CLOCKWISE[i];
2259                summon(delay, x, y, x - d.deltaX * distance, y + d.deltaY * distance,
2260                        glyph, startColor, endColor, doubleWidth,
2261                        45f * i, 45f * (i - rotationStrength),
2262                        duration);
2263            }
2264        }
2265        else
2266        {
2267            for (int i = 0; i < 4; i++) {
2268                d = Direction.CARDINALS_CLOCKWISE[i];
2269                summon(delay, x, y, x - d.deltaX * distance, y + d.deltaY * distance,
2270                        glyph, startColor, endColor, doubleWidth,
2271                        90f * i, 90f * (i - rotationStrength),
2272                        duration);
2273            }
2274
2275        }
2276    }
2277
2278        @Override
2279    public boolean hasActiveAnimations() {
2280        return 0 < animationCount || 0 < getActions().size;
2281    }
2282
2283    public OrderedSet<AnimatedEntity> getAnimatedEntities() {
2284        return animatedEntities;
2285    }
2286
2287    public void removeAnimatedEntity(AnimatedEntity ae)
2288    {
2289        animatedEntities.remove(ae);
2290    }
2291
2292    /**
2293         * @return The current color center. Never {@code null}.
2294         */
2295        public IColorCenter<Color> getColorCenter() {
2296                return scc;
2297        }
2298
2299        /**
2300         * Use this method if you use your own {@link IColorCenter} and want this
2301         * panel not to allocate its own colors (or fill
2302         * {@link DefaultResources#getSCC()} but instead to the provided center.
2303         * 
2304         * @param scc
2305         *            The color center to use. Should not be {@code null}.
2306         * @throws NullPointerException
2307         *             If {@code scc} is {@code null}.
2308         */
2309        public void setColorCenter(IColorCenter<Color> scc) {
2310                if (scc == null)
2311                        /* Better fail now than later */
2312                        throw new NullPointerException(
2313                                        "The color center should not be null in " + getClass().getSimpleName());
2314                this.scc = scc;
2315        }
2316
2317    public char getAt(int x, int y)
2318    {
2319        return contents[x][y];
2320    }
2321    public Color getColorAt(int x, int y)
2322    {
2323        return SColor.colorFromFloat(tmpColor, colors[x][y]);
2324    }
2325
2326    public Color getLightingColor() {
2327        return lightingColor;
2328    }
2329
2330    public void setLightingColor(Color lightingColor) {
2331        this.lightingColor = lightingColor;
2332        lightingFloat = lightingColor.toFloatBits();
2333    }
2334
2335    protected float clampDuration(float duration) {
2336        return Math.max(duration, 0.02f);
2337    }
2338
2339    /**
2340     * The X offset that the whole panel's internals will be rendered at. If the {@code gridWidth} of this SquidPanel is
2341     * less than the actual size of the char[][] it renders, then you can use gridOffsetX to start rendering at a
2342     * different position
2343     * @return the current offset in cells along the x axis
2344     */
2345    public int getGridOffsetX() {
2346        return gridOffsetX;
2347    }
2348
2349    /**
2350     * Sets the X offset that the whole panel's internals will be rendered at.
2351     * @param gridOffsetX the requested offset in cells
2352     */
2353    public void setGridOffsetX(int gridOffsetX) {
2354        this.gridOffsetX = clamp(gridOffsetX,0,  contents.length - gridWidth);
2355    }
2356
2357    /**
2358     * The Y offset that the whole panel's internals will be rendered at.
2359     * @return the current offset in cells along the y axis
2360     */
2361    public int getGridOffsetY() {
2362        return gridOffsetY;
2363    }
2364
2365    /**
2366     * Sets the Y offset that the whole panel's internals will be rendered at.
2367     * @param gridOffsetY the requested offset in cells
2368     */
2369    public void setGridOffsetY(int gridOffsetY) {
2370        this.gridOffsetY = clamp(gridOffsetY,0,  contents[0].length - gridHeight);
2371    }
2372
2373    /**
2374     * The number of cells along the x-axis that will be rendered of this panel.
2375     * @return the number of cells along the x-axis that will be rendered of this panel
2376     */
2377    public int getGridWidth() {
2378        return gridWidth;
2379    }
2380
2381    /**
2382     * Sets the number of cells along the x-axis that will be rendered of this panel to gridWidth.
2383     * @param gridWidth the requested width in cells
2384     */
2385    public void setGridWidth(int gridWidth) {
2386        this.gridWidth = gridWidth;
2387    }
2388
2389    /**
2390     * The number of cells along the y-axis that will be rendered of this panel
2391     * @return the number of cells along the y-axis that will be rendered of this panel
2392     */
2393    public int getGridHeight() {
2394        return gridHeight;
2395    }
2396
2397    /**
2398     * Sets the number of cells along the y-axis that will be rendered of this panel to gridHeight.
2399     * @param gridHeight the requested height in cells
2400     */
2401    public void setGridHeight(int gridHeight) {
2402        this.gridHeight = gridHeight;
2403    }
2404
2405    /**
2406     * Gets the total number of cells along the x-axis that this stores; this is usually equivalent to
2407     * {@link #getGridWidth()}, but not if the constructor
2408     * {@link #SquidPanel(int, int, TextCellFactory, IColorCenter, float, float, char[][])} was used to set a
2409     * larger-than-normal map.
2410     * @return the width of the internal array this can render, which may be larger than the visible width
2411     */
2412    public int getTotalWidth()
2413    {
2414        return contents.length;
2415    }
2416    /**
2417     * Gets the total number of cells along the y-axis that this stores; this is usually equivalent to
2418     * {@link #getGridHeight()}, but not if the constructor
2419     * {@link #SquidPanel(int, int, TextCellFactory, IColorCenter, float, float, char[][])} was used to set a
2420     * larger-than-normal map.
2421     * @return the height of the internal array this can render, which may be larger than the visible height
2422     */
2423    public int getTotalHeight()
2424    {
2425        return contents[0].length;
2426    }
2427
2428    /**
2429     * Sets the position of the actor's bottom left corner.
2430     *
2431     * @param x
2432     * @param y
2433     */
2434    @Override
2435    public void setPosition(float x, float y) {
2436        super.setPosition(x, y);
2437        setBounds(x, y, getWidth(), getHeight());
2438    }
2439
2440    public float getxOffset() {
2441        return xOffset;
2442    }
2443
2444    public void setOffsetX(float xOffset) {
2445        this.xOffset = xOffset;
2446    }
2447
2448    public float getyOffset() {
2449        return yOffset;
2450    }
2451
2452    public void setOffsetY(float yOffset) {
2453        this.yOffset = yOffset;
2454    }
2455
2456    public void setOffsets(float x, float y) {
2457        xOffset = x;
2458        yOffset = y;
2459    }
2460
2461    /**
2462     * Gets the status of a boolean flag used for rendering thin maps; it will almost always be false unless it
2463     * was set to true with {@link #setOnlyRenderEven(boolean)}.
2464     * <br>
2465     * This is meant for thin-wall maps, where only cells where x and y are both even numbers have backgrounds
2466     * displayed. Should be false when using this SquidPanel for anything that isn't specifically a background
2467     * of a map that uses the thin-wall method from ThinDungeonGenerator or something similar. Even the
2468     * foregrounds of thin-wall maps should have this false, since ThinDungeonGenerator (in conjunction with
2469     * DungeonUtility's hashesToLines() method) makes thin lines for walls that should be displayed as between
2470     * the boundaries of other cells. The overlap behavior needed for some "thin enough" cells to be displayed
2471     * between the cells can be accomplished by using {@link #setTextSize(float, float)} to double the
2472     * previously-given cell width and height.
2473     *
2474     * @return the current status of the onlyRenderEven flag, which defaults to false
2475     */
2476    public boolean getOnlyRenderEven() {
2477        return onlyRenderEven;
2478    }
2479    /**
2480     * Sets the status of a boolean flag used for rendering thin maps; it should almost always be the default,
2481     * which is false, unless you are using a thin-wall map, and then this should be true only if this
2482     * SquidPanel is used for the background layer.
2483     * <br>
2484     * This is meant for thin-wall maps, where only cells where x and y are both even numbers have backgrounds
2485     * displayed. Should be false when using this SquidPanel for anything that isn't specifically a background
2486     * of a map that uses the thin-wall method from ThinDungeonGenerator or something similar. Even the
2487     * foregrounds of thin-wall maps should have this false, since ThinDungeonGenerator (in conjunction with
2488     * DungeonUtility's hashesToLines() method) makes thin lines for walls that should be displayed as between
2489     * the boundaries of other cells. The overlap behavior needed for some "thin enough" cells to be displayed
2490     * between the cells can be accomplished by using {@link #setTextSize(float, float)} to double the
2491     * previously-given cell width and height.
2492     *
2493     * @param onlyRenderEven generally, should only be true if this SquidPanel is a background of a thin map
2494     */
2495
2496    public void setOnlyRenderEven(boolean onlyRenderEven) {
2497        this.onlyRenderEven = onlyRenderEven;
2498    }
2499
2500    /**
2501     * Gets a "snapshot" of the data represented by this SquidPanel; stores the dimensions, the char data, and the color
2502     * data in a way that can be set back to a SquidPanel using {@link #setFromSnapshot(String, int, int, int, int)} or
2503     * its overload that takes a StringBuilder. The actual contents of the returned StringBuilder are unlikely to be
2504     * legible in any way if read as text, and are meant to be concise and stable across versions.
2505     * @return a StringBuilder representation of this SquidPanel's data that can be passed later to {@link #setFromSnapshot(StringBuilder, int, int, int, int)} or converted to String and passed to its overload
2506     */
2507    public StringBuilder getSnapshot()
2508    {
2509        return getSnapshot(0, 0, gridWidth, gridHeight);
2510    }
2511    /**
2512     * Gets a "snapshot" of the data represented by this SquidPanel; stores the dimensions, the char data, and the color
2513     * data in a way that can be set back to a SquidPanel using {@link #setFromSnapshot(String, int, int, int, int)} or
2514     * its overload that takes a StringBuilder. The actual contents of the returned StringBuilder are unlikely to be
2515     * legible in any way if read as text, and are meant to be concise and stable across versions. This overload allows
2516     * the first x and y position used to be specified, as well as the width and height to use (the actual width and
2517     * height stored may be different if this SquidPanel's gridWidth and/or gridHeight are smaller than the width and/or
2518     * height given).
2519     * @param startX the first x position to use in the snapshot, inclusive
2520     * @param startY the first y position to use in the snapshot, inclusive
2521     * @param width how wide the snapshot area should be; x positions from startX to startX + width - 1 will be used
2522     * @param height how tall the snapshot area should be; y positions from startY to startY + height - 1 will be used
2523     * @return a StringBuilder representation of this SquidPanel's data that can be passed later to {@link #setFromSnapshot(StringBuilder, int, int, int, int)} or converted to String and passed to its overload
2524     */
2525    public StringBuilder getSnapshot(int startX, int startY, int width, int height) {
2526        width = Math.min(gridWidth - startX, width);
2527        height = Math.min(gridHeight - startY, height);
2528        StringBuilder sb = new StringBuilder(width * height * 9 + 12);
2529        sb.append(width).append('x').append(height).append(':');
2530        for (int x = startX, i = 0; i < width; x++, i++) {
2531            sb.append(contents[x], startY, height);
2532        }
2533        char[] reuse = new char[8];
2534        for (int x = startX, i = 0; i < width; x++, i++) {
2535            for (int y = startY, j = 0; j < height; y++, j++) {
2536                sb.append(SColor.floatToChars(reuse, colors[x][y]));
2537            }
2538        }
2539        return sb;
2540    }
2541
2542    /**
2543     * Given a "snapshot" from {@link #getSnapshot(int, int, int, int)}, this assigns the chars and colors in this
2544     * SquidPanel from 0,0 (inclusive) up to the dimensions stored in the snapshot to match the snapshot's data.
2545     * @param snapshot a StringBuilder in a special format as produced by {@link #getSnapshot(int, int, int, int)}
2546     * @return this after setting, for chaining
2547     */
2548    public SquidPanel setFromSnapshot(StringBuilder snapshot)
2549    {
2550        return setFromSnapshot(snapshot, 0, 0, -1, -1);
2551    }
2552    /**
2553     * Given a "snapshot" from {@link #getSnapshot(int, int, int, int)}, this assigns the chars and colors in this
2554     * SquidPanel from the position given by putX,putY (inclusive) up to the dimensions stored in the snapshot
2555     * (considering putX and putY as offsets) so they have the values stored in the snapshot.
2556     * @param snapshot a StringBuilder in a special format as produced by {@link #getSnapshot(int, int, int, int)}
2557     * @param putX where to start placing the data from the snapshot, x position
2558     * @param putY where to start placing the data from the snapshot, y position
2559     * @return this after setting, for chaining
2560     */
2561    public SquidPanel setFromSnapshot(StringBuilder snapshot, int putX, int putY)
2562    {
2563        return setFromSnapshot(snapshot, putX, putY, -1, -1);
2564    }
2565    /**
2566     * Given a "snapshot" from {@link #getSnapshot(int, int, int, int)}, this assigns the chars and colors in this
2567     * SquidPanel from the position given by putX,putY (inclusive) to putX+limitWidth,putY+limitHeight (exclusive) so
2568     * they have the values stored in the snapshot. If limitWidth or limitHeight is negative, this uses the full width
2569     * and height of the snapshot (stopping early if it would extend past the gridWidth or gridHeight of this
2570     * SquidPanel).
2571     * @param snapshot a StringBuilder in a special format as produced by {@link #getSnapshot(int, int, int, int)}
2572     * @param putX where to start placing the data from the snapshot, x position
2573     * @param putY where to start placing the data from the snapshot, y position
2574     * @param limitWidth if negative, uses all of snapshot's width as possible, otherwise restricts the width allowed
2575     * @param limitHeight if negative, uses all of snapshot's height as possible, otherwise restricts the height allowed
2576     * @return this after setting, for chaining
2577     */
2578    public SquidPanel setFromSnapshot(StringBuilder snapshot, int putX, int putY, int limitWidth, int limitHeight)
2579    {
2580        if(putX >= gridWidth || putY >= gridHeight || snapshot == null || snapshot.length() < 4) return this;
2581        if(putX < 0) putX = 0;
2582        if(putY < 0) putY = 0;
2583        int start = snapshot.indexOf(":")+1, width = StringKit.intFromDec(snapshot),
2584                height = StringKit.intFromDec(snapshot, snapshot.indexOf("x") + 1, start),
2585                run = start;
2586        if(limitWidth < 0)
2587            limitWidth = Math.min(width, gridWidth - putX);
2588        else
2589            limitWidth = Math.min(limitWidth, Math.min(width, gridWidth - putX));
2590
2591        if(limitHeight < 0)
2592            limitHeight = Math.min(height, gridHeight - putY);
2593        else
2594            limitHeight = Math.min(limitHeight, Math.min(height, gridHeight - putY));
2595        for (int x = putX, i = 0; i < limitWidth; x++, i++, run += height) {
2596            snapshot.getChars(run, run + limitHeight, contents[x], putY);
2597        }
2598        run = start + width * height;
2599        for (int x = putX, i = 0; i < limitWidth; x++, i++) {
2600            for (int y = putY, j = 0; j < limitHeight; y++, j++) {
2601                colors[x][y] = SColor.charsToFloat(snapshot, run);
2602                run += 8;
2603            }
2604        }
2605        return this;
2606    }
2607
2608    /**
2609     * Given a "snapshot" from {@link #getSnapshot(int, int, int, int)}, this assigns the chars and colors in this
2610     * SquidPanel from 0,0 (inclusive) up to the dimensions stored in the snapshot to match the snapshot's data.
2611     * <br>
2612     * This overload takes a String instead of a StringBuilder for potentially-easier loading from files.
2613     * @param snapshot a String in a special format as produced by {@link #getSnapshot(int, int, int, int)}
2614     * @return this after setting, for chaining
2615     */
2616    public SquidPanel setFromSnapshot(String snapshot)
2617    {
2618        return setFromSnapshot(snapshot, 0, 0, -1, -1);
2619    }
2620    /**
2621     * Given a "snapshot" from {@link #getSnapshot(int, int, int, int)}, this assigns the chars and colors in this
2622     * SquidPanel from the position given by putX,putY (inclusive) up to the dimensions stored in the snapshot
2623     * (considering putX and putY as offsets) so they have the values stored in the snapshot.
2624     * <br>
2625     * This overload takes a String instead of a StringBuilder for potentially-easier loading from files.
2626     * @param snapshot a String in a special format as produced by {@link #getSnapshot(int, int, int, int)}
2627     * @param putX where to start placing the data from the snapshot, x position
2628     * @param putY where to start placing the data from the snapshot, y position
2629     * @return this after setting, for chaining
2630     */
2631    public SquidPanel setFromSnapshot(String snapshot, int putX, int putY)
2632    {
2633        return setFromSnapshot(snapshot, putX, putY, -1, -1);
2634    }
2635
2636    /**
2637     * Given a "snapshot" from {@link #getSnapshot(int, int, int, int)}, this assigns the chars and colors in this
2638     * SquidPanel from the position given by putX,putY (inclusive) to putX+limitWidth,putY+limitHeight (exclusive) so
2639     * they have the values stored in the snapshot. If limitWidth or limitHeight is negative, this uses the full width
2640     * and height of the snapshot (stopping early if it would extend past the gridWidth or gridHeight of this
2641     * SquidPanel).
2642     * <br>
2643     * This overload takes a String instead of a StringBuilder for potentially-easier loading from files.
2644     * @param snapshot a String in a special format as produced by {@link #getSnapshot(int, int, int, int)}
2645     * @param putX where to start placing the data from the snapshot, x position
2646     * @param putY where to start placing the data from the snapshot, y position
2647     * @param limitWidth if negative, uses all of snapshot's width as possible, otherwise restricts the width allowed
2648     * @param limitHeight if negative, uses all of snapshot's height as possible, otherwise restricts the height allowed
2649     * @return this after setting, for chaining
2650     */
2651
2652    public SquidPanel setFromSnapshot(String snapshot, int putX, int putY, int limitWidth, int limitHeight)
2653    {
2654        if(putX >= gridWidth || putY >= gridHeight || snapshot == null || snapshot.length() < 4) return this;
2655        if(putX < 0) putX = 0;
2656        if(putY < 0) putY = 0;
2657        int start = snapshot.indexOf(":")+1, width = StringKit.intFromDec(snapshot),
2658                height = StringKit.intFromDec(snapshot, snapshot.indexOf("x") + 1, start),
2659                run = start;
2660        if(limitWidth < 0)
2661            limitWidth = Math.min(width, gridWidth - putX);
2662        else
2663            limitWidth = Math.min(limitWidth, Math.min(width, gridWidth - putX));
2664
2665        if(limitHeight < 0)
2666            limitHeight = Math.min(height, gridHeight - putY);
2667        else
2668            limitHeight = Math.min(limitHeight, Math.min(height, gridHeight - putY));
2669        for (int x = putX, i = 0; i < limitWidth; x++, i++, run += height) {
2670            snapshot.getChars(run, run + limitHeight, contents[x], putY);
2671        }
2672        run = start + width * height;
2673        for (int x = putX, i = 0; i < limitWidth; x++, i++) {
2674            for (int y = putY, j = 0; j < limitHeight; y++, j++) {
2675                colors[x][y] = SColor.charsToFloat(snapshot, run);
2676                run += 8;
2677            }
2678        }
2679        return this;
2680    }
2681}