瀏覽代碼

2014 Client Support
* Implemented the Roulette Game feature.
* Implemented per-character sex change feature.
* More info: https://rathena.org/board/topic/102247-2014-client-support/
- Special thanks to HerculesWS/Hercules@239d480 for the base.

aleos89 10 年之前
父節點
當前提交
3d9c6e7763

+ 5 - 0
conf/battle/feature.conf

@@ -57,3 +57,8 @@ feature.autotrade_sit: 1
 
 
 // Delay in miliseconds to open vending/buyingsotre after player logged in.
 // Delay in miliseconds to open vending/buyingsotre after player logged in.
 feature.autotrade_open_delay: 5000
 feature.autotrade_open_delay: 5000
+
+// Roulette (Note 1)
+// Requires: 2014-10-22bRagexe or later
+// Off by default while test version is out; enable at your own risk.
+feature.roulette: off

+ 1 - 0
conf/inter_athena.conf

@@ -139,6 +139,7 @@ mapreg_db: mapreg
 vending_db: vendings
 vending_db: vendings
 vending_items_db: vending_items
 vending_items_db: vending_items
 market_table: market
 market_table: market
+db_roulette_table: db_roulette
 
 
 // Use SQL item_db, mob_db and mob_skill_db for the map server? (yes/no)
 // Use SQL item_db, mob_db and mob_skill_db for the map server? (yes/no)
 use_sql_db: no
 use_sql_db: no

+ 23 - 22
conf/log_athena.conf

@@ -8,31 +8,32 @@
 //--------------------------------------------------------------
 //--------------------------------------------------------------
 
 
 // Enable Logs? (Note 3)
 // Enable Logs? (Note 3)
-// 0x00000 - Don't log at all
-// 0x00001 - (T) Log trades
-// 0x00002 - (V) Log vending transactions
-// 0x00004 - (P) Log items drop/picked by players
-// 0x00008 - (L) Log items drop/looted by monsters
-// 0x00010 - (S) Log NPC transactions (buy/sell)
-// 0x00020 - (N) Log Script transactions (items deleted/acquired through quests)
-// 0x00040 - (D) Log items stolen from mobs (Steal/Gank)
-// 0x00080 - (C) Log player-used items (consumables/pet&hom&merc food/items used for skills&attacks)
-// 0x00100 - (O) Log produced/ingredient items
-// 0x00200 - (U) Log MVP prize items
-// 0x00400 - (A) Log player created/deleted items (through @/# commands)
-// 0x00800 - (R) Log items placed/retrieved from storage.
-// 0x01000 - (G) Log items placed/retrieved from guild storage.
-// 0x02000 - (E) Log mail system transactions.
-// 0x04000 - (I) Log auction system transactions.
-// 0x08000 - (B) Log buying store transactions
-// 0x10000 - (X) Log all other transactions (rentals expiring/inserting cards/items removed by item_check/
+// 0x000000 - Don't log at all
+// 0x000001 - (T) Log trades
+// 0x000002 - (V) Log vending transactions
+// 0x000004 - (P) Log items drop/picked by players
+// 0x000008 - (L) Log items drop/looted by monsters
+// 0x000010 - (S) Log NPC transactions (buy/sell)
+// 0x000020 - (N) Log Script transactions (items deleted/acquired through quests)
+// 0x000040 - (D) Log items stolen from mobs (Steal/Gank)
+// 0x000080 - (C) Log player-used items (consumables/pet&hom&merc food/items used for skills&attacks)
+// 0x000100 - (O) Log produced/ingredient items
+// 0x000200 - (U) Log MVP prize items
+// 0x000400 - (A) Log player created/deleted items (through @/# commands)
+// 0x000800 - (R) Log items placed/retrieved from storage.
+// 0x001000 - (G) Log items placed/retrieved from guild storage.
+// 0x002000 - (E) Log mail system transactions.
+// 0x004000 - (I) Log auction system transactions.
+// 0x008000 - (B) Log buying store transactions
+// 0x010000 - (X) Log all other transactions (rentals expiring/inserting cards/items removed by item_check/
 //           rings deleted by divorce/pet egg (un)hatching/pet armor (un)equipping/Weapon Refine skill/Remove Trap skill)
 //           rings deleted by divorce/pet egg (un)hatching/pet armor (un)equipping/Weapon Refine skill/Remove Trap skill)
-// 0x20000 - ($) Log cash transactions
-// 0x40000 - (K) Log account bank transactions
-// 0x80000 - (F) Removed bound items when guild/party is broken
+// 0x020000 - ($) Log cash transactions
+// 0x040000 - (K) Log account bank transactions
+// 0x080000 - (F) Removed bound items when guild/party is broken
+// 0x100000 - (Y) Roulette Lottery
 // Example: Log trades+vending+script items+created items: 1+2+32+1024 = 1059
 // Example: Log trades+vending+script items+created items: 1+2+32+1024 = 1059
 // Please note that moving items from inventory to cart and back is not logged by design.
 // Please note that moving items from inventory to cart and back is not logged by design.
-enable_logs: 0xFFFFF
+enable_logs: 0xFFFFFF
 
 
 // Use MySQL Logs? [SQL Version Only] (Note 1)
 // Use MySQL Logs? [SQL Version Only] (Note 1)
 sql_logs: yes
 sql_logs: yes

+ 3 - 0
conf/msg_conf/map_msg.conf

@@ -1593,5 +1593,8 @@
 1495: You can't withdraw that much money
 1495: You can't withdraw that much money
 1496: Banking is disabled
 1496: Banking is disabled
 
 
+// Roulette
+1497: Roulette is disabled
+
 //Custom translations
 //Custom translations
 //import: conf/msg_conf/import/map_msg_eng_conf.txt
 //import: conf/msg_conf/import/map_msg_eng_conf.txt

+ 3 - 0
db/const.txt

@@ -432,6 +432,9 @@ CharMoves	124	1
 CharRename	125	1
 CharRename	125	1
 Font		126	1
 Font		126	1
 BankVault	127	1
 BankVault	127	1
+RouletteBronze	128	1
+RouletteSilver	129	1
+RouletteGold	130	1
 
 
 bMaxHP	6
 bMaxHP	6
 bMaxSP	8
 bMaxSP	8

+ 78 - 1
db/packet_db.txt

@@ -2318,5 +2318,82 @@ packet_keys: 0x631C511C,0x111C111C,0x111C111C // [Shakto]
 0x09D8,2,npcmarketclosed,0
 0x09D8,2,npcmarketclosed,0
 0x09DF,7
 0x09DF,7
 
 
+//2014-10-16Ragexe
+packet_ver: 50
+0x0369,7,actionrequest,2,6
+0x083C,10,useskilltoid,2,4,6
+0x0437,5,walktoxy,2
+0x035F,6,ticksend,2
+0x0967,5,changedir,2,4
+0x07E4,6,takeitem,2
+0x0362,6,dropitem,2,4
+0x07EC,8,movetokafra,2,4
+0x022D,8,movefromkafra,2,4
+0x0438,10,useskilltopos,2,4,6,8
+0x0366,90,useskilltoposinfo,2,4,6,8,10
+0x096A,6,getcharnamerequest,2
+0x0368,6,solvecharname,2
+0x0838,12,searchstoreinfolistitemclick,2,6,10
+0x0835,2,searchstoreinfonextpage,0
+0x0819,-1,searchstoreinfo,2,4,5,9,13,14,15
+0x0811,-1,reqtradebuyingstore,2,4,8,12
+0x0360,6,reqclickbuyingstore,2
+0x0817,2,reqclosebuyingstore,0
+0x0815,-1,reqopenbuyingstore,2,4,8,9,89
+0x0365,18,bookingregreq,2,4
+// 0x0363,8 // CZ_JOIN_BATTLE_FIELD
+0x0281,-1,itemlistwindowselected,2,4,8
+0x086E,19,wanttoconnection,2,6,10,14,18
+0x0802,26,partyinvite,2
+// 0x0922,4 // CZ_GANGSI_RANK
+0x094B,26,friendslistadd,2
+0x0364,5,hommenu,2,4
+0x0936,36,storagepassword,0
+0x09DF,7
+0x0a00,269
+// Roulette System [Yommy]
+0x0A19,2,rouletteopen,0 // HEADER_CZ_REQ_OPEN_ROULETTE
+0x0A1A,23 // HEADER_ZC_ACK_OPEN_ROULETTE
+0x0A1B,2,rouletteinfo,0 // HEADER_CZ_REQ_ROULETTE_INFO
+0x0A1C,-1 // HEADER_ZC_ACK_ROULETTE_INFO
+0x0A1D,2,rouletteclose,0 // HEADER_CZ_REQ_CLOSE_ROULETTE
+0x0A1E,3 // HEADER_ZC_ACK_CLOSE_ROULETTE
+0x0A1F,2,roulettegenerate,0 // HEADER_CZ_REQ_GENERATE_ROULETTE
+0x0A20,21 // HEADER_ZC_ACK_GENERATE_ROULETTE
+0x0A21,3,rouletterecvitem,2 // HEADER_CZ_RECV_ROULETTE_ITEM
+0x0A22,5 // HEADER_ZC_RECV_ROULETTE_ITEM
+
+//2014-10-22bRagexe
+packet_ver: 51
+0x0369,7,actionrequest,2,6
+0x083C,10,useskilltoid,2,4,6
+0x0437,5,walktoxy,2
+0x035F,6,ticksend,2
+0x08AD,5,changedir,2,4
+0x094E,6,takeitem,2
+0x087D,6,dropitem,2,4
+0x0878,8,movetokafra,2,4
+0x08AA,8,movefromkafra,2,4
+0x023B,10,useskilltopos,2,4,6,8
+0x0366,90,useskilltoposinfo,2,4,6,8,10
+0x096A,6,getcharnamerequest,2
+0x0368,6,solvecharname,2
+0x0835,12,searchstoreinfolistitemclick,2,6,10
+0x0940,2,searchstoreinfonextpage,0
+0x0819,-1,searchstoreinfo,2,4,5,9,13,14,15
+0x0811,-1,reqtradebuyingstore,2,4,8,12
+0x0360,6,reqclickbuyingstore,2
+0x0817,2,reqclosebuyingstore,0
+0x0815,-1,reqopenbuyingstore,2,4,8,9,89
+0x0955,18,bookingregreq,2,4
+// 0x092B,8 // CZ_JOIN_BATTLE_FIELD
+0x0281,-1,itemlistwindowselected,2,4,8
+0x093B,19,wanttoconnection,2,6,10,14,18
+0x0896,26,partyinvite,2
+// 0x08AB,4 // CZ_GANGSI_RANK
+0x091A,26,friendslistadd,2
+0x0899,5,hommenu,2,4
+0x0438,36,storagepassword,0
+
 //Add new packets here
 //Add new packets here
-//packet_ver: 47
+//packet_ver: 52

+ 3 - 3
db/re/item_db.txt

@@ -165,11 +165,11 @@
 668,Handsei,Red Envelope,2,0,,20,,,,,0xFFFFFFFF,63,2,,,,,,{ set Zeny,Zeny+rand(1000,10000); },{},{}
 668,Handsei,Red Envelope,2,0,,20,,,,,0xFFFFFFFF,63,2,,,,,,{ set Zeny,Zeny+rand(1000,10000); },{},{}
 669,Rice_Cake_Soup,Tempting Rice-Cake Soup,0,500,,100,,,,,0xFFFFFFFF,63,2,,,,,,{ percentheal -100,-100; },{},{}
 669,Rice_Cake_Soup,Tempting Rice-Cake Soup,0,500,,100,,,,,0xFFFFFFFF,63,2,,,,,,{ percentheal -100,-100; },{},{}
 670,Gold_Coin_Moneybag,Bag of Gold Coins,3,100000,,400,,,,,0xFFFFFFFF,63,2,,,,,,{},{},{}
 670,Gold_Coin_Moneybag,Bag of Gold Coins,3,100000,,400,,,,,0xFFFFFFFF,63,2,,,,,,{},{},{}
-671,Gold_Coin,Gold Coin,3,10000,,0,,,,,0xFFFFFFFF,63,2,,,,,,{ /*goldpoint++; (For Roulette game)*/ },{},{}
+671,Gold_Coin,Gold Coin,3,10000,,0,,,,,0xFFFFFFFF,63,2,,,,,,{ RouletteGold++; },{},{}
 672,Copper_Coin_Moneybag,Bag of Bronze Coins,3,1000,,400,,,,,0xFFFFFFFF,63,2,,,,,,{},{},{}
 672,Copper_Coin_Moneybag,Bag of Bronze Coins,3,1000,,400,,,,,0xFFFFFFFF,63,2,,,,,,{},{},{}
-673,Copper_Coin,Bronze Coin,3,100,,40,,,,,0xFFFFFFFF,63,2,,,,,,{},{},{}
+673,Copper_Coin,Bronze Coin,3,100,,40,,,,,0xFFFFFFFF,63,2,,,,,,{ RouletteBronze++; },{},{}
 674,Mithril_Coin,Mithril Coin,3,5000,,40,,,,,0xFFFFFFFF,63,2,,,,,,{},{},{}
 674,Mithril_Coin,Mithril Coin,3,5000,,40,,,,,0xFFFFFFFF,63,2,,,,,,{},{},{}
-675,Silver_Coin,Silver Coin,3,5000,,0,,,,,0xFFFFFFFF,63,2,,,,,,{ /*silverpoint++; (For Roulette game)*/ },{},{}
+675,Silver_Coin,Silver Coin,3,5000,,0,,,,,0xFFFFFFFF,63,2,,,,,,{ RouletteSilver++; },{},{}
 676,Silver_Coin_Moneybag,Bag of Silver Coins,3,50000,,400,,,,,0xFFFFFFFF,63,2,,,,,,{},{},{}
 676,Silver_Coin_Moneybag,Bag of Silver Coins,3,50000,,400,,,,,0xFFFFFFFF,63,2,,,,,,{},{},{}
 677,White_Gold_Coin,Platinum Coin,3,2000,,40,,,,,0xFFFFFFFF,63,2,,,,,,{},{},{}
 677,White_Gold_Coin,Platinum Coin,3,2000,,40,,,,,0xFFFFFFFF,63,2,,,,,,{},{},{}
 678,Poison_Bottle,Poison Bottle,2,5000,,100,,,,,0xFFFFFFFF,63,2,,,,,,{ if(Class == Job_Assassin_Cross || Class == Job_Guillotine_Cross || Class == Job_Guillotine_Cross_T) { sc_start SC_DPOISON,60000,0; sc_start SC_ASPDPOTION3,60000,9; } else percentheal -100,-100; },{},{}
 678,Poison_Bottle,Poison Bottle,2,5000,,100,,,,,0xFFFFFFFF,63,2,,,,,,{ if(Class == Job_Assassin_Cross || Class == Job_Guillotine_Cross || Class == Job_Guillotine_Cross_T) { sc_start SC_DPOISON,60000,0; sc_start SC_ASPDPOTION3,60000,9; } else percentheal -100,-100; },{},{}

+ 14 - 0
doc/script_commands.txt

@@ -4169,6 +4169,20 @@ they will also have their skills reset upon 'changesex'.
 
 
 ---------------------------------------
 ---------------------------------------
 
 
