فهرست منبع

No cell stacking implemented (official version) (bugreport:9378)
- Split config cell_stack_limit into custom_cell_stack_limit (previous feature) and official_cell_stack_limit (see below)
- Expanded map_count_oncell by a flag parameter, currently only one flag is supported: only count standing units (needed for official cell stack feature)
- Added a new function map_closest_freecell that will return the closest free cell using the same order that official servers use
- Monsters will now actively search for a free cell when starting to walk randomly and when unlocking target
- When any unit finishes walking (regularly) and is not on a free cell, it will now actively search for a free cell
- Step actions will be delayed until a suitable cell was found, they will even be executed when the player walked slightly out of attack range

Playtester 10 سال پیش
والد
کامیت
9d50c57e8a
11فایلهای تغییر یافته به همراه158 افزوده شده و 34 حذف شده
  1. 9 4
      conf/battle/misc.conf
  2. 2 2
      src/config/core.h
  3. 4 3
      src/map/battle.c
  4. 2 1
      src/map/battle.h
  5. 105 9
      src/map/map.c
  6. 2 1
      src/map/map.h
  7. 5 1
      src/map/mob.c
  8. 0 4
      src/map/path.c
  9. 3 3
      src/map/skill.c
  10. 25 6
      src/map/unit.c
  11. 1 0
      src/map/unit.h

+ 9 - 4
conf/battle/misc.conf

@@ -88,10 +88,15 @@ duel_time_interval: 60
 // Restrict duel usage to same map
 duel_only_on_same_map: no
 
-// Determines max number of characters that can stack within a single cell.
-// NOTE: For this setting to make effect you have to use a server compiled with 
-// Cell Stack Limit support (see src/map/map.h)
-cell_stack_limit: 1
+// Determines max number of characters that can stack within a single cell. 
+// Official - Only affects the walking routines of characters, including monsters.
+// If a unit stops walking and is on a cell with more than stack limit
+// characters on it, it will walk to the closest free cell.
+// Custom - This variation will make every full cell to be considered a wall.
+// NOTE: For this setting to take effect you have to use a server compiled
+// with Cell Stack Limit support (see src/map/map.h)
+official_cell_stack_limit: 1
+custom_cell_stack_limit: 1
 
 // Allow autotrade only in maps with autotrade flag?
 // Set this to "no" to allow autotrade where no "autotrade" mapflag is set.

+ 2 - 2
src/config/core.h

@@ -37,8 +37,8 @@
 //#define BETA_THREAD_TEST
 
 /// Uncomment to enable the Cell Stack Limit mod.
-/// It's only config is the battle_config cell_stack_limit.
-/// Only chars affected are those defined in BL_CHAR (mobs and players currently)
+/// It's only config is the battle_config custom_cell_stack_limit.
+/// Only chars affected are those defined in BL_CHAR
 //#define CELL_NOSTACK
 
 /// Uncomment to enable circular area checks.

+ 4 - 3
src/map/battle.c

@@ -7671,7 +7671,8 @@ static const struct _battle_data {
 	{ "bone_drop",                          &battle_config.bone_drop,                       0,      0,      2,              },
 	{ "buyer_name",                         &battle_config.buyer_name,                      1,      0,      1,              },
 	{ "skill_wall_check",                   &battle_config.skill_wall_check,                1,      0,      1,              },
-	{ "cell_stack_limit",                   &battle_config.cell_stack_limit,                1,      1,      255,            },
+	{ "official_cell_stack_limit",          &battle_config.official_cell_stack_limit,       1,      1,      255,            },
+	{ "custom_cell_stack_limit",            &battle_config.custom_cell_stack_limit,         1,      1,      255,            },
 	{ "dancing_weaponswitch_fix",           &battle_config.dancing_weaponswitch_fix,        1,      0,      1,              },
 
 	// eAthena additions
@@ -8150,8 +8151,8 @@ void battle_adjust_conf()
 #endif
 
 #ifndef CELL_NOSTACK
-	if (battle_config.cell_stack_limit != 1)
-		ShowWarning("Battle setting 'cell_stack_limit' takes no effect as this server was compiled without Cell Stack Limit support.\n");
+	if (battle_config.custom_cell_stack_limit != 1)
+		ShowWarning("Battle setting 'custom_cell_stack_limit' takes no effect as this server was compiled without Cell Stack Limit support.\n");
 #endif
 }
 

+ 2 - 1
src/map/battle.h

@@ -432,7 +432,8 @@ extern struct Battle_Config
 	int allow_skill_without_day; // [Komurka]
 	int allow_es_magic_pc; // [Skotlex]
 	int skill_wall_check; // [Skotlex]
