Jump to content
  • 0

[QoL] Show warning when trying to allocate skill points on a higher job tab


exequens

Question


  • Group:  Members
  • Topic Count:  2
  • Topics Per Day:  0.00
  • Content Count:  7
  • Reputation:   0
  • Joined:  03/14/24
  • Last Seen:  

I want to be able to show a warning if a player is trying to allocate a skill point on a higher job tab.
For example, a player resets their skills, normally you won't be able to allocate your skill point to your 4th job tab if you havent spent enough skpoint in all your 1st, 2nd, and 3rd job tab.
It would be very nice to be able to show a warning that basically says:
"You need to spend %d skill point(s) before spending it on your xx job tab"
I have tried doing it myself, but I just can't seem to get it right.

Am I right to tinker with this specific block on src/map/pc.cpp
 


/*==========================================
 * Update skill_lv for player sd
 * Skill point allocation
 *------------------------------------------*/
void pc_skillup(map_session_data *sd,uint16 skill_id)
{
    uint16 idx = skill_get_index(skill_id);

    nullpo_retv(sd);

    if (!idx) {
        if (skill_id)
            ShowError("pc_skillup: Player attempts to level up invalid skill '%d'\n", skill_id);
        return;
    }

    // Level up guild skill
    if (SKILL_CHK_GUILD(skill_id)) {
        guild_skillup(sd, skill_id);
        return;
    }
    // Level up homunculus skill
    else if (sd->hd && SKILL_CHK_HOMUN(skill_id)) {
        hom_skillup(sd->hd, skill_id);
        return;
    }
    else {
        if( sd->status.skill_point > 0 &&
            sd->status.skill[idx].id &&
            sd->status.skill[idx].flag == SKILL_FLAG_PERMANENT && //Don't allow raising while you have granted skills. [Skotlex]
            sd->status.skill[idx].lv < skill_tree_get_max(skill_id, sd->status.class_) )
        {
            sd->status.skill[idx].lv++;
            sd->status.skill_point--;
            if( !skill_get_inf(skill_id) || pc_checkskill_summoner(sd, SUMMONER_POWER_LAND) >= 20 || pc_checkskill_summoner(sd, SUMMONER_POWER_SEA) >= 20 )
                status_calc_pc(sd,SCO_NONE); // Only recalculate for passive skills.
            else if( sd->status.skill_point == 0 && pc_is_taekwon_ranker(sd) )
                pc_calc_skilltree(sd); // Required to grant all TK Ranker skills.
            else
                pc_check_skilltree(sd); // Check if a new skill can Lvlup

            uint16 lv = sd->status.skill[idx].lv;
            int32 range = skill_get_range2(&sd->bl, skill_id, lv, false);
            bool upgradable = ( lv < skill_tree_get_max( sd->status.skill[idx].id, sd->status.class_ ) );
            clif_skillup( *sd, skill_id, lv, range, upgradable );
            clif_updatestatus(*sd,SP_SKILLPOINT);
            if( skill_id == GN_REMODELING_CART ) /* cart weight info was updated by status_calc_pc */
                clif_updatestatus(*sd,SP_CARTINFO);
            if (pc_checkskill(sd, SG_DEVIL) && ((sd->class_&MAPID_THIRDMASK) == MAPID_STAR_EMPEROR || pc_is_maxjoblv(sd)))
                clif_status_change(&sd->bl, EFST_DEVIL1, 1, 0, 0, 0, 1); //Permanent blind effect from SG_DEVIL.
            if (!pc_has_permission(sd, PC_PERM_ALL_SKILL)) // may skill everything at any time anyways, and this would cause a huge slowdown
                clif_skillinfoblock(sd);
        }
        //else
        //    ShowDebug("Skill Level up failed. ID:%d idx:%d (CID=%d. AID=%d)\n", skill_id, idx, sd->status.char_id, sd->status.account_id);
    }

I'm reading the function pc_calc_skilltree_normalize_job_sub as well, but I can't grasp it. Anyone willing to help me?

Edited by exequens
Link to comment
Share on other sites

3 answers to this question

Recommended Posts

  • 0

  • Group:  Members
  • Topic Count:  5
  • Topics Per Day:  0.00
  • Content Count:  55
  • Reputation:   20
  • Joined:  04/21/13
  • Last Seen:  

Hi. I'm trying to achieve this too now that I read your request, It made me remember that a while ago I wanted to do something like what you explained.

Here's what I managed to produce so far, sadly it only works with Novice as I'm checking NV_BASIC skill level.

/*==========================================
 * Update skill_lv for player sd
 * Skill point allocation
 *------------------------------------------*/
void pc_skillup(map_session_data *sd,uint16 skill_id)
{
	uint16 idx = skill_get_index(skill_id);

	nullpo_retv(sd);
	
	if (!idx) {
		if (skill_id)
			ShowError("pc_skillup: Player attempts to level up invalid skill '%d'\n", skill_id);
		return;
	}
	
	// If user tries to put points in a skill other than NV_BASIC while NV_BASIC is not maxed
	if(skill_id > NV_BASIC && sd->status.skill[NV_BASIC].lv < skill_tree_get_max(NV_BASIC, sd->status.class_)){
		StringBuf *nov_msg = StringBuf_Malloc();
		StringBuf_Printf(nov_msg, "You must spend %d skillpoints on Basic Skill in Novice skill tree first", skill_tree_get_max(NV_BASIC, sd->status.class_));
		
		clif_displaymessage(sd->fd, StringBuf_Value(nov_msg));
		
		StringBuf_Free(nov_msg);
		
		// Do not spend the point
		// return;
	}
  ... Rest of the function ...
}

Edit: I can't find a way to check how many skillpoints have been invested in a specific job by the player. With that info we could check invested points in 1st / 2nd / 3rd jobs and display a message when trying to up a skill when not all previous job skills have been invested.

Edited by eleriaqueen
Link to comment
Share on other sites

  • 0

  • Group:  Members
  • Topic Count:  2
  • Topics Per Day:  0.00
  • Content Count:  7
  • Reputation:   0
  • Joined:  03/14/24
  • Last Seen:  

Thanks for telling me to use code widget, edited first post.
Also thanks for showing a clear working example.

So if that works, then what we should do is make a function that can determine what skill_id is belong to what job tab?
Something like this maybe?
 

// Returns the job tier (Novice=0, 1st=1, 2nd=2, etc.)
int pc_get_job_tier(uint64 class_) {
    if (class_ & JOBL_FOURTH) return 4;
    if (class_ & JOBL_THIRD) return 3;
    if (class_ & JOBL_2) return 2;
    if ((class_ & MAPID_BASEMASK) != MAPID_NOVICE) return 1;
    return 0; // Novice/Summoner
}

// Returns the skill's job tier
int skill_get_job_tier(uint16 skill_id) {
    if (skill_id >= NV_BASIC && skill_id <= NV_TRICKDEAD) return 0;
    if (skill_id >= SM_SWORD && skill_id <= SM_BASH) return 1;
    return 0; // Default to Novice if unknown
}

I'm honestly not sure...

Link to comment
Share on other sites

  • 0

  • Group:  Members
  • Topic Count:  5
  • Topics Per Day:  0.00
  • Content Count:  55
  • Reputation:   20
  • Joined:  04/21/13
  • Last Seen:  

Class skills aren't well organized into neatly defined id ranges if I recall correctly. Some SM_ skills may have high ID's.

Here is an attempt at making a skill_id -> Job Tier in the style you proposed.

// Helper Function : Returns the skill's job tier
int32 skill_get_job_tier(uint16 skill_id) {
	// Novice
	if (skill_id > 0 && skill_id < SM_SWORD) return 0;
	
	// Swordman
	else if (skill_id >= SM_SWORD && skill_id <= SM_ENDURE) return 1;
	
	// Magician
	else if (skill_id >= MG_SRECOVERY && skill_id <= MG_THUNDERSTORM) return 1;
	
	// Acolyte
	else if (skill_id >= AL_DP && skill_id <= AL_CURE) return 1;
	
	// Merchant
	else if (skill_id >= MC_INCCARRY && skill_id <= MC_MAMMONITE) return 1;
	
	// Archer
	else if (skill_id >= AC_OWL && skill_id <= AC_SHOWER) return 1;
	
	// Thief
	else if (skill_id >= TF_DOUBLE && skill_id <= TF_DETOXIFY) return 1;
	
	// Taekwon Kid
	else if (skill_id >= TK_RUN && skill_id <= TK_HIGHJUMP) return 1;
	else if (skill_id == TK_MISSION) return 1;
	
	// Knight
	else if (skill_id >= KN_SPEARMASTERY && skill_id <= KN_CAVALIERMASTERY) return 2;
	
	// Priest
	else if (skill_id >= PR_MACEMASTERY && skill_id <= PR_MAGNUS) return 2;
	
	// Wizard
	else if (skill_id >= WZ_FIREPILLAR && skill_id <= WZ_ESTIMATION) return 2;
	
	// Blacksmith
	else if (skill_id >= BS_IRON && skill_id <= BS_MAXIMIZE) return 2;
	
	// Hunter
	else if (skill_id >= HT_SKIDTRAP && skill_id <= HT_SPRINGTRAP) return 2;
	
	// Assassin
	else if (skill_id >= AS_RIGHT && skill_id <= AS_SPLASHER) return 2;
	
	// Rogue
	else if (skill_id >= RG_SNATCHER && skill_id <= RG_PLAGIARISM) return 2;
	
	// Alchemist
	else if (skill_id >= AM_AXEMASTERY && skill_id <= AM_RESURRECTHOMUN) return 2;
	
	// Crusader
	else if (skill_id >= CR_TRUST && skill_id <= CR_SPEARQUICKEN) return 2;
	
	// Monk
	else if (skill_id >= MO_IRONHAND && skill_id <= MO_COMBOFINISH) return 2;
	
	// Sage
	else if (skill_id >= SA_ADVANCEDBOOK && skill_id <= SA_COMA) return 2;
	
	// Bard & Dancer
	else if (skill_id >= BD_ADAPTATION && skill_id <= BD_RAGNAROK) return 2;
	
	// Bard
	else if (skill_id >= BA_MUSICALLESSON && skill_id <= BA_APPLEIDUN) return 2;
	
	// Dancer
	else if (skill_id >= DC_DANCINGLESSON && skill_id <= DC_SERVICEFORYOU) return 2;
	
	// Lord Knight
	else if (skill_id >= LK_AURABLADE && skill_id <= LK_FURY) return 2;
	else if (skill_id >= LK_SPIRALPIERCE && skill_id <= LK_JOINTBEAT) return 2;
	
	// High Priest
	else if (skill_id >= HP_ASSUMPTIO && skill_id <= HP_MEDITATIO) return 2;
	else if (skill_id == HP_MANARECHARGE) return 2;
	
	// High Wizard
	else if (skill_id >= HW_SOULDRAIN && skill_id <= HW_MAGICPOWER) return 2;
	else if (skill_id == HW_NAPALMVULCAN) return 2;
	else if (skill_id >= HW_GANBANTEIN && skill_id <= HW_GRAVITATION) return 2;
	
	// Paladin
	else if (skill_id >= PA_PRESSURE && skill_id <= PA_GOSPEL) return 2;
	else if (skill_id == PA_SHIELDCHAIN) return 2;
	
	// Champion
	else if (skill_id >= CH_PALMSTRIKE && skill_id <= CH_CHAINCRUSH) return 2;
	else if (skill_id == CH_SOULCOLLECT) return 2;
	
	// Professor
	else if (skill_id >= PF_HPCONVERSION && skill_id <= PF_SOULBURN) return 2;
	else if (skill_id >= PF_MINDBREAKER && skill_id <= PF_SPIDERWEB) return 2;
	else if (skill_id == PF_DOUBLECASTING) return 2;
	
	// Assassin Cross
	else if (skill_id >= ASC_KATAR && skill_id <= ASC_BREAKER) return 2;
	else if (skill_id >= ASC_METEORASSAULT && skill_id <= ASC_CDP) return 2;
	
	// Sniper
	else if (skill_id >= SN_SIGHT && skill_id <= SN_WINDWALK) return 2;
	
	// Whitesmith
	else if (skill_id >= WS_MELTDOWN && skill_id <= WS_SYSTEMCREATE) return 2;
	else if (skill_id == WS_WEAPONREFINE) return 2;
	else if (skill_id >= WS_CARTTERMINATION && skill_id <= WS_OVERTHRUSTMAX) return 2;
	
	// Stalker
	else if (skill_id >= ST_CHASEWALK && skill_id <= ST_STEALBACKPACK) return 2;
	else if (skill_id >= ST_PRESERVE && skill_id <= ST_FULLSTRIP) return 2;
	
	// Creator
	else if (skill_id >= CR_ALCHEMY && skill_id <= CR_SYNTHESISPOTION) return 2;
	else if (skill_id >= CR_SLIMPITCHER && skill_id <= CR_FULLPROTECTION) return 2;
	else if (skill_id == CR_ACIDDEMONSTRATION) return 2;
	
	// Clown / Gypsy
	else if (skill_id >= CG_ARROWVULCAN && skill_id <= CG_MARIONETTE) return 2;
	else if (skill_id >= CG_LONGINGFREEDOM && skill_id <= CG_TAROTCARD) return 2;
	else if (skill_id == CG_SPECIALSINGER) return 2;
	
	// Star Gladiator
	else if (skill_id >= SG_FEEL && skill_id <= SG_FUSION) return 2;
	
	// Soul Linker
	else if (skill_id == SL_ALCHEMIST) return 2;
	else if (skill_id >= SL_MONK && skill_id <= SL_SKA) return 2;
	else if (skill_id == SL_HIGH) return 2;
	// else if (skill_id >= SL_DEATHKNIGHT && skill_id <= SL_GUNNER) return 2;
	
	// Gunslinger
	else if (skill_id >= GS_GLITTERING && skill_id <= GS_GROUNDDRIFT) return 2;
	
	// Ninja
	else if (skill_id >= NJ_TOBIDOUGU && skill_id <= NJ_ISSEN) return 2;
	
	// Rune Knight
	else if (skill_id >= RK_ENCHANTBLADE && skill_id <= RK_PHANTOMTHRUST) return 3;
	else if (skill_id >= RK_DRAGONBREATH_WATER && skill_id <= RK_LUXANIMA) return 3;
	
	// Guillotine Cross
	else if (skill_id >= GC_VENOMIMPRESS && skill_id <= GC_CROSSRIPPERSLASHER) return 3;
	else if (skill_id == GC_DARKCROW) return 3;
	
	// Arch-Bishop
	else if (skill_id >= AB_JUDEX && skill_id <= AB_SILENTIUM) return 3;
	else if (skill_id == AB_SECRAMENT) return 3;
	else if (skill_id == AB_OFFERTORIUM) return 3;
	else if (skill_id >= AB_VITUPERATUM && skill_id <= AB_CONVENIO) return 3;
	
	// Warlock
	else if (skill_id >= WL_WHITEIMPRISON && skill_id <= WL_FREEZE_SP) return 3;
	else if (skill_id == WL_TELEKINESIS_INTENSE) return 3;
	
	// Ranger
	else if (skill_id >= RA_ARROWSTORM && skill_id <= RA_ICEBOUNDTRAP) return 3;
	else if (skill_id == RA_UNLIMIT) return 3;
	
	// Mechanic
	else if (skill_id >= NC_MADOLICENCE && skill_id <= NC_DISJOINT) return 3;
	else if (skill_id == NC_MAGMA_ERUPTION) return 3;
	
	// Shadow Chaser
	else if (skill_id >= SC_FATALMENACE && skill_id <= SC_FEINTBOMB) return 3;
	else if (skill_id == SC_ESCAPE) return 3;
	
	// Royal Guard
	else if (skill_id >= LG_CANNONSPEAR && skill_id <= LG_INSPIRATION) return 3;
	else if (skill_id == LG_KINGS_GRACE) return 3;
	
	// Sura
	else if (skill_id >= SR_DRAGONCOMBO && skill_id <= SR_GENTLETOUCH_REVITALIZE) return 3;
	else if (skill_id >= SR_HOWLINGOFLION && skill_id <= SR_RIDEINLIGHTNING) return 3;
	else if (skill_id == SR_FLASHCOMBO) return 3;
	
	// Wanderer
	else if (skill_id >= WA_SWING_DANCE && skill_id <= WA_MOONLIT_SERENADE) return 3;
	
	// Ministrel
	else if (skill_id >= MI_RUSH_WINDMILL && skill_id <= MI_HARMONIZE) return 3;
	
	// Wanderer && Ministrel
	else if (skill_id >= WM_LESSON && skill_id <= WM_UNLIMITED_HUMMING_VOICE) return 3;
	else if (skill_id == WM_FRIGG_SONG)  return 3;
	
	// Sorcerer
	else if (skill_id >= SO_FIREWALK && skill_id <= SO_EARTH_INSIGNIA) return 3;
	else if (skill_id == SO_ELEMENTAL_SHIELD) return 3;

	// Genetic
	else if (skill_id >= GN_TRAINING_SWORD && skill_id <= GN_SLINGITEM_RANGEMELEEATK) return 3;
	else if (skill_id == GN_ILLUSIONDOPING) return 3;
	
	// Rebellion
	else if (skill_id >= RL_GLITTERING_GREED && skill_id <= RL_B_FLICKER_ATK) return 3;
	
	// Star Emperor
	else if (skill_id >= SJ_LIGHTOFMOON && skill_id <= SJ_PROMINENCEKICK) return 3;
	
	// Soul Reaper
	else if (skill_id >= SP_SOULGOLEM && skill_id <= SP_KAUTE) return 3;
	
	// Kagerou && Oboro
	else if (skill_id >= KO_YAMIKUMO && skill_id <= KO_IZAYOI) return 3;
	
	// Kagerou
	else if (skill_id >= KG_KAGEHUMI && skill_id <= KG_KAGEMUSYA) return 3;
	
	// Oboro
	else if (skill_id >= OB_ZANGETSU && skill_id <= OB_AKAITSUKI) return 3;
	
	// Super Novice 2 
	else if (skill_id >= NV_BREAKTHROUGH && skill_id <= NV_TRANSCENDENCE) return 3;
	
	// ALL 3rd jobs
	else if (skill_id == ALL_FULL_THROTTLE) return 3;
	
	// Dragon Knight 
	else if (skill_id >= DK_SERVANTWEAPON && skill_id <= DK_STORMSLASH) return 4;
	else if (skill_id == DK_DRAGONIC_BREATH) return 4;
	else if (skill_id == DK_DRAGONIC_PIERCE) return 4;
	
	// Arc Mage 
	else if (skill_id >= AG_DEADLY_PROJECTION && skill_id <= AG_FROZEN_SLASH) return 4;
	else if (skill_id == AG_DESTRUCTIVE_HURRICANE_CLIMAX) return 4;
	else if (skill_id == AG_ENERGY_CONVERSION) return 4;
	
	// Inquisitor 
	else if (skill_id >= IQ_POWERFUL_FAITH && skill_id <= IQ_THIRD_EXOR_FLAME) return 4;
	else if (skill_id == IQ_BLAZING_FLAME_BLAST) return 4;
	
	// Imperial Guard 
	else if (skill_id >= IG_GUARD_STANCE && skill_id <= IG_CROSS_RAIN) return 4;
	else if (skill_id >= IG_RADIANT_SPEAR && skill_id <= IG_IMPERIAL_PRESSURE) return 4;
	
	// Cardinal 
	else if (skill_id >= CD_REPARATIO && skill_id <= CD_FRAMEN) return 4;
	else if (skill_id == CD_DIVINUS_FLOS) return 4;
	
	// Shadow Cross 
	else if (skill_id >= SHC_SHADOW_EXCEED && skill_id <= SHC_FATAL_SHADOW_CROW) return 4;
	else if (skill_id == SHC_CROSS_SLASH) return 4;
	
	// Meister 
	else if (skill_id >= MT_AXE_STOMP && skill_id <= MT_SUMMON_ABR_INFINITY) return 4;
	else if (skill_id >= MT_SPARK_BLASTER && skill_id <= MT_MIGHTY_SMASH) return 4;
	else if (skill_id >= MT_RUSH_STRIKE && skill_id <= MT_ENERGY_CANNONADE) return 4;
	
	// Biolo 
	else if (skill_id >= BO_ACIDIFIED_ZONE_WATER_ATK && skill_id <= BO_ACIDIFIED_ZONE_FIRE_ATK) return 4;
	else if (skill_id >= BO_BIONIC_PHARMACY && skill_id <= BO_HELLTREE) return 4;
	else if (skill_id >= BO_EXPLOSIVE_POWDER && skill_id <= BO_MAYHEMIC_THORNS) return 4;
	else if (skill_id >= BO_MYSTERY_POWDER && skill_id <= BO_DUST_EXPLOSION) return 4;
	
	// Abyss Chaser 
	else if (skill_id >= ABC_DAGGER_AND_BOW_M && skill_id <= ABC_FRENZY_SHOT) return 4;
	else if (skill_id >= ABC_HIT_AND_SLIDING && skill_id <= ABC_ABYSS_FLAME) return 4;
	
	// Wind Hawk
	else if (skill_id >= WH_ADVANCED_TRAP && skill_id <= WH_FLAMETRAP) return 4;
	else if (skill_id == WH_WILD_WALK) return 4;
	
	// Troubadour && Trouvere
	else if (skill_id >= TR_STAGE_MANNER && skill_id <= TR_PRON_MARCH) return 4;
	else if (skill_id == TR_RHYTHMICAL_WAVE) return 4;
	
	// Elemental Master
	else if (skill_id >= EM_MAGIC_BOOK_M && skill_id <= EM_ELEMENTAL_VEIL) return 4;
	else if (skill_id == EM_PSYCHIC_STREAM) return 4;
	
	// Night Watch
	else if (skill_id >= NW_P_F_I && skill_id <= NW_MISSION_BOMBARD) return 4;
	else if (skill_id >= NW_WILD_SHOT && skill_id <= NW_MIDNIGHT_FALLEN) return 4;
	
	// Soul Ascetic
	else if (skill_id >= SOA_TALISMAN_MASTERY && skill_id <= SOA_SOUL_OF_HEAVEN_AND_EARTH) return 4;
	
	// Spirit Handler
	else if (skill_id >= SH_MYSTICAL_CREATURE_MASTERY && skill_id <= SH_BLESSING_OF_MYSTICAL_CREATURES) return 4;
	else if (skill_id >= SH_CHUL_HO_BATTERING && skill_id <= SH_HYUN_ROK_SPIRIT_POWER) return 4;
	
	// Hyper Novice
	else if (skill_id >= HN_SELFSTUDY_TATICS && skill_id <= HN_RULEBREAK) return 4;
	else if (skill_id == HN_OVERCOMING_CRISIS) return 4;
	
	// Sky Emperor
	else if (skill_id >= SKE_SKY_MASTERY && skill_id <= SKE_ENCHANTING_SKY) return 4;
	else if (skill_id >= SKE_SKY_SUN && skill_id <= SKE_STAR_LIGHT_KICK) return 4;
	
	// Shinkiro && Shiranui
	else if (skill_id >= SS_TOKEDASU && skill_id <= SS_ANKOKURYUUAKUMU) return 4;
	else if (skill_id == SS_FOUR_CHARM) return 4;
	
	// Sky Emperor
	else if (skill_id >= SKE_SKY_MASTERY && skill_id <= SKE_ENCHANTING_SKY) return 4;
	
	return 0; // Default to Novice if unknown
}

We might still need a way to check skillpoints invested in a specific job, so that if user tries to skill up a 2nd Job skill we could check if they spent all their first job skills ? hmmm

Oh dang, we also need to have specific code to handle Second High jobs / Third High jobs as high 2nd can go to joblvl 70.

Edited by eleriaqueen
Fixed function
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Answer this question...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...