Parcourir la source

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

Playtester il y a 9 ans
Parent
commit
6ebcb67601

+ 1 - 1
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

+ 1 - 0
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

+ 1 - 1
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

+ 1 - 0
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

+ 81 - 58
src/map/skill.c

@@ -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
@@ -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
+				{
+					//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) {
@@ -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);
+	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:
@@ -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);
 

+ 1 - 0
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);

+ 11 - 2
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);

+ 6 - 0
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);