-	int cell_stack_limit; // [Skotlex]
+	int official_cell_stack_limit; // [Playtester]
+	int custom_cell_stack_limit; // [Skotlex]
 	int skill_caster_check; // [Skotlex]
 	int sc_castcancel; // [Skotlex]
 	int pc_sc_def_rate; // [Skotlex]

+ 105 - 9
src/map/map.c

@@ -488,8 +488,10 @@ int map_moveblock(struct block_list *bl, int x1, int y1, unsigned int tick)
 
 /*==========================================
  * Counts specified number of objects on given cell.
+ * flag:
+ *		0x1 - only count standing units
  *------------------------------------------*/
-int map_count_oncell(int16 m, int16 x, int16 y, int type)
+int map_count_oncell(int16 m, int16 x, int16 y, int type, int flag)
 {
 	int bx,by;
 	struct block_list *bl;
@@ -503,16 +505,31 @@ int map_count_oncell(int16 m, int16 x, int16 y, int type)
 
 	if (type&~BL_MOB)
 		for( bl = map[m].block[bx+by*map[m].bxs] ; bl != NULL ; bl = bl->next )
-			if(bl->x == x && bl->y == y && bl->type&type)
-				count++;
+			if(bl->x == x && bl->y == y && bl->type&type) {
+				if(flag&1) {
+					struct unit_data *ud = unit_bl2ud(bl);
+					if(!ud || ud->walktimer == INVALID_TIMER)
+						count++;
+				} else {
+					count++;
+				}
+			}
 
 	if (type&BL_MOB)
 		for( bl = map[m].block_mob[bx+by*map[m].bxs] ; bl != NULL ; bl = bl->next )
-			if(bl->x == x && bl->y == y)
-				count++;
+			if(bl->x == x && bl->y == y) {
+				if(flag&1) {
+					struct unit_data *ud = unit_bl2ud(bl);
+					if(!ud || ud->walktimer == INVALID_TIMER)
+						count++;
+				} else {
+					count++;
+				}
+			}
 
 	return count;
 }
