浏览代码

Script Engine Upgrade
* Upgraded the script engine variables to no longer be limited.
* Increased array limit from 127 to 2 billion.
* Improvement in overall speed and storage size.
* All variable types now support arrays.
- Merge from HerculesWS/Hercules@82b583b. Thanks to those who worked on it.

aleos89 10 年之前
父节点
当前提交
0696125b82

+ 9 - 3
conf/inter_athena.conf

@@ -80,9 +80,12 @@ mysql_reconnect_count: 1
 
 // Login Database Tables
 login_server_account_db: login
-login_server_accreg_db: global_reg_value
 ipban_table: ipbanlist
 
+// Shared
+global_acc_reg_num_table: global_acc_reg_num
+global_acc_reg_str_table: global_acc_reg_str
+
 // Char Database Tables
 char_db: char
 hotkey_db: hotkey
@@ -91,7 +94,6 @@ cart_db: cart_inventory
 inventory_db: inventory
 charlog_db: charlog
 storage_db: storage
-reg_db: global_reg_value
 skill_db: skill
 interlog_db: interlog
 memo_db: memo
@@ -117,6 +119,10 @@ elemental_db: elemental
 ragsrvinfo_db: ragsrvinfo
 skillcooldown_db: skillcooldown
 bonus_script_db: bonus_script
+acc_reg_num_table: acc_reg_num
+acc_reg_str_table: acc_reg_str
+char_reg_str_table: char_reg_str
+char_reg_num_table: char_reg_num
 
 // Map Database Tables
 buyingstore_db: buyingstores
@@ -135,7 +141,7 @@ mob_skill_db_db: mob_skill_db
 mob_skill_db_re_db: mob_skill_db_re
 mob_skill_db2_db: mob_skill_db2
 //mob_skill_db2_db: mob_skill_db2_re
-mapreg_db: mapreg
+mapreg_table: mapreg
 vending_db: vendings
 vending_items_db: vending_items
 market_table: market

+ 14 - 45
doc/script_commands.txt

@@ -3,7 +3,7 @@
 //===== By: ==================================================
 //= rAthena Dev Team
 //===== Last Updated: ========================================
-//= 20140313
+//= 20150610
 //===== Description: =========================================
 //= A reference manual for the rAthena scripting language.
 //= Commands are sorted depending on their functionality.
@@ -616,6 +616,8 @@ same name. You can tell between the specific variables of an array with an
 
 <variable name>[<array index>]
 
+All variable types can be used as arrays.
+
 Variables stored in this way, inside an array, are also called 'array elements'. 
 Arrays are specifically useful for storing a set of similar data (like several 
 item IDs for example) and then looping through it. You can address any array 
@@ -631,8 +633,9 @@ value from an another array) to get at an array value:
     
 This will make @arrayofnumbers[100] equal to 10.
 
