From 78eed02273414a4332f9c088ad7e795a2903ea5f Mon Sep 17 00:00:00 2001 From: Aleos Date: Thu, 28 Feb 2019 16:19:39 -0500 Subject: [PATCH] Updated the YAML parsing method (#3694) Synchronized the process of loading and parsing YAML databases. Provides more robust parse warnings/errors. Added Header checks to validate database versions. Adjusted root keys to a standard name of Body. Adjusted inter-server storages, achievements, and attendance to follow the new format. Fixes #3483 Thanks to @Lemongrass3110! --- conf/import-tmpl/inter_server.yml | 6 +- conf/inter_server.yml | 16 +- db/achievement_db.yml | 90 +++++ db/attendance.yml | 11 + db/import-tmpl/achievement_db.yml | 4 +- db/import-tmpl/attendance.yml | 2 +- db/pre-re/achievement_db.yml | 589 +++++++++++++++++++++-------- db/pre-re/attendance.yml | 2 +- db/re/achievement_db.yml | 589 +++++++++++++++++++++-------- db/re/attendance.yml | 118 +++--- doc/achievements.txt | 5 +- src/char/char.cpp | 2 +- src/char/int_storage.cpp | 45 ++- src/char/int_storage.hpp | 1 - src/char/inter.cpp | 164 ++++---- src/char/inter.hpp | 17 +- src/common/CMakeLists.txt | 2 + src/common/Makefile.in | 2 +- src/common/common.vcxproj | 2 + src/common/common.vcxproj.filters | 6 + src/common/core.cpp | 1 + src/common/core.hpp | 1 + src/common/database.cpp | 238 ++++++++++++ src/common/database.hpp | 114 ++++++ src/common/showmsg.hpp | 1 + src/common/utilities.hpp | 31 ++ src/map/achievement.cpp | 599 ++++++++++++++++-------------- src/map/achievement.hpp | 32 +- src/map/intif.cpp | 6 +- src/map/mob.cpp | 2 +- src/map/pc.cpp | 362 ++++++++++-------- src/map/script.cpp | 12 +- 32 files changed, 2120 insertions(+), 952 deletions(-) create mode 100644 db/achievement_db.yml create mode 100644 db/attendance.yml create mode 100644 src/common/database.cpp create mode 100644 src/common/database.hpp diff --git a/conf/import-tmpl/inter_server.yml b/conf/import-tmpl/inter_server.yml index 85e71fe78e..4c755ef680 100644 --- a/conf/import-tmpl/inter_server.yml +++ b/conf/import-tmpl/inter_server.yml @@ -10,7 +10,11 @@ # Table: "" // (string) Name of table where storage is saved. The table stucture is the same as the default storage table. # Max: // (int) *optional* Maximum number of items in storage. MAX_STORAGE will be used if no value is defined. ############################################################################################################################################### -#Storages: +Header: + Type: INTER_SERVER_DB + Version: 1 + +#Body: # - ID: 1 # Name: "VIP Storage" # Table: "vip_storage" diff --git a/conf/inter_server.yml b/conf/inter_server.yml index 70cd3d09c7..8608fd0c2c 100644 --- a/conf/inter_server.yml +++ b/conf/inter_server.yml @@ -28,7 +28,15 @@ # Table: "" // (string) Name of table where storage is saved. The table stucture is the same as the default storage table. # Max: // (int) *optional* Maximum number of items in storage. MAX_STORAGE will be used if no value is defined. -Storages: - - ID: 0 - Name: "Storage" - Table: "storage" +Header: + Type: INTER_SERVER_DB + Version: 1 + +Body: + - ID: 0 + Name: "Storage" + Table: "storage" + +Footer: + Imports: + - Path: conf/import/inter_server.yml diff --git a/db/achievement_db.yml b/db/achievement_db.yml new file mode 100644 index 0000000000..ed4cb8f074 --- /dev/null +++ b/db/achievement_db.yml @@ -0,0 +1,90 @@ +# 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 . +# +########################################################################### +# Renewal 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. +########################################################################### + +Header: + Type: ACHIEVEMENT_DB + Version: 1 + +Footer: + Imports: + - Path: db/pre-re/achievement_db.yml + Mode: Prerenewal + - Path: db/re/achievement_db.yml + Mode: Renewal + - Path: db/import/achievement_db.yml diff --git a/db/attendance.yml b/db/attendance.yml new file mode 100644 index 0000000000..a09028f7f5 --- /dev/null +++ b/db/attendance.yml @@ -0,0 +1,11 @@ +Header: + Type: ATTENDANCE_DB + Version: 1 + +Footer: + Imports: + - Path: db/pre-re/attendance.yml + Mode: Prerenewal + - Path: db/re/attendance.yml + Mode: Renewal + - Path: db/import/attendance.yml diff --git a/db/import-tmpl/achievement_db.yml b/db/import-tmpl/achievement_db.yml index eb8448ccd6..ace5584336 100644 --- a/db/import-tmpl/achievement_db.yml +++ b/db/import-tmpl/achievement_db.yml @@ -77,4 +77,6 @@ # Score - Achievement points that are given on completion. ########################################################################### -Achievements: +Header: + Type: ACHIEVEMENT_DB + Version: 1 diff --git a/db/import-tmpl/attendance.yml b/db/import-tmpl/attendance.yml index 338333625c..2a47cd6e43 100644 --- a/db/import-tmpl/attendance.yml +++ b/db/import-tmpl/attendance.yml @@ -1,3 +1,3 @@ Header: - Type: ATTENDANCE_CONF + Type: ATTENDANCE_DB Version: 1 diff --git a/db/pre-re/achievement_db.yml b/db/pre-re/achievement_db.yml index 5362737b0f..6badf942b1 100644 --- a/db/pre-re/achievement_db.yml +++ b/db/pre-re/achievement_db.yml @@ -77,7 +77,11 @@ # Score - Achievement points that are given on completion. ########################################################################### -Achievements: +Header: + Type: ACHIEVEMENT_DB + Version: 1 + +Body: - ID: 110000 Group: "AG_EAT" Name: "At this time I live to eat" @@ -967,572 +971,758 @@ Achievements: Name: "Prontera Contribution" Map: "prontera" Target: - - Count: 100000 + - Id: 0 + Count: 100000 Score: 10 - ID: 127002 Group: "AG_CHATTING" Name: "Geffen Contribution" Map: "geffen" Target: - - Count: 100000 + - Id: 0 + Count: 100000 Score: 10 - ID: 127003 Group: "AG_CHATTING" Name: "Morocc Contribution" Map: "morocc" Target: - - Count: 100000 + - Id: 0 + Count: 100000 Score: 10 - ID: 127004 Group: "AG_CHATTING" Name: "Payon Contribution" Map: "payon" Target: - - Count: 100000 + - Id: 0 + Count: 100000 Score: 10 - ID: 127005 Group: "AG_CHATTING" Name: "Yuno Contribution" Map: "yuno" Target: - - Count: 100000 + - Id: 0 + Count: 100000 Score: 10 - ID: 127006 Group: "AG_CHATTING" Name: "Lighthalzen Contribution" Map: "lighthalzen" Target: - - Count: 100000 + - Id: 0 + Count: 100000 Score: 10 - ID: 127007 Group: "AG_CHATTING" Name: "Einbroch Contribution" Map: "einbroch" Target: - - Count: 100000 + - Id: 0 + Count: 100000 Score: 10 - ID: 127008 Group: "AG_CHATTING" Name: "Rachel Contribution" Map: "rachel" Target: - - Count: 100000 + - Id: 0 + Count: 100000 Score: 10 - ID: 127009 Group: "AG_CHATTING" Name: "Veins Contribution" Map: "veins" Target: - - Count: 100000 + - Id: 0 + Count: 100000 Score: 10 - ID: 128000 Group: "AG_BATTLE" Name: "Uninvited Guest" #Target: - # - MobID: 2996 + # - Id: 0 + # MobID: 2996 # Count: 1 Score: 10 - ID: 128001 Group: "AG_BATTLE" Name: "Strange Guest" #Target: - # - MobID: 2996 + # - Id: 0 + # MobID: 2996 # Count: 10 Score: 10 - ID: 128002 Group: "AG_BATTLE" Name: "Get along with map..." #Target: - # - MobID: 2996 + # - Id: 0 + # MobID: 2996 # Count: 25 Score: 20 - ID: 128003 Group: "AG_BATTLE" Name: "Welcomed Guest" #Target: - # - MobID: 2996 + # - Id: 0 + # MobID: 2996 # Count: 50 Score: 30 - ID: 128004 Group: "AG_BATTLE" Name: "Kimmy's best friend" #Target: - # - MobID: 2996 + # - Id: 0 + # MobID: 2996 # Count: 100 Score: 50 - ID: 128005 Group: "AG_BATTLE" Name: "Novice Angler" #Target: - # - MobID: 2322 + # - Id: 0 + # MobID: 2322 # Count: 1 Score: 10 - ID: 128006 Group: "AG_BATTLE" Name: "Juicy Hunter" #Target: - # - MobID: 2322 + # - Id: 0 + # MobID: 2322 # Count: 10 Score: 20 - ID: 128007 Group: "AG_BATTLE" Name: "Rhythm Master" #Target: - # - MobID: 2322 + # - Id: 0 + # MobID: 2322 # Count: 50 Score: 50 - ID: 128008 Group: "AG_BATTLE" Name: "Bold Adventurer" Target: - - MobID: 1929 + - Id: 0 + MobID: 1929 Count: 1 Score: 10 - ID: 128009 Group: "AG_BATTLE" Name: "Baphomet Hatred" Target: - - MobID: 1929 + - Id: 0 + MobID: 1929 Count: 10 Score: 20 - ID: 128010 Group: "AG_BATTLE" Name: "Goat's Nemesis" Target: - - MobID: 1929 + - Id: 0 + MobID: 1929 Count: 50 Score: 50 - ID: 128011 Group: "AG_BATTLE" Name: "Ordinary Tourist" #Target: - # - MobID: 3029 + # - Id: 0 + # MobID: 3029 # Count: 1 Score: 10 - ID: 128012 Group: "AG_BATTLE" Name: "Backcountry Expert" #Target: - # - MobID: 3029 + # - Id: 0 + # MobID: 3029 # Count: 10 Score: 20 - ID: 128013 Group: "AG_BATTLE" Name: "Able to eat more like this" #Target: - # - MobID: 3029 + # - Id: 0 + # MobID: 3029 # Count: 50 Score: 50 - ID: 128014 Group: "AG_BATTLE" Name: "Digest hard meat" #Target: - # - MobID: 2319 + # - Id: 0 + # MobID: 2319 # Count: 1 Score: 10 - ID: 128015 Group: "AG_BATTLE" Name: "Master of Escape" #Target: - # - MobID: 2319 + # - Id: 0 + # MobID: 2319 # Count: 10 Score: 20 - ID: 128016 Group: "AG_BATTLE" Name: "Immortal Hunter" #Target: - # - MobID: 2319 + # - Id: 0 + # MobID: 2319 # Count: 50 Score: 50 - ID: 128017 Group: "AG_BATTLE" Name: "Stood up and overcame despair" #Target: - # - MobID: 3097 + # - Id: 0 + # MobID: 3097 # Count: 1 Score: 10 - ID: 128018 Group: "AG_BATTLE" Name: "Ember of Hope" #Target: - # - MobID: 3097 + # - Id: 0 + # MobID: 3097 # Count: 10 Score: 10 - ID: 128019 Group: "AG_BATTLE" Name: "Pouring Aurora" #Target: - # - MobID: 3097 + # - Id: 0 + # MobID: 3097 # Count: 25 Score: 20 - ID: 128020 Group: "AG_BATTLE" Name: "Who is desperate? I am hopeless!" #Target: - # - MobID: 3097 + # - Id: 0 + # MobID: 3097 # Count: 50 Score: 30 - ID: 128021 Group: "AG_BATTLE" Name: "I know god will save the world" #Target: - # - MobID: 3097 + # - Id: 0 + # MobID: 3097 # Count: 100 Score: 50 - ID: 128022 Group: "AG_BATTLE" Name: "There was mercy in Morocc army" #Target: - # - MobID: 3000 + # - Id: 0 + # MobID: 3000 # Count: 1 Score: 10 - ID: 128023 Group: "AG_BATTLE" Name: "There was fear in Morocc army" #Target: - # - MobID: 3000 + # - Id: 0 + # MobID: 3000 # Count: 10 Score: 20 - ID: 128024 Group: "AG_BATTLE" Name: "Guard of weak army" #Target: - # - MobID: 3000 + # - Id: 0 + # MobID: 3000 # Count: 50 Score: 50 - ID: 128025 Group: "AG_BATTLE" Name: "Audience with the queen" #Target: - # - MobID: 2529 + # - Id: 0 + # MobID: 2529 # Count: 1 Score: 10 - ID: 128026 Group: "AG_BATTLE" Name: "Warm earth" #Target: - # - MobID: 2533 + # - Id: 0 + # MobID: 2533 # Count: 1 Score: 10 - ID: 128027 Group: "AG_BATTLE" Name: "Water is very good exactly" #Target: - # - MobID: 2534 + # - Id: 0 + # MobID: 2534 # Count: 1 Score: 10 - ID: 128028 Group: "AG_BATTLE" Name: "Pleasant breeze" #Target: - # - MobID: 2535 + # - Id: 0 + # MobID: 2535 # Count: 1 Score: 10 - ID: 128029 Group: "AG_BATTLE" Name: "Visitor of old castle" #Target: - # - MobID: 2476 + # - Id: 0 + # MobID: 2476 # Count: 1 Score: 10 - ID: 128030 Group: "AG_BATTLE" Name: "Lord of old castle" #Target: - # - MobID: 2476 + # - Id: 0 + # MobID: 2476 # Count: 10 Score: 20 - ID: 128031 Group: "AG_BATTLE" Name: "Conqueror of old castle" #Target: - # - MobID: 2476 + # - Id: 0 + # MobID: 2476 # Count: 50 Score: 50 - ID: 128032 Group: "AG_BATTLE" Name: "Haggard sucker" #Target: - # - MobID: 3150 + # - Id: 0 + # MobID: 3150 # Count: 1 Score: 10 - ID: 128033 Group: "AG_BATTLE" Name: "Hope of the Knight" #Target: - # - MobID: 3150 + # - Id: 0 + # MobID: 3150 # Count: 10 Score: 20 - ID: 128034 Group: "AG_BATTLE" Name: "Guardian of the Dawn" #Target: - # - MobID: 3150 + # - Id: 0 + # MobID: 3150 # Count: 50 Score: 50 - ID: 128035 Group: "AG_BATTLE" Name: "Time Traveler" #Target: - # - MobID: 3190 + # - Id: 0 + # MobID: 3190 # Count: 1 Score: 10 - ID: 128036 Group: "AG_BATTLE" Name: "Restore ancient relic" #Target: - # - MobID: 3190 + # - Id: 0 + # MobID: 3190 # Count: 10 Score: 20 - ID: 128037 Group: "AG_BATTLE" Name: "Master of relic transport" #Target: - # - MobID: 3190 + # - Id: 0 + # MobID: 3190 # Count: 50 Score: 50 - ID: 128038 Group: "AG_BATTLE" Name: "Show Jailbreak to the captain" #Target: - # - MobID: 3181 + # - Id: 0 + # MobID: 3181 # Count: 1 Score: 10 - ID: 128039 Group: "AG_BATTLE" Name: "Show Jailbreak to the weak captain" #Target: - # - MobID: 3188 + # - Id: 0 + # MobID: 3188 # Count: 1 Score: 10 - ID: 128040 Group: "AG_BATTLE" Name: "Riot on board" #Target: - # - MobID: 3181 + # - Id: 0 + # MobID: 3181 # Count: 1 Score: 20 - ID: 128041 Group: "AG_BATTLE" Name: "Turmoil on board" #Target: - # - MobID: 3181 + # - Id: 0 + # MobID: 3181 # Count: 10 Score: 20 - ID: 128042 Group: "AG_BATTLE" Name: "Rebellion on board" #Target: - # - MobID: 3181 + # - Id: 0 + # MobID: 3181 # Count: 50 Score: 50 - ID: 128043 Group: "AG_BATTLE" Name: "Revolt of Riot" #Target: - # - MobID: 3188 + # - Id: 0 + # MobID: 3188 # Count: 50 Score: 50 - ID: 128044 Group: "AG_BATTLE" Name: "Magic tournament champion" #Target: - # - MobID: 2564 + # - Id: 0 + # MobID: 2564 # Count: 1 Score: 10 - ID: 128045 Group: "AG_BATTLE" Name: "Gladiator of Coliseum" #Target: - # - MobID: 2564 + # - Id: 0 + # MobID: 2564 # Count: 10 Score: 20 - ID: 128046 Group: "AG_BATTLE" Name: "Slayer of Colosseum" #Target: - # - MobID: 2564 + # - Id: 0 + # MobID: 2564 # Count: 50 Score: 50 - ID: 128047 Group: "AG_BATTLE" Name: "Endless Tower challenger" Target: - - MobID: 1956 + - Id: 0 + MobID: 1956 Count: 1 Score: 10 - ID: 128048 Group: "AG_BATTLE" Name: "Endless Tower Slayer" Target: - - MobID: 1956 + - Id: 0 + MobID: 1956 Count: 10 Score: 20 - ID: 128049 Group: "AG_BATTLE" Name: "Lord of the tower" Target: - - MobID: 1956 + - Id: 0 + MobID: 1956 Count: 50 Score: 50 - ID: 128050 Group: "AG_BATTLE" Name: "Novice Exorcist" #Target: - # - MobID: 2327 + # - Id: 0 + # MobID: 2327 # Count: 1 Score: 10 - ID: 128051 Group: "AG_BATTLE" Name: "Experienced Exorcist" #Target: - # - MobID: 2327 + # - Id: 0 + # MobID: 2327 # Count: 10 Score: 20 - ID: 128052 Group: "AG_BATTLE" Name: "Legendary Exorcist" #Target: - # - MobID: 2327 + # - Id: 0 + # 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] + Dependent: + - Id: 120001 + - Id: 120002 + - Id: 120003 + - Id: 120004 + - Id: 120005 + - Id: 120006 + - Id: 120007 + - Id: 120008 + - Id: 120009 + - Id: 120010 Reward: ItemID: 644 Score: 20 - ID: 129002 Group: "AG_ADVENTURE" Name: "Geffen Explorer" - Dependent: [120011, 120012, 120013, 120014, 120015, 120016, 120017] + Dependent: + - Id: 120011 + - Id: 120012 + - Id: 120013 + - Id: 120014 + - Id: 120015 + - Id: 120016 + - Id: 120017 Reward: ItemID: 644 Score: 20 - ID: 129003 Group: "AG_ADVENTURE" Name: "Sograt Desert Explorer" - Dependent: [120018, 120019, 120020, 120021, 120022, 120023] + Dependent: + - Id: 120018 + - Id: 120019 + - Id: 120020 + - Id: 120021 + - Id: 120022 + - Id: 120023 Reward: ItemID: 644 Score: 20 - ID: 129004 Group: "AG_ADVENTURE" Name: "Payon Explorer" - Dependent: [120024, 120025, 120026, 120027, 120028, 120029, 120030, 120031] + Dependent: + - Id: 120024 + - Id: 120025 + - Id: 120026 + - Id: 120027 + - Id: 120028 + - Id: 120029 + - Id: 120030 + - Id: 120031 Reward: ItemID: 644 Score: 20 - ID: 129005 Group: "AG_ADVENTURE" Name: "North Mjolnir Explorer" - Dependent: [120032, 120033, 120034, 120035, 120036] + Dependent: + - Id: 120032 + - Id: 120033 + - Id: 120034 + - Id: 120035 + - Id: 120036 Reward: ItemID: 644 Score: 20 - ID: 129006 Group: "AG_ADVENTURE" Name: "South Mjolnir Explorer" - Dependent: [120037, 120038, 120039, 120040, 120041, 120042, 120043] + Dependent: + - Id: 120037 + - Id: 120038 + - Id: 120039 + - Id: 120040 + - Id: 120041 + - Id: 120042 + - Id: 120043 Reward: ItemID: 644 Score: 20 - ID: 129007 Group: "AG_ADVENTURE" Name: "Comodo Explorer" - Dependent: [120044, 120045, 120046, 120047, 120048, 120049, 120050, 120051] + Dependent: + - Id: 120044 + - Id: 120045 + - Id: 120046 + - Id: 120047 + - Id: 120048 + - Id: 120049 + - Id: 120050 + - Id: 120051 Reward: ItemID: 644 Score: 20 - ID: 129008 Group: "AG_ADVENTURE" Name: "Rune Midgard Explorer" - Dependent: [129001, 129002, 129003, 129004, 129005, 129006, 129007] + Dependent: + - Id: 129001 + - Id: 129002 + - Id: 129003 + - Id: 129004 + - Id: 129005 + - Id: 129006 + - Id: 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] + Dependent: + - Id: 120052 + - Id: 120053 + - Id: 120054 + - Id: 120055 + - Id: 120056 + - Id: 120057 + - Id: 120058 + - Id: 120059 + - Id: 120060 + - Id: 120061 Reward: ItemID: 644 Score: 20 - ID: 129010 Group: "AG_ADVENTURE" Name: "Hugel Explorer" - Dependent: [120062, 120063, 120064, 120065, 120066] + Dependent: + - Id: 120062 + - Id: 120063 + - Id: 120064 + - Id: 120065 + - Id: 120066 Reward: ItemID: 644 Score: 20 - ID: 129011 Group: "AG_ADVENTURE" Name: "Einbroch Explorer" - Dependent: [120067, 120068, 120069, 120070, 120071, 120072, 120073, 120074] + Dependent: + - Id: 120067 + - Id: 120068 + - Id: 120069 + - Id: 120070 + - Id: 120071 + - Id: 120072 + - Id: 120073 + - Id: 120074 Reward: ItemID: 644 Score: 20 - ID: 129012 Group: "AG_ADVENTURE" Name: "Lighthalzen Explorer" - Dependent: [120075, 120076, 120077] + Dependent: + - Id: 120075 + - Id: 120076 + - Id: 120077 Reward: ItemID: 644 Score: 20 - ID: 129013 Group: "AG_ADVENTURE" Name: "Schwarzwald Explorer" - Dependent: [129009, 129010, 129011, 129012] + Dependent: + - Id: 129009 + - Id: 129010 + - Id: 129011 + - Id: 129012 Reward: ItemID: 617 Score: 50 - ID: 129014 Group: "AG_ADVENTURE" Name: "Rachel Explorer" - Dependent: [120078, 120079, 120080, 120081, 120082, 120083, 120084] + Dependent: + - Id: 120078 + - Id: 120079 + - Id: 120080 + - Id: 120081 + - Id: 120082 + - Id: 120083 + - Id: 120084 Reward: ItemID: 644 Score: 20 - ID: 129015 Group: "AG_ADVENTURE" Name: "Veins Explorer" - Dependent: [120085, 120086, 120087, 120088, 120089] + Dependent: + - Id: 120085 + - Id: 120086 + - Id: 120087 + - Id: 120088 + - Id: 120089 Reward: ItemID: 644 Score: 20 - ID: 129016 Group: "AG_ADVENTURE" Name: "Arunafeltz Explorer" - Dependent: [129014, 129015] + Dependent: + - Id: 129014 + - Id: 129015 Reward: ItemID: 617 Score: 50 - ID: 129017 Group: "AG_ADVENTURE" Name: "Laphine Explorer" - Dependent: [120090, 120091, 120092, 120093, 120094, 120095] + Dependent: + - Id: 120090 + - Id: 120091 + - Id: 120092 + - Id: 120093 + - Id: 120094 + - Id: 120095 Reward: ItemID: 644 Score: 20 - ID: 129018 Group: "AG_ADVENTURE" Name: "Manuk Explorer" - Dependent: [120096, 120097, 120098, 120099, 120100] + Dependent: + - Id: 120096 + - Id: 120097 + - Id: 120098 + - Id: 120099 + - Id: 120100 Reward: ItemID: 644 Score: 20 - ID: 129019 Group: "AG_ADVENTURE" Name: "Eclage Explorer" - Dependent: [129017, 129018] + Dependent: + - Id: 129017 + - Id: 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] + Dependent: + - Id: 120101 + - Id: 120102 + - Id: 120103 + - Id: 120104 + - Id: 120105 + - Id: 120106 + - Id: 120107 + - Id: 120108 + - Id: 120109 Reward: ItemID: 617 Score: 50 @@ -1563,7 +1753,8 @@ Achievements: Group: "AG_GOAL_LEVEL" Name: "Acquire the second aura!" Condition: " BaseLevel >= 150 " - Dependent: [200000] + Dependent: + - Id: 200000 Reward: ItemID: 5364 Script: " specialeffect2 EF_BLESSING; sc_start SC_BLESSING,30000,10; " @@ -1573,7 +1764,8 @@ Achievements: Group: "AG_GOAL_LEVEL" Name: "Acquire the third aura!" Condition: " BaseLevel >= 175 " - Dependent: [200001] + Dependent: + - Id: 200001 Reward: # ItemID: 18880 Script: " specialeffect2 EF_BLESSING; sc_start SC_BLESSING,30000,10; " @@ -1592,7 +1784,8 @@ Achievements: Group: "AG_GOAL_LEVEL" Name: "Grandmaster Job level!" Condition: " JobLevel >= 70 " - Dependent: [200003] + Dependent: + - Id: 200003 Reward: # ItemID: 12817 Script: " specialeffect2 EF_BLESSING; sc_start SC_BLESSING,30000,10; " @@ -1793,7 +1986,11 @@ Achievements: - ID: 230100 Group: "AG_TAMING" Name: "Poring is Love" - Dependent: [230101, 230102, 230103, 230104] + Dependent: + - Id: 230101 + - Id: 230102 + - Id: 230103 + - Id: 230104 Reward: Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; " TitleID: 1025 @@ -1801,7 +1998,13 @@ Achievements: - ID: 230110 Group: "AG_TAMING" Name: "Entomologist" - Dependent: [230111, 230112, 230113, 230114, 230115, 230116] + Dependent: + - Id: 230111 + - Id: 230112 + - Id: 230113 + - Id: 230114 + - Id: 230115 + - Id: 230116 Reward: Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; " TitleID: 1026 @@ -1809,7 +2012,15 @@ Achievements: - ID: 230120 Group: "AG_TAMING" Name: "Animals are also our friend" - Dependent: [230121, 230122, 230123, 230124, 230125, 230126, 230127, 230128] + Dependent: + - Id: 230121 + - Id: 230122 + - Id: 230123 + - Id: 230124 + - Id: 230125 + - Id: 230126 + - Id: 230127 + - Id: 230128 Reward: Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; " TitleID: 1027 @@ -1817,7 +2028,14 @@ Achievements: - ID: 230140 Group: "AG_TAMING" Name: "Monster Girls Unite!!" - Dependent: [230141, 230142, 230143, 230144, 230145, 230146, 230147] + Dependent: + - Id: 230141 + - Id: 230142 + - Id: 230143 + - Id: 230144 + - Id: 230145 + - Id: 230146 + - Id: 230147 Reward: Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; " TitleID: 1029 @@ -1826,175 +2044,200 @@ Achievements: Group: "AG_TAMING" Name: "Poring - taming" Target: - - MobID: 1002 + - Id: 0 + MobID: 1002 Count: 1 Score: 10 - ID: 230102 Group: "AG_TAMING" Name: "Drops - taming" Target: - - MobID: 1113 + - Id: 0 + MobID: 1113 Count: 1 Score: 10 - ID: 230103 Group: "AG_TAMING" Name: "Poporing - taming" Target: - - MobID: 1031 + - Id: 0 + MobID: 1031 Count: 1 Score: 10 - ID: 230104 Group: "AG_TAMING" Name: "Novice Poring - taming" #Target: - # - MobID: 2398 + # - Id: 0 + # MobID: 2398 # Count: 1 Score: 10 - ID: 230111 Group: "AG_TAMING" Name: "Chonchon - taming" Target: - - MobID: 1011 + - Id: 0 + MobID: 1011 Count: 1 Score: 10 - ID: 230112 Group: "AG_TAMING" Name: "Steel Chonchon - taming" Target: - - MobID: 1042 + - Id: 0 + MobID: 1042 Count: 1 Score: 10 - ID: 230113 Group: "AG_TAMING" Name: "Hunter Fly - taming" Target: - - MobID: 1035 + - Id: 0 + MobID: 1035 Count: 1 Score: 10 - ID: 230114 Group: "AG_TAMING" Name: "Rocker - taming" Target: - - MobID: 1052 + - Id: 0 + MobID: 1052 Count: 1 Score: 10 - ID: 230115 Group: "AG_TAMING" Name: "Spore - taming" Target: - - MobID: 1014 + - Id: 0 + MobID: 1014 Count: 1 Score: 10 - ID: 230116 Group: "AG_TAMING" Name: "Poison Spore - taming" Target: - - MobID: 1077 + - Id: 0 + MobID: 1077 Count: 1 Score: 10 - ID: 230121 Group: "AG_TAMING" Name: "Lunatic - taming" Target: - - MobID: 1063 + - Id: 0 + MobID: 1063 Count: 1 Score: 10 - ID: 230122 Group: "AG_TAMING" Name: "Picky - taming" Target: - - MobID: 1049 + - Id: 0 + MobID: 1049 Count: 1 Score: 10 - ID: 230123 Group: "AG_TAMING" Name: "Savage Bebe - taming" Target: - - MobID: 1167 + - Id: 0 + MobID: 1167 Count: 1 Score: 10 - ID: 230124 Group: "AG_TAMING" Name: "Baby Desert Wolf - taming" Target: - - MobID: 1107 + - Id: 0 + MobID: 1107 Count: 1 Score: 10 - ID: 230125 Group: "AG_TAMING" Name: "Smokie - taming" Target: - - MobID: 1056 + - Id: 0 + MobID: 1056 Count: 1 Score: 10 - ID: 230126 Group: "AG_TAMING" Name: "Yoyo - taming" Target: - - MobID: 1057 + - Id: 0 + MobID: 1057 Count: 1 Score: 10 - ID: 230127 Group: "AG_TAMING" Name: "Peco Peco - taming" Target: - - MobID: 1019 + - Id: 0 + MobID: 1019 Count: 1 Score: 10 - ID: 230128 Group: "AG_TAMING" Name: "Petite - taming" Target: - - MobID: 1155 + - Id: 0 + MobID: 1155 Count: 1 Score: 10 - ID: 230141 Group: "AG_TAMING" Name: "Munak - taming" Target: - - MobID: 1026 + - Id: 0 + MobID: 1026 Count: 1 Score: 10 - ID: 230142 Group: "AG_TAMING" Name: "Isis - taming" Target: - - MobID: 1029 + - Id: 0 + MobID: 1029 Count: 1 Score: 10 - ID: 230143 Group: "AG_TAMING" Name: "Sohee - taming" Target: - - MobID: 1170 + - Id: 0 + MobID: 1170 Count: 1 Score: 10 - ID: 230144 Group: "AG_TAMING" Name: "Zherlthsh - taming" Target: - - MobID: 1200 + - Id: 0 + MobID: 1200 Count: 1 Score: 10 - ID: 230145 Group: "AG_TAMING" Name: "Alice - taming" Target: - - MobID: 1275 + - Id: 0 + MobID: 1275 Count: 1 Score: 10 - ID: 230146 Group: "AG_TAMING" Name: "Succubus - taming" Target: - - MobID: 1370 + - Id: 0 + MobID: 1370 Count: 1 Score: 10 - ID: 230147 Group: "AG_TAMING" Name: "Loli Ruri - taming" Target: - - MobID: 1505 + - Id: 0 + MobID: 1505 Count: 1 Score: 10 - ID: 220000 @@ -2049,35 +2292,40 @@ Achievements: Name: "Activating the market economy (1)" Condition: " ARG0 >= 10000 " Target: - - Count: 10000 + - Id: 0 + Count: 10000 Score: 10 - ID: 220010 Group: "AG_SPEND_ZENY" Name: "Activating the market economy (2)" Condition: " ARG0 >= 100000 " Target: - - Count: 100000 + - Id: 0 + Count: 100000 Score: 15 - ID: 220011 Group: "AG_SPEND_ZENY" Name: "Activating the market economy (3)" Condition: " ARG0 >= 500000 " Target: - - Count: 500000 + - Id: 0 + Count: 500000 Score: 20 - ID: 220012 Group: "AG_SPEND_ZENY" Name: "Activating the market economy (4)" Condition: " ARG0 >= 1000000 " Target: - - Count: 1000000 + - Id: 0 + Count: 1000000 Score: 30 - ID: 220013 Group: "AG_SPEND_ZENY" Name: "Activating the market economy (5)" Condition: " ARG0 >= 5000000 " Target: - - Count: 5000000 + - Id: 0 + Count: 5000000 Score: 50 - ID: 220014 Group: "AG_ENCHANT_SUCCESS" @@ -2191,47 +2439,63 @@ Achievements: - ID: 230200 Group: "AG_BATTLE" Name: "Poring seeker" - Dependent: [230201, 230202, 230203] + Dependent: + - Id: 230201 + - Id: 230202 + - Id: 230203 Score: 10 - ID: 230201 Group: "AG_BATTLE" Name: "Exploring Poring's life (1)" Target: - - MobID: 1002 + - Id: 0 + MobID: 1002 Count: 10 - # - MobID: 2398 + # - Id: 1 + # MobID: 2398 # Count: 10 - - MobID: 1113 + - Id: 2 + MobID: 1113 Count: 10 - - MobID: 1031 + - Id: 3 + MobID: 1031 Count: 10 - - MobID: 1242 + - Id: 4 + MobID: 1242 Count: 10 Score: 10 - ID: 230202 Group: "AG_BATTLE" Name: "Exploring Poring's life (2)" Target: - - MobID: 1090 + - Id: 0 + MobID: 1090 Count: 1 - - MobID: 1582 + - Id: 1 + MobID: 1582 Count: 1 - - MobID: 1096 + - Id: 2 + MobID: 1096 Count: 1 - - MobID: 1388 + - Id: 3 + MobID: 1388 Count: 1 - - MobID: 1120 + - Id: 4 + MobID: 1120 Count: 1 Score: 20 - ID: 230203 Group: "AG_BATTLE" Name: "Exploring Poring's life (3)" Target: - - MobID: 1613 + - Id: 0 + MobID: 1613 Count: 5 - - MobID: 1977 + - Id: 1 + MobID: 1977 Count: 5 - - MobID: 1836 + - Id: 2 + MobID: 1836 Count: 5 Score: 20 - ID: 240000 @@ -2248,7 +2512,8 @@ Achievements: - ID: 240002 Group: "AG_GOAL_ACHIEVE" Name: "Reaching Level 2" - Dependent: [240001] + Dependent: + - Id: 240001 Reward: ItemID: 644 Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; " @@ -2256,7 +2521,8 @@ Achievements: - ID: 240003 Group: "AG_GOAL_ACHIEVE" Name: "Reaching Level 3" - Dependent: [240002] + Dependent: + - Id: 240002 Reward: ItemID: 644 Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; " @@ -2264,7 +2530,8 @@ Achievements: - ID: 240004 Group: "AG_GOAL_ACHIEVE" Name: "Reaching Level 4" - Dependent: [240003] + Dependent: + - Id: 240003 Reward: ItemID: 644 Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; " @@ -2272,7 +2539,8 @@ Achievements: - ID: 240005 Group: "AG_GOAL_ACHIEVE" Name: "Reaching Level 5" - Dependent: [240004] + Dependent: + - Id: 240004 Reward: ItemID: 644 Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; " @@ -2280,7 +2548,8 @@ Achievements: - ID: 240006 Group: "AG_GOAL_ACHIEVE" Name: "Reaching Level 6" - Dependent: [240005] + Dependent: + - Id: 240005 Reward: ItemID: 644 Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; " @@ -2288,7 +2557,8 @@ Achievements: - ID: 240007 Group: "AG_GOAL_ACHIEVE" Name: "Reaching Level 7" - Dependent: [240006] + Dependent: + - Id: 240006 Reward: ItemID: 644 Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; " @@ -2296,7 +2566,8 @@ Achievements: - ID: 240008 Group: "AG_GOAL_ACHIEVE" Name: "Reaching Level 8" - Dependent: [240007] + Dependent: + - Id: 240007 Reward: ItemID: 644 Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; " @@ -2304,7 +2575,8 @@ Achievements: - ID: 240009 Group: "AG_GOAL_ACHIEVE" Name: "Reaching Level 9" - Dependent: [240008] + Dependent: + - Id: 240008 Reward: ItemID: 644 Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; " @@ -2312,7 +2584,8 @@ Achievements: - ID: 240010 Group: "AG_GOAL_ACHIEVE" Name: "Reaching Level 10" - Dependent: [240009] + Dependent: + - Id: 240009 Reward: ItemID: 644 Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; " @@ -2321,7 +2594,8 @@ Achievements: - ID: 240011 Group: "AG_GOAL_ACHIEVE" Name: "Reaching Level 11" - Dependent: [240010] + Dependent: + - Id: 240010 Reward: ItemID: 644 Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; " @@ -2329,7 +2603,8 @@ Achievements: - ID: 240012 Group: "AG_GOAL_ACHIEVE" Name: "Reaching Level 12" - Dependent: [240011] + Dependent: + - Id: 240011 Reward: ItemID: 644 Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; " @@ -2337,7 +2612,8 @@ Achievements: - ID: 240013 Group: "AG_GOAL_ACHIEVE" Name: "Reaching Level 13" - Dependent: [240012] + Dependent: + - Id: 240012 Reward: ItemID: 644 Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; " @@ -2345,7 +2621,8 @@ Achievements: - ID: 240014 Group: "AG_GOAL_ACHIEVE" Name: "Reaching Level 14" - Dependent: [240013] + Dependent: + - Id: 240013 Reward: ItemID: 644 Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; " @@ -2353,7 +2630,8 @@ Achievements: - ID: 240015 Group: "AG_GOAL_ACHIEVE" Name: "Reaching Level 15" - Dependent: [240014] + Dependent: + - Id: 240014 Reward: ItemID: 644 Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; " @@ -2361,7 +2639,8 @@ Achievements: - ID: 240016 Group: "AG_GOAL_ACHIEVE" Name: "Reaching Level 16" - Dependent: [240015] + Dependent: + - Id: 240015 Reward: ItemID: 644 Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; " @@ -2369,7 +2648,8 @@ Achievements: - ID: 240017 Group: "AG_GOAL_ACHIEVE" Name: "Reaching Level 17" - Dependent: [240016] + Dependent: + - Id: 240016 Reward: ItemID: 644 Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; " @@ -2377,7 +2657,8 @@ Achievements: - ID: 240018 Group: "AG_GOAL_ACHIEVE" Name: "Reaching Level 18" - Dependent: [240017] + Dependent: + - Id: 240017 Reward: ItemID: 644 Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; " @@ -2385,7 +2666,8 @@ Achievements: - ID: 240019 Group: "AG_GOAL_ACHIEVE" Name: "Reaching Level 19" - Dependent: [240018] + Dependent: + - Id: 240018 Reward: ItemID: 644 Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; " @@ -2393,7 +2675,8 @@ Achievements: - ID: 240020 Group: "AG_GOAL_ACHIEVE" Name: "Reaching Level 20" - Dependent: [240019] + Dependent: + - Id: 240019 Reward: ItemID: 644 Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; " diff --git a/db/pre-re/attendance.yml b/db/pre-re/attendance.yml index 338333625c..2a47cd6e43 100644 --- a/db/pre-re/attendance.yml +++ b/db/pre-re/attendance.yml @@ -1,3 +1,3 @@ Header: - Type: ATTENDANCE_CONF + Type: ATTENDANCE_DB Version: 1 diff --git a/db/re/achievement_db.yml b/db/re/achievement_db.yml index c77bf0c6ca..7a9e94c7af 100644 --- a/db/re/achievement_db.yml +++ b/db/re/achievement_db.yml @@ -77,7 +77,11 @@ # Score - Achievement points that are given on completion. ########################################################################### -Achievements: +Header: + Type: ACHIEVEMENT_DB + Version: 1 + +Body: - ID: 110000 Group: "AG_EAT" Name: "At this time I live to eat" @@ -967,572 +971,758 @@ Achievements: Name: "Prontera Contribution" Map: "prontera" Target: - - Count: 100000 + - Id: 0 + Count: 100000 Score: 10 - ID: 127002 Group: "AG_CHATTING" Name: "Geffen Contribution" Map: "geffen" Target: - - Count: 100000 + - Id: 0 + Count: 100000 Score: 10 - ID: 127003 Group: "AG_CHATTING" Name: "Morocc Contribution" Map: "morocc" Target: - - Count: 100000 + - Id: 0 + Count: 100000 Score: 10 - ID: 127004 Group: "AG_CHATTING" Name: "Payon Contribution" Map: "payon" Target: - - Count: 100000 + - Id: 0 + Count: 100000 Score: 10 - ID: 127005 Group: "AG_CHATTING" Name: "Yuno Contribution" Map: "yuno" Target: - - Count: 100000 + - Id: 0 + Count: 100000 Score: 10 - ID: 127006 Group: "AG_CHATTING" Name: "Lighthalzen Contribution" Map: "lighthalzen" Target: - - Count: 100000 + - Id: 0 + Count: 100000 Score: 10 - ID: 127007 Group: "AG_CHATTING" Name: "Einbroch Contribution" Map: "einbroch" Target: - - Count: 100000 + - Id: 0 + Count: 100000 Score: 10 - ID: 127008 Group: "AG_CHATTING" Name: "Rachel Contribution" Map: "rachel" Target: - - Count: 100000 + - Id: 0 + Count: 100000 Score: 10 - ID: 127009 Group: "AG_CHATTING" Name: "Veins Contribution" Map: "veins" Target: - - Count: 100000 + - Id: 0 + Count: 100000 Score: 10 - ID: 128000 Group: "AG_BATTLE" Name: "Uninvited Guest" Target: - - MobID: 2996 + - Id: 0 + MobID: 2996 Count: 1 Score: 10 - ID: 128001 Group: "AG_BATTLE" Name: "Strange Guest" Target: - - MobID: 2996 + - Id: 0 + MobID: 2996 Count: 10 Score: 10 - ID: 128002 Group: "AG_BATTLE" Name: "Get along with map..." Target: - - MobID: 2996 + - Id: 0 + MobID: 2996 Count: 25 Score: 20 - ID: 128003 Group: "AG_BATTLE" Name: "Welcomed Guest" Target: - - MobID: 2996 + - Id: 0 + MobID: 2996 Count: 50 Score: 30 - ID: 128004 Group: "AG_BATTLE" Name: "Kimmy's best friend" Target: - - MobID: 2996 + - Id: 0 + MobID: 2996 Count: 100 Score: 50 - ID: 128005 Group: "AG_BATTLE" Name: "Novice Angler" Target: - - MobID: 2322 + - Id: 0 + MobID: 2322 Count: 1 Score: 10 - ID: 128006 Group: "AG_BATTLE" Name: "Juicy Hunter" Target: - - MobID: 2322 + - Id: 0 + MobID: 2322 Count: 10 Score: 20 - ID: 128007 Group: "AG_BATTLE" Name: "Rhythm Master" Target: - - MobID: 2322 + - Id: 0 + MobID: 2322 Count: 50 Score: 50 - ID: 128008 Group: "AG_BATTLE" Name: "Bold Adventurer" Target: - - MobID: 1929 + - Id: 0 + MobID: 1929 Count: 1 Score: 10 - ID: 128009 Group: "AG_BATTLE" Name: "Baphomet Hatred" Target: - - MobID: 1929 + - Id: 0 + MobID: 1929 Count: 10 Score: 20 - ID: 128010 Group: "AG_BATTLE" Name: "Goat's Nemesis" Target: - - MobID: 1929 + - Id: 0 + MobID: 1929 Count: 50 Score: 50 - ID: 128011 Group: "AG_BATTLE" Name: "Ordinary Tourist" #Target: - # - MobID: 3029 + # - Id: 0 + # MobID: 3029 # Count: 1 Score: 10 - ID: 128012 Group: "AG_BATTLE" Name: "Backcountry Expert" #Target: - # - MobID: 3029 + # - Id: 0 + # MobID: 3029 # Count: 10 Score: 20 - ID: 128013 Group: "AG_BATTLE" Name: "Able to eat more like this" #Target: - # - MobID: 3029 + # - Id: 0 + # MobID: 3029 # Count: 50 Score: 50 - ID: 128014 Group: "AG_BATTLE" Name: "Digest hard meat" Target: - - MobID: 2319 + - Id: 0 + MobID: 2319 Count: 1 Score: 10 - ID: 128015 Group: "AG_BATTLE" Name: "Master of Escape" Target: - - MobID: 2319 + - Id: 0 + MobID: 2319 Count: 10 Score: 20 - ID: 128016 Group: "AG_BATTLE" Name: "Immortal Hunter" Target: - - MobID: 2319 + - Id: 0 + MobID: 2319 Count: 50 Score: 50 - ID: 128017 Group: "AG_BATTLE" Name: "Stood up and overcame despair" #Target: - # - MobID: 3097 + # - Id: 0 + # MobID: 3097 # Count: 1 Score: 10 - ID: 128018 Group: "AG_BATTLE" Name: "Ember of Hope" #Target: - # - MobID: 3097 + # - Id: 0 + # MobID: 3097 # Count: 10 Score: 10 - ID: 128019 Group: "AG_BATTLE" Name: "Pouring Aurora" #Target: - # - MobID: 3097 + # - Id: 0 + # MobID: 3097 # Count: 25 Score: 20 - ID: 128020 Group: "AG_BATTLE" Name: "Who is desperate? I am hopeless!" #Target: - # - MobID: 3097 + # - Id: 0 + # MobID: 3097 # Count: 50 Score: 30 - ID: 128021 Group: "AG_BATTLE" Name: "I know god will save the world" #Target: - # - MobID: 3097 + # - Id: 0 + # MobID: 3097 # Count: 100 Score: 50 - ID: 128022 Group: "AG_BATTLE" Name: "There was mercy in Morocc army" #Target: - # - MobID: 3000 + # - Id: 0 + # MobID: 3000 # Count: 1 Score: 10 - ID: 128023 Group: "AG_BATTLE" Name: "There was fear in Morocc army" #Target: - # - MobID: 3000 + # - Id: 0 + # MobID: 3000 # Count: 10 Score: 20 - ID: 128024 Group: "AG_BATTLE" Name: "Guard of weak army" #Target: - # - MobID: 3000 + # - Id: 0 + # MobID: 3000 # Count: 50 Score: 50 - ID: 128025 Group: "AG_BATTLE" Name: "Audience with the queen" #Target: - # - MobID: 2529 + # - Id: 0 + # MobID: 2529 # Count: 1 Score: 10 - ID: 128026 Group: "AG_BATTLE" Name: "Warm earth" #Target: - # - MobID: 2533 + # - Id: 0 + # MobID: 2533 # Count: 1 Score: 10 - ID: 128027 Group: "AG_BATTLE" Name: "Water is very good exactly" #Target: - # - MobID: 2534 + # - Id: 0 + # MobID: 2534 # Count: 1 Score: 10 - ID: 128028 Group: "AG_BATTLE" Name: "Pleasant breeze" #Target: - # - MobID: 2535 + # - Id: 0 + # MobID: 2535 # Count: 1 Score: 10 - ID: 128029 Group: "AG_BATTLE" Name: "Visitor of old castle" Target: - - MobID: 2476 + - Id: 0 + MobID: 2476 Count: 1 Score: 10 - ID: 128030 Group: "AG_BATTLE" Name: "Lord of old castle" Target: - - MobID: 2476 + - Id: 0 + MobID: 2476 Count: 10 Score: 20 - ID: 128031 Group: "AG_BATTLE" Name: "Conqueror of old castle" Target: - - MobID: 2476 + - Id: 0 + MobID: 2476 Count: 50 Score: 50 - ID: 128032 Group: "AG_BATTLE" Name: "Haggard sucker" #Target: - # - MobID: 3150 + # - Id: 0 + # MobID: 3150 # Count: 1 Score: 10 - ID: 128033 Group: "AG_BATTLE" Name: "Hope of the Knight" #Target: - # - MobID: 3150 + # - Id: 0 + # MobID: 3150 # Count: 10 Score: 20 - ID: 128034 Group: "AG_BATTLE" Name: "Guardian of the Dawn" #Target: - # - MobID: 3150 + # - Id: 0 + # MobID: 3150 # Count: 50 Score: 50 - ID: 128035 Group: "AG_BATTLE" Name: "Time Traveler" #Target: - # - MobID: 3190 + # - Id: 0 + # MobID: 3190 # Count: 1 Score: 10 - ID: 128036 Group: "AG_BATTLE" Name: "Restore ancient relic" #Target: - # - MobID: 3190 + # - Id: 0 + # MobID: 3190 # Count: 10 Score: 20 - ID: 128037 Group: "AG_BATTLE" Name: "Master of relic transport" #Target: - # - MobID: 3190 + # - Id: 0 + # MobID: 3190 # Count: 50 Score: 50 - ID: 128038 Group: "AG_BATTLE" Name: "Show Jailbreak to the captain" #Target: - # - MobID: 3181 + # - Id: 0 + # MobID: 3181 # Count: 1 Score: 10 - ID: 128039 Group: "AG_BATTLE" Name: "Show Jailbreak to the weak captain" #Target: - # - MobID: 3188 + # - Id: 0 + # MobID: 3188 # Count: 1 Score: 10 - ID: 128040 Group: "AG_BATTLE" Name: "Riot on board" #Target: - # - MobID: 3181 + # - Id: 0 + # MobID: 3181 # Count: 1 Score: 20 - ID: 128041 Group: "AG_BATTLE" Name: "Turmoil on board" #Target: - # - MobID: 3181 + # - Id: 0 + # MobID: 3181 # Count: 10 Score: 20 - ID: 128042 Group: "AG_BATTLE" Name: "Rebellion on board" #Target: - # - MobID: 3181 + # - Id: 0 + # MobID: 3181 # Count: 50 Score: 50 - ID: 128043 Group: "AG_BATTLE" Name: "Revolt of Riot" #Target: - # - MobID: 3188 + # - Id: 0 + # MobID: 3188 # Count: 50 Score: 50 - ID: 128044 Group: "AG_BATTLE" Name: "Magic tournament champion" Target: - - MobID: 2564 + - Id: 0 + MobID: 2564 Count: 1 Score: 10 - ID: 128045 Group: "AG_BATTLE" Name: "Gladiator of Coliseum" Target: - - MobID: 2564 + - Id: 0 + MobID: 2564 Count: 10 Score: 20 - ID: 128046 Group: "AG_BATTLE" Name: "Slayer of Colosseum" Target: - - MobID: 2564 + - Id: 0 + MobID: 2564 Count: 50 Score: 50 - ID: 128047 Group: "AG_BATTLE" Name: "Endless Tower challenger" Target: - - MobID: 1956 + - Id: 0 + MobID: 1956 Count: 1 Score: 10 - ID: 128048 Group: "AG_BATTLE" Name: "Endless Tower Slayer" Target: - - MobID: 1956 + - Id: 0 + MobID: 1956 Count: 10 Score: 20 - ID: 128049 Group: "AG_BATTLE" Name: "Lord of the tower" Target: - - MobID: 1956 + - Id: 0 + MobID: 1956 Count: 50 Score: 50 - ID: 128050 Group: "AG_BATTLE" Name: "Novice Exorcist" Target: - - MobID: 2327 + - Id: 0 + MobID: 2327 Count: 1 Score: 10 - ID: 128051 Group: "AG_BATTLE" Name: "Experienced Exorcist" Target: - - MobID: 2327 + - Id: 0 + MobID: 2327 Count: 10 Score: 20 - ID: 128052 Group: "AG_BATTLE" Name: "Legendary Exorcist" Target: - - MobID: 2327 + - Id: 0 + 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] + Dependent: + - Id: 120001 + - Id: 120002 + - Id: 120003 + - Id: 120004 + - Id: 120005 + - Id: 120006 + - Id: 120007 + - Id: 120008 + - Id: 120009 + - Id: 120010 Reward: ItemID: 644 Score: 20 - ID: 129002 Group: "AG_ADVENTURE" Name: "Geffen Explorer" - Dependent: [120011, 120012, 120013, 120014, 120015, 120016, 120017] + Dependent: + - Id: 120011 + - Id: 120012 + - Id: 120013 + - Id: 120014 + - Id: 120015 + - Id: 120016 + - Id: 120017 Reward: ItemID: 644 Score: 20 - ID: 129003 Group: "AG_ADVENTURE" Name: "Sograt Desert Explorer" - Dependent: [120018, 120019, 120020, 120021, 120022, 120023] + Dependent: + - Id: 120018 + - Id: 120019 + - Id: 120020 + - Id: 120021 + - Id: 120022 + - Id: 120023 Reward: ItemID: 644 Score: 20 - ID: 129004 Group: "AG_ADVENTURE" Name: "Payon Explorer" - Dependent: [120024, 120025, 120026, 120027, 120028, 120029, 120030, 120031] + Dependent: + - Id: 120024 + - Id: 120025 + - Id: 120026 + - Id: 120027 + - Id: 120028 + - Id: 120029 + - Id: 120030 + - Id: 120031 Reward: ItemID: 644 Score: 20 - ID: 129005 Group: "AG_ADVENTURE" Name: "North Mjolnir Explorer" - Dependent: [120032, 120033, 120034, 120035, 120036] + Dependent: + - Id: 120032 + - Id: 120033 + - Id: 120034 + - Id: 120035 + - Id: 120036 Reward: ItemID: 644 Score: 20 - ID: 129006 Group: "AG_ADVENTURE" Name: "South Mjolnir Explorer" - Dependent: [120037, 120038, 120039, 120040, 120041, 120042, 120043] + Dependent: + - Id: 120037 + - Id: 120038 + - Id: 120039 + - Id: 120040 + - Id: 120041 + - Id: 120042 + - Id: 120043 Reward: ItemID: 644 Score: 20 - ID: 129007 Group: "AG_ADVENTURE" Name: "Comodo Explorer" - Dependent: [120044, 120045, 120046, 120047, 120048, 120049, 120050, 120051] + Dependent: + - Id: 120044 + - Id: 120045 + - Id: 120046 + - Id: 120047 + - Id: 120048 + - Id: 120049 + - Id: 120050 + - Id: 120051 Reward: ItemID: 644 Score: 20 - ID: 129008 Group: "AG_ADVENTURE" Name: "Rune Midgard Explorer" - Dependent: [129001, 129002, 129003, 129004, 129005, 129006, 129007] + Dependent: + - Id: 129001 + - Id: 129002 + - Id: 129003 + - Id: 129004 + - Id: 129005 + - Id: 129006 + - Id: 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] + Dependent: + - Id: 120052 + - Id: 120053 + - Id: 120054 + - Id: 120055 + - Id: 120056 + - Id: 120057 + - Id: 120058 + - Id: 120059 + - Id: 120060 + - Id: 120061 Reward: ItemID: 644 Score: 20 - ID: 129010 Group: "AG_ADVENTURE" Name: "Hugel Explorer" - Dependent: [120062, 120063, 120064, 120065, 120066] + Dependent: + - Id: 120062 + - Id: 120063 + - Id: 120064 + - Id: 120065 + - Id: 120066 Reward: ItemID: 644 Score: 20 - ID: 129011 Group: "AG_ADVENTURE" Name: "Einbroch Explorer" - Dependent: [120067, 120068, 120069, 120070, 120071, 120072, 120073, 120074] + Dependent: + - Id: 120067 + - Id: 120068 + - Id: 120069 + - Id: 120070 + - Id: 120071 + - Id: 120072 + - Id: 120073 + - Id: 120074 Reward: ItemID: 644 Score: 20 - ID: 129012 Group: "AG_ADVENTURE" Name: "Lighthalzen Explorer" - Dependent: [120075, 120076, 120077] + Dependent: + - Id: 120075 + - Id: 120076 + - Id: 120077 Reward: ItemID: 644 Score: 20 - ID: 129013 Group: "AG_ADVENTURE" Name: "Schwarzwald Explorer" - Dependent: [129009, 129010, 129011, 129012] + Dependent: + - Id: 129009 + - Id: 129010 + - Id: 129011 + - Id: 129012 Reward: ItemID: 617 Score: 50 - ID: 129014 Group: "AG_ADVENTURE" Name: "Rachel Explorer" - Dependent: [120078, 120079, 120080, 120081, 120082, 120083, 120084] + Dependent: + - Id: 120078 + - Id: 120079 + - Id: 120080 + - Id: 120081 + - Id: 120082 + - Id: 120083 + - Id: 120084 Reward: ItemID: 644 Score: 20 - ID: 129015 Group: "AG_ADVENTURE" Name: "Veins Explorer" - Dependent: [120085, 120086, 120087, 120088, 120089] + Dependent: + - Id: 120085 + - Id: 120086 + - Id: 120087 + - Id: 120088 + - Id: 120089 Reward: ItemID: 644 Score: 20 - ID: 129016 Group: "AG_ADVENTURE" Name: "Arunafeltz Explorer" - Dependent: [129014, 129015] + Dependent: + - Id: 129014 + - Id: 129015 Reward: ItemID: 617 Score: 50 - ID: 129017 Group: "AG_ADVENTURE" Name: "Laphine Explorer" - Dependent: [120090, 120091, 120092, 120093, 120094, 120095] + Dependent: + - Id: 120090 + - Id: 120091 + - Id: 120092 + - Id: 120093 + - Id: 120094 + - Id: 120095 Reward: ItemID: 644 Score: 20 - ID: 129018 Group: "AG_ADVENTURE" Name: "Manuk Explorer" - Dependent: [120096, 120097, 120098, 120099, 120100] + Dependent: + - Id: 120096 + - Id: 120097 + - Id: 120098 + - Id: 120099 + - Id: 120100 Reward: ItemID: 644 Score: 20 - ID: 129019 Group: "AG_ADVENTURE" Name: "Eclage Explorer" - Dependent: [129017, 129018] + Dependent: + - Id: 129017 + - Id: 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] + Dependent: + - Id: 120101 + - Id: 120102 + - Id: 120103 + - Id: 120104 + - Id: 120105 + - Id: 120106 + - Id: 120107 + - Id: 120108 + - Id: 120109 Reward: ItemID: 617 Score: 50 @@ -1563,7 +1753,8 @@ Achievements: Group: "AG_GOAL_LEVEL" Name: "Acquire the second aura!" Condition: " BaseLevel >= 150 " - Dependent: [200000] + Dependent: + - Id: 200000 Reward: ItemID: 5364 Script: " specialeffect2 EF_BLESSING; sc_start SC_BLESSING,30000,10; " @@ -1573,7 +1764,8 @@ Achievements: Group: "AG_GOAL_LEVEL" Name: "Acquire the third aura!" Condition: " BaseLevel >= 175 " - Dependent: [200001] + Dependent: + - Id: 200001 Reward: ItemID: 18880 Script: " specialeffect2 EF_BLESSING; sc_start SC_BLESSING,30000,10; " @@ -1592,7 +1784,8 @@ Achievements: Group: "AG_GOAL_LEVEL" Name: "Grandmaster Job level!" Condition: " JobLevel >= 70 " - Dependent: [200003] + Dependent: + - Id: 200003 Reward: ItemID: 12817 Script: " specialeffect2 EF_BLESSING; sc_start SC_BLESSING,30000,10; " @@ -1793,7 +1986,11 @@ Achievements: - ID: 230100 Group: "AG_TAMING" Name: "Poring is Love" - Dependent: [230101, 230102, 230103, 230104] + Dependent: + - Id: 230101 + - Id: 230102 + - Id: 230103 + - Id: 230104 Reward: Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; " TitleID: 1025 @@ -1801,7 +1998,13 @@ Achievements: - ID: 230110 Group: "AG_TAMING" Name: "Entomologist" - Dependent: [230111, 230112, 230113, 230114, 230115, 230116] + Dependent: + - Id: 230111 + - Id: 230112 + - Id: 230113 + - Id: 230114 + - Id: 230115 + - Id: 230116 Reward: Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; " TitleID: 1026 @@ -1809,7 +2012,15 @@ Achievements: - ID: 230120 Group: "AG_TAMING" Name: "Animals are also our friend" - Dependent: [230121, 230122, 230123, 230124, 230125, 230126, 230127, 230128] + Dependent: + - Id: 230121 + - Id: 230122 + - Id: 230123 + - Id: 230124 + - Id: 230125 + - Id: 230126 + - Id: 230127 + - Id: 230128 Reward: Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; " TitleID: 1027 @@ -1817,7 +2028,14 @@ Achievements: - ID: 230140 Group: "AG_TAMING" Name: "Monster Girls Unite!!" - Dependent: [230141, 230142, 230143, 230144, 230145, 230146, 230147] + Dependent: + - Id: 230141 + - Id: 230142 + - Id: 230143 + - Id: 230144 + - Id: 230145 + - Id: 230146 + - Id: 230147 Reward: Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; " TitleID: 1029 @@ -1826,175 +2044,200 @@ Achievements: Group: "AG_TAMING" Name: "Poring - taming" Target: - - MobID: 1002 + - Id: 0 + MobID: 1002 Count: 1 Score: 10 - ID: 230102 Group: "AG_TAMING" Name: "Drops - taming" Target: - - MobID: 1113 + - Id: 0 + MobID: 1113 Count: 1 Score: 10 - ID: 230103 Group: "AG_TAMING" Name: "Poporing - taming" Target: - - MobID: 1031 + - Id: 0 + MobID: 1031 Count: 1 Score: 10 - ID: 230104 Group: "AG_TAMING" Name: "Novice Poring - taming" Target: - - MobID: 2398 + - Id: 0 + MobID: 2398 Count: 1 Score: 10 - ID: 230111 Group: "AG_TAMING" Name: "Chonchon - taming" Target: - - MobID: 1011 + - Id: 0 + MobID: 1011 Count: 1 Score: 10 - ID: 230112 Group: "AG_TAMING" Name: "Steel Chonchon - taming" Target: - - MobID: 1042 + - Id: 0 + MobID: 1042 Count: 1 Score: 10 - ID: 230113 Group: "AG_TAMING" Name: "Hunter Fly - taming" Target: - - MobID: 1035 + - Id: 0 + MobID: 1035 Count: 1 Score: 10 - ID: 230114 Group: "AG_TAMING" Name: "Rocker - taming" Target: - - MobID: 1052 + - Id: 0 + MobID: 1052 Count: 1 Score: 10 - ID: 230115 Group: "AG_TAMING" Name: "Spore - taming" Target: - - MobID: 1014 + - Id: 0 + MobID: 1014 Count: 1 Score: 10 - ID: 230116 Group: "AG_TAMING" Name: "Poison Spore - taming" Target: - - MobID: 1077 + - Id: 0 + MobID: 1077 Count: 1 Score: 10 - ID: 230121 Group: "AG_TAMING" Name: "Lunatic - taming" Target: - - MobID: 1063 + - Id: 0 + MobID: 1063 Count: 1 Score: 10 - ID: 230122 Group: "AG_TAMING" Name: "Picky - taming" Target: - - MobID: 1049 + - Id: 0 + MobID: 1049 Count: 1 Score: 10 - ID: 230123 Group: "AG_TAMING" Name: "Savage Bebe - taming" Target: - - MobID: 1167 + - Id: 0 + MobID: 1167 Count: 1 Score: 10 - ID: 230124 Group: "AG_TAMING" Name: "Baby Desert Wolf - taming" Target: - - MobID: 1107 + - Id: 0 + MobID: 1107 Count: 1 Score: 10 - ID: 230125 Group: "AG_TAMING" Name: "Smokie - taming" Target: - - MobID: 1056 + - Id: 0 + MobID: 1056 Count: 1 Score: 10 - ID: 230126 Group: "AG_TAMING" Name: "Yoyo - taming" Target: - - MobID: 1057 + - Id: 0 + MobID: 1057 Count: 1 Score: 10 - ID: 230127 Group: "AG_TAMING" Name: "Peco Peco - taming" Target: - - MobID: 1019 + - Id: 0 + MobID: 1019 Count: 1 Score: 10 - ID: 230128 Group: "AG_TAMING" Name: "Petite - taming" Target: - - MobID: 1155 + - Id: 0 + MobID: 1155 Count: 1 Score: 10 - ID: 230141 Group: "AG_TAMING" Name: "Munak - taming" Target: - - MobID: 1026 + - Id: 0 + MobID: 1026 Count: 1 Score: 10 - ID: 230142 Group: "AG_TAMING" Name: "Isis - taming" Target: - - MobID: 1029 + - Id: 0 + MobID: 1029 Count: 1 Score: 10 - ID: 230143 Group: "AG_TAMING" Name: "Sohee - taming" Target: - - MobID: 1170 + - Id: 0 + MobID: 1170 Count: 1 Score: 10 - ID: 230144 Group: "AG_TAMING" Name: "Zherlthsh - taming" Target: - - MobID: 1200 + - Id: 0 + MobID: 1200 Count: 1 Score: 10 - ID: 230145 Group: "AG_TAMING" Name: "Alice - taming" Target: - - MobID: 1275 + - Id: 0 + MobID: 1275 Count: 1 Score: 10 - ID: 230146 Group: "AG_TAMING" Name: "Succubus - taming" Target: - - MobID: 1370 + - Id: 0 + MobID: 1370 Count: 1 Score: 10 - ID: 230147 Group: "AG_TAMING" Name: "Loli Ruri - taming" Target: - - MobID: 1505 + - Id: 0 + MobID: 1505 Count: 1 Score: 10 - ID: 220000 @@ -2049,35 +2292,40 @@ Achievements: Name: "Activating the market economy (1)" Condition: " ARG0 >= 10000 " Target: - - Count: 10000 + - Id: 0 + Count: 10000 Score: 10 - ID: 220010 Group: "AG_SPEND_ZENY" Name: "Activating the market economy (2)" Condition: " ARG0 >= 100000 " Target: - - Count: 100000 + - Id: 0 + Count: 100000 Score: 15 - ID: 220011 Group: "AG_SPEND_ZENY" Name: "Activating the market economy (3)" Condition: " ARG0 >= 500000 " Target: - - Count: 500000 + - Id: 0 + Count: 500000 Score: 20 - ID: 220012 Group: "AG_SPEND_ZENY" Name: "Activating the market economy (4)" Condition: " ARG0 >= 1000000 " Target: - - Count: 1000000 + - Id: 0 + Count: 1000000 Score: 30 - ID: 220013 Group: "AG_SPEND_ZENY" Name: "Activating the market economy (5)" Condition: " ARG0 >= 5000000 " Target: - - Count: 5000000 + - Id: 0 + Count: 5000000 Score: 50 - ID: 220014 Group: "AG_ENCHANT_SUCCESS" @@ -2191,47 +2439,63 @@ Achievements: - ID: 230200 Group: "AG_BATTLE" Name: "Poring seeker" - Dependent: [230201, 230202, 230203] + Dependent: + - Id: 230201 + - Id: 230202 + - Id: 230203 Score: 10 - ID: 230201 Group: "AG_BATTLE" Name: "Exploring Poring's life (1)" Target: - - MobID: 1002 + - Id: 0 + MobID: 1002 Count: 10 - - MobID: 2398 + - Id: 1 + MobID: 2398 Count: 10 - - MobID: 1113 + - Id: 2 + MobID: 1113 Count: 10 - - MobID: 1031 + - Id: 3 + MobID: 1031 Count: 10 - - MobID: 1242 + - Id: 4 + MobID: 1242 Count: 10 Score: 10 - ID: 230202 Group: "AG_BATTLE" Name: "Exploring Poring's life (2)" Target: - - MobID: 1090 + - Id: 0 + MobID: 1090 Count: 1 - - MobID: 1582 + - Id: 1 + MobID: 1582 Count: 1 - - MobID: 1096 + - Id: 2 + MobID: 1096 Count: 1 - - MobID: 1388 + - Id: 3 + MobID: 1388 Count: 1 - - MobID: 1120 + - Id: 4 + MobID: 1120 Count: 1 Score: 20 - ID: 230203 Group: "AG_BATTLE" Name: "Exploring Poring's life (3)" Target: - - MobID: 1613 + - Id: 0 + MobID: 1613 Count: 5 - - MobID: 1977 + - Id: 1 + MobID: 1977 Count: 5 - - MobID: 1836 + - Id: 2 + MobID: 1836 Count: 5 Score: 20 - ID: 240000 @@ -2248,7 +2512,8 @@ Achievements: - ID: 240002 Group: "AG_GOAL_ACHIEVE" Name: "Reaching Level 2" - Dependent: [240001] + Dependent: + - Id: 240001 Reward: ItemID: 644 Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; " @@ -2256,7 +2521,8 @@ Achievements: - ID: 240003 Group: "AG_GOAL_ACHIEVE" Name: "Reaching Level 3" - Dependent: [240002] + Dependent: + - Id: 240002 Reward: ItemID: 644 Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; " @@ -2264,7 +2530,8 @@ Achievements: - ID: 240004 Group: "AG_GOAL_ACHIEVE" Name: "Reaching Level 4" - Dependent: [240003] + Dependent: + - Id: 240003 Reward: ItemID: 644 Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; " @@ -2272,7 +2539,8 @@ Achievements: - ID: 240005 Group: "AG_GOAL_ACHIEVE" Name: "Reaching Level 5" - Dependent: [240004] + Dependent: + - Id: 240004 Reward: ItemID: 644 Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; " @@ -2280,7 +2548,8 @@ Achievements: - ID: 240006 Group: "AG_GOAL_ACHIEVE" Name: "Reaching Level 6" - Dependent: [240005] + Dependent: + - Id: 240005 Reward: ItemID: 644 Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; " @@ -2288,7 +2557,8 @@ Achievements: - ID: 240007 Group: "AG_GOAL_ACHIEVE" Name: "Reaching Level 7" - Dependent: [240006] + Dependent: + - Id: 240006 Reward: ItemID: 644 Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; " @@ -2296,7 +2566,8 @@ Achievements: - ID: 240008 Group: "AG_GOAL_ACHIEVE" Name: "Reaching Level 8" - Dependent: [240007] + Dependent: + - Id: 240007 Reward: ItemID: 644 Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; " @@ -2304,7 +2575,8 @@ Achievements: - ID: 240009 Group: "AG_GOAL_ACHIEVE" Name: "Reaching Level 9" - Dependent: [240008] + Dependent: + - Id: 240008 Reward: ItemID: 644 Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; " @@ -2312,7 +2584,8 @@ Achievements: - ID: 240010 Group: "AG_GOAL_ACHIEVE" Name: "Reaching Level 10" - Dependent: [240009] + Dependent: + - Id: 240009 Reward: ItemID: 644 Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; " @@ -2321,7 +2594,8 @@ Achievements: - ID: 240011 Group: "AG_GOAL_ACHIEVE" Name: "Reaching Level 11" - Dependent: [240010] + Dependent: + - Id: 240010 Reward: ItemID: 644 Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; " @@ -2329,7 +2603,8 @@ Achievements: - ID: 240012 Group: "AG_GOAL_ACHIEVE" Name: "Reaching Level 12" - Dependent: [240011] + Dependent: + - Id: 240011 Reward: ItemID: 644 Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; " @@ -2337,7 +2612,8 @@ Achievements: - ID: 240013 Group: "AG_GOAL_ACHIEVE" Name: "Reaching Level 13" - Dependent: [240012] + Dependent: + - Id: 240012 Reward: ItemID: 644 Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; " @@ -2345,7 +2621,8 @@ Achievements: - ID: 240014 Group: "AG_GOAL_ACHIEVE" Name: "Reaching Level 14" - Dependent: [240013] + Dependent: + - Id: 240013 Reward: ItemID: 644 Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; " @@ -2353,7 +2630,8 @@ Achievements: - ID: 240015 Group: "AG_GOAL_ACHIEVE" Name: "Reaching Level 15" - Dependent: [240014] + Dependent: + - Id: 240014 Reward: ItemID: 644 Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; " @@ -2361,7 +2639,8 @@ Achievements: - ID: 240016 Group: "AG_GOAL_ACHIEVE" Name: "Reaching Level 16" - Dependent: [240015] + Dependent: + - Id: 240015 Reward: ItemID: 644 Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; " @@ -2369,7 +2648,8 @@ Achievements: - ID: 240017 Group: "AG_GOAL_ACHIEVE" Name: "Reaching Level 17" - Dependent: [240016] + Dependent: + - Id: 240016 Reward: ItemID: 644 Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; " @@ -2377,7 +2657,8 @@ Achievements: - ID: 240018 Group: "AG_GOAL_ACHIEVE" Name: "Reaching Level 18" - Dependent: [240017] + Dependent: + - Id: 240017 Reward: ItemID: 644 Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; " @@ -2385,7 +2666,8 @@ Achievements: - ID: 240019 Group: "AG_GOAL_ACHIEVE" Name: "Reaching Level 19" - Dependent: [240018] + Dependent: + - Id: 240018 Reward: ItemID: 644 Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; " @@ -2393,7 +2675,8 @@ Achievements: - ID: 240020 Group: "AG_GOAL_ACHIEVE" Name: "Reaching Level 20" - Dependent: [240019] + Dependent: + - Id: 240019 Reward: ItemID: 644 Script: " specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,30000,10; " diff --git a/db/re/attendance.yml b/db/re/attendance.yml index 6de2f414d4..50a81f93f8 100644 --- a/db/re/attendance.yml +++ b/db/re/attendance.yml @@ -1,61 +1,61 @@ Header: - Type: ATTENDANCE_CONF + Type: ATTENDANCE_DB Version: 1 - -Attendance: - - Start: 20180502 - End: 20180529 - Rewards: - - Day: 1 - ItemId: 22979 - - Day: 2 - ItemId: 6316 - - Day: 3 - ItemId: 12265 - Amount: 5 - - Day: 4 - ItemId: 23047 - Amount: 5 - - Day: 5 - ItemId: 23038 - - Day: 6 - ItemId: 23043 - - Day: 7 - ItemId: 23340 - Amount: 3 - - Day: 8 - ItemId: 12516 - Amount: 5 - - Day: 9 - ItemId: 23307 - Amount: 5 - - Day: 10 - ItemId: 12610 - - Day: 11 - ItemId: 14533 - Amount: 2 - - Day: 12 - ItemId: 23012 - Amount: 3 - - Day: 13 - ItemId: 23048 - Amount: 5 - - Day: 14 - ItemId: 12264 - Amount: 5 - - Day: 15 - ItemId: 23046 - Amount: 5 - - Day: 16 - ItemId: 12515 - Amount: 5 - - Day: 17 - ItemId: 12522 - Amount: 5 - - Day: 18 - ItemId: 12523 - Amount: 5 - - Day: 19 - ItemId: 6234 - - Day: 20 - ItemId: 22845 + +Body: +# - Start: 20180502 +# End: 20180529 +# Rewards: +# - Day: 1 +# ItemId: 22979 +# - Day: 2 +# ItemId: 6316 +# - Day: 3 +# ItemId: 12265 +# Amount: 5 +# - Day: 4 +# ItemId: 23047 +# Amount: 5 +# - Day: 5 +# ItemId: 23038 +# - Day: 6 +# ItemId: 23043 +# - Day: 7 +# ItemId: 23340 +# Amount: 3 +# - Day: 8 +# ItemId: 12516 +# Amount: 5 +# - Day: 9 +# ItemId: 23307 +# Amount: 5 +# - Day: 10 +# ItemId: 12610 +# - Day: 11 +# ItemId: 14533 +# Amount: 2 +# - Day: 12 +# ItemId: 23012 +# Amount: 3 +# - Day: 13 +# ItemId: 23048 +# Amount: 5 +# - Day: 14 +# ItemId: 12264 +# Amount: 5 +# - Day: 15 +# ItemId: 23046 +# Amount: 5 +# - Day: 16 +# ItemId: 12515 +# Amount: 5 +# - Day: 17 +# ItemId: 12522 +# Amount: 5 +# - Day: 18 +# ItemId: 12523 +# Amount: 5 +# - Day: 19 +# ItemId: 6234 +# - Day: 20 +# ItemId: 22845 diff --git a/doc/achievements.txt b/doc/achievements.txt index e08b34f395..6225c33796 100644 --- a/doc/achievements.txt +++ b/doc/achievements.txt @@ -3,7 +3,7 @@ //===== By: ================================================== //= rAthena Dev Team //===== Last Updated: ======================================== -//= 20170531 +//= 20190226 //===== Description: ========================================= //= Explanation of the achievements_db.yml file and structure. //============================================================ @@ -64,7 +64,8 @@ Example 2: // 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 + - Id: 0 // Array index value + Count: 100 --------------------------------------- diff --git a/src/char/char.cpp b/src/char/char.cpp index e49925aa02..cffb8e2a12 100644 --- a/src/char/char.cpp +++ b/src/char/char.cpp @@ -3208,7 +3208,7 @@ int do_init(int argc, char **argv) ShowNotice("And then change the user/password to use in conf/char_athena.conf (or conf/import/char_conf.txt)\n"); } - inter_init_sql((argc > 2) ? argv[2] : inter_cfgName); // inter server configuration + inter_init_sql((argc > 2) ? argv[2] : SQL_CONF_NAME); // inter server configuration auth_db = idb_alloc(DB_OPT_RELEASE_DATA); online_char_db = idb_alloc(DB_OPT_RELEASE_DATA); diff --git a/src/char/int_storage.cpp b/src/char/int_storage.cpp index 9019f8986a..eb77e07041 100644 --- a/src/char/int_storage.cpp +++ b/src/char/int_storage.cpp @@ -18,23 +18,18 @@ #include "inter.hpp" #include "int_guild.hpp" -/** - * Check if storage ID is valid - * @param id: Storage ID - * @return True if success or false on failure - */ -bool inter_premiumStorage_exists(uint8 id) { - return interserv_config.storages.find(id) != interserv_config.storages.end(); -} - /** * Get max storage amount * @param id: Storage ID * @return Max amount */ int inter_premiumStorage_getMax(uint8 id) { - if (inter_premiumStorage_exists(id)) - return interserv_config.storages[id]->max_num; + std::shared_ptr storage = interServerDb.find( id ); + + if( storage != nullptr ){ + return storage->max_num; + } + return MAX_STORAGE; } @@ -44,8 +39,12 @@ int inter_premiumStorage_getMax(uint8 id) { * @return Table name */ const char *inter_premiumStorage_getTableName(uint8 id) { - if (inter_premiumStorage_exists(id)) - return interserv_config.storages[id]->table; + std::shared_ptr storage = interServerDb.find( id ); + + if( storage != nullptr ){ + return storage->table; + } + return schema_config.storage_db; } @@ -55,8 +54,12 @@ const char *inter_premiumStorage_getTableName(uint8 id) { * @return printable name */ const char *inter_premiumStorage_getPrintableName(uint8 id) { - if (inter_premiumStorage_exists(id)) - return interserv_config.storages[id]->name; + std::shared_ptr storage = interServerDb.find( id ); + + if( storage != nullptr ){ + return storage->name; + } + return "Storage"; } @@ -152,7 +155,7 @@ bool guild_storage_fromsql(int guild_id, struct s_storage* p) void inter_storage_checkDB(void) { // Checking storage tables - for (auto storage_table : interserv_config.storages) { + for( auto storage_table : interServerDb ){ if (SQL_ERROR == Sql_Query(sql_handle, "SELECT `id`,`account_id`,`nameid`,`amount`,`equip`,`identify`,`refine`," "`attribute`,`card0`,`card1`,`card2`,`card3`,`option_id0`,`option_val0`,`option_parm0`,`option_id1`,`option_val1`,`option_parm1`," "`option_id2`,`option_val2`,`option_parm2`,`option_id3`,`option_val3`,`option_parm3`,`option_id4`,`option_val4`,`option_parm4`," @@ -522,10 +525,11 @@ bool mapif_parse_StorageLoad(int fd) { switch (type) { case TABLE_INVENTORY: res = inventory_fromsql(cid, &stor); break; case TABLE_STORAGE: - if (!inter_premiumStorage_exists(stor_id)) { - ShowError("Invalid storage with id %d\n", stor_id); + if( !interServerDb.exists( stor_id ) ){ + ShowError( "Invalid storage with id %d\n", stor_id ); return false; } + res = storage_fromsql(aid, &stor); break; case TABLE_CART: res = cart_fromsql(cid, &stor); break; @@ -561,10 +565,11 @@ bool mapif_parse_StorageSave(int fd) { switch(type){ case TABLE_INVENTORY: inventory_tosql(cid, &stor); break; case TABLE_STORAGE: - if (!inter_premiumStorage_exists(stor.stor_id)) { - ShowError("Invalid storage with id %d\n", stor.stor_id); + if( !interServerDb.exists( stor.stor_id ) ){ + ShowError( "Invalid storage with id %d\n", stor.stor_id ); return false; } + storage_tosql(aid, &stor); break; case TABLE_CART: cart_tosql(cid, &stor); break; diff --git a/src/char/int_storage.hpp b/src/char/int_storage.hpp index f9dc82a79c..aa06c5c856 100644 --- a/src/char/int_storage.hpp +++ b/src/char/int_storage.hpp @@ -11,7 +11,6 @@ struct s_storage; void inter_storage_sql_init(void); void inter_storage_sql_final(void); -bool inter_premiumStorage_exists(uint8 id); int inter_premiumStorage_getMax(uint8 id); const char *inter_premiumStorage_getTableName(uint8 id); const char *inter_premiumStorage_getPrintableName(uint8 id); diff --git a/src/char/inter.cpp b/src/char/inter.cpp index 3d0933a985..c02dd4dc8b 100644 --- a/src/char/inter.cpp +++ b/src/char/inter.cpp @@ -11,6 +11,7 @@ #include #include "../common/cbasetypes.hpp" +#include "../common/database.hpp" #include "../common/malloc.hpp" #include "../common/showmsg.hpp" #include "../common/socket.hpp" @@ -34,6 +35,9 @@ #include "int_quest.hpp" #include "int_storage.hpp" +std::string cfgFile = "inter_athena.yml"; ///< Inter-Config file +InterServerDatabase interServerDb; + #define WISDATA_TTL (60*1000) //Wis data Time To Live (60 seconds) #define WISDELLIST_MAX 256 // Number of elements in the list Delete data Wis @@ -46,7 +50,6 @@ char char_server_id[32] = "ragnarok"; char char_server_pw[32] = ""; // Allow user to send empty password (bugreport:7787) char char_server_db[32] = "ragnarok"; char default_codepage[32] = ""; //Feature by irmin. -struct Inter_Config interserv_config; unsigned int party_share_level = 10; /// Received packet Lengths from map-server @@ -819,7 +822,7 @@ int inter_config_read(const char* cfgName) else if(!strcmpi(w1,"log_inter")) charserv_config.log_inter = atoi(w2); else if(!strcmpi(w1,"inter_server_conf")) - interserv_config.cfgFile = w2; + cfgFile = w2; else if(!strcmpi(w1,"import")) inter_config_read(w2); } @@ -848,103 +851,85 @@ int inter_log(const char* fmt, ...) return 0; } -void yaml_invalid_warning(const char* fmt, YAML::Node &node, std::string &file) { - YAML::Emitter out; - out << node; - ShowWarning(fmt, file.c_str()); - ShowMessage("%s\n", out.c_str()); +const std::string InterServerDatabase::getDefaultLocation(){ + return std::string(conf_path) + "/" + cfgFile; } /** - * Read inter config file - **/ -void inter_config_readConf(void) { - std::vector directories = { "conf/", "conf/import/" }; - static const std::string file_name(interserv_config.cfgFile); + * Reads and parses an entry from the inter_server. + * @param node: YAML node containing the entry. + * @return count of successfully parsed rows + */ +uint64 InterServerDatabase::parseBodyNode( const YAML::Node& node ){ + uint32 id; - for (auto directory : directories) { - std::string current_file = directory + file_name; - YAML::Node config; - int count = 0; - - try { - config = YAML::LoadFile(current_file); - } - catch (std::exception &e) { - ShowError("Cannot read storage definition file '" CL_WHITE "%s" CL_RESET "' (Caused by : " CL_WHITE "%s" CL_RESET ").\n", current_file.c_str(), e.what()); - return; - } - - if (config["Storages"]) { - for (auto node : config["Storages"]) { - unsigned int id; - - if (!node["ID"]) { - yaml_invalid_warning("inter_config_readConf: Storage definition with no ID field in '" CL_WHITE "%s" CL_RESET "', skipping.\n", node, current_file); - continue; - } - - try { - id = node["ID"].as(); - } - catch (const std::exception&) { - yaml_invalid_warning("inter_config_readConf: Storage definition with invalid ID field in '" CL_WHITE "%s" CL_RESET "', skipping.\n", node, current_file); - continue; - } - - if( id > UINT8_MAX ){ - yaml_invalid_warning("inter_config_readConf: Storage definition with invalid ID field in '" CL_WHITE "%s" CL_RESET "', skipping.\n", node, current_file); - continue; - } - - bool existing = inter_premiumStorage_exists(id); - auto storage_table = existing ? interserv_config.storages[id] : std::make_shared(); - - if (!existing && (!node["Name"] || !node["Table"])) { - yaml_invalid_warning("inter_config_readConf: Invalid storage definition in '" CL_WHITE "%s" CL_RESET "'.\n", node, current_file); - continue; - } - - if (node["Name"]) - safestrncpy(storage_table->name, node["Name"].as().c_str(), NAME_LENGTH); - if(node["Table"]) - safestrncpy(storage_table->table, node["Table"].as().c_str(), DB_NAME_LEN); - if (node["Max"]) { - try { - storage_table->max_num = node["Max"].as(); - } - catch (const std::exception&) { - yaml_invalid_warning("inter_config_readConf: Storage definition with invalid Max field in '" CL_WHITE "%s" CL_RESET "', skipping.\n", node, current_file); - continue; - } - } - else if (!existing) - storage_table->max_num = MAX_STORAGE; - - if (!existing) { - storage_table->id = (uint8)id; - interserv_config.storages[id] = storage_table; - } - - count++; - } - } - ShowStatus("Done reading '" CL_WHITE "%d" CL_RESET "' storage information in '" CL_WHITE "%s" CL_RESET "'\n", count, current_file.c_str()); + if( !this->asUInt32( node, "ID", id ) ){ + return 0; } -} -void inter_config_finalConf(void) { + auto storage_table = this->find( id ); + bool existing = storage_table != nullptr; -} + if( !existing ){ + if( !this->nodeExists( node, "Name" ) ){ + this->invalidWarning( node, "Node \"Name\" is missing.\n" ); + return 0; + } -void inter_config_defaults(void) { - interserv_config.cfgFile = "inter_server.yml"; + if( !this->nodeExists( node, "Table" ) ){ + this->invalidWarning( node, "Node \"Table\" is missing.\n" ); + return 0; + } + + storage_table = std::make_shared(); + + storage_table->id = (uint8)id; + } + + if( this->nodeExists( node, "Name" ) ){ + std::string name; + + if( !this->asString( node, "Name", name ) ){ + return 0; + } + + safestrncpy( storage_table->name, name.c_str(), NAME_LENGTH ); + } + + if( this->nodeExists( node, "Table" ) ){ + std::string table; + + if( !this->asString( node, "Table", table ) ){ + return 0; + } + + safestrncpy( storage_table->table, table.c_str(), DB_NAME_LEN ); + } + + if( this->nodeExists( node, "Max" ) ){ + uint16 max; + + if( !this->asUInt16( node, "Max", max ) ){ + return 0; + } + + storage_table->max_num = max; + }else{ + if( !existing ){ + storage_table->max_num = MAX_STORAGE; + } + } + + if( !existing ){ + this->put( storage_table->id, storage_table ); + } + + return 1; } // initialize int inter_init_sql(const char *file) { - inter_config_defaults(); inter_config_read(file); //DB connection initialized @@ -965,7 +950,7 @@ int inter_init_sql(const char *file) } wis_db = idb_alloc(DB_OPT_RELEASE_DATA); - inter_config_readConf(); + interServerDb.load(); inter_guild_sql_init(); inter_storage_sql_init(); inter_party_sql_init(); @@ -986,7 +971,6 @@ void inter_final(void) { wis_db->destroy(wis_db, NULL); - inter_config_finalConf(); inter_guild_sql_final(); inter_storage_sql_final(); inter_party_sql_final(); @@ -1009,13 +993,13 @@ void inter_final(void) * @param fd **/ void inter_Storage_sendInfo(int fd) { - int size = sizeof(struct s_storage_table), len = 4 + interserv_config.storages.size() * size, offset; + int size = sizeof(struct s_storage_table), len = 4 + interServerDb.size() * size, offset; // Send storage table information WFIFOHEAD(fd, len); WFIFOW(fd, 0) = 0x388c; WFIFOW(fd, 2) = len; offset = 4; - for (auto storage : interserv_config.storages) { + for( auto storage : interServerDb ){ memcpy(WFIFOP(fd, offset), storage.second.get(), size); offset += size; } diff --git a/src/char/inter.hpp b/src/char/inter.hpp index f3715ab980..2d7a562903 100644 --- a/src/char/inter.hpp +++ b/src/char/inter.hpp @@ -9,15 +9,22 @@ #include #include "../common/cbasetypes.hpp" +#include "../common/database.hpp" #include "../common/sql.hpp" struct s_storage_table; -struct Inter_Config { - std::string cfgFile; ///< Inter-Config file - std::unordered_map< uint8, std::shared_ptr > storages; ///< Storage name & table information + +class InterServerDatabase : public TypesafeYamlDatabase{ +public: + InterServerDatabase() : TypesafeYamlDatabase( "INTER_SERVER_DB", 1 ){ + + } + + const std::string getDefaultLocation(); + uint64 parseBodyNode( const YAML::Node& node ); }; -extern struct Inter_Config interserv_config; +extern InterServerDatabase interServerDb; int inter_init_sql(const char *file); void inter_final(void); @@ -30,8 +37,6 @@ void mapif_accinfo_ack(bool success, int map_fd, int u_fd, int u_aid, int accoun int inter_log(const char *fmt,...); -#define inter_cfgName "conf/inter_athena.conf" - extern unsigned int party_share_level; extern Sql* sql_handle; diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index cde9395bce..cee88aa2a9 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -86,6 +86,7 @@ set( COMMON_BASE_HEADERS ${COMMON_ALL_HEADERS} "${COMMON_SOURCE_DIR}/conf.hpp" "${COMMON_SOURCE_DIR}/core.hpp" + "${COMMON_SOURCE_DIR}/database.hpp" "${COMMON_SOURCE_DIR}/db.hpp" "${COMMON_SOURCE_DIR}/des.hpp" "${COMMON_SOURCE_DIR}/ers.hpp" @@ -109,6 +110,7 @@ set( COMMON_BASE_HEADERS set( COMMON_BASE_SOURCES "${COMMON_SOURCE_DIR}/conf.cpp" "${COMMON_SOURCE_DIR}/core.cpp" + "${COMMON_SOURCE_DIR}/database.cpp" "${COMMON_SOURCE_DIR}/db.cpp" "${COMMON_SOURCE_DIR}/des.cpp" "${COMMON_SOURCE_DIR}/ers.cpp" diff --git a/src/common/Makefile.in b/src/common/Makefile.in index 144941ea7f..52ba262046 100644 --- a/src/common/Makefile.in +++ b/src/common/Makefile.in @@ -1,7 +1,7 @@ COMMON_OBJ = core.o socket.o timer.o db.o nullpo.o malloc.o showmsg.o strlib.o utils.o utilities.o \ grfio.o mapindex.o ers.o md5calc.o minicore.o minisocket.o minimalloc.o random.o des.o \ - conf.o msg_conf.o cli.o sql.o + conf.o msg_conf.o cli.o sql.o database.o COMMON_DIR_OBJ = $(COMMON_OBJ:%=obj/%) COMMON_H = $(shell ls ../common/*.hpp) COMMON_AR = obj/common.a diff --git a/src/common/common.vcxproj b/src/common/common.vcxproj index 8aa566f32d..ef3a239685 100644 --- a/src/common/common.vcxproj +++ b/src/common/common.vcxproj @@ -23,6 +23,7 @@ + @@ -47,6 +48,7 @@ + diff --git a/src/common/common.vcxproj.filters b/src/common/common.vcxproj.filters index fe0d2941e7..aac83d890a 100644 --- a/src/common/common.vcxproj.filters +++ b/src/common/common.vcxproj.filters @@ -23,6 +23,9 @@ Header Files + + Header Files + Header Files @@ -91,6 +94,9 @@ Source Files + + Source Files + Source Files diff --git a/src/common/core.cpp b/src/common/core.cpp index d1cd123f09..4d10857aee 100644 --- a/src/common/core.cpp +++ b/src/common/core.cpp @@ -33,6 +33,7 @@ void (*shutdown_callback)(void) = NULL; int runflag = CORE_ST_RUN; char db_path[12] = "db"; /// relative path for db from server +char conf_path[12] = "conf"; /// relative path for conf from server char *SERVER_NAME = NULL; char SERVER_TYPE = ATHENA_SERVER_NONE; diff --git a/src/common/core.hpp b/src/common/core.hpp index 6bfb64a42f..d853174058 100644 --- a/src/common/core.hpp +++ b/src/common/core.hpp @@ -22,6 +22,7 @@ extern char **arg_v; extern int runflag; extern char *SERVER_NAME; extern char db_path[12]; /// relative path for db from servers +extern char conf_path[12]; /// relative path for conf from servers enum { ATHENA_SERVER_NONE = 0, // not defined diff --git a/src/common/database.cpp b/src/common/database.cpp new file mode 100644 index 0000000000..65f7fabb39 --- /dev/null +++ b/src/common/database.cpp @@ -0,0 +1,238 @@ +// Copyright (c) rAthena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "database.hpp" + +#include "showmsg.hpp" + +bool YamlDatabase::nodeExists( const YAML::Node& node, const std::string& name ){ + try{ + if( node[name] ){ + return true; + }else{ + return false; + } + // TODO: catch( ... ) instead? + }catch( const YAML::InvalidNode& ){ + return false; + }catch( const YAML::BadSubscript& ){ + return false; + } +} + +bool YamlDatabase::verifyCompatibility( const YAML::Node& rootNode ){ + if( !this->nodeExists( rootNode, "Header" ) ){ + ShowError( "No database \"Header\" was found.\n" ); + return false; + } + + const YAML::Node& headerNode = rootNode["Header"]; + + if( !this->nodeExists( headerNode, "Type" ) ){ + ShowError( "No database \"Type\" was found.\n" ); + return false; + } + + const YAML::Node& typeNode = headerNode["Type"]; + const std::string& tmpType = typeNode.as(); + + if( tmpType != this->type ){ + ShowError( "Database type mismatch: %s != %s.\n", this->type.c_str(), tmpType.c_str() ); + return false; + } + + uint16 tmpVersion; + + if( !this->asUInt16( headerNode, "Version", tmpVersion ) ){ + ShowError("Invalid header \"Version\" type for %s database.\n", this->type.c_str()); + return false; + } + + if( tmpVersion != this->version ){ + if( tmpVersion > this->version ){ + ShowError( "Database version %hu is not supported. Maximum version is: %hu\n", tmpVersion, this->version ); + return false; + }else if( tmpVersion >= this->minimumVersion ){ + ShowWarning( "Database version %hu is outdated and should be updated. Current version is: %hu\n", tmpVersion, this->minimumVersion ); + }else{ + ShowError( "Database version %hu is not supported anymore. Minimum version is: %hu\n", tmpVersion, this->minimumVersion ); + return false; + } + } + + return true; +} + +bool YamlDatabase::load(){ + return this->load( this->getDefaultLocation() ); +} + +bool YamlDatabase::load(const std::string& path) { + YAML::Node rootNode; + + try { + rootNode = YAML::LoadFile(path); + } + catch(YAML::Exception &e) { + ShowError("Failed to read %s database file from '" CL_WHITE "%s" CL_RESET "'.\n", this->type.c_str(), path.c_str()); + ShowError("%s (Line %d: Column %d)\n", e.msg.c_str(), e.mark.line, e.mark.column); + return false; + } + + // Required here already for header error reporting + this->currentFile = path; + + if (!this->verifyCompatibility(rootNode)){ + ShowError("Failed to verify compatibility with %s database file from '" CL_WHITE "%s" CL_RESET "'.\n", this->type.c_str(), this->currentFile.c_str()); + return false; + } + + const YAML::Node& header = rootNode["Header"]; + + if( this->nodeExists( header, "Clear" ) ){ + bool clear; + + if( this->asBool( header, "Clear", clear ) && clear ){ + this->clear(); + } + } + + this->parse( rootNode ); + + this->parseImports( rootNode ); + + return true; +} + +void YamlDatabase::parse( const YAML::Node& rootNode ){ + uint64 count = 0; + + if( this->nodeExists( rootNode, "Body" ) ){ + for( const YAML::Node &node : rootNode["Body"] ){ + count += this->parseBodyNode( node ); + } + + ShowStatus("Done reading '" CL_WHITE "%" PRIu64 CL_RESET "' entries in '" CL_WHITE "%s" CL_RESET "'\n", count, this->currentFile.c_str()); + } +} + +void YamlDatabase::parseImports( const YAML::Node& rootNode ){ + if( this->nodeExists( rootNode, "Footer" ) ){ + const YAML::Node& footerNode = rootNode["Footer"]; + + if( this->nodeExists( footerNode, "Imports") ){ + for( const YAML::Node& node : footerNode["Imports"] ){ + std::string importFile; + + if( !this->asString( node, "Path", importFile ) ){ + continue; + } + + if( this->nodeExists( node, "Mode" ) ){ + std::string mode; + + if( !this->asString( node, "Mode", mode ) ){ + continue; + } + +#ifdef RENEWAL + std::string compiledMode = "Renewal"; +#else + std::string compiledMode = "Prerenewal"; +#endif + + if( compiledMode != mode ){ + // Skip this import + continue; + } + } + + this->load( importFile ); + } + } + } +} + +template bool YamlDatabase::asType( const YAML::Node& node, const std::string& name, R& out ){ + if( this->nodeExists( node, name ) ){ + const YAML::Node& dataNode = node[name]; + + try { + R value = dataNode.as(); + + out = value; + + return true; + }catch( const YAML::BadConversion& ){ + this->invalidWarning( dataNode, "Unable to parse \"%s\".\n", name.c_str() ); + return false; + } + }else{ + this->invalidWarning( node, "Missing node \"%s\".\n", name.c_str() ); + return false; + } +} + +bool YamlDatabase::asBool(const YAML::Node &node, const std::string &name, bool &out) { + return asType(node, name, out); +} + +bool YamlDatabase::asInt16( const YAML::Node& node, const std::string& name, int16& out ){ + return asType( node, name, out); +} + +bool YamlDatabase::asUInt16(const YAML::Node& node, const std::string& name, uint16& out) { + return asType(node, name, out); +} + +bool YamlDatabase::asInt32(const YAML::Node &node, const std::string &name, int32 &out) { + return asType(node, name, out); +} + +bool YamlDatabase::asUInt32(const YAML::Node &node, const std::string &name, uint32 &out) { + return asType(node, name, out); +} + +bool YamlDatabase::asInt64(const YAML::Node &node, const std::string &name, int64 &out) { + return asType(node, name, out); +} + +bool YamlDatabase::asUInt64(const YAML::Node &node, const std::string &name, uint64 &out) { + return asType(node, name, out); +} + +bool YamlDatabase::asFloat(const YAML::Node &node, const std::string &name, float &out) { + return asType(node, name, out); +} + +bool YamlDatabase::asDouble(const YAML::Node &node, const std::string &name, double &out) { + return asType(node, name, out); +} + +bool YamlDatabase::asString(const YAML::Node &node, const std::string &name, std::string &out) { + return asType(node, name, out); +} + +void YamlDatabase::invalidWarning( const YAML::Node &node, const char* fmt, ... ){ + va_list ap; + + va_start(ap, fmt); + + _vShowMessage( MSG_ERROR, fmt, ap ); + + va_end(ap); + + ShowError( "Occurred in file '" CL_WHITE "%s" CL_RESET "' on line %d and column %d.\n", this->currentFile.c_str(), node.Mark().line + 1, node.Mark().column ); + +#ifdef DEBUG + YAML::Emitter out; + + out << node; + + ShowMessage( "%s\n", out.c_str() ); +#endif +} + +std::string YamlDatabase::getCurrentFile(){ + return this->currentFile; +} diff --git a/src/common/database.hpp b/src/common/database.hpp new file mode 100644 index 0000000000..47393f72a9 --- /dev/null +++ b/src/common/database.hpp @@ -0,0 +1,114 @@ +// Copyright (c) rAthena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef DATABASE_HPP +#define DATABASE_HPP + +#include + +#include + +#include "../config/core.hpp" + +#include "cbasetypes.hpp" +#include "core.hpp" + +class YamlDatabase{ +// Internal stuff +private: + std::string type; + uint16 version; + uint16 minimumVersion; + std::string currentFile; + + bool verifyCompatibility( const YAML::Node& rootNode ); + bool load( const std::string& path ); + void parse( const YAML::Node& rootNode ); + void parseImports( const YAML::Node& rootNode ); + template bool asType( const YAML::Node& node, const std::string& name, R& out ); + +// These should be visible/usable by the implementation provider +protected: + // Helper functions + bool nodeExists( const YAML::Node& node, const std::string& name ); + void invalidWarning( const YAML::Node &node, const char* fmt, ... ); + std::string getCurrentFile(); + + // Conversion functions + bool asBool(const YAML::Node &node, const std::string &name, bool &out); + bool asInt16(const YAML::Node& node, const std::string& name, int16& out ); + bool asUInt16(const YAML::Node& node, const std::string& name, uint16& out); + bool asInt32(const YAML::Node &node, const std::string &name, int32 &out); + bool asUInt32(const YAML::Node &node, const std::string &name, uint32 &out); + bool asInt64(const YAML::Node &node, const std::string &name, int64 &out); + bool asUInt64(const YAML::Node &node, const std::string &name, uint64 &out); + bool asFloat(const YAML::Node &node, const std::string &name, float &out); + bool asDouble(const YAML::Node &node, const std::string &name, double &out); + bool asString(const YAML::Node &node, const std::string &name, std::string &out); + +public: + YamlDatabase( const std::string type_, uint16 version_, uint16 minimumVersion_ ){ + this->type = type_; + this->version = version_; + this->minimumVersion = minimumVersion_; + } + + YamlDatabase( const std::string& type_, uint16 version_ ) : YamlDatabase( type_, version_, version_ ){ + // Empty since everything is handled by the real constructor + } + + bool load(); + + // Functions that need to be implemented for each type + virtual void clear() = 0; + virtual const std::string getDefaultLocation() = 0; + virtual uint64 parseBodyNode( const YAML::Node& node ) = 0; +}; + +template class TypesafeYamlDatabase : public YamlDatabase{ +protected: + std::unordered_map> data; + +public: + TypesafeYamlDatabase( const std::string type_, uint16 version_, uint16 minimumVersion_ ) : YamlDatabase( type_, version_, minimumVersion_ ){ + } + + TypesafeYamlDatabase( const std::string& type_, uint16 version_ ) : YamlDatabase( type_, version_, version_ ){ + } + + void clear(){ + this->data.clear(); + } + + bool exists( keytype key ){ + return this->find( key ) != nullptr; + } + + std::shared_ptr find( keytype key ){ + auto it = this->data.find( key ); + + if( it != this->data.end() ){ + return it->second; + }else{ + return nullptr; + } + } + + void put( keytype key, std::shared_ptr ptr ){ + this->data[key] = ptr; + } + + typename std::unordered_map>::iterator begin(){ + return this->data.begin(); + } + + typename std::unordered_map>::iterator end(){ + return this->data.end(); + } + + size_t size(){ + return this->data.size(); + } +}; + +#endif /* DATABASE_HPP */ diff --git a/src/common/showmsg.hpp b/src/common/showmsg.hpp index aa71ee842b..40ef667a4d 100644 --- a/src/common/showmsg.hpp +++ b/src/common/showmsg.hpp @@ -86,6 +86,7 @@ enum msg_type { }; extern void ClearScreen(void); +extern int _vShowMessage(enum msg_type flag, const char *string, va_list ap); extern void ShowMessage(const char *, ...); extern void ShowStatus(const char *, ...); extern void ShowSQL(const char *, ...); diff --git a/src/common/utilities.hpp b/src/common/utilities.hpp index 10401d8c9b..ea8b9afbd1 100644 --- a/src/common/utilities.hpp +++ b/src/common/utilities.hpp @@ -43,6 +43,22 @@ namespace rathena { } } + /** + * Find a key-value pair and return the key value as a reference + * @param map: Map to search through + * @param key: Key wanted + * @return Key value on success or nullptr on failure + */ + template std::shared_ptr map_find( std::map>& map, K key ){ + auto it = map.find( key ); + + if( it != map.end() ){ + return it->second; + }else{ + return nullptr; + } + } + /** * Get a key-value pair and return the key value * @param map: Map to search through @@ -74,6 +90,21 @@ namespace rathena { return nullptr; } + /** + * Find a key-value pair and return the key value as a reference + * @param map: Unordered Map to search through + * @param key: Key wanted + * @return Key value on success or nullptr on failure + */ + template std::shared_ptr umap_find(std::unordered_map>& map, K key) { + auto it = map.find(key); + + if (it != map.end()) + return it->second; + else + return nullptr; + } + /** * Get a key-value pair and return the key value * @param map: Unordered Map to search through diff --git a/src/map/achievement.cpp b/src/map/achievement.cpp index 8b41201559..85e090cf25 100644 --- a/src/map/achievement.cpp +++ b/src/map/achievement.cpp @@ -11,10 +11,12 @@ #include #include "../common/cbasetypes.hpp" +#include "../common/database.hpp" #include "../common/malloc.hpp" #include "../common/nullpo.hpp" #include "../common/showmsg.hpp" #include "../common/strlib.hpp" +#include "../common/utilities.hpp" #include "../common/utils.hpp" #include "battle.hpp" @@ -28,42 +30,290 @@ #include "script.hpp" #include "status.hpp" -std::unordered_map> achievements; -std::vector achievement_mobs; // Avoids checking achievements on every mob killed +using namespace rathena; -/** - * Searches an achievement by ID - * @param achievement_id: ID to lookup - * @return True if achievement exists or false if it doesn't - */ -bool achievement_exists(int achievement_id) -{ - return achievements.find(achievement_id) != achievements.end(); +void AchievementDatabase::clear(){ + TypesafeYamlDatabase::clear(); + this->achievement_mobs.clear(); +} + +const std::string AchievementDatabase::getDefaultLocation(){ + return std::string(db_path) + "/achievement_db.yml"; } /** - * Return an achievement by ID - * @param achievement_id: ID to lookup - * @return shared_ptr of achievement + * Reads and parses an entry from the achievement_db. + * @param node: YAML node containing the entry. + * @return count of successfully parsed rows */ -std::shared_ptr &achievement_get(int achievement_id) -{ - return achievements[achievement_id]; +uint64 AchievementDatabase::parseBodyNode(const YAML::Node &node){ + uint32 achievement_id; + + // TODO: doesnt match camel case + if( !this->asUInt32( node, "ID", achievement_id ) ){ + return 0; + } + + std::shared_ptr achievement = this->find( achievement_id ); + bool exists = achievement != nullptr; + + if( !exists ){ + if( !this->nodeExists( node, "Group" ) ){ + return 0; + } + + if( !this->nodeExists( node, "Name" ) ){ + return 0; + } + + achievement = std::make_shared(); + achievement->achievement_id = achievement_id; + } + + if( this->nodeExists( node, "Group" ) ){ + std::string group_name; + + if( !this->asString( node, "Group", group_name ) ){ + return 0; + } + + int constant; + + if( !script_get_constant( group_name.c_str(), &constant ) ){ + this->invalidWarning( node, "achievement_read_db_sub: Invalid group %s for achievement %d, skipping.\n", group_name.c_str(), achievement_id ); + return 0; + } + + achievement->group = (e_achievement_group)constant; + } + + if( this->nodeExists( node, "Name" ) ){ + std::string name; + + if( !this->asString( node, "Name", name ) ){ + return 0; + } + + achievement->name = name; + } + + if( this->nodeExists( node, "Target" ) ){ + const YAML::Node& targets = node["Target"]; + + for( const YAML::Node& targetNode : targets ){ + if( achievement->targets.size() >= MAX_ACHIEVEMENT_OBJECTIVES ){ + this->invalidWarning( targetNode, "Node \"Target\" list exceeds the maximum of %d, skipping.\n", MAX_ACHIEVEMENT_OBJECTIVES ); + return 0; + } + + uint16 targetId; + + if( !this->asUInt16( targetNode, "Id", targetId ) ){ + continue; + } + + if( targetId >= MAX_ACHIEVEMENT_OBJECTIVES ){ + this->invalidWarning( targetNode["Id"], "Node \"Id\" is out of valid range [0,%d], skipping.\n", MAX_ACHIEVEMENT_OBJECTIVES ); + return 0; + } + + std::shared_ptr target = rathena::util::umap_find( achievement->targets, targetId ); + bool targetExists = target != nullptr; + + if( !targetExists ){ + if( !this->nodeExists( targetNode, "Count" ) && !this->nodeExists( targetNode, "MobID" ) ){ + this->invalidWarning( targetNode, "Node \"Target\" has no data specified, skipping.\n" ); + return 0; + } + + target = std::make_shared(); + } + + if( this->nodeExists( targetNode, "Count" ) ){ + uint32 count; + + if( !this->asUInt32( targetNode, "Count", count ) ){ + return 0; + } + + target->count = count; + }else{ + if( !targetExists ){ + target->count = 0; + } + } + + if( this->nodeExists( targetNode, "MobID" ) ){ + if( achievement->group != AG_BATTLE && achievement->group != AG_TAMING ){ + this->invalidWarning( targets, "Node \"MobID\" is only supported for targets in group AG_BATTLE or AG_TAMING, skipping.\n" ); + return 0; + } + + uint32 mob_id; + + // TODO: not camel case + if( !this->asUInt32( targetNode, "MobID", mob_id ) ){ + return 0; + } + + if( mob_db( mob_id ) == nullptr ){ + this->invalidWarning( targetNode["MobID"], "Unknown monster ID %d, skipping.\n", mob_id ); + return 0; + } + + if( !this->mobexists( mob_id ) ){ + this->achievement_mobs.push_back( mob_id ); + } + }else{ + if( !targetExists ){ + target->mob = 0; + } + } + + achievement->targets[targetId] = target; + } + } + + if( this->nodeExists( node, "Condition" ) ){ + std::string condition; + + if( !this->asString( node, "Condition", condition ) ){ + return 0; + } + + if( condition.find( "achievement_condition" ) == std::string::npos ){ + condition = "achievement_condition( " + condition + " );"; + } + + achievement->condition = parse_script( condition.c_str(), this->getCurrentFile().c_str(), node["Condition"].Mark().line + 1, SCRIPT_IGNORE_EXTERNAL_BRACKETS ); + } + + if( this->nodeExists( node, "Map" ) ){ + if( achievement->group != AG_CHAT ){ + this->invalidWarning( node, "Node \"Map\" can only be used with the group AG_CHATTING, skipping.\n" ); + return 0; + } + + std::string mapname; + + if( !this->asString( node, "Map", mapname ) ){ + return 0; + } + + achievement->mapindex = map_mapname2mapid( mapname.c_str() ); + + if( achievement->mapindex == -1 ){ + this->invalidWarning( node["Map"], "Unknown map name '%s'.\n", mapname.c_str() ); + return 0; + } + }else{ + if( !exists ){ + achievement->mapindex = -1; + } + } + + if( this->nodeExists( node, "Dependent" ) ){ + for( const YAML::Node& subNode : node["Dependent"] ){ + uint32 dependent_achievement_id; + + if( !this->asUInt32( subNode, "Id", dependent_achievement_id ) ){ + return 0; + } + + // TODO: import logic for clearing => continue + // TODO: change to set to prevent multiple entries with the same id? + achievement->dependent_ids.push_back( dependent_achievement_id ); + } + } + + // TODO: not plural + if( this->nodeExists( node, "Reward" ) ){ + const YAML::Node& rewardNode = node["Reward"]; + + // TODO: not camel case + if( this->nodeExists( rewardNode, "ItemID" ) ){ + uint16 itemId; + + if( !this->asUInt16( rewardNode, "ItemID", itemId ) ){ + return 0; + } + + if( !itemdb_exists( itemId ) ){ + this->invalidWarning( rewardNode["ItemID"], "Unknown item with ID %hu.\n", itemId ); + return 0; + } + + achievement->rewards.nameid = itemId; + + if( achievement->rewards.amount == 0 ){ + // Default the amount to 1 + achievement->rewards.amount = 1; + } + } + + if( this->nodeExists( rewardNode, "Amount" ) ){ + uint16 amount; + + if( !this->asUInt16( rewardNode, "Amount", amount ) ){ + return 0; + } + + achievement->rewards.nameid = amount; + } + + if( this->nodeExists( rewardNode, "Script" ) ){ + std::string script; + + if( !this->asString( rewardNode, "Script", script ) ){ + return 0; + } + + achievement->rewards.script = parse_script( script.c_str(), this->getCurrentFile().c_str(), achievement_id, SCRIPT_IGNORE_EXTERNAL_BRACKETS ); + } + + // TODO: not camel case + if( this->nodeExists( rewardNode, "TitleID" ) ){ + uint32 title; + + if( !this->asUInt32( rewardNode, "TitleID", title ) ){ + return 0; + } + + achievement->rewards.title_id = title; + } + } + + if( this->nodeExists( node, "Score" ) ){ + uint32 score; + + if( !this->asUInt32( node, "Score", score ) ){ + return 0; + } + + achievement->score = score; + } + + if( !exists ){ + this->put( achievement_id, achievement ); + } + + return 1; } +AchievementDatabase achievement_db; + /** * 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) -{ +bool AchievementDatabase::mobexists( uint32 mob_id ){ if (!battle_config.feature_achievement) return false; - auto it = std::find(achievement_mobs.begin(), achievement_mobs.end(), mob_id); + auto it = std::find(this->achievement_mobs.begin(), this->achievement_mobs.end(), mob_id); - return (it != achievement_mobs.end()) ? true : false; + return (it != this->achievement_mobs.end()) ? true : false; } /** @@ -78,12 +328,12 @@ struct achievement *achievement_add(struct map_session_data *sd, int achievement nullpo_retr(NULL, sd); - if (!achievement_exists(achievement_id)) { - ShowError("achievement_add: Achievement %d not found in DB.\n", achievement_id); - return NULL; - } + std::shared_ptr adb = achievement_db.find( achievement_id ); - auto &adb = achievements[achievement_id]; + if( adb == nullptr ){ + ShowError( "achievement_add: Achievement %d not found in DB.\n", achievement_id ); + return nullptr; + } ARR_FIND(0, sd->achievement_data.count, i, sd->achievement_data.achievements[i].achievement_id == achievement_id); if (i < sd->achievement_data.count) { @@ -125,7 +375,7 @@ bool achievement_remove(struct map_session_data *sd, int achievement_id) nullpo_retr(false, sd); - if (!achievement_exists(achievement_id)) { + if (!achievement_db.exists(achievement_id)) { ShowError("achievement_delete: Achievement %d not found in DB.\n", achievement_id); return false; } @@ -184,10 +434,11 @@ bool achievement_check_dependent(struct map_session_data *sd, int achievement_id { nullpo_retr(false, sd); - if (!achievement_exists(achievement_id)) - return false; + std::shared_ptr adb = achievement_db.find( achievement_id ); - auto &adb = achievements[achievement_id]; + if( adb == nullptr ){ + return false; + } // Check if the achievement has a dependent // If so, then do a check on all dependents to see if they're complete @@ -242,10 +493,11 @@ bool achievement_update_achievement(struct map_session_data *sd, int achievement nullpo_retr(false, sd); - if (!achievement_exists(achievement_id)) - return false; + std::shared_ptr adb = achievement_db.find( achievement_id ); - auto &adb = achievements[achievement_id]; + if( adb == nullptr ){ + 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) @@ -260,7 +512,7 @@ bool achievement_update_achievement(struct map_session_data *sd, int achievement int k; for (k = 0; k < adb->targets.size(); k++) - sd->achievement_data.achievements[i].count[k] = adb->targets[k].count; + sd->achievement_data.achievements[i].count[k] = adb->targets[k]->count; for (k = 1; k < adb->dependent_ids.size(); k++) { sd->achievement_data.achievements[i].count[k] = max(1, sd->achievement_data.achievements[i].count[k]); @@ -279,7 +531,7 @@ bool achievement_update_achievement(struct map_session_data *sd, int achievement achievement_level(sd, true); // Re-calculate achievement level // Check dependents - for (auto &ach : achievements) + for (auto &ach : achievement_db) achievement_check_groups(sd, ach.second.get()); 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 } @@ -301,13 +553,13 @@ void achievement_get_reward(struct map_session_data *sd, int achievement_id, tim nullpo_retv(sd); - if (!achievement_exists(achievement_id)) { - ShowError("achievement_reward: Inter server sent a reward claim for achievement %d not found in DB.\n", achievement_id); + std::shared_ptr adb = achievement_db.find( achievement_id ); + + if( adb == nullptr ){ + ShowError( "achievement_reward: Inter server sent a reward claim for achievement %d not found in DB.\n", achievement_id ); return; } - auto &adb = achievements[achievement_id]; - if (rewarded == 0) { clif_achievement_reward_ack(sd->fd, 0, achievement_id); return; @@ -342,14 +594,14 @@ void achievement_check_reward(struct map_session_data *sd, int achievement_id) nullpo_retv(sd); - if (!achievement_exists(achievement_id)) { - ShowError("achievement_reward: Trying to reward achievement %d not found in DB.\n", achievement_id); - clif_achievement_reward_ack(sd->fd, 0, achievement_id); + std::shared_ptr adb = achievement_db.find( achievement_id ); + + if( adb == nullptr ){ + 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; } - auto &adb = achievements[achievement_id]; - 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); @@ -379,13 +631,12 @@ void achievement_get_titles(uint32 char_id) if (sd->achievement_data.count) { for (int i = 0; i < sd->achievement_data.count; i++) { - if (!achievement_exists(sd->achievement_data.achievements[i].achievement_id)) - continue; + std::shared_ptr adb = achievement_db.find( sd->achievement_data.achievements[i].achievement_id ); - auto &adb = achievements[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 - sd->titles.push_back(adb->rewards.title_id); + // If the achievement has a title and is complete, give it to the player + if( adb != nullptr && adb->rewards.title_id && sd->achievement_data.achievements[i].completed > 0 ){ + sd->titles.push_back( adb->rewards.title_id ); + } } } } @@ -616,13 +867,13 @@ static int achievement_update_objectives(struct map_session_data *sd, std::share break; for (i = 0; i < ad->targets.size(); i++) { - if (objective_count[i] < ad->targets[i].count) + if (objective_count[i] < ad->targets[i]->count) objective_count[i] += update_count[0]; } changed = true; - ARR_FIND(0, ad->targets.size(), k, objective_count[k] < ad->targets[k].count); + ARR_FIND(0, ad->targets.size(), k, objective_count[k] < ad->targets[k]->count); if (k == ad->targets.size()) complete = true; @@ -634,20 +885,20 @@ static int achievement_update_objectives(struct map_session_data *sd, std::share case AG_BATTLE: case AG_TAMING: auto it = std::find_if(ad->targets.begin(), ad->targets.end(), [&update_count] - (const achievement_target &curTarget) { - return curTarget.mob == update_count[0]; + (std::pair> &curTarget) { + return curTarget.second->mob == update_count[0]; }); if (it == ad->targets.end()) break; // Mob wasn't found for (k = 0; k < ad->targets.size(); k++) { - if (ad->targets[k].mob == update_count[0] && objective_count[k] < ad->targets[k].count) { + if (ad->targets[k]->mob == update_count[0] && objective_count[k] < ad->targets[k]->count) { objective_count[k]++; changed = true; } } - ARR_FIND(0, ad->targets.size(), k, objective_count[k] < ad->targets[k].count); + ARR_FIND(0, ad->targets.size(), k, objective_count[k] < ad->targets[k]->count); if (k == ad->targets.size()) complete = true; @@ -701,7 +952,7 @@ void achievement_update_objective(struct map_session_data *sd, enum e_achievemen // These have no objective use right now. break; default: - for (auto &ach : achievements) + for (auto &ach : achievement_db) achievement_update_objectives(sd, ach.second, group, count); break; } @@ -715,237 +966,23 @@ void achievement_update_objective(struct map_session_data *sd, enum e_achievemen } } -static void yaml_invalid_warning(const char* fmt, const YAML::Node &node, const std::string &file) { - YAML::Emitter out; - out << node; - ShowWarning(fmt, file.c_str()); - ShowMessage("%s\n", out.c_str()); -} - -/** - * Reads and parses an entry from the achievement_db. - * @param node: YAML node containing the entry. - * @param n: The sequential index of the current entry. - * @param source: The source YAML file. - * @return True on successful parse or false otherwise - */ -bool achievement_read_db_sub(const YAML::Node &node, int n, const std::string &source) -{ - enum e_achievement_group group = AG_NONE; - int achievement_id = 0; - std::string group_char, name, condition, mapname; - bool existing = false; - - if (!node["ID"]) { - yaml_invalid_warning("achievement_read_db_sub: Missing ID field in '" CL_WHITE "%s" CL_RESET "', skipping.\n", node, source); - return false; - } - try { - achievement_id = node["ID"].as(); - } catch (...) { - yaml_invalid_warning("achievement_read_db_sub: Achievement definition with invalid ID field in '" CL_WHITE "%s" CL_RESET "', skipping.\n", node, source); - return false; - } - 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.c_str(), n, INT_MAX); - return false; - } - - if (achievement_exists(achievement_id)) { - if (source.find("import") != std::string::npos) // Import file read-in, free previous value and store new value - existing = true; - else { // Normal file read-in - ShowWarning("achievement_read_db: Duplicate achievement %d.\n", achievement_id); - return false; - } - } - - if(!existing) - achievements[achievement_id] = std::make_shared(); - auto &entry = achievements[achievement_id]; - entry->achievement_id = achievement_id; - - if (!node["Group"]) { - yaml_invalid_warning("achievement_read_db_sub: Missing group field in '" CL_WHITE "%s" CL_RESET "', skipping.\n", node, source); - return false; - } - try { - group_char = node["Group"].as(); - } catch (...) { - yaml_invalid_warning("achievement_read_db_sub: Achievement definition with invalid group field in '" CL_WHITE "%s" CL_RESET "', skipping.\n", node, source); - return false; - } - if (!script_get_constant(group_char.c_str(), (int *)&group)) { - ShowWarning("achievement_read_db_sub: Invalid group %s for achievement %d in \"%s\", skipping.\n", group_char.c_str(), achievement_id, source.c_str()); - return false; - } - - if (!node["Name"]) { - ShowWarning("achievement_read_db_sub: Missing achievement name for achievement %d in \"%s\", skipping.\n", achievement_id, source.c_str()); - return false; - } - try { - name = node["Name"].as(); - } catch (...) { - yaml_invalid_warning("achievement_read_db_sub: Achievement definition with invalid name field in '" CL_WHITE "%s" CL_RESET "', skipping.\n", node, source); - return false; - } - - entry->group = group; - entry->name = name; - entry->mapindex = -1; - - if (node["Target"]) { - try { - const YAML::Node &target_list = node["Target"]; - - for (auto targetit = target_list.begin(); targetit != target_list.end() && target_list.size() < MAX_ACHIEVEMENT_OBJECTIVES; ++targetit) { - const YAML::Node &target = *targetit; - int mobid = 0, count = 0; - - if (target["MobID"] && (mobid = target["MobID"].as()) && mob_db(mobid) == NULL) { // 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.c_str()); - continue; - } - if (target["Count"] && (!(count = target["Count"].as()) || count <= 0)) { - ShowError("achievement_read_db_sub: Invalid count %d for achievement %d in \"%s\", skipping.\n", count, achievement_id, source.c_str()); - continue; - } - if (mobid && group == AG_BATTLE && !achievement_mobexists(mobid)) - achievement_mobs.push_back(mobid); - - entry->targets.push_back({ mobid, count }); - } - } catch (...) { - yaml_invalid_warning("achievement_read_db_sub: Achievement definition with invalid target field in '" CL_WHITE "%s" CL_RESET "', skipping.\n", node, source); - return false; - } - } - - if (node["Condition"]) { - try { - condition = node["Condition"].as(); - } catch (...) { - yaml_invalid_warning("achievement_read_db_sub: Achievement definition with invalid condition field in '" CL_WHITE "%s" CL_RESET "', skipping.\n", node, source); - return false; - } - - if( condition.find( "achievement_condition" ) == std::string::npos ){ - condition = "achievement_condition( " + condition + " );"; - } - - entry->condition = parse_script( condition.c_str(), source.c_str(), node["Condition"].Mark().line, SCRIPT_IGNORE_EXTERNAL_BRACKETS ); - } - - if (node["Map"]) { - try { - mapname = node["Map"].as(); - } catch (...) { - yaml_invalid_warning("achievement_read_db_sub: Achievement definition with invalid map field in '" CL_WHITE "%s" CL_RESET "', skipping.\n", node, source); - return false; - } - 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.c_str()); - else { - entry->mapindex = map_mapname2mapid(mapname.c_str()); - - if (entry->mapindex == -1) - ShowWarning("achievement_read_db_sub: Invalid map name %s for achievement %d in \"%s\".\n", mapname.c_str(), achievement_id, source.c_str()); - } - } - - if (node["Dependent"]) { - try { - const YAML::Node dependent_list = node["Dependent"]; - - if (dependent_list.IsSequence()) { - for (uint8 i = 0; i < dependent_list.size() && dependent_list.size() < MAX_ACHIEVEMENT_DEPENDENTS; i++) - entry->dependent_ids.push_back(dependent_list[i].as()); - } else - ShowWarning("achievement_read_db_sub: Invalid dependent format for achievement %d in \"%s\".\n", achievement_id, source.c_str()); - } catch (...) { - yaml_invalid_warning("achievement_read_db_sub: Achievement definition with invalid dependent field in '" CL_WHITE "%s" CL_RESET "', skipping.\n", node, source); - return false; - } - } - - if (node["Reward"]) { - try { - const YAML::Node reward_list = node["Reward"]; - int nameid = 0, amount = 0, titleid = 0; - - if (reward_list["ItemID"] && (nameid = reward_list["ItemID"].as())) { - 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.c_str()); - entry->rewards.nameid = nameid = 0; - } - - if (reward_list["Amount"] && (amount = reward_list["Amount"].as()) && amount > 0 && nameid > 0) - entry->rewards.amount = amount; - } - if (reward_list["Script"]) - entry->rewards.script = parse_script(reward_list["Script"].as().c_str(), source.c_str(), achievement_id, SCRIPT_IGNORE_EXTERNAL_BRACKETS); - if (reward_list["TitleID"] && (titleid = reward_list["TitleID"].as()) && titleid > 0) - entry->rewards.title_id = titleid; - } catch (...) { - yaml_invalid_warning("achievement_read_db_sub: Achievement definition with invalid target field in '" CL_WHITE "%s" CL_RESET "', skipping.\n", node, source); - return false; - } - } - - if (node["Score"]) { - try { - entry->score = node["Score"].as(); - } catch (...) { - yaml_invalid_warning("achievement_read_db_sub: Achievement definition with invalid score field in '" CL_WHITE "%s" CL_RESET "', skipping.\n", node, source); - return false; - } - } - - return true; -} - /** * Loads achievements from the achievement db. */ void achievement_read_db(void) -{ - std::vector directories = { std::string(db_path) + "/" + std::string(DBPATH), std::string(db_path) + "/" + std::string(DBIMPORT) + "/" }; - static const std::string file_name("achievement_db.yml"); +{ + achievement_db.load(); - for (auto &directory : directories) { - std::string current_file = directory + file_name; - YAML::Node config; - int count = 0; + for (auto &achit : achievement_db) { + const auto ach = achit.second; - try { - config = YAML::LoadFile(current_file); - } catch (...) { - ShowError("Cannot read '" CL_WHITE "%s" CL_RESET "'.\n", current_file.c_str()); - return; - } - - for (const auto &node : config["Achievements"]) { - if (node.IsDefined() && achievement_read_db_sub(node, count, current_file)) - count++; - } - for (auto &achit : achievements) { - const auto ach = achit.second; - - for (int i = 0; i < ach->dependent_ids.size(); i++) { - if (!achievement_exists(ach->dependent_ids[i])) { - ShowWarning("achievement_read_db: An invalid Dependent ID %d was given for Achievement %d. Removing from list.\n", ach->dependent_ids[i], ach->achievement_id); - ach->dependent_ids.erase(ach->dependent_ids.begin() + i); - } + for (int i = 0; i < ach->dependent_ids.size(); i++) { + if (!achievement_db.exists(ach->dependent_ids[i])) { + ShowWarning("achievement_read_db: An invalid Dependent ID %d was given for Achievement %d. Removing from list.\n", ach->dependent_ids[i], ach->achievement_id); + ach->dependent_ids.erase(ach->dependent_ids.begin() + i); } } - ShowStatus("Done reading '" CL_WHITE "%d" CL_RESET "' entries in '" CL_WHITE "%s" CL_RESET "'\n", count, current_file.c_str()); } - - return; } /** @@ -972,10 +1009,8 @@ void do_init_achievement(void) /** * Finalizes the achievement database */ -void do_final_achievement(void) -{ - achievement_mobs.clear(); - achievements.clear(); +void do_final_achievement(void){ + achievement_db.clear(); } /** diff --git a/src/map/achievement.hpp b/src/map/achievement.hpp index bfe724d363..9555722212 100644 --- a/src/map/achievement.hpp +++ b/src/map/achievement.hpp @@ -11,6 +11,7 @@ #include #include "../common/mmo.hpp" +#include "../common/database.hpp" #include "../common/db.hpp" struct map_session_data; @@ -69,17 +70,17 @@ struct achievement_target { }; struct s_achievement_db { - int achievement_id; + uint32 achievement_id; std::string name; enum e_achievement_group group; - std::vector targets; - std::vector dependent_ids; + std::unordered_map> targets; + std::vector dependent_ids; struct script_code* condition; int16 mapindex; struct ach_reward { unsigned short nameid, amount; struct script_code *script; - int title_id; + uint32 title_id; ach_reward(); ~ach_reward(); } rewards; @@ -90,9 +91,26 @@ struct s_achievement_db { ~s_achievement_db(); }; -bool achievement_exists(int achievement_id); -std::shared_ptr& achievement_get(int achievement_id); -bool achievement_mobexists(int mob_id); +class AchievementDatabase : public TypesafeYamlDatabase{ +private: + // Avoids checking achievements on every mob killed + std::vector achievement_mobs; + +public: + AchievementDatabase() : TypesafeYamlDatabase( "ACHIEVEMENT_DB", 1 ){ + + } + + void clear(); + const std::string getDefaultLocation(); + uint64 parseBodyNode( const YAML::Node& node ); + + // Additional + bool mobexists(uint32 mob_id); +}; + +extern AchievementDatabase achievement_db; + 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); diff --git a/src/map/intif.cpp b/src/map/intif.cpp index f5a0cc0c9e..49b40eb0bf 100644 --- a/src/map/intif.cpp +++ b/src/map/intif.cpp @@ -2150,14 +2150,14 @@ void intif_parse_achievements(int fd) CREATE(sd->achievement_data.achievements, struct achievement, num_received); for (i = 0; i < num_received; i++) { + std::shared_ptr adb = achievement_db.find( received[i].achievement_id ); - if (!achievement_exists(received[i].achievement_id)) { + + if( adb == nullptr ){ ShowError("intif_parse_achievementlog: Achievement %d not found in DB.\n", received[i].achievement_id); continue; } - auto &adb = achievement_get(received[i].achievement_id); - received[i].score = adb->score; if (received[i].completed == 0) // Insert at the beginning diff --git a/src/map/mob.cpp b/src/map/mob.cpp index f26d26cc70..fba469d489 100644 --- a/src/map/mob.cpp +++ b/src/map/mob.cpp @@ -2976,7 +2976,7 @@ 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)) + if (achievement_db.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) diff --git a/src/map/pc.cpp b/src/map/pc.cpp index 49cb2f3130..cc8f7359ad 100755 --- a/src/map/pc.cpp +++ b/src/map/pc.cpp @@ -12,6 +12,7 @@ #include "../common/cbasetypes.hpp" #include "../common/core.hpp" // get_svn_revision() +#include "../common/database.hpp" #include "../common/ers.hpp" // ers_destroy #include "../common/malloc.hpp" #include "../common/mmo.hpp" //NAME_LENGTH @@ -97,10 +98,197 @@ struct s_attendance_reward{ struct s_attendance_period{ uint32 start; uint32 end; - std::map rewards; + std::map> rewards; }; -std::vector attendance_periods; +class AttendanceDatabase : public TypesafeYamlDatabase{ +public: + AttendanceDatabase() : TypesafeYamlDatabase( "ATTENDANCE_DB", 1 ){ + + } + + const std::string getDefaultLocation(); + uint64 parseBodyNode( const YAML::Node& node ); +}; + +const std::string AttendanceDatabase::getDefaultLocation(){ + return std::string(db_path) + "/attendance.yml"; +} + +/** + * Reads and parses an entry from the attendance_db. + * @param node: YAML node containing the entry. + * @return count of successfully parsed rows + */ +uint64 AttendanceDatabase::parseBodyNode(const YAML::Node &node){ + uint32 start; + + if( !this->asUInt32( node, "Start", start ) ){ + return 0; + } + + std::shared_ptr attendance_period = this->find( start ); + bool exists = attendance_period != nullptr; + + if( !exists ){ + if( !this->nodeExists( node, "End" ) ){ + this->invalidWarning( node, "Node \"End\" is missing.\n" ); + return 0; + } + + if( !this->nodeExists( node, "Rewards" ) ){ + this->invalidWarning( node, "Node \"Rewards\" is missing.\n" ); + return 0; + } + + attendance_period = std::make_shared(); + + attendance_period->start = start; + } + + // If it does not exist yet, we need to check it for sure + bool requiresCollisionDetection = !exists; + + if( this->nodeExists( node, "End" ) ){ + uint32 end; + + if( !this->asUInt32( node, "End", end ) ){ + return 0; + } + + // If the period is outdated already, we do not even bother parsing + if( end < date_get( DT_YYYYMMDD ) ){ + this->invalidWarning( node, "Node \"End\" date %u has already passed, skipping.\n", end ); + return 0; + } + + if( !exists || attendance_period->end != end ){ + requiresCollisionDetection = true; + attendance_period->end = end; + } + } + + // Collision detection + if( requiresCollisionDetection ){ + bool collision = false; + + for( std::pair>& pair : *this ){ + std::shared_ptr period = pair.second; + + if( exists && period->start == attendance_period->start ){ + // Dont compare to yourself + continue; + } + + // Check if start is inside another period + if( period->start <= attendance_period->start && start <= period->end ){ + this->invalidWarning( node, "Node \"Start\" period %u intersects with period %u-%u, skipping.\n", attendance_period->start, period->start, period->end ); + collision = true; + break; + } + + // Check if end is inside another period + if( period->start <= attendance_period->end && attendance_period->end <= period->end ){ + this->invalidWarning( node, "Node \"End\" period %u intersects with period %u-%u.\n", attendance_period->start, period->start, period->end ); + collision = true; + break; + } + } + + if( collision ){ + return 0; + } + } + + if( this->nodeExists( node, "Rewards" ) ){ + const YAML::Node& rewardsNode = node["Rewards"]; + + for( const YAML::Node& rewardNode : rewardsNode ){ + uint32 day; + + if( !this->asUInt32( rewardNode, "Day", day ) ){ + continue; + } + + day -= 1; + + std::shared_ptr reward = util::map_find( attendance_period->rewards, day ); + bool reward_exists = reward != nullptr; + + if( !reward_exists ){ + if( !this->nodeExists( rewardNode, "ItemId" ) ){ + this->invalidWarning( rewardNode, "Node \"ItemId\" is missing.\n" ); + return 0; + } + + reward = std::make_shared(); + } + + if( this->nodeExists( rewardNode, "ItemId" ) ){ + uint16 item_id; + + if( !this->asUInt16( rewardNode, "ItemId", item_id ) ){ + continue; + } + + if( item_id == 0 || !itemdb_exists( item_id ) ){ + ShowError( "pc_attendance_load: Unknown item ID %hu for day %d.\n", item_id, day + 1 ); + continue; + } + + reward->item_id = item_id; + } + + if( this->nodeExists( rewardNode, "Amount" ) ){ + uint16 amount; + + if( !this->asUInt16( rewardNode, "Amount", amount ) ){ + continue; + } + + if( amount == 0 ){ + ShowWarning( "pc_attendance_load: Invalid reward count %hu for day %d. Defaulting to 1...\n", amount, day + 1 ); + amount = 1; + }else if( amount > MAX_AMOUNT ){ + ShowError( "pc_attendance_load: Reward count %hu above maximum %hu for day %d. Defaulting to %hu...\n", amount, MAX_AMOUNT, day + 1, MAX_AMOUNT ); + amount = MAX_AMOUNT; + } + + reward->amount = amount; + }else{ + if( !reward_exists ){ + reward->amount = 1; + } + } + + if( !reward_exists ){ + attendance_period->rewards[day] = reward; + } + } + + bool missing_day = false; + + for( int day = 0; day < attendance_period->rewards.size(); day++ ){ + if( attendance_period->rewards.find( day ) == attendance_period->rewards.end() ){ + ShowError( "pc_attendance_load: Reward for day %d is missing.\n", day + 1 ); + missing_day = true; + break; + } + } + + if( missing_day ){ + return 0; + } + } + + if( !exists ){ + this->put( start, attendance_period ); + } + + return 1; +} + +AttendanceDatabase attendance_db; #define MOTD_LINE_SIZE 128 static char motd_text[MOTD_LINE_SIZE][CHAT_SIZE_MAX]; // Message of the day buffer [Valaris] @@ -12766,12 +12954,14 @@ void pc_set_costume_view(struct map_session_data *sd) { clif_changelook(&sd->bl, LOOK_ROBE, sd->status.robe); } -struct s_attendance_period* pc_attendance_period(){ +std::shared_ptr pc_attendance_period(){ uint32 date = date_get(DT_YYYYMMDD); - for( struct s_attendance_period& period : attendance_periods ){ - if( period.start <= date && period.end >= date ){ - return . + for( std::pair>& pair : attendance_db ){ + std::shared_ptr period = pair.second; + + if( period->start <= date && period->end >= date ){ + return period; } } @@ -12793,7 +12983,7 @@ static inline bool pc_attendance_rewarded_today( struct map_session_data* sd ){ } int32 pc_attendance_counter( struct map_session_data* sd ){ - struct s_attendance_period* period = pc_attendance_period(); + std::shared_ptr period = pc_attendance_period(); // No running attendance period if( period == nullptr ){ @@ -12834,7 +13024,7 @@ void pc_attendance_claim_reward( struct map_session_data* sd ){ attendance_counter += 1; - struct s_attendance_period* period = pc_attendance_period(); + std::shared_ptr period = pc_attendance_period(); if( period == nullptr ){ return; @@ -12850,7 +13040,7 @@ void pc_attendance_claim_reward( struct map_session_data* sd ){ if( save_settings&CHARSAVE_ATTENDANCE ) chrif_save(sd, CSAVE_NORMAL); - struct s_attendance_reward& reward = period->rewards.at( attendance_counter - 1 ); + std::shared_ptr reward = period->rewards[attendance_counter - 1]; struct mail_message msg; @@ -12861,8 +13051,8 @@ void pc_attendance_claim_reward( struct map_session_data* sd ){ safesnprintf( msg.title, MAIL_TITLE_LENGTH, msg_txt( sd, 789 ), attendance_counter ); safesnprintf( msg.body, MAIL_BODY_LENGTH, msg_txt( sd, 790 ), attendance_counter ); - msg.item[0].nameid = reward.item_id; - msg.item[0].amount = reward.amount; + msg.item[0].nameid = reward->item_id; + msg.item[0].amount = reward->amount; msg.item[0].identify = 1; msg.status = MAIL_NEW; @@ -12874,152 +13064,6 @@ void pc_attendance_claim_reward( struct map_session_data* sd ){ clif_attendence_response( sd, attendance_counter ); } -void pc_attendance_load( std::string path ){ - YAML::Node root; - - try{ - root = YAML::LoadFile( path ); - }catch( ... ){ - ShowError( "pc_attendance_load: Failed to read attendance configuration file \"%s\".\n", path.c_str() ); - return; - } - - if( root["Attendance"] ){ - YAML::Node attendance = root["Attendance"]; - - for( const auto &periodNode : attendance ){ - if( !periodNode["Start"].IsDefined() ){ - ShowError( "pc_attendance_load: Missing \"Start\" for period in line %d.\n", periodNode.Mark().line ); - continue; - } - - YAML::Node startNode = periodNode["Start"]; - - if( !periodNode["End"].IsDefined() ){ - ShowError( "pc_attendance_load: Missing \"End\" for period in line %d.\n", periodNode.Mark().line ); - continue; - } - - YAML::Node endNode = periodNode["End"]; - - if( !periodNode["Rewards"].IsDefined() ){ - ShowError( "pc_attendance_load: Missing \"Rewards\" for period in line %d.\n", periodNode.Mark().line ); - continue; - } - - YAML::Node rewardsNode = periodNode["Rewards"]; - - uint32 start = startNode.as(); - uint32 end = endNode.as(); - - // If the period is outdated already, we do not even bother parsing - if( end < date_get( DT_YYYYMMDD ) ){ - continue; - } - - // Collision detection - bool collision = false; - - for( struct s_attendance_period& period : attendance_periods ){ - // Check if start is inside another period - if( period.start <= start && start <= period.end ){ - ShowError( "pc_attendance_load: period start %u intersects with period %u-%u.\n", start, period.start, period.end ); - collision = true; - break; - } - - // Check if end is inside another period - if( period.start <= end && end <= period.end ){ - ShowError( "pc_attendance_load: period end %u intersects with period %u-%u.\n", start, period.start, period.end ); - collision = true; - break; - } - } - - if( collision ){ - continue; - } - - struct s_attendance_period period; - - period.start = start; - period.end = end; - - for( const auto& rewardNode : rewardsNode ){ - if( !rewardNode["Day"].IsDefined() ){ - ShowError( "pc_attendance_load: No day defined for node in line %d.\n", rewardNode.Mark().line ); - continue; - } - - uint32 day = rewardNode["Day"].as(); - - if( !rewardNode["ItemId"].IsDefined() ){ - ShowError( "pc_attendance_load: No reward defined for day %d.\n", day ); - continue; - } - - YAML::Node itemNode = rewardNode["ItemId"]; - - uint16 item_id = itemNode.as(); - - if( item_id == 0 || !itemdb_exists( item_id ) ){ - ShowError( "pc_attendance_load: Unknown item ID %hu for day %d.\n", item_id, day ); - continue; - } - - uint16 amount; - - if( rewardNode["Amount"] ){ - amount = rewardNode["Amount"].as(); - - if( amount == 0 ){ - ShowError( "pc_attendance_load: Invalid reward count %hu for day %d. Defaulting to 1...\n", amount, day ); - amount = 1; - }else if( amount > MAX_AMOUNT ){ - ShowError( "pc_attendance_load: Reward count %hu above maximum %hu for day %d. Defaulting to %hu...\n", amount, MAX_AMOUNT, day, MAX_AMOUNT ); - amount = MAX_AMOUNT; - } - }else{ - amount = 1; - } - - struct s_attendance_reward* reward = &period.rewards[day - 1]; - - reward->item_id = item_id; - reward->amount = amount; - } - - bool missing_day = false; - - for( int day = 0; day < period.rewards.size(); day++ ){ - if( !util::map_exists( period.rewards, day ) ){ - ShowError( "pc_attendance_load: Reward for day %d is missing.\n", day + 1 ); - missing_day = true; - break; - } - } - - if( missing_day ){ - continue; - } - - attendance_periods.push_back( period ); - } - } -} - -void pc_read_attendance(){ - char path[1024]; - - sprintf( path, "%s/%sattendance.yml", db_path, DBPATH ); - - pc_attendance_load( path ); - - sprintf( path, "%s/%s/attendance.yml", db_path, DBIMPORT ); - - pc_attendance_load( path ); -} - /*========================================== * pc Init/Terminate *------------------------------------------*/ @@ -13031,7 +13075,7 @@ void do_final_pc(void) { ers_destroy(num_reg_ers); ers_destroy(str_reg_ers); - attendance_periods.clear(); + attendance_db.clear(); } void do_init_pc(void) { @@ -13040,7 +13084,7 @@ void do_init_pc(void) { pc_readdb(); pc_read_motd(); // Read MOTD [Valaris] - pc_read_attendance(); + attendance_db.load(); add_timer_func_list(pc_invincible_timer, "pc_invincible_timer"); add_timer_func_list(pc_eventtimer, "pc_eventtimer"); diff --git a/src/map/script.cpp b/src/map/script.cpp index f319fc58a5..062cd43ab8 100644 --- a/src/map/script.cpp +++ b/src/map/script.cpp @@ -23358,7 +23358,7 @@ BUILDIN_FUNC(achievementadd) { return SCRIPT_CMD_FAILURE; } - if (achievement_exists(achievement_id) == false) { + if (achievement_db.exists(achievement_id) == false) { ShowWarning("buildin_achievementadd: Achievement '%d' doesn't exist.\n", achievement_id); script_pushint(st, false); return SCRIPT_CMD_FAILURE; @@ -23395,7 +23395,7 @@ BUILDIN_FUNC(achievementremove) { return SCRIPT_CMD_FAILURE; } - if (achievement_exists(achievement_id) == false) { + if (achievement_db.exists(achievement_id) == false) { ShowWarning("buildin_achievementremove: Achievement '%d' doesn't exist.\n", achievement_id); script_pushint(st, false); return SCRIPT_CMD_SUCCESS; @@ -23431,7 +23431,7 @@ BUILDIN_FUNC(achievementinfo) { return SCRIPT_CMD_FAILURE; } - if (achievement_exists(achievement_id) == false) { + if (achievement_db.exists(achievement_id) == false) { ShowWarning("buildin_achievementinfo: Achievement '%d' doesn't exist.\n", achievement_id); script_pushint(st, false); return SCRIPT_CMD_FAILURE; @@ -23465,7 +23465,7 @@ BUILDIN_FUNC(achievementcomplete) { return SCRIPT_CMD_FAILURE; } - if (achievement_exists(achievement_id) == false) { + if (achievement_db.exists(achievement_id) == false) { ShowWarning("buildin_achievementcomplete: Achievement '%d' doesn't exist.\n", achievement_id); script_pushint(st, false); return SCRIPT_CMD_FAILURE; @@ -23502,7 +23502,7 @@ BUILDIN_FUNC(achievementexists) { return SCRIPT_CMD_FAILURE; } - if (achievement_exists(achievement_id) == false) { + if (achievement_db.exists(achievement_id) == false) { ShowWarning("buildin_achievementexists: Achievement '%d' doesn't exist.\n", achievement_id); script_pushint(st, false); return SCRIPT_CMD_SUCCESS; @@ -23541,7 +23541,7 @@ BUILDIN_FUNC(achievementupdate) { return SCRIPT_CMD_FAILURE; } - if (achievement_exists(achievement_id) == false) { + if (achievement_db.exists(achievement_id) == false) { ShowWarning("buildin_achievementupdate: Achievement '%d' doesn't exist.\n", achievement_id); script_pushint(st, false); return SCRIPT_CMD_FAILURE;