+
 /*
  * Looks for a skill unit on a given cell
  * flag&1: runs battle_check_target check based on unit->group->target_flag
@@ -1332,7 +1349,7 @@ int map_searchrandfreecell(int16 m,int16 *x,int16 *y,int stack) {
 			if(map_getcell(m,j+*x,i+*y,CELL_CHKNOPASS) && !map_getcell(m,j+*x,i+*y,CELL_CHKICEWALL))
 				continue;
 			//Avoid item stacking to prevent against exploits. [Skotlex]
-			if(stack && map_count_oncell(m,j+*x,i+*y, BL_ITEM) > stack)
+			if(stack && map_count_oncell(m,j+*x,i+*y, BL_ITEM, 0) > stack)
 				continue;
 			free_cells[free_cell][0] = j+*x;
 			free_cells[free_cell++][1] = i+*y;
@@ -1428,6 +1445,85 @@ int map_search_freecell(struct block_list *src, int16 m, int16 *x,int16 *y, int1
 	return 0;
 }
 
+/*==========================================
+ * Locates the closest, walkable cell with no blocks of a certain type on it
+ * Returns true on success and sets x and y to cell found.
+ * Otherwise returns false and x and y are not changed.
+ * type: Types of block to count
+ * flag: 
+ *		0x1 - only count standing units
+ *------------------------------------------*/
+bool map_closest_freecell(int16 m, int16 *x, int16 *y, int type, int flag)
+{
+	uint8 dir = 6;
+	int16 tx = *x;
+	int16 ty = *y;
+	int costrange = 10;
+
+	if(!map_count_oncell(m, tx, ty, type, flag))
+		return true; //Current cell is free
+
+	//Algorithm only works up to costrange of 34
+	while(costrange <= 34) {
+		short dx = dirx[dir];
+		short dy = diry[dir];
+
+		//Linear search
+		if(dir%2 == 0 && costrange%MOVE_COST == 0) {
+			tx = *x+dx*(costrange/MOVE_COST);
+			ty = *y+dy*(costrange/MOVE_COST);
+			if(!map_count_oncell(m, tx, ty, type, flag) && map_getcell(m,tx,ty,CELL_CHKPASS)) {
+				*x = tx;
+				*y = ty;
+				return true;
+			}
+		} 
+		//Full diagonal search
+		else if(dir%2 == 1 && costrange%MOVE_DIAGONAL_COST == 0) {
+			tx = *x+dx*(costrange/MOVE_DIAGONAL_COST);
+			ty = *y+dy*(costrange/MOVE_DIAGONAL_COST);
+			if(!map_count_oncell(m, tx, ty, type, flag) && map_getcell(m,tx,ty,CELL_CHKPASS)) {
+				*x = tx;
+				*y = ty;
+				return true;
+			}
+		}
+		//One cell diagonal, rest linear (TODO: Find a better algorithm for this)
+		else if(dir%2 == 1 && costrange%MOVE_COST == 4) {
+			tx = *x+dx*((dir%4==3)?(costrange/MOVE_COST):1);
+			ty = *y+dy*((dir%4==1)?(costrange/MOVE_COST):1);
+			if(!map_count_oncell(m, tx, ty, type, flag) && map_getcell(m,tx,ty,CELL_CHKPASS)) {
+				*x = tx;
+				*y = ty;
+				return true;
+			}
+			tx = *x+dx*((dir%4==1)?(costrange/MOVE_COST):1);
+			ty = *y+dy*((dir%4==3)?(costrange/MOVE_COST):1);
+			if(!map_count_oncell(m, tx, ty, type, flag) && map_getcell(m,tx,ty,CELL_CHKPASS)) {
+				*x = tx;
+				*y = ty;
+				return true;
+			}
+		}
+
+		//Get next direction
+		if (dir == 5) {
+			//Diagonal search complete, repeat with higher cost range
+			if(costrange == 14) costrange += 6;
+			else if(costrange == 28 || costrange >= 38) costrange += 2;
+			else costrange += 4;
+			dir = 6;
+		} else if (dir == 4) {
+			//Linear search complete, switch to diagonal directions
+			dir = 7;
+		} else {
+			dir = (dir+2)%8;
+		}
+	}
+
+	return false;
+}
+
 /*==========================================
  * Add an item in floor to location (m,x,y) and add restriction for those who could pickup later
  * NB : If charids are null their no restriction for pickup
@@ -2745,21 +2841,21 @@ int map_getcellp(struct map_data* m,int16 x,int16 y,cell_chk cellchk)
 		// special checks
 		case CELL_CHKPASS:
 #ifdef CELL_NOSTACK
-			if (cell.cell_bl >= battle_config.cell_stack_limit) return 0;
+			if (cell.cell_bl >= battle_config.custom_cell_stack_limit) return 0;
 #endif
 		case CELL_CHKREACH:
 			return (cell.walkable);
 
 		case CELL_CHKNOPASS:
 #ifdef CELL_NOSTACK
-			if (cell.cell_bl >= battle_config.cell_stack_limit) return 1;
+			if (cell.cell_bl >= battle_config.custom_cell_stack_limit) return 1;
 #endif
 		case CELL_CHKNOREACH:
 			return (!cell.walkable);
 
 		case CELL_CHKSTACK:
 #ifdef CELL_NOSTACK
-			return (cell.cell_bl >= battle_config.cell_stack_limit);
+			return (cell.cell_bl >= battle_config.custom_cell_stack_limit);
 #else
 			return 0;
 #endif

+ 2 - 1
src/map/map.h

@@ -776,11 +776,12 @@ int map_foreachincell(int (*func)(struct block_list*,va_list), int16 m, int16 x,
 int map_foreachinpath(int (*func)(struct block_list*,va_list), int16 m, int16 x0, int16 y0, int16 x1, int16 y1, int16 range, int length, int type, ...);
 int map_foreachinmap(int (*func)(struct block_list*,va_list), int16 m, int type, ...);
 //blocklist nb in one cell
-int map_count_oncell(int16 m,int16 x,int16 y,int type);
+int map_count_oncell(int16 m,int16 x,int16 y,int type,int flag);
 struct skill_unit *map_find_skill_unit_oncell(struct block_list *,int16 x,int16 y,uint16 skill_id,struct skill_unit *, int flag);
 // search and creation
 int map_get_new_object_id(void);
 int map_search_freecell(struct block_list *src, int16 m, int16 *x, int16 *y, int16 rx, int16 ry, int flag);
+bool map_closest_freecell(int16 m, int16 *x, int16 *y, int type, int flag);
 //
 int map_quit(struct map_session_data *);
 // npc

+ 5 - 1
src/map/mob.c

@@ -1321,6 +1321,10 @@ int mob_unlocktarget(struct mob_data *md, unsigned int tick)
 		md->ud.target_to = 0;
 		unit_set_target(&md->ud, 0);
 	}
+	if(map_count_oncell(md->bl.m, md->bl.x, md->bl.y, BL_CHAR|BL_NPC, 1) > battle_config.official_cell_stack_limit) {
+		unit_walktoxy(&md->bl, md->bl.x, md->bl.y, 8);
+	}
+
 	return 0;
 }
 /*==========================================
@@ -1349,7 +1353,7 @@ int mob_randomwalk(struct mob_data *md,unsigned int tick)
 		x+=md->bl.x;
 		y+=md->bl.y;
 
-		if(((x != md->bl.x) || (y != md->bl.y)) && map_getcell(md->bl.m,x,y,CELL_CHKPASS) && unit_walktoxy(&md->bl,x,y,0)){
+		if(((x != md->bl.x) || (y != md->bl.y)) && map_getcell(md->bl.m,x,y,CELL_CHKPASS) && unit_walktoxy(&md->bl,x,y,8)){
 			break;
 		}
 	}

+ 0 - 4
src/map/path.c

@@ -263,12 +263,8 @@ bool path_search(struct walkpath_data *wpd, int16 m, int16 x0, int16 y0, int16 x
 
 	md = &map[m];
 
-#ifdef CELL_NOSTACK
 	//Do not check starting cell as that would get you stuck.
-	if (x0 < 0 || x0 >= md->xs || y0 < 0 || y0 >= md->ys)
-#else
 	if (x0 < 0 || x0 >= md->xs || y0 < 0 || y0 >= md->ys /*|| map_getcellp(md,x0,y0,cell)*/)
