From 70a67d6c5790f9fa61edcf7f2eda339b5e8df9e0 Mon Sep 17 00:00:00 2001 From: Cydh Ramdh Date: Sun, 21 Feb 2016 18:43:24 +0700 Subject: [PATCH 1/6] Official EXP on Max Level * Fixed #942 * 'Last' EXP for max base level is 99,999,999 and 999,999,999 for Job EXP on max job level. * Player still obtains EXP on max level (of course until both base & job reach 100%). * This behavior is used for Base EXP check for: * Super Novice's Explosion Spirit. * Royal Guard's Inspiration. * High Priest's Redemptio. * Added config `death_penalty_maxlv`, default is set to 0. Player on max level, never loss EXP by death penalty. Signed-off-by: Cydh Ramdh --- conf/battle/exp.conf | 6 ++ src/map/battle.c | 1 + src/map/battle.h | 1 + src/map/clif.c | 1 - src/map/pc.c | 133 +++++++++++++++++++++++++++---------------- src/map/pc.h | 8 +-- src/map/skill.c | 14 ++--- 7 files changed, 103 insertions(+), 61 deletions(-) diff --git a/conf/battle/exp.conf b/conf/battle/exp.conf index fbc7c29675..0522323df3 100644 --- a/conf/battle/exp.conf +++ b/conf/battle/exp.conf @@ -79,6 +79,12 @@ death_penalty_job: 100 // NOTE: It is a percentage of their zeny, so 100 = 1% zeny_penalty: 0 +// By default, player on max level never loss the EXP on death. +// 0: Disabled +// 1: Loss Base EXP +// 2: Loss Job EXP +death_penalty_maxlv: 0 + // Will display experience gained from killing a monster. (Note 1) disp_experience: no diff --git a/src/map/battle.c b/src/map/battle.c index 6bf09cdb3b..4b44cfdd96 100644 --- a/src/map/battle.c +++ b/src/map/battle.c @@ -8199,6 +8199,7 @@ static const struct _battle_data { { "monster_eye_range_bonus", &battle_config.mob_eye_range_bonus, 0, 0, 10, }, { "monster_stuck_warning", &battle_config.mob_stuck_warning, 0, 0, 1, }, { "skill_eightpath_algorithm", &battle_config.skill_eightpath_algorithm, 1, 0, 1, }, + { "death_penalty_maxlv", &battle_config.death_penalty_maxlv, 0, 0, 3, }, }; #ifndef STATS_OPT_OUT diff --git a/src/map/battle.h b/src/map/battle.h index 7b65020b26..b97d9ce846 100644 --- a/src/map/battle.h +++ b/src/map/battle.h @@ -601,6 +601,7 @@ extern struct Battle_Config int mob_eye_range_bonus; //Vulture's Eye and Snake's Eye range bonus int mob_stuck_warning; //Show warning if a monster is stuck too long int skill_eightpath_algorithm; //Official path algorithm + int death_penalty_maxlv; } battle_config; void do_init_battle(void); diff --git a/src/map/clif.c b/src/map/clif.c index 989762b97a..fc17698104 100644 --- a/src/map/clif.c +++ b/src/map/clif.c @@ -14038,7 +14038,6 @@ void clif_parse_NoviceExplosionSpirits(int fd, struct map_session_data *sd) if( (sd->class_&MAPID_UPPERMASK) == MAPID_SUPER_NOVICE ) { unsigned int next = pc_nextbaseexp(sd); - if( next == 0 ) next = pc_thisbaseexp(sd); if( next ) { int percent = (int)( ( (float)sd->status.base_exp/(float)next )*1000. ); diff --git a/src/map/pc.c b/src/map/pc.c index a55257fa5e..6d54603d09 100755 --- a/src/map/pc.c +++ b/src/map/pc.c @@ -38,6 +38,8 @@ int pc_split_atoui(char* str, unsigned int* val, char sep, int max); #define PVP_CALCRANK_INTERVAL 1000 // PVP calculation interval +#define MAX_LEVEL_BASE_EXP 99999999 ///< Max Base EXP for player on Max Base Level +#define MAX_LEVEL_JOB_EXP 999999999 ///< Max Job EXP for player on Max Job Level static unsigned int statp[MAX_LEVEL+1]; #if defined(RENEWAL_DROP) || defined(RENEWAL_EXP) @@ -6236,7 +6238,7 @@ int pc_follow(struct map_session_data *sd,int target_id) int pc_checkbaselevelup(struct map_session_data *sd) { unsigned int next = pc_nextbaseexp(sd); - if (!next || sd->status.base_exp < next) + if (!next || sd->status.base_exp < next || pc_is_maxbaselv(sd)) return 0; do { @@ -6299,7 +6301,7 @@ int pc_checkjoblevelup(struct map_session_data *sd) unsigned int next = pc_nextjobexp(sd); nullpo_ret(sd); - if(!next || sd->status.job_exp < next) + if(!next || sd->status.job_exp < next || pc_is_maxjoblv(sd)) return 0; do { @@ -6361,6 +6363,7 @@ static void pc_calcexp(struct map_session_data *sd, unsigned int *base_exp, unsi #endif } + // Give EXPBOOST for quests even if src is NULL. if (&sd->sc && sd->sc.data[SC_EXPBOOST]) { bonus += sd->sc.data[SC_EXPBOOST]->val1; if( battle_config.vip_bm_increase && pc_isvip(sd) ) // Increase Battle Manual EXP rate for VIP. @@ -6369,6 +6372,7 @@ static void pc_calcexp(struct map_session_data *sd, unsigned int *base_exp, unsi *base_exp = (unsigned int) cap_value(*base_exp + (double)*base_exp * (bonus + vip_bonus_base)/100., 1, UINT_MAX); + // Give JEXPBOOST for quests even if src is NULL. if (&sd->sc && sd->sc.data[SC_JEXPBOOST]) bonus += sd->sc.data[SC_JEXPBOOST]->val1; @@ -6376,13 +6380,21 @@ static void pc_calcexp(struct map_session_data *sd, unsigned int *base_exp, unsi return; } -/*========================================== - * Give x exp at sd player and calculate remaining exp for next lvl - *------------------------------------------*/ + +/** + * Give Base or Job EXP to player, then calculate remaining exp for next lvl + * @param sd Player + * @param src EXP source + * @param base_exp Base EXP gained + * @param base_exp Job EXP gained + * @param quest True if EXP from quest, false otherwise. + * @return + **/ int pc_gainexp(struct map_session_data *sd, struct block_list *src, unsigned int base_exp, unsigned int job_exp, bool quest) { float nextbp = 0, nextjp = 0; unsigned int nextb = 0, nextj = 0; + bool is_max_base = false, is_max_job = false; // True for player with max base/job level nullpo_ret(sd); @@ -6395,11 +6407,20 @@ int pc_gainexp(struct map_session_data *sd, struct block_list *src, unsigned int if(sd->status.guild_id>0) base_exp-=guild_payexp(sd,base_exp); - pc_calcexp(sd, &base_exp, &job_exp, src); // Give (J)EXPBOOST for quests even if src is NULL. + pc_calcexp(sd, &base_exp, &job_exp, src); nextb = pc_nextbaseexp(sd); nextj = pc_nextjobexp(sd); + is_max_base = pc_is_maxbaselv(sd); + is_max_job = pc_is_maxjoblv(sd); + + // On Max Level and Max EXP, just set EXP 0 avoid unnecessary process. [Cydh] + if (is_max_base && sd->status.base_exp >= MAX_LEVEL_BASE_EXP) + base_exp = 0; + if (is_max_job && sd->status.job_exp >= MAX_LEVEL_JOB_EXP) + job_exp = 0; + if(sd->state.showexp || battle_config.max_exp_gain_rate){ if (nextb > 0) nextbp = (float) base_exp / (float) nextb; @@ -6422,9 +6443,8 @@ int pc_gainexp(struct map_session_data *sd, struct block_list *src, unsigned int } } - //Cap exp to the level up requirement of the previous level when you are at max level, otherwise cap at UINT_MAX (this is required for some S. Novice bonuses). [Skotlex] + // Give EXP for Base Level if (base_exp) { - nextb = nextb?UINT_MAX:pc_thisbaseexp(sd); if(sd->status.base_exp > nextb - base_exp) sd->status.base_exp = nextb; else @@ -6433,8 +6453,8 @@ int pc_gainexp(struct map_session_data *sd, struct block_list *src, unsigned int clif_updatestatus(sd,SP_BASEEXP); } + // Give EXP for Job Level if (job_exp) { - nextj = nextj?UINT_MAX:pc_thisjobexp(sd); if(sd->status.job_exp > nextj - job_exp) sd->status.job_exp = nextj; else @@ -6443,12 +6463,14 @@ int pc_gainexp(struct map_session_data *sd, struct block_list *src, unsigned int clif_updatestatus(sd,SP_JOBEXP); } + // On Max Level, always send EXP as 0. [Cydh] if(base_exp) - clif_displayexp(sd, base_exp, SP_BASEEXP, quest); + clif_displayexp(sd, (is_max_base) ? 0 : base_exp, SP_BASEEXP, quest); if(job_exp) - clif_displayexp(sd, job_exp, SP_JOBEXP, quest); + clif_displayexp(sd, (is_max_job) ? 0 : job_exp, SP_JOBEXP, quest); + if(sd->state.showexp) { - char output[256]; + char output[CHAT_SIZE_MAX]; sprintf(output, "Experience Gained Base:%u (%.2f%%) Job:%u (%.2f%%)",base_exp,nextbp*(float)100,job_exp,nextjp*(float)100); clif_disp_onlyself(sd,output,strlen(output)); @@ -6457,59 +6479,72 @@ int pc_gainexp(struct map_session_data *sd, struct block_list *src, unsigned int return 1; } -/*========================================== - * Returns max level for this character. - *------------------------------------------*/ +/** + * Returns max base level for this character. + * @param sd Player + * @return Max Base Level + **/ unsigned int pc_maxbaselv(struct map_session_data *sd){ return job_info[pc_class2idx(sd->status.class_)].max_level[0]; } +/** + * Returns max job level for this character. + * @param sd Player + * @return Max Job Level + **/ unsigned int pc_maxjoblv(struct map_session_data *sd){ return job_info[pc_class2idx(sd->status.class_)].max_level[1]; } -/*========================================== - * base level exp lookup. - *------------------------------------------*/ +/** + * Check if player is reached max base level + * @param sd + * @return True if reached max level + **/ +bool pc_is_maxbaselv(struct map_session_data *sd) { + nullpo_retr(false, sd); + return (sd->status.base_level >= pc_maxbaselv(sd)); +} -//Base exp needed for next level. +/** + * Check if player is reached max base level + * @param sd + * @return True if reached max level + **/ +bool pc_is_maxjoblv(struct map_session_data *sd) { + nullpo_retr(false, sd); + return (sd->status.job_level >= pc_maxjoblv(sd)); +} + +/** + * Base exp needed for player to level up. + * @param sd + * @return Base EXP needed for next base level + **/ unsigned int pc_nextbaseexp(struct map_session_data *sd){ nullpo_ret(sd); - if(sd->status.base_level>=pc_maxbaselv(sd) || sd->status.base_level==0) + if (sd->status.base_level == 0) // Is this something that possible? return 0; + if (pc_is_maxbaselv(sd)) + return MAX_LEVEL_BASE_EXP; // On max level, player's base EXP limit is 99,999,999 return job_info[pc_class2idx(sd->status.class_)].exp_table[0][sd->status.base_level-1]; } -//Base exp needed for this level. -unsigned int pc_thisbaseexp(struct map_session_data *sd){ - if(sd->status.base_level>pc_maxbaselv(sd) || sd->status.base_level<=1) - return 0; - return job_info[pc_class2idx(sd->status.class_)].exp_table[0][sd->status.base_level-2]; -} - - -/*========================================== - * job level exp lookup - * Return: - * 0 = not found - * x = exp for level - *------------------------------------------*/ - -//Job exp needed for next level. +/** + * Job exp needed for player to level up. + * @param sd + * @return Job EXP needed for next job level + **/ unsigned int pc_nextjobexp(struct map_session_data *sd){ nullpo_ret(sd); - if(sd->status.job_level>=pc_maxjoblv(sd) || sd->status.job_level==0) + if (sd->status.job_level == 0) // Is this something that possible? return 0; + if (pc_is_maxjoblv(sd)) + return MAX_LEVEL_JOB_EXP; // On max level, player's job EXP limit is 999,999,999 return job_info[pc_class2idx(sd->status.class_)].exp_table[1][sd->status.job_level-1]; } -//Job exp needed for this level. -unsigned int pc_thisjobexp(struct map_session_data *sd){ - if(sd->status.job_level>pc_maxjoblv(sd) || sd->status.job_level<=1) - return 0; - return job_info[pc_class2idx(sd->status.class_)].exp_table[1][sd->status.job_level-2]; -} - /// Returns the value of the specified stat. static int pc_getstat(struct map_session_data* sd, int type) { @@ -7295,10 +7330,9 @@ int pc_dead(struct map_session_data *sd,struct block_list *src) // Activate Steel body if a super novice dies at 99+% exp [celest] // Super Novices have no kill or die functions attached when saved by their angel if (!sd->state.snovice_dead_flag && (sd->class_&MAPID_UPPERMASK) == MAPID_SUPER_NOVICE) { - unsigned int next = pc_nextbaseexp(sd); + unsigned int exp = pc_nextbaseexp(sd); - if( next == 0 ) next = pc_thisbaseexp(sd); - if( get_percentage(sd->status.base_exp,next) >= 99 ) { + if( exp && get_percentage(sd->status.base_exp,exp) >= 99 ) { sd->state.snovice_dead_flag = 1; pc_setrestartvalue(sd,1); status_percent_heal(&sd->bl, 100, 100); @@ -7474,6 +7508,7 @@ int pc_dead(struct map_session_data *sd,struct block_list *src) uint32 base_penalty = battle_config.death_penalty_base; uint32 job_penalty = battle_config.death_penalty_job; uint32 zeny_penalty = battle_config.zeny_penalty; + bool is_max_level = pc_is_maxbaselv(sd); #ifdef VIP_ENABLE if(pc_isvip(sd)){ @@ -7486,7 +7521,7 @@ int pc_dead(struct map_session_data *sd,struct block_list *src) } #endif - if (base_penalty > 0) { + if ((!is_max_level || battle_config.death_penalty_maxlv&1) && base_penalty > 0) { switch (battle_config.death_penalty_type) { case 1: base_penalty = (uint32) ( pc_nextbaseexp(sd) * ( base_penalty / 10000. ) ); break; case 2: base_penalty = (uint32) ( sd->status.base_exp * ( base_penalty / 10000. ) ); break; @@ -7499,7 +7534,7 @@ int pc_dead(struct map_session_data *sd,struct block_list *src) } } - if(job_penalty > 0) { + if ((!is_max_level || battle_config.death_penalty_maxlv&2) && job_penalty > 0) { switch (battle_config.death_penalty_type) { case 1: job_penalty = (uint32) ( pc_nextjobexp(sd) * ( job_penalty / 10000. ) ); break; case 2: job_penalty = (uint32) ( sd->status.job_exp * ( job_penalty /10000. ) ); break; diff --git a/src/map/pc.h b/src/map/pc.h index 4a89e3cfb4..b940ad42af 100644 --- a/src/map/pc.h +++ b/src/map/pc.h @@ -1004,13 +1004,13 @@ int pc_stop_following(struct map_session_data*); unsigned int pc_maxbaselv(struct map_session_data *sd); unsigned int pc_maxjoblv(struct map_session_data *sd); +bool pc_is_maxbaselv(struct map_session_data *sd); +bool pc_is_maxjoblv(struct map_session_data *sd); int pc_checkbaselevelup(struct map_session_data *sd); int pc_checkjoblevelup(struct map_session_data *sd); int pc_gainexp(struct map_session_data*,struct block_list*,unsigned int,unsigned int, bool); -unsigned int pc_nextbaseexp(struct map_session_data *); -unsigned int pc_thisbaseexp(struct map_session_data *); -unsigned int pc_nextjobexp(struct map_session_data *); -unsigned int pc_thisjobexp(struct map_session_data *); +unsigned int pc_nextbaseexp(struct map_session_data *sd); +unsigned int pc_nextjobexp(struct map_session_data *sd); int pc_gets_status_point(int); int pc_need_status_point(struct map_session_data *,int,int); int pc_maxparameterincrease(struct map_session_data*,int); diff --git a/src/map/skill.c b/src/map/skill.c index 8c6965b695..261a11d81a 100755 --- a/src/map/skill.c +++ b/src/map/skill.c @@ -9634,14 +9634,13 @@ int skill_castend_nodamage_id (struct block_list *src, struct block_list *bl, ui break; case LG_INSPIRATION: - if( sd && !map[sd->bl.m].flag.noexppenalty && sd->status.base_level != MAX_LEVEL ) { - sd->status.base_exp -= min(sd->status.base_exp, pc_nextbaseexp(sd) * 1 / 100); // 1% penalty. - sd->status.job_exp -= min(sd->status.job_exp, pc_nextjobexp(sd) * 1 / 100); - clif_updatestatus(sd,SP_BASEEXP); - clif_updatestatus(sd,SP_JOBEXP); + if( sd && !map[sd->bl.m].flag.noexppenalty ) { + sd->status.base_exp -= min(sd->status.base_exp, pc_nextbaseexp(sd) * 1 / 100); // 1% penalty. + sd->status.job_exp -= min(sd->status.job_exp, pc_nextjobexp(sd) * 1 / 100); + clif_updatestatus(sd,SP_BASEEXP); + clif_updatestatus(sd,SP_JOBEXP); } - clif_skill_nodamage(bl,src,skill_id,skill_lv, - sc_start(src,bl, type, 100, skill_lv, skill_get_time(skill_id, skill_lv))); + clif_skill_nodamage(bl,src,skill_id,skill_lv, sc_start(src,bl, type, 100, skill_lv, skill_get_time(skill_id, skill_lv))); break; case SR_CURSEDCIRCLE: if( flag&1 ) { @@ -14757,6 +14756,7 @@ bool skill_check_condition_castbegin(struct map_session_data* sd, uint16 skill_i } } break; + case LG_INSPIRATION: case PR_REDEMPTIO: { int exp; From f2f135708b24e75645232fc67ea3f40751d473cb Mon Sep 17 00:00:00 2001 From: Cydh Ramdh Date: Mon, 22 Feb 2016 12:32:25 +0700 Subject: [PATCH 2/6] Follow up 70a67d6c5790f9fa61edcf7f2eda339b5e8df9e0 * On EXP lost by death penalty. EXP will be shown as negative value. * Fixed check for EXP value on packet `0x07f6`. EXP is limited to INT_MIN until INT_MAX by client (so don't expect client to displays value more than this!) * Added EXP gained/lost message by `@showexp` on death penalty too. Signed-off-by: Cydh Ramdh --- conf/msg_conf/map_msg.conf | 10 +++-- src/map/clif.c | 25 +++++++---- src/map/clif.h | 2 +- src/map/pc.c | 91 ++++++++++++++++++++++++-------------- 4 files changed, 83 insertions(+), 45 deletions(-) diff --git a/conf/msg_conf/map_msg.conf b/conf/msg_conf/map_msg.conf index e5bcd6a765..b395cf6f3c 100644 --- a/conf/msg_conf/map_msg.conf +++ b/conf/msg_conf/map_msg.conf @@ -781,7 +781,11 @@ 739: Please enter a body style (usage: @bodystyle ). 740: This job has no alternate body styles. -//741-899 free +// @showexp +741: Gained +742: Lost +743: Experience %s Base:%ld (%0.2f%%) Job:%ld (%0.2f%%) +//744-899 free //------------------------------------ // More atcommands message @@ -1358,8 +1362,8 @@ 1315: Available Flags: // @showexp -1316: Gained exp will not be shown. -1317: Gained exp is now shown. +1316: Gained/lost exp will not be shown. +1317: Gained/lost exp is now shown. // @showzeny 1318: Gained zeny will not be shown. diff --git a/src/map/clif.c b/src/map/clif.c index fc17698104..cd6911ef62 100644 --- a/src/map/clif.c +++ b/src/map/clif.c @@ -16649,14 +16649,21 @@ void clif_party_show_picker(struct map_session_data * sd, struct item * item_dat } -/// Display gained exp (ZC_NOTIFY_EXP). -/// 07f6 .L .L .W .W -/// var id: -/// SP_BASEEXP, SP_JOBEXP -/// exp type: -/// 0 = normal exp gain/loss -/// 1 = quest exp gain/loss -void clif_displayexp(struct map_session_data *sd, unsigned int exp, char type, bool quest) +/** Display gained exp (ZC_NOTIFY_EXP). + * 07f6 .L .L .W .W + * amount: INT32_MIN ~ INT32_MAX + * var id: + * SP_BASEEXP, SP_JOBEXP + * exp type: + * 0 = normal exp gained/lost + * 1 = quest exp gained/lost + * @param sd Player + * @param exp EXP value gained/loss + * @param type SP_BASEEXP, SP_JOBEXP + * @param quest False:Normal EXP; True:Quest EXP (displayed in purple color) + * @param lost True:if lossing EXP + */ +void clif_displayexp(struct map_session_data *sd, unsigned int exp, char type, bool quest, bool lost) { int fd; @@ -16667,7 +16674,7 @@ void clif_displayexp(struct map_session_data *sd, unsigned int exp, char type, b WFIFOHEAD(fd, packet_len(0x7f6)); WFIFOW(fd,0) = 0x7f6; WFIFOL(fd,2) = sd->bl.id; - WFIFOL(fd,6) = exp; + WFIFOL(fd,6) = (int)min(exp, INT_MAX) * (lost ? -1 : 1); WFIFOW(fd,10) = type; WFIFOW(fd,12) = quest?1:0;// Normal exp is shown in yellow, quest exp is shown in purple. WFIFOSET(fd,packet_len(0x7f6)); diff --git a/src/map/clif.h b/src/map/clif.h index 3c24c7504c..90e9ba6d74 100644 --- a/src/map/clif.h +++ b/src/map/clif.h @@ -816,7 +816,7 @@ void clif_quest_delete(struct map_session_data * sd, int quest_id); void clif_quest_update_status(struct map_session_data * sd, int quest_id, bool active); void clif_quest_update_objective(struct map_session_data * sd, struct quest * qd, int mobid); void clif_quest_show_event(struct map_session_data *sd, struct block_list *bl, short state, short color); -void clif_displayexp(struct map_session_data *sd, unsigned int exp, char type, bool quest); +void clif_displayexp(struct map_session_data *sd, unsigned int exp, char type, bool quest, bool lost); int clif_send(const uint8* buf, int len, struct block_list* bl, enum send_target type); void do_init_clif(void); diff --git a/src/map/pc.c b/src/map/pc.c index 6d54603d09..8738627490 100755 --- a/src/map/pc.c +++ b/src/map/pc.c @@ -6381,6 +6381,27 @@ static void pc_calcexp(struct map_session_data *sd, unsigned int *base_exp, unsi return; } +/** + * Show EXP gained by player in percentage by @showexp + * @param sd Player + * @param base_exp Base EXP gained/loss + * @param next_base_exp Base EXP needed for next base level + * @param job_exp Job EXP gained/loss + * @param next_job_exp Job EXP needed for next job level + * @param lost True:EXP penalty, lose EXP + **/ +static void pc_gainexp_disp(struct map_session_data *sd, unsigned int base_exp, unsigned int next_base_exp, unsigned int job_exp, unsigned int next_job_exp, bool lost) { + char output[CHAT_SIZE_MAX]; + + nullpo_retv(sd); + + sprintf(output, msg_txt(sd,743), // Experience %s Base:%ld (%0.2f%%) Job:%ld (%0.2f%%) + (lost) ? msg_txt(sd,742) : msg_txt(sd,741), + (long)base_exp * (lost ? -1 : 1), (base_exp / (float)next_base_exp * 100 * (lost ? -1 : 1)), + (long)job_exp * (lost ? -1 : 1), (job_exp / (float)next_job_exp * 100 * (lost ? -1 : 1))); + clif_disp_onlyself(sd, output, strlen(output)); +} + /** * Give Base or Job EXP to player, then calculate remaining exp for next lvl * @param sd Player @@ -6394,7 +6415,7 @@ int pc_gainexp(struct map_session_data *sd, struct block_list *src, unsigned int { float nextbp = 0, nextjp = 0; unsigned int nextb = 0, nextj = 0; - bool is_max_base = false, is_max_job = false; // True for player with max base/job level + uint8 flag = 0; ///< 1: Base EXP given, 2: Job EXP given, 4: Max Base level, 8: Max Job Level nullpo_ret(sd); @@ -6411,17 +6432,18 @@ int pc_gainexp(struct map_session_data *sd, struct block_list *src, unsigned int nextb = pc_nextbaseexp(sd); nextj = pc_nextjobexp(sd); + + flag = ((base_exp) ? 1 : 0) | + ((job_exp) ? 2 : 0) | + (pc_is_maxbaselv(sd) ? 4 : 0) | + (pc_is_maxjoblv(sd) ? 8 : 0); - is_max_base = pc_is_maxbaselv(sd); - is_max_job = pc_is_maxjoblv(sd); - - // On Max Level and Max EXP, just set EXP 0 avoid unnecessary process. [Cydh] - if (is_max_base && sd->status.base_exp >= MAX_LEVEL_BASE_EXP) + if (flag&4 && sd->status.base_exp >= MAX_LEVEL_BASE_EXP) base_exp = 0; - if (is_max_job && sd->status.job_exp >= MAX_LEVEL_JOB_EXP) + if (flag&8 && sd->status.job_exp >= MAX_LEVEL_JOB_EXP) job_exp = 0; - if(sd->state.showexp || battle_config.max_exp_gain_rate){ + if ((base_exp || job_exp) && (sd->state.showexp || battle_config.max_exp_gain_rate)){ if (nextb > 0) nextbp = (float) base_exp / (float) nextb; if (nextj > 0) @@ -6449,8 +6471,8 @@ int pc_gainexp(struct map_session_data *sd, struct block_list *src, unsigned int sd->status.base_exp = nextb; else sd->status.base_exp += base_exp; - pc_checkbaselevelup(sd); - clif_updatestatus(sd,SP_BASEEXP); + if (!pc_checkbaselevelup(sd)) + clif_updatestatus(sd,SP_BASEEXP); } // Give EXP for Job Level @@ -6459,22 +6481,17 @@ int pc_gainexp(struct map_session_data *sd, struct block_list *src, unsigned int sd->status.job_exp = nextj; else sd->status.job_exp += job_exp; - pc_checkjoblevelup(sd); - clif_updatestatus(sd,SP_JOBEXP); + if (!pc_checkjoblevelup(sd)) + clif_updatestatus(sd,SP_JOBEXP); } - // On Max Level, always send EXP as 0. [Cydh] - if(base_exp) - clif_displayexp(sd, (is_max_base) ? 0 : base_exp, SP_BASEEXP, quest); - if(job_exp) - clif_displayexp(sd, (is_max_job) ? 0 : job_exp, SP_JOBEXP, quest); + if (flag&1) + clif_displayexp(sd, (flag&4) ? 0 : base_exp, SP_BASEEXP, quest, false); + if (flag&2) + clif_displayexp(sd, (flag&8) ? 0 : job_exp, SP_JOBEXP, quest, false); - if(sd->state.showexp) { - char output[CHAT_SIZE_MAX]; - sprintf(output, - "Experience Gained Base:%u (%.2f%%) Job:%u (%.2f%%)",base_exp,nextbp*(float)100,job_exp,nextjp*(float)100); - clif_disp_onlyself(sd,output,strlen(output)); - } + if (sd->state.showexp) + pc_gainexp_disp(sd, base_exp, nextb, job_exp, nextj, false); return 1; } @@ -7508,7 +7525,6 @@ int pc_dead(struct map_session_data *sd,struct block_list *src) uint32 base_penalty = battle_config.death_penalty_base; uint32 job_penalty = battle_config.death_penalty_job; uint32 zeny_penalty = battle_config.zeny_penalty; - bool is_max_level = pc_is_maxbaselv(sd); #ifdef VIP_ENABLE if(pc_isvip(sd)){ @@ -7521,31 +7537,42 @@ int pc_dead(struct map_session_data *sd,struct block_list *src) } #endif - if ((!is_max_level || battle_config.death_penalty_maxlv&1) && base_penalty > 0) { + if ((battle_config.death_penalty_maxlv&1 || !pc_is_maxbaselv(sd)) && base_penalty > 0) { switch (battle_config.death_penalty_type) { case 1: base_penalty = (uint32) ( pc_nextbaseexp(sd) * ( base_penalty / 10000. ) ); break; case 2: base_penalty = (uint32) ( sd->status.base_exp * ( base_penalty / 10000. ) ); break; } - if (base_penalty > 0){ //recheck after altering to speedup + if (base_penalty){ //recheck after altering to speedup if (battle_config.pk_mode && src && src->type==BL_PC) - base_penalty*=2; - sd->status.base_exp -= u32min(sd->status.base_exp, base_penalty); + base_penalty *= 2; + base_penalty = u32min(sd->status.base_exp, base_penalty); + sd->status.base_exp -= base_penalty; + clif_displayexp(sd, base_penalty, SP_BASEEXP, false, true); clif_updatestatus(sd,SP_BASEEXP); } } + else + base_penalty = 0; - if ((!is_max_level || battle_config.death_penalty_maxlv&2) && job_penalty > 0) { + if ((battle_config.death_penalty_maxlv&2 || !pc_is_maxjoblv(sd)) && job_penalty > 0) { switch (battle_config.death_penalty_type) { case 1: job_penalty = (uint32) ( pc_nextjobexp(sd) * ( job_penalty / 10000. ) ); break; case 2: job_penalty = (uint32) ( sd->status.job_exp * ( job_penalty /10000. ) ); break; } - if(job_penalty) { + if (job_penalty) { if (battle_config.pk_mode && src && src->type==BL_PC) - job_penalty*=2; - sd->status.job_exp -= u32min(sd->status.job_exp, job_penalty); + job_penalty *= 2; + job_penalty = u32min(sd->status.job_exp, job_penalty); + sd->status.job_exp -= job_penalty; + clif_displayexp(sd, job_penalty, SP_JOBEXP, false, true); clif_updatestatus(sd,SP_JOBEXP); } } + else + job_penalty = 0; + + if (sd->state.showexp && (base_penalty || job_penalty)) + pc_gainexp_disp(sd, base_penalty, pc_nextbaseexp(sd), job_penalty, pc_nextjobexp(sd), true); if( zeny_penalty > 0 && !map[sd->bl.m].flag.nozenypenalty) { zeny_penalty = (uint32)( sd->status.zeny * ( zeny_penalty / 10000. ) ); From 9944c1dd4637c816681c75cc30c3254970263c58 Mon Sep 17 00:00:00 2001 From: Cydh Ramdh Date: Wed, 24 Feb 2016 12:26:04 +0700 Subject: [PATCH 3/6] Fixed PR_REDEMPTIO and LG_INSPIRATION requirement * Only charge the Base EXP. * Lost EXP will be displayed. Signed-off-by: Cydh Ramdh --- conf/battle/exp.conf | 11 +++++++++++ src/map/battle.c | 3 +++ src/map/battle.h | 3 +++ src/map/clif.c | 2 +- src/map/pc.c | 2 +- src/map/pc.h | 1 + src/map/skill.c | 38 +++++++++++++++++++++++++------------- 7 files changed, 45 insertions(+), 15 deletions(-) diff --git a/conf/battle/exp.conf b/conf/battle/exp.conf index 0522323df3..70f20a4940 100644 --- a/conf/battle/exp.conf +++ b/conf/battle/exp.conf @@ -95,3 +95,14 @@ disp_zeny: no // If no, an equation will be used which preserves statpoints earned/lost // through external means (ie: stat point buyers/sellers) use_statpoint_table: yes + +// EXP cost for cast PR_REDEMPTIO (Note 2) +exp_cost_redemptio: 1 + +// How many player needed to makes PR_REDEMPTIO's EXP penalty become 0? +// If by default, the 'exp_cost_redemptio' is 1 (1%) and every single player revived the penalty is reduced to 0.2%, +// it means 'exp_cost_redemptio_limit' is 5. +exp_cost_redemptio_limit: 5 + +// EXP cost for cast LG_INSPIRATION (Note 2) +exp_cost_inspiration: 1 diff --git a/src/map/battle.c b/src/map/battle.c index 4b44cfdd96..d25cce2e37 100644 --- a/src/map/battle.c +++ b/src/map/battle.c @@ -8200,6 +8200,9 @@ static const struct _battle_data { { "monster_stuck_warning", &battle_config.mob_stuck_warning, 0, 0, 1, }, { "skill_eightpath_algorithm", &battle_config.skill_eightpath_algorithm, 1, 0, 1, }, { "death_penalty_maxlv", &battle_config.death_penalty_maxlv, 0, 0, 3, }, + { "exp_cost_redemptio", &battle_config.exp_cost_redemptio, 1, 0, 100, }, + { "exp_cost_redemptio_limit", &battle_config.exp_cost_redemptio_limit, 5, 0, MAX_PARTY, }, + { "exp_cost_inspiration", &battle_config.exp_cost_inspiration, 1, 0, 100, }, }; #ifndef STATS_OPT_OUT diff --git a/src/map/battle.h b/src/map/battle.h index b97d9ce846..0a958ec53f 100644 --- a/src/map/battle.h +++ b/src/map/battle.h @@ -602,6 +602,9 @@ extern struct Battle_Config int mob_stuck_warning; //Show warning if a monster is stuck too long int skill_eightpath_algorithm; //Official path algorithm int death_penalty_maxlv; + int exp_cost_redemptio; + int exp_cost_redemptio_limit; + int exp_cost_inspiration; } battle_config; void do_init_battle(void); diff --git a/src/map/clif.c b/src/map/clif.c index cd6911ef62..f77b07973b 100644 --- a/src/map/clif.c +++ b/src/map/clif.c @@ -16674,7 +16674,7 @@ void clif_displayexp(struct map_session_data *sd, unsigned int exp, char type, b WFIFOHEAD(fd, packet_len(0x7f6)); WFIFOW(fd,0) = 0x7f6; WFIFOL(fd,2) = sd->bl.id; - WFIFOL(fd,6) = (int)min(exp, INT_MAX) * (lost ? -1 : 1); + WFIFOL(fd,6) = (int)umin(exp, INT_MAX) * (lost ? -1 : 1); WFIFOW(fd,10) = type; WFIFOW(fd,12) = quest?1:0;// Normal exp is shown in yellow, quest exp is shown in purple. WFIFOSET(fd,packet_len(0x7f6)); diff --git a/src/map/pc.c b/src/map/pc.c index 8738627490..af8860875b 100755 --- a/src/map/pc.c +++ b/src/map/pc.c @@ -6390,7 +6390,7 @@ static void pc_calcexp(struct map_session_data *sd, unsigned int *base_exp, unsi * @param next_job_exp Job EXP needed for next job level * @param lost True:EXP penalty, lose EXP **/ -static void pc_gainexp_disp(struct map_session_data *sd, unsigned int base_exp, unsigned int next_base_exp, unsigned int job_exp, unsigned int next_job_exp, bool lost) { +void pc_gainexp_disp(struct map_session_data *sd, unsigned int base_exp, unsigned int next_base_exp, unsigned int job_exp, unsigned int next_job_exp, bool lost) { char output[CHAT_SIZE_MAX]; nullpo_retv(sd); diff --git a/src/map/pc.h b/src/map/pc.h index b940ad42af..1ed2069cfd 100644 --- a/src/map/pc.h +++ b/src/map/pc.h @@ -1009,6 +1009,7 @@ bool pc_is_maxjoblv(struct map_session_data *sd); int pc_checkbaselevelup(struct map_session_data *sd); int pc_checkjoblevelup(struct map_session_data *sd); int pc_gainexp(struct map_session_data*,struct block_list*,unsigned int,unsigned int, bool); +void pc_gainexp_disp(struct map_session_data *sd, unsigned int base_exp, unsigned int next_base_exp, unsigned int job_exp, unsigned int next_job_exp, bool lost); unsigned int pc_nextbaseexp(struct map_session_data *sd); unsigned int pc_nextjobexp(struct map_session_data *sd); int pc_gets_status_point(int); diff --git a/src/map/skill.c b/src/map/skill.c index 261a11d81a..3effb237b0 100755 --- a/src/map/skill.c +++ b/src/map/skill.c @@ -5925,12 +5925,15 @@ int skill_castend_nodamage_id (struct block_list *src, struct block_list *bl, ui clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); break; } - skill_area_temp[0] = 5 - skill_area_temp[0]; // The actual penalty... - if (skill_area_temp[0] > 0 && !map[src->m].flag.noexppenalty) { //Apply penalty - sd->status.base_exp -= min(sd->status.base_exp, pc_nextbaseexp(sd) * skill_area_temp[0] * 2/1000); //0.2% penalty per each. - sd->status.job_exp -= min(sd->status.job_exp, pc_nextjobexp(sd) * skill_area_temp[0] * 2/1000); + skill_area_temp[0] = battle_config.exp_cost_redemptio_limit - skill_area_temp[0]; // The actual penalty... + if (skill_area_temp[0] > 0 && !map[src->m].flag.noexppenalty && battle_config.exp_cost_redemptio) { //Apply penalty + //If total penalty is 1% => reduced 0.2% penalty per each revived player + unsigned int base_penalty = u32min(sd->status.base_exp, (pc_nextbaseexp(sd) * skill_area_temp[0] * battle_config.exp_cost_redemptio / battle_config.exp_cost_redemptio_limit) / 100); + sd->status.base_exp -= base_penalty; + clif_displayexp(sd, base_penalty, SP_BASEEXP, false, true); clif_updatestatus(sd,SP_BASEEXP); - clif_updatestatus(sd,SP_JOBEXP); + if (sd->state.showexp) + pc_gainexp_disp(sd, base_penalty, pc_nextbaseexp(sd), 0, pc_nextjobexp(sd), true); } status_set_hp(src, 1, 0); status_set_sp(src, 0, 0); @@ -9634,11 +9637,13 @@ int skill_castend_nodamage_id (struct block_list *src, struct block_list *bl, ui break; case LG_INSPIRATION: - if( sd && !map[sd->bl.m].flag.noexppenalty ) { - sd->status.base_exp -= min(sd->status.base_exp, pc_nextbaseexp(sd) * 1 / 100); // 1% penalty. - sd->status.job_exp -= min(sd->status.job_exp, pc_nextjobexp(sd) * 1 / 100); + if( sd && !map[sd->bl.m].flag.noexppenalty && battle_config.exp_cost_inspiration ) { + unsigned int base_penalty = u32min(sd->status.base_exp, pc_nextbaseexp(sd) * battle_config.exp_cost_inspiration / 100); // 1% penalty. + sd->status.base_exp -= base_penalty; + clif_displayexp(sd, base_penalty, SP_BASEEXP, false, true); clif_updatestatus(sd,SP_BASEEXP); - clif_updatestatus(sd,SP_JOBEXP); + if (sd->state.showexp) + pc_gainexp_disp(sd, base_penalty, pc_nextbaseexp(sd), 0, pc_nextjobexp(sd), true); } clif_skill_nodamage(bl,src,skill_id,skill_lv, sc_start(src,bl, type, 100, skill_lv, skill_get_time(skill_id, skill_lv))); break; @@ -14756,12 +14761,19 @@ bool skill_check_condition_castbegin(struct map_session_data* sd, uint16 skill_i } } break; - case LG_INSPIRATION: case PR_REDEMPTIO: + case LG_INSPIRATION: { - int exp; - if( ((exp = pc_nextbaseexp(sd)) > 0 && get_percentage(sd->status.base_exp, exp) < 1) || - ((exp = pc_nextjobexp(sd)) > 0 && get_percentage(sd->status.job_exp, exp) < 1)) { + unsigned int exp, exp_needp = 0; + switch (skill_id) { + case PR_REDEMPTIO: + exp_needp = battle_config.exp_cost_redemptio; + break; + case LG_INSPIRATION: + exp_needp = battle_config.exp_cost_inspiration; + break; + } + if (exp_needp && ((exp = pc_nextbaseexp(sd)) > 0 && get_percentage(sd->status.base_exp, exp) < exp_needp)) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); //Not enough exp. return false; } From 811723f3aa099ebde9112ed8321fad651a2d2967 Mon Sep 17 00:00:00 2001 From: aleos89 Date: Sun, 28 Feb 2016 12:12:21 -0500 Subject: [PATCH 4/6] Corrected random Start Point issues (fixes #955) * Follow up to 994b406. * Fixes new characters initial spawn locations getting set to maps that aren't defined in the config. * Fixes issues with reading maps from import not resetting the array of maps read from the char_config. * Cleaned up the Start Item parser to follow the Start Point parser. Thanks to @LiamKarlMitchell! --- src/char/char.c | 47 +++++++++++++++++++++++------------------------ 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/src/char/char.c b/src/char/char.c index 6faadb5daa..39b0b31108 100644 --- a/src/char/char.c +++ b/src/char/char.c @@ -2659,7 +2659,7 @@ void char_set_defaults(){ charserv_config.start_point[0].map = mapindex_name2id(MAP_DEFAULT_NAME); charserv_config.start_point[0].x = MAP_DEFAULT_X; charserv_config.start_point[0].y = MAP_DEFAULT_Y; - charserv_config.start_point_count = 0; + charserv_config.start_point_count = 1; charserv_config.start_items[0].nameid = 1201; charserv_config.start_items[0].amount = 1; @@ -2697,12 +2697,14 @@ static void char_config_split_startpoint(char *w2_value) strcat(config_name, "start_point_pre"); #endif + charserv_config.start_point_count = 0; // Reset to begin reading + fields = (char **)aMalloc(fields_length * sizeof(char *)); if (fields == NULL) return; // Failed to allocate memory. lineitem = strtok(w2_value, ":"); - while (lineitem != NULL) { + while (lineitem != NULL && charserv_config.start_point_count < MAX_STARTPOINT) { int n = sv_split(lineitem, strlen(lineitem), 0, ',', fields, fields_length, SV_NOESCAPE_NOTERMINATE); if (n + 1 < fields_length) { @@ -2710,21 +2712,19 @@ static void char_config_split_startpoint(char *w2_value) lineitem = strtok(NULL, ":"); //next lineitem continue; } - if (i > MAX_STARTPOINT) - ShowDebug("%s: too many start points, only %d are allowed! Ignoring parameter %s...\n", config_name, MAX_STARTPOINT, lineitem); - else { - charserv_config.start_point[i].map = mapindex_name2id(fields[1]); - if (!charserv_config.start_point[i].map) { - ShowError("Start point %s not found in map-index cache. Setting to default location.\n", charserv_config.start_point[i].map); - charserv_config.start_point[i].map = mapindex_name2id(MAP_DEFAULT_NAME); - charserv_config.start_point[i].x = MAP_DEFAULT_X; - charserv_config.start_point[i].y = MAP_DEFAULT_Y; - } else { - charserv_config.start_point[i].x = max(0, atoi(fields[2])); - charserv_config.start_point[i].y = max(0, atoi(fields[3])); - } - charserv_config.start_point_count++; + + charserv_config.start_point[i].map = mapindex_name2id(fields[1]); + if (!charserv_config.start_point[i].map) { + ShowError("Start point %s not found in map-index cache. Setting to default location.\n", charserv_config.start_point[i].map); + charserv_config.start_point[i].map = mapindex_name2id(MAP_DEFAULT_NAME); + charserv_config.start_point[i].x = MAP_DEFAULT_X; + charserv_config.start_point[i].y = MAP_DEFAULT_Y; + } else { + charserv_config.start_point[i].x = max(0, atoi(fields[2])); + charserv_config.start_point[i].y = max(0, atoi(fields[3])); } + charserv_config.start_point_count++; + lineitem = strtok(NULL, ":"); //next lineitem i++; } @@ -2749,7 +2749,7 @@ static void char_config_split_startitem(char *w2_value) return; // Failed to allocate memory. lineitem = strtok(w2_value, ":"); - while (lineitem != NULL) { + while (lineitem != NULL && i < MAX_STARTITEM) { int n = sv_split(lineitem, strlen(lineitem), 0, ',', fields, fields_length, SV_NOESCAPE_NOTERMINATE); if (n + 1 < fields_length) { @@ -2757,13 +2757,12 @@ static void char_config_split_startitem(char *w2_value) lineitem = strtok(NULL, ":"); //next lineitem continue; } - if (i > MAX_STARTITEM) - ShowDebug("%s: too many start items, only %d are allowed! Ignoring parameter %s...\n", config_name, MAX_STARTITEM, lineitem); - else { - charserv_config.start_items[i].nameid = max(0, atoi(fields[1])); - charserv_config.start_items[i].amount = max(0, atoi(fields[2])); - charserv_config.start_items[i].pos = max(0, atoi(fields[3])); - } + + // TODO: Item ID verification + charserv_config.start_items[i].nameid = max(0, atoi(fields[1])); + charserv_config.start_items[i].amount = max(0, atoi(fields[2])); + charserv_config.start_items[i].pos = max(0, atoi(fields[3])); + lineitem = strtok(NULL, ":"); //next lineitem i++; } From 077738bea1c364a5fcb55a8d805f6c0980cb1ca9 Mon Sep 17 00:00:00 2001 From: Cydh Ramdh Date: Mon, 29 Feb 2016 09:04:07 +0700 Subject: [PATCH 5/6] Follow up f2f135708b24e75645232fc67ea3f40751d473cb * Corrected documentation for `death_penalty_maxlv` config. Signed-off-by: Cydh Ramdh --- conf/battle/exp.conf | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/conf/battle/exp.conf b/conf/battle/exp.conf index 70f20a4940..d2659b0b09 100644 --- a/conf/battle/exp.conf +++ b/conf/battle/exp.conf @@ -79,10 +79,10 @@ death_penalty_job: 100 // NOTE: It is a percentage of their zeny, so 100 = 1% zeny_penalty: 0 -// By default, player on max level never loss the EXP on death. -// 0: Disabled -// 1: Loss Base EXP -// 2: Loss Job EXP +// Will players on max base/job level lose the EXP on death? +// 0: Never lose (default as in official). +// 1: Lose Base EXP. +// 2: Lose Job EXP. death_penalty_maxlv: 0 // Will display experience gained from killing a monster. (Note 1) From 4ff4fe42eef72d09a10ca752832c658cf76119bf Mon Sep 17 00:00:00 2001 From: Cydh Ramdh Date: Mon, 29 Feb 2016 22:07:36 +0700 Subject: [PATCH 6/6] Follwo up 20588abcd83fab53f9e52deafc08bfcddfe1658a Signed-off-by: Cydh Ramdh --- src/map/pc.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/map/pc.c b/src/map/pc.c index af8860875b..03be9f29a4 100755 --- a/src/map/pc.c +++ b/src/map/pc.c @@ -7893,13 +7893,15 @@ bool pc_setparam(struct map_session_data *sd,int type,int val) case SP_BASEEXP: if(pc_nextbaseexp(sd) > 0) { sd->status.base_exp = val; - pc_checkbaselevelup(sd); + if (!pc_checkbaselevelup(sd)) + clif_updatestatus(sd, SP_BASEEXP); } break; case SP_JOBEXP: if(pc_nextjobexp(sd) > 0) { sd->status.job_exp = val; - pc_checkjoblevelup(sd); + if (!pc_checkjoblevelup(sd)) + clif_updatestatus(sd, SP_JOBEXP); } break; case SP_SEX: