From de1506a74746b91e069109f1ba3539944241a8cd Mon Sep 17 00:00:00 2001 From: Chuck Miller Date: Wed, 24 Mar 2010 18:30:12 -0400 Subject: Add the moneycount tool --- src/tool/moneycount/Makefile | 13 ++ src/tool/moneycount/athena_text.cpp | 263 ++++++++++++++++++++++++++++ src/tool/moneycount/athena_text.h | 9 + src/tool/moneycount/main.cpp | 99 +++++++++++ src/tool/moneycount/mmo.h | 329 ++++++++++++++++++++++++++++++++++++ 5 files changed, 713 insertions(+) create mode 100644 src/tool/moneycount/Makefile create mode 100644 src/tool/moneycount/athena_text.cpp create mode 100644 src/tool/moneycount/athena_text.h create mode 100644 src/tool/moneycount/main.cpp create mode 100644 src/tool/moneycount/mmo.h (limited to 'src/tool') diff --git a/src/tool/moneycount/Makefile b/src/tool/moneycount/Makefile new file mode 100644 index 0000000..27cd98e --- /dev/null +++ b/src/tool/moneycount/Makefile @@ -0,0 +1,13 @@ +all: moneycount + +OBJECTS = main.o athena_text.o +CPP = g++ + +moneycount: $(OBJECTS) + $(CPP) -o $@ $(OBJECTS) $(COMMON_OBJS) $(LIBS) +main.o: main.cpp +athena_text.o: athena_text.cpp athena_text.h + + +clean: + rm -f *.o moneycount diff --git a/src/tool/moneycount/athena_text.cpp b/src/tool/moneycount/athena_text.cpp new file mode 100644 index 0000000..59269fa --- /dev/null +++ b/src/tool/moneycount/athena_text.cpp @@ -0,0 +1,263 @@ +#include "athena_text.h" + +#include +#include +#include + +#include "mmo.h" + +//------------------------------------------------------------------------- +// Function to set the character from the line (at read of characters file) +//------------------------------------------------------------------------- +int mmo_char_fromstr (char *str, struct mmo_charstatus *p) +{ + int tmp_int[256]; + int set, next, len, i; + + // initilialise character + memset (p, '\0', sizeof (struct mmo_charstatus)); + + // If it's not char structure of version 1008 and after + if ((set = sscanf (str, "%d\t%d,%d\t%[^\t]\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d\t%d,%d,%d,%d,%d,%d\t%d,%d" "\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d,%d" "\t%[^,],%d,%d\t%[^,],%d,%d,%d%n", &tmp_int[0], &tmp_int[1], &tmp_int[2], p->name, // + &tmp_int[3], &tmp_int[4], &tmp_int[5], &tmp_int[6], &tmp_int[7], &tmp_int[8], &tmp_int[9], &tmp_int[10], &tmp_int[11], &tmp_int[12], &tmp_int[13], &tmp_int[14], &tmp_int[15], &tmp_int[16], &tmp_int[17], &tmp_int[18], &tmp_int[19], &tmp_int[20], &tmp_int[21], &tmp_int[22], &tmp_int[23], // + &tmp_int[24], &tmp_int[25], &tmp_int[26], &tmp_int[27], &tmp_int[28], &tmp_int[29], &tmp_int[30], &tmp_int[31], &tmp_int[32], &tmp_int[33], &tmp_int[34], p->last_point.map, &tmp_int[35], &tmp_int[36], // + p->save_point.map, &tmp_int[37], &tmp_int[38], + &tmp_int[39], &next)) != 43) + { + tmp_int[39] = 0; // partner id + // If not char structure from version 384 to 1007 + if ((set = sscanf (str, "%d\t%d,%d\t%[^\t]\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d\t%d,%d,%d,%d,%d,%d\t%d,%d" "\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d,%d" "\t%[^,],%d,%d\t%[^,],%d,%d%n", &tmp_int[0], &tmp_int[1], &tmp_int[2], p->name, // + &tmp_int[3], &tmp_int[4], &tmp_int[5], &tmp_int[6], &tmp_int[7], &tmp_int[8], &tmp_int[9], &tmp_int[10], &tmp_int[11], &tmp_int[12], &tmp_int[13], &tmp_int[14], &tmp_int[15], &tmp_int[16], &tmp_int[17], &tmp_int[18], &tmp_int[19], &tmp_int[20], &tmp_int[21], &tmp_int[22], &tmp_int[23], // + &tmp_int[24], &tmp_int[25], &tmp_int[26], &tmp_int[27], &tmp_int[28], &tmp_int[29], &tmp_int[30], &tmp_int[31], &tmp_int[32], &tmp_int[33], &tmp_int[34], p->last_point.map, &tmp_int[35], &tmp_int[36], // + p->save_point.map, &tmp_int[37], &tmp_int[38], + &next)) != 42) + { + // It's char structure of a version before 384 + tmp_int[26] = 0; // pet id + set = sscanf (str, "%d\t%d,%d\t%[^\t]\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d\t%d,%d,%d,%d,%d,%d\t%d,%d" "\t%d,%d,%d\t%d,%d\t%d,%d,%d\t%d,%d,%d,%d,%d" "\t%[^,],%d,%d\t%[^,],%d,%d%n", &tmp_int[0], &tmp_int[1], &tmp_int[2], p->name, // + &tmp_int[3], &tmp_int[4], &tmp_int[5], &tmp_int[6], &tmp_int[7], &tmp_int[8], &tmp_int[9], &tmp_int[10], &tmp_int[11], &tmp_int[12], &tmp_int[13], &tmp_int[14], &tmp_int[15], &tmp_int[16], &tmp_int[17], &tmp_int[18], &tmp_int[19], &tmp_int[20], &tmp_int[21], &tmp_int[22], &tmp_int[23], // + &tmp_int[24], &tmp_int[25], // + &tmp_int[27], &tmp_int[28], &tmp_int[29], &tmp_int[30], &tmp_int[31], &tmp_int[32], &tmp_int[33], &tmp_int[34], p->last_point.map, &tmp_int[35], &tmp_int[36], // + p->save_point.map, &tmp_int[37], &tmp_int[38], + &next); + set += 2; + //printf("char: old char data ver.1\n"); + // Char structure of version 1007 or older + } + else + { + set++; + //printf("char: old char data ver.2\n"); + } + // Char structure of version 1008+ + } + else + { + //printf("char: new char data ver.3\n"); + } + if (set != 43) + return 0; + + p->char_id = tmp_int[0]; + p->account_id = tmp_int[1]; + p->char_num = tmp_int[2]; + p->classb = tmp_int[3]; + p->base_level = tmp_int[4]; + p->job_level = tmp_int[5]; + p->base_exp = tmp_int[6]; + p->job_exp = tmp_int[7]; + p->zeny = tmp_int[8]; + p->hp = tmp_int[9]; + p->max_hp = tmp_int[10]; + p->sp = tmp_int[11]; + p->max_sp = tmp_int[12]; + p->str = tmp_int[13]; + p->agi = tmp_int[14]; + p->vit = tmp_int[15]; + p->int_ = tmp_int[16]; + p->dex = tmp_int[17]; + p->luk = tmp_int[18]; + p->status_point = tmp_int[19]; + p->skill_point = tmp_int[20]; + p->option = tmp_int[21]; + p->karma = tmp_int[22]; + p->manner = tmp_int[23]; + p->party_id = tmp_int[24]; + p->guild_id = tmp_int[25]; +// p->pet_id = tmp_int[26]; + p->hair = tmp_int[27]; + p->hair_color = tmp_int[28]; + p->clothes_color = tmp_int[29]; + p->weapon = tmp_int[30]; + p->shield = tmp_int[31]; + p->head_top = tmp_int[32]; + p->head_mid = tmp_int[33]; + p->head_bottom = tmp_int[34]; + p->last_point.x = tmp_int[35]; + p->last_point.y = tmp_int[36]; + p->save_point.x = tmp_int[37]; + p->save_point.y = tmp_int[38]; + p->partner_id = tmp_int[39]; + + if (str[next] == '\n' || str[next] == '\r') + return 1; // ?V?K?f?[?^ + + next++; + + for (i = 0; str[next] && str[next] != '\t'; i++) + { + if (sscanf + (str + next, "%[^,],%d,%d%n", p->memo_point[i].map, &tmp_int[0], + &tmp_int[1], &len) != 3) + return -3; + p->memo_point[i].x = tmp_int[0]; + p->memo_point[i].y = tmp_int[1]; + next += len; + if (str[next] == ' ') + next++; + } + + next++; + + for (i = 0; str[next] && str[next] != '\t'; i++) + { + if (sscanf (str + next, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d%n", + &tmp_int[0], &tmp_int[1], &tmp_int[2], &tmp_int[3], + &tmp_int[4], &tmp_int[5], &tmp_int[6], + &tmp_int[7], &tmp_int[8], &tmp_int[9], &tmp_int[10], + &tmp_int[11], &len) == 12) + { + // do nothing, it's ok + } + else if (sscanf (str + next, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d%n", + &tmp_int[0], &tmp_int[1], &tmp_int[2], &tmp_int[3], + &tmp_int[4], &tmp_int[5], &tmp_int[6], + &tmp_int[7], &tmp_int[8], &tmp_int[9], &tmp_int[10], + &len) == 11) + { + tmp_int[11] = 0; // broken doesn't exist in this version -> 0 + } + else // invalid structure + return -4; + p->inventory[i].id = tmp_int[0]; + p->inventory[i].nameid = tmp_int[1]; + p->inventory[i].amount = tmp_int[2]; + p->inventory[i].equip = tmp_int[3]; + p->inventory[i].identify = tmp_int[4]; + p->inventory[i].refine = tmp_int[5]; + p->inventory[i].attribute = tmp_int[6]; + p->inventory[i].card[0] = tmp_int[7]; + p->inventory[i].card[1] = tmp_int[8]; + p->inventory[i].card[2] = tmp_int[9]; + p->inventory[i].card[3] = tmp_int[10]; + p->inventory[i].broken = tmp_int[11]; + next += len; + if (str[next] == ' ') + next++; + } + + next++; + + for (i = 0; str[next] && str[next] != '\t'; i++) + { + if (sscanf (str + next, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d%n", + &tmp_int[0], &tmp_int[1], &tmp_int[2], &tmp_int[3], + &tmp_int[4], &tmp_int[5], &tmp_int[6], + &tmp_int[7], &tmp_int[8], &tmp_int[9], &tmp_int[10], + &tmp_int[11], &len) == 12) + { + // do nothing, it's ok + } + else if (sscanf (str + next, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d%n", + &tmp_int[0], &tmp_int[1], &tmp_int[2], &tmp_int[3], + &tmp_int[4], &tmp_int[5], &tmp_int[6], + &tmp_int[7], &tmp_int[8], &tmp_int[9], &tmp_int[10], + &len) == 11) + { + tmp_int[11] = 0; // broken doesn't exist in this version -> 0 + } + else // invalid structure + return -5; + p->cart[i].id = tmp_int[0]; + p->cart[i].nameid = tmp_int[1]; + p->cart[i].amount = tmp_int[2]; + p->cart[i].equip = tmp_int[3]; + p->cart[i].identify = tmp_int[4]; + p->cart[i].refine = tmp_int[5]; + p->cart[i].attribute = tmp_int[6]; + p->cart[i].card[0] = tmp_int[7]; + p->cart[i].card[1] = tmp_int[8]; + p->cart[i].card[2] = tmp_int[9]; + p->cart[i].card[3] = tmp_int[10]; + p->cart[i].broken = tmp_int[11]; + next += len; + if (str[next] == ' ') + next++; + } + + next++; + + for (i = 0; str[next] && str[next] != '\t'; i++) + { + if (sscanf (str + next, "%d,%d%n", &tmp_int[0], &tmp_int[1], &len) != + 2) + return -6; + p->skill[tmp_int[0]].id = tmp_int[0]; + p->skill[tmp_int[0]].lv = tmp_int[1] & 0xffff; + p->skill[tmp_int[0]].flags = ((tmp_int[1] >> 16) & 0xffff); + next += len; + if (str[next] == ' ') + next++; + } + + next++; + + for (i = 0; + str[next] && str[next] != '\t' && str[next] != '\n' + && str[next] != '\r'; i++) + { // global_reg?????O??athena.txt????^????'\n'?`?F?b?N + if (sscanf + (str + next, "%[^,],%d%n", p->global_reg[i].str, + &p->global_reg[i].value, &len) != 2) + { + // because some scripts are not correct, the str can be "". So, we must check that. + // If it's, we must not refuse the character, but just this REG value. + // Character line will have something like: nov_2nd_cos,9 ,9 nov_1_2_cos_c,1 (here, ,9 is not good) + if (str[next] == ',' + && sscanf (str + next, ",%d%n", &p->global_reg[i].value, + &len) == 1) + i--; + else + return -7; + } + next += len; + if (str[next] == ' ') + next++; + } + p->global_reg_num = i; + + return 1; +} + +int accreg_fromstr (char *str, struct accreg *reg) +{ + int j, v, n; + char buf[128]; + const char *p = str; + + if (sscanf (p, "%d\t%n", ®->account_id, &n) != 1 + || reg->account_id <= 0) + return 0; + + for (j = 0, p += n; j < ACCOUNT_REG_NUM; j++, p += n) + { + if (sscanf (p, "%[^,],%d %n", buf, &v, &n) != 2) + break; + memcpy (reg->reg[j].str, buf, 32); + reg->reg[j].value = v; + } + reg->reg_num = j; + + return 1; +} + diff --git a/src/tool/moneycount/athena_text.h b/src/tool/moneycount/athena_text.h new file mode 100644 index 0000000..705c979 --- /dev/null +++ b/src/tool/moneycount/athena_text.h @@ -0,0 +1,9 @@ +#ifndef ATHENA_TEXT_H +#define ATHENA_TEXT_H +#include "mmo.h" + +int mmo_char_fromstr (char *str, struct mmo_charstatus *p); + +int accreg_fromstr (char *str, struct accreg *reg); + +#endif diff --git a/src/tool/moneycount/main.cpp b/src/tool/moneycount/main.cpp new file mode 100644 index 0000000..8d9e5d3 --- /dev/null +++ b/src/tool/moneycount/main.cpp @@ -0,0 +1,99 @@ +#include +#include +#include +#include +#include + +#include "mmo.h" +#include "athena_text.h" + +#define ATHENA_FILE "save/athena.txt" +#define ACCREG_FILE "save/accreg.txt" + +long long countAthena() +{ + long long zeny = 0; + int total = 0; + std::string input; + std::ifstream fp(ATHENA_FILE); + char *buffer = new char[65536]; + + while (fp.good()) + { + std::getline(fp,input); + mmo_charstatus *thisChar = new struct mmo_charstatus; + + strcpy(buffer,input.c_str()); + + if (mmo_char_fromstr(buffer, thisChar)) + { + total++; + zeny += thisChar->zeny; + } + else + std::cout << "Could not parse line \"" << buffer << "\"\n"; + + delete thisChar; + } + + + std::cout << "Parsed a total of " << total << " lines in " << ATHENA_FILE << std::endl; + + delete [] buffer; + fp.close(); + + return zeny; +} + +long long countAccReg() +{ + long long zeny = 0; + int total = 0; + std::ifstream fp(ACCREG_FILE); + char *buffer = new char[65536]; + while (fp.good()) + { + std::string line; + std::getline(fp, line); + struct accreg *reg = new struct accreg; + + strcpy(buffer, line.c_str()); + + if (accreg_fromstr(buffer, reg)) + { + total++; + for (int i = 0; i < reg->reg_num; i++) + { + if (strcmp(reg->reg[i].str,"#BankAccount") == 0) + { + zeny += reg->reg[i].value; + } + } + } + else + { + std::cout << "Could not parse line: \"" << buffer << "\"\n"; + } + + delete reg; + } + + std::cout << "Parsed a total of " << total << " lines in " << ACCREG_FILE << std::endl; + + delete [] buffer; + fp.close(); + + return zeny; +} + +int main() +{ + long long count = 0; + count = countAthena(); + count += countAccReg(); + + std::cout << "There is a total of " << count << " zeny on this server!" << std::endl; + + return 0; +} + diff --git a/src/tool/moneycount/mmo.h b/src/tool/moneycount/mmo.h new file mode 100644 index 0000000..3989cea --- /dev/null +++ b/src/tool/moneycount/mmo.h @@ -0,0 +1,329 @@ +// $Id: mmo.h,v 1.3 2004/09/25 20:12:25 PoW Exp $ +// Original : mmo.h 2003/03/14 12:07:02 Rev.1.7 + +#ifndef _MMO_H_ +#define _MMO_H_ + +#include +#ifdef CYGWIN +#define RETCODE "\r\n" // (CR/LF?FWindows?n) +#else +#define RETCODE "\n" // (LF?FUnix?n?j +#endif + +#define FIFOSIZE_SERVERLINK 256*1024 + +// set to 0 to not check IP of player between each server. +// set to another value if you want to check (1) +#define CMP_AUTHFIFO_IP 1 + +#define CMP_AUTHFIFO_LOGIN2 1 + +#define MAX_MAP_PER_SERVER 512 +#define MAX_INVENTORY 100 +#define MAX_AMOUNT 30000 +#define MAX_ZENY 1000000000 // 1G zeny +#define MAX_CART 100 +#define MAX_SKILL 450 +#define GLOBAL_REG_NUM 96 +#define ACCOUNT_REG_NUM 16 +#define ACCOUNT_REG2_NUM 16 +#define DEFAULT_WALK_SPEED 150 +#define MIN_WALK_SPEED 0 +#define MAX_WALK_SPEED 1000 +#define MAX_STORAGE 300 +#define MAX_GUILD_STORAGE 1000 +#define MAX_PARTY 12 +#define MAX_GUILD 36 // increased max guild members to accomodate for +2 increase for extension levels [Valaris] (removed) [PoW] +#define MAX_GUILDPOSITION 20 // increased max guild positions to accomodate for all members [Valaris] (removed) [PoW] +#define MAX_GUILDEXPLUSION 32 +#define MAX_GUILDALLIANCE 16 +#define MAX_GUILDSKILL 8 +#define MAX_GUILDCASTLE 24 // increased to include novice castles [Valaris] +#define MAX_GUILDLEVEL 50 + +#define MIN_HAIR_STYLE battle_config.min_hair_style +#define MAX_HAIR_STYLE battle_config.max_hair_style +#define MIN_HAIR_COLOR battle_config.min_hair_color +#define MAX_HAIR_COLOR battle_config.max_hair_color +#define MIN_CLOTH_COLOR battle_config.min_cloth_color +#define MAX_CLOTH_COLOR battle_config.max_cloth_color + +// for produce +#define MIN_ATTRIBUTE 0 +#define MAX_ATTRIBUTE 4 +#define ATTRIBUTE_NORMAL 0 +#define MIN_STAR 0 +#define MAX_STAR 3 + +#define MIN_PORTAL_MEMO 0 +#define MAX_PORTAL_MEMO 2 + +#define MAX_STATUS_TYPE 5 + +#define CHAR_CONF_NAME "conf/char_athena.conf" + +struct account +{ + int account_id; + char name[50]; + char password[50]; + char lastlogin[50]; + char sex; + int num_logins; + int state; + char email[50]; + char error_message[50]; + long valitidy_time; + char last_ip[50]; + char memo[50]; + long ban_time; +}; + + +struct item +{ + int id; + short nameid; + short amount; + unsigned short equip; + char identify; + char refine; + char attribute; + short card[4]; + short broken; +}; + +struct point +{ + char map[24]; + short x, y; +}; + +struct skill +{ + unsigned short id, lv, flags; +}; + +struct global_reg +{ + char str[32]; + int value; +}; + +struct accreg +{ + int account_id, reg_num; + struct global_reg reg[ACCOUNT_REG_NUM]; +}; + +struct mmo_charstatus +{ + int char_id; + int account_id; + int partner_id; + + int base_exp, job_exp, zeny; + + short classb; + short status_point, skill_point; + int hp, max_hp, sp, max_sp; + short option, karma, manner; + short hair, hair_color, clothes_color; + int party_id, guild_id; + + short weapon, shield; + short head_top, head_mid, head_bottom; + + char name[24]; + unsigned char base_level, job_level; + short str, agi, vit, int_, dex, luk; + unsigned char char_num, sex; + + unsigned long mapip; + unsigned int mapport; + + struct point last_point, save_point, memo_point[10]; + struct item inventory[MAX_INVENTORY], cart[MAX_CART]; + struct skill skill[MAX_SKILL]; + int global_reg_num; + struct global_reg global_reg[GLOBAL_REG_NUM]; + int account_reg_num; + struct global_reg account_reg[ACCOUNT_REG_NUM]; + int account_reg2_num; + struct global_reg account_reg2[ACCOUNT_REG2_NUM]; +}; + +struct storage +{ + int dirty; + int account_id; + short storage_status; + short storage_amount; + struct item storage_[MAX_STORAGE]; +}; + +struct guild_storage +{ + int dirty; + int guild_id; + short storage_status; + short storage_amount; + struct item storage_[MAX_GUILD_STORAGE]; +}; + +struct map_session_data; + +struct gm_account +{ + int account_id; + int level; +}; + +struct party_member +{ + int account_id; + char name[24], map[24]; + int leader, online, lv; + struct map_session_data *sd; +}; + +struct party +{ + int party_id; + char name[24]; + int exp; + int item; + struct party_member member[MAX_PARTY]; +}; + +struct guild_member +{ + int account_id, char_id; + short hair, hair_color, gender, classb, lv; + int exp, exp_payper; + short online, position; + int rsv1, rsv2; + char name[24]; + struct map_session_data *sd; +}; + +struct guild_position +{ + char name[24]; + int mode; + int exp_mode; +}; + +struct guild_alliance +{ + int opposition; + int guild_id; + char name[24]; +}; + +struct guild_explusion +{ + char name[24]; + char mes[40]; + char acc[40]; + int account_id; + int rsv1, rsv2, rsv3; +}; + +struct guild_skill +{ + int id, lv; +}; + +struct guild +{ + int guild_id; + short guild_lv, connect_member, max_member, average_lv; + int exp, next_exp, skill_point, castle_id; + char name[24], master[24]; + struct guild_member member[MAX_GUILD]; + struct guild_position position[MAX_GUILDPOSITION]; + char mes1[60], mes2[120]; + int emblem_len, emblem_id; + char emblem_data[2048]; + struct guild_alliance alliance[MAX_GUILDALLIANCE]; + struct guild_explusion explusion[MAX_GUILDEXPLUSION]; + struct guild_skill skill[MAX_GUILDSKILL]; +}; + +struct guild_castle +{ + int castle_id; + char map_name[24]; + char castle_name[24]; + char castle_event[24]; + int guild_id; + int economy; + int defense; + int triggerE; + int triggerD; + int nextTime; + int payTime; + int createTime; + int visibleC; + int visibleG0; + int visibleG1; + int visibleG2; + int visibleG3; + int visibleG4; + int visibleG5; + int visibleG6; + int visibleG7; + int Ghp0; // added Guardian HP [Valaris] + int Ghp1; + int Ghp2; + int Ghp3; + int Ghp4; + int Ghp5; + int Ghp6; + int Ghp7; + int GID0; + int GID1; + int GID2; + int GID3; + int GID4; + int GID5; + int GID6; + int GID7; // end addition [Valaris] +}; +struct square +{ + int val1[5]; + int val2[5]; +}; + +enum +{ + GBI_EXP = 1, // ?M???h??EXP + GBI_GUILDLV = 2, // ?M???h??Lv + GBI_SKILLPOINT = 3, // ?M???h?~X?L???|?C???g + GBI_SKILLLV = 4, // ?M???h?X?L??Lv + + GMI_POSITION = 0, // ?????o?[???E??X + GMI_EXP = 1, // ?????o?[??EXP + +}; + +#ifndef LCCWIN32 +#ifndef strcmpi +#define strcmpi strcasecmp +#endif +#ifndef stricmp +#define stricmp strcasecmp +#endif +#ifndef strncmpi +#define strncmpi strncasecmp +#endif +#ifndef strnicmp +#define strnicmp strncasecmp +#endif +#endif + +#endif // _MMO_H_ + -- cgit v1.2.3-60-g2f50