Forráskód Böngészése

Initial release of the Achievement System (#2044)

* Information based on kRO patch notes.
- http://ro.gnjoy.com/news/update/View.asp?seq=163&curpage=1
* Includes Title System which is integrated into the Achievement System.
* Includes RODEX integration for rewards.
* Added new atcommand reloadachievementdb.
* Added new script commands achievementinfo, achievementadd, achievementremove, achievementcomplete, and achievementexists.
Thanks to @Lux-uri, @RagnarokNova, @Lemongrass3110, and @Tokeiburu for their help!
Aleos 7 éve
szülő
commit
7f5411da07
62 módosított fájl, 8032 hozzáadás és 55 törlés
  1. 4 2
      .travis.yml
  2. 2 2
      3rdparty/yaml-cpp/src/contrib/graphbuilderadapter.h
  3. 4 4
      3rdparty/yaml-cpp/src/singledocparser.cpp
  4. 3 3
      Makefile.in
  5. 4 0
      conf/battle/feature.conf
  6. 5 0
      conf/msg_conf/char_msg.conf
  7. 7 1
      conf/msg_conf/map_msg.conf
  8. 78 0
      db/import-tmpl/achievement_db.yml
  9. 2407 0
      db/pre-re/achievement_db.yml
  10. 2407 0
      db/re/achievement_db.yml
  11. 1 1
      db/re/item_db.txt
  12. 102 0
      doc/achievements.txt
  13. 4 1
      doc/atcommands.txt
  14. 21 0
      doc/packet_interserv.txt
  15. 66 0
      doc/script_commands.txt
  16. 278 0
      npc/re/other/achievements.txt
  17. 1 0
      npc/re/scripts_athena.conf
  18. 24 0
      sql-files/main.sql
  19. 25 0
      sql-files/upgrades/upgrade_20170407.sql
  20. 2 0
      src/char/char-server.vcxproj
  21. 6 0
      src/char/char-server.vcxproj.filters
  22. 22 7
      src/char/char.cpp
  23. 1 0
      src/char/char.h
  24. 344 0
      src/char/int_achievement.c
  25. 9 0
      src/char/int_achievement.h
  26. 6 2
      src/char/int_mail.c
  27. 1 1
      src/char/int_mail.h
  28. 3 1
      src/char/inter.c
  29. 20 10
      src/common/Makefile.in
  30. 6 0
      src/common/core.cpp
  31. 16 0
      src/common/mmo.h
  32. 10 0
      src/common/sql.c
  33. 7 0
      src/common/sql.h
  34. 57 5
      src/common/yamlwrapper.cpp
  35. 7 1
      src/common/yamlwrapper.h
  36. 15 6
      src/map/Makefile.in
  37. 1268 0
      src/map/achievement.c
  38. 135 0
      src/map/achievement.h
  39. 9 0
      src/map/atcommand.c
  40. 8 0
      src/map/battle.c
  41. 1 0
      src/map/battle.h
  42. 9 0
      src/map/chat.c
  43. 2 0
      src/map/chrif.c
  44. 183 1
      src/map/clif.c
  45. 7 0
      src/map/clif.h
  46. 2 2
      src/map/clif_packetdb.h
  47. 161 1
      src/map/intif.c
  48. 6 0
      src/map/intif.h
  49. 3 0
      src/map/map-server.vcxproj
  50. 6 0
      src/map/map-server.vcxproj.filters
  51. 3 4
      src/map/map.cpp
  52. 4 0
      src/map/mob.c
  53. 3 0
      src/map/party.c
  54. 30 0
      src/map/pc.c
  55. 15 0
      src/map/pc.h
  56. 2 0
      src/map/pet.c
  57. 146 0
      src/map/script.c
  58. 2 0
      src/map/script.h
  59. 43 0
      src/map/script_constants.h
  60. 3 0
      src/map/skill.c
  61. 4 0
      src/map/unit.c
  62. 2 0
      src/map/vending.c

+ 4 - 2
.travis.yml

@@ -27,8 +27,10 @@ before_script:
   - mysql -u $DB_ROOT -e "GRANT ALL ON *.* TO '$DB_USER'@'$DB_HOST' IDENTIFIED BY '$DB_PASS';"
   - sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y
   - sudo apt-get update -q
-  - sudo apt-get install gcc-4.8 -y
-  - sudo apt-get install g++-4.8 -y
+  - sudo apt-get install gcc-5 -y
+  - sudo apt-get install g++-5 -y
+  - sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-5 1
+  - sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-5 1
 
 script:
   - ./configure $CONFIGURE_FLAGS

+ 2 - 2
3rdparty/yaml-cpp/src/contrib/graphbuilderadapter.h

@@ -50,8 +50,8 @@ class GraphBuilderAdapter : public EventHandler {
   struct ContainerFrame {
     ContainerFrame(void* pSequence)
         : pContainer(pSequence), pPrevKeyNode(&sequenceMarker) {}
-    ContainerFrame(void* pMap, void* pPrevKeyNode)
-        : pContainer(pMap), pPrevKeyNode(pPrevKeyNode) {}
+    ContainerFrame(void* pMap, void* pPrevKeyNode_)
+        : pContainer(pMap), pPrevKeyNode(pPrevKeyNode_) {}
 
     void* pContainer;
     void* pPrevKeyNode;

+ 4 - 4
3rdparty/yaml-cpp/src/singledocparser.cpp

@@ -166,10 +166,10 @@ void SingleDocParser::HandleBlockSequence(EventHandler& eventHandler) {
 
     // check for null
     if (!m_scanner.empty()) {
-      const Token& token = m_scanner.peek();
-      if (token.type == Token::BLOCK_ENTRY ||
-          token.type == Token::BLOCK_SEQ_END) {
-        eventHandler.OnNull(token.mark, NullAnchor);
+      const Token& token_ = m_scanner.peek();
+      if (token_.type == Token::BLOCK_ENTRY ||
+          token_.type == Token::BLOCK_SEQ_END) {
+        eventHandler.OnNull(token_.mark, NullAnchor);
         continue;
       }
     }

+ 3 - 3
Makefile.in

@@ -5,10 +5,10 @@ OMAP=@OMAP@
 ifeq ($(HAVE_MYSQL),yes)
 	ALL_DEPENDS=server tools
 	SERVER_DEPENDS=common login char map import
-	COMMON_DEPENDS=mt19937ar libconfig
+	COMMON_DEPENDS=mt19937ar libconfig yaml-cpp
 	LOGIN_DEPENDS=mt19937ar libconfig common
 	CHAR_DEPENDS=mt19937ar libconfig common
-	MAP_DEPENDS=mt19937ar libconfig common
+	MAP_DEPENDS=mt19937ar libconfig common yaml-cpp
 else
 	ALL_DEPENDS=needs_mysql
 	SERVER_DEPENDS=needs_mysql
@@ -93,7 +93,7 @@ help:
 	@echo "'common'      - builds object files used for the three servers"
 	@echo "'mt19937ar'   - builds object file of Mersenne Twister MT19937"
 	@echo "'libconfig'   - builds object files of libconfig"
-	@echo "'libconfig'   - builds object files of yaml-cpp"
+	@echo "'yaml-cpp'    - builds object files of yaml-cpp"
 	@echo "'login'       - builds login server"
 	@echo "'char'        - builds char server"
 	@echo "'map'         - builds map server"

+ 4 - 0
conf/battle/feature.conf

@@ -63,3 +63,7 @@ feature.autotrade_open_delay: 5000
 // Requires: 2014-10-22bRagexe or later
 // Off by default while test version is out; enable at your own risk.
 feature.roulette: off
+
+// Achievement (Note 1)
+// Requires: 2015-05-13aRagexe or later
+feature.achievement: on

+ 5 - 0
conf/msg_conf/char_msg.conf

@@ -157,3 +157,8 @@
 224: -- Character Details --
 225: [Slot/CID: %d/%d] %s | %s | Level: %d/%d | %s
 226: This account doesn't have characters.
+
+// Achievements
+227: GM
+228: Achievement Reward Mail
+229: [%s] Achievement Reward.

+ 7 - 1
conf/msg_conf/map_msg.conf

@@ -825,7 +825,13 @@
 769: %s %s has been banned.
 770: %s %s has been unbanned.
 
-//771-899 free
+//@reloadachievementdb
+771: Achievement database has been reloaded.
+
+// Achievements
+772: Achievements are disabled.
+
+//773-899 free
 
 //------------------------------------
 // More atcommands message

+ 78 - 0
db/import-tmpl/achievement_db.yml

@@ -0,0 +1,78 @@
+# This file is a part of rAthena.
+#   Copyright(C) 2017 rAthena Development Team
+#   https://rathena.org - https://github.com/rathena
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+###########################################################################
+# Custom Achievement Database
+###########################################################################
+#
+# Achievement Settings
+#
+###########################################################################
+# Id - Unique achievement ID.
+###########################################################################
+# Group - Achievement group type. Each achievement type calls a specific
+# objective check.
+# Valid groups:
+#  AG_ADD_FRIEND
+#  AG_ADVENTURE
+#  AG_BABY
+#  AG_BATTLE
+#  AG_CHATTING
+#  AG_CHATTING_COUNT
+#  AG_CHATTING_CREATE
+#  AG_CHATTING_DYING
+#  AG_EAT
+#  AG_GET_ITEM
+#  AG_GET_ZENY
+#  AG_GOAL_ACHIEVE
+#  AG_GOAL_LEVEL
+#  AG_GOAL_STATUS
+#  AG_HEAR
+#  AG_JOB_CHANGE
+#  AG_MARRY
+#  AG_PARTY
+#  AG_ENCHANT_FAIL
+#  AG_ENCHANT_SUCCESS
+#  AG_SEE
+#  AG_SPEND_ZENY
+#  AG_TAMING
+###########################################################################
+# Name - Achievement name. Used when sending rewards through RODEX.
+###########################################################################
+# Target - A list of monster ID and count values that the achievement
+# requires. The target count can also be used for achievements that keep
+# a counter while not being related to monster kills.
+# Capped at MAX_ACHIEVEMENT_OBJECTIVES.
+###########################################################################
+# Condition - A conditional statement that must be met for the achievement
+# to be considered complete.
+###########################################################################
+# Map - A map name that is used for the AG_CHATTING type which increments
+# the counter based on the player's map.
+###########################################################################
+# Dependent: - A list of achievement IDs that need to be completed before
+# this achievement is considered complete.
+###########################################################################
+# Reward - A list of rewards that are given on completion. All fields are
+# optional.
+#   ItemId: Item ID
+#   Amount:  Amount of Item ID (default 1)
+#   Script: Bonus Script
+#   TitleId: Title ID
+###########################################################################
+# Score - Achievement points that are given on completion.
+###########################################################################

+ 2407 - 0
db/pre-re/achievement_db.yml

@@ -0,0 +1,2407 @@
+# This file is a part of rAthena.
+#   Copyright(C) 2017 rAthena Development Team
+#   https://rathena.org - https://github.com/rathena
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+###########################################################################
+# Custom Achievement Database
+###########################################################################
+#
+# Achievement Settings
+#
+###########################################################################
+# ID - Unique achievement ID.
+###########################################################################
+# Group - Achievement group type. Each achievement type calls a specific
+# objective check.
+# Valid groups:
+#  AG_ADD_FRIEND
+#  AG_ADVENTURE
+#  AG_BABY
+#  AG_BATTLE
+#  AG_CHATTING
+#  AG_CHATTING_COUNT
+#  AG_CHATTING_CREATE
+#  AG_CHATTING_DYING
+#  AG_EAT
+#  AG_GET_ITEM
+#  AG_GET_ZENY
+#  AG_GOAL_ACHIEVE
+#  AG_GOAL_LEVEL
+#  AG_GOAL_STATUS
+#  AG_HEAR
+#  AG_JOB_CHANGE
+#  AG_MARRY
+#  AG_PARTY
+#  AG_ENCHANT_FAIL
+#  AG_ENCHANT_SUCCESS
+#  AG_SEE
+#  AG_SPEND_ZENY
+#  AG_TAMING
+###########################################################################
+# Name - Achievement name. Used when sending rewards through RODEX.
+###########################################################################
+# Target - A list of monster ID and count values that the achievement
+# requires. The target count can also be used for achievements that keep
+# a counter while not being related to monster kills.
+# Capped at MAX_ACHIEVEMENT_OBJECTIVES.
+###########################################################################
+# Condition - A conditional statement that must be met for the achievement
+# to be considered complete.
+###########################################################################
+# Map - A map name that is used for the AG_CHATTING type which increments
+# the counter based on the player's map.
+###########################################################################
+# Dependent: - A list of achievement IDs that need to be completed before
+# this achievement is considered complete.
+###########################################################################
+# Reward - A list of rewards that are given on completion. All fields are
+# optional.
+#   ItemID: Item ID
+#   Amount:  Amount of Item ID (default 1)
+#   Script: Bonus Script
+#   TitleID: Title ID
+###########################################################################
+# Score - Achievement points that are given on completion.
+###########################################################################
+
+Achievements:
+  - ID: 110000
+    Group: "AG_EAT"
+    Name: "At this time I live to eat"
+    Score: 10
+  - ID: 110001
+    Group: "AG_SEE"
+    Name: "A fan of this polarity"
+    Score: 10
+  - ID: 120001
+    Group: "AG_ADVENTURE"
+    Name: "North Prontera Field Exploration(1)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120002
+    Group: "AG_ADVENTURE"
+    Name: "North Prontera Field Exploration(2)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120003
+    Group: "AG_ADVENTURE"
+    Name: "North Prontera Field Exploration(3)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120004
+    Group: "AG_ADVENTURE"
+    Name: "West Prontera Field Exploration(1)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120005
+    Group: "AG_ADVENTURE"
+    Name: "West Prontera Field Exploration(2)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120006
+    Group: "AG_ADVENTURE"
+    Name: "East Prontera Field Exploration"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120007
+    Group: "AG_ADVENTURE"
+    Name: "South Prontera Field Exploration(1)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120008
+    Group: "AG_ADVENTURE"
+    Name: "South Prontera Field Exploration(2)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120009
+    Group: "AG_ADVENTURE"
+    Name: "South Prontera Field Exploration(3)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120010
+    Group: "AG_ADVENTURE"
+    Name: "South Prontera Field Exploration(4)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120011
+    Group: "AG_ADVENTURE"
+    Name: "East Geffen Field Exploration"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120012
+    Group: "AG_ADVENTURE"
+    Name: "Southeast Geffen Field Exploration"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120013
+    Group: "AG_ADVENTURE"
+    Name: "Northwest Geffen Field Exploration(1)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120014
+    Group: "AG_ADVENTURE"
+    Name: "Northwest Geffen Field Exploration(2)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120015
+    Group: "AG_ADVENTURE"
+    Name: "Northwest Geffen Field Exploration(3)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120016
+    Group: "AG_ADVENTURE"
+    Name: "South Geffen Field Exploration(1)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120017
+    Group: "AG_ADVENTURE"
+    Name: "South Geffen Field Exploration(2)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120018
+    Group: "AG_ADVENTURE"
+    Name: "Sograt Desert Field Exploration(1)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120019
+    Group: "AG_ADVENTURE"
+    Name: "Sograt Desert Field Exploration(2)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120020
+    Group: "AG_ADVENTURE"
+    Name: "Sograt Desert Field Exploration(3)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120021
+    Group: "AG_ADVENTURE"
+    Name: "Sograt Desert Field Exploration(4)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120022
+    Group: "AG_ADVENTURE"
+    Name: "Sograt Desert Field Exploration(5)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120023
+    Group: "AG_ADVENTURE"
+    Name: "Sograt Desert Field Exploration(6)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120024
+    Group: "AG_ADVENTURE"
+    Name: "Southwest Payon Field Exploration(1)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120025
+    Group: "AG_ADVENTURE"
+    Name: "Southwest Payon Field Exploration(2)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120026
+    Group: "AG_ADVENTURE"
+    Name: "Southwest Payon Field Exploration(3)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120027
+    Group: "AG_ADVENTURE"
+    Name: "Southwest Payon Field Exploration(4)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120028
+    Group: "AG_ADVENTURE"
+    Name: "East Payon Field Exploration(1)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120029
+    Group: "AG_ADVENTURE"
+    Name: "East Payon Field Exploration(2)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120030
+    Group: "AG_ADVENTURE"
+    Name: "East Payon Field Exploration(3)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120031
+    Group: "AG_ADVENTURE"
+    Name: "East Payon Field Exploration(4)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120032
+    Group: "AG_ADVENTURE"
+    Name: "North Mjolnir Field Exploration(1)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120033
+    Group: "AG_ADVENTURE"
+    Name: "North Mjolnir Field Exploration(2)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120034
+    Group: "AG_ADVENTURE"
+    Name: "North Mjolnir Field Exploration(3)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120035
+    Group: "AG_ADVENTURE"
+    Name: "North Mjolnir Field Exploration(4)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120036
+    Group: "AG_ADVENTURE"
+    Name: "North Mjolnir Field Exploration(5)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120037
+    Group: "AG_ADVENTURE"
+    Name: "South Mjolnir Field Exploration(1)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120038
+    Group: "AG_ADVENTURE"
+    Name: "South Mjolnir Field Exploration(2)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120039
+    Group: "AG_ADVENTURE"
+    Name: "South Mjolnir Field Exploration(3)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120040
+    Group: "AG_ADVENTURE"
+    Name: "South Mjolnir Field Exploration(4)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120041
+    Group: "AG_ADVENTURE"
+    Name: "South Mjolnir Field Exploration(5)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120042
+    Group: "AG_ADVENTURE"
+    Name: "South Mjolnir Field Exploration(6)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120043
+    Group: "AG_ADVENTURE"
+    Name: "South Aldebaran Field Exploration"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120044
+    Group: "AG_ADVENTURE"
+    Name: "Comodo Field Exploration(1)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120045
+    Group: "AG_ADVENTURE"
+    Name: "Comodo Field Exploration(2)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120046
+    Group: "AG_ADVENTURE"
+    Name: "Comodo Field Exploration(3)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120047
+    Group: "AG_ADVENTURE"
+    Name: "Comodo Field Exploration(4)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120048
+    Group: "AG_ADVENTURE"
+    Name: "Comodo Field Exploration(5)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120049
+    Group: "AG_ADVENTURE"
+    Name: "Comodo Field Exploration(6)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120050
+    Group: "AG_ADVENTURE"
+    Name: "Comodo Field Exploration(7)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120051
+    Group: "AG_ADVENTURE"
+    Name: "Comodo Field Exploration(8)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120052
+    Group: "AG_ADVENTURE"
+    Name: "Border Checkpoint Field Exploration(1)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120053
+    Group: "AG_ADVENTURE"
+    Name: "Border Checkpoint Field Exploration(2)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120054
+    Group: "AG_ADVENTURE"
+    Name: "Kiel Hyre Mansion Field Exploration"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120055
+    Group: "AG_ADVENTURE"
+    Name: "El Mes Plateau Field Exploration(1)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120056
+    Group: "AG_ADVENTURE"
+    Name: "El Mes Plateau Field Exploration(2)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120057
+    Group: "AG_ADVENTURE"
+    Name: "El Mes Plateau Field Exploration(3)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120058
+    Group: "AG_ADVENTURE"
+    Name: "El Mes Gorge Field Exploration"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120059
+    Group: "AG_ADVENTURE"
+    Name: "Kiel Hyre Academy Field Exploration"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120060
+    Group: "AG_ADVENTURE"
+    Name: "Guard Camp Field Exploration"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120061
+    Group: "AG_ADVENTURE"
+    Name: "Yuno Field Exploration"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120062
+    Group: "AG_ADVENTURE"
+    Name: "Front of Thanatos Tower Field Exploration"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120063
+    Group: "AG_ADVENTURE"
+    Name: "Hugel Field Exploration(1)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120064
+    Group: "AG_ADVENTURE"
+    Name: "Hugel Field Exploration(2)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120065
+    Group: "AG_ADVENTURE"
+    Name: "Hugel Field Exploration(3)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120066
+    Group: "AG_ADVENTURE"
+    Name: "Abyss Lake Field Exploration(1)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120067
+    Group: "AG_ADVENTURE"
+    Name: "Einbroch Field Exploration(1)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120068
+    Group: "AG_ADVENTURE"
+    Name: "Einbroch Field Exploration(2)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120069
+    Group: "AG_ADVENTURE"
+    Name: "Einbroch Field Exploration(3)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120070
+    Group: "AG_ADVENTURE"
+    Name: "Einbroch Field Exploration(4)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120071
+    Group: "AG_ADVENTURE"
+    Name: "Einbroch Field Exploration(5)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120072
+    Group: "AG_ADVENTURE"
+    Name: "Einbroch Field Exploration(6)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120073
+    Group: "AG_ADVENTURE"
+    Name: "Einbroch Field Exploration(7)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120074
+    Group: "AG_ADVENTURE"
+    Name: "Einbroch Field Exploration(8)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120075
+    Group: "AG_ADVENTURE"
+    Name: "Lighthalzen Field Exploration(1)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120076
+    Group: "AG_ADVENTURE"
+    Name: "Lighthalzen Field Exploration(2)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120077
+    Group: "AG_ADVENTURE"
+    Name: "Lighthalzen Field Exploration(3)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120078
+    Group: "AG_ADVENTURE"
+    Name: "Rachel Audhumbla Plains Field Exploration(1)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120079
+    Group: "AG_ADVENTURE"
+    Name: "Rachel Plains Field Exploration(1)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120080
+    Group: "AG_ADVENTURE"
+    Name: "Rachel Plains Field Exploration(2)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120081
+    Group: "AG_ADVENTURE"
+    Name: "Rachel Plains Field Exploration(3)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120082
+    Group: "AG_ADVENTURE"
+    Name: "Rachel Audhumbla Grassland Field Exploration(1)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120083
+    Group: "AG_ADVENTURE"
+    Name: "Rachel Audhumbla Grassland Field Exploration(2)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120084
+    Group: "AG_ADVENTURE"
+    Name: "Portus Luna Field Exploration"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120085
+    Group: "AG_ADVENTURE"
+    Name: "Veins Field Exploration(1)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120086
+    Group: "AG_ADVENTURE"
+    Name: "Veins Field Exploration(2)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120087
+    Group: "AG_ADVENTURE"
+    Name: "Veins Field Exploration(3)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120088
+    Group: "AG_ADVENTURE"
+    Name: "Veins Field Exploration(4)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120089
+    Group: "AG_ADVENTURE"
+    Name: "Veins Field Exploration(5)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120090
+    Group: "AG_ADVENTURE"
+    Name: "Eclage Field Exploration"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120091
+    Group: "AG_ADVENTURE"
+    Name: "North Bitfrost Field Exploration(1)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120092
+    Group: "AG_ADVENTURE"
+    Name: "South Bitfrost Field Exploration(1)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120093
+    Group: "AG_ADVENTURE"
+    Name: "Splendide Field Exploration(1)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120094
+    Group: "AG_ADVENTURE"
+    Name: "Splendide Field Exploration(2)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120095
+    Group: "AG_ADVENTURE"
+    Name: "Splendide Field Exploration(3)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120096
+    Group: "AG_ADVENTURE"
+    Name: "Manuk Field Exploration(1)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120097
+    Group: "AG_ADVENTURE"
+    Name: "Manuk Field Exploration(2)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120098
+    Group: "AG_ADVENTURE"
+    Name: "Manuk Field Exploration(3)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120099
+    Group: "AG_ADVENTURE"
+    Name: "Outskirts of Kamidal Field Exploration(1)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120100
+    Group: "AG_ADVENTURE"
+    Name: "Outskirts of Kamidal Field Exploration(2)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120101
+    Group: "AG_ADVENTURE"
+    Name: "Amatsu Field Exploration"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120102
+    Group: "AG_ADVENTURE"
+    Name: "Kunlun Field Exploration"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120103
+    Group: "AG_ADVENTURE"
+    Name: "Gonryun Field Exploration"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120104
+    Group: "AG_ADVENTURE"
+    Name: "Ayothaya Field Exploration"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120105
+    Group: "AG_ADVENTURE"
+    Name: "Moscovia Field Exploration"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120106
+    Group: "AG_ADVENTURE"
+    Name: "Brasilis Field Exploration"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120107
+    Group: "AG_ADVENTURE"
+    Name: "Dewata Field Exploration"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120108
+    Group: "AG_ADVENTURE"
+    Name: "Malaya Field Exploration(1)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120109
+    Group: "AG_ADVENTURE"
+    Name: "Malaya Field Exploration(2)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 10
+  - ID: 120110
+    Group: "AG_ADVENTURE"
+    Name: "Abbey Underground Dungeon Exploration"
+    #Reward:
+    #  ItemID: 22876
+    Score: 20
+  - ID: 120111
+    Group: "AG_ADVENTURE"
+    Name: "Abyss Lake Dungeon Exploration"
+    #Reward:
+    #  ItemID: 22876
+    Score: 20
+  - ID: 120112
+    Group: "AG_ADVENTURE"
+    Name: "Clock Tower Dungeon Exploration"
+    #Reward:
+    #  ItemID: 22876
+    Score: 20
+  - ID: 120113
+    Group: "AG_ADVENTURE"
+    Name: "Amatsu Dungeon Exploration"
+    #Reward:
+    #  ItemID: 22876
+    Score: 20
+  - ID: 120114
+    Group: "AG_ADVENTURE"
+    Name: "Ant Hell Dungeon Exploration"
+    #Reward:
+    #  ItemID: 22876
+    Score: 20
+  - ID: 120115
+    Group: "AG_ADVENTURE"
+    Name: "Ayothaya Dungeon Exploration"
+    #Reward:
+    #  ItemID: 22876
+    Score: 20
+  - ID: 120116
+    Group: "AG_ADVENTURE"
+    Name: "Comodo Dungeon Exploration"
+    #Reward:
+    #  ItemID: 22876
+    Score: 20
+  - ID: 120117
+    Group: "AG_ADVENTURE"
+    Name: "Brasilis Dungeon Exploration"
+    #Reward:
+    #  ItemID: 22876
+    Score: 20
+  - ID: 120118
+    Group: "AG_ADVENTURE"
+    Name: "Clock Tower Dungeon Exploration"
+    #Reward:
+    #  ItemID: 22876
+    Score: 20
+  - ID: 120119
+    Group: "AG_ADVENTURE"
+    Name: "Istana Dungeon Exploration"
+    #Reward:
+    #  ItemID: 22876
+    Score: 20
+  - ID: 120120
+    Group: "AG_ADVENTURE"
+    Name: "Scaraba Hole Dungeon Exploration"
+    #Reward:
+    #  ItemID: 22876
+    Score: 20
+  - ID: 120121
+    Group: "AG_ADVENTURE"
+    Name: "Bitfrost Dungeon Exploration"
+    #Reward:
+    #  ItemID: 22876
+    Score: 20
+  - ID: 120122
+    Group: "AG_ADVENTURE"
+    Name: "Einbroch Dungeon Exploration"
+    #Reward:
+    #  ItemID: 22876
+    Score: 20
+  - ID: 120123
+    Group: "AG_ADVENTURE"
+    Name: "Geffen Underground Dungeon Exploration"
+    #Reward:
+    #  ItemID: 22876
+    Score: 20
+  - ID: 120124
+    Group: "AG_ADVENTURE"
+    Name: "Glastheim Dungeon Exploration(1)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 20
+  - ID: 120125
+    Group: "AG_ADVENTURE"
+    Name: "Glastheim Dungeon Exploration(2)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 20
+  - ID: 120126
+    Group: "AG_ADVENTURE"
+    Name: "Glastheim Dungeon Exploration(3)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 20
+  - ID: 120127
+    Group: "AG_ADVENTURE"
+    Name: "Glastheim Dungeon Exploration(4)"
+    #Reward:
+    #  ItemID: 22876
+    Score: 20
+  - ID: 120128
+    Group: "AG_ADVENTURE"
+    Name: "Kunlun Dungeon Exploration"
+    #Reward:
+    #  ItemID: 22876
+    Score: 20
+  - ID: 120129
+    Group: "AG_ADVENTURE"
+    Name: "Rachel Dungeon Exploration"
+    #Reward:
+    #  ItemID: 22876
+    Score: 20
+  - ID: 120130
+    Group: "AG_ADVENTURE"
+    Name: "Sphinx Dungeon Exploration"
+    #Reward:
+    #  ItemID: 22876
+    Score: 20
+  - ID: 120131
+    Group: "AG_ADVENTURE"
+    Name: "Izlude Dungeon Exploration"
+    #Reward:
+    #  ItemID: 22876
+    Score: 20
+  - ID: 120132
+    Group: "AG_ADVENTURE"
+    Name: "Robot Factory Dungeon Exploration"
+    #Reward:
+    #  ItemID: 22876
+    Score: 20
+  - ID: 120133
+    Group: "AG_ADVENTURE"
+    Name: "Bio Lab Dungeon Exploration"
+    #Reward:
+    #  ItemID: 22876
+    Score: 20
+  - ID: 120134
+    Group: "AG_ADVENTURE"
+    Name: "Gonryun Dungeon Exploration"
+    #Reward:
+    #  ItemID: 22876
+    Score: 20
+  - ID: 120135
+    Group: "AG_ADVENTURE"
+    Name: "Nogg Road Dungeon Exploration"
+    #Reward:
+    #  ItemID: 22876
+    Score: 20
+  - ID: 120136
+    Group: "AG_ADVENTURE"
+    Name: "Coal Mine Dungeon Exploration"
+    #Reward:
+    #  ItemID: 22876
+    Score: 20
+  - ID: 120137
+    Group: "AG_ADVENTURE"
+    Name: "Pyramid Dungeon Exploration"
+    #Reward:
+    #  ItemID: 22876
+    Score: 20
+  - ID: 120138
+    Group: "AG_ADVENTURE"
+    Name: "Orc Dungeon Exploration"
+    #Reward:
+    #  ItemID: 22876
+    Score: 20
+  - ID: 120139
+    Group: "AG_ADVENTURE"
+    Name: "Payon Dungeon Exploration"
+    #Reward:
+    #  ItemID: 22876
+    Score: 20
+  - ID: 120140
+    Group: "AG_ADVENTURE"
+    Name: "Labyrinth Dungeon Exploration"
+    #Reward:
+    #  ItemID: 22876
+    Score: 20
+  - ID: 120141
+    Group: "AG_ADVENTURE"
+    Name: "Undersea Tunnel Dungeon Exploration"
+    #Reward:
+    #  ItemID: 22876
+    Score: 20
+  - ID: 120142
+    Group: "AG_ADVENTURE"
+    Name: "Thanatos Tower Dungeon Exploration"
+    #Reward:
+    #  ItemID: 22876
+    Score: 20
+  - ID: 120143
+    Group: "AG_ADVENTURE"
+    Name: "Thor Volcano Dungeon Exploration"
+    #Reward:
+    #  ItemID: 22876
+    Score: 20
+  - ID: 120144
+    Group: "AG_ADVENTURE"
+    Name: "Sunken Ship Dungeon Exploration"
+    #Reward:
+    #  ItemID: 22876
+    Score: 20
+  - ID: 120145
+    Group: "AG_ADVENTURE"
+    Name: "Turtle Dungeon Exploration"
+    #Reward:
+    #  ItemID: 22876
+    Score: 20
+  - ID: 120146
+    Group: "AG_ADVENTURE"
+    Name: "Toy Factory Dungeon Exploration"
+    #Reward:
+    #  ItemID: 22876
+    Score: 20
+  - ID: 127001
+    Group: "AG_CHATTING"
+    Name: "Prontera Contribution"
+    Map: "prontera"
+    Target:
+      Count: 100000
+    Score: 10
+  - ID: 127002
+    Group: "AG_CHATTING"
+    Name: "Geffen Contribution"
+    Map: "geffen"
+    Target:
+      Count: 100000
+    Score: 10
+  - ID: 127003
+    Group: "AG_CHATTING"
+    Name: "Morocc Contribution"
+    Map: "morocc"
+    Target:
+      Count: 100000
+    Score: 10
+  - ID: 127004
+    Group: "AG_CHATTING"
+    Name: "Payon Contribution"
+    Map: "payon"
+    Target:
+      Count: 100000
+    Score: 10
+  - ID: 127005
+    Group: "AG_CHATTING"
+    Name: "Yuno Contribution"
+    Map: "yuno"
+    Target:
+      Count: 100000
+    Score: 10
+  - ID: 127006
+    Group: "AG_CHATTING"
+    Name: "Lighthalzen Contribution"
+    Map: "lighthalzen"
+    Target:
+      Count: 100000
+    Score: 10
+  - ID: 127007
+    Group: "AG_CHATTING"
+    Name: "Einbroch Contribution"
+    Map: "einbroch"
+    Target:
+      Count: 100000
+    Score: 10
+  - ID: 127008
+    Group: "AG_CHATTING"
+    Name: "Rachel Contribution"
+    Map: "rachel"
+    Target:
+      Count: 100000
+    Score: 10
+  - ID: 127009
+    Group: "AG_CHATTING"
+    Name: "Veins Contribution"
+    Map: "veins"
+    Target:
+      Count: 100000
+    Score: 10
+  - ID: 128000
+    Group: "AG_BATTLE"
+    Name: "Uninvited Guest"
+    #Target:
+    #  - MobID: 2996
+    #    Count: 1
+    Score: 10
+  - ID: 128001
+    Group: "AG_BATTLE"
+    Name: "Strange Guest"
+    #Target:
+    #  - MobID: 2996
+    #    Count: 10
+    Score: 10
+  - ID: 128002
+    Group: "AG_BATTLE"
+    Name: "Get along with map..."
+    #Target:
+    #  - MobID: 2996
+    #    Count: 25
+    Score: 20
+  - ID: 128003
+    Group: "AG_BATTLE"
+    Name: "Welcomed Guest"
+    #Target:
+    #  - MobID: 2996
+    #    Count: 50
+    Score: 30
+  - ID: 128004
+    Group: "AG_BATTLE"
+    Name: "Kimmy's best friend"
+    #Target:
+    #  - MobID: 2996
+    #    Count: 100
+    Score: 50
+  - ID: 128005
+    Group: "AG_BATTLE"
+    Name: "Novice Angler"
+    #Target:
+    #  - MobID: 2322
+    #    Count: 1
+    Score: 10
+  - ID: 128006
+    Group: "AG_BATTLE"
+    Name: "Juicy Hunter"
+    #Target:
+    #  - MobID: 2322
+    #    Count: 10
+    Score: 20
+  - ID: 128007
+    Group: "AG_BATTLE"
+    Name: "Rhythm Master"
+    #Target:
+    #  - MobID: 2322
+    #    Count: 50
+    Score: 50
+  - ID: 128008
+    Group: "AG_BATTLE"
+    Name: "Bold Adventurer"
+    Target:
+      - MobID: 1929
+        Count: 1
+    Score: 10
+  - ID: 128009
+    Group: "AG_BATTLE"
+    Name: "Baphomet Hatred"
+    Target:
+      - MobID: 1929
+        Count: 10
+    Score: 20
+  - ID: 128010
+    Group: "AG_BATTLE"
+    Name: "Goat's Nemesis"
+    Target:
+      - MobID: 1929
+        Count: 50
+    Score: 50
+  - ID: 128011
+    Group: "AG_BATTLE"
+    Name: "Ordinary Tourist"
+    #Target:
+    #  - MobID: 3029
+    #    Count: 1
+    Score: 10
+  - ID: 128012
+    Group: "AG_BATTLE"
+    Name: "Backcountry Expert"
+    #Target:
+    #  - MobID: 3029
+    #    Count: 10
+    Score: 20
+  - ID: 128013
+    Group: "AG_BATTLE"
+    Name: "Able to eat more like this"
+    #Target:
+    #  - MobID: 3029
+    #    Count: 50
+    Score: 50
+  - ID: 128014
+    Group: "AG_BATTLE"
+    Name: "Digest hard meat"
+    #Target:
+    #  - MobID: 2319
+    #    Count: 1
+    Score: 10
+  - ID: 128015
+    Group: "AG_BATTLE"
+    Name: "Master of Escape"
+    #Target:
+    #  - MobID: 2319
+    #    Count: 10
+    Score: 20
+  - ID: 128016
+    Group: "AG_BATTLE"
+    Name: "Immortal Hunter"
+    #Target:
+    #  - MobID: 2319
+    #    Count: 50
+    Score: 50
+  - ID: 128017
+    Group: "AG_BATTLE"
+    Name: "Stood up and overcame despair"
+    #Target:
+    #  - MobID: 3097
+    #    Count: 1
+    Score: 10
+  - ID: 128018
+    Group: "AG_BATTLE"
+    Name: "Ember of Hope"
+    #Target:
+    #  - MobID: 3097
+    #    Count: 10
+    Score: 10
+  - ID: 128019
+    Group: "AG_BATTLE"
+    Name: "Pouring Aurora"
+    #Target:
+    #  - MobID: 3097
+    #    Count: 25
+    Score: 20
+  - ID: 128020
+    Group: "AG_BATTLE"
+    Name: "Who is desperate? I am hopeless!"
+    #Target:
+    #  - MobID: 3097
+    #    Count: 50
+    Score: 30
+  - ID: 128021
+    Group: "AG_BATTLE"
+    Name: "I know god will save the world"
+    #Target:
+    #  - MobID: 3097
+    #    Count: 100
+    Score: 50
+  - ID: 128022
+    Group: "AG_BATTLE"
+    Name: "There was mercy in Morocc army"
+    #Target:
+    #  - MobID: 3000
+    #  Count: 1
+    Score: 10
+  - ID: 128023
+    Group: "AG_BATTLE"
+    Name: "There was fear in Morocc army"
+    #Target:
+    #  - MobID: 3000
+    #    Count: 10
+    Score: 20
+  - ID: 128024
+    Group: "AG_BATTLE"
+    Name: "Guard of weak army"
+    #Target:
+    #  - MobID: 3000
+    #    Count: 50
+    Score: 50
+  - ID: 128025
+    Group: "AG_BATTLE"
+    Name: "Audience with the queen"
+    #Target:
+    #  - MobID: 2529
+    #    Count: 1
+    Score: 10
+  - ID: 128026
+    Group: "AG_BATTLE"
+    Name: "Warm earth"
+    #Target:
+    #  - MobID: 2533
+    #    Count: 1
+    Score: 10
+  - ID: 128027
+    Group: "AG_BATTLE"
+    Name: "Water is very good exactly"
+    #Target:
+    #  - MobID: 2534
+    #    Count: 1
+    Score: 10
+  - ID: 128028
+    Group: "AG_BATTLE"
+    Name: "Pleasant breeze"
+    #Target:
+    #  - MobID: 2535
+    #    Count: 1
+    Score: 10
+  - ID: 128029
+    Group: "AG_BATTLE"
+    Name: "Visitor of old castle"
+    #Target:
+    #  - MobID: 2476
+    #    Count: 1
+    Score: 10
+  - ID: 128030
+    Group: "AG_BATTLE"
+    Name: "Lord of old castle"
+    #Target:
+    #  - MobID: 2476
+    #    Count: 10
+    Score: 20
+  - ID: 128031
+    Group: "AG_BATTLE"
+    Name: "Conqueror of old castle"
+    #Target:
+    #  - MobID: 2476
+    #    Count: 50
+    Score: 50
+  - ID: 128032
+    Group: "AG_BATTLE"
+    Name: "Haggard sucker"
+    #Target:
+    #  - MobID: 3150
+    #    Count: 1
+    Score: 10
+  - ID: 128033
+    Group: "AG_BATTLE"
+    Name: "Hope of the Knight"
+    #Target:
+    #  - MobID: 3150
+    #    Count: 10
+    Score: 20
+  - ID: 128034
+    Group: "AG_BATTLE"
+    Name: "Guardian of the Dawn"
+    #Target:
+    #  - MobID: 3150
+    #    Count: 50
+    Score: 50
+  - ID: 128035
+    Group: "AG_BATTLE"
+    Name: "Time Traveler"
+    #Target:
+    #  - MobID: 3190
+    #    Count: 1
+    Score: 10
+  - ID: 128036
+    Group: "AG_BATTLE"
+    Name: "Restore ancient relic"
+    #Target:
+    #  - MobID: 3190
+    #    Count: 10
+    Score: 20
+  - ID: 128037
+    Group: "AG_BATTLE"
+    Name: "Master of relic transport"
+    #Target:
+    #  - MobID: 3190
+    #    Count: 50
+    Score: 50
+  - ID: 128038
+    Group: "AG_BATTLE"
+    Name: "Show Jailbreak to the captain"
+    #Target:
+    #  - MobID: 3181
+    #    Count: 1
+    Score: 10
+  - ID: 128039
+    Group: "AG_BATTLE"
+    Name: "Show Jailbreak to the weak captain"
+    #Target:
+    #  - MobID: 3188
+    #    Count: 1
+    Score: 10
+  - ID: 128040
+    Group: "AG_BATTLE"
+    Name: "Riot on board"
+    #Target:
+    #  - MobID: 3181
+    #    Count: 1
+    Score: 20
+  - ID: 128041
+    Group: "AG_BATTLE"
+    Name: "Turmoil on board"
+    #Target:
+    #  - MobID: 3181
+    #    Count: 10
+    Score: 20
+  - ID: 128042
+    Group: "AG_BATTLE"
+    Name: "Rebellion on board"
+    #Target:
+    #  - MobID: 3181
+    #    Count: 50
+    Score: 50
+  - ID: 128043
+    Group: "AG_BATTLE"
+    Name: "Revolt of Riot"
+    #Target:
+    #  - MobID: 3188
+    #    Count: 50
+    Score: 50
+  - ID: 128044
+    Group: "AG_BATTLE"
+    Name: "Magic tournament champion"
+    #Target:
+    #  - MobID: 2564
+    #    Count: 1
+    Score: 10
+  - ID: 128045
+    Group: "AG_BATTLE"
+    Name: "Gladiator of Coliseum"
+    #Target:
+    #  - MobID: 2564
+    #    Count: 10
+    Score: 20
+  - ID: 128046
+    Group: "AG_BATTLE"
+    Name: "Slayer of Colosseum"
+    #Target:
+    #  - MobID: 2564
+    #    Count: 50
+    Score: 50
+  - ID: 128047
+    Group: "AG_BATTLE"
+    Name: "Endless Tower challenger"
+    Target:
+      - MobID: 1956
+        Count: 1
+    Score: 10
+  - ID: 128048
+    Group: "AG_BATTLE"
+    Name: "Endless Tower Slayer"
+    Target:
+      - MobID: 1956
+        Count: 10
+    Score: 20
+  - ID: 128049
+    Group: "AG_BATTLE"
+    Name: "Lord of the tower"
+    Target:
+      - MobID: 1956
+        Count: 50
+    Score: 50
+  - ID: 128050
+    Group: "AG_BATTLE"
+    Name: "Novice Exorcist"
+    #Target:
+    #  - MobID: 2327
+    #    Count: 1
+    Score: 10
+  - ID: 128051
+    Group: "AG_BATTLE"
+    Name: "Experienced Exorcist"
+    #Target:
+    #  - MobID: 2327
+    #    Count: 10
+    Score: 20
+  - ID: 128052
+    Group: "AG_BATTLE"
+    Name: "Legendary Exorcist"
+    #Target:
+    #  - MobID: 2327
+    #    Count: 50
+    Score: 50
+  - ID: 129001
+    Group: "AG_ADVENTURE"
+    Name: "Prontera Explorer"
+    Dependent: [120001, 120002, 120003, 120004, 120005, 120006, 120007, 120008, 120009, 120010]
+    Reward:
+      ItemID: 644
+    Score: 20
+  - ID: 129002
+    Group: "AG_ADVENTURE"
+    Name: "Geffen Explorer"
+    Dependent: [120011, 120012, 120013, 120014, 120015, 120016, 120017]
+    Reward:
+      ItemID: 644
+    Score: 20
+  - ID: 129003
+    Group: "AG_ADVENTURE"
+    Name: "Sograt Desert Explorer"
+    Dependent: [120018, 120019, 120020, 120021, 120022, 120023]
+    Reward:
+      ItemID: 644
+    Score: 20
+  - ID: 129004
+    Group: "AG_ADVENTURE"
+    Name: "Payon Explorer"
+    Dependent: [120024, 120025, 120026, 120027, 120028, 120029, 120030, 120031]
+    Reward:
+      ItemID: 644
+    Score: 20
+  - ID: 129005
+    Group: "AG_ADVENTURE"
+    Name: "North Mjolnir Explorer"
+    Dependent: [120032, 120033, 120034, 120035, 120036]
+    Reward:
+      ItemID: 644
+    Score: 20
+  - ID: 129006
+    Group: "AG_ADVENTURE"
+    Name: "South Mjolnir Explorer"
+    Dependent: [120037, 120038, 120039, 120040, 120041, 120042, 120043]
+    Reward:
+      ItemID: 644
+    Score: 20
+  - ID: 129007
+    Group: "AG_ADVENTURE"
+    Name: "Comodo Explorer"
+    Dependent: [120044, 120045, 120046, 120047, 120048, 120049, 120050, 120051]
+    Reward:
+      ItemID: 644
+    Score: 20
+  - ID: 129008
+    Group: "AG_ADVENTURE"
+    Name: "Rune Midgard Explorer"
+    Dependent: [129001, 129002, 129003, 129004, 129005, 129006, 129007]
+    Reward:
+      ItemID: 617
+    Score: 50
+  - ID: 129009
+    Group: "AG_ADVENTURE"
+    Name: "Yuno Explorer"
+    Dependent: [120052, 120053, 120054, 120055, 120056, 120057, 120058, 120059, 120060, 120061]
+    Reward:
+      ItemID: 644
+    Score: 20
+  - ID: 129010
+    Group: "AG_ADVENTURE"
+    Name: "Hugel Explorer"
+    Dependent: [120062, 120063, 120064, 120065, 120066]
+    Reward:
+      ItemID: 644
+    Score: 20
+  - ID: 129011
+    Group: "AG_ADVENTURE"
+    Name: "Einbroch Explorer"
+    Dependent: [120067, 120068, 120069, 120070, 120071, 120072, 120073, 120074]
+    Reward:
+      ItemID: 644
+    Score: 20
+  - ID: 129012
+    Group: "AG_ADVENTURE"
+    Name: "Lighthalzen Explorer"
+    Dependent: [120075, 120076, 120077]
+    Reward:
+      ItemID: 644
+    Score: 20
+  - ID: 129013
+    Group: "AG_ADVENTURE"
+    Name: "Schwarzwald Explorer"
+    Dependent: [129009, 129010, 129011, 129012]
+    Reward:
+      ItemID: 617
+    Score: 50
+  - ID: 129014
+    Group: "AG_ADVENTURE"
+    Name: "Rachel Explorer"
+    Dependent: [120078, 120079, 120080, 120081, 120082, 120083, 120084]
+    Reward:
+      ItemID: 644
+    Score: 20
+  - ID: 129015
+    Group: "AG_ADVENTURE"
+    Name: "Veins Explorer"
+    Dependent: [120085, 120086, 120087, 120088, 120089]
+    Reward:
+      ItemID: 644
+    Score: 20
+  - ID: 129016
+    Group: "AG_ADVENTURE"
+    Name: "Arunafeltz Explorer"
+    Dependent: [129014, 129015]
+    Reward:
+      ItemID: 617
+    Score: 50
+  - ID: 129017
+    Group: "AG_ADVENTURE"
+    Name: "Laphine Explorer"
+    Dependent: [120090, 120091, 120092, 120093, 120094, 120095]
+    Reward:
+      ItemID: 644
+    Score: 20
+  - ID: 129018
+    Group: "AG_ADVENTURE"
+    Name: "Manuk Explorer"
+    Dependent: [120096, 120097, 120098, 120099, 120100]
+    Reward:
+      ItemID: 644
+    Score: 20
+  - ID: 129019
+    Group: "AG_ADVENTURE"
+    Name: "Eclage Explorer"
+    Dependent: [129017, 129018]
+    Reward:
+      ItemID: 617
+    Score: 50
+  - ID: 129020
+    Group: "AG_ADVENTURE"
+    Name: "Localizing fields explorer"
+    Dependent: [120101, 120102, 120103, 120104, 120105, 120106, 120107, 120108, 120109]
+    Reward:
+      ItemID: 617
+    Score: 50
+  - ID: 130000 # Talk to Prince NPC (npc/quests/quests_morocc.txt L5288)
+    Group: "AG_CHATTING"
+    Name: "Socialite debut"
+    Reward:
+      TitleID: 1034
+    Score: 10
+  - ID: 170000
+    Group: "AG_HEAR"
+    Name: "Song chamber is not an accident"
+    Score: 10
+  - ID: 190000
+    Group: "AG_CHATTING"
+    Name: "Alliance workers of merchant city"
+    Score: 50
+  - ID: 200000
+    Group: "AG_GOAL_LEVEL"
+    Name: "Acquire the first aura!"
+    Condition: " BaseLevel >= 99 "
+    Reward:
+      ItemID: 12549
+      Script: " specialeffect2 EF_BLESSING; sc_start SC_BLESSING,30000,10; "
+      TitleID: 1000
+    Score: 50
+  - ID: 200001
+    Group: "AG_GOAL_LEVEL"
+    Name: "Acquire the second aura!"
+    Condition: " BaseLevel >= 150 "
+    Dependent: [200000]
+    Reward:
+      ItemID: 5364
+      Script: " specialeffect2 EF_BLESSING; sc_start SC_BLESSING,30000,10; "
+      TitleID: 1001
+    Score: 60
+  - ID: 200002
+    Group: "AG_GOAL_LEVEL"
+    Name: "Acquire the third aura!"
+    Condition: " BaseLevel >= 175 "
+    Dependent: [200001]
+    Reward:
+    #  ItemID: 18880
+      Script: " specialeffect2 EF_BLESSING; sc_start SC_BLESSING,30000,10; "
+      TitleID: 1002
+    Score: 70
+  - ID: 200003
+    Group: "AG_GOAL_LEVEL"
+    Name: "Master Job level!"
+    Condition: " JobLevel >= 50 "
+    Reward:
+      ItemID: 617
+      Script: " specialeffect2 EF_BLESSING; sc_start SC_BLESSING,30000,10; "
+      TitleID: 1003
+    Score: 30
+  - ID: 200004
+    Group: "AG_GOAL_LEVEL"
+    Name: "Grandmaster Job level!"
+    Condition: " JobLevel >= 70 "
+    Dependent: [200003]
+    Reward:
+    #  ItemID: 12817
+      Script: " specialeffect2 EF_BLESSING; sc_start SC_BLESSING,30000,10; "
+      TitleID: 1004
+    Score: 50
+  - ID: 200005
+    Group: "AG_JOB_CHANGE"
+    Name: "Official Adventurer"
+    Condition: " Class >= JOB_SWORDMAN && Class <= JOB_THIEF "
+    Reward:
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+    Score: 10
+  - ID: 200006
+    Group: "AG_JOB_CHANGE"
+    Name: "First step of job change!"
+    Condition: " Class >= JOB_SWORDMAN && Class <= JOB_THIEF "
+    Reward:
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+    Score: 20
+  - ID: 200007
+    Group: "AG_JOB_CHANGE"
+    Name: "Veteran Adventurer! (1)"
+    Condition: " Class >= JOB_KNIGHT && Class <= JOB_ASSASSIN "
+    Reward:
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+    Score: 25
+  - ID: 200008
+    Group: "AG_JOB_CHANGE"
+    Name: "Veteran Adventurer! (2)"
+    Condition: " Class >= JOB_CRUSADER && Class <= JOB_DANCER "
+    Reward:
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+    Score: 25
+  - ID: 200009
+    Group: "AG_JOB_CHANGE"
+    Name: "Warrior (1)"
+    Condition: " Class >= JOB_LORD_KNIGHT && Class <= JOB_ASSASSIN_CROSS "
+    Reward:
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+    Score: 30
+  - ID: 200010
+    Group: "AG_JOB_CHANGE"
+    Name: "Warrior (2)"
+    Condition: " Class >= JOB_PALADIN && Class <= JOB_GYPSY "
+    Reward:
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+    Score: 30
+  - ID: 200011
+    Group: "AG_JOB_CHANGE"
+    Name: "Elite Adventurer! (1)"
+    Condition: " Class >= JOB_RUNE_KNIGHT && Class <= JOB_GUILLOTINE_CROSS "
+    Reward:
+    #  ItemID: 16483
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+    Score: 50
+  - ID: 200012
+    Group: "AG_JOB_CHANGE"
+    Name: "Transcendentaler! (1)"
+    Condition: " Class >= JOB_RUNE_KNIGHT_T && Class <= JOB_GUILLOTINE_CROSS_T "
+    Reward:
+    #  ItemID: 16483
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+    Score: 60
+  - ID: 200013
+    Group: "AG_JOB_CHANGE"
+    Name: "Elite Adventurer! (2)"
+    Condition: " Class >= JOB_ROYAL_GUARD && Class <= JOB_SHADOW_CHASER "
+    Reward:
+    #  ItemID: 16483
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+    Score: 50
+  - ID: 200014
+    Group: "AG_JOB_CHANGE"
+    Name: "Transcendentaler! (2)"
+    Condition: " Class >= JOB_ROYAL_GUARD_T && Class <= JOB_SHADOW_CHASER_T "
+    Reward:
+    #  ItemID: 16483
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+    Score: 60
+  - ID: 200015
+    Group: "AG_JOB_CHANGE"
+    Name: "The way of exceptional character"
+    Condition: " Class == JOB_SUPER_NOVICE || Class == JOB_GUNSLINGER || Class == JOB_NINJA || Class == JOB_TAEKWON "
+    Reward:
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+    Score: 10
+  - ID: 200016
+    Group: "AG_JOB_CHANGE"
+    Name: "This is My way!"
+    Condition: " Class == JOB_STAR_GLADIATOR || Class == JOB_SOUL_LINKER || Class == JOB_KAGEROU || Class == JOB_OBORO || Class == JOB_REBELLION "
+    Reward:
+    #  ItemID: 16483
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+    Score: 20
+  - ID: 200017
+    Group: "AG_GOAL_STATUS"
+    Name: "Bearish Power!"
+    Condition: " bStr >= 90 "
+    Score: 10
+  - ID: 200018
+    Group: "AG_GOAL_STATUS"
+    Name: "Overflowing Magic!"
+    Condition: " bInt >= 90 "
+    Score: 10
+  - ID: 200019
+    Group: "AG_GOAL_STATUS"
+    Name: "Healthy Body and Mental Health!"
+    Condition: " bVit >= 90 "
+    Score: 10
+  - ID: 200020
+    Group: "AG_GOAL_STATUS"
+    Name: "Speed of Light"
+    Condition: " bAgi >= 90 "
+    Score: 10
+  - ID: 200021
+    Group: "AG_GOAL_STATUS"
+    Name: "Hawk Eyes"
+    Condition: " bDex >= 90 "
+    Score: 10
+  - ID: 200022
+    Group: "AG_GOAL_STATUS"
+    Name: "Maximum Luck"
+    Condition: " bLuk >= 90 "
+    Score: 10
+  - ID: 200023
+    Group: "AG_GOAL_STATUS"
+    Name: "Dragonlike Power!"
+    Condition: " bStr >= 125 "
+    Reward:
+      Script: " sc_start SC_GIANTGROWTH,180000,1; "
+    Score: 20
+  - ID: 200024
+    Group: "AG_GOAL_STATUS"
+    Name: "Magic Insanity"
+    Condition: " bInt >= 125 "
+    Reward:
+      Script: " specialeffect2 EF_HASTEUP; bonus_script \"{ bonus2 bHPLossRate,100,10000; bonus bBaseAtk,20; bonus bAspdRate,25; }\",60,0,0,SI_STEAMPACK; "
+    Score: 20
+  - ID: 200025
+    Group: "AG_GOAL_STATUS"
+    Name: "Rock Alloy"
+    Condition: " bVit >= 125 "
+    Reward:
+      Script: " specialeffect2 EF_HEAL3; sc_start2 SC_S_LIFEPOTION,600000,-5,5; "
+    Score: 20
+  - ID: 200026
+    Group: "AG_GOAL_STATUS"
+    Name: "Speed of Light"
+    Condition: " bAgi >= 125 "
+    Reward:
+      Script: " specialeffect2 EF_STEAL; sc_start SC_INCFLEE2,60000,20; "
+    Score: 20
+  - ID: 200027
+    Group: "AG_GOAL_STATUS"
+    Name: "Falcon's Eyes"
+    Condition: " bDex >= 125 "
+    Reward:
+      Script: " specialeffect2 EF_MAGICALATTHIT; sc_start SC_INCCRI,300000,30; "
+    Score: 20
+  - ID: 200028
+    Group: "AG_GOAL_STATUS"
+    Name: "Lucky Fever"
+    Condition: " bLuk >= 125 "
+    Reward:
+      Script: " specialeffect2 EF_GLORIA; sc_start SC_GLORIA,15000,0; "
+    Score: 20
+  - ID: 200029
+    Group: "AG_GOAL_STATUS"
+    Name: "Incarnation of Love and Hate"
+    Condition: " BaseLevel == 99 && Class == JOB_NOVICE "
+    Reward:
+    #  ItemID: 16483
+      Script: " specialeffect2 EF_BLESSING; sc_start SC_BLESSING,30000,10; "
+    Score: 30
+  - ID: 200030
+    Group: "AG_GOAL_STATUS"
+    Name: "I really love it!"
+    Condition: " BaseLevel == 99 && (Class >= JOB_SWORDMAN && Class <= JOB_THIEF) "
+    Reward:
+    #  ItemID: 16504
+      Script: " specialeffect2 EF_BLESSING; sc_start SC_BLESSING,30000,10; "
+    Score: 30
+  - ID: 200031
+    Group: "AG_JOB_CHANGE"
+    Name: "Reborn in Valhalla!"
+    Condition: " BaseLevel == 99 && Class == JOB_NOVICE_HIGH "
+    Reward:
+    #  ItemID: 22808
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+    Score: 10
+  - ID: 230100
+    Group: "AG_TAMING"
+    Name: "Poring is Love"
+    Dependent: [230101, 230102, 230103, 230104]
+    Reward:
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+      TitleID: 1025
+    Score: 50
+  - ID: 230110
+    Group: "AG_TAMING"
+    Name: "Entomologist"
+    Dependent: [230111, 230112, 230113, 230114, 230115, 230116]
+    Reward:
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+      TitleID: 1026
+    Score: 50
+  - ID: 230120
+    Group: "AG_TAMING"
+    Name: "Animals are also our friend"
+    Dependent: [230121, 230122, 230123, 230124, 230125, 230126, 230127, 230128]
+    Reward:
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+      TitleID: 1027
+    Score: 50
+  - ID: 230140
+    Group: "AG_TAMING"
+    Name: "Monster Girls Unite!!"
+    Dependent: [230141, 230142, 230143, 230144, 230145, 230146, 230147]
+    Reward:
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+      TitleID: 1029
+    Score: 50
+  - ID: 230101
+    Group: "AG_TAMING"
+    Name: "Poring - taming"
+    Target:
+      - MobID: 1002
+        Count: 1
+    Score: 10
+  - ID: 230102
+    Group: "AG_TAMING"
+    Name: "Drops - taming"
+    Target:
+      - MobID: 1113
+        Count: 1
+    Score: 10
+  - ID: 230103
+    Group: "AG_TAMING"
+    Name: "Poporing - taming"
+    Target:
+      - MobID: 1031
+        Count: 1
+    Score: 10
+  - ID: 230104
+    Group: "AG_TAMING"
+    Name: "Novice Poring - taming"
+    #Target:
+    #  - MobID: 2398
+    #    Count: 1
+    Score: 10
+  - ID: 230111
+    Group: "AG_TAMING"
+    Name: "Chonchon - taming"
+    Target:
+      - MobID: 1011
+        Count: 1
+    Score: 10
+  - ID: 230112
+    Group: "AG_TAMING"
+    Name: "Steel Chonchon - taming"
+    Target:
+      - MobID: 1042
+        Count: 1
+    Score: 10
+  - ID: 230113
+    Group: "AG_TAMING"
+    Name: "Hunter Fly - taming"
+    Target:
+      - MobID: 1035
+        Count: 1
+    Score: 10
+  - ID: 230114
+    Group: "AG_TAMING"
+    Name: "Rocker - taming"
+    Target:
+      - MobID: 1052
+        Count: 1
+    Score: 10
+  - ID: 230115
+    Group: "AG_TAMING"
+    Name: "Spore - taming"
+    Target:
+      - MobID: 1014
+        Count: 1
+    Score: 10
+  - ID: 230116
+    Group: "AG_TAMING"
+    Name: "Poison Spore - taming"
+    Target:
+      - MobID: 1077
+        Count: 1
+    Score: 10
+  - ID: 230121
+    Group: "AG_TAMING"
+    Name: "Lunatic - taming"
+    Target:
+      - MobID: 1063
+        Count: 1
+    Score: 10
+  - ID: 230122
+    Group: "AG_TAMING"
+    Name: "Picky - taming"
+    Target:
+      - MobID: 1049
+        Count: 1
+    Score: 10
+  - ID: 230123
+    Group: "AG_TAMING"
+    Name: "Savage Bebe - taming"
+    Target:
+      - MobID: 1167
+        Count: 1
+    Score: 10
+  - ID: 230124
+    Group: "AG_TAMING"
+    Name: "Baby Desert Wolf - taming"
+    Target:
+      - MobID: 1107
+        Count: 1
+    Score: 10
+  - ID: 230125
+    Group: "AG_TAMING"
+    Name: "Smokie - taming"
+    Target:
+      - MobID: 1056
+        Count: 1
+    Score: 10
+  - ID: 230126
+    Group: "AG_TAMING"
+    Name: "Yoyo - taming"
+    Target:
+      - MobID: 1057
+        Count: 1
+    Score: 10
+  - ID: 230127
+    Group: "AG_TAMING"
+    Name: "Peco Peco - taming"
+    Target:
+      - MobID: 1019
+        Count: 1
+    Score: 10
+  - ID: 230128
+    Group: "AG_TAMING"
+    Name: "Petite - taming"
+    Target:
+      - MobID: 1155
+        Count: 1
+    Score: 10
+  - ID: 230141
+    Group: "AG_TAMING"
+    Name: "Munak - taming"
+    Target:
+      - MobID: 1026
+        Count: 1
+    Score: 10
+  - ID: 230142
+    Group: "AG_TAMING"
+    Name: "Isis - taming"
+    Target:
+      - MobID: 1029
+        Count: 1
+    Score: 10
+  - ID: 230143
+    Group: "AG_TAMING"
+    Name: "Sohee - taming"
+    Target:
+      - MobID: 1170
+        Count: 1
+    Score: 10
+  - ID: 230144
+    Group: "AG_TAMING"
+    Name: "Zherlthsh - taming"
+    Target:
+      - MobID: 1200
+        Count: 1
+    Score: 10
+  - ID: 230145
+    Group: "AG_TAMING"
+    Name: "Alice - taming"
+    Target:
+      - MobID: 1275
+        Count: 1
+    Score: 10
+  - ID: 230146
+    Group: "AG_TAMING"
+    Name: "Succubus - taming"
+    Target:
+      - MobID: 1370
+        Count: 1
+    Score: 10
+  - ID: 230147
+    Group: "AG_TAMING"
+    Name: "Loli Ruri - taming"
+    Target:
+      - MobID: 1505
+        Count: 1
+    Score: 10
+  - ID: 220000
+    Group: "AG_CHATTING_CREATE"
+    Name: "Community begin"
+    Condition: " true "
+    Score: 10
+  - ID: 220001
+    Group: "AG_CHATTING_DYING"
+    Name: "A mouth only moment"
+    Condition: " true "
+    Score: 10
+  - ID: 220002
+    Group: "AG_CHATTING_COUNT"
+    Name: "Admiring the chatter"
+    Condition: " ARG0 == 20 "
+    Score: 10
+  - ID: 220003
+    Group: "AG_ADD_FRIEND"
+    Name: "My friend's friend~"
+    Condition: " ARG0 >= 1 "
+    Score: 10
+  - ID: 220004
+    Group: "AG_ADD_FRIEND"
+    Name: "A competition of popularity"
+    Condition: " ARG0 >= 10 "
+    Score: 10
+  - ID: 220005
+    Group: "AG_PARTY"
+    Name: "Let's Party~"
+    Condition: " true "
+    Score: 10
+  - ID: 220006
+    Group: "AG_MARRY"
+    Name: "Married with who..?"
+    Condition: " true "
+    Reward:
+      TitleID: 1022
+    Score: 20
+  - ID: 220007
+    Group: "AG_BABY"
+    Name: "Can you grow?"
+    Condition: " ARG0 == 1 "
+    Reward:
+      TitleID: 1032
+    Score: 20
+  - ID: 220008
+    Group: "AG_BABY"
+    Name: "Being a parent"
+    Condition: " ARG0 == 2 "
+    Reward:
+      TitleID: 1033
+    Score: 20
+  - ID: 220009
+    Group: "AG_SPEND_ZENY"
+    Name: "Activating the market economy (1)"
+    Condition: " ARG0 >= 10000 "
+    Target:
+      Count: 10000
+    Score: 10
+  - ID: 220010
+    Group: "AG_SPEND_ZENY"
+    Name: "Activating the market economy (2)"
+    Condition: " ARG0 >= 100000 "
+    Target:
+      Count: 100000
+    Score: 15
+  - ID: 220011
+    Group: "AG_SPEND_ZENY"
+    Name: "Activating the market economy (3)"
+    Condition: " ARG0 >= 500000 "
+    Target:
+      Count: 500000
+    Score: 20
+  - ID: 220012
+    Group: "AG_SPEND_ZENY"
+    Name: "Activating the market economy (4)"
+    Condition: " ARG0 >= 1000000 "
+    Target:
+      Count: 1000000
+    Score: 30
+  - ID: 220013
+    Group: "AG_SPEND_ZENY"
+    Name: "Activating the market economy (5)"
+    Condition: " ARG0 >= 5000000 "
+    Target:
+      Count: 5000000
+    Score: 50
+  - ID: 220014
+    Group: "AG_ENCHANT_SUCCESS"
+    Name: "I can't quit from refining! (1)"
+    Condition: " ARG0 == 1 && ARG1 >= 7 "
+    Score: 10
+  - ID: 220015
+    Group: "AG_ENCHANT_SUCCESS"
+    Name: "I can't quit from refining! (2)"
+    Condition: " ARG0 == 1 && ARG1 >= 12 "
+    Score: 15
+  - ID: 220016
+    Group: "AG_ENCHANT_SUCCESS"
+    Name: "I can't quit from refining! (3)"
+    Condition: " ARG0 == 2 && ARG1 >= 7 "
+    Score: 10
+  - ID: 220017
+    Group: "AG_ENCHANT_SUCCESS"
+    Name: "I can't quit from refining! (4)"
+    Condition: " ARG0 == 2 && ARG1 >= 12 "
+    Score: 15
+  - ID: 220018
+    Group: "AG_ENCHANT_SUCCESS"
+    Name: "I can't quit from refining! (5)"
+    Condition: " ARG0 == 3 && ARG1 >= 7 "
+    Score: 15
+  - ID: 220019
+    Group: "AG_ENCHANT_SUCCESS"
+    Name: "I can't quit from refining! (6)"
+    Condition: " ARG0 == 3 && ARG1 >= 12 "
+    Score: 20
+  - ID: 220020
+    Group: "AG_ENCHANT_SUCCESS"
+    Name: "I can't quit from refining! (7)"
+    Condition: " ARG0 == 4 && ARG1 >= 7 "
+    Score: 20
+  - ID: 220021
+    Group: "AG_ENCHANT_SUCCESS"
+    Name: "I can't quit from refining! (8)"
+    Condition: " ARG0 == 4 && ARG1 >= 12 "
+    Score: 30
+  - ID: 220022
+    Group: "AG_ENCHANT_FAIL"
+    Name: "Human's greed has no ending.."
+    Condition: " true "
+    Score: 10
+  - ID: 220023
+    Group: "AG_GET_ITEM"
+    Name: "I found it! (1)"
+    Condition: " ARG0 >= 100 "
+    Score: 10
+  - ID: 220024
+    Group: "AG_GET_ITEM"
+    Name: "I found it! (2)"
+    Condition: " ARG0 >= 1000 "
+    Score: 10
+  - ID: 220025
+    Group: "AG_GET_ITEM"
+    Name: "I found it! (3)"
+    Condition: " ARG0 >= 5000 "
+    Score: 15
+  - ID: 220026
+    Group: "AG_GET_ITEM"
+    Name: "I found it! (4)"
+    Condition: " ARG0 >= 10000 "
+    Score: 15
+  - ID: 220027
+    Group: "AG_GET_ITEM"
+    Name: "I found it! (5)"
+    Condition: " ARG0 >= 50000 "
+    Score: 20
+  - ID: 220028
+    Group: "AG_GET_ITEM"
+    Name: "I found it! (6)"
+    Condition: " ARG0 >= 100000 "
+    Score: 20
+  - ID: 220029
+    Group: "AG_GET_ITEM"
+    Name: "I found it! (7)"
+    Condition: " ARG0 >= 150000 "
+    Score: 30
+  - ID: 220030
+    Group: "AG_GET_ZENY"
+    Name: "Rich King (1)"
+    Condition: " ARG0 >= 10000 "
+    Score: 10
+  - ID: 220031
+    Group: "AG_GET_ZENY"
+    Name: "Rich King (2)"
+    Condition: " ARG0 >= 100000 "
+    Score: 15
+  - ID: 220032
+    Group: "AG_GET_ZENY"
+    Name: "Rich King (3)"
+    Condition: " ARG0 >= 1000000 "
+    Score: 20
+  - ID: 220033
+    Group: "AG_GET_ZENY"
+    Name: "Rich King (4)"
+    Condition: " ARG0 >= 10000000 "
+    Score: 25
+  - ID: 220034
+    Group: "AG_GET_ZENY"
+    Name: "Rich King (5)"
+    Condition: " ARG0 >= 100000000 "
+    Score: 30
+  - ID: 220035
+    Group: "AG_GET_ZENY"
+    Name: "Rich King (6)"
+    Condition: " ARG0 >= 1000000000 "
+    Score: 40
+  - ID: 230200
+    Group: "AG_BATTLE"
+    Name: "Poring seeker"
+    Dependent: [230201, 230202, 230203]
+    Score: 10
+  - ID: 230201
+    Group: "AG_BATTLE"
+    Name: "Exploring Poring's life (1)"
+    Target:
+      - MobID: 1002
+        Count: 10
+    #  - MobID: 2398
+    #    Count: 10
+      - MobID: 1113
+        Count: 10
+      - MobID: 1031
+        Count: 10
+      - MobID: 1242
+        Count: 10
+    Score: 10
+  - ID: 230202
+    Group: "AG_BATTLE"
+    Name: "Exploring Poring's life (2)"
+    Target:
+      - MobID: 1090
+        Count: 1
+      - MobID: 1582
+        Count: 1
+      - MobID: 1096
+        Count: 1
+      - MobID: 1388
+        Count: 1
+      - MobID: 1120
+        Count: 1
+    Score: 15
+  - ID: 230203
+    Group: "AG_BATTLE"
+    Name: "Exploring Poring's life (3)"
+    Target:
+      - MobID: 1613
+        Count: 5
+      - MobID: 1977
+        Count: 5
+      - MobID: 1836
+        Count: 5
+    Score: 20
+  - ID: 240000
+    Group: "AG_GOAL_LEVEL"
+    Name: "Complete challenges after first introduction"
+    Score: 10
+  - ID: 240001
+    Group: "AG_GOAL_ACHIEVE"
+    Name: "Reaching Level 1"
+    Reward:
+      ItemID: 644
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+    Score: 10
+  - ID: 240002
+    Group: "AG_GOAL_ACHIEVE"
+    Name: "Reaching Level 2"
+    Dependent: [240001]
+    Reward:
+      ItemID: 644
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+    Score: 10
+  - ID: 240003
+    Group: "AG_GOAL_ACHIEVE"
+    Name: "Reaching Level 3"
+    Dependent: [240002]
+    Reward:
+      ItemID: 644
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+    Score: 10
+  - ID: 240004
+    Group: "AG_GOAL_ACHIEVE"
+    Name: "Reaching Level 4"
+    Dependent: [240003]
+    Reward:
+      ItemID: 644
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+    Score: 10
+  - ID: 240005
+    Group: "AG_GOAL_ACHIEVE"
+    Name: "Reaching Level 5"
+    Dependent: [240004]
+    Reward:
+      ItemID: 644
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+    Score: 10
+  - ID: 240006
+    Group: "AG_GOAL_ACHIEVE"
+    Name: "Reaching Level 6"
+    Dependent: [240005]
+    Reward:
+      ItemID: 644
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+    Score: 10
+  - ID: 240007
+    Group: "AG_GOAL_ACHIEVE"
+    Name: "Reaching Level 7"
+    Dependent: [240006]
+    Reward:
+      ItemID: 644
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+    Score: 10
+  - ID: 240008
+    Group: "AG_GOAL_ACHIEVE"
+    Name: "Reaching Level 8"
+    Dependent: [240007]
+    Reward:
+      ItemID: 644
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+    Score: 10
+  - ID: 240009
+    Group: "AG_GOAL_ACHIEVE"
+    Name: "Reaching Level 9"
+    Dependent: [240008]
+    Reward:
+      ItemID: 644
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+    Score: 10
+  - ID: 240010
+    Group: "AG_GOAL_ACHIEVE"
+    Name: "Reaching Level 10"
+    Dependent: [240009]
+    Reward:
+      ItemID: 644
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+      TitleID: 1023
+    Score: 10
+  - ID: 240011
+    Group: "AG_GOAL_ACHIEVE"
+    Name: "Reaching Level 11"
+    Dependent: [240010]
+    Reward:
+      ItemID: 644
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+    Score: 10
+  - ID: 240012
+    Group: "AG_GOAL_ACHIEVE"
+    Name: "Reaching Level 12"
+    Dependent: [240011]
+    Reward:
+      ItemID: 644
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+    Score: 10
+  - ID: 240013
+    Group: "AG_GOAL_ACHIEVE"
+    Name: "Reaching Level 13"
+    Dependent: [240012]
+    Reward:
+      ItemID: 644
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+    Score: 10
+  - ID: 240014
+    Group: "AG_GOAL_ACHIEVE"
+    Name: "Reaching Level 14"
+    Dependent: [240013]
+    Reward:
+      ItemID: 644
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+    Score: 10
+  - ID: 240015
+    Group: "AG_GOAL_ACHIEVE"
+    Name: "Reaching Level 15"
+    Dependent: [240014]
+    Reward:
+      ItemID: 644
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+    Score: 10
+  - ID: 240016
+    Group: "AG_GOAL_ACHIEVE"
+    Name: "Reaching Level 16"
+    Dependent: [240015]
+    Reward:
+      ItemID: 644
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+    Score: 10
+  - ID: 240017
+    Group: "AG_GOAL_ACHIEVE"
+    Name: "Reaching Level 17"
+    Dependent: [240016]
+    Reward:
+      ItemID: 644
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+    Score: 10
+  - ID: 240018
+    Group: "AG_GOAL_ACHIEVE"
+    Name: "Reaching Level 18"
+    Dependent: [240017]
+    Reward:
+      ItemID: 644
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+    Score: 10
+  - ID: 240019
+    Group: "AG_GOAL_ACHIEVE"
+    Name: "Reaching Level 19"
+    Dependent: [240018]
+    Reward:
+      ItemID: 644
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+    Score: 10
+  - ID: 240020
+    Group: "AG_GOAL_ACHIEVE"
+    Name: "Reaching Level 20"
+    Dependent: [240019]
+    Reward:
+      ItemID: 644
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+      TitleID: 1024
+    Score: 10
+  - ID: 220036
+    Group: "AG_EAT"
+    Name: "The beginning of outdoor"
+    Score: 20
+  - ID: 220037
+    Group: "AG_EAT"
+    Name: "The first step becoming a chef"
+    Score: 20

+ 2407 - 0
db/re/achievement_db.yml

@@ -0,0 +1,2407 @@
+# This file is a part of rAthena.
+#   Copyright(C) 2017 rAthena Development Team
+#   https://rathena.org - https://github.com/rathena
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+###########################################################################
+# Custom Achievement Database
+###########################################################################
+#
+# Achievement Settings
+#
+###########################################################################
+# ID - Unique achievement ID.
+###########################################################################
+# Group - Achievement group type. Each achievement type calls a specific
+# objective check.
+# Valid groups:
+#  AG_ADD_FRIEND
+#  AG_ADVENTURE
+#  AG_BABY
+#  AG_BATTLE
+#  AG_CHATTING
+#  AG_CHATTING_COUNT
+#  AG_CHATTING_CREATE
+#  AG_CHATTING_DYING
+#  AG_EAT
+#  AG_GET_ITEM
+#  AG_GET_ZENY
+#  AG_GOAL_ACHIEVE
+#  AG_GOAL_LEVEL
+#  AG_GOAL_STATUS
+#  AG_HEAR
+#  AG_JOB_CHANGE
+#  AG_MARRY
+#  AG_PARTY
+#  AG_ENCHANT_FAIL
+#  AG_ENCHANT_SUCCESS
+#  AG_SEE
+#  AG_SPEND_ZENY
+#  AG_TAMING
+###########################################################################
+# Name - Achievement name. Used when sending rewards through RODEX.
+###########################################################################
+# Target - A list of monster ID and count values that the achievement
+# requires. The target count can also be used for achievements that keep
+# a counter while not being related to monster kills.
+# Capped at MAX_ACHIEVEMENT_OBJECTIVES.
+###########################################################################
+# Condition - A conditional statement that must be met for the achievement
+# to be considered complete.
+###########################################################################
+# Map - A map name that is used for the AG_CHATTING type which increments
+# the counter based on the player's map.
+###########################################################################
+# Dependent: - A list of achievement IDs that need to be completed before
+# this achievement is considered complete.
+###########################################################################
+# Reward - A list of rewards that are given on completion. All fields are
+# optional.
+#   ItemID: Item ID
+#   Amount:  Amount of Item ID (default 1)
+#   Script: Bonus Script
+#   TitleID: Title ID
+###########################################################################
+# Score - Achievement points that are given on completion.
+###########################################################################
+
+Achievements:
+  - ID: 110000
+    Group: "AG_EAT"
+    Name: "At this time I live to eat"
+    Score: 10
+  - ID: 110001
+    Group: "AG_SEE"
+    Name: "A fan of this polarity"
+    Score: 10
+  - ID: 120001
+    Group: "AG_ADVENTURE"
+    Name: "North Prontera Field Exploration(1)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120002
+    Group: "AG_ADVENTURE"
+    Name: "North Prontera Field Exploration(2)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120003
+    Group: "AG_ADVENTURE"
+    Name: "North Prontera Field Exploration(3)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120004
+    Group: "AG_ADVENTURE"
+    Name: "West Prontera Field Exploration(1)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120005
+    Group: "AG_ADVENTURE"
+    Name: "West Prontera Field Exploration(2)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120006
+    Group: "AG_ADVENTURE"
+    Name: "East Prontera Field Exploration"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120007
+    Group: "AG_ADVENTURE"
+    Name: "South Prontera Field Exploration(1)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120008
+    Group: "AG_ADVENTURE"
+    Name: "South Prontera Field Exploration(2)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120009
+    Group: "AG_ADVENTURE"
+    Name: "South Prontera Field Exploration(3)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120010
+    Group: "AG_ADVENTURE"
+    Name: "South Prontera Field Exploration(4)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120011
+    Group: "AG_ADVENTURE"
+    Name: "East Geffen Field Exploration"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120012
+    Group: "AG_ADVENTURE"
+    Name: "Southeast Geffen Field Exploration"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120013
+    Group: "AG_ADVENTURE"
+    Name: "Northwest Geffen Field Exploration(1)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120014
+    Group: "AG_ADVENTURE"
+    Name: "Northwest Geffen Field Exploration(2)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120015
+    Group: "AG_ADVENTURE"
+    Name: "Northwest Geffen Field Exploration(3)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120016
+    Group: "AG_ADVENTURE"
+    Name: "South Geffen Field Exploration(1)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120017
+    Group: "AG_ADVENTURE"
+    Name: "South Geffen Field Exploration(2)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120018
+    Group: "AG_ADVENTURE"
+    Name: "Sograt Desert Field Exploration(1)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120019
+    Group: "AG_ADVENTURE"
+    Name: "Sograt Desert Field Exploration(2)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120020
+    Group: "AG_ADVENTURE"
+    Name: "Sograt Desert Field Exploration(3)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120021
+    Group: "AG_ADVENTURE"
+    Name: "Sograt Desert Field Exploration(4)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120022
+    Group: "AG_ADVENTURE"
+    Name: "Sograt Desert Field Exploration(5)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120023
+    Group: "AG_ADVENTURE"
+    Name: "Sograt Desert Field Exploration(6)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120024
+    Group: "AG_ADVENTURE"
+    Name: "Southwest Payon Field Exploration(1)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120025
+    Group: "AG_ADVENTURE"
+    Name: "Southwest Payon Field Exploration(2)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120026
+    Group: "AG_ADVENTURE"
+    Name: "Southwest Payon Field Exploration(3)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120027
+    Group: "AG_ADVENTURE"
+    Name: "Southwest Payon Field Exploration(4)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120028
+    Group: "AG_ADVENTURE"
+    Name: "East Payon Field Exploration(1)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120029
+    Group: "AG_ADVENTURE"
+    Name: "East Payon Field Exploration(2)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120030
+    Group: "AG_ADVENTURE"
+    Name: "East Payon Field Exploration(3)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120031
+    Group: "AG_ADVENTURE"
+    Name: "East Payon Field Exploration(4)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120032
+    Group: "AG_ADVENTURE"
+    Name: "North Mjolnir Field Exploration(1)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120033
+    Group: "AG_ADVENTURE"
+    Name: "North Mjolnir Field Exploration(2)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120034
+    Group: "AG_ADVENTURE"
+    Name: "North Mjolnir Field Exploration(3)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120035
+    Group: "AG_ADVENTURE"
+    Name: "North Mjolnir Field Exploration(4)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120036
+    Group: "AG_ADVENTURE"
+    Name: "North Mjolnir Field Exploration(5)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120037
+    Group: "AG_ADVENTURE"
+    Name: "South Mjolnir Field Exploration(1)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120038
+    Group: "AG_ADVENTURE"
+    Name: "South Mjolnir Field Exploration(2)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120039
+    Group: "AG_ADVENTURE"
+    Name: "South Mjolnir Field Exploration(3)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120040
+    Group: "AG_ADVENTURE"
+    Name: "South Mjolnir Field Exploration(4)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120041
+    Group: "AG_ADVENTURE"
+    Name: "South Mjolnir Field Exploration(5)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120042
+    Group: "AG_ADVENTURE"
+    Name: "South Mjolnir Field Exploration(6)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120043
+    Group: "AG_ADVENTURE"
+    Name: "South Aldebaran Field Exploration"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120044
+    Group: "AG_ADVENTURE"
+    Name: "Comodo Field Exploration(1)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120045
+    Group: "AG_ADVENTURE"
+    Name: "Comodo Field Exploration(2)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120046
+    Group: "AG_ADVENTURE"
+    Name: "Comodo Field Exploration(3)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120047
+    Group: "AG_ADVENTURE"
+    Name: "Comodo Field Exploration(4)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120048
+    Group: "AG_ADVENTURE"
+    Name: "Comodo Field Exploration(5)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120049
+    Group: "AG_ADVENTURE"
+    Name: "Comodo Field Exploration(6)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120050
+    Group: "AG_ADVENTURE"
+    Name: "Comodo Field Exploration(7)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120051
+    Group: "AG_ADVENTURE"
+    Name: "Comodo Field Exploration(8)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120052
+    Group: "AG_ADVENTURE"
+    Name: "Border Checkpoint Field Exploration(1)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120053
+    Group: "AG_ADVENTURE"
+    Name: "Border Checkpoint Field Exploration(2)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120054
+    Group: "AG_ADVENTURE"
+    Name: "Kiel Hyre Mansion Field Exploration"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120055
+    Group: "AG_ADVENTURE"
+    Name: "El Mes Plateau Field Exploration(1)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120056
+    Group: "AG_ADVENTURE"
+    Name: "El Mes Plateau Field Exploration(2)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120057
+    Group: "AG_ADVENTURE"
+    Name: "El Mes Plateau Field Exploration(3)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120058
+    Group: "AG_ADVENTURE"
+    Name: "El Mes Gorge Field Exploration"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120059
+    Group: "AG_ADVENTURE"
+    Name: "Kiel Hyre Academy Field Exploration"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120060
+    Group: "AG_ADVENTURE"
+    Name: "Guard Camp Field Exploration"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120061
+    Group: "AG_ADVENTURE"
+    Name: "Yuno Field Exploration"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120062
+    Group: "AG_ADVENTURE"
+    Name: "Front of Thanatos Tower Field Exploration"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120063
+    Group: "AG_ADVENTURE"
+    Name: "Hugel Field Exploration(1)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120064
+    Group: "AG_ADVENTURE"
+    Name: "Hugel Field Exploration(2)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120065
+    Group: "AG_ADVENTURE"
+    Name: "Hugel Field Exploration(3)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120066
+    Group: "AG_ADVENTURE"
+    Name: "Abyss Lake Field Exploration(1)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120067
+    Group: "AG_ADVENTURE"
+    Name: "Einbroch Field Exploration(1)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120068
+    Group: "AG_ADVENTURE"
+    Name: "Einbroch Field Exploration(2)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120069
+    Group: "AG_ADVENTURE"
+    Name: "Einbroch Field Exploration(3)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120070
+    Group: "AG_ADVENTURE"
+    Name: "Einbroch Field Exploration(4)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120071
+    Group: "AG_ADVENTURE"
+    Name: "Einbroch Field Exploration(5)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120072
+    Group: "AG_ADVENTURE"
+    Name: "Einbroch Field Exploration(6)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120073
+    Group: "AG_ADVENTURE"
+    Name: "Einbroch Field Exploration(7)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120074
+    Group: "AG_ADVENTURE"
+    Name: "Einbroch Field Exploration(8)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120075
+    Group: "AG_ADVENTURE"
+    Name: "Lighthalzen Field Exploration(1)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120076
+    Group: "AG_ADVENTURE"
+    Name: "Lighthalzen Field Exploration(2)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120077
+    Group: "AG_ADVENTURE"
+    Name: "Lighthalzen Field Exploration(3)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120078
+    Group: "AG_ADVENTURE"
+    Name: "Rachel Audhumbla Plains Field Exploration(1)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120079
+    Group: "AG_ADVENTURE"
+    Name: "Rachel Plains Field Exploration(1)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120080
+    Group: "AG_ADVENTURE"
+    Name: "Rachel Plains Field Exploration(2)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120081
+    Group: "AG_ADVENTURE"
+    Name: "Rachel Plains Field Exploration(3)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120082
+    Group: "AG_ADVENTURE"
+    Name: "Rachel Audhumbla Grassland Field Exploration(1)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120083
+    Group: "AG_ADVENTURE"
+    Name: "Rachel Audhumbla Grassland Field Exploration(2)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120084
+    Group: "AG_ADVENTURE"
+    Name: "Portus Luna Field Exploration"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120085
+    Group: "AG_ADVENTURE"
+    Name: "Veins Field Exploration(1)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120086
+    Group: "AG_ADVENTURE"
+    Name: "Veins Field Exploration(2)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120087
+    Group: "AG_ADVENTURE"
+    Name: "Veins Field Exploration(3)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120088
+    Group: "AG_ADVENTURE"
+    Name: "Veins Field Exploration(4)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120089
+    Group: "AG_ADVENTURE"
+    Name: "Veins Field Exploration(5)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120090
+    Group: "AG_ADVENTURE"
+    Name: "Eclage Field Exploration"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120091
+    Group: "AG_ADVENTURE"
+    Name: "North Bitfrost Field Exploration(1)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120092
+    Group: "AG_ADVENTURE"
+    Name: "South Bitfrost Field Exploration(1)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120093
+    Group: "AG_ADVENTURE"
+    Name: "Splendide Field Exploration(1)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120094
+    Group: "AG_ADVENTURE"
+    Name: "Splendide Field Exploration(2)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120095
+    Group: "AG_ADVENTURE"
+    Name: "Splendide Field Exploration(3)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120096
+    Group: "AG_ADVENTURE"
+    Name: "Manuk Field Exploration(1)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120097
+    Group: "AG_ADVENTURE"
+    Name: "Manuk Field Exploration(2)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120098
+    Group: "AG_ADVENTURE"
+    Name: "Manuk Field Exploration(3)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120099
+    Group: "AG_ADVENTURE"
+    Name: "Outskirts of Kamidal Field Exploration(1)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120100
+    Group: "AG_ADVENTURE"
+    Name: "Outskirts of Kamidal Field Exploration(2)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120101
+    Group: "AG_ADVENTURE"
+    Name: "Amatsu Field Exploration"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120102
+    Group: "AG_ADVENTURE"
+    Name: "Kunlun Field Exploration"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120103
+    Group: "AG_ADVENTURE"
+    Name: "Gonryun Field Exploration"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120104
+    Group: "AG_ADVENTURE"
+    Name: "Ayothaya Field Exploration"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120105
+    Group: "AG_ADVENTURE"
+    Name: "Moscovia Field Exploration"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120106
+    Group: "AG_ADVENTURE"
+    Name: "Brasilis Field Exploration"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120107
+    Group: "AG_ADVENTURE"
+    Name: "Dewata Field Exploration"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120108
+    Group: "AG_ADVENTURE"
+    Name: "Malaya Field Exploration(1)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120109
+    Group: "AG_ADVENTURE"
+    Name: "Malaya Field Exploration(2)"
+    Reward:
+      ItemID: 22876
+    Score: 10
+  - ID: 120110
+    Group: "AG_ADVENTURE"
+    Name: "Abbey Underground Dungeon Exploration"
+    Reward:
+      ItemID: 22876
+    Score: 20
+  - ID: 120111
+    Group: "AG_ADVENTURE"
+    Name: "Abyss Lake Dungeon Exploration"
+    Reward:
+      ItemID: 22876
+    Score: 20
+  - ID: 120112
+    Group: "AG_ADVENTURE"
+    Name: "Clock Tower Dungeon Exploration"
+    Reward:
+      ItemID: 22876
+    Score: 20
+  - ID: 120113
+    Group: "AG_ADVENTURE"
+    Name: "Amatsu Dungeon Exploration"
+    Reward:
+      ItemID: 22876
+    Score: 20
+  - ID: 120114
+    Group: "AG_ADVENTURE"
+    Name: "Ant Hell Dungeon Exploration"
+    Reward:
+      ItemID: 22876
+    Score: 20
+  - ID: 120115
+    Group: "AG_ADVENTURE"
+    Name: "Ayothaya Dungeon Exploration"
+    Reward:
+      ItemID: 22876
+    Score: 20
+  - ID: 120116
+    Group: "AG_ADVENTURE"
+    Name: "Comodo Dungeon Exploration"
+    Reward:
+      ItemID: 22876
+    Score: 20
+  - ID: 120117
+    Group: "AG_ADVENTURE"
+    Name: "Brasilis Dungeon Exploration"
+    Reward:
+      ItemID: 22876
+    Score: 20
+  - ID: 120118
+    Group: "AG_ADVENTURE"
+    Name: "Clock Tower Dungeon Exploration"
+    Reward:
+      ItemID: 22876
+    Score: 20
+  - ID: 120119
+    Group: "AG_ADVENTURE"
+    Name: "Istana Dungeon Exploration"
+    Reward:
+      ItemID: 22876
+    Score: 20
+  - ID: 120120
+    Group: "AG_ADVENTURE"
+    Name: "Scaraba Hole Dungeon Exploration"
+    Reward:
+      ItemID: 22876
+    Score: 20
+  - ID: 120121
+    Group: "AG_ADVENTURE"
+    Name: "Bitfrost Dungeon Exploration"
+    Reward:
+      ItemID: 22876
+    Score: 20
+  - ID: 120122
+    Group: "AG_ADVENTURE"
+    Name: "Einbroch Dungeon Exploration"
+    Reward:
+      ItemID: 22876
+    Score: 20
+  - ID: 120123
+    Group: "AG_ADVENTURE"
+    Name: "Geffen Underground Dungeon Exploration"
+    Reward:
+      ItemID: 22876
+    Score: 20
+  - ID: 120124
+    Group: "AG_ADVENTURE"
+    Name: "Glastheim Dungeon Exploration(1)"
+    Reward:
+      ItemID: 22876
+    Score: 20
+  - ID: 120125
+    Group: "AG_ADVENTURE"
+    Name: "Glastheim Dungeon Exploration(2)"
+    Reward:
+      ItemID: 22876
+    Score: 20
+  - ID: 120126
+    Group: "AG_ADVENTURE"
+    Name: "Glastheim Dungeon Exploration(3)"
+    Reward:
+      ItemID: 22876
+    Score: 20
+  - ID: 120127
+    Group: "AG_ADVENTURE"
+    Name: "Glastheim Dungeon Exploration(4)"
+    Reward:
+      ItemID: 22876
+    Score: 20
+  - ID: 120128
+    Group: "AG_ADVENTURE"
+    Name: "Kunlun Dungeon Exploration"
+    Reward:
+      ItemID: 22876
+    Score: 20
+  - ID: 120129
+    Group: "AG_ADVENTURE"
+    Name: "Rachel Dungeon Exploration"
+    Reward:
+      ItemID: 22876
+    Score: 20
+  - ID: 120130
+    Group: "AG_ADVENTURE"
+    Name: "Sphinx Dungeon Exploration"
+    Reward:
+      ItemID: 22876
+    Score: 20
+  - ID: 120131
+    Group: "AG_ADVENTURE"
+    Name: "Izlude Dungeon Exploration"
+    Reward:
+      ItemID: 22876
+    Score: 20
+  - ID: 120132
+    Group: "AG_ADVENTURE"
+    Name: "Robot Factory Dungeon Exploration"
+    Reward:
+      ItemID: 22876
+    Score: 20
+  - ID: 120133
+    Group: "AG_ADVENTURE"
+    Name: "Bio Lab Dungeon Exploration"
+    Reward:
+      ItemID: 22876
+    Score: 20
+  - ID: 120134
+    Group: "AG_ADVENTURE"
+    Name: "Gonryun Dungeon Exploration"
+    Reward:
+      ItemID: 22876
+    Score: 20
+  - ID: 120135
+    Group: "AG_ADVENTURE"
+    Name: "Nogg Road Dungeon Exploration"
+    Reward:
+      ItemID: 22876
+    Score: 20
+  - ID: 120136
+    Group: "AG_ADVENTURE"
+    Name: "Coal Mine Dungeon Exploration"
+    Reward:
+      ItemID: 22876
+    Score: 20
+  - ID: 120137
+    Group: "AG_ADVENTURE"
+    Name: "Pyramid Dungeon Exploration"
+    Reward:
+      ItemID: 22876
+    Score: 20
+  - ID: 120138
+    Group: "AG_ADVENTURE"
+    Name: "Orc Dungeon Exploration"
+    Reward:
+      ItemID: 22876
+    Score: 20
+  - ID: 120139
+    Group: "AG_ADVENTURE"
+    Name: "Payon Dungeon Exploration"
+    Reward:
+      ItemID: 22876
+    Score: 20
+  - ID: 120140
+    Group: "AG_ADVENTURE"
+    Name: "Labyrinth Dungeon Exploration"
+    Reward:
+      ItemID: 22876
+    Score: 20
+  - ID: 120141
+    Group: "AG_ADVENTURE"
+    Name: "Undersea Tunnel Dungeon Exploration"
+    Reward:
+      ItemID: 22876
+    Score: 20
+  - ID: 120142
+    Group: "AG_ADVENTURE"
+    Name: "Thanatos Tower Dungeon Exploration"
+    Reward:
+      ItemID: 22876
+    Score: 20
+  - ID: 120143
+    Group: "AG_ADVENTURE"
+    Name: "Thor Volcano Dungeon Exploration"
+    Reward:
+      ItemID: 22876
+    Score: 20
+  - ID: 120144
+    Group: "AG_ADVENTURE"
+    Name: "Sunken Ship Dungeon Exploration"
+    Reward:
+      ItemID: 22876
+    Score: 20
+  - ID: 120145
+    Group: "AG_ADVENTURE"
+    Name: "Turtle Dungeon Exploration"
+    Reward:
+      ItemID: 22876
+    Score: 20
+  - ID: 120146
+    Group: "AG_ADVENTURE"
+    Name: "Toy Factory Dungeon Exploration"
+    Reward:
+      ItemID: 22876
+    Score: 20
+  - ID: 127001
+    Group: "AG_CHATTING"
+    Name: "Prontera Contribution"
+    Map: "prontera"
+    Target:
+      Count: 100000
+    Score: 10
+  - ID: 127002
+    Group: "AG_CHATTING"
+    Name: "Geffen Contribution"
+    Map: "geffen"
+    Target:
+      Count: 100000
+    Score: 10
+  - ID: 127003
+    Group: "AG_CHATTING"
+    Name: "Morocc Contribution"
+    Map: "morocc"
+    Target:
+      Count: 100000
+    Score: 10
+  - ID: 127004
+    Group: "AG_CHATTING"
+    Name: "Payon Contribution"
+    Map: "payon"
+    Target:
+      Count: 100000
+    Score: 10
+  - ID: 127005
+    Group: "AG_CHATTING"
+    Name: "Yuno Contribution"
+    Map: "yuno"
+    Target:
+      Count: 100000
+    Score: 10
+  - ID: 127006
+    Group: "AG_CHATTING"
+    Name: "Lighthalzen Contribution"
+    Map: "lighthalzen"
+    Target:
+      Count: 100000
+    Score: 10
+  - ID: 127007
+    Group: "AG_CHATTING"
+    Name: "Einbroch Contribution"
+    Map: "einbroch"
+    Target:
+      Count: 100000
+    Score: 10
+  - ID: 127008
+    Group: "AG_CHATTING"
+    Name: "Rachel Contribution"
+    Map: "rachel"
+    Target:
+      Count: 100000
+    Score: 10
+  - ID: 127009
+    Group: "AG_CHATTING"
+    Name: "Veins Contribution"
+    Map: "veins"
+    Target:
+      Count: 100000
+    Score: 10
+  - ID: 128000
+    Group: "AG_BATTLE"
+    Name: "Uninvited Guest"
+    Target:
+      - MobID: 2996
+        Count: 1
+    Score: 10
+  - ID: 128001
+    Group: "AG_BATTLE"
+    Name: "Strange Guest"
+    Target:
+      - MobID: 2996
+        Count: 10
+    Score: 10
+  - ID: 128002
+    Group: "AG_BATTLE"
+    Name: "Get along with map..."
+    Target:
+      - MobID: 2996
+        Count: 25
+    Score: 20
+  - ID: 128003
+    Group: "AG_BATTLE"
+    Name: "Welcomed Guest"
+    Target:
+      - MobID: 2996
+        Count: 50
+    Score: 30
+  - ID: 128004
+    Group: "AG_BATTLE"
+    Name: "Kimmy's best friend"
+    Target:
+      - MobID: 2996
+        Count: 100
+    Score: 50
+  - ID: 128005
+    Group: "AG_BATTLE"
+    Name: "Novice Angler"
+    Target:
+      - MobID: 2322
+        Count: 1
+    Score: 10
+  - ID: 128006
+    Group: "AG_BATTLE"
+    Name: "Juicy Hunter"
+    Target:
+      - MobID: 2322
+        Count: 10
+    Score: 20
+  - ID: 128007
+    Group: "AG_BATTLE"
+    Name: "Rhythm Master"
+    Target:
+      - MobID: 2322
+        Count: 50
+    Score: 50
+  - ID: 128008
+    Group: "AG_BATTLE"
+    Name: "Bold Adventurer"
+    Target:
+      - MobID: 1929
+        Count: 1
+    Score: 10
+  - ID: 128009
+    Group: "AG_BATTLE"
+    Name: "Baphomet Hatred"
+    Target:
+      - MobID: 1929
+        Count: 10
+    Score: 20
+  - ID: 128010
+    Group: "AG_BATTLE"
+    Name: "Goat's Nemesis"
+    Target:
+      - MobID: 1929
+        Count: 50
+    Score: 50
+  - ID: 128011
+    Group: "AG_BATTLE"
+    Name: "Ordinary Tourist"
+    #Target:
+    #  - MobID: 3029
+    #    Count: 1
+    Score: 10
+  - ID: 128012
+    Group: "AG_BATTLE"
+    Name: "Backcountry Expert"
+    #Target:
+    #  - MobID: 3029
+    #    Count: 10
+    Score: 20
+  - ID: 128013
+    Group: "AG_BATTLE"
+    Name: "Able to eat more like this"
+    #Target:
+    #  - MobID: 3029
+    #    Count: 50
+    Score: 50
+  - ID: 128014
+    Group: "AG_BATTLE"
+    Name: "Digest hard meat"
+    Target:
+      - MobID: 2319
+        Count: 1
+    Score: 10
+  - ID: 128015
+    Group: "AG_BATTLE"
+    Name: "Master of Escape"
+    Target:
+      - MobID: 2319
+        Count: 10
+    Score: 20
+  - ID: 128016
+    Group: "AG_BATTLE"
+    Name: "Immortal Hunter"
+    Target:
+      - MobID: 2319
+        Count: 50
+    Score: 50
+  - ID: 128017
+    Group: "AG_BATTLE"
+    Name: "Stood up and overcame despair"
+    #Target:
+    #  - MobID: 3097
+    #    Count: 1
+    Score: 10
+  - ID: 128018
+    Group: "AG_BATTLE"
+    Name: "Ember of Hope"
+    #Target:
+    #  - MobID: 3097
+    #    Count: 10
+    Score: 10
+  - ID: 128019
+    Group: "AG_BATTLE"
+    Name: "Pouring Aurora"
+    #Target:
+    #  - MobID: 3097
+    #    Count: 25
+    Score: 20
+  - ID: 128020
+    Group: "AG_BATTLE"
+    Name: "Who is desperate? I am hopeless!"
+    #Target:
+    #  - MobID: 3097
+    #    Count: 50
+    Score: 30
+  - ID: 128021
+    Group: "AG_BATTLE"
+    Name: "I know god will save the world"
+    #Target:
+    #  - MobID: 3097
+    #    Count: 100
+    Score: 50
+  - ID: 128022
+    Group: "AG_BATTLE"
+    Name: "There was mercy in Morocc army"
+    #Target:
+    #  - MobID: 3000
+    #  Count: 1
+    Score: 10
+  - ID: 128023
+    Group: "AG_BATTLE"
+    Name: "There was fear in Morocc army"
+    #Target:
+    #  - MobID: 3000
+    #    Count: 10
+    Score: 20
+  - ID: 128024
+    Group: "AG_BATTLE"
+    Name: "Guard of weak army"
+    #Target:
+    #  - MobID: 3000
+    #    Count: 50
+    Score: 50
+  - ID: 128025
+    Group: "AG_BATTLE"
+    Name: "Audience with the queen"
+    #Target:
+    #  - MobID: 2529
+    #    Count: 1
+    Score: 10
+  - ID: 128026
+    Group: "AG_BATTLE"
+    Name: "Warm earth"
+    #Target:
+    #  - MobID: 2533
+    #    Count: 1
+    Score: 10
+  - ID: 128027
+    Group: "AG_BATTLE"
+    Name: "Water is very good exactly"
+    #Target:
+    #  - MobID: 2534
+    #    Count: 1
+    Score: 10
+  - ID: 128028
+    Group: "AG_BATTLE"
+    Name: "Pleasant breeze"
+    #Target:
+    #  - MobID: 2535
+    #    Count: 1
+    Score: 10
+  - ID: 128029
+    Group: "AG_BATTLE"
+    Name: "Visitor of old castle"
+    Target:
+      - MobID: 2476
+        Count: 1
+    Score: 10
+  - ID: 128030
+    Group: "AG_BATTLE"
+    Name: "Lord of old castle"
+    Target:
+      - MobID: 2476
+        Count: 10
+    Score: 20
+  - ID: 128031
+    Group: "AG_BATTLE"
+    Name: "Conqueror of old castle"
+    Target:
+      - MobID: 2476
+        Count: 50
+    Score: 50
+  - ID: 128032
+    Group: "AG_BATTLE"
+    Name: "Haggard sucker"
+    #Target:
+    #  - MobID: 3150
+    #    Count: 1
+    Score: 10
+  - ID: 128033
+    Group: "AG_BATTLE"
+    Name: "Hope of the Knight"
+    #Target:
+    #  - MobID: 3150
+    #    Count: 10
+    Score: 20
+  - ID: 128034
+    Group: "AG_BATTLE"
+    Name: "Guardian of the Dawn"
+    #Target:
+    #  - MobID: 3150
+    #    Count: 50
+    Score: 50
+  - ID: 128035
+    Group: "AG_BATTLE"
+    Name: "Time Traveler"
+    #Target:
+    #  - MobID: 3190
+    #    Count: 1
+    Score: 10
+  - ID: 128036
+    Group: "AG_BATTLE"
+    Name: "Restore ancient relic"
+    #Target:
+    #  - MobID: 3190
+    #    Count: 10
+    Score: 20
+  - ID: 128037
+    Group: "AG_BATTLE"
+    Name: "Master of relic transport"
+    #Target:
+    #  - MobID: 3190
+    #    Count: 50
+    Score: 50
+  - ID: 128038
+    Group: "AG_BATTLE"
+    Name: "Show Jailbreak to the captain"
+    #Target:
+    #  - MobID: 3181
+    #    Count: 1
+    Score: 10
+  - ID: 128039
+    Group: "AG_BATTLE"
+    Name: "Show Jailbreak to the weak captain"
+    #Target:
+    #  - MobID: 3188
+    #    Count: 1
+    Score: 10
+  - ID: 128040
+    Group: "AG_BATTLE"
+    Name: "Riot on board"
+    #Target:
+    #  - MobID: 3181
+    #    Count: 1
+    Score: 20
+  - ID: 128041
+    Group: "AG_BATTLE"
+    Name: "Turmoil on board"
+    #Target:
+    #  - MobID: 3181
+    #    Count: 10
+    Score: 20
+  - ID: 128042
+    Group: "AG_BATTLE"
+    Name: "Rebellion on board"
+    #Target:
+    #  - MobID: 3181
+    #    Count: 50
+    Score: 50
+  - ID: 128043
+    Group: "AG_BATTLE"
+    Name: "Revolt of Riot"
+    #Target:
+    #  - MobID: 3188
+    #    Count: 50
+    Score: 50
+  - ID: 128044
+    Group: "AG_BATTLE"
+    Name: "Magic tournament champion"
+    Target:
+      - MobID: 2564
+        Count: 1
+    Score: 10
+  - ID: 128045
+    Group: "AG_BATTLE"
+    Name: "Gladiator of Coliseum"
+    Target:
+      - MobID: 2564
+        Count: 10
+    Score: 20
+  - ID: 128046
+    Group: "AG_BATTLE"
+    Name: "Slayer of Colosseum"
+    Target:
+      - MobID: 2564
+        Count: 50
+    Score: 50
+  - ID: 128047
+    Group: "AG_BATTLE"
+    Name: "Endless Tower challenger"
+    Target:
+      - MobID: 1956
+        Count: 1
+    Score: 10
+  - ID: 128048
+    Group: "AG_BATTLE"
+    Name: "Endless Tower Slayer"
+    Target:
+      - MobID: 1956
+        Count: 10
+    Score: 20
+  - ID: 128049
+    Group: "AG_BATTLE"
+    Name: "Lord of the tower"
+    Target:
+      - MobID: 1956
+        Count: 50
+    Score: 50
+  - ID: 128050
+    Group: "AG_BATTLE"
+    Name: "Novice Exorcist"
+    Target:
+      - MobID: 2327
+        Count: 1
+    Score: 10
+  - ID: 128051
+    Group: "AG_BATTLE"
+    Name: "Experienced Exorcist"
+    Target:
+      - MobID: 2327
+        Count: 10
+    Score: 20
+  - ID: 128052
+    Group: "AG_BATTLE"
+    Name: "Legendary Exorcist"
+    Target:
+      - MobID: 2327
+        Count: 50
+    Score: 50
+  - ID: 129001
+    Group: "AG_ADVENTURE"
+    Name: "Prontera Explorer"
+    Depdendent: [120001, 120002, 120003, 120004, 120005, 120006, 120007, 120008, 120009, 120010]
+    Reward:
+      ItemID: 644
+    Score: 20
+  - ID: 129002
+    Group: "AG_ADVENTURE"
+    Name: "Geffen Explorer"
+    Depdendent: [120011, 120012, 120013, 120014, 120015, 120016, 120017]
+    Reward:
+      ItemID: 644
+    Score: 20
+  - ID: 129003
+    Group: "AG_ADVENTURE"
+    Name: "Sograt Desert Explorer"
+    Depdendent: [120018, 120019, 120020, 120021, 120022, 120023]
+    Reward:
+      ItemID: 644
+    Score: 20
+  - ID: 129004
+    Group: "AG_ADVENTURE"
+    Name: "Payon Explorer"
+    Depdendent: [120024, 120025, 120026, 120027, 120028, 120029, 120030, 120031]
+    Reward:
+      ItemID: 644
+    Score: 20
+  - ID: 129005
+    Group: "AG_ADVENTURE"
+    Name: "North Mjolnir Explorer"
+    Depdendent: [120032, 120033, 120034, 120035, 120036]
+    Reward:
+      ItemID: 644
+    Score: 20
+  - ID: 129006
+    Group: "AG_ADVENTURE"
+    Name: "South Mjolnir Explorer"
+    Depdendent: [120037, 120038, 120039, 120040, 120041, 120042, 120043]
+    Reward:
+      ItemID: 644
+    Score: 20
+  - ID: 129007
+    Group: "AG_ADVENTURE"
+    Name: "Comodo Explorer"
+    Depdendent: [120044, 120045, 120046, 120047, 120048, 120049, 120050, 120051]
+    Reward:
+      ItemID: 644
+    Score: 20
+  - ID: 129008
+    Group: "AG_ADVENTURE"
+    Name: "Rune Midgard Explorer"
+    Depdendent: [129001, 129002, 129003, 129004, 129005, 129006, 129007]
+    Reward:
+      ItemID: 617
+    Score: 50
+  - ID: 129009
+    Group: "AG_ADVENTURE"
+    Name: "Yuno Explorer"
+    Depdendent: [120052, 120053, 120054, 120055, 120056, 120057, 120058, 120059, 120060, 120061]
+    Reward:
+      ItemID: 644
+    Score: 20
+  - ID: 129010
+    Group: "AG_ADVENTURE"
+    Name: "Hugel Explorer"
+    Depdendent: [120062, 120063, 120064, 120065, 120066]
+    Reward:
+      ItemID: 644
+    Score: 20
+  - ID: 129011
+    Group: "AG_ADVENTURE"
+    Name: "Einbroch Explorer"
+    Depdendent: [120067, 120068, 120069, 120070, 120071, 120072, 120073, 120074]
+    Reward:
+      ItemID: 644
+    Score: 20
+  - ID: 129012
+    Group: "AG_ADVENTURE"
+    Name: "Lighthalzen Explorer"
+    Depdendent: [120075, 120076, 120077]
+    Reward:
+      ItemID: 644
+    Score: 20
+  - ID: 129013
+    Group: "AG_ADVENTURE"
+    Name: "Schwarzwald Explorer"
+    Depdendent: [129009, 129010, 129011, 129012]
+    Reward:
+      ItemID: 617
+    Score: 50
+  - ID: 129014
+    Group: "AG_ADVENTURE"
+    Name: "Rachel Explorer"
+    Depdendent: [120078, 120079, 120080, 120081, 120082, 120083, 120084]
+    Reward:
+      ItemID: 644
+    Score: 20
+  - ID: 129015
+    Group: "AG_ADVENTURE"
+    Name: "Veins Explorer"
+    Depdendent: [120085, 120086, 120087, 120088, 120089]
+    Reward:
+      ItemID: 644
+    Score: 20
+  - ID: 129016
+    Group: "AG_ADVENTURE"
+    Name: "Arunafeltz Explorer"
+    Depdendent: [129014, 129015]
+    Reward:
+      ItemID: 617
+    Score: 50
+  - ID: 129017
+    Group: "AG_ADVENTURE"
+    Name: "Laphine Explorer"
+    Depdendent: [120090, 120091, 120092, 120093, 120094, 120095]
+    Reward:
+      ItemID: 644
+    Score: 20
+  - ID: 129018
+    Group: "AG_ADVENTURE"
+    Name: "Manuk Explorer"
+    Depdendent: [120096, 120097, 120098, 120099, 120100]
+    Reward:
+      ItemID: 644
+    Score: 20
+  - ID: 129019
+    Group: "AG_ADVENTURE"
+    Name: "Eclage Explorer"
+    Depdendent: [129017, 129018]
+    Reward:
+      ItemID: 617
+    Score: 50
+  - ID: 129020
+    Group: "AG_ADVENTURE"
+    Name: "Localizing fields explorer"
+    Depdendent: [120101, 120102, 120103, 120104, 120105, 120106, 120107, 120108, 120109]
+    Reward:
+      ItemID: 617
+    Score: 50
+  - ID: 130000 # Talk to Prince NPC (npc/quests/quests_morocc.txt L5288)
+    Group: "AG_CHATTING"
+    Name: "Socialite debut"
+    Reward:
+      TitleID: 1034
+    Score: 10
+  - ID: 170000
+    Group: "AG_HEAR"
+    Name: "Song chamber is not an accident"
+    Score: 10
+  - ID: 190000
+    Group: "AG_CHATTING"
+    Name: "Alliance workers of merchant city"
+    Score: 50
+  - ID: 200000
+    Group: "AG_GOAL_LEVEL"
+    Name: "Acquire the first aura!"
+    Condition: " BaseLevel >= 99 "
+    Reward:
+      ItemID: 12549
+      Script: " specialeffect2 EF_BLESSING; sc_start SC_BLESSING,30000,10; "
+      TitleID: 1000
+    Score: 50
+  - ID: 200001
+    Group: "AG_GOAL_LEVEL"
+    Name: "Acquire the second aura!"
+    Condition: " BaseLevel >= 150 "
+    Depdendent: [200000]
+    Reward:
+      ItemID: 5364
+      Script: " specialeffect2 EF_BLESSING; sc_start SC_BLESSING,30000,10; "
+      TitleID: 1001
+    Score: 60
+  - ID: 200002
+    Group: "AG_GOAL_LEVEL"
+    Name: "Acquire the third aura!"
+    Condition: " BaseLevel >= 175 "
+    Depdendent: [200001]
+    Reward:
+      ItemID: 18880
+      Script: " specialeffect2 EF_BLESSING; sc_start SC_BLESSING,30000,10; "
+      TitleID: 1002
+    Score: 70
+  - ID: 200003
+    Group: "AG_GOAL_LEVEL"
+    Name: "Master Job level!"
+    Condition: " JobLevel >= 50 "
+    Reward:
+      ItemID: 617
+      Script: " specialeffect2 EF_BLESSING; sc_start SC_BLESSING,30000,10; "
+      TitleID: 1003
+    Score: 30
+  - ID: 200004
+    Group: "AG_GOAL_LEVEL"
+    Name: "Grandmaster Job level!"
+    Condition: " JobLevel >= 70 "
+    Depdendent: [200003]
+    Reward:
+      ItemID: 12817
+      Script: " specialeffect2 EF_BLESSING; sc_start SC_BLESSING,30000,10; "
+      TitleID: 1004
+    Score: 50
+  - ID: 200005
+    Group: "AG_JOB_CHANGE"
+    Name: "Official Adventurer"
+    Condition: " Class >= JOB_SWORDMAN && Class <= JOB_THIEF "
+    Reward:
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+    Score: 10
+  - ID: 200006
+    Group: "AG_JOB_CHANGE"
+    Name: "First step of job change!"
+    Condition: " Class >= JOB_SWORDMAN && Class <= JOB_THIEF "
+    Reward:
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+    Score: 20
+  - ID: 200007
+    Group: "AG_JOB_CHANGE"
+    Name: "Veteran Adventurer! (1)"
+    Condition: " Class >= JOB_KNIGHT && Class <= JOB_ASSASSIN "
+    Reward:
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+    Score: 25
+  - ID: 200008
+    Group: "AG_JOB_CHANGE"
+    Name: "Veteran Adventurer! (2)"
+    Condition: " Class >= JOB_CRUSADER && Class <= JOB_DANCER "
+    Reward:
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+    Score: 25
+  - ID: 200009
+    Group: "AG_JOB_CHANGE"
+    Name: "Warrior (1)"
+    Condition: " Class >= JOB_LORD_KNIGHT && Class <= JOB_ASSASSIN_CROSS "
+    Reward:
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+    Score: 30
+  - ID: 200010
+    Group: "AG_JOB_CHANGE"
+    Name: "Warrior (2)"
+    Condition: " Class >= JOB_PALADIN && Class <= JOB_GYPSY "
+    Reward:
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+    Score: 30
+  - ID: 200011
+    Group: "AG_JOB_CHANGE"
+    Name: "Elite Adventurer! (1)"
+    Condition: " Class >= JOB_RUNE_KNIGHT && Class <= JOB_GUILLOTINE_CROSS "
+    Reward:
+      ItemID: 16483
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+    Score: 50
+  - ID: 200012
+    Group: "AG_JOB_CHANGE"
+    Name: "Transcendentaler! (1)"
+    Condition: " Class >= JOB_RUNE_KNIGHT_T && Class <= JOB_GUILLOTINE_CROSS_T "
+    Reward:
+      ItemID: 16483
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+    Score: 60
+  - ID: 200013
+    Group: "AG_JOB_CHANGE"
+    Name: "Elite Adventurer! (2)"
+    Condition: " Class >= JOB_ROYAL_GUARD && Class <= JOB_SHADOW_CHASER "
+    Reward:
+      ItemID: 16483
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+    Score: 50
+  - ID: 200014
+    Group: "AG_JOB_CHANGE"
+    Name: "Transcendentaler! (2)"
+    Condition: " Class >= JOB_ROYAL_GUARD_T && Class <= JOB_SHADOW_CHASER_T "
+    Reward:
+      ItemID: 16483
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+    Score: 60
+  - ID: 200015
+    Group: "AG_JOB_CHANGE"
+    Name: "The way of exceptional character"
+    Condition: " Class == JOB_SUPER_NOVICE || Class == JOB_GUNSLINGER || Class == JOB_NINJA || Class == JOB_TAEKWON "
+    Reward:
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+    Score: 10
+  - ID: 200016
+    Group: "AG_JOB_CHANGE"
+    Name: "This is My way!"
+    Condition: " Class == JOB_STAR_GLADIATOR || Class == JOB_SOUL_LINKER || Class == JOB_KAGEROU || Class == JOB_OBORO || Class == JOB_REBELLION "
+    Reward:
+      ItemID: 16483
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+    Score: 20
+  - ID: 200017
+    Group: "AG_GOAL_STATUS"
+    Name: "Bearish Power!"
+    Condition: " bStr >= 90 "
+    Score: 10
+  - ID: 200018
+    Group: "AG_GOAL_STATUS"
+    Name: "Overflowing Magic!"
+    Condition: " bInt >= 90 "
+    Score: 10
+  - ID: 200019
+    Group: "AG_GOAL_STATUS"
+    Name: "Healthy Body and Mental Health!"
+    Condition: " bVit >= 90 "
+    Score: 10
+  - ID: 200020
+    Group: "AG_GOAL_STATUS"
+    Name: "Speed of Light"
+    Condition: " bAgi >= 90 "
+    Score: 10
+  - ID: 200021
+    Group: "AG_GOAL_STATUS"
+    Name: "Hawk Eyes"
+    Condition: " bDex >= 90 "
+    Score: 10
+  - ID: 200022
+    Group: "AG_GOAL_STATUS"
+    Name: "Maximum Luck"
+    Condition: " bLuk >= 90 "
+    Score: 10
+  - ID: 200023
+    Group: "AG_GOAL_STATUS"
+    Name: "Dragonlike Power!"
+    Condition: " bStr >= 125 "
+    Reward:
+      Script: " sc_start SC_GIANTGROWTH,180000,1; "
+    Score: 20
+  - ID: 200024
+    Group: "AG_GOAL_STATUS"
+    Name: "Magic Insanity"
+    Condition: " bInt >= 125 "
+    Reward:
+      Script: " specialeffect2 EF_HASTEUP; bonus_script \"{ bonus2 bHPLossRate,100,10000; bonus bBaseAtk,20; bonus bAspdRate,25; }\",60,0,0,SI_STEAMPACK; "
+    Score: 20
+  - ID: 200025
+    Group: "AG_GOAL_STATUS"
+    Name: "Rock Alloy"
+    Condition: " bVit >= 125 "
+    Reward:
+      Script: " specialeffect2 EF_HEAL3; sc_start2 SC_S_LIFEPOTION,600000,-5,5; "
+    Score: 20
+  - ID: 200026
+    Group: "AG_GOAL_STATUS"
+    Name: "Speed of Light"
+    Condition: " bAgi >= 125 "
+    Reward:
+      Script: " specialeffect2 EF_STEAL; sc_start SC_INCFLEE2,60000,20; "
+    Score: 20
+  - ID: 200027
+    Group: "AG_GOAL_STATUS"
+    Name: "Falcon's Eyes"
+    Condition: " bDex >= 125 "
+    Reward:
+      Script: " specialeffect2 EF_MAGICALATTHIT; sc_start SC_INCCRI,300000,30; "
+    Score: 20
+  - ID: 200028
+    Group: "AG_GOAL_STATUS"
+    Name: "Lucky Fever"
+    Condition: " bLuk >= 125 "
+    Reward:
+      Script: " specialeffect2 EF_GLORIA; sc_start SC_GLORIA,15000,0; "
+    Score: 20
+  - ID: 200029
+    Group: "AG_GOAL_STATUS"
+    Name: "Incarnation of Love and Hate"
+    Condition: " BaseLevel == 99 && Class == JOB_NOVICE "
+    Reward:
+      ItemID: 16483
+      Script: " specialeffect2 EF_BLESSING; sc_start SC_BLESSING,30000,10; "
+    Score: 30
+  - ID: 200030
+    Group: "AG_GOAL_STATUS"
+    Name: "I really love it!"
+    Condition: " BaseLevel == 99 && (Class >= JOB_SWORDMAN && Class <= JOB_THIEF) "
+    Reward:
+      ItemID: 16504
+      Script: " specialeffect2 EF_BLESSING; sc_start SC_BLESSING,30000,10; "
+    Score: 30
+  - ID: 200031
+    Group: "AG_JOB_CHANGE"
+    Name: "Reborn in Valhalla!"
+    Condition: " BaseLevel == 99 && Class == JOB_NOVICE_HIGH "
+    Reward:
+      ItemID: 22808
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+    Score: 10
+  - ID: 230100
+    Group: "AG_TAMING"
+    Name: "Poring is Love"
+    Depdendent: [230101, 230102, 230103, 230104]
+    Reward:
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+      TitleID: 1025
+    Score: 50
+  - ID: 230110
+    Group: "AG_TAMING"
+    Name: "Entomologist"
+    Depdendent: [230111, 230112, 230113, 230114, 230115, 230116]
+    Reward:
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+      TitleID: 1026
+    Score: 50
+  - ID: 230120
+    Group: "AG_TAMING"
+    Name: "Animals are also our friend"
+    Depdendent: [230121, 230122, 230123, 230124, 230125, 230126, 230127, 230128]
+    Reward:
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+      TitleID: 1027
+    Score: 50
+  - ID: 230140
+    Group: "AG_TAMING"
+    Name: "Monster Girls Unite!!"
+    Depdendent: [230141, 230142, 230143, 230144, 230145, 230146, 230147]
+    Reward:
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+      TitleID: 1029
+    Score: 50
+  - ID: 230101
+    Group: "AG_TAMING"
+    Name: "Poring - taming"
+    Target:
+      - MobID: 1002
+        Count: 1
+    Score: 10
+  - ID: 230102
+    Group: "AG_TAMING"
+    Name: "Drops - taming"
+    Target:
+      - MobID: 1113
+        Count: 1
+    Score: 10
+  - ID: 230103
+    Group: "AG_TAMING"
+    Name: "Poporing - taming"
+    Target:
+      - MobID: 1031
+        Count: 1
+    Score: 10
+  - ID: 230104
+    Group: "AG_TAMING"
+    Name: "Novice Poring - taming"
+    Target:
+      - MobID: 2398
+        Count: 1
+    Score: 10
+  - ID: 230111
+    Group: "AG_TAMING"
+    Name: "Chonchon - taming"
+    Target:
+      - MobID: 1011
+        Count: 1
+    Score: 10
+  - ID: 230112
+    Group: "AG_TAMING"
+    Name: "Steel Chonchon - taming"
+    Target:
+      - MobID: 1042
+        Count: 1
+    Score: 10
+  - ID: 230113
+    Group: "AG_TAMING"
+    Name: "Hunter Fly - taming"
+    Target:
+      - MobID: 1035
+        Count: 1
+    Score: 10
+  - ID: 230114
+    Group: "AG_TAMING"
+    Name: "Rocker - taming"
+    Target:
+      - MobID: 1052
+        Count: 1
+    Score: 10
+  - ID: 230115
+    Group: "AG_TAMING"
+    Name: "Spore - taming"
+    Target:
+      - MobID: 1014
+        Count: 1
+    Score: 10
+  - ID: 230116
+    Group: "AG_TAMING"
+    Name: "Poison Spore - taming"
+    Target:
+      - MobID: 1077
+        Count: 1
+    Score: 10
+  - ID: 230121
+    Group: "AG_TAMING"
+    Name: "Lunatic - taming"
+    Target:
+      - MobID: 1063
+        Count: 1
+    Score: 10
+  - ID: 230122
+    Group: "AG_TAMING"
+    Name: "Picky - taming"
+    Target:
+      - MobID: 1049
+        Count: 1
+    Score: 10
+  - ID: 230123
+    Group: "AG_TAMING"
+    Name: "Savage Bebe - taming"
+    Target:
+      - MobID: 1167
+        Count: 1
+    Score: 10
+  - ID: 230124
+    Group: "AG_TAMING"
+    Name: "Baby Desert Wolf - taming"
+    Target:
+      - MobID: 1107
+        Count: 1
+    Score: 10
+  - ID: 230125
+    Group: "AG_TAMING"
+    Name: "Smokie - taming"
+    Target:
+      - MobID: 1056
+        Count: 1
+    Score: 10
+  - ID: 230126
+    Group: "AG_TAMING"
+    Name: "Yoyo - taming"
+    Target:
+      - MobID: 1057
+        Count: 1
+    Score: 10
+  - ID: 230127
+    Group: "AG_TAMING"
+    Name: "Peco Peco - taming"
+    Target:
+      - MobID: 1019
+        Count: 1
+    Score: 10
+  - ID: 230128
+    Group: "AG_TAMING"
+    Name: "Petite - taming"
+    Target:
+      - MobID: 1155
+        Count: 1
+    Score: 10
+  - ID: 230141
+    Group: "AG_TAMING"
+    Name: "Munak - taming"
+    Target:
+      - MobID: 1026
+        Count: 1
+    Score: 10
+  - ID: 230142
+    Group: "AG_TAMING"
+    Name: "Isis - taming"
+    Target:
+      - MobID: 1029
+        Count: 1
+    Score: 10
+  - ID: 230143
+    Group: "AG_TAMING"
+    Name: "Sohee - taming"
+    Target:
+      - MobID: 1170
+        Count: 1
+    Score: 10
+  - ID: 230144
+    Group: "AG_TAMING"
+    Name: "Zherlthsh - taming"
+    Target:
+      - MobID: 1200
+        Count: 1
+    Score: 10
+  - ID: 230145
+    Group: "AG_TAMING"
+    Name: "Alice - taming"
+    Target:
+      - MobID: 1275
+        Count: 1
+    Score: 10
+  - ID: 230146
+    Group: "AG_TAMING"
+    Name: "Succubus - taming"
+    Target:
+      - MobID: 1370
+        Count: 1
+    Score: 10
+  - ID: 230147
+    Group: "AG_TAMING"
+    Name: "Loli Ruri - taming"
+    Target:
+      - MobID: 1505
+        Count: 1
+    Score: 10
+  - ID: 220000
+    Group: "AG_CHATTING_CREATE"
+    Name: "Community begin"
+    Condition: " true "
+    Score: 10
+  - ID: 220001
+    Group: "AG_CHATTING_DYING"
+    Name: "A mouth only moment"
+    Condition: " true "
+    Score: 10
+  - ID: 220002
+    Group: "AG_CHATTING_COUNT"
+    Name: "Admiring the chatter"
+    Condition: " ARG0 == 20 "
+    Score: 10
+  - ID: 220003
+    Group: "AG_ADD_FRIEND"
+    Name: "My friend's friend~"
+    Condition: " ARG0 >= 1 "
+    Score: 10
+  - ID: 220004
+    Group: "AG_ADD_FRIEND"
+    Name: "A competition of popularity"
+    Condition: " ARG0 >= 10 "
+    Score: 10
+  - ID: 220005
+    Group: "AG_PARTY"
+    Name: "Let's Party~"
+    Condition: " true "
+    Score: 10
+  - ID: 220006
+    Group: "AG_MARRY"
+    Name: "Married with who..?"
+    Condition: " true "
+    Reward:
+      TitleID: 1022
+    Score: 20
+  - ID: 220007
+    Group: "AG_BABY"
+    Name: "Can you grow?"
+    Condition: " ARG0 == 1 "
+    Reward:
+      TitleID: 1032
+    Score: 20
+  - ID: 220008
+    Group: "AG_BABY"
+    Name: "Being a parent"
+    Condition: " ARG0 == 2 "
+    Reward:
+      TitleID: 1033
+    Score: 20
+  - ID: 220009
+    Group: "AG_SPEND_ZENY"
+    Name: "Activating the market economy (1)"
+    Condition: " ARG0 >= 10000 "
+    Target:
+      Count: 10000
+    Score: 10
+  - ID: 220010
+    Group: "AG_SPEND_ZENY"
+    Name: "Activating the market economy (2)"
+    Condition: " ARG0 >= 100000 "
+    Target:
+      Count: 100000
+    Score: 15
+  - ID: 220011
+    Group: "AG_SPEND_ZENY"
+    Name: "Activating the market economy (3)"
+    Condition: " ARG0 >= 500000 "
+    Target:
+      Count: 500000
+    Score: 20
+  - ID: 220012
+    Group: "AG_SPEND_ZENY"
+    Name: "Activating the market economy (4)"
+    Condition: " ARG0 >= 1000000 "
+    Target:
+      Count: 1000000
+    Score: 30
+  - ID: 220013
+    Group: "AG_SPEND_ZENY"
+    Name: "Activating the market economy (5)"
+    Condition: " ARG0 >= 5000000 "
+    Target:
+      Count: 5000000
+    Score: 50
+  - ID: 220014
+    Group: "AG_ENCHANT_SUCCESS"
+    Name: "I can't quit from refining! (1)"
+    Condition: " ARG0 == 1 && ARG1 >= 7 "
+    Score: 10
+  - ID: 220015
+    Group: "AG_ENCHANT_SUCCESS"
+    Name: "I can't quit from refining! (2)"
+    Condition: " ARG0 == 1 && ARG1 >= 12 "
+    Score: 15
+  - ID: 220016
+    Group: "AG_ENCHANT_SUCCESS"
+    Name: "I can't quit from refining! (3)"
+    Condition: " ARG0 == 2 && ARG1 >= 7 "
+    Score: 10
+  - ID: 220017
+    Group: "AG_ENCHANT_SUCCESS"
+    Name: "I can't quit from refining! (4)"
+    Condition: " ARG0 == 2 && ARG1 >= 12 "
+    Score: 15
+  - ID: 220018
+    Group: "AG_ENCHANT_SUCCESS"
+    Name: "I can't quit from refining! (5)"
+    Condition: " ARG0 == 3 && ARG1 >= 7 "
+    Score: 15
+  - ID: 220019
+    Group: "AG_ENCHANT_SUCCESS"
+    Name: "I can't quit from refining! (6)"
+    Condition: " ARG0 == 3 && ARG1 >= 12 "
+    Score: 20
+  - ID: 220020
+    Group: "AG_ENCHANT_SUCCESS"
+    Name: "I can't quit from refining! (7)"
+    Condition: " ARG0 == 4 && ARG1 >= 7 "
+    Score: 20
+  - ID: 220021
+    Group: "AG_ENCHANT_SUCCESS"
+    Name: "I can't quit from refining! (8)"
+    Condition: " ARG0 == 4 && ARG1 >= 12 "
+    Score: 30
+  - ID: 220022
+    Group: "AG_ENCHANT_FAIL"
+    Name: "Human's greed has no ending.."
+    Condition: " true "
+    Score: 10
+  - ID: 220023
+    Group: "AG_GET_ITEM"
+    Name: "I found it! (1)"
+    Condition: " ARG0 >= 100 "
+    Score: 10
+  - ID: 220024
+    Group: "AG_GET_ITEM"
+    Name: "I found it! (2)"
+    Condition: " ARG0 >= 1000 "
+    Score: 10
+  - ID: 220025
+    Group: "AG_GET_ITEM"
+    Name: "I found it! (3)"
+    Condition: " ARG0 >= 5000 "
+    Score: 15
+  - ID: 220026
+    Group: "AG_GET_ITEM"
+    Name: "I found it! (4)"
+    Condition: " ARG0 >= 10000 "
+    Score: 15
+  - ID: 220027
+    Group: "AG_GET_ITEM"
+    Name: "I found it! (5)"
+    Condition: " ARG0 >= 50000 "
+    Score: 20
+  - ID: 220028
+    Group: "AG_GET_ITEM"
+    Name: "I found it! (6)"
+    Condition: " ARG0 >= 100000 "
+    Score: 20
+  - ID: 220029
+    Group: "AG_GET_ITEM"
+    Name: "I found it! (7)"
+    Condition: " ARG0 >= 150000 "
+    Score: 30
+  - ID: 220030
+    Group: "AG_GET_ZENY"
+    Name: "Rich King (1)"
+    Condition: " ARG0 >= 10000 "
+    Score: 10
+  - ID: 220031
+    Group: "AG_GET_ZENY"
+    Name: "Rich King (2)"
+    Condition: " ARG0 >= 100000 "
+    Score: 15
+  - ID: 220032
+    Group: "AG_GET_ZENY"
+    Name: "Rich King (3)"
+    Condition: " ARG0 >= 1000000 "
+    Score: 20
+  - ID: 220033
+    Group: "AG_GET_ZENY"
+    Name: "Rich King (4)"
+    Condition: " ARG0 >= 10000000 "
+    Score: 25
+  - ID: 220034
+    Group: "AG_GET_ZENY"
+    Name: "Rich King (5)"
+    Condition: " ARG0 >= 100000000 "
+    Score: 30
+  - ID: 220035
+    Group: "AG_GET_ZENY"
+    Name: "Rich King (6)"
+    Condition: " ARG0 >= 1000000000 "
+    Score: 40
+  - ID: 230200
+    Group: "AG_BATTLE"
+    Name: "Poring seeker"
+    Depdendent: [230201, 230202, 230203]
+    Score: 10
+  - ID: 230201
+    Group: "AG_BATTLE"
+    Name: "Exploring Poring's life (1)"
+    Target:
+      - MobID: 1002
+        Count: 10
+      - MobID: 2398
+        Count: 10
+      - MobID: 1113
+        Count: 10
+      - MobID: 1031
+        Count: 10
+      - MobID: 1242
+        Count: 10
+    Score: 10
+  - ID: 230202
+    Group: "AG_BATTLE"
+    Name: "Exploring Poring's life (2)"
+    Target:
+      - MobID: 1090
+        Count: 1
+      - MobID: 1582
+        Count: 1
+      - MobID: 1096
+        Count: 1
+      - MobID: 1388
+        Count: 1
+      - MobID: 1120
+        Count: 1
+    Score: 15
+  - ID: 230203
+    Group: "AG_BATTLE"
+    Name: "Exploring Poring's life (3)"
+    Target:
+      - MobID: 1613
+        Count: 5
+      - MobID: 1977
+        Count: 5
+      - MobID: 1836
+        Count: 5
+    Score: 20
+  - ID: 240000
+    Group: "AG_GOAL_LEVEL"
+    Name: "Complete challenges after first introduction"
+    Score: 10
+  - ID: 240001
+    Group: "AG_GOAL_ACHIEVE"
+    Name: "Reaching Level 1"
+    Reward:
+      ItemID: 644
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+    Score: 10
+  - ID: 240002
+    Group: "AG_GOAL_ACHIEVE"
+    Name: "Reaching Level 2"
+    Depdendent: [240001]
+    Reward:
+      ItemID: 644
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+    Score: 10
+  - ID: 240003
+    Group: "AG_GOAL_ACHIEVE"
+    Name: "Reaching Level 3"
+    Depdendent: [240002]
+    Reward:
+      ItemID: 644
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+    Score: 10
+  - ID: 240004
+    Group: "AG_GOAL_ACHIEVE"
+    Name: "Reaching Level 4"
+    Depdendent: [240003]
+    Reward:
+      ItemID: 644
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+    Score: 10
+  - ID: 240005
+    Group: "AG_GOAL_ACHIEVE"
+    Name: "Reaching Level 5"
+    Depdendent: [240004]
+    Reward:
+      ItemID: 644
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+    Score: 10
+  - ID: 240006
+    Group: "AG_GOAL_ACHIEVE"
+    Name: "Reaching Level 6"
+    Depdendent: [240005]
+    Reward:
+      ItemID: 644
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+    Score: 10
+  - ID: 240007
+    Group: "AG_GOAL_ACHIEVE"
+    Name: "Reaching Level 7"
+    Depdendent: [240006]
+    Reward:
+      ItemID: 644
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+    Score: 10
+  - ID: 240008
+    Group: "AG_GOAL_ACHIEVE"
+    Name: "Reaching Level 8"
+    Depdendent: [240007]
+    Reward:
+      ItemID: 644
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+    Score: 10
+  - ID: 240009
+    Group: "AG_GOAL_ACHIEVE"
+    Name: "Reaching Level 9"
+    Depdendent: [240008]
+    Reward:
+      ItemID: 644
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+    Score: 10
+  - ID: 240010
+    Group: "AG_GOAL_ACHIEVE"
+    Name: "Reaching Level 10"
+    Depdendent: [240009]
+    Reward:
+      ItemID: 644
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+      TitleID: 1023
+    Score: 10
+  - ID: 240011
+    Group: "AG_GOAL_ACHIEVE"
+    Name: "Reaching Level 11"
+    Depdendent: [240010]
+    Reward:
+      ItemID: 644
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+    Score: 10
+  - ID: 240012
+    Group: "AG_GOAL_ACHIEVE"
+    Name: "Reaching Level 12"
+    Depdendent: [240011]
+    Reward:
+      ItemID: 644
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+    Score: 10
+  - ID: 240013
+    Group: "AG_GOAL_ACHIEVE"
+    Name: "Reaching Level 13"
+    Depdendent: [240012]
+    Reward:
+      ItemID: 644
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+    Score: 10
+  - ID: 240014
+    Group: "AG_GOAL_ACHIEVE"
+    Name: "Reaching Level 14"
+    Depdendent: [240013]
+    Reward:
+      ItemID: 644
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+    Score: 10
+  - ID: 240015
+    Group: "AG_GOAL_ACHIEVE"
+    Name: "Reaching Level 15"
+    Depdendent: [240014]
+    Reward:
+      ItemID: 644
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+    Score: 10
+  - ID: 240016
+    Group: "AG_GOAL_ACHIEVE"
+    Name: "Reaching Level 16"
+    Depdendent: [240015]
+    Reward:
+      ItemID: 644
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+    Score: 10
+  - ID: 240017
+    Group: "AG_GOAL_ACHIEVE"
+    Name: "Reaching Level 17"
+    Depdendent: [240016]
+    Reward:
+      ItemID: 644
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+    Score: 10
+  - ID: 240018
+    Group: "AG_GOAL_ACHIEVE"
+    Name: "Reaching Level 18"
+    Depdendent: [240017]
+    Reward:
+      ItemID: 644
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+    Score: 10
+  - ID: 240019
+    Group: "AG_GOAL_ACHIEVE"
+    Name: "Reaching Level 19"
+    Depdendent: [240018]
+    Reward:
+      ItemID: 644
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+    Score: 10
+  - ID: 240020
+    Group: "AG_GOAL_ACHIEVE"
+    Name: "Reaching Level 20"
+    Depdendent: [240019]
+    Reward:
+      ItemID: 644
+      Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; "
+      TitleID: 1024
+    Score: 10
+  - ID: 220036
+    Group: "AG_EAT"
+    Name: "The beginning of outdoor"
+    Score: 20
+  - ID: 220037
+    Group: "AG_EAT"
+    Name: "The first step becoming a chef"
+    Score: 20

+ 1 - 1
db/re/item_db.txt

@@ -11078,7 +11078,7 @@
 22873,Sealed_Beelzebub_Scroll_II,Sealed Beelzebub Scroll II,2,10,,10,,,,,0xFFFFFFFF,63,2,,,,,,{ getitem callfunc("F_Rand",22875,6238,6239,6228,6232,24231,24232,17474,6635),1; },{},{}
 22874,Sealed_Beelzebub_Card_Album,Sealed Beelzebub Card Album,2,10,,50,,,,,0xFFFFFFFF,63,2,,,,,,{/*No Info*/},{},{}
 22875,Sealed_Beelzebub_Card,Sealed Beelzebub Card,6,20,,10,,,,,,,,769,,,,,{ bonus bVariableCastrate,-15; /*Item removed on 2014-12-17*/ },{},{}
-22876,Old_Money_Pocket,Old Money Pocket,3,0,,0,,,,,,,,,,,,,{},{},{}
+22876,Old_Money_Pocket,Old Money Pocket,3,0,,0,,,,,,,,,,,,,{ Zeny += rand(500,550); },{},{}
 22881,Rope_Gallows,Rope Gallows,2,10,,0,,,,,0xFFFFFFFF,63,2,,,,,,{/*Used to catch a Lost Sheep*/},{},{}
 22882,Chocolate_Rice_Cake_Soup,Chocolate Rice Cake Soup,2,10,,0,,,,,0xFFFFFFFF,63,2,,,,,,{ percentheal 10,10; },{},{}
 22883,September_Gift_Box_,September Gift Box,2,10,,100,,,,,0xFFFFFFFF,63,2,,,,,,{/*2 Lucky Eggs*/},{},{}

+ 102 - 0
doc/achievements.txt

@@ -0,0 +1,102 @@
+//===== rAthena Documentation ================================
+//= Achievement Database Structure
+//===== By: ==================================================
+//= rAthena Dev Team
+//===== Last Updated: ========================================
+//= 20170531
+//===== Description: =========================================
+//= Explanation of the achievements_db.yml file and structure.
+//============================================================
+
+---------------------------------------
+
+ID: Unique achievement ID.
+
+---------------------------------------
+
+Group: Achievement group type. Each achievement type calls a specific objective check.
+Valid groups:
+	AG_ADD_FRIEND - Triggered when a player adds a friend.
+	AG_ADVENTURE - Does not trigger automatically. These are triggered by the achievementcomplete script command.
+	AG_BABY - Triggered when a player becomes a baby job.
+	AG_BATTLE - Triggered when a player kills a monster.
+	AG_CHATTING - Unknown.
+	AG_CHATTING_COUNT - Triggered when a player has a chatroom open and others join.
+	AG_CHATTING_CREATE - Triggered when a player creates a chatroom.
+	AG_CHATTING_DYING - Triggered when a player creates a chatroom and dies with it open.
+	AG_EAT - Unknown.
+	AG_GET_ITEM - Triggered when a player gets an item that has a specific sell value.
+	AG_GET_ZENY - Triggered when a player gets a specific amount of zeny at once.
+	AG_GOAL_ACHIEVE - Triggered when a player's achievement rank levels up.
+	AG_GOAL_LEVEL - Triggered when a player's base level or job level changes.
+	AG_GOAL_STATUS - Triggered when a player's base stats changes.
+	AG_HEAR - Unknown.
+	AG_JOB_CHANGE - Triggered when a player's job changes.
+	AG_MARRY - Triggered when two players get married.
+	AG_PARTY - Triggered when a player creates a party.
+	AG_ENCHANT_FAIL - Triggered when a player fails to refine an equipment.
+	AG_ENCHANT_SUCCESS - Triggered when a player successfully refines an equipment.
+	AG_SEE - Unknown.
+	AG_SPEND_ZENY - Triggered when a player spends any amount of zeny on vendors.
+	AG_TAMING - Triggered when a player tames a monster.
+
+---------------------------------------
+
+Name: Achievement name. Not read into source but used for quick look ups.
+
+---------------------------------------
+
+Target: A list of monster ID and count values that the achievement requires.
+		The target count can also be used for achievements that keep a counter while not being related to monster kills.
+		Capped at MAX_ACHIEVEMENT_OBJECTIVES.
+
+Example:
+  // Player must kill 5 Scorpions and 10 Poring.
+  Target:
+    - MobID: 1001
+      Count: 5
+    - MobID: 1002
+      Count: 10
+
+Example 2:
+  // Player must have 100 or more of ARG0 value. Using the count target value is useful for achievements that are increased in increments
+  // and not checked for a total (UI_Type = 1).
+  // IE: In the achievement_list.lub file, UI_Type 0 is displayed as non-incremental while 1 shows a progress bar of completion for the achievement.
+  Condition: " ARG0 >= 100 "
+  Target:
+    Count: 100
+
+---------------------------------------
+
+Condition: A conditional statement that must be met for the achievement to be considered complete. Accepts script constants, player variables, and
+		   ARGX (where X is the argument vector value). The ARGX values are sent from the server to the achievement script engine on special events.
+		   Below are two examples of how the ARGX feature works.
+
+Example:
+	// This function will send 1 argument (ARG0) with a value of i + 1 when a friend is added.
+	achievement_update_objective(f_sd, AG_ADD_FRIEND, 1, i + 1);
+
+Example 2:
+	// This function will send 2 arguments (ARG0 and ARG1) with values of weapon level and refine level, respectively, when an equipment is
+	// successfully refined.
+	achievement_update_objective(sd, AG_REFINE_SUCCESS, 2, sd->inventory_data[i]->wlv, sd->inventory.u.items_inventory[i].refine);
+
+---------------------------------------
+
+Map: A map name that is used for the AG_CHATTING type which increments the counter based on the player's map.
+
+---------------------------------------
+
+Dependent: A list of achievement IDs that need to be completed before this achievement is considered complete.
+
+---------------------------------------
+
+Reward: A list of rewards that are given on completion. All fields are optional.
+  ItemID: Item ID
+  Amount:	Amount of Item ID (default 1)
+  Script: Bonus Script
+  TitleID: Title ID
+
+---------------------------------------
+
+Score: Achievement points that are given on completion.

+ 4 - 1
doc/atcommands.txt

@@ -1325,6 +1325,7 @@ This will also send a packet to clients causing them to close.
 @reloadscript
 @reloadskilldb
 @reloadstatusdb
+@reloadachievementdb
 
 Reloads a database or configuration file.
 
@@ -1335,6 +1336,7 @@ Databases:
 -- questdb: Quest Database
 -- script: NPC Scripts
 -- skilldb: Skill Database
+-- achievementdb: Achievement Database
 
 Configuration files:
 -- atcommand: Atcommand Settings
@@ -1356,7 +1358,8 @@ Affected files:
 -- questdb: quest_db.txt
 -- script: /npc/*.txt, /npc/*.conf
 -- skilldb: skill_db.txt, const.txt, skill_require_db.txt, skill_cast_db.txt, skill_castnodex_db.txt, skill_nocast_db.txt, skill_copyable_db.txt, skill_improvise_db.txt, skill_changematerial_db.txt, skill_nonearnpc_db.txt, skill_damage_db.txt, skill_unit_db.txt, abra_db.txt, create_arrow_db.txt, produce_db.txt, spellbook_db.txt, magicmushroom_db.txt
--- statusdb: attr_fix.txt, size_fix.txt, refine_db.tx
+-- statusdb: attr_fix.txt, size_fix.txt, refine_db.txt
+-- achievementdb: achievement_db.conf
 
 Restriction:
 	- Used from 'atcommand' or 'useatcmd'. For @reload & @reloadscript

+ 21 - 0
doc/packet_interserv.txt

@@ -1189,6 +1189,27 @@ Currently the max packet size is 0xFFFF (see 'WFIFOSET()' in 'src/common/socket.
 	desc:
 		- Requests to the inter server to save a character's quest log entries.
 
+0x3062
+	Type: ZI
+	Structure: <cmd>.W <cid>.L
+	index: 0,2
+	len: 6
+	parameter:
+		- cmd : packet identification (0x3062)
+		- cid
+	desc:
+		- Requests a character's achievement log entries to the inter server.
+
+0x3063
+	Type: ZI
+	Structure: <cmd>.W <len>.W <cid>.L <achievement_log>.?B
+	index: 0,2,4,8
+	len: variable: 8+count
+	parameter:
+		- cmd : packet identification (0x3063)
+	desc:
+		- Requests to the inter server to save a character's achievement log entries.
+
 0x3070
 	Type: ZI
 	Structure: <cmd>.W <size>.W <merc>.?B

+ 66 - 0
doc/script_commands.txt

@@ -1037,6 +1037,7 @@ From here on, we will have the commands sorted as follow:
 12.- Mercenary commands.
 13.- Party commands.
 14.- Channel commands.
+15.- Achievement commands.
 
 =====================
 |1.- Basic commands.|
@@ -9733,3 +9734,68 @@ local map channel.
 Returns 0 on success.
 
 ---------------------------------------
+
+============================
+|15.- Achievement commands.|
+============================
+---------------------------------------
+
+*achievementadd(<achievement id>{,<char id>})
+
+This function will add an achievement to the player's log for the attached
+player or the supplied <char id>. The objective requirements are not ignored
+when using this function.
+Returns true on success and false on failure.
+
+---------------------------------------
+
+*achievementremove(<achievement id>{,<char id>})
+
+This function will remove an achievement from the player's log for the attached
+player or the supplied <char id>.
+Returns true on success and false on failure.
+
+---------------------------------------
+
+*achievementinfo(<achievement id>,<type>{,<char id>})
+
+This function will return the specified <type> value for an achievement of the
+attached player or the supplied <char id>. If the player doesn't have the
+achievement active (no progress has been made), if the achievement doesn't
+exist -1 will be returned, or -2 will be returned on any other error such as
+an invalid <type>.
+
+Valid types:
+- ACHIEVEINFO_COUNT1
+- ACHIEVEINFO_COUNT2
+- ACHIEVEINFO_COUNT3
+- ACHIEVEINFO_COUNT4
+- ACHIEVEINFO_COUNT5
+- ACHIEVEINFO_COUNT6
+- ACHIEVEINFO_COUNT7
+- ACHIEVEINFO_COUNT8
+- ACHIEVEINFO_COUNT9
+- ACHIEVEINFO_COUNT10
+- ACHIEVEINFO_COMPLETE
+- ACHIEVEINFO_COMPLETEDATE
+- ACHIEVEINFO_GOTREWARD
+- ACHIEVEINFO_LEVEL (<achievement id> is useless for this)
+- ACHIEVEINFO_SCORE (<achievement id> is useless for this)
+
+---------------------------------------
+
+*achievementcomplete(<achievement id>{,<char id>})
+
+This function will complete an achievement for the attached player or the supplied
+<char id>. The objective requirements are ignored when using this function.
+Returns true on success and false on failure.
+
+---------------------------------------
+
+*achievementexists(<achievement id>{,<char id>});
+
+This function will return if the achievement exists on the player or the supplied
+<char id>.
+Returns true on success and false on failure.
+
+---------------------------------------

+ 278 - 0
npc/re/other/achievements.txt

@@ -0,0 +1,278 @@
+//===== rAthena Script =======================================
+//= Adventure Achievements
+//===== Description: =========================================
+//= Spawns Adventure group type treasure chests.
+//===== Changelogs: ==========================================
+//= 1.0 Initial release. [Aleos]
+//============================================================
+
+-	script	ach_treasure#core	-1,{
+	if (strnpcinfo(3) == "")
+		end;
+	achievementcomplete(atoi(strnpcinfo(3)));
+	classchange HIDDEN_WARP_NPC;
+	initnpctimer;
+	end;
+
+OnTouch:
+	if (getnpctimer(1)) // Don't trigger touch if the delay timer is active.
+		end;
+	if (achievementexists(atoi(strnpcinfo(3)))) // Don't trigger if the player has already completed.
+		end;
+	classchange 4_TREASURE_BOX,"",bc_self;
+	end;
+
+OnTimer5000:
+	stopnpctimer;
+	end;
+}
+
+//-----------------
+// --- Dungeons ---
+//-----------------
+
+// Abbey Underground
+abbey03,26,72,0	duplicate(ach_treasure#core)	#abb_ach1::120110	HIDDEN_WARP_NPC,5,5
+
+// Abyss Lake
+abyss_03,86,55,0	duplicate(ach_treasure#core)	#aby_ach1::120111	HIDDEN_WARP_NPC,5,5
+
+// Amatsu Dungeon
+ama_dun03,60,163,0	duplicate(ach_treasure#core)	#ama_ach1::120113	HIDDEN_WARP_NPC,5,5
+
+// Ant Hell
+anthell02,253,41,0	duplicate(ach_treasure#core)	#ant_ach1::120114	HIDDEN_WARP_NPC,5,5
+
+// Ayothaya Dungeon
+ayo_dun02,150,256,0	duplicate(ach_treasure#core)	#ayo_ach1::120115	HIDDEN_WARP_NPC,5,5
+
+// Bifrost
+ecl_tdun04,37,37,0	duplicate(ach_treasure#core)	#ecl_ach1::120121	HIDDEN_WARP_NPC,5,5
+
+// Brasilis Dungeon
+bra_dun02,171,121,0	duplicate(ach_treasure#core)	#bra_ach1::120117	HIDDEN_WARP_NPC,5,5
+
+// Byalan Dungeon
+iz_dun05,64,223,0	duplicate(ach_treasure#core)	#iz_ach2::120141	HIDDEN_WARP_NPC,5,5
+
+// Clock Tower
+c_tower4,37,158,0	duplicate(ach_treasure#core)	#ct_ach1::120112	HIDDEN_WARP_NPC,5,5
+alde_dun04,90,107,0	duplicate(ach_treasure#core)	#ct_ach2::120118	HIDDEN_WARP_NPC,5,5
+
+// Coal Mine
+mjo_dun03,76,220,0	duplicate(ach_treasure#core)	#mjo_ach1::120136	HIDDEN_WARP_NPC,5,5
+
+// Comodo Dungeon
+beach_dun3,102,71,0	duplicate(ach_treasure#core)	#bea_ach1::120116	HIDDEN_WARP_NPC,5,5
+
+// Einbech
+ein_dun02,31,255,0	duplicate(ach_treasure#core)	#eind_ach1::120122	HIDDEN_WARP_NPC,5,5
+
+// Geffen Dungeon
+gef_dun02,222,163,0	duplicate(ach_treasure#core)	#gefd_ach1::120123	HIDDEN_WARP_NPC,5,5
+
+// Glastheim
+gl_cas02,53,151,0	duplicate(ach_treasure#core)	#gl_ach1::120124	HIDDEN_WARP_NPC,5,5
+gl_sew04,288,6,0	duplicate(ach_treasure#core)	#gl_ach2::120125	HIDDEN_WARP_NPC,5,5
+gl_knt02,126,235,0	duplicate(ach_treasure#core)	#gl_ach3::120126	HIDDEN_WARP_NPC,5,5
+gl_prison1,125,159,0	duplicate(ach_treasure#core)	#gl_ach4:120127	HIDDEN_WARP_NPC,5,5
+
+// Gonryun Dungeon
+gon_dun03,167,232,0	duplicate(ach_treasure#core)	#gon_ach1::120128	HIDDEN_WARP_NPC,5,5
+
+// Istana
+dew_dun02,87,272,0	duplicate(ach_treasure#core)	#dew_ach1::120119	HIDDEN_WARP_NPC,5,5
+
+// Labyrinth
+prt_maze03,11,14,0	duplicate(ach_treasure#core)	#maze_ach1::120140	HIDDEN_WARP_NPC,5,5
+
+// Louyang Dungeon
+lou_dun03,29,228,0	duplicate(ach_treasure#core)	#lou_ach1::120134	HIDDEN_WARP_NPC,5,5
+
+// Nogg Road
+mag_dun02,192,72,0	duplicate(ach_treasure#core)	#mag_ach1::120135	HIDDEN_WARP_NPC,5,5
+
+// Orc Dungeon
+orcsdun02,32,72,0	duplicate(ach_treasure#core)	#orc_ach1::120138	HIDDEN_WARP_NPC,5,5
+
+// Payon Dungeon
+pay_dun04,120,124,0	duplicate(ach_treasure#core)	#payd_ach1::120139	HIDDEN_WARP_NPC,5,5
+
+// Prontera Culvert
+prt_sewb4,19,183,0	duplicate(ach_treasure#core)	#iz_ach1::120131	HIDDEN_WARP_NPC,5,5
+
+// Pyramid
+moc_pryd06,102,121,0	duplicate(ach_treasure#core)	#pyr_ach1::120137	HIDDEN_WARP_NPC,5,5
+
+// Rachel Dungeon
+ice_dun03,44,261,0	duplicate(ach_treasure#core)	#rad_ach1::120129	HIDDEN_WARP_NPC,5,5
+
+// Robot Factory
+kh_dun02,70,106,0	duplicate(ach_treasure#core)	#jup_ach1::120132	HIDDEN_WARP_NPC,5,5
+
+// Scaraba Hall
+dic_dun03,216,211,0	duplicate(ach_treasure#core)	#dic_ach1::120120	HIDDEN_WARP_NPC,5,5
+
+// Somatology Lab
+lhz_dun03,240,221,0	duplicate(ach_treasure#core)	#lhzd_ach1::120133	HIDDEN_WARP_NPC,5,5
+
+// Sphinx
+in_sphinx5,154,107,0	duplicate(ach_treasure#core)	#sph_ach1::120130	HIDDEN_WARP_NPC,5,5
+
+// Sunken Ship
+treasure02,18,142,0	duplicate(ach_treasure#core)	#iz_ach3::120144	HIDDEN_WARP_NPC,5,5
+
+// Thanatos Tower
+tha_t06,150,176,0	duplicate(ach_treasure#core)	#tha_ach1::120142	HIDDEN_WARP_NPC,5,5
+
+// Thors Volcano
+thor_v03,220,221,0	duplicate(ach_treasure#core)	#thor_ach1::120143	HIDDEN_WARP_NPC,5,5
+
+// Toy Factory
+xmas_dun02,120,224,0	duplicate(ach_treasure#core)	#xmas_ach1::120146	HIDDEN_WARP_NPC,5,5
+
+// Turtle Island Dungeon
+tur_dun04,134,130,0	duplicate(ach_treasure#core)	#tur_ach1::120145	HIDDEN_WARP_NPC,5,5
+
+//---------------
+// --- Fields ---
+//---------------
+
+// Comodo
+cmd_fild01,112,200,0	duplicate(ach_treasure#core)	#cmd_ach1::120044	HIDDEN_WARP_NPC,5,5
+cmd_fild02,86,94,0	duplicate(ach_treasure#core)	#cmd_ach2::120045	HIDDEN_WARP_NPC,5,5
+cmd_fild03,144,190,0	duplicate(ach_treasure#core)	#cmd_ach3::120046	HIDDEN_WARP_NPC,5,5
+cmd_fild04,151,191,0	duplicate(ach_treasure#core)	#cmd_ach4::120047	HIDDEN_WARP_NPC,5,5
+cmd_fild06,221,108,0	duplicate(ach_treasure#core)	#cmd_ach5::120048	HIDDEN_WARP_NPC,5,5
+cmd_fild07,269,322,0	duplicate(ach_treasure#core)	#cmd_ach6::120049	HIDDEN_WARP_NPC,5,5
+cmd_fild08,181,136,0	duplicate(ach_treasure#core)	#cmd_ach7::120050	HIDDEN_WARP_NPC,5,5
+cmd_fild09,211,266,0	duplicate(ach_treasure#core)	#cmd_ach8::120051	HIDDEN_WARP_NPC,5,5
+
+// Einbroch
+ein_fild01,266,277,0	duplicate(ach_treasure#core)	#ein_ach1::120067	HIDDEN_WARP_NPC,5,5
+ein_fild03,99,332,0	duplicate(ach_treasure#core)	#ein_ach2::120068	HIDDEN_WARP_NPC,5,5
+ein_fild04,334,305,0	duplicate(ach_treasure#core)	#ein_ach3::120069	HIDDEN_WARP_NPC,5,5
+ein_fild05,337,233,0	duplicate(ach_treasure#core)	#ein_ach4::120070	HIDDEN_WARP_NPC,5,5
+ein_fild06,174,245,0	duplicate(ach_treasure#core)	#ein_ach5::120071	HIDDEN_WARP_NPC,5,5
+ein_fild07,188,50,0	duplicate(ach_treasure#core)	#ein_ach6::120072	HIDDEN_WARP_NPC,5,5
+ein_fild08,258,78,0	duplicate(ach_treasure#core)	#ein_ach7::120073	HIDDEN_WARP_NPC,5,5
+ein_fild09,330,76,0	duplicate(ach_treasure#core)	#ein_ach8::120074	HIDDEN_WARP_NPC,5,5
+
+// Geffen
+gef_fild00,74,119,0	duplicate(ach_treasure#core)	#gef_ach1::120011	HIDDEN_WARP_NPC,5,5
+gef_fild01,223,223,0	duplicate(ach_treasure#core)	#gef_ach2::120012	HIDDEN_WARP_NPC,5,5
+gef_fild05,202,292,0	duplicate(ach_treasure#core)	#gef_ach3::120013	HIDDEN_WARP_NPC,5,5
+gef_fild06,279,104,0	duplicate(ach_treasure#core)	#gef_ach4::120014	HIDDEN_WARP_NPC,5,5
+gef_fild07,181,250,0	duplicate(ach_treasure#core)	#gef_ach5::120015	HIDDEN_WARP_NPC,5,5
+gef_fild09,170,73,0	duplicate(ach_treasure#core)	#gef_ach6::120016	HIDDEN_WARP_NPC,5,5
+gef_fild11,238,249,0	duplicate(ach_treasure#core)	#gef_ach7::120017	HIDDEN_WARP_NPC,5,5
+
+// Hugel
+hu_fild01,347,312,0	duplicate(ach_treasure#core)	#hu_ach1::120062	HIDDEN_WARP_NPC,5,5
+hu_fild02,80,152,0	duplicate(ach_treasure#core)	#hu_ach2::120063	HIDDEN_WARP_NPC,5,5
+hu_fild04,322,313,0	duplicate(ach_treasure#core)	#hu_ach3::120064	HIDDEN_WARP_NPC,5,5
+hu_fild06,204,228,0	duplicate(ach_treasure#core)	#hu_ach4::120065	HIDDEN_WARP_NPC,5,5
+hu_fild05,197,210,0	duplicate(ach_treasure#core)	#hu_ach5::120066	HIDDEN_WARP_NPC,5,5
+
+// Laphine
+ecl_fild01,155,322,0	duplicate(ach_treasure#core)	#ecl_ach1::120090	HIDDEN_WARP_NPC,5,5
+bif_fild01,147,64,0	duplicate(ach_treasure#core)	#ecl_ach2::120091	HIDDEN_WARP_NPC,5,5
+bif_fild02,155,322,0	duplicate(ach_treasure#core)	#ecl_ach3::120092	HIDDEN_WARP_NPC,5,5
+spl_fild01,335,315,0	duplicate(ach_treasure#core)	#ecl_ach4::120093	HIDDEN_WARP_NPC,5,5
+spl_fild02,153,358,0	duplicate(ach_treasure#core)	#ecl_ach5::120094	HIDDEN_WARP_NPC,5,5
+spl_fild03,61,286,0	duplicate(ach_treasure#core)	#ecl_ach6::120095	HIDDEN_WARP_NPC,5,5
+
+// Lighthalzen
+lhz_fild01,118,73,0	duplicate(ach_treasure#core)	#lhz_ach1::120075	HIDDEN_WARP_NPC,5,5
+lhz_fild02,239,243,0	duplicate(ach_treasure#core)	#lhz_ach2::120076	HIDDEN_WARP_NPC,5,5
+lhz_fild03,313,132,0	duplicate(ach_treasure#core)	#lhz_ach3::120077	HIDDEN_WARP_NPC,5,5
+
+// Manuk
+man_fild01,41,172,0	duplicate(ach_treasure#core)	#man_ach1::120096	HIDDEN_WARP_NPC,5,5
+man_fild02,268,355,0	duplicate(ach_treasure#core)	#man_ach2::120097	HIDDEN_WARP_NPC,5,5
+man_fild03,198,91,0	duplicate(ach_treasure#core)	#man_ach3::120098	HIDDEN_WARP_NPC,5,5
+dic_fild01,227,82,0	duplicate(ach_treasure#core)	#man_ach4::120099	HIDDEN_WARP_NPC,5,5
+dic_fild02,147,196,0	duplicate(ach_treasure#core)	#man_ach5::120100	HIDDEN_WARP_NPC,5,5
+
+// Misc
+ama_fild01,187,337,0	duplicate(ach_treasure#core)	#misc_ach1::120101	HIDDEN_WARP_NPC,5,5
+gon_fild01,171,332,0	duplicate(ach_treasure#core)	#misc_ach2::120102	HIDDEN_WARP_NPC,5,5
+lou_fild01,104,232,0	duplicate(ach_treasure#core)	#misc_ach3::120103	HIDDEN_WARP_NPC,5,5
+ayo_fild01,289,70,0	duplicate(ach_treasure#core)	#misc_ach4::120104	HIDDEN_WARP_NPC,5,5
+mosk_fild02,176,77,0	duplicate(ach_treasure#core)	#misc_ach5::120105	HIDDEN_WARP_NPC,5,5
+bra_fild01,99,193,0	duplicate(ach_treasure#core)	#misc_ach6::120106	HIDDEN_WARP_NPC,5,5
+dew_fild01,175,287,0	duplicate(ach_treasure#core)	#misc_ach7::120107	HIDDEN_WARP_NPC,5,5
+ma_fild01,308,206,0	duplicate(ach_treasure#core)	#misc_ach8::120108	HIDDEN_WARP_NPC,5,5
+ma_fild02,176,77,0	duplicate(ach_treasure#core)	#misc_ach9::120109	HIDDEN_WARP_NPC,5,5
+
+// Mjolnir
+mjolnir_01,47,60,0	duplicate(ach_treasure#core)	#nmjo_ach1::120032	HIDDEN_WARP_NPC,5,5
+mjolnir_02,77,49,0	duplicate(ach_treasure#core)	#nmjo_ach2::120033	HIDDEN_WARP_NPC,5,5
+mjolnir_03,190,200,0	duplicate(ach_treasure#core)	#nmjo_ach3::120034	HIDDEN_WARP_NPC,5,5
+mjolnir_04,201,146,0	duplicate(ach_treasure#core)	#nmjo_ach4::120035	HIDDEN_WARP_NPC,5,5
+mjolnir_05,43,327,0	duplicate(ach_treasure#core)	#nmjo_ach5::120036	HIDDEN_WARP_NPC,5,5
+mjolnir_06,162,290,0	duplicate(ach_treasure#core)	#smjo_ach1::120037	HIDDEN_WARP_NPC,5,5
+mjolnir_07,321,127,0	duplicate(ach_treasure#core)	#smjo_ach2::120038	HIDDEN_WARP_NPC,5,5
+mjolnir_08,175,225,0	duplicate(ach_treasure#core)	#smjo_ach3::120039	HIDDEN_WARP_NPC,5,5
+mjolnir_09,299,123,0	duplicate(ach_treasure#core)	#smjo_ach4::120040	HIDDEN_WARP_NPC,5,5
+mjolnir_10,353,371,0	duplicate(ach_treasure#core)	#smjo_ach5::120041	HIDDEN_WARP_NPC,5,5
+mjolnir_11,329,182,0	duplicate(ach_treasure#core)	#smjo_ach6::120042	HIDDEN_WARP_NPC,5,5
+mjolnir_12,110,298,0	duplicate(ach_treasure#core)	#smjo_ach7::120043	HIDDEN_WARP_NPC,5,5
+
+// Morocc
+moc_fild11,188,218,0	duplicate(ach_treasure#core)	#moc_ach1::120018	HIDDEN_WARP_NPC,5,5
+moc_fild12,234,96,0	duplicate(ach_treasure#core)	#moc_ach2::120019	HIDDEN_WARP_NPC,5,5
+moc_fild13,290,207,0	duplicate(ach_treasure#core)	#moc_ach3::120020	HIDDEN_WARP_NPC,5,5
+moc_fild16,196,108,0	duplicate(ach_treasure#core)	#moc_ach4::120021	HIDDEN_WARP_NPC,5,5
+moc_fild17,269,105,0	duplicate(ach_treasure#core)	#moc_ach5::120022	HIDDEN_WARP_NPC,5,5
+moc_fild18,54,284,0	duplicate(ach_treasure#core)	#moc_ach6::120023	HIDDEN_WARP_NPC,5,5
+
+// Payon
+pay_fild01,167,243,0	duplicate(ach_treasure#core)	#pay_ach1::120024	HIDDEN_WARP_NPC,5,5
+pay_fild02,105,240,0	duplicate(ach_treasure#core)	#pay_ach2::120025	HIDDEN_WARP_NPC,5,5
+pay_fild03,144,97,0	duplicate(ach_treasure#core)	#pay_ach3::120026	HIDDEN_WARP_NPC,5,5
+pay_fild04,257,95,0	duplicate(ach_treasure#core)	#pay_ach4::120027	HIDDEN_WARP_NPC,5,5
+pay_fild07,365,37,0	duplicate(ach_treasure#core)	#pay_ach5::120028	HIDDEN_WARP_NPC,5,5
+pay_fild08,237,345,0	duplicate(ach_treasure#core)	#pay_ach6::120029	HIDDEN_WARP_NPC,5,5
+pay_fild09,251,42,0	duplicate(ach_treasure#core)	#pay_ach7::120030	HIDDEN_WARP_NPC,5,5
+pay_fild10,196,38,0	duplicate(ach_treasure#core)	#pay_ach8::120031	HIDDEN_WARP_NPC,5,5
+
+// Prontera
+prt_fild01,147,126,0	duplicate(ach_treasure#core)	#prt_ach1::120001	HIDDEN_WARP_NPC,5,5
+prt_fild02,140,219,0	duplicate(ach_treasure#core)	#prt_ach2::120002	HIDDEN_WARP_NPC,5,5
+prt_fild03,172,139,0	duplicate(ach_treasure#core)	#prt_ach3::120003	HIDDEN_WARP_NPC,5,5
+prt_fild04,119,291,0	duplicate(ach_treasure#core)	#prt_ach4::120004	HIDDEN_WARP_NPC,5,5
+prt_fild05,190,291,0	duplicate(ach_treasure#core)	#prt_ach5::120005	HIDDEN_WARP_NPC,5,5
+prt_fild06,296,303,0	duplicate(ach_treasure#core)	#prt_ach6::120006	HIDDEN_WARP_NPC,5,5
+prt_fild07,45,104,0	duplicate(ach_treasure#core)	#prt_ach7::120007	HIDDEN_WARP_NPC,5,5
+prt_fild08,203,223,0	duplicate(ach_treasure#core)	#prt_ach8::120008	HIDDEN_WARP_NPC,5,5
+prt_fild09,37,354,0	duplicate(ach_treasure#core)	#prt_ach9::120009	HIDDEN_WARP_NPC,5,5
+prt_fild10,177,206,0	duplicate(ach_treasure#core)	#prt_ach10::12010	HIDDEN_WARP_NPC,5,5
+
+// Rachel
+ra_fild01,138,166,0	duplicate(ach_treasure#core)	#ra_ach1::120078	HIDDEN_WARP_NPC,5,5
+ra_fild03,224,275,0	duplicate(ach_treasure#core)	#ra_ach2::120079	HIDDEN_WARP_NPC,5,5
+ra_fild08,326,45,0	duplicate(ach_treasure#core)	#ra_ach3::120080	HIDDEN_WARP_NPC,5,5
+ra_fild12,352,165,0	duplicate(ach_treasure#core)	#ra_ach4::120081	HIDDEN_WARP_NPC,5,5
+ra_fild04,92,302,0	duplicate(ach_treasure#core)	#ra_ach5::120082	HIDDEN_WARP_NPC,5,5
+ra_fild05,59,59,0	duplicate(ach_treasure#core)	#ra_ach6::120083	HIDDEN_WARP_NPC,5,5
+ra_fild06,362,230,0	duplicate(ach_treasure#core)	#ra_ach7::120084	HIDDEN_WARP_NPC,5,5
+
+// Veins
+ve_fild01,180,234,0	duplicate(ach_treasure#core)	#ve_ach1::120085	HIDDEN_WARP_NPC,5,5
+ve_fild02,65,194,0	duplicate(ach_treasure#core)	#ve_ach2::120086	HIDDEN_WARP_NPC,5,5
+ve_fild03,197,242,0	duplicate(ach_treasure#core)	#ve_ach3::120087	HIDDEN_WARP_NPC,5,5
+ve_fild04,288,279,0	duplicate(ach_treasure#core)	#ve_ach4::120088	HIDDEN_WARP_NPC,5,5
+ve_fild07,33,113,0	duplicate(ach_treasure#core)	#ve_ach5::120089	HIDDEN_WARP_NPC,5,5
+
+// Yuno
+yuno_fild01,284,138,0	duplicate(ach_treasure#core)	#yuno_ach1::120052	HIDDEN_WARP_NPC,5,5
+yuno_fild12,76,268,0	duplicate(ach_treasure#core)	#yuno_ach2::120053	HIDDEN_WARP_NPC,5,5
+yuno_fild02,142,191,0	duplicate(ach_treasure#core)	#yuno_ach3::120054	HIDDEN_WARP_NPC,5,5
+yuno_fild03,135,329,0	duplicate(ach_treasure#core)	#yuno_ach4::120055	HIDDEN_WARP_NPC,5,5
+yuno_fild04,35,369,0	duplicate(ach_treasure#core)	#yuno_ach5::120056	HIDDEN_WARP_NPC,5,5
+yuno_fild06,262,220,0	duplicate(ach_treasure#core)	#yuno_ach6::120057	HIDDEN_WARP_NPC,5,5
+yuno_fild07,113,339,0	duplicate(ach_treasure#core)	#yuno_ach7::120058	HIDDEN_WARP_NPC,5,5
+yuno_fild08,179,209,0	duplicate(ach_treasure#core)	#yuno_ach8::120059	HIDDEN_WARP_NPC,5,5
+yuno_fild09,166,228,0	duplicate(ach_treasure#core)	#yuno_ach9::120060	HIDDEN_WARP_NPC,5,5
+yuno_fild11,141,357,0	duplicate(ach_treasure#core)	#yuno_ach10::120061	HIDDEN_WARP_NPC,5,5

+ 1 - 0
npc/re/scripts_athena.conf

@@ -99,6 +99,7 @@ npc: npc/re/merchants/shops.txt
 npc: npc/re/merchants/te_merchant.txt
 
 // --------------------------- Others ---------------------------
+npc: npc/re/other/achievements.txt
 npc: npc/re/other/adven_boards.txt
 npc: npc/re/other/bulletin_boards.txt
 npc: npc/re/other/Global_Functions.txt

+ 24 - 0
sql-files/main.sql

@@ -24,6 +24,29 @@ CREATE TABLE IF NOT EXISTS `acc_reg_str` (
   KEY `account_id` (`account_id`)
 ) ENGINE=MyISAM;
 
+--
+-- Table structure for table `achievement`
+--
+
+CREATE TABLE IF NOT EXISTS `achievement` (
+  `char_id` int(11) unsigned NOT NULL default '0',
+  `id` bigint(11) unsigned NOT NULL,
+  `count1` mediumint(8) unsigned NOT NULL default '0',
+  `count2` mediumint(8) unsigned NOT NULL default '0',
+  `count3` mediumint(8) unsigned NOT NULL default '0',
+  `count4` mediumint(8) unsigned NOT NULL default '0',
+  `count5` mediumint(8) unsigned NOT NULL default '0',
+  `count6` mediumint(8) unsigned NOT NULL default '0',
+  `count7` mediumint(8) unsigned NOT NULL default '0',
+  `count8` mediumint(8) unsigned NOT NULL default '0',
+  `count9` mediumint(8) unsigned NOT NULL default '0',
+  `count10` mediumint(8) unsigned NOT NULL default '0',
+  `completed` datetime,
+  `rewarded` datetime,
+  PRIMARY KEY (`char_id`,`id`),
+  KEY `char_id` (`char_id`)
+) ENGINE=MyISAM;
+
 --
 -- Table structure for table `auction`
 --
@@ -231,6 +254,7 @@ CREATE TABLE IF NOT EXISTS `char` (
   `hotkey_rowshift` tinyint(3) unsigned NOT NULL default '0',
   `clan_id` int(11) unsigned NOT NULL default '0',
   `last_login` datetime DEFAULT NULL,
+  `title_id` INT(11) unsigned NOT NULL default '0',
   PRIMARY KEY  (`char_id`),
   UNIQUE KEY `name_key` (`name`),
   KEY `account_id` (`account_id`),

+ 25 - 0
sql-files/upgrades/upgrade_20170407.sql

@@ -0,0 +1,25 @@
+ALTER TABLE `char`
+	ADD COLUMN `title_id` int(11) unsigned NOT NULL default '0' AFTER `clan_id`;
+
+--
+-- Table structure for table `achievement`
+--
+
+CREATE TABLE IF NOT EXISTS `achievement` (
+  `char_id` int(11) unsigned NOT NULL default '0',
+  `id` bigint(11) unsigned NOT NULL,
+  `count1` mediumint(8) unsigned NOT NULL default '0',
+  `count2` mediumint(8) unsigned NOT NULL default '0',
+  `count3` mediumint(8) unsigned NOT NULL default '0',
+  `count4` mediumint(8) unsigned NOT NULL default '0',
+  `count5` mediumint(8) unsigned NOT NULL default '0',
+  `count6` mediumint(8) unsigned NOT NULL default '0',
+  `count7` mediumint(8) unsigned NOT NULL default '0',
+  `count8` mediumint(8) unsigned NOT NULL default '0',
+  `count9` mediumint(8) unsigned NOT NULL default '0',
+  `count10` mediumint(8) unsigned NOT NULL default '0',
+  `completed` datetime,
+  `rewarded` datetime,
+  PRIMARY KEY (`char_id`,`id`),
+  KEY `char_id` (`char_id`)
+) ENGINE=MyISAM;

+ 2 - 0
src/char/char-server.vcxproj

@@ -161,6 +161,7 @@
     <ClInclude Include="char_logif.h" />
     <ClInclude Include="char_mapif.h" />
     <ClInclude Include="inter.h" />
+    <ClInclude Include="int_achievement.h" />
     <ClInclude Include="int_auction.h" />
     <ClInclude Include="int_clan.h" />
     <ClInclude Include="int_elemental.h" />
@@ -182,6 +183,7 @@
     <ClCompile Include="char_logif.c" />
     <ClCompile Include="char_mapif.c" />
     <ClCompile Include="inter.c" />
+    <ClCompile Include="int_achievement.c" />
     <ClCompile Include="int_auction.c" />
     <ClCompile Include="int_clan.c" />
     <ClCompile Include="int_elemental.c" />

+ 6 - 0
src/char/char-server.vcxproj.filters

@@ -26,6 +26,9 @@
     <ClInclude Include="char_mapif.h">
       <Filter>Header Files</Filter>
     </ClInclude>
+    <ClInclude Include="int_achievement.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
     <ClInclude Include="int_auction.h">
       <Filter>Header Files</Filter>
     </ClInclude>
@@ -79,6 +82,9 @@
     <ClCompile Include="char_mapif.c">
       <Filter>Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="int_achievement.c">
+      <Filter>Source Files</Filter>
+    </ClCompile>
     <ClCompile Include="int_auction.c">
       <Filter>Source Files</Filter>
     </ClCompile>

+ 22 - 7
src/char/char.cpp

@@ -300,7 +300,7 @@ int char_mmo_char_tosql(uint32 char_id, struct mmo_charstatus* p){
 		(p->head_mid != cp->head_mid) || (p->head_bottom != cp->head_bottom) || (p->delete_date != cp->delete_date) ||
 		(p->rename != cp->rename) || (p->robe != cp->robe) || (p->character_moves != cp->character_moves) ||
 		(p->unban_time != cp->unban_time) || (p->font != cp->font) || (p->uniqueitem_counter != cp->uniqueitem_counter) ||
-		(p->hotkey_rowshift != cp->hotkey_rowshift) || (p->clan_id != cp->clan_id )
+		(p->hotkey_rowshift != cp->hotkey_rowshift) || (p->clan_id != cp->clan_id ) || (p->title_id != cp->title_id)
 	)
 	{	//Save status
 		if( SQL_ERROR == Sql_Query(sql_handle, "UPDATE `%s` SET `base_level`='%d', `job_level`='%d',"
@@ -311,7 +311,7 @@ int char_mmo_char_tosql(uint32 char_id, struct mmo_charstatus* p){
 			"`weapon`='%d',`shield`='%d',`head_top`='%d',`head_mid`='%d',`head_bottom`='%d',"
 			"`last_map`='%s',`last_x`='%d',`last_y`='%d',`save_map`='%s',`save_x`='%d',`save_y`='%d', `rename`='%d',"
 			"`delete_date`='%lu',`robe`='%d',`moves`='%d',`font`='%u',`uniqueitem_counter`='%u',"
-			"`hotkey_rowshift`='%d', `clan_id`='%d'"
+			"`hotkey_rowshift`='%d', `clan_id`='%d', `title_id`='%lu'"
 			" WHERE `account_id`='%d' AND `char_id` = '%d'",
 			schema_config.char_db, p->base_level, p->job_level,
 			p->base_exp, p->job_exp, p->zeny,
@@ -323,7 +323,7 @@ int char_mmo_char_tosql(uint32 char_id, struct mmo_charstatus* p){
 			mapindex_id2name(p->save_point.map), p->save_point.x, p->save_point.y, p->rename,
 			(unsigned long)p->delete_date, // FIXME: platform-dependent size
 			p->robe, p->character_moves, p->font, p->uniqueitem_counter,
-			p->hotkey_rowshift, p->clan_id,
+			p->hotkey_rowshift, p->clan_id, p->title_id,
 			p->account_id, p->char_id) )
 		{
 			Sql_ShowDebug(sql_handle);
@@ -917,7 +917,7 @@ int char_mmo_chars_fromsql(struct char_session_data* sd, uint8* buf) {
 		"`str`,`agi`,`vit`,`int`,`dex`,`luk`,`max_hp`,`hp`,`max_sp`,`sp`,"
 		"`status_point`,`skill_point`,`option`,`karma`,`manner`,`hair`,`hair_color`,"
 		"`clothes_color`,`body`,`weapon`,`shield`,`head_top`,`head_mid`,`head_bottom`,`last_map`,`rename`,`delete_date`,"
-		"`robe`,`moves`,`unban_time`,`font`,`uniqueitem_counter`,`sex`,`hotkey_rowshift`"
+		"`robe`,`moves`,`unban_time`,`font`,`uniqueitem_counter`,`sex`,`hotkey_rowshift`,`title_id`"
 		" FROM `%s` WHERE `account_id`='%d' AND `char_num` < '%d'", schema_config.char_db, sd->account_id, MAX_CHARS)
 	||	SQL_ERROR == SqlStmt_Execute(stmt)
 	||	SQL_ERROR == SqlStmt_BindColumn(stmt, 0,  SQLDT_INT,    &p.char_id, 0, NULL, NULL)
@@ -963,6 +963,7 @@ int char_mmo_chars_fromsql(struct char_session_data* sd, uint8* buf) {
 	||	SQL_ERROR == SqlStmt_BindColumn(stmt, 40, SQLDT_UINT,   &p.uniqueitem_counter, 0, NULL, NULL)
 	||	SQL_ERROR == SqlStmt_BindColumn(stmt, 41, SQLDT_ENUM,   &sex, sizeof(sex), NULL, NULL)
 	||	SQL_ERROR == SqlStmt_BindColumn(stmt, 42, SQLDT_UCHAR,   &p.hotkey_rowshift, 0, NULL, NULL)
+	||	SQL_ERROR == SqlStmt_BindColumn(stmt, 43, SQLDT_ULONG,	&p.title_id, 0, NULL, NULL)
 	)
 	{
 		SqlStmt_ShowDebug(stmt);
@@ -1026,7 +1027,7 @@ int char_mmo_char_fromsql(uint32 char_id, struct mmo_charstatus* p, bool load_ev
 		"`status_point`,`skill_point`,`option`,`karma`,`manner`,`party_id`,`guild_id`,`pet_id`,`homun_id`,`elemental_id`,`hair`,"
 		"`hair_color`,`clothes_color`,`body`,`weapon`,`shield`,`head_top`,`head_mid`,`head_bottom`,`last_map`,`last_x`,`last_y`,"
 		"`save_map`,`save_x`,`save_y`,`partner_id`,`father`,`mother`,`child`,`fame`,`rename`,`delete_date`,`robe`, `moves`,"
-		"`unban_time`,`font`,`uniqueitem_counter`,`sex`,`hotkey_rowshift`,`clan_id`"
+		"`unban_time`,`font`,`uniqueitem_counter`,`sex`,`hotkey_rowshift`,`clan_id`,`title_id`"
 		" FROM `%s` WHERE `char_id`=? LIMIT 1", schema_config.char_db)
 	||	SQL_ERROR == SqlStmt_BindParam(stmt, 0, SQLDT_INT, &char_id, 0)
 	||	SQL_ERROR == SqlStmt_Execute(stmt)
@@ -1090,6 +1091,7 @@ int char_mmo_char_fromsql(uint32 char_id, struct mmo_charstatus* p, bool load_ev
 	||	SQL_ERROR == SqlStmt_BindColumn(stmt, 57, SQLDT_ENUM,   &sex, sizeof(sex), NULL, NULL)
 	||	SQL_ERROR == SqlStmt_BindColumn(stmt, 58, SQLDT_UCHAR,  &p->hotkey_rowshift, 0, NULL, NULL)
 	||	SQL_ERROR == SqlStmt_BindColumn(stmt, 59, SQLDT_INT,    &p->clan_id, 0, NULL, NULL)
+	||	SQL_ERROR == SqlStmt_BindColumn(stmt, 60, SQLDT_ULONG,	&p->title_id, 0, NULL, NULL)
 	)
 	{
 		SqlStmt_ShowDebug(stmt);
@@ -1671,6 +1673,10 @@ int char_delete_char_sql(uint32 char_id){
 	if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `char_id` = '%d'", schema_config.bonus_script_db, char_id) )
 		Sql_ShowDebug(sql_handle);
 
+	/* Achievement Data */
+	if (SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `char_id` = '%d'", schema_config.achievement_table, char_id))
+		Sql_ShowDebug(sql_handle);
+
 	if (charserv_config.log_char) {
 		if( SQL_ERROR == Sql_Query(sql_handle, "INSERT INTO `%s`(`time`, `account_id`,`char_num`,`char_msg`,`name`) VALUES (NOW(), '%d', '%d', 'Deleted char (CID %d)', '%s')",
 			schema_config.charlog_db, account_id, 0, char_id, esc_name) )
@@ -2236,7 +2242,7 @@ bool char_checkdb(void){
                 schema_config.auction_db, schema_config.quest_db, schema_config.homunculus_db, schema_config.skill_homunculus_db,
                 schema_config.mercenary_db, schema_config.mercenary_owner_db,
 		schema_config.elemental_db, schema_config.ragsrvinfo_db, schema_config.skillcooldown_db, schema_config.bonus_script_db,
-		schema_config.clan_table, schema_config.clan_alliance_table, schema_config.mail_attachment_db
+		schema_config.clan_table, schema_config.clan_alliance_table, schema_config.mail_attachment_db, schema_config.achievement_table
 	};
 	ShowInfo("Start checking DB integrity\n");
 	for (i=0; i<ARRAYLENGTH(sqltable); i++){ //check if they all exist and we can acces them in sql-server
@@ -2252,7 +2258,7 @@ bool char_checkdb(void){
 		"`guild_id`,`pet_id`,`homun_id`,`elemental_id`,`hair`,`hair_color`,`clothes_color`,`weapon`,"
 		"`shield`,`head_top`,`head_mid`,`head_bottom`,`robe`,`last_map`,`last_x`,`last_y`,`save_map`,"
 		"`save_x`,`save_y`,`partner_id`,`online`,`father`,`mother`,`child`,`fame`,`rename`,`delete_date`,"
-		"`moves`,`unban_time`,`font`,`sex`,`hotkey_rowshift`,`clan_id`,`last_login`"
+		"`moves`,`unban_time`,`font`,`sex`,`hotkey_rowshift`,`clan_id`,`last_login`,`title_id`"
 		" FROM `%s` LIMIT 1;", schema_config.char_db) ){
 		Sql_ShowDebug(sql_handle);
 		return false;
@@ -2478,6 +2484,12 @@ bool char_checkdb(void){
 		Sql_ShowDebug(sql_handle);
 		return false;
 	}
+	//checking achievement_table
+	if (SQL_ERROR == Sql_Query(sql_handle, "SELECT `char_id`,`id`,`count1`,`count2`,`count3`,`count4`,`count5`,`count6`,`count7`,`count8`,`count9`,`count10`,`completed`,`rewarded`"
+		" FROM `%s` LIMIT 1;", schema_config.achievement_table)) {
+		Sql_ShowDebug(sql_handle);
+		return false;
+	}
 	Sql_FreeResult(sql_handle);
 	ShowInfo("DB integrity check finished with success\n");
 	return true;
@@ -2573,6 +2585,8 @@ void char_sql_config_read(const char* cfgName) {
 			safestrncpy(schema_config.clan_table, w2, sizeof(schema_config.clan_table));
 		else if(!strcmpi(w1,"clan_alliance_table"))
 			safestrncpy(schema_config.clan_alliance_table, w2, sizeof(schema_config.clan_alliance_table));
+		else if(!strcmpi(w1,"achievement_table"))
+			safestrncpy(schema_config.achievement_table, w2, sizeof(schema_config.achievement_table));
 		//support the import command, just like any other config
 		else if(!strcmpi(w1,"import"))
 			char_sql_config_read(w2);
@@ -2622,6 +2636,7 @@ void char_set_default_sql(){
 	safestrncpy(schema_config.acc_reg_num_table,"acc_reg_num",sizeof(schema_config.acc_reg_num_table));
 	safestrncpy(schema_config.clan_table,"clan",sizeof(schema_config.clan_table));
 	safestrncpy(schema_config.clan_table,"clan_alliance",sizeof(schema_config.clan_alliance_table));
+	safestrncpy(schema_config.achievement_table,"achievement",sizeof(schema_config.achievement_table));
 }
 
 //set default config

+ 1 - 0
src/char/char.h

@@ -80,6 +80,7 @@ struct Schema_Config {
 	char char_reg_num_table[DB_NAME_LEN];
 	char clan_table[DB_NAME_LEN];
 	char clan_alliance_table[DB_NAME_LEN];
+	char achievement_table[DB_NAME_LEN];
 };
 extern struct Schema_Config schema_config;
 

+ 344 - 0
src/char/int_achievement.c

@@ -0,0 +1,344 @@
+// Copyright (c) Athena Dev Teams - Licensed under GNU GPL
+// For more information, see LICENCE in the main folder
+
+#include "../common/db.h"
+#include "../common/malloc.h"
+#include "../common/mmo.h"
+#include "../common/socket.h"
+#include "../common/sql.h"
+#include "../common/strlib.h"
+
+#include "char.h"
+#include "inter.h"
+#include "int_achievement.h"
+#include "int_mail.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+/**
+ * Load achievements for a character.
+ * @param char_id: Character ID
+ * @param count: Pointer to return the number of found entries.
+ * @return Array of found entries. It has *count entries, and it is care of the caller to aFree() it afterwards.
+ */
+struct achievement *mapif_achievements_fromsql(uint32 char_id, int *count)
+{
+	struct achievement *achievelog = NULL;
+	struct achievement tmp_achieve;
+	SqlStmt *stmt;
+	StringBuf buf;
+	int i;
+
+	if (!count)
+		return NULL;
+
+	memset(&tmp_achieve, 0, sizeof(tmp_achieve));
+
+	StringBuf_Init(&buf);
+	StringBuf_AppendStr(&buf, "SELECT `id`, COALESCE(UNIX_TIMESTAMP(`completed`),0), COALESCE(UNIX_TIMESTAMP(`rewarded`),0)");
+	for (i = 0; i < MAX_ACHIEVEMENT_OBJECTIVES; ++i)
+		StringBuf_Printf(&buf, ", `count%d`", i + 1);
+	StringBuf_Printf(&buf, " FROM `%s` WHERE `char_id` = '%u'", schema_config.achievement_table, char_id);
+
+	stmt = SqlStmt_Malloc(sql_handle);
+	if( SQL_ERROR == SqlStmt_PrepareStr(stmt, StringBuf_Value(&buf))
+	||  SQL_ERROR == SqlStmt_Execute(stmt) )
+	{
+		SqlStmt_ShowDebug(stmt);
+		SqlStmt_Free(stmt);
+		StringBuf_Destroy(&buf);
+		*count = 0;
+		return NULL;
+	}
+
+	SqlStmt_BindColumn(stmt, 0, SQLDT_INT,  &tmp_achieve.achievement_id, 0, NULL, NULL);
+	SqlStmt_BindColumn(stmt, 1, SQLDT_INT,  &tmp_achieve.completed, 0, NULL, NULL);
+	SqlStmt_BindColumn(stmt, 2, SQLDT_INT,  &tmp_achieve.rewarded, 0, NULL, NULL);
+	for (i = 0; i < MAX_ACHIEVEMENT_OBJECTIVES; ++i)
+		SqlStmt_BindColumn(stmt, 3 + i, SQLDT_INT, &tmp_achieve.count[i], 0, NULL, NULL);
+
+	*count = (int)SqlStmt_NumRows(stmt);
+	if (*count > 0) {
+		i = 0;
+
+		achievelog = (struct achievement *)aCalloc(*count, sizeof(struct achievement));
+		while (SQL_SUCCESS == SqlStmt_NextRow(stmt)) {
+			if (i >= *count) // Sanity check, should never happen
+				break;
+			memcpy(&achievelog[i++], &tmp_achieve, sizeof(tmp_achieve));
+		}
+		if (i < *count) {
+			// Should never happen. Compact array
+			*count = i;
+			achievelog = (struct achievement *)aRealloc(achievelog, sizeof(struct achievement) * i);
+		}
+	}
+
+	SqlStmt_Free(stmt);
+	StringBuf_Clear(&buf);
+	return achievelog;
+}
+
+/**
+ * Deletes an achievement from a character's achievementlog.
+ * @param char_id: Character ID
+ * @param achievement_id: Achievement ID
+ * @return false in case of errors, true otherwise
+ */
+bool mapif_achievement_delete(uint32 char_id, int achievement_id)
+{
+	if (SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `id` = '%d' AND `char_id` = '%u'", schema_config.achievement_table, achievement_id, char_id)) {
+		Sql_ShowDebug(sql_handle);
+		return false;
+	}
+
+	return true;
+}
+
+/**
+ * Adds an achievement to a character's achievementlog.
+ * @param char_id: Character ID
+ * @param ad: Achievement data
+ * @return false in case of errors, true otherwise
+ */
+bool mapif_achievement_add(uint32 char_id, struct achievement ad)
+{
+	StringBuf buf;
+	int i;
+
+	StringBuf_Init(&buf);
+	StringBuf_Printf(&buf, "INSERT INTO `%s` (`char_id`, `id`, `completed`, `rewarded`", schema_config.achievement_table);
+	for (i = 0; i < MAX_ACHIEVEMENT_OBJECTIVES; ++i)
+		StringBuf_Printf(&buf, ", `count%d`", i + 1);
+	StringBuf_AppendStr(&buf, ")");
+	StringBuf_Printf(&buf, " VALUES ('%u', '%d',", char_id, ad.achievement_id, (uint32)ad.completed, (uint32)ad.rewarded);
+	if( ad.completed ){
+		StringBuf_Printf(&buf, "FROM_UNIXTIME('%u'),", (uint32)ad.completed);
+	}else{
+		StringBuf_AppendStr(&buf, "NULL,");
+	}
+	if( ad.rewarded ){
+		StringBuf_Printf(&buf, "FROM_UNIXTIME('%u')", (uint32)ad.rewarded);
+	}else{
+		StringBuf_AppendStr(&buf, "NULL");
+	}
+	for (i = 0; i < MAX_ACHIEVEMENT_OBJECTIVES; ++i)
+		StringBuf_Printf(&buf, ", '%d'", ad.count[i]);
+	StringBuf_AppendStr(&buf, ")");
+
+	if (SQL_ERROR == Sql_QueryStr(sql_handle, StringBuf_Value(&buf))) {
+		Sql_ShowDebug(sql_handle);
+		StringBuf_Clear(&buf);
+		return false;
+	}
+
+	StringBuf_Clear(&buf);
+
+	return true;
+}
+
+/**
+ * Updates an achievement in a character's achievementlog.
+ * @param char_id: Character ID
+ * @param ad: Achievement data
+ * @return false in case of errors, true otherwise
+ */
+bool mapif_achievement_update(uint32 char_id, struct achievement ad)
+{
+	StringBuf buf;
+	int i;
+
+	StringBuf_Init(&buf);
+	StringBuf_Printf(&buf, "UPDATE `%s` SET ", schema_config.achievement_table);
+	if( ad.completed ){
+		StringBuf_Printf(&buf, "`completed` = FROM_UNIXTIME('%u'),", (uint32)ad.completed);
+	}else{
+		StringBuf_AppendStr(&buf, "`completed` = NULL,");
+	}
+	if( ad.rewarded ){
+		StringBuf_Printf(&buf, "`rewarded` = FROM_UNIXTIME('%u')", (uint32)ad.rewarded);
+	}else{
+		StringBuf_AppendStr(&buf, "`rewarded` = NULL");
+	}
+	for (i = 0; i < MAX_ACHIEVEMENT_OBJECTIVES; ++i)
+		StringBuf_Printf(&buf, ", `count%d` = '%d'", i + 1, ad.count[i]);
+	StringBuf_Printf(&buf, " WHERE `id` = %d AND `char_id` = %u", ad.achievement_id, char_id);
+
+	if (SQL_ERROR == Sql_QueryStr(sql_handle, StringBuf_Value(&buf))) {
+		Sql_ShowDebug(sql_handle);
+		StringBuf_Clear(&buf);
+		return false;
+	}
+
+	StringBuf_Clear(&buf);
+
+	return true;
+}
+
+/**
+ * Notifies the map-server of the result of saving a character's achievementlog.
+ */
+void mapif_achievement_save( int fd, uint32 char_id, bool success ){
+	WFIFOHEAD(fd, 7);
+	WFIFOW(fd, 0) = 0x3863;
+	WFIFOL(fd, 2) = char_id;
+	WFIFOB(fd, 6) = success;
+	WFIFOSET(fd, 7);
+}
+
+/**
+ * Handles the save request from mapserver for a character's achievementlog.
+ * Received achievements are saved, and an ack is sent back to the map server.
+ * @see inter_parse_frommap
+ */
+int mapif_parse_achievement_save(int fd)
+{
+	int i, j, k, old_n, new_n = (RFIFOW(fd, 2) - 8) / sizeof(struct achievement);
+	uint32 char_id = RFIFOL(fd, 4);
+	struct achievement *old_ad = NULL, *new_ad = NULL;
+	bool success = true;
+
+	if (new_n > 0)
+		new_ad = (struct achievement *)RFIFOP(fd, 8);
+
+	old_ad = mapif_achievements_fromsql(char_id, &old_n);
+
+	for (i = 0; i < new_n; i++) {
+		ARR_FIND(0, old_n, j, new_ad[i].achievement_id == old_ad[j].achievement_id);
+		if (j < old_n) { // Update existing achievements
+			// Only counts, complete, and reward are changable.
+			ARR_FIND(0, MAX_ACHIEVEMENT_OBJECTIVES, k, new_ad[i].count[k] != old_ad[j].count[k]);
+			if (k != MAX_ACHIEVEMENT_OBJECTIVES || new_ad[i].completed != old_ad[j].completed || new_ad[i].rewarded != old_ad[j].rewarded) {
+				if ((success = mapif_achievement_update(char_id, new_ad[i])) == false)
+					break;
+			}
+
+			if (j < (--old_n)) {
+				// Compact array
+				memmove(&old_ad[j], &old_ad[j + 1], sizeof(struct achievement) * (old_n - j));
+				memset(&old_ad[old_n], 0, sizeof(struct achievement));
+			}
+		} else { // Add new achievements
+			if (new_ad[i].achievement_id) {
+				if ((success = mapif_achievement_add(char_id, new_ad[i])) == false)
+					break;
+			}
+		}
+	}
+
+	for (i = 0; i < old_n; i++) { // Achievements not in new_ad but in old_ad are to be erased.
+		if ((success = mapif_achievement_delete(char_id, old_ad[i].achievement_id)) == false)
+			break;
+	}
+
+	if (old_ad)
+		aFree(old_ad);
+
+	mapif_achievement_save(fd, char_id, success);
+
+	return 0;
+}
+
+/**
+ * Sends the achievementlog of a character to the map-server.
+ */
+void mapif_achievement_load( int fd, uint32 char_id ){
+	struct achievement *tmp_achievementlog = NULL;
+	int num_achievements = 0;
+
+	tmp_achievementlog = mapif_achievements_fromsql(char_id, &num_achievements);
+
+	WFIFOHEAD(fd, num_achievements * sizeof(struct achievement) + 8);
+	WFIFOW(fd, 0) = 0x3862;
+	WFIFOW(fd, 2) = num_achievements * sizeof(struct achievement) + 8;
+	WFIFOL(fd, 4) = char_id;
+
+	if (num_achievements > 0)
+		memcpy(WFIFOP(fd, 8), tmp_achievementlog, sizeof(struct achievement) * num_achievements);
+
+	WFIFOSET(fd, num_achievements * sizeof(struct achievement) + 8);
+
+	if (tmp_achievementlog)
+		aFree(tmp_achievementlog);
+}
+
+/**
+ * Sends achievementlog to the map server
+ * NOTE: Achievements sent to the player are only completed ones
+ * @see inter_parse_frommap
+ */
+int mapif_parse_achievement_load(int fd)
+{
+	mapif_achievement_load( fd, RFIFOL(fd, 2) );
+
+	return 0;
+}
+
+/**
+ * Notify the map-server if claiming the reward has succeeded.
+ */
+void mapif_achievement_reward( int fd, uint32 char_id, int32 achievement_id, time_t rewarded ){
+	WFIFOHEAD(fd, 14);
+	WFIFOW(fd, 0) = 0x3864;
+	WFIFOL(fd, 2) = char_id;
+	WFIFOL(fd, 6) = achievement_id;
+	WFIFOL(fd, 10) = (uint32)rewarded;
+	WFIFOSET(fd, 14);
+}
+
+/**
+ * Request of the map-server that a player claimed his achievement rewards.
+ * @see inter_parse_frommap
+ */
+int mapif_parse_achievement_reward(int fd){
+	time_t current = time(NULL);
+	uint32 char_id = RFIFOL(fd, 2);
+	int32 achievement_id = RFIFOL(fd, 6);
+
+	if( Sql_Query( sql_handle, "UPDATE `%s` SET `rewarded` = FROM_UNIXTIME('%u') WHERE `char_id`='%u' AND `id` = '%d' AND `completed` IS NOT NULL AND `rewarded` IS NULL", schema_config.achievement_table, (uint32)current, char_id, achievement_id ) == SQL_ERROR ||
+		Sql_NumRowsAffected(sql_handle) <= 0 ){
+		current = 0;
+	}else if( RFIFOW(fd,10) > 0 ){ // Do not send a mail if no item reward
+		char mail_sender[NAME_LENGTH];
+		char mail_receiver[NAME_LENGTH];
+		char mail_title[MAIL_TITLE_LENGTH];
+		char mail_text[MAIL_BODY_LENGTH];
+		struct item item;
+
+		memset(&item, 0, sizeof(struct item));
+		item.nameid = RFIFOW(fd, 10);
+		item.amount = RFIFOL(fd, 12);
+		item.identify = 1;
+
+		safesnprintf(mail_sender, NAME_LENGTH, char_msg_txt(227)); // 227: GM
+		safestrncpy(mail_receiver, RFIFOCP(fd,16), NAME_LENGTH);
+		safesnprintf(mail_title, MAIL_TITLE_LENGTH, char_msg_txt(228)); // 228: Achievement Reward Mail
+		safesnprintf(mail_text, MAIL_BODY_LENGTH, char_msg_txt(229), RFIFOCP(fd,16+NAME_LENGTH) ); // 229: [%s] Achievement Reward.
+
+		if( !mail_sendmail(0, mail_sender, char_id, mail_receiver, mail_title, mail_text, 0, &item, 1) ){
+			current = 0;
+		}
+	}
+
+	mapif_achievement_reward(fd, char_id, achievement_id, current);
+
+	return 0;
+}
+
+/**
+ * Parses achievementlog related packets from the map server.
+ * @see inter_parse_frommap
+ */
+int inter_achievement_parse_frommap(int fd)
+{
+	switch (RFIFOW(fd, 0)) {
+		case 0x3062: mapif_parse_achievement_load(fd); break;
+		case 0x3063: mapif_parse_achievement_save(fd); break;
+		case 0x3064: mapif_parse_achievement_reward(fd); break;
+		default:
+			return 0;
+	}
+	return 1;
+}

+ 9 - 0
src/char/int_achievement.h

@@ -0,0 +1,9 @@
+// Copyright (c) Athena Dev Teams - Licensed under GNU GPL
+// For more information, see LICENCE in the main folder
+
+#ifndef _INT_ACHIEVEMENT_SQL_H_
+#define _INT_ACHIEVEMENT_SQL_H_
+
+int inter_achievement_parse_frommap(int fd);
+
+#endif /* _INT_ACHIEVEMENT_SQL_H_ */

+ 6 - 2
src/char/int_mail.c

@@ -578,7 +578,7 @@ static void mapif_parse_Mail_send(int fd)
 	mapif_Mail_new(&msg); // notify recipient
 }
 
-void mail_sendmail(int send_id, const char* send_name, int dest_id, const char* dest_name, const char* title, const char* body, int zeny, struct item *item, int amount)
+bool mail_sendmail(int send_id, const char* send_name, int dest_id, const char* dest_name, const char* title, const char* body, int zeny, struct item *item, int amount)
 {
 	struct mail_message msg;
 	memset(&msg, 0, sizeof(struct mail_message));
@@ -601,8 +601,12 @@ void mail_sendmail(int send_id, const char* send_name, int dest_id, const char*
 	msg.timestamp = time(NULL);
 	msg.type = MAIL_INBOX_NORMAL;
 
-	mail_savemessage(&msg);
+	if( !mail_savemessage(&msg) ){
+		return false;
+	}
+
 	mapif_Mail_new(&msg);
+	return true;
 }
 
 static void mapif_Mail_receiver_send( int fd, int requesting_char_id, int char_id, int class_, int base_level, const char* name ){

+ 1 - 1
src/char/int_mail.h

@@ -12,7 +12,7 @@ int mail_return_timer( int tid, unsigned int tick, int id, intptr_t data );
 int mail_delete_timer( int tid, unsigned int tick, int id, intptr_t data );
 
 int inter_mail_parse_frommap(int fd);
-void mail_sendmail(int send_id, const char* send_name, int dest_id, const char* dest_name, const char* title, const char* body, int zeny, struct item *item, int amount);
+bool mail_sendmail(int send_id, const char* send_name, int dest_id, const char* dest_name, const char* title, const char* body, int zeny, struct item *item, int amount);
 
 int inter_mail_sql_init(void);
 void inter_mail_sql_final(void);

+ 3 - 1
src/char/inter.c

@@ -22,6 +22,7 @@
 #include "int_quest.h"
 #include "int_elemental.h"
 #include "int_clan.h"
+#include "int_achievement.h"
 
 #include <stdlib.h>
 
@@ -51,7 +52,7 @@ int inter_recv_packet_length[] = {
 	-1, 6,-1,-1, 55,19, 6,-1, 14,-1,-1,-1, 18,19,186,-1,	// 3030-
 	-1, 9, 0, 0,  0, 0, 0, 0,  8, 6,11,10, 10,-1,6+NAME_LENGTH, 0,	// 3040-
 	-1,-1,10,10,  0,-1,12, 0,  0, 0, 0, 0,  0, 0,  0, 0,	// 3050-  Auction System [Zephyrus]
-	 6,-1, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0,  0, 0,	// 3060-  Quest system [Kevin] [Inkfish]
+	 6,-1, 6,-1, 16+NAME_LENGTH+ACHIEVEMENT_NAME_LENGTH, 0, 0, 0,  0, 0, 0, 0,  0, 0,  0, 0,	// 3060-  Quest system [Kevin] [Inkfish] / Achievements [Aleos]
 	-1,10, 6,-1,  0, 0, 0, 0,  0, 0, 0, 0, -1,10,  6,-1,	// 3070-  Mercenary packets [Zephyrus], Elemental packets [pakpil]
 	48,14,-1, 6,  0, 0, 0, 0,  0, 0,13,-1,  0, 0,  0, 0,	// 3080-  Pet System, Storage
 	-1,10,-1, 6,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0,  0, 0,	// 3090-  Homunculus packets [albator]
@@ -1409,6 +1410,7 @@ int inter_parse_frommap(int fd)
 		  || inter_auction_parse_frommap(fd)
 		  || inter_quest_parse_frommap(fd)
 		  || inter_clan_parse_frommap(fd)
+		  || inter_achievement_parse_frommap(fd)
 		   )
 			break;
 		else

+ 20 - 10
src/common/Makefile.in

@@ -2,7 +2,7 @@
 #COMMON_OBJ = $(ls *.c | grep -viw sql.c | sed -e "s/\.c/\.o/g")
 COMMON_OBJ = core.o socket.o timer.o db.o nullpo.o malloc.o showmsg.o strlib.o utils.o \
 	grfio.o mapindex.o ers.o md5calc.o minicore.o minisocket.o minimalloc.o random.o des.o \
-	conf.o thread.o mutex.o raconf.o mempool.o msg_conf.o cli.o sql.o
+	conf.o thread.o mutex.o raconf.o mempool.o msg_conf.o cli.o sql.o yamlwrapper.o
 COMMON_DIR_OBJ = $(COMMON_OBJ:%=obj/%)
 COMMON_H = $(shell ls ../common/*.h)
 COMMON_AR = obj/common.a
@@ -15,6 +15,12 @@ LIBCONFIG_H = $(shell ls ../../3rdparty/libconfig/*.h)
 LIBCONFIG_AR = ../../3rdparty/libconfig/obj/libconfig.a
 LIBCONFIG_INCLUDE = -I../../3rdparty/libconfig
 
+YAML_CPP_OBJ = $(shell find ../../3rdparty/yaml-cpp/ -type f -name "*.cpp" | sed -e "s/\.cpp/\.o/g" )
+YAML_CPP_DIR_OBJ = $(YAML_CPP_OBJ:%=obj/%)
+YAML_CPP_AR = ../../3rdparty/yaml-cpp/obj/yaml-cpp.a
+YAML_CPP_H = $(shell find ../../3rdparty/yaml-cpp/ -type f -name "*.h")
+YAML_CPP_INCLUDE = -I../../3rdparty/yaml-cpp/include
+
 HAVE_MYSQL=@HAVE_MYSQL@
 ifeq ($(HAVE_MYSQL),yes)
 	SERVER_DEPENDS=common
@@ -59,23 +65,23 @@ $(COMMON_AR): $(COMMON_DIR_OBJ)
 	@echo "	AR	$@"
 	@@AR@ rcs $(COMMON_AR) $(COMMON_DIR_OBJ)
 
-common: obj $(COMMON_DIR_OBJ) $(MT19937AR_OBJ) $(LIBCONFIG_AR) $(COMMON_AR)
+common: obj $(COMMON_DIR_OBJ) $(MT19937AR_OBJ) $(LIBCONFIG_AR) $(YAML_CPP_AR) $(COMMON_AR)
 
-obj/%.o: %.c $(COMMON_H) $(MT19937AR_H) $(LIBCONFIG_H)
+obj/%.o: %.c $(COMMON_H) $(MT19937AR_H) $(LIBCONFIG_H) $(YAML_CPP_H)
 	@echo "	CC	$<"
-	@@CC@ @CFLAGS_AR@ $(MT19937AR_INCLUDE) $(LIBCONFIG_INCLUDE) @MYSQL_CFLAGS@ @CPPFLAGS@ -c $(OUTPUT_OPTION) $<
+	@@CC@ @CFLAGS_AR@ $(MT19937AR_INCLUDE) $(LIBCONFIG_INCLUDE) $(YAML_CPP_INCLUDE) @MYSQL_CFLAGS@ @CPPFLAGS@ -c $(OUTPUT_OPTION) $<
 
-obj/%.o: %.cpp $(COMMON_H) $(MT19937AR_H) $(LIBCONFIG_H)
+obj/%.o: %.cpp $(COMMON_H) $(MT19937AR_H) $(LIBCONFIG_H) $(YAML_CPP_H)
 	@echo "	CXX	$<"
-	@@CXX@ $(CXXFLAG) @CFLAGS_AR@ $(MT19937AR_INCLUDE) $(LIBCONFIG_INCLUDE) @MYSQL_CFLAGS@ @CPPFLAGS@ -c $(OUTPUT_OPTION) $<
+	@@CXX@ $(CXXFLAG) @CFLAGS_AR@ $(MT19937AR_INCLUDE) $(LIBCONFIG_INCLUDE) $(YAML_CPP_INCLUDE) @MYSQL_CFLAGS@ @CPPFLAGS@ -c $(OUTPUT_OPTION) $<
 
-obj/mini%.o: %.c $(COMMON_H) $(MT19937AR_H) $(LIBCONFIG_H)
+obj/mini%.o: %.c $(COMMON_H) $(MT19937AR_H) $(LIBCONFIG_H) $(YAML_CPP_H)
 	@echo "	CC	$<"
-	@@CC@ @CFLAGS_AR@ $(MT19937AR_INCLUDE) $(LIBCONFIG_INCLUDE) @MYSQL_CFLAGS@ -DMINICORE @CPPFLAGS@ -c $(OUTPUT_OPTION) $<
+	@@CC@ @CFLAGS_AR@ $(MT19937AR_INCLUDE) $(LIBCONFIG_INCLUDE) $(YAML_CPP_INCLUDE) @MYSQL_CFLAGS@ -DMINICORE @CPPFLAGS@ -c $(OUTPUT_OPTION) $<
 
-obj/mini%.o: %.cpp $(COMMON_H) $(MT19937AR_H) $(LIBCONFIG_H)
+obj/mini%.o: %.cpp $(COMMON_H) $(MT19937AR_H) $(LIBCONFIG_H) $(YAML_CPP_H)
 	@echo "	CXX	$<"
-	@@CXX@ $(CXXFLAG) @CFLAGS_AR@ $(MT19937AR_INCLUDE) $(LIBCONFIG_INCLUDE) @MYSQL_CFLAGS@ -DMINICORE @CPPFLAGS@ -c $(OUTPUT_OPTION) $<
+	@@CXX@ $(CXXFLAG) @CFLAGS_AR@ $(MT19937AR_INCLUDE) $(LIBCONFIG_INCLUDE) $(YAML_CPP_INCLUDE) @MYSQL_CFLAGS@ -DMINICORE @CPPFLAGS@ -c $(OUTPUT_OPTION) $<
 
 # missing object files
 $(MT19937AR_OBJ):
@@ -83,3 +89,7 @@ $(MT19937AR_OBJ):
 
 $(LIBCONFIG_AR):
 	@$(MAKE) -C ../../3rdparty/libconfig
+
+$(YAML_CPP_AR):
+	@$(MAKE) -C ../../3rdparty/yaml-cpp
+

+ 6 - 0
src/common/core.cpp

@@ -379,5 +379,11 @@ int main (int argc, char **argv)
 
 	malloc_final();
 
+#if defined(BUILDBOT)
+	if( buildbotflag ){
+		exit(EXIT_FAILURE);
+	}
+#endif
+
 	return 0;
 }

+ 16 - 0
src/common/mmo.h

@@ -141,6 +141,12 @@
 #define EL_CLASS_BASE 2114
 #define EL_CLASS_MAX (EL_CLASS_BASE+MAX_ELEMENTAL_CLASS-1)
 
+//Achievement System
+#define MAX_ACHIEVEMENT_RANK 20 /// Maximum achievement level
+#define MAX_ACHIEVEMENT_OBJECTIVES 10 /// Maximum different objectives in achievement_db.conf
+#define MAX_ACHIEVEMENT_DEPENDENTS 20 /// Maximum different dependents in achievement_db.conf
+#define ACHIEVEMENT_NAME_LENGTH 50 /// Max Achievement Name length
+
 enum item_types {
 	IT_HEALING = 0,
 	IT_UNKNOWN, //1
@@ -180,6 +186,15 @@ struct s_item_randomoption {
 	char param;
 };
 
+/// Achievement log entry
+struct achievement {
+	int achievement_id;                    ///< Achievement ID
+	int count[MAX_ACHIEVEMENT_OBJECTIVES]; ///< Counters of each achievement objective
+	time_t completed;                      ///< Date completed
+	time_t rewarded;                       ///< Received reward?
+	int score;                             ///< Amount of points achievement is worth
+};
+
 struct item {
 	int id;
 	unsigned short nameid;
@@ -478,6 +493,7 @@ struct mmo_charstatus {
 	uint32 uniqueitem_counter;
 
 	unsigned char hotkey_rowshift;
+	unsigned long title_id;
 };
 
 typedef enum mail_status {

+ 10 - 0
src/common/sql.c

@@ -352,6 +352,16 @@ uint64 Sql_NumRows(Sql* self)
 
 
 
+/// Returns the number of rows affected by the last query
+uint64 Sql_NumRowsAffected(Sql* self)
+{
+	if( self )
+		return (uint64)mysql_affected_rows(&self->handle);
+	return 0;
+}
+
+
+
 /// Fetches the next row.
 int Sql_NextRow(Sql* self)
 {

+ 7 - 0
src/common/sql.h

@@ -175,6 +175,13 @@ uint64 Sql_NumRows(Sql* self);
 
 
 
+/// Returns the number of rows affected by the last query
+///
+/// @return Number of rows
+uint64 Sql_NumRowsAffected(Sql* self);
+
+
+
 /// Fetches the next row.
 /// The data of the previous row is no longer valid.
 ///

+ 57 - 5
src/common/yamlwrapper.cpp

@@ -31,8 +31,8 @@ yamlwrapper::yamlwrapper(YAML::Node node) {
 	this->root = node;
 }
 
-yamliterator::yamliterator(YAML::Node sequence) {
-	this->sequence = sequence;
+yamliterator::yamliterator(YAML::Node sequence_) {
+	this->sequence = sequence_;
 	this->index = 0;
 }
 
@@ -41,13 +41,21 @@ yamliterator* yamlwrapper::iterator() {
 }
 
 yamlwrapper* yaml_load_file(const char* file_name) {
-	YAML::Node node = YAML::LoadFile(file_name);
-	if (!node.IsDefined())
+	YAML::Node node;
+
+	try {
+		node = YAML::LoadFile(file_name);
+		if (!node.IsDefined())
+			return NULL;
+	} catch (YAML::ParserException &e) {
+		ShowError("YAML Exception Caught: %s\n", e.what());
 		return NULL;
+	}
+
 	return new yamlwrapper(node);
 }
 
-extern "C++" YAML::Node yaml_get_node(YAML::Node& node, std::string& key) {
+extern "C++" YAML::Node yaml_get_node(const YAML::Node& node,const std::string& key) {
 	if (key.empty())
 		return node;
 
@@ -107,6 +115,50 @@ bool yaml_get_boolean(yamlwrapper* wrapper, const char* key) {
 	return yaml_get_value<bool>(wrapper, key);
 }
 
+char* yaml_as_c_string(yamlwrapper* wrapper) {
+	std::string cpp_str = wrapper->root.as<std::string>();
+	const char* c_str = cpp_str.c_str();
+	size_t str_size = std::strlen(c_str) + 1;
+	char* buf = (char*)aCalloc(1, str_size);
+	strcpy(buf, c_str);
+	return buf;
+}
+
+extern "C++" {
+	template<typename T>
+	T yaml_as_value(yamlwrapper* wrapper) {
+		if (wrapper == nullptr)
+			return {};
+		try {
+			return wrapper->root.as<T>();
+		}
+		catch (const std::exception& e) {
+			ShowError("Error during YAML node value resolving in node %s.\n", e.what());
+			return {};
+		}
+	}
+}
+
+int yaml_as_int(yamlwrapper* wrapper) {
+	return yaml_as_value<int>(wrapper);
+}
+
+int16 yaml_as_int16(yamlwrapper* wrapper) {
+	return yaml_as_value<int16>(wrapper);
+}
+
+int32 yaml_as_int32(yamlwrapper* wrapper) {
+	return yaml_as_value<int32>(wrapper);
+}
+
+int64 yaml_as_int64(yamlwrapper* wrapper) {
+	return yaml_as_value<int64>(wrapper);
+}
+
+bool yaml_as_boolean(yamlwrapper* wrapper) {
+	return yaml_as_value<bool>(wrapper);
+}
+
 bool yaml_node_is_defined(yamlwrapper* wrapper, const char* key) {
 	if (wrapper == nullptr || key == nullptr)
 		return false;

+ 7 - 1
src/common/yamlwrapper.h

@@ -42,7 +42,7 @@ class yamliterator {
 public:
 	YAML::Node sequence;
 	unsigned int index;
-	yamliterator(YAML::Node sequence);
+	yamliterator(YAML::Node sequence_);
 };
 
 class yamlwrapper {
@@ -65,6 +65,12 @@ int16 yaml_get_int16(yamlwrapper* wrapper, const char* key);
 int32 yaml_get_int32(yamlwrapper* wrapper, const char* key);
 int64 yaml_get_int64(yamlwrapper* wrapper, const char* key);
 bool yaml_get_boolean(yamlwrapper* wrapper, const char* key);
+char* yaml_as_c_string(yamlwrapper* wrapper);
+int yaml_as_int(yamlwrapper* wrapper);
+int16 yaml_as_int16(yamlwrapper* wrapper);
+int32 yaml_as_int32(yamlwrapper* wrapper);
+int64 yaml_as_int64(yamlwrapper* wrapper);
+bool yaml_as_boolean(yamlwrapper* wrapper);
 bool yaml_node_is_defined(yamlwrapper* wrapper, const char* key);
 yamlwrapper* yaml_get_subnode(yamlwrapper* wrapper, const char* key);
 yamliterator* yaml_get_iterator(yamlwrapper* wrapper);

+ 15 - 6
src/map/Makefile.in

@@ -11,6 +11,12 @@ LIBCONFIG_H = $(shell ls ../../3rdparty/libconfig/*.h)
 LIBCONFIG_AR = ../../3rdparty/libconfig/obj/libconfig.a
 LIBCONFIG_INCLUDE = -I../../3rdparty/libconfig
 
+YAML_CPP_OBJ = $(shell find ../../3rdparty/yaml-cpp/ -type f -name "*.cpp" | sed -e "s/\.cpp/\.o/g" )
+YAML_CPP_DIR_OBJ = $(YAML_CPP_OBJ:%=obj/%)
+YAML_CPP_AR = ../../3rdparty/yaml-cpp/obj/yaml-cpp.a
+YAML_CPP_H = $(shell find ../../3rdparty/yaml-cpp/ -type f -name "*.h")
+YAML_CPP_INCLUDE = -I../../3rdparty/yaml-cpp/include
+
 MAP_OBJ = $(shell ls *.c | sed -e "s/\.c/\.o/g") $(shell ls *.cpp | sed -e "s/\.cpp/\.o/g")
 MAP_DIR_OBJ = $(MAP_OBJ:%=obj/%)
 MAP_H = $(shell ls ../map/*.h) \
@@ -67,20 +73,20 @@ obj:
 
 # executables
 
-map-server: obj $(MAP_DIR_OBJ) $(COMMON_AR) $(LIBCONFIG_AR)
+map-server: obj $(MAP_DIR_OBJ) $(COMMON_AR) $(LIBCONFIG_AR) $(YAML_CPP_AR)
 	@echo "	LD	@OMAP@@EXEEXT@"
-	@@CXX@ @LDFLAGS@ -o ../../@OMAP@@EXEEXT@ $(MAP_DIR_OBJ) $(COMMON_AR) $(MT19937AR_OBJ) $(LIBCONFIG_AR) @LIBS@ @PCRE_LIBS@ @MYSQL_LIBS@
+	@@CXX@ @LDFLAGS@ -o ../../@OMAP@@EXEEXT@ $(MAP_DIR_OBJ) $(COMMON_AR) $(MT19937AR_OBJ) $(LIBCONFIG_AR) $(YAML_CPP_AR) @LIBS@ @PCRE_LIBS@ @MYSQL_LIBS@
 
 
 # map object files
 
-obj/%.o: %.c $(MAP_H) $(COMMON_H)  $(MT19937AR_H) $(LIBCONFIG_H)
+obj/%.o: %.c $(MAP_H) $(COMMON_H)  $(MT19937AR_H) $(LIBCONFIG_H) $(YAML_CPP_H)
 	@echo "	CC	$<"
-	@@CC@ @CFLAGS@ $(COMMON_INCLUDE) $(MT19937AR_INCLUDE) $(LIBCONFIG_INCLUDE) $(PCRE_CFLAGS) @MYSQL_CFLAGS@ @CPPFLAGS@ -c $(OUTPUT_OPTION) $<
+	@@CC@ @CFLAGS@ $(COMMON_INCLUDE) $(MT19937AR_INCLUDE) $(LIBCONFIG_INCLUDE) $(PCRE_CFLAGS) $(YAML_CPP_INCLUDE) @MYSQL_CFLAGS@ @CPPFLAGS@ -c $(OUTPUT_OPTION) $<
 	
-obj/%.o: %.cpp $(MAP_H) $(COMMON_H)  $(MT19937AR_H) $(LIBCONFIG_H)
+obj/%.o: %.cpp $(MAP_H) $(COMMON_H)  $(MT19937AR_H) $(LIBCONFIG_H) $(YAML_CPP_H)
 	@echo "	CXX	$<"
-	@@CXX@ @CXXFLAGS@ $(COMMON_INCLUDE) $(MT19937AR_INCLUDE) $(LIBCONFIG_INCLUDE) $(PCRE_CFLAGS) @MYSQL_CFLAGS@ @CPPFLAGS@ -c $(OUTPUT_OPTION) $<
+	@@CXX@ @CXXFLAGS@ $(COMMON_INCLUDE) $(MT19937AR_INCLUDE) $(LIBCONFIG_INCLUDE) $(PCRE_CFLAGS) $(YAML_CPP_INCLUDE) @MYSQL_CFLAGS@ @CPPFLAGS@ -c $(OUTPUT_OPTION) $<
 
 # missing object files
 $(COMMON_AR):
@@ -91,3 +97,6 @@ $(MT19937AR_OBJ):
 
 $(LIBCONFIG_AR):
 	@$(MAKE) -C ../../3rdparty/libconfig
+
+$(YAML_CPP_AR):
+	@$(MAKE) -C ../../3rdparty/yaml-cpp

+ 1268 - 0
src/map/achievement.c

@@ -0,0 +1,1268 @@
+// Copyright (c) Athena Dev Teams - Licensed under GNU GPL
+// For more information, see LICENCE in the main folder
+
+#include "../common/cbasetypes.h"
+#include "../common/malloc.h"
+#include "../common/nullpo.h"
+#include "../common/showmsg.h"
+#include "../common/strlib.h"
+#include "../common/utils.h"
+#include "../common/yamlwrapper.h"
+
+#include "achievement.h"
+#include "chrif.h"
+#include "clif.h"
+#include "intif.h"
+#include "itemdb.h"
+#include "map.h"
+#include "pc.h"
+#include "script.h"
+#include "status.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <setjmp.h>
+
+static jmp_buf     av_error_jump;
+static char*       av_error_msg;
+static const char* av_error_pos;
+static int         av_error_report;
+
+static DBMap *achievement_db = NULL; // int achievement_id -> struct achievement_db *
+static DBMap *achievementmobs_db = NULL; // Avoids checking achievements on every mob killed
+static void achievement_db_free_sub(struct achievement_db *achievement, bool free);
+
+/**
+ * Searches an achievement by ID
+ * @param achievement_id: ID to lookup
+ * @return Achievement entry (equals to &achievement_dummy if the ID is invalid)
+ */
+struct achievement_db *achievement_search(int achievement_id)
+{
+	struct achievement_db *achievement = (struct achievement_db *)idb_get(achievement_db, achievement_id);
+
+	if (!achievement)
+		return &achievement_dummy;
+	return achievement;
+}
+
+/**
+ * Searches for an achievement by monster ID
+ * @param mob_id: Monster ID to lookup
+ * @return True on success, false on failure
+ */
+bool achievement_mobexists(int mob_id)
+{
+	if (!battle_config.feature_achievement)
+		return false;
+	return idb_exists(achievementmobs_db, mob_id);
+}
+
+/**
+ * Add an achievement to the player's log
+ * @param sd: Player data
+ * @param achievement_id: Achievement to add
+ * @return NULL on failure, achievement data on success
+ */
+struct achievement *achievement_add(struct map_session_data *sd, int achievement_id)
+{
+	struct achievement_db *adb = &achievement_dummy;
+	int i, index;
+
+	nullpo_retr(NULL, sd);
+
+	if ((adb = achievement_search(achievement_id)) == &achievement_dummy) {
+		ShowError("achievement_add: Achievement %d not found in DB.\n", achievement_id);
+		return NULL;
+	}
+
+	ARR_FIND(0, sd->achievement_data.count, i, sd->achievement_data.achievements[i].achievement_id == achievement_id);
+	if (i < sd->achievement_data.count) {
+		ShowError("achievement_add: Character %d already has achievement %d.\n", sd->status.char_id, achievement_id);
+		return NULL;
+	}
+
+	index = sd->achievement_data.incompleteCount;
+
+	sd->achievement_data.count++;
+	sd->achievement_data.incompleteCount++;
+	RECREATE(sd->achievement_data.achievements, struct achievement, sd->achievement_data.count);
+
+	// The character has some completed achievements, make room before them so that they will stay at the end of the array
+	if (sd->achievement_data.incompleteCount != sd->achievement_data.count)
+		memmove(&sd->achievement_data.achievements[index + 1], &sd->achievement_data.achievements[index], sizeof(struct achievement) * (sd->achievement_data.count - sd->achievement_data.incompleteCount));
+
+	memset(&sd->achievement_data.achievements[index], 0, sizeof(struct achievement));
+
+	sd->achievement_data.achievements[index].achievement_id = achievement_id;
+	sd->achievement_data.achievements[index].score = adb->score;
+	sd->achievement_data.save = true;
+
+	clif_achievement_update(sd, &sd->achievement_data.achievements[index], sd->achievement_data.count - sd->achievement_data.incompleteCount);
+
+	return &sd->achievement_data.achievements[index];
+}
+
+/**
+ * Removes an achievement from a player's log
+ * @param sd: Player's data
+ * @param achievement_id: Achievement to remove
+ * @return True on success, false on failure
+ */
+bool achievement_remove(struct map_session_data *sd, int achievement_id)
+{
+	struct achievement dummy;
+	int i;
+
+	nullpo_retr(false, sd);
+
+	if (achievement_search(achievement_id) == &achievement_dummy) {
+		ShowError("achievement_delete: Achievement %d not found in DB.\n", achievement_id);
+		return false;
+	}
+
+	ARR_FIND(0, sd->achievement_data.count, i, sd->achievement_data.achievements[i].achievement_id == achievement_id);
+	if (i == sd->achievement_data.count) {
+		ShowError("achievement_delete: Character %d doesn't have achievement %d.\n", sd->status.char_id, achievement_id);
+		return false;
+	}
+
+	if (i != sd->achievement_data.count - 1)
+		memmove(&sd->achievement_data.achievements[i], &sd->achievement_data.achievements[i + 1], sizeof(struct achievement) * (sd->achievement_data.count - 1 - i));
+
+	sd->achievement_data.count--;
+	if (!sd->achievement_data.achievements[i].completed)
+		sd->achievement_data.incompleteCount--;
+	RECREATE(sd->achievement_data.achievements, struct achievement, sd->achievement_data.count);
+	sd->achievement_data.save = true;
+
+	// Send a removed fake achievement
+	memset(&dummy, 0, sizeof(struct achievement));
+	dummy.achievement_id = achievement_id;
+	clif_achievement_update(sd, &dummy, sd->achievement_data.count - sd->achievement_data.incompleteCount);
+
+	return true;
+}
+
+/**
+ * Checks to see if an achievement has a dependent, and if so, checks if that dependent is complete
+ * @param sd: Player data
+ * @param achievement_id: Achievement to check if it has a dependent
+ * @return False on failure or not complete, true on complete or no dependents
+ */
+bool achievement_check_dependent(struct map_session_data *sd, int achievement_id)
+{
+	struct achievement_db *adb = &achievement_dummy;
+
+	nullpo_retr(false, sd);
+
+	adb = achievement_search(achievement_id);
+
+	if (adb == &achievement_dummy)
+		return false;
+
+	// Check if the achievement has a dependent
+	// If so, then do a check on all dependents to see if they're complete
+	if (adb->dependent_count) {
+		int i;
+
+		for (i = 0; i < adb->dependent_count; i++) {
+			struct achievement_db *adb_dep = achievement_search(adb->dependents[i].achievement_id);
+			int j;
+
+			if (adb_dep == &achievement_dummy)
+				return false;
+
+			ARR_FIND(0, sd->achievement_data.count, j, sd->achievement_data.achievements[j].achievement_id == adb->dependents[i].achievement_id && sd->achievement_data.achievements[j].completed > 0);
+			if (j == sd->achievement_data.count)
+				return false; // One of the dependent is not complete!
+		}
+	}
+
+	return true;
+}
+
+/**
+ * Check achievements that only have dependents and no other requirements
+ * @return True if successful, false if not
+ */
+static int achievement_check_groups(DBKey key, DBData *data, va_list ap)
+{
+	struct achievement_db *ad;
+	struct map_session_data *sd;
+	int i;
+
+	ad = (struct achievement_db *)db_data2ptr(data);
+	sd = va_arg(ap, struct map_session_data *);
+
+	if (ad == &achievement_dummy || sd == NULL)
+		return 0;
+
+	if (ad->group != AG_BATTLE && ad->group != AG_TAMING && ad->group != AG_ADVENTURE)
+		return 0;
+
+	if (ad->dependent_count == 0 || ad->condition)
+		return 0;
+
+	ARR_FIND(0, sd->achievement_data.count, i, sd->achievement_data.achievements[i].achievement_id == ad->achievement_id);
+	if (i == sd->achievement_data.count) { // Achievment isn't in player's log
+		if (achievement_check_dependent(sd, ad->achievement_id) == true) {
+			achievement_add(sd, ad->achievement_id);
+			achievement_update_achievement(sd, ad->achievement_id, true);
+		}
+	}
+
+	return 1;
+}
+
+/**
+ * Update an achievement
+ * @param sd: Player to update
+ * @param achievement_id: Achievement ID of the achievement to update
+ * @param complete: Complete state of an achievement
+ * @return True if successful, false if not
+ */
+bool achievement_update_achievement(struct map_session_data *sd, int achievement_id, bool complete)
+{
+	struct achievement_db *adb = &achievement_dummy;
+	int i;
+
+	nullpo_retr(false, sd);
+
+	adb = achievement_search(achievement_id);
+
+	if (adb == &achievement_dummy)
+		return false;
+
+	ARR_FIND(0, sd->achievement_data.incompleteCount, i, sd->achievement_data.achievements[i].achievement_id == achievement_id);
+	if (i == sd->achievement_data.incompleteCount)
+		return false;
+
+	if (sd->achievement_data.achievements[i].completed > 0)
+		return false;
+
+	// Finally we send the updated achievement to the client
+	if (complete) {
+		if (adb->target_count) { // Make sure all the objective targets are at their respective total requirement
+			int k;
+
+			for (k = 0; k < adb->target_count; k++)
+				sd->achievement_data.achievements[i].count[k] = adb->targets[k].count;
+
+			for (k = 1; k < adb->dependent_count; k++) {
+				sd->achievement_data.achievements[i].count[k] = max(1, sd->achievement_data.achievements[i].count[k]);
+			}
+		}
+
+		sd->achievement_data.achievements[i].completed = time(NULL);
+
+		if (i < (--sd->achievement_data.incompleteCount)) { // The achievement needs to be moved to the completed achievements block at the end of the array
+			struct achievement tmp_ach;
+
+			memcpy(&tmp_ach, &sd->achievement_data.achievements[i], sizeof(struct achievement));
+			memcpy(&sd->achievement_data.achievements[i], &sd->achievement_data.achievements[sd->achievement_data.incompleteCount], sizeof(struct achievement));
+			memcpy(&sd->achievement_data.achievements[sd->achievement_data.incompleteCount], &tmp_ach, sizeof(struct achievement));
+		}
+
+		achievement_level(sd, true); // Re-calculate achievement level
+		// Check dependents
+		achievement_db->foreach(achievement_db, achievement_check_groups, sd);
+		ARR_FIND(sd->achievement_data.incompleteCount, sd->achievement_data.count, i, sd->achievement_data.achievements[i].achievement_id == achievement_id); // Look for the index again, the position most likely changed
+	}
+
+	clif_achievement_update(sd, &sd->achievement_data.achievements[i], sd->achievement_data.count - sd->achievement_data.incompleteCount);
+	sd->achievement_data.save = true; // Flag to save with the autosave interval
+
+	if (sd->achievement_data.sendlist) {
+		clif_achievement_list_all(sd);
+		sd->achievement_data.sendlist = false;
+	}
+
+	return true;
+}
+
+/**
+ * Get the reward of an achievement
+ * @param sd: Player getting the reward
+ * @param achievement_id: Achievement to get reward data
+ */
+void achievement_get_reward(struct map_session_data *sd, int achievement_id, time_t rewarded)
+{
+	struct achievement_db *adb = achievement_search(achievement_id);
+	int i;
+
+	nullpo_retv(sd);
+
+	if( rewarded == 0 ){
+		clif_achievement_reward_ack(sd->fd, 0, achievement_id);
+		return;
+	}
+
+	if (adb == &achievement_dummy) {
+		ShowError("achievement_reward: Inter server sent a reward claim for achievement %d not found in DB.\n", achievement_id);
+		return;
+	}
+
+	ARR_FIND(0, sd->achievement_data.count, i, sd->achievement_data.achievements[i].achievement_id == achievement_id);
+
+	if (i == sd->achievement_data.count) {
+		return;
+	}
+
+	// Only update in the cache, db was updated already
+	sd->achievement_data.achievements[i].rewarded = rewarded;
+
+	run_script(adb->rewards.script, 0, sd->bl.id, fake_nd->bl.id);
+	if (adb->rewards.title_id) {
+		RECREATE(sd->titles, int, sd->titleCount + 1);
+		sd->titles[sd->titleCount] = adb->rewards.title_id;
+		sd->titleCount++;
+		sd->achievement_data.sendlist = true;
+	}
+
+	clif_achievement_reward_ack(sd->fd, 1, achievement_id);
+	clif_achievement_update(sd, &sd->achievement_data.achievements[i], sd->achievement_data.count - sd->achievement_data.incompleteCount);
+}
+
+/**
+ * Check if player has recieved an achievement's reward
+ * @param sd: Player to get reward
+ * @param achievement_id: Achievement to get reward data
+ */
+void achievement_check_reward(struct map_session_data *sd, int achievement_id)
+{
+	int i;
+	struct achievement_db *adb = achievement_search(achievement_id);
+
+	nullpo_retv(sd);
+
+	if (adb == &achievement_dummy) {
+		ShowError("achievement_reward: Trying to reward achievement %d not found in DB.\n", achievement_id);
+		clif_achievement_reward_ack(sd->fd, 0, achievement_id);
+		return;
+	}
+
+	ARR_FIND(0, sd->achievement_data.count, i, sd->achievement_data.achievements[i].achievement_id == achievement_id);
+	if (i == sd->achievement_data.count) {
+		clif_achievement_reward_ack(sd->fd, 0, achievement_id);
+		return;
+	}
+
+	if (sd->achievement_data.achievements[i].rewarded > 0 || sd->achievement_data.achievements[i].completed == 0) {
+		clif_achievement_reward_ack(sd->fd, 0, achievement_id);
+		return;
+	}
+
+	if( !intif_achievement_reward(sd,adb) ){
+		clif_achievement_reward_ack(sd->fd, 0, achievement_id);
+	}
+}
+
+/**
+ * Return all titles to a player based on completed achievements
+ * @param char_id: Character ID requesting
+ */
+void achievement_get_titles(uint32 char_id)
+{
+	struct map_session_data *sd = map_charid2sd(char_id);
+
+	if (sd) {
+		sd->titles = NULL;
+		sd->titleCount = 0;
+
+		if (sd->achievement_data.count) {
+			int i;
+
+			for (i = 0; i < sd->achievement_data.count; i++) {
+				struct achievement_db *adb = achievement_search(sd->achievement_data.achievements[i].achievement_id);
+
+				if (adb && adb->rewards.title_id && sd->achievement_data.achievements[i].completed > 0) { // If the achievement has a title and is complete, give it to the player
+					RECREATE(sd->titles, int, sd->titleCount + 1);
+					sd->titles[sd->titleCount] = adb->rewards.title_id;
+					sd->titleCount++;
+				}
+			}
+		}
+	}
+}
+
+/**
+ * Frees the player's data for achievements and titles
+ * @param sd: Player's session
+ */
+void achievement_free(struct map_session_data *sd)
+{
+	nullpo_retv(sd);
+
+	if (sd->titleCount) {
+		aFree(sd->titles);
+		sd->titles = NULL;
+		sd->titleCount = 0;
+	}
+
+	if (sd->achievement_data.count) {
+		aFree(sd->achievement_data.achievements);
+		sd->achievement_data.achievements = NULL;
+		sd->achievement_data.count = sd->achievement_data.incompleteCount = 0;
+	}
+}
+
+/**
+ * Get an achievement's progress information
+ * @param sd: Player to check achievement progress
+ * @param achievement_id: Achievement progress to check
+ * @param type: Type to return
+ * @return The type's data, -1 if player doesn't have achievement, -2 on failure/incorrect type
+ */
+int achievement_check_progress(struct map_session_data *sd, int achievement_id, int type)
+{
+	int i;
+
+	nullpo_retr(-2, sd);
+
+	// Achievement ID is not needed so skip the lookup
+	if (type == ACHIEVEINFO_LEVEL)
+		return sd->achievement_data.level;
+	else if (type == ACHIEVEINFO_SCORE)
+		return sd->achievement_data.total_score;
+
+	ARR_FIND(0, sd->achievement_data.count, i, sd->achievement_data.achievements[i].achievement_id == achievement_id);
+	if (i == sd->achievement_data.count)
+		return -1;
+
+	if (type >= ACHIEVEINFO_COUNT1 && type <= ACHIEVEINFO_COUNT10)
+		return sd->achievement_data.achievements[i].count[type - 1];
+	else if (type == ACHIEVEINFO_COMPLETE)
+		return sd->achievement_data.achievements[i].completed > 0;
+	else if (type == ACHIEVEINFO_COMPLETEDATE)
+		return (int)sd->achievement_data.achievements[i].completed;
+	else if (type == ACHIEVEINFO_GOTREWARD)
+		return sd->achievement_data.achievements[i].rewarded > 0;
+	return -2;
+}
+
+/**
+ * Calculate a player's achievement level
+ * @param sd: Player to check achievement level
+ * @param flag: If the call should attempt to give the AG_GOAL_ACHIEVE achievement
+ */
+int *achievement_level(struct map_session_data *sd, bool flag)
+{
+	static int info[2];
+	int i, old_level;
+	const int score_table[MAX_ACHIEVEMENT_RANK] = { 18, 31, 49, 73, 135, 104, 140, 178, 900, 1000, 1100, 1200, 1300, 1400, 1500, 1600, 1700, 1800, 1900, 2000 }; //! TODO: Figure out the EXP required to level up from 8-20
+
+	nullpo_retr(0, sd);
+
+	sd->achievement_data.total_score = 0;
+	old_level = sd->achievement_data.level;
+
+	for (i = 0; i < sd->achievement_data.count; i++) {
+		if (sd->achievement_data.achievements[i].completed > 0)
+			sd->achievement_data.total_score += sd->achievement_data.achievements[i].score;
+	}
+
+	info[0] = 0;
+	info[1] = 0;
+
+	for (i = 0; i < MAX_ACHIEVEMENT_RANK; i++) {
+		info[0] = info[1];
+			
+		if (i < ARRAYLENGTH(score_table))
+			info[1] = score_table[i];
+		else {
+			info[0] = info[1];
+			info[1] = info[1] + 500;
+		}
+
+		if (sd->achievement_data.total_score < info[1])
+			break;
+	}
+
+	if (i == MAX_ACHIEVEMENT_RANK)
+		i = 0;
+
+	info[1] = info[1] - info[0]; // Right number
+	info[0] = sd->achievement_data.total_score - info[0]; // Left number
+	sd->achievement_data.level = i;
+
+	if (flag == true && old_level != sd->achievement_data.level) {
+		int achievement_id = 240000 + sd->achievement_data.level;
+
+		achievement_add(sd, achievement_id);
+		achievement_update_achievement(sd, achievement_id, true);
+	}
+
+	return info;
+}
+
+/**
+ * Update achievement objectives.
+ * @see DBApply
+ */
+static int achievement_update_objectives(DBKey key, DBData *data, va_list ap)
+{
+	struct achievement_db *ad;
+	struct map_session_data *sd;
+	enum e_achievement_group group;
+	struct achievement *entry = NULL;
+	bool isNew = false, changed = false, complete = false;
+	int i, k = 0, objective_count[MAX_ACHIEVEMENT_OBJECTIVES], update_count[MAX_ACHIEVEMENT_OBJECTIVES];
+
+	ad = (struct achievement_db *)db_data2ptr(data);
+	sd = va_arg(ap, struct map_session_data *);
+	group = (enum e_achievement_group)va_arg(ap, int);
+	memcpy(update_count, (int *)va_arg(ap, int *), sizeof(update_count));
+
+	if (ad == NULL || sd == NULL)
+		return 0;
+
+	if (group <= AG_NONE || group >= AG_MAX)
+		return 0;
+
+	if (group != ad->group)
+		return 0;
+
+	memset(objective_count, 0, sizeof(objective_count)); // Current objectives count
+
+	ARR_FIND(0, sd->achievement_data.count, i, sd->achievement_data.achievements[i].achievement_id == ad->achievement_id);
+	if (i == sd->achievement_data.count) { // Achievment isn't in player's log
+		if (achievement_check_dependent(sd, ad->achievement_id) == false) // Check to see if dependents are complete before adding to player's log
+			return 0;
+		isNew = true;
+	} else {
+		entry = &sd->achievement_data.achievements[i];
+
+		if (entry->completed > 0) // Player has completed the achievement
+			return 0;
+
+		memcpy(objective_count, entry->count, sizeof(objective_count));
+	}
+
+	switch (group) {
+		case AG_ADD_FRIEND:
+		case AG_BABY:
+		case AG_CHAT_COUNT:
+		case AG_CHAT_CREATE:
+		case AG_CHAT_DYING:
+		case AG_GET_ITEM:
+		case AG_GET_ZENY:
+		case AG_GOAL_LEVEL:
+		case AG_GOAL_STATUS:
+		case AG_JOB_CHANGE:
+		case AG_MARRY:
+		case AG_PARTY:
+		case AG_REFINE_FAIL:
+		case AG_REFINE_SUCCESS:
+		case AG_SPEND_ZENY:
+			if (group == AG_SPEND_ZENY) { // Achievement type is cummulative
+				objective_count[0] += update_count[0];
+				changed = true;
+			}
+
+			if (!ad->condition || achievement_check_condition(ad->condition, sd, update_count)) {
+				changed = true;
+				complete = true;
+			}
+
+			if (changed == false)
+				break;
+
+			if (isNew) {
+				if ((entry = achievement_add(sd, ad->achievement_id)) == NULL)
+					return 0; // Failed to add achievement, fall out
+			}
+			break;
+		case AG_CHAT:
+			if (!ad->target_count)
+				break;
+
+			if (ad->condition && !achievement_check_condition(ad->condition, sd, update_count)) // Parameters weren't met
+				break;
+
+			if (ad->mapindex > -1 && sd->bl.m != ad->mapindex)
+				break;
+
+			for (i = 0; i < ad->target_count; i++) {
+				if (objective_count[i] < ad->targets[i].count)
+					objective_count[i] += update_count[0];
+			}
+
+			changed = true;
+
+			ARR_FIND(0, ad->target_count, k, objective_count[k] < ad->targets[k].count);
+			if (k == ad->target_count)
+				complete = true;
+
+			if (isNew) {
+				if ((entry = achievement_add(sd, ad->achievement_id)) == NULL)
+					return 0; // Failed to add achievement, fall out
+			}
+			break;
+		case AG_BATTLE:
+		case AG_TAMING:
+			ARR_FIND(0, ad->target_count, k, ad->targets[k].mob == update_count[0]);
+			if (k == ad->target_count)
+				break; // Mob wasn't found
+
+			for (k = 0; k < ad->target_count; k++) {
+				if (ad->targets[k].mob == update_count[0] && objective_count[k] < ad->targets[k].count) {
+					objective_count[k]++;
+					changed = true;
+				}
+			}
+
+			ARR_FIND(0, ad->target_count, k, objective_count[k] < ad->targets[k].count);
+			if (k == ad->target_count)
+				complete = true;
+
+			if (isNew) {
+				if ((entry = achievement_add(sd, ad->achievement_id)) == NULL)
+					return 0; // Failed to add achievement, fall out
+			}
+			break;
+	}
+
+	if (changed) {
+		memcpy(entry->count, objective_count, sizeof(objective_count));
+		achievement_update_achievement(sd, ad->achievement_id, complete);
+	}
+
+	return 1;
+}
+
+/**
+ * Update achievement objective count.
+ * @param sd: Player data
+ * @param group: Achievement enum type
+ * @param sp_value: SP parameter value
+ * @param arg_count: va_arg count
+ */
+void achievement_update_objective(struct map_session_data *sd, enum e_achievement_group group, uint8 arg_count, ...)
+{
+	if (sd) {
+		va_list ap;
+		int i, count[MAX_ACHIEVEMENT_OBJECTIVES];
+
+		if (!battle_config.feature_achievement)
+			return;
+
+		memset(count, 0, sizeof(count)); // Clear out array before setting values
+
+		va_start(ap, arg_count);
+		for (i = 0; i < arg_count; i++)
+			count[i] = va_arg(ap, int);
+		va_end(ap);
+
+		switch(group) {
+			case AG_CHAT: //! TODO: Not sure how this works officially
+			case AG_GOAL_ACHIEVE:
+				// These have no objective use right now.
+				break;
+			default:
+				achievement_db->foreach(achievement_db, achievement_update_objectives, sd, (int)group, count);
+				break;
+		}
+	}
+}
+
+/*==========================================
+ * Achievement condition parsing section
+ *------------------------------------------*/
+static void disp_error_message2(const char *mes,const char *pos,int report)
+{
+	av_error_msg = aStrdup(mes);
+	av_error_pos = pos;
+	av_error_report = report;
+	longjmp(av_error_jump, 1);
+}
+#define disp_error_message(mes,pos) disp_error_message2(mes,pos,1)
+
+/**
+ * Checks the condition of an achievement.
+ * @param condition: Achievement condition
+ * @param sd: Player data
+ * @param count: Script arguments
+ * @return The result of the condition.
+ */
+long long achievement_check_condition(struct av_condition *condition, struct map_session_data *sd, int *count)
+{
+	long long left = 0;
+	long long right = 0;
+
+	// Reduce the recursion, almost all calls will be C_PARAM, C_NAME or C_ARG
+	if (condition->left) {
+		if (condition->left->op == C_NAME || condition->left->op == C_INT)
+			left = condition->left->value;
+		else if (condition->left->op == C_PARAM)
+			left = pc_readparam(sd, (int)condition->left->value);
+		else if (condition->left->op == C_ARG && condition->left->value < MAX_ACHIEVEMENT_OBJECTIVES)
+			left = count[condition->left->value];
+		else
+			left = achievement_check_condition(condition->left, sd, count);
+	}
+
+	if (condition->right) {
+		if (condition->right->op == C_NAME || condition->right->op == C_INT)
+			right = condition->right->value;
+		else if (condition->right->op == C_PARAM)
+			right = pc_readparam(sd, (int)condition->right->value);
+		else if (condition->right->op == C_ARG && condition->right->value < MAX_ACHIEVEMENT_OBJECTIVES)
+			right = count[condition->right->value];
+		else
+			right = achievement_check_condition(condition->right, sd, count);
+	}
+
+	switch(condition->op) {
+		case C_NOP:
+			return false;
+		case C_NAME:
+		case C_INT:
+			return condition->value;
+		case C_PARAM:
+			return pc_readparam(sd, (int)condition->value);
+		case C_LOR: 
+			return left || right;
+		case C_LAND:
+			return left && right;
+		case C_LE:
+			return left <= right;
+		case C_LT:
+			return left < right;
+		case C_GE:
+			return left >= right;
+		case C_GT:
+			return left > right;
+		case C_EQ:
+			return left == right;
+		case C_NE:
+			return left != right;
+		case C_XOR:
+			return left ^ right;
+		case C_OR:
+			return left || right;
+		case C_AND:
+			return left & right;
+		case C_ADD:
+			return left + right;
+		case C_SUB:
+			return left - right;
+		case C_MUL:
+			return left * right;
+		case C_DIV:
+			return left / right;
+		case C_MOD:
+			return left % right;
+		case C_NEG:
+			return -left;
+		case C_LNOT:
+			return !left;
+		case C_NOT:
+			return ~left;
+		case C_R_SHIFT:
+			return left >> right;
+		case C_L_SHIFT:
+			return left << right;
+		case C_ARG:
+			if (condition->value < MAX_ACHIEVEMENT_OBJECTIVES)
+				return count[condition->value];
+
+			return false;
+		default:
+			ShowError("achievement_check_condition: unexpected operator: %d\n", condition->op);
+			return false;
+	}
+
+	return false;
+}
+
+static const char *skip_word(const char *p)
+{
+	while (ISALNUM(*p) || *p == '_')
+		++p;
+
+	if (*p == '$') // String
+		p++;
+
+	return p;
+}
+
+const char *av_parse_simpleexpr(const char *p, struct av_condition *parent)
+{
+	long long i;
+
+	p = skip_space(p);
+
+	if(*p == ';' || *p == ',')
+		disp_error_message("av_parse_simpleexpr: unexpected character.", p);
+	if(*p == '(') {
+		p = av_parse_subexpr(p + 1, -1, parent);
+		p = skip_space(p);
+
+		if (*p != ')')
+			disp_error_message("av_parse_simpleexpr: unmatched ')'", p);
+		++p;
+	} else if(is_number(p)) {
+		char *np;
+
+		while(*p == '0' && ISDIGIT(p[1]))
+			p++;
+		i = strtoll(p, &np, 0);
+
+		if (i < INT_MIN) {
+			i = INT_MIN;
+			disp_error_message("av_parse_simpleexpr: underflow detected, capping value to INT_MIN.", p);
+		} else if (i > INT_MAX) {
+			i = INT_MAX;
+			disp_error_message("av_parse_simpleexpr: underflow detected, capping value to INT_MAX.", p);
+		}
+
+		parent->op = C_INT;
+		parent->value = i;
+		p = np;
+	} else {
+		int v, len;
+		char * word;
+
+		if (skip_word(p) == p)
+			disp_error_message("av_parse_simpleexpr: unexpected character.", p);
+
+		len = skip_word(p) - p;
+
+		if (len == 0)
+			disp_error_message("av_parse_simpleexpr: invalid word. A word consists of undercores and/or alphanumeric characters.", p);
+
+		word = (char*)aMalloc(len + 1);
+		memcpy(word, p, len);
+		word[len] = 0;
+
+		if (script_get_parameter(word, &v))
+			parent->op = C_PARAM;
+		else if (script_get_constant(word, &v)) {
+			if (word[0] == 'b' && ISUPPER(word[1])) // Consider b* variables as parameters (because they... are?)
+				parent->op = C_PARAM;
+			else
+				parent->op = C_NAME;
+		} else {
+			if (word[0] == 'A' && word[1] == 'R' && word[2] == 'G' && ISDIGIT(word[3])) { // Special constants used to set temporary variables
+				parent->op = C_ARG;
+				v = atoi(word + 3);
+			} else {
+				aFree(word);
+				disp_error_message("av_parse_simpleexpr: invalid constant.", p);
+			}
+		}
+
+		aFree(word);
+		parent->value = v;
+		p = skip_word(p);
+	}
+
+	return p;
+}
+
+const char* av_parse_subexpr(const char* p, int limit, struct av_condition *parent)
+{
+	int op, opl, len;
+
+	p = skip_space(p);
+
+	CREATE(parent->left, struct av_condition, 1);
+
+	if ((op = C_NEG, *p == '-') || (op = C_LNOT, *p == '!') || (op = C_NOT, *p == '~')) { // Unary - ! ~ operators
+		p = av_parse_subexpr(p + 1, 11, parent->left);
+		parent->op = op;
+	} else
+		p = av_parse_simpleexpr(p, parent->left);
+
+	p = skip_space(p);
+
+	while((
+			(op=C_ADD,opl=9,len=1,*p=='+') ||
+			(op=C_SUB,opl=9,len=1,*p=='-') ||
+			(op=C_MUL,opl=10,len=1,*p=='*') ||
+			(op=C_DIV,opl=10,len=1,*p=='/') ||
+			(op=C_MOD,opl=10,len=1,*p=='%') ||
+			(op=C_LAND,opl=2,len=2,*p=='&' && p[1]=='&') ||
+			(op=C_AND,opl=5,len=1,*p=='&') ||
+			(op=C_LOR,opl=1,len=2,*p=='|' && p[1]=='|') ||
+			(op=C_OR,opl=3,len=1,*p=='|') ||
+			(op=C_XOR,opl=4,len=1,*p=='^') ||
+			(op=C_EQ,opl=6,len=2,*p=='=' && p[1]=='=') ||
+			(op=C_NE,opl=6,len=2,*p=='!' && p[1]=='=') ||
+			(op=C_R_SHIFT,opl=8,len=2,*p=='>' && p[1]=='>') ||
+			(op=C_GE,opl=7,len=2,*p=='>' && p[1]=='=') ||
+			(op=C_GT,opl=7,len=1,*p=='>') ||
+			(op=C_L_SHIFT,opl=8,len=2,*p=='<' && p[1]=='<') ||
+			(op=C_LE,opl=7,len=2,*p=='<' && p[1]=='=') ||
+			(op=C_LT,opl=7,len=1,*p=='<')) && opl>limit) {
+		p += len;
+
+		if (parent->right) { // Chain conditions
+			struct av_condition *condition = NULL;
+			CREATE(condition, struct av_condition, 1);
+			condition->op = parent->op;
+			condition->left = parent->left;
+			condition->right = parent->right;
+			parent->left = condition;
+			parent->right = NULL;
+		}
+
+		CREATE(parent->right, struct av_condition, 1);
+		p = av_parse_subexpr(p, opl, parent->right);
+		parent->op = op;
+		p = skip_space(p);
+	}
+
+	if (parent->op == C_NOP && parent->right == NULL) { // Move the node up
+		struct av_condition *temp = parent->left;
+
+		parent->right = parent->left->right;
+		parent->op = parent->left->op;
+		parent->value = parent->left->value;
+		parent->left = parent->left->left;
+
+		aFree(temp);
+	}
+
+	return p;
+}
+
+/**
+ * Parses a condition from a script.
+ * @param p: The script buffer.
+ * @param file: The file being parsed.
+ * @param line: The current achievement line number.
+ * @return The parsed achievement condition.
+ */
+struct av_condition *parse_condition(const char *p, const char *file, int line)
+{
+	struct av_condition *condition = NULL;
+
+	if (setjmp(av_error_jump) != 0) {
+		if (av_error_report)
+			script_error(p,file,line,av_error_msg,av_error_pos);
+		aFree(av_error_msg);
+		if (condition)
+			achievement_script_free(condition);
+		return NULL;
+	}
+
+	switch(*p) {
+		case ')': case ';': case ':': case '[': case ']': case '}':
+			disp_error_message("parse_condition: unexpected character.", p);
+	}
+
+	condition = (struct av_condition *) aCalloc(1, sizeof(struct av_condition));
+	av_parse_subexpr(p, -1, condition);
+
+	return condition;
+}
+
+/**
+ * Reads and parses an entry from the achievement_db.
+ * @param wrapper: The YAML wrapper containing the entry.
+ * @param n: The sequential index of the current entry.
+ * @param source: The source YAML file.
+ * @return The parsed achievement entry or NULL in case of error.
+ */
+struct achievement_db *achievement_read_db_sub(yamlwrapper *wrapper, int n, const char *source)
+{
+	struct achievement_db *entry = NULL;
+	yamlwrapper *t = NULL;
+	yamliterator *it;
+	enum e_achievement_group group = AG_NONE;
+	int score = 0, achievement_id = 0;
+	char *group_char = NULL, *name = NULL, *condition = NULL, *mapname = NULL;
+
+	if (!yaml_node_is_defined(wrapper, "ID")) {
+		ShowWarning("achievement_read_db_sub: Missing ID in \"%s\", entry #%d, skipping.\n", source, n);
+		return NULL;
+	} else
+		achievement_id = yaml_get_int(wrapper, "ID");
+	if (achievement_id < 1 || achievement_id > INT_MAX) {
+		ShowWarning("achievement_read_db_sub: Invalid achievement ID %d in \"%s\", entry #%d (min: 1, max: %d), skipping.\n", achievement_id, source, n, INT_MAX);
+		return NULL;
+	}
+
+	if (!yaml_node_is_defined(wrapper, "Group")) {
+		ShowWarning("achievement_read_db_sub: Missing group for achievement %d in \"%s\", skipping.\n", achievement_id, source);
+		return NULL;
+	} else
+		group_char = yaml_get_c_string(wrapper, "Group");
+	if (!script_get_constant(group_char, (int *)&group)) {
+		ShowWarning("achievement_read_db_sub: Invalid group %s for achievement %d in \"%s\", skipping.\n", group_char, achievement_id, source);
+		return NULL;
+	}
+	aFree(group_char);
+
+	if (!yaml_node_is_defined(wrapper, "Name")) {
+		ShowWarning("achievement_read_db_sub: Missing achievement name for achievement %d in \"%s\", skipping.\n", name, achievement_id, source);
+		return NULL;
+	} else
+		name = yaml_get_c_string(wrapper, "Name");
+
+	CREATE(entry, struct achievement_db, 1);
+	entry->achievement_id = achievement_id;
+	entry->group = group;
+	safestrncpy(entry->name, name, sizeof(entry->name));
+	aFree(name);
+	entry->mapindex = -1;
+
+	if (yaml_node_is_defined(wrapper, "Target") && (t = yaml_get_subnode(wrapper, "Target")) && (it = yaml_get_iterator(t)) && yaml_iterator_is_valid(it)) {
+		yamlwrapper *tt = NULL;
+
+		for (tt = yaml_iterator_first(it); yaml_iterator_has_next(it) && entry->target_count < MAX_ACHIEVEMENT_OBJECTIVES; tt = yaml_iterator_next(it)) {
+			int mobid = 0, count = 0;
+
+			if (yaml_node_is_defined(tt, "MobID") && (mobid = yaml_get_int(tt, "MobID")) && !mobdb_exists(mobid)) { // The mob ID field is not required
+				ShowError("achievement_read_db_sub: Invalid mob ID %d for achievement %d in \"%s\", skipping.\n", mobid, achievement_id, source);
+				continue;
+			}
+			if (yaml_node_is_defined(tt, "Count") && (!(count = yaml_get_int(tt, "Count")) || count <= 0)) {
+				ShowError("achievement_read_db_sub: Invalid count %d for achievement %d in \"%s\", skipping.\n", count, achievement_id, source);
+				continue;
+			}
+			if (mobid && group == AG_BATTLE && !idb_exists(achievementmobs_db, mobid)) {
+				struct achievement_mob *entrymob = NULL;
+
+				CREATE(entrymob, struct achievement_mob, 1);
+				idb_put(achievementmobs_db, mobid, entrymob);
+			}
+
+			RECREATE(entry->targets, struct achievement_target, entry->target_count + 1);
+			entry->targets[entry->target_count].mob = mobid;
+			entry->targets[entry->target_count].count = count;
+			entry->target_count++;
+			yaml_destroy_wrapper(tt);
+		}
+		yaml_iterator_destroy(it);
+	}
+
+	if (yaml_node_is_defined(wrapper, "Condition") && (condition = yaml_get_c_string(wrapper, "Condition"))){
+		entry->condition = parse_condition(condition, source, n);
+		aFree(condition);
+	}
+
+	if (yaml_node_is_defined(wrapper, "Map") && (mapname = yaml_get_c_string(wrapper, "Map"))) {
+		if (group != AG_CHAT)
+			ShowWarning("achievement_read_db_sub: The map argument can only be used with the group AG_CHATTING (achievement %d in \"%s\"), skipping.\n", achievement_id, source);
+		else {
+			entry->mapindex = map_mapname2mapid(mapname);
+
+			if (entry->mapindex == -1)
+				ShowWarning("achievement_read_db_sub: Invalid map name %s for achievement %d in \"%s\".\n", mapname, achievement_id, source);
+		}
+		aFree(mapname);
+	}
+
+	if (yaml_node_is_defined(wrapper, "Dependent") && (t = yaml_get_subnode(wrapper, "Dependent")) && (it = yaml_get_iterator(t))) {
+		if (yaml_iterator_is_valid(it)) {
+			yamlwrapper *tt = NULL;
+
+			for (tt = yaml_iterator_first(it); yaml_iterator_has_next(it) && entry->dependent_count < MAX_ACHIEVEMENT_DEPENDENTS; tt = yaml_iterator_next(it)) {
+				RECREATE(entry->dependents, struct achievement_dependent, entry->dependent_count + 1);
+				entry->dependents[entry->dependent_count].achievement_id = yaml_as_int(tt);
+				entry->dependent_count++;
+				yaml_destroy_wrapper(tt);
+			}
+			yaml_iterator_destroy(it);
+		} else
+			ShowWarning("achievement_read_db_sub: Invalid dependent format for achievement %d in \"%s\".\n", achievement_id, source);
+	}
+
+	if (yaml_node_is_defined(wrapper, "Reward") && (t = yaml_get_subnode(wrapper, "Reward"))) {
+		char *script_char = NULL;
+		int nameid = 0, amount = 0, titleid = 0;
+
+		if (yaml_node_is_defined(t, "ItemID") && (nameid = yaml_get_int(t, "ItemID"))) {
+			if (itemdb_exists(nameid)) {
+				entry->rewards.nameid = nameid;
+				entry->rewards.amount = 1; // Default the amount to 1
+			} else if (nameid && !itemdb_exists(nameid)) {
+				ShowWarning("achievement_read_db_sub: Invalid reward item ID %hu for achievement %d in \"%s\". Setting to 0.\n", nameid, achievement_id, source);
+				entry->rewards.nameid = nameid = 0;
+			}
+
+			if (yaml_node_is_defined(t, "Amount") && (amount = yaml_get_int(t, "Amount")) && amount > 0 && nameid)
+				entry->rewards.amount = amount;
+		}
+		if (yaml_node_is_defined(t, "Script") && (script_char = yaml_get_c_string(t, "Script"))){
+			entry->rewards.script = parse_script(script_char, source, achievement_id, SCRIPT_IGNORE_EXTERNAL_BRACKETS);
+			aFree(script_char);
+		}
+		if (yaml_node_is_defined(t, "TitleID") && (titleid = yaml_get_int(t, "TitleID")) && titleid > 0)
+			entry->rewards.title_id = titleid;
+	}
+
+	if ((score = yaml_get_int(wrapper, "Score")) && score > 0)
+		entry->score = score;
+
+	return entry;
+}
+
+/**
+ * Loads achievements from the achievement db.
+ */
+void achievement_read_db(void)
+{
+	yamlwrapper *adb = NULL, *adb_sub = NULL;
+	yamliterator *it;
+	int i = 0;
+	const char *dbsubpath[] = {
+		"",
+		"/"DBIMPORT"/",
+		//add other path here
+	};
+
+	for (i = 0; i < ARRAYLENGTH(dbsubpath); i++) {
+		char filepath[256];
+		int count = 0;
+
+		if (!i)
+			sprintf(filepath, "%s/%s%s%s", db_path, DBPATH, dbsubpath[i], "achievement_db.yml");
+		else
+			sprintf(filepath, "%s%s%s", db_path, dbsubpath[i], "achievement_db.yml");
+
+		if ((adb = yaml_load_file(filepath)) == NULL) {
+			ShowError("Failed to read '%s'.\n", filepath);
+			continue;
+		}
+
+		if (!yaml_node_is_defined(adb, "Achievements"))
+			continue; // Skip if base structure isn't defined
+		adb_sub = yaml_get_subnode(adb, "Achievements");
+		it = yaml_get_iterator(adb_sub);
+		if (yaml_iterator_is_valid(it)) {
+			yamlwrapper *id = NULL;
+
+			for (id = yaml_iterator_first(it); yaml_iterator_has_next(it); id = yaml_iterator_next(it)) {
+				struct achievement_db *duplicate = &achievement_dummy, *entry = achievement_read_db_sub(id, count, filepath);
+
+				if (!entry) {
+					ShowWarning("achievement_read_db: Failed to parse achievement entry %d.\n", count);
+					continue;
+				}
+				if ((duplicate = achievement_search(entry->achievement_id)) != &achievement_dummy) {
+					if (!i) { // Normal file read-in
+						ShowWarning("achievement_read_db: Duplicate achievement %d.\n", entry->achievement_id);
+						achievement_db_free_sub(entry, false);
+						continue;
+					}
+					else // Import file read-in, free previous value and store new value
+						achievement_db_free_sub(duplicate, false);
+				}
+				yaml_destroy_wrapper(id);
+				idb_put(achievement_db, entry->achievement_id, entry);
+				count++;
+			}
+		}
+		yaml_destroy_wrapper(adb_sub);
+		yaml_iterator_destroy(it);
+
+		ShowStatus("Done reading '"CL_WHITE"%d"CL_RESET"' entries in '"CL_WHITE"%s"CL_RESET"'.\n", count, filepath);
+	}
+
+	return;
+}
+
+/**
+ * Recursive method to free an achievement condition
+ * @param condition: Condition to clear
+ */
+void achievement_script_free(struct av_condition *condition) 
+{
+	if (condition->left) {
+		achievement_script_free(condition->left);
+		condition->left = NULL;
+	}
+
+	if (condition->right) {
+		achievement_script_free(condition->right);
+		condition->right = NULL;
+	}
+
+	aFree(condition);
+}
+
+/**
+ * Clear achievement single entry
+ * @param achievement: Achievement to clear
+ * @param free: Will free achievement from memory
+ */
+void achievement_db_free_sub(struct achievement_db *achievement, bool free)
+{
+	if (achievement->targets) {
+		aFree(achievement->targets);
+		achievement->targets = NULL;
+		achievement->target_count = 0;
+	}
+	if (achievement->condition) {
+		achievement_script_free(achievement->condition);
+		achievement->condition = NULL;
+	}
+	if (achievement->dependents) {
+		aFree(achievement->dependents);
+		achievement->dependents = NULL;
+		achievement->dependent_count = 0;
+	}
+	if (achievement->rewards.script) {
+		script_free_code(achievement->rewards.script);
+		achievement->rewards.script = NULL;
+	}
+	if (free)
+		aFree(achievement);
+}
+
+/**
+ * Clears the achievement database for shutdown or reload.
+ */
+static int achievement_db_free(DBKey key, DBData *data, va_list ap)
+{
+	struct achievement_db *achievement = (struct achievement_db *)db_data2ptr(data);
+
+	if (!achievement)
+		return 0;
+
+	achievement_db_free_sub(achievement, true);
+	return 1;
+}
+
+static int achievementmobs_db_free(DBKey key, DBData *data, va_list ap)
+{
+	struct achievementmobs_db *achievement = (struct achievementmobs_db *)db_data2ptr(data);
+
+	if (!achievement)
+		return 0;
+
+	aFree(achievement);
+	return 1;
+}
+
+void achievement_db_reload(void)
+{
+	if (!battle_config.feature_achievement)
+		return;
+	achievementmobs_db->clear(achievementmobs_db, achievementmobs_db_free);
+	achievement_db->clear(achievement_db, achievement_db_free);
+	achievement_read_db();
+}
+
+void do_init_achievement(void)
+{
+	if (!battle_config.feature_achievement)
+		return;
+	memset(&achievement_dummy, 0, sizeof(achievement_dummy));
+	achievement_db = idb_alloc(DB_OPT_BASE);
+	achievementmobs_db = idb_alloc(DB_OPT_BASE);
+	achievement_read_db();
+}
+
+void do_final_achievement(void)
+{
+	if (!battle_config.feature_achievement)
+		return;
+	achievementmobs_db->destroy(achievementmobs_db, achievementmobs_db_free);
+	achievement_db->destroy(achievement_db, achievement_db_free);
+}

+ 135 - 0
src/map/achievement.h

@@ -0,0 +1,135 @@
+// Copyright (c) Athena Dev Teams - Licensed under GNU GPL
+// For more information, see LICENCE in the main folder
+
+#ifndef MAP_ACHIEVEMENTS_H
+#define MAP_ACHIEVEMENTS_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "../common/mmo.h"
+#include "../common/db.h"
+
+enum e_achievement_group {
+	AG_NONE = 0,
+	AG_ADD_FRIEND,
+	AG_ADVENTURE,
+	AG_BABY,
+	AG_BATTLE,
+	AG_CHAT,
+	AG_CHAT_COUNT,
+	AG_CHAT_CREATE,
+	AG_CHAT_DYING,
+	AG_EAT,
+	AG_GET_ITEM,
+	AG_GET_ZENY,
+	AG_GOAL_ACHIEVE,
+	AG_GOAL_LEVEL,
+	AG_GOAL_STATUS,
+	AG_HEAR,
+	AG_JOB_CHANGE,
+	AG_MARRY,
+	AG_PARTY,
+	AG_REFINE_FAIL,
+	AG_REFINE_SUCCESS,
+	AG_SEE,
+	AG_SPEND_ZENY,
+	AG_TAMING,
+	AG_MAX
+};
+
+enum e_achievement_info {
+	ACHIEVEINFO_COUNT1 = 1,
+	ACHIEVEINFO_COUNT2,
+	ACHIEVEINFO_COUNT3,
+	ACHIEVEINFO_COUNT4,
+	ACHIEVEINFO_COUNT5,
+	ACHIEVEINFO_COUNT6,
+	ACHIEVEINFO_COUNT7,
+	ACHIEVEINFO_COUNT8,
+	ACHIEVEINFO_COUNT9,
+	ACHIEVEINFO_COUNT10,
+	ACHIEVEINFO_COMPLETE,
+	ACHIEVEINFO_COMPLETEDATE,
+	ACHIEVEINFO_GOTREWARD,
+	ACHIEVEINFO_LEVEL,
+	ACHIEVEINFO_SCORE,
+	ACHIEVEINFO_MAX,
+};
+
+struct achievement_mob {
+	int mod_id;
+};
+
+struct achievement_target {
+	int mob;
+	int count;
+};
+
+struct achievement_dependent {
+	int achievement_id;
+};
+
+struct av_condition {
+	int op;
+	struct av_condition *left;
+	struct av_condition *right;
+	long long value;
+};
+
+struct achievement_db {
+	int achievement_id;
+	char name[ACHIEVEMENT_NAME_LENGTH];
+	enum e_achievement_group group;
+	uint8 target_count;
+	struct achievement_target *targets;
+	uint8 dependent_count;
+	struct achievement_dependent *dependents;
+	struct av_condition *condition;
+	int16 mapindex;
+	struct ach_reward {
+		unsigned short nameid, amount;
+		struct script_code *script;
+		int title_id;
+	} rewards;
+	int score;
+	int has_dependent; // Used for quick updating of achievements that depend on others - this is their ID
+};
+
+struct map_session_data;
+struct block_list;
+struct config_setting_t;
+enum _sp;
+
+struct achievement_db achievement_dummy;	///< Dummy entry for invalid achievement lookups
+
+struct achievement_db *achievement_search(int achievement_id);
+bool achievement_mobexists(int mob_id);
+void achievement_get_reward(struct map_session_data *sd, int achievement_id, time_t rewarded);
+struct achievement *achievement_add(struct map_session_data *sd, int achievement_id);
+bool achievement_remove(struct map_session_data *sd, int achievement_id);
+bool achievement_update_achievement(struct map_session_data *sd, int achievement_id, bool complete);
+void achievement_check_reward(struct map_session_data *sd, int achievement_id);
+void achievement_free(struct map_session_data *sd);
+int achievement_check_progress(struct map_session_data *sd, int achievement_id, int type);
+int *achievement_level(struct map_session_data *sd, bool flag);
+void achievement_get_titles(uint32 char_id);
+void achievement_update_objective(struct map_session_data *sd, enum e_achievement_group group, uint8 arg_count, ...);
+void achievement_read_db(void);
+void achievement_db_reload(void);
+
+void do_init_achievement(void);
+void do_final_achievement(void);
+
+// Parser
+const char *av_parse_subexpr(const char *p,int limit, struct av_condition *parent);
+const char *av_parse_simpleexpr(const char *p, struct av_condition *parent);
+long long achievement_check_condition(struct av_condition *condition, struct map_session_data *sd, int *count);
+void achievement_script_free(struct av_condition *condition);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* MAP_ACHIEVEMENTS_H */

+ 9 - 0
src/map/atcommand.c

@@ -34,6 +34,7 @@
 #include "mapreg.h"
 #include "quest.h"
 #include "pc.h"
+#include "achievement.h"
 
 #include <stdlib.h>
 #include <math.h>
@@ -1422,6 +1423,8 @@ ACMD_FUNC(baselevelup)
 		status_calc_pc(sd, SCO_FORCE);
 		status_percent_heal(&sd->bl, 100, 100);
 		clif_misceffect(&sd->bl, 0);
+		achievement_update_objective(sd, AG_GOAL_LEVEL, 1, sd->status.base_level);
+		achievement_update_objective(sd, AG_GOAL_STATUS, 2, sd->status.base_level, sd->status.class_);
 		clif_displaymessage(fd, msg_txt(sd,21)); // Base level raised.
 	} else {
 		if (sd->status.base_level == 1) {
@@ -1483,6 +1486,7 @@ ACMD_FUNC(joblevelup)
 		sd->status.job_level += (unsigned int)level;
 		sd->status.skill_point += level;
 		clif_misceffect(&sd->bl, 1);
+		achievement_update_objective(sd, AG_GOAL_LEVEL, 1, sd->status.job_level);
 		clif_displaymessage(fd, msg_txt(sd,24)); // Job level raised.
 	} else {
 		if (sd->status.job_level == 1) {
@@ -2277,6 +2281,7 @@ ACMD_FUNC(refine)
 			clif_additem(sd, i, 1, 0);
 			pc_equipitem(sd, i, current_position);
 			clif_misceffect(&sd->bl, 3);
+			achievement_update_objective(sd, AG_REFINE_SUCCESS, 2, sd->inventory_data[i]->wlv, sd->inventory.u.items_inventory[i].refine);
 			count++;
 		}
 	}
@@ -3930,6 +3935,9 @@ ACMD_FUNC(reload) {
 	} else if (strstr(command, "instancedb") || strncmp(message, "instancedb", 4) == 0) {
 		instance_reload();
 		clif_displaymessage(fd, msg_txt(sd,516)); // Instance database has been reloaded.
+	} else if (strstr(command, "achievementdb") || strncmp(message, "achievementdb", 4) == 0) {
+		achievement_db_reload();
+		clif_displaymessage(fd, msg_txt(sd,771)); // Achievement database has been reloaded.
 	}
 
 	return 0;
@@ -10083,6 +10091,7 @@ void atcommand_basecommands(void) {
 		ACMD_DEF2("reloadquestdb", reload),
 		ACMD_DEF2("reloadmsgconf", reload),
 		ACMD_DEF2("reloadinstancedb", reload),
+		ACMD_DEF2("reloadachievementdb",reload),
 		ACMD_DEF(partysharelvl),
 		ACMD_DEF(mapinfo),
 		ACMD_DEF(dye),

+ 8 - 0
src/map/battle.c

@@ -8427,6 +8427,7 @@ static const struct _battle_data {
 	{ "banana_bomb_duration",				&battle_config.banana_bomb_duration,			0,		0,		UINT16_MAX,		},
 	{ "guild_leaderchange_delay",			&battle_config.guild_leaderchange_delay,		1440,	0,		INT32_MAX,		},
 	{ "guild_leaderchange_woe",				&battle_config.guild_leaderchange_woe,			0,		0,		1,				},
+	{ "feature.achievement",                &battle_config.feature_achievement,             1,      0,      1,              },
 
 #include "../custom/battle_config_init.inc"
 };
@@ -8550,6 +8551,13 @@ void battle_adjust_conf()
 	}
 #endif
 
+#if PACKETVER < 20150513
+	if (battle_config.feature_achievement) {
+		ShowWarning("conf/battle/feature.conf achievement is enabled but it requires PACKETVER 2015-05-13 or newer, disabling...\n");
+		battle_config.feature_achievement = 0;
+	}
+#endif
+
 #ifndef CELL_NOSTACK
 	if (battle_config.custom_cell_stack_limit != 1)
 		ShowWarning("Battle setting 'custom_cell_stack_limit' takes no effect as this server was compiled without Cell Stack Limit support.\n");

+ 1 - 0
src/map/battle.h

@@ -627,6 +627,7 @@ extern struct Battle_Config
 	int banana_bomb_duration;
 	int guild_leaderchange_delay;
 	int guild_leaderchange_woe;
+	int feature_achievement;
 
 #include "../custom/battle_config_struct.inc"
 } battle_config;

+ 9 - 0
src/map/chat.c

@@ -14,6 +14,7 @@
 #include "npc.h" // npc_event_do()
 #include "pc.h"
 #include "chat.h"
+#include "achievement.h"
 
 
 int chat_triggerevent(struct chat_data *cd); // forward declaration
@@ -102,6 +103,11 @@ int chat_createpcchat(struct map_session_data* sd, const char* title, const char
 		pc_stop_attack(sd);
 		clif_createchat(sd,0);
 		clif_dispchat(cd,0);
+
+		if (status_isdead(&sd->bl))
+			achievement_update_objective(sd, AG_CHAT_DYING, 1, 1);
+		else
+			achievement_update_objective(sd, AG_CHAT_CREATE, 1, 1);
 	} else
 		clif_createchat(sd,1);
 
@@ -164,6 +170,9 @@ int chat_joinchat(struct map_session_data* sd, int chatid, const char* pass)
 
 	chat_triggerevent(cd); //Event
 
+	if (cd->owner->type == BL_PC)
+		achievement_update_objective(map_id2sd(cd->owner->id), AG_CHAT_COUNT, 1, cd->users);
+
 	return 0;
 }
 

+ 2 - 0
src/map/chrif.c

@@ -362,6 +362,8 @@ int chrif_save(struct map_session_data *sd, int flag) {
 		elemental_save(sd->ed);
 	if( sd->save_quest )
 		intif_quest_save(sd);
+	if (sd->achievement_data.save)
+		intif_achievement_save(sd);
 
 	return 0;
 }

+ 183 - 1
src/map/clif.c

@@ -45,6 +45,7 @@
 #include "quest.h"
 #include "cashshop.h"
 #include "channel.h"
+#include "achievement.h"
 
 #include <stdio.h>
 #include <stdlib.h>
@@ -9464,7 +9465,7 @@ void clif_name( struct block_list* src, struct block_list *bl, send_target targe
 			}
 
 #if PACKETVER >= 20150513
-			WBUFL(buf,102) = 0; // Title ID
+			WBUFL(buf,102) = sd->status.title_id; // Title ID
 #endif
 		}
 		break;
@@ -9489,7 +9490,11 @@ void clif_name( struct block_list* src, struct block_list *bl, send_target targe
 			safestrncpy(WBUFCP(buf,6), md->name, NAME_LENGTH);
 			if( md->guardian_data && md->guardian_data->guild_id )
 			{
+#if PACKETVER >= 20150513
+				WBUFW(buf, 0) = cmd = 0xa30;
+#else
 				WBUFW(buf, 0) = cmd = 0x195;
+#endif
 				WBUFB(buf,30) = 0;
 				safestrncpy(WBUFCP(buf,54), md->guardian_data->guild_name, NAME_LENGTH);
 				safestrncpy(WBUFCP(buf,78), md->guardian_data->castle->castle_name, NAME_LENGTH);
@@ -9497,7 +9502,11 @@ void clif_name( struct block_list* src, struct block_list *bl, send_target targe
 			else if( battle_config.show_mob_info )
 			{
 				char mobhp[50], *str_p = mobhp;
+#if PACKETVER >= 20150513
+				WBUFW(buf, 0) = cmd = 0xa30;
+#else
 				WBUFW(buf, 0) = cmd = 0x195;
+#endif
 				if( battle_config.show_mob_info&4 )
 					str_p += sprintf(str_p, "Lv. %d | ", md->level);
 				if( battle_config.show_mob_info&1 )
@@ -9514,6 +9523,9 @@ void clif_name( struct block_list* src, struct block_list *bl, send_target targe
 					WBUFB(buf,78) = 0;
 				}
 			}
+#if PACKETVER >= 20150513
+			WBUFL(buf, 102) = 0; // Title ID
+#endif
 		}
 		break;
 	case BL_CHAT:	//FIXME: Clients DO request this... what should be done about it? The chat's title may not fit... [Skotlex]
@@ -10810,6 +10822,7 @@ void clif_parse_GlobalMessage(int fd, struct map_session_data* sd)
 
 	// Chat logging type 'O' / Global Chat
 	log_chat(LOG_CHAT_GLOBAL, 0, sd->status.char_id, sd->status.account_id, mapindex_id2name(sd->mapindex), sd->bl.x, sd->bl.y, NULL, message);
+	//achievement_update_objective(sd, AG_CHAT, 1, sd->bl.m); //! TODO: What's the official use of this achievement type?
 }
 
 
@@ -14431,6 +14444,8 @@ void clif_parse_FriendsListReply(int fd, struct map_session_data *sd)
 		safestrncpy(f_sd->status.friends[i].name, sd->status.name, NAME_LENGTH);
 		clif_friendslist_reqack(f_sd, sd, 0);
 
+		achievement_update_objective(f_sd, AG_ADD_FRIEND, 1, i + 1);
+
 		if (battle_config.friend_auto_add) {
 			// Also add f_sd to sd's friendlist.
 			for (i = 0; i < MAX_FRIENDS; i++) {
@@ -14448,6 +14463,8 @@ void clif_parse_FriendsListReply(int fd, struct map_session_data *sd)
 			sd->status.friends[i].char_id = f_sd->status.char_id;
 			safestrncpy(sd->status.friends[i].name, f_sd->status.name, NAME_LENGTH);
 			clif_friendslist_reqack(sd, f_sd, 0);
+
+			achievement_update_objective(sd, AG_ADD_FRIEND, 1, i + 1);
 		}
 	}
 }
@@ -18848,6 +18865,60 @@ void clif_clan_leave( struct map_session_data* sd ){
 #endif
 }
 
+/**
+ * Acknowledge the client about change title result (ZC_ACK_CHANGE_TITLE).
+ * 0A2F <result>.B <title_id>.L
+ */
+void clif_change_title_ack(struct map_session_data *sd, unsigned char result, unsigned long title_id)
+{
+#if PACKETVER >= 20150513
+	int fd;
+
+	nullpo_retv(sd);
+
+	if (!clif_session_isValid(sd))
+		return;
+	fd = sd->fd;
+
+	WFIFOHEAD(fd, packet_len(0xa2f));
+	WFIFOW(fd, 0) = 0xa2f;
+	WFIFOB(fd, 2) = result;
+	WFIFOL(fd, 3) = title_id;
+	WFIFOSET(fd, packet_len(0xa2f));
+#endif
+}
+
+/**
+ * Parsing a request from the client change title (CZ_REQ_CHANGE_TITLE).
+ * 0A2E <title_id>.L
+ */
+void clif_parse_change_title(int fd, struct map_session_data *sd)
+{
+	int title_id, i;
+
+	nullpo_retv(sd);
+
+	title_id = RFIFOL(fd, 2);
+
+	if( title_id == sd->status.title_id ){
+		// It is exactly the same as the old one
+		return;
+	}else if( title_id <= 0 ){
+		sd->status.title_id = 0;
+	}else{
+		ARR_FIND(0, sd->titleCount, i, sd->titles[i] == title_id);
+		if( i == sd->titleCount ){
+			clif_change_title_ack(sd, 1, title_id);
+			return;
+		}
+
+		sd->status.title_id = title_id;
+	}
+	
+	clif_name_area(&sd->bl);
+	clif_change_title_ack(sd, 0, title_id);
+}
+
 #ifdef DUMP_UNKNOWN_PACKET
 void DumpUnknown(int fd,TBL_PC *sd,int cmd,int packet_len)
 {
@@ -19770,6 +19841,117 @@ void clif_parse_sale_remove( int fd, struct map_session_data* sd ){
 #endif
 }
 
+/// Achievement System
+/// Author: Luxuri, Aleos
+
+/**
+ * Sends all achievement data to the client (ZC_ALL_AG_LIST).
+ * 0a23 <packetType>.W <packetLength>.W <ACHCount>.L <ACHPoint>.L
+ */
+void clif_achievement_list_all(struct map_session_data *sd)
+{
+	int i, j, len, fd, *info;
+	uint16 count = 0;
+
+	nullpo_retv(sd);
+
+	if (!battle_config.feature_achievement) {
+		clif_messagecolor(&sd->bl,color_table[COLOR_RED],msg_txt(sd,772),false,SELF); // Achievements are disabled.
+		return;
+	}
+
+	fd = sd->fd;
+	count = sd->achievement_data.count; // All achievements should be sent to the client
+	len = (50 * count) + 22;
+
+	if (len <= 22)
+		return;
+
+	info = achievement_level(sd, true);
+
+	WFIFOHEAD(fd,len);
+	WFIFOW(fd, 0) = 0xa23;
+	WFIFOW(fd, 2) = len;
+	WFIFOL(fd, 4) = count; // Amount of achievements the player has in their list (started/completed)
+	WFIFOL(fd, 8) = sd->achievement_data.total_score; // Top number
+	WFIFOW(fd, 12) = sd->achievement_data.level; // Achievement Level (gold circle)
+	WFIFOL(fd, 14) = info[0]; // Achievement EXP (left number in bar)
+	WFIFOL(fd, 18) = info[1]; // Achievement EXP TNL (right number in bar)
+
+	for (i = 0; i < count; i++) {
+		WFIFOL(fd, i * 50 + 22) = (uint32)sd->achievement_data.achievements[i].achievement_id;
+		WFIFOB(fd, i * 50 + 26) = (uint32)sd->achievement_data.achievements[i].completed > 0;
+		for (j = 0; j < MAX_ACHIEVEMENT_OBJECTIVES; j++) 
+			WFIFOL(fd, (i * 50) + 27 + (j * 4)) = (uint32)sd->achievement_data.achievements[i].count[j];
+		WFIFOL(fd, i * 50 + 67) = (uint32)sd->achievement_data.achievements[i].completed;
+		WFIFOB(fd, i * 50 + 71) = sd->achievement_data.achievements[i].rewarded > 0;
+	}
+	WFIFOSET(fd, len);
+}
+
+/**
+ * Sends a single achievement's data to the client (ZC_AG_UPDATE).
+ * 0a24 <packetType>.W <ACHPoint>.L
+ */
+void clif_achievement_update(struct map_session_data *sd, struct achievement *ach, int count)
+{
+	int fd, i, *info;
+
+	nullpo_retv(sd);
+
+	if (!battle_config.feature_achievement) {
+		clif_messagecolor(&sd->bl,color_table[COLOR_RED],msg_txt(sd,772),false,SELF); // Achievements are disabled.
+		return;
+	}
+
+	fd = sd->fd;
+	info = achievement_level(sd, true);
+
+	WFIFOHEAD(fd, packet_len(0xa24));
+	WFIFOW(fd, 0) = 0xa24;
+	WFIFOL(fd, 2) = sd->achievement_data.total_score; // Total Achievement Points (top of screen)
+	WFIFOW(fd, 6) = sd->achievement_data.level; // Achievement Level (gold circle)
+	WFIFOL(fd, 8) = info[0]; // Achievement EXP (left number in bar)
+	WFIFOL(fd, 12) = info[1]; // Achievement EXP TNL (right number in bar)
+	if (ach) {
+		WFIFOL(fd, 16) = ach->achievement_id; // Achievement ID
+		WFIFOB(fd, 20) = ach->completed > 0; // Is it complete?
+		for (i = 0; i < MAX_ACHIEVEMENT_OBJECTIVES; i++)
+			WFIFOL(fd, 21 + (i * 4)) = (uint32)ach->count[i]; // 1~10 pre-reqs
+		WFIFOL(fd, 61) = (uint32)ach->completed; // Epoch time
+		WFIFOB(fd, 65) = ach->rewarded > 0; // Got reward?
+	} else
+		memset(WFIFOP(fd, 16), 0, 40);
+	WFIFOSET(fd, packet_len(0xa24));
+}
+
+/**
+ * Checks if an achievement reward can be rewarded (CZ_REQ_AG_REWARD).
+ * 0a25 <packetType>.W <achievementID>.L
+ */
+void clif_parse_AchievementCheckReward(int fd, struct map_session_data *sd)
+{
+	nullpo_retv(sd);
+
+	if( sd->achievement_data.save )
+		intif_achievement_save(sd);
+
+	achievement_check_reward(sd, RFIFOL(fd,2));
+}
+
+/**
+ * Returns the result of achievement_check_reward (ZC_REQ_AG_REWARD_ACK).
+ * 0a26 <packetType>.W <result>.W <achievementID>.L
+ */
+void clif_achievement_reward_ack(int fd, unsigned char result, int achievement_id)
+{
+	WFIFOHEAD(fd, packet_len(0xa26));
+	WFIFOW(fd, 0) = 0xa26;
+	WFIFOB(fd, 2) = result;
+	WFIFOL(fd, 3) = achievement_id;
+	WFIFOSET(fd, packet_len(0xa26));
+}
+
 /*==========================================
  * Main client packet processing function
  *------------------------------------------*/

+ 7 - 0
src/map/clif.h

@@ -39,6 +39,7 @@ struct sale_item_data;
 enum mail_inbox_type;
 struct mail_message;
 enum mail_attachment_type;
+struct achievement;
 #include <stdarg.h>
 
 enum { // packet DB
@@ -1051,6 +1052,12 @@ void clif_dressing_room(struct map_session_data *sd, int flag);
 void clif_navigateTo(struct map_session_data *sd, const char* mapname, uint16 x, uint16 y, uint8 flag, bool hideWindow, uint16 mob_id );
 void clif_SelectCart(struct map_session_data *sd);
 
+/// Achievement System
+void clif_achievement_list_all(struct map_session_data *sd);
+void clif_achievement_update(struct map_session_data *sd, struct achievement *ach, int count);
+void clif_pAchievementCheckReward(int fd, struct map_session_data *sd);
+void clif_achievement_reward_ack(int fd, unsigned char result, int ach_id);
+
 #ifdef __cplusplus
 }
 #endif

+ 2 - 2
src/map/clif_packetdb.h

@@ -2270,10 +2270,10 @@
 	// Achievement System
 	packet(0x0A23,-1); // ZC_ALL_ACH_LIST
 	packet(0x0A24,66); // ZC_ACH_UPDATE
-	parseable_packet(0x0A25,6,clif_parse_dull,0); // CZ_REQ_ACH_REWARD
+	parseable_packet(0x0A25,6,clif_parse_AchievementCheckReward,0); // CZ_REQ_ACH_REWARD
 	packet(0x0A26,7); // ZC_REQ_ACH_REWARD_ACK
 	// Title System
-	parseable_packet(0x0A2E,6,clif_parse_dull,0); // CZ_REQ_CHANGE_TITLE
+	parseable_packet(0x0A2E,6,clif_parse_change_title,0); // CZ_REQ_CHANGE_TITLE
 	packet(0x0A2F,7); // ZC_ACK_CHANGE_TITLE
 	packet(0x0A30,106); // ZC_ACK_REQNAMEALL2
 	// Pet Evolution System

+ 161 - 1
src/map/intif.c

@@ -23,6 +23,7 @@
 #include "mail.h"
 #include "quest.h"
 #include "status.h"
+#include "achievement.h"
 
 #include <stdlib.h>
 
@@ -34,7 +35,7 @@ static const int packet_len_table[] = {
 	10,-1,15, 0, 79,19, 7,-1,  0,-1,-1,-1, 14,67,186,-1, //0x3830
 	-1, 0, 0,18,  0, 0, 0, 0, -1,75,-1,11, 11,-1, 38, 0, //0x3840
 	-1,-1, 7, 7,  7,11, 8,-1,  0, 0, 0, 0,  0, 0,  0, 0, //0x3850  Auctions [Zephyrus] itembound[Akinari]
-	-1, 7, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0,  0, 0, //0x3860  Quests [Kevin] [Inkfish]
+	-1, 7,-1, 7, 14, 0, 0, 0,  0, 0, 0, 0,  0, 0,  0, 0, //0x3860  Quests [Kevin] [Inkfish] / Achievements [Aleos]
 	-1, 3, 3, 0,  0, 0, 0, 0,  0, 0, 0, 0, -1, 3,  3, 0, //0x3870  Mercenaries [Zephyrus] / Elemental [pakpil]
 	12,-1, 7, 3,  0, 0, 0, 0,  0, 0,-1, 9, -1, 0,  0, 0, //0x3880  Pet System,  Storages
 	-1,-1, 7, 3,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0,  0, 0, //0x3890  Homunculus [albator]
@@ -2076,6 +2077,160 @@ int intif_quest_save(struct map_session_data *sd)
 	return 1;
 }
 
+/*==========================================
+ * Achievement System
+ *------------------------------------------*/
+
+/**
+ * Requests a character's achievement log entries to the inter server.
+ * @param char_id: Character ID
+ */
+void intif_request_achievements(uint32 char_id)
+{
+	if (CheckForCharServer())
+		return;
+
+	WFIFOHEAD(inter_fd, 6);
+	WFIFOW(inter_fd, 0) = 0x3062;
+	WFIFOL(inter_fd, 2) = char_id;
+	WFIFOSET(inter_fd, 6);
+}
+
+/**
+ * Receive a character's achievements
+ * @param fd: char-serv link
+ */
+void intif_parse_achievements(int fd)
+{
+	uint32 char_id = RFIFOL(fd, 4), num_received = (RFIFOW(fd, 2) - 8) / sizeof(struct achievement);
+	struct map_session_data *sd = map_charid2sd(char_id);
+
+	if (!sd) // User not online anymore
+		return;
+
+	if (num_received == 0) {
+		if (sd->achievement_data.achievements) {
+			aFree(sd->achievement_data.achievements);
+			sd->achievement_data.achievements = NULL;
+		}
+	} else {
+		struct achievement *received = (struct achievement *)RFIFOP(fd, 8);
+		int i, k = num_received;
+
+		if (sd->achievement_data.achievements)
+			RECREATE(sd->achievement_data.achievements, struct achievement, num_received);
+		else
+			CREATE(sd->achievement_data.achievements, struct achievement, num_received);
+
+		for (i = 0; i < num_received; i++) {
+			struct achievement_db *adb = achievement_search(received[i].achievement_id);
+
+			if (!adb) {
+				ShowError("intif_parse_achievementlog: Achievement %d not found in DB.\n", received[i].achievement_id);
+				continue;
+			}
+
+			received[i].score = adb->score;
+
+			if (received[i].completed == 0) // Insert at the beginning
+				memcpy(&sd->achievement_data.achievements[sd->achievement_data.incompleteCount++], &received[i], sizeof(struct achievement));
+			else // Insert at the end
+				memcpy(&sd->achievement_data.achievements[--k], &received[i], sizeof(struct achievement));
+			sd->achievement_data.count++;
+		}
+		if (sd->achievement_data.incompleteCount < k) {
+			// sd->achievement_data.incompleteCount and k didn't meet in the middle: some entries were skipped
+			if (k < num_received) // Move the entries at the end to fill the gap
+				memmove(&sd->achievement_data.achievements[k], &sd->achievement_data.achievements[sd->achievement_data.incompleteCount], sizeof(struct achievement) * (num_received - k));
+			sd->achievement_data.achievements = (struct achievement *)aRealloc(sd->achievement_data.achievements, sizeof(struct achievement) * sd->achievement_data.count);
+		}
+		achievement_level(sd, false); // Calculate level info but don't give any AG_GOAL_ACHIEVE achievements
+		achievement_get_titles(sd->status.char_id); // Populate the title list for completed achievements
+		clif_achievement_update(sd, NULL, 0);
+		clif_achievement_list_all(sd);
+	}
+}
+
+/**
+ * Parses the achievement log save ack for a character from the inter server.
+ * Received in reply to the requests made by intif_achievement_save.
+ * @see intif_parse
+ * @param fd : char-serv link
+ */
+void intif_parse_achievementsave(int fd)
+{
+	int cid = RFIFOL(fd, 2);
+	struct map_session_data *sd = map_charid2sd(cid);
+
+	if (!sd) // User not online anymore
+		return;
+
+	if (!RFIFOB(fd, 6))
+		ShowError("intif_parse_achievementsave: Failed to save achievement(s) for character %s (%d)!\n", sd->status.name, cid);
+}
+
+/**
+ * Requests to the inter server to save a character's achievement log entries.
+ * @param sd: Character's data
+ * @return 0 in case of success, nonzero otherwise
+ */
+int intif_achievement_save(struct map_session_data *sd)
+{
+	int len = sizeof(struct achievement) * sd->achievement_data.count + 8;
+
+	if (CheckForCharServer())
+		return 0;
+
+	WFIFOHEAD(inter_fd, len);
+	WFIFOW(inter_fd, 0) = 0x3063;
+	WFIFOW(inter_fd, 2) = len;
+	WFIFOL(inter_fd, 4) = sd->status.char_id;
+	if (sd->achievement_data.count)
+		memcpy(WFIFOP(inter_fd, 8), sd->achievement_data.achievements, sizeof(struct achievement) * sd->achievement_data.count);
+	WFIFOSET(inter_fd, len);
+
+	sd->achievement_data.save = false;
+
+	return 1;
+}
+
+/**
+ * Parses the reply of the reward claiming for a achievement from the inter server.
+ * @see intif_parse
+ * @param fd : char-serv link
+ */
+void intif_parse_achievementreward(int fd){
+	struct map_session_data *sd = map_charid2sd(RFIFOL(fd,2));
+
+	// User not online anymore
+	if( !sd ){
+		return;
+	}
+
+	achievement_get_reward(sd, RFIFOL(fd, 6), RFIFOL(fd, 10));
+}
+
+/**
+ * Request the achievement rewards from the inter server.
+ */
+int intif_achievement_reward(struct map_session_data *sd, struct achievement_db *adb){
+	if( CheckForCharServer() ){
+		return 0;
+	}
+
+	WFIFOHEAD(inter_fd, 16+NAME_LENGTH+ACHIEVEMENT_NAME_LENGTH);
+	WFIFOW(inter_fd, 0) = 0x3064;
+	WFIFOL(inter_fd, 2) = sd->status.char_id;
+	WFIFOL(inter_fd, 6) = adb->achievement_id;
+	WFIFOW(inter_fd, 10) = adb->rewards.nameid;
+	WFIFOL(inter_fd, 12) = adb->rewards.amount;
+	safestrncpy(WFIFOCP(inter_fd, 16), sd->status.name, NAME_LENGTH);
+	safestrncpy(WFIFOCP(inter_fd, 16+NAME_LENGTH), adb->name, ACHIEVEMENT_NAME_LENGTH);
+	WFIFOSET(inter_fd, 16+NAME_LENGTH+ACHIEVEMENT_NAME_LENGTH);
+
+	return 1;
+}
+
 /*==========================================
  * MAIL SYSTEM
  * By Zephyrus
@@ -3601,6 +3756,11 @@ int intif_parse(int fd)
 	case 0x3860:	intif_parse_questlog(fd); break;
 	case 0x3861:	intif_parse_questsave(fd); break;
 
+	//Achievement system
+	case 0x3862:	intif_parse_achievements(fd); break;
+	case 0x3863:	intif_parse_achievementsave(fd); break;
+	case 0x3864:	intif_parse_achievementreward(fd); break;
+
 	// Mercenary System
 	case 0x3870:	intif_parse_mercenary_received(fd); break;
 	case 0x3871:	intif_parse_mercenary_deleted(fd); break;

+ 6 - 0
src/map/intif.h

@@ -18,6 +18,8 @@ struct s_mercenary;
 struct s_elemental;
 struct mail_message;
 struct auction_data;
+enum storage_type;
+struct achievement_db;
 
 int intif_parse(int fd);
 
@@ -116,6 +118,10 @@ int intif_clan_requestclans();
 int intif_clan_message(int clan_id,uint32 account_id,const char *mes,int len);
 int intif_clan_member_joined( int clan_id );
 int intif_clan_member_left( int clan_id );
+// ACHIEVEMENT SYSTEM
+void intif_request_achievements(uint32 char_id);
+int intif_achievement_save(struct map_session_data *sd);
+int intif_achievement_reward(struct map_session_data *sd, struct achievement_db *adb);
 
 int intif_request_accinfo(int u_fd, int aid, int group_lv, char* query, char type);
 

+ 3 - 0
src/map/map-server.vcxproj

@@ -159,6 +159,7 @@
     </Link>
   </ItemDefinitionGroup>
   <ItemGroup>
+    <ClInclude Include="achievement.h" />
     <ClInclude Include="atcommand.h" />
     <ClInclude Include="battle.h" />
     <ClInclude Include="battleground.h" />
@@ -204,6 +205,7 @@
     <ClInclude Include="vending.h" />
   </ItemGroup>
   <ItemGroup>
+    <ClCompile Include="achievement.c" />
     <ClCompile Include="atcommand.c" />
     <ClCompile Include="battle.c" />
     <ClCompile Include="battleground.c" />
@@ -285,6 +287,7 @@
     <Copy SourceFiles="$(SolutionDir)conf\msg_conf\import-tmpl\map_msg_spn_conf.txt" DestinationFolder="$(SolutionDir)conf\msg_conf\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)conf\msg_conf\import\map_msg_spn_conf.txt')" />
     <Copy SourceFiles="$(SolutionDir)conf\msg_conf\import-tmpl\map_msg_tha_conf.txt" DestinationFolder="$(SolutionDir)conf\msg_conf\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)conf\msg_conf\import\map_msg_tha_conf.txt')" />
     <Copy SourceFiles="$(SolutionDir)db\import-tmpl\abra_db.txt" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\abra_db.txt')" />
+    <Copy SourceFiles="$(SolutionDir)db\import-tmpl\achievement_db.yml" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\achievement_db.yml')" />
     <Copy SourceFiles="$(SolutionDir)db\import-tmpl\attr_fix.txt" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\attr_fix.txt')" />
     <Copy SourceFiles="$(SolutionDir)db\import-tmpl\castle_db.txt" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\castle_db.txt')" />
     <Copy SourceFiles="$(SolutionDir)db\import-tmpl\create_arrow_db.txt" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\create_arrow_db.txt')" />

+ 6 - 0
src/map/map-server.vcxproj.filters

@@ -11,6 +11,9 @@
     </Filter>
   </ItemGroup>
   <ItemGroup>
+    <ClInclude Include="achievement.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
     <ClInclude Include="atcommand.h">
       <Filter>Header Files</Filter>
     </ClInclude>
@@ -142,6 +145,9 @@
     </ClInclude>
   </ItemGroup>
   <ItemGroup>
+    <ClCompile Include="achievement.c">
+      <Filter>Source Files</Filter>
+    </ClCompile>
     <ClCompile Include="atcommand.c">
       <Filter>Source Files</Filter>
     </ClCompile>

+ 3 - 4
src/map/map.cpp

@@ -38,6 +38,7 @@
 #include "elemental.h"
 #include "cashshop.h"
 #include "channel.h"
+#include "achievement.h"
 
 #include <stdlib.h>
 #include <math.h>
@@ -4385,6 +4386,7 @@ void do_final(void)
 	do_final_clif();
 	do_final_npc();
 	do_final_quest();
+	do_final_achievement();
 	do_final_script();
 	do_final_instance();
 	do_final_itemdb();
@@ -4739,6 +4741,7 @@ int do_init(int argc, char *argv[])
 	do_init_mercenary();
 	do_init_elemental();
 	do_init_quest();
+	do_init_achievement();
 	do_init_npc();
 	do_init_unit();
 	do_init_battleground();
@@ -4758,10 +4761,6 @@ int do_init(int argc, char *argv[])
 		shutdown_callback = do_shutdown;
 		runflag = MAPSERVER_ST_RUNNING;
 	}
-#if defined(BUILDBOT)
-	if( buildbotflag )
-		exit(EXIT_FAILURE);
-#endif
 
 	if( console ){ //start listening
 		add_timer_func_list(parse_console_timer, "parse_console_timer");

+ 4 - 0
src/map/mob.c

@@ -24,6 +24,7 @@
 #include "elemental.h"
 #include "party.h"
 #include "quest.h"
+#include "achievement.h"
 
 #include <stdlib.h>
 #include <math.h>
@@ -2905,6 +2906,9 @@ int mob_dead(struct mob_data *md, struct block_list *src, int type)
 			else if (sd->avail_quests)
 				quest_update_objective(sd, md->mob_id);
 
+			if (achievement_mobexists(md->mob_id))
+				achievement_update_objective(sd, AG_BATTLE, 1, md->mob_id);
+
 			if (sd->md && src && src->type == BL_MER && mob_db(md->mob_id)->lv > sd->status.base_level / 2)
 				mercenary_kills(sd->md);
 		}

+ 3 - 0
src/map/party.c

@@ -18,6 +18,7 @@
 #include "intif.h"
 #include "mapreg.h"
 #include "trade.h"
+#include "achievement.h"
 
 #include <stdlib.h>
 
@@ -175,6 +176,8 @@ void party_created(uint32 account_id,uint32 char_id,int fail,int party_id,char *
 		sd->status.party_id = party_id;
 		clif_party_created(sd,0); // Success message
 
+		achievement_update_objective(sd, AG_PARTY, 1, 1);
+
 		// We don't do any further work here because the char-server sends a party info packet right after creating the party
 		if(party_create_byscript) {	// returns party id in $@party_create_id if party is created by script
 			mapreg_setreg(add_str("$@party_create_id"),party_id);

+ 30 - 0
src/map/pc.c

@@ -34,6 +34,7 @@
 #include "party.h" // party_search()
 #include "storage.h"
 #include "quest.h"
+#include "achievement.h"
 
 #include <stdlib.h>
 #include <math.h>
@@ -948,6 +949,10 @@ bool pc_adoption(struct map_session_data *p1_sd, struct map_session_data *p2_sd,
 		pc_skill(p1_sd, WE_CALLBABY, 1, ADDSKILL_PERMANENT);
 		pc_skill(p2_sd, WE_CALLBABY, 1, ADDSKILL_PERMANENT);
 
+		achievement_update_objective(b_sd, AG_BABY, 1, 1);
+		achievement_update_objective(p1_sd, AG_BABY, 1, 2);
+		achievement_update_objective(p2_sd, AG_BABY, 1, 2);
+
 		return true;
 	}
 
@@ -1454,6 +1459,17 @@ void pc_reg_received(struct map_session_data *sd)
 	intif_Mail_requestinbox(sd->status.char_id, 0, MAIL_INBOX_NORMAL); // MAIL SYSTEM - Request Mail Inbox
 	intif_request_questlog(sd);
 
+	if (battle_config.feature_achievement) {
+		sd->achievement_data.total_score = 0;
+		sd->achievement_data.level = 0;
+		sd->achievement_data.save = false;
+		sd->achievement_data.sendlist = false;
+		sd->achievement_data.count = 0;
+		sd->achievement_data.incompleteCount = 0;
+		sd->achievement_data.achievements = NULL;
+		intif_request_achievements(sd->status.char_id);
+	}
+
 	if (sd->state.connect_new == 0 && sd->fd) { //Character already loaded map! Gotta trigger LoadEndAck manually.
 		sd->state.connect_new = 1;
 		clif_parse_LoadEndAck(sd->fd, sd);
@@ -4311,6 +4327,8 @@ char pc_getzeny(struct map_session_data *sd, int zeny, enum e_log_pick_type type
 		clif_messagecolor(&sd->bl, color_table[COLOR_LIGHT_GREEN], output, false, SELF);
 	}
 
+	achievement_update_objective(sd, AG_GET_ZENY, 1, sd->status.zeny);
+
 	return 0;
 }
 
@@ -4554,6 +4572,8 @@ char pc_additem(struct map_session_data *sd,struct item *item,int amount,e_log_p
 		}
 	}
 
+	achievement_update_objective(sd, AG_GET_ITEM, 1, id->value_sell);
+
 	return ADDITEM_SUCCESS;
 }
 
@@ -6463,6 +6483,8 @@ int pc_checkbaselevelup(struct map_session_data *sd) {
 		party_send_levelup(sd);
 
 	pc_baselevelchanged(sd);
+	achievement_update_objective(sd, AG_GOAL_LEVEL, 1, sd->status.base_level);
+	achievement_update_objective(sd, AG_GOAL_STATUS, 2, sd->status.base_level, sd->status.class_);
 	return 1;
 }
 
@@ -6510,6 +6532,7 @@ int pc_checkjoblevelup(struct map_session_data *sd)
 		clif_status_change(&sd->bl,SI_DEVIL, 1, 0, 0, 0, 1); //Permanent blind effect from SG_DEVIL.
 
 	npc_script_event(sd, NPCE_JOBLVUP);
+	achievement_update_objective(sd, AG_GOAL_LEVEL, 1, sd->status.job_level);
 	return 1;
 }
 
@@ -6963,6 +6986,8 @@ bool pc_statusup(struct map_session_data* sd, int type, int increase)
 	if( final_value > 255 )
 		clif_updatestatus(sd, type); // send after the 'ack' to override the truncated value
 
+	achievement_update_objective(sd, AG_GOAL_STATUS, 1, final_value);
+
 	return true;
 }
 
@@ -8610,6 +8635,7 @@ bool pc_jobchange(struct map_session_data *sd,int job, char upper)
 	pc_checkallowskill(sd);
 	pc_equiplookall(sd);
 	pc_show_questinfo(sd);
+	achievement_update_objective(sd, AG_JOB_CHANGE, 2, sd->status.base_level, job);
 	if( sd->status.party_id ){
 		struct party_data* p;
 		
@@ -10161,6 +10187,10 @@ bool pc_marriage(struct map_session_data *sd,struct map_session_data *dstsd)
 		return false;
 	sd->status.partner_id = dstsd->status.char_id;
 	dstsd->status.partner_id = sd->status.char_id;
+
+	achievement_update_objective(sd, AG_MARRY, 1, 1);
+	achievement_update_objective(dstsd, AG_MARRY, 1, 1);
+
 	return true;
 }
 

+ 15 - 0
src/map/pc.h

@@ -603,6 +603,21 @@ struct map_session_data {
 	struct quest *quest_log; ///< Quest log entries (note: Q_COMPLETE quests follow the first <avail_quests>th enties
 	bool save_quest;         ///< Whether the quest_log entries were modified and are waitin to be saved
 
+	// Achievement log system
+	struct s_achievement_data {
+		int total_score;                  ///< Total achievement points
+		int level;                        ///< Achievement level
+		bool save;                        ///< Flag to know if achievements need to be saved
+		bool sendlist;                    ///< Flag to know if all achievements should be sent to the player (refresh list if an achievement has a title)
+		uint16 count;                     ///< Total achievements in log
+		uint16 incompleteCount;           ///< Total incomplete achievements in log
+		struct achievement *achievements; ///< Achievement log entries
+	} achievement_data;
+
+	// Title system
+	int *titles;
+	uint8 titleCount;
+
 	/* ShowEvent Data Cache flags from map */
 	bool *qi_display;
 	unsigned short qi_count;

+ 2 - 0
src/map/pet.c

@@ -15,6 +15,7 @@
 #include "intif.h"
 #include "chrif.h"
 #include "pet.h"
+#include "achievement.h"
 
 #include <stdlib.h>
 
@@ -672,6 +673,7 @@ int pet_catch_process2(struct map_session_data* sd, int target_id)
 		pet_catch_rate = (pet_catch_rate*battle_config.pet_catch_rate)/100;
 
 	if(rnd()%10000 < pet_catch_rate) {
+		achievement_update_objective(sd, AG_TAMING, 1, md->mob_id);
 		unit_remove_map(&md->bl,CLR_OUTSIGHT);
 		status_kill(&md->bl);
 		clif_pet_roulette(sd,1);

+ 146 - 0
src/map/script.c

@@ -50,6 +50,7 @@
 #include "quest.h"
 #include "elemental.h"
 #include "channel.h"
+#include "achievement.h"
 
 #include <math.h>
 #include <stdlib.h> // atoi, strtol, strtoll, exit
@@ -2293,6 +2294,20 @@ static void add_buildin_func(void)
 	}
 }
 
+/// Retrieves the value of a constant parameter.
+bool script_get_parameter(const char* name, int* value)
+{
+	int n = search_str(name);
+
+	if (n == -1 || str_data[n].type != C_PARAM)
+	{// not found or not a parameter
+		return false;
+	}
+	value[0] = str_data[n].val;
+
+	return true;
+}
+
 /// Retrieves the value of a constant.
 bool script_get_constant(const char* name, int* value)
 {
@@ -8918,6 +8933,7 @@ BUILDIN_FUNC(successrefitem) {
 		clif_additem(sd,i,1,0);
 		pc_equipitem(sd,i,ep);
 		clif_misceffect(&sd->bl,3);
+		achievement_update_objective(sd, AG_REFINE_SUCCESS, 2, sd->inventory_data[i]->wlv, sd->inventory.u.items_inventory[i].refine);
 		if (sd->inventory.u.items_inventory[i].refine == MAX_REFINE &&
 			sd->inventory.u.items_inventory[i].card[0] == CARD0_FORGE &&
 			sd->status.char_id == (int)MakeDWord(sd->inventory.u.items_inventory[i].card[2],sd->inventory.u.items_inventory[i].card[3]))
@@ -8967,6 +8983,7 @@ BUILDIN_FUNC(failedrefitem) {
 		clif_refine(sd->fd,1,i,sd->inventory.u.items_inventory[i].refine); //notify client of failure
 		pc_delitem(sd,i,1,0,2,LOG_TYPE_SCRIPT);
 		clif_misceffect(&sd->bl,2); 	// display failure effect
+		achievement_update_objective(sd, AG_REFINE_FAIL, 1, 1);
 		script_pushint(st, 1);
 		return SCRIPT_CMD_SUCCESS;
 	}
@@ -9015,6 +9032,7 @@ BUILDIN_FUNC(downrefitem) {
 		clif_additem(sd,i,1,0);
 		pc_equipitem(sd,i,ep);
 		clif_misceffect(&sd->bl,2);
+		achievement_update_objective(sd, AG_REFINE_FAIL, 1, sd->inventory.u.items_inventory[i].refine);
 		script_pushint(st, sd->inventory.u.items_inventory[i].refine);
 		return SCRIPT_CMD_SUCCESS;
 	}
@@ -23226,6 +23244,127 @@ BUILDIN_FUNC(unloadnpc) {
 	return SCRIPT_CMD_SUCCESS;
 }
 
+/**
+ * Add an achievement to the player's log
+ * achievementadd(<achievement ID>{,<char ID>});
+ */
+BUILDIN_FUNC(achievementadd) {
+	struct map_session_data *sd;
+	int achievement_id = script_getnum(st, 2);
+
+	if (!script_charid2sd(3, sd)) {
+		script_pushint(st, false);
+		return SCRIPT_CMD_FAILURE;
+	}
+
+	if (achievement_search(achievement_id) == &achievement_dummy) {
+		ShowWarning("buildin_achievementadd: Achievement '%d' doesn't exist.\n", achievement_id);
+		script_pushint(st, false);
+		return SCRIPT_CMD_FAILURE;
+	}
+
+	if (achievement_add(sd, achievement_id))
+		script_pushint(st, true);
+	else
+		script_pushint(st, false);
+	return SCRIPT_CMD_SUCCESS;
+}
+
+/**
+ * Removes an achievement on a player.
+ * achievementremove(<achievement ID>{,<char ID>});
+ * Just for Atemo. ;)
+ */
+BUILDIN_FUNC(achievementremove) {
+	struct map_session_data *sd;
+	int achievement_id = script_getnum(st, 2);
+
+	if (!script_charid2sd(3, sd)) {
+		script_pushint(st, false);
+		return SCRIPT_CMD_FAILURE;
+	}
+
+	if (achievement_search(achievement_id) == &achievement_dummy) {
+		ShowWarning("buildin_achievementremove: Achievement '%d' doesn't exist.\n", achievement_id);
+		script_pushint(st, false);
+		return SCRIPT_CMD_SUCCESS;
+	}
+
+	if (achievement_remove(sd, achievement_id))
+		script_pushint(st, true);
+	else
+		script_pushint(st, false);
+	return SCRIPT_CMD_SUCCESS;
+}
+
+/**
+ * Returns achievement progress
+ * achievementinfo(<achievement ID>,<type>{,<char ID>});
+ */
+BUILDIN_FUNC(achievementinfo) {
+	struct map_session_data *sd;
+	int achievement_id = script_getnum(st, 2);
+
+	if (!script_charid2sd(4, sd)) {
+		script_pushint(st, false);
+		return SCRIPT_CMD_FAILURE;
+	}
+
+	script_pushint(st, achievement_check_progress(sd, achievement_id, script_getnum(st, 3)));
+	return SCRIPT_CMD_SUCCESS;
+}
+
+/**
+ * Award an achievement; Ignores requirements
+ * achievementcomplete(<achievement ID>{,<char ID>});
+ */
+BUILDIN_FUNC(achievementcomplete) {
+	struct map_session_data *sd;
+	int i, achievement_id = script_getnum(st, 2);
+
+	if (!script_charid2sd(3, sd)) {
+		script_pushint(st, false);
+		return SCRIPT_CMD_FAILURE;
+	}
+
+	if (achievement_search(achievement_id) == &achievement_dummy) {
+		ShowWarning("buildin_achievementcomplete: Achievement '%d' doesn't exist.\n", achievement_id);
+		script_pushint(st, false);
+		return SCRIPT_CMD_FAILURE;
+	}
+
+	ARR_FIND(0, sd->achievement_data.count, i, sd->achievement_data.achievements[i].achievement_id == achievement_id);
+	if (i == sd->achievement_data.count)
+		achievement_add(sd, achievement_id);
+	achievement_update_achievement(sd, achievement_id, true);
+	script_pushint(st, true);
+	return SCRIPT_CMD_SUCCESS;
+}
+
+/**
+ * Checks if the achievement exists on player.
+ * achievementexists(<achievement ID>{,<char ID>});
+ */
+BUILDIN_FUNC(achievementexists) {
+	struct map_session_data *sd;
+	int i, achievement_id = script_getnum(st, 2);
+
+	if (!script_charid2sd(3, sd)) {
+		script_pushint(st, false);
+		return SCRIPT_CMD_FAILURE;
+	}
+
+	if (achievement_search(achievement_id) == &achievement_dummy) {
+		ShowWarning("buildin_achievementexists: Achievement '%d' doesn't exist.\n", achievement_id);
+		script_pushint(st, false);
+		return SCRIPT_CMD_SUCCESS;
+	}
+
+	ARR_FIND(0, sd->achievement_data.count, i, sd->achievement_data.achievements[i].achievement_id == achievement_id);
+	script_pushint(st, i < sd->achievement_data.count ? true : false);
+	return SCRIPT_CMD_SUCCESS;
+}
+
 #include "../custom/script.inc"
 
 // declarations that were supposed to be exported from npc_chat.c
@@ -23857,6 +23996,13 @@ struct script_function buildin_func[] = {
 	BUILDIN_DEF2(delitem2,"delitem3","viiiiiiiirrr?"),
 	BUILDIN_DEF2(countitem,"countitem3","viiiiiiirrr?"),
 
+	// Achievement System
+	BUILDIN_DEF(achievementinfo,"ii?"),
+	BUILDIN_DEF(achievementadd,"i?"),
+	BUILDIN_DEF(achievementremove,"i?"),
+	BUILDIN_DEF(achievementcomplete,"i?"),
+	BUILDIN_DEF(achievementexists,"i?"),
+
 #include "../custom/script_def.inc"
 
 	{NULL,NULL,NULL},

+ 2 - 0
src/map/script.h

@@ -717,6 +717,7 @@ const char* skip_space(const char* p);
 void script_error(const char* src, const char* file, int start_line, const char* error_msg, const char* error_pos);
 void script_warning(const char* src, const char* file, int start_line, const char* error_msg, const char* error_pos);
 
+bool is_number(const char *p);
 struct script_code* parse_script(const char* src,const char* file,int line,int options);
 void run_script(struct script_code *rootscript,int pos,int rid,int oid);
 
@@ -740,6 +741,7 @@ struct DBMap* script_get_label_db(void);
 struct DBMap* script_get_userfunc_db(void);
 void script_run_autobonus(const char *autobonus, struct map_session_data *sd, unsigned int pos);
 
+bool script_get_parameter(const char* name, int* value);
 bool script_get_constant(const char* name, int* value);
 void script_set_constant(const char* name, int value, bool isparameter, bool deprecated);
 void script_hardcoded_constants(void);

+ 43 - 0
src/map/script_constants.h

@@ -3822,6 +3822,49 @@
 	export_constant(USW_FORCE_STOP);
 	export_constant(USW_ALL);
 
+	/* achievement groups */
+	export_constant2("AG_ADD_FRIEND", AG_ADD_FRIEND);
+	export_constant2("AG_ADVENTURE", AG_ADVENTURE);
+	export_constant2("AG_BABY", AG_BABY);
+	export_constant2("AG_BATTLE", AG_BATTLE);
+	export_constant2("AG_CHATTING", AG_CHAT);
+	export_constant2("AG_CHATTING_COUNT", AG_CHAT_COUNT);
+	export_constant2("AG_CHATTING_CREATE", AG_CHAT_CREATE);
+	export_constant2("AG_CHATTING_DYING", AG_CHAT_DYING);
+	export_constant2("AG_EAT", AG_EAT);
+	export_constant2("AG_GET_ITEM", AG_GET_ITEM);
+	export_constant2("AG_GET_ZENY", AG_GET_ZENY);
+	export_constant2("AG_GOAL_ACHIEVE", AG_GOAL_ACHIEVE);
+	export_constant2("AG_GOAL_LEVEL", AG_GOAL_LEVEL);
+	export_constant2("AG_GOAL_STATUS", AG_GOAL_STATUS);
+	export_constant2("AG_HEAR", AG_HEAR);
+	export_constant2("AG_JOB_CHANGE", AG_JOB_CHANGE);
+	export_constant2("AG_MARRY", AG_MARRY);
+	export_constant2("AG_PARTY", AG_PARTY);
+	export_constant2("AG_ENCHANT_FAIL", AG_REFINE_FAIL);
+	export_constant2("AG_ENCHANT_SUCCESS", AG_REFINE_SUCCESS);
+	export_constant2("AG_SEE", AG_SEE);
+	export_constant2("AG_SPEND_ZENY", AG_SPEND_ZENY);
+	export_constant2("AG_TAMING", AG_TAMING);
+
+	/* achievement info */
+	export_constant(ACHIEVEINFO_COUNT1);
+	export_constant(ACHIEVEINFO_COUNT2);
+	export_constant(ACHIEVEINFO_COUNT3);
+	export_constant(ACHIEVEINFO_COUNT4);
+	export_constant(ACHIEVEINFO_COUNT5);
+	export_constant(ACHIEVEINFO_COUNT6);
+	export_constant(ACHIEVEINFO_COUNT7);
+	export_constant(ACHIEVEINFO_COUNT8);
+	export_constant(ACHIEVEINFO_COUNT9);
+	export_constant(ACHIEVEINFO_COUNT10);
+	export_constant(ACHIEVEINFO_COMPLETE);
+	export_constant(ACHIEVEINFO_COMPLETEDATE);
+	export_constant(ACHIEVEINFO_GOTREWARD);
+	export_constant(ACHIEVEINFO_LEVEL);
+	export_constant(ACHIEVEINFO_SCORE);
+	export_constant(ACHIEVEINFO_MAX);
+
 	#undef export_constant
 	#undef export_constant2
 	#undef export_parameter

+ 3 - 0
src/map/skill.c

@@ -34,6 +34,7 @@
 #include "guild.h"
 #include "date.h"
 #include "unit.h"
+#include "achievement.h"
 
 #include <stdio.h>
 #include <stdlib.h>
@@ -16725,6 +16726,7 @@ void skill_weaponrefine(struct map_session_data *sd, int idx)
 				clif_upgrademessage(sd->fd, 0, item->nameid);
 				clif_inventorylist(sd);
 				clif_refine(sd->fd,0,idx,item->refine);
+				achievement_update_objective(sd, AG_REFINE_SUCCESS, 2, ditem->wlv, item->refine);
 				if (ep)
 					pc_equipitem(sd,idx,ep);
 				clif_misceffect(&sd->bl,3);
@@ -16750,6 +16752,7 @@ void skill_weaponrefine(struct map_session_data *sd, int idx)
 					pc_unequipitem(sd,idx,3);
 				clif_upgrademessage(sd->fd, 1, item->nameid);
 				clif_refine(sd->fd,1,idx,item->refine);
+				achievement_update_objective(sd, AG_REFINE_FAIL, 1, 1);
 				pc_delitem(sd,idx,1,0,2, LOG_TYPE_OTHER);
 				clif_misceffect(&sd->bl,2);
 				clif_emotion(&sd->bl, E_OMG);

+ 4 - 0
src/map/unit.c

@@ -9,6 +9,7 @@
 #include "../common/random.h"
 #include "../common/socket.h"
 
+#include "achievement.h"
 #include "map.h"
 #include "path.h"
 #include "pc.h"
@@ -3228,6 +3229,9 @@ int unit_free(struct block_list *bl, clr_type clrtype)
 			}
 #endif
 
+			if (sd->achievement_data.achievements)
+				achievement_free(sd);
+
 			// Clearing...
 			if (sd->bonus_script.head)
 				pc_bonus_script_clear(sd, BSF_REM_ALL);

+ 2 - 0
src/map/vending.c

@@ -13,6 +13,7 @@
 #include "vending.h"
 #include "pc.h"
 #include "buyingstore.h" // struct s_autotrade_entry, struct s_autotrader
+#include "achievement.h"
 
 #include <stdlib.h> // atoi
 
@@ -191,6 +192,7 @@ void vending_purchasereq(struct map_session_data* sd, int aid, int uid, const ui
 	}
 
 	pc_payzeny(sd, (int)z, LOG_TYPE_VENDING, vsd);
+	achievement_update_objective(sd, AG_SPEND_ZENY, 1, (int)z);
 	if( battle_config.vending_tax )
 		z -= z * (battle_config.vending_tax/10000.);
 	pc_getzeny(vsd, (int)z, LOG_TYPE_VENDING, sd);