Jump to content

RumbleRO

Members
  • Posts

    16
  • Joined

  • Last visited

Recent Profile Visitors

The recent visitors block is disabled and is not being shown to other users.

RumbleRO's Achievements

Poporing

Poporing (4/15)

  • Collaborator
  • One Year In
  • First Post
  • Conversation Starter
  • Dedicated

Recent Badges

1

Reputation

  1. // Copyright (c) rAthena Dev Teams - Licensed under GNU GPL // For more information, see LICENCE in the main folder #include "skill.hpp" #include <array> #include <math.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #include "../common/cbasetypes.hpp" #include "../common/ers.hpp" #include "../common/malloc.hpp" #include "../common/nullpo.hpp" #include "../common/random.hpp" #include "../common/showmsg.hpp" #include "../common/strlib.hpp" #include "../common/timer.hpp" #include "../common/utilities.hpp" #include "../common/utils.hpp" #include "achievement.hpp" #include "battle.hpp" #include "battleground.hpp" #include "chrif.hpp" #include "clif.hpp" #include "date.hpp" #include "elemental.hpp" #include "guild.hpp" #include "homunculus.hpp" #include "intif.hpp" #include "itemdb.hpp" #include "log.hpp" #include "map.hpp" #include "mercenary.hpp" #include "mob.hpp" #include "npc.hpp" #include "party.hpp" #include "path.hpp" #include "pc.hpp" #include "pc_groups.hpp" #include "pet.hpp" #include "script.hpp" #include "status.hpp" #include "unit.hpp" using namespace rathena; #define SKILLUNITTIMER_INTERVAL 100 #define TIMERSKILL_INTERVAL 150 static struct eri *skill_timer_ers = NULL; //For handling skill_timerskills [Skotlex] static DBMap* bowling_db = NULL; // int mob_id -> struct mob_data* DBMap* skillunit_db = NULL; // int id -> struct skill_unit* /** * Skill Unit Persistency during endack routes (mostly for songs see bugreport:4574) */ DBMap* skillusave_db = NULL; // char_id -> struct skill_usave struct skill_usave { uint16 skill_id, skill_lv; }; struct s_skill_produce_db skill_produce_db[MAX_SKILL_PRODUCE_DB]; static unsigned short skill_produce_count; AbraDatabase abra_db; ReadingSpellbookDatabase reading_spellbook_db; SkillArrowDatabase skill_arrow_db; #define MAX_SKILL_CHANGEMATERIAL_DB 75 #define MAX_SKILL_CHANGEMATERIAL_SET 3 struct s_skill_changematerial_db { t_itemid nameid; unsigned short rate; unsigned short qty[MAX_SKILL_CHANGEMATERIAL_SET]; unsigned short qty_rate[MAX_SKILL_CHANGEMATERIAL_SET]; }; struct s_skill_changematerial_db skill_changematerial_db[MAX_SKILL_CHANGEMATERIAL_DB]; static unsigned short skill_changematerial_count; MagicMushroomDatabase magic_mushroom_db; struct s_skill_unit_layout skill_unit_layout[MAX_SKILL_UNIT_LAYOUT]; int firewall_unit_pos; int icewall_unit_pos; int earthstrain_unit_pos; int firerain_unit_pos; int wallofthorn_unit_pos; struct s_skill_nounit_layout skill_nounit_layout[MAX_SKILL_UNIT_LAYOUT2]; static char dir_ka = -1; // Holds temporary direction to the target for SR_KNUCKLEARROW //Early declaration bool skill_strip_equip(struct block_list *src, struct block_list *target, uint16 skill_id, uint16 skill_lv); static int skill_check_unit_range (struct block_list *bl, int x, int y, uint16 skill_id, uint16 skill_lv); static int skill_check_unit_range2 (struct block_list *bl, int x, int y, uint16 skill_id, uint16 skill_lv, bool isNearNPC); static int skill_destroy_trap( struct block_list *bl, va_list ap ); static int skill_check_condition_mob_master_sub (struct block_list *bl, va_list ap); static bool skill_check_condition_sc_required(map_session_data *sd, unsigned short skill_id, struct s_skill_condition *require); static bool skill_check_unit_movepos(uint8 check_flag, struct block_list *bl, short dst_x, short dst_y, int easy, bool checkpath); // Use this function for splash skills that can't hit icewall when cast by players static inline int splash_target(struct block_list* bl) { return ( bl->type == BL_MOB ) ? BL_SKILL|BL_CHAR : BL_CHAR; } /** * Get skill id from name * @param name * @return Skill ID of the skill, or 0 if not found. **/ uint16 skill_name2id(const char* name) { if (name == nullptr) return 0; for (const auto &it : skill_db) { if (strcmpi(it.second->name, name) == 0) return it.first; } return 0; } /** * Get Skill name * @param skill_id * @return AEGIS Skill name **/ const char* skill_get_name( uint16 skill_id ) { return skill_db.find(skill_id)->name; } /** * Get Skill name * @param skill_id * @return English Skill name **/ const char* skill_get_desc( uint16 skill_id ) { return skill_db.find(skill_id)->desc; } static bool skill_check(uint16 id) { if (id == 0 || skill_get_index(id) == 0) return false; return true; } #define skill_get(id, var) do {\ if (!skill_check(id))\ return 0;\ return var;\ } while(0) #define skill_get_lv(id, lv, arrvar) do {\ if (!skill_check(id))\ return 0;\ int lv_idx = min(lv, MAX_SKILL_LEVEL) - 1;\ if (lv > MAX_SKILL_LEVEL && arrvar[lv_idx] > 1 && lv_idx > 1) {\ int a__ = arrvar[lv_idx - 2];\ int b__ = arrvar[lv_idx - 1];\ int c__ = arrvar[lv_idx];\ return (c__ + ((lv - MAX_SKILL_LEVEL + 1) * (b__ - a__) / 2) + ((lv - MAX_SKILL_LEVEL) * (c__ - b__) / 2));\ }\ return arrvar[lv_idx];\ } while(0) // Skill DB e_damage_type skill_get_hit( uint16 skill_id ) { if (!skill_check(skill_id)) return DMG_NORMAL; return skill_db.find(skill_id)->hit; } int skill_get_inf( uint16 skill_id ) { skill_get(skill_id, skill_db.find(skill_id)->inf); } int skill_get_ele( uint16 skill_id , uint16 skill_lv ) { skill_get_lv(skill_id, skill_lv, skill_db.find(skill_id)->element); } int skill_get_max( uint16 skill_id ) { skill_get(skill_id, skill_db.find(skill_id)->max); } int skill_get_range( uint16 skill_id , uint16 skill_lv ) { skill_get_lv(skill_id, skill_lv, skill_db.find(skill_id)->range); } int skill_get_splash_( uint16 skill_id , uint16 skill_lv ) { skill_get_lv(skill_id, skill_lv, skill_db.find(skill_id)->splash); } int skill_get_num( uint16 skill_id ,uint16 skill_lv ) { skill_get_lv(skill_id, skill_lv, skill_db.find(skill_id)->num); } int skill_get_cast( uint16 skill_id ,uint16 skill_lv ) { skill_get_lv(skill_id, skill_lv, skill_db.find(skill_id)->cast); } int skill_get_delay( uint16 skill_id ,uint16 skill_lv ) { skill_get_lv(skill_id, skill_lv, skill_db.find(skill_id)->delay); } int skill_get_walkdelay( uint16 skill_id ,uint16 skill_lv ) { skill_get_lv(skill_id, skill_lv, skill_db.find(skill_id)->walkdelay); } int skill_get_time( uint16 skill_id ,uint16 skill_lv ) { skill_get_lv(skill_id, skill_lv, skill_db.find(skill_id)->upkeep_time); } int skill_get_time2( uint16 skill_id ,uint16 skill_lv ) { skill_get_lv(skill_id, skill_lv, skill_db.find(skill_id)->upkeep_time2); } int skill_get_castdef( uint16 skill_id ) { skill_get(skill_id, skill_db.find(skill_id)->cast_def_rate); } int skill_get_castcancel( uint16 skill_id ) { skill_get(skill_id, skill_db.find(skill_id)->castcancel); } int skill_get_maxcount( uint16 skill_id ,uint16 skill_lv ) { skill_get_lv(skill_id, skill_lv, skill_db.find(skill_id)->maxcount); } int skill_get_blewcount( uint16 skill_id ,uint16 skill_lv ) { skill_get_lv(skill_id, skill_lv, skill_db.find(skill_id)->blewcount); } int skill_get_castnodex( uint16 skill_id ) { skill_get(skill_id, skill_db.find(skill_id)->castnodex); } int skill_get_delaynodex( uint16 skill_id ) { skill_get(skill_id, skill_db.find(skill_id)->delaynodex); } int skill_get_nocast ( uint16 skill_id ) { skill_get(skill_id, skill_db.find(skill_id)->nocast); } int skill_get_type( uint16 skill_id ) { skill_get(skill_id, skill_db.find(skill_id)->skill_type); } int skill_get_unit_id ( uint16 skill_id ) { skill_get(skill_id, skill_db.find(skill_id)->unit_id); } int skill_get_unit_id2 ( uint16 skill_id ) { skill_get(skill_id, skill_db.find(skill_id)->unit_id2); } int skill_get_unit_interval( uint16 skill_id ) { skill_get(skill_id, skill_db.find(skill_id)->unit_interval); } int skill_get_unit_range( uint16 skill_id, uint16 skill_lv ) { skill_get_lv(skill_id, skill_lv, skill_db.find(skill_id)->unit_range); } int skill_get_unit_target( uint16 skill_id ) { skill_get(skill_id, skill_db.find(skill_id)->unit_target&BCT_ALL); } int skill_get_unit_bl_target( uint16 skill_id ) { skill_get(skill_id, skill_db.find(skill_id)->unit_target&BL_ALL); } int skill_get_unit_layout_type( uint16 skill_id ,uint16 skill_lv ) { skill_get_lv(skill_id, skill_lv, skill_db.find(skill_id)->unit_layout_type); } int skill_get_cooldown( uint16 skill_id, uint16 skill_lv ) { skill_get_lv(skill_id, skill_lv, skill_db.find(skill_id)->cooldown); } int skill_get_giveap( uint16 skill_id, uint16 skill_lv ) { skill_get_lv(skill_id, skill_lv, skill_db.find(skill_id)->giveap); } #ifdef RENEWAL_CAST int skill_get_fixed_cast( uint16 skill_id ,uint16 skill_lv ) { skill_get_lv(skill_id, skill_lv, skill_db.find(skill_id)->fixed_cast); } #endif // Skill requirements int skill_get_hp( uint16 skill_id ,uint16 skill_lv ) { skill_get_lv(skill_id, skill_lv, skill_db.find(skill_id)->require.hp); } int skill_get_mhp( uint16 skill_id ,uint16 skill_lv ) { skill_get_lv(skill_id, skill_lv, skill_db.find(skill_id)->require.mhp); } int skill_get_sp( uint16 skill_id ,uint16 skill_lv ) { skill_get_lv(skill_id, skill_lv, skill_db.find(skill_id)->require.sp); } int skill_get_ap( uint16 skill_id, uint16 skill_lv ) { skill_get_lv(skill_id, skill_lv, skill_db.find(skill_id)->require.ap); } int skill_get_hp_rate( uint16 skill_id, uint16 skill_lv ) { skill_get_lv(skill_id, skill_lv, skill_db.find(skill_id)->require.hp_rate); } int skill_get_sp_rate( uint16 skill_id, uint16 skill_lv ) { skill_get_lv(skill_id, skill_lv, skill_db.find(skill_id)->require.sp_rate); } int skill_get_ap_rate(uint16 skill_id, uint16 skill_lv) { skill_get_lv(skill_id, skill_lv, skill_db.find(skill_id)->require.ap_rate); } int skill_get_zeny( uint16 skill_id ,uint16 skill_lv ) { skill_get_lv(skill_id, skill_lv, skill_db.find(skill_id)->require.zeny); } int skill_get_weapontype( uint16 skill_id ) { skill_get(skill_id, skill_db.find(skill_id)->require.weapon); } int skill_get_ammotype( uint16 skill_id ) { skill_get(skill_id, skill_db.find(skill_id)->require.ammo); } int skill_get_ammo_qty( uint16 skill_id, uint16 skill_lv ) { skill_get_lv(skill_id, skill_lv, skill_db.find(skill_id)->require.ammo_qty); } int skill_get_state( uint16 skill_id ) { skill_get(skill_id, skill_db.find(skill_id)->require.state); } int skill_get_status_count( uint16 skill_id ) { skill_get(skill_id, skill_db.find(skill_id)->require.status.size()); } int skill_get_spiritball( uint16 skill_id, uint16 skill_lv ) { skill_get_lv(skill_id, skill_lv, skill_db.find(skill_id)->require.spiritball); } sc_type skill_get_sc(int16 skill_id) { if (!skill_check(skill_id)) return SC_NONE; return skill_db.find(skill_id)->sc; } int skill_get_splash( uint16 skill_id , uint16 skill_lv ) { int splash = skill_get_splash_(skill_id, skill_lv); if (splash < 0) return AREA_SIZE; return splash; } bool skill_get_nk_(uint16 skill_id, std::vector<e_skill_nk> nk) { if( skill_id == 0 ){ return false; } std::shared_ptr<s_skill_db> skill = skill_db.find(skill_id); if (!skill) return false; for (const auto &nkit : nk) { if (skill->nk[nkit]) return true; } return false; } bool skill_get_inf2_(uint16 skill_id, std::vector<e_skill_inf2> inf2) { if( skill_id == 0 ){ return false; } std::shared_ptr<s_skill_db> skill = skill_db.find(skill_id); if (!skill) return false; for (const auto &inf2it : inf2) { if (skill->inf2[inf2it]) return true; } return false; } bool skill_get_unit_flag_(uint16 skill_id, std::vector<e_skill_unit_flag> unit) { if( skill_id == 0 ){ return false; } std::shared_ptr<s_skill_db> skill = skill_db.find(skill_id); if (!skill) return false; for (const auto &unitit : unit) { if (skill->unit_flag[unitit]) return true; } return false; } int skill_tree_get_max(uint16 skill_id, int b_class) { auto skill = skill_tree_db.get_skill_data(b_class, skill_id); if (skill != nullptr) return skill->max_lv; else return skill_get_max(skill_id); } int skill_frostjoke_scream(struct block_list *bl,va_list ap); int skill_attack_area(struct block_list *bl,va_list ap); std::shared_ptr<s_skill_unit_group> skill_locate_element_field(struct block_list *bl); // [Skotlex] int skill_graffitiremover(struct block_list *bl, va_list ap); // [Valaris] int skill_greed(struct block_list *bl, va_list ap); static int skill_cell_overlap(struct block_list *bl, va_list ap); static int skill_trap_splash(struct block_list *bl, va_list ap); struct skill_unit_group_tickset *skill_unitgrouptickset_search(struct block_list *bl,std::shared_ptr<s_skill_unit_group> sg,t_tick tick); static int skill_unit_onplace(struct skill_unit *src,struct block_list *bl,t_tick tick); int skill_unit_onleft(uint16 skill_id, struct block_list *bl,t_tick tick); static int skill_unit_effect(struct block_list *bl,va_list ap); static int skill_bind_trap(struct block_list *bl, va_list ap); e_cast_type skill_get_casttype (uint16 skill_id) { std::shared_ptr<s_skill_db> skill = skill_db.find(skill_id); if( skill == nullptr ){ return CAST_DAMAGE; } if (skill->inf&(INF_GROUND_SKILL)) return CAST_GROUND; if (skill->inf&INF_SUPPORT_SKILL) return CAST_NODAMAGE; if (skill->inf&INF_SELF_SKILL) { if(skill->inf2[INF2_NOTARGETSELF]) return CAST_DAMAGE; //Combo skill. return CAST_NODAMAGE; } if (skill->nk[NK_NODAMAGE]) return CAST_NODAMAGE; return CAST_DAMAGE; } //Returns actual skill range taking into account attack range and AC_OWL [Skotlex] int skill_get_range2(struct block_list *bl, uint16 skill_id, uint16 skill_lv, bool isServer) { if( bl->type == BL_MOB && battle_config.mob_ai&0x400 ) return 9; //Mobs have a range of 9 regardless of skill used. int32 range = skill_get_range(skill_id, skill_lv); if( range < 0 ) { if( battle_config.use_weapon_skill_range&bl->type ) return status_get_range(bl); range *=-1; } if (isServer && range > 14) { range = 14; // Server-sided base range can't be above 14 } std::bitset<INF2_MAX> inf2 = skill_db.find(skill_id)->inf2; if(inf2[INF2_ALTERRANGEVULTURE] || inf2[INF2_ALTERRANGESNAKEEYE] ){ if( bl->type == BL_PC ) { if(inf2[INF2_ALTERRANGEVULTURE]) range += pc_checkskill((TBL_PC*)bl, AC_VULTURE); // added to allow GS skills to be effected by the range of Snake Eyes [Reddozen] if(inf2[INF2_ALTERRANGESNAKEEYE]) range += pc_checkskill((TBL_PC*)bl, GS_SNAKEEYE); } else range += battle_config.mob_eye_range_bonus; } if(inf2[INF2_ALTERRANGESHADOWJUMP] || inf2[INF2_ALTERRANGERADIUS] || inf2[INF2_ALTERRANGERESEARCHTRAP] ){ if( bl->type == BL_PC ) { if(inf2[INF2_ALTERRANGESHADOWJUMP]) range = skill_get_range(NJ_SHADOWJUMP,pc_checkskill((TBL_PC*)bl,NJ_SHADOWJUMP)); if(inf2[INF2_ALTERRANGERADIUS]) range += pc_checkskill((TBL_PC*)bl, WL_RADIUS); if(inf2[INF2_ALTERRANGERESEARCHTRAP]) { int rt_range[11] = { 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5 }; range += rt_range[pc_checkskill((TBL_PC*)bl, RA_RESEARCHTRAP)]; } } } if( !range && bl->type != BL_PC ) return 9; // Enable non players to use self skills on others. [Skotlex] return range; } /** Copy Referral: dummy skills should point to their source. * @param skill_id Dummy skill ID * @return Real skill id if found **/ unsigned short skill_dummy2skill_id(unsigned short skill_id) { switch (skill_id) { case AB_DUPLELIGHT_MELEE: case AB_DUPLELIGHT_MAGIC: return AB_DUPLELIGHT; case WL_CHAINLIGHTNING_ATK: return WL_CHAINLIGHTNING; case WL_TETRAVORTEX_FIRE: case WL_TETRAVORTEX_WATER: case WL_TETRAVORTEX_WIND: case WL_TETRAVORTEX_GROUND: return WL_TETRAVORTEX; case WL_SUMMON_ATK_FIRE: return WL_SUMMONFB; case WL_SUMMON_ATK_WIND: return WL_SUMMONBL; case WL_SUMMON_ATK_WATER: return WL_SUMMONWB; case WL_SUMMON_ATK_GROUND: return WL_SUMMONSTONE; case LG_OVERBRAND_BRANDISH: case LG_OVERBRAND_PLUSATK: return LG_OVERBRAND; case WM_REVERBERATION_MELEE: case WM_REVERBERATION_MAGIC: return WM_REVERBERATION; case WM_SEVERE_RAINSTORM_MELEE: return WM_SEVERE_RAINSTORM; case GN_CRAZYWEED_ATK: return GN_CRAZYWEED; case GN_HELLS_PLANT_ATK: return GN_HELLS_PLANT; case GN_SLINGITEM_RANGEMELEEATK: return GN_SLINGITEM; case RL_R_TRIP_PLUSATK: return RL_R_TRIP; case NPC_MAXPAIN_ATK: return NPC_MAXPAIN; case SU_CN_METEOR2: return SU_CN_METEOR; case SU_SV_ROOTTWIST_ATK: return SU_SV_ROOTTWIST; case SU_LUNATICCARROTBEAT2: return SU_LUNATICCARROTBEAT; case NPC_REVERBERATION_ATK: return NPC_REVERBERATION; case NPC_MAGMA_ERUPTION_DOTDAMAGE: return NPC_MAGMA_ERUPTION; case NPC_DANCINGBLADE_ATK: return NPC_DANCINGBLADE; case DK_SERVANTWEAPON_ATK: return DK_SERVANTWEAPON; case DK_HACKANDSLASHER_ATK: return DK_HACKANDSLASHER; case AG_DESTRUCTIVE_HURRICANE_CLIMAX: return AG_DESTRUCTIVE_HURRICANE; case AG_VIOLENT_QUAKE_ATK: return AG_VIOLENT_QUAKE; case AG_ALL_BLOOM_ATK: case AG_ALL_BLOOM_ATK2: return AG_ALL_BLOOM; case AG_CRYSTAL_IMPACT_ATK: return AG_CRYSTAL_IMPACT; case AG_ASTRAL_STRIKE_ATK: return AG_ASTRAL_STRIKE; case AG_CRIMSON_ARROW_ATK: return AG_CRIMSON_ARROW; case CD_ARBITRIUM_ATK: return CD_ARBITRIUM; case ABC_CHAIN_REACTION_SHOT_ATK: return ABC_CHAIN_REACTION_SHOT; case ABC_FROM_THE_ABYSS_ATK: return ABC_FROM_THE_ABYSS; case BO_ACIDIFIED_ZONE_WATER_ATK: return BO_ACIDIFIED_ZONE_WATER; case BO_ACIDIFIED_ZONE_GROUND_ATK: return BO_ACIDIFIED_ZONE_GROUND; case BO_ACIDIFIED_ZONE_WIND_ATK: return BO_ACIDIFIED_ZONE_WIND; case BO_ACIDIFIED_ZONE_FIRE_ATK: return BO_ACIDIFIED_ZONE_FIRE; case TR_ROSEBLOSSOM_ATK: return TR_ROSEBLOSSOM; case EM_ELEMENTAL_BUSTER_FIRE: case EM_ELEMENTAL_BUSTER_WATER: case EM_ELEMENTAL_BUSTER_WIND: case EM_ELEMENTAL_BUSTER_GROUND: case EM_ELEMENTAL_BUSTER_POISON: return EM_ELEMENTAL_BUSTER; } return skill_id; } /** * Check skill unit maxcount * @param src: Caster to check against * @param x: X location of skill * @param y: Y location of skill * @param skill_id: Skill used * @param skill_lv: Skill level used * @param type: Type of unit to check against for battle_config checks * @param display_failure: Display skill failure message * @return True on skill cast success or false on failure */ bool skill_pos_maxcount_check(struct block_list *src, int16 x, int16 y, uint16 skill_id, uint16 skill_lv, enum bl_type type, bool display_failure) { if (!src) return false; struct unit_data *ud = unit_bl2ud(src); map_session_data *sd = map_id2sd(src->id); int maxcount = 0; std::shared_ptr<s_skill_db> skill = skill_db.find(skill_id); if (!(type&battle_config.skill_reiteration) && skill->unit_flag[UF_NOREITERATION] && skill_check_unit_range(src, x, y, skill_id, skill_lv)) { if (sd && display_failure) clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); return false; } if (type&battle_config.skill_nofootset && skill->unit_flag[UF_NOFOOTSET] && skill_check_unit_range2(src, x, y, skill_id, skill_lv, false)) { if (sd && display_failure) clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); return false; } if (type&battle_config.land_skill_limit && (maxcount = skill_get_maxcount(skill_id, skill_lv)) > 0) { unit_skillunit_maxcount(*ud, skill_id, maxcount); if (maxcount == 0) { if (sd && display_failure) clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); return false; } } return true; } /** * Calculates heal value of skill's effect * @param src: Unit casting heal * @param target: Target of src * @param skill_id: Skill ID used * @param skill_lv: Skill Level used * @param heal: True if it's the heal part or false if it's the damage part of the skill * @return modified heal value */ int skill_calc_heal(struct block_list *src, struct block_list *target, uint16 skill_id, uint16 skill_lv, bool heal) { int skill, hp = 0; #ifdef RENEWAL int hp_bonus = 0; double global_bonus = 1; #endif map_session_data *sd = BL_CAST(BL_PC, src); map_session_data *tsd = BL_CAST(BL_PC, target); status_change *sc, *tsc; sc = status_get_sc(src); tsc = status_get_sc(target); switch( skill_id ) { #ifndef RENEWAL case BA_APPLEIDUN: hp = 30 + 5 * skill_lv + (status_get_vit(src) / 2); // HP recovery if (sd) hp += 5 * pc_checkskill(sd, BA_MUSICALLESSON); break; #endif case PR_SANCTUARY: hp = (skill_lv > 6) ? 777 : skill_lv * 100; break; case NPC_EVILLAND: hp = (skill_lv > 6) ? 666 : skill_lv * 100; break; case AB_HIGHNESSHEAL: #ifdef RENEWAL hp = ((status_get_int(src) + status_get_lv(src)) / 5) * 30; if (sd && ((skill = pc_checkskill(sd, HP_MEDITATIO)) > 0)) hp_bonus += skill * 2; #else hp = ((status_get_lv(src) + status_get_int(src)) / * (4 + ((sd ? pc_checkskill(sd, AL_HEAL) : 1) * 8)); hp = (hp * (17 + 3 * skill_lv)) / 10; #endif break; case SU_FRESHSHRIMP: hp = (status_get_lv(src) + status_get_int(src)) / 5 * 6; break; case SU_BUNCHOFSHRIMP: hp = (status_get_lv(src) + status_get_int(src)) / 5 * 15; break; case CD_MEDIALE_VOTUM:// Does the learned level of heal affect this skill? case CD_DILECTIO_HEAL:// Same question for this one too. [Rytech] //hp = (status_get_lv(src) + status_get_int(src)) / 5 * 30 * pc_checkskill(sd, AL_HEAL) / 10; hp = (status_get_lv(src) + status_get_int(src)) / 5 * 30; #ifdef RENEWAL if (sd && ((skill = pc_checkskill(sd, HP_MEDITATIO)) > 0)) hp_bonus += skill * 2; #endif break; default: if (skill_lv >= battle_config.max_heal_lv) return battle_config.max_heal; #ifdef RENEWAL /** * Renewal Heal Formula * Formula: ( [(Base Level + INT) / 5] x 30 ) x (Heal Level / 10) x (Modifiers) + MATK */ hp = (status_get_lv(src) + status_get_int(src)) / 5 * 30 * skill_lv / 10; #else hp = (status_get_lv(src) + status_get_int(src)) / 8 * (4 + (skill_lv * 8)); #endif if (sd && ((skill = pc_checkskill(sd, HP_MEDITATIO)) > 0)) #ifdef RENEWAL hp_bonus += skill * 2; #else hp += hp * skill * 2 / 100; #endif else if (src->type == BL_HOM && (skill = hom_checkskill(((TBL_HOM*)src), HLIF_BRAIN)) > 0) #ifdef RENEWAL hp_bonus += skill * 2; #else hp += hp * skill * 2 / 100; #endif if (sd && tsd && sd->status.partner_id == tsd->status.char_id && (sd->class_&MAPID_UPPERMASK) == MAPID_SUPER_NOVICE && sd->status.sex == 0) hp *= 2; break; } if( (!heal || (target && target->type == BL_MER)) && skill_id != NPC_EVILLAND ) hp >>= 1; if (sd) { if (pc_checkskill(sd, SU_POWEROFSEA) > 0) { #ifdef RENEWAL hp_bonus += 10; #else hp += hp * 10 / 100; #endif if (pc_checkskill_summoner(sd, SUMMONER_POWER_SEA) >= 20) #ifdef RENEWAL hp_bonus += 20; #else hp += hp * 20 / 100; #endif } if ((skill = pc_checkskill(sd, NV_BREAKTHROUGH)) > 0) #ifdef RENEWAL hp_bonus += 2 * skill; #else hp += hp * skill * 2 / 100; #endif if ((skill = pc_checkskill(sd, NV_TRANSCENDENCE)) > 0) #ifdef RENEWAL hp_bonus += 3 * skill; #else hp += hp * skill * 3 / 100; #endif if (skill = pc_skillheal_bonus(sd, skill_id)) #ifdef RENEWAL hp_bonus += skill; #else hp += hp * skill / 100; #endif } if (tsd && (skill = pc_skillheal2_bonus(tsd, skill_id))) #ifdef RENEWAL hp_bonus += skill; #else hp += hp * skill / 100; #endif if (sc && sc->count) { if (sc->getSCE(SC_OFFERTORIUM) && (skill_id == AB_HIGHNESSHEAL || skill_id == AB_CHEAL || skill_id == NPC_CHEAL || skill_id == PR_SANCTUARY || skill_id == AL_HEAL || skill_id == CD_DILECTIO_HEAL || skill_id == CD_MEDIALE_VOTUM )) #ifdef RENEWAL hp_bonus += sc->getSCE(SC_OFFERTORIUM)->val2; #else hp += hp * sc->getSCE(SC_OFFERTORIUM)->val2 / 100; #endif if (sc->getSCE(SC_GLASTHEIM_HEAL) && skill_id != NPC_EVILLAND && skill_id != BA_APPLEIDUN) #ifdef RENEWAL hp_bonus += sc->getSCE(SC_GLASTHEIM_HEAL)->val1; #else hp += hp * sc->getSCE(SC_GLASTHEIM_HEAL)->val1 / 100; #endif #ifdef RENEWAL if (sc->getSCE(SC_MEDIALE) && skill_id == CD_MEDIALE_VOTUM) hp_bonus += sc->getSCE(SC_MEDIALE)->val2; #endif } if (tsc && tsc->count) { if (skill_id != NPC_EVILLAND && skill_id != BA_APPLEIDUN) { if (tsc->getSCE(SC_INCHEALRATE)) #ifdef RENEWAL hp_bonus += tsc->getSCE(SC_INCHEALRATE)->val1; //Only affects Heal, Sanctuary and PotionPitcher.(like bHealPower) [Inkfish] #else hp += hp * tsc->getSCE(SC_INCHEALRATE)->val1 / 100; #endif if (tsc->getSCE(SC_GLASTHEIM_HEAL)) #ifdef RENEWAL hp_bonus += tsc->getSCE(SC_GLASTHEIM_HEAL)->val2; #else hp += hp * tsc->getSCE(SC_GLASTHEIM_HEAL)->val2 / 100; #endif if (tsc->getSCE(SC_ANCILLA)) #ifdef RENEWAL hp_bonus += tsc->getSCE(SC_ANCILLA)->val1; #else hp += hp * tsc->getSCE(SC_ANCILLA)->val1 / 100; if (tsc->getSCE(SC_WATER_INSIGNIA) && tsc->getSCE(SC_WATER_INSIGNIA)->val1 == 2) hp += hp / 10; #endif #ifdef RENEWAL if (tsc->getSCE(SC_ASSUMPTIO)) hp_bonus += tsc->getSCE(SC_ASSUMPTIO)->val1 * 2; #endif if (tsc->getSCE(SC_VITALIZE_POTION)) #ifdef RENEWAL hp_bonus += 10; #else hp += hp * 10 / 100; #endif } } #ifdef RENEWAL if (hp_bonus) hp += hp * hp_bonus / 100; // MATK part of the RE heal formula [malufett] // Note: in this part matk bonuses from items or skills are not applied switch( skill_id ) { case BA_APPLEIDUN: case PR_SANCTUARY: case NPC_EVILLAND: break; default: { struct status_data *status = status_get_status_data(src); int min, max; min = status_base_matk_min(src, status, status_get_lv(src)); max = status_base_matk_max(src, status, status_get_lv(src)); if( status->rhw.matk > 0 ){ int wMatk, variance; wMatk = status->rhw.matk; variance = wMatk * status->rhw.wlv / 10; min += wMatk - variance; max += wMatk + variance; } if( sc && sc->getSCE(SC_RECOGNIZEDSPELL) ) min = max; if( sd && sd->right_weapon.overrefine > 0 ){ min++; max += sd->right_weapon.overrefine - 1; } if(max > min) hp += min+rnd()%(max-min); else hp += min; } } // Global multipliers are applied after the MATK is applied if (tsc && tsc->count) { if (skill_id != NPC_EVILLAND && skill_id != BA_APPLEIDUN) { if (tsc->getSCE(SC_WATER_INSIGNIA) && tsc->getSCE(SC_WATER_INSIGNIA)->val1 == 2) global_bonus *= 1.1f; } } if (skill_id == AB_HIGHNESSHEAL) global_bonus *= 2 + 0.3f * (skill_lv - 1); else if (skill_id == CD_DILECTIO_HEAL)// Description says its 10% on Lv 1 but thats clearly a typo. [Rytech] global_bonus *= 1 + 0.15f + 0.05f * skill_lv; #endif if (heal && tsc && tsc->count) { uint8 penalty = 0; if (tsc->getSCE(SC_CRITICALWOUND)) penalty += tsc->getSCE(SC_CRITICALWOUND)->val2; if (tsc->getSCE(SC_DEATHHURT) && tsc->getSCE(SC_DEATHHURT)->val3 == 1) penalty += 20; if (tsc->getSCE(SC_NORECOVER_STATE)) penalty = 100; if (penalty > 0) { #ifdef RENEWAL penalty = cap_value(penalty, 1, 100); global_bonus *= (100 - penalty) / 100.f; #else hp -= hp * penalty / 100; #endif } } #ifdef RENEWAL hp = (int)(hp * global_bonus); // Final heal increased by HPlus. // Is this the right place for this??? [Rytech] if ( sd && status_get_hplus(src) > 0 ) hp += hp * status_get_hplus(src) / 100; return (heal) ? max(1, hp) : hp; #else return hp; #endif } /** * Making Plagiarism and Reproduce check their own function * Previous prevention for NPC skills, Wedding skills, and INF3_DIS_PLAGIA are removed since we use skill_db.yml [Cydh] * @param sd: Player who will copy the skill * @param skill_id: Target skill * @return 0 - Cannot be copied; 1 - Can be copied by Plagiarism 2 - Can be copied by Reproduce * @author Aru - for previous check; Jobbie for class restriction idea; Cydh expands the copyable skill */ int8 skill_isCopyable(map_session_data *sd, uint16 skill_id) { uint16 skill_idx = skill_get_index(skill_id); if (!skill_idx) return 0; // Only copy skill that player doesn't have or the skill is old clone if (sd->status.skill[skill_idx].id != 0 && sd->status.skill[skill_idx].flag != SKILL_FLAG_PLAGIARIZED) return 0; s_skill_copyable copyable = skill_db.find(skill_id)->copyable; //Plagiarism only able to copy skill while SC_PRESERVE is not active and skill is copyable by Plagiarism if (copyable.option & SKILL_COPY_PLAGIARISM && pc_checkskill(sd,RG_PLAGIARISM) && !sd->sc.getSCE(SC_PRESERVE)) return 1; //Reproduce can copy skill if SC__REPRODUCE is active and the skill is copyable by Reproduce if (copyable.option & SKILL_COPY_REPRODUCE && pc_checkskill(sd,SC_REPRODUCE) && sd->sc.getSCE(SC__REPRODUCE) && sd->sc.getSCE(SC__REPRODUCE)->val1) return 2; return 0; } /** * Check if the skill is ok to cast and when. * Done before skill_check_condition_castbegin, requirement * @param skill_id: Skill ID that casted * @param sd: Player who casted * @return true: Skill cannot be used, false: otherwise * @author [MouseJstr] */ bool skill_isNotOk(uint16 skill_id, map_session_data *sd) { nullpo_retr(1,sd); if (pc_has_permission(sd,PC_PERM_SKILL_UNCONDITIONAL)) return false; // can do any damn thing they want if (skill_id == AL_TELEPORT && sd->skillitem == skill_id && sd->skillitemlv > 2) return false; // Teleport lv 3 bypasses this check.[Inkfish] struct map_data *mapdata = map_getmapdata(sd->bl.m); if (mapdata->flag[MF_NOSKILL] && skill_id != ALL_EQSWITCH && !sd->skillitem) //Item skills bypass noskill return true; // Epoque: // This code will compare the player's attack motion value which is influenced by ASPD before // allowing a skill to be cast. This is to prevent no-delay ACT files from spamming skills such as // AC_DOUBLE which do not have a skill delay and are not regarded in terms of attack motion. if (!sd->state.autocast && sd->skillitem != skill_id && sd->canskill_tick && DIFF_TICK(gettick(),sd->canskill_tick) < (sd->battle_status.amotion * (battle_config.skill_amotion_leniency) / 100)) {// attempted to cast a skill before the attack motion has finished return true; } if (skill_blockpc_get(sd, skill_id) != -1){ clif_skill_fail(sd,skill_id,USESKILL_FAIL_SKILLINTERVAL,0); return true; } /** * It has been confirmed on a official server (thanks to Yommy) that item-cast skills bypass all mapflag restrictions * Also, without this check, an exploit where an item casting + healing (or any other kind buff) isn't deleted after used on a restricted map */ if( sd->skillitem == skill_id && !sd->skillitem_keep_requirement && !sd->state.abra_flag) return false; uint32 skill_nocast = skill_get_nocast(skill_id); // Check skill restrictions [Celest] if( (skill_nocast&1 && !mapdata_flag_vs2(mapdata)) || (skill_nocast&2 && mapdata->flag[MF_PVP]) || (skill_nocast&4 && mapdata_flag_gvg2_no_te(mapdata)) || (skill_nocast&8 && mapdata->flag[MF_BATTLEGROUND]) || (skill_nocast&16 && mapdata_flag_gvg2_te(mapdata)) || // WOE:TE (mapdata->zone && skill_nocast&(mapdata->zone) && mapdata->flag[MF_RESTRICTED]) ){ clif_msg(sd, SKILL_CANT_USE_AREA); // This skill cannot be used within this area return true; } if( sd->sc.getSCE(SC_ALL_RIDING) ) return true; //You can't use skills while in the new mounts (The client doesn't let you, this is to make cheat-safe) switch (skill_id) { case AL_WARP: case RETURN_TO_ELDICASTES: case ALL_GUARDIAN_RECALL: case ECLAGE_RECALL: case ALL_PRONTERA_RECALL: if(mapdata->flag[MF_NOWARP]) { clif_skill_teleportmessage(sd,0); return true; } return false; case AL_TELEPORT: case NPC_FATALMENACE: case SC_DIMENSIONDOOR: case ALL_ODINS_RECALL: case WE_CALLALLFAMILY: if(mapdata->flag[MF_NOTELEPORT]) { clif_skill_teleportmessage(sd,0); return true; } return false; // gonna be checked in 'skill_castend_nodamage_id' case WE_CALLPARTNER: case WE_CALLPARENT: case WE_CALLBABY: if (mapdata->flag[MF_NOMEMO]) { clif_skill_teleportmessage(sd,1); return true; } break; case MC_VENDING: if (map_getmapflag(sd->bl.m, MF_NOVENDING)) { clif_displaymessage(sd->fd, msg_txt(sd, 276)); // "You can't open a shop on this map" clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); return true; } if (map_getcell(sd->bl.m, sd->bl.x, sd->bl.y, CELL_CHKNOVENDING)) { clif_displaymessage(sd->fd, msg_txt(sd, 204)); // "You can't open a shop on this cell." clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); return true; } if (npc_isnear(&sd->bl)) { // uncomment to send msg_txt. //char output[150]; //sprintf(output, msg_txt(662), battle_config.min_npc_vendchat_distance); //clif_displaymessage(sd->fd, output); clif_skill_fail(sd, skill_id, USESKILL_FAIL_THERE_ARE_NPC_AROUND, 0); return true; } break; case ALL_BUYING_STORE: if( map_getmapflag(sd->bl.m, MF_NOBUYINGSTORE) ) { clif_displaymessage (sd->fd, msg_txt(sd,276)); // "You can't open a shop on this map" clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); return true; } if( map_getcell(sd->bl.m,sd->bl.x,sd->bl.y,CELL_CHKNOBUYINGSTORE) ) { clif_displaymessage (sd->fd, msg_txt(sd,204)); // "You can't open a shop on this cell." clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); return true; } if( npc_isnear(&sd->bl) ) { // uncomment to send msg_txt. //char output[150]; //sprintf(output, msg_txt(662), battle_config.min_npc_vendchat_distance); //clif_displaymessage(sd->fd, output); clif_skill_fail(sd,skill_id,USESKILL_FAIL_THERE_ARE_NPC_AROUND,0); return true; } break; case MC_IDENTIFY: return false; // always allowed case WZ_ICEWALL: // noicewall flag [Valaris] if (mapdata->flag[MF_NOICEWALL]) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); return true; } break; case GC_DARKILLUSION: if( mapdata_flag_gvg2(mapdata) ) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); return true; } break; case GD_EMERGENCYCALL: case GD_ITEMEMERGENCYCALL: if ( !(battle_config.emergency_call&((is_agit_start())?2:1)) || !(battle_config.emergency_call&(mapdata_flag_gvg2(mapdata)?8:4)) || (battle_config.emergency_call&16 && mapdata->flag[MF_NOWARPTO] && !(mapdata->flag[MF_GVG_CASTLE] || mapdata->flag[MF_GVG_TE_CASTLE])) ) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); return true; } break; case WM_SIRCLEOFNATURE: case WM_SOUND_OF_DESTRUCTION: case WM_LULLABY_DEEPSLEEP: case WM_GLOOMYDAY: case WM_SATURDAY_NIGHT_FEVER: if( !mapdata_flag_vs(mapdata) ) { clif_skill_teleportmessage(sd,2); // This skill uses this msg instead of skill fails. return true; } break; } return false; } /** * Check if the homunculus skill is ok to be processed * After checking from Homunculus side, also check the master condition * @param hd: Homunculus who casted * @param skill_id: Skill ID casted * @param skill_lv: Skill level casted * @return true: Skill cannot be used, false: otherwise */ bool skill_isNotOk_hom(struct homun_data *hd, uint16 skill_id, uint16 skill_lv) { map_session_data *sd = NULL; status_change *sc; int8 spiritball = 0; nullpo_retr(true, hd); spiritball = skill_get_spiritball(skill_id, skill_lv); sd = hd->master; sc = status_get_sc(&hd->bl); if (!sd) return true; if (sc && !sc->count) sc = NULL; if (util::vector_exists(hd->blockskill, skill_id)) return true; switch(skill_id) { case HFLI_SBR44: if (hom_get_intimacy_grade(hd) <= HOMGRADE_HATE_WITH_PASSION) { clif_skill_fail(sd, skill_id, USESKILL_FAIL_RELATIONGRADE, 0); return true; } break; case HVAN_EXPLOSION: if (hd->homunculus.intimacy < (unsigned int)battle_config.hvan_explosion_intimate) { clif_skill_fail(sd, skill_id, USESKILL_FAIL_RELATIONGRADE, 0); return true; } break; case MH_LIGHT_OF_REGENE: // Must be cordial if (hom_get_intimacy_grade(hd) < HOMGRADE_CORDIAL) { clif_skill_fail(sd, skill_id, USESKILL_FAIL_RELATIONGRADE, 0); return true; } break; case MH_GOLDENE_FERSE: // Can't be used with Angriff's Modus if (sc && sc->getSCE(SC_ANGRIFFS_MODUS)) return true; break; case MH_ANGRIFFS_MODUS: if (sc && sc->getSCE(SC_GOLDENE_FERSE)) return true; break; case MH_TINDER_BREAKER: // Must be in grappling mode if (!(sc && sc->getSCE(SC_STYLE_CHANGE) && sc->getSCE(SC_STYLE_CHANGE)->val1 == MH_MD_GRAPPLING)) { clif_skill_fail(sd, skill_id, USESKILL_FAIL_STYLE_CHANGE_GRAPPLER, 1); return true; } break; case MH_SONIC_CRAW: // Must be in fighting mode if (!(sc && sc->getSCE(SC_STYLE_CHANGE) && sc->getSCE(SC_STYLE_CHANGE)->val1 == MH_MD_FIGHTING)) { clif_skill_fail(sd, skill_id, USESKILL_FAIL_STYLE_CHANGE_FIGHTER, 0); return true; } break; case MH_SILVERVEIN_RUSH: if (!(sc && sc->getSCE(SC_STYLE_CHANGE) && sc->getSCE(SC_STYLE_CHANGE)->val1 == MH_MD_FIGHTING)) { clif_skill_fail(sd, skill_id, USESKILL_FAIL_STYLE_CHANGE_FIGHTER, 0); return true; } if (!(sc && sc->getSCE(SC_COMBO) && sc->getSCE(SC_COMBO)->val1 == MH_SONIC_CRAW)) { clif_skill_fail(sd, skill_id, USESKILL_FAIL_COMBOSKILL, MH_SONIC_CRAW); return true; } break; case MH_MIDNIGHT_FRENZY: if (!(sc && sc->getSCE(SC_STYLE_CHANGE) && sc->getSCE(SC_STYLE_CHANGE)->val1 == MH_MD_FIGHTING)) { clif_skill_fail(sd, skill_id, USESKILL_FAIL_STYLE_CHANGE_FIGHTER, 0); return true; } if (!(sc && sc->getSCE(SC_COMBO) && sc->getSCE(SC_COMBO)->val1 == MH_SILVERVEIN_RUSH)) { clif_skill_fail(sd, skill_id, USESKILL_FAIL_COMBOSKILL, MH_SILVERVEIN_RUSH); return true; } break; case MH_CBC: if (!(sc && sc->getSCE(SC_STYLE_CHANGE) && sc->getSCE(SC_STYLE_CHANGE)->val1 == MH_MD_GRAPPLING)) { clif_skill_fail(sd, skill_id, USESKILL_FAIL_STYLE_CHANGE_GRAPPLER, 0); return true; } if (!(sc && sc->getSCE(SC_COMBO) && sc->getSCE(SC_COMBO)->val1 == MH_TINDER_BREAKER)) { clif_skill_fail(sd, skill_id, USESKILL_FAIL_COMBOSKILL, MH_TINDER_BREAKER); return true; } break; case MH_EQC: if (!(sc && sc->getSCE(SC_STYLE_CHANGE) && sc->getSCE(SC_STYLE_CHANGE)->val1 == MH_MD_GRAPPLING)) { clif_skill_fail(sd, skill_id, USESKILL_FAIL_STYLE_CHANGE_GRAPPLER, 0); return true; } if (!(sc && sc->getSCE(SC_COMBO) && sc->getSCE(SC_COMBO)->val1 == MH_CBC)) { clif_skill_fail(sd, skill_id, USESKILL_FAIL_COMBOSKILL, MH_CBC); return true; } break; } if (spiritball) { if (hd->homunculus.spiritball < spiritball) { clif_skill_fail(sd, skill_id, USESKILL_FAIL_SPIRITS, spiritball); return true; } hom_delspiritball(hd, spiritball, 1); } //Use master's criteria. return skill_isNotOk(skill_id, hd->master); } /** * Check if the mercenary skill is ok to be processed * After checking from Homunculus side, also check the master condition * @param skill_id: Skill ID that casted * @param md: Mercenary who casted * @return true: Skill cannot be used, false: otherwise */ bool skill_isNotOk_mercenary(uint16 skill_id, s_mercenary_data *md) { nullpo_retr(1, md); if (util::vector_exists(md->blockskill, skill_id)) return true; return skill_isNotOk(skill_id, md->master); } /** * Check if the skill can be casted near NPC or not * @param src Object who casted * @param skill_id Skill ID that casted * @param skill_lv Skill Lv * @param pos_x Position x of the target * @param pos_y Position y of the target * @return true: Skill cannot be used, false: otherwise * @author [Cydh] */ bool skill_isNotOk_npcRange(struct block_list *src, uint16 skill_id, uint16 skill_lv, int pos_x, int pos_y) { if (!src) return false; if (src->type == BL_PC && pc_has_permission(BL_CAST(BL_PC,src),PC_PERM_SKILL_UNCONDITIONAL)) return false; //if self skill if (skill_get_inf(skill_id)&INF_SELF_SKILL) { pos_x = src->x; pos_y = src->y; } if (pos_x <= 0) pos_x = src->x; if (pos_y <= 0) pos_y = src->y; return skill_check_unit_range2(src,pos_x,pos_y,skill_id,skill_lv,true) != 0; } struct s_skill_unit_layout *skill_get_unit_layout(uint16 skill_id, uint16 skill_lv, struct block_list* src, int x, int y) { int pos = skill_get_unit_layout_type(skill_id,skill_lv); uint8 dir; if (pos < -1 || pos >= MAX_SKILL_UNIT_LAYOUT) { ShowError("skill_get_unit_layout: unsupported layout type %d for skill %d (level %d)\n", pos, skill_id, skill_lv); pos = cap_value(pos, 0, MAX_SQUARE_LAYOUT); // cap to nearest square layout } nullpo_retr(NULL, src); //Monsters sometimes deploy more units on level 10 if (src->type == BL_MOB && skill_lv >= 10) { if (skill_id == WZ_WATERBALL) pos = 4; //9x9 Area } if (pos != -1) // simple single-definition layout return &skill_unit_layout[pos]; dir = (src->x == x && src->y == y) ? 6 : map_calc_dir(src,x,y); // 6 - default aegis direction if (skill_id == MG_FIREWALL) return &skill_unit_layout [firewall_unit_pos + dir]; else if (skill_id == WZ_ICEWALL) return &skill_unit_layout [icewall_unit_pos + dir]; else if( skill_id == WL_EARTHSTRAIN ) return &skill_unit_layout [earthstrain_unit_pos + dir]; else if( skill_id == RL_FIRE_RAIN ) return &skill_unit_layout[firerain_unit_pos + dir]; else if( skill_id == GN_WALLOFTHORN ) return &skill_unit_layout[wallofthorn_unit_pos + dir]; ShowError("skill_get_unit_layout: unknown unit layout for skill %d (level %d)\n", skill_id, skill_lv); return &skill_unit_layout[0]; // default 1x1 layout } /** Stores temporary values. * Common usages: * [0] holds number of targets in area * [1] holds the id of the original target * [2] counts how many targets have been processed. counter is added in skill_area_sub if the foreach function flag is: flag&(SD_SPLASH|SD_PREAMBLE) */ static int skill_area_temp[8]; /*========================================== * Add effect to skill when hit succesfully target *------------------------------------------*/ int skill_additional_effect(struct block_list* src, struct block_list *bl, uint16 skill_id, uint16 skill_lv, int attack_type, enum damage_lv dmg_lv, t_tick tick) { map_session_data *sd, *dstsd; struct mob_data *md, *dstmd; struct status_data *sstatus, *tstatus; status_change *sc, *tsc; int skill; int rate; nullpo_ret(src); nullpo_ret(bl); if(skill_id > 0 && !skill_lv) return 0; // don't forget auto attacks! - celest if( dmg_lv < ATK_BLOCK ) // Don't apply effect if miss. return 0; sd = BL_CAST(BL_PC, src); md = BL_CAST(BL_MOB, src); dstsd = BL_CAST(BL_PC, bl); dstmd = BL_CAST(BL_MOB, bl); sc = status_get_sc(src); tsc = status_get_sc(bl); sstatus = status_get_status_data(src); tstatus = status_get_status_data(bl); // Taekwon combos activate on traps, so we need to check them even for targets that don't have status if (sd && skill_id == 0 && !(attack_type&BF_SKILL) && sc) { // Chance to trigger Taekwon kicks if (sc->getSCE(SC_READYSTORM) && sc_start4(src, src, SC_COMBO, 15, TK_STORMKICK, 0, 2, 0, (2000 - 4 * sstatus->agi - 2 * sstatus->dex))) ; //Stance triggered else if (sc->getSCE(SC_READYDOWN) && sc_start4(src, src, SC_COMBO, 15, TK_DOWNKICK, 0, 2, 0, (2000 - 4 * sstatus->agi - 2 * sstatus->dex))) ; //Stance triggered else if (sc->getSCE(SC_READYTURN) && sc_start4(src, src, SC_COMBO, 15, TK_TURNKICK, 0, 2, 0, (2000 - 4 * sstatus->agi - 2 * sstatus->dex))) ; //Stance triggered else if (sc->getSCE(SC_READYCOUNTER)) { //additional chance from SG_FRIEND [Komurka] rate = 20; if (sc->getSCE(SC_SKILLRATE_UP) && sc->getSCE(SC_SKILLRATE_UP)->val1 == TK_COUNTER) { rate += rate*sc->getSCE(SC_SKILLRATE_UP)->val2 / 100; status_change_end(src, SC_SKILLRATE_UP); } sc_start4(src, src, SC_COMBO, rate, TK_COUNTER, 0, 2, 0, (2000 - 4 * sstatus->agi - 2 * sstatus->dex)) ; //Stance triggered } } if (!tsc) //skill additional effect is about adding effects to the target... //So if the target can't be inflicted with statuses, this is pointless. return 0; if( sd ) { // These statuses would be applied anyway even if the damage was blocked by some skills. [Inkfish] if( skill_id != WS_CARTTERMINATION && skill_id != AM_DEMONSTRATION && skill_id != CR_REFLECTSHIELD && skill_id != MS_REFLECTSHIELD && skill_id != GN_HELLS_PLANT_ATK #ifndef RENEWAL && skill_id != ASC_BREAKER #endif ) { // Trigger status effects for (const auto &it : sd->addeff) { rate = it.rate; if( attack_type&BF_LONG ) // Any ranged physical attack takes status arrows into account (Grimtooth...) [DracoRPG] rate += it.arrow_rate; if( !rate ) continue; if ((it.flag&(ATF_WEAPON|ATF_MAGIC|ATF_MISC)) != (ATF_WEAPON|ATF_MAGIC|ATF_MISC)) { // Trigger has attack type consideration. if ((it.flag&ATF_WEAPON && attack_type&BF_WEAPON) || (it.flag&ATF_MAGIC && attack_type&BF_MAGIC) || (it.flag&ATF_MISC && attack_type&BF_MISC)) ; else continue; } if ((it.flag&(ATF_LONG|ATF_SHORT)) != (ATF_LONG|ATF_SHORT)) { // Trigger has range consideration. if ((it.flag&ATF_LONG && !(attack_type&BF_LONG)) || (it.flag&ATF_SHORT && !(attack_type&BF_SHORT))) continue; //Range Failed. } if (it.flag&ATF_TARGET) status_change_start(src, bl, it.sc, rate, 7, 0, 0, 0, it.duration, SCSTART_NONE, 100); if (it.flag&ATF_SELF) status_change_start(src, src, it.sc, rate, 7, 0, 0, 0, it.duration, SCSTART_NONE, 100); } } // Enchanting Shadow gives a chance to inflict Shadow Wounds to the enemy. if (sc != nullptr) { status_change_entry *sce = sc->getSCE(SC_SHADOW_WEAPON); unit_data *ud = unit_bl2ud(bl); if (sce != nullptr && ud != nullptr && rnd_chance(sce->val1, 100)) { unit_addshadowscar(*ud, skill_get_time2(SHC_ENCHANTING_SHADOW, sce->val1)); } } if( skill_id ) { // Trigger status effects on skills for (const auto &it : sd->addeff_onskill) { if (skill_id != it.skill_id || !it.rate) continue; if (it.target&ATF_TARGET) status_change_start(src, bl, it.sc, rate, 7, 0, 0, 0, it.duration, SCSTART_NONE, 100); if (it.target&ATF_SELF) status_change_start(src, src, it.sc, rate, 7, 0, 0, 0, it.duration, SCSTART_NONE, 100); } //"While the damage can be blocked by Pneuma, the chance to break armor remains", irowiki. [Cydh] if (dmg_lv == ATK_BLOCK && skill_id == AM_ACIDTERROR) { sc_start2(src,bl,SC_BLEEDING,(skill_lv*3),skill_lv,src->id,skill_get_time2(skill_id,skill_lv)); #ifdef RENEWAL if (skill_break_equip(src,bl, EQP_ARMOR, (1000 * skill_lv + 500) - 1000, BCT_ENEMY)) #else if (skill_break_equip(src,bl, EQP_ARMOR, 100*skill_get_time(skill_id,skill_lv), BCT_ENEMY)) #endif clif_emotion(bl,ET_HUK); } } } if( dmg_lv < ATK_DEF ) // no damage, return; return 0; switch(skill_id) { case 0: { // Normal attacks (no skill used) if( attack_type&BF_SKILL ) break; // If a normal attack is a skill, it's splash damage. [Inkfish] if(sd) { // Automatic trigger of Blitz Beat if (pc_isfalcon(sd) && sd->status.weapon == W_BOW && (skill = pc_checkskill(sd, HT_BLITZBEAT)) > 0 && rnd() % 1000 <= sstatus->luk * 10 / 3 + 1) { if ((sd->class_ & MAPID_THIRDMASK) == MAPID_RANGER) rate = 5; else rate = (sd->status.job_level + 9) / 10; skill_castend_damage_id(src, bl, HT_BLITZBEAT, (skill < rate) ? skill : rate, tick, SD_LEVEL); } // Automatic trigger of Warg Strike if (pc_iswug(sd) && (skill = pc_checkskill(sd, RA_WUGSTRIKE)) > 0) { rate = sstatus->luk * 10 / 3 + 1; if (pc_isfalcon(sd)) rate = rate / 3; if (rnd() % 1000 <= rate) skill_castend_damage_id(src, bl, RA_WUGSTRIKE, skill, tick, 0); } // Automatic trigger of Hawk Rush if (pc_isfalcon(sd) && sd->status.weapon == W_BOW && (skill = pc_checkskill(sd, WH_HAWKRUSH)) > 0) { rate = sstatus->con * 10 / 3 + 1; rate += rate * (20 * pc_checkskill(sd, WH_NATUREFRIENDLY)) / 100; if (rnd() % 1000 <= rate) skill_castend_damage_id(src, bl, WH_HAWKRUSH, skill, tick, 0); } // Gank if(dstmd && sd->status.weapon != W_BOW && (skill=pc_checkskill(sd,RG_SNATCHER)) > 0 && (skill*15 + 55) + pc_checkskill(sd,TF_STEAL)*10 > rnd()%1000) { if(pc_steal_item(sd,bl,pc_checkskill(sd,TF_STEAL))) clif_skill_nodamage(src,bl,TF_STEAL,skill,1); else clif_skill_fail(sd,RG_SNATCHER,USESKILL_FAIL_LEVEL,0); } } if (sc) { struct status_change_entry *sce; // Enchant Poison gives a chance to poison attacked enemies if((sce=sc->getSCE(SC_ENCPOISON))) //Don't use sc_start since chance comes in 1/10000 rate. status_change_start(src,bl,SC_POISON,sce->val2, sce->val1,src->id,0,0, skill_get_time2(AS_ENCHANTPOISON,sce->val1),SCSTART_NONE); // Enchant Deadly Poison gives a chance to deadly poison attacked enemies if((sce=sc->getSCE(SC_EDP))) sc_start4(src,bl,SC_DPOISON,sce->val2, sce->val1,src->id,0,0, skill_get_time2(ASC_EDP,sce->val1)); if ((sce = sc->getSCE(SC_LUXANIMA)) && rnd() % 100 < sce->val2) skill_castend_nodamage_id(src, bl, RK_STORMBLAST, 1, tick, 0); } } break; case SM_BASH: if( sd && skill_lv > 5 && pc_checkskill(sd,SM_FATALBLOW)>0 ){ //BaseChance gets multiplied with BaseLevel/50.0; 500/50 simplifies to 10 [Playtester] status_change_start(src,bl,SC_STUN,(skill_lv-5)*sd->status.base_level*10, skill_lv,0,0,0,skill_get_time2(skill_id,skill_lv),SCSTART_NONE); } break; case MER_CRASH: sc_start(src,bl,SC_STUN,(6*skill_lv),skill_lv,skill_get_time2(skill_id,skill_lv)); break; case AS_VENOMKNIFE: if (sd) //Poison chance must be that of Envenom. [Skotlex] skill_lv = pc_checkskill(sd, TF_POISON); case TF_POISON: case AS_SPLASHER: if(!sc_start2(src,bl,SC_POISON,(4*skill_lv+10),skill_lv,src->id,skill_get_time2(skill_id,skill_lv)) && sd && skill_id==TF_POISON ) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); break; case AS_SONICBLOW: sc_start(src,bl,SC_STUN,(2*skill_lv+10),skill_lv,skill_get_time2(skill_id,skill_lv)); break; case AS_GRIMTOOTH: if (dstmd && !status_has_mode(tstatus,MD_STATUSIMMUNE)) sc_start(src,bl,SC_QUAGMIRE,100,0,skill_get_time2(skill_id,skill_lv)); break; case WZ_FIREPILLAR: unit_set_walkdelay(bl, tick, skill_get_time2(skill_id, skill_lv), 1); break; case MG_FROSTDIVER: if(!sc_start(src,bl,SC_FREEZE,min(skill_lv*3+35,skill_lv+60),skill_lv,skill_get_time2(skill_id,skill_lv)) && sd) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); break; case WZ_FROSTNOVA: sc_start(src,bl,SC_FREEZE,skill_lv*5+33,skill_lv,skill_get_time2(skill_id,skill_lv)); break; case WZ_STORMGUST: // Storm Gust counter was dropped in renewal #ifdef RENEWAL sc_start(src,bl,SC_FREEZE,65-(5*skill_lv),skill_lv,skill_get_time2(skill_id,skill_lv)); #else //On third hit, there is a 150% to freeze the target if(tsc->sg_counter >= 3 && sc_start(src,bl,SC_FREEZE,150,skill_lv,skill_get_time2(skill_id,skill_lv))) tsc->sg_counter = 0; // Being it only resets on success it'd keep stacking and eventually overflowing on mvps, so we reset at a high value else if( tsc->sg_counter > 250 ) tsc->sg_counter = 0; #endif break; case NPC_STORMGUST2: if (skill_lv == 1) sc_start(src,bl,SC_FREEZE,10,skill_lv,skill_get_time2(skill_id,skill_lv)); else if (skill_lv == 2) sc_start(src,bl,SC_FREEZE,7,skill_lv,skill_get_time2(skill_id,skill_lv)); else sc_start(src,bl,SC_FREEZE,3,skill_lv,skill_get_time2(skill_id,skill_lv)); break; case WZ_METEOR: sc_start(src,bl,SC_STUN,3*skill_lv,skill_lv,skill_get_time2(skill_id,skill_lv)); break; case WZ_VERMILION: #ifdef RENEWAL sc_start(src,bl,SC_BLIND,10 + 5 * skill_lv,skill_lv,skill_get_time2(skill_id,skill_lv)); #else sc_start(src,bl,SC_BLIND,min(4*skill_lv,40),skill_lv,skill_get_time2(skill_id,skill_lv)); #endif break; case WZ_HEAVENDRIVE: status_change_end(bl, SC_SV_ROOTTWIST); break; case HT_FREEZINGTRAP: case MA_FREEZINGTRAP: sc_start(src,bl,SC_FREEZE,100,skill_lv,skill_get_time2(skill_id,skill_lv)); break; case HT_FLASHER: sc_start(src,bl,SC_BLIND,100,skill_lv,skill_get_time2(skill_id,skill_lv)); break; case HT_LANDMINE: case MA_LANDMINE: sc_start(src,bl,SC_STUN,10,skill_lv,skill_get_time2(skill_id,skill_lv)); break; case HT_SHOCKWAVE: status_percent_damage(src, bl, 0, -(15*skill_lv+5), false); break; case HT_SANDMAN: case MA_SANDMAN: sc_start(src,bl,SC_SLEEP,(10*skill_lv+40),skill_lv,skill_get_time2(skill_id,skill_lv)); break; case TF_SPRINKLESAND: sc_start(src,bl,SC_BLIND,20,skill_lv,skill_get_time2(skill_id,skill_lv)); break; case TF_THROWSTONE: if (!sc_start(src,bl,SC_STUN,3,skill_lv,skill_get_time(skill_id,skill_lv))) //only blind if success sc_start(src,bl,SC_BLIND,3,skill_lv,skill_get_time2(skill_id,skill_lv)); break; case NPC_DARKCROSS: case CR_HOLYCROSS: sc_start(src,bl,SC_BLIND,3*skill_lv,skill_lv,skill_get_time2(skill_id,skill_lv)); break; case NPC_GRANDDARKNESS: sc_start(src, bl, SC_BLIND, 100, skill_lv, skill_get_time2(skill_id, skill_lv)); attack_type |= BF_WEAPON; break; case CR_GRANDCROSS: //Chance to cause blind status vs demon and undead element, but not against players if(!dstsd && (battle_check_undead(tstatus->race,tstatus->def_ele) || tstatus->race == RC_DEMON)) sc_start(src,bl,SC_BLIND,100,skill_lv,skill_get_time2(skill_id,skill_lv)); attack_type |= BF_WEAPON; break; case AM_ACIDTERROR: sc_start2(src,bl,SC_BLEEDING,(skill_lv*3),skill_lv,src->id,skill_get_time2(skill_id,skill_lv)); #ifdef RENEWAL if (skill_break_equip(src,bl, EQP_ARMOR, (1000 * skill_lv + 500) - 1000, BCT_ENEMY)) #else if (skill_break_equip(src,bl, EQP_ARMOR, 100*skill_get_time(skill_id,skill_lv), BCT_ENEMY)) #endif clif_emotion(bl,ET_HUK); break; case AM_DEMONSTRATION: #ifdef RENEWAL skill_break_equip(src,bl, EQP_WEAPON, 300 * skill_lv, BCT_ENEMY); #else skill_break_equip(src,bl, EQP_WEAPON, 100*skill_lv, BCT_ENEMY); #endif break; case CR_SHIELDCHARGE: sc_start(src,bl,SC_STUN,(15+skill_lv*5),skill_lv,skill_get_time2(skill_id,skill_lv)); break; #ifndef RENEWAL case PA_PRESSURE: status_percent_damage(src, bl, 0, 15+5*skill_lv, false); //Fall through case HW_GRAVITATION: //Pressure and Gravitation can trigger physical autospells attack_type |= BF_NORMAL; attack_type |= BF_WEAPON; break; #endif case RG_RAID: sc_start(src,bl,SC_STUN,(10+3*skill_lv),skill_lv,skill_get_time(skill_id,skill_lv)); sc_start(src,bl,SC_BLIND,(10+3*skill_lv),skill_lv,skill_get_time2(skill_id,skill_lv)); #ifdef RENEWAL sc_start(src, bl, SC_RAID, 100, skill_lv, 10000); // Hardcoded to 10 seconds since Duration1 and Duration2 are used break; case RG_BACKSTAP: sc_start(src,bl,SC_STUN,(5+2*skill_lv),skill_lv,skill_get_time(skill_id,skill_lv)); #endif break; case BA_FROSTJOKER: sc_start(src,bl,SC_FREEZE,(15+5*skill_lv),skill_lv,skill_get_time2(skill_id,skill_lv)); break; case DC_SCREAM: sc_start(src,bl,SC_STUN,(25+5*skill_lv),skill_lv,skill_get_time2(skill_id,skill_lv)); break; case BD_LULLABY: status_change_start(src, bl, SC_SLEEP, (sstatus->int_ * 2 + rnd_value(100, 300)) * 10, skill_lv, 0, 0, 0, skill_get_time2(skill_id, skill_lv), SCSTART_NONE); break; case DC_UGLYDANCE: rate = 5+5*skill_lv; if(sd && (skill=pc_checkskill(sd,DC_DANCINGLESSON))) rate += 5+skill; #ifdef RENEWAL status_zap(bl, 0, 2 * skill_lv + 10); // !TODO: How does caster's DEX/AGI play a role? #else status_zap(bl, 0, rate); #endif break; case SL_STUN: if (tstatus->size==SZ_MEDIUM) //Only stuns mid-sized mobs. sc_start(src,bl,SC_STUN,(30+10*skill_lv),skill_lv,skill_get_time(skill_id,skill_lv)); break; case NPC_PETRIFYATTACK: sc_start2(src,bl,SC_STONEWAIT,(20*skill_lv),skill_lv,src->id,skill_get_time2(skill_id,skill_lv),skill_get_time(skill_id, skill_lv)); break; case NPC_CURSEATTACK: sc_start(src,bl,SC_CURSE,(20*skill_lv),skill_lv,skill_get_time2(skill_id,skill_lv)); break; case NPC_SLEEPATTACK: sc_start(src,bl,SC_SLEEP,(20*skill_lv),skill_lv,skill_get_time2(skill_id,skill_lv)); break; case NPC_BLINDATTACK: sc_start(src,bl,SC_BLIND,(20*skill_lv),skill_lv,skill_get_time2(skill_id,skill_lv)); break; case NPC_POISON: sc_start(src,bl,SC_POISON,(20*skill_lv),skill_lv,skill_get_time2(skill_id,skill_lv)); break; case NPC_SILENCEATTACK: sc_start(src,bl,SC_SILENCE,(20*skill_lv),skill_lv,skill_get_time2(skill_id,skill_lv)); break; case NPC_STUNATTACK: sc_start(src,bl,SC_STUN,(20*skill_lv),skill_lv,skill_get_time2(skill_id,skill_lv)); break; case NPC_BLEEDING: sc_start(src,bl,SC_BLEEDING,(20*skill_lv),skill_lv,skill_get_time2(skill_id,skill_lv)); break; case NPC_BLEEDING2: sc_start(src,bl,SC_BLEEDING,(50+10*skill_lv),skill_lv,skill_get_time2(skill_id,skill_lv)); break; case NPC_CHANGEUNDEAD: sc_start(src, bl, SC_CHANGEUNDEAD, (10 * skill_lv), skill_lv, skill_get_time2(skill_id, skill_lv)); break; case NPC_ACIDBREATH: sc_start(src,bl,SC_POISON,70,skill_lv,skill_get_time2(skill_id,skill_lv)); break; case NPC_ICEBREATH: sc_start(src,bl,SC_FREEZE,70,skill_lv,skill_get_time2(skill_id,skill_lv)); break; case NPC_ICEBREATH2: sc_start(src,bl,SC_FREEZE,100,skill_lv,skill_get_time2(skill_id,skill_lv)); break; case NPC_MENTALBREAKER: { //Based on observations by Tharis, Mental Breaker should do SP damage //equal to Matk*skLevel. rate = sstatus->matk_min; if (rate < sstatus->matk_max) rate += rnd()%(sstatus->matk_max - sstatus->matk_min); rate*=skill_lv; status_zap(bl, 0, rate); break; } // Equipment breaking monster skills [Celest] case NPC_ARMORBRAKE: skill_break_equip(src,bl, EQP_ARMOR, 150*skill_lv, BCT_ENEMY); break; case NPC_HELMBRAKE: skill_break_equip(src,bl, EQP_HELM, 150*skill_lv, BCT_ENEMY); break; case NPC_SHIELDBRAKE: skill_break_equip(src,bl, EQP_SHIELD, 150*skill_lv, BCT_ENEMY); break; case CH_TIGERFIST: { uint16 basetime = skill_get_time(skill_id, skill_lv); uint16 mintime = 30 * (status_get_lv(src) + 100); if (status_get_class_(bl) == CLASS_BOSS) basetime /= 5; basetime = min((basetime * status_get_agi(bl)) / -200 + basetime, mintime) / 2; sc_start(src, bl, SC_STOP, (1 + skill_lv) * 10, 0, basetime); } break; case LK_SPIRALPIERCE: case ML_SPIRALPIERCE: if( dstsd || ( dstmd && !status_bl_has_mode(bl,MD_STATUSIMMUNE) ) ) //Does not work on status immune sc_start(src,bl,SC_STOP,100,0,skill_get_time2(skill_id,skill_lv)); break; case ST_REJECTSWORD: sc_start(src,bl,SC_AUTOCOUNTER,(skill_lv*15),skill_lv,skill_get_time(skill_id,skill_lv)); break; case PF_FOGWALL: if (src != bl && !tsc->getSCE(SC_DELUGE)) sc_start(src,bl,SC_BLIND,100,skill_lv,skill_get_time2(skill_id,skill_lv)); break; case LK_HEADCRUSH: //Headcrush has chance of causing Bleeding status, except on demon and undead element if (!(battle_check_undead(tstatus->race, tstatus->def_ele) || tstatus->race == RC_DEMON)) sc_start2(src,bl, SC_BLEEDING,50, skill_lv, src->id, skill_get_time2(skill_id,skill_lv)); break; case ASC_METEORASSAULT: //Any enemies hit by this skill will receive Stun, Darkness, or external bleeding status ailment with a 5%+5*skill_lv% chance. switch(rnd()%3) { case 0: sc_start(src,bl,SC_BLIND,(5+skill_lv*5),skill_lv,skill_get_time2(skill_id,1)); break; case 1: sc_start(src,bl,SC_STUN,(5+skill_lv*5),skill_lv,skill_get_time2(skill_id,2)); break; default: sc_start2(src,bl,SC_BLEEDING,(5+skill_lv*5),skill_lv,src->id,skill_get_time2(skill_id,3)); } break; case HW_NAPALMVULCAN: sc_start(src,bl,SC_CURSE,5*skill_lv,skill_lv,skill_get_time2(skill_id,skill_lv)); break; case WS_CARTTERMINATION: // Cart termination sc_start(src,bl,SC_STUN,5*skill_lv,skill_lv,skill_get_time2(skill_id,skill_lv)); break; case CR_ACIDDEMONSTRATION: case GN_FIRE_EXPANSION_ACID: skill_break_equip(src,bl, EQP_WEAPON|EQP_ARMOR, 100*skill_lv, BCT_ENEMY); break; case TK_DOWNKICK: sc_start(src,bl,SC_STUN,100,skill_lv,skill_get_time2(skill_id,skill_lv)); break; case TK_JUMPKICK: // debuff the following statuses if( dstsd && dstsd->class_ != MAPID_SOUL_LINKER && !tsc->getSCE(SC_PRESERVE) ) { status_change_end(bl, SC_SPIRIT); status_change_end(bl, SC_ADRENALINE2); status_change_end(bl, SC_KAITE); status_change_end(bl, SC_KAAHI); status_change_end(bl, SC_ONEHAND); status_change_end(bl, SC_ASPDPOTION2); // New soul links confirmed to not dispell with this skill // but thats likely a bug since soul links can't stack and // soul cutter skill works on them. So ill add this here for now. [Rytech] status_change_end(bl, SC_SOULGOLEM); status_change_end(bl, SC_SOULSHADOW); status_change_end(bl, SC_SOULFALCON); status_change_end(bl, SC_SOULFAIRY); } break; case TK_TURNKICK: case MO_BALKYOUNG: //Note: attack_type is passed as BF_WEAPON for the actual target, BF_MISC for the splash-affected mobs. if(attack_type&BF_MISC) //70% base stun chance... sc_start(src,bl,SC_STUN,70,skill_lv,skill_get_time2(skill_id,skill_lv)); break; case GS_BULLSEYE: //0.1% coma rate. if(tstatus->race == RC_BRUTE || tstatus->race == RC_DEMIHUMAN || tstatus->race == RC_PLAYER_HUMAN || tstatus->race == RC_PLAYER_DORAM) status_change_start(src,bl,SC_COMA,10,skill_lv,0,src->id,0,0,SCSTART_NONE); break; case GS_PIERCINGSHOT: sc_start2(src,bl,SC_BLEEDING,(skill_lv*3),skill_lv,src->id,skill_get_time2(skill_id,skill_lv)); break; case NJ_HYOUSYOURAKU: sc_start(src,bl,SC_FREEZE,(10+10*skill_lv),skill_lv,skill_get_time2(skill_id,skill_lv)); break; case GS_FLING: sc_start(src,bl,SC_FLING,100, sd?sd->spiritball_old:5,skill_get_time(skill_id,skill_lv)); break; case GS_DISARM: skill_strip_equip(src, bl, skill_id, skill_lv); clif_skill_nodamage(src,bl,skill_id,skill_lv,1); break; case NPC_EVILLAND: sc_start(src,bl,SC_BLIND,5*skill_lv,skill_lv,skill_get_time2(skill_id,skill_lv)); break; case NPC_HELLJUDGEMENT: sc_start(src,bl,SC_CURSE,100,skill_lv,skill_get_time2(skill_id,skill_lv)); break; case NPC_HELLJUDGEMENT2: switch(rnd()%6) { case 0: sc_start(src,bl,SC_SLEEP,100,skill_lv,skill_get_time2(skill_id,skill_lv)); break; case 1: sc_start(src,bl,SC_CONFUSION,100,skill_lv,skill_get_time2(skill_id,skill_lv)); break; case 2: sc_start(src,bl,SC_HALLUCINATION,100,skill_lv,skill_get_time2(skill_id,skill_lv)); break; case 3: sc_start(src,bl,SC_STUN,100,skill_lv,skill_get_time2(skill_id,skill_lv)); break; case 4: sc_start(src,bl,SC_FEAR,100,skill_lv,skill_get_time2(skill_id,skill_lv)); break; default: sc_start(src,bl,SC_CURSE,100,skill_lv,skill_get_time2(skill_id,skill_lv)); break; } break; case NPC_CRITICALWOUND: sc_start(src,bl,SC_CRITICALWOUND,100,skill_lv,skill_get_time2(skill_id,skill_lv)); break; case NPC_FIRESTORM: sc_start(src,bl,SC_BURNT,100,skill_lv,skill_get_time(skill_id,skill_lv)); break; case RK_DRAGONBREATH: sc_start4(src,bl,SC_BURNING,15,skill_lv,1000,src->id,0,skill_get_time(skill_id,skill_lv)); break; case RK_DRAGONBREATH_WATER: sc_start(src,bl,SC_FREEZING,15,skill_lv,skill_get_time(skill_id,skill_lv)); break; case NPC_DRAGONBREATH: if (skill_lv > 5) sc_start4(src,bl,SC_FREEZING,50,skill_lv,1000,src->id,0,skill_get_time(skill_id,skill_lv)); else sc_start4(src,bl,SC_BURNING,50,skill_lv,1000,src->id,0,skill_get_time(skill_id,skill_lv)); break; case AB_ADORAMUS: sc_start(src,bl, SC_ADORAMUS, skill_lv * 4 + (sd ? sd->status.job_level : 50) / 2, skill_lv, skill_get_time2(skill_id, skill_lv)); break; case WL_COMET: sc_start(src, bl, SC_MAGIC_POISON, 100, skill_lv, 20000); break; case NPC_COMET: sc_start4(src,bl,SC_BURNING,100,skill_lv,1000,src->id,0,skill_get_time2(skill_id,skill_lv)); break; case NPC_JACKFROST: sc_start(src,bl,SC_FREEZE,200,skill_lv,skill_get_time(skill_id,skill_lv)); break; case RA_WUGBITE: { int wug_rate = (50 + 10 * skill_lv) + 2 * ((sd) ? pc_checkskill(sd,RA_TOOTHOFWUG)*2 : skill_get_max(RA_TOOTHOFWUG)) - (status_get_agi(bl) / 4); if (wug_rate < 50) wug_rate = 50; sc_start(src,bl, SC_BITE, wug_rate, skill_lv, (skill_get_time(skill_id,skill_lv) + ((sd) ? pc_checkskill(sd,RA_TOOTHOFWUG)*500 : skill_get_max(RA_TOOTHOFWUG))) ); } break; case RA_SENSITIVEKEEN: if( rnd()%100 < 8 * skill_lv ) skill_castend_damage_id(src, bl, RA_WUGBITE, ((sd) ? pc_checkskill(sd, RA_WUGBITE) : skill_get_max(RA_WUGBITE)), tick, SD_ANIMATION); break; case RA_FIRINGTRAP: sc_start4(src, bl, SC_BURNING, 50 + skill_lv * 10, skill_lv, 1000, src->id, 0, skill_get_time2(skill_id, skill_lv)); break; case RA_ICEBOUNDTRAP: sc_start(src, bl, SC_FREEZING, 50 + skill_lv * 10, skill_lv, skill_get_time2(skill_id, skill_lv)); break; case NC_PILEBUNKER: if( rnd()%100 < 25 + 15*skill_lv ) { status_change_end(bl, SC_KYRIE); status_change_end(bl, SC_ASSUMPTIO); status_change_end(bl, SC_STEELBODY); status_change_end(bl, SC_GT_CHANGE); status_change_end(bl, SC_GT_REVITALIZE); status_change_end(bl, SC_AUTOGUARD); status_change_end(bl, SC_REFLECTDAMAGE); status_change_end(bl, SC_DEFENDER); status_change_end(bl, SC_PRESTIGE); status_change_end(bl, SC_BANDING); status_change_end(bl, SC_MILLENNIUMSHIELD); } break; case NC_FLAMELAUNCHER: sc_start(src, bl, SC_BURNING, 20 + 10 * skill_lv, skill_lv, skill_get_time(skill_id, skill_lv)); break; case NC_COLDSLOWER: // Status chances are applied officially through a check // The skill first trys to give the frozen status to targets that are hit sc_start(src, bl, SC_FREEZE, 10 * skill_lv, skill_lv, skill_get_time(skill_id, skill_lv)); if (!tsc->getSCE(SC_FREEZE)) // If it fails to give the frozen status, it will attempt to give the freezing status sc_start(src, bl, SC_FREEZING, 20 + skill_lv * 10, skill_lv, skill_get_time2(skill_id, skill_lv)); break; case NC_POWERSWING: sc_start(src,bl, SC_STUN, 10, skill_lv, skill_get_time(skill_id, skill_lv)); break; case GC_WEAPONCRUSH: skill_castend_nodamage_id(src,bl,skill_id,skill_lv,tick,BCT_ENEMY); break; case LG_PINPOINTATTACK: rate = 30 + 5 * ((sd) ? pc_checkskill(sd,LG_PINPOINTATTACK) : skill_lv) + (status_get_agi(src) + status_get_lv(src)) / 10; switch( skill_lv ) { case 1: sc_start2(src,bl,SC_BLEEDING,rate,skill_lv,src->id,skill_get_time(skill_id,skill_lv)); break; case 2: skill_break_equip(src, bl, EQP_HELM, rate * 100, BCT_ENEMY); break; case 3: skill_break_equip(src, bl, EQP_SHIELD, rate * 100, BCT_ENEMY); break; case 4: skill_break_equip(src, bl, EQP_ARMOR, rate * 100, BCT_ENEMY); break; case 5: skill_break_equip(src, bl, EQP_WEAPON, rate * 100, BCT_ENEMY); break; } break; case LG_MOONSLASHER: sc_start(src,src,SC_OVERBRANDREADY,100,skill_lv,skill_get_time2(skill_id,skill_lv)); break; case LG_RAYOFGENESIS: // 50% chance to cause Blind on Undead and Demon monsters. if ( battle_check_undead(tstatus->race, tstatus->def_ele) || tstatus->race == RC_DEMON ) sc_start(src,bl, SC_BLIND, 50, skill_lv, skill_get_time(skill_id,skill_lv)); break; case NPC_RAYOFGENESIS: if (skill_lv < sc_start(src,bl, SC_BLIND, 50, skill_lv, skill_get_time(skill_id,skill_lv)); else sc_start(src,bl, SC_BLIND, 100, skill_lv, skill_get_time(skill_id,skill_lv)); break; case LG_HESPERUSLIT: if( pc_checkskill(sd,LG_PINPOINTATTACK) > 0 && sc && sc->getSCE(SC_BANDING) && sc->getSCE(SC_BANDING)->val2 > 5 ) skill_castend_damage_id(src,bl,LG_PINPOINTATTACK,rnd_value(1, pc_checkskill(sd,LG_PINPOINTATTACK)),tick,0); break; case SR_DRAGONCOMBO: sc_start(src,bl, SC_STUN, 1 + skill_lv, skill_lv, skill_get_time(skill_id, skill_lv)); break; case SR_WINDMILL: if( dstsd ) skill_addtimerskill(src,tick+status_get_amotion(src),bl->id,0,0,skill_id,skill_lv,BF_WEAPON,0); else if( dstmd ) sc_start(src,bl, SC_STUN, 100, skill_lv, 1000 + 1000 * (rnd() %3)); break; case SR_GENTLETOUCH_QUIET: // [(Skill Level x 5) + (Caster?s DEX + Caster?s Base Level) / 10] sc_start(src,bl, SC_SILENCE, 5 * skill_lv + (status_get_dex(src) + status_get_lv(src)) / 10, skill_lv, skill_get_time(skill_id, skill_lv)); break; case SR_EARTHSHAKER: sc_start(src,bl,SC_STUN, 25 + 5 * skill_lv,skill_lv,skill_get_time(skill_id,skill_lv)); sc_start(src, bl, SC_EARTHSHAKER, 100, skill_lv, skill_get_time2(skill_id, skill_lv)); status_change_end(bl, SC_SV_ROOTTWIST); break; case SO_EARTHGRAVE: sc_start2(src,bl, SC_BLEEDING, 5 * skill_lv, skill_lv, src->id, skill_get_time2(skill_id, skill_lv)); // Need official rate. [LimitLine] break; case SO_DIAMONDDUST: rate = 5 + 5 * skill_lv; if( sc && sc->getSCE(SC_COOLER_OPTION) ) rate += (sd ? sd->status.job_level / 5 : 0); sc_start(src,bl, SC_CRYSTALIZE, rate, skill_lv, skill_get_time2(skill_id, skill_lv)); break; case SO_VARETYR_SPEAR: sc_start(src,bl, SC_STUN, 5 * skill_lv, skill_lv, skill_get_time(skill_id, skill_lv)); break; case SO_POISON_BUSTER: sc_start(src,bl, SC_POISON, 5 * skill_lv, skill_lv, skill_get_time(skill_id, skill_lv)); break; case GN_SPORE_EXPLOSION: sc_start(src, bl, SC_SPORE_EXPLOSION, 100, skill_lv, skill_get_time(skill_id, skill_lv)); break; case GN_SLINGITEM_RANGEMELEEATK: if( sd ) { switch( sd->itemid ) { // Starting SCs here instead of do it in skill_additional_effect to simplify the code. case ITEMID_COCONUT_BOMB: sc_start(src,bl, SC_STUN, 5 + sd->status.job_level / 2, skill_lv, 1000 * sd->status.job_level / 3); sc_start2(src,bl, SC_BLEEDING, 3 + sd->status.job_level / 2, skill_lv, src->id, 1000 * status_get_lv(src) / 4 + sd->status.job_level / 3); break; case ITEMID_MELON_BOMB: sc_start4(src, bl, SC_MELON_BOMB, 100, skill_lv, 20 + sd->status.job_level, 10 + sd->status.job_level / 2, 0, 1000 * status_get_lv(src) / 4); break; case ITEMID_BANANA_BOMB: { uint16 duration = (battle_config.banana_bomb_duration ? battle_config.banana_bomb_duration : 1000 * sd->status.job_level / 4); sc_start(src,bl, SC_BANANA_BOMB_SITDOWN, status_get_lv(src) + sd->status.job_level + sstatus->dex / 6 - status_get_lv(bl) - tstatus->agi / 4 - tstatus->luk / 5, skill_lv, duration); sc_start(src,bl, SC_BANANA_BOMB, 100, skill_lv, 30000); break; } } sd->itemid = 0; } break; case GN_HELLS_PLANT_ATK: sc_start(src,bl, SC_STUN, 20 + 10 * skill_lv, skill_lv, skill_get_time(skill_id, skill_lv)); sc_start2(src,bl, SC_BLEEDING, 5 + 5 * skill_lv, skill_lv, src->id,skill_get_time(skill_id, skill_lv)); break; case EL_WIND_SLASH: // Non confirmed rate. sc_start2(src,bl, SC_BLEEDING, 25, skill_lv, src->id, skill_get_time(skill_id,skill_lv)); break; case EL_STONE_HAMMER: rate = 10 * skill_lv; sc_start(src,bl, SC_STUN, rate, skill_lv, skill_get_time(skill_id,skill_lv)); break; case EL_ROCK_CRUSHER: sc_start(src,bl, SC_ROCK_CRUSHER,50,skill_lv,skill_get_time(EL_ROCK_CRUSHER,skill_lv)); break; case EL_ROCK_CRUSHER_ATK: sc_start(src,bl,SC_ROCK_CRUSHER_ATK,50,skill_lv,skill_get_time(EL_ROCK_CRUSHER,skill_lv)); break; case EL_TYPOON_MIS: sc_start(src,bl,SC_SILENCE,10*skill_lv,skill_lv,skill_get_time(skill_id,skill_lv)); break; case KO_JYUMONJIKIRI: sc_start(src,bl,SC_JYUMONJIKIRI,100,skill_lv,skill_get_time(skill_id,skill_lv)); break; case SP_SOULEXPLOSION: case KO_SETSUDAN: // Remove soul link when hit. status_change_end(bl, SC_SPIRIT); status_change_end(bl, SC_SOULGOLEM); status_change_end(bl, SC_SOULSHADOW); status_change_end(bl, SC_SOULFALCON); status_change_end(bl, SC_SOULFAIRY); break; case KO_MAKIBISHI: sc_start(src,bl, SC_STUN, 10 * skill_lv, skill_lv, skill_get_time2(skill_id,skill_lv)); break; case MH_EQC: { struct homun_data *hd = BL_CAST(BL_HOM, src); if (hd) { sc_start2(src, bl, SC_STUN, 100, skill_lv, bl->id, 1000 * hd->homunculus.level / 50 + 500 * skill_lv); status_change_end(bl, SC_TINDER_BREAKER2); } } break; case MH_STAHL_HORN: sc_start(src,bl, SC_STUN, 20 + 2 * skill_lv, skill_lv, skill_get_time(skill_id, skill_lv)); break; case MH_NEEDLE_OF_PARALYZE: sc_start(src,bl, SC_PARALYSIS, 30 + 5 * skill_lv, skill_lv, skill_get_time(skill_id, skill_lv)); break; case MH_XENO_SLASHER: sc_start4(src, bl, SC_BLEEDING, skill_lv, skill_lv, src->id, 0, 0, skill_get_time2(skill_id, skill_lv)); break; case NPC_MAGMA_ERUPTION: case NC_MAGMA_ERUPTION: // Stun effect from 'slam' sc_start(src, bl, SC_STUN, 90, skill_lv, skill_get_time2(skill_id, skill_lv)); break; case NC_MAGMA_ERUPTION_DOTDAMAGE: // Burning effect from 'eruption' sc_start4(src, bl, SC_BURNING, 10 * skill_lv, skill_lv, 1000, src->id, 0, skill_get_time2(skill_id, skill_lv)); break; case GN_ILLUSIONDOPING: if( sc_start(src,bl,SC_ILLUSIONDOPING,100 - skill_lv * 10,skill_lv,skill_get_time(skill_id,skill_lv)) ) sc_start(src,bl,SC_HALLUCINATION,100,skill_lv,skill_get_time(skill_id,skill_lv)); break; case RL_MASS_SPIRAL: sc_start2(src,bl,SC_BLEEDING,30 + 10 * skill_lv,skill_lv,src->id,skill_get_time(skill_id,skill_lv)); break; case RL_SLUGSHOT: sc_start(src,bl,SC_STUN,100,skill_lv,skill_get_time2(skill_id,skill_lv)); break; case RL_BANISHING_BUSTER: { if (!tsc || !tsc->count) break; if (status_isimmune(bl)) break; if ((dstsd && (dstsd->class_&MAPID_UPPERMASK) == MAPID_SOUL_LINKER) || rnd()%100 >= 50 + 5 * skill_lv) { if (sd) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); break; } uint16 n = skill_lv; for (const auto &it : status_db) { sc_type status = static_cast<sc_type>(it.first); if (n <= 0) break; if (!tsc->getSCE(status)) continue; if (it.second->flag[SCF_NOBANISHINGBUSTER]) continue; switch (status) { case SC_WHISTLE: case SC_ASSNCROS: case SC_POEMBRAGI: case SC_APPLEIDUN: case SC_HUMMING: case SC_DONTFORGETME: case SC_FORTUNE: case SC_SERVICE4U: if (!battle_config.dispel_song || tsc->getSCE(status)->val4 == 0) continue; //If in song area don't end it, even if config enabled break; case SC_ASSUMPTIO: if( bl->type == BL_MOB ) continue; break; } if( status == SC_BERSERK || status == SC_SATURDAYNIGHTFEVER ) tsc->getSCE(status)->val2 = 0; status_change_end(bl,status); n--; } //Remove bonus_script by Banishing Buster if (dstsd) pc_bonus_script_clear(dstsd,BSF_REM_ON_BANISHING_BUSTER); } break; case RL_S_STORM: //kRO update 2014-02-12. Break a headgear by minimum chance 5%/10%/15%/20%/25% skill_break_equip(src, bl, EQP_HEAD_TOP, max(skill_lv * 500, (sstatus->dex * skill_lv * 10) - (tstatus->agi * 20)), BCT_ENEMY); //! TODO: Figure out break chance formula break; case RL_AM_BLAST: sc_start(src,bl,SC_ANTI_M_BLAST,20 + 10 * skill_lv,skill_lv,skill_get_time2(skill_id,skill_lv)); break; case SU_SCRATCH: sc_start2(src, bl, SC_BLEEDING, skill_lv * 10 + 70, skill_lv, src->id, skill_get_time(skill_id, skill_lv)); break; case SU_SV_STEMSPEAR: sc_start2(src, bl, SC_BLEEDING, 10, skill_lv, src->id, skill_get_time2(skill_id, skill_lv)); break; case SU_CN_METEOR2: sc_start(src, bl, SC_CURSE, 20, skill_lv, skill_get_time2(skill_id, skill_lv)); break; case SU_SCAROFTAROU: sc_start(src, bl, SC_STUN, 10, skill_lv, skill_get_time2(skill_id, skill_lv)); //! TODO: What's the chance/time? break; case SU_LUNATICCARROTBEAT2: sc_start(src, bl, SC_STUN, 20, skill_lv, skill_get_time2(skill_id, skill_lv)); break; case SJ_FULLMOONKICK: sc_start(src, bl, SC_BLIND, 15 + 5 * skill_lv, skill_lv, skill_get_time(skill_id, skill_lv)); break; case SJ_STAREMPEROR: sc_start(src, bl, SC_SILENCE, 50 + 10 * skill_lv, skill_lv, skill_get_time(skill_id, skill_lv)); break; case SP_CURSEEXPLOSION: status_change_end(bl, SC_SOULCURSE); break; case SP_SHA: sc_start(src, bl, SC_SP_SHA, 100, skill_lv, skill_get_time(skill_id, skill_lv)); break; case DK_SERVANT_W_PHANTOM: sc_start(src, bl, SC_HANDICAPSTATE_DEEPBLIND, 30 + 10 * skill_lv, skill_lv, skill_get_time(skill_id, skill_lv)); break; case AG_DESTRUCTIVE_HURRICANE:// Targets hit are dealt a additional hit through Climax. if (sc && sc->getSCE(SC_CLIMAX) && sc->getSCE(SC_CLIMAX)->val1 == 1) skill_castend_damage_id(src, bl, AG_DESTRUCTIVE_HURRICANE_CLIMAX, skill_lv, tick, SD_LEVEL|SD_ANIMATION); break; case AG_CRYSTAL_IMPACT:// Targets hit are dealt aftershock damage. skill_castend_damage_id(src, bl, AG_CRYSTAL_IMPACT_ATK, skill_lv, tick, SD_LEVEL); break; case IQ_OLEUM_SANCTUM: sc_start(src, bl, SC_HOLY_OIL, 100, skill_lv, skill_get_time(skill_id, skill_lv)); break; case IQ_FIRST_BRAND: sc_start(src, bl, SC_FIRST_BRAND, 100, skill_lv, skill_get_time(skill_id, skill_lv)); break; case IQ_SECOND_FLAME: case IQ_SECOND_FAITH: case IQ_SECOND_JUDGEMENT: sc_start(src, bl, SC_SECOND_BRAND, 100, skill_lv, skill_get_time(skill_id, skill_lv)); break; case IQ_THIRD_PUNISH: case IQ_THIRD_FLAME_BOMB: case IQ_THIRD_CONSECRATION: status_change_end(bl, SC_SECOND_BRAND); break; case CD_ARBITRIUM:// Target is Deep Silenced by chance and is then dealt a 2nd splash hit. sc_start(src, bl, SC_HANDICAPSTATE_DEEPSILENCE, 20 + 5 * skill_lv, skill_lv, skill_get_time(skill_id, skill_lv)); skill_castend_damage_id(src, bl, CD_ARBITRIUM_ATK, skill_lv, tick, SD_LEVEL); break; case SHC_FATAL_SHADOW_CROW: sc_start( src, bl, SC_DARKCROW, 100, max( 1, pc_checkskill( sd, GC_DARKCROW ) ), skill_get_time( skill_id, skill_lv ) ); break; case ABC_UNLUCKY_RUSH: sc_start(src, bl, SC_HANDICAPSTATE_MISFORTUNE, 30 + 10 * skill_lv, skill_lv, skill_get_time(skill_id, skill_lv)); break; case ABC_CHAIN_REACTION_SHOT: skill_castend_damage_id(src, bl, ABC_CHAIN_REACTION_SHOT_ATK, skill_lv, tick, SD_LEVEL); break; case WH_DEEPBLINDTRAP:// Need official success chances for all 4 Windhawk traps. sc_start(src, bl, SC_HANDICAPSTATE_DEEPBLIND, 50, skill_lv, skill_get_time2(skill_id, skill_lv)); break; case WH_SOLIDTRAP: sc_start(src, bl, SC_HANDICAPSTATE_CRYSTALLIZATION, 50, skill_lv, skill_get_time2(skill_id, skill_lv)); break; case WH_SWIFTTRAP: sc_start(src, bl, SC_HANDICAPSTATE_LIGHTNINGSTRIKE, 50, skill_lv, skill_get_time2(skill_id, skill_lv)); break; case WH_FLAMETRAP: sc_start(src, bl, SC_HANDICAPSTATE_CONFLAGRATION, 50, skill_lv, skill_get_time2(skill_id, skill_lv)); break; case TR_ROSEBLOSSOM:// Rose blossom seed can only bloom if the target is hit. sc_start4(src, bl, SC_ROSEBLOSSOM, 100, skill_lv, TR_ROSEBLOSSOM_ATK, src->id, 0, skill_get_time(skill_id, skill_lv)); case WM_METALICSOUND: case WM_REVERBERATION: case TR_RHYTHMSHOOTING: case TR_METALIC_FURY: status_change_end(bl, SC_SOUNDBLEND); break; case EM_DIAMOND_STORM: sc_start(src, bl, SC_HANDICAPSTATE_FROSTBITE, 40 + 10 * skill_lv, skill_lv, skill_get_time2(skill_id, skill_lv)); break; case EM_LIGHTNING_LAND: sc_start(src, bl, SC_HANDICAPSTATE_LIGHTNINGSTRIKE, 10 + 10 * skill_lv, skill_lv, skill_get_time2(skill_id, skill_lv)); break; case EM_VENOM_SWAMP: sc_start(src, bl, SC_HANDICAPSTATE_DEADLYPOISON, 10 + 10 * skill_lv, skill_lv, skill_get_time2(skill_id, skill_lv)); break; case EM_CONFLAGRATION: sc_start(src, bl, SC_HANDICAPSTATE_CONFLAGRATION, 10 + 10 * skill_lv, skill_lv, skill_get_time2(skill_id, skill_lv)); break; case EM_TERRA_DRIVE: sc_start(src, bl, SC_HANDICAPSTATE_CRYSTALLIZATION, 40 + 10 * skill_lv, skill_lv, skill_get_time2(skill_id, skill_lv)); break; } //end switch skill_id if (md && battle_config.summons_trigger_autospells && md->master_id && md->special_state.ai && md->special_state.ai != AI_ABR && md->special_state.ai != AI_BIONIC) { //Pass heritage to Master for status causing effects. [Skotlex] sd = map_id2sd(md->master_id); src = sd?&sd->bl:src; } // Coma if (sd && sd->special_state.bonus_coma && (!md || util::vector_exists(status_get_race2(&md->bl), RC2_GVG) || status_get_class(&md->bl) != CLASS_BATTLEFIELD)) { rate = 0; //! TODO: Filter the skills that shouldn't inflict coma bonus, to avoid some non-damage skills inflict coma. [Cydh] if (!skill_id || !skill_get_nk(skill_id, NK_NODAMAGE)) { rate += sd->indexed_bonus.coma_class[tstatus->class_] + sd->indexed_bonus.coma_class[CLASS_ALL]; rate += sd->indexed_bonus.coma_race[tstatus->race] + sd->indexed_bonus.coma_race[RC_ALL]; } if (attack_type&BF_WEAPON) { rate += sd->indexed_bonus.weapon_coma_ele[tstatus->def_ele] + sd->indexed_bonus.weapon_coma_ele[ELE_ALL]; rate += sd->indexed_bonus.weapon_coma_race[tstatus->race] + sd->indexed_bonus.weapon_coma_race[RC_ALL]; rate += sd->indexed_bonus.weapon_coma_class[tstatus->class_] + sd->indexed_bonus.weapon_coma_class[CLASS_ALL]; } if (rate > 0) status_change_start(src,bl, SC_COMA, rate, 0, 0, src->id, 0, 0, SCSTART_NONE); } if( attack_type&BF_WEAPON ) { // Breaking Equipment if( sd && battle_config.equip_self_break_rate ) { // Self weapon breaking rate = battle_config.equip_natural_break_rate; #ifndef RENEWAL if( sc ) { if(sc->getSCE(SC_OVERTHRUST)) rate += 10; if(sc->getSCE(SC_MAXOVERTHRUST)) rate += 10; } #endif if( rate ) skill_break_equip(src,src, EQP_WEAPON, rate, BCT_SELF); } if( battle_config.equip_skill_break_rate && skill_id != WS_CARTTERMINATION && skill_id != ITM_TOMAHAWK ) { // Cart Termination/Tomahawk won't trigger breaking data. Why? No idea, go ask Gravity. // Target weapon breaking rate = 0; if( sd ) rate += sd->bonus.break_weapon_rate; if (sc) { if (sc->getSCE(SC_MELTDOWN)) rate += sc->getSCE(SC_MELTDOWN)->val2; if (sc->getSCE(SC_WEAPONBREAKER)) rate += sc->getSCE(SC_WEAPONBREAKER)->val2; } if( rate ) skill_break_equip(src,bl, EQP_WEAPON, rate, BCT_ENEMY); // Target armor breaking rate = 0; if( sd ) rate += sd->bonus.break_armor_rate; if( sc && sc->getSCE(SC_MELTDOWN) ) rate += sc->getSCE(SC_MELTDOWN)->val3; if( rate ) skill_break_equip(src,bl, EQP_ARMOR, rate, BCT_ENEMY); } if (sd && !skill_id && bl->type == BL_PC) { // This effect does not work with skills. if (sd->def_set_race[tstatus->race].rate) status_change_start(src,bl, SC_DEFSET, sd->def_set_race[tstatus->race].rate, sd->def_set_race[tstatus->race].value, 0, 0, 0, sd->def_set_race[tstatus->race].tick, SCSTART_NOTICKDEF); if (sd->mdef_set_race[tstatus->race].rate) status_change_start(src,bl, SC_MDEFSET, sd->mdef_set_race[tstatus->race].rate, sd->mdef_set_race[tstatus->race].value, 0, 0, 0, sd->mdef_set_race[tstatus->race].tick, SCSTART_NOTICKDEF); if (sd->norecover_state_race[tstatus->race].rate) status_change_start(src, bl, SC_NORECOVER_STATE, sd->norecover_state_race[tstatus->race].rate, 0, 0, 0, 0, sd->norecover_state_race[tstatus->race].tick, SCSTART_NONE); } } if( sd && sd->ed && sc && !status_isdead(bl) && !skill_id ) { struct unit_data *ud = unit_bl2ud(src); if( sc->getSCE(SC_WILD_STORM_OPTION) ) skill = sc->getSCE(SC_WILD_STORM_OPTION)->val2; else if( sc->getSCE(SC_UPHEAVAL_OPTION) ) skill = sc->getSCE(SC_UPHEAVAL_OPTION)->val3; else if( sc->getSCE(SC_TROPIC_OPTION) ) skill = sc->getSCE(SC_TROPIC_OPTION)->val3; else if( sc->getSCE(SC_CHILLY_AIR_OPTION) ) skill = sc->getSCE(SC_CHILLY_AIR_OPTION)->val3; else skill = 0; if ( rnd()%100 < 25 && skill ){ skill_castend_damage_id(src, bl, skill, 5, tick, 0); if (ud) { rate = skill_delayfix(src, skill, skill_lv); if (DIFF_TICK(ud->canact_tick, tick + rate) < 0){ ud->canact_tick = i64max(tick + rate, ud->canact_tick); if ( battle_config.display_status_timers ) clif_status_change(src, EFST_POSTDELAY, 1, rate, 0, 0, 0); } } } } // Autospell when attacking if( sd && !status_isdead(bl) && !sd->autospell.empty() ) { for (const auto &it : sd->autospell) { if (!(((it.battle_flag)&attack_type)&BF_WEAPONMASK && ((it.battle_flag)&attack_type)&BF_RANGEMASK && ((it.battle_flag)&attack_type)&BF_SKILLMASK)) continue; // one or more trigger conditions were not fulfilled skill = it.id; sd->state.autocast = 1; if ( skill_isNotOk(skill, sd) ) { sd->state.autocast = 0; continue; } sd->state.autocast = 0; uint16 autospl_skill_lv = it.lv ? it.lv : 1; if (it.flag & AUTOSPELL_FORCE_RANDOM_LEVEL) autospl_skill_lv = rnd_value( 1, autospl_skill_lv ); rate = (!sd->state.arrow_atk) ? it.rate : it.rate / 2; if (rnd()%1000 >= rate) continue; block_list *tbl = (it.flag & AUTOSPELL_FORCE_TARGET) ? bl : src; e_cast_type type = skill_get_casttype(skill); if (type == CAST_GROUND) { if (!skill_pos_maxcount_check(src, tbl->x, tbl->y, skill, autospl_skill_lv, BL_PC, false)) continue; } if (battle_config.autospell_check_range && !battle_check_range(bl, tbl, skill_get_range2(src, skill, autospl_skill_lv, true))) continue; if (skill == PF_SPIDERWEB) //Special case, due to its nature of coding. type = CAST_GROUND; #ifndef RENEWAL // else if (skill == AS_SONICBLOW) // pc_stop_attack(sd); //Special case, Sonic Blow autospell should stop the player attacking. #endif sd->state.autocast = 1; skill_consume_requirement(sd,skill,autospl_skill_lv,1); #ifndef RENEWAL skill_toggle_magicpower(src, skill); #endif switch (type) { case CAST_GROUND: skill_castend_pos2(src, tbl->x, tbl->y, skill, autospl_skill_lv, tick, 0); break; case CAST_NODAMAGE: skill_castend_nodamage_id(src, tbl, skill, autospl_skill_lv, tick, 0); break; case CAST_DAMAGE: skill_castend_damage_id(src, tbl, skill, autospl_skill_lv, tick, 0); break; } sd->state.autocast = 0; //Set canact delay. [Skotlex] unit_data *ud = unit_bl2ud(src); if (ud) { int delay = skill_delayfix(src, skill, autospl_skill_lv); if (DIFF_TICK(ud->canact_tick, tick + delay) < 0){ ud->canact_tick = i64max(tick + delay, ud->canact_tick); if ( battle_config.display_status_timers && sd ) clif_status_change(src, EFST_POSTDELAY, 1, delay, 0, 0, 0); } } } } // Check for player and pet autobonuses when attacking if (sd != nullptr) { // Player if (!sd->autobonus.empty()) { for (auto &it : sd->autobonus) { if (it == nullptr) continue; if (rnd_value(0, 1000) >= it->rate) continue; if (!(((it->atk_type) & attack_type) & BF_WEAPONMASK && ((it->atk_type) & attack_type) & BF_RANGEMASK && ((it->atk_type) & attack_type) & BF_SKILLMASK)) continue; // one or more trigger conditions were not fulfilled pc_exeautobonus(*sd, &sd->autobonus, it); } } // Pet if (sd->pd != nullptr && !sd->pd->autobonus.empty()) { for (auto &it : sd->pd->autobonus) { if (it == nullptr) continue; if (rnd_value(0, 1000) >= it->rate) continue; if (!(((it->atk_type) & attack_type) & BF_WEAPONMASK && ((it->atk_type) & attack_type) & BF_RANGEMASK && ((it->atk_type) & attack_type) & BF_SKILLMASK)) continue; // one or more trigger conditions were not fulfilled pet_exeautobonus(*sd, &sd->pd->autobonus, it); } } } //Polymorph if(sd && sd->bonus.classchange && attack_type&BF_WEAPON && dstmd && !status_has_mode(tstatus,MD_STATUSIMMUNE) && (rnd()%10000 < sd->bonus.classchange)) { int class_ = mob_get_random_id(MOBG_BRANCH_OF_DEAD_TREE, RMF_DB_RATE, 0); if (class_ != 0 && mobdb_checkid(class_)) mob_class_change(dstmd,class_); } if (sd && sc) { struct status_change_entry *sce; if ((sce = sc->getSCE(SC_2011RWC_SCROLL)) && rnd() % 1000 <= 10) skill_castend_nodamage_id(src, src, AC_CONCENTRATION, max(3, pc_checkskill(sd,AC_CONCENTRATION)), tick, 0); } return 0; } int skill_onskillusage(map_session_data *sd, struct block_list *bl, uint16 skill_id, t_tick tick) { if( sd == nullptr || !skill_id ) return 0; for (auto &it : sd->autospell3) { if (it.trigger_skill != skill_id) continue; if (it.lock) continue; // autospell already being executed uint16 skill = it.id; sd->state.autocast = 1; //set this to bypass sd->canskill_tick check if( skill_isNotOk(skill, sd) ) { sd->state.autocast = 0; continue; } sd->state.autocast = 0; // DANGER DANGER: here force target actually means use yourself as target! block_list *tbl = (it.flag & AUTOSPELL_FORCE_TARGET) ? &sd->bl : bl; if( tbl == nullptr ){ continue; // No target } if( rnd()%1000 >= it.rate ) continue; uint16 skill_lv = it.lv ? it.lv : 1; if (it.flag & AUTOSPELL_FORCE_RANDOM_LEVEL) skill_lv = rnd_value( 1, skill_lv ); //random skill_lv e_cast_type type = skill_get_casttype(skill); if (type == CAST_GROUND && !skill_pos_maxcount_check(&sd->bl, tbl->x, tbl->y, skill_id, skill_lv, BL_PC, false)) continue; if (battle_config.autospell_check_range && !battle_check_range(bl, tbl, skill_get_range2(&sd->bl, skill, skill_lv, true))) continue; sd->state.autocast = 1; it.lock = true; skill_consume_requirement(sd,skill,skill_lv,1); switch( type ) { case CAST_GROUND: skill_castend_pos2(&sd->bl, tbl->x, tbl->y, skill, skill_lv, tick, 0); break; case CAST_NODAMAGE: skill_castend_nodamage_id(&sd->bl, tbl, skill, skill_lv, tick, 0); break; case CAST_DAMAGE: skill_castend_damage_id(&sd->bl, tbl, skill, skill_lv, tick, 0); break; } it.lock = false; sd->state.autocast = 0; } // Check for player and pet autobonuses when being attacked by skill_id if (sd != nullptr) { // Player if (!sd->autobonus3.empty()) { for (auto &it : sd->autobonus3) { if (it == nullptr) continue; if (rnd_value(0, 1000) >= it->rate) continue; if (it->atk_type != skill_id) continue; pc_exeautobonus(*sd, &sd->autobonus3, it); } } // Pet if (sd->pd != nullptr && !sd->pd->autobonus3.empty()) { for (auto &it : sd->pd->autobonus3) { if (it == nullptr) continue; if (rnd_value(0, 1000) >= it->rate) continue; if (it->atk_type != skill_id) continue; pet_exeautobonus(*sd, &sd->pd->autobonus3, it); } } } return 1; } /* Splitted off from skill_additional_effect, which is never called when the * attack skill kills the enemy. Place in this function counter status effects * when using skills (eg: Asura's sp regen penalty, or counter-status effects * from cards) that will take effect on the source, not the target. [Skotlex] * Note: Currently this function only applies to Extremity Fist and BF_WEAPON * type of skills, so not every instance of skill_additional_effect needs a call * to this one. */ int skill_counter_additional_effect (struct block_list* src, struct block_list *bl, uint16 skill_id, uint16 skill_lv, int attack_type, t_tick tick) { int rate; map_session_data *sd=NULL; map_session_data *dstsd=NULL; nullpo_ret(src); nullpo_ret(bl); if(skill_id > 0 && !skill_lv) return 0; // don't forget auto attacks! - celest sd = BL_CAST(BL_PC, src); dstsd = BL_CAST(BL_PC, bl); if(dstsd && attack_type & BF_WEAPONMASK) { //Counter effects. for (const auto &it : dstsd->addeff_atked) { rate = it.rate; if (attack_type&BF_LONG) rate += it.arrow_rate; if (rate == 0) continue; if ((it.flag&(ATF_WEAPON|ATF_MAGIC|ATF_MISC)) != (ATF_WEAPON|ATF_MAGIC|ATF_MISC)) { // Trigger has attack type consideration. if ((it.flag&ATF_WEAPON && attack_type&BF_WEAPON) || (it.flag&ATF_MAGIC && attack_type&BF_MAGIC) || (it.flag&ATF_MISC && attack_type&BF_MISC)) ; else continue; } if ((it.flag&(ATF_LONG|ATF_SHORT)) != (ATF_LONG|ATF_SHORT)) { //Trigger has range consideration. if((it.flag&ATF_LONG && !(attack_type&BF_LONG)) || (it.flag&ATF_SHORT && !(attack_type&BF_SHORT))) continue; //Range Failed. } if (it.flag&ATF_TARGET && src != bl) status_change_start(src, src, it.sc, rate, 7, 0, 0, 0, it.duration, SCSTART_NONE, 100); if (it.flag&ATF_SELF && !status_isdead(bl)) status_change_start(src, bl, it.sc, rate, 7, 0, 0, 0, it.duration, SCSTART_NONE, 100); } } switch(skill_id) { case MO_EXTREMITYFIST: sc_start(src,src,SC_EXTREMITYFIST,100,skill_lv,skill_get_time2(skill_id,skill_lv)); break; case GS_FULLBUSTER: sc_start(src,src,SC_BLIND,2*skill_lv,skill_lv,skill_get_time2(skill_id,skill_lv)); break; case HFLI_SBR44: //[orn] if(src->type == BL_HOM){ struct homun_data *hd = (struct homun_data *)src; if (hd != nullptr) { hd->homunculus.intimacy = hom_intimacy_grade2intimacy(HOMGRADE_HATE_WITH_PASSION); if (hd->master) clif_send_homdata(hd->master,SP_INTIMATE,hd->homunculus.intimacy / 100); } } break; case CR_GRANDCROSS: case NPC_GRANDDARKNESS: attack_type |= BF_WEAPON; break; case LG_HESPERUSLIT: { status_change *sc = status_get_sc(src); if( sc && sc->getSCE(SC_FORCEOFVANGUARD)) { for(int i = 0; i < sc->getSCE(SC_FORCEOFVANGUARD)->val3; i++ ) pc_addspiritball(sd, skill_get_time(LG_FORCEOFVANGUARD,1),sc->getSCE(SC_FORCEOFVANGUARD)->val3); } } break; case SP_SPA: sc_start(src, src, SC_USE_SKILL_SP_SPA, 100, skill_lv, skill_get_time(skill_id, skill_lv)); break; case SP_SHA: sc_start(src, src, SC_USE_SKILL_SP_SHA, 100, skill_lv, skill_get_time2(skill_id, skill_lv)); break; case SP_SWHOO: sc_start(src, src, SC_USE_SKILL_SP_SHA, 100, skill_lv, skill_get_time(skill_id, skill_lv)); break; } if(sd && (sd->class_&MAPID_UPPERMASK) == MAPID_STAR_GLADIATOR && map_getmapflag(sd->bl.m, MF_NOSUNMOONSTARMIRACLE) == 0) //SG_MIRACLE [Komurka] status_change_start(src,src,SC_MIRACLE,battle_config.sg_miracle_skill_ratio,1,0,0,0,battle_config.sg_miracle_skill_duration,SCSTART_NONE); if(sd && skill_id && attack_type&BF_MAGIC && status_isdead(bl) && !(skill_get_inf(skill_id)&(INF_GROUND_SKILL|INF_SELF_SKILL)) && (rate=pc_checkskill(sd,HW_SOULDRAIN))>0 ){ //Soul Drain should only work on targetted spells [Skotlex] if (pc_issit(sd)) pc_setstand(sd, true); //Character stuck in attacking animation while 'sitting' fix. [Skotlex] clif_skill_nodamage(src,bl,HW_SOULDRAIN,rate,1); status_heal(src, 0, status_get_lv(bl)*(95+15*rate)/100, 2); } if( sd && status_isdead(bl) ) { int sp = 0, hp = 0; if( (attack_type&(BF_WEAPON|BF_SHORT)) == (BF_WEAPON|BF_SHORT) ) { sp += sd->bonus.sp_gain_value; sp += sd->indexed_bonus.sp_gain_race[status_get_race(bl)] + sd->indexed_bonus.sp_gain_race[RC_ALL]; hp += sd->bonus.hp_gain_value; } if( (attack_type&(BF_WEAPON|BF_LONG)) == (BF_WEAPON|BF_LONG) ) { sp += sd->bonus.long_sp_gain_value; hp += sd->bonus.long_hp_gain_value; } if( attack_type&BF_MAGIC ) { sp += sd->bonus.magic_sp_gain_value; hp += sd->bonus.magic_hp_gain_value; if( skill_id == WZ_WATERBALL ) {//(bugreport:5303) status_change *sc = NULL; if( ( sc = status_get_sc(src) ) ) { if(sc->getSCE(SC_SPIRIT) && sc->getSCE(SC_SPIRIT)->val2 == SL_WIZARD && sc->getSCE(SC_SPIRIT)->val3 == WZ_WATERBALL) sc->getSCE(SC_SPIRIT)->val3 = 0; //Clear bounced spell check. } } } if( hp || sp ) { // updated to force healing to allow healing through berserk status_heal(src, hp, sp, battle_config.show_hp_sp_gain ? 3 : 1); } } if (dstsd && !status_isdead(bl) && !(skill_id && skill_get_nk(skill_id, NK_NODAMAGE))) { status_change *sc = status_get_sc(bl); if (sc && sc->getSCE(SC_DORAM_SVSP) && attack_type&(BF_MAGIC|BF_LONG)) skill_castend_damage_id(bl, src, SU_SV_STEMSPEAR, (pc_checkskill(dstsd, SU_SV_STEMSPEAR) ? pc_checkskill(dstsd, SU_SV_STEMSPEAR) : 1), tick, 0); } // Trigger counter-spells to retaliate against damage causing skills. if(dstsd && !status_isdead(bl) && !dstsd->autospell2.empty() && !(skill_id && skill_get_nk(skill_id, NK_NODAMAGE))) { for (const auto &it : dstsd->autospell2) { if (!(((it.battle_flag)&attack_type)&BF_WEAPONMASK && ((it.battle_flag)&attack_type)&BF_RANGEMASK && ((it.battle_flag)&attack_type)&BF_SKILLMASK)) continue; // one or more trigger conditions were not fulfilled uint16 autospl_skill_id = it.id, autospl_skill_lv = it.lv ? it.lv : 1; if (it.flag & AUTOSPELL_FORCE_RANDOM_LEVEL) autospl_skill_lv = rnd_value( 1, autospl_skill_lv ); int autospl_rate = it.rate; //Physical range attacks only trigger autospells half of the time if ((attack_type&(BF_WEAPON|BF_LONG)) == (BF_WEAPON|BF_LONG)) autospl_rate>>=1; dstsd->state.autocast = 1; if ( skill_isNotOk(autospl_skill_id, dstsd) ) { dstsd->state.autocast = 0; continue; } dstsd->state.autocast = 0; if (rnd()%1000 >= autospl_rate) continue; block_list *tbl = (it.flag & AUTOSPELL_FORCE_TARGET) ? src : bl; e_cast_type type = skill_get_casttype(autospl_skill_id); if (type == CAST_GROUND && !skill_pos_maxcount_check(bl, tbl->x, tbl->y, autospl_skill_id, autospl_skill_lv, BL_PC, false)) continue; if (!battle_check_range(bl, tbl, skill_get_range2(src, autospl_skill_id, autospl_skill_lv, true)) && battle_config.autospell_check_range) continue; dstsd->state.autocast = 1; skill_consume_requirement(dstsd,autospl_skill_id,autospl_skill_lv,1); switch (type) { case CAST_GROUND: skill_castend_pos2(bl, tbl->x, tbl->y, autospl_skill_id, autospl_skill_lv, tick, 0); break; case CAST_NODAMAGE: skill_castend_nodamage_id(bl, tbl, autospl_skill_id, autospl_skill_lv, tick, 0); break; case CAST_DAMAGE: skill_castend_damage_id(bl, tbl, autospl_skill_id, autospl_skill_lv, tick, 0); break; } dstsd->state.autocast = 0; //Set canact delay. [Skotlex] unit_data *ud = unit_bl2ud(bl); if (ud) { int delay = skill_delayfix(bl, autospl_skill_id, autospl_skill_lv); if (DIFF_TICK(ud->canact_tick, tick + delay) < 0){ ud->canact_tick = i64max(tick + delay, ud->canact_tick); if ( battle_config.display_status_timers && dstsd ) clif_status_change(bl, EFST_POSTDELAY, 1, delay, 0, 0, 0); } } } } // Check for player and pet autobonuses when attacked if (dstsd != nullptr && !status_isdead(bl) && !(skill_id && skill_get_nk(skill_id, NK_NODAMAGE))) { // Player if (!dstsd->autobonus2.empty()) { for (auto &it : dstsd->autobonus2) { if (it == nullptr) continue; if (rnd_value(0, 1000) >= it->rate) continue; if (!(((it->atk_type) & attack_type) & BF_WEAPONMASK && ((it->atk_type) & attack_type) & BF_RANGEMASK && ((it->atk_type) & attack_type) & BF_SKILLMASK)) continue; // one or more trigger conditions were not fulfilled pc_exeautobonus(*dstsd, &dstsd->autobonus2, it); } } // Pet if (dstsd->pd != nullptr && !dstsd->pd->autobonus2.empty()) { for (auto &it : dstsd->pd->autobonus2) { if (it == nullptr) continue; if (rnd_value(0, 1000) >= it->rate) continue; if (!(((it->atk_type) & attack_type) & BF_WEAPONMASK && ((it->atk_type) & attack_type) & BF_RANGEMASK && ((it->atk_type) & attack_type) & BF_SKILLMASK)) continue; // one or more trigger conditions were not fulfilled pet_exeautobonus(*dstsd, &dstsd->pd->autobonus2, it); } } } return 0; } /*========================================================================= Breaks equipment. On-non players causes the corresponding strip effect. - rate goes from 0 to 10000 (100.00%) - flag is a BCT_ flag to indicate which type of adjustment should be used (BCT_ENEMY/BCT_PARTY/BCT_SELF) are the valid values. --------------------------------------------------------------------------*/ int skill_break_equip(struct block_list *src, struct block_list *bl, unsigned short where, int rate, int flag) { status_change *src_sc = status_get_sc(src); // Grant player skills/items the ability to "break" non-player equipment. // WS_MELTDOWN is exempt from this check. if (!battle_config.break_mob_equip && bl->type != BL_PC && !(src_sc && src_sc->getSCE(SC_MELTDOWN))) return 0; const int where_list[6] = { EQP_WEAPON, EQP_ARMOR, EQP_SHIELD, EQP_HELM, EQP_ACC, EQP_SHADOW_GEAR }; const enum sc_type scatk[6] = { SC_STRIPWEAPON, SC_STRIPARMOR, SC_STRIPSHIELD, SC_STRIPHELM, SC__STRIPACCESSORY, SC_SHADOW_STRIP }; const enum sc_type scdef[6] = { SC_CP_WEAPON, SC_CP_ARMOR, SC_CP_SHIELD, SC_CP_HELM, SC_NONE, SC_PROTECTSHADOWEQUIP }; status_change *sc = status_get_sc(bl); int i; TBL_PC *sd; sd = BL_CAST(BL_PC, bl); if (sc && !sc->count) sc = NULL; if (sd) { if (sd->bonus.unbreakable_equip) where &= ~sd->bonus.unbreakable_equip; if (sd->bonus.unbreakable) rate -= rate*sd->bonus.unbreakable/100; if (where&EQP_WEAPON) { switch (sd->status.weapon) { case W_FIST: //Bare fists should not break case W_1HAXE: case W_2HAXE: case W_MACE: // Axes and Maces can't be broken [DracoRPG] case W_2HMACE: case W_STAFF: case W_2HSTAFF: case W_BOOK: //Rods and Books can't be broken [Skotlex] case W_HUUMA: case W_DOUBLE_AA: // Axe usage during dual wield should also prevent breaking [Neutral] case W_DOUBLE_DA: case W_DOUBLE_SA: where &= ~EQP_WEAPON; } } } if (flag&BCT_ENEMY) { if (battle_config.equip_skill_break_rate != 100) rate = rate*battle_config.equip_skill_break_rate/100; } else if (flag&(BCT_PARTY|BCT_SELF)) { if (battle_config.equip_self_break_rate != 100) rate = rate*battle_config.equip_self_break_rate/100; } for (i = 0; i < 6; i++) { if (where&where_list[i]) { if (sc && sc->count && sc->getSCE(scdef[i])) where&=~where_list[i]; else if (rnd()%10000 >= rate) where&=~where_list[i]; else if (!sd) //Cause Strip effect. sc_start(src,bl,scatk[i],100,0,skill_get_time(status_db.getSkill(scatk[i]),1)); } } if (!where) //Nothing to break. return 0; if (sd) { for (i = 0; i < EQI_MAX; i++) { short j = sd->equip_index[i]; if (j < 0 || sd->inventory.u.items_inventory[j].attribute == 1 || !sd->inventory_data[j]) continue; switch(i) { case EQI_HEAD_TOP: //Upper Head flag = (where&EQP_HELM); break; case EQI_ARMOR: //Body flag = (where&EQP_ARMOR); break; case EQI_HAND_R: //Left/Right hands case EQI_HAND_L: flag = ( (where&EQP_WEAPON && sd->inventory_data[j]->type == IT_WEAPON) || (where&EQP_SHIELD && sd->inventory_data[j]->type == IT_ARMOR)); break; case EQI_SHOES: flag = (where&EQP_SHOES); break; case EQI_GARMENT: flag = (where&EQP_GARMENT); break; case EQI_ACC_L: flag = (where&EQP_ACC_L); break; case EQI_ACC_R: flag = (where&EQP_ACC_R); break; case EQI_SHADOW_ARMOR: flag = (where&EQP_SHADOW_ARMOR); break; case EQI_SHADOW_WEAPON: flag = (where&EQP_SHADOW_WEAPON); break; case EQI_SHADOW_SHIELD: flag = (where&EQP_SHADOW_SHIELD); break; case EQI_SHADOW_SHOES: flag = (where&EQP_SHADOW_SHOES); break; case EQI_SHADOW_ACC_R: flag = (where&EQP_SHADOW_ACC_R); break; case EQI_SHADOW_ACC_L: flag = (where&EQP_SHADOW_ACC_L); break; default: continue; } if (flag) { sd->inventory.u.items_inventory[j].attribute = 1; pc_unequipitem(sd, j, 3); } } clif_equiplist(sd); } return where; //Return list of pieces broken. } /** * Strip equipment from a target * @param src: Source of call * @param target: Target to strip * @param skill_id: Skill used * @param skill_lv: Skill level used * @return True on successful strip or false otherwise */ bool skill_strip_equip(struct block_list *src, struct block_list *target, uint16 skill_id, uint16 skill_lv) { nullpo_retr(false, src); nullpo_retr(false, target); status_change *tsc = status_get_sc(target); if (!tsc || tsc->option&OPTION_MADOGEAR) // Mado Gear cannot be divested [Ind] return false; const int pos[6] = { EQP_WEAPON, EQP_SHIELD, EQP_ARMOR, EQP_HELM, EQP_ACC, EQP_SHADOW_GEAR }; const enum sc_type sc_atk[6] = { SC_STRIPWEAPON, SC_STRIPSHIELD, SC_STRIPARMOR, SC_STRIPHELM, SC__STRIPACCESSORY, SC_SHADOW_STRIP }; const enum sc_type sc_def[6] = { SC_CP_WEAPON, SC_CP_SHIELD, SC_CP_ARMOR, SC_CP_HELM, SC_NONE, SC_PROTECTSHADOWEQUIP }; struct status_data *sstatus = status_get_status_data(src), *tstatus = status_get_status_data(target); int rate, time, location, mod = 100; switch (skill_id) { // Rate case RG_STRIPWEAPON: case RG_STRIPARMOR: case RG_STRIPSHIELD: case RG_STRIPHELM: case GC_WEAPONCRUSH: rate = 50 * (skill_lv + 1) + 2 * (sstatus->dex - tstatus->dex); mod = 1000; break; case ST_FULLSTRIP: { int min_rate = 50 + 20 * skill_lv; rate = min_rate + 2 * (sstatus->dex - tstatus->dex); rate = max(min_rate, rate); mod = 1000; break; } case GS_DISARM: rate = sstatus->dex / (4 * (7 - skill_lv)) + sstatus->luk / (4 * (6 - skill_lv)); rate = rate + status_get_lv(src) - (tstatus->agi * rate / 100) - tstatus->luk - status_get_lv(target); break; case WL_EARTHSTRAIN: { int job_lv = 0; if (src->type == BL_PC) job_lv = ((TBL_PC*)src)->status.job_level; rate = 6 * skill_lv + job_lv / 4 + sstatus->dex / 10; break; } case SC_STRIPACCESSARY: rate = 12 + 2 * skill_lv; break; case ABC_STRIP_SHADOW: rate = 50 * (skill_lv + 3) + 2 * (sstatus->dex - tstatus->dex); mod = 1000; break; default: return false; } if (rnd()%mod >= rate) return false; switch (skill_id) { // Duration case SC_STRIPACCESSARY: case GS_DISARM: time = skill_get_time(skill_id, skill_lv); break; case WL_EARTHSTRAIN: case RG_STRIPWEAPON: case RG_STRIPARMOR: case RG_STRIPSHIELD: case RG_STRIPHELM: case GC_WEAPONCRUSH: case ST_FULLSTRIP: case ABC_STRIP_SHADOW: if (skill_id == WL_EARTHSTRAIN) time = skill_get_time2(skill_id, skill_lv); else time = skill_get_time(skill_id, skill_lv); if (target->type == BL_PC) time += max(1, skill_lv + 500 * (sstatus->dex - tstatus->dex)); else { time += 15000; time += max(1, skill_lv + 500 * (sstatus->dex - tstatus->dex)); } break; } switch (skill_id) { // Location case GC_WEAPONCRUSH: case RG_STRIPWEAPON: case GS_DISARM: location = EQP_WEAPON; break; case RG_STRIPARMOR: location = EQP_ARMOR; break; case RG_STRIPSHIELD: location = EQP_SHIELD; break; case RG_STRIPHELM: location = EQP_HELM; break; case ST_FULLSTRIP: location = EQP_WEAPON|EQP_SHIELD|EQP_ARMOR|EQP_HELM; break; case SC_STRIPACCESSARY: location = EQP_ACC; break; case ABC_STRIP_SHADOW: location = EQP_SHADOW_GEAR; break; } for (uint8 i = 0; i < ARRAYLENGTH(pos); i++) { if (location&pos[i] && sc_def[i] > SC_NONE && tsc->getSCE(sc_def[i])) location &=~ pos[i]; } if (!location) return false; for (uint8 i = 0; i < ARRAYLENGTH(pos); i++) { if (location&pos[i] && !sc_start(src, target, sc_atk[i], 100, skill_lv, time)) location &=~ pos[i]; } return location ? true : false; } /** * Used to knock back players, monsters, traps, etc * @param src Object that give knock back * @param target Object that receive knock back * @param count Number of knock back cell requested * @param dir Direction indicates the way OPPOSITE to the knockback direction (or -1 for default behavior) * @param flag BLOWN_DONT_SEND_PACKET - position update packets must not be sent BLOWN_IGNORE_NO_KNOCKBACK - ignores players' special_state.no_knockback These flags "return 'count' instead of 0 if target is cannot be knocked back": BLOWN_NO_KNOCKBACK_MAP - at WOE/BG map BLOWN_MD_KNOCKBACK_IMMUNE - if target is MD_KNOCKBACK_IMMUNE BLOWN_TARGET_NO_KNOCKBACK - if target has 'special_state.no_knockback' BLOWN_TARGET_BASILICA - if target is in Basilica area (Pre-Renewal) * @return Number of knocked back cells done */ short skill_blown(struct block_list* src, struct block_list* target, char count, int8 dir, enum e_skill_blown flag) { int dx = 0, dy = 0; uint8 checkflag = 0; status_change *tsc = status_get_sc(target); enum e_unit_blown reason = UB_KNOCKABLE; nullpo_ret(src); nullpo_ret(target); if (!count) return count; // Actual knockback distance is 0. // Create flag needed in unit_blown_immune if(src != target) checkflag |= 0x1; // Offensive if(!(flag&BLOWN_IGNORE_NO_KNOCKBACK)) checkflag |= 0x2; // Knockback type if(status_get_class_(src) == CLASS_BOSS) checkflag |= 0x4; // Boss attack // Get reason and check for flags reason = unit_blown_immune(target, checkflag); switch(reason) { case UB_NO_KNOCKBACK_MAP: return ((flag&BLOWN_NO_KNOCKBACK_MAP) ? count : 0); // No knocking back in WoE / BG case UB_MD_KNOCKBACK_IMMUNE: return ((flag&BLOWN_MD_KNOCKBACK_IMMUNE) ? count : 0); // Immune can't be knocked back case UB_TARGET_BASILICA: return ((flag&BLOWN_TARGET_BASILICA) ? count : 0); // Basilica caster can't be knocked-back by normal monsters. case UB_TARGET_NO_KNOCKBACK: return ((flag&BLOWN_TARGET_NO_KNOCKBACK) ? count : 0); // Target has special_state.no_knockback (equip) case UB_TARGET_TRAP: return count; // Trap cannot be knocked back } if (dir == -1) // <optimized>: do the computation here instead of outside dir = map_calc_dir(target, src->x, src->y); // Direction from src to target, reversed if (dir >= 0 && dir < { // Take the reversed 'direction' and reverse it dx = -dirx[dir]; dy = -diry[dir]; } if (tsc) { if (tsc->getSCE(SC_SU_STOOP)) // Any knockback will cancel it. status_change_end(target, SC_SU_STOOP); if (tsc->getSCE(SC_ROLLINGCUTTER)) status_change_end(target, SC_ROLLINGCUTTER); if (tsc->getSCE(SC_CRESCIVEBOLT)) status_change_end(target, SC_CRESCIVEBOLT); if (tsc->getSCE(SC_SV_ROOTTWIST)) // Shouldn't move. return 0; } return unit_blown(target, dx, dy, count, flag); // Send over the proper flag } // Checks if 'bl' should reflect back a spell cast by 'src'. // type is the type of magic attack: 0: indirect (aoe), 1: direct (targetted) // In case of success returns type of reflection, otherwise 0 // 1 - Regular reflection (Maya) // 2 - SL_KAITE reflection static int skill_magic_reflect(struct block_list* src, struct block_list* bl, int type) { status_change *sc = status_get_sc(bl); map_session_data* sd = BL_CAST(BL_PC, bl); // Deadly Projection null's all magic reflection. if (sc && sc->getSCE(SC_DEADLY_DEFEASANCE)) return 0; if (!sc || !sc->getSCE(SC_KYOMU)) { // Kyomu doesn't reflect // Item-based reflection - Bypasses Boss check if (sd && sd->bonus.magic_damage_return && type && rnd()%100 < sd->bonus.magic_damage_return) return 1; } // Magic Mirror reflection - Bypasses Boss check if (sc && sc->getSCE(SC_MAGICMIRROR) && rnd()%100 < sc->getSCE(SC_MAGICMIRROR)->val2) return 1; if( status_get_class_(src) == CLASS_BOSS ) return 0; // status-based reflection if( !sc || sc->count == 0 ) return 0; // Kaite reflection - Does not bypass Boss check if( sc->getSCE(SC_KAITE) && (src->type == BL_PC || status_get_lv(src) <= 80) #ifdef RENEWAL && type // Does not reflect AoE #endif ) { // Kaite only works against non-players if they are low-level. // Kyomu doesn't disable Kaite, but the "skill fail chance" part of Kyomu applies to it. clif_specialeffect(bl, EF_ATTACKENERGY2, AREA); if( --sc->getSCE(SC_KAITE)->val2 <= 0 ) status_change_end(bl, SC_KAITE); return 2; } return 0; } /** * Checks whether a skill can be used in combos or not * @param skill_id: Target skill * @return 0: Skill is not a combo * 1: Skill is a normal combo * 2: Skill is combo that prioritizes auto-target even if val2 is set * @author Panikon */ int skill_is_combo(uint16 skill_id) { switch(skill_id) { case MO_CHAINCOMBO: case MO_COMBOFINISH: case CH_TIGERFIST: case CH_CHAINCRUSH: case MO_EXTREMITYFIST: case TK_TURNKICK: case TK_STORMKICK: case TK_DOWNKICK: case TK_COUNTER: case TK_JUMPKICK: case HT_POWER: case SR_DRAGONCOMBO: return 1; case SR_FALLENEMPIRE: case SR_TIGERCANNON: case SR_GATEOFHELL: return 2; } return 0; } /* * Combo handler, start stop combo status */ void skill_combo_toggle_inf(struct block_list* bl, uint16 skill_id, int inf){ TBL_PC *sd = BL_CAST(BL_PC, bl); switch (skill_id) { case MH_MIDNIGHT_FRENZY: case MH_EQC: { int skill_id2 = ((skill_id==MH_EQC)?MH_TINDER_BREAKER:MH_SONIC_CRAW); short idx = hom_skill_get_index(skill_id2); int flag = (inf?SKILL_FLAG_TMP_COMBO:SKILL_FLAG_PERMANENT); TBL_HOM *hd = BL_CAST(BL_HOM, bl); if (idx == -1) break; sd = hd->master; hd->homunculus.hskill[idx].flag= flag; if(sd) clif_homskillinfoblock(sd); //refresh info //@FIXME we only want to refresh one skill } break; case MO_COMBOFINISH: case CH_TIGERFIST: case CH_CHAINCRUSH: if (sd) clif_skillinfo(sd,MO_EXTREMITYFIST, inf); break; case TK_JUMPKICK: if (sd) clif_skillinfo(sd,TK_JUMPKICK, inf); break; case MO_TRIPLEATTACK: if (sd && pc_checkskill(sd, SR_DRAGONCOMBO) > 0) clif_skillinfo(sd,SR_DRAGONCOMBO, inf); break; case SR_FALLENEMPIRE: if (sd){ clif_skillinfo(sd,SR_GATEOFHELL, inf); clif_skillinfo(sd,SR_TIGERCANNON, inf); } break; } } void skill_combo(struct block_list* src,struct block_list *dsrc, struct block_list *bl, uint16 skill_id, uint16 skill_lv, t_tick tick){ t_tick duration = 0; //Set to duration the user can use a combo skill or 1 for aftercast delay of pre-skill int nodelay = 0; //Set to 1 for no walk/attack delay, set to 2 for no walk delay int target_id = bl->id; //Set to 0 if combo skill should not autotarget struct status_change_entry *sce; TBL_PC *sd = BL_CAST(BL_PC,src); TBL_HOM *hd = BL_CAST(BL_HOM,src); status_change *sc = status_get_sc(src); if(sc == NULL) return; //End previous combo state after skill is invoked if ((sce = sc->getSCE(SC_COMBO)) != NULL) { switch (skill_id) { case TK_TURNKICK: case TK_STORMKICK: case TK_DOWNKICK: case TK_COUNTER: if (sd && pc_famerank(sd->status.char_id,MAPID_TAEKWON)) {//Extend combo time. sce->val1 = skill_id; //Update combo-skill sce->val3 = skill_id; if( sce->timer != INVALID_TIMER ) delete_timer(sce->timer, status_change_timer); sce->timer = add_timer(tick+sce->val4, status_change_timer, src->id, SC_COMBO); break; } unit_cancel_combo(src); // Cancel combo wait break; default: if( src == dsrc ) // Ground skills are exceptions. [Inkfish] status_change_end(src, SC_COMBO); } } //start new combo if (sd) { //player only switch (skill_id) { case MO_TRIPLEATTACK: if (pc_checkskill(sd, MO_CHAINCOMBO) > 0 || pc_checkskill(sd, SR_DRAGONCOMBO) > 0) { duration = 1; target_id = 0; // Will target current auto-target instead } break; case MO_CHAINCOMBO: if (pc_checkskill(sd, MO_COMBOFINISH) > 0 && sd->spiritball > 0) { duration = 1; target_id = 0; // Will target current auto-target instead } break; case MO_COMBOFINISH: if (sd->status.party_id > 0) //bonus from SG_FRIEND [Komurka] party_skill_check(sd, sd->status.party_id, MO_COMBOFINISH, skill_lv); if (pc_checkskill(sd, CH_TIGERFIST) > 0 && sd->spiritball > 0) { duration = 1; target_id = 0; // Will target current auto-target instead } case CH_TIGERFIST: if (!duration && pc_checkskill(sd, CH_CHAINCRUSH) > 0 && sd->spiritball > 1) { duration = 1; target_id = 0; // Will target current auto-target instead } case CH_CHAINCRUSH: if (!duration && pc_checkskill(sd, MO_EXTREMITYFIST) > 0 && sd->spiritball > 0 && sd->sc.getSCE(SC_EXPLOSIONSPIRITS)) { duration = 1; target_id = 0; // Will target current auto-target instead } break; case AC_DOUBLE: if (pc_checkskill(sd, HT_POWER)) { duration = 2000; nodelay = 1; //Neither gives walk nor attack delay target_id = 0; //Does not need to be used on previous target } break; case SR_DRAGONCOMBO: if (pc_checkskill(sd, SR_FALLENEMPIRE) > 0) duration = 1; break; case SR_FALLENEMPIRE: if (pc_checkskill(sd, SR_TIGERCANNON) > 0 || pc_checkskill(sd, SR_GATEOFHELL) > 0) duration = 1; break; case SJ_PROMINENCEKICK: if (pc_checkskill(sd, SJ_SOLARBURST) > 0) duration = 1; break; } } else { //other switch(skill_id) { case MH_TINDER_BREAKER: case MH_CBC: case MH_SONIC_CRAW: case MH_SILVERVEIN_RUSH: if(hd->homunculus.spiritball > 0) duration = 2000; nodelay = 1; break; case MH_EQC: case MH_MIDNIGHT_FRENZY: if(hd->homunculus.spiritball >= 2) duration = 2000; nodelay = 1; break; } } if (duration) { //Possible to chain if(sd && duration==1) duration = DIFF_TICK(sd->ud.canact_tick, tick); //Auto calc duration duration = i64max(status_get_amotion(src),duration); //Never less than aMotion sc_start4(src,src,SC_COMBO,100,skill_id,target_id,nodelay,0,duration); clif_combo_delay(src, duration); } } /** * Copy skill by Plagiarism or Reproduce * @param src: The caster * @param bl: The target * @param skill_id: Skill that casted * @param skill_lv: Skill level of the casted skill */ static void skill_do_copy(struct block_list* src,struct block_list *bl, uint16 skill_id, uint16 skill_lv) { TBL_PC *tsd = BL_CAST(BL_PC, bl); if (!tsd || (!pc_checkskill(tsd,RG_PLAGIARISM) && !pc_checkskill(tsd,SC_REPRODUCE))) return; //If SC_PRESERVE is active and SC__REPRODUCE is not active, nothing to do else if (tsd->sc.getSCE(SC_PRESERVE) && !tsd->sc.getSCE(SC__REPRODUCE)) return; else { uint16 idx; uint8 lv; skill_id = skill_dummy2skill_id(skill_id); //Use skill index, avoiding out-of-bound array [Cydh] if (!(idx = skill_get_index(skill_id))) return; switch (skill_isCopyable(tsd,skill_id)) { case 1: //Copied by Plagiarism { if (tsd->cloneskill_idx > 0 && tsd->status.skill[tsd->cloneskill_idx].flag == SKILL_FLAG_PLAGIARIZED) { clif_deleteskill(tsd,tsd->status.skill[tsd->cloneskill_idx].id); tsd->status.skill[tsd->cloneskill_idx].id = 0; tsd->status.skill[tsd->cloneskill_idx].lv = 0; tsd->status.skill[tsd->cloneskill_idx].flag = SKILL_FLAG_PERMANENT; } lv = min(skill_lv,pc_checkskill(tsd,RG_PLAGIARISM)); //Copied level never be > player's RG_PLAGIARISM level tsd->cloneskill_idx = idx; pc_setglobalreg(tsd, add_str(SKILL_VAR_PLAGIARISM), skill_id); pc_setglobalreg(tsd, add_str(SKILL_VAR_PLAGIARISM_LV), lv); } break; case 2: //Copied by Reproduce { status_change *tsc = status_get_sc(bl); //Already did SC check //Skill level copied depends on Reproduce skill that used lv = (tsc) ? tsc->getSCE(SC__REPRODUCE)->val1 : 1; if( tsd->reproduceskill_idx > 0 && tsd->status.skill[tsd->reproduceskill_idx].flag == SKILL_FLAG_PLAGIARIZED ) { clif_deleteskill(tsd,tsd->status.skill[tsd->reproduceskill_idx].id); tsd->status.skill[tsd->reproduceskill_idx].id = 0; tsd->status.skill[tsd->reproduceskill_idx].lv = 0; tsd->status.skill[tsd->reproduceskill_idx].flag = SKILL_FLAG_PERMANENT; } //Level dependent and limitation. if (src->type == BL_PC) //If player, max skill level is skill_get_max(skill_id) lv = min(lv,skill_get_max(skill_id)); else //Monster might used skill level > allowed player max skill lv. Ex. Drake with Waterball lv. 10 lv = min(lv,skill_lv); tsd->reproduceskill_idx = idx; pc_setglobalreg(tsd, add_str(SKILL_VAR_REPRODUCE), skill_id); pc_setglobalreg(tsd, add_str(SKILL_VAR_REPRODUCE_LV), lv); } break; default: return; } tsd->status.skill[idx].id = skill_id; tsd->status.skill[idx].lv = lv; tsd->status.skill[idx].flag = SKILL_FLAG_PLAGIARIZED; clif_addskill(tsd,skill_id); } } /** * Knockback the target on skill_attack * @param src is the master behind the attack * @param dsrc is the actual originator of the damage, can be the same as src, or a BL_SKILL * @param target is the target to be attacked. * @param blewcount * @param skill_id * @param skill_lv * @param damage * @param tick * @param flag can hold a bunch of information: */ void skill_attack_blow(struct block_list *src, struct block_list *dsrc, struct block_list *target, uint8 blewcount, uint16 skill_id, uint16 skill_lv, int64 damage, t_tick tick, int flag) { int8 dir = -1; // Default direction //Only knockback if it's still alive, otherwise a "ghost" is left behind. [Skotlex] //Reflected spells do not bounce back (src == dsrc since it only happens for direct skills) if (!blewcount || target == dsrc || status_isdead(target)) return; // Skill specific direction switch (skill_id) { case MG_FIREWALL: case EL_FIRE_MANTLE: dir = unit_getdir(target); // Backwards break; // This ensures the storm randomly pushes instead of exactly a cell backwards per official mechanics. case NPC_STORMGUST2: case WZ_STORMGUST: if(!battle_config.stormgust_knockback) dir = rnd()%8; break; case MC_CARTREVOLUTION: if (battle_config.cart_revo_knockback) dir = 6; // Official servers push target to the West break; case AC_SHOWER: case WL_CRIMSONROCK: if (!battle_config.arrow_shower_knockback && skill_id == AC_SHOWER) dir = map_calc_dir(target, src->x, src->y); else dir = map_calc_dir(target, skill_area_temp[4], skill_area_temp[5]); break; case HT_PHANTASMIC: // issue #1378 if (status_get_hp(target) - damage <= 0) return; break; } // Blown-specific handling switch( skill_id ) { case SR_KNUCKLEARROW: // Ignore knockback damage bonus if in WOE (player cannot be knocked in WOE) // Boss & Immune Knockback stay in place and don't get bonus damage // Give knockback damage bonus only hits the wall. (bugreport:9096) if (skill_blown(dsrc, target, blewcount, dir_ka, (enum e_skill_blown)(BLOWN_IGNORE_NO_KNOCKBACK|BLOWN_NO_KNOCKBACK_MAP|BLOWN_MD_KNOCKBACK_IMMUNE|BLOWN_TARGET_NO_KNOCKBACK|BLOWN_TARGET_BASILICA)) < blewcount) skill_addtimerskill(src, tick + 300 * ((flag&2) ? 1 : 2), target->id, 0, 0, skill_id, skill_lv, BF_WEAPON, flag|4); dir_ka = -1; break; case RL_R_TRIP: if (skill_blown(dsrc,target,blewcount,dir,BLOWN_NONE) < blewcount) skill_addtimerskill(src, tick + status_get_amotion(src), target->id, 0, 0, RL_R_TRIP_PLUSATK, skill_lv, BF_WEAPON, flag|SD_ANIMATION); break; default: skill_blown(dsrc,target,blewcount,dir, BLOWN_NONE); if (!blewcount && target->type == BL_SKILL && damage > 0) { TBL_SKILL *su = (TBL_SKILL*)target; if (su->group && su->group->skill_id == HT_BLASTMINE) skill_blown(src, target, 3, -1, BLOWN_NONE); } break; } clif_fixpos(target); } /* * ========================================================================= * Does a skill attack with the given properties. * @param src is the master behind the attack (player/mob/pet) * @param dsrc is the actual originator of the damage, can be the same as src, or a BL_SKILL * @param bl is the target to be attacked. * @param flag can hold a bunch of information: * flag&1 * flag&2 - Disable re-triggered by double casting * flag&4 - Skip to blow target (because already knocked back before skill_attack somewhere) * flag&8 - SC_COMBO state used to deal bonus damage * * flag&0xFFF is passed to the underlying battle_calc_attack for processing. * (usually holds number of targets, or just 1 for simple splash attacks) * * flag&0xF000 - Values from enum e_skill_display * flag&0x3F0000 - Values from enum e_battle_check_target * * flag&0x1000000 - Return 0 if damage was reflected *-------------------------------------------------------------------------*/ int64 skill_attack (int attack_type, struct block_list* src, struct block_list *dsrc, struct block_list *bl, uint16 skill_id, uint16 skill_lv, t_tick tick, int flag) { struct Damage dmg; struct status_data *sstatus, *tstatus; status_change *sc, *tsc; map_session_data *sd, *tsd; int64 damage; bool rmdamage = false;//magic reflected int type; enum e_damage_type dmg_type; bool shadow_flag = false; bool additional_effects = true; if(skill_id > 0 && !skill_lv) return 0; nullpo_ret(src); //Source is the master behind the attack (player/mob/pet) nullpo_ret(dsrc); //dsrc is the actual originator of the damage, can be the same as src, or a skill casted by src. nullpo_ret(bl); //Target to be attacked. if (status_bl_has_mode(bl,MD_SKILLIMMUNE) || (status_get_class(bl) == MOBID_EMPERIUM && !skill_get_inf2(skill_id, INF2_TARGETEMPERIUM))) return 0; if (src != dsrc) { //When caster is not the src of attack, this is a ground skill, and as such, do the relevant target checking. [Skotlex] if (!status_check_skilluse(battle_config.skill_caster_check?src:NULL, bl, skill_id, 2)) return 0; } else if ((flag&SD_ANIMATION) && skill_get_nk(skill_id, NK_SPLASH)) { //Note that splash attacks often only check versus the targetted mob, those around the splash area normally don't get checked for being hidden/cloaked/etc. [Skotlex] if (!status_check_skilluse(src, bl, skill_id, 2)) return 0; } sd = BL_CAST(BL_PC, src); tsd = BL_CAST(BL_PC, bl); sstatus = status_get_status_data(src); tstatus = status_get_status_data(bl); sc= status_get_sc(src); tsc= status_get_sc(bl); if (tsc && !tsc->count) tsc = NULL; //Don't need it. //Trick Dead protects you from damage, but not from buffs and the like, hence it's placed here. if (tsc && tsc->getSCE(SC_TRICKDEAD)) return 0; #ifndef RENEWAL //When Gravitational Field is active, damage can only be dealt by Gravitational Field and Autospells if(sd && sc && sc->getSCE(SC_GRAVITATION) && sc->getSCE(SC_GRAVITATION)->val3 == BCT_SELF && skill_id != HW_GRAVITATION && !sd->state.autocast) return 0; #endif dmg = battle_calc_attack(attack_type,src,bl,skill_id,skill_lv,flag&0xFFF); //If the damage source is a unit, the damage is not delayed if (src != dsrc && skill_id != GS_GROUNDDRIFT) dmg.amotion = 0; //! CHECKME: This check maybe breaks the battle_calc_attack, and maybe need better calculation. // Adjusted to the new system [Skotlex] if( src->type == BL_PET ) { // [Valaris] struct pet_data *pd = (TBL_PET*)src; if (pd->a_skill && pd->a_skill->div_ && pd->a_skill->id == skill_id) { //petskillattack2 if (battle_config.pet_ignore_infinite_def || !is_infinite_defense(bl,dmg.flag)) { int element = skill_get_ele(skill_id, skill_lv); /*if (skill_id == -1) Does it ever worked? element = sstatus->rhw.ele;*/ if (element != ELE_NEUTRAL || !(battle_config.attack_attr_none&BL_PET)) dmg.damage = battle_attr_fix(src, bl, pd->a_skill->damage, element, tstatus->def_ele, tstatus->ele_lv); else dmg.damage = pd->a_skill->damage; // Fixed damage } else dmg.damage = 1*pd->a_skill->div_; dmg.damage2 = 0; dmg.div_= pd->a_skill->div_; } } if( dmg.flag&BF_MAGIC && ( skill_id != NPC_EARTHQUAKE || (battle_config.eq_single_target_reflectable && (flag&0xFFF) == 1) ) ) { // Earthquake on multiple targets is not counted as a target skill. [Inkfish] if( (dmg.damage || dmg.damage2) && (type = skill_magic_reflect(src, bl, src==dsrc)) ) { //Magic reflection, switch caster/target struct block_list *tbl = bl; rmdamage = true; bl = src; src = tbl; dsrc = tbl; sd = BL_CAST(BL_PC, src); tsd = BL_CAST(BL_PC, bl); tsc = status_get_sc(bl); if (tsc && !tsc->count) tsc = NULL; //Don't need it. /* bugreport:2564 flag&2 disables double casting trigger */ flag |= 2; //Reflected magic damage will not cause the caster to be knocked back [Playtester] flag |= 4; //Spirit of Wizard blocks Kaite's reflection if( type == 2 && tsc && tsc->getSCE(SC_SPIRIT) && tsc->getSCE(SC_SPIRIT)->val2 == SL_WIZARD ) { //Consume one Fragment per hit of the casted skill? [Skotlex] type = tsd?pc_search_inventory (tsd, ITEMID_FRAGMENT_OF_CRYSTAL):0; if (type >= 0) { if ( tsd ) pc_delitem(tsd, type, 1, 0, 1, LOG_TYPE_CONSUME); dmg.damage = dmg.damage2 = 0; dmg.dmg_lv = ATK_MISS; tsc->getSCE(SC_SPIRIT)->val3 = skill_id; tsc->getSCE(SC_SPIRIT)->val4 = dsrc->id; } } else if( type != 2 ) /* Kaite bypasses */ additional_effects = false; // Official Magic Reflection Behavior : damage reflected depends on gears caster wears, not target #if MAGIC_REFLECTION_TYPE #ifdef RENEWAL if( dmg.dmg_lv != ATK_MISS ) { //Wiz SL cancelled and consumed fragment #else // issue:6415 in pre-renewal Kaite reflected the entire damage received // regardless of caster's equipment (Aegis 11.1) if( dmg.dmg_lv != ATK_MISS && type == 1 ) { //Wiz SL cancelled and consumed fragment #endif short s_ele = skill_get_ele(skill_id, skill_lv); if (s_ele == ELE_WEAPON) // the skill takes the weapon's element s_ele = sstatus->rhw.ele; else if (s_ele == ELE_ENDOWED) //Use status element s_ele = status_get_attack_sc_element(src,status_get_sc(src)); else if( s_ele == ELE_RANDOM) //Use random element s_ele = rnd()%ELE_ALL; dmg.damage = battle_attr_fix(bl, bl, dmg.damage, s_ele, status_get_element(bl), status_get_element_level(bl)); if( tsc && tsc->getSCE(SC_ENERGYCOAT) ) { struct status_data *status = status_get_status_data(bl); int per = 100*status->sp / status->max_sp -1; //100% should be counted as the 80~99% interval per /=20; //Uses 20% SP intervals. //SP Cost: 1% + 0.5% per every 20% SP if (!status_charge(bl, 0, (10+5*per)*status->max_sp/1000)) status_change_end(bl, SC_ENERGYCOAT); //Reduction: 6% + 6% every 20% dmg.damage -= dmg.damage * (6 * (1+per)) / 100; } if (dmg.damage > 0 && tsd && tsd->bonus.reduce_damage_return != 0) { dmg.damage -= dmg.damage * tsd->bonus.reduce_damage_return / 100; dmg.damage = i64max(dmg.damage, 1); } } #endif } if(tsc && tsc->getSCE(SC_MAGICROD) && src == dsrc) { int sp = skill_get_sp(skill_id,skill_lv); #ifndef RENEWAL clif_skill_nodamage(bl,bl,SA_MAGICROD,skill_lv,1); #endif dmg.damage = dmg.damage2 = 0; dmg.dmg_lv = ATK_MISS; //This will prevent skill additional effect from taking effect. [Skotlex] sp = sp * tsc->getSCE(SC_MAGICROD)->val2 / 100; if(skill_id == WZ_WATERBALL && skill_lv > 1) sp = sp/((skill_lv|1)*(skill_lv|1)); //Estimate SP cost of a single water-ball status_heal(bl, 0, sp, 2); } if( (dmg.damage || dmg.damage2) && tsc && (tsc->getSCE(SC_HALLUCINATIONWALK) && rnd()%100 < tsc->getSCE(SC_HALLUCINATIONWALK)->val3 || tsc->getSCE(SC_NPC_HALLUCINATIONWALK) && rnd()%100 < tsc->getSCE(SC_NPC_HALLUCINATIONWALK)->val3) ) { dmg.damage = dmg.damage2 = 0; dmg.dmg_lv = ATK_MISS; } } damage = dmg.damage + dmg.damage2; if ((dmg.flag & BF_MAGIC) && tsc && tsc->getSCE(SC_MAXPAIN)) { auto * sce = tsc->getSCE(SC_MAXPAIN); sce->val3 = (int)damage; sce->val2 = 0; if (!tsc->getSCE(SC_KYOMU)) //SC_KYOMU invalidates reflecting ability. skill_castend_damage_id(bl, src, NPC_MAXPAIN_ATK, sce->val1, tick, flag); } if( (skill_id == AL_INCAGI || skill_id == AL_BLESSING || skill_id == CASH_BLESSING || skill_id == CASH_INCAGI || skill_id == MER_INCAGI || skill_id == MER_BLESSING) && tsc && tsc->getSCE(SC_CHANGEUNDEAD) ) damage = 1; if( damage && tsc && tsc->getSCE(SC_GENSOU) && dmg.flag&BF_MAGIC ){ struct block_list *nbl; nbl = battle_getenemyarea(bl,bl->x,bl->y,2,BL_CHAR,bl->id); if( nbl ){ // Only one target is chosen. damage = damage / 2; // Deflect half of the damage to a target nearby clif_skill_damage(bl, nbl, tick, status_get_amotion(src), 0, status_fix_damage(bl,nbl,damage,0,0), dmg.div_, OB_OBOROGENSOU_TRANSITION_ATK, -1, DMG_SINGLE); } } //Skill hit type dmg_type = (skill_id == 0) ? DMG_SPLASH : skill_get_hit(skill_id); switch( skill_id ) { case SC_TRIANGLESHOT: if( rnd()%100 > (1 + skill_lv) ) dmg.blewcount = 0; break; default: if (damage < dmg.div_ && skill_id != CH_PALMSTRIKE) dmg.blewcount = 0; //only pushback when it hit for other break; } switch( skill_id ) { case CR_GRANDCROSS: case NPC_GRANDDARKNESS: if( battle_config.gx_disptype) dsrc = src; if( src == bl) dmg_type = DMG_ENDURE; else flag|= SD_ANIMATION; break; case NJ_TATAMIGAESHI: //For correct knockback. dsrc = src; flag|= SD_ANIMATION; break; case TK_COUNTER: { //bonus from SG_FRIEND [Komurka] int level; if( sd && sd->status.party_id > 0 && (level = pc_checkskill(sd,SG_FRIEND)) ) party_skill_check(sd, sd->status.party_id, TK_COUNTER,level); } break; case SL_STIN: case SL_STUN: if (skill_lv >= 7) { status_change *sc_cur = status_get_sc(src); if (sc_cur && !sc_cur->getSCE(SC_SMA)) sc_start(src,src,SC_SMA,100,skill_lv,skill_get_time(SL_SMA, skill_lv)); } break; case DK_SERVANT_W_DEMOL:// Only give servant's per target after damage calculation. pc_addservantball( *sd ); break; case KN_PIERCE: case LK_SPIRALPIERCE: case RK_HUNDREDSPEAR: case DK_MADNESS_CRUSHER: if (sc && sc->getSCE(SC_CHARGINGPIERCE)) { if (sc->getSCE(SC_CHARGINGPIERCE_COUNT)) { if (sc->getSCE(SC_CHARGINGPIERCE_COUNT)->val1 < 10) // If the charge count is below 10, add 1. sc_start(src, src, SC_CHARGINGPIERCE_COUNT, 100, sc->getSCE(SC_CHARGINGPIERCE_COUNT)->val1 + 1, skill_get_time2(DK_CHARGINGPIERCE, 1)); else { // If charge count is 10, bonus damage is applied for 1 attack and then the count status ends. clif_specialeffect(bl, 1767, AREA); status_change_end(src, SC_CHARGINGPIERCE_COUNT); } } else // No count status detected? Start charge count at 1. sc_start(src, src, SC_CHARGINGPIERCE_COUNT, 100, 1, skill_get_time2(DK_CHARGINGPIERCE, 1)); } break; } //combo handling skill_combo(src,dsrc,bl,skill_id,skill_lv,tick); //Display damage. switch( skill_id ) { case PA_GOSPEL: //Should look like Holy Cross [Skotlex] dmg.dmotion = clif_skill_damage(src,bl,tick,dmg.amotion,dmg.dmotion, damage, dmg.div_, CR_HOLYCROSS, -1, DMG_SPLASH); break; //Skills that need be passed as a normal attack for the client to display correctly. case HVAN_EXPLOSION: case NPC_SELFDESTRUCTION: if(src->type == BL_PC) dmg.blewcount = 10; dmg.amotion = 0; //Disable delay or attack will do no damage since source is dead by the time it takes effect. [Skotlex] // fall through case KN_AUTOCOUNTER: case NPC_CRITICALSLASH: case TF_DOUBLE: case GS_CHAINACTION: dmg.dmotion = clif_damage(src,bl,tick,dmg.amotion,dmg.dmotion,damage,dmg.div_,dmg.type,dmg.damage2,false); break; case AS_SPLASHER: if( flag&SD_ANIMATION ) // the surrounding targets dmg.dmotion = clif_skill_damage(dsrc,bl,tick, dmg.amotion, dmg.dmotion, damage, dmg.div_, skill_id, -1, DMG_SPLASH); // needs -1 as skill level else // the central target doesn't display an animation dmg.dmotion = clif_skill_damage(dsrc,bl,tick, dmg.amotion, dmg.dmotion, damage, dmg.div_, skill_id, -2, DMG_SPLASH); // needs -2(!) as skill level break; case SR_EARTHSHAKER: dmg.dmotion = clif_skill_damage(src,bl,tick,dmg.amotion,dmg.dmotion,damage,1,skill_id,-2,DMG_SINGLE); break; case WL_SOULEXPANSION: case WL_COMET: case NPC_COMET: case KO_MUCHANAGE: #ifndef RENEWAL case NJ_HUUMA: #endif dmg.dmotion = clif_skill_damage(src,bl,tick,dmg.amotion,dmg.dmotion,damage,dmg.div_,skill_id,skill_lv,DMG_MULTI_HIT); break; case WL_CHAINLIGHTNING_ATK: dmg.dmotion = clif_skill_damage(src,bl,tick,dmg.amotion,dmg.dmotion,damage,1,WL_CHAINLIGHTNING_ATK,-2,DMG_SINGLE); break; case WL_TETRAVORTEX_FIRE: dmg.dmotion = clif_skill_damage(dsrc, bl, tick, dmg.amotion, dmg.dmotion, damage, dmg.div_, WL_TETRAVORTEX_WIND, -1, DMG_SPLASH); break; case LG_SHIELDPRESS: dmg.dmotion = clif_skill_damage(dsrc, bl, tick, status_get_amotion(src), dmg.dmotion, damage, dmg.div_, skill_id, -1, DMG_SINGLE); break; case NPC_EARTHQUAKE: dmg.dmotion = clif_skill_damage(src, bl, tick, dmg.amotion, dmg.dmotion, damage, dmg.div_, skill_id, -1, DMG_ENDURE); break; case NPC_DARKPIERCING: case EL_FIRE_BOMB: case EL_FIRE_BOMB_ATK: case EL_FIRE_WAVE: case EL_FIRE_WAVE_ATK: case EL_FIRE_MANTLE: case EL_CIRCLE_OF_FIRE: case EL_FIRE_ARROW: case EL_ICE_NEEDLE: case EL_WATER_SCREW: case EL_WATER_SCREW_ATK: case EL_WIND_SLASH: case EL_TIDAL_WEAPON: case EL_ROCK_CRUSHER: case EL_ROCK_CRUSHER_ATK: case EL_HURRICANE: case EL_HURRICANE_ATK: case KO_BAKURETSU: case GN_HELLS_PLANT_ATK: case SU_SV_ROOTTWIST_ATK: dmg.dmotion = clif_skill_damage(src,bl,tick,dmg.amotion,dmg.dmotion,damage,dmg.div_,skill_id,-1,DMG_SPLASH); break; case GN_FIRE_EXPANSION_ACID: dmg.dmotion = clif_skill_damage(dsrc, bl, tick, dmg.amotion, dmg.dmotion, damage, dmg.div_, CR_ACIDDEMONSTRATION, skill_lv, DMG_MULTI_HIT); break; case GN_SLINGITEM_RANGEMELEEATK: dmg.dmotion = clif_skill_damage(src,bl,tick,dmg.amotion,dmg.dmotion,damage,dmg.div_,GN_SLINGITEM,-2,DMG_SINGLE); break; case EL_STONE_RAIN: dmg.dmotion = clif_skill_damage(dsrc,bl,tick,dmg.amotion,dmg.dmotion,damage,dmg.div_,skill_id,-1,(flag&1)?DMG_MULTI_HIT:DMG_SPLASH); break; case WM_SEVERE_RAINSTORM_MELEE: dmg.dmotion = clif_skill_damage(src,bl,tick,dmg.amotion,dmg.dmotion,damage,dmg.div_,WM_SEVERE_RAINSTORM,-2,DMG_SPLASH); break; case HT_CLAYMORETRAP: case HT_BLASTMINE: case HT_FLASHER: case HT_FREEZINGTRAP: case RA_CLUSTERBOMB: case RA_FIRINGTRAP: case RA_ICEBOUNDTRAP: dmg.dmotion = clif_skill_damage(src, bl, tick, dmg.amotion, dmg.dmotion, damage, dmg.div_, skill_id, (flag&SD_LEVEL) ? -1 : skill_lv, DMG_SPLASH); if( dsrc != src ) // avoid damage display redundancy break; //Fall through case HT_LANDMINE: dmg.dmotion = clif_skill_damage(dsrc,bl,tick, dmg.amotion, dmg.dmotion, damage, dmg.div_, skill_id, -1, dmg_type); break; case WZ_SIGHTBLASTER: //Sightblaster should never call clif_skill_damage twice dmg.dmotion = clif_skill_damage(src, bl, tick, dmg.amotion, dmg.dmotion, damage, dmg.div_, skill_id, (flag&SD_LEVEL) ? -1 : skill_lv, DMG_SPLASH); break; case RL_R_TRIP_PLUSATK: case RL_S_STORM: dmg.dmotion = clif_skill_damage(dsrc,bl,tick,status_get_amotion(src),dmg.dmotion,damage,dmg.div_,skill_id,-1,DMG_SPLASH); break; case SU_LUNATICCARROTBEAT: case SU_LUNATICCARROTBEAT2: case SP_CURSEEXPLOSION: case SP_SPA: case SP_SHA: if (dmg.div_ < 2) type = DMG_SPLASH; if (!(flag&SD_ANIMATION)) clif_skill_nodamage(dsrc, bl, skill_id, skill_lv, 1); // Fall through case WM_REVERBERATION: dmg.dmotion = clif_skill_damage(dsrc, bl, tick, dmg.amotion, dmg.dmotion, damage, dmg.div_, skill_id, -2, dmg_type); break; case SJ_FALLINGSTAR_ATK: case SJ_FALLINGSTAR_ATK2: dmg.dmotion = clif_skill_damage(src,bl,tick, dmg.amotion, dmg.dmotion, damage, dmg.div_, skill_id, -2, DMG_MULTI_HIT); break; case SJ_NOVAEXPLOSING: dmg.dmotion = clif_skill_damage(dsrc,bl,tick, dmg.amotion, dmg.dmotion, damage, dmg.div_, skill_id, -2, DMG_SINGLE); break; case DK_HACKANDSLASHER_ATK: dmg.dmotion = clif_skill_damage(dsrc, bl, tick, dmg.amotion, dmg.dmotion, damage, dmg.div_, skill_id, -1, dmg_type); break; case AG_STORM_CANNON: case AG_CRIMSON_ARROW: dmg.dmotion = clif_skill_damage(dsrc, bl, tick, dmg.amotion, dmg.dmotion, damage, dmg.div_, skill_id, skill_lv, DMG_SPLASH); break; case TR_ROSEBLOSSOM_ATK: case ABC_FROM_THE_ABYSS_ATK: dmg.dmotion = clif_skill_damage(dsrc, bl, tick, dmg.amotion, dmg.dmotion, damage, dmg.div_, skill_id, -1, DMG_SPLASH); break; case TR_SOUNDBLEND: if (flag&SD_ANIMATION)// For some reason the caster reacts on the splash flag. Best reduce amotion to minimize it for now. [Rytech] dmg.dmotion = clif_skill_damage(dsrc, bl, tick, 10, dmg.dmotion, damage, dmg.div_, skill_id, -1, DMG_SPLASH); else dmg.dmotion = clif_skill_damage(dsrc, bl, tick, dmg.amotion, dmg.dmotion, damage, dmg.div_, skill_id, skill_lv, dmg_type); break; case AB_DUPLELIGHT_MELEE: case AB_DUPLELIGHT_MAGIC: dmg.amotion = 300;/* makes the damage value not overlap with previous damage (when displayed by the client) */ default: if( flag&SD_ANIMATION && dmg.div_ < 2 ) //Disabling skill animation doesn't works on multi-hit. dmg_type = DMG_SPLASH; if (src->type == BL_SKILL) { TBL_SKILL *su = (TBL_SKILL*)src; if (su->group && skill_get_inf2(su->group->skill_id, INF2_ISTRAP)) { // show damage on trap targets clif_skill_damage(src, bl, tick, dmg.amotion, dmg.dmotion, damage, dmg.div_, skill_id, flag&SD_LEVEL ? -1 : skill_lv, DMG_SPLASH); break; } } dmg.dmotion = clif_skill_damage(dsrc,bl,tick, dmg.amotion, dmg.dmotion, damage, dmg.div_, skill_id, flag&SD_LEVEL?-1:skill_lv, dmg_type); break; } map_freeblock_lock(); if (bl->type == BL_PC && skill_id && skill_db.find(skill_id)->copyable.option && //Only copy skill that copyable [Cydh] dmg.flag&BF_SKILL && dmg.damage+dmg.damage2 > 0 && damage < status_get_hp(bl)) //Cannot copy skills if the blow will kill you. [Skotlex] skill_do_copy(src,bl,skill_id,skill_lv); if (dmg.dmg_lv >= ATK_MISS && (type = skill_get_walkdelay(skill_id, skill_lv)) > 0) { //Skills with can't walk delay also stop normal attacking for that //duration when the attack connects. [Skotlex] struct unit_data *ud = unit_bl2ud(src); if (ud && DIFF_TICK(ud->attackabletime, tick + type) < 0) ud->attackabletime = tick + type; } shadow_flag = skill_check_shadowform(bl, damage, dmg.div_); // Instant damage if( !dmg.amotion ) { if( (!tsc || (!tsc->getSCE(SC_DEVOTION) && skill_id != CR_REFLECTSHIELD && !tsc->getSCE(SC_WATER_SCREEN_OPTION)) #ifndef RENEWAL || skill_id == HW_GRAVITATION #endif || skill_id == NPC_EVILLAND) && !shadow_flag ) status_fix_damage(src,bl,damage,dmg.dmotion,skill_id); //Deal damage before knockback to allow stuff like firewall+storm gust combo. if( !status_isdead(bl) && additional_effects ) skill_additional_effect(src,bl,skill_id,skill_lv,dmg.flag,dmg.dmg_lv,tick); if( damage > 0 ) //Counter status effects [Skotlex] skill_counter_additional_effect(src,bl,skill_id,skill_lv,dmg.flag,tick); } // Blow! if (!(flag&4)) skill_attack_blow(src, dsrc, bl, (uint8)dmg.blewcount, skill_id, skill_lv, damage, tick, flag); // Delayed damage must be dealt after the knockback (it needs to know actual position of target) if( dmg.amotion ) { if( shadow_flag ) { if( !status_isdead(bl) && additional_effects ) skill_additional_effect(src, bl, skill_id, skill_lv, dmg.flag, dmg.dmg_lv, tick); if( dmg.flag > ATK_BLOCK ) skill_counter_additional_effect(src, bl, skill_id, skill_lv, dmg.flag, tick); } else battle_delay_damage(tick, dmg.amotion,src,bl,dmg.flag,skill_id,skill_lv,damage,dmg.dmg_lv,dmg.dmotion, additional_effects, false); } else { // Trigger monster skill condition for damage skills with no amotion. if (bl->type == BL_MOB && src != bl && !status_isdead(bl)) { if (damage > 0) mobskill_event(BL_CAST(BL_MOB, bl), src, tick, dmg.flag); if (skill_id > 0) mobskill_event(BL_CAST(BL_MOB, bl), src, tick, MSC_SKILLUSED | (skill_id << 16)); } } // Trigger monster skill condition for damage skills. if (bl->type == BL_MOB && src != bl && !status_isdead(bl)) { if (damage > 0) mobskill_event(BL_CAST(BL_MOB, bl), src, tick, dmg.flag, damage); if (skill_id > 0) mobskill_event(BL_CAST(BL_MOB, bl), src, tick, MSC_SKILLUSED | (skill_id << 16), damage); } if (tsc && skill_id != NPC_EVILLAND && skill_id != SP_SOULEXPLOSION && skill_id != SJ_NOVAEXPLOSING #ifndef RENEWAL && skill_id != PA_PRESSURE && skill_id != HW_GRAVITATION #endif ) { if (tsc->getSCE(SC_DEVOTION)) { struct status_change_entry *sce = tsc->getSCE(SC_DEVOTION); struct block_list *d_bl = map_id2bl(sce->val1); if (d_bl && ( (d_bl->type == BL_MER && ((TBL_MER*)d_bl)->master && ((TBL_MER*)d_bl)->master->bl.id == bl->id) || (d_bl->type == BL_PC && ((TBL_PC*)d_bl)->devotion[sce->val2] == bl->id) ) && check_distance_bl(bl, d_bl, sce->val3) ) { int64 devotion_damage = damage; // Needed to check the devotion master for Rebound Shield status. status_change *d_sc = status_get_sc(d_bl); if (d_sc && d_sc->getSCE(SC_REBOUND_S)) devotion_damage -= devotion_damage * d_sc->getSCE(SC_REBOUND_S)->val2 / 100; if (!rmdamage) { clif_damage(d_bl, d_bl, gettick(), 0, 0, devotion_damage, 0, DMG_NORMAL, 0, false); status_fix_damage(NULL, d_bl, devotion_damage, 0, 0); } else { bool isDevotRdamage = false; if (battle_config.devotion_rdamage && battle_config.devotion_rdamage > rnd()%100) isDevotRdamage = true; // If !isDevotRdamage, reflected magics are done directly on the target not on paladin // This check is only for magical skill. // For BF_WEAPON skills types track var rdamage and function battle_calc_return_damage clif_damage(bl, (!isDevotRdamage) ? bl : d_bl, gettick(), 0, 0, devotion_damage, 0, DMG_NORMAL, 0, false); status_fix_damage(bl, (!isDevotRdamage) ? bl : d_bl, devotion_damage, 0, 0); } } else { status_change_end(bl, SC_DEVOTION); if (!dmg.amotion) status_fix_damage(src, bl, damage, dmg.dmotion, 0); } } if (tsc->getSCE(SC_WATER_SCREEN_OPTION)) { struct status_change_entry *sce = tsc->getSCE(SC_WATER_SCREEN_OPTION); struct block_list *e_bl = map_id2bl(sce->val1); if (e_bl) { if (!rmdamage) { clif_skill_damage(e_bl, e_bl, gettick(), 0, 0, damage, dmg.div_, skill_id, -1, skill_get_hit(skill_id)); status_fix_damage(NULL, e_bl, damage, 0, 0); } else { clif_skill_damage(bl, bl, gettick(), 0, 0, damage, dmg.div_, skill_id, -1, skill_get_hit(skill_id)); status_fix_damage(bl, bl, damage, 0, 0); } } } } if(damage > 0 && !status_has_mode(tstatus,MD_STATUSIMMUNE)) { if( skill_id == RG_INTIMIDATE ) { int rate = 50 + skill_lv * 5; rate = rate + (status_get_lv(src) - status_get_lv(bl)); if(rnd()%100 < rate) skill_addtimerskill(src,tick + 800,bl->id,0,0,skill_id,skill_lv,0,flag); } else if( skill_id == NPC_FATALMENACE ) { int16 x = skill_area_temp[4], y = skill_area_temp[5]; map_search_freecell(nullptr, bl->m, &x, &y, 2, 2, 1); skill_addtimerskill(bl,tick + 800,bl->id,x,y,skill_id,skill_lv,0,flag); } } if(skill_id == CR_GRANDCROSS || skill_id == NPC_GRANDDARKNESS) dmg.flag |= BF_WEAPON; if( sd && src != bl && damage > 0 && ( dmg.flag&BF_WEAPON || (dmg.flag&BF_MISC && (skill_id == RA_CLUSTERBOMB || skill_id == RA_FIRINGTRAP || skill_id == RA_ICEBOUNDTRAP)) ) ) { if (battle_config.left_cardfix_to_right) battle_drain(sd, bl, dmg.damage, dmg.damage, tstatus->race, tstatus->class_); else battle_drain(sd, bl, dmg.damage, dmg.damage2, tstatus->race, tstatus->class_); } if (sd && tsc && dmg.flag&BF_LONG && tsc->getSCE(SC_WINDSIGN) && rand()%100 < tsc->getSCE(SC_WINDSIGN)->val2) status_heal(src, 0, 0, 1, 0); if( damage > 0 ) { // Post-damage effects switch( skill_id ) { case GC_VENOMPRESSURE: { status_change *ssc = status_get_sc(src); if( ssc && ssc->getSCE(SC_POISONINGWEAPON) && rnd()%100 < 70 + 5*skill_lv ) { sc_start4(src, bl, (sc_type)ssc->getSCE(SC_POISONINGWEAPON)->val2, 100, ssc->getSCE(SC_POISONINGWEAPON)->val1, 0, 1, 0, skill_get_time2(GC_POISONINGWEAPON, 1)); status_change_end(src,SC_POISONINGWEAPON); clif_skill_nodamage(src,bl,skill_id,skill_lv,1); } } break; } if( sd ) skill_onskillusage(sd, bl, skill_id, tick); } if (!(flag&2)) { switch (skill_id) { case MG_COLDBOLT: case MG_FIREBOLT: case MG_LIGHTNINGBOLT: if (sc && sc->getSCE(SC_DOUBLECAST) && rnd() % 100 < sc->getSCE(SC_DOUBLECAST)->val2) //skill_addtimerskill(src, tick + dmg.div_*dmg.amotion, bl->id, 0, 0, skill_id, skill_lv, BF_MAGIC, flag|2); skill_addtimerskill(src, tick + dmg.amotion, bl->id, 0, 0, skill_id, skill_lv, BF_MAGIC, flag|2); break; case SU_BITE: case SU_SCRATCH: case SU_SV_STEMSPEAR: case SU_SCAROFTAROU: case SU_PICKYPECK: if (status_get_lv(src) > 29 && rnd() % 100 < 10 * status_get_lv(src) / 30) skill_addtimerskill(src, tick + dmg.amotion + skill_get_delay(skill_id, skill_lv), bl->id, 0, 0, skill_id, skill_lv, attack_type, flag|2); break; case ABC_DEFT_STAB: if (skill_area_temp[1] == bl->id && rnd()%100 < 4 * skill_lv)// Need official autocast chance. [Rytech] skill_addtimerskill(src, tick + dmg.amotion, bl->id, 0, 0, skill_id, skill_lv, BF_WEAPON, 2); break; case ABC_FRENZY_SHOT: if (rnd()%100 < 4 * skill_lv)// Need official autocast chance. [Rytech] skill_addtimerskill(src, tick + dmg.amotion, bl->id, 0, 0, skill_id, skill_lv, BF_WEAPON, 2); break; } } map_freeblock_unlock(); if ((flag&0x1000000) && rmdamage) return 0; //Should return 0 when damage was reflected return damage; } /*========================================== * Sub function for recursive skill call. * Checking bl battle flag and display damage * then call func with source,target,skill_id,skill_lv,tick,flag *------------------------------------------*/ typedef int (*SkillFunc)(struct block_list *, struct block_list *, int, int, t_tick, int); int skill_area_sub(struct block_list *bl, va_list ap) { struct block_list *src; uint16 skill_id,skill_lv; int flag; t_tick tick; SkillFunc func; nullpo_ret(bl); src = va_arg(ap,struct block_list *); skill_id = va_arg(ap,int); skill_lv = va_arg(ap,int); tick = va_arg(ap,t_tick); flag = va_arg(ap,int); func = va_arg(ap,SkillFunc); if (flag&BCT_WOS && src == bl) return 0; if(battle_check_target(src,bl,flag) > 0) { // several splash skills need this initial dummy packet to display correctly if (flag&SD_PREAMBLE && skill_area_temp[2] == 0) clif_skill_damage(src,bl,tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, DMG_SINGLE); if (flag&(SD_SPLASH|SD_PREAMBLE)) skill_area_temp[2]++; return func(src,bl,skill_id,skill_lv,tick,flag); } return 0; } static int skill_check_unit_range_sub(struct block_list *bl, va_list ap) { struct skill_unit *unit; uint16 skill_id,g_skill_id; unit = (struct skill_unit *)bl; if(bl->prev == NULL || bl->type != BL_SKILL) return 0; if(!unit->alive) return 0; skill_id = va_arg(ap,int); g_skill_id = unit->group->skill_id; switch (skill_id) { case AL_PNEUMA: //Pneuma doesn't work even if just one cell overlaps with Land Protector if(g_skill_id == SA_LANDPROTECTOR) break; //Fall through case MH_STEINWAND: case MG_SAFETYWALL: case SC_MAELSTROM: if(g_skill_id != MH_STEINWAND && g_skill_id != MG_SAFETYWALL && g_skill_id != AL_PNEUMA && g_skill_id != SC_MAELSTROM) return 0; break; case AL_WARP: case HT_SKIDTRAP: case MA_SKIDTRAP: case HT_LANDMINE: case MA_LANDMINE: case HT_ANKLESNARE: case HT_SHOCKWAVE: case HT_SANDMAN: case MA_SANDMAN: case HT_FLASHER: case HT_FREEZINGTRAP: case MA_FREEZINGTRAP: case HT_BLASTMINE: case HT_CLAYMORETRAP: case HT_TALKIEBOX: #ifndef RENEWAL case HP_BASILICA: #endif case RA_ELECTRICSHOCKER: case RA_CLUSTERBOMB: case RA_MAGENTATRAP: case RA_COBALTTRAP: case RA_MAIZETRAP: case RA_VERDURETRAP: case RA_FIRINGTRAP: case RA_ICEBOUNDTRAP: case SC_DIMENSIONDOOR: case SC_BLOODYLUST: case NPC_REVERBERATION: case GN_THORNS_TRAP: case RL_B_TRAP: case SC_ESCAPE: //Non stackable on themselves and traps (including venom dust which does not has the trap inf2 set) if (skill_id != g_skill_id && !skill_get_inf2(g_skill_id, INF2_ISTRAP) && g_skill_id != AS_VENOMDUST && g_skill_id != MH_POISON_MIST) return 0; break; default: //Avoid stacking with same kind of trap. [Skotlex] if (g_skill_id != skill_id) return 0; break; } return 1; } static int skill_check_unit_range (struct block_list *bl, int x, int y, uint16 skill_id, uint16 skill_lv) { //Non players do not check for the skill's splash-trigger area. int range = bl->type==BL_PC?skill_get_unit_range(skill_id, skill_lv):0; int layout_type = skill_get_unit_layout_type(skill_id,skill_lv); if (layout_type==-1 || layout_type>MAX_SQUARE_LAYOUT) { ShowError("skill_check_unit_range: unsupported layout type %d for skill %d\n",layout_type,skill_id); return 0; } range += layout_type; return map_foreachinallarea(skill_check_unit_range_sub,bl->m,x-range,y-range,x+range,y+range,BL_SKILL,skill_id); } static int skill_check_unit_range2_sub (struct block_list *bl, va_list ap) { uint16 skill_id; if(bl->prev == NULL) return 0; skill_id = va_arg(ap,int); if( status_isdead(bl) && skill_id != AL_WARP ) return 0; #ifndef RENEWAL if( skill_id == HP_BASILICA && bl->type == BL_PC ) return 0; #endif if( skill_id == AM_DEMONSTRATION && bl->type == BL_MOB && ((TBL_MOB*)bl)->mob_id == MOBID_EMPERIUM ) return 0; //Allow casting Bomb/Demonstration Right under emperium [Skotlex] return 1; } /** * Used to check range condition of the casted skill. Used if the skill has UF_NOFOOTSET or INF2_DISABLENEARNPC * @param bl Object that casted skill * @param x Position x of the target * @param y Position y of the target * @param skill_id The casted skill * @param skill_lv The skill Lv * @param isNearNPC 'true' means, check the range between target and nearer NPC by using npc_isnear and range calculation [Cydh] * @return 0: No object (BL_CHAR or BL_PC) within the range. If 'isNearNPC' the target oject is BL_NPC */ static int skill_check_unit_range2 (struct block_list *bl, int x, int y, uint16 skill_id, uint16 skill_lv, bool isNearNPC) { int range = 0, type; //Range for INF2_DISABLENEARNPC is using skill splash value [Cydh] if (isNearNPC) range = skill_get_splash(skill_id,skill_lv); //While checking INF2_DISABLENEARNPC and the range from splash is 0, get the range from skill_unit range and layout. [Cydh] if (!isNearNPC || !range) { switch (skill_id) { // to be expanded later case WZ_ICEWALL: range = 2; break; case SC_MANHOLE: range = 0; break; default: { int layout_type = skill_get_unit_layout_type(skill_id,skill_lv); if (layout_type == -1 || layout_type > MAX_SQUARE_LAYOUT) { ShowError("skill_check_unit_range2: unsupported layout type %d for skill %d\n",layout_type,skill_id); return 0; } range = skill_get_unit_range(skill_id,skill_lv) + layout_type; } break; } } uint16 skill_npc_range = skill_db.find(skill_id)->unit_nonearnpc_range; //Check the additional range [Cydh] if (isNearNPC && skill_npc_range > 0) range += skill_npc_range; if (!isNearNPC) { //Doesn't check the NPC range //If the caster is a monster/NPC, only check for players. Otherwise just check characters if (bl->type&battle_config.skill_nofootset) type = BL_CHAR; else if(bl->type == BL_MOB) type = BL_MOB; //Monsters can never place traps on top of each other regardless of setting else return 0; //Don't check } else type = BL_NPC; return (!isNearNPC) ? //!isNearNPC is used for UF_NOFOOTSET, regardless the NPC position, only check the BL_CHAR or BL_PC map_foreachinallarea(skill_check_unit_range2_sub,bl->m,x - range,y - range,x + range,y + range,type,skill_id): //isNearNPC is used to check range from NPC map_foreachinallarea(npc_isnear_sub,bl->m,x - range,y - range,x + range,y + range,type,skill_id); } /*========================================== * Checks that you have the requirements for casting a skill for homunculus/mercenary. * Flag: * &1: finished casting the skill (invoke hp/sp/item consumption) * &2: picked menu entry (Warp Portal, Teleport and other menu based skills) *------------------------------------------*/ static int skill_check_condition_mercenary(struct block_list *bl, uint16 skill_id, uint16 skill_lv, int type) { struct status_data *status; map_session_data *sd = NULL; int i, hp, sp, hp_rate, sp_rate, state, mhp; t_itemid itemid[MAX_SKILL_ITEM_REQUIRE]; int amount[ARRAYLENGTH(itemid)], index[ARRAYLENGTH(itemid)]; nullpo_retr(0, bl); switch( bl->type ) { case BL_HOM: sd = ((TBL_HOM*)bl)->master; break; case BL_MER: sd = ((TBL_MER*)bl)->master; break; } status = status_get_status_data(bl); skill_lv = cap_value(skill_lv, 1, MAX_SKILL_LEVEL); std::shared_ptr<s_skill_db> skill = skill_db.find(skill_id); if (skill == nullptr) return 0; // Requirements for( i = 0; i < ARRAYLENGTH(itemid); i++ ) { itemid[i] = skill->require.itemid[i]; amount[i] = skill->require.amount[i]; } hp = skill->require.hp[skill_lv - 1]; sp = skill->require.sp[skill_lv - 1]; hp_rate = skill->require.hp_rate[skill_lv - 1]; sp_rate = skill->require.sp_rate[skill_lv - 1]; state = skill->require.state; if ((mhp = skill->require.mhp[skill_lv - 1]) > 0) hp += (status->max_hp * mhp) / 100; if( hp_rate > 0 ) hp += (status->hp * hp_rate) / 100; else hp += (status->max_hp * (-hp_rate)) / 100; if( sp_rate > 0 ) sp += (status->sp * sp_rate) / 100; else sp += (status->max_sp * (-sp_rate)) / 100; if( !(type&2) ) { if( hp > 0 && status->hp <= (unsigned int)hp ) { clif_skill_fail(sd, skill_id, USESKILL_FAIL_HP_INSUFFICIENT, 0); return 0; } if( sp > 0 && status->sp <= (unsigned int)sp ) { clif_skill_fail(sd, skill_id, USESKILL_FAIL_SP_INSUFFICIENT, 0); return 0; } } if( !type ) switch( state ) { case ST_MOVE_ENABLE: if( !unit_can_move(bl) ) { clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); return 0; } break; } if( !(type&1) ) return 1; // Check item existences for( i = 0; i < ARRAYLENGTH(itemid); i++ ) { index[i] = -1; if( itemid[i] == 0 ) continue; // No item index[i] = pc_search_inventory(sd, itemid[i]); if( index[i] < 0 || sd->inventory.u.items_inventory[index[i]].amount < amount[i] ) { clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); return 0; } } // Consume items for( i = 0; i < ARRAYLENGTH(itemid); i++ ) { if( index[i] >= 0 ) pc_delitem(sd, index[i], amount[i], 0, 1, LOG_TYPE_CONSUME); } if( type&2 ) return 1; if( sp || hp ) status_zap(bl, hp, sp); return 1; } /*========================================== * *------------------------------------------*/ int skill_area_sub_count (struct block_list *src, struct block_list *target, uint16 skill_id, uint16 skill_lv, t_tick tick, int flag) { switch (skill_id) { case RL_QD_SHOT: { if (src->type == BL_PC && BL_CAST(BL_PC,src)) { struct unit_data *ud = unit_bl2ud(src); if (ud && ud->target == target->id) return 1; } } } return 1; } /*========================================== * *------------------------------------------*/ static TIMER_FUNC(skill_timerskill){ struct block_list *src = map_id2bl(id),*target; struct unit_data *ud = unit_bl2ud(src); struct skill_timerskill *skl; struct skill_unit *unit = NULL; int range; nullpo_ret(src); nullpo_ret(ud); skl = ud->skilltimerskill[data]; nullpo_ret(skl); ud->skilltimerskill[data] = NULL; do { if(src->prev == NULL) break; // Source not on Map if(skl->target_id) { target = map_id2bl(skl->target_id); if( ( skl->skill_id == RG_INTIMIDATE || skl->skill_id == NPC_FATALMENACE ) && (!target || target->prev == NULL || !check_distance_bl(src,target,AREA_SIZE)) ) target = src; //Required since it has to warp. if (skl->skill_id == SR_SKYNETBLOW) { skill_area_temp[1] = 0; clif_skill_damage(src,src,tick,status_get_amotion(src),0,-30000,1,skl->skill_id,skl->skill_lv,DMG_SINGLE); map_foreachinallrange(skill_area_sub,src,skill_get_splash(skl->skill_id,skl->skill_lv),BL_CHAR|BL_SKILL,src, skl->skill_id,skl->skill_lv,tick,skl->flag|BCT_ENEMY|SD_SPLASH|1,skill_castend_damage_id); break; } if(target == NULL) break; // Target offline? if(target->prev == NULL) break; // Target not on Map if(src->m != target->m) break; // Different Maps if(status_isdead(src)) { switch(skl->skill_id) { case WL_CHAINLIGHTNING_ATK: case WL_TETRAVORTEX_FIRE: case WL_TETRAVORTEX_WATER: case WL_TETRAVORTEX_WIND: case WL_TETRAVORTEX_GROUND: // For SR_FLASHCOMBO case SR_DRAGONCOMBO: case SR_FALLENEMPIRE: case SR_TIGERCANNON: case NPC_DANCINGBLADE_ATK: if (src->type != BL_PC) continue; break; // Exceptions default: continue; // Caster is Dead } } if(status_isdead(target) && skl->skill_id != RG_INTIMIDATE && skl->skill_id != WZ_WATERBALL) break; switch(skl->skill_id) { case KN_AUTOCOUNTER: clif_skill_nodamage(src,target,skl->skill_id,skl->skill_lv,1); break; case RG_INTIMIDATE: if (unit_warp(src,-1,-1,-1,CLR_TELEPORT) == 0) { short x,y; map_search_freecell(src, 0, &x, &y, 1, 1, 0); if (target != src && !status_isdead(target)) unit_warp(target, -1, x, y, CLR_TELEPORT); } break; case BA_FROSTJOKER: case DC_SCREAM: range= skill_get_splash(skl->skill_id, skl->skill_lv); map_foreachinallarea(skill_frostjoke_scream,skl->map,skl->x-range,skl->y-range, skl->x+range,skl->y+range,BL_CHAR,src,skl->skill_id,skl->skill_lv,tick); break; case PR_LEXDIVINA: if (src->type == BL_MOB) { // Monsters use the default duration when casting Lex Divina sc_start(src, target, SC_SILENCE, skl->type, skl->skill_lv, skill_get_time2(status_db.getSkill(SC_SILENCE), 1)); break; } sc_start(src, target, SC_SILENCE, skl->type, skl->skill_lv, skill_get_time2(skl->skill_id, skl->skill_lv)); break; case PR_STRECOVERY: sc_start(src, target, SC_BLIND, skl->type, skl->skill_lv, skill_get_time2(skl->skill_id, skl->skill_lv)); break; case BS_HAMMERFALL: sc_start(src, target, SC_STUN, skl->type, skl->skill_lv, skill_get_time2(skl->skill_id, skl->skill_lv)); break; case MER_LEXDIVINA: sc_start(src, target, SC_SILENCE, skl->type, skl->skill_lv, skill_get_time2(skl->skill_id, skl->skill_lv)); break; case WZ_WATERBALL: { //Get the next waterball cell to consume struct s_skill_unit_layout *layout; int i; layout = skill_get_unit_layout(skl->skill_id, skl->skill_lv, src, skl->x, skl->y); for (i = skl->type; i >= 0 && i < layout->count; i++) { int ux = skl->x + layout->dx[i]; int uy = skl->y + layout->dy[i]; unit = map_find_skill_unit_oncell(src, ux, uy, WZ_WATERBALL, NULL, 0); if (unit) break; } } // Fall through case WZ_JUPITEL: // Official behaviour is to hit as long as there is a line of sight, regardless of distance if (skl->type > 0 && !status_isdead(target) && path_search_long(NULL,src->m,src->x,src->y,target->x,target->y,CELL_CHKWALL)) { // Apply canact delay here to prevent hacks (unlimited casting) ud->canact_tick = i64max(tick + status_get_amotion(src), ud->canact_tick); skill_attack(BF_MAGIC, src, src, target, skl->skill_id, skl->skill_lv, tick, skl->flag); } if (unit && !status_isdead(target) && !status_isdead(src)) { skill_delunit(unit); // Consume unit for next waterball //Timer will continue and walkdelay set until target is dead, even if there is currently no line of sight unit_set_walkdelay(src, tick, TIMERSKILL_INTERVAL, 1); skill_addtimerskill(src,tick+TIMERSKILL_INTERVAL,target->id,skl->x,skl->y,skl->skill_id,skl->skill_lv,skl->type+1,skl->flag); } else { status_change *sc = status_get_sc(src); if(sc) { if(sc->getSCE(SC_SPIRIT) && sc->getSCE(SC_SPIRIT)->val2 == SL_WIZARD && sc->getSCE(SC_SPIRIT)->val3 == skl->skill_id) sc->getSCE(SC_SPIRIT)->val3 = 0; //Clear bounced spell check. } } break; case NPC_DANCINGBLADE_ATK: skill_attack(BF_WEAPON,src,src,target,skl->skill_id,skl->skill_lv,tick,skl->flag); if (skl->type < 4) { struct block_list *nbl = NULL; nbl = battle_getenemyarea(src, target->x, target->y, 5, splash_target(src), src->id); skill_addtimerskill(src, tick + 650, (nbl?nbl:target)->id, skl->x, 0, NPC_DANCINGBLADE_ATK, skl->skill_lv, skl->type + 1, 0); } break; case WL_CHAINLIGHTNING_ATK: { #ifndef RENEWAL skill_toggle_magicpower(src, skl->skill_id); // Only the first hit will be amplified #endif skill_attack(BF_MAGIC,src,src,target,skl->skill_id,skl->skill_lv,tick,9 - skl->type); // Hit a Lightning on the current Target if( skl->type < (4 + skl->skill_lv - 1) && skl->x < 3 ) { // Remaining Chains Hit struct block_list *nbl = NULL; // Next Target of Chain nbl = battle_getenemyarea(src, target->x, target->y, (skl->type>2)?2:3, // After 2 bounces, it will bounce to other targets in 7x7 range. splash_target(src), target->id); // Search for a new Target around current one... if( nbl == NULL ) skl->x++; else skl->x = 0; skill_addtimerskill(src, tick + 650, (nbl?nbl:target)->id, skl->x, 0, WL_CHAINLIGHTNING_ATK, skl->skill_lv, skl->type + 1, 0); } } break; case WL_TETRAVORTEX_FIRE: case WL_TETRAVORTEX_WATER: case WL_TETRAVORTEX_WIND: case WL_TETRAVORTEX_GROUND: clif_skill_nodamage(src,target,skl->skill_id,skl->skill_lv,1); skill_attack(BF_MAGIC,src,src,target,skl->skill_id,skl->skill_lv,tick,skl->flag|SD_LEVEL|SD_ANIMATION); if (skl->type >= 3) { // Final Hit if (!status_isdead(target)) { // Final Status Effect int effects[4] = { SC_BURNING, SC_FREEZING, SC_BLEEDING, SC_STUN }, applyeffects[4] = { 0, 0, 0, 0 }, i, j = 0, k = 0; for(i = 1; i <= 8; i = i + i) { if (skl->x&i) { applyeffects[j] = effects[k]; j++; } k++; } if (j) { i = applyeffects[rnd()%j]; sc_start(src, target, static_cast<sc_type>(i), 100, skl->skill_lv, (i == SC_BURNING ? 18000 : (i == SC_FREEZING ? 27000 : (i == SC_BLEEDING ? 108000 : 4500)))); } } } break; case NPC_REVERBERATION_ATK: skill_castend_damage_id(src,target,skl->skill_id,skl->skill_lv,tick,skl->flag|SD_LEVEL|SD_ANIMATION); break; case NPC_FATALMENACE: unit_warp(src, -1, skl->x, skl->y, CLR_TELEPORT); break; case SR_WINDMILL: if( target->type == BL_PC ) { map_session_data *tsd = NULL; if( (tsd = ((TBL_PC*)target)) && !pc_issit(tsd) ) { pc_setsit(tsd); skill_sit(tsd, true); clif_sitting(&tsd->bl); } } break; case SR_KNUCKLEARROW: skill_attack(BF_WEAPON, src, src, target, skl->skill_id, skl->skill_lv, tick, skl->flag|SD_LEVEL); break; case CH_PALMSTRIKE: { status_change* tsc = status_get_sc(target); status_change* sc = status_get_sc(src); if( ( tsc && tsc->option&OPTION_HIDE ) || ( sc && sc->option&OPTION_HIDE ) ){ skill_blown(src,target,skill_get_blewcount(skl->skill_id, skl->skill_lv), -1, BLOWN_NONE); break; } skill_attack(skl->type,src,src,target,skl->skill_id,skl->skill_lv,tick,skl->flag); break; } // For SR_FLASHCOMBO case SR_DRAGONCOMBO: case SR_FALLENEMPIRE: case SR_TIGERCANNON: if( src->type == BL_PC ) { if( distance_xy(src->x, src->y, target->x, target->y) >= 3 ) break; skill_castend_damage_id(src, target, skl->skill_id, pc_checkskill(((TBL_PC *)src), skl->skill_id), tick, 0); } break; case SU_SV_ROOTTWIST_ATK: { status_change *tsc = status_get_sc(target); if (tsc && tsc->getSCE(SC_SV_ROOTTWIST)) { if (check_distance_bl(src, target, 32)) // Only damage if caster is within 32x32 area skill_attack(skl->type, src, target, target, skl->skill_id, skl->skill_lv, tick, skl->flag); skill_addtimerskill(src, tick + 1000, target->id, 0, 0, skl->skill_id, skl->skill_lv, skl->type, skl->flag); } } break; case NPC_PULSESTRIKE2: skill_castend_damage_id(src,target,skl->skill_id,skl->skill_lv,tick,skl->flag); break; case ABC_DEFT_STAB: case ABC_FRENZY_SHOT: skill_castend_damage_id(src, target, skl->skill_id, skl->skill_lv, tick, skl->flag); break; default: skill_attack(skl->type,src,src,target,skl->skill_id,skl->skill_lv,tick,skl->flag); break; } } else { if(src->m != skl->map) break; switch( skl->skill_id ) { case GN_CRAZYWEED_ATK: { int dummy = 1, i = skill_get_unit_range(skl->skill_id,skl->skill_lv); map_foreachinarea(skill_cell_overlap, src->m, skl->x-i, skl->y-i, skl->x+i, skl->y+i, BL_SKILL, skl->skill_id, &dummy, src); } case WL_EARTHSTRAIN: skill_unitsetting(src,skl->skill_id,skl->skill_lv,skl->x,skl->y,(skl->type<<16)|skl->flag); break; case RL_FIRE_RAIN: { int dummy = 1, i = skill_get_splash(skl->skill_id,skl->skill_lv); if (rnd() % 100 < (15 + 5 * skl->skill_lv)) map_foreachinallarea(skill_cell_overlap,src->m,skl->x-i,skl->y-i,skl->x+i,skl->y+i,BL_SKILL,skl->skill_id,&dummy,src); skill_unitsetting(src,skl->skill_id,skl->skill_lv,skl->x,skl->y,0); } break; case NPC_MAGMA_ERUPTION: case NC_MAGMA_ERUPTION: skill_unitsetting(src,skl->skill_id,skl->skill_lv,skl->x,skl->y,0); break; } } } while (0); //Free skl now that it is no longer needed. ers_free(skill_timer_ers, skl); return 0; } /*========================================== * *------------------------------------------*/ int skill_addtimerskill (struct block_list *src, t_tick tick, int target, int x,int y, uint16 skill_id, uint16 skill_lv, int type, int flag) { int i; struct unit_data *ud; nullpo_retr(1, src); if (src->prev == NULL) return 0; ud = unit_bl2ud(src); nullpo_retr(1, ud); ARR_FIND( 0, MAX_SKILLTIMERSKILL, i, ud->skilltimerskill[i] == 0 ); if( i == MAX_SKILLTIMERSKILL ) return 1; ud->skilltimerskill[i] = ers_alloc(skill_timer_ers, struct skill_timerskill); ud->skilltimerskill[i]->timer = add_timer(tick, skill_timerskill, src->id, i); ud->skilltimerskill[i]->src_id = src->id; ud->skilltimerskill[i]->target_id = target; ud->skilltimerskill[i]->skill_id = skill_id; ud->skilltimerskill[i]->skill_lv = skill_lv; ud->skilltimerskill[i]->map = src->m; ud->skilltimerskill[i]->x = x; ud->skilltimerskill[i]->y = y; ud->skilltimerskill[i]->type = type; ud->skilltimerskill[i]->flag = flag; return 0; } /*========================================== * *------------------------------------------*/ int skill_cleartimerskill (struct block_list *src) { int i; struct unit_data *ud; nullpo_ret(src); ud = unit_bl2ud(src); nullpo_ret(ud); for(i=0;i<MAX_SKILLTIMERSKILL;i++) { if(ud->skilltimerskill[i]) { switch(ud->skilltimerskill[i]->skill_id) { case WL_TETRAVORTEX_FIRE: case WL_TETRAVORTEX_WATER: case WL_TETRAVORTEX_WIND: case WL_TETRAVORTEX_GROUND: // For SR_FLASHCOMBO case SR_DRAGONCOMBO: case SR_FALLENEMPIRE: case SR_TIGERCANNON: if (src->type != BL_PC) break; continue; } delete_timer(ud->skilltimerskill[i]->timer, skill_timerskill); ers_free(skill_timer_ers, ud->skilltimerskill[i]); ud->skilltimerskill[i]=NULL; } } return 1; } static int skill_active_reverberation(struct block_list *bl, va_list ap) { skill_unit *su = (skill_unit*)bl; nullpo_ret(su); if (bl->type != BL_SKILL) return 0; std::shared_ptr<s_skill_unit_group> sg = su->group; if (su->alive && sg && sg->skill_id == NPC_REVERBERATION) { map_foreachinallrange(skill_trap_splash, bl, skill_get_splash(sg->skill_id, sg->skill_lv), sg->bl_flag, bl, gettick()); su->limit = DIFF_TICK(gettick(), sg->tick); sg->unit_id = UNT_USED_TRAPS; } return 1; } /** * Reveal hidden trap **/ static int skill_reveal_trap(struct block_list *bl, va_list ap) { TBL_SKILL *su = (TBL_SKILL*)bl; if (su->alive && su->group && su->hidden && skill_get_inf2(su->group->skill_id, INF2_ISTRAP)) { //Change look is not good enough, the client ignores it as an actual trap still. [Skotlex] //clif_changetraplook(bl, su->group->unit_id); su->hidden = false; skill_getareachar_skillunit_visibilty(su, AREA); return 1; } return 0; } /** * Attempt to reveal trap in area * @param src Skill caster * @param range Affected range * @param x * @param y * TODO: Remove hardcode usages for this function **/ void skill_reveal_trap_inarea(struct block_list *src, int range, int x, int y) { if (!battle_config.traps_setting) return; nullpo_retv(src); map_foreachinallarea(skill_reveal_trap, src->m, x-range, y-range, x+range, y+range, BL_SKILL); } /*========================================== [Playtester] * Process tarot card's effects * @param src: Source of the tarot card effect * @param target: Target of the tartor card effect * @param skill_id: ID of the skill used * @param skill_lv: Level of the skill used * @param tick: Processing tick time * @return Card number *------------------------------------------*/ static int skill_tarotcard(struct block_list* src, struct block_list *target, uint16 skill_id, uint16 skill_lv, t_tick tick) { int card = 0; if (battle_config.tarotcard_equal_chance) { //eAthena equal chances card = rnd() % 14 + 1; } else { //Official chances int rate = rnd() % 100; if (rate < 10) card = 1; // THE FOOL else if (rate < 20) card = 2; // THE MAGICIAN else if (rate < 30) card = 3; // THE HIGH PRIESTESS else if (rate < 37) card = 4; // THE CHARIOT else if (rate < 47) card = 5; // STRENGTH else if (rate < 62) card = 6; // THE LOVERS else if (rate < 63) card = 7; // WHEEL OF FORTUNE else if (rate < 69) card = 8; // THE HANGED MAN else if (rate < 74) card = 9; // DEATH else if (rate < 82) card = 10; // TEMPERANCE else if (rate < 83) card = 11; // THE DEVIL else if (rate < 85) card = 12; // THE TOWER else if (rate < 90) card = 13; // THE STAR else card = 14; // THE SUN } switch (card) { case 1: // THE FOOL - heals SP to 0 { status_percent_damage(src, target, 0, 100, false); break; } case 2: // THE MAGICIAN - matk halved { sc_start(src, target, SC_INCMATKRATE, 100, -50, skill_get_time2(skill_id, skill_lv)); break; } case 3: // THE HIGH PRIESTESS - all buffs removed { status_change_clear_buffs(target, SCCB_BUFFS | SCCB_CHEM_PROTECT); break; } case 4: // THE CHARIOT - 1000 damage, random armor destroyed { status_fix_damage(src, target, 1000, 0, skill_id); clif_damage(src, target, tick, 0, 0, 1000, 0, DMG_NORMAL, 0, false); if (!status_isdead(target)) { unsigned short where[] = { EQP_ARMOR, EQP_SHIELD, EQP_HELM }; skill_break_equip(src, target, where[rnd() % 3], 10000, BCT_ENEMY); } break; } case 5: // STRENGTH - atk halved { sc_start(src, target, SC_INCATKRATE, 100, -50, skill_get_time2(skill_id, skill_lv)); break; } case 6: // THE LOVERS - 2000HP heal, random teleported { status_heal(target, 2000, 0, 0); if (!map_flag_vs(target->m)) unit_warp(target, -1, -1, -1, CLR_TELEPORT); break; } case 7: // WHEEL OF FORTUNE - random 2 other effects { // Recursive call skill_tarotcard(src, target, skill_id, skill_lv, tick); skill_tarotcard(src, target, skill_id, skill_lv, tick); break; } case 8: // THE HANGED MAN - stop, freeze or stoned { enum sc_type sc[] = { SC_STOP, SC_FREEZE, SC_STONEWAIT }; uint8 rand_eff = rnd() % 3; int time = ((rand_eff == 0) ? skill_get_time2(skill_id, skill_lv) : skill_get_time2(status_db.getSkill(sc[rand_eff]), 1)); if (sc[rand_eff] == SC_STONEWAIT) sc_start2(src, target, SC_STONEWAIT, 100, skill_lv, src->id, time, skill_get_time(status_db.getSkill(SC_STONEWAIT), 1)); else sc_start(src, target, sc[rand_eff], 100, skill_lv, time); break; } case 9: // DEATH - curse, coma and poison { status_change_start(src, target, SC_COMA, 10000, skill_lv, 0, src->id, 0, 0, SCSTART_NONE); sc_start(src, target, SC_CURSE, 100, skill_lv, skill_get_time2(status_db.getSkill(SC_CURSE), 1)); sc_start2(src, target, SC_POISON, 100, skill_lv, src->id, skill_get_time2(status_db.getSkill(SC_POISON), 1)); break; } case 10: // TEMPERANCE - confusion { sc_start(src, target, SC_CONFUSION, 100, skill_lv, skill_get_time2(skill_id, skill_lv)); break; } case 11: // THE DEVIL - 6666 damage, atk and matk halved, cursed { status_fix_damage(src, target, 6666, 0, skill_id); clif_damage(src, target, tick, 0, 0, 6666, 0, DMG_NORMAL, 0, false); sc_start(src, target, SC_INCATKRATE, 100, -50, skill_get_time2(skill_id, skill_lv)); sc_start(src, target, SC_INCMATKRATE, 100, -50, skill_get_time2(skill_id, skill_lv)); sc_start(src, target, SC_CURSE, skill_lv, 100, skill_get_time2(status_db.getSkill(SC_CURSE), 1)); break; } case 12: // THE TOWER - 4444 damage { status_fix_damage(src, target, 4444, 0, skill_id); clif_damage(src, target, tick, 0, 0, 4444, 0, DMG_NORMAL, 0, false); break; } case 13: // THE STAR - stun { sc_start(src, target, SC_STUN, 100, skill_lv, skill_get_time2(status_db.getSkill(SC_STUN), 1)); break; } default: // THE SUN - atk, matk, hit, flee and def reduced, immune to more tarot card effects { #ifdef RENEWAL //In renewal, this card gives the SC_TAROTCARD status change which makes you immune to other cards sc_start(src, target, SC_TAROTCARD, 100, skill_lv, skill_get_time2(skill_id, skill_lv)); #endif sc_start(src, target, SC_INCATKRATE, 100, -20, skill_get_time2(skill_id, skill_lv)); sc_start(src, target, SC_INCMATKRATE, 100, -20, skill_get_time2(skill_id, skill_lv)); sc_start(src, target, SC_INCHITRATE, 100, -20, skill_get_time2(skill_id, skill_lv)); sc_start(src, target, SC_INCFLEERATE, 100, -20, skill_get_time2(skill_id, skill_lv)); sc_start(src, target, SC_INCDEFRATE, 100, -20, skill_get_time2(skill_id, skill_lv)); return 14; //To make sure a valid number is returned } } return card; } /*========================================== * * *------------------------------------------*/ int skill_castend_damage_id (struct block_list* src, struct block_list *bl, uint16 skill_id, uint16 skill_lv, t_tick tick, int flag) { map_session_data *sd = NULL; struct status_data *tstatus; status_change *sc, *tsc; if (skill_id > 0 && !skill_lv) return 0; nullpo_retr(1, src); nullpo_retr(1, bl); if (src->m != bl->m) return 1; if (bl->prev == NULL) return 1; sd = BL_CAST(BL_PC, src); if (status_isdead(bl)) return 1; if (skill_id && skill_id != AG_DEADLY_PROJECTION && skill_get_type(skill_id) == BF_MAGIC && status_isimmune(bl) == 100) { //GTB makes all targetted magic display miss with a single bolt. sc_type sct = skill_get_sc(skill_id); if(sct != SC_NONE) status_change_end(bl, sct); clif_skill_damage(src, bl, tick, status_get_amotion(src), status_get_dmotion(bl), 0, 1, skill_id, skill_lv, skill_get_hit(skill_id)); return 1; } sc = status_get_sc(src); tsc = status_get_sc(bl); if (sc && !sc->count) sc = NULL; //Unneeded if (tsc && !tsc->count) tsc = NULL; tstatus = status_get_status_data(bl); map_freeblock_lock(); switch(skill_id) { case MER_CRASH: case SM_BASH: case MS_BASH: case MC_MAMMONITE: case TF_DOUBLE: case AC_DOUBLE: case MA_DOUBLE: case AS_SONICBLOW: case KN_PIERCE: case ML_PIERCE: case KN_SPEARBOOMERANG: case TF_POISON: case TF_SPRINKLESAND: case AC_CHARGEARROW: case MA_CHARGEARROW: case RG_INTIMIDATE: case AM_ACIDTERROR: case BA_MUSICALSTRIKE: case DC_THROWARROW: case BA_DISSONANCE: case CR_HOLYCROSS: case NPC_DARKCROSS: case CR_SHIELDCHARGE: case CR_SHIELDBOOMERANG: case NPC_PIERCINGATT: case NPC_MENTALBREAKER: case NPC_RANGEATTACK: case NPC_CRITICALSLASH: case NPC_COMBOATTACK: case NPC_GUIDEDATTACK: case NPC_POISON: case NPC_RANDOMATTACK: case NPC_WATERATTACK: case NPC_GROUNDATTACK: case NPC_FIREATTACK: case NPC_WINDATTACK: case NPC_POISONATTACK: case NPC_HOLYATTACK: case NPC_DARKNESSATTACK: case NPC_TELEKINESISATTACK: case NPC_UNDEADATTACK: case NPC_CHANGEUNDEAD: case NPC_ARMORBRAKE: case NPC_HELMBRAKE: case NPC_SHIELDBRAKE: case NPC_BLINDATTACK: case NPC_SILENCEATTACK: case NPC_STUNATTACK: case NPC_PETRIFYATTACK: case NPC_CURSEATTACK: case NPC_SLEEPATTACK: #ifdef RENEWAL case CR_ACIDDEMONSTRATION: #endif case LK_AURABLADE: case LK_SPIRALPIERCE: case ML_SPIRALPIERCE: case CG_ARROWVULCAN: case HW_MAGICCRASHER: case ITM_TOMAHAWK: case CH_CHAINCRUSH: case CH_TIGERFIST: case PA_SHIELDCHAIN: // Shield Chain case PA_SACRIFICE: case WS_CARTTERMINATION: // Cart Termination case AS_VENOMKNIFE: case HT_PHANTASMIC: case TK_DOWNKICK: case TK_COUNTER: case GS_CHAINACTION: case GS_TRIPLEACTION: #ifndef RENEWAL case GS_MAGICALBULLET: #endif case GS_TRACKING: case GS_PIERCINGSHOT: case GS_RAPIDSHOWER: case GS_DUST: case GS_DISARM: // Added disarm. [Reddozen] case GS_FULLBUSTER: case NJ_SYURIKEN: case NJ_KUNAI: case ASC_BREAKER: case HFLI_MOON: //[orn] case HFLI_SBR44: //[orn] case NPC_BLEEDING: case NPC_BLEEDING2: case NPC_CRITICALWOUND: case NPC_HELLPOWER: case RK_SONICWAVE: case AB_DUPLELIGHT_MELEE: case RA_AIMEDBOLT: case NC_BOOSTKNUCKLE: case NC_PILEBUNKER: case NC_AXEBOOMERANG: case NC_POWERSWING: case NPC_MAGMA_ERUPTION: case NC_MAGMA_ERUPTION: case GC_WEAPONCRUSH: case GC_VENOMPRESSURE: case SC_TRIANGLESHOT: case SC_FEINTBOMB: case LG_BANISHINGPOINT: case LG_SHIELDPRESS: case LG_RAGEBURST: case LG_HESPERUSLIT: case SR_DRAGONCOMBO: case SR_FALLENEMPIRE: case SR_CRESCENTELBOW_AUTOSPELL: case SR_GATEOFHELL: case SR_GENTLETOUCH_QUIET: case WM_SEVERE_RAINSTORM_MELEE: case WM_GREAT_ECHO: case GN_SLINGITEM_RANGEMELEEATK: case KO_SETSUDAN: case RL_MASS_SPIRAL: case RL_BANISHING_BUSTER: case RL_SLUGSHOT: case RL_AM_BLAST: case DK_SERVANTWEAPON_ATK: case BO_ACIDIFIED_ZONE_WATER_ATK: case BO_ACIDIFIED_ZONE_GROUND_ATK: case BO_ACIDIFIED_ZONE_WIND_ATK: case BO_ACIDIFIED_ZONE_FIRE_ATK: case ABC_CHAIN_REACTION_SHOT_ATK: case ABR_BATTLE_BUSTER: case ABR_DUAL_CANNON_FIRE: case ABR_INFINITY_BUSTER: skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); break; case IG_SHIELD_SHOOTING: clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); skill_attack(BF_WEAPON, src, src, bl, skill_id, skill_lv, tick, flag); sc_start(src, src, SC_SHIELD_POWER, 100, skill_lv, skill_get_time(skill_id, skill_lv)); break; case DK_DRAGONIC_AURA: case DK_STORMSLASH: case IG_GRAND_JUDGEMENT: case CD_EFFLIGO: case ABC_FRENZY_SHOT: case WH_HAWKRUSH: case WH_HAWKBOOMERANG: case TR_ROSEBLOSSOM: case TR_RHYTHMSHOOTING: clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); skill_attack(BF_WEAPON, src, src, bl, skill_id, skill_lv, tick, flag); if (skill_id == DK_DRAGONIC_AURA) sc_start(src, src, SC_DRAGONIC_AURA, 100, skill_lv, skill_get_time(skill_id,skill_lv)); else if (skill_id == IG_GRAND_JUDGEMENT) sc_start(src, src, SC_SPEAR_SCAR, 100, skill_lv, skill_get_time(skill_id, skill_lv)); break; case SHC_ETERNAL_SLASH: if( sc && sc->getSCE(SC_E_SLASH_COUNT) ) sc_start(src, src, SC_E_SLASH_COUNT, 100, min( 5, 1 + sc->getSCE(SC_E_SLASH_COUNT)->val1 ), skill_get_time(skill_id, skill_lv)); else sc_start(src, src, SC_E_SLASH_COUNT, 100, 1, skill_get_time(skill_id, skill_lv)); clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); skill_attack(BF_WEAPON, src, src, bl, skill_id, skill_lv, tick, flag); break; case SHC_SHADOW_STAB: if (sc && (sc->getSCE(SC_CLOAKING) || sc->getSCE(SC_CLOAKINGEXCEED))) flag |= 2;// Flag to deal 2 hits. status_change_end(src, SC_CLOAKING); status_change_end(src, SC_CLOAKINGEXCEED); clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); skill_attack(BF_WEAPON, src, src, bl, skill_id, skill_lv, tick, flag); break; case WH_CRESCIVE_BOLT: clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); skill_attack(BF_WEAPON, src, src, bl, skill_id, skill_lv, tick, flag); if( sc && sc->getSCE(SC_CRESCIVEBOLT) ) sc_start(src, src, SC_CRESCIVEBOLT, 100, min( 3, 1 + sc->getSCE(SC_CRESCIVEBOLT)->val1 ), skill_get_time(skill_id, skill_lv)); else sc_start(src, src, SC_CRESCIVEBOLT, 100, 1, skill_get_time(skill_id, skill_lv)); break; case ABC_UNLUCKY_RUSH: // Jump to the target before attacking. if (skill_check_unit_movepos(5, src, bl->x, bl->y, 0, 1)) skill_blown(src, src, 1, (map_calc_dir(bl, src->x, src->y) + 4) % 8, BLOWN_NONE); clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); skill_attack(BF_WEAPON, src, src, bl, skill_id, skill_lv, tick, flag); break; case MO_TRIPLEATTACK: skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag|SD_ANIMATION); break; case LK_HEADCRUSH: if (status_get_class_(bl) == CLASS_BOSS) { if (sd) clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); break; } skill_attack(BF_WEAPON, src, src, bl, skill_id, skill_lv, tick, flag); break; case LK_JOINTBEAT: flag = 1 << rnd() % 6; if (flag != BREAK_NECK && tsc && tsc->getSCE(SC_JOINTBEAT) && tsc->getSCE(SC_JOINTBEAT)->val2 & BREAK_NECK) flag = BREAK_NECK; // Target should always receive double damage if neck is already broken if (skill_attack(BF_WEAPON, src, src, bl, skill_id, skill_lv, tick, flag)) status_change_start(src, bl, SC_JOINTBEAT, (50 * (skill_lv + 1) - (270 * tstatus->str) / 100) * 10, skill_lv, flag & BREAK_FLAGS, src->id, 0, skill_get_time2(skill_id, skill_lv), SCSTART_NONE); break; case MO_COMBOFINISH: if (!(flag&1) && sc && sc->getSCE(SC_SPIRIT) && sc->getSCE(SC_SPIRIT)->val2 == SL_MONK) { //Becomes a splash attack when Soul Linked. map_foreachinshootrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv),BL_CHAR|BL_SKILL, src,skill_id,skill_lv,tick, flag|BCT_ENEMY|1, skill_castend_damage_id); } else skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); break; case TK_STORMKICK: // Taekwon kicks [Dralnu] clif_skill_nodamage(src,bl,skill_id,skill_lv,1); skill_area_temp[1] = 0; map_foreachinshootrange(skill_attack_area, src, skill_get_splash(skill_id, skill_lv), BL_CHAR|BL_SKILL, BF_WEAPON, src, src, skill_id, skill_lv, tick, flag, BCT_ENEMY); break; case KN_CHARGEATK: { bool path = path_search_long(NULL, src->m, src->x, src->y, bl->x, bl->y,CELL_CHKWALL); #ifdef RENEWAL int dist = skill_get_blewcount(skill_id, skill_lv); #else unsigned int dist = distance_bl(src, bl); #endif uint8 dir = map_calc_dir(bl, src->x, src->y); // teleport to target (if not on WoE grounds) if (skill_check_unit_movepos(5, src, bl->x, bl->y, 0, 1)) skill_blown(src, src, 1, (dir+4)%8, BLOWN_NONE); //Target position is actually one cell next to the target // cause damage and knockback if the path to target was a straight one if (path) { if(skill_attack(BF_WEAPON, src, src, bl, skill_id, skill_lv, tick, dist)) { #ifdef RENEWAL if (map_getmapdata(src->m)->flag[MF_PVP]) dist += 2; // Knockback is 4 on PvP maps #endif skill_blown(src, bl, dist, dir, BLOWN_NONE); } //HACK: since knockback officially defaults to the left, the client also turns to the left... therefore, // make the caster look in the direction of the target unit_setdir(src, (dir+4)%8); } } break; case NC_FLAMELAUNCHER: skill_area_temp[1] = bl->id; if (battle_config.skill_eightpath_algorithm) { //Use official AoE algorithm map_foreachindir(skill_attack_area, src->m, src->x, src->y, bl->x, bl->y, skill_get_splash(skill_id, skill_lv), skill_get_maxcount(skill_id, skill_lv), 0, splash_target(src), skill_get_type(skill_id), src, src, skill_id, skill_lv, tick, flag, BCT_ENEMY); } else { map_foreachinpath(skill_attack_area, src->m, src->x, src->y, bl->x, bl->y, skill_get_splash(skill_id, skill_lv), skill_get_maxcount(skill_id, skill_lv), splash_target(src), skill_get_type(skill_id), src, src, skill_id, skill_lv, tick, flag, BCT_ENEMY); } break; #ifndef RENEWAL case SN_SHARPSHOOTING: flag |= 2; // Flag for specific mob damage formula #endif case MA_SHARPSHOOTING: case NJ_KAMAITACHI: case NPC_DARKPIERCING: case NPC_ACIDBREATH: case NPC_DARKNESSBREATH: case NPC_FIREBREATH: case NPC_ICEBREATH: case NPC_ICEBREATH2: case NPC_THUNDERBREATH: case AG_STORM_CANNON: case AG_CRIMSON_ARROW: skill_area_temp[1] = bl->id; if (skill_id == AG_STORM_CANNON || skill_id == AG_CRIMSON_ARROW) clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); if (battle_config.skill_eightpath_algorithm) { //Use official AoE algorithm if (!(map_foreachindir(skill_attack_area, src->m, src->x, src->y, bl->x, bl->y, skill_get_splash(skill_id, skill_lv), skill_get_maxcount(skill_id, skill_lv), 0, splash_target(src), skill_get_type(skill_id), src, src, skill_id, skill_lv, tick, flag, BCT_ENEMY))) { #ifndef RENEWAL if (skill_id == SN_SHARPSHOOTING) flag &= ~2; // Only targets in the splash area are affected #endif //These skills hit at least the target if the AoE doesn't hit skill_attack(skill_get_type(skill_id), src, src, bl, skill_id, skill_lv, tick, flag); } } else { map_foreachinpath(skill_attack_area, src->m, src->x, src->y, bl->x, bl->y, skill_get_splash(skill_id, skill_lv), skill_get_maxcount(skill_id, skill_lv), splash_target(src), skill_get_type(skill_id), src, src, skill_id, skill_lv, tick, flag, BCT_ENEMY); } if (skill_id == AG_CRIMSON_ARROW) skill_attack(skill_get_type(AG_CRIMSON_ARROW_ATK), src, src, bl, AG_CRIMSON_ARROW_ATK, skill_lv, tick, flag|SD_LEVEL|SD_ANIMATION); break; case MO_INVESTIGATE: skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); status_change_end(src, SC_BLADESTOP); break; case RG_BACKSTAP: { if (!check_distance_bl(src, bl, 0)) { #ifdef RENEWAL uint8 dir = map_calc_dir(src, bl->x, bl->y); short x, y; if (dir > 0 && dir < 4) x = -1; else if (dir > 4) x = 1; else x = 0; if (dir > 2 && dir < 6) y = -1; else if (dir == 7 || dir < 2) y = 1; else y = 0; if (battle_check_target(src, bl, BCT_ENEMY) > 0 && unit_movepos(src, bl->x + x, bl->y + y, 2, true)) { // Display movement + animation. #else uint8 dir = map_calc_dir(src, bl->x, bl->y), t_dir = unit_getdir(bl); if (!map_check_dir(dir, t_dir) || bl->type == BL_SKILL) { #endif status_change_end(src, SC_HIDING); dir = dir < 4 ? dir+4 : dir-4; // change direction [Celest] unit_setdir(bl,dir); #ifdef RENEWAL clif_blown(src); #endif skill_attack(BF_WEAPON, src, src, bl, skill_id, skill_lv, tick, flag); } else if (sd) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); } } break; case MO_FINGEROFFENSIVE: skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); if (battle_config.finger_offensive_type && sd) { for (int i = 1; i < sd->spiritball_old; i++) skill_addtimerskill(src, tick + i * 200, bl->id, 0, 0, skill_id, skill_lv, BF_WEAPON, flag); } status_change_end(src, SC_BLADESTOP); break; case MO_CHAINCOMBO: skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); status_change_end(src, SC_BLADESTOP); break; #ifndef RENEWAL case NJ_ISSEN: #endif case MO_EXTREMITYFIST: { struct block_list *mbl = bl; // For NJ_ISSEN short x, y, i = 2; // Move 2 cells (From target) short dir = map_calc_dir(src,bl->x,bl->y); #ifdef RENEWAL if (skill_id == MO_EXTREMITYFIST && sd && sd->spiritball_old > 5) flag |= 1; // Give +100% damage increase #endif skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); if (skill_id == MO_EXTREMITYFIST) { status_set_sp(src, 0, 0); status_change_end(src, SC_EXPLOSIONSPIRITS); status_change_end(src, SC_BLADESTOP); #ifdef RENEWAL sc_start(src,src,SC_EXTREMITYFIST2,100,skill_lv,skill_get_time(skill_id,skill_lv)); #endif } else { status_set_hp(src, 1, 0); status_change_end(src, SC_NEN); status_change_end(src, SC_HIDING); } if (skill_id == MO_EXTREMITYFIST) { mbl = src; // For MO_EXTREMITYFIST i = 3; // Move 3 cells (From caster) } if (dir > 0 && dir < 4) x = -i; else if (dir > 4) x = i; else x = 0; if (dir > 2 && dir < 6) y = -i; else if (dir == 7 || dir < 2) y = i; else y = 0; // Ashura Strike still has slide effect in GVG if ((mbl == src || (!map_flag_gvg2(src->m) && !map_getmapflag(src->m, MF_BATTLEGROUND))) && unit_movepos(src, mbl->x + x, mbl->y + y, 1, 1)) { clif_blown(src); clif_spiritball(src); } } break; case HT_POWER: if( tstatus->race == RC_BRUTE || tstatus->race == RC_PLAYER_DORAM || tstatus->race == RC_INSECT ) skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); break; case SU_PICKYPECK: clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); case SU_BITE: skill_attack(BF_WEAPON, src, src, bl, skill_id, skill_lv, tick, flag); break; case SU_SVG_SPIRIT: skill_area_temp[1] = bl->id; map_foreachinpath(skill_attack_area, src->m, src->x, src->y, bl->x, bl->y, skill_get_splash(skill_id, skill_lv), skill_get_maxcount(skill_id, skill_lv), splash_target(src), skill_get_type(skill_id), src, src, skill_id, skill_lv, tick, flag, BCT_ENEMY); break; //Splash attack skills. case AS_GRIMTOOTH: case MC_CARTREVOLUTION: case NPC_SPLASHATTACK: flag |= SD_PREAMBLE; // a fake packet will be sent for the first target to be hit case AS_SPLASHER: case HT_BLITZBEAT: case AC_SHOWER: case MA_SHOWER: case MG_NAPALMBEAT: case MG_FIREBALL: case RG_RAID: #ifdef RENEWAL case SN_SHARPSHOOTING: #endif case HW_NAPALMVULCAN: case NJ_HUUMA: case ASC_METEORASSAULT: case GS_SPREADATTACK: case NPC_PULSESTRIKE: case NPC_PULSESTRIKE2: case NPC_HELLJUDGEMENT: case NPC_HELLJUDGEMENT2: case NPC_VAMPIRE_GIFT: case NPC_MAXPAIN_ATK: case NPC_JACKFROST: case NPC_REVERBERATION_ATK: case NPC_ARROWSTORM: case NPC_IGNITIONBREAK: case RK_IGNITIONBREAK: case RK_HUNDREDSPEAR: case AB_JUDEX: case AB_ADORAMUS: case WL_SOULEXPANSION: case WL_CRIMSONROCK: case WL_JACKFROST: case RA_ARROWSTORM: case RA_WUGDASH: case NC_VULCANARM: case NC_COLDSLOWER: case NC_SELFDESTRUCTION: case NC_AXETORNADO: case GC_ROLLINGCUTTER: case GC_COUNTERSLASH: case LG_CANNONSPEAR: case LG_OVERBRAND: case LG_MOONSLASHER: case LG_RAYOFGENESIS: case NPC_RAYOFGENESIS: case LG_EARTHDRIVE: case SR_RAMPAGEBLASTER: case SR_SKYNETBLOW: case SR_WINDMILL: case SR_RIDEINLIGHTNING: case SO_VARETYR_SPEAR: case SO_POISON_BUSTER: case GN_CART_TORNADO: case GN_CARTCANNON: case GN_SPORE_EXPLOSION: case GN_DEMONIC_FIRE: case GN_FIRE_EXPANSION_ACID: case GN_HELLS_PLANT_ATK: case KO_HAPPOKUNAI: case KO_HUUMARANKA: case KO_MUCHANAGE: case KO_BAKURETSU: case GN_ILLUSIONDOPING: case RL_FIREDANCE: case RL_S_STORM: case RL_R_TRIP: case MH_XENO_SLASHER: case NC_ARMSCANNON: case SU_SCRATCH: case SU_LUNATICCARROTBEAT: case SU_LUNATICCARROTBEAT2: case SJ_FULLMOONKICK: case SJ_NEWMOONKICK: case SJ_SOLARBURST: case SJ_PROMINENCEKICK: case SJ_STAREMPEROR: case SJ_FALLINGSTAR_ATK2: case SP_CURSEEXPLOSION: case SP_SHA: case SP_SWHOO: case DK_SERVANT_W_PHANTOM: case DK_SERVANT_W_DEMOL: case DK_MADNESS_CRUSHER: case AG_DESTRUCTIVE_HURRICANE: case AG_SOUL_VC_STRIKE: case AG_CRYSTAL_IMPACT: case AG_CRYSTAL_IMPACT_ATK: case AG_ROCK_DOWN: case AG_FROZEN_SLASH: case IQ_OLEUM_SANCTUM: case IQ_MASSIVE_F_BLASTER: case IQ_EXPOSION_BLASTER: case IQ_FIRST_BRAND: case IQ_SECOND_FLAME: case IQ_SECOND_FAITH: case IQ_SECOND_JUDGEMENT: case IQ_THIRD_PUNISH: case IQ_THIRD_FLAME_BOMB: case IQ_THIRD_CONSECRATION: case IG_OVERSLASH: case CD_ARBITRIUM_ATK: case CD_PETITIO: case CD_FRAMEN: case SHC_DANCING_KNIFE: case SHC_SAVAGE_IMPACT: case SHC_IMPACT_CRATER: case SHC_FATAL_SHADOW_CROW: case MT_AXE_STOMP: case MT_RUSH_QUAKE: case MT_A_MACHINE: case ABC_ABYSS_DAGGER: case ABC_CHAIN_REACTION_SHOT: case ABC_DEFT_STAB: case WH_GALESTORM: case BO_ACIDIFIED_ZONE_WATER: case BO_ACIDIFIED_ZONE_GROUND: case BO_ACIDIFIED_ZONE_WIND: case BO_ACIDIFIED_ZONE_FIRE: case TR_ROSEBLOSSOM_ATK: case TR_METALIC_FURY: case ABC_FROM_THE_ABYSS_ATK: case EM_ELEMENTAL_BUSTER_FIRE: case EM_ELEMENTAL_BUSTER_WATER: case EM_ELEMENTAL_BUSTER_WIND: case EM_ELEMENTAL_BUSTER_GROUND: case EM_ELEMENTAL_BUSTER_POISON: case EM_EL_FLAMEROCK: case EM_EL_AGE_OF_ICE: case EM_EL_STORM_WIND: case EM_EL_AVALANCHE: case EM_EL_DEADLY_POISON: if( flag&1 ) {//Recursive invocation int sflag = skill_area_temp[0] & 0xFFF; int heal = 0; std::bitset<INF2_MAX> inf2 = skill_db.find(skill_id)->inf2; if (tsc && tsc->getSCE(SC_HOVERING) && inf2[INF2_IGNOREHOVERING]) break; // Under Hovering characters are immune to select trap and ground target skills. if (skill_id == AB_ADORAMUS && map_getcell(bl->m, bl->x, bl->y, CELL_CHKLANDPROTECTOR)) break; // No damage should happen if the target is on Land Protector // Servant Weapon - Demol only hits if the target is marked with a sign by the attacking caster. if (skill_id == DK_SERVANT_W_DEMOL && !(tsc && tsc->getSCE(SC_SERVANT_SIGN) && tsc->getSCE(SC_SERVANT_SIGN)->val1 == src->id)) break; // Deft Stab - Make sure the flag of 2 is passed on when the skill is double casted. if (skill_id == ABC_DEFT_STAB && flag&2) sflag |= 2; if( flag&SD_LEVEL ) sflag |= SD_LEVEL; // -1 will be used in packets instead of the skill level if( skill_area_temp[1] != bl->id && !inf2[INF2_ISNPC] ) sflag |= SD_ANIMATION; // original target gets no animation (as well as all NPC skills) // If a enemy player is standing next to a mob when splash Es- skill is casted, the player won't get hurt. if ((skill_id == SP_SHA || skill_id == SP_SWHOO) && !battle_config.allow_es_magic_pc && bl->type != BL_MOB) break; heal = (int)skill_attack(skill_get_type(skill_id), src, src, bl, skill_id, skill_lv, tick, sflag); switch (skill_id) { case NPC_VAMPIRE_GIFT: if (heal > 0) { clif_skill_nodamage(NULL, src, AL_HEAL, heal, 1); status_heal(src, heal, 0, 0); } break; #ifdef RENEWAL case SN_SHARPSHOOTING: status_change_end(src, SC_CAMOUFLAGE); break; #endif case SJ_PROMINENCEKICK: // Trigger the 2nd hit. (100% fire damage.) skill_attack(skill_get_type(skill_id), src, src, bl, skill_id, skill_lv, tick, sflag|8|SD_ANIMATION); break; } } else { int starget = BL_CHAR|BL_SKILL, splash_size = skill_get_splash(skill_id, skill_lv); skill_area_temp[0] = 0; skill_area_temp[1] = bl->id; skill_area_temp[2] = 0; if (sd && (skill_id == SP_SHA || skill_id == SP_SWHOO) && !battle_config.allow_es_magic_pc && bl->type != BL_MOB) { status_change_start(src, bl, SC_STUN, 10000, skill_lv, 0, 0, 0, 500, 10); clif_skill_fail(sd, skill_id, USESKILL_FAIL, 0); break; } if (skill_id == SP_SWHOO) status_change_end(src, SC_USE_SKILL_SP_SPA); switch ( skill_id ) { case LG_EARTHDRIVE: case GN_CARTCANNON: case SU_SCRATCH: clif_skill_nodamage(src,bl,skill_id,skill_lv,1); break; #ifdef RENEWAL case NJ_HUUMA: #endif case LG_MOONSLASHER: case MH_XENO_SLASHER: clif_skill_damage(src,bl,tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, DMG_SINGLE); break; case NPC_REVERBERATION_ATK: case NC_ARMSCANNON: skill_area_temp[1] = 0; starget = splash_target(src); break; case WL_CRIMSONROCK: skill_area_temp[4] = bl->x; skill_area_temp[5] = bl->y; break; case SU_LUNATICCARROTBEAT: if (sd && pc_search_inventory(sd, skill_db.find(SU_LUNATICCARROTBEAT)->require.itemid[0]) >= 0) skill_id = SU_LUNATICCARROTBEAT2; break; case DK_SERVANT_W_PHANTOM: case MT_RUSH_QUAKE: // Jump to the target before attacking. if (skill_check_unit_movepos(5, src, bl->x, bl->y, 0, 1)) skill_blown(src, src, 1, (map_calc_dir(bl, src->x, src->y) + 4) % 8, BLOWN_NONE); clif_skill_nodamage(src, bl, skill_id, skill_lv, 1);// Trigger animation on servants. break; case SHC_SAVAGE_IMPACT: case SHC_FATAL_SHADOW_CROW: { uint8 dir = map_calc_dir(bl, src->x, src->y); // dir based on target as we move player based on target location // Move the player 1 cell near the target, between the target and the player if (skill_check_unit_movepos(5, src, bl->x + dirx[dir], bl->y + diry[dir], 0, 1)) clif_blown(src); clif_skill_nodamage(src, bl, skill_id, skill_lv, 1);// Trigger animation on servants. break; } case AG_CRYSTAL_IMPACT_ATK: if (sc && sc->getSCE(SC_CLIMAX) && sc->getSCE(SC_CLIMAX)->val1 == 5) splash_size = 2;// Gives the aftershock hit a 5x5 splash AoE. break; case AG_ROCK_DOWN: case IQ_FIRST_BRAND: case IQ_SECOND_FLAME: case IQ_SECOND_FAITH: case IQ_SECOND_JUDGEMENT: case CD_PETITIO: case CD_FRAMEN: case ABC_DEFT_STAB: case ABC_CHAIN_REACTION_SHOT: case EM_EL_FLAMEROCK: case EM_EL_AGE_OF_ICE: case EM_EL_STORM_WIND: case EM_EL_AVALANCHE: case EM_EL_DEADLY_POISON: clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); break; case IQ_THIRD_PUNISH: clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); if (sd) { uint8 limit = 5; if (sc && sc->getSCE(SC_RAISINGDRAGON)) limit += sc->getSCE(SC_RAISINGDRAGON)->val1; for (uint8 i = 0; i < limit; i++) pc_addspiritball(sd, skill_get_time(skill_id, skill_lv), limit); } break; case IQ_THIRD_FLAME_BOMB: clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); if (sd && sd->spiritball / 5 > 1) skill_area_temp[0] = sd->spiritball / 5 - 1; break; case IQ_THIRD_CONSECRATION: clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); status_heal(src, status_get_max_hp(src) * skill_lv / 100, status_get_max_sp(src) * skill_lv / 100, 0); break; case IG_OVERSLASH: clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); skill_area_temp[0] = map_foreachinallrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), BL_CHAR, src, skill_id, skill_lv, tick, BCT_ENEMY, skill_area_sub_count); break; case WH_GALESTORM:// Give AP if 3 or more targets are hit. if (sd && map_foreachinallrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), BL_CHAR, src, skill_id, skill_lv, tick, BCT_ENEMY, skill_area_sub_count) >= 3) status_heal(src, 0, 0, 10, 0); break; case BO_ACIDIFIED_ZONE_WATER: case BO_ACIDIFIED_ZONE_GROUND: case BO_ACIDIFIED_ZONE_WIND: case BO_ACIDIFIED_ZONE_FIRE: clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); if (bl->type == BL_PC)// Place single cell AoE if hitting a player. skill_castend_pos2(src, bl->x, bl->y, skill_id, skill_lv, tick, 0); break; case TR_METALIC_FURY: clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); if (tsc && tsc->getSCE(SC_SOUNDBLEND)) skill_area_temp[0] = 1 + rnd()%4; break; } // if skill damage should be split among targets, count them //SD_LEVEL -> Forced splash damage for Auto Blitz-Beat -> count targets //special case: Venom Splasher uses a different range for searching than for splashing if( flag&SD_LEVEL || skill_get_nk(skill_id, NK_SPLASHSPLIT) ) skill_area_temp[0] = map_foreachinallrange(skill_area_sub, bl, (skill_id == AS_SPLASHER)?1:splash_size, BL_CHAR, src, skill_id, skill_lv, tick, BCT_ENEMY, skill_area_sub_count); // recursive invocation of skill_castend_damage_id() with flag|1 map_foreachinrange(skill_area_sub, bl, splash_size, starget, src, skill_id, skill_lv, tick, flag|BCT_ENEMY|SD_SPLASH|1, skill_castend_damage_id); if (skill_id == RA_ARROWSTORM) status_change_end(src, SC_CAMOUFLAGE); if( skill_id == AS_SPLASHER ) { map_freeblock_unlock(); // Don't consume a second gemstone. return 0; } } break; case DK_HACKANDSLASHER: case DK_HACKANDSLASHER_ATK: if (flag & 1) { skill_addtimerskill(src, tick + (200 + status_get_amotion(src)), bl->id, 0, 0, skill_id, skill_lv, BF_WEAPON, flag); } else { skill_area_temp[0] = 0; skill_area_temp[1] = bl->id; skill_area_temp[2] = 0; clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); skill_attack(skill_get_type(skill_id), src, src, bl, skill_id, skill_lv, tick, flag); map_foreachinrange(skill_area_sub, bl, skill_get_splash(DK_HACKANDSLASHER_ATK, skill_lv), BL_CHAR|BL_SKILL, src, DK_HACKANDSLASHER_ATK, skill_lv, tick, flag|BCT_ENEMY|SD_SPLASH|1, skill_castend_damage_id); } break; //Place units around target case NJ_BAKUENRYU: clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); skill_unitsetting(src, skill_id, skill_lv, bl->x, bl->y, 0); break; case SM_MAGNUM: case MS_MAGNUM: if( flag&1 ) { // For players, damage depends on distance, so add it to flag if it is > 1 // Cannot hit hidden targets skill_attack(skill_get_type(skill_id), src, src, bl, skill_id, skill_lv, tick, flag|SD_ANIMATION|(sd?distance_bl(src, bl):0)); } break; #ifdef RENEWAL case KN_BRANDISHSPEAR: skill_attack(skill_get_type(skill_id), src, src, bl, skill_id, skill_lv, tick, flag); break; #else case KN_BRANDISHSPEAR: #endif case ML_BRANDISH: //Coded apart for it needs the flag passed to the damage calculation. if (skill_area_temp[1] != bl->id) skill_attack(skill_get_type(skill_id), src, src, bl, skill_id, skill_lv, tick, flag|SD_ANIMATION); else skill_attack(skill_get_type(skill_id), src, src, bl, skill_id, skill_lv, tick, flag); break; #ifdef RENEWAL case KN_BOWLINGBASH: if (flag & 1) { skill_attack(skill_get_type(skill_id), src, src, bl, skill_id, skill_lv, tick, (skill_area_temp[0]) > 0 ? SD_ANIMATION | skill_area_temp[0] : skill_area_temp[0]); skill_blown(src, bl, skill_get_blewcount(skill_id, skill_lv), -1, BLOWN_NONE); } else { skill_area_temp[0] = map_foreachinallrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), BL_CHAR, src, skill_id, skill_lv, tick, BCT_ENEMY, skill_area_sub_count); map_foreachinrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), BL_CHAR|BL_SKILL, src, skill_id, skill_lv, tick, flag | BCT_ENEMY | SD_SPLASH | 1, skill_castend_damage_id); } break; #else case KN_BOWLINGBASH: #endif case MS_BOWLINGBASH: { int min_x,max_x,min_y,max_y,i,c,dir,tx,ty; // Chain effect and check range gets reduction by recursive depth, as this can reach 0, we don't use blowcount c = (skill_lv-(flag&0xFFF)+1)/2; // Determine the Bowling Bash area depending on configuration if (battle_config.bowling_bash_area == 0) { // Gutter line system min_x = ((src->x)-c) - ((src->x)-c)%40; if(min_x < 0) min_x = 0; max_x = min_x + 39; min_y = ((src->y)-c) - ((src->y)-c)%40; if(min_y < 0) min_y = 0; max_y = min_y + 39; } else if (battle_config.bowling_bash_area == 1) { // Gutter line system without demi gutter bug min_x = src->x - (src->x)%40; max_x = min_x + 39; min_y = src->y - (src->y)%40; max_y = min_y + 39; } else { // Area around caster min_x = src->x - battle_config.bowling_bash_area; max_x = src->x + battle_config.bowling_bash_area; min_y = src->y - battle_config.bowling_bash_area; max_y = src->y + battle_config.bowling_bash_area; } // Initialization, break checks, direction if((flag&0xFFF) > 0) { // Ignore monsters outside area if(bl->x < min_x || bl->x > max_x || bl->y < min_y || bl->y > max_y) break; // Ignore monsters already in list if(idb_exists(bowling_db, bl->id)) break; // Random direction dir = rnd()%8; } else { // Create an empty list of already hit targets db_clear(bowling_db); // Direction is walkpath dir = (unit_getdir(src)+4)%8; } // Add current target to the list of already hit targets idb_put(bowling_db, bl->id, bl); // Keep moving target in direction square by square tx = bl->x; ty = bl->y; for(i=0;i<c;i++) { // Target coordinates (get changed even if knockback fails) tx -= dirx[dir]; ty -= diry[dir]; // If target cell is a wall then break if(map_getcell(bl->m,tx,ty,CELL_CHKWALL)) break; skill_blown(src,bl,1,dir,BLOWN_NONE); int count; // Splash around target cell, but only cells inside area; we first have to check the area is not negative if((max(min_x,tx-1) <= min(max_x,tx+1)) && (max(min_y,ty-1) <= min(max_y,ty+1)) && (count = map_foreachinallarea(skill_area_sub, bl->m, max(min_x,tx-1), max(min_y,ty-1), min(max_x,tx+1), min(max_y,ty+1), splash_target(src), src, skill_id, skill_lv, tick, flag|BCT_ENEMY, skill_area_sub_count))) { // Recursive call map_foreachinallarea(skill_area_sub, bl->m, max(min_x,tx-1), max(min_y,ty-1), min(max_x,tx+1), min(max_y,ty+1), splash_target(src), src, skill_id, skill_lv, tick, (flag|BCT_ENEMY)+1, skill_castend_damage_id); // Self-collision if(bl->x >= min_x && bl->x <= max_x && bl->y >= min_y && bl->y <= max_y) skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,(flag&0xFFF)>0?SD_ANIMATION|count:count); break; } } // Original hit or chain hit depending on flag skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,(flag&0xFFF)>0?SD_ANIMATION:0); } break; case KN_SPEARSTAB: if(flag&1) { if (bl->id==skill_area_temp[1]) break; if (skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,SD_ANIMATION)) skill_blown(src,bl,skill_area_temp[2],-1,BLOWN_NONE); } else { int x=bl->x,y=bl->y,i,dir; dir = map_calc_dir(bl,src->x,src->y); skill_area_temp[1] = bl->id; skill_area_temp[2] = skill_get_blewcount(skill_id,skill_lv); // all the enemies between the caster and the target are hit, as well as the target if (skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,0)) skill_blown(src,bl,skill_area_temp[2],-1,BLOWN_NONE); for (i=0;i<4;i++) { map_foreachincell(skill_area_sub,bl->m,x,y,BL_CHAR, src,skill_id,skill_lv,tick,flag|BCT_ENEMY|1,skill_castend_damage_id); x += dirx[dir]; y += diry[dir]; } } break; case TK_TURNKICK: case MO_BALKYOUNG: //Active part of the attack. Skill-attack [Skotlex] { skill_area_temp[1] = bl->id; //NOTE: This is used in skill_castend_nodamage_id to avoid affecting the target. if (skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag)) map_foreachinallrange(skill_area_sub,bl, skill_get_splash(skill_id, skill_lv),BL_CHAR, src,skill_id,skill_lv,tick,flag|BCT_ENEMY|1, skill_castend_nodamage_id); } break; case CH_PALMSTRIKE: // Palm Strike takes effect 1sec after casting. [Skotlex] // clif_skill_nodamage(src,bl,skill_id,skill_lv,0); //Can't make this one display the correct attack animation delay clif_damage(src,bl,tick,status_get_amotion(src),0,-1,1,DMG_ENDURE,0,false); //Display an absorbed damage attack. skill_addtimerskill(src, tick + (1000+status_get_amotion(src)), bl->id, 0, 0, skill_id, skill_lv, BF_WEAPON, flag); break; case PR_TURNUNDEAD: case ALL_RESURRECTION: if (!battle_check_undead(tstatus->race, tstatus->def_ele)) break; skill_attack(BF_MAGIC,src,src,bl,skill_id,skill_lv,tick,flag); break; case AL_HOLYLIGHT: status_change_end(bl, SC_P_ALTER); case MG_SOULSTRIKE: case NPC_DARKSTRIKE: case MG_COLDBOLT: case MG_FIREBOLT: case MG_LIGHTNINGBOLT: case WZ_EARTHSPIKE: case AL_HEAL: case NPC_DARKTHUNDER: case NPC_FIRESTORM: case PR_ASPERSIO: case MG_FROSTDIVER: case WZ_SIGHTBLASTER: case WZ_SIGHTRASHER: #ifdef RENEWAL case PA_PRESSURE: #endif case NJ_KOUENKA: case NJ_HYOUSENSOU: case NJ_HUUJIN: case AB_HIGHNESSHEAL: case AB_DUPLELIGHT_MAGIC: case WM_METALICSOUND: case KO_KAIHOU: case MH_ERASER_CUTTER: case AG_ASTRAL_STRIKE: case AG_ASTRAL_STRIKE_ATK: case AG_DESTRUCTIVE_HURRICANE_CLIMAX: case CD_ARBITRIUM: skill_attack(BF_MAGIC,src,src,bl,skill_id,skill_lv,tick,flag); break; case IG_JUDGEMENT_CROSS: case TR_SOUNDBLEND: clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); skill_attack(BF_MAGIC, src, src, bl, skill_id, skill_lv, tick, flag); break; case AG_DEADLY_PROJECTION: sc_start(src, bl, SC_DEADLY_DEFEASANCE, 100, skill_lv, skill_get_time(skill_id, skill_lv)); skill_attack(BF_MAGIC, src, src, bl, skill_id, skill_lv, tick, flag); break; case NPC_MAGICALATTACK: skill_attack(BF_MAGIC,src,src,bl,skill_id,skill_lv,tick,flag); sc_start(src,src,SC_MAGICALATTACK,100,skill_lv,skill_get_time(skill_id,skill_lv)); break; case HVAN_CAPRICE: //[blackhole89] { int ran=rnd()%4; int sid = 0; switch(ran) { case 0: sid=MG_COLDBOLT; break; case 1: sid=MG_FIREBOLT; break; case 2: sid=MG_LIGHTNINGBOLT; break; case 3: sid=WZ_EARTHSPIKE; break; } skill_attack(BF_MAGIC,src,src,bl,sid,skill_lv,tick,flag|SD_LEVEL); } break; case WZ_WATERBALL: //Deploy waterball cells, these are used and turned into waterballs via the timerskill skill_unitsetting(src, skill_id, skill_lv, src->x, src->y, 0); skill_addtimerskill(src, tick, bl->id, src->x, src->y, skill_id, skill_lv, 0, flag); break; case WZ_JUPITEL: //Jupitel Thunder is delayed by 150ms, you can cast another spell before the knockback skill_addtimerskill(src, tick+TIMERSKILL_INTERVAL, bl->id, 0, 0, skill_id, skill_lv, 1, flag); break; case PR_BENEDICTIO: //Should attack undead and demons. [Skotlex] if (battle_check_undead(tstatus->race, tstatus->def_ele) || tstatus->race == RC_DEMON) skill_attack(BF_MAGIC, src, src, bl, skill_id, skill_lv, tick, flag); break; case SJ_NOVAEXPLOSING: skill_attack(BF_MISC, src, src, bl, skill_id, skill_lv, tick, flag); // We can end Dimension here since the cooldown code is processed before this point. if (sc && sc->getSCE(SC_DIMENSION)) status_change_end(src, SC_DIMENSION); else // Dimension not active? Activate the 2 second skill block penalty. sc_start(src, &sd->bl, SC_NOVAEXPLOSING, 100, skill_lv, skill_get_time(skill_id, skill_lv)); break; case SP_SOULEXPLOSION: if (!(tsc && (tsc->getSCE(SC_SPIRIT) || tsc->getSCE(SC_SOULGOLEM) || tsc->getSCE(SC_SOULSHADOW) || tsc->getSCE(SC_SOULFALCON) || tsc->getSCE(SC_SOULFAIRY))) || tstatus->hp < 10 * tstatus->max_hp / 100) { // Requires target to have a soul link and more then 10% of MaxHP. // With this skill requiring a soul link, and the target to have more then 10% if MaxHP, I wonder // if the cooldown still happens after it fails. Need a confirm. [Rytech] if (sd) clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); break; } skill_attack(BF_MISC, src, src, bl, skill_id, skill_lv, tick, flag); break; case SL_SMA: status_change_end(src, SC_SMA); case SL_STIN: case SL_STUN: case SP_SPA: if (sd && !battle_config.allow_es_magic_pc && bl->type != BL_MOB) { status_change_start(src,src,SC_STUN,10000,skill_lv,0,0,0,500,SCSTART_NOTICKDEF|SCSTART_NORATEDEF); clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); break; } skill_attack(BF_MAGIC,src,src,bl,skill_id,skill_lv,tick,flag); break; case NPC_DARKBREATH: clif_emotion(src,ET_ANGER); if (rnd() % 2 == 0) break; // 50% chance case SN_FALCONASSAULT: #ifndef RENEWAL case PA_PRESSURE: case CR_ACIDDEMONSTRATION: #endif case TF_THROWSTONE: case NPC_SMOKING: case GS_FLING: case NJ_ZENYNAGE: case GN_THORNS_TRAP: case RL_B_TRAP: skill_attack(skill_get_type(skill_id),src,src,bl,skill_id,skill_lv,tick,flag); break; #ifdef RENEWAL case NJ_ISSEN: { short x, y; short dir = map_calc_dir(src, bl->x, bl->y); // Move 2 cells (From target) if (dir > 0 && dir < 4) x = -2; else if (dir > 4) x = 2; else x = 0; if (dir > 2 && dir < 6) y = -2; else if (dir == 7 || dir < 2) y = 2; else y = 0; // Doesn't have slide effect in GVG if (skill_check_unit_movepos(5, src, bl->x + x, bl->y + y, 1, 1)) { clif_blown(src); clif_spiritball(src); } skill_attack(BF_MISC, src, src, bl, skill_id, skill_lv, tick, flag); status_set_hp(src, umax(status_get_max_hp(src) / 100, 1), 0); status_change_end(src, SC_NEN); status_change_end(src, SC_HIDING); } break; #endif case RK_DRAGONBREATH_WATER: case RK_DRAGONBREATH: case NPC_DRAGONBREATH: if( tsc && tsc->getSCE(SC_HIDING) ) clif_skill_nodamage(src,src,skill_id,skill_lv,1); else { skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); } break; case NPC_SELFDESTRUCTION: if( tsc && tsc->getSCE(SC_HIDING) ) break; case HVAN_EXPLOSION: if (src != bl) skill_attack(BF_MISC,src,src,bl,skill_id,skill_lv,tick,flag); break; // Celest case PF_SOULBURN: if (rnd()%100 < (skill_lv < 5 ? 30 + skill_lv * 10 : 70)) { clif_skill_nodamage(src,bl,skill_id,skill_lv,1); if (skill_lv == 5) skill_attack(BF_MAGIC,src,src,bl,skill_id,skill_lv,tick,flag); status_percent_damage(src, bl, 0, 100, false); } else { clif_skill_nodamage(src,src,skill_id,skill_lv,1); if (skill_lv == 5) skill_attack(BF_MAGIC,src,src,src,skill_id,skill_lv,tick,flag); status_percent_damage(src, src, 0, 100, false); } break; case NPC_BLOODDRAIN: case NPC_ENERGYDRAIN: { int heal = (int)skill_attack( (skill_id == NPC_BLOODDRAIN) ? BF_WEAPON : BF_MAGIC, src, src, bl, skill_id, skill_lv, tick, flag); if (heal > 0){ clif_skill_nodamage(NULL, src, AL_HEAL, heal, 1); status_heal(src, heal, 0, 0); } } break; case GS_BULLSEYE: skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); break; case NJ_KASUMIKIRI: if (skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag) > 0) sc_start(src,src,SC_HIDING,100,skill_lv,skill_get_time(skill_id,skill_lv)); break; case NJ_KIRIKAGE: if( !map_flag_gvg2(src->m) && !map_getmapflag(src->m, MF_BATTLEGROUND) ) { //You don't move on GVG grounds. short x, y; map_search_freecell(bl, 0, &x, &y, 1, 1, 0); if (unit_movepos(src, x, y, 0, 0)) { clif_blown(src); } } status_change_end(src, SC_HIDING); skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); break; case RK_PHANTOMTHRUST: case NPC_PHANTOMTHRUST: unit_setdir(src,map_calc_dir(src, bl->x, bl->y)); clif_skill_nodamage(src,bl,skill_id,skill_lv,1); skill_blown(src,bl,distance_bl(src,bl)-1,unit_getdir(src),BLOWN_NONE); if( battle_check_target(src,bl,BCT_ENEMY) > 0 ) skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); break; case RK_WINDCUTTER: case RK_STORMBLAST: if( flag&1 ) skill_attack(skill_get_type(skill_id),src,src,bl,skill_id,skill_lv,tick,flag); else { clif_skill_nodamage(src,bl,skill_id,skill_lv,1); map_foreachinallrange(skill_area_sub, bl,skill_get_splash(skill_id, skill_lv),BL_CHAR,src,skill_id,skill_lv,tick, flag|BCT_ENEMY|1,skill_castend_nodamage_id); } break; case GC_DARKILLUSION: { short x, y; short dir = map_calc_dir(src,bl->x,bl->y); if( dir > 0 && dir < 4) x = 2; else if( dir > 4 ) x = -2; else x = 0; if( dir > 2 && dir < 6 ) y = 2; else if( dir == 7 || dir < 2 ) y = -2; else y = 0; if( unit_movepos(src, bl->x+x, bl->y+y, 1, 1) ) { clif_blown(src); skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); if( rnd()%100 < 4 * skill_lv ) skill_castend_damage_id(src,bl,GC_CROSSIMPACT,skill_lv,tick,flag); } } break; case GC_CROSSRIPPERSLASHER: if( sd && !(sc && sc->getSCE(SC_ROLLINGCUTTER)) ) clif_skill_fail(sd,skill_id,USESKILL_FAIL_CONDITION,0); else { skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); } break; case GC_CROSSIMPACT: { uint8 dir = map_calc_dir(bl, src->x, src->y); // dir based on target as we move player based on target location if (skill_check_unit_movepos(0, src, bl->x + dirx[dir], bl->y + diry[dir], 1, 1)) { clif_blown(src); skill_attack(BF_WEAPON, src, src, bl, skill_id, skill_lv, tick, flag); } else { if (sd) clif_skill_fail(sd, skill_id, USESKILL_FAIL, 0); } break; } case GC_PHANTOMMENACE: if (flag&1) { // Only Hits Invisible Targets if(tsc && (tsc->option&(OPTION_HIDE|OPTION_CLOAK|OPTION_CHASEWALK) || tsc->getSCE(SC_CAMOUFLAGE) || tsc->getSCE(SC_STEALTHFIELD))) { status_change_end(bl, SC_CLOAKINGEXCEED); skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); } if (tsc && tsc->getSCE(SC__SHADOWFORM) && rnd() % 100 < 100 - tsc->getSCE(SC__SHADOWFORM)->val1 * 10) // [100 - (Skill Level x 10)] % status_change_end(bl, SC__SHADOWFORM); // Should only end, no damage dealt. } break; case GC_DARKCROW: skill_attack(BF_WEAPON, src, src, bl, skill_id, skill_lv, tick, flag); sc_start(src, bl, SC_DARKCROW, 100, skill_lv, skill_get_time(skill_id, skill_lv)); // Should be applied even on miss break; case WL_DRAINLIFE: { int heal = (int)skill_attack(skill_get_type(skill_id), src, src, bl, skill_id, skill_lv, tick, flag); int rate = 70 + 5 * skill_lv; heal = heal * (5 + 5 * skill_lv) / 100; if( bl->type == BL_SKILL ) heal = 0; // Don't absorb heal from Ice Walls or other skill units. if( heal && rnd()%100 < rate ) { status_heal(src, heal, 0, 0); clif_skill_nodamage(NULL, src, AL_HEAL, heal, 1); } } break; case WL_TETRAVORTEX_FIRE: case WL_TETRAVORTEX_WATER: case WL_TETRAVORTEX_WIND: case WL_TETRAVORTEX_GROUND: skill_addtimerskill(src, tick + skill_area_temp[0] * 200, bl->id, skill_area_temp[1], 0, skill_id, skill_lv, 0, flag); break; case WL_TETRAVORTEX: if (sd == nullptr) { // Monster usage uint8 i = 0; const static std::vector<std::vector<uint16>> tetra_skills = { { WL_TETRAVORTEX_FIRE, 1 }, { WL_TETRAVORTEX_WIND, 4 }, { WL_TETRAVORTEX_WATER, 2 }, { WL_TETRAVORTEX_GROUND, 8 } }; for (const auto &skill : tetra_skills) { if (skill_lv > 5) { skill_area_temp[0] = i; skill_area_temp[1] = skill[1]; map_foreachinallrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), BL_CHAR, src, skill[0], skill_lv, tick, flag | BCT_ENEMY, skill_castend_damage_id); } else skill_addtimerskill(src, tick + i * 200, bl->id, skill[1], 0, skill[0], skill_lv, i, flag); i++; } } else if (sc) { // No SC? No spheres int i, k = 0; if (sc->getSCE(SC_SPHERE_5)) // If 5 spheres, remove last one (based on reverse order) and only do 4 actions (Official behavior) status_change_end(src, SC_SPHERE_1); for (i = SC_SPHERE_5; i >= SC_SPHERE_1; i--) { // Loop should always be 4 for regular players, but unconditional_skill could be less if (sc->getSCE(static_cast<sc_type>(i)) == nullptr) continue; uint16 subskill = 0; switch (sc->getSCE(static_cast<sc_type>(i))->val1) { case WLS_FIRE: subskill = WL_TETRAVORTEX_FIRE; k |= 1; break; case WLS_WIND: subskill = WL_TETRAVORTEX_WIND; k |= 4; break; case WLS_WATER: subskill = WL_TETRAVORTEX_WATER; k |= 2; break; case WLS_STONE: subskill = WL_TETRAVORTEX_GROUND; k |= 8; break; } if (skill_lv > 5) { skill_area_temp[0] = abs(i - SC_SPHERE_5); skill_area_temp[1] = k; map_foreachinallrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), BL_CHAR, src, subskill, skill_lv, tick, flag | BCT_ENEMY, skill_castend_damage_id); } else skill_addtimerskill(src, tick + abs(i - SC_SPHERE_5) * 200, bl->id, k, 0, subskill, skill_lv, abs(i - SC_SPHERE_5), flag); status_change_end(src, static_cast<sc_type>(i)); } } break; case WL_RELEASE: if (sc == nullptr) break; if (sd) { int i; #ifndef RENEWAL skill_toggle_magicpower(src, skill_id); // No hit will be amplified #endif if (skill_lv == 1) { // SpellBook if (sc->getSCE(SC_FREEZE_SP) == nullptr) break; bool found_spell = false; for (i = SC_MAXSPELLBOOK; i >= SC_SPELLBOOK1; i--) { // List all available spell to be released if (sc->getSCE(i) != nullptr) { found_spell = true; break; } } if (!found_spell) break; // Now extract the data from the preserved spell uint16 pres_skill_id = sc->getSCE(i)->val1; uint16 pres_skill_lv = sc->getSCE(i)->val2; uint16 point = sc->getSCE(i)->val3; status_change_end(src, static_cast<sc_type>(i)); if( sc->getSCE(SC_FREEZE_SP)->val2 > point ) sc->getSCE(SC_FREEZE_SP)->val2 -= point; else // Last spell to be released status_change_end(src, SC_FREEZE_SP); if( !skill_check_condition_castbegin(sd, pres_skill_id, pres_skill_lv) ) break; // Get the requirement for the preserved skill skill_consume_requirement(sd, pres_skill_id, pres_skill_lv, 1); switch( skill_get_casttype(pres_skill_id) ) { case CAST_GROUND: skill_castend_pos2(src, bl->x, bl->y, pres_skill_id, pres_skill_lv, tick, 0); break; case CAST_NODAMAGE: skill_castend_nodamage_id(src, bl, pres_skill_id, pres_skill_lv, tick, 0); break; case CAST_DAMAGE: skill_castend_damage_id(src, bl, pres_skill_id, pres_skill_lv, tick, 0); break; } sd->ud.canact_tick = i64max(tick + skill_delayfix(src, pres_skill_id, pres_skill_lv), sd->ud.canact_tick); clif_status_change(src, EFST_POSTDELAY, 1, skill_delayfix(src, pres_skill_id, pres_skill_lv), 0, 0, 0); int cooldown = pc_get_skillcooldown(sd,pres_skill_id, pres_skill_lv); if( cooldown > 0 ) skill_blockpc_start(sd, pres_skill_id, cooldown); } else { // Summoned Balls for (i = SC_SPHERE_5; i >= SC_SPHERE_1; i--) { if (sc->getSCE(static_cast<sc_type>(i)) == nullptr) continue; int skele = WL_RELEASE - 5 + sc->getSCE(static_cast<sc_type>(i))->val1 - WLS_FIRE; // Convert Ball Element into Skill ATK for balls // WL_SUMMON_ATK_FIRE, WL_SUMMON_ATK_WIND, WL_SUMMON_ATK_WATER, WL_SUMMON_ATK_GROUND skill_addtimerskill(src, tick + (t_tick)status_get_adelay(src) * abs(i - SC_SPHERE_1), bl->id, 0, 0, skele, sc->getSCE(static_cast<sc_type>(i))->val2, BF_MAGIC, flag | SD_LEVEL); status_change_end(src, static_cast<sc_type>(i)); // Eliminate ball } clif_skill_nodamage(src, bl, skill_id, 0, 1); } } break; case WL_FROSTMISTY: // Causes Freezing status through walls. sc_start(src, bl, SC_FREEZING, 25 + 5 * skill_lv, skill_lv, skill_get_time(skill_id, skill_lv)); sc_start(src, bl, SC_MISTY_FROST, 100, skill_lv, skill_get_time2(skill_id, skill_lv)); // Doesn't deal damage through non-shootable walls. if( !battle_config.skill_wall_check || (battle_config.skill_wall_check && path_search(NULL,src->m,src->x,src->y,bl->x,bl->y,1,CELL_CHKWALL)) ) skill_attack(BF_MAGIC,src,src,bl,skill_id,skill_lv,tick,flag|SD_ANIMATION); break; case WL_HELLINFERNO: if (flag & 1) { skill_attack(BF_MAGIC, src, src, bl, skill_id, skill_lv, tick, flag); skill_addtimerskill(src, tick + 300, bl->id, 0, 0, skill_id, skill_lv, BF_MAGIC, flag | 2); } else { clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); map_foreachinrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), BL_CHAR, src, skill_id, skill_lv, tick, flag | BCT_ENEMY | SD_SPLASH | 1, skill_castend_damage_id); } break; case RA_WUGSTRIKE: if( sd && pc_isridingwug(sd) ){ short x[8]={0,-1,-1,-1,0,1,1,1}; short y[8]={1,1,0,-1,-1,-1,0,1}; uint8 dir = map_calc_dir(bl, src->x, src->y); if( unit_movepos(src, bl->x+x[dir], bl->y+y[dir], 1, 1) ) { clif_blown(src); skill_attack(BF_WEAPON, src, src, bl, skill_id, skill_lv, tick, flag); } break; } case RA_WUGBITE: if( path_search(NULL,src->m,src->x,src->y,bl->x,bl->y,1,CELL_CHKNOREACH) ) { skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); }else if( sd && skill_id == RA_WUGBITE ) // Only RA_WUGBITE has the skill fail message. clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); break; case RA_SENSITIVEKEEN: if( bl->type != BL_SKILL ) { // Only Hits Invisible Targets if (tsc && ((tsc->option&(OPTION_HIDE|OPTION_CLOAK|OPTION_CHASEWALK)) || tsc->getSCE(SC_CAMOUFLAGE) || tsc->getSCE(SC_STEALTHFIELD))) { status_change_end(bl, SC_CLOAKINGEXCEED); skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); } if (tsc && tsc->getSCE(SC__SHADOWFORM) && rnd() % 100 < 100 - tsc->getSCE(SC__SHADOWFORM)->val1 * 10) // [100 - (Skill Level x 10)] % status_change_end(bl, SC__SHADOWFORM); // Should only end, no damage dealt. } else { skill_unit *su = BL_CAST(BL_SKILL, bl); std::shared_ptr<s_skill_unit_group> sg; if (su && (sg = su->group) && skill_get_inf2(sg->skill_id, INF2_ISTRAP)) { if( !(sg->unit_id == UNT_USED_TRAPS || (sg->unit_id == UNT_ANKLESNARE && sg->val2 != 0 )) ) { struct item item_tmp; memset(&item_tmp,0,sizeof(item_tmp)); item_tmp.nameid = sg->item_id?sg->item_id:ITEMID_TRAP; item_tmp.identify = 1; if( item_tmp.nameid ) map_addflooritem(&item_tmp,1,bl->m,bl->x,bl->y,0,0,0,4,0); } skill_delunit(su); } } break; case NC_INFRAREDSCAN: if( flag&1 ) { status_change_end(bl, SC_HIDING); status_change_end(bl, SC_CLOAKING); status_change_end(bl, SC_CLOAKINGEXCEED); status_change_end(bl, SC_CAMOUFLAGE); status_change_end(bl, SC_NEWMOON); if (tsc && tsc->getSCE(SC__SHADOWFORM) && rnd() % 100 < 100 - tsc->getSCE(SC__SHADOWFORM)->val1 * 10) // [100 - (Skill Level x 10)] % status_change_end(bl, SC__SHADOWFORM); sc_start(src,bl, SC_INFRAREDSCAN, 10000, skill_lv, skill_get_time(skill_id, skill_lv)); } else { clif_skill_damage(src,bl,tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, DMG_SINGLE); map_foreachinallrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), splash_target(src), src, skill_id, skill_lv, tick, flag|BCT_ENEMY|SD_SPLASH|1, skill_castend_damage_id); } break; case SC_FATALMENACE: if( flag&1 ) skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); else { map_foreachinrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), splash_target(src), src, skill_id, skill_lv, tick, flag|BCT_ENEMY|1, skill_castend_damage_id); clif_skill_damage(src,src,tick,status_get_amotion(src),0,-30000,1,skill_id,skill_lv,DMG_SINGLE); } break; case NPC_FATALMENACE: // todo should it teleport the target ? if( flag&1 ) skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); else { short x, y; map_search_freecell(src, 0, &x, &y, -1, -1, 0); // Destination area skill_area_temp[4] = x; skill_area_temp[5] = y; map_foreachinrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), splash_target(src), src, skill_id, skill_lv, tick, flag|BCT_ENEMY|1, skill_castend_damage_id); skill_addtimerskill(src,tick + 800,src->id,x,y,skill_id,skill_lv,0,flag); // To teleport Self clif_skill_damage(src,src,tick,status_get_amotion(src),0,-30000,1,skill_id,skill_lv,DMG_SINGLE); } break; case LG_PINPOINTATTACK: if (skill_check_unit_movepos(5, src, bl->x, bl->y, 1, 1)) clif_blown(src); skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); break; case SR_KNUCKLEARROW: // Holds current direction of bl/target to src/attacker before the src is moved to bl location dir_ka = map_calc_dir(bl, src->x, src->y); // Has slide effect if (skill_check_unit_movepos(5, src, bl->x, bl->y, 1, 1)) skill_blown(src, src, 1, (dir_ka + 4) % 8, BLOWN_NONE); // Target position is actually one cell next to the target skill_addtimerskill(src, tick + 300, bl->id, 0, 0, skill_id, skill_lv, BF_WEAPON, flag|SD_LEVEL|2); break; case SR_HOWLINGOFLION: status_change_end(bl, SC_SWINGDANCE); status_change_end(bl, SC_SYMPHONYOFLOVER); status_change_end(bl, SC_MOONLITSERENADE); status_change_end(bl, SC_RUSHWINDMILL); status_change_end(bl, SC_ECHOSONG); status_change_end(bl, SC_HARMONIZE); status_change_end(bl, SC_NETHERWORLD); status_change_end(bl, SC_VOICEOFSIREN); status_change_end(bl, SC_DEEPSLEEP); status_change_end(bl, SC_SIRCLEOFNATURE); status_change_end(bl, SC_GLOOMYDAY); status_change_end(bl, SC_GLOOMYDAY_SK); status_change_end(bl, SC_SONGOFMANA); status_change_end(bl, SC_DANCEWITHWUG); status_change_end(bl, SC_SATURDAYNIGHTFEVER); status_change_end(bl, SC_LERADSDEW); status_change_end(bl, SC_MELODYOFSINK); status_change_end(bl, SC_BEYONDOFWARCRY); status_change_end(bl, SC_UNLIMITEDHUMMINGVOICE); skill_attack(BF_WEAPON, src, src, bl, skill_id, skill_lv, tick, flag|SD_ANIMATION); break; case SR_EARTHSHAKER: if( flag&1 ) { //by default cloaking skills are remove by aoe skills so no more checking/removing except hiding and cloaking exceed. skill_attack(BF_WEAPON, src, src, bl, skill_id, skill_lv, tick, flag); status_change_end(bl, SC_CLOAKINGEXCEED); if (tsc && tsc->getSCE(SC__SHADOWFORM) && rnd() % 100 < 100 - tsc->getSCE(SC__SHADOWFORM)->val1 * 10) // [100 - (Skill Level x 10)] % status_change_end(bl, SC__SHADOWFORM); } else { map_foreachinrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), BL_CHAR|BL_SKILL, src, skill_id, skill_lv, tick, flag|BCT_ENEMY|SD_SPLASH|1, skill_castend_damage_id); clif_skill_damage(src, src, tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, DMG_SINGLE); } break; case SR_TIGERCANNON: if (flag & 1) { skill_attack(BF_WEAPON, src, src, bl, skill_id, skill_lv, tick, flag); } else if (sd) { if (sc && sc->getSCE(SC_COMBO) && sc->getSCE(SC_COMBO)->val1 == SR_FALLENEMPIRE && !sc->getSCE(SC_FLASHCOMBO)) flag |= 8; // Only apply Combo bonus when Tiger Cannon is not used through Flash Combo map_foreachinrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), BL_CHAR | BL_SKILL, src, skill_id, skill_lv, tick, flag | BCT_ENEMY | SD_SPLASH | 1, skill_castend_damage_id); } break; case WM_REVERBERATION: if (flag & 1) skill_attack(skill_get_type(skill_id), src, src, bl, skill_id, skill_lv, tick, flag); else { clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); map_foreachinallrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), BL_CHAR|BL_SKILL, src, skill_id, skill_lv, tick, flag|BCT_ENEMY|SD_SPLASH|1, skill_castend_damage_id); battle_consume_ammo(sd, skill_id, skill_lv); // Consume here since Magic/Misc attacks reset arrow_atk } break; case NPC_POISON_BUSTER: if( tsc && tsc->getSCE(SC_POISON) ) { skill_attack(skill_get_type(skill_id), src, src, bl, skill_id, skill_lv, tick, flag); status_change_end(bl, SC_POISON); } else if( sd ) clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); break; case KO_JYUMONJIKIRI: { short x, y; short dir = map_calc_dir(src,bl->x,bl->y); if (dir > 0 && dir < 4) x = 2; else if (dir > 4) x = -2; else x = 0; if (dir > 2 && dir < 6) y = 2; else if (dir == 7 || dir < 2) y = -2; else y = 0; if (unit_movepos(src,bl->x + x,bl->y + y,1,1)) { clif_blown(src); skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); } } break; case EL_FIRE_BOMB: case EL_FIRE_WAVE: case EL_WATER_SCREW: case EL_HURRICANE: case EL_TYPOON_MIS: if( flag&1 ) skill_attack(skill_get_type(skill_id+1),src,src,bl,skill_id+1,skill_lv,tick,flag); else { int i = skill_get_splash(skill_id,skill_lv); clif_skill_nodamage(src,battle_get_master(src),skill_id,skill_lv,1); clif_skill_damage(src, bl, tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, DMG_SINGLE); if( rnd()%100 < 30 ) map_foreachinrange(skill_area_sub,bl,i,BL_CHAR,src,skill_id,skill_lv,tick,flag|BCT_ENEMY|1,skill_castend_damage_id); else skill_attack(skill_get_type(skill_id),src,src,bl,skill_id,skill_lv,tick,flag); } break; case EL_ROCK_CRUSHER: clif_skill_nodamage(src,battle_get_master(src),skill_id,skill_lv,1); clif_skill_damage(src, src, tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, DMG_SINGLE); if( rnd()%100 < 50 ) skill_attack(BF_MAGIC,src,src,bl,skill_id,skill_lv,tick,flag); else skill_attack(BF_WEAPON,src,src,bl,EL_ROCK_CRUSHER_ATK,skill_lv,tick,flag); break; case EL_STONE_RAIN: if( flag&1 ) skill_attack(skill_get_type(skill_id),src,src,bl,skill_id,skill_lv,tick,flag); else { int i = skill_get_splash(skill_id,skill_lv); clif_skill_nodamage(src,battle_get_master(src),skill_id,skill_lv,1); clif_skill_damage(src, src, tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, DMG_SINGLE); if( rnd()%100 < 30 ) map_foreachinrange(skill_area_sub,bl,i,BL_CHAR,src,skill_id,skill_lv,tick,flag|BCT_ENEMY|1,skill_castend_damage_id); else skill_attack(skill_get_type(skill_id),src,src,bl,skill_id,skill_lv,tick,flag); } break; case EL_FIRE_ARROW: case EL_ICE_NEEDLE: case EL_WIND_SLASH: case EL_STONE_HAMMER: clif_skill_nodamage(src,battle_get_master(src),skill_id,skill_lv,1); clif_skill_damage(src, bl, tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, DMG_SINGLE); skill_attack(skill_get_type(skill_id),src,src,bl,skill_id,skill_lv,tick,flag); break; case EL_TIDAL_WEAPON: if( src->type == BL_ELEM ) { s_elemental_data *ele = BL_CAST(BL_ELEM,src); status_change *tsc_ele = status_get_sc(&ele->bl); sc_type type = SC_TIDAL_WEAPON_OPTION, type2 = SC_TIDAL_WEAPON; clif_skill_nodamage(src,battle_get_master(src),skill_id,skill_lv,1); clif_skill_damage(src, src, tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, DMG_SINGLE); if( (tsc_ele && tsc_ele->getSCE(type2)) || (tsc && tsc->getSCE(type)) ) { status_change_end(battle_get_master(src),type); status_change_end(src,type2); } if( rnd()%100 < 50 ) skill_attack(skill_get_type(skill_id),src,src,bl,skill_id,skill_lv,tick,flag); else { sc_start(src,src,type2,100,skill_lv,skill_get_time(skill_id,skill_lv)); sc_start(src,battle_get_master(src),type,100,ele->bl.id,skill_get_time(skill_id,skill_lv)); } clif_skill_nodamage(src,src,skill_id,skill_lv,1); } break; //recursive homon skill case MH_MAGMA_FLOW: case MH_HEILIGE_STANGE: if(flag&1){ if((skill_id == MH_MAGMA_FLOW) && ((rnd()%100)>(3*skill_lv)) ) break;//chance to not trigger atk for magma skill_attack(skill_get_type(skill_id), src, src, bl, skill_id, skill_lv, tick, flag); } else map_foreachinrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), BL_CHAR|BL_SKILL, src, skill_id, skill_lv, tick, flag | BCT_ENEMY | SD_SPLASH | 1, skill_castend_damage_id); break; case MH_STAHL_HORN: case MH_NEEDLE_OF_PARALYZE: case MH_SONIC_CRAW: case MH_MIDNIGHT_FRENZY: case MH_SILVERVEIN_RUSH: skill_attack(skill_get_type(skill_id), src, src, bl, skill_id, skill_lv, tick, flag); break; case MH_TINDER_BREAKER: case MH_CBC: case MH_EQC: { TBL_HOM *hd = BL_CAST(BL_HOM,src); int duration = max(skill_lv, (status_get_str(src) / 7 - status_get_str(bl) / 10)) * 1000; //Yommy formula sc_type type; if( skill_id == MH_TINDER_BREAKER ){ type = SC_TINDER_BREAKER2; if( unit_movepos(src, bl->x, bl->y, 1, 1) ){ clif_blown(src); clif_skill_poseffect(src,skill_id,skill_lv,bl->x,bl->y,tick); } }else if( skill_id == MH_CBC ){ type = SC_CBC; }else if( skill_id == MH_EQC ){ type = SC_EQC; } clif_skill_nodamage(src,bl,skill_id,skill_lv,sc_start4(src,bl,type,100,skill_lv,src->id,0,0,duration)); skill_attack(skill_get_type(skill_id),src,src,bl,skill_id,skill_lv,tick,flag); } break; case RL_H_MINE: if (!(flag&1)) { // Direct attack if (!sd || !sd->flicker) { if (skill_attack(skill_get_type(skill_id), src, src, bl, skill_id, skill_lv, tick, flag)) status_change_start(src, bl, SC_H_MINE, 10000, skill_id, 0, 0, 0, skill_get_time(skill_id,skill_lv), SCSTART_NOAVOID|SCSTART_NOTICKDEF|SCSTART_NORATEDEF); break; } // Triggered by RL_FLICKER if (sd && sd->flicker && tsc && tsc->getSCE(SC_H_MINE) && tsc->getSCE(SC_H_MINE)->val2 == src->id) { // Splash damage around it! map_foreachinrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), BL_CHAR|BL_SKILL, src, skill_id, skill_lv, tick, flag|BCT_ENEMY|1, skill_castend_damage_id); flag |= 1; // Don't consume requirement tsc->getSCE(SC_H_MINE)->val3 = 1; // Mark the SC end because not expired status_change_end(bl, SC_H_MINE); sc_start4(src, bl, SC_BURNING, 10 * skill_lv, skill_lv, 1000, src->id, 0, skill_get_time2(skill_id,skill_lv)); } } else skill_attack(skill_get_type(skill_id), src, src, bl, skill_id, skill_lv, tick, flag); if (sd && sd->flicker) flag |= 1; // Don't consume requirement break; case RL_QD_SHOT: if (skill_area_temp[1] == bl->id) break; if (flag&1 && tsc && tsc->getSCE(SC_C_MARKER)) skill_attack(skill_get_type(skill_id), src, src, bl, skill_id, skill_lv, tick, flag|SD_ANIMATION); break; case RL_D_TAIL: case RL_HAMMER_OF_GOD: if (flag&1) skill_attack(skill_get_type(skill_id), src, src, bl, skill_id, skill_lv, tick, flag|SD_ANIMATION); else { if (sd && tsc && tsc->getSCE(SC_C_MARKER)) { int i; ARR_FIND(0, MAX_SKILL_CRIMSON_MARKER, i, sd->c_marker[i] == bl->id); if (i < MAX_SKILL_CRIMSON_MARKER) flag |= 8; } if (skill_id == RL_HAMMER_OF_GOD) clif_skill_poseffect(src, skill_id, 1, bl->x, bl->y, gettick()); else clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); map_foreachinrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), BL_CHAR, src, skill_id, skill_lv, tick, flag|BCT_ENEMY|SD_SPLASH|1, skill_castend_damage_id); } break; case SU_SCAROFTAROU: sc_start(src, bl, SC_BITESCAR, 10, skill_lv, skill_get_time(skill_id, skill_lv)); //! TODO: What's the activation chance for the Bite effect? skill_attack(skill_get_type(skill_id), src, src, bl, skill_id, skill_lv, tick, flag); break; case SU_SV_STEMSPEAR: if (sd && pc_checkskill(sd, SU_SPIRITOFLAND)) sc_start(src, src, SC_DORAM_WALKSPEED, 100, 50, skill_get_time(SU_SPIRITOFLAND, 1)); skill_attack(skill_get_type(skill_id), src, src, bl, skill_id, skill_lv, tick, flag); break; case 0:/* no skill - basic/normal attack */ if(sd) { if (flag & 3){ if (bl->id != skill_area_temp[1]) skill_attack(BF_WEAPON, src, src, bl, skill_id, skill_lv, tick, SD_LEVEL|flag); } else { skill_area_temp[1] = bl->id; map_foreachinallrange(skill_area_sub, bl, sd->bonus.splash_range, BL_CHAR, src, skill_id, skill_lv, tick, flag | BCT_ENEMY | 1, skill_castend_damage_id); flag|=1; //Set flag to 1 so ammo is not double-consumed. [Skotlex] } } break; case SJ_FALLINGSTAR_ATK: if (sd) { // If a player used the skill it will search for targets marked by that player. if (tsc && tsc->getSCE(SC_FLASHKICK) && tsc->getSCE(SC_FLASHKICK)->val4 == 1) { // Mark placed by a player. int8 i = 0; ARR_FIND(0, MAX_STELLAR_MARKS, i, sd->stellar_mark[i] == bl->id); if (i < MAX_STELLAR_MARKS) { skill_attack(BF_WEAPON, src, src, bl, skill_id, skill_lv, tick, flag); skill_castend_damage_id(src, bl, SJ_FALLINGSTAR_ATK2, skill_lv, tick, 0); } } } else if ( tsc && tsc->getSCE(SC_FLASHKICK) && tsc->getSCE(SC_FLASHKICK)->val4 == 2 ) { // Mark placed by a monster. // If a monster used the skill it will search for targets marked by any monster since they can't track their own targets. skill_attack(BF_WEAPON, src, src, bl, skill_id, skill_lv, tick, flag); skill_castend_damage_id(src, bl, SJ_FALLINGSTAR_ATK2, skill_lv, tick, 0); } break; case SJ_FLASHKICK: { map_session_data *tsd = BL_CAST(BL_PC, bl); struct mob_data *md = BL_CAST(BL_MOB, src), *tmd = BL_CAST(BL_MOB, bl); // Only players and monsters can be tagged....I think??? [Rytech] // Lets only allow players and monsters to use this skill for safety reasons. if ((!tsd && !tmd) || !sd && !md) { if (sd) clif_skill_fail(sd, skill_id, USESKILL_FAIL, 0); break; } // Check if the target is already tagged by another source. if ((tsd && tsd->sc.getSCE(SC_FLASHKICK) && tsd->sc.getSCE(SC_FLASHKICK)->val1 != src->id) || (tmd && tmd->sc.getSCE(SC_FLASHKICK) && tmd->sc.getSCE(SC_FLASHKICK)->val1 != src->id)) { // Same as the above check, but for monsters. // Can't tag a player that was already tagged from another source. if (sd) clif_skill_fail(sd,skill_id, USESKILL_FAIL, 0); map_freeblock_unlock(); return 1; } if (sd) { // Tagging the target. int i; ARR_FIND(0, MAX_STELLAR_MARKS, i, sd->stellar_mark[i] == bl->id); if (i == MAX_STELLAR_MARKS) { ARR_FIND(0, MAX_STELLAR_MARKS, i, sd->stellar_mark[i] == 0); if (i == MAX_STELLAR_MARKS) { // Max number of targets tagged. Fail the skill. clif_skill_fail(sd, skill_id, USESKILL_FAIL, 0); map_freeblock_unlock(); return 1; } } // Tag the target only if damage was done. If it deals no damage, it counts as a miss and won't tag. // Note: Not sure if it works like this in official but you can't mark on something you can't // hit, right? For now well just use this logic until we can get a confirm on if it does this or not. [Rytech] if (skill_attack(BF_WEAPON, src, src, bl, skill_id, skill_lv, tick, flag) > 0) { // Add the ID of the tagged target to the player's tag list and start the status on the target. sd->stellar_mark[i] = bl->id; // Val4 flags if the status was applied by a player or a monster. // This will be important for other skills that work together with this one. // 1 = Player, 2 = Monster. // Note: Because the attacker's ID and the slot number is handled here, we have to // apply the status here. We can't pass this data to skill_additional_effect. sc_start4(src, bl, SC_FLASHKICK, 100, src->id, i, skill_lv, 1, skill_get_time(skill_id, skill_lv)); } } else if (md) { // Monsters can't track with this skill. Just give the status. if (skill_attack(BF_WEAPON, src, src, bl, skill_id, skill_lv, tick, flag) > 0) sc_start4(src, bl, SC_FLASHKICK, 100, 0, 0, skill_lv, 2, skill_get_time(skill_id, skill_lv)); } } break; case NPC_VENOMIMPRESS: if (skill_attack(BF_WEAPON, src, src, bl, skill_id, skill_lv, tick, flag)) sc_start(src, bl, SC_VENOMIMPRESS, 100, skill_lv, skill_get_time(skill_id,skill_lv)); break; default: ShowWarning("skill_castend_damage_id: Unknown skill used:%d\n",skill_id); clif_skill_damage(src, bl, tick, status_get_amotion(src), tstatus->dmotion, 0, abs(skill_get_num(skill_id, skill_lv)), skill_id, skill_lv, skill_get_hit(skill_id)); map_freeblock_unlock(); return 1; } if( sc && sc->getSCE(SC_CURSEDCIRCLE_ATKER) ) //Should only remove after the skill has been casted. status_change_end(src,SC_CURSEDCIRCLE_ATKER); map_freeblock_unlock(); if( sd && !(flag&1) ) {// ensure that the skill last-cast tick is recorded sd->canskill_tick = gettick(); if( sd->state.arrow_atk ) {// consume arrow on last invocation to this skill. battle_consume_ammo(sd, skill_id, skill_lv); } // perform skill requirement consumption skill_consume_requirement(sd,skill_id,skill_lv,2); } return 0; } /** * Give a song's buff/debuff or damage to all targets around * @param target: Target * @param ap: Argument list * @return 1 on success or 0 otherwise */ static int skill_apply_songs(struct block_list* target, va_list ap) { int flag = va_arg(ap, int); struct block_list* src = va_arg(ap, struct block_list*); uint16 skill_id = static_cast<uint16>(va_arg(ap, int)); uint16 skill_lv = static_cast<uint16>(va_arg(ap, int)); t_tick tick = va_arg(ap, t_tick); if (flag & BCT_WOS && src == target) return 0; if (battle_check_target(src, target, flag) > 0) { switch (skill_id) { // Attack type songs case BA_DISSONANCE: skill_attack(BF_MAGIC, src, src, target, skill_id, skill_lv, tick, 0); return 1; case DC_UGLYDANCE: case BD_LULLABY: return skill_additional_effect(src, target, skill_id, skill_lv, BF_LONG | BF_SKILL | BF_MISC, ATK_DEF, tick); default: // Buff/Debuff type songs if (skill_id == CG_HERMODE && src->id != target->id) status_change_clear_buffs(target, SCCB_HERMODE); // Should dispell only allies. return sc_start(src, target, skill_get_sc(skill_id), 100, skill_lv, skill_get_time(skill_id, skill_lv)); } } return 0; } /** * Calculate a song's bonus values * @param src: Caster * @param skill_id: Song skill ID * @param skill_lv: Song skill level * @param tick: Timer tick * @return Number of targets or 0 otherwise */ static int skill_castend_song(struct block_list* src, uint16 skill_id, uint16 skill_lv, t_tick tick) { nullpo_ret(src); if (src->type != BL_PC) { ShowWarning("skill_castend_song: Expected player type for src!\n"); return 0; } if (!(skill_get_inf2_(skill_id, { INF2_ISSONG, INF2_ISENSEMBLE }))) { ShowWarning("skill_castend_song: Unknown song skill ID: %u\n", skill_id); return 0; } map_session_data* sd = BL_CAST(BL_PC, src); int flag = BCT_PARTY; switch (skill_id) { case BD_ROKISWEIL: flag = BCT_ENEMY | BCT_WOS; break; case BD_LULLABY: case BD_ETERNALCHAOS: case BA_DISSONANCE: case DC_UGLYDANCE: case DC_DONTFORGETME: flag = BCT_ENEMY; break; case CG_HERMODE: flag |= BCT_GUILD; break; } clif_skill_nodamage(src, src, skill_id, skill_lv, 1); sd->skill_id_dance = skill_id; sd->skill_lv_dance = skill_lv; if (skill_get_inf2(skill_id, INF2_ISENSEMBLE)) skill_check_pc_partner(sd, skill_id, &skill_lv, 3, 1); return map_foreachinrange(skill_apply_songs, src, skill_get_splash(skill_id, skill_lv), splash_target(src), flag, src, skill_id, skill_lv, tick); } /** * Use no-damage skill from 'src' to 'bl * @param src Caster * @param bl Target of the skill, bl maybe same with src for self skill * @param skill_id * @param skill_lv * @param tick * @param flag Various value, &1: Recursive effect **/ int skill_castend_nodamage_id (struct block_list *src, struct block_list *bl, uint16 skill_id, uint16 skill_lv, t_tick tick, int flag) { map_session_data *sd, *dstsd; struct mob_data *md, *dstmd; struct homun_data *hd; s_mercenary_data *mer; struct status_data *sstatus, *tstatus; status_change *tsc; struct status_change_entry *tsce; int i = 0; enum sc_type type; if(skill_id > 0 && !skill_lv) return 0; // celest nullpo_retr(1, src); nullpo_retr(1, bl); if (src->m != bl->m) return 1; sd = BL_CAST(BL_PC, src); hd = BL_CAST(BL_HOM, src); md = BL_CAST(BL_MOB, src); mer = BL_CAST(BL_MER, src); dstsd = BL_CAST(BL_PC, bl); dstmd = BL_CAST(BL_MOB, bl); if(bl->prev == NULL) return 1; if(status_isdead(src)) return 1; if( src != bl && status_isdead(bl) ) { switch( skill_id ) { // Skills that may be cast on dead targets case NPC_WIDESOULDRAIN: case PR_REDEMPTIO: case ALL_RESURRECTION: case WM_DEADHILLHERE: case WE_ONEFOREVER: break; default: return 1; } } tstatus = status_get_status_data(bl); sstatus = status_get_status_data(src); //Check for undead skills that convert a no-damage skill into a damage one. [Skotlex] switch (skill_id) { case HLIF_HEAL: //[orn] if (bl->type != BL_HOM) { if (sd) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0) ; break ; } case AL_HEAL: case ALL_RESURRECTION: case PR_ASPERSIO: case AB_HIGHNESSHEAL: //Apparently only player casted skills can be offensive like this. if (sd && battle_check_undead(tstatus->race,tstatus->def_ele)) { if (battle_check_target(src, bl, BCT_ENEMY) < 1) { //Offensive heal does not works on non-enemies. [Skotlex] clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); return 0; } return skill_castend_damage_id (src, bl, skill_id, skill_lv, tick, flag); } break; case NPC_SMOKING: //Since it is a self skill, this one ends here rather than in damage_id. [Skotlex] return skill_castend_damage_id (src, bl, skill_id, skill_lv, tick, flag); case MH_STEINWAND: { struct block_list *s_src = battle_get_master(src); short ret = 0; if(!skill_check_unit_range(src, src->x, src->y, skill_id, skill_lv)) //prevent reiteration ret = skill_castend_pos2(src,src->x,src->y,skill_id,skill_lv,tick,flag); //cast on homon if(s_src && !skill_check_unit_range(s_src, s_src->x, s_src->y, skill_id, skill_lv)) ret |= skill_castend_pos2(s_src,s_src->x,s_src->y,skill_id,skill_lv,tick,flag); //cast on master if (hd) skill_blockhomun_start(hd, skill_id, skill_get_cooldown(skill_id, skill_lv)); return ret; } break; default: //Skill is actually ground placed. if (src == bl && skill_get_unit_id(skill_id)) return skill_castend_pos2(src,bl->x,bl->y,skill_id,skill_lv,tick,0); } type = skill_get_sc(skill_id); tsc = status_get_sc(bl); tsce = (tsc && type != SC_NONE)?tsc->getSCE(type):NULL; if (src!=bl && type > SC_NONE && CHK_ELEMENT((i = skill_get_ele(skill_id, skill_lv))) && i > ELE_NEUTRAL && skill_get_inf(skill_id) != INF_SUPPORT_SKILL && battle_attr_fix(NULL, NULL, 100, i, tstatus->def_ele, tstatus->ele_lv) <= 0) return 1; //Skills that cause an status should be blocked if the target element blocks its element. map_freeblock_lock(); switch(skill_id) { case HLIF_HEAL: //[orn] case AL_HEAL: case AB_HIGHNESSHEAL: { int heal = skill_calc_heal(src, bl, skill_id, skill_lv, true); if (status_isimmune(bl) || (dstmd && (status_get_class(bl) == MOBID_EMPERIUM || status_get_class_(bl) == CLASS_BATTLEFIELD))) heal = 0; if( tsc && tsc->count ) { if( tsc->getSCE(SC_KAITE) && !status_has_mode(sstatus,MD_STATUSIMMUNE) ) { //Bounce back heal if (--tsc->getSCE(SC_KAITE)->val2 <= 0) status_change_end(bl, SC_KAITE); if (src == bl) heal=0; //When you try to heal yourself under Kaite, the heal is voided. else { bl = src; dstsd = sd; } } else if (tsc->getSCE(SC_BERSERK) || tsc->getSCE(SC_SATURDAYNIGHTFEVER)) heal = 0; //Needed so that it actually displays 0 when healing. } if (skill_id == AL_HEAL) status_change_end(bl, SC_BITESCAR); clif_skill_nodamage (src, bl, skill_id, heal, 1); if( tsc && tsc->getSCE(SC_AKAITSUKI) && heal && skill_id != HLIF_HEAL ) heal = ~heal + 1; t_exp heal_get_jobexp = status_heal(bl,heal,0,0); if(sd && dstsd && heal > 0 && sd != dstsd && battle_config.heal_exp > 0){ heal_get_jobexp = heal_get_jobexp * battle_config.heal_exp / 100; if (heal_get_jobexp <= 0) heal_get_jobexp = 1; pc_gainexp (sd, bl, 0, heal_get_jobexp, 0); } } break; case CD_REPARATIO: { if (bl->type != BL_PC) { // Only works on players. clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); break; } int heal_amount = 0; if (!status_isimmune(bl)) heal_amount = tstatus->max_hp; clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); clif_skill_nodamage(nullptr, bl, AL_HEAL, heal_amount, 1); status_heal(bl, heal_amount, 0, 0); } break; case PR_REDEMPTIO: if (sd && !(flag&1)) { if (sd->status.party_id == 0) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); break; } skill_area_temp[0] = 0; party_foreachsamemap(skill_area_sub, sd,skill_get_splash(skill_id, skill_lv), src,skill_id,skill_lv,tick, flag|BCT_PARTY|1, skill_castend_nodamage_id); if (skill_area_temp[0] == 0) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); break; } #ifndef RENEWAL skill_area_temp[0] = battle_config.exp_cost_redemptio_limit - skill_area_temp[0]; // The actual penalty... if (skill_area_temp[0] > 0 && !map_getmapflag(src->m, MF_NOEXPPENALTY) && battle_config.exp_cost_redemptio) { //Apply penalty //If total penalty is 1% => reduced 0.2% penalty per each revived player pc_lostexp(sd, u64min(sd->status.base_exp, (pc_nextbaseexp(sd) * skill_area_temp[0] * battle_config.exp_cost_redemptio / battle_config.exp_cost_redemptio_limit) / 100), 0); } status_set_sp(src, 0, 0); #endif status_set_hp(src, 1, 0); break; } else if (status_isdead(bl) && flag&1) { //Revive skill_area_temp[0]++; //Count it in, then fall-through to the Resurrection code. skill_lv = 3; //Resurrection level 3 is used } else //Invalid target, skip resurrection. break; case ALL_RESURRECTION: if(sd && (map_flag_gvg2(bl->m) || map_getmapflag(bl->m, MF_BATTLEGROUND))) { //No reviving in WoE grounds! clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); break; } if (!status_isdead(bl)) break; { int per = 0, sper = 0; if (tsc && tsc->getSCE(SC_HELLPOWER)) { clif_skill_nodamage(src, bl, ALL_RESURRECTION, skill_lv, 1); break; } if (map_getmapflag(bl->m, MF_PVP) && dstsd && dstsd->pvp_point < 0) break; switch(skill_lv){ case 1: per=10; break; case 2: per=30; break; case 3: per=50; break; case 4: per=80; break; } if(dstsd && dstsd->special_state.restart_full_recover) per = sper = 100; if (status_revive(bl, per, sper)) { clif_skill_nodamage(src,bl,ALL_RESURRECTION,skill_lv,1); //Both Redemptio and Res show this skill-animation. if(sd && dstsd && battle_config.resurrection_exp > 0) { t_exp exp = 0,jexp = 0; int lv = dstsd->status.base_level - sd->status.base_level, jlv = dstsd->status.job_level - sd->status.job_level; if(lv > 0 && pc_nextbaseexp(dstsd)) { exp = (t_exp)(dstsd->status.base_exp * lv * battle_config.resurrection_exp / 1000000.); if (exp < 1) exp = 1; } if(jlv > 0 && pc_nextjobexp(dstsd)) { jexp = (t_exp)(dstsd->status.job_exp * lv * battle_config.resurrection_exp / 1000000.); if (jexp < 1) jexp = 1; } if(exp > 0 || jexp > 0) pc_gainexp (sd, bl, exp, jexp, 0); } } } break; case AL_DECAGI: case MER_DECAGI: clif_skill_nodamage (src, bl, skill_id, skill_lv, sc_start(src,bl, type, (50 + skill_lv * 3 + (status_get_lv(src) + sstatus->int_)/5), skill_lv, skill_get_time(skill_id,skill_lv))); break; case AL_CRUCIS: if (flag&1) sc_start(src,bl,type, 23+skill_lv*4 +status_get_lv(src) -status_get_lv(bl), skill_lv,skill_get_time(skill_id,skill_lv)); else { map_foreachinallrange(skill_area_sub, src, skill_get_splash(skill_id, skill_lv), BL_CHAR, src, skill_id, skill_lv, tick, flag|BCT_ENEMY|1, skill_castend_nodamage_id); clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); } break; case SP_SOULCURSE: if (flag&1) sc_start(src, bl, type, 30 + 10 * skill_lv, skill_lv, skill_get_time(skill_id, skill_lv)); else { map_foreachinrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), BL_CHAR, src, skill_id, skill_lv, tick, flag|BCT_ENEMY|1, skill_castend_nodamage_id); clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); } break; case PR_LEXDIVINA: case MER_LEXDIVINA: if (tsce) status_change_end(bl, type); else skill_addtimerskill(src, tick+1000, bl->id, 0, 0, skill_id, skill_lv, 100, flag); clif_skill_nodamage (src, bl, skill_id, skill_lv, 1); break; case SA_ABRACADABRA: if (abra_db.empty()) { clif_skill_nodamage (src, bl, skill_id, skill_lv, 1); break; } else { int abra_skill_id = 0, abra_skill_lv, checked = 0, checked_max = abra_db.size() * 3; do { auto abra_spell = abra_db.random(); abra_skill_id = abra_spell->skill_id; abra_skill_lv = min(skill_lv, skill_get_max(abra_skill_id)); if( rnd() % 10000 < abra_spell->per[max(skill_lv - 1, 0)] ){ break; } } while (checked++ < checked_max); clif_skill_nodamage (src, bl, skill_id, skill_lv, 1); if( sd ) {// player-casted sd->state.abra_flag = 1; sd->skillitem = abra_skill_id; sd->skillitemlv = abra_skill_lv; sd->skillitem_keep_requirement = false; clif_item_skill(sd, abra_skill_id, abra_skill_lv); } else {// mob-casted struct unit_data *ud = unit_bl2ud(src); int inf = skill_get_inf(abra_skill_id); if (!ud) break; if (inf&INF_SELF_SKILL || inf&INF_SUPPORT_SKILL) { if (src->type == BL_PET) bl = (struct block_list*)((TBL_PET*)src)->master; if (!bl) bl = src; unit_skilluse_id(src, bl->id, abra_skill_id, abra_skill_lv); } else { //Assume offensive skills int target_id = 0; if (ud->target) target_id = ud->target; else switch (src->type) { case BL_MOB: target_id = ((TBL_MOB*)src)->target_id; break; case BL_PET: target_id = ((TBL_PET*)src)->target_id; break; } if (!target_id) break; if (skill_get_casttype(abra_skill_id) == CAST_GROUND) { bl = map_id2bl(target_id); if (!bl) bl = src; unit_skilluse_pos(src, bl->x, bl->y, abra_skill_id, abra_skill_lv); } else unit_skilluse_id(src, target_id, abra_skill_id, abra_skill_lv); } } } break; case SA_COMA: clif_skill_nodamage(src,bl,skill_id,skill_lv, sc_start(src,bl,type,100,skill_lv,skill_get_time2(skill_id,skill_lv))); break; case SA_FULLRECOVERY: clif_skill_nodamage(src,bl,skill_id,skill_lv,1); if (status_isimmune(bl)) break; status_percent_heal(bl, 100, 100); break; case NPC_ALLHEAL: { int heal; if( status_isimmune(bl) ) break; heal = status_percent_heal(bl, 100, 0); clif_skill_nodamage(NULL, bl, AL_HEAL, heal, 1); if( dstmd ) { // Reset Damage Logs memset(dstmd->dmglog, 0, sizeof(dstmd->dmglog)); dstmd->tdmg = 0; } } break; case SA_SUMMONMONSTER: clif_skill_nodamage(src,bl,skill_id,skill_lv,1); if (sd) mob_once_spawn(sd, src->m, src->x, src->y,"--ja--", -1, 1, "", SZ_SMALL, AI_NONE); break; case SA_LEVELUP: clif_skill_nodamage(src,bl,skill_id,skill_lv,1); if (sd && pc_nextbaseexp(sd)) pc_gainexp(sd, NULL, pc_nextbaseexp(sd) * 10 / 100, 0, 0); break; case SA_INSTANTDEATH: clif_skill_nodamage(src,bl,skill_id,skill_lv,1); status_kill(src); break; case SA_QUESTION: clif_emotion(src,ET_QUESTION); case SA_GRAVITY: clif_skill_nodamage(src,bl,skill_id,skill_lv,1); break; case SA_CLASSCHANGE: case SA_MONOCELL: if (dstmd) { int class_; if ( sd && status_has_mode(&dstmd->status,MD_STATUSIMMUNE) ) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); break; } class_ = (skill_id == SA_MONOCELL ? MOBID_PORING : mob_get_random_id(MOBG_CLASSCHANGE, RMF_DB_RATE, 0)); clif_skill_nodamage(src,bl,skill_id,skill_lv,1); mob_class_change(dstmd,class_); if( tsc && status_has_mode(&dstmd->status,MD_STATUSIMMUNE) ) { const enum sc_type scs[] = { SC_QUAGMIRE, SC_PROVOKE, SC_ROKISWEIL, SC_GRAVITATION, SC_SUITON, SC_STRIPWEAPON, SC_STRIPSHIELD, SC_STRIPARMOR, SC_STRIPHELM, SC_BLADESTOP }; for (i = SC_COMMON_MIN; i <= SC_COMMON_MAX; i++) if (tsc->getSCE(i)) status_change_end(bl, (sc_type)i); for (i = 0; i < ARRAYLENGTH(scs); i++) if (tsc->getSCE(scs[i])) status_change_end(bl, scs[i]); } } break; case SA_DEATH: if ( sd && dstmd && status_has_mode(&dstmd->status,MD_STATUSIMMUNE) ) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); break; } clif_skill_nodamage(src,bl,skill_id,skill_lv,1); status_kill(bl); break; case SA_REVERSEORCISH: case ALL_REVERSEORCISH: clif_skill_nodamage(src,bl,skill_id,skill_lv, sc_start(src,bl,type,100,skill_lv,skill_get_time(skill_id, skill_lv))); break; case SA_FORTUNE: clif_skill_nodamage(src,bl,skill_id,skill_lv,1); if(sd) pc_getzeny(sd,status_get_lv(bl)*100,LOG_TYPE_STEAL,NULL); break; case SA_TAMINGMONSTER: clif_skill_nodamage(src,bl,skill_id,skill_lv,1); if (sd && dstmd && pet_db.find(dstmd->mob_id)) { pet_catch_process1(sd, dstmd->mob_id); } break; case CR_PROVIDENCE: if(sd && dstsd){ //Check they are not another crusader [Skotlex] if ((dstsd->class_&MAPID_UPPERMASK) == MAPID_CRUSADER) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); map_freeblock_unlock(); return 1; } } clif_skill_nodamage(src,bl,skill_id,skill_lv, sc_start(src,bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); break; case CG_MARIONETTE: { if( (sd && dstsd && (dstsd->class_&MAPID_UPPERMASK) == MAPID_BARDDANCER && dstsd->status.sex == sd->status.sex) || (tsc && (tsc->getSCE(SC_CURSE) || tsc->getSCE(SC_QUAGMIRE))) ) {// Cannot cast on another bard/dancer-type class of the same gender as caster, or targets under Curse/Quagmire clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); map_freeblock_unlock(); return 1; } status_change* sc = status_get_sc(src); if( sc && tsc ) { if( !sc->getSCE(SC_MARIONETTE) && !tsc->getSCE(SC_MARIONETTE2) ) { sc_start(src,src,SC_MARIONETTE,100,bl->id,skill_get_time(skill_id,skill_lv)); sc_start(src,bl,SC_MARIONETTE2,100,src->id,skill_get_time(skill_id,skill_lv)); clif_skill_nodamage(src,bl,skill_id,skill_lv,1); } else if( sc->getSCE(SC_MARIONETTE ) && sc->getSCE(SC_MARIONETTE )->val1 == bl->id && tsc->getSCE(SC_MARIONETTE2) && tsc->getSCE(SC_MARIONETTE2)->val1 == src->id ) { status_change_end(src, SC_MARIONETTE); status_change_end(bl, SC_MARIONETTE2); } else { if( sd ) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); map_freeblock_unlock(); return 1; } } } break; case RG_CLOSECONFINE: clif_skill_nodamage(src,bl,skill_id,skill_lv, sc_start4(src,bl,type,100,skill_lv,src->id,0,0,skill_get_time(skill_id,skill_lv))); break; case SA_FLAMELAUNCHER: // added failure chance and chance to break weapon if turned on [Valaris] case SA_FROSTWEAPON: case SA_LIGHTNINGLOADER: case SA_SEISMICWEAPON: if (dstsd && dstsd->status.weapon == W_FIST) { if (sd) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); clif_skill_nodamage(src,bl,skill_id,skill_lv,0); break; } #ifdef RENEWAL clif_skill_nodamage(src, bl, skill_id, skill_lv, sc_start(src, bl, type, 100, skill_lv, skill_get_time(skill_id, skill_lv))); #else // 100% success rate at lv4 & 5, but lasts longer at lv5 if(!clif_skill_nodamage(src,bl,skill_id,skill_lv, sc_start(src,bl,type,(60+skill_lv*10),skill_lv, skill_get_time(skill_id,skill_lv)))) { if (dstsd){ short index = dstsd->equip_index[EQI_HAND_R]; if (index != -1 && dstsd->inventory_data[index] && dstsd->inventory_data[index]->type == IT_WEAPON) pc_unequipitem(dstsd, index, 3); //Must unequip the weapon instead of breaking it [Daegaladh] } if (sd) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); } #endif break; case PR_ASPERSIO: if (sd && dstmd) { clif_skill_nodamage(src,bl,skill_id,skill_lv,0); break; } clif_skill_nodamage(src,bl,skill_id,skill_lv, sc_start(src,bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); break; case ITEM_ENCHANTARMS: clif_skill_nodamage(src, bl, skill_id, skill_lv, sc_start(src, bl, type, 100, skill_get_ele(skill_id, skill_lv), skill_get_time(skill_id, skill_lv))); break; case TK_SEVENWIND: switch(skill_get_ele(skill_id,skill_lv)) { case ELE_EARTH : type = SC_EARTHWEAPON; break; case ELE_WIND : type = SC_WINDWEAPON; break; case ELE_WATER : type = SC_WATERWEAPON; break; case ELE_FIRE : type = SC_FIREWEAPON; break; case ELE_GHOST : type = SC_GHOSTWEAPON; break; case ELE_DARK : type = SC_SHADOWWEAPON; break; case ELE_HOLY : type = SC_ASPERSIO; break; } clif_skill_nodamage(src,bl,skill_id,skill_lv, sc_start(src,bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); sc_start(src,bl,SC_SEVENWIND,100,skill_lv,skill_get_time(skill_id,skill_lv)); break; case PR_KYRIE: case MER_KYRIE: case SU_TUNAPARTY: case SU_GROOMING: case SU_CHATTERING: clif_skill_nodamage(bl,bl,skill_id,skill_lv, sc_start(src,bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); break; //Passive Magnum, should had been casted on yourself. case SM_MAGNUM: case MS_MAGNUM: skill_area_temp[1] = 0; map_foreachinshootrange(skill_area_sub, src, skill_get_splash(skill_id, skill_lv), BL_SKILL|BL_CHAR, src,skill_id,skill_lv,tick, flag|BCT_ENEMY|1, skill_castend_damage_id); clif_skill_nodamage (src,src,skill_id,skill_lv,1); // Initiate 20% of your damage becomes fire element. #ifdef RENEWAL sc_start4(src,src,SC_SUB_WEAPONPROPERTY,100,3,20,skill_id,0,skill_get_time2(skill_id, skill_lv)); #else sc_start4(src,src,SC_WATK_ELEMENT,100,3,20,0,0,skill_get_time2(skill_id, skill_lv)); #endif break; case TK_JUMPKICK: /* Check if the target is an enemy; if not, skill should fail so the character doesn't unit_movepos (exploitable) */ if( battle_check_target(src, bl, BCT_ENEMY) > 0 ) { if( unit_movepos(src, bl->x, bl->y, 2, 1) ) { skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); clif_blown(src); } } else clif_skill_fail(sd,skill_id,USESKILL_FAIL,0); break; case PR_BENEDICTIO: if (!battle_check_undead(tstatus->race, tstatus->def_ele) && tstatus->race != RC_DEMON) clif_skill_nodamage(src, bl, skill_id, skill_lv, sc_start(src, bl, type, 100, skill_lv, skill_get_time(skill_id, skill_lv))); break; case AL_INCAGI: case AL_BLESSING: case MER_INCAGI: case MER_BLESSING: clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); if (dstsd != nullptr && tsc && tsc->getSCE(SC_CHANGEUNDEAD)) { if (tstatus->hp > 1) skill_attack(BF_MISC,src,src,bl,skill_id,skill_lv,tick,flag); break; } sc_start(src, bl, type, 100, skill_lv, skill_get_time(skill_id, skill_lv)); break; case CR_REFLECTSHIELD: case MS_REFLECTSHIELD: if (tsc && tsc->getSCE(SC_DARKCROW)) { // SC_DARKCROW prevents using reflecting skills if (sd) clif_skill_fail(sd, skill_id, USESKILL_FAIL, 0); break; } case PR_SLOWPOISON: case PR_LEXAETERNA: #ifndef RENEWAL case PR_IMPOSITIO: case PR_SUFFRAGIUM: #endif case LK_BERSERK: case MS_BERSERK: case KN_TWOHANDQUICKEN: case KN_ONEHAND: case MER_QUICKEN: case CR_SPEARQUICKEN: case AS_POISONREACT: #ifndef RENEWAL case MC_LOUD: #endif case MG_ENERGYCOAT: case MO_EXPLOSIONSPIRITS: case MO_STEELBODY: case MO_BLADESTOP: case LK_AURABLADE: case LK_PARRYING: case MS_PARRYING: case LK_CONCENTRATION: #ifdef RENEWAL case HP_BASILICA: #endif case WS_CARTBOOST: case SN_SIGHT: case WS_MELTDOWN: case WS_OVERTHRUSTMAX: case ST_REJECTSWORD: case HW_MAGICPOWER: case PF_MEMORIZE: case PA_SACRIFICE: #ifndef RENEWAL case ASC_EDP: #endif case PF_DOUBLECASTING: case SG_SUN_COMFORT: case SG_MOON_COMFORT: case SG_STAR_COMFORT: case GS_MADNESSCANCEL: case GS_ADJUSTMENT: case GS_INCREASING: #ifdef RENEWAL case GS_MAGICALBULLET: #endif case NJ_KASUMIKIRI: case NJ_UTSUSEMI: case NJ_NEN: case NPC_DEFENDER: case NPC_MAGICMIRROR: case ST_PRESERVE: case NPC_KEEPING: case NPC_WEAPONBRAKER: case NPC_BARRIER: case NPC_INVINCIBLE: case NPC_INVINCIBLEOFF: case MER_INVINCIBLEOFF2: case RK_DEATHBOUND: case AB_EXPIATIO: case AB_DUPLELIGHT: case AB_SECRAMENT: case AB_OFFERTORIUM: case NC_ACCELERATION: case NC_HOVERING: case NC_SHAPESHIFT: case WL_MARSHOFABYSS: case WL_RECOGNIZEDSPELL: case GC_VENOMIMPRESS: case SC_DEADLYINFECT: case LG_EXEEDBREAK: case LG_PRESTIGE: case LG_INSPIRATION: case SR_CRESCENTELBOW: case SR_LIGHTNINGWALK: case GN_CARTBOOST: case GN_BLOOD_SUCKER: case GN_HELLS_PLANT: case KO_MEIKYOUSISUI: case ALL_ODINS_POWER: case ALL_FULL_THROTTLE: case RA_UNLIMIT: case WL_TELEKINESIS_INTENSE: case RL_HEAT_BARREL: case RL_P_ALTER: case RL_E_CHAIN: case SU_FRESHSHRIMP: case SU_ARCLOUSEDASH: case NPC_MAXPAIN: case SP_SOULREAPER: case SJ_LIGHTOFMOON: case SJ_LIGHTOFSTAR: case SJ_FALLINGSTAR: case SJ_LIGHTOFSUN: case SJ_BOOKOFDIMENSION: case NPC_HALLUCINATIONWALK: case DK_CHARGINGPIERCE: case DK_VIGOR: case AG_CLIMAX: case IQ_POWERFUL_FAITH: case IQ_FIRM_FAITH: case IQ_SINCERE_FAITH: case IQ_FIRST_FAITH_POWER: case IQ_JUDGE: case IQ_THIRD_EXOR_FLAME: case IG_REBOUND_SHIELD: case IG_HOLY_SHIELD: case CD_ARGUTUS_VITA: case CD_ARGUTUS_TELUM: case CD_PRESENS_ACIES: case CD_RELIGIO: case CD_BENEDICTUM: case SHC_SHADOW_EXCEED: case SHC_POTENT_VENOM: case SHC_ENCHANTING_SHADOW: case MT_D_MACHINE: case ABC_ABYSS_SLAYER: case WH_WIND_SIGN: case WH_CALAMITYGALE: case BO_RESEARCHREPORT: case TR_MYSTIC_SYMPHONY: case TR_KVASIR_SONATA: case EM_SPELL_ENCHANTING: clif_skill_nodamage(src,bl,skill_id,skill_lv, sc_start(src,bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); break; #ifdef RENEWAL // EDP also give +25% WATK poison pseudo element to user. case ASC_EDP: clif_skill_nodamage(src,bl,skill_id,skill_lv, sc_start(src,bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); sc_start4(src,src,SC_SUB_WEAPONPROPERTY,100,5,25,skill_id,0,skill_get_time2(skill_id, skill_lv)); break; #endif case LG_SHIELDSPELL: if (skill_lv == 1) type = SC_SHIELDSPELL_HP; else if (skill_lv == 2) type = SC_SHIELDSPELL_SP; else type = SC_SHIELDSPELL_ATK; clif_skill_nodamage(src, bl, skill_id, skill_lv, sc_start(src, bl, type, 100, skill_lv, skill_get_time(skill_id, skill_lv))); break; case DK_SERVANTWEAPON: case ABC_FROM_THE_ABYSS: clif_skill_nodamage(src, bl, skill_id, skill_lv, sc_start2(src, bl, type, 100, skill_lv, src->id, skill_get_time(skill_id, skill_lv))); break; case TR_SOUNDBLEND: skill_castend_damage_id(src, bl, skill_id, skill_lv, tick, 0); clif_skill_nodamage(src, bl, skill_id, skill_lv, sc_start2(src, bl, type, 100, skill_lv, src->id, skill_get_time(skill_id, skill_lv))); break; case AG_VIOLENT_QUAKE: case AG_ALL_BLOOM: sc_start(src, bl, type, 100, skill_lv, skill_get_time2(skill_id, skill_lv)); break; case AG_DESTRUCTIVE_HURRICANE: case AG_CRYSTAL_IMPACT: if (flag&1) { // Buff from Crystal Impact with level 1 Climax. sc_start(src, bl, type, 100, skill_lv, skill_get_time2(skill_id, skill_lv)); } else { uint16 climax_lv = 0, splash_size = skill_get_splash(skill_id, skill_lv); status_change *sc = status_get_sc(src); if (sc && sc->getSCE(SC_CLIMAX)) climax_lv = sc->getSCE(SC_CLIMAX)->val1; if (climax_lv == 5) { // Adjusts splash AoE size depending on skill. if (skill_id == AG_DESTRUCTIVE_HURRICANE) splash_size = 9; // 19x19 else if(skill_id == AG_CRYSTAL_IMPACT) splash_size = AREA_SIZE; // 29x29 - Entire screen. } skill_area_temp[1] = 0; clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); if (skill_id == AG_DESTRUCTIVE_HURRICANE && climax_lv == 4) // Buff for caster instead of damage AoE. sc_start(src, bl, type, 100, skill_lv, skill_get_time2(skill_id, skill_lv)); else if (skill_id == AG_CRYSTAL_IMPACT && climax_lv == 1) // Buffs the caster and allies instead of doing damage AoE. map_foreachinrange(skill_area_sub, bl, splash_size, BL_CHAR, src, skill_id, skill_lv, tick, flag|BCT_ALLY|SD_SPLASH|1, skill_castend_nodamage_id); else { if (skill_id == AG_DESTRUCTIVE_HURRICANE && climax_lv == 1) // Display extra animation for the additional hit cast. clif_skill_nodamage(src, bl, AG_DESTRUCTIVE_HURRICANE_CLIMAX, skill_lv, 1); map_foreachinrange(skill_area_sub, bl, splash_size, BL_CHAR, src, skill_id, skill_lv, tick, flag | BCT_ENEMY | SD_SPLASH | 1, skill_castend_damage_id); } } break; case CD_MEDIALE_VOTUM: case CD_DILECTIO_HEAL: if (flag & 1) { if (sd == nullptr || sd->status.party_id == 0 || (flag & 2)) { int heal_amount = skill_calc_heal(src, bl, skill_id, skill_lv, 1); clif_skill_nodamage( nullptr, bl, AL_HEAL, heal_amount, 1 ); status_heal(bl, heal_amount, 0, 0); } else if (sd) party_foreachsamemap(skill_area_sub, sd, skill_get_splash(skill_id, skill_lv), src, skill_id, skill_lv, tick, flag | BCT_PARTY | 3, skill_castend_nodamage_id); } else { if (skill_id == CD_MEDIALE_VOTUM) clif_skill_nodamage(src, bl, skill_id, skill_lv, sc_start(src, bl, type, 100, skill_lv, skill_get_time(skill_id, skill_lv))); else { // Dilectio Heal clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); // Placed here to display animation on target only. skill_castend_nodamage_id(bl, bl, skill_id, skill_lv, tick, 1); } } break; case CD_COMPETENTIA: if (sd == nullptr || sd->status.party_id == 0 || (flag & 1)) { int hp_amount = tstatus->max_hp * (20 * skill_lv) / 100; int sp_amount = tstatus->max_sp * (20 * skill_lv) / 100; clif_skill_nodamage( nullptr, bl, AL_HEAL, hp_amount, 1 ); status_heal(bl, hp_amount, 0, 0); clif_skill_nodamage( nullptr, bl, MG_SRECOVERY, sp_amount, 1 ); status_heal(bl, 0, sp_amount, 0); clif_skill_nodamage(bl, bl, skill_id, skill_lv, sc_start(src, bl, type, 100, skill_lv, skill_get_time(skill_id, skill_lv))); } else if (sd) party_foreachsamemap(skill_area_sub, sd, skill_get_splash(skill_id, skill_lv), src, skill_id, skill_lv, tick, flag|BCT_PARTY|1, skill_castend_nodamage_id); break; case BO_ADVANCE_PROTECTION: if( sd && ( !dstsd || pc_checkequip( dstsd, EQP_SHADOW_GEAR ) < 0 ) ){ clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); map_freeblock_unlock(); // Don't consume item requirements return 0; } clif_skill_nodamage( src, bl, skill_id, skill_lv, sc_start( src, bl, type, 100, skill_lv, skill_get_time( skill_id, skill_lv ) ) ); break; case EM_ACTIVITY_BURN: if (bl->type == BL_PC && rnd() % 100 < 20 + 10 * skill_lv) { uint8 ap_burn[5] = { 20, 30, 50, 60, 70 }; clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); status_fix_apdamage(src, bl, ap_burn[skill_lv - 1], 0, skill_id); } else clif_skill_fail(sd, skill_id, USESKILL_FAIL, 0); break; case EM_INCREASING_ACTIVITY: if (bl->type == BL_PC) { clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); status_heal(bl, 0, 0, 20 + 5 * skill_lv, 0); } else clif_skill_fail(sd, skill_id, USESKILL_FAIL, 0); break; case SJ_GRAVITYCONTROL: { int fall_damage = sstatus->batk + sstatus->rhw.atk - tstatus->def2; if (bl->type == BL_PC) fall_damage += dstsd->weight / 10 - tstatus->def; else // Monster's don't have weight. Put something in its place. fall_damage += 50 * status_get_lv(src) - tstatus->def; fall_damage = max(1, fall_damage); clif_skill_nodamage(src, bl, skill_id, skill_lv, sc_start2(src, bl, type, 100, skill_lv, fall_damage, skill_get_time(skill_id, skill_lv))); } break; case NPC_HALLUCINATION: case NPC_HELLPOWER: clif_skill_nodamage(src, bl, skill_id, skill_lv, sc_start(src, bl, type, skill_lv*20, skill_lv, skill_get_time2(skill_id, skill_lv))); break; case KN_AUTOCOUNTER: sc_start(src,bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv)); skill_addtimerskill(src,tick + 100,bl->id,0,0,skill_id,skill_lv,BF_WEAPON,flag); break; case SO_STRIKING: if (battle_check_target(src, bl, BCT_SELF|BCT_PARTY) > 0) { int bonus = 0; if (dstsd) { short index = dstsd->equip_index[EQI_HAND_R]; if (index >= 0 && dstsd->inventory_data[index] && dstsd->inventory_data[index]->type == IT_WEAPON) bonus = (20 * skill_lv) * dstsd->inventory_data[index]->weapon_level; } clif_skill_nodamage(src, bl, skill_id, skill_lv, sc_start2(src,bl, type, 100, skill_lv, bonus, skill_get_time(skill_id, skill_lv))); } else if (sd) clif_skill_fail(sd, skill_id, USESKILL_FAIL_TOTARGET, 0); break; case NPC_STOP: if( clif_skill_nodamage(src,bl,skill_id,skill_lv, sc_start2(src,bl,type,100,skill_lv,src->id,skill_get_time(skill_id,skill_lv)) ) ) sc_start2(src,src,type,100,skill_lv,bl->id,skill_get_time(skill_id,skill_lv)); break; case HP_ASSUMPTIO: if( sd && dstmd ) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); else clif_skill_nodamage(src,bl,skill_id,skill_lv, sc_start(src,bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); break; case MG_SIGHT: case MER_SIGHT: case AL_RUWACH: case WZ_SIGHTBLASTER: case NPC_WIDESIGHT: case NPC_STONESKIN: case NPC_ANTIMAGIC: clif_skill_nodamage(src,bl,skill_id,skill_lv, sc_start2(src,bl,type,100,skill_lv,skill_id,skill_get_time(skill_id,skill_lv))); break; case HLIF_AVOID: case HAMI_DEFENCE: sc_start(src,bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv)); // Master clif_skill_nodamage(src,src,skill_id,skill_lv,sc_start(src,src,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); // Homunc break; case NJ_BUNSINJYUTSU: status_change_end(bl, SC_BUNSINJYUTSU); // on official recasting cancels existing mirror image [helvetica] clif_skill_nodamage(src,bl,skill_id,skill_lv, sc_start(src,bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); status_change_end(bl, SC_NEN); break; /* Was modified to only affect targetted char. [Skotlex] case HP_ASSUMPTIO: if (flag&1) sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv)); else { map_foreachinallrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), BL_PC, src, skill_id, skill_lv, tick, flag|BCT_ALL|1, skill_castend_nodamage_id); clif_skill_nodamage(src,bl,skill_id,skill_lv,1); } break; */ case SM_ENDURE: clif_skill_nodamage(src,bl,skill_id,skill_lv, sc_start(src,bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); break; case AS_ENCHANTPOISON: if( sc_start( src, bl, type, 100, skill_lv, skill_get_time( skill_id, skill_lv ) ) ){ clif_skill_nodamage( src, bl, skill_id, skill_lv, 1 ); }else{ clif_skill_nodamage( src, bl, skill_id, skill_lv, 0 ); if( sd != nullptr ){ clif_skill_fail( sd, skill_id, USESKILL_FAIL_LEVEL, 0 ); } } break; case LK_TENSIONRELAX: clif_skill_nodamage(src,bl,skill_id,skill_lv, sc_start4(src,bl,type,100,skill_lv,0,0,skill_get_time2(skill_id,skill_lv), skill_get_time(skill_id,skill_lv))); break; case MC_CHANGECART: clif_skill_nodamage(src,bl,skill_id,skill_lv,1); break; case MC_CARTDECORATE: clif_skill_nodamage(src,bl,skill_id,skill_lv,1); if( sd ) { clif_SelectCart(sd); } break; case TK_MISSION: if (sd) { if (sd->mission_mobid && (sd->mission_count || rnd()%100)) { //Cannot change target when already have one clif_mission_info(sd, sd->mission_mobid, sd->mission_count); clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); break; } int id = mob_get_random_id(MOBG_TAEKWON_MISSION, RMF_NONE, 0); if (!id) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); break; } sd->mission_mobid = id; sd->mission_count = 0; pc_setglobalreg(sd, add_str(TKMISSIONID_VAR), id); clif_mission_info(sd, id, 0); clif_skill_nodamage(src,bl,skill_id,skill_lv,1); } break; case AC_CONCENTRATION: { int splash = skill_get_splash(skill_id, skill_lv); clif_skill_nodamage(src,bl,skill_id,skill_lv, sc_start(src,bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); skill_reveal_trap_inarea(src, splash, src->x, src->y); map_foreachinallrange( status_change_timer_sub, src, splash, BL_CHAR, src, NULL, type, tick); } break; case SM_PROVOKE: case SM_SELFPROVOKE: case MER_PROVOKE: if( status_has_mode(tstatus,MD_STATUSIMMUNE) || battle_check_undead(tstatus->race,tstatus->def_ele) ) { map_freeblock_unlock(); return 1; } // Official chance is 70% + 3%*skill_lv + srcBaseLevel% - tarBaseLevel% if(!(i = sc_start(src, bl, type, skill_id == SM_SELFPROVOKE ? 100 : (70 + 3 * skill_lv + status_get_lv(src) - status_get_lv(bl)), skill_lv, skill_get_time(skill_id, skill_lv)))) { if( sd ) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); map_freeblock_unlock(); return 0; } clif_skill_nodamage(src, bl, skill_id == SM_SELFPROVOKE ? SM_PROVOKE : skill_id, skill_lv, i); unit_skillcastcancel(bl, 2); if( dstmd ) { dstmd->state.provoke_flag = src->id; mob_target(dstmd, src, skill_get_range2(src, skill_id, skill_lv, true)); } break; case ML_DEVOTION: case CR_DEVOTION: { int count, lv; if( !dstsd || (!sd && !mer) ) { // Only players can be devoted if( sd ) clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); break; } if( (lv = status_get_lv(src) - dstsd->status.base_level) < 0 ) lv = -lv; if( lv > battle_config.devotion_level_difference || // Level difference requeriments (dstsd->sc.getSCE(type) && dstsd->sc.getSCE(type)->val1 != src->id) || // Cannot Devote a player devoted from another source (skill_id == ML_DEVOTION && (!mer || mer != dstsd->md)) || // Mercenary only can devote owner (dstsd->class_&MAPID_UPPERMASK) == MAPID_CRUSADER || // Crusader Cannot be devoted (dstsd->sc.getSCE(SC_HELLPOWER))) // Players affected by SC_HELLPOWER cannot be devoted. { if( sd ) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); map_freeblock_unlock(); return 1; } i = 0; count = (sd)? min(skill_lv,MAX_DEVOTION) : 1; // Mercenary only can Devote owner if( sd ) { // Player Devoting Player ARR_FIND(0, count, i, sd->devotion[i] == bl->id ); if( i == count ) { ARR_FIND(0, count, i, sd->devotion[i] == 0 ); if( i == count ) { // No free slots, skill Fail clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); map_freeblock_unlock(); return 1; } } sd->devotion[i] = bl->id; } else mer->devotion_flag = 1; // Mercenary Devoting Owner clif_skill_nodamage(src, bl, skill_id, skill_lv, sc_start4(src, bl, type, 10000, src->id, i, skill_get_range2(src, skill_id, skill_lv, true), 0, skill_get_time2(skill_id, skill_lv))); clif_devotion(src, NULL); } break; case SP_SOULUNITY: { int8 count = min(5 + skill_lv, MAX_UNITED_SOULS); if (sd == nullptr || sd->status.party_id == 0 || (flag & 1)) { if (!dstsd || !sd) { // Only put player's souls in unity. if (sd) clif_skill_fail(sd, skill_id, USESKILL_FAIL, 0); break; } if (dstsd->sc.getSCE(type) && dstsd->sc.getSCE(type)->val2 != src->id) { // Fail if a player is in unity with another source. if (sd) clif_skill_fail(sd, skill_id, USESKILL_FAIL, 0); map_freeblock_unlock(); return 1; } if (sd) { // Unite player's soul with caster's soul. i = 0; ARR_FIND(0, count, i, sd->united_soul[i] == bl->id); if (i == count) { ARR_FIND(0, count, i, sd->united_soul[i] == 0); if(i == count) { // No more free slots? Fail the skill. clif_skill_fail(sd, skill_id, USESKILL_FAIL, 0); map_freeblock_unlock(); return 1; } } sd->united_soul[i] = bl->id; } clif_skill_nodamage(src, bl, skill_id, skill_lv, sc_start4(src, bl, type, 100, skill_lv, src->id, i, 0, skill_get_time(skill_id, skill_lv))); } else if (sd) party_foreachsamemap(skill_area_sub, sd, skill_get_splash(skill_id, skill_lv), src, skill_id, skill_lv, tick, flag|BCT_PARTY|1, skill_castend_nodamage_id); } break; case DK_SERVANT_W_SIGN: // Max allowed targets to be marked. // Only players and monsters can be marked....I think??? [Rytech] // Lets only allow players and monsters to use this skill for safety reasons. if ((!dstsd && !dstmd) || !sd && !md) { if (sd) clif_skill_fail(sd, skill_id, USESKILL_FAIL, 0); break; } // Check if the target is already marked by another source. if (tsc && tsc->getSCE(type) && tsc->getSCE(type)->val1 != src->id) { if (sd) clif_skill_fail(sd, skill_id, USESKILL_FAIL, 0); map_freeblock_unlock(); return 1; } // Mark the target. if( sd ){ int8 count = MAX_SERVANT_SIGN; ARR_FIND(0, count, i, sd->servant_sign[i] == bl->id); if (i == count) { ARR_FIND(0, count, i, sd->servant_sign[i] == 0); if (i == count) { // Max number of targets marked. Fail the skill. clif_skill_fail(sd, skill_id, USESKILL_FAIL, 0); map_freeblock_unlock(); return 1; } // Add the ID of the marked target to the player's sign list. sd->servant_sign[i] = bl->id; } clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); sc_start4(src, bl, type, 100, src->id, i, skill_lv, 0, skill_get_time(skill_id, skill_lv)); } else if (md) // Monster's cant track with this skill. Just give the status. clif_skill_nodamage(src, bl, skill_id, skill_lv, sc_start4(src, bl, type, 100, 0, 0, skill_lv, 0, skill_get_time(skill_id, skill_lv))); break; case MO_CALLSPIRITS: if(sd) { int limit = skill_lv; if( sd->sc.getSCE(SC_RAISINGDRAGON) ) limit += sd->sc.getSCE(SC_RAISINGDRAGON)->val1; clif_skill_nodamage(src,bl,skill_id,skill_lv,1); pc_addspiritball(sd,skill_get_time(skill_id,skill_lv),limit); } break; case CH_SOULCOLLECT: if(sd) { int limit = 5; if( sd->sc.getSCE(SC_RAISINGDRAGON) ) limit += sd->sc.getSCE(SC_RAISINGDRAGON)->val1; clif_skill_nodamage(src,bl,skill_id,skill_lv,1); for (i = 0; i < limit; i++) pc_addspiritball(sd,skill_get_time(skill_id,skill_lv),limit); } break; case MO_KITRANSLATION: if(dstsd && ((dstsd->class_&MAPID_BASEMASK) != MAPID_GUNSLINGER && (dstsd->class_&MAPID_UPPERMASK) != MAPID_REBELLION) && dstsd->spiritball < 5) { //Require will define how many spiritballs will be transferred struct s_skill_condition require; require = skill_get_requirement(sd,skill_id,skill_lv); pc_delspiritball(sd,require.spiritball,0); for (i = 0; i < require.spiritball; i++) pc_addspiritball(dstsd,skill_get_time(skill_id,skill_lv),5); } else { if(sd) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); map_freeblock_unlock(); return 0; } break; case TK_TURNKICK: case MO_BALKYOUNG: //Passive part of the attack. Splash knock-back+stun. [Skotlex] if (skill_area_temp[1] != bl->id) { skill_blown(src,bl,skill_get_blewcount(skill_id,skill_lv),-1,BLOWN_NONE); skill_additional_effect(src,bl,skill_id,skill_lv,BF_MISC,ATK_DEF,tick); //Use Misc rather than weapon to signal passive pushback } break; case MO_ABSORBSPIRITS: i = 0; if (dstsd && (battle_check_target(src, bl, BCT_SELF) > 0 || (battle_check_target(src, bl, BCT_ENEMY) > 0 && (map_flag_vs(src->m) || (sd && sd->duel_group && sd->duel_group == dstsd->duel_group)))) && // Only works on self and enemies ((dstsd->class_&MAPID_BASEMASK) != MAPID_GUNSLINGER || (dstsd->class_&MAPID_UPPERMASK) != MAPID_REBELLION)) { // split the if for readability, and included gunslingers in the check so that their coins cannot be removed [Reddozen] if (dstsd->spiritball > 0) { i = dstsd->spiritball * 7; pc_delspiritball(dstsd,dstsd->spiritball,0); } if (dstsd->spiritcharm_type != CHARM_TYPE_NONE && dstsd->spiritcharm > 0) { i += dstsd->spiritcharm * 7; pc_delspiritcharm(dstsd,dstsd->spiritcharm,dstsd->spiritcharm_type); } } else if (dstmd && !status_has_mode(tstatus,MD_STATUSIMMUNE) && rnd() % 100 < 20) { // check if target is a monster and not status immune, for the 20% chance to absorb 2 SP per monster's level [Reddozen] i = 2 * dstmd->level; mob_target(dstmd,src,0); } else { if (sd) clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); break; } if (i) status_heal(src, 0, i, 3); clif_skill_nodamage(src,bl,skill_id,skill_lv,i?1:0); break; case AC_MAKINGARROW: if(sd) { clif_arrow_create_list(sd); clif_skill_nodamage(src,bl,skill_id,skill_lv,1); } break; case AM_PHARMACY: if(sd) { clif_skill_produce_mix_list(sd,skill_id,22); clif_skill_nodamage(src,bl,skill_id,skill_lv,1); } break; case SA_CREATECON: if(sd) { clif_elementalconverter_list(sd); clif_skill_nodamage(src,bl,skill_id,skill_lv,1); } break; case BS_HAMMERFALL: skill_addtimerskill(src, tick+1000, bl->id, 0, 0, skill_id, skill_lv, min(20+10*skill_lv, 50+5*skill_lv), flag); break; case RG_RAID: skill_area_temp[1] = 0; clif_skill_nodamage(src,bl,skill_id,skill_lv,1); map_foreachinrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), BL_CHAR|BL_SKILL, src,skill_id,skill_lv,tick, flag|BCT_ENEMY|1, skill_castend_damage_id); status_change_end(src, SC_HIDING); break; //List of self skills that give damage around caster case ASC_METEORASSAULT: case GS_SPREADATTACK: case RK_WINDCUTTER: case RK_STORMBLAST: case NC_AXETORNADO: case GC_COUNTERSLASH: case SR_SKYNETBLOW: case SR_RAMPAGEBLASTER: case SR_HOWLINGOFLION: case LG_CANNONSPEAR: case LG_OVERBRAND: case NPC_RAYOFGENESIS: case LG_RAYOFGENESIS: case KO_HAPPOKUNAI: case RL_FIREDANCE: case RL_R_TRIP: case SJ_FULLMOONKICK: case SJ_NEWMOONKICK: case SJ_SOLARBURST: case SJ_STAREMPEROR: case SJ_FALLINGSTAR_ATK: case DK_SERVANT_W_DEMOL: case AG_FROZEN_SLASH: case IQ_OLEUM_SANCTUM: case IQ_MASSIVE_F_BLASTER: case IQ_EXPOSION_BLASTER: case SHC_IMPACT_CRATER: case MT_AXE_STOMP: case ABC_ABYSS_DAGGER: { status_change *sc = status_get_sc(src); int starget = BL_CHAR|BL_SKILL; if (skill_id == SR_HOWLINGOFLION) starget = splash_target(src); if (skill_id == SJ_NEWMOONKICK) { if (tsce) { status_change_end(bl, type); clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); break; } else sc_start(src, bl, type, 100, skill_lv, skill_get_time(skill_id, skill_lv)); } if (skill_id == SJ_STAREMPEROR && sc && sc->getSCE(SC_DIMENSION)) { if (sd) { // Remove old shields if any exist. pc_delspiritball(sd, sd->spiritball, 0); sc_start2(src, bl, SC_DIMENSION1, 100, skill_lv, status_get_max_sp(src), skill_get_time2(SJ_BOOKOFDIMENSION, 1)); sc_start2(src, bl, SC_DIMENSION2, 100, skill_lv, status_get_max_sp(src), skill_get_time2(SJ_BOOKOFDIMENSION, 1)); } status_change_end(src, SC_DIMENSION); } if (skill_id == IQ_MASSIVE_F_BLASTER || skill_id == SHC_IMPACT_CRATER || skill_id == MT_AXE_STOMP || skill_id == ABC_ABYSS_DAGGER) sc_start(src, bl, type, 100, skill_lv, skill_get_time(skill_id, skill_lv)); skill_area_temp[1] = 0; clif_skill_nodamage(src,bl,skill_id,skill_lv,1); i = map_foreachinrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), starget, src, skill_id, skill_lv, tick, flag|BCT_ENEMY|SD_SPLASH|1, skill_castend_damage_id); if( !i && ( skill_id == RK_WINDCUTTER || skill_id == NC_AXETORNADO || skill_id == LG_CANNONSPEAR || skill_id == SR_SKYNETBLOW || skill_id == KO_HAPPOKUNAI ) ) clif_skill_damage(src,src,tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, DMG_SINGLE); } break; case SHC_DANCING_KNIFE: if (flag & 1) { skill_area_temp[1] = 0; // Note: doesn't force player to stand before attacking map_foreachinrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), BL_CHAR | BL_SKILL, src, skill_id, skill_lv, tick, flag | BCT_ENEMY | SD_LEVEL | SD_SPLASH, skill_castend_damage_id); } else { clif_skill_nodamage(src, bl, skill_id, skill_lv, sc_start(src, bl, type, 100, skill_lv, skill_get_time(skill_id, skill_lv))); } break; case MT_A_MACHINE: if (flag & 1) { skill_area_temp[1] = 0; if (sd && pc_issit(sd)) { // Force player to stand before attacking pc_setstand(sd, true); skill_sit(sd, false); } map_foreachinrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), BL_CHAR | BL_SKILL, src, skill_id, skill_lv, tick, flag | BCT_ENEMY | SD_LEVEL | SD_SPLASH, skill_castend_damage_id); } else { if (dstsd) { int lv = abs( status_get_lv( src ) - status_get_lv( bl ) ); if (lv > battle_config.attack_machine_level_difference) { if (sd) clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); map_freeblock_unlock(); return 0; } } clif_skill_nodamage(src, bl, skill_id, skill_lv, sc_start(src, bl, type, 100, skill_lv, skill_get_time(skill_id, skill_lv))); } break; case EM_ELEMENTAL_BUSTER: { if (sd == nullptr) break; if (!sd->ed || !(sd->ed->elemental.class_ >= ELEMENTALID_DILUVIO && sd->ed->elemental.class_ <= ELEMENTALID_SERPENS)) { clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); map_freeblock_unlock(); return 0; } uint16 buster_element; switch (sd->ed->elemental.class_) { case ELEMENTALID_ARDOR: buster_element = EM_ELEMENTAL_BUSTER_FIRE; break; case ELEMENTALID_DILUVIO: buster_element = EM_ELEMENTAL_BUSTER_WATER; break; case ELEMENTALID_PROCELLA: buster_element = EM_ELEMENTAL_BUSTER_WIND; break; case ELEMENTALID_TERREMOTUS: buster_element = EM_ELEMENTAL_BUSTER_GROUND; break; case ELEMENTALID_SERPENS: buster_element = EM_ELEMENTAL_BUSTER_POISON; break; } skill_area_temp[1] = 0; clif_skill_nodamage(src, bl, buster_element, skill_lv, 1);// Animation for the triggered blaster element. clif_skill_nodamage(src, bl, skill_id, skill_lv, 1);// Triggered after blaster animation to make correct skill name scream appear. map_foreachinrange(skill_area_sub, bl, 6, BL_CHAR | BL_SKILL, src, buster_element, skill_lv, tick, flag | BCT_ENEMY | SD_LEVEL | SD_SPLASH | 1, skill_castend_damage_id); } break; case NPC_IGNITIONBREAK: case RK_IGNITIONBREAK: skill_area_temp[1] = 0; #if PACKETVER >= 20180207 clif_skill_nodamage(src,bl,skill_id,skill_lv,1); #else clif_skill_damage(src, src, tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, DMG_SINGLE); #endif map_foreachinrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), BL_CHAR|BL_SKILL, src, skill_id, skill_lv, tick, flag|BCT_ENEMY|SD_SPLASH|1, skill_castend_damage_id); break; case SR_TIGERCANNON: case SR_WINDMILL: case GN_CART_TORNADO: clif_skill_nodamage(src,bl,skill_id,skill_lv,1); case SR_EARTHSHAKER: case NC_INFRAREDSCAN: case NPC_VAMPIRE_GIFT: case NPC_HELLJUDGEMENT: case NPC_HELLJUDGEMENT2: case NPC_PULSESTRIKE: case LG_MOONSLASHER: skill_castend_damage_id(src, src, skill_id, skill_lv, tick, flag); break; #ifdef RENEWAL case KN_BRANDISHSPEAR: map_foreachindir(skill_area_sub, src->m, src->x, src->y, bl->x, bl->y, skill_get_splash(skill_id, skill_lv), skill_get_maxcount(skill_id, skill_lv), 0, splash_target(src), src, skill_id, skill_lv, tick, flag | BCT_ENEMY | 0, skill_castend_damage_id); break; #else case KN_BRANDISHSPEAR: #endif case ML_BRANDISH: skill_area_temp[1] = bl->id; if(skill_lv >= 10) map_foreachindir(skill_area_sub, src->m, src->x, src->y, bl->x, bl->y, skill_get_splash(skill_id, skill_lv), 1, skill_get_maxcount(skill_id, skill_lv)-1, splash_target(src), src, skill_id, skill_lv, tick, flag | BCT_ENEMY | (sd?3:0), skill_castend_damage_id); if(skill_lv >= 7) map_foreachindir(skill_area_sub, src->m, src->x, src->y, bl->x, bl->y, skill_get_splash(skill_id, skill_lv), 1, skill_get_maxcount(skill_id, skill_lv)-2, splash_target(src), src, skill_id, skill_lv, tick, flag | BCT_ENEMY | (sd?2:0), skill_castend_damage_id); if(skill_lv >= 4) map_foreachindir(skill_area_sub, src->m, src->x, src->y, bl->x, bl->y, skill_get_splash(skill_id, skill_lv), 1, skill_get_maxcount(skill_id, skill_lv)-3, splash_target(src), src, skill_id, skill_lv, tick, flag | BCT_ENEMY | (sd?1:0), skill_castend_damage_id); map_foreachindir(skill_area_sub, src->m, src->x, src->y, bl->x, bl->y, skill_get_splash(skill_id, skill_lv), skill_get_maxcount(skill_id, skill_lv)-3, 0, splash_target(src), src, skill_id, skill_lv, tick, flag | BCT_ENEMY | 0, skill_castend_damage_id); break; case WZ_SIGHTRASHER: //Passive side of the attack. status_change_end(src, SC_SIGHT); clif_skill_nodamage(src,bl,skill_id,skill_lv,1); map_foreachinshootrange(skill_area_sub,src, skill_get_splash(skill_id, skill_lv),BL_CHAR|BL_SKILL, src,skill_id,skill_lv,tick, flag|BCT_ENEMY|SD_ANIMATION|1, skill_castend_damage_id); break; case WZ_FROSTNOVA: clif_skill_nodamage(src,bl,skill_id,skill_lv,1); skill_area_temp[1] = 0; map_foreachinshootrange(skill_attack_area, src, skill_get_splash(skill_id, skill_lv), splash_target(src), BF_MAGIC, src, src, skill_id, skill_lv, tick, flag, BCT_ENEMY); break; case HVAN_EXPLOSION: //[orn] case NPC_SELFDESTRUCTION: //Self Destruction hits everyone in range (allies+enemies) //Except for Summoned Marine spheres on non-versus maps, where it's just enemy. i = ((!md || md->special_state.ai == AI_SPHERE) && !map_flag_vs(src->m))? BCT_ENEMY:BCT_ALL; clif_skill_nodamage(src, src, skill_id, -1, 1); map_delblock(src); //Required to prevent chain-self-destructions hitting back. map_foreachinshootrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), BL_CHAR|BL_SKILL, src, skill_id, skill_lv, tick, flag|i, skill_castend_damage_id); if(map_addblock(src)) { map_freeblock_unlock(); return 1; } status_damage(src, src, sstatus->max_hp,0,0,1, skill_id); if(skill_id == HVAN_EXPLOSION && src->type == BL_HOM) { struct homun_data *hd = (struct homun_data *)src; if (hd != nullptr) { hd->homunculus.intimacy = hom_intimacy_grade2intimacy(HOMGRADE_HATE_WITH_PASSION); if (hd->master) clif_send_homdata(hd->master,SP_INTIMATE,hd->homunculus.intimacy / 100); } } break; case AL_ANGELUS: #ifdef RENEWAL case PR_SUFFRAGIUM: case PR_IMPOSITIO: #endif case PR_MAGNIFICAT: case PR_GLORIA: if (sd == NULL || sd->status.party_id == 0 || (flag & 1)) { // Animations don't play when outside visible range if (check_distance_bl(src, bl, AREA_SIZE)) clif_skill_nodamage(bl, bl, skill_id, skill_lv, 1); sc_start(src, bl, type, 100, skill_lv, skill_get_time(skill_id, skill_lv)); } else if (sd) party_foreachsamemap(skill_area_sub, sd, skill_get_splash(skill_id, skill_lv), src, skill_id, skill_lv, tick, flag | BCT_PARTY | 1, skill_castend_nodamage_id); break; #ifdef RENEWAL case MC_LOUD: #endif case SN_WINDWALK: case CASH_BLESSING: case CASH_INCAGI: case CASH_ASSUMPTIO: case WM_FRIGG_SONG: case NV_HELPANGEL: case IG_GUARDIAN_SHIELD: case IG_ULTIMATE_SACRIFICE:// Is the animation on this skill correct? Check if its on caster only or all affected. [Rytech] if( sd == NULL || sd->status.party_id == 0 || (flag & 1) ) clif_skill_nodamage(bl, bl, skill_id, skill_lv, sc_start(src,bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); else if (sd) { if (skill_id == IG_ULTIMATE_SACRIFICE) status_set_hp(src, 1, 0); party_foreachsamemap(skill_area_sub, sd, skill_get_splash(skill_id, skill_lv), src, skill_id, skill_lv, tick, flag|BCT_PARTY|1, skill_castend_nodamage_id); } break; case MER_MAGNIFICAT: if( mer != NULL ) { clif_skill_nodamage(bl, bl, skill_id, skill_lv, sc_start(src,bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); if( mer->master && mer->master->status.party_id != 0 && !(flag&1) ) party_foreachsamemap(skill_area_sub, mer->master, skill_get_splash(skill_id, skill_lv), src, skill_id, skill_lv, tick, flag|BCT_PARTY|1, skill_castend_nodamage_id); else if( mer->master && !(flag&1) ) clif_skill_nodamage(src, &mer->master->bl, skill_id, skill_lv, sc_start(src,bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); } break; case BS_ADRENALINE: case BS_ADRENALINE2: case BS_WEAPONPERFECT: case BS_OVERTHRUST: if (sd == NULL || sd->status.party_id == 0 || (flag & 1)) { int weapontype = skill_get_weapontype(skill_id); if (!weapontype || !dstsd || pc_check_weapontype(dstsd, weapontype)) { clif_skill_nodamage(bl, bl, skill_id, skill_lv, sc_start2(src, bl, type, 100, skill_lv, (src == bl) ? 1 : 0, skill_get_time(skill_id, skill_lv))); } } else if (sd) { party_foreachsamemap(skill_area_sub, sd,skill_get_splash(skill_id, skill_lv), src,skill_id,skill_lv,tick, flag|BCT_PARTY|1, skill_castend_nodamage_id); } break; case BS_MAXIMIZE: case NV_TRICKDEAD: case CR_DEFENDER: case ML_DEFENDER: case CR_AUTOGUARD: case ML_AUTOGUARD: case TK_READYSTORM: case TK_READYDOWN: case TK_READYTURN: case TK_READYCOUNTER: case TK_DODGE: case CR_SHRINK: case SG_FUSION: case GS_GATLINGFEVER: case SJ_LUNARSTANCE: case SJ_STARSTANCE: case SJ_UNIVERSESTANCE: case SJ_SUNSTANCE: case SP_SOULCOLLECT: case IG_GUARD_STANCE: case IG_ATTACK_STANCE: if( tsce ) { clif_skill_nodamage(src,bl,skill_id,skill_lv,status_change_end(bl, type)); map_freeblock_unlock(); return 0; } if( skill_id == SP_SOULCOLLECT ){ clif_skill_nodamage(src, bl, skill_id, skill_lv, sc_start2(src, bl, type, 100, skill_lv, pc_checkskill(sd, SP_SOULENERGY), skill_get_time(skill_id, skill_lv))); }else{ clif_skill_nodamage(src, bl, skill_id, skill_lv, sc_start(src, bl, type, 100, skill_lv, skill_get_time(skill_id, skill_lv))); } break; case SL_KAITE: case SL_KAAHI: case SL_KAIZEL: case SL_KAUPE: case SP_KAUTE: if (sd) { if (!dstsd || !( (sd->sc.getSCE(SC_SPIRIT) && sd->sc.getSCE(SC_SPIRIT)->val2 == SL_SOULLINKER) || (dstsd->class_&MAPID_UPPERMASK) == MAPID_SOUL_LINKER || dstsd->status.char_id == sd->status.char_id || dstsd->status.char_id == sd->status.partner_id || dstsd->status.char_id == sd->status.child || (skill_id == SP_KAUTE && dstsd->sc.getSCE(SC_SOULUNITY)) )) { status_change_start(src,src,SC_STUN,10000,skill_lv,0,0,0,500,SCSTART_NORATEDEF); clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); break; } } if (skill_id == SP_KAUTE) { if (!status_charge(src, sstatus->max_hp * (10 + 2 * skill_lv) / 100, 0)) { if (sd) clif_skill_fail(sd,skill_id, USESKILL_FAIL,0); break; } clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); status_heal(bl, 0, tstatus->max_sp * (10 + 2 * skill_lv) / 100, 2); } else clif_skill_nodamage(src, bl, skill_id, skill_lv, sc_start(src, bl, type, 100, skill_lv, skill_get_time(skill_id, skill_lv))); break; case SM_AUTOBERSERK: case MER_AUTOBERSERK: if( tsce ) i = status_change_end(bl, type); else i = sc_start(src,bl,type,100,skill_lv,60000); clif_skill_nodamage(src,bl,skill_id,skill_lv,i); break; case TF_HIDING: case ST_CHASEWALK: case KO_YAMIKUMO: if (tsce) { clif_skill_nodamage(src,bl,skill_id,-1,status_change_end(bl, type)); //Hide skill-scream animation. map_freeblock_unlock(); return 0; } clif_skill_nodamage(src,bl,skill_id,-1,sc_start(src,bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); break; case TK_RUN: if (tsce) { clif_skill_nodamage(src,bl,skill_id,skill_lv,status_change_end(bl, type)); map_freeblock_unlock(); return 0; } clif_skill_nodamage(src,bl,skill_id,skill_lv,sc_start4(src,bl,type,100,skill_lv,unit_getdir(bl),0,0,0)); if (sd) // If the client receives a skill-use packet inmediately before a walkok packet, it will discard the walk packet! [Skotlex] clif_walkok(sd); // So aegis has to resend the walk ok. break; case AS_CLOAKING: case GC_CLOAKINGEXCEED: case LG_FORCEOFVANGUARD: case SC_REPRODUCE: case SC_INVISIBILITY: case RA_CAMOUFLAGE: if (tsce) { i = status_change_end(bl, type); if( i ) clif_skill_nodamage(src,bl,skill_id,( skill_id == LG_FORCEOFVANGUARD || skill_id == RA_CAMOUFLAGE ) ? skill_lv : -1,i); else if( sd ) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); map_freeblock_unlock(); return 0; } i = sc_start(src,bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv)); if( i ) clif_skill_nodamage(src,bl,skill_id,( skill_id == LG_FORCEOFVANGUARD || skill_id == RA_CAMOUFLAGE ) ? skill_lv : -1,i); else if( sd ) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); break; case CG_SPECIALSINGER: if (tsc && tsc->getSCE(SC_ENSEMBLEFATIGUE)) { clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); status_change_end(bl, SC_ENSEMBLEFATIGUE); } break; case BD_ADAPTATION: #ifdef RENEWAL clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); sc_start(src, bl, type, 100, skill_lv, skill_get_time(skill_id, skill_lv)); #else if(tsc && tsc->getSCE(SC_DANCING)){ clif_skill_nodamage(src,bl,skill_id,skill_lv,1); status_change_end(bl, SC_DANCING); } #endif break; case BA_FROSTJOKER: case DC_SCREAM: clif_skill_nodamage(src,bl,skill_id,skill_lv,1); skill_addtimerskill(src,tick+3000,bl->id,src->x,src->y,skill_id,skill_lv,0,flag); if (md) { // custom hack to make the mob display the skill, because these skills don't show the skill use text themselves //NOTE: mobs don't have the sprite animation that is used when performing this skill (will cause glitches) char temp[70]; snprintf(temp, sizeof(temp), "%s : %s !!",md->name,skill_get_desc(skill_id)); clif_disp_overhead(&md->bl,temp); } break; case BA_PANGVOICE: clif_skill_nodamage(src,bl,skill_id,skill_lv, sc_start(src,bl,SC_CONFUSION,70,7,skill_get_time(skill_id,skill_lv))); #ifdef RENEWAL sc_start(src, bl, SC_BLEEDING, 30, skill_lv, skill_get_time2(skill_id, skill_lv)); // TODO: Confirm success rate #endif break; case DC_WINKCHARM: if( dstsd ) { clif_skill_nodamage(src,bl,skill_id,skill_lv, sc_start(src,bl,SC_CONFUSION,10,7,skill_get_time2(skill_id,skill_lv))); #ifdef RENEWAL sc_start(src, bl, SC_HALLUCINATION, 30, skill_lv, skill_get_time(skill_id, skill_lv)); // TODO: Confirm success rate and duration #endif } else if( dstmd ) { if( status_get_lv(src) > status_get_lv(bl) && (tstatus->race == RC_DEMON || tstatus->race == RC_DEMIHUMAN || tstatus->race == RC_PLAYER_HUMAN || tstatus->race == RC_PLAYER_DORAM || tstatus->race == RC_ANGEL) && !status_has_mode(tstatus,MD_STATUSIMMUNE) ) clif_skill_nodamage(src,bl,skill_id,skill_lv, sc_start2(src,bl,type,(status_get_lv(src) - status_get_lv(bl)) + 40, skill_lv, src->id, skill_get_time(skill_id, skill_lv))); else { clif_skill_nodamage(src,bl,skill_id,skill_lv,0); if(sd) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); } } break; #ifdef RENEWAL case BD_LULLABY: case BD_RICHMANKIM: case BD_ETERNALCHAOS: case BD_DRUMBATTLEFIELD: case BD_RINGNIBELUNGEN: case BD_ROKISWEIL: case BD_INTOABYSS: case BD_SIEGFRIED: case BA_DISSONANCE: case BA_POEMBRAGI: case BA_WHISTLE: case BA_ASSASSINCROSS: case BA_APPLEIDUN: case DC_UGLYDANCE: case DC_HUMMING: case DC_DONTFORGETME: case DC_FORTUNEKISS: case DC_SERVICEFORYOU: skill_castend_song(src, skill_id, skill_lv, tick); break; #endif case TF_STEAL: if(sd) { if(pc_steal_item(sd,bl,skill_lv)) clif_skill_nodamage(src,bl,skill_id,skill_lv,1); else clif_skill_fail(sd,skill_id,USESKILL_FAIL,0); } break; case RG_STEALCOIN: if(sd) { if(pc_steal_coin(sd,bl)) { dstmd->state.provoke_flag = src->id; mob_target(dstmd, src, skill_get_range2(src, skill_id, skill_lv, true)); clif_skill_nodamage(src,bl,skill_id,skill_lv,1); } else clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); } break; case MG_STONECURSE: { if (status_has_mode(tstatus,MD_STATUSIMMUNE)) { if (sd) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); break; } if(status_isimmune(bl) || !tsc) break; int32 brate = 0; if (sd && sd->sc.getSCE(SC_PETROLOGY_OPTION)) brate = sd->sc.getSCE(SC_PETROLOGY_OPTION)->val3; if (sc_start2(src, bl, type, (skill_lv * 4 + 20) + brate, skill_lv, src->id, skill_get_time2(skill_id, skill_lv), skill_get_time(skill_id, skill_lv))) clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); else if(sd) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); // Level 6-10 doesn't consume a red gem if it fails [celest] if (skill_lv > 5) { // not to consume items map_freeblock_unlock(); return 0; } } } break; case NV_FIRSTAID: clif_skill_nodamage(src,bl,skill_id,5,1); status_heal(bl,5,0,0); break; case AL_CURE: if(status_isimmune(bl)) { clif_skill_nodamage(src,bl,skill_id,skill_lv,0); break; } status_change_end(bl, SC_SILENCE); status_change_end(bl, SC_BLIND); status_change_end(bl, SC_CONFUSION); status_change_end(bl, SC_BITESCAR); clif_skill_nodamage(src,bl,skill_id,skill_lv,1); break; case TF_DETOXIFY: clif_skill_nodamage(src,bl,skill_id,skill_lv,1); status_change_end(bl, SC_POISON); status_change_end(bl, SC_DPOISON); break; case PR_STRECOVERY: if(status_isimmune(bl)) { clif_skill_nodamage(src,bl,skill_id,skill_lv,0); break; } if (tsc) { status_change_end(bl, SC_FREEZE); status_change_end(bl, SC_STONEWAIT); status_change_end(bl, SC_STONE); status_change_end(bl, SC_SLEEP); status_change_end(bl, SC_STUN); status_change_end(bl, SC_WHITEIMPRISON); status_change_end(bl, SC_STASIS); status_change_end(bl, SC_NETHERWORLD); } if(battle_check_undead(tstatus->race,tstatus->def_ele)) skill_addtimerskill(src, tick+1000, bl->id, 0, 0, skill_id, skill_lv, 100, flag); clif_skill_nodamage(src,bl,skill_id,skill_lv,1); if(dstmd) mob_unlocktarget(dstmd,tick); break; // Mercenary Supportive Skills case MER_BENEDICTION: status_change_end(bl, SC_CURSE); status_change_end(bl, SC_BLIND); clif_skill_nodamage(src,bl,skill_id,skill_lv,1); break; case MER_COMPRESS: status_change_end(bl, SC_BLEEDING); clif_skill_nodamage(src,bl,skill_id,skill_lv,1); break; case MER_MENTALCURE: status_change_end(bl, SC_CONFUSION); clif_skill_nodamage(src,bl,skill_id,skill_lv,1); break; case MER_RECUPERATE: status_change_end(bl, SC_POISON); status_change_end(bl, SC_DPOISON); status_change_end(bl, SC_SILENCE); clif_skill_nodamage(src,bl,skill_id,skill_lv,1); break; case MER_REGAIN: status_change_end(bl, SC_SLEEP); status_change_end(bl, SC_STUN); clif_skill_nodamage(src,bl,skill_id,skill_lv,1); break; case MER_TENDER: status_change_end(bl, SC_FREEZE); status_change_end(bl, SC_STONE); clif_skill_nodamage(src,bl,skill_id,skill_lv,1); break; case MER_SCAPEGOAT: if( mer && mer->master ) { status_heal(&mer->master->bl, mer->battle_status.hp, 0, 2); status_damage(src, src, mer->battle_status.max_hp, 0, 0, 1, skill_id); } break; case MER_ESTIMATION: if( !mer ) break; sd = mer->master; case WZ_ESTIMATION: if( sd == NULL ) break; if( dstsd ) { // Fail on Players clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); break; } clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); clif_skill_estimation(sd, bl); if( skill_id == MER_ESTIMATION ) sd = NULL; break; case BS_REPAIRWEAPON: if(sd && dstsd) clif_item_repair_list(sd,dstsd,skill_lv); break; case MC_IDENTIFY: if(sd) { clif_item_identify_list(sd); if( sd->menuskill_id != MC_IDENTIFY ) {// failed, dont consume anything map_freeblock_unlock(); return 1; } else { // consume sp only if succeeded struct s_skill_condition req = skill_get_requirement(sd,skill_id,skill_lv); status_zap(src,0,req.sp); } } break; // Weapon Refining [Celest] case WS_WEAPONREFINE: if(sd) clif_item_refine_list(sd); break; case MC_VENDING: if(sd) { //Prevent vending of GMs with unnecessary Level to trade/drop. [Skotlex] if ( !pc_can_give_items(sd) ) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); else { sd->state.prevend = 1; sd->state.workinprogress = WIP_DISABLE_ALL; sd->vend_skill_lv = skill_lv; ARR_FIND(0, MAX_CART, i, sd->cart.u.items_cart[i].nameid && sd->cart.u.items_cart[i].id == 0); if (i < MAX_CART) { // Save the cart before opening the vending UI sd->state.pending_vending_ui = true; intif_storage_save(sd, &sd->cart); } else{ // Instantly open the vending UI sd->state.pending_vending_ui = false; clif_openvendingreq(sd,2+skill_lv); } } } break; case AL_TELEPORT: case ALL_ODINS_RECALL: if(sd) { if (map_getmapflag(bl->m, MF_NOTELEPORT) && skill_lv <= 2) { clif_skill_teleportmessage(sd,0); break; } if(!battle_config.duel_allow_teleport && sd->duel_group && skill_lv <= 2) { // duel restriction [LuzZza] char output[128]; sprintf(output, msg_txt(sd,365), skill_get_name(AL_TELEPORT)); clif_displaymessage(sd->fd, output); //"Duel: Can't use %s in duel." break; } if (sd->hd && battle_config.hom_setting&HOMSET_RESET_REUSESKILL_TELEPORTED) { sd->hd->blockskill.clear(); sd->hd->blockskill.shrink_to_fit(); } if( sd->state.autocast || ( (sd->skillitem == AL_TELEPORT || battle_config.skip_teleport_lv1_menu) && skill_lv == 1 ) || skill_lv == 3 ) { if( skill_lv == 1 ) pc_randomwarp(sd,CLR_TELEPORT); else pc_setpos( sd, mapindex_name2id( sd->status.save_point.map ), sd->status.save_point.x, sd->status.save_point.y, CLR_TELEPORT ); break; } clif_skill_nodamage(src,bl,skill_id,skill_lv,1); if( skill_lv == 1 && skill_id != ALL_ODINS_RECALL ) clif_skill_warppoint( sd, skill_id, skill_lv, "Random" ); else clif_skill_warppoint( sd, skill_id, skill_lv, "Random", sd->status.save_point.map ); } else unit_warp(bl,-1,-1,-1,CLR_TELEPORT); break; case NPC_EXPULSION: clif_skill_nodamage(src,bl,skill_id,skill_lv,1); unit_warp(bl,-1,-1,-1,CLR_TELEPORT); break; case AL_HOLYWATER: if(sd) { if (skill_produce_mix(sd, skill_id, ITEMID_HOLY_WATER, 0, 0, 0, 1, -1)) { struct skill_unit* su; if ((su = map_find_skill_unit_oncell(bl, bl->x, bl->y, NJ_SUITON, NULL, 0)) != NULL) skill_delunit(su); clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); } else clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); } break; case TF_PICKSTONE: if(sd) { unsigned char eflag; struct item item_tmp; struct block_list tbl; clif_skill_nodamage(src,bl,skill_id,skill_lv,1); memset(&item_tmp,0,sizeof(item_tmp)); memset(&tbl,0,sizeof(tbl)); // [MouseJstr] item_tmp.nameid = ITEMID_STONE; item_tmp.identify = 1; tbl.id = 0; // Commented because of duplicate animation [Lemongrass] // At the moment this displays the pickup animation a second time // If this is required in older clients, we need to add a version check here //clif_takeitem(&sd->bl,&tbl); eflag = pc_additem(sd,&item_tmp,1,LOG_TYPE_PRODUCE); if(eflag) { clif_additem(sd,0,0,eflag); map_addflooritem(&item_tmp,1,sd->bl.m,sd->bl.x,sd->bl.y,0,0,0,0,0); } } break; case ASC_CDP: if(sd) { if(skill_produce_mix(sd, skill_id, ITEMID_POISON_BOTTLE, 0, 0, 0, 1, -1)) //Produce a Poison Bottle. clif_skill_nodamage(src,bl,skill_id,skill_lv,1); else clif_skill_fail(sd,skill_id,USESKILL_FAIL_STUFF_INSUFFICIENT,0); } break; case RG_STRIPWEAPON: case RG_STRIPSHIELD: case RG_STRIPARMOR: case RG_STRIPHELM: case ST_FULLSTRIP: case GC_WEAPONCRUSH: case SC_STRIPACCESSARY: case ABC_STRIP_SHADOW: { bool i; //Special message when trying to use strip on FCP [Jobbie] if( sd && skill_id == ST_FULLSTRIP && tsc && tsc->getSCE(SC_CP_WEAPON) && tsc->getSCE(SC_CP_HELM) && tsc->getSCE(SC_CP_ARMOR) && tsc->getSCE(SC_CP_SHIELD)) { clif_gospel_info(sd, 0x28); break; } if ( sd && tsc && sd->sc.getSCE(SC_SPIRIT) && sd->sc.getSCE(SC_SPIRIT)->val2 == SL_ROGUE && rand()%100 < 10 && ( skill_id == RG_STRIPWEAPON && tsc->getSCE(SC_CP_WEAPON) || skill_id == RG_STRIPSHIELD && tsc->getSCE(SC_CP_SHIELD) || skill_id == RG_STRIPARMOR && tsc->getSCE(SC_CP_ARMOR) || skill_id == RG_STRIPHELM && tsc->getSCE(SC_CP_HELM) ) ) { int item_id = 7139; // Glistening Coat int ii; ARR_FIND( 0, MAX_INVENTORY, ii, sd->inventory.u.items_inventory[ii].nameid == item_id ); if ( ii < MAX_INVENTORY ) { pc_delitem( sd, ii, 1, 0, 0, LOG_TYPE_CONSUME); switch ( skill_id ) { case RG_STRIPWEAPON: status_change_end( bl, SC_CP_WEAPON, INVALID_TIMER ); sc_start(NULL,bl,SC_STRIPWEAPON,100,skill_lv,skill_get_time(skill_id,skill_lv)); break; case RG_STRIPSHIELD: status_change_end( bl, SC_CP_SHIELD, INVALID_TIMER ); sc_start(NULL,bl,SC_STRIPSHIELD,100,skill_lv,skill_get_time(skill_id,skill_lv)); break; case RG_STRIPARMOR: status_change_end( bl, SC_CP_ARMOR, INVALID_TIMER ); sc_start(NULL,bl,SC_STRIPARMOR,100,skill_lv,skill_get_time(skill_id,skill_lv)); break; case RG_STRIPHELM: status_change_end( bl, SC_CP_HELM, INVALID_TIMER ); sc_start(NULL,bl,SC_STRIPHELM,100,skill_lv,skill_get_time(skill_id,skill_lv)); break; } clif_skill_nodamage( src, bl, skill_id, skill_lv, i ); break; } } if( (i = skill_strip_equip(src, bl, skill_id, skill_lv)) || (skill_id != ST_FULLSTRIP && skill_id != GC_WEAPONCRUSH ) ) clif_skill_nodamage(src,bl,skill_id,skill_lv,i); //Nothing stripped. if( sd && !i ) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); break; } case AM_BERSERKPITCHER: case AM_POTIONPITCHER: { int j,hp = 0,sp = 0; if( dstmd && dstmd->mob_id == MOBID_EMPERIUM ) { map_freeblock_unlock(); return 1; } if( sd ) { int x,bonus=100; struct s_skill_condition require = skill_get_requirement(sd, skill_id, skill_lv); x = skill_lv%11 - 1; j = pc_search_inventory(sd, require.itemid[x]); if (j < 0 || require.itemid[x] <= 0) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); map_freeblock_unlock(); return 1; } if (sd->inventory_data[j] == NULL || sd->inventory.u.items_inventory[j].amount < require.amount[x]) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); map_freeblock_unlock(); return 1; } if( skill_id == AM_BERSERKPITCHER ) { if( dstsd && dstsd->status.base_level < (unsigned int)sd->inventory_data[j]->elv ) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); map_freeblock_unlock(); return 1; } } potion_flag = 1; potion_hp = potion_sp = potion_per_hp = potion_per_sp = 0; potion_target = bl->id; run_script(sd->inventory_data[j]->script,0,sd->bl.id,0); potion_flag = potion_target = 0; if( sd->sc.getSCE(SC_SPIRIT) && sd->sc.getSCE(SC_SPIRIT)->val2 == SL_ALCHEMIST ) bonus += sd->status.base_level; if( potion_per_hp > 0 || potion_per_sp > 0 ) { hp = tstatus->max_hp * potion_per_hp / 100; hp = hp * (100 + pc_checkskill(sd,AM_POTIONPITCHER)*10 + pc_checkskill(sd,AM_LEARNINGPOTION)*5)*bonus/10000; if( dstsd ) { sp = dstsd->status.max_sp * potion_per_sp / 100; sp = sp * (100 + pc_checkskill(sd,AM_POTIONPITCHER)*10 + pc_checkskill(sd,AM_LEARNINGPOTION)*5)*bonus/10000; } } else { if( potion_hp > 0 ) { hp = potion_hp * (100 + pc_checkskill(sd,AM_POTIONPITCHER)*10 + pc_checkskill(sd,AM_LEARNINGPOTION)*5)*bonus/10000; hp = hp * (100 + (tstatus->vit<<1)) / 100; if( dstsd ) hp = hp * (100 + pc_checkskill(dstsd,SM_RECOVERY)*10) / 100; } if( potion_sp > 0 ) { sp = potion_sp * (100 + pc_checkskill(sd,AM_POTIONPITCHER)*10 + pc_checkskill(sd,AM_LEARNINGPOTION)*5)*bonus/10000; sp = sp * (100 + (tstatus->int_<<1)) / 100; if( dstsd ) sp = sp * (100 + pc_checkskill(dstsd,MG_SRECOVERY)*10) / 100; } } if ((bonus = pc_get_itemgroup_bonus_group(sd, IG_POTION, sd->itemgrouphealrate))) { hp += hp * bonus / 100; } if( ( bonus = pc_get_itemgroup_bonus_group( sd, IG_POTION, sd->itemgroupsphealrate ) ) ){ sp += sp * bonus / 100; } if( (j = pc_skillheal_bonus(sd, skill_id)) ) { hp += hp * j / 100; sp += sp * j / 100; } } else { //Maybe replace with potion_hp, but I'm unsure how that works [Playtester] switch (skill_lv) { case 1: hp = 45; break; case 2: hp = 105; break; case 3: hp = 175; break; default: hp = 325; break; } hp = (hp + rnd()%(skill_lv*20+1)) * (150 + skill_lv*10) / 100; hp = hp * (100 + (tstatus->vit<<1)) / 100; if( dstsd ) hp = hp * (100 + pc_checkskill(dstsd,SM_RECOVERY)*10) / 100; } if( dstsd && (j = pc_skillheal2_bonus(dstsd, skill_id)) ) { hp += hp * j / 100; sp += sp * j / 100; } // Final heal increased by HPlus. // Is this the right place for this??? [Rytech] // Can HPlus also affect SP recovery??? if (sd && sstatus->hplus > 0) { hp += hp * sstatus->hplus / 100; sp += sp * sstatus->hplus / 100; } if (tsc && tsc->count) { uint8 penalty = 0; if (tsc->getSCE(SC_WATER_INSIGNIA) && tsc->getSCE(SC_WATER_INSIGNIA)->val1 == 2) { hp += hp / 10; sp += sp / 10; } if (tsc->getSCE(SC_CRITICALWOUND)) penalty += tsc->getSCE(SC_CRITICALWOUND)->val2; if (tsc->getSCE(SC_DEATHHURT) && tsc->getSCE(SC_DEATHHURT)->val3) penalty += 20; if (tsc->getSCE(SC_NORECOVER_STATE)) penalty = 100; if (penalty > 0) { hp -= hp * penalty / 100; sp -= sp * penalty / 100; } } #ifdef RENEWAL if (bl->type == BL_HOM) hp *= 3; // Heal effectiveness is 3x for Homunculus #endif clif_skill_nodamage(src,bl,skill_id,skill_lv,1); if( hp > 0 || (skill_id == AM_POTIONPITCHER && sp <= 0) ) clif_skill_nodamage(NULL,bl,AL_HEAL,hp,1); if( sp > 0 ) clif_skill_nodamage(NULL,bl,MG_SRECOVERY,sp,1); if (tsc) { #ifdef RENEWAL if (tsc->getSCE(SC_EXTREMITYFIST2)) sp = 0; #endif if (tsc->getSCE(SC_NORECOVER_STATE)) { hp = 0; sp = 0; } } status_heal(bl,hp,sp,0); } break; case AM_CP_WEAPON: case AM_CP_SHIELD: case AM_CP_ARMOR: case AM_CP_HELM: { unsigned int equip[] = {EQP_WEAPON, EQP_SHIELD, EQP_ARMOR, EQP_HEAD_TOP}; enum sc_type scid = (sc_type)(SC_STRIPWEAPON + (skill_id - AM_CP_WEAPON)); if( sd && ( bl->type != BL_PC /*|| ( dstsd && pc_checkequip(dstsd,equip[skill_id - AM_CP_WEAPON]) < 0 )*/ ) ){ clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); map_freeblock_unlock(); // Don't consume item requirements return 0; } status_change_end(bl, scid, INVALID_TIMER); clif_skill_nodamage(src,bl,skill_id,skill_lv, sc_start(src,bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); } break; case AM_TWILIGHT1: if (sd) { clif_skill_nodamage(src,bl,skill_id,skill_lv,1); //Prepare 200 White Potions. if (!skill_produce_mix(sd, skill_id, ITEMID_WHITE_POTION, 0, 0, 0, 200, -1)) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); } break; case AM_TWILIGHT2: if (sd) { clif_skill_nodamage(src,bl,skill_id,skill_lv,1); //Prepare 200 Slim White Potions. if (!skill_produce_mix(sd, skill_id, ITEMID_WHITE_SLIM_POTION, 0, 0, 0, 200, -1)) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); } break; case AM_TWILIGHT3: if (sd) { int ebottle = pc_search_inventory(sd,ITEMID_EMPTY_BOTTLE); short alcohol_idx = -1, acid_idx = -1, fire_idx = -1; if( ebottle >= 0 ) ebottle = sd->inventory.u.items_inventory[ebottle].amount; //check if you can produce all three, if not, then fail: if (!(alcohol_idx = skill_can_produce_mix(sd,ITEMID_ALCOHOL,-1, 100)) //100 Alcohol || !(acid_idx = skill_can_produce_mix(sd,ITEMID_ACID_BOTTLE,-1, 50)) //50 Acid Bottle || !(fire_idx = skill_can_produce_mix(sd,ITEMID_FIRE_BOTTLE,-1, 50)) //50 Flame Bottle || ebottle < 200 //200 empty bottle are required at total. ) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); break; } clif_skill_nodamage(src,bl,skill_id,skill_lv,1); skill_produce_mix(sd, skill_id, ITEMID_ALCOHOL, 0, 0, 0, 100, alcohol_idx-1); skill_produce_mix(sd, skill_id, ITEMID_ACID_BOTTLE, 0, 0, 0, 50, acid_idx-1); skill_produce_mix(sd, skill_id, ITEMID_FIRE_BOTTLE, 0, 0, 0, 50, fire_idx-1); } break; case SA_DISPELL: if (flag&1 || (i = skill_get_splash(skill_id, skill_lv)) < 1) { if (sd && dstsd && !map_flag_vs(sd->bl.m) && (!sd->duel_group || sd->duel_group != dstsd->duel_group) && (!sd->status.party_id || sd->status.party_id != dstsd->status.party_id)) break; // Outside PvP it should only affect party members and no skill fail message clif_skill_nodamage(src,bl,skill_id,skill_lv,1); if((dstsd && (dstsd->class_&MAPID_UPPERMASK) == MAPID_SOUL_LINKER) || (tsc && tsc->getSCE(SC_SPIRIT) && tsc->getSCE(SC_SPIRIT)->val2 == SL_ROGUE) //Rogue's spirit defends againt dispel. || rnd()%100 >= 50+10*skill_lv) { if (sd) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); break; } if(status_isimmune(bl)) break; //Remove bonus_script by Dispell if (dstsd) pc_bonus_script_clear(dstsd,BSF_REM_ON_DISPELL); if(!tsc || !tsc->count) break; //Statuses that can't be Dispelled for (const auto &it : status_db) { sc_type status = static_cast<sc_type>(it.first); if (!tsc->getSCE(status)) continue; if (it.second->flag[SCF_NODISPELL]) continue; switch (status) { // bugreport:4888 these songs may only be dispelled if you're not in their song area anymore case SC_WHISTLE: case SC_ASSNCROS: case SC_POEMBRAGI: case SC_APPLEIDUN: case SC_HUMMING: case SC_DONTFORGETME: case SC_FORTUNE: case SC_SERVICE4U: if (!battle_config.dispel_song || tsc->getSCE(status)->val4 == 0) continue; //If in song area don't end it, even if config enabled break; case SC_ASSUMPTIO: if( bl->type == BL_MOB ) continue; break; } if (i == SC_BERSERK || i == SC_SATURDAYNIGHTFEVER) tsc->getSCE(status)->val2 = 0; //Mark a dispelled berserk to avoid setting hp to 100 by setting hp penalty to 0. status_change_end(bl, status); } break; } //Affect all targets on splash area. map_foreachinallrange(skill_area_sub, bl, i, BL_CHAR, src, skill_id, skill_lv, tick, flag|1, skill_castend_damage_id); break; case TF_BACKSLIDING: //This is the correct implementation as per packet logging information. [Skotlex] { short blew_count = skill_blown(src,bl,skill_get_blewcount(skill_id,skill_lv),unit_getdir(bl),(enum e_skill_blown)(BLOWN_IGNORE_NO_KNOCKBACK #ifdef RENEWAL |BLOWN_DONT_SEND_PACKET #endif )); clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); #ifdef RENEWAL if(blew_count > 0) clif_blown(src); // Always blow, otherwise it shows a casting animation. [Lemongrass] #else clif_slide(bl, bl->x, bl->y); //Show the casting animation on pre-re #endif } break; case TK_HIGHJUMP: { int x,y, dir = unit_getdir(src); struct map_data *mapdata = &map[src->m]; //Fails on noteleport maps, except for GvG and BG maps [Skotlex] if( mapdata->flag[MF_NOTELEPORT] && !(mapdata->flag[MF_BATTLEGROUND] || mapdata_flag_gvg2(mapdata) ) ) { clif_skill_nodamage(src, bl, TK_HIGHJUMP, skill_lv, 1); break; } else if(dir%2) { //Diagonal x = src->x + dirx[dir]*(skill_lv*4)/3; y = src->y + diry[dir]*(skill_lv*4)/3; } else { x = src->x + dirx[dir]*skill_lv*2; y = src->y + diry[dir]*skill_lv*2; } int x1 = x + dirx[dir]; int y1 = y + diry[dir]; clif_skill_nodamage(src,bl,TK_HIGHJUMP,skill_lv,1); if( !map_count_oncell(src->m,x,y,BL_PC|BL_NPC|BL_MOB,0) && map_getcell(src->m,x,y,CELL_CHKREACH) && !map_count_oncell(src->m,x1,y1,BL_PC|BL_NPC|BL_MOB,0) && map_getcell(src->m,x1,y1,CELL_CHKREACH) && unit_movepos(src, x, y, 1, 0)) clif_blown(src); } break; case SA_CASTCANCEL: case SO_SPELLFIST: clif_skill_nodamage(src,bl,skill_id,skill_lv,1); unit_skillcastcancel(src,1); if(sd) { int sp = skill_get_sp(sd->skill_id_old,sd->skill_lv_old); if( skill_id == SO_SPELLFIST ){ sc_start4(src,src,type,100,skill_lv,sd->skill_id_old,sd->skill_lv_old,0,skill_get_time(skill_id,skill_lv)); sd->skill_id_old = sd->skill_lv_old = 0; break; } sp = sp * (90 - (skill_lv-1)*20) / 100; if(sp < 0) sp = 0; status_zap(src, 0, sp); } break; case SA_SPELLBREAKER: { int sp; if(tsc && tsc->getSCE(SC_MAGICROD)) { sp = skill_get_sp(skill_id,skill_lv); sp = sp * tsc->getSCE(SC_MAGICROD)->val2 / 100; if(sp < 1) sp = 1; status_heal(bl,0,sp,2); status_percent_damage(bl, src, 0, -20, false); //20% max SP damage. } else { struct unit_data *ud = unit_bl2ud(bl); int bl_skill_id=0,bl_skill_lv=0,hp = 0; if (!ud || ud->skilltimer == INVALID_TIMER) break; //Nothing to cancel. bl_skill_id = ud->skill_id; bl_skill_lv = ud->skill_lv; if (status_has_mode(tstatus,MD_STATUSIMMUNE)) { //Only 10% success chance against status immune. [Skotlex] if (rnd()%100 < 90) { if (sd) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); break; } } else if (!dstsd || map_flag_vs(bl->m)) //HP damage only on pvp-maps when against players. hp = tstatus->max_hp/50; //Recover 2% HP [Skotlex] clif_skill_nodamage(src,bl,skill_id,skill_lv,1); unit_skillcastcancel(bl,0); sp = skill_get_sp(bl_skill_id,bl_skill_lv); status_zap(bl, hp, sp); if (hp && skill_lv >= 5) hp>>=1; //Recover half damaged HP at level 5 [Skotlex] else hp = 0; if (sp) //Recover some of the SP used sp = sp*(25*(skill_lv-1))/100; if(hp || sp) status_heal(src, hp, sp, 2); } } break; case SA_MAGICROD: #ifdef RENEWAL clif_skill_nodamage(src,src,SA_MAGICROD,skill_lv,1); #endif sc_start(src,bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv)); break; case SA_AUTOSPELL: clif_skill_nodamage(src,bl,skill_id,skill_lv,1); if (sd) { sd->state.workinprogress = WIP_DISABLE_ALL; clif_autospell(sd,skill_lv); } else { int maxlv=1,spellid=0; static const int spellarray[3] = { MG_COLDBOLT,MG_FIREBOLT,MG_LIGHTNINGBOLT }; if(skill_lv >= 10) { spellid = MG_FROSTDIVER; // if (tsc && tsc->getSCE(SC_SPIRIT) && tsc->getSCE(SC_SPIRIT)->val2 == SA_SAGE) // maxlv = 10; // else maxlv = skill_lv - 9; } else if(skill_lv >=8) { spellid = MG_FIREBALL; maxlv = skill_lv - 7; } else if(skill_lv >=5) { spellid = MG_SOULSTRIKE; maxlv = skill_lv - 4; } else if(skill_lv >=2) { int i_rnd = rnd()%3; spellid = spellarray[i_rnd]; maxlv = skill_lv - 1; } else if(skill_lv > 0) { spellid = MG_NAPALMBEAT; maxlv = 3; } if(spellid > 0) sc_start4(src,src,SC_AUTOSPELL,100,skill_lv,spellid,maxlv,0, skill_get_time(SA_AUTOSPELL,skill_lv)); } break; case BS_GREED: if(sd){ clif_skill_nodamage(src,bl,skill_id,skill_lv,1); map_foreachinallrange(skill_greed,bl, skill_get_splash(skill_id, skill_lv),BL_ITEM,bl); } break; case SA_ELEMENTWATER: case SA_ELEMENTFIRE: case SA_ELEMENTGROUND: case SA_ELEMENTWIND: if (sd && (!dstmd || status_has_mode(tstatus,MD_STATUSIMMUNE))) // Only works on monsters (Except status immune monsters). break; case NPC_ATTRICHANGE: case NPC_CHANGEWATER: case NPC_CHANGEGROUND: case NPC_CHANGEFIRE: case NPC_CHANGEWIND: case NPC_CHANGEPOISON: case NPC_CHANGEHOLY: case NPC_CHANGEDARKNESS: case NPC_CHANGETELEKINESIS: clif_skill_nodamage(src,bl,skill_id,skill_lv, sc_start2(src,bl, type, 100, skill_lv, skill_get_ele(skill_id,skill_lv), skill_get_time(skill_id, skill_lv))); break; case NPC_PROVOCATION: clif_skill_nodamage(src,bl,skill_id,skill_lv,1); if (md) mob_unlocktarget(md, tick); break; case NPC_REBIRTH: if( md && md->state.rebirth ) break; // only works once sc_start(src,bl,type,100,skill_lv,INFINITE_TICK); break; case NPC_DARKBLESSING: clif_skill_nodamage(src,bl,skill_id,skill_lv, sc_start2(src,bl,type,(50+skill_lv*5),skill_lv,skill_lv,skill_get_time2(skill_id,skill_lv))); break; case NPC_LICK: status_zap(bl, 0, 100); clif_skill_nodamage(src,bl,skill_id,skill_lv, sc_start(src,bl,type,(skill_lv*20),skill_lv,skill_get_time2(skill_id,skill_lv))); break; case NPC_SUICIDE: clif_skill_nodamage(src,bl,skill_id,skill_lv,1); status_kill(src); //When suiciding, neither exp nor drops is given. break; case NPC_SUMMONSLAVE: case NPC_SUMMONMONSTER: case NPC_DEATHSUMMON: if(md && md->skill_idx >= 0) mob_summonslave(md,md->db->skill[md->skill_idx]->val,skill_lv,skill_id); break; case NPC_CALLSLAVE: mob_warpslave(src,MOB_SLAVEDISTANCE); break; case NPC_RANDOMMOVE: if (md) { md->next_walktime = tick - 1; if (md->special_state.ai == AI_SPHERE) unit_escape(&md->bl, bl, 7, 2); else mob_randomwalk(md,tick); } break; case NPC_SPEEDUP: { // or does it increase casting rate? just a guess xD int i_type = SC_ASPDPOTION0 + skill_lv - 1; if (i_type > SC_ASPDPOTION3) i_type = SC_ASPDPOTION3; clif_skill_nodamage(src,bl,skill_id,skill_lv, sc_start(src,bl,(sc_type)i_type,100,skill_lv,skill_lv * 60000)); } break; case NPC_REVENGE: // not really needed... but adding here anyway ^^ if (md && md->master_id > 0) { struct block_list *mbl, *tbl; if ((mbl = map_id2bl(md->master_id)) == NULL || (tbl = battle_gettargeted(mbl)) == NULL) break; md->state.provoke_flag = tbl->id; mob_target(md, tbl, sstatus->rhw.range); } break; case NPC_RUN: if (md) { block_list* tbl = map_id2bl(md->target_id); if (tbl) { mob_unlocktarget(md, tick); unit_escape(src, tbl, skill_lv > 1 ? skill_lv : AREA_SIZE, 2); // Send distance in skill level > 1 } } break; case NPC_TRANSFORMATION: case NPC_METAMORPHOSIS: if(md && md->skill_idx >= 0) { int class_ = mob_random_class (md->db->skill[md->skill_idx]->val,0); if (skill_lv > 1) //Multiply the rest of mobs. [Skotlex] mob_summonslave(md,md->db->skill[md->skill_idx]->val,skill_lv-1,skill_id); if (class_) mob_class_change(md, class_); } break; case NPC_EMOTION_ON: case NPC_EMOTION: //val[0] is the emotion to use. //NPC_EMOTION & NPC_EMOTION_ON can change a mob's mode 'permanently' [Skotlex] //val[1] 'sets' the mode //val[2] adds to the current mode //val[3] removes from the current mode //val[4] if set, asks to delete the previous mode change. if(md && md->skill_idx >= 0 && tsc) { clif_emotion(bl, md->db->skill[md->skill_idx]->val[0]); if(md->db->skill[md->skill_idx]->val[4] && tsce) status_change_end(bl, type); //If mode gets set by NPC_EMOTION then the target should be reset [Playtester] if(!battle_config.npc_emotion_behavior && skill_id == NPC_EMOTION && md->db->skill[md->skill_idx]->val[1]) mob_unlocktarget(md,tick); if(md->db->skill[md->skill_idx]->val[1] || md->db->skill[md->skill_idx]->val[2]) sc_start4(src,src, type, 100, skill_lv, md->db->skill[md->skill_idx]->val[1], md->db->skill[md->skill_idx]->val[2], md->db->skill[md->skill_idx]->val[3], skill_get_time(skill_id, skill_lv)); //Reset aggressive state depending on resulting mode if (!battle_config.npc_emotion_behavior) md->state.aggressive = status_has_mode(&md->status,MD_ANGRY)?1:0; } break; case NPC_POWERUP: sc_start(src,bl,SC_INCATKRATE,100,200,skill_get_time(skill_id, skill_lv)); clif_skill_nodamage(src,bl,skill_id,skill_lv, sc_start(src,bl,type,100,100,skill_get_time(skill_id, skill_lv))); break; case NPC_AGIUP: sc_start(src,bl,SC_SPEEDUP1,100,50,skill_get_time(skill_id, skill_lv)); clif_skill_nodamage(src,bl,skill_id,skill_lv, sc_start(src,bl,type,100,100,skill_get_time(skill_id, skill_lv))); break; case NPC_INVISIBLE: //Have val4 passed as 6 is for "infinite cloak" (do not end on attack/skill use). clif_skill_nodamage(src,bl,skill_id,skill_lv, sc_start4(src,bl,type,100,skill_lv,0,0,6,skill_get_time(skill_id,skill_lv))); break; case NPC_SIEGEMODE: // Not implemented/used: Gives EFST_SIEGEMODE which reduces speed to 1000. clif_skill_nodamage(src,bl,skill_id,skill_lv,1); break; case WE_MALE: { uint8 hp_rate = abs(skill_get_hp_rate(skill_id, skill_lv)); if (hp_rate && status_get_hp(src) > status_get_max_hp(src) / hp_rate) { int gain_hp = tstatus->max_hp * hp_rate / 100; // The earned is the same % of the target HP than it costed the caster. [Skotlex] clif_skill_nodamage(src,bl,skill_id,status_heal(bl, gain_hp, 0, 0),1); } } break; case WE_FEMALE: { uint8 sp_rate = abs(skill_get_sp_rate(skill_id, skill_lv)); if (sp_rate && status_get_sp(src) > status_get_max_sp(src) / sp_rate) { int gain_sp = tstatus->max_sp * sp_rate / 100; // The earned is the same % of the target SP than it costed the caster. [Skotlex] clif_skill_nodamage(src,bl,skill_id,status_heal(bl, 0, gain_sp, 0),1); } } break; // parent-baby skills case WE_BABY: if(sd){ map_session_data *f_sd = pc_get_father(sd); map_session_data *m_sd = pc_get_mother(sd); if( (!f_sd && !m_sd) // if neither was found || (sd->status.party_id != 0 && //not in same party ((!f_sd || sd->status.party_id != f_sd->status.party_id) && (!m_sd || sd->status.party_id != m_sd->status.party_id) //if both are online they should all be in same team )) || ((!f_sd || !check_distance_bl(&sd->bl, &f_sd->bl, AREA_SIZE)) //not in same screen && (!m_sd || !check_distance_bl(&sd->bl, &m_sd->bl, AREA_SIZE))) ) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); map_freeblock_unlock(); return 0; } status_change_start(src,bl,SC_STUN,10000,skill_lv,0,0,0,skill_get_time2(skill_id,skill_lv),SCSTART_NORATEDEF); if (f_sd) sc_start(src,&f_sd->bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv)); if (m_sd) sc_start(src,&m_sd->bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv)); } break; case WE_CALLALLFAMILY: if (sd) { map_session_data *p_sd = pc_get_partner(sd); map_session_data *c_sd = pc_get_child(sd); if (!p_sd && !c_sd) { // Fail if no family members are found clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); map_freeblock_unlock(); return 1; } // Partner must be on the same map and in same party if (p_sd && !status_isdead(&p_sd->bl) && p_sd->bl.m == sd->bl.m && p_sd->status.party_id == sd->status.party_id) pc_setpos(p_sd, map_id2index(sd->bl.m), sd->bl.x, sd->bl.y, CLR_TELEPORT); // Child must be on the same map and in same party as the parent casting if (c_sd && !status_isdead(&c_sd->bl) && c_sd->bl.m == sd->bl.m && c_sd->status.party_id == sd->status.party_id) pc_setpos(c_sd, map_id2index(sd->bl.m), sd->bl.x, sd->bl.y, CLR_TELEPORT); } break; case WE_ONEFOREVER: if (sd) { map_session_data *p_sd = pc_get_partner(sd); map_session_data *c_sd = pc_get_child(sd); if (!p_sd && !c_sd && !dstsd) { // Fail if no family members are found clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); map_freeblock_unlock(); return 1; } if (map_flag_gvg2(bl->m) || map_getmapflag(bl->m, MF_BATTLEGROUND)) { // No reviving in WoE grounds! clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); break; } if (status_isdead(bl)) { int per = 30, sper = 0; if (battle_check_undead(tstatus->race, tstatus->def_ele)) break; if (tsc && tsc->getSCE(SC_HELLPOWER)) break; if (map_getmapflag(bl->m, MF_PVP) && dstsd->pvp_point < 0) break; if (dstsd->special_state.restart_full_recover) per = sper = 100; if ((dstsd == p_sd || dstsd == c_sd) && status_revive(bl, per, sper)) // Only family members can be revived clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); } } break; case WE_CHEERUP: if (sd) { map_session_data *f_sd = pc_get_father(sd); map_session_data *m_sd = pc_get_mother(sd); if (!f_sd && !m_sd && !dstsd) { // Fail if no family members are found clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); map_freeblock_unlock(); return 1; } if (flag&1) { // Buff can only be given to parents in 7x7 AoE around baby if (dstsd == f_sd || dstsd == m_sd) clif_skill_nodamage(src, bl, skill_id, skill_lv, sc_start(src, bl, type, 100, skill_lv, skill_get_time(skill_id, skill_lv))); } else map_foreachinrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), BL_PC, src, skill_id, skill_lv, tick, flag|BCT_ALL|1, skill_castend_nodamage_id); } break; case PF_HPCONVERSION: { int hp, sp; hp = sstatus->max_hp/10; sp = hp * 10 * skill_lv / 100; if (!status_charge(src,hp,0)) { if (sd) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); break; } clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); status_heal(bl,0,sp,2); } break; case MA_REMOVETRAP: case HT_REMOVETRAP: { skill_unit* su = BL_CAST(BL_SKILL, bl); std::shared_ptr<s_skill_unit_group> sg; std::shared_ptr<s_skill_db> skill_group; // Mercenaries can remove any trap // Players can only remove their own traps or traps on Vs maps. if( su && (sg = su->group) && (src->type == BL_MER || sg->src_id == src->id || map_flag_vs(bl->m)) && ( skill_group = skill_db.find(sg->skill_id) ) && skill_group->inf2[INF2_ISTRAP] ) { clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); if( sd && !(sg->unit_id == UNT_USED_TRAPS || (sg->unit_id == UNT_ANKLESNARE && sg->val2 != 0 )) ) { // prevent picking up expired traps if( battle_config.skill_removetrap_type ) { // get back all items used to deploy the trap for( i = 0; i < MAX_SKILL_ITEM_REQUIRE; i++ ) { if( skill_group->require.itemid[i] > 0 ) { int flag2; struct item item_tmp; memset(&item_tmp,0,sizeof(item_tmp)); item_tmp.nameid = skill_group->require.itemid[i]; item_tmp.identify = 1; item_tmp.amount = skill_group->require.amount[i]; if( item_tmp.nameid && (flag2=pc_additem(sd,&item_tmp,item_tmp.amount,LOG_TYPE_OTHER)) ){ clif_additem(sd,0,0,flag2); map_addflooritem(&item_tmp,item_tmp.amount,sd->bl.m,sd->bl.x,sd->bl.y,0,0,0,4,0); } } } } else { // get back 1 trap struct item item_tmp; memset(&item_tmp,0,sizeof(item_tmp)); item_tmp.nameid = su->group->item_id?su->group->item_id:ITEMID_TRAP; item_tmp.identify = 1; if( item_tmp.nameid && (flag=pc_additem(sd,&item_tmp,1,LOG_TYPE_OTHER)) ) { clif_additem(sd,0,0,flag); map_addflooritem(&item_tmp,1,sd->bl.m,sd->bl.x,sd->bl.y,0,0,0,4,0); } } } skill_delunit(su); }else if(sd) clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); } break; case HT_SPRINGTRAP: clif_skill_nodamage(src,bl,skill_id,skill_lv,1); { struct skill_unit *su=NULL; if((bl->type==BL_SKILL) && (su=(struct skill_unit *)bl) && (su->group) ){ switch(su->group->unit_id){ case UNT_ANKLESNARE: // ankle snare if (su->group->val2 != 0) // if it is already trapping something don't spring it, // remove trap should be used instead break; // otherwise fallthrough to below case UNT_BLASTMINE: case UNT_SKIDTRAP: case UNT_LANDMINE: case UNT_SHOCKWAVE: case UNT_SANDMAN: case UNT_FLASHER: case UNT_FREEZINGTRAP: case UNT_CLAYMORETRAP: case UNT_TALKIEBOX: su->group->unit_id = UNT_USED_TRAPS; clif_changetraplook(bl, UNT_USED_TRAPS); su->group->limit=DIFF_TICK(tick+1500,su->group->tick); su->limit=DIFF_TICK(tick+1500,su->group->tick); } } } break; case BD_ENCORE: clif_skill_nodamage(src,bl,skill_id,skill_lv,1); if(sd) unit_skilluse_id(src,src->id,sd->skill_id_dance,sd->skill_lv_dance); break; case TR_RETROSPECTION: clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); if (sd) unit_skilluse_id(src, src->id, sd->skill_id_song, sd->skill_lv_song); break; case AS_SPLASHER: if( status_has_mode(tstatus,MD_STATUSIMMUNE) // Renewal dropped the 3/4 hp requirement #ifndef RENEWAL || tstatus-> hp > tstatus->max_hp*3/4 #endif ) { if (sd) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); map_freeblock_unlock(); return 1; } clif_skill_nodamage(src,bl,skill_id,skill_lv, sc_start4(src,bl,type,100,skill_lv,skill_id,src->id,skill_get_time(skill_id,skill_lv),1000)); #ifndef RENEWAL if (sd) skill_blockpc_start (sd, skill_id, skill_get_time(skill_id, skill_lv)+3000); #endif break; case PF_MINDBREAKER: { if(status_has_mode(tstatus,MD_STATUSIMMUNE) || battle_check_undead(tstatus->race,tstatus->def_ele)) { map_freeblock_unlock(); return 1; } if (tsce) { //HelloKitty2 (?) explained that this silently fails when target is //already inflicted. [Skotlex] map_freeblock_unlock(); return 1; } //Has a 55% + skill_lv*5% success chance. if (!clif_skill_nodamage(src,bl,skill_id,skill_lv, sc_start(src,bl,type,55+5*skill_lv,skill_lv,skill_get_time(skill_id,skill_lv)))) { if (sd) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); map_freeblock_unlock(); return 0; } unit_skillcastcancel(bl,0); if (dstmd) mob_target(dstmd, src, skill_get_range2(src, skill_id, skill_lv, true)); } break; case PF_SOULCHANGE: { unsigned int sp1 = 0, sp2 = 0; if (dstmd) { if (dstmd->state.soul_change_flag) { if(sd) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); break; } dstmd->state.soul_change_flag = 1; sp2 = sstatus->max_sp * 3 /100; status_heal(src, 0, sp2, 2); clif_skill_nodamage(src,bl,skill_id,skill_lv,1); break; } sp1 = sstatus->sp; sp2 = tstatus->sp; #ifdef RENEWAL sp1 = sp1 / 2; sp2 = sp2 / 2; if (tsc && tsc->getSCE(SC_EXTREMITYFIST2)) sp1 = tstatus->sp; #endif if (tsc && tsc->getSCE(SC_NORECOVER_STATE)) sp1 = tstatus->sp; status_set_sp(src, sp2, 3); status_set_sp(bl, sp1, 3); clif_skill_nodamage(src,bl,skill_id,skill_lv,1); } break; // Slim Pitcher case CR_SLIMPITCHER: // Updated to block Slim Pitcher from working on barricades and guardian stones. if (dstmd && (dstmd->mob_id == MOBID_EMPERIUM || status_get_class_(bl) == CLASS_BATTLEFIELD)) break; if (potion_hp || potion_sp) { int hp = potion_hp, sp = potion_sp; hp = hp * (100 + (tstatus->vit<<1))/100; sp = sp * (100 + (tstatus->int_<<1))/100; if (dstsd) { if (hp) hp = hp * (100 + pc_checkskill(dstsd,SM_RECOVERY)*10 + pc_skillheal2_bonus(dstsd, skill_id))/40; if (sp) sp = sp * (100 + pc_checkskill(dstsd,MG_SRECOVERY)*10 + pc_skillheal2_bonus(dstsd, skill_id))/100; } if (tsc && tsc->count) { uint8 penalty = 0; if (tsc->getSCE(SC_WATER_INSIGNIA) && tsc->getSCE(SC_WATER_INSIGNIA)->val1 == 2) { hp += hp / 10; sp += sp / 10; } if (tsc->getSCE(SC_CRITICALWOUND)) penalty += tsc->getSCE(SC_CRITICALWOUND)->val2; if (tsc->getSCE(SC_DEATHHURT) && tsc->getSCE(SC_DEATHHURT)->val3 == 1) penalty += 20; if (tsc->getSCE(SC_NORECOVER_STATE)) penalty = 100; if (penalty > 0) { hp -= hp * penalty / 100; sp -= sp * penalty / 100; } } if(hp > 0) clif_skill_nodamage(NULL,bl,AL_HEAL,hp,1); if(sp > 0) clif_skill_nodamage(NULL,bl,MG_SRECOVERY,sp,1); status_heal(bl,hp,sp,0); } break; // Full Chemical Protection case CR_FULLPROTECTION: { unsigned int equip[] = {EQP_WEAPON, EQP_SHIELD, EQP_ARMOR, EQP_HEAD_TOP}; int i_eqp, s = 0, skilltime = skill_get_time(skill_id,skill_lv); for (i_eqp = 0; i_eqp < 4; i_eqp++) { if( bl->type != BL_PC /*|| ( dstsd && pc_checkequip(dstsd,equip[i_eqp]) < 0 )*/ ) continue; status_change_end(bl, (sc_type)(SC_STRIPWEAPON + i), INVALID_TIMER); status_change_end(bl, (sc_type)(SC_STRIPHELM + i), INVALID_TIMER); status_change_end(bl, (sc_type)(SC_STRIPSHIELD + i), INVALID_TIMER); status_change_end(bl, (sc_type)(SC_STRIPARMOR + i), INVALID_TIMER); sc_start(src,bl,(sc_type)(SC_CP_WEAPON + i_eqp),100,skill_lv,skilltime); s++; } if( sd && !s ){ clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); map_freeblock_unlock(); // Don't consume item requirements return 0; } clif_skill_nodamage(src,bl,skill_id,skill_lv,1); } break; case RG_CLEANER: //AppleGirl clif_skill_nodamage(src,bl,skill_id,skill_lv,1); break; #ifndef RENEWAL case CG_LONGINGFREEDOM: { if (tsc && !tsce && (tsce=tsc->getSCE(SC_DANCING)) && tsce->val4 && (tsce->val1&0xFFFF) != CG_MOONLIT) //Can't use Longing for Freedom while under Moonlight Petals. [Skotlex] { clif_skill_nodamage(src,bl,skill_id,skill_lv, sc_start(src,bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); } } break; #endif case CG_TAROTCARD: { int card = -1; if (tsc && tsc->getSCE(SC_TAROTCARD)) { //Target currently has the SUN tarot card effect and is immune to any other effect map_freeblock_unlock(); return 0; } if( rnd() % 100 > skill_lv * 8 || #ifndef RENEWAL (tsc && tsc->getSCE(SC_BASILICA)) || #endif (dstmd && ((dstmd->guardian_data && dstmd->mob_id == MOBID_EMPERIUM) || status_get_class_(bl) == CLASS_BATTLEFIELD)) ) { if( sd ) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); map_freeblock_unlock(); return 0; } status_zap(src,0,skill_get_sp(skill_id,skill_lv)); // consume sp only if succeeded [Inkfish] card = skill_tarotcard(src, bl, skill_id, skill_lv, tick); // actual effect is executed here clif_specialeffect((card == 6) ? src : bl, EF_TAROTCARD1 + card - 1, AREA); clif_skill_nodamage(src,bl,skill_id,skill_lv,1); } break; case SL_ALCHEMIST: case SL_ASSASIN: case SL_BARDDANCER: case SL_BLACKSMITH: case SL_CRUSADER: case SL_HUNTER: case SL_KNIGHT: case SL_MONK: case SL_PRIEST: case SL_ROGUE: case SL_SAGE: case SL_SOULLINKER: case SL_STAR: case SL_SUPERNOVICE: case SL_WIZARD: case SL_NINJA: case SL_GUNNER: case SL_HIGH: if( sc_start2( src, bl, type, 100, skill_lv, skill_id, skill_get_time( skill_id, skill_lv ) ) ){ clif_skill_nodamage( src, bl, skill_id, skill_lv, 1 ); // 1% chance to erase death count on successful cast if( skill_id == SL_SUPERNOVICE && dstsd && dstsd->die_counter && rnd_chance( 1, 100 ) ){ pc_setparam( dstsd, SP_PCDIECOUNTER, 0 ); clif_specialeffect( bl, EF_ANGEL2, AREA ); status_calc_pc( dstsd, SCO_NONE ); } sc_start( src, src, SC_SMA, 100, skill_lv, skill_get_time( SL_SMA, skill_lv ) ); }else{ if( sd ){ clif_skill_fail( sd, skill_id, USESKILL_FAIL_LEVEL, 0 ); } } break; case SP_SOULGOLEM: case SP_SOULSHADOW: case SP_SOULFALCON: case SP_SOULFAIRY: if( sc_start( src, bl, type, 100, skill_lv, skill_get_time( skill_id, skill_lv ) ) ){ clif_skill_nodamage( src, bl, skill_id, skill_lv, 1 ); }else{ if( sd ){ clif_skill_fail( sd, skill_id, USESKILL_FAIL, 0 ); } } break; case SP_SOULREVOLVE: if (!(tsc && (tsc->getSCE(SC_SPIRIT) || tsc->getSCE(SC_SOULGOLEM) || tsc->getSCE(SC_SOULSHADOW) || tsc->getSCE(SC_SOULFALCON) || tsc->getSCE(SC_SOULFAIRY)))) { if (sd) clif_skill_fail(sd, skill_id, USESKILL_FAIL, 0); break; } status_heal(bl, 0, 50*skill_lv, 2); status_change_end(bl, SC_SPIRIT); status_change_end(bl, SC_SOULGOLEM); status_change_end(bl, SC_SOULSHADOW); status_change_end(bl, SC_SOULFALCON); status_change_end(bl, SC_SOULFAIRY); break; case SL_SWOO: if (tsce) { if(sd) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); status_change_start(src,src,SC_STUN,10000,skill_lv,0,0,0,10000,SCSTART_NORATEDEF); status_change_end(bl, SC_SWOO); break; } case SL_SKA: // [marquis007] case SL_SKE: if (sd && !battle_config.allow_es_magic_pc && bl->type != BL_MOB) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); status_change_start(src,src,SC_STUN,10000,skill_lv,0,0,0,500,SCSTART_NOTICKDEF|SCSTART_NORATEDEF); break; } clif_skill_nodamage(src,bl,skill_id,skill_lv,sc_start(src,bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); if (skill_id == SL_SKE) sc_start(src,src,SC_SMA,100,skill_lv,skill_get_time(SL_SMA,skill_lv)); break; // New guild skills [Celest] case GD_BATTLEORDER: case GD_REGENERATION: case GD_RESTORE: case GD_EMERGENCY_MOVE: if(flag&1) { if (status_get_guild_id(src) == status_get_guild_id(bl)) { if( skill_id == GD_RESTORE ) clif_skill_nodamage(src,bl,AL_HEAL,status_percent_heal(bl,90,90),1); else sc_start(src,bl,type,100,skill_lv,skill_get_time(skill_id, skill_lv)); } } else if (status_get_guild_id(src)) { clif_skill_nodamage(src,bl,skill_id,skill_lv,1); map_foreachinallrange(skill_area_sub, src, skill_get_splash(skill_id, skill_lv), BL_PC, src,skill_id,skill_lv,tick, flag|BCT_GUILD|1, skill_castend_nodamage_id); if (sd) #ifdef RENEWAL skill_blockpc_start(sd, skill_id, skill_get_cooldown(skill_id, skill_lv)); #else guild_block_skill(sd, skill_get_time2(skill_id, skill_lv)); #endif } break; case GD_EMERGENCYCALL: case GD_ITEMEMERGENCYCALL: { int8 dx[9] = {-1, 1, 0, 0,-1, 1,-1, 1, 0}; int8 dy[9] = { 0, 0, 1,-1, 1,-1,-1, 1, 0}; uint8 j = 0, calls = 0, called = 0; struct guild *g; // i don't know if it actually summons in a circle, but oh well. g = sd?sd->guild:guild_search(status_get_guild_id(src)); if (!g) break; if (skill_id == GD_ITEMEMERGENCYCALL) switch (skill_lv) { case 1: calls = 7; break; case 2: calls = 12; break; case 3: calls = 20; break; default: calls = 0; break; } clif_skill_nodamage(src,bl,skill_id,skill_lv,1); for (i = 0; i < g->max_member && (!calls || (calls && called < calls)); i++, j++) { if (j > j = 0; if ((dstsd = g->member[i].sd) != NULL && sd != dstsd && !dstsd->state.autotrade && !pc_isdead(dstsd)) { if (map_getmapflag(dstsd->bl.m, MF_NOWARP) && !map_flag_gvg2(dstsd->bl.m)) continue; if (!pc_job_can_entermap((enum e_job)dstsd->status.class_, src->m, pc_get_group_level(dstsd))) continue; if(map_getcell(src->m,src->x+dx[j],src->y+dy[j],CELL_CHKNOREACH)) dx[j] = dy[j] = 0; if (!pc_setpos(dstsd, map_id2index(src->m), src->x+dx[j], src->y+dy[j], CLR_RESPAWN)) called++; } } if (sd) #ifdef RENEWAL skill_blockpc_start(sd, skill_id, skill_get_cooldown(skill_id, skill_lv)); #else guild_block_skill(sd, skill_get_time2(skill_id, skill_lv)); #endif } break; case GD_CHARGESHOUT_FLAG: if (sd && sd->guild && sd->state.gmaster_flag == 1) { mob_data *md = mob_once_spawn_sub(src, src->m, src->x, src->y, sd->guild->name, MOBID_GUILD_SKILL_FLAG, nullptr, SZ_SMALL, AI_GUILD); if (md) { sd->guild->chargeshout_flag_id = md->bl.id; md->master_id = src->id; if (md->deletetimer != INVALID_TIMER) delete_timer(md->deletetimer, mob_timer_delete); md->deletetimer = add_timer(gettick() + skill_get_time(GD_CHARGESHOUT_FLAG, skill_lv), mob_timer_delete, md->bl.id, 0); mob_spawn(md); } } break; case GD_CHARGESHOUT_BEATING: if (sd && sd->guild && map_blid_exists(sd->guild->chargeshout_flag_id)) { block_list *mob_bl = map_id2bl(sd->guild->chargeshout_flag_id); if (pc_setpos(sd, map_id2index(mob_bl->m), mob_bl->x, mob_bl->y, CLR_RESPAWN) != SETPOS_OK) clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); else clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); } else clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); break; case SG_FEEL: //AuronX reported you CAN memorize the same map as all three. [Skotlex] if (sd) { if(!sd->feel_map[skill_lv-1].index) clif_feel_req(sd->fd,sd, skill_lv); else clif_feel_info(sd, skill_lv-1, 1); } break; case SG_HATE: if (sd) { clif_skill_nodamage(src,bl,skill_id,skill_lv,1); if (!pc_set_hate_mob(sd, skill_lv-1, bl)) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); } break; case SJ_DOCUMENT: if (sd) { switch (skill_lv) { case 1: pc_resetfeel(sd); break; case 2: pc_resethate(sd); break; case 3: pc_resetfeel(sd); pc_resethate(sd); break; } } clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); break; case GS_GLITTERING: if(sd) { clif_skill_nodamage(src,bl,skill_id,skill_lv,1); if(rnd()%100 < (20+10*skill_lv)) pc_addspiritball(sd,skill_get_time(skill_id,skill_lv),10); else if(sd->spiritball > 0 && !pc_checkskill(sd,RL_RICHS_COIN)) pc_delspiritball(sd,1,0); } break; case GS_CRACKER: /* per official standards, this skill works on players and mobs. */ if (sd && (dstsd || dstmd)) { i =65 -5*distance_bl(src,bl); //Base rate if (i < 30) i = 30; clif_skill_nodamage(src,bl,skill_id,skill_lv,1); sc_start(src,bl,SC_STUN, i,skill_lv,skill_get_time2(skill_id,skill_lv)); } break; case AM_CALLHOMUN: //[orn] if (sd && !hom_call(sd)) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); #ifdef RENEWAL else if (sd && hom_is_active(sd->hd)) skill_area_temp[0] = 1; // Already passed pre-cast checks #endif break; case AM_REST: if (sd) { if (hom_vaporize(sd,HOM_ST_REST)) clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); else clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); } break; case HAMI_CASTLE: //[orn] if (src != bl && rnd()%100 < 20 * skill_lv) { int x = src->x, y = src->y; if (hd) skill_blockhomun_start(hd,skill_id,skill_get_time2(skill_id,skill_lv)); // Move source if (unit_movepos(src,bl->x,bl->y,0,0)) { clif_skill_nodamage(src,src,skill_id,skill_lv,1); // Homunc clif_blown(src); // Move target if (unit_movepos(bl,x,y,0,0)) { clif_skill_nodamage(bl,bl,skill_id,skill_lv,1); clif_blown(bl); } map_foreachinallrange(unit_changetarget,src,AREA_SIZE,BL_MOB,bl,src); } } else if (hd && hd->master) // Failed clif_skill_fail(hd->master, skill_id, USESKILL_FAIL_LEVEL, 0); else if (sd) clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); break; case HVAN_CHAOTIC: //[orn] { static const int per[5][2]={{20,50},{50,60},{25,75},{60,64},{34,67}}; int r = rnd()%100; i = (skill_lv-1)%5; if(r<per[i][0]) //Self bl = src; else if(r<per[i][1]) //Master bl = battle_get_master(src); else //Enemy bl = map_id2bl(battle_gettarget(src)); if (!bl) bl = src; i = skill_calc_heal(src, bl, skill_id, 1+rnd()%skill_lv, true); //Eh? why double skill packet? clif_skill_nodamage(src,bl,AL_HEAL,i,1); clif_skill_nodamage(src,bl,skill_id,i,1); status_heal(bl, i, 0, 0); } break; //Homun single-target support skills [orn] case HAMI_BLOODLUST: case HFLI_FLEET: case HFLI_SPEED: case HLIF_CHANGE: case MH_ANGRIFFS_MODUS: case MH_GOLDENE_FERSE: clif_skill_nodamage(src,bl,skill_id,skill_lv, sc_start(src,bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); if (hd) skill_blockhomun_start(hd, skill_id, skill_get_time2(skill_id,skill_lv)); break; case NPC_DRAGONFEAR: if (flag&1) { const enum sc_type sc[] = { SC_STUN, SC_SILENCE, SC_CONFUSION, SC_BLEEDING }; int j; j = i = rnd()%ARRAYLENGTH(sc); while ( !sc_start2(src,bl,sc[i],100,skill_lv,src->id,skill_get_time2(skill_id,i+1)) ) { i++; if ( i == ARRAYLENGTH(sc) ) i = 0; if (i == j) break; } break; } case NPC_WIDEBLEEDING: case NPC_WIDEBLEEDING2: case NPC_WIDECONFUSE: case NPC_WIDECONFUSE2: case NPC_WIDECURSE: case NPC_WIDECURSE2: case NPC_WIDEFREEZE: case NPC_WIDEFREEZE2: case NPC_WIDESLEEP: case NPC_WIDESLEEP2: case NPC_WIDESILENCE: case NPC_WIDESILENCE2: case NPC_WIDESTONE: case NPC_WIDESTONE2: case NPC_WIDESTUN: case NPC_WIDESTUN2: case NPC_SLOWCAST: case NPC_WIDEHELLDIGNITY: case NPC_WIDEHEALTHFEAR: case NPC_WIDEBODYBURNNING: case NPC_WIDEFROSTMISTY: case NPC_WIDECOLD: case NPC_WIDE_DEEP_SLEEP: case NPC_WIDESIREN: case NPC_WIDEWEB: if (flag&1){ switch ( type ) { case SC_BURNING: sc_start4(src,bl,type,100,skill_lv,1000,src->id,0,skill_get_time2(skill_id,skill_lv)); break; case SC_STONEWAIT: sc_start2(src,bl,type,100,skill_lv,src->id,skill_get_time2(skill_id,skill_lv),skill_get_time(skill_id, skill_lv)); break; default: sc_start2(src,bl,type,100,skill_lv,src->id,skill_get_time2(skill_id,skill_lv)); } } else { skill_area_temp[2] = 0; //For SD_PREAMBLE clif_skill_nodamage(src,bl,skill_id,skill_lv,1); map_foreachinallrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv),BL_CHAR, src,skill_id,skill_lv,tick, flag|BCT_ENEMY|SD_PREAMBLE|1, skill_castend_nodamage_id); } break; case NPC_WIDESOULDRAIN: if (flag&1) status_percent_damage(src,bl,0,((skill_lv-1)%5+1)*20,false); else { skill_area_temp[2] = 0; //For SD_PREAMBLE clif_skill_nodamage(src,bl,skill_id,skill_lv,1); map_foreachinallrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv),BL_CHAR, src,skill_id,skill_lv,tick, flag|BCT_ENEMY|SD_PREAMBLE|1, skill_castend_nodamage_id); } break; case NPC_FIRESTORM: { int sflag = flag; if( skill_lv > 1 ) sflag |= 4; clif_skill_nodamage(src,bl,skill_id,skill_lv,1); map_foreachinshootrange(skill_area_sub,src,skill_get_splash(skill_id,skill_lv),splash_target(src),src, skill_id,skill_lv,tick,sflag|BCT_ENEMY|SD_ANIMATION|1,skill_castend_damage_id); } break; case ALL_PARTYFLEE: if( sd && !(flag&1) ) { if( !sd->status.party_id ) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); break; } party_foreachsamemap(skill_area_sub, sd, skill_get_splash(skill_id, skill_lv), src, skill_id, skill_lv, tick, flag|BCT_PARTY|1, skill_castend_nodamage_id); } else clif_skill_nodamage(src,bl,skill_id,skill_lv,sc_start(src,bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); break; case NPC_TALK: case ALL_WEWISH: case ALL_CATCRY: case ALL_DREAM_SUMMERNIGHT: clif_skill_nodamage(src,bl,skill_id,skill_lv,1); break; case ALL_BUYING_STORE: if( sd ) {// players only, skill allows 5 buying slots clif_skill_nodamage(src, bl, skill_id, skill_lv, buyingstore_setup(sd, MAX_BUYINGSTORE_SLOTS) ? 0 : 1); } break; case RK_ENCHANTBLADE: clif_skill_nodamage(src,bl,skill_id,skill_lv, sc_start2(src,bl,type,100,skill_lv,((100+20*skill_lv)*status_get_lv(src))/100+sstatus->int_,skill_get_time(skill_id,skill_lv))); break; case RK_DRAGONHOWLING: if( flag&1) sc_start(src,bl,type,50 + 6 * skill_lv,skill_lv,skill_get_time(skill_id,skill_lv)); else { skill_area_temp[2] = 0; clif_skill_nodamage(src,bl,skill_id,skill_lv,1); map_foreachinallrange(skill_area_sub, src, skill_get_splash(skill_id,skill_lv),BL_CHAR, src,skill_id,skill_lv,tick,flag|BCT_ENEMY|SD_PREAMBLE|1, skill_castend_nodamage_id); } break; case LG_EARTHDRIVE: { int dummy = 1; clif_skill_damage(src,bl,tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, DMG_SINGLE); i = skill_get_splash(skill_id,skill_lv); map_foreachinallarea(skill_cell_overlap, src->m, src->x-i, src->y-i, src->x+i, src->y+i, BL_SKILL, LG_EARTHDRIVE, &dummy, src); map_foreachinrange(skill_area_sub, bl,i,BL_CHAR,src,skill_id,skill_lv,tick,flag|BCT_ENEMY|1,skill_castend_damage_id); } break; case RK_LUXANIMA: status_change_clear_buffs(bl, SCCB_LUXANIMA); // For bonus_script case RK_GIANTGROWTH: case RK_STONEHARDSKIN: case RK_VITALITYACTIVATION: case RK_ABUNDANCE: case RK_CRUSHSTRIKE: case RK_REFRESH: case RK_MILLENNIUMSHIELD: if (sd) { uint8 rune_level = 1; // RK_GIANTGROWTH if (skill_id == RK_VITALITYACTIVATION) rune_level = 2; else if (skill_id == RK_STONEHARDSKIN) rune_level = 4; else if (skill_id == RK_ABUNDANCE) rune_level = 6; else if (skill_id == RK_CRUSHSTRIKE) rune_level = 7; else if (skill_id == RK_REFRESH) rune_level = 8; else if (skill_id == RK_MILLENNIUMSHIELD) rune_level = 9; if (pc_checkskill(sd, RK_RUNEMASTERY) >= rune_level) { if (sc_start(src, bl, type, 100, skill_lv, skill_get_time(skill_id, skill_lv))) clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); else if (skill_id == RK_STONEHARDSKIN) clif_skill_fail(sd, skill_id, USESKILL_FAIL_HP_INSUFFICIENT, 0); } else clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); } break; case NPC_MILLENNIUMSHIELD: if (sc_start(src, bl, type, 100, skill_lv, skill_get_time(skill_id, skill_lv))) clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); break; case RK_FIGHTINGSPIRIT: { uint8 runemastery_skill_lv = (sd ? pc_checkskill(sd, RK_RUNEMASTERY) : skill_get_max(RK_RUNEMASTERY)); // val1: ATKBonus: ? // !TODO: Confirm new ATK formula // val2: ASPD boost: [RK_RUNEMASTERYlevel * 4 / 10] * 10 ==> RK_RUNEMASTERYlevel * 4 sc_start2(src,bl,type,100,70 + 7 * runemastery_skill_lv,4 * runemastery_skill_lv,skill_get_time(skill_id,skill_lv)); clif_skill_nodamage(src,bl,skill_id,skill_lv,1); break; } case GC_ROLLINGCUTTER: { short count = 1; skill_area_temp[2] = 0; map_foreachinrange(skill_area_sub,src,skill_get_splash(skill_id,skill_lv),BL_CHAR,src,skill_id,skill_lv,tick,flag|BCT_ENEMY|SD_PREAMBLE|SD_SPLASH|1,skill_castend_damage_id); if( tsc && tsc->getSCE(SC_ROLLINGCUTTER) ) { // Every time the skill is casted the status change is reseted adding a counter. count += (short)tsc->getSCE(SC_ROLLINGCUTTER)->val1; if( count > 10 ) count = 10; // Max coounter status_change_end(bl, SC_ROLLINGCUTTER); } sc_start(src,bl,SC_ROLLINGCUTTER,100,count,skill_get_time(skill_id,skill_lv)); clif_skill_nodamage(src,src,skill_id,skill_lv,1); } break; case GC_WEAPONBLOCKING: if( tsc && tsc->getSCE(SC_WEAPONBLOCKING) ) status_change_end(bl, SC_WEAPONBLOCKING); else sc_start(src,bl,SC_WEAPONBLOCKING,100,skill_lv,skill_get_time(skill_id,skill_lv)); clif_skill_nodamage(src,bl,skill_id,skill_lv,1); break; case GC_CREATENEWPOISON: if( sd ) { clif_skill_produce_mix_list(sd,skill_id,25); clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); } break; case GC_POISONINGWEAPON: if( sd ) { clif_poison_list(sd,skill_lv); clif_skill_nodamage(src,bl,skill_id,skill_lv,1); } break; case GC_ANTIDOTE: clif_skill_nodamage(src,bl,skill_id,skill_lv,1); if( tsc ) { status_change_end(bl, SC_PARALYSE); status_change_end(bl, SC_PYREXIA); status_change_end(bl, SC_DEATHHURT); status_change_end(bl, SC_LEECHESEND); status_change_end(bl, SC_VENOMBLEED); status_change_end(bl, SC_MAGICMUSHROOM); status_change_end(bl, SC_TOXIN); status_change_end(bl, SC_OBLIVIONCURSE); } break; case GC_PHANTOMMENACE: clif_skill_damage(src,bl,tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, DMG_SINGLE); clif_skill_nodamage(src,bl,skill_id,skill_lv,1); map_foreachinrange(skill_area_sub,src,skill_get_splash(skill_id,skill_lv),BL_CHAR, src,skill_id,skill_lv,tick,flag|BCT_ENEMY|1,skill_castend_damage_id); break; case GC_HALLUCINATIONWALK: { int heal = status_get_max_hp(bl) / 10; if( status_get_hp(bl) < heal ) { // if you haven't enough HP skill fails. if( sd ) clif_skill_fail(sd,skill_id,USESKILL_FAIL_HP_INSUFFICIENT,0); break; } if( !status_charge(bl,heal,0) ) { if( sd ) clif_skill_fail(sd,skill_id,USESKILL_FAIL_HP_INSUFFICIENT,0); break; } clif_skill_nodamage(src,bl,skill_id,skill_lv,sc_start(src,bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); } break; case AB_ANCILLA: if( sd ) { clif_skill_nodamage(src,bl,skill_id,skill_lv,1); skill_produce_mix(sd, skill_id, ITEMID_ANCILLA, 0, 0, 0, 1, -1); } break; case AB_CLEMENTIA: case AB_CANTO: { int bless_lv = ((sd) ? pc_checkskill(sd,AL_BLESSING) : skill_get_max(AL_BLESSING)) + (((sd) ? sd->status.job_level : 50) / 10); int agi_lv = ((sd) ? pc_checkskill(sd,AL_INCAGI) : skill_get_max(AL_INCAGI)) + (((sd) ? sd->status.job_level : 50) / 10); if( sd == NULL || sd->status.party_id == 0 || flag&1 ) clif_skill_nodamage(bl, bl, skill_id, skill_lv, sc_start(src,bl,type,100, (skill_id == AB_CLEMENTIA)? bless_lv : (skill_id == AB_CANTO)? agi_lv : skill_lv, skill_get_time(skill_id,skill_lv))); else if( sd ) party_foreachsamemap(skill_area_sub, sd, skill_get_splash(skill_id, skill_lv), src, skill_id, skill_lv, tick, flag|BCT_PARTY|1, skill_castend_nodamage_id); } break; case AB_PRAEFATIO: case AB_RENOVATIO: if( !sd || sd->status.party_id == 0 || flag&1 ) { if (skill_id == AB_PRAEFATIO) clif_skill_nodamage(bl, bl, skill_id, skill_lv, sc_start4(src, bl, type, 100, skill_lv, 0, 0, (sd && sd->status.party_id ? party_foreachsamemap(party_sub_count, sd, 0) : 1 ), skill_get_time(skill_id, skill_lv))); else clif_skill_nodamage(bl, bl, skill_id, skill_lv, sc_start(src, bl, type, 100, skill_lv, skill_get_time(skill_id, skill_lv))); } else if( sd ) party_foreachsamemap(skill_area_sub, sd, skill_get_splash(skill_id, skill_lv), src, skill_id, skill_lv, tick, flag|BCT_PARTY|1, skill_castend_nodamage_id); break; case AB_CHEAL: if( !sd || sd->status.party_id == 0 || flag&1 ) { if( sd && tstatus && !battle_check_undead(tstatus->race, tstatus->def_ele) && !tsc->getSCE(SC_BERSERK) ) { int partycount = (sd->status.party_id ? party_foreachsamemap(party_sub_count, sd, 0) : 0); i = skill_calc_heal(src, bl, AL_HEAL, pc_checkskill(sd, AL_HEAL), true); if( partycount > 1 ) i += (i / 100) * (partycount * 10) / 4; if( (dstsd && pc_ismadogear(dstsd)) || status_isimmune(bl)) i = 0; // Should heal by 0 or won't do anything?? in iRO it breaks the healing to members.. [malufett] clif_skill_nodamage(src, bl, skill_id, i, 1); if( tsc && tsc->getSCE(SC_AKAITSUKI) && i ) i = ~i + 1; status_heal(bl, i, 0, 0); } } else if( sd ) party_foreachsamemap(skill_area_sub, sd, skill_get_splash(skill_id, skill_lv), src, skill_id, skill_lv, tick, flag|BCT_PARTY|1, skill_castend_nodamage_id); break; case NPC_CHEAL: if( flag&1 ) { if( tstatus && !battle_check_undead(tstatus->race, tstatus->def_ele) && !tsc->getSCE(SC_BERSERK) ) { i = skill_calc_heal(src, bl, AL_HEAL, 10, true); if (status_isimmune(bl)) i = 0; clif_skill_nodamage(src, bl, skill_id, i, 1); if( tsc && tsc->getSCE(SC_AKAITSUKI) && i ) i = ~i + 1; status_heal(bl, i, 0, 0); } } else { map_foreachinallrange(skill_area_sub, src, skill_get_splash(skill_id, skill_lv), BL_MOB, src, skill_id, skill_lv, tick, flag|BCT_PARTY|1, skill_castend_nodamage_id); clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); } break; case AB_ORATIO: if( flag&1 ) sc_start(src,bl, type, 40 + 5 * skill_lv, skill_lv, skill_get_time(skill_id, skill_lv)); else { map_foreachinallrange(skill_area_sub, src, skill_get_splash(skill_id, skill_lv), BL_CHAR, src, skill_id, skill_lv, tick, flag|BCT_ENEMY|1, skill_castend_nodamage_id); clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); } break; case AB_LAUDAAGNUS: if( flag&1 || !sd || !sd->status.party_id ) { if( tsc && (tsc->getSCE(SC_FREEZE) || tsc->getSCE(SC_STONE) || tsc->getSCE(SC_BLIND) || tsc->getSCE(SC_BURNING) || tsc->getSCE(SC_FREEZING) || tsc->getSCE(SC_CRYSTALIZE))) { // Success Chance: (60 + 10 * Skill Level) % if( rnd()%100 > 60+10*skill_lv ) break; status_change_end(bl, SC_FREEZE); status_change_end(bl, SC_STONE); status_change_end(bl, SC_BLIND); status_change_end(bl, SC_BURNING); status_change_end(bl, SC_FREEZING); status_change_end(bl, SC_CRYSTALIZE); } else //Success rate only applies to the curing effect and not stat bonus. Bonus status only applies to non infected targets clif_skill_nodamage(bl, bl, skill_id, skill_lv, sc_start(src,bl, type, 100, skill_lv, skill_get_time(skill_id, skill_lv))); } else if( sd ) party_foreachsamemap(skill_area_sub, sd, skill_get_splash(skill_id, skill_lv), src, skill_id, skill_lv, tick, flag|BCT_PARTY|1, skill_castend_nodamage_id); break; case AB_LAUDARAMUS: if( flag&1 || !sd || !sd->status.party_id ) { if( tsc && (tsc->getSCE(SC_SLEEP) || tsc->getSCE(SC_STUN) || tsc->getSCE(SC_MANDRAGORA) || tsc->getSCE(SC_SILENCE) || tsc->getSCE(SC_DEEPSLEEP)) ){ // Success Chance: (60 + 10 * Skill Level) % if( rnd()%100 > 60+10*skill_lv ) break; status_change_end(bl, SC_SLEEP); status_change_end(bl, SC_STUN); status_change_end(bl, SC_MANDRAGORA); status_change_end(bl, SC_SILENCE); status_change_end(bl, SC_DEEPSLEEP); } else // Success rate only applies to the curing effect and not stat bonus. Bonus status only applies to non infected targets clif_skill_nodamage(bl, bl, skill_id, skill_lv, sc_start(src,bl, type, 100, skill_lv, skill_get_time(skill_id, skill_lv))); } else if( sd ) party_foreachsamemap(skill_area_sub, sd, skill_get_splash(skill_id, skill_lv), src, skill_id, skill_lv, tick, flag|BCT_PARTY|1, skill_castend_nodamage_id); break; case AB_CLEARANCE: if( flag&1 || (i = skill_get_splash(skill_id, skill_lv)) < 1 ) { // As of the behavior in official server Clearance is just a super version of Dispell skill. [Jobbie] if( bl->type != BL_MOB && battle_check_target(src,bl,BCT_PARTY) <= 0 ) // Only affect mob or party. break; clif_skill_nodamage(src,bl,skill_id,skill_lv,1); if(rnd()%100 >= 60 + 8 * skill_lv) { if (sd) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); break; } if(status_isimmune(bl)) break; //Remove bonus_script by Clearance if (dstsd) pc_bonus_script_clear(dstsd,BSF_REM_ON_CLEARANCE); if(!tsc || !tsc->count) break; //Statuses change that can't be removed by Cleareance for (const auto &it : status_db) { sc_type status = static_cast<sc_type>(it.first); if (!tsc->getSCE(status)) continue; if (it.second->flag[SCF_NOCLEARANCE]) continue; switch (status) { case SC_WHISTLE: case SC_ASSNCROS: case SC_POEMBRAGI: case SC_APPLEIDUN: case SC_HUMMING: case SC_DONTFORGETME: case SC_FORTUNE: case SC_SERVICE4U: if (!battle_config.dispel_song || tsc->getSCE(status)->val4 == 0) continue; //If in song area don't end it, even if config enabled break; case SC_ASSUMPTIO: if (bl->type == BL_MOB) continue; break; } if (i == SC_BERSERK || i == SC_SATURDAYNIGHTFEVER) tsc->getSCE(status)->val2 = 0; //Mark a dispelled berserk to avoid setting hp to 100 by setting hp penalty to 0. status_change_end(bl,status); } break; } map_foreachinallrange(skill_area_sub, bl, i, BL_CHAR, src, skill_id, skill_lv, tick, flag|1, skill_castend_damage_id); break; case AB_SILENTIUM: // Should the level of Lex Divina be equivalent to the level of Silentium or should the highest level learned be used? [LimitLine] map_foreachinallrange(skill_area_sub, src, skill_get_splash(skill_id, skill_lv), BL_CHAR, src, PR_LEXDIVINA, skill_lv, tick, flag|BCT_ENEMY|1, skill_castend_nodamage_id); clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); break; case WL_STASIS: if (flag&1) sc_start(src,bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv)); else { struct map_data *mapdata = map_getmapdata(src->m); map_foreachinallrange(skill_area_sub,src,skill_get_splash(skill_id, skill_lv),BL_CHAR,src,skill_id,skill_lv,tick,(mapdata_flag_vs(mapdata)?BCT_ALL:BCT_ENEMY|BCT_SELF)|flag|1,skill_castend_nodamage_id); clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); } break; case NPC_DANCINGBLADE: skill_addtimerskill(src, tick + status_get_amotion(src), bl->id, 0, 0, NPC_DANCINGBLADE_ATK, skill_lv, 0, 0); break; case WL_CHAINLIGHTNING: skill_addtimerskill(src, tick + status_get_amotion(src), bl->id, 0, 0, WL_CHAINLIGHTNING_ATK, skill_lv, 0, 0); break; case WL_WHITEIMPRISON: if( (src == bl || battle_check_target(src, bl, BCT_ENEMY)>0) && status_get_class_(bl) != CLASS_BOSS && !status_isimmune(bl) ) // Should not work with Bosses. { int rate = ( sd? sd->status.job_level : 50 ) / 4; if( src == bl ) rate = 100; // Success Chance: On self, 100% else if(bl->type == BL_PC) rate += 20 + 10 * skill_lv; // On Players, (20 + 10 * Skill Level) % else rate += 40 + 10 * skill_lv; // On Monsters, (40 + 10 * Skill Level) % if( sd ) skill_blockpc_start(sd,skill_id,4000); if( !(tsc && tsc->getSCE(type)) ){ i = sc_start2(src,bl,type,rate,skill_lv,src->id,(src == bl)?5000:(bl->type == BL_PC)?skill_get_time(skill_id,skill_lv):skill_get_time2(skill_id, skill_lv)); clif_skill_nodamage(src,bl,skill_id,skill_lv,i); if( !i ) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); } }else if( sd ) clif_skill_fail(sd,skill_id,USESKILL_FAIL_TOTARGET,0); break; case NPC_JACKFROST: clif_skill_nodamage(src,bl,skill_id,skill_lv,1); map_foreachinrange(skill_area_sub,bl,skill_get_splash(skill_id,skill_lv),BL_CHAR|BL_SKILL,src,skill_id,skill_lv,tick,flag|BCT_ENEMY|1,skill_castend_damage_id); break; case WL_SIENNAEXECRATE: if( status_isimmune(bl) || !tsc ) break; if( flag&1 ) { if( bl->id == skill_area_temp[1] ) break; // Already work on this target status_change_start(src,bl,type,10000,skill_lv,src->id,0,0,skill_get_time2(skill_id,skill_lv), SCSTART_NOTICKDEF, skill_get_time(skill_id, skill_lv)); } else { int rate = 45 + 5 * skill_lv + ( sd? sd->status.job_level : 50 ) / 4; // IroWiki says Rate should be reduced by target stats, but currently unknown if( rnd()%100 < rate ) { // Success on First Target if( status_change_start(src,bl,type,10000,skill_lv,src->id,0,0,skill_get_time2(skill_id,skill_lv), SCSTART_NOTICKDEF, skill_get_time(skill_id, skill_lv)) ) { clif_skill_nodamage(src,bl,skill_id,skill_lv,1); skill_area_temp[1] = bl->id; map_foreachinallrange(skill_area_sub,bl,skill_get_splash(skill_id,skill_lv),BL_CHAR,src,skill_id,skill_lv,tick,flag|BCT_ENEMY|1,skill_castend_nodamage_id); } // Doesn't send failure packet if it fails on defense. } else if( sd ) // Failure on Rate clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); } break; case WL_SUMMONFB: case WL_SUMMONBL: case WL_SUMMONWB: case WL_SUMMONSTONE: { status_change *sc = status_get_sc(src); if (sc == nullptr) break; e_wl_spheres element; switch (skill_id) { // Set val2. The SC element for this ball case WL_SUMMONFB: element = WLS_FIRE; break; case WL_SUMMONBL: element = WLS_WIND; break; case WL_SUMMONWB: element = WLS_WATER; break; case WL_SUMMONSTONE: element = WLS_STONE; break; } if (skill_lv == 1) { sc_type sphere = SC_NONE; for (i = SC_SPHERE_1; i <= SC_SPHERE_5; i++) { if (sc->getSCE(i) == nullptr) { sphere = static_cast<sc_type>(i); // Take the free SC break; } } if (sphere == SC_NONE) { if (sd) // No free slots to put SC clif_skill_fail(sd, skill_id, USESKILL_FAIL_SUMMON, 0); break; } sc_start2(src, src, sphere, 100, element, skill_lv, skill_get_time(skill_id, skill_lv)); } else { for (i = SC_SPHERE_1; i <= SC_SPHERE_5; i++) { status_change_end(src, static_cast<sc_type>(i)); // Removes previous type sc_start2(src, src, static_cast<sc_type>(i), 100, element, skill_lv, skill_get_time(skill_id, skill_lv)); } } clif_skill_nodamage(src, bl, skill_id, 0, 0); } break; case WL_READING_SB_READING: if (sd) { if (pc_checkskill(sd, WL_READING_SB) == 0 || skill_lv < 1 || skill_lv > 10) { clif_skill_fail(sd, skill_id, USESKILL_FAIL_SPELLBOOK_READING, 0); break; } clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); skill_spellbook(*sd, ITEMID_WL_MB_SG + skill_lv - 1); } break; case RA_FEARBREEZE: clif_skill_damage(src, src, tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, DMG_SINGLE); clif_skill_nodamage(src, bl, skill_id, skill_lv, sc_start(src,bl, type, 100, skill_lv, skill_get_time(skill_id, skill_lv))); break; case RA_WUGMASTERY: if( sd ) { if( !pc_iswug(sd) ) pc_setoption(sd,sd->sc.option|OPTION_WUG); else pc_setoption(sd,sd->sc.option&~OPTION_WUG); clif_skill_nodamage(src,bl,skill_id,skill_lv,1); } break; case RA_WUGRIDER: if( sd ) { if( !pc_isridingwug(sd) && pc_iswug(sd) ) { pc_setoption(sd,sd->sc.option&~OPTION_WUG); pc_setoption(sd,sd->sc.option|OPTION_WUGRIDER); } else if( pc_isridingwug(sd) ) { pc_setoption(sd,sd->sc.option&~OPTION_WUGRIDER); pc_setoption(sd,sd->sc.option|OPTION_WUG); } clif_skill_nodamage(src,bl,skill_id,skill_lv,1); } break; case RA_WUGDASH: if( tsce ) { clif_skill_nodamage(src,bl,skill_id,skill_lv,status_change_end(bl, type)); map_freeblock_unlock(); return 0; } if( sd && pc_isridingwug(sd) ) { clif_skill_nodamage(src,bl,skill_id,skill_lv,sc_start4(src,bl,type,100,skill_lv,unit_getdir(bl),0,0,0)); clif_walkok(sd); } break; case RA_SENSITIVEKEEN: clif_skill_nodamage(src,bl,skill_id,skill_lv,1); clif_skill_damage(src,src,tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, DMG_SINGLE); map_foreachinrange(skill_area_sub,src,skill_get_splash(skill_id,skill_lv),BL_CHAR|BL_SKILL,src,skill_id,skill_lv,tick,flag|BCT_ENEMY,skill_castend_damage_id); break; case NC_F_SIDESLIDE: case NC_B_SIDESLIDE: { uint8 dir = (skill_id == NC_F_SIDESLIDE) ? (unit_getdir(src)+4)%8 : unit_getdir(src); skill_blown(src,bl,skill_get_blewcount(skill_id,skill_lv),dir,BLOWN_IGNORE_NO_KNOCKBACK); clif_skill_nodamage(src,bl,skill_id,skill_lv,1); } break; case NC_SELFDESTRUCTION: if( sd ) { if( pc_ismadogear(sd) ) pc_setmadogear(sd, false); skill_area_temp[1] = 0; clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); map_foreachinrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), BL_CHAR|BL_SKILL, src, skill_id, skill_lv, tick, flag|BCT_ENEMY|SD_SPLASH|1, skill_castend_damage_id); status_set_sp(src, 0, 0); skill_clear_unitgroup(src); } break; case NC_EMERGENCYCOOL: clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); if (sd) { struct s_skill_condition req = skill_get_requirement(sd, skill_id, skill_lv); int16 limit[] = { -45, -75, -105 }; i = 0; for (const auto &reqItem : req.eqItem) { if (pc_search_inventory(sd, reqItem) != -1) break; i++; } pc_overheat(*sd, limit[min(i, 2)]); } break; case NC_ANALYZE: clif_skill_damage(src, bl, tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, DMG_SINGLE); sc_start(src,bl,type, 30 + 12 * skill_lv,skill_lv,skill_get_time(skill_id,skill_lv)); break; case NC_MAGNETICFIELD: if (flag & 1) { sc_start2(src, bl, SC_MAGNETICFIELD, 100, skill_lv, src->id, skill_get_time(skill_id, skill_lv)); } else { if (map_flag_vs(src->m)) // Doesn't affect the caster in non-PVP maps [exneval] sc_start2(src, bl, type, 100, skill_lv, src->id, skill_get_time(skill_id, skill_lv)); map_foreachinallrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), splash_target(src), src, skill_id, skill_lv, tick, flag | BCT_ENEMY | SD_SPLASH | 1, skill_castend_nodamage_id); clif_skill_damage(src, bl, tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, DMG_SINGLE); } break; case NC_REPAIR: if( sd ) { int heal, hp = 0; if( !dstsd || !pc_ismadogear(dstsd) ) { clif_skill_fail(sd, skill_id, USESKILL_FAIL_TOTARGET, 0); break; } switch(skill_lv) { case 1: hp = 4; break; case 2: hp = 7; break; case 3: hp = 13; break; case 4: hp = 17; break; case 5: default: hp = 23; break; } heal = dstsd->status.max_hp * hp / 100; status_heal(bl,heal,0,2); clif_skill_nodamage(src, bl, skill_id, skill_lv, heal); } break; case NC_DISJOINT: { if( bl->type != BL_MOB ) break; md = map_id2md(bl->id); if( md && md->mob_id >= MOBID_SILVERSNIPER && md->mob_id <= MOBID_MAGICDECOY_WIND ) status_kill(bl); clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); } break; case SC_AUTOSHADOWSPELL: if( sd ) { if( (sd->reproduceskill_idx > 0 && sd->status.skill[sd->reproduceskill_idx].id) || (sd->cloneskill_idx > 0 && sd->status.skill[sd->cloneskill_idx].id) ) { sc_start(src,src,SC_STOP,100,skill_lv,INFINITE_TICK);// The skill_lv is stored in val1 used in skill_select_menu to determine the used skill lvl [Xazax] clif_autoshadowspell_list(sd); clif_skill_nodamage(src,bl,skill_id,1,1); } else clif_skill_fail(sd,skill_id,USESKILL_FAIL_IMITATION_SKILL_NONE,0); } break; case SC_SHADOWFORM: if( sd && dstsd && src != bl && !dstsd->shadowform_id ) { if( clif_skill_nodamage(src,bl,skill_id,skill_lv,sc_start4(src,src,type,100,skill_lv,bl->id,4+skill_lv,0,skill_get_time(skill_id, skill_lv))) ) dstsd->shadowform_id = src->id; } else if( sd ) clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); break; case SC_BODYPAINT: if( flag&1 ) { if (tsc && ((tsc->option&(OPTION_HIDE|OPTION_CLOAK)) || tsc->getSCE(SC_CAMOUFLAGE) || tsc->getSCE(SC_STEALTHFIELD))) { status_change_end(bl,SC_HIDING); status_change_end(bl,SC_CLOAKING); status_change_end(bl,SC_CLOAKINGEXCEED); status_change_end(bl,SC_CAMOUFLAGE); status_change_end(bl,SC_NEWMOON); if (tsc && tsc->getSCE(SC__SHADOWFORM) && rnd() % 100 < 100 - tsc->getSCE(SC__SHADOWFORM)->val1 * 10) // [100 - (Skill Level x 10)] % status_change_end(bl, SC__SHADOWFORM); } // Attack Speed decrease and Blind happen to everyone around caster, not just hidden targets. sc_start(src, bl, type, 100, skill_lv, skill_get_time(skill_id, skill_lv)); sc_start(src, bl, SC_BLIND, 53 + 2 * skill_lv, skill_lv, skill_get_time2(skill_id, skill_lv)); } else { clif_skill_nodamage(src, bl, skill_id, 0, 1); map_foreachinallrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), BL_CHAR, src, skill_id, skill_lv, tick, flag|BCT_ENEMY|1, skill_castend_nodamage_id); } break; case SC_ENERVATION: case SC_GROOMY: case SC_LAZINESS: case SC_UNLUCKY: case SC_WEAKNESS: if( !(tsc && tsc->getSCE(type)) ) { int rate; if (status_get_class_(bl) == CLASS_BOSS) break; rate = status_get_lv(src) / 10 + rnd_value(sstatus->dex / 12, sstatus->dex / 4) + ( sd ? sd->status.job_level : 50 ) + 10 * skill_lv - (status_get_lv(bl) / 10 + rnd_value(tstatus->agi / 6, tstatus->agi / 3) + tstatus->luk / 10 + ( dstsd ? (dstsd->max_weight / 10 - dstsd->weight / 10 ) / 100 : 0)); rate = cap_value(rate, skill_lv + sstatus->dex / 20, 100); clif_skill_nodamage(src,bl,skill_id,0,sc_start(src,bl,type,rate,skill_lv,skill_get_time(skill_id,skill_lv))); } else if( sd ) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); break; case SC_IGNORANCE: if( !(tsc && tsc->getSCE(type)) ) { int rate; if (status_get_class_(bl) == CLASS_BOSS) break; rate = status_get_lv(src) / 10 + rnd_value(sstatus->dex / 12, sstatus->dex / 4) + ( sd ? sd->status.job_level : 50 ) + 10 * skill_lv - (status_get_lv(bl) / 10 + rnd_value(tstatus->agi / 6, tstatus->agi / 3) + tstatus->luk / 10 + ( dstsd ? (dstsd->max_weight / 10 - dstsd->weight / 10 ) / 100 : 0)); rate = cap_value(rate, skill_lv + sstatus->dex / 20, 100); if (clif_skill_nodamage(src,bl,skill_id,0,sc_start(src,bl,type,rate,skill_lv,skill_get_time(skill_id,skill_lv)))) { int sp = 100 * skill_lv; if( dstmd ) sp = dstmd->level; if( !dstmd ) status_zap(bl, 0, sp); status_heal(src, 0, sp / 2, 3); } else if( sd ) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); } else if( sd ) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); break; case LG_TRAMPLE: clif_skill_damage(src,bl,tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, DMG_SINGLE); if (rnd()%100 < (25 + 25 * skill_lv)) map_foreachinallrange(skill_destroy_trap,bl,skill_get_splash(skill_id,skill_lv),BL_SKILL,tick); status_change_end(bl, SC_SV_ROOTTWIST); break; case LG_REFLECTDAMAGE: if( tsc && tsc->getSCE(type) ) status_change_end(bl,type); else sc_start(src,bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv)); clif_skill_nodamage(src,bl,skill_id,skill_lv,1); break; case LG_PIETY: if( flag&1 ) sc_start(src,bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv)); else { skill_area_temp[2] = 0; map_foreachinallrange(skill_area_sub,bl,skill_get_splash(skill_id,skill_lv),BL_PC,src,skill_id,skill_lv,tick,flag|SD_PREAMBLE|BCT_PARTY|BCT_SELF|1,skill_castend_nodamage_id); clif_skill_nodamage(src,bl,skill_id,skill_lv,1); } break; case SR_CURSEDCIRCLE: if( flag&1 ) { if( status_get_class_(bl) == CLASS_BOSS ) break; if( sc_start2(src,bl, type, 100, skill_lv, src->id, skill_get_time(skill_id, skill_lv))) { if( bl->type == BL_MOB ) mob_unlocktarget((TBL_MOB*)bl,gettick()); clif_bladestop(src, bl->id, 1); map_freeblock_unlock(); return 1; } } else { int count = 0; clif_skill_damage(src, bl, tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, DMG_SINGLE); count = map_forcountinrange(skill_area_sub, src, skill_get_splash(skill_id,skill_lv), (sd)?sd->spiritball_old:15, // Assume 15 spiritballs in non-charactors BL_CHAR, src, skill_id, skill_lv, tick, flag|BCT_ENEMY|1, skill_castend_nodamage_id); if( sd ) pc_delspiritball(sd, count, 0); clif_skill_nodamage(src, src, skill_id, skill_lv, sc_start2(src,src, SC_CURSEDCIRCLE_ATKER, 100, skill_lv, count, skill_get_time(skill_id,skill_lv))); } break; case NPC_SR_CURSEDCIRCLE: if( flag&1 ) { if( status_get_class_(bl) == CLASS_BOSS ) break; if( sc_start2(src,bl, type, 50, skill_lv, src->id, skill_get_time(skill_id, skill_lv))) { if( bl->type == BL_MOB ) mob_unlocktarget((TBL_MOB*)bl,gettick()); clif_bladestop(src, bl->id, 1); map_freeblock_unlock(); return 1; } } else { int count = 0; clif_skill_damage(src, bl, tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, DMG_SINGLE); count = map_forcountinrange(skill_area_sub, src, skill_get_splash(skill_id,skill_lv), (sd)?sd->spiritball_old:15, // Assume 15 spiritballs in non-charactors BL_CHAR, src, skill_id, skill_lv, tick, flag|BCT_ENEMY|1, skill_castend_nodamage_id); if( sd ) pc_delspiritball(sd, count, 0); clif_skill_nodamage(src, src, skill_id, skill_lv, sc_start2(src,src, SC_CURSEDCIRCLE_ATKER, 50, skill_lv, count, skill_get_time(skill_id,skill_lv))); } break; case SR_RAISINGDRAGON: if( sd ) { short max = 5 + skill_lv; sc_start(src,bl, SC_EXPLOSIONSPIRITS, 100, skill_lv, skill_get_time(skill_id, skill_lv)); for( i = 0; i < max; i++ ) // Don't call more than max available spheres. pc_addspiritball(sd, skill_get_time(skill_id, skill_lv), max); clif_skill_nodamage(src, bl, skill_id, skill_lv, sc_start(src,bl, type, 100, skill_lv,skill_get_time(skill_id, skill_lv))); } break; case SR_ASSIMILATEPOWER: if (flag&1) { i = 0; if (dstsd && (sd == dstsd || map_flag_vs(src->m)) && (dstsd->class_&MAPID_BASEMASK)!=MAPID_GUNSLINGER) { if (dstsd->spiritball > 0) { i = dstsd->spiritball; pc_delspiritball(dstsd,dstsd->spiritball,0); } if (dstsd->spiritcharm_type != CHARM_TYPE_NONE && dstsd->spiritcharm > 0) { i += dstsd->spiritcharm; pc_delspiritcharm(dstsd,dstsd->spiritcharm,dstsd->spiritcharm_type); } } if (i) status_percent_heal(src, 0, i); clif_skill_nodamage(src, bl, skill_id, skill_lv, i ? 1:0); } else { clif_skill_damage(src,bl,tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, DMG_SINGLE); map_foreachinallrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), splash_target(src), src, skill_id, skill_lv, tick, flag|BCT_ENEMY|BCT_SELF|SD_SPLASH|1, skill_castend_nodamage_id); } break; case SR_POWERVELOCITY: if( !dstsd ) break; if( sd && dstsd->spiritball <= 5 ) { for(i = 0; i <= 5; i++) { pc_addspiritball(dstsd, skill_get_time(MO_CALLSPIRITS, pc_checkskill(sd,MO_CALLSPIRITS)), i); pc_delspiritball(sd, sd->spiritball, 0); } } clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); break; case SR_GENTLETOUCH_CURE: { unsigned int heal; if (dstmd && (dstmd->mob_id == MOBID_EMPERIUM || status_get_class_(bl) == CLASS_BATTLEFIELD)) heal = 0; else { heal = (120 * skill_lv) + (status_get_max_hp(bl) * skill_lv / 100); status_heal(bl, heal, 0, 0); } if( tsc && tsc->count && rnd_chance( ( skill_lv * 5 + ( status_get_dex( src ) + status_get_lv( src ) ) / 4 ) - rnd_value( 1, 10 ), 100 ) ){ status_change_end(bl, SC_STONE); status_change_end(bl, SC_FREEZE); status_change_end(bl, SC_STUN); status_change_end(bl, SC_POISON); status_change_end(bl, SC_SILENCE); status_change_end(bl, SC_BLIND); status_change_end(bl, SC_HALLUCINATION); } clif_skill_nodamage(src,bl,skill_id,skill_lv,1); } break; case SR_GENTLETOUCH_ENERGYGAIN: case SR_GENTLETOUCH_CHANGE: case SR_GENTLETOUCH_REVITALIZE: clif_skill_nodamage(src,bl,skill_id,skill_lv, sc_start(src,bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); break; case SR_FLASHCOMBO: { const int combo[] = { SR_DRAGONCOMBO, SR_FALLENEMPIRE, SR_TIGERCANNON }; const int delay[] = { 0, 750, 1250 }; if (sd) // Disable attacking/acting/moving for skill's duration. sd->ud.attackabletime = sd->canuseitem_tick = sd->ud.canact_tick = tick + delay[2]; clif_skill_nodamage(src,bl,skill_id,skill_lv, sc_start(src,src,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); for (i = 0; i < ARRAYLENGTH(combo); i++) skill_addtimerskill(src,tick + delay[i],bl->id,0,0,combo[i],skill_lv,BF_WEAPON,flag|SD_LEVEL); } break; case WA_SWING_DANCE: case WA_MOONLIT_SERENADE: case WA_SYMPHONY_OF_LOVER: case MI_RUSH_WINDMILL: case MI_ECHOSONG: if( !sd || !sd->status.party_id || (flag & 1) ) { clif_skill_nodamage(src,bl,skill_id,skill_lv,1); sc_start2(src,bl,type,100,skill_lv,((sd) ? pc_checkskill(sd,WM_LESSON) : skill_get_max(WM_LESSON)),skill_get_time(skill_id,skill_lv)); } else if( sd ) { party_foreachsamemap(skill_area_sub, sd, skill_get_splash(skill_id, skill_lv), src, skill_id, skill_lv, tick, flag|BCT_PARTY|1, skill_castend_nodamage_id); sc_start2(src,bl,type,100,skill_lv,((sd) ? pc_checkskill(sd,WM_LESSON) : skill_get_max(WM_LESSON)),skill_get_time(skill_id,skill_lv)); clif_skill_nodamage(src,bl,skill_id,skill_lv,1); } break; case MI_HARMONIZE: if( src != bl ) clif_skill_nodamage(src, src, skill_id, skill_lv, sc_start(src,src, type, 100, skill_lv, skill_get_time(skill_id,skill_lv))); clif_skill_nodamage(src, bl, skill_id, skill_lv, sc_start(src,bl, type, 100, skill_lv, skill_get_time(skill_id,skill_lv))); break; case WM_DEADHILLHERE: if( bl->type == BL_PC ) { if( !status_isdead(bl) ) break; int heal = tstatus->sp; if( heal <= 0 ) heal = 1; tstatus->hp = heal; tstatus->sp -= tstatus->sp * ( 60 - 10 * skill_lv ) / 100; clif_skill_nodamage(src,bl,skill_id,skill_lv,1); pc_revive((TBL_PC*)bl,heal,0); clif_resurrection(bl,1); } break; case WM_VOICEOFSIREN: if (flag&1) sc_start2(src,bl,type,skill_area_temp[5],skill_lv,src->id,skill_area_temp[6]); else { // Success chance: (Skill Level x 6) + (Voice Lesson Skill Level x 2) + (Caster's Job Level / 2) % skill_area_temp[5] = skill_lv * 6 + ((sd) ? pc_checkskill(sd, WM_LESSON) : 1) * 2 + (sd ? sd->status.job_level : 50) / 2; skill_area_temp[6] = skill_get_time(skill_id,skill_lv); map_foreachinallrange(skill_area_sub, src, skill_get_splash(skill_id,skill_lv), BL_CHAR|BL_SKILL, src, skill_id, skill_lv, tick, flag|BCT_ALL|BCT_WOS|1, skill_castend_nodamage_id); clif_skill_nodamage(src,bl,skill_id,skill_lv,1); } break; case WM_GLOOMYDAY: clif_skill_nodamage(src,bl,skill_id,skill_lv,1); if( dstsd && ( pc_checkskill(dstsd,KN_BRANDISHSPEAR) || pc_checkskill(dstsd,LK_SPIRALPIERCE) || pc_checkskill(dstsd,CR_SHIELDCHARGE) || pc_checkskill(dstsd,CR_SHIELDBOOMERANG) || pc_checkskill(dstsd,PA_SHIELDCHAIN) || pc_checkskill(dstsd,LG_SHIELDPRESS) ) ) { // !TODO: Which skills aren't boosted anymore? sc_start(src,bl,SC_GLOOMYDAY_SK,100,skill_lv,skill_get_time(skill_id,skill_lv)); break; } sc_start(src,bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv)); break; case WM_SATURDAY_NIGHT_FEVER: if( flag&1 ) { sc_start(src,bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv)); } else if (sd) { if( rnd()%100 < sstatus->int_ / 6 + sd->status.job_level / 5 + skill_lv * 4 + pc_checkskill(sd, WM_LESSON) ) { // !TODO: What's the Lesson bonus? map_foreachinallrange(skill_area_sub, src, skill_get_splash(skill_id,skill_lv),BL_PC, src, skill_id, skill_lv, tick, flag|BCT_ENEMY|1, skill_castend_nodamage_id); clif_skill_nodamage(src,bl,skill_id,skill_lv,1); } } break; case WM_SIRCLEOFNATURE: case WM_SONG_OF_MANA: case WM_DANCE_WITH_WUG: case WM_LERADS_DEW: case WM_UNLIMITED_HUMMING_VOICE: if( flag&1 ) { // These affect to to all party members near the caster. status_change *sc = status_get_sc(src); if( sc && sc->getSCE(type) ) { sc_start2(src,bl,type,100,skill_lv,pc_checkskill(sd, WM_LESSON),skill_get_time(skill_id,skill_lv)); } } else if( sd ) { if( sc_start2(src,bl,type,100,skill_lv,pc_checkskill(sd, WM_LESSON),skill_get_time(skill_id,skill_lv)) ) party_foreachsamemap(skill_area_sub,sd,skill_get_splash(skill_id,skill_lv),src,skill_id,skill_lv,tick,flag|BCT_PARTY|1,skill_castend_nodamage_id); clif_skill_nodamage(src,bl,skill_id,skill_lv,1); } break; case WM_MELODYOFSINK: if( flag&1 ) { sc_start(src,bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv)); } else { // These affect to all targets around the caster. if( rnd()%100 < 5 + 5 * skill_lv + pc_checkskill(sd, WM_LESSON) ) { // !TODO: What's the Lesson bonus? map_foreachinallrange(skill_area_sub, src, skill_get_splash(skill_id,skill_lv),BL_PC, src, skill_id, skill_lv, tick, flag|BCT_ENEMY|1, skill_castend_nodamage_id); clif_skill_nodamage(src,bl,skill_id,skill_lv,1); } } break; case WM_BEYOND_OF_WARCRY: if( flag&1 ) { sc_start(src,bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv)); } else { // These affect to all targets around the caster. if( rnd()%100 < 12 + 3 * skill_lv + (sd ? pc_checkskill(sd, WM_LESSON) : 0) ) { // !TODO: What's the Lesson bonus? map_foreachinallrange(skill_area_sub, src, skill_get_splash(skill_id,skill_lv),BL_PC, src, skill_id, skill_lv, tick, flag|BCT_ENEMY|1, skill_castend_nodamage_id); clif_skill_nodamage(src,bl,skill_id,skill_lv,1); } } break; case WM_SOUND_OF_DESTRUCTION: if (flag&1) { sc_start(src, bl, type, 100, skill_lv, (sd ? pc_checkskill(sd, WM_LESSON) * 500 : 0) + skill_get_time(skill_id, skill_lv)); // !TODO: Confirm Lesson increase } else { map_foreachinallrange(skill_area_sub, src, skill_get_splash(skill_id, skill_lv),BL_PC, src, skill_id, skill_lv, tick, flag|BCT_ENEMY|1, skill_castend_nodamage_id); clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); } break; case WM_RANDOMIZESPELL: if (rnd() % 100 < 30 + (10 * skill_lv)) { status_change_end(bl, SC_SONGOFMANA); status_change_end(bl, SC_DANCEWITHWUG); status_change_end(bl, SC_LERADSDEW); status_change_end(bl, SC_SATURDAYNIGHTFEVER); status_change_end(bl, SC_BEYONDOFWARCRY); status_change_end(bl, SC_MELODYOFSINK); status_change_end(bl, SC_BEYONDOFWARCRY); status_change_end(bl, SC_UNLIMITEDHUMMINGVOICE); clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); } break; case RETURN_TO_ELDICASTES: case ALL_GUARDIAN_RECALL: case ECLAGE_RECALL: case ALL_PRONTERA_RECALL: if( sd ) { short x=0, y=0; // Destiny position. unsigned short mapindex=0; switch(skill_id){ default: case RETURN_TO_ELDICASTES: x = 198; y = 187; mapindex = mapindex_name2id(MAP_DICASTES); break; case ALL_GUARDIAN_RECALL: x = 44; y = 151; mapindex = mapindex_name2id(MAP_MORA); break; case ECLAGE_RECALL: x = 47; y = 31; mapindex = mapindex_name2id(MAP_ECLAGE_IN); break; case ALL_PRONTERA_RECALL: if(skill_lv == 1) { x = 115; y = 72; } else if(skill_lv == 2) { x = 159; y = 192; } mapindex = mapindex_name2id(MAP_PRONTERA); break; } if(!mapindex) { //Given map not found? clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); map_freeblock_unlock(); return 0; } pc_setpos(sd, mapindex, x, y, CLR_TELEPORT); } break; case ECL_SNOWFLIP: case ECL_PEONYMAMY: case ECL_SADAGUI: case ECL_SEQUOIADUST: switch(skill_id){ case ECL_SNOWFLIP: status_change_end(bl, SC_SLEEP); status_change_end(bl, SC_BLEEDING); status_change_end(bl, SC_BURNING); status_change_end(bl, SC_DEEPSLEEP); break; case ECL_PEONYMAMY: status_change_end(bl, SC_FREEZE); status_change_end(bl, SC_FREEZING); status_change_end(bl, SC_CRYSTALIZE); break; case ECL_SADAGUI: status_change_end(bl, SC_STUN); status_change_end(bl, SC_CONFUSION); status_change_end(bl, SC_HALLUCINATION); status_change_end(bl, SC_FEAR); break; case ECL_SEQUOIADUST: status_change_end(bl, SC_STONE); status_change_end(bl, SC_POISON); status_change_end(bl, SC_CURSE); status_change_end(bl, SC_BLIND); status_change_end(bl, SC_ORCISH); status_change_end(bl, SC_DECREASEAGI); break; } clif_skill_damage(src, bl, tick, status_get_amotion(src), 0, -30000, 1, skill_id, 1, DMG_SINGLE); clif_skill_nodamage(src,bl,skill_id,skill_lv,1); break; case GM_SANDMAN: if( tsc ) { if( tsc->opt1 == OPT1_SLEEP ) tsc->opt1 = 0; else tsc->opt1 = OPT1_SLEEP; clif_changeoption(bl); clif_skill_nodamage (src, bl, skill_id, skill_lv, 1); } break; case SO_ARRULLO: { int rate = (15 + 5 * skill_lv) + status_get_int(src) / 5 + (sd ? sd->status.job_level / 5 : 0) - status_get_int(bl) / 6 - status_get_luk(bl) / 10; clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); sc_start(src, bl, type, rate, skill_lv, skill_get_time(skill_id, skill_lv)); } break; case WM_LULLABY_DEEPSLEEP: if (flag&1) { int rate = 4 * skill_lv + (sd ? pc_checkskill(sd, WM_LESSON) * 2 : 0) + status_get_lv(src) / 15 + (sd ? sd->status.job_level / 5 : 0); int duration = skill_get_time(skill_id, skill_lv) - (status_get_base_status(bl)->int_ * 50 + status_get_lv(bl) * 50); // Duration reduction for Deep Sleep Lullaby is doubled sc_start(src, bl, type, rate, skill_lv, duration); } else { clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); map_foreachinallrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), BL_CHAR, src, skill_id, skill_lv, tick, flag|BCT_ENEMY|1, skill_castend_nodamage_id); } break; case SO_SUMMON_AGNI: case SO_SUMMON_AQUA: case SO_SUMMON_VENTUS: case SO_SUMMON_TERA: if( sd ) { int elemental_class = skill_get_elemental_type(skill_id,skill_lv); // Remove previous elemental first. if( sd->ed ) elemental_delete(sd->ed); // Summoning the new one. if( !elemental_create(sd,elemental_class,skill_get_time(skill_id,skill_lv)) ) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); break; } clif_skill_nodamage(src,bl,skill_id,skill_lv,1); } break; case SO_EL_CONTROL: if( sd ) { int mode; if( !sd->ed ) break; if( skill_lv == 4 ) {// At level 4 delete elementals. elemental_delete(sd->ed); break; } switch( skill_lv ) {// Select mode bassed on skill level used. case 1: mode = EL_MODE_PASSIVE; break; // Standard mode. case 2: mode = EL_MODE_ASSIST; break; case 3: mode = EL_MODE_AGGRESSIVE; break; } if( !elemental_change_mode(sd->ed,mode) ) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); break; } clif_skill_nodamage(src,bl,skill_id,skill_lv,1); } break; case SO_EL_ACTION: if( sd ) { int duration = 3000; if( !sd->ed ) break; switch(sd->ed->db->class_) { case ELEMENTALID_AGNI_M: case ELEMENTALID_AQUA_M: case ELEMENTALID_VENTUS_M: case ELEMENTALID_TERA_M: duration = 6000; break; case ELEMENTALID_AGNI_L: case ELEMENTALID_AQUA_L: case ELEMENTALID_VENTUS_L: case ELEMENTALID_TERA_L: duration = 9000; break; } sd->skill_id_old = skill_id; elemental_action(sd->ed, bl, tick); clif_skill_nodamage(src,bl,skill_id,skill_lv,1); skill_blockpc_start(sd, skill_id, duration); } break; case SO_EL_CURE: if( sd ) { s_elemental_data *ed = sd->ed; int s_hp, s_sp; if( !ed ) break; s_hp = sd->battle_status.hp * 10 / 100; s_sp = sd->battle_status.sp * 10 / 100; if( !status_charge(&sd->bl,s_hp,s_sp) ) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); break; } status_heal(&ed->bl,s_hp,s_sp,3); clif_skill_nodamage(src,&ed->bl,skill_id,skill_lv,1); } break; case GN_CHANGEMATERIAL: case SO_EL_ANALYSIS: if( sd ) { clif_skill_nodamage(src,bl,skill_id,skill_lv,1); clif_skill_itemlistwindow(sd,skill_id,skill_lv); } break; case NPC_MANDRAGORA: case GN_MANDRAGORA: if( flag&1 ) { int rate; if (skill_id == NPC_MANDRAGORA) rate = (20 * skill_lv) - (tstatus->vit + tstatus->luk) / 5; else rate = 25 + (10 * skill_lv) - (tstatus->vit + tstatus->luk) / 5; if (rate < 10) rate = 10; if (bl->type == BL_MOB || (tsc && tsc->getSCE(type))) break; // Don't activate if target is a monster or zap SP if target already has Mandragora active. if (rnd()%100 < rate) { sc_start(src,bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv)); status_zap(bl,0,status_get_max_sp(bl) * (25 + 5 * skill_lv) / 100); } } else { map_foreachinallrange(skill_area_sub,bl,skill_get_splash(skill_id,skill_lv),BL_CHAR,src,skill_id,skill_lv,tick,flag|BCT_ENEMY|1,skill_castend_nodamage_id); clif_skill_nodamage(src,src,skill_id,skill_lv,1); } break; case GN_SLINGITEM: if( sd ) { i = sd->equip_index[EQI_AMMO]; if( i < 0 ) break; // No ammo. t_itemid ammo_id = sd->inventory_data[i]->nameid; if( ammo_id == 0 ) break; sd->itemid = ammo_id; if( itemdb_group.item_exists(IG_BOMB, ammo_id) ) { if(battle_check_target(src,bl,BCT_ENEMY) > 0) {// Only attack if the target is an enemy. if( ammo_id == ITEMID_PINEAPPLE_BOMB ) map_foreachincell(skill_area_sub,bl->m,bl->x,bl->y,BL_CHAR,src,GN_SLINGITEM_RANGEMELEEATK,skill_lv,tick,flag|BCT_ENEMY|1,skill_castend_damage_id); else skill_attack(BF_WEAPON,src,src,bl,GN_SLINGITEM_RANGEMELEEATK,skill_lv,tick,flag); } else //Otherwise, it fails, shows animation and removes items. clif_skill_fail(sd,GN_SLINGITEM_RANGEMELEEATK,USESKILL_FAIL,0); } else if (itemdb_group.item_exists(IG_THROWABLE, ammo_id)) { switch (ammo_id) { case ITEMID_HP_INC_POTS_TO_THROW: // MaxHP +(500 + Thrower BaseLv * 10 / 3) and heals 1% MaxHP sc_start2(src, bl, SC_PROMOTE_HEALTH_RESERCH, 100, 2, 1, 500000); status_percent_heal(bl, 1, 0); break; case ITEMID_HP_INC_POTM_TO_THROW: // MaxHP +(1500 + Thrower BaseLv * 10 / 3) and heals 2% MaxHP sc_start2(src, bl, SC_PROMOTE_HEALTH_RESERCH, 100, 2, 2, 500000); status_percent_heal(bl, 2, 0); break; case ITEMID_HP_INC_POTL_TO_THROW: // MaxHP +(2500 + Thrower BaseLv * 10 / 3) and heals 5% MaxHP sc_start2(src, bl, SC_PROMOTE_HEALTH_RESERCH, 100, 2, 3, 500000); status_percent_heal(bl, 5, 0); break; case ITEMID_SP_INC_POTS_TO_THROW: // MaxSP +(Thrower BaseLv / 10 - 5)% and recovers 2% MaxSP sc_start2(src, bl, SC_ENERGY_DRINK_RESERCH, 100, 2, 1, 500000); status_percent_heal(bl, 0, 2); break; case ITEMID_SP_INC_POTM_TO_THROW: // MaxSP +(Thrower BaseLv / 10)% and recovers 4% MaxSP sc_start2(src, bl, SC_ENERGY_DRINK_RESERCH, 100, 2, 2, 500000); status_percent_heal(bl, 0, 4); break; case ITEMID_SP_INC_POTL_TO_THROW: // MaxSP +(Thrower BaseLv / 10 + 5)% and recovers 8% MaxSP sc_start2(src, bl, SC_ENERGY_DRINK_RESERCH, 100, 2, 3, 500000); status_percent_heal(bl, 0, 8); break; default: if (dstsd) run_script(sd->inventory_data[i]->script, 0, dstsd->bl.id, fake_nd->bl.id); break; } } } clif_skill_nodamage(src,bl,skill_id,skill_lv,1); clif_skill_nodamage(src,bl,skill_id,skill_lv,1);// This packet is received twice actually, I think it is to show the animation. break; case GN_MIX_COOKING: case GN_MAKEBOMB: case GN_S_PHARMACY: if( sd ) { int qty = 1; sd->skill_id_old = skill_id; sd->skill_lv_old = skill_lv; if( skill_id != GN_S_PHARMACY && skill_lv > 1 ) qty = 10; clif_cooking_list(sd,(skill_id - GN_MIX_COOKING) + 27,skill_id,qty,skill_id==GN_MAKEBOMB?5:6); clif_skill_nodamage(src,bl,skill_id,skill_lv,1); } break; case EL_CIRCLE_OF_FIRE: case EL_PYROTECHNIC: case EL_HEATER: case EL_TROPIC: case EL_AQUAPLAY: case EL_COOLER: case EL_CHILLY_AIR: case EL_GUST: case EL_BLAST: case EL_WILD_STORM: case EL_PETROLOGY: case EL_CURSED_SOIL: case EL_UPHEAVAL: case EL_FIRE_CLOAK: case EL_WATER_DROP: case EL_WIND_CURTAIN: case EL_SOLID_SKIN: case EL_STONE_SHIELD: case EL_WIND_STEP: case EM_EL_FLAMETECHNIC: case EM_EL_FLAMEARMOR: case EM_EL_COLD_FORCE: case EM_EL_CRYSTAL_ARMOR: case EM_EL_GRACE_BREEZE: case EM_EL_EYES_OF_STORM: case EM_EL_EARTH_CARE: case EM_EL_STRONG_PROTECTION: case EM_EL_DEEP_POISONING: case EM_EL_POISON_SHIELD: { s_elemental_data *ele = BL_CAST(BL_ELEM, src); if( ele ) { sc_type type2 = (sc_type)(type-1); status_change *sc = status_get_sc(&ele->bl); if( (sc && sc->getSCE(type2)) || (tsc && tsc->getSCE(type)) ) { status_change_end(src,type); status_change_end(bl,type2); } else { clif_skill_nodamage(src,src,skill_id,skill_lv,1); if (!(skill_id >= EM_EL_FLAMETECHNIC && skill_id <= EM_EL_DEADLY_POISON)) clif_skill_damage(src, ( skill_id == EL_GUST || skill_id == EL_BLAST || skill_id == EL_WILD_STORM )?src:bl, tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, DMG_SINGLE); if( skill_id == EL_WIND_STEP ) // There aren't teleport, just push the master away. skill_blown(src,bl,(rnd()%skill_get_blewcount(skill_id,skill_lv))+1,rnd()%8,BLOWN_NONE); sc_start(src,src,type2,100,skill_lv,skill_get_time(skill_id,skill_lv)); sc_start(src,bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv)); } } } break; case EL_FIRE_MANTLE: case EL_WATER_BARRIER: case EL_ZEPHYR: case EL_POWER_OF_GAIA: clif_skill_damage(src, bl, tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, DMG_SINGLE); skill_unitsetting(src,skill_id,skill_lv,bl->x,bl->y,0); break; case EL_WATER_SCREEN: { s_elemental_data *ele = BL_CAST(BL_ELEM, src); if( ele ) { status_change *sc = status_get_sc(&ele->bl); sc_type type2 = (sc_type)(type-1); clif_skill_nodamage(src,src,skill_id,skill_lv,1); if( (sc && sc->getSCE(type2)) || (tsc && tsc->getSCE(type)) ) { status_change_end(bl,type); status_change_end(src,type2); } else { // This not heals at the end. clif_skill_damage(src, src, tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, DMG_SINGLE); sc_start(src,src,type2,100,skill_lv,skill_get_time(skill_id,skill_lv)); sc_start(src,bl,type,100,src->id,skill_get_time(skill_id,skill_lv)); } } } break; case KO_KAHU_ENTEN: case KO_HYOUHU_HUBUKI: case KO_KAZEHU_SEIRAN: case KO_DOHU_KOUKAI: if (sd) { int ele_type = skill_get_ele(skill_id,skill_lv); clif_skill_nodamage(src,bl,skill_id,skill_lv,1); pc_addspiritcharm(sd,skill_get_time(skill_id,skill_lv),MAX_SPIRITCHARM,ele_type); } break; case KO_ZANZOU: if(sd){ struct mob_data *md2; md2 = mob_once_spawn_sub(src, src->m, src->x, src->y, status_get_name(src), MOBID_ZANZOU, "", SZ_SMALL, AI_NONE); if( md2 ) { md2->master_id = src->id; md2->special_state.ai = AI_ZANZOU; if( md2->deletetimer != INVALID_TIMER ) delete_timer(md2->deletetimer, mob_timer_delete); md2->deletetimer = add_timer (gettick() + skill_get_time(skill_id, skill_lv), mob_timer_delete, md2->bl.id, 0); mob_spawn( md2 ); map_foreachinallrange(unit_changetarget, src, AREA_SIZE, BL_MOB, src, &md2->bl); clif_skill_nodamage(src,bl,skill_id,skill_lv,1); skill_blown(src,bl,skill_get_blewcount(skill_id,skill_lv),unit_getdir(bl),BLOWN_NONE); } } break; case KO_KYOUGAKU: if( dstsd && tsc && !tsc->getSCE(type) && rnd()%100 < tstatus->int_/2 ){ clif_skill_nodamage(src,bl,skill_id,skill_lv, sc_start(src,bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); }else if( sd ) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); break; case KO_JYUSATSU: if( dstsd && tsc && !tsc->getSCE(type) && rnd()%100 < ((45+5*skill_lv) + skill_lv*5 - status_get_int(bl)/2) ){//[(Base chance of success) + (Skill Level x 5) - (int / 2)]%. clif_skill_nodamage(src,bl,skill_id,skill_lv, status_change_start(src,bl,type,10000,skill_lv,0,0,0,skill_get_time(skill_id,skill_lv),SCSTART_NOAVOID|SCSTART_NOTICKDEF)); status_percent_damage(src, bl, tstatus->hp * skill_lv * 5, 0, false); // Does not kill the target. if( status_get_lv(bl) <= status_get_lv(src) ) status_change_start(src,bl,SC_COMA,10,skill_lv,0,src->id,0,0,SCSTART_NONE); }else if( sd ) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); break; case KO_GENWAKU: if ((dstsd || dstmd) && !status_has_mode(tstatus,MD_IGNOREMELEE|MD_IGNOREMAGIC|MD_IGNORERANGED|MD_IGNOREMISC) && battle_check_target(src,bl,BCT_ENEMY) > 0) { int x = src->x, y = src->y; if (sd && rnd()%100 > ((45+5*skill_lv) - status_get_int(bl)/10)) { //[(Base chance of success) - (Intelligence Objectives / 10)]%. clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); break; } // Confusion is still inflicted (but rate isn't reduced), no matter map type. status_change_start(src, src, SC_CONFUSION, 2500, skill_lv, 0, 0, 0, skill_get_time(skill_id, skill_lv), SCSTART_NORATEDEF); status_change_start(src, bl, SC_CONFUSION, 7500, skill_lv, 0, 0, 0, skill_get_time(skill_id, skill_lv), SCSTART_NORATEDEF); if (skill_check_unit_movepos(5,src,bl->x,bl->y,0,0)) { clif_skill_nodamage(src, src, skill_id, skill_lv, 1); clif_blown(src); if (!unit_blown_immune(bl, 0x1)) { unit_movepos(bl,x,y,0,0); if (bl->type == BL_PC && pc_issit((TBL_PC*)bl)) clif_sitting(bl); //Avoid sitting sync problem clif_blown(bl); map_foreachinallrange(unit_changetarget, src, AREA_SIZE, BL_CHAR, src, bl); } } } break; case OB_AKAITSUKI: case OB_OBOROGENSOU: if( sd && ( (skill_id == OB_OBOROGENSOU && bl->type == BL_MOB) // This skill does not work on monsters. || status_bl_has_mode(bl,MD_STATUSIMMUNE) ) ){ // Does not work on status immune monsters. clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); break; } case KO_IZAYOI: case OB_ZANGETSU: case KG_KYOMU: case KG_KAGEMUSYA: case SP_SOULDIVISION: if (skill_id == SP_SOULDIVISION) { // Usable only on other players. if (bl->type != BL_PC) { clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); break; } } clif_skill_nodamage(src,bl,skill_id,skill_lv, sc_start(src,bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); clif_skill_damage(src,bl,tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, DMG_SINGLE); break; case KG_KAGEHUMI: if( flag&1 ){ if (bl->type != BL_PC) break; if (tsc && (tsc->option & (OPTION_CLOAK | OPTION_HIDE) || tsc->getSCE(SC_CAMOUFLAGE) || tsc->getSCE(SC__SHADOWFORM) || tsc->getSCE(SC_MARIONETTE) || tsc->getSCE(SC_HARMONIZE))) { status_change_end(bl, SC_HIDING); status_change_end(bl, SC_CLOAKING); status_change_end(bl, SC_CLOAKINGEXCEED); status_change_end(bl, SC_CAMOUFLAGE); status_change_end(bl, SC_NEWMOON); if (tsc && tsc->getSCE(SC__SHADOWFORM) && rnd() % 100 < 100 - tsc->getSCE(SC__SHADOWFORM)->val1 * 10) // [100 - (Skill Level x 10)] % status_change_end(bl, SC__SHADOWFORM); status_change_end(bl, SC_MARIONETTE); status_change_end(bl, SC_HARMONIZE); sc_start(src, bl, type, 100, skill_lv, skill_get_time(skill_id, skill_lv)); } }else{ map_foreachinrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), BL_CHAR|BL_SKILL, src, skill_id, skill_lv, tick, flag|BCT_ENEMY|1, skill_castend_nodamage_id); clif_skill_damage(src, src, tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, DMG_SINGLE); clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); } break; case MH_SILENT_BREEZE: { int heal = 5 * status_get_lv(&hd->bl) + #ifdef RENEWAL status_base_matk_min(bl, &hd->battle_status, status_get_lv(&hd->bl)); #else status_base_matk_min(&hd->battle_status); #endif //Silences the homunculus and target status_change_start(src,src,SC_SILENCE,10000,skill_lv,0,0,0,skill_get_time(skill_id,skill_lv),SCSTART_NONE); status_change_start(src,bl,SC_SILENCE,10000,skill_lv,0,0,0,skill_get_time(skill_id,skill_lv),SCSTART_NONE); //Recover the target's HP status_heal(bl,heal,0,3); //Removes these SC from target if (tsc) { const enum sc_type scs[] = { SC_MANDRAGORA, SC_HARMONIZE, SC_DEEPSLEEP, SC_VOICEOFSIREN, SC_SLEEP, SC_CONFUSION, SC_HALLUCINATION }; for (i = 0; i < ARRAYLENGTH(scs); i++) if (tsc->getSCE(scs[i])) status_change_end(bl, scs[i]); } if (hd) skill_blockhomun_start(hd, skill_id, skill_get_cooldown(skill_id, skill_lv)); } break; case MH_OVERED_BOOST: if (hd && battle_get_master(src)) { sc_start(src, battle_get_master(src), type, 100, skill_lv, skill_get_time(skill_id, skill_lv)); sc_start(src, bl, type, 100, skill_lv, skill_get_time(skill_id, skill_lv)); skill_blockhomun_start(hd, skill_id, skill_get_cooldown(skill_id, skill_lv)); } break; case MH_GRANITIC_ARMOR: case MH_PYROCLASTIC: if(hd) { struct block_list *s_bl = battle_get_master(src); if(s_bl) sc_start2(src, s_bl, type, 100, skill_lv, hd->homunculus.level, skill_get_time(skill_id, skill_lv)); //start on master sc_start2(src, bl, type, 100, skill_lv, hd->homunculus.level, skill_get_time(skill_id, skill_lv)); skill_blockhomun_start(hd, skill_id, skill_get_cooldown(skill_id, skill_lv)); } break; case MH_LIGHT_OF_REGENE: //self if(hd) { struct block_list *s_bl = battle_get_master(src); if(s_bl) sc_start(src, s_bl, type, 100, skill_lv, skill_get_time(skill_id, skill_lv)); sc_start2(src, src, type, 100, skill_lv, hd->homunculus.level, skill_get_time(skill_id, skill_lv)); skill_blockhomun_start(hd, skill_id, skill_get_cooldown(skill_id, skill_lv)); } break; case MH_STYLE_CHANGE: if(hd){ struct status_change_entry *sce; if((sce=hd->sc.getSCE(SC_STYLE_CHANGE))!=NULL){ //in preparation for other bl usage if(sce->val1 == MH_MD_FIGHTING) sce->val1 = MH_MD_GRAPPLING; else sce->val1 = MH_MD_FIGHTING; //if(hd->master && hd->sc.getSCE(SC_STYLE_CHANGE)) { // Aegis does not show any message when switching fighting style // char output[128]; // safesnprintf(output,sizeof(output),msg_txt(sd,378),(sce->val1==MH_MD_FIGHTING?"fighthing":"grappling")); // clif_messagecolor(&hd->master->bl, color_table[COLOR_RED], output, false, SELF); //} } else sc_start(&hd->bl,&hd->bl, SC_STYLE_CHANGE, 100, MH_MD_FIGHTING, INFINITE_TICK); } break; case MH_PAIN_KILLER: bl = battle_get_master(src); if (bl != nullptr) sc_start(src, bl, type, 100, skill_lv, skill_get_time(skill_id, skill_lv)); if (hd) skill_blockhomun_start(hd, skill_id, skill_get_cooldown(skill_id, skill_lv)); break; case MH_MAGMA_FLOW: sc_start(src,bl, type, 100, skill_lv, skill_get_time(skill_id, skill_lv)); if (hd) skill_blockhomun_start(hd, skill_id, skill_get_cooldown(skill_id, skill_lv)); break; case MH_SUMMON_LEGION: { int summons[5] = {MOBID_S_HORNET, MOBID_S_GIANT_HORNET, MOBID_S_GIANT_HORNET, MOBID_S_LUCIOLA_VESPA, MOBID_S_LUCIOLA_VESPA}; int qty[5] = {3 , 3 , 4 , 4 , 5}; struct mob_data *sum_md; int i_slave,c=0; int maxcount = qty[skill_lv-1]; i_slave = map_foreachinmap(skill_check_condition_mob_master_sub ,hd->bl.m, BL_MOB, hd->bl.id, summons[skill_lv-1], skill_id, &c); if(c >= maxcount) { map_freeblock_unlock(); return 0; //max qty already spawned } for(i_slave=0; i_slave<qty[skill_lv - 1]; i_slave++){ //easy way sum_md = mob_once_spawn_sub(src, src->m, src->x, src->y, status_get_name(src), summons[skill_lv - 1], "", SZ_SMALL, AI_ATTACK); if (sum_md) { sum_md->master_id = src->id; sum_md->special_state.ai = AI_LEGION; if (sum_md->deletetimer != INVALID_TIMER) delete_timer(sum_md->deletetimer, mob_timer_delete); sum_md->deletetimer = add_timer(gettick() + skill_get_time(skill_id, skill_lv), mob_timer_delete, sum_md->bl.id, 0); mob_spawn(sum_md); //Now it is ready for spawning. sc_start4(&sum_md->bl,&sum_md->bl, SC_MODECHANGE, 100, 1, 0, MD_CANATTACK|MD_AGGRESSIVE, 0, 60000); } } if (hd) skill_blockhomun_start(hd, skill_id, skill_get_cooldown(skill_id, skill_lv)); } break; case RL_RICHS_COIN: if (sd) { clif_skill_nodamage(src,bl,skill_id,skill_lv,1); for (i = 0; i < 10; i++) pc_addspiritball(sd,skill_get_time(skill_id,skill_lv),10); } break; case RL_C_MARKER: if (sd) { // If marked by someone else remove it if (tsce && tsce->val2 != src->id) status_change_end(bl, type); // Check if marked before ARR_FIND(0, MAX_SKILL_CRIMSON_MARKER, i, sd->c_marker[i] == bl->id); if (i == MAX_SKILL_CRIMSON_MARKER) { // Find empty slot ARR_FIND(0, MAX_SKILL_CRIMSON_MARKER, i, !sd->c_marker[i]); if (i == MAX_SKILL_CRIMSON_MARKER) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); break; } } sd->c_marker[i] = bl->id; status_change_start(src, bl, type, 10000, skill_lv, src->id, 0, 0, skill_get_time(skill_id,skill_lv), SCSTART_NOAVOID|SCSTART_NOTICKDEF|SCSTART_NORATEDEF); clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); } // If mob casts this, at least SC_C_MARKER as debuff else { status_change_start(src, bl, type, 10000, skill_lv, src->id, 0, 0, skill_get_time(skill_id,skill_lv), SCSTART_NOAVOID|SCSTART_NOTICKDEF|SCSTART_NORATEDEF); clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); } break; case RL_QD_SHOT: if (sd) { skill_area_temp[1] = bl->id; // Check surrounding skill_area_temp[0] = map_foreachinrange(skill_area_sub, src, skill_get_splash(skill_id, skill_lv), BL_CHAR, src, skill_id, skill_lv, tick, BCT_ENEMY, skill_area_sub_count); if (skill_area_temp[0]) map_foreachinallrange(skill_area_sub, src, skill_get_splash(skill_id, skill_lv), BL_CHAR, src, skill_id, skill_lv, tick, flag|BCT_ENEMY|SD_SPLASH|1, skill_castend_damage_id); // Main target always receives damage clif_skill_nodamage(src, src, skill_id, skill_lv, 1); skill_attack(skill_get_type(skill_id), src, src, bl, skill_id, skill_lv, tick, flag|BCT_ENEMY|SD_LEVEL); } else { clif_skill_nodamage(src, src, skill_id, skill_lv, 1); map_foreachinrange(skill_area_sub, src, skill_get_splash(skill_id, skill_lv), BL_CHAR, src, skill_id, skill_lv, tick, flag|BCT_ENEMY|SD_SPLASH|1, skill_castend_damage_id); } status_change_end(src, SC_QD_SHOT_READY); // End here to prevent spamming of the skill onto the target. skill_area_temp[0] = 0; skill_area_temp[1] = 0; break; case RL_FLICKER: if (sd) { sd->flicker = true; skill_area_temp[1] = 0; clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); // Detonate RL_B_TRAP if (pc_checkskill(sd, RL_B_TRAP)) map_foreachinallrange(skill_bind_trap, src, AREA_SIZE, BL_SKILL, src); // Detonate RL_H_MINE if ((i = pc_checkskill(sd, RL_H_MINE))) map_foreachinallrange(skill_area_sub, src, skill_get_splash(skill_id, skill_lv), BL_CHAR, src, RL_H_MINE, i, tick, flag|BCT_ENEMY|SD_SPLASH, skill_castend_damage_id); sd->flicker = false; } break; case SO_ELEMENTAL_SHIELD: if (!sd || sd->status.party_id == 0 || flag&1) { if (sd && sd->status.party_id == 0) { clif_skill_nodamage(src,bl,skill_id,skill_lv,1); if (sd->ed && skill_get_state(skill_id) == ST_ELEMENTALSPIRIT2) elemental_delete(sd->ed); } skill_unitsetting(bl, MG_SAFETYWALL, skill_lv + 5, bl->x, bl->y, 0); skill_unitsetting(bl, AL_PNEUMA, 1, bl->x, bl->y, 0); } else { clif_skill_nodamage(src,bl,skill_id,skill_lv,1); if (sd->ed && skill_get_state(skill_id) == ST_ELEMENTALSPIRIT2) elemental_delete(sd->ed); party_foreachsamemap(skill_area_sub, sd, skill_get_splash(skill_id,skill_lv), src, skill_id, skill_lv, tick, flag|BCT_PARTY|1, skill_castend_nodamage_id); } break; case SU_HIDE: if (tsce) { clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); status_change_end(bl, type); map_freeblock_unlock(); return 0; } clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); sc_start(src, bl, type, 100, skill_lv, skill_get_time(skill_id, skill_lv)); break; case SU_STOOP: clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); sc_start(src, bl, type, 100, skill_lv, skill_get_time(skill_id, skill_lv)); break; case SU_SV_ROOTTWIST: clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); if (sd && status_get_class_(bl) == CLASS_BOSS) { clif_skill_fail(sd, skill_id, USESKILL_FAIL_TOTARGET, 0); break; } if (tsc && tsc->count && tsc->getSCE(type)) // Refresh the status only if it's already active. sc_start(src, bl, type, 100, skill_lv, skill_get_time(skill_id, skill_lv)); else { sc_start(src, bl, type, 100, skill_lv, skill_get_time(skill_id, skill_lv)); if (sd && pc_checkskill(sd, SU_SPIRITOFLAND)) sc_start(src, src, SC_DORAM_MATK, 100, sd->status.base_level, skill_get_time(SU_SPIRITOFLAND, 1)); skill_addtimerskill(src, tick + 1000, bl->id, 0, 0, SU_SV_ROOTTWIST_ATK, skill_lv, skill_get_type(SU_SV_ROOTTWIST_ATK), flag); } break; case SU_TUNABELLY: { unsigned int heal = 0; if (dstmd && (dstmd->mob_id == MOBID_EMPERIUM || status_get_class_(bl) == CLASS_BATTLEFIELD)) heal = 0; else if (status_get_hp(bl) != status_get_max_hp(bl)) heal = ((2 * skill_lv - 1) * 10) * status_get_max_hp(bl) / 100; clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); status_heal(bl, heal, 0, 0); } break; case SU_BUNCHOFSHRIMP: case SU_HISS: case SU_PURRING: case SU_MEOWMEOW: if (sd == NULL || sd->status.party_id == 0 || flag&1) { int duration = skill_get_time(skill_id, skill_lv); if (skill_id == SU_BUNCHOFSHRIMP && pc_checkskill(sd, SU_SPIRITOFSEA)) duration += skill_get_time2(SU_BUNCHOFSHRIMP, skill_lv); clif_skill_nodamage(bl, bl, skill_id, skill_lv, sc_start(src, bl, type, 100, skill_lv, duration)); } else if (sd) { party_foreachsamemap(skill_area_sub, sd, skill_get_splash(skill_id, skill_lv), src, skill_id, skill_lv, tick, flag|BCT_PARTY|1, skill_castend_nodamage_id); } break; case SU_SHRIMPARTY: if (sd == NULL || sd->status.party_id == 0 || flag&1) { sc_start(src, bl, type, 100, skill_lv, skill_get_time(skill_id, skill_lv)); if (sd && (i = pc_checkskill(sd, SU_FRESHSHRIMP)) > 0) { clif_skill_nodamage(bl, bl, SU_FRESHSHRIMP, i, 1); sc_start(src, bl, SC_FRESHSHRIMP, 100, i, skill_get_time(SU_FRESHSHRIMP, i)); } } else if (sd) party_foreachsamemap(skill_area_sub, sd, skill_get_splash(skill_id, skill_lv), src, skill_id, skill_lv, tick, flag|BCT_PARTY|1, skill_castend_nodamage_id); break; case SU_POWEROFFLOCK: if (flag&1) { sc_start(src, bl, SC_FEAR, 100, skill_lv, skill_get_time(skill_id, skill_lv)); sc_start(src, bl, SC_FREEZE, 100, skill_lv, skill_get_time2(skill_id, skill_lv)); //! TODO: What's the duration? } else { clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); if (battle_config.skill_wall_check) map_foreachinshootrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), BL_CHAR, src, skill_id, skill_lv, tick, flag|BCT_ENEMY|1, skill_castend_nodamage_id); else map_foreachinrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), BL_CHAR, src, skill_id, skill_lv, tick, flag|BCT_ENEMY|1, skill_castend_nodamage_id); } break; case ALL_EQSWITCH: if( sd ){ clif_equipswitch_reply( sd, false ); for( int i = 0, position = 0; i < EQI_MAX; i++ ){ if( sd->equip_switch_index[i] >= 0 && !( position & equip_bitmask[i] ) ){ position |= pc_equipswitch( sd, sd->equip_switch_index[i] ); } } } break; case AB_VITUPERATUM: if (flag&1) clif_skill_nodamage(src, bl, skill_id, skill_lv, sc_start(src, bl, type, 100, skill_lv, skill_get_time(skill_id, skill_lv))); else { map_foreachinrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), BL_CHAR, src, skill_id, skill_lv, tick, flag|BCT_ENEMY|1, skill_castend_nodamage_id); clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); } break; case AB_CONVENIO: if (sd) { party_data *p = party_search(sd->status.party_id); int i = 0, count = 0; // Only usable in party if (p == nullptr) { clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); break; } // Only usable as party leader. ARR_FIND(0, MAX_PARTY, i, p->data[i].sd == sd); if (i == MAX_PARTY || !p->party.member[i].leader) { clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); break; } // Do the teleport part for (i = 0; i < MAX_PARTY; ++i) { map_session_data *pl_sd = p->data[i].sd; if (pl_sd == nullptr || pl_sd == sd || pl_sd->status.party_id != p->party.party_id || pc_isdead(pl_sd) || sd->bl.m != pl_sd->bl.m) continue; // Respect /call configuration if( pl_sd->status.disable_call ){ continue; } if (!(map_getmapflag(sd->bl.m, MF_NOTELEPORT) || map_getmapflag(sd->bl.m, MF_PVP) || map_getmapflag(sd->bl.m, MF_BATTLEGROUND) || map_flag_gvg2(sd->bl.m))) { pc_setpos(pl_sd, map_id2index(sd->bl.m), sd->bl.x, sd->bl.y, CLR_TELEPORT); count++; } } if (!count) clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); } break; case NPC_PULSESTRIKE2: for (int i = 0; i < 3; i++) skill_addtimerskill(src, tick + (t_tick)skill_get_time(skill_id, skill_lv) * i, bl->id, 0, 0, skill_id, skill_lv, skill_get_type(skill_id), flag); break; case BO_THE_WHOLE_PROTECTION: if (sd == nullptr || sd->status.party_id == 0 || (flag & 1)) { unsigned int equip[] = { EQP_WEAPON, EQP_SHIELD, EQP_ARMOR, EQP_HEAD_TOP }; for (uint8 i_eqp = 0; i_eqp < 4; i_eqp++) { if (bl->type != BL_PC || (dstsd && pc_checkequip(dstsd, equip[i_eqp]) < 0)) continue; sc_start(src, bl, (sc_type)(SC_CP_WEAPON + i_eqp), 100, skill_lv, skill_get_time(skill_id, skill_lv)); } } else if (sd) { clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); party_foreachsamemap(skill_area_sub, sd, skill_get_splash(skill_id, skill_lv), src, skill_id, skill_lv, tick, flag | BCT_PARTY | 1, skill_castend_nodamage_id); } break; case TR_MUSICAL_INTERLUDE: case TR_JAWAII_SERENADE: case TR_PRON_MARCH: if (sd == nullptr || sd->status.party_id == 0 || (flag & 1)) sc_start4(src, bl, type, 100, skill_lv, 0, flag, 0, skill_get_time(skill_id, skill_lv)); else if (sd) { clif_skill_nodamage(bl, bl, skill_id, skill_lv, 1); sd->skill_id_song = skill_id; sd->skill_lv_song = skill_lv; if (skill_check_pc_partner(sd, skill_id, &skill_lv, AREA_SIZE, 0) > 0) flag |= 2; party_foreachsamemap(skill_area_sub, sd, skill_get_splash(skill_id, skill_lv), src, skill_id, skill_lv, tick, flag | BCT_PARTY | 1, skill_castend_nodamage_id); } break; case TR_GEF_NOCTURN: case TR_AIN_RHAPSODY: if (flag & 1) sc_start4(src, bl, type, 100, skill_lv, 0, flag, 0, skill_get_time(skill_id, skill_lv)); else if (sd) { clif_skill_nodamage(bl, bl, skill_id, skill_lv, 1); sd->skill_id_song = skill_id; sd->skill_lv_song = skill_lv; if (skill_check_pc_partner(sd, skill_id, &skill_lv, AREA_SIZE, 0) > 0) flag |= 2; map_foreachinallrange(skill_area_sub, src, skill_get_splash(skill_id, skill_lv), BL_CHAR, src, skill_id, skill_lv, tick, flag | BCT_ENEMY | 1, skill_castend_nodamage_id); } break; case TR_ROKI_CAPRICCIO: case TR_NIPELHEIM_REQUIEM: if (flag & 1) { // Need official success chances. uint16 success_chance = 5 * skill_lv; if (flag & 2) success_chance *= 2; // Is it a chance to inflect so and so, or seprate chances for inflicting each status? [Rytech] if (skill_id == TR_ROKI_CAPRICCIO) { sc_start(src, bl, SC_CONFUSION, 4 * skill_lv, skill_lv, skill_get_time(skill_id, skill_lv)); sc_start(src, bl, SC_HANDICAPSTATE_MISFORTUNE, success_chance, skill_lv, skill_get_time2(skill_id, skill_lv)); } else { // TR_NIPELHEIM_REQUIEM sc_start(src, bl, SC_CURSE, 4 * skill_lv, skill_lv, skill_get_time(skill_id, skill_lv)); sc_start(src, bl, SC_HANDICAPSTATE_DEPRESSION, success_chance, skill_lv, skill_get_time2(skill_id, skill_lv)); } } else if (sd) { clif_skill_nodamage(bl, bl, skill_id, skill_lv, 1); sd->skill_id_song = skill_id; sd->skill_lv_song = skill_lv; if (skill_check_pc_partner(sd, skill_id, &skill_lv, AREA_SIZE, 0) > 0) flag |= 2; map_foreachinallrange(skill_area_sub, src, skill_get_splash(skill_id, skill_lv), BL_CHAR, src, skill_id, skill_lv, tick, flag | BCT_ENEMY | 1, skill_castend_nodamage_id); } break; case ABR_NET_REPAIR: case ABR_NET_SUPPORT: if (flag & 1) { int heal_amount; if (skill_id == ABR_NET_REPAIR) { heal_amount = tstatus->max_hp * 10 / 100; clif_skill_nodamage( nullptr, bl, AL_HEAL, heal_amount, 1 ); status_heal(bl, heal_amount, 0, 0); } else { // ABR_NET_SUPPORT heal_amount = tstatus->max_sp * 3 / 100; clif_skill_nodamage( nullptr, bl, MG_SRECOVERY, heal_amount, 1 ); status_heal(bl, 0, heal_amount, 0); } } else { clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); map_foreachinrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), BL_CHAR, src, skill_id, skill_lv, tick, flag | BCT_ALLY | SD_SPLASH | 1, skill_castend_nodamage_id); } break; case WH_HAWK_M: if (sd) { if (!pc_isfalcon(sd)) pc_setoption(sd, sd->sc.option | OPTION_FALCON); else pc_setoption(sd, sd->sc.option&~OPTION_FALCON); clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); } break; case EM_SUMMON_ELEMENTAL_ARDOR: case EM_SUMMON_ELEMENTAL_DILUVIO: case EM_SUMMON_ELEMENTAL_PROCELLA: case EM_SUMMON_ELEMENTAL_TERREMOTUS: case EM_SUMMON_ELEMENTAL_SERPENS: { clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); if (sd == nullptr) break; uint16 em_elem[5] = { ELEMENTALID_ARDOR, ELEMENTALID_DILUVIO, ELEMENTALID_PROCELLA, ELEMENTALID_TERREMOTUS, ELEMENTALID_SERPENS }; uint16 so_elem[5] = { ELEMENTALID_AGNI_L, ELEMENTALID_AQUA_L, ELEMENTALID_VENTUS_L, ELEMENTALID_TERA_L, 0 }; uint8 elem_value = 4 - (EM_SUMMON_ELEMENTAL_SERPENS - skill_id); if (sd->ed && ((skill_id >= EM_SUMMON_ELEMENTAL_ARDOR && skill_id <= EM_SUMMON_ELEMENTAL_TERREMOTUS && sd->ed->elemental.class_ == so_elem[elem_value]) || (skill_id == EM_SUMMON_ELEMENTAL_SERPENS && (sd->ed->elemental.class_ == ELEMENTALID_AGNI_L || sd->ed->elemental.class_ == ELEMENTALID_AQUA_L || sd->ed->elemental.class_ == ELEMENTALID_VENTUS_L || sd->ed->elemental.class_ == ELEMENTALID_TERA_L)))) { // Remove the old elemental before summoning the super one. elemental_delete(sd->ed); if (!elemental_create(sd, em_elem[elem_value], skill_get_time(skill_id, skill_lv))) { clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); break; } else // Elemental summoned. Buff the player with the bonus. sc_start(src, bl, type, 100, skill_lv, skill_get_time(skill_id, skill_lv)); } else { clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); break; } } break; case EM_ELEMENTAL_VEIL: clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); if (sd == nullptr) break; if (sd->ed && sd->ed->elemental.class_ >= ELEMENTALID_DILUVIO && sd->ed->elemental.class_ <= ELEMENTALID_SERPENS) sc_start(src, &sd->ed->bl, type, 100, skill_lv, skill_get_time(skill_id, skill_lv)); else clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); break; case MT_M_MACHINE: case BO_BIONIC_PHARMACY: if (sd) { sd->skill_id_old = skill_id; sd->skill_lv_old = skill_lv; if (skill_id == MT_M_MACHINE) clif_cooking_list(sd, 31, skill_id, 1, 7); else // BO_BIONIC_PHARMACY clif_cooking_list(sd, 32, skill_id, 1, 8); clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); } break; case MT_SUMMON_ABR_BATTLE_WARIOR: case MT_SUMMON_ABR_DUAL_CANNON: case MT_SUMMON_ABR_MOTHER_NET: case MT_SUMMON_ABR_INFINITY: { uint32 abrs[4] = { MOBID_ABR_BATTLE_WARIOR, MOBID_ABR_DUAL_CANNON, MOBID_ABR_MOTHER_NET, MOBID_ABR_INFINITY }; clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); sc_start(src, bl, type, 100, skill_lv, skill_get_time(skill_id, skill_lv)); mob_data *md = mob_once_spawn_sub(src, src->m, src->x, src->y, "--ja--", abrs[3 - (MT_SUMMON_ABR_INFINITY - skill_id)], "", SZ_SMALL, AI_ABR); if (md) { md->master_id = src->id; md->special_state.ai = AI_ABR; if (md->deletetimer != INVALID_TIMER) delete_timer(md->deletetimer, mob_timer_delete); md->deletetimer = add_timer(gettick() + skill_get_time(skill_id, skill_lv), mob_timer_delete, md->bl.id, 0); mob_spawn(md); } } break; case BO_WOODENWARRIOR: case BO_WOODEN_FAIRY: case BO_CREEPER: case BO_HELLTREE: { // A poring is used in the 4th slot as a dummy since the Research Report skill is in between the Creeper and Hell Tree skills. uint32 bionics[5] = { MOBID_BIONIC_WOODENWARRIOR, MOBID_BIONIC_WOODEN_FAIRY, MOBID_BIONIC_CREEPER, MOBID_PORING, MOBID_BIONIC_HELLTREE }; clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); sc_start(src, bl, type, 100, skill_lv, skill_get_time(skill_id, skill_lv)); mob_data *md = mob_once_spawn_sub(src, src->m, src->x, src->y, "--ja--", bionics[4 - (BO_HELLTREE - skill_id)], "", SZ_SMALL, AI_BIONIC); if (md) { md->master_id = src->id; md->special_state.ai = AI_BIONIC; if (md->deletetimer != INVALID_TIMER) delete_timer(md->deletetimer, mob_timer_delete); md->deletetimer = add_timer(gettick() + skill_get_time(skill_id, skill_lv), mob_timer_delete, md->bl.id, 0); mob_spawn(md); } } break; #ifdef RENEWAL case CG_HERMODE: skill_castend_song(src, skill_id, skill_lv, tick); break; #endif default: { std::shared_ptr<s_skill_db> skill = skill_db.find(skill_id); ShowWarning("skill_castend_nodamage_id: missing code case for skill %s(%d)\n", skill ? skill->name : "UNKNOWN", skill_id); clif_skill_nodamage(src,bl,skill_id,skill_lv,1); map_freeblock_unlock(); return 1; } } if (skill_id != SR_CURSEDCIRCLE && skill_id != NPC_SR_CURSEDCIRCLE) { status_change *sc = status_get_sc(src); if (sc && sc->getSCE(SC_CURSEDCIRCLE_ATKER)) // Should only remove after the skill had been casted. status_change_end(src,SC_CURSEDCIRCLE_ATKER); } if (dstmd) { //Mob skill event for no damage skills (damage ones are handled in battle_damage/skill_attack) [Skotlex] mob_log_damage(dstmd, src, 0); //Log interaction (counts as 'attacker' for the exp bonus) mobskill_event(dstmd, src, tick, MSC_SKILLUSED|(skill_id<<16)); } if( sd && !(flag&1) ) {// ensure that the skill last-cast tick is recorded sd->canskill_tick = gettick(); if( sd->state.arrow_atk ) {// consume arrow on last invocation to this skill. battle_consume_ammo(sd, skill_id, skill_lv); } skill_onskillusage(sd, bl, skill_id, tick); // perform skill requirement consumption skill_consume_requirement(sd,skill_id,skill_lv,2); } map_freeblock_unlock(); return 0; } /** * Checking that causing skill failed * @param src Caster * @param target Target * @param skill_id * @param skill_lv * @return -1 success, others are failed @see enum useskill_fail_cause. **/ static int8 skill_castend_id_check(struct block_list *src, struct block_list *target, uint16 skill_id, uint16 skill_lv) { std::shared_ptr<s_skill_db> skill = skill_db.find(skill_id); int inf = skill->inf; status_change *tsc = status_get_sc(target); if (src != target && (status_bl_has_mode(target,MD_SKILLIMMUNE) || (status_get_class(target) == MOBID_EMPERIUM && !skill->inf2[INF2_TARGETEMPERIUM])) && skill_get_casttype(skill_id) == CAST_NODAMAGE) return USESKILL_FAIL_MAX; // Don't show a skill fail message (NoDamage type doesn't consume requirements) switch (skill_id) { case AL_HEAL: case AL_INCAGI: case AL_DECAGI: case SA_DISPELL: // Mado Gear is immune to Dispell according to bugreport:49 [Ind] case AB_RENOVATIO: case AB_HIGHNESSHEAL: if (tsc && tsc->option&OPTION_MADOGEAR) return USESKILL_FAIL_TOTARGET; break; case RG_BACKSTAP: { #ifndef RENEWAL uint8 dir = map_calc_dir(src,target->x,target->y), t_dir = unit_getdir(target); if (map_check_dir(dir, t_dir)) return USESKILL_FAIL_MAX; #endif if (check_distance_bl(src, target, 0)) return USESKILL_FAIL_MAX; } break; case PR_TURNUNDEAD: { struct status_data *tstatus = status_get_status_data(target); if (!battle_check_undead(tstatus->race, tstatus->def_ele)) return USESKILL_FAIL_MAX; } break; case PR_LEXDIVINA: case MER_LEXDIVINA: { //If it's not an enemy, and not silenced, you can't use the skill on them. [Skotlex] if (battle_check_target(src,target, BCT_ENEMY) <= 0 && (!tsc || !tsc->getSCE(SC_SILENCE))) return USESKILL_FAIL_LEVEL; else return -1; //Works on silenced allies } break; case RA_WUGSTRIKE: // Check if path can be reached if (!path_search(NULL,src->m,src->x,src->y,target->x,target->y,1,CELL_CHKNOREACH)) return USESKILL_FAIL_MAX; break; case MG_NAPALMBEAT: case MG_FIREBALL: case HT_BLITZBEAT: case AS_GRIMTOOTH: case MO_COMBOFINISH: case NC_VULCANARM: case SR_TIGERCANNON: // These can damage traps, but can't target traps directly if (target->type == BL_SKILL) { TBL_SKILL *su = (TBL_SKILL*)target; if (!su || !su->group) return USESKILL_FAIL_MAX; if (skill_get_inf2(su->group->skill_id, INF2_ISTRAP)) return USESKILL_FAIL_MAX; } break; case IQ_SECOND_FLAME: case IQ_SECOND_FAITH: case IQ_SECOND_JUDGEMENT: if (!tsc || !(tsc->getSCE(SC_FIRST_BRAND) || tsc->getSCE(SC_SECOND_BRAND))) return USESKILL_FAIL_LEVEL; break; case IQ_THIRD_PUNISH: case IQ_THIRD_FLAME_BOMB: case IQ_THIRD_CONSECRATION: if (!tsc || !tsc->getSCE(SC_SECOND_BRAND)) return USESKILL_FAIL_LEVEL; break; } if (inf&INF_ATTACK_SKILL || (inf&INF_SELF_SKILL && skill->inf2[INF2_NOTARGETSELF]) //Combo skills ) // Casted through combo. inf = BCT_ENEMY; //Offensive skill. else if (skill->inf2[INF2_NOTARGETENEMY]) inf = BCT_NOENEMY; else inf = 0; if ((skill->inf2[INF2_PARTYONLY] || skill->inf2[INF2_GUILDONLY]) && src != target) { inf |= (skill->inf2[INF2_PARTYONLY]?BCT_PARTY:0)| (skill->inf2[INF2_GUILDONLY]?BCT_GUILD:0); //Remove neutral targets (but allow enemy if skill is designed to be so) inf &= ~BCT_NEUTRAL; } switch (skill_id) { // Cannot be casted to Emperium case WZ_ESTIMATION: case SL_SKE: case SL_SKA: case RK_PHANTOMTHRUST: case NPC_PHANTOMTHRUST: if (target->type == BL_MOB && ((TBL_MOB*)target)->mob_id == MOBID_EMPERIUM) return USESKILL_FAIL_MAX; break; } if (inf && battle_check_target(src, target, inf) <= 0) { switch(skill_id) { case RK_PHANTOMTHRUST: case NPC_PHANTOMTHRUST: case AB_CLEARANCE: return USESKILL_FAIL_TOTARGET; default: return USESKILL_FAIL_LEVEL; } } // Fogwall makes all offensive-type targetted skills fail at 75% // Jump Kick can still fail even though you can jump to friendly targets. if ((inf&BCT_ENEMY || skill_id == TK_JUMPKICK) && tsc && tsc->getSCE(SC_FOGWALL) && rnd() % 100 < 75) return USESKILL_FAIL_LEVEL; return -1; } TIMER_FUNC( skill_keep_using ){ map_session_data* sd = map_id2sd( id ); if( sd && sd->skill_keep_using.skill_id ){ clif_parse_skill_toid( sd, sd->skill_keep_using.skill_id, sd->skill_keep_using.level, sd->skill_keep_using.target ); sd->skill_keep_using.tid = INVALID_TIMER; } return 0; } /** * Check & process skill to target on castend. Determines if skill is 'damage' or 'nodamage' * @param tid * @param tick * @param data **/ TIMER_FUNC(skill_castend_id){ struct block_list *target, *src; map_session_data *sd; struct mob_data *md; struct unit_data *ud; status_change *sc = NULL; int flag = 0; src = map_id2bl(id); if( src == NULL ) { ShowDebug("skill_castend_id: src == NULL (tid=%d, id=%d)\n", tid, id); return 0;// not found } ud = unit_bl2ud(src); if( ud == NULL ) { ShowDebug("skill_castend_id: ud == NULL (tid=%d, id=%d)\n", tid, id); return 0;// ??? } sd = BL_CAST(BL_PC, src); md = BL_CAST(BL_MOB, src); if( src->prev == NULL ) { ud->skilltimer = INVALID_TIMER; return 0; } if(ud->skill_id != SA_CASTCANCEL && ud->skill_id != SO_SPELLFIST) {// otherwise handled in unit_skillcastcancel() if( ud->skilltimer != tid ) { ShowError("skill_castend_id: Timer mismatch %d!=%d!\n", ud->skilltimer, tid); ud->skilltimer = INVALID_TIMER; return 0; } if( sd && ud->skilltimer != INVALID_TIMER && (pc_checkskill(sd,SA_FREECAST) > 0 || ud->skill_id == LG_EXEEDBREAK) ) {// restore original walk speed ud->skilltimer = INVALID_TIMER; status_calc_bl(&sd->bl, { SCB_SPEED, SCB_ASPD }); } else ud->skilltimer = INVALID_TIMER; } if (ud->skilltarget == id) target = src; else target = map_id2bl(ud->skilltarget); // Use a do so that you can break out of it when the skill fails. do { bool fail = false; int8 res = USESKILL_FAIL_LEVEL; if (!target || target->prev == NULL) break; if (src->m != target->m || status_isdead(src)) break; //These should become skill_castend_pos switch (ud->skill_id) { case WE_CALLPARTNER: if (sd) { map_session_data *p_sd = pc_get_partner(sd); if (p_sd && p_sd->state.autotrade) { fail = true; break; } else clif_callpartner(*sd); } break; case WE_CALLPARENT: if (sd) { map_session_data *f_sd = pc_get_father(sd); map_session_data *m_sd = pc_get_mother(sd); if ((f_sd && f_sd->state.autotrade) || (m_sd && m_sd->state.autotrade)) { fail = true; break; } } break; case WE_CALLBABY: if (sd) { map_session_data *c_sd = pc_get_child(sd); if (c_sd && c_sd->state.autotrade) { fail = true; break; } } break; case AM_RESURRECTHOMUN: case PF_SPIDERWEB: { //Find a random spot to place the skill. [Skotlex] int splash = skill_get_splash(ud->skill_id, ud->skill_lv); ud->skillx = target->x + splash; ud->skilly = target->y + splash; if (splash && !map_random_dir(target, &ud->skillx, &ud->skilly)) { ud->skillx = target->x; ud->skilly = target->y; } ud->skilltimer = tid; return skill_castend_pos(tid,tick,id,data); } case GN_WALLOFTHORN: case SC_ESCAPE: case WL_FROSTMISTY: case SU_CN_POWDERING: case AG_RAIN_OF_CRYSTAL: ud->skillx = target->x; ud->skilly = target->y; ud->skilltimer = tid; return skill_castend_pos(tid,tick,id,data); } // Failing if (fail || (res = skill_castend_id_check(src, target, ud->skill_id, ud->skill_lv)) >= 0) { if (sd && res != USESKILL_FAIL_MAX) clif_skill_fail(sd, ud->skill_id, (enum useskill_fail_cause)res, 0); break; } //Avoid doing double checks for instant-cast skills. if (tid != INVALID_TIMER && !status_check_skilluse(src, target, ud->skill_id, 1)) break; if(md) { md->last_thinktime=tick +MIN_MOBTHINKTIME; if(md->skill_idx >= 0 && md->db->skill[md->skill_idx]->emotion >= 0) clif_emotion(src, md->db->skill[md->skill_idx]->emotion); } if (src != target && battle_config.skill_add_range && !check_distance_bl(src, target, skill_get_range2(src, ud->skill_id, ud->skill_lv, true) + battle_config.skill_add_range)) { if (sd) { clif_skill_fail(sd, ud->skill_id, USESKILL_FAIL_LEVEL, 0); if (battle_config.skill_out_range_consume) //Consume items anyway. [Skotlex] skill_consume_requirement(sd, ud->skill_id, ud->skill_lv, 3); } break; } #ifdef OFFICIAL_WALKPATH if(skill_get_casttype(ud->skill_id) != CAST_NODAMAGE && !path_search_long(NULL, src->m, src->x, src->y, target->x, target->y, CELL_CHKWALL)) { if (sd) { clif_skill_fail(sd,ud->skill_id,USESKILL_FAIL_LEVEL,0); skill_consume_requirement(sd,ud->skill_id,ud->skill_lv,3); //Consume items anyway. } break; } #endif if( sd ) { if( !skill_check_condition_castend(sd, ud->skill_id, ud->skill_lv) ) break; else { skill_consume_requirement(sd,ud->skill_id,ud->skill_lv,1); int add_ap = skill_get_giveap( ud->skill_id, ud->skill_lv ); // Give AP if (add_ap > 0) { switch (ud->skill_id) { case TR_ROSEBLOSSOM: case TR_RHYTHMSHOOTING: case TR_METALIC_FURY: // Don't know the official increase. For now lets do up to 50% increase. case TR_SOUNDBLEND: // Retrospection does the same for song skills. [Rytech] add_ap += add_ap * (10 * pc_checkskill(sd, TR_STAGE_MANNER)) / 100; break; case TR_GEF_NOCTURN: case TR_ROKI_CAPRICCIO: case TR_AIN_RHAPSODY: case TR_MUSICAL_INTERLUDE: case TR_JAWAII_SERENADE: case TR_NIPELHEIM_REQUIEM: case TR_PRON_MARCH: if (sd->skill_id_old == TR_RETROSPECTION) { add_ap += add_ap * 50 / 100; sd->skill_id_old = ud->skill_id; // Prevents AP bonus on non Retro Spection use. } break; } status_heal(&sd->bl, 0, 0, add_ap, 0); } if (src != target && (status_bl_has_mode(target,MD_SKILLIMMUNE) || (status_get_class(target) == MOBID_EMPERIUM && !skill_get_inf2(ud->skill_id, INF2_TARGETEMPERIUM))) && skill_get_casttype(ud->skill_id) == CAST_DAMAGE) { clif_skill_fail(sd, ud->skill_id, USESKILL_FAIL_LEVEL, 0); break; // Show a skill fail message (Damage type consumes requirements) } } } if( (src->type == BL_MER || src->type == BL_HOM) && !skill_check_condition_mercenary(src, ud->skill_id, ud->skill_lv, 1) ) break; if (ud->state.running && ud->skill_id == TK_JUMPKICK){ ud->state.running = 0; status_change_end(src, SC_RUN); flag = 1; } if (ud->walktimer != INVALID_TIMER && ud->skill_id != TK_RUN && ud->skill_id != RA_WUGDASH) unit_stop_walking(src,1); if (!sd || sd->skillitem != ud->skill_id || skill_get_delay(ud->skill_id, ud->skill_lv)) ud->canact_tick = i64max(tick + skill_delayfix(src, ud->skill_id, ud->skill_lv), ud->canact_tick - SECURITY_CASTTIME); if (sd) { //Cooldown application int cooldown = pc_get_skillcooldown(sd,ud->skill_id, ud->skill_lv); // Increases/Decreases cooldown of a skill by item/card bonuses. if(cooldown) skill_blockpc_start(sd, ud->skill_id, cooldown); } if( battle_config.display_status_timers && sd ) clif_status_change(src, EFST_POSTDELAY, 1, skill_delayfix(src, ud->skill_id, ud->skill_lv), 0, 0, 0); if( sd ) { switch( ud->skill_id ) { case GS_DESPERADO: case RL_FIREDANCE: sd->canequip_tick = tick + skill_get_time(ud->skill_id, ud->skill_lv); break; case KN_BRANDISHSPEAR: case CR_GRANDCROSS: { sc_type type; if (ud->skill_id == KN_BRANDISHSPEAR) type = SC_STRIPWEAPON; else type = SC_STRIPSHIELD; if ((sc = status_get_sc(src)) && sc->getSCE(type)) { const struct TimerData* timer = get_timer(sc->getSCE(type)->timer); if (timer && timer->func == status_change_timer && DIFF_TICK(timer->tick, gettick() + skill_get_time(ud->skill_id, ud->skill_lv)) > 0) break; } sc_start2(src, src, type, 100, 0, 1, skill_get_time(ud->skill_id, ud->skill_lv)); break; } } } if (skill_get_state(ud->skill_id) != ST_MOVE_ENABLE) unit_set_walkdelay(src, tick, battle_config.default_walk_delay+skill_get_walkdelay(ud->skill_id, ud->skill_lv), 1); if(battle_config.skill_log && battle_config.skill_log&src->type) ShowInfo("Type %d, ID %d skill castend id [id =%d, lv=%d, target ID %d]\n", src->type, src->id, ud->skill_id, ud->skill_lv, target->id); map_freeblock_lock(); if (skill_get_casttype(ud->skill_id) == CAST_NODAMAGE) skill_castend_nodamage_id(src,target,ud->skill_id,ud->skill_lv,tick,flag); else skill_castend_damage_id(src,target,ud->skill_id,ud->skill_lv,tick,flag); if( sd && sd->skill_keep_using.tid == INVALID_TIMER && sd->skill_keep_using.skill_id > 0 && sd->skill_keep_using.skill_id == ud->skill_id && !skill_isNotOk(ud->skill_id, sd) && skill_check_condition_castbegin(sd, ud->skill_id, ud->skill_lv) ){ sd->skill_keep_using.tid = add_timer( sd->ud.canact_tick + 100, skill_keep_using, sd->bl.id, 0 ); } sc = status_get_sc(src); if(sc && sc->count) { if (ud->skill_id != RA_CAMOUFLAGE) status_change_end(src, SC_CAMOUFLAGE); // Applies to the first skill if active if(sc->getSCE(SC_SPIRIT) && sc->getSCE(SC_SPIRIT)->val2 == SL_WIZARD && sc->getSCE(SC_SPIRIT)->val3 == ud->skill_id && ud->skill_id != WZ_WATERBALL) sc->getSCE(SC_SPIRIT)->val3 = 0; //Clear bounced spell check. #ifndef RENEWAL if( sc->getSCE(SC_DANCING) && sd && skill_get_inf2(ud->skill_id, INF2_ISSONG) ) skill_blockpc_start(sd,BD_ADAPTATION,3000); #endif } if (sd && ud->skill_id != SA_ABRACADABRA) // they just set the data so leave it as it is.[Inkfish] sd->skillitem = sd->skillitemlv = sd->skillitem_keep_requirement = 0; if (ud->skilltimer == INVALID_TIMER) { if(md) md->skill_idx = -1; else ud->skill_id = 0; //mobs can't clear this one as it is used for skill condition 'afterskill' ud->skill_lv = ud->skilltarget = 0; } map_freeblock_unlock(); return 1; } while(0); //Skill failed. if (ud->skill_id == MO_EXTREMITYFIST && sd && !(sc && sc->getSCE(SC_FOGWALL))) { //When Asura fails... (except when it fails from Wall of Fog) //Consume SP/spheres skill_consume_requirement(sd,ud->skill_id, ud->skill_lv,1); status_set_sp(src, 0, 0); sc = &sd->sc; if (sc->count) { //End states status_change_end(src, SC_EXPLOSIONSPIRITS); status_change_end(src, SC_BLADESTOP); #ifdef RENEWAL sc_start(src,src, SC_EXTREMITYFIST2, 100, ud->skill_lv, skill_get_time(ud->skill_id, ud->skill_lv)); #endif } if( target && target->m == src->m ) { //Move character to target anyway. short x, y; short dir = map_calc_dir(src,target->x,target->y); //Move 3 cells (From Caster) if( dir > 0 && dir < 4 ) x = -3; else if( dir > 4 ) x = 3; else x = 0; if( dir > 2 && dir < 6 ) y = -3; else if( dir == 7 || dir < 2 ) y = 3; else y = 0; if( unit_movepos(src, src->x + x, src->y + y, 1, 1) ) { //Display movement + animation. clif_blown(src); clif_spiritball(src); } clif_skill_damage(src,target,tick,sd->battle_status.amotion,0,0,1,ud->skill_id,ud->skill_lv,DMG_SPLASH); } } ud->skill_id = ud->skilltarget = 0; if( !sd || sd->skillitem != ud->skill_id || skill_get_delay(ud->skill_id,ud->skill_lv) ) ud->canact_tick = tick; //You can't place a skill failed packet here because it would be //sent in ALL cases, even cases where skill_check_condition fails //which would lead to double 'skill failed' messages u.u [Skotlex] if (sd) { sd->skillitem = sd->skillitemlv = sd->skillitem_keep_requirement = 0; if (sd->skill_keep_using.skill_id > 0) { sd->skill_keep_using.skill_id = 0; if (sd->skill_keep_using.tid != INVALID_TIMER) { delete_timer(sd->skill_keep_using.tid, skill_keep_using); sd->skill_keep_using.tid = INVALID_TIMER; } } } else if (md) md->skill_idx = -1; return 0; } /*========================================== * *------------------------------------------*/ TIMER_FUNC(skill_castend_pos){ struct block_list* src = map_id2bl(id); map_session_data *sd; struct unit_data *ud = unit_bl2ud(src); struct mob_data *md; nullpo_ret(ud); sd = BL_CAST(BL_PC , src); md = BL_CAST(BL_MOB, src); if( src->prev == NULL ) { ud->skilltimer = INVALID_TIMER; return 0; } if( ud->skilltimer != tid ) { ShowError("skill_castend_pos: Timer mismatch %d!=%d\n", ud->skilltimer, tid); ud->skilltimer = INVALID_TIMER; return 0; } if( sd && ud->skilltimer != INVALID_TIMER && ( pc_checkskill(sd,SA_FREECAST) > 0 || ud->skill_id == LG_EXEEDBREAK ) ) {// restore original walk speed ud->skilltimer = INVALID_TIMER; status_calc_bl(&sd->bl, { SCB_SPEED, SCB_ASPD }); } else ud->skilltimer = INVALID_TIMER; do { if( status_isdead(src) ) break; if (!skill_pos_maxcount_check(src, ud->skillx, ud->skilly, ud->skill_id, ud->skill_lv, src->type, true)) break; if(tid != INVALID_TIMER) { //Avoid double checks on instant cast skills. [Skotlex] if (!status_check_skilluse(src, NULL, ud->skill_id, 1)) break; if (battle_config.skill_add_range && !check_distance_blxy(src, ud->skillx, ud->skilly, skill_get_range2(src, ud->skill_id, ud->skill_lv, true) + battle_config.skill_add_range)) { if (sd && battle_config.skill_out_range_consume) //Consume items anyway. skill_consume_requirement(sd, ud->skill_id, ud->skill_lv, 3); break; } } if( sd ) { if( ud->skill_id != AL_WARP && !skill_check_condition_castend(sd, ud->skill_id, ud->skill_lv) ) break; else { skill_consume_requirement(sd, ud->skill_id, ud->skill_lv, 1); int add_ap = skill_get_giveap(ud->skill_id, ud->skill_lv); // Give AP if (add_ap > 0) { switch (ud->skill_id) { case WH_DEEPBLINDTRAP: case WH_SOLIDTRAP: case WH_SWIFTTRAP: case WH_FLAMETRAP: if (pc_checkskill(sd, WH_ADVANCED_TRAP) >= 3) add_ap += 1; break; } status_heal(&sd->bl, 0, 0, add_ap, 0); } } } if( (src->type == BL_MER || src->type == BL_HOM) && !skill_check_condition_mercenary(src, ud->skill_id, ud->skill_lv, 1) ) break; if(md) { md->last_thinktime=tick +MIN_MOBTHINKTIME; if(md->skill_idx >= 0 && md->db->skill[md->skill_idx]->emotion >= 0) clif_emotion(src, md->db->skill[md->skill_idx]->emotion); } if(battle_config.skill_log && battle_config.skill_log&src->type) ShowInfo("Type %d, ID %d skill castend pos [id =%d, lv=%d, (%d,%d)]\n", src->type, src->id, ud->skill_id, ud->skill_lv, ud->skillx, ud->skilly); if (ud->walktimer != INVALID_TIMER) unit_stop_walking(src,1); if (!sd || sd->skillitem != ud->skill_id || skill_get_delay(ud->skill_id, ud->skill_lv)) ud->canact_tick = i64max(tick + skill_delayfix(src, ud->skill_id, ud->skill_lv), ud->canact_tick - SECURITY_CASTTIME); if (sd) { //Cooldown application int cooldown = pc_get_skillcooldown(sd,ud->skill_id, ud->skill_lv); if(cooldown) skill_blockpc_start(sd, ud->skill_id, cooldown); } if( battle_config.display_status_timers && sd ) clif_status_change(src, EFST_POSTDELAY, 1, skill_delayfix(src, ud->skill_id, ud->skill_lv), 0, 0, 0); // if( sd ) // { // switch( ud->skill_id ) // { // case ????: // sd->canequip_tick = tick + ????; // break; // } // } unit_set_walkdelay(src, tick, battle_config.default_walk_delay+skill_get_walkdelay(ud->skill_id, ud->skill_lv), 1); map_freeblock_lock(); skill_castend_pos2(src,ud->skillx,ud->skilly,ud->skill_id,ud->skill_lv,tick,0); if (ud->skill_id != RA_CAMOUFLAGE) status_change_end(src, SC_CAMOUFLAGE); // Applies to the first skill if active if( sd && sd->skillitem != AL_WARP ) // Warp-Portal thru items will clear data in skill_castend_map. [Inkfish] sd->skillitem = sd->skillitemlv = sd->skillitem_keep_requirement = 0; if (ud->skilltimer == INVALID_TIMER) { if (md) md->skill_idx = -1; else ud->skill_id = 0; //Non mobs can't clear this one as it is used for skill condition 'afterskill' ud->skill_lv = ud->skillx = ud->skilly = 0; } map_freeblock_unlock(); return 1; } while(0); if( !sd || sd->skillitem != ud->skill_id || skill_get_delay(ud->skill_id,ud->skill_lv) ) ud->canact_tick = tick; ud->skill_id = ud->skill_lv = 0; if(sd) sd->skillitem = sd->skillitemlv = sd->skillitem_keep_requirement = 0; else if(md) md->skill_idx = -1; return 0; } /* skill count without self */ static int skill_count_wos(struct block_list *bl,va_list ap) { struct block_list* src = va_arg(ap, struct block_list*); if( src->id != bl->id ) { return 1; } return 0; } /*========================================== * *------------------------------------------*/ int skill_castend_pos2(struct block_list* src, int x, int y, uint16 skill_id, uint16 skill_lv, t_tick tick, int flag) { map_session_data* sd; status_change* sc; struct status_change_entry *sce; std::shared_ptr<s_skill_unit_group> sg; enum sc_type type; int i; //if(skill_lv <= 0) return 0; if(skill_id > 0 && !skill_lv) return 0; // celest nullpo_ret(src); if(status_isdead(src)) return 0; sd = BL_CAST(BL_PC, src); sc = status_get_sc(src); type = skill_get_sc(skill_id); sce = (sc && type != SC_NONE)?sc->getSCE(type):NULL; switch (skill_id) { //Skill effect. case WZ_METEOR: case WZ_ICEWALL: case MO_BODYRELOCATION: case CR_CULTIVATION: case HW_GANBANTEIN: case LG_EARTHDRIVE: case SC_ESCAPE: case SU_CN_METEOR: break; //Effect is displayed on respective switch case. default: if(skill_get_inf(skill_id)&INF_SELF_SKILL) clif_skill_nodamage(src,src,skill_id,skill_lv,1); else clif_skill_poseffect(src,skill_id,skill_lv,x,y,tick); } switch(skill_id) { case PR_BENEDICTIO: skill_area_temp[1] = src->id; i = skill_get_splash(skill_id, skill_lv); map_foreachinallarea(skill_area_sub, src->m, x-i, y-i, x+i, y+i, BL_PC, src, skill_id, skill_lv, tick, flag|BCT_ALL|1, skill_castend_nodamage_id); map_foreachinallarea(skill_area_sub, src->m, x-i, y-i, x+i, y+i, BL_CHAR, src, skill_id, skill_lv, tick, flag|BCT_ENEMY|1, skill_castend_damage_id); break; case BS_HAMMERFALL: i = skill_get_splash(skill_id, skill_lv); map_foreachinallarea(skill_area_sub, src->m, x-i, y-i, x+i, y+i, BL_CHAR, src, skill_id, skill_lv, tick, flag|BCT_ENEMY|2, skill_castend_nodamage_id); break; case HT_DETECTING: i = skill_get_splash(skill_id, skill_lv); map_foreachinallarea( status_change_timer_sub, src->m, x-i, y-i, x+i,y+i,BL_CHAR, src,NULL,SC_SIGHT,tick); skill_reveal_trap_inarea(src, i, x, y); break; case SR_RIDEINLIGHTNING: i = skill_get_splash(skill_id, skill_lv); map_foreachinallarea(skill_area_sub, src->m, x-i, y-i, x+i, y+i, BL_CHAR, src, skill_id, skill_lv, tick, flag|BCT_ENEMY|1, skill_castend_damage_id); break; case NPC_LEX_AETERNA: i = skill_get_splash(skill_id, skill_lv); map_foreachinallarea(skill_area_sub, src->m, x-i, y-i, x+i, y+i, BL_CHAR, src, PR_LEXAETERNA, 1, tick, flag|BCT_ENEMY|1, skill_castend_nodamage_id); break; case SA_VOLCANO: case SA_DELUGE: case SA_VIOLENTGALE: { //Does not consumes if the skill is already active. [Skotlex] std::shared_ptr<s_skill_unit_group> sg2; if ((sg2= skill_locate_element_field(src)) != NULL && ( sg2->skill_id == SA_VOLCANO || sg2->skill_id == SA_DELUGE || sg2->skill_id == SA_VIOLENTGALE )) { if (sg2->limit - DIFF_TICK(gettick(), sg2->tick) > 0) { skill_unitsetting(src,skill_id,skill_lv,x,y,0); return 0; // not to consume items } else sg2->limit = 0; //Disable it. } skill_unitsetting(src,skill_id,skill_lv,x,y,0); break; } // Skill Unit Setting case MG_SAFETYWALL: { int dummy = 1; if (map_foreachincell(skill_cell_overlap, src->m, x, y, BL_SKILL, skill_id, &dummy, src)) { skill_unitsetting(src, skill_id, skill_lv, x, y, 0); return 0; // Don't consume gems if cast on Land Protector } } case MG_FIREWALL: case MG_THUNDERSTORM: case AL_PNEUMA: case WZ_FIREPILLAR: case WZ_QUAGMIRE: case WZ_VERMILION: case WZ_STORMGUST: case WZ_HEAVENDRIVE: case PR_SANCTUARY: case PR_MAGNUS: case CR_GRANDCROSS: case NPC_GRANDDARKNESS: case HT_SKIDTRAP: case MA_SKIDTRAP: case HT_LANDMINE: case MA_LANDMINE: case HT_ANKLESNARE: case HT_SHOCKWAVE: case HT_SANDMAN: case MA_SANDMAN: case HT_FLASHER: case HT_FREEZINGTRAP: case MA_FREEZINGTRAP: case HT_BLASTMINE: case HT_CLAYMORETRAP: case AS_VENOMDUST: case AM_DEMONSTRATION: case PF_FOGWALL: case PF_SPIDERWEB: case HT_TALKIEBOX: case WE_CALLPARTNER: case WE_CALLPARENT: case WE_CALLBABY: case SA_LANDPROTECTOR: #ifndef RENEWAL case BD_LULLABY: case BD_RICHMANKIM: case BD_ETERNALCHAOS: case BD_DRUMBATTLEFIELD: case BD_RINGNIBELUNGEN: case BD_ROKISWEIL: case BD_INTOABYSS: case BD_SIEGFRIED: case BA_DISSONANCE: case BA_POEMBRAGI: case BA_WHISTLE: case BA_ASSASSINCROSS: case BA_APPLEIDUN: case DC_UGLYDANCE: case DC_HUMMING: case DC_DONTFORGETME: case DC_FORTUNEKISS: case DC_SERVICEFORYOU: #endif case CG_MOONLIT: case GS_DESPERADO: case NJ_KAENSIN: case NJ_BAKUENRYU: case NJ_SUITON: case NJ_HYOUSYOURAKU: case NJ_RAIGEKISAI: case NJ_KAMAITACHI: #ifdef RENEWAL case HW_GRAVITATION: #endif case NPC_EVILLAND: case NPC_VENOMFOG: case NPC_COMET: case NPC_WIDESUCK: case NPC_ICEMINE: case NPC_FLAMECROSS: case NPC_HELLBURNING: case NPC_REVERBERATION: case WL_COMET: case RA_ELECTRICSHOCKER: case RA_CLUSTERBOMB: case RA_MAGENTATRAP: case RA_COBALTTRAP: case RA_MAIZETRAP: case RA_VERDURETRAP: case RA_FIRINGTRAP: case RA_ICEBOUNDTRAP: case SC_MANHOLE: case SC_DIMENSIONDOOR: case SC_CHAOSPANIC: case SC_MAELSTROM: case SC_BLOODYLUST: case WM_POEMOFNETHERWORLD: case SO_PSYCHIC_WAVE: case NPC_PSYCHIC_WAVE: case SO_VACUUM_EXTREME: case GN_THORNS_TRAP: case SO_EARTHGRAVE: case SO_DIAMONDDUST: case SO_FIRE_INSIGNIA: case SO_WATER_INSIGNIA: case SO_WIND_INSIGNIA: case SO_EARTH_INSIGNIA: case KO_ZENKAI: case MH_LAVA_SLIDE: case MH_VOLCANIC_ASH: case MH_POISON_MIST: case MH_STEINWAND: case MH_XENO_SLASHER: case LG_KINGS_GRACE: case SJ_BOOKOFCREATINGSTAR: case RL_B_TRAP: case NPC_STORMGUST2: case AG_RAIN_OF_CRYSTAL: case AG_MYSTERY_ILLUSION: case AG_STRANTUM_TREMOR: case AG_TORNADO_STORM: case AG_FLORAL_FLARE_ROAD: case IG_CROSS_RAIN: case CD_PNEUMATICUS_PROCELLA: case ABC_ABYSS_STRIKE: case ABC_ABYSS_SQUARE: case WH_DEEPBLINDTRAP: case WH_SOLIDTRAP: case WH_SWIFTTRAP: case WH_FLAMETRAP: case BO_ACIDIFIED_ZONE_WATER: case BO_ACIDIFIED_ZONE_GROUND: case BO_ACIDIFIED_ZONE_WIND: case BO_ACIDIFIED_ZONE_FIRE: case EM_DIAMOND_STORM: case EM_LIGHTNING_LAND: case EM_VENOM_SWAMP: case EM_CONFLAGRATION: case EM_TERRA_DRIVE: flag|=1;//Set flag to 1 to prevent deleting ammo (it will be deleted on group-delete). case GS_GROUNDDRIFT: //Ammo should be deleted right away. case GN_WALLOFTHORN: case GN_DEMONIC_FIRE: skill_unitsetting(src,skill_id,skill_lv,x,y,0); break; case WZ_ICEWALL: flag|=1; if(skill_unitsetting(src,skill_id,skill_lv,x,y,0)) clif_skill_poseffect(src,skill_id,skill_lv,x,y,tick); break; case RG_GRAFFITI: /* Graffiti [Valaris] */ skill_unitsetting(src,skill_id,skill_lv,x,y,0); flag|=1; break; case NPC_EARTHQUAKE: clif_skill_damage(src, src, tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, DMG_SINGLE); skill_unitsetting(src, skill_id, skill_lv, x, y, 0); break; #ifndef RENEWAL case HP_BASILICA: if( sc->getSCE(SC_BASILICA) ) { status_change_end(src, SC_BASILICA); // Cancel Basilica and return so requirement isn't consumed again return 0; } else { // Create Basilica. Start SC on caster. Unit timer start SC on others. if( map_getcell(src->m, x, y, CELL_CHKLANDPROTECTOR) ) { clif_skill_fail(sd,skill_id,USESKILL_FAIL,0); return 0; } skill_clear_unitgroup(src); skill_unitsetting(src,skill_id,skill_lv,x,y,0); flag|=1; } break; #endif #ifndef RENEWAL case CG_HERMODE: skill_clear_unitgroup(src); if ((sg = skill_unitsetting(src,skill_id,skill_lv,x,y,0))) sc_start4(src,src,SC_DANCING,100, skill_id,0,skill_lv,sg->group_id,skill_get_time(skill_id,skill_lv)); flag|=1; #endif break; case RG_CLEANER: // [Valaris] i = skill_get_splash(skill_id, skill_lv); map_foreachinallarea(skill_graffitiremover,src->m,x-i,y-i,x+i,y+i,BL_SKILL,1); break; case SO_WARMER: case SO_CLOUD_KILL: case NPC_CLOUD_KILL: flag |= (skill_id == SO_WARMER) ? 8 : 4; skill_unitsetting(src,skill_id,skill_lv,x,y,0); break; case SU_CN_POWDERING: case SU_NYANGGRASS: if (sd && pc_checkskill(sd, SU_SPIRITOFLAND)) { if (skill_id == SU_CN_POWDERING) sc_start(src, src, SC_DORAM_FLEE2, 100, sd->status.base_level * 10 / 12, skill_get_time(SU_SPIRITOFLAND, 1)); else sc_start(src, src, SC_DORAM_MATK, 100, sd->status.base_level, skill_get_time(SU_SPIRITOFLAND, 1)); } flag |= 1; skill_unitsetting(src, skill_id, skill_lv, x, y, 0); break; case SU_CN_METEOR: if (sd) { if (pc_search_inventory(sd, skill_db.find(SU_CN_METEOR)->require.itemid[0]) >= 0) skill_id = SU_CN_METEOR2; if (pc_checkskill(sd, SU_SPIRITOFLAND)) sc_start(src, src, SC_DORAM_SVSP, 100, 100, skill_get_time(SU_SPIRITOFLAND, 1)); } // Fall through case WZ_METEOR: { int area = skill_get_splash(skill_id, skill_lv); short tmpx = 0, tmpy = 0; for (i = 1; i <= skill_get_time(skill_id, skill_lv) / skill_get_unit_interval(skill_id); i++) { // Creates a random Cell in the Splash Area tmpx = x - area + rnd() % (area * 2 + 1); tmpy = y - area + rnd() % (area * 2 + 1); skill_unitsetting(src, skill_id, skill_lv, tmpx, tmpy, flag + i * skill_get_unit_interval(skill_id)); } } break; case AL_WARP: if(sd) { clif_skill_warppoint(sd, skill_id, skill_lv, sd->status.save_point.map, (skill_lv >= 2) ? sd->status.memo_point[0].map : "", (skill_lv >= 3) ? sd->status.memo_point[1].map : "", (skill_lv >= 4) ? sd->status.memo_point[2].map : "" ); } if( sc && sc->getSCE(SC_CURSEDCIRCLE_ATKER) ) //Should only remove after the skill has been casted. status_change_end(src,SC_CURSEDCIRCLE_ATKER); return 0; // not to consume item. case MO_BODYRELOCATION: if(sc && (sc->getSCE(SC_SPIDERWEB) || sc->getSCE(SC_ANKLE))) break; if (unit_movepos(src, x, y, 2, 1)) { #if PACKETVER >= 20111005 clif_snap(src, src->x, src->y); #else clif_skill_poseffect(src,skill_id,skill_lv,src->x,src->y,tick); #endif if (sd) skill_blockpc_start (sd, MO_EXTREMITYFIST, 2000); } break; case NJ_SHADOWJUMP: if( map_getcell(src->m,x,y,CELL_CHKREACH) && skill_check_unit_movepos(5, src, x, y, 1, 0) ) //You don't move on GVG grounds. clif_blown(src); status_change_end(src, SC_HIDING); break; case AM_SPHEREMINE: case AM_CANNIBALIZE: { int summons[5] = { MOBID_G_MANDRAGORA, MOBID_G_HYDRA, MOBID_G_FLORA, MOBID_G_PARASITE, MOBID_G_GEOGRAPHER }; int class_ = skill_id==AM_SPHEREMINE?MOBID_MARINE_SPHERE:summons[skill_lv-1]; enum mob_ai ai = (skill_id == AM_SPHEREMINE) ? AI_SPHERE : AI_FLORA; struct mob_data *md; // Correct info, don't change any of this! [celest] md = mob_once_spawn_sub(src, src->m, x, y, status_get_name(src), class_, "", SZ_SMALL, ai); if (md) { md->master_id = src->id; md->special_state.ai = ai; if( md->deletetimer != INVALID_TIMER ) delete_timer(md->deletetimer, mob_timer_delete); md->deletetimer = add_timer (gettick() + skill_get_time(skill_id,skill_lv), mob_timer_delete, md->bl.id, 0); mob_spawn (md); //Now it is ready for spawning. } } break; // Slim Pitcher [Celest] case CR_SLIMPITCHER: if (sd) { int i_lv = 0, j = 0; struct s_skill_condition require = skill_get_requirement(sd, skill_id, skill_lv); i_lv = skill_lv%11 - 1; j = pc_search_inventory(sd, require.itemid[i_lv]); if (j < 0 || require.itemid[i_lv] <= 0 || sd->inventory_data[j] == NULL || sd->inventory.u.items_inventory[j].amount < require.amount[i_lv]) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); return 1; } potion_flag = 1; potion_hp = 0; potion_sp = 0; run_script(sd->inventory_data[j]->script,0,sd->bl.id,0); potion_flag = 0; //Apply skill bonuses i_lv = pc_checkskill(sd,CR_SLIMPITCHER)*10 + pc_checkskill(sd,AM_POTIONPITCHER)*10 + pc_checkskill(sd,AM_LEARNINGPOTION)*5 + pc_skillheal_bonus(sd, skill_id); potion_hp = potion_hp * (100+i_lv)/100; potion_sp = potion_sp * (100+i_lv)/100; // Final heal increased by HPlus. // Is this the right place for this??? [Rytech] // Can HPlus also affect SP recovery??? status_data *sstatus = status_get_status_data(src); if (sstatus && sstatus->hplus > 0) { potion_hp += potion_hp * sstatus->hplus / 100; potion_sp += potion_sp * sstatus->hplus / 100; } if(potion_hp > 0 || potion_sp > 0) { i_lv = skill_get_splash(skill_id, skill_lv); map_foreachinallarea(skill_area_sub, src->m,x-i_lv,y-i_lv,x+i_lv,y+i_lv,BL_CHAR, src,skill_id,skill_lv,tick,flag|BCT_PARTY|BCT_GUILD|1, skill_castend_nodamage_id); } } else { struct item_data *item = itemdb_search(skill_db.find(skill_id)->require.itemid[skill_lv - 1]); int id = skill_get_max(CR_SLIMPITCHER) * 10; potion_flag = 1; potion_hp = 0; potion_sp = 0; run_script(item->script,0,src->id,0); potion_flag = 0; potion_hp = potion_hp * (100+id)/100; potion_sp = potion_sp * (100+id)/100; if(potion_hp > 0 || potion_sp > 0) { id = skill_get_splash(skill_id, skill_lv); map_foreachinallarea(skill_area_sub, src->m,x-id,y-id,x+id,y+id,BL_CHAR, src,skill_id,skill_lv,tick,flag|BCT_PARTY|BCT_GUILD|1, skill_castend_nodamage_id); } } break; case HW_GANBANTEIN: if (rnd()%100 < 80) { int dummy = 1; clif_skill_poseffect(src,skill_id,skill_lv,x,y,tick); i = skill_get_splash(skill_id, skill_lv); map_foreachinallarea(skill_cell_overlap, src->m, x-i, y-i, x+i, y+i, BL_SKILL, HW_GANBANTEIN, &dummy, src); } else { if (sd) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); return 1; } break; #ifndef RENEWAL case HW_GRAVITATION: if ((sg = skill_unitsetting(src,skill_id,skill_lv,x,y,0))) sc_start4(src,src,type,100,skill_lv,0,BCT_SELF,sg->group_id,skill_get_time(skill_id,skill_lv)); flag|=1; break; #endif // Plant Cultivation [Celest] case CR_CULTIVATION: if (sd) { if( map_count_oncell(src->m,x,y,BL_CHAR,0) > 0 ) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); return 1; } clif_skill_poseffect(src,skill_id,skill_lv,x,y,tick); if (rnd()%100 < 50) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); } else { TBL_MOB* md = NULL; int t, mob_id; if (skill_lv == 1) mob_id = MOBID_BLACK_MUSHROOM + rnd() % 2; else { int rand_val = rnd() % 100; if (rand_val < 30) mob_id = MOBID_GREEN_PLANT; else if (rand_val < 55) mob_id = MOBID_RED_PLANT; else if (rand_val < 80) mob_id = MOBID_YELLOW_PLANT; else if (rand_val < 90) mob_id = MOBID_WHITE_PLANT; else if (rand_val < 98) mob_id = MOBID_BLUE_PLANT; else mob_id = MOBID_SHINING_PLANT; } md = mob_once_spawn_sub(src, src->m, x, y, "--ja--", mob_id, "", SZ_SMALL, AI_NONE); if (!md) break; if ((t = skill_get_time(skill_id, skill_lv)) > 0) { if( md->deletetimer != INVALID_TIMER ) delete_timer(md->deletetimer, mob_timer_delete); md->deletetimer = add_timer (tick + t, mob_timer_delete, md->bl.id, 0); } mob_spawn(md); } } break; case SG_SUN_WARM: case SG_MOON_WARM: case SG_STAR_WARM: skill_clear_unitgroup(src); if ((sg = skill_unitsetting(src,skill_id,skill_lv,src->x,src->y,0))) sc_start4(src,src,type,100,skill_lv,0,0,sg->group_id,skill_get_time(skill_id,skill_lv)); flag|=1; break; case PA_GOSPEL: if (sce && sce->val4 == BCT_SELF) { status_change_end(src, SC_GOSPEL); return 0; } else { sg = skill_unitsetting(src,skill_id,skill_lv,src->x,src->y,0); if (!sg) break; if (sce) status_change_end(src, type); //Was under someone else's Gospel. [Skotlex] sc_start4(src,src,type,100,skill_lv,0,sg->group_id,BCT_SELF,skill_get_time(skill_id,skill_lv)); clif_skill_poseffect(src, skill_id, skill_lv, 0, 0, tick); // PA_GOSPEL music packet } break; case NJ_TATAMIGAESHI: if (skill_unitsetting(src,skill_id,skill_lv,src->x,src->y,0)) sc_start(src,src,type,100,skill_lv,skill_get_time2(skill_id,skill_lv)); break; case AM_RESURRECTHOMUN: //[orn] if (sd) { if (!hom_ressurect(sd, 20*skill_lv, x, y)) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); break; } } break; case AC_SHOWER: status_change_end(src, SC_CAMOUFLAGE); case MA_SHOWER: case NC_COLDSLOWER: case RK_DRAGONBREATH: case RK_DRAGONBREATH_WATER: case NPC_DRAGONBREATH: case WL_FROSTMISTY: case RL_HAMMER_OF_GOD: // Cast center might be relevant later (e.g. for knockback direction) skill_area_temp[4] = x; skill_area_temp[5] = y; i = skill_get_splash(skill_id,skill_lv); map_foreachinarea(skill_area_sub,src->m,x-i,y-i,x+i,y+i,BL_CHAR|BL_SKILL,src,skill_id,skill_lv,tick,flag|BCT_ENEMY|1,skill_castend_damage_id); break; case SO_ARRULLO: i = skill_get_splash(skill_id,skill_lv); map_foreachinallarea(skill_area_sub,src->m,x-i,y-i,x+i,y+i,BL_CHAR, src, skill_id, skill_lv, tick, flag|BCT_ENEMY|1, skill_castend_nodamage_id); break; case GC_POISONSMOKE: if( !(sc && sc->getSCE(SC_POISONINGWEAPON)) ) { if( sd ) clif_skill_fail(sd,skill_id,USESKILL_FAIL_GC_POISONINGWEAPON,0); return 0; } clif_skill_damage(src,src,tick,status_get_amotion(src),0,-30000,1,skill_id,skill_lv,DMG_SINGLE); skill_unitsetting(src, skill_id, skill_lv, x, y, flag); break; case AB_EPICLESIS: if( (sg = skill_unitsetting(src, skill_id, skill_lv, x, y, 0)) ) { i = skill_get_splash(skill_id, skill_lv); map_foreachinallarea(skill_area_sub, src->m, x - i, y - i, x + i, y + i, BL_CHAR, src, ALL_RESURRECTION, 1, tick, flag|BCT_NOENEMY|1,skill_castend_nodamage_id); } break; case WL_EARTHSTRAIN: { int w, wave = skill_lv + 4, dir = map_calc_dir(src,x,y); int sx = x = src->x, sy = y = src->y; // Store first caster's location to avoid glitch on unit setting for( w = 1; w <= wave; w++ ) { switch( dir ){ case 0: case 1: case 7: sy = y + w; break; case 3: case 4: case 5: sy = y - w; break; case 2: sx = x - w; break; case 6: sx = x + w; break; } skill_addtimerskill(src,gettick() + (140 * w),0,sx,sy,skill_id,skill_lv,dir,flag&2); } } break; case RA_DETONATOR: i = skill_get_splash(skill_id, skill_lv); map_foreachinallarea(skill_detonator, src->m, x-i, y-i, x+i, y+i, BL_SKILL, src); clif_skill_damage(src, src, tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, DMG_SINGLE); break; case NC_NEUTRALBARRIER: case NC_STEALTHFIELD: if( (sc->getSCE(SC_NEUTRALBARRIER_MASTER) && skill_id == NC_NEUTRALBARRIER) || (sc->getSCE(SC_STEALTHFIELD_MASTER) && skill_id == NC_STEALTHFIELD) ) { skill_clear_unitgroup(src); return 0; } skill_clear_unitgroup(src); // To remove previous skills - cannot used combined if( (sg = skill_unitsetting(src,skill_id,skill_lv,src->x,src->y,0)) != NULL ) { sc_start2(src,src,skill_id == NC_NEUTRALBARRIER ? SC_NEUTRALBARRIER_MASTER : SC_STEALTHFIELD_MASTER,100,skill_lv,sg->group_id,skill_get_time(skill_id,skill_lv)); } break; case NC_SILVERSNIPER: { struct mob_data *md; md = mob_once_spawn_sub(src, src->m, x, y, status_get_name(src), MOBID_SILVERSNIPER, "", SZ_SMALL, AI_NONE); if( md ) { md->master_id = src->id; md->special_state.ai = AI_FAW; if( md->deletetimer != INVALID_TIMER ) delete_timer(md->deletetimer, mob_timer_delete); md->deletetimer = add_timer (gettick() + skill_get_time(skill_id, skill_lv), mob_timer_delete, md->bl.id, 0); mob_spawn(md); } } break; case NC_MAGICDECOY: if( sd ) clif_magicdecoy_list(sd,skill_lv,x,y); break; case SC_FEINTBOMB: { std::shared_ptr<s_skill_unit_group> group = skill_unitsetting(src,skill_id,skill_lv,x,y,0); // Set bomb on current Position if( group == nullptr || group->unit == nullptr ) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); return 1; } map_foreachinallrange(unit_changetarget, src, AREA_SIZE, BL_MOB, src, &group->unit->bl); // Release all targets against the caster skill_blown(src, src, skill_get_blewcount(skill_id, skill_lv), unit_getdir(src), BLOWN_IGNORE_NO_KNOCKBACK); // Don't stop the caster from backsliding if special_state.no_knockback is active clif_skill_nodamage(src, src, skill_id, skill_lv, 0); sc_start(src, src, type, 100, skill_lv, skill_get_time(skill_id, skill_lv)); } break; case SC_ESCAPE: skill_unitsetting(src, skill_id, skill_lv, x, y, 0); skill_blown(src, src, skill_get_blewcount(skill_id, skill_lv), unit_getdir(src), BLOWN_IGNORE_NO_KNOCKBACK); // Don't stop the caster from backsliding if special_state.no_knockback is active clif_skill_nodamage(src,src,skill_id,skill_lv,1); flag |= 1; break; case LG_BANDING: if( sc && sc->getSCE(SC_BANDING) ) status_change_end(src,SC_BANDING); else if( (sg = skill_unitsetting(src,skill_id,skill_lv,src->x,src->y,0)) != NULL ) sc_start4(src,src,SC_BANDING,100,skill_lv,0,0,sg->group_id,skill_get_time(skill_id,skill_lv)); clif_skill_nodamage(src,src,skill_id,skill_lv,1); break; case WM_DOMINION_IMPULSE: i = skill_get_splash(skill_id, skill_lv); map_foreachinallarea(skill_active_reverberation, src->m, x-i, y-i, x+i,y+i,BL_SKILL); break; case WM_GREAT_ECHO: i = skill_get_splash(skill_id,skill_lv); map_foreachinarea(skill_area_sub,src->m,x-i,y-i,x+i,y+i,BL_CHAR,src,skill_id,skill_lv,tick,flag|BCT_ENEMY|1,skill_castend_damage_id); break; case WM_SEVERE_RAINSTORM: flag |= 1; if (sd) sd->canequip_tick = tick + skill_get_time(skill_id, skill_lv); // Can't switch equips for the duration of the skill. skill_unitsetting(src,skill_id,skill_lv,x,y,0); break; case GN_CRAZYWEED: { int area = skill_get_splash(GN_CRAZYWEED_ATK, skill_lv); for( i = 0; i < 3 + (skill_lv/2); i++ ) { int x1 = x - area + rnd()%(area * 2 + 1); int y1 = y - area + rnd()%(area * 2 + 1); skill_addtimerskill(src,tick+i*150,0,x1,y1,GN_CRAZYWEED_ATK,skill_lv,-1,0); } } break; case GN_FIRE_EXPANSION: { struct unit_data* ud = unit_bl2ud(src); if (!ud) break; auto predicate = [x, y](std::shared_ptr<s_skill_unit_group> sg) { auto* su = sg->unit; return sg->skill_id == GN_DEMONIC_FIRE && distance_xy(x, y, su->bl.x, su->bl.y) < 4; }; auto it = std::find_if(ud->skillunits.begin(), ud->skillunits.end(), predicate); if (it != ud->skillunits.end()) { auto* unit_group = it->get(); skill_unit* su = unit_group->unit; switch (skill_lv) { case 1: { // TODO: int duration = (int)(unit_group->limit - DIFF_TICK(tick, unit_group->tick)); skill_delunit(su); skill_unitsetting(src, GN_DEMONIC_FIRE, 1, x, y, duration); flag |= 1; } break; case 2: map_foreachinallarea(skill_area_sub, src->m, su->bl.x - 2, su->bl.y - 2, su->bl.x + 2, su->bl.y + 2, BL_CHAR, src, GN_DEMONIC_FIRE, skill_lv + 20, tick, flag | BCT_ENEMY | SD_LEVEL | 1, skill_castend_damage_id); if (su != NULL) skill_delunit(su); break; case 3: skill_delunit(su); skill_unitsetting(src, GN_FIRE_EXPANSION_SMOKE_POWDER, 1, x, y, 0); flag |= 1; break; case 4: skill_delunit(su); skill_unitsetting(src, GN_FIRE_EXPANSION_TEAR_GAS, 1, x, y, 0); flag |= 1; break; case 5: { uint16 acid_lv = 5; // Cast at Acid Demonstration at level 5 unless the user has a higher level learned. if (sd && pc_checkskill(sd, CR_ACIDDEMONSTRATION) > 5) acid_lv = pc_checkskill(sd, CR_ACIDDEMONSTRATION); map_foreachinallarea(skill_area_sub, src->m, su->bl.x - 2, su->bl.y - 2, su->bl.x + 2, su->bl.y + 2, BL_CHAR, src, GN_FIRE_EXPANSION_ACID, acid_lv, tick, flag | BCT_ENEMY | SD_LEVEL | 1, skill_castend_damage_id); if (su != NULL) skill_delunit(su); } break; } } } break; case SO_FIREWALK: case SO_ELECTRICWALK: case NPC_FIREWALK: case NPC_ELECTRICWALK: if( sc && sc->getSCE(type) ) status_change_end(src,type); sc_start2(src, src, type, 100, skill_id, skill_lv, skill_get_time(skill_id, skill_lv)); break; case KO_MAKIBISHI: for( i = 0; i < (skill_lv+2); i++ ) { x = src->x - 1 + rnd()%3; y = src->y - 1 + rnd()%3; skill_unitsetting(src,skill_id,skill_lv,x,y,0); } break; case KO_MUCHANAGE: { struct status_data *sstatus; int rate = 0; sstatus = status_get_status_data(src); i = skill_get_splash(skill_id,skill_lv); rate = (100 - (1000 / (sstatus->dex + sstatus->luk) * 5)) * (skill_lv / 2 + 5) / 10; if( rate < 0 ) rate = 0; skill_area_temp[0] = map_foreachinarea(skill_area_sub,src->m,x-i,y-i,x+i,y+i,BL_CHAR,src,skill_id,skill_lv,tick,BCT_ENEMY,skill_area_sub_count); if( rnd()%100 < rate ) map_foreachinarea(skill_area_sub,src->m,x-i,y-i,x+i,y+i,BL_CHAR,src,skill_id,skill_lv,tick,flag|BCT_ENEMY|1,skill_castend_damage_id); } break; case RL_FALLEN_ANGEL: if (unit_movepos(src,x,y,1,1)) { clif_snap(src, src->x, src->y); sc_start(src, src, type, 100, skill_id, skill_get_time(skill_id, skill_lv)); } else { if (sd) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); } break; case RL_FIRE_RAIN: { int w, wave = skill_lv + 5, dir = map_calc_dir(src,x,y); int sx = x = src->x, sy = y = src->y; for (w = 0; w <= wave; w++) { switch (dir) { case DIR_NORTH: case DIR_NORTHWEST: case DIR_NORTHEAST: sy = y + w; break; case DIR_WEST: sx = x - w; break; case DIR_SOUTHWEST: case DIR_SOUTH: case DIR_SOUTHEAST: sy = y - w; break; case DIR_EAST: sx = x + w; break; } skill_addtimerskill(src,gettick() + (80 * w),0,sx,sy,skill_id,skill_lv,dir,flag); } } break; case NPC_MAGMA_ERUPTION: case NC_MAGMA_ERUPTION: // 1st, AoE 'slam' damage i = skill_get_splash(skill_id, skill_lv); map_foreachinarea(skill_area_sub, src->m, x-i, y-i, x+i, y+i, BL_CHAR, src, skill_id, skill_lv, tick, flag|BCT_ENEMY|SD_ANIMATION|1, skill_castend_damage_id); // 2nd, AoE 'eruption' unit skill_addtimerskill(src,tick + status_get_amotion(src) * 2,0,x,y,skill_id,skill_lv,0,flag); break; case SU_LOPE: { uint8 dir = map_calc_dir(src, x, y); // Fails on noteleport maps, except for GvG and BG maps if (map_getmapflag(src->m, MF_NOTELEPORT) && !(map_getmapflag(src->m, MF_BATTLEGROUND) || map_flag_gvg2(src->m))) { x = src->x; y = src->y; } clif_skill_nodamage(src, src, skill_id, skill_lv, 1); if (!map_count_oncell(src->m, x, y, BL_PC|BL_NPC|BL_MOB, 0) && map_getcell(src->m, x, y, CELL_CHKREACH) && unit_movepos(src, x, y, 1, 0)) clif_blown(src); } break; case AG_ASTRAL_STRIKE: i = skill_get_splash(skill_id, skill_lv); map_foreachinallarea(skill_area_sub, src->m, x-i, y-i, x+i, y+i, BL_CHAR, src, skill_id, skill_lv, tick, flag|BCT_ENEMY|1, skill_castend_damage_id); flag |= 1; skill_unitsetting(src, skill_id, skill_lv, x, y, 0); break; case AG_VIOLENT_QUAKE: case AG_ALL_BLOOM: { int area = skill_get_splash(skill_id, skill_lv); int unit_time = skill_get_time(skill_id, skill_lv); int unit_interval = skill_get_unit_interval(skill_id); uint16 tmpx = 0, tmpy = 0, sub_skill = 0, climax_lv = 0; // Grab Climax's effect level if active. // This affects the behavior of certain skills in certain ways. if (sc && sc->getSCE(SC_CLIMAX)) climax_lv = sc->getSCE(SC_CLIMAX)->val1; if (skill_id == AG_VIOLENT_QUAKE) { sub_skill = AG_VIOLENT_QUAKE_ATK; // Fixes rising rocks spawn area to 7x7. if (climax_lv == 5) area = 3; } else { // AG_ALL_BLOOM sub_skill = AG_ALL_BLOOM_ATK; if (climax_lv == 1) { // Rose buds spawn at double the speed. unit_time /= 2; unit_interval /= 2; } } // Displays the earthquake / flower garden. skill_unitsetting(src, skill_id, skill_lv, x, y, 0); if (climax_lv == 4) { // Deals no damage and instead inflicts a status on the enemys in range. i = skill_get_splash(skill_id, skill_lv); map_foreachinallarea(skill_area_sub, src->m, x - i, y - i, x + i, y + i, BL_CHAR, src, skill_id, skill_lv, tick, flag | BCT_ENEMY | 1, skill_castend_nodamage_id); } else for (i = 1; i <= unit_time / unit_interval; i++) { // Spawn the rising rocks / rose buds on random spots at seperate intervals tmpx = x - area + rnd() % (area * 2 + 1); tmpy = y - area + rnd() % (area * 2 + 1); skill_unitsetting(src, sub_skill, skill_lv, tmpx, tmpy, flag + i * unit_interval); if ((skill_id == AG_VIOLENT_QUAKE && climax_lv == 1) || (skill_id == AG_ALL_BLOOM && climax_lv == 2)) { // Spwan a 2nd rising rock / rose bud along with the 1st one. tmpx = x - area + rnd() % (area * 2 + 1); tmpy = y - area + rnd() % (area * 2 + 1); skill_unitsetting(src, sub_skill, skill_lv, tmpx, tmpy, flag + i * unit_interval); } } // One final attack the size of the flower garden is dealt after // all rose buds explode if Climax level 5 is active. if (skill_id == AG_ALL_BLOOM && climax_lv == 5) skill_unitsetting(src, AG_ALL_BLOOM_ATK2, skill_lv, x, y, flag + i * unit_interval); } break; default: ShowWarning("skill_castend_pos2: Unknown skill used:%d\n",skill_id); return 1; } if( sc && sc->getSCE(SC_CURSEDCIRCLE_ATKER) ) //Should only remove after the skill has been casted. status_change_end(src,SC_CURSEDCIRCLE_ATKER); if( sd ) {// ensure that the skill last-cast tick is recorded sd->canskill_tick = gettick(); if( sd->state.arrow_atk && !(flag&1) ) {// consume arrow if this is a ground skill battle_consume_ammo(sd, skill_id, skill_lv); } skill_onskillusage(sd, NULL, skill_id, tick); // perform skill requirement consumption skill_consume_requirement(sd,skill_id,skill_lv,2); } return 0; } /*========================================== * *------------------------------------------*/ int skill_castend_map (map_session_data *sd, uint16 skill_id, const char *mapname) { nullpo_ret(sd); //Simplify skill_failed code. #define skill_failed(sd) { sd->menuskill_id = sd->menuskill_val = 0; } if(skill_id != sd->menuskill_id) return 0; if( sd->bl.prev == NULL || pc_isdead(sd) ) { skill_failed(sd); return 0; } if( sd->sc.cant.cast ) { skill_failed(sd); return 0; } pc_stop_attack(sd); if(battle_config.skill_log && battle_config.skill_log&BL_PC) ShowInfo("PC %d skill castend skill =%d map=%s\n",sd->bl.id,skill_id,mapname); if(strcmp(mapname,"cancel")==0) { skill_failed(sd); return 0; } switch(skill_id) { case AL_TELEPORT: case ALL_ODINS_RECALL: //The storage window is closed automatically by the client when there's //any kind of map change, so we need to restore it automatically //bugreport:8027 if(strcmp(mapname,"Random") == 0) pc_randomwarp(sd,CLR_TELEPORT); else if (sd->menuskill_val > 1 || skill_id == ALL_ODINS_RECALL) //Need lv2 to be able to warp here. pc_setpos( sd, mapindex_name2id( sd->status.save_point.map ),sd->status.save_point.x, sd->status.save_point.y, CLR_TELEPORT ); clif_refresh_storagewindow(sd); break; case AL_WARP: { const struct s_point_str *p[4]; std::shared_ptr<s_skill_unit_group> group; int i, lv, wx, wy; int maxcount=0; int x,y; unsigned short mapindex; mapindex = mapindex_name2id((char*)mapname); if(!mapindex) { //Given map not found? clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); skill_failed(sd); return 0; } p[0] = &sd->status.save_point; p[1] = &sd->status.memo_point[0]; p[2] = &sd->status.memo_point[1]; p[3] = &sd->status.memo_point[2]; if((maxcount = skill_get_maxcount(skill_id, sd->menuskill_val)) > 0) { unit_skillunit_maxcount(sd->ud, skill_id, maxcount); if (maxcount == 0) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); skill_failed(sd); return 0; } } lv = sd->skillitem==skill_id?sd->skillitemlv:pc_checkskill(sd,skill_id); wx = sd->menuskill_val>>16; wy = sd->menuskill_val&0xffff; if( lv <= 0 ) return 0; if( lv > 4 ) lv = 4; // crash prevention // check if the chosen map exists in the memo list ARR_FIND( 0, lv, i, strncmp( p[i]->map, mapname, sizeof( p[i]->map ) ) == 0 ); if( i < lv ) { x=p[i]->x; y=p[i]->y; } else { skill_failed(sd); return 0; } if(!skill_check_condition_castend(sd, sd->menuskill_id, lv)) { // This checks versus skill_id/skill_lv... skill_failed(sd); return 0; } skill_consume_requirement(sd,sd->menuskill_id,lv,2); sd->skillitem = sd->skillitemlv = sd->skillitem_keep_requirement = 0; // Clear data that's skipped in 'skill_castend_pos' [Inkfish] if((group=skill_unitsetting(&sd->bl,skill_id,lv,wx,wy,0))==nullptr) { skill_failed(sd); return 0; } group->val1 = (group->val1<<16)|(short)0; // record the destination coordinates group->val2 = (x<<16)|y; group->val3 = mapindex; } break; } sd->menuskill_id = sd->menuskill_val = 0; return 0; #undef skill_failed } /// transforms 'target' skill unit into dissonance (if conditions are met) static int skill_dance_overlap_sub(struct block_list* bl, va_list ap) { struct skill_unit* target = (struct skill_unit*)bl; struct skill_unit* src = va_arg(ap, struct skill_unit*); int flag = va_arg(ap, int); if (src == target) return 0; if (!target->group || !(target->group->state.song_dance&0x1)) return 0; if (!(target->val2 & src->val2 & ~(1 << UF_ENSEMBLE))) //They don't match (song + dance) is valid. return 0; if (flag) //Set dissonance target->val2 |= (1 << UF_ENSEMBLE); //Add ensemble to signal this unit is overlapping. else //Remove dissonance target->val2 &= ~(1 << UF_ENSEMBLE); skill_getareachar_skillunit_visibilty(target, AREA); return 1; } //Does the song/dance overlapping -> dissonance check. [Skotlex] //When flag is 0, this unit is about to be removed, cancel the dissonance effect //When 1, this unit has been positioned, so start the cancel effect. int skill_dance_overlap(struct skill_unit* unit, int flag) { // (^~_~^) LGP Start return 0; // (^~_~^) LGP End if (!unit || !unit->group || !(unit->group->state.song_dance&0x1)) return 0; if (!flag && !(unit->val2&(1 << UF_ENSEMBLE))) return 0; //Nothing to remove, this unit is not overlapped. if (unit->val1 != unit->group->skill_id) { //Reset state unit->val1 = unit->group->skill_id; unit->val2 &= ~(1 << UF_ENSEMBLE); } return map_foreachincell(skill_dance_overlap_sub, unit->bl.m,unit->bl.x,unit->bl.y,BL_SKILL, unit,flag); } /** * Converts this group information so that it is handled as a Dissonance or Ugly Dance cell. * @param unit Skill unit data (from BA_DISSONANCE or DC_UGLYDANCE) * @param flag 0 Convert * @param flag 1 Revert * @return true success * @TODO: This should be completely removed later and rewritten * The entire execution of the overlapping songs instances is dirty and hacked together * Overlapping cells should be checked on unit entry, not infinitely loop checked causing 1000's of executions a song/dance */ static bool skill_dance_switch(struct skill_unit* unit, int flag) { static int prevflag = 1; // by default the backup is empty static s_skill_unit_group backup; std::shared_ptr<s_skill_unit_group> group; if( unit == nullptr || (group = unit->group) == nullptr ) return false; //val2&(1 << UF_ENSEMBLE) is a hack to indicate dissonance if ( !((group->state.song_dance&0x1) && (unit->val2&(1 << UF_ENSEMBLE))) ) return false; if( flag == prevflag ) { //Protection against attempts to read an empty backup/write to a full backup ShowError("skill_dance_switch: Attempted to %s (skill_id=%d, skill_lv=%d, src_id=%d).\n", flag ? "read an empty backup" : "write to a full backup", group->skill_id, group->skill_lv, group->src_id); return false; } prevflag = flag; if (!flag) { //Transform uint16 skill_id = unit->val2&(1 << UF_SONG) ? BA_DISSONANCE : DC_UGLYDANCE; // backup backup.skill_id = group->skill_id; backup.skill_lv = group->skill_lv; backup.unit_id = group->unit_id; backup.target_flag = group->target_flag; backup.bl_flag = group->bl_flag; backup.interval = group->interval; // replace group->skill_id = skill_id; group->skill_lv = 1; group->unit_id = skill_get_unit_id(skill_id); group->target_flag = skill_get_unit_target(skill_id); group->bl_flag = skill_get_unit_bl_target(skill_id); group->interval = skill_get_unit_interval(skill_id); } else { //Restore group->skill_id = backup.skill_id; group->skill_lv = backup.skill_lv; group->unit_id = backup.unit_id; group->target_flag = backup.target_flag; group->bl_flag = backup.bl_flag; group->interval = backup.interval; } return true; } /** * Initializes and sets a ground skill / skill unit. Usually called after skill_casted_pos() or skill_castend_map() * @param src Object that triggers the skill * @param skill_id Skill ID * @param skill_lv Skill level of used skill * @param x Position x * @param y Position y * @param flag &1: Used to determine when the skill 'morphs' (Warp portal becomes active, or Fire Pillar becomes active) * xx_METEOR: flag &1 contains if the unit can cause curse, flag is also the duration of the unit in milliseconds * @return s_skill_unit_group */ std::shared_ptr<s_skill_unit_group> skill_unitsetting(struct block_list *src, uint16 skill_id, uint16 skill_lv, int16 x, int16 y, int flag) { std::shared_ptr<s_skill_unit_group> group; int i, val1 = 0, val2 = 0, val3 = 0; t_tick limit; int link_group_id = 0; int target, interval, range; t_itemid req_item = 0; struct s_skill_unit_layout *layout; map_session_data *sd; struct status_data *status; status_change *sc; int active_flag = 1; int subunt = 0; bool hidden = false; struct map_data *mapdata; nullpo_retr(nullptr, src); std::shared_ptr<s_skill_db> skill = skill_db.find(skill_id); mapdata = map_getmapdata(src->m); limit = skill_get_time3(mapdata, skill_id,skill_lv); range = skill_get_unit_range(skill_id,skill_lv); interval = skill->unit_interval; target = skill_get_unit_target(skill_id); layout = skill_get_unit_layout(skill_id,skill_lv,src,x,y); sd = BL_CAST(BL_PC, src); status = status_get_status_data(src); sc = status_get_sc(src); // for traps, firewall and fogwall - celest hidden = (skill->unit_flag[UF_HIDDENTRAP] && (battle_config.traps_setting == 2 || (battle_config.traps_setting == 1 && map_flag_vs(src->m)))); switch( skill_id ) { case MH_STEINWAND: val2 = 4 + skill_lv; val3 = 300 * skill_lv + 65 * ( status->int_ + status_get_lv(src) ) + status->max_sp; //nb hp break; case MG_SAFETYWALL: val2 = skill_lv + 1; #ifdef RENEWAL val3 = 300 * skill_lv + 65 * (status->int_ + status_get_lv(src)) + status->max_sp; #endif break; case MG_FIREWALL: if(sc && sc->getSCE(SC_VIOLENTGALE)) limit = limit*3/2; val2 = 4+skill_lv; break; case AL_WARP: val1=skill_lv+6; if(!(flag&1)) limit=2000; else // previous implementation (not used anymore) { //Warp Portal morphing to active mode, extract relevant data from src. [Skotlex] if( src->type != BL_SKILL ) return nullptr; group = ((TBL_SKILL*)src)->group; src = map_id2bl(group->src_id); if( !src ) return nullptr; val2 = group->val2; //Copy the (x,y) position you warp to val3 = group->val3; //as well as the mapindex to warp to. } break; #ifndef RENEWAL case HP_BASILICA: val1 = src->id; // Store caster id. break; #endif case PR_SANCTUARY: case NPC_EVILLAND: val1=skill_lv+3; break; case WZ_METEOR: case SU_CN_METEOR: case SU_CN_METEOR2: limit = flag; flag = 0; // Flag should not influence anything else for these skills break; case WZ_FIREPILLAR: if( map_getcell(src->m, x, y, CELL_CHKLANDPROTECTOR) ) return nullptr; if((flag&1)!=0) limit=1000; val1=skill_lv+2; break; case WZ_QUAGMIRE: //The target changes to "all" if used in a gvg map. [Skotlex] case AM_DEMONSTRATION: if (battle_config.vs_traps_bctall && (src->type&battle_config.vs_traps_bctall) && map_flag_vs(src->m)) target = BCT_ALL; break; case HT_SKIDTRAP: case MA_SKIDTRAP: //Save position of caster val1 = ((src->x)<<16)|(src->y); case HT_ANKLESNARE: case HT_SHOCKWAVE: case HT_SANDMAN: case MA_SANDMAN: case HT_CLAYMORETRAP: case HT_LANDMINE: case MA_LANDMINE: case HT_FLASHER: case HT_FREEZINGTRAP: case MA_FREEZINGTRAP: case HT_BLASTMINE: case RA_ELECTRICSHOCKER: case RA_CLUSTERBOMB: case RA_MAGENTATRAP: case RA_COBALTTRAP: case RA_MAIZETRAP: case RA_VERDURETRAP: case RA_FIRINGTRAP: case RA_ICEBOUNDTRAP: case RL_B_TRAP: case SC_ESCAPE: { struct s_skill_condition req = skill_get_requirement(sd,skill_id,skill_lv); ARR_FIND(0, MAX_SKILL_ITEM_REQUIRE, i, req.itemid[i] && (req.itemid[i] == ITEMID_TRAP || req.itemid[i] == ITEMID_TRAP_ALLOY)); if( i != MAX_SKILL_ITEM_REQUIRE && req.itemid[i] ) req_item = req.itemid[i]; if (skill_id == RL_B_TRAP) // Target type should not change on GvG maps. break; if( battle_config.vs_traps_bctall && (src->type&battle_config.vs_traps_bctall) && map_flag_vs(src->m) ) target = BCT_ALL; } break; case SA_LANDPROTECTOR: case SA_VOLCANO: case SA_DELUGE: case SA_VIOLENTGALE: case SC_CHAOSPANIC: { std::shared_ptr<s_skill_unit_group> old_sg = skill_locate_element_field(src); if (old_sg != nullptr) { //HelloKitty confirmed that these are interchangeable, //so you can change element and not consume gemstones. if (( old_sg->skill_id == SA_VOLCANO || old_sg->skill_id == SA_DELUGE || old_sg->skill_id == SA_VIOLENTGALE ) && old_sg->limit > 0) { //Use the previous limit (minus the elapsed time) [Skotlex] limit = old_sg->limit - DIFF_TICK(gettick(), old_sg->tick); if (limit < 0) //This can happen... limit = skill_get_time(skill_id,skill_lv); } skill_clear_group(src,1); } break; } case BA_WHISTLE: val1 = skill_lv + status->agi / 10; // Flee increase val2 = (skill_lv + 1) / 2 + status->luk / 30; // Perfect dodge increase if (sd) { val1 += pc_checkskill(sd, BA_MUSICALLESSON) / 2; val2 += pc_checkskill(sd, BA_MUSICALLESSON) / 5; } break; case DC_HUMMING: val1 = 1 + 2 * skill_lv + status->dex / 10; // Hit increase if (sd) val1 += pc_checkskill(sd, DC_DANCINGLESSON); break; case BA_POEMBRAGI: val1 = 3 * skill_lv + status->dex / 10; // Casting time reduction //For some reason at level 10 the base delay reduction is 50%. val2 = (skill_lv < 10 ? 3 * skill_lv : 50) + status->int_ / 5; // After-cast delay reduction if (sd) { val1 += pc_checkskill(sd, BA_MUSICALLESSON); val2 += 2 * pc_checkskill(sd, BA_MUSICALLESSON); } break; case DC_DONTFORGETME: #ifdef RENEWAL val1 = 3 * skill_lv + status->dex / 15; // ASPD decrease val2 = 2 * skill_lv + status->agi / 20; // Movement speed adjustment. #else val1 = 5 + 3 * skill_lv + status->dex / 10; // ASPD decrease val2 = 5 + 3 * skill_lv + status->agi / 10; // Movement speed adjustment. #endif if (sd) { val1 += pc_checkskill(sd, DC_DANCINGLESSON); #ifdef RENEWAL val2 += pc_checkskill(sd, DC_DANCINGLESSON) / 2; #else val2 += pc_checkskill(sd, DC_DANCINGLESSON); #endif } val1 *= 10; //Because 10 is actually 1% aspd break; case DC_SERVICEFORYOU: val1 = 15 + skill_lv + (status->int_ / 10); // MaxSP percent increase val2 = 20 + 3 * skill_lv + (status->int_ / 10); // SP cost reduction if (sd) { val1 += pc_checkskill(sd, DC_DANCINGLESSON) / 2; val2 += pc_checkskill(sd, DC_DANCINGLESSON) / 2; } break; case BA_ASSASSINCROSS: if (sd) val1 = pc_checkskill(sd, BA_MUSICALLESSON) / 2; val1 += 5 + skill_lv + (status->agi / 20); val1 *= 10; // ASPD works with 1000 as 100% break; case DC_FORTUNEKISS: val1 = 10 + skill_lv + (status->luk / 10); // Critical increase val1 *= 10; //Because every 10 crit is an actual cri point. if (sd) val1 += 5 * pc_checkskill(sd, DC_DANCINGLESSON); break; case BD_DRUMBATTLEFIELD: val1 = (skill_lv+1)*25; //Atk increase val2 = (skill_lv+1)*2; //Def increase break; case BD_RINGNIBELUNGEN: val1 = (skill_lv+2)*25; //Atk increase break; case BD_RICHMANKIM: val1 = 25 + 11*skill_lv; //Exp increase bonus. break; case BD_SIEGFRIED: val1 = 55 + skill_lv*5; //Elemental Resistance val2 = skill_lv*10; //Status ailment resistance break; case WE_CALLPARTNER: if (sd) val1 = sd->status.partner_id; break; case WE_CALLPARENT: if (sd) { val1 = sd->status.father; val2 = sd->status.mother; } break; case WE_CALLBABY: if (sd) val1 = sd->status.child; break; case NJ_KAENSIN: skill_clear_group(src, 1); //Delete previous Kaensins/Suitons val2 = (skill_lv+1)/2 + 4; break; case NJ_SUITON: skill_clear_group(src, 1); break; case GS_GROUNDDRIFT: { // Ground Drift Element is decided when it's placed. int ele = skill_get_ele(skill_id, skill_lv); int element[5] = { ELE_WIND, ELE_DARK, ELE_POISON, ELE_WATER, ELE_FIRE }; if (ele == ELE_RANDOM) val1 = element[rnd()%5]; // Use random from available unit visual? else if (ele == ELE_ENDOWED) val1 = status_get_attack_sc_element(src,sc); else if (ele == ELE_WEAPON) { val1 = status->rhw.ele; if (sc && sc->getSCE(SC_ENCHANTARMS)) val1 = sc->getSCE(SC_ENCHANTARMS)->val1; } switch (val1) { case ELE_FIRE: subunt++; case ELE_WATER: subunt++; case ELE_POISON: subunt++; case ELE_DARK: subunt++; case ELE_WIND: break; default: subunt = rnd()%5; break; } break; } case GC_POISONSMOKE: if( !(sc && sc->getSCE(SC_POISONINGWEAPON)) ) return nullptr; val2 = sc->getSCE(SC_POISONINGWEAPON)->val2; // Type of Poison val3 = sc->getSCE(SC_POISONINGWEAPON)->val1; limit = skill_get_time(skill_id, skill_lv); break; case NPC_COMET: case WL_COMET: if (sc) { sc->comet_x = x; sc->comet_y = y; } break; case GD_LEADERSHIP: case GD_GLORYWOUNDS: case GD_SOULCOLD: case GD_HAWKEYES: limit = 1000000;//it doesn't matter break; case LG_BANDING: limit = -1; break; case WM_POEMOFNETHERWORLD: // Can't be placed on top of Land Protector. if( skill_id == WM_POEMOFNETHERWORLD && map_flag_gvg2(src->m) ) target = BCT_ALL; case WM_SEVERE_RAINSTORM: case SO_WATER_INSIGNIA: case SO_FIRE_INSIGNIA: case SO_WIND_INSIGNIA: case SO_EARTH_INSIGNIA: if( map_getcell(src->m, x, y, CELL_CHKLANDPROTECTOR) ) return nullptr; break; case SO_CLOUD_KILL: case NPC_CLOUD_KILL: skill_clear_group(src, 4); break; case SO_WARMER: skill_clear_group(src, 8); break; case SO_FIREWALK: case SO_ELECTRICWALK: limit = skill_get_time2(skill_id, skill_lv); break; case GN_WALLOFTHORN: // Turns to Firewall if( flag&1 ) limit = 3000; val3 = (x<<16)|y; break; case GN_DEMONIC_FIRE: if (flag) { // Fire Expansion level 1 limit = flag + 10000; flag = 0; } break; case GN_FIRE_EXPANSION_SMOKE_POWDER: case GN_FIRE_EXPANSION_TEAR_GAS: limit = ((sd ? pc_checkskill(sd,GN_DEMONIC_FIRE) : 1) + 1) * limit; break; case KO_ZENKAI: if (sd && sd->spiritcharm_type != CHARM_TYPE_NONE && sd->spiritcharm > 0) { val1 = sd->spiritcharm; val2 = sd->spiritcharm_type; limit = 6000 * val1; subunt = sd->spiritcharm_type - 1; pc_delspiritcharm(sd,sd->spiritcharm,sd->spiritcharm_type); } break; #ifndef RENEWAL case HW_GRAVITATION: if(sc && sc->getSCE(SC_GRAVITATION) && sc->getSCE(SC_GRAVITATION)->val3 == BCT_SELF) link_group_id = sc->getSCE(SC_GRAVITATION)->val4; break; #endif case SO_VACUUM_EXTREME: // Coordinates val1 = x; val2 = y; val3 = 0; // Suck target at n seconds. break; case MH_POISON_MIST: case MH_LAVA_SLIDE: skill_clear_group(src, 1); break; case MH_VOLCANIC_ASH: if (!map_flag_vs(src->m)) target = BCT_ENEMY; break; case AG_VIOLENT_QUAKE: case AG_ALL_BLOOM: if (sc && sc->getSCE(SC_CLIMAX)) { if (skill_id == AG_ALL_BLOOM && sc->getSCE(SC_CLIMAX)->val1 == 1) limit /= 2; // Rose buds spawn at double the speed, so rose garden duration must be halved. else if (sc->getSCE(SC_CLIMAX)->val1 == 4) limit = 3000; // Show main AoE for fixed duration on status giving effect. } break; case AG_VIOLENT_QUAKE_ATK: case AG_ALL_BLOOM_ATK: case AG_ALL_BLOOM_ATK2: limit = flag; flag = 0; if (skill_id == AG_VIOLENT_QUAKE_ATK && sc && sc->getSCE(SC_CLIMAX) && sc->getSCE(SC_CLIMAX)->val1 == 2) range = 4; // Rising rocks splash is increased to 9x9. break; case WH_DEEPBLINDTRAP: case WH_SOLIDTRAP: case WH_SWIFTTRAP: case WH_FLAMETRAP: limit += 3000 * (sd ? pc_checkskill(sd, WH_ADVANCED_TRAP) : 5); break; } // Init skill unit group group = skill_initunitgroup(src, layout->count, skill_id, skill_lv, (flag & 1 ? skill->unit_id2 : skill->unit_id) + subunt, limit, interval); if (group == nullptr) return nullptr; group->val1 = val1; group->val2 = val2; group->val3 = val3; group->link_group_id = link_group_id; group->target_flag = target; group->bl_flag = skill_get_unit_bl_target(skill_id); group->state.ammo_consume = (sd && sd->state.arrow_atk && skill_id != GS_GROUNDDRIFT); //Store if this skill needs to consume ammo. group->state.song_dance = (((skill->unit_flag[UF_DANCE] || skill->unit_flag[UF_SONG])?1:0)|(skill->unit_flag[UF_ENSEMBLE]?2:0)); //Signals if this is a song/dance/duet group->state.guildaura = ( skill_id >= GD_LEADERSHIP && skill_id <= GD_HAWKEYES )?1:0; group->item_id = req_item; // If tick is greater than current, do not invoke onplace function just yet. [Skotlex] if (DIFF_TICK(group->tick, gettick()) > SKILLUNITTIMER_INTERVAL) active_flag = 0; // Put message for Talkie Box & Graffiti if (skill_id == HT_TALKIEBOX || skill_id == RG_GRAFFITI) { group->valstr=(char *) aMalloc(MESSAGE_SIZE*sizeof(char)); if (sd) safestrncpy(group->valstr, sd->message, MESSAGE_SIZE); else //Eh... we have to write something here... even though mobs shouldn't use this. [Skotlex] safestrncpy(group->valstr, "Boo!", MESSAGE_SIZE); } // Dance skill if (group->state.song_dance) { if(sd) { sd->skill_id_dance = skill_id; sd->skill_lv_dance = skill_lv; } if ( sc_start4(src, src, SC_DANCING, 100, skill_id, group->group_id, skill_lv, (group->state.song_dance&2?BCT_SELF:0), limit+1000) && sd && group->state.song_dance&2 && skill_id != CG_HERMODE //Hermod is a encore with a warp! ) skill_check_pc_partner(sd, skill_id, &skill_lv, 1, 1); } // Set skill unit limit = group->limit; for( i = 0; i < layout->count; i++ ) { struct skill_unit *unit; int ux = x + layout->dx[i]; int uy = y + layout->dy[i]; int unit_val1 = skill_lv; int unit_val2 = 0; int alive = 1; // are the coordinates out of range? if( ux <= 0 || uy <= 0 || ux >= mapdata->xs || uy >= mapdata->ys ){ continue; } if( !group->state.song_dance && !map_getcell(src->m,ux,uy,CELL_CHKREACH) ) continue; // don't place skill units on walls (except for songs/dances/encores) if( battle_config.skill_wall_check && skill->unit_flag[UF_PATHCHECK] && !path_search_long(NULL,src->m,ux,uy,src->x,src->y,CELL_CHKWALL) ) continue; // no path between cell and caster switch( skill_id ) { // HP for Skill unit that can be damaged, see also skill_unit_ondamaged case HT_LANDMINE: case MA_LANDMINE: case HT_ANKLESNARE: case HT_SHOCKWAVE: case HT_SANDMAN: case MA_SANDMAN: case HT_FLASHER: case HT_FREEZINGTRAP: case MA_FREEZINGTRAP: case HT_SKIDTRAP: case MA_SKIDTRAP: case HT_CLAYMORETRAP: case HT_BLASTMINE: case SC_ESCAPE: unit_val1 = 3500; break; case MG_FIREWALL: case NJ_KAENSIN: unit_val2 = group->val2; break; case CR_GRANDCROSS: case NPC_GRANDDARKNESS: unit_val1 = (skill_get_time(skill_id, skill_lv) / interval); //Default: 950/300 = 3 hits break; case WZ_ICEWALL: unit_val1 = 200 + 200*skill_lv; unit_val2 = map_getcell(src->m, ux, uy, CELL_GETTYPE); break; case WZ_WATERBALL: //Check if there are cells that can be turned into waterball units if (!sd || map_getcell(src->m, ux, uy, CELL_CHKWATER) || (map_find_skill_unit_oncell(src, ux, uy, SA_DELUGE, NULL, 1)) != NULL || (map_find_skill_unit_oncell(src, ux, uy, NJ_SUITON, NULL, 1)) != NULL) break; //Turn water, deluge or suiton into waterball cell continue; case GS_DESPERADO: unit_val1 = abs(layout->dx[i]); unit_val2 = abs(layout->dy[i]); if (unit_val1 < 2 || unit_val2 < 2) { //Nearby cross, linear decrease with no diagonals if (unit_val2 > unit_val1) unit_val1 = unit_val2; if (unit_val1) unit_val1--; unit_val1 = 36 -12*unit_val1; } else //Diagonal edges unit_val1 = 28 -4*unit_val1 -4*unit_val2; if (unit_val1 < 1) unit_val1 = 1; unit_val2 = 0; break; case NPC_REVERBERATION: unit_val1 = 1 + skill_lv; break; case WM_POEMOFNETHERWORLD: unit_val1 = 1 + skill_lv; break; case GN_WALLOFTHORN: if (flag&1) // Turned become Firewall break; unit_val1 = 2000 + 2000 * skill_lv; // HP unit_val2 = 20; // Max hits break; case RL_B_TRAP: unit_val1 = 3500; unit_val2 = 0; break; default: if (group->state.song_dance&0x1) unit_val2 = (skill->unit_flag[UF_DANCE] ? (1 << UF_DANCE) : skill->unit_flag[UF_SONG] ? (1 << UF_SONG) : 0); //Store whether this is a song/dance break; } if (skill->unit_flag[UF_RANGEDSINGLEUNIT] && i == (layout->count / 2)) unit_val2 |= (1 << UF_RANGEDSINGLEUNIT); // center. if( sd && map_getcell(src->m, ux, uy, CELL_CHKMAELSTROM) ) //Does not recover SP from monster skills map_foreachincell(skill_maelstrom_suction,src->m,ux,uy,BL_SKILL,skill_id,skill_lv); // Check active cell to failing or remove current unit map_foreachincell(skill_cell_overlap,src->m,ux,uy,BL_SKILL,skill_id, &alive, src); if( !alive ) continue; nullpo_retr(nullptr, (unit = skill_initunit(group,i,ux,uy,unit_val1,unit_val2,hidden))); unit->limit = limit; unit->range = range; if (skill_id == PF_FOGWALL && alive == 2) { //Double duration of cells on top of Deluge/Suiton unit->limit *= 2; group->limit = unit->limit; } // Execute on all targets standing on this cell if (range == 0 && active_flag) map_foreachincell(skill_unit_effect,unit->bl.m,unit->bl.x,unit->bl.y,group->bl_flag,&unit->bl,gettick(),1); } if (!group->alive_count) { //No cells? Something that was blocked completely by Land Protector? skill_delunitgroup(group); return nullptr; } //success, unit created. switch( skill_id ) { case NJ_TATAMIGAESHI: //Store number of tiles. group->val1 = group->alive_count; break; } return group; } /*========================================== * *------------------------------------------*/ void ext_skill_unit_onplace(struct skill_unit *unit, struct block_list *bl, t_tick tick) { skill_unit_onplace(unit, bl, tick); } /** * Triggeres when 'target' (based on skill unit target) is stand at unit area * while skill unit initialized or moved (such by knock back). * As a follow of skill_unit_effect flag &1 * @param unit * @param bl Target * @param tick */ static int skill_unit_onplace(struct skill_unit *unit, struct block_list *bl, t_tick tick) { struct block_list *ss; // Actual source that cast the skill unit status_change *sc; struct status_change_entry *sce; struct status_data *tstatus; enum sc_type type; uint16 skill_id; nullpo_ret(unit); nullpo_ret(bl); if(bl->prev == NULL || !unit->alive || status_isdead(bl)) return 0; std::shared_ptr<s_skill_unit_group> sg = unit->group; if (sg == nullptr) return 0; nullpo_ret(ss = map_id2bl(sg->src_id)); tstatus = status_get_status_data(bl); if( (skill_get_type(sg->skill_id) == BF_MAGIC && ((battle_config.land_protector_behavior) ? map_getcell(bl->m, bl->x, bl->y, CELL_CHKLANDPROTECTOR) : map_getcell(unit->bl.m, unit->bl.x, unit->bl.y, CELL_CHKLANDPROTECTOR)) && sg->skill_id != SA_LANDPROTECTOR) || map_getcell(bl->m, bl->x, bl->y, CELL_CHKMAELSTROM) ) return 0; //AoE skills are ineffective. [Skotlex] std::shared_ptr<s_skill_db> skill = skill_db.find(sg->skill_id); if( (skill->inf2[INF2_ISSONG] || skill->inf2[INF2_ISENSEMBLE]) && map_getcell(bl->m, bl->x, bl->y, CELL_CHKBASILICA) ) return 0; //Songs don't work in Basilica sc = status_get_sc(bl); if (sc && sc->option&OPTION_HIDE && !skill->inf2[INF2_TARGETHIDDEN]) return 0; //Hidden characters are immune to AoE skills except to these. [Skotlex] if (sc && sc->getSCE(SC_VACUUM_EXTREME) && map_getcell(bl->m, bl->x, bl->y, CELL_CHKLANDPROTECTOR)) status_change_end(bl, SC_VACUUM_EXTREME); if (sc && sc->getSCE(SC_HOVERING) && skill->inf2[INF2_IGNOREHOVERING]) return 0; // Under Hovering characters are immune to select trap and ground target skills. type = skill_get_sc(sg->skill_id); sce = (sc && type != SC_NONE) ? sc->getSCE(type) : NULL; skill_id = sg->skill_id; //In case the group is deleted, we need to return the correct skill id, still. switch (sg->unit_id) { case UNT_SPIDERWEB: if (sc) { //Duration in PVM is: 1st - 8s, 2nd - 16s, 3rd - 8s //Duration in PVP is: 1st - 4s, 2nd - 8s, 3rd - 12s t_tick sec = skill_get_time2(sg->skill_id, sg->skill_lv); const struct TimerData* td; struct map_data *mapdata = map_getmapdata(bl->m); if (mapdata_flag_vs(mapdata)) sec /= 2; if (sc->getSCE(type)) { if (sc->getSCE(type)->val2 && sc->getSCE(type)->val3 && sc->getSCE(type)->val4) { //Already triple affected, immune sg->limit = DIFF_TICK(tick, sg->tick); break; } //Don't increase val1 here, we need a higher val in status_change_start so it overwrites the old one if (mapdata_flag_vs(mapdata) && sc->getSCE(type)->val1 < 3) sec *= (sc->getSCE(type)->val1 + 1); else if(!mapdata_flag_vs(mapdata) && sc->getSCE(type)->val1 < 2) sec *= (sc->getSCE(type)->val1 + 1); //Add group id to status change if (sc->getSCE(type)->val2 == 0) sc->getSCE(type)->val2 = sg->group_id; else if (sc->getSCE(type)->val3 == 0) sc->getSCE(type)->val3 = sg->group_id; else if (sc->getSCE(type)->val4 == 0) sc->getSCE(type)->val4 = sg->group_id; //Overwrite status change with new duration if ((td = get_timer(sc->getSCE(type)->timer))!=NULL) status_change_start(ss, bl, type, 10000, sc->getSCE(type)->val1 + 1, sc->getSCE(type)->val2, sc->getSCE(type)->val3, sc->getSCE(type)->val4, i64max(DIFF_TICK(td->tick, tick), sec), SCSTART_NORATEDEF); } else { if (status_change_start(ss, bl, type, 10000, 1, sg->group_id, 0, 0, sec, SCSTART_NORATEDEF)) { td = sc->getSCE(type) ? get_timer(sc->getSCE(type)->timer) : NULL; if (td) sec = DIFF_TICK(td->tick, tick); map_moveblock(bl, unit->bl.x, unit->bl.y, tick); clif_fixpos(bl); } else sec = 3000; //Couldn't trap it? } sg->val2 = bl->id; sg->limit = DIFF_TICK(tick, sg->tick) + sec; } break; case UNT_SAFETYWALL: if (!sce) sc_start4(ss, bl,type,100,sg->skill_lv,sg->skill_id,sg->group_id,0,sg->limit); break; case UNT_BLOODYLUST: if (sg->src_id == bl->id) break; //Does not affect the caster. if( !sce && sc_start4(ss, bl,type,100,sg->skill_lv,0,SC__BLOODYLUST,0,sg->limit) ) // Dirty fix to add extra time to Bloody Lust so it doesn't end before // Berserk, causing HP to drop to 100 when we don't want it to [Akinari] sc_start(ss, bl,SC__BLOODYLUST,100,sg->skill_lv,sg->limit+100); break; case UNT_PNEUMA: if (!sce) sc_start4(ss, bl,type,100,sg->skill_lv,sg->group_id,0,0,sg->limit); break; case UNT_CHAOSPANIC: status_change_start(ss, bl, type, 3500 + (sg->skill_lv * 1500), sg->skill_lv, 0, 0, 1, skill_get_time2(sg->skill_id, sg->skill_lv), SCSTART_NOAVOID|SCSTART_NORATEDEF|SCSTART_NOTICKDEF); break; case UNT_WARP_WAITING: { int working = sg->val1&0xffff; if(bl->type==BL_PC && !working){ map_session_data *sd = (map_session_data *)bl; if((!sd->chatID || battle_config.chat_warpportal) && sd->ud.to_x == unit->bl.x && sd->ud.to_y == unit->bl.y) { int x = sg->val2>>16; int y = sg->val2&0xffff; int count = sg->val1>>16; unsigned short m = sg->val3; if( --count <= 0 ) skill_delunitgroup(sg); if ( map_mapindex2mapid(m) == sd->bl.m && x == sd->bl.x && y == sd->bl.y ) working = 1;/* we break it because officials break it, lovely stuff. */ sg->val1 = (count<<16)|working; if (pc_job_can_entermap((enum e_job)sd->status.class_, map_mapindex2mapid(m), pc_get_group_level(sd))) pc_setpos(sd,m,x,y,CLR_TELEPORT); } } else if(bl->type == BL_MOB && battle_config.mob_warp&2) { int16 m = map_mapindex2mapid(sg->val3); if (m < 0) break; //Map not available on this map-server. unit_warp(bl,m,sg->val2>>16,sg->val2&0xffff,CLR_TELEPORT); } } break; case UNT_QUAGMIRE: if( !sce && battle_check_target(&unit->bl,bl,sg->target_flag) > 0 ) sc_start4(ss, bl,type,100,sg->skill_lv,sg->group_id,0,0,sg->limit); break; case UNT_VOLCANO: case UNT_DELUGE: case UNT_VIOLENTGALE: case UNT_FIRE_INSIGNIA: case UNT_WATER_INSIGNIA: case UNT_WIND_INSIGNIA: case UNT_EARTH_INSIGNIA: if(!sce) sc_start(ss, bl,type,100,sg->skill_lv,sg->limit); break; case UNT_WATER_BARRIER: case UNT_ZEPHYR: case UNT_POWER_OF_GAIA: if (bl->id == ss->id) break; // Doesn't affect the Elemental if (!sce) sc_start(ss, bl, type, 100, sg->skill_lv, sg->limit); break; case UNT_SUITON: if(!sce) sc_start4(ss, bl,type,100,sg->skill_lv, map_flag_vs(bl->m) || battle_check_target(&unit->bl,bl,BCT_ENEMY)>0?1:0, //Send val3 =1 to reduce agi. 0,0,sg->limit); break; case UNT_HERMODE: if (sg->src_id!=bl->id && battle_check_target(&unit->bl,bl,BCT_PARTY|BCT_GUILD) > 0) status_change_clear_buffs(bl, SCCB_HERMODE); //Should dispell only allies. case UNT_RICHMANKIM: case UNT_ETERNALCHAOS: case UNT_DRUMBATTLEFIELD: case UNT_RINGNIBELUNGEN: case UNT_ROKISWEIL: case UNT_INTOABYSS: case UNT_SIEGFRIED: //Needed to check when a dancer/bard leaves their ensemble area. if (sg->src_id==bl->id && !(sc && sc->getSCE(SC_SPIRIT) && sc->getSCE(SC_SPIRIT)->val2 == SL_BARDDANCER)) return skill_id; if (!sce) sc_start4(ss, bl,type,100,sg->skill_lv,sg->val1,sg->val2,0,sg->limit); break; case UNT_WHISTLE: case UNT_ASSASSINCROSS: case UNT_POEMBRAGI: case UNT_APPLEIDUN: case UNT_HUMMING: case UNT_DONTFORGETME: case UNT_FORTUNEKISS: case UNT_SERVICEFORYOU: if (sg->src_id==bl->id && !(sc && sc->getSCE(SC_SPIRIT) && sc->getSCE(SC_SPIRIT)->val2 == SL_BARDDANCER)) return 0; if (!sc) return 0; if (!sce) sc_start4(ss, bl,type,100,sg->skill_lv,sg->val1,sg->val2,0,sg->limit); else if (sce->val4 == 1) { //Readjust timers since the effect will not last long. sce->val4 = 0; //remove the mark that we stepped out delete_timer(sce->timer, status_change_timer); sce->timer = add_timer(tick+sg->limit, status_change_timer, bl->id, type); //put duration back to 3min } break; case UNT_FOGWALL: if (!sce) { sc_start4(ss, bl, type, 100, sg->skill_lv, sg->val1, sg->val2, sg->group_id, sg->limit); if (battle_check_target(&unit->bl,bl,BCT_ENEMY)>0) skill_additional_effect (ss, bl, sg->skill_id, sg->skill_lv, BF_MISC, ATK_DEF, tick); } break; #ifndef RENEWAL case UNT_GRAVITATION: if (!sce) sc_start4(ss, bl,type,100,sg->skill_lv,0,BCT_ENEMY,sg->group_id,sg->limit); break; case UNT_BASILICA: { int i = battle_check_target(bl, bl, BCT_ENEMY); if (i > 0) { skill_blown(ss, bl, skill_get_blewcount(skill_id, sg->skill_lv), unit_getdir(bl), BLOWN_NONE); break; } if (!sce && i <= 0) sc_start4(ss, bl, type, 100, 0, 0, sg->group_id, ss->id, sg->limit); } break; #endif case UNT_MOONLIT: //Knockback out of area if affected char isn't in Moonlit effect if (sc && sc->getSCE(SC_DANCING) && (sc->getSCE(SC_DANCING)->val1&0xFFFF) == CG_MOONLIT) break; if (ss == bl) //Also needed to prevent infinite loop crash. break; skill_blown(ss,bl,skill_get_blewcount(sg->skill_id,sg->skill_lv),unit_getdir(bl),BLOWN_NONE); break; case UNT_REVERBERATION: if (sg->src_id == bl->id) break; //Does not affect the caster. clif_changetraplook(&unit->bl,UNT_USED_TRAPS); map_foreachinrange(skill_trap_splash,&unit->bl, skill_get_splash(sg->skill_id, sg->skill_lv), sg->bl_flag, &unit->bl,tick); sg->limit = DIFF_TICK(tick,sg->tick) + 1500; sg->unit_id = UNT_USED_TRAPS; break; case UNT_FIRE_EXPANSION_SMOKE_POWDER: if (!sce && battle_check_target(&unit->bl, bl, sg->target_flag) > 0) sc_start(ss, bl, type, 100, sg->skill_lv, sg->limit); break; case UNT_FIRE_EXPANSION_TEAR_GAS: if (!sce && battle_check_target(&unit->bl, bl, sg->target_flag) > 0 && map_flag_vs(bl->m)) if( sc_start4(ss, bl, type, 100, sg->skill_lv, 0, ss->id,0, sg->limit) ) sc_start(ss, bl, SC_TEARGAS_SOB, 100, sg->skill_lv, sg->limit); break; case UNT_VOLCANIC_ASH: if (!sce) sc_start(ss, bl, SC_ASH, 100, sg->skill_lv, skill_get_time(MH_VOLCANIC_ASH, sg->skill_lv)); break; case UNT_KINGS_GRACE: if (!sce) { int state = 0; if (!map_flag_vs(ss->m) && !map_flag_gvg2(ss->m)) state |= BCT_GUILD; if (battle_check_target(&unit->bl, bl, BCT_SELF|BCT_PARTY|state) > 0) sc_start4(ss, bl, type, 100, sg->skill_lv, 0, ss->id, 0, sg->limit); } break; case UNT_STEALTHFIELD: if( bl->id == sg->src_id ) break; // Doesn't work on self (video shows that) if (!sce) sc_start(ss, bl,type,100,sg->skill_lv,sg->limit); break; case UNT_NEUTRALBARRIER: if (!sce) status_change_start(ss, bl, type, 10000, sg->skill_lv, 0, 0, 0, sg->limit, SCSTART_NOICON); break; case UNT_WARMER: if (!sce && bl->type == BL_PC && !battle_check_undead(tstatus->race, tstatus->def_ele) && tstatus->race != RC_DEMON) sc_start2(ss, bl, type, 100, sg->skill_lv, ss->id, skill_get_time(sg->skill_id, sg->skill_lv)); break; case UNT_CATNIPPOWDER: if (sg->src_id == bl->id) break; // Does not affect the caster or Boss. if (!sce && battle_check_target(&unit->bl, bl, BCT_ENEMY) > 0) sc_start(ss, bl, type, 100, sg->skill_lv, skill_get_time(sg->skill_id, sg->skill_lv)); break; case UNT_NYANGGRASS: if (!sce) sc_start(ss, bl, type, 100, sg->skill_lv, skill_get_time(sg->skill_id, sg->skill_lv)); break; case UNT_CREATINGSTAR: if (!sce) sc_start4(ss, bl, type, 100, sg->skill_lv, ss->id, unit->bl.id, 0, sg->limit); break; case UNT_GD_LEADERSHIP: case UNT_GD_GLORYWOUNDS: case UNT_GD_SOULCOLD: case UNT_GD_HAWKEYES: if ( !sce && battle_check_target(&unit->bl, bl, sg->target_flag) > 0 ) sc_start4(ss, bl,type,100,sg->skill_lv,0,0,0,1000); break; } return skill_id; } /** * Process skill unit each interval (sg->interval, see interval field of skill_db.yml) * @param unit Skill unit * @param bl Valid 'target' above the unit, that has been check in skill_unit_timer_sub_onplace * @param tick */ int skill_unit_onplace_timer(struct skill_unit *unit, struct block_list *bl, t_tick tick) { struct block_list *ss; TBL_PC* tsd; struct status_data *tstatus; status_change *sc, *tsc; struct skill_unit_group_tickset *ts; enum sc_type type; uint16 skill_id; t_tick diff = 0; nullpo_ret(unit); nullpo_ret(bl); if (bl->prev == NULL || !unit->alive || status_isdead(bl)) return 0; std::shared_ptr<s_skill_unit_group> sg = unit->group; if (sg == nullptr) return 0; nullpo_ret(ss = map_id2bl(sg->src_id)); tsd = BL_CAST(BL_PC, bl); tsc = status_get_sc(bl); sc = status_get_sc(ss); tstatus = status_get_status_data(bl); type = skill_get_sc(sg->skill_id); skill_id = sg->skill_id; std::bitset<INF2_MAX> inf2 = skill_db.find(skill_id)->inf2; if (sc && sc->getSCE(SC_VOICEOFSIREN) && sc->getSCE(SC_VOICEOFSIREN)->val2 == bl->id && inf2[INF2_ISTRAP]) return 0; // Traps cannot be activated by the Maestro or Wanderer that enticed the trapper with this skill. if (tsc && tsc->getSCE(SC_HOVERING) && inf2[INF2_IGNOREHOVERING]) return 0; // Under Hovering characters are immune to trap and ground target skills. if (sg->interval == -1) { switch (sg->unit_id) { case UNT_ANKLESNARE: //These happen when a trap is splash-triggered by multiple targets on the same cell. case UNT_FIREPILLAR_ACTIVE: case UNT_ELECTRICSHOCKER: case UNT_MANHOLE: return 0; default: ShowError("skill_unit_onplace_timer: interval error (unit id %x)\n", sg->unit_id); return 0; } } if ((ts = skill_unitgrouptickset_search(bl,sg,tick))) { //Not all have it, eg: Traps don't have it even though they can be hit by Heaven's Drive [Skotlex] diff = DIFF_TICK(tick,ts->tick); if (diff < 0) return 0; ts->tick = tick+sg->interval; } // Wall of Thorn damaged by Fire element unit [Cydh] //! TODO: This check doesn't matter the location, as long as one of units touched, this check will be executed. if (bl->type == BL_SKILL && skill_get_ele(sg->skill_id, sg->skill_lv) == ELE_FIRE) { struct skill_unit *su = (struct skill_unit *)bl; if (su && su->group && su->group->unit_id == UNT_WALLOFTHORN) { skill_unitsetting(map_id2bl(su->group->src_id), su->group->skill_id, su->group->skill_lv, su->group->val3>>16, su->group->val3&0xffff, 1); su->group->limit = sg->limit = 0; su->group->unit_id = sg->unit_id = UNT_USED_TRAPS; return skill_id; } } switch (sg->unit_id) { // Units that deals simple attack case UNT_GRAVITATION: case UNT_EARTHSTRAIN: case UNT_FIREWALK: case UNT_ELECTRICWALK: case UNT_PSYCHIC_WAVE: case UNT_LAVA_SLIDE: case UNT_MAKIBISHI: case UNT_VENOMFOG: case UNT_ICEMINE: case UNT_FLAMECROSS: case UNT_HELLBURNING: case UNT_RAIN_OF_CRYSTAL: case UNT_MYSTERY_ILLUSION: case UNT_STRANTUM_TREMOR: case UNT_TORNADO_STORM: case UNT_FLORAL_FLARE_ROAD: case UNT_CROSS_RAIN: case UNT_PNEUMATICUS_PROCELLA: case UNT_LIGHTNING_LAND: case UNT_VENOM_SWAMP: case UNT_CONFLAGRATION: case UNT_DEEPBLINDTRAP: case UNT_SOLIDTRAP: case UNT_SWIFTTRAP: case UNT_FLAMETRAP: skill_attack(skill_get_type(sg->skill_id),ss,&unit->bl,bl,sg->skill_id,sg->skill_lv,tick,0); break; case UNT_DUMMYSKILL: switch (sg->skill_id) { case SG_SUN_WARM: //SG skills [Komurka] case SG_MOON_WARM: case SG_STAR_WARM: { int count = 0; const int x = bl->x, y = bl->y; //If target isn't knocked back it should hit every "interval" ms [Playtester] do { if( bl->type == BL_PC ) status_zap(bl, 0, 15); // sp damage to players else // mobs if( status_charge(ss, 0, 2) ) { // costs 2 SP per hit if( !skill_attack(BF_WEAPON,ss,&unit->bl,bl,sg->skill_id,sg->skill_lv,tick+(t_tick)count*sg->interval,0) ) status_charge(ss, 0, 8); //costs additional 8 SP if miss } else { //should end when out of sp. sg->limit = DIFF_TICK(tick,sg->tick); break; } } while(sg->interval > 0 && x == bl->x && y == bl->y && ++count < SKILLUNITTIMER_INTERVAL/sg->interval && !status_isdead(bl) ); } break; #ifndef RENEWAL // The storm gust counter was dropped in renewal case WZ_STORMGUST: //SG counter does not reset per stormgust. IE: One hit from a SG and two hits from another will freeze you. if (tsc) tsc->sg_counter++; //SG hit counter. if (skill_attack(skill_get_type(sg->skill_id),ss,&unit->bl,bl,sg->skill_id,sg->skill_lv,tick,0) <= 0 && tsc) tsc->sg_counter=0; //Attack absorbed. break; #endif case GS_DESPERADO: if (rnd()%100 < unit->val1) skill_attack(BF_WEAPON,ss,&unit->bl,bl,sg->skill_id,sg->skill_lv,tick,0); break; case NPC_COMET: case WL_COMET: if (map_getcell(bl->m, bl->x, bl->y, CELL_CHKLANDPROTECTOR) == 0)// Nothing should happen if the target is on Land Protector skill_attack(skill_get_type(sg->skill_id),ss,&unit->bl,bl,sg->skill_id,sg->skill_lv,tick,0); break; case NPC_WIDESUCK: { int heal = (int)skill_attack(skill_get_type(sg->skill_id),ss,&unit->bl,bl,sg->skill_id,sg->skill_lv,tick,0); if (heal > 0) { clif_skill_nodamage(ss,bl,sg->skill_id,sg->skill_lv,1); clif_skill_nodamage(nullptr,ss,AL_HEAL,heal,1); status_heal(ss,heal,0,0); } } break; case CR_GRANDCROSS: case NPC_GRANDDARKNESS: if(!battle_config.gx_allhit) unit->val1--; skill_attack(skill_get_type(sg->skill_id),ss,&unit->bl,bl,sg->skill_id,sg->skill_lv,tick,0); break; default: skill_attack(skill_get_type(sg->skill_id),ss,&unit->bl,bl,sg->skill_id,sg->skill_lv,tick,0); } break; case UNT_FIREWALL: case UNT_KAEN: { int count = 0; const int x = bl->x, y = bl->y; if (skill_id == GN_WALLOFTHORN && battle_check_target(ss, bl, sg->target_flag) <= 0) break; //Take into account these hit more times than the timer interval can handle. do skill_attack(BF_MAGIC,ss,&unit->bl,bl,sg->skill_id,sg->skill_lv,tick+(t_tick)count*sg->interval,0); while(sg->interval > 0 && --unit->val2 && x == bl->x && y == bl->y && ++count < SKILLUNITTIMER_INTERVAL/sg->interval && !status_isdead(bl)); if (unit->val2 <= 0) skill_delunit(unit); } break; case UNT_SANCTUARY: if( battle_check_undead(tstatus->race, tstatus->def_ele) || tstatus->race==RC_DEMON ) { //Only damage enemies with offensive Sanctuary. [Skotlex] if( battle_check_target(&unit->bl,bl,BCT_ENEMY) > 0 && skill_attack(BF_MAGIC, ss, &unit->bl, bl, sg->skill_id, sg->skill_lv, tick, 0) ) sg->val1 -= 1; // Reduce the number of targets that can still be hit } else { int heal = skill_calc_heal(ss,bl,sg->skill_id,sg->skill_lv,true); struct mob_data *md = BL_CAST(BL_MOB, bl); #ifdef RENEWAL if (md && md->mob_id == MOBID_EMPERIUM) break; #endif if (md && status_get_class_(bl) == CLASS_BATTLEFIELD) break; if( tstatus->hp >= tstatus->max_hp ) break; if( status_isimmune(bl) ) heal = 0; clif_skill_nodamage(&unit->bl, bl, AL_HEAL, heal, 1); if( tsc && tsc->getSCE(SC_AKAITSUKI) && heal ) heal = ~heal + 1; status_heal(bl, heal, 0, 0); } break; case UNT_EVILLAND: //Will heal demon and undead element monsters, but not players. if ((bl->type == BL_PC) || (!battle_check_undead(tstatus->race, tstatus->def_ele) && tstatus->race!=RC_DEMON)) { //Damage enemies if(battle_check_target(&unit->bl,bl,BCT_ENEMY)>0) skill_attack(BF_MISC, ss, &unit->bl, bl, sg->skill_id, sg->skill_lv, tick, 0); } else { int heal = skill_calc_heal(ss,bl,sg->skill_id,sg->skill_lv,true); if (tstatus->hp >= tstatus->max_hp) break; if (status_isimmune(bl)) heal = 0; clif_skill_nodamage(&unit->bl, bl, AL_HEAL, heal, 1); status_heal(bl, heal, 0, 0); } break; case UNT_MAGNUS: #ifndef RENEWAL if (!battle_check_undead(tstatus->race,tstatus->def_ele) && tstatus->race!=RC_DEMON) break; #endif skill_attack(BF_MAGIC,ss,&unit->bl,bl,sg->skill_id,sg->skill_lv,tick,0); break; case UNT_FIREPILLAR_WAITING: skill_unitsetting(ss,sg->skill_id,sg->skill_lv,unit->bl.x,unit->bl.y,1); skill_delunit(unit); break; case UNT_SKIDTRAP: { //Knockback away from position of user during placement [Playtester] skill_blown(&unit->bl,bl,skill_get_blewcount(sg->skill_id,sg->skill_lv), (map_calc_dir_xy(sg->val1>>16,sg->val1&0xFFFF,bl->x,bl->y,6)+4)%8,BLOWN_NONE); sg->unit_id = UNT_USED_TRAPS; clif_changetraplook(&unit->bl, UNT_USED_TRAPS); sg->limit=DIFF_TICK(tick,sg->tick)+1500; //Target will be stopped for 3 seconds sc_start(ss,bl,SC_STOP,100,0,skill_get_time2(sg->skill_id,sg->skill_lv)); } break; case UNT_ANKLESNARE: case UNT_MANHOLE: if( sg->val2 == 0 && tsc && ((sg->unit_id == UNT_ANKLESNARE && skill_id != SC_ESCAPE) || bl->id != sg->src_id) ) { t_tick sec = skill_get_time2(sg->skill_id,sg->skill_lv); if( status_change_start(ss, bl,type,10000,sg->skill_lv,sg->group_id,0,0,sec, SCSTART_NORATEDEF) ) { const struct TimerData* td = tsc->getSCE(type)?get_timer(tsc->getSCE(type)->timer):NULL; if( td ) sec = DIFF_TICK(td->tick, tick); if( (sg->unit_id == UNT_MANHOLE && bl->type == BL_PC) || !unit_blown_immune(bl,0x1) ) { unit_movepos(bl, unit->bl.x, unit->bl.y, 0, 0); clif_fixpos(bl); } sg->val2 = bl->id; } else sec = 3000; //Couldn't trap it? if (sg->unit_id == UNT_ANKLESNARE) { clif_skillunit_update(&unit->bl); /** * If you're snared from a trap that was invisible this makes the trap be * visible again -- being you stepped on it (w/o this the trap remains invisible and you go "WTF WHY I CANT MOVE") * bugreport:3961 **/ clif_changetraplook(&unit->bl, UNT_ANKLESNARE); } sg->limit = DIFF_TICK(tick,sg->tick)+sec; sg->interval = -1; unit->range = 0; } break; case UNT_EARTHQUAKE: sg->val1++; // Hit count skill_attack(skill_get_type(sg->skill_id), ss, &unit->bl, bl, sg->skill_id, sg->skill_lv, tick, map_foreachinallrange(skill_area_sub, &unit->bl, skill_get_splash(sg->skill_id, sg->skill_lv), BL_CHAR, &unit->bl, sg->skill_id, sg->skill_lv, tick, BCT_ENEMY, skill_area_sub_count) | (sg->val1 == 1 ? NPC_EARTHQUAKE_FLAG : 0)); break; case UNT_ELECTRICSHOCKER: if( bl->id != ss->id ) { if( status_change_start(ss, bl,type,10000,sg->skill_lv,sg->group_id,0,0,skill_get_time2(sg->skill_id, sg->skill_lv), SCSTART_NORATEDEF) ) { map_moveblock(bl, unit->bl.x, unit->bl.y, tick); clif_fixpos(bl); } map_foreachinallrange(skill_trap_splash, &unit->bl, skill_get_splash(sg->skill_id, sg->skill_lv), sg->bl_flag, &unit->bl, tick); sg->unit_id = UNT_USED_TRAPS; //Changed ID so it does not invoke a for each in area again. } break; case UNT_VENOMDUST: if(tsc && !tsc->getSCE(type)) status_change_start(ss,bl,type,10000,sg->skill_lv,sg->src_id,0,0,skill_get_time2(sg->skill_id,sg->skill_lv),SCSTART_NONE); break; case UNT_LANDMINE: //Land Mine only hits single target skill_attack(skill_get_type(sg->skill_id),ss,&unit->bl,bl,sg->skill_id,sg->skill_lv,tick,0); sg->unit_id = UNT_USED_TRAPS; //Changed ID so it does not invoke a for each in area again. sg->limit = 1500; break; case UNT_MAGENTATRAP: case UNT_COBALTTRAP: case UNT_MAIZETRAP: case UNT_VERDURETRAP: if( bl->type == BL_PC )// it won't work on players break; case UNT_FIRINGTRAP: case UNT_ICEBOUNDTRAP: case UNT_CLUSTERBOMB: if( bl->id == ss->id )// it won't trigger on caster break; case UNT_BLASTMINE: case UNT_SHOCKWAVE: case UNT_SANDMAN: case UNT_FLASHER: case UNT_FREEZINGTRAP: case UNT_FIREPILLAR_ACTIVE: case UNT_CLAYMORETRAP: { int bl_flag = sg->bl_flag; if (tsc && tsc->getSCE(SC__MANHOLE)) break; if (sg->unit_id == UNT_FIRINGTRAP || sg->unit_id == UNT_ICEBOUNDTRAP || sg->unit_id == UNT_CLAYMORETRAP) bl_flag = bl_flag|BL_SKILL|~BCT_SELF; map_foreachinrange(skill_trap_splash, &unit->bl, skill_get_splash(sg->skill_id, sg->skill_lv), bl_flag, &unit->bl, tick); if (sg->unit_id != UNT_FIREPILLAR_ACTIVE) clif_changetraplook(&unit->bl,(sg->unit_id == UNT_LANDMINE ? UNT_FIREPILLAR_ACTIVE : UNT_USED_TRAPS)); sg->limit = DIFF_TICK(tick, sg->tick) + (sg->unit_id == UNT_CLUSTERBOMB || sg->unit_id == UNT_ICEBOUNDTRAP ? 1000 : 0) + // Cluster Bomb/Icebound has 1s to disappear once activated. (sg->unit_id == UNT_FIRINGTRAP ? 0 : 1500); // Firing Trap gets removed immediately once activated. sg->unit_id = UNT_USED_TRAPS; // Change ID so it does not invoke a for each in area again. } break; case UNT_TALKIEBOX: if (sg->src_id == bl->id) break; if (sg->val2 == 0) { clif_talkiebox(&unit->bl, sg->valstr); sg->unit_id = UNT_USED_TRAPS; clif_changetraplook(&unit->bl, UNT_USED_TRAPS); sg->limit = DIFF_TICK(tick, sg->tick) + 5000; sg->val2 = -1; } break; case UNT_LULLABY: if (ss->id == bl->id) break; skill_additional_effect(ss, bl, sg->skill_id, sg->skill_lv, BF_LONG|BF_SKILL|BF_MISC, ATK_DEF, tick); break; case UNT_UGLYDANCE: //Ugly Dance [Skotlex] if (ss->id != bl->id) skill_additional_effect(ss, bl, sg->skill_id, sg->skill_lv, BF_LONG|BF_SKILL|BF_MISC, ATK_DEF, tick); break; case UNT_DISSONANCE: skill_attack(BF_MISC, ss, &unit->bl, bl, sg->skill_id, sg->skill_lv, tick, 0); break; case UNT_APPLEIDUN: { //Apple of Idun [Skotlex] int heal; #ifdef RENEWAL struct mob_data *md = BL_CAST(BL_MOB, bl); if (md && md->mob_id == MOBID_EMPERIUM) break; #endif if (sg->src_id == bl->id && !(tsc && tsc->getSCE(SC_SPIRIT) && tsc->getSCE(SC_SPIRIT)->val2 == SL_BARDDANCER)) break; // affects self only when soullinked heal = skill_calc_heal(ss,bl,sg->skill_id, sg->skill_lv, true); if (tsc->getSCE(SC_AKAITSUKI) && heal) heal = ~heal + 1; clif_skill_nodamage(&unit->bl, bl, AL_HEAL, heal, 1); status_heal(bl, heal, 0, 0); } break; case UNT_TATAMIGAESHI: case UNT_DEMONSTRATION: skill_attack(BF_WEAPON,ss,&unit->bl,bl,sg->skill_id,sg->skill_lv,tick,0); break; case UNT_GOSPEL: if (rnd() % 100 >= 50 + sg->skill_lv * 5 || ss == bl) break; if (battle_check_target(ss, bl, BCT_PARTY) > 0) { // Support Effect only on party, not guild int heal; int i = rnd() % 13; // Positive buff count int time = skill_get_time2(sg->skill_id, sg->skill_lv); //Duration switch (i) { case 0: // Heal 1000~9999 HP heal = rnd() % 9000 + 1000; clif_skill_nodamage(ss, bl, AL_HEAL, heal, 1); status_heal(bl, heal, 0, 0); break; case 1: // End all negative status status_change_clear_buffs(bl, SCCB_DEBUFFS | SCCB_REFRESH); if (tsd) clif_gospel_info(tsd, 0x15); break; case 2: // Immunity to all status sc_start(ss, bl, SC_SCRESIST, 100, 100, time); if (tsd) clif_gospel_info(tsd, 0x16); break; case 3: // MaxHP +100% sc_start(ss, bl, SC_INCMHPRATE, 100, 100, time); if (tsd) clif_gospel_info(tsd, 0x17); break; case 4: // MaxSP +100% sc_start(ss, bl, SC_INCMSPRATE, 100, 100, time); if (tsd) clif_gospel_info(tsd, 0x18); break; case 5: // All stats +20 sc_start(ss, bl, SC_INCALLSTATUS, 100, 20, time); if (tsd) clif_gospel_info(tsd, 0x19); break; case 6: // Level 10 Blessing sc_start(ss, bl, SC_BLESSING, 100, 10, skill_get_time(AL_BLESSING, 10)); break; case 7: // Level 10 Increase AGI sc_start(ss, bl, SC_INCREASEAGI, 100, 10, skill_get_time(AL_INCAGI, 10)); break; case 8: // Enchant weapon with Holy element sc_start(ss, bl, SC_ASPERSIO, 100, 1, time); if (tsd) clif_gospel_info(tsd, 0x1c); break; case 9: // Enchant armor with Holy element sc_start(ss, bl, SC_BENEDICTIO, 100, 1, time); if (tsd) clif_gospel_info(tsd, 0x1d); break; case 10: // DEF +25% sc_start(ss, bl, SC_INCDEFRATE, 100, 25, 10000); //10 seconds if (tsd) clif_gospel_info(tsd, 0x1e); break; case 11: // ATK +100% sc_start(ss, bl, SC_INCATKRATE, 100, 100, time); if (tsd) clif_gospel_info(tsd, 0x1f); break; case 12: // HIT/Flee +50 sc_start(ss, bl, SC_INCHIT, 100, 50, time); sc_start(ss, bl, SC_INCFLEE, 100, 50, time); if (tsd) clif_gospel_info(tsd, 0x20); break; } } else if (battle_check_target(&unit->bl, bl, BCT_ENEMY) > 0) { // Offensive Effect int i = rnd() % 10; // Negative buff count switch (i) { case 0: // Deal 3000~7999 damage reduced by DEF case 1: // Deal 1500~5499 damage unreducable skill_attack(BF_MISC, ss, &unit->bl, bl, sg->skill_id, sg->skill_lv, tick, i); break; case 2: // Curse sc_start(ss, bl, SC_CURSE, 100, 1, 1800000); //30 minutes break; case 3: // Blind sc_start(ss, bl, SC_BLIND, 100, 1, 1800000); //30 minutes break; case 4: // Poison sc_start2(ss, bl, SC_POISON, 100, 1, ss->id, 1800000); //30 minutes break; case 5: // Level 10 Provoke clif_skill_nodamage(NULL, bl, SM_PROVOKE, 10, sc_start(ss, bl, SC_PROVOKE, 100, 10, INFINITE_TICK)); //Infinite break; case 6: // DEF -100% sc_start(ss, bl, SC_INCDEFRATE, 100, -100, 20000); //20 seconds break; case 7: // ATK -100% sc_start(ss, bl, SC_INCATKRATE, 100, -100, 20000); //20 seconds break; case 8: // Flee -100% sc_start(ss, bl, SC_INCFLEERATE, 100, -100, 20000); //20 seconds break; case 9: // Speed/ASPD -25% sc_start4(ss, bl, SC_GOSPEL, 100, 1, 0, 0, BCT_ENEMY, 20000); //20 seconds break; } } break; #ifndef RENEWAL case UNT_BASILICA: { int i = battle_check_target(&unit->bl, bl, BCT_ENEMY); if (i > 0) { skill_blown(&unit->bl, bl, skill_get_blewcount(skill_id, sg->skill_lv), unit_getdir(bl), BLOWN_NONE); break; } if (i <= 0 && (!tsc || !tsc->getSCE(SC_BASILICA))) sc_start4(ss, bl, type, 100, 0, 0, sg->group_id, ss->id, sg->limit); } break; #endif case UNT_GROUNDDRIFT_WIND: case UNT_GROUNDDRIFT_DARK: case UNT_GROUNDDRIFT_POISON: case UNT_GROUNDDRIFT_WATER: case UNT_GROUNDDRIFT_FIRE: map_foreachinrange(skill_trap_splash,&unit->bl, skill_get_splash(sg->skill_id, sg->skill_lv), sg->bl_flag, &unit->bl,tick); sg->unit_id = UNT_USED_TRAPS; //clif_changetraplook(&src->bl, UNT_FIREPILLAR_ACTIVE); sg->limit=DIFF_TICK(tick,sg->tick); break; case UNT_POISONSMOKE: if( battle_check_target(ss,bl,BCT_ENEMY) > 0 && !(tsc && tsc->getSCE(sg->val2)) && rnd()%100 < 50 ) sc_start4(ss,bl,(sc_type)sg->val2,100,sg->val3,0,1,0,skill_get_time2(GC_POISONINGWEAPON, 1)); break; case UNT_EPICLESIS: ++sg->val1; // Increment outside of the check to get the exact interval of the skill unit if( bl->type == BL_PC && !battle_check_undead(tstatus->race, tstatus->def_ele) && tstatus->race != RC_DEMON ) { if (sg->val1 % 3 == 0) { // Recover players every 3 seconds int hp, sp; switch( sg->skill_lv ) { case 1: case 2: hp = 3; sp = 2; break; case 3: case 4: hp = 4; sp = 3; break; case 5: default: hp = 5; sp = 4; break; } hp = tstatus->max_hp * hp / 100; sp = tstatus->max_sp * sp / 100; if (tstatus->hp < tstatus->max_hp) clif_skill_nodamage(&unit->bl, bl, AL_HEAL, hp, 1); if (tstatus->sp < tstatus->max_sp) clif_skill_nodamage(&unit->bl, bl, MG_SRECOVERY, sp, 1); if (tsc && tsc->getSCE(SC_AKAITSUKI) && hp) hp = ~hp + 1; status_heal(bl, hp, sp, 3); } if (sg->val1 % 5 == 0) { // Reveal hidden players every 5 seconds // Doesn't remove Invisibility or Chase Walk. status_change_end(bl,SC_HIDING); status_change_end(bl,SC_CLOAKING); status_change_end(bl,SC_CLOAKINGEXCEED); status_change_end(bl,SC_CAMOUFLAGE); status_change_end(bl,SC_NEWMOON); if (tsc && tsc->getSCE(SC__SHADOWFORM) && rnd() % 100 < 100 - tsc->getSCE(SC__SHADOWFORM)->val1 * 10) // [100 - (Skill Level x 10)] % status_change_end(bl, SC__SHADOWFORM); } sc_start(ss, bl, type, 100, sg->skill_lv, sg->interval + 100); } /* Enable this if kRO fix the current skill. Currently no damage on undead and demon monster. [Jobbie] else if( battle_check_target(ss, bl, BCT_ENEMY) > 0 && battle_check_undead(tstatus->race, tstatus->def_ele) ) skill_castend_damage_id(&src->bl, bl, sg->skill_id, sg->skill_lv, 0, 0);*/ break; case UNT_DIMENSIONDOOR: if( tsd && !map_getmapflag(bl->m, MF_NOTELEPORT) ) pc_randomwarp(tsd,CLR_TELEPORT); else if( bl->type == BL_MOB && battle_config.mob_warp&8 ) unit_warp(bl,-1,-1,-1,CLR_TELEPORT); break; case UNT_REVERBERATION: clif_changetraplook(&unit->bl,UNT_USED_TRAPS); map_foreachinrange(skill_trap_splash, &unit->bl, skill_get_splash(sg->skill_id, sg->skill_lv), sg->bl_flag, &unit->bl, tick); sg->limit = DIFF_TICK(tick,sg->tick) + 1000; sg->unit_id = UNT_USED_TRAPS; break; case UNT_SEVERE_RAINSTORM: if( battle_check_target(&unit->bl, bl, BCT_ENEMY) > 0 ) skill_attack(BF_WEAPON,ss,&unit->bl,bl,WM_SEVERE_RAINSTORM_MELEE,sg->skill_lv,tick,0); break; case UNT_NETHERWORLD: if (!status_bl_has_mode(bl,MD_STATUSIMMUNE) || (!map_flag_gvg2(ss->m) && battle_check_target(&unit->bl,bl,BCT_PARTY) < 0)) { if (!(tsc && tsc->getSCE(type))) { sc_start(ss, bl, type, 100, sg->skill_lv, skill_get_time2(sg->skill_id,sg->skill_lv)); sg->limit = DIFF_TICK(tick,sg->tick); sg->unit_id = UNT_USED_TRAPS; } } break; case UNT_THORNS_TRAP: if( tsc ) { if( !sg->val2 ) { t_tick sec = skill_get_time2(sg->skill_id, sg->skill_lv); if( sc_start(ss, bl, type, 100, sg->skill_lv, sec) ) { const struct TimerData* td = tsc->getSCE(type)?get_timer(tsc->getSCE(type)->timer):NULL; if( td ) sec = DIFF_TICK(td->tick, tick); ///map_moveblock(bl, src->bl.x, src->bl.y, tick); // in official server it doesn't behave like this. [malufett] clif_fixpos(bl); sg->val2 = bl->id; } else sec = 3000; // Couldn't trap it? sg->limit = DIFF_TICK(tick, sg->tick) + sec; } else if( tsc->getSCE(SC_THORNSTRAP) && bl->id == sg->val2 ) skill_attack(skill_get_type(GN_THORNS_TRAP), ss, ss, bl, sg->skill_id, sg->skill_lv, tick, SD_LEVEL|SD_ANIMATION); } break; case UNT_WALLOFTHORN: if (unit->val2-- <= 0) // Max hit reached break; if (status_bl_has_mode(bl,MD_STATUSIMMUNE)) break; // This skill doesn't affect to Boss monsters. [iRO Wiki] skill_blown(&unit->bl, bl, skill_get_blewcount(sg->skill_id, sg->skill_lv), unit_getdir(bl), BLOWN_IGNORE_NO_KNOCKBACK); skill_addtimerskill(ss, tick + 100, bl->id, unit->bl.x, unit->bl.y, sg->skill_id, sg->skill_lv, skill_get_type(sg->skill_id), 4|SD_LEVEL); break; case UNT_DEMONIC_FIRE: switch( sg->val2 ) { case 1: default: sc_start4(ss, bl, SC_BURNING, 4 + 4 * sg->skill_lv, sg->skill_lv, 1000, ss->id, 0, skill_get_time2(sg->skill_id, sg->skill_lv)); skill_attack(skill_get_type(skill_id), ss, &unit->bl, bl, sg->skill_id, sg->skill_lv + 10 * sg->val2, tick, 0); break; } break; case UNT_ZEPHYR: if (ss == bl) break; // Doesn't affect the Elemental sc_start(ss, bl, type, 100, sg->skill_lv, sg->interval); break; case UNT_CLOUD_KILL: if (tsc && !tsc->getSCE(type)) status_change_start(ss, bl, type, 10000, sg->skill_lv, ss->id, 0, 0, skill_get_time(sg->skill_id, sg->skill_lv), SCSTART_NOTICKDEF); skill_attack(skill_get_type(sg->skill_id), ss, &unit->bl, bl, sg->skill_id, sg->skill_lv, tick, 0); break; case UNT_VACUUM_EXTREME: if (tsc && (tsc->getSCE(SC_HALLUCINATIONWALK) || tsc->getSCE(SC_NPC_HALLUCINATIONWALK) || tsc->getSCE(SC_HOVERING) || tsc->getSCE(SC_VACUUM_EXTREME) || (tsc->getSCE(SC_VACUUM_EXTREME_POSTDELAY) && tsc->getSCE(SC_VACUUM_EXTREME_POSTDELAY)->val2 == sg->group_id))) // Ignore post delay from other vacuum (this will make stack effect enabled) return 0; // Apply effect and suck targets one-by-one each n seconds sc_start4(ss, bl, type, 100, sg->skill_lv, sg->group_id, (sg->val1 << 16) | (sg->val2), ++sg->val3 * 500, (sg->limit - DIFF_TICK(tick, sg->tick))); break; case UNT_BANDING: if( battle_check_target(&unit->bl, bl, BCT_ENEMY) > 0 && !(tsc && tsc->getSCE(SC_BANDING_DEFENCE)) ) sc_start(ss, bl, SC_BANDING_DEFENCE, (status_get_lv(&unit->bl) / 5) + (sg->skill_lv * 5) - (status_get_agi(bl) / 10), 90, skill_get_time2(sg->skill_id, sg->skill_lv)); break; case UNT_FIRE_MANTLE: if( battle_check_target(&unit->bl, bl, BCT_ENEMY) > 0 ) skill_attack(BF_MAGIC,ss,&unit->bl,bl,sg->skill_id,sg->skill_lv,tick,0); break; case UNT_ZENKAI_WATER: case UNT_ZENKAI_LAND: case UNT_ZENKAI_FIRE: case UNT_ZENKAI_WIND: if( battle_check_target(&unit->bl,bl,BCT_ENEMY) > 0 ) { switch( sg->unit_id ) { case UNT_ZENKAI_WATER: switch (rnd()%2 + 1) { case 1: sc_start(ss, bl, SC_FREEZE, sg->val1*5, sg->skill_lv, skill_get_time2(sg->skill_id, sg->skill_lv)); break; case 2: sc_start(ss, bl, SC_FREEZING, sg->val1*5, sg->skill_lv, skill_get_time2(sg->skill_id, sg->skill_lv)); break; } break; case UNT_ZENKAI_LAND: switch (rnd()%2 + 1) { case 1: sc_start2(ss, bl, SC_STONEWAIT, sg->val1*5, sg->skill_lv, ss->id, skill_get_time2(sg->skill_id, sg->skill_lv), skill_get_time(sg->skill_id, sg->skill_lv)); break; case 2: sc_start2(ss, bl, SC_POISON, sg->val1*5, sg->skill_lv, ss->id, skill_get_time2(sg->skill_id, sg->skill_lv)); break; } break; case UNT_ZENKAI_FIRE: sc_start4(ss, bl, SC_BURNING, sg->val1*5, sg->skill_lv, 1000, ss->id, 0, skill_get_time2(sg->skill_id, sg->skill_lv)); break; case UNT_ZENKAI_WIND: switch (rnd()%3 + 1) { case 1: sc_start(ss, bl, SC_SLEEP, sg->val1*5, sg->skill_lv, skill_get_time2(sg->skill_id, sg->skill_lv)); break; case 2: sc_start(ss, bl, SC_SILENCE, sg->val1*5, sg->skill_lv, skill_get_time2(sg->skill_id, sg->skill_lv)); break; case 3: sc_start(ss, bl, SC_DEEPSLEEP, sg->val1*5, sg->skill_lv, skill_get_time2(sg->skill_id, sg->skill_lv)); break; } break; } } else sc_start2(ss, bl,type,100,sg->val1,sg->val2,skill_get_time2(sg->skill_id, sg->skill_lv)); break; case UNT_POISON_MIST: skill_attack(BF_MAGIC, ss, &unit->bl, bl, sg->skill_id, sg->skill_lv, tick, 0); status_change_start(ss, bl, SC_POISON_MIST, (10 + 10 * sg->skill_lv)*100, sg->skill_lv, sg->skill_id, 0, 0, skill_get_time2(sg->skill_id, sg->skill_lv), SCSTART_NOTICKDEF|SCSTART_NORATEDEF); break; case UNT_CHAOSPANIC: if (tsc && tsc->getSCE(type)) break; status_change_start(ss, bl, type, 3500 + (sg->skill_lv * 1500), sg->skill_lv, 0, 0, 1, skill_get_time2(sg->skill_id, sg->skill_lv), SCSTART_NOAVOID|SCSTART_NORATEDEF|SCSTART_NOTICKDEF); break; case UNT_B_TRAP: if (tsc && tsc->getSCE(type)) break; sc_start(ss, bl, type, 100, sg->skill_lv, skill_get_time2(sg->skill_id,sg->skill_lv)); unit->val2++; // Mark as ever been used break; case UNT_FIRE_RAIN: clif_skill_damage(ss,bl,tick,status_get_amotion(ss),0, skill_attack(skill_get_type(sg->skill_id),ss,&unit->bl,bl,sg->skill_id,sg->skill_lv,tick,SD_ANIMATION|SD_SPLASH), 1,sg->skill_id,sg->skill_lv,DMG_SINGLE); break; case UNT_MAGMA_ERUPTION: skill_attack(skill_get_type(NC_MAGMA_ERUPTION_DOTDAMAGE), ss, &unit->bl, bl, NC_MAGMA_ERUPTION_DOTDAMAGE, sg->skill_lv, tick, 0); if (sg->skill_id == NC_MAGMA_ERUPTION) skill_attack(skill_get_type(NC_MAGMA_ERUPTION_DOTDAMAGE), ss, &unit->bl, bl, NC_MAGMA_ERUPTION_DOTDAMAGE, sg->skill_lv, tick, 0); else skill_attack(skill_get_type(NPC_MAGMA_ERUPTION_DOTDAMAGE), ss, &unit->bl, bl, NPC_MAGMA_ERUPTION_DOTDAMAGE, sg->skill_lv, tick, 0); break; case UNT_ACIDIFIED_ZONE_WATER: skill_attack(skill_get_type(BO_ACIDIFIED_ZONE_WATER_ATK), ss, &unit->bl, bl, BO_ACIDIFIED_ZONE_WATER_ATK, sg->skill_lv, tick, 0); break; case UNT_ACIDIFIED_ZONE_GROUND: skill_attack(skill_get_type(BO_ACIDIFIED_ZONE_GROUND_ATK), ss, &unit->bl, bl, BO_ACIDIFIED_ZONE_GROUND_ATK, sg->skill_lv, tick, 0); break; case UNT_ACIDIFIED_ZONE_WIND: skill_attack(skill_get_type(BO_ACIDIFIED_ZONE_WIND_ATK), ss, &unit->bl, bl, BO_ACIDIFIED_ZONE_WIND_ATK, sg->skill_lv, tick, 0); break; case UNT_ACIDIFIED_ZONE_FIRE: skill_attack(skill_get_type(BO_ACIDIFIED_ZONE_FIRE_ATK), ss, &unit->bl, bl, BO_ACIDIFIED_ZONE_FIRE_ATK, sg->skill_lv, tick, 0); break; case UNT_ASTRAL_STRIKE: skill_attack(skill_get_type(AG_ASTRAL_STRIKE_ATK), ss, &unit->bl, bl, AG_ASTRAL_STRIKE_ATK, sg->skill_lv, tick, 0); break; case UNT_ABYSS_SQUARE: { short flag = 0; // Check to see if the caster is in the AoE. if (distance_bl(ss, &unit->bl) <= unit->range) flag |= 2; // If yes, skill hits twice. skill_attack(skill_get_type(sg->skill_id), ss, &unit->bl, bl, sg->skill_id, sg->skill_lv, tick, flag); } break; } if (bl->type == BL_MOB && ss != bl) mobskill_event((TBL_MOB*)bl, ss, tick, MSC_SKILLUSED|(skill_id<<16)); return skill_id; } /** * Triggered when a char steps out of a skill unit * @param src Skill unit from char moved out * @param bl Char * @param tick */ int skill_unit_onout(struct skill_unit *src, struct block_list *bl, t_tick tick) { status_change *sc; struct status_change_entry *sce; enum sc_type type; nullpo_ret(src); nullpo_ret(bl); std::shared_ptr<s_skill_unit_group> sg = src->group; if (sg == nullptr) return 0; sc = status_get_sc(bl); type = skill_get_sc(sg->skill_id); sce = (sc && type != SC_NONE)?sc->getSCE(type):NULL; if (bl->prev == NULL || (status_isdead(bl) && sg->unit_id != UNT_ANKLESNARE)) //Need to delete the trap if the source died. return 0; switch(sg->unit_id){ case UNT_SAFETYWALL: case UNT_PNEUMA: case UNT_EPICLESIS://Arch Bishop if (sce) status_change_end(bl, type); break; #ifndef RENEWAL case UNT_BASILICA: if (sce && sce->val4 != bl->id) status_change_end(bl, type); break; #endif case UNT_HERMODE: //Clear Hermode if the owner moved. if (sce && sce->val3 == BCT_SELF && sce->val4 == sg->group_id) status_change_end(bl, type); break; case UNT_DISSONANCE: case UNT_UGLYDANCE: //Used for updating timers in song overlap instances { for(uint16 i = BA_WHISTLE; i <= DC_SERVICEFORYOU; i++) { if(skill_get_inf2(i, INF2_ISSONG)) { type = skill_get_sc(i); sce = (sc && type != SC_NONE)?sc->getSCE(type):NULL; if(sce) return i; } } } case UNT_WHISTLE: case UNT_ASSASSINCROSS: case UNT_POEMBRAGI: case UNT_APPLEIDUN: case UNT_HUMMING: case UNT_DONTFORGETME: case UNT_FORTUNEKISS: case UNT_SERVICEFORYOU: if (sg->src_id==bl->id && !(sc && sc->getSCE(SC_SPIRIT) && sc->getSCE(SC_SPIRIT)->val2 == SL_BARDDANCER)) return -1; } return sg->skill_id; } /** * Triggered when a char steps out of a skill group (entirely) [Skotlex] * @param skill_id Skill ID * @param bl A char * @param tick */ int skill_unit_onleft(uint16 skill_id, struct block_list *bl, t_tick tick) { status_change *sc; struct status_change_entry *sce; enum sc_type type; sc = status_get_sc(bl); if (sc && !sc->count) sc = NULL; type = skill_get_sc(skill_id); sce = (sc && type != SC_NONE)?sc->getSCE(type):NULL; switch (skill_id) { case WZ_QUAGMIRE: if (bl->type==BL_MOB) break; if (sce) status_change_end(bl, type); break; case BD_LULLABY: case BD_RICHMANKIM: case BD_ETERNALCHAOS: case BD_DRUMBATTLEFIELD: case BD_RINGNIBELUNGEN: case BD_ROKISWEIL: case BD_INTOABYSS: case BD_SIEGFRIED: if(sc && sc->getSCE(SC_DANCING) && (sc->getSCE(SC_DANCING)->val1&0xFFFF) == skill_id) { //Check if you just stepped out of your ensemble skill to cancel dancing. [Skotlex] //We don't check for SC_LONGING because someone could always have knocked you back and out of the song/dance. //FIXME: This code is not perfect, it doesn't checks for the real ensemble's owner, //it only checks if you are doing the same ensemble. So if there's two chars doing an ensemble //which overlaps, by stepping outside of the other parther's ensemble will cause you to cancel //your own. Let's pray that scenario is pretty unlikely and noone will complain too much about it. status_change_end(bl, SC_DANCING); } case MH_STEINWAND: case MG_SAFETYWALL: case AL_PNEUMA: case SA_VOLCANO: case SA_DELUGE: case SA_VIOLENTGALE: case CG_HERMODE: #ifndef RENEWAL case HW_GRAVITATION: case HP_BASILICA: #endif case NJ_SUITON: case SC_MAELSTROM: case EL_WATER_BARRIER: case EL_ZEPHYR: case EL_POWER_OF_GAIA: case SO_WARMER: case SO_FIRE_INSIGNIA: case SO_WATER_INSIGNIA: case SO_WIND_INSIGNIA: case SO_EARTH_INSIGNIA: case SJ_BOOKOFCREATINGSTAR: case SC_BLOODYLUST: case GN_FIRE_EXPANSION_SMOKE_POWDER: case GN_FIRE_EXPANSION_TEAR_GAS: case LG_KINGS_GRACE: case NC_STEALTHFIELD: case NC_NEUTRALBARRIER: case SU_NYANGGRASS: if (sce) status_change_end(bl, type); break; case BA_DISSONANCE: case DC_UGLYDANCE: //Used for updating song timers in overlap instances { for(uint16 i = BA_WHISTLE; i <= DC_SERVICEFORYOU; i++){ if(skill_get_inf2(i, INF2_ISSONG)){ type = skill_get_sc(i); sce = (sc && type != SC_NONE)?sc->getSCE(type):NULL; if(sce && !sce->val4){ //We don't want dissonance updating this anymore delete_timer(sce->timer, status_change_timer); sce->val4 = 1; //Store the fact that this is a "reduced" duration effect. sce->timer = add_timer(tick+skill_get_time2(i,1), status_change_timer, bl->id, type); } } } } break; case BA_POEMBRAGI: case BA_WHISTLE: case BA_ASSASSINCROSS: case BA_APPLEIDUN: case DC_HUMMING: case DC_DONTFORGETME: case DC_FORTUNEKISS: case DC_SERVICEFORYOU: if (sce) { delete_timer(sce->timer, status_change_timer); //NOTE: It'd be nice if we could get the skill_lv for a more accurate extra time, but alas... //not possible on our current implementation. sce->val4 = 1; //Store the fact that this is a "reduced" duration effect. sce->timer = add_timer(tick+skill_get_time2(skill_id,1), status_change_timer, bl->id, type); } break; case PF_FOGWALL: if (sce) { status_change_end(bl, type); if ((sce=sc->getSCE(SC_BLIND))) { if (bl->type == BL_PC) //Players get blind ended inmediately, others have it still for 30 secs. [Skotlex] status_change_end(bl, SC_BLIND); else { delete_timer(sce->timer, status_change_timer); sce->timer = add_timer(30000+tick, status_change_timer, bl->id, SC_BLIND); } } } break; case GD_LEADERSHIP: case GD_GLORYWOUNDS: case GD_SOULCOLD: case GD_HAWKEYES: if( !(sce && sce->val4) ) status_change_end(bl, type); break; } return skill_id; } /*========================================== * Invoked when a unit cell has been placed/removed/deleted. * flag values: * flag&1: Invoke onplace function (otherwise invoke onout) * flag&4: Invoke a onleft call (the unit might be scheduled for deletion) * flag&8: Recursive *------------------------------------------*/ static int skill_unit_effect(struct block_list* bl, va_list ap) { struct skill_unit* unit = va_arg(ap,struct skill_unit*); t_tick tick = va_arg(ap,t_tick); unsigned int flag = va_arg(ap,unsigned int); uint16 skill_id; bool dissonance = false; bool isTarget = false; if( (!unit->alive && !(flag&4)) || bl->prev == NULL ) return 0; std::shared_ptr<s_skill_unit_group> group = unit->group; if (group == nullptr) return 0; if( !(flag&8) ) { dissonance = skill_dance_switch(unit, 0); //Target-type check. isTarget = group->bl_flag & bl->type && battle_check_target( &unit->bl, bl, group->target_flag ) > 0; } //Necessary in case the group is deleted after calling on_place/on_out [Skotlex] skill_id = group->skill_id; if( isTarget ){ if( flag&1 ) skill_unit_onplace(unit,bl,tick); else { if( skill_unit_onout(unit,bl,tick) == -1 ) return 0; // Don't let a Bard/Dancer update their own song timer } if( flag&4 ) skill_unit_onleft(skill_id, bl, tick); } else if( !isTarget && flag&4 && ( group->state.song_dance&0x1 || ( group->src_id == bl->id && group->state.song_dance&0x2 ) ) ) skill_unit_onleft(skill_id, bl, tick);//Ensemble check to terminate it. if( dissonance ) { skill_dance_switch(unit, 1); //we placed a dissonance, let's update map_foreachincell(skill_unit_effect,unit->bl.m,unit->bl.x,unit->bl.y,group->bl_flag,&unit->bl,gettick(),4|8); } return 0; } /** * Check skill unit while receiving damage * @param unit Skill unit * @param damage Received damage * @return Damage */ int64 skill_unit_ondamaged(struct skill_unit *unit, int64 damage) { nullpo_ret(unit); std::shared_ptr<s_skill_unit_group> sg = unit->group; if (sg == nullptr) return 0; switch( sg->unit_id ) { case UNT_BLASTMINE: case UNT_SKIDTRAP: case UNT_LANDMINE: case UNT_SHOCKWAVE: case UNT_SANDMAN: case UNT_FLASHER: case UNT_CLAYMORETRAP: case UNT_FREEZINGTRAP: case UNT_ANKLESNARE: case UNT_ICEWALL: case UNT_WALLOFTHORN: case UNT_REVERBERATION: case UNT_NETHERWORLD: unit->val1 -= (int)cap_value(damage,INT_MIN,INT_MAX); break; default: damage = 0; break; } return damage; } /** * Check char condition around the skill caster * @param bl Char around area * @param *c Counter for 'valid' condition found * @param *p_sd Stores 'rid' of char found * @param skill_id Skill ID * @param skill_lv Level of used skill */ int skill_check_condition_char_sub (struct block_list *bl, va_list ap) { int *c, skill_id; struct block_list *src; map_session_data *sd; map_session_data *tsd; int *p_sd; //Contains the list of characters found. nullpo_ret(bl); nullpo_ret(tsd=(map_session_data*)bl); nullpo_ret(src=va_arg(ap,struct block_list *)); nullpo_ret(sd=(map_session_data*)src); c=va_arg(ap,int *); p_sd = va_arg(ap, int *); skill_id = va_arg(ap,int); bool is_chorus = skill_get_inf2(skill_id, INF2_ISCHORUS); if (skill_id == PR_BENEDICTIO) { if (*c >= 2) // Check for two companions for Benedictio. [Skotlex] return 0; } else if (is_chorus) { if (*c == MAX_PARTY) // Check for partners for Chorus; Cap if the entire party is accounted for. return 0; } else if (*c >= 1) // Check for one companion for all other cases. return 0; if (bl == src) return 0; if(pc_isdead(tsd)) return 0; if (tsd->sc.cant.cast) return 0; if( is_chorus ) { if( tsd->status.party_id && sd->status.party_id && tsd->status.party_id == sd->status.party_id && (tsd->class_&MAPID_THIRDMASK) == MAPID_MINSTRELWANDERER ) p_sd[(*c)++] = tsd->bl.id; return 1; } else { switch(skill_id) { case PR_BENEDICTIO: { uint8 dir = map_calc_dir(&sd->bl,tsd->bl.x,tsd->bl.y); dir = (unit_getdir(&sd->bl) + dir)%8; //This adjusts dir to account for the direction the sd is facing. if ((tsd->class_&MAPID_BASEMASK) == MAPID_ACOLYTE && (dir == 2 || dir == 6) //Must be standing to the left/right of Priest. && sd->status.sp >= 10) p_sd[(*c)++]=tsd->bl.id; return 1; } case AB_ADORAMUS: // Adoramus does not consume Blue Gemstone when there is at least 1 Priest class next to the caster if( (tsd->class_&MAPID_UPPERMASK) == MAPID_PRIEST ) p_sd[(*c)++] = tsd->bl.id; return 1; case TR_GEF_NOCTURN: case TR_ROKI_CAPRICCIO: case TR_AIN_RHAPSODY: case TR_MUSICAL_INTERLUDE: case TR_JAWAII_SERENADE: case TR_NIPELHEIM_REQUIEM: case TR_PRON_MARCH:// Does the partner's learned skill level affects anything? [Rytech] if (sd->status.sex != tsd->status.sex && (tsd->class_&MAPID_FOURTHMASK) == MAPID_TROUBADOURTROUVERE && sd->status.party_id && tsd->status.party_id && sd->status.party_id == tsd->status.party_id) p_sd[(*c)++] = tsd->bl.id; return 1; default: //Warning: Assuming Ensemble Dance/Songs for code speed. [Skotlex] { uint16 skill_lv; if(pc_issit(tsd) || !unit_can_move(&tsd->bl)) return 0; if (sd->status.sex != tsd->status.sex && (tsd->class_&MAPID_UPPERMASK) == MAPID_BARDDANCER && (skill_lv = pc_checkskill(tsd, skill_id)) > 0 && (tsd->weapontype1==W_MUSICAL || tsd->weapontype1==W_WHIP) && sd->status.party_id && tsd->status.party_id && sd->status.party_id == tsd->status.party_id && !tsd->sc.getSCE(SC_DANCING)) { p_sd[(*c)++]=tsd->bl.id; return skill_lv; } } break; } } return 0; } /** * Checks and stores partners for ensemble skills [Skotlex] * Max partners is 2. * @param sd Caster * @param skill_id * @param skill_lv * @param range Area range to check * @param cast_flag Special handle */ int skill_check_pc_partner(map_session_data *sd, uint16 skill_id, uint16 *skill_lv, int range, int cast_flag) { static int c=0; static int p_sd[MAX_PARTY]; int i; bool is_chorus = skill_get_inf2(skill_id, INF2_ISCHORUS); if (!sd) return 0; if (!battle_config.player_skill_partner_check || pc_has_permission(sd, PC_PERM_SKILL_UNCONDITIONAL)) return is_chorus ? MAX_PARTY : 99; //As if there were infinite partners. if (cast_flag) { //Execute the skill on the partners. map_session_data* tsd; switch (skill_id) { case PR_BENEDICTIO: case WM_GREAT_ECHO: for (i = 0; i < c; i++) { if ((tsd = map_id2sd(p_sd[i])) != NULL) status_charge(&tsd->bl, 0, (skill_id == PR_BENEDICTIO) ? 10 : skill_get_sp(skill_id, *skill_lv) / 2); } return c; case AB_ADORAMUS: if( c > 0 && (tsd = map_id2sd(p_sd[0])) != NULL ) { i = 2 * (*skill_lv); status_charge(&tsd->bl, 0, i); } break; default: if( is_chorus ) break;//Chorus skills are not to be parsed as ensembles if (skill_get_inf2(skill_id, INF2_ISENSEMBLE)) { if (c > 0 && sd->sc.getSCE(SC_DANCING) && (tsd = map_id2sd(p_sd[0])) != NULL) { sd->sc.getSCE(SC_DANCING)->val4 = tsd->bl.id; sc_start4(&sd->bl,&tsd->bl,SC_DANCING,100,skill_id,sd->sc.getSCE(SC_DANCING)->val2,*skill_lv,sd->bl.id,skill_get_time(skill_id,*skill_lv)+1000); clif_skill_nodamage(&tsd->bl, &sd->bl, skill_id, *skill_lv, 1); tsd->skill_id_dance = skill_id; tsd->skill_lv_dance = *skill_lv; #ifdef RENEWAL sc_start(&sd->bl, &sd->bl, SC_ENSEMBLEFATIGUE, 100, 1, skill_get_time(CG_SPECIALSINGER, *skill_lv)); sc_start(&sd->bl, &tsd->bl, SC_ENSEMBLEFATIGUE, 100, 1, skill_get_time(CG_SPECIALSINGER, *skill_lv)); #endif } } return c; } } //Else: new search for partners. c = 0; memset (p_sd, 0, sizeof(p_sd)); i = map_foreachinallrange(skill_check_condition_char_sub, &sd->bl, range, BL_PC, &sd->bl, &c, &p_sd, skill_id); if ( skill_id != PR_BENEDICTIO && skill_id != AB_ADORAMUS && skill_id != WM_GREAT_ECHO && !(skill_id >= TR_GEF_NOCTURN && skill_id <= TR_PRON_MARCH)) //Apply the average lv to encore skills. *skill_lv = (i+(*skill_lv))/(c+1); //I know c should be one, but this shows how it could be used for the average of n partners. return c; } /** * Sub function to count how many spawned mob is around. * Some skills check with matched AI. * @param rid Source ID * @param mob_class Monster ID * @param skill_id Used skill * @param *c Counter for found monster */ static int skill_check_condition_mob_master_sub(struct block_list *bl, va_list ap) { int *c,src_id,mob_class,skill; uint16 ai; struct mob_data *md; md=(struct mob_data*)bl; src_id=va_arg(ap,int); mob_class=va_arg(ap,int); skill=va_arg(ap,int); c=va_arg(ap,int *); switch (skill) { case AM_SPHEREMINE: ai = AI_SPHERE; break; case AM_CANNIBALIZE: ai = AI_FLORA; break; case KO_ZANZOU: ai = AI_ZANZOU; break; case MH_SUMMON_LEGION: ai = AI_LEGION; break; case NC_SILVERSNIPER: case NC_MAGICDECOY: ai = AI_FAW; break; case MT_SUMMON_ABR_BATTLE_WARIOR: case MT_SUMMON_ABR_DUAL_CANNON: case MT_SUMMON_ABR_MOTHER_NET: case MT_SUMMON_ABR_INFINITY: ai = AI_ABR; break; case BO_WOODENWARRIOR: case BO_WOODEN_FAIRY: case BO_CREEPER: case BO_HELLTREE: ai = AI_BIONIC; break; default: ai = AI_FLORA; break; } if( md->master_id != src_id || md->special_state.ai != ai) return 0; //Non alchemist summoned mobs have nothing to do here. if(md->mob_id==mob_class) (*c)++; return 1; } /** * Determines if a given skill should be made to consume ammo * when used by the player. [Skotlex] * @param sd Player * @param skill_id Skill ID * @return True if skill is need ammo; False otherwise. */ int skill_isammotype(map_session_data *sd, unsigned short skill_id) { std::shared_ptr<s_skill_db> skill = skill_db.find(skill_id); return ( battle_config.arrow_decrement == 2 && (sd->status.weapon == W_BOW || (sd->status.weapon >= W_REVOLVER && sd->status.weapon <= W_GRENADE)) && skill_id != HT_PHANTASMIC && skill->skill_type == BF_WEAPON && !skill->nk[NK_NODAMAGE] && !skill_get_spiritball(skill_id,1) //Assume spirit spheres are used as ammo instead. ); } /** * Check SC required to cast a skill * @param sc * @param skill_id * @return True if condition is met, False otherwise **/ static bool skill_check_condition_sc_required(map_session_data *sd, unsigned short skill_id, struct s_skill_condition *require) { if (require == nullptr || require->status.empty()) return true; nullpo_ret(sd); status_change *sc = &sd->sc; if (sc == nullptr) { clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); return false; } // May have multiple requirements for (const auto &reqStatus : require->status) { if (reqStatus == SC_NONE) continue; useskill_fail_cause cause; switch (reqStatus) { // Official fail message case SC_PUSH_CART: cause = USESKILL_FAIL_CART; break; case SC_POISONINGWEAPON: cause = USESKILL_FAIL_GC_POISONINGWEAPON; break; case SC_WEAPONBLOCK_ON: cause = USESKILL_FAIL_GC_WEAPONBLOCKING; break; default: cause = USESKILL_FAIL_LEVEL; break; } if (!sc->getSCE(reqStatus)) { clif_skill_fail(sd, skill_id, cause, 0); return false; } } return true; } /** * Check skill condition when cast begin * For ammo, only check if the skill need ammo * For checking ammo requirement (type and amount) will be skill_check_condition_castend * @param sd Player who uses skill * @param skill_id ID of used skill * @param skill_lv Level of used skill * @return true: All condition passed, false: Failed */ bool skill_check_condition_castbegin(map_session_data* sd, uint16 skill_id, uint16 skill_lv) { struct status_data *status; status_change *sc; struct s_skill_condition require; int i; nullpo_retr(false,sd); if (sd->chatID) return false; if( pc_has_permission(sd, PC_PERM_SKILL_UNCONDITIONAL) && sd->skillitem != skill_id ) { //GMs don't override the skillItem check, otherwise they can use items without them being consumed! [Skotlex] sd->state.arrow_atk = skill_get_ammotype(skill_id)?1:0; //Need to do arrow state check. sd->spiritball_old = sd->spiritball; //Need to do Spiritball check. sd->soulball_old = sd->soulball; //Need to do Soulball check. sd->servantball_old = sd->servantball; //Need to do Servantball check. sd->abyssball_old = sd->abyssball; //Need to do Abyssball check. return true; } switch( sd->menuskill_id ) { case AM_PHARMACY: switch( skill_id ) { case AM_PHARMACY: case AC_MAKINGARROW: case BS_REPAIRWEAPON: case AM_TWILIGHT1: case AM_TWILIGHT2: case AM_TWILIGHT3: return false; } break; case GN_MIX_COOKING: case GN_MAKEBOMB: case GN_S_PHARMACY: case GN_CHANGEMATERIAL: case MT_M_MACHINE: case BO_BIONIC_PHARMACY: if( sd->menuskill_id != skill_id ) return false; break; } status = &sd->battle_status; sc = &sd->sc; if( !sc->count ) sc = NULL; if( sd->skillitem == skill_id ) { if( sd->state.abra_flag ) // Hocus-Pocus was used. [Inkfish] sd->state.abra_flag = 0; else { // When a target was selected, consume items that were skipped in pc_use_item [Skotlex] if( (i = sd->itemindex) == -1 || sd->inventory.u.items_inventory[i].nameid != sd->itemid || sd->inventory_data[i] == NULL || sd->inventory_data[i]->flag.delay_consume == DELAYCONSUME_NONE || sd->inventory.u.items_inventory[i].amount < 1 ) { //Something went wrong, item exploit? sd->itemid = 0; sd->itemindex = -1; return false; } //Consume sd->itemid = 0; sd->itemindex = -1; if( (skill_id == WZ_EARTHSPIKE && sc && sc->getSCE(SC_EARTHSCROLL) && rnd()%100 > sc->getSCE(SC_EARTHSCROLL)->val2) || sd->inventory_data[i]->flag.delay_consume & DELAYCONSUME_NOCONSUME ) // [marquis007] ; //Do not consume item. else if( sd->inventory.u.items_inventory[i].expire_time == 0 ) pc_delitem(sd,i,1,0,0,LOG_TYPE_CONSUME); // Rental usable items are not consumed until expiration } if(!sd->skillitem_keep_requirement) return true; } if( pc_is90overweight(sd) ) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_WEIGHTOVER,0); return false; } if( sc && ( sc->getSCE(SC__SHADOWFORM) || sc->getSCE(SC__IGNORANCE) ) ) return false; //Checks if disabling skill - in which case no SP requirements are necessary if( sc && skill_disable_check(*sc,skill_id)) return true; std::bitset<INF2_MAX> inf2 = skill_db.find(skill_id)->inf2; // Check the skills that can be used while mounted on a warg if( pc_isridingwug(sd) ) { if(!inf2[INF2_ALLOWONWARG]) return false; // in official there is no message. } if( pc_ismadogear(sd) ) { // Skills that are unusable when Mado is equipped. [Jobbie] if(!inf2[INF2_ALLOWONMADO]){ clif_skill_fail(sd,skill_id,USESKILL_FAIL_MADOGEAR_RIDE,0); return false; } } //if (skill_lv < 1 || skill_lv > MAX_SKILL_LEVEL) // return false; require = skill_get_requirement(sd,skill_id,skill_lv); //Can only update state when weapon/arrow info is checked. sd->state.arrow_atk = require.ammo?1:0; // perform skill-group checks if(skill_id != WM_GREAT_ECHO && inf2[INF2_ISCHORUS]) { if (skill_check_pc_partner(sd, skill_id, &skill_lv, AREA_SIZE, 0) < 1) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); return false; } } else if(inf2[INF2_ISENSEMBLE]) { if (skill_check_pc_partner(sd, skill_id, &skill_lv, 1, 0) < 1 && !(sc && sc->getSCE(SC_KVASIR_SONATA))) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); return false; } } // perform skill-specific checks (and actions) switch( skill_id ) { case RG_GRAFFITI: if (map_foreachinmap(skill_graffitiremover,sd->bl.m,BL_SKILL,0)) { // If a previous Graffiti exists skill fails to cast. clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); return false; } break; case SO_SPELLFIST: if(sd->skill_id_old != MG_FIREBOLT && sd->skill_id_old != MG_COLDBOLT && sd->skill_id_old != MG_LIGHTNINGBOLT) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); return false; } case SA_CASTCANCEL: if(sd->ud.skilltimer == INVALID_TIMER) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); return false; } break; case AS_CLOAKING: { if( skill_lv < 3 && ((sd->bl.type == BL_PC && battle_config.pc_cloak_check_type&1) || (sd->bl.type != BL_PC && battle_config.monster_cloak_check_type&1) )) { //Check for walls. static int dx[] = { 0, 1, 0, -1, -1, 1, 1, -1}; static int dy[] = {-1, 0, 1, 0, -1, -1, 1, 1}; int di; ARR_FIND( 0, 8, di, map_getcell(sd->bl.m, sd->bl.x+dx[di], sd->bl.y+dy[di], CELL_CHKNOPASS) != 0 ); if( di == 8 ) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); return false; } } break; } case AL_WARP: if(!battle_config.duel_allow_teleport && sd->duel_group) { // duel restriction [LuzZza] char output[128]; sprintf(output, msg_txt(sd,365), skill_get_name(AL_WARP)); clif_displaymessage(sd->fd, output); //"Duel: Can't use %s in duel." return false; } break; case AL_HOLYWATER: if(pc_search_inventory(sd,ITEMID_EMPTY_BOTTLE) < 0) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); return false; } break; case MO_CALLSPIRITS: if(sc && sc->getSCE(SC_RAISINGDRAGON)) skill_lv += sc->getSCE(SC_RAISINGDRAGON)->val1; if(sd->spiritball >= skill_lv) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); return false; } break; case MO_FINGEROFFENSIVE: case GS_FLING: case SR_RIDEINLIGHTNING: if( sd->spiritball > 0 && sd->spiritball < require.spiritball ) sd->spiritball_old = require.spiritball = sd->spiritball; else sd->spiritball_old = require.spiritball; break; case MO_CHAINCOMBO: if(!sc) return false; if(sc->getSCE(SC_BLADESTOP)) break; if(sc->getSCE(SC_COMBO) && sc->getSCE(SC_COMBO)->val1 == MO_TRIPLEATTACK) break; return false; case MO_COMBOFINISH: if(!(sc && sc->getSCE(SC_COMBO) && sc->getSCE(SC_COMBO)->val1 == MO_CHAINCOMBO)) return false; break; case CH_TIGERFIST: if(!(sc && sc->getSCE(SC_COMBO) && sc->getSCE(SC_COMBO)->val1 == MO_COMBOFINISH)) return false; break; case CH_CHAINCRUSH: if(!(sc && sc->getSCE(SC_COMBO))) return false; if(sc->getSCE(SC_COMBO)->val1 != MO_COMBOFINISH && sc->getSCE(SC_COMBO)->val1 != CH_TIGERFIST) return false; break; case SJ_SOLARBURST: if (!(sc && sc->getSCE(SC_COMBO) && sc->getSCE(SC_COMBO)->val1 == SJ_PROMINENCEKICK)) return 0; break; case MO_EXTREMITYFIST: // if(sc && sc->getSCE(SC_EXTREMITYFIST)) //To disable Asura during the 5 min skill block uncomment this... // return false; if( sc && (sc->getSCE(SC_BLADESTOP) || sc->getSCE(SC_CURSEDCIRCLE_ATKER)) ) break; if( sc && sc->getSCE(SC_COMBO) ) { switch(sc->getSCE(SC_COMBO)->val1) { case MO_COMBOFINISH: case CH_TIGERFIST: case CH_CHAINCRUSH: break; default: return false; } } else if( !unit_can_move(&sd->bl) ) { //Placed here as ST_MOVE_ENABLE should not apply if rooted or on a combo. [Skotlex] clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); return false; } #ifdef RENEWAL sd->spiritball_old = sd->spiritball; #endif break; case TK_MISSION: if( (sd->class_&MAPID_UPPERMASK) != MAPID_TAEKWON ) { // Cannot be used by Non-Taekwon classes clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); return false; } break; case ASC_EDP: #ifdef RENEWAL if (sd->weapontype1 == W_FIST && battle_config.switch_remove_edp&2) { #else if (sd->weapontype1 == W_FIST && battle_config.switch_remove_edp&1) { #endif clif_skill_fail(sd,skill_id,USESKILL_FAIL_THIS_WEAPON,0); return false; } break; case TK_READYCOUNTER: case TK_READYDOWN: case TK_READYSTORM: case TK_READYTURN: case TK_JUMPKICK: if( (sd->class_&MAPID_UPPERMASK) == MAPID_SOUL_LINKER ) { // Soul Linkers cannot use this skill clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); return false; } break; case TK_TURNKICK: case TK_STORMKICK: case TK_DOWNKICK: case TK_COUNTER: if ((sd->class_&MAPID_UPPERMASK) == MAPID_SOUL_LINKER) return false; //Anti-Soul Linker check in case you job-changed with Stances active. if(!(sc && sc->getSCE(SC_COMBO)) || sc->getSCE(SC_COMBO)->val1 == TK_JUMPKICK) return false; //Combo needs to be ready if (sc->getSCE(SC_COMBO)->val3) { //Kick chain //Do not repeat a kick. if (sc->getSCE(SC_COMBO)->val3 != skill_id) break; status_change_end(&sd->bl, SC_COMBO); return false; } if(sc->getSCE(SC_COMBO)->val1 != skill_id && !pc_is_taekwon_ranker(sd)) { //Cancel combo wait. unit_cancel_combo(&sd->bl); return false; } break; //Combo ready. #ifndef RENEWAL case BD_ADAPTATION: { int time; if(!(sc && sc->getSCE(SC_DANCING))) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); return false; } time = 1000*(sc->getSCE(SC_DANCING)->val3>>16); if (skill_get_time( (sc->getSCE(SC_DANCING)->val1&0xFFFF), //Dance Skill ID (sc->getSCE(SC_DANCING)->val1>>16)) //Dance Skill LV - time < skill_get_time2(skill_id,skill_lv)) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); return false; } } break; #endif case PR_BENEDICTIO: if (skill_check_pc_partner(sd, skill_id, &skill_lv, 1, 0) < 2) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); return false; } break; case SL_SMA: if(!sc || !(sc->getSCE(SC_SMA) || sc->getSCE(SC_USE_SKILL_SP_SHA))) return false; break; case HT_POWER: if(!(sc && sc->getSCE(SC_COMBO) && sc->getSCE(SC_COMBO)->val1 == AC_DOUBLE)) return false; break; #ifndef RENEWAL case CG_HERMODE: if(!npc_check_areanpc(1,sd->bl.m,sd->bl.x,sd->bl.y,skill_get_splash(skill_id, skill_lv))) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); return false; } break; #endif case CG_MOONLIT: //Check there's no wall in the range+1 area around the caster. [Skotlex] { int s,range = skill_get_splash(skill_id, skill_lv)+1; int size = range*2+1; for (s=0;s<size*size;s++) { int x = sd->bl.x+(s%size-range); int y = sd->bl.y+(s/size-range); if (map_getcell(sd->bl.m,x,y,CELL_CHKWALL)) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); return false; } } } break; #ifndef RENEWAL case PR_REDEMPTIO: { t_exp exp = pc_nextbaseexp(sd); uint32 exp_needp = battle_config.exp_cost_redemptio; if (exp_needp && (exp > 0 && get_percentage_exp(sd->status.base_exp, exp) < exp_needp)) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); //Not enough exp. return false; } break; } case HP_BASILICA: if( !sc || (sc && !sc->getSCE(SC_BASILICA))) { if( sd ) { // When castbegin, needs 7x7 clear area int s,range = skill_get_unit_layout_type(skill_id,skill_lv)+1; int size = range*2+1; for( s=0;s<size*size;s++ ) { int x = sd->bl.x+(s%size-range); int y = sd->bl.y+(s/size-range); if( map_getcell(sd->bl.m,x,y,CELL_CHKWALL) ) { clif_skill_fail(sd,skill_id,USESKILL_FAIL,0); return false; } } if( map_foreachinallrange(skill_count_wos, &sd->bl, range, BL_MOB|BL_PC, &sd->bl) ) { clif_skill_fail(sd,skill_id,USESKILL_FAIL,0); return false; } } } break; #endif case AM_TWILIGHT2: case AM_TWILIGHT3: if (!party_skill_check(sd, sd->status.party_id, skill_id, skill_lv)) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); return false; } break; case SG_SUN_WARM: case SG_MOON_WARM: case SG_STAR_WARM: if (sc && sc->getSCE(SC_MIRACLE)) break; i = skill_id-SG_SUN_WARM; if (sd->bl.m == sd->feel_map[i].m) break; clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); return false; break; case SG_SUN_COMFORT: case SG_MOON_COMFORT: case SG_STAR_COMFORT: if (sc && sc->getSCE(SC_MIRACLE)) break; i = skill_id-SG_SUN_COMFORT; if (sd->bl.m == sd->feel_map[i].m && (battle_config.allow_skill_without_day || sg_info[i].day_func())) break; clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); return false; case SG_FUSION: if (sc && sc->getSCE(SC_SPIRIT) && sc->getSCE(SC_SPIRIT)->val2 == SL_STAR) break; //Auron insists we should implement SP consumption when you are not Soul Linked. [Skotlex] //Only invoke on skill begin cast (instant cast skill). [Kevin] if( require.sp > 0 ) { if (status->sp < (unsigned int)require.sp) clif_skill_fail(sd,skill_id,USESKILL_FAIL_SP_INSUFFICIENT,0); else status_zap(&sd->bl, 0, require.sp); } return false; case GD_BATTLEORDER: case GD_REGENERATION: case GD_RESTORE: case GD_CHARGESHOUT_FLAG: case GD_CHARGESHOUT_BEATING: case GD_EMERGENCY_MOVE: if (!map_flag_gvg2(sd->bl.m)) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); return false; } case GD_EMERGENCYCALL: case GD_ITEMEMERGENCYCALL: // other checks were already done in skill_isNotOk() if (!sd->status.guild_id || (sd->state.gmaster_flag == 0 && skill_id != GD_CHARGESHOUT_BEATING)) return false; break; case GS_GLITTERING: case RL_RICHS_COIN: if(sd->spiritball >= 10) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); return false; } break; case NJ_ISSEN: #ifdef RENEWAL if (status->hp < (status->hp/100)) { #else if (status->hp < 2) { #endif clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); return false; } case NJ_BUNSINJYUTSU: if (!(sc && sc->getSCE(SC_NEN))) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); return false; } break; case NJ_ZENYNAGE: case KO_MUCHANAGE: if(sd->status.zeny < require.zeny) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_MONEY,0); return false; } break; case PF_HPCONVERSION: if (status->sp == status->max_sp) return false; //Unusable when at full SP. break; case SP_KAUTE: // Fail if below 30% MaxHP. if (status->hp < 30 * status->max_hp / 100) { clif_skill_fail(sd, skill_id, USESKILL_FAIL, 0); return false; } break; case AM_CALLHOMUN: //Can't summon if a hom is already out if (sd->status.hom_id && sd->hd && !sd->hd->homunculus.vaporize) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); return false; } break; case AM_REST: //Can't vapo homun if you don't have an active homunc or it's hp is < 80% if (!hom_is_active(sd->hd) || sd->hd->battle_status.hp < (sd->hd->battle_status.max_hp*80/100)) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); return false; } break; case AB_ANCILLA: { int count = 0; for( i = 0; i < MAX_INVENTORY; i++ ) if( sd->inventory.u.items_inventory[i].nameid == ITEMID_ANCILLA ) count += sd->inventory.u.items_inventory[i].amount; if( count >= 3 ) { clif_skill_fail(sd, skill_id, USESKILL_FAIL_ANCILLA_NUMOVER, 0); return false; } } break; case AB_ADORAMUS: // bugreport:7647 mistress card DOES remove requirements for gemstones from Adoramus -helvetica if( skill_check_pc_partner(sd,skill_id,&skill_lv,1,0) <= 0 && require.itemid[0] && sd->special_state.no_gemstone == 0 && ((i = pc_search_inventory(sd,require.itemid[0])) < 0 || sd->inventory.u.items_inventory[i].amount < require.amount[0]) ) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); return false; } break; case WL_SUMMONFB: case WL_SUMMONBL: case WL_SUMMONWB: case WL_SUMMONSTONE: if (skill_lv == 1 && sc) { // Failure only happens on level 1 ARR_FIND(SC_SPHERE_1, SC_SPHERE_5 + 1, i, !sc->getSCE(i)); if (i == SC_SPHERE_5 + 1) { // No more free slots clif_skill_fail(sd, skill_id, USESKILL_FAIL_SUMMON, 0); return false; } } break; case WL_TETRAVORTEX: // bugreport:7598 moved sphere check to precast to avoid triggering cooldown per official behavior -helvetica case WL_RELEASE: { int active_spheres = 0, req_spheres = 0; for (i = SC_SPHERE_1; i <= SC_SPHERE_5; i++) { if (sc && sc->getSCE(i)) active_spheres++; } // Cast requirement if (skill_id == WL_TETRAVORTEX) req_spheres = 4; else if (skill_id == WL_RELEASE && skill_lv == 2) // Only level 2 uses Spheres req_spheres = 1; if (active_spheres < req_spheres) { // Need minimum amount of spheres clif_skill_fail(sd, skill_id, (skill_id == WL_RELEASE) ? USESKILL_FAIL_SUMMON_NONE : USESKILL_FAIL_LEVEL, 0); return false; } } break; case GC_HALLUCINATIONWALK: if( sc && (sc->getSCE(SC_HALLUCINATIONWALK) || sc->getSCE(SC_HALLUCINATIONWALK_POSTDELAY)) ) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); return false; } break; case NPC_HALLUCINATIONWALK: if( sc && sc->getSCE(SC_NPC_HALLUCINATIONWALK) ) { return false; } break; case RA_WUGMASTERY: if( (pc_isfalcon(sd) && (!pc_checkskill(sd, WH_HAWK_M) && !battle_config.warg_can_falcon)) || pc_isridingwug(sd) || sd->sc.getSCE(SC__GROOMY)) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); return false; } break; case RA_WUGSTRIKE: if( !pc_iswug(sd) && !pc_isridingwug(sd) ) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); return false; } break; case RA_WUGRIDER: if( (pc_isfalcon(sd) && (!pc_checkskill(sd, WH_HAWK_M) && !battle_config.warg_can_falcon)) || ( !pc_isridingwug(sd) && !pc_iswug(sd) ) ) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); return false; } break; case RA_WUGDASH: if(!pc_isridingwug(sd)) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); return false; } else { int16 sx = sd->bl.x; int16 sy = sd->bl.y; uint8 dir = (unit_getdir(&sd->bl)) % 8; switch (dir) { case 0: case 8: sy++; break; case 1: sx--; sy++; break; case 2: sx--; break; case 3: sx--; sy--; break; case 4: sy--; break; case 5: sx++; sy--; break; case 6: sx++; break; case 7: sx++; sy++; break; } if (map_count_oncell(sd->bl.m, sx, sy, BL_CHAR, 1) > 0) { return false; } } break; case LG_RAYOFGENESIS: case LG_BANDING: if( sc && sc->getSCE(SC_INSPIRATION) ) return true; // Don't check for partner. break; case LG_PRESTIGE: if( sc && sc->getSCE(SC_INSPIRATION) ) return true; // Don't check for partner. if( sc && sc->getSCE(SC_BANDING) ) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); return false; } break; case LG_RAGEBURST: if( sd->spiritball == 0 ) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_SKILLINTERVAL,0); return false; } sd->spiritball_old = require.spiritball = sd->spiritball; break; case SR_FALLENEMPIRE: if( !(sc && sc->getSCE(SC_COMBO) && sc->getSCE(SC_COMBO)->val1 == SR_DRAGONCOMBO) ) return false; break; case SR_CRESCENTELBOW: if( sc && sc->getSCE(SC_CRESCENTELBOW) ) { clif_skill_fail(sd, skill_id, USESKILL_FAIL_DUPLICATE, 0); return false; } break; case SR_CURSEDCIRCLE: if (map_flag_gvg2(sd->bl.m)) { if (map_foreachinallrange(mob_count_sub, &sd->bl, skill_get_splash(skill_id, skill_lv), BL_MOB, MOBID_EMPERIUM, MOBID_GUARDIAN_STONE1, MOBID_GUARDIAN_STONE2)) { char output[128]; sprintf(output,"%s",msg_txt(sd,382)); // You're too close to a stone or emperium to use this skill. clif_messagecolor(&sd->bl,color_table[COLOR_RED], output, false, SELF); return false; } } if( sd->spiritball > 0 ) sd->spiritball_old = require.spiritball = sd->spiritball; else { clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); return false; } break; case SR_GATEOFHELL: if( sd->spiritball > 0 ) sd->spiritball_old = require.spiritball; break; case SC_MANHOLE: case SC_DIMENSIONDOOR: if( sc && sc->getSCE(SC_MAGNETICFIELD) ) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); return false; } break; case SC_FEINTBOMB: if( map_getcell(sd->bl.m,sd->bl.x,sd->bl.y,CELL_CHKLANDPROTECTOR) || map_getcell(sd->bl.m,sd->bl.x,sd->bl.y,CELL_CHKMAELSTROM) ) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); return false; } break; case WM_GREAT_ECHO: { int count = skill_check_pc_partner(sd, skill_id, &skill_lv, AREA_SIZE, 1); if (count > 0) require.sp -= require.sp * 20 * count / 100; // -20% each W/M in the party. } break; case SO_FIREWALK: case SO_ELECTRICWALK: case NPC_FIREWALK: case NPC_ELECTRICWALK: // Can't be casted until you've walked all cells. if( sc && sc->getSCE(SC_PROPERTYWALK) && sc->getSCE(SC_PROPERTYWALK)->val3 < skill_get_maxcount(sc->getSCE(SC_PROPERTYWALK)->val1,sc->getSCE(SC_PROPERTYWALK)->val2) ) { if( sd ) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); return false; } break; case SO_EL_CONTROL: if( !sd->status.ele_id || !sd->ed ) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); return false; } break; case KO_JYUMONJIKIRI: if (sd->weapontype1 != W_FIST && (sd->weapontype2 != W_FIST || sd->status.shield != W_FIST)) return true; else { clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); return false; } break; case KO_KAHU_ENTEN: case KO_HYOUHU_HUBUKI: case KO_KAZEHU_SEIRAN: case KO_DOHU_KOUKAI: if (sd->spiritcharm_type == skill_get_ele(skill_id,skill_lv) && sd->spiritcharm >= MAX_SPIRITCHARM) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_SUMMON,0); return false; } break; case KO_KAIHOU: case KO_ZENKAI: if (sd->spiritcharm_type == CHARM_TYPE_NONE || sd->spiritcharm <= 0) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_SUMMON_NONE,0); return false; } break; case SJ_FULLMOONKICK: if (!(sc && sc->getSCE(SC_NEWMOON))) { clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); return false; } break; case SJ_STAREMPEROR: case SJ_NOVAEXPLOSING: case SJ_GRAVITYCONTROL: case SJ_BOOKOFDIMENSION: case SJ_BOOKOFCREATINGSTAR: case SP_SOULDIVISION: case SP_SOULEXPLOSION: if (!map_flag_vs(sd->bl.m)) { clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); return false; } break; case SP_SWHOO: if (!(sc && sc->getSCE(SC_USE_SKILL_SP_SPA))) return false; break; case DK_SERVANT_W_PHANTOM: case DK_SERVANT_W_DEMOL: if (sd->servantball > 0 && sd->servantball < require.spiritball) sd->servantball_old = require.spiritball = sd->servantball; else sd->servantball_old = require.spiritball; break; case IQ_SECOND_FAITH: case IQ_THIRD_PUNISH: if (!(sc && (sc->getSCE(SC_FIRST_FAITH_POWER) || sc->getSCE(SC_SECOND_JUDGE) || sc->getSCE(SC_THIRD_EXOR_FLAME)))) return false; break; case IQ_SECOND_JUDGEMENT: case IQ_THIRD_CONSECRATION: if (!(sc && (sc->getSCE(SC_SECOND_JUDGE) || sc->getSCE(SC_THIRD_EXOR_FLAME)))) return false; break; case IQ_SECOND_FLAME: case IQ_THIRD_FLAME_BOMB: if (!(sc && sc->getSCE(SC_THIRD_EXOR_FLAME))) return false; break; } /* check state required */ switch (require.state) { case ST_HIDDEN: if(!pc_ishiding(sd)) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); return false; } break; case ST_RIDING: if(!pc_isriding(sd) && !pc_isridingdragon(sd)) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); return false; } break; case ST_FALCON: if(!pc_isfalcon(sd)) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); return false; } break; case ST_CART: if(!pc_iscarton(sd)) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_CART,0); return false; } break; case ST_SHIELD: if(sd->status.shield <= 0) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); return false; } break; case ST_RECOVER_WEIGHT_RATE: #ifdef RENEWAL if(pc_is70overweight(sd)) { #else if(pc_is50overweight(sd)) { #endif clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); return false; } break; case ST_MOVE_ENABLE: if (sc && sc->getSCE(SC_COMBO) && sc->getSCE(SC_COMBO)->val1 == skill_id) sd->ud.canmove_tick = gettick(); //When using a combo, cancel the can't move delay to enable the skill. [Skotlex] if (!unit_can_move(&sd->bl)) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); return false; } break; case ST_WATER: if (sc && (sc->getSCE(SC_DELUGE) || sc->getSCE(SC_SUITON))) break; if (map_getcell(sd->bl.m,sd->bl.x,sd->bl.y,CELL_CHKWATER) && !map_getcell(sd->bl.m,sd->bl.x,sd->bl.y,CELL_CHKLANDPROTECTOR)) break; clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); return false; case ST_RIDINGDRAGON: if( !pc_isridingdragon(sd) ) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_DRAGON,0); return false; } break; case ST_WUG: if( !pc_iswug(sd) ) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); return false; } break; case ST_RIDINGWUG: if( !pc_isridingwug(sd) ) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); return false; } break; case ST_MADO: if( !pc_ismadogear(sd) ) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_MADOGEAR,0); return false; } break; case ST_ELEMENTALSPIRIT: case ST_ELEMENTALSPIRIT2: if(!sd->ed) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_EL_SUMMON,0); return false; } break; case ST_PECO: if(!pc_isriding(sd)) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); return false; } break; case ST_SUNSTANCE: if (!(sc && (sc->getSCE(SC_SUNSTANCE) || sc->getSCE(SC_UNIVERSESTANCE)))) { clif_skill_fail(sd, skill_id, USESKILL_FAIL, 0); return false; } break; case ST_MOONSTANCE: if (!(sc && (sc->getSCE(SC_LUNARSTANCE) || sc->getSCE(SC_UNIVERSESTANCE)))) { clif_skill_fail(sd, skill_id, USESKILL_FAIL, 0); return false; } break; case ST_STARSTANCE: if (!(sc && (sc->getSCE(SC_STARSTANCE) || sc->getSCE(SC_UNIVERSESTANCE)))) { clif_skill_fail(sd, skill_id, USESKILL_FAIL, 0); return false; } break; case ST_UNIVERSESTANCE: if (!(sc && sc->getSCE(SC_UNIVERSESTANCE))) { clif_skill_fail(sd, skill_id, USESKILL_FAIL, 0); return false; } break; } /* check the status required */ if (!require.status.empty()) { switch (skill_id) { // Being checked later in skill_check_condition_castend() case WZ_SIGHTRASHER: break; default: if (!skill_check_condition_sc_required(sd, skill_id, &require)) return false; break; } } // Check for equipped item(s) if (!require.eqItem.empty()) { size_t count = require.eqItem.size(); for (const auto &it : require.eqItem) { t_itemid reqeqit = it; if (!reqeqit) break; // Skill has no required item(s); get out of here switch(skill_id) { // Specific skills require multiple items while default will handle singular cases case NC_PILEBUNKER: case RL_P_ALTER: if (!pc_checkequip2(sd,reqeqit,EQI_ACC_L,EQI_MAX)) { count--; if (!count) { if( skill_id == RL_P_ALTER ){ clif_msg( sd, SKILL_NEED_HOLY_BULLET ); }else{ clif_skill_fail(sd,skill_id,USESKILL_FAIL_THIS_WEAPON,0); } return false; } else continue; } break; case NC_ACCELERATION: case NC_SELFDESTRUCTION: case NC_SHAPESHIFT: case NC_EMERGENCYCOOL: case NC_MAGNETICFIELD: case NC_NEUTRALBARRIER: case NC_STEALTHFIELD: if (pc_search_inventory(sd, reqeqit) == -1) { count--; if (!count) { clif_skill_fail(sd, skill_id, USESKILL_FAIL_NEED_EQUIPMENT, 0, require.eqItem[0]); return false; } else continue; } break; default: if (!pc_checkequip2(sd,reqeqit,EQI_ACC_L,EQI_MAX)) { clif_skill_fail(sd, skill_id, USESKILL_FAIL_NEED_EQUIPMENT, 0, reqeqit); return false; } break; } } } if(require.mhp > 0 && get_percentage(status->hp, status->max_hp) > require.mhp) { //mhp is the max-hp-requirement, that is, //you must have this % or less of HP to cast it. clif_skill_fail(sd,skill_id,USESKILL_FAIL_HP_INSUFFICIENT,0); return false; } if( require.weapon && !pc_check_weapontype(sd,require.weapon) ) { switch(skill_id) { case RA_AIMEDBOLT: break; default: switch((unsigned int)log2(require.weapon)) { case W_REVOLVER: clif_msg(sd, SKILL_NEED_REVOLVER); break; case W_RIFLE: clif_msg(sd, SKILL_NEED_RIFLE); break; case W_GATLING: clif_msg(sd, SKILL_NEED_GATLING); break; case W_SHOTGUN: clif_msg(sd, SKILL_NEED_SHOTGUN); break; case W_GRENADE: clif_msg(sd, SKILL_NEED_GRENADE); break; default: clif_skill_fail(sd, skill_id, USESKILL_FAIL_THIS_WEAPON, 0); break; } return false; } } if( require.sp > 0 && status->sp < (unsigned int)require.sp) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_SP_INSUFFICIENT,0); return false; } if (require.ap > 0 && status->ap < (unsigned int)require.ap) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_AP_INSUFFICIENT,0); return false; } if( require.zeny > 0 && sd->status.zeny < require.zeny ) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_MONEY,0); return false; } if (require.spiritball > 0) { // Skills that require certain types of spheres to use. switch (skill_id) { // Skills that require soul spheres. case SP_SOULGOLEM: case SP_SOULSHADOW: case SP_SOULFALCON: case SP_SOULFAIRY: case SP_SOULCURSE: case SP_SPA: case SP_SHA: case SP_SWHOO: case SP_SOULUNITY: case SP_SOULDIVISION: case SP_SOULREAPER: case SP_SOULEXPLOSION: case SP_KAUTE: if (sd->soulball < require.spiritball) { clif_skill_fail(sd, skill_id, USESKILL_FAIL_SPIRITS, 0); return false; } break; // Skills that requires servants. case DK_SERVANT_W_SIGN: case DK_SERVANT_W_PHANTOM: case DK_SERVANT_W_DEMOL: if (sd->servantball < require.spiritball) { clif_skill_fail(sd, skill_id, USESKILL_FAIL_SPIRITS, 0); return false; } break; default: // Skills that require spirit/coin spheres. if (sd->spiritball < require.spiritball) { if ((sd->class_&MAPID_BASEMASK) == MAPID_GUNSLINGER || (sd->class_&MAPID_UPPERMASK) == MAPID_REBELLION) clif_skill_fail(sd, skill_id, USESKILL_FAIL_COINS, (require.spiritball == -1) ? 1 : require.spiritball); else clif_skill_fail(sd, skill_id, USESKILL_FAIL_SPIRITS, (require.spiritball == -1) ? 1 : require.spiritball); return false; } break; } } return true; } /** * Check skill condition when cast end. * Checking ammo requirement (type and amount) will be here, not at skill_check_condition_castbegin * @param sd Player who uses skill * @param skill_id ID of used skill * @param skill_lv Level of used skill * @return true: All condition passed, false: Failed */ bool skill_check_condition_castend(map_session_data* sd, uint16 skill_id, uint16 skill_lv) { struct s_skill_condition require; struct status_data *status; int i; short index[MAX_SKILL_ITEM_REQUIRE]; nullpo_retr(false,sd); if( sd->chatID ) return false; if( pc_has_permission(sd, PC_PERM_SKILL_UNCONDITIONAL) && sd->skillitem != skill_id ) { //GMs don't override the skillItem check, otherwise they can use items without them being consumed! [Skotlex] sd->state.arrow_atk = skill_get_ammotype(skill_id)?1:0; //Need to do arrow state check. sd->spiritball_old = sd->spiritball; //Need to do Spiritball check. sd->soulball_old = sd->soulball; //Need to do Soulball check. sd->servantball_old = sd->servantball; //Need to do Servantball check. sd->abyssball_old = sd->abyssball; //Need to do Abyssball check. return true; } switch( sd->menuskill_id ) { // Cast start or cast end?? case AM_PHARMACY: switch( skill_id ) { case AM_PHARMACY: case AC_MAKINGARROW: case BS_REPAIRWEAPON: case AM_TWILIGHT1: case AM_TWILIGHT2: case AM_TWILIGHT3: return false; } break; case GN_MIX_COOKING: case GN_MAKEBOMB: case GN_S_PHARMACY: case GN_CHANGEMATERIAL: case MT_M_MACHINE: case BO_BIONIC_PHARMACY: if( sd->menuskill_id != skill_id ) return false; break; } if( sd->skillitem == skill_id && !sd->skillitem_keep_requirement ) // Casting finished (Item skill or Hocus-Pocus) return true; if( pc_is90overweight(sd) ) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_WEIGHTOVER,0); return false; } // perform skill-specific checks (and actions) switch( skill_id ) { case PR_BENEDICTIO: skill_check_pc_partner(sd, skill_id, &skill_lv, 1, 1); break; case AM_CANNIBALIZE: case AM_SPHEREMINE: { int c=0; int summons[5] = { MOBID_G_MANDRAGORA, MOBID_G_HYDRA, MOBID_G_FLORA, MOBID_G_PARASITE, MOBID_G_GEOGRAPHER }; int maxcount = (skill_id==AM_CANNIBALIZE)? 6-skill_lv : skill_get_maxcount(skill_id,skill_lv); int mob_class = (skill_id==AM_CANNIBALIZE)? summons[skill_lv-1] :MOBID_MARINE_SPHERE; if(battle_config.land_skill_limit && maxcount>0 && (battle_config.land_skill_limit&BL_PC)) { i = map_foreachinmap(skill_check_condition_mob_master_sub, sd->bl.m, BL_MOB, sd->bl.id, mob_class, skill_id, &c); if(c >= maxcount || (skill_id==AM_CANNIBALIZE && c != i && battle_config.summon_flora&2)) { //Fails when: exceed max limit. There are other plant types already out. clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); return false; } } break; } case NC_SILVERSNIPER: case NC_MAGICDECOY: { int c = 0; int maxcount = skill_get_maxcount(skill_id,skill_lv); int mob_class = (skill_id == NC_MAGICDECOY)? MOBID_MAGICDECOY_FIRE : MOBID_SILVERSNIPER; if( battle_config.land_skill_limit && maxcount > 0 && ( battle_config.land_skill_limit&BL_PC ) ) { if( skill_id == NC_MAGICDECOY ) { int j; for( j = mob_class; j <= MOBID_MAGICDECOY_WIND; j++ ) map_foreachinmap(skill_check_condition_mob_master_sub, sd->bl.m, BL_MOB, sd->bl.id, j, skill_id, &c); } else map_foreachinmap(skill_check_condition_mob_master_sub, sd->bl.m, BL_MOB, sd->bl.id, mob_class, skill_id, &c); if( c >= maxcount ) { clif_skill_fail(sd , skill_id, USESKILL_FAIL_LEVEL, 0); return false; } } } break; case KO_ZANZOU: { int c = 0; i = map_foreachinmap(skill_check_condition_mob_master_sub, sd->bl.m, BL_MOB, sd->bl.id, MOBID_ZANZOU, skill_id, &c); if( c >= skill_get_maxcount(skill_id,skill_lv) || c != i) { clif_skill_fail(sd , skill_id, USESKILL_FAIL_LEVEL, 0); return false; } } break; case MT_SUMMON_ABR_BATTLE_WARIOR: case MT_SUMMON_ABR_DUAL_CANNON: case MT_SUMMON_ABR_MOTHER_NET: case MT_SUMMON_ABR_INFINITY: { uint32 abrs[4] = { MOBID_ABR_BATTLE_WARIOR, MOBID_ABR_DUAL_CANNON, MOBID_ABR_MOTHER_NET, MOBID_ABR_INFINITY }; int maxcount = skill_get_maxcount(skill_id, skill_lv), c = 0; if (battle_config.land_skill_limit && maxcount > 0 && (battle_config.land_skill_limit & BL_PC)) { map_foreachinmap(skill_check_condition_mob_master_sub, sd->bl.m, BL_MOB, sd->bl.id, abrs[3 - (MT_SUMMON_ABR_INFINITY - skill_id)], skill_id, &c); if (c >= maxcount) { clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); return false; } } break; } case BO_WOODENWARRIOR: case BO_WOODEN_FAIRY: case BO_CREEPER: case BO_HELLTREE: { uint32 bionics[5] = { MOBID_BIONIC_WOODENWARRIOR, MOBID_BIONIC_WOODEN_FAIRY, MOBID_BIONIC_CREEPER, MOBID_PORING, MOBID_BIONIC_HELLTREE }; int maxcount = skill_get_maxcount(skill_id, skill_lv), c = 0; if (battle_config.land_skill_limit && maxcount > 0 && (battle_config.land_skill_limit & BL_PC)) { map_foreachinmap(skill_check_condition_mob_master_sub, sd->bl.m, BL_MOB, sd->bl.id, bionics[4 - (BO_HELLTREE - skill_id)], skill_id, &c); if (c >= maxcount) { clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); return false; } } break; } #ifdef RENEWAL case ASC_EDP: if(sd) { int16 item_edp = itemdb_group.item_exists_pc(sd, IG_EDP); if (item_edp < 0) { clif_skill_fail( sd, skill_id, USESKILL_FAIL_NEED_ITEM, 1, ITEMID_POISON_BOTTLE ); // [%s] required '%d' amount. return false; } else pc_delitem(sd, item_edp, 1, 0, 1, LOG_TYPE_CONSUME); } break; #endif } status = &sd->battle_status; require = skill_get_requirement(sd,skill_id,skill_lv); if( require.hp > 0 && status->hp <= (unsigned int)require.hp) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_HP_INSUFFICIENT,0); return false; } if( require.weapon && !pc_check_weapontype(sd,require.weapon) ) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_THIS_WEAPON,0); return false; } if( require.ammo ) { //Skill requires stuff equipped in the ammo slot. uint8 extra_ammo = 0; #ifdef RENEWAL switch(skill_id) { // 2016-10-26 kRO update made these skills require an extra ammo to cast case WM_SEVERE_RAINSTORM: case RL_FIREDANCE: case RL_R_TRIP: case RL_FIRE_RAIN: extra_ammo = 1; break; default: break; } #endif if((i=sd->equip_index[EQI_AMMO]) < 0 || !sd->inventory_data[i] ) { clif_arrow_fail(sd,0); return false; } else if( sd->inventory.u.items_inventory[i].amount < require.ammo_qty + extra_ammo ) { char e_msg[100]; if (require.ammo&(1<<AMMO_BULLET|1<<AMMO_GRENADE|1<<AMMO_SHELL)) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_NEED_MORE_BULLET,0); return false; } else if (require.ammo&(1<<AMMO_KUNAI)) { clif_skill_fail(sd,skill_id,USESKILL_FAIL_NEED_EQUIPMENT_KUNAI,0); return false; } sprintf(e_msg,msg_txt(sd,381), //Skill Failed. [%s] requires %dx %s. skill_get_desc(skill_id), require.ammo_qty, itemdb_ename(sd->inventory.u.items_inventory[i].nameid)); clif_messagecolor(&sd->bl,color_table[COLOR_RED],e_msg,false,SELF); return false; } if (!(require.ammo&1<<sd->inventory_data[i]->subtype)) { //Ammo type check. Send the "wrong weapon type" message //which is the closest we have to wrong ammo type. [Skotlex] clif_arrow_fail(sd,0); //Haplo suggested we just send the equip-arrows message instead. [Skotlex] //clif_skill_fail(sd,skill_id,USESKILL_FAIL_THIS_WEAPON,0); return false; } } for( i = 0; i < MAX_SKILL_ITEM_REQUIRE; ++i ) { if( !require.itemid[i] ) continue; index[i] = pc_search_inventory(sd,require.itemid[i]); if( index[i] < 0 || sd->inventory.u.items_inventory[index[i]].amount < require.amount[i] ) { if( require.itemid[i] == ITEMID_HOLY_WATER ) clif_skill_fail(sd,skill_id,USESKILL_FAIL_HOLYWATER,0); //Holy water is required. else if( require.itemid[i] == ITEMID_RED_GEMSTONE ) clif_skill_fail(sd,skill_id,USESKILL_FAIL_REDJAMSTONE,0); //Red gemstone is required. else if( require.itemid[i] == ITEMID_BLUE_GEMSTONE ) clif_skill_fail(sd,skill_id,USESKILL_FAIL_BLUEJAMSTONE,0); //Blue gemstone is required. else if( require.itemid[i] == ITEMID_PAINT_BRUSH ) clif_skill_fail(sd,skill_id,USESKILL_FAIL_PAINTBRUSH,0); //Paint brush is required. else if( require.itemid[i] == ITEMID_ANCILLA ) clif_skill_fail(sd,skill_id,USESKILL_FAIL_ANCILLA,0); //Ancilla is required. else clif_skill_fail( sd, skill_id, USESKILL_FAIL_NEED_ITEM, require.amount[i], require.itemid[i] ); // [%s] required '%d' amount. return false; } } /* check the status required */ if (!require.status.empty()) { switch (skill_id) { case WZ_SIGHTRASHER: if (!skill_check_condition_sc_required(sd, skill_id, &require)) return false; break; default: break; } } return true; } /** Consume skill requirement * @param sd Player who uses the skill * @param skill_id ID of used skill * @param skill_lv Level of used skill * @param type Consume type * type&1: consume the others (before skill was used); * type&2: consume items (after skill was used) */ void skill_consume_requirement(map_session_data *sd, uint16 skill_id, uint16 skill_lv, short type) { struct s_skill_condition require; nullpo_retv(sd); require = skill_get_requirement(sd,skill_id,skill_lv); if( type&1 ) { switch( skill_id ) { case CG_TAROTCARD: // TarotCard will consume sp in skill_cast_nodamage_id [Inkfish] case MC_IDENTIFY: require.sp = 0; break; case MO_KITRANSLATION: //Spiritual Bestowment only uses spirit sphere when giving it to someone require.spiritball = 0; //Fall through default: if(sd->state.autocast) require.sp = 0; break; } if(require.hp || require.sp || require.ap) status_zap(&sd->bl, require.hp, require.sp, require.ap); if(require.spiritball > 0) { // Skills that require certain types of spheres to use switch (skill_id) { // Skills that require soul spheres. case SP_SOULGOLEM: case SP_SOULSHADOW: case SP_SOULFALCON: case SP_SOULFAIRY: case SP_SOULCURSE: case SP_SPA: case SP_SHA: case SP_SWHOO: case SP_SOULUNITY: case SP_SOULDIVISION: case SP_SOULREAPER: case SP_SOULEXPLOSION: case SP_KAUTE: pc_delsoulball(sd, require.spiritball, false); break; // Skills that require servants. // Note: We don't update the servants display here // since using these skills auto trigger an animation // with them in unique ways that makes them vanish. case DK_SERVANT_W_SIGN: case DK_SERVANT_W_PHANTOM: case DK_SERVANT_W_DEMOL: pc_delservantball( *sd, require.spiritball ); break; default: // Skills that require spirit/coin spheres. pc_delspiritball(sd, require.spiritball, 0); break; } } else if(require.spiritball == -1) { sd->spiritball_old = sd->spiritball; pc_delspiritball(sd,sd->spiritball,0); } if(require.zeny > 0) { if( skill_id == NJ_ZENYNAGE ) require.zeny = 0; //Zeny is reduced on skill_attack. if( sd->status.zeny < require.zeny ) require.zeny = sd->status.zeny; pc_payzeny(sd,require.zeny,LOG_TYPE_CONSUME,NULL); } } if( type&2 ) { status_change *sc = &sd->sc; int n,i; if( !sc->count ) sc = NULL; for( i = 0; i < MAX_SKILL_ITEM_REQUIRE; ++i ) { if( !require.itemid[i] ) continue; if( itemdb_group.item_exists(IG_GEMSTONE, require.itemid[i]) && skill_id != HW_GANBANTEIN && sc && sc->getSCE(SC_SPIRIT) && sc->getSCE(SC_SPIRIT)->val2 == SL_WIZARD ) continue; //Gemstones are checked, but not substracted from inventory. switch( skill_id ){ case SA_SEISMICWEAPON: if( sc && sc->getSCE(SC_UPHEAVAL_OPTION) && rnd()%100 < 50 ) continue; break; case SA_FLAMELAUNCHER: case SA_VOLCANO: if( sc && sc->getSCE(SC_TROPIC_OPTION) && rnd()%100 < 50 ) continue; break; case SA_FROSTWEAPON: case SA_DELUGE: if( sc && sc->getSCE(SC_CHILLY_AIR_OPTION) && rnd()%100 < 50 ) continue; break; case SA_LIGHTNINGLOADER: case SA_VIOLENTGALE: if( sc && sc->getSCE(SC_WILD_STORM_OPTION) && rnd()%100 < 50 ) continue; break; } if( (n = pc_search_inventory(sd,require.itemid[i])) >= 0 ) pc_delitem(sd,n,require.amount[i],0,1,LOG_TYPE_CONSUME); } } } /** * Get skill requirements and return the value after some additional/reduction condition (such item bonus and status change) * @param sd Player's that will be checked * @param skill_id Skill that's being used * @param skill_lv Skill level of used skill * @return s_skill_condition Struct 's_skill_condition' that store the modified skill requirements */ struct s_skill_condition skill_get_requirement(map_session_data* sd, uint16 skill_id, uint16 skill_lv) { struct s_skill_condition req; struct status_data *status; status_change *sc; int i,hp_rate,sp_rate, ap_rate, sp_skill_rate_bonus = 100; memset(&req,0,sizeof(req)); if( !sd ) return req; if( sd->skillitem == skill_id && !sd->skillitem_keep_requirement ) return req; // Item skills and Hocus-Pocus don't have requirements.[Inkfish] sc = &sd->sc; if( !sc->count ) sc = NULL; //Checks if disabling skill - in which case no SP requirements are necessary if( sc && skill_disable_check(*sc,skill_id) ) return req; skill_lv = cap_value(skill_lv, 1, MAX_SKILL_LEVEL); status = &sd->battle_status; std::shared_ptr<s_skill_db> skill = skill_db.find(skill_id); req.hp = skill->require.hp[skill_lv - 1]; hp_rate = skill->require.hp_rate[skill_lv - 1]; if(hp_rate > 0) req.hp += (status->hp * hp_rate)/100; else req.hp += (status->max_hp * (-hp_rate))/100; req.sp = skill->require.sp[skill_lv-1]; if((sd->skill_id_old == BD_ENCORE) && skill_id == sd->skill_id_dance) req.sp /= 2; if ((sd->skill_id_old == TR_RETROSPECTION) && skill_id == sd->skill_id_song) req.sp -= req.sp * 30 / 100; sp_rate = skill->require.sp_rate[skill_lv-1]; if(sp_rate > 0) req.sp += (status->sp * sp_rate)/100; else req.sp += (status->max_sp * (-sp_rate))/100; if( sd->dsprate != 100 ) req.sp = req.sp * sd->dsprate / 100; for (auto &it : sd->skillusesprate) { if (it.id == skill_id) { sp_skill_rate_bonus -= it.val; break; } } for (auto &it : sd->skillusesp) { if (it.id == skill_id) { req.sp -= it.val; break; } } if (skill_id == sd->status.skill[sd->reproduceskill_idx].id) req.sp += req.sp * 30 / 100; req.sp = cap_value(req.sp * sp_skill_rate_bonus / 100, 0, SHRT_MAX); if( sc ) { if( sc->getSCE(SC__LAZINESS) ) req.sp += req.sp + sc->getSCE(SC__LAZINESS)->val1 * 10; if( sc->getSCE(SC_RECOGNIZEDSPELL) ) req.sp += req.sp / 4; if( sc->getSCE(SC_OFFERTORIUM)) req.sp += req.sp * sc->getSCE(SC_OFFERTORIUM)->val3 / 100; if( sc->getSCE(SC_TELEKINESIS_INTENSE) && skill_get_ele(skill_id, skill_lv) == ELE_GHOST) req.sp -= req.sp * sc->getSCE(SC_TELEKINESIS_INTENSE)->val2 / 100; #ifdef RENEWAL if (sc->getSCE(SC_ADAPTATION) && (skill_get_inf2(skill_id, INF2_ISSONG))) req.sp -= req.sp * 20 / 100; if (sc->getSCE(SC_NIBELUNGEN) && sc->getSCE(SC_NIBELUNGEN)->val2 == RINGNBL_SPCONSUM) req.sp -= req.sp * 30 / 100; #endif if (sc->getSCE(SC_GLOOMYDAY)) req.sp += req.sp * (skill_lv * 10) / 100; if (sc->getSCE(SC_CRESCIVEBOLT)) req.sp += req.sp * (20 * sc->getSCE(SC_CRESCIVEBOLT)->val1) / 100; } req.ap = skill->require.ap[skill_lv - 1]; ap_rate = skill->require.ap_rate[skill_lv - 1]; if (ap_rate > 0) req.ap += (status->ap * ap_rate) / 100; else req.ap += (status->max_ap * (-ap_rate)) / 100; req.zeny = skill->require.zeny[skill_lv-1]; if( sc && sc->getSCE(SC__UNLUCKY) ) { if(sc->getSCE(SC__UNLUCKY)->val1 < 3) req.zeny += sc->getSCE(SC__UNLUCKY)->val1 * 250; else req.zeny += 1000; } req.spiritball = skill->require.spiritball[skill_lv-1]; req.state = skill->require.state; req.mhp = skill->require.mhp[skill_lv-1]; req.weapon = skill->require.weapon; req.ammo_qty = skill->require.ammo_qty[skill_lv-1]; if (req.ammo_qty) req.ammo = skill->require.ammo; if (!req.ammo && skill_id && skill_isammotype(sd, skill_id)) { //Assume this skill is using the weapon, therefore it requires arrows. req.ammo = AMMO_TYPE_ALL; //Enable use on all ammo types. req.ammo_qty = 1; } req.status = skill->require.status; req.eqItem = skill->require.eqItem; // Level dependence flag is determined based on the ItemCost Level label bool level_dependent = skill->require.itemid_level_dependent; switch( skill_id ) { /* Skill level-dependent checks */ case NC_SHAPESHIFT: // NOTE: Magic_Gear_Fuel must be last in the ItemCost list depending on the skill's max level case NC_REPAIR: // NOTE: Repair_Kit must be last in the ItemCost list depending on the skill's max level req.itemid[1] = skill->require.itemid[skill->max]; req.amount[1] = skill->require.amount[skill->max]; // Fall through /* Normal skill requirements and gemstone checks */ default: for( i = 0; i < ((!level_dependent) ? MAX_SKILL_ITEM_REQUIRE : 2); i++ ) { // Skip this for level_dependent requirement, just looking forward for gemstone removal. Assumed if there is gemstone there. if (!level_dependent) { switch( skill_id ) { case AM_POTIONPITCHER: case CR_SLIMPITCHER: case CR_CULTIVATION: if (i != skill_lv%11 - 1) continue; break; #ifdef RENEWAL case AM_CALLHOMUN: // Player has no homunculus, only requires first item if (i > 0 && sd->hd == nullptr) { i = MAX_SKILL_ITEM_REQUIRE; continue; } // Recalling from Rest state has a different consume item (stored as second item) if (skill_area_temp[0] == 1 || (sd->hd != nullptr && sd->hd->homunculus.vaporize == HOM_ST_REST)) { req.itemid[0] = skill->require.itemid[1]; req.amount[0] = skill->require.amount[1]; if (skill_area_temp[0] == 1 && hom_is_active(sd->hd)) skill_area_temp[0] = 0; i = MAX_SKILL_ITEM_REQUIRE; continue; } break; #else case AM_CALLHOMUN: if (sd->status.hom_id) //Don't delete items when hom is already out. continue; break; #endif case AB_ADORAMUS: if( itemdb_group.item_exists(IG_GEMSTONE, skill->require.itemid[i]) && (sd->special_state.no_gemstone == 2 || skill_check_pc_partner(sd,skill_id,&skill_lv, 1, 2)) ) continue; break; } req.itemid[i] = skill->require.itemid[i]; req.amount[i] = skill->require.amount[i]; if ((skill_id >= HT_SKIDTRAP && skill_id <= HT_TALKIEBOX && pc_checkskill(sd, RA_RESEARCHTRAP) > 0) || skill_id == SC_ESCAPE) { int16 itIndex; if ((itIndex = pc_search_inventory(sd,req.itemid[i])) < 0 || ( itIndex >= 0 && sd->inventory.u.items_inventory[itIndex].amount < req.amount[i])) { if (skill_id == SC_ESCAPE) // Alloy Trap has priority over normal Trap req.itemid[i] = ITEMID_TRAP; else req.itemid[i] = ITEMID_TRAP_ALLOY; req.amount[i] = 1; } break; } } else { // Process level_dependent requirement if (level_dependent && skill_lv <= MAX_SKILL_ITEM_REQUIRE) { req.itemid[0] = skill->require.itemid[skill_lv - 1]; req.amount[0] = skill->require.amount[skill_lv - 1]; } } // Check requirement for gemstone. if (itemdb_group.item_exists(IG_GEMSTONE, req.itemid[i])) { if( sd->special_state.no_gemstone == 2 ) // Remove all Magic Stone required for all skills for VIP. req.itemid[i] = req.amount[i] = 0; else { if( sd->special_state.no_gemstone || (sc && sc->getSCE(SC_INTOABYSS)) ) { // All gem skills except Hocus Pocus and Ganbantein can cast for free with Mistress card -helvetica if (skill_id != SA_ABRACADABRA && skill_id != HW_GANBANTEIN) req.itemid[i] = req.amount[i] = 0; else if( --req.amount[i] < 1 ) req.amount[i] = 1; // Hocus Pocus always use at least 1 gem } } } // Check requirement for Magic Gear Fuel if (req.itemid[i] == ITEMID_MAGIC_GEAR_FUEL && sd->special_state.no_mado_fuel) req.itemid[i] = req.amount[i] = 0; } break; } // Check for cost reductions due to skills & SCs switch(skill_id) { case MC_MAMMONITE: #ifdef RENEWAL case WS_CARTTERMINATION: #endif if(pc_checkskill(sd,BS_UNFAIRLYTRICK)>0) #ifdef RENEWAL req.zeny -= req.zeny*20/100; #else req.zeny -= req.zeny*10/100; #endif break; case AL_HOLYLIGHT: if(sc && sc->getSCE(SC_SPIRIT) && sc->getSCE(SC_SPIRIT)->val2 == SL_PRIEST) req.sp *= 5; break; case SL_SMA: case SL_STUN: case SL_STIN: { int kaina_lv = sd?pc_checkskill(sd,SL_KAINA):skill_get_max(SL_KAINA); if(kaina_lv==0 || !sd || sd->status.base_level<70) break; if(sd->status.base_level>=90) req.sp -= req.sp*7*kaina_lv/100; else if(sd->status.base_level>=80) req.sp -= req.sp*5*kaina_lv/100; else if(sd->status.base_level>=70) req.sp -= req.sp*3*kaina_lv/100; } break; case MO_CHAINCOMBO: case MO_COMBOFINISH: case CH_TIGERFIST: case CH_CHAINCRUSH: if(sc && sc->getSCE(SC_SPIRIT) && sc->getSCE(SC_SPIRIT)->val2 == SL_MONK) req.sp = 2; //Monk Spirit makes monk/champion combo skills cost 2 SP regardless of original cost break; case MO_BODYRELOCATION: if( sc && sc->getSCE(SC_EXPLOSIONSPIRITS) ) req.spiritball = 0; break; case MO_EXTREMITYFIST: if( sc ) { if( sc->getSCE(SC_BLADESTOP) ) req.spiritball--; else if( sc->getSCE(SC_COMBO) ) { #ifndef RENEWAL switch( sc->getSCE(SC_COMBO)->val1 ) { case MO_COMBOFINISH: req.spiritball = 4; break; case CH_TIGERFIST: req.spiritball = 3; break; case CH_CHAINCRUSH: //It should consume whatever is left as long as it's at least 1. req.spiritball = sd->spiritball?sd->spiritball:1; break; } #else req.spiritball = sd->spiritball ? sd->spiritball : 1; #endif } else if( sc->getSCE(SC_RAISINGDRAGON) && sd->spiritball > 5) req.spiritball = sd->spiritball; // must consume all regardless of the amount required } break; case LG_RAGEBURST: req.spiritball = sd->spiritball?sd->spiritball:1; break; case SR_FALLENEMPIRE: if (sc && (sc->getSCE(SC_FIRST_FAITH_POWER) || sc->getSCE(SC_SECOND_JUDGE) || sc->getSCE(SC_THIRD_EXOR_FLAME))) req.spiritball = 0; break; case SR_TIGERCANNON: if (sc && sc->getSCE(SC_THIRD_EXOR_FLAME)) req.spiritball = 0; break; case SR_RAMPAGEBLASTER: case SR_RIDEINLIGHTNING: if (sc && sc->getSCE(SC_MASSIVE_F_BLASTER)) req.spiritball = 0; break; case SR_GATEOFHELL: if( sc && sc->getSCE(SC_COMBO) && sc->getSCE(SC_COMBO)->val1 == SR_FALLENEMPIRE ) req.sp -= req.sp * 10 / 100; break; case SR_FLASHCOMBO: if (sc && (sc->getSCE(SC_SECOND_JUDGE) || sc->getSCE(SC_THIRD_EXOR_FLAME))) req.spiritball = 0; break; case SO_SUMMON_AGNI: case SO_SUMMON_AQUA: case SO_SUMMON_VENTUS: case SO_SUMMON_TERA: { int spirit_sympathy = pc_checkskill(sd,SO_EL_SYMPATHY); if( spirit_sympathy ) req.sp -= req.sp * (5 + 5 * spirit_sympathy) / 100; } break; case NPC_PSYCHIC_WAVE: case SO_PSYCHIC_WAVE: if( sc && (sc->getSCE(SC_HEATER_OPTION) || sc->getSCE(SC_COOLER_OPTION) || sc->getSCE(SC_CURSED_SOIL_OPTION) || sc->getSCE(SC_BLAST_OPTION)) ) req.sp += req.sp / 2; // 1.5x SP cost break; } //Check if player is using the copied skill [Cydh] uint16 idx = skill_get_index(skill_id); if (sd->status.skill[idx].flag == SKILL_FLAG_PLAGIARIZED) { uint16 req_opt = skill->copyable.req_opt; if (req_opt & SKILL_REQ_HPCOST) req.hp = 0; if (req_opt & SKILL_REQ_MAXHPTRIGGER) req.mhp = 0; if (req_opt & SKILL_REQ_SPCOST) req.sp = 0; if (req_opt & SKILL_REQ_HPRATECOST) req.hp_rate = 0; if (req_opt & SKILL_REQ_SPRATECOST) req.sp_rate = 0; if (req_opt & SKILL_REQ_ZENYCOST) req.zeny = 0; if (req_opt & SKILL_REQ_WEAPON) req.weapon = 0; if (req_opt & SKILL_REQ_AMMO) { req.ammo = 0; req.ammo_qty = 0; } if (req_opt & SKILL_REQ_STATE) req.state = ST_NONE; if (req_opt & SKILL_REQ_STATUS) { req.status.clear(); req.status.shrink_to_fit(); } if (req_opt & SKILL_REQ_SPIRITSPHERECOST) req.spiritball = 0; if (req_opt & SKILL_REQ_ITEMCOST) { memset(req.itemid, 0, sizeof(req.itemid)); memset(req.amount, 0, sizeof(req.amount)); } if (req_opt & SKILL_REQ_EQUIPMENT) { req.eqItem.clear(); req.eqItem.shrink_to_fit(); } if (req_opt & SKILL_REQ_APCOST) req.ap = 0; if (req_opt & SKILL_REQ_APRATECOST) req.ap_rate = 0; } return req; } /*========================================== * Does cast-time reductions based on dex, item bonuses and config setting *------------------------------------------*/ int skill_castfix(struct block_list *bl, uint16 skill_id, uint16 skill_lv) { nullpo_ret(bl); double time = skill_get_cast(skill_id, skill_lv); #ifndef RENEWAL_CAST { map_session_data *sd = BL_CAST(BL_PC, bl); status_change *sc = status_get_sc(bl); int reduce_cast_rate = 0; uint8 flag = skill_get_castnodex(skill_id); // Calculate base cast time (reduced by dex) if (!(flag&1)) { int scale = battle_config.castrate_dex_scale - status_get_dex(bl); if (scale > 0) // not instant cast time = time * (float)scale / battle_config.castrate_dex_scale; else return 0; // instant cast } // Calculate cast time reduced by item/card bonuses if (sd) { if (!(flag&4)) { if (sd->castrate != 100) reduce_cast_rate += 100 - sd->castrate; if (sd->bonus.add_varcast != 0) time += sd->bonus.add_varcast; // bonus bVariableCast } // Skill-specific reductions work regardless of flag for (const auto &it : sd->skillcastrate) { if (it.id == skill_id) { time += time * it.val / 100; break; } } for (const auto &it : sd->skillvarcast) { if (it.id == skill_id) { // bonus2 bSkillVariableCast time += it.val; break; } } } // These cast time reductions are processed even if the skill fails if (sc && sc->count) { // Magic Strings stacks additively with item bonuses if (!(flag&2) && sc->getSCE(SC_POEMBRAGI)) reduce_cast_rate += sc->getSCE(SC_POEMBRAGI)->val2; // Foresight halves the cast time, it does not stack additively if (sc->getSCE(SC_MEMORIZE)) { if (!sd || pc_checkskill(sd, skill_id) > 0) { // Foresight only decreases cast times from learned skills, not skills granted by items if(!(flag&2)) time -= time * 50 / 100; // Foresight counter gets reduced even if the skill is not affected by it if ((--sc->getSCE(SC_MEMORIZE)->val2) <= 0) status_change_end(bl, SC_MEMORIZE); } } } time = time * (1 - (float)reduce_cast_rate / 100); } #endif // config cast time multiplier if (battle_config.cast_rate != 100) time = time * battle_config.cast_rate / 100; // return final cast time time = max((int)time, 0); //ShowInfo("Castime castfix = %f\n",time); return (int)time; } #ifndef RENEWAL_CAST /** * Get the skill cast time for Pre-Re cast * @param bl: The caster * @param time: Cast time before Status Change addition or reduction * @return time: Modified castime after status change addition or reduction */ int skill_castfix_sc(struct block_list *bl, double time, uint8 flag) { if (time < 0) return 0; if (bl->type == BL_MOB || bl->type == BL_NPC) return (int)time; status_change *sc = status_get_sc(bl); if (sc && sc->count) { if (!(flag&2)) { if (sc->getSCE(SC_SLOWCAST)) time += time * sc->getSCE(SC_SLOWCAST)->val2 / 100; if (sc->getSCE(SC_PARALYSIS)) time += sc->getSCE(SC_PARALYSIS)->val3; if (sc->getSCE(SC_IZAYOI)) time -= time * 50 / 100; if (sc->getSCE(SC_2011RWC_SCROLL)) time -= time * 5 / 100; } if (sc->getSCE(SC_SUFFRAGIUM)) { if(!(flag&2)) time -= time * sc->getSCE(SC_SUFFRAGIUM)->val2 / 100; //Suffragium ends even if the skill is not affected by it status_change_end(bl, SC_SUFFRAGIUM); } } time = std::max(time, 0.0); //ShowInfo("Castime castfix_sc = %f\n",time); return (int)time; } #else /** * Get the skill cast time for RENEWAL_CAST. * FixedRate reduction never be stacked, always get the HIGHEST VALUE TO REDUCE (-20% vs 10%, -20% wins!) * Additive value: * Variable CastTime : time += value * Fixed CastTime : fixed += value * Multipicative value * Variable CastTime : VARCAST_REDUCTION(value) * Fixed CastTime : FIXEDCASTRATE2(value) * @param bl: The caster * @param time: Cast time without reduction * @param skill_id: Skill ID of the casted skill * @param skill_lv: Skill level of the casted skill * @return time: Modified castime after status and bonus addition or reduction */ int skill_vfcastfix(struct block_list *bl, double time, uint16 skill_id, uint16 skill_lv) { nullpo_ret(bl); if (time < 0) return 0; if (bl->type == BL_MOB || bl->type == BL_NPC) return (int)time; status_change *sc = status_get_sc(bl); map_session_data *sd = BL_CAST(BL_PC, bl); int fixed = skill_get_fixed_cast(skill_id, skill_lv), fixcast_r = 0, varcast_r = 0, reduce_cast_rate = 0; uint8 flag = skill_get_castnodex(skill_id); if (fixed < 0) { if (battle_config.default_fixed_castrate > 0) { fixed = (int)time * battle_config.default_fixed_castrate / 100; // fixed time time = time * (100 - battle_config.default_fixed_castrate) / 100; // variable time } else fixed = 0; } // Additive Variable Cast bonus adjustments by items if (sd && !(flag&4)) { if (sd->bonus.varcastrate != 0) reduce_cast_rate += sd->bonus.varcastrate; // bonus bVariableCastrate if (sd->bonus.fixcastrate != 0) fixcast_r -= sd->bonus.fixcastrate; // bonus bFixedCastrate if (sd->bonus.add_varcast != 0) time += sd->bonus.add_varcast; // bonus bVariableCast if (sd->bonus.add_fixcast != 0) fixed += sd->bonus.add_fixcast; // bonus bFixedCast for (const auto &it : sd->skillfixcast) { if (it.id == skill_id) { // bonus2 bSkillFixedCast fixed += it.val; break; } } for (const auto &it : sd->skillvarcast) { if (it.id == skill_id) { // bonus2 bSkillVariableCast time += it.val; break; } } for (const auto &it : sd->skillcastrate) { if (it.id == skill_id) { // bonus2 bVariableCastrate reduce_cast_rate += it.val; break; } } for (const auto &it : sd->skillfixcastrate) { if (it.id == skill_id) { // bonus2 bFixedCastrate fixcast_r = max(fixcast_r, it.val); break; } } } // Adjusted by active statuses if (sc && sc->count && !(flag&2)) { // Multiplicative Variable CastTime values if (sc->getSCE(SC_SLOWCAST)) VARCAST_REDUCTION(-sc->getSCE(SC_SLOWCAST)->val2); if (sc->getSCE(SC__LAZINESS)) VARCAST_REDUCTION(-sc->getSCE(SC__LAZINESS)->val2); if (sc->getSCE(SC_SUFFRAGIUM)) { VARCAST_REDUCTION(sc->getSCE(SC_SUFFRAGIUM)->val2); #ifndef RENEWAL status_change_end(bl, SC_SUFFRAGIUM, INVALID_TIMER); #endif } if (sc->getSCE(SC_MEMORIZE)) { if (!sd || pc_checkskill(sd, skill_id) > 0) { // Foresight only decreases cast times from learned skills, not skills granted by items reduce_cast_rate += 50; if ((--sc->getSCE(SC_MEMORIZE)->val2) <= 0) status_change_end(bl, SC_MEMORIZE); } } if (sc->getSCE(SC_POEMBRAGI)) reduce_cast_rate += sc->getSCE(SC_POEMBRAGI)->val2; if (sc->getSCE(SC_IZAYOI)) VARCAST_REDUCTION(50); if (sc->getSCE(SC_WATER_INSIGNIA) && sc->getSCE(SC_WATER_INSIGNIA)->val1 == 3 && skill_get_type(skill_id) == BF_MAGIC && skill_get_ele(skill_id, skill_lv) == ELE_WATER) VARCAST_REDUCTION(30); //Reduces 30% Variable Cast Time of magic Water spells. if (sc->getSCE(SC_TELEKINESIS_INTENSE)) VARCAST_REDUCTION(sc->getSCE(SC_TELEKINESIS_INTENSE)->val2); if (sc->getSCE(SC_SOULFAIRY)) VARCAST_REDUCTION(sc->getSCE(SC_SOULFAIRY)->val3); if (sc->getSCE(SC_EP16_2_BUFF_AC)) VARCAST_REDUCTION(80); // Multiplicative Fixed CastTime values if (sc->getSCE(SC_SECRAMENT)) fixcast_r = max(fixcast_r, sc->getSCE(SC_SECRAMENT)->val2); if (sd && (skill_lv = pc_checkskill(sd, WL_RADIUS) ) && skill_id >= WL_WHITEIMPRISON && skill_id <= WL_FREEZE_SP) fixcast_r = max(fixcast_r, ((status_get_int(bl) + status_get_lv(bl)) / 15 + skill_lv * 5)); if (sc->getSCE(SC_DANCEWITHWUG)) fixcast_r = max(fixcast_r, sc->getSCE(SC_DANCEWITHWUG)->val4); if (sc->getSCE(SC_HEAT_BARREL)) fixcast_r = max(fixcast_r, sc->getSCE(SC_HEAT_BARREL)->val2); if (sc->getSCE(SC_FREEZING)) fixcast_r -= 50; if (sc->getSCE(SC_SWINGDANCE)) fixcast_r = max(fixcast_r, skill_lv * 6); // Additive Fixed CastTime values if (sc->getSCE(SC_MANDRAGORA)) fixed += sc->getSCE(SC_MANDRAGORA)->val1 * 500; if (sc->getSCE(SC_GUST_OPTION) || sc->getSCE(SC_BLAST_OPTION) || sc->getSCE(SC_WILD_STORM_OPTION)) fixed -= 1000; if (sc->getSCE(SC_IZAYOI)) fixed = 0; if (sc->getSCE(SC_GLOOMYDAY)) fixed += skill_lv * 500; if (sc->getSCE(SC_2011RWC_SCROLL)) VARCAST_REDUCTION(5); } if (sc && sc->getSCE(SC_SECRAMENT) && skill_id == HW_MAGICPOWER && (flag&2)) // Sacrament lowers Mystical Amplification cast time fixcast_r = max(fixcast_r, sc->getSCE(SC_SECRAMENT)->val2); if (varcast_r < 0) time = time * (1 - (float)min(varcast_r, 100) / 100); // Apply Variable CastTime calculation by INT & DEX if (!(flag&1)) time = time * (1 - sqrt(((float)(status_get_dex(bl) * 2 + status_get_int(bl)) / battle_config.vcast_stat_scale))); time = time * (1 - (float)min(reduce_cast_rate, 100) / 100); time = max((int)time, 0) + (1 - (float)min(fixcast_r, 100) / 100) * max(fixed, 0); //Underflow checking/capping return (int)time; } #endif /*========================================== * Does delay reductions based on dex/agi, sc data, item bonuses, ... *------------------------------------------*/ int skill_delayfix(struct block_list *bl, uint16 skill_id, uint16 skill_lv) { nullpo_ret(bl); if (skill_id == SA_ABRACADABRA) return 0; //Will use picked skill's delay. if (bl->type&battle_config.no_skill_delay) return battle_config.min_skill_delay_limit; int delaynodex = skill_get_delaynodex(skill_id); double time = skill_get_delay(skill_id, skill_lv); if (time < 0) time = -time + status_get_amotion(bl); // If set to <0, add to attack motion. status_change* sc = status_get_sc(bl); // Delay reductions switch (skill_id) { //Monk combo skills have their delay reduced by agi/dex. case MO_TRIPLEATTACK: case MO_CHAINCOMBO: case MO_COMBOFINISH: case CH_TIGERFIST: case CH_CHAINCRUSH: case SR_DRAGONCOMBO: case SR_FALLENEMPIRE: case SJ_PROMINENCEKICK: //If delay not specified, it will be 1000 - 4*agi - 2*dex if (time == 0) time = 1000; time -= (4 * status_get_agi(bl) + 2 * status_get_dex(bl)); break; #ifndef RENEWAL case HP_BASILICA: if (sc && !sc->getSCE(SC_BASILICA)) time = 0; // There is no Delay on Basilica creation, only on cancel break; #endif default: if (battle_config.delay_dependon_dex && !(delaynodex&1)) { // if skill delay is allowed to be reduced by dex int scale = battle_config.castrate_dex_scale - status_get_dex(bl); if (scale > 0) time = time * scale / battle_config.castrate_dex_scale; else //To be capped later to minimum. time = 0; } if (battle_config.delay_dependon_agi && !(delaynodex&1)) { // if skill delay is allowed to be reduced by agi int scale = battle_config.castrate_dex_scale - status_get_agi(bl); if (scale > 0) time = time * scale / battle_config.castrate_dex_scale; else //To be capped later to minimum. time = 0; } } if (sc && sc->count) { if (sc->getSCE(SC_SPIRIT)) { switch (skill_id) { case CR_SHIELDBOOMERANG: if (sc->getSCE(SC_SPIRIT)->val2 == SL_CRUSADER) time /= 2; break; case AS_SONICBLOW: if (!map_flag_gvg2(bl->m) && !map_getmapflag(bl->m, MF_BATTLEGROUND) && sc->getSCE(SC_SPIRIT)->val2 == SL_ASSASIN) time /= 2; break; } } } int delay = 0; if (!(delaynodex&2)) { if (sc && sc->count) { if (sc->getSCE(SC_POEMBRAGI)) delay += sc->getSCE(SC_POEMBRAGI)->val3; if (sc->getSCE(SC_WIND_INSIGNIA) && sc->getSCE(SC_WIND_INSIGNIA)->val1 == 3 && skill_get_type(skill_id) == BF_MAGIC && skill_get_ele(skill_id, skill_lv) == ELE_WIND) delay += 50; // After Delay of Wind element spells reduced by 50%. if (sc->getSCE(SC_MAGICMUSHROOM) && sc->getSCE(SC_MAGICMUSHROOM)->val3 == 0) delay += sc->getSCE(SC_MAGICMUSHROOM)->val2; } } if (!(delaynodex&4) && bl->type == BL_PC) { map_session_data* sd = (map_session_data*)bl; if (sd->bonus.delayrate != 0) // bonus bDelayRate delay += sd->bonus.delayrate; for (auto &it : sd->skilldelay) { // bonus2 bSkillDelay if (it.id == skill_id) { time += it.val; break; } } } if (delay != 0) time = time * (1 - (float)min(delay, 100) / 100); if (battle_config.delay_rate != 100) time = time * battle_config.delay_rate / 100; //ShowInfo("Delay delayfix = %f\n",time); return max((int)time,0); } /*========================================== * Weapon Repair [Celest/DracoRPG] *------------------------------------------*/ void skill_repairweapon(map_session_data *sd, int idx) { static const t_itemid weapon_materials[MAX_WEAPON_LEVEL] = { ITEMID_IRON_ORE, ITEMID_IRON, ITEMID_STEEL, ITEMID_ORIDECON_STONE, #ifdef RENEWAL 0 #endif }; static const t_itemid armor_materials[MAX_ARMOR_LEVEL] = { ITEMID_STEEL, #ifdef RENEWAL 0 #endif }; t_itemid material = 0; struct item *item; map_session_data *target_sd; nullpo_retv(sd); if ( !( target_sd = map_id2sd(sd->menuskill_val) ) ) //Failed.... return; if( idx == 0xFFFF ) // No item selected ('Cancel' clicked) return; if( idx < 0 || idx >= MAX_INVENTORY ) return; //Invalid index?? item = &target_sd->inventory.u.items_inventory[idx]; if( !item->nameid || !item->attribute ) return; //Again invalid item.... if (itemdb_ishatched_egg(item)) return; if (sd != target_sd && !battle_check_range(&sd->bl, &target_sd->bl, skill_get_range2(&sd->bl, sd->menuskill_id, sd->menuskill_val2, true))) { clif_item_repaireffect(sd, idx, 1); return; } if( target_sd->inventory_data[idx]->type == IT_WEAPON ){ material = weapon_materials[target_sd->inventory_data[idx]->weapon_level - 1]; }else if( target_sd->inventory_data[idx]->type == IT_ARMOR ){ material = armor_materials[target_sd->inventory_data[idx]->armor_level - 1]; } if( material == 0 || pc_search_inventory( sd, material ) < 0 ){ clif_skill_fail(sd,sd->menuskill_id,USESKILL_FAIL_LEVEL,0); return; } clif_skill_nodamage(&sd->bl,&target_sd->bl,sd->menuskill_id,1,1); item->attribute = 0;/* clear broken state */ clif_equiplist(target_sd); pc_delitem(sd,pc_search_inventory(sd,material),1,0,0,LOG_TYPE_CONSUME); clif_item_repaireffect(sd,idx,0); if( sd != target_sd ) clif_item_repaireffect(target_sd,idx,0); } /*========================================== * Item Appraisal *------------------------------------------*/ void skill_identify(map_session_data *sd, int idx) { int flag=1; nullpo_retv(sd); sd->state.workinprogress = WIP_DISABLE_NONE; if(idx >= 0 && idx < MAX_INVENTORY) { if(sd->inventory.u.items_inventory[idx].nameid > 0 && sd->inventory.u.items_inventory[idx].identify == 0 ){ flag=0; sd->inventory.u.items_inventory[idx].identify = 1; } } clif_item_identified(sd,idx,flag); } /*========================================== * Weapon Refine [Celest] *------------------------------------------*/ void skill_weaponrefine(map_session_data *sd, int idx) { static const t_itemid material[MAX_WEAPON_LEVEL] = { ITEMID_PHRACON, ITEMID_EMVERETARCON, ITEMID_ORIDECON, ITEMID_ORIDECON, #ifdef RENEWAL 0 #endif }; nullpo_retv(sd); if (idx >= 0 && idx < MAX_INVENTORY) { struct item *item; struct item_data *ditem = sd->inventory_data[idx]; item = &sd->inventory.u.items_inventory[idx]; if(item->nameid > 0 && ditem->type == IT_WEAPON) { if( ditem->flag.no_refine || ditem->weapon_level < 1 ) { // if the item isn't refinable clif_skill_fail(sd,sd->menuskill_id,USESKILL_FAIL_LEVEL,0); return; } if( item->refine >= sd->menuskill_val || item->refine >= 10 ) { clif_upgrademessage(sd, 2, item->nameid); return; } int i = pc_search_inventory( sd, material[ditem->weapon_level - 1] ); if( i < 0 ) { clif_upgrademessage( sd, 3, material[ditem->weapon_level - 1] ); return; } std::shared_ptr<s_refine_level_info> info = refine_db.findLevelInfo( *ditem, *item ); if( info == nullptr ){ clif_skill_fail( sd, sd->menuskill_id, USESKILL_FAIL_LEVEL, 0 ); return; } std::shared_ptr<s_refine_cost> cost = util::umap_find( info->costs, (uint16)REFINE_COST_NORMAL ); if( cost == nullptr ){ clif_skill_fail( sd, sd->menuskill_id, USESKILL_FAIL_LEVEL, 0 ); return; } if( cost->nameid != material[ditem->weapon_level - 1] ){ ShowDebug( "skill_weaponrefine: The hardcoded refine requirement %d for weapon level %d does not match %d from the refine database.\n", material[ditem->weapon_level - 1], ditem->weapon_level, cost->nameid ); clif_skill_fail( sd, sd->menuskill_id, USESKILL_FAIL_LEVEL, 0 ); return; } int per = ( cost->chance / 100 ); if( sd->class_&JOBL_THIRD ) per += 10; else per += (((signed int)sd->status.job_level)-50)/2; //Updated per the new kro descriptions. [Skotlex] pc_delitem(sd, i, 1, 0, 0, LOG_TYPE_OTHER); if (per > rnd() % 100) { int ep=0; log_pick_pc(sd, LOG_TYPE_OTHER, -1, item); item->refine++; log_pick_pc(sd, LOG_TYPE_OTHER, 1, item); if(item->equip) { ep = item->equip; pc_unequipitem(sd,idx,3); } clif_delitem(sd,idx,1,3); clif_upgrademessage(sd, 0, item->nameid); clif_inventorylist(sd); clif_refine(sd->fd,0,idx,item->refine); if( ditem->type == IT_WEAPON ){ achievement_update_objective(sd, AG_ENCHANT_SUCCESS, 2, ditem->weapon_level, item->refine); } if (ep) pc_equipitem(sd,idx,ep); clif_misceffect(&sd->bl,3); if(item->refine == 10 && item->card[0] == CARD0_FORGE && (int)MakeDWord(item->card[2],item->card[3]) == sd->status.char_id) { // Fame point system [DracoRPG] switch(ditem->weapon_level){ case 1: pc_addfame(*sd, battle_config.fame_refine_lv1); // Success to refine to +10 a lv1 weapon you forged = +1 fame point break; case 2: pc_addfame(*sd, battle_config.fame_refine_lv2); // Success to refine to +10 a lv2 weapon you forged = +25 fame point break; case 3: pc_addfame(*sd, battle_config.fame_refine_lv3); // Success to refine to +10 a lv3 weapon you forged = +1000 fame point break; } } } else { item->refine = 0; if(item->equip) pc_unequipitem(sd,idx,3); clif_upgrademessage(sd, 1, item->nameid); clif_refine(sd->fd,1,idx,item->refine); achievement_update_objective(sd, AG_ENCHANT_FAIL, 1, 1); pc_delitem(sd,idx,1,0,2, LOG_TYPE_OTHER); clif_misceffect(&sd->bl,2); clif_emotion(&sd->bl, ET_HUK); } } } } /*========================================== * *------------------------------------------*/ int skill_autospell(map_session_data *sd, uint16 skill_id) { nullpo_ret(sd); if (skill_id == 0 || skill_db.get_index(skill_id, true, __FUNCTION__, __FILE__, __LINE__) == 0 || SKILL_CHK_GUILD(skill_id)) return 0; uint16 lv = pc_checkskill(sd, skill_id), skill_lv = sd->menuskill_val; uint16 maxlv = 1; if (skill_lv == 0 || lv == 0) return 0; // Player must learn the skill before doing auto-spell [Lance] #ifdef RENEWAL if ((skill_id == MG_COLDBOLT || skill_id == MG_FIREBOLT || skill_id == MG_LIGHTNINGBOLT) && sd->sc.getSCE(SC_SPIRIT) && sd->sc.getSCE(SC_SPIRIT)->val2 == SL_SAGE) maxlv = 10; //Soul Linker bonus. [Skotlex] else maxlv = skill_lv / 2; // Half of Autospell's level unless player learned a lower level (capped below) #else if(skill_id==MG_NAPALMBEAT) maxlv=3; else if(skill_id==MG_COLDBOLT || skill_id==MG_FIREBOLT || skill_id==MG_LIGHTNINGBOLT){ if (sd->sc.getSCE(SC_SPIRIT) && sd->sc.getSCE(SC_SPIRIT)->val2 == SL_SAGE) maxlv = 10; //Soul Linker bonus. [Skotlex] else if(skill_lv==2) maxlv=1; else if(skill_lv==3) maxlv=2; else if(skill_lv>=4) maxlv=3; } else if(skill_id==MG_SOULSTRIKE){ if(skill_lv==5) maxlv=1; else if(skill_lv==6) maxlv=2; else if(skill_lv>=7) maxlv=3; } else if(skill_id==MG_FIREBALL){ if(skill_lv==8) maxlv=1; else if(skill_lv>=9) maxlv=2; } else if(skill_id==MG_FROSTDIVER) maxlv=1; else return 0; #endif maxlv = min(lv, maxlv); sc_start4(&sd->bl,&sd->bl,SC_AUTOSPELL,100,skill_lv,skill_id,maxlv,0, skill_get_time(SA_AUTOSPELL,skill_lv)); return 0; } /** * Count the number of players with Gangster Paradise, Peaceful Break, or Happy Break. * @param bl: Player object * @param ap: va_arg list * @return 1 if the player has learned Gangster Paradise, Peaceful Break, or Happy Break otherwise 0 */ static int skill_sit_count(struct block_list *bl, va_list ap) { map_session_data *sd = (map_session_data*)bl; int flag = va_arg(ap, int); if (!pc_issit(sd)) return 0; if (flag&1 && pc_checkskill(sd, RG_GANGSTER) > 0) return 1; if (flag&2 && (pc_checkskill(sd, TK_HPTIME) > 0 || pc_checkskill(sd, TK_SPTIME) > 0)) return 1; return 0; } /** * Triggered when a player sits down to activate bonus states. * @param bl: Player object * @param ap: va_arg list * @return 0 */ static int skill_sit_in(struct block_list *bl, va_list ap) { map_session_data *sd = (map_session_data*)bl; int flag = va_arg(ap, int); if (!pc_issit(sd)) return 0; if (flag&1 && pc_checkskill(sd, RG_GANGSTER) > 0) sd->state.gangsterparadise = 1; if (flag&2 && (pc_checkskill(sd, TK_HPTIME) > 0 || pc_checkskill(sd, TK_SPTIME) > 0 )) { sd->state.rest = 1; status_calc_regen(bl, &sd->battle_status, &sd->regen); status_calc_regen_rate(bl, &sd->regen, &sd->sc); } return 0; } /** * Triggered when a player stands up to deactivate bonus states. * @param bl: Player object * @param ap: va_arg list * @return 0 */ static int skill_sit_out(struct block_list *bl, va_list ap) { map_session_data *sd = (map_session_data*)bl; int flag = va_arg(ap, int), range = va_arg(ap, int); if (map_foreachinallrange(skill_sit_count, &sd->bl, range, BL_PC, flag) > 1) return 0; if (flag&1 && sd->state.gangsterparadise) sd->state.gangsterparadise = 0; if (flag&2 && sd->state.rest) { sd->state.rest = 0; status_calc_regen(bl, &sd->battle_status, &sd->regen); status_calc_regen_rate(bl, &sd->regen, &sd->sc); } return 0; } /** * Toggle Sit icon and player bonuses when sitting/standing. * @param sd: Player data * @param sitting: True when sitting or false when standing * @return 0 */ int skill_sit(map_session_data *sd, bool sitting) { int flag = 0, range = 0, lv; nullpo_ret(sd); if ((lv = pc_checkskill(sd, RG_GANGSTER)) > 0) { flag |= 1; range = skill_get_splash(RG_GANGSTER, lv); } if ((lv = pc_checkskill(sd, TK_HPTIME)) > 0) { flag |= 2; range = skill_get_splash(TK_HPTIME, lv); } else if ((lv = pc_checkskill(sd, TK_SPTIME)) > 0) { flag |= 2; range = skill_get_splash(TK_SPTIME, lv); } if (sitting) clif_status_load(&sd->bl, EFST_SIT, 1); else clif_status_load(&sd->bl, EFST_SIT, 0); if (!flag) // No need to count area if no skills are learned. return 0; if (sitting) { if (map_foreachinallrange(skill_sit_count, &sd->bl, range, BL_PC, flag) > 1) map_foreachinallrange(skill_sit_in, &sd->bl, range, BL_PC, flag); } else map_foreachinallrange(skill_sit_out, &sd->bl, range, BL_PC, flag, range); return 0; } /*========================================== * Do Forstjoke/Scream effect *------------------------------------------*/ int skill_frostjoke_scream(struct block_list *bl, va_list ap) { struct block_list *src; uint16 skill_id,skill_lv; t_tick tick; nullpo_ret(bl); nullpo_ret(src = va_arg(ap,struct block_list*)); skill_id = va_arg(ap,int); skill_lv = va_arg(ap,int); if(!skill_lv) return 0; tick = va_arg(ap,t_tick); if (src == bl || status_isdead(bl)) return 0; if (bl->type == BL_PC) { map_session_data *sd = (map_session_data *)bl; if ( sd && sd->sc.option&(OPTION_INVISIBLE|OPTION_MADOGEAR) ) return 0;//Frost Joke / Scream cannot target invisible or MADO Gear characters [Ind] } //It has been reported that Scream/Joke works the same regardless of woe-setting. [Skotlex] if(battle_check_target(src,bl,BCT_ENEMY) > 0) skill_additional_effect(src,bl,skill_id,skill_lv,BF_MISC,ATK_DEF,tick); else if(battle_check_target(src,bl,BCT_PARTY) > 0 && rnd()%100 < 10) skill_additional_effect(src,bl,skill_id,skill_lv,BF_MISC,ATK_DEF,tick); return 0; } /** * Set map cell flag as skill unit effect * @param src Skill unit * @param skill_id * @param skill_lv * @param cell Cell type cell_t * @param flag 0/1 */ static void skill_unitsetmapcell(struct skill_unit *src, uint16 skill_id, uint16 skill_lv, cell_t cell, bool flag) { int range = skill_get_unit_range(skill_id,skill_lv); int x, y; for( y = src->bl.y - range; y <= src->bl.y + range; ++y ) for( x = src->bl.x - range; x <= src->bl.x + range; ++x ) map_setcell(src->bl.m, x, y, cell, flag); } /** * Do skill attack area (such splash effect) around the 'first' target. * First target will skip skill condition, always receive damage. But, * around it, still need target/condition validation by * battle_check_target and status_check_skilluse * @param bl * @param ap { atk_type, src, dsrc, skill_id, skill_lv, tick, flag, type } */ int skill_attack_area(struct block_list *bl, va_list ap) { struct block_list *src,*dsrc; int atk_type,skill_id,skill_lv,flag,type; t_tick tick; if(status_isdead(bl)) return 0; atk_type = va_arg(ap,int); src = va_arg(ap,struct block_list*); dsrc = va_arg(ap,struct block_list*); skill_id = va_arg(ap,int); skill_lv = va_arg(ap,int); tick = va_arg(ap,t_tick); flag = va_arg(ap,int); type = va_arg(ap,int); if (skill_area_temp[1] == bl->id) { //This is the target of the skill, do a full attack and skip target checks. return (int)skill_attack(atk_type,src,dsrc,bl,skill_id,skill_lv,tick,flag); } if(battle_check_target(dsrc,bl,type) <= 0 || !status_check_skilluse(NULL, bl, skill_id, 2)) return 0; switch (skill_id) { case WZ_FROSTNOVA: //Skills that don't require the animation to be removed if (src->x == bl->x && src->y == bl->y) return 0; //Does not hit current cell if (map_getcell(bl->m, bl->x, bl->y, CELL_CHKLANDPROTECTOR)) // Attack should not happen if the target is on Land Protector return 0; //Fall through case NPC_ACIDBREATH: case NPC_DARKNESSBREATH: case NPC_FIREBREATH: case NPC_ICEBREATH: case NPC_ICEBREATH2: case NPC_THUNDERBREATH: return (int)skill_attack(atk_type,src,dsrc,bl,skill_id,skill_lv,tick,flag); default: //Area-splash, disable skill animation. return (int)skill_attack(atk_type,src,dsrc,bl,skill_id,skill_lv,tick,flag|SD_ANIMATION); } } /** * Clear skill unit group * @param bl: Unit to check * @param flag: Skill group to clear */ int skill_clear_group(block_list *bl, uint8 flag) { nullpo_ret(bl); unit_data *ud = unit_bl2ud(bl); if (ud == nullptr) return 0; size_t count = 0; bool deleted = false; // The after loop statement might look stupid, but this prevents iteration problems, if an entry was deleted for (auto it = ud->skillunits.begin(); it != ud->skillunits.end(); (deleted ? it = ud->skillunits.begin() : it++), deleted = false) { switch ((*it)->skill_id) { case SA_DELUGE: case SA_VOLCANO: case SA_VIOLENTGALE: case SA_LANDPROTECTOR: case NJ_SUITON: case NJ_KAENSIN: case SC_CHAOSPANIC: case MH_POISON_MIST: case MH_LAVA_SLIDE: if (flag & 1) { skill_delunitgroup(*it); count++; deleted = true; } break; case SO_CLOUD_KILL: case NPC_CLOUD_KILL: if (flag & 4) { skill_delunitgroup(*it); count++; deleted = true; } break; case SO_WARMER: if (flag & { skill_delunitgroup(*it); count++; deleted = true; } break; default: if (flag & 2 && skill_get_inf2((*it)->skill_id, INF2_ISTRAP)) { skill_delunitgroup(*it); count++; deleted = true; } break; } } return static_cast<int>(count); } /** * Returns the first element field found [Skotlex] * @param bl * @return s_skill_unit_group */ std::shared_ptr<s_skill_unit_group> skill_locate_element_field(struct block_list *bl) { nullpo_ret(bl); unit_data *ud = unit_bl2ud(bl); if (ud == nullptr) return nullptr; for (const auto su : ud->skillunits) { switch (su->skill_id) { case SA_DELUGE: case SA_VOLCANO: case SA_VIOLENTGALE: case SA_LANDPROTECTOR: case NJ_SUITON: case SO_CLOUD_KILL: case NPC_CLOUD_KILL: case SO_WARMER: case SC_CHAOSPANIC: case MH_POISON_MIST: case MH_LAVA_SLIDE: return su; } } return nullptr; } /// Graffiti cleaner [Valaris] int skill_graffitiremover(struct block_list *bl, va_list ap) { struct skill_unit *unit = NULL; int remove = va_arg(ap, int); nullpo_retr(0, bl); if (bl->type != BL_SKILL || (unit = (struct skill_unit *)bl) == NULL) return 0; if ((unit->group) && (unit->group->unit_id == UNT_GRAFFITI)) { if (remove == 1) skill_delunit(unit); return 1; } return 0; } /// Greed effect int skill_greed(struct block_list *bl, va_list ap) { struct block_list *src; map_session_data *sd = NULL; struct flooritem_data *fitem = NULL; nullpo_ret(bl); nullpo_ret(src = va_arg(ap, struct block_list *)); if(src->type == BL_PC && (sd = (map_session_data *)src) && bl->type == BL_ITEM && (fitem = (struct flooritem_data *)bl)) pc_takeitem(sd, fitem); return 0; } /// Ranger's Detonator [Jobbie/3CeAM] int skill_detonator(struct block_list *bl, va_list ap) { nullpo_ret(bl); if (bl->type != BL_SKILL) return 0; block_list *src = va_arg(ap, block_list *); skill_unit *unit = (skill_unit *)bl; if (unit == nullptr) return 0; std::shared_ptr<s_skill_unit_group> group = unit->group; if (group == nullptr || group->src_id != src->id) return 0; int unit_id = group->unit_id; switch( unit_id ) { //List of Hunter and Ranger Traps that can be detonate. case UNT_BLASTMINE: case UNT_SANDMAN: case UNT_CLAYMORETRAP: case UNT_TALKIEBOX: case UNT_CLUSTERBOMB: case UNT_FIRINGTRAP: case UNT_ICEBOUNDTRAP: switch(unit_id) { case UNT_TALKIEBOX: clif_talkiebox(bl,group->valstr); group->val2 = -1; break; case UNT_CLAYMORETRAP: case UNT_FIRINGTRAP: case UNT_ICEBOUNDTRAP: map_foreachinrange(skill_trap_splash,bl,skill_get_splash(group->skill_id,group->skill_lv),group->bl_flag|BL_SKILL|~BCT_SELF,bl,group->tick); break; default: map_foreachinrange(skill_trap_splash,bl,skill_get_splash(group->skill_id,group->skill_lv),group->bl_flag,bl,group->tick); break; } if (unit->group == nullptr) return 0; clif_changetraplook(bl, UNT_USED_TRAPS); group->unit_id = UNT_USED_TRAPS; group->limit = DIFF_TICK(gettick(),group->tick) + (unit_id == UNT_TALKIEBOX ? 5000 : (unit_id == UNT_CLUSTERBOMB || unit_id == UNT_ICEBOUNDTRAP? 2500 : (unit_id == UNT_FIRINGTRAP ? 0 : 1500)) ); break; } return 0; } /** * Calculate Royal Guard's Banding bonus * @param sd: Player data * @return Number of Royal Guard */ int skill_banding_count(map_session_data *sd) { nullpo_ret(sd); return cap_value(party_foreachsamemap(party_sub_count, sd, skill_get_splash(LG_BANDING, 1)), 0, MAX_PARTY); } /** * Rebellion's Bind Trap explosion * @author [Cydh] */ static int skill_bind_trap(struct block_list *bl, va_list ap) { struct skill_unit *su = NULL; struct block_list *src = NULL; nullpo_ret(bl); src = va_arg(ap,struct block_list *); if (bl->type != BL_SKILL || !(su = (struct skill_unit *)bl) || !(su->group)) return 0; if (su->group->unit_id != UNT_B_TRAP || su->group->src_id != src->id) return 0; map_foreachinallrange(skill_trap_splash, bl, su->range, BL_CHAR, bl,su->group->tick); clif_changetraplook(bl, UNT_USED_TRAPS); su->group->unit_id = UNT_USED_TRAPS; su->group->limit = DIFF_TICK(gettick(), su->group->tick) + 500; return 1; } /*========================================== * Check new skill unit cell when overlapping in other skill unit cell. * Catched skill in cell value pushed to *unit pointer. * Set (*alive) to 0 will ends 'new unit' check *------------------------------------------*/ static int skill_cell_overlap(struct block_list *bl, va_list ap) { uint16 skill_id; int *alive; struct skill_unit *unit; skill_id = va_arg(ap,int); alive = va_arg(ap,int *); unit = (struct skill_unit *)bl; if (unit == NULL || unit->group == NULL || (*alive) == 0) return 0; if (unit->group->state.guildaura) /* guild auras are not cancelled! */ return 0; switch (skill_id) { case SA_LANDPROTECTOR: { if( unit->group->skill_id == SA_LANDPROTECTOR ) {//Check for offensive Land Protector to delete both. [Skotlex] (*alive) = 0; skill_delunit(unit); return 1; } std::shared_ptr<s_skill_db> skill = skill_db.find(unit->group->skill_id); //It deletes everything except traps and barriers if ((!skill->inf2[INF2_ISTRAP] && !skill->inf2[INF2_IGNORELANDPROTECTOR]) || unit->group->skill_id == WZ_FIREPILLAR) { if (skill->unit_flag[UF_RANGEDSINGLEUNIT]) { if (unit->val2&(1 << UF_RANGEDSINGLEUNIT)) skill_delunitgroup(unit->group); } else skill_delunit(unit); return 1; } } break; case GN_CRAZYWEED_ATK: if (skill_get_unit_flag(unit->group->skill_id, UF_CRAZYWEEDIMMUNE)) break; case HW_GANBANTEIN: case LG_EARTHDRIVE: // Officially songs/dances are removed if (skill_get_unit_flag(unit->group->skill_id, UF_RANGEDSINGLEUNIT)) { if (unit->val2&(1 << UF_RANGEDSINGLEUNIT)) skill_delunitgroup(unit->group); } else skill_delunit(unit); return 1; case SA_VOLCANO: case SA_DELUGE: case SA_VIOLENTGALE: // The official implementation makes them fail to appear when casted on top of ANYTHING // but I wonder if they didn't actually meant to fail when casted on top of each other? // hence, I leave the alternate implementation here, commented. [Skotlex] if (unit->range <= 0 && skill_get_unit_id(unit->group->skill_id) != UNT_DUMMYSKILL) { (*alive) = 0; return 1; } /* switch (unit->group->skill_id) { //These cannot override each other. case SA_VOLCANO: case SA_DELUGE: case SA_VIOLENTGALE: (*alive) = 0; return 1; } */ break; case PF_FOGWALL: switch(unit->group->skill_id) { case SA_VOLCANO: //Can't be placed on top of these case SA_VIOLENTGALE: (*alive) = 0; return 1; case SA_DELUGE: case NJ_SUITON: //Cheap 'hack' to notify the calling function that duration should be doubled [Skotlex] (*alive) = 2; break; } break; case WZ_WATERBALL: switch (unit->group->skill_id) { case SA_DELUGE: case NJ_SUITON: //Consumes deluge/suiton skill_delunit(unit); return 1; } break; case WZ_ICEWALL: #ifndef RENEWAL case HP_BASILICA: case HW_GRAVITATION: #endif //These can't be placed on top of themselves (duration can't be refreshed) if (unit->group->skill_id == skill_id) { (*alive) = 0; return 1; } break; case RL_FIRE_RAIN: { std::bitset<UF_MAX> uf = skill_db.find(unit->group->skill_id)->unit_flag; if (uf[UF_REMOVEDBYFIRERAIN]) { if (uf[UF_RANGEDSINGLEUNIT]) { if (unit->val2&(1 << UF_RANGEDSINGLEUNIT)) skill_delunitgroup(unit->group); } else skill_delunit(unit); return 1; } } break; } std::bitset<INF2_MAX> inf2 = skill_db.find(skill_id)->inf2; if (unit->group->skill_id == SA_LANDPROTECTOR && !inf2[INF2_ISTRAP] && !inf2[INF2_IGNORELANDPROTECTOR] ) { //It deletes everything except traps and barriers (*alive) = 0; return 1; } return 0; } /*========================================== * Splash effect for skill unit 'trap type'. * Chance triggered when damaged, timeout, or char step on it. *------------------------------------------*/ static int skill_trap_splash(struct block_list *bl, va_list ap) { struct block_list *src = va_arg(ap,struct block_list *); struct skill_unit *unit = NULL; t_tick tick = va_arg(ap,t_tick); struct block_list *ss; //Skill src bl nullpo_ret(src); unit = (struct skill_unit *)src; if (!unit || !unit->alive || bl->prev == NULL) return 0; std::shared_ptr<s_skill_unit_group> sg = unit->group; if (sg == nullptr) return 0; nullpo_ret(ss = map_id2bl(sg->src_id)); if (battle_check_target(src,bl,sg->target_flag) <= 0) return 0; switch (sg->unit_id) { case UNT_B_TRAP: if (battle_check_target(ss, bl, sg->target_flag&~BCT_SELF) > 0) skill_castend_damage_id(ss, bl, sg->skill_id, sg->skill_lv, tick, SD_ANIMATION|SD_LEVEL|SD_SPLASH|1); break; case UNT_SHOCKWAVE: case UNT_SANDMAN: case UNT_FLASHER: skill_additional_effect(ss,bl,sg->skill_id,sg->skill_lv,BF_MISC,ATK_DEF,tick); break; case UNT_GROUNDDRIFT_WIND: if(skill_attack(skill_get_type(sg->skill_id),ss,src,bl,sg->skill_id,sg->skill_lv,tick,sg->val1)) sc_start(ss,bl,SC_STUN,50,sg->skill_lv,skill_get_time2(sg->skill_id, 1)); break; case UNT_GROUNDDRIFT_DARK: if(skill_attack(skill_get_type(sg->skill_id),ss,src,bl,sg->skill_id,sg->skill_lv,tick,sg->val1)) sc_start(ss,bl,SC_BLIND,50,sg->skill_lv,skill_get_time2(sg->skill_id, 2)); break; case UNT_GROUNDDRIFT_POISON: if(skill_attack(skill_get_type(sg->skill_id),ss,src,bl,sg->skill_id,sg->skill_lv,tick,sg->val1)) sc_start2(ss,bl,SC_POISON,50,sg->skill_lv,ss->id,skill_get_time2(sg->skill_id, 3)); break; case UNT_GROUNDDRIFT_WATER: if(skill_attack(skill_get_type(sg->skill_id),ss,src,bl,sg->skill_id,sg->skill_lv,tick,sg->val1)) sc_start(ss,bl,SC_FREEZE,50,sg->skill_lv,skill_get_time2(sg->skill_id, 4)); break; case UNT_GROUNDDRIFT_FIRE: if(skill_attack(skill_get_type(sg->skill_id),ss,src,bl,sg->skill_id,sg->skill_lv,tick,sg->val1)) skill_blown(src,bl,skill_get_blewcount(sg->skill_id,sg->skill_lv),-1,BLOWN_NONE); break; case UNT_ELECTRICSHOCKER: if (bl->id != ss->id) { if (status_bl_has_mode(bl,MD_STATUSIMMUNE)) break; if (status_change_start(ss, bl, SC_ELECTRICSHOCKER, 10000, sg->skill_lv, sg->group_id, 0, 0, skill_get_time2(sg->skill_id, sg->skill_lv), SCSTART_NORATEDEF)) { map_moveblock(bl, unit->bl.x, unit->bl.y, tick); clif_fixpos(bl); clif_skill_damage(src, bl, tick, 0, 0, -30000, 1, sg->skill_id, sg->skill_lv, DMG_SPLASH); } } break; case UNT_MAGENTATRAP: case UNT_COBALTTRAP: case UNT_MAIZETRAP: case UNT_VERDURETRAP: if( bl->type == BL_MOB && status_get_class_(bl) != CLASS_BOSS ) { struct status_data *status = status_get_status_data(bl); status->def_ele = skill_get_ele(sg->skill_id, sg->skill_lv); status->ele_lv = (unsigned char)sg->skill_lv; } break; case UNT_REVERBERATION: // For proper skill delay animation when used with Dominion Impulse skill_addtimerskill(ss, tick + 50, bl->id, 0, 0, NPC_REVERBERATION_ATK, sg->skill_lv, BF_WEAPON, 0); break; case UNT_FIRINGTRAP: case UNT_ICEBOUNDTRAP: if( src->id == bl->id ) break; if( bl->type == BL_SKILL ) { struct skill_unit *su = (struct skill_unit *)bl; if (su && su->group->unit_id == UNT_USED_TRAPS) break; } case UNT_CLUSTERBOMB: if( ss != bl ) skill_attack(BF_MISC,ss,src,bl,sg->skill_id,sg->skill_lv,tick,sg->val1|SD_LEVEL); break; case UNT_CLAYMORETRAP: if( src->id == bl->id ) break; if( bl->type == BL_SKILL ) { struct skill_unit *su = (struct skill_unit *)bl; if (!su) return 0; switch(su->group->unit_id) { case UNT_CLAYMORETRAP: case UNT_LANDMINE: case UNT_BLASTMINE: case UNT_SHOCKWAVE: case UNT_SANDMAN: case UNT_FLASHER: case UNT_FREEZINGTRAP: case UNT_FIRINGTRAP: case UNT_ICEBOUNDTRAP: clif_changetraplook(bl, UNT_USED_TRAPS); su->group->limit = DIFF_TICK(gettick(),su->group->tick) + 1500; su->group->unit_id = UNT_USED_TRAPS; break; } } default: { int split_count = 0; if (skill_get_nk(sg->skill_id, NK_SPLASHSPLIT)) split_count = max(1, map_foreachinallrange(skill_area_sub, src, skill_get_splash(sg->skill_id, sg->skill_lv), BL_CHAR, src, sg->skill_id, sg->skill_lv, tick, BCT_ENEMY, skill_area_sub_count)); skill_attack(skill_get_type(sg->skill_id), ss, src, bl, sg->skill_id, sg->skill_lv, tick, split_count); } break; } return 1; } int skill_maelstrom_suction(struct block_list *bl, va_list ap) { uint16 skill_id, skill_lv; struct skill_unit *unit; nullpo_ret(bl); skill_id = va_arg(ap,int); skill_lv = va_arg(ap,int); unit = (struct skill_unit *)bl; if( unit == NULL || unit->group == NULL ) return 0; if( skill_get_inf2(skill_id, INF2_ISTRAP) ) return 0; if( unit->group->skill_id == SC_MAELSTROM ) { struct block_list *src; if( (src = map_id2bl(unit->group->src_id)) ){ int sp = unit->group->skill_lv * skill_lv; if( src->type == BL_PC ) sp += ((TBL_PC*)src)->status.job_level / 5; status_heal(src, 0, sp/2, 1); } } return 0; } /** * Check cloaking condition * @param bl * @param sce * @return True if near wall; False otherwise */ bool skill_check_cloaking(struct block_list *bl, struct status_change_entry *sce) { bool wall = true; if( (bl->type == BL_PC && battle_config.pc_cloak_check_type&1) || (bl->type != BL_PC && battle_config.monster_cloak_check_type&1) ) { //Check for walls. static int dx[] = { 0, 1, 0, -1, -1, 1, 1, -1}; static int dy[] = {-1, 0, 1, 0, -1, -1, 1, 1}; int i; ARR_FIND( 0, 8, i, map_getcell(bl->m, bl->x+dx[i], bl->y+dy[i], CELL_CHKNOPASS) != 0 ); if( i == 8 ) wall = false; } if( sce ) { if( !wall ) { if( sce->val1 < 3 ) //End cloaking. status_change_end(bl, SC_CLOAKING); else if( sce->val4&1 ) { //Remove wall bonus sce->val4&=~1; status_calc_bl(bl, { SCB_SPEED }); } } else { if( !(sce->val4&1) ) { //Add wall speed bonus sce->val4|=1; status_calc_bl(bl, { SCB_SPEED }); } } } return wall; } /** Check Shadow Form on the target * @param bl: Target * @param damage: Damage amount * @param hit * @return true - in Shadow Form state; false - otherwise */ bool skill_check_shadowform(struct block_list *bl, int64 damage, int hit) { status_change *sc; nullpo_retr(false,bl); if (!damage) return false; sc = status_get_sc(bl); if( sc && sc->getSCE(SC__SHADOWFORM) ) { struct block_list *src = map_id2bl(sc->getSCE(SC__SHADOWFORM)->val2); if( !src || src->m != bl->m ) { status_change_end(bl, SC__SHADOWFORM); return false; } if( src && (status_isdead(src) || !battle_check_target(bl,src,BCT_ENEMY)) ) { if( src->type == BL_PC ) ((TBL_PC*)src)->shadowform_id = 0; status_change_end(bl, SC__SHADOWFORM); return false; } status_damage(bl, src, damage, 0, clif_damage(src, src, gettick(), 500, 500, damage, hit, (hit > 1 ? DMG_MULTI_HIT : DMG_NORMAL), 0, false), 0, SC__SHADOWFORM); if( sc && sc->getSCE(SC__SHADOWFORM) && (--sc->getSCE(SC__SHADOWFORM)->val3) <= 0 ) { status_change_end(bl, SC__SHADOWFORM); if( src->type == BL_PC ) ((TBL_PC*)src)->shadowform_id = 0; } return true; } return false; } /** * Check camouflage condition * @param bl * @param sce * @return True if near wall; False otherwise * @TODO: Seems wrong */ bool skill_check_camouflage(struct block_list *bl, struct status_change_entry *sce) { bool wall = true; if( bl->type == BL_PC ) { //Check for walls. static int dx[] = { 0, 1, 0, -1, -1, 1, 1, -1}; static int dy[] = {-1, 0, 1, 0, -1, -1, 1, 1}; int i; ARR_FIND( 0, 8, i, map_getcell(bl->m, bl->x+dx[i], bl->y+dy[i], CELL_CHKNOPASS) != 0 ); if( i == 8 ) wall = false; } if( sce ) { if( !wall && sce->val1 < 3 ) //End camouflage. status_change_end(bl, SC_CAMOUFLAGE); status_calc_bl(bl, { SCB_SPEED }); } return wall; } /** * Process skill unit visibilty for single BL in area * @param bl * @param ap * @author [Cydh] **/ int skill_getareachar_skillunit_visibilty_sub(struct block_list *bl, va_list ap) { struct skill_unit *su = NULL; struct block_list *src = NULL; unsigned int party1 = 0; bool visible = true; nullpo_ret(bl); nullpo_ret((su = va_arg(ap, struct skill_unit*))); nullpo_ret((src = va_arg(ap, struct block_list*))); party1 = va_arg(ap, unsigned int); if (src != bl) { unsigned int party2 = status_get_party_id(bl); if (!party1 || !party2 || party1 != party2) visible = false; } clif_getareachar_skillunit(bl, su, SELF, visible); return 1; } /** * Check for skill unit visibilty in area on * - skill first placement * - skill moved (knocked back, moved dance) * @param su Skill unit * @param target Affected target for this visibility @see enum send_target * @author [Cydh] **/ void skill_getareachar_skillunit_visibilty(struct skill_unit *su, enum send_target target) { nullpo_retv(su); if (!su->hidden) // It's not hidden, just do this! clif_getareachar_skillunit(&su->bl, su, target, true); else { struct block_list *src = battle_get_master(&su->bl); map_foreachinallarea(skill_getareachar_skillunit_visibilty_sub, su->bl.m, su->bl.x-AREA_SIZE, su->bl.y-AREA_SIZE, su->bl.x+AREA_SIZE, su->bl.y+AREA_SIZE, BL_PC, su, src, status_get_party_id(src)); } } /** * Check for skill unit visibilty on single BL on insight/spawn action * @param su Skill unit * @param bl Block list * @author [Cydh] **/ void skill_getareachar_skillunit_visibilty_single(struct skill_unit *su, struct block_list *bl) { bool visible = true; struct block_list *src = NULL; nullpo_retv(bl); nullpo_retv(su); nullpo_retv((src = battle_get_master(&su->bl))); if (su->hidden && src != bl) { unsigned int party1 = status_get_party_id(src); unsigned int party2 = status_get_party_id(bl); if (!party1 || !party2 || party1 != party2) visible = false; } clif_getareachar_skillunit(bl, su, SELF, visible); } /** * Initialize new skill unit for skill unit group. * Overall, Skill Unit makes skill unit group which each group holds their cell datas (skill unit) * @param group Skill unit group * @param idx * @param x * @param y * @param val1 * @param val2 */ struct skill_unit *skill_initunit(std::shared_ptr<s_skill_unit_group> group, int idx, int x, int y, int val1, int val2, bool hidden) { if (group == nullptr || group->unit == nullptr) return nullptr; skill_unit *unit = &group->unit[idx]; if (unit == nullptr) return nullptr; if( map_getcell(map_id2bl(group->src_id)->m, x, y, CELL_CHKMAELSTROM) ) return unit; if(!unit->alive) group->alive_count++; unit->bl.id = map_get_new_object_id(); unit->bl.type = BL_SKILL; unit->bl.m = group->map; unit->bl.x = x; unit->bl.y = y; unit->group = group; unit->alive = 1; unit->val1 = val1; unit->val2 = val2; unit->hidden = hidden; // Stores new skill unit idb_put(skillunit_db, unit->bl.id, unit); map_addiddb(&unit->bl); if(map_addblock(&unit->bl)) return NULL; // Perform oninit actions switch (group->skill_id) { case WZ_ICEWALL: map_setgatcell(unit->bl.m,unit->bl.x,unit->bl.y,5); clif_changemapcell(0,unit->bl.m,unit->bl.x,unit->bl.y,5,AREA); skill_unitsetmapcell(unit,WZ_ICEWALL,group->skill_lv,CELL_ICEWALL,true); break; case SA_LANDPROTECTOR: skill_unitsetmapcell(unit,SA_LANDPROTECTOR,group->skill_lv,CELL_LANDPROTECTOR,true); break; #ifndef RENEWAL case HP_BASILICA: skill_unitsetmapcell(unit,HP_BASILICA,group->skill_lv,CELL_BASILICA,true); break; #endif case SC_MAELSTROM: skill_unitsetmapcell(unit,SC_MAELSTROM,group->skill_lv,CELL_MAELSTROM,true); break; default: if (group->state.song_dance&0x1) //Check for dissonance. skill_dance_overlap(unit, 1); break; } skill_getareachar_skillunit_visibilty(unit, AREA); return unit; } /** * Remove unit * @param unit */ int skill_delunit(struct skill_unit* unit) { nullpo_ret(unit); if( !unit->alive ) return 0; unit->alive = 0; std::shared_ptr<s_skill_unit_group> group = unit->group; if (group == nullptr) return 0; if( group->state.song_dance&0x1 ) //Cancel dissonance effect. skill_dance_overlap(unit, 0); // invoke onout event if( !unit->range ) map_foreachincell(skill_unit_effect,unit->bl.m,unit->bl.x,unit->bl.y,group->bl_flag,&unit->bl,gettick(),4); // perform ondelete actions switch (group->skill_id) { case HT_ANKLESNARE: case SC_ESCAPE: { struct block_list* target = map_id2bl(group->val2); if( target ) status_change_end(target, SC_ANKLE); } break; case WZ_ICEWALL: map_setgatcell(unit->bl.m,unit->bl.x,unit->bl.y,unit->val2); clif_changemapcell(0,unit->bl.m,unit->bl.x,unit->bl.y,unit->val2,ALL_SAMEMAP); // hack to avoid clientside cell bug skill_unitsetmapcell(unit,WZ_ICEWALL,group->skill_lv,CELL_ICEWALL,false); break; case SA_LANDPROTECTOR: skill_unitsetmapcell(unit,SA_LANDPROTECTOR,group->skill_lv,CELL_LANDPROTECTOR,false); break; #ifndef RENEWAL case HP_BASILICA: skill_unitsetmapcell(unit,HP_BASILICA,group->skill_lv,CELL_BASILICA,false); break; #endif case RA_ELECTRICSHOCKER: { struct block_list* target = map_id2bl(group->val2); if( target ) status_change_end(target, SC_ELECTRICSHOCKER); } break; case SC_MAELSTROM: skill_unitsetmapcell(unit,SC_MAELSTROM,group->skill_lv,CELL_MAELSTROM,false); break; case SC_MANHOLE: // Note : Removing the unit don't remove the status (official info) if( group->val2 ) { // Someone Traped status_change *tsc = status_get_sc( map_id2bl(group->val2)); if( tsc && tsc->getSCE(SC__MANHOLE) ) tsc->getSCE(SC__MANHOLE)->val4 = 0; // Remove the Unit ID } break; } clif_skill_delunit(unit); unit->group=NULL; map_delblock(&unit->bl); // don't free yet map_deliddb(&unit->bl); idb_remove(skillunit_db, unit->bl.id); if(--group->alive_count==0) skill_delunitgroup(group); return 0; } static std::unordered_map<int, std::shared_ptr<s_skill_unit_group>> skillunit_group_db; /// Skill unit group DB. Key int group_id -> struct s_skill_unit_group* /// Returns the target s_skill_unit_group or nullptr if not found. std::shared_ptr<s_skill_unit_group> skill_id2group(int group_id) { return util::umap_find(skillunit_group_db, group_id); } static int skill_unit_group_newid = MAX_SKILL; /// Skill Unit Group ID /** * Returns a new group_id that isn't being used in skillunit_group_db. * Fatal error if nothing is available. */ static int skill_get_new_group_id(void) { if( skill_unit_group_newid >= MAX_SKILL && skill_id2group(skill_unit_group_newid) == NULL ) return skill_unit_group_newid++;// available {// find next id int base_id = skill_unit_group_newid; while( base_id != ++skill_unit_group_newid ) { if( skill_unit_group_newid < MAX_SKILL ) skill_unit_group_newid = MAX_SKILL; if( skill_id2group(skill_unit_group_newid) == NULL ) return skill_unit_group_newid++;// available } // full loop, nothing available ShowFatalError("skill_get_new_group_id: All ids are taken. Exiting..."); exit(1); } } /** * Initialize skill unit group called while setting new unit (skill unit/ground skill) in skill_unitsetting() * @param src Object that cast the skill * @param count How many 'cells' used that needed. Related with skill layout * @param skill_id ID of used skill * @param skill_lv Skill level of used skill * @param unit_id Unit ID (see skill.hpp::e_skill_unit_id) * @param limit Lifetime for skill unit, uses skill_get_time(skill_id, skill_lv) * @param interval Time interval * @return s_skill_unit_group */ std::shared_ptr<s_skill_unit_group> skill_initunitgroup(struct block_list* src, int count, uint16 skill_id, uint16 skill_lv, int unit_id, t_tick limit, int interval) { nullpo_retr(nullptr, src); unit_data *ud = unit_bl2ud(src); nullpo_retr(nullptr, ud); if (skill_id == 0 || skill_lv == 0) return 0; auto group = std::make_shared<s_skill_unit_group>(); group->src_id = src->id; group->party_id = status_get_party_id(src); group->guild_id = status_get_guild_id(src); group->bg_id = bg_team_get_id(src); group->group_id = skill_get_new_group_id(); group->link_group_id = 0; group->unit = (skill_unit *)aCalloc(count, sizeof(skill_unit)); group->unit_count = count; group->alive_count = 0; group->val1 = 0; group->val2 = 0; group->val3 = 0; group->skill_id = skill_id; group->skill_lv = skill_lv; group->unit_id = unit_id; group->map = src->m; group->limit = limit; group->interval = interval; group->tick = gettick(); group->valstr = nullptr; ud->skillunits.push_back(group); // Stores this new group skillunit_group_db.insert({ group->group_id, group }); return group; } /** * Remove skill unit group * @param group * @param file * @param line * @param *func */ int skill_delunitgroup_(std::shared_ptr<s_skill_unit_group> group, const char* file, int line, const char* func) { struct block_list* src; struct unit_data *ud; short i; int link_group_id; if( group == nullptr ) { ShowDebug("skill_delunitgroup: group is NULL (source=%s:%d, %s)! Please report this! (#3504)\n", file, line, func); return 0; } src = map_id2bl(group->src_id); ud = unit_bl2ud(src); if (!src || !ud) { ShowError("skill_delunitgroup: Group's source not found! (src_id: %d skill_id: %d)\n", group->src_id, group->skill_id); return 0; } if( !status_isdead(src) && ((TBL_PC*)src)->state.warping && !((TBL_PC*)src)->state.changemap ) { switch( group->skill_id ) { case BA_DISSONANCE: case BA_POEMBRAGI: case BA_WHISTLE: case BA_ASSASSINCROSS: case BA_APPLEIDUN: case DC_UGLYDANCE: case DC_HUMMING: case DC_DONTFORGETME: case DC_FORTUNEKISS: case DC_SERVICEFORYOU: case NC_NEUTRALBARRIER: case NC_STEALTHFIELD: skill_usave_add(((TBL_PC*)src), group->skill_id, group->skill_lv); break; } } if (skill_get_unit_flag_(group->skill_id, { UF_DANCE, UF_SONG, UF_ENSEMBLE })) { status_change* sc = status_get_sc(src); if (sc && sc->getSCE(SC_DANCING)) { sc->getSCE(SC_DANCING)->val2 = 0 ; //This prevents status_change_end attempting to redelete the group. [Skotlex] status_change_end(src, SC_DANCING); } } // End SC from the master when the skill group is deleted i = SC_NONE; switch (group->unit_id) { case UNT_GOSPEL: i = SC_GOSPEL; break; #ifndef RENEWAL case UNT_BASILICA: i = SC_BASILICA; break; #endif } if (i != SC_NONE) { status_change *sc = status_get_sc(src); if (sc && sc->getSCE(i)) { sc->getSCE(i)->val3 = 0; //Remove reference to this group. [Skotlex] status_change_end(src, (sc_type)i); } } switch( group->skill_id ) { case PF_SPIDERWEB: { struct block_list* target = map_id2bl(group->val2); status_change *sc; bool removed = true; //Clear group id from status change if (target && (sc = status_get_sc(target)) != NULL && sc->getSCE(SC_SPIDERWEB)) { if (sc->getSCE(SC_SPIDERWEB)->val2 == group->group_id) sc->getSCE(SC_SPIDERWEB)->val2 = 0; else if (sc->getSCE(SC_SPIDERWEB)->val3 == group->group_id) sc->getSCE(SC_SPIDERWEB)->val3 = 0; else if (sc->getSCE(SC_SPIDERWEB)->val4 == group->group_id) sc->getSCE(SC_SPIDERWEB)->val4 = 0; else //Group was already removed in status_change_end, don't call it again! removed = false; //The last group was cleared, end status change if(removed && sc->getSCE(SC_SPIDERWEB)->val2 == 0 && sc->getSCE(SC_SPIDERWEB)->val3 == 0 && sc->getSCE(SC_SPIDERWEB)->val4 == 0) status_change_end(target, SC_SPIDERWEB); } } break; case SG_SUN_WARM: case SG_MOON_WARM: case SG_STAR_WARM: { status_change *sc = status_get_sc(src); if (sc && sc->getSCE(SC_WARM)) { sc->getSCE(SC_WARM)->val4 = 0; status_change_end(src, SC_WARM); } } break; case LG_BANDING: { status_change *sc = status_get_sc(src); if (sc && sc->getSCE(SC_BANDING)) { sc->getSCE(SC_BANDING)->val4 = 0; status_change_end(src, SC_BANDING); } } break; case NC_NEUTRALBARRIER: { status_change *sc = NULL; if( (sc = status_get_sc(src)) != NULL ) { if ( sc->getSCE(SC_NEUTRALBARRIER_MASTER) ) { sc->getSCE(SC_NEUTRALBARRIER_MASTER)->val2 = 0; status_change_end(src,SC_NEUTRALBARRIER_MASTER); } status_change_end(src,SC_NEUTRALBARRIER); } } break; case NC_STEALTHFIELD: { status_change *sc = NULL; if( (sc = status_get_sc(src)) != NULL && sc->getSCE(SC_STEALTHFIELD_MASTER) ) { sc->getSCE(SC_STEALTHFIELD_MASTER)->val2 = 0; status_change_end(src,SC_STEALTHFIELD_MASTER); } } break; } if (src->type==BL_PC && group->state.ammo_consume) battle_consume_ammo((TBL_PC*)src, group->skill_id, group->skill_lv); group->alive_count=0; // remove all unit cells if(group->unit != NULL) for( int j = 0; j < group->unit_count; j++ ) skill_delunit(&group->unit[j]); // clear Talkie-box string if( group->valstr != NULL ) { aFree(group->valstr); group->valstr = NULL; } link_group_id = group->link_group_id; if (skillunit_group_db.erase(group->group_id) != 1) ShowError("skill_delunitgroup: Group not found! (src_id: %d skill_id: %d)\n", group->src_id, group->skill_id); util::vector_erase_if_exists(ud->skillunits, group); if(link_group_id) { std::shared_ptr<s_skill_unit_group> group_cur = skill_id2group(link_group_id); if(group_cur) skill_delunitgroup(group_cur); } return 1; } /** * Clear all Skill Unit Group from an Object, example usage when player logged off or dead * @param src */ void skill_clear_unitgroup(struct block_list *src) { nullpo_retv(src); unit_data *ud = unit_bl2ud(src); nullpo_retv(ud); // The after loop statement might look stupid, but this prevents iteration problems, if an entry was deleted for (auto it = ud->skillunits.begin(); it != ud->skillunits.end(); it = ud->skillunits.begin()) { skill_delunitgroup(*it); } } /** * Search tickset for skill unit in skill unit group * @param bl Block List for skill_unit * @param group Skill unit group * @param tick * @return skill_unit_group_tickset if found */ struct skill_unit_group_tickset *skill_unitgrouptickset_search(struct block_list *bl, std::shared_ptr<s_skill_unit_group> group, t_tick tick) { int i, j = -1, s, id; struct unit_data *ud; struct skill_unit_group_tickset *set; nullpo_ret(bl); if (group->interval == -1) return NULL; ud = unit_bl2ud(bl); if (!ud) return NULL; set = ud->skillunittick; if (skill_get_unit_flag(group->skill_id, UF_NOOVERLAP)) id = s = group->skill_id; else id = s = group->group_id; for (i=0; i<MAX_SKILLUNITGROUPTICKSET; i++) { int k = (i+s) % MAX_SKILLUNITGROUPTICKSET; if (set[k].id == id) return &set[k]; else if (j==-1 && (DIFF_TICK(tick,set[k].tick)>0 || set[k].id==0)) j=k; } if (j == -1) { ShowWarning ("skill_unitgrouptickset_search: tickset is full\n"); j = id % MAX_SKILLUNITGROUPTICKSET; } set[j].id = id; set[j].tick = tick; return &set[j]; } /*========================================== * Check for validity skill unit that triggered by skill_unit_timer_sub * And trigger skill_unit_onplace_timer for object that maybe stands there (catched object is *bl) *------------------------------------------*/ int skill_unit_timer_sub_onplace(struct block_list* bl, va_list ap) { struct skill_unit* unit = va_arg(ap,struct skill_unit *); t_tick tick = va_arg(ap,t_tick); nullpo_ret(unit); if( !unit->alive || bl->prev == NULL ) return 0; std::shared_ptr<s_skill_unit_group> group = unit->group; if (group == nullptr) return 0; std::shared_ptr<s_skill_db> skill = skill_db.find(group->skill_id); if( !(skill->inf2[INF2_ISSONG] || skill->inf2[INF2_ISTRAP]) && !skill->inf2[INF2_IGNORELANDPROTECTOR] && group->skill_id != NC_NEUTRALBARRIER && (battle_config.land_protector_behavior ? map_getcell(bl->m, bl->x, bl->y, CELL_CHKLANDPROTECTOR) : map_getcell(unit->bl.m, unit->bl.x, unit->bl.y, CELL_CHKLANDPROTECTOR)) ) return 0; //AoE skills are ineffective. [Skotlex] if( battle_check_target(&unit->bl,bl,group->target_flag) <= 0 ) return 0; skill_unit_onplace_timer(unit,bl,tick); return 1; } /** * @see DBApply * Sub function of skill_unit_timer for executing each skill unit from skillunit_db */ static int skill_unit_timer_sub(DBKey key, DBData *data, va_list ap) { struct skill_unit* unit = (struct skill_unit*)db_data2ptr(data); t_tick tick = va_arg(ap,t_tick); bool dissonance; struct block_list* bl = &unit->bl; nullpo_ret(unit); if( !unit->alive ) return 0; std::shared_ptr<s_skill_unit_group> group = unit->group; if (group == nullptr) return 0; // Check for expiration if( !group->state.guildaura && (DIFF_TICK(tick,group->tick) >= group->limit || DIFF_TICK(tick,group->tick) >= unit->limit) ) {// skill unit expired (inlined from skill_unit_onlimit()) switch( group->unit_id ) { case UNT_ICEWALL: unit->val1 -= 50; // icewall loses 50 hp every second group->limit = DIFF_TICK(tick + group->interval,group->tick); unit->limit = DIFF_TICK(tick + group->interval,group->tick); if( unit->val1 <= 0 ) skill_delunit(unit); break; case UNT_BLASTMINE: #ifdef RENEWAL case UNT_CLAYMORETRAP: #endif case UNT_GROUNDDRIFT_WIND: case UNT_GROUNDDRIFT_DARK: case UNT_GROUNDDRIFT_POISON: case UNT_GROUNDDRIFT_WATER: case UNT_GROUNDDRIFT_FIRE: group->unit_id = UNT_USED_TRAPS; //clif_changetraplook(bl, UNT_FIREPILLAR_ACTIVE); group->limit=DIFF_TICK(tick+1500,group->tick); unit->limit=DIFF_TICK(tick+1500,group->tick); break; case UNT_ANKLESNARE: case UNT_ELECTRICSHOCKER: if (group->val2 > 0) { //Used Trap doesn't return back to item skill_delunit(unit); break; } case UNT_SKIDTRAP: case UNT_LANDMINE: case UNT_SHOCKWAVE: case UNT_SANDMAN: case UNT_FLASHER: case UNT_FREEZINGTRAP: #ifndef RENEWAL case UNT_CLAYMORETRAP: #endif case UNT_TALKIEBOX: case UNT_CLUSTERBOMB: case UNT_MAGENTATRAP: case UNT_COBALTTRAP: case UNT_MAIZETRAP: case UNT_VERDURETRAP: case UNT_FIRINGTRAP: case UNT_ICEBOUNDTRAP: { struct block_list* src; if( unit->val1 > 0 && (src = map_id2bl(group->src_id)) != NULL && src->type == BL_PC ) { // revert unit back into a trap struct item item_tmp; memset(&item_tmp,0,sizeof(item_tmp)); item_tmp.nameid = group->item_id?group->item_id:ITEMID_TRAP; item_tmp.identify = 1; map_addflooritem(&item_tmp,1,bl->m,bl->x,bl->y,0,0,0,4,0); } skill_delunit(unit); } break; case UNT_WARP_ACTIVE: // warp portal opens (morph to a UNT_WARP_WAITING cell) group->unit_id = skill_get_unit_id2(group->skill_id); // UNT_WARP_WAITING clif_changelook(&unit->bl, LOOK_BASE, group->unit_id); // restart timers group->limit = skill_get_time(group->skill_id,group->skill_lv); unit->limit = skill_get_time(group->skill_id,group->skill_lv); // apply effect to all units standing on it map_foreachincell(skill_unit_effect,unit->bl.m,unit->bl.x,unit->bl.y,group->bl_flag,&unit->bl,gettick(),1); break; case UNT_CALLFAMILY: { map_session_data *sd = NULL; if(group->val1) { sd = map_charid2sd(group->val1); group->val1 = 0; if (sd && !map_getmapflag(sd->bl.m, MF_NOWARP) && pc_job_can_entermap((enum e_job)sd->status.class_, unit->bl.m, pc_get_group_level(sd))) pc_setpos(sd,map_id2index(unit->bl.m),unit->bl.x,unit->bl.y,CLR_TELEPORT); } if(group->val2) { sd = map_charid2sd(group->val2); group->val2 = 0; if (sd && !map_getmapflag(sd->bl.m, MF_NOWARP) && pc_job_can_entermap((enum e_job)sd->status.class_, unit->bl.m, pc_get_group_level(sd))) pc_setpos(sd,map_id2index(unit->bl.m),unit->bl.x,unit->bl.y,CLR_TELEPORT); } skill_delunit(unit); } break; case UNT_REVERBERATION: case UNT_NETHERWORLD: if( unit->val1 <= 0 ) { // If it was deactivated. skill_delunit(unit); break; } clif_changetraplook(bl,UNT_USED_TRAPS); if (group->unit_id == UNT_REVERBERATION) map_foreachinrange(skill_trap_splash, bl, skill_get_splash(group->skill_id, group->skill_lv), group->bl_flag, bl, tick); group->limit = DIFF_TICK(tick,group->tick) + 1000; unit->limit = DIFF_TICK(tick,group->tick) + 1000; group->unit_id = UNT_USED_TRAPS; break; case UNT_FEINTBOMB: { struct block_list *src = map_id2bl(group->src_id); if (src) map_foreachinrange(skill_area_sub, &unit->bl, unit->range, BL_CHAR|BL_SKILL, src, group->skill_id, group->skill_lv, tick, BCT_ENEMY|SD_ANIMATION|5, skill_castend_damage_id); skill_delunit(unit); } break; case UNT_BANDING: { struct block_list *src = map_id2bl(group->src_id); status_change *sc; if( !src || (sc = status_get_sc(src)) == NULL || !sc->getSCE(SC_BANDING) ) { skill_delunit(unit); break; } // This unit isn't removed while SC_BANDING is active. group->limit = DIFF_TICK(tick+group->interval,group->tick); unit->limit = DIFF_TICK(tick+group->interval,group->tick); } break; case UNT_B_TRAP: { struct block_list* src; if (group->item_id && unit->val2 <= 0 && (src = map_id2bl(group->src_id)) && src->type == BL_PC) { struct item item_tmp; memset(&item_tmp, 0, sizeof(item_tmp)); item_tmp.nameid = group->item_id; item_tmp.identify = 1; map_addflooritem(&item_tmp, 1, bl->m, bl->x, bl->y, 0, 0, 0, 4, 0); } skill_delunit(unit); } break; default: if (group->val2 == 1 && (group->skill_id == WZ_METEOR || group->skill_id == SU_CN_METEOR || group->skill_id == SU_CN_METEOR2 || group->skill_id == AG_VIOLENT_QUAKE_ATK || group->skill_id == AG_ALL_BLOOM_ATK || group->skill_id == AG_ALL_BLOOM_ATK2)) { // Deal damage before expiration break; } skill_delunit(unit); break; } } else {// skill unit is still active switch( group->unit_id ) { case UNT_BLASTMINE: case UNT_SKIDTRAP: case UNT_LANDMINE: case UNT_SHOCKWAVE: case UNT_SANDMAN: case UNT_FLASHER: case UNT_CLAYMORETRAP: case UNT_FREEZINGTRAP: case UNT_TALKIEBOX: case UNT_ANKLESNARE: case UNT_B_TRAP: if( unit->val1 <= 0 ) { if( group->unit_id == UNT_ANKLESNARE && group->val2 > 0 ) skill_delunit(unit); else { clif_changetraplook(bl, group->unit_id == UNT_LANDMINE ? UNT_FIREPILLAR_ACTIVE : UNT_USED_TRAPS); group->limit = DIFF_TICK(tick, group->tick) + 1500; group->unit_id = UNT_USED_TRAPS; } } break; case UNT_REVERBERATION: case UNT_NETHERWORLD: if (unit->val1 <= 0) { clif_changetraplook(bl,UNT_USED_TRAPS); if (group->unit_id == UNT_REVERBERATION) map_foreachinrange(skill_trap_splash, bl, skill_get_splash(group->skill_id, group->skill_lv), group->bl_flag, bl, tick); group->limit = DIFF_TICK(tick,group->tick) + 1000; unit->limit = DIFF_TICK(tick,group->tick) + 1000; group->unit_id = UNT_USED_TRAPS; } break; case UNT_WALLOFTHORN: if (group->val3 < 0) { // Remove if attacked by fire element, turned to Fire Wall skill_delunitgroup(group); break; } if (unit->val1 <= 0 || unit->val2 <= 0) // Remove the unit only if no HP or hit limit is reached skill_delunit(unit); break; case UNT_SANCTUARY: if (group->val1 <= 0) { skill_delunitgroup(group); } break; default: if (group->skill_id == WZ_METEOR || group->skill_id == SU_CN_METEOR || group->skill_id == SU_CN_METEOR2 || group->skill_id == AG_VIOLENT_QUAKE_ATK || group->skill_id == AG_ALL_BLOOM_ATK || group->skill_id == AG_ALL_BLOOM_ATK2) { if (group->val2 == 0 && (DIFF_TICK(tick, group->tick) >= group->limit - group->interval || DIFF_TICK(tick, group->tick) >= unit->limit - group->interval)) { // Unit will expire the next interval, start dropping Meteor block_list *src = map_id2bl(group->src_id); if (src != nullptr) { if (group->skill_id == AG_VIOLENT_QUAKE_ATK || group->skill_id == AG_ALL_BLOOM_ATK || group->skill_id == AG_ALL_BLOOM_ATK2) clif_skill_poseffect(src, group->skill_id, -1, bl->x, bl->y, tick); // Don't yell a blank skill name. else clif_skill_poseffect(src, group->skill_id, group->skill_lv, bl->x, bl->y, tick); group->val2 = 1; } } // No damage until expiration return 0; } break; } } //Don't continue if unit or even group is expired and has been deleted. if( !group || !unit->alive ) return 0; dissonance = skill_dance_switch(unit, 0); if( unit->range >= 0 && group->interval != -1 ) { map_foreachinrange(skill_unit_timer_sub_onplace, bl, unit->range, group->bl_flag, bl,tick); if(unit->range == -1) //Unit disabled, but it should not be deleted yet. group->unit_id = UNT_USED_TRAPS; else if( group->unit_id == UNT_TATAMIGAESHI ) { unit->range = -1; //Disable processed cell. if (--group->val1 <= 0) { // number of live cells //All tiles were processed, disable skill. group->target_flag=BCT_NOONE; group->bl_flag= BL_NUL; } } else if (group->skill_id == WZ_METEOR || group->skill_id == SU_CN_METEOR || group->skill_id == SU_CN_METEOR2 || group->skill_id == AG_VIOLENT_QUAKE_ATK || group->skill_id == AG_ALL_BLOOM_ATK || group->skill_id == AG_ALL_BLOOM_ATK2 || ((group->skill_id == CR_GRANDCROSS || group->skill_id == NPC_GRANDDARKNESS) && unit->val1 <= 0)) { skill_delunit(unit); return 0; } } if( dissonance ) skill_dance_switch(unit, 1); return 0; } /*========================================== * Executes on all skill units every SKILLUNITTIMER_INTERVAL miliseconds. *------------------------------------------*/ TIMER_FUNC(skill_unit_timer){ map_freeblock_lock(); skillunit_db->foreach(skillunit_db, skill_unit_timer_sub, tick); map_freeblock_unlock(); return 0; } static int skill_unit_temp[20]; // temporary storage for tracking skill unit skill ids as players move in/out of them /*========================================== * flag : * 1 : store that skill_unit in array * 2 : clear that skill_unit * 4 : call_on_left *------------------------------------------*/ int skill_unit_move_sub(struct block_list* bl, va_list ap) { struct skill_unit* unit = (struct skill_unit *)bl; struct block_list* target = va_arg(ap,struct block_list*); t_tick tick = va_arg(ap,t_tick); int flag = va_arg(ap,int); bool dissonance; uint16 skill_id; int i; nullpo_ret(unit); nullpo_ret(target); if( !unit->alive || target->prev == NULL ) return 0; std::shared_ptr<s_skill_unit_group> group = unit->group; if (group == nullptr) return 0; if( flag&1 && ( group->skill_id == PF_SPIDERWEB || group->skill_id == GN_THORNS_TRAP ) ) return 0; // Fiberlock is never supposed to trigger on skill_unit_move. [Inkfish] dissonance = skill_dance_switch(unit, 0); //Necessary in case the group is deleted after calling on_place/on_out [Skotlex] skill_id = group->skill_id; if( group->interval != -1 && !skill_get_unit_flag(skill_id, UF_DUALMODE) && skill_id != BD_LULLABY ) //Lullaby is the exception, bugreport:411 { //Non-dualmode unit skills with a timer don't trigger when walking, so just return if( dissonance ) { skill_dance_switch(unit, 1); skill_unit_onleft(skill_unit_onout(unit,target,tick),target,tick); //we placed a dissonance, let's update } return 0; } //Target-type check. if( !(group->bl_flag&target->type && battle_check_target(&unit->bl,target,group->target_flag) > 0) ) { if( group->src_id == target->id && group->state.song_dance&0x2 ) { //Ensemble check to see if they went out/in of the area [Skotlex] if( flag&1 ) { if( flag&2 ) { //Clear this skill id. ARR_FIND( 0, ARRAYLENGTH(skill_unit_temp), i, skill_unit_temp[i] == skill_id ); if( i < ARRAYLENGTH(skill_unit_temp) ) skill_unit_temp[i] = 0; } } else { if( flag&2 ) { //Store this skill id. ARR_FIND( 0, ARRAYLENGTH(skill_unit_temp), i, skill_unit_temp[i] == 0 ); if( i < ARRAYLENGTH(skill_unit_temp) ) skill_unit_temp[i] = skill_id; else ShowError("skill_unit_move_sub: Reached limit of unit objects per cell! (skill_id: %hu)\n", skill_id ); } } if( flag&4 ) skill_unit_onleft(skill_id,target,tick); } if( dissonance ) skill_dance_switch(unit, 1); return 0; } else { if( flag&1 ) { int result = skill_unit_onplace(unit,target,tick); if( flag&2 && result ) { //Clear skill ids we have stored in onout. ARR_FIND( 0, ARRAYLENGTH(skill_unit_temp), i, skill_unit_temp[i] == result ); if( i < ARRAYLENGTH(skill_unit_temp) ) skill_unit_temp[i] = 0; } } else { int result = skill_unit_onout(unit,target,tick); if( flag&2 && result ) { //Store this unit id. ARR_FIND( 0, ARRAYLENGTH(skill_unit_temp), i, skill_unit_temp[i] == 0 ); if( i < ARRAYLENGTH(skill_unit_temp) ) skill_unit_temp[i] = skill_id; else ShowError("skill_unit_move_sub: Reached limit of unit objects per cell! (skill_id: %hu)\n", skill_id ); } } //TODO: Normally, this is dangerous since the unit and group could be freed //inside the onout/onplace functions. Currently it is safe because we know song/dance //cells do not get deleted within them. [Skotlex] if( dissonance ) skill_dance_switch(unit, 1); if( flag&4 ) skill_unit_onleft(skill_id,target,tick); return 1; } } /*========================================== * Invoked when a char has moved and unit cells must be invoked (onplace, onout, onleft) * Flag values: * flag&1: invoke skill_unit_onplace (otherwise invoke skill_unit_onout) * flag&2: this function is being invoked twice as a bl moves, store in memory the affected * units to figure out when they have left a group. * flag&4: Force a onleft event (triggered when the bl is killed, for example) *------------------------------------------*/ int skill_unit_move(struct block_list *bl, t_tick tick, int flag) { nullpo_ret(bl); if( bl->prev == NULL ) return 0; if( flag&2 && !(flag&1) ) //Onout, clear data memset(skill_unit_temp, 0, sizeof(skill_unit_temp)); map_foreachincell(skill_unit_move_sub,bl->m,bl->x,bl->y,BL_SKILL,bl,tick,flag); if( flag&2 && flag&1 ) { //Onplace, check any skill units you have left. int i; for( i = 0; i < ARRAYLENGTH(skill_unit_temp); i++ ) if( skill_unit_temp[i] ) skill_unit_onleft(skill_unit_temp[i], bl, tick); } return 0; } /*========================================== * Moves skill unit to map m with coordinates x & y (example when knocked back) * @param bl Skill unit * @param m Map * @param dx * @param dy *------------------------------------------*/ void skill_unit_move_unit(struct block_list *bl, int dx, int dy) { t_tick tick = gettick(); struct skill_unit *su; if (bl->type != BL_SKILL) return; if (!(su = (struct skill_unit *)bl)) return; if (!su->alive) return; if (su->group && skill_get_unit_flag(su->group->skill_id, UF_ENSEMBLE)) return; //Ensembles may not be moved around. if (!bl->prev) { bl->x = dx; bl->y = dy; return; } map_moveblock(bl, dx, dy, tick); map_foreachincell(skill_unit_effect,bl->m,bl->x,bl->y,su->group->bl_flag,bl,tick,1); skill_getareachar_skillunit_visibilty(su, AREA); return; } /** * Moves skill unit group to map m with coordinates x & y (example when knocked back) * @param group Skill Group * @param m Map * @param dx * @param dy */ void skill_unit_move_unit_group(std::shared_ptr<s_skill_unit_group> group, int16 m, int16 dx, int16 dy) { int i, j; t_tick tick = gettick(); int *m_flag; struct skill_unit *unit1; struct skill_unit *unit2; if (group == NULL) return; if (group->unit_count <= 0) return; if (group->unit == NULL) return; if (skill_get_unit_flag(group->skill_id, UF_ENSEMBLE)) return; //Ensembles may not be moved around. m_flag = (int *) aCalloc(group->unit_count, sizeof(int)); // m_flag // 0: Neither of the following (skill_unit_onplace & skill_unit_onout are needed) // 1: Unit will move to a slot that had another unit of the same group (skill_unit_onplace not needed) // 2: Another unit from same group will end up positioned on this unit (skill_unit_onout not needed) // 3: Both 1+2. for(i = 0; i < group->unit_count; i++) { unit1 =& group->unit[i]; if (!unit1->alive || unit1->bl.m != m) continue; for(j = 0; j < group->unit_count; j++) { unit2 = &group->unit[j]; if (!unit2->alive) continue; if (unit1->bl.x+dx == unit2->bl.x && unit1->bl.y+dy == unit2->bl.y) m_flag[i] |= 0x1; if (unit1->bl.x-dx == unit2->bl.x && unit1->bl.y-dy == unit2->bl.y) m_flag[i] |= 0x2; } } j = 0; for (i = 0; i < group->unit_count; i++) { unit1 = &group->unit[i]; if (!unit1->alive) continue; if (!(m_flag[i]&0x2)) { if (group->state.song_dance&0x1) //Cancel dissonance effect. skill_dance_overlap(unit1, 0); map_foreachincell(skill_unit_effect,unit1->bl.m,unit1->bl.x,unit1->bl.y,group->bl_flag,&unit1->bl,tick,4); } //Move Cell using "smart" criteria (avoid useless moving around) switch(m_flag[i]) { case 0: //Cell moves independently, safely move it. // (^~_~^) LGP Start clif_skill_delunit(unit1); // (^~_~^) LGP End map_foreachinmovearea(clif_outsight, &unit1->bl, AREA_SIZE, dx, dy, BL_PC, &unit1->bl); map_moveblock(&unit1->bl, unit1->bl.x+dx, unit1->bl.y+dy, tick); break; case 1: //Cell moves unto another cell, look for a replacement cell that won't collide //and has no cell moving into it (flag == 2) for(; j < group->unit_count; j++) { int dx2, dy2; if(m_flag[j] != 2 || !group->unit[j].alive) continue; //Move to where this cell would had moved. // (^~_~^) LGP Start clif_skill_delunit(unit1); // (^~_~^) LGP End unit2 = &group->unit[j]; dx2 = unit2->bl.x + dx - unit1->bl.x; dy2 = unit2->bl.y + dy - unit1->bl.y; map_foreachinmovearea(clif_outsight, &unit1->bl, AREA_SIZE, dx2, dy2, BL_PC, &unit1->bl); map_moveblock(&unit1->bl, unit2->bl.x+dx, unit2->bl.y+dy, tick); j++; //Skip this cell as we have used it. break; } break; case 2: case 3: break; //Don't move the cell as a cell will end on this tile anyway. } if (!(m_flag[i]&0x2)) { //We only moved the cell in 0-1 if (group->state.song_dance&0x1) //Check for dissonance effect. skill_dance_overlap(unit1, 1); skill_getareachar_skillunit_visibilty(unit1, AREA); map_foreachincell(skill_unit_effect,unit1->bl.m,unit1->bl.x,unit1->bl.y,group->bl_flag,&unit1->bl,tick,1); } } aFree(m_flag); } /** * Checking product requirement in player's inventory. * Checking if player has the item or not, the amount, and the weight limit. * @param sd Player * @param nameid Product requested * @param trigger Trigger criteria to match will 'ItemLv' * @param qty Amount of item will be created * @return 0 If failed or Index+1 of item found on skill_produce_db[] */ short skill_can_produce_mix(map_session_data *sd, t_itemid nameid, int trigger, int qty) { nullpo_ret(sd); if (!item_db.exists(nameid)) return 0; short i, j; for (i = 0; i < MAX_SKILL_PRODUCE_DB; i++) { if (skill_produce_db[i].nameid == nameid) { if ((j = skill_produce_db[i].req_skill) > 0 && pc_checkskill(sd,j) < skill_produce_db[i].req_skill_lv) continue; // must iterate again to check other skills that produce it. [malufett] if (j > 0 && sd->menuskill_id > 0 && sd->menuskill_id != j) continue; // special case break; } } if (nameid == ITEMID_HOMUNCULUS_SUPPLEMENT) { // Temporary check since the produce_db specifically wants the Pharmacy skill to use if (pc_checkskill(sd, AM_BIOETHICS) == 0) return 0; } if (i >= MAX_SKILL_PRODUCE_DB) return 0; // Cannot carry the produced stuff if (pc_checkadditem(sd, nameid, qty) == CHKADDITEM_OVERAMOUNT) return 0; // Matching the requested produce list if (trigger >= 0) { if (trigger > 20) { // Non-weapon, non-food item (itemlv must match) if (skill_produce_db[i].itemlv != trigger) return 0; } else if (trigger > 10) { // Food (any item level between 10 and 20 will do) if (skill_produce_db[i].itemlv <= 10 || skill_produce_db[i].itemlv > 20) return 0; } else { // Weapon (itemlv must be higher or equal) if (skill_produce_db[i].itemlv > trigger) return 0; } } // Check on player's inventory for (j = 0; j < MAX_PRODUCE_RESOURCE; j++) { t_itemid nameid_produce; if (!(nameid_produce = skill_produce_db[i].mat_id[j])) continue; if (skill_produce_db[i].mat_amount[j] == 0) { if (pc_search_inventory(sd,nameid_produce) < 0) return 0; } else { unsigned short idx, amt; for (idx = 0, amt = 0; idx < MAX_INVENTORY; idx++) if (sd->inventory.u.items_inventory[idx].nameid == nameid_produce) amt += sd->inventory.u.items_inventory[idx].amount; if (amt < qty * skill_produce_db[i].mat_amount[j]) return 0; } } return i + 1; } /** * Attempt to produce an item * @param sd Player * @param skill_id Skill used * @param nameid Requested product * @param slot1 * @param slot2 * @param slot3 * @param qty Amount of requested item * @param produce_idx Index of produce entry in skill_produce_db[]. (Optional. Assumed the requirements are complete, checked somewhere) * @return True is success, False if failed */ bool skill_produce_mix(map_session_data *sd, uint16 skill_id, t_itemid nameid, int slot1, int slot2, int slot3, int qty, short produce_idx) { int slot[3]; int i, sc, ele, idx, equip, wlv, make_per = 0, flag = 0, skill_lv = 0; int num = -1; // exclude the recipe struct status_data *status; nullpo_ret(sd); status = status_get_status_data(&sd->bl); if( sd->skill_id_old == skill_id ) skill_lv = sd->skill_lv_old; if (produce_idx == -1) { if( !(idx = skill_can_produce_mix(sd,nameid,-1, qty)) ) return false; idx--; } else idx = produce_idx; if (qty < 1) qty = 1; if (!skill_id) //A skill can be specified for some override cases. skill_id = skill_produce_db[idx].req_skill; if( skill_id == GC_RESEARCHNEWPOISON ) skill_id = GC_CREATENEWPOISON; slot[0] = slot1; slot[1] = slot2; slot[2] = slot3; for (i = 0, sc = 0, ele = 0; i < 3; i++) { //Note that qty should always be one if you are using these! short j; if (slot[i] <= 0) continue; j = pc_search_inventory(sd,slot[i]); if (j < 0) continue; if (slot[i] == ITEMID_STAR_CRUMB) { pc_delitem(sd,j,1,1,0,LOG_TYPE_PRODUCE); sc++; } if (slot[i] >= ITEMID_FLAME_HEART && slot[i] <= ITEMID_GREAT_NATURE && ele == 0) { static const int ele_table[4] = { ELE_FIRE, ELE_WATER, ELE_WIND, ELE_EARTH }; pc_delitem(sd,j,1,1,0,LOG_TYPE_PRODUCE); ele = ele_table[slot[i]-ITEMID_FLAME_HEART]; } } for (i = 0; i < MAX_PRODUCE_RESOURCE; i++) { short x, j; t_itemid id = skill_produce_db[idx].mat_id[i]; if (!item_db.exists(id)) continue; num++; x = (skill_id == RK_RUNEMASTERY ? 1 : qty) * skill_produce_db[idx].mat_amount[i]; do { int y = 0; j = pc_search_inventory(sd,id); if (j >= 0) { y = sd->inventory.u.items_inventory[j].amount; if (y > x) y = x; pc_delitem(sd,j,y,0,0,LOG_TYPE_PRODUCE); } else { ShowError("skill_produce_mix: material item error\n"); return false; } x -= y; } while( j >= 0 && x > 0 ); } if ((equip = (itemdb_isequip(nameid) && skill_id != GN_CHANGEMATERIAL && skill_id != GN_MAKEBOMB)) && itemdb_type(nameid) == IT_WEAPON ) wlv = itemdb_wlv(nameid); else wlv = 0; if (!equip) { switch (skill_id) { case BS_IRON: case BS_STEEL: case BS_ENCHANTEDSTONE: // Ores & Metals Refining - skill bonuses are straight from kRO website [DracoRPG] i = pc_checkskill(sd,skill_id); make_per = sd->status.job_level*20 + status->dex*10 + status->luk*10; //Base chance switch (nameid) { case ITEMID_IRON: make_per += 4000+i*500; // Temper Iron bonus: +26/+32/+38/+44/+50 break; case ITEMID_STEEL: make_per += 3000+i*500; // Temper Steel bonus: +35/+40/+45/+50/+55 break; case ITEMID_STAR_CRUMB: make_per = 100000; // Star Crumbs are 100% success crafting rate? (made 1000% so it succeeds even after penalties) [Skotlex] break; default: // Enchanted Stones make_per += 1000+i*500; // Enchanted stone Craft bonus: +15/+20/+25/+30/+35 break; } break; case ASC_CDP: make_per = (2000 + 40*status->dex + 20*status->luk); break; case AL_HOLYWATER: case AB_ANCILLA: make_per = 100000; //100% success break; case AM_PHARMACY: // Potion Preparation - reviewed with the help of various Ragnainfo sources [DracoRPG] case AM_TWILIGHT1: case AM_TWILIGHT2: case AM_TWILIGHT3: make_per = pc_checkskill(sd,AM_LEARNINGPOTION)*50 + pc_checkskill(sd,AM_PHARMACY)*300 + sd->status.job_level*20 + (status->int_/2)*10 + status->dex*10+status->luk*10; if (hom_is_active(sd->hd)) {//Player got a homun int skill; if ((skill = hom_checkskill(sd->hd,HVAN_INSTRUCT)) > 0) //His homun is a vanil with instruction change make_per += skill*100; //+1% bonus per level } switch(nameid){ case ITEMID_RED_POTION: case ITEMID_YELLOW_POTION: case ITEMID_WHITE_POTION: make_per += (1+rnd()%100)*10 + 2000; break; case ITEMID_ALCOHOL: make_per += (1+rnd()%100)*10 + 1000; break; case ITEMID_FIRE_BOTTLE: case ITEMID_ACID_BOTTLE: case ITEMID_MAN_EATER_BOTTLE: case ITEMID_MINI_BOTTLE: make_per += (1+rnd()%100)*10; break; case ITEMID_YELLOW_SLIM_POTION: make_per -= (1+rnd()%50)*10; break; case ITEMID_WHITE_SLIM_POTION: case ITEMID_COATING_BOTTLE: make_per -= (1+rnd()%100)*10; break; //Common items, receive no bonus or penalty, listed just because they are commonly produced case ITEMID_BLUE_POTION: case ITEMID_RED_SLIM_POTION: case ITEMID_ANODYNE: case ITEMID_ALOEBERA: default: break; } if (battle_config.pp_rate != 100) make_per = make_per * battle_config.pp_rate / 100; break; case SA_CREATECON: // Elemental Converter Creation make_per = 100000; // should be 100% success rate break; case RK_RUNEMASTERY: { int A = 100 * (30 + 2 * pc_checkskill(sd, skill_id)); int B = 100 * status->dex / 30 + 10 * (status->luk + sd->status.job_level); int C = 100 * cap_value(sd->itemid,0,100); //itemid depend on makerune() int D = 0; switch (nameid) { //rune rank it_diff 9 craftable rune case ITEMID_BERKANA: D = -2000; break; //Rank S case ITEMID_NAUTHIZ: case ITEMID_URUZ: D = -1500; break; //Rank A case ITEMID_ISA: case ITEMID_WYRD: D = -1000; break; //Rank B case ITEMID_RAIDO: case ITEMID_THURISAZ: case ITEMID_HAGALAZ: case ITEMID_OTHILA: D = -500; break; //Rank C default: D = -1500; break; //not specified =-15% } make_per = A + B + C + D; uint8 runemastery_skill_lv = pc_checkskill(sd,skill_id); if (runemastery_skill_lv > 9) qty = 2 + rnd() % 5; // 2~6 else if (runemastery_skill_lv > 4) qty = 2 + rnd() % 3; // 2~4 else qty = 2; } break; case GC_CREATENEWPOISON: make_per = 3000 + 500 * pc_checkskill(sd,GC_RESEARCHNEWPOISON); qty = 1+rnd()%pc_checkskill(sd,GC_RESEARCHNEWPOISON); break; case GN_CHANGEMATERIAL: for (i = 0; i < MAX_SKILL_CHANGEMATERIAL_DB; i++) { if (skill_changematerial_db[i].nameid == nameid) { make_per = skill_changematerial_db[i].rate * 10; break; } } break; case GN_S_PHARMACY: { int difficulty = (620 - 20 * skill_lv); // (620 - 20 * Skill Level) const int production_count[] = { 7, 8, 8, 9, 9, 10, 10, 11, 11, 12 }; switch (nameid) { // Item difficulty factor case ITEMID_HP_INCREASE_POTION_SMALL: case ITEMID_SP_INCREASE_POTION_SMALL: case ITEMID_CONCENTRATED_WHITE_POTION_Z: difficulty += 10; break; case ITEMID_BOMB_MUSHROOM_SPORE: case ITEMID_SP_INCREASE_POTION_MEDIUM: difficulty += 15; break; case ITEMID_HP_INCREASE_POTION_MEDIUM: case ITEMID_SP_INCREASE_POTION_LARGE: case ITEMID_VITATA500: difficulty += 20; break; case ITEMID_SEED_OF_HORNY_PLANT: case ITEMID_BLOODSUCK_PLANT_SEED: case ITEMID_CONCENTRATED_CEROMAIN_SOUP: difficulty += 30; break; case ITEMID_HP_INCREASE_POTION_LARGE: case ITEMID_CURE_FREE: difficulty += 40; break; } make_per = status->int_ + status->dex / 2 + status->luk + sd->status.job_level + (30 + rnd() % 120 + 1) + // Caster's INT + (Caster's DEX / 2) + Caster's LUK + Caster's Job Level + Random number between (30 ~ 150) + sd->status.base_level + 5 * (pc_checkskill(sd, AM_LEARNINGPOTION) - 20) + pc_checkskill(sd, CR_FULLPROTECTION) * (6 + rnd() % 4 + 1); // Caster's Base Level + (5 x (Potion Research Skill Level - 20)) + (Full Chemical Protection Skill Level x Random number between (6 ~ 10)) make_per -= difficulty; qty = production_count[skill_lv - 1]; // Determine quantity from difficulty if (make_per < 1) qty -= 6; else if (make_per < 100) qty -= 5; else if (make_per < 300) qty -= 4; else if (make_per < 400) qty -= 3; make_per = 100000; // Adjust success back to 100% for crafting } break; case GN_MAKEBOMB: case GN_MIX_COOKING: { int difficulty = 30 + rnd() % 120 + 1; // Random number between (30 ~ 150) switch (nameid) { // Item difficulty factor // GN_MAKEBOMB case ITEMID_APPLE_BOMB: difficulty += 5; break; case ITEMID_COCONUT_BOMB: case ITEMID_MELON_BOMB: difficulty += 10; break; case ITEMID_PINEAPPLE_BOMB: difficulty += 15; break; case ITEMID_BANANA_BOMB: difficulty += 20; break; // GN_MIX_COOKING case ITEMID_SAVAGE_FULL_ROAST: case ITEMID_COCKTAIL_WARG_BLOOD: case ITEMID_MINOR_STEW: case ITEMID_SIROMA_ICED_TEA: case ITEMID_DROSERA_HERB_SALAD: case ITEMID_PETITE_TAIL_NOODLES: difficulty += 15; break; } make_per = sd->status.job_level / 4 + status->luk / 2 + status->dex / 3; // (Caster's Job Level / 4) + (Caster's LUK / 2) + (Caster's DEX / 3) if (skill_lv > 1) { make_per -= difficulty; // Determine quantity from difficulty if (make_per >= 30) qty = 10 + rnd() % 2; else if (make_per >= 10) qty = 10; else if (make_per >= -10) qty = 8; else if (make_per >= -30) qty = 5; else qty = 0; } else { if (make_per < difficulty) qty = 0; } make_per = 100000; // Adjust success back to 100% for crafting } break; case MT_M_MACHINE: case BO_BIONIC_PHARMACY: // Difficulty formula unknown. Making it 100% success for now. [Rytech] if (skill_id == MT_M_MACHINE) qty = 7 + skill_lv; else // BO_BIONIC_PHARMACY qty = 10 + skill_lv; make_per = 100000; break; default: if (sd->menuskill_id == AM_PHARMACY && sd->menuskill_val > 10 && sd->menuskill_val <= 20) { //Assume Cooking Dish if (sd->menuskill_val >= 15) //Legendary Cooking Set. make_per = 10000; //100% Success else make_per = 1200 * (sd->menuskill_val - 10) + 20 * (sd->status.base_level + 1) + 20 * (status->dex + 1) + 100 * (rnd()%(30+5*(sd->cook_mastery/400) - (6+sd->cook_mastery/80)) + (6+sd->cook_mastery/80)) - 400 * (skill_produce_db[idx].itemlv - 11 + 1) - 10 * (100 - status->luk + 1) - 500 * (num - 1) - 100 * (rnd()%4 + 1); break; } make_per = 5000; break; } } else { // Weapon Forging - skill bonuses are straight from kRO website, other things from a jRO calculator [DracoRPG] make_per = 5000 + ((sd->class_&JOBL_THIRD)?1400:sd->status.job_level*20) + status->dex*10 + status->luk*10; // Base make_per += pc_checkskill(sd,skill_id)*500; // Smithing skills bonus: +5/+10/+15 // Weaponry Research bonus: +1/+2/+3/+4/+5/+6/+7/+8/+9/+10 make_per += pc_checkskill(sd,BS_WEAPONRESEARCH)*100; // Oridecon Research bonus (custom): +1/+2/+3/+4/+5 if( wlv >= 3 ){ make_per += pc_checkskill(sd, BS_ORIDEOCON) * 100; } // Element Stone: -20% if( ele ){ make_per -= 2000; } // Star Crumb: -15% each make_per -= sc * 1500; // Weapon level malus: -0/-10/-20/-30 if( wlv > 1 ){ make_per -= ( wlv * 1000 ); } if (pc_search_inventory(sd,ITEMID_EMPERIUM_ANVIL) > -1) make_per+= 1000; // Emperium Anvil: +10 else if (pc_search_inventory(sd,ITEMID_GOLDEN_ANVIL) > -1) make_per+= 500; // Golden Anvil: +5 else if (pc_search_inventory(sd,ITEMID_ORIDECON_ANVIL) > -1) make_per+= 300; // Oridecon Anvil: +3 else if (pc_search_inventory(sd,ITEMID_ANVIL) > -1) make_per+= 0; // Anvil: +0? if (battle_config.wp_rate != 100) make_per = make_per * battle_config.wp_rate / 100; } if (sd->class_&JOBL_BABY) //if it's a Baby Class make_per = (make_per * 50) / 100; //Baby penalty is 50% (bugreport:4847) if (make_per < 1) make_per = 1; if (qty > 1 || rnd()%10000 < make_per){ //Success, or crafting multiple items. struct item tmp_item; memset(&tmp_item,0,sizeof(tmp_item)); tmp_item.nameid = nameid; tmp_item.amount = 1; tmp_item.identify = 1; if (equip) { tmp_item.card[0] = CARD0_FORGE; tmp_item.card[1] = ((sc*5)<<8)+ele; tmp_item.card[2] = GetWord(sd->status.char_id,0); // CharId tmp_item.card[3] = GetWord(sd->status.char_id,1); } else { //Flag is only used on the end, so it can be used here. [Skotlex] switch (skill_id) { case BS_DAGGER: case BS_SWORD: case BS_TWOHANDSWORD: case BS_AXE: case BS_MACE: case BS_KNUCKLE: case BS_SPEAR: flag = battle_config.produce_item_name_input&0x1; break; case AM_PHARMACY: case AM_TWILIGHT1: case AM_TWILIGHT2: case AM_TWILIGHT3: flag = battle_config.produce_item_name_input&0x2; break; case AL_HOLYWATER: case AB_ANCILLA: flag = battle_config.produce_item_name_input&0x8; break; case ASC_CDP: flag = battle_config.produce_item_name_input&0x10; break; default: flag = battle_config.produce_item_name_input&0x80; break; } if (flag) { tmp_item.card[0] = CARD0_CREATE; tmp_item.card[1] = 0; tmp_item.card[2] = GetWord(sd->status.char_id,0); // CharId tmp_item.card[3] = GetWord(sd->status.char_id,1); } } // if(log_config.produce > 0) // log_produce(sd,nameid,slot1,slot2,slot3,1); //TODO update PICKLOG if (equip) { clif_produceeffect(sd,0,nameid); clif_misceffect(&sd->bl,3); if (wlv >= 3 && ((ele? 1 : 0) + sc) >= 3) // Fame point system [DracoRPG] pc_addfame(*sd, battle_config.fame_forge); // Success to forge a lv3 weapon with 3 additional ingredients = +10 fame point } else { int fame = 0; tmp_item.amount = 0; for (i = 0; i < qty; i++) { //Apply quantity modifiers. if ((skill_id == GN_MIX_COOKING || skill_id == GN_MAKEBOMB || skill_id == GN_S_PHARMACY || skill_id == MT_M_MACHINE || skill_id == BO_BIONIC_PHARMACY) && make_per > 1) { tmp_item.amount = qty; break; } if (qty == 1 || rnd()%10000 < make_per) { //Success tmp_item.amount++; if (nameid < ITEMID_RED_SLIM_POTION || nameid > ITEMID_WHITE_SLIM_POTION) continue; if (skill_id != AM_PHARMACY && skill_id != AM_TWILIGHT1 && skill_id != AM_TWILIGHT2 && skill_id != AM_TWILIGHT3) continue; //Add fame as needed. switch(++sd->potion_success_counter) { case 3: fame += battle_config.fame_pharmacy_3; // Success to prepare 3 Condensed Potions in a row break; case 5: fame += battle_config.fame_pharmacy_5; // Success to prepare 5 Condensed Potions in a row break; case 7: fame += battle_config.fame_pharmacy_7; // Success to prepare 7 Condensed Potions in a row break; case 10: fame += battle_config.fame_pharmacy_10; // Success to prepare 10 Condensed Potions in a row sd->potion_success_counter = 0; break; } } else //Failure sd->potion_success_counter = 0; } if (fame) pc_addfame(*sd, fame); //Visual effects and the like. switch (skill_id) { case AM_PHARMACY: case AM_TWILIGHT1: case AM_TWILIGHT2: case AM_TWILIGHT3: case ASC_CDP: case GC_CREATENEWPOISON: clif_produceeffect(sd,2,nameid); clif_misceffect(&sd->bl,5); break; case BS_IRON: case BS_STEEL: case BS_ENCHANTEDSTONE: clif_produceeffect(sd,0,nameid); clif_misceffect(&sd->bl,3); break; default: //Those that don't require a skill? if (skill_produce_db[idx].itemlv > 10 && skill_produce_db[idx].itemlv <= 20) { //Cooking items. clif_specialeffect(&sd->bl, EF_COOKING_OK, AREA); pc_setparam(sd, SP_COOKMASTERY, sd->cook_mastery + ( 1 << ( (skill_produce_db[idx].itemlv - 11) / 2 ) ) * 5); } break; } } if (skill_id == GN_CHANGEMATERIAL && tmp_item.amount) { //Success int j, k = 0, l; bool isStackable = itemdb_isstackable(tmp_item.nameid); for (i = 0; i < MAX_SKILL_CHANGEMATERIAL_DB; i++) { if (skill_changematerial_db[i].nameid == nameid){ for (j = 0; j < MAX_SKILL_CHANGEMATERIAL_SET; j++){ if (rnd()%1000 < skill_changematerial_db[i].qty_rate[j]){ uint16 total_qty = qty * skill_changematerial_db[i].qty[j]; tmp_item.amount = (isStackable ? total_qty : 1); for (l = 0; l < total_qty; l += tmp_item.amount) { if ((flag = pc_additem(sd,&tmp_item,tmp_item.amount,LOG_TYPE_PRODUCE))) { clif_additem(sd,0,0,flag); if( battle_config.skill_drop_items_full ){ map_addflooritem(&tmp_item,tmp_item.amount,sd->bl.m,sd->bl.x,sd->bl.y,0,0,0,0,0); } } } k++; } } break; } } if (k) { clif_produceeffect(sd,6,nameid); clif_misceffect(&sd->bl,5); clif_msg_skill(sd,skill_id,ITEM_PRODUCE_SUCCESS); return true; } } else if (tmp_item.amount) { //Success if ((flag = pc_additem(sd,&tmp_item,tmp_item.amount,LOG_TYPE_PRODUCE))) { clif_additem(sd,0,0,flag); if( battle_config.skill_drop_items_full ){ map_addflooritem(&tmp_item,tmp_item.amount,sd->bl.m,sd->bl.x,sd->bl.y,0,0,0,0,0); } } switch (skill_id) { case RK_RUNEMASTERY: clif_produceeffect(sd, 4, nameid); clif_misceffect(&sd->bl, 5); break; case GN_MIX_COOKING: case GN_MAKEBOMB: case GN_S_PHARMACY: clif_produceeffect(sd, 6, nameid); clif_misceffect(&sd->bl, 5); clif_msg_skill(sd, skill_id, ITEM_PRODUCE_SUCCESS); break; case MT_M_MACHINE: clif_produceeffect(sd, 0, nameid); clif_misceffect(&sd->bl, 3); clif_msg_skill(sd, skill_id, ITEM_PRODUCE_SUCCESS); break; case BO_BIONIC_PHARMACY: clif_produceeffect(sd, 2, nameid); clif_misceffect(&sd->bl, 5); clif_msg_skill(sd, skill_id, ITEM_PRODUCE_SUCCESS); break; } return true; } } //Failure // if(log_config.produce) // log_produce(sd,nameid,slot1,slot2,slot3,0); //TODO update PICKLOG if (equip) { clif_produceeffect(sd,1,nameid); clif_misceffect(&sd->bl,2); } else { switch (skill_id) { case ASC_CDP: //25% Damage yourself, and display same effect as failed potion. status_percent_damage(NULL, &sd->bl, -25, 0, true); case AM_PHARMACY: case AM_TWILIGHT1: case AM_TWILIGHT2: case AM_TWILIGHT3: case GC_CREATENEWPOISON: clif_produceeffect(sd,3,nameid); clif_misceffect(&sd->bl,6); sd->potion_success_counter = 0; // Fame point system [DracoRPG] break; case BS_IRON: case BS_STEEL: case BS_ENCHANTEDSTONE: clif_produceeffect(sd,1,nameid); clif_misceffect(&sd->bl,2); break; case RK_RUNEMASTERY: clif_produceeffect(sd,5,nameid); clif_misceffect(&sd->bl,6); break; case GN_MIX_COOKING: if (qty == 0) { item tmp_item; const t_itemid compensation[5] = { ITEMID_BLACK_LUMP, ITEMID_BLACK_HARD_LUMP, ITEMID_VERY_HARD_LUMP, ITEMID_BLACK_MASS, ITEMID_MYSTERIOUS_POWDER }; int rate = rnd() % 1000 + 1; memset(&tmp_item, 0, sizeof(tmp_item)); if (rate < 500) i = 0; else if (rate < 750) i = 1; else if (rate < 850) i = 2; else if (rate < 950) i = 3; else i = 4; tmp_item.nameid = compensation[i]; tmp_item.amount = (skill_lv == 1) ? 1 : 5 + rand() % 5; tmp_item.identify = 1; if ((flag = pc_additem(sd,&tmp_item,tmp_item.amount,LOG_TYPE_PRODUCE))) { clif_additem(sd,0,0,flag); if( battle_config.skill_drop_items_full ){ map_addflooritem(&tmp_item,tmp_item.amount,sd->bl.m,sd->bl.x,sd->bl.y,0,0,0,0,0); } } clif_produceeffect(sd,7,nameid); clif_misceffect(&sd->bl,6); clif_msg_skill(sd,skill_id,ITEM_PRODUCE_FAIL); } break; case GN_MAKEBOMB: case GN_S_PHARMACY: case GN_CHANGEMATERIAL: clif_produceeffect(sd,7,nameid); clif_misceffect(&sd->bl,6); clif_msg_skill(sd,skill_id,ITEM_PRODUCE_FAIL); break; case MT_M_MACHINE: clif_produceeffect(sd, 1, nameid); clif_misceffect(&sd->bl, 2); clif_msg_skill(sd, skill_id, ITEM_PRODUCE_FAIL); break; case BO_BIONIC_PHARMACY: clif_produceeffect(sd, 3, nameid); clif_misceffect(&sd->bl, 6); clif_msg_skill(sd, skill_id, ITEM_PRODUCE_FAIL); break; default: if (skill_produce_db[idx].itemlv > 10 && skill_produce_db[idx].itemlv <= 20 ) { //Cooking items. clif_specialeffect(&sd->bl, EF_COOKING_FAIL, AREA); pc_setparam(sd, SP_COOKMASTERY, sd->cook_mastery - ( 1 << ((skill_produce_db[idx].itemlv - 11) / 2) ) - ( ( ( 1 << ((skill_produce_db[idx].itemlv - 11) / 2) ) >> 1 ) * 3 )); } break; } } return false; } /** * Attempt to create arrow by specified material * @param sd Player * @param nameid Item ID of material * @return True if created, False is failed */ bool skill_arrow_create(map_session_data *sd, t_itemid nameid) { nullpo_ret(sd); if (!nameid || !item_db.exists(nameid) || skill_arrow_db.empty()) return false; std::shared_ptr<s_skill_arrow_db> arrow = nullptr; for (const auto &it : skill_arrow_db) { if (nameid == it.second->nameid) { arrow = it.second; break; } } short j; if (arrow == nullptr || (j = pc_search_inventory(sd,nameid)) < 0 || arrow->created.empty()) return false; pc_delitem(sd,j,1,0,0,LOG_TYPE_PRODUCE); for (const auto &it : arrow->created) { char flag = 0; if (it.first == 0 || !item_db.exists(it.first) || it.second == 0) continue; struct item tmp_item = { 0 }; tmp_item.identify = 1; tmp_item.nameid = it.first; tmp_item.amount = it.second; if (battle_config.produce_item_name_input&0x4) { tmp_item.card[0] = CARD0_CREATE; tmp_item.card[1] = 0; tmp_item.card[2] = GetWord(sd->status.char_id,0); // CharId tmp_item.card[3] = GetWord(sd->status.char_id,1); } if ((flag = pc_additem(sd,&tmp_item,tmp_item.amount,LOG_TYPE_PRODUCE))) { clif_additem(sd,0,0,flag); map_addflooritem(&tmp_item,tmp_item.amount,sd->bl.m,sd->bl.x,sd->bl.y,0,0,0,0,0); } } return true; } /** * Enchant weapon with poison * @param sd Player * @nameid Item ID of poison type */ int skill_poisoningweapon(map_session_data *sd, t_itemid nameid) { nullpo_ret(sd); if( !nameid || pc_delitem(sd,pc_search_inventory(sd,nameid),1,0,0,LOG_TYPE_CONSUME) ) { clif_skill_fail(sd,GC_POISONINGWEAPON,USESKILL_FAIL_LEVEL,0); return 0; } sc_type type; int chance; //uint16 msg = 1443; //Official is using msgstringtable.txt char output[CHAT_SIZE_MAX]; const char *msg; switch( nameid ) { case ITEMID_PARALYSE: type = SC_PARALYSE; /*msg = 1444;*/ msg = "Paralyze"; break; case ITEMID_PYREXIA: type = SC_PYREXIA; /*msg = 1448;*/ msg = "Pyrexia"; break; case ITEMID_DEATHHURT: type = SC_DEATHHURT; /*msg = 1447;*/ msg = "Deathhurt"; break; case ITEMID_LEECHESEND: type = SC_LEECHESEND; /*msg = 1450;*/ msg = "Leech End"; break; case ITEMID_VENOMBLEED: type = SC_VENOMBLEED; /*msg = 1445;*/ msg = "Venom Bleed"; break; case ITEMID_TOXIN: type = SC_TOXIN; /*msg = 1443;*/ msg = "Toxin"; break; case ITEMID_MAGICMUSHROOM: type = SC_MAGICMUSHROOM; /*msg = 1446;*/ msg = "Magic Mushroom"; break; case ITEMID_OBLIVIONCURSE: type = SC_OBLIVIONCURSE; /*msg = 1449;*/ msg = "Oblivion Curse"; break; default: clif_skill_fail(sd,GC_POISONINGWEAPON,USESKILL_FAIL_LEVEL,0); return 0; } status_change_end(&sd->bl, SC_POISONINGWEAPON); // End the status so a new poison can be applied (if changed) chance = 2 + 2 * sd->menuskill_val; // 2 + 2 * skill_lv sc_start4(&sd->bl,&sd->bl, SC_POISONINGWEAPON, 100, pc_checkskill(sd, GC_RESEARCHNEWPOISON), //in Aegis it store the level of GC_RESEARCHNEWPOISON in val1 type, chance, 0, skill_get_time(GC_POISONINGWEAPON, sd->menuskill_val)); status_change_start(&sd->bl, &sd->bl, type, 10000, sd->menuskill_val, 0, 0, 0, skill_get_time(GC_POISONINGWEAPON, sd->menuskill_val), SCSTART_NOAVOID | SCSTART_NOICON); // Apply bonus to caster sprintf(output, msg_txt(sd,721), msg); clif_messagecolor(&sd->bl,color_table[COLOR_WHITE],output,false,SELF); /*#if PACKETVER >= 20110208 //! TODO: Check the correct PACKVETVER clif_msg(sd,msg); #endif*/ return 0; } /** * Toggles the effect of Mystical Amplifcation (Pre-renewal). * @param bl: Object * @param skill_id: Skill invoking to determine if Magic type */ void skill_toggle_magicpower(struct block_list *bl, uint16 skill_id) { status_change *sc = status_get_sc(bl); // non-offensive and non-magic skills do not affect the status if (skill_get_nk(skill_id, NK_NODAMAGE) || !(skill_get_type(skill_id)&BF_MAGIC)) return; if (sc && sc->count && sc->getSCE(SC_MAGICPOWER)) { if (sc->getSCE(SC_MAGICPOWER)->val4) { status_change_end(bl, SC_MAGICPOWER); } else { sc->getSCE(SC_MAGICPOWER)->val4 = 1; status_calc_bl_(bl, status_db.getCalcFlag(SC_MAGICPOWER)); if(bl->type == BL_PC){// update current display. clif_updatestatus(((TBL_PC *)bl),SP_MATK1); clif_updatestatus(((TBL_PC *)bl),SP_MATK2); } } } } int skill_magicdecoy(map_session_data *sd, t_itemid nameid) { int x, y, i, class_, skill; struct mob_data *md; nullpo_ret(sd); skill = sd->menuskill_val; if( !nameid || !itemdb_group.item_exists(IG_ELEMENT, nameid) || (i = pc_search_inventory(sd,nameid)) < 0 || !skill || pc_delitem(sd,i,1,0,0,LOG_TYPE_CONSUME) ) { clif_skill_fail(sd,NC_MAGICDECOY,USESKILL_FAIL_LEVEL,0); return 0; } // Spawn Position pc_delitem(sd,i,1,0,0,LOG_TYPE_CONSUME); x = sd->sc.comet_x; y = sd->sc.comet_y; sd->sc.comet_x = 0; sd->sc.comet_y = 0; sd->menuskill_val = 0; // Item picked decides the mob class switch(nameid) { case ITEMID_SCARLET_PTS: class_ = MOBID_MAGICDECOY_FIRE; break; case ITEMID_INDIGO_PTS: class_ = MOBID_MAGICDECOY_WATER; break; case ITEMID_YELLOW_WISH_PTS: class_ = MOBID_MAGICDECOY_WIND; break; case ITEMID_LIME_GREEN_PTS: class_ = MOBID_MAGICDECOY_EARTH; break; default: clif_skill_fail(sd,NC_MAGICDECOY,USESKILL_FAIL_LEVEL,0); return 0; } md = mob_once_spawn_sub(&sd->bl, sd->bl.m, x, y, sd->status.name, class_, "", SZ_SMALL, AI_NONE); if( md ) { struct unit_data *ud = unit_bl2ud(&md->bl); md->master_id = sd->bl.id; md->special_state.ai = AI_FAW; if(ud) { ud->skill_id = NC_MAGICDECOY; ud->skill_lv = skill; } if( md->deletetimer != INVALID_TIMER ) delete_timer(md->deletetimer, mob_timer_delete); md->deletetimer = add_timer (gettick() + skill_get_time(NC_MAGICDECOY,skill), mob_timer_delete, md->bl.id, 0); mob_spawn(md); } return 0; } /** * Process Warlock Spellbooks * @param sd: Player data * @param nameid: Spellbook item used */ void skill_spellbook(map_session_data &sd, t_itemid nameid) { if (reading_spellbook_db.empty()) return; status_change *sc = status_get_sc(&sd.bl); for (int i = SC_SPELLBOOK1; i <= SC_MAXSPELLBOOK; i++) { if (sc == nullptr || sc->getSCE(i) == nullptr) break; if (i == SC_MAXSPELLBOOK) { clif_skill_fail(&sd, WL_READING_SB, USESKILL_FAIL_SPELLBOOK_READING, 0); return; } } std::shared_ptr<s_skill_spellbook_db> spell = reading_spellbook_db.findBook(nameid); if (spell == nullptr) return; uint16 skill_id = spell->skill_id, skill_lv = pc_checkskill(&sd, skill_id); if (skill_lv == 0) { // Caster hasn't learned the skill sc_start(&sd.bl, &sd.bl, SC_SLEEP, 100, 1, skill_get_time(WL_READING_SB, pc_checkskill(&sd, WL_READING_SB))); clif_skill_fail(&sd, WL_READING_SB, USESKILL_FAIL_SPELLBOOK_DIFFICULT_SLEEP, 0); return; } uint16 points = spell->points; if (sc && sc->getSCE(SC_FREEZE_SP)) { if ((sc->getSCE(SC_FREEZE_SP)->val2 + points) > 8 * pc_checkskill(&sd, WL_FREEZE_SP) + status_get_int(&sd.bl) / 10 + sd.status.base_level / 10) { clif_skill_fail(&sd, WL_READING_SB, USESKILL_FAIL_SPELLBOOK_PRESERVATION_POINT, 0); return; } for (int i = SC_MAXSPELLBOOK; i >= SC_SPELLBOOK1; i--) { // This is how official saves spellbook. [malufett] if (!sc->getSCE(i)) { sc->getSCE(SC_FREEZE_SP)->val2 += points; // increase points sc_start4(&sd.bl, &sd.bl, (sc_type)i, 100, skill_id, skill_lv, points, 0, INFINITE_TICK); break; } } } else { sc_start2(&sd.bl, &sd.bl, SC_FREEZE_SP, 100, 0, points, INFINITE_TICK); sc_start4(&sd.bl, &sd.bl, SC_MAXSPELLBOOK, 100, skill_id, skill_lv, points, 0, INFINITE_TICK); } // Reading Spell Book SP cost same as the sealed spell. status_zap(&sd.bl, 0, skill_get_sp(skill_id, skill_lv)); } int skill_select_menu(map_session_data *sd,uint16 skill_id) { int lv, prob, aslvl = 0; uint16 id, sk_idx = 0; nullpo_ret(sd); if (sd->sc.getSCE(SC_STOP)) { aslvl = sd->sc.getSCE(SC_STOP)->val1; status_change_end(&sd->bl,SC_STOP); } if (!skill_id || !(sk_idx = skill_get_index(skill_id))) return 0; if( !skill_get_inf2(skill_id, INF2_ISAUTOSHADOWSPELL) || (id = sd->status.skill[sk_idx].id) == 0 || sd->status.skill[sk_idx].flag != SKILL_FLAG_PLAGIARIZED ) { clif_skill_fail(sd,SC_AUTOSHADOWSPELL,USESKILL_FAIL_LEVEL,0); return 0; } lv = (aslvl + 5) / 2; // The level the skill will be autocasted lv = min(lv,sd->status.skill[sk_idx].lv); prob = (aslvl >= 10) ? 15 : (30 - 2 * aslvl); // Probability at level 10 was increased to 15. sc_start4(&sd->bl,&sd->bl,SC__AUTOSHADOWSPELL,100,id,lv,prob,aslvl,skill_get_time(SC_AUTOSHADOWSPELL,aslvl)); return 0; } int skill_elementalanalysis(map_session_data* sd, int n, uint16 skill_lv, unsigned short* item_list) { int i; nullpo_ret(sd); nullpo_ret(item_list); if( n <= 0 ) return 1; for( i = 0; i < n; i++ ) { t_itemid nameid, product; int add_amount, del_amount, idx; struct item tmp_item; idx = item_list[i*2+0]-2; if( idx < 0 || idx >= MAX_INVENTORY ){ return 1; } del_amount = item_list[i*2+1]; if( skill_lv == 2 ) del_amount -= (del_amount % 10); add_amount = (skill_lv == 1) ? del_amount * (5 + rnd()%5) : del_amount / 10 ; if( (nameid = sd->inventory.u.items_inventory[idx].nameid) <= 0 || del_amount > sd->inventory.u.items_inventory[idx].amount ) { clif_skill_fail(sd,SO_EL_ANALYSIS,USESKILL_FAIL_LEVEL,0); return 1; } switch( nameid ) { // Level 1 case ITEMID_FLAME_HEART: product = ITEMID_BLOODY_RED; break; case ITEMID_MISTIC_FROZEN: product = ITEMID_CRYSTAL_BLUE; break; case ITEMID_ROUGH_WIND: product = ITEMID_WIND_OF_VERDURE; break; case ITEMID_GREAT_NATURE: product = ITEMID_YELLOW_LIVE; break; // Level 2 case ITEMID_BLOODY_RED: product = ITEMID_FLAME_HEART; break; case ITEMID_CRYSTAL_BLUE: product = ITEMID_MISTIC_FROZEN; break; case ITEMID_WIND_OF_VERDURE: product = ITEMID_ROUGH_WIND; break; case ITEMID_YELLOW_LIVE: product = ITEMID_GREAT_NATURE; break; default: clif_skill_fail(sd,SO_EL_ANALYSIS,USESKILL_FAIL_LEVEL,0); return 1; } if( pc_delitem(sd,idx,del_amount,0,1,LOG_TYPE_CONSUME) ) { clif_skill_fail(sd,SO_EL_ANALYSIS,USESKILL_FAIL_LEVEL,0); return 1; } if( skill_lv == 2 && rnd()%100 < 25 ) { // At level 2 have a fail chance. You loose your items if it fails. clif_skill_fail(sd,SO_EL_ANALYSIS,USESKILL_FAIL_LEVEL,0); return 1; } memset(&tmp_item,0,sizeof(tmp_item)); tmp_item.nameid = product; tmp_item.amount = add_amount; tmp_item.identify = 1; if( tmp_item.amount ) { unsigned char flag = pc_additem(sd,&tmp_item,tmp_item.amount,LOG_TYPE_CONSUME); if( flag != 0 ) { clif_additem(sd,0,0,flag); map_addflooritem(&tmp_item,tmp_item.amount,sd->bl.m,sd->bl.x,sd->bl.y,0,0,0,0,0); } } } return 0; } int skill_changematerial(map_session_data *sd, int n, unsigned short *item_list) { int i, j, k, c, p = 0, amount; t_itemid nameid; nullpo_ret(sd); nullpo_ret(item_list); // Search for objects that can be created. for( i = 0; i < MAX_SKILL_PRODUCE_DB; i++ ) { if( skill_produce_db[i].itemlv == 26 && skill_produce_db[i].nameid > 0 ) { p = 0; do { c = 0; // Verification of overlap between the objects required and the list submitted. for( j = 0; j < MAX_PRODUCE_RESOURCE; j++ ) { if( skill_produce_db[i].mat_id[j] > 0 ) { for( k = 0; k < n; k++ ) { int idx = item_list[k*2+0]-2; if( idx < 0 || idx >= MAX_INVENTORY ){ return 0; } nameid = sd->inventory.u.items_inventory[idx].nameid; amount = item_list[k*2+1]; if( nameid > 0 && sd->inventory.u.items_inventory[idx].identify == 0 ){ clif_msg_skill(sd,GN_CHANGEMATERIAL,ITEM_UNIDENTIFIED); return 0; } if( nameid == skill_produce_db[i].mat_id[j] && (amount-p*skill_produce_db[i].mat_amount[j]) >= skill_produce_db[i].mat_amount[j] && (amount-p*skill_produce_db[i].mat_amount[j])%skill_produce_db[i].mat_amount[j] == 0 ) // must be in exact amount c++; // match } } else break; // No more items required } p++; } while(n == j && c == n); p--; if ( p > 0 ) { skill_produce_mix(sd,GN_CHANGEMATERIAL,skill_produce_db[i].nameid,0,0,0,p,i); return 1; } } } if( p == 0) clif_msg_skill(sd,GN_CHANGEMATERIAL,ITEM_CANT_COMBINE); return 0; } /** * For Royal Guard's LG_TRAMPLE */ static int skill_destroy_trap(struct block_list *bl, va_list ap) { skill_unit *su = (struct skill_unit *)bl; nullpo_ret(su); std::shared_ptr<s_skill_unit_group> sg; t_tick tick = va_arg(ap, t_tick); if (su->alive && (sg = su->group) && skill_get_inf2(sg->skill_id, INF2_ISTRAP)) { switch( sg->unit_id ) { case UNT_CLAYMORETRAP: case UNT_FIRINGTRAP: case UNT_ICEBOUNDTRAP: map_foreachinrange(skill_trap_splash,&su->bl, skill_get_splash(sg->skill_id, sg->skill_lv), sg->bl_flag|BL_SKILL|~BCT_SELF, &su->bl,tick); break; case UNT_LANDMINE: case UNT_BLASTMINE: case UNT_SHOCKWAVE: case UNT_SANDMAN: case UNT_FLASHER: case UNT_FREEZINGTRAP: case UNT_CLUSTERBOMB: if (battle_config.skill_wall_check && !skill_get_nk(sg->skill_id, NK_NODAMAGE)) map_foreachinshootrange(skill_trap_splash,&su->bl, skill_get_splash(sg->skill_id, sg->skill_lv), sg->bl_flag, &su->bl,tick); else map_foreachinallrange(skill_trap_splash,&su->bl, skill_get_splash(sg->skill_id, sg->skill_lv), sg->bl_flag, &su->bl,tick); break; } // Traps aren't recovered. skill_delunit(su); } return 0; } /*========================================== * *------------------------------------------*/ int skill_blockpc_get(map_session_data *sd, int skillid) { int i; nullpo_retr(-1, sd); ARR_FIND(0, MAX_SKILLCOOLDOWN, i, sd->scd[i] && sd->scd[i]->skill_id == skillid); return (i >= MAX_SKILLCOOLDOWN) ? -1 : i; } TIMER_FUNC(skill_blockpc_end){ map_session_data *sd = map_id2sd(id); int i = (int)data; if (!sd || data < 0 || data >= MAX_SKILLCOOLDOWN) return 0; if (!sd->scd[i] || sd->scd[i]->timer != tid) { ShowWarning("skill_blockpc_end: Invalid Timer or not Skill Cooldown.\n"); return 0; } aFree(sd->scd[i]); sd->scd[i] = NULL; return 1; } /** * Flags a singular skill as being blocked from persistent usage. * @param sd the player the skill delay affects * @param skill_id the skill which should be delayed * @param tick the length of time the delay should last * @param load whether this assignment is being loaded upon player login * @return 0 if successful, -1 otherwise */ int skill_blockpc_start(map_session_data *sd, int skill_id, t_tick tick) { int i; nullpo_retr(-1, sd); if (!skill_id || tick < 1) return -1; ARR_FIND(0, MAX_SKILLCOOLDOWN, i, sd->scd[i] && sd->scd[i]->skill_id == skill_id); if (i < MAX_SKILLCOOLDOWN) { // Skill already with cooldown delete_timer(sd->scd[i]->timer, skill_blockpc_end); aFree(sd->scd[i]); sd->scd[i] = NULL; } ARR_FIND(0, MAX_SKILLCOOLDOWN, i, !sd->scd[i]); if (i < MAX_SKILLCOOLDOWN) { // Free Slot found CREATE(sd->scd[i], struct skill_cooldown_entry, 1); sd->scd[i]->skill_id = skill_id; sd->scd[i]->timer = add_timer(gettick() + tick, skill_blockpc_end, sd->bl.id, i); if (battle_config.display_status_timers) clif_skill_cooldown(sd, skill_id, tick); return 1; } else { ShowWarning("skill_blockpc_start: Too many skillcooldowns, increase MAX_SKILLCOOLDOWN.\n"); return 0; } } int skill_blockpc_clear(map_session_data *sd) { int i; nullpo_ret(sd); for (i = 0; i < MAX_SKILLCOOLDOWN; i++) { if (!sd->scd[i]) continue; delete_timer(sd->scd[i]->timer, skill_blockpc_end); aFree(sd->scd[i]); sd->scd[i] = NULL; } return 1; } TIMER_FUNC(skill_blockhomun_end){ struct homun_data *hd = (TBL_HOM*) map_id2bl(id); if (hd) { auto skill = util::vector_get(hd->blockskill, (uint16)data); if (skill != hd->blockskill.end()) hd->blockskill.erase(skill); } return 1; } int skill_blockhomun_start(struct homun_data *hd, uint16 skill_id, int tick) //[orn] { nullpo_retr(-1, hd); if (!skill_db.exists(skill_id)) return -1; auto skill = util::vector_get(hd->blockskill, skill_id); if (tick < 1 && skill != hd->blockskill.end()) { hd->blockskill.erase(skill); return -1; } hd->blockskill.push_back(skill_id); return add_timer(gettick() + tick, skill_blockhomun_end, hd->bl.id, skill_id); } TIMER_FUNC(skill_blockmerc_end){ s_mercenary_data *md = (TBL_MER*)map_id2bl(id); if (md) { auto skill = util::vector_get(md->blockskill, (uint16)data); if (skill != md->blockskill.end()) md->blockskill.erase(skill); } return 1; } int skill_blockmerc_start(s_mercenary_data *md, uint16 skill_id, int tick) { nullpo_retr(-1, md); if (!skill_db.exists(skill_id)) return -1; auto skill = util::vector_get(md->blockskill, skill_id); if (tick < 1 && skill != md->blockskill.end()) { md->blockskill.erase(skill); return -1; } md->blockskill.push_back(skill_id); return add_timer(gettick() + tick, skill_blockmerc_end, md->bl.id, skill_id); } /** * Adds a new skill unit entry for this player to recast after map load * @param sd: Player * @param skill_id: Skill ID to save * @param skill_lv: Skill level to save */ void skill_usave_add(map_session_data *sd, uint16 skill_id, uint16 skill_lv) { struct skill_usave *sus = NULL; if (idb_exists(skillusave_db,sd->status.char_id)) idb_remove(skillusave_db,sd->status.char_id); CREATE(sus, struct skill_usave, 1); idb_put(skillusave_db, sd->status.char_id, sus); sus->skill_id = skill_id; sus->skill_lv = skill_lv; } /** * Loads saved skill unit entries for this player after map load * @param sd: Player */ void skill_usave_trigger(map_session_data *sd) { skill_usave *sus; std::shared_ptr<s_skill_unit_group> group; if (!(sus = static_cast<skill_usave *>(idb_get(skillusave_db,sd->status.char_id)))) return; if ((group = skill_unitsetting(&sd->bl, sus->skill_id, sus->skill_lv, sd->bl.x, sd->bl.y, 0))) if (sus->skill_id == NC_NEUTRALBARRIER || sus->skill_id == NC_STEALTHFIELD ) sc_start2(&sd->bl, &sd->bl, (sus->skill_id == NC_NEUTRALBARRIER ? SC_NEUTRALBARRIER_MASTER : SC_STEALTHFIELD_MASTER), 100, sus->skill_lv, group->group_id, skill_get_time(sus->skill_id, sus->skill_lv)); idb_remove(skillusave_db, sd->status.char_id); } /* * */ int skill_split_str (char *str, char **val, int num) { int i; for( i = 0; i < num && str; i++ ) { val[i] = str; str = strchr(str,','); if( str ) *str++ = 0; } return i; } /* * */ void skill_init_unit_layout (void) { int i,j,pos = 0; memset(skill_unit_layout,0,sizeof(skill_unit_layout)); // standard square layouts go first for (i=0; i<=MAX_SQUARE_LAYOUT; i++) { int size = i*2+1; skill_unit_layout[i].count = size*size; for (j=0; j<size*size; j++) { skill_unit_layout[i].dx[j] = (j%size-i); skill_unit_layout[i].dy[j] = (j/size-i); } } // afterwards add special ones pos = i; for (const auto &it : skill_db) { std::shared_ptr<s_skill_db> skill = it.second; uint16 skill_id = skill->nameid; if (!skill->unit_id || skill->unit_layout_type[0] != -1) continue; if( skill_id == EL_FIRE_MANTLE ) { static const int dx[] = {-1, 0, 1, 1, 1, 0,-1,-1}; static const int dy[] = { 1, 1, 1, 0,-1,-1,-1, 0}; skill_unit_layout[pos].count = 8; memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); } else { switch (skill_id) { case MG_FIREWALL: case WZ_ICEWALL: case WL_EARTHSTRAIN: case RL_FIRE_RAIN: // these will be handled later break; case PR_SANCTUARY: case NPC_EVILLAND: { static const int dx[] = { -1, 0, 1,-2,-1, 0, 1, 2,-2,-1, 0, 1, 2,-2,-1, 0, 1, 2,-1, 0, 1}; static const int dy[]={ -2,-2,-2,-1,-1,-1,-1,-1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2}; skill_unit_layout[pos].count = 21; memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); } break; case PR_MAGNUS: { static const int dx[] = { -1, 0, 1,-1, 0, 1,-3,-2,-1, 0, 1, 2, 3,-3,-2,-1, 0, 1, 2, 3, -3,-2,-1, 0, 1, 2, 3,-1, 0, 1,-1, 0, 1}; static const int dy[] = { -3,-3,-3,-2,-2,-2,-1,-1,-1,-1, -1,-1,-1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 3, 3, 3}; skill_unit_layout[pos].count = 33; memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); } break; case AS_VENOMDUST: { static const int dx[] = {-1, 0, 0, 0, 1}; static const int dy[] = { 0,-1, 0, 1, 0}; skill_unit_layout[pos].count = 5; memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); } break; case CR_GRANDCROSS: case NPC_GRANDDARKNESS: { static const int dx[] = { 0, 0,-1, 0, 1,-2,-1, 0, 1, 2, -4,-3,-2,-1, 0, 1, 2, 3, 4,-2, -1, 0, 1, 2,-1, 0, 1, 0, 0}; static const int dy[] = { -4,-3,-2,-2,-2,-1,-1,-1,-1,-1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 3, 4}; skill_unit_layout[pos].count = 29; memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); } break; case PF_FOGWALL: { static const int dx[] = { -2,-1, 0, 1, 2,-2,-1, 0, 1, 2,-2,-1, 0, 1, 2}; static const int dy[] = { -1,-1,-1,-1,-1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1}; skill_unit_layout[pos].count = 15; memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); } break; case PA_GOSPEL: { static const int dx[] = { -1, 0, 1,-1, 0, 1,-3,-2,-1, 0, 1, 2, 3,-3,-2,-1, 0, 1, 2, 3, -3,-2,-1, 0, 1, 2, 3,-1, 0, 1, -1, 0, 1}; static const int dy[] = { -3,-3,-3,-2,-2,-2,-1,-1,-1,-1, -1,-1,-1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 3, 3, 3}; skill_unit_layout[pos].count = 33; memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); } break; case NJ_KAENSIN: { static const int dx[] = {-2,-1, 0, 1, 2,-2,-1, 0, 1, 2,-2,-1, 1, 2,-2,-1, 0, 1, 2,-2,-1, 0, 1, 2}; static const int dy[] = { 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 0, 0, 0, 0,-1,-1,-1,-1,-1,-2,-2,-2,-2,-2}; skill_unit_layout[pos].count = 24; memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); } break; case NJ_TATAMIGAESHI: { //Level 1 (count 4, cross of 3x3) static const int dx1[] = {-1, 1, 0, 0}; static const int dy1[] = { 0, 0,-1, 1}; //Level 2-3 (count 8, cross of 5x5) static const int dx2[] = {-2,-1, 1, 2, 0, 0, 0, 0}; static const int dy2[] = { 0, 0, 0, 0,-2,-1, 1, 2}; //Level 4-5 (count 12, cross of 7x7 static const int dx3[] = {-3,-2,-1, 1, 2, 3, 0, 0, 0, 0, 0, 0}; static const int dy3[] = { 0, 0, 0, 0, 0, 0,-3,-2,-1, 1, 2, 3}; //lv1 j = 0; skill_unit_layout[pos].count = 4; memcpy(skill_unit_layout[pos].dx,dx1,sizeof(dx1)); memcpy(skill_unit_layout[pos].dy,dy1,sizeof(dy1)); skill->unit_layout_type[j] = pos; //lv2/3 j++; pos++; skill_unit_layout[pos].count = 8; memcpy(skill_unit_layout[pos].dx,dx2,sizeof(dx2)); memcpy(skill_unit_layout[pos].dy,dy2,sizeof(dy2)); skill->unit_layout_type[j] = pos; skill->unit_layout_type[++j] = pos; //lv4/5 j++; pos++; skill_unit_layout[pos].count = 12; memcpy(skill_unit_layout[pos].dx,dx3,sizeof(dx3)); memcpy(skill_unit_layout[pos].dy,dy3,sizeof(dy3)); skill->unit_layout_type[j] = pos; skill->unit_layout_type[++j] = pos; //Fill in the rest using lv 5. for (;j<MAX_SKILL_LEVEL;j++) skill->unit_layout_type[j] = pos; //Skip, this way the check below will fail and continue to the next skill. pos++; } break; case GN_WALLOFTHORN: { static const int dx[] = {-1,-2,-2,-2,-2,-2,-1, 0, 1, 2, 2, 2, 2, 2, 1, 0}; static const int dy[] = { 2, 2, 1, 0,-1,-2,-2,-2,-2,-2,-1, 0, 1, 2, 2, 2}; skill_unit_layout[pos].count = 16; memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); } break; case NPC_FLAMECROSS: { static const int dx[] = {-2,-1, 1, 2, 0, 0, 0, 0}; static const int dy[] = { 0, 0, 0, 0,-2,-1, 1, 2}; skill_unit_layout[pos].count = 8; memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); } break; default: ShowError("unknown unit layout at skill %d\n",i); break; } } if (!skill_unit_layout[pos].count) continue; for (j=0;j<MAX_SKILL_LEVEL;j++) skill->unit_layout_type[j] = pos; pos++; } // firewall and icewall have 8 layouts (direction-dependent) firewall_unit_pos = pos; for (i=0;i<8;i++) { if (i&1) { skill_unit_layout[pos].count = 5; if (i&0x2) { int dx[] = {-1,-1, 0, 0, 1}; int dy[] = { 1, 0, 0,-1,-1}; memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); } else { int dx[] = { 1, 1 ,0, 0,-1}; int dy[] = { 1, 0, 0,-1,-1}; memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); } } else { skill_unit_layout[pos].count = 3; if (i%4==0) { int dx[] = {-1, 0, 1}; int dy[] = { 0, 0, 0}; memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); } else { int dx[] = { 0, 0, 0}; int dy[] = {-1, 0, 1}; memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); } } pos++; } icewall_unit_pos = pos; for (i=0;i<8;i++) { skill_unit_layout[pos].count = 5; if (i&1) { if (i&0x2) { int dx[] = {-2,-1, 0, 1, 2}; int dy[] = { 2, 1, 0,-1,-2}; memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); } else { int dx[] = { 2, 1 ,0,-1,-2}; int dy[] = { 2, 1, 0,-1,-2}; memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); } } else { if (i%4==0) { int dx[] = {-2,-1, 0, 1, 2}; int dy[] = { 0, 0, 0, 0, 0}; memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); } else { int dx[] = { 0, 0, 0, 0, 0}; int dy[] = {-2,-1, 0, 1, 2}; memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); } } pos++; } earthstrain_unit_pos = pos; for( i = 0; i < 8; i++ ) { // For each Direction skill_unit_layout[pos].count = 15; switch( i ) { case 0: case 1: case 3: case 4: case 5: case 7: { int dx[] = {-7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7}; int dy[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); } break; case 2: case 6: { int dx[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; int dy[] = {-7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7}; memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); } break; } pos++; } firerain_unit_pos = pos; for( i = 0; i < 8; i++ ) { skill_unit_layout[pos].count = 3; switch( i ) { case 0: case 1: case 3: case 4: case 5: case 7: { static const int dx[] = {-1, 0, 1}; static const int dy[] = { 0, 0, 0}; memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); } break; case 2: case 6: { static const int dx[] = { 0, 0, 0}; static const int dy[] = {-1, 0, 1}; memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); } break; } pos++; } if( pos >= MAX_SKILL_UNIT_LAYOUT ) ShowError("skill_init_unit_layout: The skill_unit_layout has met the limit or overflowed (pos=%d)\n", pos); } void skill_init_nounit_layout (void) { int i, pos = 0; memset(skill_nounit_layout,0,sizeof(skill_nounit_layout)); for( i = 0; i < 8; i++ ) { if( i&1 ) { skill_nounit_layout[pos].count = 33; if( i&2 ) { if( i&4 ) { // 7 int dx[] = { 5, 6, 7, 5, 6, 4, 5, 6, 4, 5, 3, 4, 5, 3, 4, 2, 3, 4, 2, 3, 1, 2, 3, 1, 2, 0, 1, 2, 0, 1,-1, 0, 1}; int dy[] = { 7, 6, 5, 6, 5, 6, 5, 4, 5, 4, 5, 4, 3, 4, 3, 4, 3, 2, 3, 2, 3, 2, 1, 2, 1, 2, 1, 0, 1, 0, 1, 0,-1}; memcpy(skill_nounit_layout[pos].dx,dx,sizeof(dx)); memcpy(skill_nounit_layout[pos].dy,dy,sizeof(dy)); } else { // 3 int dx[] = {-5,-6,-7,-5,-6,-4,-5,-6,-4,-5,-3,-4,-5,-3,-4,-2,-3,-4,-2,-3,-1,-2,-3,-1,-2, 0,-1,-2, 0,-1, 1, 0,-1}; int dy[] = {-7,-6,-5,-6,-5,-6,-5,-4,-5,-4,-5,-4,-3,-4,-3,-4,-3,-2,-3,-2,-3,-2,-1,-2,-1,-2,-1, 0,-1, 0,-1, 0, 1}; memcpy(skill_nounit_layout[pos].dx,dx,sizeof(dx)); memcpy(skill_nounit_layout[pos].dy,dy,sizeof(dy)); } } else { if( i&4 ) { // 5 int dx[] = { 7, 6, 5, 6, 5, 6, 5, 4, 5, 4, 5, 4, 3, 4, 3, 4, 3, 2, 3, 2, 3, 2, 1, 2, 1, 2, 1, 0, 1, 0, 1, 0,-1}; int dy[] = {-5,-6,-7,-5,-6,-4,-5,-6,-4,-5,-3,-4,-5,-3,-4,-2,-3,-4,-2,-3,-1,-2,-3,-1,-2, 0,-1,-2, 0,-1, 1, 0,-1}; memcpy(skill_nounit_layout[pos].dx,dx,sizeof(dx)); memcpy(skill_nounit_layout[pos].dy,dy,sizeof(dy)); } else { // 1 int dx[] = {-7,-6,-5,-6,-5,-6,-5,-4,-5,-4,-5,-4,-3,-4,-3,-4,-3,-2,-3,-2,-3,-2,-1,-2,-1,-2,-1, 0,-1, 0,-1, 0, 1}; int dy[] = { 5, 6, 7, 5, 6, 4, 5, 6, 4, 5, 3, 4, 5, 3, 4, 2, 3, 4, 2, 3, 1, 2, 3, 1, 2, 0, 1, 2, 0, 1,-1, 0, 1}; memcpy(skill_nounit_layout[pos].dx,dx,sizeof(dx)); memcpy(skill_nounit_layout[pos].dy,dy,sizeof(dy)); } } } else { skill_nounit_layout[pos].count = 21; if( i&2 ) { if( i&4 ) { // 6 int dx[] = { 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6}; int dy[] = { 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,-1,-1,-1,-1,-1,-1,-1}; memcpy(skill_nounit_layout[pos].dx,dx,sizeof(dx)); memcpy(skill_nounit_layout[pos].dy,dy,sizeof(dy)); } else { // 2 int dx[] = {-6,-5,-4,-3,-2,-1, 0,-6,-5,-4,-3,-2,-1, 0,-6,-5,-4,-3,-2,-1, 0}; int dy[] = { 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,-1,-1,-1,-1,-1,-1,-1}; memcpy(skill_nounit_layout[pos].dx,dx,sizeof(dx)); memcpy(skill_nounit_layout[pos].dy,dy,sizeof(dy)); } } else { if( i&4 ) { // 4 int dx[] = {-1, 0, 1,-1, 0, 1,-1, 0, 1,-1, 0, 1,-1, 0, 1,-1, 0, 1,-1, 0, 1}; int dy[] = { 0, 0, 0,-1,-1,-1,-2,-2,-2,-3,-3,-3,-4,-4,-4,-5,-5,-5,-6,-6,-6}; memcpy(skill_nounit_layout[pos].dx,dx,sizeof(dx)); memcpy(skill_nounit_layout[pos].dy,dy,sizeof(dy)); } else { // 0 int dx[] = {-1, 0, 1,-1, 0, 1,-1, 0, 1,-1, 0, 1,-1, 0, 1,-1, 0, 1,-1, 0, 1}; int dy[] = { 6, 6, 6, 5, 5, 5, 4, 4, 4, 3, 3, 3, 2, 2, 2, 1, 1, 1, 0, 0, 0}; memcpy(skill_nounit_layout[pos].dx,dx,sizeof(dx)); memcpy(skill_nounit_layout[pos].dy,dy,sizeof(dy)); } } } pos++; } for( i = 0; i < 8; i++ ) { if( i&1 ) { skill_nounit_layout[pos].count = 74; if( i&2 ) { if( i&4 ) { // 7 int dx[] = {-2,-1, 0, 1, 2, 3, 4, 5, 6, 7, 8,-2,-1, 0, 1, 2, 3, 4, 5, 6, 7, -3,-2,-1, 0, 1, 2, 3, 4, 5, 6, 7,-3,-2,-1,-0, 1, 2, 3, 4, 5, 6, -4,-3,-2,-1, 0, 1, 2, 3, 4, 5, 6,-4,-3,-2,-1,-0, 1, 2, 3, 4, 5, -5,-4,-3,-2,-1, 0, 1, 2, 3, 4, 5}; int dy[] = { 8, 7, 6, 5, 4, 3, 2, 1, 0,-1,-2, 7, 6, 5, 4, 3, 2, 1, 0,-1,-2, 7, 6, 5, 4, 3, 2, 1, 0,-1,-2,-3, 6, 5, 4, 3, 2, 1, 0,-1,-2,-3, 6, 5, 4, 3, 2, 1, 0,-1,-2,-3,-4, 5, 4, 3, 2, 1, 0,-1,-2,-3,-4, 5, 4, 3, 2, 1, 0,-1,-2,-3,-4,-5}; memcpy(skill_nounit_layout[pos].dx,dx,sizeof(dx)); memcpy(skill_nounit_layout[pos].dy,dy,sizeof(dy)); } else { // 3 int dx[] = { 2, 1, 0,-1,-2,-3,-4,-5,-6,-7,-8, 2, 1, 0,-1,-2,-3,-4,-5,-6,-7, 3, 2, 1, 0,-1,-2,-3,-4,-5,-6,-7, 3, 2, 1, 0,-1,-2,-3,-4,-5,-6, 4, 3, 2, 1, 0,-1,-2,-3,-4,-5,-6, 4, 3, 2, 1, 0,-1,-2,-3,-4,-5, 5, 4, 3, 2, 1, 0,-1,-2,-3,-4,-5}; int dy[] = {-8,-7,-6,-5,-4,-3,-2,-1, 0, 1, 2,-7,-6,-5,-4,-3,-2,-1, 0, 1, 2, -7,-6,-5,-4,-3,-2,-1, 0, 1, 2, 3,-6,-5,-4,-3,-2,-1, 0, 1, 2, 3, -6,-5,-4,-3,-2,-1, 0, 1, 2, 3, 4,-5,-4,-3,-2,-1, 0, 1, 2, 3, 4, -5,-4,-3,-2,-1, 0, 1, 2, 3, 4, 5}; memcpy(skill_nounit_layout[pos].dx,dx,sizeof(dx)); memcpy(skill_nounit_layout[pos].dy,dy,sizeof(dy)); } } else { if( i&4 ) { // 5 int dx[] = { 8, 7, 6, 5, 4, 3, 2, 1, 0,-1,-2, 7, 6, 5, 4, 3, 2, 1, 0,-1,-2, 7, 6, 5, 4, 3, 2, 1, 0,-1,-2,-3, 6, 5, 4, 3, 2, 1, 0,-1,-2,-3, 6, 5, 4, 3, 2, 1, 0,-1,-2,-3,-4, 5, 4, 3, 2, 1, 0,-1,-2,-3,-4, 5, 4, 3, 2, 1, 0,-1,-2,-3,-4,-5}; int dy[] = { 2, 1, 0,-1,-2,-3,-4,-5,-6,-7,-8, 2, 1, 0,-1,-2,-3,-4,-5,-6,-7, 3, 2, 1, 0,-1,-2,-3,-4,-5,-6,-7, 3, 2, 1, 0,-1,-2,-3,-4,-5,-6, 4, 3, 2, 1, 0,-1,-2,-3,-4,-5,-6, 4, 3, 2, 1, 0,-1,-2,-3,-4,-5, 5, 4, 3, 2, 1, 0,-1,-2,-3,-4,-5}; memcpy(skill_nounit_layout[pos].dx,dx,sizeof(dx)); memcpy(skill_nounit_layout[pos].dy,dy,sizeof(dy)); } else { // 1 int dx[] = {-8,-7,-6,-5,-4,-3,-2,-1, 0, 1, 2,-7,-6,-5,-4,-3,-2,-1, 0, 1, 2, -7,-6,-5,-4,-3,-2,-1, 0, 1, 2, 3,-6,-5,-4,-3,-2,-1, 0, 1, 2, 3, -6,-5,-4,-3,-2,-1, 0, 1, 2, 3, 4,-5,-4,-3,-2,-1, 0, 1, 2, 3, 4, -5,-4,-3,-2,-1, 0, 1, 2, 3, 4, 5}; int dy[] = {-2,-1, 0, 1, 2, 3, 4, 5, 6, 7, 8,-2,-1, 0, 1, 2, 3, 4, 5, 6, 7, -3,-2,-1, 0, 1, 2, 3, 4, 5, 6, 7,-3,-2,-1, 0, 1, 2, 3, 4, 5, 6, -4,-3,-2,-1, 0, 1, 2, 3, 4, 5, 6,-4,-3,-2,-1, 0, 1, 2, 3, 4, 5, -5,-4,-3,-2,-1, 0, 1, 2, 3, 4, 5}; memcpy(skill_nounit_layout[pos].dx,dx,sizeof(dx)); memcpy(skill_nounit_layout[pos].dy,dy,sizeof(dy)); } } } else { skill_nounit_layout[pos].count = 44; if( i&2 ) { if( i&4 ) { // 6 int dx[] = { 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3}; int dy[] = { 5, 5, 5, 5, 4, 4, 4, 4, 3, 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0,-1,-1,-1,-1,-2,-2,-2,-2,-3,-3,-3,-3,-4,-4,-4,-4,-5,-5,-5,-5}; memcpy(skill_nounit_layout[pos].dx,dx,sizeof(dx)); memcpy(skill_nounit_layout[pos].dy,dy,sizeof(dy)); } else { // 2 int dx[] = {-3,-2,-1, 0,-3,-2,-1, 0,-3,-2,-1, 0,-3,-2,-1, 0,-3,-2,-1, 0,-3,-2,-1, 0,-3,-2,-1, 0,-3,-2,-1, 0,-3,-2,-1, 0,-3,-2,-1, 0,-3,-2,-1, 0}; int dy[] = { 5, 5, 5, 5, 4, 4, 4, 4, 3, 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0,-1,-1,-1,-1,-2,-2,-2,-2,-3,-3,-3,-3,-4,-4,-4,-4,-5,-5,-5,-5}; memcpy(skill_nounit_layout[pos].dx,dx,sizeof(dx)); memcpy(skill_nounit_layout[pos].dy,dy,sizeof(dy)); } } else { if( i&4 ) { // 4 int dx[] = { 5, 4, 3, 2, 1, 0,-1,-2,-3,-4,-5, 5, 4, 3, 2, 1, 0,-1,-2,-3,-4,-5, 5, 4, 3, 2, 1, 0,-1,-2,-3,-4,-5, 5, 4, 3, 2, 1, 0,-1,-2,-3,-4,-5}; int dy[] = {-3,-3,-3,-3,-3,-3,-3,-3,-3,-3,-3,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; memcpy(skill_nounit_layout[pos].dx,dx,sizeof(dx)); memcpy(skill_nounit_layout[pos].dy,dy,sizeof(dy)); } else { // 0 int dx[] = {-5,-4,-3,-2,-1, 0, 1, 2, 3, 4, 5,-5,-4,-3,-2,-1, 0, 1, 2, 3, 4, 5,-5,-4,-3,-2,-1, 0, 1, 2, 3, 4, 5,-5,-4,-3,-2,-1, 0, 1, 2, 3, 4, 5}; int dy[] = { 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; memcpy(skill_nounit_layout[pos].dx,dx,sizeof(dx)); memcpy(skill_nounit_layout[pos].dy,dy,sizeof(dy)); } } } pos++; } if( pos >= MAX_SKILL_UNIT_LAYOUT2 ) ShowError("skill_init_nounit_layout: The skill_nounit_layout has met the limit or overflowed (pos=%d)\n", pos); } int skill_block_check(struct block_list *bl, sc_type type , uint16 skill_id) { status_change *sc = status_get_sc(bl); if( !sc || !bl || !skill_id ) return 0; // Can do it switch (type) { case SC_ANKLE: if (skill_id == AL_TELEPORT) return 1; break; case SC_STASIS: if (skill_get_inf2_(skill_id, { INF2_ISSONG, INF2_ISENSEMBLE, INF2_ISCHORUS }) || skill_get_type(skill_id) == BF_MAGIC) return 1; // Can't do it. break; case SC_KAGEHUMI: if (skill_get_inf2(skill_id, INF2_IGNOREKAGEHUMI)) return 1; case SC_BITE: if (skill_get_inf2(skill_id, INF2_IGNOREWUGBITE)) return 1; break; } return 0; } /** * Determines whether a skill is currently active or not. Used for purposes of cancelling HP/SP usage when disabling a skill. * @param sc: Status changes active on target * @param skill_id: Skill to toggle * @return True on success or false otherwise */ bool skill_disable_check(status_change &sc, uint16 skill_id) { std::shared_ptr<s_skill_db> skill = skill_db.find(skill_id); if (skill == nullptr || skill->sc <= SC_NONE || skill->sc >= SC_MAX) return false; if (skill->inf2[INF2_TOGGLEABLE]) { if (sc.getSCE(skill->sc)) return true; // These 2 skills contain a master and are not correctly pulled using skill_get_sc if (skill->nameid == NC_NEUTRALBARRIER && sc.getSCE(SC_NEUTRALBARRIER_MASTER)) return true; if (skill->nameid == NC_STEALTHFIELD && sc.getSCE(SC_STEALTHFIELD_MASTER)) return true; } return false; } int skill_get_elemental_type( uint16 skill_id , uint16 skill_lv ) { int type = 0; switch( skill_id ) { case SO_SUMMON_AGNI: type = ELEMENTALID_AGNI_S; break; case SO_SUMMON_AQUA: type = ELEMENTALID_AQUA_S; break; case SO_SUMMON_VENTUS: type = ELEMENTALID_VENTUS_S; break; case SO_SUMMON_TERA: type = ELEMENTALID_TERA_S; break; } type += skill_lv - 1; return type; } /** * Check before do `unit_movepos` call * @param check_flag Flags: 1:Check for BG maps, 2:Check for GVG maps on WOE times, 4:Check for GVG maps regardless Agit flags * @return True:If unit can be moved, False:If check on flags are met or unit cannot be moved. **/ static bool skill_check_unit_movepos(uint8 check_flag, struct block_list *bl, short dst_x, short dst_y, int easy, bool checkpath) { status_change *sc; nullpo_retr(false, bl); struct map_data *mapdata = map_getmapdata(bl->m); if (check_flag&1 && mapdata->flag[MF_BATTLEGROUND]) return false; if (check_flag&2 && mapdata_flag_gvg(mapdata)) return false; if (check_flag&4 && mapdata_flag_gvg2(mapdata)) return false; sc = status_get_sc(bl); if (sc && sc->getSCE(SC_SV_ROOTTWIST)) return false; return unit_movepos(bl, dst_x, dst_y, easy, checkpath); } /** * Get skill duration after adjustments by skill_duration mapflag * @param mapdata: Source map data * @param skill_id: Skill ID * @param skill_lv: Skill level * @return Adjusted skill duration */ int skill_get_time3(struct map_data *mapdata, uint16 skill_id, uint16 skill_lv) { int time = 0; if (!(time = skill_get_time(skill_id, skill_lv))) return 0; if (mapdata && !mapdata->skill_duration.empty() && mapdata->skill_duration.find(skill_id) != mapdata->skill_duration.end()) return time / 100 * mapdata->skill_duration[skill_id]; return time; } const std::string SkillDatabase::getDefaultLocation() { return std::string(db_path) + "/skill_db.yml"; } template<typename T, size_t S> bool SkillDatabase::parseNode(const std::string& nodeName, const std::string& subNodeName, const ryml::NodeRef& node, T (&arr)[S]) { int32 value; const auto& skNode = node[c4::to_csubstr(nodeName)]; if (!skNode.is_seq()) { if (!this->asInt32(node, nodeName, value)) return false; for (size_t i = 0; i < S; i++) arr[i] = value; } else { uint16 max_level = 0; for (const auto& it : skNode) { uint16 skill_lv; if (!this->asUInt16(it, "Level", skill_lv)) continue; if (skill_lv > MAX_SKILL_LEVEL) { this->invalidWarning(it["Level"], "%s Level exceeds the maximum skill level of %d, skipping.\n", nodeName.c_str(), MAX_SKILL_LEVEL); return false; } if (!this->asInt32(it, subNodeName, value)) continue; arr[skill_lv - 1] = value; max_level = max(max_level, skill_lv); } size_t i = max_level, j; // Check for linear change with increasing steps until we reach half of the data acquired. for (size_t step = 1; step <= i / 2; step++) { int diff = arr[i - 1] - arr[i - step - 1]; for (j = i - 1; j >= step; j--) { if ((arr[j] - arr[j - step]) != diff) break; } if (j >= step) // No match, try next step. continue; for (; i < MAX_SKILL_LEVEL; i++) { // Apply linear increase arr[i] = arr[i - step] + diff; if (arr[i] < 1 && arr[i - 1] >= 0) { // Check if we have switched from + to -, cap the decrease to 0 in said cases. arr[i] = 1; diff = 0; step = 1; } } return true; } // Unable to determine linear trend, fill remaining array values with last value for (; i < S; i++) arr[i] = arr[max_level - 1]; } return true; } /** * Reads and parses an entry from the skill_db. * @param node: YAML node containing the entry. * @return count of successfully parsed rows */ uint64 SkillDatabase::parseBodyNode(const ryml::NodeRef& node) { uint16 skill_id; if (!this->asUInt16(node, "Id", skill_id)) return 0; std::shared_ptr<s_skill_db> skill = this->find(skill_id); bool exists = skill != nullptr; if (!exists) { if (!this->nodesExist(node, { "Name", "Description", "MaxLevel" })) return 0; skill = std::make_shared<s_skill_db>(); skill->nameid = skill_id; } if (this->nodeExists(node, "Name")) { std::string name; if (!this->asString(node, "Name", name)) return 0; name.resize(SKILL_NAME_LENGTH); memcpy(skill->name, name.c_str(), sizeof(skill->name)); } if (this->nodeExists(node, "Description")) { std::string name; if (!this->asString(node, "Description", name)) return 0; name.resize(SKILL_DESC_LENGTH); memcpy(skill->desc, name.c_str(), sizeof(skill->desc)); } if (this->nodeExists(node, "MaxLevel")) { uint16 skill_lv; if (!this->asUInt16(node, "MaxLevel", skill_lv)) return 0; if (skill_lv == 0 || skill_lv > MAX_SKILL_LEVEL) { this->invalidWarning(node["MaxLevel"], "MaxLevel %hu does not meet the bounds of 1~%d.\n", skill_lv, MAX_SKILL_LEVEL); return 0; } skill->max = skill_lv; } if (this->nodeExists(node, "Type")) { std::string type; if (!this->asString(node, "Type", type)) return 0; std::string type_constant = "BF_" + type; int64 constant; if (!script_get_constant(type_constant.c_str(), &constant)) { this->invalidWarning(node["Type"], "Type %s is invalid.\n", type.c_str()); return 0; } if (constant < BF_NONE || constant > BF_MISC) { this->invalidWarning(node["Type"], "Constant Type %s is not a supported skill type.\n", type_constant.c_str()); return 0; } skill->skill_type = static_cast<e_battle_flag>(constant); } else { if (!exists) skill->skill_type = BF_NONE; } if (this->nodeExists(node, "TargetType")) { std::string inf; if (!this->asString(node, "TargetType", inf)) return 0; std::string inf_constant = "INF_" + inf + "_SKILL"; int64 constant; if (!script_get_constant(inf_constant.c_str(), &constant)) { this->invalidWarning(node["TargetType"], "TargetType %s is invalid.\n", inf.c_str()); return 0; } skill->inf = static_cast<uint16>(constant); } if (this->nodeExists(node, "DamageFlags")) { const auto& damageNode = node["DamageFlags"]; for (const auto& it : damageNode) { std::string nk; c4::from_chars(it.key(), &nk); std::string nk_constant = "NK_" + nk; int64 constant; if (!script_get_constant(nk_constant.c_str(), &constant)) { this->invalidWarning(damageNode, "DamageFlags %s is invalid.\n", nk.c_str()); return 0; } bool active; if (!this->asBool(damageNode, nk, active)) return 0; if (active) skill->nk.set(static_cast<uint8>(constant)); else skill->nk.reset(static_cast<uint8>(constant)); } } if (this->nodeExists(node, "Flags")) { const auto& infoNode = node["Flags"]; for (const auto& it : infoNode) { std::string inf2; c4::from_chars(it.key(), &inf2); std::string inf2_constant = "INF2_" + inf2; int64 constant; if (!script_get_constant(inf2_constant.c_str(), &constant)) { this->invalidWarning(infoNode, "Flag %s is invalid, skipping.\n", inf2.c_str()); continue; } bool active; if (!this->asBool(infoNode, inf2, active)) return 0; if (active) skill->inf2.set(static_cast<uint8>(constant)); else skill->inf2.reset(static_cast<uint8>(constant)); } } if (this->nodeExists(node, "Range")) { if (!this->parseNode("Range", "Size", node, skill->range)) return 0; } else { if (!exists) memset(skill->range, 0, sizeof(skill->range)); } if (this->nodeExists(node, "Hit")) { std::string hit; if (!this->asString(node, "Hit", hit)) return 0; std::string hit_constant = "DMG_" + hit; int64 constant; if (!script_get_constant(hit_constant.c_str(), &constant)) { this->invalidWarning(node["Hit"], "Hit %s is invalid.\n", hit.c_str()); return 0; } skill->hit = static_cast<e_damage_type>(constant); } else { if (!exists) skill->hit = DMG_NORMAL; } if (this->nodeExists(node, "HitCount")) { if (!this->parseNode("HitCount", "Count", node, skill->num)) return 0; } else { if (!exists) memset(skill->num, 0, sizeof(skill->num)); } if (this->nodeExists(node, "Element")) { const auto elementNode = node["Element"]; std::string element; if (!elementNode.is_seq()) { if (!this->asString(node, "Element", element)) return 0; std::string element_constant = "ELE_" + element; int64 constant; if (!script_get_constant(element_constant.c_str(), &constant)) { this->invalidWarning(elementNode["Element"], "Element %s is invalid.\n", element.c_str()); return 0; } if (constant == ELE_NONE) { this->invalidWarning(elementNode["Element"], "%s is not a valid element.\n", element.c_str()); return 0; } memset(skill->element, static_cast<e_element>(constant), sizeof(skill->element)); } else { for (const auto& it : elementNode) { uint16 skill_lv; if (!this->asUInt16(it, "Level", skill_lv)) continue; if (skill_lv > MAX_SKILL_LEVEL) { this->invalidWarning(it["Level"], "Element Level exceeds the maximum skill level of %d, skipping.\n", MAX_SKILL_LEVEL); return false; } if (!this->asString(it, "Element", element)) continue; std::string element_constant = "ELE_" + element; int64 constant; if (!script_get_constant(element_constant.c_str(), &constant)) { this->invalidWarning(elementNode["Element"], "Element %s is invalid.\n", element.c_str()); return 0; } if (constant == ELE_NONE) { this->invalidWarning(elementNode["Element"], "%s is not a valid element.\n", element.c_str()); return 0; } skill->element[skill_lv - 1] = static_cast<e_element>(constant); } } } else { if (!exists) memset(skill->element, ELE_NEUTRAL, sizeof(skill->element)); } if (this->nodeExists(node, "SplashArea")) { if (!this->parseNode("SplashArea", "Area", node, skill->splash)) return 0; } else { if (!exists) memset(skill->splash, 0, sizeof(skill->splash)); } if (this->nodeExists(node, "ActiveInstance")) { if (!this->parseNode("ActiveInstance", "Max", node, skill->maxcount)) return 0; } else { if (!exists) memset(skill->maxcount, 0, sizeof(skill->maxcount)); } if (this->nodeExists(node, "Knockback")) { if (!this->parseNode("Knockback", "Amount", node, skill->blewcount)) return 0; } else { if (!exists) memset(skill->blewcount, 0, sizeof(skill->blewcount)); } if (this->nodeExists(node, "CopyFlags")) { const auto& copyNode = node["CopyFlags"]; if (this->nodeExists(copyNode, "Skill")) { const auto& copyskillNode = copyNode["Skill"]; if (this->nodeExists(copyskillNode, "Plagiarism")) { bool active; if (!this->asBool(copyskillNode, "Plagiarism", active)) return 0; if (active) skill->copyable.option |= SKILL_COPY_PLAGIARISM; else skill->copyable.option &= SKILL_COPY_PLAGIARISM; } if (this->nodeExists(copyskillNode, "Reproduce")) { bool active; if (!this->asBool(copyskillNode, "Reproduce", active)) return 0; if (active) skill->copyable.option |= SKILL_COPY_REPRODUCE; else skill->copyable.option &= SKILL_COPY_REPRODUCE; } } else { this->invalidWarning(copyNode, "CopyFlags requires a Skill copy type.\n"); return 0; } if (this->nodeExists(copyNode, "RemoveRequirement")) { const auto& copyreqNode = copyNode["RemoveRequirement"]; for (const auto& it : copyreqNode) { std::string req; c4::from_chars(it.key(), &req); std::string req_constant = "SKILL_REQ_" + req; int64 constant; if (!script_get_constant(req_constant.c_str(), &constant)) { this->invalidWarning(copyreqNode, "CopyFlags RemoveRequirement %s is invalid.\n", req.c_str()); return 0; } skill->copyable.req_opt |= constant; } } else { if (!exists) skill->copyable.req_opt = 0; } } if (this->nodeExists(node, "NoNearNpc")) { const auto& npcNode = node["NoNearNpc"]; if (this->nodeExists(npcNode, "AdditionalRange")) { uint16 range; if (!this->asUInt16(npcNode, "AdditionalRange", range)) return 0; skill->unit_nonearnpc_range = range; } else { if (!exists) skill->unit_nonearnpc_range = 0; } if (this->nodeExists(npcNode, "Type")) { const auto& npctypeNode = npcNode["Type"]; for (const auto& it : npctypeNode) { std::string type; c4::from_chars(it.key(), &type); std::string type_constant = "SKILL_NONEAR_" + type; int64 constant; if (!script_get_constant(type_constant.c_str(), &constant)) { this->invalidWarning(npctypeNode, "NoNearNPC Type %s is invalid.\n", type.c_str()); return 0; } bool active; if (!this->asBool(npctypeNode, type, active)) return 0; if (active) skill->unit_nonearnpc_type |= constant; else skill->unit_nonearnpc_type &= ~constant; } } else { if (!exists) skill->unit_nonearnpc_type = 0; } } if (this->nodeExists(node, "CastCancel")) { bool active; if (!this->asBool(node, "CastCancel", active)) return 0; skill->castcancel = active; } else { if (!exists) skill->castcancel = false; } if (this->nodeExists(node, "CastDefenseReduction")) { uint16 reduction; if (!this->asUInt16(node, "CastDefenseReduction", reduction)) return 0; skill->cast_def_rate = reduction; } else { if (!exists) skill->cast_def_rate = 0; } if (this->nodeExists(node, "CastTime")) { if (!this->parseNode("CastTime", "Time", node, skill->cast)) return 0; } else { if (!exists) memset(skill->cast, 0, sizeof(skill->cast)); } if (this->nodeExists(node, "AfterCastActDelay")) { if (!this->parseNode("AfterCastActDelay", "Time", node, skill->delay)) return 0; } else { if (!exists) memset(skill->delay, 0, sizeof(skill->delay)); } if (this->nodeExists(node, "AfterCastWalkDelay")) { if (!this->parseNode("AfterCastWalkDelay", "Time", node, skill->walkdelay)) return 0; } else { if (!exists) memset(skill->walkdelay, 0, sizeof(skill->walkdelay)); } if (this->nodeExists(node, "Duration1")) { if (!this->parseNode("Duration1", "Time", node, skill->upkeep_time)) return 0; } else { if (!exists) memset(skill->upkeep_time, 0, sizeof(skill->upkeep_time)); } if (this->nodeExists(node, "Duration2")) { if (!this->parseNode("Duration2", "Time", node, skill->upkeep_time2)) return 0; } else { if (!exists) memset(skill->upkeep_time2, 0, sizeof(skill->upkeep_time2)); } if (this->nodeExists(node, "Cooldown")) { if (!this->parseNode("Cooldown", "Time", node, skill->cooldown)) return 0; } else { if (!exists) memset(skill->cooldown, 0, sizeof(skill->cooldown)); } #ifdef RENEWAL_CAST if (this->nodeExists(node, "FixedCastTime")) { if (!this->parseNode("FixedCastTime", "Time", node, skill->fixed_cast)) return 0; } else { if (!exists) memset(skill->fixed_cast, 0, sizeof(skill->fixed_cast)); } #endif if (this->nodeExists(node, "CastTimeFlags")) { const auto& castNode = node["CastTimeFlags"]; for (const auto& it : castNode) { std::string flag; c4::from_chars(it.key(), &flag); std::string flag_constant = "SKILL_CAST_" + flag; int64 constant; if (!script_get_constant(flag_constant.c_str(), &constant)) { this->invalidWarning(castNode, "CastTimeFlags %s option is invalid.\n", flag.c_str()); return 0; } bool active; if (!this->asBool(castNode, flag, active)) return 0; if (active) skill->castnodex |= constant; else skill->castnodex &= ~constant; } } if (this->nodeExists(node, "CastDelayFlags")) { const auto& castNode = node["CastDelayFlags"]; for (const auto& it : castNode) { std::string flag; c4::from_chars(it.key(), &flag); std::string flag_constant = "SKILL_CAST_" + flag; int64 constant; if (!script_get_constant(flag_constant.c_str(), &constant)) { this->invalidWarning(castNode, "CastDelayFlags %s option is invalid.\n", flag.c_str()); return 0; } bool active; if (!this->asBool(castNode, flag, active)) return 0; if (active) skill->delaynodex |= constant; else skill->delaynodex &= ~constant; } } if (this->nodeExists(node, "Requires")) { const auto& requireNode = node["Requires"]; if (this->nodeExists(requireNode, "HpCost")) { if (!this->parseNode("HpCost", "Amount", requireNode, skill->require.hp)) return 0; } else { if (!exists) memset(skill->require.hp, 0, sizeof(skill->require.hp)); } if (this->nodeExists(requireNode, "SpCost")) { if (!this->parseNode("SpCost", "Amount", requireNode, skill->require.sp)) return 0; } else { if (!exists) memset(skill->require.sp, 0, sizeof(skill->require.sp)); } if (this->nodeExists(requireNode, "ApCost")) { if (!this->parseNode("ApCost", "Amount", requireNode, skill->require.ap)) return 0; } else { if (!exists) memset(skill->require.ap, 0, sizeof(skill->require.ap)); } if (this->nodeExists(requireNode, "HpRateCost")) { if (!this->parseNode("HpRateCost", "Amount", requireNode, skill->require.hp_rate)) return 0; } else { if (!exists) memset(skill->require.hp_rate, 0, sizeof(skill->require.hp_rate)); } if (this->nodeExists(requireNode, "SpRateCost")) { if (!this->parseNode("SpRateCost", "Amount", requireNode, skill->require.sp_rate)) return 0; } else { if (!exists) memset(skill->require.sp_rate, 0, sizeof(skill->require.sp_rate)); } if (this->nodeExists(requireNode, "ApRateCost")) { if (!this->parseNode("ApRateCost", "Amount", requireNode, skill->require.ap_rate)) return 0; } else { if (!exists) memset(skill->require.ap_rate, 0, sizeof(skill->require.ap_rate)); } if (this->nodeExists(requireNode, "MaxHpTrigger")) { if (!this->parseNode("MaxHpTrigger", "Amount", requireNode, skill->require.mhp)) return 0; } else { if (!exists) memset(skill->require.mhp, 0, sizeof(skill->require.mhp)); } if (this->nodeExists(requireNode, "ZenyCost")) { if (!this->parseNode("ZenyCost", "Amount", requireNode, skill->require.zeny)) return 0; } else { if (!exists) memset(skill->require.zeny, 0, sizeof(skill->require.zeny)); } if (this->nodeExists(requireNode, "Weapon")) { const auto& weaponNode = requireNode["Weapon"]; if (this->nodeExists(weaponNode, "All")) { bool active; if (!this->asBool(weaponNode, "All", active)) return 0; if (active) skill->require.weapon = 0; } else { for (const auto& it : weaponNode) { std::string weapon; c4::from_chars(it.key(), &weapon); std::string weapon_constant = "W_" + weapon; int64 constant; if (!script_get_constant(weapon_constant.c_str(), &constant)) { this->invalidWarning(weaponNode, "Requires Weapon %s is invalid.\n", weapon.c_str()); return 0; } bool active; if (!this->asBool(weaponNode, weapon, active)) return 0; if (active) skill->require.weapon |= 1 << constant; else skill->require.weapon &= ~(1 << constant); } } } else { if (!exists) skill->require.weapon = 0; } if (this->nodeExists(requireNode, "Ammo")) { const auto& ammoNode = requireNode["Ammo"]; if (this->nodeExists(ammoNode, "None")) { bool active; if (!this->asBool(ammoNode, "None", active)) return 0; if (active) skill->require.ammo = 0; } else { for (const auto& it : ammoNode) { std::string ammo; c4::from_chars(it.key(), &ammo); std::string ammo_constant = "AMMO_" + ammo; int64 constant; if (!script_get_constant(ammo_constant.c_str(), &constant)) { this->invalidWarning(ammoNode, "Requires Ammo %s is invalid.\n", ammo.c_str()); return 0; } bool active; if (!this->asBool(ammoNode, ammo, active)) return 0; if (active) skill->require.ammo |= 1 << constant; else skill->require.ammo &= ~(1 << constant); } } } else { if (!exists) skill->require.ammo = 0; } if (this->nodeExists(requireNode, "AmmoAmount")) { if (skill->require.ammo == 0) { this->invalidWarning(requireNode["AmmoAmount"], "An ammo type is required before specifying ammo amount.\n"); return 0; } if (!this->parseNode("AmmoAmount", "Amount", requireNode, skill->require.ammo_qty)) return 0; } else { if (!exists) memset(skill->require.ammo_qty, 0, sizeof(skill->require.ammo_qty)); } if (this->nodeExists(requireNode, "State")) { std::string state; if (!this->asString(requireNode, "State", state)) return 0; std::string state_constant = "ST_" + state; int64 constant; if (!script_get_constant(state_constant.c_str(), &constant)) { this->invalidWarning(requireNode["State"], "Requires State %s is invalid.\n", state.c_str()); return 0; } skill->require.state = static_cast<int32>(constant); } if (this->nodeExists(requireNode, "Status")) { const auto& statusNode = requireNode["Status"]; for (const auto& it : statusNode) { std::string status; c4::from_chars(it.key(), &status); std::string status_constant = "SC_" + status; int64 constant; if (!script_get_constant(status_constant.c_str(), &constant)) { this->invalidWarning(statusNode, "Requires Status %s is invalid.\n", status.c_str()); return 0; } bool active; if (!this->asBool(statusNode, status, active)) return 0; auto status_exists = util::vector_get(skill->require.status, constant); if (active && status_exists == skill->require.status.end()) skill->require.status.push_back(static_cast<sc_type>(constant)); else if (!active && status_exists != skill->require.status.end()) skill->require.status.erase(status_exists); } } if (this->nodeExists(requireNode, "SpiritSphereCost")) { if (!this->parseNode("SpiritSphereCost", "Amount", requireNode, skill->require.spiritball)) return 0; } else { if (!exists) memset(skill->require.spiritball, 0, sizeof(skill->require.spiritball)); } if (this->nodeExists(requireNode, "ItemCost")) { const auto itemNode = requireNode["ItemCost"]; int32 count = 0; for (const auto& it : itemNode) { std::string item_name; if (!this->asString(it, "Item", item_name)) continue; std::shared_ptr<item_data> item = item_db.search_aegisname( item_name.c_str() ); if (item == nullptr) { this->invalidWarning(it["Item"], "Requires ItemCost Item %s does not exist.\n", item_name.c_str()); return 0; } int32 amount; if (!this->asInt32(it, "Amount", amount)) continue; if (this->nodeExists(it, "Level")) { uint16 cost_level; if (!this->asUInt16(it, "Level", cost_level)) continue; if (cost_level < 1 || cost_level > skill->max) { this->invalidWarning(it["Level"], "Requires ItemCost Level %d is not within %s's level range of 1~%d.\n", cost_level, skill->name, skill->max); return 0; } count = cost_level - 1; if (!skill->require.itemid_level_dependent) skill->require.itemid_level_dependent = true; } skill->require.itemid[count] = item->nameid; skill->require.amount[count] = amount; count++; } } if (this->nodeExists(requireNode, "Equipment")) { const auto& equipNode = requireNode["Equipment"]; for (const auto& it : equipNode) { std::string item_name; c4::from_chars(it.key(), &item_name); std::shared_ptr<item_data> item = item_db.search_aegisname( item_name.c_str() ); if (item == nullptr) { this->invalidWarning(it, "Requires Equipment %s does not exist.\n", item_name.c_str()); return 0; } bool active; if (!this->asBool(equipNode, item_name, active)) return 0; auto equip_exists = util::vector_get(skill->require.eqItem, item->nameid); if (active && equip_exists == skill->require.eqItem.end()) skill->require.eqItem.push_back(item->nameid); else if (!active && equip_exists != skill->require.eqItem.end()) skill->require.eqItem.erase(equip_exists); } } } if (this->nodeExists(node, "GiveAp")) { if (!this->parseNode("GiveAp", "Amount", node, skill->giveap)) return 0; } else { if (!exists) memset(skill->giveap, 0, sizeof(skill->giveap)); } if (this->nodeExists(node, "Unit")) { const auto& unitNode = node["Unit"]; if (this->nodeExists(unitNode, "Id")) { std::string unit; if (!this->asString(unitNode, "Id", unit)) return 0; std::string unit_constant = "UNT_" + unit; int64 constant; if (!script_get_constant(unit_constant.c_str(), &constant)) { this->invalidWarning(unitNode["Id"], "Unit Id %s is invalid.\n", unit.c_str()); return 0; } skill->unit_id = static_cast<uint16>(constant); } else { this->invalidWarning(unitNode["Id"], "Unit requires an Id.\n"); return 0; } if (this->nodeExists(unitNode, "AlternateId")) { std::string unit; if (!this->asString(unitNode, "AlternateId", unit)) return 0; std::string unit_constant = "UNT_" + unit; int64 constant; if (!script_get_constant(unit_constant.c_str(), &constant)) { this->invalidWarning(unitNode["AlternateId"], "Alternate Unit Id %s is invalid.\n", unit.c_str()); return 0; } skill->unit_id2 = static_cast<uint16>(constant); } else { if (!exists) skill->unit_id2 = 0; } if (this->nodeExists(unitNode, "Layout")) { if (!this->parseNode("Layout", "Size", unitNode, skill->unit_layout_type)) return 0; } else { if (!exists) memset(skill->unit_layout_type, 0, sizeof(skill->unit_layout_type)); } if (this->nodeExists(unitNode, "Range")) { if (!this->parseNode("Range", "Size", unitNode, skill->unit_range)) return 0; } else { if (!exists) memset(skill->unit_range, 0, sizeof(skill->unit_range)); } if (this->nodeExists(unitNode, "Interval")) { int16 interval; if (!this->asInt16(unitNode, "Interval", interval)) return 0; skill->unit_interval = interval; } else { if (!exists) skill->unit_interval = 0; } if (this->nodeExists(unitNode, "Target")) { std::string target; if (!this->asString(unitNode, "Target", target)) return 0; std::string target_constant = "BCT_" + target; int64 constant; if (!script_get_constant(target_constant.c_str(), &constant)) { this->invalidWarning(unitNode["Target"], "Unit Target %s is invalid.\n", target.c_str()); return 0; } skill->unit_target = static_cast<int32>(constant); } else { if (!exists) skill->unit_target = BCT_ALL; } if (this->nodeExists(unitNode, "Flag")) { const auto& flagNode = unitNode["Flag"]; for (const auto& it : flagNode) { std::string flag; c4::from_chars(it.key(), &flag); std::string flag_constant = "UF_" + flag; int64 constant; if (!script_get_constant(flag_constant.c_str(), &constant)) { this->invalidWarning(it, "Skill Unit Flag %s is invalid.\n", flag.c_str()); return 0; } bool active; if (!this->asBool(flagNode, flag, active)) return 0; if (active) skill->unit_flag.set(static_cast<uint8>(constant)); else skill->unit_flag.reset(static_cast<uint8>(constant)); } if (skill->unit_flag[UF_NOENEMY] && battle_config.defnotenemy) skill->unit_target = BCT_NOENEMY; // By default, target just characters. skill->unit_target |= BL_CHAR; if (skill->unit_flag[UF_NOPC]) skill->unit_target &= ~BL_PC; if (skill->unit_flag[UF_NOMOB]) skill->unit_target &= ~BL_MOB; if (skill->unit_flag[UF_SKILL]) skill->unit_target |= BL_SKILL; } else { if (!exists){ skill->unit_flag = UF_NONE; // By default, target just characters. skill->unit_target |= BL_CHAR; } } } if (this->nodeExists(node, "Status")) { std::string status; if (!this->asString(node, "Status", status)) return 0; std::string status_constant = "SC_" + status; int64 constant; if (!script_get_constant(status_constant.c_str(), &constant)) { this->invalidWarning(node["Status"], "Status %s is invalid.\n", status.c_str()); return 0; } if (constant < SC_NONE || constant >= SC_MAX) { this->invalidWarning(node["Status"], "Status %s is unknown. Defaulting to SC_NONE.\n", status.c_str()); constant = SC_NONE; } skill->sc = static_cast<sc_type>(constant); } else { if (!exists) skill->sc = SC_NONE; } if (!exists) { this->put(skill_id, skill); this->skilldb_id2idx[skill_id] = this->skill_num; this->skill_num++; } return 1; } void SkillDatabase::clear() { TypesafeCachedYamlDatabase::clear(); memset( this->skilldb_id2idx, 0, sizeof( this->skilldb_id2idx ) ); this->skill_num = 1; } void SkillDatabase::loadingFinished(){ if( this->skill_num > MAX_SKILL ){ ShowError( "There are more skills defined in the skill database (%d) than the MAX_SKILL (%d) define. Please increase it and recompile.\n", this->skill_num, MAX_SKILL ); } TypesafeCachedYamlDatabase::loadingFinished(); } /** * Get skill index from skill_db array. The index is also being used for skill lookup in mmo_charstatus::skill[] * @param skill_id * @param silent If Skill is undefined, show error message! * @return Skill Index or 0 if not found/unset **/ uint16 SkillDatabase::get_index( uint16 skill_id, bool silent, const char *func, const char *file, int line ){ uint16 idx = this->skilldb_id2idx[skill_id]; if( idx == 0 && skill_id != 0 && !silent ){ ShowError( "Skill '%d' is undefined! %s:%d::%s\n", skill_id, file, line, func ); } return idx; } SkillDatabase skill_db; const std::string ReadingSpellbookDatabase::getDefaultLocation() { return std::string(db_path) + "/spellbook_db.yml"; } /** * Reads and parses an entry from the spellbook_db. * @param node: YAML node containing the entry. * @return count of successfully parsed rows */ uint64 ReadingSpellbookDatabase::parseBodyNode(const ryml::NodeRef& node) { std::string skill_name; if (!this->asString(node, "Skill", skill_name)) return 0; uint16 skill_id = skill_name2id(skill_name.c_str()); if (skill_id == 0) { this->invalidWarning(node["Skill"], "Invalid skill name \"%s\", skipping.\n", skill_name.c_str()); return 0; } if (skill_get_inf(skill_id) == INF_PASSIVE_SKILL) { this->invalidWarning(node["Skill"], "Passive skill %s cannot be memorized in a Spell Book.\n", skill_name.c_str()); return 0; } std::shared_ptr<s_skill_spellbook_db> spell = this->find(skill_id); bool exists = spell != nullptr; if (!exists) { if (!this->nodesExist(node, { "Book", "PreservePoints" })) return 0; spell = std::make_shared<s_skill_spellbook_db>(); spell->skill_id = skill_id; } if (this->nodeExists(node, "Book")) { std::string book_name; if (!this->asString(node, "Book", book_name)) return 0; std::shared_ptr<item_data> item = item_db.search_aegisname( book_name.c_str() ); if (item == nullptr) { this->invalidWarning(node["Book"], "Book item %s does not exist.\n", book_name.c_str()); return 0; } spell->nameid = item->nameid; } if (this->nodeExists(node, "PreservePoints")) { uint16 points; if (!this->asUInt16(node, "PreservePoints", points)) return 0; spell->points = points; } if (!exists) this->put(skill_id, spell); return 1; } /** * Check if the specified item is available in the spellbook_db or not * @param nameid: Book Item ID * @return Spell data or nullptr otherwise */ std::shared_ptr<s_skill_spellbook_db> ReadingSpellbookDatabase::findBook(t_itemid nameid) { if (!item_db.exists(nameid) || reading_spellbook_db.empty()) return nullptr; for (const auto &spell : reading_spellbook_db) { if (spell.second->nameid == nameid) return spell.second; } return nullptr; } const std::string MagicMushroomDatabase::getDefaultLocation() { return std::string(db_path) + "/magicmushroom_db.yml"; } /** * Reads and parses an entry from the magicmushroom_db. * @param node: YAML node containing the entry. * @return count of successfully parsed rows */ uint64 MagicMushroomDatabase::parseBodyNode(const ryml::NodeRef& node) { std::string skill_name; if (!this->asString(node, "Skill", skill_name)) return 0; uint16 skill_id = skill_name2id(skill_name.c_str()); if (!skill_id) { this->invalidWarning(node["Skill"], "Invalid Magic Mushroom skill name \"%s\", skipping.\n", skill_name.c_str()); return 0; } if (!skill_get_inf(skill_id)) { this->invalidWarning(node["Skill"], "Passive skill %s cannot be casted by Magic Mushroom.\n", skill_name.c_str()); return 0; } std::shared_ptr<s_skill_magicmushroom_db> mushroom = this->find(skill_id); bool exists = mushroom != nullptr; if (!exists) { mushroom = std::make_shared<s_skill_magicmushroom_db>(); mushroom->skill_id = skill_id; this->put(skill_id, mushroom); } return 1; } /** Reads skill no cast db * Structure: SkillID,Flag */ static bool skill_parse_row_nocastdb(char* split[], int columns, int current) { std::shared_ptr<s_skill_db> skill = skill_db.find(atoi(split[0])); if (!skill) return false; skill->nocast |= atoi(split[1]); return true; } /** Reads Produce db * Structure: ProduceItemID,ItemLV,RequireSkill,Requireskill_lv,MaterialID1,MaterialAmount1,... */ static bool skill_parse_row_producedb(char* split[], int columns, int current) { unsigned short x, y; unsigned short id = atoi(split[0]); t_itemid nameid = 0; bool found = false; if (id >= ARRAYLENGTH(skill_produce_db)) { ShowError("skill_parse_row_producedb: Maximum db entries reached.\n"); return false; } // Clear previous data, for importing support memset(&skill_produce_db[id], 0, sizeof(skill_produce_db[id])); // Import just for clearing/disabling from original data if (!(nameid = strtoul(split[1], nullptr, 10))) { //ShowInfo("skill_parse_row_producedb: Product list with ID %d removed from list.\n", id); return true; } if (!item_db.exists(nameid)) { ShowError("skill_parse_row_producedb: Invalid item %u.\n", nameid); return false; } skill_produce_db[id].nameid = nameid; skill_produce_db[id].itemlv = atoi(split[2]); skill_produce_db[id].req_skill = atoi(split[3]); skill_produce_db[id].req_skill_lv = atoi(split[4]); for (x = 5, y = 0; x+1 < columns && split[x] && split[x+1] && y < MAX_PRODUCE_RESOURCE; x += 2, y++) { skill_produce_db[id].mat_id[y] = strtoul(split[x], nullptr, 10); skill_produce_db[id].mat_amount[y] = atoi(split[x+1]); } if (!found) skill_produce_count++; return true; } const std::string SkillArrowDatabase::getDefaultLocation() { return std::string(db_path) + "/create_arrow_db.yml"; } /** * Reads and parses an entry from the create_arrow_db. * @param node: YAML node containing the entry. * @return count of successfully parsed rows */ uint64 SkillArrowDatabase::parseBodyNode(const ryml::NodeRef& node) { std::string source_name; if (!this->asString(node, "Source", source_name)) return 0; std::shared_ptr<item_data> item = item_db.search_aegisname( source_name.c_str() ); if (item == nullptr) { this->invalidWarning(node["Source"], "Item %s does not exist.\n", source_name.c_str()); return 0; } t_itemid nameid = item->nameid; std::shared_ptr<s_skill_arrow_db> arrow = this->find(nameid); bool exists = arrow != nullptr; if (!exists) { arrow = std::make_shared<s_skill_arrow_db>(); arrow->nameid = nameid; } const auto& MakeNode = node["Make"]; for (const auto &it : MakeNode) { std::string item_name; if (!this->asString(it, "Item", item_name)) return 0; std::shared_ptr<item_data> item = item_db.search_aegisname( item_name.c_str() ); if (item == nullptr) { this->invalidWarning(it["Item"], "Item %s does not exist.\n", item_name.c_str()); return 0; } uint16 amount; if (!this->asUInt16(it, "Amount", amount)) return 0; if (amount == 0) { if (arrow->created.erase(item->nameid) == 0) this->invalidWarning(it["Amount"], "Failed to remove %s, the entry doesn't exist in Source %s.\n", item_name.c_str(), source_name.c_str()); continue; } if (amount > MAX_AMOUNT) { this->invalidWarning(it["Amount"], "Amount %hu exceeds %hu, skipping.\n", amount, MAX_AMOUNT); continue; } arrow->created[item->nameid] = amount; } if (!exists) this->put(nameid, arrow); return 1; } const std::string AbraDatabase::getDefaultLocation() { return std::string(db_path) + "/abra_db.yml"; } /** * Reads and parses an entry from the abra_db. * @param node: YAML node containing the entry. * @return count of successfully parsed rows */ uint64 AbraDatabase::parseBodyNode(const ryml::NodeRef& node) { std::string skill_name; if (!this->asString(node, "Skill", skill_name)) return 0; uint16 skill_id = skill_name2id(skill_name.c_str()); if (!skill_id) { this->invalidWarning(node["Skill"], "Invalid Abra skill name \"%s\", skipping.\n", skill_name.c_str()); return 0; } if (!skill_get_inf(skill_id)) { this->invalidWarning(node["Skill"], "Passive skill %s cannot be casted by Abra.\n", skill_name.c_str()); return 0; } std::shared_ptr<s_skill_abra_db> abra = this->find(skill_id); bool exists = abra != nullptr; if (!exists) { abra = std::make_shared<s_skill_abra_db>(); abra->skill_id = skill_id; } if (this->nodeExists(node, "Probability")) { const auto& probNode = node["Probability"]; uint16 probability; if (!probNode.is_seq()) { if (!this->asUInt16Rate(probNode, "Probability", probability)) return 0; abra->per.fill(probability); } else { abra->per.fill(0); for (const auto& it : probNode) { uint16 skill_lv; if (!this->asUInt16(it, "Level", skill_lv)) continue; if (skill_lv > MAX_SKILL_LEVEL) { this->invalidWarning(probNode["Level"], "Probability Level exceeds the maximum skill level of %d, skipping.\n", MAX_SKILL_LEVEL); return 0; } if (!this->asUInt16Rate(it, "Probability", probability)) continue; abra->per[skill_lv - 1] = probability; } } } else { if (!exists) abra->per.fill(500); } if (!exists) this->put(skill_id, abra); return 1; } /** Reads change material db * Structure: ProductID,BaseRate,MakeAmount1,MakeAmountRate1...,MakeAmount5,MakeAmountRate5 */ static bool skill_parse_row_changematerialdb(char* split[], int columns, int current) { uint16 id = atoi(split[0]); t_itemid nameid = strtoul(split[1], nullptr, 10); short rate = atoi(split[2]); bool found = false; int x, y; if (id >= MAX_SKILL_CHANGEMATERIAL_DB) { ShowError("skill_parse_row_changematerialdb: Maximum amount of entries reached (%d), increase MAX_SKILL_CHANGEMATERIAL_DB\n",MAX_SKILL_CHANGEMATERIAL_DB); return false; } // Clear previous data, for importing support if (skill_changematerial_db[id].nameid > 0) { found = true; memset(&skill_changematerial_db[id], 0, sizeof(skill_changematerial_db[id])); } // Import just for clearing/disabling from original data // NOTE: If import for disabling, better disable list from produce_db instead of here, or creation just failed with deleting requirements. if (nameid == 0) { memset(&skill_changematerial_db[id], 0, sizeof(skill_changematerial_db[id])); //ShowInfo("skill_parse_row_changematerialdb: Change Material list with ID %d removed from list.\n", id); return true; } // Entry must be exists in skill_produce_db and with required skill GN_CHANGEMATERIAL for (x = 0; x < MAX_SKILL_PRODUCE_DB; x++) { if (skill_produce_db[x].nameid == nameid) if( skill_produce_db[x].req_skill == GN_CHANGEMATERIAL ) break; } if (x >= MAX_SKILL_PRODUCE_DB) { ShowError("skill_parse_row_changematerialdb: Not supported item ID (%u) for Change Material. \n", nameid); return false; } skill_changematerial_db[id].nameid = nameid; skill_changematerial_db[id].rate = rate; for (x = 3, y = 0; x+1 < columns && split[x] && split[x+1] && y < MAX_SKILL_CHANGEMATERIAL_SET; x += 2, y++) { skill_changematerial_db[id].qty[y] = atoi(split[x]); skill_changematerial_db[id].qty_rate[y] = atoi(split[x+1]); } if (!found) skill_changematerial_count++; return true; } /** * Reads skill damage adjustment * @author [Lilith] */ static bool skill_parse_row_skilldamage(char* split[], int columns, int current) { int64 caster_tmp; uint16 id; int caster, value; char *result; trim(split[0]); if (ISDIGIT(split[0][0])) { value = strtol(split[0], &result, 10); if (*result) { ShowError("skill_parse_row_skilldamage: Invalid skill %s given, skipping.\n", result); return false; } id = value; } else id = skill_name2id(split[0]); std::shared_ptr<s_skill_db> skill = skill_db.find(id); if (!skill) return false; skill->damage = {}; trim(split[1]); if (ISDIGIT(split[1][0])) { value = strtol(split[1], &result, 10); if (*result) { ShowError("skill_parse_row_skilldamage: Invalid caster %s given for skill %d, skipping.\n", result, id); return false; } caster = value; } else { // Try to parse caster as constant if (!script_get_constant(split[1], &caster_tmp)) { ShowError("skill_parse_row_skilldamage: Invalid caster constant given for skill %d, skipping.\n", id); return false; } caster = static_cast<uint16>(caster_tmp); } skill->damage.caster |= caster; value = strtol(split[2], &result, 10); if (*result) { ShowError("skill_parse_row_skilldamage: Invalid map %s given for skill %d, skipping.\n", result, id); return false; } skill->damage.map |= value; for(int offset = 3, i = SKILLDMG_PC; i < SKILLDMG_MAX && offset < columns; i++, offset++ ){ value = strtol(split[offset], &result, 10); if (*result && *result != ' ') { ShowError("skill_parse_row_skilldamage: Invalid damage %s given for skill %d, defaulting to 0.\n", result, id); value = 0; } skill->damage.rate[i] = cap_value(value, -100, 100000); } return true; } /** Reads skill database files */ static void skill_readdb(void) { int i; const char* dbsubpath[] = { "", "/" DBIMPORT, //add other path here }; memset(skill_produce_db,0,sizeof(skill_produce_db)); memset(skill_changematerial_db,0,sizeof(skill_changematerial_db)); skill_produce_count = skill_changematerial_count = 0; skill_db.load(); for(i=0; i<ARRAYLENGTH(dbsubpath); i++){ size_t n1 = strlen(db_path)+strlen(dbsubpath[i])+1; size_t n2 = strlen(db_path)+strlen(DBPATH)+strlen(dbsubpath[i])+1; char* dbsubpath1 = (char*)aMalloc(n1+1); char* dbsubpath2 = (char*)aMalloc(n2+1); if (i == 0) { safesnprintf(dbsubpath1,n1,"%s%s",db_path,dbsubpath[i]); safesnprintf(dbsubpath2,n2,"%s/%s%s",db_path,DBPATH,dbsubpath[i]); } else { safesnprintf(dbsubpath1,n1,"%s%s",db_path,dbsubpath[i]); safesnprintf(dbsubpath2,n1,"%s%s",db_path,dbsubpath[i]); } sv_readdb(dbsubpath2, "skill_nocast_db.txt" , ',', 2, 2, -1, skill_parse_row_nocastdb, i > 0); sv_readdb(dbsubpath2, "produce_db.txt" , ',', 5, 5+2*MAX_PRODUCE_RESOURCE, MAX_SKILL_PRODUCE_DB, skill_parse_row_producedb, i > 0); sv_readdb(dbsubpath1, "skill_changematerial_db.txt" , ',', 5, 5+2*MAX_SKILL_CHANGEMATERIAL_SET, MAX_SKILL_CHANGEMATERIAL_DB, skill_parse_row_changematerialdb, i > 0); sv_readdb(dbsubpath1, "skill_damage_db.txt" , ',', 4, 3+SKILLDMG_MAX, -1, skill_parse_row_skilldamage, i > 0); aFree(dbsubpath1); aFree(dbsubpath2); } abra_db.load(); magic_mushroom_db.load(); reading_spellbook_db.load(); skill_arrow_db.load(); skill_init_unit_layout(); skill_init_nounit_layout(); } void skill_reload (void) { skill_db.clear(); abra_db.clear(); magic_mushroom_db.clear(); reading_spellbook_db.clear(); skill_arrow_db.clear(); skill_readdb(); /* lets update all players skill tree : so that if any skill modes were changed they're properly updated */ s_mapiterator *iter = mapit_getallusers(); for( map_session_data *sd = (TBL_PC*)mapit_first(iter); mapit_exists(iter); sd = (TBL_PC*)mapit_next(iter) ) { pc_validate_skill(sd); clif_skillinfoblock(sd); } mapit_free(iter); } /*========================================== * *------------------------------------------*/ void do_init_skill(void) { skill_readdb(); skillunit_db = idb_alloc(DB_OPT_BASE); skillusave_db = idb_alloc(DB_OPT_RELEASE_DATA); bowling_db = idb_alloc(DB_OPT_BASE); skill_timer_ers = ers_new(sizeof(struct skill_timerskill),"skill.cpp::skill_timer_ers",ERS_CACHE_OPTIONS); ers_chunk_size(skill_timer_ers, 150); add_timer_func_list(skill_unit_timer,"skill_unit_timer"); add_timer_func_list(skill_castend_id,"skill_castend_id"); add_timer_func_list(skill_castend_pos,"skill_castend_pos"); add_timer_func_list(skill_timerskill,"skill_timerskill"); add_timer_func_list(skill_blockpc_end, "skill_blockpc_end"); add_timer_func_list(skill_keep_using, "skill_keep_using"); add_timer_interval(gettick()+SKILLUNITTIMER_INTERVAL,skill_unit_timer,0,0,SKILLUNITTIMER_INTERVAL); } void do_final_skill(void) { skill_db.clear(); abra_db.clear(); magic_mushroom_db.clear(); reading_spellbook_db.clear(); skill_arrow_db.clear(); db_destroy(skillunit_db); db_destroy(skillusave_db); db_destroy(bowling_db); ers_destroy(skill_timer_ers); }
  2. Hi everyone! I seem to have a bug on tarot high priest card. It does not dispel. It happened when I added extended pvp script. Any idea what went wrong. Thanks!
  3. Hi. I tried this my server Crashes when i use @go outside the xy xy area.
  4. Anyone here experienced using a GM Account and cannot trade bounded items? Thanks!
  5. Above here? May I ask what you mean with Character's Group shouldn't have the warp anywhere permission. Thank you!
  6. I can still use @go 0 when inside the pvp cell..
  7. I would like to ask if it is possible or doable to disable warping on certain cell. I have my PvP Cell in the middle of Prontera but I can still use the command @go # and can warp to it prontera # #.. Is it possible to disable certain cell. Thanks!
  8. I would like to add if it is possible to make it work like this.. AS_SONICBLOW,BL_PC,2,-50 // Decrease Sonic Blow Damage on PVP Maps. AS_SONICBLOW,BL_PC,4,50 // Increase Sonic Blow Damage on GvG Maps. I just want to configure the damage on PvP Maps and Gvg Maps. The Damage on PvP maps will be reduced while the damage on Gvg maps will be increased. Thanks!
  9. case MO_BODYRELOCATION: if(sc && (sc->getSCE(SC_SPIDERWEB) || sc->getSCE(SC_ANKLE))) break; if (unit_movepos(src, x, y, 2, 1)) { Hello. Solved it! Thanks @pajodex @Katakuri
  10. This does not work. That is what is in my attached picture.
  11. Hello! I am currently using the latest rAthena and this seems to be NOT working. I just want to ask if why I am getting this error. Thank you so much!
×
×
  • Create New...