-#endif
 		return false;
 
 	// Check destination cell

+ 3 - 3
src/map/skill.c

@@ -7377,7 +7377,7 @@ int skill_castend_nodamage_id (struct block_list *src, struct block_list *bl, ui
 			}
 
 			clif_skill_nodamage(src,bl,TK_HIGHJUMP,skill_lv,1);
-			if(!map_count_oncell(src->m,x,y,BL_PC|BL_NPC|BL_MOB) && map_getcell(src->m,x,y,CELL_CHKREACH) && unit_movepos(src, x, y, 1, 0))
+			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;
@@ -11233,7 +11233,7 @@ int skill_castend_pos2(struct block_list* src, int x, int y, uint16 skill_id, ui
 	// Plant Cultivation [Celest]
 	case CR_CULTIVATION:
 		if (sd) {
-			if( map_count_oncell(src->m,x,y,BL_CHAR) > 0 )
+			if( map_count_oncell(src->m,x,y,BL_CHAR,0) > 0 )
 			{
 				clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0);
 				return 1;
@@ -12663,7 +12663,7 @@ int skill_unit_onplace_timer(struct skill_unit *unit, struct block_list *bl, uns
 		ts->tick = tick+sg->interval;
 
 		if ((skill_id==CR_GRANDCROSS || skill_id==NPC_GRANDDARKNESS) && !battle_config.gx_allhit)
-			ts->tick += sg->interval*(map_count_oncell(bl->m,bl->x,bl->y,BL_CHAR)-1);
+			ts->tick += sg->interval*(map_count_oncell(bl->m,bl->x,bl->y,BL_CHAR,0)-1);
 	}
 
 	// Wall of Thorn damaged by Fire element unit [Cydh]

+ 25 - 6
src/map/unit.c

@@ -298,7 +298,7 @@ int unit_step_timer(int tid, unsigned int tick, int id, intptr_t data)
 		}
 		if(ud->stepskill_id == 0) {
 			//Execute normal attack
-			unit_attack(bl, tbl->id, ud->state.attack_continue);
+			unit_attack(bl, tbl->id, (ud->state.attack_continue) + 2);
 		} else {
 			//Execute non-ground skill
 			unit_skilluse_id(bl, tbl->id, ud->stepskill_id, ud->stepskill_lv);
@@ -314,7 +314,7 @@ int unit_step_timer(int tid, unsigned int tick, int id, intptr_t data)
  * @param tid: Timer ID
  * @param tick: Current tick to decide next timer update
  * @param data: Data used in timer calls
- * @return 0 or unit_walktoxy_sub()
+ * @return 0 or unit_walktoxy_sub() or unit_walktoxy()
  */
 static int unit_walktoxy_timer(int tid, unsigned int tick, int id, intptr_t data)
 {
@@ -518,6 +518,16 @@ static int unit_walktoxy_timer(int tid, unsigned int tick, int id, intptr_t data
 	} else { // Stopped walking. Update to_x and to_y to current location [Skotlex]
 		ud->to_x = bl->x;
 		ud->to_y = bl->y;
+
+		if(map_count_oncell(bl->m, x, y, BL_CHAR|BL_NPC, 1) > battle_config.official_cell_stack_limit) {
+			//Walked on occupied cell, call unit_walktoxy again
+			if(ud->steptimer != INVALID_TIMER) {
+				//Execute step timer on next step instead
+				delete_timer(ud->steptimer, unit_step_timer);
+				ud->steptimer = INVALID_TIMER;
+			}
+			return unit_walktoxy(bl, x, y, 8);
+		}
 	}
 
 	return 0;
@@ -576,6 +586,7 @@ int unit_delay_walktobl_timer(int tid, unsigned int tick, int id, intptr_t data)
  *	&1: Easy walk (fail if CELL_CHKNOPASS is in direct path)
  *	&2: Force walking (override can_move)
  *	&4: Delay walking for can_move
+ *  &8: Search for an unoccupied cell and cancel if none available
  * @return 1: Success 0: Fail or unit_walktoxy_sub()
  */
 int unit_walktoxy( struct block_list *bl, short x, short y, unsigned char flag)
@@ -595,6 +606,9 @@ int unit_walktoxy( struct block_list *bl, short x, short y, unsigned char flag)
 	if (bl->type == BL_PC)
 		sd = BL_CAST(BL_PC, bl);
 
+	if ((flag&8) && !map_closest_freecell(bl->m, &x, &y, BL_CHAR|BL_NPC, 1)) //This might change x and y
+		return 0;
+
 	if (!path_search(&wpd, bl->m, bl->x, bl->y, x, y, flag&1, CELL_CHKNOPASS)) // Count walk path cells
 		return 0;
 
@@ -819,7 +833,7 @@ bool unit_run(struct block_list *bl, struct map_session_data *sd, enum sc_type t
 			break;
 
 		// If sprinting and there's a PC/Mob/NPC, block the path [Kevin]
-		if(map_count_oncell(bl->m, to_x + dir_x, to_y + dir_y, BL_PC|BL_MOB|BL_NPC))
+		if(map_count_oncell(bl->m, to_x + dir_x, to_y + dir_y, BL_PC|BL_MOB|BL_NPC, 0))
 			break;
 
 		to_x += dir_x;
@@ -2147,6 +2161,7 @@ int unit_unattackable(struct block_list *bl)
 
 	if (ud) {
 		ud->state.attack_continue = 0;
+		ud->state.step_attack = 0;
 		ud->target_to = 0;
 		unit_set_target(ud, 0);
 	}
@@ -2163,7 +2178,9 @@ int unit_unattackable(struct block_list *bl)
  * Requests a unit to attack a target
  * @param src: Object initiating attack
  * @param target_id: Target ID (bl->id)
- * @param continuous: Whether or not the attack is ongoing
+ * @param continuous: 
+ *		0x1 - Whether or not the attack is ongoing
+ *		0x2 - Whether function was called from unit_step_timer or not
  * @return Success(0); Fail(1);
  */
 int unit_attack(struct block_list *src,int target_id,int continuous)
@@ -2204,7 +2221,8 @@ int unit_attack(struct block_list *src,int target_id,int continuous)
 		return 1;
 	}
 
-	ud->state.attack_continue = continuous;
+	ud->state.attack_continue = (continuous&1)?1:0;
+	ud->state.step_attack = (continuous&2)?1:0;
 	unit_set_target(ud, target_id);
 
 	range = status_get_range(src);
@@ -2493,7 +2511,8 @@ static int unit_attack_timer_sub(struct block_list* src, int tid, unsigned int t
 	sstatus = status_get_status_data(src);
 	range = sstatus->rhw.range;
 
-	if( unit_is_walking(target) && (target->type == BL_PC || !map_getcell(target->m,target->x,target->y,CELL_CHKICEWALL)) )
+	if( (unit_is_walking(target) || ud->state.step_attack)
+		&& (target->type == BL_PC || !map_getcell(target->m,target->x,target->y,CELL_CHKICEWALL)) )
 		range++; // Extra range when chasing (does not apply to mobs locked in an icewall)
 
 	if(sd && !check_distance_client_bl(src,target,range)) {

+ 1 - 0
src/map/unit.h

@@ -47,6 +47,7 @@ struct unit_data {
 		unsigned change_walk_target : 1 ;
 		unsigned skillcastcancel : 1 ;
 		unsigned attack_continue : 1 ;
+		unsigned step_attack : 1;
 		unsigned walk_easy : 1 ;
 		unsigned running : 1;
 		unsigned speed_changed : 1;