Gridarta Editor
AbstractMapArchObject.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.maparchobject;
21 
22 import java.awt.Point;
23 import java.io.File;
24 import java.util.Arrays;
25 import java.util.regex.Matcher;
26 import java.util.regex.Pattern;
32 import net.sf.gridarta.utils.Size2D;
33 import org.jetbrains.annotations.NotNull;
34 import org.jetbrains.annotations.Nullable;
35 
45 public abstract class AbstractMapArchObject<A extends MapArchObject<A>> implements MapArchObject<A> {
46 
50  private static final long serialVersionUID = 1L;
51 
55  @NotNull
56  public static final String MAP_NAME_UNNAMED = "<unnamed>";
57 
62  @NotNull
63  private static final String MODIFIED_ATTRIBUTE = "Modified:";
64 
68  @NotNull
69  private static final Pattern MODIFIED_ATTRIBUTE_PATTERN = Pattern.compile("^" + MODIFIED_ATTRIBUTE + " *(.*)$", Pattern.MULTILINE);
70 
75  @NotNull
76  private static final Pattern MAP_FILE_NAME_PATTERN = Pattern.compile("((?:(?!_(?:[\\d-]+))[\\w_])+)_([\\d-]+)_([\\d-]+)(?:_([\\d-]+))?");
77 
81  @NotNull
82  private final StringBuilder msgText = new StringBuilder();
83 
87  @NotNull
88  private Size2D mapSize;
89 
93  @NotNull
94  private String mapName;
95 
99  private int enterX;
100 
104  private int enterY;
105 
109  private boolean outdoor;
110 
114  private int resetTimeout;
115 
120  private int swapTime;
121 
125  private int difficulty;
126 
131  private boolean fixedReset;
132 
136  private int darkness;
137 
143  @NotNull
144  private final String[] tilePaths;
145 
149  @NotNull
151 
158  private int transactionDepth;
159 
164  @Nullable
165  private transient Thread transactionThread;
166 
170  private boolean attributeHasChanged;
171 
175  protected AbstractMapArchObject() {
176  tilePaths = new String[Direction.values().length];
177  Arrays.fill(tilePaths, "");
178  mapSize = Size2D.ONE;
179  mapName = MAP_NAME_UNNAMED;
180  }
181 
186  protected AbstractMapArchObject(@NotNull final AbstractMapArchObject<A> mapArchObject) {
187  msgText.append(mapArchObject.msgText);
188  mapSize = mapArchObject.mapSize;
189  mapName = mapArchObject.mapName;
190  enterX = mapArchObject.enterX;
191  enterY = mapArchObject.enterY;
192  outdoor = mapArchObject.outdoor;
193  resetTimeout = mapArchObject.resetTimeout;
194  swapTime = mapArchObject.swapTime;
195  difficulty = mapArchObject.difficulty;
196  fixedReset = mapArchObject.fixedReset;
197  darkness = mapArchObject.darkness;
198  tilePaths = mapArchObject.tilePaths.clone();
199  }
200 
201  @NotNull
202  @Override
203  public Size2D getMapSize() {
204  return mapSize;
205  }
206 
207  @Override
208  public void setMapSize(@NotNull final Size2D mapSize) {
209  if (this.mapSize.equals(mapSize)) {
210  return;
211  }
212 
213  this.mapSize = mapSize;
214  setModified();
215  for (final MapArchObjectListener listener : listenerList.getListeners()) {
216  listener.mapSizeChanged(mapSize);
217  }
218  }
219 
220  @NotNull
221  @Override
222  public String getMapName() {
223  return mapName;
224  }
225 
226  @Override
227  public void setMapName(@NotNull final String name) {
228  final String trimmedName = name.trim();
229  if (mapName.equals(trimmedName)) {
230  return;
231  }
232 
233  mapName = trimmedName;
234  setModified();
235  }
236 
237  @Override
238  public int getEnterX() {
239  return enterX;
240  }
241 
242  @Override
243  public void setEnterX(final int enterX) {
244  if (this.enterX == enterX) {
245  return;
246  }
247 
248  this.enterX = enterX;
249  setModified();
250  }
251 
252  @Override
253  public int getEnterY() {
254  return enterY;
255  }
256 
257  @Override
258  public void setEnterY(final int enterY) {
259  if (this.enterY == enterY) {
260  return;
261  }
262 
263  this.enterY = enterY;
264  setModified();
265  }
266 
267  @NotNull
268  @Override
269  public Point getEnter() {
270  return new Point(enterX, enterY);
271  }
272 
273  @Override
274  public boolean isOutdoor() {
275  return outdoor;
276  }
277 
278  @Override
279  public void setOutdoor(final boolean outdoor) {
280  if (this.outdoor == outdoor) {
281  return;
282  }
283 
284  this.outdoor = outdoor;
285  setModified();
286  }
287 
288  @Override
289  public int getResetTimeout() {
290  return resetTimeout;
291  }
292 
293  @Override
294  public void setResetTimeout(final int resetTimeout) {
295  if (this.resetTimeout == resetTimeout) {
296  return;
297  }
298 
299  this.resetTimeout = resetTimeout;
300  setModified();
301  }
302 
303  @Override
304  public int getSwapTime() {
305  return swapTime;
306  }
307 
308  @Override
309  public void setSwapTime(final int swapTime) {
310  if (this.swapTime == swapTime) {
311  return;
312  }
313 
314  this.swapTime = swapTime;
315  setModified();
316  }
317 
318  @Override
319  public int getDifficulty() {
320  return difficulty;
321  }
322 
323  @Override
324  public void setDifficulty(final int difficulty) {
325  if (this.difficulty == difficulty) {
326  return;
327  }
328 
329  this.difficulty = difficulty;
330  setModified();
331  }
332 
333  @Override
334  public boolean isFixedReset() {
335  return fixedReset;
336  }
337 
338  @Override
339  public void setFixedReset(final boolean fixedReset) {
340  if (this.fixedReset == fixedReset) {
341  return;
342  }
343 
344  this.fixedReset = fixedReset;
345  setModified();
346  }
347 
348  @Override
349  public int getDarkness() {
350  return darkness;
351  }
352 
353  @Override
354  public void setDarkness(final int darkness) {
355  if (this.darkness == darkness) {
356  return;
357  }
358 
359  this.darkness = darkness;
360  setModified();
361  }
362 
363  @Override
364  public int getTilePaths() {
365  return tilePaths.length;
366  }
367 
368  @NotNull
369  @Override
370  public String getTilePath(@NotNull final Direction direction) {
371  return tilePaths[direction.ordinal()];
372  }
373 
374  @Override
375  public void setTilePath(@NotNull final Direction direction, @NotNull final String tilePath) {
376  if (tilePaths[direction.ordinal()].equals(tilePath)) {
377  return;
378  }
379 
380  tilePaths[direction.ordinal()] = tilePath;
381  setModified();
382  }
383 
384  @NotNull
385  @Override
386  public String calculateTilePath(@NotNull final Direction direction, @Nullable final MapFile mapFile) {
387  if (mapFile == null) {
388  return "";
389  }
390 
391  final MapPath mapPath = mapFile.getMapPath().getRelativeMapPathTo(mapFile.getMapPath());
392  final Matcher m = MAP_FILE_NAME_PATTERN.matcher(mapPath.toString());
393  if (!m.matches()) {
394  return "";
395  }
396 
397  final int x = Integer.parseInt(m.group(2));
398  final int y = Integer.parseInt(m.group(3));
399  final int z = m.group(4) == null ? 0 : Integer.parseInt(m.group(4));
400 
401  final StringBuilder sb = new StringBuilder(m.group(1));
402  sb.append(String.format("_%d_%d", x + direction.getDx(), y + direction.getDy()));
403  if (z + direction.getDz() != 0) {
404  sb.append(String.format("_%d", z + direction.getDz()));
405  }
406  return sb.toString();
407  }
408 
409  @Override
410  public void calculateTilePaths(@Nullable final MapFile mapFile, @NotNull final File mapsDirectory) {
411  if (mapFile == null) {
412  return;
413  }
414 
415  for (final Direction direction : Direction.values()) {
416  if (!tilePaths[direction.ordinal()].isEmpty()) {
417  continue;
418  }
419 
420  final String path = calculateTilePath(direction, mapFile);
421  if (path.isEmpty()) {
422  continue;
423  }
424 
425  final AbsoluteMapPath mapPath = new AbsoluteMapPath(mapFile.getMapPath(), "..");
426  final String realPath = mapsDirectory + "/" + mapPath + "/" + path;
427  final File file = new File(realPath);
428 
429  if (file.exists() && file.isFile()) {
430  setTilePath(direction, path);
431  }
432  }
433  }
434 
435  @Override
436  public boolean isTilePathAuto(@NotNull final Direction direction, @Nullable final MapFile mapFile) {
437  return calculateTilePath(direction, mapFile).equals(getTilePath(direction));
438  }
439 
440  @Override
441  public void setState(@NotNull final A mapArchObject) {
442  final AbstractMapArchObject<?> abstractMapArchObject = (AbstractMapArchObject<?>) mapArchObject;
443  setText(abstractMapArchObject.msgText.toString());
444  setMapSize(abstractMapArchObject.mapSize);
445  setMapName(abstractMapArchObject.mapName);
446  setEnterX(abstractMapArchObject.enterX);
447  setEnterY(abstractMapArchObject.enterY);
448  setOutdoor(abstractMapArchObject.outdoor);
449  setResetTimeout(abstractMapArchObject.resetTimeout);
450  setSwapTime(abstractMapArchObject.swapTime);
451  setDifficulty(abstractMapArchObject.difficulty);
452  setFixedReset(abstractMapArchObject.fixedReset);
453  setDarkness(abstractMapArchObject.darkness);
454  for (final Direction direction : Direction.values()) {
455  setTilePath(direction, abstractMapArchObject.tilePaths[direction.ordinal()]);
456  }
457  }
458 
459  @Override
460  public boolean equals(@Nullable final Object obj) {
461  if (obj == this) {
462  return true;
463  }
464  if (obj == null || obj.getClass() != getClass()) {
465  return false;
466  }
467  final AbstractMapArchObject<?> mapArchObject = (AbstractMapArchObject<?>) obj;
468  return mapArchObject.msgText.toString().equals(msgText.toString()) && mapArchObject.mapSize.equals(mapSize) && mapArchObject.mapName.equals(mapName) && mapArchObject.enterX == enterX && mapArchObject.enterY == enterY && mapArchObject.outdoor == outdoor && mapArchObject.resetTimeout == resetTimeout && mapArchObject.swapTime == swapTime && mapArchObject.difficulty == difficulty && mapArchObject.fixedReset == fixedReset && mapArchObject.darkness == darkness && Arrays.equals(mapArchObject.tilePaths, tilePaths);
469  }
470 
471  @Override
472  public int hashCode() {
473  return msgText.hashCode() + mapSize.hashCode() + mapName.hashCode() + enterX + enterY + (outdoor ? 2 : 0) + resetTimeout + swapTime + difficulty + (fixedReset ? 1 : 0) + darkness + Arrays.hashCode(tilePaths);
474  }
475 
476  @Override
477  public void addMapArchObjectListener(@NotNull final MapArchObjectListener listener) {
478  listenerList.add(listener);
479  }
480 
481  @Override
482  public void removeMapArchObjectListener(@NotNull final MapArchObjectListener listener) {
483  listenerList.remove(listener);
484  }
485 
490  protected void setModified() {
491  attributeHasChanged = true;
492  }
493 
494  @Override
495  public void beginTransaction() {
496  if (transactionDepth == 0) {
497  attributeHasChanged = false;
498  transactionThread = Thread.currentThread();
499  } else {
500  // == is okay for threads.
501  //noinspection ObjectEquality
502  if (transactionThread != Thread.currentThread()) {
503  throw new IllegalStateException("A transaction must only be used by one thread.");
504  }
505  }
506  transactionDepth++;
507  }
508 
509  @Override
510  public void endTransaction() {
511  endTransaction(false);
512  }
513 
514  @Override
515  public void endTransaction(final boolean fireEvent) {
516  if (transactionDepth <= 0) {
517  throw new IllegalStateException("Tried to end a transaction but no transaction was open.");
518  }
519  transactionDepth--;
520  assert transactionDepth >= 0;
521  if (transactionDepth == 0) {
523  } else if (fireEvent && transactionDepth > 0) {
524  if (attributeHasChanged) {
526  }
527  }
528  }
529 
534  private void commitTransaction() {
535  transactionDepth = 0;
536  transactionThread = null;
537  if (attributeHasChanged) {
539  attributeHasChanged = false;
540  }
541  }
542 
546  private void fireMetaChangedEvent() {
547  for (final MapArchObjectListener listener : listenerList.getListeners()) {
548  listener.mapMetaChanged();
549  }
550  }
551 
552  @Override
553  public void addText(@NotNull final String text) {
554  if (text.isEmpty()) {
555  return;
556  }
557 
558  msgText.append(text);
559  setModified();
560  }
561 
562  @Override
563  public void setText(final String text) {
564  if (msgText.toString().equals(text)) {
565  return;
566  }
567 
568  msgText.delete(0, msgText.length());
569  msgText.append(text);
570  setModified();
571  }
572 
573  @NotNull
574  @Override
575  public String getText() {
576  return msgText.toString();
577  }
578 
579  @Override
580  public void updateModifiedAttribute(@NotNull final String userName) {
581  final String modifiedAttribute = MODIFIED_ATTRIBUTE + " " + String.format("%tF", System.currentTimeMillis()) + " " + userName;
582 
583  final Matcher matcher = MODIFIED_ATTRIBUTE_PATTERN.matcher(msgText);
584  if (!matcher.find()) {
585  // attribute does not exist => add a new one
586  msgText.append("\n");
587  msgText.append(modifiedAttribute);
588  setModified();
589  } else if (!matcher.group().equals(modifiedAttribute)) {
590  // attribute exists => replace it
591  msgText.replace(matcher.start(), matcher.end(), modifiedAttribute);
592  setModified();
593  }
594  }
595 
596  @Override
597  public boolean isPointValid(@Nullable final Point pos) {
598  return pos != null && pos.x >= 0 && pos.y >= 0 && pos.x < mapSize.getWidth() && pos.y < mapSize.getHeight();
599  }
600 
605  @NotNull
606  protected abstract A getThis();
607 
608 }
void setSwapTime(final int swapTime)
Sets the swap time (in ticks).
void removeMapArchObjectListener(@NotNull final MapArchObjectListener listener)
Unregisters an event listener.
Interface for listeners listening on map arch object changes.
boolean attributeHasChanged
Set if any attribute has changed inside the current transaction.
abstract A getThis()
Returns this map arch object cast to its real type.
void addMapArchObjectListener(@NotNull final MapArchObjectListener listener)
Registers an event listener.
int resetTimeout
The number of ticks that need to elapse before this map will be reset.
T [] getListeners()
Returns an array of all the listeners.
boolean isOutdoor()
Returns whether the map is an "outdoor" map.
void setResetTimeout(final int resetTimeout)
Sets the reset timeout (in seconds).
boolean isTilePathAuto(@NotNull final Direction direction, @Nullable final MapFile mapFile)
Determines whether a tiled map in the specified direction is automatic tiled path or not...
int getDarkness()
Returns the light / darkness of this map.
Base implementation of MapArchObject that covers similarities between Crossfire maps and Daimonin map...
A MapPath that is absolute, that is, it starts with a "/".
void setMapName(@NotNull final String name)
Sets the map name.
static final Size2D ONE
One size object.
Definition: Size2D.java:40
Represents a maps directory local map path.
Definition: MapPath.java:31
void addText(@NotNull final String text)
Appends &#39;text&#39; to the map text.
static final Pattern MODIFIED_ATTRIBUTE_PATTERN
The pattern to find the MODIFIED_ATTRIBUTE in the message text.
boolean fixedReset
If nonzero, the map reset time will not be updated when someone enters / exits the map...
boolean isFixedReset()
Returns whether this map uses a fixed reset.
boolean isPointValid(@Nullable final Point pos)
Checks whether the given coordinate is within map bounds.
Base package of all Gridarta classes.
AbstractMapArchObject(@NotNull final AbstractMapArchObject< A > mapArchObject)
Creates a new instance as a copy of another map arch object.
void setEnterY(final int enterY)
Sets the enter y coordinate.
Size2D mapSize
The size of the map reflected by this map arch object.
static final Pattern MAP_FILE_NAME_PATTERN
The pattern used to determine the base map name and its x/y/z coordinates from a filename.
void remove(@NotNull final T listener)
Removes a listener.
void setModified()
Marks this map arch object as changed.
void add(@NotNull final T listener)
Adds a listener.
static final String MAP_NAME_UNNAMED
The name of an unnamed map.
int getWidth()
Returns the width of the area.
Definition: Size2D.java:96
static final long serialVersionUID
The serial version UID.
void setOutdoor(final boolean outdoor)
Sets whether the map is an "outdoor" map.
final EventListenerList2< MapArchObjectListener > listenerList
The registered event listeners.
void setDarkness(final int darkness)
Sets the light / darkness of this map.
String calculateTilePath(@NotNull final Direction direction, @Nullable final MapFile mapFile)
Calculate a single tile path in a particular direction.
void setMapSize(@NotNull final Size2D mapSize)
Sets the map size.
void setDifficulty(final int difficulty)
Sets the map&#39;s difficulty.
int swapTime
The number of ticks that must elapse after tha map has not been used before it gets swapped out...
void setEnterX(final int enterX)
Sets the enter x coordinate.
void setFixedReset(final boolean fixedReset)
Sets whether this map uses a fixed reset.
void calculateTilePaths(@Nullable final MapFile mapFile, @NotNull final File mapsDirectory)
Calculates tiled paths for the map automatically.
Type-safe version of EventListenerList.
void setState(@NotNull final A mapArchObject)
Resets the state of this object to the state of the given map arch object.
int getResetTimeout()
Returns the reset timeout (in seconds).
static final String MODIFIED_ATTRIBUTE
The prefix for the map attribute that is updated with the last modification timestamp.
final String [] tilePaths
The map tile paths used for map tiling.
void endTransaction(final boolean fireEvent)
Ends a transaction.
int getHeight()
Returns the height of the area.
Definition: Size2D.java:104
void setTilePath(@NotNull final Direction direction, @NotNull final String tilePath)
Sets a tile path.
void updateModifiedAttribute(@NotNull final String userName)
Updates the "Modified:" attribute in the message text.
The location of a map file with a map directory.
Definition: MapFile.java:31
The class Size2D represents a 2d rectangular area.
Definition: Size2D.java:30
String getTilePath(@NotNull final Direction direction)
Returns a tile path.
transient Thread transactionThread
The thread that performs the current transaction.