+*changecharsex({<char_id>});
+
+This command will change the gender of the attached character. If it 
+was male, it will become female, if it was female, it will become male. The 
+change will be written to the character server, the player will receive the 
+message: "Need disconnection to perform change-sex request..." and the player 
+will be immediately kicked to the login screen. When they log back in, they will 
+be the opposite sex.
+
+If the character being changed is a Dancer/Gypsy or Bard/Clown class type, 
+the character will also have their skills reset upon 'changecharsex'.
+
+---------------------------------------
+
 *getexp <base xp>,<job xp>{,<char_id>};
 *getexp <base xp>,<job xp>{,<char_id>};
 
 
 This command will give the invoking character a specified number of base and job 
 This command will give the invoking character a specified number of base and job 

+ 66 - 0
sql-files/main.sql

@@ -125,6 +125,7 @@ CREATE TABLE IF NOT EXISTS `char` (
   `unban_time` int(11) unsigned NOT NULL default '0',
   `unban_time` int(11) unsigned NOT NULL default '0',
   `font` tinyint(3) unsigned NOT NULL default '0',
   `font` tinyint(3) unsigned NOT NULL default '0',
   `uniqueitem_counter` int(11) unsigned NOT NULL default '0',
   `uniqueitem_counter` int(11) unsigned NOT NULL default '0',
+  `sex` ENUM('M','F','U') NOT NULL default 'U',
   PRIMARY KEY  (`char_id`),
   PRIMARY KEY  (`char_id`),
   UNIQUE KEY `name_key` (`name`),
   UNIQUE KEY `name_key` (`name`),
   KEY `account_id` (`account_id`),
   KEY `account_id` (`account_id`),
@@ -637,6 +638,71 @@ CREATE TABLE IF NOT EXISTS `ragsrvinfo` (
   `drop` int(11) unsigned NOT NULL default '0'
   `drop` int(11) unsigned NOT NULL default '0'
 ) ENGINE=MyISAM;
 ) ENGINE=MyISAM;
 
 
+--
+-- Table structure for `db_roulette`
+--
+CREATE TABLE `db_roulette` (
+  `level` smallint(5) unsigned NOT NULL,
+  `item_id` smallint(5) unsigned NOT NULL,
+  `amount` smallint(5) unsigned NOT NULL DEFAULT '1',
+  `flag` smallint(5) unsigned NOT NULL DEFAULT '1',
+  PRIMARY KEY (`level`,`item_id`)
+) ENGINE=MyISAM;
+
+-- ----------------------------
+-- Records of db_roulette
+-- ----------------------------
+-- Info: http://ro.gnjoy.com/news/update/View.asp?seq=157&curpage=1
+
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 1, 675, 1, 1 ); -- Silver_Coin
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 1, 671, 1, 0 ); -- Gold_Coin
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 1, 678, 1, 0 ); -- Poison_Bottle
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 1, 604, 1, 0 ); -- Branch_Of_Dead_Tree
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 1, 522, 1, 0 ); -- Fruit_Of_Mastela
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 1, 671, 1, 0 ); -- Old_Ore_Box
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 1, 12523, 1, 0 ); -- E_Inc_Agi_10_Scroll
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 1, 985, 1, 0 ); -- Elunium
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 1, 984, 1, 0 ); -- Oridecon
+
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 2, 675, 1, 1 ); -- Silver_Coin
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 2, 671, 1, 0 ); -- Gold_Coin
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 2, 603, 1, 0 ); -- Old_Blue_Box
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 2, 608, 1, 0 ); -- Seed_Of_Yggdrasil
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 2, 607, 1, 0 ); -- Yggdrasilberry
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 2, 12522, 1, 0 ); -- E_Blessing_10_Scroll
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 2, 6223, 1, 0 ); -- Carnium
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 2, 6224, 1, 0 ); -- Bradium
+
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 3, 675, 1, 1 ); -- Silver_Coin
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 3, 671, 1, 0 ); -- Gold_Coin
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 3, 12108, 1, 0 ); -- Bundle_Of_Magic_Scroll
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 3, 617, 1, 0 ); -- Old_Violet_Box
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 3, 12514, 1, 0 ); -- E_Abrasive
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 3, 7444, 1, 0 ); -- Treasure_Box
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 3, 969, 1, 0 ); -- Gold
+
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 4, 675, 1, 1 ); -- Silver_Coin
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 4, 671, 1, 0 ); -- Gold_Coin
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 4, 616, 1, 0 ); -- Old_Card_Album
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 4, 12516, 1, 0 ); -- E_Small_Life_Potion
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 4, 22777, 1, 0 ); -- Gift_Buff_Set
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 4, 6231, 1, 0 ); -- Guarantee_Weapon_6Up
+
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 5, 671, 1, 1 ); -- Gold_Coin
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 5, 12246, 1, 0 ); -- Magic_Card_Album
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 5, 12263, 1, 0 ); -- Comp_Battle_Manual
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 5, 671, 1, 0 ); -- Potion_Box
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 5, 6235, 1, 0 ); -- Guarantee_Armor_6Up
+
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 6, 671, 1, 1 ); -- Gold_Coin
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 6, 12766, 1, 0 ); -- Reward_Job_BM25
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 6, 6234, 1, 0 ); -- Guarantee_Armor_7Up
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 6, 6233, 1, 0 ); -- Guarantee_Armor_8Up
+
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 7, 671, 1, 1 ); -- Gold_Coin
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 7, 6233, 1, 0 ); -- Guarantee_Armor_8Up
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 7, 6233, 1, 0 ); -- Guarantee_Armor_8Up	// KRO lists this twice
+
 --
 --
 -- Table structure for table `skill`
 -- Table structure for table `skill`
 --
 --

+ 63 - 0
sql-files/upgrades/upgrade_20150619.sql

@@ -0,0 +1,63 @@
+ALTER TABLE  `char` ADD COLUMN `sex` ENUM('M','F','U') NOT NULL default 'U';
+
+CREATE TABLE `db_roulette` (
+  `level` smallint(5) unsigned NOT NULL,
+  `item_id` smallint(5) unsigned NOT NULL,
+  `amount` smallint(5) unsigned NOT NULL DEFAULT '1',
+  `flag` smallint(5) unsigned NOT NULL DEFAULT '1',
+  PRIMARY KEY (`level`,`item_id`)
+) ENGINE=MyISAM;
+
+-- ----------------------------
+-- Records of db_roulette
+-- ----------------------------
+-- Info: http://ro.gnjoy.com/news/update/View.asp?seq=157&curpage=1
+
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 1, 675, 1, 1 ); -- Silver_Coin
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 1, 671, 1, 0 ); -- Gold_Coin
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 1, 678, 1, 0 ); -- Poison_Bottle
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 1, 604, 1, 0 ); -- Branch_Of_Dead_Tree
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 1, 522, 1, 0 ); -- Fruit_Of_Mastela
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 1, 671, 1, 0 ); -- Old_Ore_Box
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 1, 12523, 1, 0 ); -- E_Inc_Agi_10_Scroll
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 1, 985, 1, 0 ); -- Elunium
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 1, 984, 1, 0 ); -- Oridecon
+
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 2, 675, 1, 1 ); -- Silver_Coin
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 2, 671, 1, 0 ); -- Gold_Coin
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 2, 603, 1, 0 ); -- Old_Blue_Box
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 2, 608, 1, 0 ); -- Seed_Of_Yggdrasil
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 2, 607, 1, 0 ); -- Yggdrasilberry
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 2, 12522, 1, 0 ); -- E_Blessing_10_Scroll
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 2, 6223, 1, 0 ); -- Carnium
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 2, 6224, 1, 0 ); -- Bradium
+
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 3, 675, 1, 1 ); -- Silver_Coin
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 3, 671, 1, 0 ); -- Gold_Coin
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 3, 12108, 1, 0 ); -- Bundle_Of_Magic_Scroll
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 3, 617, 1, 0 ); -- Old_Violet_Box
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 3, 12514, 1, 0 ); -- E_Abrasive
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 3, 7444, 1, 0 ); -- Treasure_Box
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 3, 969, 1, 0 ); -- Gold
+
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 4, 675, 1, 1 ); -- Silver_Coin
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 4, 671, 1, 0 ); -- Gold_Coin
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 4, 616, 1, 0 ); -- Old_Card_Album
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 4, 12516, 1, 0 ); -- E_Small_Life_Potion
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 4, 22777, 1, 0 ); -- Gift_Buff_Set
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 4, 6231, 1, 0 ); -- Guarantee_Weapon_6Up
+
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 5, 671, 1, 1 ); -- Gold_Coin
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 5, 12246, 1, 0 ); -- Magic_Card_Album
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 5, 12263, 1, 0 ); -- Comp_Battle_Manual
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 5, 671, 1, 0 ); -- Potion_Box
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 5, 6235, 1, 0 ); -- Guarantee_Armor_6Up
+
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 6, 671, 1, 1 ); -- Gold_Coin
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 6, 12766, 1, 0 ); -- Reward_Job_BM25
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 6, 6234, 1, 0 ); -- Guarantee_Armor_7Up
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 6, 6233, 1, 0 ); -- Guarantee_Armor_8Up
+
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 7, 671, 1, 1 ); -- Gold_Coin
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 7, 6233, 1, 0 ); -- Guarantee_Armor_8Up
+INSERT INTO `db_roulette`(`level`, `item_id`, `amount`, `flag` ) VALUES ( 7, 6233, 1, 0 ); -- Guarantee_Armor_8Up	// KRO lists this twice

+ 1 - 0
sql-files/upgrades/upgrade_20150619_log.sql

@@ -0,0 +1 @@
+ALTER TABLE `picklog` MODIFY `type` enum('M','P','L','T','V','S','N','C','A','R','G','E','B','O','I','X','D','U','$','F','Y') NOT NULL default 'P',

+ 69 - 6
src/char/char.c

@@ -837,6 +837,54 @@ int char_inventory_to_sql(const struct item items[], int max, int id) {
 	return errors;
 	return errors;
 }
 }
 
 
+/**
+ * Returns the correct gender ID for the given character and enum value.
+ *
+ * If the per-character sex is defined but not supported by the current packetver, the database entries are corrected.
+ *
+ * @param sd Character data, if available.
+ * @param p  Character status.
+ * @param sex Character sex (database enum)
+ *
+ * @retval SEX_MALE if the per-character sex is male
+ * @retval SEX_FEMALE if the per-character sex is female
+ * @retval 99 if the per-character sex is not defined or the current PACKETVER doesn't support it.
+ */
+int char_mmo_gender(const struct char_session_data *sd, const struct mmo_charstatus *p, char sex)
+{
+#if PACKETVER >= 20141016
+	(void)sd; (void)p; // Unused
+	switch (sex) {
+		case 'M':
+			return SEX_MALE;
+		case 'F':
+			return SEX_FEMALE;
+		case 'U':
+		default:
+			return 99;
+	}
+#else
+	if (sex == 'M' || sex == 'F') {
+		if (!sd) {
+			// sd is not available, there isn't much we can do. Just return and print a warning.
+			ShowWarning("Character '%s' (CID: %d, AID: %d) has sex '%c', but PACKETVER does not support per-character sex. Defaulting to 'U'.\n",
+					p->name, p->char_id, p->account_id, sex);
+			return 99;
+		}
+		if ((sex == 'M' && sd->sex == SEX_FEMALE)
+		 || (sex == 'F' && sd->sex == SEX_MALE)) {
+			ShowWarning("Changing sex of character '%s' (CID: %d, AID: %d) to 'U' due to incompatible PACKETVER.\n", p->name, p->char_id, p->account_id);
+			chlogif_parse_ackchangecharsex(p->char_id, sd->sex);
+		} else {
+			ShowInfo("Resetting sex of character '%s' (CID: %d, AID: %d) to 'U' due to incompatible PACKETVER.\n", p->name, p->char_id, p->account_id);
+		}
+		if (SQL_ERROR == Sql_Query(sql_handle, "UPDATE `%s` SET `sex` = 'U' WHERE `char_id` = '%d'", schema_config.char_db, p->char_id)) {
+			Sql_ShowDebug(sql_handle);
+		}
+	}
+	return 99;
+#endif
+}
 
 
 int char_mmo_char_tobuf(uint8* buf, struct mmo_charstatus* p);
 int char_mmo_char_tobuf(uint8* buf, struct mmo_charstatus* p);
 
 
@@ -847,16 +895,16 @@ int char_mmo_chars_fromsql(struct char_session_data* sd, uint8* buf) {
 	struct mmo_charstatus p;
 	struct mmo_charstatus p;
 	int j = 0, i;
 	int j = 0, i;
 	char last_map[MAP_NAME_LENGTH_EXT];
 	char last_map[MAP_NAME_LENGTH_EXT];
+	char sex[2];
 
 
 	stmt = SqlStmt_Malloc(sql_handle);
 	stmt = SqlStmt_Malloc(sql_handle);
-	if( stmt == NULL )
-	{
+	if( stmt == NULL ) {
 		SqlStmt_ShowDebug(stmt);
 		SqlStmt_ShowDebug(stmt);
 		return 0;
 		return 0;
 	}
 	}
 	memset(&p, 0, sizeof(p));
 	memset(&p, 0, sizeof(p));
 
 
-	for( i = 0; i < MAX_CHARS; i++ ){
+	for( i = 0; i < MAX_CHARS; i++ ) {
 		sd->found_char[i] = -1;
 		sd->found_char[i] = -1;
 		sd->unban_time[i] = 0;
 		sd->unban_time[i] = 0;
 	}
 	}
@@ -867,7 +915,7 @@ int char_mmo_chars_fromsql(struct char_session_data* sd, uint8* buf) {
 		"`str`,`agi`,`vit`,`int`,`dex`,`luk`,`max_hp`,`hp`,`max_sp`,`sp`,"
 		"`str`,`agi`,`vit`,`int`,`dex`,`luk`,`max_hp`,`hp`,`max_sp`,`sp`,"
 		"`status_point`,`skill_point`,`option`,`karma`,`manner`,`hair`,`hair_color`,"
 		"`status_point`,`skill_point`,`option`,`karma`,`manner`,`hair`,`hair_color`,"
 		"`clothes_color`,`weapon`,`shield`,`head_top`,`head_mid`,`head_bottom`,`last_map`,`rename`,`delete_date`,"
 		"`clothes_color`,`weapon`,`shield`,`head_top`,`head_mid`,`head_bottom`,`last_map`,`rename`,`delete_date`,"
-		"`robe`,`moves`,`unban_time`,`font`,`uniqueitem_counter`"
+		"`robe`,`moves`,`unban_time`,`font`,`uniqueitem_counter`,`sex`"
 		" FROM `%s` WHERE `account_id`='%d' AND `char_num` < '%d'", schema_config.char_db, sd->account_id, MAX_CHARS)
 		" FROM `%s` WHERE `account_id`='%d' AND `char_num` < '%d'", schema_config.char_db, sd->account_id, MAX_CHARS)
 	||	SQL_ERROR == SqlStmt_Execute(stmt)
 	||	SQL_ERROR == SqlStmt_Execute(stmt)
 	||	SQL_ERROR == SqlStmt_BindColumn(stmt, 0,  SQLDT_INT,    &p.char_id, 0, NULL, NULL)
 	||	SQL_ERROR == SqlStmt_BindColumn(stmt, 0,  SQLDT_INT,    &p.char_id, 0, NULL, NULL)
