001package squidpony.squidgrid.gui.gdx;
002
003import java.util.ArrayList;
004import java.util.LinkedHashSet;
005
006import squidpony.panel.IColoredString;
007import squidpony.panel.ISquidPanel;
008import squidpony.squidgrid.Direction;
009import squidpony.squidmath.Coord;
010
011import com.badlogic.gdx.Gdx;
012import com.badlogic.gdx.graphics.Color;
013import com.badlogic.gdx.graphics.g2d.Batch;
014import com.badlogic.gdx.graphics.g2d.TextureRegion;
015import com.badlogic.gdx.scenes.scene2d.Actor;
016import com.badlogic.gdx.scenes.scene2d.Group;
017import com.badlogic.gdx.scenes.scene2d.actions.Actions;
018import com.badlogic.gdx.utils.Align;
019import squidpony.squidmath.LightRNG;
020import squidpony.squidmath.StatefulRNG;
021
022/**
023 * Displays text and images in a grid pattern. Supports basic animations.
024 * 
025 * Grid width and height settings are in terms of number of cells. Cell width and height
026 * are in terms of number of pixels.
027 *
028 * When text is placed, the background color is set separately from the foreground character. When moved, only the
029 * foreground character is moved.
030 *
031 * @author Eben Howard - http://squidpony.com - howard@squidpony.com
032 */
033public class SquidPanel extends Group implements ISquidPanel<Color> {
034
035    public float DEFAULT_ANIMATION_DURATION = 0.12F;
036    private int animationCount = 0;
037    private Color defaultForeground = Color.WHITE;
038    private final int gridWidth, gridHeight, cellWidth, cellHeight;
039    private String[][] contents;
040    private int[][] colors;
041    private final TextCellFactory textFactory;
042    private LinkedHashSet<AnimatedEntity> animatedEntities;
043
044    /**
045     * Creates a bare-bones panel with all default values for text rendering.
046     * 
047     * @param gridWidth the number of cells horizontally
048     * @param gridHeight the number of cells vertically
049     */
050    public SquidPanel(int gridWidth, int gridHeight) {
051        this(gridWidth, gridHeight, new TextCellFactory().defaultSquareFont());
052    }
053
054    /**
055     * Creates a panel with the given grid and cell size. Uses a default square font.
056     *
057     * @param gridWidth the number of cells horizontally
058     * @param gridHeight the number of cells vertically
059     * @param cellWidth the number of horizontal pixels in each cell
060     * @param cellHeight the number of vertical pixels in each cell
061     */
062    public SquidPanel(int gridWidth, int gridHeight, int cellWidth, int cellHeight) {
063        this(gridWidth, gridHeight, new TextCellFactory().defaultSquareFont().width(cellWidth).height(cellHeight));
064    }
065
066    /**
067     * Builds a panel with the given grid size and all other parameters determined by the factory. Even if sprite images
068     * are being used, a TextCellFactory is still needed to perform sizing and other utility functions.
069     * 
070     * If the TextCellFactory has not yet been initialized, then it will be sized at 12x12 px per cell. If it is null
071     * then a default one will be created and initialized.
072     *
073     * @param gridWidth the number of cells horizontally
074     * @param gridHeight the number of cells vertically
075     * @param factory the factory to use for cell rendering
076     */
077    public SquidPanel(int gridWidth, int gridHeight, TextCellFactory factory) {
078        this.gridWidth = gridWidth;
079        this.gridHeight = gridHeight;
080        textFactory = factory;
081
082        if (factory == null) {
083            factory = new TextCellFactory();
084        }
085
086        if (!factory.initialized()) {
087            factory.initByFont();
088        }
089
090        cellWidth = factory.width();
091        cellHeight = factory.height();
092
093        contents = new String[gridWidth][gridHeight];
094        colors = new int[gridWidth][gridHeight];
095
096        int w = gridWidth * cellWidth;
097        int h = gridHeight * cellHeight;
098        setSize(w, h);
099        animatedEntities = new LinkedHashSet<AnimatedEntity>();
100    }
101
102    /**
103     * Places the given characters into the grid starting at 0,0.
104     *
105     * @param chars
106     */
107    public void put(char[][] chars) {
108        SquidPanel.this.put(0, 0, chars);
109    }
110
111    @Override
112        public void put(char[][] chars, Color[][] foregrounds) {
113        SquidPanel.this.put(0, 0, chars, foregrounds);
114    }
115
116    public void put(char[][] chars, int[][] indices, ArrayList<Color> palette) {
117        SquidPanel.this.put(0, 0, chars, indices, palette);
118    }
119
120    public void put(int xOffset, int yOffset, char[][] chars) {
121        SquidPanel.this.put(xOffset, yOffset, chars, defaultForeground);
122    }
123
124    public void put(int xOffset, int yOffset, char[][] chars, Color[][] foregrounds) {
125        for (int x = xOffset; x < xOffset + chars.length; x++) {
126            for (int y = yOffset; y < yOffset + chars[0].length; y++) {
127                if (x >= 0 && y >= 0 && x < gridWidth && y < gridHeight) {//check for valid input
128                    SquidPanel.this.put(x, y, chars[x - xOffset][y - yOffset], foregrounds[x - xOffset][y - yOffset]);
129                }
130            }
131        }
132    }
133
134    public void put(int xOffset, int yOffset, char[][] chars, int[][] indices, ArrayList<Color> palette) {
135        for (int x = xOffset; x < xOffset + chars.length; x++) {
136            for (int y = yOffset; y < yOffset + chars[0].length; y++) {
137                if (x >= 0 && y >= 0 && x < gridWidth && y < gridHeight) {//check for valid input
138                    SquidPanel.this.put(x, y, chars[x - xOffset][y - yOffset], palette.get(indices[x - xOffset][y - yOffset]));
139                }
140            }
141        }
142    }
143
144    public void put(int xOffset, int yOffset, Color[][] foregrounds) {
145        for (int x = xOffset; x < xOffset + foregrounds.length; x++) {
146            for (int y = yOffset; y < yOffset + foregrounds[0].length; y++) {
147                if (x >= 0 && y >= 0 && x < gridWidth && y < gridHeight) {//check for valid input
148                    SquidPanel.this.put(x, y, '\0', foregrounds[x - xOffset][y - yOffset]);
149                }
150            }
151        }
152    }
153
154    public void put(int xOffset, int yOffset, int[][] indices, ArrayList<Color> palette) {
155        for (int x = xOffset; x < xOffset + indices.length; x++) {
156            for (int y = yOffset; y < yOffset + indices[0].length; y++) {
157                if (x >= 0 && y >= 0 && x < gridWidth && y < gridHeight) {//check for valid input
158                    SquidPanel.this.put(x, y, '\0', palette.get(indices[x - xOffset][y - yOffset]));
159                }
160            }
161        }
162    }
163
164    public void put(int xOffset, int yOffset, char[][] chars, Color foreground) {
165        for (int x = xOffset; x < xOffset + chars.length; x++) {
166            for (int y = yOffset; y < yOffset + chars[0].length; y++) {
167                if (x >= 0 && y >= 0 && x < gridWidth && y < gridHeight) {//check for valid input
168                    SquidPanel.this.put(x, y, chars[x - xOffset][y - yOffset], foreground);
169                }
170            }
171        }
172    }
173
174    /**
175     * Puts the given string horizontally with the first character at the given offset.
176     *
177     * Does not word wrap. Characters that are not renderable (due to being at negative offsets or offsets greater than
178     * the grid size) will not be shown but will not cause any malfunctions.
179     *
180     * Will use the default color for this component to draw the characters.
181     *
182     * @param xOffset the x coordinate of the first character
183     * @param yOffset the y coordinate of the first character
184     * @param string the characters to be displayed
185     */
186    public void put(int xOffset, int yOffset, String string) {
187        SquidPanel.this.put(xOffset, yOffset, string, defaultForeground);
188    }
189
190        @Override
191        public void put(int xOffset, int yOffset, IColoredString<? extends Color> cs) {
192                int x = xOffset;
193                for (IColoredString.Bucket<? extends Color> fragment : cs) {
194                        final String s = fragment.getText();
195                        final Color color = fragment.getColor();
196                        put(x, yOffset, s, color == null ? getDefaultForegroundColor() : color);
197                        x += s.length();
198                }
199        }
200
201        @Override
202        public void put(int xOffset, int yOffset, String string, Color foreground) {
203        char[][] temp = new char[string.length()][1];
204        for (int i = 0; i < string.length(); i++) {
205            temp[i][0] = string.charAt(i);
206        }
207        SquidPanel.this.put(xOffset, yOffset, temp, foreground);
208    }
209
210    /**
211     * Puts the given string horizontally with the first character at the given offset.
212     *
213     * Does not word wrap. Characters that are not renderable (due to being at negative offsets or offsets greater than
214     * the grid size) will not be shown but will not cause any malfunctions.
215     *
216     * Will use the default color for this component to draw the characters.
217     *
218     * @param xOffset the x coordinate of the first character
219     * @param yOffset the y coordinate of the first character
220     * @param string the characters to be displayed
221     * @param vertical true if the text should be written vertically, from top to bottom
222     */
223    public void placeVerticalString(int xOffset, int yOffset, String string, boolean vertical) {
224        SquidPanel.this.put(xOffset, yOffset, string, defaultForeground, vertical);
225    }
226
227    /**
228     * Puts the given string horizontally with the first character at the given offset.
229     *
230     * Does not word wrap. Characters that are not renderable (due to being at negative offsets or offsets greater than
231     * the grid size) will not be shown but will not cause any malfunctions.
232     *
233     * @param xOffset the x coordinate of the first character
234     * @param yOffset the y coordinate of the first character
235     * @param string the characters to be displayed
236     * @param foreground the color to draw the characters
237     * @param vertical true if the text should be written vertically, from top to bottom
238     */
239    public void put(int xOffset, int yOffset, String string, Color foreground, boolean vertical) {
240        if (vertical) {
241            SquidPanel.this.put(xOffset, yOffset, new char[][]{string.toCharArray()}, foreground);
242        } else {
243            SquidPanel.this.put(xOffset, yOffset, string, foreground);
244        }
245    }
246
247    /**
248     * Erases the entire panel, leaving only a transparent space.
249     */
250    public void erase() {
251        for (int i = 0; i < contents.length; i++) {
252            for (int j = 0; j < contents[i].length; j++) {
253                contents[i][j] = "";
254                colors[i][j] = (255 << 24);
255            }
256
257        }
258    }
259
260    @Override
261        public void clear(int x, int y) {
262        this.put(x, y, Color.CLEAR);
263    }
264
265    @Override
266        public void put(int x, int y, Color color) {
267        put(x, y, '\0', color);
268    }
269
270    @Override
271        public void put(int x, int y, char c) {
272        put(x, y, c, defaultForeground);
273    }
274
275    /**
276     * Takes a unicode codepoint for input.
277     *
278     * @param x
279     * @param y
280     * @param code
281     */
282    public void put(int x, int y, int code) {
283        put(x, y, code, defaultForeground);
284    }
285
286    public void put(int x, int y, int c, Color color) {
287        put(x, y, String.valueOf(Character.toChars(c)), color);
288    }
289
290    public void put(int x, int y, int index, ArrayList<Color> palette) {
291        put(x, y, palette.get(index));
292    }
293
294    public void put(int x, int y, char c, int index, ArrayList<Color> palette) {
295        put(x, y, c, palette.get(index));
296    }
297
298    /**
299     * Takes a unicode codepoint for input.
300     *
301     * @param x
302     * @param y
303     * @param c
304     * @param color
305     */
306    @Override
307        public void put(int x, int y, char c, Color color) {
308        if (x < 0 || x >= gridWidth || y < 0 || y >= gridHeight) {
309            return;//skip if out of bounds
310        }
311        contents[x][y] = String.valueOf(c);
312        colors[x][y] = Color.rgba8888(color);
313    }
314
315    public int cellWidth() {
316        return cellWidth;
317    }
318
319    public int cellHeight() {
320        return cellHeight;
321    }
322
323    @Override
324        public int gridHeight() {
325        return gridHeight;
326    }
327
328    @Override
329        public int gridWidth() {
330        return gridWidth;
331    }
332
333    @Override
334    public void draw (Batch batch, float parentAlpha) {
335        Color tmp = new Color();
336        for (int x = 0; x < gridWidth; x++) {
337            for (int y = 0; y < gridHeight; y++) {
338                Color.rgba8888ToColor(tmp, colors[x][y]);
339                textFactory.draw(batch, contents[x][y], tmp, x * cellWidth, (gridHeight - y) * cellHeight);
340            }
341        }
342        super.draw(batch, parentAlpha);
343        for(AnimatedEntity ae : animatedEntities)
344        {
345            ae.actor.act(Gdx.graphics.getDeltaTime());
346        }
347    }
348
349    /**
350     * Draws one AnimatedEntity, specifically the Actor it contains. Batch must be between start() and end()
351     * @param batch Must have start() called already but not stop() yet during this frame.
352     * @param parentAlpha This can be assumed to be 1.0f if you don't know it
353     * @param ae The AnimatedEntity to draw; the position to draw ae is stored inside it.
354     */
355    public void drawActor(Batch batch, float parentAlpha, AnimatedEntity ae)
356    {
357            ae.actor.draw(batch, parentAlpha);
358    }
359
360    @Override
361        public void setDefaultForeground(Color defaultForeground) {
362        this.defaultForeground = defaultForeground;
363    }
364
365        @Override
366        public Color getDefaultForegroundColor() {
367                return defaultForeground;
368        }
369
370    public AnimatedEntity getAnimatedEntityByCell(int x, int y) {
371        for(AnimatedEntity ae : animatedEntities)
372        {
373            if(ae.gridX == x && ae.gridY == y)
374                return ae;
375        }
376        return  null;
377    }
378
379    /**
380     * Create an AnimatedEntity at position x, y, using the char c in the given color.
381     * @param x
382     * @param y
383     * @param c
384     * @param color
385     * @return
386     */
387    public AnimatedEntity animateActor(int x, int y, char c, Color color)
388    {
389        Actor a = textFactory.makeActor("" + c, color);
390        a.setName("" + c);
391        a.setPosition(x * cellWidth, (gridHeight - y - 1) * cellHeight - 1);
392
393        AnimatedEntity ae = new AnimatedEntity(a, x, y);
394        animatedEntities.add(ae);
395        return ae;
396    }
397
398    /**
399     * Create an AnimatedEntity at position x, y, using the char c in the given color. If doubleWidth is true, treats
400     * the char c as the left char to be placed in a grid of 2-char cells.
401     * @param x
402     * @param y
403     * @param doubleWidth
404     * @param c
405     * @param color
406     * @return
407     */
408    public AnimatedEntity animateActor(int x, int y, boolean doubleWidth, char c, Color color)
409    {
410        Actor a = textFactory.makeActor("" + c, color);
411        a.setName("" + c);
412        if(doubleWidth)
413            a.setPosition(x * 2 * cellWidth, (gridHeight - y - 1) * cellHeight - 1);
414        else
415            a.setPosition(x * cellWidth, (gridHeight - y - 1) * cellHeight - 1);
416
417        AnimatedEntity ae = new AnimatedEntity(a, x, y);
418        animatedEntities.add(ae);
419        return ae;
420    }
421
422    /**
423     * Create an AnimatedEntity at position x, y, using the String s in the given color.
424     * @param x
425     * @param y
426     * @param s
427     * @param color
428     * @return
429     */
430    public AnimatedEntity animateActor(int x, int y, String s, Color color)
431    {
432        Actor a = textFactory.makeActor(s, color);
433        a.setName(s);
434        a.setPosition(x * cellWidth, (gridHeight - y - 1) * cellHeight - 1);
435
436        AnimatedEntity ae = new AnimatedEntity(a, x, y);
437        animatedEntities.add(ae);
438        return ae;
439    }
440
441    /**
442     * Create an AnimatedEntity at position x, y, using the String s in the given color. If doubleWidth is true, treats
443     * the String s as starting in the left cell of a pair to be placed in a grid of 2-char cells.
444     * @param x
445     * @param y
446     * @param doubleWidth
447     * @param s
448     * @param color
449     * @return
450     */
451    public AnimatedEntity animateActor(int x, int y, boolean doubleWidth, String s, Color color)
452    {
453        Actor a = textFactory.makeActor(s, color);
454        a.setName(s);
455        if(doubleWidth)
456            a.setPosition(x * 2 * cellWidth, (gridHeight - y - 1) * cellHeight - 1);
457        else
458            a.setPosition(x * cellWidth, (gridHeight - y - 1) * cellHeight - 1);
459
460        AnimatedEntity ae = new AnimatedEntity(a, x, y, doubleWidth);
461        animatedEntities.add(ae);
462        return ae;
463    }
464
465    /**
466     * Create an AnimatedEntity at position x, y, using the char c with a color looked up by index in palette.
467     * @param x
468     * @param y
469     * @param c
470     * @param index
471     * @param palette
472     * @return
473     */
474    public AnimatedEntity animateActor(int x, int y, char c, int index, ArrayList<Color> palette)
475    {
476        return animateActor(x, y, c, palette.get(index));
477    }
478
479    /**
480     * Create an AnimatedEntity at position x, y, using the String s with a color looked up by index in palette.
481     * @param x
482     * @param y
483     * @param s
484     * @param index
485     * @param palette
486     * @return
487     */
488    public AnimatedEntity animateActor(int x, int y, String s, int index, ArrayList<Color> palette)
489    {
490        return animateActor(x, y, s, palette.get(index));
491    }
492
493    /**
494     * Create an AnimatedEntity at position x, y, using a TextureRegion with no color modifications, which will be
495     * stretched to fit one cell.
496     * @param x
497     * @param y
498     * @param texture
499     * @return
500     */
501    public AnimatedEntity animateActor(int x, int y, TextureRegion texture)
502    {
503        Actor a = textFactory.makeActor(texture, Color.WHITE);
504        a.setName("");
505        a.setPosition(x * cellWidth, (gridHeight - y - 1) * cellHeight - 1);
506
507        AnimatedEntity ae = new AnimatedEntity(a, x, y);
508        animatedEntities.add(ae);
509        return ae;
510    }
511
512    /**
513     * Create an AnimatedEntity at position x, y, using a TextureRegion with the given color, which will be
514     * stretched to fit one cell.
515     * @param x
516     * @param y
517     * @param texture
518     * @param color
519     * @return
520     */
521    public AnimatedEntity animateActor(int x, int y, TextureRegion texture, Color color)
522    {
523        Actor a = textFactory.makeActor(texture, color);
524        a.setName("");
525        a.setPosition(x * cellWidth, (gridHeight - y - 1) * cellHeight - 1);
526
527        AnimatedEntity ae = new AnimatedEntity(a, x, y);
528        animatedEntities.add(ae);
529        return ae;
530    }
531
532    /**
533     * Create an AnimatedEntity at position x, y, using a TextureRegion with no color modifications, which will be
534     * stretched to fit one cell, or two cells if doubleWidth is true.
535     * @param x
536     * @param y
537     * @param doubleWidth
538     * @param texture
539     * @return
540     */
541    public AnimatedEntity animateActor(int x, int y, boolean doubleWidth, TextureRegion texture)
542    {
543        Actor a = textFactory.makeActor(texture, Color.WHITE, (doubleWidth ? 2 : 1) * cellWidth, cellHeight);
544        a.setName("");
545        if(doubleWidth)
546            a.setPosition(x * 2 * cellWidth, (gridHeight - y - 1) * cellHeight - 1);
547        else
548            a.setPosition(x * cellWidth, (gridHeight - y - 1) * cellHeight - 1);
549
550        AnimatedEntity ae = new AnimatedEntity(a, x, y, doubleWidth);
551        animatedEntities.add(ae);
552        return ae;
553    }
554
555    /**
556     * Create an AnimatedEntity at position x, y, using a TextureRegion with the given color, which will be
557     * stretched to fit one cell, or two cells if doubleWidth is true.
558     * @param x
559     * @param y
560     * @param doubleWidth
561     * @param texture
562     * @param color
563     * @return
564     */
565    public AnimatedEntity animateActor(int x, int y, boolean doubleWidth, TextureRegion texture, Color color) {
566        Actor a = textFactory.makeActor(texture, color, (doubleWidth ? 2 : 1) * cellWidth, cellHeight);
567        a.setName("");
568        if (doubleWidth)
569            a.setPosition(x * 2 * cellWidth, (gridHeight - y - 1) * cellHeight - 1);
570        else
571            a.setPosition(x * cellWidth, (gridHeight - y - 1) * cellHeight - 1);
572
573        AnimatedEntity ae = new AnimatedEntity(a, x, y, doubleWidth);
574        animatedEntities.add(ae);
575        return ae;
576    }
577
578    /**
579     * Create an AnimatedEntity at position x, y, using a TextureRegion with no color modifications, which, if and only
580     * if stretch is true, will be stretched to fit one cell, or two cells if doubleWidth is true. If stretch is false,
581     * this will preserve the existing size of texture.
582     * @param x
583     * @param y
584     * @param doubleWidth
585     * @param stretch
586     * @param texture
587     * @return
588     */
589    public AnimatedEntity animateActor(int x, int y, boolean doubleWidth, boolean stretch, TextureRegion texture)
590    {
591        Actor a = (stretch)
592                ? textFactory.makeActor(texture, Color.WHITE, (doubleWidth ? 2 : 1) * cellWidth, cellHeight)
593                : textFactory.makeActor(texture, Color.WHITE, texture.getRegionWidth(), texture.getRegionHeight());
594        a.setName("");
595        if(doubleWidth)
596            a.setPosition(x * 2 * cellWidth, (gridHeight - y - 1) * cellHeight - 1);
597        else
598            a.setPosition(x * cellWidth, (gridHeight - y - 1) * cellHeight - 1);
599
600        AnimatedEntity ae = new AnimatedEntity(a, x, y, doubleWidth);
601        animatedEntities.add(ae);
602        return ae;
603    }
604
605    /**
606     * Create an AnimatedEntity at position x, y, using a TextureRegion with the given color, which, if and only
607     * if stretch is true, will be stretched to fit one cell, or two cells if doubleWidth is true. If stretch is false,
608     * this will preserve the existing size of texture.
609     * @param x
610     * @param y
611     * @param doubleWidth
612     * @param stretch
613     * @param texture
614     * @param color
615     * @return
616     */
617    public AnimatedEntity animateActor(int x, int y, boolean doubleWidth, boolean stretch, TextureRegion texture, Color color) {
618
619        Actor a = (stretch)
620                ? textFactory.makeActor(texture, color, (doubleWidth ? 2 : 1) * cellWidth, cellHeight)
621                : textFactory.makeActor(texture, color, texture.getRegionWidth(), texture.getRegionHeight());
622        a.setName("");
623        if (doubleWidth)
624            a.setPosition(x * 2 * cellWidth, (gridHeight - y - 1) * cellHeight - 1);
625        else
626            a.setPosition(x * cellWidth, (gridHeight - y - 1) * cellHeight - 1);
627
628        AnimatedEntity ae = new AnimatedEntity(a, x, y, doubleWidth);
629        animatedEntities.add(ae);
630        return ae;
631    }
632
633    /**
634     * Created an Actor from the contents of the given x,y position on the grid.
635     * @param x
636     * @param y
637     * @return
638     */
639    public Actor cellToActor(int x, int y)
640    {
641        if(contents[x][y] == null || contents[x][y].equals(""))
642            return null;
643
644        Actor a = textFactory.makeActor(contents[x][y], new Color(colors[x][y]));
645        a.setName(contents[x][y]);
646        a.setPosition(x * cellWidth, (gridHeight - y - 1) * cellHeight - 1);
647
648        addActor(a);
649
650        contents[x][y] = "";
651        return a;
652    }
653
654    /**
655     * Created an Actor from the contents of the given x,y position on the grid.
656     * @param x
657     * @param y
658     * @param doubleWidth
659     * @return
660     */
661    public Actor cellToActor(int x, int y, boolean doubleWidth)
662    {
663        if(contents[x][y] == null || contents[x][y].equals(""))
664            return null;
665
666        Actor a = textFactory.makeActor(contents[x][y], new Color(colors[x][y]));
667        a.setName(contents[x][y]);
668        if(doubleWidth)
669            a.setPosition(x * 2 * cellWidth, (gridHeight - y - 1) * cellHeight - 1);
670        else
671            a.setPosition(x * cellWidth, (gridHeight - y - 1) * cellHeight - 1);
672
673        addActor(a);
674
675        contents[x][y] = "";
676        return a;
677    }
678
679    /*
680    public void startAnimation(Actor a, int oldX, int oldY)
681    {
682        Coord tmp = Coord.get(oldX, oldY);
683
684        tmp.x = Math.round(a.getX() / cellWidth);
685        tmp.y = gridHeight - Math.round(a.getY() / cellHeight) - 1;
686        if(tmp.x >= 0 && tmp.x < gridWidth && tmp.y > 0 && tmp.y < gridHeight)
687        {
688        }
689    }
690    */
691    public void recallActor(Actor a)
692    {
693        int x = Math.round(a.getX() / cellWidth),
694             y = gridHeight - Math.round(a.getY() / cellHeight) - 1;
695        contents[x][y] = a.getName();
696        animationCount--;
697        removeActor(a);
698    }
699    public void recallActor(AnimatedEntity ae)
700    {
701        if(ae.doubleWidth)
702            ae.gridX = Math.round(ae.actor.getX() / (2 * cellWidth));
703        else
704            ae.gridX = Math.round(ae.actor.getX() / cellWidth);
705        ae.gridY = gridHeight - Math.round(ae.actor.getY() / cellHeight) - 1;
706        ae.animating = false;
707        animationCount--;
708    }
709
710    /**
711     * Start a bumping animation in the given direction that will last duration seconds.
712     * @param ae an AnimatedEntity returned by animateActor()
713     * @param direction
714     * @param duration a float, measured in seconds, for how long the animation should last; commonly 0.12f
715     */
716    public void bump(final AnimatedEntity ae, Direction direction, float duration)
717    {
718        final Actor a = ae.actor;
719        final int x = ae.gridX * cellWidth, y = (gridHeight - ae.gridY - 1) * cellHeight - 1;
720        if(a == null || ae.animating) return;
721        if(duration < 0.02f) duration = 0.02f;
722        animationCount++;
723        ae.animating = true;
724        a.addAction(Actions.sequence(
725                Actions.moveToAligned(x + (direction.deltaX / 3F) * ((ae.doubleWidth) ? 2F : 1F), y + direction.deltaY / 3F,
726                        Align.center, duration * 0.35F),
727                Actions.moveToAligned(x, y, Align.bottomLeft, duration * 0.65F),
728                Actions.delay(duration, Actions.run(new Runnable() {
729                    @Override
730                    public void run() {
731                        recallActor(ae);
732                    }
733                }))));
734
735    }
736    /**
737     * Start a bumping animation in the given direction that will last duration seconds.
738     * @param x
739     * @param y
740     * @param direction
741     * @param duration a float, measured in seconds, for how long the animation should last; commonly 0.12f
742     */
743    public void bump(int x, int y, Direction direction, float duration)
744    {
745        final Actor a = cellToActor(x, y);
746        if(a == null) return;
747        if(duration < 0.02f) duration = 0.02f;
748        animationCount++;
749        x *= cellWidth;
750        y = (gridHeight - y - 1);
751        y *= cellHeight;
752        y -= 1;
753        a.addAction(Actions.sequence(
754                Actions.moveToAligned(x + direction.deltaX / 3F, y + direction.deltaY / 3F,
755                        Align.center, duration * 0.35F),
756                Actions.moveToAligned(x, y, Align.bottomLeft, duration * 0.65F),
757                Actions.delay(duration, Actions.run(new Runnable() {
758                    @Override
759                    public void run() {
760                        recallActor(a);
761                    }
762                }))));
763
764    }
765
766    /**
767     * Starts a bumping animation in the direction provided.
768     *
769     * @param x
770     * @param y
771     * @param direction
772     */
773    public void bump(int x, int y, Direction direction) {
774        bump(x, y, direction, DEFAULT_ANIMATION_DURATION);
775    }
776    /**
777     * Starts a bumping animation in the direction provided.
778     *
779     * @param location
780     * @param direction
781     */
782    public void bump(Coord location, Direction direction) {
783        bump(location.x, location.y, direction, DEFAULT_ANIMATION_DURATION);
784    }
785    /**
786     * Start a movement animation for the object at the grid location x, y and moves it to newX, newY over a number of
787     * seconds given by duration (often 0.12f or somewhere around there).
788     * @param ae an AnimatedEntity returned by animateActor()
789     * @param newX
790     * @param newY
791     * @param duration
792     */
793    public void slide(final AnimatedEntity ae, int newX, int newY, float duration)
794    {
795        final Actor a = ae.actor;
796        final int nextX = newX * cellWidth * ((ae.doubleWidth) ? 2 : 1), nextY = (gridHeight - newY - 1) * cellHeight - 1;
797        if(a == null || ae.animating) return;
798        if(duration < 0.02f) duration = 0.02f;
799        animationCount++;
800        ae.animating = true;
801        a.addAction(Actions.sequence(
802                Actions.moveToAligned(nextX, nextY, Align.bottomLeft, duration),
803                Actions.delay(duration, Actions.run(new Runnable() {
804                    @Override
805                    public void run() {
806                        recallActor(ae);
807                    }
808                }))));
809    }
810
811    /**
812     * Start a movement animation for the object at the grid location x, y and moves it to newX, newY over a number of
813     * seconds given by duration (often 0.12f or somewhere around there).
814     * @param x
815     * @param y
816     * @param newX
817     * @param newY
818     * @param duration
819     */
820    public void slide(int x, int y, int newX, int newY, float duration)
821    {
822        final Actor a = cellToActor(x, y);
823        if(a == null) return;
824        if(duration < 0.02f) duration = 0.02f;
825        animationCount++;
826        newX *= cellWidth;
827        newY = (gridHeight - newY - 1);
828        newY *= cellHeight;
829        newY -= 1;
830        a.addAction(Actions.sequence(
831                Actions.moveToAligned(newX, newY, Align.bottomLeft, duration),
832                Actions.delay(duration, Actions.run(new Runnable() {
833                    @Override
834                    public void run() {
835                        recallActor(a);
836                    }
837                }))));
838    }
839    /**
840     * Starts a movement animation for the object at the given grid location at the default speed.
841     *
842     * @param start
843     * @param end
844     */
845    public void slide(Coord start, Coord end) {
846        slide(start.x, start.y, end.x, end.y, DEFAULT_ANIMATION_DURATION);
847    }
848
849    /**
850     * Starts a movement animation for the object at the given grid location at the default speed for one grid square in
851     * the direction provided.
852     *
853     * @param start
854     * @param direction
855     */
856    public void slide(Coord start, Direction direction) {
857        slide(start.x, start.y, start.x + direction.deltaX, start.y + direction.deltaY, DEFAULT_ANIMATION_DURATION);
858    }
859
860    /**
861     * Starts a sliding movement animation for the object at the given location at the provided speed. The duration is
862     * how many seconds should pass for the entire animation.
863     *
864     * @param start
865     * @param end
866     * @param duration
867     */
868    public void slide(Coord start, Coord end, float duration) {
869        slide(start.x, start.y, end.x, end.y, duration);
870    }
871
872    /**
873     * Starts an wiggling animation for the object at the given location for the given duration in seconds.
874     *
875     * @param ae an AnimatedEntity returned by animateActor()
876     * @param duration
877     */
878    public void wiggle(final AnimatedEntity ae, float duration) {
879
880        final Actor a = ae.actor;
881        final int x = ae.gridX * cellWidth * ((ae.doubleWidth) ? 2 : 1), y = (gridHeight - ae.gridY - 1) * cellHeight - 1;
882        if(a == null || ae.animating)
883            return;
884        if(duration < 0.02f) duration = 0.02f;
885        ae.animating = true;
886        animationCount++;
887        StatefulRNG gRandom = DefaultResources.getGuiRandom();
888        a.addAction(Actions.sequence(
889                Actions.moveToAligned(x + (gRandom.nextFloat() - 0.5F) * cellWidth * 0.4f,
890                        y + (gRandom.nextFloat() - 0.5F) * cellHeight * 0.4f,
891                        Align.bottomLeft, duration * 0.2F),
892                Actions.moveToAligned(x + (gRandom.nextFloat() - 0.5F) * cellWidth * 0.4f,
893                        y + (gRandom.nextFloat() - 0.5F) * cellHeight * 0.4f,
894                        Align.bottomLeft, duration * 0.2F),
895                Actions.moveToAligned(x + (gRandom.nextFloat() - 0.5F) * cellWidth * 0.4f,
896                        y + (gRandom.nextFloat() - 0.5F) * cellHeight * 0.4f,
897                        Align.bottomLeft, duration * 0.2F),
898                Actions.moveToAligned(x + (gRandom.nextFloat() - 0.5F) * cellWidth * 0.4f,
899                        y + (gRandom.nextFloat() - 0.5F) * cellHeight * 0.4f,
900                        Align.bottomLeft, duration * 0.2F),
901                Actions.moveToAligned(x, y, Align.bottomLeft, duration * 0.2F),
902                Actions.delay(duration, Actions.run(new Runnable() {
903                    @Override
904                    public void run() {
905                        recallActor(ae);
906                    }
907                }))));
908    }
909    /**
910     * Starts an wiggling animation for the object at the given location for the given duration in seconds.
911     *
912     * @param x
913     * @param y
914     * @param duration
915     */
916    public void wiggle(int x, int y, float duration) {
917        final Actor a = cellToActor(x, y);
918        if(a == null) return;
919        if(duration < 0.02f) duration = 0.02f;
920        animationCount++;
921        x *= cellWidth;
922        y = (gridHeight - y - 1);
923        y *= cellHeight;
924        y -= 1;
925        StatefulRNG gRandom = DefaultResources.getGuiRandom();
926        a.addAction(Actions.sequence(
927                Actions.moveToAligned(x + (gRandom.nextFloat() - 0.5F) * cellWidth * 0.4f,
928                        y + (gRandom.nextFloat() - 0.5F) * cellHeight * 0.4f,
929                        Align.bottomLeft, duration * 0.2F),
930                Actions.moveToAligned(x + (gRandom.nextFloat() - 0.5F) * cellWidth * 0.4f,
931                        y + (gRandom.nextFloat() - 0.5F) * cellHeight * 0.4f,
932                        Align.bottomLeft, duration * 0.2F),
933                Actions.moveToAligned(x + (gRandom.nextFloat() - 0.5F) * cellWidth * 0.4f,
934                        y + (gRandom.nextFloat() - 0.5F) * cellHeight * 0.4f,
935                        Align.bottomLeft, duration * 0.2F),
936                Actions.moveToAligned(x + (gRandom.nextFloat() - 0.5F) * cellWidth * 0.4f,
937                        y + (gRandom.nextFloat() - 0.5F) * cellHeight * 0.4f,
938                        Align.bottomLeft, duration * 0.2F),
939                Actions.moveToAligned(x, y, Align.bottomLeft, duration * 0.2F),
940                Actions.delay(duration, Actions.run(new Runnable() {
941                    @Override
942                    public void run() {
943                        recallActor(a);
944                    }
945                }))));
946    }
947
948    /**
949     * Starts an wiggling animation for the object at the given location for the given duration in seconds.
950     *
951     * @param ae an AnimatedEntity returned by animateActor()
952     * @param color
953     * @param duration
954     */
955    public void tint(final AnimatedEntity ae, Color color, float duration) {
956
957        final Actor a = ae.actor;
958        if(a == null || ae.animating)
959            return;
960        if(duration < 0.02f) duration = 0.02f;
961        ae.animating = true;
962        animationCount++;
963        Color ac = a.getColor().cpy();
964        a.addAction(Actions.sequence(
965                Actions.color(color, duration * 0.3f),
966                Actions.color(ac, duration * 0.7f),
967                Actions.delay(duration, Actions.run(new Runnable() {
968                    @Override
969                    public void run() {
970                        recallActor(ae);
971                    }
972                }))));
973    }
974    /**
975     * Starts an wiggling animation for the object at the given location for the given duration in seconds.
976     *
977     * @param x
978     * @param y
979     * @param color
980     * @param duration
981     */
982    public void tint(int x, int y, Color color, float duration) {
983        final Actor a = cellToActor(x, y);
984        if(a == null)
985            return;
986        if(duration < 0.02f) duration = 0.02f;
987        animationCount++;
988
989        Color ac = a.getColor().cpy();
990        a.addAction(Actions.sequence(
991                Actions.color(color, duration * 0.3f),
992                Actions.color(ac, duration * 0.7f),
993                Actions.delay(duration, Actions.run(new Runnable() {
994                    @Override
995                    public void run() {
996                        recallActor(a);
997                    }
998                }))));
999    }
1000
1001    /**
1002     * Returns true if there are animations running when this method is called.
1003     *
1004     * @return
1005     */
1006    public boolean hasActiveAnimations() {
1007        return animationCount != 0;
1008    }
1009
1010    public LinkedHashSet<AnimatedEntity> getAnimatedEntities() {
1011        return animatedEntities;
1012    }
1013
1014        @Override
1015        public void refresh() {
1016                /* smelC: should we do something here ? */
1017        /* Tommy Ettinger: potentially, but it would need to call draw, and that means keeping a Batch. */
1018        }
1019
1020        @Override
1021        public ISquidPanel<Color> getBacker() {
1022                return this;
1023        }
1024
1025}