|
@@ -42,7 +42,7 @@
|
|
#include <math.h>
|
|
#include <math.h>
|
|
|
|
|
|
#define SKILLUNITTIMER_INTERVAL 100
|
|
#define SKILLUNITTIMER_INTERVAL 100
|
|
-#define WATERBALL_INTERVAL 150
|
|
|
|
|
|
+#define TIMERSKILL_INTERVAL 150
|
|
|
|
|
|
// ranges reserved for mapping skill ids to skilldb offsets
|
|
// ranges reserved for mapping skill ids to skilldb offsets
|
|
#define HM_SKILLRANGEMIN 700
|
|
#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]
|
|
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_graffitiremover(struct block_list *bl, va_list ap); // [Valaris]
|
|
int skill_greed(struct block_list *bl, va_list ap);
|
|
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_cell_overlap(struct block_list *bl, va_list ap);
|
|
static int skill_trap_splash(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);
|
|
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
|
|
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
|
|
if (pos != -1) // simple single-definition layout
|
|
return &skill_unit_layout[pos];
|
|
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:
|
|
case RL_SLUGSHOT:
|
|
dmg.dmotion = clif_skill_damage(dsrc,bl,tick,status_get_amotion(src),dmg.dmotion,damage,dmg.div_,skill_id,-1,5);
|
|
dmg.dmotion = clif_skill_damage(dsrc,bl,tick,status_get_amotion(src),dmg.dmotion,damage,dmg.div_,skill_id,-1,5);
|
|
break;
|
|
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_MELEE:
|
|
case AB_DUPLELIGHT_MAGIC:
|
|
case AB_DUPLELIGHT_MAGIC:
|
|
dmg.amotion = 300;/* makes the damage value not overlap with previous damage (when displayed by the client) */
|
|
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 block_list *src = map_id2bl(id),*target;
|
|
struct unit_data *ud = unit_bl2ud(src);
|
|
struct unit_data *ud = unit_bl2ud(src);
|
|
struct skill_timerskill *skl;
|
|
struct skill_timerskill *skl;
|
|
|
|
+ struct skill_unit *unit = NULL;
|
|
int range;
|
|
int range;
|
|
|
|
|
|
nullpo_ret(src);
|
|
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);
|
|
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;
|
|
break;
|
|
case WZ_WATERBALL:
|
|
case WZ_WATERBALL:
|
|
- skill_toggle_magicpower(src, skl->skill_id); // only the first hit will be amplify
|
|
|
|
|
|
+ {
|
|
|
|
+ //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;
|
|
|
|
+ }
|
|
|
|
+ } // Fall through
|
|
|
|
+ case WZ_JUPITEL:
|
|
// Official behaviour is to hit as long as there is a line of sight, regardless of distance
|
|
// 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)
|
|
|
|
|
|
+ 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);
|
|
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);
|
|
|
|
|
|
+ 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 (skl->type>1 && !status_isdead(target) && !status_isdead(src)) {
|
|
|
|
|
|
+ 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
|
|
//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 {
|
|
} else {
|
|
struct status_change *sc = status_get_sc(src);
|
|
struct status_change *sc = status_get_sc(src);
|
|
if(sc) {
|
|
if(sc) {
|
|
@@ -3910,7 +3940,7 @@ static int skill_timerskill(int tid, unsigned int tick, int id, intptr_t data)
|
|
break;
|
|
break;
|
|
case WL_CHAINLIGHTNING_ATK: {
|
|
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_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 )
|
|
if( skl->type < (4 + skl->skill_lv - 1) && skl->x < 3 )
|
|
{ // Remaining Chains Hit
|
|
{ // Remaining Chains Hit
|
|
struct block_list *nbl = NULL; // Next Target of Chain
|
|
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:
|
|
case WL_TETRAVORTEX_GROUND:
|
|
clif_skill_nodamage(src,target,skl->skill_id,skl->skill_lv,1);
|
|
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_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 (skl->type >= 3) { // Final Hit
|
|
if (!status_isdead(target)) { // Final Status Effect
|
|
if (!status_isdead(target)) { // Final Status Effect
|
|
int effects[4] = { SC_BURNING, SC_FREEZING, SC_BLEEDING, SC_STUN },
|
|
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 WZ_EARTHSPIKE:
|
|
case AL_HEAL:
|
|
case AL_HEAL:
|
|
case AL_HOLYLIGHT:
|
|
case AL_HOLYLIGHT:
|
|
- case WZ_JUPITEL:
|
|
|
|
case NPC_DARKTHUNDER:
|
|
case NPC_DARKTHUNDER:
|
|
case PR_ASPERSIO:
|
|
case PR_ASPERSIO:
|
|
case MG_FROSTDIVER:
|
|
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);
|
|
skill_attack(BF_MAGIC,src,src,bl,sid,skill_lv,tick,flag|SD_LEVEL);
|
|
}
|
|
}
|
|
break;
|
|
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);
|
|
|
|
|
|
+ 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;
|
|
break;
|
|
|
|
|
|
case PR_BENEDICTIO:
|
|
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
|
|
// Get the requirement for the preserved skill
|
|
skill_consume_requirement(sd, pres_skill_id, pres_skill_lv, 1);
|
|
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) )
|
|
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) )
|
|
if( sd && !(flag&1) )
|
|
{// ensure that the skill last-cast tick is recorded
|
|
{// 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 )
|
|
if( sd->state.arrow_atk )
|
|
{// consume arrow on last invocation to this skill.
|
|
{// 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();
|
|
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
|
|
// only normal attack and auto cast skills benefit from its bonuses
|
|
if(!(skill_get_inf3(ud->skill_id)&INF3_NOENDCAMOUFLAGE))
|
|
if(!(skill_get_inf3(ud->skill_id)&INF3_NOENDCAMOUFLAGE))
|
|
status_change_end(src,SC_CAMOUFLAGE, INVALID_TIMER);
|
|
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);
|
|
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)
|
|
switch(skill_id)
|
|
{
|
|
{
|
|
case PR_BENEDICTIO:
|
|
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_val1 = (skill_lv <= 1) ? 500 : 200 + 200*skill_lv;
|
|
unit_val2 = map_getcell(src->m, ux, uy, CELL_GETTYPE);
|
|
unit_val2 = map_getcell(src->m, ux, uy, CELL_GETTYPE);
|
|
break;
|
|
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:
|
|
case GS_DESPERADO:
|
|
unit_val1 = abs(layout->dx[i]);
|
|
unit_val1 = abs(layout->dx[i]);
|
|
unit_val2 = abs(layout->dy[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;
|
|
}
|
|
}
|
|
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 WZ_ICEWALL:
|
|
case HP_BASILICA:
|
|
case HP_BASILICA:
|
|
case HW_GRAVITATION:
|
|
case HW_GRAVITATION:
|
|
@@ -19135,7 +19158,7 @@ int skill_poisoningweapon(struct map_session_data *sd, unsigned short nameid)
|
|
return 0;
|
|
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);
|
|
struct status_change *sc = status_get_sc(bl);
|
|
|
|
|