Skip to content

Commit

Permalink
Merge pull request #3298 from skyleo/fix-copying-own-skills
Browse files Browse the repository at this point in the history
Fix copying own skills via RG_PLAGIARISM or SC_REPRODUCE causing the skill to be deleted and skills that required it eventually.
  • Loading branch information
MishimaHaruna committed Jun 30, 2024
2 parents 7312ec2 + 8790b0b commit 9b9c8be
Show file tree
Hide file tree
Showing 3 changed files with 142 additions and 53 deletions.
148 changes: 117 additions & 31 deletions src/map/pc.c
Original file line number Diff line number Diff line change
Expand Up @@ -1520,21 +1520,33 @@ static int pc_reg_received(struct map_session_data *sd)
if ((i = pc->checkskill(sd,RG_PLAGIARISM)) > 0) {
sd->cloneskill_id = pc_readglobalreg(sd,script->add_variable("CLONE_SKILL"));
if (sd->cloneskill_id > 0 && (idx = skill->get_index(sd->cloneskill_id)) > 0) {
int learned_lv = sd->status.skill[idx].lv;
bool is_own_skill = pc->is_own_skill(sd, sd->cloneskill_id);
sd->status.skill[idx].id = sd->cloneskill_id;
sd->status.skill[idx].lv = pc_readglobalreg(sd,script->add_variable("CLONE_SKILL_LV"));
if (sd->status.skill[idx].lv > i)
sd->status.skill[idx].lv = i;
sd->status.skill[idx].flag = SKILL_FLAG_PLAGIARIZED;

if (is_own_skill)
sd->status.skill[idx].flag = learned_lv + SKILL_FLAG_REPLACED_LV_0;
else
sd->status.skill[idx].flag = SKILL_FLAG_PLAGIARIZED;
}
}
if ((i = pc->checkskill(sd,SC_REPRODUCE)) > 0) {
sd->reproduceskill_id = pc_readglobalreg(sd,script->add_variable("REPRODUCE_SKILL"));
if( sd->reproduceskill_id > 0 && (idx = skill->get_index(sd->reproduceskill_id)) > 0) {
int learned_lv = sd->status.skill[idx].lv;
bool is_own_skill = pc->is_own_skill(sd, sd->reproduceskill_id);
sd->status.skill[idx].id = sd->reproduceskill_id;
sd->status.skill[idx].lv = pc_readglobalreg(sd,script->add_variable("REPRODUCE_SKILL_LV"));
if( i < sd->status.skill[idx].lv)
sd->status.skill[idx].lv = i;
sd->status.skill[idx].flag = SKILL_FLAG_PLAGIARIZED;

if (is_own_skill)
sd->status.skill[idx].flag = learned_lv + SKILL_FLAG_REPLACED_LV_0;
else
sd->status.skill[idx].flag = SKILL_FLAG_PLAGIARIZED;
}
}

Expand Down Expand Up @@ -1644,8 +1656,10 @@ static void pc_calc_skilltree_clear(struct map_session_data *sd)
nullpo_retv(sd);

