001/*
002 * Gridarta MMORPG map editor for Crossfire, Daimonin and similar games.
003 * Copyright (C) 2000-2010 The Gridarta Developers.
004 *
005 * This program is free software; you can redistribute it and/or modify
006 * it under the terms of the GNU General Public License as published by
007 * the Free Software Foundation; either version 2 of the License, or
008 * (at your option) any later version.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
013 * GNU General Public License for more details.
014 *
015 * You should have received a copy of the GNU General Public License along
016 * with this program; if not, write to the Free Software Foundation, Inc.,
017 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
018 */
019
020package net.sf.gridarta.model.mapmodel;
021
022import java.awt.Point;
023import java.util.regex.Pattern;
024import net.sf.gridarta.model.anim.AnimationObjects;
025import net.sf.gridarta.model.archetype.DuplicateArchetypeException;
026import net.sf.gridarta.model.archetype.TestArchetype;
027import net.sf.gridarta.model.archetype.TestDefaultArchetype;
028import net.sf.gridarta.model.archetypeset.ArchetypeSet;
029import net.sf.gridarta.model.baseobject.BaseObject;
030import net.sf.gridarta.model.baseobject.GameObjectContainer;
031import net.sf.gridarta.model.face.FaceObjectProviders;
032import net.sf.gridarta.model.gameobject.GameObject;
033import net.sf.gridarta.model.gameobject.GameObjectFactory;
034import net.sf.gridarta.model.gameobject.TestGameObject;
035import net.sf.gridarta.model.maparchobject.TestMapArchObject;
036import net.sf.gridarta.utils.Size2D;
037import net.sf.gridarta.utils.StringUtils;
038import org.jetbrains.annotations.NotNull;
039import org.junit.Assert;
040
041/**
042 * Helper class for creating {@link MapModel} instances for regression tests.
043 * @author Andreas Kirschbaum
044 */
045public class TestMapModelHelper {
046
047    /**
048     * The archetype type used for "exit" game objects.
049     */
050    public static final int EXIT_TYPE = 1;
051
052    /**
053     * The archetype type used for "floor" game objects.
054     */
055    private static final int FLOOR_TYPE = 2;
056
057    /**
058     * The archetype type used for "mob" game objects.
059     */
060    private static final int MOB_TYPE = 3;
061
062    /**
063     * An empty array of strings.
064     */
065    @NotNull
066    private static final String[] EMPTY_STRING_ARRAY = new String[0];
067
068    /**
069     * The "topmost" {@link InsertionMode} instance.
070     */
071    @NotNull
072    private final InsertionMode<TestGameObject, TestMapArchObject, TestArchetype> topmostInsertionMode;
073
074    /**
075     * The {@link GameObjectFactory} instance.
076     */
077    @NotNull
078    private final GameObjectFactory<TestGameObject, TestMapArchObject, TestArchetype> gameObjectFactory;
079
080    /**
081     * The archetype to create floor game objects.
082     */
083    @NotNull
084    private final TestArchetype floorArchetype;
085
086    /**
087     * The archetype to create exit game objects.
088     */
089    @NotNull
090    private final TestArchetype exitArchetype;
091
092    /**
093     * The archetype to create 2x1 mob game objects.
094     */
095    @NotNull
096    private final TestArchetype mob21Archetype;
097
098    /**
099     * Creates a new instance.
100     * @param topmostInsertionMode the "topmost" insertion mode instance
101     * @param gameObjectFactory the game object factory to use
102     * @param archetypeSet the archetype set to use
103     * @param faceObjectProviders the face object providers to use
104     * @param animationObjects the animation objects to use
105     * @throws DuplicateArchetypeException if an internal error occurs
106     */
107    public TestMapModelHelper(@NotNull final InsertionMode<TestGameObject, TestMapArchObject, TestArchetype> topmostInsertionMode, @NotNull final GameObjectFactory<TestGameObject, TestMapArchObject, TestArchetype> gameObjectFactory, @NotNull final ArchetypeSet<TestGameObject, TestMapArchObject, TestArchetype> archetypeSet, @NotNull final FaceObjectProviders faceObjectProviders, @NotNull final AnimationObjects animationObjects) throws DuplicateArchetypeException {
108        this.topmostInsertionMode = topmostInsertionMode;
109        this.gameObjectFactory = gameObjectFactory;
110
111        floorArchetype = new TestDefaultArchetype("floor", faceObjectProviders, animationObjects);
112        floorArchetype.setAttributeInt(BaseObject.TYPE, FLOOR_TYPE);
113        archetypeSet.addArchetype(floorArchetype);
114
115        exitArchetype = new TestDefaultArchetype("exit", faceObjectProviders, animationObjects);
116        exitArchetype.setAttributeInt(BaseObject.TYPE, EXIT_TYPE);
117        archetypeSet.addArchetype(exitArchetype);
118
119        mob21Archetype = new TestDefaultArchetype("mob21", faceObjectProviders, animationObjects);
120        mob21Archetype.setAttributeInt(BaseObject.TYPE, MOB_TYPE);
121        archetypeSet.addArchetype(mob21Archetype);
122        final TestArchetype mob21bArchetype = new TestDefaultArchetype("mob21b", faceObjectProviders, animationObjects);
123        mob21bArchetype.setMultiX(1);
124        mob21Archetype.addTailPart(mob21bArchetype);
125        archetypeSet.addArchetype(mob21bArchetype);
126    }
127
128    /**
129     * Inserts a {@link #floorArchetype} game object into a map model.
130     * @param mapModel the map model to insert into
131     * @param point the position to insert at
132     * @return the inserted game object
133     * @throws CannotInsertGameObjectException if the archetype cannot be
134     * inserted
135     */
136    @NotNull
137    public TestGameObject insertFloor(@NotNull final MapModel<TestGameObject, TestMapArchObject, TestArchetype> mapModel, @NotNull final Point point) throws CannotInsertGameObjectException {
138        return insertArchetype(mapModel, point, floorArchetype, false);
139    }
140
141    /**
142     * Inserts an {@link #exitArchetype} game object into a map model.
143     * @param mapModel the map model to insert into
144     * @param point the position to insert at
145     * @return the inserted game object
146     * @throws CannotInsertGameObjectException if the archetype cannot be
147     * inserted
148     */
149    @NotNull
150    public TestGameObject insertExit(@NotNull final MapModel<TestGameObject, TestMapArchObject, TestArchetype> mapModel, @NotNull final Point point) throws CannotInsertGameObjectException {
151        return insertArchetype(mapModel, point, exitArchetype, false);
152    }
153
154    /**
155     * Inserts a {@link #mob21Archetype} game object into a map model.
156     * @param mapModel the map model to insert into
157     * @param point the position to insert at
158     * @return the inserted game object
159     * @throws CannotInsertGameObjectException if the archetype cannot be
160     * inserted
161     */
162    @NotNull
163    public TestGameObject insertMob21(@NotNull final MapModel<TestGameObject, TestMapArchObject, TestArchetype> mapModel, @NotNull final Point point) throws CannotInsertGameObjectException {
164        return insertArchetype(mapModel, point, mob21Archetype, false);
165    }
166
167    /**
168     * Inserts an archetype game object into a map model.
169     * @param mapModel the map model to insert into
170     * @param x the x coordinate to insert at
171     * @param y the y coordinate to insert at
172     * @param archetype the archetype to insert
173     * @param join whether to perform autojoining
174     * @return the inserted game object
175     * @throws CannotInsertGameObjectException if the archetype cannot be
176     * inserted
177     */
178    @NotNull
179    public TestGameObject insertArchetype(@NotNull final MapModel<TestGameObject, TestMapArchObject, TestArchetype> mapModel, final int x, final int y, @NotNull final BaseObject<TestGameObject, TestMapArchObject, TestArchetype, ?> archetype, final boolean join) throws CannotInsertGameObjectException {
180        return insertArchetype(mapModel, new Point(x, y), archetype, join);
181    }
182
183    /**
184     * Inserts an archetype game object into a map model.
185     * @param mapModel the map model to insert into
186     * @param point the position to insert at
187     * @param archetype the archetype to insert
188     * @param join whether to perform autojoining
189     * @return the inserted game object
190     * @throws CannotInsertGameObjectException if the archetype cannot be
191     * inserted
192     */
193    @NotNull
194    public TestGameObject insertArchetype(@NotNull final MapModel<TestGameObject, TestMapArchObject, TestArchetype> mapModel, @NotNull final Point point, @NotNull final BaseObject<TestGameObject, TestMapArchObject, TestArchetype, ?> archetype, final boolean join) throws CannotInsertGameObjectException {
195        final TestGameObject gameObject = mapModel.insertBaseObject(archetype, point, true, join, topmostInsertionMode);
196        if (gameObject == null) {
197            throw new CannotInsertGameObjectException(archetype, point);
198        }
199        return gameObject;
200    }
201
202    /**
203     * Inserts an {@link #exitArchetype} game object into a game object.
204     * @param gameObject the game object
205     * @return the inserted game object
206     */
207    @NotNull
208    public TestGameObject insertExit(@NotNull final GameObjectContainer<TestGameObject, TestMapArchObject, TestArchetype> gameObject) {
209        return insertArchetype(gameObject, exitArchetype);
210    }
211
212    /**
213     * Inserts a {@link #mob21Archetype} game object into a game object.
214     * @param gameObject the game object
215     * @return the inserted game object
216     */
217    @NotNull
218    public TestGameObject insertMob21(@NotNull final GameObjectContainer<TestGameObject, TestMapArchObject, TestArchetype> gameObject) {
219        return insertArchetype(gameObject, mob21Archetype);
220    }
221
222    /**
223     * Inserts an archetype into a game object.
224     * @param gameObject the game object
225     * @param archetype the archetype to insert
226     * @return the inserted game object
227     */
228    @NotNull
229    public TestGameObject insertArchetype(@NotNull final GameObjectContainer<TestGameObject, TestMapArchObject, TestArchetype> gameObject, @NotNull final BaseObject<TestGameObject, TestMapArchObject, TestArchetype, ?> archetype) {
230        final TestGameObject newGameObject = archetype.newInstance(gameObjectFactory);
231        gameObject.addLast(newGameObject);
232        return newGameObject;
233    }
234
235    /**
236     * Checks for expected {@link MapModel}'s contents.
237     * @param mapModel the map model
238     * @param lines the expected contents
239     */
240    public static void checkMapContents(@NotNull final MapModel<TestGameObject, TestMapArchObject, TestArchetype> mapModel, @NotNull final String... lines) {
241        final Size2D mapSize = mapModel.getMapArchObject().getMapSize();
242        Assert.assertEquals(lines.length, mapSize.getHeight());
243        final Pattern pattern1 = Pattern.compile("\\|");
244        final Pattern pattern2 = StringUtils.PATTERN_COMMA;
245        final Point pos = new Point();
246        for (int y = 0; y < lines.length; y++) {
247            final CharSequence line = lines[y];
248            final String[] square = pattern1.split(line, -1);
249            Assert.assertEquals(square.length, mapSize.getWidth());
250
251            for (int x = 0; x < square.length; x++) {
252                final String square2 = square[x];
253                final String[] gameObjects = square2.isEmpty() ? EMPTY_STRING_ARRAY : pattern2.split(square2, -1);
254                pos.x = x;
255                pos.y = y;
256                checkContentsString(mapModel.getMapSquare(pos), gameObjects);
257            }
258        }
259    }
260
261    /**
262     * Checks that a {@link MapSquare} contains the given game objects.
263     * @param mapSquare the map square
264     * @param gameObjects the game object
265     */
266    private static void checkContentsString(@NotNull final MapSquare<TestGameObject, TestMapArchObject, TestArchetype> mapSquare, @NotNull final String... gameObjects) {
267        int i = 0;
268        for (final BaseObject<?, ?, ?, ?> gameObject : mapSquare) {
269            final String gameObjectName = gameObject.getBestName();
270            if (i >= gameObjects.length) {
271                Assert.fail("map square " + mapSquare.getMapX() + "/" + mapSquare.getMapY() + " contains excess game object '" + gameObjectName + "'");
272            } else if (!gameObjectName.equals(gameObjects[i])) {
273                Assert.fail("map square " + mapSquare.getMapX() + "/" + mapSquare.getMapY() + " contains wrong game object '" + gameObjectName + "' at index " + i + ", expected '" + gameObjects[i] + "'");
274            }
275            i++;
276        }
277        if (i < gameObjects.length) {
278            Assert.fail("map square " + mapSquare.getMapX() + "/" + mapSquare.getMapY() + " is missing game object '" + gameObjects[i] + "'");
279        }
280    }
281
282    /**
283     * Checks that a {@link MapSquare} contains the given game objects.
284     * @param mapSquare the map square
285     * @param gameObjects the game object
286     */
287    public static void checkContents(@NotNull final Iterable<TestGameObject> mapSquare, @NotNull final BaseObject<?, ?, ?, ?>... gameObjects) {
288        int i = 0;
289        for (final BaseObject<?, ?, ?, ?> gameObject : mapSquare) {
290            final String gameObjectName = gameObject.getBestName();
291            if (i >= gameObjects.length) {
292                Assert.fail("map square contains excess game object '" + gameObjectName + "'");
293            } else if (gameObject != gameObjects[i]) {
294                Assert.fail("map square contains wrong game object '" + gameObjectName + "' at index " + i + ", expected '" + gameObjects[i].getBestName() + "'");
295            }
296            i++;
297        }
298        if (i < gameObjects.length) {
299            Assert.fail("map square is missing game object '" + gameObjects[i].getBestName() + "'");
300        }
301
302        final boolean inContainer = mapSquare instanceof GameObject;
303        for (final GameObject<?, ?, ?> gameObject : mapSquare) {
304            Assert.assertEquals(inContainer, gameObject.isInContainer());
305            if (inContainer) {
306                // game objects within inventories must not contain tail parts
307                Assert.assertFalse(gameObject.isMulti());
308            } else {
309                // game objects on the map must have expanded tail parts
310                Assert.assertEquals(gameObject.getArchetype().isMulti(), gameObject.isMulti());
311            }
312        }
313    }
314
315}