From f7994cd4dcb1b1490d7c3c157485c68bba64f28b Mon Sep 17 00:00:00 2001 From: nisovin Date: Sat, 4 Dec 2010 15:55:40 +0800 Subject: [PATCH 01/39] Updated for server v.0.2.8 --- FetchBlockQuest.java | 2 +- NonPlayerCharacter.java | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/FetchBlockQuest.java b/FetchBlockQuest.java index 2b489a6..3c96c0b 100644 --- a/FetchBlockQuest.java +++ b/FetchBlockQuest.java @@ -118,7 +118,7 @@ public void onPlayerMove(Player player, Location from, Location to) { } public void sendFakeBlockPacket(Player player, int x, int y, int z, int type) { - fm packet = new fm(); + fn packet = new fn(); packet.a = x; packet.b = y; packet.c = z; diff --git a/NonPlayerCharacter.java b/NonPlayerCharacter.java index 008cb29..bec0703 100644 --- a/NonPlayerCharacter.java +++ b/NonPlayerCharacter.java @@ -4,26 +4,26 @@ public abstract class NonPlayerCharacter { public static List players; - private es user; - private gv handler; + private et user; + private gw handler; public NonPlayerCharacter(String name, double x, double y, double z, float rotation, float pitch, int itemInHand) { if (players == null) getPlayerList(); MinecraftServer s = etc.getServer().getMCServer(); - user = new es(s, s.e, name, new ju(s.e)); + user = new et(s, s.e, name, new jv(s.e)); teleportTo(x,y,z,rotation,pitch); if (itemInHand > 0) { setItemInHand(itemInHand); } - handler = new gv(user, 512, 1 , true ); + handler = new gw(user, 512, 1 , true ); } public void delete() { for (Object player : players) { - ((es)player).a.b(new dh(handler.a.g)); + ((et)player).a.b(new di(handler.a.g)); } } @@ -94,7 +94,7 @@ public int getItemInHand() { } public void setItemInHand(int type) { - user.am.a[0] = new hm(type); + user.am.a[0] = new hn(type); } public void teleportTo(double x, double y, double z, float rotation, float pitch) { From ef3dd50ab51f7d533093c17304c5829e1b9cd659 Mon Sep 17 00:00:00 2001 From: durron597 Date: Mon, 6 Dec 2010 15:39:13 -0500 Subject: [PATCH 02/39] Added rankreq and rankreward functionality Cleaned up some warnings --- Craftizen.java | 11 +++++-- CraftizenSQLDataSource.java | 38 +++++++++++++++++++----- Craftizens.java | 8 +---- CraftizensListener.java | 59 ++++++++++++++++++++++++++++++++----- Quest.java | 27 +++++++++++++++++ QuestInfo.java | 40 +++++++++++++++++++++++-- 6 files changed, 155 insertions(+), 28 deletions(-) diff --git a/Craftizen.java b/Craftizen.java index 199fc8a..e75fe63 100644 --- a/Craftizen.java +++ b/Craftizen.java @@ -1,5 +1,3 @@ -import net.minecraft.server.MinecraftServer; -import java.util.List; import java.util.ArrayList; import java.util.logging.*; import java.util.Random; @@ -154,7 +152,14 @@ public String getId() { public String getName() { return name; } - + + /** + * @return the speed + */ + public double getSpeed() { + return speed; + } + public enum RouteType { NONE, WANDER, PATROL } diff --git a/CraftizenSQLDataSource.java b/CraftizenSQLDataSource.java index 9339d77..6ae71de 100644 --- a/CraftizenSQLDataSource.java +++ b/CraftizenSQLDataSource.java @@ -246,7 +246,9 @@ public QuestInfo loadQuestInfo(String id) { results.getString("rewards"), results.getString("location"), results.getString("data"), - results.getString("completion_text") + results.getString("completion_text"), + results.getString("rankreq"), + results.getString("rankreward") ); } } catch (SQLException e) { @@ -281,12 +283,25 @@ public ArrayList getAvailableQuests(Craftizen c, Player p) { "LEFT JOIN quests_completed qc2 ON qc2.player_name = ? AND qc2.quest_id = q.id " + "LEFT JOIN quests_active qa ON qa.player_name = ? AND qa.quest_id = q.id " + "WHERE c.npc_id = ? AND (q.prereq IS NULL OR qc.date_completed IS NOT NULL) " + - "AND qc2.quest_id IS NULL and qa.quest_id IS NULL " + "AND qc2.quest_id IS NULL and qa.quest_id IS NULL " + + "AND (q.rankreq IS NULL OR q.rankreq = ?) " ); query.setString(1, p.getName().toLowerCase()); query.setString(2, p.getName().toLowerCase()); query.setString(3, p.getName().toLowerCase()); query.setString(4, c.getId()); + + String[] groups = p.getGroups(); + if (groups.length > 0) { + if(groups[0] == null || groups[0].equals("")) { + query.setString(5, ""); + } else { + query.setString(5, groups[0]); + } + } else { + query.setString(5, ""); + } + results = query.executeQuery(); while (results.next()) { QuestInfo q = new QuestInfo( @@ -301,7 +316,9 @@ public ArrayList getAvailableQuests(Craftizen c, Player p) { results.getString("rewards"), results.getString("location"), results.getString("data"), - results.getString("completion_text") + results.getString("completion_text"), + results.getString("rankreq"), + results.getString("rankreward") ); quests.add(q); } @@ -350,7 +367,9 @@ public HashMap getActiveQuests(Player p) { results.getString("rewards"), results.getString("location"), results.getString("data"), - results.getString("completion_text") + results.getString("completion_text"), + results.getString("rankreq"), + results.getString("rankreward") ); String s = results.getString("progress"); quests.put(q,s); @@ -497,7 +516,8 @@ public void saveQuest(QuestInfo quest) { "quest_name = ?, quest_type = ?, quest_desc = ?, " + "start_npc = ?, end_npc = ?, prereq = ?, " + "items_provided = ?, rewards = ?, " + - "location = ?, data = ?, completion_text = ? " + + "location = ?, data = ?, completion_text = ?, " + + "rankreq = ?, rankreward = ? " + "WHERE id = ?"); query.setString(1,quest.name); query.setString(2,quest.type); @@ -510,10 +530,12 @@ public void saveQuest(QuestInfo quest) { query.setString(9,quest.location); query.setString(10,quest.data); query.setString(11,quest.completionText); - query.setString(12,quest.id); + query.setString(12,quest.rankReq); + query.setString(13,quest.rankReward); + query.setString(14,quest.id); query.executeUpdate(); } else { - query = conn.prepareStatement("INSERT INTO quests (id, quest_name, quest_type, quest_desc, start_npc, end_npc, prereq, items_provided, rewards, location, data, completion_text) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + query = conn.prepareStatement("INSERT INTO quests (id, quest_name, quest_type, quest_desc, start_npc, end_npc, prereq, items_provided, rewards, location, data, completion_text, rankreq, rankreward) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); query.setString(1,quest.id); query.setString(2,quest.name); query.setString(3,quest.type); @@ -526,6 +548,8 @@ public void saveQuest(QuestInfo quest) { query.setString(10,quest.location); query.setString(11,quest.data); query.setString(12,quest.completionText); + query.setString(13,quest.rankReq); + query.setString(14,quest.rankReward); query.executeUpdate(); } } catch (SQLException e) { diff --git a/Craftizens.java b/Craftizens.java index 4967411..6a80a21 100644 --- a/Craftizens.java +++ b/Craftizens.java @@ -1,12 +1,6 @@ import java.util.HashSet; import java.util.HashMap; import java.util.ArrayList; -import java.util.List; -import java.io.File; -import java.io.FileWriter; -import java.io.BufferedWriter; -import java.io.IOException; -import java.util.Scanner; import java.util.logging.*; public class Craftizens extends Plugin { @@ -73,7 +67,7 @@ public void enable() { CraftizensListener.loadActiveQuests(p); } - log.info("Craftizens v0.6 loaded successfully!"); + log.info("Craftizens v0.7 loaded successfully!"); } public void disable() { diff --git a/CraftizensListener.java b/CraftizensListener.java index 2b36fad..7885b17 100644 --- a/CraftizensListener.java +++ b/CraftizensListener.java @@ -1,4 +1,3 @@ -import net.minecraft.server.MinecraftServer; import java.util.logging.*; import java.util.ArrayList; import java.util.HashMap; @@ -20,8 +19,7 @@ public boolean onCommand(Player player, String [] split) { return false; } - public void npcCommand(Player player, String [] command) { - MinecraftServer s = etc.getServer().getMCServer(); + public void npcCommand(Player player, String [] command) { if (command[1].equals("clear")) { for (Craftizen npc : Craftizens.npcs) { npc.delete(); @@ -68,14 +66,21 @@ public void npcCommand(Player player, String [] command) { } + @SuppressWarnings("unchecked") public void questCommand(Player player, String [] command) { if (command.length == 1) { questCommandUsage(player); } else if ("view".startsWith(command[1].toLowerCase()) && command.length == 3) { if (Craftizens.pendingQuests.containsKey(player.getName())) { Object o = Craftizens.pendingQuests.get(player.getName()); - if (o instanceof ArrayList) { - ArrayList quests = (ArrayList)o; + if (o instanceof ArrayList) { + ArrayList quests = null; + try { +// TODO: this is extremely poor programming practice. Need to refactor/fix later. + quests = (ArrayList) o; + } catch (ClassCastException e) { + Craftizen.log.warning("[Craftizen] Pending quests was not an ArrayList!"); + } int i = -1; try { i = Integer.parseInt(command[2]); @@ -251,8 +256,10 @@ public void qadminCommand(Player player, String [] command) { player.sendMessage("End NPC: " + q.turnIn); player.sendMessage("Prereq quest: " + q.prereq); player.sendMessage("Items prov: " + q.itemsProvidedStr); - player.sendMessage("Rewards: " + q.rewardsStr); - player.sendMessage("Comp text: " + ((q.completionText.length()>50)?q.completionText.substring(0,50)+"...":q.completionText)); + player.sendMessage("Rewards: " + (q.rewardsStr == null ? "None" : q.rewardsStr)); + player.sendMessage("Rank requirement: " + q.rankReq); + player.sendMessage("Rank reward: " + q.rankReward); + player.sendMessage("Comp text: " + ((q.completionText != null && q.completionText.length()>50)?q.completionText.substring(0,50)+"...":q.completionText)); player.sendMessage("Data: " + q.data); q = null; } else { @@ -412,6 +419,42 @@ public void qadminCommand(Player player, String [] command) { player.sendMessage("Use /qadmin prereq -delete to remove the prereq quest."); } + // rankreq + } else if (command[1].equalsIgnoreCase("rankreq")) { + if (command.length == 3) { + if (command[2].equals("-delete")) { + quest.rankReq = null; + player.sendMessage("Quest rank requirement removed."); + } else if (quest.setRankReq(command[2])){ + player.sendMessage("Quest rank requirmenet set to " + command[2] + "."); + } else { + player.sendMessage("Invalid rank requirement."); + } + } else if (command.length == 2 && quest.rankReq != null) { + player.sendMessage("Quest " + quest.id + " rank requirement: " + quest.rankReq); + } else { + player.sendMessage("Use /qadmin rankreq [rankname] to set the rank requirement."); + player.sendMessage("Use /qadmin rankreq -delete to remove the rank requirement."); + } + + // rankreward + } else if (command[1].equalsIgnoreCase("rankreward")) { + if (command.length == 3) { + if (command[2].equals("-delete")) { + quest.rankReq = null; + player.sendMessage("Quest rank reward removed."); + } else if (quest.setRankReward(command[2])){ + player.sendMessage("Quest rank reward set to " + command[2] + "."); + } else { + player.sendMessage("Invalid rank reward."); + } + } else if (command.length == 2 && quest.rankReward != null) { + player.sendMessage("Quest " + quest.id + " rank reward: " + quest.rankReward); + } else { + player.sendMessage("Use /qadmin rankreward [rankname] to set the rank reward."); + player.sendMessage("Use /qadmin rankreward -delete to remove the rank reward."); + } + // items provided } else if (command[1].equalsIgnoreCase("itemsprovided")) { if (command.length > 2) { @@ -629,7 +672,7 @@ public static boolean lookingAt(Player player, double x, double y, double z) { if (x <= player.getX() && z <= player.getZ()) { angle = 180 - angle; } else if (x <= player.getX() && z >= player.getZ()) { - angle = angle; +// angle = angle; } else if (x >= player.getX() && z >= player.getZ()) { angle = 360 - angle; } else { diff --git a/Quest.java b/Quest.java index 9414a33..01e4a4f 100644 --- a/Quest.java +++ b/Quest.java @@ -42,6 +42,33 @@ protected void giveRewards() { player.giveItem(i, info.rewards.get(i)); } } + if (info.rankReward != null) { + setRank(player, info.rankReward); + } + } + + private void setRank(Player p, String rank) { + etc.getInstance(); + Group g = etc.getDataSource().getGroup(rank); + if (g != null) { + String[] arrayOfString = { g.Name }; + p.setGroups(arrayOfString); + p.setIgnoreRestrictions(g.IgnoreRestrictions); + p.setAdmin(g.Administrator); + + int i = 0; + + if (!etc.getDataSource().doesPlayerExist(p.getName())) { + i = 1; + } + + if (i != 0) + etc.getDataSource().addPlayer(p); + else + etc.getDataSource().modifyPlayer(p); + } else { + p.sendMessage("Error setting rank. Please contact your local admin."); + } } protected void complete() { diff --git a/QuestInfo.java b/QuestInfo.java index 4e03be2..eee08df 100644 --- a/QuestInfo.java +++ b/QuestInfo.java @@ -11,10 +11,13 @@ public class QuestInfo { public String turnIn; public String prereq; + public String rankReq; + public String rankReward; + public String itemsProvidedStr; - public HashMap itemsProvided; + public HashMap itemsProvided = new HashMap(); public String rewardsStr; - public HashMap rewards; + public HashMap rewards = new HashMap(); public String location; public String data; @@ -27,7 +30,7 @@ public QuestInfo(String id) { this.desc = ""; } - public QuestInfo(String id, String type, String name, String desc, String pickUp, String turnIn, String prereq, String itemsProvided, String rewards, String location, String data, String completionText) { + public QuestInfo(String id, String type, String name, String desc, String pickUp, String turnIn, String prereq, String itemsProvided, String rewards, String location, String data, String completionText, String rankReq, String rankReward) { // get normal data this.id = id; this.type = type; @@ -74,6 +77,8 @@ public QuestInfo(String id, String type, String name, String desc, String pickUp this.location = location; this.data = data; this.completionText = completionText; + this.rankReq = rankReq; + this.rankReward = rankReward; } public void show(Player player) { @@ -171,6 +176,35 @@ public boolean setPrereq(String q) { } } + /** + * @param rankReq the rankReq to set + */ + public boolean setRankReq(String rankReq) { + if (rankReq.matches("^[a-zA-Z0-9]+$")) { + this.rankReq = rankReq; + return true; + } else { + return false; + } + } + + /** + * @param rankReward the rankReward to set + */ + public boolean setRankReward(String rankReward) { + if (rankReward.matches("^[a-zA-Z0-9]+$")) { + etc.getInstance(); + Group g = etc.getDataSource().getGroup(rankReward); + if (g != null) { + this.rankReward = rankReward; + return true; + } + return false; + } else { + return false; + } + } + public boolean setItemsProvided(String i) { if (i.matches("[a-zA-Z0-9\\- ]+:([0-9]+ [0-9]+,)*[0-9]+ [0-9]+")) { this.itemsProvidedStr = i; From a17c68f9b65989e6474162bcd07e016d8aba654c Mon Sep 17 00:00:00 2001 From: durron597 Date: Tue, 7 Dec 2010 02:56:04 -0500 Subject: [PATCH 03/39] Added sql script with changes for 0.7 and 0.7.1 --- craftizens.sql | 67 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 craftizens.sql diff --git a/craftizens.sql b/craftizens.sql new file mode 100644 index 0000000..3807033 --- /dev/null +++ b/craftizens.sql @@ -0,0 +1,67 @@ +CREATE TABLE `minecraft`.`craftizens` ( + `npc_id` varchar(12) NOT NULL, + `npc_name` varchar(30) CHARACTER SET utf8 NOT NULL, + `posx` double NOT NULL, + `posy` double NOT NULL, + `posz` double NOT NULL, + `rotation` float NOT NULL, + `pitch` float NOT NULL, + PRIMARY KEY (`npc_id`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1; + +CREATE TABLE `minecraft`.`quests` ( + `id` varchar(12) NOT NULL, + `quest_name` text CHARACTER SET utf8 NOT NULL, + `start_npc` varchar(12) NOT NULL, + `end_npc` varchar(12) NOT NULL, + `items_provided` text CHARACTER SET utf8, + `rewards` text CHARACTER SET utf8, + `quest_desc` mediumtext CHARACTER SET utf8 NOT NULL, + `quest_type` varchar(10) NOT NULL, + `prereq` varchar(12) DEFAULT NULL, + `location` text, + `data` text, + PRIMARY KEY (`id`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1; + +CREATE TABLE `minecraft`.`quests_active` ( + `player_name` varchar(25) NOT NULL, + `quest_id` varchar(12) NOT NULL, + `progress` varchar(50) DEFAULT NULL, + PRIMARY KEY (`player_name`,`quest_id`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1; + +CREATE TABLE `minecraft`.`quests_completed` ( + `player_name` varchar(25) NOT NULL, + `quest_id` varchar(12) NOT NULL, + `date_completed` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`player_name`,`quest_id`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1; + +-- v0.2 update + +ALTER TABLE `minecraft`.`craftizens` ADD COLUMN `item_in_hand` INT NOT NULL DEFAULT 0 AFTER `pitch`; + +-- v0.5 update + +ALTER TABLE `minecraft`.`quests` ADD COLUMN `completion_text` TEXT DEFAULT NULL AFTER `data`; + +CREATE TABLE `minecraft`.`craftizens_dialog` ( + `npc_id` varchar(12) NOT NULL, + `dialog_id` varchar(12) NOT NULL, + `dialog_text` text NOT NULL, + PRIMARY KEY (`npc_id`,`dialog_id`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1; + +-- v0.6 update + +ALTER TABLE `minecraft`.`craftizens` ADD COLUMN `route_type` VARCHAR(8) DEFAULT NULL AFTER `item_in_hand`, + ADD COLUMN `route` TEXT DEFAULT NULL AFTER `route_type`; + +-- v0.7 update +ALTER TABLE `minecraft`.`quests` ADD COLUMN `rankreq` VARCHAR(12) DEFAULT NULL AFTER `prereq`, + ADD COLUMN `rankreward` VARCHAR(12) DEFAULT NULL AFTER `rankreq`; + +-- v0.7.1 update +ALTER TABLE `minecraft`.`quests` ADD COLUMN `cost` VARCHAR(12) DEFAULT '0' AFTER `rankreward`, + ADD COLUMN `prize` VARCHAR(12) DEFAULT '0' AFTER `cost`; From 8fb9f643317ed7183dca60cbe10edabeea0c7717 Mon Sep 17 00:00:00 2001 From: durron597 Date: Tue, 7 Dec 2010 05:52:04 -0500 Subject: [PATCH 04/39] Added iConomy interface Now have: /qadmin cost /qadmin prize /qadmin iConomy (to enable/disable iConomy) --- CraftizenSQLDataSource.java | 23 +++- Craftizens.java | 30 ++++- CraftizensListener.java | 71 ++++++++++-- Quest.java | 9 ++ QuestInfo.java | 80 +++++++++++++- iData.java | 211 ++++++++++++++++++++++++++++++++++++ 6 files changed, 406 insertions(+), 18 deletions(-) create mode 100644 iData.java diff --git a/CraftizenSQLDataSource.java b/CraftizenSQLDataSource.java index 6ae71de..5cc3989 100644 --- a/CraftizenSQLDataSource.java +++ b/CraftizenSQLDataSource.java @@ -248,7 +248,9 @@ public QuestInfo loadQuestInfo(String id) { results.getString("data"), results.getString("completion_text"), results.getString("rankreq"), - results.getString("rankreward") + results.getString("rankreward"), + results.getString("cost"), + results.getString("prize") ); } } catch (SQLException e) { @@ -318,7 +320,9 @@ public ArrayList getAvailableQuests(Craftizen c, Player p) { results.getString("data"), results.getString("completion_text"), results.getString("rankreq"), - results.getString("rankreward") + results.getString("rankreward"), + results.getString("cost"), + results.getString("prize") ); quests.add(q); } @@ -369,7 +373,9 @@ public HashMap getActiveQuests(Player p) { results.getString("data"), results.getString("completion_text"), results.getString("rankreq"), - results.getString("rankreward") + results.getString("rankreward"), + results.getString("cost"), + results.getString("prize") ); String s = results.getString("progress"); quests.put(q,s); @@ -517,7 +523,8 @@ public void saveQuest(QuestInfo quest) { "start_npc = ?, end_npc = ?, prereq = ?, " + "items_provided = ?, rewards = ?, " + "location = ?, data = ?, completion_text = ?, " + - "rankreq = ?, rankreward = ? " + + "rankreq = ?, rankreward = ?, " + + "cost = ?, prize = ? " + "WHERE id = ?"); query.setString(1,quest.name); query.setString(2,quest.type); @@ -532,10 +539,12 @@ public void saveQuest(QuestInfo quest) { query.setString(11,quest.completionText); query.setString(12,quest.rankReq); query.setString(13,quest.rankReward); - query.setString(14,quest.id); + query.setString(14,(new Integer(quest.cost)).toString()); + query.setString(15,(new Integer(quest.prize)).toString()); + query.setString(16,quest.id); query.executeUpdate(); } else { - query = conn.prepareStatement("INSERT INTO quests (id, quest_name, quest_type, quest_desc, start_npc, end_npc, prereq, items_provided, rewards, location, data, completion_text, rankreq, rankreward) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + query = conn.prepareStatement("INSERT INTO quests (id, quest_name, quest_type, quest_desc, start_npc, end_npc, prereq, items_provided, rewards, location, data, completion_text, rankreq, rankreward, cost, prize) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); query.setString(1,quest.id); query.setString(2,quest.name); query.setString(3,quest.type); @@ -550,6 +559,8 @@ public void saveQuest(QuestInfo quest) { query.setString(12,quest.completionText); query.setString(13,quest.rankReq); query.setString(14,quest.rankReward); + query.setString(15,(new Integer(quest.cost)).toString()); + query.setString(16,(new Integer(quest.prize)).toString()); query.executeUpdate(); } } catch (SQLException e) { diff --git a/Craftizens.java b/Craftizens.java index 6a80a21..404c5c2 100644 --- a/Craftizens.java +++ b/Craftizens.java @@ -7,7 +7,8 @@ public class Craftizens extends Plugin { static final Logger log = Logger.getLogger("Minecraft"); public static boolean DEBUG = true; - public static String NPC_PREFIX = "§e"; +// public static String NPC_PREFIX = "§e"; + public static String NPC_PREFIX = "§e"; public static String NPC_SUFFIX = " (NPC)"; public static String TEXT_COLOR = Colors.Yellow; public static int INTERACT_ITEM = 340; @@ -16,6 +17,7 @@ public class Craftizens extends Plugin { public static int INTERACT_ANGLE_VARIATION = 25; public static int QADMIN_BOUNDARY_MARKER = 340; public static boolean QUESTS_ENABLED = true; + public static boolean ICONOMY_DETECTED = false; public static CraftizenDataSource data; public static HashSet npcs; @@ -50,9 +52,11 @@ public void enable() { INTERACT_ANGLE_VARIATION = props.getInt("npc-interact-angle-variation",25); QADMIN_BOUNDARY_MARKER = props.getInt("qadmin-boundary-marker",340); QUESTS_ENABLED = props.getBoolean("quests-enabled",true); - + data = new CraftizenSQLDataSource(); + loadiConomy(); + Craftizen.getPlayerList(); npcs = data.loadCraftizens(); @@ -67,7 +71,7 @@ public void enable() { CraftizensListener.loadActiveQuests(p); } - log.info("Craftizens v0.7 loaded successfully!"); + log.info("[Craftizens] Craftizens v0.7 loaded successfully!"); } public void disable() { @@ -95,4 +99,24 @@ public void disable() { data = null; } + public static boolean loadiConomy() { + if (etc.getLoader().getPlugin("iConomy") != null) { + PropertiesFile iConomySettings = new PropertiesFile(iData.mainDir + "settings.properties"); + // MySQL + String driver = iConomySettings.getString("driver", "com.mysql.jdbc.Driver"); + String user = iConomySettings.getString("user", "root"); + String pass = iConomySettings.getString("pass", "root"); + String db = iConomySettings.getString("db", "jdbc:mysql://localhost:3306/minecraft"); + + // Data + iData.setup(true, 0, driver, user, pass, db); + log.info("[Craftizens] iConomy loaded successfully."); + ICONOMY_DETECTED = true; + return true; + } else { + log.warning("[Craftizens] iConomy failed to load."); + ICONOMY_DETECTED = false; + return false; + } + } } diff --git a/CraftizensListener.java b/CraftizensListener.java index 7885b17..6ef05f2 100644 --- a/CraftizensListener.java +++ b/CraftizensListener.java @@ -105,14 +105,18 @@ public void questCommand(Player player, String [] command) { Object o = Craftizens.pendingQuests.get(player.getName()); if (o instanceof QuestInfo) { QuestInfo qi = (QuestInfo)o; - Quest q = qi.createQuest(player, true); - Craftizens.pendingQuests.remove(player.getName()); - if (!Craftizens.activeQuests.containsKey(player.getName())) { - Craftizens.activeQuests.put(player.getName(),new ArrayList()); + if (qi.checkBalance(player, true)) { + Quest q = qi.createQuest(player, true); + Craftizens.pendingQuests.remove(player.getName()); + if (!Craftizens.activeQuests.containsKey(player.getName())) { + Craftizens.activeQuests.put(player.getName(),new ArrayList()); + } + Craftizens.activeQuests.get(player.getName()).add(q); + Craftizens.data.saveActiveQuest(player, q); + player.sendMessage(Craftizens.TEXT_COLOR + "Quest accepted!"); + } else { + player.sendMessage(Craftizens.TEXT_COLOR + "You can't afford this quest! It costs " + qi.cost + "."); } - Craftizens.activeQuests.get(player.getName()).add(q); - Craftizens.data.saveActiveQuest(player, q); - player.sendMessage(Craftizens.TEXT_COLOR + "Quest accepted!"); } else { player.sendMessage(Craftizens.TEXT_COLOR + "No quest to accept."); } @@ -259,6 +263,8 @@ public void qadminCommand(Player player, String [] command) { player.sendMessage("Rewards: " + (q.rewardsStr == null ? "None" : q.rewardsStr)); player.sendMessage("Rank requirement: " + q.rankReq); player.sendMessage("Rank reward: " + q.rankReward); + player.sendMessage("Cost: " + q.cost + " (iConomy is currently " + (Craftizens.ICONOMY_DETECTED ? "enabled)" : "disabled)")); + player.sendMessage("Prize: " + q.prize + " (iConomy is currently " + (Craftizens.ICONOMY_DETECTED ? "enabled)" : "disabled)")); player.sendMessage("Comp text: " + ((q.completionText != null && q.completionText.length()>50)?q.completionText.substring(0,50)+"...":q.completionText)); player.sendMessage("Data: " + q.data); q = null; @@ -297,6 +303,19 @@ public void qadminCommand(Player player, String [] command) { player.sendMessage("No such quest."); } + // iConomy Hook + } else if (command[1].equalsIgnoreCase("iConomy")) { + if (command.length > 2 && command[2].equals("-disable")) { + Craftizens.ICONOMY_DETECTED = false; + player.sendMessage("iConomy disabled. Use /qadmin iConomy to turn it back on."); + } else if (Craftizens.loadiConomy()) { + player.sendMessage("iConomy loaded successfully."); + player.sendMessage("use /qadmin iConomy -disable to turn it off."); + } else { + player.sendMessage("iConomy failed to load."); + player.sendMessage("Perhaps you don't have the plugin running?"); + } + } else if (command.length >= 2 && Craftizens.newQuests != null && Craftizens.newQuests.containsKey(player.getName())) { QuestInfo quest = Craftizens.newQuests.get(player.getName()); @@ -441,7 +460,7 @@ public void qadminCommand(Player player, String [] command) { } else if (command[1].equalsIgnoreCase("rankreward")) { if (command.length == 3) { if (command[2].equals("-delete")) { - quest.rankReq = null; + quest.rankReward = null; player.sendMessage("Quest rank reward removed."); } else if (quest.setRankReward(command[2])){ player.sendMessage("Quest rank reward set to " + command[2] + "."); @@ -454,6 +473,42 @@ public void qadminCommand(Player player, String [] command) { player.sendMessage("Use /qadmin rankreward [rankname] to set the rank reward."); player.sendMessage("Use /qadmin rankreward -delete to remove the rank reward."); } + + // cost + } else if (command[1].equalsIgnoreCase("cost")) { + if (command.length == 3) { + if (command[2].equals("-delete")) { + quest.cost = 0; + player.sendMessage("Quest cost removed."); + } else if (quest.setCost(command[2])){ + player.sendMessage("Quest cost set to " + command[2] + "."); + } else { + player.sendMessage("Invalid cost."); + } + } else if (command.length == 2) { + player.sendMessage("Quest " + quest.id + " cost: " + quest.cost); + } else { + player.sendMessage("Use /qadmin cost [rankname] to set the quest cost."); + player.sendMessage("Use /qadmin cost -delete to remove the quest cost."); + } + + // prize + } else if (command[1].equalsIgnoreCase("prize")) { + if (command.length == 3) { + if (command[2].equals("-delete")) { + quest.prize = 0; + player.sendMessage("Quest prize removed."); + } else if (quest.setPrize(command[2])){ + player.sendMessage("Quest prize set to " + command[2] + "."); + } else { + player.sendMessage("Invalid prize."); + } + } else if (command.length == 2) { + player.sendMessage("Quest " + quest.id + " prize: " + quest.prize); + } else { + player.sendMessage("Use /qadmin prize [rankname] to set the quest prize."); + player.sendMessage("Use /qadmin prize -delete to remove the quest prize."); + } // items provided } else if (command[1].equalsIgnoreCase("itemsprovided")) { diff --git a/Quest.java b/Quest.java index 01e4a4f..ef3a99c 100644 --- a/Quest.java +++ b/Quest.java @@ -45,6 +45,15 @@ protected void giveRewards() { if (info.rankReward != null) { setRank(player, info.rankReward); } + + if (Craftizens.ICONOMY_DETECTED) { + if (info.prize > 0) { + int money = iData.getBalance(player.getName()); + iData.setBalance(player.getName(), money + info.prize); + + player.sendMessage("Awarding cash prize of " + info.prize + "."); + } + } } private void setRank(Player p, String rank) { diff --git a/QuestInfo.java b/QuestInfo.java index eee08df..2c6caf0 100644 --- a/QuestInfo.java +++ b/QuestInfo.java @@ -14,6 +14,9 @@ public class QuestInfo { public String rankReq; public String rankReward; + public int cost = 0; + public int prize = 0; + public String itemsProvidedStr; public HashMap itemsProvided = new HashMap(); public String rewardsStr; @@ -30,7 +33,7 @@ public QuestInfo(String id) { this.desc = ""; } - public QuestInfo(String id, String type, String name, String desc, String pickUp, String turnIn, String prereq, String itemsProvided, String rewards, String location, String data, String completionText, String rankReq, String rankReward) { + public QuestInfo(String id, String type, String name, String desc, String pickUp, String turnIn, String prereq, String itemsProvided, String rewards, String location, String data, String completionText, String rankReq, String rankReward, String cost, String prize) { // get normal data this.id = id; this.type = type; @@ -79,6 +82,37 @@ public QuestInfo(String id, String type, String name, String desc, String pickUp this.completionText = completionText; this.rankReq = rankReq; this.rankReward = rankReward; + + try { + this.cost = Integer.parseInt(cost); + } catch (NumberFormatException e) { + this.cost = 0; + } + + try { + this.prize = Integer.parseInt(prize); + } catch (NumberFormatException e) { + this.prize = 0; + } + } + + // this function uses hardcoded € because iConomy is not a singleton + // so I can't get iConony.moneyName + public boolean checkBalance(Player player, boolean deduct) { + if (Craftizens.ICONOMY_DETECTED) { + int money = iData.getBalance(player.getName()); + if (money >= cost) { + if (deduct && cost > 0) { + iData.setBalance(player.getName(), money - cost); + player.sendMessage(Craftizens.TEXT_COLOR + "Taking cost of " + cost + "."); + } + return true; + } else { + return false; + } + } else { + return true; + } } public void show(Player player) { @@ -101,6 +135,18 @@ public void show(Player player) { if (!rewardsStr.equals("")) { player.sendMessage(Craftizens.TEXT_COLOR + " Reward: " + rewardsStr.split(":")[0]); } + if (rankReq != null && !rankReq.equals("")) { + player.sendMessage(Craftizens.TEXT_COLOR + " Rank Required: " + rankReq); + } + if (rankReward != null && !rankReward.equals("")) { + player.sendMessage(Craftizens.TEXT_COLOR + " Rank Reward: " + rankReward); + } + if (Craftizens.ICONOMY_DETECTED && cost > 0) { + player.sendMessage(Craftizens.TEXT_COLOR + " Cost: " + cost); + } + if (Craftizens.ICONOMY_DETECTED && prize > 0) { + player.sendMessage(Craftizens.TEXT_COLOR + " Prize: " + prize); + } player.sendMessage(Craftizens.TEXT_COLOR + "Type '/quest accept' to accept this quest."); } @@ -205,6 +251,38 @@ public boolean setRankReward(String rankReward) { } } + /** + * @param cost the cost to set + */ + public boolean setCost(String cost) { + if (cost.matches("^[0-9]+$")) { + try { + this.cost = Integer.parseInt(cost); + return true; + } catch (NumberFormatException e) { + return false; + } + } else { + return false; + } + } + + /** + * @param prize the prize to set + */ + public boolean setPrize(String prize) { + if (prize.matches("^[0-9]+$")) { + try { + this.prize = Integer.parseInt(prize); + return true; + } catch (NumberFormatException e) { + return false; + } + } else { + return false; + } + } + public boolean setItemsProvided(String i) { if (i.matches("[a-zA-Z0-9\\- ]+:([0-9]+ [0-9]+,)*[0-9]+ [0-9]+")) { this.itemsProvidedStr = i; diff --git a/iData.java b/iData.java new file mode 100644 index 0000000..3da762b --- /dev/null +++ b/iData.java @@ -0,0 +1,211 @@ +import java.io.*; +import java.sql.*; +import java.util.Map; +import java.util.logging.Logger; + +public final class iData implements Serializable { + protected static final Logger log = Logger.getLogger("Minecraft"); + public static PropertiesFile accounts; + private static int startingBalance; + + // Serial + private static final long serialVersionUID = -5796481236376288855L; + + // Database + static boolean mysql = false; + static String driver = "com.mysql.jdbc.Driver"; + static String user = "root"; + static String pass = "root"; + static String db = "jdbc:mysql://localhost:3306/minecraft"; + + // Directories + static String mainDir = "iConomy/"; + static String logDir = "logs/"; + + public static void setup(boolean mysql, int balance, String driver, String user, String pass, String db) { + startingBalance = balance; + + // Database + iData.driver = driver; + iData.user = user; + iData.pass = pass; + iData.db = db; + + if (!mysql) { + accounts = new PropertiesFile(mainDir + "balances.properties"); + } else { + // MySQL + iData.mysql = true; + + try { + Class.forName(driver); + } catch (ClassNotFoundException ex) { + log.severe("[iConomy MySQL] Unable to find driver class " + driver); + } + } + } + + public static Connection MySQL() { + try { + return DriverManager.getConnection(db,user,pass); + } catch (SQLException ex) { + log.severe("[iConomy MySQL] Unable to retreive MySQL connection"); + } + + return null; + } + + public static int globalBalance() { + Connection conn = null; + PreparedStatement ps = null; + ResultSet rs = null; + int current = 0; + + if (mysql) { + try { + conn = MySQL(); + ps = conn.prepareStatement("SELECT balance FROM iBalances"); + rs = ps.executeQuery(); + + while(rs.next()) { + current += rs.getInt("balance"); + } + + return current; + } catch (SQLException ex) { + return 0; + } finally { + try { + if (ps != null) { ps.close(); } + if (rs != null) { rs.close(); } + if (conn != null) { conn.close(); } + } catch (SQLException ex) { } + } + } else { + Map balances; + + try { + balances = accounts.returnMap(); + } catch (Exception ex) { + log.info("[iConomy] Listing failed for accounts."); + return 0; + } + + for (Object key: balances.keySet()) { + int balance = Integer.parseInt((String)balances.get(key)); + current += balance; + } + + return current; + } + } + + public static boolean hasBalance(String playerName) { + Connection conn = null; + PreparedStatement ps = null; + ResultSet rs = null; + boolean has = false; + + if (mysql) { + try { + conn = MySQL(); + ps = conn.prepareStatement("SELECT balance FROM iBalances WHERE player = ? LIMIT 1"); + ps.setString(1, playerName); + rs = ps.executeQuery(); + + has = (rs.next()) ? true : false; + } catch (SQLException ex) { + log.severe("[iConomy] Unable to grab the balance for [" + playerName + "] from database!"); + } finally { + try { + if (ps != null) { ps.close(); } + if (rs != null) { rs.close(); } + if (conn != null) { conn.close(); } + } catch (SQLException ex) { } + } + } else { + return (accounts.getInt(playerName) != 0) ? true : false; + } + + return has; + } + + public static int getBalance(String playerName) { + Connection conn = null; + PreparedStatement ps = null; + ResultSet rs = null; + int balance = startingBalance; + + if (mysql) { + try { + conn = MySQL(); + ps = conn.prepareStatement("SELECT balance FROM iBalances WHERE player = ? LIMIT 1"); + ps.setString(1, playerName); + rs = ps.executeQuery(); + + if (rs.next()) { + balance = rs.getInt("balance"); + } else { + ps = conn.prepareStatement("INSERT INTO iBalances (player, balance) VALUES(?,?)"); + ps.setString(1, playerName); + ps.setInt(2, balance); + ps.executeUpdate(); + } + } catch (SQLException ex) { + log.severe("[iConomy] Unable to grab the balance for [" + playerName + "] from database!"); + } finally { + try { + if (ps != null) { ps.close(); } + if (rs != null) { rs.close(); } + if (conn != null) { conn.close(); } + } catch (SQLException ex) { } + } + } else { + // To work with plugins we must do this. + try { + accounts.load(); + } catch (IOException ex) { + log.severe("[iConomy] Unable to reload balances."); + } + + // Return the balance + return (hasBalance(playerName)) ? accounts.getInt(playerName) : accounts.getInt(playerName, startingBalance); + } + + return balance; + } + + public static void setBalance(String playerName, int balance) { + Connection conn = null; + PreparedStatement ps = null; + ResultSet rs = null; + + if (mysql) { + try { + conn = MySQL(); + + if (hasBalance(playerName)) { + ps = conn.prepareStatement("UPDATE iBalances SET balance = ? WHERE player = ? LIMIT 1", Statement.RETURN_GENERATED_KEYS); + ps.setInt(1, balance); + ps.setString(2, playerName); + ps.executeUpdate(); + } else { + ps = conn.prepareStatement("INSERT INTO iBalances (player, balance) VALUES(?,?)"); + ps.setString(1, playerName); + ps.setInt(2, balance); + ps.executeUpdate(); + } + } catch (SQLException ex) { + log.severe("[iConomy] Unable to update or create the balance for [" + playerName + "] from database!"); + } finally { + try { + if (ps != null) { ps.close(); } + if (rs != null) { rs.close(); } + if (conn != null) { conn.close(); } + } catch (SQLException ex) { } + } + } else { + accounts.setInt(playerName, balance); + } + } +} From 6808f03e0c3f5b29df7aae1970e37cc5789b8231 Mon Sep 17 00:00:00 2001 From: durron597 Date: Tue, 7 Dec 2010 17:44:03 -0500 Subject: [PATCH 05/39] Added Readme.txt --- README.txt | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 README.txt diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..e69de29 From 1e7d4bd41df71824a90cc17d3a3bb51b41aaafd1 Mon Sep 17 00:00:00 2001 From: durron597 Date: Tue, 7 Dec 2010 17:44:50 -0500 Subject: [PATCH 06/39] Edited Readme --- README.txt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.txt b/README.txt index e69de29..1ffd7b6 100644 --- a/README.txt +++ b/README.txt @@ -0,0 +1,8 @@ +0.7 - Added rankreq and rankreward functionality + +0.7.1 - Added iConomy hook + +Also I added the .sql script to the commit + +Find latest compiled jar here: +http://mc.nexua.org/plugins/Craftizens/v071/ \ No newline at end of file From d5e6d0bde0216646ccf04f5b11437fadb4c20fab Mon Sep 17 00:00:00 2001 From: durron597 Date: Wed, 8 Dec 2010 20:36:38 -0500 Subject: [PATCH 07/39] Added support for flatfile iConomy --- Craftizens.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Craftizens.java b/Craftizens.java index 404c5c2..8a5a2da 100644 --- a/Craftizens.java +++ b/Craftizens.java @@ -18,6 +18,7 @@ public class Craftizens extends Plugin { public static int QADMIN_BOUNDARY_MARKER = 340; public static boolean QUESTS_ENABLED = true; public static boolean ICONOMY_DETECTED = false; + public static String VERSION = "v0.7.2"; public static CraftizenDataSource data; public static HashSet npcs; @@ -71,7 +72,7 @@ public void enable() { CraftizensListener.loadActiveQuests(p); } - log.info("[Craftizens] Craftizens v0.7 loaded successfully!"); + log.info("[Craftizens] Craftizens " + VERSION + " loaded successfully!"); } public void disable() { @@ -102,6 +103,8 @@ public void disable() { public static boolean loadiConomy() { if (etc.getLoader().getPlugin("iConomy") != null) { PropertiesFile iConomySettings = new PropertiesFile(iData.mainDir + "settings.properties"); + boolean mysql = iConomySettings.getBoolean("use-mysql", false); + // MySQL String driver = iConomySettings.getString("driver", "com.mysql.jdbc.Driver"); String user = iConomySettings.getString("user", "root"); @@ -109,7 +112,7 @@ public static boolean loadiConomy() { String db = iConomySettings.getString("db", "jdbc:mysql://localhost:3306/minecraft"); // Data - iData.setup(true, 0, driver, user, pass, db); + iData.setup(mysql, 0, driver, user, pass, db); log.info("[Craftizens] iConomy loaded successfully."); ICONOMY_DETECTED = true; return true; From 78efb1a4020c242eee99f336519d8f3262030a15 Mon Sep 17 00:00:00 2001 From: Michael Nowak Date: Wed, 15 Dec 2010 18:36:49 +0100 Subject: [PATCH 08/39] First version with support for HSQLDB --- CraftizenSQLDataSource.java | 149 +++++++++++++++++++++--------------- Craftizens.java | 44 ++++++----- craftizens.hysql.sql | 67 ++++++++++++++++ craftizens.mysql.sql | 67 ++++++++++++++++ craftizens.sql | 67 ---------------- 5 files changed, 248 insertions(+), 146 deletions(-) create mode 100644 craftizens.hysql.sql create mode 100644 craftizens.mysql.sql delete mode 100644 craftizens.sql diff --git a/CraftizenSQLDataSource.java b/CraftizenSQLDataSource.java index 5cc3989..700e2cc 100644 --- a/CraftizenSQLDataSource.java +++ b/CraftizenSQLDataSource.java @@ -7,15 +7,44 @@ public class CraftizenSQLDataSource extends CraftizenDataSource { static final Logger log = Logger.getLogger("Minecraft"); - + static protected Connection connection = null; + + static public Connection getSQLConnection() throws SQLException + { + if (connection == null) { + if (Craftizens.DATA_SOURCE_DRIVER_NAME.isEmpty()) { + connection = etc.getSQLConnection(); + } else { + try { + Class.forName(Craftizens.DATA_SOURCE_DRIVER_NAME); + } catch (ClassNotFoundException ex) { + log.log(Level.SEVERE, null, ex); + return null; + } + connection = DriverManager.getConnection(Craftizens.DATA_SOURCE_CONNECTION_URL, Craftizens.DATA_SOURCE_USERNAME, Craftizens.DATA_SOURCE_PASSWORD); + } + } else { + if (connection.isClosed()) { + connection = null; + log.info("[" + Craftizens.NAME + "] SQL-Connection got closed; Reconnecting!"); + return getSQLConnection(); + } else if (!connection.isValid(0)) { + connection = null; + log.info("[" + Craftizens.NAME + "] SQL-Connection got invalid; Reconnecting!"); + return getSQLConnection(); + } + } + return connection; + } + public void sample() { synchronized (dbLock) { Connection conn = null; PreparedStatement query = null; ResultSet results = null; try { - conn = etc.getSQLConnection(); - query = conn.prepareStatement("select * from"); + conn = getSQLConnection(); + query = conn.prepareStatement("SELECT * FROM"); results = query.executeQuery(); while (results.next()) { } @@ -40,7 +69,7 @@ public HashSet loadCraftizens() { PreparedStatement query = null; ResultSet results = null; try { - conn = etc.getSQLConnection(); + conn = getSQLConnection(); query = conn.prepareStatement("SELECT * FROM craftizens"); results = query.executeQuery(); while (results.next()) { @@ -90,7 +119,7 @@ public void saveCraftizen(Craftizen c) { PreparedStatement query = null; ResultSet results = null; try { - conn = etc.getSQLConnection(); + conn = getSQLConnection(); // check if npc exists already boolean exists = false; @@ -148,7 +177,7 @@ public void addCraftizenDialog(String npcid, String dialogid, String dialog) { PreparedStatement query = null; ResultSet results = null; try { - conn = etc.getSQLConnection(); + conn = getSQLConnection(); query = conn.prepareStatement("INSERT INTO craftizens_dialog (npc_id, dialog_id, dialog_text) VALUES (?,?,?)"); query.setString(1,npcid); query.setString(2,dialogid); @@ -173,7 +202,7 @@ public void deleteCraftizen(String id) { PreparedStatement query = null; ResultSet results = null; try { - conn = etc.getSQLConnection(); + conn = getSQLConnection(); query = conn.prepareStatement("DELETE FROM craftizens WHERE npc_id = ?"); query.setString(1,id); query.executeUpdate(); @@ -198,7 +227,7 @@ public ArrayList getQuestList() { PreparedStatement query = null; ResultSet results = null; try { - conn = etc.getSQLConnection(); + conn = getSQLConnection(); query = conn.prepareStatement("SELECT id FROM quests ORDER BY id"); results = query.executeQuery(); while (results.next()) { @@ -229,29 +258,29 @@ public QuestInfo loadQuestInfo(String id) { PreparedStatement query = null; ResultSet results = null; try { - conn = etc.getSQLConnection(); + conn = getSQLConnection(); query = conn.prepareStatement("SELECT * FROM quests WHERE id = ?"); query.setString(1,id); results = query.executeQuery(); if (results.next()) { quest = new QuestInfo( - results.getString("id"), - results.getString("quest_type"), - results.getString("quest_name"), - results.getString("quest_desc"), - results.getString("start_npc"), - results.getString("end_npc"), - results.getString("prereq"), - results.getString("items_provided"), - results.getString("rewards"), - results.getString("location"), - results.getString("data"), - results.getString("completion_text"), - results.getString("rankreq"), - results.getString("rankreward"), - results.getString("cost"), - results.getString("prize") - ); + results.getString("id"), + results.getString("quest_type"), + results.getString("quest_name"), + results.getString("quest_desc"), + results.getString("start_npc"), + results.getString("end_npc"), + results.getString("prereq"), + results.getString("items_provided"), + results.getString("rewards"), + results.getString("location"), + results.getString("data"), + results.getString("completion_text"), + results.getString("rankreq"), + results.getString("rankreward"), + results.getString("cost"), + results.getString("prize") + ); } } catch (SQLException e) { log.log(Level.SEVERE,"DB Error loading quest info "+id,e); @@ -276,14 +305,14 @@ public ArrayList getAvailableQuests(Craftizen c, Player p) { PreparedStatement query = null; ResultSet results = null; try { - conn = etc.getSQLConnection(); + conn = getSQLConnection(); query = conn.prepareStatement( "SELECT q.* " + - "FROM craftizens c " + - "JOIN quests q ON q.start_npc = c.npc_id " + - "LEFT JOIN quests_completed qc ON qc.player_name = ? AND qc.quest_id = q.prereq " + - "LEFT JOIN quests_completed qc2 ON qc2.player_name = ? AND qc2.quest_id = q.id " + - "LEFT JOIN quests_active qa ON qa.player_name = ? AND qa.quest_id = q.id " + + "FROM craftizens AS c " + + "JOIN quests AS q ON q.start_npc = c.npc_id " + + "LEFT JOIN quests_completed AS qc ON qc.player_name = ? AND qc.quest_id = q.prereq " + + "LEFT JOIN quests_completed AS qc2 ON qc2.player_name = ? AND qc2.quest_id = q.id " + + "LEFT JOIN quests_active AS qa ON qa.player_name = ? AND qa.quest_id = q.id " + "WHERE c.npc_id = ? AND (q.prereq IS NULL OR qc.date_completed IS NOT NULL) " + "AND qc2.quest_id IS NULL and qa.quest_id IS NULL " + "AND (q.rankreq IS NULL OR q.rankreq = ?) " @@ -349,34 +378,34 @@ public HashMap getActiveQuests(Player p) { PreparedStatement query = null; ResultSet results = null; try { - conn = etc.getSQLConnection(); + conn = getSQLConnection(); query = conn.prepareStatement( "SELECT q.*, qa.progress " + - "FROM quests_active qa " + - "JOIN quests q ON q.id = qa.quest_id " + + "FROM quests_active AS qa " + + "JOIN quests AS q ON q.id = qa.quest_id " + "WHERE qa.player_name = ? " ); query.setString(1, p.getName().toLowerCase()); results = query.executeQuery(); while (results.next()) { QuestInfo q = new QuestInfo( - results.getString("id"), - results.getString("quest_type"), - results.getString("quest_name"), - results.getString("quest_desc"), - results.getString("start_npc"), - results.getString("end_npc"), - results.getString("prereq"), - results.getString("items_provided"), - results.getString("rewards"), - results.getString("location"), - results.getString("data"), - results.getString("completion_text"), - results.getString("rankreq"), - results.getString("rankreward"), - results.getString("cost"), - results.getString("prize") - ); + results.getString("id"), + results.getString("quest_type"), + results.getString("quest_name"), + results.getString("quest_desc"), + results.getString("start_npc"), + results.getString("end_npc"), + results.getString("prereq"), + results.getString("items_provided"), + results.getString("rewards"), + results.getString("location"), + results.getString("data"), + results.getString("completion_text"), + results.getString("rankreq"), + results.getString("rankreward"), + results.getString("cost"), + results.getString("prize") + ); String s = results.getString("progress"); quests.put(q,s); } @@ -401,7 +430,7 @@ public void saveActiveQuest(Player player, Quest quest) { PreparedStatement query = null; ResultSet results = null; try { - conn = etc.getSQLConnection(); + conn = getSQLConnection(); query = conn.prepareStatement("INSERT INTO quests_active (player_name, quest_id) VALUES (?,?)"); query.setString(1, player.getName().toLowerCase()); query.setString(2, quest.getId()); @@ -425,7 +454,7 @@ public void saveQuestProgress(Player player, Quest quest, String progress) { PreparedStatement query = null; ResultSet results = null; try { - conn = etc.getSQLConnection(); + conn = getSQLConnection(); query = conn.prepareStatement("UPDATE quests_active SET progress = ? WHERE player_name = ? AND quest_id = ?"); query.setString(1, progress); query.setString(2, player.getName().toLowerCase()); @@ -450,7 +479,7 @@ public void dropActiveQuest(Player player, Quest quest) { PreparedStatement query = null; ResultSet results = null; try { - conn = etc.getSQLConnection(); + conn = getSQLConnection(); query = conn.prepareStatement("DELETE FROM quests_active where player_name = ? AND quest_id = ?"); query.setString(1, player.getName().toLowerCase()); query.setString(2, quest.getId()); @@ -474,14 +503,14 @@ public void saveCompletedQuest(Player player, Quest quest) { PreparedStatement query = null; ResultSet results = null; try { - conn = etc.getSQLConnection(); + conn = getSQLConnection(); query = conn.prepareStatement("DELETE FROM quests_active WHERE player_name = ? AND quest_id = ?"); query.setString(1, player.getName().toLowerCase()); query.setString(2, quest.getId()); query.executeUpdate(); query.close(); - query = conn.prepareStatement("INSERT INTO quests_completed (player_name, quest_id) VALUES (?,?)"); + query = conn.prepareStatement("INSERT INTO quests_completed (player_name, quest_id, date_completed) VALUES (?,?,CURRENT_TIME)"); query.setString(1, player.getName().toLowerCase()); query.setString(2, quest.getId()); query.executeUpdate(); @@ -505,7 +534,7 @@ public void saveQuest(QuestInfo quest) { PreparedStatement query = null; ResultSet results = null; try { - conn = etc.getSQLConnection(); + conn = getSQLConnection(); boolean exists = false; query = conn.prepareStatement("SELECT * FROM quests WHERE id = ?"); @@ -583,7 +612,7 @@ public void deleteQuest(String questid) { PreparedStatement query = null; ResultSet results = null; try { - conn = etc.getSQLConnection(); + conn = getSQLConnection(); query = conn.prepareStatement("DELETE FROM quests_active WHERE quest_id = ?"); query.setString(1, questid); query.executeUpdate(); @@ -612,6 +641,4 @@ public void deleteQuest(String questid) { } } - - } diff --git a/Craftizens.java b/Craftizens.java index 8a5a2da..b672ff7 100644 --- a/Craftizens.java +++ b/Craftizens.java @@ -6,9 +6,8 @@ public class Craftizens extends Plugin { static final Logger log = Logger.getLogger("Minecraft"); - public static boolean DEBUG = true; -// public static String NPC_PREFIX = "§e"; - public static String NPC_PREFIX = "§e"; + public static boolean DEBUG = false; + public static String NPC_PREFIX = Colors.Yellow; public static String NPC_SUFFIX = " (NPC)"; public static String TEXT_COLOR = Colors.Yellow; public static int INTERACT_ITEM = 340; @@ -17,7 +16,12 @@ public class Craftizens extends Plugin { public static int INTERACT_ANGLE_VARIATION = 25; public static int QADMIN_BOUNDARY_MARKER = 340; public static boolean QUESTS_ENABLED = true; + public static String DATA_SOURCE_DRIVER_NAME = ""; + public static String DATA_SOURCE_CONNECTION_URL = ""; + public static String DATA_SOURCE_USERNAME = ""; + public static String DATA_SOURCE_PASSWORD = ""; public static boolean ICONOMY_DETECTED = false; + public static String NAME = "Craftizens"; public static String VERSION = "v0.7.2"; public static CraftizenDataSource data; @@ -43,18 +47,22 @@ public void initialize() { public void enable() { // load properties PropertiesFile props = new PropertiesFile("craftizens.properties"); - DEBUG = props.getBoolean("debug-mode",false); - NPC_PREFIX = props.getString("npc-name-prefix","§e"); - NPC_SUFFIX = props.getString("npc-name-suffix"," (NPC)"); - TEXT_COLOR = "§" + props.getString("quest-text-color","e"); - INTERACT_ITEM = props.getInt("npc-interact-item",340); - INTERACT_ITEM_2 = props.getInt("npc-interact-item-2",340); - INTERACT_RANGE = props.getInt("npc-interact-range",2); - INTERACT_ANGLE_VARIATION = props.getInt("npc-interact-angle-variation",25); - QADMIN_BOUNDARY_MARKER = props.getInt("qadmin-boundary-marker",340); - QUESTS_ENABLED = props.getBoolean("quests-enabled",true); - - data = new CraftizenSQLDataSource(); + DEBUG = props.getBoolean("debug-mode", DEBUG); + NPC_PREFIX = props.getString("npc-name-prefix", NPC_PREFIX); + NPC_SUFFIX = props.getString("npc-name-suffix", NPC_SUFFIX); + TEXT_COLOR = props.getString("quest-text-color", TEXT_COLOR); + INTERACT_ITEM = props.getInt("npc-interact-item", INTERACT_ITEM); + INTERACT_ITEM_2 = props.getInt("npc-interact-item-2", INTERACT_ITEM_2); + INTERACT_RANGE = props.getInt("npc-interact-range", INTERACT_RANGE); + INTERACT_ANGLE_VARIATION = props.getInt("npc-interact-angle-variation", INTERACT_ANGLE_VARIATION); + QADMIN_BOUNDARY_MARKER = props.getInt("qadmin-boundary-marker", QADMIN_BOUNDARY_MARKER); + QUESTS_ENABLED = props.getBoolean("quests-enabled", QUESTS_ENABLED); + DATA_SOURCE_DRIVER_NAME = props.getString("data-source-driver-name", DATA_SOURCE_DRIVER_NAME); + DATA_SOURCE_CONNECTION_URL = props.getString("data-source-connection-url", DATA_SOURCE_CONNECTION_URL); + DATA_SOURCE_USERNAME = props.getString("data-source-username", DATA_SOURCE_USERNAME); + DATA_SOURCE_PASSWORD = props.getString("data-source-password", DATA_SOURCE_PASSWORD); + + data = new CraftizenSQLDataSource(); loadiConomy(); @@ -72,7 +80,7 @@ public void enable() { CraftizensListener.loadActiveQuests(p); } - log.info("[Craftizens] Craftizens " + VERSION + " loaded successfully!"); + log.info("[" + NAME + "] Plugin " + VERSION + " loaded successfully!"); } public void disable() { @@ -113,11 +121,11 @@ public static boolean loadiConomy() { // Data iData.setup(mysql, 0, driver, user, pass, db); - log.info("[Craftizens] iConomy loaded successfully."); + log.info("[" + NAME + "] iConomy loaded successfully."); ICONOMY_DETECTED = true; return true; } else { - log.warning("[Craftizens] iConomy failed to load."); + log.warning("[" + NAME + "] iConomy failed to load."); ICONOMY_DETECTED = false; return false; } diff --git a/craftizens.hysql.sql b/craftizens.hysql.sql new file mode 100644 index 0000000..9a964ba --- /dev/null +++ b/craftizens.hysql.sql @@ -0,0 +1,67 @@ +CREATE TABLE craftizens ( + npc_id VARCHAR(12) NOT NULL, + npc_name VARCHAR(30)NOT NULL, + posx DOUBLE NOT NULL, + posy DOUBLE NOT NULL, + posz DOUBLE NOT NULL, + rotation FLOAT NOT NULL, + pitch FLOAT NOT NULL, + PRIMARY KEY (npc_id) +); + +CREATE TABLE quests ( + id VARCHAR(12) NOT NULL, + quest_name VARCHAR(512) NOT NULL, + start_npc VARCHAR(12) NOT NULL, + end_npc VARCHAR(12) NOT NULL, + items_provided VARCHAR(512) DEFAULT NULL, + rewards VARCHAR(512) DEFAULT NULL, + quest_desc VARCHAR(256) NOT NULL, + quest_type VARCHAR(10) NOT NULL, + prereq VARCHAR(12) DEFAULT NULL, + location VARCHAR(512) DEFAULT NULL, + data VARCHAR(512) DEFAULT NULL, + PRIMARY KEY (id) +); + +CREATE TABLE quests_active ( + player_name VARCHAR(25) NOT NULL, + quest_id VARCHAR(12) NOT NULL, + progress VARCHAR(50) DEFAULT NULL, + PRIMARY KEY (player_name,quest_id) +); + +CREATE TABLE quests_completed ( + player_name VARCHAR(25) NOT NULL, + quest_id VARCHAR(12) NOT NULL, + date_completed TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, + PRIMARY KEY (player_name,quest_id) +); + +-- v0.2 update + +ALTER TABLE craftizens ADD COLUMN item_in_hand INT DEFAULT 0 NOT NULL; + +-- v0.5 update + +ALTER TABLE quests ADD COLUMN completion_text VARCHAR(512) DEFAULT NULL; + +CREATE TABLE craftizens_dialog ( + npc_id VARCHAR(12) NOT NULL, + dialog_id VARCHAR(12) NOT NULL, + dialog_text VARCHAR(512) NOT NULL, + PRIMARY KEY (npc_id,dialog_id) +); + +-- v0.6 update + +ALTER TABLE craftizens ADD COLUMN route_type VARCHAR(8) DEFAULT NULL; +ALTER TABLE craftizens ADD COLUMN route VARCHAR(512) DEFAULT NULL; + +-- v0.7 update +ALTER TABLE quests ADD COLUMN rankreq VARCHAR(12) DEFAULT NULL BEFORE location; +ALTER TABLE quests ADD COLUMN rankreward VARCHAR(12) DEFAULT NULL BEFORE location; + +-- v0.7.1 update +ALTER TABLE quests ADD COLUMN cost VARCHAR(12) DEFAULT '0' BEFORE location; +ALTER TABLE quests ADD COLUMN prize VARCHAR(12) DEFAULT '0' BEFORE location; diff --git a/craftizens.mysql.sql b/craftizens.mysql.sql new file mode 100644 index 0000000..90666dc --- /dev/null +++ b/craftizens.mysql.sql @@ -0,0 +1,67 @@ +CREATE TABLE `craftizens` ( + `npc_id` VARCHAR(12) NOT NULL, + `npc_name` VARCHAR(30)NOT NULL, + `posx` DOUBLE NOT NULL, + `posy` DOUBLE NOT NULL, + `posz` DOUBLE NOT NULL, + `rotation` FLOAT NOT NULL, + `pitch` FLOAT NOT NULL, + PRIMARY KEY (`npc_id`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +CREATE TABLE `quests` ( + `id` VARCHAR(12) NOT NULL, + `quest_name` TEXT NOT NULL, + `start_npc` VARCHAR(12) NOT NULL, + `end_npc` VARCHAR(12) NOT NULL, + `items_provided` TEXT DEFAULT NULL, + `rewards` TEXT DEFAULT NULL, + `quest_desc` MEDIUMTEXT NOT NULL, + `quest_type` VARCHAR(10) NOT NULL, + `prereq` VARCHAR(12) DEFAULT NULL, + `location` TEXT DEFAULT NULL, + `data` TEXT DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +CREATE TABLE `quests_active` ( + `player_name` VARCHAR(25) NOT NULL, + `quest_id` VARCHAR(12) NOT NULL, + `progress` VARCHAR(50) DEFAULT NULL, + PRIMARY KEY (`player_name`,`quest_id`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +CREATE TABLE `quests_completed` ( + `player_name` VARCHAR(25) NOT NULL, + `quest_id` VARCHAR(12) NOT NULL, + `date_completed` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`player_name`,`quest_id`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +-- v0.2 update + +ALTER TABLE `craftizens` ADD COLUMN `item_in_hand` INT NOT NULL DEFAULT 0 AFTER `pitch`; + +-- v0.5 update + +ALTER TABLE `quests` ADD COLUMN `completion_text` TEXT DEFAULT NULL AFTER `data`; + +CREATE TABLE `craftizens_dialog` ( + `npc_id` VARCHAR(12) NOT NULL, + `dialog_id` VARCHAR(12) NOT NULL, + `dialog_text` TEXT NOT NULL, + PRIMARY KEY (`npc_id`,`dialog_id`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +-- v0.6 update + +ALTER TABLE `craftizens` ADD COLUMN `route_type` VARCHAR(8) DEFAULT NULL AFTER `item_in_hand`; +ALTER TABLE `craftizens` ADD COLUMN `route` TEXT DEFAULT NULL AFTER `route_type`; + +-- v0.7 update +ALTER TABLE `quests` ADD COLUMN `rankreq` VARCHAR(12) DEFAULT NULL AFTER `prereq`; +ALTER TABLE `quests` ADD COLUMN `rankreward` VARCHAR(12) DEFAULT NULL AFTER `rankreq`; + +-- v0.7.1 update +ALTER TABLE `quests` ADD COLUMN `cost` VARCHAR(12) DEFAULT '0' AFTER `rankreward`; +ALTER TABLE `quests` ADD COLUMN `prize` VARCHAR(12) DEFAULT '0' AFTER `cost`; diff --git a/craftizens.sql b/craftizens.sql deleted file mode 100644 index 3807033..0000000 --- a/craftizens.sql +++ /dev/null @@ -1,67 +0,0 @@ -CREATE TABLE `minecraft`.`craftizens` ( - `npc_id` varchar(12) NOT NULL, - `npc_name` varchar(30) CHARACTER SET utf8 NOT NULL, - `posx` double NOT NULL, - `posy` double NOT NULL, - `posz` double NOT NULL, - `rotation` float NOT NULL, - `pitch` float NOT NULL, - PRIMARY KEY (`npc_id`) -) ENGINE=MyISAM DEFAULT CHARSET=latin1; - -CREATE TABLE `minecraft`.`quests` ( - `id` varchar(12) NOT NULL, - `quest_name` text CHARACTER SET utf8 NOT NULL, - `start_npc` varchar(12) NOT NULL, - `end_npc` varchar(12) NOT NULL, - `items_provided` text CHARACTER SET utf8, - `rewards` text CHARACTER SET utf8, - `quest_desc` mediumtext CHARACTER SET utf8 NOT NULL, - `quest_type` varchar(10) NOT NULL, - `prereq` varchar(12) DEFAULT NULL, - `location` text, - `data` text, - PRIMARY KEY (`id`) -) ENGINE=MyISAM DEFAULT CHARSET=latin1; - -CREATE TABLE `minecraft`.`quests_active` ( - `player_name` varchar(25) NOT NULL, - `quest_id` varchar(12) NOT NULL, - `progress` varchar(50) DEFAULT NULL, - PRIMARY KEY (`player_name`,`quest_id`) -) ENGINE=MyISAM DEFAULT CHARSET=latin1; - -CREATE TABLE `minecraft`.`quests_completed` ( - `player_name` varchar(25) NOT NULL, - `quest_id` varchar(12) NOT NULL, - `date_completed` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (`player_name`,`quest_id`) -) ENGINE=MyISAM DEFAULT CHARSET=latin1; - --- v0.2 update - -ALTER TABLE `minecraft`.`craftizens` ADD COLUMN `item_in_hand` INT NOT NULL DEFAULT 0 AFTER `pitch`; - --- v0.5 update - -ALTER TABLE `minecraft`.`quests` ADD COLUMN `completion_text` TEXT DEFAULT NULL AFTER `data`; - -CREATE TABLE `minecraft`.`craftizens_dialog` ( - `npc_id` varchar(12) NOT NULL, - `dialog_id` varchar(12) NOT NULL, - `dialog_text` text NOT NULL, - PRIMARY KEY (`npc_id`,`dialog_id`) -) ENGINE=MyISAM DEFAULT CHARSET=latin1; - --- v0.6 update - -ALTER TABLE `minecraft`.`craftizens` ADD COLUMN `route_type` VARCHAR(8) DEFAULT NULL AFTER `item_in_hand`, - ADD COLUMN `route` TEXT DEFAULT NULL AFTER `route_type`; - --- v0.7 update -ALTER TABLE `minecraft`.`quests` ADD COLUMN `rankreq` VARCHAR(12) DEFAULT NULL AFTER `prereq`, - ADD COLUMN `rankreward` VARCHAR(12) DEFAULT NULL AFTER `rankreq`; - --- v0.7.1 update -ALTER TABLE `minecraft`.`quests` ADD COLUMN `cost` VARCHAR(12) DEFAULT '0' AFTER `rankreward`, - ADD COLUMN `prize` VARCHAR(12) DEFAULT '0' AFTER `cost`; From 117b7f3c75093717265d3fec814a3b17088ffe64 Mon Sep 17 00:00:00 2001 From: Michael Nowak Date: Wed, 15 Dec 2010 19:21:33 +0100 Subject: [PATCH 09/39] Fixed error with itemInHand --- NonPlayerCharacter.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NonPlayerCharacter.java b/NonPlayerCharacter.java index bec0703..9c26770 100644 --- a/NonPlayerCharacter.java +++ b/NonPlayerCharacter.java @@ -16,6 +16,8 @@ public NonPlayerCharacter(String name, double x, double y, double z, float rotat teleportTo(x,y,z,rotation,pitch); if (itemInHand > 0) { setItemInHand(itemInHand); + } else { + setItemInHand(0); } handler = new gw(user, 512, 1 , true ); From f8edaf36a092330814bef2c9392bb554915b3370 Mon Sep 17 00:00:00 2001 From: Michael Nowak Date: Thu, 16 Dec 2010 00:23:36 +0100 Subject: [PATCH 10/39] Added SQL-file for SQLite --- Craftizens.java | 2 +- QuestInfo.java | 2 +- README.txt | 10 +++---- craftizens.hysql.sql | 10 +++---- craftizens.mysql.sql | 2 +- craftizens.sqlite.sql | 67 +++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 80 insertions(+), 13 deletions(-) create mode 100644 craftizens.sqlite.sql diff --git a/Craftizens.java b/Craftizens.java index b672ff7..56c3b8b 100644 --- a/Craftizens.java +++ b/Craftizens.java @@ -22,7 +22,7 @@ public class Craftizens extends Plugin { public static String DATA_SOURCE_PASSWORD = ""; public static boolean ICONOMY_DETECTED = false; public static String NAME = "Craftizens"; - public static String VERSION = "v0.7.2"; + public static String VERSION = "v0.7.2-with-jdbc-support"; public static CraftizenDataSource data; public static HashSet npcs; diff --git a/QuestInfo.java b/QuestInfo.java index 2c6caf0..f4cc33f 100644 --- a/QuestInfo.java +++ b/QuestInfo.java @@ -96,7 +96,7 @@ public QuestInfo(String id, String type, String name, String desc, String pickUp } } - // this function uses hardcoded € because iConomy is not a singleton + // this function uses hardcoded because iConomy is not a singleton // so I can't get iConony.moneyName public boolean checkBalance(Player player, boolean deduct) { if (Craftizens.ICONOMY_DETECTED) { diff --git a/README.txt b/README.txt index 1ffd7b6..cfcf646 100644 --- a/README.txt +++ b/README.txt @@ -1,8 +1,8 @@ -0.7 - Added rankreq and rankreward functionality +0.7.0 - Added rankreq and rankreward functionality -0.7.1 - Added iConomy hook +0.7.1 - Added iConomy hook; Also I added the .sql script to the commit -Also I added the .sql script to the commit +0.7.2 - Added support for JDBC; Currently HyperSQL and SQLite -Find latest compiled jar here: -http://mc.nexua.org/plugins/Craftizens/v071/ \ No newline at end of file +Find latest compiled JAR here: +https://github.com/THExSYSTEM/CraftizensPlugin/ diff --git a/craftizens.hysql.sql b/craftizens.hysql.sql index 9a964ba..48e0364 100644 --- a/craftizens.hysql.sql +++ b/craftizens.hysql.sql @@ -38,11 +38,11 @@ CREATE TABLE quests_completed ( PRIMARY KEY (player_name,quest_id) ); --- v0.2 update +/* v0.2 update */ ALTER TABLE craftizens ADD COLUMN item_in_hand INT DEFAULT 0 NOT NULL; --- v0.5 update +/* v0.5 update */ ALTER TABLE quests ADD COLUMN completion_text VARCHAR(512) DEFAULT NULL; @@ -53,15 +53,15 @@ CREATE TABLE craftizens_dialog ( PRIMARY KEY (npc_id,dialog_id) ); --- v0.6 update +/* v0.6 update */ ALTER TABLE craftizens ADD COLUMN route_type VARCHAR(8) DEFAULT NULL; ALTER TABLE craftizens ADD COLUMN route VARCHAR(512) DEFAULT NULL; --- v0.7 update +/* v0.7 update */ ALTER TABLE quests ADD COLUMN rankreq VARCHAR(12) DEFAULT NULL BEFORE location; ALTER TABLE quests ADD COLUMN rankreward VARCHAR(12) DEFAULT NULL BEFORE location; --- v0.7.1 update +/* v0.7.1 update */ ALTER TABLE quests ADD COLUMN cost VARCHAR(12) DEFAULT '0' BEFORE location; ALTER TABLE quests ADD COLUMN prize VARCHAR(12) DEFAULT '0' BEFORE location; diff --git a/craftizens.mysql.sql b/craftizens.mysql.sql index 90666dc..e342f24 100644 --- a/craftizens.mysql.sql +++ b/craftizens.mysql.sql @@ -1,6 +1,6 @@ CREATE TABLE `craftizens` ( `npc_id` VARCHAR(12) NOT NULL, - `npc_name` VARCHAR(30)NOT NULL, + `npc_name` VARCHAR(30) NOT NULL, `posx` DOUBLE NOT NULL, `posy` DOUBLE NOT NULL, `posz` DOUBLE NOT NULL, diff --git a/craftizens.sqlite.sql b/craftizens.sqlite.sql new file mode 100644 index 0000000..7d6bb4c --- /dev/null +++ b/craftizens.sqlite.sql @@ -0,0 +1,67 @@ +CREATE TABLE `craftizens` ( + `npc_id` VARCHAR(12) NOT NULL, + `npc_name` VARCHAR(30) NOT NULL, + `posx` DOUBLE NOT NULL, + `posy` DOUBLE NOT NULL, + `posz` DOUBLE NOT NULL, + `rotation` FLOAT NOT NULL, + `pitch` FLOAT NOT NULL, + PRIMARY KEY (`npc_id`) +); + +CREATE TABLE `quests` ( + `id` VARCHAR(12) NOT NULL, + `quest_name` TEXT NOT NULL, + `start_npc` VARCHAR(12) NOT NULL, + `end_npc` VARCHAR(12) NOT NULL, + `items_provided` TEXT DEFAULT NULL, + `rewards` TEXT DEFAULT NULL, + `quest_desc` MEDIUMTEXT NOT NULL, + `quest_type` VARCHAR(10) NOT NULL, + `prereq` VARCHAR(12) DEFAULT NULL, + `location` TEXT DEFAULT NULL, + `data` TEXT DEFAULT NULL, + PRIMARY KEY (`id`) +); + +CREATE TABLE `quests_active` ( + `player_name` VARCHAR(25) NOT NULL, + `quest_id` VARCHAR(12) NOT NULL, + `progress` VARCHAR(50) DEFAULT NULL, + PRIMARY KEY (`player_name`,`quest_id`) +); + +CREATE TABLE `quests_completed` ( + `player_name` VARCHAR(25) NOT NULL, + `quest_id` VARCHAR(12) NOT NULL, + `date_completed` TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, + PRIMARY KEY (`player_name`,`quest_id`) +); + +-- v0.2 update + +ALTER TABLE `craftizens` ADD COLUMN `item_in_hand` INT DEFAULT 0 NOT NULL; + +-- v0.5 update + +ALTER TABLE `quests` ADD COLUMN `completion_text` TEXT DEFAULT NULL; + +CREATE TABLE `craftizens_dialog` ( + `npc_id` VARCHAR(12) NOT NULL, + `dialog_id` VARCHAR(12) NOT NULL, + `dialog_text` TEXT NOT NULL, + PRIMARY KEY (`npc_id`,`dialog_id`) +); + +-- v0.6 update + +ALTER TABLE `craftizens` ADD COLUMN `route_type` VARCHAR(8) DEFAULT NULL; +ALTER TABLE `craftizens` ADD COLUMN `route` TEXT DEFAULT NULL; + +-- v0.7 update +ALTER TABLE `quests` ADD COLUMN `rankreq` VARCHAR(12) DEFAULT NULL; +ALTER TABLE `quests` ADD COLUMN `rankreward` VARCHAR(12) DEFAULT NULL; + +-- v0.7.1 update +ALTER TABLE `quests` ADD COLUMN `cost` VARCHAR(12) DEFAULT '0'; +ALTER TABLE `quests` ADD COLUMN `prize` VARCHAR(12) DEFAULT '0'; From a40254598cdb2d9e2f0e0aebde13ef3cd4f42e75 Mon Sep 17 00:00:00 2001 From: Michael Nowak Date: Fri, 17 Dec 2010 02:01:42 +0100 Subject: [PATCH 11/39] Added build.xml for easy building with ant --- .gitignore | 5 ++ build.xml | 84 +++++++++++++++++++ ...izens.sqlite.sql => craftizens.sqlite3.sql | 0 BuildQuest.java => src/BuildQuest.java | 0 Craftizen.java => src/Craftizen.java | 0 .../CraftizenDataSource.java | 0 .../CraftizenSQLDataSource.java | 0 .../CraftizenTicker.java | 0 Craftizens.java => src/Craftizens.java | 0 .../CraftizensListener.java | 0 .../FetchBlockQuest.java | 0 FindLocQuest.java => src/FindLocQuest.java | 0 GatherQuest.java => src/GatherQuest.java | 0 HarvestQuest.java => src/HarvestQuest.java | 0 .../NonPlayerCharacter.java | 0 Quest.java => src/Quest.java | 0 QuestInfo.java => src/QuestInfo.java | 0 iData.java => src/iData.java | 0 18 files changed, 89 insertions(+) create mode 100644 .gitignore create mode 100644 build.xml rename craftizens.sqlite.sql => craftizens.sqlite3.sql (100%) rename BuildQuest.java => src/BuildQuest.java (100%) rename Craftizen.java => src/Craftizen.java (100%) rename CraftizenDataSource.java => src/CraftizenDataSource.java (100%) rename CraftizenSQLDataSource.java => src/CraftizenSQLDataSource.java (100%) rename CraftizenTicker.java => src/CraftizenTicker.java (100%) rename Craftizens.java => src/Craftizens.java (100%) rename CraftizensListener.java => src/CraftizensListener.java (100%) rename FetchBlockQuest.java => src/FetchBlockQuest.java (100%) rename FindLocQuest.java => src/FindLocQuest.java (100%) rename GatherQuest.java => src/GatherQuest.java (100%) rename HarvestQuest.java => src/HarvestQuest.java (100%) rename NonPlayerCharacter.java => src/NonPlayerCharacter.java (100%) rename Quest.java => src/Quest.java (100%) rename QuestInfo.java => src/QuestInfo.java (100%) rename iData.java => src/iData.java (100%) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..83c8a34 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.classpath +.project +bin +dist +lib diff --git a/build.xml b/build.xml new file mode 100644 index 0000000..5c30e5a --- /dev/null +++ b/build.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/craftizens.sqlite.sql b/craftizens.sqlite3.sql similarity index 100% rename from craftizens.sqlite.sql rename to craftizens.sqlite3.sql diff --git a/BuildQuest.java b/src/BuildQuest.java similarity index 100% rename from BuildQuest.java rename to src/BuildQuest.java diff --git a/Craftizen.java b/src/Craftizen.java similarity index 100% rename from Craftizen.java rename to src/Craftizen.java diff --git a/CraftizenDataSource.java b/src/CraftizenDataSource.java similarity index 100% rename from CraftizenDataSource.java rename to src/CraftizenDataSource.java diff --git a/CraftizenSQLDataSource.java b/src/CraftizenSQLDataSource.java similarity index 100% rename from CraftizenSQLDataSource.java rename to src/CraftizenSQLDataSource.java diff --git a/CraftizenTicker.java b/src/CraftizenTicker.java similarity index 100% rename from CraftizenTicker.java rename to src/CraftizenTicker.java diff --git a/Craftizens.java b/src/Craftizens.java similarity index 100% rename from Craftizens.java rename to src/Craftizens.java diff --git a/CraftizensListener.java b/src/CraftizensListener.java similarity index 100% rename from CraftizensListener.java rename to src/CraftizensListener.java diff --git a/FetchBlockQuest.java b/src/FetchBlockQuest.java similarity index 100% rename from FetchBlockQuest.java rename to src/FetchBlockQuest.java diff --git a/FindLocQuest.java b/src/FindLocQuest.java similarity index 100% rename from FindLocQuest.java rename to src/FindLocQuest.java diff --git a/GatherQuest.java b/src/GatherQuest.java similarity index 100% rename from GatherQuest.java rename to src/GatherQuest.java diff --git a/HarvestQuest.java b/src/HarvestQuest.java similarity index 100% rename from HarvestQuest.java rename to src/HarvestQuest.java diff --git a/NonPlayerCharacter.java b/src/NonPlayerCharacter.java similarity index 100% rename from NonPlayerCharacter.java rename to src/NonPlayerCharacter.java diff --git a/Quest.java b/src/Quest.java similarity index 100% rename from Quest.java rename to src/Quest.java diff --git a/QuestInfo.java b/src/QuestInfo.java similarity index 100% rename from QuestInfo.java rename to src/QuestInfo.java diff --git a/iData.java b/src/iData.java similarity index 100% rename from iData.java rename to src/iData.java From e456d6f57272b1c996388272dd77a4d594d359d3 Mon Sep 17 00:00:00 2001 From: Michael Nowak Date: Fri, 17 Dec 2010 02:04:11 +0100 Subject: [PATCH 12/39] Changed URL for downloads --- README.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.txt b/README.txt index cfcf646..094d9cc 100644 --- a/README.txt +++ b/README.txt @@ -5,4 +5,4 @@ 0.7.2 - Added support for JDBC; Currently HyperSQL and SQLite Find latest compiled JAR here: -https://github.com/THExSYSTEM/CraftizensPlugin/ +https://github.com/THExSYSTEM/CraftizensPlugin/downloads/ From b6add0c13ebea6199fa293fc1c266b0613e35564 Mon Sep 17 00:00:00 2001 From: Michael Nowak Date: Fri, 17 Dec 2010 16:38:33 +0100 Subject: [PATCH 13/39] Added ant targets: lib:install, dist:sqlite3 and dist:hsqldb --- build.xml | 46 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/build.xml b/build.xml index 5c30e5a..86b0c6d 100644 --- a/build.xml +++ b/build.xml @@ -12,10 +12,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -41,6 +72,7 @@ + @@ -66,19 +98,21 @@ - - - + + - - + + - + + + + \ No newline at end of file From ce94742ed68852851c4ac731ff87ecb0a94a2bf9 Mon Sep 17 00:00:00 2001 From: Michael Nowak Date: Sun, 19 Dec 2010 17:22:01 +0100 Subject: [PATCH 14/39] The getConnection() method will return a new connection on every call Changed version to 0.7.2_1 --- build.xml | 2 +- src/CraftizenSQLDataSource.java | 30 +++++++++--------------------- src/Craftizens.java | 2 +- 3 files changed, 11 insertions(+), 23 deletions(-) diff --git a/build.xml b/build.xml index 86b0c6d..41c8a18 100644 --- a/build.xml +++ b/build.xml @@ -2,7 +2,7 @@ - + diff --git a/src/CraftizenSQLDataSource.java b/src/CraftizenSQLDataSource.java index 700e2cc..90a595d 100644 --- a/src/CraftizenSQLDataSource.java +++ b/src/CraftizenSQLDataSource.java @@ -7,32 +7,20 @@ public class CraftizenSQLDataSource extends CraftizenDataSource { static final Logger log = Logger.getLogger("Minecraft"); - static protected Connection connection = null; static public Connection getSQLConnection() throws SQLException { - if (connection == null) { - if (Craftizens.DATA_SOURCE_DRIVER_NAME.isEmpty()) { - connection = etc.getSQLConnection(); - } else { - try { - Class.forName(Craftizens.DATA_SOURCE_DRIVER_NAME); - } catch (ClassNotFoundException ex) { - log.log(Level.SEVERE, null, ex); - return null; - } - connection = DriverManager.getConnection(Craftizens.DATA_SOURCE_CONNECTION_URL, Craftizens.DATA_SOURCE_USERNAME, Craftizens.DATA_SOURCE_PASSWORD); - } + Connection connection = null; + if (Craftizens.DATA_SOURCE_DRIVER_NAME.isEmpty()) { + connection = etc.getSQLConnection(); } else { - if (connection.isClosed()) { - connection = null; - log.info("[" + Craftizens.NAME + "] SQL-Connection got closed; Reconnecting!"); - return getSQLConnection(); - } else if (!connection.isValid(0)) { - connection = null; - log.info("[" + Craftizens.NAME + "] SQL-Connection got invalid; Reconnecting!"); - return getSQLConnection(); + try { + Class.forName(Craftizens.DATA_SOURCE_DRIVER_NAME); + } catch (ClassNotFoundException ex) { + log.log(Level.SEVERE, null, ex); + return null; } + connection = DriverManager.getConnection(Craftizens.DATA_SOURCE_CONNECTION_URL, Craftizens.DATA_SOURCE_USERNAME, Craftizens.DATA_SOURCE_PASSWORD); } return connection; } diff --git a/src/Craftizens.java b/src/Craftizens.java index 56c3b8b..d3c7faf 100644 --- a/src/Craftizens.java +++ b/src/Craftizens.java @@ -22,7 +22,7 @@ public class Craftizens extends Plugin { public static String DATA_SOURCE_PASSWORD = ""; public static boolean ICONOMY_DETECTED = false; public static String NAME = "Craftizens"; - public static String VERSION = "v0.7.2-with-jdbc-support"; + public static String VERSION = "v0.7.2_1"; public static CraftizenDataSource data; public static HashSet npcs; From 267dad5456eef1590372a29ffd82618733629f3d Mon Sep 17 00:00:00 2001 From: durron597 Date: Wed, 22 Dec 2010 23:30:25 -0500 Subject: [PATCH 15/39] Fixed bug with /qadmin (no arguments) throwing a NullPointerException --- CraftizensListener.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CraftizensListener.java b/CraftizensListener.java index 6ef05f2..ac56bd2 100644 --- a/CraftizensListener.java +++ b/CraftizensListener.java @@ -304,7 +304,7 @@ public void qadminCommand(Player player, String [] command) { } // iConomy Hook - } else if (command[1].equalsIgnoreCase("iConomy")) { + } else if (command.length >= 2 && command[1].equalsIgnoreCase("iConomy")) { if (command.length > 2 && command[2].equals("-disable")) { Craftizens.ICONOMY_DETECTED = false; player.sendMessage("iConomy disabled. Use /qadmin iConomy to turn it back on."); From 38aedb4086a4013d4f3e910a38b9cfae3cbb9d1f Mon Sep 17 00:00:00 2001 From: durron597 Date: Wed, 22 Dec 2010 23:30:43 -0500 Subject: [PATCH 16/39] Updated for Beta --- FetchBlockQuest.java | 2 +- NonPlayerCharacter.java | 31 +++++++++++++++++++++---------- Quest.java | 3 ++- 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/FetchBlockQuest.java b/FetchBlockQuest.java index 3c96c0b..879eb25 100644 --- a/FetchBlockQuest.java +++ b/FetchBlockQuest.java @@ -118,7 +118,7 @@ public void onPlayerMove(Player player, Location from, Location to) { } public void sendFakeBlockPacket(Player player, int x, int y, int z, int type) { - fn packet = new fn(); + gd packet = new gd(); packet.a = x; packet.b = y; packet.c = z; diff --git a/NonPlayerCharacter.java b/NonPlayerCharacter.java index bec0703..e2bcbfd 100644 --- a/NonPlayerCharacter.java +++ b/NonPlayerCharacter.java @@ -4,30 +4,36 @@ public abstract class NonPlayerCharacter { public static List players; - private et user; - private gw handler; + // XXX: VARIABLE TYPE ON AN UPDATE + private fi user; + // XXX: VARIABLE TYPE ON AN UPDATE + private hr handler; public NonPlayerCharacter(String name, double x, double y, double z, float rotation, float pitch, int itemInHand) { if (players == null) getPlayerList(); MinecraftServer s = etc.getServer().getMCServer(); - - user = new et(s, s.e, name, new jv(s.e)); + + // XXX: VARIABLE TYPE ON AN UPDATE + user = new fi(s, s.e, name, new kw(s.e)); teleportTo(x,y,z,rotation,pitch); if (itemInHand > 0) { setItemInHand(itemInHand); } - handler = new gw(user, 512, 1 , true ); + // XXX: VARIABLE TYPE ON AN UPDATE + handler = new hr(user, 512, 1 , true ); } public void delete() { for (Object player : players) { - ((et)player).a.b(new di(handler.a.g)); + // XXX: VARIABLE TYPE ON AN UPDATE + ((fi)player).a.b(new dv(handler.a.g)); } } public void untrack(Player player) { + // XXX: VARIABLE TYPE ON AN UPDATE if (handler.q.contains(player.getUser())) { handler.q.remove(player.getUser()); } @@ -42,11 +48,13 @@ public void broadcastMovement() { } public String getName() { - return user.at; + // XXX: VARIABLE TYPE ON AN UPDATE + return user.aw; } public void setName(String name) { - user.at = name; + // XXX: VARIABLE TYPE ON AN UPDATE + user.aw = name; } public double getX() { @@ -90,11 +98,14 @@ public void setPitch(float pitch) { } public int getItemInHand() { - return user.am.a[0].c; + // XXX: VARIABLE TYPE ON AN UPDATE + if (user.an.a[0] == null) return 0; + return user.an.a[0].c; } public void setItemInHand(int type) { - user.am.a[0] = new hn(type); + // XXX: VARIABLE TYPE ON AN UPDATE + user.an.a[0] = new il(type); } public void teleportTo(double x, double y, double z, float rotation, float pitch) { diff --git a/Quest.java b/Quest.java index ef3a99c..073e1f0 100644 --- a/Quest.java +++ b/Quest.java @@ -124,7 +124,8 @@ public boolean compass() { cx /= (6*area); cy /= (6*area); - player.getUser().a.b(new co((int)cx, (int)player.getY(), (int)cy)); + // XXX: VARIABLE TYPE ON AN UPDATE + player.getUser().a.b(new da((int)cx, (int)player.getY(), (int)cy)); return true; } else { From e9cecd7f2a205503fb726af03718f0b3454cdaf5 Mon Sep 17 00:00:00 2001 From: durron597 Date: Wed, 22 Dec 2010 23:34:42 -0500 Subject: [PATCH 17/39] Moved all files from root to src/ --- BuildQuest.java => src/BuildQuest.java | 0 Craftizen.java => src/Craftizen.java | 0 CraftizenDataSource.java => src/CraftizenDataSource.java | 0 CraftizenSQLDataSource.java => src/CraftizenSQLDataSource.java | 0 CraftizenTicker.java => src/CraftizenTicker.java | 0 Craftizens.java => src/Craftizens.java | 0 CraftizensListener.java => src/CraftizensListener.java | 0 FetchBlockQuest.java => src/FetchBlockQuest.java | 0 FindLocQuest.java => src/FindLocQuest.java | 0 GatherQuest.java => src/GatherQuest.java | 0 HarvestQuest.java => src/HarvestQuest.java | 0 NonPlayerCharacter.java => src/NonPlayerCharacter.java | 0 Quest.java => src/Quest.java | 0 QuestInfo.java => src/QuestInfo.java | 0 iData.java => src/iData.java | 0 15 files changed, 0 insertions(+), 0 deletions(-) rename BuildQuest.java => src/BuildQuest.java (100%) rename Craftizen.java => src/Craftizen.java (100%) rename CraftizenDataSource.java => src/CraftizenDataSource.java (100%) rename CraftizenSQLDataSource.java => src/CraftizenSQLDataSource.java (100%) rename CraftizenTicker.java => src/CraftizenTicker.java (100%) rename Craftizens.java => src/Craftizens.java (100%) rename CraftizensListener.java => src/CraftizensListener.java (100%) rename FetchBlockQuest.java => src/FetchBlockQuest.java (100%) rename FindLocQuest.java => src/FindLocQuest.java (100%) rename GatherQuest.java => src/GatherQuest.java (100%) rename HarvestQuest.java => src/HarvestQuest.java (100%) rename NonPlayerCharacter.java => src/NonPlayerCharacter.java (100%) rename Quest.java => src/Quest.java (100%) rename QuestInfo.java => src/QuestInfo.java (100%) rename iData.java => src/iData.java (100%) diff --git a/BuildQuest.java b/src/BuildQuest.java similarity index 100% rename from BuildQuest.java rename to src/BuildQuest.java diff --git a/Craftizen.java b/src/Craftizen.java similarity index 100% rename from Craftizen.java rename to src/Craftizen.java diff --git a/CraftizenDataSource.java b/src/CraftizenDataSource.java similarity index 100% rename from CraftizenDataSource.java rename to src/CraftizenDataSource.java diff --git a/CraftizenSQLDataSource.java b/src/CraftizenSQLDataSource.java similarity index 100% rename from CraftizenSQLDataSource.java rename to src/CraftizenSQLDataSource.java diff --git a/CraftizenTicker.java b/src/CraftizenTicker.java similarity index 100% rename from CraftizenTicker.java rename to src/CraftizenTicker.java diff --git a/Craftizens.java b/src/Craftizens.java similarity index 100% rename from Craftizens.java rename to src/Craftizens.java diff --git a/CraftizensListener.java b/src/CraftizensListener.java similarity index 100% rename from CraftizensListener.java rename to src/CraftizensListener.java diff --git a/FetchBlockQuest.java b/src/FetchBlockQuest.java similarity index 100% rename from FetchBlockQuest.java rename to src/FetchBlockQuest.java diff --git a/FindLocQuest.java b/src/FindLocQuest.java similarity index 100% rename from FindLocQuest.java rename to src/FindLocQuest.java diff --git a/GatherQuest.java b/src/GatherQuest.java similarity index 100% rename from GatherQuest.java rename to src/GatherQuest.java diff --git a/HarvestQuest.java b/src/HarvestQuest.java similarity index 100% rename from HarvestQuest.java rename to src/HarvestQuest.java diff --git a/NonPlayerCharacter.java b/src/NonPlayerCharacter.java similarity index 100% rename from NonPlayerCharacter.java rename to src/NonPlayerCharacter.java diff --git a/Quest.java b/src/Quest.java similarity index 100% rename from Quest.java rename to src/Quest.java diff --git a/QuestInfo.java b/src/QuestInfo.java similarity index 100% rename from QuestInfo.java rename to src/QuestInfo.java diff --git a/iData.java b/src/iData.java similarity index 100% rename from iData.java rename to src/iData.java From df029548f6a8c852c83204596bf11a76171d4b6b Mon Sep 17 00:00:00 2001 From: durron597 Date: Wed, 22 Dec 2010 23:49:13 -0500 Subject: [PATCH 18/39] Moved FlatFileDataSource to src/ --- src/CraftizenFlatfileDataSource.java | 390 +++++++++++++++++++++++++++ 1 file changed, 390 insertions(+) create mode 100644 src/CraftizenFlatfileDataSource.java diff --git a/src/CraftizenFlatfileDataSource.java b/src/CraftizenFlatfileDataSource.java new file mode 100644 index 0000000..e01c42a --- /dev/null +++ b/src/CraftizenFlatfileDataSource.java @@ -0,0 +1,390 @@ +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.logging.Logger; + + +public class CraftizenFlatfileDataSource extends CraftizenDataSource +{ + static final Logger log = Logger.getLogger("Minecraft"); + + private static final String DATA_FOLDER = "craftizens/"; + + private HashMap> npcQuests; + + public CraftizenFlatfileDataSource() + { + (new File(DATA_FOLDER)).mkdir(); + } + + public HashSet loadCraftizens() + { + synchronized (craftizenLock) + { + HashSet npcs = new HashSet(); + npcQuests = new HashMap>(); + + // get file list + File[] files = (new File(DATA_FOLDER)).listFiles(); + + // load npc data + for (File file : files) + { + if (file.getName().startsWith("npc-")) + { + PropertiesFile data = new PropertiesFile(DATA_FOLDER + + file.getName()); + Craftizen npc = new Craftizen(data.getString("npc_id"), + data.getString("npc_name"), data.getDouble("posx"), + data.getDouble("posy"), data.getDouble("posz"), + (float) data.getDouble("rotation"), + (float) data.getDouble("pitch"), + data.getInt("item_in_hand")); + + int i = 0; + ArrayList dialog = new ArrayList(); + while (data.containsKey("dialog_" + i)) + { + dialog.add(data.getString("dialog_" + i)); + i++; + } + npc.setDialog(dialog); + npcs.add(npc); + npcQuests.put(npc.getId(), new ArrayList()); + } + } + + // load quest data + for (File file : files) + { + if (file.getName().startsWith("quest-")) + { + PropertiesFile data = new PropertiesFile(DATA_FOLDER + + file.getName()); + ArrayList quests = npcQuests.get(data + .getString("start_npc")); + quests.add(data.getString("id")); + npcQuests.put(data.getString("start_npc"), quests); + } + } + return npcs; + } + } + + public void saveCraftizen(Craftizen c) + { + synchronized (craftizenLock) + { + PropertiesFile data = new PropertiesFile(DATA_FOLDER + "npc-" + + c.getId() + ".txt"); + data.setString("npc_id", c.getId()); + data.setString("npc_name", c.getName()); + data.setDouble("posx", c.getX()); + data.setDouble("posy", c.getY()); + data.setDouble("posz", c.getZ()); + data.setDouble("rotation", c.getRotation()); + data.setDouble("pitch", c.getPitch()); + data.setInt("item_in_hand", c.getItemInHand()); + } + } + + public void addCraftizenDialog(String npcid, String dialogid, String dialog) + { + synchronized (craftizenLock) + { + PropertiesFile data = new PropertiesFile(DATA_FOLDER + "npc-" + + npcid + ".txt"); + int i = -1; + while (data.containsKey("dialog_" + ++i)) + { + } + data.setString("dialog_" + i, dialog); + } + } + + public void deleteCraftizen(String id) + { + synchronized (craftizenLock) + { + (new File(DATA_FOLDER + "npc-" + id + ".txt")).delete(); + } + } + + public ArrayList getQuestList() + { + synchronized (questLock) + { + ArrayList quests = null; + + // get file list + File[] files = (new File(DATA_FOLDER)).listFiles(); + + // load quest data + for (File file : files) + { + if (file.getName().startsWith("quest-")) + { + PropertiesFile data = new PropertiesFile(DATA_FOLDER + + file.getName()); + if (quests == null) quests = new ArrayList(); + quests.add(data.getString("id")); + } + } + + return quests; + } + } + + public QuestInfo loadQuestInfo(String id) + { + synchronized (questLock) + { + if ((new File(DATA_FOLDER + "quest-" + id + ".txt")).exists()) + { + PropertiesFile data = new PropertiesFile(DATA_FOLDER + "quest-" + + id + ".txt"); + QuestInfo quest = new QuestInfo(data.getString("id"), + data.getString("quest_type"), + data.getString("quest_name"), + data.getString("quest_desc"), + data.getString("start_npc"), data.getString("end_npc"), + data.getString("prereq"), + data.getString("items_provided"), + data.getString("rewards"), data.getString("location"), + data.getString("data"), + data.getString("completion_text"), + data.getString("rankreq"), + data.getString("rankreward"), data.getString("cost"), + data.getString("prize")); + return quest; + } + else + { + return null; + } + } + } + + public ArrayList getAvailableQuests(Craftizen c, Player p) + { + synchronized (questLock) + { + ArrayList quests = new ArrayList(); + // getting quests this NPC has + for (String q : npcQuests.get(c.id)) + { + // now we only add QuestInfo objects that the player can + // actually access + QuestInfo quest = loadQuestInfo(q); + + // logic for ranks + String[] groups = p.getGroups(); + if (groups.length > 0) + { + if (groups[0] == null || groups[0].equals("")) + { + if (!quest.rankReq.isEmpty()) + { + continue; + } + } + else + { + if ((!quest.rankReq.isEmpty()) + && (!quest.rankReq.equalsIgnoreCase(groups[0]))) + { + continue; + } + } + } + else + { + if (!quest.rankReq.isEmpty()) + { + continue; + } + } + + PropertiesFile player = loadPlayer(p); + // logic for active quests + String[] activeQuests = player.getString("active-quests") + .split(","); + boolean activeFound = false; + for (String aq : activeQuests) + { + if (aq.equalsIgnoreCase(q)) + { + activeFound = true; + break; + } + } + if (activeFound) + { + continue; + } + // logic for already completed quests + String[] completedQuests = player.getString("completed-quests") + .split(","); + boolean completedFound = false; + for (String cq : completedQuests) + { + if (cq.equalsIgnoreCase(q)) + { + completedFound = true; + break; + } + } + if (completedFound) + { + continue; + } + + // add the quest to the list + quests.add(quest); + } + return quests; + } + } + + private PropertiesFile loadPlayer(Player p) + { + return new PropertiesFile(DATA_FOLDER + "player-" + p.getName() + + ".txt"); + } + + public HashMap getActiveQuests(Player p) + { + synchronized (questLock) + { + HashMap quests = new HashMap(); + + PropertiesFile player = loadPlayer(p); + String[] q = player.getString("active-quests").split(","); + for (String aq : q) + { + QuestInfo quest = loadQuestInfo(aq); + if (quest == null) + { + continue; + } + String progress = player.getString("progress-" + aq); + quests.put(quest, progress); + } + return quests; + } + } + + public void saveActiveQuest(Player player, Quest quest) + { + synchronized (questLock) + { + PropertiesFile p = loadPlayer(player); + if (p.getString("active-quests").isEmpty()) + { + p.setString("active-quests", quest.getId()); + } + else + { + p.setString("active-quests", p.getString("active-quests") + "," + + quest.getId()); + } + } + } + + public void saveQuestProgress(Player player, Quest quest, String progress) + { + synchronized (questLock) + { + PropertiesFile p = loadPlayer(player); + p.setString("progress-" + quest.getId(), progress); + } + } + + public void dropActiveQuest(Player player, Quest quest) + { + synchronized (questLock) + { + PropertiesFile p = loadPlayer(player); + String[] q = p.getString("active-quests").split(","); + String activeQuests = ""; + for (String aq : q) + { + if (!quest.getId().equalsIgnoreCase(aq)) + { + if (activeQuests.isEmpty()) + { + activeQuests = aq; + } + else + { + activeQuests = activeQuests + "," + aq; + } + } + } + p.setString("active-quests", activeQuests); + } + } + + public void saveCompletedQuest(Player player, Quest quest) + { + dropActiveQuest(player, quest); + synchronized (questLock) + { + PropertiesFile p = loadPlayer(player); + if (p.getString("completed-quests").isEmpty()) + { + p.setString("completed-quests", quest.getId()); + } + else + { + p.setString("completed-quests", p.getString("completed-quests") + + "," + quest.getId()); + } + } + } + + public void saveQuest(QuestInfo quest) + { + synchronized (questLock) + { + PropertiesFile data = new PropertiesFile(DATA_FOLDER + "quest-" + + quest.getId() + ".txt"); + + data.setString("id", quest.id); + data.setString("quest_name", (quest.name == null) ? "" : quest.name); + data.setString("quest_type", (quest.type == null) ? "" : quest.type); + data.setString("quest_desc", (quest.desc == null) ? "" : quest.desc); + data.setString("start_npc", (quest.pickUp == null) ? "" + : quest.pickUp); + data.setString("end_npc", (quest.turnIn == null) ? "" + : quest.turnIn); + data.setString("prereq", (quest.prereq == null) ? "" : quest.prereq); + data.setString("items_provided", + (quest.itemsProvidedStr == null) ? "" + : quest.itemsProvidedStr); + data.setString("rewards", (quest.rewardsStr == null) ? "" + : quest.rewardsStr); + data.setString("location", (quest.location == null) ? "" + : quest.location); + data.setString("data", (quest.data == null) ? "" : quest.data); + data.setString("completion_text", + (quest.completionText == null) ? "" : quest.completionText); + data.setString("rankreq", (quest.rankReq == null) ? "" + : quest.rankReq); + data.setString("rankreward", (quest.rankReward == null) ? "" + : quest.rankReward); + data.setInt("cost", quest.cost); + data.setInt("prize", quest.prize); + } + } + + public void deleteQuest(String questid) + { + synchronized (questLock) + { + (new File(DATA_FOLDER + "quest-" + questid + ".txt")).delete(); + } + } + +} From 13774e551d276bcfdef47b6ad8323b11ffee8eb2 Mon Sep 17 00:00:00 2001 From: durron597 Date: Thu, 23 Dec 2010 00:53:59 -0500 Subject: [PATCH 19/39] Added flatfile-data-enabled boolean to properties file --- src/Craftizens.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Craftizens.java b/src/Craftizens.java index d3c7faf..be9bb27 100644 --- a/src/Craftizens.java +++ b/src/Craftizens.java @@ -16,6 +16,7 @@ public class Craftizens extends Plugin { public static int INTERACT_ANGLE_VARIATION = 25; public static int QADMIN_BOUNDARY_MARKER = 340; public static boolean QUESTS_ENABLED = true; + public static boolean FLATFILE_DATA = false; public static String DATA_SOURCE_DRIVER_NAME = ""; public static String DATA_SOURCE_CONNECTION_URL = ""; public static String DATA_SOURCE_USERNAME = ""; @@ -57,12 +58,16 @@ public void enable() { INTERACT_ANGLE_VARIATION = props.getInt("npc-interact-angle-variation", INTERACT_ANGLE_VARIATION); QADMIN_BOUNDARY_MARKER = props.getInt("qadmin-boundary-marker", QADMIN_BOUNDARY_MARKER); QUESTS_ENABLED = props.getBoolean("quests-enabled", QUESTS_ENABLED); - DATA_SOURCE_DRIVER_NAME = props.getString("data-source-driver-name", DATA_SOURCE_DRIVER_NAME); + FLATFILE_DATA = props.getBoolean("flatfile-data-enabled", FLATFILE_DATA); + DATA_SOURCE_DRIVER_NAME = props.getString("data-source-driver-name", DATA_SOURCE_DRIVER_NAME); DATA_SOURCE_CONNECTION_URL = props.getString("data-source-connection-url", DATA_SOURCE_CONNECTION_URL); DATA_SOURCE_USERNAME = props.getString("data-source-username", DATA_SOURCE_USERNAME); DATA_SOURCE_PASSWORD = props.getString("data-source-password", DATA_SOURCE_PASSWORD); - data = new CraftizenSQLDataSource(); + if (FLATFILE_DATA) + data = new CraftizenFlatfileDataSource(); + else + data = new CraftizenSQLDataSource(); loadiConomy(); From 973e964363c1021ae296a728af0fd06b34adde7c Mon Sep 17 00:00:00 2001 From: durron597 Date: Thu, 23 Dec 2010 00:57:18 -0500 Subject: [PATCH 20/39] Updated version number to 0.8.0, Updated readme --- README.txt | 4 +++- src/Craftizens.java | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/README.txt b/README.txt index 094d9cc..4ad4bb7 100644 --- a/README.txt +++ b/README.txt @@ -4,5 +4,7 @@ 0.7.2 - Added support for JDBC; Currently HyperSQL and SQLite +0.8.0 - Bugfixes, updated for Beta + Find latest compiled JAR here: -https://github.com/THExSYSTEM/CraftizensPlugin/downloads/ +http://www.stwing.upenn.edu/~martinja/minecraft/ diff --git a/src/Craftizens.java b/src/Craftizens.java index be9bb27..faaf338 100644 --- a/src/Craftizens.java +++ b/src/Craftizens.java @@ -23,7 +23,7 @@ public class Craftizens extends Plugin { public static String DATA_SOURCE_PASSWORD = ""; public static boolean ICONOMY_DETECTED = false; public static String NAME = "Craftizens"; - public static String VERSION = "v0.7.2_1"; + public static String VERSION = "v0.8.0"; public static CraftizenDataSource data; public static HashSet npcs; From b2085828f00bd0042f3f9d7102f1a7cbf1524168 Mon Sep 17 00:00:00 2001 From: durron597 Date: Thu, 23 Dec 2010 12:29:49 -0500 Subject: [PATCH 21/39] Fixed changes to inventory API --- src/FetchBlockQuest.java | 2 +- src/GatherQuest.java | 4 ++-- src/HarvestQuest.java | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/FetchBlockQuest.java b/src/FetchBlockQuest.java index 879eb25..212a843 100644 --- a/src/FetchBlockQuest.java +++ b/src/FetchBlockQuest.java @@ -57,7 +57,7 @@ public void complete() { for (int i = 0; i < types.length; i++) { inv.removeItem(new Item(types[i], 1)); } - inv.updateInventory(); + inv.update(); super.complete(); } diff --git a/src/GatherQuest.java b/src/GatherQuest.java index 60ccae3..aae6f9d 100644 --- a/src/GatherQuest.java +++ b/src/GatherQuest.java @@ -36,7 +36,7 @@ public boolean isComplete() { public int countItems(int itemId) { Inventory inv = player.getInventory(); int amt = 0; - for (int i = 0; i < inv.getArray().length; i++) { + for (int i = 0; i < inv.getContents().length; i++) { Item item = inv.getItemFromSlot(i); if (item != null && item.getItemId() == itemId) { amt += item.getAmount(); @@ -50,7 +50,7 @@ public void complete() { for (int i = 0; i < types.size(); i++) { inv.removeItem(new Item(types.get(i), quantities[i])); } - inv.updateInventory(); + inv.update(); super.complete(); } diff --git a/src/HarvestQuest.java b/src/HarvestQuest.java index a465574..26b6765 100644 --- a/src/HarvestQuest.java +++ b/src/HarvestQuest.java @@ -41,9 +41,9 @@ public boolean isComplete() { public void complete() { Inventory inv = player.getInventory(); for (int i = 0; i < types.size(); i++) { - inv.removeItem(new Item(types.get(i), quantities[i])); + inv.removeItem(types.get(i), quantities[i]); } - inv.updateInventory(); + inv.update(); super.complete(); } @@ -70,7 +70,7 @@ public String getProgress() { public int countItems(int itemId) { Inventory inv = player.getInventory(); int amt = 0; - for (int i = 0; i < inv.getArray().length; i++) { + for (int i = 0; i < inv.getContents().length; i++) { Item item = inv.getItemFromSlot(i); if (item != null && item.getItemId() == itemId) { amt += item.getAmount(); From 0e38c34943ef08cd02bf542842d8f86f92a50dfa Mon Sep 17 00:00:00 2001 From: durron597 Date: Thu, 23 Dec 2010 23:57:34 -0500 Subject: [PATCH 22/39] Added /npc list, usage instructions for /npc noargs --- src/CraftizensListener.java | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/CraftizensListener.java b/src/CraftizensListener.java index ac56bd2..158ee81 100644 --- a/src/CraftizensListener.java +++ b/src/CraftizensListener.java @@ -25,7 +25,14 @@ public void npcCommand(Player player, String [] command) { npc.delete(); } Craftizens.npcs.clear(); - } else if (command[1].equals("create") && command.length > 3) { + } else if (command.length > 1 && command[1].equals("list")) { + for (Craftizen npc : Craftizens.npcs) { + player.sendMessage(Craftizens.TEXT_COLOR + npc.getId() + " - " + npc.getName() + " - " + + ((int) Math.floor(npc.getX())) + "," + + ((int) Math.floor(npc.getY())) + "," + + ((int) Math.floor(npc.getZ())) + ","); + } + } else if (command.length > 3 && command[1].equals("create")) { String id = command[2]; String n = ""; for (int i = 3; i < command.length; i++) { @@ -38,7 +45,7 @@ public void npcCommand(Player player, String [] command) { player.sendMessage("NPC '" + id + "' created."); - } else if (command[1].equals("adddialog") && command.length > 4) { + } else if (command.length > 4 && command[1].equals("adddialog")) { String npcid = command[2]; String dialogid = command[3]; String dialog = ""; @@ -55,13 +62,19 @@ public void npcCommand(Player player, String [] command) { player.sendMessage("No such npc"); } - } else if (command[1].equals("delete") && command.length == 3) { + } else if (command.length == 3 && command[1].equals("delete")) { Craftizen.delete(command[2]); Craftizens.data.deleteCraftizen(command[2]); player.sendMessage("NPC '" + command[2] + "' deleted."); + } else { + player.sendMessage(Craftizens.TEXT_COLOR + "Usage instructions:"); + player.sendMessage(Craftizens.TEXT_COLOR + " /npc list - list your npcs"); + player.sendMessage(Craftizens.TEXT_COLOR + " /npc create - create a new npc"); + player.sendMessage(Craftizens.TEXT_COLOR + " /npc adddialog - add some dialog for your npc"); + player.sendMessage(Craftizens.TEXT_COLOR + " /npc delete - delete your npc"); } } From f60a2547ab15b9b8b31995b8253306396ba1b613 Mon Sep 17 00:00:00 2001 From: durron597 Date: Fri, 24 Dec 2010 00:31:05 -0500 Subject: [PATCH 23/39] Fixed a bug with /npc noargs --- src/CraftizensListener.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CraftizensListener.java b/src/CraftizensListener.java index 158ee81..77e8b1c 100644 --- a/src/CraftizensListener.java +++ b/src/CraftizensListener.java @@ -20,7 +20,7 @@ public boolean onCommand(Player player, String [] split) { } public void npcCommand(Player player, String [] command) { - if (command[1].equals("clear")) { + if (command.length > 1 && command[1].equals("clear")) { for (Craftizen npc : Craftizens.npcs) { npc.delete(); } From 5a5f5ad10ff8ff916799567d1f24ac500afffd61 Mon Sep 17 00:00:00 2001 From: durron597 Date: Fri, 24 Dec 2010 00:31:21 -0500 Subject: [PATCH 24/39] Fixed a bug where /rankreward is not set --- src/Quest.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Quest.java b/src/Quest.java index 073e1f0..511f7cf 100644 --- a/src/Quest.java +++ b/src/Quest.java @@ -58,6 +58,9 @@ protected void giveRewards() { private void setRank(Player p, String rank) { etc.getInstance(); + + if (rank.isEmpty()) return; + Group g = etc.getDataSource().getGroup(rank); if (g != null) { String[] arrayOfString = { g.Name }; From 6ca343a95b13a3b93d95c464ffb129aaf969a86c Mon Sep 17 00:00:00 2001 From: jmartin Date: Wed, 29 Dec 2010 12:48:48 -0500 Subject: [PATCH 25/39] Fixed bug with completed quests throwing a database error --- src/CraftizenSQLDataSource.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CraftizenSQLDataSource.java b/src/CraftizenSQLDataSource.java index 90a595d..a580947 100644 --- a/src/CraftizenSQLDataSource.java +++ b/src/CraftizenSQLDataSource.java @@ -498,7 +498,7 @@ public void saveCompletedQuest(Player player, Quest quest) { query.executeUpdate(); query.close(); - query = conn.prepareStatement("INSERT INTO quests_completed (player_name, quest_id, date_completed) VALUES (?,?,CURRENT_TIME)"); + query = conn.prepareStatement("INSERT INTO quests_completed (player_name, quest_id) VALUES (?,?)"); query.setString(1, player.getName().toLowerCase()); query.setString(2, quest.getId()); query.executeUpdate(); From 339aae6850c0f853f772f5389d422591fdfa03cf Mon Sep 17 00:00:00 2001 From: jmartin Date: Wed, 29 Dec 2010 12:49:37 -0500 Subject: [PATCH 26/39] Fixed bug where npe is thrown when an npc has no quests --- src/CraftizenFlatfileDataSource.java | 105 ++++++++++++++------------- 1 file changed, 54 insertions(+), 51 deletions(-) diff --git a/src/CraftizenFlatfileDataSource.java b/src/CraftizenFlatfileDataSource.java index e01c42a..9e4c8be 100644 --- a/src/CraftizenFlatfileDataSource.java +++ b/src/CraftizenFlatfileDataSource.java @@ -172,76 +172,79 @@ public ArrayList getAvailableQuests(Craftizen c, Player p) { ArrayList quests = new ArrayList(); // getting quests this NPC has - for (String q : npcQuests.get(c.id)) - { - // now we only add QuestInfo objects that the player can - // actually access - QuestInfo quest = loadQuestInfo(q); - - // logic for ranks - String[] groups = p.getGroups(); - if (groups.length > 0) + ArrayList questNames = npcQuests.get(c.id); + if (questNames != null) { + for (String q : npcQuests.get(c.id)) { - if (groups[0] == null || groups[0].equals("")) + // now we only add QuestInfo objects that the player can + // actually access + QuestInfo quest = loadQuestInfo(q); + + // logic for ranks + String[] groups = p.getGroups(); + if (groups.length > 0) { - if (!quest.rankReq.isEmpty()) + if (groups[0] == null || groups[0].equals("")) { - continue; + if (!quest.rankReq.isEmpty()) + { + continue; + } + } + else + { + if ((!quest.rankReq.isEmpty()) + && (!quest.rankReq.equalsIgnoreCase(groups[0]))) + { + continue; + } } } else { - if ((!quest.rankReq.isEmpty()) - && (!quest.rankReq.equalsIgnoreCase(groups[0]))) + if (!quest.rankReq.isEmpty()) { continue; } } - } - else - { - if (!quest.rankReq.isEmpty()) + + PropertiesFile player = loadPlayer(p); + // logic for active quests + String[] activeQuests = player.getString("active-quests") + .split(","); + boolean activeFound = false; + for (String aq : activeQuests) + { + if (aq.equalsIgnoreCase(q)) + { + activeFound = true; + break; + } + } + if (activeFound) { continue; } - } - - PropertiesFile player = loadPlayer(p); - // logic for active quests - String[] activeQuests = player.getString("active-quests") - .split(","); - boolean activeFound = false; - for (String aq : activeQuests) - { - if (aq.equalsIgnoreCase(q)) + // logic for already completed quests + String[] completedQuests = player.getString("completed-quests") + .split(","); + boolean completedFound = false; + for (String cq : completedQuests) { - activeFound = true; - break; + if (cq.equalsIgnoreCase(q)) + { + completedFound = true; + break; + } } - } - if (activeFound) - { - continue; - } - // logic for already completed quests - String[] completedQuests = player.getString("completed-quests") - .split(","); - boolean completedFound = false; - for (String cq : completedQuests) - { - if (cq.equalsIgnoreCase(q)) + if (completedFound) { - completedFound = true; - break; + continue; } + + // add the quest to the list + quests.add(quest); } - if (completedFound) - { - continue; - } - - // add the quest to the list - quests.add(quest); } return quests; } From 604005976e52948fcf37116cc33b7db964eeffa8 Mon Sep 17 00:00:00 2001 From: jmartin Date: Wed, 29 Dec 2010 12:51:23 -0500 Subject: [PATCH 27/39] Changed /npc to /craftnpc so as not to step on the npcs plugin --- src/CraftizensListener.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CraftizensListener.java b/src/CraftizensListener.java index 77e8b1c..3026637 100644 --- a/src/CraftizensListener.java +++ b/src/CraftizensListener.java @@ -6,7 +6,7 @@ public class CraftizensListener extends PluginListener { static final Logger log = Logger.getLogger("Minecraft"); public boolean onCommand(Player player, String [] split) { - if (split[0].equals("/npc") && player.canUseCommand("/npc")) { + if (split[0].equals("/craftnpc") && player.canUseCommand("/craftnpc")) { npcCommand(player, split); return true; } else if ((split[0].equalsIgnoreCase("/quest") || split[0].equalsIgnoreCase("/q")) && player.canUseCommand("/quest")) { From d3201d235796ed3228ae87a2bb78269d3033eb29 Mon Sep 17 00:00:00 2001 From: jmartin Date: Wed, 29 Dec 2010 13:00:34 -0500 Subject: [PATCH 28/39] Added help text for commands. Cleaned up iConomy loading --- src/Craftizens.java | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/Craftizens.java b/src/Craftizens.java index faaf338..cee7c7e 100644 --- a/src/Craftizens.java +++ b/src/Craftizens.java @@ -23,7 +23,7 @@ public class Craftizens extends Plugin { public static String DATA_SOURCE_PASSWORD = ""; public static boolean ICONOMY_DETECTED = false; public static String NAME = "Craftizens"; - public static String VERSION = "v0.8.0"; + public static String VERSION = "v0.8.1"; public static CraftizenDataSource data; public static HashSet npcs; @@ -46,6 +46,10 @@ public void initialize() { } public void enable() { + etc.getInstance().addCommand("/craftnpc", "Create an npc"); + etc.getInstance().addCommand("/quest", "Command for working with your quests"); + etc.getInstance().addCommand("/qadmin", "Create, modify, delete, control the quests on your server"); + // load properties PropertiesFile props = new PropertiesFile("craftizens.properties"); DEBUG = props.getBoolean("debug-mode", DEBUG); @@ -89,6 +93,10 @@ public void enable() { } public void disable() { + etc.getInstance().removeCommand("/craftnpc"); + etc.getInstance().removeCommand("/quest"); + etc.getInstance().removeCommand("/qadmin"); + ticker.stop(); ticker = null; @@ -114,8 +122,13 @@ public void disable() { } public static boolean loadiConomy() { - if (etc.getLoader().getPlugin("iConomy") != null) { + if (etc.getLoader().getPlugin("iConomy") != null && etc.getLoader().getPlugin("iConomy").isEnabled()) { PropertiesFile iConomySettings = new PropertiesFile(iData.mainDir + "settings.properties"); + if (!iConomySettings.containsKey("use-mysql")) { + log.warning("[" + NAME + "] iConomy settings failed to be read."); + ICONOMY_DETECTED = false; + return false; + } boolean mysql = iConomySettings.getBoolean("use-mysql", false); // MySQL From d3de1799a929742fb20ea78787b1b67005508e0d Mon Sep 17 00:00:00 2001 From: jmartin Date: Wed, 29 Dec 2010 13:18:47 -0500 Subject: [PATCH 29/39] Added option where you could choose to have rankreward ADD a group (and make it the first listed group) instead of OVERWRITE it. --- src/Craftizens.java | 2 ++ src/Quest.java | 22 ++++++++++++++++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/Craftizens.java b/src/Craftizens.java index cee7c7e..6cd1b54 100644 --- a/src/Craftizens.java +++ b/src/Craftizens.java @@ -21,6 +21,7 @@ public class Craftizens extends Plugin { public static String DATA_SOURCE_CONNECTION_URL = ""; public static String DATA_SOURCE_USERNAME = ""; public static String DATA_SOURCE_PASSWORD = ""; + public static boolean REPLACE_GROUP = true; public static boolean ICONOMY_DETECTED = false; public static String NAME = "Craftizens"; public static String VERSION = "v0.8.1"; @@ -67,6 +68,7 @@ public void enable() { DATA_SOURCE_CONNECTION_URL = props.getString("data-source-connection-url", DATA_SOURCE_CONNECTION_URL); DATA_SOURCE_USERNAME = props.getString("data-source-username", DATA_SOURCE_USERNAME); DATA_SOURCE_PASSWORD = props.getString("data-source-password", DATA_SOURCE_PASSWORD); + REPLACE_GROUP = props.getBoolean("replace-group", REPLACE_GROUP); if (FLATFILE_DATA) data = new CraftizenFlatfileDataSource(); diff --git a/src/Quest.java b/src/Quest.java index 511f7cf..dbf189a 100644 --- a/src/Quest.java +++ b/src/Quest.java @@ -1,4 +1,5 @@ import java.awt.Polygon; +import java.util.ArrayList; public abstract class Quest { @@ -63,8 +64,25 @@ private void setRank(Player p, String rank) { Group g = etc.getDataSource().getGroup(rank); if (g != null) { - String[] arrayOfString = { g.Name }; - p.setGroups(arrayOfString); + String[] groupList = null; + if (Craftizens.REPLACE_GROUP) { + groupList = new String[]{ g.Name }; + } else { + groupList = p.getGroups(); + if (groupList != null && groupList.length > 0) { + String[] fullList = new String[groupList.length + 1]; + fullList[0] = g.Name; + int i = 0; + for (String group : groupList) { + i++; + fullList[i] = group; + } + groupList = fullList; + } else { + groupList = new String[]{ g.Name }; + } + } + p.setGroups(groupList); p.setIgnoreRestrictions(g.IgnoreRestrictions); p.setAdmin(g.Administrator); From 635c065d635db599e4639451fa03cc58f85d9a40 Mon Sep 17 00:00:00 2001 From: jmartin Date: Wed, 29 Dec 2010 13:52:32 -0500 Subject: [PATCH 30/39] Added config option for interacting with anything --- src/Craftizens.java | 2 ++ src/CraftizensListener.java | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Craftizens.java b/src/Craftizens.java index 6cd1b54..2e76846 100644 --- a/src/Craftizens.java +++ b/src/Craftizens.java @@ -14,6 +14,7 @@ public class Craftizens extends Plugin { public static int INTERACT_ITEM_2 = 340; public static int INTERACT_RANGE = 2; public static int INTERACT_ANGLE_VARIATION = 25; + public static boolean INTERACT_ANYTHING = false; public static int QADMIN_BOUNDARY_MARKER = 340; public static boolean QUESTS_ENABLED = true; public static boolean FLATFILE_DATA = false; @@ -61,6 +62,7 @@ public void enable() { INTERACT_ITEM_2 = props.getInt("npc-interact-item-2", INTERACT_ITEM_2); INTERACT_RANGE = props.getInt("npc-interact-range", INTERACT_RANGE); INTERACT_ANGLE_VARIATION = props.getInt("npc-interact-angle-variation", INTERACT_ANGLE_VARIATION); + INTERACT_ANYTHING = props.getBoolean("interact-anything", INTERACT_ANYTHING); QADMIN_BOUNDARY_MARKER = props.getInt("qadmin-boundary-marker", QADMIN_BOUNDARY_MARKER); QUESTS_ENABLED = props.getBoolean("quests-enabled", QUESTS_ENABLED); FLATFILE_DATA = props.getBoolean("flatfile-data-enabled", FLATFILE_DATA); diff --git a/src/CraftizensListener.java b/src/CraftizensListener.java index 3026637..802f35e 100644 --- a/src/CraftizensListener.java +++ b/src/CraftizensListener.java @@ -670,7 +670,7 @@ public void qadminCommand(Player player, String [] command) { } public void onArmSwing(Player player) { - if (player.getItemInHand() == Craftizens.INTERACT_ITEM || player.getItemInHand() == Craftizens.INTERACT_ITEM_2) { + if (Craftizens.INTERACT_ANYTHING || player.getItemInHand() == Craftizens.INTERACT_ITEM || player.getItemInHand() == Craftizens.INTERACT_ITEM_2) { if (Craftizens.DEBUG) log.info("Player " + player.getName() + " trying to check quest"); for (Craftizen npc : Craftizens.npcs) { if (Craftizens.DEBUG) log.info("--checking npc " + npc.getName()); From 1c85672b7244f54b66b1c9412afd9c76d00a8e0c Mon Sep 17 00:00:00 2001 From: jmartin Date: Wed, 29 Dec 2010 13:52:50 -0500 Subject: [PATCH 31/39] Flatfile now checks quest prereqs --- src/CraftizenFlatfileDataSource.java | 11 +++++++++-- src/QuestInfo.java | 4 ++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/CraftizenFlatfileDataSource.java b/src/CraftizenFlatfileDataSource.java index 9e4c8be..81c4de0 100644 --- a/src/CraftizenFlatfileDataSource.java +++ b/src/CraftizenFlatfileDataSource.java @@ -229,15 +229,22 @@ public ArrayList getAvailableQuests(Craftizen c, Player p) String[] completedQuests = player.getString("completed-quests") .split(","); boolean completedFound = false; + boolean prereqFound = false; + boolean hasPrereq = (quest.getPrereq() != null && !quest.getPrereq().isEmpty()); for (String cq : completedQuests) { if (cq.equalsIgnoreCase(q)) { completedFound = true; - break; + if (!hasPrereq || prereqFound) break; + } + if (hasPrereq && cq.equalsIgnoreCase(quest.getPrereq())) + { + prereqFound = true; + if (completedFound) break; } } - if (completedFound) + if (completedFound || (hasPrereq && !prereqFound)) { continue; } diff --git a/src/QuestInfo.java b/src/QuestInfo.java index f4cc33f..a62863a 100644 --- a/src/QuestInfo.java +++ b/src/QuestInfo.java @@ -376,4 +376,8 @@ public String getLocation() { public String getData() { return data; } + + public String getPrereq() { + return prereq; + } } From 1fef15a762cff4b9f037a34a8692bb5de40cd870 Mon Sep 17 00:00:00 2001 From: durron597 Date: Wed, 29 Dec 2010 14:03:31 -0500 Subject: [PATCH 32/39] Changed version number --- build.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.xml b/build.xml index 41c8a18..cd45925 100644 --- a/build.xml +++ b/build.xml @@ -2,7 +2,7 @@ - + From 5fc7ed8a2b84713151b6fb9de74c680599244c5f Mon Sep 17 00:00:00 2001 From: durron597 Date: Wed, 29 Dec 2010 14:11:45 -0500 Subject: [PATCH 33/39] removed unnecessary import --- src/Quest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Quest.java b/src/Quest.java index dbf189a..5b9eb36 100644 --- a/src/Quest.java +++ b/src/Quest.java @@ -1,5 +1,4 @@ import java.awt.Polygon; -import java.util.ArrayList; public abstract class Quest { From 6d2ae1957137058249219d9d14f972fbb598abf5 Mon Sep 17 00:00:00 2001 From: durron597 Date: Thu, 30 Dec 2010 11:52:38 -0500 Subject: [PATCH 34/39] Added updatr support --- Craftizens.updatr | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 Craftizens.updatr diff --git a/Craftizens.updatr b/Craftizens.updatr new file mode 100644 index 0000000..d98913f --- /dev/null +++ b/Craftizens.updatr @@ -0,0 +1,5 @@ +name = Craftizens +version = 0.8.1 +url = www.stwing.upenn.edu/~martinja/minecraft/Craftizens.updatr +file = www.stwing.upenn.edu/~martinja/minecraft/Craftizens.jar +notes = Now with Updatr support! \ No newline at end of file From dcb298e422a774b966922aeb6664910bf5dcfc5d Mon Sep 17 00:00:00 2001 From: durron597 Date: Thu, 30 Dec 2010 11:53:09 -0500 Subject: [PATCH 35/39] Changed default colors so they work better. --- src/Craftizens.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Craftizens.java b/src/Craftizens.java index 2e76846..1609eb5 100644 --- a/src/Craftizens.java +++ b/src/Craftizens.java @@ -7,9 +7,9 @@ public class Craftizens extends Plugin { static final Logger log = Logger.getLogger("Minecraft"); public static boolean DEBUG = false; - public static String NPC_PREFIX = Colors.Yellow; + public static String NPC_PREFIX = "\u00C2\u00A7e"; public static String NPC_SUFFIX = " (NPC)"; - public static String TEXT_COLOR = Colors.Yellow; + public static String TEXT_COLOR = "\u00C2\u00A7e"; public static int INTERACT_ITEM = 340; public static int INTERACT_ITEM_2 = 340; public static int INTERACT_RANGE = 2; From f57d22f38ee95b16d4ff015016d86e42720846d0 Mon Sep 17 00:00:00 2001 From: durron597 Date: Thu, 30 Dec 2010 12:14:14 -0500 Subject: [PATCH 36/39] Prevent NPE when quests don't have a valid startnpc --- src/CraftizenFlatfileDataSource.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/CraftizenFlatfileDataSource.java b/src/CraftizenFlatfileDataSource.java index 81c4de0..ae12242 100644 --- a/src/CraftizenFlatfileDataSource.java +++ b/src/CraftizenFlatfileDataSource.java @@ -64,8 +64,14 @@ public HashSet loadCraftizens() + file.getName()); ArrayList quests = npcQuests.get(data .getString("start_npc")); - quests.add(data.getString("id")); - npcQuests.put(data.getString("start_npc"), quests); + if (quests != null) { + quests.add(data.getString("id")); + npcQuests.put(data.getString("start_npc"), quests); + } else { + Craftizens.log.warning("[Craftizens] " + file.getName() + + " has a non-existant start_npc: " + + data.getString("start_npc") + ". Quest not loaded."); + } } } return npcs; From 85ee890040423723a571581b937cb9878e493e9b Mon Sep 17 00:00:00 2001 From: durron597 Date: Thu, 30 Dec 2010 12:26:31 -0500 Subject: [PATCH 37/39] Fixed a few user messages (including /npc usage instructions) Added the ability to globally turn /quest compass off --- src/Craftizens.java | 2 ++ src/CraftizensListener.java | 14 +++++++------- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/Craftizens.java b/src/Craftizens.java index 1609eb5..e1256b9 100644 --- a/src/Craftizens.java +++ b/src/Craftizens.java @@ -24,6 +24,7 @@ public class Craftizens extends Plugin { public static String DATA_SOURCE_PASSWORD = ""; public static boolean REPLACE_GROUP = true; public static boolean ICONOMY_DETECTED = false; + public static boolean ALLOW_COMPASS = true; public static String NAME = "Craftizens"; public static String VERSION = "v0.8.1"; @@ -71,6 +72,7 @@ public void enable() { DATA_SOURCE_USERNAME = props.getString("data-source-username", DATA_SOURCE_USERNAME); DATA_SOURCE_PASSWORD = props.getString("data-source-password", DATA_SOURCE_PASSWORD); REPLACE_GROUP = props.getBoolean("replace-group", REPLACE_GROUP); + ALLOW_COMPASS = props.getBoolean("allow-compass", ALLOW_COMPASS); if (FLATFILE_DATA) data = new CraftizenFlatfileDataSource(); diff --git a/src/CraftizensListener.java b/src/CraftizensListener.java index 802f35e..b811121 100644 --- a/src/CraftizensListener.java +++ b/src/CraftizensListener.java @@ -71,10 +71,10 @@ public void npcCommand(Player player, String [] command) { } else { player.sendMessage(Craftizens.TEXT_COLOR + "Usage instructions:"); - player.sendMessage(Craftizens.TEXT_COLOR + " /npc list - list your npcs"); - player.sendMessage(Craftizens.TEXT_COLOR + " /npc create - create a new npc"); - player.sendMessage(Craftizens.TEXT_COLOR + " /npc adddialog - add some dialog for your npc"); - player.sendMessage(Craftizens.TEXT_COLOR + " /npc delete - delete your npc"); + player.sendMessage(Craftizens.TEXT_COLOR + " /craftnpc list - list your npcs"); + player.sendMessage(Craftizens.TEXT_COLOR + " /craftnpc create - create a new npc"); + player.sendMessage(Craftizens.TEXT_COLOR + " /craftnpc adddialog - add some dialog for your npc"); + player.sendMessage(Craftizens.TEXT_COLOR + " /craftnpc delete - delete your npc"); } } @@ -92,7 +92,7 @@ public void questCommand(Player player, String [] command) { // TODO: this is extremely poor programming practice. Need to refactor/fix later. quests = (ArrayList) o; } catch (ClassCastException e) { - Craftizen.log.warning("[Craftizen] Pending quests was not an ArrayList!"); + Craftizen.log.warning("[Craftizens] Pending quests was not an ArrayList!"); } int i = -1; try { @@ -189,7 +189,7 @@ public void questCommand(Player player, String [] command) { player.sendMessage(Craftizens.TEXT_COLOR + "No active quests."); } - } else if ("compass".startsWith(command[1].toLowerCase()) && command.length == 3) { + } else if (Craftizens.ALLOW_COMPASS && "compass".startsWith(command[1].toLowerCase()) && command.length == 3) { if (Craftizens.activeQuests.containsKey(player.getName())) { ArrayList quests = Craftizens.activeQuests.get(player.getName()); int i = -1; @@ -235,7 +235,7 @@ public void questCommandUsage(Player player) { player.sendMessage(Craftizens.TEXT_COLOR + " /quest list - list your active quests"); player.sendMessage(Craftizens.TEXT_COLOR + " /quest progress # - shows progress on the quest"); player.sendMessage(Craftizens.TEXT_COLOR + " /quest desc # - shows the quest description"); - player.sendMessage(Craftizens.TEXT_COLOR + " /quest compass # - makes your compass point to the quest"); + if (Craftizens.ALLOW_COMPASS) player.sendMessage(Craftizens.TEXT_COLOR + " /quest compass # - makes your compass point to the quest"); } public void qadminCommand(Player player, String [] command) { From 7b317ba3106727c504bd2889ac0726fd79f931f5 Mon Sep 17 00:00:00 2001 From: durron597 Date: Thu, 30 Dec 2010 12:27:04 -0500 Subject: [PATCH 38/39] Changed version to 0.8.2 --- Craftizens.updatr | 2 +- src/Craftizens.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Craftizens.updatr b/Craftizens.updatr index d98913f..3ac8454 100644 --- a/Craftizens.updatr +++ b/Craftizens.updatr @@ -1,5 +1,5 @@ name = Craftizens -version = 0.8.1 +version = 0.8.2 url = www.stwing.upenn.edu/~martinja/minecraft/Craftizens.updatr file = www.stwing.upenn.edu/~martinja/minecraft/Craftizens.jar notes = Now with Updatr support! \ No newline at end of file diff --git a/src/Craftizens.java b/src/Craftizens.java index e1256b9..1a8e110 100644 --- a/src/Craftizens.java +++ b/src/Craftizens.java @@ -26,7 +26,7 @@ public class Craftizens extends Plugin { public static boolean ICONOMY_DETECTED = false; public static boolean ALLOW_COMPASS = true; public static String NAME = "Craftizens"; - public static String VERSION = "v0.8.1"; + public static String VERSION = "v0.8.2"; public static CraftizenDataSource data; public static HashSet npcs; From 94e1296d7a9b5653bd9e9662eb8cbd13da7f2919 Mon Sep 17 00:00:00 2001 From: durron597 Date: Thu, 30 Dec 2010 12:28:52 -0500 Subject: [PATCH 39/39] Added Craftizens.jar to git so people can download from there if they want to --- .gitignore | 1 - dist/Craftizens.jar | Bin 0 -> 60233 bytes 2 files changed, 1 deletion(-) create mode 100644 dist/Craftizens.jar diff --git a/.gitignore b/.gitignore index 83c8a34..6e2c058 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ .classpath .project bin -dist lib diff --git a/dist/Craftizens.jar b/dist/Craftizens.jar new file mode 100644 index 0000000000000000000000000000000000000000..a7f2ad6d829073b468e2f477b08f8d2b03464f03 GIT binary patch literal 60233 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 T+ldT^^(AX7Gf&{m2jq>}IwH8(on9)SfU*=^N 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 literal 0 HcmV?d00001