Gridarta Editor
DefaultMapModel.java
Go to the documentation of this file.
1 /*
2  * Gridarta MMORPG map editor for Crossfire, Daimonin and similar games.
3  * Copyright (C) 2000-2015 The Gridarta Developers.
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18  */
19 
20 package net.sf.gridarta.model.mapmodel;
21 
22 import java.awt.Point;
23 import java.util.ArrayList;
24 import java.util.Collection;
25 import java.util.Collections;
26 import java.util.HashSet;
27 import java.util.Iterator;
28 import java.util.List;
29 import java.util.Set;
46 import net.sf.gridarta.utils.Size2D;
47 import org.apache.log4j.Category;
48 import org.apache.log4j.Logger;
49 import org.jetbrains.annotations.NotNull;
50 import org.jetbrains.annotations.Nullable;
51 
60 public class DefaultMapModel<G extends GameObject<G, A, R>, A extends MapArchObject<A>, R extends Archetype<G, A, R>> implements MapModel<G, A, R> {
61 
65  private static final long serialVersionUID = 1L;
66 
70  private static final Category LOG = Logger.getLogger(DefaultMapModel.class);
71 
75  private final transient Object syncLock = new Object();
76 
80  @NotNull
81  private final A mapArchObject;
82 
86  @NotNull
88 
93  @NotNull
95 
99  @NotNull
101 
105  @NotNull
107 
111  @NotNull
113 
120  private int transactionDepth;
121 
126  @Nullable
127  private transient Thread transactionThread;
128 
132  @NotNull
133  private final Set<MapSquare<G, A, R>> changedSquares = new HashSet<>();
134 
139  @NotNull
141 
145  @NotNull
146  private final Set<G> changedGameObjects = new HashSet<>();
147 
151  @NotNull
152  private final Set<G> transientChangedGameObjects = new HashSet<>();
153 
157  @NotNull
159 
164  private int activeEditType;
165 
170  @NotNull
172 
176  @NotNull
178 
182  @NotNull
184 
189  @Nullable
190  private MapFile mapFile;
191 
195  private boolean modified;
196 
200  @NotNull
202 
207  @NotNull
209 
210  @Override
211  public void mapMetaChanged() {
212  setModified();
213  }
214 
215  @Override
216  public void mapSizeChanged(@NotNull final Size2D mapSize) {
217  resizeMapInt(mapSize);
218  }
219 
220  };
221 
232  public DefaultMapModel(@NotNull final AutojoinLists<G, A, R> autojoinLists, @NotNull final A mapArchObject, @NotNull final ArchetypeChooserModel<G, A, R> archetypeChooserModel, @NotNull final GameObjectFactory<G, A, R> gameObjectFactory, @NotNull final GameObjectMatchers gameObjectMatchers, @NotNull final InsertionMode<G, A, R> topmostInsertionMode) {
233  this.mapArchObject = mapArchObject;
234  this.autojoinLists = autojoinLists;
235  this.archetypeChooserModel = archetypeChooserModel;
236  this.gameObjectFactory = gameObjectFactory;
237  this.gameObjectMatchers = gameObjectMatchers;
238  this.topmostInsertionMode = topmostInsertionMode;
240  mapArchObject.addMapArchObjectListener(mapArchObjectListener);
241  mapGrid = new MapSquareGrid<>(this, mapArchObject.getMapSize());
242  modified = false;
243  }
244 
245  @Override
246  public void mapClosed() {
247  mapArchObject.removeMapArchObjectListener(mapArchObjectListener);
248  }
249 
250  @NotNull
251  @Override
252  public A getMapArchObject() {
253  return mapArchObject;
254  }
255 
256  @Override
257  public Iterator<MapSquare<G, A, R>> iterator() {
258  return new MapSquareIterator<>(this, null, 1, false);
259  }
260 
261  @NotNull
262  @Override
263  public MapSquare<G, A, R> getMapSquare(@NotNull final Point pos) {
264  return mapGrid.getMapSquare(pos.x, pos.y);
265  }
266 
267  @Override
268  public void addObjectListToMap(@NotNull final Iterable<G> objects) {
269  for (final G gameObject : objects) {
270  if (!gameObject.isInContainer()) { // only map arches....
271  addGameObjectToMap(gameObject, new Point(gameObject.getMapX(), gameObject.getMapY()), topmostInsertionMode);
272  }
273  }
274  }
275 
276  @Override
277  public void clearMap() {
278  mapGrid.clearMap();
279  }
280 
281  @Override
282  public boolean isEmpty() {
283  return mapGrid.isEmpty();
284  }
285 
290  private void resizeMapInt(@NotNull final Size2D newSize) {
291  final Size2D mapSize = mapGrid.getMapSize();
292  if (newSize.equals(mapSize)) {
293  return;
294  }
295 
296  final Collection<GameObject<G, A, R>> objectsToDelete = new HashSet<>();
297 
298  // no other thread may access this map model while resizing
299  synchronized (syncLock) {
300  lightMapModelTracker.mapSizeChanging(newSize, mapSize);
301 
302  // first delete all arches in the area that will get cut off
303  // (this is especially important to remove all multi-part objects
304  // reaching into that area)
305  if (mapSize.getWidth() > newSize.getWidth()) {
306  // clear out the right stripe (as far as being cut off)
307  mapGrid.collectHeads(newSize.getWidth(), 0, mapSize.getWidth(), mapSize.getHeight(), objectsToDelete);
308  }
309 
310  if (mapSize.getHeight() > newSize.getHeight()) {
311  // clear out the bottom stripe (as far as being cut off)
312  mapGrid.collectHeads(0, newSize.getHeight(), Math.min(mapSize.getWidth(), newSize.getWidth()), mapSize.getHeight(), objectsToDelete);
313  }
314 
315  for (final GameObject<G, A, R> node : objectsToDelete) {
316  node.remove();
317  }
318 
319  mapGrid.resize(newSize);
320  discardInvalidMapSquares(changedSquares, newSize);
321  discardInvalidGameObjects(changedGameObjects, newSize);
322  discardInvalidGameObjects(transientChangedGameObjects, newSize);
323 
324  fireMapSizeChanged(newSize);
325  }
326  }
327 
333  private void discardInvalidMapSquares(@NotNull final Iterable<MapSquare<G, A, R>> mapSquares, @NotNull final Size2D mapSize) {
334  final Iterator<MapSquare<G, A, R>> it = mapSquares.iterator();
335  while (it.hasNext()) {
336  final MapSquare<G, A, R> mapSquare = it.next();
337  if (mapSquare.getMapX() >= mapSize.getWidth() || mapSquare.getMapY() >= mapSize.getHeight()) {
338  it.remove();
339  }
340  }
341  }
342 
348  private void discardInvalidGameObjects(@NotNull final Iterable<G> gameObjects, @NotNull final Size2D mapSize) {
349  final Iterator<G> it2 = gameObjects.iterator();
350  while (it2.hasNext()) {
351  final GameObject<G, A, R> gameObject = it2.next();
352  final G topGameObject = gameObject.getTopContainer();
353  if (topGameObject.getContainer() == null || topGameObject.getMapX() >= mapSize.getWidth() || topGameObject.getMapY() >= mapSize.getHeight()) {
354  it2.remove();
355  }
356  }
357  }
358 
359  @Override
360  public void addMapModelListener(@NotNull final MapModelListener<G, A, R> listener) {
361  mapModelListeners.add(listener);
362  }
363 
364  @Override
365  public void removeMapModelListener(@NotNull final MapModelListener<G, A, R> listener) {
366  mapModelListeners.remove(listener);
367  }
368 
369  @Override
370  public void addMapTransactionListener(@NotNull final MapTransactionListener<G, A, R> listener) {
371  mapTransactionListeners.add(listener);
372  }
373 
374  @Override
375  public void removeMapTransactionListener(@NotNull final MapTransactionListener<G, A, R> listener) {
376  mapTransactionListeners.remove(listener);
377  }
378 
379  @Override
380  public void beginSquareChange(@NotNull final MapSquare<G, A, R> mapSquare) {
381  if (transactionDepth == 0) {
382  LOG.error("beginSquareChange: square (" + mapSquare + ") is about to change outside a transaction");
383  return;
384  }
385 
386  savedSquares.recordMapSquare(mapSquare);
387  }
388 
389  @Override
390  public void endSquareChange(@NotNull final MapSquare<G, A, R> mapSquare) {
391  if (transactionDepth == 0) {
392  LOG.error("endSquareChange: square (" + mapSquare + ") was changed outside a transaction");
393  final Set<MapSquare<G, A, R>> mapSquares = new HashSet<>(1);
394  mapSquares.add(mapSquare);
395  lightMapModelTracker.mapSquaresChanged(Collections.unmodifiableCollection(mapSquares));
396  fireMapSquaresChangedEvent(mapSquares);
397  } else {
398  synchronized (changedSquares) {
399  changedSquares.add(mapSquare);
400  }
401  }
402  }
403 
404  @Override
405  public void beginGameObjectChange(@NotNull final G gameObject) {
406  if (transactionDepth == 0) {
407  LOG.error("beginGameObjectChange: game object (" + gameObject.getBestName() + "@" + gameObject.getMapSquare() + ") is about to change outside a transaction");
408  return;
409  }
410 
411  final MapSquare<G, A, R> mapSquare = gameObject.getMapSquare();
412  assert mapSquare != null;
413  savedSquares.recordMapSquare(mapSquare);
414  }
415 
416  @Override
417  public void endGameObjectChange(@NotNull final G gameObject) {
418  if (transactionDepth == 0) {
419  LOG.error("endGameObjectChange: game object (" + gameObject.getBestName() + "@" + gameObject.getMapSquare() + ") was changed outside a transaction");
420  final Set<G> gameObjects = new HashSet<>(1);
421  gameObjects.add(gameObject);
422  fireGameObjectsChangedEvent(gameObjects, Collections.emptySet());
423  } else {
424  synchronized (changedGameObjects) {
425  changedGameObjects.add(gameObject);
426  }
427  }
428  }
429 
430  @Override
431  public void transientGameObjectChange(@NotNull final G gameObject) {
432  if (transactionDepth == 0) {
433  LOG.error("transientGameObjectChange: game object (" + gameObject.getBestName() + "@" + gameObject.getMapSquare() + ") was changed outside a transaction");
434  final Set<G> gameObjects = new HashSet<>(1);
435  gameObjects.add(gameObject);
436  fireGameObjectsChangedEvent(Collections.emptySet(), gameObjects);
437  } else {
438  synchronized (transientChangedGameObjects) {
439  transientChangedGameObjects.add(gameObject);
440  }
441  }
442  }
443 
444  @Override
445  public void beginTransaction(@NotNull final String name) {
446  if (transactionDepth == 0) {
448  transactionThread = Thread.currentThread();
449  transactionDepth++;
450  fireBeginTransaction(name);
451  } else {
452  // == is okay for threads.
453  //noinspection ObjectEquality
454  if (transactionThread != Thread.currentThread()) {
455  throw new IllegalStateException("A transaction must only be used by one thread.");
456  }
457  transactionDepth++;
458  }
459  }
460 
461  @Override
462  public void endTransaction() {
463  endTransaction(false);
464  }
465 
466  @Override
467  public void endTransaction(final boolean fireEvent) {
468  if (transactionDepth <= 0) {
469  throw new IllegalStateException("Tried to end a transaction but no transaction was open.");
470  }
471  transactionDepth--;
472  assert transactionDepth >= 0;
473  if (transactionDepth == 0) {
475  } else if (fireEvent && transactionDepth == 1) {
476  fireEvents();
477  }
478  }
479 
483  private void fireEvents() {
484  // Call lightMapModelTracker first as it might change more game objects
485  transactionDepth++; // temporarily increase transaction depth because updating light information causes changes
486  try {
487  // Create copy to avoid ConcurrentModificationExceptions due to newly changed squares
488  final Collection<MapSquare<G, A, R>> mapSquares = new HashSet<>(changedSquares);
489  for (final G gameObject : changedGameObjects) {
490  final MapSquare<G, A, R> mapSquare = gameObject.getMapSquare();
491  if (mapSquare != null) {
492  mapSquares.add(mapSquare);
493  }
494  }
495  lightMapModelTracker.mapSquaresChanged(mapSquares);
496  } finally {
497  transactionDepth--;
498  }
499  if (!changedGameObjects.isEmpty() || !transientChangedGameObjects.isEmpty()) {
500  transientChangedGameObjects.removeAll(changedGameObjects);
501  transactionDepth++; // temporarily increase transaction depth because updating edit types causes transient changes
502  try {
503  for (final G gameObject : changedGameObjects) {
504  if (gameObject.isHead()) {
505  for (G env = gameObject; env != null; env = env.getContainerGameObject()) {
506  updateEditType(env, activeEditType);
507  }
508  }
509  }
510  } finally {
511  transactionDepth--;
512  }
513  fireGameObjectsChangedEvent(Collections.unmodifiableSet(changedGameObjects), Collections.unmodifiableSet(transientChangedGameObjects));
514  changedGameObjects.clear();
515  }
516  if (!changedSquares.isEmpty()) {
517  fireMapSquaresChangedEvent(Collections.unmodifiableSet(changedSquares));
518  changedSquares.clear();
519  }
520  }
521 
522  @Override
523  public void endAllTransactions() {
524  if (transactionDepth > 0) {
526  }
527  }
528 
533  private void commitTransaction() {
534  transactionDepth = 0;
535  transactionThread = null;
536  fireEvents();
539  }
540 
541  @Override
542  public int getTransactionDepth() {
543  return transactionDepth;
544  }
545 
546  @Override
547  public boolean isAnyTransactionActive() {
548  return transactionDepth > 0;
549  }
550 
555  private void fireMapSquaresChangedEvent(final Set<MapSquare<G, A, R>> mapSquares) {
556  setModified();
557  for (final MapModelListener<G, A, R> listener : mapModelListeners.getListeners()) {
558  listener.mapSquaresChanged(mapSquares);
559  }
560  }
561 
567  private void fireGameObjectsChangedEvent(@NotNull final Set<G> gameObjects, @NotNull final Set<G> transientGameObjects) {
568  if (!gameObjects.isEmpty()) {
569  setModified();
570  }
571  for (final MapModelListener<G, A, R> listener : mapModelListeners.getListeners()) {
572  listener.mapObjectsChanged(gameObjects, transientGameObjects);
573  }
574  }
575 
580  private void fireMapSizeChanged(@NotNull final Size2D newSize) {
581  setModified();
582  for (final MapModelListener<G, A, R> listener : mapModelListeners.getListeners()) {
583  listener.mapSizeChanged(newSize);
584  }
585  }
586 
590  private void firePreBeginTransaction() {
591  for (final MapTransactionListener<G, A, R> listener : mapTransactionListeners.getListeners()) {
592  listener.preBeginTransaction();
593  }
594  }
595 
600  private void fireBeginTransaction(@NotNull final String name) {
601  savedSquares.clear();
602  for (final MapTransactionListener<G, A, R> listener : mapTransactionListeners.getListeners()) {
603  listener.beginTransaction(name);
604  }
605  }
606 
610  private void fireEndTransaction() {
611  for (final MapTransactionListener<G, A, R> listener : mapTransactionListeners.getListeners()) {
612  listener.endTransaction(savedSquares);
613  }
614  savedSquares.clear();
615  }
616 
620  private void firePostEndTransaction() {
621  for (final MapTransactionListener<G, A, R> listener : mapTransactionListeners.getListeners()) {
622  listener.postEndTransaction();
623  }
624  }
625 
630  private void fireMapFileChanged(@Nullable final MapFile oldMapFile) {
631  for (final MapModelListener<G, A, R> listener : mapModelListeners.getListeners()) {
632  listener.mapFileChanged(oldMapFile);
633  }
634  }
635 
639  private void fireModifiedChanged() {
640  for (final MapModelListener<G, A, R> listener : mapModelListeners.getListeners()) {
641  listener.modifiedChanged();
642  }
643  }
644 
645  @Override
646  public void removeGameObject(@NotNull final G gameObject, final boolean join) {
647  final MapSquare<?, ?, ?> mapSquare = gameObject.getMapSquare();
648  assert mapSquare != null;
649  if (gameObject.isInContainer()) {
650  gameObject.remove();
651  } else {
652  gameObject.remove();
653 
654  if (join) {
655  joinDelete(mapSquare.getMapLocation(), gameObject.getArchetype());
656  }
657  }
658  }
659 
660  @Override
661  public boolean isMultiArchFittingToMap(@NotNull final Archetype<G, A, R> archetype, @NotNull final Point pos, final boolean allowDouble) {
662  for (Archetype<G, A, R> part = archetype; part != null; part = part.getMultiNext()) {
663  final Point point = new Point(part.getMultiX(), part.getMultiY());
664  point.translate(pos.x, pos.y);
665  // outside map
666  if (!mapArchObject.isPointValid(point)) {
667  return false;
668  }
669 
670  if (!allowDouble) {
671  final String temp = part.getArchetypeName();
672  for (final BaseObject<G, A, R, ?> node : mapGrid.getMapSquare(point.x, point.y)) {
673  if (node.getArchetype().getArchetypeName().equals(temp)) {
674  return false;
675  }
676  }
677  }
678  }
679 
680  return true;
681  }
682 
683  @Override
684  public void setErrors(@NotNull final ErrorCollector<G, A, R> errors) {
685  this.errors = errors;
686  for (final MapModelListener<G, A, R> listener : mapModelListeners.getListeners()) {
687  listener.errorsChanged(errors);
688  }
689  }
690 
691  @NotNull
692  @Override
694  return errors;
695  }
696 
700  @Nullable
701  @Override
702  public G insertArchToMap(@NotNull final BaseObject<G, A, R, ?> templateBaseObject, @Nullable final G nextGameObject, @NotNull final Point pos, final boolean join) {
703  // map coordinates must be valid
704  if (!mapArchObject.isPointValid(pos)) {
705  return null;
706  }
707 
708  final G newGameObject;
709  final GameObject<G, A, R> nextGameObjectEnv = nextGameObject == null ? null : nextGameObject.getContainerGameObject();
710  if (nextGameObject == null || nextGameObjectEnv == null) {
711  newGameObject = insertBaseObject(templateBaseObject, pos, true, join, topmostInsertionMode);
712  if (newGameObject == null) {
713  return null;
714  }
715 
716  int position = 0;
717  for (final G gameObject : mapGrid.getMapSquare(pos.x, pos.y).reverse()) {
718  // This is okay because nextGameObject is on the desired square.
719  //noinspection ObjectEquality
720  if (gameObject == nextGameObject) {
721  break;
722  }
723  position++;
724  }
725  for (int i = 0; i < position - 1; i++) {
726  newGameObject.moveDown();
727  }
728  } else {
729  newGameObject = templateBaseObject.newInstance(gameObjectFactory);
730  nextGameObjectEnv.addLast(newGameObject);
731  if (templateBaseObject instanceof Archetype) {
732  gameObjectFactory.createInventory(newGameObject, templateBaseObject);
733  }
734  }
735 
736  return newGameObject;
737  }
738 
739  @Nullable
740  @Override
741  public G insertBaseObject(@NotNull final BaseObject<G, A, R, ?> baseObject, @NotNull final Point pos, final boolean allowMany, final boolean join, @NotNull final InsertionMode<G, A, R> insertionMode) {
742  if (!mapArchObject.isPointValid(pos)) {
743  return null;
744  }
745 
746  final R realArchetype = baseObject.getArchetype();
747  final R effectiveArchetype;
748  if (join) {
749  final InsertionResult<G, A, R> insertionResult = joinInsert(pos, realArchetype);
750  final G gameObject = insertionResult.getGameObject();
751  if (gameObject != null) {
752  return gameObject;
753  }
754 
755  effectiveArchetype = insertionResult.getArchetype();
756  if (effectiveArchetype == null) {
757  return null; // only one autojoin type per square allowed
758  }
759  } else {
760  effectiveArchetype = realArchetype;
761  }
762 
763  if (!isMultiArchFittingToMap(effectiveArchetype, pos, allowMany)) {
764  return null;
765  }
766 
767  final Integer direction = effectiveArchetype.usesDirection() ? archetypeChooserModel.getDirection() : null;
768 
769  final List<G> parts = new ArrayList<>();
770  for (R archetypePart = effectiveArchetype; archetypePart != null; archetypePart = archetypePart.getMultiNext()) {
771  final G part;
772  if (archetypePart == effectiveArchetype) {
773  part = baseObject.newInstance(gameObjectFactory);
774  part.setArchetype(archetypePart);
775  } else {
776  part = archetypePart.newInstance(gameObjectFactory);
777  }
778  if (direction != null) {
779  part.setAttributeInt(BaseObject.DIRECTION, direction);
780  }
781  if (!parts.isEmpty()) {
782  parts.get(0).addTailPart(part);
783  }
784  parts.add(part);
785  }
786 
787  for (final G part : parts) {
788  final int mapX = pos.x + part.getArchetype().getMultiX();
789  final int mapY = pos.y + part.getArchetype().getMultiY();
790  insertionMode.insert(part, mapGrid.getMapSquare(mapX, mapY));
791  }
792 
793  final G head = parts.get(0);
794  if (baseObject instanceof Archetype) {
795  gameObjectFactory.createInventory(head, effectiveArchetype);
796  }
797 
798  return head;
799  }
800 
801  @Override
802  public void addGameObjectToMap(@NotNull final G gameObject, @NotNull final Point pos, @NotNull final InsertionMode<G, A, R> insertionMode) {
803  if (!mapArchObject.isPointValid(pos)) {
804  LOG.error("addGameObjectToMap: trying to insert game object out of map bounds at " + pos.x + "/" + pos.y + ", map bounds is " + mapArchObject.getMapSize());
805  return;
806  }
807 
808  insertionMode.insert(gameObject, mapGrid.getMapSquare(pos.x, pos.y));
809  }
810 
811  @Override
812  public void moveEnv(@NotNull final G gameObject, @NotNull final Point pos, @NotNull final G nextGameObject) {
813  if (!nextGameObject.isHead()) {
814  throw new IllegalArgumentException();
815  }
816  assert !gameObject.isMulti(); // no tail parts should be present when inside inventory of another game object
817  final GameObjectContainer<G, A, R> nextGameObjectContainer;
818  if (nextGameObject.isInContainer()) {
819  nextGameObjectContainer = nextGameObject.getContainer();
820  if (nextGameObjectContainer == null) {
821  throw new IllegalArgumentException();
822  }
823  } else {
824  nextGameObjectContainer = getMapSquare(pos);
825  }
826  final MapSquare<G, A, R> mapSquare = nextGameObjectContainer.getMapSquare();
827  if (mapSquare == null || mapSquare.getMapModel() != this) {
828  throw new IllegalArgumentException();
829  }
830  gameObject.remove();
831  nextGameObjectContainer.insertAfter(nextGameObject, gameObject);
832 
833  // regenerate tail parts when inserted into a map square
834  if (!nextGameObject.isInContainer() && gameObject.getArchetype().isMulti()) {
835  final Point tmp = new Point();
836  for (R archetypeTail = gameObject.getArchetype().getMultiNext(); archetypeTail != null; archetypeTail = archetypeTail.getMultiNext()) {
837  final G gameObjectTail = archetypeTail.newInstance(gameObjectFactory);
838  gameObject.addTailPart(gameObjectTail);
839  tmp.x = pos.x + archetypeTail.getMultiX();
840  tmp.y = pos.y + archetypeTail.getMultiY();
841  addGameObjectToMap(gameObjectTail, tmp, topmostInsertionMode);
842  }
843  }
844  }
845 
846  @Override
847  public void moveInv(@NotNull final G gameObject, @NotNull final GameObject<G, A, R> prevGameObject) {
848  if (!gameObject.isHead() || !prevGameObject.isHead()) {
849  throw new IllegalArgumentException();
850  }
851 
852  gameObject.remove();
853  gameObject.removeTailParts();
854  prevGameObject.addFirst(gameObject);
855  }
856 
857  @Override
858  public boolean isAreaEmpty(final int left, final int top, final int width, final int height) {
859  final Point point = new Point();
860  for (int x = left; x < left + width; x++) {
861  point.x = x;
862  for (int y = top; y < top + height; y++) {
863  point.y = y;
864  if (!getMapSquare(point).isEmpty()) {
865  return false;
866  }
867  }
868  }
869  return true;
870  }
871 
872  @Override
873  public void addActiveEditType(final int editType) {
874  // calculate only if needed
875  if ((activeEditType & editType) != editType) {
876  beginTransaction("update edit types");
877  try {
878  for (final Iterable<G> mapSquare : this) {
879  for (final GameObject<G, A, R> gameObject : mapSquare) {
880  updateEditType(gameObject.getHead(), editType);
881  }
882  }
883  } finally {
884  endTransaction();
885  }
886  // from now on we have this type, so we don't have to calculate it again
887  activeEditType |= editType;
888  }
889  }
890 
891  @Override
892  public void setMapFile(@Nullable final MapFile mapFile) {
893  if (this.mapFile == null ? mapFile == null : this.mapFile.equals(mapFile)) {
894  return;
895  }
896 
897  final MapFile oldMapFile = this.mapFile;
898  this.mapFile = mapFile;
899  fireMapFileChanged(oldMapFile);
900  }
901 
902  @Nullable
903  @Override
904  public MapFile getMapFile() {
905  return mapFile;
906  }
907 
908  @NotNull
909  @Override
910  public MapFile getMapFile(@NotNull final MapPath mapPath) throws SameMapException, UnsavedMapException {
911  final MapFile thisMapFile = mapFile;
912  if (thisMapFile == null) {
913  throw new UnsavedMapException();
914  }
915  final MapFile newMapFile = new MapFile(thisMapFile, mapPath);
916  if (newMapFile.equals(thisMapFile)) {
917  throw new SameMapException();
918  }
919  return newMapFile;
920  }
921 
922  @NotNull
923  @Override
924  public List<G> getAllGameObjects() {
925  final List<G> gameObjects = new ArrayList<>();
926  for (final Iterable<G> mapSquare : this) {
927  for (final G gameObject : mapSquare) {
928  if (gameObject.isHead()) {
929  gameObjects.add(gameObject);
930  }
931  }
932  }
933  return gameObjects;
934  }
935 
936  @Override
937  public boolean isModified() {
938  return modified;
939  }
940 
941  @Override
942  public void resetModified() {
943  if (!modified) {
944  return;
945  }
946 
947  modified = false;
949  }
950 
951  @Override
952  public void facesReloaded() {
953  beginTransaction("reload faces");
954  try {
955  for (final Iterable<G> mapSquare : this) {
956  for (final G gameObject : mapSquare) {
957  gameObject.facesReloaded();
958  }
959  }
960  } finally {
961  endTransaction();
962  }
963  }
964 
965  @Override
966  public void nextPoint(final Point point, final int direction) {
967  final Size2D mapSize = mapArchObject.getMapSize();
968  if (direction > 0) {
969  point.x++;
970  if (point.x >= mapSize.getWidth()) {
971  point.x = 0;
972  point.y++;
973  if (point.y >= mapSize.getHeight()) {
974  point.y = 0;
975  }
976  }
977  } else {
978  point.x--;
979  if (point.x < 0) {
980  point.x = mapSize.getWidth() - 1;
981  point.y--;
982  if (point.y < 0) {
983  point.y = mapSize.getHeight() - 1;
984  }
985  }
986  }
987  }
988 
992  private void setModified() {
993  if (modified) {
994  return;
995  }
996 
997  modified = true;
999  }
1000 
1007  private void updateEditType(@NotNull final GameObject<G, A, R> gameObject, final int checkType) {
1008  assert gameObject.isHead();
1009  if (checkType == 0) {
1010  return;
1011  }
1012 
1013  // all flags from 'checkType' must be unset in this game object because
1014  // they get recalculated now
1015  final int editType = gameObject.getEditType();
1016  final int retainedEditType = editType == BaseObject.EDIT_TYPE_NONE ? 0 : editType & ~checkType;
1017  final int newEditType = retainedEditType | calculateEditType(gameObject, checkType);
1018  gameObject.setEditType(newEditType);
1019  }
1020 
1027  private int calculateEditType(@NotNull final GameObject<?, ?, ?> gameObject, final int checkType) {
1028  int editType = 0;
1029  for (final NamedGameObjectMatcher matcher : gameObjectMatchers) {
1030  final int matcherEditType = matcher.getEditType();
1031  if ((matcherEditType & checkType) != 0 && matcher.isMatching(gameObject)) {
1032  editType |= matcherEditType;
1033  }
1034  }
1035  return editType;
1036  }
1037 
1048  @NotNull
1049  private InsertionResult<G, A, R> joinInsert(@NotNull final Point point, @NotNull final R archetype) {
1050  if (archetype.isMulti()) {
1051  return new InsertionResult<>(null, archetype);
1052  }
1053 
1054  final AutojoinList<G, A, R> autojoinList = autojoinLists.getAutojoinList(archetype);
1055  if (autojoinList == null) {
1056  return new InsertionResult<>(null, archetype);
1057  }
1058 
1059  // if there already is an archetype of this list at point -> abort
1060  final boolean isMainIndex = autojoinList.isMainIndex(archetype);
1061  final G gameObject = findGameObjectOfAutojoinList(point, autojoinList);
1062  if (gameObject != null) {
1063  final R gameObjectArchetype = gameObject.getArchetype();
1064  final boolean isExistingMainIndex = autojoinList.isMainIndex(gameObjectArchetype);
1065  if (isMainIndex) {
1066  // alt/main -> main
1067  if (isExistingMainIndex) {
1068  // ignore main -> main
1069  return new InsertionResult<>(null, null); // we don't want same archetypes over each other
1070  } else {
1071  // alt -> main -> update
1072  }
1073  } else {
1074  // alt/main -> alt
1075  if (isExistingMainIndex) {
1076  // main -> alt -> update
1077  } else {
1078  // alt -> alt -> update if different archetype
1079  if (gameObjectArchetype == archetype) {
1080  return new InsertionResult<>(null, null); // we don't want same archetypes over each other
1081  }
1082  }
1083  }
1084  }
1085 
1086  // now do the joining in all four directions:
1087  final int altIndex = isMainIndex ? -1 : autojoinList.getAlternativeIndex(archetype);
1088  int newIndex = 0; // return value, see above
1089  newIndex |= joinInsert(point, autojoinList, 0, -1, AutojoinList.NORTH, AutojoinList.SOUTH, altIndex);
1090  newIndex |= joinInsert(point, autojoinList, +1, 0, AutojoinList.EAST, AutojoinList.WEST, altIndex);
1091  newIndex |= joinInsert(point, autojoinList, 0, +1, AutojoinList.SOUTH, AutojoinList.NORTH, altIndex);
1092  newIndex |= joinInsert(point, autojoinList, -1, 0, AutojoinList.WEST, AutojoinList.EAST, altIndex);
1093  final R newArchetype = isMainIndex ? autojoinList.getArchetype(newIndex) : archetype;
1094  if (gameObject != null) {
1095  gameObject.setArchetype(newArchetype);
1096  return new InsertionResult<>(gameObject, null);
1097  }
1098  return new InsertionResult<>(null, newArchetype);
1099  }
1100 
1101  private int joinInsert(@NotNull final Point point, @NotNull final AutojoinList<G, A, R> autojoinList, final int dx, final int dy, final int dir, final int reverseDir, final int altIndex) {
1102  final Point tmp = new Point(point.x + dx, point.y + dy);
1103  if (!mapArchObject.isPointValid(tmp)) {
1104  return 0;
1105  }
1106 
1107  final GameObject<G, A, R> gameObject = findGameObjectOfAutojoinList(tmp, autojoinList);
1108  if (gameObject == null) {
1109  return 0;
1110  }
1111 
1112  final R archetype = gameObject.getArchetype();
1113  final int index = autojoinList.getAlternativeIndex(archetype);
1114  if (index != -1) {
1115  return (index & reverseDir) == 0 ? 0 : dir;
1116  }
1117 
1118  final int archetypeIndex = autojoinList.getIndex(archetype);
1119  final int newIndex;
1120  if ((altIndex & dir) == 0) {
1121  newIndex = archetypeIndex & ~reverseDir;
1122  } else {
1123  newIndex = archetypeIndex | reverseDir;
1124  }
1125  gameObject.setArchetype(autojoinList.getArchetype(newIndex));
1126  return dir;
1127  }
1128 
1137  private void joinDelete(@NotNull final Point point, @NotNull final R archetype) {
1138  if (archetype.isMulti()) {
1139  return;
1140  }
1141 
1142  final AutojoinList<G, A, R> autojoinList = autojoinLists.getAutojoinList(archetype);
1143  if (autojoinList == null) {
1144  return;
1145  }
1146 
1147  final boolean isMainIndex = autojoinList.isMainIndex(archetype);
1148  final int altIndex = isMainIndex ? -1 : autojoinList.getAlternativeIndex(archetype);
1149 
1150  joinDelete(point, autojoinList, 0, -1, AutojoinList.NORTH, AutojoinList.SOUTH, altIndex);
1151  joinDelete(point, autojoinList, +1, 0, AutojoinList.EAST, AutojoinList.WEST, altIndex);
1152  joinDelete(point, autojoinList, 0, +1, AutojoinList.SOUTH, AutojoinList.NORTH, altIndex);
1153  joinDelete(point, autojoinList, -1, 0, AutojoinList.WEST, AutojoinList.EAST, altIndex);
1154  }
1155 
1156  private void joinDelete(@NotNull final Point point, @NotNull final AutojoinList<G, A, R> autojoinList, final int dx, final int dy, final int reverseDir, final int dir, final int altIndex) {
1157  if ((altIndex & reverseDir) == 0) {
1158  return;
1159  }
1160 
1161  final Point tmp = new Point(point.x + dx, point.y + dy);
1162  if (!mapArchObject.isPointValid(tmp)) {
1163  return;
1164  }
1165 
1166  final GameObject<G, A, R> gameObject = findMainGameObjectOfAutojoinList(tmp, autojoinList);
1167  if (gameObject == null) {
1168  return;
1169  }
1170 
1171  gameObject.setArchetype(autojoinList.getArchetype(autojoinList.getIndex(gameObject.getArchetype()) & ~dir));
1172  }
1173 
1182  @Nullable
1183  private G findGameObjectOfAutojoinList(@NotNull final Point point, @NotNull final AutojoinList<G, A, R> autojoinList) {
1184  for (final G gameObject : getMapSquare(point).reverse()) {
1185  if (autojoinLists.getAutojoinList(gameObject.getArchetype()) == autojoinList) {
1186  return gameObject;
1187  }
1188  }
1189 
1190  return null;
1191  }
1192 
1201  @Nullable
1202  private GameObject<G, A, R> findMainGameObjectOfAutojoinList(@NotNull final Point point, @NotNull final AutojoinList<G, A, R> autojoinList) {
1203  for (final GameObject<G, A, R> gameObject : getMapSquare(point).reverse()) {
1204  final R archetype = gameObject.getArchetype();
1205  if (autojoinLists.getAutojoinList(archetype) == autojoinList && autojoinList.isMainIndex(archetype)) {
1206  return gameObject;
1207  }
1208  }
1209 
1210  return null;
1211  }
1212 
1213 }
void addMapTransactionListener(@NotNull final MapTransactionListener< G, A, R > listener)
void updateEditType(@NotNull final GameObject< G, A, R > gameObject, final int checkType)
Updates the edit type of a GameObject.
int getMapX()
Returns the x coordinate on the map.
Definition: MapSquare.java:107
void fireEvents()
Deliver all pending events.
G insertBaseObject(@NotNull final BaseObject< G, A, R, ?> baseObject, @NotNull final Point pos, final boolean allowMany, final boolean join, @NotNull final InsertionMode< G, A, R > insertionMode)
Interface for listeners listening on map arch object changes.
G insertArchToMap(@NotNull final BaseObject< G, A, R, ?> templateBaseObject, @Nullable final G nextGameObject, @NotNull final Point pos, final boolean join)
I&#39;m too complex
DefaultMapModel(@NotNull final AutojoinLists< G, A, R > autojoinLists, @NotNull final A mapArchObject, @NotNull final ArchetypeChooserModel< G, A, R > archetypeChooserModel, @NotNull final GameObjectFactory< G, A, R > gameObjectFactory, @NotNull final GameObjectMatchers gameObjectMatchers, @NotNull final InsertionMode< G, A, R > topmostInsertionMode)
Create a new instance.
final Set< G > transientChangedGameObjects
The ArrayList with transient changed gameObjects.
Exception thrown if the destination path points to the source map.
boolean isMultiArchFittingToMap(@NotNull final Archetype< G, A, R > archetype, @NotNull final Point pos, final boolean allowDouble)
R getArchetype()
Returns the Archetype that should be inserted.
void fireGameObjectsChangedEvent(@NotNull final Set< G > gameObjects, @NotNull final Set< G > transientGameObjects)
Fire a GameObjectsChangedEvent.
A MapModel reflects the data of a map.
Definition: MapModel.java:75
void resizeMapInt(@NotNull final Size2D newSize)
Resizes the map grid after the map size has changed.
void firePreBeginTransaction()
Fire a pre-begin transaction event.
T [] getListeners()
Returns an array of all the listeners.
boolean modified
Set if the map has changed since last save.
void recordMapSquare(@NotNull final MapSquare< G, A, R > mapSquare)
Records a map square as changed.
void endGameObjectChange(@NotNull final G gameObject)
This package contains classes related to matching GameObjects, so called GameObjectMatchers.
abstract MapSquare< G, A, R > getMapSquare()
Returns the MapSquare of this container.
final EventListenerList2< MapTransactionListener< G, A, R > > mapTransactionListeners
The registered MapTransactionListeners.
T getMultiNext()
Returns the next of this multi-part object.
final AutojoinLists< G, A, R > autojoinLists
The AutojoinLists for performing autojoining.
MapModel< G, A, R > getMapModel()
Returns the MapModel this map square is part of.
Definition: MapSquare.java:99
This package contains the framework for validating maps.
MapSquare< G, A, R > getMapSquare(@NotNull final Point pos)
void setArchetype(@NotNull R archetype)
Set the Archetype of this GameObject.
void beginSquareChange(@NotNull final MapSquare< G, A, R > mapSquare)
void removeMapModelListener(@NotNull final MapModelListener< G, A, R > listener)
void endSquareChange(@NotNull final MapSquare< G, A, R > mapSquare)
void setMapFile(@Nullable final MapFile mapFile)
Interface for listeners listening on MapModel events.
void nextPoint(final Point point, final int direction)
G getContainerGameObject()
Returns the environment game object if this game object is in the inventory or.
void resize(@NotNull final Size2D newSize)
Resizes the map grid to a new size.
void beginTransaction(@NotNull final String name)
Base class for classes that contain GameObjects as children in the sense of containment.
final LightMapModelTracker< G, A, R > lightMapModelTracker
The LightMapModelTracker tracking this instance.
Represents a maps directory local map path.
Definition: MapPath.java:31
boolean isEmpty()
Returns whether the map is empty.
Decorates an arbitrary GameObjectMatcher with a localized name that is suitable for the user interfac...
final transient MapArchObjectListener mapArchObjectListener
The MapArchObjectListener used to detect changes in mapArchObject and set the modified flag according...
void createInventory(@NotNull GameObject< G, A, R > gameObject, @NotNull Iterable< G > archetype)
Copies inventory objects from an archetype into a game object.
void setModified()
Marks the map as being modified.
final A mapArchObject
The MapArchObject associated with this model.
ErrorCollector< G, A, R > errors
The errors of this map model.
G findGameObjectOfAutojoinList(@NotNull final Point point, @NotNull final AutojoinList< G, A, R > autojoinList)
Looks for an archetype at map-position point which is part of an autojoin list.
InsertionResult< G, A, R > joinInsert(@NotNull final Point point, @NotNull final R archetype)
Do autojoining on insertion of an game object on the map.
Contains a list of (typically wall-)arches which do autojoining.
Base package of all Gridarta classes.
void setErrors(@NotNull final ErrorCollector< G, A, R > errors)
Interface for listeners listening on map transactions of MapModels.
final GameObjectFactory< G, A, R > gameObjectFactory
The GameObjectFactory for creating GameObjects.
Reflects a game object (object on a map).
Definition: GameObject.java:36
boolean equals(@Nullable final Object obj)
Definition: MapFile.java:122
Abstract factory for creating GameObject instances.
void fireBeginTransaction(@NotNull final String name)
Fire a begin transaction event.
void discardInvalidMapSquares(@NotNull final Iterable< MapSquare< G, A, R >> mapSquares, @NotNull final Size2D mapSize)
Discards map squares that are out of map bounds.
void removeGameObject(@NotNull final G gameObject, final boolean join)
String DIRECTION
The attribute name of the object&#39;s direction.
Definition: BaseObject.java:48
void fireMapFileChanged(@Nullable final MapFile oldMapFile)
Fires a map file changed event.
void remove(@NotNull final T listener)
Removes a listener.
boolean isMainIndex(@NotNull final R archetype)
Returns the index of an Archetype if it is a main archetype for any direction.
void mapSizeChanging(@NotNull final Size2D newSize, @NotNull final Size2D oldSize)
Called whenever the tracked map is about to change size.
GameObjects are the objects based on Archetypes found on maps.
void removeMapTransactionListener(@NotNull final MapTransactionListener< G, A, R > listener)
void add(@NotNull final T listener)
Adds a listener.
int joinInsert(@NotNull final Point point, @NotNull final AutojoinList< G, A, R > autojoinList, final int dx, final int dy, final int dir, final int reverseDir, final int altIndex)
int getWidth()
Returns the width of the area.
Definition: Size2D.java:96
void transientGameObjectChange(@NotNull final G gameObject)
Maintains GameObjectMatcher instances.
Point getMapLocation()
Returns the coordinate on the map.
Definition: MapSquare.java:124
int calculateEditType(@NotNull final GameObject<?, ?, ?> gameObject, final int checkType)
Returns the edit type for a GameObject.
void fireMapSizeChanged(@NotNull final Size2D newSize)
Fire a map size changed event.
AutojoinList< G, A, R > getAutojoinList(@NotNull final Archetype< G, A, R > archetype)
Returns an AutojoinList for a given archetype.
final Set< G > changedGameObjects
The ArrayList with changed gameObjects.
void addMapModelListener(@NotNull final MapModelListener< G, A, R > listener)
void clearMap()
This implementation is very safe by recreating every single MapSquare as new empty square with the tr...
void insertAfter(@Nullable final G previousGameObject, @NotNull final G gameObject)
Add a GameObject after another.
static final long serialVersionUID
The serial version UID.
void fireEndTransaction()
Fire an end transaction event.
void mapSquaresChanged(@NotNull final Iterable< MapSquare< G, A, R >> mapSquares)
Called whenever some game objects have changed.
void joinDelete(@NotNull final Point point, @NotNull final R archetype)
Do autojoining on deletion of an GameObject on the map.
void addObjectListToMap(@NotNull final Iterable< G > objects)
MapSquare< G, A, R > getMapSquare(final int x, final int y)
Returns the MapSquare at a given location.
static final Category LOG
The Logger for printing log messages.
R getArchetype()
Returns the Archetype this GameObject is based on.
final transient Object syncLock
Sync Lock Object.
final EventListenerList2< MapModelListener< G, A, R > > mapModelListeners
The registered MapModelListeners.
void fireMapSquaresChangedEvent(final Set< MapSquare< G, A, R >> mapSquares)
Fire a MapSquaresChangedEvent.
final GameObjectMatchers gameObjectMatchers
The GameObjectMatchers to use.
Type-safe version of EventListenerList.
int getAlternativeIndex(@NotNull final R archetype)
Returns the index of an Archetype if it is an alternative archetype for any direction.
final SavedSquares< G, A, R > savedSquares
Records unchanged square contents for all squares in changedSquares.
void addGameObjectToMap(@NotNull final G gameObject, @NotNull final Point pos, @NotNull final InsertionMode< G, A, R > insertionMode)
Iterator< MapSquare< G, A, R > > iterator()
void clear()
Forgets all saved squares.
final Set< MapSquare< G, A, R > > changedSquares
The ArrayList with changed squares.
final InsertionMode< G, A, R > topmostInsertionMode
The "topmost" InsertionMode.
Exception thrown if an operation is attempted on an unsaved map.
Manages a mapping between archetypes to AutojoinLists.
MapFile getMapFile(@NotNull final MapPath mapPath)
void moveInv(@NotNull final G gameObject, @NotNull final GameObject< G, A, R > prevGameObject)
An interface for classes that collect errors.
void firePostEndTransaction()
Fire a post-end transaction event.
void moveEnv(@NotNull final G gameObject, @NotNull final Point pos, @NotNull final G nextGameObject)
G getGameObject()
Returns the GameObject that has been modified.
G getTopContainer()
Get the topmost container of this GameObject (in Game sense, which means being in a MapSquare isn&#39;t...
void remove(@NotNull final G gameObject)
Remove a GameObject from this container.
final MapSquareGrid< G, A, R > mapGrid
The map, containing all arches grid-wise.
Simple error collector that just collects the error in a collection for later retrieval through itera...
void fireModifiedChanged()
Fires a map size changed event.
int getHeight()
Returns the height of the area.
Definition: Size2D.java:104
Size2D getMapSize()
Returns the size of this map grid in map squares.
GameObject< G, A, R > findMainGameObjectOfAutojoinList(@NotNull final Point point, @NotNull final AutojoinList< G, A, R > autojoinList)
Looks for an archetype at map-position point which is the main archetypes part of an autojoin list...
void joinDelete(@NotNull final Point point, @NotNull final AutojoinList< G, A, R > autojoinList, final int dx, final int dy, final int reverseDir, final int dir, final int altIndex)
void addLast(@NotNull G gameObject)
Add the given GameObject at the end of this Container.
void commitTransaction()
Performs ending a transaction.
The result of an insertion operation involving autojoining.
final ArchetypeChooserModel< G, A, R > archetypeChooserModel
The ArchetypeChooserModel to use when inserting directional game objects.
void discardInvalidGameObjects(@NotNull final Iterable< G > gameObjects, @NotNull final Size2D mapSize)
Discards game objects that are out of map bounds.
int activeEditType
Contains the edit types that have already been (requested and) calculated (edit types get calculated ...
void collectHeads(final int minX, final int minY, final int maxX, final int maxY, @NotNull final Collection< GameObject< G, A, R >> objectsToDelete)
Adds all head parts for game object within an area to a collection.
The location of a map file with a map directory.
Definition: MapFile.java:31
int getMapY()
Returns the y coordinate on the map.
Definition: MapSquare.java:115
transient Thread transactionThread
The thread that performs the current transaction.
Implementation of MapModel that covers the similarities between crossfire and daimonin.
void beginGameObjectChange(@NotNull final G gameObject)
boolean isAreaEmpty(final int left, final int top, final int width, final int height)
The class Size2D represents a 2d rectangular area.
Definition: Size2D.java:30
Integer getDirection()
Returns the default direction for game objects created from archetypes.
Iterator for iterating over all squares of a model.