-Notice that index numbering always starts with 0. Arrays cannot hold more than 
-128 variables. (So the last one can't have a number higher than 127)
+Index numbering always starts with 0 and arrays can hold over 2 billion
+variables. As such, the (guaranteed) allowed values for indices are in the
+range 0 ~ 2147483647.
 
 And array indexes probably can't be negative. Nobody tested what happens when 
 you try to get a negatively numbered variable from an array, but it's not going 
@@ -644,41 +647,6 @@ Arrays can naturally store strings:
 the '$', normally denoting a string variable, before the square brackets that 
 denotes an array index.
 
-Resume of the allowed variable and array scopes
------------------------------------------------
-
-+==========+======+=======+
-|VarType   | Norm | Array |
-+==========+======+=======+
-|$Str$     | OK!  | OK!   |
-+----------+------+-------+
-|$@Str$    | OK!  | OK!   |
-+----------+------+-------+
-|@Str$     | OK!  | OK!   |
-+----------+------+-------+
-|#Str$     | OK!  | FAIL! |
-+----------+------+-------+
-|Str$      | OK!  | FAIL! |
-+----------+------+-------+
-|$Int      | OK!  | OK!   |
-+----------+------+-------+
-|$@Int     | OK!  | OK!   |
-+----------+------+-------+
-|@Int      | OK!  | OK!   |
-+----------+------+-------+
-|#Int      | OK!  | FAIL! |
-+----------+------+-------+
-|Int       | OK!  | FAIL! |
-+----------+------+-------+
-|.Str$     | OK!  | OK!   |
-+----------+------+-------+
-|.Int      | OK!  | OK!   |
-+----------+------+-------+
-|.@Str$    | OK!  | OK!   |
-+----------+------+-------+
-|.@Int     | OK!  | OK!   |
-+----------+------+-------+
-
 Variable References
 -------------------
 
@@ -991,6 +959,7 @@ On<label name>:
 These special labels are used with Mob scripts mostly, and script commands
 that requires you to point/link a command to a mob or another NPC, giving a label
 name to start from. The label name can be any of your liking, but must be
+started with "On".
 
 Example:
 
@@ -2167,7 +2136,7 @@ example:
 
     setarray @array[0],200,200,200;
     setarray @array[1],300,150;
-    
+
 will produce:
 
  @array[0]=200
@@ -2200,7 +2169,7 @@ some cases invaluable.
     setarray @array[0], 100, 200, 300, 400, 500, 600;
     // So we have made @array[]
     copyarray @array2[0],@array[2],2;
-    
+
     // Now, @array2[0] will be equal to @array[2] (300) and 
     // @array2[1] will be equal to @array[3].
 
@@ -2274,9 +2243,9 @@ Whatever it returns is determined by type.
 
 *getarraysize(<array name>)
 
-This function returns the number of values that are contained inside the 
-specified array. Notice that zeros and empty strings at the end of this array 
-are not counted towards this number.
+This function returns highest index of the array that is filled.
+Notice that zeros and empty strings at the end of this array are not
+counted towards this number.
 
 For example:
 
@@ -2287,8 +2256,8 @@ This will make @arraysize == 6. But if you try this:
 
     setarray @array[0], 100, 200, 300, 400, 500, 600, 0;
     set @arraysize,getarraysize(@array);
-    
-@arraysize will still equal 6, even though you've set 7 values.    
+
+@arraysize will still equal 6, even though you've set 7 values.
 
 ---------------------------------------
 

+ 4 - 4
npc/custom/quests/hunting_missions.txt

@@ -55,7 +55,7 @@ function Chk;
 			mes "You've started a mission";
 			mes "on another character.";
 			if (!@hm_char_del_check) {  // check for deleted character
-				query_sql("SELECT 1 FROM `global_reg_value` WHERE `str` = 'Mission0' AND `char_id` IN(SELECT `char_id` FROM `char` WHERE `account_id` = " + getcharid(3) + ")", .@i);
+				query_sql("SELECT 1 FROM `char_reg_num` WHERE `key` = 'Mission0' AND `char_id` IN(SELECT `char_id` FROM `char` WHERE `account_id` = " + getcharid(3) + ")", .@i);
 				if (!.@i) {
 					next;
 					mes "[Hunting Missions]";
@@ -146,7 +146,7 @@ function Chk;
 	case 6:
 		mes "[Hunting Missions]";
 		mes "The top hunters are:";
-		query_sql("SELECT char_id AS id, (SELECT `name` FROM `char` WHERE char_id = id),`value` FROM `global_reg_value` WHERE str = 'Mission_Total' ORDER BY CAST(`value` AS SIGNED) DESC LIMIT 5", .@id, .@name$, .@val);
+		query_sql("SELECT char_id AS id, (SELECT `name` FROM `char` WHERE char_id = id),`value` FROM `char_reg_num` WHERE `key` = 'Mission_Total' ORDER BY CAST(`value` AS SIGNED) DESC LIMIT 5", .@id, .@name$, .@val);
 		for (.@i = 0; .@i < 5; .@i++)
 			mes "  [Rank " + (.@i+1) + "]  " + ((.@name$[.@i] == "") ? "^777777none" : "^0055FF" + .@name$[.@i]+"^000000 : ^FF0000" + .@val[.@i] + " pt.") + "^000000";
 		close;
@@ -207,9 +207,9 @@ Mission_Status:
 		#Mission_Delay = gettimetick(2) + (.Delay * 3600);
 	Mission_Total++;
 	if (Mission_Total == 1)
-		query_sql("INSERT INTO `global_reg_value` (`char_id`,`str`,`value`,`type`,`account_id`) VALUES (" + getcharid(0) + ",'Mission_Total','1',3,0)");
+		query_sql("INSERT INTO `char_reg_num` (`char_id`,`key`,`index`,`value`) VALUES (" + getcharid(0) + ",'Mission_Total','0',1)");
 	else
-		query_sql("UPDATE `global_reg_value` SET `value` = " + Mission_Total + " WHERE char_id = " + getcharid(0) + " AND `str` = 'Mission_Total'");
+		query_sql("UPDATE `char_reg_num` SET `value` = " + Mission_Total + " WHERE `char_id` = " + getcharid(0) + " AND `key` = 'Mission_Total'");
 	close;
 
 Mission_Info:

+ 72 - 10
sql-files/main.sql

@@ -1,3 +1,28 @@
+--
+-- Table structure for table `acc_reg_num`
+--
+
+CREATE TABLE IF NOT EXISTS `acc_reg_num` (
+  `account_id` int(11) unsigned NOT NULL default '0',
+  `key` varchar(32) binary NOT NULL default '',
+  `index` int(11) unsigned NOT NULL default '0',
+  `value` int(11) NOT NULL default '0',
+  PRIMARY KEY (`account_id`,`key`,`index`),
+  KEY `account_id` (`account_id`)
+) ENGINE=MyISAM;
+
+--
+-- Table structure for table `acc_reg_str`
+--
+
+CREATE TABLE IF NOT EXISTS `acc_reg_str` (
+  `account_id` int(11) unsigned NOT NULL default '0',
+  `key` varchar(32) binary NOT NULL default '',
+  `index` int(11) unsigned NOT NULL default '0',
+  `value` varchar(254) NOT NULL default '0',
+  PRIMARY KEY (`account_id`,`key`,`index`),
+  KEY `account_id` (`account_id`)
+) ENGINE=MyISAM;
 
 --
 -- Table structure for table `skillcooldown`
@@ -133,6 +158,32 @@ CREATE TABLE IF NOT EXISTS `char` (
   KEY `online` (`online`)
 ) ENGINE=MyISAM AUTO_INCREMENT=150000; 
 
+--
+-- Table structure for table `char_reg_num`
+--
+
+CREATE TABLE IF NOT EXISTS `char_reg_num` (
+  `char_id` int(11) unsigned NOT NULL default '0',
+  `key` varchar(32) binary NOT NULL default '',
+  `index` int(11) unsigned NOT NULL default '0',
+  `value` int(11) NOT NULL default '0',
+  PRIMARY KEY (`char_id`,`key`,`index`),
+  KEY `char_id` (`char_id`)
+) ENGINE=MyISAM;
+
+--
+-- Table structure for table `char_reg_str`
+--
+
+CREATE TABLE IF NOT EXISTS `char_reg_str` (
+  `char_id` int(11) unsigned NOT NULL default '0',
+  `key` varchar(32) binary NOT NULL default '',
+  `index` int(11) unsigned NOT NULL default '0',
+  `value` varchar(254) NOT NULL default '0',
+  PRIMARY KEY (`char_id`,`key`,`index`),
+  KEY `char_id` (`char_id`)
+) ENGINE=MyISAM;
+
 --
 -- Table structure for table `charlog`
 --
@@ -203,16 +254,28 @@ CREATE TABLE IF NOT EXISTS `hotkey` (
 ) ENGINE=MyISAM;
 
 --
--- Table structure for table `global_reg_value`
+-- Table structure for table `global_acc_reg_num`
 --
 
-CREATE TABLE IF NOT EXISTS `global_reg_value` (
-  `char_id` int(11) unsigned NOT NULL default '0',
-  `str` varchar(255) NOT NULL default '',
-  `value` varchar(255) NOT NULL default '0',
-  `type` tinyint(1) NOT NULL default '3',
+CREATE TABLE IF NOT EXISTS `global_acc_reg_num` (
+  `account_id` int(11) unsigned NOT NULL default '0',
+  `key` varchar(32) binary NOT NULL default '',
+  `index` int(11) unsigned NOT NULL default '0',
+  `value` int(11) NOT NULL default '0',
+  PRIMARY KEY (`account_id`,`key`,`index`),
+  KEY `account_id` (`account_id`)
+) ENGINE=MyISAM;
+
+--
+-- Table structure for table `global_acc_reg_str`
+--
+
+CREATE TABLE IF NOT EXISTS `global_acc_reg_str` (
   `account_id` int(11) unsigned NOT NULL default '0',
-  PRIMARY KEY  (`char_id`,`str`,`account_id`),
+  `key` varchar(32) binary NOT NULL default '',
+  `index` int(11) unsigned NOT NULL default '0',
+  `value` varchar(254) NOT NULL default '0',
+  PRIMARY KEY (`account_id`,`key`,`index`),
   KEY `account_id` (`account_id`)
 ) ENGINE=MyISAM;
 
@@ -478,11 +541,10 @@ INSERT INTO `login` (`account_id`, `userid`, `user_pass`, `sex`, `email`) VALUES
 --
 
 CREATE TABLE IF NOT EXISTS `mapreg` (
-  `varname` varchar(32) NOT NULL,
+  `varname` varchar(32) binary NOT NULL,
   `index` int(11) unsigned NOT NULL default '0',
   `value` varchar(255) NOT NULL,
-  KEY `varname` (`varname`),
-  KEY `index` (`index`)
+  PRIMARY KEY (`varname`,`index`)
 ) ENGINE=MyISAM;
 
 --

+ 66 - 0
sql-files/upgrades/upgrade_20150610.sql

@@ -0,0 +1,66 @@
+ALTER TABLE `mapreg` ADD PRIMARY KEY (`varname`, `index`);
+ALTER TABLE `mapreg` DROP INDEX `varname`;
+ALTER TABLE `mapreg` DROP INDEX `index`;
+ALTER TABLE `mapreg` MODIFY `varname` varchar(32) binary NOT NULL;
+
+CREATE TABLE IF NOT EXISTS `acc_reg_num` (
+  `account_id` int(11) unsigned NOT NULL default '0',
+  `key` varchar(32) binary NOT NULL default '',
+  `index` int(11) unsigned NOT NULL default '0',
+  `value` int(11) NOT NULL default '0',
+  PRIMARY KEY (`account_id`,`key`,`index`),
+  KEY `account_id` (`account_id`)
+) ENGINE=MyISAM;
+
+CREATE TABLE IF NOT EXISTS `acc_reg_str` (
+  `account_id` int(11) unsigned NOT NULL default '0',
+  `key` varchar(32) binary NOT NULL default '',
+  `index` int(11) unsigned NOT NULL default '0',
+  `value` varchar(254) NOT NULL default '0',
+  PRIMARY KEY (`account_id`,`key`,`index`),
+  KEY `account_id` (`account_id`)
+) ENGINE=MyISAM;
+
+CREATE TABLE IF NOT EXISTS `char_reg_num` (
+  `char_id` int(11) unsigned NOT NULL default '0',
+  `key` varchar(32) binary NOT NULL default '',
+  `index` int(11) unsigned NOT NULL default '0',
+  `value` int(11) NOT NULL default '0',
+  PRIMARY KEY (`char_id`,`key`,`index`),
+  KEY `char_id` (`char_id`)
+) ENGINE=MyISAM;
+
+CREATE TABLE IF NOT EXISTS `char_reg_str` (
+  `char_id` int(11) unsigned NOT NULL default '0',
+  `key` varchar(32) binary NOT NULL default '',
+  `index` int(11) unsigned NOT NULL default '0',
+  `value` varchar(254) NOT NULL default '0',
+  PRIMARY KEY (`char_id`,`key`,`index`),
+  KEY `char_id` (`char_id`)
+) ENGINE=MyISAM;
+
+CREATE TABLE IF NOT EXISTS `global_acc_reg_num` (
+  `account_id` int(11) unsigned NOT NULL default '0',
+  `key` varchar(32) binary NOT NULL default '',
+  `index` int(11) unsigned NOT NULL default '0',
+  `value` int(11) NOT NULL default '0',
+  PRIMARY KEY (`account_id`,`key`,`index`),
+  KEY `account_id` (`account_id`)
+) ENGINE=MyISAM;
+
+CREATE TABLE IF NOT EXISTS `global_acc_reg_str` (
+  `account_id` int(11) unsigned NOT NULL default '0',
+  `key` varchar(32) binary NOT NULL default '',
+  `index` int(11) unsigned NOT NULL default '0',
+  `value` varchar(254) NOT NULL default '0',
+  PRIMARY KEY (`account_id`,`key`,`index`),
+  KEY `account_id` (`account_id`)
+) ENGINE=MyISAM;
+
+INSERT INTO `acc_reg_num` (`account_id`, `key`, `index`, `value`) SELECT `account_id`, `str`, 0, `value` FROM `global_reg_value` WHERE `type` = 2 AND `str` NOT LIKE '%$';
+INSERT INTO `acc_reg_str` (`account_id`, `key`, `index`, `value`) SELECT `account_id`, `str`, 0, `value` FROM `global_reg_value` WHERE `type` = 2 AND `str` LIKE '%$';
+INSERT INTO `char_reg_num` (`char_id`, `key`, `index`, `value`) SELECT `char_id`, `str`, 0, `value` FROM `global_reg_value` WHERE `type` = 3 AND `str` NOT LIKE '%$';
+INSERT INTO `char_reg_str` (`char_id`, `key`, `index`, `value`) SELECT `char_id`, `str`, 0, `value` FROM `global_reg_value` WHERE `type` = 3 AND `str` LIKE '%$';
+INSERT INTO `global_acc_reg_num` (`account_id`, `key`, `index`, `value`) SELECT `account_id`, `str`, 0, `value` FROM `global_reg_value` WHERE `type` = 1 AND `str` NOT LIKE '%$';
+INSERT INTO `global_acc_reg_str` (`account_id`, `key`, `index`, `value`) SELECT `account_id`, `str`, 0, `value` FROM `global_reg_value` WHERE `type` = 1 AND `str` LIKE '%$';
+# DROP TABLE `global_reg_value`;

+ 34 - 8
src/char/char.c

@@ -1575,7 +1575,9 @@ int char_delete_char_sql(uint32 char_id){
 		Sql_ShowDebug(sql_handle);
 
 	/* delete character registry */
-	if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `type`=3 AND `char_id`='%d'", schema_config.reg_db, char_id) )
+	if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `char_id`='%d'", schema_config.char_reg_str_table, char_id) )
+		Sql_ShowDebug(sql_handle);
+	if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `char_id`='%d'", schema_config.char_reg_num_table, char_id) )
 		Sql_ShowDebug(sql_handle);
 
 	/* delete skills */
@@ -2129,7 +2131,8 @@ bool char_checkdb(void){
 	const char* sqltable[] = {
 		schema_config.char_db, schema_config.hotkey_db, schema_config.scdata_db, schema_config.cart_db, 
                 schema_config.inventory_db, schema_config.charlog_db, schema_config.storage_db, 
-                schema_config.reg_db, schema_config.skill_db, schema_config.interlog_db, schema_config.memo_db,
+                schema_config.char_reg_str_table, schema_config.char_reg_num_table, schema_config.acc_reg_str_table,
+                schema_config.acc_reg_num_table, schema_config.skill_db, schema_config.interlog_db, schema_config.memo_db,
 		schema_config.guild_db, schema_config.guild_alliance_db, schema_config.guild_castle_db, 
                 schema_config.guild_expulsion_db, schema_config.guild_member_db, 
                 schema_config.guild_skill_db, schema_config.guild_position_db, schema_config.guild_storage_db,
@@ -2162,8 +2165,23 @@ bool char_checkdb(void){
 		Sql_ShowDebug(sql_handle);
 		return false;
 	}
-	//checking reg_db
-	if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `char_id`,`str`,`value`,`type`,`account_id` FROM `%s` LIMIT 1;", schema_config.reg_db) ){
+	//checking char_reg_str_table
+	if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `char_id`,`key`,`index`,`value` from `%s` LIMIT 1;", schema_config.char_reg_str_table) ) {
+		Sql_ShowDebug(sql_handle);
+		return false;
+	}
+	//checking char_reg_num_table
+	if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `char_id`,`key`,`index`,`value` from `%s` LIMIT 1;", schema_config.char_reg_num_table) ) {
+		Sql_ShowDebug(sql_handle);
+		return false;
+	}
+	//checking global_acc_reg_str_table
+	if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `account_id`,`key`,`index`,`value` from `%s` LIMIT 1;", schema_config.acc_reg_str_table) ) {
+		Sql_ShowDebug(sql_handle);
+		return false;
+	}
+	//checking global_acc_reg_num_table
+	if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `account_id`,`key`,`index`,`value` from `%s` LIMIT 1;", schema_config.acc_reg_num_table) ) {
 		Sql_ShowDebug(sql_handle);
 		return false;
 	}
@@ -2321,7 +2339,6 @@ bool char_checkdb(void){
 		Sql_ShowDebug(sql_handle);
 		return false;
 	}
-	
 	//checking cart_db
 	if( SQL_ERROR == Sql_Query(sql_handle, "SELECT  `id`,`char_id`,`nameid`,`amount`,`equip`,`identify`,`refine`,"
 		"`attribute`,`card0`,`card1`,`card2`,`card3`,`expire_time`,`bound`,`unique_id`"
@@ -2383,8 +2400,6 @@ void char_sql_config_read(const char* cfgName) {
 			safestrncpy(schema_config.charlog_db, w2, sizeof(schema_config.charlog_db));
 		else if(!strcmpi(w1,"storage_db"))
 			safestrncpy(schema_config.storage_db, w2, sizeof(schema_config.storage_db));
-		else if(!strcmpi(w1,"reg_db"))
-			safestrncpy(schema_config.reg_db, w2, sizeof(schema_config.reg_db));
 		else if(!strcmpi(w1,"skill_db"))
 			safestrncpy(schema_config.skill_db, w2, sizeof(schema_config.skill_db));
 		else if(!strcmpi(w1,"interlog_db"))
@@ -2435,6 +2450,14 @@ void char_sql_config_read(const char* cfgName) {
 			safestrncpy(schema_config.skillcooldown_db, w2, sizeof(schema_config.skillcooldown_db));
 		else if(!strcmpi(w1,"bonus_script_db"))
 			safestrncpy(schema_config.bonus_script_db, w2, sizeof(schema_config.bonus_script_db));
+		else if(!strcmpi(w1,"char_reg_num_table"))
+			safestrncpy(schema_config.char_reg_num_table, w2, sizeof(schema_config.char_reg_num_table));
+		else if(!strcmpi(w1,"char_reg_str_table"))
+			safestrncpy(schema_config.char_reg_str_table, w2, sizeof(schema_config.char_reg_str_table));
+		else if(!strcmpi(w1,"acc_reg_str_table"))
+			safestrncpy(schema_config.acc_reg_str_table, w2, sizeof(schema_config.acc_reg_str_table));
+		else if(!strcmpi(w1,"acc_reg_num_table"))
+			safestrncpy(schema_config.acc_reg_num_table, w2, sizeof(schema_config.acc_reg_num_table));
 		//support the import command, just like any other config
 		else if(!strcmpi(w1,"import"))
 			char_sql_config_read(w2);
@@ -2454,7 +2477,6 @@ void char_set_default_sql(){
 	safestrncpy(schema_config.charlog_db,"charlog",sizeof(schema_config.charlog_db));
 	safestrncpy(schema_config.storage_db,"storage",sizeof(schema_config.storage_db));
 	safestrncpy(schema_config.interlog_db,"interlog",sizeof(schema_config.interlog_db));
-	safestrncpy(schema_config.reg_db,"global_reg_value",sizeof(schema_config.reg_db));
 	safestrncpy(schema_config.skill_db,"skill",sizeof(schema_config.skill_db));
 	safestrncpy(schema_config.memo_db,"memo",sizeof(schema_config.memo_db));
 	safestrncpy(schema_config.guild_db,"guild",sizeof(schema_config.guild_db));
@@ -2479,6 +2501,10 @@ void char_set_default_sql(){
 	safestrncpy(schema_config.ragsrvinfo_db,"ragsrvinfo",sizeof(schema_config.ragsrvinfo_db));
 	safestrncpy(schema_config.skillcooldown_db,"skillcooldown",sizeof(schema_config.skillcooldown_db));
 	safestrncpy(schema_config.bonus_script_db,"bonus_script",sizeof(schema_config.bonus_script_db));
+	safestrncpy(schema_config.char_reg_num_table,"char_reg_num",sizeof(schema_config.char_reg_num_table));
+	safestrncpy(schema_config.char_reg_str_table,"char_reg_str",sizeof(schema_config.char_reg_str_table));
+	safestrncpy(schema_config.acc_reg_str_table,"acc_reg_str",sizeof(schema_config.acc_reg_str_table));
+	safestrncpy(schema_config.acc_reg_num_table,"acc_reg_num",sizeof(schema_config.acc_reg_num_table));
 }
 
 //set default config

+ 4 - 2
src/char/char.h

@@ -40,7 +40,6 @@ struct Schema_Config {
 	char charlog_db[DB_NAME_LEN];
 	char storage_db[DB_NAME_LEN];
 	char interlog_db[DB_NAME_LEN];
-	char reg_db[DB_NAME_LEN];
 	char skill_db[DB_NAME_LEN];
 	char memo_db[DB_NAME_LEN];
 	char guild_db[DB_NAME_LEN];
@@ -65,6 +64,10 @@ struct Schema_Config {
 	char ragsrvinfo_db[DB_NAME_LEN];
 	char elemental_db[DB_NAME_LEN];
 	char bonus_script_db[DB_NAME_LEN];
+	char acc_reg_num_table[DB_NAME_LEN];
+	char acc_reg_str_table[DB_NAME_LEN];
+	char char_reg_str_table[DB_NAME_LEN];
+	char char_reg_num_table[DB_NAME_LEN];
 };
 extern struct Schema_Config schema_config;
 
@@ -249,7 +252,6 @@ int char_child(int parent_id, int child_id);
 int char_family(int pl1,int pl2,int pl3);
 
 int char_request_accreg2(uint32 account_id, uint32 char_id);
-int char_save_accreg2(unsigned char* buf, int len);
 
 //extern bool char_gm_read;
 int char_loadName(uint32 char_id, char* name);

+ 62 - 15
src/char/char_logif.c

@@ -164,17 +164,67 @@ int chlogif_broadcast_user_count(int tid, unsigned int tick, int id, intptr_t da
 	return 0;
 }
 
-//Send packet forward to login-server for account saving
-int chlogif_save_accreg2(unsigned char* buf, int len){
-	if (login_fd > 0) {
-		WFIFOHEAD(login_fd,len+4);
-		memcpy(WFIFOP(login_fd,4), buf, len);
-		WFIFOW(login_fd,0) = 0x2728;
-		WFIFOW(login_fd,2) = len+4;
-		WFIFOSET(login_fd,len+4);
-		return 1;
+void chlogif_request_global_accreg(uint32 account_id, uint32 char_id) {
+	WFIFOHEAD(login_fd, 60000 + 300);
+	WFIFOW(login_fd, 0) = 0x2728;
+	WFIFOW(login_fd, 2) = 14;
+	WFIFOL(login_fd, 4) = account_id;
+	WFIFOL(login_fd, 8) = char_id;
+	WFIFOW(login_fd, 12) = 0; // count
+}
+
+void chlogif_prepsend_global_accreg(void) {
+	WFIFOSET(login_fd, WFIFOW(login_fd,2));
+}
+
+void chlogif_send_global_accreg(const char *key, unsigned int index, intptr_t val, bool is_string) {
+	int nlen = WFIFOW(login_fd, 2);
+	size_t len;
+
+	len = strlen(key)+1;
+
+	WFIFOB(login_fd, nlen) = (unsigned char)len; // won't be higher; the column size is 32
+	nlen += 1;
+
+	safestrncpy((char*)WFIFOP(login_fd,nlen), key, len);
+	nlen += len;
+
+	WFIFOL(login_fd, nlen) = index;
+	nlen += 4;
+
+	if( is_string ) {
+		WFIFOB(login_fd, nlen) = val ? 2 : 3;
+		nlen += 1;
+
+		if( val ) {
+			char *sval = (char*)val;
+			len = strlen(sval)+1;
+
+			WFIFOB(login_fd, nlen) = (unsigned char)len; // won't be higher; the column size is 254
+			nlen += 1;
+
+			safestrncpy((char*)WFIFOP(login_fd,nlen), sval, len);
+			nlen += len;
+		}
+	} else {
+		WFIFOB(login_fd, nlen) = val ? 0 : 1;
+		nlen += 1;
+
+		if( val ) {
+			WFIFOL(login_fd, nlen) = (int)val;
+			nlen += 4;
+		}
+	}
+
+	WFIFOW(login_fd,12) += 1;
+	WFIFOW(login_fd, 2) = nlen;
+
+	if( WFIFOW(login_fd, 2) > 60000 ) {
+		int account_id = WFIFOL(login_fd,4), char_id = WFIFOL(login_fd,8);
+
+		chlogif_prepsend_global_accreg();
+		chlogif_request_global_accreg(account_id, char_id); // prepare next
 	}
-	return 0;
 }
 
 int chlogif_request_accreg2(uint32 account_id, uint32 char_id){
@@ -426,10 +476,7 @@ int chlogif_parse_ackacc2req(int fd, struct char_session_data* sd){
 		return 0;
 
 	{	//Receive account_reg2 registry, forward to map servers.
-		unsigned char buf[13+ACCOUNT_REG2_NUM*sizeof(struct global_reg)];
-		memcpy(buf,RFIFOP(fd,0), RFIFOW(fd,2));
-		WBUFW(buf,0) = 0x3804; //Map server can now receive all kinds of reg values with the same packet. [Skotlex]
-		chmapif_sendall(buf, WBUFW(buf,2));
+		chmapif_sendall(RFIFOP(fd,0), RFIFOW(fd,2));
 		RFIFOSKIP(fd, RFIFOW(fd,2));
 	}
 	return 1;
@@ -651,7 +698,7 @@ int chlogif_parse(int fd) {
 			// changesex reply
 			case 0x2723: next = chlogif_parse_ackchangesex(fd, sd); break;
 			// reply to an account_reg2 registry request
-			case 0x2729: next = chlogif_parse_ackacc2req(fd, sd); break;
+			case 0x3804: next = chlogif_parse_ackacc2req(fd, sd); break;
 			// State change of account/ban notification (from login-server)
 			case 0x2731: next = chlogif_parse_accbannotification(fd, sd); break;
 			// Login server request to kick a character out. [Skotlex]

+ 3 - 1
src/char/char_logif.h

@@ -20,7 +20,9 @@ void chlogif_pincode_start(int fd, struct char_session_data* sd);
 int chlogif_send_acc_tologin(int tid, unsigned int tick, int id, intptr_t data);
 int chlogif_broadcast_user_count(int tid, unsigned int tick, int id, intptr_t data);
 int chlogif_send_usercount(int users);
-int chlogif_save_accreg2(unsigned char* buf, int len);
+void chlogif_request_global_accreg(uint32 account_id, uint32 char_id);
+void chlogif_prepsend_global_accreg(void);
+void chlogif_send_global_accreg(const char *key, unsigned int index, intptr_t val, bool is_string);
 int chlogif_request_accreg2(uint32 account_id, uint32 char_id);
 int chlogif_send_reqaccdata(int fd, struct char_session_data *sd);
 int chlogif_send_setacconline(int aid);

+ 233 - 134
src/char/inter.c

@@ -40,7 +40,6 @@ char char_server_pw[32] = ""; // Allow user to send empty password (bugreport:77
 char char_server_db[32] = "ragnarok";
 char default_codepage[32] = ""; //Feature by irmin.
 
-static struct accreg *accreg_pt;
 unsigned int party_share_level = 10;
 
 // recv. packet list
@@ -512,120 +511,224 @@ void mapif_accinfo_ack(bool success, int map_fd, int u_fd, int u_aid, int accoun
 	Sql_FreeResult(sql_handle);
 }
 
-//--------------------------------------------------------
-// Save registry to sql
-int inter_accreg_tosql(uint32 account_id, uint32 char_id, struct accreg* reg, int type)
+/**
+ * Handles save reg data from map server and distributes accordingly.
+ *
+ * @param val either str or int, depending on type
+ * @param type false when int, true otherwise
+ **/
+void inter_savereg(uint32 account_id, uint32 char_id, const char *key, unsigned int index, intptr_t val, bool is_string)
 {
-	StringBuf buf;
-	int i;
+	if( key[0] == '#' && key[1] == '#' ) { // global account reg
+		if( session_isValid(login_fd) )
+			chlogif_send_global_accreg(key,index,val,is_string);
+		else {
+			ShowError("Login server unavailable, can't perform update on '%s' variable for AID:%d CID:%d\n",key,account_id,char_id);
+		}
+	} else if ( key[0] == '#' ) { // local account reg
+		if( is_string ) {
+			if( val ) {
+				if( SQL_ERROR == Sql_Query(sql_handle, "REPLACE INTO `%s` (`account_id`,`key`,`index`,`value`) VALUES ('%d','%s','%u','%s')", schema_config.acc_reg_str_table, account_id, key, index, (char*)val) )
+					Sql_ShowDebug(sql_handle);
+			} else {
+				if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `account_id` = '%d' AND `key` = '%s' AND `index` = '%u' LIMIT 1", schema_config.acc_reg_str_table, account_id, key, index) )
+					Sql_ShowDebug(sql_handle);
+			}
+		} else {
+			if( val ) {
+				if( SQL_ERROR == Sql_Query(sql_handle, "REPLACE INTO `%s` (`account_id`,`key`,`index`,`value`) VALUES ('%d','%s','%u','%d')", schema_config.acc_reg_num_table, account_id, key, index, (int)val) )
+					Sql_ShowDebug(sql_handle);
+			} else {
+				if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `account_id` = '%d' AND `key` = '%s' AND `index` = '%u' LIMIT 1", schema_config.acc_reg_num_table, account_id, key, index) )
+					Sql_ShowDebug(sql_handle);
+			}
+		}
+	} else { /* char reg */
+		if( is_string ) {
+			if( val ) {
+				if( SQL_ERROR == Sql_Query(sql_handle, "REPLACE INTO `%s` (`char_id`,`key`,`index`,`value`) VALUES ('%d','%s','%u','%s')", schema_config.char_reg_str_table, char_id, key, index, (char*)val) )
+					Sql_ShowDebug(sql_handle);
+			} else {
+				if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `char_id` = '%d' AND `key` = '%s' AND `index` = '%u' LIMIT 1", schema_config.char_reg_str_table, char_id, key, index) )
+					Sql_ShowDebug(sql_handle);
+			}
+		} else {
+			if( val ) {
+				if( SQL_ERROR == Sql_Query(sql_handle, "REPLACE INTO `%s` (`char_id`,`key`,`index`,`value`) VALUES ('%d','%s','%u','%d')", schema_config.char_reg_num_table, char_id, key, index, (int)val) )
+					Sql_ShowDebug(sql_handle);
+			} else {
+				if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `char_id` = '%d' AND `key` = '%s' AND `index` = '%u' LIMIT 1", schema_config.char_reg_num_table, char_id, key, index) )
+					Sql_ShowDebug(sql_handle);
+			}
+		}
+	}
+}
 
-	if( account_id <= 0 )
-		return 0;
-	reg->account_id = account_id;
-	reg->char_id = char_id;
+// Load account_reg from sql (type=2)
+int inter_accreg_fromsql(uint32 account_id, uint32 char_id, int fd, int type)
+{
+	char* data;
+	size_t len;
+	unsigned int plen = 0;
 
-	//`global_reg_value` (`type`, `account_id`, `char_id`, `str`, `value`)
 	switch( type ) {
-		case 3: //Char Reg
-			if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `type`=3 AND `char_id`='%d'", schema_config.reg_db, char_id) )
+		case 3: //char reg
+			if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `key`, `index`, `value` FROM `%s` WHERE `char_id`='%d'", schema_config.char_reg_str_table, char_id) )
 				Sql_ShowDebug(sql_handle);
-			account_id = 0;
 			break;
-		case 2: //Account Reg
-			if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `type`=2 AND `account_id`='%d'", schema_config.reg_db, account_id) )
+		case 2: //account reg
+			if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `key`, `index`, `value` FROM `%s` WHERE `account_id`='%d'", schema_config.acc_reg_str_table, account_id) )
 				Sql_ShowDebug(sql_handle);
-			char_id = 0;
 			break;
-		case 1: //Account2 Reg
-			ShowError("inter_accreg_tosql: Char server shouldn't handle type 1 registry values (##). That is the login server's work!\n");
+		case 1: //account2 reg
+			ShowError("inter_accreg_fromsql: Char server shouldn't handle type 1 registry values (##). That is the login server's job!\n");
 			return 0;
 		default:
-			ShowError("inter_accreg_tosql: Invalid type %d\n", type);
+			ShowError("inter_accreg_fromsql: Invalid type %d\n", type);
 			return 0;
 	}
 
-	if( reg->reg_num <= 0 )
-		return 0;
-
-	StringBuf_Init(&buf);
-	StringBuf_Printf(&buf, "INSERT INTO `%s` (`type`,`account_id`,`char_id`,`str`,`value`) VALUES ", schema_config.reg_db);
-
-	for( i = 0; i < reg->reg_num; ++i ) {
-		struct global_reg* r = &reg->reg[i];
-		if( r->str[0] != '\0' && r->value[0] != '\0' ) {
-			char str[32];
-			char val[256];
-
-			if( i > 0 )
-				StringBuf_AppendStr(&buf, ",");
-
-			Sql_EscapeString(sql_handle, str, r->str);
-			Sql_EscapeString(sql_handle, val, r->value);
-
-			StringBuf_Printf(&buf, "('%d','%d','%d','%s','%s')", type, account_id, char_id, str, val);
+	WFIFOHEAD(fd, 60000 + 300);
+	WFIFOW(fd, 0) = 0x3804;
+	// 0x2 = length, set prior to being sent
+	WFIFOL(fd, 4) = account_id;
+	WFIFOL(fd, 8) = char_id;
+	WFIFOB(fd, 12) = 0; // var type (only set when all vars have been sent, regardless of type)
+	WFIFOB(fd, 13) = 1; // is string type
+	WFIFOW(fd, 14) = 0; // count
+	plen = 16;
+
+	/**
+	 * Vessel!
+	 *
+	 * str type
+	 * { keyLength(B), key(<keyLength>), index(L), valLength(B), val(<valLength>) }
+	 **/
+	while ( SQL_SUCCESS == Sql_NextRow(sql_handle) ) {
+		Sql_GetData(sql_handle, 0, &data, NULL);
+		len = strlen(data)+1;
+
+		WFIFOB(fd, plen) = (unsigned char)len; // won't be higher; the column size is 32
+		plen += 1;
+
+		safestrncpy((char*)WFIFOP(fd,plen), data, len);
+		plen += len;
+
+		Sql_GetData(sql_handle, 1, &data, NULL);
+
+		WFIFOL(fd, plen) = (unsigned int)atol(data);
+		plen += 4;
+
+		Sql_GetData(sql_handle, 2, &data, NULL);
+		len = strlen(data)+1;
+
+		WFIFOB(fd, plen) = (unsigned char)len; // won't be higher; the column size is 254
+		plen += 1;
+
+		safestrncpy((char*)WFIFOP(fd,plen), data, len);
+		plen += len;
+
+		WFIFOW(fd, 14) += 1;
+
+		if( plen > 60000 ) {
+			WFIFOW(fd, 2) = plen;
+			WFIFOSET(fd, plen);
+
+			// prepare follow up
+			WFIFOHEAD(fd, 60000 + 300);
+			WFIFOW(fd, 0) = 0x3804;
+			// 0x2 = length, set prior to being sent
+			WFIFOL(fd, 4) = account_id;
+			WFIFOL(fd, 8) = char_id;
+			WFIFOB(fd, 12) = 0; // var type (only set when all vars have been sent, regardless of type)
+			WFIFOB(fd, 13) = 1; // is string type
+			WFIFOW(fd, 14) = 0; // count
+			plen = 16;
 		}
 	}
 
-	if( SQL_ERROR == Sql_QueryStr(sql_handle, StringBuf_Value(&buf)) ) {
-		Sql_ShowDebug(sql_handle);
-	}
-
-	StringBuf_Destroy(&buf);
-
-	return 1;
-}
-
-// Load account_reg from sql (type=2)
-int inter_accreg_fromsql(uint32 account_id,uint32 char_id, struct accreg *reg, int type)
-{
-	char* data;
-	size_t len;
-	int i;
-
-	if( reg == NULL)
-		return 0;
+	WFIFOW(fd, 2) = plen;
+	WFIFOSET(fd, plen);
 
-	memset(reg, 0, sizeof(struct accreg));
-	reg->account_id = account_id;
-	reg->char_id = char_id;
+	Sql_FreeResult(sql_handle);
 
-	//`global_reg_value` (`type`, `account_id`, `char_id`, `str`, `value`)
 	switch( type ) {
 		case 3: //char reg
-			if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `str`, `value` FROM `%s` WHERE `type`=3 AND `char_id`='%d'", schema_config.reg_db, char_id) )
+			if (SQL_ERROR == Sql_Query(sql_handle, "SELECT `key`, `index`, `value` FROM `%s` WHERE `char_id`='%d'", schema_config.char_reg_num_table, char_id))
 				Sql_ShowDebug(sql_handle);
 			break;
 		case 2: //account reg
-			if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `str`, `value` FROM `%s` WHERE `type`=2 AND `account_id`='%d'", schema_config.reg_db, account_id) )
+			if (SQL_ERROR == Sql_Query(sql_handle, "SELECT `key`, `index`, `value` FROM `%s` WHERE `account_id`='%d'", schema_config.acc_reg_num_table, account_id))
 				Sql_ShowDebug(sql_handle);
 			break;
+#if 0 // This is already checked above.
 		case 1: //account2 reg
 			ShowError("inter_accreg_fromsql: Char server shouldn't handle type 1 registry values (##). That is the login server's work!\n");
 			return 0;
-		default:
-			ShowError("inter_accreg_fromsql: Invalid type %d\n", type);
-			return 0;
+#endif // 0
 	}
-	for( i = 0; i < MAX_REG_NUM && SQL_SUCCESS == Sql_NextRow(sql_handle); ++i ) {
-		struct global_reg* r = &reg->reg[i];
-		// str
-		Sql_GetData(sql_handle, 0, &data, &len);
-		memcpy(r->str, data, min(len, sizeof(r->str)));
-		// value
-		Sql_GetData(sql_handle, 1, &data, &len);
-		memcpy(r->value, data, min(len, sizeof(r->value)));
+
+	WFIFOHEAD(fd, 60000 + 300);
+	WFIFOW(fd, 0) = 0x3804;
+	// 0x2 = length, set prior to being sent
+	WFIFOL(fd, 4) = account_id;
+	WFIFOL(fd, 8) = char_id;
+	WFIFOB(fd, 12) = 0; // var type (only set when all vars have been sent, regardless of type)
+	WFIFOB(fd, 13) = 0; // is int type
+	WFIFOW(fd, 14) = 0; // count
+	plen = 16;
+
+	/**
+	 * Vessel!
+	 *
+	 * int type
+	 * { keyLength(B), key(<keyLength>), index(L), value(L) }
+	 **/
+	while ( SQL_SUCCESS == Sql_NextRow(sql_handle) ) {
+		Sql_GetData(sql_handle, 0, &data, NULL);
+		len = strlen(data)+1;
+
+		WFIFOB(fd, plen) = (unsigned char)len;/* won't be higher; the column size is 32 */
+		plen += 1;
+
+		safestrncpy((char*)WFIFOP(fd,plen), data, len);
+		plen += len;
+
+		Sql_GetData(sql_handle, 1, &data, NULL);
+
+		WFIFOL(fd, plen) = (unsigned int)atol(data);
+		plen += 4;
+
+		Sql_GetData(sql_handle, 2, &data, NULL);
+
+		WFIFOL(fd, plen) = atoi(data);
+		plen += 4;
+
+		WFIFOW(fd, 14) += 1;
+
+		if( plen > 60000 ) {
+			WFIFOW(fd, 2) = plen;
+			WFIFOSET(fd, plen);
+
+			/* prepare follow up */
+			WFIFOHEAD(fd, 60000 + 300);
+			WFIFOW(fd, 0) = 0x3804;
+			/* 0x2 = length, set prior to being sent */
+			WFIFOL(fd, 4) = account_id;
+			WFIFOL(fd, 8) = char_id;
+			WFIFOB(fd, 12) = 0;/* var type (only set when all vars have been sent, regardless of type) */
+			WFIFOB(fd, 13) = 0;/* is int type */
+			WFIFOW(fd, 14) = 0;/* count */
+			plen = 16;
+		}
 	}
-	reg->reg_num = i;
-	Sql_FreeResult(sql_handle);
-	return 1;
-}
 
-// Initialize
-int inter_accreg_sql_init(void)
-{
-	CREATE(accreg_pt, struct accreg, 1);
-	return 0;
+	WFIFOB(fd, 12) = type;
+	WFIFOW(fd, 2) = plen;
+	WFIFOSET(fd, plen);
 
+	Sql_FreeResult(sql_handle);
+	return 1;
 }
 
 /*==========================================
@@ -727,7 +830,6 @@ int inter_init_sql(const char *file)
 	inter_homunculus_sql_init();
 	inter_mercenary_sql_init();
 	inter_elemental_sql_init();
-	inter_accreg_sql_init();
 	inter_mail_sql_init();
 	inter_auction_sql_init();
 
@@ -750,7 +852,6 @@ void inter_final(void)
 	inter_mail_sql_final();
 	inter_auction_sql_final();
 
-	if (accreg_pt) aFree(accreg_pt);
 	if(geoip_cache) aFree(geoip_cache);
 	
 	return;
@@ -820,29 +921,9 @@ static void mapif_account_reg(int fd, unsigned char *src)
 }
 
 // Send the requested account_reg
-int mapif_account_reg_reply(int fd,uint32 account_id,uint32 char_id, int type)
+int mapif_account_reg_reply(int fd, uint32 account_id, uint32 char_id, int type)
 {
-	struct accreg *reg=accreg_pt;
-	WFIFOHEAD(fd, 13 + 5000);
-	inter_accreg_fromsql(account_id,char_id,reg,type);
-
-	WFIFOW(fd,0)=0x3804;
-	WFIFOL(fd,4)=account_id;
-	WFIFOL(fd,8)=char_id;
-	WFIFOB(fd,12)=type;
-	if(reg->reg_num==0){
-		WFIFOW(fd,2)=13;
-	}else{
-		int i,p;
-		for (p=13,i = 0; i < reg->reg_num && p < 5000; i++) {
-			p+= sprintf((char*)WFIFOP(fd,p), "%s", reg->reg[i].str)+1; //We add 1 to consider the '\0' in place.
-			p+= sprintf((char*)WFIFOP(fd,p), "%s", reg->reg[i].value)+1;
-		}
-		WFIFOW(fd,2)=p;
-		if (p>= 5000)
-			ShowWarning("Too many acc regs for %d:%d, not all values were loaded.\n", account_id, char_id);
-	}
-	WFIFOSET(fd,WFIFOW(fd,2));
+	inter_accreg_fromsql(account_id,char_id,fd,type);
 	return 0;
 }
 
@@ -1020,34 +1101,52 @@ int mapif_parse_WisToGM(int fd)
 // Save account_reg into sql (type=2)
 int mapif_parse_Registry(int fd)
 {
-	int j,p,len, max;
-	struct accreg *reg=accreg_pt;
-
-	memset(accreg_pt,0,sizeof(struct accreg));
-	switch (RFIFOB(fd, 12)) {
-	case 3: //Character registry
-		max = GLOBAL_REG_NUM;
-	break;
-	case 2: //Account Registry
-		max = ACCOUNT_REG_NUM;
-	break;
-	case 1: //Account2 registry, must be sent over to login server.
-		return chlogif_save_accreg2(RFIFOP(fd,4), RFIFOW(fd,2)-4);
-	default:
-		return 1;
-	}
-	for(j=0,p=13;j<max && p<RFIFOW(fd,2);j++){
-		sscanf((char*)RFIFOP(fd,p), "%31c%n",reg->reg[j].str,&len);
-		reg->reg[j].str[len]='\0';
-		p +=len+1; //+1 to skip the '\0' between strings.
-		sscanf((char*)RFIFOP(fd,p), "%255c%n",reg->reg[j].value,&len);
-		reg->reg[j].value[len]='\0';
-		p +=len+1;
-	}
-	reg->reg_num=j;
+	int account_id = RFIFOL(fd, 4), char_id = RFIFOL(fd, 8), count = RFIFOW(fd, 12);
+
+	if( count ) {
+		int cursor = 14, i;
+		char key[32], sval[254];
+		bool isLoginActive = session_isActive(login_fd);
+
+		if( isLoginActive )
+			chlogif_request_global_accreg(account_id,char_id);
+
+		for(i = 0; i < count; i++) {
+			unsigned int index;
+			safestrncpy(key, (char*)RFIFOP(fd, cursor + 1), RFIFOB(fd, cursor));
+			cursor += RFIFOB(fd, cursor) + 1;
+
+			index = RFIFOL(fd, cursor);
+			cursor += 4;
+
+			switch (RFIFOB(fd, cursor++)) {
+				// int
+				case 0:
+					inter_savereg(account_id,char_id,key,index,RFIFOL(fd, cursor),false);
+					cursor += 4;
+					break;
+				case 1:
+					inter_savereg(account_id,char_id,key,index,0,false);
+					break;
+				// str
+				case 2:
+					safestrncpy(sval, (char*)RFIFOP(fd, cursor + 1), RFIFOB(fd, cursor));
+					cursor += RFIFOB(fd, cursor) + 1;
+					inter_savereg(account_id,char_id,key,index,(intptr_t)sval,true);
+					break;
+				case 3:
+					inter_savereg(account_id,char_id,key,index,0,true);
+					break;
+				default:
+					ShowError("mapif_parse_Registry: unknown type %d\n",RFIFOB(fd, cursor - 1));
+					return 1;
+			}
+
+		}
 
-	inter_accreg_tosql(RFIFOL(fd,4),RFIFOL(fd,8),reg, RFIFOB(fd,12));
-	mapif_account_reg(fd,RFIFOP(fd,0));	// Send updated accounts to other map servers.
+		if (isLoginActive)
+			chlogif_prepsend_global_accreg();
+	}
 	return 0;
 }
 

+ 2 - 2
src/char/inter.h

@@ -4,7 +4,6 @@
 #ifndef _INTER_SQL_H_
 #define _INTER_SQL_H_
 
-struct accreg;
 #include "../common/sql.h"
 
 int inter_init_sql(const char *file);
@@ -26,6 +25,7 @@ extern unsigned int party_share_level;
 extern Sql* sql_handle;
 extern Sql* lsql_handle;
 
-int inter_accreg_tosql(uint32 account_id, uint32 char_id, struct accreg *reg, int type);
+void inter_savereg(uint32 account_id, uint32 char_id, const char *key, unsigned int index, intptr_t val, bool is_string);
+int inter_accreg_fromsql(uint32 account_id, uint32 char_id, int fd, int type);
 
 #endif /* _INTER_SQL_H_ */

+ 192 - 41
src/common/db.c

@@ -3,7 +3,7 @@
  *  For more information, see LICENCE in the main folder
  *
  *  This file is separated in five sections:
- *  (1) Private typedefs, enums, structures, defines and gblobal variables
+ *  (1) Private typedefs, enums, structures, defines and global variables
  *  (2) Private functions
  *  (3) Protected functions used internally
  *  (4) Protected functions used in the interface of the database
@@ -47,6 +47,7 @@
  *  - create a db that organizes itself by splaying
  *
  *  HISTORY:
+ *    2013/08/25 - Added int64/uint64 support for keys [Ind/Hercules]
  *    2012/03/09 - Added enum for data types (int, uint, void*)
  *    2008/02/19 - Fixed db_obj_get not handling deleted entries correctly.
  *    2007/11/09 - Added an iterator to the database.
@@ -82,15 +83,15 @@
  *  DBNColor        - Enumeration of colors of the nodes.                    *
  *  DBNode          - Structure of a node in RED-BLACK trees.                *
  *  struct db_free  - Structure that holds a deleted node to be freed.       *
- *  DBMap_impl      - Struture of the database.                              *
+ *  DBMap_impl      - Structure of the database.                             *
  *  stats           - Statistics about the database system.                  *
 \*****************************************************************************/
 
 /**
- * If defined statistics about database nodes, database creating/destruction 
- * and function usage are keept and displayed when finalizing the database
+ * If defined statistics about database nodes, database creating/destruction
+ * and function usage are kept and displayed when finalizing the database
  * system.
- * WARNING: This adds overhead to every database operation (not shure how much).
+ * WARNING: This adds overhead to every database operation (not sure how much).
  * @private
  * @see #DBStats
  * @see #stats
@@ -234,10 +235,14 @@ static struct db_stats {
 	uint32 db_uint_alloc;
 	uint32 db_string_alloc;
 	uint32 db_istring_alloc;
+	uint32 db_int64_alloc;
+	uint32 db_uint64_alloc;
 	uint32 db_int_destroy;
 	uint32 db_uint_destroy;
 	uint32 db_string_destroy;
 	uint32 db_istring_destroy;
+	uint32 db_int64_destroy;
+	uint32 db_uint64_destroy;
 	// Function usage counters
 	uint32 db_rotate_left;
 	uint32 db_rotate_right;
@@ -254,10 +259,14 @@ static struct db_stats {
 	uint32 db_uint_cmp;
 	uint32 db_string_cmp;
 	uint32 db_istring_cmp;
+	uint32 db_int64_cmp;
+	uint32 db_uint64_cmp;
 	uint32 db_int_hash;
 	uint32 db_uint_hash;
 	uint32 db_string_hash;
 	uint32 db_istring_hash;
+	uint32 db_int64_hash;
+	uint32 db_uint64_hash;
 	uint32 db_release_nothing;
 	uint32 db_release_key;
 	uint32 db_release_data;
@@ -296,6 +305,8 @@ static struct db_stats {
 	uint32 db_i2key;
 	uint32 db_ui2key;
 	uint32 db_str2key;
+	uint32 db_i642key;
+	uint32 db_ui642key;
 	uint32 db_i2data;
 	uint32 db_ui2data;
 	uint32 db_ptr2data;
@@ -489,7 +500,7 @@ static void db_rebalance_erase(DBNode node, DBNode *root)
 	}
 
 	// Remove the node from the tree
-	if (y != node) { // both childs existed
+	if (y != node) { // both child existed
 		// put the left of 'node' in the left of 'y'
 		node->left->parent = y;
 		y->left = node->left;
@@ -500,10 +511,10 @@ static void db_rebalance_erase(DBNode node, DBNode *root)
 			x_parent = y->parent;
 			if (x) x->parent = y->parent;
 			y->parent->left = x;
-			// put the right of 'node' in 'y' 
+			// put the right of 'node' in 'y'
 			y->right = node->right;
 			node->right->parent = y;
-		// 'y' is a direct child of 'node'
+			// 'y' is a direct child of 'node'
 		} else {
 			x_parent = y;
 		}
@@ -556,7 +567,7 @@ static void db_rebalance_erase(DBNode node, DBNode *root)
 					x = x_parent;
 					x_parent = x_parent->parent;
 				} else {
-					if (w->right == NULL ||	w->right->color == BLACK) {
+					if (w->right == NULL || w->right->color == BLACK) {
 						if (w->left) w->left->color = BLACK;
 						w->color = RED;
 						db_rotate_right(w, root);
@@ -824,10 +835,14 @@ static void db_free_unlock(DBMap_impl* db)
  *  db_uint_cmp        - Default comparator for DB_UINT databases.           *
  *  db_string_cmp      - Default comparator for DB_STRING databases.         *
  *  db_istring_cmp     - Default comparator for DB_ISTRING databases.        *
+ *  db_int64_cmp       - Default comparator for DB_INT64 databases.          *
+ *  db_uint64_cmp      - Default comparator for DB_UINT64 databases.         *
  *  db_int_hash        - Default hasher for DB_INT databases.                *
  *  db_uint_hash       - Default hasher for DB_UINT databases.               *
  *  db_string_hash     - Default hasher for DB_STRING databases.             *
  *  db_istring_hash    - Default hasher for DB_ISTRING databases.            *
+ *  db_int64_hash      - Default hasher for DB_INT64 databases.              *
+ *  db_uint64_hash     - Default hasher for DB_UINT64 databases.             *
  *  db_release_nothing - Releaser that releases nothing.                     *
  *  db_release_key     - Releaser that only releases the key.                *
  *  db_release_data    - Releaser that only releases the data.               *
@@ -914,6 +929,51 @@ static int db_istring_cmp(DBKey key1, DBKey key2, unsigned short maxlen)
 	return strncasecmp((const char *)key1.str, (const char *)key2.str, maxlen);
 }
 
+/**
+ * Default comparator for DB_INT64 databases.
+ * Compares key1 to key2.
+ * Return 0 if equal, negative if lower and positive if higher.
+ * <code>maxlen</code> is ignored.
+ * @param key1 Key to be compared
+ * @param key2 Key being compared to
+ * @param maxlen Maximum length of the key to hash
+ * @return 0 if equal, negative if lower and positive if higher
+ * @see DBType#DB_INT64
+ * @see #DBComparator
+ * @see #db_default_cmp(DBType)
+ */
+static int db_int64_cmp(DBKey key1, DBKey key2, unsigned short maxlen)
+{
+	(void)maxlen;//not used
+	DB_COUNTSTAT(db_int64_cmp);
+	if (key1.i64 < key2.i64) return -1;
+	if (key1.i64 > key2.i64) return 1;
+	return 0;
+}
+
+/**
+ * Default comparator for DB_UINT64 databases.
+ * Compares key1 to key2.
+ * Return 0 if equal, negative if lower and positive if higher.
+ * <code>maxlen</code> is ignored.
+ * @param key1 Key to be compared
+ * @param key2 Key being compared to
+ * @param maxlen Maximum length of the key to hash
+ * @return 0 if equal, negative if lower and positive if higher
+ * @see DBType#DB_UINT64
+ * @see #DBComparator
+ * @see #db_default_cmp(DBType)
+ */
+static int db_uint64_cmp(DBKey key1, DBKey key2, unsigned short maxlen)
+{
+	(void)maxlen;//not used
+	DB_COUNTSTAT(db_uint64_cmp);
+	if (key1.ui64 < key2.ui64) return -1;
+	if (key1.ui64 > key2.ui64) return 1;
+	return 0;
+}
+
+
 /**
  * Default hasher for DB_INT databases.
  * Returns the value of the key as an unsigned int.
@@ -925,11 +985,11 @@ static int db_istring_cmp(DBKey key1, DBKey key2, unsigned short maxlen)
  * @see #DBHasher
  * @see #db_default_hash(DBType)
  */
-static unsigned int db_int_hash(DBKey key, unsigned short maxlen)
+static uint64 db_int_hash(DBKey key, unsigned short maxlen)
 {
 	(void)maxlen;//not used
 	DB_COUNTSTAT(db_int_hash);
-	return (unsigned int)key.i;
+	return (uint64)key.i;
 }
 
 /**
@@ -943,11 +1003,11 @@ static unsigned int db_int_hash(DBKey key, unsigned short maxlen)
  * @see #DBHasher
  * @see #db_default_hash(DBType)
  */
-static unsigned int db_uint_hash(DBKey key, unsigned short maxlen)
+static uint64 db_uint_hash(DBKey key, unsigned short maxlen)
 {
 	(void)maxlen;//not used
 	DB_COUNTSTAT(db_uint_hash);
-	return key.ui;
+	return (uint64)key.ui;
 }
 
 /**
@@ -959,7 +1019,7 @@ static unsigned int db_uint_hash(DBKey key, unsigned short maxlen)
  * @see #DBHasher
  * @see #db_default_hash(DBType)
  */
-static unsigned int db_string_hash(DBKey key, unsigned short maxlen)
+static uint64 db_string_hash(DBKey key, unsigned short maxlen)
 {
 	const char *k = key.str;
 	unsigned int hash = 0;
@@ -974,7 +1034,7 @@ static unsigned int db_string_hash(DBKey key, unsigned short maxlen)
 			break;
 	}
 
-	return hash;
+	return (uint64)hash;
 }
 
 /**
@@ -985,7 +1045,7 @@ static unsigned int db_string_hash(DBKey key, unsigned short maxlen)
  * @see DBType#DB_ISTRING
  * @see #db_default_hash(DBType)
  */
-static unsigned int db_istring_hash(DBKey key, unsigned short maxlen)
+static uint64 db_istring_hash(DBKey key, unsigned short maxlen)
 {
 	const char *k = key.str;
 	unsigned int hash = 0;
@@ -1000,7 +1060,43 @@ static unsigned int db_istring_hash(DBKey key, unsigned short maxlen)
 			break;
 	}
 
-	return hash;
+	return (uint64)hash;
+}
+
+/**
+ * Default hasher for DB_INT64 databases.
+ * Returns the value of the key as an unsigned int.
+ * <code>maxlen</code> is ignored.
+ * @param key Key to be hashed
+ * @param maxlen Maximum length of the key to hash
+ * @return hash of the key
+ * @see DBType#DB_INT64
+ * @see #DBHasher
+ * @see #db_default_hash(DBType)
+ */
+static uint64 db_int64_hash(DBKey key, unsigned short maxlen)
+{
+	(void)maxlen;//not used
+	DB_COUNTSTAT(db_int64_hash);
+	return (uint64)key.i64;
+}
+
+/**
+ * Default hasher for DB_UINT64 databases.
+ * Just returns the value of the key.
+ * <code>maxlen</code> is ignored.
+ * @param key Key to be hashed
+ * @param maxlen Maximum length of the key to hash
+ * @return hash of the key
+ * @see DBType#DB_UINT64
+ * @see #DBHasher
+ * @see #db_default_hash(DBType)
+ */
+static uint64 db_uint64_hash(DBKey key, unsigned short maxlen)
+{
+	(void)maxlen;//not used
+	DB_COUNTSTAT(db_uint64_hash);
+	return key.ui64;
 }
 
 /**
@@ -1117,7 +1213,7 @@ static void db_release_both(DBKey key, DBData data, DBRelease which)
 DBData* dbit_obj_first(DBIterator* self, DBKey* out_key)
 {
 	DBIterator_impl* it = (DBIterator_impl*)self;
-	
+
 	DB_COUNTSTAT(dbit_first);
 	// position before the first entry
 	it->ht_index = -1;
@@ -1139,7 +1235,7 @@ DBData* dbit_obj_first(DBIterator* self, DBKey* out_key)
 DBData* dbit_obj_last(DBIterator* self, DBKey* out_key)
 {
 	DBIterator_impl* it = (DBIterator_impl*)self;
-	
+
 	DB_COUNTSTAT(dbit_last);
 	// position after the last entry
 	it->ht_index = HASH_SIZE;
@@ -1261,7 +1357,6 @@ DBData* dbit_obj_prev(DBIterator* self, DBKey* out_key)
 			node = &fake;
 		}
 
-		
 		while( node )
 		{// next node
 			if( node->left )
@@ -1303,7 +1398,7 @@ DBData* dbit_obj_prev(DBIterator* self, DBKey* out_key)
 
 /**
  * Returns true if the fetched entry exists.
- * The databases entries might have NULL data, so use this to to test if 
+ * The databases entries might have NULL data, so use this to to test if
  * the iterator is done.
  * @param self Iterator
  * @return true if the entry exists
@@ -1320,7 +1415,7 @@ bool dbit_obj_exists(DBIterator* self)
 
 /**
  * Removes the current entry from the database.
- * NOTE: {@link DBIterator#exists} will return false until another entry 
+ * NOTE: {@link DBIterator#exists} will return false until another entry
  *       is fetched
  * Puts data of the removed entry in out_data, if out_data is not NULL.
  * @param self Iterator
@@ -1371,7 +1466,7 @@ void dbit_obj_destroy(DBIterator* self)
 /**
  * Returns a new iterator for this database.
  * The iterator keeps the database locked until it is destroyed.
- * The database will keep functioning normally but will only free internal 
+ * The database will keep functioning normally but will only free internal
  * memory when unlocked, so destroy the iterator as soon as possible.
  * @param self Database
  * @return New iterator
@@ -1507,7 +1602,7 @@ static DBData* db_obj_get(DBMap* self, DBKey key)
  * It puts a maximum of <code>max</code> entries into <code>buf</code>.
  * If <code>buf</code> is NULL, it only counts the matches.
  * Returns the number of entries that matched.
- * NOTE: if the value returned is greater than <code>max</code>, only the 
+ * NOTE: if the value returned is greater than <code>max</code>, only the
  * first <code>max</code> entries found are put into the buffer.
  * @param self Interface of the database
  * @param buf Buffer to put the data of the matched entries
@@ -1546,17 +1641,17 @@ static unsigned int db_obj_vgetall(DBMap* self, DBData **buf, unsigned int max,
 				}
 				va_end(argscopy);
 			}
-			
+
 			if (node->left) {
 				node = node->left;
 				continue;
 			}
-			
+
 			if (node->right) {
 				node = node->right;
 				continue;
 			}
-			
+
 			while (node) {
 				parent = node->parent;
 				if (parent && parent->right && parent->left == node) {
@@ -1578,7 +1673,7 @@ static unsigned int db_obj_vgetall(DBMap* self, DBData **buf, unsigned int max,
  * It puts a maximum of <code>max</code> entries into <code>buf</code>.
  * If <code>buf</code> is NULL, it only counts the matches.
  * Returns the number of entries that matched.
- * NOTE: if the value returned is greater than <code>max</code>, only the 
+ * NOTE: if the value returned is greater than <code>max</code>, only the
  * first <code>max</code> entries found are put into the buffer.
  * @param self Interface of the database
  * @param buf Buffer to put the data of the matched entries
@@ -1606,7 +1701,7 @@ static unsigned int db_obj_getall(DBMap* self, DBData **buf, unsigned int max, D
 
 /**
  * Get the data of the entry identified by the key.
- * If the entry does not exist, an entry is added with the data returned by 
+ * If the entry does not exist, an entry is added with the data returned by
  * <code>create</code>.
  * @param self Interface of the database
  * @param key Key that identifies the entry
@@ -1705,7 +1800,7 @@ static DBData* db_obj_vensure(DBMap* self, DBKey key, DBCreateData create, va_li
 /**
  * Just calls {@link DBMap#vensure}.
  * Get the data of the entry identified by the key.
- * If the entry does not exist, an entry is added with the data returned by 
+ * If the entry does not exist, an entry is added with the data returned by
  * <code>create</code>.
  * @param self Interface of the database
  * @param key Key that identifies the entry
@@ -1863,7 +1958,7 @@ static int db_obj_remove(DBMap* self, DBKey key, DBData *out_data)
 				db->alloc_file, db->alloc_line);
 		return 0; // nullpo candidate
 	}
-	if (!(db->options&DB_OPT_ALLOW_NULL_KEY) && db_is_key_null(db->type, key))	{
+	if (!(db->options&DB_OPT_ALLOW_NULL_KEY) && db_is_key_null(db->type, key)) {
 		ShowError("db_remove: Attempted to use non-allowed NULL key for db allocated at %s:%d\n",db->alloc_file, db->alloc_line);
 		return 0; // nullpo candidate
 	}
@@ -1956,7 +2051,7 @@ static int db_obj_vforeach(DBMap* self, DBApply func, va_list args)
  * Apply <code>func</code> to every entry in the database.
  * Returns the sum of values returned by func.
  * @param self Interface of the database
- * @param func Function to be applyed
+ * @param func Function to be applied
  * @param ... Extra arguments for func
  * @return Sum of the values returned by func
  * @protected
@@ -2053,7 +2148,7 @@ static int db_obj_vclear(DBMap* self, DBApply func, va_list args)
  * Before deleting an entry, func is applied to it.
  * Releases the key and the data.
  * Returns the sum of values returned by func, if it exists.
- * NOTE: This locks the database globally. Any attempt to insert or remove 
+ * NOTE: This locks the database globally. Any attempt to insert or remove
  * a database entry will give an error and be aborted (except for clearing).
  * @param self Interface of the database
  * @param func Function to be applied to every entry before deleting
@@ -2081,7 +2176,7 @@ static int db_obj_clear(DBMap* self, DBApply func, ...)
  * Finalize the database, feeing all the memory it uses.
  * Before deleting an entry, func is applied to it.
  * Returns the sum of values returned by func, if it exists.
- * NOTE: This locks the database globally. Any attempt to insert or remove 
+ * NOTE: This locks the database globally. Any attempt to insert or remove
  * a database entry will give an error and be aborted (except for clearing).
  * @param self Interface of the database
  * @param func Function to be applied to every entry before deleting
@@ -2114,6 +2209,8 @@ static int db_obj_vdestroy(DBMap* self, DBApply func, va_list args)
 		case DB_UINT: DB_COUNTSTAT(db_uint_destroy); break;
 		case DB_STRING: DB_COUNTSTAT(db_string_destroy); break;
 		case DB_ISTRING: DB_COUNTSTAT(db_istring_destroy); break;
+		case DB_INT64: DB_COUNTSTAT(db_int64_destroy); break;
+		case DB_UINT64: DB_COUNTSTAT(db_uint64_destroy); break;
 	}
 #endif /* DB_ENABLE_STATS */
 	db_free_lock(db);
@@ -2134,7 +2231,7 @@ static int db_obj_vdestroy(DBMap* self, DBApply func, va_list args)
  * Before deleting an entry, func is applied to it.
  * Releases the key and the data.
  * Returns the sum of values returned by func, if it exists.
- * NOTE: This locks the database globally. Any attempt to insert or remove 
+ * NOTE: This locks the database globally. Any attempt to insert or remove
  * a database entry will give an error and be aborted.
  * @param self Database
  * @param func Function to be applied to every entry before deleting
@@ -2233,11 +2330,13 @@ static DBOptions db_obj_options(DBMap* self)
  *  db_default_cmp     - Get the default comparator for a type of database.
  *  db_default_hash    - Get the default hasher for a type of database.
  *  db_default_release - Get the default releaser for a type of database with the specified options.
- *  db_custom_release  - Get a releaser that behaves a certains way.
+ *  db_custom_release  - Get a releaser that behaves a certain way.
  *  db_alloc           - Allocate a new database.
  *  db_i2key           - Manual cast from 'int' to 'DBKey'.
  *  db_ui2key          - Manual cast from 'unsigned int' to 'DBKey'.
  *  db_str2key         - Manual cast from 'unsigned char *' to 'DBKey'.
+ *  db_i642key         - Manual cast from 'int64' to 'DBKey'.
+ *  db_ui642key        - Manual cast from 'uin64' to 'DBKey'.
  *  db_i2data          - Manual cast from 'int' to 'DBData'.
  *  db_ui2data         - Manual cast from 'unsigned int' to 'DBData'.
  *  db_ptr2data        - Manual cast from 'void*' to 'DBData'.
@@ -2264,7 +2363,9 @@ DBOptions db_fix_options(DBType type, DBOptions options)
 	DB_COUNTSTAT(db_fix_options);
 	switch (type) {
 		case DB_INT:
-		case DB_UINT: // Numeric database, do nothing with the keys
+		case DB_UINT:
+		case DB_INT64:
+		case DB_UINT64: // Numeric database, do nothing with the keys
 			return (DBOptions)(options&~(DB_OPT_DUP_KEY|DB_OPT_RELEASE_KEY));
 
 		default:
@@ -2284,6 +2385,8 @@ DBOptions db_fix_options(DBType type, DBOptions options)
  * @see #db_uint_cmp(DBKey,DBKey,unsigned short)
  * @see #db_string_cmp(DBKey,DBKey,unsigned short)
  * @see #db_istring_cmp(DBKey,DBKey,unsigned short)
+ * @see #db_int64_cmp(DBKey,DBKey,unsigned short)
+ * @see #db_uint64_cmp(DBKey,DBKey,unsigned short)
  */
 DBComparator db_default_cmp(DBType type)
 {
@@ -2293,6 +2396,8 @@ DBComparator db_default_cmp(DBType type)
 		case DB_UINT:    return &db_uint_cmp;
 		case DB_STRING:  return &db_string_cmp;
 		case DB_ISTRING: return &db_istring_cmp;
+		case DB_INT64:   return &db_int64_cmp;
+		case DB_UINT64:  return &db_uint64_cmp;
 		default:
 			ShowError("db_default_cmp: Unknown database type %u\n", type);
 			return NULL;
@@ -2308,6 +2413,8 @@ DBComparator db_default_cmp(DBType type)
  * @see #db_uint_hash(DBKey,unsigned short)
  * @see #db_string_hash(DBKey,unsigned short)
  * @see #db_istring_hash(DBKey,unsigned short)
+ * @see #db_int64_hash(DBKey,unsigned short)
+ * @see #db_uint64_hash(DBKey,unsigned short)
  */
 DBHasher db_default_hash(DBType type)
 {
@@ -2317,6 +2424,8 @@ DBHasher db_default_hash(DBType type)
 		case DB_UINT:    return &db_uint_hash;
 		case DB_STRING:  return &db_string_hash;
 		case DB_ISTRING: return &db_istring_hash;
+		case DB_INT64:   return &db_int64_hash;
+		case DB_UINT64:  return &db_uint64_hash;
 		default:
 			ShowError("db_default_hash: Unknown database type %u\n", type);
 			return NULL;
@@ -2324,7 +2433,7 @@ DBHasher db_default_hash(DBType type)
 }
 
 /**
- * Returns the default releaser for the specified type of database with the 
+ * Returns the default releaser for the specified type of database with the
  * specified options.
  * NOTE: the options are fixed with {@link #db_fix_options(DBType,DBOptions)}
  * before choosing the releaser.
@@ -2385,7 +2494,7 @@ DBReleaser db_custom_release(DBRelease which)
  * @param line Line of the file where the database is being allocated
  * @param type Type of database
  * @param options Options of the database
- * @param maxlen Maximum length of the string to be used as key in string 
+ * @param maxlen Maximum length of the string to be used as key in string
  *          databases. If 0, the maximum number of maxlen is used (64K).
  * @return The interface of the database
  * @public
@@ -2404,6 +2513,8 @@ DBMap* db_alloc(const char *file, int line, DBType type, DBOptions options, unsi
 		case DB_UINT: DB_COUNTSTAT(db_uint_alloc); break;
 		case DB_STRING: DB_COUNTSTAT(db_string_alloc); break;
 		case DB_ISTRING: DB_COUNTSTAT(db_istring_alloc); break;
+		case DB_INT64: DB_COUNTSTAT(db_int64_alloc); break;
+		case DB_UINT64: DB_COUNTSTAT(db_uint64_alloc); break;
 	}
 #endif /* DB_ENABLE_STATS */
 	CREATE(db, struct DBMap_impl, 1);
@@ -2501,6 +2612,36 @@ DBKey db_str2key(const char *key)
 	return ret;
 }
 
+/**
+ * Manual cast from 'int64' to the union DBKey.
+ * @param key Key to be casted
+ * @return The key as a DBKey union
+ * @public
+ */
+DBKey db_i642key(int64 key)
+{
+	DBKey ret;
+
+	DB_COUNTSTAT(db_i642key);
+	ret.i64 = key;
+	return ret;
+}
+
+/**
+ * Manual cast from 'uin64' to the union DBKey.
+ * @param key Key to be casted
+ * @return The key as a DBKey union
+ * @public
+ */
+DBKey db_ui642key(uint64 key)
+{
+	DBKey ret;
+
+	DB_COUNTSTAT(db_ui642key);
+	ret.ui64 = key;
+	return ret;
+}
+
 /**
  * Manual cast from 'int' to the struct DBData.
  * @param data Data to be casted
@@ -2620,11 +2761,15 @@ void db_final(void)
 			"DB_INT     : allocated %10u, destroyed %10u\n"
 			"DB_UINT    : allocated %10u, destroyed %10u\n"
 			"DB_STRING  : allocated %10u, destroyed %10u\n"
-			"DB_ISTRING : allocated %10u, destroyed %10u\n",
+			"DB_ISTRING : allocated %10u, destroyed %10u\n"
+			"DB_INT64   : allocated %10u, destroyed %10u\n"
+			"DB_UINT64  : allocated %10u, destroyed %10u\n",
 			stats.db_int_alloc,     stats.db_int_destroy,
 			stats.db_uint_alloc,    stats.db_uint_destroy,
 			stats.db_string_alloc,  stats.db_string_destroy,
-			stats.db_istring_alloc, stats.db_istring_destroy);
+			stats.db_istring_alloc, stats.db_istring_destroy,
+			stats.db_int64_alloc,   stats.db_int64_destroy,
+			stats.db_uint64_alloc,  stats.db_uint64_destroy);
 	ShowInfo(CL_WHITE"Database function counters"CL_RESET":\n"
 			"db_rotate_left     %10u, db_rotate_right    %10u,\n"
 			"db_rebalance       %10u, db_rebalance_erase %10u,\n"
@@ -2634,8 +2779,10 @@ void db_final(void)
 			"db_free_lock       %10u, db_free_unlock     %10u,\n"
 			"db_int_cmp         %10u, db_uint_cmp        %10u,\n"
 			"db_string_cmp      %10u, db_istring_cmp     %10u,\n"
+			"db_int64_cmp       %10u, db_uint64_cmp      %10u,\n"
 			"db_int_hash        %10u, db_uint_hash       %10u,\n"
 			"db_string_hash     %10u, db_istring_hash    %10u,\n"
+			"db_int64_hash      %10u, db_uint64_hash     %10u,\n"
 			"db_release_nothing %10u, db_release_key     %10u,\n"
 			"db_release_data    %10u, db_release_both    %10u,\n"
 			"dbit_first         %10u, dbit_last          %10u,\n"
@@ -2655,6 +2802,7 @@ void db_final(void)
 			"db_default_release %10u, db_custom_release  %10u,\n"
 			"db_alloc           %10u, db_i2key           %10u,\n"
 			"db_ui2key          %10u, db_str2key         %10u,\n"
+			"db_i642key         %10u, db_ui642key        %10u,\n"
 			"db_i2data          %10u, db_ui2data         %10u,\n"
 			"db_ptr2data        %10u, db_data2i          %10u,\n"
 			"db_data2ui         %10u, db_data2ptr        %10u,\n"
@@ -2667,8 +2815,10 @@ void db_final(void)
 			stats.db_free_lock,       stats.db_free_unlock,
 			stats.db_int_cmp,         stats.db_uint_cmp,
 			stats.db_string_cmp,      stats.db_istring_cmp,
+			stats.db_int64_cmp,       stats.db_uint64_cmp,
 			stats.db_int_hash,        stats.db_uint_hash,
 			stats.db_string_hash,     stats.db_istring_hash,
+			stats.db_int64_hash,      stats.db_uint64_hash,
 			stats.db_release_nothing, stats.db_release_key,
 			stats.db_release_data,    stats.db_release_both,
 			stats.dbit_first,         stats.dbit_last,
@@ -2688,6 +2838,7 @@ void db_final(void)
 			stats.db_default_release, stats.db_custom_release,
 			stats.db_alloc,           stats.db_i2key,
 			stats.db_ui2key,          stats.db_str2key,
+			stats.db_i642key,         stats.db_ui642key,
 			stats.db_i2data,          stats.db_ui2data,
 			stats.db_ptr2data,        stats.db_data2i,
 			stats.db_data2ui,         stats.db_data2ptr,

+ 106 - 60
src/common/db.h

@@ -20,6 +20,7 @@
  *  - see what functions need or should be added to the database interface   *
  *                                                                           *
  *  HISTORY:                                                                 *
+ *    2013/08/25 - Added int64/uint64 support for keys                       *
  *    2012/03/09 - Added enum for data types (int, uint, void*)              *
  *    2007/11/09 - Added an iterator to the database.                        *
  *    2.1 (Athena build #???#) - Portability fix                             *
@@ -77,12 +78,14 @@ typedef enum DBRelease {
 
 /**
  * Supported types of database.
- * See {@link #db_fix_options(DBType,DBOptions)} for restrictions of the 
+ * See {@link #db_fix_options(DBType,DBOptions)} for restrictions of the
  * types of databases.
  * @param DB_INT Uses int's for keys
  * @param DB_UINT Uses unsigned int's for keys
  * @param DB_STRING Uses strings for keys.
  * @param DB_ISTRING Uses case insensitive strings for keys.
+ * @param DB_INT64 Uses int64's for keys
+ * @param DB_UINT64 Uses uint64's for keys
  * @public
  * @see #DBOptions
  * @see #DBKey
@@ -96,21 +99,23 @@ typedef enum DBType {
 	DB_INT,
 	DB_UINT,
 	DB_STRING,
-	DB_ISTRING
+	DB_ISTRING,
+	DB_INT64,
+	DB_UINT64,
 } DBType;
 
 /**
- * Bitfield of options that define the behaviour of the database.
- * See {@link #db_fix_options(DBType,DBOptions)} for restrictions of the 
+ * Bitfield of options that define the behavior of the database.
+ * See {@link #db_fix_options(DBType,DBOptions)} for restrictions of the
  * types of databases.
  * @param DB_OPT_BASE Base options: does not duplicate keys, releases nothing
  *          and does not allow NULL keys or NULL data.
- * @param DB_OPT_DUP_KEY Duplicates the keys internally. If DB_OPT_RELEASE_KEY 
+ * @param DB_OPT_DUP_KEY Duplicates the keys internally. If DB_OPT_RELEASE_KEY
  *          is defined, the real key is freed as soon as the entry is added.
  * @param DB_OPT_RELEASE_KEY Releases the key.
- * @param DB_OPT_RELEASE_DATA Releases the data whenever an entry is removed 
+ * @param DB_OPT_RELEASE_DATA Releases the data whenever an entry is removed
  *          from the database.
- *          WARNING: for funtions that return the data (like DBMap::remove),
+ *          WARNING: for functions that return the data (like DBMap::remove),
  *          a dangling pointer will be returned.
  * @param DB_OPT_RELEASE_BOTH Releases both key and data.
  * @param DB_OPT_ALLOW_NULL_KEY Allow NULL keys in the database.
@@ -145,6 +150,8 @@ typedef union DBKey {
 	int i;
 	unsigned int ui;
 	const char *str;
+	int64 i64;
+	uint64 ui64;
 } DBKey;
 
 /**
@@ -180,7 +187,7 @@ typedef struct DBData {
 } DBData;
 
 /**
- * Format of functions that create the data for the key when the entry doesn't 
+ * Format of functions that create the data for the key when the entry doesn't
  * exist in the database yet.
  * @param key Key of the database entry
  * @param args Extra arguments of the function
@@ -192,9 +199,9 @@ typedef struct DBData {
 typedef DBData (*DBCreateData)(DBKey key, va_list args);
 
 /**
- * Format of functions to be applied to an unspecified quantity of entries of 
+ * Format of functions to be applied to an unspecified quantity of entries of
  * a database.
- * Any function that applies this function to the database will return the sum 
+ * Any function that applies this function to the database will return the sum
  * of values returned by this function.
  * @param key Key of the database entry
  * @param data Data of the database entry
@@ -245,7 +252,7 @@ typedef int (*DBComparator)(DBKey key1, DBKey key2, unsigned short maxlen);
  * @public
  * @see #db_default_hash(DBType)
  */
-typedef unsigned int (*DBHasher)(DBKey key, unsigned short maxlen);
+typedef uint64 (*DBHasher)(DBKey key, unsigned short maxlen);
 
 /**
  * Format of the releaser used by the database system.
@@ -272,7 +279,7 @@ typedef struct DBMap DBMap;
  * Database iterator.
  * Supports forward iteration, backward iteration and removing entries from the database.
  * The iterator is initially positioned before the first entry of the database.
- * While the iterator exists the database is locked internally, so invoke 
+ * While the iterator exists the database is locked internally, so invoke
  * {@link DBIterator#destroy} as soon as possible.
  * @public
  * @see #DBMap
@@ -326,7 +333,7 @@ struct DBIterator
 
 	/**
 	 * Returns true if the fetched entry exists.
-	 * The databases entries might have NULL data, so use this to to test if 
+	 * The databases entries might have NULL data, so use this to to test if
 	 * the iterator is done.
 	 * @param self Iterator
 	 * @return true is the entry exists
@@ -336,7 +343,7 @@ struct DBIterator
 
 	/**
 	 * Removes the current entry from the database.
-	 * NOTE: {@link DBIterator#exists} will return false until another entry 
+	 * NOTE: {@link DBIterator#exists} will return false until another entry
 	 *       is fetched
 	 * Puts data of the removed entry in out_data, if out_data is not NULL.
 	 * @param self Iterator
@@ -357,7 +364,7 @@ struct DBIterator
 };
 
 /**
- * Public interface of a database. Only contains funtions.
+ * Public interface of a database. Only contains functions.
  * All the functions take the interface as the first argument.
  * @public
  * @see #db_alloc(const char*,int,DBType,DBOptions,unsigned short)
@@ -367,7 +374,7 @@ struct DBMap {
 	/**
 	 * Returns a new iterator for this database.
 	 * The iterator keeps the database locked until it is destroyed.
-	 * The database will keep functioning normally but will only free internal 
+	 * The database will keep functioning normally but will only free internal
 	 * memory when unlocked, so destroy the iterator as soon as possible.
 	 * @param self Database
 	 * @return New iterator
@@ -399,7 +406,7 @@ struct DBMap {
 	 * It puts a maximum of <code>max</code> entries into <code>buf</code>.
 	 * If <code>buf</code> is NULL, it only counts the matches.
 	 * Returns the number of entries that matched.
-	 * NOTE: if the value returned is greater than <code>max</code>, only the 
+	 * NOTE: if the value returned is greater than <code>max</code>, only the
 	 * first <code>max</code> entries found are put into the buffer.
 	 * @param self Database
 	 * @param buf Buffer to put the data of the matched entries
@@ -417,7 +424,7 @@ struct DBMap {
 	 * It puts a maximum of <code>max</code> entries into <code>buf</code>.
 	 * If <code>buf</code> is NULL, it only counts the matches.
 	 * Returns the number of entries that matched.
-	 * NOTE: if the value returned is greater than <code>max</code>, only the 
+	 * NOTE: if the value returned is greater than <code>max</code>, only the
 	 * first <code>max</code> entries found are put into the buffer.
 	 * @param self Database
 	 * @param buf Buffer to put the data of the matched entries
@@ -433,7 +440,7 @@ struct DBMap {
 	/**
 	 * Just calls {@link DBMap#vensure}.
 	 * Get the data of the entry identified by the key.
-	 * If the entry does not exist, an entry is added with the data returned by 
+	 * If the entry does not exist, an entry is added with the data returned by
 	 * <code>create</code>.
 	 * @param self Database
 	 * @param key Key that identifies the entry
@@ -447,7 +454,7 @@ struct DBMap {
 
 	/**
 	 * Get the data of the entry identified by the key.
-	 * If the entry does not exist, an entry is added with the data returned by 
+	 * If the entry does not exist, an entry is added with the data returned by
 	 * <code>create</code>.
 	 * @param self Database
 	 * @param key Key that identifies the entry
@@ -544,7 +551,7 @@ struct DBMap {
 	 * Before deleting an entry, func is applied to it.
 	 * Releases the key and the data.
 	 * Returns the sum of values returned by func, if it exists.
-	 * NOTE: This locks the database globally. Any attempt to insert or remove 
+	 * NOTE: This locks the database globally. Any attempt to insert or remove
 	 * a database entry will give an error and be aborted (except for clearing).
 	 * @param self Database
 	 * @param func Function to be applied to every entry before deleting
@@ -559,7 +566,7 @@ struct DBMap {
 	 * Finalize the database, feeing all the memory it uses.
 	 * Before deleting an entry, func is applied to it.
 	 * Returns the sum of values returned by func, if it exists.
-	 * NOTE: This locks the database globally. Any attempt to insert or remove 
+	 * NOTE: This locks the database globally. Any attempt to insert or remove
 	 * a database entry will give an error and be aborted (except for clearing).
 	 * @param self Database
 	 * @param func Function to be applied to every entry before deleting
@@ -598,65 +605,86 @@ struct DBMap {
 
 // For easy access to the common functions.
 
-#define db_exists(db,k)    ( (db)->exists((db),(k)) )
-#define idb_exists(db,k)   ( (db)->exists((db),db_i2key(k)) )
-#define uidb_exists(db,k)  ( (db)->exists((db),db_ui2key(k)) )
-#define strdb_exists(db,k) ( (db)->exists((db),db_str2key(k)) )
+#define db_exists(db,k)     ( (db)->exists((db),(k)) )
+#define idb_exists(db,k)    ( (db)->exists((db),db_i2key(k)) )
+#define uidb_exists(db,k)   ( (db)->exists((db),db_ui2key(k)) )
+#define strdb_exists(db,k)  ( (db)->exists((db),db_str2key(k)) )
+#define i64db_exists(db,k)  ( (db)->exists((db),db_i642key(k)) )
+#define ui64db_exists(db,k) ( (db)->exists((db),db_ui642key(k)) )
 
 // Get pointer-type data from DBMaps of various key types
-#define db_get(db,k)    ( db_data2ptr((db)->get((db),(k))) )
-#define idb_get(db,k)   ( db_data2ptr((db)->get((db),db_i2key(k))) )
-#define uidb_get(db,k)  ( db_data2ptr((db)->get((db),db_ui2key(k))) )
-#define strdb_get(db,k) ( db_data2ptr((db)->get((db),db_str2key(k))) )
+#define db_get(db,k)     ( db_data2ptr((db)->get((db),(k))) )
+#define idb_get(db,k)    ( db_data2ptr((db)->get((db),db_i2key(k))) )
+#define uidb_get(db,k)   ( db_data2ptr((db)->get((db),db_ui2key(k))) )
+#define strdb_get(db,k)  ( db_data2ptr((db)->get((db),db_str2key(k))) )
+#define i64db_get(db,k)  ( db_data2ptr((db)->get((db),db_i642key(k))) )
+#define ui64db_get(db,k) ( db_data2ptr((db)->get((db),db_ui642key(k))) )
+
 
 // Get int-type data from DBMaps of various key types
-#define db_iget(db,k)    ( db_data2i((db)->get((db),(k))) )
-#define idb_iget(db,k)   ( db_data2i((db)->get((db),db_i2key(k))) )
-#define uidb_iget(db,k)  ( db_data2i((db)->get((db),db_ui2key(k))) )
-#define strdb_iget(db,k) ( db_data2i((db)->get((db),db_str2key(k))) )
+#define db_iget(db,k)     ( db_data2i((db)->get((db),(k))) )
+#define idb_iget(db,k)    ( db_data2i((db)->get((db),db_i2key(k))) )
+#define uidb_iget(db,k)   ( db_data2i((db)->get((db),db_ui2key(k))) )
+#define strdb_iget(db,k)  ( db_data2i((db)->get((db),db_str2key(k))) )
+#define i64db_iget(db,k)  ( db_data2i((db)->get((db),db_i642key(k))) )
+#define ui64db_iget(db,k) ( db_data2i((db)->get((db),db_ui642key(k))) )
 
 // Get uint-type data from DBMaps of various key types
-#define db_uiget(db,k)    ( db_data2ui((db)->get((db),(k))) )
-#define idb_uiget(db,k)   ( db_data2ui((db)->get((db),db_i2key(k))) )
-#define uidb_uiget(db,k)  ( db_data2ui((db)->get((db),db_ui2key(k))) )
-#define strdb_uiget(db,k) ( db_data2ui((db)->get((db),db_str2key(k))) )
+#define db_uiget(db,k)     ( db_data2ui((db)->get((db),(k))) )
+#define idb_uiget(db,k)    ( db_data2ui((db)->get((db),db_i2key(k))) )
+#define uidb_uiget(db,k)   ( db_data2ui((db)->get((db),db_ui2key(k))) )
+#define strdb_uiget(db,k)  ( db_data2ui((db)->get((db),db_str2key(k))) )
+#define i64db_uiget(db,k)  ( db_data2ui((db)->get((db),db_i642key(k))) )
+#define ui64db_uiget(db,k) ( db_data2ui((db)->get((db),db_ui642key(k))) )
 
 // Put pointer-type data into DBMaps of various key types
-#define db_put(db,k,d)    ( (db)->put((db),(k),db_ptr2data(d),NULL) )
-#define idb_put(db,k,d)   ( (db)->put((db),db_i2key(k),db_ptr2data(d),NULL) )
-#define uidb_put(db,k,d)  ( (db)->put((db),db_ui2key(k),db_ptr2data(d),NULL) )
-#define strdb_put(db,k,d) ( (db)->put((db),db_str2key(k),db_ptr2data(d),NULL) )
+#define db_put(db,k,d)     ( (db)->put((db),(k),db_ptr2data(d),NULL) )
+#define idb_put(db,k,d)    ( (db)->put((db),db_i2key(k),db_ptr2data(d),NULL) )
+#define uidb_put(db,k,d)   ( (db)->put((db),db_ui2key(k),db_ptr2data(d),NULL) )
+#define strdb_put(db,k,d)  ( (db)->put((db),db_str2key(k),db_ptr2data(d),NULL) )
+#define i64db_put(db,k,d)  ( (db)->put((db),db_i642key(k),db_ptr2data(d),NULL) )
+#define ui64db_put(db,k,d) ( (db)->put((db),db_ui642key(k),db_ptr2data(d),NULL) )
 
 // Put int-type data into DBMaps of various key types
-#define db_iput(db,k,d)    ( (db)->put((db),(k),db_i2data(d),NULL) )
-#define idb_iput(db,k,d)   ( (db)->put((db),db_i2key(k),db_i2data(d),NULL) )
-#define uidb_iput(db,k,d)  ( (db)->put((db),db_ui2key(k),db_i2data(d),NULL) )
-#define strdb_iput(db,k,d) ( (db)->put((db),db_str2key(k),db_i2data(d),NULL) )
+#define db_iput(db,k,d)     ( (db)->put((db),(k),db_i2data(d),NULL) )
+#define idb_iput(db,k,d)    ( (db)->put((db),db_i2key(k),db_i2data(d),NULL) )
+#define uidb_iput(db,k,d)   ( (db)->put((db),db_ui2key(k),db_i2data(d),NULL) )
+#define strdb_iput(db,k,d)  ( (db)->put((db),db_str2key(k),db_i2data(d),NULL) )
+#define i64db_iput(db,k,d)  ( (db)->put((db),db_i642key(k),db_i2data(d),NULL) )
+#define ui64db_iput(db,k,d) ( (db)->put((db),db_ui642key(k),db_i2data(d),NULL) )
 
 // Put uint-type data into DBMaps of various key types
-#define db_uiput(db,k,d)    ( (db)->put((db),(k),db_ui2data(d),NULL) )
-#define idb_uiput(db,k,d)   ( (db)->put((db),db_i2key(k),db_ui2data(d),NULL) )
-#define uidb_uiput(db,k,d)  ( (db)->put((db),db_ui2key(k),db_ui2data(d),NULL) )
-#define strdb_uiput(db,k,d) ( (db)->put((db),db_str2key(k),db_ui2data(d),NULL) )
+#define db_uiput(db,k,d)     ( (db)->put((db),(k),db_ui2data(d),NULL) )
+#define idb_uiput(db,k,d)    ( (db)->put((db),db_i2key(k),db_ui2data(d),NULL) )
+#define uidb_uiput(db,k,d)   ( (db)->put((db),db_ui2key(k),db_ui2data(d),NULL) )
+#define strdb_uiput(db,k,d)  ( (db)->put((db),db_str2key(k),db_ui2data(d),NULL) )
+#define i64db_uiput(db,k,d)  ( (db)->put((db),db_i642key(k),db_ui2data(d),NULL) )
+#define ui64db_uiput(db,k,d) ( (db)->put((db),db_ui642key(k),db_ui2data(d),NULL) )
 
 // Remove entry from DBMaps of various key types
-#define db_remove(db,k)    ( (db)->remove((db),(k),NULL) )
-#define idb_remove(db,k)   ( (db)->remove((db),db_i2key(k),NULL) )
-#define uidb_remove(db,k)  ( (db)->remove((db),db_ui2key(k),NULL) )
-#define strdb_remove(db,k) ( (db)->remove((db),db_str2key(k),NULL) )
+#define db_remove(db,k)     ( (db)->remove((db),(k),NULL) )
+#define idb_remove(db,k)    ( (db)->remove((db),db_i2key(k),NULL) )
+#define uidb_remove(db,k)   ( (db)->remove((db),db_ui2key(k),NULL) )
+#define strdb_remove(db,k)  ( (db)->remove((db),db_str2key(k),NULL) )
+#define i64db_remove(db,k)  ( (db)->remove((db),db_i642key(k),NULL) )
+#define ui64db_remove(db,k) ( (db)->remove((db),db_ui642key(k),NULL) )
 
 //These are discarding the possible vargs you could send to the function, so those
 //that require vargs must not use these defines.
-#define db_ensure(db,k,f)    ( db_data2ptr((db)->ensure((db),(k),(f))) )
-#define idb_ensure(db,k,f)   ( db_data2ptr((db)->ensure((db),db_i2key(k),(f))) )
-#define uidb_ensure(db,k,f)  ( db_data2ptr((db)->ensure((db),db_ui2key(k),(f))) )
-#define strdb_ensure(db,k,f) ( db_data2ptr((db)->ensure((db),db_str2key(k),(f))) )
+#define db_ensure(db,k,f)     ( db_data2ptr((db)->ensure((db),(k),(f))) )
+#define idb_ensure(db,k,f)    ( db_data2ptr((db)->ensure((db),db_i2key(k),(f))) )
+#define uidb_ensure(db,k,f)   ( db_data2ptr((db)->ensure((db),db_ui2key(k),(f))) )
+#define strdb_ensure(db,k,f)  ( db_data2ptr((db)->ensure((db),db_str2key(k),(f))) )
+#define i64db_ensure(db,k,f)  ( db_data2ptr((db)->ensure((db),db_i642key(k),(f))) )
+#define ui64db_ensure(db,k,f) ( db_data2ptr((db)->ensure((db),db_ui642key(k),(f))) )
 
 // Database creation and destruction macros
 #define idb_alloc(opt)            db_alloc(__FILE__,__LINE__,DB_INT,(opt),sizeof(int))
 #define uidb_alloc(opt)           db_alloc(__FILE__,__LINE__,DB_UINT,(opt),sizeof(unsigned int))
 #define strdb_alloc(opt,maxlen)   db_alloc(__FILE__,__LINE__,DB_STRING,(opt),(maxlen))
 #define stridb_alloc(opt,maxlen)  db_alloc(__FILE__,__LINE__,DB_ISTRING,(opt),(maxlen))
+#define i64db_alloc(opt)          db_alloc(__FILE__,__LINE__,DB_INT64,(opt),sizeof(int64))
+#define ui64db_alloc(opt)         db_alloc(__FILE__,__LINE__,DB_UINT64,(opt),sizeof(uint64))
 #define db_destroy(db)            ( (db)->destroy((db),NULL) )
 // Other macros
 #define db_clear(db)        ( (db)->clear(db,NULL) )
@@ -682,6 +710,8 @@ struct DBMap {
  *  db_i2key           - Manual cast from 'int' to 'DBKey'.                  *
  *  db_ui2key          - Manual cast from 'unsigned int' to 'DBKey'.         *
  *  db_str2key         - Manual cast from 'unsigned char *' to 'DBKey'.      *
+ *  db_i642key         - Manual cast from 'int64' to 'DBKey'.                *
+ *  db_ui642key        - Manual cast from 'uint64' to 'DBKey'.               *
  *  db_i2data          - Manual cast from 'int' to 'DBData'.                 *
  *  db_ui2data         - Manual cast from 'unsigned int' to 'DBData'.        *
  *  db_ptr2data        - Manual cast from 'void*' to 'DBData'.               *
@@ -801,6 +831,22 @@ DBKey db_ui2key(unsigned int key);
  */
 DBKey db_str2key(const char *key);
 
+/**
+ * Manual cast from 'int64' to the union DBKey.
+ * @param key Key to be casted
+ * @return The key as a DBKey union
+ * @public
+ */
+DBKey db_i642key(int64 key);
+
+/**
+ * Manual cast from 'uint64' to the union DBKey.
+ * @param key Key to be casted
+ * @return The key as a DBKey union
+ * @public
+ */
+DBKey db_ui642key(uint64 key);
+
 /**
  * Manual cast from 'int' to the struct DBData.
  * @param data Data to be casted
@@ -1446,7 +1492,7 @@ int   linkdb_foreach( struct linkdb_node** head, LinkDBFunc func, ...  );
 		VECTOR_INDEX(__heap,0) = VECTOR_POP(__heap); /* put last at index */ \
 		if( !VECTOR_LENGTH(__heap) ) /* removed last, nothing to do */ \
 			break; \
-		BHEAP_SIFTUP(__heap,0,__topcmp,__swp);	\
+		BHEAP_SIFTUP(__heap,0,__topcmp,__swp); \
 	}while(0)
 
 
@@ -1508,7 +1554,7 @@ int   linkdb_foreach( struct linkdb_node** head, LinkDBFunc func, ...  );
 /// @param __idx Index of an inserted element
 /// @param __topcmp Comparator
 /// @param __swp Swapper
-#define BHEAP_SIFTDOWN(__heap,__startidx,__idx,__topcmp,__swp)	\
+#define BHEAP_SIFTDOWN(__heap,__startidx,__idx,__topcmp,__swp) \
 	do{ \
 		size_t _i2_ = __idx; \
 		while( _i2_ > __startidx ) \

+ 5 - 5
src/common/ers.c

@@ -13,16 +13,16 @@
  *  If it has reusable entries (freed entry), it uses one.                   *
  *  So no assumption should be made about the data of the entry.             *
  *  Entries should be freed in the manager they where allocated from.        *
- *  Failure to do so can lead to unexpected behaviours.                      *
+ *  Failure to do so can lead to unexpected behaviors.                      *
  *                                                                           *
  *  <H2>Advantages:</H2>                                                     *
  *  - The same manager is used for entries of the same size.                 *
  *    So entries freed in one instance of the manager can be used by other   *
  *    instances of the manager.                                              *
  *  - Much less memory allocation/deallocation - program will be faster.     *
- *  - Avoids memory fragmentaion - program will run better for longer.       *
+ *  - Avoids memory fragmentation - program will run better for longer.       *
  *                                                                           *
- *  <H2>Disavantages:</H2>                                                   *
+ *  <H2>Disadvantages:</H2>                                                   *
  *  - Unused entries are almost inevitable - memory being wasted.            *
  *  - A  manager will only auto-destroy when all of its instances are        *
  *    destroyed so memory will usually only be recovered near the end.       *
@@ -70,12 +70,12 @@ typedef struct ers_cache
 	// Memory blocks array
 	unsigned char **Blocks;
 
-	// Max number of blocks 
+	// Max number of blocks
 	unsigned int Max;
 
 	// Free objects count
 	unsigned int Free;
-	
+
 	// Used objects count
 	unsigned int Used;
 

+ 12 - 12
src/common/ers.h

@@ -13,16 +13,16 @@
  *  If it has reusable entries (freed entry), it uses one.                   *
  *  So no assumption should be made about the data of the entry.             *
  *  Entries should be freed in the manager they where allocated from.        *
- *  Failure to do so can lead to unexpected behaviours.                      *
+ *  Failure to do so can lead to unexpected behaviors.                      *
  *                                                                           *
  *  <H2>Advantages:</H2>                                                     *
  *  - The same manager is used for entries of the same size.                 *
  *    So entries freed in one instance of the manager can be used by other   *
  *    instances of the manager.                                              *
  *  - Much less memory allocation/deallocation - program will be faster.     *
- *  - Avoids memory fragmentaion - program will run better for longer.       *
+ *  - Avoids memory fragmentation - program will run better for longer.       *
  *                                                                           *
- *  <H2>Disavantages:</H2>                                                   *
+ *  <H2>Disadvantages:</H2>                                                   *
  *  - Unused entries are almost inevitable - memory being wasted.            *
  *  - A  manager will only auto-destroy when all of its instances are        *
  *    destroyed so memory will usually only be recovered near the end.       *
@@ -55,7 +55,7 @@
 /**
  * Define this to disable the Entry Reusage System.
  * All code except the typedef of ERInterface will be disabled.
- * To allow a smooth transition, 
+ * To allow a smooth transition,
  */
 //#define DISABLE_ERS
 
@@ -63,8 +63,8 @@
  * Entries are aligned to ERS_ALIGNED bytes in the blocks of entries.
  * By default it aligns to one byte, using the "natural order" of the entries.
  * This should NEVER be set to zero or less.
- * If greater than one, some memory can be wasted. This should never be needed 
- * but is here just in case some aligment issues arise.
+ * If greater than one, some memory can be wasted. This should never be needed
+ * but is here just in case some alignment issues arise.
  */
 #ifndef ERS_ALIGNED
 #	define ERS_ALIGNED 1
@@ -95,7 +95,7 @@ typedef struct eri {
 	/**
 	 * Free an entry allocated from this manager.
 	 * WARNING: Does not check if the entry was allocated by this manager.
-	 * Freeing such an entry can lead to unexpected behaviour.
+	 * Freeing such an entry can lead to unexpected behavior.
 	 * @param self Interface of the entry manager
 	 * @param entry Entry to be freed
 	 */
@@ -111,7 +111,7 @@ typedef struct eri {
 	/**
 	 * Destroy this instance of the manager.
 	 * The manager is actually only destroyed when all the instances are destroyed.
-	 * When destroying the manager a warning is shown if the manager has 
+	 * When destroying the manager a warning is shown if the manager has
 	 * missing/extra entries.
 	 * @param self Interface of the entry manager
 	 */
@@ -130,7 +130,7 @@ typedef struct eri {
 #	define ers_report()
 #	define ers_force_destroy_all()
 #else /* not DISABLE_ERS */
-// These defines should be used to allow the code to keep working whenever 
+// These defines should be used to allow the code to keep working whenever
 // the system is disabled
 #	define ers_alloc(obj,type) (type *)(obj)->alloc(obj)
 #	define ers_free(obj,entry) (obj)->free((obj),(entry))
@@ -140,9 +140,9 @@ typedef struct eri {
 /**
  * Get a new instance of the manager that handles the specified entry size.
  * Size has to greater than 0.
- * If the specified size is smaller than a pointer, the size of a pointer is 
+ * If the specified size is smaller than a pointer, the size of a pointer is
  * used instead.
- * It's also aligned to ERS_ALIGNED bytes, so the smallest multiple of 
+ * It's also aligned to ERS_ALIGNED bytes, so the smallest multiple of
  * ERS_ALIGNED that is greater or equal to size is what's actually used.
  * @param The requested size of the entry in bytes
  * @return Interface of the object
@@ -152,7 +152,7 @@ ERS ers_new(uint32 size, char *name, enum ERSOptions options);
 /**
  * Print a report about the current state of the Entry Reusage System.
  * Shows information about the global system and each entry manager.
- * The number of entries are checked and a warning is shown if extra reusable 
+ * The number of entries are checked and a warning is shown if extra reusable
  * entries are found.
  * The extra entries are included in the count of reusable entries.
  */

+ 19 - 21
src/common/mmo.h

@@ -55,10 +55,6 @@
 #define MAX_FAME 1000000000 ///Max fame points
 #define MAX_CART 100 ///Maximum item in cart
 #define MAX_SKILL 1200 ///Maximum skill can be hold by Player, Homunculus, & Mercenary (skill list) AND skill_db limit
-#define GLOBAL_REG_NUM 256 ///Max permanent character variables per char
-#define ACCOUNT_REG_NUM 64 ///Max permanent local account variables per account
-#define ACCOUNT_REG2_NUM 16 ///Max permanent global account variables per account
-#define MAX_REG_NUM 256 ///Should hold the max of GLOBAL/ACCOUNT/ACCOUNT2 (needed for some arrays that hold all three)
 #define DEFAULT_WALK_SPEED 150 ///Default walk speed
 #define MIN_WALK_SPEED 20 ///Min walk speed
 #define MAX_WALK_SPEED 1000 ///Max walk speed
@@ -242,16 +238,19 @@ struct s_skill {
 	uint8 flag; // see enum e_skill_flag
 };
 
-struct global_reg {
-	char str[32];
-	char value[256];
+struct script_reg_state {
+	unsigned int type : 1; // because I'm a memory hoarder and having them in the same struct would be a 8-byte/instance waste while ints outnumber str on a 10000-to-1 ratio.
+	unsigned int update : 1; // whether it needs to be sent to char server for insertion/update/delete
 };
 
-//Holds array of global registries, used by the char server and converter.
-struct accreg {
-	uint32 account_id, char_id;
-	int reg_num;
-	struct global_reg reg[MAX_REG_NUM];
+struct script_reg_num {
+	struct script_reg_state flag;
+	int value;
+};
+
+struct script_reg_str {
+	struct script_reg_state flag;
+	char *value;
 };
 
 //For saving status changes across sessions. [Skotlex]
@@ -480,15 +479,6 @@ struct auction_data {
 	int auction_end_timer;
 };
 
-struct registry {
-	int global_num;
-	struct global_reg global[GLOBAL_REG_NUM];
-	int account_num;
-	struct global_reg account[ACCOUNT_REG_NUM];
-	int account2_num;
-	struct global_reg account2[ACCOUNT_REG2_NUM];
-};
-
 struct party_member {
 	uint32 account_id;
 	uint32 char_id;
@@ -812,6 +802,14 @@ enum bound_type {
 	BOUND_DISPYELLOW = 2, /// Shows the item name in yellow color
 };
 
+enum e_pc_reg_loading {
+	PRL_NONE = 0x0,
+	PRL_CHAR = 0x1,
+	PRL_ACCL = 0x2, // local
+	PRL_ACCG = 0x4, // global
+	PRL_ALL = 0xFF,
+};
+
 // sanity checks...
 #if MAX_ZENY > INT_MAX
 #error MAX_ZENY is too big

+ 204 - 58
src/login/account.c

@@ -10,6 +10,7 @@
 #include "../common/malloc.h"
 #include "../common/mmo.h"
 #include "../common/showmsg.h"
+#include "../common/socket.h"
 #include "../common/sql.h"
 #include "../common/strlib.h"
 #include "account.h"
@@ -32,7 +33,8 @@ typedef struct AccountDB_SQL {
 	bool case_sensitive;
 	//table name
 	char account_db[32];
-	char accreg_db[32];
+	char global_acc_reg_num_table[32];
+	char global_acc_reg_str_table[32];
 
 } AccountDB_SQL;
 
@@ -88,7 +90,8 @@ AccountDB* account_db_sql(void) {
 	// other settings
 	db->case_sensitive = false;
 	safestrncpy(db->account_db, "login", sizeof(db->account_db));
-	safestrncpy(db->accreg_db, "global_reg_value", sizeof(db->accreg_db));
+	safestrncpy(db->global_acc_reg_num_table, "global_acc_reg_num", sizeof(db->global_acc_reg_num_table));
+	safestrncpy(db->global_acc_reg_str_table, "global_acc_reg_str", sizeof(db->global_acc_reg_str_table));
 
 	return &db->vtable;
 }
@@ -187,8 +190,11 @@ static bool account_db_sql_get_property(AccountDB* self, const char* key, char*
 		if( strcmpi(key, "account_db") == 0 )
 			safesnprintf(buf, buflen, "%s", db->account_db);
 		else
-		if( strcmpi(key, "accreg_db") == 0 )
-			safesnprintf(buf, buflen, "%s", db->accreg_db);
+		if( strcmpi(key, "global_acc_reg_str_table") == 0 )
+			safesnprintf(buf, buflen, "%s", db->global_acc_reg_str_table);
+		else
+		if( strcmpi(key, "global_acc_reg_num_table") == 0 )
+			safesnprintf(buf, buflen, "%s", db->global_acc_reg_num_table);
 		else
 			return false;// not found
 		return true;
@@ -243,8 +249,11 @@ static bool account_db_sql_set_property(AccountDB* self, const char* key, const
 		if( strcmpi(key, "account_db") == 0 )
 			safestrncpy(db->account_db, value, sizeof(db->account_db));
 		else
-		if( strcmpi(key, "accreg_db") == 0 )
-			safestrncpy(db->accreg_db, value, sizeof(db->accreg_db));
+		if( strcmpi(key, "global_acc_reg_str_table") == 0 )
+			safestrncpy(db->global_acc_reg_str_table, value, sizeof(db->global_acc_reg_str_table));
+		else
+		if( strcmpi(key, "global_acc_reg_num_table") == 0 )
+			safestrncpy(db->global_acc_reg_num_table, value, sizeof(db->global_acc_reg_num_table));
 		else
 			return false;// not found
 		return true;
@@ -336,7 +345,8 @@ static bool account_db_sql_remove(AccountDB* self, const uint32 account_id) {
 
 	if( SQL_SUCCESS != Sql_QueryStr(sql_handle, "START TRANSACTION")
 	||  SQL_SUCCESS != Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `account_id` = %d", db->account_db, account_id)
-	||  SQL_SUCCESS != Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `account_id` = %d", db->accreg_db, account_id) )
+	||  SQL_SUCCESS != Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `account_id` = %d", db->global_acc_reg_num_table, account_id)
+	||  SQL_SUCCESS != Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `account_id` = %d", db->global_acc_reg_str_table, account_id) )
 		Sql_ShowDebug(sql_handle);
 	else
 		result = true;
@@ -491,7 +501,6 @@ static bool account_db_sql_iter_next(AccountDBIterator* self, struct mmo_account
 static bool mmo_auth_fromsql(AccountDB_SQL* db, struct mmo_account* acc, uint32 account_id) {
 	Sql* sql_handle = db->accounts;
 	char* data;
-	int i = 0;
 
 	// retrieve login entry for the specified account
 	if( SQL_ERROR == Sql_Query(sql_handle,
@@ -533,27 +542,6 @@ static bool mmo_auth_fromsql(AccountDB_SQL* db, struct mmo_account* acc, uint32
 	Sql_GetData(sql_handle, 17, &data, NULL); acc->old_group = atoi(data);
 #endif
 	Sql_FreeResult(sql_handle);
-	
-	// retrieve account regs for the specified user
-	if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `str`,`value` FROM `%s` WHERE `type`='1' AND `account_id`='%d'", db->accreg_db, acc->account_id) )
-	{
-		Sql_ShowDebug(sql_handle);
-		return false;
-	}
-
-	acc->account_reg2_num = (int)Sql_NumRows(sql_handle);
-
-	while( SQL_SUCCESS == Sql_NextRow(sql_handle) )
-	{
-		char* data_tmp;
-		Sql_GetData(sql_handle, 0, &data_tmp, NULL); safestrncpy(acc->account_reg2[i].str, data_tmp, sizeof(acc->account_reg2[i].str));
-		Sql_GetData(sql_handle, 1, &data_tmp, NULL); safestrncpy(acc->account_reg2[i].value, data_tmp, sizeof(acc->account_reg2[i].value));
-		++i;
-	}
-	Sql_FreeResult(sql_handle);
-
-	if( i != acc->account_reg2_num )
-		return false;
 
 	return true;
 }
@@ -569,7 +557,6 @@ static bool mmo_auth_tosql(AccountDB_SQL* db, const struct mmo_account* acc, boo
 	Sql* sql_handle = db->accounts;
 	SqlStmt* stmt = SqlStmt_Malloc(sql_handle);
 	bool result = false;
-	int i;
 
 	// try
 	do
@@ -651,34 +638,6 @@ static bool mmo_auth_tosql(AccountDB_SQL* db, const struct mmo_account* acc, boo
 		}
 	}
 
-	// remove old account regs
-	if( SQL_SUCCESS != Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `type`='1' AND `account_id`='%d'", db->accreg_db, acc->account_id) )
-	{
-		Sql_ShowDebug(sql_handle);
-		break;
-	}
-	// insert new account regs
-	if( SQL_SUCCESS != SqlStmt_Prepare(stmt, "INSERT INTO `%s` (`type`, `account_id`, `str`, `value`) VALUES ( 1 , '%d' , ? , ? );",  db->accreg_db, acc->account_id) )
-	{
-		SqlStmt_ShowDebug(stmt);
-		break;
-	}
-	for( i = 0; i < acc->account_reg2_num; ++i )
-	{
-		if( SQL_SUCCESS != SqlStmt_BindParam(stmt, 0, SQLDT_STRING, (void*)acc->account_reg2[i].str, strlen(acc->account_reg2[i].str))
-		||  SQL_SUCCESS != SqlStmt_BindParam(stmt, 1, SQLDT_STRING, (void*)acc->account_reg2[i].value, strlen(acc->account_reg2[i].value))
-		||  SQL_SUCCESS != SqlStmt_Execute(stmt)
-		) {
-			SqlStmt_ShowDebug(stmt);
-			break;
-		}
-	}
-	if( i < acc->account_reg2_num )
-	{
-		result = false;
-		break;
-	}
-
 	// if we got this far, everything was successful
 	result = true;
 
@@ -690,3 +649,190 @@ static bool mmo_auth_tosql(AccountDB_SQL* db, const struct mmo_account* acc, boo
 
 	return result;
 }
+
+void mmo_save_accreg2(AccountDB* self, int fd, int account_id, int char_id) {
+	Sql* sql_handle = ((AccountDB_SQL*)self)->accounts;
+	AccountDB_SQL* db = (AccountDB_SQL*)self;
+	int count = RFIFOW(fd, 12);
+
+	if (count) {
+		int cursor = 14, i;
+		char key[32], sval[254];
+
+		for (i = 0; i < count; i++) {
+			unsigned int index;
+			safestrncpy(key, (char*)RFIFOP(fd, cursor + 1), RFIFOB(fd, cursor));
+			cursor += RFIFOB(fd, cursor) + 1;
+
+			index = RFIFOL(fd, cursor);
+			cursor += 4;
+
+			switch (RFIFOB(fd, cursor++)) {
+				// int
+				case 0:
+					if( SQL_ERROR == Sql_Query(sql_handle, "REPLACE INTO `%s` (`account_id`,`key`,`index`,`value`) VALUES ('%d','%s','%u','%d')", db->global_acc_reg_num_table, account_id, key, index, RFIFOL(fd, cursor)) )
+						Sql_ShowDebug(sql_handle);
+					cursor += 4;
+					break;
+				case 1:
+					if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `account_id` = '%d' AND `key` = '%s' AND `index` = '%u' LIMIT 1", db->global_acc_reg_num_table, account_id, key, index) )
+						Sql_ShowDebug(sql_handle);
+					break;
+				// str
+				case 2:
+					safestrncpy(sval, (char*)RFIFOP(fd, cursor + 1), RFIFOB(fd, cursor));
+					cursor += RFIFOB(fd, cursor) + 1;
+					if( SQL_ERROR == Sql_Query(sql_handle, "REPLACE INTO `%s` (`account_id`,`key`,`index`,`value`) VALUES ('%d','%s','%u','%s')", db->global_acc_reg_str_table, account_id, key, index, sval) )
+						Sql_ShowDebug(sql_handle);
+					break;
+				case 3:
+					if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `account_id` = '%d' AND `key` = '%s' AND `index` = '%u' LIMIT 1", db->global_acc_reg_str_table, account_id, key, index) )
+						Sql_ShowDebug(sql_handle);
+					break;
+				default:
+					ShowError("mmo_save_accreg2: unknown type %d\n",RFIFOB(fd, cursor - 1));
+					return;
+			}
+		}
+	}
+}
+
+void mmo_send_accreg2(AccountDB* self, int fd, int account_id, int char_id) {
+	Sql* sql_handle = ((AccountDB_SQL*)self)->accounts;
+	AccountDB_SQL* db = (AccountDB_SQL*)self;
+	char* data;
+	int plen = 0;
+	size_t len;
+
+	if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `key`, `index`, `value` FROM `%s` WHERE `account_id`='%d'", db->global_acc_reg_str_table, account_id) )
+		Sql_ShowDebug(sql_handle);
+
+	WFIFOHEAD(fd, 60000 + 300);
+	WFIFOW(fd, 0) = 0x3804;
+	// 0x2 = length, set prior to being sent
+	WFIFOL(fd, 4) = account_id;
+	WFIFOL(fd, 8) = char_id;
+	WFIFOB(fd, 12) = 0; // var type (only set when all vars have been sent, regardless of type)
+	WFIFOB(fd, 13) = 1; // is string type
+	WFIFOW(fd, 14) = 0; // count
+	plen = 16;
+
+	/**
+	 * Vessel!
+	 *
+	 * str type
+	 * { keyLength(B), key(<keyLength>), index(L), valLength(B), val(<valLength>) }
+	 **/
+	while ( SQL_SUCCESS == Sql_NextRow(sql_handle) ) {
+		Sql_GetData(sql_handle, 0, &data, NULL);
+		len = strlen(data)+1;
+
+		WFIFOB(fd, plen) = (unsigned char)len; // won't be higher; the column size is 32
+		plen += 1;
+
+		safestrncpy((char*)WFIFOP(fd,plen), data, len);
+		plen += len;
+
+		Sql_GetData(sql_handle, 1, &data, NULL);
+
+		WFIFOL(fd, plen) = (unsigned int)atol(data);
+		plen += 4;
+
+		Sql_GetData(sql_handle, 2, &data, NULL);
+		len = strlen(data)+1;
+
+		WFIFOB(fd, plen) = (unsigned char)len; // won't be higher; the column size is 254
+		plen += 1;
+
+		safestrncpy((char*)WFIFOP(fd,plen), data, len);
+		plen += len;
+
+		WFIFOW(fd, 14) += 1;
+
+		if( plen > 60000 ) {
+			WFIFOW(fd, 2) = plen;
+			WFIFOSET(fd, plen);
+
+			// prepare follow up
+			WFIFOHEAD(fd, 60000 + 300);
+			WFIFOW(fd, 0) = 0x3804;
+			// 0x2 = length, set prior to being sent
+			WFIFOL(fd, 4) = account_id;
+			WFIFOL(fd, 8) = char_id;
+			WFIFOB(fd, 12) = 0; // var type (only set when all vars have been sent, regardless of type)
+			WFIFOB(fd, 13) = 1; // is string type
+			WFIFOW(fd, 14) = 0; // count
+			plen = 16;
+		}
+	}
+
+	WFIFOW(fd, 2) = plen;
+	WFIFOSET(fd, plen);
+
+	Sql_FreeResult(sql_handle);
+
+	if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `key`, `index`, `value` FROM `%s` WHERE `account_id`='%d'", db->global_acc_reg_num_table, account_id) )
+		Sql_ShowDebug(sql_handle);
+
+	WFIFOHEAD(fd, 60000 + 300);
+	WFIFOW(fd, 0) = 0x3804;
+	// 0x2 = length, set prior to being sent
+	WFIFOL(fd, 4) = account_id;
+	WFIFOL(fd, 8) = char_id;
+	WFIFOB(fd, 12) = 0; // var type (only set when all vars have been sent, regardless of type)
+	WFIFOB(fd, 13) = 0; // is int type
+	WFIFOW(fd, 14) = 0; // count
+	plen = 16;
+
+	/**
+	 * Vessel!
+	 *
+	 * int type
+	 * { keyLength(B), key(<keyLength>), index(L), value(L) }
+	 **/
+	while ( SQL_SUCCESS == Sql_NextRow(sql_handle) ) {
+		Sql_GetData(sql_handle, 0, &data, NULL);
+		len = strlen(data)+1;
+
+		WFIFOB(fd, plen) = (unsigned char)len; // won't be higher; the column size is 32
+		plen += 1;
+
+		safestrncpy((char*)WFIFOP(fd,plen), data, len);
+		plen += len;
+
+		Sql_GetData(sql_handle, 1, &data, NULL);
+
+		WFIFOL(fd, plen) = (unsigned int)atol(data);
+		plen += 4;
+
+		Sql_GetData(sql_handle, 2, &data, NULL);
+
+		WFIFOL(fd, plen) = atoi(data);
+		plen += 4;
+
+		WFIFOW(fd, 14) += 1;
+
+		if( plen > 60000 ) {
+			WFIFOW(fd, 2) = plen;
+			WFIFOSET(fd, plen);
+
+			// prepare follow up
+			WFIFOHEAD(fd, 60000 + 300);
+			WFIFOW(fd, 0) = 0x3804;
+			// 0x2 = length, set prior to being sent
+			WFIFOL(fd, 4) = account_id;
+			WFIFOL(fd, 8) = char_id;
+			WFIFOB(fd, 12) = 0; // var type (only set when all vars have been sent, regardless of type)
+			WFIFOB(fd, 13) = 0; // is int type
+			WFIFOW(fd, 14) = 0; // count
+
+			plen = 16;
+		}
+	}
+
+	WFIFOB(fd, 12) = 1;
+	WFIFOW(fd, 2) = plen;
+	WFIFOSET(fd, plen);
+
+	Sql_FreeResult(sql_handle);
+}

+ 3 - 2
src/login/account.h

@@ -38,12 +38,10 @@ struct mmo_account {
 	char birthdate[10+1];   // assigned birth date (format: YYYY-MM-DD, default: 0000-00-00)
 	char pincode[PINCODE_LENGTH+1];		// pincode system
 	time_t pincode_change;	// (timestamp): last time of pincode change
-	int account_reg2_num;
 #ifdef VIP_ENABLE
 	int old_group;
 	time_t vip_time;
 #endif
-	struct global_reg account_reg2[ACCOUNT_REG2_NUM]; // account script variables (stored on login server)
 };
 
 
@@ -140,5 +138,8 @@ struct AccountDB {
 	AccountDBIterator* (*iterator)(AccountDB* self);
 };
 
+void mmo_send_accreg2(AccountDB* self, int fd, int account_id, int char_id);
+void mmo_save_accreg2(AccountDB* self, int fd, int account_id, int char_id);
+
 
 #endif // __ACCOUNT_H_INCLUDED__

+ 3 - 44
src/login/loginchrif.c

@@ -486,28 +486,8 @@ int logchrif_parse_updreg2(int fd, int id, char* ip){
 
 		if( !accounts->load_num(accounts, &acc, account_id) )
 			ShowStatus("Char-server '%s': receiving (from the char-server) of account_reg2 (account: %d not found, ip: %s).\n", ch_server[id].name, account_id, ip);
-		else{
-			int len;
-			int p;
-			uint8 j;
-			ShowNotice("char-server '%s': receiving (from the char-server) of account_reg2 (account: %d, ip: %s).\n", ch_server[id].name, account_id, ip);
-			for( j = 0, p = 13; j < ACCOUNT_REG2_NUM && p < RFIFOW(fd,2); ++j ){
-				sscanf((char*)RFIFOP(fd,p), "%31c%n", acc.account_reg2[j].str, &len);
-				acc.account_reg2[j].str[len]='\0';
-				p +=len+1; //+1 to skip the '\0' between strings.
-				sscanf((char*)RFIFOP(fd,p), "%255c%n", acc.account_reg2[j].value, &len);
-				acc.account_reg2[j].value[len]='\0';
-				p +=len+1;
-				remove_control_chars(acc.account_reg2[j].str);
-				remove_control_chars(acc.account_reg2[j].value);
-			}
-			acc.account_reg2_num = j;
-			// Save
-			accounts->save(accounts, &acc);
-			// Sending information towards the other char-servers.
-			RFIFOW(fd,0) = 0x2729;// reusing read buffer
-			logchrif_sendallwos(fd, RFIFOP(fd,0), RFIFOW(fd,2));
-		}
+		else
+			mmo_save_accreg2(accounts,fd,account_id,RFIFOL(fd, 8));
 		RFIFOSKIP(fd,RFIFOW(fd,2));
 	}
 	return 1;
@@ -609,33 +589,12 @@ int logchrif_parse_reqacc2reg(int fd){
 	if (RFIFOREST(fd) < 10)
 		return 0;
 	else{
-		struct mmo_account acc;
 		AccountDB* accounts = login_get_accounts_db();
-		size_t off;
-
 		uint32 account_id = RFIFOL(fd,2);
 		uint32 char_id = RFIFOL(fd,6);
 		RFIFOSKIP(fd,10);
 
-		WFIFOHEAD(fd,ACCOUNT_REG2_NUM*sizeof(struct global_reg));
-		WFIFOW(fd,0) = 0x2729;
-		WFIFOL(fd,4) = account_id;
-		WFIFOL(fd,8) = char_id;
-		WFIFOB(fd,12) = 1; //Type 1 for Account2 registry
-
-		off = 13;
-		if( accounts->load_num(accounts, &acc, account_id) ){
-			uint8 j;
-			for( j = 0; j < acc.account_reg2_num; j++ ){
-				if( acc.account_reg2[j].str[0] != '\0' ){
-					off += sprintf((char*)WFIFOP(fd,off), "%s", acc.account_reg2[j].str)+1; //We add 1 to consider the '\0' in place.
-					off += sprintf((char*)WFIFOP(fd,off), "%s", acc.account_reg2[j].value)+1;
-				}
-			}
-		}
-
-		WFIFOW(fd,2) = (uint16)off;
-		WFIFOSET(fd,WFIFOW(fd,2));
+		mmo_send_accreg2(accounts,fd,account_id,char_id);
 	}
 	return 1;
 }

+ 8 - 8
src/map/atcommand.c

@@ -28,6 +28,7 @@
 #include "mercenary.h"
 #include "elemental.h"
 #include "party.h"
+#include "script.h"
 #include "storage.h"
 #include "trade.h"
 #include "mapreg.h"
@@ -8978,12 +8979,12 @@ ACMD_FUNC(set) {
 				break;
 			case '#':
 				if( reg[1] == '#' )
-					data->u.str = pc_readaccountreg2str(sd, reg);// global
+					data->u.str = pc_readaccountreg2str(sd, add_str(reg));// global
 				else
-					data->u.str = pc_readaccountregstr(sd, reg);// local
+					data->u.str = pc_readaccountregstr(sd, add_str(reg));// local
 				break;
 			default:
-				data->u.str = pc_readglobalreg_str(sd, reg);
+				data->u.str = pc_readglobalreg_str(sd, add_str(reg));
 				break;
 		}
 
@@ -9007,18 +9008,17 @@ ACMD_FUNC(set) {
 				break;
 			case '#':
 				if( reg[1] == '#' )
-					data->u.num = pc_readaccountreg2(sd, reg);// global
+					data->u.num = pc_readaccountreg2(sd, add_str(reg));// global
 				else
-					data->u.num = pc_readaccountreg(sd, reg);// local
+					data->u.num = pc_readaccountreg(sd, add_str(reg));// local
 				break;
 			default:
-				data->u.num = pc_readglobalreg(sd, reg);
+				data->u.num = pc_readglobalreg(sd, add_str(reg));
 				break;
 		}
 
 	}
 
-
 	switch( data->type ) {
 		case C_INT:
 			sprintf(atcmd_output,msg_txt(sd,1373),reg,data->u.num); // %s value is now :%d
@@ -9353,7 +9353,7 @@ ACMD_FUNC(langtype)
 		lang = msg_langstr2langtype(langstr); //Switch langstr to associated langtype
 		if( msg_checklangtype(lang,false) == 1 ){ //Verify it's enabled and set it
 			char output[100];
-			pc_setaccountreg(sd, "#langtype", lang); //For login/char
+			pc_setaccountreg(sd, add_str("#langtype"), lang); //For login/char
 			sd->langtype = lang;
 			sprintf(output,msg_txt(sd,461),msg_langtype2langstr(lang)); // Language is now set to %s.
 			clif_displaymessage(fd,output);

+ 18 - 8
src/map/chrif.c

@@ -157,8 +157,15 @@ bool chrif_auth_delete(uint32 account_id, uint32 char_id, enum sd_state state) {
 		if ( node->char_dat )
 			aFree(node->char_dat);
 
-		if ( node->sd )
+		if ( node->sd ) {
+			if (node->sd->regs.vars)
+				node->sd->regs.vars->destroy(node->sd->regs.vars, script_reg_destroy);
+
+			if (node->sd->regs.arrays)
+				node->sd->regs.arrays->destroy(node->sd->regs.arrays, script_free_array_db);
+
 			aFree(node->sd);
+		}
 
 		ers_free(auth_db_ers, node);
 		idb_remove(auth_db,account_id);
@@ -298,12 +305,8 @@ int chrif_save(struct map_session_data *sd, int flag) {
 		sd->state.storage_flag = 0; //Force close it.
 
 	//Saving of registry values.
-	if (sd->state.reg_dirty&4)
-		intif_saveregistry(sd, 3); //Save char regs
-	if (sd->state.reg_dirty&2)
-		intif_saveregistry(sd, 2); //Save account regs
-	if (sd->state.reg_dirty&1)
-		intif_saveregistry(sd, 1); //Save account2 regs
+	if (sd->vars_dirty)
+		intif_saveregistry(sd);
 
 	mmo_charstatus_len = sizeof(sd->status) + 13;
 	WFIFOHEAD(char_fd, mmo_charstatus_len);
@@ -1927,8 +1930,15 @@ int auth_db_final(DBKey key, DBData *data, va_list ap) {
 	if (node->char_dat)
 		aFree(node->char_dat);
 
-	if (node->sd)
+	if (node->sd) {
+		if (node->sd->regs.vars)
+			node->sd->regs.vars->destroy(node->sd->regs.vars, script_reg_destroy);
+
+		if (node->sd->regs.arrays)
+			node->sd->regs.arrays->destroy(node->sd->regs.arrays, script_free_array_db);
+
 		aFree(node->sd);
+	}
 
 	ers_free(auth_db_ers, node);
 

+ 1 - 1
src/map/clif.c

@@ -14223,7 +14223,7 @@ void clif_parse_FeelSaveOk(int fd,struct map_session_data *sd)
 
 	sd->feel_map[i].index = map_id2index(sd->bl.m);
 	sd->feel_map[i].m = sd->bl.m;
-	pc_setglobalreg(sd,sg_info[i].feel_var,sd->feel_map[i].index);
+	pc_setglobalreg(sd, add_str(sg_info[i].feel_var), sd->feel_map[i].index);
 
 //Are these really needed? Shouldn't they show up automatically from the feel save packet?
 //	clif_misceffect2(&sd->bl, 0x1b0);

+ 2 - 2
src/map/duel.c

@@ -27,7 +27,7 @@ void duel_savetime(struct map_session_data* sd)
 	time(&timer);
 	t = localtime(&timer);
 
-	pc_setglobalreg(sd, "PC_LAST_DUEL_TIME", t->tm_mday*24*60 + t->tm_hour*60 + t->tm_min);
+	pc_setglobalreg(sd, add_str("PC_LAST_DUEL_TIME"), t->tm_mday*24*60 + t->tm_hour*60 + t->tm_min);
 }
 
 /*
@@ -42,7 +42,7 @@ int duel_checktime(struct map_session_data* sd)
 	time(&timer);
 	t = localtime(&timer);
 
-	diff = t->tm_mday*24*60 + t->tm_hour*60 + t->tm_min - pc_readglobalreg(sd, "PC_LAST_DUEL_TIME");
+	diff = t->tm_mday*24*60 + t->tm_hour*60 + t->tm_min - pc_readglobalreg(sd, add_str("PC_LAST_DUEL_TIME"));
 
 	return !(diff >= 0 && diff < battle_config.duel_time_interval);
 }

+ 8 - 4
src/map/instance.c

@@ -278,7 +278,8 @@ int instance_create(int party_id, const char *name)
 	instance_data[i].keep_timer = INVALID_TIMER;
 	instance_data[i].idle_limit = 0;
 	instance_data[i].idle_timer = INVALID_TIMER;
-	instance_data[i].vars = idb_alloc(DB_OPT_RELEASE_DATA);
+	instance_data[i].regs.vars = i64db_alloc(DB_OPT_RELEASE_DATA);
+	instance_data[i].regs.arrays = NULL;
 	memset(instance_data[i].map, 0, sizeof(instance_data[i].map));
 
 	p->instance_id = i;
@@ -459,11 +460,14 @@ int instance_destroy(short instance_id)
 			clif_instance_changewait( party_getavailablesd( p ), 0xffff, 1 );
 	}
 
-	if( im->vars ) {
-		db_destroy(im->vars);
-		im->vars = NULL;
+	if( im->regs.vars ) {
+		db_destroy(im->regs.vars);
+		im->regs.vars = NULL;
 	}
 
+	if( im->regs.arrays )
+		instance_data[instance_id].regs.arrays->destroy(instance_data[instance_id].regs.arrays, script_free_array_db);
+
 	ShowInfo("[Instance] Destroyed %d.\n", instance_id);
 
 	memset(&instance_data[instance_id], 0, sizeof(instance_data[instance_id]));

+ 7 - 1
src/map/instance.h

@@ -4,6 +4,11 @@
 #ifndef _INSTANCE_H_
 #define _INSTANCE_H_
 
+#include "../common/mmo.h" // struct point
+#include "script.h" // struct reg_db
+
+struct block_list;
+
 #define MAX_INSTANCE_DATA	300	// Essentially how many instances we can create, but instance creation is primarily decided by MAX_MAP_PER_SERVER	
 #define MAX_MAP_PER_INSTANCE 	10	// Max number of maps per instance
 
@@ -21,7 +26,8 @@ struct instance_data {
 	unsigned int idle_limit;
 	int idle_timer;
 
-	struct DBMap* vars; // Instance Variable for scripts
+	struct reg_db regs; ///< Instance variables for scripts
+
 	struct {
 		int m;
 		int src_m;

+ 174 - 91
src/map/intif.c

@@ -350,73 +350,120 @@ int intif_wis_message_to_gm(char *wisp_name, int permission, char *mes)
 	return 1;
 }
 
-/**
- * 
- * @param str
- * @param reg
- * @param qty
- * @return 
- */
-int intif_regtostr(char* str, struct global_reg *reg, int qty)
-{
-	int len =0, i;
-
-	for (i = 0; i < qty; i++) {
-		len+= sprintf(str+len, "%s", reg[i].str)+1; //We add 1 to consider the '\0' in place.
-		len+= sprintf(str+len, "%s", reg[i].value)+1;
-	}
-	return len;
-}
-
 /**
  * Request for saving registry values.
  * @param sd : Player to save registry
- * @param type : Type of registry to save, 1=login save, 2=acc on char, 3=char
  * @return 1=msg sent, -1=error
  */
-int intif_saveregistry(struct map_session_data *sd, int type)
+int intif_saveregistry(struct map_session_data *sd)
 {
-	struct global_reg *reg;
-	int count;
-	int i, p;
+	DBIterator *iter;
+	DBKey key;
+	DBData *data;
+	int plen = 0;
+	size_t len;
 
-	if (CheckForCharServer())
+	if (CheckForCharServer() || !sd->regs.vars)
 		return -1;
 
-	switch (type) {
-	case 3: //Character reg
-		reg = sd->save_reg.global;
-		count = sd->save_reg.global_num;
-		sd->state.reg_dirty &= ~0x4;
-	break;
-	case 2: //Account reg
-		reg = sd->save_reg.account;
-		count = sd->save_reg.account_num;
-		sd->state.reg_dirty &= ~0x2;
-	break;
-	case 1: //Account2 reg
-		reg = sd->save_reg.account2;
-		count = sd->save_reg.account2_num;
-		sd->state.reg_dirty &= ~0x1;
-	break;
-	default: //Broken code?
-		ShowError("intif_saveregistry: Invalid type %d\n", type);
-		return -1;
-	}
-	WFIFOHEAD(inter_fd, 288 * MAX_REG_NUM+13);
-	WFIFOW(inter_fd,0)=0x3004;
-	WFIFOL(inter_fd,4)=sd->status.account_id;
-	WFIFOL(inter_fd,8)=sd->status.char_id;
-	WFIFOB(inter_fd,12)=type;
-	for( p = 13, i = 0; i < count; i++ ) {
-		if (reg[i].str[0] != '\0' && reg[i].value[0] != '\0') {
-			p+= sprintf((char*)WFIFOP(inter_fd,p), "%s", reg[i].str)+1; //We add 1 to consider the '\0' in place.
-			p+= sprintf((char*)WFIFOP(inter_fd,p), "%s", reg[i].value)+1;
+	WFIFOHEAD(inter_fd, 60000 + 300);
+	WFIFOW(inter_fd,0)  = 0x3004;
+	// 0x2 = length (set later)
+	WFIFOL(inter_fd,4)  = sd->status.account_id;
+	WFIFOL(inter_fd,8)  = sd->status.char_id;
+	WFIFOW(inter_fd,12) = 0; // count
+
+	plen = 14;
+
+	iter = db_iterator(sd->regs.vars);
+	for( data = iter->first(iter,&key); iter->exists(iter); data = iter->next(iter,&key) ) {
+		const char *varname = NULL;
+		struct script_reg_state *src = NULL;
+
+		if( data->type != DB_DATA_PTR ) // it's a @number
+			continue;
+
+		varname = get_str(script_getvarid(key.i64));
+
+		if( varname[0] == '@' ) // @string$ can get here, so we skip
+			continue;
+
+		src = db_data2ptr(data);
+
+		if( !src->update )
+			continue;
+
+		src->update = false;
+
+		len = strlen(varname)+1;
+
+		WFIFOB(inter_fd, plen) = (unsigned char)len; // won't be higher; the column size is 32
+		plen += 1;
+
+		safestrncpy((char*)WFIFOP(inter_fd,plen), varname, len);
+		plen += len;
+
+		WFIFOL(inter_fd, plen) = script_getvaridx(key.i64);
+		plen += 4;
+
+		if( src->type ) {
+			struct script_reg_str *p = (struct script_reg_str *)src;
+
+			WFIFOB(inter_fd, plen) = p->value ? 2 : 3;
+			plen += 1;
+
+			if( p->value ) {
+				len = strlen(p->value)+1;
+
+				WFIFOB(inter_fd, plen) = (unsigned char)len; // won't be higher; the column size is 254
+				plen += 1;
+
+				safestrncpy((char*)WFIFOP(inter_fd,plen), p->value, len);
+				plen += len;
+			} else {
+				script_reg_destroy_single(sd,key.i64,&p->flag);
+			}
+
+		} else {
+			struct script_reg_num *p = (struct script_reg_num *)src;
+
+			WFIFOB(inter_fd, plen) =  p->value ? 0 : 1;
+			plen += 1;
+
+			if( p->value ) {
+				WFIFOL(inter_fd, plen) = p->value;
+				plen += 4;
+			} else {
+				script_reg_destroy_single(sd,key.i64,&p->flag);
+			}
+
+		}
+
+		WFIFOW(inter_fd,12) += 1;
+
+		if( plen > 60000 ) {
+			WFIFOW(inter_fd, 2) = plen;
+			WFIFOSET(inter_fd, plen);
+
+			// prepare follow up
+			WFIFOHEAD(inter_fd, 60000 + 300);
+			WFIFOW(inter_fd,0)  = 0x3004;
+			// 0x2 = length (set later)
+			WFIFOL(inter_fd,4)  = sd->status.account_id;
+			WFIFOL(inter_fd,8)  = sd->status.char_id;
+			WFIFOW(inter_fd,12) = 0; // count
+
+			plen = 14;
 		}
 	}
-	WFIFOW(inter_fd,2)=p;
-	WFIFOSET(inter_fd,WFIFOW(inter_fd,2));
-	return 1;
+	dbi_destroy(iter);
+
+	WFIFOW(inter_fd, 2) = plen;
+	WFIFOSET(inter_fd, plen);
+
+	sd->vars_dirty = false;
+
+	return 0;
 }
 
 /**
@@ -429,10 +476,6 @@ int intif_request_registry(struct map_session_data *sd, int flag)
 {
 	nullpo_ret(sd);
 
-	sd->save_reg.account2_num = -1;
-	sd->save_reg.account_num = -1;
-	sd->save_reg.global_num = -1;
-
 	if (CheckForCharServer())
 		return 0;
 
@@ -1266,58 +1309,98 @@ int mapif_parse_WisToGM(int fd)
  * @param fd : char-serv link
  * @return 0=error, 1=sucess
  */
-int intif_parse_Registers(int fd)
+void intif_parse_Registers(int fd)
 {
-	int j,p,len,max, flag;
+	int flag;
 	struct map_session_data *sd;
-	struct global_reg *reg;
-	int *qty;
 	uint32 account_id = RFIFOL(fd,4), char_id = RFIFOL(fd,8);
 	struct auth_node *node = chrif_auth_check(account_id, char_id, ST_LOGIN);
+	char type = RFIFOB(fd, 13);
+
 	if (node)
 		sd = node->sd;
 	else { //Normally registries should arrive for in log-in chars.
 		sd = map_id2sd(account_id);
-		if (sd && RFIFOB(fd,12) == 3 && sd->status.char_id != char_id)
-			sd = NULL; //Character registry from another character.
 	}
-	if (!sd) return 0;
 
-	flag = (sd->save_reg.global_num == -1 || sd->save_reg.account_num == -1 || sd->save_reg.account2_num == -1);
+	if (!sd || sd->status.char_id != char_id) {
+		return; //Character registry from another character.
+	}
+
+	flag = ( sd->vars_received&PRL_ACCG && sd->vars_received&PRL_ACCL && sd->vars_received&PRL_CHAR ) ? 0 : 1;
 
 	switch (RFIFOB(fd,12)) {
 		case 3: //Character Registry
-			reg = sd->save_reg.global;
-			qty = &sd->save_reg.global_num;
-			max = GLOBAL_REG_NUM;
-		break;
+			sd->vars_received |= PRL_CHAR;
+			break;
 		case 2: //Account Registry
-			reg = sd->save_reg.account;
-			qty = &sd->save_reg.account_num;
-			max = ACCOUNT_REG_NUM;
-		break;
+			sd->vars_received |= PRL_ACCL;
+			break;
 		case 1: //Account2 Registry
-			reg = sd->save_reg.account2;
-			qty = &sd->save_reg.account2_num;
-			max = ACCOUNT_REG2_NUM;
-		break;
+			sd->vars_received |= PRL_ACCG;
+			break;
+		case 0:
+			break;
 		default:
 			ShowError("intif_parse_Registers: Unrecognized type %d\n",RFIFOB(fd,12));
-			return 0;
+			return;
 	}
-	for(j=0,p=13;j<max && p<RFIFOW(fd,2);j++){
-		sscanf((char*)RFIFOP(fd,p), "%31c%n", reg[j].str,&len);
-		reg[j].str[len]='\0';
-		p += len+1; //+1 to skip the '\0' between strings.
-		sscanf((char*)RFIFOP(fd,p), "%255c%n", reg[j].value,&len);
-		reg[j].value[len]='\0';
-		p += len+1;
+
+	// have it not complain about insertion of vars before loading, and not set those vars as new or modified
+	reg_load = true;
+	
+	if( RFIFOW(fd, 14) ) {
+		char key[32];
+		unsigned int index;
+		int max = RFIFOW(fd, 14), cursor = 16, i;
+
+		/**
+		 * Vessel!char_reg_num_db
+		 *
+		 * str type
+		 * { keyLength(B), key(<keyLength>), index(L), valLength(B), val(<valLength>) }
+		 **/
+		if (type) {
+			for(i = 0; i < max; i++) {
+				char sval[254];
+				safestrncpy(key, (char*)RFIFOP(fd, cursor + 1), RFIFOB(fd, cursor));
+				cursor += RFIFOB(fd, cursor) + 1;
+
+				index = RFIFOL(fd, cursor);
+				cursor += 4;
+
+				safestrncpy(sval, (char*)RFIFOP(fd, cursor + 1), RFIFOB(fd, cursor));
+				cursor += RFIFOB(fd, cursor) + 1;
+
+				set_reg(NULL,sd,reference_uid(add_str(key), index), key, (void*)sval, NULL);
+			}
+		/**
+		 * Vessel!
+		 *
+		 * int type
+		 * { keyLength(B), key(<keyLength>), index(L), value(L) }
+		 **/
+		} else {
+			for(i = 0; i < max; i++) {
+				int ival;
+				safestrncpy(key, (char*)RFIFOP(fd, cursor + 1), RFIFOB(fd, cursor));
+				cursor += RFIFOB(fd, cursor) + 1;
+
+				index = RFIFOL(fd, cursor);
+				cursor += 4;
+
+				ival = RFIFOL(fd, cursor);
+				cursor += 4;
+
+				set_reg(NULL,sd,reference_uid(add_str(key), index), key, (void*)__64BPRTSIZE(ival), NULL);
+			}
+		}
 	}
-	*qty = j;
 
-	if (flag && sd->save_reg.global_num > -1 && sd->save_reg.account_num > -1 && sd->save_reg.account2_num > -1)
+	reg_load = false;
+
+	if (flag && sd->vars_received&PRL_ACCG && sd->vars_received&PRL_ACCL && sd->vars_received&PRL_CHAR)
 		pc_reg_received(sd); //Received all registry values, execute init scripts and what-not. [Skotlex]
-	return 1;
 }
 
 /**

+ 1 - 1
src/map/intif.h

@@ -24,7 +24,7 @@ int intif_main_message(struct map_session_data* sd, const char* message);
 int intif_wis_message(struct map_session_data *sd,char *nick,char *mes,int mes_len);
 int intif_wis_message_to_gm(char *Wisp_name, int permission, char *mes);
 
-int intif_saveregistry(struct map_session_data *sd, int type);
+int intif_saveregistry(struct map_session_data *sd);
 int intif_request_registry(struct map_session_data *sd, int flag);
 
 int intif_request_guild_storage(uint32 account_id, int guild_id);

+ 230 - 111
src/map/mapreg.c

@@ -3,62 +3,103 @@
 
 #include "../common/cbasetypes.h"
 #include "../common/db.h"
+#include "../common/ers.h"
 #include "../common/malloc.h"
+#include "../common/showmsg.h"
 #include "../common/sql.h"
 #include "../common/strlib.h"
 #include "../common/timer.h"
 
 #include "map.h" // mmysql_handle
+#include "mapreg.h"
 #include "script.h"
 
 #include <stdlib.h>
 
-static DBMap* mapreg_db = NULL; // int var_id -> int value
-static DBMap* mapregstr_db = NULL; // int var_id -> char* value
+static struct eri *mapreg_ers;
+
+bool skip_insert = false;
 
 static char mapreg_table[32] = "mapreg";
-static bool mapreg_dirty = false;
+static bool mapreg_dirty = false; // Whether there are modified regs to be saved
+
 #define MAPREG_AUTOSAVE_INTERVAL (300*1000)
 
 
-/// Looks up the value of an integer variable using its uid.
-int mapreg_readreg(int uid)
+/**
+ * Looks up the value of an integer variable using its uid.
+ *
+ * @param uid: variable's unique identifier.
+ * @return: variable's integer value
+ */
+int mapreg_readreg(int64 uid)
 {
-	return idb_iget(mapreg_db, uid);
+	struct mapreg_save *m = i64db_get(regs.vars, uid);
+	return m ? m->u.i : 0;
 }
 
-/// Looks up the value of a string variable using its uid.
-char* mapreg_readregstr(int uid)
+/**
+ * Looks up the value of a string variable using its uid.
+ *
+ * @param uid: variable's unique identifier
+ * @return: variable's string value
+ */
+char* mapreg_readregstr(int64 uid)
 {
-	return idb_get(mapregstr_db, uid);
+	struct mapreg_save *m = i64db_get(regs.vars, uid);
+	return m ? m->u.str : NULL;
 }
 
-/// Modifies the value of an integer variable.
-bool mapreg_setreg(int uid, int val)
+/**
+ * Modifies the value of an integer variable.
+ *
+ * @param uid: variable's unique identifier
+ * @param val new value
+ * @return: true value was successfully set
+ */
+bool mapreg_setreg(int64 uid, int val)
 {
-	int num = (uid & 0x00ffffff);
-	int i   = (uid & 0xff000000) >> 24;
+	struct mapreg_save *m;
+	int num = script_getvarid(uid);
+	unsigned int i = script_getvaridx(uid);
 	const char* name = get_str(num);
 
-	if( val != 0 )
-	{
-		if( idb_iput(mapreg_db,uid,val) )
-			mapreg_dirty = true; // already exists, delay write
-		else if(name[1] != '@')
-		{// write new variable to database
-			char tmp_str[32*2+1];
-			Sql_EscapeStringLen(mmysql_handle, tmp_str, name, strnlen(name, 32));
-			if( SQL_ERROR == Sql_Query(mmysql_handle, "INSERT INTO `%s`(`varname`,`index`,`value`) VALUES ('%s','%d','%d')", mapreg_table, tmp_str, i, val) )
-				Sql_ShowDebug(mmysql_handle);
+	if (val != 0) {
+		if ((m = i64db_get(regs.vars, uid))) {
+			m->u.i = val;
+			if (name[1] != '@') {
+				m->save = true;
+				mapreg_dirty = true;
+			}
+		} else {
+			if (i)
+				script_array_update(&regs, uid, false);
+
+			m = ers_alloc(mapreg_ers, struct mapreg_save);
+
+			m->u.i = val;
+			m->uid = uid;
+			m->save = false;
+			m->is_string = false;
+
+			if (name[1] != '@' && !skip_insert) {// write new variable to database
+				char tmp_str[32 * 2 + 1];
+				Sql_EscapeStringLen(mmysql_handle, tmp_str, name, strnlen(name, 32));
+				if (SQL_ERROR == Sql_Query(mmysql_handle, "INSERT INTO `%s`(`varname`,`index`,`value`) VALUES ('%s','%d','%d')", mapreg_table, tmp_str, i, val))
+					Sql_ShowDebug(mmysql_handle);
+			}
+			i64db_put(regs.vars, uid, m);
 		}
-	}
-	else // val == 0
-	{
-		idb_remove(mapreg_db,uid);
+	} else { // val == 0
+		if (i)
+			script_array_update(&regs, uid, true);
+		if ((m = i64db_get(regs.vars, uid))) {
+			ers_free(mapreg_ers, m);
+		}
+		i64db_remove(regs.vars, uid);
 
-		if( name[1] != '@' )
-		{// Remove from database because it is unused.
-			if( SQL_ERROR == Sql_Query(mmysql_handle, "DELETE FROM `%s` WHERE `varname`='%s' AND `index`='%d'", mapreg_table, name, i) )
+		if (name[1] != '@') {// Remove from database because it is unused.
+			if (SQL_ERROR == Sql_Query(mmysql_handle, "DELETE FROM `%s` WHERE `varname`='%s' AND `index`='%d'", mapreg_table, name, i))
 				Sql_ShowDebug(mmysql_handle);
 		}
 	}
@@ -66,40 +107,71 @@ bool mapreg_setreg(int uid, int val)
 	return true;
 }
 
-/// Modifies the value of a string variable.
-bool mapreg_setregstr(int uid, const char* str)
+/**
+ * Modifies the value of a string variable.
+ *
+ * @param uid: variable's unique identifier
+ * @param str: new value
+ * @return: true value was successfully set
+ */
+bool mapreg_setregstr(int64 uid, const char* str)
 {
-	int num = (uid & 0x00ffffff);
-	int i   = (uid & 0xff000000) >> 24;
+	struct mapreg_save *m;
+	int num = script_getvarid(uid);
+	unsigned int i = script_getvaridx(uid);
 	const char* name = get_str(num);
 
-	if( str == NULL || *str == 0 )
-	{
-		if(name[1] != '@') {
-			if( SQL_ERROR == Sql_Query(mmysql_handle, "DELETE FROM `%s` WHERE `varname`='%s' AND `index`='%d'", mapreg_table, name, i) )
+	if (str == NULL || *str == 0) {
+		if (i)
+			script_array_update(&regs, uid, true);
+		if (name[1] != '@') {
+			if (SQL_ERROR == Sql_Query(mmysql_handle, "DELETE FROM `%s` WHERE `varname`='%s' AND `index`='%d'", mapreg_table, name, i))
 				Sql_ShowDebug(mmysql_handle);
 		}
-		idb_remove(mapregstr_db,uid);
-	}
-	else
-	{
-		if (idb_put(mapregstr_db,uid, aStrdup(str)))
-			mapreg_dirty = true;
-		else if(name[1] != '@') { //put returned null, so we must insert.
-			// Someone is causing a database size infinite increase here without name[1] != '@' [Lance]
-			char tmp_str[32*2+1];
-			char tmp_str2[255*2+1];
-			Sql_EscapeStringLen(mmysql_handle, tmp_str, name, strnlen(name, 32));
-			Sql_EscapeStringLen(mmysql_handle, tmp_str2, str, strnlen(str, 255));
-			if( SQL_ERROR == Sql_Query(mmysql_handle, "INSERT INTO `%s`(`varname`,`index`,`value`) VALUES ('%s','%d','%s')", mapreg_table, tmp_str, i, tmp_str2) )
-				Sql_ShowDebug(mmysql_handle);
+		if ((m = i64db_get(regs.vars, uid))) {
+			if (m->u.str != NULL)
+				aFree(m->u.str);
+			ers_free(mapreg_ers, m);
+		}
+		i64db_remove(regs.vars, uid);
+	} else {
+		if ((m = i64db_get(regs.vars, uid))) {
+			if (m->u.str != NULL)
+				aFree(m->u.str);
+			m->u.str = aStrdup(str);
+			if (name[1] != '@') {
+				mapreg_dirty = true;
+				m->save = true;
+			}
+		} else {
+			if (i)
+				script_array_update(&regs, uid, false);
+
+			m = ers_alloc(mapreg_ers, struct mapreg_save);
+
+			m->uid = uid;
+			m->u.str = aStrdup(str);
+			m->save = false;
+			m->is_string = true;
+
+			if (name[1] != '@' && !skip_insert) { //put returned null, so we must insert.
+				char tmp_str[32 * 2 + 1];
+				char tmp_str2[255 * 2 + 1];
+				Sql_EscapeStringLen(mmysql_handle, tmp_str, name, strnlen(name, 32));
+				Sql_EscapeStringLen(mmysql_handle, tmp_str2, str, strnlen(str, 255));
+				if (SQL_ERROR == Sql_Query(mmysql_handle, "INSERT INTO `%s`(`varname`,`index`,`value`) VALUES ('%s','%d','%s')", mapreg_table, tmp_str, i, tmp_str2))
+					Sql_ShowDebug(mmysql_handle);
+			}
+			i64db_put(regs.vars, uid, m);
 		}
 	}
 
 	return true;
 }
 
-/// Loads permanent variables from database
+/**
+ * Loads permanent variables from database.
+ */
 static void script_load_mapreg(void)
 {
 	/*
@@ -122,100 +194,144 @@ static void script_load_mapreg(void)
 		return;
 	}
 
+	skip_insert = true;
+
 	SqlStmt_BindColumn(stmt, 0, SQLDT_STRING, &varname[0], sizeof(varname), &length, NULL);
 	SqlStmt_BindColumn(stmt, 1, SQLDT_INT, &index, 0, NULL, NULL);
 	SqlStmt_BindColumn(stmt, 2, SQLDT_STRING, &value[0], sizeof(value), NULL, NULL);
-	
-	while ( SQL_SUCCESS == SqlStmt_NextRow(stmt) )
-	{
+
+	while ( SQL_SUCCESS == SqlStmt_NextRow(stmt) ) {
 		int s = add_str(varname);
 		int i = index;
 
-		if( varname[length-1] == '$' )
-			idb_put(mapregstr_db, (i<<24)|s, aStrdup(value));
-		else
-			idb_iput(mapreg_db, (i<<24)|s, atoi(value));
+		if( varname[length-1] == '$' ) {
+			if( i64db_exists(regs.vars, reference_uid(s, i)) ) {
+				ShowWarning("load_mapreg: Duplicate! '%s' => '%s' skipping...\n",varname,value);
+				continue;
+			}
+
+			mapreg_setregstr(reference_uid(s, i), value);
+		} else {
+			if( i64db_exists(regs.vars, reference_uid(s, i)) ) {
+				ShowWarning("load_mapreg: Duplicate! '%s' => '%s' skipping...\n",varname,value);
+				continue;
+			}
+
+			mapreg_setreg(reference_uid(s, i), atoi(value));
+		}
 	}
+
 	SqlStmt_Free(stmt);
 
+	skip_insert = false;
 	mapreg_dirty = false;
 }
 
-/// Saves permanent variables to database
+/**
+ * Saves permanent variables to database.
+ */
 static void script_save_mapreg(void)
 {
-	DBIterator* iter;
-	DBData *data;
-	DBKey key;
-
-	iter = db_iterator(mapreg_db);
-	for( data = iter->first(iter,&key); iter->exists(iter); data = iter->next(iter,&key) )
-	{
-		int num = (key.i & 0x00ffffff);
-		int i   = (key.i & 0xff000000) >> 24;
-		const char* name = get_str(num);
-
-		if( name[1] == '@' )
-			continue;
-
-		if( SQL_ERROR == Sql_Query(mmysql_handle, "UPDATE `%s` SET `value`='%d' WHERE `varname`='%s' AND `index`='%d'", mapreg_table, db_data2i(data), name, i) )
-			Sql_ShowDebug(mmysql_handle);
-	}
-	dbi_destroy(iter);
-
-	iter = db_iterator(mapregstr_db);
-	for( data = iter->first(iter,&key); iter->exists(iter); data = iter->next(iter,&key) )
-	{
-		int num = (key.i & 0x00ffffff);
-		int i   = (key.i & 0xff000000) >> 24;
-		const char* name = get_str(num);
-		char tmp_str2[2*255+1];
-
-		if( name[1] == '@' )
-			continue;
-
-		Sql_EscapeStringLen(mmysql_handle, tmp_str2, db_data2ptr(data), safestrnlen(db_data2ptr(data), 255));
-		if( SQL_ERROR == Sql_Query(mmysql_handle, "UPDATE `%s` SET `value`='%s' WHERE `varname`='%s' AND `index`='%d'", mapreg_table, tmp_str2, name, i) )
-			Sql_ShowDebug(mmysql_handle);
+	if (mapreg_dirty) {
+		DBIterator *iter = db_iterator(regs.vars);
+		struct mapreg_save *m;
+		for (m = dbi_first(iter); dbi_exists(iter); m = dbi_next(iter)) {
+			if (m->save) {
+				int num = script_getvarid(m->uid);
+				int i = script_getvaridx(m->uid);
+				const char* name = get_str(num);
+				if (!m->is_string) {
+					if (SQL_ERROR == Sql_Query(mmysql_handle, "UPDATE `%s` SET `value`='%d' WHERE `varname`='%s' AND `index`='%d' LIMIT 1", mapreg_table, m->u.i, name, i))
+						Sql_ShowDebug(mmysql_handle);
+				} else {
+					char tmp_str2[2 * 255 + 1];
+					Sql_EscapeStringLen(mmysql_handle, tmp_str2, m->u.str, safestrnlen(m->u.str, 255));
+					if (SQL_ERROR == Sql_Query(mmysql_handle, "UPDATE `%s` SET `value`='%s' WHERE `varname`='%s' AND `index`='%d' LIMIT 1", mapreg_table, tmp_str2, name, i))
+						Sql_ShowDebug(mmysql_handle);
+				}
+				m->save = false;
+			}
+		}
+		dbi_destroy(iter);
+		mapreg_dirty = false;
 	}
-	dbi_destroy(iter);
-
-	mapreg_dirty = false;
 }
 
+/**
+ * Timer event to auto-save permanent variables.
+ */
 static int script_autosave_mapreg(int tid, unsigned int tick, int id, intptr_t data)
 {
-	if( mapreg_dirty )
-		script_save_mapreg();
-
+	script_save_mapreg();
 	return 0;
 }
 
+/**
+ * Destroys a mapreg_save structure, freeing the contained string, if any.
+ *
+ * @see DBApply
+ */
+int mapreg_destroyreg(DBKey key, DBData *data, va_list ap)
+{
+	struct mapreg_save *m = NULL;
+
+	if (data->type != DB_DATA_PTR) // Sanity check
+		return 0;
+
+	m = db_data2ptr(data);
+
+	if (m->is_string) {
+		if (m->u.str)
+			aFree(m->u.str);
+	}
+	ers_free(mapreg_ers, m);
+
+	return 0;
+}
 
+/**
+ * Reloads mapregs, saving to database beforehand.
+ *
+ * This has the effect of clearing the temporary variables, and
+ * reloading the permanent ones.
+ */
 void mapreg_reload(void)
 {
-	if( mapreg_dirty )
-		script_save_mapreg();
+	script_save_mapreg();
 
-	db_clear(mapreg_db);
-	db_clear(mapregstr_db);
+	regs.vars->clear(regs.vars, mapreg_destroyreg);
+
+	if (regs.arrays)
+		regs.arrays->destroy(regs.arrays, script_free_array_db);
 
 	script_load_mapreg();
 }
 
+/**
+ * Finalizer.
+ */
 void mapreg_final(void)
 {
-	if( mapreg_dirty )
-		script_save_mapreg();
+	script_save_mapreg();
+
+	regs.vars->clear(regs.vars, mapreg_destroyreg);
 
-	db_destroy(mapreg_db);
-	db_destroy(mapregstr_db);
+	ers_destroy(mapreg_ers);
+
+	if (regs.arrays)
+		regs.arrays->destroy(regs.arrays, script_free_array_db);
 }
 
+/**
+ * Initializer.
+ */
 void mapreg_init(void)
 {
-	mapreg_db = idb_alloc(DB_OPT_BASE);
-	mapregstr_db = idb_alloc(DB_OPT_RELEASE_DATA);
+	regs.vars = i64db_alloc(DB_OPT_BASE);
+	mapreg_ers = ers_new(sizeof(struct mapreg_save), "mapreg.c:mapreg_ers", ERS_OPT_NONE);
+
+	skip_insert = false;
+	regs.arrays = NULL;
 
 	script_load_mapreg();
 
@@ -223,9 +339,12 @@ void mapreg_init(void)
 	add_timer_interval(gettick() + MAPREG_AUTOSAVE_INTERVAL, script_autosave_mapreg, 0, 0, MAPREG_AUTOSAVE_INTERVAL);
 }
 
+/**
+ * Loads the mapreg configuration file.
+ */
 bool mapreg_config_read(const char* w1, const char* w2)
 {
-	if(!strcmpi(w1, "mapreg_db"))
+	if(!strcmpi(w1, "mapreg_table"))
 		safestrncpy(mapreg_table, w2, sizeof(mapreg_table));
 	else
 		return false;

+ 20 - 4
src/map/mapreg.h

@@ -4,14 +4,30 @@
 #ifndef _MAPREG_H_
 #define _MAPREG_H_
 
+#include "script.h"
+
+struct mapreg_save {
+	int64 uid;         ///< Unique ID
+	union {
+		int i;         ///< Numeric value
+		char *str;     ///< String value
+	} u;
+	bool is_string;    ///< true if it's a string, false if it's a number
+	bool save;         ///< Whether a save operation is pending
+};
+
+struct reg_db regs;
+extern bool skip_insert;
+
 void mapreg_reload(void);
 void mapreg_final(void);
 void mapreg_init(void);
 bool mapreg_config_read(const char* w1, const char* w2);
 
-int mapreg_readreg(int uid);
-char* mapreg_readregstr(int uid);
-bool mapreg_setreg(int uid, int val);
-bool mapreg_setregstr(int uid, const char* str);
+int mapreg_readreg(int64 uid);
+char* mapreg_readregstr(int64 uid);
+bool mapreg_setreg(int64 uid, int val);
+bool mapreg_setregstr(int64 uid, const char* str);
+int mapreg_destroyreg(DBKey key, DBData *data, va_list ap);
 
 #endif /* _MAPREG_H_ */

+ 2 - 2
src/map/mob.c

@@ -2662,11 +2662,11 @@ int mob_dead(struct mob_data *md, struct block_list *src, int type)
 				{
 					pc_addfame(sd, battle_config.fame_taekwon_mission);
 					sd->mission_mobid = temp;
-					pc_setglobalreg(sd,"TK_MISSION_ID", temp);
+					pc_setglobalreg(sd, add_str("TK_MISSION_ID"), temp);
 					sd->mission_count = 0;
 					clif_mission_info(sd, temp, 0);
 				}
-				pc_setglobalreg(sd,"TK_MISSION_COUNT", sd->mission_count);
+				pc_setglobalreg(sd, add_str("TK_MISSION_COUNT"), sd->mission_count);
 			}
 
 			if( sd->status.party_id )

+ 4 - 5
src/map/npc.c

@@ -3523,8 +3523,7 @@ static const char* npc_parse_function(char* w1, char* w2, char* w3, char* w4, co
 
 	script_start = strstr(start,"\t{");
 	end = strchr(start,'\n');
-	if( *w4 != '{' || script_start == NULL || (end != NULL && script_start > end) )
-	{
+	if (*w4 != '{' || script_start == NULL || (end != NULL && script_start > end)) {
 		ShowError("npc_parse_function: Missing left curly '%%TAB%%{' in file '%s', line '%d'. Skipping the rest of the file.\n * w1=%s\n * w2=%s\n * w3=%s\n * w4=%s\n", filepath, strline(buffer,start-buffer), w1, w2, w3, w4);
 		return NULL;// can't continue
 	}
@@ -3539,11 +3538,11 @@ static const char* npc_parse_function(char* w1, char* w2, char* w3, char* w4, co
 		return end;
 
 	func_db = script_get_userfunc_db();
-	if (func_db->put(func_db, db_str2key(w3), db_ptr2data(script), &old_data))
-	{
+	if (func_db->put(func_db, db_str2key(w3), db_ptr2data(script), &old_data)) {
 		struct script_code *oldscript = (struct script_code*)db_data2ptr(&old_data);
+
 		ShowInfo("npc_parse_function: Overwriting user function [%s] (%s:%d)\n", w3, filepath, strline(buffer,start-buffer));
-		script_free_vars(oldscript->script_vars);
+		script_free_vars(oldscript->local.vars);
 		aFree(oldscript->script_buf);
 		aFree(oldscript);
 	}

+ 248 - 301
src/map/pc.c

@@ -1193,6 +1193,12 @@ bool pc_authok(struct map_session_data *sd, uint32 login_id2, time_t expiration_
 	sd->save_quest = false;
 	sd->count_rewarp = 0;
 
+	sd->regs.vars = i64db_alloc(DB_OPT_BASE);
+	sd->regs.arrays = NULL;
+	sd->vars_dirty = false;
+	sd->vars_ok = false;
+	sd->vars_received = 0x0;
+
 	//warp player
 	if ((i=pc_setpos(sd,sd->status.last_point.map, sd->status.last_point.x, sd->status.last_point.y, CLR_OUTSIGHT)) != 0) {
 		ShowError ("Last_point_map %s - id %d not found (error code %d)\n", mapindex_id2name(sd->status.last_point.map), sd->status.last_point.map, i);
@@ -1309,7 +1315,7 @@ bool pc_set_hate_mob(struct map_session_data *sd, int pos, struct block_list *bl
 			return false; //Wrong size
 	}
 	sd->hate_mob[pos] = class_;
-	pc_setglobalreg(sd,sg_info[pos].hate_var,class_+1);
+	pc_setglobalreg(sd, add_str(sg_info[pos].hate_var), class_+1);
 	clif_hate_info(sd, pos, class_, 1);
 	return true;
 }
@@ -1321,24 +1327,27 @@ void pc_reg_received(struct map_session_data *sd)
 {
 	uint8 i;
 
-	sd->change_level_2nd = pc_readglobalreg(sd,"jobchange_level");
-	sd->change_level_3rd = pc_readglobalreg(sd,"jobchange_level_3rd");
-	sd->die_counter = pc_readglobalreg(sd,"PC_DIE_COUNTER");
+	sd->vars_ok = true;
+
+	sd->change_level_2nd = pc_readglobalreg(sd, add_str("jobchange_level"));
+	sd->change_level_3rd = pc_readglobalreg(sd, add_str("jobchange_level_3rd"));
+	sd->die_counter = pc_readglobalreg(sd, add_str("PC_DIE_COUNTER"));
 
-	sd->langtype = pc_readaccountreg(sd,"#langtype");
-	if(msg_checklangtype(sd->langtype,true)<0) sd->langtype=0; //invalid langtype reset to default
+	sd->langtype = pc_readaccountreg(sd, add_str("#langtype"));
+	if (msg_checklangtype(sd->langtype,true) < 0)
+		sd->langtype = 0; //invalid langtype reset to default
 
 	// Cash shop
-	sd->cashPoints = pc_readaccountreg(sd,"#CASHPOINTS");
-	sd->kafraPoints = pc_readaccountreg(sd,"#KAFRAPOINTS");
+	sd->cashPoints = pc_readaccountreg(sd, add_str("#CASHPOINTS"));
+	sd->kafraPoints = pc_readaccountreg(sd, add_str("#KAFRAPOINTS"));
 
 	// Cooking Exp
-	sd->cook_mastery = pc_readglobalreg(sd,"COOK_MASTERY");
+	sd->cook_mastery = pc_readglobalreg(sd, add_str("COOK_MASTERY"));
 
 	if( (sd->class_&MAPID_BASEMASK) == MAPID_TAEKWON )
 	{ // Better check for class rather than skill to prevent "skill resets" from unsetting this
-		sd->mission_mobid = pc_readglobalreg(sd,"TK_MISSION_ID");
-		sd->mission_count = pc_readglobalreg(sd,"TK_MISSION_COUNT");
+		sd->mission_mobid = pc_readglobalreg(sd, add_str("TK_MISSION_ID"));
+		sd->mission_count = pc_readglobalreg(sd, add_str("TK_MISSION_COUNT"));
 	}
 
 	if (battle_config.feature_banking)
@@ -1348,31 +1357,31 @@ void pc_reg_received(struct map_session_data *sd)
 	for(i=0;i<MAX_PC_FEELHATE;i++) { //for now - someone need to make reading from txt/sql
 		uint16 j;
 
-		if ((j = pc_readglobalreg(sd,sg_info[i].feel_var))!=0) {
+		if ((j = pc_readglobalreg(sd, add_str(sg_info[i].feel_var))) != 0) {
 			sd->feel_map[i].index = j;
 			sd->feel_map[i].m = map_mapindex2mapid(j);
 		} else {
 			sd->feel_map[i].index = 0;
 			sd->feel_map[i].m = -1;
 		}
-		sd->hate_mob[i] = pc_readglobalreg(sd,sg_info[i].hate_var)-1;
+		sd->hate_mob[i] = pc_readglobalreg(sd, add_str(sg_info[i].hate_var))-1;
 	}
 
 	if ((i = pc_checkskill(sd,RG_PLAGIARISM)) > 0) {
-		sd->cloneskill_idx = skill_get_index(pc_readglobalreg(sd,SKILL_VAR_PLAGIARISM));
+		sd->cloneskill_idx = skill_get_index(pc_readglobalreg(sd, add_str(SKILL_VAR_PLAGIARISM)));
 		if (sd->cloneskill_idx > 0) {
-			sd->status.skill[sd->cloneskill_idx].id = pc_readglobalreg(sd,SKILL_VAR_PLAGIARISM);
-			sd->status.skill[sd->cloneskill_idx].lv = pc_readglobalreg(sd,SKILL_VAR_PLAGIARISM_LV);
+			sd->status.skill[sd->cloneskill_idx].id = pc_readglobalreg(sd, add_str(SKILL_VAR_PLAGIARISM));
+			sd->status.skill[sd->cloneskill_idx].lv = pc_readglobalreg(sd, add_str(SKILL_VAR_PLAGIARISM_LV));
 			if (sd->status.skill[sd->cloneskill_idx].lv > i)
 				sd->status.skill[sd->cloneskill_idx].lv = i;
 			sd->status.skill[sd->cloneskill_idx].flag = SKILL_FLAG_PLAGIARIZED;
 		}
 	}
 	if ((i = pc_checkskill(sd,SC_REPRODUCE)) > 0) {
-		sd->reproduceskill_idx = skill_get_index(pc_readglobalreg(sd,SKILL_VAR_REPRODUCE));
+		sd->reproduceskill_idx = skill_get_index(pc_readglobalreg(sd, add_str(SKILL_VAR_REPRODUCE)));
 		if (sd->reproduceskill_idx > 0) {
-			sd->status.skill[sd->reproduceskill_idx].id = pc_readglobalreg(sd,SKILL_VAR_REPRODUCE);
-			sd->status.skill[sd->reproduceskill_idx].lv = pc_readglobalreg(sd,SKILL_VAR_REPRODUCE_LV);
+			sd->status.skill[sd->reproduceskill_idx].id = pc_readglobalreg(sd, add_str(SKILL_VAR_REPRODUCE));
+			sd->status.skill[sd->reproduceskill_idx].lv = pc_readglobalreg(sd, add_str(SKILL_VAR_REPRODUCE_LV));
 			if (i < sd->status.skill[sd->reproduceskill_idx].lv)
 				sd->status.skill[sd->reproduceskill_idx].lv = i;
 			sd->status.skill[sd->reproduceskill_idx].flag = SKILL_FLAG_PLAGIARIZED;
@@ -1842,7 +1851,7 @@ int pc_calc_skilltree_normalize_job(struct map_session_data *sd)
 
 			}
 
-			pc_setglobalreg (sd, "jobchange_level", sd->change_level_2nd);
+			pc_setglobalreg(sd, add_str("jobchange_level"), sd->change_level_2nd);
 		}
 
 		if (skill_point < novice_skills + (sd->change_level_2nd - 1))
@@ -1859,7 +1868,7 @@ int pc_calc_skilltree_normalize_job(struct map_session_data *sd)
 						- (sd->status.job_level - 1)
 						- (sd->change_level_2nd - 1)
 						- novice_skills;
-					pc_setglobalreg (sd, "jobchange_level_3rd", sd->change_level_3rd);
+					pc_setglobalreg(sd, add_str("jobchange_level_3rd"), sd->change_level_3rd);
 			}
 
 			if (skill_point < novice_skills + (sd->change_level_2nd - 1) + (sd->change_level_3rd - 1))
@@ -4173,11 +4182,11 @@ int pc_paycash(struct map_session_data *sd, int price, int points, e_log_pick_ty
 		return -1;
 	}
 
-	pc_setaccountreg(sd, "#CASHPOINTS", sd->cashPoints-cash);
+	pc_setaccountreg(sd, add_str("#CASHPOINTS"), sd->cashPoints-cash);
 	if( cash ){
 		log_cash( sd, type, LOG_CASH_TYPE_CASH, -cash );
 	}
-	pc_setaccountreg(sd, "#KAFRAPOINTS", sd->kafraPoints-points);
+	pc_setaccountreg(sd, add_str("#KAFRAPOINTS"), sd->kafraPoints-points);
 	if( points ){
 		log_cash( sd, type, LOG_CASH_TYPE_KAFRA, -points );
 	}
@@ -4205,7 +4214,7 @@ int pc_getcash( struct map_session_data *sd, int cash, int points, e_log_pick_ty
 			cash = MAX_ZENY-sd->cashPoints;
 		}
 
-		pc_setaccountreg(sd, "#CASHPOINTS", sd->cashPoints+cash);
+		pc_setaccountreg(sd, add_str("#CASHPOINTS"), sd->cashPoints+cash);
 		if( cash ){
 			log_cash( sd, type, LOG_CASH_TYPE_CASH, cash );
 		}
@@ -4231,7 +4240,7 @@ int pc_getcash( struct map_session_data *sd, int cash, int points, e_log_pick_ty
 			points = MAX_ZENY-sd->kafraPoints;
 		}
 
-		pc_setaccountreg(sd, "#KAFRAPOINTS", sd->kafraPoints+points);
+		pc_setaccountreg(sd, add_str("#KAFRAPOINTS"), sd->kafraPoints+points);
 		if( points ){
 			log_cash( sd, type, LOG_CASH_TYPE_KAFRA, points );
 		}
@@ -6940,7 +6949,7 @@ int pc_resetstate(struct map_session_data* sd)
 	if( sd->mission_mobid ) { //bugreport:2200
 		sd->mission_mobid = 0;
 		sd->mission_count = 0;
-		pc_setglobalreg(sd,"TK_MISSION_ID", 0);
+		pc_setglobalreg(sd, add_str("TK_MISSION_ID"), 0);
 	}
 
 	status_calc_pc(sd, SCO_NONE);
@@ -7076,7 +7085,7 @@ int pc_resetfeel(struct map_session_data* sd)
 	{
 		sd->feel_map[i].m = -1;
 		sd->feel_map[i].index = 0;
-		pc_setglobalreg(sd,sg_info[i].feel_var,0);
+		pc_setglobalreg(sd, add_str(sg_info[i].feel_var), 0);
 	}
 
 	return 0;
@@ -7090,7 +7099,7 @@ int pc_resethate(struct map_session_data* sd)
 	for (i=0; i<3; i++)
 	{
 		sd->hate_mob[i] = -1;
-		pc_setglobalreg(sd,sg_info[i].hate_var,0);
+		pc_setglobalreg(sd, add_str(sg_info[i].hate_var), 0);
 	}
 	return 0;
 }
@@ -7331,7 +7340,7 @@ int pc_dead(struct map_session_data *sd,struct block_list *src)
 
 	pc_setdead(sd);
 
-	pc_setglobalreg(sd,"PC_DIE_COUNTER",sd->die_counter+1);
+	pc_setglobalreg(sd, add_str("PC_DIE_COUNTER"), sd->die_counter+1);
 	pc_setparam(sd, SP_KILLERRID, src?src->id:0);
 
 	//Reset menu skills/item skills
@@ -8090,12 +8099,12 @@ bool pc_jobchange(struct map_session_data *sd,int job, char upper)
 	// changing from 1st to 2nd job
 	if ((b_class&JOBL_2) && !(sd->class_&JOBL_2) && (sd->class_&MAPID_UPPERMASK) != MAPID_SUPER_NOVICE) {
 		sd->change_level_2nd = sd->status.job_level;
-		pc_setglobalreg (sd, "jobchange_level", sd->change_level_2nd);
+		pc_setglobalreg(sd, add_str("jobchange_level"), sd->change_level_2nd);
 	}
 	// changing from 2nd to 3rd job
 	else if((b_class&JOBL_THIRD) && !(sd->class_&JOBL_THIRD)) {
 		sd->change_level_3rd = sd->status.job_level;
-		pc_setglobalreg (sd, "jobchange_level_3rd", sd->change_level_3rd);
+		pc_setglobalreg(sd, add_str("jobchange_level_3rd"), sd->change_level_3rd);
 	}
 
 	if(sd->cloneskill_idx > 0) {
@@ -8103,11 +8112,11 @@ bool pc_jobchange(struct map_session_data *sd,int job, char upper)
 			sd->status.skill[sd->cloneskill_idx].id = 0;
 			sd->status.skill[sd->cloneskill_idx].lv = 0;
 			sd->status.skill[sd->cloneskill_idx].flag = SKILL_FLAG_PERMANENT;
-			clif_deleteskill(sd,pc_readglobalreg(sd,SKILL_VAR_PLAGIARISM));
+			clif_deleteskill(sd,pc_readglobalreg(sd, add_str(SKILL_VAR_PLAGIARISM)));
 		}
 		sd->cloneskill_idx = 0;
-		pc_setglobalreg(sd,SKILL_VAR_PLAGIARISM, 0);
-		pc_setglobalreg(sd,SKILL_VAR_PLAGIARISM_LV, 0);
+		pc_setglobalreg(sd, add_str(SKILL_VAR_PLAGIARISM), 0);
+		pc_setglobalreg(sd, add_str(SKILL_VAR_PLAGIARISM_LV), 0);
 	}
 
 	if(sd->reproduceskill_idx > 0) {
@@ -8115,11 +8124,11 @@ bool pc_jobchange(struct map_session_data *sd,int job, char upper)
 			sd->status.skill[sd->reproduceskill_idx].id = 0;
 			sd->status.skill[sd->reproduceskill_idx].lv = 0;
 			sd->status.skill[sd->reproduceskill_idx].flag = SKILL_FLAG_PERMANENT;
-			clif_deleteskill(sd,pc_readglobalreg(sd,SKILL_VAR_REPRODUCE));
+			clif_deleteskill(sd,pc_readglobalreg(sd, add_str(SKILL_VAR_REPRODUCE)));
 		}
 		sd->reproduceskill_idx = 0;
-		pc_setglobalreg(sd,SKILL_VAR_REPRODUCE,0);
-		pc_setglobalreg(sd,SKILL_VAR_REPRODUCE_LV,0);
+		pc_setglobalreg(sd, add_str(SKILL_VAR_REPRODUCE), 0);
+		pc_setglobalreg(sd, add_str(SKILL_VAR_REPRODUCE_LV), 0);
 	}
 
 	// Give or reduce transcendent status points
@@ -8554,332 +8563,266 @@ bool pc_can_attack( struct map_session_data *sd, int target_id ) {
 }
 
 /*==========================================
- * Read ram register for player sd
- * get val (int) from reg for player sd
+ * Read '@type' variables (temporary numeric char reg)
  *------------------------------------------*/
-int pc_readreg(struct map_session_data* sd, int reg)
+int pc_readreg(struct map_session_data* sd, int64 reg)
 {
-	int i;
-
-	nullpo_ret(sd);
-
-	ARR_FIND( 0, sd->reg_num, i,  sd->reg[i].index == reg );
-	return ( i < sd->reg_num ) ? sd->reg[i].data : 0;
+	return i64db_iget(sd->regs.vars, reg);
 }
+
 /*==========================================
- * Set ram register for player sd
- * memo val(int) at reg for player sd
+ * Set '@type' variables (temporary numeric char reg)
  *------------------------------------------*/
-bool pc_setreg(struct map_session_data* sd, int reg, int val)
+bool pc_setreg(struct map_session_data* sd, int64 reg, int val)
 {
-	int i;
-
-	nullpo_retr(false,sd);
+	unsigned int index = script_getvaridx(reg);
 
-	ARR_FIND( 0, sd->reg_num, i, sd->reg[i].index == reg );
-	if( i < sd->reg_num )
-	{// overwrite existing entry
-		sd->reg[i].data = val;
-		return true;
-	}
+	nullpo_retr(false, sd);
 
-	ARR_FIND( 0, sd->reg_num, i, sd->reg[i].data == 0 );
-	if( i == sd->reg_num )
-	{// nothing free, increase size
-		sd->reg_num++;
-		RECREATE(sd->reg, struct script_reg, sd->reg_num);
+	if( val ) {
+		i64db_iput(sd->regs.vars, reg, val);
+		if( index )
+			script_array_update(&sd->regs, reg, false);
+	} else {
+		i64db_remove(sd->regs.vars, reg);
+		if( index )
+			script_array_update(&sd->regs, reg, true);
 	}
-	sd->reg[i].index = reg;
-	sd->reg[i].data = val;
 
 	return true;
 }
 
 /*==========================================
- * Read ram register for player sd
- * get val (str) from reg for player sd
+ * Read '@type$' variables (temporary string char reg)
  *------------------------------------------*/
-char* pc_readregstr(struct map_session_data* sd, int reg)
+char* pc_readregstr(struct map_session_data* sd, int64 reg)
 {
-	int i;
+	struct script_reg_str *p = NULL;
 
-	nullpo_ret(sd);
+	p = i64db_get(sd->regs.vars, reg);
 
-	ARR_FIND( 0, sd->regstr_num, i,  sd->regstr[i].index == reg );
-	return ( i < sd->regstr_num ) ? sd->regstr[i].data : NULL;
+	return p ? p->value : NULL;
 }
+
 /*==========================================
- * Set ram register for player sd
- * memo val(str) at reg for player sd
+ * Set '@type$' variables (temporary string char reg)
  *------------------------------------------*/
-bool pc_setregstr(struct map_session_data* sd, int reg, const char* str)
+bool pc_setregstr(struct map_session_data* sd, int64 reg, const char* str)
 {
-	int i;
+	struct script_reg_str *p = NULL;
+	unsigned int index = script_getvaridx(reg);
+	DBData prev;
 
-	nullpo_retr(false,sd);
+	nullpo_retr(false, sd);
 
-	ARR_FIND( 0, sd->regstr_num, i, sd->regstr[i].index == reg );
-	if( i < sd->regstr_num )
-	{// found entry, update
-		if( str == NULL || *str == '\0' )
-		{// empty string
-			if( sd->regstr[i].data != NULL )
-				aFree(sd->regstr[i].data);
-			sd->regstr[i].data = NULL;
-		}
-		else if( sd->regstr[i].data )
-		{// recreate
-			size_t len = strlen(str)+1;
-			RECREATE(sd->regstr[i].data, char, len);
-			memcpy(sd->regstr[i].data, str, len*sizeof(char));
-		}
-		else
-		{// create
-			sd->regstr[i].data = aStrdup(str);
-		}
-		return true;
-	}
+	if( str[0] ) {
+		p = ers_alloc(str_reg_ers, struct script_reg_str);
 
-	if( str == NULL || *str == '\0' )
-		return true;// nothing to add, empty string
+		p->value = aStrdup(str);
+		p->flag.type = 1;
 
-	ARR_FIND( 0, sd->regstr_num, i, sd->regstr[i].data == NULL );
-	if( i == sd->regstr_num )
-	{// nothing free, increase size
-		sd->regstr_num++;
-		RECREATE(sd->regstr, struct script_regstr, sd->regstr_num);
+		if (sd->regs.vars->put(sd->regs.vars, db_i642key(reg), db_ptr2data(p), &prev)) {
+			p = db_data2ptr(&prev);
+			if( p->value )
+				aFree(p->value);
+			ers_free(str_reg_ers, p);
+		} else {
+			if( index )
+				script_array_update(&sd->regs, reg, false);
+		}
+	} else {
+		if (sd->regs.vars->remove(sd->regs.vars, db_i642key(reg), &prev)) {
+			p = db_data2ptr(&prev);
+			if( p->value )
+				aFree(p->value);
+			ers_free(str_reg_ers, p);
+			if( index )
+				script_array_update(&sd->regs, reg, true);
+		}
 	}
-	sd->regstr[i].index = reg;
-	sd->regstr[i].data = aStrdup(str);
 
 	return true;
 }
 
-int pc_readregistry(struct map_session_data *sd,const char *reg,int type)
+/**
+ * Serves the following variable types:
+ * - 'type' (permanent numeric char reg)
+ * - '#type' (permanent numeric account reg)
+ * - '##type' (permanent numeric account reg2)
+ **/
+int pc_readregistry(struct map_session_data *sd, int64 reg)
 {
-	struct global_reg *sd_reg;
-	int i,max;
+	struct script_reg_num *p = NULL;
 
-	nullpo_ret(sd);
-	switch (type) {
-	case 3: //Char reg
-		sd_reg = sd->save_reg.global;
-		max = sd->save_reg.global_num;
-	break;
-	case 2: //Account reg
-		sd_reg = sd->save_reg.account;
-		max = sd->save_reg.account_num;
-	break;
-	case 1: //Account2 reg
-		sd_reg = sd->save_reg.account2;
-		max = sd->save_reg.account2_num;
-	break;
-	default:
-		return 0;
-	}
-	if (max == -1) {
-		ShowError("pc_readregistry: Trying to read reg value %s (type %d) before it's been loaded!\n", reg, type);
+	if (!sd->vars_ok) {
+		ShowError("pc_readregistry: Trying to read reg %s before it's been loaded!\n", get_str(script_getvarid(reg)));
 		//This really shouldn't happen, so it's possible the data was lost somewhere, we should request it again.
-		intif_request_registry(sd,type==3?4:type);
+		//intif->request_registry(sd,type==3?4:type);
+		set_eof(sd->fd);
 		return 0;
 	}
 
-	ARR_FIND( 0, max, i, strcmp(sd_reg[i].str,reg) == 0 );
-	return ( i < max ) ? atoi(sd_reg[i].value) : 0;
+	p = i64db_get(sd->regs.vars, reg);
+
+	return p ? p->value : 0;
 }
 
-char* pc_readregistry_str(struct map_session_data *sd,const char *reg,int type)
+/**
+ * Serves the following variable types:
+ * - 'type$' (permanent str char reg)
+ * - '#type$' (permanent str account reg)
+ * - '##type$' (permanent str account reg2)
+ **/
+char* pc_readregistry_str(struct map_session_data *sd, int64 reg)
 {
-	struct global_reg *sd_reg;
-	int i,max;
+	struct script_reg_str *p = NULL;
 
-	nullpo_ret(sd);
-	switch (type) {
-	case 3: //Char reg
-		sd_reg = sd->save_reg.global;
-		max = sd->save_reg.global_num;
-	break;
-	case 2: //Account reg
-		sd_reg = sd->save_reg.account;
-		max = sd->save_reg.account_num;
-	break;
-	case 1: //Account2 reg
-		sd_reg = sd->save_reg.account2;
-		max = sd->save_reg.account2_num;
-	break;
-	default:
-		return NULL;
-	}
-	if (max == -1) {
-		ShowError("pc_readregistry: Trying to read reg value %s (type %d) before it's been loaded!\n", reg, type);
+	if (!sd->vars_ok) {
+		ShowError("pc_readregistry_str: Trying to read reg %s before it's been loaded!\n", get_str(script_getvarid(reg)));
 		//This really shouldn't happen, so it's possible the data was lost somewhere, we should request it again.
-		intif_request_registry(sd,type==3?4:type);
+		//intif->request_registry(sd,type==3?4:type);
+		set_eof(sd->fd);
 		return NULL;
 	}
 
-	ARR_FIND( 0, max, i, strcmp(sd_reg[i].str,reg) == 0 );
-	return ( i < max ) ? sd_reg[i].value : NULL;
-}
+	p = i64db_get(sd->regs.vars, reg);
 
-bool pc_setregistry(struct map_session_data *sd,const char *reg,int val,int type)
-{
-	struct global_reg *sd_reg;
-	int i,*max, regmax;
-
-	nullpo_retr(false,sd);
+	return p ? p->value : NULL;
+}
 
-	switch( type )
-	{
-	case 3: //Char reg
-		if( !strcmp(reg,"PC_DIE_COUNTER") && sd->die_counter != val ) {
-			i = (!sd->die_counter && (sd->class_&MAPID_UPPERMASK) == MAPID_SUPER_NOVICE);
-			sd->die_counter = val;
-			if( i )
-				status_calc_pc(sd,SCO_NONE); // Lost the bonus.
-		} else if( !strcmp(reg,"COOK_MASTERY") && sd->cook_mastery != val ) {
-			val = cap_value(val, 0, 1999);
-			sd->cook_mastery = val;
-		}
-		sd_reg = sd->save_reg.global;
-		max = &sd->save_reg.global_num;
-		regmax = GLOBAL_REG_NUM;
-	break;
-	case 2: //Account reg
-		if( !strcmp(reg,"#CASHPOINTS") && sd->cashPoints != val ) {
-			val = cap_value(val, 0, MAX_ZENY);
-			sd->cashPoints = val;
-		} else if( !strcmp(reg,"#KAFRAPOINTS") && sd->kafraPoints != val ) {
-			val = cap_value(val, 0, MAX_ZENY);
-			sd->kafraPoints = val;
-		}
-		sd_reg = sd->save_reg.account;
-		max = &sd->save_reg.account_num;
-		regmax = ACCOUNT_REG_NUM;
-	break;
-	case 1: //Account2 reg
-		sd_reg = sd->save_reg.account2;
-		max = &sd->save_reg.account2_num;
-		regmax = ACCOUNT_REG2_NUM;
-	break;
-	default:
-		return false;
+/**
+ * Serves the following variable types:
+ * - 'type' (permanent numeric char reg)
+ * - '#type' (permanent numeric account reg)
+ * - '##type' (permanent numeric account reg2)
+ **/
+int pc_setregistry(struct map_session_data *sd, int64 reg, int val)
+{
+	struct script_reg_num *p = NULL;
+	const char *regname = get_str(script_getvarid(reg));
+	unsigned int index = script_getvaridx(reg);
+
+	// These should be stored elsewhere e.g. char ones in char table, the cash ones in account_data table!
+	switch( regname[0] ) {
+		default: //Char reg
+			if( !strcmp(regname,"PC_DIE_COUNTER") && sd->die_counter != val ) {
+				int i = (!sd->die_counter && (sd->class_&MAPID_UPPERMASK) == MAPID_SUPER_NOVICE);
+				sd->die_counter = val;
+				if( i )
+					status_calc_pc(sd,SCO_NONE); // Lost the bonus.
+			} else if( !strcmp(regname,"COOK_MASTERY") && sd->cook_mastery != val ) {
+				val = cap_value(val, 0, 1999);
+				sd->cook_mastery = val;
+			}
+			break;
+		case '#':
+			if( !strcmp(regname,"#CASHPOINTS") && sd->cashPoints != val ) {
+				val = cap_value(val, 0, MAX_ZENY);
+				sd->cashPoints = val;
+			} else if( !strcmp(regname,"#KAFRAPOINTS") && sd->kafraPoints != val ) {
+				val = cap_value(val, 0, MAX_ZENY);
+				sd->kafraPoints = val;
+			}
+			break;
 	}
-	if (*max == -1) {
-		ShowError("pc_setregistry : refusing to set %s (type %d) until vars are received.\n", reg, type);
-		return true;
+	
+	if ( !reg_load && !sd->vars_ok ) {
+		ShowError("pc_setregistry : refusing to set %s until vars are received.\n", regname);
+		return 0;
 	}
 
-	// delete reg
-	if (val == 0) {
-		ARR_FIND( 0, *max, i, strcmp(sd_reg[i].str, reg) == 0 );
-		if( i < *max )
-		{
-			if (i != *max - 1)
-				memcpy(&sd_reg[i], &sd_reg[*max - 1], sizeof(struct global_reg));
-			memset(&sd_reg[*max - 1], 0, sizeof(struct global_reg));
-			(*max)--;
-			sd->state.reg_dirty |= 1<<(type-1); //Mark this registry as "need to be saved"
+	if ((p = i64db_get(sd->regs.vars, reg))) {
+		if( val ) {
+			if( !p->value && index ) /* its a entry that was deleted, so we reset array */
+				script_array_update(&sd->regs, reg, false);
+			p->value = val;
+		} else {
+			p->value = 0;
+			if( index )
+				script_array_update(&sd->regs, reg, true);
 		}
-		return true;
-	}
-	// change value if found
-	ARR_FIND( 0, *max, i, strcmp(sd_reg[i].str, reg) == 0 );
-	if( i < *max )
-	{
-		safesnprintf(sd_reg[i].value, sizeof(sd_reg[i].value), "%d", val);
-		sd->state.reg_dirty |= 1<<(type-1);
-		return true;
-	}
+		if (!reg_load)
+			p->flag.update = 1;/* either way, it will require either delete or replace */
+	} else if( val ) {
+		DBData prev;
 
-	// add value if not found
-	if (i < regmax) {
-		memset(&sd_reg[i], 0, sizeof(struct global_reg));
-		safestrncpy(sd_reg[i].str, reg, sizeof(sd_reg[i].str));
-		safesnprintf(sd_reg[i].value, sizeof(sd_reg[i].value), "%d", val);
-		(*max)++;
-		sd->state.reg_dirty |= 1<<(type-1);
-		return true;
+		if( index )
+			script_array_update(&sd->regs, reg, false);
+
+		p = ers_alloc(num_reg_ers, struct script_reg_num);
+
+		p->value = val;
+		if (!reg_load)
+			p->flag.update = 1;
+
+		if (sd->regs.vars->put(sd->regs.vars, db_i642key(reg), db_ptr2data(p), &prev)) {
+			p = db_data2ptr(&prev);
+			ers_free(num_reg_ers, p);
+		}
 	}
 
-	ShowError("pc_setregistry : couldn't set %s, limit of registries reached (%d)\n", reg, regmax);
+	if (!reg_load && p)
+		sd->vars_dirty = true;
 
-	return false;
+	return 1;
 }
 
-bool pc_setregistry_str(struct map_session_data *sd,const char *reg,const char *val,int type)
+/**
+ * Serves the following variable types:
+ * - 'type$' (permanent str char reg)
+ * - '#type$' (permanent str account reg)
+ * - '##type$' (permanent str account reg2)
+ **/
+int pc_setregistry_str(struct map_session_data *sd, int64 reg, const char *val)
 {
-	struct global_reg *sd_reg;
-	int i,*max, regmax;
+	struct script_reg_str *p = NULL;
+	const char *regname = get_str(script_getvarid(reg));
+	unsigned int index = script_getvaridx(reg);
 
-	nullpo_retr(false,sd);
-	if (reg[strlen(reg)-1] != '$') {
-		ShowError("pc_setregistry_str : reg %s must be string (end in '$') to use this!\n", reg);
-		return false;
-	}
-
-	switch (type) {
-	case 3: //Char reg
-		sd_reg = sd->save_reg.global;
-		max = &sd->save_reg.global_num;
-		regmax = GLOBAL_REG_NUM;
-	break;
-	case 2: //Account reg
-		sd_reg = sd->save_reg.account;
-		max = &sd->save_reg.account_num;
-		regmax = ACCOUNT_REG_NUM;
-	break;
-	case 1: //Account2 reg
-		sd_reg = sd->save_reg.account2;
-		max = &sd->save_reg.account2_num;
-		regmax = ACCOUNT_REG2_NUM;
-	break;
-	default:
-		return false;
-	}
-	if (*max == -1) {
-		ShowError("pc_setregistry_str : refusing to set %s (type %d) until vars are received.\n", reg, type);
-		return false;
+	if (!reg_load && !sd->vars_ok) {
+		ShowError("pc_setregistry_str : refusing to set %s until vars are received.\n", regname);
+		return 0;
 	}
 
-	// delete reg
-	if (!val || strcmp(val,"")==0)
-	{
-		ARR_FIND( 0, *max, i, strcmp(sd_reg[i].str, reg) == 0 );
-		if( i < *max )
-		{
-			if (i != *max - 1)
-				memcpy(&sd_reg[i], &sd_reg[*max - 1], sizeof(struct global_reg));
-			memset(&sd_reg[*max - 1], 0, sizeof(struct global_reg));
-			(*max)--;
-			sd->state.reg_dirty |= 1<<(type-1); //Mark this registry as "need to be saved"
-			if (type!=3) intif_saveregistry(sd,type);
+	if( (p = i64db_get(sd->regs.vars, reg) ) ) {
+		if( val[0] ) {
+			if( p->value )
+				aFree(p->value);
+			else if ( index ) // an entry that was deleted, so we reset
+				script_array_update(&sd->regs, reg, false);
+			p->value = aStrdup(val);
+		} else {
+			p->value = NULL;
+			if( index )
+				script_array_update(&sd->regs, reg, true);
 		}
-		return true;
-	}
+		if( !reg_load )
+			p->flag.update = 1; // either way, it will require either delete or replace
+	} else if( val[0] ) {
+		DBData prev;
 
-	// change value if found
-	ARR_FIND( 0, *max, i, strcmp(sd_reg[i].str, reg) == 0 );
-	if( i < *max )
-	{
-		safestrncpy(sd_reg[i].value, val, sizeof(sd_reg[i].value));
-		sd->state.reg_dirty |= 1<<(type-1); //Mark this registry as "need to be saved"
-		if (type!=3) intif_saveregistry(sd,type);
-		return true;
-	}
+		if( index )
+			script_array_update(&sd->regs, reg, false);
 
-	// add value if not found
-	if (i < regmax) {
-		memset(&sd_reg[i], 0, sizeof(struct global_reg));
-		safestrncpy(sd_reg[i].str, reg, sizeof(sd_reg[i].str));
-		safestrncpy(sd_reg[i].value, val, sizeof(sd_reg[i].value));
-		(*max)++;
-		sd->state.reg_dirty |= 1<<(type-1); //Mark this registry as "need to be saved"
-		if (type!=3) intif_saveregistry(sd,type);
-		return true;
+		p = ers_alloc(str_reg_ers, struct script_reg_str);
+
+		p->value = aStrdup(val);
+		if( !reg_load )
+			p->flag.update = 1;
+		p->flag.type = 1;
+
+		if( sd->regs.vars->put(sd->regs.vars, db_i642key(reg), db_ptr2data(p), &prev) ) {
+			p = db_data2ptr(&prev);
+			if( p->value )
+				aFree(p->value);
+			ers_free(str_reg_ers, p);
+		}
 	}
 
-	ShowError("pc_setregistry : couldn't set %s, limit of registries reached (%d)\n", reg, regmax);
-	return false;
+	if( !reg_load && p )
+		sd->vars_dirty = true;
+
+	return 1;
 }
 
 /**
@@ -8910,9 +8853,9 @@ bool pc_setreg2(struct map_session_data *sd, const char *reg, int val) {
 		case '@':
 			return pc_setreg(sd, add_str(reg), val);
 		case '#':
-			return (reg[1] == '#') ? pc_setaccountreg2(sd, reg, val) : pc_setaccountreg(sd, reg, val);
+			return (reg[1] == '#') ? pc_setaccountreg2(sd, add_str(reg), val) : pc_setaccountreg(sd, add_str(reg), val);
 		default:
-			return pc_setglobalreg(sd, reg, val);
+			return pc_setglobalreg(sd, add_str(reg), val);
 	}
 
 	return false;
@@ -8943,9 +8886,9 @@ int pc_readreg2(struct map_session_data *sd, const char *reg) {
 		case '@':
 			return pc_readreg(sd, add_str(reg));
 		case '#':
-			return (reg[1] == '#') ? pc_readaccountreg2(sd, reg) : pc_readaccountreg(sd, reg);
+			return (reg[1] == '#') ? pc_readaccountreg2(sd, add_str(reg)) : pc_readaccountreg(sd, add_str(reg));
 		default:
-			return pc_readglobalreg(sd, reg);
+			return pc_readglobalreg(sd, add_str(reg));
 	}
 
 	return 0;
@@ -11612,6 +11555,8 @@ void do_final_pc(void) {
 
 	ers_destroy(pc_sc_display_ers);
 	ers_destroy(pc_itemgrouphealrate_ers);
+	ers_destroy(num_reg_ers);
+	ers_destroy(str_reg_ers);
 }
 
 void do_init_pc(void) {
@@ -11654,4 +11599,6 @@ void do_init_pc(void) {
 
 	pc_sc_display_ers = ers_new(sizeof(struct sc_display_entry), "pc.c:pc_sc_display_ers", ERS_OPT_NONE);
 	pc_itemgrouphealrate_ers = ers_new(sizeof(struct s_pc_itemgrouphealrate), "pc.c:pc_itemgrouphealrate_ers", ERS_OPT_NONE);
+	num_reg_ers = ers_new(sizeof(struct script_reg_num), "pc.c:num_reg_ers", ERS_OPT_NONE);
+	str_reg_ers = ers_new(sizeof(struct script_reg_str), "pc.c:str_reg_ers", ERS_OPT_NONE);
 }

+ 39 - 30
src/map/pc.h

@@ -196,7 +196,6 @@ struct map_session_data {
 		unsigned int abra_flag : 2; // Abracadabra bugfix by Aru
 		unsigned int autocast : 1; // Autospell flag [Inkfish]
 		unsigned int autotrade : 3;	//By Fantik. &2 Requested by vending autotrade; &4 Requested by buyingstore autotrade
-		unsigned int reg_dirty : 4; //By Skotlex (marks whether registry variables have been saved or not yet)
 		unsigned int showdelay :1;
 		unsigned int showexp :1;
 		unsigned int showzeny :1;
@@ -257,7 +256,6 @@ struct map_session_data {
 	int langtype;
 	uint32 packet_ver;  // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04, 11: 21sept04, 12: 18oct04, 13: 25oct04 ... 18
 	struct mmo_charstatus status;
-	struct registry save_reg;
 
 	struct item_data* inventory_data[MAX_INVENTORY]; // direct pointers to itemdb entries (faster than doing item_id lookups)
 	short equip_index[EQI_MAX];
@@ -464,11 +462,6 @@ struct map_session_data {
 	short mission_mobid; //Stores the target mob_id for TK_MISSION
 	int die_counter; //Total number of times you've died
 	int devotion[MAX_DEVOTION]; //Stores the account IDs of chars devoted to.
-	int reg_num; //Number of registries (type numeric)
-	int regstr_num; //Number of registries (type string)
-
-	struct script_reg *reg;
-	struct script_regstr *regstr;
 
 	int trade_partner;
 	struct s_deal {
@@ -606,6 +599,14 @@ struct map_session_data {
 
 	unsigned char delayed_damage; //[Ind]
 
+	/**
+	 * Account/Char variables & array control of those variables
+	 **/
+	struct reg_db regs;
+	unsigned char vars_received; // char loading is only complete when you get it all.
+	bool vars_ok;
+	bool vars_dirty;
+
 	// temporary debugging of bug #3504
 	const char* delunit_prevfile;
 	int delunit_prevline;
@@ -647,6 +648,14 @@ struct map_session_data {
 struct eri *pc_sc_display_ers; /// Player's SC display table
 struct eri *pc_itemgrouphealrate_ers; /// Player's Item Group Heal Rate table
 
+/**
+ * ERS for the bulk of pc vars
+ **/
+struct eri *num_reg_ers;
+struct eri *str_reg_ers;
+/* */
+bool reg_load;
+
 /* Global Expiration Timer ID */
 extern int pc_expiration_tid;
 
@@ -1012,29 +1021,29 @@ void pc_setmadogear(struct map_session_data* sd, int flag);
 void pc_changelook(struct map_session_data *,int,int);
 void pc_equiplookall(struct map_session_data *sd);
 
-int pc_readparam(struct map_session_data*,int);
-bool pc_setparam(struct map_session_data*,int,int);
-int pc_readreg(struct map_session_data*,int);
-bool pc_setreg(struct map_session_data*,int,int);
-char *pc_readregstr(struct map_session_data *sd,int reg);
-bool pc_setregstr(struct map_session_data *sd,int reg,const char *str);
-
-#define pc_readglobalreg(sd,reg) pc_readregistry(sd,reg,3)
-#define pc_setglobalreg(sd,reg,val) pc_setregistry(sd,reg,val,3)
-#define pc_readglobalreg_str(sd,reg) pc_readregistry_str(sd,reg,3)
-#define pc_setglobalreg_str(sd,reg,val) pc_setregistry_str(sd,reg,val,3)
-#define pc_readaccountreg(sd,reg) pc_readregistry(sd,reg,2)
-#define pc_setaccountreg(sd,reg,val) pc_setregistry(sd,reg,val,2)
-#define pc_readaccountregstr(sd,reg) pc_readregistry_str(sd,reg,2)
-#define pc_setaccountregstr(sd,reg,val) pc_setregistry_str(sd,reg,val,2)
-#define pc_readaccountreg2(sd,reg) pc_readregistry(sd,reg,1)
-#define pc_setaccountreg2(sd,reg,val) pc_setregistry(sd,reg,val,1)
-#define pc_readaccountreg2str(sd,reg) pc_readregistry_str(sd,reg,1)
-#define pc_setaccountreg2str(sd,reg,val) pc_setregistry_str(sd,reg,val,1)
-int pc_readregistry(struct map_session_data*,const char*,int);
-bool pc_setregistry(struct map_session_data*,const char*,int,int);
-char *pc_readregistry_str(struct map_session_data*,const char*,int);
-bool pc_setregistry_str(struct map_session_data*,const char*,const char*,int);
+int pc_readparam(struct map_session_data *sd, int type);
+bool pc_setparam(struct map_session_data *sd, int type, int val);
+int pc_readreg(struct map_session_data *sd, int64 reg);
+bool pc_setreg(struct map_session_data *sd, int64 reg, int val);
+char *pc_readregstr(struct map_session_data *sd, int64 reg);
+bool pc_setregstr(struct map_session_data *sd, int64 reg, const char *str);
+int pc_readregistry(struct map_session_data *sd, int64 reg);
+int pc_setregistry(struct map_session_data *sd, int64 reg, int val);
+char *pc_readregistry_str(struct map_session_data *sd, int64 reg);
+int pc_setregistry_str(struct map_session_data *sd, int64 reg, const char *val);
+
+#define pc_readglobalreg(sd,reg) pc_readregistry(sd,reg)
+#define pc_setglobalreg(sd,reg,val) pc_setregistry(sd,reg,val)
+#define pc_readglobalreg_str(sd,reg) pc_readregistry_str(sd,reg)
+#define pc_setglobalreg_str(sd,reg,val) pc_setregistry_str(sd,reg,val)
+#define pc_readaccountreg(sd,reg) pc_readregistry(sd,reg)
+#define pc_setaccountreg(sd,reg,val) pc_setregistry(sd,reg,val)
+#define pc_readaccountregstr(sd,reg) pc_readregistry_str(sd,reg)
+#define pc_setaccountregstr(sd,reg,val) pc_setregistry_str(sd,reg,val)
+#define pc_readaccountreg2(sd,reg) pc_readregistry(sd,reg)
+#define pc_setaccountreg2(sd,reg,val) pc_setregistry(sd,reg,val)
+#define pc_readaccountreg2str(sd,reg) pc_readregistry_str(sd,reg)
+#define pc_setaccountreg2str(sd,reg,val) pc_setregistry_str(sd,reg,val)
 
 bool pc_setreg2(struct map_session_data *sd, const char *reg, int val);
 int pc_readreg2(struct map_session_data *sd, const char *reg);

文件差异内容过多而无法显示
+ 494 - 352
src/map/script.c


+ 180 - 16
src/map/script.h

@@ -4,13 +4,137 @@
 #ifndef _SCRIPT_H_
 #define _SCRIPT_H_
 
+#include "../common/cbasetypes.h"
+#include "map.h"
+
 #define NUM_WHISPER_VAR 10
 
+///////////////////////////////////////////////////////////////////////////////
+//## TODO possible enhancements: [FlavioJS]
+// - 'callfunc' supporting labels in the current npc "::LabelName"
+// - 'callfunc' supporting labels in other npcs "NpcName::LabelName"
+// - 'function FuncName;' function declarations reverting to global functions
+//   if local label isn't found
+// - join callfunc and callsub's functionality
+// - remove dynamic allocation in add_word()
+// - remove GETVALUE / SETVALUE
+// - clean up the set_reg / set_val / setd_sub mess
+// - detect invalid label references at parse-time
+
+//
+// struct script_state* st;
+//
+
+/// Returns the script_data at the target index
+#define script_getdata(st,i) ( &((st)->stack->stack_data[(st)->start + (i)]) )
+/// Returns if the stack contains data at the target index
+#define script_hasdata(st,i) ( (st)->end > (st)->start + (i) )
+/// Returns the index of the last data in the stack
+#define script_lastdata(st) ( (st)->end - (st)->start - 1 )
+/// Pushes an int into the stack
+#define script_pushint(st,val) push_val((st)->stack, C_INT, (val))
+/// Pushes a string into the stack (script engine frees it automatically)
+#define script_pushstr(st,val) push_str((st)->stack, C_STR, (val))
+/// Pushes a copy of a string into the stack
+#define script_pushstrcopy(st,val) push_str((st)->stack, C_STR, aStrdup(val))
+/// Pushes a constant string into the stack (must never change or be freed)
+#define script_pushconststr(st,val) push_str((st)->stack, C_CONSTSTR, (val))
+/// Pushes a nil into the stack
+#define script_pushnil(st) push_val((st)->stack, C_NOP, 0)
+/// Pushes a copy of the data in the target index
+#define script_pushcopy(st,i) push_copy((st)->stack, (st)->start + (i))
+
+#define script_isstring(st,i) data_isstring(script_getdata(st,i))
+#define script_isint(st,i) data_isint(script_getdata(st,i))
+
+#define script_getnum(st,val) conv_num(st, script_getdata(st,val))
+#define script_getstr(st,val) conv_str(st, script_getdata(st,val))
+#define script_getref(st,val) ( script_getdata(st,val)->ref )
+// Returns name of currently running function
+#define script_getfuncname(st) ( st->funcname )
+
+// Note: "top" functions/defines use indexes relative to the top of the stack
+//       -1 is the index of the data at the top
+
+/// Returns the script_data at the target index relative to the top of the stack
+#define script_getdatatop(st,i) ( &((st)->stack->stack_data[(st)->stack->sp + (i)]) )
+/// Pushes a copy of the data in the target index relative to the top of the stack
+#define script_pushcopytop(st,i) push_copy((st)->stack, (st)->stack->sp + (i))
+/// Removes the range of values [start,end[ relative to the top of the stack
+#define script_removetop(st,start,end) ( pop_stack((st), ((st)->stack->sp + (start)), (st)->stack->sp + (end)) )
+
+//
+// struct script_data* data;
+//
+
+/// Returns if the script data is a string
+#define data_isstring(data) ( (data)->type == C_STR || (data)->type == C_CONSTSTR )
+/// Returns if the script data is an int
+#define data_isint(data) ( (data)->type == C_INT )
+/// Returns if the script data is a reference
+#define data_isreference(data) ( (data)->type == C_NAME )
+/// Returns if the script data is a label
+#define data_islabel(data) ( (data)->type == C_POS )
+/// Returns if the script data is an internal script function label
+#define data_isfunclabel(data) ( (data)->type == C_USERFUNC_POS )
+
+/// Returns if this is a reference to a constant
+#define reference_toconstant(data) ( str_data[reference_getid(data)].type == C_INT )
+/// Returns if this a reference to a param
+#define reference_toparam(data) ( str_data[reference_getid(data)].type == C_PARAM )
+/// Returns if this a reference to a variable
+//##TODO confirm it's C_NAME [FlavioJS]
+#define reference_tovariable(data) ( str_data[reference_getid(data)].type == C_NAME )
+/// Returns the unique id of the reference (id and index)
+#define reference_getuid(data) ( (data)->u.num )
+/// Returns the id of the reference
+#define reference_getid(data) ( (int32)(int64)(reference_getuid(data) & 0xffffffff) )
+/// Returns the array index of the reference
+#define reference_getindex(data) ( (uint32)(int64)((reference_getuid(data) >> 32) & 0xffffffff) )
+/// Returns the name of the reference
+#define reference_getname(data) ( str_buf + str_data[reference_getid(data)].str )
+/// Returns the linked list of uid-value pairs of the reference (can be NULL)
+#define reference_getref(data) ( (data)->ref )
+/// Returns the value of the constant
+#define reference_getconstant(data) ( str_data[reference_getid(data)].val )
+/// Returns the type of param
+#define reference_getparamtype(data) ( str_data[reference_getid(data)].val )
+
+/// Composes the uid of a reference from the id and the index
+#define reference_uid(id,idx) ( (int64) ((uint64)(id) & 0xFFFFFFFF) | ((uint64)(idx) << 32) )
+
+/// Checks whether two references point to the same variable (or array)
+#define is_same_reference(data1, data2) \
+	(  reference_getid(data1) == reference_getid(data2) \
+	&& ( (data1->ref == data2->ref && data1->ref == NULL) \
+	  || (data1->ref != NULL && data2->ref != NULL && data1->ref->vars == data2->ref->vars \
+	     ) ) )
+
+#define script_getvarid(var) ( (int32)(int64)(var & 0xFFFFFFFF) )
+#define script_getvaridx(var) ( (uint32)(int64)((var >> 32) & 0xFFFFFFFF) )
+
+#define not_server_variable(prefix) ( (prefix) != '$' && (prefix) != '.' && (prefix) != '\'')
+#define is_string_variable(name) ( (name)[strlen(name) - 1] == '$' )
+
+#define FETCH(n, t) \
+		if( script_hasdata(st,n) ) \
+			(t)=script_getnum(st,n);
+
+/// Maximum amount of elements in script arrays
+#define SCRIPT_MAX_ARRAYSIZE (UINT_MAX - 1)
+#define SCRIPT_CMD_SUCCESS 0 ///when a buildin cmd was correctly done
+#define SCRIPT_CMD_FAILURE 1 ///when an errors appear in cmd, show_debug will follow
+
+#define SCRIPT_BLOCK_SIZE 512
+enum { LABEL_NEXTLINE = 1, LABEL_START };
+
 struct map_session_data;
 
 extern int potion_flag; //For use on Alchemist improved potions/Potion Pitcher. [Skotlex]
 extern int potion_hp, potion_per_hp, potion_sp, potion_per_sp;
 extern int potion_target;
+extern unsigned int *generic_ui_array;
+extern unsigned int generic_ui_array_size;
 
 extern struct Script_Config {
 	unsigned warn_func_mismatch_argtypes : 1;
@@ -77,22 +201,30 @@ typedef enum c_op {
 	C_SUB_PP, // --a
 } c_op;
 
+/**
+ * Generic reg database abstraction to be used with various types of regs/script variables.
+ */
+struct reg_db {
+	struct DBMap *vars;
+	struct DBMap *arrays;
+};
+
 struct script_retinfo {
-	struct DBMap* var_function;// scope variables
-	struct script_code* script;// script code
-	int pos;// script location
-	int nargs;// argument count
-	int defsp;// default stack pointer
+	struct reg_db scope;        ///< scope variables
+	struct script_code* script; ///< script code
+	int pos;                    ///< script location
+	int nargs;                  ///< argument count
+	int defsp;                  ///< default stack pointer
 };
 
 struct script_data {
 	enum c_op type;
 	union script_data_val {
-		int num;
+		int64 num;
 		char *str;
 		struct script_retinfo* ri;
 	} u;
-	struct DBMap** ref;
+	struct reg_db *ref;
 };
 
 // Moved defsp from script_state to script_stack since
@@ -100,15 +232,15 @@ struct script_data {
 struct script_code {
 	int script_size;
 	unsigned char* script_buf;
-	struct DBMap* script_vars;
+	struct reg_db local;
 };
 
 struct script_stack {
-	int sp;// number of entries in the stack
-	int sp_max;// capacity of the stack
+	int sp;                         ///< number of entries in the stack
+	int sp_max;                     ///< capacity of the stack
 	int defsp;
-	struct script_data *stack_data;// stack
-	struct DBMap* var_function;// scope variables
+	struct script_data *stack_data; ///< stack
+	struct reg_db scope;            ///< scope variables
 };
 
 
@@ -138,21 +270,32 @@ struct script_state {
 };
 
 struct script_reg {
-	int index;
+	int64 index;
 	int data;
 };
 
 struct script_regstr {
-	int index;
+	int64 index;
 	char* data;
 };
 
+struct script_array {
+	unsigned int id;       ///< the first 32b of the 64b uid, aka the id
+	unsigned int size;     ///< how many members
+	unsigned int *members; ///< member list
+};
+
 enum script_parse_options {
 	SCRIPT_USE_LABEL_DB = 0x1,// records labels in scriptlabel_db
 	SCRIPT_IGNORE_EXTERNAL_BRACKETS = 0x2,// ignores the check for {} brackets around the script
 	SCRIPT_RETURN_EMPTY_SCRIPT = 0x4// returns the script object instead of NULL for empty scripts
 };
 
+/**
+ * used to generate quick script_array entries
+ **/
+struct eri *array_ers;
+
 const char* skip_space(const char* p);
 void script_error(const char* src, const char* file, int start_line, const char* error_msg, const char* error_pos);
 void script_warning(const char* src, const char* file, int start_line, const char* error_msg, const char* error_pos);
@@ -161,9 +304,11 @@ struct script_code* parse_script(const char* src,const char* file,int line,int o
 void run_script_sub(struct script_code *rootscript,int pos,int rid,int oid, char* file, int lineno);
 void run_script(struct script_code *rootscript,int pos,int rid,int oid);
 
+int set_reg(struct script_state* st, TBL_PC* sd, int64 num, const char* name, const void* value, struct reg_db *ref);
 int set_var(struct map_session_data *sd, char *name, void *val);
 int conv_num(struct script_state *st,struct script_data *data);
 const char* conv_str(struct script_state *st,struct script_data *data);
+void pop_stack(struct script_state* st, int start, int end);
 int run_script_timer(int tid, unsigned int tick, int id, intptr_t data);
 void run_script_main(struct script_state *st);
 
@@ -183,7 +328,7 @@ void script_set_constant(const char* name, int value, bool isparameter);
 void script_hardcoded_constants(void);
 
 void script_cleararray_pc(struct map_session_data* sd, const char* varname, void* value);
-void script_setarray_pc(struct map_session_data* sd, const char* varname, uint8 idx, void* value, int* refcache);
+void script_setarray_pc(struct map_session_data* sd, const char* varname, uint32 idx, void* value, int* refcache);
 
 int script_config_read(char *cfgName);
 void do_init_script(void);
@@ -193,7 +338,26 @@ const char* get_str(int id);
 void script_reload(void);
 
 // @commands (script based)
-void setd_sub(struct script_state *st, TBL_PC *sd, const char *varname, int elem, void *value, struct DBMap **ref);
+void setd_sub(struct script_state *st, TBL_PC *sd, const char *varname, int elem, void *value, struct reg_db *ref);
+
+/**
+ * Array Handling
+ **/
+struct reg_db *script_array_src(struct script_state *st, struct map_session_data *sd, const char *name, struct reg_db *ref);
+void script_array_update(struct reg_db *src, int64 num, bool empty);
+void script_array_delete(struct reg_db *src, struct script_array *sa);
+void script_array_remove_member(struct reg_db *src, struct script_array *sa, unsigned int idx);
+void script_array_add_member(struct script_array *sa, unsigned int idx);
+unsigned int script_array_size(struct script_state *st, struct map_session_data *sd, const char *name, struct reg_db *ref);
+unsigned int script_array_highest_key(struct script_state *st, struct map_session_data *sd, const char *name, struct reg_db *ref);
+void script_array_ensure_zero(struct script_state *st, struct map_session_data *sd, int64 uid, struct reg_db *ref);
+int script_free_array_db(DBKey key, DBData *data, va_list ap);
+/* */
+void script_reg_destroy_single(struct map_session_data *sd, int64 reg, struct script_reg_state *data);
+int script_reg_destroy(DBKey key, DBData *data, va_list ap);
+/* */
+void script_generic_ui_array_expand(unsigned int plus);
+unsigned int *script_array_cpy_list(struct script_array *sa);
 
 #ifdef BETA_THREAD_TEST
 void queryThread_log(char * entry, int length);

+ 8 - 8
src/map/skill.c

@@ -2747,8 +2747,8 @@ static void skill_do_copy(struct block_list* src,struct block_list *bl, uint16 s
 					lv = min(skill_lv,pc_checkskill(tsd,RG_PLAGIARISM)); //Copied level never be > player's RG_PLAGIARISM level
 
 					tsd->cloneskill_idx = idx;
-					pc_setglobalreg(tsd,SKILL_VAR_PLAGIARISM,skill_id);
-					pc_setglobalreg(tsd,SKILL_VAR_PLAGIARISM_LV,lv);
+					pc_setglobalreg(tsd, add_str(SKILL_VAR_PLAGIARISM), skill_id);
+					pc_setglobalreg(tsd, add_str(SKILL_VAR_PLAGIARISM_LV), lv);
 				}
 				break;
 			case 2: //Copied by Reproduce
@@ -2771,8 +2771,8 @@ static void skill_do_copy(struct block_list* src,struct block_list *bl, uint16 s
 						lv = min(lv,skill_lv);
 
 					tsd->reproduceskill_idx = idx;
-					pc_setglobalreg(tsd,SKILL_VAR_REPRODUCE,skill_id);
-					pc_setglobalreg(tsd,SKILL_VAR_REPRODUCE_LV,lv);
+					pc_setglobalreg(tsd, add_str(SKILL_VAR_REPRODUCE), skill_id);
+					pc_setglobalreg(tsd, add_str(SKILL_VAR_REPRODUCE_LV), lv);
 				}
 				break;
 			default: return;
@@ -6338,7 +6338,7 @@ int skill_castend_nodamage_id (struct block_list *src, struct block_list *bl, ui
 			}
 			sd->mission_mobid = id;
 			sd->mission_count = 0;
-			pc_setglobalreg(sd,"TK_MISSION_ID", id);
+			pc_setglobalreg(sd, add_str("TK_MISSION_ID"), id);
 			clif_mission_info(sd, id, 0);
 			clif_skill_nodamage(src,bl,skill_id,skill_lv,1);
 		}
@@ -8159,7 +8159,7 @@ int skill_castend_nodamage_id (struct block_list *src, struct block_list *bl, ui
 		if (skill_id == SL_SUPERNOVICE && dstsd && dstsd->die_counter && !(rnd()%100))
 		{	//Erase death count 1% of the casts
 			dstsd->die_counter = 0;
-			pc_setglobalreg(dstsd,"PC_DIE_COUNTER", 0);
+			pc_setglobalreg(dstsd, add_str("PC_DIE_COUNTER"), 0);
 			clif_specialeffect(bl, 0x152, AREA);
 			//SC_SPIRIT invokes status_calc_pc for us.
 		}
@@ -18600,7 +18600,7 @@ bool skill_produce_mix(struct map_session_data *sd, uint16 skill_id, unsigned sh
 					if (skill_produce_db[idx].itemlv > 10 && skill_produce_db[idx].itemlv <= 20) { //Cooking items.
 						clif_specialeffect(&sd->bl, 608, AREA);
 						if (sd->cook_mastery < 1999)
-							pc_setglobalreg(sd, "COOK_MASTERY",sd->cook_mastery + ( 1 << ( (skill_produce_db[idx].itemlv - 11) / 2 ) ) * 5);
+							pc_setglobalreg(sd, add_str("COOK_MASTERY"), sd->cook_mastery + ( 1 << ( (skill_produce_db[idx].itemlv - 11) / 2 ) ) * 5);
 					}
 					break;
 			}
@@ -18707,7 +18707,7 @@ bool skill_produce_mix(struct map_session_data *sd, uint16 skill_id, unsigned sh
 				if (skill_produce_db[idx].itemlv > 10 && skill_produce_db[idx].itemlv <= 20 ) { //Cooking items.
 					clif_specialeffect(&sd->bl, 609, AREA);
 					if (sd->cook_mastery > 0)
-						pc_setglobalreg(sd, "COOK_MASTERY", sd->cook_mastery - ( 1 << ((skill_produce_db[idx].itemlv - 11) / 2) ) - ( ( ( 1 << ((skill_produce_db[idx].itemlv - 11) / 2) ) >> 1 ) * 3 ));
+						pc_setglobalreg(sd, add_str("COOK_MASTERY"), sd->cook_mastery - ( 1 << ((skill_produce_db[idx].itemlv - 11) / 2) ) - ( ( ( 1 << ((skill_produce_db[idx].itemlv - 11) / 2) ) >> 1 ) * 3 ));
 				}
 				break;
 		}

+ 1 - 1
src/map/trade.c

@@ -177,7 +177,7 @@ int impossible_trade_check(struct map_session_data *sd)
 	nullpo_retr(1, sd);
 
 	if(sd->deal.zeny > sd->status.zeny) {
-		pc_setglobalreg(sd,"ZENY_HACKER",1);
+		pc_setglobalreg(sd, add_str("ZENY_HACKER"), 1);
 		return -1;
 	}
 

+ 0 - 18
src/map/unit.c

@@ -3168,24 +3168,6 @@ int unit_free(struct block_list *bl, clr_type clrtype)
 			for(i = 1; i < 5; i++)
 				pc_del_talisman(sd, sd->talisman[i], i);
 
-			if( sd->reg ) {	// Double logout already freed pointer fix... [Skotlex]
-				aFree(sd->reg);
-				sd->reg = NULL;
-				sd->reg_num = 0;
-			}
-
-			if( sd->regstr ) {
-				int j;
-
-				for( j = 0; j < sd->regstr_num; ++j )
-					if( sd->regstr[j].data )
-						aFree(sd->regstr[j].data);
-
-				aFree(sd->regstr);
-				sd->regstr = NULL;
-				sd->regstr_num = 0;
-			}
-
 			if( sd->st && sd->st->state != RUN ) {// free attached scripts that are waiting
 				script_free_state(sd->st);
 				sd->st = NULL;

部分文件因为文件数量过多而无法显示