summaryrefslogtreecommitdiff
path: root/src/char
diff options
context:
space:
mode:
authorglighta <glighta@54d463be-8e91-2dee-dedb-b68131a5f0ec>2012-12-05 14:16:04 +0000
committerglighta <glighta@54d463be-8e91-2dee-dedb-b68131a5f0ec>2012-12-05 14:16:04 +0000
commit8c8ef2dd18e2e729450a59e18c6d6b0df9dc4a09 (patch)
tree2a5bbd19577d058691c0747a1b902573c1cb30cf /src/char
parenta6a7512d138abf322b48f16ba4dbb583e614d5b9 (diff)
downloadhercules-8c8ef2dd18e2e729450a59e18c6d6b0df9dc4a09.tar.gz
hercules-8c8ef2dd18e2e729450a59e18c6d6b0df9dc4a09.tar.bz2
hercules-8c8ef2dd18e2e729450a59e18c6d6b0df9dc4a09.tar.xz
hercules-8c8ef2dd18e2e729450a59e18c6d6b0df9dc4a09.zip
- Re-committed r16985 (pid:161281).
git-svn-id: https://rathena.svn.sourceforge.net/svnroot/rathena/trunk@17001 54d463be-8e91-2dee-dedb-b68131a5f0ec
Diffstat (limited to 'src/char')
-rw-r--r--src/char/char.c156
1 files changed, 83 insertions, 73 deletions
diff --git a/src/char/char.c b/src/char/char.c
index 0003e590f..eb214fedf 100644
--- a/src/char/char.c
+++ b/src/char/char.c
@@ -243,7 +243,7 @@ void set_char_online(int map_id, int char_id, int account_id)
{
struct online_char_data* character;
struct mmo_charstatus *cp;
-
+
//Update DB
if( SQL_ERROR == Sql_Query(sql_handle, "UPDATE `%s` SET `online`='1' WHERE `char_id`='%d' LIMIT 1", char_db, char_id) )
Sql_ShowDebug(sql_handle);
@@ -787,7 +787,7 @@ int memitemdata_to_sql(const struct item items[], int max, int id, int tableswit
for( j = 0; j < MAX_SLOTS; ++j )
StringBuf_Printf(&buf, ", `card%d`=%d", j, items[i].card[j]);
StringBuf_Printf(&buf, " WHERE `id`='%d' LIMIT 1", item.id);
-
+
if( SQL_ERROR == Sql_QueryStr(sql_handle, StringBuf_Value(&buf)) )
{
Sql_ShowDebug(sql_handle);
@@ -858,19 +858,19 @@ int inventory_to_sql(const struct item items[], int max, int id) {
bool* flag; // bit array for inventory matching
bool found;
int errors = 0;
-
-
+
+
// The following code compares inventory with current database values
// and performs modification/deletion/insertion only on relevant rows.
// This approach is more complicated than a trivial delete&insert, but
// it significantly reduces cpu load on the database server.
-
+
StringBuf_Init(&buf);
StringBuf_AppendStr(&buf, "SELECT `id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `expire_time`, `favorite`");
for( j = 0; j < MAX_SLOTS; ++j )
StringBuf_Printf(&buf, ", `card%d`", j);
StringBuf_Printf(&buf, " FROM `%s` WHERE `char_id`='%d'", inventory_db, id);
-
+
stmt = SqlStmt_Malloc(sql_handle);
if( SQL_ERROR == SqlStmt_PrepareStr(stmt, StringBuf_Value(&buf))
|| SQL_ERROR == SqlStmt_Execute(stmt) )
@@ -880,7 +880,7 @@ int inventory_to_sql(const struct item items[], int max, int id) {
StringBuf_Destroy(&buf);
return 1;
}
-
+
SqlStmt_BindColumn(stmt, 0, SQLDT_INT, &item.id, 0, NULL, NULL);
SqlStmt_BindColumn(stmt, 1, SQLDT_SHORT, &item.nameid, 0, NULL, NULL);
SqlStmt_BindColumn(stmt, 2, SQLDT_SHORT, &item.amount, 0, NULL, NULL);
@@ -892,10 +892,10 @@ int inventory_to_sql(const struct item items[], int max, int id) {
SqlStmt_BindColumn(stmt, 8, SQLDT_CHAR, &item.favorite, 0, NULL, NULL);
for( j = 0; j < MAX_SLOTS; ++j )
SqlStmt_BindColumn(stmt, 9+j, SQLDT_SHORT, &item.card[j], 0, NULL, NULL);
-
+
// bit array indicating which inventory items have already been matched
flag = (bool*) aCalloc(max, sizeof(bool));
-
+
while( SQL_SUCCESS == SqlStmt_NextRow(stmt) ) {
found = false;
// search for the presence of the item in the char's inventory
@@ -903,7 +903,7 @@ int inventory_to_sql(const struct item items[], int max, int id) {
// skip empty and already matched entries
if( items[i].nameid == 0 || flag[i] )
continue;
-
+
if( items[i].nameid == item.nameid
&& items[i].card[0] == item.card[0]
&& items[i].card[2] == item.card[2]
@@ -927,13 +927,13 @@ int inventory_to_sql(const struct item items[], int max, int id) {
for( j = 0; j < MAX_SLOTS; ++j )
StringBuf_Printf(&buf, ", `card%d`=%d", j, items[i].card[j]);
StringBuf_Printf(&buf, " WHERE `id`='%d' LIMIT 1", item.id);
-
+
if( SQL_ERROR == Sql_QueryStr(sql_handle, StringBuf_Value(&buf)) ) {
Sql_ShowDebug(sql_handle);
errors++;
}
}
-
+
found = flag[i] = true; //Item dealt with,
break; //skip to next item in the db.
}
@@ -946,40 +946,40 @@ int inventory_to_sql(const struct item items[], int max, int id) {
}
}
SqlStmt_Free(stmt);
-
+
StringBuf_Clear(&buf);
StringBuf_Printf(&buf, "INSERT INTO `%s` (`char_id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `expire_time`, `favorite`", inventory_db);
for( j = 0; j < MAX_SLOTS; ++j )
StringBuf_Printf(&buf, ", `card%d`", j);
StringBuf_AppendStr(&buf, ") VALUES ");
-
+
found = false;
// insert non-matched items into the db as new items
for( i = 0; i < max; ++i ) {
// skip empty and already matched entries
if( items[i].nameid == 0 || flag[i] )
continue;
-
+
if( found )
StringBuf_AppendStr(&buf, ",");
else
found = true;
-
+
StringBuf_Printf(&buf, "('%d', '%d', '%d', '%d', '%d', '%d', '%d', '%u', '%d'",
id, items[i].nameid, items[i].amount, items[i].equip, items[i].identify, items[i].refine, items[i].attribute, items[i].expire_time, items[i].favorite);
for( j = 0; j < MAX_SLOTS; ++j )
StringBuf_Printf(&buf, ", '%d'", items[i].card[j]);
StringBuf_AppendStr(&buf, ")");
}
-
+
if( found && SQL_ERROR == Sql_QueryStr(sql_handle, StringBuf_Value(&buf)) ) {
Sql_ShowDebug(sql_handle);
errors++;
}
-
+
StringBuf_Destroy(&buf);
aFree(flag);
-
+
return errors;
}
@@ -1090,7 +1090,7 @@ int mmo_char_fromsql(int char_id, struct mmo_charstatus* p, bool load_everything
#endif
memset(p, 0, sizeof(struct mmo_charstatus));
-
+
if (save_log) ShowInfo("Char load request (%d)\n", char_id);
stmt = SqlStmt_Malloc(sql_handle);
@@ -1139,7 +1139,7 @@ int mmo_char_fromsql(int char_id, struct mmo_charstatus* p, bool load_everything
|| SQL_ERROR == SqlStmt_BindColumn(stmt, 26, SQLDT_INT, &p->guild_id, 0, NULL, NULL)
|| SQL_ERROR == SqlStmt_BindColumn(stmt, 27, SQLDT_INT, &p->pet_id, 0, NULL, NULL)
|| SQL_ERROR == SqlStmt_BindColumn(stmt, 28, SQLDT_INT, &p->hom_id, 0, NULL, NULL)
- || SQL_ERROR == SqlStmt_BindColumn(stmt, 29, SQLDT_INT, &p->ele_id, 0, NULL, NULL)
+ || SQL_ERROR == SqlStmt_BindColumn(stmt, 29, SQLDT_INT, &p->ele_id, 0, NULL, NULL)
|| SQL_ERROR == SqlStmt_BindColumn(stmt, 30, SQLDT_SHORT, &p->hair, 0, NULL, NULL)
|| SQL_ERROR == SqlStmt_BindColumn(stmt, 31, SQLDT_SHORT, &p->hair_color, 0, NULL, NULL)
|| SQL_ERROR == SqlStmt_BindColumn(stmt, 32, SQLDT_SHORT, &p->clothes_color, 0, NULL, NULL)
@@ -1362,7 +1362,7 @@ int rename_char_sql(struct char_session_data *sd, int char_id)
if( sd->new_name[0] == 0 ) // Not ready for rename
return 2;
-
+
if( !mmo_char_fromsql(char_id, &char_dat, false) ) // Only the short data is needed.
return 2;
@@ -1478,7 +1478,7 @@ int make_new_char_sql(struct char_session_data* sd, char* name_, int str, int ag
//check other inputs
#if PACKETVER >= 20120307
- if(slot >= MAX_CHARS)
+ if(slot >= MAX_CHARS)
#else
if((slot >= MAX_CHARS) // slots
|| (str + agi + vit + int_ + dex + luk != 6*5 ) // stats
@@ -1532,7 +1532,7 @@ int make_new_char_sql(struct char_session_data* sd, char* name_, int str, int ag
{
Sql_ShowDebug(sql_handle);
return -2; //No, stop the procedure!
- }
+ }
#endif
//Retrieve the newly auto-generated char id
char_id = (int)Sql_LastInsertId(sql_handle);
@@ -1580,11 +1580,11 @@ int delete_char_sql(int char_id)
{
char name[NAME_LENGTH];
char esc_name[NAME_LENGTH*2+1]; //Name needs be escaped.
- int account_id, party_id, guild_id, hom_id, base_level, partner_id, father_id, mother_id;
- char* data;
+ int account_id, party_id, guild_id, hom_id, base_level, partner_id, father_id, mother_id, elemental_id;
+ char *data;
size_t len;
- if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `name`,`account_id`,`party_id`,`guild_id`,`base_level`,`homun_id`,`partner_id`,`father`,`mother` FROM `%s` WHERE `char_id`='%d'", char_db, char_id) )
+ if (SQL_ERROR == Sql_Query(sql_handle, "SELECT `name`,`account_id`,`party_id`,`guild_id`,`base_level`,`homun_id`,`partner_id`,`father`,`mother`,`elemental_id` FROM `%s` WHERE `char_id`='%d'", char_db, char_id))
Sql_ShowDebug(sql_handle);
if( SQL_SUCCESS != Sql_NextRow(sql_handle) )
@@ -1603,6 +1603,8 @@ int delete_char_sql(int char_id)
Sql_GetData(sql_handle, 6, &data, NULL); partner_id = atoi(data);
Sql_GetData(sql_handle, 7, &data, NULL); father_id = atoi(data);
Sql_GetData(sql_handle, 8, &data, NULL); mother_id = atoi(data);
+ Sql_GetData(sql_handle, 9, &data, NULL);
+ elemental_id = atoi(data);
Sql_EscapeStringLen(sql_handle, esc_name, name, min(len, NAME_LENGTH));
Sql_FreeResult(sql_handle);
@@ -1652,11 +1654,15 @@ int delete_char_sql(int char_id)
if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` USING `%s` JOIN `%s` ON `pet_id` = `card1`|`card2`<<16 WHERE `%s`.char_id = '%d' AND card0 = -256", pet_db, pet_db, cart_db, cart_db, char_id) )
Sql_ShowDebug(sql_handle);
- /* remove homunculus */
+ /* remove homunculus */
if( hom_id )
mapif_homunculus_delete(hom_id);
- /* remove mercenary data */
+ /* remove elemental */
+ if (elemental_id)
+ mapif_elemental_delete(elemental_id);
+
+ /* remove mercenary data */
mercenary_owner_delete(char_id);
/* delete char's friends list */
@@ -1689,11 +1695,15 @@ int delete_char_sql(int char_id)
/* delete character registry */
if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `type`=3 AND `char_id`='%d'", reg_db, char_id) )
Sql_ShowDebug(sql_handle);
-
+
/* delete skills */
if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `char_id`='%d'", skill_db, char_id) )
Sql_ShowDebug(sql_handle);
-
+
+ /* delete mails (only received) */
+ if (SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `dest_id`='%d'", mail_db, char_id))
+ Sql_ShowDebug(sql_handle);
+
#ifdef ENABLE_SC_SAVING
/* status changes */
if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `account_id` = '%d' AND `char_id`='%d'", scdata_db, account_id, char_id) )
@@ -1774,10 +1784,10 @@ int mmo_char_tobuf(uint8* buffer, struct mmo_charstatus* p)
WBUFW(buf,50) = DEFAULT_WALK_SPEED; // p->speed;
WBUFW(buf,52) = p->class_;
WBUFW(buf,54) = p->hair;
-
+
//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,58) = p->base_level;
WBUFW(buf,60) = min(p->skill_point, INT16_MAX);
WBUFW(buf,62) = p->head_bottom;
@@ -2015,7 +2025,7 @@ void loginif_on_ready(void)
int i;
loginif_check_shutdown();
-
+
//Send online accounts to login server.
send_accounts_tologin(INVALID_TIMER, gettick(), 0, 0);
@@ -2029,7 +2039,7 @@ void loginif_on_ready(void)
int parse_fromlogin(int fd) {
struct char_session_data* sd = NULL;
int i;
-
+
// only process data from the login-server
if( fd != login_fd ) {
ShowDebug("parse_fromlogin: Disconnecting invalid session #%d (is not the login-server)\n", fd);
@@ -2228,9 +2238,9 @@ int parse_fromlogin(int fd) {
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'", char_db, class_[i], char_id[i]) )
- 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'", char_db, class_[i], char_id[i]) )
+ Sql_ShowDebug(sql_handle);
+
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);
}
@@ -2366,7 +2376,7 @@ void do_init_loginif(void)
// establish char-login connection if not present
add_timer_func_list(check_connect_login_server, "check_connect_login_server");
add_timer_interval(gettick() + 1000, check_connect_login_server, 0, 0, 10 * 1000);
-
+
// send a list of all online account IDs to login server
add_timer_func_list(send_accounts_tologin, "send_accounts_tologin");
add_timer_interval(gettick() + 1000, send_accounts_tologin, 0, 0, 3600 * 1000); //Sync online accounts every hour
@@ -2471,7 +2481,7 @@ int char_send_fame_list(int fd)
{
int i, len = 8;
unsigned char buf[32000];
-
+
WBUFW(buf,0) = 0x2b1b;
for(i = 0; i < fame_list_size_smith && smith_fame_list[i].id; i++) {
@@ -2630,7 +2640,7 @@ int parse_frommap(int fd)
ShowStatus("Map-Server %d connected: %d maps, from IP %d.%d.%d.%d port %d.\n",
id, j, CONVIP(server[id].ip), server[id].port);
ShowStatus("Map-server %d loading complete.\n", id);
-
+
// send name for wisp to player
WFIFOHEAD(fd, 3 + NAME_LENGTH);
WFIFOW(fd,0) = 0x2afb;
@@ -2816,7 +2826,7 @@ int parse_frommap(int fd)
uint32 login_id2 = RFIFOL(fd,10);
uint32 ip = RFIFOL(fd,14);
RFIFOSKIP(fd,18);
-
+
if( runflag != CHARSERVER_ST_RUNNING )
{
WFIFOHEAD(fd,7);
@@ -2869,7 +2879,7 @@ int parse_frommap(int fd)
mmo_char_fromsql(RFIFOL(fd,14), &char_dat, true);
char_data = (struct mmo_charstatus*)uidb_get(char_db_,RFIFOL(fd,14));
}
-
+
if( runflag == CHARSERVER_ST_RUNNING &&
session_isActive(map_fd) &&
char_data )
@@ -3147,7 +3157,7 @@ int parse_frommap(int fd)
set_all_offline(id);
RFIFOSKIP(fd,2);
break;
-
+
case 0x2b19: // Character set online [Wizputer]
if (RFIFOREST(fd) < 10)
return 0;
@@ -3236,7 +3246,7 @@ int parse_frommap(int fd)
}
if( runflag == CHARSERVER_ST_RUNNING &&
cd != NULL &&
- node != NULL &&
+ node != NULL &&
node->account_id == account_id &&
node->char_id == char_id &&
node->login_id1 == login_id1 &&
@@ -3288,29 +3298,29 @@ int parse_frommap(int fd)
else {
int sfd;/* stat server fd */
RFIFOSKIP(fd, 2);/* we skip first 2 bytes which are the 0x3008, so we end up with a buffer equal to the one we send */
-
+
if( (sfd = make_connection(host2ip("stats.rathena.org"),(uint16)25421,true) ) == -1 ) {
RFIFOSKIP(fd, RFIFOW(fd,2) );/* skip this packet */
break;/* connection not possible, we drop the report */
}
-
+
session[sfd]->flag.server = 1;/* to ensure we won't drop our own packet */
-
+
WFIFOHEAD(sfd, RFIFOW(fd,2) );
-
+
memcpy((char*)WFIFOP(sfd,0), (char*)RFIFOP(fd, 0), RFIFOW(fd,2));
-
+
WFIFOSET(sfd, RFIFOW(fd,2) );
-
+
flush_fifo(sfd);
-
+
do_close(sfd);
-
+
RFIFOSKIP(fd, RFIFOW(fd,2) );/* skip this packet */
}
break;
-
+
default:
{
// inter server - packet
@@ -3325,7 +3335,7 @@ int parse_frommap(int fd)
}
} // switch
} // while
-
+
return 0;
}
@@ -3348,7 +3358,7 @@ void do_final_mapif(void)
int search_mapserver(unsigned short map, uint32 ip, uint16 port)
{
int i, j;
-
+
for(i = 0; i < ARRAYLENGTH(server); i++)
{
if (server[i].fd > 0
@@ -3458,9 +3468,9 @@ static void char_delete2_req(int fd, struct char_session_data* sd)
char_delete2_ack(fd, char_id, 3, 0);
return;
}
-
+
Sql_GetData(sql_handle, 0, &data, NULL); delete_date = strtoul(data, NULL, 10);
-
+
if( delete_date ) {// character already queued for deletion
char_delete2_ack(fd, char_id, 0, 0);
return;
@@ -3660,7 +3670,7 @@ int parse_char(int fd)
//TODO: and perhaps send back a reply?
break;
}
-
+
CREATE(session[fd]->session_data, struct char_session_data, 1);
sd = (struct char_session_data*)session[fd]->session_data;
sd->account_id = account_id;
@@ -3918,9 +3928,9 @@ int parse_char(int fd)
ShowInfo(CL_RED"Request Char Deletion: "CL_GREEN"%d (%d)"CL_RESET"\n", sd->account_id, cid);
memcpy(email, RFIFOP(fd,6), 40);
RFIFOSKIP(fd,( cmd == 0x68) ? 46 : 56);
-
- // Check if e-mail is correct
- if(strcmpi(email, sd->email) && //email does not matches and
+
+ // Check if e-mail is correct
+ if(strcmpi(email, sd->email) && //email does not matches and
(
strcmp("a@a.com", sd->email) || //it is not default email, or
(strcmp("a@a.com", email) && strcmp("", email)) //email sent does not matches default
@@ -3947,7 +3957,7 @@ int parse_char(int fd)
for(ch = i; ch < MAX_CHARS-1; ch++)
sd->found_char[ch] = sd->found_char[ch+1];
sd->found_char[MAX_CHARS-1] = -1;
-
+
/* Delete character */
if(delete_char_sql(cid)<0){
//can't delete the char
@@ -3998,7 +4008,7 @@ int parse_char(int fd)
i = 1;
safestrncpy(sd->new_name, name, NAME_LENGTH);
}
- else
+ else
i = 0;
WFIFOHEAD(fd, 4);
@@ -4279,7 +4289,7 @@ int check_connect_login_server(int tid, unsigned int tick, int id, intptr_t data
session[login_fd]->func_parse = parse_fromlogin;
session[login_fd]->flag.server = 1;
realloc_fifo(login_fd, FIFOSIZE_SERVERLINK, FIFOSIZE_SERVERLINK);
-
+
WFIFOHEAD(login_fd,86);
WFIFOW(login_fd,0) = 0x2710;
memcpy(WFIFOP(login_fd,2), userid, 24);
@@ -4292,7 +4302,7 @@ int check_connect_login_server(int tid, unsigned int tick, int id, intptr_t data
WFIFOW(login_fd,82) = char_maintenance;
WFIFOW(login_fd,84) = char_new_display; //only display (New) if they want to [Kevin]
WFIFOSET(login_fd,86);
-
+
return 1;
}
@@ -4342,7 +4352,7 @@ int char_lan_config_read(const char *lancfgName)
FILE *fp;
int line_num = 0;
char line[1024], w1[64], w2[64], w3[64], w4[64];
-
+
if((fp = fopen(lancfgName, "r")) == NULL) {
ShowWarning("LAN Support configuration file is not found: %s\n", lancfgName);
return 1;
@@ -4354,7 +4364,7 @@ int char_lan_config_read(const char *lancfgName)
continue;
if(sscanf(line,"%[^:]: %[^:]:%[^:]:%[^\r\n]", w1, w2, w3, w4) != 4) {
-
+
ShowWarning("Error syntax of configuration file %s in line %d.\n", lancfgName, line_num);
continue;
}
@@ -4375,7 +4385,7 @@ int char_lan_config_read(const char *lancfgName)
ShowError("%s: Configuration Error: The char server (%s) and map server (%s) belong to different subnetworks!\n", lancfgName, w3, w4);
continue;
}
-
+
subnet_count++;
}
}
@@ -4639,7 +4649,7 @@ void do_final(void)
inter_final();
flush_fifos();
-
+
do_final_mapif();
do_final_loginif();
@@ -4711,7 +4721,7 @@ int do_init(int argc, char **argv)
}
inter_init_sql((argc > 2) ? argv[2] : inter_cfgName); // inter server configuration
-
+
auth_db = idb_alloc(DB_OPT_RELEASE_DATA);
online_char_db = idb_alloc(DB_OPT_RELEASE_DATA);
mmo_char_sql_init();
@@ -4754,7 +4764,7 @@ int do_init(int argc, char **argv)
{
//##TODO invoke a CONSOLE_START plugin event
}
-
+
//Cleaning the tables for NULL entrys @ startup [Sirius]
//Chardb clean
if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `account_id` = '0'", char_db) )
@@ -4771,7 +4781,7 @@ int do_init(int argc, char **argv)
set_defaultparse(parse_char);
char_fd = make_listen_bind(bind_ip, char_port);
ShowStatus("The char-server is "CL_GREEN"ready"CL_RESET" (Server is listening on the port %d).\n\n", char_port);
-
+
if( runflag != CORE_ST_STOP )
{
shutdown_callback = do_shutdown;