diff --git a/.travis.yml b/.travis.yml
index 13745097b4..963fe75d9b 100644
--- a/.travis.yml
+++ b/.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
diff --git a/3rdparty/yaml-cpp/src/contrib/graphbuilderadapter.h b/3rdparty/yaml-cpp/src/contrib/graphbuilderadapter.h
index 0d1e579208..726db89897 100644
--- a/3rdparty/yaml-cpp/src/contrib/graphbuilderadapter.h
+++ b/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;
diff --git a/3rdparty/yaml-cpp/src/singledocparser.cpp b/3rdparty/yaml-cpp/src/singledocparser.cpp
index a27c1c3b04..752835822c 100644
--- a/3rdparty/yaml-cpp/src/singledocparser.cpp
+++ b/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;
}
}
diff --git a/Makefile.in b/Makefile.in
index 348034f51d..bf4e105e0d 100644
--- a/Makefile.in
+++ b/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"
diff --git a/conf/battle/feature.conf b/conf/battle/feature.conf
index a2af2d184e..cd31321d41 100644
--- a/conf/battle/feature.conf
+++ b/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
diff --git a/conf/msg_conf/char_msg.conf b/conf/msg_conf/char_msg.conf
index 181b29b6a9..687a524371 100644
--- a/conf/msg_conf/char_msg.conf
+++ b/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.
diff --git a/conf/msg_conf/map_msg.conf b/conf/msg_conf/map_msg.conf
index 40151e8697..50306ef970 100644
--- a/conf/msg_conf/map_msg.conf
+++ b/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
diff --git a/db/import-tmpl/achievement_db.yml b/db/import-tmpl/achievement_db.yml
new file mode 100644
index 0000000000..92044f77aa
--- /dev/null
+++ b/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 .
+#
+###########################################################################
+# 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.
+###########################################################################
diff --git a/db/pre-re/achievement_db.yml b/db/pre-re/achievement_db.yml
new file mode 100644
index 0000000000..24e7173607
--- /dev/null
+++ b/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 .
+#
+###########################################################################
+# 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
diff --git a/db/re/achievement_db.yml b/db/re/achievement_db.yml
new file mode 100644
index 0000000000..fa72b9259a
--- /dev/null
+++ b/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 .
+#
+###########################################################################
+# 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
diff --git a/db/re/item_db.txt b/db/re/item_db.txt
index dc5d0ac1ee..c989981f2d 100644
--- a/db/re/item_db.txt
+++ b/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*/},{},{}
diff --git a/doc/achievements.txt b/doc/achievements.txt
new file mode 100644
index 0000000000..70c9ad2a46
--- /dev/null
+++ b/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.
diff --git a/doc/atcommands.txt b/doc/atcommands.txt
index 7997c73eb4..d310424c48 100644
--- a/doc/atcommands.txt
+++ b/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
diff --git a/doc/packet_interserv.txt b/doc/packet_interserv.txt
index 5b1f0ca1b7..4718eb61e1 100644
--- a/doc/packet_interserv.txt
+++ b/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: .W .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: .W .W .L .?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: .W .W .?B
diff --git a/doc/script_commands.txt b/doc/script_commands.txt
index a18847a59f..75a216fd1c 100644
--- a/doc/script_commands.txt
+++ b/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({,})
+
+This function will add an achievement to the player's log for the attached
+player or the supplied . The objective requirements are not ignored
+when using this function.
+Returns true on success and false on failure.
+
+---------------------------------------
+
+*achievementremove({,})
+
+This function will remove an achievement from the player's log for the attached
+player or the supplied .
+Returns true on success and false on failure.
+
+---------------------------------------
+
+*achievementinfo(,{,})
+
+This function will return the specified value for an achievement of the
+attached player or the supplied . 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 .
+
+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 ( is useless for this)
+- ACHIEVEINFO_SCORE ( is useless for this)
+
+---------------------------------------
+
+*achievementcomplete({,})
+
+This function will complete an achievement for the attached player or the supplied
+. The objective requirements are ignored when using this function.
+Returns true on success and false on failure.
+
+---------------------------------------
+
+*achievementexists({,});
+
+This function will return if the achievement exists on the player or the supplied
+.
+Returns true on success and false on failure.
+
+---------------------------------------
\ No newline at end of file
diff --git a/npc/re/other/achievements.txt b/npc/re/other/achievements.txt
new file mode 100644
index 0000000000..fadad9a392
--- /dev/null
+++ b/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
diff --git a/npc/re/scripts_athena.conf b/npc/re/scripts_athena.conf
index 1d355b6749..0e77f70c7e 100644
--- a/npc/re/scripts_athena.conf
+++ b/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
diff --git a/sql-files/main.sql b/sql-files/main.sql
index cc58bc690f..57be49e635 100644
--- a/sql-files/main.sql
+++ b/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`),
diff --git a/sql-files/upgrades/upgrade_20170407.sql b/sql-files/upgrades/upgrade_20170407.sql
new file mode 100644
index 0000000000..f0dd3b6fc5
--- /dev/null
+++ b/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;
diff --git a/src/char/char-server.vcxproj b/src/char/char-server.vcxproj
index 6c2bf4cd56..f515e8ad25 100644
--- a/src/char/char-server.vcxproj
+++ b/src/char/char-server.vcxproj
@@ -161,6 +161,7 @@
+
@@ -182,6 +183,7 @@
+
diff --git a/src/char/char-server.vcxproj.filters b/src/char/char-server.vcxproj.filters
index 93cd821683..cc6aa2d202 100644
--- a/src/char/char-server.vcxproj.filters
+++ b/src/char/char-server.vcxproj.filters
@@ -26,6 +26,9 @@
Header Files
+
+ Header Files
+
Header Files
@@ -79,6 +82,9 @@
Source Files
+
+ Source Files
+
Source Files
diff --git a/src/char/char.cpp b/src/char/char.cpp
index 87296138da..2ff09d9fc5 100644
--- a/src/char/char.cpp
+++ b/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
+#include
+#include
+
+/**
+ * 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;
+}
diff --git a/src/char/int_achievement.h b/src/char/int_achievement.h
new file mode 100644
index 0000000000..908c61dcbc
--- /dev/null
+++ b/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_ */
diff --git a/src/char/int_mail.c b/src/char/int_mail.c
index 0420ffed5a..9859a19124 100644
--- a/src/char/int_mail.c
+++ b/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 ){
diff --git a/src/char/int_mail.h b/src/char/int_mail.h
index 7149e49cb8..766ab9b136 100644
--- a/src/char/int_mail.h
+++ b/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);
diff --git a/src/char/inter.c b/src/char/inter.c
index 8252f7ca2e..135c944107 100644
--- a/src/char/inter.c
+++ b/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
@@ -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
diff --git a/src/common/Makefile.in b/src/common/Makefile.in
index 08791a6a6b..c3f07ceeb4 100644
--- a/src/common/Makefile.in
+++ b/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
+
diff --git a/src/common/core.cpp b/src/common/core.cpp
index 7d3ddbe530..758850d234 100644
--- a/src/common/core.cpp
+++ b/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;
}
diff --git a/src/common/mmo.h b/src/common/mmo.h
index 23cae6a173..83bef472b5 100644
--- a/src/common/mmo.h
+++ b/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 {
diff --git a/src/common/sql.c b/src/common/sql.c
index 6533aafebc..38760139be 100644
--- a/src/common/sql.c
+++ b/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)
{
diff --git a/src/common/sql.h b/src/common/sql.h
index 828b000654..e86b0b3b2d 100644
--- a/src/common/sql.h
+++ b/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.
///
diff --git a/src/common/yamlwrapper.cpp b/src/common/yamlwrapper.cpp
index 59838720aa..a63532f4cb 100644
--- a/src/common/yamlwrapper.cpp
+++ b/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(wrapper, key);
}
+char* yaml_as_c_string(yamlwrapper* wrapper) {
+ std::string cpp_str = wrapper->root.as();
+ 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
+ T yaml_as_value(yamlwrapper* wrapper) {
+ if (wrapper == nullptr)
+ return {};
+ try {
+ return wrapper->root.as();
+ }
+ 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(wrapper);
+}
+
+int16 yaml_as_int16(yamlwrapper* wrapper) {
+ return yaml_as_value(wrapper);
+}
+
+int32 yaml_as_int32(yamlwrapper* wrapper) {
+ return yaml_as_value(wrapper);
+}
+
+int64 yaml_as_int64(yamlwrapper* wrapper) {
+ return yaml_as_value(wrapper);
+}
+
+bool yaml_as_boolean(yamlwrapper* wrapper) {
+ return yaml_as_value(wrapper);
+}
+
bool yaml_node_is_defined(yamlwrapper* wrapper, const char* key) {
if (wrapper == nullptr || key == nullptr)
return false;
diff --git a/src/common/yamlwrapper.h b/src/common/yamlwrapper.h
index d6f7069c4a..d00c084d17 100644
--- a/src/common/yamlwrapper.h
+++ b/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);
diff --git a/src/map/Makefile.in b/src/map/Makefile.in
index c9775682d9..41079d0980 100644
--- a/src/map/Makefile.in
+++ b/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
diff --git a/src/map/achievement.c b/src/map/achievement.c
new file mode 100644
index 0000000000..90b1059a34
--- /dev/null
+++ b/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
+#include
+#include
+#include
+
+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);
+}
diff --git a/src/map/achievement.h b/src/map/achievement.h
new file mode 100644
index 0000000000..c3b3b0eb13
--- /dev/null
+++ b/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 */
diff --git a/src/map/atcommand.c b/src/map/atcommand.c
index 0606430fd5..b9280b6c38 100644
--- a/src/map/atcommand.c
+++ b/src/map/atcommand.c
@@ -34,6 +34,7 @@
#include "mapreg.h"
#include "quest.h"
#include "pc.h"
+#include "achievement.h"
#include
#include
@@ -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),
diff --git a/src/map/battle.c b/src/map/battle.c
index 7754f85f12..0ea1cd84d4 100644
--- a/src/map/battle.c
+++ b/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");
diff --git a/src/map/battle.h b/src/map/battle.h
index bd046a1ba6..b2ec1c5af0 100644
--- a/src/map/battle.h
+++ b/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;
diff --git a/src/map/chat.c b/src/map/chat.c
index 3cbc904b9f..81ba0dab63 100644
--- a/src/map/chat.c
+++ b/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;
}
diff --git a/src/map/chrif.c b/src/map/chrif.c
index 1c96972fe4..2a11fa3892 100644
--- a/src/map/chrif.c
+++ b/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;
}
diff --git a/src/map/clif.c b/src/map/clif.c
index 7f0c1f1d64..b8b7ae8452 100644
--- a/src/map/clif.c
+++ b/src/map/clif.c
@@ -45,6 +45,7 @@
#include "quest.h"
#include "cashshop.h"
#include "channel.h"
+#include "achievement.h"
#include
#include
@@ -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 .B .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 .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 .W .W .L .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 .W .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 .W .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 .W .W .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
*------------------------------------------*/
diff --git a/src/map/clif.h b/src/map/clif.h
index 4bbd8910c2..874d871198 100644
--- a/src/map/clif.h
+++ b/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
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
diff --git a/src/map/clif_packetdb.h b/src/map/clif_packetdb.h
index 773456b781..cdb1b6235c 100644
--- a/src/map/clif_packetdb.h
+++ b/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
diff --git a/src/map/intif.c b/src/map/intif.c
index ee6c524cff..2f9c934f64 100644
--- a/src/map/intif.c
+++ b/src/map/intif.c
@@ -23,6 +23,7 @@
#include "mail.h"
#include "quest.h"
#include "status.h"
+#include "achievement.h"
#include
@@ -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;
diff --git a/src/map/intif.h b/src/map/intif.h
index db09f1cb7c..76f2571556 100644
--- a/src/map/intif.h
+++ b/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);
diff --git a/src/map/map-server.vcxproj b/src/map/map-server.vcxproj
index 477694b960..139b5dc7e6 100644
--- a/src/map/map-server.vcxproj
+++ b/src/map/map-server.vcxproj
@@ -159,6 +159,7 @@
+
@@ -204,6 +205,7 @@
+
@@ -285,6 +287,7 @@
+
diff --git a/src/map/map-server.vcxproj.filters b/src/map/map-server.vcxproj.filters
index 2568e84f52..11f732c4b8 100644
--- a/src/map/map-server.vcxproj.filters
+++ b/src/map/map-server.vcxproj.filters
@@ -11,6 +11,9 @@
+
+ Header Files
+
Header Files
@@ -142,6 +145,9 @@
+
+ Source Files
+
Source Files
diff --git a/src/map/map.cpp b/src/map/map.cpp
index 710e0f56cc..f8a73aa4a4 100644
--- a/src/map/map.cpp
+++ b/src/map/map.cpp
@@ -38,6 +38,7 @@
#include "elemental.h"
#include "cashshop.h"
#include "channel.h"
+#include "achievement.h"
#include
#include
@@ -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");
diff --git a/src/map/mob.c b/src/map/mob.c
index 86e1cecdd2..639cd28553 100644
--- a/src/map/mob.c
+++ b/src/map/mob.c
@@ -24,6 +24,7 @@
#include "elemental.h"
#include "party.h"
#include "quest.h"
+#include "achievement.h"
#include
#include
@@ -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);
}
diff --git a/src/map/party.c b/src/map/party.c
index 9159ec3495..bc6c4f94a6 100644
--- a/src/map/party.c
+++ b/src/map/party.c
@@ -18,6 +18,7 @@
#include "intif.h"
#include "mapreg.h"
#include "trade.h"
+#include "achievement.h"
#include
@@ -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);
diff --git a/src/map/pc.c b/src/map/pc.c
index e71f535c78..97a4e74998 100755
--- a/src/map/pc.c
+++ b/src/map/pc.c
@@ -34,6 +34,7 @@
#include "party.h" // party_search()
#include "storage.h"
#include "quest.h"
+#include "achievement.h"
#include
#include
@@ -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;
}
diff --git a/src/map/pc.h b/src/map/pc.h
index 7e72a76b9e..43153b49d7 100644
--- a/src/map/pc.h
+++ b/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 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;
diff --git a/src/map/pet.c b/src/map/pet.c
index d9205518a0..a07805b392 100644
--- a/src/map/pet.c
+++ b/src/map/pet.c
@@ -15,6 +15,7 @@
#include "intif.h"
#include "chrif.h"
#include "pet.h"
+#include "achievement.h"
#include
@@ -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);
diff --git a/src/map/script.c b/src/map/script.c
index 4340d7efdf..98223e58ff 100644
--- a/src/map/script.c
+++ b/src/map/script.c
@@ -50,6 +50,7 @@
#include "quest.h"
#include "elemental.h"
#include "channel.h"
+#include "achievement.h"
#include
#include // 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({,});
+ */
+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({,});
+ * 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(,{,});
+ */
+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({,});
+ */
+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({,});
+ */
+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},
diff --git a/src/map/script.h b/src/map/script.h
index 12ac404c4c..b47f00587b 100644
--- a/src/map/script.h
+++ b/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);
diff --git a/src/map/script_constants.h b/src/map/script_constants.h
index e74786e024..cf2cf5396d 100644
--- a/src/map/script_constants.h
+++ b/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
diff --git a/src/map/skill.c b/src/map/skill.c
index dece786049..9656e5788c 100755
--- a/src/map/skill.c
+++ b/src/map/skill.c
@@ -34,6 +34,7 @@
#include "guild.h"
#include "date.h"
#include "unit.h"
+#include "achievement.h"
#include
#include
@@ -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);
diff --git a/src/map/unit.c b/src/map/unit.c
index 1ac34bb3d0..c6694f6567 100644
--- a/src/map/unit.c
+++ b/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);
diff --git a/src/map/vending.c b/src/map/vending.c
index 54b1713d89..6cf5589b11 100755
--- a/src/map/vending.c
+++ b/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 // 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);