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);