Skip to content

Commit

Permalink
Waterball, Jupitel Thunder and Mystical Amplification behavior improved
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
Playtester committed Jan 17, 2016
1 parent bbe9601 commit 6ebcb67
Show file tree
Hide file tree
Showing 8 changed files with 103 additions and 62 deletions.
2 changes: 1 addition & 1 deletion db/pre-re/skill_cast_db.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions db/pre-re/skill_unit_db.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion db/re/skill_cast_db.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions db/re/skill_unit_db.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
139 changes: 81 additions & 58 deletions src/map/skill.c
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
#include <math.h>

#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
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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];

Expand Down Expand Up @@ -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) */
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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
{
//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
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);
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
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) {
Expand All @@ -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
Expand All @@ -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 },
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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);
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:
Expand Down Expand Up @@ -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) )
{
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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]);
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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);

Expand Down
1 change: 1 addition & 0 deletions src/map/skill.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
13 changes: 11 additions & 2 deletions src/map/status.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
6 changes: 6 additions & 0 deletions src/map/unit.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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);

Expand Down

0 comments on commit 6ebcb67

Please sign in to comment.