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

Merge branch 'master' into hotfix/issue4984

Aleos 2 éve
szülő
commit
1e01f6448f
88 módosított fájl, 5901 hozzáadás és 1287 törlés
  1. 3 0
      conf/char_athena.conf
  2. 0 2
      conf/inter_athena.conf
  3. 12 0
      conf/maps_athena.conf
  4. 4 1
      conf/msg_conf/map_msg.conf
  5. 8 164
      db/castle_db.yml
  6. 8 1
      db/import-tmpl/castle_db.yml
  7. 33 0
      db/import-tmpl/item_cash.yml
  8. 0 19
      db/import-tmpl/item_cash_db.txt
  9. 37 0
      db/item_cash.yml
  10. BIN
      db/map_cache.dat
  11. 10 0
      db/map_index.txt
  12. 235 1
      db/pre-re/castle_db.yml
  13. 0 19
      db/pre-re/item_cash_db.txt
  14. 334 1
      db/re/castle_db.yml
  15. 0 19
      db/re/item_cash_db.txt
  16. 51 1
      db/re/item_combos.yml
  17. 910 3
      db/re/item_db_equip.yml
  18. 267 0
      db/re/item_db_etc.yml
  19. 12 0
      db/re/item_db_usable.yml
  20. 546 0
      db/re/item_enchant.yml
  21. 88 0
      db/re/item_reform.yml
  22. 953 14
      db/re/mob_db.yml
  23. 63 0
      db/re/mob_skill_db.txt
  24. 26 2
      db/re/quest_db.yml
  25. 5 2
      doc/atcommands.txt
  26. 15 4
      doc/script_commands.txt
  27. 12 0
      doc/yaml/db/item_cash.yml
  28. 7 0
      npc/re/mobs/dungeons/sp_rudus.txt
  29. 480 0
      npc/re/quests/quests_dungeons_200.txt
  30. 1 0
      npc/re/scripts_athena.conf
  31. 2 0
      npc/re/warps/dungeons/sp_rudus.txt
  32. 0 2
      sql-files/README.md
  33. 0 7
      sql-files/item_cash_db.sql
  34. 0 7
      sql-files/item_cash_db2.sql
  35. 2 1
      src/char/char-server.vcxproj
  36. 115 110
      src/char/char.cpp
  37. 12 7
      src/char/char.hpp
  38. 23 18
      src/char/char_clif.cpp
  39. 28 32
      src/char/char_logif.cpp
  40. 81 64
      src/char/char_mapif.cpp
  41. 50 47
      src/char/int_auction.cpp
  42. 38 52
      src/char/int_clan.cpp
  43. 150 168
      src/char/int_guild.cpp
  44. 9 4
      src/char/int_mail.cpp
  45. 99 100
      src/char/int_party.cpp
  46. 40 53
      src/char/inter.cpp
  47. 1 0
      src/common/common-minicore.vcxproj
  48. 2 1
      src/common/common.vcxproj
  49. 8 8
      src/common/core.cpp
  50. 8 8
      src/common/core.hpp
  51. 22 2
      src/common/mmo.hpp
  52. 1 0
      src/login/login-server.vcxproj
  53. 4 0
      src/map/atcommand.cpp
  54. 34 2
      src/map/battleground.cpp
  55. 1 1
      src/map/battleground.hpp
  56. 103 142
      src/map/cashshop.cpp
  57. 29 16
      src/map/cashshop.hpp
  58. 208 51
      src/map/clif.cpp
  59. 9 0
      src/map/clif.hpp
  60. 9 1
      src/map/clif_packetdb.hpp
  61. 192 1
      src/map/guild.cpp
  62. 3 1
      src/map/guild.hpp
  63. 28 4
      src/map/instance.cpp
  64. 5 1
      src/map/intif.cpp
  65. 2 1
      src/map/map-server-generator.vcxproj
  66. 3 2
      src/map/map-server.vcxproj
  67. 0 6
      src/map/map.cpp
  68. 38 18
      src/map/npc.cpp
  69. 28 2
      src/map/npc.hpp
  70. 25 0
      src/map/packets.hpp
  71. 13 13
      src/map/pc.cpp
  72. 1 0
      src/map/pc.hpp
  73. 2 1
      src/map/script.cpp
  74. 1 0
      src/map/script.hpp
  75. 142 0
      src/map/script_constants.hpp
  76. 8 2
      src/map/skill.cpp
  77. 142 74
      src/map/status.hpp
  78. 52 0
      src/tool/csv2yaml.cpp
  79. 1 0
      src/tool/csv2yaml.hpp
  80. 1 0
      src/tool/csv2yaml.vcxproj
  81. 1 0
      src/tool/mapcache.vcxproj
  82. 1 0
      src/tool/yaml.hpp
  83. 1 0
      src/tool/yaml2sql.cpp
  84. 1 0
      src/tool/yaml2sql.vcxproj
  85. 1 0
      src/tool/yamlupgrade.vcxproj
  86. 1 0
      src/web/web-server.vcxproj
  87. 0 2
      tools/ci/sql.bat
  88. 0 2
      tools/ci/sql.sh

+ 3 - 0
conf/char_athena.conf

@@ -198,6 +198,9 @@ char_del_restriction: 3
 // Uncomment to customize the restriction
 //allowed_job_flag: 3
 
+// Should parties that don't have any members be cleared from the party_db table at start up?
+clear_parties: no
+
 // Folder that contains the database files.
 db_path: db
 

+ 0 - 2
conf/inter_athena.conf

@@ -147,8 +147,6 @@ item_table: item_db
 renewal-item_table: item_db_re
 item2_table: item_db2
 renewal-item2_table: item_db2_re
-item_cash_table: item_cash_db
-item_cash2_table: item_cash_db2
 mob_table: mob_db
 renewal-mob_table: mob_db_re
 mob2_table: mob_db2

+ 12 - 0
conf/maps_athena.conf

@@ -1517,6 +1517,9 @@ map: bl_grass
 map: bl_lava
 map: bl_ice
 map: bl_death
+map: bl_soul
+map: bl_temple
+map: bl_venom
 
 // Clock Tower: Unknown Basement
 map: clock_01
@@ -1535,6 +1538,11 @@ map: 1@vrcas
 map: 1@vrev
 map: 1@vrgen
 map: 1@vrpo
+map: 1@vrac1
+map: 1@vrac2
+map: 1@vrclo
+map: 1@vrhha
+map: 2@vrclo
 
 // ??
 map: 1@pdb
@@ -1558,5 +1566,9 @@ map: hero_out4
 map: hero_tra
 map: herosria
 
+// Level 260 Expansion
+map: mjo_wst01
+map: ra_pol01
+
 //------------------------- Clone Maps ---------------------------
 //------------------------- Extra Maps ---------------------------

+ 4 - 1
conf/msg_conf/map_msg.conf

@@ -927,7 +927,10 @@
 // Enchant UI
 829: Enchanting is not possible for your item's enchant grade.
 
-//830-899 free
+// @reloadbarterdb
+830: Barter database has been reloaded.
+
+//831-899 free
 
 //------------------------------------
 // More atcommands message

+ 8 - 164
db/castle_db.yml

@@ -26,174 +26,18 @@
 #   Map               Map name to be considered as the castle map.
 #   Name              Name of the castle (used by scripts and guardian name tags).
 #   Npc               NPC unique name to invoke ::OnGuildBreak on, when a occupied castle is abandoned during guild break.
+#   Type              The WoE type this castle belongs to. (Default: First_Edition)
+#   ClientId          Client side ID of the castle. (Default: 0)
+#   WarpEnabled       If the warp to the castle is enabled. (Default: false)
+#   WarpX             X coordinate to warp to. (Default: 0)
+#   WarpY             Y coordinate to warp to. (Default: 0)
+#   WarpCost          Zeny cost to use the warp. (Default: 100)
+#   WarpCostSiege     Zeny cost to use the warp during WoE. (Default: 100000)
 ###########################################################################
 
 Header:
   Type: CASTLE_DB
-  Version: 1
-
-Body:
-# WOE FE castle
-  - Id: 0
-    Map: aldeg_cas01
-    Name: Neuschwanstein
-    #Name: Noisyubantian
-    Npc: Agit#aldeg_cas01
-  - Id: 1
-    Map: aldeg_cas02
-    Name: Hohenschwangau
-    #Name: Hohensyubangawoo
-    Npc: Agit#aldeg_cas02
-  - Id: 2
-    Map: aldeg_cas03
-    Name: Nuernberg
-    #Name: Nyirenverk
-    Npc: Agit#aldeg_cas03
-  - Id: 3
-    Map: aldeg_cas04
-    Name: Wuerzburg
-    #Name: Byirtsburi
-    Npc: Agit#aldeg_cas04
-  - Id: 4
-    Map: aldeg_cas05
-    Name: Rothenburg
-    #Name: Rotenburk
-    Npc: Agit#aldeg_cas05
-  - Id: 5
-    Map: gefg_cas01
-    Name: Repherion
-    #Name: Reprion
-    Npc: Agit#gefg_cas01
-  - Id: 6
-    Map: gefg_cas02
-    Name: Eeyolbriggar
-    #Name: Yolbriger
-    Npc: Agit#gefg_cas02
-  - Id: 7
-    Map: gefg_cas03
-    Name: Yesnelph
-    #Name: Isinlife
-    Npc: Agit#gefg_cas03
-  - Id: 8
-    Map: gefg_cas04
-    Name: Bergel
-    #Name: Berigel
-    Npc: Agit#gefg_cas04
-  - Id: 9
-    Map: gefg_cas05
-    Name: Mersetzdeitz
-    #Name: Melsedetsu
-    Npc: Agit#gefg_cas05
-  - Id: 10
-    Map: payg_cas01
-    Name: Bright Arbor
-    #Name: Mingting
-    Npc: Agit#payg_cas01
-  - Id: 11
-    Map: payg_cas02
-    Name: Scarlet Palace
-    #Name: Tiantan
-    Npc: Agit#payg_cas02
-  - Id: 12
-    Map: payg_cas03
-    Name: Holy Shadow
-    #Name: Fuying
-    Npc: Agit#payg_cas03
-  - Id: 13
-    Map: payg_cas04
-    Name: Sacred Altar
-    #Name: Honglou
-    Npc: Agit#payg_cas04
-  - Id: 14
-    Map: payg_cas05
-    Name: Bamboo Grove Hill
-    #Name: Zhulinxian
-    Npc: Agit#payg_cas05
-  - Id: 15
-    Map: prtg_cas01
-    Name: Kriemhild
-    #Name: Creamhilt
-    Npc: Agit#prtg_cas01
-  - Id: 16
-    Map: prtg_cas02
-    Name: Swanhild
-    #Name: Sbanhealt
-    Npc: Agit#prtg_cas02
-  - Id: 17
-    Map: prtg_cas03
-    Name: Fadhgridh
-    #Name: Lazrigees
-    Npc: Agit#prtg_cas03
-  - Id: 18
-    Map: prtg_cas04
-    Name: Skoegul
-    #Name: Squagul
-    Npc: Agit#prtg_cas04
-  - Id: 19
-    Map: prtg_cas05
-    Name: Gondul
-    #Name: Guindull
-    Npc: Agit#prtg_cas05
-
-# WOE NGuild castle
-  - Id: 20
-    Map: nguild_alde
-    Name: Earth
-    Npc: Agit_N01
-  - Id: 21
-    Map: nguild_gef
-    Name: Air
-    Npc: Agit_N02
-  - Id: 22
-    Map: nguild_pay
-    Name: Water
-    Npc: Agit_N03
-  - Id: 23
-    Map: nguild_prt
-    Name: Fire
-    Npc: Agit_N04
-
-# WOE SE castle
-  - Id: 24
-    Map: schg_cas01
-    Name: Himinn
-    Npc: Manager#schg_cas01
-  - Id: 25
-    Map: schg_cas02
-    Name: Andlangr
-    Npc: Manager#schg_cas02
-  - Id: 26
-    Map: schg_cas03
-    Name: Viblainn
-    Npc: Manager#schg_cas03
-  - Id: 27
-    Map: schg_cas04
-    Name: Hljod
-    Npc: Manager#schg_cas04
-  - Id: 28
-    Map: schg_cas05
-    Name: Skidbladnir
-    Npc: Manager#schg_cas05
-  - Id: 29
-    Map: arug_cas01
-    Name: Mardol
-    Npc: Manager#arug_cas01
-  - Id: 30
-    Map: arug_cas02
-    Name: Cyr
-    Npc: Manager#arug_cas02
-  - Id: 31
-    Map: arug_cas03
-    Name: Horn
-    Npc: Manager#arug_cas03
-  - Id: 32
-    Map: arug_cas04
-    Name: Gefn
-    Npc: Manager#arug_cas04
-  - Id: 33
-    Map: arug_cas05
-    Name: Bandis
-    Npc: Manager#arug_cas05
+  Version: 2
 
 Footer:
   Imports:

+ 8 - 1
db/import-tmpl/castle_db.yml

@@ -26,8 +26,15 @@
 #   Map               Map name to be considered as the castle map.
 #   Name              Name of the castle (used by scripts and guardian name tags).
 #   Npc               NPC unique name to invoke ::OnGuildBreak on, when a occupied castle is abandoned during guild break.
+#   Type              The WoE type this castle belongs to. (Default: First_Edition)
+#   ClientId          Client side ID of the castle. (Default: 0)
+#   WarpEnabled       If the warp to the castle is enabled. (Default: false)
+#   WarpX             X coordinate to warp to. (Default: 0)
+#   WarpY             Y coordinate to warp to. (Default: 0)
+#   WarpCost          Zeny cost to use the warp. (Default: 100)
+#   WarpCostSiege     Zeny cost to use the warp during WoE. (Default: 100000)
 ###########################################################################
 
 Header:
   Type: CASTLE_DB
-  Version: 1
+  Version: 2

+ 33 - 0
db/import-tmpl/item_cash.yml

@@ -0,0 +1,33 @@
+# This file is a part of rAthena.
+#   Copyright(C) 2022 rAthena Development Team
+#   https://rathena.org - https://github.com/rathena
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+###########################################################################
+# Item Cash Database
+###########################################################################
+#
+# Item Cash Settings
+#
+###########################################################################
+# - Tab                     Cash shop tab. Available tabs are New, Hot, Limited, Rental, Permanent, Scrolls, Consumables, Other, Sale.
+#   Items:                  List of possible items.
+#     - Item                Item name.
+#       Price               Item cost in cash points (#CASHPOINTS).
+###########################################################################
+
+Header:
+  Type: ITEM_CASH_DB
+  Version: 1

+ 0 - 19
db/import-tmpl/item_cash_db.txt

@@ -1,19 +0,0 @@
-// Cash Shop Database
-// Contains the items sold in the ingame cash shop.
-//
-// Structure of Database:
-// Type,ItemID,Price
-//
-// Type:
-//    0: New
-//    1: Hot
-//    2: Limited
-//    3: Rental
-//    4: Gear
-//    5: Buff
-//    6: Heal
-//    7: Other
-//    8: Sale
-//
-// Price:
-//    Item cost, in cash points (#CASHPOINTS).

+ 37 - 0
db/item_cash.yml

@@ -0,0 +1,37 @@
+# This file is a part of rAthena.
+#   Copyright(C) 2022 rAthena Development Team
+#   https://rathena.org - https://github.com/rathena
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+###########################################################################
+# Item Cash Database
+###########################################################################
+#
+# Item Cash Settings
+#
+###########################################################################
+# - Tab                     Cash shop tab. Available tabs are New, Hot, Limited, Rental, Permanent, Scrolls, Consumables, Other, Sale.
+#   Items:                  List of possible items.
+#     - Item                Item name.
+#       Price               Item cost in cash points (#CASHPOINTS).
+###########################################################################
+
+Header:
+  Type: ITEM_CASH_DB
+  Version: 1
+
+Footer:
+  Imports:
+    - Path: db/import/item_cash.yml

BIN
db/map_cache.dat


+ 10 - 0
db/map_index.txt

@@ -1200,6 +1200,16 @@ hero_out3
 hero_out4
 hero_tra
 herosria
+1@vrac1
+1@vrac2
+1@vrclo
+1@vrhha
+2@vrclo
+bl_soul
+bl_temple
+bl_venom
+mjo_wst01
+ra_pol01
 
 //======================================================================================
 // - Other/Extra maps -

+ 235 - 1
db/pre-re/castle_db.yml

@@ -26,8 +26,242 @@
 #   Map               Map name to be considered as the castle map.
 #   Name              Name of the castle (used by scripts and guardian name tags).
 #   Npc               NPC unique name to invoke ::OnGuildBreak on, when a occupied castle is abandoned during guild break.
+#   Type              The WoE type this castle belongs to. (Default: First_Edition)
+#   ClientId          Client side ID of the castle. (Default: 0)
+#   WarpEnabled       If the warp to the castle is enabled. (Default: false)
+#   WarpX             X coordinate to warp to. (Default: 0)
+#   WarpY             Y coordinate to warp to. (Default: 0)
+#   WarpCost          Zeny cost to use the warp. (Default: 100)
+#   WarpCostSiege     Zeny cost to use the warp during WoE. (Default: 100000)
 ###########################################################################
 
 Header:
   Type: CASTLE_DB
-  Version: 1
+  Version: 2
+
+Body:
+# WOE FE castle
+  - Id: 0
+    Map: aldeg_cas01
+    Name: Neuschwanstein
+    #Name: Noisyubantian
+    Npc: Agit#aldeg_cas01
+    Type: First_Edition
+    ClientId: 6
+  - Id: 1
+    Map: aldeg_cas02
+    Name: Hohenschwangau
+    #Name: Hohensyubangawoo
+    Npc: Agit#aldeg_cas02
+    Type: First_Edition
+    ClientId: 7
+  - Id: 2
+    Map: aldeg_cas03
+    Name: Nuernberg
+    #Name: Nyirenverk
+    Npc: Agit#aldeg_cas03
+    Type: First_Edition
+    ClientId: 8
+  - Id: 3
+    Map: aldeg_cas04
+    Name: Wuerzburg
+    #Name: Byirtsburi
+    Npc: Agit#aldeg_cas04
+    Type: First_Edition
+    ClientId: 9
+  - Id: 4
+    Map: aldeg_cas05
+    Name: Rothenburg
+    #Name: Rotenburk
+    Npc: Agit#aldeg_cas05
+    Type: First_Edition
+    ClientId: 10
+  - Id: 5
+    Map: gefg_cas01
+    Name: Repherion
+    #Name: Reprion
+    Npc: Agit#gefg_cas01
+    Type: First_Edition
+    ClientId: 11
+  - Id: 6
+    Map: gefg_cas02
+    Name: Eeyolbriggar
+    #Name: Yolbriger
+    Npc: Agit#gefg_cas02
+    Type: First_Edition
+    ClientId: 12
+  - Id: 7
+    Map: gefg_cas03
+    Name: Yesnelph
+    #Name: Isinlife
+    Npc: Agit#gefg_cas03
+    Type: First_Edition
+    ClientId: 13
+  - Id: 8
+    Map: gefg_cas04
+    Name: Bergel
+    #Name: Berigel
+    Npc: Agit#gefg_cas04
+    Type: First_Edition
+    ClientId: 14
+  - Id: 9
+    Map: gefg_cas05
+    Name: Mersetzdeitz
+    #Name: Melsedetsu
+    Npc: Agit#gefg_cas05
+    Type: First_Edition
+    ClientId: 15
+  - Id: 10
+    Map: payg_cas01
+    Name: Bright Arbor
+    #Name: Mingting
+    Npc: Agit#payg_cas01
+    Type: First_Edition
+    ClientId: 16
+  - Id: 11
+    Map: payg_cas02
+    Name: Scarlet Palace
+    #Name: Tiantan
+    Npc: Agit#payg_cas02
+    Type: First_Edition
+    ClientId: 17
+  - Id: 12
+    Map: payg_cas03
+    Name: Holy Shadow
+    #Name: Fuying
+    Npc: Agit#payg_cas03
+    Type: First_Edition
+    ClientId: 18
+  - Id: 13
+    Map: payg_cas04
+    Name: Sacred Altar
+    #Name: Honglou
+    Npc: Agit#payg_cas04
+    Type: First_Edition
+    ClientId: 19
+  - Id: 14
+    Map: payg_cas05
+    Name: Bamboo Grove Hill
+    #Name: Zhulinxian
+    Npc: Agit#payg_cas05
+    Type: First_Edition
+    ClientId: 20
+  - Id: 15
+    Map: prtg_cas01
+    Name: Kriemhild
+    #Name: Creamhilt
+    Npc: Agit#prtg_cas01
+    Type: First_Edition
+    ClientId: 1
+  - Id: 16
+    Map: prtg_cas02
+    Name: Swanhild
+    #Name: Sbanhealt
+    Npc: Agit#prtg_cas02
+    Type: First_Edition
+    ClientId: 2
+  - Id: 17
+    Map: prtg_cas03
+    Name: Fadhgridh
+    #Name: Lazrigees
+    Npc: Agit#prtg_cas03
+    Type: First_Edition
+    ClientId: 3
+  - Id: 18
+    Map: prtg_cas04
+    Name: Skoegul
+    #Name: Squagul
+    Npc: Agit#prtg_cas04
+    Type: First_Edition
+    ClientId: 4
+  - Id: 19
+    Map: prtg_cas05
+    Name: Gondul
+    #Name: Guindull
+    Npc: Agit#prtg_cas05
+    Type: First_Edition
+    ClientId: 5
+
+# WOE NGuild castle
+  - Id: 20
+    Map: nguild_alde
+    Name: Earth
+    Npc: Agit_N01
+    Type: First_Edition
+  - Id: 21
+    Map: nguild_gef
+    Name: Air
+    Npc: Agit_N02
+    Type: First_Edition
+  - Id: 22
+    Map: nguild_pay
+    Name: Water
+    Npc: Agit_N03
+    Type: First_Edition
+  - Id: 23
+    Map: nguild_prt
+    Name: Fire
+    Npc: Agit_N04
+    Type: First_Edition
+
+# WOE SE castle
+  - Id: 24
+    Map: schg_cas01
+    Name: Himinn
+    Npc: Manager#schg_cas01
+    Type: Second_Edition
+    ClientId: 26
+  - Id: 25
+    Map: schg_cas02
+    Name: Andlangr
+    Npc: Manager#schg_cas02
+    Type: Second_Edition
+    ClientId: 27
+  - Id: 26
+    Map: schg_cas03
+    Name: Viblainn
+    Npc: Manager#schg_cas03
+    Type: Second_Edition
+    ClientId: 28
+  - Id: 27
+    Map: schg_cas04
+    Name: Hljod
+    Npc: Manager#schg_cas04
+    Type: Second_Edition
+    ClientId: 29
+  - Id: 28
+    Map: schg_cas05
+    Name: Skidbladnir
+    Npc: Manager#schg_cas05
+    Type: Second_Edition
+    ClientId: 30
+  - Id: 29
+    Map: arug_cas01
+    Name: Mardol
+    Npc: Manager#arug_cas01
+    Type: Second_Edition
+    ClientId: 21
+  - Id: 30
+    Map: arug_cas02
+    Name: Cyr
+    Npc: Manager#arug_cas02
+    Type: Second_Edition
+    ClientId: 22
+  - Id: 31
+    Map: arug_cas03
+    Name: Horn
+    Npc: Manager#arug_cas03
+    Type: Second_Edition
+    ClientId: 23
+  - Id: 32
+    Map: arug_cas04
+    Name: Gefn
+    Npc: Manager#arug_cas04
+    Type: Second_Edition
+    ClientId: 24
+  - Id: 33
+    Map: arug_cas05
+    Name: Bandis
+    Npc: Manager#arug_cas05
+    Type: Second_Edition
+    ClientId: 25

+ 0 - 19
db/pre-re/item_cash_db.txt

@@ -1,19 +0,0 @@
-// Cash Shop Database
-// Contains the items sold in the ingame cash shop.
-//
-// Structure of Database:
-// Type,ItemID,Price
-//
-// Type:
-//    0: New
-//    1: Hot
-//    2: Limited
-//    3: Rental
-//    4: Gear
-//    5: Buff
-//    6: Heal
-//    7: Other
-//    8: Sale
-//
-// Price:
-//    Item cost, in cash points (#CASHPOINTS).

+ 334 - 1
db/re/castle_db.yml

@@ -26,51 +26,384 @@
 #   Map               Map name to be considered as the castle map.
 #   Name              Name of the castle (used by scripts and guardian name tags).
 #   Npc               NPC unique name to invoke ::OnGuildBreak on, when a occupied castle is abandoned during guild break.
+#   Type              The WoE type this castle belongs to. (Default: First_Edition)
+#   ClientId          Client side ID of the castle. (Default: 0)
+#   WarpEnabled       If the warp to the castle is enabled. (Default: false)
+#   WarpX             X coordinate to warp to. (Default: 0)
+#   WarpY             Y coordinate to warp to. (Default: 0)
+#   WarpCost          Zeny cost to use the warp. (Default: 100)
+#   WarpCostSiege     Zeny cost to use the warp during WoE. (Default: 100000)
 ###########################################################################
 
 Header:
   Type: CASTLE_DB
-  Version: 1
+  Version: 2
 
 Body:
+# WOE FE castle
+  - Id: 0
+    Map: aldeg_cas01
+    Name: Neuschwanstein
+    #Name: Noisyubantian
+    Npc: Agit#aldeg_cas01
+    Type: First_Edition
+    ClientId: 6
+    WarpEnabled: true
+    WarpX: 212
+    WarpY: 175
+  - Id: 1
+    Map: aldeg_cas02
+    Name: Hohenschwangau
+    #Name: Hohensyubangawoo
+    Npc: Agit#aldeg_cas02
+    Type: First_Edition
+    ClientId: 7
+    WarpEnabled: true
+    WarpX: 82
+    WarpY: 71
+  - Id: 2
+    Map: aldeg_cas03
+    Name: Nuernberg
+    #Name: Nyirenverk
+    Npc: Agit#aldeg_cas03
+    Type: First_Edition
+    ClientId: 8
+    WarpEnabled: true
+    WarpX: 109
+    WarpY: 112
+  - Id: 3
+    Map: aldeg_cas04
+    Name: Wuerzburg
+    #Name: Byirtsburi
+    Npc: Agit#aldeg_cas04
+    Type: First_Edition
+    ClientId: 9
+    WarpEnabled: true
+    WarpX: 60
+    WarpY: 116
+  - Id: 4
+    Map: aldeg_cas05
+    Name: Rothenburg
+    #Name: Rotenburk
+    Npc: Agit#aldeg_cas05
+    Type: First_Edition
+    ClientId: 10
+    WarpEnabled: true
+    WarpX: 61
+    WarpY: 185
+  - Id: 5
+    Map: gefg_cas01
+    Name: Repherion
+    #Name: Reprion
+    Npc: Agit#gefg_cas01
+    Type: First_Edition
+    ClientId: 11
+    WarpEnabled: true
+    WarpX: 40
+    WarpY: 43
+  - Id: 6
+    Map: gefg_cas02
+    Name: Eeyolbriggar
+    #Name: Yolbriger
+    Npc: Agit#gefg_cas02
+    Type: First_Edition
+    ClientId: 12
+    WarpEnabled: true
+    WarpX: 22
+    WarpY: 66
+  - Id: 7
+    Map: gefg_cas03
+    Name: Yesnelph
+    #Name: Isinlife
+    Npc: Agit#gefg_cas03
+    Type: First_Edition
+    ClientId: 13
+    WarpEnabled: true
+    WarpX: 112
+    WarpY: 23
+  - Id: 8
+    Map: gefg_cas04
+    Name: Bergel
+    #Name: Berigel
+    Npc: Agit#gefg_cas04
+    Type: First_Edition
+    ClientId: 14
+    WarpEnabled: true
+    WarpX: 58
+    WarpY: 46
+  - Id: 9
+    Map: gefg_cas05
+    Name: Mersetzdeitz
+    #Name: Melsedetsu
+    Npc: Agit#gefg_cas05
+    Type: First_Edition
+    ClientId: 15
+    WarpEnabled: true
+    WarpX: 66
+    WarpY: 48
+  - Id: 10
+    Map: payg_cas01
+    Name: Bright Arbor
+    #Name: Mingting
+    Npc: Agit#payg_cas01
+    Type: First_Edition
+    ClientId: 16
+    WarpEnabled: true
+    WarpX: 115
+    WarpY: 57
+  - Id: 11
+    Map: payg_cas02
+    Name: Scarlet Palace
+    #Name: Tiantan
+    Npc: Agit#payg_cas02
+    Type: First_Edition
+    ClientId: 17
+    WarpEnabled: true
+    WarpX: 26
+    WarpY: 265
+  - Id: 12
+    Map: payg_cas03
+    Name: Holy Shadow
+    #Name: Fuying
+    Npc: Agit#payg_cas03
+    Type: First_Edition
+    ClientId: 18
+    WarpEnabled: true
+    WarpX: 43
+    WarpY: 264
+  - Id: 13
+    Map: payg_cas04
+    Name: Sacred Altar
+    #Name: Honglou
+    Npc: Agit#payg_cas04
+    Type: First_Edition
+    ClientId: 19
+    WarpEnabled: true
+    WarpX: 36
+    WarpY: 272
+  - Id: 14
+    Map: payg_cas05
+    Name: Bamboo Grove Hill
+    #Name: Zhulinxian
+    Npc: Agit#payg_cas05
+    Type: First_Edition
+    ClientId: 20
+    WarpEnabled: true
+    WarpX: 274
+    WarpY: 246
+  - Id: 15
+    Map: prtg_cas01
+    Name: Kriemhild
+    #Name: Creamhilt
+    Npc: Agit#prtg_cas01
+    Type: First_Edition
+    ClientId: 1
+    WarpEnabled: true
+    WarpX: 107
+    WarpY: 180
+  - Id: 16
+    Map: prtg_cas02
+    Name: Swanhild
+    #Name: Sbanhealt
+    Npc: Agit#prtg_cas02
+    Type: First_Edition
+    ClientId: 2
+    WarpEnabled: true
+    WarpX: 94
+    WarpY: 56
+  - Id: 17
+    Map: prtg_cas03
+    Name: Fadhgridh
+    #Name: Lazrigees
+    Npc: Agit#prtg_cas03
+    Type: First_Edition
+    ClientId: 3
+    WarpEnabled: true
+    WarpX: 46
+    WarpY: 97
+  - Id: 18
+    Map: prtg_cas04
+    Name: Skoegul
+    #Name: Squagul
+    Npc: Agit#prtg_cas04
+    Type: First_Edition
+    ClientId: 4
+    WarpEnabled: true
+    WarpX: 260
+    WarpY: 262
+  - Id: 19
+    Map: prtg_cas05
+    Name: Gondul
+    #Name: Guindull
+    Npc: Agit#prtg_cas05
+    Type: First_Edition
+    ClientId: 5
+    WarpEnabled: true
+    WarpX: 26
+    WarpY: 38
+
+# WOE NGuild castle
+  - Id: 20
+    Map: nguild_alde
+    Name: Earth
+    Npc: Agit_N01
+    Type: First_Edition
+  - Id: 21
+    Map: nguild_gef
+    Name: Air
+    Npc: Agit_N02
+    Type: First_Edition
+  - Id: 22
+    Map: nguild_pay
+    Name: Water
+    Npc: Agit_N03
+    Type: First_Edition
+  - Id: 23
+    Map: nguild_prt
+    Name: Fire
+    Npc: Agit_N04
+    Type: First_Edition
+
+# WOE SE castle
+  - Id: 24
+    Map: schg_cas01
+    Name: Himinn
+    Npc: Manager#schg_cas01
+    Type: Second_Edition
+    ClientId: 26
+    WarpEnabled: true
+    WarpX: 233
+    WarpY: 300
+  - Id: 25
+    Map: schg_cas02
+    Name: Andlangr
+    Npc: Manager#schg_cas02
+    Type: Second_Edition
+    ClientId: 27
+    WarpEnabled: true
+    WarpX: 101
+    WarpY: 372
+  - Id: 26
+    Map: schg_cas03
+    Name: Viblainn
+    Npc: Manager#schg_cas03
+    Type: Second_Edition
+    ClientId: 28
+    WarpEnabled: true
+    WarpX: 81
+    WarpY: 94
+  - Id: 27
+    Map: schg_cas04
+    Name: Hljod
+    Npc: Manager#schg_cas04
+    Type: Second_Edition
+    ClientId: 29
+    WarpEnabled: true
+    WarpX: 233
+    WarpY: 300
+  - Id: 28
+    Map: schg_cas05
+    Name: Skidbladnir
+    Npc: Manager#schg_cas05
+    Type: Second_Edition
+    ClientId: 30
+    WarpEnabled: true
+    WarpX: 233
+    WarpY: 300
+  - Id: 29
+    Map: arug_cas01
+    Name: Mardol
+    Npc: Manager#arug_cas01
+    Type: Second_Edition
+    ClientId: 21
+    WarpEnabled: true
+    WarpX: 77
+    WarpY: 371
+  - Id: 30
+    Map: arug_cas02
+    Name: Cyr
+    Npc: Manager#arug_cas02
+    Type: Second_Edition
+    ClientId: 22
+    WarpEnabled: true
+    WarpX: 301
+    WarpY: 332
+  - Id: 31
+    Map: arug_cas03
+    Name: Horn
+    Npc: Manager#arug_cas03
+    Type: Second_Edition
+    ClientId: 23
+    WarpEnabled: true
+    WarpX: 322
+    WarpY: 91
+  - Id: 32
+    Map: arug_cas04
+    Name: Gefn
+    Npc: Manager#arug_cas04
+    Type: Second_Edition
+    ClientId: 24
+    WarpEnabled: true
+    WarpX: 322
+    WarpY: 91
+  - Id: 33
+    Map: arug_cas05
+    Name: Bandis
+    Npc: Manager#arug_cas05
+    Type: Second_Edition
+    ClientId: 25
+    WarpEnabled: true
+    WarpX: 322
+    WarpY: 91
+
 # WOE TE castle
   - Id: 34
     Map: te_aldecas1
     Name: Kafragarten 1
     Npc: Manager_TE#Glaris
+    Type: Third_Edition
   - Id: 35
     Map: te_aldecas2
     Name: Kafragarten 2
     Npc: Manager_TE#Defolty
+    Type: Third_Edition
   - Id: 36
     Map: te_aldecas3
     Name: Kafragarten 3
     Npc: Manager_TE#Sorin
+    Type: Third_Edition
   - Id: 37
     Map: te_aldecas4
     Name: Kafragarten 4
     Npc: Manager_TE#Bennit
+    Type: Third_Edition
   - Id: 38
     Map: te_aldecas5
     Name: Kafragarten 5
     Npc: Manager_TE#W
+    Type: Third_Edition
   - Id: 39
     Map: te_prtcas01
     Name: Gloria 1
     Npc: Manager_TE#Gaebolg
+    Type: Third_Edition
   - Id: 40
     Map: te_prtcas02
     Name: Gloria 2
     Npc: Manager_TE#Richard
+    Type: Third_Edition
   - Id: 41
     Map: te_prtcas03
     Name: Gloria 3
     Npc: Manager_TE#Wigner
+    Type: Third_Edition
   - Id: 42
     Map: te_prtcas04
     Name: Gloria 4
     Npc: Manager_TE#Heine
+    Type: Third_Edition
   - Id: 43
     Map: te_prtcas05
     Name: Gloria 5
     Npc: Manager_TE#Nerious
+    Type: Third_Edition

+ 0 - 19
db/re/item_cash_db.txt

@@ -1,19 +0,0 @@
-// Cash Shop Database
-// Contains the items sold in the ingame cash shop.
-//
-// Structure of Database:
-// Type,ItemID,Price
-//
-// Type:
-//    0: New
-//    1: Hot
-//    2: Limited
-//    3: Rental
-//    4: Gear
-//    5: Buff
-//    6: Heal
-//    7: Other
-//    8: Sale
-//
-// Price:
-//    Item cost, in cash points (#CASHPOINTS).

+ 51 - 1
db/re/item_combos.yml

@@ -30436,7 +30436,7 @@ Body:
   - Combos:
       - Combo:
           - aegis_311883    # 311883
-          - Evil_Card_J    # 27141
+          - Silva_Papilia_Card    # 300099
     Script: |
       bonus2 bSubEle,Ele_Neutral,20;
       skill "GN_BLOOD_SUCKER",5;
@@ -31690,3 +31690,53 @@ Body:
           - Holy_Cannon_Ball    # 18001
     Script: |
       bonus2 bSubSize,Size_Small,8;
+  - Combos:
+      - Combo:
+          - Aferde_Card   # 300447
+          - Timbers_Card   # 300449
+    Script: |
+      bonus bLongAtkRate,8;
+  - Combos:
+      - Combo:
+          - Mocadas_Knife   # 510103
+          - Mocadas_Degger   # 510104
+    Script: |
+      .@sum = getequiprefinerycnt(EQI_HAND_R)+getequiprefinerycnt(EQI_HAND_L);
+      bonus bAtkRate,5;
+      bonus2 bSkillAtk,"SS_KAGEGISSEN",10;
+      if (.@sum>=14) {
+         bonus bShortAtkRate,10;
+         if (.@sum>=18) {
+            bonus2 bSkillAtk,"SS_KAGEGISSEN",10;
+            if (.@sum>=22) {
+               bonus2 bSkillAtk,"SS_KAGEGISSEN",10;
+            }
+         }
+      }
+  - Combos:
+      - Combo:
+          - F_Ein_1HDAGGER   # 510105
+          - F_Ein_1HDAGGER2   # 510106
+    Script: |
+      .@sum = getequiprefinerycnt(EQI_HAND_R)+getequiprefinerycnt(EQI_HAND_L);
+      bonus bAtkRate,10;
+      bonus2 bSkillAtk,"SHC_SHADOW_STAB",20;
+      bonus2 bSkillAtk,"GC_CROSSIMPACT",20;
+      if (.@sum>=14) {
+         bonus bAspdRate,10;
+         bonus bBaseAtk,70;
+         if (.@sum>=18) {
+            bonus2 bAddSize,Size_All,15;
+            bonus2 bSkillAtk,"SHC_SHADOW_STAB",10;
+            bonus2 bSkillAtk,"GC_CROSSIMPACT",15;
+            bonus2 bSkillUseSP,"SHC_SHADOW_STAB",-15;
+            bonus2 bSkillUseSP,"GC_CROSSIMPACT",-15;
+            if (.@sum>=22) {
+               bonus bDelayrate,-15;
+               bonus2 bSkillAtk,"SHC_SHADOW_STAB",10;
+               bonus2 bSkillAtk,"GC_CROSSIMPACT",10;
+               bonus2 bSkillUseSP,"SHC_SHADOW_STAB",-15;
+               bonus2 bSkillUseSP,"GC_CROSSIMPACT",-15;
+            }
+         }
+      }

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 910 - 3
db/re/item_db_equip.yml


+ 267 - 0
db/re/item_db_etc.yml

@@ -50295,6 +50295,166 @@ Body:
     Script: |
       bonus2 bSubEle,Ele_Neutral,-10;
       bonus bLongAtkRate,5+4*(getrefine()/2);
+  - Id: 300441
+    AegisName: Burning_Knight_Card
+    Name: Burning Night Card
+    Type: Card
+    Weight: 10
+    Locations:
+      Right_Hand: true
+    Flags:
+      BuyingStore: true
+      DropEffect: CLIENT
+    Script: |
+      .@r = getrefine();
+      bonus bAtkRate,5;
+      .@view = getiteminfo(getequipid(EQI_HAND_R), ITEMINFO_VIEW);
+      if (.@view == W_1HAXE || .@view == W_2HAXE) {
+         bonus bLongAtkRate,2*(.@r/3)+10;
+         if (BaseLevel>=200) {
+            bonus bLongAtkRate,3*(.@r/3);
+         }
+      }
+  - Id: 300442
+    AegisName: Dedsera_Card
+    Name: Deadsera Card
+    Type: Card
+    Weight: 10
+    Locations:
+      Right_Hand: true
+    Flags:
+      BuyingStore: true
+      DropEffect: CLIENT
+    Script: |
+      .@r = getrefine();
+      bonus bMatkRate,5;
+      if (getiteminfo(getequipid(EQI_HAND_R), ITEMINFO_VIEW) == W_STAFF) {
+         bonus2 bMagicAtkEle,Ele_All,10+2*(.@r/3);
+      }
+      if (BaseLevel>=200) {
+         bonus2 bMagicAtkEle,Ele_All,3*(.@r/3);
+      }
+  - Id: 300443
+    AegisName: Hardrock_Titan_Card
+    Name: Hardrock Titan Card
+    Type: Card
+    Weight: 10
+    Locations:
+      Shoes: true
+    Flags:
+      BuyingStore: true
+      DropEffect: CLIENT
+    Script: |
+      .@r = getrefine();
+      bonus bAtkRate,5;
+      if (.@r>=7) {
+         bonus2 bAddEle,Ele_Earth,10;
+         if (.@r>=9) {
+            bonus2 bAddEle,Ele_Earth,7;
+         }
+      }
+  - Id: 300444
+    AegisName: Deadween_Card
+    Name: Deadween Card
+    Type: Card
+    Weight: 10
+    Locations:
+      Shoes: true
+    Flags:
+      BuyingStore: true
+      DropEffect: CLIENT
+    Script: |
+      .@r = getrefine();
+      bonus bAtkRate,5;
+      if (.@r>=7) {
+         bonus bAtkRate,3;
+         if (.@r>=9) {
+            bonus bAtkRate,2;
+         }
+      }
+  - Id: 300445
+    AegisName: Gaia_Pol_Card
+    Name: Gaia Pol Card
+    Type: Card
+    Weight: 10
+    Locations:
+      Armor: true
+    Flags:
+      BuyingStore: true
+      DropEffect: CLIENT
+    Script: |
+      bonus2 bAddRace,RC_Plant,40;
+      bonus2 bAddRace,RC_DemiHuman,40;
+      bonus2 bAddEle,Ele_Neutral,40;
+      bonus2 bAddEle,Ele_Earth,40;
+  - Id: 300446
+    AegisName: Punch_Bug_Card
+    Name: Punch Bug Card
+    Type: Card
+    Weight: 10
+    Locations:
+      Right_Hand: true
+    Flags:
+      BuyingStore: true
+      DropEffect: CLIENT
+    Script: |
+      .@r = getrefine();
+      bonus bAtkRate,5;
+      if (getiteminfo(getequipid(EQI_HAND_R), ITEMINFO_VIEW) == W_KNUCKLE) {
+         bonus bShortAtkRate,10+2*(.@r/3);
+         if (BaseLevel>=200) {
+            bonus bShortAtkRate,3*(.@r/3);
+         }
+      }
+  - Id: 300447
+    AegisName: Aferde_Card
+    Name: Aferde Card
+    Type: Card
+    Weight: 10
+    Locations:
+      Left_Accessory: true
+    Flags:
+      BuyingStore: true
+      DropEffect: CLIENT
+    Script: |
+      bonus bLongAtkRate,6;
+  - Id: 300448
+    AegisName: Dispol_Card
+    Name: Dispol Card
+    Type: Card
+    Weight: 10
+    Locations:
+      Left_Accessory: true
+    Flags:
+      BuyingStore: true
+      DropEffect: CLIENT
+    Script: |
+      bonus2 bMagicAtkEle,Ele_Holy,35;
+      bonus bUseSPrate,10;
+  - Id: 300449
+    AegisName: Timbers_Card
+    Name: Timbers Card
+    Type: Card
+    Weight: 10
+    Locations:
+      Right_Accessory: true
+    Flags:
+      BuyingStore: true
+      DropEffect: CLIENT
+    Script: |
+      bonus bLongAtkRate,6;
+  - Id: 300450
+    AegisName: Renire_Card
+    Name: Renire Card
+    Type: Card
+    Weight: 10
+    Locations:
+      Shoes: true
+    Flags:
+      BuyingStore: true
+      DropEffect: CLIENT
+    Script: |
+      bonus2 bMagicAtkEle,Ele_Wind,3*getrefine();
   - Id: 310000
     AegisName: Ranger_Top2
     Name: Sniper Stone II (Top)
@@ -65649,6 +65809,62 @@ Body:
     SubType: Enchant
     Script: |
       bonus bWis,5;
+  - Id: 312004
+    AegisName: Bishop_Blessing1
+    Name: Bishop's Blessing (Attack Speed)
+    Type: Card
+    SubType: Enchant
+    Script: |
+      bonus bAspdRate,3+3*getenchantgrade();
+  - Id: 312005
+    AegisName: Bishop_Blessing2
+    Name: Bishop's Blessing (Variable Casting)
+    Type: Card
+    SubType: Enchant
+    Script: |
+      bonus bVariableCastrate,-3-3*getenchantgrade();
+  - Id: 312006
+    AegisName: Bishop_Blessing3
+    Name: Bishop's Blessing (Global Cool Time)
+    Type: Card
+    SubType: Enchant
+    Script: |
+      bonus bDelayrate,-3-3*getenchantgrade();
+  - Id: 312007
+    AegisName: Bishop_Blessing4
+    Name: Bishop's Blessing (Perfect Hit)
+    Type: Card
+    SubType: Enchant
+    Script: |
+      bonus bPerfectHitRate,3+3*getenchantgrade();
+  - Id: 312008
+    AegisName: Bishop_Blessing5
+    Name: Bishop's Blessing (Cri)
+    Type: Card
+    SubType: Enchant
+    Script: |
+      bonus bCritical,3+3*getenchantgrade();
+  - Id: 312009
+    AegisName: Warped_Spell1
+    Name: Warped Spell (Melee)
+    Type: Card
+    SubType: Enchant
+    Script: |
+      bonus bShortAtkRate,5+5*getenchantgrade();
+  - Id: 312010
+    AegisName: Warped_Spell2
+    Name: Warped Spell (Magic)
+    Type: Card
+    SubType: Enchant
+    Script: |
+      bonus2 bMagicAtkEle,Ele_All,5+5*getenchantgrade();
+  - Id: 312011
+    AegisName: Warped_Spell3
+    Name: Warped Spell (Long Ranged)
+    Type: Card
+    SubType: Enchant
+    Script: |
+      bonus bLongAtkRate,5+5*getenchantgrade();
   - Id: 312014
     AegisName: Justice_Power
     Name: Good Spell
@@ -70253,6 +70469,57 @@ Body:
     Weight: 10
     Flags:
       BuyingStore: true
+  - Id: 1001198
+    AegisName: Sources_Of_Pollution
+    Name: Source of contamination    # !todo check english name
+    Type: Etc
+    Weight: 10
+    Flags:
+      BuyingStore: true
+      DropEffect: CLIENT
+  - Id: 1001199
+    AegisName: Fragments_Of_Pollution
+    Name: Fragments of Corruption    # !todo check english name
+    Type: Etc
+    Weight: 10
+    Flags:
+      BuyingStore: true
+      DropEffect: CLIENT
+  - Id: 1001200
+    AegisName: Crystal_Of_Pollution
+    Name: Determination of contamination    # !todo check english name
+    Type: Etc
+    Weight: 10
+    Flags:
+      BuyingStore: true
+      DropEffect: CLIENT
+  - Id: 1001201
+    AegisName: Mocadas_Gold
+    Name: Priest's Relic    # !todo check english name
+    Type: Etc
+    Flags:
+      BuyingStore: true
+  - Id: 1001202
+    AegisName: Mocadas_Water
+    Name: Priest's Holy Water    # !todo check english name
+    Type: Etc
+    Flags:
+      BuyingStore: true
+  - Id: 1001203
+    AegisName: Mjo_Treasure
+    Name: Myornil's Treasure    # !todo check english name
+    Type: Etc
+    Weight: 10
+    Flags:
+      BuyingStore: true
+      DropEffect: CLIENT
+  - Id: 1001204
+    AegisName: Mjo_Energy
+    Name: Myornil's Energy    # !todo check english name
+    Type: Etc
+    Flags:
+      BuyingStore: true
+      DropEffect: CLIENT
   - Id: 1220001
     AegisName: Kunai
     Name: Kunai

+ 12 - 0
db/re/item_db_usable.yml

@@ -65929,6 +65929,12 @@ Body:
     Weight: 10
     Flags:
       BuyingStore: true
+  - Id: 101745
+    AegisName: Dimmen_A_Reform
+    Name: Unsealing the dimension    # !todo check english name
+    Type: DelayConsume
+    Script: |
+      item_reform();
   - Id: 101792
     AegisName: SubjectCape_Refine_Cube
     Name: Subject Manteau 12 Refine Cube
@@ -65938,6 +65944,12 @@ Body:
       BuyingStore: true
     Script: |
       laphine_upgrade();
+  - Id: 101816
+    AegisName: Dimmen_A_Refine
+    Name: Dimension Enhancer    # !todo check english name
+    Type: DelayConsume
+    Script: |
+      item_reform();
   - Id: 101876
     AegisName: aegis_101876
     Name: Costume Enchant Stone Box 29

+ 546 - 0
db/re/item_enchant.yml

@@ -20123,6 +20123,552 @@ Body:
                 Chance: 50
               - Item: Bio_Jam_Boss
                 Chance: 50
+  - Id: 63
+    TargetItems:
+      F_Ein_1HDAGGER: true
+      F_Ein_1HMAGGER: true
+      F_Ein_1HSWORD: true
+      F_Ein_1H_Foxtail: true
+      F_Ein_1HSPEAR: true
+      F_Ein_BHSWORD: true
+      F_Ein_1HHAMMER: true
+      F_Ein_BHAXE: true
+      F_Ein_1HKNUCK: true
+      F_Ein_1HBOOK: true
+      F_Ein_1HWAND: true
+      F_Ein_BHSTAFF: true
+      F_Ein_BHHuuma: true
+      F_Ein_BHBOW: true
+      F_Ein_BHKATAR: true
+      F_Ein_1HGUN: true
+      F_Ein_1HWHIP: true
+      F_Ein_1HLUTE: true
+    Reset:
+      Chance: 100000
+      Price: 3000000
+      Materials:
+        - Material: Mjo_Treasure
+          Amount: 30
+        - Material: Mjo_Energy
+          Amount: 30
+        - Material: Ein_DYNITE
+          Amount: 30
+    Order:
+      - Slot: 3
+      - Slot: 2
+    Slots:
+      - Slot: 3
+        PerfectEnchants:
+          - Item: Sharp1
+            Price: 300000
+            Materials:
+              - Material: Mjo_Energy
+                Amount: 100
+              - Material: Ein_DYNITE
+                Amount: 50
+              - Material: Mjo_Treasure
+                Amount: 75
+          - Item: Hit_Plus1
+            Price: 300000
+            Materials:
+              - Material: Mjo_Energy
+                Amount: 100
+              - Material: Ein_DYNITE
+                Amount: 50
+              - Material: Mjo_Treasure
+                Amount: 75
+          - Item: Attack_Delay_1
+            Price: 300000
+            Materials:
+              - Material: Mjo_Energy
+                Amount: 100
+              - Material: Ein_DYNITE
+                Amount: 50
+              - Material: Mjo_Treasure
+                Amount: 75
+          - Item: Caster1
+            Price: 300000
+            Materials:
+              - Material: Mjo_Energy
+                Amount: 100
+              - Material: Ein_DYNITE
+                Amount: 50
+              - Material: Mjo_Treasure
+                Amount: 75
+        Upgrades:
+          - Enchant: Sharp1
+            Upgrade: Sharp2
+            Price: 200000
+            Materials:
+              - Material: Mjo_Energy
+                Amount: 50
+              - Material: Ein_DYNITE
+                Amount: 5
+              - Material: Mjo_Treasure
+                Amount: 40
+          - Enchant: Hit_Plus1
+            Upgrade: Hit_Plus2
+            Price: 200000
+            Materials:
+              - Material: Mjo_Energy
+                Amount: 50
+              - Material: Ein_DYNITE
+                Amount: 5
+              - Material: Mjo_Treasure
+                Amount: 40
+          - Enchant: Attack_Delay_1
+            Upgrade: Attack_Delay_2
+            Price: 200000
+            Materials:
+              - Material: Mjo_Energy
+                Amount: 50
+              - Material: Ein_DYNITE
+                Amount: 5
+              - Material: Mjo_Treasure
+                Amount: 40
+          - Enchant: Caster1
+            Upgrade: Caster2
+            Price: 200000
+            Materials:
+              - Material: Mjo_Energy
+                Amount: 50
+              - Material: Ein_DYNITE
+                Amount: 5
+              - Material: Mjo_Treasure
+                Amount: 40
+          - Enchant: Sharp2
+            Upgrade: Sharp3
+            Price: 300000
+            Materials:
+              - Material: Mjo_Energy
+                Amount: 70
+              - Material: Ein_DYNITE
+                Amount: 10
+              - Material: Mjo_Treasure
+                Amount: 45
+          - Enchant: Hit_Plus2
+            Upgrade: Hit_Plus3
+            Price: 300000
+            Materials:
+              - Material: Mjo_Energy
+                Amount: 70
+              - Material: Ein_DYNITE
+                Amount: 10
+              - Material: Mjo_Treasure
+                Amount: 45
+          - Enchant: Attack_Delay_2
+            Upgrade: Attack_Delay_3
+            Price: 300000
+            Materials:
+              - Material: Mjo_Energy
+                Amount: 70
+              - Material: Ein_DYNITE
+                Amount: 10
+              - Material: Mjo_Treasure
+                Amount: 45
+          - Enchant: Caster2
+            Upgrade: Caster3
+            Price: 300000
+            Materials:
+              - Material: Mjo_Energy
+                Amount: 70
+              - Material: Ein_DYNITE
+                Amount: 10
+              - Material: Mjo_Treasure
+                Amount: 45
+          - Enchant: Sharp3
+            Upgrade: Sharp4
+            Price: 400000
+            Materials:
+              - Material: Mjo_Energy
+                Amount: 90
+              - Material: Ein_DYNITE
+                Amount: 15
+              - Material: Mjo_Treasure
+                Amount: 50
+          - Enchant: Hit_Plus3
+            Upgrade: Hit_Plus4
+            Price: 400000
+            Materials:
+              - Material: Mjo_Energy
+                Amount: 90
+              - Material: Ein_DYNITE
+                Amount: 15
+              - Material: Mjo_Treasure
+                Amount: 50
+          - Enchant: Attack_Delay_3
+            Upgrade: Attack_Delay_4
+            Price: 400000
+            Materials:
+              - Material: Mjo_Energy
+                Amount: 90
+              - Material: Ein_DYNITE
+                Amount: 15
+              - Material: Mjo_Treasure
+                Amount: 50
+          - Enchant: Caster3
+            Upgrade: Caster4
+            Price: 400000
+            Materials:
+              - Material: Mjo_Energy
+                Amount: 90
+              - Material: Ein_DYNITE
+                Amount: 15
+              - Material: Mjo_Treasure
+                Amount: 50
+          - Enchant: Sharp4
+            Upgrade: Sharp5
+            Price: 500000
+            Materials:
+              - Material: Mjo_Energy
+                Amount: 110
+              - Material: Ein_DYNITE
+                Amount: 20
+              - Material: Mjo_Treasure
+                Amount: 60
+          - Enchant: Hit_Plus4
+            Upgrade: Hit_Plus5
+            Price: 500000
+            Materials:
+              - Material: Mjo_Energy
+                Amount: 110
+              - Material: Ein_DYNITE
+                Amount: 20
+              - Material: Mjo_Treasure
+                Amount: 60
+          - Enchant: Attack_Delay_4
+            Upgrade: Attack_Delay_5
+            Price: 500000
+            Materials:
+              - Material: Mjo_Energy
+                Amount: 110
+              - Material: Ein_DYNITE
+                Amount: 20
+              - Material: Mjo_Treasure
+                Amount: 60
+          - Enchant: Caster4
+            Upgrade: Caster5
+            Price: 500000
+            Materials:
+              - Material: Mjo_Energy
+                Amount: 110
+              - Material: Ein_DYNITE
+                Amount: 20
+              - Material: Mjo_Treasure
+                Amount: 60
+      - Slot: 2
+        PerfectEnchants:
+          - Item: Expert_Archer1
+            Price: 300000
+            Materials:
+              - Material: Mjo_Energy
+                Amount: 200
+              - Material: Ein_DYNITE
+                Amount: 75
+              - Material: Mjo_Treasure
+                Amount: 150
+          - Item: Expert_Fighter1
+            Price: 300000
+            Materials:
+              - Material: Mjo_Energy
+                Amount: 200
+              - Material: Ein_DYNITE
+                Amount: 75
+              - Material: Mjo_Treasure
+                Amount: 150
+          - Item: Expert_Magician1
+            Price: 300000
+            Materials:
+              - Material: Mjo_Energy
+                Amount: 200
+              - Material: Ein_DYNITE
+                Amount: 75
+              - Material: Mjo_Treasure
+                Amount: 150
+        Upgrades:
+          - Enchant: Expert_Archer1
+            Upgrade: Expert_Archer2
+            Price: 200000
+            Materials:
+              - Material: Mjo_Energy
+                Amount: 110
+              - Material: Ein_DYNITE
+                Amount: 10
+              - Material: Mjo_Treasure
+                Amount: 70
+          - Enchant: Expert_Fighter1
+            Upgrade: Expert_Fighter2
+            Price: 200000
+            Materials:
+              - Material: Mjo_Energy
+                Amount: 110
+              - Material: Ein_DYNITE
+                Amount: 10
+              - Material: Mjo_Treasure
+                Amount: 70
+          - Enchant: Expert_Magician1
+            Upgrade: Expert_Magician2
+            Price: 200000
+            Materials:
+              - Material: Mjo_Energy
+                Amount: 110
+              - Material: Ein_DYNITE
+                Amount: 10
+              - Material: Mjo_Treasure
+                Amount: 70
+          - Enchant: Expert_Archer2
+            Upgrade: Expert_Archer3
+            Price: 300000
+            Materials:
+              - Material: Mjo_Energy
+                Amount: 140
+              - Material: Ein_DYNITE
+                Amount: 30
+              - Material: Mjo_Treasure
+                Amount: 80
+          - Enchant: Expert_Fighter2
+            Upgrade: Expert_Fighter3
+            Price: 300000
+            Materials:
+              - Material: Mjo_Energy
+                Amount: 140
+              - Material: Ein_DYNITE
+                Amount: 30
+              - Material: Mjo_Treasure
+                Amount: 80
+          - Enchant: Expert_Magician2
+            Upgrade: Expert_Magician3
+            Price: 300000
+            Materials:
+              - Material: Mjo_Energy
+                Amount: 140
+              - Material: Ein_DYNITE
+                Amount: 30
+              - Material: Mjo_Treasure
+                Amount: 80
+          - Enchant: Expert_Archer3
+            Upgrade: Expert_Archer4
+            Price: 400000
+            Materials:
+              - Material: Mjo_Energy
+                Amount: 170
+              - Material: Ein_DYNITE
+                Amount: 50
+              - Material: Mjo_Treasure
+                Amount: 90
+          - Enchant: Expert_Fighter3
+            Upgrade: Expert_Fighter4
+            Price: 400000
+            Materials:
+              - Material: Mjo_Energy
+                Amount: 170
+              - Material: Ein_DYNITE
+                Amount: 50
+              - Material: Mjo_Treasure
+                Amount: 90
+          - Enchant: Expert_Magician3
+            Upgrade: Expert_Magician4
+            Price: 400000
+            Materials:
+              - Material: Mjo_Energy
+                Amount: 170
+              - Material: Ein_DYNITE
+                Amount: 50
+              - Material: Mjo_Treasure
+                Amount: 90
+          - Enchant: Expert_Archer4
+            Upgrade: Expert_Archer5
+            Price: 500000
+            Materials:
+              - Material: Mjo_Energy
+                Amount: 200
+              - Material: Ein_DYNITE
+                Amount: 70
+              - Material: Mjo_Treasure
+                Amount: 100
+          - Enchant: Expert_Fighter4
+            Upgrade: Expert_Fighter5
+            Price: 500000
+            Materials:
+              - Material: Mjo_Energy
+                Amount: 200
+              - Material: Ein_DYNITE
+                Amount: 70
+              - Material: Mjo_Treasure
+                Amount: 100
+          - Enchant: Expert_Magician4
+            Upgrade: Expert_Magician5
+            Price: 500000
+            Materials:
+              - Material: Mjo_Energy
+                Amount: 200
+              - Material: Ein_DYNITE
+                Amount: 70
+              - Material: Mjo_Treasure
+                Amount: 100
+  - Id: 64
+    TargetItems:
+      Mocadas_G_Sword: true
+      Mocadas_Spear: true
+      Mocadas_Stem: true
+      Mocadas_Spellbooks: true
+      Mocadas_C_Sword: true
+      Mocadas_Katar: true
+      Mocadas_Axe: true
+      Mocadas_Judgement: true
+      Mocadas_Knuckle: true
+      Mocadas_Rod: true
+      Mocadas_Bow: true
+      Mocadas_Instrument: true
+      Mocadas_Whip: true
+      Mocadas_Foxtail: true
+      Mocadas_Wand: true
+      Mocadas_Book: true
+      Mocadas_Knife: true
+      Mocadas_Hall: true
+      Mocadas_Launcher: true
+      Mocadas_Rifle: true
+      Mocadas_Shotgun: true
+      Mocadas_Gatling: true
+    Reset:
+      Chance: 100000
+      Price: 3000000
+      Materials:
+        - Material: Mocadas_Water
+          Amount: 150
+        - Material: Mocadas_Gold
+          Amount: 150
+    Order:
+      - Slot: 3
+      - Slot: 2
+    Slots:
+      - Slot: 3
+        Price: 300000
+        Materials:
+          - Material: Mocadas_Water
+            Amount: 50
+          - Material: Mocadas_Gold
+            Amount: 50
+        Enchants:
+          - Enchantgrade: 0
+            Items:
+              - Item: Bishop_Blessing1
+                Chance: 20000
+              - Item: Bishop_Blessing2
+                Chance: 20000
+              - Item: Bishop_Blessing3
+                Chance: 20000
+              - Item: Bishop_Blessing4
+                Chance: 20000
+              - Item: Bishop_Blessing5
+                Chance: 20000
+          - Enchantgrade: 1
+            Items:
+              - Item: Bishop_Blessing1
+                Chance: 20000
+              - Item: Bishop_Blessing2
+                Chance: 20000
+              - Item: Bishop_Blessing3
+                Chance: 20000
+              - Item: Bishop_Blessing4
+                Chance: 20000
+              - Item: Bishop_Blessing5
+                Chance: 20000
+          - Enchantgrade: 2
+            Items:
+              - Item: Bishop_Blessing1
+                Chance: 20000
+              - Item: Bishop_Blessing2
+                Chance: 20000
+              - Item: Bishop_Blessing3
+                Chance: 20000
+              - Item: Bishop_Blessing4
+                Chance: 20000
+              - Item: Bishop_Blessing5
+                Chance: 20000
+          - Enchantgrade: 3
+            Items:
+              - Item: Bishop_Blessing1
+                Chance: 20000
+              - Item: Bishop_Blessing2
+                Chance: 20000
+              - Item: Bishop_Blessing3
+                Chance: 20000
+              - Item: Bishop_Blessing4
+                Chance: 20000
+              - Item: Bishop_Blessing5
+                Chance: 20000
+          - Enchantgrade: 4
+            Items:
+              - Item: Bishop_Blessing1
+                Chance: 20000
+              - Item: Bishop_Blessing2
+                Chance: 20000
+              - Item: Bishop_Blessing3
+                Chance: 20000
+              - Item: Bishop_Blessing4
+                Chance: 20000
+              - Item: Bishop_Blessing5
+                Chance: 20000
+        PerfectEnchants:
+          - Item: Bishop_Blessing1
+            Price: 3000000
+            Materials:
+              - Material: Mocadas_Water
+                Amount: 200
+              - Material: Mocadas_Gold
+                Amount: 200
+          - Item: Bishop_Blessing2
+            Price: 3000000
+            Materials:
+              - Material: Mocadas_Water
+                Amount: 200
+              - Material: Mocadas_Gold
+                Amount: 200
+          - Item: Bishop_Blessing3
+            Price: 3000000
+            Materials:
+              - Material: Mocadas_Water
+                Amount: 200
+              - Material: Mocadas_Gold
+                Amount: 200
+          - Item: Bishop_Blessing4
+            Price: 3000000
+            Materials:
+              - Material: Mocadas_Water
+                Amount: 200
+              - Material: Mocadas_Gold
+                Amount: 200
+          - Item: Bishop_Blessing5
+            Price: 3000000
+            Materials:
+              - Material: Mocadas_Water
+                Amount: 200
+              - Material: Mocadas_Gold
+                Amount: 200
+      - Slot: 2
+        PerfectEnchants:
+          - Item: Warped_Spell1
+            Price: 5000000
+            Materials:
+              - Material: Mocadas_Water
+                Amount: 300
+              - Material: Mocadas_Gold
+                Amount: 300
+          - Item: Warped_Spell2
+            Price: 5000000
+            Materials:
+              - Material: Mocadas_Water
+                Amount: 300
+              - Material: Mocadas_Gold
+                Amount: 300
+          - Item: Warped_Spell3
+            Price: 5000000
+            Materials:
+              - Material: Mocadas_Water
+                Amount: 300
+              - Material: Mocadas_Gold
+                Amount: 300
   - Id: 65
     TargetItems:
       Punish_Hall: true

+ 88 - 0
db/re/item_reform.yml

@@ -6892,3 +6892,91 @@ Body:
 #          Amount: 150
 #       ResultItem: Blessed_Knife_LT
 #       ChangeRefine: 1
+  - Item: Dimmen_A_Reform
+    BaseItems:
+      - BaseItem: Under_Seal_D_Key
+        MaximumRefine: 0
+        CardsAllowed: false
+        Materials:
+          - Material: Dimmension_Jewel
+            Amount: 1
+          - Material: Dimmension_Jewelry
+            Amount: 1
+          - Material: Herosria_Jewel
+            Amount: 1
+          - Material: Creed_Of_Herosria
+            Amount: 1
+        ResultItem: Dimmension_W_Key
+      - BaseItem: Under_Seal_D_Padlock
+        MaximumRefine: 0
+        CardsAllowed: false
+        Materials:
+          - Material: Dimmension_Jewel
+            Amount: 1
+          - Material: Dimmension_Jewelry
+            Amount: 1
+          - Material: Herosria_Jewel
+            Amount: 1
+          - Material: Justice_Of_Herosria
+            Amount: 1
+        ResultItem: Dimmension_W_Padlock
+      - BaseItem: Under_Seal_D_keyring
+        MaximumRefine: 0
+        CardsAllowed: false
+        Materials:
+          - Material: Dimmension_Jewel
+            Amount: 1
+          - Material: Dimmension_Jewelry
+            Amount: 1
+          - Material: Herosria_Jewel
+            Amount: 1
+          - Material: Glory_Of_Herosria
+            Amount: 1
+        ResultItem: Dimmension_W_Keyring
+  - Item: Dimmen_A_Refine
+    BaseItems:
+      - BaseItem: Dimmension_W_Key
+        MinimumRefine: 9
+        MaximumRefine: 11
+        CardsAllowed: false
+        Materials:
+          - Material: Heros_Gold_Bar
+            Amount: 50
+          - Material: Heros_Silver_Bar
+            Amount: 100
+          - Material: Heros_Copper_Bar
+            Amount: 200
+          - Material: Heros_Iron_Bar
+            Amount: 300
+        ResultItem: Dimmension_W_Key
+        ChangeRefine: 1
+      - BaseItem: Dimmension_W_Padlock
+        MinimumRefine: 9
+        MaximumRefine: 11
+        CardsAllowed: false
+        Materials:
+          - Material: Heros_Gold_Bar
+            Amount: 50
+          - Material: Heros_Silver_Bar
+            Amount: 100
+          - Material: Heros_Copper_Bar
+            Amount: 200
+          - Material: Heros_Iron_Bar
+            Amount: 300
+        ResultItem: Dimmension_W_Padlock
+        ChangeRefine: 1
+      - BaseItem: Dimmension_W_Keyring
+        MinimumRefine: 9
+        MaximumRefine: 11
+        CardsAllowed: false
+        Materials:
+          - Material: Heros_Gold_Bar
+            Amount: 50
+          - Material: Heros_Silver_Bar
+            Amount: 100
+          - Material: Heros_Copper_Bar
+            Amount: 200
+          - Material: Heros_Iron_Bar
+            Amount: 300
+        ResultItem: Dimmension_W_Keyring
+        ChangeRefine: 1

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 953 - 14
db/re/mob_db.yml


+ 63 - 0
db/re/mob_skill_db.txt

@@ -13041,6 +13041,69 @@
 20837,ABR_INFINITY@ABR_INFINITY_BUSTER,chase,8605,1,10000,500,5000,yes,target,always,0,,,,,,,
 20837,ABR_INFINITY@ABR_INFINITY_BUSTER,attack,8605,1,10000,500,5000,yes,target,always,0,,,,,,,
 
+// sp_rudus4
+20929,GIANT_CAPUT@NPC_EMOTION,chase,197,1,1000,0,30000,yes,self,always,0,,,,,,19,
+20929,GIANT_CAPUT@NPC_EMOTION,idle,197,1,8000,0,30000,yes,self,always,0,,,,,,44,
+20929,GIANT_CAPUT@NPC_COMBOATTACK,attack,171,2,1000,500,15000,no,target,always,0,,,,,,,
+20929,GIANT_CAPUT@LK_SPIRALPIERCE,chase,397,3,3000,500,30000,no,target,always,0,,,,,,,
+20929,GIANT_CAPUT@NPC_DARKNESSATTACK,chase,190,3,5000,500,10000,no,target,always,0,,,,,,,
+20929,GIANT_CAPUT@NPC_DARKNESSATTACK,attack,190,3,5000,500,10000,no,target,always,0,,,,,,,
+20929,GIANT_CAPUT@NPC_HELMBRAKE,attack,345,5,3000,0,30000,yes,target,always,0,,,,,,,
+20929,GIANT_CAPUT@NPC_PULSESTRIKE,attack,661,3,2000,1000,60000,no,self,always,0,,,,,,,
+20929,GIANT_CAPUT@NPC_POWERUP,attack,349,3,5000,0,60000,yes,self,myhpltmaxrate,30,,,,,,,
+20929,GIANT_CAPUT@NPC_POWERUP,chase,349,3,5000,0,60000,yes,self,myhpltmaxrate,30,,,,,,,
+20930,DOLORIAN@AL_TELEPORT,chase,26,1,3000,0,300000,yes,self,mobnearbygt,23,,,,,,12,
+20930,DOLORIAN@NPC_EMOTION,chase,197,1,500,0,30000,yes,self,always,0,,,,,,19,
+20930,DOLORIAN@NPC_CRITICALWOUND,attack,673,1,2000,0,60000,yes,target,always,0,,,,,,,
+20930,DOLORIAN@NPC_EMOTION,idle,197,1,2000,0,30000,yes,self,always,0,,,,,,44,
+20930,DOLORIAN@NPC_BLOODDRAIN,chase,199,1,3000,0,30000,yes,target,always,0,,,,,,,
+20930,DOLORIAN@NPC_STONESKIN,chase,675,3,3000,500,30000,no,self,myhpltmaxrate,30,,,,,,47,
+20930,DOLORIAN@NPC_POISONATTACK,attack,188,3,2000,500,10000,no,target,always,0,,,,,,,
+20931,PLAGARION@AL_TELEPORT,chase,26,1,3000,0,300000,yes,self,mobnearbygt,23,,,,,,12,
+20931,PLAGARION@NPC_EMOTION,chase,197,1,500,0,30000,yes,self,always,0,,,,,,19,
+20931,PLAGARION@NPC_EMOTION,idle,197,1,2000,0,30000,yes,self,always,0,,,,,,44,
+20931,PLAGARION@NPC_CRITICALWOUND,attack,673,3,2000,0,60000,yes,target,always,0,,,,,,,
+20931,PLAGARION@MG_FIREBALL,chase,17,3,3000,500,30000,no,target,always,0,,,,,,,
+20931,PLAGARION@NPC_FIREATTACK,chase,186,2,2000,500,30000,no,target,always,0,,,,,,,
+20931,PLAGARION@NPC_DARKSTRIKE,chase,340,5,3000,500,30000,no,target,always,0,,,,,,,
+20932,DEADRE@AL_TELEPORT,chase,26,1,3000,0,300000,yes,self,mobnearbygt,23,,,,,,12,
+20932,DEADRE@NPC_EMOTION,chase,197,1,500,0,30000,yes,self,always,0,,,,,,19,
+20932,DEADRE@AL_HEAL,idle,28,9,7000,500,5000,no,friend,myhpltmaxrate,50,,,,,,3,
+20932,DEADRE@NPC_EMOTION,idle,197,1,2000,0,30000,yes,self,always,0,,,,,,14,
+20932,DEADRE@AL_HEAL,attack,28,9,3000,500,15000,no,friend,always,0,,,,,,3,
+20932,DEADRE@NPC_DARKCROSS,attack,338,5,3000,500,15000,no,target,always,0,,,,,,,
+20932,DEADRE@AL_HOLYLIGHT,chase,156,1,3000,300,15000,no,target,always,0,,,,,,,
+20932,DEADRE@NPC_HOLYATTACK,chase,189,3,4000,500,15000,no,target,always,0,,,,,,,
+20932,DEADRE@AL_HEAL,idle,28,9,10000,700,3000,no,friend,friendhpltmaxrate,50,,,,,,,
+20933,VENEDI@NPC_EMOTION,chase,197,1,500,0,30000,yes,self,always,0,,,,,,19,
+20933,VENEDI@NPC_EMOTION,idle,197,1,2000,0,30000,yes,self,always,0,,,,,,44,
+20933,VENEDI@NPC_BLINDATTACK,chase,177,3,3000,500,30000,no,target,always,0,,,,,,,
+20933,VENEDI@NPC_POISONATTACK,attack,188,5,1000,0,5000,yes,target,always,0,,,,,,,
+20933,VENEDI@NPC_POISON_BUSTER,attack,742,1,2000,1500,30000,no,self,always,0,,,,,,,
+20933,VENEDI@NPC_CLOUD_KILL,attack,739,3,2000,500,15000,no,target,always,0,,,,,,,
+20934,R001_BESTIA@AL_TELEPORT,idle,26,1,10000,0,0,yes,self,rudeattacked,0,,,,,,,
+20934,R001_BESTIA@WZ_METEOR,idle,83,9,10000,0,5000,no,target,longrangeattacked,0,,,,,,,
+20934,R001_BESTIA@AL_PNEUMA,idle,25,1,10000,0,1000,no,self,longrangeattacked,0,,,,,,,
+20934,R001_BESTIA@AL_HEAL,idle,28,11,10000,0,3000,no,friend,myhpltmaxrate,50,,,,,,,
+20934,R001_BESTIA@AL_HEAL,idle,28,5,10000,0,3000,no,friend,myhpltmaxrate,99,,,,,,,
+20934,R001_BESTIA@NPC_SUMMONSLAVE,idle,196,1,10000,1000,30000,no,self,slavele,1,,,,,,,
+20934,R001_BESTIA@NPC_CALLSLAVE,idle,352,1,10000,0,30000,no,self,always,0,,,,,,,
+20934,R001_BESTIA@NPC_FIREBREATH,chase,654,10,2000,500,5000,no,target,myhpltmaxrate,80,,,,,,,
+// 20934,R001_BESTIA@NPC_EARTHQUAKE_K,chase,750,5,10000,500,20000,no,target,myhpltmaxrate,40,,,,,,,
+20934,R001_BESTIA@NPC_EARTHQUAKE,chase,653,5,10000,500,20000,no,target,myhpltmaxrate,40,,,,,,,
+20934,R001_BESTIA@NPC_CALLSLAVE,attack,352,1,10000,0,30000,no,self,always,0,,,,,,,
+20934,R001_BESTIA@NPC_SUMMONSLAVE,attack,196,1,10000,700,10000,no,self,slavele,2,,,,,,,
+20934,R001_BESTIA@ST_FULLSTRIP,attack,476,5,2000,0,5000,no,target,always,0,,,,,,,
+20934,R001_BESTIA@NPC_CRITICALWOUND,attack,673,3,2000,0,30000,no,target,always,0,,,,,,,
+20934,R001_BESTIA@NPC_FIREBREATH,attack,654,10,2000,500,5000,no,target,myhpltmaxrate,80,,,,,,,
+20934,R001_BESTIA@WZ_METEOR,attack,83,11,2000,500,5000,no,target,myhpltmaxrate,90,,,,,,,
+20934,R001_BESTIA@KN_TWOHANDQUICKEN,attack,60,9,10000,0,100000,no,self,myhpltmaxrate,30,,,,,,,
+// 20934,R001_BESTIA@NPC_EARTHQUAKE_K,attack,750,5,10000,500,20000,no,target,myhpltmaxrate,40,,,,,,,
+20934,R001_BESTIA@NPC_EARTHQUAKE,attack,653,5,10000,500,20000,no,target,myhpltmaxrate,40,,,,,,,
+20934,R001_BESTIA@NPC_PULSESTRIKE,attack,661,5,10000,0,15000,no,self,myhpltmaxrate,60,,,,,,,
+20934,R001_BESTIA@NPC_DRAGONFEAR,attack,659,5,10000,0,10000,no,self,always,0,,,,,,,
+// 20934,R001_BESTIA@NPC_DAMAGE_HEAL,attack,753,1,3000,1500,40000,no,target,myhpltmaxrate,20,,,,,,,
+
 // Training Ground outside Prontera
 21064,S_DUMMY_100_SMALL@NPC_ALLHEAL,attack,687,1,10000,0,500,no,self,myhpltmaxrate,90,,,,,,,
 21064,S_DUMMY_100_SMALL@NPC_ALLHEAL,idle,687,1,10000,0,500,no,self,myhpltmaxrate,90,,,,,,,

+ 26 - 2
db/re/quest_db.yml

@@ -10420,15 +10420,37 @@ Body:
   - Id: 16439
     Title: Award Garden-Waiting
     TimeLimit: 4h
+  - Id: 16521
+    Title: 4th floor of Rudus, an organic field for waste specimens
+  - Id: 16522
+    Title: 4th floor of Rudus, an organic field for waste specimens
   - Id: 16523
-    Title: The solitude of the abyss
+    Title: Abyssal Solitude - Standby
     TimeLimit: 4h
+  - Id: 16524
+    Title: Abyssal Solitude
+    Targets:
+      - Mob: DEADRE
+        Count: 10
+      - Mob: DOLORIAN
+        Count: 10
+      - Mob: PLAGARION
+        Count: 10
   - Id: 16525
-    Title: Transformation and development - Standby
+    Title: Transformation and Evolution - Standby
     TimeLimit: 4h
+  - Id: 16526
+    Title: Transformation and Evolution
+    Targets:
+      - Mob: GIANT_CAPUT
+        Count: 15
+      - Mob: VENEDI
+        Count: 15
   - Id: 16527
     Title: Data backup - Waiting
     TimeLimit: 4h
+  - Id: 16528
+    Title: Data Backup
   - Id: 16530
     Title: The ground and fire
     TimeLimit: 4h
@@ -10444,6 +10466,8 @@ Body:
   - Id: 16538
     Title: Natural change - Waiting
     TimeLimit: 4h
+  - Id: 16540
+    Title: Data Backup
   - Id: 16546
     Title: Farm Worker
   - Id: 16547

+ 5 - 2
doc/atcommands.txt

@@ -1419,6 +1419,7 @@ This will also send a packet to clients causing them to close.
 @reloadstatusdb
 @reloadachievementdb
 @reloadattendancedb
+@reloadbarterdb
 
 Reloads a database or configuration file.
 
@@ -1427,10 +1428,11 @@ Databases:
 -- itemdb: Item Database
 -- mobdb: Monster Database
 -- questdb: Quest Database
--- script: NPC Scripts
+-- script: NPC Scripts and Barter Database
 -- skilldb: Skill Database
 -- achievementdb: Achievement Database
 -- attendancedb: Attendance Database
+-- barterdb: Barter Database
 
 Configuration files:
 -- atcommand: Atcommand Settings
@@ -1450,11 +1452,12 @@ Affected files:
 -- msgconf: atcommands.yml
 -- pcdb: statpoint.yml, job_exp.yml, skill_tree.yml, attr_fix.yml, job_stats.yml, job_basepoints.yml, level_penalty.yml
 -- questdb: quest_db.yml
--- script: /npc/*.txt, /npc/*.conf
+-- script: /npc/*.txt, /npc/*.conf, /npc/barters.yml
 -- skilldb: skill_db.yml, skill_nocast_db.txt, skill_changematerial_db.txt, skill_damage_db.txt, abra_db.yml, create_arrow_db.yml, produce_db.txt, spellbook_db.yml, magicmushroom_db.yml
 -- statusdb: attr_fix.yml, size_fix.yml, refine.yml
 -- achievementdb: achievement_db.yml
 -- attendancedb: attendance.yml
+-- barterdb: /npc/barters.yml
 
 Restriction:
 	- Used from 'atcommand' or 'useatcmd'. For @reload & @reloadscript

+ 15 - 4
doc/script_commands.txt

@@ -2731,7 +2731,7 @@ See 'getequipid' for a full list of valid equipment slots.
 
 Given the database ID number of an item, this function will return the text
 stored in the 'Name' field in item_db_*.yml for text version
-or 'name_english' field for SQL version.
+or 'name_english' field for SQL version. The function returns "null" if the item doesn't exist.
 
 ---------------------------------------
 
@@ -9670,9 +9670,13 @@ returns an empty string instead.
 
 *instance_id({<instance mode>})
 
-Returns the unique instance ID of the given mode. By default it returns the
-attached script instance. If <instance mode> is provided then the instance
-of the currently attached player is used. If that fails, the function will return 0.
+Returns the unique instance ID of the given mode.
+
+By default (no parameter given) the command returns the instance ID from the attached NPC.
+If <instance mode> is provided the instance ID of the currently attached player is returned.
+If that fails, the function will return 0.
+
+Please note that the command always requires the parameter <instance mode> to get the instance ID of an attached player!
 
 Instance Mode options:
  IM_CHAR:	Attached to character.
@@ -9680,6 +9684,13 @@ Instance Mode options:
  IM_GUILD:	Attached to character's guild.
  IM_CLAN:	Attached to character's clan.
 
+Examples:
+	// Example with an attached player :
+	npctalk "The current instance ID (mode party) from the attached player is : " + instance_id(IM_PARTY);
+
+	// Example with an attached NPC on an instance map :
+	npctalk "The current instance ID from the attached NPC is : " + instance_id();
+
 ---------------------------------------
 
 *instance_warpall "<map name>",<x>,<y>{,<instance id>};

+ 12 - 0
doc/yaml/db/item_cash.yml

@@ -0,0 +1,12 @@
+###########################################################################
+# Item Cash Database
+###########################################################################
+#
+# Item Cash Settings
+#
+###########################################################################
+# - Tab                     Cash shop tab. Available tabs are New, Hot, Limited, Rental, Permanent, Scrolls, Consumables, Other, Sale.
+#   Items:                  List of possible items.
+#     - Item                Item name.
+#       Price               Item cost in cash points (#CASHPOINTS).
+###########################################################################

+ 7 - 0
npc/re/mobs/dungeons/sp_rudus.txt

@@ -22,3 +22,10 @@ sp_rudus3,0,0	monster	Mutant Dolor	20362,60,5000
 sp_rudus3,0,0	monster	Mutant Plaga	20360,53,5000
 sp_rudus3,0,0	monster	Mutant Venenum	20364,65,5000
 sp_rudus3,0,0	monster	Mutant Twin Caput	20366,65,5000
+
+sp_rudus4	monster	Giant Caput	GIANT_CAPUT,75
+sp_rudus4	monster	Dolorian	DOLORIAN,75
+sp_rudus4	monster	Deadre	DEADRE,75
+sp_rudus4	monster	Venedi	VENEDI,75
+sp_rudus4	monster	Plagarion	PLAGARION,75
+sp_rudus4	boss_monster	R001-Bestia	R001_BESTIA,1,21600000,600000,0

+ 480 - 0
npc/re/quests/quests_dungeons_200.txt

@@ -0,0 +1,480 @@
+//===== rAthena Script =======================================
+//= New dungeons
+//===== Description: =========================================
+//= [Walkthrough Conversion]
+//===== Changelog: ===========================================
+//= 1.0 Added sp_rudus4 quests. [Capuche]
+//============================================================
+
+// sp_rudus4
+pub_cat,94,43,5	script	Yulma#rudus4	4_F_ANYA,{
+	if (BaseLevel < 200) {
+		mes "[Yulma]";
+		mes "Uh... who are you?";
+		mes "Sorry, I'm not good at remembering people's faces! But I think you are not good enough to ask about my work here.";
+		next;
+		mes " - This is a quest that can be performed at level 200 or higher - ";
+		close;
+	}
+	switch( isbegin_quest(16521) ) {
+	case 0:
+		emotion ET_SMILE;
+		mes "[Yulma]";
+		mes "Oh, " + strcharinfo(0) + ". Are you here for a drink?";
+		mes "I just had a business, and it went well.";
+		next;
+		select( "What is it for?" );
+		mes "[Yulma]";
+		mes "You know that Secret Wing and our rebellion have been tracking the traces of Illusion's illegal experiments, right?";
+		next;
+		mes "[Yulma]";
+		mes "In the meantime, I found a way to the 4th floor of Rudus.";
+		next;
+		switch( select( "Well, I know.", "Where was Rudus?" ) ) {
+		case 1:
+			emotion ET_DELIGHT;
+			mes "[Yulma]";
+			mes "Hehe, it's good to have a good conversation with " + strcharinfo(0) + ".";
+			break;
+		case 2:
+			emotion ET_SURPRISE;
+			mes "[Yulma]";
+			mes "What, how many times did you forget?";
+			mes "It was the place where Illusion abandoned the failed works that were created after the experiment.";
+			next;
+			mes "[Yulma]";
+			mes "Remember Morning? Where the little boy stays.";
+			mes "He is on the first floor, and Rudus itself exists on the second and third floors.";
+			break;
+		}
+		next;
+		mes "[Yulma]";
+		mes "By the way, the 4th floor of Rudus is quite amazing...";
+		mes "Somehow, among the test subjects abandoned in Rudus, only particularly strong ones seem to be gathered.";
+		next;
+		mes "[Yulma]";
+		mes "Researchers of the Secret Wing say that after fierce competition among the subjects, the strongest subjects seem to have settled there.";
+		next;
+		mes "[Yulma]";
+		mes "Well, actually, I'm not sure how complicated it is!";
+		mes "It seems that the Secret Wing researchers need to study the entities there.";
+		next;
+		mes "[Yulma]";
+		mes "So I was looking for someone to help me.";
+		mes "To be precise you, " + strcharinfo(0) + "!";
+		next;
+		mes "[Yulma]";
+		mes "Do you need a job? I know everything~";
+		mes "If you have an idea, go to the 3rd floor of Rudus.";
+		next;
+		mes "[Yulma]";
+		mes "<NAVI>[Pion]<INFO>sp_rudus3,380,42,0,101,0</INFO></NAVI> is in control of the path leading down to the fourth floor.";
+		mes "Go and tell him I sent you, and find out if it's worth it to come!";
+		setquest 16521;
+		close2;
+		navigateto("sp_rudus3",380,42);
+		end;
+	case 1:
+		mes "[Yulma]";
+		mes "Do you need a job? I know everything~";
+		mes "If you have an idea, go to the 3rd floor of Rudus.";
+		next;
+		mes "[Yulma]";
+		mes "<NAVI>[Pion]<INFO>sp_rudus3,380,42,0,101,0</INFO></NAVI> is in control of the path leading down to the fourth floor.";
+		mes "Go and tell him I sent you, and find out if it's worth it to come!";
+		close;
+	case 2:
+		break;
+	}
+	if (isbegin_quest(16522) == 1) {
+		mes "[Yulma]";
+		mes "Oh, you went down to the 4th floor of Rudus. How was it?";
+		mes "Is Pian doing well? Did the subjects feel a bit worthy of a fight?";
+		next;
+		mes "[Yulma]";
+		mes "Then help me study the people from the Secret Wing.";
+		mes "I was looking for someone to hunt them, because I was not enough to catch and research monsters myself.";
+		next;
+		select( "Ask if I can just hunt" );
+		emotion ET_KEK;
+		mes "[Yulma]";
+		mes "Oh, I almost forgot.";
+		mes "If you attach this chip somewhere on your body and hunt them, researchers can receive data.";
+		next;
+		mes "[Yulma]";
+		mes "New material, eco-friendly, non-toxic, safe material, so you don't have to worry about it.";
+		mes "Where would you like to hang it?";
+		next;
+		select( "Earlobe", "Back neck", "Back of hand", "Forehead" );
+		mes "[Yulma]";
+		mes "Okay, I'll attach it well so it doesn't bother you.";
+		mes "Okay, that's it then...";
+		completequest 16522;
+		next;
+	}
+	mes "[Yulma]";
+	mes "Please take good care of me today!";
+	mes "So, what kind of research would you like to help with today?";
+	next;
+	switch( select("Abyssal Solitude", "Transformation and Evolution", "Data Backup", "Don't help" ) ) {
+	case 1:
+		switch( checkquest(16523,PLAYTIME) ) {
+		case -1:
+			break;
+		case 0:
+		case 1:
+			mes "[Yulma]";
+			mes "Secret Wing researchers are resting.";
+			mes "So, " + strcharinfo(0) + ", how about coming back after a little rest?";
+			next;
+			mes "[Yulma]";
+			mes "The 4th floor of Rudus is not a comfortable place to go in and out easily.";
+			mes "Let's get some rest and work fully prepared!";
+			close;
+		case 2:
+			erasequest 16523;
+			break;
+		}
+		switch( checkquest(16524,HUNTING) ) {
+		case -1:
+			mes "[Yulma]";
+			mes "A place where only strong individuals survive and bite weaker ones...";
+			mes "Like a deep, dark pit of solitude.";
+			next;
+			emotion ET_SWEAT;
+			mes "[Yulma]";
+			mes "Well, I don't know about that!";
+			mes "My job is to deliver the requests of researchers.";
+			next;
+			mes "[Yulma]";
+			mes "Researchers want to know how the subjects of Rudus differ from those distributed on the 4th floor.";
+			next;
+			emotion ET_PROFUSELY_SWAT;
+			mes "[Yulma]";
+			mes "How much more has it lost it's original form, and whether it has changed itself after the experiment.";
+			mes "<FONT SIZE = 9>Um... these researchers look a little dangerous too? I think that's how all researchers are, to a person like me.</FONT>";
+			next;
+			mes "[Yulma]";
+			mes "Anyway, you want to collect <FONT COLOR = 0000CD>Dolorian, Plagarion, and Deadre</FONT> data.";
+			mes "What if we hunt 10 individuals each?";
+			next;
+			if (select( "I'll come hunting", "I refuse" ) == 2) {
+				mes "[Yulma]";
+				mes "We're already comrades on the same boat~?";
+				mes "Isn't that right, comrade? Think again!";
+				close;
+			}
+			mes "[Yulma]";
+			mes "Okay, if you go hunting and from hunting, data will be gathered.";
+			mes "And my allowance will be accumulating!";
+			setquest 16524;
+			close;
+		case 0:
+		case 1:
+			mes "[Yulma]";
+			mes "Secret Wing researchers want to collect <FONT COLOR = 0000CD>Dolorian, Plagarion, and Deadre</FONT> data.";
+			mes "What if we hunt 10 individuals each?";
+			close;
+		case 2:
+			mes "[Yulma]";
+			mes "Today's hunting seems to be successful!";
+			mes "How did you know, " + strcharinfo(0) + ", the chip I put on you worked today.";
+			next;
+			emotion ET_DELIGHT;
+			mes "[Yulma]";
+			mes "I see it every time, but every time it's amazing, how the chip works.";
+			mes "Well, I should pay you for the work you did today.";
+			next;
+			mes "[Yulma]";
+			mes "Here you are!";
+			mes "Then since you've finished your work, take a good rest. Please take care of me tomorrow as well~";
+			erasequest 16524;
+			setquest 16523;
+			getexp 15915940,11141160;	// TODO: the amount has been changed with the monster update
+			close;
+		}
+		end;
+	case 2:
+		switch( checkquest(16525,PLAYTIME) ) {
+		case -1:
+			break;
+		case 0:
+		case 1:
+			mes "[Yulma]";
+			mes "Secret Wing researchers left work already.";
+			mes "So, " + strcharinfo(0) + ", how about coming back after a little rest?";
+			next;
+			mes "[Yulma]";
+			mes "The 4th floor of Rudus is not a comfortable place to go in and out easily.";
+			mes "You too, get a good rest, and let's work fully prepared!";
+			close;
+		case 2:
+			erasequest 16525;
+			break;
+		}
+		switch( checkquest(16526,HUNTING) ) {
+		case -1:
+			mes "[Yulma]";
+			mes "The 4th-floor entities Giant Caput and Venedi are greatly altered from the other-floor entities";
+			mes "to become gigantic, or to harbor more venom...";
+			next;
+			emotion ET_PROFUSELY_SWAT;
+			mes "[Yulma]";
+			mes "Researchers seem to be very interested in these guys.";
+			mes "That's why I ask you to hunt them.";
+			mes "<FONT SIZE = 9>Um...why don't we do it? There must be something complicated at the upper level. I don't know.</FONT>";
+			next;
+			mes "[Yulma]";
+			mes "Can you hunt 15 <FONT COLOR = 0000CD>Giant Caput and Venedi</FONT> each?";
+			mes "Data will be collected automatically, so you just need to focus on hunting.";
+			next;
+			if (select( "I'll hunt them", "I refuse" ) == 2) {
+				mes "[Yulma]";
+				mes "We're already comrades on the same boat~?";
+				mes "Isn't that right, comrade? Think again!";
+				close;
+			}
+			mes "[Yulma]";
+			mes "Okay, go and hunt hard!";
+			mes "My bonus depends on your performance!";
+			setquest 16526;
+			close;
+		case 0:
+		case 1:
+			mes "[Yulma]";
+			mes "Can you hunt 15 <FONT COLOR = 0000CD>Giant Caput and Venedi</FONT> each?";
+			mes "Data will be collected automatically, so you just need to focus on hunting.";
+			close;
+		case 2:
+			mes "[Yulma]";
+			mes "Have you been well? You came back from hunting safely.";
+			mes "Please tell me that the researchers worked hard today.";
+			next;
+			mes "[Yulma]";
+			mes "Looks like good data has been gathered.";
+			mes "Then I should pay you for the work you did today.";
+			next;
+			mes "[Yulma]";
+			mes "Here you are!";
+			mes "You worked hard today, so get a good rest and come back. Let's work hard tomorrow!";
+			erasequest 16526;
+			setquest 16525;
+			getexp 15176970,11001900;	// TODO: the amount has been changed with the monster update
+			close;
+		}
+		end;
+	case 3:
+		switch( checkquest(16527,PLAYTIME) ) {
+		case -1:
+			break;
+		case 0:
+		case 1:
+			mes "[Yulma]";
+			mes "The researcher said he's withdrawn now. Chip maintenance is enough once a day!";
+			mes "So, " + strcharinfo(0) + ", how about coming back after a little rest?";
+			next;
+			mes "[Yulma]";
+			mes "The 4th floor of Rudus is not a comfortable place to go in and out easily.";
+			mes "Let's get some rest and work fully prepared!";
+			close;
+		case 2:
+			erasequest 16527;
+			break;
+		}
+		switch( isbegin_quest(16528) ) {
+		case 0:
+			mes "[Yulma]";
+			mes "Oh, by the way, are the chips sticking well?";
+			mes "It doesn't last that long...";
+			next;
+			mes "[Yulma]";
+			mes "And I have to back up and format the data I have collected so that I can receive new data, but I don't have the talent to do that...";
+			next;
+			mes "[Yulma]";
+			mes "I'll introduce you to a researcher who is currently in the field, would you like to go directly?";
+			mes "He is probably disguised to observe monsters, but I'll give you the location, so go check it out!";
+			next;
+			if (select( "Look for him", "Don't look for him" ) == 2) {
+				mes "[Yulma]";
+				mes "Isn't it frustrating to only work with me?";
+				mes "It wouldn't be a bad idea to meet the researchers working in the field...";
+				close;
+			}
+			mes "[Yulma]";
+			mes "Yeah! Isn't it frustrating to only meet with me to get work delivered?";
+			next;
+			mes "[Yulma]";
+			mes "I'm on my way to see him, so if you have any questions, you can ask him directly!";
+			mes "I heard that <NAVI>[Researcher]<INFO>sp_rudus4,192,204,0,101,0</INFO></NAVI> mostly burrows around the center of the 4th floor of Rudus.";
+			setquest 16528;
+			close;
+		case 1:
+			mes "[Yulma]";
+			mes "I'm on my way to meet the Secret Wing researcher, so if you have any questions, you can ask him directly!";
+			mes "I heard that <NAVI>[Researcher]<INFO>sp_rudus4,192,204,0,101,0</INFO></NAVI> mostly burrows around the center of the 4th floor of Rudus.";
+			close;
+		case 2:
+			mes "[Yulma]";
+			mes "Have you been there? Did you talk a bit?";
+			mes "Is the researcher hiding well?";
+			next;
+			mes "[Yulma]";
+			mes "He's known for hiding very well, even though he doesn't have the skills to hide.";
+			mes "That's why you're out in the field.";
+			next;
+			mes "[Yulma]";
+			mes "Because this is also a job... Since I safely replaced him, the superiors asked me to take care of the allowance.";
+			next;
+			mes "[Yulma]";
+			mes "Well, it's good that you came back to me too!";
+			mes "Then have a good day and see you tomorrow!";
+			erasequest 16528;
+			erasequest 16540;
+			setquest 16527;
+			getexp 15000000,11000000;
+			close;
+		}
+		end;
+	case 4:
+		mes "[Yulma]";
+		mes "Eh, won't you?";
+		mes "I was eagerly waiting for " + strcharinfo(0) + "!";
+		close;
+	}
+	end;
+
+OnInit:
+	questinfo( QTYPE_QUEST2, QMARK_YELLOW, "checkquest(16524,HUNTING) == 2" );
+	questinfo( QTYPE_QUEST2, QMARK_YELLOW, "checkquest(16526,HUNTING) == 2" );
+	questinfo( QTYPE_QUEST2, QMARK_YELLOW, "isbegin_quest(16528) == 2" );
+
+	questinfo( QTYPE_DAILYQUEST, QMARK_YELLOW, "checkquest(16523,PLAYTIME) == 2" );
+	questinfo( QTYPE_DAILYQUEST, QMARK_YELLOW, "checkquest(16525,PLAYTIME) == 2" );
+	questinfo( QTYPE_DAILYQUEST, QMARK_YELLOW, "checkquest(16527,PLAYTIME) == 2" );
+
+	questinfo( QTYPE_DAILYQUEST, QMARK_YELLOW, "BaseLevel >= 200 && (checkquest(16524,HUNTING) == -1 && checkquest(16523,PLAYTIME) == -1)" );
+	questinfo( QTYPE_DAILYQUEST, QMARK_YELLOW, "BaseLevel >= 200 && (checkquest(16526,HUNTING) == -1 && checkquest(16525,PLAYTIME) == -1)" );
+	questinfo( QTYPE_DAILYQUEST, QMARK_YELLOW, "BaseLevel >= 200 && (isbegin_quest(16528) == 0 && checkquest(16527,PLAYTIME) == -1)" );
+	end;
+}
+
+sp_rudus3,380,42,3	script	Pion#rudus4	4_M_REBELLION3,{
+	switch( isbegin_quest(16521) ) {
+	case 0:
+		emotion ET_SLEEPY;
+		mes "[Pion]";
+		mes "Zzz... Whoa!";
+		mes "Excuse me, sir! No more work!";
+		next;
+		mes "[Pion]";
+		mes "Seup, Ha-am... Down this slope is an access control area.";
+		mes "People are not allowed to enter.";
+		next;
+		select("I'm not an ordinary person." );
+		mes "[Pion]";
+		mes "Yes? Ah... ah no! " + strcharinfo(0) + " !";
+		mes "That... Still, I can't let you in on my own.";
+		close;
+	case 1:
+		emotion ET_SLEEPY;
+		mes "[Pion]";
+		mes "Zzz... Whoa!";
+		mes "Excuse me! No more work!";
+		next;
+		mes "[Pion]";
+		mes "Oops... ah, this is definitely not saliva";
+		mes "It is a drop of sweat that shines with passion and effort.";
+		next;
+		mes "[Pion]";
+		mes "Hmm, hmmm. The entrance down this slope is now restricted...";
+		mes "What's going on?";
+		next;
+		if (select( "I want to go to the 4th floor of Rudus.", "Point out the working attitude." ) == 2) {
+			mes "[Pion]";
+			mes "Oh, there's a problem with your working attitude?";
+			mes "Sa, some people might sleep!!";
+			next;
+			mes "[Pion]";
+			mes "I only slept a little!";
+			mes "Really!";
+			close;
+		}
+		mes "[Pion]";
+		mes "Fourth floor? I'm in control right there...";
+		mes "Ah, did you come to see Yulma from Bullet Meow?";
+		next;
+		mes "[Pion]";
+		mes "Then you should pass it.";
+		mes "If you want to look at the specimens on the 4th floor and help with a research request, go back to <NAVI>[Yulma]<INFO>pub_cat,94,43,0,101,0</INFO></NAVI> and ask.";
+		next;
+		mes "[Pion]";
+		mes "I'll move you to the 4th floor.";
+		close2;
+		completequest 16521;
+		setquest 16522;
+		warp "sp_rudus4",370,156;
+		end;
+	case 2:
+		break;
+	}
+	emotion ET_SLEEPY;
+	mes "[Pion]";
+	mes "Zzz... Huh! Shi, excuse me!";
+	mes "Are you sure you want to go down to the 4th floor of Rudus?";
+	next;
+	if (select( "Move", "Do not move" ) == 2) {
+		mes "[Pion]";
+		mes "I see. Ha-am....";
+		close;
+	}
+	mes "[Pion]";
+	mes "I'll move you. Take care!";
+	close2;
+	warp "sp_rudus4",370,156;
+	end;
+
+OnInit:
+	questinfo( QTYPE_DAILYQUEST, QMARK_YELLOW, "isbegin_quest(16521) == 1" );
+	end;
+}
+
+sp_rudus4,192,204,3	script	Secret Wing Researcher#rudu	4_SOIL,{
+	mes "[Secret Wing Researcher]";
+	mes "Good. Field work.";
+	mes "You have to watch a lot of monsters.";
+	next;
+	emotion ET_HUK;
+	mes "[Secret Wing Researcher]";
+	mes "...huh!";
+	mes "How did you recognize me?";
+	next;
+	if (isbegin_quest(16528) == 1) {
+		mes "[Secret Wing Researcher]";
+		mes "Oh, look again, " + strcharinfo(0) + ".";
+		mes "I got a call. Are you here to renew the chip?";
+		next;
+		mes "[Secret Wing Researcher]";
+		mes "Let's see... Let's get some chips.";
+		mes "Hmmmm... Connect with my machine... Empty data...";
+		next;
+		emotion ET_BEST;
+		mes "[Secret Wing Researcher]";
+		mes "Okay, that's it! The data is clean and very nice. I'll paste it again.";
+		next;
+		mes "[Secret Wing Researcher]";
+		mes "Because this is also a job, <NAVI>[Yulma]<INFO>pub_cat,94,43,0,101,0</INFO></NAVI> will give you a small allowance when you return.";
+		mes "I am always indebted to you. Good job!";
+		completequest 16528;
+		setquest 16540;
+		close;
+	}
+	emotion ET_KEK;
+	mes "[Secret Wing Researcher]";
+	mes "Evil! If you pretend to know, monsters will come!";
+	mes "Go away!";
+	close;
+
+OnInit:
+	questinfo( QTYPE_DAILYQUEST, QMARK_YELLOW, "isbegin_quest(16528) == 1" );
+	end;
+}

+ 1 - 0
npc/re/scripts_athena.conf

@@ -228,6 +228,7 @@ npc: npc/re/quests/quests_aldebaran.txt
 npc: npc/re/quests/quests_brasilis.txt
 npc: npc/re/quests/quests_dewata.txt
 npc: npc/re/quests/quests_dicastes.txt
+npc: npc/re/quests/quests_dungeons_200.txt
 npc: npc/re/quests/quests_eclage.txt
 npc: npc/re/quests/quests_glastheim.txt
 npc: npc/re/quests/quests_izlude.txt

+ 2 - 0
npc/re/warps/dungeons/sp_rudus.txt

@@ -9,3 +9,5 @@ sp_rudus,180,258,0	warp	rudus1_to_rudus2	1,1,sp_rudus2,185,258
 sp_rudus2,180,258,0	warp	rudus2_to_rudus1	1,1,sp_rudus,185,258
 sp_rudus2,359,206,0	warp	rudus2_to_rudus3	1,1,sp_rudus3,366,207
 sp_rudus3,371,207,0	warp	rudus3_to_rudus2	1,1,sp_rudus2,355,206
+
+sp_rudus4,380,156,0	warp2	#b_hw1	2,2,sp_rudus3,375,45

+ 0 - 2
sql-files/README.md

@@ -21,8 +21,6 @@ Note: The schema name is defined in `conf/inter_athena.conf::log_db_db`.
 If your server is setup to read SQL database data, import the following into the main schema:
 Note: If `conf/inter_athena.conf::use_sql_db` is set to yes continue with these imports else these can be skipped. Not all files have to be imported, only the ones that apply to the same mode as the server being ran.
 
-* item_cash_db.sql - Used for client's cash shop.
-* item_cash_db2.sql - Used for client's cash shop (import).
 * item_db.sql - Contains __pre-renewal__ item data table structure.
 * item_db_equip.sql - Contains __pre-renewal__ equipment item data.
 * item_db_etc.sql - Contains __pre-renewal__ etcetera item data.

+ 0 - 7
sql-files/item_cash_db.sql

@@ -1,7 +0,0 @@
-DROP TABLE IF EXISTS `item_cash_db`;
-CREATE TABLE `item_cash_db` (
-  `tab` smallint(6) NOT NULL,
-  `item_id` int(10) unsigned NOT NULL,
-  `price` mediumint(10) unsigned NOT NULL DEFAULT '0',
-  PRIMARY KEY (`tab`,`item_id`)
-) ENGINE=MyISAM;

+ 0 - 7
sql-files/item_cash_db2.sql

@@ -1,7 +0,0 @@
-DROP TABLE IF EXISTS `item_cash_db2`;
-CREATE TABLE `item_cash_db2` (
-  `tab` smallint(6) NOT NULL,
-  `item_id` int(10) unsigned NOT NULL,
-  `price` mediumint(10) unsigned NOT NULL DEFAULT '0',
-  PRIMARY KEY (`tab`,`item_id`)
-) ENGINE=MyISAM;

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

@@ -110,6 +110,7 @@
       <PreprocessorDefinitions>$(DefineConstants);WIN32;FD_SETSIZE=4096;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;_WINSOCK_DEPRECATED_NO_WARNINGS;LIBCONFIG_STATIC;YY_USE_CONST;_DEBUG;_CONSOLE;_LIB;_ITERATOR_DEBUG_LEVEL=0;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
       <AdditionalIncludeDirectories>$(SolutionDir)3rdparty\rapidyaml\src;$(SolutionDir)3rdparty\rapidyaml\ext\c4core\src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <ConformanceMode>true</ConformanceMode>
     </ClCompile>
     <Link>
       <SubSystem>Console</SubSystem>
@@ -229,4 +230,4 @@
     <Copy SourceFiles="$(SolutionDir)tools\charserv.bat" DestinationFolder="$(SolutionDir)" ContinueOnError="true" Condition="!Exists('$(SolutionDir)charserv.bat')" />
     <Copy SourceFiles="$(SolutionDir)tools\runserver.bat" DestinationFolder="$(SolutionDir)" ContinueOnError="true" Condition="!Exists('$(SolutionDir)runserver.bat')" />
   </Target>
-</Project>
+</Project>

+ 115 - 110
src/char/char.cpp

@@ -4,6 +4,9 @@
 #pragma warning(disable:4800)
 #include "char.hpp"
 
+#include <memory>
+#include <unordered_map>
+
 #include <stdarg.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -22,6 +25,7 @@
 #include "../common/socket.hpp"
 #include "../common/strlib.hpp"
 #include "../common/timer.hpp"
+#include "../common/utilities.hpp"
 
 #include "char_clif.hpp"
 #include "char_cnslif.hpp"
@@ -37,6 +41,7 @@
 #include "int_storage.hpp"
 #include "packets.hpp"
 
+using namespace rathena;
 using namespace rathena::server_character;
 
 //definition of exported var declared in header
@@ -70,31 +75,31 @@ struct s_subnet {
 } subnet[16];
 int subnet_count = 0;
 
-DBMap* auth_db; // uint32 account_id -> struct auth_node*
-DBMap* online_char_db; // uint32 account_id -> struct online_char_data*
-DBMap* char_db_; // uint32 char_id -> struct mmo_charstatus*
-DBMap* char_get_authdb() { return auth_db; }
-DBMap* char_get_onlinedb() { return online_char_db; }
-DBMap* char_get_chardb() { return char_db_; }
-
-/**
- * @see DBCreateData
- */
-DBData char_create_online_data(DBKey key, va_list args){
-	struct online_char_data* character;
-	CREATE(character, struct online_char_data, 1);
-	character->account_id = key.i;
-	character->char_id = -1;
-	character->server = -1;
-	character->fd = -1;
-	character->waiting_disconnect = INVALID_TIMER;
-	return db_ptr2data(character);
+// uint32 account_id -> struct auth_node*
+std::unordered_map<uint32, std::shared_ptr<struct auth_node>> auth_db;
+// uint32 account_id -> struct online_char_data*
+std::unordered_map<uint32, std::shared_ptr<struct online_char_data>> online_char_db;
+// uint32 char_id -> struct mmo_charstatus*
+std::unordered_map<uint32, std::shared_ptr<struct mmo_charstatus>> char_db;
+std::unordered_map<uint32, std::shared_ptr<struct auth_node>>& char_get_authdb() { return auth_db; }
+std::unordered_map<uint32, std::shared_ptr<struct online_char_data>>& char_get_onlinedb() { return online_char_db; }
+std::unordered_map<uint32, std::shared_ptr<struct mmo_charstatus>>& char_get_chardb() { return char_db; }
+
+online_char_data::online_char_data( uint32 account_id ){
+	this->account_id = account_id;
+	this->char_id = -1;
+	this->server = -1;
+	this->fd = -1;
+	this->waiting_disconnect = INVALID_TIMER;
 }
 
 void char_set_charselect(uint32 account_id) {
-	struct online_char_data* character;
+	std::shared_ptr<struct online_char_data> character = util::umap_find( char_get_onlinedb(), account_id );
 
-	character = (struct online_char_data*)idb_ensure(online_char_db, account_id, char_create_online_data);
+	if( character == nullptr ){
+		character = std::make_shared<struct online_char_data>( account_id );
+		char_get_onlinedb()[account_id] = character;
+	}
 
 	if( character->server > -1 )
 		if( map_server[character->server].users > 0 ) // Prevent this value from going negative.
@@ -113,20 +118,27 @@ void char_set_charselect(uint32 account_id) {
 }
 
 void char_set_char_online(int map_id, uint32 char_id, uint32 account_id) {
-	struct online_char_data* character;
-	struct mmo_charstatus *cp;
-
 	//Update DB
 	if( SQL_ERROR == Sql_Query(sql_handle, "UPDATE `%s` SET `online`='1', `last_login`=NOW() WHERE `char_id`='%d' LIMIT 1", schema_config.char_db, char_id) )
 		Sql_ShowDebug(sql_handle);
 
 	//Check to see for online conflicts
-	character = (struct online_char_data*)idb_ensure(online_char_db, account_id, char_create_online_data);
-	if( character->char_id != -1 && character->server > -1 && character->server != map_id )
-	{
-		ShowNotice("set_char_online: Character %d:%d marked in map server %d, but map server %d claims to have (%d:%d) online!\n",
-			character->account_id, character->char_id, character->server, map_id, account_id, char_id);
-		mapif_disconnectplayer(map_server[character->server].fd, character->account_id, character->char_id, 2);
+	std::shared_ptr<struct online_char_data> character = util::umap_find( char_get_onlinedb(), account_id );
+
+	if( character != nullptr ){
+		if( character->char_id != -1 && character->server > -1 && character->server != map_id ){
+			ShowNotice("set_char_online: Character %d:%d marked in map server %d, but map server %d claims to have (%d:%d) online!\n",
+				character->account_id, character->char_id, character->server, map_id, account_id, char_id);
+			mapif_disconnectplayer(map_server[character->server].fd, character->account_id, character->char_id, 2);
+		}
+
+		// Get rid of disconnect timer
+		if( character->waiting_disconnect != INVALID_TIMER ){
+			delete_timer( character->waiting_disconnect, char_chardb_waiting_disconnect );
+			character->waiting_disconnect = INVALID_TIMER;
+		}
+	}else{
+		character = std::make_shared<struct online_char_data>( account_id );
 	}
 
 	//Update state data
@@ -136,14 +148,9 @@ void char_set_char_online(int map_id, uint32 char_id, uint32 account_id) {
 	if( character->server > -1 )
 		map_server[character->server].users++;
 
-	//Get rid of disconnect timer
-	if(character->waiting_disconnect != INVALID_TIMER) {
-		delete_timer(character->waiting_disconnect, char_chardb_waiting_disconnect);
-		character->waiting_disconnect = INVALID_TIMER;
-	}
-
 	//Set char online in guild cache. If char is in memory, use the guild id on it, otherwise seek it.
-	cp = (struct mmo_charstatus*)idb_get(char_db_,char_id);
+	std::shared_ptr<struct mmo_charstatus> cp = util::umap_find( char_get_chardb(), char_id );
+
 	inter_guild_CharOnline(char_id, cp?cp->guild_id:-1);
 
 	//Notify login server
@@ -151,8 +158,6 @@ void char_set_char_online(int map_id, uint32 char_id, uint32 account_id) {
 }
 
 void char_set_char_offline(uint32 char_id, uint32 account_id){
-	struct online_char_data* character;
-
 	if ( char_id == -1 )
 	{
 		if( SQL_ERROR == Sql_Query(sql_handle, "UPDATE `%s` SET `online`='0' WHERE `account_id`='%d'", schema_config.char_db, account_id) )
@@ -160,17 +165,20 @@ void char_set_char_offline(uint32 char_id, uint32 account_id){
 	}
 	else
 	{
-		struct mmo_charstatus* cp = (struct mmo_charstatus*)idb_get(char_db_,char_id);
+		std::shared_ptr<struct mmo_charstatus> cp = util::umap_find( char_get_chardb(), char_id );
+
 		inter_guild_CharOffline(char_id, cp?cp->guild_id:-1);
 		if (cp)
-			idb_remove(char_db_,char_id);
+			char_get_chardb().erase( char_id );
 
 		if( SQL_ERROR == Sql_Query(sql_handle, "UPDATE `%s` SET `online`='0' WHERE `char_id`='%d' LIMIT 1", schema_config.char_db, char_id) )
 			Sql_ShowDebug(sql_handle);
 	}
 
-	if ((character = (struct online_char_data*)idb_get(online_char_db, account_id)) != NULL)
-	{	//We don't free yet to avoid aCalloc/aFree spamming during char change. [Skotlex]
+	std::shared_ptr<struct online_char_data> character = util::umap_find( char_get_onlinedb(), account_id );
+
+	// We don't free yet to avoid aCalloc/aFree spamming during char change. [Skotlex]
+	if( character != nullptr ){	
 		if( character->server > -1 )
 			if( map_server[character->server].users > 0 ) // Prevent this value from going negative.
 				map_server[character->server].users--;
@@ -197,12 +205,7 @@ void char_set_char_offline(uint32 char_id, uint32 account_id){
 	}
 }
 
-/**
- * @see DBApply
- */
-int char_db_setoffline(DBKey key, DBData *data, va_list ap) {
-	struct online_char_data* character = (struct online_char_data*)db_data2ptr(data);
-	int server = va_arg(ap, int);
+void char_db_setoffline( std::shared_ptr<struct online_char_data> character, int server ){
 	if (server == -1) {
 		character->char_id = -1;
 		character->server = -1;
@@ -212,28 +215,17 @@ int char_db_setoffline(DBKey key, DBData *data, va_list ap) {
 		}
 	} else if (character->server == server)
 		character->server = -2; //In some map server that we aren't connected to.
-	return 0;
 }
 
-/**
- * @see DBApply
- */
-int char_db_kickoffline(DBKey key, DBData *data, va_list ap){
-	struct online_char_data* character = (struct online_char_data*)db_data2ptr(data);
-	int server_id = va_arg(ap, int);
-
+void char_db_kickoffline( std::shared_ptr<struct online_char_data> character, int server_id ){
 	if (server_id > -1 && character->server != server_id)
-		return 0;
+		return;
 
 	//Kick out any connected characters, and set them offline as appropriate.
 	if (character->server > -1)
 		mapif_disconnectplayer(map_server[character->server].fd, character->account_id, character->char_id, 1);
 	else if (character->waiting_disconnect == INVALID_TIMER)
 		char_set_char_offline(character->char_id, character->account_id);
-	else
-		return 0; // fail
-
-	return 1;
 }
 
 void char_set_all_offline(int id){
@@ -241,7 +233,10 @@ void char_set_all_offline(int id){
 		ShowNotice("Sending all users offline.\n");
 	else
 		ShowNotice("Sending users of map-server %d offline.\n",id);
-	online_char_db->foreach(online_char_db,char_db_kickoffline,id);
+
+	for( const auto& pair : char_get_onlinedb() ){
+		char_db_kickoffline( pair.second, id );
+	}
 
 	if (id >= 0 || !chlogif_isconnected())
 		return;
@@ -257,28 +252,23 @@ void char_set_all_offline_sql(void){
 		Sql_ShowDebug(sql_handle);
 }
 
-/**
- * @see DBCreateData
- */
-DBData char_create_charstatus(DBKey key, va_list args) {
-	struct mmo_charstatus *cp;
-	cp = (struct mmo_charstatus *) aCalloc(1,sizeof(struct mmo_charstatus));
-	cp->char_id = key.i;
-	return db_ptr2data(cp);
-}
-
 int char_mmo_char_tosql(uint32 char_id, struct mmo_charstatus* p){
 	int i = 0;
 	int count = 0;
 	int diff = 0;
 	char save_status[128]; //For displaying save information. [Skotlex]
-	struct mmo_charstatus *cp;
 	int errors = 0; //If there are any errors while saving, "cp" will not be updated at the end.
 	StringBuf buf;
 
 	if (char_id!=p->char_id) return 0;
 
-	cp = (struct mmo_charstatus *)idb_ensure(char_db_, char_id, char_create_charstatus);
+	std::shared_ptr<struct mmo_charstatus> cp = util::umap_find( char_get_chardb(), char_id );
+
+	if( cp == nullptr ){
+		cp = std::make_shared<struct mmo_charstatus>();
+		cp->char_id = char_id;
+		char_get_chardb()[cp->char_id] = cp;
+	}
 
 	StringBuf_Init(&buf);
 	memset(save_status, 0, sizeof(save_status));
@@ -526,8 +516,11 @@ int char_mmo_char_tosql(uint32 char_id, struct mmo_charstatus* p){
 	StringBuf_Destroy(&buf);
 	if (save_status[0]!='\0' && charserv_config.save_log)
 		ShowInfo("Saved char %d - %s:%s.\n", char_id, p->name, save_status);
-	if (!errors)
-		memcpy(cp, p, sizeof(struct mmo_charstatus));
+
+	if( !errors ){
+		memcpy( cp.get(), p, sizeof( struct mmo_charstatus ) );
+	}
+
 	return 0;
 }
 
@@ -1035,7 +1028,6 @@ int char_mmo_chars_fromsql(struct char_session_data* sd, uint8* buf, uint8* coun
 //=====================================================================================================
 int char_mmo_char_fromsql(uint32 char_id, struct mmo_charstatus* p, bool load_everything) {
 	int i;
-	struct mmo_charstatus* cp;
 	SqlStmt* stmt;
 	char last_map[MAP_NAME_LENGTH_EXT];
 	char save_map[MAP_NAME_LENGTH_EXT];
@@ -1273,16 +1265,22 @@ int char_mmo_char_fromsql(uint32 char_id, struct mmo_charstatus* p, bool load_ev
 		ShowInfo("Loaded char (%d - %s): %s\n", char_id, p->name, StringBuf_Value(&msg_buf)); //ok. all data load successfully!
 	SqlStmt_Free(stmt);
 
-	cp = (struct mmo_charstatus *)idb_ensure(char_db_, char_id, char_create_charstatus);
-	memcpy(cp, p, sizeof(struct mmo_charstatus));
+	std::shared_ptr<struct mmo_charstatus> cp = util::umap_find( char_get_chardb(), char_id );
+
+	if( cp == nullptr ){
+		cp = std::make_shared<struct mmo_charstatus>();
+		cp->char_id = char_id;
+		char_get_chardb()[cp->char_id] = cp;
+	}
+
+	memcpy( cp.get(), p, sizeof( struct mmo_charstatus ) );
+
 	StringBuf_Destroy(&msg_buf);
 	return 1;
 }
 
 //==========================================================================================================
 int char_mmo_sql_init(void) {
-	char_db_= idb_alloc(DB_OPT_RELEASE_DATA);
-
 	ShowStatus("Characters per Account: '%d'.\n", charserv_config.char_config.char_per_account);
 
 	//the 'set offline' part is now in check_login_conn ...
@@ -2000,10 +1998,10 @@ void char_set_session_flag_(int account_id, int val, bool set) {
 }
 
 void char_auth_ok(int fd, struct char_session_data *sd) {
-	struct online_char_data* character;
+	std::shared_ptr<struct online_char_data> character = util::umap_find( char_get_onlinedb(), sd->account_id );
 
-	if( (character = (struct online_char_data*)idb_get(online_char_db, sd->account_id)) != NULL )
-	{	// check if character is not online already. [Skotlex]
+	// Check if character is not online already. [Skotlex]
+	if( character != nullptr ){
 		if (character->server > -1)
 		{	//Character already online. KICK KICK KICK
 			mapif_disconnectplayer(map_server[character->server].fd, character->account_id, character->char_id, 2);
@@ -2208,33 +2206,38 @@ void char_pincode_decrypt( uint32 userSeed, char* pin ){
 //replies/disconnect the player we tried to kick. [Skotlex]
 //------------------------------------------------
 TIMER_FUNC(char_chardb_waiting_disconnect){
-	struct online_char_data* character;
-	if ((character = (struct online_char_data*)idb_get(online_char_db, id)) != NULL && character->waiting_disconnect == tid)
-	{	//Mark it offline due to timeout.
+	std::shared_ptr<struct online_char_data> character = util::umap_find( char_get_onlinedb(), static_cast<uint32>( id ) );
+
+	// Mark it offline due to timeout.
+	if( character != nullptr && character->waiting_disconnect == tid ){	
 		character->waiting_disconnect = INVALID_TIMER;
 		char_set_char_offline(character->char_id, character->account_id);
 	}
 	return 0;
 }
 
-/**
- * @see DBApply
- */
-int char_online_data_cleanup_sub(DBKey key, DBData *data, va_list ap)
-{
-	struct online_char_data *character= (struct online_char_data *)db_data2ptr(data);
-	if (character->fd != -1)
-		return 0; //Character still connected
-	if (character->server == -2) //Unknown server.. set them offline
-		char_set_char_offline(character->char_id, character->account_id);
-	if (character->server < 0)
-		//Free data from players that have not been online for a while.
-		db_remove(online_char_db, key);
-	return 0;
-}
-
 TIMER_FUNC(char_online_data_cleanup){
-	online_char_db->foreach(online_char_db, char_online_data_cleanup_sub);
+	for( auto it = char_get_onlinedb().begin(); it != char_get_onlinedb().end(); ){
+		std::shared_ptr<struct online_char_data> character = it->second;
+
+		// Character still connected
+		if( character->fd != -1 ){
+			return 0;
+		}
+
+		// Unknown server - set them offline
+		if( character->server == -2 ){
+			char_set_char_offline( character->char_id, character->account_id );
+		}
+
+		// Free data from players that have not been online for a while.
+		if( character->server < 0 ){
+			it = char_get_onlinedb().erase( it );
+		}else{
+			it++;
+		}
+	}
+
 	return 0;
 }
 
@@ -2822,6 +2825,8 @@ void char_set_defaults(){
 #else
 	charserv_config.allowed_job_flag = 1;
 #endif
+
+	charserv_config.clear_parties = 0;
 }
 
 /**
@@ -3109,6 +3114,8 @@ bool char_config_read(const char* cfgName, bool normal){
 			charserv_config.mail_return_empty = config_switch(w2);
 		} else if (strcmpi(w1, "allowed_job_flag") == 0) {
 			charserv_config.allowed_job_flag = atoi(w2);
+		} else if (strcmpi(w1, "clear_parties") == 0) {
+			charserv_config.clear_parties = config_switch(w2);
 		} else if (strcmpi(w1, "import") == 0) {
 			char_config_read(w2, normal);
 		}
@@ -3158,9 +3165,9 @@ void CharacterServer::finalize(){
 	do_final_chmapif();
 	do_final_chlogif();
 
-	char_db_->destroy(char_db_, NULL);
-	online_char_db->destroy(online_char_db, NULL);
-	auth_db->destroy(auth_db, NULL);
+	char_get_chardb().clear();
+	char_get_onlinedb().clear();
+	char_get_authdb().clear();
 
 	if( char_fd != -1 )
 	{
@@ -3214,8 +3221,6 @@ bool CharacterServer::initialize( int argc, char *argv[] ){
 
 	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);
 	char_mmo_sql_init();
 	char_read_fame_list(); //Read fame lists.
 

+ 12 - 7
src/char/char.hpp

@@ -4,6 +4,8 @@
 #ifndef CHAR_HPP
 #define CHAR_HPP
 
+#include <memory>
+#include <unordered_map>
 #include <vector>
 
 #include "../common/core.hpp" // CORE_ST_LAST
@@ -206,6 +208,7 @@ struct CharServ_Config {
 	int mail_return_empty;
 
 	int allowed_job_flag;
+	int clear_parties;
 };
 extern struct CharServ_Config charserv_config;
 
@@ -232,7 +235,8 @@ struct auth_node {
 	unsigned changing_mapservers : 1;
 	uint8 version;
 };
-DBMap* char_get_authdb(); // uint32 account_id -> struct auth_node*
+
+std::unordered_map<uint32, std::shared_ptr<struct auth_node>>& char_get_authdb();
 
 struct online_char_data {
 	uint32 account_id;
@@ -241,8 +245,12 @@ struct online_char_data {
 	int waiting_disconnect;
 	short server; // -2: unknown server, -1: not connected, 0+: id of server
 	bool pincode_success;
+
+public: 
+	online_char_data( uint32 account_id );
 };
-DBMap* char_get_onlinedb(); // uint32 account_id -> struct online_char_data*
+
+std::unordered_map<uint32, std::shared_ptr<struct online_char_data>>& char_get_onlinedb();
 
 struct char_session_data {
 	bool auth; // whether the session is authed or not
@@ -270,9 +278,7 @@ struct char_session_data {
 	uint8 flag; // &1 - Retrieving guild bound items
 };
 
-
-struct mmo_charstatus;
-DBMap* char_get_chardb(); // uint32 char_id -> struct mmo_charstatus*
+std::unordered_map<uint32, std::shared_ptr<struct mmo_charstatus>>& char_get_chardb();
 
 //Custom limits for the fame lists. [Skotlex]
 extern int fame_list_size_chemist;
@@ -290,8 +296,7 @@ int char_search_mapserver(unsigned short map, uint32 ip, uint16 port);
 int char_lan_subnetcheck(uint32 ip);
 
 int char_count_users(void);
-DBData char_create_online_data(DBKey key, va_list args);
-int char_db_setoffline(DBKey key, DBData *data, va_list ap);
+void char_db_setoffline( std::shared_ptr<struct online_char_data> character, int server );
 void char_set_char_online(int map_id, uint32 char_id, uint32 account_id);
 void char_set_char_offline(uint32 char_id, uint32 account_id);
 void char_set_all_offline(int id);

+ 23 - 18
src/char/char_clif.cpp

@@ -3,6 +3,9 @@
 
 #include "char_clif.hpp"
 
+#include <memory>
+#include <unordered_map>
+
 #include <stdlib.h>
 #include <string.h>
 
@@ -15,6 +18,7 @@
 #include "../common/sql.hpp"
 #include "../common/strlib.hpp"
 #include "../common/timer.hpp"
+#include "../common/utilities.hpp"
 #include "../common/utils.hpp"
 
 #include "char.hpp"
@@ -23,6 +27,8 @@
 #include "inter.hpp"
 #include "packets.hpp"
 
+using namespace rathena;
+
 #if PACKETVER_SUPPORTS_PINCODE
 bool pincode_allowed( char* pincode );
 #endif
@@ -703,9 +709,6 @@ int chclif_parse_reqtoconnect(int fd, struct char_session_data* sd,uint32 ipl){
 	if( RFIFOREST(fd) < 17 ) // request to connect
 		return 0;
 	else {
-		struct auth_node* node;
-		DBMap *auth_db = char_get_authdb();
-
 		uint32 account_id = RFIFOL(fd,2);
 		uint32 login_id1 = RFIFOL(fd,6);
 		uint32 login_id2 = RFIFOL(fd,10);
@@ -741,14 +744,15 @@ int chclif_parse_reqtoconnect(int fd, struct char_session_data* sd,uint32 ipl){
 		}
 
 		// search authentification
-		node = (struct auth_node*)idb_get(auth_db, account_id);
-		if( node != NULL &&
+		std::shared_ptr<struct auth_node> node = util::umap_find( char_get_authdb(), account_id);
+
+		if( node != nullptr &&
 			node->account_id == account_id &&
 			node->login_id1  == login_id1 &&
 			node->login_id2  == login_id2 /*&&
 			node->ip         == ipl*/ )
 		{// authentication found (coming from map server)
-			idb_remove(auth_db, account_id);
+			char_get_authdb().erase(account_id);
 			char_auth_ok(fd, sd);
 		}
 		else
@@ -780,7 +784,7 @@ int chclif_parse_req_charlist(int fd, struct char_session_data* sd){
 }
 
 //Send player to map
-void chclif_send_map_data( int fd, struct mmo_charstatus *cd, uint32 ipl, int map_server_index ){
+void chclif_send_map_data( int fd, std::shared_ptr<struct mmo_charstatus> cd, uint32 ipl, int map_server_index ){
 #if PACKETVER >= 20170315
 	int cmd = 0xAC5;
 	int size = 156;
@@ -806,13 +810,9 @@ int chclif_parse_charselect(int fd, struct char_session_data* sd,uint32 ipl){
 	FIFOSD_CHECK(3)
 	{
 		struct mmo_charstatus char_dat;
-		struct mmo_charstatus *cd;
 		char* data;
 		uint32 char_id;
-		struct auth_node* node;
 		int i, map_fd, server_id;
-		DBMap *auth_db = char_get_authdb();
-		DBMap *char_db_ = char_get_chardb();
 
 		int slot = RFIFOB(fd,2);
 		RFIFOSKIP(fd,3);
@@ -864,7 +864,7 @@ int chclif_parse_charselect(int fd, struct char_session_data* sd,uint32 ipl){
 		}
 
 		//Have to switch over to the DB instance otherwise data won't propagate [Kevin]
-		cd = (struct mmo_charstatus *)idb_get(char_db_, char_id);
+		std::shared_ptr<struct mmo_charstatus> cd = util::umap_find( char_get_chardb(), char_id );
 
 		if (charserv_config.log_char) {
 			char esc_name[NAME_LENGTH*2+1];
@@ -930,7 +930,8 @@ int chclif_parse_charselect(int fd, struct char_session_data* sd,uint32 ipl){
 		chclif_send_map_data( fd, cd, ipl, i );
 
 		// create temporary auth entry
-		CREATE(node, struct auth_node, 1);
+		std::shared_ptr<struct auth_node> node = std::make_shared<struct auth_node>();
+
 		node->account_id = sd->account_id;
 		node->char_id = cd->char_id;
 		node->login_id1 = sd->login_id1;
@@ -939,8 +940,8 @@ int chclif_parse_charselect(int fd, struct char_session_data* sd,uint32 ipl){
 		node->expiration_time = sd->expiration_time;
 		node->group_id = sd->group_id;
 		node->ip = ipl;
-		idb_put(auth_db, sd->account_id, node);
 
+		char_get_authdb()[node->account_id] = node;
 	}
 	return 1;
 }
@@ -1363,12 +1364,16 @@ int chclif_parse(int fd) {
 
 	if(session[fd]->flag.eof) {
 		if( sd != NULL && sd->auth ) { // already authed client
-			DBMap *online_char_db = char_get_onlinedb();
-			struct online_char_data* data = (struct online_char_data*)idb_get(online_char_db, sd->account_id);
-			if( data != NULL && data->fd == fd)
+			std::shared_ptr<struct online_char_data> data = util::umap_find( char_get_onlinedb(), sd->account_id );
+
+			if( data != nullptr && data->fd == fd ){
 				data->fd = -1;
-			if( data == NULL || data->server == -1) //If it is not in any server, send it offline. [Skotlex]
+			}
+
+			// If it is not in any server, send it offline. [Skotlex]
+			if( data == nullptr || data->server == -1 ){
 				char_set_char_offline(-1,sd->account_id);
+			}
 		}
 		do_close(fd);
 		return 0;

+ 28 - 32
src/char/char_logif.cpp

@@ -3,6 +3,8 @@
 
 #include "char_logif.hpp"
 
+#include <memory>
+
 #include <stdlib.h>
 #include <string.h>
 
@@ -11,6 +13,7 @@
 #include "../common/sql.hpp"
 #include "../common/strlib.hpp"
 #include "../common/timer.hpp"
+#include "../common/utilities.hpp"
 #include "../common/utils.hpp"
 
 #include "char.hpp"
@@ -19,6 +22,8 @@
 #include "inter.hpp"
 #include "int_guild.hpp"
 
+using namespace rathena;
+
 //early declaration
 void chlogif_on_ready(void);
 void chlogif_on_disconnect(void);
@@ -59,10 +64,9 @@ void chlogif_pincode_start(int fd, struct char_session_data* sd){
 		}else{
 			if( !(charserv_config.pincode_config.pincode_changetime)
 			|| ( sd->pincode_change + charserv_config.pincode_config.pincode_changetime ) > time(NULL) ){
-				DBMap*  online_char_db = char_get_onlinedb();
-				struct online_char_data* node = (struct online_char_data*)idb_get( online_char_db, sd->account_id );
+				std::shared_ptr<struct online_char_data> node = util::umap_find( char_get_onlinedb(), sd->account_id );
 
-				if( node != NULL && node->pincode_success ){
+				if( node != nullptr && node->pincode_success ){
 					// User has already passed the check
 					chclif_pincode_sendstate( fd, sd, PINCODE_PASSED );
 				}else{
@@ -82,21 +86,6 @@ void chlogif_pincode_start(int fd, struct char_session_data* sd){
 }
 #endif
 
-/**
- * Load this character's account id into the 'online accounts' packet
- * @see DBApply
- */
-int chlogif_send_acc_tologin_sub(DBKey key, DBData *data, va_list ap) {
-	struct online_char_data* character = (struct online_char_data*)db_data2ptr(data);
-	int* i = va_arg(ap, int*);
-	if(character->server > -1) {
-		WFIFOL(login_fd,8+(*i)*4) = character->account_id;
-		(*i)++;
-		return 1;
-	}
-	return 0;
-}
-
 /**
  * Timered function to send all account_id connected to login-serv
  * @param tid : Timer id
@@ -107,14 +96,20 @@ int chlogif_send_acc_tologin_sub(DBKey key, DBData *data, va_list ap) {
  */
 TIMER_FUNC(chlogif_send_acc_tologin){
 	if ( chlogif_isconnected() ){
-		DBMap*  online_char_db = char_get_onlinedb();
 		// send account list to login server
-		int users = online_char_db->size(online_char_db);
+		int users = char_get_onlinedb().size();
 		int i = 0;
 
 		WFIFOHEAD(login_fd,8+users*4);
 		WFIFOW(login_fd,0) = 0x272d;
-		online_char_db->foreach(online_char_db, chlogif_send_acc_tologin_sub, &i, users);
+		for( const auto& pair : char_get_onlinedb() ){
+			std::shared_ptr<struct online_char_data> character = pair.second;
+
+			if( character->server > -1 ){
+				WFIFOL( login_fd, 8 + i * 4 ) = character->account_id;
+				i++;
+			}
+		}
 		WFIFOW(login_fd,2) = 8+ i*4;
 		WFIFOL(login_fd,4) = i;
 		WFIFOSET(login_fd,WFIFOW(login_fd,2));
@@ -434,18 +429,17 @@ int chlogif_parse_ackchangesex(int fd)
 		return 0;
 	else {
 		unsigned char buf[7];
-		int acc = RFIFOL(fd,2);
+		uint32 acc = RFIFOL(fd,2);
 		int sex = RFIFOB(fd,6);
 		RFIFOSKIP(fd,7);
 
 		if (acc > 0) { // TODO: Is this even possible?
 			unsigned char i;
 			int char_id = 0, class_ = 0, guild_id = 0;
-			DBMap* auth_db = char_get_authdb();
-			struct auth_node* node = (struct auth_node*)idb_get(auth_db, acc);
+			std::shared_ptr<struct auth_node> node = util::umap_find( char_get_authdb(), acc );
 			SqlStmt *stmt;
 
-			if (node != NULL)
+			if (node != nullptr)
 				node->sex = sex;
 
 			// get characters
@@ -550,13 +544,13 @@ int chlogif_parse_askkick(int fd){
 	if (RFIFOREST(fd) < 6)
 		return 0;
 	else {
-		DBMap*  online_char_db = char_get_onlinedb();
-		DBMap*  auth_db = char_get_authdb();
-		int aid = RFIFOL(fd,2);
-		struct online_char_data* character = (struct online_char_data*)idb_get(online_char_db, aid);
+		uint32 aid = RFIFOL(fd,2);
 		RFIFOSKIP(fd,6);
-		if( character != NULL )
-		{// account is already marked as online!
+
+		std::shared_ptr<struct online_char_data> character = util::umap_find( char_get_onlinedb(), aid );
+
+		// account is already marked as online!
+		if( character != nullptr ){
 			if( character->server > -1 )
 			{	//Kick it from the map server it is on.
 				mapif_disconnectplayer(map_server[character->server].fd, character->account_id, character->char_id, 2);
@@ -577,7 +571,9 @@ int chlogif_parse_askkick(int fd){
 					char_set_char_offline(-1, aid);
 			}
 		}
-		idb_remove(auth_db, aid);// reject auth attempts from map-server
+
+		// reject auth attempts from map-server
+		char_get_authdb().erase( aid );
 	}
 	return 1;
 }

+ 81 - 64
src/char/char_mapif.cpp

@@ -3,6 +3,8 @@
 
 #include "char_mapif.hpp"
 
+#include <memory>
+
 #include <stdlib.h>
 #include <string.h> //memcpy
 
@@ -12,11 +14,14 @@
 #include "../common/sql.hpp"
 #include "../common/strlib.hpp"
 #include "../common/timer.hpp"
+#include "../common/utilities.hpp"
 
 #include "char.hpp"
 #include "char_logif.hpp"
 #include "inter.hpp"
 
+using namespace rathena;
+
 /**
  * Packet send to all map-servers, attach to ourself
  * @param buf: packet to send in form of an array buffer
@@ -359,21 +364,29 @@ int chmapif_parse_regmapuser(int fd, int id){
 		return 0;
 	else {
 		//TODO: When data mismatches memory, update guild/party online/offline states.
-		DBMap* online_char_db = char_get_onlinedb();
-		int i;
-
 		map_server[id].users = RFIFOW(fd,4);
-		online_char_db->foreach(online_char_db,char_db_setoffline,id); //Set all chars from this server as 'unknown'
-		for(i = 0; i < map_server[id].users; i++) {
-			int aid = RFIFOL(fd,6+i*8);
-			int cid = RFIFOL(fd,6+i*8+4);
-			struct online_char_data* character = (struct online_char_data*)idb_ensure(online_char_db, aid, char_create_online_data);
-			if( character->server > -1 && character->server != id )
-			{
-				ShowNotice("Set map user: Character (%d:%d) marked on map server %d, but map server %d claims to have (%d:%d) online!\n",
-					character->account_id, character->char_id, character->server, id, aid, cid);
-				mapif_disconnectplayer(map_server[character->server].fd, character->account_id, character->char_id, 2);
+
+		// Set all chars from this server as 'unknown'
+		for( const auto& pair : char_get_onlinedb() ){
+			char_db_setoffline( pair.second, id );
+		}
+
+		for( int i = 0; i < map_server[id].users; i++ ){
+			uint32 aid = RFIFOL(fd,6+i*8);
+			uint32 cid = RFIFOL(fd,6+i*8+4);
+
+			std::shared_ptr<struct online_char_data> character = util::umap_find( char_get_onlinedb(), aid );
+
+			if( character != nullptr ){
+				if( character->server > -1 && character->server != id ){
+					ShowNotice("Set map user: Character (%d:%d) marked on map server %d, but map server %d claims to have (%d:%d) online!\n",
+						character->account_id, character->char_id, character->server, id, aid, cid);
+					mapif_disconnectplayer(map_server[character->server].fd, character->account_id, character->char_id, 2);
+				}
+			}else{
+				character = std::make_shared<struct online_char_data>( aid );
 			}
+
 			character->server = id;
 			character->char_id = cid;
 		}
@@ -394,9 +407,8 @@ int chmapif_parse_reqsavechar(int fd, int id){
 	if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2))
 		return 0;
 	else {
-		int aid = RFIFOL(fd,4), cid = RFIFOL(fd,8), size = RFIFOW(fd,2);
-		struct online_char_data* character;
-		DBMap* online_char_db = char_get_onlinedb();
+		uint32 aid = RFIFOL( fd, 4 ), cid = RFIFOL( fd, 8 );
+		uint16 size = RFIFOW( fd, 2 );
 
 		if (size - 13 != sizeof(struct mmo_charstatus))
 		{
@@ -404,11 +416,11 @@ int chmapif_parse_reqsavechar(int fd, int id){
 			RFIFOSKIP(fd,size);
 			return 1;
 		}
+
+		std::shared_ptr<struct online_char_data> character = util::umap_find( char_get_onlinedb(), aid );
+
 		//Check account only if this ain't final save. Final-save goes through because of the char-map reconnect
-		if (RFIFOB(fd,12) || RFIFOB(fd,13) || (
-			(character = (struct online_char_data*)idb_get(online_char_db, aid)) != NULL &&
-			character->char_id == cid))
-		{
+		if( RFIFOB( fd, 12 ) || RFIFOB( fd, 13 ) || ( character != nullptr && character->char_id == cid ) ){
 			struct mmo_charstatus char_dat;
 			memcpy(&char_dat, RFIFOP(fd,13), sizeof(struct mmo_charstatus));
 			char_mmo_char_tosql(cid, &char_dat);
@@ -463,12 +475,9 @@ int chmapif_parse_authok(int fd){
 		if( !global_core->is_running() ){
 			chmapif_charselres(fd,account_id,0);
 		}else{
-			struct auth_node* node;
-			DBMap*  auth_db = char_get_authdb();
-			DBMap* online_char_db = char_get_onlinedb();
-
 			// create temporary auth entry
-			CREATE(node, struct auth_node, 1);
+			std::shared_ptr<struct auth_node> node = std::make_shared<struct auth_node>();
+
 			node->account_id = account_id;
 			node->char_id = 0;
 			node->login_id1 = login_id1;
@@ -477,16 +486,18 @@ int chmapif_parse_authok(int fd){
 			node->ip = ntohl(ip);
 			//node->expiration_time = 0; // unlimited/unknown time by default (not display in map-server)
 			//node->gmlevel = 0;
-			idb_put(auth_db, account_id, node);
+
+			char_get_authdb()[node->account_id] = node;
 
 			//Set char to "@ char select" in online db [Kevin]
 			char_set_charselect(account_id);
-			{
-				struct online_char_data* character = (struct online_char_data*)idb_get(online_char_db, account_id);
-				if( character != NULL ){
-					character->pincode_success = true;
-				}
+			
+			std::shared_ptr<struct online_char_data> character = util::umap_find( char_get_onlinedb(), account_id );
+
+			if( character != nullptr ){
+				character->pincode_success = true;
 			}
+
 			chmapif_charselres(fd,account_id,1);
 		}
 	}
@@ -598,30 +609,28 @@ int chmapif_parse_reqchangemapserv(int fd){
 		return 0;
 	else {
 		int map_id, map_fd = -1;
-		struct mmo_charstatus* char_data;
 		struct mmo_charstatus char_dat;
-		DBMap* char_db_ = char_get_chardb();
 
 		map_id = char_search_mapserver(RFIFOW(fd,18), ntohl(RFIFOL(fd,24)), ntohs(RFIFOW(fd,28))); //Locate mapserver by ip and port.
 		if (map_id >= 0)
 			map_fd = map_server[map_id].fd;
-		//Char should just had been saved before this packet, so this should be safe. [Skotlex]
-		char_data = (struct mmo_charstatus*)uidb_get(char_db_,RFIFOL(fd,14));
-		if (char_data == NULL) {	//Really shouldn't happen.
-			char_mmo_char_fromsql(RFIFOL(fd,14), &char_dat, true);
-			char_data = (struct mmo_charstatus*)uidb_get(char_db_,RFIFOL(fd,14));
+
+		uint32 char_id = RFIFOL( fd, 14 );
+
+		// Char should just had been saved before this packet, so this should be safe. [Skotlex]
+		std::shared_ptr<struct mmo_charstatus> char_data = util::umap_find( char_get_chardb(), char_id );
+
+		// Really shouldn't happen.
+		if( char_data == nullptr ){
+			char_mmo_char_fromsql( char_id, &char_dat, true );
+			char_data = util::umap_find( char_get_chardb(), char_id );
 		}
 
 		if( global_core->is_running() &&
 			session_isActive(map_fd) &&
 			char_data )
 		{	//Send the map server the auth of this player.
-			struct online_char_data* data;
-			struct auth_node* node;
-			DBMap*  auth_db = char_get_authdb();
-			DBMap* online_char_db = char_get_onlinedb();
-
-			int aid = RFIFOL(fd,2);
+			uint32 aid = RFIFOL( fd, 2 );
 
 			//Update the "last map" as this is where the player must be spawned on the new map server.
 			char_data->last_point.map = RFIFOW(fd,18);
@@ -630,9 +639,10 @@ int chmapif_parse_reqchangemapserv(int fd){
 			char_data->sex = RFIFOB(fd,30);
 
 			// create temporary auth entry
-			CREATE(node, struct auth_node, 1);
+			std::shared_ptr<struct auth_node> node = std::make_shared<struct auth_node>();
+
 			node->account_id = aid;
-			node->char_id = RFIFOL(fd,14);
+			node->char_id = char_id;
 			node->login_id1 = RFIFOL(fd,6);
 			node->login_id2 = RFIFOL(fd,10);
 			node->sex = RFIFOB(fd,30);
@@ -640,9 +650,15 @@ int chmapif_parse_reqchangemapserv(int fd){
 			node->ip = ntohl(RFIFOL(fd,31));
 			node->group_id = RFIFOL(fd,35);
 			node->changing_mapservers = 1;
-			idb_put(auth_db, aid, node);
 
-			data = (struct online_char_data*)idb_ensure(online_char_db, aid, char_create_online_data);
+			char_get_authdb()[node->account_id] = node;
+
+			std::shared_ptr<struct online_char_data> data = util::umap_find( char_get_onlinedb(), aid );
+
+			if( data == nullptr ){
+				data = std::make_shared<struct online_char_data>( aid );
+			}
+
 			data->char_id = char_data->char_id;
 			data->server = map_id; //Update server where char is.
 
@@ -990,14 +1006,9 @@ int chmapif_parse_reqauth(int fd, int id){
 		uint32 login_id1;
 		unsigned char sex;
 		uint32 ip;
-		struct auth_node* node;
-		struct mmo_charstatus* cd;
 		struct mmo_charstatus char_dat;
 		bool autotrade;
 
-		DBMap*  auth_db = char_get_authdb();
-		DBMap* char_db_ = char_get_chardb();
-
 		account_id = RFIFOL(fd,2);
 		char_id    = RFIFOL(fd,6);
 		login_id1  = RFIFOL(fd,10);
@@ -1006,13 +1017,15 @@ int chmapif_parse_reqauth(int fd, int id){
 		autotrade  = RFIFOB(fd,19) != 0;
 		RFIFOSKIP(fd,20);
 
-		node = (struct auth_node*)idb_get(auth_db, account_id);
-		cd = (struct mmo_charstatus*)uidb_get(char_db_,char_id);
-		if( cd == NULL )
-		{	//Really shouldn't happen. (or autotrade)
-				char_mmo_char_fromsql(char_id, &char_dat, true);
-				cd = (struct mmo_charstatus*)uidb_get(char_db_,char_id);
+		std::shared_ptr<struct auth_node> node = util::umap_find( char_get_authdb(), account_id );
+		std::shared_ptr<struct mmo_charstatus> cd = util::umap_find( char_get_chardb(), char_id );
+
+		if( cd == nullptr ){
+			// Really shouldn't happen. (or autotrade)
+			char_mmo_char_fromsql( char_id, &char_dat, true );
+			cd = util::umap_find( char_get_chardb(), char_id );
 		}
+
 		if( global_core->is_running() && autotrade && cd ){
 			uint16 mmo_charstatus_len = sizeof(struct mmo_charstatus) + 25;
 
@@ -1025,7 +1038,7 @@ int chmapif_parse_reqauth(int fd, int id){
 			WFIFOL(fd,16) = 0;
 			WFIFOL(fd,20) = 0;
 			WFIFOB(fd,24) = 0;
-			memcpy(WFIFOP(fd,25), cd, sizeof(struct mmo_charstatus));
+			memcpy( WFIFOP( fd, 25 ), cd.get(), sizeof(struct mmo_charstatus));
 			WFIFOSET(fd, WFIFOW(fd,2));
 
 			char_set_char_online(id, char_id, account_id);
@@ -1052,11 +1065,11 @@ int chmapif_parse_reqauth(int fd, int id){
 			WFIFOL(fd,16) = (uint32)node->expiration_time; // FIXME: will wrap to negative after "19-Jan-2038, 03:14:07 AM GMT"
 			WFIFOL(fd,20) = node->group_id;
 			WFIFOB(fd,24) = node->changing_mapservers;
-			memcpy(WFIFOP(fd,25), cd, sizeof(struct mmo_charstatus));
+			memcpy( WFIFOP( fd, 25 ), cd.get(), sizeof( struct mmo_charstatus ) );
 			WFIFOSET(fd, WFIFOW(fd,2));
 
 			// only use the auth once and mark user online
-			idb_remove(auth_db, account_id);
+			char_get_authdb().erase( account_id );
 			char_set_char_online(id, char_id, account_id);
 		} else {// auth failed
 			WFIFOHEAD(fd,19);
@@ -1494,7 +1507,6 @@ void chmapif_server_reset(int id){
 	int j = 0;
 	unsigned char buf[16384];
 	int fd = map_server[id].fd;
-	DBMap* online_char_db = char_get_onlinedb();
 
 	//Notify other map servers that this one is gone. [Skotlex]
 	WBUFW(buf,0) = 0x2b20;
@@ -1507,7 +1519,12 @@ void chmapif_server_reset(int id){
 		WBUFW(buf,2) = j * 4 + 10;
 		chmapif_sendallwos(fd, buf, WBUFW(buf,2));
 	}
-	online_char_db->foreach(online_char_db,char_db_setoffline,id); //Tag relevant chars as 'in disconnected' server.
+
+	// Tag relevant chars as 'in disconnected' server.
+	for( const auto& pair : char_get_onlinedb() ){
+		char_db_setoffline( pair.second, id );
+	}
+
 	chmapif_server_destroy(id);
 	chmapif_server_init(id);
 }

+ 50 - 47
src/char/int_auction.cpp

@@ -3,6 +3,9 @@
 
 #include "int_auction.hpp"
 
+#include <memory>
+#include <unordered_map>
+
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -14,35 +17,37 @@
 #include "../common/sql.hpp"
 #include "../common/strlib.hpp"
 #include "../common/timer.hpp"
+#include "../common/utilities.hpp"
 
 #include "char.hpp"
 #include "char_mapif.hpp"
 #include "inter.hpp"
 #include "int_mail.hpp"
 
-static DBMap* auction_db_ = NULL; // int auction_id -> struct auction_data*
+using namespace rathena;
+
+// int auction_id -> struct auction_data*
+static std::unordered_map<uint32, std::shared_ptr<struct auction_data>> auction_db;
 
-void auction_delete(struct auction_data *auction);
+void auction_delete( std::shared_ptr<struct auction_data> auction );
 TIMER_FUNC(auction_end_timer);
 
 int auction_count(uint32 char_id, bool buy)
 {
 	int i = 0;
-	struct auction_data *auction;
-	DBIterator *iter = db_iterator(auction_db_);
 
-	for( auction = (struct auction_data *)dbi_first(iter); dbi_exists(iter); auction = (struct auction_data *)dbi_next(iter) )
-	{
-		if( (buy && auction->buyer_id == char_id) || (!buy && auction->seller_id == char_id) )
+	for( const auto& pair : auction_db ){
+		std::shared_ptr<struct auction_data> auction = pair.second;
+
+		if( ( buy && auction->buyer_id == char_id ) || ( !buy && auction->seller_id == char_id ) ){
 			i++;
+		}
 	}
-	dbi_destroy(iter);
 
 	return i;
 }
 
-void auction_save(struct auction_data *auction)
-{
+void auction_save( std::shared_ptr<struct auction_data> auction ){
 	int j;
 	StringBuf buf;
 	SqlStmt* stmt;
@@ -76,8 +81,7 @@ void auction_save(struct auction_data *auction)
 	StringBuf_Destroy(&buf);
 }
 
-unsigned int auction_create(struct auction_data *auction)
-{
+uint32 auction_create( std::shared_ptr<struct auction_data> auction ){
 	int j;
 	StringBuf buf;
 	SqlStmt* stmt;
@@ -119,7 +123,6 @@ unsigned int auction_create(struct auction_data *auction)
 	}
 	else
 	{
-		struct auction_data *auction_;
 		t_tick tick = auction->hours * 3600000;
 
 		auction->item.amount = 1;
@@ -130,9 +133,7 @@ unsigned int auction_create(struct auction_data *auction)
 		auction->auction_end_timer = add_timer( gettick() + tick , auction_end_timer, auction->auction_id, 0);
 		ShowInfo("New Auction %u | time left %" PRtf " ms | By %s.\n", auction->auction_id, tick, auction->seller_name);
 
-		CREATE(auction_, struct auction_data, 1);
-		memcpy(auction_, auction, sizeof(struct auction_data));
-		idb_put(auction_db_, auction_->auction_id, auction_);
+		auction_db[auction->auction_id] = auction;
 	}
 
 	SqlStmt_Free(stmt);
@@ -152,9 +153,9 @@ void mapif_Auction_message(uint32 char_id, unsigned char result)
 }
 
 TIMER_FUNC(auction_end_timer){
-	struct auction_data *auction;
-	if( (auction = (struct auction_data *)idb_get(auction_db_, id)) != NULL )
-	{
+	std::shared_ptr<struct auction_data> auction = util::umap_find( auction_db, static_cast<uint32>( id ) );
+
+	if( auction != nullptr ){
 		if( auction->buyer_id )
 		{
 			mail_sendmail(0, msg_txt(200), auction->buyer_id, auction->buyer_name, msg_txt(201), msg_txt(202), 0, &auction->item, 1);
@@ -173,8 +174,7 @@ TIMER_FUNC(auction_end_timer){
 	return 0;
 }
 
-void auction_delete(struct auction_data *auction)
-{
+void auction_delete( std::shared_ptr<struct auction_data> auction ){
 	unsigned int auction_id = auction->auction_id;
 
 	if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `auction_id` = '%d'", schema_config.auction_db, auction_id) )
@@ -183,7 +183,7 @@ void auction_delete(struct auction_data *auction)
 	if( auction->auction_end_timer != INVALID_TIMER )
 		delete_timer(auction->auction_end_timer, auction_end_timer);
 
-	idb_remove(auction_db_, auction_id);
+	auction_db.erase( auction_id );
 }
 
 void inter_auctions_fromsql(void)
@@ -214,8 +214,8 @@ void inter_auctions_fromsql(void)
 	while( SQL_SUCCESS == Sql_NextRow(sql_handle) )
 	{
 		struct item *item;
-		struct auction_data *auction;
-		CREATE(auction, struct auction_data, 1);
+		std::shared_ptr<struct auction_data> auction = std::make_shared<struct auction_data>();
+
 		Sql_GetData(sql_handle, 0, &data, NULL); auction->auction_id = atoi(data);
 		Sql_GetData(sql_handle, 1, &data, NULL); auction->seller_id = atoi(data);
 		Sql_GetData(sql_handle, 2, &data, NULL); safestrncpy(auction->seller_name, data, NAME_LENGTH);
@@ -261,7 +261,8 @@ void inter_auctions_fromsql(void)
 			endtick = tick + 10000; // 10 Second's to process ended auctions
 
 		auction->auction_end_timer = add_timer(endtick, auction_end_timer, auction->auction_id, 0);
-		idb_put(auction_db_, auction->auction_id, auction);
+
+		auction_db[auction->auction_id] = auction;
 	}
 
 	Sql_FreeResult(sql_handle);
@@ -288,14 +289,13 @@ void mapif_parse_Auction_requestlist(int fd)
 	int price = RFIFOL(fd,10);
 	short type = RFIFOW(fd,8), page = max(1,RFIFOW(fd,14));
 	unsigned char buf[5 * sizeof(struct auction_data)];
-	DBIterator *iter = db_iterator(auction_db_);
-	struct auction_data *auction;
 	short i = 0, j = 0, pages = 1;
 
 	memcpy(searchtext, RFIFOP(fd,16), NAME_LENGTH);
 
-	for( auction = static_cast<auction_data *>(dbi_first(iter)); dbi_exists(iter); auction = static_cast<auction_data *>(dbi_next(iter)) )
-	{
+	for( const auto& pair : auction_db ){
+		std::shared_ptr<struct auction_data> auction = pair.second;
+
 		if( (type == 0 && auction->type != IT_ARMOR && auction->type != IT_PETARMOR) ||
 			(type == 1 && auction->type != IT_WEAPON) ||
 			(type == 2 && auction->type != IT_CARD) ||
@@ -316,10 +316,9 @@ void mapif_parse_Auction_requestlist(int fd)
 		if( page != pages )
 			continue; // This is not the requested Page
 
-		memcpy(WBUFP(buf, j * len), auction, len);
+		memcpy( WBUFP( buf, j * len ), auction.get(), len );
 		j++; // Found Results
 	}
-	dbi_destroy(iter);
 
 	mapif_Auction_sendlist(fd, char_id, j, pages, buf);
 }
@@ -337,15 +336,22 @@ void mapif_Auction_register(int fd, struct auction_data *auction)
 
 void mapif_parse_Auction_register(int fd)
 {
-	struct auction_data auction;
 	if( RFIFOW(fd,2) != sizeof(struct auction_data) + 4 )
 		return;
 
-	memcpy(&auction, RFIFOP(fd,4), sizeof(struct auction_data));
-	if( auction_count(auction.seller_id, false) < 5 )
-		auction.auction_id = auction_create(&auction);
+	struct auction_data* auction = reinterpret_cast<struct auction_data*>( RFIFOP( fd, 4 ) );
+
+	if( auction_count( auction->seller_id, false ) < 5 ){
+		std::shared_ptr<struct auction_data> auction2 = std::make_shared<struct auction_data>();
 
-	mapif_Auction_register(fd, &auction);
+		memcpy( auction2.get(), auction, sizeof( struct auction_data ) );
+
+		auction2->auction_id = auction_create( auction2 );
+
+		auction = auction2.get();
+	}
+
+	mapif_Auction_register( fd, auction );
 }
 
 void mapif_Auction_cancel(int fd, uint32 char_id, unsigned char result)
@@ -360,10 +366,10 @@ void mapif_Auction_cancel(int fd, uint32 char_id, unsigned char result)
 void mapif_parse_Auction_cancel(int fd)
 {
 	uint32 char_id = RFIFOL(fd,2), auction_id = RFIFOL(fd,6);
-	struct auction_data *auction;
 
-	if( (auction = (struct auction_data *)idb_get(auction_db_, auction_id)) == NULL )
-	{
+	std::shared_ptr<struct auction_data> auction = util::umap_find( auction_db, auction_id );
+
+	if( auction == nullptr ){
 		mapif_Auction_cancel(fd, char_id, 1); // Bid Number is Incorrect
 		return;
 	}
@@ -398,10 +404,9 @@ void mapif_Auction_close(int fd, uint32 char_id, unsigned char result)
 void mapif_parse_Auction_close(int fd)
 {
 	uint32 char_id = RFIFOL(fd,2), auction_id = RFIFOL(fd,6);
-	struct auction_data *auction;
+	std::shared_ptr<struct auction_data> auction = util::umap_find( auction_db, auction_id );
 
-	if( (auction = (struct auction_data *)idb_get(auction_db_, auction_id)) == NULL )
-	{
+	if( auction == nullptr ){
 		mapif_Auction_close(fd, char_id, 2); // Bid Number is Incorrect
 		return;
 	}
@@ -442,10 +447,9 @@ void mapif_parse_Auction_bid(int fd)
 {
 	uint32 char_id = RFIFOL(fd,4), auction_id = RFIFOL(fd,8);
 	int bid = RFIFOL(fd,12);
-	struct auction_data *auction;
+	std::shared_ptr<struct auction_data> auction = util::umap_find( auction_db, auction_id );
 
-	if( (auction = (struct auction_data *)idb_get(auction_db_, auction_id)) == NULL || auction->price >= bid || auction->seller_id == char_id )
-	{
+	if( auction == nullptr || auction->price >= bid || auction->seller_id == char_id ){
 		mapif_Auction_bid(fd, char_id, bid, 0); // You have failed to bid in the auction
 		return;
 	}
@@ -508,7 +512,6 @@ int inter_auction_parse_frommap(int fd)
 
 int inter_auction_sql_init(void)
 {
-	auction_db_ = idb_alloc(DB_OPT_RELEASE_DATA);
 	inter_auctions_fromsql();
 
 	return 0;
@@ -516,7 +519,7 @@ int inter_auction_sql_init(void)
 
 void inter_auction_sql_final(void)
 {
-	auction_db_->destroy(auction_db_,NULL);
+	auction_db.clear();
 
 	return;
 }

+ 38 - 52
src/char/int_clan.cpp

@@ -3,6 +3,10 @@
 
 #include "int_clan.hpp"
 
+#include <memory>
+#include <unordered_map>
+#include <vector>
+
 #include <stdlib.h>
 #include <string.h> //memset
 
@@ -17,8 +21,10 @@
 #include "char_mapif.hpp"
 #include "inter.hpp"
 
-//clan cache
-static DBMap* clan_db; // int clan_id -> struct clan*
+// int clan_id -> struct clan*
+static std::unordered_map<int32, std::shared_ptr<struct clan>> clan_db;
+
+using namespace rathena;
 
 int inter_clan_removemember_tosql(uint32 account_id, uint32 char_id){
 	if( SQL_ERROR == Sql_Query( sql_handle, "UPDATE `%s` SET `clan_id` = '0' WHERE `char_id` = '%d'", schema_config.char_db, char_id ) ){
@@ -29,16 +35,15 @@ int inter_clan_removemember_tosql(uint32 account_id, uint32 char_id){
 	}
 }
 
-struct clan* inter_clan_fromsql(int clan_id){
-	struct clan* clan;
+std::shared_ptr<struct clan> inter_clan_fromsql(int clan_id){
 	char* data;
 	size_t len;
 	int i;
 
 	if( clan_id <= 0 )
-		return NULL;
+		return nullptr;
 
-	clan = (struct clan*)idb_get(clan_db, clan_id);
+	std::shared_ptr<struct clan> clan = util::umap_find( clan_db, clan_id );
 
 	if( clan ){
 		return clan;
@@ -46,15 +51,14 @@ struct clan* inter_clan_fromsql(int clan_id){
 
 	if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `name`, `master`, `mapname`, `max_member` FROM `%s` WHERE `clan_id`='%d'", schema_config.clan_table, clan_id) ){
 		Sql_ShowDebug(sql_handle);
-		return NULL;
+		return nullptr;
 	}
 
 	if( SQL_SUCCESS != Sql_NextRow(sql_handle) ){
-		return NULL;// Clan does not exists.
+		return nullptr; // Clan does not exists.
 	}
 
-	CREATE(clan, struct clan, 1);
-	memset(clan, 0, sizeof(struct clan));
+	clan = std::make_shared<struct clan>();
 
 	clan->id = clan_id;
 	Sql_GetData(sql_handle,  0, &data, &len); memcpy(clan->name, data, min(len, NAME_LENGTH));
@@ -73,8 +77,7 @@ struct clan* inter_clan_fromsql(int clan_id){
 
 	if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `opposition`,`alliance_id`,`name` FROM `%s` WHERE `clan_id`='%d'", schema_config.clan_alliance_table, clan_id) ){
 		Sql_ShowDebug(sql_handle);
-		aFree(clan);
-		return NULL;
+		return nullptr;
 	}
 
 	for( i = 0; i < MAX_CLANALLIANCE && SQL_SUCCESS == Sql_NextRow(sql_handle); i++ ){
@@ -85,7 +88,7 @@ struct clan* inter_clan_fromsql(int clan_id){
 		Sql_GetData(sql_handle, 2, &data, &len); memcpy(a->name, data, zmin(len, NAME_LENGTH));
 	}
 
-	idb_put( clan_db, clan_id, clan);
+	clan_db[clan->id] = clan;
 
 	if (charserv_config.save_log)
 		ShowInfo("Clan loaded (%d - %s)\n", clan_id, clan->name);
@@ -94,24 +97,22 @@ struct clan* inter_clan_fromsql(int clan_id){
 }
 
 int mapif_clan_info( int fd ){
-	DBIterator *iter;
 	int offset;
-	struct clan* clan;
 	int length;
 
-	length = 4 + db_size(clan_db) * sizeof( struct clan );
+	length = 4 + clan_db.size() * sizeof( struct clan );
 
 	WFIFOHEAD( fd, length );
 	WFIFOW( fd, 0 ) = 0x38A0;
 	WFIFOW( fd, 2 ) = length;
 
 	offset = 4;
-	iter = db_iterator(clan_db);
-	for( clan = (struct clan*)dbi_first(iter); dbi_exists(iter); clan = (struct clan*)dbi_next(iter) ){
-		memcpy( WFIFOP( fd, offset ), clan, sizeof( struct clan ) );
+	for( const auto& pair : clan_db ){
+		std::shared_ptr<struct clan> clan = pair.second;
+
+		memcpy( WFIFOP( fd, offset ), clan.get(), sizeof( struct clan ) );
 		offset += sizeof( struct clan );
 	}
-	dbi_destroy(iter);
 
 	WFIFOSET( fd, length );
 
@@ -138,7 +139,7 @@ static int mapif_parse_clan_message( int fd ){
 	return 0;
 }
 
-static void mapif_clan_refresh_onlinecount( int fd, struct clan* clan ){
+static void mapif_clan_refresh_onlinecount( int fd, std::shared_ptr<struct clan> clan ){
 	unsigned char buf[8];
 
 	WBUFW(buf,0) = 0x38A2;
@@ -149,9 +150,10 @@ static void mapif_clan_refresh_onlinecount( int fd, struct clan* clan ){
 }
 
 static void mapif_parse_clan_member_left( int fd ){
-	struct clan* clan = (struct clan*)idb_get(clan_db, RFIFOL(fd,2) );
+	std::shared_ptr<struct clan> clan = util::umap_find( clan_db, static_cast<int32>( RFIFOL( fd, 2 ) ) );
 
-	if( clan == NULL ){ // Unknown clan
+	// Unknown clan
+	if( clan == nullptr ){
 		return;
 	}
 
@@ -163,9 +165,10 @@ static void mapif_parse_clan_member_left( int fd ){
 }
 
 static void mapif_parse_clan_member_joined( int fd ){
-	struct clan* clan = (struct clan*)idb_get(clan_db, RFIFOL(fd,2) );
+	std::shared_ptr<struct clan> clan = util::umap_find( clan_db, static_cast<int32>( RFIFOL( fd, 2 ) ) );
 
-	if( clan == NULL ){ // Unknown clan
+	// Unknown clan
+	if( clan == nullptr ){
 		return;
 	}
 
@@ -203,45 +206,28 @@ int inter_clan_parse_frommap( int fd ){
 
 // Initialize clan sql
 int inter_clan_init(void){
-	char* data;
-	int* clan_ids;
-	int amount;
-
-	clan_db = idb_alloc(DB_OPT_RELEASE_DATA);
-
 	if( SQL_ERROR == Sql_Query( sql_handle, "SELECT `clan_id` FROM `%s`", schema_config.clan_table ) ){
 		Sql_ShowDebug(sql_handle);
 		return 1;
 	}
 
-	amount = (int)Sql_NumRows( sql_handle );
+	std::vector<int32> clan_ids;
 
-	if( amount > 0 ){
-		int i;
-
-		CREATE( clan_ids, int, amount );
-		i = 0;
-
-		while( SQL_SUCCESS == Sql_NextRow(sql_handle) ){
-			Sql_GetData(sql_handle,  0, &data, NULL);
-			clan_ids[i++] = atoi(data);
-		}
-
-		Sql_FreeResult( sql_handle );
-
-		// If we didnt load a row as expected
-		amount = i;
+	while( SQL_SUCCESS == Sql_NextRow( sql_handle ) ){
+		char* data;
+		Sql_GetData( sql_handle,  0, &data, nullptr );
+		clan_ids.push_back( atoi( data ) );
+	}
 
-		for( i = 0; i < amount; i++ ){
-			inter_clan_fromsql( clan_ids[i] );
-		}
+	Sql_FreeResult( sql_handle );
 
-		aFree(clan_ids);
+	for( int32 clan_id : clan_ids ){
+		inter_clan_fromsql( clan_id );
 	}
 
 	return 0;
 }
 
 void inter_clan_final(){
-	db_destroy(clan_db);
+	clan_db.clear();
 }

+ 150 - 168
src/char/int_guild.cpp

@@ -3,6 +3,9 @@
 
 #include "int_guild.hpp"
 
+#include <memory>
+#include <unordered_map>
+
 #include <stdlib.h>
 #define __STDC_WANT_LIB_EXT1__ 1
 #include <string.h>
@@ -34,32 +37,29 @@ using namespace rathena;
 
 static const char dataToHex[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
 
-//Guild cache
-static DBMap* guild_db_; // int guild_id -> struct guild*
-static DBMap *castle_db;
+// int guild_id -> struct guild*
+static std::unordered_map<int32, std::shared_ptr<struct guild>> guild_db;
+static std::unordered_map<int32, std::shared_ptr<struct guild_castle>> castle_db;
 
 int mapif_parse_GuildLeave(int fd,int guild_id,uint32 account_id,uint32 char_id,int flag,const char *mes);
 int mapif_guild_broken(int guild_id,int flag);
-bool guild_check_empty(struct guild *g);
-int guild_calcinfo(struct guild *g);
+bool guild_check_empty( std::shared_ptr<struct guild> g );
+int guild_calcinfo( std::shared_ptr<struct guild> g );
 int mapif_guild_basicinfochanged(int guild_id,int type,const void *data,int len);
-int mapif_guild_info(int fd,struct guild *g);
-int guild_break_sub(int key,void *data,va_list ap);
-int inter_guild_tosql(struct guild *g,int flag);
-int guild_checkskill(struct guild *g, int id);
+int mapif_guild_info( int fd, std::shared_ptr<struct guild> g );
+int inter_guild_tosql( std::shared_ptr<struct guild> g, int flag );
+int guild_checkskill( std::shared_ptr<struct guild> g, int id );
 
 TIMER_FUNC(guild_save_timer){
 	static int last_id = 0; //To know in which guild we were.
 	int state = 0; //0: Have not reached last guild. 1: Reached last guild, ready for save. 2: Some guild saved, don't do further saving.
-	DBIterator *iter = db_iterator(guild_db_);
-	DBKey key;
-	struct guild* g;
 
 	if( last_id == 0 ) //Save the first guild in the list.
 		state = 1;
 
-	for( g = (struct guild*)db_data2ptr(iter->first(iter, &key)); dbi_exists(iter); g = (struct guild*)db_data2ptr(iter->next(iter, &key)) )
-	{
+	for( auto it = guild_db.begin(); it != guild_db.end(); ){
+		std::shared_ptr<struct guild> g = it->second;
+
 		if( state == 0 && g->guild_id == last_id )
 			state++; //Save next guild in the list.
 		else if( state == 1 && g->save_flag&GS_MASK )
@@ -72,19 +72,20 @@ TIMER_FUNC(guild_save_timer){
 			state++;
 		}
 
-		if( g->save_flag == GS_REMOVE )
-		{// Nothing to save, guild is ready for removal.
+		// Nothing to save, guild is ready for removal.
+		if( g->save_flag == GS_REMOVE ){
 			if (charserv_config.save_log)
 				ShowInfo("Guild Unloaded (%d - %s)\n", g->guild_id, g->name);
-			db_remove(guild_db_, key);
+			it = guild_db.erase( it );
+		}else{
+			it++;
 		}
 	}
-	dbi_destroy(iter);
 
 	if( state != 2 ) //Reached the end of the guild db without saving.
 		last_id = 0; //Reset guild saved, return to beginning.
 
-	state = guild_db_->size(guild_db_);
+	state = guild_db.size();
 	if( state < 1 ) state = 1; //Calculate the time slot for the next save.
 	add_timer(tick  + (charserv_config.autosave_interval)/state, guild_save_timer, 0, 0);
 	return 0;
@@ -100,8 +101,7 @@ int inter_guild_removemember_tosql(uint32 char_id)
 }
 
 // Save guild into sql
-int inter_guild_tosql(struct guild *g,int flag)
-{
+int inter_guild_tosql( std::shared_ptr<struct guild> g, int flag ){
 	// Table guild (GS_BASIC_MASK)
 	// GS_EMBLEM `emblem_len`,`emblem_id`,`emblem_data`
 	// GS_CONNECT `connect_member`,`average_lv`
@@ -333,20 +333,21 @@ int inter_guild_tosql(struct guild *g,int flag)
 }
 
 // Read guild from sql
-struct guild * inter_guild_fromsql(int guild_id)
-{
-	struct guild *g;
+std::shared_ptr<struct guild> inter_guild_fromsql( int32 guild_id ){
 	char* data;
 	size_t len;
 	char* p;
 	int i;
 
-	if( guild_id <= 0 )
-		return NULL;
+	if( guild_id <= 0 ){
+		return nullptr;
+	}
+
+	std::shared_ptr<struct guild> g = util::umap_find( guild_db, guild_id );
 
-	g = (struct guild*)idb_get(guild_db_, guild_id);
-	if( g )
+	if( g != nullptr ){
 		return g;
+	}
 
 #ifdef NOISY
 	ShowInfo("Guild load request (%d)...\n", guild_id);
@@ -356,13 +357,15 @@ struct guild * inter_guild_fromsql(int guild_id)
 		"FROM `%s` g LEFT JOIN `%s` c ON c.`char_id` = g.`char_id` WHERE g.`guild_id`='%d'", schema_config.guild_db, schema_config.char_db, guild_id) )
 	{
 		Sql_ShowDebug(sql_handle);
-		return NULL;
+		return nullptr;
 	}
 
-	if( SQL_SUCCESS != Sql_NextRow(sql_handle) )
-		return NULL;// Guild does not exists.
+	// Guild does not exists.
+	if( SQL_SUCCESS != Sql_NextRow( sql_handle ) ){
+		return nullptr;
+	}
 
-	CREATE(g, struct guild, 1);
+	g = std::make_shared<struct guild>();
 
 	g->guild_id = guild_id;
 	Sql_GetData(sql_handle,  0, &data, &len); memcpy(g->name, data, zmin(len, NAME_LENGTH));
@@ -412,8 +415,7 @@ struct guild * inter_guild_fromsql(int guild_id)
 		"FROM `%s` `m` INNER JOIN `%s` `c` on `c`.`char_id`=`m`.`char_id` WHERE `m`.`guild_id`='%d' ORDER BY `position`", schema_config.guild_member_db, schema_config.char_db, guild_id) )
 	{
 		Sql_ShowDebug(sql_handle);
-		aFree(g);
-		return NULL;
+		return nullptr;
 	}
 	for( i = 0; i < g->max_member && SQL_SUCCESS == Sql_NextRow(sql_handle); ++i )
 	{
@@ -452,8 +454,7 @@ struct guild * inter_guild_fromsql(int guild_id)
 	if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `position`,`name`,`mode`,`exp_mode` FROM `%s` WHERE `guild_id`='%d'", schema_config.guild_position_db, guild_id) )
 	{
 		Sql_ShowDebug(sql_handle);
-		aFree(g);
-		return NULL;
+		return nullptr;
 	}
 	while( SQL_SUCCESS == Sql_NextRow(sql_handle) )
 	{
@@ -474,8 +475,7 @@ struct guild * inter_guild_fromsql(int guild_id)
 	if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `opposition`,`alliance_id`,`name` FROM `%s` WHERE `guild_id`='%d'", schema_config.guild_alliance_db, guild_id) )
 	{
 		Sql_ShowDebug(sql_handle);
-		aFree(g);
-		return NULL;
+		return nullptr;
 	}
 	for( i = 0; i < MAX_GUILDALLIANCE && SQL_SUCCESS == Sql_NextRow(sql_handle); ++i )
 	{
@@ -490,8 +490,7 @@ struct guild * inter_guild_fromsql(int guild_id)
 	if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `account_id`,`name`,`mes` FROM `%s` WHERE `guild_id`='%d'", schema_config.guild_expulsion_db, guild_id) )
 	{
 		Sql_ShowDebug(sql_handle);
-		aFree(g);
-		return NULL;
+		return nullptr;
 	}
 	for( i = 0; i < MAX_GUILDEXPULSION && SQL_SUCCESS == Sql_NextRow(sql_handle); ++i )
 	{
@@ -506,8 +505,7 @@ struct guild * inter_guild_fromsql(int guild_id)
 	if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `id`,`lv` FROM `%s` WHERE `guild_id`='%d' ORDER BY `id`", schema_config.guild_skill_db, guild_id) )
 	{
 		Sql_ShowDebug(sql_handle);
-		aFree(g);
-		return NULL;
+		return nullptr;
 	}
 
 	for(i = 0; i < MAX_GUILDSKILL; i++)
@@ -525,8 +523,11 @@ struct guild * inter_guild_fromsql(int guild_id)
 	}
 	Sql_FreeResult(sql_handle);
 
-	idb_put(guild_db_, guild_id, g); //Add to cache
-	g->save_flag |= GS_REMOVE; //But set it to be removed, in case it is not needed for long.
+	// Add to cache
+	guild_db[g->guild_id] = g;
+
+	// But set it to be removed, in case it is not needed for long.
+	g->save_flag |= GS_REMOVE;
 
 	if (charserv_config.save_log)
 		ShowInfo("Guild loaded (%d - %s)\n", guild_id, g->name);
@@ -542,10 +543,10 @@ struct guild * inter_guild_fromsql(int guild_id)
 uint16 inter_guild_storagemax(int guild_id)
 {
 #ifdef OFFICIAL_GUILD_STORAGE
-	struct guild *g = inter_guild_fromsql(guild_id);
+	std::shared_ptr<struct guild> g = inter_guild_fromsql( guild_id );
 	uint16 max = 0;
 
-	if (!g) {
+	if( g == nullptr ){
 		ShowError("Guild %d not found!\n", guild_id);
 		return 0;
 	}
@@ -561,8 +562,7 @@ uint16 inter_guild_storagemax(int guild_id)
 }
 
 // `guild_castle` (`castle_id`, `guild_id`, `economy`, `defense`, `triggerE`, `triggerD`, `nextTime`, `payTime`, `createTime`, `visibleC`, `visibleG0`, `visibleG1`, `visibleG2`, `visibleG3`, `visibleG4`, `visibleG5`, `visibleG6`, `visibleG7`)
-int inter_guildcastle_tosql(struct guild_castle *gc)
-{
+int inter_guildcastle_tosql( std::shared_ptr<struct guild_castle> gc ){
 	StringBuf buf;
 	int i;
 
@@ -584,15 +584,16 @@ int inter_guildcastle_tosql(struct guild_castle *gc)
 }
 
 // Read guild_castle from SQL
-struct guild_castle* inter_guildcastle_fromsql(int castle_id)
-{
+std::shared_ptr<struct guild_castle> inter_guildcastle_fromsql( int castle_id ){
 	char *data;
 	int i;
 	StringBuf buf;
-	struct guild_castle *gc = (struct guild_castle *)idb_get(castle_db, castle_id);
 
-	if (gc != NULL)
+	std::shared_ptr<struct guild_castle> gc = util::umap_find( castle_db, castle_id );
+
+	if( gc != nullptr ){
 		return gc;
+	}
 
 	StringBuf_Init(&buf);
 	StringBuf_AppendStr(&buf, "SELECT `castle_id`, `guild_id`, `economy`, `defense`, `triggerE`, "
@@ -607,7 +608,8 @@ struct guild_castle* inter_guildcastle_fromsql(int castle_id)
 	}
 	StringBuf_Destroy(&buf);
 
-	CREATE(gc, struct guild_castle, 1);
+	gc = std::make_shared<struct guild_castle>();
+
 	gc->castle_id = castle_id;
 
 	if (SQL_SUCCESS == Sql_NextRow(sql_handle)) {
@@ -626,7 +628,7 @@ struct guild_castle* inter_guildcastle_fromsql(int castle_id)
 	}
 	Sql_FreeResult(sql_handle);
 
-	idb_put(castle_db, castle_id, gc);
+	castle_db[gc->castle_id] = gc;
 
 	if (charserv_config.save_log)
 		ShowInfo("Loaded guild castle (%d - guild %d)\n", castle_id, gc->guild_id);
@@ -637,7 +639,6 @@ struct guild_castle* inter_guildcastle_fromsql(int castle_id)
 
 int inter_guild_CharOnline(uint32 char_id, int guild_id)
 {
-	struct guild *g;
 	int i;
 
 	if (guild_id == -1) {
@@ -664,8 +665,9 @@ int inter_guild_CharOnline(uint32 char_id, int guild_id)
 	if (guild_id == 0)
 		return 0; //No guild...
 
-	g = inter_guild_fromsql(guild_id);
-	if(!g) {
+	std::shared_ptr<struct guild> g = inter_guild_fromsql( guild_id );
+
+	if( g == nullptr ){
 		ShowError("Character %d's guild %d not found!\n", char_id, guild_id);
 		return 0;
 	}
@@ -687,7 +689,6 @@ int inter_guild_CharOnline(uint32 char_id, int guild_id)
 
 int inter_guild_CharOffline(uint32 char_id, int guild_id)
 {
-	struct guild *g=NULL;
 	int online_count, i;
 
 	if (guild_id == -1)
@@ -716,9 +717,12 @@ int inter_guild_CharOffline(uint32 char_id, int guild_id)
 		return 0; //No guild...
 
 	//Character has a guild, set character offline and check if they were the only member online
-	g = inter_guild_fromsql(guild_id);
-	if (g == NULL) //Guild not found?
+	std::shared_ptr<struct guild> g = inter_guild_fromsql( guild_id );
+
+	// Guild not found?
+	if( g == nullptr ){
 		return 0;
+	}
 
 	//Set member offline
 	ARR_FIND( 0, g->max_member, i, g->member[i].char_id == char_id );
@@ -800,33 +804,22 @@ void GuildExpDatabase::loadingFinished() {
 
 // Initialize guild sql and read exp_guild.yml
 void inter_guild_sql_init(void) {
-	//Initialize the guild cache
-	guild_db_= idb_alloc(DB_OPT_RELEASE_DATA);
-	castle_db = idb_alloc(DB_OPT_RELEASE_DATA);
-
 	guild_exp_db.load();
 	add_timer_func_list(guild_save_timer, "guild_save_timer");
 	add_timer(gettick() + 10000, guild_save_timer, 0, 0);
 }
 
-/**
- * @see DBApply
- */
-int guild_db_final(DBKey key, DBData *data, va_list ap)
-{
-	struct guild *g = (struct guild*)db_data2ptr(data);
-	if (g->save_flag&GS_MASK) {
-		inter_guild_tosql(g, g->save_flag&GS_MASK);
-		return 1;
-	}
-	return 0;
-}
-
 void inter_guild_sql_final(void)
 {
-	guild_db_->destroy(guild_db_, guild_db_final);
-	db_destroy(castle_db);
-	return;
+	for( const auto& pair : guild_db ){
+		std::shared_ptr<struct guild> guild = pair.second;
+
+		if( guild->save_flag&GS_MASK ){
+			inter_guild_tosql( guild, guild->save_flag&GS_MASK );
+		}
+	}
+	guild_db.clear();
+	castle_db.clear();
 }
 
 // Get guild_id by its name. Returns 0 if not found, -1 on error.
@@ -859,8 +852,7 @@ int search_guildname(char *str)
 }
 
 // Check if guild is empty
-bool guild_check_empty(struct guild *g)
-{
+bool guild_check_empty( std::shared_ptr<struct guild> g ){
 	int i;
 	ARR_FIND( 0, g->max_member, i, g->member[i].account_id > 0 );
 	//Let the calling function handle the guild removal in case they need
@@ -874,14 +866,12 @@ t_exp GuildExpDatabase::get_nextexp(uint16 level) {
 	return ((guild_exp == nullptr) ? 0 : guild_exp->exp);
 }
 
-int guild_checkskill(struct guild *g,int id)
-{
+int guild_checkskill( std::shared_ptr<struct guild> g, int id ){
 	int idx = id - GD_SKILLBASE;
 	return idx < 0 || idx >= MAX_GUILDSKILL ? 0 : g->skill[idx].lv;
 }
 
-int guild_calcinfo(struct guild *g)
-{
+int guild_calcinfo( std::shared_ptr<struct guild> g ){
 	int i,c;
 	struct guild before = *g; // Save guild current values
 
@@ -943,8 +933,7 @@ int guild_calcinfo(struct guild *g)
 //-------------------------------------------------------------------
 // Packet sent to map server
 
-int mapif_guild_created(int fd,uint32 account_id,struct guild *g)
-{
+int mapif_guild_created( int fd, uint32 account_id, std::shared_ptr<struct guild> g ){
 	WFIFOHEAD(fd, 10);
 	WFIFOW(fd,0)=0x3830;
 	WFIFOL(fd,2)=account_id;
@@ -975,12 +964,11 @@ int mapif_guild_noinfo(int fd,int guild_id)
 }
 
 // Send guild info
-int mapif_guild_info(int fd,struct guild *g)
-{
+int mapif_guild_info( int fd, std::shared_ptr<struct guild> g ){
 	unsigned char buf[8+sizeof(struct guild)];
 	WBUFW(buf,0)=0x3831;
 	WBUFW(buf,2)=4+sizeof(struct guild);
-	memcpy(buf+4,g,sizeof(struct guild));
+	memcpy( buf + 4, g.get(), sizeof( struct guild ) );
 	if(fd<0)
 		chmapif_sendall(buf,WBUFW(buf,2));
 	else
@@ -1018,8 +1006,7 @@ int mapif_guild_withdraw(int guild_id,uint32 account_id,uint32 char_id,int flag,
 }
 
 // Send short member's info
-int mapif_guild_memberinfoshort(struct guild *g,int idx)
-{
+int mapif_guild_memberinfoshort( std::shared_ptr<struct guild> g, int idx ){
 	unsigned char buf[19];
 	WBUFW(buf, 0)=0x3835;
 	WBUFL(buf, 2)=g->guild_id;
@@ -1120,8 +1107,7 @@ int mapif_guild_alliance(int guild_id1,int guild_id2,uint32 account_id1,uint32 a
 }
 
 // Send a guild position desc
-int mapif_guild_position(struct guild *g,int idx)
-{
+int mapif_guild_position( std::shared_ptr<struct guild> g, int idx ){
 	unsigned char buf[12 + sizeof(struct guild_position)];
 	WBUFW(buf,0)=0x383b;
 	WBUFW(buf,2)=sizeof(struct guild_position)+12;
@@ -1133,8 +1119,7 @@ int mapif_guild_position(struct guild *g,int idx)
 }
 
 // Send the guild notice
-int mapif_guild_notice(struct guild *g)
-{
+int mapif_guild_notice( std::shared_ptr<struct guild> g ){
 	unsigned char buf[256];
 	WBUFW(buf,0)=0x383e;
 	WBUFL(buf,2)=g->guild_id;
@@ -1145,8 +1130,7 @@ int mapif_guild_notice(struct guild *g)
 }
 
 // Send emblem data
-int mapif_guild_emblem(struct guild *g)
-{
+int mapif_guild_emblem( std::shared_ptr<struct guild> g ){
 	unsigned char buf[12 + sizeof(g->emblem_data)];
 	WBUFW(buf,0)=0x383f;
 	WBUFW(buf,2)=g->emblem_len+12;
@@ -1158,8 +1142,7 @@ int mapif_guild_emblem(struct guild *g)
 }
 
 // Send the guild emblem_id (version)
-int mapif_guild_emblem_version(guild* g)
-{
+int mapif_guild_emblem_version( std::shared_ptr<struct guild> g ){
 	unsigned char buf[10];
 	WBUFW(buf, 0) = 0x3841;
 	WBUFL(buf, 2) = g->guild_id;
@@ -1169,8 +1152,7 @@ int mapif_guild_emblem_version(guild* g)
 	return 0;
 }
 
-int mapif_guild_master_changed(struct guild *g, int aid, int cid, time_t time)
-{
+int mapif_guild_master_changed( std::shared_ptr<struct guild> g, int aid, int cid, time_t time ){
 	unsigned char buf[18];
 	WBUFW(buf,0)=0x3843;
 	WBUFL(buf,2)=g->guild_id;
@@ -1183,17 +1165,16 @@ int mapif_guild_master_changed(struct guild *g, int aid, int cid, time_t time)
 
 int mapif_guild_castle_dataload(int fd, int sz, int *castle_ids)
 {
-	struct guild_castle *gc = NULL;
 	int num = (sz - 4) / sizeof(int);
-	int len = 4 + num * sizeof(*gc);
+	int len = 4 + num * sizeof( struct guild_castle );
 	int i;
 
 	WFIFOHEAD(fd, len);
 	WFIFOW(fd, 0) = 0x3840;
 	WFIFOW(fd, 2) = len;
 	for (i = 0; i < num; i++) {
-		gc = inter_guildcastle_fromsql(*(castle_ids++));
-		memcpy(WFIFOP(fd, 4 + i * sizeof(*gc)), gc, sizeof(*gc));
+		std::shared_ptr<struct guild_castle> gc = inter_guildcastle_fromsql( *(castle_ids++) );
+		memcpy( WFIFOP( fd, 4 + i * sizeof( struct guild_castle ) ), gc.get(), sizeof( struct guild_castle ) );
 	}
 	WFIFOSET(fd, len);
 	return 0;
@@ -1206,7 +1187,6 @@ int mapif_guild_castle_dataload(int fd, int sz, int *castle_ids)
 // Guild creation request
 int mapif_parse_CreateGuild(int fd,uint32 account_id,char *name,struct guild_member *master)
 {
-	struct guild *g;
 	int i=0;
 #ifdef NOISY
 	ShowInfo("Creating Guild (%s)\n", name);
@@ -1231,8 +1211,9 @@ int mapif_parse_CreateGuild(int fd,uint32 account_id,char *name,struct guild_mem
 			}
 	}
 
-	g = (struct guild *)aMalloc(sizeof(struct guild));
-	memset(g,0,sizeof(struct guild));
+	std::shared_ptr<struct guild> g = std::make_shared<struct guild>();
+
+	memset( g.get(), 0, sizeof( struct guild ) );
 
 	memcpy(g->name,name,NAME_LENGTH);
 	memcpy(g->master,master->name,NAME_LENGTH);
@@ -1263,13 +1244,12 @@ int mapif_parse_CreateGuild(int fd,uint32 account_id,char *name,struct guild_mem
 		//Failed to Create guild....
 		ShowError("Failed to create Guild %s (Guild Master: %s)\n", g->name, g->master);
 		mapif_guild_created(fd,account_id,NULL);
-		aFree(g);
 		return 0;
 	}
 	ShowInfo("Created Guild %d - %s (Guild Master: %s)\n", g->guild_id, g->name, g->master);
 
-	//Add to cache
-	idb_put(guild_db_, g->guild_id, g);
+	// Add to cache
+	guild_db[g->guild_id] = g;
 
 	// Report to client
 	mapif_guild_created(fd,account_id,g);
@@ -1285,13 +1265,12 @@ int mapif_parse_CreateGuild(int fd,uint32 account_id,char *name,struct guild_mem
 // Return guild info to client
 int mapif_parse_GuildInfo(int fd,int guild_id)
 {
-	struct guild * g = inter_guild_fromsql(guild_id); //We use this because on start-up the info of castle-owned guilds is requied. [Skotlex]
-	if(g)
-	{
+	std::shared_ptr<struct guild> g = inter_guild_fromsql( guild_id );
+
+	if( g != nullptr ){
 		if (!guild_calcinfo(g))
 			mapif_guild_info(fd,g);
-	}
-	else
+	}else
 		mapif_guild_noinfo(fd,guild_id); // Failed to load info
 	return 0;
 }
@@ -1299,19 +1278,16 @@ int mapif_parse_GuildInfo(int fd,int guild_id)
 // Add member to guild
 int mapif_parse_GuildAddMember(int fd,int guild_id,struct guild_member *m)
 {
-	struct guild * g;
-	int i;
+	std::shared_ptr<struct guild> g = inter_guild_fromsql( guild_id );
 
-	g = inter_guild_fromsql(guild_id);
-	if(g==NULL){
+	if( g == nullptr ){
 		// Failed to add
 		mapif_guild_memberadded(fd,guild_id,m->account_id,m->char_id,1);
 		return 0;
 	}
 
 	// Find an empty slot
-	for(i=0;i<g->max_member;i++)
-	{
+	for( int i = 0; i < g->max_member; i++ ){
 		if(g->member[i].account_id==0)
 		{
 			memcpy(&g->member[i],m,sizeof(struct guild_member));
@@ -1337,9 +1313,9 @@ int mapif_parse_GuildLeave(int fd, int guild_id, uint32 account_id, uint32 char_
 {
 	int i;
 
-	struct guild* g = inter_guild_fromsql(guild_id);
-	if( g == NULL )
-	{
+	std::shared_ptr<struct guild> g = inter_guild_fromsql( guild_id );
+
+	if( g == nullptr ){
 		// Unknown guild, just update the player
 		if( SQL_ERROR == Sql_Query(sql_handle, "UPDATE `%s` SET `guild_id`='0' WHERE `account_id`='%d' AND `char_id`='%d'", schema_config.char_db, account_id, char_id) )
 			Sql_ShowDebug(sql_handle);
@@ -1394,13 +1370,14 @@ int mapif_parse_GuildLeave(int fd, int guild_id, uint32 account_id, uint32 char_
 int mapif_parse_GuildChangeMemberInfoShort(int fd,int guild_id,uint32 account_id,uint32 char_id,int online,int lv,int class_)
 {
 	// Could speed up by manipulating only guild_member
-	struct guild * g;
 	int i,sum,c;
 	int prev_count, prev_alv;
 
-	g = inter_guild_fromsql(guild_id);
-	if(g==NULL)
+	std::shared_ptr<struct guild> g = inter_guild_fromsql( guild_id );
+
+	if( g == nullptr ){
 		return 0;
+	}
 
 	ARR_FIND( 0, g->max_member, i, g->member[i].account_id == account_id && g->member[i].char_id == char_id );
 	if( i < g->max_member )
@@ -1446,11 +1423,11 @@ int mapif_parse_GuildChangeMemberInfoShort(int fd,int guild_id,uint32 account_id
 // BreakGuild
 int mapif_parse_BreakGuild(int fd,int guild_id)
 {
-	struct guild * g;
+	std::shared_ptr<struct guild> g = inter_guild_fromsql( guild_id );
 
-	g = inter_guild_fromsql(guild_id);
-	if(g==NULL)
+	if( g == nullptr ){
 		return 0;
+	}
 
 	// Delete guild from sql
 	//printf("- Delete guild %d from guild\n",guild_id);
@@ -1487,8 +1464,9 @@ int mapif_parse_BreakGuild(int fd,int guild_id)
 	if(charserv_config.log_inter)
 		inter_log("guild %s (id=%d) broken\n",g->name,guild_id);
 
-	//Remove the guild from memory. [Skotlex]
-	idb_remove(guild_db_, guild_id);
+	// Remove the guild from memory. [Skotlex]
+	guild_db.erase( guild_id );
+
 	return 0;
 }
 
@@ -1501,10 +1479,11 @@ int mapif_parse_GuildMessage(int fd,int guild_id,uint32 account_id,char *mes,int
 // Modification of the guild
 int mapif_parse_GuildBasicInfoChange(int fd,int guild_id,int type,const char *data,int len)
 {
-	struct guild *g = inter_guild_fromsql(guild_id);
+	std::shared_ptr<struct guild> g = inter_guild_fromsql( guild_id );
 
-	if (!g)
+	if( g == nullptr ){
 		return 0;
+	}
 
 	short data_value = *((short *)data);
 
@@ -1542,11 +1521,12 @@ int mapif_parse_GuildMemberInfoChange(int fd,int guild_id,uint32 account_id,uint
 {
 	// Could make some improvement in speed, because only change guild_member
 	int i;
-	struct guild * g;
 
-	g = inter_guild_fromsql(guild_id);
-	if(g==NULL)
+	std::shared_ptr<struct guild> g = inter_guild_fromsql( guild_id );
+
+	if( g == nullptr ){
 		return 0;
+	}
 
 	// Search the member
 	for(i=0;i<g->max_member;i++)
@@ -1649,12 +1629,11 @@ int inter_guild_sex_changed(int guild_id,uint32 account_id,uint32 char_id, short
 
 int inter_guild_charname_changed(int guild_id,uint32 account_id, uint32 char_id, char *name)
 {
-	struct guild *g;
 	int i, flag = 0;
 
-	g = inter_guild_fromsql(guild_id);
-	if( g == NULL )
-	{
+	std::shared_ptr<struct guild> g = inter_guild_fromsql( guild_id );
+
+	if( g == nullptr ){
 		ShowError("inter_guild_charrenamed: Can't find guild %d.\n", guild_id);
 		return 0;
 	}
@@ -1687,11 +1666,11 @@ int inter_guild_charname_changed(int guild_id,uint32 account_id, uint32 char_id,
 int mapif_parse_GuildPosition(int fd,int guild_id,int idx,struct guild_position *p)
 {
 	// Could make some improvement in speed, because only change guild_position
-	struct guild * g;
+	std::shared_ptr<struct guild> g = inter_guild_fromsql( guild_id );
 
-	g = inter_guild_fromsql(guild_id);
-	if(g==NULL || idx<0 || idx>=MAX_GUILDPOSITION)
+	if( g == nullptr || idx < 0 || idx >= MAX_GUILDPOSITION ){
 		return 0;
+	}
 
 	memcpy(&g->position[idx],p,sizeof(struct guild_position));
 	mapif_guild_position(g,idx);
@@ -1703,12 +1682,13 @@ int mapif_parse_GuildPosition(int fd,int guild_id,int idx,struct guild_position
 // Guild Skill UP
 int mapif_parse_GuildSkillUp(int fd,int guild_id,uint16 skill_id,uint32 account_id,int max)
 {
-	struct guild * g;
 	int idx = skill_id - GD_SKILLBASE;
 
-	g = inter_guild_fromsql(guild_id);
-	if(g == NULL || idx < 0 || idx >= MAX_GUILDSKILL)
+	std::shared_ptr<struct guild> g = inter_guild_fromsql( guild_id );
+
+	if( g == nullptr || idx < 0 || idx >= MAX_GUILDSKILL ){
 		return 0;
+	}
 
 	if(g->skill_point>0 && g->skill[idx].id>0 && g->skill[idx].lv<max )
 	{
@@ -1725,8 +1705,7 @@ int mapif_parse_GuildSkillUp(int fd,int guild_id,uint16 skill_id,uint32 account_
 }
 
 //Manual deletion of an alliance when partnering guild does not exists. [Skotlex]
-int mapif_parse_GuildDeleteAlliance(struct guild *g, int guild_id, uint32 account_id1, uint32 account_id2, int flag)
-{
+int mapif_parse_GuildDeleteAlliance( std::shared_ptr<struct guild> g, int guild_id, uint32 account_id1, uint32 account_id2, int flag ){
 	int i;
 	char name[NAME_LENGTH];
 
@@ -1755,16 +1734,19 @@ int mapif_parse_GuildDeleteAlliance(struct guild *g, int guild_id, uint32 accoun
 int mapif_parse_GuildAlliance(int fd,int guild_id1,int guild_id2,uint32 account_id1,uint32 account_id2,int flag)
 {
 	// Could speed up
-	struct guild *g[2];
+	std::shared_ptr<struct guild> g[2];
 	int j,i;
 	g[0] = inter_guild_fromsql(guild_id1);
 	g[1] = inter_guild_fromsql(guild_id2);
 
-	if(g[0] && g[1]==NULL && (flag & GUILD_ALLIANCE_REMOVE)) //Requested to remove an alliance with a not found guild.
+	// Requested to remove an alliance with a not found guild.
+	if( g[0] != nullptr && g[1] == nullptr && ( flag & GUILD_ALLIANCE_REMOVE ) ){
 		return mapif_parse_GuildDeleteAlliance(g[0], guild_id2,	account_id1, account_id2, flag); //Try to do a manual removal of said guild.
+	}
 
-	if(g[0]==NULL || g[1]==NULL)
+	if( g[0] == nullptr || g[1] == nullptr ){
 		return 0;
+	}
 
 	if(flag&GUILD_ALLIANCE_REMOVE)
 	{
@@ -1805,11 +1787,11 @@ int mapif_parse_GuildAlliance(int fd,int guild_id1,int guild_id2,uint32 account_
 // Change guild message
 int mapif_parse_GuildNotice(int fd,int guild_id,const char *mes1,const char *mes2)
 {
-	struct guild *g;
+	std::shared_ptr<struct guild> g = inter_guild_fromsql( guild_id );
 
-	g = inter_guild_fromsql(guild_id);
-	if(g==NULL)
+	if( g == nullptr ){
 		return 0;
+	}
 
 	memcpy(g->mes1,mes1,MAX_GUILDMES1);
 	memcpy(g->mes2,mes2,MAX_GUILDMES2);
@@ -1820,11 +1802,11 @@ int mapif_parse_GuildNotice(int fd,int guild_id,const char *mes1,const char *mes
 
 int mapif_parse_GuildEmblem(int fd,int len,int guild_id,int dummy,const char *data)
 {
-	struct guild * g;
+	std::shared_ptr<struct guild> g = inter_guild_fromsql( guild_id );
 
-	g = inter_guild_fromsql(guild_id);
-	if(g==NULL)
+	if( g == nullptr ){
 		return 0;
+	}
 
 	if (len > sizeof(g->emblem_data))
 		len = sizeof(g->emblem_data);
@@ -1843,9 +1825,9 @@ int mapif_parse_GuildCastleDataLoad(int fd, int len, int *castle_ids)
 
 int mapif_parse_GuildCastleDataSave(int fd, int castle_id, int index, int value)
 {
-	struct guild_castle *gc = inter_guildcastle_fromsql(castle_id);
+	std::shared_ptr<struct guild_castle> gc = inter_guildcastle_fromsql( castle_id );
 
-	if (gc == NULL) {
+	if( gc == nullptr ){
 		ShowError("mapif_parse_GuildCastleDataSave: castle id=%d not found\n", castle_id);
 		return 0;
 	}
@@ -1854,7 +1836,7 @@ int mapif_parse_GuildCastleDataSave(int fd, int castle_id, int index, int value)
 		case CD_GUILD_ID:
 			if (charserv_config.log_inter && gc->guild_id != value) {
 				int gid = (value) ? value : gc->guild_id;
-				struct guild *g = (struct guild*)idb_get(guild_db_, gid);
+				std::shared_ptr<struct guild> g = util::umap_find( guild_db, gid );
 				inter_log("guild %s (id=%d) %s castle id=%d\n",
 				          (g) ? g->name : "??", gid, (value) ? "occupy" : "abandon", castle_id);
 			}
@@ -1882,14 +1864,14 @@ int mapif_parse_GuildCastleDataSave(int fd, int castle_id, int index, int value)
 
 int mapif_parse_GuildMasterChange(int fd, int guild_id, const char* name, int len)
 {
-	struct guild * g;
 	struct guild_member gm;
 	int pos;
 
-	g = inter_guild_fromsql(guild_id);
+	std::shared_ptr<struct guild> g = inter_guild_fromsql( guild_id );
 
-	if(g==NULL || len > NAME_LENGTH)
+	if( g == nullptr || len > NAME_LENGTH ){
 		return 0;
+	}
 
 	// Find member (name)
 	for (pos = 0; pos < g->max_member && strncmp(g->member[pos].name, name, len); pos++);
@@ -1922,7 +1904,7 @@ int mapif_parse_GuildMasterChange(int fd, int guild_id, const char* name, int le
 
 int mapif_parse_GuildEmblemVersion(int fd, int guild_id, int version)
 {
-	guild* g = inter_guild_fromsql(guild_id);
+	std::shared_ptr<struct guild> g = inter_guild_fromsql( guild_id );
 
 	if (g == nullptr)
 		return 0;

+ 9 - 4
src/char/int_mail.cpp

@@ -3,6 +3,8 @@
 
 #include "int_mail.hpp"
 
+#include <memory>
+
 #include <stdlib.h>
 #include <string.h>
 
@@ -11,11 +13,14 @@
 #include "../common/socket.hpp"
 #include "../common/sql.hpp"
 #include "../common/strlib.hpp"
+#include "../common/utilities.hpp"
 
 #include "char.hpp"
 #include "char_mapif.hpp"
 #include "inter.hpp"
 
+using namespace rathena;
+
 bool mail_loadmessage(int mail_id, struct mail_message* msg);
 void mapif_Mail_return( int fd, uint32 char_id, int mail_id, uint32 account_id_receiver = 0, uint32 account_id_sender = 0 );
 bool mapif_Mail_delete( int fd, uint32 char_id, int mail_id, uint32 account_id = 0 );
@@ -460,10 +465,10 @@ bool mapif_Mail_delete( int fd, uint32 char_id, int mail_id, uint32 account_id )
 
 	// If the char server triggered this, check if we have to notify a map server
 	if( fd <= 0 ){
-		struct online_char_data* character;
+		std::shared_ptr<struct online_char_data> character = util::umap_find( char_get_onlinedb(), account_id );
 
 		// Check for online players
-		if( ( character = (struct online_char_data*)idb_get( char_get_onlinedb(), account_id ) ) != nullptr && character->server >= 0 ){
+		if( character != nullptr && character->server >= 0 ){
 			fd = map_server[character->server].fd;
 		}else{
 			// The request was triggered inside the character server or the player is offline now
@@ -561,10 +566,10 @@ void mapif_Mail_return( int fd, uint32 char_id, int mail_id, uint32 account_id_r
 
 	// If the char server triggered this, check if we have to notify a map server
 	if( fd <= 0 ){
-		struct online_char_data* character;
+		std::shared_ptr<struct online_char_data> character = util::umap_find( char_get_onlinedb(), account_id_sender );
 
 		// Check for online players
-		if( ( character = (struct online_char_data*)idb_get( char_get_onlinedb(), account_id_sender ) ) != nullptr && character->server >= 0 ){
+		if( character != nullptr && character->server >= 0 ){
 			fd = map_server[character->server].fd;
 		}else{
 			// The request was triggered inside the character server or the player is offline now

+ 99 - 100
src/char/int_party.cpp

@@ -3,6 +3,9 @@
 
 #include "int_party.hpp"
 
+#include <memory>
+#include <unordered_map>
+
 #include <stdlib.h>
 #include <string.h>
 
@@ -14,11 +17,14 @@
 #include "../common/socket.hpp"
 #include "../common/sql.hpp"
 #include "../common/strlib.hpp"
+#include "../common/utilities.hpp"
 
 #include "char.hpp"
 #include "char_mapif.hpp"
 #include "inter.hpp"
 
+using namespace rathena;
+
 struct party_data {
 	struct party party;
 	unsigned int min_lv, max_lv;
@@ -26,18 +32,18 @@ struct party_data {
 	unsigned char size; //Total size of party.
 };
 
-static struct party_data *party_pt;
-static DBMap* party_db_; // int party_id -> struct party_data*
+// int party_id -> struct party_data*
+static std::unordered_map<int32, std::shared_ptr<struct party_data>> party_db;
 
 int mapif_party_broken(int party_id,int flag);
-int party_check_empty(struct party_data *p);
+int party_check_empty( std::shared_ptr<struct party_data> p );
 int mapif_parse_PartyLeave(int fd, int party_id, uint32 account_id, uint32 char_id, char *name, enum e_party_member_withdraw type);
-int party_check_exp_share(struct party_data *p);
+int party_check_exp_share( std::shared_ptr<struct party_data> p );
 int mapif_party_optionchanged(int fd,struct party *p, uint32 account_id, int flag);
-int party_check_family_share(struct party_data *p);
+int party_check_family_share( std::shared_ptr<struct party_data> p );
 
 //Updates party's level range and unsets even share if broken.
-static int int_party_check_lv(struct party_data *p) {
+static int int_party_check_lv( std::shared_ptr<struct party_data> p ){
 	int i;
 	unsigned int lv;
 	p->min_lv = UINT_MAX;
@@ -62,8 +68,7 @@ static int int_party_check_lv(struct party_data *p) {
 	return 1;
 }
 //Calculates the state of a party.
-void int_party_calc_state(struct party_data *p)
-{
+void int_party_calc_state( std::shared_ptr<struct party_data> p ){
 	int i;
 	p->min_lv = UINT_MAX;
 	p->max_lv = 0;
@@ -116,7 +121,7 @@ int inter_party_tosql(struct party *p, int flag, int index)
 {
 	// 'party' ('party_id','name','exp','item','leader_id','leader_char')
 	char esc_name[NAME_LENGTH*2+1];// escaped party name
-	int party_id;
+	int32 party_id;
 
 	if( p == NULL || p->party_id == 0 )
 		return 0;
@@ -135,7 +140,7 @@ int inter_party_tosql(struct party *p, int flag, int index)
 		if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `party_id`='%d'", schema_config.party_db, party_id) )
 			Sql_ShowDebug(sql_handle);
 		//Remove from memory
-		idb_remove(party_db_, party_id);
+		party_db.erase( party_id );
 		return 1;
 	}
 
@@ -186,11 +191,9 @@ int inter_party_tosql(struct party *p, int flag, int index)
 }
 
 // Read party from mysql
-struct party_data *inter_party_fromsql(int party_id)
-{
+std::shared_ptr<struct party_data> inter_party_fromsql( int party_id ){
 	int leader_id = 0;
 	int leader_char = 0;
-	struct party_data* p;
 	struct party_member* m;
 	char* data;
 	size_t len;
@@ -203,12 +206,11 @@ struct party_data *inter_party_fromsql(int party_id)
 		return NULL;
 
 	//Load from memory
-	p = (struct party_data*)idb_get(party_db_, party_id);
-	if( p != NULL )
-		return p;
+	std::shared_ptr<struct party_data> p = util::umap_find( party_db, party_id );
 
-	p = party_pt;
-	memset(p, 0, sizeof(struct party_data));
+	if( p != nullptr ){
+		return p;
+	}
 
 	if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `party_id`, `name`,`exp`,`item`, `leader_id`, `leader_char` FROM `%s` WHERE `party_id`='%d'", schema_config.party_db, party_id) )
 	{
@@ -219,6 +221,8 @@ struct party_data *inter_party_fromsql(int party_id)
 	if( SQL_SUCCESS != Sql_NextRow(sql_handle) )
 		return NULL;
 
+	p = std::make_shared<struct party_data>();
+
 	p->party.party_id = party_id;
 	Sql_GetData(sql_handle, 1, &data, &len); memcpy(p->party.name, data, zmin(len, NAME_LENGTH));
 	Sql_GetData(sql_handle, 2, &data, NULL); p->party.exp = (atoi(data) ? 1 : 0);
@@ -249,47 +253,38 @@ struct party_data *inter_party_fromsql(int party_id)
 
 	if( charserv_config.save_log )
 		ShowInfo("Party loaded (%d - %s).\n", party_id, p->party.name);
-	//Add party to memory.
-	CREATE(p, struct party_data, 1);
-	memcpy(p, party_pt, sizeof(struct party_data));
+
 	//init state
 	int_party_calc_state(p);
-	idb_put(party_db_, party_id, p);
+
+	party_db[p->party.party_id] = p;
+
 	return p;
 }
 
 int inter_party_sql_init(void)
 {
-	//memory alloc
-	party_db_ = idb_alloc(DB_OPT_RELEASE_DATA);
-	party_pt = (struct party_data*)aCalloc(sizeof(struct party_data), 1);
-	if (!party_pt) {
-		ShowFatalError("inter_party_sql_init: Out of Memory!\n");
-		exit(EXIT_FAILURE);
-	}
-
-	/* Uncomment the following if you want to do a party_db cleanup (remove parties with no members) on startup.[Skotlex]
-	ShowStatus("cleaning party table...\n");
-	if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` USING `%s` LEFT JOIN `%s` ON `%s`.leader_id =`%s`.account_id AND `%s`.leader_char = `%s`.char_id WHERE `%s`.account_id IS NULL",
-		party_db, party_db, char_db, party_db, char_db, party_db, char_db, char_db) )
-		Sql_ShowDebug(sql_handle);
-	*/
+	// Remove parties with no members on startup from party_db. [Skotlex]
+	if (charserv_config.clear_parties) {
+		ShowStatus("Cleaning party table...\n");
+		if (SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` USING `%s` LEFT JOIN `%s` ON `%s`.leader_id =`%s`.account_id AND `%s`.leader_char = `%s`.char_id WHERE `%s`.account_id IS NULL",
+								   schema_config.party_db, schema_config.party_db, schema_config.char_db, schema_config.party_db, schema_config.char_db, schema_config.party_db, schema_config.char_db, schema_config.char_db))
+			Sql_ShowDebug(sql_handle);
+	}
+
 	return 0;
 }
 
 void inter_party_sql_final(void)
 {
-	party_db_->destroy(party_db_, NULL);
-	aFree(party_pt);
-	return;
+	party_db.clear();
 }
 
 // Search for the party according to its name
-struct party_data* search_partyname(char* str)
-{
+std::shared_ptr<struct party_data> search_partyname( char* str ){
 	char esc_name[NAME_LENGTH*2+1];
 	char* data;
-	struct party_data* p = NULL;
+	std::shared_ptr<struct party_data> p = nullptr;
 
 	Sql_EscapeStringLen(sql_handle, esc_name, str, safestrnlen(str, NAME_LENGTH));
 	if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `party_id` FROM `%s` WHERE `name`='%s'", schema_config.party_db, esc_name) )
@@ -304,7 +299,7 @@ struct party_data* search_partyname(char* str)
 	return p;
 }
 
-int party_check_family_share(struct party_data *p) {
+int party_check_family_share( std::shared_ptr<struct party_data> p ){
 	int i;
 	unsigned short map = 0;
 	if (!p->family)
@@ -340,16 +335,18 @@ int party_check_family_share(struct party_data *p) {
 }
 
 // Returns whether this party can keep having exp share or not.
-int party_check_exp_share(struct party_data *p)
-{
+int party_check_exp_share( std::shared_ptr<struct party_data> p ){
 	return (p->party.count < 2 || p->max_lv - p->min_lv <= party_share_level || party_check_family_share(p));
 }
 
 // Is there any member in the party?
-int party_check_empty(struct party_data *p)
-{
+int party_check_empty( std::shared_ptr<struct party_data> p ){
 	int i;
-	if (p==NULL||p->party.party_id==0) return 1;
+
+	if( p == nullptr || p->party.party_id == 0 ){
+		return 1;
+	}
+
 	for(i=0;i<MAX_PARTY && !p->party.member[i].account_id;i++);
 	if (i < MAX_PARTY) return 0;
 	// If there is no member, then break the party
@@ -503,15 +500,16 @@ int mapif_party_message(int party_id,uint32 account_id,char *mes,int len, int sf
 // Create Party
 int mapif_parse_CreateParty(int fd, char *name, int item, int item2, struct party_member *leader)
 {
-	struct party_data *p;
-	int i;
-	if( (p=search_partyname(name))!=NULL){
+	std::shared_ptr<struct party_data> p = search_partyname( name );
+
+	if( p != nullptr ){
 		mapif_party_created(fd,leader->account_id,leader->char_id,NULL);
 		return 0;
 	}
+
 	// Check Authorised letters/symbols in the name of the character
 	if (charserv_config.char_config.char_name_option == 1) { // only letters/symbols in char_name_letters are authorised
-		for (i = 0; i < NAME_LENGTH && name[i]; i++)
+		for( int i = 0; i < NAME_LENGTH && name[i]; i++ ){
 			if (strchr(charserv_config.char_config.char_name_letters, name[i]) == NULL) {
 				if( name[i] == '"' ) { /* client-special-char */
 					normalize_name(name,"\"");
@@ -521,15 +519,17 @@ int mapif_parse_CreateParty(int fd, char *name, int item, int item2, struct part
 				mapif_party_created(fd,leader->account_id,leader->char_id,NULL);
 				return 0;
 			}
+		}
 	} else if (charserv_config.char_config.char_name_option == 2) { // letters/symbols in char_name_letters are forbidden
-		for (i = 0; i < NAME_LENGTH && name[i]; i++)
+		for( int i = 0; i < NAME_LENGTH && name[i]; i++ ){
 			if (strchr(charserv_config.char_config.char_name_letters, name[i]) != NULL) {
 				mapif_party_created(fd,leader->account_id,leader->char_id,NULL);
 				return 0;
 			}
+		}
 	}
 
-	p = (struct party_data*)aCalloc(1, sizeof(struct party_data));
+	p = std::make_shared<struct party_data>();
 
 	memcpy(p->party.name,name,NAME_LENGTH);
 	p->party.exp=0;
@@ -543,11 +543,11 @@ int mapif_parse_CreateParty(int fd, char *name, int item, int item2, struct part
 	if (inter_party_tosql(&p->party,PS_CREATE|PS_ADDMEMBER,0)) {
 		//Add party to db
 		int_party_calc_state(p);
-		idb_put(party_db_, p->party.party_id, p);
+		party_db[p->party.party_id] = p;
 		mapif_party_info(fd, &p->party, 0);
 		mapif_party_created(fd,leader->account_id,leader->char_id,&p->party);
-	} else { //Failed to create party.
-		aFree(p);
+	}else{
+		// Failed to create party.
 		mapif_party_created(fd,leader->account_id,leader->char_id,NULL);
 	}
 
@@ -557,22 +557,23 @@ int mapif_parse_CreateParty(int fd, char *name, int item, int item2, struct part
 // Party information request
 void mapif_parse_PartyInfo(int fd, int party_id, uint32 char_id)
 {
-	struct party_data *p;
-	p = inter_party_fromsql(party_id);
-	if (p)
+	std::shared_ptr<struct party_data> p = inter_party_fromsql( party_id );
+
+	if( p != nullptr ){
 		mapif_party_info(fd, &p->party, char_id);
-	else
+	}else{
 		mapif_party_noinfo(fd, party_id, char_id);
+	}
 }
 
 // Add a player to party request
 int mapif_parse_PartyAddMember(int fd, int party_id, struct party_member *member)
 {
-	struct party_data *p;
 	int i;
 
-	p = inter_party_fromsql(party_id);
-	if( p == NULL || p->size == MAX_PARTY ) {
+	std::shared_ptr<struct party_data> p = inter_party_fromsql( party_id );
+
+	if( p == nullptr || p->size == MAX_PARTY ){
 		mapif_party_memberadded(fd, party_id, member->account_id, member->char_id, 1);
 		return 0;
 	}
@@ -606,9 +607,9 @@ int mapif_parse_PartyAddMember(int fd, int party_id, struct party_member *member
 //Party setting change request
 int mapif_parse_PartyChangeOption(int fd,int party_id,uint32 account_id,int exp,int item)
 {
-	struct party_data *p;
 	int flag = 0;
-	p = inter_party_fromsql(party_id);
+
+	std::shared_ptr<struct party_data> p = inter_party_fromsql( party_id );
 
 	if(!p)
 		return 0;
@@ -628,12 +629,12 @@ int mapif_parse_PartyChangeOption(int fd,int party_id,uint32 account_id,int exp,
 //Request leave party
 int mapif_parse_PartyLeave(int fd, int party_id, uint32 account_id, uint32 char_id, char *name, enum e_party_member_withdraw type)
 {
-	struct party_data *p;
 	int i,j=-1;
 
-	p = inter_party_fromsql(party_id);
-	if( p == NULL )
-	{// Party does not exists?
+	std::shared_ptr<struct party_data> p = inter_party_fromsql( party_id );
+
+	// Party does not exists?
+	if( p == nullptr ){
 		if( SQL_ERROR == Sql_Query(sql_handle, "UPDATE `%s` SET `party_id`='0' WHERE `party_id`='%d'", schema_config.char_db, party_id) )
 			Sql_ShowDebug(sql_handle);
 		return 0;
@@ -680,12 +681,13 @@ int mapif_parse_PartyLeave(int fd, int party_id, uint32 account_id, uint32 char_
 // When member goes to other map or levels up.
 int mapif_parse_PartyChangeMap(int fd, int party_id, uint32 account_id, uint32 char_id, unsigned short map, int online, unsigned int lv)
 {
-	struct party_data *p;
 	int i;
 
-	p = inter_party_fromsql(party_id);
-	if (p == NULL)
+	std::shared_ptr<struct party_data> p = inter_party_fromsql( party_id );
+
+	if( p == nullptr ){
 		return 0;
+	}
 
 	for(i = 0; i < MAX_PARTY &&
 		(p->party.member[i].account_id != account_id ||
@@ -738,9 +740,7 @@ int mapif_parse_PartyChangeMap(int fd, int party_id, uint32 account_id, uint32 c
 //Request party dissolution
 int mapif_parse_BreakParty(int fd,int party_id)
 {
-	struct party_data *p;
-
-	p = inter_party_fromsql(party_id);
+	std::shared_ptr<struct party_data> p = inter_party_fromsql( party_id );
 
 	if(!p)
 		return 0;
@@ -757,16 +757,12 @@ int mapif_parse_PartyMessage(int fd,int party_id,uint32 account_id,char *mes,int
 
 int mapif_parse_PartyLeaderChange(int fd,int party_id,uint32 account_id,uint32 char_id)
 {
-	struct party_data *p;
-	int i;
-
-	p = inter_party_fromsql(party_id);
+	std::shared_ptr<struct party_data> p = inter_party_fromsql( party_id );
 
 	if(!p)
 		return 0;
 
-	for (i = 0; i < MAX_PARTY; i++)
-	{
+	for( int i = 0; i < MAX_PARTY; i++ ){
 		if(p->party.member[i].leader)
 			p->party.member[i].leader = 0;
 		if(p->party.member[i].account_id == account_id &&
@@ -787,16 +783,16 @@ int mapif_parse_PartyLeaderChange(int fd,int party_id,uint32 account_id,uint32 c
  */
 int mapif_parse_PartyShareLevel(int fd,unsigned int share_lvl)
 {
-	struct party_data *p;
-	DBIterator* iter = db_iterator(party_db_);
-
 	party_share_level = share_lvl;
 
-	for(p = (struct party_data *)dbi_first(iter); dbi_exists(iter); p = (struct party_data *)dbi_next(iter)) { //Update online parties
-		if(p->party.count > 1)
-			int_party_calc_state(p);
+	// Update online parties
+	for( const auto& pair : party_db ){
+		std::shared_ptr<struct party_data> p = pair.second;
+
+		if( p->party.count > 1 ){
+			int_party_calc_state( p );
+		}
 	}
-	dbi_destroy(iter);
 
 	return 1;
 }
@@ -837,7 +833,6 @@ int inter_party_leave(int party_id,uint32 account_id, uint32 char_id, char *name
 
 int inter_party_CharOnline(uint32 char_id, int party_id)
 {
-	struct party_data* p;
 	int i;
 
 	if( party_id == -1 )
@@ -860,7 +855,8 @@ int inter_party_CharOnline(uint32 char_id, int party_id)
 	if (party_id == 0)
 		return 0; //No party...
 
-	p = inter_party_fromsql(party_id);
+	std::shared_ptr<struct party_data> p = inter_party_fromsql( party_id );
+
 	if(!p) {
 		ShowError("Character %d's party %d not found!\n", char_id, party_id);
 		return 0;
@@ -883,7 +879,6 @@ int inter_party_CharOnline(uint32 char_id, int party_id)
 }
 
 int inter_party_CharOffline(uint32 char_id, int party_id) {
-	struct party_data *p=NULL;
 	int i;
 
 	if( party_id == -1 )
@@ -907,8 +902,11 @@ int inter_party_CharOffline(uint32 char_id, int party_id) {
 		return 0; //No party...
 
 	//Character has a party, set character offline and check if they were the only member online
-	if ((p = inter_party_fromsql(party_id)) == NULL)
+	std::shared_ptr<struct party_data> p = inter_party_fromsql( party_id );
+
+	if( p == nullptr ){
 		return 0;
+	}
 
 	//Set member offline
 	for(i=0; i< MAX_PARTY; i++) {
@@ -923,20 +921,21 @@ int inter_party_CharOffline(uint32 char_id, int party_id) {
 		}
 	}
 
-	if(!p->party.count)
-		//Parties don't have any data that needs be saved at this point... so just remove it from memory.
-		idb_remove(party_db_, party_id);
+	// Parties don't have any data that needs be saved at this point... so just remove it from memory.
+	if( p->party.count == 0 ){
+		party_db.erase( party_id );
+	}
+
 	return 1;
 }
 
 int inter_party_charname_changed(int party_id, uint32 char_id, char *name)
 {
-	struct party_data* p = NULL;
 	int i;
 
-	p = inter_party_fromsql(party_id);
-	if( p == NULL || p->party.party_id == 0 )
-	{
+	std::shared_ptr<struct party_data> p = inter_party_fromsql( party_id );
+
+	if( p == nullptr || p->party.party_id == 0 ){
 		ShowError("inter_party_charname_changed: Can't find party %d.\n", party_id);
 		return 0;
 	}

+ 40 - 53
src/char/inter.cpp

@@ -3,11 +3,14 @@
 
 #include "inter.hpp"
 
+#include <memory>
+#include <unordered_map>
+#include <vector>
+
 #include <stdlib.h>
 #include <string.h>
 #include <string>
 #include <sys/stat.h> // for stat/lstat/fstat - [Dekamaster/Ultimate GM Tool]
-#include <vector>
 
 #include "../common/cbasetypes.hpp"
 #include "../common/database.hpp"
@@ -34,12 +37,12 @@
 #include "int_quest.hpp"
 #include "int_storage.hpp"
 
+using namespace rathena;
+
 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
-
 
 Sql* sql_handle = NULL;	///Link to mysql db, connection FD
 
@@ -66,13 +69,18 @@ int inter_recv_packet_length[] = {
 	 2,-1, 6, 6,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0,  0, 0,	// 30A0-  Clan packets
 };
 
+#ifndef WHISPER_MESSAGE_SIZE
+	#define WHISPER_MESSAGE_SIZE 512
+#endif
+
 struct WisData {
 	int id, fd, count, len, gmlvl;
 	t_tick tick;
-	char src[NAME_LENGTH], dst[NAME_LENGTH], msg[512];
+	char src[NAME_LENGTH], dst[NAME_LENGTH], msg[WHISPER_MESSAGE_SIZE];
 };
-static DBMap* wis_db = NULL; // int wis_id -> struct WisData*
-static int wis_dellist[WISDELLIST_MAX], wis_delnum;
+
+// int wis_id -> struct WisData*
+static std::unordered_map<int32, std::shared_ptr<struct WisData>> wis_db;
 
 /* from pc.cpp due to @accinfo. any ideas to replace this crap are more than welcome. */
 const char* job_name(int class_) {
@@ -405,7 +413,7 @@ void geoip_readdb(void){
 	ShowStatus("Finished Reading " CL_GREEN "GeoIP" CL_RESET " Database.\n");
 }
 /* [Dekamaster/Nightroad] */
-/* WHY NOT A DBMAP: There are millions of entries in GeoIP and it has its own algorithm to go quickly through them, a DBMap wouldn't be efficient */
+/* There are millions of entries in GeoIP and it has its own algorithm to go quickly through them */
 const char* geoip_getcountry(uint32 ipnum){
 	int depth;
 	unsigned int x;
@@ -987,7 +995,6 @@ int inter_init_sql(const char *file)
 			Sql_ShowDebug(sql_handle);
 	}
 
-	wis_db = idb_alloc(DB_OPT_RELEASE_DATA);
 	interServerDb.load();
 	inter_guild_sql_init();
 	inter_storage_sql_init();
@@ -1007,7 +1014,7 @@ int inter_init_sql(const char *file)
 // finalize
 void inter_final(void)
 {
-	wis_db->destroy(wis_db, NULL);
+	wis_db.clear();
 
 	inter_guild_sql_final();
 	inter_storage_sql_final();
@@ -1073,8 +1080,7 @@ int mapif_broadcast(unsigned char *mes, int len, unsigned long fontColor, short
 }
 
 // Wis sending
-int mapif_wis_message(struct WisData *wd)
-{
+int mapif_wis_message( std::shared_ptr<struct WisData> wd ){
 	unsigned char buf[2048];
 	int headersize = 12 + 2 * NAME_LENGTH;
 
@@ -1116,40 +1122,19 @@ int mapif_disconnectplayer(int fd, uint32 account_id, uint32 char_id, int reason
 
 //--------------------------------------------------------
 
-/**
- * Existence check of WISP data
- * @see DBApply
- */
-int check_ttl_wisdata_sub(DBKey key, DBData *data, va_list ap)
-{
-	t_tick tick;
-	struct WisData *wd = (struct WisData *)db_data2ptr(data);
-	tick = va_arg(ap, t_tick);
-
-	if (DIFF_TICK(tick, wd->tick) > WISDATA_TTL && wis_delnum < WISDELLIST_MAX)
-		wis_dellist[wis_delnum++] = wd->id;
-
-	return 0;
-}
-
-int check_ttl_wisdata(void)
-{
+void check_ttl_wisdata(){
 	t_tick tick = gettick();
-	int i;
 
-	do {
-		wis_delnum = 0;
-		wis_db->foreach(wis_db, check_ttl_wisdata_sub, tick);
-		for(i = 0; i < wis_delnum; i++) {
-			struct WisData *wd = (struct WisData*)idb_get(wis_db, wis_dellist[i]);
-			ShowWarning("inter: wis data id=%d time out : from %s to %s\n", wd->id, wd->src, wd->dst);
-			// removed. not send information after a timeout. Just no answer for the player
-			//mapif_wis_reply(wd->fd, wd->src, 1); // flag: 0: success to send wisper, 1: target character is not loged in?, 2: ignored by target
-			idb_remove(wis_db, wd->id);
-		}
-	} while(wis_delnum >= WISDELLIST_MAX);
+	for( auto it = wis_db.begin(); it != wis_db.end(); ){
+		std::shared_ptr<struct WisData> wd = it->second;
 
-	return 0;
+		if( DIFF_TICK( tick, wd->tick ) > WISDATA_TTL ){
+			ShowWarning( "inter: wis data id=%d time out : from %s to %s\n", wd->id, wd->src, wd->dst );
+			it = wis_db.erase( it );
+		}else{
+			it++;
+		}
+	}
 }
 
 //--------------------------------------------------------
@@ -1193,7 +1178,6 @@ int mapif_wis_reply( int mapserver_fd, char* target, uint8 flag ){
 // Wisp/page request to send
 int mapif_parse_WisRequest(int fd)
 {
-	struct WisData* wd;
 	char name[NAME_LENGTH];
 	char esc_name[NAME_LENGTH*2+1];// escaped name
 	char* data;
@@ -1203,7 +1187,7 @@ int mapif_parse_WisRequest(int fd)
 
 	if ( fd <= 0 ) {return 0;} // check if we have a valid fd
 
-	if (RFIFOW(fd,2)-headersize >= sizeof(wd->msg)) {
+	if( RFIFOW( fd, 2 ) - headersize >= WHISPER_MESSAGE_SIZE ){
 		ShowWarning("inter: Wis message size too long.\n");
 		return 0;
 	} else if (RFIFOW(fd,2)-headersize <= 0) { // normaly, impossible, but who knows...
@@ -1237,11 +1221,11 @@ int mapif_parse_WisRequest(int fd)
 		{
 			static int wisid = 0;
 
-			CREATE(wd, struct WisData, 1);
-
 			// Whether the failure of previous wisp/page transmission (timeout)
 			check_ttl_wisdata();
 
+			std::shared_ptr<struct WisData> wd = std::make_shared<struct WisData>();
+
 			wd->id = ++wisid;
 			wd->fd = fd;
 			wd->len= RFIFOW(fd,2)-headersize;
@@ -1250,8 +1234,10 @@ int mapif_parse_WisRequest(int fd)
 			safestrncpy(wd->dst, RFIFOCP(fd,8+NAME_LENGTH), NAME_LENGTH);
 			safestrncpy(wd->msg, RFIFOCP(fd,8+2*NAME_LENGTH), wd->len);
 			wd->tick = gettick();
-			idb_put(wis_db, wd->id, wd);
-			mapif_wis_message(wd);
+
+			wis_db[wd->id] = wd;
+
+			mapif_wis_message( wd );
 		}
 	}
 
@@ -1263,19 +1249,20 @@ int mapif_parse_WisRequest(int fd)
 // Wisp/page transmission result
 int mapif_parse_WisReply(int fd)
 {
-	int id;
+	int32 id;
 	uint8 flag;
-	struct WisData *wd;
 
 	id = RFIFOL(fd,2);
 	flag = RFIFOB(fd,6);
-	wd = (struct WisData*)idb_get(wis_db, id);
-	if (wd == NULL)
+	std::shared_ptr<struct WisData> wd = util::umap_find( wis_db, id );
+
+	if( wd == nullptr ){
 		return 0;	// This wisp was probably suppress before, because it was timeout of because of target was found on another map-server
+	}
 
 	if ((--wd->count) <= 0 || flag != 1) {
 		mapif_wis_reply(wd->fd, wd->src, flag); // flag: 0: success to send wisper, 1: target character is not loged in?, 2: ignored by target
-		idb_remove(wis_db, id);
+		wis_db.erase( id );
 	}
 
 	return 0;

+ 1 - 0
src/common/common-minicore.vcxproj

@@ -128,6 +128,7 @@
       <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
       <AdditionalIncludeDirectories>$(SolutionDir)3rdparty\zlib\include\;$(SolutionDir)3rdparty\mysql\include\;$(SolutionDir)3rdparty\libconfig\;$(SolutionDir)3rdparty\yaml-cpp\include\</AdditionalIncludeDirectories>
       <AdditionalIncludeDirectories>$(SolutionDir)3rdparty\zlib\include\;$(SolutionDir)3rdparty\libconfig\</AdditionalIncludeDirectories>
+      <ConformanceMode>true</ConformanceMode>
     </ClCompile>
     <Link>
       <SubSystem>Windows</SubSystem>

+ 2 - 1
src/common/common.vcxproj

@@ -160,6 +160,7 @@
       <PreprocessorDefinitions>$(DefineConstants);WIN32;FD_SETSIZE=4096;PCRE_SUPPORT;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;_WINSOCK_DEPRECATED_NO_WARNINGS;LIBCONFIG_STATIC;YY_USE_CONST;_DEBUG;_LIB;_ITERATOR_DEBUG_LEVEL=0;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
       <AdditionalIncludeDirectories>$(SolutionDir)\3rdparty\rapidyaml\src;$(SolutionDir)\3rdparty\rapidyaml\ext\c4core\src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <ConformanceMode>true</ConformanceMode>
     </ClCompile>
     <Link>
       <SubSystem>Windows</SubSystem>
@@ -235,4 +236,4 @@
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
   <ImportGroup Label="ExtensionTargets">
   </ImportGroup>
-</Project>
+</Project>

+ 8 - 8
src/common/core.cpp

@@ -377,12 +377,12 @@ int Core::start( int argc, char **argv ){
 	}
 
 	// If initialization did not trigger shutdown
-	if( this->status != e_core_status::STOPPING ){
+	if( this->m_status != e_core_status::STOPPING ){
 		this->set_status( e_core_status::SERVER_INITIALIZED );
 
 		this->set_status( e_core_status::RUNNING );
 #ifndef MINICORE
-		if( !this->run_once ){
+		if( !this->m_run_once ){
 			// Main runtime cycle
 			while( this->get_status() == e_core_status::RUNNING ){
 				t_tick next = do_timer( gettick_nocache() );
@@ -445,15 +445,15 @@ void Core::finalize(){
 }
 
 void Core::set_status( e_core_status status ){
-	this->status = status;
+	this->m_status = status;
 }
 
 e_core_status Core::get_status(){
-	return this->status;
+	return this->m_status;
 }
 
 e_core_type Core::get_type(){
-	return this->type;
+	return this->m_type;
 }
 
 bool Core::is_running(){
@@ -461,17 +461,17 @@ bool Core::is_running(){
 }
 
 void Core::set_run_once( bool run_once ){
-	this->run_once = run_once;
+	this->m_run_once = run_once;
 }
 
 void Core::signal_crash(){
 	this->set_status( e_core_status::STOPPING );
 
-	if( this->crashed ){
+	if( this->m_crashed ){
 		ShowFatalError( "Received another crash signal, while trying to handle the last crash!\n" );
 	}else{
 		ShowFatalError( "Received a crash signal, trying to handle it as good as possible!\n" );
-		this->crashed = true;
+		this->m_crashed = true;
 		this->handle_crash();
 	}
 

+ 8 - 8
src/common/core.hpp

@@ -59,10 +59,10 @@ namespace rathena{
 
 		class Core{
 			private:
-				e_core_status status;
-				e_core_type type;
-				bool run_once;
-				bool crashed;
+				e_core_status m_status;
+				e_core_type m_type;
+				bool m_run_once;
+				bool m_crashed;
 
 			protected:
 				virtual bool initialize( int argc, char* argv[] );
@@ -74,10 +74,10 @@ namespace rathena{
 
 			public:
 				Core( e_core_type type ){
-					this->status = e_core_status::NOT_STARTED;
-					this->run_once = false;
-					this->crashed = false;
-					this->type = type;
+					this->m_status = e_core_status::NOT_STARTED;
+					this->m_run_once = false;
+					this->m_crashed = false;
+					this->m_type = type;
 				}
 
 				e_core_status get_status();

+ 22 - 2
src/common/mmo.hpp

@@ -81,6 +81,12 @@ typedef uint32 t_itemid;
 #define MAX_AMOUNT 30000 ////Max amount of a single stacked item
 #define MAX_ZENY INT_MAX ///Max zeny
 #define MAX_BANK_ZENY SINT32_MAX ///Max zeny in Bank
+#ifndef MAX_CASHPOINT
+	#define MAX_CASHPOINT INT_MAX
+#endif
+#ifndef MAX_KAFRAPOINT
+	#define MAX_KAFRAPOINT INT_MAX
+#endif
 #define MAX_FAME 1000000000 ///Max fame points
 #define MAX_CART 100 ///Maximum item in cart
 #define MAX_SKILL 1454 ///Maximum skill can be hold by Player, Homunculus, & Mercenary (skill list) AND skill_db limit
@@ -353,8 +359,8 @@ enum equip_pos : uint32 {
 };
 
 struct point {
-	unsigned short map;
-	short x,y;
+	uint16 map;
+	uint16 x,y;
 };
 
 struct startitem {
@@ -756,11 +762,25 @@ struct guild {
 	int32 chargeshout_flag_id;
 };
 
+enum e_woe_type{
+	WOE_FIRST_EDITION = 1,
+	WOE_SECOND_EDITION,
+	WOE_THIRD_EDITION,
+	WOE_MAX
+};
+
 struct guild_castle {
 	int castle_id;
 	int mapindex;
 	char castle_name[NAME_LENGTH];
 	char castle_event[NPC_NAME_LENGTH];
+	e_woe_type type;
+	uint16 client_id;
+	bool warp_enabled;
+	uint16 warp_x;
+	uint16 warp_y;
+	uint32 zeny;
+	uint32 zeny_siege;
 	int guild_id;
 	int economy;
 	int defense;

+ 1 - 0
src/login/login-server.vcxproj

@@ -109,6 +109,7 @@
       <Optimization>Disabled</Optimization>
       <PreprocessorDefinitions>FD_SETSIZE=4096;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;_WINSOCK_DEPRECATED_NO_WARNINGS;LIBCONFIG_STATIC;YY_USE_CONST;WIN32;_DEBUG;_CONSOLE;_LIB;_ITERATOR_DEBUG_LEVEL=0;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
+      <ConformanceMode>true</ConformanceMode>
     </ClCompile>
     <Link>
       <SubSystem>Console</SubSystem>

+ 4 - 0
src/map/atcommand.cpp

@@ -4319,6 +4319,9 @@ ACMD_FUNC(reload) {
 	} else if (strstr(command, "attendancedb") || strncmp(message, "attendancedb", 4) == 0) {
 		attendance_db.reload();
 		clif_displaymessage(fd, msg_txt(sd, 795)); // Attendance database has been reloaded.
+	}else if( strstr( command, "barterdb" ) || strncmp( message, "barterdb", 4 ) == 0 ){
+		barter_db.reload();
+		clif_displaymessage(fd, msg_txt(sd, 830)); // Barter database has been reloaded.
 	}
 
 	return 0;
@@ -10951,6 +10954,7 @@ void atcommand_basecommands(void) {
 		ACMD_DEF2("reloadinstancedb", reload),
 		ACMD_DEF2("reloadachievementdb",reload),
 		ACMD_DEF2("reloadattendancedb",reload),
+		ACMD_DEF2("reloadbarterdb",reload),
 		ACMD_DEF(partysharelvl),
 		ACMD_DEF(mapinfo),
 		ACMD_DEF(dye),

+ 34 - 2
src/map/battleground.cpp

@@ -298,13 +298,45 @@ uint64 BattlegroundDatabase::parseBodyNode(const ryml::NodeRef& node) {
 					}
 
 					if (this->nodeExists(team[team_name], "RespawnX")) {
-						if (!this->asInt16(team[team_name], "RespawnX", team_ptr->warp_x))
+						uint16 warp_x;
+
+						if (!this->asUInt16(team[team_name], "RespawnX", warp_x))
+							return 0;
+
+						if (warp_x == 0) {
+							this->invalidWarning(node["RespawnX"], "RespawnX has to be greater than zero.\n");
 							return 0;
+						}
+
+						map_data *md = map_getmapdata(map_mapindex2mapid(map_entry.mapindex));
+
+						if (warp_x >= md->xs) {
+							this->invalidWarning(node["RespawnX"], "RespawnX has to be smaller than %hu.\n", md->xs);
+							return 0;
+						}
+
+						team_ptr->warp_x = warp_x;
 					}
 
 					if (this->nodeExists(team[team_name], "RespawnY")) {
-						if (!this->asInt16(team[team_name], "RespawnY", team_ptr->warp_y))
+						uint16 warp_y;
+
+						if (!this->asUInt16(team[team_name], "RespawnY", warp_y))
+							return 0;
+
+						if (warp_y == 0) {
+							this->invalidWarning(node["RespawnY"], "RespawnY has to be greater than zero.\n");
 							return 0;
+						}
+
+						map_data *md = map_getmapdata(map_mapindex2mapid(map_entry.mapindex));
+
+						if (warp_y >= md->ys) {
+							this->invalidWarning(node["RespawnY"], "RespawnY has to be smaller than %hu.\n", md->ys);
+							return 0;
+						}
+
+						team_ptr->warp_y = warp_y;
 					}
 
 					if (this->nodeExists(team[team_name], "DeathEvent")) {

+ 1 - 1
src/map/battleground.hpp

@@ -31,7 +31,7 @@ struct s_battleground_data {
 };
 
 struct s_battleground_team {
-	int16 warp_x, warp_y; ///< Team respawn coordinates
+	uint16 warp_x, warp_y; ///< Team respawn coordinates
 	std::string quit_event, ///< Team NPC Event to call on log out events
 		death_event, ///< Team NPC Event to call on death events
 		active_event, ///< Team NPC Event to call on players joining an active battleground

+ 103 - 142
src/map/cashshop.cpp

@@ -15,144 +15,132 @@
 #include "pc.hpp" // s_map_session_data
 #include "pet.hpp" // pet_create_egg
 
-struct cash_item_db cash_shop_items[CASHSHOP_TAB_MAX];
 #if PACKETVER_SUPPORTS_SALES
 struct sale_item_db sale_items;
 #endif
-bool cash_shop_defined = false;
 
-extern char item_cash_table[32];
-extern char item_cash2_table[32];
 extern char sales_table[32];
 
-/*
- * Reads one line from database and assigns it to RAM.
- * return
- *  0 = failure
- *  1 = success
- */
-static bool cashshop_parse_dbrow(char* fields[], int columns, int current) {
-	uint16 tab = atoi(fields[0]);
-	t_itemid nameid = strtoul(fields[1], nullptr, 10);
-	uint32 price = atoi(fields[2]);
-	int j;
-	struct cash_item_data* cid;
-
-	if( !item_db.exists( nameid ) ){
-		ShowWarning( "cashshop_parse_dbrow: Invalid ID %u in line '%d', skipping...\n", nameid, current );
+const std::string CashShopDatabase::getDefaultLocation(){
+	return std::string( db_path ) + "/item_cash.yml";
+}
+
+uint64 CashShopDatabase::parseBodyNode( const ryml::NodeRef& node ){
+	std::string name;
+
+	if( !this->asString( node, "Tab", name ) ){
 		return 0;
 	}
 
-	if( tab >= CASHSHOP_TAB_MAX ){
-		ShowWarning( "cashshop_parse_dbrow: Invalid tab %d in line '%d', skipping...\n", tab, current );
-		return 0;
-	}else if( price < 1 ){
-		ShowWarning( "cashshop_parse_dbrow: Invalid price %d in line '%d', skipping...\n", price, current );
+	std::string tab_constant = "CASHSHOP_TAB_" + name;
+	int64 constant;
+
+	if( !script_get_constant( tab_constant.c_str(), &constant ) ){
+		this->invalidWarning( node["Tab"], "Invalid tab %s, skipping.\n", tab_constant.c_str() );
 		return 0;
 	}
 
-	ARR_FIND( 0, cash_shop_items[tab].count, j, nameid == cash_shop_items[tab].item[j]->nameid );
-
-	if( j == cash_shop_items[tab].count ){
-		RECREATE( cash_shop_items[tab].item, struct cash_item_data *, ++cash_shop_items[tab].count );
-		CREATE( cash_shop_items[tab].item[ cash_shop_items[tab].count - 1], struct cash_item_data, 1 );
-		cid = cash_shop_items[tab].item[ cash_shop_items[tab].count - 1];
-	}else{
-		cid = cash_shop_items[tab].item[j];
+	if( constant < CASHSHOP_TAB_NEW || constant >= CASHSHOP_TAB_MAX ){
+		this->invalidWarning( node["Tab"], "Tab %" PRId64 " is out of range, skipping.\n", constant );
+		return 0;
 	}
 
-	cid->nameid = nameid;
-	cid->price = price;
-	cash_shop_defined = true;
+	e_cash_shop_tab tab = static_cast<e_cash_shop_tab>( constant );
 
-	return 1;
-}
+	std::shared_ptr<s_cash_item_tab> entry = this->find( tab );
+	bool exists = entry != nullptr;
 
-/*
- * Reads database from TXT format,
- * parses lines and sends them to parse_dbrow.
- */
-static void cashshop_read_db_txt( void ){
-	const char* dbsubpath[] = {
-		"",
-		"/" DBIMPORT,
-	};
-	int fi;
-
-	for( fi = 0; fi < ARRAYLENGTH( dbsubpath ); ++fi ){
-		uint8 n1 = (uint8)(strlen(db_path)+strlen(dbsubpath[fi])+1);
-		uint8 n2 = (uint8)(strlen(db_path)+strlen(DBPATH)+strlen(dbsubpath[fi])+1);
-		char* dbsubpath1 = (char*)aMalloc(n1+1);
-		char* dbsubpath2 = (char*)aMalloc(n2+1);
-
-		if(fi==0) {
-			safesnprintf(dbsubpath1,n1,"%s%s",db_path,dbsubpath[fi]);
-			safesnprintf(dbsubpath2,n2,"%s/%s%s",db_path,DBPATH,dbsubpath[fi]);
-		}
-		else {
-			safesnprintf(dbsubpath1,n1,"%s%s",db_path,dbsubpath[fi]);
-			safesnprintf(dbsubpath2,n1,"%s%s",db_path,dbsubpath[fi]);
+	if( !exists ){
+		if( !this->nodesExist( node, { "Items" } ) ){
+			return 0;
 		}
 
-		sv_readdb(dbsubpath2, "item_cash_db.txt", ',', 3, 3, -1, &cashshop_parse_dbrow, fi > 0);
-
-		aFree(dbsubpath1);
-		aFree(dbsubpath2);
+		entry = std::make_shared<s_cash_item_tab>();
+		entry->tab = tab;
 	}
-}
 
-/*
- * Reads database from SQL format,
- * parses line and sends them to parse_dbrow.
- */
-static int cashshop_read_db_sql( void ){
-	const char* cash_db_name[] = { item_cash_table, item_cash2_table };
-	int fi;
+	for( const ryml::NodeRef& it : node["Items"] ){
+		std::string item_name;
+
+		if( !this->asString( it, "Item", item_name ) ){
+			return 0;
+		}
 
-	for( fi = 0; fi < ARRAYLENGTH( cash_db_name ); ++fi ){
-		uint32 lines = 0, count = 0;
+		std::shared_ptr<item_data> item = item_db.search_aegisname( item_name.c_str() );
 
-		if( SQL_ERROR == Sql_Query( mmysql_handle, "SELECT `tab`, `item_id`, `price` FROM `%s`", cash_db_name[fi] ) ){
-			Sql_ShowDebug( mmysql_handle );
+		if( item == nullptr ){
+			this->invalidWarning( it["Item"], "Cash item %s does not exist, skipping.\n", item_name.c_str() );
 			continue;
 		}
 
+		std::shared_ptr<s_cash_item> cash_item = nullptr;
+		bool cash_item_exists = false;
 
-		while( SQL_SUCCESS == Sql_NextRow( mmysql_handle ) ){
-			char* str[3];
-			char dummy[256] = "";
-			int i;
+		for( std::shared_ptr<s_cash_item> cash_it : entry->items ){
+			if( cash_it->nameid == item->nameid ){
+				cash_item = cash_it;
+				cash_item_exists = true;
+				break;
+			}
+		}
 
-			++lines;
+		if( !cash_item_exists ){
+			cash_item = std::make_shared<s_cash_item>();
+			cash_item->nameid = item->nameid;
+		}
 
-			for( i = 0; i < 3; ++i ){
-				Sql_GetData( mmysql_handle, i, &str[i], NULL );
+		uint32 price;
 
-				if( str[i] == NULL ){
-					str[i] = dummy;
-				}
-			}
+		if( !this->asUInt32( it, "Price", price ) ){
+			return 0;
+		}
 
-			if( !cashshop_parse_dbrow( str, 3, lines ) ) {
-				ShowError("cashshop_read_db_sql: Cannot process table '%s' at line '%d', skipping...\n", cash_db_name[fi], lines);
-				continue;
-			}
+		if( price == 0 ){
+			this->invalidWarning( it["Price"], "Price has to be greater than zero." );
+			return 0;
+		}
 
-			++count;
+		if( price > MAX_CASHPOINT ){
+			this->invalidWarning( it["Price"], "Price has to be lower than MAX_CASHPOINT(%d).", MAX_CASHPOINT );
+			return 0;
 		}
 
-		Sql_FreeResult( mmysql_handle );
+		cash_item->price = price;
+
+		if( !cash_item_exists ){
+			entry->items.push_back( cash_item );
+		}
+	}
 
-		ShowStatus( "Done reading '" CL_WHITE "%u" CL_RESET "' entries in '" CL_WHITE "%s" CL_RESET "'.\n", count, cash_db_name[fi] );
+	if( !exists ){
+		this->put( tab, entry );
 	}
 
-	return 0;
+	return 1;
+}
+
+std::shared_ptr<s_cash_item> CashShopDatabase::findItemInTab( e_cash_shop_tab tab, t_itemid nameid ){
+	std::shared_ptr<s_cash_item_tab> cash_tab = this->find( tab );
+
+	if( cash_tab == nullptr ){
+		return nullptr;
+	}
+
+	for( std::shared_ptr<s_cash_item> cash_it : cash_tab->items ){
+		if( cash_it->nameid == nameid ){
+			return cash_it;
+		}
+	}
+
+	return nullptr;
 }
 
+CashShopDatabase cash_shop_db;
+
 #if PACKETVER_SUPPORTS_SALES
 static bool sale_parse_dbrow( char* fields[], int columns, int current ){
 	t_itemid nameid = strtoul(fields[0], nullptr, 10);
-	int start = atoi(fields[1]), end = atoi(fields[2]), amount = atoi(fields[3]), i;
+	int start = atoi(fields[1]), end = atoi(fields[2]), amount = atoi(fields[3]);
 	time_t now = time(NULL);
 	struct sale_item_data* sale_item = NULL;
 
@@ -161,10 +149,9 @@ static bool sale_parse_dbrow( char* fields[], int columns, int current ){
 		return false;
 	}
 
-	ARR_FIND( 0, cash_shop_items[CASHSHOP_TAB_SALE].count, i, cash_shop_items[CASHSHOP_TAB_SALE].item[i]->nameid == nameid );
-
-	if( i == cash_shop_items[CASHSHOP_TAB_SALE].count ){
-		ShowWarning( "sale_parse_dbrow: ID %u is not registered in the limited tab in line '%d', skipping...\n", nameid, current );
+	// Check if the item exists in the sales tab
+	if( cash_shop_db.findItemInTab( CASHSHOP_TAB_SALE, nameid ) == nullptr ){
+		ShowWarning( "sale_parse_dbrow: ID %u is not registered in the Sale tab in line '%d', skipping...\n", nameid, current );
 		return false;
 	}
 
@@ -268,14 +255,8 @@ static TIMER_FUNC(sale_start_timer){
 }
 
 enum e_sale_add_result sale_add_item( t_itemid nameid, int32 count, time_t from, time_t to ){
-	int i;
-	struct sale_item_data* sale_item;
-
 	// Check if the item exists in the sales tab
-	ARR_FIND( 0, cash_shop_items[CASHSHOP_TAB_SALE].count, i, cash_shop_items[CASHSHOP_TAB_SALE].item[i]->nameid == nameid );
-
-	// Item does not exist in the sales tab
-	if( i == cash_shop_items[CASHSHOP_TAB_SALE].count ){
+	if( cash_shop_db.findItemInTab( CASHSHOP_TAB_SALE, nameid ) == nullptr ){
 		return SALE_ADD_FAILED;
 	}
 
@@ -306,7 +287,7 @@ enum e_sale_add_result sale_add_item( t_itemid nameid, int32 count, time_t from,
 
 	RECREATE(sale_items.item, struct sale_item_data *, ++sale_items.count);
 	CREATE(sale_items.item[sale_items.count - 1], struct sale_item_data, 1);
-	sale_item = sale_items.item[sale_items.count - 1];
+	struct sale_item_data* sale_item = sale_items.item[sale_items.count - 1];
 
 	sale_item->nameid = nameid;
 	sale_item->start = from;
@@ -421,23 +402,13 @@ void sale_notify_login( map_session_data* sd ){
 }
 #endif
 
-/*
- * Determines whether to read TXT or SQL database
- * based on 'db_use_sqldbs' in conf/map_athena.conf.
- */
 static void cashshop_read_db( void ){
+	cash_shop_db.load();
+
 #if PACKETVER_SUPPORTS_SALES
 	int i;
 	time_t now = time(NULL);
-#endif
 
-	if( db_use_sqldbs ){
-		cashshop_read_db_sql();
-	} else {
-		cashshop_read_db_txt();
-	}
-
-#if PACKETVER_SUPPORTS_SALES
 	sale_read_db_sql();
 
 	// Clean outdated sales
@@ -472,7 +443,7 @@ bool cashshop_buylist( map_session_data* sd, uint32 kafrapoints, int n, struct P
 	uint32 totalweight = 0;
 	int i,new_;
 
-	if( sd == NULL || item_list == NULL || !cash_shop_defined){
+	if( sd == NULL || item_list == NULL ){
 		clif_cashshop_result( sd, 0, CASHSHOP_RESULT_ERROR_UNKNOWN );
 		return false;
 	}else if( sd->state.trading ){
@@ -486,21 +457,19 @@ bool cashshop_buylist( map_session_data* sd, uint32 kafrapoints, int n, struct P
 		t_itemid nameid = item_list[i].itemId;
 		uint32 quantity = item_list[i].amount;
 		uint16 tab = item_list[i].tab;
-		int j;
 
 		if( tab >= CASHSHOP_TAB_MAX ){
 			clif_cashshop_result( sd, nameid, CASHSHOP_RESULT_ERROR_UNKNOWN );
 			return false;
 		}
 
-		ARR_FIND( 0, cash_shop_items[tab].count, j, nameid == cash_shop_items[tab].item[j]->nameid || nameid == itemdb_viewid(cash_shop_items[tab].item[j]->nameid) );
+		std::shared_ptr<s_cash_item> cash_item = cash_shop_db.findItemInTab( static_cast<e_cash_shop_tab>( tab ), nameid );
 
-		if( j == cash_shop_items[tab].count ){
+		if( cash_item == nullptr ){
 			clif_cashshop_result( sd, nameid, CASHSHOP_RESULT_ERROR_UNKONWN_ITEM );
 			return false;
 		}
 
-		nameid = item_list[i].itemId = cash_shop_items[tab].item[j]->nameid; //item_avail replacement
 
 		std::shared_ptr<item_data> id = item_db.find(nameid);
 
@@ -515,8 +484,8 @@ bool cashshop_buylist( map_session_data* sd, uint32 kafrapoints, int n, struct P
 			return false;
 		}
 
-#if PACKETVER_SUPPORTS_SALES
 		if( tab == CASHSHOP_TAB_SALE ){
+#if PACKETVER_SUPPORTS_SALES
 			struct sale_item_data* sale = sale_find_item( nameid, true );
 
 			if( sale == NULL ){
@@ -532,8 +501,11 @@ bool cashshop_buylist( map_session_data* sd, uint32 kafrapoints, int n, struct P
 				clif_sale_amount( sale, &sd->bl, SELF );
 				return false;
 			}
-		}
+#else
+			clif_cashshop_result( sd, nameid, CASHSHOP_RESULT_ERROR_UNKNOWN );
+			return false;
 #endif
+		}
 
 		switch( pc_checkadditem( sd, nameid, quantity ) ){
 			case CHKADDITEM_EXIST:
@@ -548,7 +520,7 @@ bool cashshop_buylist( map_session_data* sd, uint32 kafrapoints, int n, struct P
 				return false;
 		}
 
-		totalcash += cash_shop_items[tab].item[j]->price * quantity;
+		totalcash += cash_item->price * quantity;
 		totalweight += itemdb_weight( nameid ) * quantity;
 	}
 
@@ -664,19 +636,11 @@ void cashshop_reloaddb( void ){
  * Closes all and cleanup.
  */
 void do_final_cashshop( void ){
-	int tab, i;
-
-	for( tab = CASHSHOP_TAB_NEW; tab < CASHSHOP_TAB_MAX; tab++ ){
-		for( i = 0; i < cash_shop_items[tab].count; i++ ){
-			aFree( cash_shop_items[tab].item[i] );
-		}
-		aFree( cash_shop_items[tab].item );
-	}
-	memset( cash_shop_items, 0, sizeof( cash_shop_items ) );
+	cash_shop_db.clear();
 
 #if PACKETVER_SUPPORTS_SALES
 	if( sale_items.count > 0 ){
-		for( i = 0; i < sale_items.count; i++ ){
+		for( int i = 0; i < sale_items.count; i++ ){
 			struct sale_item_data* it = sale_items.item[i];
 
 			if( it->timer_start != INVALID_TIMER ){
@@ -702,10 +666,7 @@ void do_final_cashshop( void ){
 
 /*
  * Initializes cashshop class.
- * return
- *  0 : success
  */
 void do_init_cashshop( void ){
-	cash_shop_defined = false;
 	cashshop_read_db();
 }

+ 29 - 16
src/map/cashshop.hpp

@@ -4,9 +4,13 @@
 #ifndef CASHSHOP_HPP
 #define CASHSHOP_HPP
 
+#include <memory> // std::shared_ptr
+#include <vector> // std::vector
+
 #include "../config/core.hpp"
 
 #include "../common/cbasetypes.hpp" // uint16, uint32
+#include "../common/database.hpp" // TypesafeYamlDatabase
 #include "../common/mmo.hpp" // t_itemid
 #include "../common/timer.hpp" // ShowWarning, ShowStatus
 
@@ -17,20 +21,17 @@ void do_final_cashshop( void );
 void cashshop_reloaddb( void );
 bool cashshop_buylist( map_session_data* sd, uint32 kafrapoints, int n, struct PACKET_CZ_SE_PC_BUY_CASHITEM_LIST_sub* item_list );
 
-// Taken from AEGIS
-enum CASH_SHOP_TAB_CODE
-{
+// Taken from AEGIS (CASH_SHOP_TAB_CODE)
+enum e_cash_shop_tab : uint16{
 	CASHSHOP_TAB_NEW =  0x0,
-	CASHSHOP_TAB_POPULAR,
+	CASHSHOP_TAB_HOT,
 	CASHSHOP_TAB_LIMITED,
 	CASHSHOP_TAB_RENTAL,
-	CASHSHOP_TAB_PERPETUITY,
-	CASHSHOP_TAB_BUFF,
-	CASHSHOP_TAB_RECOVERY,
-	CASHSHOP_TAB_ETC,
-#if PACKETVER_SUPPORTS_SALES
+	CASHSHOP_TAB_PERMANENT,
+	CASHSHOP_TAB_SCROLLS,
+	CASHSHOP_TAB_CONSUMABLES,
+	CASHSHOP_TAB_OTHER,
 	CASHSHOP_TAB_SALE,
-#endif
 	CASHSHOP_TAB_MAX
 };
 
@@ -52,18 +53,30 @@ enum CASHSHOP_BUY_RESULT
 	CASHSHOP_RESULT_ERROR_BUSY =  0xc,
 };
 
-struct cash_item_data{
+struct s_cash_item{
 	t_itemid nameid;
 	uint32 price;
 };
 
-struct cash_item_db{
-	struct cash_item_data** item;
-	uint32 count;
+struct s_cash_item_tab{
+	e_cash_shop_tab tab;
+	std::vector<std::shared_ptr<s_cash_item>> items;
+};
+
+class CashShopDatabase : public TypesafeYamlDatabase<e_cash_shop_tab, s_cash_item_tab>{
+public:
+	CashShopDatabase() : TypesafeYamlDatabase( "ITEM_CASH_DB", 1 ){
+
+	}
+
+	const std::string getDefaultLocation();
+	uint64 parseBodyNode( const ryml::NodeRef& node );
+
+	// Additional
+	std::shared_ptr<s_cash_item> findItemInTab( e_cash_shop_tab tab, t_itemid nameid );
 };
 
-extern struct cash_item_db cash_shop_items[CASHSHOP_TAB_MAX];
-extern bool cash_shop_defined;
+extern CashShopDatabase cash_shop_db;
 
 enum e_sale_add_result {
 	SALE_ADD_SUCCESS = 0,

+ 208 - 51
src/map/clif.cpp

@@ -3290,6 +3290,148 @@ void clif_guild_xy_remove(map_session_data *sd)
 	clif_send(buf,packet_len(0x1eb),&sd->bl,GUILD_SAMEMAP_WOS);
 }
 
+/*==========================================
+ * Load castle list for guild UI. [Asheraf] / [Balfear]
+ *------------------------------------------*/
+void clif_guild_castle_list(map_session_data& sd){
+#if PACKETVER_MAIN_NUM >= 20190731 || PACKETVER_RE_NUM >= 20190717 || PACKETVER_ZERO_NUM >= 20190814
+	struct guild* g = sd.guild;
+
+	if (g == nullptr)
+		return;
+
+	int castle_count = guild_checkcastles(g);
+
+	if (castle_count > 0) {
+		struct PACKET_ZC_GUILD_AGIT_INFO* p = (struct PACKET_ZC_GUILD_AGIT_INFO*)packet_buffer;
+
+		p->packetType = HEADER_ZC_GUILD_AGIT_INFO;
+		p->packetLength = static_cast<int16>( sizeof( struct PACKET_ZC_GUILD_AGIT_INFO ) );
+
+		int i = 0;
+		for (const auto& gc : castle_db) {
+			if (gc.second->guild_id == g->guild_id && gc.second->client_id) {
+				p->castle_list[i] = static_cast<int8>( gc.second->client_id );
+				p->packetLength += static_cast<int16>( sizeof( p->castle_list[0] ) );
+				++i;
+			}
+		}
+
+		clif_send(p, p->packetLength, &sd.bl, SELF);
+	}
+#endif
+}
+
+/*==========================================
+ * Send castle info Economy/Defence. [Asheraf] / [Balfear]
+ *------------------------------------------*/
+void clif_guild_castleinfo(map_session_data& sd, std::shared_ptr<guild_castle> castle ){
+#if PACKETVER_MAIN_NUM >= 20190731 || PACKETVER_RE_NUM >= 20190717 || PACKETVER_ZERO_NUM >= 20190814
+	if( castle->client_id == 0 ){
+		return;
+	}
+
+	struct PACKET_ZC_REQ_ACK_AGIT_INVESTMENT p = {};
+
+	p.packetType = HEADER_ZC_REQ_ACK_AGIT_INVESTMENT;
+	p.castle_id = static_cast<int8>( castle->client_id );
+	p.economy = castle->economy;
+	p.defense = castle->defense;
+
+	clif_send(&p, sizeof(p), &sd.bl, SELF);
+#endif
+}
+
+/*==========================================
+ * Show teleport request result. [Asheraf] / [Balfear]
+ *------------------------------------------*/
+void clif_guild_castle_teleport_res(map_session_data& sd, enum e_siege_teleport_result result){
+#if PACKETVER_MAIN_NUM >= 20190731 || PACKETVER_RE_NUM >= 20190717 || PACKETVER_ZERO_NUM >= 20190814
+	struct PACKET_ZC_REQ_ACK_MOVE_GUILD_AGIT p = {};
+
+	p.packetType = HEADER_ZC_REQ_ACK_MOVE_GUILD_AGIT;
+	p.result = static_cast<int16>( result );
+
+	clif_send(&p, sizeof(p), &sd.bl, SELF);
+#endif
+}
+
+/*==========================================
+ * Request castle info. [Asheraf] / [Balfear]
+ *------------------------------------------*/
+void clif_parse_guild_castle_info_request(int fd, map_session_data* sd){
+#if PACKETVER_MAIN_NUM >= 20190522 || PACKETVER_RE_NUM >= 20190522 || PACKETVER_ZERO_NUM >= 20190515
+	const struct PACKET_CZ_REQ_AGIT_INVESTMENT* p = (struct PACKET_CZ_REQ_AGIT_INVESTMENT*)RFIFOP(fd, 0);
+	struct guild* g = sd->guild;
+
+	if (g == nullptr)
+		return;
+
+	std::shared_ptr<guild_castle> gc = castle_db.find_by_clientid( p->castle_id );
+
+	if (gc == nullptr)
+		return;
+	if (gc->guild_id != g->guild_id)
+		return;
+
+	clif_guild_castleinfo(*sd, gc);
+#endif
+}
+
+/*==========================================
+ * Teleport to castle. [Asheraf] / [Balfear]
+ *------------------------------------------*/
+void clif_parse_guild_castle_teleport_request(int fd, map_session_data* sd){
+#if PACKETVER_MAIN_NUM >= 20190522 || PACKETVER_RE_NUM >= 20190522 || PACKETVER_ZERO_NUM >= 20190515
+	const struct PACKET_CZ_REQ_MOVE_GUILD_AGIT* p = (struct PACKET_CZ_REQ_MOVE_GUILD_AGIT*)RFIFOP(fd, 0);
+	struct guild* g = sd->guild;
+
+	if (g == nullptr)
+		return;
+
+	std::shared_ptr<guild_castle> gc = castle_db.find_by_clientid( p->castle_id );
+
+	if (gc == nullptr)
+		return;
+	if (!gc->warp_enabled)
+		return;
+	if (gc->guild_id != g->guild_id)
+		return;
+
+	if (map_getmapflag(sd->bl.m, MF_GVG_CASTLE) 
+		|| map_getmapflag(sd->bl.m, MF_NOTELEPORT)
+		|| map_getmapflag(sd->bl.m, MF_NOWARP))
+		return;
+
+	uint32 zeny = gc->zeny;
+
+	switch( gc->type ){
+		case WOE_FIRST_EDITION:
+			if( agit_flag ){
+				zeny = gc->zeny_siege;
+			}
+			break;
+		case WOE_SECOND_EDITION:
+			if( agit2_flag ){
+				zeny = gc->zeny_siege;
+			}
+			break;
+		case WOE_THIRD_EDITION:
+			if( agit3_flag ){
+				zeny = gc->zeny_siege;
+			}
+			break;
+	}
+
+	if (zeny && pc_payzeny(sd, zeny, LOG_TYPE_OTHER, nullptr)) {
+		clif_guild_castle_teleport_res(*sd, SIEGE_TP_NOT_ENOUGH_ZENY);
+		return;
+	}
+
+	pc_setpos(sd, gc->mapindex, gc->warp_x, gc->warp_y, CLR_OUTSIGHT);
+#endif
+}
+
 /*==========================================
  *
  *------------------------------------------*/
@@ -12438,9 +12580,7 @@ void clif_parse_StopAttack(int fd,map_session_data *sd)
 /// Request to move an item from inventory to cart (CZ_MOVE_ITEM_FROM_BODY_TO_CART).
 /// 0126 <index>.W <amount>.L
 void clif_parse_PutItemToCart( int fd, map_session_data *sd ){
-	if (pc_istrading(sd))
-		return;
-	if (!pc_iscarton(sd))
+	if (pc_istrading(sd) || !pc_iscarton(sd) || pc_cant_act2(sd))
 		return;
 	if (map_getmapflag(sd->bl.m, MF_NOUSECART))
 		return;
@@ -12456,7 +12596,7 @@ void clif_parse_PutItemToCart( int fd, map_session_data *sd ){
 void clif_parse_GetItemFromCart(int fd,map_session_data *sd)
 {
 	struct s_packet_db* info = &packet_db[RFIFOW(fd,0)];
-	if (!pc_iscarton(sd))
+	if (!pc_iscarton(sd) || pc_cant_act2(sd))
 		return;
 	if (map_getmapflag(sd->bl.m, MF_NOUSECART))
 		return;
@@ -14037,6 +14177,7 @@ void clif_parse_GuildRequestInfo(int fd, map_session_data *sd)
 	case 0:	// Basic Information Guild, hostile alliance information
 		clif_guild_basicinfo( *sd );
 		clif_guild_allianceinfo(sd);
+		clif_guild_castle_list(*sd);
 		break;
 	case 1:	// Members list, list job title
 		clif_guild_positionnamelist(sd);
@@ -16974,26 +17115,46 @@ void clif_parse_cashshop_close( int fd, map_session_data* sd ){
 //0846 <tabid>.W (CZ_REQ_SE_CASH_TAB_CODE))
 //08c0 <len>.W <openIdentity>.L <itemcount>.W (ZC_ACK_SE_CASH_ITEM_LIST2)
 void clif_parse_CashShopReqTab(int fd, map_session_data *sd) {
+#if PACKETVER_MAIN_NUM >= 20100824 || PACKETVER_RE_NUM >= 20100824 || defined(PACKETVER_ZERO)
+	struct PACKET_CZ_REQ_SE_CASH_TAB_CODE* p_in = (struct PACKET_CZ_REQ_SE_CASH_TAB_CODE*)RFIFOP( fd, 0 );
+
+// TODO: most likely wrong answer packet. Most likely HEADER_ZC_ACK_SE_CASH_ITEM_LIST (0x847) would be correct [Lemongrass]
 // [4144] packet exists only in 2011 and was dropped after
 #if PACKETVER >= 20110222 && PACKETVER < 20120000
-	short tab = RFIFOW(fd, packet_db[RFIFOW(fd,0)].pos[0]);
-	int j;
+	std::shared_ptr<s_cash_item_tab> tab = cash_shop_db.find( static_cast<e_cash_shop_tab>( p_in->tab ) );
+
+	if( tab == nullptr ){
+		return;
+	}
+
+	// Skip empty tabs, the client only expects filled ones
+	if( tab->items.empty() ){
+		return;
+	}
 
-	if( tab < 0 || tab >= CASHSHOP_TAB_MAX )
+#if !(PACKETVER_SUPPORTS_SALES)
+	if( tab->tab == CASHSHOP_TAB_SALE ){
 		return;
+	}
+#endif
+
+	struct PACKET_ZC_ACK_SE_CASH_ITEM_LIST2* p = (struct PACKET_ZC_ACK_SE_CASH_ITEM_LIST2*)packet_buffer;
 
-	WFIFOHEAD(fd, 10 + ( cash_shop_items[tab].count * 6 ) );
-	WFIFOW(fd, 0) = 0x8c0;
-	WFIFOW(fd, 2) = 10 + ( cash_shop_items[tab].count * 6 );
-	WFIFOL(fd, 4) = tab;
-	WFIFOW(fd, 8) = cash_shop_items[tab].count;
+	p->packetType = HEADER_ZC_ACK_SE_CASH_ITEM_LIST2;
+	p->packetLength = sizeof( struct PACKET_ZC_ACK_SE_CASH_ITEM_LIST2 );
+	p->tab = tab->tab;
+	p->count = 0;
 
-	for( j = 0; j < cash_shop_items[tab].count; j++ ) {
-		WFIFOW(fd, 10 + ( 6 * j ) ) = client_nameid( cash_shop_items[tab].item[j]->nameid );
-		WFIFOL(fd, 12 + ( 6 * j ) ) = cash_shop_items[tab].item[j]->price;
+	for( std::shared_ptr<s_cash_item> item : tab->items ){
+		p->items[p->count].itemId = client_nameid( item->nameid );
+		p->items[p->count].price = item->price;
+
+		p->packetLength += sizeof( struct PACKET_ZC_ACK_SE_CASH_ITEM_LIST2_sub );
+		p->count++;
 	}
 
-	WFIFOSET(fd, 10 + ( cash_shop_items[tab].count * 6 ));
+	clif_send( p, p->packetLength, &sd->bl, SELF );
+#endif
 #endif
 }
 
@@ -17001,44 +17162,46 @@ void clif_parse_CashShopReqTab(int fd, map_session_data *sd) {
 void clif_cashshop_list( map_session_data* sd ){
 	nullpo_retv( sd );
 
-	int fd = sd->fd;
+	for( const auto& pair : cash_shop_db ){
+		std::shared_ptr<s_cash_item_tab> tab = pair.second;
 
-	if( !session_isActive( fd ) ){
-		return;
-	}
-
-	for( int tab = CASHSHOP_TAB_NEW; tab < CASHSHOP_TAB_MAX; tab++ ){
 		// Skip empty tabs, the client only expects filled ones
-		if( cash_shop_items[tab].count == 0 ){
+		if( tab->items.empty() ){
+			continue;
+		}
+
+#if !(PACKETVER_SUPPORTS_SALES)
+		if( tab->tab == CASHSHOP_TAB_SALE ){
 			continue;
 		}
+#endif
 
-		int len = sizeof( struct PACKET_ZC_ACK_SCHEDULER_CASHITEM ) + ( cash_shop_items[tab].count * sizeof( struct PACKET_ZC_ACK_SCHEDULER_CASHITEM_sub ) );
-		WFIFOHEAD( fd, len );
-		struct PACKET_ZC_ACK_SCHEDULER_CASHITEM *p = (struct PACKET_ZC_ACK_SCHEDULER_CASHITEM *)WFIFOP( fd, 0 );
+		struct PACKET_ZC_ACK_SCHEDULER_CASHITEM *p = (struct PACKET_ZC_ACK_SCHEDULER_CASHITEM *)packet_buffer;
 
-		p->packetType = 0x8ca;
-		p->packetLength = len;
-		p->count = cash_shop_items[tab].count;
-		p->tabNum = tab;
+		p->packetType = HEADER_ZC_ACK_SCHEDULER_CASHITEM;
+		p->count = 0;
+		p->tabNum = tab->tab;
 
-		for( int i = 0; i < cash_shop_items[tab].count; i++ ){
-			p->items[i].itemId = client_nameid( cash_shop_items[tab].item[i]->nameid );
-			p->items[i].price = cash_shop_items[tab].item[i]->price;
+		for( std::shared_ptr<s_cash_item> item : tab->items ){
+			p->items[p->count].itemId = client_nameid( item->nameid );
+			p->items[p->count].price = item->price;
 #ifdef ENABLE_CASHSHOP_PREVIEW_PATCH
-			struct item_data* id = itemdb_search( cash_shop_items[tab].item[i]->nameid );
+			struct item_data* id = itemdb_search( item->nameid );
 
 			if( id == nullptr ){
-				p->items[i].location = 0;
-				p->items[i].viewSprite = 0;
+				p->items[p->count].location = 0;
+				p->items[p->count].viewSprite = 0;
 			}else{
-				p->items[i].location = pc_equippoint_sub( sd, id );
-				p->items[i].viewSprite = id->look;
+				p->items[p->count].location = pc_equippoint_sub( sd, id );
+				p->items[p->count].viewSprite = id->look;
 			}
 #endif
+			p->count++;
 		}
 
-		WFIFOSET( fd, len );
+		p->packetLength = sizeof( struct PACKET_ZC_ACK_SCHEDULER_CASHITEM ) + p->count * sizeof( struct PACKET_ZC_ACK_SCHEDULER_CASHITEM_sub );
+
+		clif_send( p, p->packetLength, &sd->bl, SELF );
 	}
 }
 
@@ -21152,7 +21315,7 @@ void clif_parse_sale_close(int fd, map_session_data* sd) {
 
 /// Reply to a item search request for item sale administration.
 /// 09ad <result>.W <item id>.W <price>.L (ZC_ACK_CASH_BARGAIN_SALE_ITEM_INFO)
-void clif_sale_search_reply( map_session_data* sd, struct cash_item_data* item ){
+void clif_sale_search_reply( map_session_data* sd, std::shared_ptr<s_cash_item> item ){
 #if PACKETVER_SUPPORTS_SALES
 	struct PACKET_ZC_ACK_CASH_BARGAIN_SALE_ITEM_INFO p;
 
@@ -21192,19 +21355,13 @@ void clif_parse_sale_search( int fd, map_session_data* sd ){
 
 	std::shared_ptr<item_data> id = item_db.searchname( item_name );
 
-	if( id ){
-		int i;
-
-		for( i = 0; i < cash_shop_items[CASHSHOP_TAB_SALE].count; i++ ){
-			if( cash_shop_items[CASHSHOP_TAB_SALE].item[i]->nameid == id->nameid ){
-				clif_sale_search_reply( sd, cash_shop_items[CASHSHOP_TAB_SALE].item[i] );
-				return;
-			}
-		}
+	// not found
+	if( id == nullptr ){
+		clif_sale_search_reply( sd, nullptr );
+		return;
 	}
 
-	// not found
-	clif_sale_search_reply( sd, NULL );
+	clif_sale_search_reply( sd, cash_shop_db.findItemInTab( CASHSHOP_TAB_SALE, id->nameid ) );
 #endif
 }
 

+ 9 - 0
src/map/clif.hpp

@@ -611,6 +611,12 @@ enum e_dynamicnpc_result : int32{
 	DYNAMICNPC_RESULT_OUTOFTIME
 };
 
+enum e_siege_teleport_result : uint8 {
+	SIEGE_TP_SUCCESS = 0,
+	SIEGE_TP_NOT_ENOUGH_ZENY = 1,
+	SIEGE_TP_INVALID_MODE = 2
+};
+
 int clif_setip(const char* ip);
 void clif_setbindip(const char* ip);
 void clif_setport(uint16 port);
@@ -862,6 +868,9 @@ void clif_guild_broken(map_session_data *sd,int flag);
 void clif_guild_xy(map_session_data *sd);
 void clif_guild_xy_single(int fd, map_session_data *sd);
 void clif_guild_xy_remove(map_session_data *sd);
+void clif_guild_castle_list(map_session_data& sd);
+void clif_guild_castle_info(map_session_data& sd, std::shared_ptr<guild_castle> castle );
+void clif_guild_castle_teleport_res(map_session_data& sd, enum e_siege_teleport_result result);
 
 // Battleground
 void clif_bg_hp(map_session_data *sd);

+ 9 - 1
src/map/clif_packetdb.hpp

@@ -1747,6 +1747,10 @@
 	parseable_packet(0x0843,6,clif_parse_GMRemove2,2);
 #endif
 
+#if PACKETVER_MAIN_NUM >= 20100824 || PACKETVER_RE_NUM >= 20100824 || defined(PACKETVER_ZERO)
+	parseable_packet( HEADER_CZ_REQ_SE_CASH_TAB_CODE, sizeof( struct PACKET_CZ_REQ_SE_CASH_TAB_CODE ), clif_parse_CashShopReqTab, 0 );
+#endif
+
 // 2010-11-24aRagexeRE
 #if PACKETVER >= 20101124
 	parseable_packet(0x0288,-1,clif_parse_npccashshop_buy,2,4,8,10);
@@ -1783,7 +1787,6 @@
 	parseable_packet(0x0439,8,clif_parse_UseItem,2,4);
 	packet(0x08d2,10);
 	packet(0x08d1,7);
-	parseable_packet(0x0846,4,clif_parse_CashShopReqTab,2); //2011-07-18
 #endif
 
 // 2011-11-02aRagexe
@@ -2381,6 +2384,11 @@
 	parseable_packet( HEADER_CZ_PING_LIVE, sizeof( struct PACKET_CZ_PING_LIVE ), clif_parse_dull, 0 );
 #endif
 
+#if PACKETVER_MAIN_NUM >= 20190522 || PACKETVER_RE_NUM >= 20190522 || PACKETVER_ZERO_NUM >= 20190515
+	parseable_packet( HEADER_CZ_REQ_MOVE_GUILD_AGIT, sizeof(PACKET_CZ_REQ_MOVE_GUILD_AGIT), clif_parse_guild_castle_teleport_request, 0);
+	parseable_packet( HEADER_CZ_REQ_AGIT_INVESTMENT, sizeof(PACKET_CZ_REQ_AGIT_INVESTMENT), clif_parse_guild_castle_info_request, 0);
+#endif
+
 #if PACKETVER >= 20190724
 	parseable_packet(HEADER_CZ_GUILD_EMBLEM_CHANGE2, sizeof( PACKET_CZ_GUILD_EMBLEM_CHANGE2 ), clif_parse_GuildChangeEmblem2, 0 );
 	packet(HEADER_ZC_CHANGE_GUILD, sizeof(PACKET_ZC_CHANGE_GUILD));

+ 192 - 1
src/map/guild.cpp

@@ -330,12 +330,193 @@ uint64 CastleDatabase::parseBodyNode(const ryml::NodeRef& node) {
 		safestrncpy(gc->castle_event, npc_name.c_str(), sizeof(gc->castle_event));
 	}
 
+	if( this->nodeExists( node, "Type" ) ){
+		std::string type;
+
+		if( !this->asString( node, "Type", type ) ){
+			return 0;
+		}
+
+		std::string type_constant = "WOE_" + type;
+		int64 constant;
+
+		if( !script_get_constant( type_constant.c_str(), &constant ) || constant < WOE_FIRST_EDITION || constant >= WOE_MAX ){
+			this->invalidWarning( node["Type"], "Invalid WoE type %s.\n", type.c_str() );
+			return 0;
+		}
+
+		gc->type = static_cast<e_woe_type>( constant );
+	}else{
+		if( !exists ){
+			gc->type = WOE_FIRST_EDITION;
+		}
+	}
+
+	if( this->nodeExists( node, "ClientId" ) ){
+		uint16 id;
+
+		if( !this->asUInt16( node, "ClientId", id ) ){
+			return 0;
+		}
+
+		gc->client_id = id;
+	}else{
+		if( !exists ){
+			gc->client_id = 0;
+		}
+	}
+
+	if( this->nodeExists( node, "WarpEnabled" ) ){
+		bool enabled;
+
+		if( !this->asBool( node, "WarpEnabled", enabled ) ){
+			return 0;
+		}
+
+		gc->warp_enabled = enabled;
+	}else{
+		if( !exists ){
+			gc->warp_enabled = false;
+		}
+	}
+
+	if (this->nodeExists(node, "WarpX")) {
+		uint16 warp_x;
+
+		if (!this->asUInt16(node, "WarpX", warp_x)) {
+			return 0;
+		}
+
+		if( warp_x == 0 ){
+			this->invalidWarning( node["WarpX"], "WarpX has to be greater than zero.\n" );
+			return 0;
+		}
+
+		map_data* md = map_getmapdata( map_mapindex2mapid( gc->mapindex ) );
+
+		if( warp_x >= md->xs ){
+			this->invalidWarning( node["WarpX"], "WarpX has to be smaller than %hu.\n", md->xs );
+			return 0;
+		}
+
+		gc->warp_x = warp_x;
+	}
+	else {
+		if (!exists)
+			gc->warp_x = 0;
+	}
+
+	if (this->nodeExists(node, "WarpY")) {
+		uint16 warp_y;
+
+		if (!this->asUInt16(node, "WarpY", warp_y)) {
+			return 0;
+		}
+
+		if( warp_y == 0 ){
+			this->invalidWarning( node["WarpY"], "WarpY has to be greater than zero.\n" );
+			return 0;
+		}
+
+		map_data* md = map_getmapdata( map_mapindex2mapid( gc->mapindex ) );
+
+		if( warp_y >= md->ys ){
+			this->invalidWarning( node["WarpY"], "WarpY has to be smaller than %hu.\n", md->ys );
+			return 0;
+		}
+
+		gc->warp_y = warp_y;
+	}
+	else {
+		if (!exists)
+			gc->warp_y = 0;
+	}
+
+	if (this->nodeExists(node, "WarpCost")) {
+		uint32 zeny;
+
+		if (!this->asUInt32(node, "WarpCost", zeny)) {
+			return 0;
+		}
+
+		if( zeny > MAX_ZENY ){
+			this->invalidWarning( node["WarpCost"], "WarpCost has to be smaller than %d.\n", MAX_ZENY );
+			return 0;
+		}
+
+		gc->zeny = zeny;
+	} else {
+		if (!exists)
+			gc->zeny = 100;
+	}
+
+	if (this->nodeExists(node, "WarpCostSiege")) {
+		uint32 zeny_siege;
+
+		if (!this->asUInt32(node, "WarpCostSiege", zeny_siege)) {
+			return 0;
+		}
+
+		if( zeny_siege > MAX_ZENY ){
+			this->invalidWarning( node["WarpCostSiege"], "WarpCostSiege has to be smaller than %d.\n", MAX_ZENY );
+			return 0;
+		}
+
+		gc->zeny_siege = zeny_siege;
+	}
+	else {
+		if (!exists)
+			gc->zeny_siege = 100000;
+	}
+
 	if (!exists)
 		this->put(castle_id, gc);
 
 	return 1;
 }
 
+void CastleDatabase::loadingFinished(){
+	for( const auto& pair : *this ){
+		std::shared_ptr<guild_castle> castle = pair.second;
+
+		if( castle->client_id != 0 ){
+			// Check if ClientId is unique
+			for( const auto& pair2 : *this ){
+				std::shared_ptr<guild_castle> castle2 = pair2.second;
+
+				if( castle->castle_id == castle2->castle_id ){
+					continue;
+				}
+
+				if( castle->client_id == castle2->client_id ){
+					ShowWarning( "Castle ClientId %hu is ambigous.\n", castle->client_id );
+					break;
+				}
+			}
+		}
+
+		if( castle->warp_enabled ){
+			if( castle->client_id == 0 ){
+				ShowWarning( "Warping to castle %d is enabled, but no ClientId is set. Disabling...\n", castle->castle_id );
+				castle->warp_enabled = false;
+				continue;
+			}
+
+			if( castle->warp_x == 0 ){
+				ShowWarning( "Warping to castle %d is enabled, but no WarpX is set. Disabling...\n", castle->castle_id );
+				castle->warp_enabled = false;
+				continue;
+			}
+
+			if( castle->warp_y == 0 ){
+				ShowWarning( "Warping to castle %d is enabled, but no WarpY is set. Disabling...\n", castle->castle_id );
+				castle->warp_enabled = false;
+				continue;
+			}
+		}
+	}
+}
+
 /// lookup: guild id -> guild*
 struct guild* guild_search(int guild_id) {
 	return (struct guild*)idb_get(guild_db,guild_id);
@@ -357,7 +538,7 @@ struct guild* guild_searchname(char* str) {
 
 /// lookup: map index -> castle*
 std::shared_ptr<guild_castle> CastleDatabase::mapindex2gc(int16 mapindex) {
-	for (const auto &it : castle_db) {
+	for (const auto &it : *this) {
 		if (it.second->mapindex == mapindex)
 			return it.second;
 	}
@@ -369,6 +550,16 @@ std::shared_ptr<guild_castle> CastleDatabase::mapname2gc(const char* mapname) {
 	return castle_db.mapindex2gc(mapindex_name2id(mapname));
 }
 
+std::shared_ptr<guild_castle> CastleDatabase::find_by_clientid( uint16 client_id ){
+	for( const auto &it : *this ){
+		if( it.second->client_id == client_id ){
+			return it.second;
+		}
+	}
+
+	return nullptr;
+}
+
 map_session_data* guild_getavailablesd(struct guild* g) {
 	int i;
 

+ 3 - 1
src/map/guild.hpp

@@ -118,16 +118,18 @@ void do_final_guild(void);
 
 class CastleDatabase : public TypesafeYamlDatabase <int32, guild_castle> {
 public:
-	CastleDatabase() : TypesafeYamlDatabase("CASTLE_DB", 1) {
+	CastleDatabase() : TypesafeYamlDatabase("CASTLE_DB", 2, 1) {
 
 	}
 
 	const std::string getDefaultLocation() override;
 	uint64 parseBodyNode(const ryml::NodeRef& node) override;
+	void loadingFinished() override;
 
 	// Additional
 	std::shared_ptr<guild_castle> mapname2gc(const char* mapname);
 	std::shared_ptr<guild_castle> mapindex2gc(int16 mapindex);
+	std::shared_ptr<guild_castle> find_by_clientid( uint16 client_id );
 };
 
 extern CastleDatabase castle_db;

+ 28 - 4
src/map/instance.cpp

@@ -178,20 +178,44 @@ uint64 InstanceDatabase::parseBodyNode(const ryml::NodeRef& node) {
 		}
 
 		if (this->nodeExists(enterNode, "X")) {
-			int16 x;
+			uint16 x;
 
-			if (!this->asInt16(enterNode, "X", x))
+			if (!this->asUInt16(enterNode, "X", x))
 				return 0;
 
+			if (x == 0) {
+				this->invalidWarning(node["X"], "X has to be greater than zero.\n");
+				return 0;
+			}
+
+			map_data *md = map_getmapdata(instance->enter.map);
+
+			if (x >= md->xs) {
+				this->invalidWarning(node["X"], "X has to be smaller than %hu.\n", md->xs);
+				return 0;
+			}
+
 			instance->enter.x = x;
 		}
 
 		if (this->nodeExists(enterNode, "Y")) {
-			int16 y;
+			uint16 y;
 
-			if (!this->asInt16(enterNode, "Y", y))
+			if (!this->asUInt16(enterNode, "Y", y))
 				return 0;
 
+			if (y == 0) {
+				this->invalidWarning(node["Y"], "Y has to be greater than zero.\n");
+				return 0;
+			}
+
+			map_data *md = map_getmapdata(instance->enter.map);
+
+			if (y >= md->ys) {
+				this->invalidWarning(node["Y"], "Y has to be smaller than %hu.\n", md->ys);
+				return 0;
+			}
+
 			instance->enter.y = y;
 		}
 	}

+ 5 - 1
src/map/intif.cpp

@@ -3514,7 +3514,11 @@ static bool intif_parse_StorageReceived(int fd)
 			}else if( sd->state.prevend ){
 				clif_clearcart(sd->fd);
 				clif_cartlist(sd);
-				clif_openvendingreq(sd, sd->vend_skill_lv+2);
+				// Only open the vending UI, if it has not been opened already
+				if (sd->state.pending_vending_ui) {
+					clif_openvendingreq(sd, sd->vend_skill_lv + 2);
+					sd->state.pending_vending_ui = false;
+				}
 			}
 			break;
 

+ 2 - 1
src/map/map-server-generator.vcxproj

@@ -112,6 +112,7 @@
       <MultiProcessorCompilation>true</MultiProcessorCompilation>
       <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
       <AdditionalIncludeDirectories>$(SolutionDir)\3rdparty\rapidyaml\src;$(SolutionDir)\3rdparty\rapidyaml\ext\c4core\src;$(SolutionDir)\3rdparty\json\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <ConformanceMode>true</ConformanceMode>
     </ClCompile>
     <Link>
       <SubSystem>Console</SubSystem>
@@ -370,4 +371,4 @@
     <MakeDir Directories="$(SolutionDir)generated\clientside\data\luafiles514\lua files\navigation\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)generated\clientside\data\luafiles514\lua files\navigation\')" />
     <MakeDir Directories="$(SolutionDir)generated\clientside\data\contentdata\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)generated\clientside\data\contentdata\')" />
   </Target>
-</Project>
+</Project>

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

@@ -112,6 +112,7 @@
       <MultiProcessorCompilation>true</MultiProcessorCompilation>
       <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
       <AdditionalIncludeDirectories>$(SolutionDir)\3rdparty\rapidyaml\src;$(SolutionDir)\3rdparty\rapidyaml\ext\c4core\src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <ConformanceMode>true</ConformanceMode>
     </ClCompile>
     <Link>
       <SubSystem>Console</SubSystem>
@@ -332,7 +333,7 @@
     <Copy SourceFiles="$(SolutionDir)db\import-tmpl\homun_skill_tree.txt" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\homun_skill_tree.txt')" />
     <Copy SourceFiles="$(SolutionDir)db\import-tmpl\homunculus_db.txt" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\homunculus_db.txt')" />
     <Copy SourceFiles="$(SolutionDir)db\import-tmpl\instance_db.yml" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\instance_db.yml')" />
-    <Copy SourceFiles="$(SolutionDir)db\import-tmpl\item_cash_db.txt" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\item_cash_db.txt')" />
+    <Copy SourceFiles="$(SolutionDir)db\import-tmpl\item_cash.yml" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\item_cash.yml')" />
     <Copy SourceFiles="$(SolutionDir)db\import-tmpl\item_combos.yml" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\item_combos.yml')" />
     <Copy SourceFiles="$(SolutionDir)db\import-tmpl\item_db.yml" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\item_db.yml')" />
     <Copy SourceFiles="$(SolutionDir)db\import-tmpl\item_enchant.yml" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\item_enchant.yml')" />
@@ -375,4 +376,4 @@
     <Copy SourceFiles="$(SolutionDir)db\import-tmpl\status.yml" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\status.yml')" />
     <Copy SourceFiles="$(SolutionDir)db\import-tmpl\stylist.yml" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\stylist.yml')" />
   </Target>
-</Project>
+</Project>

+ 0 - 6
src/map/map.cpp

@@ -70,8 +70,6 @@ int db_use_sqldbs = 0;
 char barter_table[32] = "barter";
 char buyingstores_table[32] = "buyingstores";
 char buyingstore_items_table[32] = "buyingstore_items";
-char item_cash_table[32] = "item_cash_db";
-char item_cash2_table[32] = "item_cash_db2";
 #ifdef RENEWAL
 char item_table[32] = "item_db_re";
 char item2_table[32] = "item_db2_re";
@@ -4162,10 +4160,6 @@ int inter_config_read(const char *cfgName)
 			safestrncpy(mob_skill_table,w2,sizeof(mob_skill_table));
 		else if(strcmpi(w1,"mob_skill2_table")==0)
 			safestrncpy(mob_skill2_table,w2,sizeof(mob_skill2_table));
-		else if( strcmpi( w1, "item_cash_table" ) == 0 )
-			safestrncpy( item_cash_table, w2, sizeof(item_cash_table) );
-		else if( strcmpi( w1, "item_cash2_table" ) == 0 )
-			safestrncpy( item_cash2_table, w2, sizeof(item_cash2_table) );
 		else if( strcmpi( w1, "vending_db" ) == 0 )
 			safestrncpy( vendings_table, w2, sizeof(vendings_table) );
 		else if( strcmpi( w1, "vending_items_table" ) == 0 )

+ 38 - 18
src/map/npc.cpp

@@ -410,6 +410,7 @@ uint64 BarterDatabase::parseBodyNode( const ryml::NodeRef& node ){
 	if( !exists ){
 		barter = std::make_shared<s_npc_barter>();
 		barter->name = npcname;
+		barter->npcid = 0;
 	}
 
 	if( this->nodeExists( node, "Map" ) ){
@@ -729,59 +730,64 @@ void BarterDatabase::loadingFinished(){
 
 		std::shared_ptr<s_npc_barter> barter = pair.second;
 
-		struct npc_data* nd = npc_create_npc( barter->m, barter->x, barter->y );
-
-		npc_parsename( nd, barter->name.c_str(), nullptr, nullptr, __FILE__ ":" QUOTE(__LINE__) );
-
-		nd->class_ = barter->sprite;
-		nd->speed = 200;
-
-		nd->bl.type = BL_NPC;
-		nd->subtype = NPCTYPE_BARTER;
-
-		nd->u.barter.extended = false;
+		bool extended = false;
 
 		// Check if it has to use the extended barter feature or not
 		for( const auto& itemPair : barter->items ){
 			// Normal barter cannot have zeny requirements
 			if( itemPair.second->price > 0 ){
-				nd->u.barter.extended = true;
+				extended = true;
 				break;
 			}
 
 			// Normal barter needs to have exchange items defined
 			if( itemPair.second->requirements.empty() ){
-				nd->u.barter.extended = true;
+				extended = true;
 				break;
 			}
 
 			// Normal barter can only exchange 1:1
 			if( itemPair.second->requirements.size() > 1 ){
-				nd->u.barter.extended = true;
+				extended = true;
 				break;
 			}
 
 			// Normal barter cannot handle refine
 			for( const auto& requirement : itemPair.second->requirements ){
 				if( requirement.second->refine >= 0 ){
-					nd->u.barter.extended = true;
+					extended = true;
 					break;
 				}
 			}
 
 			// Check if a refine requirement has been set in the loop above
-			if( nd->u.barter.extended ){
+			if( extended ){
 				break;
 			}
 		}
 
-		if( nd->u.barter.extended && !battle_config.feature_barter_extended ){
+		if( extended && !battle_config.feature_barter_extended ){
 #ifndef BUILDBOT
-			ShowError( "Barter %s uses extended mechanics but this is not enabled.\n", nd->name );
+			ShowError( "Barter %s uses extended mechanics but this is not enabled.\n", barter->name.c_str() );
 #endif
 			continue;
 		}
 
+		struct npc_data* nd = npc_create_npc( barter->m, barter->x, barter->y );
+
+		// Store the npcid for the destructor
+		barter->npcid = nd->bl.id;
+
+		npc_parsename( nd, barter->name.c_str(), nullptr, nullptr, __FILE__ ":" QUOTE(__LINE__) );
+
+		nd->class_ = barter->sprite;
+		nd->speed = 200;
+
+		nd->bl.type = BL_NPC;
+		nd->subtype = NPCTYPE_BARTER;
+
+		nd->u.barter.extended = extended;
+
 		if( nd->bl.m >= 0 ){
 			map_addnpc( nd->bl.m, nd );
 			npc_setcells( nd );
@@ -840,6 +846,20 @@ void BarterDatabase::loadingFinished(){
 	TypesafeYamlDatabase::loadingFinished();
 }
 
+s_npc_barter::~s_npc_barter(){
+	if( this->npcid != 0 ){
+		struct npc_data* nd = map_id2nd( this->npcid );
+
+		// Check if the NPC still exists or has been removed already
+		if( nd != nullptr ){
+			// Delete the NPC
+			npc_unload( nd, true );
+			// Update NPC event database
+			npc_read_event_script();
+		}
+	}
+}
+
 BarterDatabase barter_db;
 
 /**

+ 28 - 2
src/map/npc.hpp

@@ -104,6 +104,9 @@ struct s_npc_barter{
 	uint8 dir;
 	int16 sprite;
 	std::map<uint16, std::shared_ptr<s_npc_barter_item>> items;
+	int32 npcid;
+
+	~s_npc_barter();
 };
 
 class BarterDatabase : public TypesafeYamlDatabase<std::string, s_npc_barter>{
@@ -1424,12 +1427,35 @@ enum e_job_types
 	JT_4_RAGFES_16,
 	JT_4_RAGFES_16_M,
 	JT_4_EXJOB_NINJA2,
-
-	JT_ROZ_MQ_LUCIAN = 10510,
+	JT_4_VR_BOOK_FAIRY,
+	JT_ROZ_MQ_LUCIAN,
 	JT_ROZ_MQ_BRITIA,
 	JT_ROZ_MQ_ASSASIN01,
 	JT_STRANGE_B_SMITH1,
 	JT_STRONGER_B_SMTIH,
+	JT_4_VR_BOOK_RED,
+	JT_4_VR_BOOK_BLUE,
+	JT_4_VR_BOOK_YELLOW,
+	JT_4_VR_BOOK_GREEN,
+	JT_4_VR_BOOK_WHITE,
+	JT_4_VR_YGNIZEM,
+
+	JT_4_JP_19TH = 10524,
+	JT_4_KING_PORING,
+	JT_4_VR_SWORDMAN_DEAD,
+	JT_GATE_SKYBLUE,
+	JT_4_CS_RIGEL,
+	JT_4_M_NILLEM,
+	JT_4_LARVA_RED,
+	JT_4_LARVA_YELLOW,
+	JT_4_LARVA_BLACK,
+	JT_4_LARVA_VIOLET,
+	JT_4_HERO_SAUSAGE,
+	JT_4_PRINCESS_SAUSAGE,
+
+	JT_ROZ_MQ_XAVIER = 13000,
+	JT_ROZ_MQ_MOCLORD,
+	JT_ROZ_MQ_SKULD,
 
 	JT_NEW_NPC_3RD_END = 19999,
 	NPC_RANGE3_END, // Official: JT_NEW_NPC_3RD_END=19999

+ 25 - 0
src/map/packets.hpp

@@ -453,6 +453,28 @@ struct PACKET_ZC_PARTY_JOIN_REQ_ACK_FROM_MASTER{
 	uint32 refused;
 } __attribute__((packed));
 
+struct PACKET_CZ_REQ_SE_CASH_TAB_CODE{
+	int16 packetType;
+	int16 tab;
+} __attribute__((packed));
+
+struct PACKET_ZC_ACK_SE_CASH_ITEM_LIST2_sub{
+#if PACKETVER_MAIN_NUM >= 20181121 || PACKETVER_RE_NUM >= 20180704 || PACKETVER_ZERO_NUM >= 20181114
+	uint32 itemId;
+#else
+	uint16 itemId;
+#endif
+	int32 price;
+} __attribute__((packed));
+
+struct PACKET_ZC_ACK_SE_CASH_ITEM_LIST2{
+	int16 packetType;
+	int16 packetLength;
+	uint32 tab;
+	int16 count;
+	struct PACKET_ZC_ACK_SE_CASH_ITEM_LIST2_sub items[];
+} __attribute__((packed));
+
 struct PACKET_EFST_SET_ENTER {
 	int16 PacketType;
 	uint32 GID;
@@ -502,6 +524,9 @@ DEFINE_PACKET_HEADER(ZC_CASH_ITEM_DELETE, 0x299)
 DEFINE_PACKET_HEADER(ZC_NOTIFY_BIND_ON_EQUIP, 0x2d3)
 DEFINE_PACKET_HEADER(ZC_FAILED_TRADE_BUYING_STORE_TO_SELLER, 0x824)
 DEFINE_PACKET_HEADER(CZ_SSILIST_ITEM_CLICK, 0x83c)
+DEFINE_PACKET_HEADER(CZ_REQ_SE_CASH_TAB_CODE, 0x846)
+DEFINE_PACKET_HEADER(ZC_ACK_SE_CASH_ITEM_LIST2, 0x8c0)
+DEFINE_PACKET_HEADER(ZC_ACK_SCHEDULER_CASHITEM, 0x8ca)
 DEFINE_PACKET_HEADER(ZC_CLEAR_DIALOG, 0x8d6)
 #if PACKETVER >= 20111108 && PACKETVER < 20120618
 DEFINE_PACKET_HEADER(ZC_EFST_SET_ENTER, 0x8ff)

+ 13 - 13
src/map/pc.cpp

@@ -3094,7 +3094,7 @@ static void pc_bonus_item_drop(std::vector<s_add_drop> &drop, t_itemid nameid, u
 		ShowWarning("pc_bonus_item_drop: No Item ID nor Item Group ID specified.\n");
 		return;
 	}
-	if (!item_db.exists(nameid)) {
+	if (nameid && !item_db.exists(nameid)) {
 		ShowWarning("pc_bonus_item_drop: Invalid item id %u\n",nameid);
 		return;
 	}
@@ -5623,7 +5623,7 @@ int pc_paycash(map_session_data *sd, int price, int points, e_log_pick_type type
 	int cash;
 	nullpo_retr(-1,sd);
 
-	points = cap_value(points, 0, MAX_ZENY); //prevent command UB
+	points = cap_value(points, 0, MAX_KAFRAPOINT); //prevent command UB
 
 	cash = price-points;
 
@@ -5669,14 +5669,14 @@ int pc_getcash(map_session_data *sd, int cash, int points, e_log_pick_type type)
 
 	nullpo_retr(-1,sd);
 
-	cash = cap_value(cash, 0, MAX_ZENY); //prevent command UB
-	points = cap_value(points, 0, MAX_ZENY); //prevent command UB
+	cash = cap_value(cash, 0, MAX_CASHPOINT); //prevent command UB
+	points = cap_value(points, 0, MAX_KAFRAPOINT); //prevent command UB
 	if( cash > 0 )
 	{
-		if( cash > MAX_ZENY-sd->cashPoints )
+		if( cash > MAX_CASHPOINT-sd->cashPoints )
 		{
 			ShowWarning("pc_getcash: Cash point overflow (cash=%d, have cash=%d, account_id=%d, char_id=%d).\n", cash, sd->cashPoints, sd->status.account_id, sd->status.char_id);
-			cash = MAX_ZENY-sd->cashPoints;
+			cash = MAX_CASHPOINT-sd->cashPoints;
 		}
 
 		pc_setaccountreg(sd, add_str(CASHPOINT_VAR), sd->cashPoints+cash);
@@ -5693,10 +5693,10 @@ int pc_getcash(map_session_data *sd, int cash, int points, e_log_pick_type type)
 
 	if( points > 0 )
 	{
-		if( points > MAX_ZENY-sd->kafraPoints )
+		if( points > MAX_KAFRAPOINT-sd->kafraPoints )
 		{
 			ShowWarning("pc_getcash: Kafra point overflow (points=%d, have points=%d, account_id=%d, char_id=%d).\n", points, sd->kafraPoints, sd->status.account_id, sd->status.char_id);
-			points = MAX_ZENY-sd->kafraPoints;
+			points = MAX_KAFRAPOINT-sd->kafraPoints;
 		}
 
 		pc_setaccountreg(sd, add_str(KAFRAPOINT_VAR), sd->kafraPoints+points);
@@ -6065,7 +6065,7 @@ bool pc_isUseitem(map_session_data *sd,int n)
 		}
 	}
 
-	if( itemdb_group.item_exists( MF_NORETURN, nameid ) ){
+	if( itemdb_group.item_exists( IG_MF_NORETURN, nameid ) ){
 		if( mapdata->flag[MF_NORETURN] ){
 			return false;
 		}
@@ -10278,16 +10278,16 @@ bool pc_setparam(map_session_data *sd,int64 type,int64 val_tmp)
 		if (val < 0)
 			return false;
 		if (!sd->state.connect_new)
-			log_cash(sd, LOG_TYPE_SCRIPT, LOG_CASH_TYPE_CASH, -(sd->cashPoints - cap_value(val, 0, MAX_ZENY)));
-		sd->cashPoints = cap_value(val, 0, MAX_ZENY);
+			log_cash(sd, LOG_TYPE_SCRIPT, LOG_CASH_TYPE_CASH, -(sd->cashPoints - cap_value(val, 0, MAX_CASHPOINT)));
+		sd->cashPoints = cap_value(val, 0, MAX_CASHPOINT);
 		pc_setaccountreg(sd, add_str(CASHPOINT_VAR), sd->cashPoints);
 		return true;
 	case SP_KAFRAPOINTS:
 		if (val < 0)
 			return false;
 		if (!sd->state.connect_new)
-			log_cash(sd, LOG_TYPE_SCRIPT, LOG_CASH_TYPE_KAFRA, -(sd->kafraPoints - cap_value(val, 0, MAX_ZENY)));
-		sd->kafraPoints = cap_value(val, 0, MAX_ZENY);
+			log_cash(sd, LOG_TYPE_SCRIPT, LOG_CASH_TYPE_KAFRA, -(sd->kafraPoints - cap_value(val, 0, MAX_KAFRAPOINT)));
+		sd->kafraPoints = cap_value(val, 0, MAX_KAFRAPOINT);
 		pc_setaccountreg(sd, add_str(KAFRAPOINT_VAR), sd->kafraPoints);
 		return true;
 	case SP_PCDIECOUNTER:

+ 1 - 0
src/map/pc.hpp

@@ -425,6 +425,7 @@ public:
 		unsigned int autolooting : 1; //performance-saver, autolooting state for @alootid
 		unsigned int gmaster_flag : 1;
 		unsigned int prevend : 1;//used to flag wheather you've spent 40sp to open the vending or not.
+		bool pending_vending_ui; // flag whether the vending packet should still be sent to this player or not
 		unsigned int warping : 1;//states whether you're in the middle of a warp processing
 		unsigned int permanent_speed : 1; // When 1, speed cannot be changed through status_calc_pc().
 		bool hold_recalc;

+ 2 - 1
src/map/script.cpp

@@ -35,6 +35,7 @@
 #include "atcommand.hpp"
 #include "battle.hpp"
 #include "battleground.hpp"
+#include "cashshop.hpp"
 #include "channel.hpp"
 #include "chat.hpp"
 #include "chrif.hpp"
@@ -25779,7 +25780,7 @@ BUILDIN_FUNC(mail){
 			for( k = 0; k < num_items && start < end; k++, start++ ){
 				msg.item[k].card[i] = (t_itemid)get_val2_num( st, reference_uid( id, start ), reference_getref( data ) );
 
-				if( !item_db.exists(msg.item[k].card[i]) ){
+				if( msg.item[k].card[i] > 0 && !item_db.exists(msg.item[k].card[i]) ){
 					ShowError( "buildin_mail: invalid card id %u.\n", msg.item[k].card[i] );
 					return SCRIPT_CMD_FAILURE;
 				}

+ 1 - 0
src/map/script.hpp

@@ -2069,6 +2069,7 @@ enum e_hat_effects : int16{
 	HAT_EF_C_MELODY_WING,
 	HAT_EF_C_SPOT_LIGHT,
 	HAT_EF_C_ASTRA_BLESSING,
+	HAT_EF_EFST_C_20TH_ANNIVERSARY_HAT,
 	HAT_EF_MAX
 };
 

+ 142 - 0
src/map/script_constants.hpp

@@ -3572,6 +3572,7 @@
 	export_constant(EFST_DRESS_UP);
 	export_constant(EFST_MAPLE_FALLS);
 	export_constant(EFST_ALL_NIFLHEIM_RECALL);
+	export_constant(EFST_MARKING_USE_CHANGEMONSTER);
 	export_constant(EFST_MTF_MARIONETTE);
 	export_constant(EFST_MTF_LUDE);
 	export_constant(EFST_MTF_CRUISER);
@@ -3582,6 +3583,8 @@
 	export_constant(EFST_LIMIT_POWER_BOOSTER);
 	export_constant(EFST_GIFT_OF_SNOW);
 	export_constant(EFST_NPC_HALLUCINATIONWALK);
+	export_constant(EFST_NPC_HALLUCINATIONWALK_POSTDELAY);
+	export_constant(EFST_NPC_XXXWALK);
 	export_constant(EFST_TIME_ACCESSORY);
 	export_constant(EFST_EP16_DEF);
 	export_constant(EFST_NORMAL_ATKED_SP);
@@ -3644,6 +3647,7 @@
 	export_constant(EFST_HELM_ASIR);
 	export_constant(EFST_HELM_URJ);
 	export_constant(EFST_SUHIDE);
+	export_constant(EFST_REUSE_LIMIT_MG);
 	export_constant(EFST_DORAM_BUF_01);
 	export_constant(EFST_DORAM_BUF_02);
 	export_constant(EFST_SPRITEMABLE);
@@ -3655,10 +3659,21 @@
 	export_constant(EFST_ALL_GLASTHEIM_RECALL);
 	export_constant(EFST_REUSE_LIMIT_PEPO_MD);
 	export_constant(EFST_ALL_THANATOS_RECALL);
+	export_constant(EFST_KAFRA_STORE);
+	export_constant(EFST_REUSE_ABBYS);
 	export_constant(EFST_MAGICSTONE_OF_GRACE_SET);
+	export_constant(EFST_PRIVATE_AIRPLANE);
 	export_constant(EFST_HISS);
+	export_constant(EFST_HISS_AVOID);
 	export_constant(EFST_NYANGGRASS);
 	export_constant(EFST_CHATTERING);
+	export_constant(EFST_CHATTERING_OPT_ATK_MATK);
+	export_constant(EFST_CHATTERING_OPT_HASTE);
+	export_constant(EFST_SPIRITOFLAND_STEMSPEAR);
+	export_constant(EFST_SPIRITOFLAND_ROOTTWIST);
+	export_constant(EFST_SPIRITOFLAND_POWDERING);
+	export_constant(EFST_SPIRITOFLAND_METEOR);
+	export_constant(EFST_SPIRITOFLAND_NYANGGRASS);
 	export_constant(EFST_GROOMING);
 	export_constant(EFST_PROTECTIONOFSHRIMP);
 	export_constant(EFST_EP16_2_BUFF_SS);
@@ -3666,6 +3681,8 @@
 	export_constant(EFST_EP16_2_BUFF_AC);
 	export_constant(EFST_GS_MAGICAL_BULLET);
 	export_constant(EFST_FALLEN_ANGEL);
+	export_constant(EFST_REUSE_LIMIT_MOVEPOINT);
+	export_constant(EFST_MACRO_DETECTOR_ANSWER_WAITING);
 	export_constant(EFST_BLAZE_BEAD);
 	export_constant(EFST_FROZEN_BEAD);
 	export_constant(EFST_BREEZE_BEAD);
@@ -3674,19 +3691,32 @@
 	export_constant(EFST_AID_PERIOD_PLUSEXP_2ND);
 	export_constant(EFST_AID_PERIOD_PLUSJOBEXP_2ND);
 	export_constant(EFST_PRONTERA_JP);
+	export_constant(EFST_ASSISTANT_VENDING);
 	export_constant(EFST_GLOOM_CARD);
 	export_constant(EFST_PHARAOH_CARD);
 	export_constant(EFST_KIEL_CARD);
+	export_constant(EFST_ASSISTANT_BUYING);
 	export_constant(EFST_CHEERUP);
 	export_constant(EFST_GET_CNT_UNREAD_RODEX_CHARDB);
 	export_constant(EFST_GET_CNT_UNREAD_RODEX_GLOBALDB);
 	export_constant(EFST_S_MANAPOTION);
 	export_constant(EFST_M_DEFSCROLL);
+	export_constant(EFST_OPEN_REFINING_UI);
+	export_constant(EFST_ALL_LIGHTHALZEN_RECALL);
+	export_constant(EFST_SWAP_EQUIPITEM);
 	export_constant(EFST_AS_RAGGED_GOLEM_CARD);
 	export_constant(EFST_LHZ_DUN_N1);
 	export_constant(EFST_LHZ_DUN_N2);
 	export_constant(EFST_LHZ_DUN_N3);
 	export_constant(EFST_LHZ_DUN_N4);
+	export_constant(EFST_TAEKWON_MISSION);
+	export_constant(EFST_SUN_PLACE);
+	export_constant(EFST_MOON_PLACE);
+	export_constant(EFST_STAR_PLACE);
+	export_constant(EFST_SUN_MONSTER);
+	export_constant(EFST_MOON_MONSTER);
+	export_constant(EFST_STAR_MONSTER);
+	export_constant(EFST_AL_WARP_ADDSLOT);
 	export_constant(EFST_ALL_STAT_DOWN);
 	export_constant(EFST_GRADUAL_GRAVITY);
 	export_constant(EFST_DAMAGE_HEAL);
@@ -3725,6 +3755,8 @@
 	export_constant(EFST_FALLINGSTAR);
 	export_constant(EFST_NOVAEXPLOSING);
 	export_constant(EFST_GRAVITYCONTROL);
+	export_constant(XXX_EFST_WORLDSTORE_ACTIVE);
+	export_constant(EFST_WORLDSTORE_ITEMMOVEINFO_SENDCOMPLETE);
 	export_constant(EFST_SOULCOLLECT);
 	export_constant(EFST_SOULREAPER);
 	export_constant(EFST_SOULUNITY);
@@ -3753,6 +3785,7 @@
 	export_constant(EFST_REUSE_LIMIT_TBG);
 	export_constant(EFST_REUSE_LIMIT_TBM);
 	export_constant(EFST_YGGDRASIL_BLESS);
+	export_constant(EFST_USE_SKILL_SP_SWHOO);
 	export_constant(EFST_HUNTING_EVENT);
 	export_constant(EFST_PERIOD_RECEIVEITEM_2ND);
 	export_constant(EFST_PERIOD_PLUSEXP_2ND);
@@ -3760,9 +3793,23 @@
 	export_constant(EFST_TW_NEWYEAR_EVENT);
 	export_constant(EFST_ENSEMBLEFATIGUE);
 	export_constant(EFST_ADAPTATION);
+	export_constant(EFST_DANCINGLESSON);
+	export_constant(EFST_MUSICALLESSON);
+	export_constant(EFST_REUSE_LIMIT_RC);
+	export_constant(EFST_DANCINGLESSON_EQUIPPED);
+	export_constant(EFST_MUSICALLESSON_EQUIPPED);
 	export_constant(EFST_ANCILLA);
+	export_constant(EFST_REUSE_LIMIT_POTION_A);
+	export_constant(EFST_REUSE_LIMIT_POTION_B);
+	export_constant(EFST_REUSE_LIMIT_POTION_C);
+	export_constant(EFST_REUSE_LIMIT_POTION_D);
+	export_constant(EFST_REUSE_LIMIT_POTION_E);
+	export_constant(EFST_REUSE_LIMIT_POTION_F);
+	export_constant(EFST_BRAVESET);
+	export_constant(EFST_MACEMASTERY_EQUIPPED);
 	export_constant(EFST_FESTIVE_ENERGY);
 	export_constant(EFST_TEST_KR01);
+	export_constant(EFST_STARFISH_JP);
 	export_constant(EFST_WEAPONBLOCK_ON);
 	export_constant(EFST_CRI_DAMAGE);
 	export_constant(EFST_DEF_POWER);
@@ -3770,12 +3817,17 @@
 	export_constant(EFST_BOW_ATK_POWER);
 	export_constant(EFST_RED_ORG_POTION);
 	export_constant(EFST_CAST_TIME);
+	export_constant(EFST_BLADESTOPREADY);
+	export_constant(EFST_TELEPORT_BR);
+	export_constant(EFST_SA_WEAPON_PROPERTY);
 	export_constant(EFST_LEAPIMPAIRED);
+	export_constant(EFST_SENDING_ITEMLIST);
 	export_constant(EFST_EXCLUSIVE_RECEIVEITEM);
 	export_constant(EFST_EXCLUSIVE_PLUSEXP);
 	export_constant(EFST_ASSUMPTIO_BUFF);
 	export_constant(EFST_BASILICA_BUFF);
 	export_constant(EFST_OVERLAPEXPUP2);
+	export_constant(EFST_STOPMOVE_IMMEDIATELY);
 	export_constant(EFST_SOULCURSE);
 	export_constant(EFST_SOUND_OF_DESTRUCTION);
 	export_constant(EFST_DF_MANAPLUS);
@@ -3785,17 +3837,26 @@
 	export_constant(EFST_NV_TRANSCENDENCE);
 	export_constant(EFST_SWEETSFAIR_ATK);
 	export_constant(EFST_SWEETSFAIR_MATK);
+	export_constant(EFST_REUSE_SKILL);
 	export_constant(EFST_FLOWER_LEAF2);
 	export_constant(EFST_FLOWER_LEAF3);
 	export_constant(EFST_FLOWER_LEAF4);
+	export_constant(EFST_CHARM_BOOST);
+	export_constant(EFST_EARTHSHAKER);
+	export_constant(EFST_PERIOD_USE_WORLDMAP);
 	export_constant(EFST_MISTY_FROST);
 	export_constant(EFST_MAGIC_POISON);
 	export_constant(EFST_KAUTE);
+	export_constant(EFST_REUSE_JPNONLY_LIMIT_I);
+	export_constant(EFST_REUSE_JPNONLY_LIMIT_J);
+	export_constant(EFST_REUSE_JPNONLY_LIMIT_K);
 	export_constant(EFST_JPNONLY_TACTICS);
+	export_constant(EFST_PRISON);
 	export_constant(EFST_MADOGEAR);
 	export_constant(EFST_DEADLY_DEFEASANCE);
 	export_constant(EFST_CLIMAX_DES_HU);
 	export_constant(EFST_CLIMAX);
+	export_constant(EFST_FEINTBOMB);
 	export_constant(EFST_LUXANIMA);
 	export_constant(EFST_BATH_FOAM_A);
 	export_constant(EFST_BATH_FOAM_B);
@@ -3810,6 +3871,7 @@
 	export_constant(EFST_HELLS_PLANT_ARMOR);
 	export_constant(EFST_RELIEVE_DAMAGE);
 	export_constant(EFST_LOCKON_LASER);
+	export_constant(EFST_GRADE_ENCHANT_UI_OPEN);
 	export_constant(EFST_REF_T_POTION);
 	export_constant(EFST_ADD_ATK_DAMAGE);
 	export_constant(EFST_ADD_MATK_DAMAGE);
@@ -3820,7 +3882,9 @@
 	export_constant(EFST_DRAGONIC_AURA);
 	export_constant(EFST_BIG_SCAR);
 	export_constant(EFST_VIGOR);
+	export_constant(EFST_WILL_OF_FAITH);
 	export_constant(EFST_PRESSURE);
+	export_constant(EFST_SA_DRAGONOLOGY);
 	export_constant(EFST_CLIMAX_EARTH);
 	export_constant(EFST_CLIMAX_BLOOM);
 	export_constant(EFST_CLIMAX_CRYIMP);
@@ -3828,6 +3892,7 @@
 	export_constant(EFST_MD_MA_POTION);
 	export_constant(EFST_MD_TA_POTION);
 	export_constant(EFST_MD_RA_POTION);
+	export_constant(EFST_REUSE_MEGAPHONE);
 	export_constant(EFST_HOLY_OIL);
 	export_constant(EFST_CRYSTAL_IMPACT);
 	export_constant(EFST_SHADOW_EXCEED);
@@ -3862,6 +3927,8 @@
 	export_constant(EFST_ULTIMATE_S);
 	export_constant(EFST_SPEAR_SCAR);
 	export_constant(EFST_SHIELD_POWER);
+	export_constant(EFST_FIDUS_ANIMUS);
+	export_constant(EFST_MACE_BOOK_M);
 	export_constant(EFST_SHADOW_WEAPON);
 	export_constant(EFST_RELIGIO);
 	export_constant(EFST_BENEDICTUM);
@@ -3882,12 +3949,14 @@
 	export_constant(EFST_ABYSS_DAGGER);
 	export_constant(EFST_ABYSSFORCEWEAPON);
 	export_constant(EFST_ABYSS_SLAYER);
+	export_constant(EFST_TWOHANDDEF);
 	export_constant(EFST_PROTECTSHADOWEQUIP);
 	export_constant(EFST_RESEARCHREPORT);
 	export_constant(EFST_BO_HELL_DUSTY);
 	export_constant(EFST_WINDSIGN);
 	export_constant(EFST_CRESCIVEBOLT);
 	export_constant(EFST_CALAMITYGALE);
+	export_constant(EFST_CRESCIVEBOLT3);
 	export_constant(EFST_STAGE_MANNER);
 	export_constant(EFST_RETROSPECTION);
 	export_constant(EFST_MYSTIC_SYMPHONY);
@@ -3899,6 +3968,7 @@
 	export_constant(EFST_JAWAII_SERENADE);
 	export_constant(EFST_PRON_MARCH);
 	export_constant(EFST_ROSEBLOSSOM);
+	export_constant(EFST_BO_BIONIC_PHARMACY_OPERATOR);
 	export_constant(EFST_ACIDIFIED_ZONE_WATER);
 	export_constant(EFST_ACIDIFIED_ZONE_GROUND);
 	export_constant(EFST_ACIDIFIED_ZONE_WIND);
@@ -3935,6 +4005,7 @@
 	export_constant(EFST_ABR_MOTHER_NET);
 	export_constant(EFST_ABR_INFINITY);
 	export_constant(EFST_ELEMENTAL_VEIL);
+	export_constant(EFST_RENOVATIO_EXT);
 	export_constant(EFST_HOMUN_TIME);
 	export_constant(EFST_POWER_ACCELERATION);
 	export_constant(EFST_MAX_HP_SP_AVOID);
@@ -3943,9 +4014,13 @@
 	export_constant(EFST_AID_PERIOD_MAX_HP_SP_AVOID);
 	export_constant(EFST_AID_PERIOD_ADD_ALL_STATE);
 	export_constant(EFST_POISON_MIST);
+	export_constant(EFST_HACKANDSLASHER);
+	export_constant(EFST_GET_CNT_UNREAD_RETURN_RODEX_CHARDB);
 	export_constant(EFST_STONE_WALL);
+	export_constant(EFST_REUSE_LIMIT_I);
 	export_constant(EFST_OVERBRANDREADY);
 	export_constant(EFST_SHIELDSPELL);
+	export_constant(EFST_AUTOSHADOWSPELL_CHECK2);
 	export_constant(EFST_CLOUD_POISON);
 	export_constant(EFST_SPORE_EXPLOSION_DEBUFF);
 	export_constant(EFST_DEFSCROLL);
@@ -4024,6 +4099,34 @@
 	export_constant(EFST_RUSH_QUAKE2);
 	export_constant(EFST_SBUNSHIN);
 	export_constant(EFST_MTP_W_POTION_100);
+	export_constant(EFST_CHANGE_SIZE);
+	export_constant(EFST_CHANGE_SIZE_MONSTER);
+	export_constant(EFST_SHOW_EFFECT1);
+	export_constant(EFST_SHOW_EFFECT2);
+	export_constant(EFST_SHOW_EFFECT3);
+	export_constant(EFST_VR_SPEED);
+	export_constant(EFST_VR_ASPD);
+	export_constant(EFST_VR_MHP);
+	export_constant(EFST_VR_MSP);
+	export_constant(EFST_VR_HIT);
+	export_constant(EFST_VR_DEF);
+	export_constant(EFST_VR_MDEF);
+	export_constant(EFST_VR_BOOK001);
+	export_constant(EFST_VR_BOOK002);
+	export_constant(EFST_VR_BOOK003);
+	export_constant(EFST_VR_BOOK004);
+	export_constant(EFST_REUSE_LIMIT_VR_BOOK);
+	export_constant(EFST_VR_BOOK005);
+	export_constant(EFST_VR_BOOK006);
+	export_constant(EFST_VR_BOOK007);
+	export_constant(EFST_VR_BOOK008);
+	export_constant(EFST_VR_BOOK009);
+	export_constant(EFST_ALL_T_STAT);
+	export_constant(EFST_P_ATK_PLUS);
+	export_constant(EFST_S_MATK_PLUS);
+	export_constant(EFST_C_RATE_PLUS);
+	export_constant(EFST_RESIST_PLUS);
+	export_constant(EFST_PVP_DUN_BUFF);
 /// @APIHOOK_END
 /// Do not modify code above this, since it will be automatically generated by the API again
 	export_constant(EFST_MAX);
@@ -7896,11 +7999,33 @@
 	export_constant_npc(JT_4_RAGFES_16);
 	export_constant_npc(JT_4_RAGFES_16_M);
 	export_constant_npc(JT_4_EXJOB_NINJA2);
+	export_constant_npc(JT_4_VR_BOOK_FAIRY);
 	export_constant_npc(JT_ROZ_MQ_LUCIAN);
 	export_constant_npc(JT_ROZ_MQ_BRITIA);
 	export_constant_npc(JT_ROZ_MQ_ASSASIN01);
 	export_constant_npc(JT_STRANGE_B_SMITH1);
 	export_constant_npc(JT_STRONGER_B_SMTIH);
+	export_constant_npc(JT_4_VR_BOOK_RED);
+	export_constant_npc(JT_4_VR_BOOK_BLUE);
+	export_constant_npc(JT_4_VR_BOOK_YELLOW);
+	export_constant_npc(JT_4_VR_BOOK_GREEN);
+	export_constant_npc(JT_4_VR_BOOK_WHITE);
+	export_constant_npc(JT_4_VR_YGNIZEM);
+	export_constant_npc(JT_4_JP_19TH);
+	export_constant_npc(JT_4_KING_PORING);
+	export_constant_npc(JT_4_VR_SWORDMAN_DEAD);
+	export_constant_npc(JT_GATE_SKYBLUE);
+	export_constant_npc(JT_4_CS_RIGEL);
+	export_constant_npc(JT_4_M_NILLEM);
+	export_constant_npc(JT_4_LARVA_RED);
+	export_constant_npc(JT_4_LARVA_YELLOW);
+	export_constant_npc(JT_4_LARVA_BLACK);
+	export_constant_npc(JT_4_LARVA_VIOLET);
+	export_constant_npc(JT_4_HERO_SAUSAGE);
+	export_constant_npc(JT_4_PRINCESS_SAUSAGE);
+	export_constant_npc(JT_ROZ_MQ_XAVIER);
+	export_constant_npc(JT_ROZ_MQ_MOCLORD);
+	export_constant_npc(JT_ROZ_MQ_SKULD);
 	export_constant_npc(JT_NEW_NPC_3RD_END);
 	#undef export_constant_npc
 
@@ -9202,6 +9327,7 @@
 	export_constant(HAT_EF_C_MELODY_WING);
 	export_constant(HAT_EF_C_SPOT_LIGHT);
 	export_constant(HAT_EF_C_ASTRA_BLESSING);
+	export_constant(HAT_EF_EFST_C_20TH_ANNIVERSARY_HAT);
 
 	/* pet catch */
 	export_constant(PET_CATCH_UNIVERSAL);
@@ -9927,6 +10053,22 @@
 	export_constant(ENCHANTGRADE_A);
 	export_constant(MAX_ENCHANTGRADE);
 
+	/* cash shop tabs */
+	export_constant(CASHSHOP_TAB_NEW);
+	export_constant(CASHSHOP_TAB_HOT);
+	export_constant(CASHSHOP_TAB_LIMITED);
+	export_constant(CASHSHOP_TAB_RENTAL);
+	export_constant(CASHSHOP_TAB_PERMANENT);
+	export_constant(CASHSHOP_TAB_SCROLLS);
+	export_constant(CASHSHOP_TAB_CONSUMABLES);
+	export_constant(CASHSHOP_TAB_OTHER);
+	export_constant(CASHSHOP_TAB_SALE);
+
+	/* WoE Types */
+	export_constant(WOE_FIRST_EDITION);
+	export_constant(WOE_SECOND_EDITION);
+	export_constant(WOE_THIRD_EDITION);
+
 	#undef export_constant
 	#undef export_constant2
 	#undef export_parameter

+ 8 - 2
src/map/skill.cpp

@@ -9019,10 +9019,16 @@ int skill_castend_nodamage_id (struct block_list *src, struct block_list *bl, ui
 				sd->state.workinprogress = WIP_DISABLE_ALL;
 				sd->vend_skill_lv = skill_lv;
 				ARR_FIND(0, MAX_CART, i, sd->cart.u.items_cart[i].nameid && sd->cart.u.items_cart[i].id == 0);
-				if (i < MAX_CART)
+				if (i < MAX_CART) {
+					// Save the cart before opening the vending UI
+					sd->state.pending_vending_ui = true;
 					intif_storage_save(sd, &sd->cart);
-				else
+				}
+				else{
+					// Instantly open the vending UI
+					sd->state.pending_vending_ui = false;
 					clif_openvendingreq(sd,2+skill_lv);
+				}
 			}
 		}
 		break;

+ 142 - 74
src/map/status.hpp

@@ -2119,8 +2119,8 @@ enum efst_type : short{
 	EFST_DRESS_UP,
 	EFST_MAPLE_FALLS,
 	EFST_ALL_NIFLHEIM_RECALL,
-
-	EFST_MTF_MARIONETTE = 860,
+	EFST_MARKING_USE_CHANGEMONSTER,
+	EFST_MTF_MARIONETTE,
 	EFST_MTF_LUDE,
 	EFST_MTF_CRUISER,
 	EFST_MERMAID_LONGING,
@@ -2130,8 +2130,9 @@ enum efst_type : short{
 	EFST_LIMIT_POWER_BOOSTER,
 	EFST_GIFT_OF_SNOW,
 	EFST_NPC_HALLUCINATIONWALK,
-
-	EFST_TIME_ACCESSORY = 872,
+	EFST_NPC_HALLUCINATIONWALK_POSTDELAY,
+	EFST_NPC_XXXWALK,
+	EFST_TIME_ACCESSORY,
 	EFST_EP16_DEF,
 	EFST_NORMAL_ATKED_SP,
 	EFST_BODYSTATE_STONECURSE,
@@ -2193,8 +2194,8 @@ enum efst_type : short{
 	EFST_HELM_ASIR,
 	EFST_HELM_URJ,
 	EFST_SUHIDE,
-
-	EFST_DORAM_BUF_01 = 935,
+	EFST_REUSE_LIMIT_MG,
+	EFST_DORAM_BUF_01,
 	EFST_DORAM_BUF_02,
 	EFST_SPRITEMABLE,
 	EFST_AID_PERIOD_RECEIVEITEM,
@@ -2205,15 +2206,22 @@ enum efst_type : short{
 	EFST_ALL_GLASTHEIM_RECALL,
 	EFST_REUSE_LIMIT_PEPO_MD,
 	EFST_ALL_THANATOS_RECALL,
-
-	EFST_MAGICSTONE_OF_GRACE_SET = 948,
-
-	EFST_HISS = 950,
-
-	EFST_NYANGGRASS = 952,
+	EFST_KAFRA_STORE,
+	EFST_REUSE_ABBYS,
+	EFST_MAGICSTONE_OF_GRACE_SET,
+	EFST_PRIVATE_AIRPLANE,
+	EFST_HISS,
+	EFST_HISS_AVOID,
+	EFST_NYANGGRASS,
 	EFST_CHATTERING,
-
-	EFST_GROOMING = 961,
+	EFST_CHATTERING_OPT_ATK_MATK,
+	EFST_CHATTERING_OPT_HASTE,
+	EFST_SPIRITOFLAND_STEMSPEAR,
+	EFST_SPIRITOFLAND_ROOTTWIST,
+	EFST_SPIRITOFLAND_POWDERING,
+	EFST_SPIRITOFLAND_METEOR,
+	EFST_SPIRITOFLAND_NYANGGRASS,
+	EFST_GROOMING,
 	EFST_PROTECTIONOFSHRIMP,
 	EFST_EP16_2_BUFF_SS,
 	EFST_EP16_2_BUFF_SC,
@@ -2221,8 +2229,9 @@ enum efst_type : short{
 	EFST_GS_MAGICAL_BULLET,
 
 	EFST_FALLEN_ANGEL = 976,
-
-	EFST_BLAZE_BEAD = 979,
+	EFST_REUSE_LIMIT_MOVEPOINT,
+	EFST_MACRO_DETECTOR_ANSWER_WAITING,
+	EFST_BLAZE_BEAD,
 	EFST_FROZEN_BEAD,
 	EFST_BREEZE_BEAD,
 	EFST_SOULATTACK,
@@ -2230,24 +2239,33 @@ enum efst_type : short{
 	EFST_AID_PERIOD_PLUSEXP_2ND,
 	EFST_AID_PERIOD_PLUSJOBEXP_2ND,
 	EFST_PRONTERA_JP,
-
-	EFST_GLOOM_CARD = 988,
+	EFST_ASSISTANT_VENDING,
+	EFST_GLOOM_CARD,
 	EFST_PHARAOH_CARD,
 	EFST_KIEL_CARD,
-
-	EFST_CHEERUP = 992,
+	EFST_ASSISTANT_BUYING,
+	EFST_CHEERUP,
 	EFST_GET_CNT_UNREAD_RODEX_CHARDB,
 	EFST_GET_CNT_UNREAD_RODEX_GLOBALDB,
 	EFST_S_MANAPOTION,
 	EFST_M_DEFSCROLL,
-
-	EFST_AS_RAGGED_GOLEM_CARD = 1000,
+	EFST_OPEN_REFINING_UI,
+	EFST_ALL_LIGHTHALZEN_RECALL,
+	EFST_SWAP_EQUIPITEM,
+	EFST_AS_RAGGED_GOLEM_CARD,
 	EFST_LHZ_DUN_N1,
 	EFST_LHZ_DUN_N2,
 	EFST_LHZ_DUN_N3,
 	EFST_LHZ_DUN_N4,
-
-	EFST_ALL_STAT_DOWN = 1013,
+	EFST_TAEKWON_MISSION,
+	EFST_SUN_PLACE,
+	EFST_MOON_PLACE,
+	EFST_STAR_PLACE,
+	EFST_SUN_MONSTER,
+	EFST_MOON_MONSTER,
+	EFST_STAR_MONSTER,
+	EFST_AL_WARP_ADDSLOT,
+	EFST_ALL_STAT_DOWN,
 	EFST_GRADUAL_GRAVITY,
 	EFST_DAMAGE_HEAL,
 	EFST_IMMUNE_PROPERTY_NOTHING,
@@ -2285,8 +2303,9 @@ enum efst_type : short{
 	EFST_FALLINGSTAR,
 	EFST_NOVAEXPLOSING,
 	EFST_GRAVITYCONTROL,
-
-	EFST_SOULCOLLECT = 1053,
+	XXX_EFST_WORLDSTORE_ACTIVE,
+	EFST_WORLDSTORE_ITEMMOVEINFO_SENDCOMPLETE,
+	EFST_SOULCOLLECT,
 	EFST_SOULREAPER,
 	EFST_SOULUNITY,
 	EFST_SOULSHADOW,
@@ -2315,37 +2334,50 @@ enum efst_type : short{
 	EFST_REUSE_LIMIT_TBG,
 	EFST_REUSE_LIMIT_TBM,
 	EFST_YGGDRASIL_BLESS,
-
-	EFST_HUNTING_EVENT = 1083,
+	EFST_USE_SKILL_SP_SWHOO,
+	EFST_HUNTING_EVENT,
 	EFST_PERIOD_RECEIVEITEM_2ND,
 	EFST_PERIOD_PLUSEXP_2ND,
 	EFST_EXPDROPUP,
 	EFST_TW_NEWYEAR_EVENT,
 	EFST_ENSEMBLEFATIGUE,
 	EFST_ADAPTATION,
-
-	EFST_ANCILLA = 1095,
-
-	EFST_FESTIVE_ENERGY = 1104,
+	EFST_DANCINGLESSON,
+	EFST_MUSICALLESSON,
+	EFST_REUSE_LIMIT_RC,
+	EFST_DANCINGLESSON_EQUIPPED,
+	EFST_MUSICALLESSON_EQUIPPED,
+	EFST_ANCILLA,
+	EFST_REUSE_LIMIT_POTION_A,
+	EFST_REUSE_LIMIT_POTION_B,
+	EFST_REUSE_LIMIT_POTION_C,
+	EFST_REUSE_LIMIT_POTION_D,
+	EFST_REUSE_LIMIT_POTION_E,
+	EFST_REUSE_LIMIT_POTION_F,
+	EFST_BRAVESET,
+	EFST_MACEMASTERY_EQUIPPED,
+	EFST_FESTIVE_ENERGY,
 	EFST_TEST_KR01,
-
-	EFST_WEAPONBLOCK_ON = 1107,
+	EFST_STARFISH_JP,
+	EFST_WEAPONBLOCK_ON,
 	EFST_CRI_DAMAGE,
 	EFST_DEF_POWER,
 	EFST_DEF_IGNORE,
 	EFST_BOW_ATK_POWER,
 	EFST_RED_ORG_POTION,
 	EFST_CAST_TIME,
-
-	EFST_LEAPIMPAIRED = 1117,
-
-	EFST_EXCLUSIVE_RECEIVEITEM = 1119,
+	EFST_BLADESTOPREADY,
+	EFST_TELEPORT_BR,
+	EFST_SA_WEAPON_PROPERTY,
+	EFST_LEAPIMPAIRED,
+	EFST_SENDING_ITEMLIST,
+	EFST_EXCLUSIVE_RECEIVEITEM,
 	EFST_EXCLUSIVE_PLUSEXP,
 	EFST_ASSUMPTIO_BUFF,
 	EFST_BASILICA_BUFF,
 	EFST_OVERLAPEXPUP2,
-
-	EFST_SOULCURSE = 1125,
+	EFST_STOPMOVE_IMMEDIATELY,
+	EFST_SOULCURSE,
 	EFST_SOUND_OF_DESTRUCTION,
 	EFST_DF_MANAPLUS,
 	EFST_DF_FULLSWINGK,
@@ -2354,23 +2386,27 @@ enum efst_type : short{
 	EFST_NV_TRANSCENDENCE,
 	EFST_SWEETSFAIR_ATK,
 	EFST_SWEETSFAIR_MATK,
-
-	EFST_FLOWER_LEAF2 = 1135,
+	EFST_REUSE_SKILL,
+	EFST_FLOWER_LEAF2,
 	EFST_FLOWER_LEAF3,
 	EFST_FLOWER_LEAF4,
-
-	EFST_MISTY_FROST = 1141,
+	EFST_CHARM_BOOST,
+	EFST_EARTHSHAKER,
+	EFST_PERIOD_USE_WORLDMAP,
+	EFST_MISTY_FROST,
 	EFST_MAGIC_POISON,
 	EFST_KAUTE,
-
-	EFST_JPNONLY_TACTICS = 1147,
-
-	EFST_MADOGEAR = 1149,
+	EFST_REUSE_JPNONLY_LIMIT_I,
+	EFST_REUSE_JPNONLY_LIMIT_J,
+	EFST_REUSE_JPNONLY_LIMIT_K,
+	EFST_JPNONLY_TACTICS,
+	EFST_PRISON,
+	EFST_MADOGEAR,
 	EFST_DEADLY_DEFEASANCE,
 	EFST_CLIMAX_DES_HU,
 	EFST_CLIMAX,
-
-	EFST_LUXANIMA = 1154,
+	EFST_FEINTBOMB,
+	EFST_LUXANIMA,
 	EFST_BATH_FOAM_A,
 	EFST_BATH_FOAM_B,
 	EFST_BATH_FOAM_C,
@@ -2384,8 +2420,8 @@ enum efst_type : short{
 	EFST_HELLS_PLANT_ARMOR,
 	EFST_RELIEVE_DAMAGE,
 	EFST_LOCKON_LASER,
-
-	EFST_REF_T_POTION = 1169,
+	EFST_GRADE_ENCHANT_UI_OPEN,
+	EFST_REF_T_POTION,
 	EFST_ADD_ATK_DAMAGE,
 	EFST_ADD_MATK_DAMAGE,
 	EFST_SERVANTWEAPON,
@@ -2395,18 +2431,18 @@ enum efst_type : short{
 	EFST_DRAGONIC_AURA,
 	EFST_BIG_SCAR,
 	EFST_VIGOR,
-
-	EFST_PRESSURE = 1180,
-
-	EFST_CLIMAX_EARTH = 1182,
+	EFST_WILL_OF_FAITH,
+	EFST_PRESSURE,
+	EFST_SA_DRAGONOLOGY,
+	EFST_CLIMAX_EARTH,
 	EFST_CLIMAX_BLOOM,
 	EFST_CLIMAX_CRYIMP,
 	EFST_MD_ME_POTION,
 	EFST_MD_MA_POTION,
 	EFST_MD_TA_POTION,
 	EFST_MD_RA_POTION,
-
-	EFST_HOLY_OIL = 1190,
+	EFST_REUSE_MEGAPHONE,
+	EFST_HOLY_OIL,
 	EFST_CRYSTAL_IMPACT,
 	EFST_SHADOW_EXCEED,
 	EFST_DANCING_KNIFE,
@@ -2440,8 +2476,9 @@ enum efst_type : short{
 	EFST_ULTIMATE_S,
 	EFST_SPEAR_SCAR,
 	EFST_SHIELD_POWER,
-
-	EFST_SHADOW_WEAPON = 1226,
+	EFST_FIDUS_ANIMUS,
+	EFST_MACE_BOOK_M,
+	EFST_SHADOW_WEAPON,
 	EFST_RELIGIO,
 	EFST_BENEDICTUM,
 	EFST_MVPCARD_KIEL,
@@ -2461,15 +2498,15 @@ enum efst_type : short{
 	EFST_ABYSS_DAGGER,
 	EFST_ABYSSFORCEWEAPON,
 	EFST_ABYSS_SLAYER,
-
-	EFST_PROTECTSHADOWEQUIP = 1247,
+	EFST_TWOHANDDEF,
+	EFST_PROTECTSHADOWEQUIP,
 	EFST_RESEARCHREPORT,
 	EFST_BO_HELL_DUSTY,
 	EFST_WINDSIGN,
 	EFST_CRESCIVEBOLT,
 	EFST_CALAMITYGALE,
-
-	EFST_STAGE_MANNER = 1254,
+	EFST_CRESCIVEBOLT3,
+	EFST_STAGE_MANNER,
 	EFST_RETROSPECTION,
 	EFST_MYSTIC_SYMPHONY,
 	EFST_KVASIR_SONATA,
@@ -2480,8 +2517,8 @@ enum efst_type : short{
 	EFST_JAWAII_SERENADE,
 	EFST_PRON_MARCH,
 	EFST_ROSEBLOSSOM,
-
-	EFST_ACIDIFIED_ZONE_WATER = 1266,
+	EFST_BO_BIONIC_PHARMACY_OPERATOR,
+	EFST_ACIDIFIED_ZONE_WATER,
 	EFST_ACIDIFIED_ZONE_GROUND,
 	EFST_ACIDIFIED_ZONE_WIND,
 	EFST_ACIDIFIED_ZONE_FIRE,
@@ -2517,8 +2554,8 @@ enum efst_type : short{
 	EFST_ABR_MOTHER_NET,
 	EFST_ABR_INFINITY,
 	EFST_ELEMENTAL_VEIL,
-
-	EFST_HOMUN_TIME = 1303,
+	EFST_RENOVATIO_EXT,
+	EFST_HOMUN_TIME,
 	EFST_POWER_ACCELERATION,
 	EFST_MAX_HP_SP_AVOID,
 	EFST_ADD_ALL_STATE,
@@ -2526,13 +2563,14 @@ enum efst_type : short{
 	EFST_AID_PERIOD_MAX_HP_SP_AVOID,
 	EFST_AID_PERIOD_ADD_ALL_STATE,
 	EFST_POISON_MIST,
-
-	EFST_STONE_WALL = 1313,
-
-	EFST_OVERBRANDREADY = 1315,
+	EFST_HACKANDSLASHER,
+	EFST_GET_CNT_UNREAD_RETURN_RODEX_CHARDB,
+	EFST_STONE_WALL,
+	EFST_REUSE_LIMIT_I,
+	EFST_OVERBRANDREADY,
 	EFST_SHIELDSPELL,
-
-	EFST_CLOUD_POISON = 1318,
+	EFST_AUTOSHADOWSPELL_CHECK2,
+	EFST_CLOUD_POISON,
 	EFST_SPORE_EXPLOSION_DEBUFF,
 
 	EFST_DEFSCROLL = 1321,
@@ -2617,6 +2655,36 @@ enum efst_type : short{
 	EFST_SBUNSHIN = 1415,
 
 	EFST_MTP_W_POTION_100 = 1418,
+
+	EFST_CHANGE_SIZE = 1420,
+	EFST_CHANGE_SIZE_MONSTER,
+	EFST_SHOW_EFFECT1,
+	EFST_SHOW_EFFECT2,
+	EFST_SHOW_EFFECT3,
+	EFST_VR_SPEED,
+	EFST_VR_ASPD,
+	EFST_VR_MHP,
+	EFST_VR_MSP,
+	EFST_VR_HIT,
+	EFST_VR_DEF,
+	EFST_VR_MDEF,
+	EFST_VR_BOOK001,
+	EFST_VR_BOOK002,
+	EFST_VR_BOOK003,
+	EFST_VR_BOOK004,
+	EFST_REUSE_LIMIT_VR_BOOK,
+
+	EFST_VR_BOOK005 = 1439,
+	EFST_VR_BOOK006,
+	EFST_VR_BOOK007,
+	EFST_VR_BOOK008,
+	EFST_VR_BOOK009,
+	EFST_ALL_T_STAT,
+	EFST_P_ATK_PLUS,
+	EFST_S_MATK_PLUS,
+	EFST_C_RATE_PLUS,
+	EFST_RESIST_PLUS,
+	EFST_PVP_DUN_BUFF,
 /// @APIHOOK_END
 /// Do not modify code above this, since it will be automatically generated by the API again
 	EFST_MAX,

+ 52 - 0
src/tool/csv2yaml.cpp

@@ -540,6 +540,12 @@ bool Csv2YamlTool::initialize( int argc, char* argv[] ){
 		return false;
 	}
 
+	if( !process( "ITEM_CASH_DB", 1, root_paths, "item_cash_db", []( const std::string& path, const std::string& name_ext ) -> bool {
+		return sv_readdb( path.c_str(), name_ext.c_str(), ',', 3, 3, -1, &cashshop_parse_dbrow, false );
+	}, "item_cash" ) ){
+		return 0;
+	}
+
 	// TODO: add implementations ;-)
 
 	return true;
@@ -4918,6 +4924,52 @@ static bool itemdb_read_combos(const char* file) {
 	return true;
 }
 
+// Copied and adjusted from cashshop.cpp
+static bool cashshop_parse_dbrow( char* fields[], int columns, int current ){
+	uint16 tab = atoi( fields[0] );
+	t_itemid nameid = strtoul( fields[1], nullptr, 10 );
+	uint32 price = atoi( fields[2] );
+
+	std::string* item_name = util::umap_find( aegis_itemnames, nameid );
+
+	if( item_name == nullptr ){
+		ShowWarning( "cashshop_parse_dbrow: Invalid item id %u.\n", nameid );
+		return false;
+	}
+
+	if( tab >= CASHSHOP_TAB_MAX ){
+		ShowWarning( "cashshop_parse_dbrow: Invalid tab %d in line '%d', skipping...\n", tab, current );
+		return false;
+	}else if( price < 1 ){
+		ShowWarning( "cashshop_parse_dbrow: Invalid price %d in line '%d', skipping...\n", price, current );
+		return false;
+	}
+
+	const char* constant_ptr = constant_lookup( tab, "CASHSHOP_TAB_" );
+
+	if( constant_ptr == nullptr ){
+		ShowError( "cashshop_parse_dbrow: CASHSHOP_TAB constant for tab %hu was not found, skipping...\n", tab );
+		return false;
+	}
+
+	std::string constant = constant_ptr;
+	constant.erase( 0, 13 );
+	constant = name2Upper( constant );
+
+	body << YAML::BeginMap;
+	body << YAML::Key << "Tab" << YAML::Value << constant;
+	body << YAML::Key << "Items";
+	body << YAML::BeginSeq;
+	body << YAML::BeginMap;
+	body << YAML::Key << "Item" << YAML::Value << *item_name;
+	body << YAML::Key << "Price" << YAML::Value << price;
+	body << YAML::EndMap;
+	body << YAML::EndSeq;
+	body << YAML::EndMap;
+
+	return true;
+}
+
 int main( int argc, char *argv[] ){
 	return main_core<Csv2YamlTool>( argc, argv );
 }

+ 1 - 0
src/tool/csv2yaml.hpp

@@ -530,5 +530,6 @@ static bool mercenary_readdb(char* str[], int columns, int current);
 static bool pc_readdb_skilltree(char* str[], int columns, int current);
 static bool pc_readdb_skilltree_yaml(void);
 static bool itemdb_read_combos(const char* file);
+static bool cashshop_parse_dbrow( char* fields[], int columns, int current );
 
 #endif /* CSV2YAML_HPP */

+ 1 - 0
src/tool/csv2yaml.vcxproj

@@ -114,6 +114,7 @@
       <PreprocessorDefinitions>$(DefineConstants);WIN32;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;_WINSOCK_DEPRECATED_NO_WARNINGS;LIBCONFIG_STATIC;YY_USE_CONST;MINICORE;_DEBUG;_CONSOLE;_LIB;_ITERATOR_DEBUG_LEVEL=0;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
       <AdditionalIncludeDirectories>$(SolutionDir)3rdparty\yaml-cpp\include\;$(SolutionDir)3rdparty\rapidyaml\src;$(SolutionDir)3rdparty\rapidyaml\ext\c4core\src</AdditionalIncludeDirectories>
+      <ConformanceMode>true</ConformanceMode>
     </ClCompile>
     <Link>
       <SubSystem>Console</SubSystem>

+ 1 - 0
src/tool/mapcache.vcxproj

@@ -94,6 +94,7 @@
       <Optimization>Disabled</Optimization>
       <PreprocessorDefinitions>$(DefineConstants);WIN32;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;_WINSOCK_DEPRECATED_NO_WARNINGS;LIBCONFIG_STATIC;YY_USE_CONST;MINICORE;_DEBUG;_CONSOLE;_LIB;_ITERATOR_DEBUG_LEVEL=0;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
+      <ConformanceMode>true</ConformanceMode>
     </ClCompile>
     <Link>
       <SubSystem>Console</SubSystem>

+ 1 - 0
src/tool/yaml.hpp

@@ -40,6 +40,7 @@
 #include "../map/achievement.hpp"
 #include "../map/battle.hpp"
 #include "../map/battleground.hpp"
+#include "../map/cashshop.hpp"
 #include "../map/channel.hpp"
 #include "../map/chat.hpp"
 #include "../map/date.hpp"

+ 1 - 0
src/tool/yaml2sql.cpp

@@ -37,6 +37,7 @@
 #include "../map/achievement.hpp"
 #include "../map/battle.hpp"
 #include "../map/battleground.hpp"
+#include "../map/cashshop.hpp"
 #include "../map/channel.hpp"
 #include "../map/chat.hpp"
 #include "../map/date.hpp"

+ 1 - 0
src/tool/yaml2sql.vcxproj

@@ -95,6 +95,7 @@
       <PreprocessorDefinitions>$(DefineConstants);WIN32;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;_WINSOCK_DEPRECATED_NO_WARNINGS;LIBCONFIG_STATIC;YY_USE_CONST;MINICORE;_DEBUG;_CONSOLE;_LIB;_ITERATOR_DEBUG_LEVEL=0;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
       <AdditionalIncludeDirectories>$(SolutionDir)3rdparty\yaml-cpp\include\;$(SolutionDir)3rdparty\rapidyaml\src;$(SolutionDir)3rdparty\rapidyaml\ext\c4core\src</AdditionalIncludeDirectories>
+      <ConformanceMode>true</ConformanceMode>
     </ClCompile>
     <Link>
       <SubSystem>Console</SubSystem>

+ 1 - 0
src/tool/yamlupgrade.vcxproj

@@ -95,6 +95,7 @@
       <PreprocessorDefinitions>$(DefineConstants);WIN32;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;_WINSOCK_DEPRECATED_NO_WARNINGS;LIBCONFIG_STATIC;YY_USE_CONST;MINICORE;_DEBUG;_CONSOLE;_LIB;_ITERATOR_DEBUG_LEVEL=0;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
       <AdditionalIncludeDirectories>$(SolutionDir)3rdparty\yaml-cpp\include\;$(SolutionDir)3rdparty\rapidyaml\src;$(SolutionDir)3rdparty\rapidyaml\ext\c4core\src</AdditionalIncludeDirectories>
+      <ConformanceMode>true</ConformanceMode>
     </ClCompile>
     <Link>
       <SubSystem>Console</SubSystem>

+ 1 - 0
src/web/web-server.vcxproj

@@ -97,6 +97,7 @@
       <AdditionalIncludeDirectories>$(SolutionDir)3rdparty\httplib\;$(SolutionDir)\3rdparty\rapidyaml\src;$(SolutionDir)\3rdparty\rapidyaml\ext\c4core\src;$(SolutionDir)\3rdparty\json\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <DisableSpecificWarnings>4018;4200</DisableSpecificWarnings>
       <MultiProcessorCompilation>true</MultiProcessorCompilation>
+      <ConformanceMode>true</ConformanceMode>
     </ClCompile>
     <Link>
       <SubSystem>Console</SubSystem>

+ 0 - 2
tools/ci/sql.bat

@@ -8,8 +8,6 @@ set MYSQL_PWD=%DB_ROOTPW%
 %MYSQL% -u %DB_ROOT% -e "CREATE DATABASE %DB_NAME%;"
 %MYSQL% -u %DB_ROOT% %DB_NAME% -e "source sql-files\main.sql"
 %MYSQL% -u %DB_ROOT% %DB_NAME% -e "source sql-files\logs.sql"
-%MYSQL% -u %DB_ROOT% %DB_NAME% -e "source sql-files\item_cash_db.sql"
-%MYSQL% -u %DB_ROOT% %DB_NAME% -e "source sql-files\item_cash_db2.sql"
 %MYSQL% -u %DB_ROOT% %DB_NAME% -e "source sql-files\item_db.sql"
 %MYSQL% -u %DB_ROOT% %DB_NAME% -e "source sql-files\item_db_usable.sql"
 %MYSQL% -u %DB_ROOT% %DB_NAME% -e "source sql-files\item_db_equip.sql"

+ 0 - 2
tools/ci/sql.sh

@@ -17,8 +17,6 @@ DB_PASS=ragnarok
 mysql -u $DB_ROOT -p$DB_ROOTPW -e "CREATE DATABASE $DB_NAME;" || aborterror "Unable to create database."
 mysql -u $DB_ROOT -p$DB_ROOTPW $DB_NAME < sql-files/main.sql || aborterror "Unable to import main database."
 mysql -u $DB_ROOT -p$DB_ROOTPW $DB_NAME < sql-files/logs.sql || aborterror "Unable to import logs database."
-mysql -u $DB_ROOT -p$DB_ROOTPW $DB_NAME < sql-files/item_cash_db.sql || aborterror "Unable to import cash item table."
-mysql -u $DB_ROOT -p$DB_ROOTPW $DB_NAME < sql-files/item_cash_db2.sql || aborterror "Unable to import cash item 2 table."
 mysql -u $DB_ROOT -p$DB_ROOTPW $DB_NAME < sql-files/item_db.sql || aborterror "Unable to import pre-renewal item table structure."
 mysql -u $DB_ROOT -p$DB_ROOTPW $DB_NAME < sql-files/item_db_usable.sql || aborterror "Unable to import pre-renewal usable item table."
 mysql -u $DB_ROOT -p$DB_ROOTPW $DB_NAME < sql-files/item_db_equip.sql || aborterror "Unable to import pre-renewal equip item table."

Nem az összes módosított fájl került megjelenítésre, mert túl sok fájl változott