001/*
002 * Gridarta MMORPG map editor for Crossfire, Daimonin and similar games.
003 * Copyright (C) 2000-2011 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.actions;
021
022import java.awt.Point;
023import java.io.File;
024import net.sf.gridarta.model.archetype.DuplicateArchetypeException;
025import net.sf.gridarta.model.archetype.TestArchetype;
026import net.sf.gridarta.model.baseobject.BaseObject;
027import net.sf.gridarta.model.exitconnector.ExitConnectorModel;
028import net.sf.gridarta.model.exitconnector.ExitLocation;
029import net.sf.gridarta.model.exitconnector.TestExitConnectorModel;
030import net.sf.gridarta.model.gameobject.TestGameObject;
031import net.sf.gridarta.model.maparchobject.TestMapArchObject;
032import net.sf.gridarta.model.mapcontrol.MapControl;
033import net.sf.gridarta.model.mapcontrol.TestMapControlCreator;
034import net.sf.gridarta.model.mapmanager.TestFileControl;
035import net.sf.gridarta.model.mapmodel.CannotInsertGameObjectException;
036import net.sf.gridarta.model.mapmodel.MapModel;
037import net.sf.gridarta.model.mapmodel.TestMapModelHelper;
038import net.sf.gridarta.utils.Size2D;
039import org.jetbrains.annotations.NotNull;
040import org.jetbrains.annotations.Nullable;
041import org.junit.Assert;
042import org.junit.Before;
043import org.junit.Test;
044
045/**
046 * Regression tests for {@link ExitConnectorActions}.
047 * @author Andreas Kirschbaum
048 */
049public class ExitConnectorActionsTest {
050
051    /**
052     * The first map file.
053     */
054    private static final File MAP_FILE1 = new File("a");
055
056    /**
057     * The first map name.
058     */
059    private static final String MAP_NAME1 = "name1";
060
061    /**
062     * The second map file.
063     */
064    private static final File MAP_FILE2 = new File("b");
065
066    /**
067     * The second map name.
068     */
069    private static final String MAP_NAME2 = "name2";
070
071    /**
072     * The {@link TestMapControlCreator} for creating maps.
073     */
074    @Nullable
075    private TestMapControlCreator testMapControlCreator;
076
077    /**
078     * The {@link TestMapModelHelper} instance.
079     */
080    @Nullable
081    private TestMapModelHelper mapModelHelper;
082
083    /**
084     * Checks that {@link ExitConnectorActions#doExitCopy(boolean, MapControl,
085     * Point)} does work.
086     */
087    @Test
088    public void testExitCopy1() {
089        final ExitConnectorModel model = new TestExitConnectorModel();
090        final ExitConnectorActions<TestGameObject, TestMapArchObject, TestArchetype> actions = createActions(model);
091        final Size2D mapSize = new Size2D(5, 5);
092        assert testMapControlCreator != null;
093        final MapControl<TestGameObject, TestMapArchObject, TestArchetype> mapControl = testMapControlCreator.newMapControl(MAP_FILE1, MAP_NAME1, mapSize);
094
095        // active ==> enabled
096        final Point point1 = new Point(3, 4);
097        Assert.assertTrue(actions.doExitCopy(false, mapControl, point1));
098        Assert.assertNull(model.getExitLocation());
099
100        // perform copy
101        Assert.assertTrue(actions.doExitCopy(true, mapControl, point1));
102        assert testMapControlCreator != null;
103        Assert.assertEquals(new ExitLocation(MAP_FILE1, point1, MAP_NAME1, testMapControlCreator.getPathManager()), model.getExitLocation());
104
105        // unsaved map ==> disabled
106        mapControl.getMapModel().setMapFile(null);
107        Assert.assertFalse(actions.doExitCopy(false, mapControl, point1));
108        assert testMapControlCreator != null;
109        Assert.assertEquals(new ExitLocation(MAP_FILE1, point1, MAP_NAME1, testMapControlCreator.getPathManager()), model.getExitLocation()); // disabled -> unchanged model
110    }
111
112    /**
113     * Checks that {@link ExitConnectorActions#doExitPaste(boolean, MapControl,
114     * Point)} does work.
115     * @throws CannotInsertGameObjectException if the test fails
116     */
117    @Test
118    public void testExitPaste1() throws CannotInsertGameObjectException {
119        final ExitConnectorModel model = new TestExitConnectorModel();
120        final ExitConnectorActions<TestGameObject, TestMapArchObject, TestArchetype> actions = createActions(model);
121        final Size2D mapSize = new Size2D(5, 5);
122        assert testMapControlCreator != null;
123        final MapControl<TestGameObject, TestMapArchObject, TestArchetype> mapControl = testMapControlCreator.newMapControl(MAP_FILE1, MAP_NAME1, mapSize);
124        final MapModel<TestGameObject, TestMapArchObject, TestArchetype> mapModel = mapControl.getMapModel();
125
126        final Point point1 = new Point(3, 4);
127        final Point point2 = new Point(1, 2);
128
129        assert testMapControlCreator != null;
130        model.setExitLocation(new ExitLocation(MAP_FILE2, point2, MAP_NAME2, testMapControlCreator.getPathManager()));
131
132        // no exit at cursor and no auto-create ==> disabled
133        Assert.assertFalse(actions.doExitPaste(false, mapControl, point1));
134
135        mapModel.beginTransaction("TEST");
136        try {
137            assert mapModelHelper != null;
138            mapModelHelper.insertFloor(mapModel, point1);
139        } finally {
140            mapModel.endTransaction();
141        }
142
143        // no exit at cursor and no auto-create ==> disabled
144        Assert.assertFalse(actions.doExitPaste(false, mapControl, point1));
145
146        mapModel.beginTransaction("TEST");
147        try {
148            assert mapModelHelper != null;
149            mapModelHelper.insertExit(mapModel, point1);
150        } finally {
151            mapModel.endTransaction();
152        }
153
154        // exit at cursor ==> enabled
155        Assert.assertTrue(actions.doExitPaste(false, mapControl, point1));
156        checkExit(mapModel, point1, 1, "", new Point(0, 0)); // "check only" ==> exit is unchanged
157
158        // perform paste
159        Assert.assertTrue(actions.doExitPaste(true, mapControl, point1));
160        checkExit(mapModel, point1, 1, "b", point2);
161    }
162
163    /**
164     * Checks that {@link ExitConnectorActions#doExitPaste(boolean, MapControl,
165     * Point)} does not crash when connecting to an unsaved map.
166     */
167    @Test
168    public void testExitPaste2() {
169        final ExitConnectorModel model = new TestExitConnectorModel();
170        final ExitConnectorActions<TestGameObject, TestMapArchObject, TestArchetype> actions = createActions(model);
171        final Size2D mapSize = new Size2D(5, 5);
172
173        assert testMapControlCreator != null;
174        final MapControl<TestGameObject, TestMapArchObject, TestArchetype> mapControl1 = testMapControlCreator.newMapControl(MAP_FILE1, MAP_NAME1, mapSize);
175        final MapModel<TestGameObject, TestMapArchObject, TestArchetype> mapModel1 = mapControl1.getMapModel();
176
177        final Point point1 = new Point(3, 4);
178        final Point point2 = new Point(1, 2);
179
180        assert testMapControlCreator != null;
181        model.setExitLocation(new ExitLocation(MAP_FILE2, point2, MAP_NAME2, testMapControlCreator.getPathManager()));
182
183        model.setAutoCreateExit(true);
184
185        // copy must fail for unsaved map
186        mapModel1.setMapFile(null); // pretend unsaved map
187        Assert.assertFalse(actions.doExitCopy(true, mapControl1, point1));
188
189        // paste must succeed for unsaved map
190        Assert.assertTrue(actions.doExitPaste(true, mapControl1, point1));
191    }
192
193    /**
194     * Checks that {@link ExitConnectorActions#doExitConnect(boolean,
195     * MapControl, Point)} does work.
196     * @throws CannotInsertGameObjectException if the test fails
197     */
198    @Test
199    public void testExitConnect1() throws CannotInsertGameObjectException {
200        final ExitConnectorModel model = new TestExitConnectorModel();
201        final ExitConnectorActions<TestGameObject, TestMapArchObject, TestArchetype> actions = createActions(model);
202
203        final Size2D mapSize = new Size2D(5, 5);
204        assert testMapControlCreator != null;
205        final MapControl<TestGameObject, TestMapArchObject, TestArchetype> mapControl1 = testMapControlCreator.newMapControl(MAP_FILE1, MAP_NAME1, mapSize);
206        final MapModel<TestGameObject, TestMapArchObject, TestArchetype> mapModel1 = mapControl1.getMapModel();
207
208        assert testMapControlCreator != null;
209        final MapControl<TestGameObject, TestMapArchObject, TestArchetype> mapControl2 = testMapControlCreator.newMapControl(MAP_FILE2, MAP_NAME2, mapSize);
210        final MapModel<TestGameObject, TestMapArchObject, TestArchetype> mapModel2 = mapControl2.getMapModel();
211
212        final Point point1 = new Point(3, 4);
213        final Point point2 = new Point(1, 2);
214
215        assert testMapControlCreator != null;
216        model.setExitLocation(new ExitLocation(MAP_FILE2, point2, MAP_NAME2, testMapControlCreator.getPathManager()));
217
218        // no exit at cursor and no auto-create ==> disabled
219        Assert.assertFalse(actions.doExitConnect(false, mapControl1, point1));
220
221        mapModel1.beginTransaction("TEST");
222        try {
223            assert mapModelHelper != null;
224            mapModelHelper.insertFloor(mapModel1, point1);
225        } finally {
226            mapModel1.endTransaction();
227        }
228
229        // no exit at cursor and no auto-create ==> disabled
230        Assert.assertFalse(actions.doExitConnect(false, mapControl1, point1));
231
232        mapModel1.beginTransaction("TEST");
233        try {
234            assert mapModelHelper != null;
235            mapModelHelper.insertExit(mapModel1, point1);
236        } finally {
237            mapModel1.endTransaction();
238        }
239
240        // no exit at source and no auto-create ==> disabled
241        Assert.assertFalse(actions.doExitConnect(false, mapControl1, point1));
242
243        mapModel2.beginTransaction("TEST");
244        try {
245            assert mapModelHelper != null;
246            mapModelHelper.insertExit(mapModel2, point2);
247        } finally {
248            mapModel2.endTransaction();
249        }
250
251        // exit at source and cursor ==> enabled
252        Assert.assertTrue(actions.doExitConnect(false, mapControl1, point1));
253        checkExit(mapModel1, point1, 1, "", new Point(0, 0)); // "check only" ==> exit is unchanged
254        checkExit(mapModel2, point2, 0, "", new Point(0, 0));
255
256        // perform connect
257        Assert.assertTrue(actions.doExitConnect(true, mapControl1, point1));
258        checkExit(mapModel1, point1, 1, "b", point2);
259        checkExit(mapModel2, point2, 0, "a", point1);
260    }
261
262    /**
263     * Checks that {@link ExitConnectorActions#doExitConnect(boolean,
264     * MapControl, Point)} does work when auto-creating exit objects.
265     */
266    @Test
267    public void testExitConnectAuto1() {
268        final ExitConnectorModel model = new TestExitConnectorModel();
269        final ExitConnectorActions<TestGameObject, TestMapArchObject, TestArchetype> actions = createActions(model);
270
271        final Size2D mapSize = new Size2D(5, 5);
272        assert testMapControlCreator != null;
273        final MapControl<TestGameObject, TestMapArchObject, TestArchetype> mapControl1 = testMapControlCreator.newMapControl(MAP_FILE1, MAP_NAME1, mapSize);
274        final MapModel<TestGameObject, TestMapArchObject, TestArchetype> mapModel1 = mapControl1.getMapModel();
275
276        assert testMapControlCreator != null;
277        final MapControl<TestGameObject, TestMapArchObject, TestArchetype> mapControl2 = testMapControlCreator.newMapControl(MAP_FILE2, MAP_NAME2, mapSize);
278        final MapModel<TestGameObject, TestMapArchObject, TestArchetype> mapModel2 = mapControl2.getMapModel();
279
280        final Point point1 = new Point(3, 4);
281        final Point point2 = new Point(1, 2);
282
283        assert testMapControlCreator != null;
284        model.setExitLocation(new ExitLocation(MAP_FILE2, point2, MAP_NAME2, testMapControlCreator.getPathManager()));
285        model.setAutoCreateExit(true);
286
287        model.setExitArchetypeName("undefined");
288
289        // fails due to undefined archetype
290        Assert.assertFalse(actions.doExitConnect(true, mapControl1, point1));
291        checkExit(mapModel1, point1, 0, null, null);
292        checkExit(mapModel2, point2, 0, null, null);
293
294        model.setExitArchetypeName("exit");
295
296        // perform connect
297        Assert.assertTrue(actions.doExitConnect(true, mapControl1, point1));
298        checkExit(mapModel1, point1, 0, "b", point2);
299        checkExit(mapModel2, point2, 0, "a", point1);
300
301        // perform connect; auto-create will re-use existing objects
302        Assert.assertTrue(actions.doExitConnect(true, mapControl1, point1));
303        checkExit(mapModel1, point1, 0, "b", point2);
304        checkExit(mapModel1, point1, 1, null, null);
305        checkExit(mapModel2, point2, 0, "a", point1);
306        checkExit(mapModel2, point2, 1, null, null);
307    }
308
309    /**
310     * Checks that exit paths are correctly generated.
311     * @throws CannotInsertGameObjectException if the test fails
312     */
313    @Test
314    public void testPath1() throws CannotInsertGameObjectException {
315        // both root ==> relative
316        testPath("/a", "/b", "b"); // both root ==> relative
317
318        // to or from root ==> absolute
319        testPath("/HallOfSelection", "/world/world_104_115", "/world/world_104_115");
320        testPath("/world/world_104_115", "/HallOfSelection", "/HallOfSelection");
321
322        // same top-level component ==> relative
323        testPath("/a/b/c/d", "/a/d/e", "../../d/e");
324        testPath("/a/b/c/d", "/a/b/d", "../d");
325        testPath("/a/b/c", "/a/b/d", "d");
326        testPath("/a/b/c", "/a/b/c/d", "c/d");
327
328        // else ==> absolute
329        testPath("/a/b/c", "/b/c/d", "/b/c/d");
330        testPath("/a/b", "/b", "/b");
331        testPath("/a", "/b/c", "/b/c");
332    }
333
334    /**
335     * Checks that an exit path is correctly generated.
336     * @param mapPathFrom the map path to connect from; this map contains the
337     * exit object
338     * @param mapPathTo the map path to connect to
339     * @param expectedExitPath the expected map in the exit object
340     * @throws CannotInsertGameObjectException if the test fails
341     */
342    private void testPath(@NotNull final String mapPathFrom, @NotNull final String mapPathTo, @NotNull final String expectedExitPath) throws CannotInsertGameObjectException {
343        final ExitConnectorModel model = new TestExitConnectorModel();
344        final ExitConnectorActions<TestGameObject, TestMapArchObject, TestArchetype> actions = createActions(model);
345
346        assert testMapControlCreator != null;
347        final File mapDir = testMapControlCreator.getProjectSettings().getMapsDirectory();
348        final File mapFileFrom = new File(mapDir, mapPathFrom);
349        final File mapFileTo = new File(mapDir, mapPathTo);
350
351        final Size2D mapSize = new Size2D(5, 5);
352        assert testMapControlCreator != null;
353        final MapControl<TestGameObject, TestMapArchObject, TestArchetype> mapControl = testMapControlCreator.newMapControl(mapFileFrom, MAP_NAME1, mapSize);
354        final MapModel<TestGameObject, TestMapArchObject, TestArchetype> mapModel = mapControl.getMapModel();
355
356        final Point pointFrom = new Point(3, 4);
357        final Point pointTo = new Point(1, 2);
358
359        assert testMapControlCreator != null;
360        model.setExitLocation(new ExitLocation(mapFileTo, pointTo, MAP_NAME2, testMapControlCreator.getPathManager()));
361        mapModel.beginTransaction("TEST");
362        try {
363            assert mapModelHelper != null;
364            mapModelHelper.insertExit(mapModel, pointFrom);
365        } finally {
366            mapModel.endTransaction();
367        }
368
369        // perform connect
370        Assert.assertTrue(actions.doExitPaste(true, mapControl, pointFrom));
371        checkExit(mapModel, pointFrom, 0, expectedExitPath, pointTo);
372    }
373
374    /**
375     * Initializes the test case.
376     * @throws DuplicateArchetypeException if the test case fails
377     */
378    @Before
379    public void setUp() throws DuplicateArchetypeException {
380        testMapControlCreator = new TestMapControlCreator();
381        mapModelHelper = testMapControlCreator.newMapModelCreator();
382    }
383
384    /**
385     * Creates a new {@link ExitConnectorActions} instance.
386     * @param model the exit connector model to use
387     * @return the new exit connector actions instance
388     */
389    private ExitConnectorActions<TestGameObject, TestMapArchObject, TestArchetype> createActions(@NotNull final ExitConnectorModel model) {
390        final TestMapControlCreator tmpTestMapControlCreator = testMapControlCreator;
391        assert tmpTestMapControlCreator != null;
392        return new ExitConnectorActions<TestGameObject, TestMapArchObject, TestArchetype>(model, tmpTestMapControlCreator.getExitMatcher(), tmpTestMapControlCreator.getArchetypeSet(), tmpTestMapControlCreator.getMapManager(), new TestFileControl(), tmpTestMapControlCreator.getPathManager(), tmpTestMapControlCreator.getInsertionModeSet());
393    }
394
395    /**
396     * Checks that a map model contains an exit game object.
397     * @param mapModel the map model to check
398     * @param point the position to check
399     * @param index the index to check
400     * @param exitPath the expected exit path or <code>null</code> if no exit
401     * game object is expected
402     * @param exitPoint the expected exit destination or <code>null</code> if no
403     * exit game object is expected
404     */
405    private static void checkExit(@NotNull final MapModel<TestGameObject, TestMapArchObject, TestArchetype> mapModel, @NotNull final Point point, final int index, @Nullable final String exitPath, @Nullable final Point exitPoint) {
406        int thisIndex = 0;
407        for (final BaseObject<?, ?, ?, ?> gameObject : mapModel.getMapSquare(point)) {
408            if (thisIndex == index) {
409                if (gameObject.getTypeNo() != TestMapModelHelper.EXIT_TYPE) {
410                    break;
411                }
412
413                if (exitPath == null || exitPoint == null) {
414                    Assert.fail("exit found but none expected");
415                    throw new AssertionError();
416                }
417
418                Assert.assertEquals(exitPath, gameObject.getAttributeString(BaseObject.SLAYING));
419                Assert.assertEquals(exitPoint.x, gameObject.getAttributeInt(BaseObject.HP));
420                Assert.assertEquals(exitPoint.y, gameObject.getAttributeInt(BaseObject.SP));
421                return;
422            }
423
424            thisIndex++;
425        }
426
427        if (exitPath != null || exitPoint != null) {
428            Assert.fail("no exit found");
429        }
430    }
431
432}