diff --git a/pom.xml b/pom.xml
index fdbb19e..681ca87 100644
--- a/pom.xml
+++ b/pom.xml
@@ -89,6 +89,26 @@
maven-surefire-plugin3.0.0-M9
+
+ org.jacoco
+ jacoco-maven-plugin
+ 0.8.12
+
+
+ prepare-agent
+
+ prepare-agent
+
+
+
+ report
+ test
+
+ report
+
+
+
+
@@ -110,6 +130,11 @@
jdom22.0.6.1
+
+ com.fasterxml.jackson.dataformat
+ jackson-dataformat-xml
+ 2.16.1
+ org.graalvm.polyglotpolyglot
@@ -132,7 +157,12 @@
mbassador1.3.2
-
+
+
+ com.github.weisj
+ jsvg
+ 1.7.2
+ net.phys2dphys2d
diff --git a/src/main/java/neon/core/DefaultGameContext.java b/src/main/java/neon/core/DefaultGameContext.java
index aac7f34..c9b9325 100644
--- a/src/main/java/neon/core/DefaultGameContext.java
+++ b/src/main/java/neon/core/DefaultGameContext.java
@@ -20,11 +20,13 @@
import java.util.EventObject;
import lombok.Setter;
+import neon.core.event.TaskQueue;
import neon.entities.Player;
import neon.entities.UIDStore;
import neon.maps.Atlas;
import neon.narrative.QuestTracker;
import neon.resources.ResourceManager;
+import neon.systems.files.FileSystem;
import neon.systems.physics.PhysicsSystem;
import neon.systems.timing.Timer;
import net.engio.mbassy.bus.MBassador;
@@ -48,6 +50,9 @@ public class DefaultGameContext implements GameContext {
@Setter private PhysicsSystem physicsEngine;
@Setter private Context scriptEngine;
@Setter private MBassador bus;
+ @Setter private FileSystem fileSystem;
+ @Setter private TaskQueue queue;
+ @Setter private Engine engine;
// Game-level state (set when a game starts)
@Setter private Game game;
@@ -111,4 +116,26 @@ public void quit() {
public void post(EventObject event) {
bus.publishAsync(event);
}
+
+ @Override
+ public FileSystem getFileSystem() {
+ return fileSystem;
+ }
+
+ @Override
+ public TaskQueue getQueue() {
+ return queue;
+ }
+
+ @Override
+ public void startGame(Game game) {
+ // Delegate to Engine's startGame implementation
+ // Engine is responsible for registering handlers and setting up script bindings
+ if (engine != null) {
+ engine.startGame(game);
+ } else {
+ // Fallback for tests that don't have an Engine instance
+ setGame(game);
+ }
+ }
}
diff --git a/src/main/java/neon/core/Engine.java b/src/main/java/neon/core/Engine.java
index bb8dd9f..fce630e 100644
--- a/src/main/java/neon/core/Engine.java
+++ b/src/main/java/neon/core/Engine.java
@@ -115,6 +115,9 @@ public Engine(Port port) throws IOException {
context.setPhysicsEngine(physics);
context.setScriptEngine(engine);
context.setBus(bus);
+ context.setFileSystem(files);
+ context.setQueue(queue);
+ context.setEngine(this);
}
/** This method is the run method of the gamethread. It sets up the event system. */
@@ -126,7 +129,7 @@ public void run() {
bus.subscribe(new InventoryHandler());
bus.subscribe(adapter);
bus.subscribe(quests);
- bus.subscribe(new GameLoader(this, config));
+ bus.subscribe(new GameLoader(context, config));
bus.subscribe(new GameSaver(queue));
}
diff --git a/src/main/java/neon/core/GameContext.java b/src/main/java/neon/core/GameContext.java
index 1c3a1b1..b3976e1 100644
--- a/src/main/java/neon/core/GameContext.java
+++ b/src/main/java/neon/core/GameContext.java
@@ -19,11 +19,13 @@
package neon.core;
import java.util.EventObject;
+import neon.core.event.TaskQueue;
import neon.entities.Player;
import neon.entities.UIDStore;
import neon.maps.Atlas;
import neon.narrative.QuestTracker;
import neon.resources.ResourceManager;
+import neon.systems.files.FileSystem;
import neon.systems.physics.PhysicsSystem;
import neon.systems.timing.Timer;
import org.graalvm.polyglot.Context;
@@ -76,6 +78,20 @@ public interface GameContext {
*/
ResourceManager getResources();
+ /**
+ * Returns the file system.
+ *
+ * @return the file system for accessing game data files
+ */
+ FileSystem getFileSystem();
+
+ /**
+ * Returns the task queue.
+ *
+ * @return the task queue for deferred execution
+ */
+ TaskQueue getQueue();
+
/**
* Returns the quest tracker.
*
@@ -116,4 +132,11 @@ public interface GameContext {
* @param event the event to post
*/
void post(EventObject event);
+
+ /**
+ * Starts a new game with the provided game instance.
+ *
+ * @param game the game instance to start
+ */
+ void startGame(Game game);
}
diff --git a/src/main/java/neon/core/GameLoader.java b/src/main/java/neon/core/GameLoader.java
index 0f86b48..54d4321 100644
--- a/src/main/java/neon/core/GameLoader.java
+++ b/src/main/java/neon/core/GameLoader.java
@@ -1,344 +1,384 @@
-/*
- * Neon, a roguelike engine.
- * Copyright (C) 2013 - Maarten Driesen
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package neon.core;
-
-import java.awt.Rectangle;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.List;
-import lombok.extern.slf4j.Slf4j;
-import neon.core.event.LoadEvent;
-import neon.core.event.MagicTask;
-import neon.core.event.ScriptAction;
-import neon.core.event.TaskQueue;
-import neon.core.handlers.InventoryHandler;
-import neon.core.handlers.SkillHandler;
-import neon.entities.Entity;
-import neon.entities.EntityFactory;
-import neon.entities.Item;
-import neon.entities.Player;
-import neon.entities.UIDStore;
-import neon.entities.components.Stats;
-import neon.entities.property.Ability;
-import neon.entities.property.Feat;
-import neon.entities.property.Gender;
-import neon.entities.property.Skill;
-import neon.magic.Effect;
-import neon.magic.Spell;
-import neon.magic.SpellFactory;
-import neon.maps.Map;
-import neon.resources.CGame;
-import neon.resources.RCreature;
-import neon.resources.RMod;
-import neon.resources.RSign;
-import neon.resources.RSpell.SpellType;
-import neon.systems.files.FileUtils;
-import neon.systems.files.XMLTranslator;
-import net.engio.mbassy.listener.Handler;
-import net.engio.mbassy.listener.Listener;
-import net.engio.mbassy.listener.References;
-import org.jdom2.*;
-import org.jdom2.input.SAXBuilder;
-
-@Listener(references = References.Strong)
-@Slf4j
-public class GameLoader {
- private Engine engine;
- private TaskQueue queue;
- private Configuration config;
-
- public GameLoader(Engine engine, Configuration config) {
- this.engine = engine;
- this.config = config;
- queue = engine.getQueue();
- }
-
- @Handler
- public void loadGame(LoadEvent le) {
- log.trace("loadGame from {}: {}", le.getSource(), le);
- // load game
- switch (le.getMode()) {
- case LOAD:
- loadGame(le.getSaveName());
- // indicate that loading is complete
- Engine.post(new LoadEvent(this));
- break;
- case NEW:
- try {
- initGame(le.race, le.name, le.gender, le.specialisation, le.profession, le.sign);
- } catch (RuntimeException re) {
- System.out.println(re);
- re.fillInStackTrace().printStackTrace();
- }
- // indicate that loading is complete
- Engine.post(new LoadEvent(this));
- break;
- default:
- break;
- }
- }
-
- /**
- * Creates a new game using the supplied data.
- *
- * @param race
- * @param name
- * @param gender
- * @param spec
- * @param profession
- * @param sign
- */
- public void initGame(
- String race,
- String name,
- Gender gender,
- Player.Specialisation spec,
- String profession,
- RSign sign) {
- try {
- log.debug("Engine.initGame() start");
-
- // initialize player
- RCreature species =
- new RCreature(((RCreature) Engine.getResources().getResource(race)).toElement());
- Player player = new Player(species, name, gender, spec, profession);
- player.species.text = "@";
- engine.startGame(new Game(player, Engine.getFileSystem()));
- setSign(player, sign);
- for (Skill skill : Skill.values()) {
- SkillHandler.checkFeat(skill, player);
- }
-
- // initialize maps
- initMaps();
-
- CGame game = (CGame) Engine.getResources().getResource("game", "config");
-
- // starting items
- for (String i : game.getStartingItems()) {
- Item item = EntityFactory.getItem(i, Engine.getStore().createNewEntityUID());
- Engine.getStore().addEntity(item);
- InventoryHandler.addItem(player, item.getUID());
- }
- // starting spells
- for (String i : game.getStartingSpells()) {
- player.getMagicComponent().addSpell(SpellFactory.getSpell(i));
- }
-
- // position player
- Rectangle bounds = player.getShapeComponent();
- bounds.setLocation(game.getStartPosition().x, game.getStartPosition().y);
- Map map = Engine.getAtlas().getMap(Engine.getStore().getMapUID(game.getStartMap()));
- Engine.getScriptEngine().getBindings("js").putMember("map", map);
- Engine.getAtlas().setMap(map);
- Engine.getAtlas().setCurrentZone(game.getStartZone());
- } catch (RuntimeException re) {
- log.error("Error during initGame", re);
- }
- log.debug("Engine.initGame() exit");
- }
-
- private void setSign(Player player, RSign sign) {
- player.setSign(sign.id);
- for (String power : sign.powers) {
- player.getMagicComponent().addSpell(SpellFactory.getSpell(power));
- }
- for (Ability ability : sign.abilities.keySet()) {
- player.getCharacteristicsComponent().addAbility(ability, sign.abilities.get(ability));
- }
- }
-
- /*
- * Loads a saved game.
- *
- * @param save the name of the saved game
- */
- private void loadGame(String save) {
- config.setProperty("save", save);
-
- Document doc = new Document();
- try {
- FileInputStream in = new FileInputStream("saves/" + save + "/save.xml");
- doc = new SAXBuilder().build(in);
- in.close();
- } catch (IOException e) {
- System.out.println("IOException in loadGame");
- } catch (JDOMException e) {
- System.out.println("JDOMException in loadGame");
- }
- Element root = doc.getRootElement();
-
- // copy save map to temp
- Path savePath = Paths.get("saves", save);
- Path tempPath = Paths.get("temp");
- FileUtils.copy(savePath, tempPath);
-
- // initialize maps
- initMaps();
-
- // set time correctly (using setTime(), otherwise listeners would be called)
- Engine.getTimer().setTime(Integer.parseInt(root.getChild("timer").getAttributeValue("ticks")));
-
- // create player
- loadPlayer(root.getChild("player"));
-
- // events
- loadEvents(root.getChild("events"));
-
- // quests
- Element journal = root.getChild("journal");
- Player player = Engine.getPlayer();
- if (player != null) {
- for (Element e : journal.getChildren()) {
- Engine.getPlayer().getJournal().addQuest(e.getAttributeValue("id"), e.getText());
- Engine.getPlayer()
- .getJournal()
- .updateQuest(e.getAttributeValue("id"), Integer.parseInt(e.getAttributeValue("stage")));
- }
- } else {
- System.out.println("Skipping journal update");
- }
- }
-
- private void loadEvents(Element events) {
- // normal tasks
- for (Element event : events.getChildren("task")) {
- String description = event.getAttributeValue("desc");
- if (event.getAttribute("script") != null) {
- String script = event.getAttributeValue("script");
- queue.add(description, new ScriptAction(script));
- }
- }
-
- // timed tasks
- for (Element event : events.getChildren("timer")) {
- String[] ticks = event.getAttributeValue("tick").split(":");
- int start = Integer.parseInt(ticks[0]);
- int period = Integer.parseInt(ticks[1]);
- int stop = Integer.parseInt(ticks[2]);
-
- switch (event.getAttributeValue("task")) {
- case "script":
- queue.add(event.getAttributeValue("script"), start, period, stop);
- break;
- case "magic":
- Effect effect = Effect.valueOf(event.getAttributeValue("effect").toUpperCase());
- float magnitude = Float.parseFloat(event.getAttributeValue("magnitude"));
- String script = event.getAttributeValue("script");
- SpellType type = SpellType.valueOf(event.getAttributeValue("type").toUpperCase());
- Entity caster = null;
- if (event.getAttribute("caster") != null) {
- caster = Engine.getStore().getEntity(Long.parseLong(event.getAttributeValue("caster")));
- }
- Entity target = null;
- if (event.getAttribute("target") != null) {
- target = Engine.getStore().getEntity(Long.parseLong(event.getAttributeValue("target")));
- }
- Spell spell = new Spell(target, caster, effect, magnitude, script, type);
- queue.add(new MagicTask(spell, stop), start, stop, period);
- break;
- }
- }
- }
-
- private void loadPlayer(Element playerData) {
- // create player
- RCreature species =
- (RCreature) Engine.getResources().getResource(playerData.getAttributeValue("race"));
- Player player =
- new Player(
- new RCreature(species.toElement()),
- playerData.getAttributeValue("name"),
- Gender.valueOf(playerData.getAttributeValue("gender").toUpperCase()),
- Player.Specialisation.valueOf(playerData.getAttributeValue("spec")),
- playerData.getAttributeValue("prof"));
- engine.startGame(new Game(player, Engine.getFileSystem()));
- Rectangle bounds = player.getShapeComponent();
- bounds.setLocation(
- Integer.parseInt(playerData.getAttributeValue("x")),
- Integer.parseInt(playerData.getAttributeValue("y")));
- player.setSign(playerData.getAttributeValue("sign"));
- player.species.text = "@";
-
- // start map
- int mapUID = Integer.parseInt(playerData.getAttributeValue("map"));
- Engine.getAtlas().setMap(Engine.getAtlas().getMap(mapUID));
- int level = Integer.parseInt(playerData.getAttributeValue("l"));
- Engine.getAtlas().setCurrentZone(level);
-
- // stats
- Stats stats = player.getStatsComponent();
- stats.addStr(
- Integer.parseInt(playerData.getChild("stats").getAttributeValue("str")) - stats.getStr());
- stats.addCon(
- Integer.parseInt(playerData.getChild("stats").getAttributeValue("con")) - stats.getCon());
- stats.addDex(
- Integer.parseInt(playerData.getChild("stats").getAttributeValue("dex")) - stats.getDex());
- stats.addInt(
- Integer.parseInt(playerData.getChild("stats").getAttributeValue("int")) - stats.getInt());
- stats.addWis(
- Integer.parseInt(playerData.getChild("stats").getAttributeValue("wis")) - stats.getWis());
- stats.addCha(
- Integer.parseInt(playerData.getChild("stats").getAttributeValue("cha")) - stats.getCha());
-
- // skills
- for (Attribute skill : (List) playerData.getChild("skills").getAttributes()) {
- player.setSkill(Skill.valueOf(skill.getName()), Integer.parseInt(skill.getValue()));
- }
-
- // items
- for (Element e : playerData.getChildren("item")) {
- long uid = Long.parseLong(e.getAttributeValue("uid"));
- player.getInventoryComponent().addItem(uid);
- }
-
- // spells
- for (Element e : playerData.getChildren("spell")) {
- player.getMagicComponent().addSpell(SpellFactory.getSpell(e.getText()));
- }
-
- // feats
- for (Element e : playerData.getChildren("feat")) {
- player.getCharacteristicsComponent().addFeat(Feat.valueOf(e.getText()));
- }
-
- // money
- player.getInventoryComponent().addMoney(Integer.parseInt(playerData.getChildText("money")));
- }
-
- private void initMaps() {
- // put mods and maps in uidstore
- for (RMod mod : Engine.getResources().getResources(RMod.class)) {
- if (Engine.getStore().getModUID(mod.id) == 0) {
- Engine.getStore().addMod(mod.id);
- }
- for (String[] path : mod.getMaps())
- try { // maps are in twowaymap, and are therefore not stored in cache
- Element map = Engine.getFileSystem().getFile(new XMLTranslator(), path).getRootElement();
- short mapUID = Short.parseShort(map.getChild("header").getAttributeValue("uid"));
- int uid = UIDStore.getMapUID(Engine.getStore().getModUID(path[0]), mapUID);
- Engine.getStore().addMap(uid, path);
- } catch (Exception e) {
- log.info("Map error in mod {}", path[0]);
- }
- }
- }
-}
+/*
+ * Neon, a roguelike engine.
+ * Copyright (C) 2013 - Maarten Driesen
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package neon.core;
+
+import java.awt.Rectangle;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.extern.slf4j.Slf4j;
+import neon.core.event.LoadEvent;
+import neon.core.event.MagicTask;
+import neon.core.event.ScriptAction;
+import neon.core.event.TaskQueue;
+import neon.core.handlers.InventoryHandler;
+import neon.core.handlers.SkillHandler;
+import neon.core.model.SaveGameModel;
+import neon.entities.Entity;
+import neon.entities.EntityFactory;
+import neon.entities.Item;
+import neon.entities.Player;
+import neon.entities.UIDStore;
+import neon.entities.components.Stats;
+import neon.entities.property.Ability;
+import neon.entities.property.Feat;
+import neon.entities.property.Gender;
+import neon.entities.property.Skill;
+import neon.magic.Effect;
+import neon.magic.Spell;
+import neon.magic.SpellFactory;
+import neon.maps.Atlas;
+import neon.maps.Map;
+import neon.maps.MapLoader;
+import neon.maps.MapUtils;
+import neon.maps.services.GameContextResourceProvider;
+import neon.resources.CGame;
+import neon.resources.RCreature;
+import neon.resources.RMod;
+import neon.resources.RSign;
+import neon.resources.RSpell.SpellType;
+import neon.systems.files.FileUtils;
+import neon.systems.files.JacksonMapper;
+import neon.systems.files.XMLTranslator;
+import net.engio.mbassy.listener.Handler;
+import net.engio.mbassy.listener.Listener;
+import net.engio.mbassy.listener.References;
+import org.jdom2.Element;
+
+@Listener(references = References.Strong)
+@Slf4j
+public class GameLoader {
+ private GameContext context;
+ private TaskQueue queue;
+ private Configuration config;
+ private GameContextResourceProvider resourceProvider;
+ private MapLoader mapLoader;
+ @Getter @Setter private int worldMapUID;
+
+ public GameLoader(GameContext context, Configuration config) {
+ this.context = context;
+ this.config = config;
+ queue = context.getQueue();
+ resourceProvider = new GameContextResourceProvider(context);
+ mapLoader = new MapLoader(context.getStore(), resourceProvider, new MapUtils());
+ }
+
+ @Handler
+ public void loadGame(LoadEvent le) {
+ log.trace("loadGame from {}: {}", le.getSource(), le);
+ // load game
+ switch (le.getMode()) {
+ case LOAD:
+ loadGame(le.getSaveName());
+ // indicate that loading is complete
+ context.post(new LoadEvent(this));
+ break;
+ case NEW:
+ try {
+ initGame(le.race, le.name, le.gender, le.specialisation, le.profession, le.sign);
+ } catch (RuntimeException re) {
+ System.out.println(re);
+ re.fillInStackTrace().printStackTrace();
+ }
+ // indicate that loading is complete
+ context.post(new LoadEvent(this));
+ break;
+ default:
+ break;
+ }
+ }
+
+ /**
+ * Creates a new game using the supplied data.
+ *
+ * @param race
+ * @param name
+ * @param gender
+ * @param spec
+ * @param profession
+ * @param sign
+ */
+ public void initGame(
+ String race,
+ String name,
+ Gender gender,
+ Player.Specialisation spec,
+ String profession,
+ RSign sign) {
+ try {
+ log.debug("Engine.initGame() start");
+
+ // initialize player
+ RCreature species = ((RCreature) context.getResources().getResource(race)).clone();
+ Player player = new Player(species, name, gender, spec, profession);
+ player.species.text = "@";
+ context.startGame(new Game(player, context.getFileSystem()));
+ setSign(player, sign);
+ for (Skill skill : Skill.values()) {
+ SkillHandler.checkFeat(skill, player);
+ }
+
+ // initialize maps
+ initMaps();
+
+ CGame game = (CGame) context.getResources().getResource("game", "config");
+
+ // starting items
+ for (String i : game.getStartingItems()) {
+ Item item = EntityFactory.getItem(i, context.getStore().createNewEntityUID());
+ context.getStore().addEntity(item);
+ InventoryHandler.addItem(player, item.getUID());
+ }
+ // starting spells
+ for (String i : game.getStartingSpells()) {
+ player.getMagicComponent().addSpell(SpellFactory.getSpell(i));
+ }
+
+ // position player
+ Rectangle bounds = player.getShapeComponent();
+ bounds.setLocation(game.getStartPosition().x, game.getStartPosition().y);
+ Atlas atlas = context.getAtlas();
+ UIDStore store = context.getStore();
+ String[] startMap = game.getStartMap();
+
+ Map map = atlas.getMap(store.getMapUID(startMap));
+ context.getScriptEngine().getBindings("js").putMember("map", map);
+ context.getAtlas().setMap(map);
+ context.getAtlas().setCurrentZone(game.getStartZone());
+ } catch (RuntimeException re) {
+ log.error("Error during initGame", re);
+ }
+ log.debug("Engine.initGame() exit");
+ }
+
+ private void setSign(Player player, RSign sign) {
+ player.setSign(sign.id);
+ for (String power : sign.powers) {
+ player.getMagicComponent().addSpell(SpellFactory.getSpell(power));
+ }
+ for (Ability ability : sign.abilities.keySet()) {
+ player.getCharacteristicsComponent().addAbility(ability, sign.abilities.get(ability));
+ }
+ }
+
+ /*
+ * Loads a saved game.
+ *
+ * @param save the name of the saved game
+ */
+ private void loadGame(String save) {
+ config.setProperty("save", save);
+
+ SaveGameModel saveModel = null;
+ try {
+ FileInputStream in = new FileInputStream("saves/" + save + "/save.xml");
+ JacksonMapper mapper = new JacksonMapper();
+ saveModel = mapper.fromXml(in, SaveGameModel.class);
+ in.close();
+ } catch (IOException e) {
+ System.out.println("IOException in loadGame: " + e.getMessage());
+ return;
+ } catch (Exception e) {
+ System.out.println("Error parsing save file: " + e.getMessage());
+ return;
+ }
+
+ // copy save map to temp
+ Path savePath = Paths.get("saves", save);
+ Path tempPath = Paths.get("temp");
+ FileUtils.copy(savePath, tempPath);
+
+ // initialize maps
+ initMaps();
+
+ // set time correctly (using setTime(), otherwise listeners would be called)
+ context.getTimer().setTime(saveModel.timer.ticks);
+
+ // create player
+ loadPlayer(saveModel.player);
+
+ // events
+ loadEvents(saveModel.events);
+
+ // quests
+ Player player = context.getPlayer();
+ if (player != null) {
+ for (SaveGameModel.QuestEntry quest : saveModel.journal.quests) {
+ context.getPlayer().getJournal().addQuest(quest.id, quest.subject);
+ context.getPlayer().getJournal().updateQuest(quest.id, quest.stage);
+ }
+ } else {
+ System.out.println("Skipping journal update");
+ }
+ }
+
+ private void loadEvents(SaveGameModel.EventsData events) {
+ // normal tasks
+ for (SaveGameModel.TaskEvent event : events.tasks) {
+ if (event.script != null) {
+ queue.add(event.description, new ScriptAction(event.script));
+ }
+ }
+
+ // timed tasks
+ for (SaveGameModel.TimerEvent event : events.timerEvents) {
+ String[] ticks = event.tick.split(":");
+ int start = Integer.parseInt(ticks[0]);
+ int period = Integer.parseInt(ticks[1]);
+ int stop = Integer.parseInt(ticks[2]);
+
+ if (event.taskType == null) {
+ continue;
+ }
+
+ switch (event.taskType) {
+ case "script":
+ queue.add(event.script, start, period, stop);
+ break;
+ case "magic":
+ Effect effect = Effect.valueOf(event.effect.toUpperCase());
+ float magnitude = event.magnitude;
+ String script = event.script;
+ SpellType type = SpellType.valueOf(event.spellType.toUpperCase());
+ Entity caster = null;
+ if (event.caster != null) {
+ caster = context.getStore().getEntity(event.caster);
+ }
+ Entity target = null;
+ if (event.target != null) {
+ target = context.getStore().getEntity(event.target);
+ }
+ Spell spell = new Spell(target, caster, effect, magnitude, script, type);
+ queue.add(new MagicTask(spell, stop), start, stop, period);
+ break;
+ }
+ }
+ }
+
+ private void loadPlayer(SaveGameModel.PlayerSaveData playerData) {
+ // create player
+ RCreature species = (RCreature) context.getResources().getResource(playerData.race);
+ Player player =
+ new Player(
+ species.clone(),
+ playerData.name,
+ Gender.valueOf(playerData.gender.toUpperCase()),
+ Player.Specialisation.valueOf(playerData.specialisation),
+ playerData.profession);
+ context.startGame(new Game(player, context.getFileSystem()));
+ Rectangle bounds = player.getShapeComponent();
+ bounds.setLocation(playerData.x, playerData.y);
+ player.setSign(playerData.sign);
+ player.species.text = "@";
+
+ // start map
+ context.getAtlas().setMap(context.getAtlas().getMap(playerData.map));
+ context.getAtlas().setCurrentZone(playerData.level);
+
+ // stats
+ Stats stats = player.getStatsComponent();
+ stats.addStr(playerData.stats.str - stats.getStr());
+ stats.addCon(playerData.stats.con - stats.getCon());
+ stats.addDex(playerData.stats.dex - stats.getDex());
+ stats.addInt(playerData.stats.int_ - stats.getInt());
+ stats.addWis(playerData.stats.wis - stats.getWis());
+ stats.addCha(playerData.stats.cha - stats.getCha());
+
+ // skills
+ loadSkills(player, playerData.skills);
+
+ // items
+ for (SaveGameModel.ItemReference itemRef : playerData.items) {
+ player.getInventoryComponent().addItem(itemRef.uid);
+ }
+
+ // spells
+ for (SaveGameModel.SpellReference spellRef : playerData.spells) {
+ player.getMagicComponent().addSpell(SpellFactory.getSpell(spellRef.id));
+ }
+
+ // feats
+ for (SaveGameModel.FeatReference featRef : playerData.feats) {
+ player.getCharacteristicsComponent().addFeat(Feat.valueOf(featRef.name));
+ }
+
+ // money
+ player.getInventoryComponent().addMoney(playerData.money.value);
+ }
+
+ private void loadSkills(Player player, SaveGameModel.SkillsData skills) {
+ // Load each skill if it has a value
+ if (skills.CREATION != null) player.setSkill(Skill.CREATION, skills.CREATION);
+ if (skills.DESTRUCTION != null) player.setSkill(Skill.DESTRUCTION, skills.DESTRUCTION);
+ if (skills.RESTORATION != null) player.setSkill(Skill.RESTORATION, skills.RESTORATION);
+ if (skills.ALTERATION != null) player.setSkill(Skill.ALTERATION, skills.ALTERATION);
+ if (skills.ILLUSION != null) player.setSkill(Skill.ILLUSION, skills.ILLUSION);
+ if (skills.ENCHANT != null) player.setSkill(Skill.ENCHANT, skills.ENCHANT);
+ if (skills.ALCHEMY != null) player.setSkill(Skill.ALCHEMY, skills.ALCHEMY);
+ if (skills.CONJURATION != null) player.setSkill(Skill.CONJURATION, skills.CONJURATION);
+ if (skills.ARCHERY != null) player.setSkill(Skill.ARCHERY, skills.ARCHERY);
+ if (skills.AXE != null) player.setSkill(Skill.AXE, skills.AXE);
+ if (skills.BLUNT != null) player.setSkill(Skill.BLUNT, skills.BLUNT);
+ if (skills.BLADE != null) player.setSkill(Skill.BLADE, skills.BLADE);
+ if (skills.SPEAR != null) player.setSkill(Skill.SPEAR, skills.SPEAR);
+ if (skills.UNARMED != null) player.setSkill(Skill.UNARMED, skills.UNARMED);
+ if (skills.CLIMBING != null) player.setSkill(Skill.CLIMBING, skills.CLIMBING);
+ if (skills.SWIMMING != null) player.setSkill(Skill.SWIMMING, skills.SWIMMING);
+ if (skills.SNEAK != null) player.setSkill(Skill.SNEAK, skills.SNEAK);
+ if (skills.HEAVY_ARMOR != null) player.setSkill(Skill.HEAVY_ARMOR, skills.HEAVY_ARMOR);
+ if (skills.MEDIUM_ARMOR != null) player.setSkill(Skill.MEDIUM_ARMOR, skills.MEDIUM_ARMOR);
+ if (skills.LIGHT_ARMOR != null) player.setSkill(Skill.LIGHT_ARMOR, skills.LIGHT_ARMOR);
+ if (skills.DODGING != null) player.setSkill(Skill.DODGING, skills.DODGING);
+ if (skills.BLOCK != null) player.setSkill(Skill.BLOCK, skills.BLOCK);
+ if (skills.UNARMORED != null) player.setSkill(Skill.UNARMORED, skills.UNARMORED);
+ if (skills.MERCANTILE != null) player.setSkill(Skill.MERCANTILE, skills.MERCANTILE);
+ if (skills.PICKPOCKET != null) player.setSkill(Skill.PICKPOCKET, skills.PICKPOCKET);
+ if (skills.ARMORER != null) player.setSkill(Skill.ARMORER, skills.ARMORER);
+ if (skills.LOCKPICKING != null) player.setSkill(Skill.LOCKPICKING, skills.LOCKPICKING);
+ if (skills.MEDICAL != null) player.setSkill(Skill.MEDICAL, skills.MEDICAL);
+ if (skills.DISABLE != null) player.setSkill(Skill.DISABLE, skills.DISABLE);
+ if (skills.SPEECHCRAFT != null) player.setSkill(Skill.SPEECHCRAFT, skills.SPEECHCRAFT);
+ if (skills.PERFORM != null) player.setSkill(Skill.PERFORM, skills.PERFORM);
+ if (skills.DISGUISE != null) player.setSkill(Skill.DISGUISE, skills.DISGUISE);
+ if (skills.RIDING != null) player.setSkill(Skill.RIDING, skills.RIDING);
+ if (skills.NONE != null) player.setSkill(Skill.NONE, skills.NONE);
+ }
+
+ private void initMaps() {
+ // put mods and maps in uidstore
+ for (RMod mod : context.getResources().getResources(RMod.class)) {
+ if (!context.getStore().isModUIDLoaded(mod.id)) {
+ context.getStore().addMod(mod.id);
+ }
+ for (String[] path : mod.getMaps())
+ try { // maps are in twowaymap, and are therefore not stored in cache
+ Element map = context.getFileSystem().getFile(new XMLTranslator(), path).getRootElement();
+ short mapUID = Short.parseShort(map.getChild("header").getAttributeValue("uid"));
+ int uid = UIDStore.getMapUID(context.getStore().getModUID(path[0]), mapUID);
+ mapLoader.load(path, mapUID, context.getFileSystem());
+ context.getStore().addMap(uid, path);
+ } catch (Exception e) {
+ log.info("Map error in mod {} : {}", path, e.toString());
+ }
+ }
+ }
+}
diff --git a/src/main/java/neon/core/GameSaver.java b/src/main/java/neon/core/GameSaver.java
index 01dfd76..e6693aa 100644
--- a/src/main/java/neon/core/GameSaver.java
+++ b/src/main/java/neon/core/GameSaver.java
@@ -1,216 +1,333 @@
-/*
- * Neon, a roguelike engine.
- * Copyright (C) 2013 - Maarten Driesen
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package neon.core;
-
-import com.google.common.collect.Multimap;
-import java.awt.Rectangle;
-import java.io.File;
-import neon.core.event.MagicTask;
-import neon.core.event.SaveEvent;
-import neon.core.event.ScriptAction;
-import neon.core.event.TaskQueue;
-import neon.entities.Player;
-import neon.entities.property.Feat;
-import neon.entities.property.Skill;
-import neon.magic.Spell;
-import neon.maps.Atlas;
-import neon.resources.RSpell;
-import neon.systems.files.XMLTranslator;
-import neon.util.fsm.Action;
-import net.engio.mbassy.listener.Handler;
-import net.engio.mbassy.listener.Listener;
-import net.engio.mbassy.listener.References;
-import org.jdom2.Document;
-import org.jdom2.Element;
-
-@Listener(references = References.Strong)
-public class GameSaver {
- private TaskQueue queue;
-
- public GameSaver(TaskQueue queue) {
- this.queue = queue;
- }
-
- /** Saves the current game. */
- @Handler
- public void saveGame(SaveEvent se) {
- Document doc = new Document();
- Element root = new Element("save");
- doc.setRootElement(root);
-
- Player player = Engine.getPlayer();
- root.addContent(savePlayer(player)); // save player data
- root.addContent(saveJournal(player)); // save journal
- root.addContent(saveEvents()); // save events
- root.addContent(saveQuests()); // save quests
- Element timer = new Element("timer");
- timer.setAttribute("ticks", String.valueOf(Engine.getTimer().getTime()));
- root.addContent(timer);
-
- File saves = new File("saves");
- if (!saves.exists()) {
- saves.mkdir();
- }
-
- File dir = new File("saves/" + player.getName());
- if (!dir.exists()) {
- dir.mkdir();
- }
-
- // first copy everything from temp to save, to ensure savedoc is not overwritten
- Engine.getAtlas().getCache().commit();
- Engine.getStore().getCache().commit();
- Engine.getFileSystem().storeTemp(dir);
- Engine.getFileSystem()
- .saveFile(doc, new XMLTranslator(), "saves", player.getName(), "save.xml");
- }
-
- private Element saveEvents() {
- Element events = new Element("events");
-
- // all normal tasks (for now only script tasks)
- Multimap tasks = queue.getTasks();
- for (String key : tasks.keySet()) {
- for (Action action : tasks.get(key)) {
- Element event = new Element("task");
- event.setAttribute("desc", key);
- if (action instanceof ScriptAction) {
- ScriptAction task = (ScriptAction) action;
- event.setAttribute("script", task.getScript());
- }
- events.addContent(event);
- }
- }
-
- // all timer tasks
- Multimap repeats = queue.getTimerTasks();
- for (Integer key : repeats.keySet()) {
- for (TaskQueue.RepeatEntry entry : repeats.get(key)) {
- Element event = new Element("timer");
- event.setAttribute("tick", key + ":" + entry.getPeriod() + ":" + entry.getStop());
- if (entry.getScript() != null) {
- event.setAttribute("task", "script");
- event.setAttribute("script", entry.getScript());
- } else if (entry.getTask() instanceof MagicTask) {
- event.setAttribute("task", "magic");
- Spell spell = ((MagicTask) entry.getTask()).getSpell();
- event.setAttribute("effect", spell.getEffect().name());
- if (spell.getTarget() != null) {
- event.setAttribute("target", Long.toString(spell.getTarget().getUID()));
- }
- if (spell.getCaster() != null) {
- event.setAttribute("caster", Long.toString(spell.getCaster().getUID()));
- }
- if (spell.getScript() != null) {
- event.setAttribute("script", spell.getScript());
- }
- event.setAttribute("stype", spell.getType().name());
- event.setAttribute("mag", Float.toString(spell.getMagnitude()));
- }
- events.addContent(event);
- }
- }
-
- return events;
- }
-
- private Element saveQuests() {
- Element quests = new Element("quests");
- // TODO: save random quests
- return quests;
- }
-
- private Element savePlayer(Player player) {
- Element PC = new Element("player");
-
- PC.setAttribute("name", player.getName());
- PC.setAttribute("race", player.species.id);
-
- PC.setAttribute("gender", player.getGender().toString().toLowerCase());
-
- PC.setAttribute("spec", player.getSpecialisation().toString());
-
- Atlas atlas = Engine.getAtlas();
- PC.setAttribute("map", Integer.toString(atlas.getCurrentMap().getUID()));
- int l = atlas.getCurrentZoneIndex();
- PC.setAttribute("l", Integer.toString(l));
- Rectangle bounds = player.getShapeComponent();
- PC.setAttribute("x", String.valueOf(bounds.x));
- PC.setAttribute("y", String.valueOf(bounds.y));
- PC.setAttribute("sign", player.getSign());
-
- Element skills = new Element("skills");
- for (Skill s : Skill.values()) {
- skills.setAttribute(s.toString(), String.valueOf(player.getSkill(s)));
- }
- PC.addContent(skills);
-
- Element stats = new Element("stats");
- stats.setAttribute("str", String.valueOf(player.getStatsComponent().getStr()));
- stats.setAttribute("con", String.valueOf(player.getStatsComponent().getCon()));
- stats.setAttribute("dex", String.valueOf(player.getStatsComponent().getDex()));
- stats.setAttribute("int", String.valueOf(player.getStatsComponent().getInt()));
- stats.setAttribute("wis", String.valueOf(player.getStatsComponent().getWis()));
- stats.setAttribute("cha", String.valueOf(player.getStatsComponent().getCha()));
- PC.addContent(stats);
-
- Element money = new Element("money");
- money.setText(String.valueOf(player.getInventoryComponent().getMoney()));
- PC.addContent(money);
-
- for (long uid : player.getInventoryComponent()) {
- Element item = new Element("item");
- item.setAttribute("uid", Long.toString(uid));
- PC.addContent(item);
- }
-
- for (RSpell s : player.getMagicComponent().getSpells()) {
- Element spell = new Element("spell");
- spell.setText(s.id);
- PC.addContent(spell);
- }
-
- for (RSpell p : player.getMagicComponent().getPowers()) {
- Element spell = new Element("spell");
- spell.setText(p.id);
- PC.addContent(spell);
- }
-
- for (Feat f : player.getCharacteristicsComponent().getFeats()) {
- Element feat = new Element("feat");
- feat.setText(f.toString());
- PC.addContent(feat);
- }
-
- return PC;
- }
-
- private Element saveJournal(Player player) {
- Element journal = new Element("journal");
-
- for (String q : player.getJournal().getQuests().keySet()) {
- Element quest = new Element("quest");
- quest.setAttribute("id", q);
- quest.setAttribute("stage", String.valueOf(player.getJournal().getQuests().get(q)));
- quest.setText(player.getJournal().getSubjects().get(q));
- journal.addContent(quest);
- }
- return journal;
- }
-}
+/*
+ * Neon, a roguelike engine.
+ * Copyright (C) 2013 - Maarten Driesen
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package neon.core;
+
+import com.google.common.collect.Multimap;
+import java.awt.Rectangle;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import neon.core.event.MagicTask;
+import neon.core.event.SaveEvent;
+import neon.core.event.ScriptAction;
+import neon.core.event.TaskQueue;
+import neon.core.model.SaveGameModel;
+import neon.entities.Player;
+import neon.entities.property.Feat;
+import neon.entities.property.Skill;
+import neon.magic.Spell;
+import neon.maps.Atlas;
+import neon.resources.RSpell;
+import neon.systems.files.JacksonMapper;
+import neon.systems.files.StringTranslator;
+import neon.util.fsm.Action;
+import net.engio.mbassy.listener.Handler;
+import net.engio.mbassy.listener.Listener;
+import net.engio.mbassy.listener.References;
+
+@Listener(references = References.Strong)
+public class GameSaver {
+ private TaskQueue queue;
+
+ public GameSaver(TaskQueue queue) {
+ this.queue = queue;
+ }
+
+ /** Saves the current game. */
+ @Handler
+ public void saveGame(SaveEvent se) {
+ Player player = Engine.getPlayer();
+
+ // Build save game model
+ SaveGameModel save = new SaveGameModel();
+ save.version = "2.0";
+ save.player = buildPlayerData(player);
+ save.journal = buildJournalData(player);
+ save.events = buildEventsData();
+ save.timer = new SaveGameModel.TimerData();
+ save.timer.ticks = Engine.getTimer().getTime();
+ save.quests = null; // No random quests yet
+
+ // Ensure directories exist
+ File saves = new File("saves");
+ if (!saves.exists()) {
+ saves.mkdir();
+ }
+
+ File dir = new File("saves/" + player.getName());
+ if (!dir.exists()) {
+ dir.mkdir();
+ }
+
+ // first copy everything from temp to save, to ensure savedoc is not overwritten
+ Engine.getAtlas().getCache().commit();
+ Engine.getStore().getCache().commit();
+ Engine.getFileSystem().storeTemp(dir);
+
+ // Serialize with Jackson
+ try {
+ JacksonMapper mapper = new JacksonMapper();
+ ByteArrayOutputStream out = mapper.toXml(save);
+ String xml = out.toString("UTF-8");
+ Engine.getFileSystem()
+ .saveFile(xml, new StringTranslator(), "saves", player.getName(), "save.xml");
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to save game", e);
+ }
+ }
+
+ private SaveGameModel.EventsData buildEventsData() {
+ SaveGameModel.EventsData events = new SaveGameModel.EventsData();
+
+ // all normal tasks (for now only script tasks)
+ Multimap tasks = queue.getTasks();
+ for (String key : tasks.keySet()) {
+ for (Action action : tasks.get(key)) {
+ if (action instanceof ScriptAction) {
+ SaveGameModel.TaskEvent event = new SaveGameModel.TaskEvent();
+ event.description = key;
+ event.script = ((ScriptAction) action).getScript();
+ events.tasks.add(event);
+ }
+ }
+ }
+
+ // all timer tasks
+ Multimap repeats = queue.getTimerTasks();
+ for (Integer key : repeats.keySet()) {
+ for (TaskQueue.RepeatEntry entry : repeats.get(key)) {
+ SaveGameModel.TimerEvent event = new SaveGameModel.TimerEvent();
+ event.tick = key + ":" + entry.getPeriod() + ":" + entry.getStop();
+
+ if (entry.getScript() != null) {
+ event.taskType = "script";
+ event.script = entry.getScript();
+ } else if (entry.getTask() instanceof MagicTask) {
+ event.taskType = "magic";
+ Spell spell = ((MagicTask) entry.getTask()).getSpell();
+ event.effect = spell.getEffect().name();
+ if (spell.getTarget() != null) {
+ event.target = spell.getTarget().getUID();
+ }
+ if (spell.getCaster() != null) {
+ event.caster = spell.getCaster().getUID();
+ }
+ if (spell.getScript() != null) {
+ event.script = spell.getScript();
+ }
+ event.spellType = spell.getType().name();
+ event.magnitude = spell.getMagnitude();
+ }
+
+ events.timerEvents.add(event);
+ }
+ }
+
+ return events;
+ }
+
+ private SaveGameModel.PlayerSaveData buildPlayerData(Player player) {
+ SaveGameModel.PlayerSaveData data = new SaveGameModel.PlayerSaveData();
+
+ // Basic attributes
+ data.name = player.getName();
+ data.race = player.species.id;
+ data.gender = player.getGender().toString().toLowerCase();
+ data.specialisation = player.getSpecialisation().toString();
+ data.profession = player.getProfession();
+ data.sign = player.getSign();
+
+ // Position
+ Atlas atlas = Engine.getAtlas();
+ data.map = atlas.getCurrentMap().getUID();
+ data.level = atlas.getCurrentZoneIndex();
+ Rectangle bounds = player.getShapeComponent();
+ data.x = bounds.x;
+ data.y = bounds.y;
+
+ // Skills
+ data.skills = new SaveGameModel.SkillsData();
+ for (Skill s : Skill.values()) {
+ float skillValue = player.getSkill(s);
+ setSkillValue(data.skills, s, skillValue);
+ }
+
+ // Stats
+ data.stats = new SaveGameModel.StatsData();
+ data.stats.str = player.getStatsComponent().getStr();
+ data.stats.con = player.getStatsComponent().getCon();
+ data.stats.dex = player.getStatsComponent().getDex();
+ data.stats.int_ = player.getStatsComponent().getInt();
+ data.stats.wis = player.getStatsComponent().getWis();
+ data.stats.cha = player.getStatsComponent().getCha();
+
+ // Money
+ data.money = new SaveGameModel.MoneyData();
+ data.money.value = player.getInventoryComponent().getMoney();
+
+ // Items
+ for (long uid : player.getInventoryComponent()) {
+ SaveGameModel.ItemReference item = new SaveGameModel.ItemReference();
+ item.uid = uid;
+ data.items.add(item);
+ }
+
+ // Spells and powers
+ for (RSpell s : player.getMagicComponent().getSpells()) {
+ SaveGameModel.SpellReference spell = new SaveGameModel.SpellReference();
+ spell.id = s.id;
+ data.spells.add(spell);
+ }
+
+ for (RSpell p : player.getMagicComponent().getPowers()) {
+ SaveGameModel.SpellReference spell = new SaveGameModel.SpellReference();
+ spell.id = p.id;
+ data.spells.add(spell);
+ }
+
+ // Feats
+ for (Feat f : player.getCharacteristicsComponent().getFeats()) {
+ SaveGameModel.FeatReference feat = new SaveGameModel.FeatReference();
+ feat.name = f.toString();
+ data.feats.add(feat);
+ }
+
+ return data;
+ }
+
+ private void setSkillValue(SaveGameModel.SkillsData skills, Skill skill, float value) {
+ switch (skill) {
+ case CREATION:
+ skills.CREATION = value;
+ break;
+ case DESTRUCTION:
+ skills.DESTRUCTION = value;
+ break;
+ case RESTORATION:
+ skills.RESTORATION = value;
+ break;
+ case ALTERATION:
+ skills.ALTERATION = value;
+ break;
+ case ILLUSION:
+ skills.ILLUSION = value;
+ break;
+ case ENCHANT:
+ skills.ENCHANT = value;
+ break;
+ case ALCHEMY:
+ skills.ALCHEMY = value;
+ break;
+ case CONJURATION:
+ skills.CONJURATION = value;
+ break;
+ case ARCHERY:
+ skills.ARCHERY = value;
+ break;
+ case AXE:
+ skills.AXE = value;
+ break;
+ case BLUNT:
+ skills.BLUNT = value;
+ break;
+ case BLADE:
+ skills.BLADE = value;
+ break;
+ case SPEAR:
+ skills.SPEAR = value;
+ break;
+ case UNARMED:
+ skills.UNARMED = value;
+ break;
+ case CLIMBING:
+ skills.CLIMBING = value;
+ break;
+ case SWIMMING:
+ skills.SWIMMING = value;
+ break;
+ case SNEAK:
+ skills.SNEAK = value;
+ break;
+ case HEAVY_ARMOR:
+ skills.HEAVY_ARMOR = value;
+ break;
+ case MEDIUM_ARMOR:
+ skills.MEDIUM_ARMOR = value;
+ break;
+ case LIGHT_ARMOR:
+ skills.LIGHT_ARMOR = value;
+ break;
+ case DODGING:
+ skills.DODGING = value;
+ break;
+ case BLOCK:
+ skills.BLOCK = value;
+ break;
+ case UNARMORED:
+ skills.UNARMORED = value;
+ break;
+ case MERCANTILE:
+ skills.MERCANTILE = value;
+ break;
+ case PICKPOCKET:
+ skills.PICKPOCKET = value;
+ break;
+ case ARMORER:
+ skills.ARMORER = value;
+ break;
+ case LOCKPICKING:
+ skills.LOCKPICKING = value;
+ break;
+ case MEDICAL:
+ skills.MEDICAL = value;
+ break;
+ case DISABLE:
+ skills.DISABLE = value;
+ break;
+ case SPEECHCRAFT:
+ skills.SPEECHCRAFT = value;
+ break;
+ case PERFORM:
+ skills.PERFORM = value;
+ break;
+ case DISGUISE:
+ skills.DISGUISE = value;
+ break;
+ case RIDING:
+ skills.RIDING = value;
+ break;
+ case NONE:
+ skills.NONE = value;
+ break;
+ }
+ }
+
+ private SaveGameModel.JournalData buildJournalData(Player player) {
+ SaveGameModel.JournalData journal = new SaveGameModel.JournalData();
+
+ for (String q : player.getJournal().getQuests().keySet()) {
+ SaveGameModel.QuestEntry quest = new SaveGameModel.QuestEntry();
+ quest.id = q;
+ quest.stage = player.getJournal().getQuests().get(q);
+ quest.subject = player.getJournal().getSubjects().get(q);
+ journal.quests.add(quest);
+ }
+
+ return journal;
+ }
+}
diff --git a/src/main/java/neon/core/model/NeonConfig.java b/src/main/java/neon/core/model/NeonConfig.java
new file mode 100644
index 0000000..ca8e266
--- /dev/null
+++ b/src/main/java/neon/core/model/NeonConfig.java
@@ -0,0 +1,60 @@
+/*
+ * Neon, a roguelike engine.
+ * Copyright (C) 2026 - Peter Riewe
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package neon.core.model;
+
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
+
+/**
+ * Jackson model for neon.ini.xml configuration file.
+ *
+ * @author priewe
+ */
+@JacksonXmlRootElement(localName = "root")
+public class NeonConfig {
+
+ @JacksonXmlProperty(localName = "files")
+ public FilesElement files = new FilesElement();
+
+ @JacksonXmlProperty(localName = "threads")
+ public ThreadsElement threads = new ThreadsElement();
+
+ @JacksonXmlProperty(localName = "ai")
+ public String ai;
+
+ @JacksonXmlProperty(localName = "log")
+ public String log;
+
+ @JacksonXmlProperty(localName = "lang")
+ public String lang;
+
+ @JacksonXmlProperty(localName = "keys")
+ public String keys;
+
+ /** Empty files element */
+ public static class FilesElement {
+ // Empty element placeholder
+ }
+
+ /** Threads configuration */
+ public static class ThreadsElement {
+ @JacksonXmlProperty(isAttribute = true, localName = "generate")
+ public String generate;
+ }
+}
diff --git a/src/main/java/neon/core/model/SaveGameModel.java b/src/main/java/neon/core/model/SaveGameModel.java
new file mode 100644
index 0000000..68d234a
--- /dev/null
+++ b/src/main/java/neon/core/model/SaveGameModel.java
@@ -0,0 +1,337 @@
+/*
+ * Neon, a roguelike engine.
+ * Copyright (C) 2026 - Peter Riewe
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package neon.core.model;
+
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlText;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Jackson model for save game XML structure.
+ *
+ *
This class represents the parsed XML structure of a save game file. It is designed to separate
+ * XML parsing (Jackson's responsibility) from game object construction (GameLoader's
+ * responsibility).
+ *
+ * @author priewe
+ */
+@JacksonXmlRootElement(localName = "save")
+public class SaveGameModel {
+
+ @JacksonXmlProperty(isAttribute = true, localName = "version")
+ public String version = "2.0"; // Add versioning for future compatibility
+
+ @JacksonXmlProperty(localName = "player")
+ public PlayerSaveData player;
+
+ @JacksonXmlProperty(localName = "journal")
+ public JournalData journal = new JournalData();
+
+ @JacksonXmlProperty(localName = "events")
+ public EventsData events = new EventsData();
+
+ @JacksonXmlProperty(localName = "timer")
+ public TimerData timer;
+
+ @JacksonXmlProperty(localName = "quests")
+ public QuestsData quests; // Optional - null if no random quests
+
+ /** Player save data */
+ public static class PlayerSaveData {
+ @JacksonXmlProperty(isAttribute = true, localName = "name")
+ public String name;
+
+ @JacksonXmlProperty(isAttribute = true, localName = "race")
+ public String race;
+
+ @JacksonXmlProperty(isAttribute = true, localName = "gender")
+ public String gender;
+
+ @JacksonXmlProperty(isAttribute = true, localName = "spec")
+ public String specialisation;
+
+ @JacksonXmlProperty(isAttribute = true, localName = "prof")
+ public String profession;
+
+ @JacksonXmlProperty(isAttribute = true, localName = "sign")
+ public String sign;
+
+ @JacksonXmlProperty(isAttribute = true, localName = "map")
+ public int map;
+
+ @JacksonXmlProperty(isAttribute = true, localName = "l")
+ public int level;
+
+ @JacksonXmlProperty(isAttribute = true, localName = "x")
+ public int x;
+
+ @JacksonXmlProperty(isAttribute = true, localName = "y")
+ public int y;
+
+ @JacksonXmlProperty(localName = "skills")
+ public SkillsData skills;
+
+ @JacksonXmlProperty(localName = "stats")
+ public StatsData stats;
+
+ @JacksonXmlProperty(localName = "money")
+ public MoneyData money;
+
+ @JacksonXmlElementWrapper(useWrapping = false)
+ @JacksonXmlProperty(localName = "item")
+ public List items = new ArrayList<>();
+
+ @JacksonXmlElementWrapper(useWrapping = false)
+ @JacksonXmlProperty(localName = "spell")
+ public List spells = new ArrayList<>();
+
+ @JacksonXmlElementWrapper(useWrapping = false)
+ @JacksonXmlProperty(localName = "feat")
+ public List feats = new ArrayList<>();
+ }
+
+ /** Skills data stored as XML attributes */
+ public static class SkillsData {
+ // Skills are stored as XML attributes - use @JacksonXmlProperty for each skill
+ // All 38 skills from the Skill enum
+ @JacksonXmlProperty(isAttribute = true, localName = "CREATION")
+ public Float CREATION;
+
+ @JacksonXmlProperty(isAttribute = true, localName = "DESTRUCTION")
+ public Float DESTRUCTION;
+
+ @JacksonXmlProperty(isAttribute = true, localName = "RESTORATION")
+ public Float RESTORATION;
+
+ @JacksonXmlProperty(isAttribute = true, localName = "ALTERATION")
+ public Float ALTERATION;
+
+ @JacksonXmlProperty(isAttribute = true, localName = "ILLUSION")
+ public Float ILLUSION;
+
+ @JacksonXmlProperty(isAttribute = true, localName = "ENCHANT")
+ public Float ENCHANT;
+
+ @JacksonXmlProperty(isAttribute = true, localName = "ALCHEMY")
+ public Float ALCHEMY;
+
+ @JacksonXmlProperty(isAttribute = true, localName = "CONJURATION")
+ public Float CONJURATION;
+
+ @JacksonXmlProperty(isAttribute = true, localName = "ARCHERY")
+ public Float ARCHERY;
+
+ @JacksonXmlProperty(isAttribute = true, localName = "AXE")
+ public Float AXE;
+
+ @JacksonXmlProperty(isAttribute = true, localName = "BLUNT")
+ public Float BLUNT;
+
+ @JacksonXmlProperty(isAttribute = true, localName = "BLADE")
+ public Float BLADE;
+
+ @JacksonXmlProperty(isAttribute = true, localName = "SPEAR")
+ public Float SPEAR;
+
+ @JacksonXmlProperty(isAttribute = true, localName = "UNARMED")
+ public Float UNARMED;
+
+ @JacksonXmlProperty(isAttribute = true, localName = "CLIMBING")
+ public Float CLIMBING;
+
+ @JacksonXmlProperty(isAttribute = true, localName = "SWIMMING")
+ public Float SWIMMING;
+
+ @JacksonXmlProperty(isAttribute = true, localName = "SNEAK")
+ public Float SNEAK;
+
+ @JacksonXmlProperty(isAttribute = true, localName = "HEAVY_ARMOR")
+ public Float HEAVY_ARMOR;
+
+ @JacksonXmlProperty(isAttribute = true, localName = "MEDIUM_ARMOR")
+ public Float MEDIUM_ARMOR;
+
+ @JacksonXmlProperty(isAttribute = true, localName = "LIGHT_ARMOR")
+ public Float LIGHT_ARMOR;
+
+ @JacksonXmlProperty(isAttribute = true, localName = "DODGING")
+ public Float DODGING;
+
+ @JacksonXmlProperty(isAttribute = true, localName = "BLOCK")
+ public Float BLOCK;
+
+ @JacksonXmlProperty(isAttribute = true, localName = "UNARMORED")
+ public Float UNARMORED;
+
+ @JacksonXmlProperty(isAttribute = true, localName = "MERCANTILE")
+ public Float MERCANTILE;
+
+ @JacksonXmlProperty(isAttribute = true, localName = "PICKPOCKET")
+ public Float PICKPOCKET;
+
+ @JacksonXmlProperty(isAttribute = true, localName = "ARMORER")
+ public Float ARMORER;
+
+ @JacksonXmlProperty(isAttribute = true, localName = "LOCKPICKING")
+ public Float LOCKPICKING;
+
+ @JacksonXmlProperty(isAttribute = true, localName = "MEDICAL")
+ public Float MEDICAL;
+
+ @JacksonXmlProperty(isAttribute = true, localName = "DISABLE")
+ public Float DISABLE;
+
+ @JacksonXmlProperty(isAttribute = true, localName = "SPEECHCRAFT")
+ public Float SPEECHCRAFT;
+
+ @JacksonXmlProperty(isAttribute = true, localName = "PERFORM")
+ public Float PERFORM;
+
+ @JacksonXmlProperty(isAttribute = true, localName = "DISGUISE")
+ public Float DISGUISE;
+
+ @JacksonXmlProperty(isAttribute = true, localName = "RIDING")
+ public Float RIDING;
+
+ @JacksonXmlProperty(isAttribute = true, localName = "NONE")
+ public Float NONE;
+ }
+
+ /** Stats data */
+ public static class StatsData {
+ @JacksonXmlProperty(isAttribute = true, localName = "str")
+ public int str;
+
+ @JacksonXmlProperty(isAttribute = true, localName = "con")
+ public int con;
+
+ @JacksonXmlProperty(isAttribute = true, localName = "dex")
+ public int dex;
+
+ @JacksonXmlProperty(isAttribute = true, localName = "int")
+ public int int_;
+
+ @JacksonXmlProperty(isAttribute = true, localName = "wis")
+ public int wis;
+
+ @JacksonXmlProperty(isAttribute = true, localName = "cha")
+ public int cha;
+ }
+
+ /** Money data */
+ public static class MoneyData {
+ @JacksonXmlText public int value;
+ }
+
+ /** Item reference */
+ public static class ItemReference {
+ @JacksonXmlProperty(isAttribute = true, localName = "uid")
+ public long uid;
+ }
+
+ /** Spell reference */
+ public static class SpellReference {
+ @JacksonXmlText public String id;
+ }
+
+ /** Feat reference */
+ public static class FeatReference {
+ @JacksonXmlText public String name;
+ }
+
+ /** Journal data */
+ public static class JournalData {
+ @JacksonXmlElementWrapper(useWrapping = false)
+ @JacksonXmlProperty(localName = "quest")
+ public List quests = new ArrayList<>();
+ }
+
+ /** Quest entry */
+ public static class QuestEntry {
+ @JacksonXmlProperty(isAttribute = true, localName = "id")
+ public String id;
+
+ @JacksonXmlProperty(isAttribute = true, localName = "stage")
+ public int stage;
+
+ @JacksonXmlText public String subject;
+ }
+
+ /** Events data */
+ public static class EventsData {
+ @JacksonXmlElementWrapper(useWrapping = false)
+ @JacksonXmlProperty(localName = "task")
+ public List tasks = new ArrayList<>();
+
+ @JacksonXmlElementWrapper(useWrapping = false)
+ @JacksonXmlProperty(localName = "timer")
+ public List timerEvents = new ArrayList<>();
+ }
+
+ /** Task event */
+ public static class TaskEvent {
+ @JacksonXmlProperty(isAttribute = true, localName = "desc")
+ public String description;
+
+ @JacksonXmlProperty(isAttribute = true, localName = "script")
+ public String script;
+ }
+
+ /** Timer event */
+ public static class TimerEvent {
+ @JacksonXmlProperty(isAttribute = true, localName = "tick")
+ public String tick; // Format: "start:period:stop"
+
+ @JacksonXmlProperty(isAttribute = true, localName = "task")
+ public String taskType; // "script" or "magic"
+
+ @JacksonXmlProperty(isAttribute = true, localName = "script")
+ public String script;
+
+ // Magic task attributes
+ @JacksonXmlProperty(isAttribute = true, localName = "effect")
+ public String effect;
+
+ @JacksonXmlProperty(isAttribute = true, localName = "target")
+ public Long target;
+
+ @JacksonXmlProperty(isAttribute = true, localName = "caster")
+ public Long caster;
+
+ @JacksonXmlProperty(isAttribute = true, localName = "stype")
+ public String spellType;
+
+ @JacksonXmlProperty(isAttribute = true, localName = "mag")
+ public Float magnitude;
+ }
+
+ /** Timer data */
+ public static class TimerData {
+ @JacksonXmlProperty(isAttribute = true, localName = "ticks")
+ public int ticks;
+ }
+
+ /** Quests data (for random quests - currently unused) */
+ public static class QuestsData {
+ // Empty for now, placeholder for future random quest saving
+ }
+}
diff --git a/src/main/java/neon/editor/DataStore.java b/src/main/java/neon/editor/DataStore.java
index 681c8bf..85e92de 100644
--- a/src/main/java/neon/editor/DataStore.java
+++ b/src/main/java/neon/editor/DataStore.java
@@ -53,6 +53,18 @@ public Multimap getEvents() {
return events;
}
+ /**
+ * Loads all data from a mod.
+ *
+ *
NOTE (Phase 6 - Minimal Migration): Currently uses JDOM constructors for resource loading.
+ * Full migration to Jackson constructors deferred to Phase 7 when JDOM constructors are removed
+ * from resource classes. Maps already save via Jackson (Phase 2D), but resource loading still
+ * uses JDOM via XMLTranslator, similar to ModLoader.
+ *
+ * @param root the mod path
+ * @param active whether this is the active mod
+ * @param extension whether the mod is an extension
+ */
public void loadData(String root, boolean active, boolean extension) {
RMod mod = new RMod(loadInfo(root, "main.xml"), loadCC(root, "cc.xml"), root);
if (active) {
diff --git a/src/main/java/neon/editor/JacksonXmlBuilder.java b/src/main/java/neon/editor/JacksonXmlBuilder.java
new file mode 100644
index 0000000..262c0b8
--- /dev/null
+++ b/src/main/java/neon/editor/JacksonXmlBuilder.java
@@ -0,0 +1,227 @@
+/*
+ * Neon, a roguelike engine.
+ * Copyright (C) 2012 - Maarten Driesen
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package neon.editor;
+
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
+import java.io.ByteArrayInputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import neon.resources.RData;
+import neon.resources.RMod;
+import neon.systems.files.JacksonMapper;
+import org.jdom2.Document;
+import org.jdom2.Element;
+import org.jdom2.input.SAXBuilder;
+
+/**
+ * Jackson-based XML document builder for serializing game resources.
+ *
+ *
This class replaces {@link XMLBuilder} as part of Phase 7A of the JDOM-to-Jackson migration.
+ * It uses Jackson for events serialization and leverages the existing {@code toElement()} bridge
+ * pattern for resources (as established in RSpell, RCreature, and RMod).
+ *
+ *
Phase 7A Scope: This class still produces JDOM Documents for compatibility with the
+ * existing {@code ModFiler.saveFile(Document)} API. In Phase 7B, when {@code toElement()} methods
+ * are removed from all resources, this class will be updated to serialize directly to
+ * ByteArrayOutputStream without the JDOM intermediate representation.
+ *
+ *
Phase 7B: Remove toElement() from resources, update this class to skip JDOM
+ *
Phase 7C: Direct Jackson serialization, eliminate this class
+ *
+ *
+ * @author mdriesen
+ */
+public class JacksonXmlBuilder {
+ private final JacksonMapper mapper = new JacksonMapper();
+ private final DataStore store;
+
+ /**
+ * Creates a new JacksonXmlBuilder.
+ *
+ * @param store the data store containing events and active mod information
+ */
+ public JacksonXmlBuilder(DataStore store) {
+ this.store = store;
+ }
+
+ /**
+ * Creates an events XML document using Jackson serialization.
+ *
+ *
Serializes the scheduled game events from {@code DataStore.getEvents()} into an XML document
+ * with the structure:
+ *
+ *
{@code
+ *
+ *
+ *
+ *
+ * }
+ *
+ * @return JDOM Document containing events
+ */
+ public Document getEventsDoc() {
+ EventsModel model = new EventsModel();
+ model.events = new ArrayList<>();
+
+ // Convert Multimap to List
+ for (Map.Entry> entry : store.getEvents().asMap().entrySet()) {
+ String script = entry.getKey();
+ for (String tick : entry.getValue()) {
+ EventsModel.Event event = new EventsModel.Event();
+ event.script = script;
+ event.tick = tick;
+ model.events.add(event);
+ }
+ }
+
+ return toDocument(model);
+ }
+
+ /**
+ * Creates a resource document with unsorted resources.
+ *
+ *
Resources maintain their natural iteration order. Filters resources to only include those
+ * belonging to the specified mod.
+ *
+ *
Use this for: factions, recipes, terrain, themes (resources where order doesn't matter).
+ *
+ * @param resources the collection of resources to serialize
+ * @param rootName the XML root element name (e.g., "factions", "terrain")
+ * @param mod the active mod (filters by mod ID)
+ * @return JDOM Document with unsorted resources
+ */
+ public Document getListDoc(Collection extends RData> resources, String rootName, RMod mod) {
+ return buildResourceDoc(resources, rootName, mod, false);
+ }
+
+ /**
+ * Creates a resource document with resources sorted alphabetically by ID.
+ *
+ *
Resources are sorted alphabetically by their {@code id} field. Filters resources to only
+ * include those belonging to the specified mod.
+ *
+ *
Use this for: items, creatures, spells (resources that benefit from alphabetical order).
+ *
+ * @param resources the collection of resources to serialize
+ * @param rootName the XML root element name (e.g., "items", "monsters")
+ * @param mod the active mod (filters by mod ID)
+ * @return JDOM Document with sorted resources
+ */
+ public Document getResourceDoc(Collection extends RData> resources, String rootName, RMod mod) {
+ return buildResourceDoc(resources, rootName, mod, true);
+ }
+
+ /**
+ * Builds a resource document, filtering by mod and optionally sorting by ID.
+ *
+ *
This method uses each resource's {@code toElement()} method to serialize individual
+ * resources. Many resources (RCreature, RSpell, etc.) internally use Jackson in their {@code
+ * toElement()} implementation, following the established bridge pattern.
+ *
+ * @param resources the collection of resources
+ * @param rootName the XML root element name
+ * @param mod the active mod
+ * @param sorted whether to sort resources alphabetically by ID
+ * @return JDOM Document
+ */
+ private Document buildResourceDoc(
+ Collection extends RData> resources, String rootName, RMod mod, boolean sorted) {
+
+ // Filter resources belonging to this mod
+ List filtered =
+ resources.stream()
+ .filter(r -> r.getPath()[0].equals(mod.get("id")))
+ .collect(Collectors.toList());
+
+ // Sort if requested
+ if (sorted) {
+ filtered.sort(Comparator.comparing(r -> r.id));
+ }
+
+ // Build JDOM element with children using toElement() bridge
+ Element root = new Element(rootName);
+ for (RData resource : filtered) {
+ root.addContent(resource.toElement());
+ }
+
+ return new Document(root);
+ }
+
+ /**
+ * Converts a Jackson model to a JDOM Document using the Jackson→JDOM bridge pattern.
+ *
+ *
This follows the same pattern as {@code RSpell.toElement()} (line 384-398) and {@code
+ * RMod.getMainElement()} (line 179-203): serialize to XML via Jackson, then parse back to JDOM.
+ *
+ *
This bridge is temporary and will be removed in Phase 7B when {@code ModFiler} is updated to
+ * save ByteArrayOutputStream directly.
+ *
+ * @param model the Jackson-annotated model object to serialize
+ * @return JDOM Document
+ */
+ private Document toDocument(Object model) {
+ try {
+ // Serialize model to XML using Jackson
+ String xml = mapper.toXml(model).toString();
+
+ // Parse XML string back to JDOM Document
+ return new SAXBuilder().build(new ByteArrayInputStream(xml.getBytes()));
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to convert Jackson model to JDOM Document", e);
+ }
+ }
+
+ /**
+ * Jackson model for events.xml structure.
+ *
+ *
Represents the XML structure:
+ *
+ *
{@code
+ *
+ *
+ * ...
+ *
+ * }
+ */
+ @JacksonXmlRootElement(localName = "events")
+ static class EventsModel {
+ @JacksonXmlElementWrapper(useWrapping = false)
+ @JacksonXmlProperty(localName = "event")
+ public List events;
+
+ /** Represents a single scheduled event. */
+ static class Event {
+ @JacksonXmlProperty(isAttribute = true)
+ public String script;
+
+ @JacksonXmlProperty(isAttribute = true)
+ public String tick;
+ }
+ }
+}
diff --git a/src/main/java/neon/editor/ModFiler.java b/src/main/java/neon/editor/ModFiler.java
index ca676c1..e99ee1e 100644
--- a/src/main/java/neon/editor/ModFiler.java
+++ b/src/main/java/neon/editor/ModFiler.java
@@ -1,245 +1,274 @@
-/*
- * Neon, a roguelike engine.
- * Copyright (C) 2013 - Maarten Driesen
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package neon.editor;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import javax.swing.JFileChooser;
-import javax.swing.JFrame;
-import javax.swing.JOptionPane;
-import neon.editor.resources.*;
-import neon.resources.RCraft;
-import neon.resources.RCreature;
-import neon.resources.RDungeonTheme;
-import neon.resources.RItem;
-import neon.resources.RMod;
-import neon.resources.RPerson;
-import neon.resources.RRecipe;
-import neon.resources.RRegionTheme;
-import neon.resources.RScript;
-import neon.resources.RSign;
-import neon.resources.RSpell;
-import neon.resources.RTattoo;
-import neon.resources.RTerrain;
-import neon.resources.RZoneTheme;
-import neon.resources.quest.RQuest;
-import neon.systems.files.FileSystem;
-import neon.systems.files.StringTranslator;
-import neon.systems.files.XMLTranslator;
-import org.jdom2.Document;
-import org.jdom2.Element;
-import org.jdom2.JDOMException;
-import org.jdom2.input.SAXBuilder;
-
-public class ModFiler {
- private FileSystem files;
- private DataStore store;
- private Editor editor;
- private JFrame frame;
-
- public ModFiler(JFrame frame, FileSystem files, DataStore store, Editor editor) {
- this.frame = frame;
- this.files = files;
- this.store = store;
- this.editor = editor;
- }
-
- void loadMod() {
- // hacky way to make the filechooser start in the game dir
- JFileChooser chooser = new JFileChooser(new File("neon.ini.xml"));
- chooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
- chooser.setDialogTitle("Choose module");
- if (chooser.showOpenDialog(frame) == JFileChooser.APPROVE_OPTION) {
- load(chooser.getSelectedFile(), true);
- }
- }
-
- void load(File file, boolean active) {
- String path = file.getPath();
- try {
- path = files.mount(path);
- if (!isMod(path)) { // check if this is a mod
- JOptionPane.showMessageDialog(frame, "Selected file is not a valid mod.");
- files.removePath(path);
- } else {
- if (isExtension(path)) { // if extension: load all masters
- Document doc = files.getFile(new XMLTranslator(), path, "main.xml");
- for (Object master : doc.getRootElement().getChildren("master")) {
- String id = ((Element) master).getText();
- Document ini = new Document();
- try { // check in neon.ini.xml which mods exist
- FileInputStream in = new FileInputStream("neon.ini.xml");
- ini = new SAXBuilder().build(in);
- in.close();
- } catch (JDOMException e) {
- }
-
- // check if there is a mod with the correct id
- for (Element mod : ini.getRootElement().getChild("files").getChildren()) {
- if (!mod.getText().equals(path)) { // make sure current mod is not loaded again
- System.out.println(mod.getText() + ", " + path);
- files.mount(mod.getText());
- Document d = files.getFile(new XMLTranslator(), mod.getText(), "main.xml");
- if (d.getRootElement().getAttributeValue("id").equals(id)) {
- store.loadData(mod.getText(), false, false);
- } else {
- files.removePath(mod.getText());
- }
- }
- }
- }
- }
-
- frame.setTitle("Neon Editor: " + path);
- store.loadData(path, active, isExtension(path));
- editor.mapEditor.loadMaps(Editor.resources.getResources(RMap.class), path);
- editor.enableEditing(file.isDirectory());
- frame.pack();
- }
- } catch (IOException e1) {
- JOptionPane.showMessageDialog(frame, "Selected file is not a valid mod.");
- }
- }
-
- public void save() {
- XMLBuilder builder = new XMLBuilder(store);
- RMod active = store.getActive();
- saveFile(new Document(store.getActive().getMainElement()), "main.xml");
- saveFile(new Document(store.getActive().getCCElement()), "cc.xml");
- saveFile(
- builder.getResourceDoc(Editor.resources.getResources(RItem.class), "items", active),
- "objects",
- "items.xml");
- saveFile(
- builder.getListDoc(Editor.resources.getResources(RFaction.class), "factions", active),
- "factions.xml");
- saveFile(
- builder.getListDoc(Editor.resources.getResources(RRecipe.class), "recipes", active),
- "objects",
- "alchemy.xml");
- saveFile(builder.getEventsDoc(), "events.xml");
- saveFile(
- builder.getListDoc(Editor.resources.getResources(RPerson.class), "people", active),
- "objects",
- "npc.xml");
- saveFile(
- builder.getResourceDoc(Editor.resources.getResources(RCreature.class), "monsters", active),
- "objects",
- "monsters.xml");
- saveFile(
- builder.getResourceDoc(Editor.resources.getResources(RSpell.class), "spells", active),
- "spells.xml");
- saveFile(
- builder.getListDoc(Editor.resources.getResources(RTerrain.class), "terrain", active),
- "terrain.xml");
- saveFile(
- builder.getListDoc(Editor.resources.getResources(RCraft.class), "items", active),
- "objects",
- "crafting.xml");
- saveFile(
- builder.getListDoc(Editor.resources.getResources(RSign.class), "signs", active),
- "signs.xml");
- saveFile(
- builder.getListDoc(Editor.resources.getResources(RTattoo.class), "tattoos", active),
- "tattoos.xml");
- saveFile(
- builder.getListDoc(Editor.resources.getResources(RZoneTheme.class), "themes", active),
- "themes",
- "zones.xml");
- saveFile(
- builder.getListDoc(Editor.resources.getResources(RDungeonTheme.class), "themes", active),
- "themes",
- "dungeons.xml");
- saveFile(
- builder.getListDoc(Editor.resources.getResources(RRegionTheme.class), "themes", active),
- "themes",
- "regions.xml");
- saveMaps();
- saveQuests();
- saveScripts();
- }
-
- private void saveMaps() {
- for (String name : files.listFiles(store.getActive().getPath()[0], "maps")) {
- String map =
- name.substring(name.lastIndexOf(File.separator) + 1, name.length() - 4); // -4 for ".xml"
- if (Editor.resources.getResource(map, "maps") == null) {
- files.delete(name);
- }
- }
- for (RMap map : editor.mapEditor.getActiveMaps()) {
- Document doc = new Document().setRootElement(map.toElement());
- saveFile(doc, "maps", map.id + ".xml");
- }
- }
-
- private void saveQuests() {
- for (String name : files.listFiles(store.getActive().getPath()[0], "quests")) {
- String quest =
- name.substring(name.lastIndexOf(File.separator) + 1, name.length() - 4); // -4 for ".xml"
- if (Editor.resources.getResource(quest, "quest") == null) {
- files.delete(name);
- }
- }
- for (RQuest quest : Editor.resources.getResources(RQuest.class)) {
- saveFile(new Document(quest.toElement()), "quests", quest.id + ".xml");
- }
- }
-
- private void saveScripts() {
- for (String name : files.listFiles(store.getActive().getPath()[0], "scripts")) {
- String script =
- name.substring(name.lastIndexOf(File.separator) + 1, name.length() - 3); // -3 for ".js"
- if (!store.getScripts().containsKey(script)) {
- files.delete(name);
- }
- }
- for (RScript script : store.getScripts().values()) {
- saveFile(script.script, "scripts", script.id + ".js");
- }
- }
-
- private void saveFile(String text, String... file) {
- String[] fullPath = new String[file.length + 1];
- System.arraycopy(file, 0, fullPath, 1, file.length);
- fullPath[0] = store.getActive().getPath()[0];
- files.saveFile(text, new StringTranslator(), fullPath);
- }
-
- private void saveFile(Document doc, String... file) {
- String[] fullPath = new String[file.length + 1];
- System.arraycopy(file, 0, fullPath, 1, file.length);
- fullPath[0] = store.getActive().getPath()[0];
- files.saveFile(doc, new XMLTranslator(), fullPath);
- }
-
- private boolean isExtension(String path) {
- Document doc = files.getFile(new XMLTranslator(), path, "main.xml");
- return doc.getRootElement().getName().equals("extension");
- }
-
- private boolean isMod(String path) {
- try { // main.xml must exist and be valid xml
- return files.getFile(new XMLTranslator(), path, "main.xml") != null;
- } catch (NullPointerException e) {
- return false;
- }
- }
-}
+/*
+ * Neon, a roguelike engine.
+ * Copyright (C) 2013 - Maarten Driesen
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package neon.editor;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import javax.swing.JFileChooser;
+import javax.swing.JFrame;
+import javax.swing.JOptionPane;
+import neon.editor.resources.*;
+import neon.maps.model.DungeonModel;
+import neon.maps.model.WorldModel;
+import neon.resources.RCraft;
+import neon.resources.RCreature;
+import neon.resources.RDungeonTheme;
+import neon.resources.RItem;
+import neon.resources.RMod;
+import neon.resources.RPerson;
+import neon.resources.RRecipe;
+import neon.resources.RRegionTheme;
+import neon.resources.RScript;
+import neon.resources.RSign;
+import neon.resources.RSpell;
+import neon.resources.RTattoo;
+import neon.resources.RTerrain;
+import neon.resources.RZoneTheme;
+import neon.resources.quest.RQuest;
+import neon.systems.files.FileSystem;
+import neon.systems.files.JacksonMapper;
+import neon.systems.files.StringTranslator;
+import neon.systems.files.XMLTranslator;
+import org.jdom2.Document;
+import org.jdom2.Element;
+import org.jdom2.JDOMException;
+import org.jdom2.input.SAXBuilder;
+
+public class ModFiler {
+ private FileSystem files;
+ private DataStore store;
+ private Editor editor;
+ private JFrame frame;
+
+ public ModFiler(JFrame frame, FileSystem files, DataStore store, Editor editor) {
+ this.frame = frame;
+ this.files = files;
+ this.store = store;
+ this.editor = editor;
+ }
+
+ void loadMod() {
+ // hacky way to make the filechooser start in the game dir
+ JFileChooser chooser = new JFileChooser(new File("neon.ini.xml"));
+ chooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
+ chooser.setDialogTitle("Choose module");
+ if (chooser.showOpenDialog(frame) == JFileChooser.APPROVE_OPTION) {
+ load(chooser.getSelectedFile(), true);
+ }
+ }
+
+ void load(File file, boolean active) {
+ String path = file.getPath();
+ try {
+ path = files.mount(path);
+ if (!isMod(path)) { // check if this is a mod
+ JOptionPane.showMessageDialog(frame, "Selected file is not a valid mod.");
+ files.removePath(path);
+ } else {
+ if (isExtension(path)) { // if extension: load all masters
+ Document doc = files.getFile(new XMLTranslator(), path, "main.xml");
+ for (Object master : doc.getRootElement().getChildren("master")) {
+ String id = ((Element) master).getText();
+ Document ini = new Document();
+ try { // check in neon.ini.xml which mods exist
+ FileInputStream in = new FileInputStream("neon.ini.xml");
+ ini = new SAXBuilder().build(in);
+ in.close();
+ } catch (JDOMException e) {
+ }
+
+ // check if there is a mod with the correct id
+ for (Element mod : ini.getRootElement().getChild("files").getChildren()) {
+ if (!mod.getText().equals(path)) { // make sure current mod is not loaded again
+ System.out.println(mod.getText() + ", " + path);
+ files.mount(mod.getText());
+ Document d = files.getFile(new XMLTranslator(), mod.getText(), "main.xml");
+ if (d.getRootElement().getAttributeValue("id").equals(id)) {
+ store.loadData(mod.getText(), false, false);
+ } else {
+ files.removePath(mod.getText());
+ }
+ }
+ }
+ }
+ }
+
+ frame.setTitle("Neon Editor: " + path);
+ store.loadData(path, active, isExtension(path));
+ editor.mapEditor.loadMaps(Editor.resources.getResources(RMap.class), path);
+ editor.enableEditing(file.isDirectory());
+ frame.pack();
+ }
+ } catch (IOException e1) {
+ JOptionPane.showMessageDialog(frame, "Selected file is not a valid mod.");
+ }
+ }
+
+ public void save() {
+ JacksonXmlBuilder builder = new JacksonXmlBuilder(store);
+ RMod active = store.getActive();
+ saveFile(new Document(store.getActive().getMainElement()), "main.xml");
+ saveFile(new Document(store.getActive().getCCElement()), "cc.xml");
+ saveFile(
+ builder.getResourceDoc(Editor.resources.getResources(RItem.class), "items", active),
+ "objects",
+ "items.xml");
+ saveFile(
+ builder.getListDoc(Editor.resources.getResources(RFaction.class), "factions", active),
+ "factions.xml");
+ saveFile(
+ builder.getListDoc(Editor.resources.getResources(RRecipe.class), "recipes", active),
+ "objects",
+ "alchemy.xml");
+ saveFile(builder.getEventsDoc(), "events.xml");
+ saveFile(
+ builder.getListDoc(Editor.resources.getResources(RPerson.class), "people", active),
+ "objects",
+ "npc.xml");
+ saveFile(
+ builder.getResourceDoc(Editor.resources.getResources(RCreature.class), "monsters", active),
+ "objects",
+ "monsters.xml");
+ saveFile(
+ builder.getResourceDoc(Editor.resources.getResources(RSpell.class), "spells", active),
+ "spells.xml");
+ saveFile(
+ builder.getListDoc(Editor.resources.getResources(RTerrain.class), "terrain", active),
+ "terrain.xml");
+ saveFile(
+ builder.getListDoc(Editor.resources.getResources(RCraft.class), "items", active),
+ "objects",
+ "crafting.xml");
+ saveFile(
+ builder.getListDoc(Editor.resources.getResources(RSign.class), "signs", active),
+ "signs.xml");
+ saveFile(
+ builder.getListDoc(Editor.resources.getResources(RTattoo.class), "tattoos", active),
+ "tattoos.xml");
+ saveFile(
+ builder.getListDoc(Editor.resources.getResources(RZoneTheme.class), "themes", active),
+ "themes",
+ "zones.xml");
+ saveFile(
+ builder.getListDoc(Editor.resources.getResources(RDungeonTheme.class), "themes", active),
+ "themes",
+ "dungeons.xml");
+ saveFile(
+ builder.getListDoc(Editor.resources.getResources(RRegionTheme.class), "themes", active),
+ "themes",
+ "regions.xml");
+ saveMaps();
+ saveQuests();
+ saveScripts();
+ }
+
+ /**
+ * Saves all maps using Jackson XML serialization.
+ *
+ *
NOTE (Phase 6 - Partial Migration): Maps use Jackson via toWorldModel()/toDungeonModel()
+ * (migrated in Phase 2D). Other resources still use toElement() bridge and XMLTranslator. Full
+ * migration of resource saving to Jackson deferred to Phase 7.
+ */
+ private void saveMaps() {
+ // Delete maps that no longer exist
+ for (String name : files.listFiles(store.getActive().getPath()[0], "maps")) {
+ String map =
+ name.substring(name.lastIndexOf(File.separator) + 1, name.length() - 4); // -4 for ".xml"
+ if (Editor.resources.getResource(map, "maps") == null) {
+ files.delete(name);
+ }
+ }
+
+ // Save maps using Jackson serialization
+ JacksonMapper mapper = new JacksonMapper();
+ for (RMap map : editor.mapEditor.getActiveMaps()) {
+ try {
+ ByteArrayOutputStream out;
+ if (map.isDungeon()) {
+ DungeonModel model = map.toDungeonModel();
+ out = mapper.toXml(model);
+ } else {
+ WorldModel model = map.toWorldModel();
+ out = mapper.toXml(model);
+ }
+ // Convert ByteArrayOutputStream to String for saveFile
+ String xml = out.toString("UTF-8");
+ saveFile(xml, "maps", map.id + ".xml");
+ } catch (Exception e) {
+ System.err.println("Failed to save map: " + map.id);
+ e.printStackTrace();
+ }
+ }
+ }
+
+ private void saveQuests() {
+ for (String name : files.listFiles(store.getActive().getPath()[0], "quests")) {
+ String quest =
+ name.substring(name.lastIndexOf(File.separator) + 1, name.length() - 4); // -4 for ".xml"
+ if (Editor.resources.getResource(quest, "quest") == null) {
+ files.delete(name);
+ }
+ }
+ for (RQuest quest : Editor.resources.getResources(RQuest.class)) {
+ saveFile(new Document(quest.toElement()), "quests", quest.id + ".xml");
+ }
+ }
+
+ private void saveScripts() {
+ for (String name : files.listFiles(store.getActive().getPath()[0], "scripts")) {
+ String script =
+ name.substring(name.lastIndexOf(File.separator) + 1, name.length() - 3); // -3 for ".js"
+ if (!store.getScripts().containsKey(script)) {
+ files.delete(name);
+ }
+ }
+ for (RScript script : store.getScripts().values()) {
+ saveFile(script.script, "scripts", script.id + ".js");
+ }
+ }
+
+ private void saveFile(String text, String... file) {
+ String[] fullPath = new String[file.length + 1];
+ System.arraycopy(file, 0, fullPath, 1, file.length);
+ fullPath[0] = store.getActive().getPath()[0];
+ files.saveFile(text, new StringTranslator(), fullPath);
+ }
+
+ private void saveFile(Document doc, String... file) {
+ String[] fullPath = new String[file.length + 1];
+ System.arraycopy(file, 0, fullPath, 1, file.length);
+ fullPath[0] = store.getActive().getPath()[0];
+ files.saveFile(doc, new XMLTranslator(), fullPath);
+ }
+
+ private boolean isExtension(String path) {
+ Document doc = files.getFile(new XMLTranslator(), path, "main.xml");
+ return doc.getRootElement().getName().equals("extension");
+ }
+
+ private boolean isMod(String path) {
+ try { // main.xml must exist and be valid xml
+ return files.getFile(new XMLTranslator(), path, "main.xml") != null;
+ } catch (NullPointerException e) {
+ return false;
+ }
+ }
+}
diff --git a/src/main/java/neon/editor/XMLBuilder.java b/src/main/java/neon/editor/XMLBuilder.java
index b7ad47d..3f61c13 100644
--- a/src/main/java/neon/editor/XMLBuilder.java
+++ b/src/main/java/neon/editor/XMLBuilder.java
@@ -25,6 +25,14 @@
import org.jdom2.Document;
import org.jdom2.Element;
+/**
+ * Builds JDOM Documents for editor save operations using JDOM construction.
+ *
+ * @deprecated Replaced by {@link JacksonXmlBuilder} in Phase 7A. Use JacksonXmlBuilder for all new
+ * code. This class will be removed in Phase 7B when toElement() methods are eliminated from
+ * resources.
+ */
+@Deprecated(forRemoval = true)
public class XMLBuilder {
private DataStore store;
diff --git a/src/main/java/neon/editor/editors/NPCEditor.java b/src/main/java/neon/editor/editors/NPCEditor.java
index d755cb2..67ee012 100644
--- a/src/main/java/neon/editor/editors/NPCEditor.java
+++ b/src/main/java/neon/editor/editors/NPCEditor.java
@@ -1,667 +1,689 @@
-/*
- * Neon, a roguelike engine.
- * Copyright (C) 2013 - Maarten Driesen
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package neon.editor.editors;
-
-import java.awt.*;
-import java.awt.event.*;
-import java.text.NumberFormat;
-import java.util.*;
-import javax.swing.*;
-import javax.swing.border.*;
-import javax.swing.event.*;
-import neon.editor.*;
-import neon.editor.help.HelpLabels;
-import neon.editor.resources.RFaction;
-import neon.entities.property.Skill;
-import neon.resources.*;
-import neon.resources.RSpell.SpellType;
-import org.jdom2.Element;
-
-public class NPCEditor extends ObjectEditor implements MouseListener {
- private RPerson data;
- private JList spellList, itemList, destList;
- private JTextField nameField;
- private JComboBox factionBox;
- private JComboBox raceBox;
- private JComboBox aiTypeBox;
- private JSpinner aggressionSpinner, confidenceSpinner, factionSpinner;
- private JFormattedTextField rangeField, destX, destY, destCost, skillField;
- private HashMap skills;
- private Set trainedSkills;
- private HashMap joinedFactions;
- private HashMap destMap;
- private JCheckBox spellBox,
- skillBox,
- tradeBox,
- travelBox,
- trainBox,
- spellMakerBox,
- factionCheckBox,
- potionBox,
- healerBox,
- tattooBox;
- private JComboBox skillComboBox;
- private DefaultListModel destListModel, spellListModel, itemListModel;
- private Skill currentSkill;
- private Element currentDest;
- private ArrayList spells;
-
- public NPCEditor(JFrame parent, RPerson data) {
- super(parent, "NPC Editor: " + data.id);
- this.data = data;
-
- spells = new ArrayList();
- for (RSpell spell : Editor.resources.getResources(RSpell.class)) {
- if (spell.type == SpellType.SPELL) {
- spells.add(spell.id);
- }
- }
-
- JPanel npcProps = new JPanel();
- npcProps.setBorder(new TitledBorder("Properties"));
- BoxLayout propLayout = new BoxLayout(npcProps, BoxLayout.PAGE_AXIS);
- npcProps.setLayout(propLayout);
-
- JPanel generalPanel = new JPanel();
- generalPanel.setBorder(new TitledBorder("General"));
- nameField = new JTextField(10);
- raceBox = new JComboBox(Editor.resources.getResources(RCreature.class));
- generalPanel.add(new JLabel("Name: "));
- generalPanel.add(nameField);
- generalPanel.add(new JLabel(" "));
- generalPanel.add(HelpLabels.getNameHelpLabel());
- generalPanel.add(new JLabel(" "));
- generalPanel.add(new JLabel("Species: "));
- generalPanel.add(raceBox);
- generalPanel.add(new JLabel(" "));
- generalPanel.add(HelpLabels.getRaceHelpLabel());
- generalPanel.setMaximumSize(
- new Dimension(generalPanel.getMaximumSize().width, generalPanel.getPreferredSize().height));
-
- JPanel aiPanel = new JPanel();
- GroupLayout layout = new GroupLayout(aiPanel);
- aiPanel.setLayout(layout);
- layout.setAutoCreateGaps(true);
- JLabel aiTypeLabel = new JLabel("Type: ");
- JLabel aggressionLabel = new JLabel("Aggression: ");
- JLabel confidenceLabel = new JLabel("Confidence: ");
- JLabel rangeLabel = new JLabel("Territory: ");
- aiTypeBox = new JComboBox(RCreature.AIType.values());
- aggressionSpinner = new JSpinner(new SpinnerNumberModel(0, 0, 100, 1));
- confidenceSpinner = new JSpinner(new SpinnerNumberModel(0, 0, 100, 1));
- rangeField = new JFormattedTextField(NumberFormat.getIntegerInstance());
- rangeField.setValue(0);
- JLabel aiHelpLabel = HelpLabels.getAITypeHelpLabel();
- JLabel confidenceHelpLabel = HelpLabels.getConfidenceHelpLabel();
- JLabel aggressionHelpLabel = HelpLabels.getAggressionHelpLabel();
- JLabel rangeHelpLabel = HelpLabels.getRangeHelpLabel();
- aiPanel.setMaximumSize(
- new Dimension(generalPanel.getMaximumSize().width, generalPanel.getPreferredSize().height));
- layout.setVerticalGroup(
- layout
- .createSequentialGroup()
- .addGroup(
- layout
- .createParallelGroup(GroupLayout.Alignment.BASELINE)
- .addComponent(aiTypeLabel)
- .addComponent(aiTypeBox)
- .addComponent(aiHelpLabel)
- .addComponent(aggressionLabel)
- .addComponent(aggressionSpinner)
- .addComponent(aggressionHelpLabel))
- .addGroup(
- layout
- .createParallelGroup(GroupLayout.Alignment.BASELINE)
- .addComponent(confidenceLabel)
- .addComponent(confidenceSpinner)
- .addComponent(confidenceHelpLabel)
- .addComponent(rangeLabel)
- .addComponent(rangeField)
- .addComponent(rangeHelpLabel)));
- layout.setHorizontalGroup(
- layout
- .createSequentialGroup()
- .addGroup(
- layout
- .createParallelGroup(GroupLayout.Alignment.LEADING)
- .addComponent(
- aiTypeLabel,
- GroupLayout.PREFERRED_SIZE,
- GroupLayout.DEFAULT_SIZE,
- GroupLayout.PREFERRED_SIZE)
- .addComponent(confidenceLabel))
- .addGroup(
- layout
- .createParallelGroup(GroupLayout.Alignment.LEADING, false)
- .addComponent(
- aiTypeBox,
- GroupLayout.PREFERRED_SIZE,
- GroupLayout.DEFAULT_SIZE,
- GroupLayout.PREFERRED_SIZE)
- .addComponent(confidenceSpinner))
- .addGap(10)
- .addGroup(
- layout
- .createParallelGroup(GroupLayout.Alignment.LEADING)
- .addComponent(aiHelpLabel)
- .addComponent(confidenceHelpLabel))
- .addGap(10)
- .addGroup(
- layout
- .createParallelGroup(GroupLayout.Alignment.LEADING)
- .addComponent(
- aggressionLabel,
- GroupLayout.PREFERRED_SIZE,
- GroupLayout.DEFAULT_SIZE,
- GroupLayout.PREFERRED_SIZE)
- .addComponent(rangeLabel))
- .addGroup(
- layout
- .createParallelGroup(GroupLayout.Alignment.LEADING, false)
- .addComponent(
- aggressionSpinner,
- GroupLayout.PREFERRED_SIZE,
- GroupLayout.DEFAULT_SIZE,
- GroupLayout.PREFERRED_SIZE)
- .addComponent(rangeField))
- .addGap(10)
- .addGroup(
- layout
- .createParallelGroup(GroupLayout.Alignment.LEADING)
- .addComponent(aggressionHelpLabel)
- .addComponent(rangeHelpLabel)));
- aiPanel.setBorder(new TitledBorder("AI"));
-
- JTabbedPane servicePane = new JTabbedPane();
- servicePane.setBorder(new TitledBorder("Services"));
-
- JPanel spellPanel = new JPanel(new BorderLayout());
- spellBox = new JCheckBox("Spell trader");
- spellMakerBox = new JCheckBox("Spell maker");
- healerBox = new JCheckBox("Healer");
- JPanel spellBoxPanel = new JPanel();
- spellBoxPanel.add(spellBox, BorderLayout.PAGE_START);
- spellBoxPanel.add(spellMakerBox, BorderLayout.CENTER);
- spellBoxPanel.add(healerBox, BorderLayout.PAGE_END);
- spellPanel.add(spellBoxPanel, BorderLayout.PAGE_START);
- spellListModel = new DefaultListModel();
- spellList = new JList(spellListModel);
- spellList.addMouseListener(this);
- JScrollPane spellScroller = new JScrollPane(spellList);
- spellPanel.add(spellScroller, BorderLayout.CENTER);
- servicePane.add(spellPanel, "Magic");
-
- JPanel tradePanel = new JPanel(new BorderLayout());
- tradeBox = new JCheckBox("Trader");
- tradeBox.setHorizontalAlignment(SwingConstants.CENTER);
- tradePanel.add(tradeBox, BorderLayout.PAGE_START);
- itemListModel = new DefaultListModel();
- itemList = new JList(itemListModel);
- itemList.addMouseListener(this);
- JScrollPane itemScroller = new JScrollPane(itemList);
- tradePanel.add(itemScroller, BorderLayout.CENTER);
- servicePane.add(tradePanel, "Trade");
-
- JPanel skillPanel = new JPanel(new BorderLayout());
- skills = new HashMap();
- trainedSkills = new HashSet();
- trainBox = new JCheckBox("Skill trainer");
- trainBox.setHorizontalAlignment(SwingConstants.CENTER);
- skillPanel.add(trainBox, BorderLayout.PAGE_START);
- JPanel skillSubPanel = new JPanel();
- skillSubPanel.add(new JLabel("Skills: "));
- skillComboBox = new JComboBox(Skill.values());
- skillComboBox.addActionListener(new SkillListListener());
- skillSubPanel.add(skillComboBox);
- skillField = new JFormattedTextField(NumberFormat.getIntegerInstance());
- skillField.setColumns(3);
- skillSubPanel.add(skillField);
- skillBox = new JCheckBox("Trainable?");
- skillSubPanel.add(skillBox);
- skillPanel.add(skillSubPanel);
- servicePane.add(skillPanel, "Training");
-
- JPanel travelPanel = new JPanel(new BorderLayout());
- destMap = new HashMap();
- travelBox = new JCheckBox("Travel agent");
- travelBox.setHorizontalAlignment(SwingConstants.CENTER);
- travelPanel.add(travelBox, BorderLayout.PAGE_START);
- destListModel = new DefaultListModel();
- destList = new JList(destListModel);
- destList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
- destList.addMouseListener(this);
- destList.addListSelectionListener(new DestListAction());
- JScrollPane destScroller = new JScrollPane(destList);
- travelPanel.add(destScroller, BorderLayout.CENTER);
- JPanel destPanel = new JPanel();
- destPanel.add(new JLabel("x: "));
- destX = new JFormattedTextField(NumberFormat.getIntegerInstance());
- destX.setColumns(5);
- destPanel.add(destX);
- destPanel.add(new JLabel("y: "));
- destY = new JFormattedTextField(NumberFormat.getIntegerInstance());
- destY.setColumns(5);
- destPanel.add(destY);
- destPanel.add(new JLabel("price: "));
- destCost = new JFormattedTextField(NumberFormat.getIntegerInstance());
- destCost.setColumns(5);
- destPanel.add(destCost);
- travelPanel.add(destPanel, BorderLayout.PAGE_END);
- servicePane.add(travelPanel, "Travel");
-
- JPanel otherPanel = new JPanel();
- potionBox = new JCheckBox("Potion maker");
- otherPanel.add(potionBox);
- tattooBox = new JCheckBox("Tattoo artist");
- otherPanel.add(tattooBox);
- servicePane.add(otherPanel, "Other");
-
- // factions
- JPanel factionPanel = new JPanel();
-
- joinedFactions = new HashMap();
- FactionListListener fl = new FactionListListener();
-
- factionBox = new JComboBox(Editor.resources.getResources(RFaction.class));
- factionBox.addActionListener(fl);
- factionPanel.add(factionBox);
- factionCheckBox = new JCheckBox();
- factionCheckBox.addItemListener(fl);
- factionPanel.add(factionCheckBox);
- factionSpinner = new JSpinner(new SpinnerNumberModel(0, 0, 100, 1));
- factionSpinner.addChangeListener(fl);
- factionPanel.add(factionSpinner);
- factionPanel.add(new JLabel(" "));
- factionPanel.add(HelpLabels.getFactionHelpLabel());
- factionPanel.setBorder(new TitledBorder("Factions"));
-
- npcProps.add(generalPanel);
- npcProps.add(aiPanel);
- npcProps.add(factionPanel);
- npcProps.add(servicePane);
-
- frame.add(new JScrollPane(npcProps), BorderLayout.CENTER);
- }
-
- protected void load() {
- nameField.setText(data.name);
- RCreature species = (RCreature) Editor.resources.getResource(data.species);
- raceBox.setSelectedItem(species);
-
- for (String s : data.factions.keySet()) {
- joinedFactions.put(s, data.factions.get(s));
- }
- factionCheckBox.setSelected(joinedFactions.containsKey(factionBox.getSelectedItem()));
- if (joinedFactions.containsKey(factionBox.getSelectedItem())) {
- factionSpinner.setValue(data.factions.get(factionBox.getSelectedItem()));
- factionSpinner.setEnabled(true);
- } else {
- factionSpinner.setEnabled(false);
- factionSpinner.setValue(0);
- }
-
- if (data.aiType != null) {
- aiTypeBox.setSelectedItem(data.aiType);
- } else if (species != null) {
- aiTypeBox.setSelectedItem(species.aiType);
- }
- if (data.aiRange > -1) {
- rangeField.setValue(data.aiRange);
- } else if (species != null) {
- rangeField.setValue(species.aiRange);
- }
- if (data.aiAggr > -1) {
- aggressionSpinner.setValue(data.aiAggr);
- } else if (species != null) {
- rangeField.setValue(species.aiAggr);
- }
- if (data.aiConf > -1) {
- confidenceSpinner.setValue(data.aiConf);
- } else if (species != null) {
- rangeField.setValue(species.aiConf);
- }
-
- skills = data.skills;
- if (skills.containsKey(skillComboBox.getSelectedItem())) {
- skillField.setValue(skills.get(skillComboBox.getSelectedItem()));
- } else {
- skillField.setValue(0);
- }
-
- for (String rs : data.spells) {
- spellListModel.addElement(rs);
- }
-
- for (String i : data.items) {
- itemListModel.addElement(i);
- }
-
- for (Element service : data.services) {
- if (service.getAttributeValue("id").equals("trade")) {
- tradeBox.setSelected(true);
- } else if (service.getAttributeValue("id").equals("travel")) {
- travelBox.setSelected(true);
- for (Element d : service.getChildren()) {
- destListModel.addElement(d.getAttributeValue("name"));
- destMap.put(d.getAttributeValue("name"), d);
- }
- } else if (service.getAttributeValue("id").equals("training")) {
- trainBox.setSelected(true);
- for (Element s : service.getChildren()) {
- trainedSkills.add(Skill.valueOf(s.getText().toUpperCase()));
- }
- skillBox.setSelected(trainedSkills.contains(skillComboBox.getSelectedItem()));
- } else if (service.getAttributeValue("id").equals("spells")) {
- spellBox.setSelected(true);
- } else if (service.getAttributeValue("id").equals("spellmaker")) {
- spellMakerBox.setSelected(true);
- } else if (service.getAttributeValue("id").equals("healer")) {
- healerBox.setSelected(true);
- } else if (service.getAttributeValue("id").equals("alchemy")) {
- potionBox.setSelected(true);
- } else if (service.getAttributeValue("id").equals("tattoo")) {
- tattooBox.setSelected(true);
- }
- }
- }
-
- public void mouseExited(MouseEvent e) {}
-
- public void mouseEntered(MouseEvent e) {}
-
- public void mouseReleased(MouseEvent e) {}
-
- public void mousePressed(MouseEvent e) {}
-
- public void mouseClicked(MouseEvent e) {
- if (e.getButton() == MouseEvent.BUTTON3) {
- if (e.getComponent() == itemList) {
- JPopupMenu menu = new JPopupMenu();
- menu.add(new ItemListAction("Add item"));
- menu.add(new ItemListAction("Delete item"));
- menu.show(e.getComponent(), e.getX(), e.getY());
- itemList.setSelectedIndex(itemList.locationToIndex(e.getPoint()));
- } else if (e.getComponent() == spellList) {
- JPopupMenu menu = new JPopupMenu();
- menu.add(new SpellListAction("Add spell"));
- menu.add(new SpellListAction("Delete spell"));
- menu.show(e.getComponent(), e.getX(), e.getY());
- spellList.setSelectedIndex(spellList.locationToIndex(e.getPoint()));
- } else if (e.getComponent() == destList) {
- JPopupMenu menu = new JPopupMenu();
- menu.add(new DestListAction("Add destination"));
- menu.add(new DestListAction("Delete destination"));
- menu.show(e.getComponent(), e.getX(), e.getY());
- destList.setSelectedIndex(destList.locationToIndex(e.getPoint()));
- }
- }
- }
-
- protected void save() {
- data.name = nameField.getText();
- RCreature species = raceBox.getItemAt(raceBox.getSelectedIndex());
- data.species = species.id;
-
- if (species.aiType.equals(aiTypeBox.getItemAt(aiTypeBox.getSelectedIndex()))) {
- data.aiType = null;
- } else {
- data.aiType = aiTypeBox.getItemAt(aiTypeBox.getSelectedIndex());
- }
- if (species.aiRange == (Integer) rangeField.getValue()) {
- data.aiRange = -1;
- } else {
- data.aiRange = (Integer) rangeField.getValue();
- }
- if (species.aiAggr == (Integer) aggressionSpinner.getValue()) {
- data.aiAggr = -1;
- } else {
- data.aiAggr = (Integer) aggressionSpinner.getValue();
- }
- if (species.aiConf == (Integer) confidenceSpinner.getValue()) {
- data.aiConf = -1;
- } else {
- data.aiConf = (Integer) confidenceSpinner.getValue();
- }
-
- data.factions.clear();
- for (String f : joinedFactions.keySet()) {
- data.factions.put(f, joinedFactions.get(f));
- }
-
- data.services.clear();
- if (tradeBox.isSelected()) {
- data.services.add(new Element("service").setAttribute("id", "trade"));
- }
- data.items.clear();
- for (Enumeration e = itemListModel.elements(); e.hasMoreElements(); ) {
- data.items.add(e.nextElement());
- }
-
- if (spellMakerBox.isSelected()) {
- data.services.add(new Element("service").setAttribute("id", "spellmaker"));
- }
- if (healerBox.isSelected()) {
- data.services.add(new Element("service").setAttribute("id", "healer"));
- }
- if (spellBox.isSelected()) {
- data.services.add(new Element("service").setAttribute("id", "spells"));
- }
- for (Enumeration e = spellListModel.elements(); e.hasMoreElements(); ) {
- data.spells.add(e.nextElement());
- }
-
- if (trainBox.isSelected()) {
- Element training = new Element("service");
- training.setAttribute("id", "training");
- data.services.add(training);
- for (Skill s : trainedSkills) {
- training.addContent(new Element("skill").setText(s.toString()));
- }
- }
- data.skills.clear();
- for (Skill s : skills.keySet()) {
- if (skills.get(s) != null && !skills.get(s).equals(0)) {
- skills.put(s, skills.get(s));
- }
- }
-
- if (travelBox.isSelected()) {
- Element travel = new Element("service");
- travel.setAttribute("id", "travel");
- // a bit of magic to still get the last modified value into destMap
- if (currentDest != null) {
- currentDest.setAttribute("x", destX.getValue().toString());
- currentDest.setAttribute("y", destY.getValue().toString());
- currentDest.setAttribute("cost", destCost.getValue().toString());
- }
- // magic done
- for (Element d : destMap.values()) {
- d.detach();
- travel.addContent(d);
- }
- data.services.add(travel);
- }
-
- if (potionBox.isSelected()) {
- data.services.add(new Element("service").setAttribute("id", "alchemy"));
- }
-
- if (tattooBox.isSelected()) {
- data.services.add(new Element("service").setAttribute("id", "tattoo"));
- }
-
- data.setPath(Editor.getStore().getActive().get("id"));
- }
-
- private class SkillListListener implements ActionListener {
- public void actionPerformed(ActionEvent e) {
- try {
- skills.put(currentSkill, Integer.parseInt(skillField.getText()));
- } catch (NumberFormatException f) {
- }
- if (skillBox.isSelected()) {
- trainedSkills.add(currentSkill);
- } else {
- trainedSkills.remove(currentSkill);
- }
-
- Skill skill = (Skill) skillComboBox.getSelectedItem();
- if (skills.containsKey(skill)) {
- skillField.setText(skills.get(skill).toString());
- } else {
- skillField.setText("0");
- }
- skillBox.setSelected(trainedSkills.contains(skill));
- currentSkill = skill;
- }
- }
-
- private class FactionListListener implements ActionListener, ItemListener, ChangeListener {
- public void actionPerformed(ActionEvent e) {
- String faction = factionBox.getSelectedItem().toString();
- factionCheckBox.setSelected(joinedFactions.containsKey(faction));
- if (joinedFactions.containsKey(faction)) {
- factionSpinner.setEnabled(true);
- factionSpinner.setValue(joinedFactions.get(faction));
- } else {
- factionSpinner.setEnabled(false);
- factionSpinner.setValue(0);
- }
- }
-
- public void itemStateChanged(ItemEvent e) {
- String faction = factionBox.getSelectedItem().toString();
- if (e.getSource() == factionCheckBox) {
- if (factionCheckBox.isSelected()) {
- if (!joinedFactions.containsKey(faction)) {
- joinedFactions.put(faction, (Integer) factionSpinner.getValue());
- }
- factionSpinner.setEnabled(true);
- } else {
- joinedFactions.remove(faction);
- factionSpinner.setEnabled(false);
- }
- }
- }
-
- public void stateChanged(ChangeEvent ce) {
- String faction = factionBox.getSelectedItem().toString();
- if (joinedFactions.containsKey(faction)) {
- joinedFactions.put(faction, (Integer) factionSpinner.getValue());
- System.out.println("state.factions.put: " + (Integer) factionSpinner.getValue());
- }
- }
- }
-
- @SuppressWarnings("serial")
- private class SpellListAction extends AbstractAction {
- public SpellListAction(String name) {
- super(name);
- }
-
- public void actionPerformed(ActionEvent e) {
- if (e.getActionCommand().equals("Add spell")) {
- String s =
- (String)
- JOptionPane.showInputDialog(
- frame,
- "New spell:",
- "New spell",
- JOptionPane.PLAIN_MESSAGE,
- null,
- spells.toArray(),
- 0);
- if (s != null) {
- spellListModel.addElement(s);
- }
- } else if (e.getActionCommand().equals("Delete spell")) {
- spellListModel.remove(spellList.getSelectedIndex());
- }
- }
- }
-
- @SuppressWarnings("serial")
- private class ItemListAction extends AbstractAction {
- public ItemListAction(String name) {
- super(name);
- }
-
- public void actionPerformed(ActionEvent e) {
- if (e.getActionCommand().equals("Add item")) {
- Object[] items = Editor.resources.getResources(RItem.class).toArray();
- String s =
- (String)
- JOptionPane.showInputDialog(
- frame, "Add item:", "Add item", JOptionPane.PLAIN_MESSAGE, null, items, 0);
- if (s != null) {
- itemListModel.addElement(s);
- }
- } else if (e.getActionCommand().equals("Delete item")) {
- itemListModel.remove(itemList.getSelectedIndex());
- }
- }
- }
-
- @SuppressWarnings("serial")
- private class DestListAction extends AbstractAction implements ListSelectionListener {
- public DestListAction() {
- super();
- }
-
- public DestListAction(String name) {
- super(name);
- }
-
- public void valueChanged(ListSelectionEvent e) {
- try { // in case npc is not a travel agent
- if (currentDest != null) {
- currentDest.setAttribute("x", destX.getValue().toString());
- currentDest.setAttribute("y", destY.getValue().toString());
- currentDest.setAttribute("cost", destCost.getValue().toString());
- }
- currentDest = destMap.get(destList.getSelectedValue());
- destX.setValue(Integer.parseInt(currentDest.getAttributeValue("x")));
- destY.setValue(Integer.parseInt(currentDest.getAttributeValue("y")));
- destCost.setValue(Integer.parseInt(currentDest.getAttributeValue("cost")));
- } catch (NullPointerException f) {
- }
- }
-
- public void actionPerformed(ActionEvent e) {
- if (e.getActionCommand().equals("Add destination")) {
- String s =
- (String)
- JOptionPane.showInputDialog(
- frame, "New destination:", "New destination", JOptionPane.QUESTION_MESSAGE);
- if ((s != null) && (s.length() > 0)) {
- destListModel.addElement(s);
- Element dest = new Element("dest");
- dest.setAttribute("name", s);
- dest.setAttribute("x", "0");
- dest.setAttribute("y", "0");
- dest.setAttribute("cost", "0");
- destMap.put(s, dest);
- }
- } else if (e.getActionCommand().equals("Delete destination")) {
- destMap.remove(destList.getSelectedValue());
- destListModel.remove(destList.getSelectedIndex());
- }
- }
- }
-}
+/*
+ * Neon, a roguelike engine.
+ * Copyright (C) 2013 - Maarten Driesen
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package neon.editor.editors;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.text.NumberFormat;
+import java.util.*;
+import javax.swing.*;
+import javax.swing.border.*;
+import javax.swing.event.*;
+import neon.editor.*;
+import neon.editor.help.HelpLabels;
+import neon.editor.resources.RFaction;
+import neon.entities.property.Skill;
+import neon.resources.*;
+import neon.resources.RSpell.SpellType;
+import org.jdom2.Element;
+
+public class NPCEditor extends ObjectEditor implements MouseListener {
+ private RPerson data;
+ private JList spellList, itemList, destList;
+ private JTextField nameField;
+ private JComboBox factionBox;
+ private JComboBox raceBox;
+ private JComboBox aiTypeBox;
+ private JSpinner aggressionSpinner, confidenceSpinner, factionSpinner;
+ private JFormattedTextField rangeField, destX, destY, destCost, skillField;
+ private HashMap skills;
+ private Set trainedSkills;
+ private HashMap joinedFactions;
+ private HashMap destMap;
+ private JCheckBox spellBox,
+ skillBox,
+ tradeBox,
+ travelBox,
+ trainBox,
+ spellMakerBox,
+ factionCheckBox,
+ potionBox,
+ healerBox,
+ tattooBox;
+ private JComboBox skillComboBox;
+ private DefaultListModel destListModel, spellListModel, itemListModel;
+ private Skill currentSkill;
+ private Element currentDest;
+ private ArrayList spells;
+
+ public NPCEditor(JFrame parent, RPerson data) {
+ super(parent, "NPC Editor: " + data.id);
+ this.data = data;
+
+ spells = new ArrayList();
+ for (RSpell spell : Editor.resources.getResources(RSpell.class)) {
+ if (spell.type == SpellType.SPELL) {
+ spells.add(spell.id);
+ }
+ }
+
+ JPanel npcProps = new JPanel();
+ npcProps.setBorder(new TitledBorder("Properties"));
+ BoxLayout propLayout = new BoxLayout(npcProps, BoxLayout.PAGE_AXIS);
+ npcProps.setLayout(propLayout);
+
+ JPanel generalPanel = new JPanel();
+ generalPanel.setBorder(new TitledBorder("General"));
+ nameField = new JTextField(10);
+ raceBox = new JComboBox(Editor.resources.getResources(RCreature.class));
+ generalPanel.add(new JLabel("Name: "));
+ generalPanel.add(nameField);
+ generalPanel.add(new JLabel(" "));
+ generalPanel.add(HelpLabels.getNameHelpLabel());
+ generalPanel.add(new JLabel(" "));
+ generalPanel.add(new JLabel("Species: "));
+ generalPanel.add(raceBox);
+ generalPanel.add(new JLabel(" "));
+ generalPanel.add(HelpLabels.getRaceHelpLabel());
+ generalPanel.setMaximumSize(
+ new Dimension(generalPanel.getMaximumSize().width, generalPanel.getPreferredSize().height));
+
+ JPanel aiPanel = new JPanel();
+ GroupLayout layout = new GroupLayout(aiPanel);
+ aiPanel.setLayout(layout);
+ layout.setAutoCreateGaps(true);
+ JLabel aiTypeLabel = new JLabel("Type: ");
+ JLabel aggressionLabel = new JLabel("Aggression: ");
+ JLabel confidenceLabel = new JLabel("Confidence: ");
+ JLabel rangeLabel = new JLabel("Territory: ");
+ aiTypeBox = new JComboBox(RCreature.AIType.values());
+ aggressionSpinner = new JSpinner(new SpinnerNumberModel(0, 0, 100, 1));
+ confidenceSpinner = new JSpinner(new SpinnerNumberModel(0, 0, 100, 1));
+ rangeField = new JFormattedTextField(NumberFormat.getIntegerInstance());
+ rangeField.setValue(0);
+ JLabel aiHelpLabel = HelpLabels.getAITypeHelpLabel();
+ JLabel confidenceHelpLabel = HelpLabels.getConfidenceHelpLabel();
+ JLabel aggressionHelpLabel = HelpLabels.getAggressionHelpLabel();
+ JLabel rangeHelpLabel = HelpLabels.getRangeHelpLabel();
+ aiPanel.setMaximumSize(
+ new Dimension(generalPanel.getMaximumSize().width, generalPanel.getPreferredSize().height));
+ layout.setVerticalGroup(
+ layout
+ .createSequentialGroup()
+ .addGroup(
+ layout
+ .createParallelGroup(GroupLayout.Alignment.BASELINE)
+ .addComponent(aiTypeLabel)
+ .addComponent(aiTypeBox)
+ .addComponent(aiHelpLabel)
+ .addComponent(aggressionLabel)
+ .addComponent(aggressionSpinner)
+ .addComponent(aggressionHelpLabel))
+ .addGroup(
+ layout
+ .createParallelGroup(GroupLayout.Alignment.BASELINE)
+ .addComponent(confidenceLabel)
+ .addComponent(confidenceSpinner)
+ .addComponent(confidenceHelpLabel)
+ .addComponent(rangeLabel)
+ .addComponent(rangeField)
+ .addComponent(rangeHelpLabel)));
+ layout.setHorizontalGroup(
+ layout
+ .createSequentialGroup()
+ .addGroup(
+ layout
+ .createParallelGroup(GroupLayout.Alignment.LEADING)
+ .addComponent(
+ aiTypeLabel,
+ GroupLayout.PREFERRED_SIZE,
+ GroupLayout.DEFAULT_SIZE,
+ GroupLayout.PREFERRED_SIZE)
+ .addComponent(confidenceLabel))
+ .addGroup(
+ layout
+ .createParallelGroup(GroupLayout.Alignment.LEADING, false)
+ .addComponent(
+ aiTypeBox,
+ GroupLayout.PREFERRED_SIZE,
+ GroupLayout.DEFAULT_SIZE,
+ GroupLayout.PREFERRED_SIZE)
+ .addComponent(confidenceSpinner))
+ .addGap(10)
+ .addGroup(
+ layout
+ .createParallelGroup(GroupLayout.Alignment.LEADING)
+ .addComponent(aiHelpLabel)
+ .addComponent(confidenceHelpLabel))
+ .addGap(10)
+ .addGroup(
+ layout
+ .createParallelGroup(GroupLayout.Alignment.LEADING)
+ .addComponent(
+ aggressionLabel,
+ GroupLayout.PREFERRED_SIZE,
+ GroupLayout.DEFAULT_SIZE,
+ GroupLayout.PREFERRED_SIZE)
+ .addComponent(rangeLabel))
+ .addGroup(
+ layout
+ .createParallelGroup(GroupLayout.Alignment.LEADING, false)
+ .addComponent(
+ aggressionSpinner,
+ GroupLayout.PREFERRED_SIZE,
+ GroupLayout.DEFAULT_SIZE,
+ GroupLayout.PREFERRED_SIZE)
+ .addComponent(rangeField))
+ .addGap(10)
+ .addGroup(
+ layout
+ .createParallelGroup(GroupLayout.Alignment.LEADING)
+ .addComponent(aggressionHelpLabel)
+ .addComponent(rangeHelpLabel)));
+ aiPanel.setBorder(new TitledBorder("AI"));
+
+ JTabbedPane servicePane = new JTabbedPane();
+ servicePane.setBorder(new TitledBorder("Services"));
+
+ JPanel spellPanel = new JPanel(new BorderLayout());
+ spellBox = new JCheckBox("Spell trader");
+ spellMakerBox = new JCheckBox("Spell maker");
+ healerBox = new JCheckBox("Healer");
+ JPanel spellBoxPanel = new JPanel();
+ spellBoxPanel.add(spellBox, BorderLayout.PAGE_START);
+ spellBoxPanel.add(spellMakerBox, BorderLayout.CENTER);
+ spellBoxPanel.add(healerBox, BorderLayout.PAGE_END);
+ spellPanel.add(spellBoxPanel, BorderLayout.PAGE_START);
+ spellListModel = new DefaultListModel();
+ spellList = new JList(spellListModel);
+ spellList.addMouseListener(this);
+ JScrollPane spellScroller = new JScrollPane(spellList);
+ spellPanel.add(spellScroller, BorderLayout.CENTER);
+ servicePane.add(spellPanel, "Magic");
+
+ JPanel tradePanel = new JPanel(new BorderLayout());
+ tradeBox = new JCheckBox("Trader");
+ tradeBox.setHorizontalAlignment(SwingConstants.CENTER);
+ tradePanel.add(tradeBox, BorderLayout.PAGE_START);
+ itemListModel = new DefaultListModel();
+ itemList = new JList(itemListModel);
+ itemList.addMouseListener(this);
+ JScrollPane itemScroller = new JScrollPane(itemList);
+ tradePanel.add(itemScroller, BorderLayout.CENTER);
+ servicePane.add(tradePanel, "Trade");
+
+ JPanel skillPanel = new JPanel(new BorderLayout());
+ skills = new HashMap();
+ trainedSkills = new HashSet();
+ trainBox = new JCheckBox("Skill trainer");
+ trainBox.setHorizontalAlignment(SwingConstants.CENTER);
+ skillPanel.add(trainBox, BorderLayout.PAGE_START);
+ JPanel skillSubPanel = new JPanel();
+ skillSubPanel.add(new JLabel("Skills: "));
+ skillComboBox = new JComboBox(Skill.values());
+ skillComboBox.addActionListener(new SkillListListener());
+ skillSubPanel.add(skillComboBox);
+ skillField = new JFormattedTextField(NumberFormat.getIntegerInstance());
+ skillField.setColumns(3);
+ skillSubPanel.add(skillField);
+ skillBox = new JCheckBox("Trainable?");
+ skillSubPanel.add(skillBox);
+ skillPanel.add(skillSubPanel);
+ servicePane.add(skillPanel, "Training");
+
+ JPanel travelPanel = new JPanel(new BorderLayout());
+ destMap = new HashMap();
+ travelBox = new JCheckBox("Travel agent");
+ travelBox.setHorizontalAlignment(SwingConstants.CENTER);
+ travelPanel.add(travelBox, BorderLayout.PAGE_START);
+ destListModel = new DefaultListModel();
+ destList = new JList(destListModel);
+ destList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+ destList.addMouseListener(this);
+ destList.addListSelectionListener(new DestListAction());
+ JScrollPane destScroller = new JScrollPane(destList);
+ travelPanel.add(destScroller, BorderLayout.CENTER);
+ JPanel destPanel = new JPanel();
+ destPanel.add(new JLabel("x: "));
+ destX = new JFormattedTextField(NumberFormat.getIntegerInstance());
+ destX.setColumns(5);
+ destPanel.add(destX);
+ destPanel.add(new JLabel("y: "));
+ destY = new JFormattedTextField(NumberFormat.getIntegerInstance());
+ destY.setColumns(5);
+ destPanel.add(destY);
+ destPanel.add(new JLabel("price: "));
+ destCost = new JFormattedTextField(NumberFormat.getIntegerInstance());
+ destCost.setColumns(5);
+ destPanel.add(destCost);
+ travelPanel.add(destPanel, BorderLayout.PAGE_END);
+ servicePane.add(travelPanel, "Travel");
+
+ JPanel otherPanel = new JPanel();
+ potionBox = new JCheckBox("Potion maker");
+ otherPanel.add(potionBox);
+ tattooBox = new JCheckBox("Tattoo artist");
+ otherPanel.add(tattooBox);
+ servicePane.add(otherPanel, "Other");
+
+ // factions
+ JPanel factionPanel = new JPanel();
+
+ joinedFactions = new HashMap();
+ FactionListListener fl = new FactionListListener();
+
+ factionBox = new JComboBox(Editor.resources.getResources(RFaction.class));
+ factionBox.addActionListener(fl);
+ factionPanel.add(factionBox);
+ factionCheckBox = new JCheckBox();
+ factionCheckBox.addItemListener(fl);
+ factionPanel.add(factionCheckBox);
+ factionSpinner = new JSpinner(new SpinnerNumberModel(0, 0, 100, 1));
+ factionSpinner.addChangeListener(fl);
+ factionPanel.add(factionSpinner);
+ factionPanel.add(new JLabel(" "));
+ factionPanel.add(HelpLabels.getFactionHelpLabel());
+ factionPanel.setBorder(new TitledBorder("Factions"));
+
+ npcProps.add(generalPanel);
+ npcProps.add(aiPanel);
+ npcProps.add(factionPanel);
+ npcProps.add(servicePane);
+
+ frame.add(new JScrollPane(npcProps), BorderLayout.CENTER);
+ }
+
+ protected void load() {
+ nameField.setText(data.name);
+ RCreature species = (RCreature) Editor.resources.getResource(data.species);
+ raceBox.setSelectedItem(species);
+
+ for (String s : data.factions.keySet()) {
+ joinedFactions.put(s, data.factions.get(s));
+ }
+ factionCheckBox.setSelected(joinedFactions.containsKey(factionBox.getSelectedItem()));
+ if (joinedFactions.containsKey(factionBox.getSelectedItem())) {
+ factionSpinner.setValue(data.factions.get(factionBox.getSelectedItem()));
+ factionSpinner.setEnabled(true);
+ } else {
+ factionSpinner.setEnabled(false);
+ factionSpinner.setValue(0);
+ }
+
+ if (data.aiType != null) {
+ aiTypeBox.setSelectedItem(data.aiType);
+ } else if (species != null) {
+ aiTypeBox.setSelectedItem(species.aiType);
+ }
+ if (data.aiRange > -1) {
+ rangeField.setValue(data.aiRange);
+ } else if (species != null) {
+ rangeField.setValue(species.aiRange);
+ }
+ if (data.aiAggr > -1) {
+ aggressionSpinner.setValue(data.aiAggr);
+ } else if (species != null) {
+ rangeField.setValue(species.aiAggr);
+ }
+ if (data.aiConf > -1) {
+ confidenceSpinner.setValue(data.aiConf);
+ } else if (species != null) {
+ rangeField.setValue(species.aiConf);
+ }
+
+ skills = data.skills;
+ if (skills.containsKey(skillComboBox.getSelectedItem())) {
+ skillField.setValue(skills.get(skillComboBox.getSelectedItem()));
+ } else {
+ skillField.setValue(0);
+ }
+
+ for (String rs : data.spells) {
+ spellListModel.addElement(rs);
+ }
+
+ for (String i : data.items) {
+ itemListModel.addElement(i);
+ }
+
+ for (RPerson.Service service : data.services) {
+ if (service.id.equals("trade")) {
+ tradeBox.setSelected(true);
+ } else if (service.id.equals("travel")) {
+ travelBox.setSelected(true);
+ for (RPerson.Service.Destination d : service.destinations) {
+ destListModel.addElement(d.name);
+ // Store destination for editing
+ Element destElement = new Element("dest");
+ destElement.setAttribute("name", d.name);
+ destElement.setAttribute("x", String.valueOf(d.x));
+ destElement.setAttribute("y", String.valueOf(d.y));
+ destElement.setAttribute("cost", String.valueOf(d.cost));
+ destMap.put(d.name, destElement);
+ }
+ } else if (service.id.equals("training")) {
+ trainBox.setSelected(true);
+ for (String s : service.skills) {
+ trainedSkills.add(Skill.valueOf(s.toUpperCase()));
+ }
+ skillBox.setSelected(trainedSkills.contains(skillComboBox.getSelectedItem()));
+ } else if (service.id.equals("spells")) {
+ spellBox.setSelected(true);
+ } else if (service.id.equals("spellmaker")) {
+ spellMakerBox.setSelected(true);
+ } else if (service.id.equals("healer")) {
+ healerBox.setSelected(true);
+ } else if (service.id.equals("alchemy")) {
+ potionBox.setSelected(true);
+ } else if (service.id.equals("tattoo")) {
+ tattooBox.setSelected(true);
+ }
+ }
+ }
+
+ public void mouseExited(MouseEvent e) {}
+
+ public void mouseEntered(MouseEvent e) {}
+
+ public void mouseReleased(MouseEvent e) {}
+
+ public void mousePressed(MouseEvent e) {}
+
+ public void mouseClicked(MouseEvent e) {
+ if (e.getButton() == MouseEvent.BUTTON3) {
+ if (e.getComponent() == itemList) {
+ JPopupMenu menu = new JPopupMenu();
+ menu.add(new ItemListAction("Add item"));
+ menu.add(new ItemListAction("Delete item"));
+ menu.show(e.getComponent(), e.getX(), e.getY());
+ itemList.setSelectedIndex(itemList.locationToIndex(e.getPoint()));
+ } else if (e.getComponent() == spellList) {
+ JPopupMenu menu = new JPopupMenu();
+ menu.add(new SpellListAction("Add spell"));
+ menu.add(new SpellListAction("Delete spell"));
+ menu.show(e.getComponent(), e.getX(), e.getY());
+ spellList.setSelectedIndex(spellList.locationToIndex(e.getPoint()));
+ } else if (e.getComponent() == destList) {
+ JPopupMenu menu = new JPopupMenu();
+ menu.add(new DestListAction("Add destination"));
+ menu.add(new DestListAction("Delete destination"));
+ menu.show(e.getComponent(), e.getX(), e.getY());
+ destList.setSelectedIndex(destList.locationToIndex(e.getPoint()));
+ }
+ }
+ }
+
+ protected void save() {
+ data.name = nameField.getText();
+ RCreature species = raceBox.getItemAt(raceBox.getSelectedIndex());
+ data.species = species.id;
+
+ if (species.aiType.equals(aiTypeBox.getItemAt(aiTypeBox.getSelectedIndex()))) {
+ data.aiType = null;
+ } else {
+ data.aiType = aiTypeBox.getItemAt(aiTypeBox.getSelectedIndex());
+ }
+ if (species.aiRange == (Integer) rangeField.getValue()) {
+ data.aiRange = -1;
+ } else {
+ data.aiRange = (Integer) rangeField.getValue();
+ }
+ if (species.aiAggr == (Integer) aggressionSpinner.getValue()) {
+ data.aiAggr = -1;
+ } else {
+ data.aiAggr = (Integer) aggressionSpinner.getValue();
+ }
+ if (species.aiConf == (Integer) confidenceSpinner.getValue()) {
+ data.aiConf = -1;
+ } else {
+ data.aiConf = (Integer) confidenceSpinner.getValue();
+ }
+
+ data.factions.clear();
+ for (String f : joinedFactions.keySet()) {
+ data.factions.put(f, joinedFactions.get(f));
+ }
+
+ data.services.clear();
+ if (tradeBox.isSelected()) {
+ RPerson.Service service = new RPerson.Service();
+ service.id = "trade";
+ data.services.add(service);
+ }
+ data.items.clear();
+ for (Enumeration e = itemListModel.elements(); e.hasMoreElements(); ) {
+ data.items.add(e.nextElement());
+ }
+
+ if (spellMakerBox.isSelected()) {
+ RPerson.Service service = new RPerson.Service();
+ service.id = "spellmaker";
+ data.services.add(service);
+ }
+ if (healerBox.isSelected()) {
+ RPerson.Service service = new RPerson.Service();
+ service.id = "healer";
+ data.services.add(service);
+ }
+ if (spellBox.isSelected()) {
+ RPerson.Service service = new RPerson.Service();
+ service.id = "spells";
+ data.services.add(service);
+ }
+ for (Enumeration e = spellListModel.elements(); e.hasMoreElements(); ) {
+ data.spells.add(e.nextElement());
+ }
+
+ if (trainBox.isSelected()) {
+ RPerson.Service training = new RPerson.Service();
+ training.id = "training";
+ for (Skill s : trainedSkills) {
+ training.skills.add(s.toString());
+ }
+ data.services.add(training);
+ }
+ data.skills.clear();
+ for (Skill s : skills.keySet()) {
+ if (skills.get(s) != null && !skills.get(s).equals(0)) {
+ skills.put(s, skills.get(s));
+ }
+ }
+
+ if (travelBox.isSelected()) {
+ RPerson.Service travel = new RPerson.Service();
+ travel.id = "travel";
+ // a bit of magic to still get the last modified value into destMap
+ if (currentDest != null) {
+ currentDest.setAttribute("x", destX.getValue().toString());
+ currentDest.setAttribute("y", destY.getValue().toString());
+ currentDest.setAttribute("cost", destCost.getValue().toString());
+ }
+ // Convert Element destMap to Service.Destination objects
+ for (Element d : destMap.values()) {
+ RPerson.Service.Destination dest = new RPerson.Service.Destination();
+ dest.name = d.getAttributeValue("name");
+ dest.x = Integer.parseInt(d.getAttributeValue("x"));
+ dest.y = Integer.parseInt(d.getAttributeValue("y"));
+ dest.cost = Integer.parseInt(d.getAttributeValue("cost"));
+ travel.destinations.add(dest);
+ }
+ data.services.add(travel);
+ }
+
+ if (potionBox.isSelected()) {
+ RPerson.Service service = new RPerson.Service();
+ service.id = "alchemy";
+ data.services.add(service);
+ }
+
+ if (tattooBox.isSelected()) {
+ RPerson.Service service = new RPerson.Service();
+ service.id = "tattoo";
+ data.services.add(service);
+ }
+
+ data.setPath(Editor.getStore().getActive().get("id"));
+ }
+
+ private class SkillListListener implements ActionListener {
+ public void actionPerformed(ActionEvent e) {
+ try {
+ skills.put(currentSkill, Integer.parseInt(skillField.getText()));
+ } catch (NumberFormatException f) {
+ }
+ if (skillBox.isSelected()) {
+ trainedSkills.add(currentSkill);
+ } else {
+ trainedSkills.remove(currentSkill);
+ }
+
+ Skill skill = (Skill) skillComboBox.getSelectedItem();
+ if (skills.containsKey(skill)) {
+ skillField.setText(skills.get(skill).toString());
+ } else {
+ skillField.setText("0");
+ }
+ skillBox.setSelected(trainedSkills.contains(skill));
+ currentSkill = skill;
+ }
+ }
+
+ private class FactionListListener implements ActionListener, ItemListener, ChangeListener {
+ public void actionPerformed(ActionEvent e) {
+ String faction = factionBox.getSelectedItem().toString();
+ factionCheckBox.setSelected(joinedFactions.containsKey(faction));
+ if (joinedFactions.containsKey(faction)) {
+ factionSpinner.setEnabled(true);
+ factionSpinner.setValue(joinedFactions.get(faction));
+ } else {
+ factionSpinner.setEnabled(false);
+ factionSpinner.setValue(0);
+ }
+ }
+
+ public void itemStateChanged(ItemEvent e) {
+ String faction = factionBox.getSelectedItem().toString();
+ if (e.getSource() == factionCheckBox) {
+ if (factionCheckBox.isSelected()) {
+ if (!joinedFactions.containsKey(faction)) {
+ joinedFactions.put(faction, (Integer) factionSpinner.getValue());
+ }
+ factionSpinner.setEnabled(true);
+ } else {
+ joinedFactions.remove(faction);
+ factionSpinner.setEnabled(false);
+ }
+ }
+ }
+
+ public void stateChanged(ChangeEvent ce) {
+ String faction = factionBox.getSelectedItem().toString();
+ if (joinedFactions.containsKey(faction)) {
+ joinedFactions.put(faction, (Integer) factionSpinner.getValue());
+ System.out.println("state.factions.put: " + (Integer) factionSpinner.getValue());
+ }
+ }
+ }
+
+ @SuppressWarnings("serial")
+ private class SpellListAction extends AbstractAction {
+ public SpellListAction(String name) {
+ super(name);
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ if (e.getActionCommand().equals("Add spell")) {
+ String s =
+ (String)
+ JOptionPane.showInputDialog(
+ frame,
+ "New spell:",
+ "New spell",
+ JOptionPane.PLAIN_MESSAGE,
+ null,
+ spells.toArray(),
+ 0);
+ if (s != null) {
+ spellListModel.addElement(s);
+ }
+ } else if (e.getActionCommand().equals("Delete spell")) {
+ spellListModel.remove(spellList.getSelectedIndex());
+ }
+ }
+ }
+
+ @SuppressWarnings("serial")
+ private class ItemListAction extends AbstractAction {
+ public ItemListAction(String name) {
+ super(name);
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ if (e.getActionCommand().equals("Add item")) {
+ Object[] items = Editor.resources.getResources(RItem.class).toArray();
+ String s =
+ (String)
+ JOptionPane.showInputDialog(
+ frame, "Add item:", "Add item", JOptionPane.PLAIN_MESSAGE, null, items, 0);
+ if (s != null) {
+ itemListModel.addElement(s);
+ }
+ } else if (e.getActionCommand().equals("Delete item")) {
+ itemListModel.remove(itemList.getSelectedIndex());
+ }
+ }
+ }
+
+ @SuppressWarnings("serial")
+ private class DestListAction extends AbstractAction implements ListSelectionListener {
+ public DestListAction() {
+ super();
+ }
+
+ public DestListAction(String name) {
+ super(name);
+ }
+
+ public void valueChanged(ListSelectionEvent e) {
+ try { // in case npc is not a travel agent
+ if (currentDest != null) {
+ currentDest.setAttribute("x", destX.getValue().toString());
+ currentDest.setAttribute("y", destY.getValue().toString());
+ currentDest.setAttribute("cost", destCost.getValue().toString());
+ }
+ currentDest = destMap.get(destList.getSelectedValue());
+ destX.setValue(Integer.parseInt(currentDest.getAttributeValue("x")));
+ destY.setValue(Integer.parseInt(currentDest.getAttributeValue("y")));
+ destCost.setValue(Integer.parseInt(currentDest.getAttributeValue("cost")));
+ } catch (NullPointerException f) {
+ }
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ if (e.getActionCommand().equals("Add destination")) {
+ String s =
+ (String)
+ JOptionPane.showInputDialog(
+ frame, "New destination:", "New destination", JOptionPane.QUESTION_MESSAGE);
+ if ((s != null) && (s.length() > 0)) {
+ destListModel.addElement(s);
+ Element dest = new Element("dest");
+ dest.setAttribute("name", s);
+ dest.setAttribute("x", "0");
+ dest.setAttribute("y", "0");
+ dest.setAttribute("cost", "0");
+ destMap.put(s, dest);
+ }
+ } else if (e.getActionCommand().equals("Delete destination")) {
+ destMap.remove(destList.getSelectedValue());
+ destListModel.remove(destList.getSelectedIndex());
+ }
+ }
+ }
+}
diff --git a/src/main/java/neon/editor/editors/QuestEditor.java b/src/main/java/neon/editor/editors/QuestEditor.java
index 9b7088f..6addd43 100644
--- a/src/main/java/neon/editor/editors/QuestEditor.java
+++ b/src/main/java/neon/editor/editors/QuestEditor.java
@@ -28,8 +28,8 @@
import neon.editor.DialogEditor;
import neon.editor.NeonFormat;
import neon.editor.help.HelpLabels;
+import neon.resources.quest.QuestVariable;
import neon.resources.quest.RQuest;
-import org.jdom2.Element;
public class QuestEditor extends ObjectEditor implements ActionListener, MouseListener {
private RQuest quest;
@@ -148,19 +148,16 @@ protected void save() {
quest.getConditions().add(data.get(0));
}
- Element vars = new Element("objects");
+ // Convert table data to QuestVariable objects
+ quest.getVariables().clear();
for (Vector> data : (Vector) varModel.getDataVector()) {
- Element var = new Element(data.get(1).toString());
- var.setText(data.get(0).toString());
- if (data.get(2) != null) {
- var.setAttribute("id", data.get(2).toString());
- }
- if (data.get(3) != null) {
- var.setAttribute("type", data.get(3).toString());
- }
- vars.addContent(var);
+ QuestVariable var = new QuestVariable();
+ var.name = data.get(0) != null ? data.get(0).toString() : null;
+ var.category = data.get(1) != null ? data.get(1).toString() : null;
+ var.id = data.get(2) != null ? data.get(2).toString() : null;
+ var.typeFilter = data.get(3) != null ? data.get(3).toString() : null;
+ quest.getVariables().add(var);
}
- quest.variables = vars;
// quest.getTopics().clear();
for (Vector> data : (Vector) dialogModel.getDataVector()) {
@@ -183,16 +180,10 @@ protected void load() {
freqField.setValue(null);
}
- if (quest.variables != null) {
- for (Element item : quest.variables.getChildren()) {
- String[] data = {
- item.getText(),
- item.getName(),
- item.getAttributeValue("id"),
- item.getAttributeValue("type")
- };
- varModel.insertRow(0, data);
- }
+ // Load QuestVariable objects into table
+ for (QuestVariable var : quest.getVariables()) {
+ String[] data = {var.name, var.category, var.id, var.typeFilter};
+ varModel.insertRow(0, data);
}
for (String condition : quest.getConditions()) {
diff --git a/src/main/java/neon/editor/editors/ZoneThemeEditor.java b/src/main/java/neon/editor/editors/ZoneThemeEditor.java
index eaeb13f..5fd650f 100644
--- a/src/main/java/neon/editor/editors/ZoneThemeEditor.java
+++ b/src/main/java/neon/editor/editors/ZoneThemeEditor.java
@@ -1,356 +1,362 @@
-/*
- * Neon, a roguelike engine.
- * Copyright (C) 2012 - Maarten Driesen
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package neon.editor.editors;
-
-import java.awt.*;
-import java.awt.event.*;
-import java.util.*;
-import javax.swing.*;
-import javax.swing.border.*;
-import javax.swing.table.DefaultTableModel;
-import javax.swing.table.TableColumn;
-import neon.editor.Editor;
-import neon.editor.NeonFormat;
-import neon.editor.help.HelpLabels;
-import neon.resources.RCreature;
-import neon.resources.RItem;
-import neon.resources.RTerrain;
-import neon.resources.RZoneTheme;
-
-@SuppressWarnings("serial")
-public class ZoneThemeEditor extends ObjectEditor implements MouseListener {
- private JTextField floorField, wallsField, doorsField;
- private JFormattedTextField minField, maxField;
- private DefaultTableModel creatureModel, itemModel, featureModel;
- private JTable creatureTable, itemTable, featureTable;
- private RZoneTheme theme;
- private JComboBox typeBox;
-
- public ZoneThemeEditor(JFrame parent, RZoneTheme theme) {
- super(parent, "Zone theme: " + theme.id);
- this.theme = theme;
-
- JPanel props = new JPanel();
- GroupLayout layout = new GroupLayout(props);
- props.setLayout(layout);
- layout.setAutoCreateGaps(true);
- props.setBorder(new TitledBorder("Properties"));
- String[] types = {"cave", "pits", "maze", "mine", "bsp", "packed", "sparse"};
- typeBox = new JComboBox(types);
- floorField = new JTextField(15);
- wallsField = new JTextField(15);
- doorsField = new JTextField(15);
- minField = new JFormattedTextField(NeonFormat.getIntegerInstance());
- maxField = new JFormattedTextField(NeonFormat.getIntegerInstance());
- JLabel typeLabel = new JLabel("Type: ");
- JLabel floorLabel = new JLabel("Floors: ");
- JLabel wallsLabel = new JLabel("Walls: ");
- JLabel doorsLabel = new JLabel("Doors: ");
- JLabel minLabel = new JLabel("Min. size: ");
- JLabel maxLabel = new JLabel("Max. size: ");
- JLabel floorHelpLabel = HelpLabels.getFloorHelpLabel();
- JLabel wallHelpLabel = HelpLabels.getWallHelpLabel();
- JLabel doorHelpLabel = HelpLabels.getDoorHelpLabel();
- JLabel minHelpLabel = HelpLabels.getMinSizeHelpLabel();
- JLabel maxHelpLabel = HelpLabels.getMaxSizeHelpLabel();
- layout.setVerticalGroup(
- layout
- .createSequentialGroup()
- .addGroup(
- layout
- .createParallelGroup(GroupLayout.Alignment.BASELINE)
- .addComponent(typeLabel)
- .addComponent(typeBox)
- .addComponent(floorLabel)
- .addComponent(floorField)
- .addComponent(floorHelpLabel))
- .addGroup(
- layout
- .createParallelGroup(GroupLayout.Alignment.BASELINE)
- .addComponent(wallsLabel)
- .addComponent(wallsField)
- .addComponent(wallHelpLabel)
- .addComponent(doorsLabel)
- .addComponent(doorsField)
- .addComponent(doorHelpLabel))
- .addGroup(
- layout
- .createParallelGroup(GroupLayout.Alignment.BASELINE)
- .addComponent(minLabel)
- .addComponent(minField)
- .addComponent(minHelpLabel)
- .addComponent(maxLabel)
- .addComponent(maxField)
- .addComponent(maxHelpLabel)));
- layout.setHorizontalGroup(
- layout
- .createSequentialGroup()
- .addGroup(
- layout
- .createParallelGroup(GroupLayout.Alignment.LEADING)
- .addComponent(typeLabel)
- .addComponent(wallsLabel)
- .addComponent(
- minLabel,
- GroupLayout.PREFERRED_SIZE,
- GroupLayout.DEFAULT_SIZE,
- GroupLayout.PREFERRED_SIZE))
- .addGroup(
- layout
- .createParallelGroup(GroupLayout.Alignment.LEADING)
- .addComponent(typeBox)
- .addComponent(wallsField)
- .addComponent(minField))
- .addGap(10)
- .addGroup(
- layout
- .createParallelGroup(GroupLayout.Alignment.LEADING, false)
- .addComponent(wallHelpLabel)
- .addComponent(minHelpLabel))
- .addGap(10)
- .addGroup(
- layout
- .createParallelGroup(GroupLayout.Alignment.LEADING)
- .addComponent(floorLabel)
- .addComponent(doorsLabel)
- .addComponent(maxLabel))
- .addGroup(
- layout
- .createParallelGroup(GroupLayout.Alignment.LEADING, false)
- .addComponent(
- floorField,
- GroupLayout.PREFERRED_SIZE,
- GroupLayout.DEFAULT_SIZE,
- GroupLayout.PREFERRED_SIZE)
- .addComponent(doorsField)
- .addComponent(maxField))
- .addGap(10)
- .addGroup(
- layout
- .createParallelGroup(GroupLayout.Alignment.LEADING, false)
- .addComponent(floorHelpLabel)
- .addComponent(doorHelpLabel)
- .addComponent(maxHelpLabel)));
-
- JTabbedPane stuff = new JTabbedPane();
-
- String[] columns = {"id", "chance"};
- itemModel = new ThemesTableModel(columns, String.class, Integer.class);
- itemTable = new JTable(itemModel);
- itemTable.setFillsViewportHeight(true);
- itemTable.addMouseListener(this);
- JScrollPane itemScroller = new JScrollPane(itemTable);
-
- creatureModel = new ThemesTableModel(columns, String.class, Integer.class);
- creatureTable = new JTable(creatureModel);
- creatureTable.setFillsViewportHeight(true);
- creatureTable.addMouseListener(this);
- JScrollPane creatureScroller = new JScrollPane(creatureTable);
-
- String[] moreColumns = {"id", "type", "size", "chance"};
- featureModel =
- new ThemesTableModel(moreColumns, String.class, String.class, Integer.class, Integer.class);
- featureTable = new JTable(featureModel);
- featureTable.setFillsViewportHeight(true);
- featureTable.addMouseListener(this);
- TableColumn typeColumn = featureTable.getColumnModel().getColumn(1);
- JComboBox comboBox = new JComboBox();
- comboBox.addItem("stain");
- comboBox.addItem("lake");
- comboBox.addItem("patch");
- comboBox.addItem("river");
- typeColumn.setCellEditor(new DefaultCellEditor(comboBox));
- JScrollPane featureScroller = new JScrollPane(featureTable);
-
- stuff.add("Features", featureScroller);
- stuff.add("Items", itemScroller);
- stuff.add("Creatures", creatureScroller);
- stuff.setBorder(new TitledBorder("Contents"));
-
- JPanel center = new JPanel(new BorderLayout());
- center.add(props, BorderLayout.PAGE_START);
- center.add(stuff);
-
- frame.add(center, BorderLayout.CENTER);
- }
-
- protected void save() {
- theme.type = typeBox.getSelectedItem().toString();
- theme.floor = floorField.getText();
- theme.walls = wallsField.getText();
- theme.doors = doorsField.getText();
- theme.min = Integer.parseInt(minField.getText());
- theme.max = Integer.parseInt(maxField.getText());
- theme.setPath(Editor.getStore().getActive().get("id"));
-
- theme.creatures.clear();
- for (Vector> data : (Vector) creatureModel.getDataVector()) {
- theme.creatures.put(data.get(0).toString(), (Integer) data.get(1));
- }
-
- theme.features.clear();
- for (Vector> data : (Vector) featureModel.getDataVector()) {
- theme.features.add(data.toArray());
- }
-
- theme.items.clear();
- for (Vector> data : (Vector) itemModel.getDataVector()) {
- theme.items.put(data.get(0).toString(), (Integer) data.get(1));
- }
- }
-
- protected void load() {
- typeBox.setSelectedItem(theme.type);
- floorField.setText(theme.floor);
- wallsField.setText(theme.walls);
- doorsField.setText(theme.doors);
- minField.setValue(theme.min);
- maxField.setValue(theme.max);
-
- creatureModel.setNumRows(0);
- featureModel.setNumRows(0);
- itemModel.setNumRows(0);
-
- for (Map.Entry creature : theme.creatures.entrySet()) {
- Object[] data = {creature.getKey(), creature.getValue()};
- creatureModel.insertRow(0, data);
- }
-
- for (Map.Entry item : theme.items.entrySet()) {
- Object[] data = {item.getKey(), item.getValue()};
- itemModel.insertRow(0, data);
- }
-
- for (Object[] feature : theme.features) {
- featureModel.insertRow(0, feature);
- }
- }
-
- public void mousePressed(MouseEvent e) {}
-
- public void mouseReleased(MouseEvent e) {}
-
- public void mouseEntered(MouseEvent e) {}
-
- public void mouseExited(MouseEvent e) {}
-
- public void mouseClicked(MouseEvent e) {
- if (e.getButton() == MouseEvent.BUTTON3) {
- JPopupMenu menu = new JPopupMenu();
- if (e.getSource().equals(itemTable)) {
- menu.add(new ClickAction("Add item"));
- menu.add(new ClickAction("Remove item"));
- int row = itemTable.rowAtPoint(e.getPoint());
- itemTable.getSelectionModel().setSelectionInterval(row, row);
- } else if (e.getSource().equals(creatureTable)) {
- menu.add(new ClickAction("Add creature"));
- menu.add(new ClickAction("Remove creature"));
- int row = creatureTable.rowAtPoint(e.getPoint());
- creatureTable.getSelectionModel().setSelectionInterval(row, row);
- } else if (e.getSource().equals(featureTable)) {
- menu.add(new ClickAction("Add feature"));
- menu.add(new ClickAction("Remove feature"));
- int row = featureTable.rowAtPoint(e.getPoint());
- featureTable.getSelectionModel().setSelectionInterval(row, row);
- }
- menu.show(e.getComponent(), e.getX(), e.getY());
- }
- }
-
- private class ClickAction extends AbstractAction {
- public ClickAction(String name) {
- super(name);
- }
-
- public void actionPerformed(ActionEvent e) {
- if (e.getActionCommand().equals("Add item")) {
- Object[] items = Editor.resources.getResources(RItem.class).toArray();
- String s =
- (String)
- JOptionPane.showInputDialog(
- frame,
- "Choose item:",
- "Add item",
- JOptionPane.PLAIN_MESSAGE,
- null,
- items,
- "ham");
- if (s != null) {
- String[] row = {s, "1"};
- itemModel.addRow(row);
- }
- } else if (e.getActionCommand().equals("Remove item")) {
- itemModel.removeRow(itemTable.getSelectedRow());
- } else if (e.getActionCommand().equals("Add feature")) {
- Object[] terrain = Editor.resources.getResources(RTerrain.class).toArray();
- String s =
- (String)
- JOptionPane.showInputDialog(
- frame,
- "Choose terrain type:",
- "Add feature",
- JOptionPane.PLAIN_MESSAGE,
- null,
- terrain,
- "ham");
- if (s != null) {
- String[] row = {s, "patch", "1", "1"};
- featureModel.addRow(row);
- }
- } else if (e.getActionCommand().equals("Remove feature")) {
- featureModel.removeRow(featureTable.getSelectedRow());
- } else if (e.getActionCommand().equals("Add creature")) {
- Object[] creatures = Editor.resources.getResources(RCreature.class).toArray();
- String s =
- JOptionPane.showInputDialog(
- frame,
- "Choose creature:",
- "Add creature",
- JOptionPane.PLAIN_MESSAGE,
- null,
- creatures,
- null)
- .toString();
- if (s != null) {
- String[] row = {s, "1"};
- creatureModel.addRow(row);
- }
- } else if (e.getActionCommand().equals("Remove creature")) {
- creatureModel.removeRow(creatureTable.getSelectedRow());
- }
- }
- }
-
- private static class ThemesTableModel extends DefaultTableModel {
- private Class>[] classes;
-
- public ThemesTableModel(String[] columns, Class>... classes) {
- super(columns, 0);
- this.classes = classes;
- }
-
- public Class> getColumnClass(int i) {
- return classes[i];
- }
-
- public boolean isCellEditable(int row, int column) {
- return column != 0;
- }
- }
-}
+/*
+ * Neon, a roguelike engine.
+ * Copyright (C) 2012 - Maarten Driesen
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package neon.editor.editors;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.util.*;
+import javax.swing.*;
+import javax.swing.border.*;
+import javax.swing.table.DefaultTableModel;
+import javax.swing.table.TableColumn;
+import neon.editor.Editor;
+import neon.editor.NeonFormat;
+import neon.editor.help.HelpLabels;
+import neon.resources.RCreature;
+import neon.resources.RItem;
+import neon.resources.RTerrain;
+import neon.resources.RZoneTheme;
+
+@SuppressWarnings("serial")
+public class ZoneThemeEditor extends ObjectEditor implements MouseListener {
+ private JTextField floorField, wallsField, doorsField;
+ private JFormattedTextField minField, maxField;
+ private DefaultTableModel creatureModel, itemModel, featureModel;
+ private JTable creatureTable, itemTable, featureTable;
+ private RZoneTheme theme;
+ private JComboBox typeBox;
+
+ public ZoneThemeEditor(JFrame parent, RZoneTheme theme) {
+ super(parent, "Zone theme: " + theme.id);
+ this.theme = theme;
+
+ JPanel props = new JPanel();
+ GroupLayout layout = new GroupLayout(props);
+ props.setLayout(layout);
+ layout.setAutoCreateGaps(true);
+ props.setBorder(new TitledBorder("Properties"));
+ String[] types = {"cave", "pits", "maze", "mine", "bsp", "packed", "sparse"};
+ typeBox = new JComboBox(types);
+ floorField = new JTextField(15);
+ wallsField = new JTextField(15);
+ doorsField = new JTextField(15);
+ minField = new JFormattedTextField(NeonFormat.getIntegerInstance());
+ maxField = new JFormattedTextField(NeonFormat.getIntegerInstance());
+ JLabel typeLabel = new JLabel("Type: ");
+ JLabel floorLabel = new JLabel("Floors: ");
+ JLabel wallsLabel = new JLabel("Walls: ");
+ JLabel doorsLabel = new JLabel("Doors: ");
+ JLabel minLabel = new JLabel("Min. size: ");
+ JLabel maxLabel = new JLabel("Max. size: ");
+ JLabel floorHelpLabel = HelpLabels.getFloorHelpLabel();
+ JLabel wallHelpLabel = HelpLabels.getWallHelpLabel();
+ JLabel doorHelpLabel = HelpLabels.getDoorHelpLabel();
+ JLabel minHelpLabel = HelpLabels.getMinSizeHelpLabel();
+ JLabel maxHelpLabel = HelpLabels.getMaxSizeHelpLabel();
+ layout.setVerticalGroup(
+ layout
+ .createSequentialGroup()
+ .addGroup(
+ layout
+ .createParallelGroup(GroupLayout.Alignment.BASELINE)
+ .addComponent(typeLabel)
+ .addComponent(typeBox)
+ .addComponent(floorLabel)
+ .addComponent(floorField)
+ .addComponent(floorHelpLabel))
+ .addGroup(
+ layout
+ .createParallelGroup(GroupLayout.Alignment.BASELINE)
+ .addComponent(wallsLabel)
+ .addComponent(wallsField)
+ .addComponent(wallHelpLabel)
+ .addComponent(doorsLabel)
+ .addComponent(doorsField)
+ .addComponent(doorHelpLabel))
+ .addGroup(
+ layout
+ .createParallelGroup(GroupLayout.Alignment.BASELINE)
+ .addComponent(minLabel)
+ .addComponent(minField)
+ .addComponent(minHelpLabel)
+ .addComponent(maxLabel)
+ .addComponent(maxField)
+ .addComponent(maxHelpLabel)));
+ layout.setHorizontalGroup(
+ layout
+ .createSequentialGroup()
+ .addGroup(
+ layout
+ .createParallelGroup(GroupLayout.Alignment.LEADING)
+ .addComponent(typeLabel)
+ .addComponent(wallsLabel)
+ .addComponent(
+ minLabel,
+ GroupLayout.PREFERRED_SIZE,
+ GroupLayout.DEFAULT_SIZE,
+ GroupLayout.PREFERRED_SIZE))
+ .addGroup(
+ layout
+ .createParallelGroup(GroupLayout.Alignment.LEADING)
+ .addComponent(typeBox)
+ .addComponent(wallsField)
+ .addComponent(minField))
+ .addGap(10)
+ .addGroup(
+ layout
+ .createParallelGroup(GroupLayout.Alignment.LEADING, false)
+ .addComponent(wallHelpLabel)
+ .addComponent(minHelpLabel))
+ .addGap(10)
+ .addGroup(
+ layout
+ .createParallelGroup(GroupLayout.Alignment.LEADING)
+ .addComponent(floorLabel)
+ .addComponent(doorsLabel)
+ .addComponent(maxLabel))
+ .addGroup(
+ layout
+ .createParallelGroup(GroupLayout.Alignment.LEADING, false)
+ .addComponent(
+ floorField,
+ GroupLayout.PREFERRED_SIZE,
+ GroupLayout.DEFAULT_SIZE,
+ GroupLayout.PREFERRED_SIZE)
+ .addComponent(doorsField)
+ .addComponent(maxField))
+ .addGap(10)
+ .addGroup(
+ layout
+ .createParallelGroup(GroupLayout.Alignment.LEADING, false)
+ .addComponent(floorHelpLabel)
+ .addComponent(doorHelpLabel)
+ .addComponent(maxHelpLabel)));
+
+ JTabbedPane stuff = new JTabbedPane();
+
+ String[] columns = {"id", "chance"};
+ itemModel = new ThemesTableModel(columns, String.class, Integer.class);
+ itemTable = new JTable(itemModel);
+ itemTable.setFillsViewportHeight(true);
+ itemTable.addMouseListener(this);
+ JScrollPane itemScroller = new JScrollPane(itemTable);
+
+ creatureModel = new ThemesTableModel(columns, String.class, Integer.class);
+ creatureTable = new JTable(creatureModel);
+ creatureTable.setFillsViewportHeight(true);
+ creatureTable.addMouseListener(this);
+ JScrollPane creatureScroller = new JScrollPane(creatureTable);
+
+ String[] moreColumns = {"id", "type", "size", "chance"};
+ featureModel =
+ new ThemesTableModel(moreColumns, String.class, String.class, Integer.class, Integer.class);
+ featureTable = new JTable(featureModel);
+ featureTable.setFillsViewportHeight(true);
+ featureTable.addMouseListener(this);
+ TableColumn typeColumn = featureTable.getColumnModel().getColumn(1);
+ JComboBox comboBox = new JComboBox();
+ comboBox.addItem("stain");
+ comboBox.addItem("lake");
+ comboBox.addItem("patch");
+ comboBox.addItem("river");
+ typeColumn.setCellEditor(new DefaultCellEditor(comboBox));
+ JScrollPane featureScroller = new JScrollPane(featureTable);
+
+ stuff.add("Features", featureScroller);
+ stuff.add("Items", itemScroller);
+ stuff.add("Creatures", creatureScroller);
+ stuff.setBorder(new TitledBorder("Contents"));
+
+ JPanel center = new JPanel(new BorderLayout());
+ center.add(props, BorderLayout.PAGE_START);
+ center.add(stuff);
+
+ frame.add(center, BorderLayout.CENTER);
+ }
+
+ protected void save() {
+ theme.type = typeBox.getSelectedItem().toString();
+ theme.floor = floorField.getText();
+ theme.walls = wallsField.getText();
+ theme.doors = doorsField.getText();
+ theme.min = Integer.parseInt(minField.getText());
+ theme.max = Integer.parseInt(maxField.getText());
+ theme.setPath(Editor.getStore().getActive().get("id"));
+
+ theme.creatures.clear();
+ for (Vector> data : (Vector) creatureModel.getDataVector()) {
+ theme.creatures.put(data.get(0).toString(), (Integer) data.get(1));
+ }
+
+ theme.features.clear();
+ for (Vector> data : (Vector) featureModel.getDataVector()) {
+ RZoneTheme.Feature feature = new RZoneTheme.Feature();
+ feature.value = data.get(0).toString();
+ feature.t = data.get(1).toString();
+ feature.s = (Integer) data.get(2);
+ feature.n = (Integer) data.get(3);
+ theme.features.add(feature);
+ }
+
+ theme.items.clear();
+ for (Vector> data : (Vector) itemModel.getDataVector()) {
+ theme.items.put(data.get(0).toString(), (Integer) data.get(1));
+ }
+ }
+
+ protected void load() {
+ typeBox.setSelectedItem(theme.type);
+ floorField.setText(theme.floor);
+ wallsField.setText(theme.walls);
+ doorsField.setText(theme.doors);
+ minField.setValue(theme.min);
+ maxField.setValue(theme.max);
+
+ creatureModel.setNumRows(0);
+ featureModel.setNumRows(0);
+ itemModel.setNumRows(0);
+
+ for (Map.Entry creature : theme.creatures.entrySet()) {
+ Object[] data = {creature.getKey(), creature.getValue()};
+ creatureModel.insertRow(0, data);
+ }
+
+ for (Map.Entry item : theme.items.entrySet()) {
+ Object[] data = {item.getKey(), item.getValue()};
+ itemModel.insertRow(0, data);
+ }
+
+ for (RZoneTheme.Feature feature : theme.features) {
+ Object[] data = {feature.value, feature.t, feature.s, feature.n};
+ featureModel.insertRow(0, data);
+ }
+ }
+
+ public void mousePressed(MouseEvent e) {}
+
+ public void mouseReleased(MouseEvent e) {}
+
+ public void mouseEntered(MouseEvent e) {}
+
+ public void mouseExited(MouseEvent e) {}
+
+ public void mouseClicked(MouseEvent e) {
+ if (e.getButton() == MouseEvent.BUTTON3) {
+ JPopupMenu menu = new JPopupMenu();
+ if (e.getSource().equals(itemTable)) {
+ menu.add(new ClickAction("Add item"));
+ menu.add(new ClickAction("Remove item"));
+ int row = itemTable.rowAtPoint(e.getPoint());
+ itemTable.getSelectionModel().setSelectionInterval(row, row);
+ } else if (e.getSource().equals(creatureTable)) {
+ menu.add(new ClickAction("Add creature"));
+ menu.add(new ClickAction("Remove creature"));
+ int row = creatureTable.rowAtPoint(e.getPoint());
+ creatureTable.getSelectionModel().setSelectionInterval(row, row);
+ } else if (e.getSource().equals(featureTable)) {
+ menu.add(new ClickAction("Add feature"));
+ menu.add(new ClickAction("Remove feature"));
+ int row = featureTable.rowAtPoint(e.getPoint());
+ featureTable.getSelectionModel().setSelectionInterval(row, row);
+ }
+ menu.show(e.getComponent(), e.getX(), e.getY());
+ }
+ }
+
+ private class ClickAction extends AbstractAction {
+ public ClickAction(String name) {
+ super(name);
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ if (e.getActionCommand().equals("Add item")) {
+ Object[] items = Editor.resources.getResources(RItem.class).toArray();
+ String s =
+ (String)
+ JOptionPane.showInputDialog(
+ frame,
+ "Choose item:",
+ "Add item",
+ JOptionPane.PLAIN_MESSAGE,
+ null,
+ items,
+ "ham");
+ if (s != null) {
+ String[] row = {s, "1"};
+ itemModel.addRow(row);
+ }
+ } else if (e.getActionCommand().equals("Remove item")) {
+ itemModel.removeRow(itemTable.getSelectedRow());
+ } else if (e.getActionCommand().equals("Add feature")) {
+ Object[] terrain = Editor.resources.getResources(RTerrain.class).toArray();
+ String s =
+ (String)
+ JOptionPane.showInputDialog(
+ frame,
+ "Choose terrain type:",
+ "Add feature",
+ JOptionPane.PLAIN_MESSAGE,
+ null,
+ terrain,
+ "ham");
+ if (s != null) {
+ String[] row = {s, "patch", "1", "1"};
+ featureModel.addRow(row);
+ }
+ } else if (e.getActionCommand().equals("Remove feature")) {
+ featureModel.removeRow(featureTable.getSelectedRow());
+ } else if (e.getActionCommand().equals("Add creature")) {
+ Object[] creatures = Editor.resources.getResources(RCreature.class).toArray();
+ String s =
+ JOptionPane.showInputDialog(
+ frame,
+ "Choose creature:",
+ "Add creature",
+ JOptionPane.PLAIN_MESSAGE,
+ null,
+ creatures,
+ null)
+ .toString();
+ if (s != null) {
+ String[] row = {s, "1"};
+ creatureModel.addRow(row);
+ }
+ } else if (e.getActionCommand().equals("Remove creature")) {
+ creatureModel.removeRow(creatureTable.getSelectedRow());
+ }
+ }
+ }
+
+ private static class ThemesTableModel extends DefaultTableModel {
+ private Class>[] classes;
+
+ public ThemesTableModel(String[] columns, Class>... classes) {
+ super(columns, 0);
+ this.classes = classes;
+ }
+
+ public Class> getColumnClass(int i) {
+ return classes[i];
+ }
+
+ public boolean isCellEditable(int row, int column) {
+ return column != 0;
+ }
+ }
+}
diff --git a/src/main/java/neon/editor/resources/IObject.java b/src/main/java/neon/editor/resources/IObject.java
index 5cf1d66..2f1b726 100644
--- a/src/main/java/neon/editor/resources/IObject.java
+++ b/src/main/java/neon/editor/resources/IObject.java
@@ -30,6 +30,8 @@
public class IObject extends Instance {
public final int uid;
+ private JVShape cachedSvgShape;
+ private String cachedSvgContent;
public IObject(RData resource, int x, int y, int z, int uid) {
super(resource, x, y, z, 1, 1);
@@ -53,14 +55,20 @@ public IObject(Element properties) {
public void paint(Graphics2D graphics, float zoom, boolean isSelected) {
if (resource instanceof RItem && ((RItem) resource).svg != null) {
if (MapEditor.isVisible(this)) {
- JVShape shape = SVGLoader.loadShape(((RItem) resource).svg);
- shape.setX(x);
- shape.setY(y);
- width = shape.getBounds().width;
- height = shape.getBounds().height;
- shape.paint(graphics, zoom, isSelected);
+ // Only reload SVG shape if content has changed (caching optimization)
+ String currentSvg = ((RItem) resource).svg;
+ if (cachedSvgShape == null || !currentSvg.equals(cachedSvgContent)) {
+ cachedSvgShape = SVGLoader.loadShape(currentSvg);
+ cachedSvgContent = currentSvg;
+ }
+
+ cachedSvgShape.setX(x);
+ cachedSvgShape.setY(y);
+ width = cachedSvgShape.getBounds().width;
+ height = cachedSvgShape.getBounds().height;
+ cachedSvgShape.paint(graphics, zoom, isSelected);
if (isSelected) {
- graphics.setPaint(shape.getPaint());
+ graphics.setPaint(cachedSvgShape.getPaint());
Rectangle2D rect = new Rectangle2D.Float(x * zoom, y * zoom, width * zoom, height * zoom);
graphics.draw(rect);
}
diff --git a/src/main/java/neon/editor/resources/RMap.java b/src/main/java/neon/editor/resources/RMap.java
index 46e0dfc..d4f893a 100644
--- a/src/main/java/neon/editor/resources/RMap.java
+++ b/src/main/java/neon/editor/resources/RMap.java
@@ -1,224 +1,434 @@
-/*
- * Neon, a roguelike engine.
- * Copyright (C) 2013 - Maarten Driesen
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package neon.editor.resources;
-
-import java.util.*;
-import neon.editor.Editor;
-import neon.editor.maps.*;
-import neon.resources.RData;
-import neon.resources.RDungeonTheme;
-import neon.systems.files.XMLTranslator;
-import neon.ui.graphics.Renderable;
-import org.jdom2.*;
-
-/*
- * life cycle of a map:
- * A. existing map:
- * 1. load map
- * 2. load zones
- * B. new map:
- * a. dungeon
- * b. random dungeon
- * c. outdoor
- */
-public class RMap extends RData {
- // id van map = path
- public static final boolean DUNGEON = true;
- public HashMap zones = new HashMap();
- public RDungeonTheme theme;
- public short uid;
- private boolean type;
- private ArrayList uids;
-
- // for already existing maps during loadMod
- public RMap(String id, Element properties, String... path) {
- super(id, path);
- uid = Short.parseShort(properties.getChild("header").getAttributeValue("uid"));
- name = properties.getChild("header").getChildText("name");
- type = properties.getName().equals("dungeon");
-
- if (type == DUNGEON) {
- if (properties.getChild("header").getAttribute("theme") != null) {
- theme =
- (RDungeonTheme)
- Editor.resources.getResource(
- properties.getChild("header").getAttributeValue("theme"), "theme");
- } else {
- for (Element zone : properties.getChildren("level")) {
- zones.put(Integer.parseInt(zone.getAttributeValue("l")), new RZone(zone, this, path));
- }
- }
- } else {
- zones.put(0, new RZone(properties, this, path));
- }
- }
-
- // for new maps to be created
- public RMap(short uid, String mod, MapDialog.Properties props) {
- super(props.getID(), mod);
- this.uid = uid;
- type = props.isDungeon();
- name = props.getName();
-
- if (!props.isDungeon()) { // always set zone and base region for outdoor
- Element region = new Element("region");
- region.setAttribute("x", "0");
- region.setAttribute("y", "0");
- region.setAttribute("w", Integer.toString(props.getWidth()));
- region.setAttribute("h", Integer.toString(props.getHeight()));
- region.setAttribute("text", props.getTerrain());
- region.setAttribute("l", "0");
- Instance ri = new IRegion(region);
- RZone zone = new RZone(name, mod, ri, this);
- zones.put(0, zone);
- }
- }
-
- public void setName(String name) {
- this.name = name;
- }
-
- public boolean isDungeon() {
- return type;
- }
-
- public RZone getZone(int index) {
- if (zones.isEmpty()) {
- load();
- }
- return zones.get(index);
- }
-
- public int getZone(RZone zone) {
- if (zones.isEmpty()) {
- load();
- }
-
- for (Integer i : zones.keySet()) {
- if (zones.get(i) == zone) {
- return i;
- }
- }
- return 0;
- }
-
- public short getUID() {
- return uid;
- }
-
- public String toString() {
- return name;
- }
-
- // also remove objects from tree if needed!!!
- public void removeObjectUID(int uid) {
- uids.remove((Integer) uid); // because remove(int) removes the int'th value
- }
-
- // don't forget to add objects to the tree!!!
- public int createUID(Element e) {
- int hash = e.hashCode();
- while (uids.contains(hash)) {
- hash++;
- }
- uids.add(hash);
- return hash;
- }
-
- public Element toElement() {
- System.out.println("save map: " + name);
- Element root = new Element(isDungeon() ? "dungeon" : "world");
- Element header = new Element("header");
- header.setAttribute("uid", Short.toString(uid));
- header.addContent(new Element("name").setText(name));
- root.addContent(header);
- if (type == DUNGEON) {
- for (Integer level : zones.keySet()) {
- root.addContent(zones.get(level).toElement().setAttribute("l", level.toString()));
- }
- } else {
- RZone zone = zones.get(0);
- Element creatures = new Element("creatures");
- Element items = new Element("items");
- Element regions = new Element("regions");
- for (Renderable r : zone.getScene().getElements()) {
- Instance i = (Instance) r;
- Element element = i.toElement();
- element.detach();
- if (element.getName().equals("region")) {
- regions.addContent(element);
- } else if (element.getName().equals("creature")) {
- creatures.addContent(element);
- } else if (element.getName().equals("item")
- || element.getName().equals("door")
- || element.getName().equals("container")) {
- items.addContent(element);
- }
- }
- root.addContent(creatures);
- root.addContent(items);
- root.addContent(regions);
- }
-
- return root;
- }
-
- public void load() {
- if (uids == null) { // avoid loading map twice
- uids = new ArrayList();
- try {
- String file = Editor.getStore().getMod(path[0]).getPath()[0];
- Element root =
- Editor.files.getFile(new XMLTranslator(), file, "maps", id + ".xml").getRootElement();
-
- if (root.getName().equals("world")) {
- uids.addAll(zones.get(0).load(root));
- } else if (root.getName().equals("dungeon")) {
- for (Element level : root.getChildren("level")) {
- uids.addAll(zones.get(Integer.parseInt(level.getAttributeValue("l"))).load(level));
- }
- } else {
- System.out.println("fout in EditableMap.load(" + id + ")");
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
-
- /**
- * This method removes a zone from this map.
- *
- * @param level the zone to remove
- */
- public void removeZone(int level) {
- for (Renderable r : zones.get(level).getScene().getElements()) {
- Instance instance = (Instance) r;
- if (instance instanceof IObject) { // remove uids
- uids.remove(Integer.parseInt(instance.toElement().getAttributeValue("uid")));
- if (instance.toElement().getName().equals("container")) { // remove container contents
- for (Element e : instance.toElement().getChildren()) {
- uids.remove(Integer.parseInt(e.getAttributeValue("uid")));
- }
- }
- }
- }
- zones.remove(level);
- }
-}
+/*
+ * Neon, a roguelike engine.
+ * Copyright (C) 2013 - Maarten Driesen
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package neon.editor.resources;
+
+import java.util.*;
+import neon.editor.Editor;
+import neon.editor.maps.*;
+import neon.maps.model.DungeonModel;
+import neon.maps.model.WorldModel;
+import neon.resources.RData;
+import neon.resources.RDungeonTheme;
+import neon.resources.RScript;
+import neon.systems.files.XMLTranslator;
+import neon.ui.graphics.Renderable;
+import org.jdom2.*;
+
+/*
+ * life cycle of a map:
+ * A. existing map:
+ * 1. load map
+ * 2. load zones
+ * B. new map:
+ * a. dungeon
+ * b. random dungeon
+ * c. outdoor
+ */
+public class RMap extends RData {
+ // id van map = path
+ public static final boolean DUNGEON = true;
+ public HashMap zones = new HashMap();
+ public RDungeonTheme theme;
+ public short uid;
+ private boolean type;
+ private ArrayList uids;
+
+ // for already existing maps during loadMod
+ public RMap(String id, Element properties, String... path) {
+ super(id, path);
+ uid = Short.parseShort(properties.getChild("header").getAttributeValue("uid"));
+ name = properties.getChild("header").getChildText("name");
+ type = properties.getName().equals("dungeon");
+
+ if (type == DUNGEON) {
+ if (properties.getChild("header").getAttribute("theme") != null) {
+ theme =
+ (RDungeonTheme)
+ Editor.resources.getResource(
+ properties.getChild("header").getAttributeValue("theme"), "theme");
+ } else {
+ for (Element zone : properties.getChildren("level")) {
+ zones.put(Integer.parseInt(zone.getAttributeValue("l")), new RZone(zone, this, path));
+ }
+ }
+ } else {
+ zones.put(0, new RZone(properties, this, path));
+ }
+ }
+
+ // for new maps to be created
+ public RMap(short uid, String mod, MapDialog.Properties props) {
+ super(props.getID(), mod);
+ this.uid = uid;
+ type = props.isDungeon();
+ name = props.getName();
+
+ if (!props.isDungeon()) { // always set zone and base region for outdoor
+ Element region = new Element("region");
+ region.setAttribute("x", "0");
+ region.setAttribute("y", "0");
+ region.setAttribute("w", Integer.toString(props.getWidth()));
+ region.setAttribute("h", Integer.toString(props.getHeight()));
+ region.setAttribute("text", props.getTerrain());
+ region.setAttribute("l", "0");
+ Instance ri = new IRegion(region);
+ RZone zone = new RZone(name, mod, ri, this);
+ zones.put(0, zone);
+ }
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public boolean isDungeon() {
+ return type;
+ }
+
+ public RZone getZone(int index) {
+ if (zones.isEmpty()) {
+ load();
+ }
+ return zones.get(index);
+ }
+
+ public int getZone(RZone zone) {
+ if (zones.isEmpty()) {
+ load();
+ }
+
+ for (Integer i : zones.keySet()) {
+ if (zones.get(i) == zone) {
+ return i;
+ }
+ }
+ return 0;
+ }
+
+ public short getUID() {
+ return uid;
+ }
+
+ public String toString() {
+ return name;
+ }
+
+ // also remove objects from tree if needed!!!
+ public void removeObjectUID(int uid) {
+ uids.remove((Integer) uid); // because remove(int) removes the int'th value
+ }
+
+ // don't forget to add objects to the tree!!!
+ public int createUID(Element e) {
+ int hash = e.hashCode();
+ while (uids.contains(hash)) {
+ hash++;
+ }
+ uids.add(hash);
+ return hash;
+ }
+
+ public Element toElement() {
+ System.out.println("save map: " + name);
+ Element root = new Element(isDungeon() ? "dungeon" : "world");
+ Element header = new Element("header");
+ header.setAttribute("uid", Short.toString(uid));
+ header.addContent(new Element("name").setText(name));
+ root.addContent(header);
+ if (type == DUNGEON) {
+ for (Integer level : zones.keySet()) {
+ root.addContent(zones.get(level).toElement().setAttribute("l", level.toString()));
+ }
+ } else {
+ RZone zone = zones.get(0);
+ Element creatures = new Element("creatures");
+ Element items = new Element("items");
+ Element regions = new Element("regions");
+ for (Renderable r : zone.getScene().getElements()) {
+ Instance i = (Instance) r;
+ Element element = i.toElement();
+ element.detach();
+ if (element.getName().equals("region")) {
+ regions.addContent(element);
+ } else if (element.getName().equals("creature")) {
+ creatures.addContent(element);
+ } else if (element.getName().equals("item")
+ || element.getName().equals("door")
+ || element.getName().equals("container")) {
+ items.addContent(element);
+ }
+ }
+ root.addContent(creatures);
+ root.addContent(items);
+ root.addContent(regions);
+ }
+
+ return root;
+ }
+
+ /**
+ * Converts this map to a WorldModel for Jackson XML serialization.
+ *
+ * @return WorldModel representation
+ */
+ public WorldModel toWorldModel() {
+ WorldModel model = new WorldModel();
+
+ // Header
+ model.header = new WorldModel.Header();
+ model.header.uid = uid;
+ model.header.name = name;
+
+ RZone zone = zones.get(0);
+ for (Renderable r : zone.getScene().getElements()) {
+ Instance instance = (Instance) r;
+
+ if (instance instanceof IRegion) {
+ model.regions.add(convertRegion((IRegion) instance));
+ } else if (instance instanceof IObject) {
+ IObject obj = (IObject) instance;
+ if (instance instanceof IDoor) {
+ model.items.doors.add(convertDoor((IDoor) instance));
+ } else if (instance instanceof IContainer) {
+ model.items.containers.add(convertContainer((IContainer) instance));
+ } else {
+ model.items.items.add(convertItem(obj));
+ }
+ } else if (instance instanceof IPerson) {
+ model.creatures.add(convertCreature((IPerson) instance));
+ }
+ }
+
+ return model;
+ }
+
+ /**
+ * Converts this map to a DungeonModel for Jackson XML serialization.
+ *
+ * @return DungeonModel representation
+ */
+ public DungeonModel toDungeonModel() {
+ DungeonModel model = new DungeonModel();
+
+ // Header
+ model.header = new WorldModel.Header();
+ model.header.uid = uid;
+ model.header.name = name;
+
+ // Levels
+ for (Integer levelNum : zones.keySet()) {
+ RZone zone = zones.get(levelNum);
+ DungeonModel.Level level = new DungeonModel.Level();
+ level.l = levelNum;
+ level.name = zone.name;
+
+ if (zone.theme != null) {
+ level.theme = zone.theme.id;
+ // Theme-based zones don't have explicit content
+ } else {
+ // Explicit zone content
+ for (Renderable r : zone.getScene().getElements()) {
+ Instance instance = (Instance) r;
+
+ if (instance instanceof IRegion) {
+ level.regions.add(convertRegion((IRegion) instance));
+ } else if (instance instanceof IObject) {
+ IObject obj = (IObject) instance;
+ if (instance instanceof IDoor) {
+ level.items.doors.add(convertDoor((IDoor) instance));
+ } else if (instance instanceof IContainer) {
+ level.items.containers.add(convertContainer((IContainer) instance));
+ } else {
+ level.items.items.add(convertItem(obj));
+ }
+ } else if (instance instanceof IPerson) {
+ level.creatures.add(convertCreature((IPerson) instance));
+ }
+ }
+ }
+
+ model.levels.add(level);
+ }
+
+ return model;
+ }
+
+ private WorldModel.RegionData convertRegion(IRegion region) {
+ WorldModel.RegionData data = new WorldModel.RegionData();
+ data.x = region.x;
+ data.y = region.y;
+ data.w = region.width;
+ data.h = region.height;
+ data.l = (byte) region.z;
+ data.text = region.resource.id;
+ if (region.theme != null) {
+ data.random = region.theme.id;
+ }
+ if (region.label != null && !region.label.isEmpty()) {
+ data.label = region.label;
+ }
+ for (RScript script : region.scripts) {
+ WorldModel.RegionData.ScriptReference scriptRef = new WorldModel.RegionData.ScriptReference();
+ scriptRef.id = script.id;
+ data.scripts.add(scriptRef);
+ }
+ return data;
+ }
+
+ private WorldModel.CreaturePlacement convertCreature(IPerson person) {
+ WorldModel.CreaturePlacement cp = new WorldModel.CreaturePlacement();
+ cp.x = person.x;
+ cp.y = person.y;
+ cp.id = person.resource.id;
+ cp.uid = person.uid;
+ return cp;
+ }
+
+ private WorldModel.ItemPlacement convertItem(IObject obj) {
+ WorldModel.ItemPlacement ip = new WorldModel.ItemPlacement();
+ ip.x = obj.x;
+ ip.y = obj.y;
+ ip.id = obj.resource.id;
+ ip.uid = obj.uid;
+ return ip;
+ }
+
+ private WorldModel.DoorPlacement convertDoor(IDoor door) {
+ WorldModel.DoorPlacement dp = new WorldModel.DoorPlacement();
+ dp.x = door.x;
+ dp.y = door.y;
+ dp.id = door.resource.id;
+ dp.uid = door.uid;
+
+ if (door.state != null) {
+ dp.state = door.state.toString().toLowerCase();
+ }
+ if (door.lock > 0) {
+ dp.lock = door.lock;
+ }
+ if (door.key != null) {
+ dp.key = door.key.id;
+ }
+ if (door.trap > 0) {
+ dp.trap = door.trap;
+ }
+ if (door.spell != null) {
+ dp.spell = door.spell.id;
+ }
+
+ // Destination
+ if (door.destMap != null || door.destTheme != null) {
+ dp.destination = new WorldModel.DoorPlacement.Destination();
+ if (door.destTheme != null) {
+ dp.destination.theme = door.destTheme.id;
+ } else {
+ if (door.destPos != null) {
+ dp.destination.x = door.destPos.x;
+ dp.destination.y = door.destPos.y;
+ }
+ if (door.destZone != null && door.destMap != null) {
+ dp.destination.z = door.destMap.getZone(door.destZone);
+ }
+ if (door.destMap != null) {
+ dp.destination.map = (int) door.destMap.uid;
+ }
+ }
+ if (door.text != null && !door.text.isEmpty()) {
+ dp.destination.sign = door.text;
+ }
+ }
+
+ return dp;
+ }
+
+ private WorldModel.ContainerPlacement convertContainer(IContainer container) {
+ WorldModel.ContainerPlacement cp = new WorldModel.ContainerPlacement();
+ cp.x = container.x;
+ cp.y = container.y;
+ cp.id = container.resource.id;
+ cp.uid = container.uid;
+
+ if (container.lock > 0) {
+ cp.lock = container.lock;
+ }
+ if (container.key != null) {
+ cp.key = container.key.id;
+ }
+ if (container.trap > 0) {
+ cp.trap = container.trap;
+ }
+ if (container.spell != null) {
+ cp.spell = container.spell.id;
+ }
+
+ // Contents
+ for (IObject item : container.contents) {
+ WorldModel.ContainerPlacement.ContainerItem ci =
+ new WorldModel.ContainerPlacement.ContainerItem();
+ ci.id = item.resource.id;
+ ci.uid = item.uid;
+ cp.contents.add(ci);
+ }
+
+ return cp;
+ }
+
+ public void load() {
+ if (uids == null) { // avoid loading map twice
+ uids = new ArrayList();
+ try {
+ String file = Editor.getStore().getMod(path[0]).getPath()[0];
+ Element root =
+ Editor.files.getFile(new XMLTranslator(), file, "maps", id + ".xml").getRootElement();
+
+ if (root.getName().equals("world")) {
+ uids.addAll(zones.get(0).load(root));
+ } else if (root.getName().equals("dungeon")) {
+ for (Element level : root.getChildren("level")) {
+ uids.addAll(zones.get(Integer.parseInt(level.getAttributeValue("l"))).load(level));
+ }
+ } else {
+ System.out.println("fout in EditableMap.load(" + id + ")");
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ /**
+ * This method removes a zone from this map.
+ *
+ * @param level the zone to remove
+ */
+ public void removeZone(int level) {
+ for (Renderable r : zones.get(level).getScene().getElements()) {
+ Instance instance = (Instance) r;
+ if (instance instanceof IObject) { // remove uids
+ uids.remove(Integer.parseInt(instance.toElement().getAttributeValue("uid")));
+ if (instance.toElement().getName().equals("container")) { // remove container contents
+ for (Element e : instance.toElement().getChildren()) {
+ uids.remove(Integer.parseInt(e.getAttributeValue("uid")));
+ }
+ }
+ }
+ }
+ zones.remove(level);
+ }
+}
diff --git a/src/main/java/neon/entities/UIDStore.java b/src/main/java/neon/entities/UIDStore.java
index 88bddad..8412ac2 100644
--- a/src/main/java/neon/entities/UIDStore.java
+++ b/src/main/java/neon/entities/UIDStore.java
@@ -22,6 +22,7 @@
import com.google.common.collect.HashBiMap;
import java.io.*;
import java.util.Map;
+import lombok.extern.slf4j.Slf4j;
import neon.maps.services.EntityStore;
import org.h2.mvstore.MVStore;
@@ -32,6 +33,7 @@
*
* @author mdriesen
*/
+@Slf4j
public class UIDStore implements EntityStore, Closeable {
// dummy uid for objects that don't actually exist
public static final long DUMMY = 0;
@@ -73,8 +75,18 @@ public short getModUID(String name) {
return mod.uid;
}
}
- System.out.println("Mod " + name + " not found");
- return 0;
+ throw new RuntimeException("Mod " + name + " not found");
+ // System.out.println("Mod " + name + " not found");
+ // return 0;
+ }
+
+ public boolean isModUIDLoaded(String name) {
+ for (Mod mod : mods.values()) {
+ if (mod.name.equals(name)) {
+ return true;
+ }
+ }
+ return false;
}
/**
@@ -149,6 +161,10 @@ public String[] getMapPath(int uid) {
* @return the uid of the given map
*/
public int getMapUID(String... path) {
+ var uid = maps.inverse().get(toString(path));
+ if (uid == null) {
+ log.warn("{} doesn't have uid", (Object) path);
+ }
return maps.inverse().get(toString(path));
}
diff --git a/src/main/java/neon/maps/Atlas.java b/src/main/java/neon/maps/Atlas.java
index 5921c9a..4099ce0 100644
--- a/src/main/java/neon/maps/Atlas.java
+++ b/src/main/java/neon/maps/Atlas.java
@@ -51,6 +51,7 @@ public class Atlas implements Closeable, MapAtlas {
private final ResourceProvider resourceProvider;
private final QuestProvider questProvider;
private final ZoneActivator zoneActivator;
+ private final MapLoader mapLoader;
/**
* Initializes this {@code Atlas} with the given {@code FileSystem} and cache path. The cache is
@@ -98,17 +99,17 @@ public Atlas(
// files.delete(path);
// String fileName = files.getFullPath(path);
// log.warn("Creating new MVStore at {}", fileName);
-
+ this.mapLoader = new MapLoader(this.entityStore, this.resourceProvider);
// db = MVStore.open(fileName);
maps = atlasStore.openMap("maps");
}
private static MVStore getMVStore(FileSystem files, String path) {
files.delete(path);
- String fileName = files.getFullPath(path);
- log.warn("Creating new MVStore at {}", fileName);
- return MVStore.open(fileName);
+ log.warn("Creating new MVStore at {}", path);
+
+ return MVStore.open(path);
}
/**
@@ -152,13 +153,22 @@ public int getCurrentZoneIndex() {
@Override
public Map getMap(int uid) {
if (!maps.containsKey(uid)) {
- Map map = MapLoader.loadMap(entityStore.getMapPath(uid), uid, files);
+ if (entityStore.getMapPath(uid) == null) {
+ throw new RuntimeException(String.format("No existing mappath for uid %d", uid));
+ }
+ Map map = mapLoader.load(entityStore.getMapPath(uid), uid, files);
System.out.println("Loaded map " + map.toString());
maps.put(uid, map);
}
return maps.get(uid);
}
+ @Override
+ public Map getMap(int uid, String... path) {
+ Map map = mapLoader.load(path, uid, files);
+ return map;
+ }
+
/**
* Sets the current zone.
*
diff --git a/src/main/java/neon/maps/MapLoader.java b/src/main/java/neon/maps/MapLoader.java
index 51f51e3..399225f 100644
--- a/src/main/java/neon/maps/MapLoader.java
+++ b/src/main/java/neon/maps/MapLoader.java
@@ -19,6 +19,8 @@
package neon.maps;
import java.awt.Point;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
import neon.core.*;
import neon.entities.Container;
import neon.entities.Creature;
@@ -28,6 +30,8 @@
import neon.entities.UIDStore;
import neon.entities.components.Enchantment;
import neon.entities.components.Lock;
+import neon.maps.model.DungeonModel;
+import neon.maps.model.WorldModel;
import neon.maps.services.EngineEntityStore;
import neon.maps.services.EngineResourceProvider;
import neon.maps.services.EntityStore;
@@ -39,8 +43,11 @@
import neon.resources.RTerrain;
import neon.resources.RZoneTheme;
import neon.systems.files.FileSystem;
+import neon.systems.files.JacksonMapper;
import neon.systems.files.XMLTranslator;
import org.jdom2.*;
+import org.jdom2.output.XMLOutputter;
+import org.jetbrains.annotations.NotNull;
/**
* This class loads a map from an xml file.
@@ -86,20 +93,6 @@ private static MapLoader createDefault() {
return new MapLoader(new EngineEntityStore(), new EngineResourceProvider());
}
- /**
- * Returns a map described in an xml file with the given name.
- *
- * @param path the pathname of a map file
- * @param uid the unique identifier of this map
- * @param files the file system
- * @return the Map described by the map file
- * @deprecated Use instance method {@link #load(String[], int, FileSystem)} instead
- */
- @Deprecated
- public static Map loadMap(String[] path, int uid, FileSystem files) {
- return createDefault().load(path, uid, files);
- }
-
/**
* Returns a map described in an xml file with the given name (instance method).
*
@@ -108,10 +101,15 @@ public static Map loadMap(String[] path, int uid, FileSystem files) {
* @param files the file system
* @return the Map described by the map file
*/
- public Map load(String[] path, int uid, FileSystem files) {
+ public Map load(@NotNull String[] path, int uid, FileSystem files) {
+ // For now, use JDOM to determine type, then build models
+ // In the future, FileSystem can provide InputStream directly
+
Document doc = files.getFile(new XMLTranslator(), path);
Element root = doc.getRootElement();
+
if (root.getName().equals("world")) {
+
return loadWorld(root, uid);
} else {
return loadDungeon(root, uid);
@@ -138,39 +136,41 @@ public static Dungeon loadDungeon(String theme) {
}
private World loadWorld(Element root, int uid) {
- World world = new World(root.getChild("header").getChildText("name"), uid);
- loadZone(root, world, 0, uid); // outdoor has only 1 zone, namely 0
- return world;
+ try {
+ // Convert JDOM Element to XML string
+ XMLOutputter outputter = new XMLOutputter();
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ outputter.output(root, out);
+
+ // Parse with Jackson
+ JacksonMapper mapper = new JacksonMapper();
+ ByteArrayInputStream input = new ByteArrayInputStream(out.toByteArray());
+ WorldModel model = mapper.fromXml(input, WorldModel.class);
+
+ // Build World from model
+ return buildWorldFromModel(model, uid);
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to load world map", e);
+ }
}
private Dungeon loadDungeon(Element root, int uid) {
- if (root.getChild("header").getAttribute("theme") != null) {
- String name = root.getChild("header").getChildText("name");
- return loadThemedDungeon(name, root.getChild("header").getAttributeValue("theme"), uid);
- }
-
- Dungeon map = new Dungeon(root.getChild("header").getChildText("name"), uid);
-
- for (Element l : root.getChildren("level")) {
- int level = Integer.parseInt(l.getAttributeValue("l"));
- String name = l.getAttributeValue("name");
- if (l.getAttribute("theme") != null) {
- RZoneTheme theme =
- (RZoneTheme) resourceProvider.getResource(l.getAttributeValue("theme"), "theme");
- map.addZone(level, name, theme);
- if (l.getAttribute("out") != null) {
- String[] connections = l.getAttributeValue("out").split(",");
- for (String connection : connections) {
- map.addConnection(level, Integer.parseInt(connection));
- }
- }
- } else {
- map.addZone(level, name);
- loadZone(l, map, level, uid);
- }
+ try {
+ // Convert JDOM Element to XML string
+ XMLOutputter outputter = new XMLOutputter();
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ outputter.output(root, out);
+
+ // Parse with Jackson
+ JacksonMapper mapper = new JacksonMapper();
+ ByteArrayInputStream input = new ByteArrayInputStream(out.toByteArray());
+ DungeonModel model = mapper.fromXml(input, DungeonModel.class);
+
+ // Build Dungeon from model
+ return buildDungeonFromModel(model, uid);
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to load dungeon map", e);
}
-
- return map;
}
private Dungeon loadThemedDungeon(String name, String dungeon, int uid) {
@@ -203,6 +203,294 @@ private Dungeon loadThemedDungeon(String name, String dungeon, int uid) {
return map;
}
+ /**
+ * Builds a World from a WorldModel (Jackson-parsed structure).
+ *
+ * @param model the WorldModel from Jackson XML parsing
+ * @param uid the unique identifier for this map
+ * @return the constructed World
+ */
+ private World buildWorldFromModel(WorldModel model, int uid) {
+ World world = new World(model.header.name, uid);
+ Zone zone = world.getZone(0); // outdoor maps have only zone 0
+
+ // Add regions
+ for (WorldModel.RegionData regionData : model.regions) {
+ Region region = buildRegionFromModel(regionData);
+ zone.addRegion(region);
+ }
+
+ // Add creatures
+ for (WorldModel.CreaturePlacement cp : model.creatures) {
+ long creatureUID = UIDStore.getObjectUID(uid, cp.uid);
+ Creature creature = EntityFactory.getCreature(cp.id, cp.x, cp.y, creatureUID);
+ entityStore.addEntity(creature);
+ zone.addCreature(creature);
+ }
+
+ // Add items (simple items)
+ for (WorldModel.ItemPlacement ip : model.items.items) {
+ long itemUID = UIDStore.getObjectUID(uid, ip.uid);
+ Item item = EntityFactory.getItem(ip.id, ip.x, ip.y, itemUID);
+ entityStore.addEntity(item);
+ zone.addItem(item);
+ }
+
+ // Add doors
+ for (WorldModel.DoorPlacement dp : model.items.doors) {
+ long doorUID = UIDStore.getObjectUID(uid, dp.uid);
+ Door door = buildDoorFromModel(dp, uid, doorUID);
+ entityStore.addEntity(door);
+ zone.addItem(door);
+ }
+
+ // Add containers
+ for (WorldModel.ContainerPlacement cp : model.items.containers) {
+ long containerUID = UIDStore.getObjectUID(uid, cp.uid);
+ Container container = buildContainerFromModel(cp, uid, containerUID);
+ entityStore.addEntity(container);
+ zone.addItem(container);
+ }
+
+ return world;
+ }
+
+ /**
+ * Builds a Dungeon from a DungeonModel (Jackson-parsed structure).
+ *
+ * @param model the DungeonModel from Jackson XML parsing
+ * @param uid the unique identifier for this map
+ * @return the constructed Dungeon
+ */
+ private Dungeon buildDungeonFromModel(DungeonModel model, int uid) {
+ // Check for themed dungeon
+ if (model.header.theme != null) {
+ return loadThemedDungeon(model.header.name, model.header.theme, uid);
+ }
+
+ Dungeon dungeon = new Dungeon(model.header.name, uid);
+
+ for (DungeonModel.Level levelData : model.levels) {
+ int level = levelData.l;
+ String name = levelData.name;
+
+ if (levelData.theme != null) {
+ // Themed zone - add theme reference
+ RZoneTheme theme = (RZoneTheme) resourceProvider.getResource(levelData.theme, "theme");
+ dungeon.addZone(level, name, theme);
+
+ if (levelData.out != null) {
+ String[] connections = levelData.out.split(",");
+ for (String connection : connections) {
+ dungeon.addConnection(level, Integer.parseInt(connection.trim()));
+ }
+ }
+ } else {
+ // Explicit zone - load all content
+ dungeon.addZone(level, name);
+ Zone zone = dungeon.getZone(level);
+
+ // Add regions
+ for (WorldModel.RegionData regionData : levelData.regions) {
+ zone.addRegion(buildRegionFromModel(regionData));
+ }
+
+ // Add creatures
+ for (WorldModel.CreaturePlacement cp : levelData.creatures) {
+ long creatureUID = UIDStore.getObjectUID(uid, cp.uid);
+ Creature creature = EntityFactory.getCreature(cp.id, cp.x, cp.y, creatureUID);
+ entityStore.addEntity(creature);
+ zone.addCreature(creature);
+ }
+
+ // Add items
+ for (WorldModel.ItemPlacement ip : levelData.items.items) {
+ long itemUID = UIDStore.getObjectUID(uid, ip.uid);
+ Item item = EntityFactory.getItem(ip.id, ip.x, ip.y, itemUID);
+ entityStore.addEntity(item);
+ zone.addItem(item);
+ }
+
+ // Add doors
+ for (WorldModel.DoorPlacement dp : levelData.items.doors) {
+ long doorUID = UIDStore.getObjectUID(uid, dp.uid);
+ Door door = buildDoorFromModel(dp, uid, doorUID);
+ entityStore.addEntity(door);
+ zone.addItem(door);
+ }
+
+ // Add containers
+ for (WorldModel.ContainerPlacement cp : levelData.items.containers) {
+ long containerUID = UIDStore.getObjectUID(uid, cp.uid);
+ Container container = buildContainerFromModel(cp, uid, containerUID);
+ entityStore.addEntity(container);
+ zone.addItem(container);
+ }
+ }
+ }
+
+ return dungeon;
+ }
+
+ /**
+ * Builds a Region from RegionData model.
+ *
+ * @param regionData the region data from model
+ * @return the constructed Region
+ */
+ private Region buildRegionFromModel(WorldModel.RegionData regionData) {
+ RTerrain terrain = (RTerrain) resourceProvider.getResource(regionData.text, "terrain");
+ RRegionTheme theme = (RRegionTheme) resourceProvider.getResource(regionData.random, "theme");
+
+ Region region =
+ new Region(
+ regionData.text,
+ regionData.x,
+ regionData.y,
+ regionData.w,
+ regionData.h,
+ theme,
+ regionData.l,
+ terrain);
+
+ region.setLabel(regionData.label);
+
+ for (WorldModel.RegionData.ScriptReference script : regionData.scripts) {
+ region.addScript(script.id, false);
+ }
+
+ return region;
+ }
+
+ /**
+ * Builds a Door from DoorPlacement model.
+ *
+ * @param doorData the door placement data
+ * @param mapUID the map UID
+ * @param doorUID the door entity UID
+ * @return the constructed Door
+ */
+ private Door buildDoorFromModel(WorldModel.DoorPlacement doorData, int mapUID, long doorUID) {
+ Door door = (Door) EntityFactory.getItem(doorData.id, doorData.x, doorData.y, doorUID);
+
+ // Lock
+ if (doorData.lock != null) {
+ door.lock.setLockDC(doorData.lock);
+ }
+
+ // Key
+ if (doorData.key != null) {
+ RItem key = (RItem) resourceProvider.getResource(doorData.key);
+ door.lock.setKey(key);
+ }
+
+ // State
+ if (doorData.state != null) {
+ if (doorData.state.equals("locked")) {
+ if (doorData.lock != null && doorData.lock > 0) {
+ door.lock.setState(Lock.LOCKED);
+ } else {
+ door.lock.setState(Lock.CLOSED);
+ }
+ } else if (doorData.state.equals("closed")) {
+ door.lock.setState(Lock.CLOSED);
+ }
+ }
+
+ // Trap
+ if (doorData.trap != null) {
+ door.trap.setTrapDC(doorData.trap);
+ }
+
+ // Spell
+ if (doorData.spell != null) {
+ RSpell.Enchantment enchantment =
+ (RSpell.Enchantment) resourceProvider.getResource(doorData.spell, "magic");
+ door.setMagicComponent(new Enchantment(enchantment, 0, door.getUID()));
+ }
+
+ // Destination
+ if (doorData.destination != null) {
+ WorldModel.DoorPlacement.Destination dest = doorData.destination;
+ Point destPos = null;
+ int destLevel = 0;
+ int destMapUID = 0;
+
+ if (dest.x != null && dest.y != null) {
+ destPos = new Point(dest.x, dest.y);
+ }
+ if (dest.z != null) {
+ destLevel = dest.z;
+ }
+ if (dest.map != null) {
+ destMapUID = (mapUID & 0xFFFF0000) + dest.map;
+ }
+
+ door.portal.setDestination(destPos, destLevel, destMapUID);
+ door.portal.setDestTheme(dest.theme);
+ door.setSign(dest.sign);
+ }
+
+ return door;
+ }
+
+ /**
+ * Builds a Container from ContainerPlacement model.
+ *
+ * @param containerData the container placement data
+ * @param mapUID the map UID
+ * @param containerUID the container entity UID
+ * @return the constructed Container
+ */
+ private Container buildContainerFromModel(
+ WorldModel.ContainerPlacement containerData, int mapUID, long containerUID) {
+ Container container =
+ (Container)
+ EntityFactory.getItem(containerData.id, containerData.x, containerData.y, containerUID);
+
+ // Lock
+ if (containerData.lock != null) {
+ container.lock.setLockDC(containerData.lock);
+ container.lock.setState(Lock.LOCKED);
+ }
+
+ // Key
+ if (containerData.key != null) {
+ RItem key = (RItem) resourceProvider.getResource(containerData.key);
+ container.lock.setKey(key);
+ }
+
+ // Trap
+ if (containerData.trap != null) {
+ container.trap.setTrapDC(containerData.trap);
+ }
+
+ // Spell
+ if (containerData.spell != null) {
+ RSpell.Enchantment enchantment =
+ (RSpell.Enchantment) resourceProvider.getResource(containerData.spell, "magic");
+ container.setMagicComponent(new Enchantment(enchantment, 0, container.getUID()));
+ }
+
+ // Contents
+ if (!containerData.contents.isEmpty()) {
+ for (WorldModel.ContainerPlacement.ContainerItem contentData : containerData.contents) {
+ long contentUID = UIDStore.getObjectUID(mapUID, contentData.uid);
+ entityStore.addEntity(EntityFactory.getItem(contentData.id, contentUID));
+ container.addItem(contentUID);
+ }
+ } else {
+ // Default items from resource definition
+ for (String itemId : ((RItem.Container) container.resource).contents) {
+ Item item = EntityFactory.getItem(itemId, entityStore.createNewEntityUID());
+ entityStore.addEntity(item);
+ container.addItem(item.getUID());
+ }
+ }
+
+ return container;
+ }
+
private void loadZone(Element root, Map map, int l, int uid) {
for (Element region : root.getChild("regions").getChildren()) { // load regions
map.getZone(l).addRegion(loadRegion(region));
diff --git a/src/main/java/neon/maps/generators/DungeonGenerator.java b/src/main/java/neon/maps/generators/DungeonGenerator.java
index e6dce6c..088010e 100644
--- a/src/main/java/neon/maps/generators/DungeonGenerator.java
+++ b/src/main/java/neon/maps/generators/DungeonGenerator.java
@@ -372,20 +372,20 @@ int[][] generateBaseTiles(String type, int width, int height) {
return tiles;
}
- private void generateFeatures(Collection