Gridarta Editor
GameObjectContainer.java
Go to the documentation of this file.
1 /*
2  * Gridarta MMORPG map editor for Crossfire, Daimonin and similar games.
3  * Copyright (C) 2000-2023 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.baseobject;
21 
22 import java.io.IOException;
23 import java.io.ObjectInputStream;
24 import java.io.Serializable;
25 import java.util.ArrayList;
26 import java.util.Iterator;
27 import java.util.List;
28 import java.util.ListIterator;
34 import org.jetbrains.annotations.NotNull;
35 import org.jetbrains.annotations.Nullable;
36 
49 @SuppressWarnings("ClassReferencesSubclass")
50 public abstract class GameObjectContainer<G extends GameObject<G, A, R>, A extends MapArchObject<A>, R extends Archetype<G, A, R>> implements Cloneable, Iterable<G>, Serializable {
51 
55  private static final long serialVersionUID = 1L;
56 
62  @NotNull
63  private List<G> contents;
64 
68  @NotNull
69  private transient Iterable<G> recursive;
70 
74  @NotNull
75  private transient Iterable<G> reverse;
76 
80  protected GameObjectContainer() {
81  initData();
82  }
83 
92  private void initData() {
93  contents = new ArrayList<>(0);
94  recursive = () -> new RecursiveGameObjectIterator<>(GameObjectContainer.this);
95  reverse = () -> new ReverseIterator<>(contents);
96  }
97 
104  @NotNull
105  @Override
106  public Iterator<G> iterator() {
107  // Do not return contents.iterator() directly because otherwise the remove() operation will not properly unlink the removed GameObject from its container.
108  return new Iterator<G>() {
109 
113  @NotNull
114  private final Iterator<G> delegate = contents.iterator();
115 
120  @Nullable
121  private G current;
122 
123  @Override
124  public boolean hasNext() {
125  return delegate.hasNext();
126  }
127 
128  @Override
129  public G next() {
130  current = delegate.next();
131  assert current != null;
132  return current;
133  }
134 
135  @Override
136  public void remove() {
137  final G tmp = current;
138  if (tmp == null) {
139  throw new IllegalStateException("no game object available to remove");
140  }
141  // keep this in sync with GameObjectContainer#remove(G)
142  // we can't simply invoke GameObjectContainer#remove(current) because that would result in a ConcurrentModificationException.
143  notifyBeginChange();
144  try {
145  if (contents.size() >= 2 && contents.get(0) == tmp) {
146  contents.get(1).propagateElevation(tmp);
147  }
148  delegate.remove();
149  tmp.setContainer(null, 0, 0);
150  current = null;
151  } finally {
152  notifyEndChange();
153  }
154  }
155  };
156  }
157 
163  @NotNull
164  public Iterable<G> reverse() {
165  return reverse;
166  }
167 
173  @NotNull
174  public Iterable<G> recursive() {
175  return recursive;
176  }
177 
182  public boolean isEmpty() {
183  return contents.isEmpty();
184  }
185 
191  @Nullable
192  public G getFirst() {
193  return contents.isEmpty() ? null : contents.get(0);
194  }
195 
202  @Nullable
203  public G getPrev(@NotNull final G gameObject) {
204  final Iterator<G> it = contents.iterator();
205  while (it.hasNext()) {
206  if (it.next() == gameObject) {
207  return it.hasNext() ? it.next() : null;
208  }
209  }
210  return null;
211  }
212 
219  @Nullable
220  public G getNext(@NotNull final G gameObject) {
221  G prevGameObject = null;
222  for (final G tmpGameObject : contents) {
223  if (tmpGameObject == gameObject) {
224  return prevGameObject;
225  }
226  prevGameObject = tmpGameObject;
227  }
228  return null;
229  }
230 
238  @Nullable
239  public G getLast() {
240  return contents.isEmpty() ? null : contents.get(contents.size() - 1);
241  }
242 
250  public void remove(@NotNull final G gameObject) {
251  // keep this in sync with iterator()
252  notifyBeginChange();
253  try {
254  if (contents.size() >= 2 && contents.get(0) == gameObject) {
255  contents.get(1).propagateElevation(gameObject);
256  }
257  if (!contents.remove(gameObject)) {
258  throw new NotInsideContainerException(this, gameObject);
259  }
260  gameObject.setContainer(null, 0, 0);
261  } finally {
262  notifyEndChange();
263  }
264  }
265 
270  public void removeAll() {
271  if (contents.size() <= 0) {
272  return;
273  }
274 
275  notifyBeginChange();
276  try {
277  for (final GameObject<G, A, R> gameObject : contents) {
278  gameObject.setContainer(null, 0, 0);
279  }
280  contents.clear();
281  } finally {
282  notifyEndChange();
283  }
284  }
285 
293  public boolean isTop(@NotNull final G gameObject) {
294  return !contents.isEmpty() && contents.get(contents.size() - 1) == gameObject;
295  }
296 
304  public boolean isBottom(@NotNull final G gameObject) {
305  return !contents.isEmpty() && contents.get(0) == gameObject;
306  }
307 
314  public void moveTop(@NotNull final G gameObject) {
315  final int oldIndex = contents.indexOf(gameObject);
316  if (oldIndex != contents.size() - 1) {
317  notifyBeginChange();
318  try {
319  if (!contents.remove(gameObject)) {
320  throw new NotInsideContainerException(this, gameObject);
321  }
322  if (oldIndex == 0) {
323  assert contents.size() >= 1;
324  contents.get(0).propagateElevation(gameObject);
325  }
326  contents.add(gameObject);
327  } finally {
328  notifyEndChange();
329  }
330  }
331  }
332 
339  public void moveUp(@NotNull final G gameObject) {
340  final int oldIndex = contents.indexOf(gameObject);
341  if (oldIndex < contents.size() - 1) {
342  notifyBeginChange();
343  try {
344  if (!contents.remove(gameObject)) {
345  throw new NotInsideContainerException(this, gameObject);
346  }
347  if (oldIndex == 0) {
348  assert contents.size() >= 1;
349  contents.get(0).propagateElevation(gameObject);
350  }
351  contents.add(oldIndex + 1, gameObject);
352  } finally {
353  notifyEndChange();
354  }
355  }
356  }
357 
364  public void moveDown(@NotNull final G gameObject) {
365  final int oldIndex = contents.indexOf(gameObject);
366  if (oldIndex > 0) {
367  notifyBeginChange();
368  try {
369  if (!contents.remove(gameObject)) {
370  throw new NotInsideContainerException(this, gameObject);
371  }
372  if (oldIndex == 1) {
373  assert contents.size() >= 1;
374  gameObject.propagateElevation(contents.get(0));
375  }
376  contents.add(oldIndex - 1, gameObject);
377  } finally {
378  notifyEndChange();
379  }
380  }
381  }
382 
389  public void moveBottom(@NotNull final G gameObject) {
390  final int oldIndex = contents.indexOf(gameObject);
391  if (oldIndex != 0) {
392  notifyBeginChange();
393  try {
394  if (!contents.remove(gameObject)) {
395  throw new NotInsideContainerException(this, gameObject);
396  }
397  assert contents.size() >= 1;
398  gameObject.propagateElevation(contents.get(0));
399  contents.add(0, gameObject);
400  } finally {
401  notifyEndChange();
402  }
403  }
404  }
405 
413  public void addLast(@NotNull final G gameObject) {
414  if (gameObject.isInContainer()) {
415  throw new IllegalArgumentException("Can't add " + gameObject + " to " + this + " because it's already inside " + gameObject.getContainer());
416  }
417 
418  notifyBeginChange();
419  try {
420  contents.add(gameObject);
421  setThisContainer(gameObject);
422  gameObject.markModified();
423  } finally {
424  notifyEndChange();
425  }
426  }
427 
435  public void addFirst(@NotNull final G gameObject) {
436  if (gameObject.isInContainer()) {
437  throw new IllegalArgumentException("Can't add " + gameObject + " to " + this + " because it's already inside " + gameObject.getContainer());
438  }
439 
440  notifyBeginChange();
441  try {
442  if (!contents.isEmpty()) {
443  gameObject.propagateElevation(contents.get(0));
444  }
445  contents.add(0, gameObject);
446  setThisContainer(gameObject);
447  gameObject.markModified();
448  } finally {
449  notifyEndChange();
450  }
451  }
452 
461  public void insertAfter(@Nullable final G previousGameObject, @NotNull final G gameObject) {
462  if (gameObject.isInContainer()) {
463  throw new IllegalArgumentException("Can't add " + gameObject + " to " + this + " because it's already inside " + gameObject.getContainer());
464  }
465 
466  if (previousGameObject == null) {
467  addLast(gameObject);
468  return;
469  }
470 
471  if (!previousGameObject.isHead()) {
472  throw new IllegalArgumentException("game object must be the head part: " + previousGameObject);
473  }
474 
475  notifyBeginChange();
476  try {
477  boolean added = false;
478  int index = 0;
479  for (final G tmpGameObject : contents) {
480  if (tmpGameObject.getHead() == previousGameObject) {
481  if (index == 0) {
482  gameObject.propagateElevation(contents.get(0));
483  }
484  contents.add(index, gameObject);
485  added = true;
486  break;
487  }
488  index++;
489  }
490  if (!added) {
491  throw new IllegalArgumentException("Can't add " + gameObject + " to " + this + " because " + previousGameObject + " is not inside");
492  }
493  setThisContainer(gameObject);
494  gameObject.markModified();
495  } finally {
496  notifyEndChange();
497  }
498  }
499 
507  public void insertBefore(@NotNull final G gameObject, @Nullable final G nextGameObject) {
508  if (gameObject.isInContainer()) {
509  throw new IllegalArgumentException("Can't add " + gameObject + " to " + this + " because it's already inside " + gameObject.getContainer());
510  }
511 
512  if (nextGameObject == null) {
513  addFirst(gameObject);
514  return;
515  }
516 
517  if (!nextGameObject.isHead()) {
518  throw new IllegalArgumentException("game object must be the head part: " + nextGameObject);
519  }
520 
521  notifyBeginChange();
522  try {
523  final int insertIndex = contents.indexOf(nextGameObject);
524  if (insertIndex == -1) {
525  throw new IllegalArgumentException("Can't insert " + gameObject + " before " + nextGameObject + " because that isn't inside " + this);
526  }
527  contents.add(insertIndex + 1, gameObject);
528  setThisContainer(gameObject);
529  gameObject.markModified();
530  } finally {
531  notifyEndChange();
532  }
533  }
534 
542  public void replace(@NotNull final G oldGameObject, @NotNull final G newGameObject) {
543  if (oldGameObject.isMulti()) {
544  throw new IllegalArgumentException("cannot replace multi-part game object: " + oldGameObject);
545  }
546 
547  notifyBeginChange();
548  try {
549  final int insertIndex = contents.indexOf(oldGameObject);
550  if (insertIndex == -1) {
551  throw new NotInsideContainerException(this, oldGameObject);
552  }
553  if (insertIndex == 0) {
554  newGameObject.propagateElevation(oldGameObject);
555  }
556  contents.remove(oldGameObject);
557  oldGameObject.setContainer(null, 0, 0);
558  contents.add(insertIndex, newGameObject);
559  setThisContainer(newGameObject);
560  newGameObject.markModified();
561  } finally {
562  notifyEndChange();
563  }
564  }
565 
571  @NotNull
572  public abstract MapSquare<G, A, R> getMapSquare();
573 
579  @Nullable
580  public abstract MapSquare<G, A, R> getMapSquareOptional();
581 
585  protected abstract void notifyBeginChange();
586 
590  protected abstract void notifyEndChange();
591 
592  @NotNull
593  @Override
594  @SuppressWarnings("unchecked")
595  protected Object clone() {
596  try {
598  clone.initData();
599  for (final BaseObject<G, A, R, G> gameObject : contents) {
600  final G clonedGameObject = gameObject.clone();
601  clone.contents.add(clonedGameObject);
602  clone.setThisContainer(clonedGameObject);
603  }
604  return clone;
605  } catch (final CloneNotSupportedException e) {
606  assert false : "This class must be cloneable" + e;
607  throw new AssertionError(e);
608  }
609  }
610 
611  // writeObject() is not required because this class doesn't require special handling during serialization.
612 
613  private void readObject(@NotNull final ObjectInputStream stream) throws IOException, ClassNotFoundException {
614  stream.defaultReadObject();
615  // initialize transients
616  initData();
617  }
618 
624  protected boolean hasSameContents(@NotNull final GameObjectContainer<?, ?, ?> gameObjectContainer) {
625  if (gameObjectContainer.contents.size() != contents.size()) {
626  return false;
627  }
628 
629  for (int i = 0; i < contents.size(); i++) {
630  if (!gameObjectContainer.contents.get(i).isEqual(contents.get(i))) {
631  return false;
632  }
633  }
634 
635  return true;
636  }
637 
642  protected abstract void setThisContainer(@NotNull G gameObject);
643 
649  @Nullable
650  public abstract G asGameObject();
651 
652  @NotNull
653  @Override
654  public String toString() {
655  final StringBuilder sb = new StringBuilder();
656  sb.append("(");
657  final Iterator<G> it = contents.iterator();
658  if (it.hasNext()) {
659  sb.append(it.next());
660  while (it.hasNext()) {
661  sb.append(",");
662  sb.append(it.next());
663  }
664  }
665  sb.append(")");
666  return sb.toString();
667  }
668 
673  private static class ReverseIterator<T extends GameObject<?, ?, ?>> implements Iterator<T> {
674 
678  @NotNull
679  private final ListIterator<T> delegate;
680 
684  @NotNull
685  private final List<T> list;
686 
691  private ReverseIterator(@NotNull final List<T> list) {
692  this.list = list;
693  delegate = list.listIterator(list.size());
694  }
695 
696  @Override
697  public boolean hasNext() {
698  return delegate.hasPrevious();
699  }
700 
701  // previous can throw NoSuchElementException but InspectionGadgets doesn't know about that.
702  @NotNull
703  @Override
704  public T next() {
705  return delegate.previous();
706  }
707 
708  @Override
709  public void remove() {
710  if (delegate.nextIndex() == 0 && list.size() >= 2) {
711  list.get(1).propagateElevation(list.get(0));
712  }
713  delegate.remove();
714  }
715 
716  }
717 
718 }
net.sf.gridarta.model.baseobject.GameObjectContainer.contents
List< G > contents
The contents of this container.
Definition: GameObjectContainer.java:63
net.sf.gridarta.model.baseobject.GameObjectContainer.clone
Object clone()
Definition: GameObjectContainer.java:595
net.sf.gridarta.model.baseobject.GameObjectContainer.isBottom
boolean isBottom(@NotNull final G gameObject)
Returns whether this game object is the bottom-most one.
Definition: GameObjectContainer.java:304
net.sf.gridarta.model.baseobject.RecursiveGameObjectIterator
Iterator for recursively iterating over GameObjectContainers.
Definition: RecursiveGameObjectIterator.java:33
net.sf.gridarta.model.baseobject.GameObjectContainer.getNext
G getNext(@NotNull final G gameObject)
Returns the GameObject succeeding a given game object.
Definition: GameObjectContainer.java:220
net.sf.gridarta.model.baseobject.GameObjectContainer.ReverseIterator.hasNext
boolean hasNext()
Definition: GameObjectContainer.java:697
net.sf.gridarta
Base package of all Gridarta classes.
net.sf.gridarta.model.mapmodel.MapSquare
A single Map Square.
Definition: MapSquare.java:45
net.sf.gridarta.model.baseobject.GameObjectContainer.removeAll
void removeAll()
Removes all game objects from this container.
Definition: GameObjectContainer.java:270
net.sf.gridarta.model.baseobject.GameObjectContainer.hasSameContents
boolean hasSameContents(@NotNull final GameObjectContainer<?, ?, ?> gameObjectContainer)
Compares this object to another game object container.
Definition: GameObjectContainer.java:624
net.sf
net.sf.gridarta.model.mapmodel
Definition: AboveFloorInsertionMode.java:20
net.sf.gridarta.model.baseobject.GameObjectContainer.readObject
void readObject(@NotNull final ObjectInputStream stream)
Definition: GameObjectContainer.java:613
net.sf.gridarta.model.baseobject.GameObjectContainer.ReverseIterator.list
final List< T > list
The list being iterated over.
Definition: GameObjectContainer.java:685
net.sf.gridarta.model.archetype
Definition: AbstractArchetype.java:20
net.sf.gridarta.model.gameobject.GameObject
Reflects a game object (object on a map).
Definition: GameObject.java:36
net.sf.gridarta.model.baseobject.GameObjectContainer.getFirst
G getFirst()
Returns the first GameObject contained in this container.
Definition: GameObjectContainer.java:192
net.sf.gridarta.model.baseobject.GameObjectContainer.moveBottom
void moveBottom(@NotNull final G gameObject)
Moves a game object to bottom.
Definition: GameObjectContainer.java:389
net.sf.gridarta.model.baseobject.GameObjectContainer.setThisContainer
abstract void setThisContainer(@NotNull G gameObject)
Sets a GameObject's container to this container.
net.sf.gridarta.model.baseobject.GameObjectContainer.getPrev
G getPrev(@NotNull final G gameObject)
Returns the GameObject preceding a given game object.
Definition: GameObjectContainer.java:203
net.sf.gridarta.model.baseobject.GameObjectContainer.ReverseIterator.ReverseIterator
ReverseIterator(@NotNull final List< T > list)
Creates a new instance.
Definition: GameObjectContainer.java:691
net.sf.gridarta.model.gameobject
GameObjects are the objects based on Archetypes found on maps.
Definition: AbstractGameObject.java:20
net
net.sf.gridarta.model.baseobject.GameObjectContainer.ReverseIterator.delegate
final ListIterator< T > delegate
The iterator used for delegation.
Definition: GameObjectContainer.java:679
net.sf.gridarta.model.baseobject.GameObjectContainer.insertBefore
void insertBefore(@NotNull final G gameObject, @Nullable final G nextGameObject)
Adds a game object before another.
Definition: GameObjectContainer.java:507
net.sf.gridarta.model.baseobject.GameObjectContainer.addLast
void addLast(@NotNull final G gameObject)
Adds the given game object at the end of this container.
Definition: GameObjectContainer.java:413
net.sf.gridarta.model.baseobject.GameObjectContainer.iterator
Iterator< G > iterator()
The Iterator returned does not recurse, it only contains objects on the first level.
Definition: GameObjectContainer.java:106
net.sf.gridarta.model.baseobject.GameObjectContainer.reverse
transient Iterable< G > reverse
Iterable implementation for reverse traversal.
Definition: GameObjectContainer.java:75
net.sf.gridarta.model.baseobject.GameObjectContainer.recursive
transient Iterable< G > recursive
Iterable implementation for recursive traversal.
Definition: GameObjectContainer.java:69
list
This document describes some hints and requirements for general development on the CrossfireEditor If you plan to make changes to the editor code or setup please read the following and keep it in derived from a basic editor application called Gridder by Pasi Ker�nen so please communicate with best through the cf devel mailing list
Definition: Developer_README.txt:13
net.sf.gridarta.model.maparchobject.MapArchObject
Interface for MapArchObjects.
Definition: MapArchObject.java:40
net.sf.gridarta.model.baseobject.GameObjectContainer.GameObjectContainer
GameObjectContainer()
Creates a new instance.
Definition: GameObjectContainer.java:80
net.sf.gridarta.model.baseobject.BaseObject< G, A, R, G >
net.sf.gridarta.model.baseobject.GameObjectContainer.moveTop
void moveTop(@NotNull final G gameObject)
Moves a game object to top.
Definition: GameObjectContainer.java:314
net.sf.gridarta.model.baseobject.GameObjectContainer.insertAfter
void insertAfter(@Nullable final G previousGameObject, @NotNull final G gameObject)
Adds a game object after another.
Definition: GameObjectContainer.java:461
net.sf.gridarta.model.gameobject.NotInsideContainerException
This exception is thrown in case a method of a GameObject without a container or the wrong container ...
Definition: NotInsideContainerException.java:34
net.sf.gridarta.model.baseobject.GameObjectContainer.toString
String toString()
Definition: GameObjectContainer.java:654
net.sf.gridarta.model
net.sf.gridarta.model.archetype.Archetype
Reflects an Archetype.
Definition: Archetype.java:41
net.sf.gridarta.model.baseobject.GameObjectContainer.ReverseIterator
An iterator for iterating over a list in reverse order.
Definition: GameObjectContainer.java:673
net.sf.gridarta.model.baseobject.GameObjectContainer.recursive
Iterable< G > recursive()
Returns an object that is a recursive representation.
Definition: GameObjectContainer.java:174
net.sf.gridarta.model.baseobject.GameObjectContainer.isEmpty
boolean isEmpty()
Check whether this square is empty.
Definition: GameObjectContainer.java:182
net.sf.gridarta.model.baseobject.GameObjectContainer.addFirst
void addFirst(@NotNull final G gameObject)
Adds the given game object at the end of this container.
Definition: GameObjectContainer.java:435
net.sf.gridarta.model.baseobject.GameObjectContainer.reverse
Iterable< G > reverse()
Returns an object that is the reverse representation.
Definition: GameObjectContainer.java:164
net.sf.gridarta.model.maparchobject
Definition: AbstractMapArchObject.java:20
net.sf.gridarta.model.baseobject.GameObjectContainer.moveUp
void moveUp(@NotNull final G gameObject)
Moves a game object up.
Definition: GameObjectContainer.java:339
net.sf.gridarta.model.baseobject.GameObjectContainer
Base class for classes that contain GameObjects as children in the sense of containment.
Definition: GameObjectContainer.java:50
net.sf.gridarta.model.baseobject.GameObjectContainer.moveDown
void moveDown(@NotNull final G gameObject)
Moves a game object down.
Definition: GameObjectContainer.java:364
net.sf.gridarta.model.baseobject.GameObjectContainer.replace
void replace(@NotNull final G oldGameObject, @NotNull final G newGameObject)
Replaces a game object with another one.
Definition: GameObjectContainer.java:542
net.sf.gridarta.model.baseobject.GameObjectContainer.initData
void initData()
Initializes the fields.
Definition: GameObjectContainer.java:92
net.sf.gridarta.model.baseobject.GameObjectContainer.isTop
boolean isTop(@NotNull final G gameObject)
Returns whether this game object is the top-most one.
Definition: GameObjectContainer.java:293
net.sf.gridarta.model.baseobject.GameObjectContainer.ReverseIterator.next
T next()
Definition: GameObjectContainer.java:704
net.sf.gridarta.model.baseobject.GameObjectContainer.getLast
G getLast()
Returns the last GameObject contained in this container.
Definition: GameObjectContainer.java:239
it
This document describes some hints and requirements for general development on the CrossfireEditor If you plan to make changes to the editor code or setup please read the following and keep it in derived from a basic editor application called Gridder by Pasi Ker�nen so please communicate with best through the cf devel mailing before considering any fundamental changes About code DO NOT USE TABS No matter what Java development platform you are please configure insert indent Tabs are displayed totally different in every editor and there are millions of different editors out there The insertion of tabs in the source code is messing up the syntax formatting in a way that is UNREPAIRABLE Apart from please keep code indentation accurate This is not just good it helps to keep code readable and in that way dramatically decreases the chance for overlooked bugs Everyone is welcomed to correct indentation errors wherever they are spotted Before you start to do this please double check that your editor is really configured to insert spaces Line feeds may be checked in either in windows or in unix linux style All reasonable text and java editors can deal with both linefeed formats Converting line feeds is but in this case please make sure that only linefeed characters are changed and nothing else is affected Due to the platform independent nature of the editor has the potential to run on almost any given operating system the build process differs greatly between systems as well as java environments In the several people have attempted to add build scripts along with structural changes to optimize the setup on one particular system environment which has led to conflict Please do *not *attempt to change the structure or any directories for the mere purpose of improving a build process or performance in a java environment Build scripts may be placed in the root it would be especially fine if it is just one or two files but the latter is not required Please excuse me for placing such restriction I and many users of the editor greatly appreciate build scripts We just had some real troubles over this issue in the past and I don t want to have them repeated the editor has relatively high performance requirements I ve spent a lot of extra work to keep everything as fast and memory efficient as possible when you add new data fields or calculations in the archetype please make sure they are as efficient as possible and worth both the time and space they consume Now don t be afraid too much No development would be possible without adding calculations and data at all Just bear in mind unlike for many other open source performance does make a difference for the CrossfireEditor The for as many systems as possible In case you are unexperienced with java and note that the graphics look different on every and with every font They also have different sizes proportions and behave different A seemingly trivial and effectless change can wreck havoc for the same GUI run on another system please don t be totally afraid of it
Definition: Developer_README.txt:76