@@ -910,6 +958,7 @@ int char_mmo_chars_fromsql(struct char_session_data* sd, uint8* buf) {
 	||	SQL_ERROR == SqlStmt_BindColumn(stmt, 37, SQLDT_LONG,   &p.unban_time, 0, NULL, NULL)
 	||	SQL_ERROR == SqlStmt_BindColumn(stmt, 37, SQLDT_LONG,   &p.unban_time, 0, NULL, NULL)
 	||	SQL_ERROR == SqlStmt_BindColumn(stmt, 38, SQLDT_UCHAR,  &p.font, 0, NULL, NULL)
 	||	SQL_ERROR == SqlStmt_BindColumn(stmt, 38, SQLDT_UCHAR,  &p.font, 0, NULL, NULL)
 	||	SQL_ERROR == SqlStmt_BindColumn(stmt, 39, SQLDT_UINT,   &p.uniqueitem_counter, 0, NULL, NULL)
 	||	SQL_ERROR == SqlStmt_BindColumn(stmt, 39, SQLDT_UINT,   &p.uniqueitem_counter, 0, NULL, NULL)
+	||	SQL_ERROR == SqlStmt_BindColumn(stmt, 40, SQLDT_ENUM,   &sex, sizeof(sex), NULL, NULL)
 	)
 	)
 	{
 	{
 		SqlStmt_ShowDebug(stmt);
 		SqlStmt_ShowDebug(stmt);
@@ -922,6 +971,7 @@ int char_mmo_chars_fromsql(struct char_session_data* sd, uint8* buf) {
 		p.last_point.map = mapindex_name2id(last_map);
 		p.last_point.map = mapindex_name2id(last_map);
 		sd->found_char[p.slot] = p.char_id;
 		sd->found_char[p.slot] = p.char_id;
 		sd->unban_time[p.slot] = p.unban_time;
 		sd->unban_time[p.slot] = p.unban_time;
+		p.sex = char_mmo_gender(sd, &p, sex[0]);
 		j += char_mmo_char_tobuf(WBUFP(buf, j), &p);
 		j += char_mmo_char_tobuf(WBUFP(buf, j), &p);
 
 
 		// Addon System
 		// Addon System
@@ -954,6 +1004,7 @@ int char_mmo_char_fromsql(uint32 char_id, struct mmo_charstatus* p, bool load_ev
 	int hotkey_num;
 	int hotkey_num;
 #endif
 #endif
 	StringBuf msg_buf;
 	StringBuf msg_buf;
+	char sex[2];
 
 
 	memset(p, 0, sizeof(struct mmo_charstatus));
 	memset(p, 0, sizeof(struct mmo_charstatus));
 
 
@@ -973,7 +1024,7 @@ int char_mmo_char_fromsql(uint32 char_id, struct mmo_charstatus* p, bool load_ev
 		"`status_point`,`skill_point`,`option`,`karma`,`manner`,`party_id`,`guild_id`,`pet_id`,`homun_id`,`elemental_id`,`hair`,"
 		"`status_point`,`skill_point`,`option`,`karma`,`manner`,`party_id`,`guild_id`,`pet_id`,`homun_id`,`elemental_id`,`hair`,"
 		"`hair_color`,`clothes_color`,`weapon`,`shield`,`head_top`,`head_mid`,`head_bottom`,`last_map`,`last_x`,`last_y`,"
 		"`hair_color`,`clothes_color`,`weapon`,`shield`,`head_top`,`head_mid`,`head_bottom`,`last_map`,`last_x`,`last_y`,"
 		"`save_map`,`save_x`,`save_y`,`partner_id`,`father`,`mother`,`child`,`fame`,`rename`,`delete_date`,`robe`, `moves`,"
 		"`save_map`,`save_x`,`save_y`,`partner_id`,`father`,`mother`,`child`,`fame`,`rename`,`delete_date`,`robe`, `moves`,"
-		"`unban_time`,`font`,`uniqueitem_counter`"
+		"`unban_time`,`font`,`uniqueitem_counter`,`sex`"
 		" FROM `%s` WHERE `char_id`=? LIMIT 1", schema_config.char_db)
 		" FROM `%s` WHERE `char_id`=? LIMIT 1", schema_config.char_db)
 	||	SQL_ERROR == SqlStmt_BindParam(stmt, 0, SQLDT_INT, &char_id, 0)
 	||	SQL_ERROR == SqlStmt_BindParam(stmt, 0, SQLDT_INT, &char_id, 0)
 	||	SQL_ERROR == SqlStmt_Execute(stmt)
 	||	SQL_ERROR == SqlStmt_Execute(stmt)
@@ -1033,6 +1084,7 @@ int char_mmo_char_fromsql(uint32 char_id, struct mmo_charstatus* p, bool load_ev
 	||	SQL_ERROR == SqlStmt_BindColumn(stmt, 53, SQLDT_LONG,   &p->unban_time, 0, NULL, NULL)
 	||	SQL_ERROR == SqlStmt_BindColumn(stmt, 53, SQLDT_LONG,   &p->unban_time, 0, NULL, NULL)
 	||	SQL_ERROR == SqlStmt_BindColumn(stmt, 54, SQLDT_UCHAR,  &p->font, 0, NULL, NULL)
 	||	SQL_ERROR == SqlStmt_BindColumn(stmt, 54, SQLDT_UCHAR,  &p->font, 0, NULL, NULL)
 	||	SQL_ERROR == SqlStmt_BindColumn(stmt, 55, SQLDT_UINT,   &p->uniqueitem_counter, 0, NULL, NULL)
 	||	SQL_ERROR == SqlStmt_BindColumn(stmt, 55, SQLDT_UINT,   &p->uniqueitem_counter, 0, NULL, NULL)
+	||	SQL_ERROR == SqlStmt_BindColumn(stmt, 56, SQLDT_ENUM,   &sex, sizeof(sex), NULL, NULL)
 	)
 	)
 	{
 	{
 		SqlStmt_ShowDebug(stmt);
 		SqlStmt_ShowDebug(stmt);
@@ -1045,6 +1097,7 @@ int char_mmo_char_fromsql(uint32 char_id, struct mmo_charstatus* p, bool load_ev
 		SqlStmt_Free(stmt);
 		SqlStmt_Free(stmt);
 		return 0;
 		return 0;
 	}
 	}
+	p->sex = char_mmo_gender(NULL, p, sex[0]);
 	p->last_point.map = mapindex_name2id(last_map);
 	p->last_point.map = mapindex_name2id(last_map);
 	p->save_point.map = mapindex_name2id(save_map);
 	p->save_point.map = mapindex_name2id(save_map);
 
 
@@ -1669,7 +1722,13 @@ int char_mmo_char_tobuf(uint8* buffer, struct mmo_charstatus* p)
 	WBUFW(buf,48) = min(p->max_sp, INT16_MAX);
 	WBUFW(buf,48) = min(p->max_sp, INT16_MAX);
 	WBUFW(buf,50) = DEFAULT_WALK_SPEED; // p->speed;
 	WBUFW(buf,50) = DEFAULT_WALK_SPEED; // p->speed;
 	WBUFW(buf,52) = p->class_;
 	WBUFW(buf,52) = p->class_;
+#if PACKETVER >= 20141022
+	WBUFL(buf,54) = p->hair;
+	offset+=2;
+	buf = WBUFP(buffer,offset);
+#else
 	WBUFW(buf,54) = p->hair;
 	WBUFW(buf,54) = p->hair;
+#endif
 
 
 	//When the weapon is sent and your option is riding, the client crashes on login!?
 	//When the weapon is sent and your option is riding, the client crashes on login!?
 	WBUFW(buf,56) = p->option&(0x20|0x80000|0x100000|0x200000|0x400000|0x800000|0x1000000|0x2000000|0x4000000|0x8000000) ? 0 : p->weapon;
 	WBUFW(buf,56) = p->option&(0x20|0x80000|0x100000|0x200000|0x400000|0x800000|0x1000000|0x2000000|0x4000000|0x8000000) ? 0 : p->weapon;
@@ -1723,6 +1782,10 @@ int char_mmo_char_tobuf(uint8* buffer, struct mmo_charstatus* p)
 		WBUFL(buf,136) = ( p->rename > 0 ) ? 1 : 0;  // (0 = disabled, otherwise displays "Add-Ons" sidebar)
 		WBUFL(buf,136) = ( p->rename > 0 ) ? 1 : 0;  // (0 = disabled, otherwise displays "Add-Ons" sidebar)
 		offset += 4;
 		offset += 4;
 	#endif
 	#endif
+	#if PACKETVER >= 20141016
+		WBUFB(buf,140) = p->sex;// sex - (0 = female, 1 = male, 99 = logindefined)
+		offset += 1;
+	#endif
 #endif
 #endif
 
 
 	return 106+offset;
 	return 106+offset;
@@ -2890,7 +2953,7 @@ int do_init(int argc, char **argv)
 	add_timer_func_list(char_online_data_cleanup, "online_data_cleanup");
 	add_timer_func_list(char_online_data_cleanup, "online_data_cleanup");
 	add_timer_interval(gettick() + 1000, char_online_data_cleanup, 0, 0, 600 * 1000);
 	add_timer_interval(gettick() + 1000, char_online_data_cleanup, 0, 0, 600 * 1000);
 
 
-	//chek db tables
+	//check db tables
 	if(charserv_config.char_check_db && char_checkdb() == 0){
 	if(charserv_config.char_check_db && char_checkdb() == 0){
 		ShowFatalError("char : A tables is missing in sql-server, please fix it, see (sql-files main.sql for structure) \n");
 		ShowFatalError("char : A tables is missing in sql-server, please fix it, see (sql-files main.sql for structure) \n");
 		exit(EXIT_FAILURE);
 		exit(EXIT_FAILURE);

+ 2 - 1
src/char/char.h

@@ -219,7 +219,7 @@ extern struct fame_list chemist_fame_list[MAX_FAME_LIST];
 extern struct fame_list taekwon_fame_list[MAX_FAME_LIST];
 extern struct fame_list taekwon_fame_list[MAX_FAME_LIST];
 
 
 #define DEFAULT_AUTOSAVE_INTERVAL 300*1000
 #define DEFAULT_AUTOSAVE_INTERVAL 300*1000
-#define MAX_CHAR_BUF 144 //Max size (for WFIFOHEAD calls)
+#define MAX_CHAR_BUF 150 //Max size (for WFIFOHEAD calls)
 
 
 int char_search_mapserver(unsigned short map, uint32 ip, uint16 port);
 int char_search_mapserver(unsigned short map, uint32 ip, uint16 port);
 int char_lan_subnetcheck(uint32 ip);
 int char_lan_subnetcheck(uint32 ip);
@@ -233,6 +233,7 @@ void char_set_all_offline(int id);
 void char_disconnect_player(uint32 account_id);
 void char_disconnect_player(uint32 account_id);
 int char_chardb_waiting_disconnect(int tid, unsigned int tick, int id, intptr_t data);
 int char_chardb_waiting_disconnect(int tid, unsigned int tick, int id, intptr_t data);
 
 
+int char_mmo_gender(const struct char_session_data *sd, const struct mmo_charstatus *p, char sex);
 int char_mmo_char_tobuf(uint8* buffer, struct mmo_charstatus* p);
 int char_mmo_char_tobuf(uint8* buffer, struct mmo_charstatus* p);
 int char_mmo_char_tosql(uint32 char_id, struct mmo_charstatus* p);
 int char_mmo_char_tosql(uint32 char_id, struct mmo_charstatus* p);
 int char_mmo_char_fromsql(uint32 char_id, struct mmo_charstatus* p, bool load_everything);
 int char_mmo_char_fromsql(uint32 char_id, struct mmo_charstatus* p, bool load_everything);

+ 2 - 1
src/char/char_clif.c

@@ -711,7 +711,8 @@ int chclif_parse_charselect(int fd, struct char_session_data* sd,uint32 ipl){
 
 
 		//Have to switch over to the DB instance otherwise data won't propagate [Kevin]
 		//Have to switch over to the DB instance otherwise data won't propagate [Kevin]
 		cd = (struct mmo_charstatus *)idb_get(char_db_, char_id);
 		cd = (struct mmo_charstatus *)idb_get(char_db_, char_id);
-		cd->sex = sd->sex;
+		if (cd->sex == 99)
+			cd->sex = sd->sex;
 
 
 		if (charserv_config.log_char) {
 		if (charserv_config.log_char) {
 			char esc_name[NAME_LENGTH*2+1];
 			char esc_name[NAME_LENGTH*2+1];

+ 105 - 54
src/char/char_logif.c

@@ -340,76 +340,79 @@ int chlogif_parse_keepalive(int fd, struct char_session_data* sd){
 	return 1;
 	return 1;
 }
 }
 
 
-int chlogif_parse_ackchangesex(int fd, struct char_session_data* sd){
+/**
+ * Performs the necessary operations when changing a character's sex, such as
+ * correcting the job class and unequipping items, and propagating the
+ * information to the guild data.
+ *
+ * @param sex      The new sex (SEX_MALE or SEX_FEMALE).
+ * @param acc      The character's account ID.
+ * @param char_id  The character ID.
+ * @param class_   The character's current job class.
+ * @param guild_id The character's guild ID.
+ */
+void chlogif_parse_change_sex_sub(int sex, int acc, int char_id, int class_, int guild_id)
+{
+	// job modification
+	if (class_ == JOB_BARD || class_ == JOB_DANCER)
+		class_ = (sex == SEX_MALE ? JOB_BARD : JOB_DANCER);
+	else if (class_ == JOB_CLOWN || class_ == JOB_GYPSY)
+		class_ = (sex == SEX_MALE ? JOB_CLOWN : JOB_GYPSY);
+	else if (class_ == JOB_BABY_BARD || class_ == JOB_BABY_DANCER)
+		class_ = (sex == SEX_MALE ? JOB_BABY_BARD : JOB_BABY_DANCER);
+	else if (class_ == JOB_MINSTREL || class_ == JOB_WANDERER)
+		class_ = (sex == SEX_MALE ? JOB_MINSTREL : JOB_WANDERER);
+	else if (class_ == JOB_MINSTREL_T || class_ == JOB_WANDERER_T)
+		class_ = (sex == SEX_MALE ? JOB_MINSTREL_T : JOB_WANDERER_T);
+	else if (class_ == JOB_BABY_MINSTREL || class_ == JOB_BABY_WANDERER)
+		class_ = (sex == SEX_MALE ? JOB_BABY_MINSTREL : JOB_BABY_WANDERER);
+	else if (class_ == JOB_KAGEROU || class_ == JOB_OBORO)
+		class_ = (sex == SEX_MALE ? JOB_KAGEROU : JOB_OBORO);
+
+	if (SQL_ERROR == Sql_Query(sql_handle, "UPDATE `%s` SET `equip` = '0' WHERE `char_id` = '%d'", schema_config.inventory_db, char_id))
+		Sql_ShowDebug(sql_handle);
+
+	if (SQL_ERROR == Sql_Query(sql_handle, "UPDATE `%s` SET `class` = '%d', `weapon` = '0', `shield` = '0', `head_top` = '0', `head_mid` = '0', `head_bottom` = '0' WHERE `char_id` = '%d'", schema_config.char_db, class_, char_id))
+		Sql_ShowDebug(sql_handle);
+	if (guild_id) // If there is a guild, update the guild_member data [Skotlex]
+		inter_guild_sex_changed(guild_id, acc, char_id, sex);
+}
+
+int chlogif_parse_ackchangesex(int fd, struct char_session_data* sd)
+{
 	if (RFIFOREST(fd) < 7)
 	if (RFIFOREST(fd) < 7)
 		return 0;
 		return 0;
 	{
 	{
 		unsigned char buf[7];
 		unsigned char buf[7];
-
 		int acc = RFIFOL(fd,2);
 		int acc = RFIFOL(fd,2);
 		int sex = RFIFOB(fd,6);
 		int sex = RFIFOB(fd,6);
 		RFIFOSKIP(fd,7);
 		RFIFOSKIP(fd,7);
 
 
-		if( acc > 0 )
-		{// TODO: Is this even possible?
-			uint32 char_id[MAX_CHARS];
-			int class_[MAX_CHARS];
-			int guild_id[MAX_CHARS];
-			unsigned char num, i;
-			char* data;
-			DBMap*  auth_db = char_get_authdb();
-
+		if (acc > 0) { // TODO: Is this even possible?
+			unsigned char i;
+			int char_id = 0, class_ = 0, guild_id = 0;
+			DBMap* auth_db = char_get_authdb();
 			struct auth_node* node = (struct auth_node*)idb_get(auth_db, acc);
 			struct auth_node* node = (struct auth_node*)idb_get(auth_db, acc);
-			if( node != NULL )
+			SqlStmt *stmt;
+
+			if (node != NULL)
 				node->sex = sex;
 				node->sex = sex;
 
 
 			// get characters
 			// get characters
-			if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `char_id`,`class`,`guild_id` FROM `%s` WHERE `account_id` = '%d'", schema_config.char_db, acc) )
-				Sql_ShowDebug(sql_handle);
-			for( i = 0; i < MAX_CHARS && SQL_SUCCESS == Sql_NextRow(sql_handle); ++i )
-			{
-				Sql_GetData(sql_handle, 0, &data, NULL); char_id[i] = atoi(data);
-				Sql_GetData(sql_handle, 1, &data, NULL); class_[i] = atoi(data);
-				Sql_GetData(sql_handle, 2, &data, NULL); guild_id[i] = atoi(data);
+			stmt = SqlStmt_Malloc(sql_handle);
+			if (SQL_ERROR == SqlStmt_Prepare(stmt, "SELECT `char_id`, `class`, `guild_id` FROM `%s` WHERE `account_id` = '%d'", schema_config.char_db, acc) || SqlStmt_Execute(stmt)) {
+				SqlStmt_ShowDebug(stmt);
+				SqlStmt_Free(stmt);
 			}
 			}
-			num = i;
-			for( i = 0; i < num; ++i )
-			{
-				if( class_[i] == JOB_BARD || class_[i] == JOB_DANCER ||
-					class_[i] == JOB_CLOWN || class_[i] == JOB_GYPSY ||
-					class_[i] == JOB_BABY_BARD || class_[i] == JOB_BABY_DANCER ||
-					class_[i] == JOB_MINSTREL || class_[i] == JOB_WANDERER ||
-					class_[i] == JOB_MINSTREL_T || class_[i] == JOB_WANDERER_T ||
-					class_[i] == JOB_BABY_MINSTREL || class_[i] == JOB_BABY_WANDERER ||
-					class_[i] == JOB_KAGEROU || class_[i] == JOB_OBORO )
-				{
-					// job modification
-					if( class_[i] == JOB_BARD || class_[i] == JOB_DANCER )
-						class_[i] = (sex ? JOB_BARD : JOB_DANCER);
-					else if( class_[i] == JOB_CLOWN || class_[i] == JOB_GYPSY )
-						class_[i] = (sex ? JOB_CLOWN : JOB_GYPSY);
-					else if( class_[i] == JOB_BABY_BARD || class_[i] == JOB_BABY_DANCER )
-						class_[i] = (sex ? JOB_BABY_BARD : JOB_BABY_DANCER);
-					else if( class_[i] == JOB_MINSTREL || class_[i] == JOB_WANDERER )
-						class_[i] = (sex ? JOB_MINSTREL : JOB_WANDERER);
-					else if( class_[i] == JOB_MINSTREL_T || class_[i] == JOB_WANDERER_T )
-						class_[i] = (sex ? JOB_MINSTREL_T : JOB_WANDERER_T);
-					else if( class_[i] == JOB_BABY_MINSTREL || class_[i] == JOB_BABY_WANDERER )
-						class_[i] = (sex ? JOB_BABY_MINSTREL : JOB_BABY_WANDERER);
-					else if( class_[i] == JOB_KAGEROU || class_[i] == JOB_OBORO )
-						class_[i] = (sex ? JOB_KAGEROU : JOB_OBORO);
-				}
 
 
-				if( SQL_ERROR == Sql_Query(sql_handle, "UPDATE `%s` SET `class`='%d', `weapon`='0', `shield`='0', `head_top`='0', `head_mid`='0', `head_bottom`='0' WHERE `char_id`='%d'", schema_config.char_db, class_[i], char_id[i]) )
-					Sql_ShowDebug(sql_handle);
+			SqlStmt_BindColumn(stmt, 0, SQLDT_INT,   &char_id,  0, NULL, NULL);
+			SqlStmt_BindColumn(stmt, 1, SQLDT_SHORT, &class_,   0, NULL, NULL);
+			SqlStmt_BindColumn(stmt, 2, SQLDT_INT,   &guild_id, 0, NULL, NULL);
 
 
-				if( guild_id[i] )// If there is a guild, update the guild_member data [Skotlex]
-					inter_guild_sex_changed(guild_id[i], acc, char_id[i], sex);
+			for (i = 0; i < MAX_CHARS && SQL_SUCCESS == SqlStmt_NextRow(stmt); ++i) {
+				chlogif_parse_change_sex_sub(sex, acc, char_id, class_, guild_id);
 			}
 			}
-			Sql_FreeResult(sql_handle);
-
-			// disconnect player if online on char-server
-			char_disconnect_player(acc);
+			SqlStmt_Free(stmt);
 		}
 		}
 
 
 		// notify all mapservers about this change
 		// notify all mapservers about this change
@@ -421,6 +424,54 @@ int chlogif_parse_ackchangesex(int fd, struct char_session_data* sd){
 	return 1;
 	return 1;
 }
 }
 
 
+/**
+ * Changes a character's sex.
+ * The information is updated on database, and the character is kicked if it
+ * currently is online.
+ *
+ * @param char_id The character's ID.
+ * @param sex     The new sex.
+ * @retval 0 in case of success.
+ * @retval 1 in case of failure.
+ */
+int chlogif_parse_ackchangecharsex(int char_id, int sex)
+{
+	int class_ = 0, guild_id = 0, account_id = 0;
+	unsigned char buf[7];
+	char *data;
+
+	// get character data
+	if (SQL_ERROR == Sql_Query(sql_handle, "SELECT `account_id`,`class`,`guild_id` FROM `%s` WHERE `char_id` = '%d'", schema_config.char_db, char_id)) {
+		Sql_ShowDebug(sql_handle);
+		return 1;
+	}
+	if (Sql_NumRows(sql_handle) != 1 || SQL_ERROR == Sql_NextRow(sql_handle)) {
+		Sql_FreeResult(sql_handle);
+		return 1;
+	}
+
+	Sql_GetData(sql_handle, 0, &data, NULL); account_id = atoi(data);
+	Sql_GetData(sql_handle, 1, &data, NULL); class_ = atoi(data);
+	Sql_GetData(sql_handle, 2, &data, NULL); guild_id = atoi(data);
+	Sql_FreeResult(sql_handle);
+
+	if (SQL_ERROR == Sql_Query(sql_handle, "UPDATE `%s` SET `sex` = '%c' WHERE `char_id` = '%d'", schema_config.char_db, sex == SEX_MALE ? 'M' : 'F', char_id)) {
+		Sql_ShowDebug(sql_handle);
+		return 1;
+	}
+	chlogif_parse_change_sex_sub(sex, account_id, char_id, class_, guild_id);
+
+	// disconnect player if online on char-server
+	char_disconnect_player(account_id);
+
+	// notify all mapservers about this change
+	WBUFW(buf,0) = 0x2b0d;
+	WBUFL(buf,2) = account_id;
+	WBUFB(buf,6) = sex;
+	chmapif_sendall(buf, 7);
+	return 0;
+}
+
 int chlogif_parse_ackacc2req(int fd, struct char_session_data* sd){
 int chlogif_parse_ackacc2req(int fd, struct char_session_data* sd){
 	if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2))
 	if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2))
 		return 0;
 		return 0;

+ 2 - 0
src/char/char_logif.h

@@ -30,7 +30,9 @@ int chlogif_parse_ackconnect(int fd, struct char_session_data* sd);
 int chlogif_parse_ackaccreq(int fd, struct char_session_data* sd);
 int chlogif_parse_ackaccreq(int fd, struct char_session_data* sd);
 int chlogif_parse_reqaccdata(int fd, struct char_session_data* sd);
 int chlogif_parse_reqaccdata(int fd, struct char_session_data* sd);
 int chlogif_parse_keepalive(int fd, struct char_session_data* sd);
 int chlogif_parse_keepalive(int fd, struct char_session_data* sd);
+void chlogif_parse_change_sex_sub(int sex, int acc, int char_id, int class_, int guild_id);
 int chlogif_parse_ackchangesex(int fd, struct char_session_data* sd);
 int chlogif_parse_ackchangesex(int fd, struct char_session_data* sd);
+int chlogif_parse_ackchangecharsex(int char_id, int sex);
 int chlogif_parse_ackacc2req(int fd, struct char_session_data* sd);
 int chlogif_parse_ackacc2req(int fd, struct char_session_data* sd);
 int chlogif_parse_accbannotification(int fd, struct char_session_data* sd);
 int chlogif_parse_accbannotification(int fd, struct char_session_data* sd);
 int chlogif_parse_askkick(int fd, struct char_session_data* sd);
 int chlogif_parse_askkick(int fd, struct char_session_data* sd);

+ 27 - 11
src/char/char_mapif.c

@@ -717,7 +717,7 @@ int chmapif_parse_reqnewemail(int fd){
 
 
 /**
 /**
  * Forward a change of status for account to login-serv
  * Forward a change of status for account to login-serv
- * @param fd: wich fd to parse from
+ * @param fd: which fd to parse from
  * @return : 0 not enough data received, 1 success
  * @return : 0 not enough data received, 1 success
  */
  */
 int chmapif_parse_fwlog_changestatus(int fd){
 int chmapif_parse_fwlog_changestatus(int fd){
@@ -730,14 +730,19 @@ int chmapif_parse_fwlog_changestatus(int fd){
 
 
 		int aid = RFIFOL(fd,2); // account_id of who ask (-1 if server itself made this request)
 		int aid = RFIFOL(fd,2); // account_id of who ask (-1 if server itself made this request)
 		const char* name = (char*)RFIFOP(fd,6); // name of the target character
 		const char* name = (char*)RFIFOP(fd,6); // name of the target character
-		int operation = RFIFOW(fd,30); // type of operation: 1-block, 2-ban, 3-unblock, 4-unban, 5-changesex, 6-vip
-		int32 timediff = RFIFOL(fd,32);
-		int val1 = RFIFOL(fd,36);
-		//int val2 = RFIFOL(fd,40); // Since BankVault is moved out, this value is unused for now
+		int operation = RFIFOW(fd,30); // type of operation: 1-block, 2-ban, 3-unblock, 4-unban, 5-changesex, 6-vip, 7-changecharsex
+		int32 timediff = 0;
+		int val1 = 0, sex = SEX_MALE;
+
+		if (operation == 2) {
+			timediff = RFIFOL(fd, 32);
+			val1 = RFIFOL(fd, 36);
+		} else if (operation == 7)
+			sex = RFIFOB(fd, 32);
 		RFIFOSKIP(fd,44);
 		RFIFOSKIP(fd,44);
 
 
 		Sql_EscapeStringLen(sql_handle, esc_name, name, strnlen(name, NAME_LENGTH));
 		Sql_EscapeStringLen(sql_handle, esc_name, name, strnlen(name, NAME_LENGTH));
-		if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `account_id` FROM `%s` WHERE `name` = '%s'", schema_config.char_db, esc_name) )
+		if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `account_id`, `char_id` FROM `%s` WHERE `name` = '%s'", schema_config.char_db, esc_name) )
 			Sql_ShowDebug(sql_handle);
 			Sql_ShowDebug(sql_handle);
 		else if( Sql_NumRows(sql_handle) == 0 ) {
 		else if( Sql_NumRows(sql_handle) == 0 ) {
 			result = 1; // 1-player not found
 			result = 1; // 1-player not found
@@ -746,10 +751,12 @@ int chmapif_parse_fwlog_changestatus(int fd){
 			Sql_ShowDebug(sql_handle);
 			Sql_ShowDebug(sql_handle);
 			result = 1;
 			result = 1;
 		} else {
 		} else {
-			int t_aid; //targit account id
+			int t_aid; // target account id
+			int t_cid; // target char id
 			char* data;
 			char* data;
 
 
 			Sql_GetData(sql_handle, 0, &data, NULL); t_aid = atoi(data);
 			Sql_GetData(sql_handle, 0, &data, NULL); t_aid = atoi(data);
+			Sql_GetData(sql_handle, 0, &data, NULL); t_cid = atoi(data);
 			Sql_FreeResult(sql_handle);
 			Sql_FreeResult(sql_handle);
 
 
 			if(!chlogif_isconnected())
 			if(!chlogif_isconnected())
@@ -794,16 +801,24 @@ int chmapif_parse_fwlog_changestatus(int fd){
 						WFIFOL(login_fd,2) = t_aid;
 						WFIFOL(login_fd,2) = t_aid;
 						WFIFOSET(login_fd,6);
 						WFIFOSET(login_fd,6);
 						break;
 						break;
-					case 6:
+					case 6: // vip
 						answer = (val1&4); // vip_req val1=type, &1 login send return, &2 update timestamp, &4 map send answer
 						answer = (val1&4); // vip_req val1=type, &1 login send return, &2 update timestamp, &4 map send answer
 						chlogif_reqvipdata(t_aid, val1, timediff, fd);
 						chlogif_reqvipdata(t_aid, val1, timediff, fd);
 						break;
 						break;
+					case 7: // changecharsex
+						answer = false;
+						WFIFOHEAD(login_fd,6);
+						WFIFOW(login_fd,0) = 0x2727;
+						WFIFOL(login_fd,2) = t_aid;
+						WFIFOL(login_fd,6) = t_cid;
+						WFIFOSET(login_fd,10);
+						break;
 				} //end switch operation
 				} //end switch operation
 			} //login is connected
 			} //login is connected
 		}
 		}
 
 
 		// send answer if a player asks, not if the server asks
 		// send answer if a player asks, not if the server asks
-		if( aid != -1 && answer) { // Don't send answer for changesex
+		if( aid != -1 && answer) { // Don't send answer for changesex/changecharsex
 			WFIFOHEAD(fd,34);
 			WFIFOHEAD(fd,34);
 			WFIFOW(fd, 0) = 0x2b0f;
 			WFIFOW(fd, 0) = 0x2b0f;
 			WFIFOL(fd, 2) = aid;
 			WFIFOL(fd, 2) = aid;
@@ -817,7 +832,7 @@ int chmapif_parse_fwlog_changestatus(int fd){
 }
 }
 
 
 /**
 /**
- * Transmit the acknolegement of divorce of partner_id1 and partner_id2
+ * Transmit the acknowledgement of divorce of partner_id1 and partner_id2
  * Update the list associated and transmit the new ranking
  * Update the list associated and transmit the new ranking
  * @param partner_id1: char id1 divorced
  * @param partner_id1: char id1 divorced
  * @param partner_id2: char id2 divorced
  * @param partner_id2: char id2 divorced
@@ -1047,7 +1062,8 @@ int chmapif_parse_reqauth(int fd, int id){
             node->ip == ip*/ )
             node->ip == ip*/ )
         {// auth ok
         {// auth ok
             uint16 mmo_charstatus_len = sizeof(struct mmo_charstatus) + 25;
             uint16 mmo_charstatus_len = sizeof(struct mmo_charstatus) + 25;
-            cd->sex = sex;
+			if (cd->sex == 99)
+				cd->sex = sex;
 
 
             WFIFOHEAD(fd,mmo_charstatus_len);
             WFIFOHEAD(fd,mmo_charstatus_len);
             WFIFOW(fd,0) = 0x2afd;
             WFIFOW(fd,0) = 0x2afd;

+ 5 - 1
src/common/utils.c

@@ -378,7 +378,11 @@ uint32 date2version(int date) {
 	else if(date < 20130717) return 43;
 	else if(date < 20130717) return 43;
 	else if(date < 20130807) return 44;
 	else if(date < 20130807) return 44;
 	else if(date < 20131223) return 45;
 	else if(date < 20131223) return 45;
-	else if(date >= 20131223) return 46;
+	else if(date < 20140212) return 46;
+	else if(date < 20140613) return 47;
+	else if(date < 20141016) return 48;
+	else if(date < 20141022) return 49;
+	else if(date >= 20141022) return 50;
 
 
 	else return 30; //default
 	else return 30; //default
 }
 }

+ 31 - 4
src/map/atcommand.c

@@ -6769,17 +6769,43 @@ ACMD_FUNC(uptime)
 
 
 /*==========================================
 /*==========================================
  * @changesex <sex>
  * @changesex <sex>
- * => Changes one's sex. Argument sex can be 0 or 1, m or f, male or female.
+ * => Changes one's account sex. Argument sex can be 0 or 1, m or f, male or female.
  *------------------------------------------*/
  *------------------------------------------*/
 ACMD_FUNC(changesex)
 ACMD_FUNC(changesex)
 {
 {
 	int i;
 	int i;
+
+	nullpo_retr(-1, sd);
+
+	pc_resetskill(sd,4);
+	// to avoid any problem with equipment and invalid sex, equipment is unequiped.
+	for (i = 0; i < EQI_MAX; i++) {
+		if (sd->equip_index[i] >= 0)
+			pc_unequipitem(sd, sd->equip_index[i], 3);
+	}
+
+	chrif_changesex(sd, true);
+	return 0;
+}
+
+/*==========================================
+ * @changecharsex <sex>
+ * => Changes one's character sex. Argument sex can be 0 or 1, m or f, male or female.
+ *------------------------------------------*/
+ACMD_FUNC(changecharsex)
+{
+	int i;
+
 	nullpo_retr(-1, sd);
 	nullpo_retr(-1, sd);
+
 	pc_resetskill(sd,4);
 	pc_resetskill(sd,4);
 	// to avoid any problem with equipment and invalid sex, equipment is unequiped.
 	// to avoid any problem with equipment and invalid sex, equipment is unequiped.
-	for( i=0; i<EQI_MAX; i++ )
-		if( sd->equip_index[i] >= 0 ) pc_unequipitem(sd, sd->equip_index[i], 3);
-	chrif_changesex(sd);
+	for (i = 0; i < EQI_MAX; i++) {
+		if (sd->equip_index[i] >= 0)
+			pc_unequipitem(sd, sd->equip_index[i], 3);
+	}
+
+	chrif_changesex(sd, false);
 	return 0;
 	return 0;
 }
 }
 
 
@@ -9886,6 +9912,7 @@ void atcommand_basecommands(void) {
 		ACMD_DEF(clearweather),
 		ACMD_DEF(clearweather),
 		ACMD_DEF(uptime),
 		ACMD_DEF(uptime),
 		ACMD_DEF(changesex),
 		ACMD_DEF(changesex),
+		ACMD_DEF(changecharsex),
 		ACMD_DEF(mute),
 		ACMD_DEF(mute),
 		ACMD_DEF(refresh),
 		ACMD_DEF(refresh),
 		ACMD_DEF(refreshall),
 		ACMD_DEF(refreshall),

+ 8 - 0
src/map/battle.c

@@ -8168,6 +8168,7 @@ static const struct _battle_data {
 	{ "homunculus_evo_intimacy_need",       &battle_config.homunculus_evo_intimacy_need,    91100,  0,      INT_MAX,        },
 	{ "homunculus_evo_intimacy_need",       &battle_config.homunculus_evo_intimacy_need,    91100,  0,      INT_MAX,        },
 	{ "homunculus_evo_intimacy_reset",      &battle_config.homunculus_evo_intimacy_reset,   1000,   0,      INT_MAX,        },
 	{ "homunculus_evo_intimacy_reset",      &battle_config.homunculus_evo_intimacy_reset,   1000,   0,      INT_MAX,        },
 	{ "monster_loot_search_type",           &battle_config.monster_loot_search_type,        1,      0,      1,              },
 	{ "monster_loot_search_type",           &battle_config.monster_loot_search_type,        1,      0,      1,              },
+	{ "feature.roulette",                   &battle_config.feature_roulette,                1,      0,      1,              },
 };
 };
 
 
 #ifndef STATS_OPT_OUT
 #ifndef STATS_OPT_OUT
@@ -8396,6 +8397,13 @@ void battle_adjust_conf()
 	}
 	}
 #endif
 #endif
 
 
+#if PACKETVER < 20141022
+	if (battle_config.feature_roulette) {
+		ShowWarning("conf/battle/feature.conf roulette is enabled but it requires PACKETVER 2014-10-22 or newer, disabling...\n");
+		battle_config.feature_roulette = 0;
+	}
+#endif
+
 #ifndef CELL_NOSTACK
 #ifndef CELL_NOSTACK
 	if (battle_config.custom_cell_stack_limit != 1)
 	if (battle_config.custom_cell_stack_limit != 1)
 		ShowWarning("Battle setting 'custom_cell_stack_limit' takes no effect as this server was compiled without Cell Stack Limit support.\n");
 		ShowWarning("Battle setting 'custom_cell_stack_limit' takes no effect as this server was compiled without Cell Stack Limit support.\n");

+ 1 - 0
src/map/battle.h

@@ -590,6 +590,7 @@ extern struct Battle_Config
 	int homunculus_evo_intimacy_need;
 	int homunculus_evo_intimacy_need;
 	int homunculus_evo_intimacy_reset;
 	int homunculus_evo_intimacy_reset;
 	int monster_loot_search_type;
 	int monster_loot_search_type;
+	int feature_roulette;
 } battle_config;
 } battle_config;
 
 
 void do_init_battle(void);
 void do_init_battle(void);

+ 10 - 5
src/map/chrif.c

@@ -63,7 +63,7 @@ static const int packet_len_table[0x3d] = { // U - used, F - free
 //2b0a: Outgoing, chrif_skillcooldown_request -> requesting the list of skillcooldown for char
 //2b0a: Outgoing, chrif_skillcooldown_request -> requesting the list of skillcooldown for char
 //2b0b: Incoming, chrif_skillcooldown_load -> received the list of cooldown for char
 //2b0b: Incoming, chrif_skillcooldown_load -> received the list of cooldown for char
 //2b0c: Outgoing, chrif_changeemail -> 'change mail address ...'
 //2b0c: Outgoing, chrif_changeemail -> 'change mail address ...'
-//2b0d: Incoming, chrif_changedsex -> 'Change sex of acc XY'
+//2b0d: Incoming, chrif_changedsex -> 'Change sex of acc XY' (or char)
 //2b0e: Outgoing, chrif_req_login_operation -> 'Do some operations (change sex, ban / unban etc)'
 //2b0e: Outgoing, chrif_req_login_operation -> 'Do some operations (change sex, ban / unban etc)'
 //2b0f: Incoming, chrif_ack_login_req -> 'answer of the 2b0e'
 //2b0f: Incoming, chrif_ack_login_req -> 'answer of the 2b0e'
 //2b10: Outgoing, chrif_updatefamelist -> 'Update the fame ranking lists and send them'
 //2b10: Outgoing, chrif_updatefamelist -> 'Update the fame ranking lists and send them'
@@ -864,17 +864,19 @@ int chrif_req_login_operation(int aid, const char* character_name, unsigned shor
 
 
 /**
 /**
  * S 2b0e <accid>.l <name>.24B <operation_type>.w <timediff>L <val1>L <val2>L
  * S 2b0e <accid>.l <name>.24B <operation_type>.w <timediff>L <val1>L <val2>L
- * Send an account modification (changesex) request to the login server (via char server).
+ * Send a sex change (for account or character) request to the login server (via char server).
  * @sd : Player requesting operation
  * @sd : Player requesting operation
  */
  */
-int chrif_changesex(struct map_session_data *sd) {
+int chrif_changesex(struct map_session_data *sd, bool change_account) {
 	chrif_check(-1);
 	chrif_check(-1);
 
 
 	WFIFOHEAD(char_fd,44);
 	WFIFOHEAD(char_fd,44);
 	WFIFOW(char_fd,0) = 0x2b0e;
 	WFIFOW(char_fd,0) = 0x2b0e;
 	WFIFOL(char_fd,2) = sd->status.account_id;
 	WFIFOL(char_fd,2) = sd->status.account_id;
 	safestrncpy((char*)WFIFOP(char_fd,6), sd->status.name, NAME_LENGTH);
 	safestrncpy((char*)WFIFOP(char_fd,6), sd->status.name, NAME_LENGTH);
-	WFIFOW(char_fd,30) = CHRIF_OP_LOGIN_CHANGESEX;
+	WFIFOW(char_fd,30) = (change_account ? CHRIF_OP_LOGIN_CHANGESEX : CHRIF_OP_LOGIN_CHANGECHARSEX);
+	if (!change_account)
+		WFIFOB(char_fd,32) = sd->status.sex == SEX_MALE ? SEX_FEMALE : SEX_MALE;
 	WFIFOSET(char_fd,44);
 	WFIFOSET(char_fd,44);
 
 
 	clif_displaymessage(sd->fd, msg_txt(sd,408)); //"Need disconnection to perform change-sex request..."
 	clif_displaymessage(sd->fd, msg_txt(sd,408)); //"Need disconnection to perform change-sex request..."
@@ -917,6 +919,9 @@ static void chrif_ack_login_req(int aid, const char* player_name, uint16 type, u
 		case CHRIF_OP_LOGIN_UNBLOCK:
 		case CHRIF_OP_LOGIN_UNBLOCK:
 		case CHRIF_OP_LOGIN_UNBAN:
 		case CHRIF_OP_LOGIN_UNBAN:
 		case CHRIF_OP_LOGIN_CHANGESEX:
 		case CHRIF_OP_LOGIN_CHANGESEX:
+		case CHRIF_OP_LOGIN_CHANGECHARSEX:
+			if (type == CHRIF_OP_LOGIN_CHANGECHARSEX)
+				type--; // So we don't have to create a new msgstring.
 			snprintf(action,25,"%s",msg_txt(sd,427+type)); //block|ban|unblock|unban|change the sex of
 			snprintf(action,25,"%s",msg_txt(sd,427+type)); //block|ban|unblock|unban|change the sex of
 			break;
 			break;
 		case CHRIF_OP_LOGIN_VIP:
 		case CHRIF_OP_LOGIN_VIP:
@@ -929,7 +934,7 @@ static void chrif_ack_login_req(int aid, const char* player_name, uint16 type, u
 			break;
 			break;
 	}
 	}
 
 
-	switch( answer ) {
+	switch (answer) {
 		case 0: sprintf(output, msg_txt(sd,424), action, NAME_LENGTH, player_name); break; //Login-serv has been asked to %s '%.*s'.
 		case 0: sprintf(output, msg_txt(sd,424), action, NAME_LENGTH, player_name); break; //Login-serv has been asked to %s '%.*s'.
 		case 1: sprintf(output, msg_txt(sd,425), NAME_LENGTH, player_name); break;
 		case 1: sprintf(output, msg_txt(sd,425), NAME_LENGTH, player_name); break;
 		case 2: sprintf(output, msg_txt(sd,426), action, NAME_LENGTH, player_name); break;
 		case 2: sprintf(output, msg_txt(sd,426), action, NAME_LENGTH, player_name); break;

+ 2 - 1
src/map/chrif.h

@@ -27,6 +27,7 @@ enum chrif_req_op {
 	CHRIF_OP_LOGIN_UNBAN,
 	CHRIF_OP_LOGIN_UNBAN,
 	CHRIF_OP_LOGIN_CHANGESEX,
 	CHRIF_OP_LOGIN_CHANGESEX,
 	CHRIF_OP_LOGIN_VIP,
 	CHRIF_OP_LOGIN_VIP,
+	CHRIF_OP_LOGIN_CHANGECHARSEX,
 
 
 	// Char-server operation
 	// Char-server operation
 	CHRIF_OP_BAN,
 	CHRIF_OP_BAN,
@@ -73,7 +74,7 @@ int chrif_char_offline_nsd(uint32 account_id, uint32 char_id);
 int chrif_char_reset_offline(void);
 int chrif_char_reset_offline(void);
 int send_users_tochar(void);
 int send_users_tochar(void);
 int chrif_char_online(struct map_session_data *sd);
 int chrif_char_online(struct map_session_data *sd);
-int chrif_changesex(struct map_session_data *sd);
+int chrif_changesex(struct map_session_data *sd, bool change_account);
 int chrif_chardisconnect(struct map_session_data *sd);
 int chrif_chardisconnect(struct map_session_data *sd);
 int chrif_divorce(int partner_id1, int partner_id2);
 int chrif_divorce(int partner_id1, int partner_id2);
 
 

+ 268 - 8
src/map/clif.c

@@ -662,6 +662,9 @@ void clif_authok(struct map_session_data *sd)
 	WFIFOB(fd,10) = 5; // ignored
 	WFIFOB(fd,10) = 5; // ignored
 #if PACKETVER >= 20080102
 #if PACKETVER >= 20080102
 	WFIFOW(fd,11) = sd->status.font;
 	WFIFOW(fd,11) = sd->status.font;
+#endif
+#if PACKETVER >= 20141016
+	WFIFOB(fd,13) = sd->status.sex;
 #endif
 #endif
 	WFIFOSET(fd,packet_len(cmd));
 	WFIFOSET(fd,packet_len(cmd));
 }
 }
@@ -5751,6 +5754,11 @@ void clif_displaymessage(const int fd, const char* mes)
 	if (fd == 0)
 	if (fd == 0)
 		;
 		;
 	else {
 	else {
+#if PACKETVER == 20141022
+		/** for some reason game client crashes depending on message pattern (only for this packet) **/
+		/** so we redirect to ZC_NPC_CHAT **/
+		clif_colormes(fd, color_table[COLOR_DEFAULT], mes);
+#else
 		char *message, *line;
 		char *message, *line;
 
 
 		message = aStrdup(mes);
 		message = aStrdup(mes);
@@ -5769,6 +5777,7 @@ void clif_displaymessage(const int fd, const char* mes)
 			line = strtok(NULL, "\n");
 			line = strtok(NULL, "\n");
 		}
 		}
 		aFree(message);
 		aFree(message);
+#endif
 	}
 	}
 }
 }
 
 
@@ -6833,6 +6842,14 @@ void clif_openvending(struct map_session_data* sd, int id, struct s_vending* ven
 		clif_addcards(WFIFOP(fd,22+i*22), &sd->status.cart[index]);
 		clif_addcards(WFIFOP(fd,22+i*22), &sd->status.cart[index]);
 	}
 	}
 	WFIFOSET(fd,WFIFOW(fd,2));
 	WFIFOSET(fd,WFIFOW(fd,2));
+
+#if PACKETVER >= 20141022
+	// Should go elsewhere perhaps? It has to be bundled with this however.
+	WFIFOHEAD(fd, 3);
+	WFIFOW(fd, 0) = 0xa28;
+	WFIFOB(fd, 2) = 0; // 1 is failure. Our current responses to failure are working so not yet implemented.
+	WFIFOSET(fd, 3);
+#endif
 }
 }
 
 
 
 
@@ -10114,18 +10131,22 @@ void clif_hotkeys_send(struct map_session_data *sd) {
 #ifdef HOTKEY_SAVING
 #ifdef HOTKEY_SAVING
 	const int fd = sd->fd;
 	const int fd = sd->fd;
 	int i;
 	int i;
+	int offset = 2;
 #if PACKETVER < 20090603
 #if PACKETVER < 20090603
 	const int cmd = 0x2b9;
 	const int cmd = 0x2b9;
-#else
+#elif PACKETVER < 20141022
 	const int cmd = 0x7d9;
 	const int cmd = 0x7d9;
+#else
+	const int cmd = 0xa00;
+	offset = 3;
 #endif
 #endif
 	if (!fd) return;
 	if (!fd) return;
-	WFIFOHEAD(fd, 2+MAX_HOTKEYS*7);
+	WFIFOHEAD(fd, offset + MAX_HOTKEYS * 7);
 	WFIFOW(fd, 0) = cmd;
 	WFIFOW(fd, 0) = cmd;
 	for(i = 0; i < MAX_HOTKEYS; i++) {
 	for(i = 0; i < MAX_HOTKEYS; i++) {
-		WFIFOB(fd, 2 + 0 + i * 7) = sd->status.hotkeys[i].type; // type: 0: item, 1: skill
-		WFIFOL(fd, 2 + 1 + i * 7) = sd->status.hotkeys[i].id; // item or skill ID
-		WFIFOW(fd, 2 + 5 + i * 7) = sd->status.hotkeys[i].lv; // skill level
+		WFIFOB(fd, offset + 0 + i * 7) = sd->status.hotkeys[i].type; // type: 0: item, 1: skill
+		WFIFOL(fd, offset + 1 + i * 7) = sd->status.hotkeys[i].id; // item or skill ID
+		WFIFOW(fd, offset + 5 + i * 7) = sd->status.hotkeys[i].lv; // item qty or skill level
 	}
 	}
 	WFIFOSET(fd, packet_len(cmd));
 	WFIFOSET(fd, packet_len(cmd));
 #endif
 #endif
@@ -17729,6 +17750,229 @@ void DumpUnknown(int fd,TBL_PC *sd,int cmd,int packet_len)
 }
 }
 #endif
 #endif
 
 
+/// Roulette System
+/// Author: Yommy
+
+/**
+ * Opens Roulette window
+ * @param fd
+ * @param sd
+ */
+void clif_parse_RouletteOpen(int fd, struct map_session_data* sd)
+{
+	nullpo_retv(sd);
+
+	if (!battle_config.feature_roulette) {
+		clif_colormes(sd,color_table[COLOR_RED],msg_txt(sd,1497)); //Roulette is disabled
+		return;
+	}
+
+	WFIFOHEAD(fd,packet_len(0xa1a));
+	WBUFW(fd,0) = 0xa1a;
+	WBUFW(fd,2) = 0; // result
+	WBUFL(fd,3) = 0; // serial
+	WBUFW(fd,7) = sd->roulette.stage - 1;
+	WBUFW(fd,8) = (char)sd->roulette.prizeIdx;
+	WBUFW(fd,9) = -1; // TODO
+	WBUFL(fd,11) = sd->roulette_point.gold;
+	WBUFL(fd,15) = sd->roulette_point.silver;
+	WBUFL(fd,19) = sd->roulette_point.bronze;
+	WFIFOSET(fd,packet_len(0xa1a));
+}
+
+/**
+ * Generates information to be displayed
+ * @param fd
+ * @param sd
+ */
+void clif_parse_RouletteInfo(int fd, struct map_session_data* sd)
+{
+	unsigned short i, j, count = 0;
+	int len = 8 + (42 * 8);
+
+	nullpo_retv(sd);
+
+	if (!battle_config.feature_roulette) {
+		clif_colormes(sd,color_table[COLOR_RED],msg_txt(sd,1497)); //Roulette is disabled
+		return;
+	}
+
+	WFIFOHEAD(fd,len);
+	WBUFW(fd,0) = 0xa1c;
+	WBUFW(fd,2) = len;
+	WBUFL(fd,6) = 1; // serial
+
+	for(i = 0; i < MAX_ROULETTE_LEVEL; i++) {
+		for(j = 0; j < MAX_ROULETTE_COLUMNS - i; j++) {
+			WBUFW(fd,8 + i * 8) = i;
+			WBUFW(fd,8 + i * 8 + 2) = j;
+			WBUFW(fd,8 + i * 8 + 4) = rd.nameid[i][j];
+			WBUFW(fd,8 + i * 8 + 6) = rd.qty[i][j];
+			count++;
+		}
+	}
+
+	WFIFOSET(fd,len);
+	return;
+}
+
+/**
+ * Closes Roulette window
+ * @param fd
+ * @param sd
+ */
+void clif_parse_RouletteClose(int fd, struct map_session_data* sd)
+{
+	nullpo_retv(sd);
+
+	if (!battle_config.feature_roulette) {
+		clif_colormes(sd,color_table[COLOR_RED],msg_txt(sd,1497)); //Roulette is disabled
+		return;
+	}
+
+	/** What do we need this for? (other than state tracking), game client closes the window without our response. **/
+	return;
+}
+
+/**
+ * Process the stage and attempt to give a prize
+ * @param fd
+ * @param sd
+ */
+void clif_parse_RouletteGenerate(int fd, struct map_session_data* sd)
+{
+	unsigned char result = GENERATE_ROULETTE_SUCCESS;
+	short stage = sd->roulette.stage;
+
+	nullpo_retv(sd);
+
+	if (!battle_config.feature_roulette) {
+		clif_colormes(sd,color_table[COLOR_RED],msg_txt(sd,1497)); //Roulette is disabled
+		return;
+	}
+
+	if (sd->roulette.stage >= MAX_ROULETTE_LEVEL)
+		stage = sd->roulette.stage = 0;
+
+	if (!stage) {
+		if (sd->roulette_point.bronze <= 0 && sd->roulette_point.silver < 10 && sd->roulette_point.gold < 10)
+			result = GENERATE_ROULETTE_NO_ENOUGH_POINT;
+	}
+
+	if (result == GENERATE_ROULETTE_SUCCESS) {
+		if (!stage) {
+			if (sd->roulette_point.bronze > 0) {
+				sd->roulette_point.bronze -= 1;
+			} else if (sd->roulette_point.silver > 9) {
+				sd->roulette_point.silver -= 10;
+				stage = sd->roulette.stage = 2;
+			} else if (sd->roulette_point.gold > 9) {
+				sd->roulette_point.gold -= 10;
+				stage = sd->roulette.stage = 4;
+			}
+		}
+
+		sd->roulette.prizeStage = stage;
+		sd->roulette.prizeIdx = rnd()%rd.items[stage];
+		if (sd->roulette.prizeIdx == 0) {
+			struct item it;
+			memset(&it, 0, sizeof(it));
+
+			it.nameid = rd.nameid[stage][0];
+			it.identify = 1;
+
+			pc_additem(sd, &it, rd.qty[stage][0], LOG_TYPE_ROULETTE);
+
+			sd->roulette.stage = 0;
+			result = GENERATE_ROULETTE_LOSING;
+		} else
+			sd->roulette.claimPrize = true;
+	}
+
+	clif_roulette_generate_ack(sd,result,stage,sd->roulette.prizeIdx,0);
+	if (result == GENERATE_ROULETTE_SUCCESS)
+		sd->roulette.stage++;
+}
+
+/**
+ * Request to cash in prize
+ * @param fd
+ * @param sd
+ */
+void clif_parse_RouletteRecvItem(int fd, struct map_session_data* sd)
+{
+	nullpo_retv(sd);
+
+	if (!battle_config.feature_roulette) {
+		clif_colormes(sd,color_table[COLOR_RED],msg_txt(sd,1497)); //Roulette is disabled
+		return;
+	}
+
+	WFIFOHEAD(fd,packet_len(0xa22));
+	WBUFW(fd,0) = 0xa22;
+
+	if (sd->roulette.claimPrize) {
+		struct item it;
+		memset(&it, 0, sizeof(it));
+
+		it.nameid = rd.nameid[sd->roulette.prizeStage][sd->roulette.prizeIdx];
+		it.identify = 1;
+
+		switch (pc_additem(sd, &it, rd.qty[sd->roulette.prizeStage][sd->roulette.prizeIdx], LOG_TYPE_OTHER)) {
+			case 0:
+				WBUFW(fd,2) = RECV_ITEM_SUCCESS;
+				sd->roulette.claimPrize = false;
+				sd->roulette.prizeStage = 0;
+				sd->roulette.prizeIdx = 0;
+				sd->roulette.stage = 0;
+				break;
+			case 1:
+			case 4:
+			case 5:
+				WBUFW(fd,2) = RECV_ITEM_OVERCOUNT;
+				break;
+			case 2:
+				WBUFW(fd,2) = RECV_ITEM_OVERWEIGHT;
+				break;
+			default:
+			case 7:
+				WBUFW(fd,2) = RECV_ITEM_FAILED;
+				break;
+		}
+	} else
+		WBUFW(fd,2) = RECV_ITEM_FAILED;
+
+	WBUFL(fd,3) = 0; // additional item TODO
+	WFIFOSET(fd,packet_len(0xa22));
+	return;
+}
+
+/**
+ * Update Roulette window with current stats
+ * @param sd
+ * @param result
+ * @param stage
+ * @param prizeIdx
+ * @param bonusItemID
+ */
+void clif_roulette_generate_ack(struct map_session_data *sd, unsigned char result, short stage, short prizeIdx, short bonusItemID)
+{
+	int fd = sd->fd;
+
+	nullpo_retv(sd);
+
+	WFIFOHEAD(fd,packet_len(0xa20));
+	WBUFW(fd,0) = 0xa20;
+	WBUFW(fd,2) = result;
+	WBUFW(fd,3) = stage;
+	WBUFW(fd,5) = prizeIdx;
+	WBUFW(fd,7) = bonusItemID;
+	WBUFL(fd,9) = sd->roulette_point.gold;
+	WBUFL(fd,13) = sd->roulette_point.silver;
+	WBUFL(fd,17) = sd->roulette_point.bronze;
+	WFIFOSET(fd,packet_len(0xa20));
+}
+
 /*==========================================
 /*==========================================
  * Main client packet processing function
  * Main client packet processing function
  *------------------------------------------*/
  *------------------------------------------*/
@@ -18120,8 +18364,17 @@ void packetdb_readdb(bool reload)
 	//#0x09C0
 	//#0x09C0
 		0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 23,  0,  0,  0,102,  0,
 		0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 23,  0,  0,  0,102,  0,
 		0,  0,  0,  0,  2,  0, -1, -1,  2,  0,  0,  0,  0,  0,  0,  7,
 		0,  0,  0,  0,  2,  0, -1, -1,  2,  0,  0,  0,  0,  0,  0,  7,
-		0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
-		0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+		0,  0,  0,  0,  0,  0,  0,  3, 11,  0, 11, -1,  0,  3, 11,  0,
+		0, 11, 12, 11,  0,  0,  0,  0,  0,143,  0,  0,  0,  0,  0,  0,
+	//#0x0a00
+#if PACKETVER >= 20141022
+	  269,  0,  0,  2,  6, 49,  6,  9, 26, 45, 47, 47, 56, -1,  0,  0,
+#else
+	  269,  0,  0,  2,  6, 48,  6,  9, 26, 45, 47, 47, 56, -1,  0,  0,
+#endif
+		0,  0,  0, 26,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+		0,  3,  5,  0, 66,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+ 		0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
 	};
 	};
 	struct {
 	struct {
 		void (*func)(int, struct map_session_data *);
 		void (*func)(int, struct map_session_data *);
@@ -18344,9 +18597,15 @@ void packetdb_readdb(bool reload)
 		{ clif_parse_client_version, "clientversion"},
 		{ clif_parse_client_version, "clientversion"},
 		{ clif_parse_blocking_playcancel, "booking_playcancel"},
 		{ clif_parse_blocking_playcancel, "booking_playcancel"},
 		{ clif_parse_ranklist, "ranklist"},
 		{ clif_parse_ranklist, "ranklist"},
-		/* Market NPC */
+		// Market NPC
 		{ clif_parse_NPCMarketClosed, "npcmarketclosed" },
 		{ clif_parse_NPCMarketClosed, "npcmarketclosed" },
 		{ clif_parse_NPCMarketPurchase, "npcmarketpurchase" },
 		{ clif_parse_NPCMarketPurchase, "npcmarketpurchase" },
+		// Roulette
+		{ clif_parse_RouletteOpen, "rouletteopen" },
+		{ clif_parse_RouletteInfo, "rouletteinfo" },
+		{ clif_parse_RouletteClose, "rouletteclose" },
+		{ clif_parse_RouletteGenerate, "roulettegenerate" },
+		{ clif_parse_RouletteRecvItem, "rouletterecvitem" },
 		{NULL,NULL}
 		{NULL,NULL}
 	};
 	};
 	struct {
 	struct {
@@ -18591,6 +18850,7 @@ void packetdb_readdb(bool reload)
  *------------------------------------------*/
  *------------------------------------------*/
 void do_init_clif(void) {
 void do_init_clif(void) {
 	const char* colors[COLOR_MAX] = {
 	const char* colors[COLOR_MAX] = {
+		"0x00FF00",
 		"0xFF0000",
 		"0xFF0000",
 		"0xFFFFFF",
 		"0xFFFFFF",
 		"0xFFFF00",
 		"0xFFFF00",

+ 42 - 4
src/map/clif.h

@@ -33,9 +33,9 @@ struct party_booking_ad_info;
 #include <stdarg.h>
 #include <stdarg.h>
 
 
 enum { // packet DB
 enum { // packet DB
-	MIN_PACKET_DB  = 0x0064,
-	MAX_PACKET_DB  = 0xf00,
-	MAX_PACKET_VER = 46,
+	MIN_PACKET_DB  = 0x064,
+	MAX_PACKET_DB  = 0xAFF,
+	MAX_PACKET_VER = 51,
 	MAX_PACKET_POS = 20,
 	MAX_PACKET_POS = 20,
 };
 };
 
 
@@ -73,13 +73,42 @@ enum e_BANKING_DEPOSIT_ACK {
 	BDA_NO_MONEY = 0x2,
 	BDA_NO_MONEY = 0x2,
 	BDA_OVERFLOW = 0x3,
 	BDA_OVERFLOW = 0x3,
 };
 };
- 
+
 enum e_BANKING_WITHDRAW_ACK {
 enum e_BANKING_WITHDRAW_ACK {
 	BWA_SUCCESS       = 0x0,
 	BWA_SUCCESS       = 0x0,
 	BWA_NO_MONEY      = 0x1,
 	BWA_NO_MONEY      = 0x1,
 	BWA_UNKNOWN_ERROR = 0x2,
 	BWA_UNKNOWN_ERROR = 0x2,
 };
 };
 
 
+enum RECV_ROULETTE_ITEM_REQ {
+	RECV_ITEM_SUCCESS    = 0x0,
+	RECV_ITEM_FAILED     = 0x1,
+	RECV_ITEM_OVERCOUNT  = 0x2,
+	RECV_ITEM_OVERWEIGHT = 0x3,
+};
+
+enum RECV_ROULETTE_ITEM_ACK {
+	RECV_ITEM_NORMAL =  0x0,
+	RECV_ITEM_LOSING =  0x1,
+};
+
+enum GENERATE_ROULETTE_ACK {
+	GENERATE_ROULETTE_SUCCESS         = 0x0,
+	GENERATE_ROULETTE_FAILED          = 0x1,
+	GENERATE_ROULETTE_NO_ENOUGH_POINT = 0x2,
+	GENERATE_ROULETTE_LOSING          = 0x3,
+};
+
+enum OPEN_ROULETTE_ACK {
+	OPEN_ROULETTE_SUCCESS = 0x0,
+	OPEN_ROULETTE_FAILED  = 0x1,
+};
+
+enum CLOSE_ROULETTE_ACK {
+	CLOSE_ROULETTE_SUCCESS = 0x0,
+	CLOSE_ROULETTE_FAILED  = 0x1,
+};
+
 // packet_db[SERVER] is reserved for server use
 // packet_db[SERVER] is reserved for server use
 #define SERVER 0
 #define SERVER 0
 #define packet_len(cmd) packet_db[SERVER][cmd].len
 #define packet_len(cmd) packet_db[SERVER][cmd].len
@@ -861,6 +890,14 @@ void clif_cashshop_open( struct map_session_data* sd );
 
 
 void clif_display_pinfo(struct map_session_data *sd, int type);
 void clif_display_pinfo(struct map_session_data *sd, int type);
 
 
+/// Roulette
+void clif_roulette_generate_ack(struct map_session_data *sd, unsigned char result, short stage, short prizeIdx, short bonusItemID);
+void clif_parse_RouletteOpen(int fd, struct map_session_data *sd);
+void clif_parse_RouletteInfo(int fd, struct map_session_data *sd);
+void clif_parse_RouletteClose(int fd, struct map_session_data *sd);
+void clif_parse_RouletteGenerate(int fd, struct map_session_data *sd);
+void clif_parse_RouletteRecvItem(int fd, struct map_session_data *sd);
+
 /**
 /**
  * 3CeAM
  * 3CeAM
  **/
  **/
@@ -892,6 +929,7 @@ void clif_monster_hp_bar( struct mob_data* md, int fd );
  * Color Table
  * Color Table
  **/
  **/
 enum clif_colors {
 enum clif_colors {
+	COLOR_DEFAULT,
 	COLOR_RED,
 	COLOR_RED,
 	COLOR_WHITE,
 	COLOR_WHITE,
 	COLOR_YELLOW,
 	COLOR_YELLOW,

+ 102 - 0
src/map/itemdb.c

@@ -929,6 +929,7 @@ static int itemdb_combo_split_atoi (char *str, int *val) {
 
 
 	return i;
 	return i;
 }
 }
+
 /**
 /**
  * <combo{:combo{:combo:{..}}}>,<{ script }>
  * <combo{:combo{:combo:{..}}}>,<{ script }>
  **/
  **/
@@ -1056,7 +1057,101 @@ static void itemdb_read_combos(const char* basedir, bool silent) {
 	return;
 	return;
 }
 }
 
 
+/**
+ * Process Roulette items
+ */
+bool itemdb_parse_roulette_db(void)
+{
+	int i, j;
+	uint32 count = 0;
+
+	// retrieve all rows from the item database
+	if (SQL_ERROR == Sql_Query(mmysql_handle, "SELECT * FROM `%s`", db_roulette_table)) {
+		Sql_ShowDebug(mmysql_handle);
+		return false;
+	}
+
+	for (i = 0; i < MAX_ROULETTE_LEVEL; i++) {
+		rd.items[i] = 0;
+	}
+
+	// process rows one by one
+	while (SQL_SUCCESS == Sql_NextRow(mmysql_handle)) {
+		char* str[4];
+		char* dummy = "";
+		int i;
+
+		for (i = 0; i < MAX_ROULETTE_LEVEL; i++) {
+			struct item_data * data = NULL;
+
+			Sql_GetData(mmysql_handle, i, &str[i], NULL);
+
+			if (str[i] == NULL)
+				str[i] = dummy; // get rid of NULL columns
+
+			if (!(data = itemdb_exists(atoi(str[1])))) {
+				ShowWarning("itemdb_parse_roulette_db: Unknown item_id '%hu' in level '%d'\n", atoi(str[1]), atoi(str[0]));
+				continue;
+			}
+			if (atoi(str[2]) < 1) {
+				ShowWarning("itemdb_parse_roulette_db: Unsupported amount '%hu' for item_id '%hu' in level '%d'\n", atoi(str[2]), atoi(str[1]), atoi(str[0]));
+				continue;
+			}
+			if (atoi(str[3]) < 0 || atoi(str[3]) > 1) {
+				ShowWarning("itemdb_parse_roueltte_db: Unsupported flag '%d' for item_id '%hu' in level '%d'\n", atoi(str[3]), atoi(str[1]), atoi(str[0]));
+				continue;
+			}
+
+			j = rd.items[i];
+			RECREATE(rd.nameid[i], unsigned short, ++rd.items[i]);
+			RECREATE(rd.qty[i], unsigned short, rd.items[i]);
+			RECREATE(rd.flag[i], int, rd.items[i]);
+
+			rd.nameid[i][j] = data->nameid;
+			rd.qty[i][j] = atoi(str[2]);
+			rd.flag[i][j] = atoi(str[3]);
 
 
+			++count;
+		}
+	}
+
+	// free the query result
+	Sql_FreeResult(mmysql_handle);
+
+	for (i = 0; i < MAX_ROULETTE_LEVEL; i++) {
+		int limit = MAX_ROULETTE_COLUMNS - i;
+
+		if (rd.items[i] == limit)
+			continue;
+
+		if (rd.items[i] > limit) {
+			ShowWarning("itemdb_parse_roulette_db: level %d has %d items, only %d supported, capping...\n", i + 1, rd.items[i], limit);
+			rd.items[i] = limit;
+			continue;
+		}
+
+		/** this scenario = rd.items[i] < limit **/
+		ShowWarning("itemdb_parse_roulette_db: Level %d has %d items, %d are required. filling with apples\n", i + 1, rd.items[i], limit);
+
+		rd.items[i] = limit;
+		RECREATE(rd.nameid[i], unsigned short, rd.items[i]);
+		RECREATE(rd.qty[i], unsigned short, rd.items[i]);
+		RECREATE(rd.flag[i], int, rd.items[i]);
+
+		for (j = 0; j < MAX_ROULETTE_COLUMNS - i; j++) {
+			if (rd.qty[i][j])
+				continue;
+
+			rd.nameid[i][j] = ITEMID_APPLE;
+			rd.qty[i][j] = 1;
+			rd.flag[i][j] = 1;
+		}
+	}
+
+	ShowStatus("Done reading '"CL_WHITE"%lu"CL_RESET"' entries in '"CL_WHITE"%s"CL_RESET"'.\n", count, db_roulette_table);
+
+	return true;
+}
 
 
 /*======================================
 /*======================================
  * Applies gender restrictions according to settings. [Skotlex]
  * Applies gender restrictions according to settings. [Skotlex]
@@ -1074,6 +1169,7 @@ static char itemdb_gendercheck(struct item_data *id)
 
 
 	return (battle_config.ignore_items_gender) ? 2 : id->sex;
 	return (battle_config.ignore_items_gender) ? 2 : id->sex;
 }
 }
+
 /**
 /**
  * [RRInd]
  * [RRInd]
  * For backwards compatibility, in Renewal mode, MATK from weapons comes from the atk slot
  * For backwards compatibility, in Renewal mode, MATK from weapons comes from the atk slot
@@ -1579,6 +1675,9 @@ void itemdb_reload(void) {
 	itemdb_read();
 	itemdb_read();
 	cashshop_reloaddb();
 	cashshop_reloaddb();
 
 
+	if (!itemdb_parse_roulette_db())
+		battle_config.feature_roulette = 0;
+
 	//Epoque's awesome @reloaditemdb fix - thanks! [Ind]
 	//Epoque's awesome @reloaditemdb fix - thanks! [Ind]
 	//- Fixes the need of a @reloadmobdb after a @reloaditemdb to re-link monster drop data
 	//- Fixes the need of a @reloadmobdb after a @reloaditemdb to re-link monster drop data
 	for( i = 0; i < MAX_MOB_DB; i++ ) {
 	for( i = 0; i < MAX_MOB_DB; i++ ) {
@@ -1649,4 +1748,7 @@ void do_init_itemdb(void) {
 	itemdb_group = uidb_alloc(DB_OPT_BASE);
 	itemdb_group = uidb_alloc(DB_OPT_BASE);
 	itemdb_create_dummy();
 	itemdb_create_dummy();
 	itemdb_read();
 	itemdb_read();
+
+	if (!itemdb_parse_roulette_db())
+		battle_config.feature_roulette = 0;
 }
 }

+ 14 - 0
src/map/itemdb.h

@@ -25,6 +25,9 @@
 
 
 #define MAX_ITEMGROUP_RANDGROUP 4	///Max group for random item (increase this when needed). TODO: Remove this limit and use dynamic size if needed
 #define MAX_ITEMGROUP_RANDGROUP 4	///Max group for random item (increase this when needed). TODO: Remove this limit and use dynamic size if needed
 
 
+#define MAX_ROULETTE_LEVEL 7 /** client-defined value **/
+#define MAX_ROULETTE_COLUMNS 9 /** client-defined value **/
+
 #define CARD0_FORGE 0x00FF
 #define CARD0_FORGE 0x00FF
 #define CARD0_CREATE 0x00FE
 #define CARD0_CREATE 0x00FE
 #define CARD0_PET 0x0100
 #define CARD0_PET 0x0100
@@ -39,6 +42,7 @@ enum item_itemid
 	ITEMID_YELLOW_POTION				= 503,
 	ITEMID_YELLOW_POTION				= 503,
 	ITEMID_WHITE_POTION					= 504,
 	ITEMID_WHITE_POTION					= 504,
 	ITEMID_BLUE_POTION					= 505,
 	ITEMID_BLUE_POTION					= 505,
+	ITEMID_APPLE						= 512,
 	ITEMID_HOLY_WATER					= 523,
 	ITEMID_HOLY_WATER					= 523,
 	ITEMID_PUMPKIN						= 535,
 	ITEMID_PUMPKIN						= 535,
 	ITEMID_RED_SLIM_POTION				= 545,
 	ITEMID_RED_SLIM_POTION				= 545,
@@ -371,6 +375,14 @@ struct s_item_group_db
 	struct s_item_group_random random[MAX_ITEMGROUP_RANDGROUP]; //! TODO: Move this fixed array to dynamic size if needed.
 	struct s_item_group_random random[MAX_ITEMGROUP_RANDGROUP]; //! TODO: Move this fixed array to dynamic size if needed.
 };
 };
 
 
+/// Struct of Roulette db
+struct {
+	unsigned short *nameid[MAX_ROULETTE_LEVEL], /// Item ID
+		           *qty[MAX_ROULETTE_LEVEL]; /// Amount of Item ID
+	int *flag[MAX_ROULETTE_LEVEL]; /// Whether the item is for loss or win
+	int items[MAX_ROULETTE_LEVEL]; /// Number of items in the list for each
+} rd;
+
 ///Main item data struct
 ///Main item data struct
 struct item_data
 struct item_data
 {
 {
@@ -513,6 +525,8 @@ struct s_item_group_db *itemdb_group_exists(unsigned short group_id);
 char itemdb_pc_get_itemgroup(uint16 group_id, struct map_session_data *sd);
 char itemdb_pc_get_itemgroup(uint16 group_id, struct map_session_data *sd);
 uint16 itemdb_get_randgroupitem_count(uint16 group_id, uint8 sub_group, unsigned short nameid);
 uint16 itemdb_get_randgroupitem_count(uint16 group_id, uint8 sub_group, unsigned short nameid);
 
 
+bool itemdb_parse_roulette_db(void);
+
 void itemdb_reload(void);
 void itemdb_reload(void);
 
 
 void do_final_itemdb(void);
 void do_final_itemdb(void);

+ 2 - 1
src/map/log.c

@@ -56,7 +56,7 @@ static char log_picktype2char(e_log_pick_type type)
 	{
 	{
 		case LOG_TYPE_TRADE:            return 'T';  // (T)rade
 		case LOG_TYPE_TRADE:            return 'T';  // (T)rade
 		case LOG_TYPE_VENDING:          return 'V';  // (V)ending
 		case LOG_TYPE_VENDING:          return 'V';  // (V)ending
-		case LOG_TYPE_PICKDROP_PLAYER:  return 'P';  // (P)player
+		case LOG_TYPE_PICKDROP_PLAYER:  return 'P';  // (P)layer
 		case LOG_TYPE_PICKDROP_MONSTER: return 'M';  // (M)onster
 		case LOG_TYPE_PICKDROP_MONSTER: return 'M';  // (M)onster
 		case LOG_TYPE_NPC:              return 'S';  // NPC (S)hop
 		case LOG_TYPE_NPC:              return 'S';  // NPC (S)hop
 		case LOG_TYPE_SCRIPT:           return 'N';  // (N)PC Script
 		case LOG_TYPE_SCRIPT:           return 'N';  // (N)PC Script
@@ -75,6 +75,7 @@ static char log_picktype2char(e_log_pick_type type)
 		case LOG_TYPE_OTHER:			return 'X';  // Other
 		case LOG_TYPE_OTHER:			return 'X';  // Other
 		case LOG_TYPE_CASH:				return '$';  // Cash
 		case LOG_TYPE_CASH:				return '$';  // Cash
 		case LOG_TYPE_BOUND_REMOVAL:	return 'F';  // Removed bound items when guild/party is broken
 		case LOG_TYPE_BOUND_REMOVAL:	return 'F';  // Removed bound items when guild/party is broken
+		case LOG_TYPE_ROULETTE:			return 'Y';  // Roulette Lotter(Y)
 	}
 	}
 
 
 	// should not get here, fallback
 	// should not get here, fallback

+ 22 - 21
src/map/log.h

@@ -24,30 +24,31 @@ typedef enum e_log_chat_type
 typedef enum e_log_pick_type
 typedef enum e_log_pick_type
 {
 {
 	LOG_TYPE_NONE             = 0,
 	LOG_TYPE_NONE             = 0,
-	LOG_TYPE_TRADE            = 0x00001,
-	LOG_TYPE_VENDING          = 0x00002,
-	LOG_TYPE_PICKDROP_PLAYER  = 0x00004,
-	LOG_TYPE_PICKDROP_MONSTER = 0x00008,
-	LOG_TYPE_NPC              = 0x00010,
-	LOG_TYPE_SCRIPT           = 0x00020,
-	LOG_TYPE_STEAL            = 0x00040,
-	LOG_TYPE_CONSUME          = 0x00080,
-	LOG_TYPE_PRODUCE          = 0x00100,
-	LOG_TYPE_MVP              = 0x00200,
-	LOG_TYPE_COMMAND          = 0x00400,
-	LOG_TYPE_STORAGE          = 0x00800,
-	LOG_TYPE_GSTORAGE         = 0x01000,
-	LOG_TYPE_MAIL             = 0x02000,
-	LOG_TYPE_AUCTION          = 0x04000,
-	LOG_TYPE_BUYING_STORE     = 0x08000,
-	LOG_TYPE_OTHER            = 0x10000,
-	LOG_TYPE_CASH             = 0x20000,
-	LOG_TYPE_BANK             = 0x40000,
-	LOG_TYPE_BOUND_REMOVAL    = 0x80000,
+	LOG_TYPE_TRADE            = 0x000001,
+	LOG_TYPE_VENDING          = 0x000002,
+	LOG_TYPE_PICKDROP_PLAYER  = 0x000004,
+	LOG_TYPE_PICKDROP_MONSTER = 0x000008,
+	LOG_TYPE_NPC              = 0x000010,
+	LOG_TYPE_SCRIPT           = 0x000020,
+	LOG_TYPE_STEAL            = 0x000040,
+	LOG_TYPE_CONSUME          = 0x000080,
+	LOG_TYPE_PRODUCE          = 0x000100,
+	LOG_TYPE_MVP              = 0x000200,
+	LOG_TYPE_COMMAND          = 0x000400,
+	LOG_TYPE_STORAGE          = 0x000800,
+	LOG_TYPE_GSTORAGE         = 0x001000,
+	LOG_TYPE_MAIL             = 0x002000,
+	LOG_TYPE_AUCTION          = 0x004000,
+	LOG_TYPE_BUYING_STORE     = 0x008000,
+	LOG_TYPE_OTHER            = 0x010000,
+	LOG_TYPE_CASH             = 0x020000,
+	LOG_TYPE_BANK             = 0x040000,
+	LOG_TYPE_BOUND_REMOVAL    = 0x080000,
+	LOG_TYPE_ROULETTE         = 0x100000,
 	// combinations
 	// combinations
 	LOG_TYPE_LOOT             = LOG_TYPE_PICKDROP_MONSTER|LOG_TYPE_CONSUME,
 	LOG_TYPE_LOOT             = LOG_TYPE_PICKDROP_MONSTER|LOG_TYPE_CONSUME,
 	// all
 	// all
-	LOG_TYPE_ALL              = 0xFFFFF,
+	LOG_TYPE_ALL              = 0xFFFFFF,
 } e_log_pick_type;
 } e_log_pick_type;
 
 
 typedef enum e_log_cash_type
 typedef enum e_log_cash_type

+ 4 - 1
src/map/map.c

@@ -70,6 +70,7 @@ char mob_skill_db2_db[32] = "mob_skill_db2";
 char vendings_db[32] = "vendings";
 char vendings_db[32] = "vendings";
 char vending_items_db[32] = "vending_items";
 char vending_items_db[32] = "vending_items";
 char market_table[32] = "market";
 char market_table[32] = "market";
+char db_roulette_table[32] = "db_roulette";
 
 
 // log database
 // log database
 char log_db_ip[32] = "127.0.0.1";
 char log_db_ip[32] = "127.0.0.1";
@@ -3759,7 +3760,9 @@ int inter_config_read(char *cfgName)
 		else if( strcmpi( w1, "vending_db" ) == 0 )
 		else if( strcmpi( w1, "vending_db" ) == 0 )
 			strcpy( vendings_db, w2 );
 			strcpy( vendings_db, w2 );
 		else if( strcmpi( w1, "vending_items_db" ) == 0 )
 		else if( strcmpi( w1, "vending_items_db" ) == 0 )
-			strcpy( vending_items_db, w2 );
+			strcpy(vending_items_db, w2);
+		else if( strcmpi(w1, "db_roulette_table") == 0)
+			strcpy(db_roulette_table, w2);
 		else if (strcmpi(w1, "market_table") == 0)
 		else if (strcmpi(w1, "market_table") == 0)
 			strcpy(market_table, w2);
 			strcpy(market_table, w2);
 		else
 		else

+ 4 - 0
src/map/map.h

@@ -459,6 +459,9 @@ enum _sp {
 	SP_CHARRENAME=125,
 	SP_CHARRENAME=125,
 	SP_CHARFONT=126,
 	SP_CHARFONT=126,
 	SP_BANK_VAULT = 127,
 	SP_BANK_VAULT = 127,
+	SP_ROULETTE_BRONZE = 128,
+	SP_ROULETTE_SILVER = 129,
+	SP_ROULETTE_GOLD = 130,
 
 
 	// Mercenaries
 	// Mercenaries
 	SP_MERCFLEE=165, SP_MERCKILLS=189, SP_MERCFAITH=190,
 	SP_MERCFLEE=165, SP_MERCKILLS=189, SP_MERCFAITH=190,
@@ -1006,6 +1009,7 @@ extern char mob_skill_db2_db[32];
 extern char vendings_db[32];
 extern char vendings_db[32];
 extern char vending_items_db[32];
 extern char vending_items_db[32];
 extern char market_table[32];
 extern char market_table[32];
+extern char db_roulette_table[32];
 
 
 void do_shutdown(void);
 void do_shutdown(void);
 
 

+ 22 - 1
src/map/pc.c

@@ -1344,6 +1344,12 @@ void pc_reg_received(struct map_session_data *sd)
 	if (battle_config.feature_banking)
 	if (battle_config.feature_banking)
 		sd->bank_vault = pc_readreg2(sd, BANK_VAULT_VAR);
 		sd->bank_vault = pc_readreg2(sd, BANK_VAULT_VAR);
 
 
+	if (battle_config.feature_roulette) {
+		sd->roulette_point.bronze = pc_readreg2(sd, ROULETTE_BRONZE_VAR);
+		sd->roulette_point.silver = pc_readreg2(sd, ROULETTE_SILVER_VAR);
+		sd->roulette_point.gold = pc_readreg2(sd, ROULETTE_GOLD_VAR);
+	}
+
 	//SG map and mob read [Komurka]
 	//SG map and mob read [Komurka]
 	for(i=0;i<MAX_PC_FEELHATE;i++) { //for now - someone need to make reading from txt/sql
 	for(i=0;i<MAX_PC_FEELHATE;i++) { //for now - someone need to make reading from txt/sql
 		uint16 j;
 		uint16 j;
@@ -7602,7 +7608,10 @@ int pc_readparam(struct map_session_data* sd,int type)
 		case SP_CHARMOVE:		 val = sd->status.character_moves; break;
 		case SP_CHARMOVE:		 val = sd->status.character_moves; break;
 		case SP_CHARRENAME:		 val = sd->status.rename; break;
 		case SP_CHARRENAME:		 val = sd->status.rename; break;
 		case SP_CHARFONT:		 val = sd->status.font; break;
 		case SP_CHARFONT:		 val = sd->status.font; break;
-		case SP_BANK_VAULT:			 val = sd->bank_vault; break;
+		case SP_BANK_VAULT:      val = sd->bank_vault; break;
+		case SP_ROULETTE_BRONZE: val = sd->roulette_point.bronze; break;
+		case SP_ROULETTE_SILVER: val = sd->roulette_point.silver; break;
+		case SP_ROULETTE_GOLD:   val = sd->roulette_point.gold; break;
 		case SP_CRITICAL:        val = sd->battle_status.cri/10; break;
 		case SP_CRITICAL:        val = sd->battle_status.cri/10; break;
 		case SP_ASPD:            val = (2000-sd->battle_status.amotion)/10; break;
 		case SP_ASPD:            val = (2000-sd->battle_status.amotion)/10; break;
 		case SP_BASE_ATK:	     val = sd->battle_status.batk; break;
 		case SP_BASE_ATK:	     val = sd->battle_status.batk; break;
@@ -7861,6 +7870,18 @@ bool pc_setparam(struct map_session_data *sd,int type,int val)
 		sd->bank_vault = cap_value(val, 0, MAX_BANK_ZENY);
 		sd->bank_vault = cap_value(val, 0, MAX_BANK_ZENY);
 		pc_setreg2(sd, BANK_VAULT_VAR, sd->bank_vault);
 		pc_setreg2(sd, BANK_VAULT_VAR, sd->bank_vault);
 		return true;
 		return true;
+	case SP_ROULETTE_BRONZE:
+		sd->roulette_point.bronze = val;
+		pc_setreg2(sd, ROULETTE_BRONZE_VAR, sd->roulette_point.bronze);
+		return true;
+	case SP_ROULETTE_SILVER:
+		sd->roulette_point.silver = val;
+		pc_setreg2(sd, ROULETTE_SILVER_VAR, sd->roulette_point.silver);
+		return true;
+	case SP_ROULETTE_GOLD:
+		sd->roulette_point.gold = val;
+		pc_setreg2(sd, ROULETTE_GOLD_VAR, sd->roulette_point.gold);
+		return true;
 	default:
 	default:
 		ShowError("pc_setparam: Attempted to set unknown parameter '%d'.\n", type);
 		ShowError("pc_setparam: Attempted to set unknown parameter '%d'.\n", type);
 		return false;
 		return false;

+ 14 - 0
src/map/pc.h

@@ -31,6 +31,9 @@
 #define MAX_SPIRITCHARM 10 /// Max spirit charms
 #define MAX_SPIRITCHARM 10 /// Max spirit charms
 
 
 #define BANK_VAULT_VAR "#BANKVAULT"
 #define BANK_VAULT_VAR "#BANKVAULT"
+#define ROULETTE_BRONZE_VAR "RouletteBronze"
+#define ROULETTE_SILVER_VAR "RouletteSilver"
+#define ROULETTE_GOLD_VAR "RouletteGold"
 
 
 //Update this max as necessary. 55 is the value needed for Super Baby currently
 //Update this max as necessary. 55 is the value needed for Super Baby currently
 //Raised to 84 since Expanded Super Novice needs it.
 //Raised to 84 since Expanded Super Novice needs it.
@@ -640,6 +643,17 @@ struct map_session_data {
 #ifdef PACKET_OBFUSCATION
 #ifdef PACKET_OBFUSCATION
 	unsigned int cryptKey; ///< Packet obfuscation key to be used for the next received packet
 	unsigned int cryptKey; ///< Packet obfuscation key to be used for the next received packet
 #endif
 #endif
+
+	struct {
+		int bronze, silver, gold; ///< Roulette Coin
+	} roulette_point;
+
+	struct {
+		short stage;
+		short prizeIdx;
+		short prizeStage;
+		bool claimPrize;
+	} roulette;
 };
 };
 
 
 struct eri *pc_sc_display_ers; /// Player's SC display table
 struct eri *pc_sc_display_ers; /// Player's SC display table

+ 36 - 5
src/map/script.c

@@ -10981,9 +10981,9 @@ BUILDIN_FUNC(changebase)
 }
 }
 
 
 /**
 /**
- * Change sec and unequip all item and request for a changesex to char-serv
+ * Change account sex and unequip all item and request for a changesex to char-serv
  * changesex({<char_id>});
  * changesex({<char_id>});
- **/
+ */
 BUILDIN_FUNC(changesex)
 BUILDIN_FUNC(changesex)
 {
 {
 	int i;
 	int i;
@@ -10994,12 +10994,42 @@ BUILDIN_FUNC(changesex)
 
 
 	pc_resetskill(sd,4);
 	pc_resetskill(sd,4);
 	// to avoid any problem with equipment and invalid sex, equipment is unequiped.
 	// to avoid any problem with equipment and invalid sex, equipment is unequiped.
-	for( i=0; i<EQI_MAX; i++ )
-		if( sd->equip_index[i] >= 0 ) pc_unequipitem(sd, sd->equip_index[i], 3);
-	chrif_changesex(sd);
+	for(i = 0; i < EQI_MAX; i++) {
+		if (sd->equip_index[i] >= 0)
+			pc_unequipitem(sd, sd->equip_index[i], 3);
+	}
+
+	chrif_changesex(sd, true);
 	return SCRIPT_CMD_SUCCESS;
 	return SCRIPT_CMD_SUCCESS;
 }
 }
 
 
+/**
+ * Change character's sex and unequip all item and request for a changesex to char-serv
+ * changecharsex({<char_id>});
+ */
+BUILDIN_FUNC(changecharsex)
+{
+#if PACKETVER >= 20141016
+	int i;
+	TBL_PC *sd = NULL;
+
+	if (!script_charid2sd(2,sd))
+		return SCRIPT_CMD_FAILURE;
+
+	pc_resetskill(sd,4);
+	// to avoid any problem with equipment and invalid sex, equipment is unequiped.
+	for (i = 0; i < EQI_MAX; i++) {
+		if (sd->equip_index[i] >= 0)
+			pc_unequipitem(sd, sd->equip_index[i], 3);
+	}
+
+	chrif_changesex(sd, false);
+	return SCRIPT_CMD_SUCCESS;
+#else
+	return SCRIPT_CMD_FAILURE;
+#endif
+}
+
 /*==========================================
 /*==========================================
  * Works like 'announce' but outputs in the common chat window
  * Works like 'announce' but outputs in the common chat window
  *------------------------------------------*/
  *------------------------------------------*/
@@ -20486,6 +20516,7 @@ struct script_function buildin_func[] = {
 	BUILDIN_DEF(skillpointcount,"?"),
 	BUILDIN_DEF(skillpointcount,"?"),
 	BUILDIN_DEF(changebase,"i?"),
 	BUILDIN_DEF(changebase,"i?"),
 	BUILDIN_DEF(changesex,"?"),
 	BUILDIN_DEF(changesex,"?"),
+	BUILDIN_DEF(changecharsex,"?"),
 	BUILDIN_DEF(waitingroom,"si?????"),
 	BUILDIN_DEF(waitingroom,"si?????"),
 	BUILDIN_DEF(delwaitingroom,"?"),
 	BUILDIN_DEF(delwaitingroom,"?"),
 	BUILDIN_DEF2(waitingroomkickall,"kickwaitingroomall","?"),
 	BUILDIN_DEF2(waitingroomkickall,"kickwaitingroomall","?"),