for (i = 0; i < MAX_SKILL_DB; i++) {
if (sd->status.skill[i].flag != SKILL_FLAG_PLAGIARIZED && sd->status.skill[i].flag != SKILL_FLAG_PERM_GRANTED) //Don't touch these
sd->status.skill[i].id = 0; //First clear skills.
if (sd->status.skill[i].flag == SKILL_FLAG_PLAGIARIZED || sd->status.skill[i].flag == SKILL_FLAG_PERM_GRANTED
|| sd->status.skill[i].id == sd->cloneskill_id || sd->status.skill[i].id == sd->reproduceskill_id) //Don't touch these
continue;
sd->status.skill[i].id = 0; //First clear skills.
/* permanent skills that must be re-checked */
if (sd->status.skill[i].flag == SKILL_FLAG_PERMANENT) {
switch (skill->dbs->db[i].nameid) {
Expand Down Expand Up @@ -1679,7 +1693,8 @@ static int pc_calc_skilltree(struct map_session_data *sd)
pc->calc_skilltree_clear(sd);

for (int i = 0; i < MAX_SKILL_DB; i++) {
if (sd->status.skill[i].flag == SKILL_FLAG_TEMPORARY || sd->status.skill[i].flag >= SKILL_FLAG_REPLACED_LV_0) {
if ((sd->status.skill[i].flag >= SKILL_FLAG_REPLACED_LV_0 && sd->status.skill[i].id != sd->cloneskill_id && sd->status.skill[i].id != sd->reproduceskill_id)
|| sd->status.skill[i].flag == SKILL_FLAG_TEMPORARY) {
// Restore original level of skills after deleting earned skills.
sd->status.skill[i].lv = (sd->status.skill[i].flag == SKILL_FLAG_TEMPORARY) ? 0 : sd->status.skill[i].flag - SKILL_FLAG_REPLACED_LV_0;
sd->status.skill[i].flag = SKILL_FLAG_PERMANENT;
Expand Down Expand Up @@ -8823,7 +8838,7 @@ static int jobchange_killclone(struct block_list *bl, va_list ap)
static int pc_jobchange(struct map_session_data *sd, int class, int upper)
{
int i, fame_flag=0;
int job, idx = 0;
int job = 0;

nullpo_ret(sd);

Expand Down Expand Up @@ -8862,31 +8877,8 @@ static int pc_jobchange(struct map_session_data *sd, int class, int upper)
pc_setglobalreg(sd, script->add_variable("jobchange_level_3rd"), sd->change_level_3rd);
}

if(sd->cloneskill_id) {
idx = skill->get_index(sd->cloneskill_id);
if( sd->status.skill[idx].flag == SKILL_FLAG_PLAGIARIZED ) {
sd->status.skill[idx].id = 0;
sd->status.skill[idx].lv = 0;
sd->status.skill[idx].flag = 0;
clif->deleteskill(sd, sd->cloneskill_id, false);
}
sd->cloneskill_id = 0;
pc_setglobalreg(sd, script->add_variable("CLONE_SKILL"), 0);
pc_setglobalreg(sd, script->add_variable("CLONE_SKILL_LV"), 0);
}

if(sd->reproduceskill_id) {
idx = skill->get_index(sd->reproduceskill_id);
if( sd->status.skill[idx].flag == SKILL_FLAG_PLAGIARIZED ) {
sd->status.skill[idx].id = 0;
sd->status.skill[idx].lv = 0;
sd->status.skill[idx].flag = 0;
clif->deleteskill(sd, sd->reproduceskill_id, false);
}
sd->reproduceskill_id = 0;
pc_setglobalreg(sd, script->add_variable("REPRODUCE_SKILL"),0);
pc_setglobalreg(sd, script->add_variable("REPRODUCE_SKILL_LV"),0);
}
pc->clear_existing_cloneskill(sd, true);
pc->clear_existing_reproduceskill(sd, true);

if ((job & MAPID_UPPERMASK) != (sd->job & MAPID_UPPERMASK)) { //Things to remove when changing class tree.
const int class_idx = pc->class2idx(sd->status.class);
Expand Down Expand Up @@ -12609,6 +12601,96 @@ void pc_crimson_marker_clear(struct map_session_data *sd)
}
}

/**
* Checks if a skill is a permanent skill that one has prerequisites for or has learned.
*
* NOTE: Unfortunately even uninitialized skills have the flag SKILL_FLAG_PERMANENT, so we have to distinguish by id as well.
* Ideally a more saner zero value should be used for e_skill_flag.
* WARNING: This function relies on the skill tree being already calculated / filled.
*
* @param sd The player to check for.
* @param skill_id The skill to check for.
* @return True if the player meets the skill prerequisites or has it, false otherwise.
*/
static bool pc_is_own_skill(struct map_session_data *sd, uint16 skill_id)
{
nullpo_retr(false, sd);

int idx = skill->get_index(skill_id);
if (idx <= 0)
return false; // skill not found
if (sd->status.skill[idx].id != skill_id)
return false; // not meeting pre-requisites for skill or skill id to index mapping faulty.
if (sd->status.skill[idx].flag != SKILL_FLAG_PERMANENT)
return false; // script granted or temporary.

return true;
}

/**
* Clears / removes the existing cloned skill from RG_PLAGIARISM
*
* @param sd The player to clear the cloned skill from.
* @param clear_vars If true, the remembered clone_skill level and ID will be cleared, otherwise it will not be touched.
*/
static void pc_clear_existing_cloneskill(struct map_session_data *sd, bool clear_vars)
{
nullpo_retv(sd);

if (sd->cloneskill_id != 0) {
int idx = skill->get_index(sd->cloneskill_id);
if (sd->status.skill[idx].flag == SKILL_FLAG_PLAGIARIZED) {
sd->status.skill[idx].id = 0;
sd->status.skill[idx].lv = 0;
sd->status.skill[idx].flag = 0;
clif->deleteskill(sd, sd->cloneskill_id, false);
} else if (sd->status.skill[idx].flag >= SKILL_FLAG_REPLACED_LV_0) {
sd->status.skill[idx].lv = sd->status.skill[idx].flag - SKILL_FLAG_REPLACED_LV_0;
sd->status.skill[idx].flag = SKILL_FLAG_PERMANENT;
// CAREFUL! This assumes you will only ever use SKILL_FLAG_REPLACED_LV_0 logic when copying SKILL_FLAG_PERMANENT skills!!!
clif->addskill(sd, sd->cloneskill_id);
}
}

if (clear_vars) {
sd->cloneskill_id = 0;
pc_setglobalreg(sd, script->add_variable("CLONE_SKILL"), 0);
pc_setglobalreg(sd, script->add_variable("CLONE_SKILL_LV"), 0);
}
}

/**
* Clears / removes the existing reproduced skill from SC_REPRODUCE
*
* @param sd The player to clear the reproduced skill from.
* @param clear_vars If true, the remembered reproduced skill level and ID will be cleared, otherwise it will not be touched.
*/
static void pc_clear_existing_reproduceskill(struct map_session_data *sd, bool clear_vars)
{
nullpo_retv(sd);

if (sd->reproduceskill_id != 0) {
int idx = skill->get_index(sd->reproduceskill_id);
if (sd->status.skill[idx].flag == SKILL_FLAG_PLAGIARIZED) {
sd->status.skill[idx].id = 0;
sd->status.skill[idx].lv = 0;
sd->status.skill[idx].flag = 0;
clif->deleteskill(sd, sd->reproduceskill_id, false);
} else if (sd->status.skill[idx].flag >= SKILL_FLAG_REPLACED_LV_0) {
sd->status.skill[idx].lv = sd->status.skill[idx].flag - SKILL_FLAG_REPLACED_LV_0;
sd->status.skill[idx].flag = SKILL_FLAG_PERMANENT;
// CAREFUL! This assumes you will only ever use SKILL_FLAG_REPLACED_LV_0 logic when copying SKILL_FLAG_PERMANENT skills!!!
clif->addskill(sd, sd->reproduceskill_id);
}
}

if (clear_vars) {
sd->reproduceskill_id = 0;
pc_setglobalreg(sd, script->add_variable("REPRODUCE_SKILL"), 0);
pc_setglobalreg(sd, script->add_variable("REPRODUCE_SKILL_LV"), 0);
}
}

static void do_final_pc(void)
{

Expand Down Expand Up @@ -13035,4 +13117,8 @@ void pc_defaults(void)
pc->auto_exp_insurance = pc_auto_exp_insurance;

pc->crimson_marker_clear = pc_crimson_marker_clear;

pc->is_own_skill = pc_is_own_skill;
pc->clear_existing_cloneskill = pc_clear_existing_cloneskill;
pc->clear_existing_reproduceskill = pc_clear_existing_reproduceskill;
}
4 changes: 4 additions & 0 deletions src/map/pc.h
Original file line number Diff line number Diff line change
Expand Up @@ -1283,6 +1283,10 @@ END_ZEROED_BLOCK; /* End */
bool (*auto_exp_insurance) (struct map_session_data *sd);

void (*crimson_marker_clear) (struct map_session_data *sd);

bool (*is_own_skill) (struct map_session_data *sd, uint16 skill_id);
void (*clear_existing_cloneskill) (struct map_session_data *sd, bool clear_vars);
void (*clear_existing_reproduceskill) (struct map_session_data *sd, bool clear_vars);
};

#ifdef HERCULES_CORE
Expand Down
43 changes: 21 additions & 22 deletions src/map/skill.c
Original file line number Diff line number Diff line change
Expand Up @@ -1262,7 +1262,9 @@ static int can_copy(struct map_session_data *sd, uint16 skill_id)
if (!cidx)
return 0;

if (sd->status.skill[cidx].id && sd->status.skill[cidx].flag == SKILL_FLAG_PLAGIARIZED)
if (sd->status.skill[cidx].id != 0 && (sd->status.skill[cidx].flag >= SKILL_FLAG_REPLACED_LV_0
|| sd->status.skill[cidx].flag == SKILL_FLAG_PLAGIARIZED
|| sd->status.skill[cidx].flag == SKILL_FLAG_PERM_GRANTED))
return 0;

// Checks if preserve is active and if skill can be copied by Plagiarism
Expand Down Expand Up @@ -3681,54 +3683,51 @@ static int skill_attack(int attack_type, struct block_list *src, struct block_li
break;
}

int cidx, idx, lv = 0;
int cidx, lv = 0;
cidx = skill->get_index(copy_skill);
int learned_lv = tsd->status.skill[cidx].lv;
bool copying_own_skill = pc->is_own_skill(tsd, copy_skill);
switch(can_copy(tsd, copy_skill)) {
case 1: // Plagiarism
{
if (tsd->cloneskill_id) {
idx = skill->get_index(tsd->cloneskill_id);
if (tsd->status.skill[idx].flag == SKILL_FLAG_PLAGIARIZED) {
tsd->status.skill[idx].id = 0;
tsd->status.skill[idx].lv = 0;
tsd->status.skill[idx].flag = 0;
clif->deleteskill(tsd, tsd->cloneskill_id, false);
}
}
pc->clear_existing_cloneskill(tsd, false);

lv = min(skill_lv, pc->checkskill(tsd, RG_PLAGIARISM));
if (learned_lv > lv)
break; // [Aegis] can't overwrite skill of higher level, but will still remove previously copied skill.

tsd->cloneskill_id = copy_skill;
pc_setglobalreg(tsd, script->add_variable("CLONE_SKILL"), copy_skill);
pc_setglobalreg(tsd, script->add_variable("CLONE_SKILL_LV"), lv);

tsd->status.skill[cidx].id = copy_skill;
tsd->status.skill[cidx].lv = lv;
tsd->status.skill[cidx].flag = SKILL_FLAG_PLAGIARIZED;
if (copying_own_skill)
tsd->status.skill[cidx].flag = learned_lv + SKILL_FLAG_REPLACED_LV_0;
else
tsd->status.skill[cidx].flag = SKILL_FLAG_PLAGIARIZED;
clif->addskill(tsd, copy_skill);
}
break;
case 2: // Reproduce
{
lv = sc ? sc->data[SC__REPRODUCE]->val1 : 1;
if (tsd->reproduceskill_id) {
idx = skill->get_index(tsd->reproduceskill_id);
if (tsd->status.skill[idx].flag == SKILL_FLAG_PLAGIARIZED) {
tsd->status.skill[idx].id = 0;
tsd->status.skill[idx].lv = 0;
tsd->status.skill[idx].flag = 0;
clif->deleteskill(tsd, tsd->reproduceskill_id, false);
}
}
pc->clear_existing_reproduceskill(tsd, false);

lv = min(lv, skill->get_max(copy_skill));
if (learned_lv > lv)
break; // unconfirmed, but probably the same behavior as for RG_PLAGIARISM

tsd->reproduceskill_id = copy_skill;
pc_setglobalreg(tsd, script->add_variable("REPRODUCE_SKILL"), copy_skill);
pc_setglobalreg(tsd, script->add_variable("REPRODUCE_SKILL_LV"), lv);

tsd->status.skill[cidx].id = copy_skill;
tsd->status.skill[cidx].lv = lv;
tsd->status.skill[cidx].flag = SKILL_FLAG_PLAGIARIZED;
if (copying_own_skill)
tsd->status.skill[cidx].flag = learned_lv + SKILL_FLAG_REPLACED_LV_0;
else
tsd->status.skill[cidx].flag = SKILL_FLAG_PLAGIARIZED;
clif->addskill(tsd, copy_skill);
}
break;
Expand Down

0 comments on commit 9b9c8be

Please sign in to comment.