From 6ebcb67601117f0e6a39aad67a1a4de11c9b8d86 Mon Sep 17 00:00:00 2001 From: Playtester Date: Sun, 17 Jan 2016 18:00:18 +0100 Subject: [PATCH] Waterball, Jupitel Thunder and Mystical Amplification behavior improved - Implemented Waterball and Jupitel Thunder double cast (fixes #907) * Waterball and Jupitel now won't apply a delay after they have been cast and won't notify the client about the spell until 150ms later, if another skill request is received, it will actually be cast as well * To prevent exploits this behavior can only be used every 2 seconds * This allows you to cast a spell after Jupitel that would not have enough range to be cast after knockback - Finally implemented the official waterball unit behavior * When casting Waterball, water, deluge and suiton units will be turned into waterball units * These waterball units are then turned into waterballs one-by-one * All unit behavior now also applies to waterball, special handling is now solved via db files * If there are multiple waterball timers active at the same time, they will actually compete for the waterball units * When waterball does not deal damage (100% resist), it will now cancel, allowing you to act and move again * If this breaks anything, please notify me - Mystical Amplification improved (fixes #908) * Mystical Amplification will now toggle at cast begin rather than cast end * When Mystical Amplification ends, spell damage will be immediately lower even for ongoing spells --- db/pre-re/skill_cast_db.txt | 2 +- db/pre-re/skill_unit_db.txt | 1 + db/re/skill_cast_db.txt | 2 +- db/re/skill_unit_db.txt | 1 + src/map/skill.c | 143 +++++++++++++++++++++--------------- src/map/skill.h | 1 + src/map/status.c | 13 +++- src/map/unit.c | 6 ++ 8 files changed, 105 insertions(+), 64 deletions(-) diff --git a/db/pre-re/skill_cast_db.txt b/db/pre-re/skill_cast_db.txt index 2dcacef467..10276618ec 100644 --- a/db/pre-re/skill_cast_db.txt +++ b/db/pre-re/skill_cast_db.txt @@ -165,7 +165,7 @@ //-- WZ_VERMILION 85,15000:14500:14000:13500:13000:12500:12000:11500:11000:10500,5000,0,4000,5500:6000:6500:7000:7500:8000:8500:9000:9500:10000,0 //-- WZ_WATERBALL -86,1000:2000:3000:4000:5000:6000:7000:8000:9000:10000,0,0,0,0,0 +86,1000:2000:3000:4000:5000:6000:7000:8000:9000:10000,0,0,10000,0,0 //-- WZ_ICEWALL 87,0,0,0,5000:10000:15000:20000:25000:30000:35000:40000:45000:50000,0,0 //-- WZ_FROSTNOVA diff --git a/db/pre-re/skill_unit_db.txt b/db/pre-re/skill_unit_db.txt index ac09c3cde3..ae2ccd6846 100644 --- a/db/pre-re/skill_unit_db.txt +++ b/db/pre-re/skill_unit_db.txt @@ -42,6 +42,7 @@ 80,0x87,0x88, 0, 1,2000,enemy, 0x4006 //WZ_FIREPILLAR 83,0x86, , 0, 3,1000,enemy, 0x010 //WZ_METEOR 85,0x86, , 5, 1:1:1:1:1:1:1:1:1:1:3,1250,enemy,0x018 //WZ_VERMILION + 86,0x8c, , 0:1:1:2:2:2:2:2:2:2, 0,-1,noone, 0x010 //WZ_WATERBALL 87,0x8d, , -1, 0, -1,all, 0x9010 //WZ_ICEWALL 89,0x86, , 4, 1, 450,enemy, 0x018 //WZ_STORMGUST 91,0x86, , 2, 0,1000,enemy, 0x010 //WZ_HEAVENDRIVE diff --git a/db/re/skill_cast_db.txt b/db/re/skill_cast_db.txt index 101cb3d2d6..49d4bddbc4 100644 --- a/db/re/skill_cast_db.txt +++ b/db/re/skill_cast_db.txt @@ -166,7 +166,7 @@ //-- WZ_VERMILION 85,9600:9280:8960:8640:8320:8000:7680:7360:7040:6720,5000,0,4000,5500:6000:6500:7000:7500:8000:8500:9000:9500:10000,0,2400:2320:2240:2160:2080:2000:1920:1840:1760:1680 //-- WZ_WATERBALL -86,640:1280:1920:2560:3200,0,0,0,0,0,160:320:480:640:800 +86,640:1280:1920:2560:3200,0,0,10000,0,0,160:320:480:640:800 //-- WZ_ICEWALL 87,0,0,0,5000:10000:15000:20000:25000:30000:35000:40000:45000:50000,0,0,0 //-- WZ_FROSTNOVA diff --git a/db/re/skill_unit_db.txt b/db/re/skill_unit_db.txt index a76fcf5e3e..6a6258b989 100644 --- a/db/re/skill_unit_db.txt +++ b/db/re/skill_unit_db.txt @@ -42,6 +42,7 @@ 80,0x87,0x88, 0, 1,2000,enemy, 0x4006 //WZ_FIREPILLAR 83,0x86, , 0, 3,1000,enemy, 0x010 //WZ_METEOR 85,0x86, , 5, 1:1:1:1:1:1:1:1:1:1:3,1250,enemy,0x018 //WZ_VERMILION + 86,0x8c, , 0:1:1:2:2:2:2:2:2:2, 0,-1,noone, 0x010 //WZ_WATERBALL 87,0x8d, , -1, 0, -1,all, 0x9010 //WZ_ICEWALL 89,0x86, , 4, 1, 450,enemy, 0x018 //WZ_STORMGUST 91,0x86, , 2, 0,1000,enemy, 0x010 //WZ_HEAVENDRIVE diff --git a/src/map/skill.c b/src/map/skill.c index f9f4734919..5bab65c6af 100755 --- a/src/map/skill.c +++ b/src/map/skill.c @@ -42,7 +42,7 @@ #include #define SKILLUNITTIMER_INTERVAL 100 -#define WATERBALL_INTERVAL 150 +#define TIMERSKILL_INTERVAL 150 // ranges reserved for mapping skill ids to skilldb offsets #define HM_SKILLRANGEMIN 700 @@ -315,7 +315,6 @@ int skill_attack_area(struct block_list *bl,va_list ap); struct 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 void skill_toggle_magicpower(struct block_list *bl, uint16 skill_id); 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,struct skill_unit_group *sg,int tick); @@ -880,6 +879,14 @@ struct s_skill_unit_layout *skill_get_unit_layout(uint16 skill_id, uint16 skill_ 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]; @@ -3292,6 +3299,10 @@ int64 skill_attack (int attack_type, struct block_list* src, struct block_list * case RL_SLUGSHOT: dmg.dmotion = clif_skill_damage(dsrc,bl,tick,status_get_amotion(src),dmg.dmotion,damage,dmg.div_,skill_id,-1,5); break; + case WZ_WATERBALL: + if (damage > 0) + dmg.dmotion = clif_skill_damage(dsrc, bl, tick, dmg.amotion, dmg.dmotion, damage, dmg.div_, skill_id, flag&SD_LEVEL ? -1 : skill_lv, 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) */ @@ -3819,6 +3830,7 @@ static int skill_timerskill(int tid, unsigned int tick, int id, intptr_t data) 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); @@ -3887,17 +3899,35 @@ static int skill_timerskill(int tid, unsigned int tick, int id, intptr_t data) map_foreachinrange(skill_area_sub, src, skill_get_splash(skl->skill_id, skl->skill_lv), splash_target(src), src, skl->skill_id, skl->skill_lv, tick, skl->flag, skill_castend_damage_id); break; case WZ_WATERBALL: - skill_toggle_magicpower(src, skl->skill_id); // only the first hit will be amplify - // Official behaviour is to hit as long as there is a line of sight, regardless of distance - if (!status_isdead(target) && path_search_long(NULL,src->m,src->x,src->y,target->x,target->y,CELL_CHKNOREACH)) { - //Apply canact delay here to prevent hacks (unlimited waterball casting) - ud->canact_tick = tick + skill_delayfix(src, skl->skill_id, skl->skill_lv); - skill_attack(BF_MAGIC,src,src,target,skl->skill_id,skl->skill_lv,tick,skl->flag); + { + //Get the next waterball cell to consume + struct s_skill_unit_layout *layout; + layout = skill_get_unit_layout(skl->skill_id, skl->skill_lv, src, skl->x, skl->y); + for (int 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; } - if (skl->type>1 && !status_isdead(target) && !status_isdead(src)) { + } // 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_CHKNOREACH)) { + // Apply canact delay here to prevent hacks (unlimited casting) + ud->canact_tick = tick + skill_delayfix(src, skl->skill_id, skl->skill_lv); + if (!skill_attack(BF_MAGIC, src, src, target, skl->skill_id, skl->skill_lv, tick, skl->flag) && skl->type > 1) { + // If skill doesn't deal damage, no new timer is created + unit = NULL; + } + } + if (unit && !status_isdead(target) && !status_isdead(src)) { + if(skl->type > 0) + skill_toggle_magicpower(src, skl->skill_id); // Only the first hit will be amplified + 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, WATERBALL_INTERVAL, 1); - skill_addtimerskill(src,tick+WATERBALL_INTERVAL,target->id,0,0,skl->skill_id,skl->skill_lv,skl->type-1,skl->flag); + 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 { struct status_change *sc = status_get_sc(src); if(sc) { @@ -3910,7 +3940,7 @@ static int skill_timerskill(int tid, unsigned int tick, int id, intptr_t data) break; case WL_CHAINLIGHTNING_ATK: { skill_attack(BF_MAGIC,src,src,target,skl->skill_id,skl->skill_lv,tick,skl->flag); // Hit a Lightning on the current Target - skill_toggle_magicpower(src, skl->skill_id); // only the first hit will be amplify + skill_toggle_magicpower(src, skl->skill_id); // Only the first hit will be amplified if( skl->type < (4 + skl->skill_lv - 1) && skl->x < 3 ) { // Remaining Chains Hit struct block_list *nbl = NULL; // Next Target of Chain @@ -3930,7 +3960,7 @@ static int skill_timerskill(int tid, unsigned int tick, int id, intptr_t data) 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_ANIMATION); - skill_toggle_magicpower(src, skl->skill_id); // only the first hit will be amplify + skill_toggle_magicpower(src, skl->skill_id); // Only the first hit will be amplified if (skl->type >= 3) { // Final Hit if (!status_isdead(target)) { // Final Status Effect int effects[4] = { SC_BURNING, SC_FREEZING, SC_BLEEDING, SC_STUN }, @@ -4803,7 +4833,6 @@ int skill_castend_damage_id (struct block_list* src, struct block_list *bl, uint case WZ_EARTHSPIKE: case AL_HEAL: case AL_HOLYLIGHT: - case WZ_JUPITEL: case NPC_DARKTHUNDER: case PR_ASPERSIO: case MG_FROSTDIVER: @@ -4842,43 +4871,15 @@ int skill_castend_damage_id (struct block_list* src, struct block_list *bl, uint skill_attack(BF_MAGIC,src,src,bl,sid,skill_lv,tick,flag|SD_LEVEL); } break; + case WZ_WATERBALL: - { - int range = skill_lv / 2; - int maxlv = skill_get_max(skill_id); // learnable level - int count = 0; - int x, y; - struct skill_unit* unit; - - if( skill_lv > maxlv ) - { - if( src->type == BL_MOB && skill_lv == 10 ) - range = 4; - else - range = maxlv / 2; - } - - for( y = src->y - range; y <= src->y + range; ++y ) - for( x = src->x - range; x <= src->x + range; ++x ) - { - if( !map_find_skill_unit_oncell(src,x,y,SA_LANDPROTECTOR,NULL,1) ) - { - if( src->type != BL_PC || map_getcell(src->m,x,y,CELL_CHKWATER) ) // non-players bypass the water requirement - count++; // natural water cell - else if( (unit = map_find_skill_unit_oncell(src,x,y,SA_DELUGE,NULL,1)) != NULL || (unit = map_find_skill_unit_oncell(src,x,y,NJ_SUITON,NULL,1)) != NULL ) - { - count++; // skill-induced water cell - skill_delunit(unit); // consume cell - } - } - } - - if( count > (10000/WATERBALL_INTERVAL)+1 ) //Waterball has a max duration of 10 seconds [Playtester] - count = (10000/WATERBALL_INTERVAL)+1; - if( count > 1 ) // queue the remaining count - 1 timerskill Waterballs - skill_addtimerskill(src,tick+WATERBALL_INTERVAL,bl->id,0,0,skill_id,skill_lv,count-1,flag); - } - skill_attack(BF_MAGIC,src,src,bl,skill_id,skill_lv,tick,flag); + //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: @@ -5189,8 +5190,6 @@ int skill_castend_damage_id (struct block_list* src, struct block_list *bl, uint // Get the requirement for the preserved skill skill_consume_requirement(sd, pres_skill_id, pres_skill_lv, 1); - // SC_MAGICPOWER needs to switch states before any damage is actually dealt - skill_toggle_magicpower(src, pres_skill_id); switch( skill_get_casttype(pres_skill_id) ) { @@ -5698,7 +5697,22 @@ int skill_castend_damage_id (struct block_list* src, struct block_list *bl, uint if( sd && !(flag&1) ) {// ensure that the skill last-cast tick is recorded - sd->canskill_tick = gettick(); + tick = gettick(); + switch (skill_id) { + //These skill don't call skill_attack right away and allow to cast a second spell before the first skill deals damage + case WZ_JUPITEL: + case WZ_WATERBALL: + //Only allow the double-cast trick every 2000ms to prevent hacks + if (DIFF_TICK(tick, sd->canskill_tick) > 2000) { + sd->ud.canact_tick = tick; + sd->canskill_tick = tick-2000+TIMERSKILL_INTERVAL; + break; + } + //Fall through + default: + sd->canskill_tick = tick; + break; + } if( sd->state.arrow_atk ) {// consume arrow on last invocation to this skill. @@ -10979,9 +10993,6 @@ int skill_castend_id(int tid, unsigned int tick, int id, intptr_t data) map_freeblock_lock(); - // SC_MAGICPOWER needs to switch states before any damage is actually dealt - skill_toggle_magicpower(src, ud->skill_id); - // only normal attack and auto cast skills benefit from its bonuses if(!(skill_get_inf3(ud->skill_id)&INF3_NOENDCAMOUFLAGE)) status_change_end(src,SC_CAMOUFLAGE, INVALID_TIMER); @@ -11273,9 +11284,6 @@ int skill_castend_pos2(struct block_list* src, int x, int y, uint16 skill_id, ui clif_skill_poseffect(src,skill_id,skill_lv,x,y,tick); } - // SC_MAGICPOWER needs to switch states before any damage is actually dealt - skill_toggle_magicpower(src, skill_id); - switch(skill_id) { case PR_BENEDICTIO: @@ -12708,6 +12716,12 @@ struct skill_unit_group *skill_unitsetting(struct block_list *src, uint16 skill_ unit_val1 = (skill_lv <= 1) ? 500 : 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]); @@ -17009,6 +17023,15 @@ static int skill_cell_overlap(struct block_list *bl, va_list ap) break; } break; + case WZ_WATERBALL: + switch (unit->group->skill_id) { + case SA_DELUGE: + case NJ_SUITON: + //Consumes deluge/suiton + skill_delunit(unit); + return 1; + } + //Fall through case WZ_ICEWALL: case HP_BASILICA: case HW_GRAVITATION: @@ -19135,7 +19158,7 @@ int skill_poisoningweapon(struct map_session_data *sd, unsigned short nameid) return 0; } -static void skill_toggle_magicpower(struct block_list *bl, uint16 skill_id) +void skill_toggle_magicpower(struct block_list *bl, uint16 skill_id) { struct status_change *sc = status_get_sc(bl); diff --git a/src/map/skill.h b/src/map/skill.h index 4d13db0ce6..de133cbf13 100644 --- a/src/map/skill.h +++ b/src/map/skill.h @@ -445,6 +445,7 @@ int skill_castfix_sc(struct block_list *bl, double time); int skill_vfcastfix(struct block_list *bl, double time, uint16 skill_id, uint16 skill_lv); #endif int skill_delayfix(struct block_list *bl, uint16 skill_id, uint16 skill_lv); +void skill_toggle_magicpower(struct block_list *bl, uint16 skill_id); // Skill conditions check and remove [Inkfish] bool skill_check_condition_castbegin(struct map_session_data *sd, uint16 skill_id, uint16 skill_lv); diff --git a/src/map/status.c b/src/map/status.c index ec21292b81..5104c16b61 100644 --- a/src/map/status.c +++ b/src/map/status.c @@ -11672,8 +11672,17 @@ int status_change_end_(struct block_list* bl, enum sc_type type, int tid, const clif_changelook(bl,LOOK_BODY2,cap_value(sd->status.body,0,battle_config.max_body_style)); } } - if (calc_flag) - status_calc_bl(bl,calc_flag); + if (calc_flag) { + switch (type) { + case SC_MAGICPOWER: + //If Mystical Amplification ends, MATK is immediately recalculated + status_calc_bl_(bl, calc_flag, SCO_FORCE); + break; + default: + status_calc_bl(bl, calc_flag); + break; + } + } if(opt_flag&4) // Out of hiding, invoke on place. skill_unit_move(bl,gettick(),1); diff --git a/src/map/unit.c b/src/map/unit.c index 698be4302b..f4bacf7742 100644 --- a/src/map/unit.c +++ b/src/map/unit.c @@ -1815,6 +1815,9 @@ int unit_skilluse_id2(struct block_list *src, int target_id, uint16 skill_id, ui if(!ud->state.running) // Need TK_RUN or WUGDASH handler to be done before that, see bugreport:6026 unit_stop_walking(src, 1); // Even though this is not how official works but this will do the trick. bugreport:6829 + // SC_MAGICPOWER needs to switch states at start of cast + skill_toggle_magicpower(src, skill_id); + // In official this is triggered even if no cast time. clif_skillcasting(src, src->id, target_id, 0,0, skill_id, skill_get_ele(skill_id, skill_lv), casttime); @@ -2065,6 +2068,9 @@ int unit_skilluse_pos2( struct block_list *src, short skill_x, short skill_y, ui unit_stop_walking(src,1); + // SC_MAGICPOWER needs to switch states at start of cast + skill_toggle_magicpower(src, skill_id); + // In official this is triggered even if no cast time. clif_skillcasting(src, src->id, 0, skill_x, skill_y, skill_id, skill_get_ele(skill_id, skill_lv), casttime);