Skip to content

Commit

Permalink
Spawn and Free Cell Search Behavior (#8324)
Browse files Browse the repository at this point in the history
- When searching for a map-wide free cell, the tiles 15 cells from the edge are no longer considered
  * Added a configuration to change the edge size to any value between 1 and 40
- When searching for a free cell, the tiles 4-5 cells from the edge are now considered invalid and trigger a retry
  * If you make the edge size smaller than this, it will use edge size instead
- Searching for a free cell now defaults to 50 tries, but if the "no spawn on player" option is active, those failed attempts are not counted towards the limit anymore
- When a monster spawns in a defined area there will now be 8 attempts to spawn it on a valid cell within the area and then one attempt on the center cell; if all 9 attempts fail, there will now be 50 tries to spawn it map-wide before it gives up
- When a monster has fixed spawn coordinates, but those coordinates are a wall, it will now spawn in a random location map-wide instead
  * This also applies to icewall blocking the cell unless the boss_monster command was used
- Each monster in an area spawn will now receive its own spawn center within the spawn area on server start
  * This results in the spawn area being larger but having a bias towards the center
  * Added a configuration to disable this behavior
- Fixed slave monsters always being active and constantly calling the "search freecell" function even though neither them nor their master have been spotted yet
- Fixed map server crash when setting no_spawn_on_player to 100 (follow-up to 33b2b02)
- Updated prontera field spawns to official episode 18+
- Updated all champion mob respawn times to 3 minutes and sorted them by map name
- Fixes #8300
  • Loading branch information
Playtester committed May 19, 2024
1 parent 949a330 commit 5d232db
Show file tree
Hide file tree
Showing 13 changed files with 473 additions and 397 deletions.
7 changes: 7 additions & 0 deletions conf/battle/misc.conf
Original file line number Diff line number Diff line change
Expand Up @@ -183,3 +183,10 @@ mail_delay: 1000

// Hides items from the player's favorite tab from being sold to a NPC. (Note 1)
hide_fav_sell: no

// When searching for a random position on the map, how much of the border of the map shall not be considered?
// Officially the 15 tiles from the edge of the map on each side are not considered as target cells.
// On some maps like in Pyramids this causes there to be very few monsters in the outer areas. This also
// affects teleportation. Set this to 1 if you want it to be closer to the old emulator behavior.
// Valid values: 1-40
map_edge_size: 15
9 changes: 9 additions & 0 deletions conf/battle/monster.conf
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,15 @@ no_spawn_on_player: 0
// map regardless of what the mob-spawn file says.
force_random_spawn: no

// Should each monster's center cell be randomized? (Note 1)
// Officially, at server start, each monster's center cell is set to a random cell in the spawn area.
// Each time the monster spawns it will spawn in an area around its center cell rather than the
// original center of the spawn definition. This results in a much larger total spawn area and a
// different experience each server start.
// Set this to "no" if you want all monsters of a spawn to spawn around the original center of the
// spawn definition, making the total spawn area much smaller (old eAthena behavior).
randomize_center_cell: yes

// Do summon slaves inherit the passive/aggressive traits of their master?
// 0: No, retain original mode.
// 1: Slaves are always aggressive.
Expand Down
25 changes: 21 additions & 4 deletions doc/script_commands.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//===== rAthena Documentation================================
//===== rAthena Documentation================================
//= rAthena Script Commands
//===== By:==================================================
//= rAthena Dev Team
Expand Down Expand Up @@ -135,11 +135,26 @@ and 'doc/mapflags.txt'.
<map name>{,<x>{,<y>{,<xs>{,<ys>}}}}%TAB%monster%TAB%<monster name>{,<monster level>}%TAB%<mob id>,<amount>{,<delay1>{,<delay2>{,<event>{,<mob size>{,<mob ai>}}}}}

Map name is the name of the map the monsters will spawn on. x,y are the
coordinates where the mob should spawn. If xs and ys are non-zero, they
specify the 'radius' of a spawn-rectangle area centered at x,y.
Putting zeros instead of these coordinates will spawn the monsters randomly.
coordinates where the mob should spawn. Putting zeros instead of these
coordinates will spawn the monsters randomly.

If the coordinates are non-zero and xs and ys are above 1, each monster will
spawn in a radius around x,y. At server start, each monster will get assigned
its own center cell adding or substracting up to (xs-1),(ys-1) from the given
x,y coordinates. Each time a monster respawns, it will spawn in a radius around
its personal center cell by adding or substracting up to (xs-1),(ys-1) again.
This results in a total possible spawn area over all monsters of the spawn line
of (xs,ys)*4-3, but with a strong bias towards the center of that area:
2,2 - 5x5
3,3 - 9x9
4,4 - 13x13
5,5 - 17x17
etc.

Note this is only the initial spawn zone, as mobs random-walk, they are free
to move away from their specified spawn region.
You can disable the picking of a random center cell for each monster in the
battle config. (See /conf/battle/monster.conf::randomize_center_cell.)

Monster name is the name the monsters will have on screen, and has no relation
whatsoever to their names anywhere else. It's the mob id that counts, which
Expand Down Expand Up @@ -188,6 +203,8 @@ Natural enemies for AI monsters are normal monsters.

Alternately, a monster spawned using 'boss_monster' instead of 'monster' is able
to be detected on the map with the SC_BOSSMAPINFO status (used by Convex Mirror).
A boss monster spawn with fixed coordinates will always spawn at the given
coordinates, even if they are blocked (e.g. by Icewall).

** NPC names

Expand Down
641 changes: 323 additions & 318 deletions npc/re/mobs/championmobs.txt

Large diffs are not rendered by default.

54 changes: 23 additions & 31 deletions npc/re/mobs/fields/prontera.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@
//===== By: ==================================================
//= Athena (1.0)
//===== Current Version: =====================================
//= 1.7
//= 1.8
//===== Additional Comments: =================================
//= 1.2 fixed some map name typos [Lupus]
//= 1.3 Official kRO 10.1 spawns [Playtester]
//= 1.4 More accurate spawns [Playtester]
//= 1.5 Updated to Renewal spawns.
//= 1.6 Added Prontera Field 8 duplicates. [Euphy]
//= 1.7 Correct Spawn by Navigation's mob data. [attackjom]
//= 1.8 Official 18.1 Field 8 spawns, duplicates removed [Playtester]
//============================================================

//==================================================
Expand Down Expand Up @@ -90,36 +91,27 @@ prt_fild07,225,110,5,5 monster Black Mushroom 1084,3,360000,180000
//==================================================
// prt_fild08 - Prontera Field
//==================================================
prt_fild08,0,0 monster Poring 1002,110,5000
prt_fild08,0,0 monster Lunatic 1063,110,5000
prt_fild08,0,0 monster Fabre 1007,88,5000
prt_fild08,0,0 monster Little Poring 2398,33,50000
prt_fild08,0,0 monster Pupa 1008,66,5000

//==================================================
// prt_fild08 - Duplicates
//==================================================
prt_fild08a,0,0 monster Poring 1002,110,5000
prt_fild08a,0,0 monster Lunatic 1063,110,5000
prt_fild08a,0,0 monster Fabre 1007,88,5000
prt_fild08a,0,0 monster Little Poring 2398,33,50000
prt_fild08a,0,0 monster Pupa 1008,66,5000
prt_fild08b,0,0 monster Poring 1002,110,5000
prt_fild08b,0,0 monster Lunatic 1063,110,5000
prt_fild08b,0,0 monster Fabre 1007,88,5000
prt_fild08b,0,0 monster Little Poring 2398,33,50000
prt_fild08b,0,0 monster Pupa 1008,66,5000
prt_fild08c,0,0 monster Poring 1002,110,5000
prt_fild08c,0,0 monster Lunatic 1063,110,5000
prt_fild08c,0,0 monster Fabre 1007,88,5000
prt_fild08c,0,0 monster Little Poring 2398,33,50000
prt_fild08c,0,0 monster Pupa 1008,66,5000
prt_fild08d,0,0 monster Poring 1002,110,5000
prt_fild08d,0,0 monster Lunatic 1063,110,5000
prt_fild08d,0,0 monster Fabre 1007,88,5000
prt_fild08d,0,0 monster Little Poring 2398,33,50000
prt_fild08d,0,0 monster Pupa 1008,66,5000

prt_fild08,0,0 monster Poring 1002,20,5000

This comment has been minimized.

Copy link
@Pokye

Pokye May 19, 2024

Contributor

the quantities of mobs do not match the divine-pride information.
https://www.divine-pride.net/database/map/prt_fild08

This comment has been minimized.

Copy link
@Playtester

Playtester May 19, 2024

Author Member

Yeah, they seem bugged on Divine Pride.
(Edit: I informed DP that their amount display is bugged.)

prt_fild08,0,0 monster Lunatic 1063,20,5000
prt_fild08,0,0 monster Fabre 1007,10,5000
prt_fild08,305,233,10,10 monster Poring 1002,2,15000
prt_fild08,305,233,10,10 monster Lunatic 1063,2,15000
prt_fild08,305,233,10,10 monster Fabre 1007,2,15000
prt_fild08,271,249,20,20 monster Poring 1002,5,15000
prt_fild08,271,249,20,20 monster Lunatic 1063,5,15000
prt_fild08,271,249,20,20 monster Fabre 1007,5,15000
prt_fild08,94,335,50,50 monster Poring 1002,20,10000
prt_fild08,166,334,50,50 monster Poring 1002,20,10000
prt_fild08,253,337,50,50 monster Poring 1002,20,10000
prt_fild08,228,230,30,30 monster Lunatic 1063,10,10000
prt_fild08,246,263,50,50 monster Lunatic 1063,10,10000
prt_fild08,190,237,50,50 monster Lunatic 1063,10,10000
prt_fild08,100,256,50,50 monster Lunatic 1063,10,10000
prt_fild08,70,164,70,70 monster Fabre 1007,20,10000
prt_fild08,144,147,70,70 monster Fabre 1007,20,10000
prt_fild08,263,79,90,90 monster Fabre 1007,20,10000
prt_fild08,336,113,40,40 monster Pupa 1008,20,10000
prt_fild08,330,269,40,40 monster Little Poring 2398,20,10000

//==================================================
// prt_fild09 - Prontera Field
Expand Down
2 changes: 2 additions & 0 deletions src/map/battle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11497,6 +11497,8 @@ static const struct _battle_data {

{ "mob_respawn_time", &battle_config.mob_respawn_time, 1000, 1000, INT_MAX, },
{ "mob_unlock_time", &battle_config.mob_unlock_time, 2000, 0, INT_MAX, },
{ "map_edge_size", &battle_config.map_edge_size, 15, 1, 40, },
{ "randomize_center_cell", &battle_config.randomize_center_cell, 1, 0, 1, },

{ "feature.stylist", &battle_config.feature_stylist, 1, 0, 1, },
{ "feature.banking_state_enforce", &battle_config.feature_banking_state_enforce, 0, 0, 1, },
Expand Down
2 changes: 2 additions & 0 deletions src/map/battle.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -749,6 +749,8 @@ struct Battle_Config

int mob_respawn_time;
int mob_unlock_time;
int map_edge_size;
int randomize_center_cell;

int feature_stylist;
int feature_banking_state_enforce;
Expand Down
35 changes: 19 additions & 16 deletions src/map/map.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1699,9 +1699,9 @@ static int map_count_sub(struct block_list *bl,va_list ap)
* &2 = the target should be able to walk to the target tile.
* &4 = there shouldn't be any players around the target tile (use the no_spawn_on_player setting)
*------------------------------------------*/
int map_search_freecell(struct block_list *src, int16 m, int16 *x,int16 *y, int16 rx, int16 ry, int flag)
int map_search_freecell(struct block_list *src, int16 m, int16 *x, int16 *y, int16 rx, int16 ry, int flag, int32 tries)
{
int tries, spawn=0;
int spawn=0;
int bx, by;

if( !src && (!(flag&1) || flag&2) )
Expand Down Expand Up @@ -1731,21 +1731,23 @@ int map_search_freecell(struct block_list *src, int16 m, int16 *x,int16 *y, int1
return 0;
}

if (rx >= 0 && ry >= 0) {
tries = (rx * 2 + 1) * (ry * 2 + 1);
if (tries > 100) tries = 100;
} else {
tries = mapdata->xs*mapdata->ys;
if (tries > 500) tries = 500;
}

int16 edge = battle_config.map_edge_size;
int16 edge_valid = std::min(edge, (int16)5);
// In most situations there are 50 tries officially (default value)
while(tries--) {
*x = (rx >= 0) ? rnd_value(bx - rx, bx + rx) : rnd_value<int16>(1, mapdata->xs - 1);
*y = (ry >= 0) ? rnd_value(by - ry, by + ry) : rnd_value<int16>(1, mapdata->ys - 1);
// For map-wide search, the configured tiles from the edge are not considered (default: 15)
*x = (rx >= 0) ? rnd_value(bx - rx, bx + rx) : rnd_value<int16>(edge, mapdata->xs - edge - 1);
*y = (ry >= 0) ? rnd_value(by - ry, by + ry) : rnd_value<int16>(edge, mapdata->ys - edge - 1);

if (*x == bx && *y == by)
continue; //Avoid picking the same target tile.

// Cells outside the map or within 4-5 cells of the map edge are considered invalid officially
// Note that unlike the other edge, this isn't symmetric (NE - 4 cells, SW - 5 cells)
// If for some reason edge size was set to below 5 cells, we consider them as valid
if (*x < edge_valid || *x > mapdata->xs - edge_valid || *y < edge_valid || *y > mapdata->ys - edge_valid)
continue;

if (map_getcell(m,*x,*y,CELL_CHKREACH))
{
if(flag&2 && !unit_can_reach_pos(src, *x, *y, 1))
Expand All @@ -1754,10 +1756,11 @@ int map_search_freecell(struct block_list *src, int16 m, int16 *x,int16 *y, int1
if (spawn >= 100) return 0; //Limit of retries reached.
if (spawn++ < battle_config.no_spawn_on_player &&
map_foreachinallarea(map_count_sub, m,
*x-AREA_SIZE, *y-AREA_SIZE,
*x+AREA_SIZE, *y+AREA_SIZE, BL_PC)
)
continue;
*x - AREA_SIZE, *y - AREA_SIZE,
*x + AREA_SIZE, *y + AREA_SIZE, BL_PC)) {
tries++; // This failure should not affect the number of official tries
continue;
}
}
return 1;
}
Expand Down
2 changes: 1 addition & 1 deletion src/map/map.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1124,7 +1124,7 @@ 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);
int map_search_freecell(struct block_list *src, int16 m, int16 *x, int16 *y, int16 rx, int16 ry, int flag, int32 tries = 50);
bool map_closest_freecell(int16 m, int16 *x, int16 *y, int type, int flag);
//
int map_quit(map_session_data *);
Expand Down
51 changes: 37 additions & 14 deletions src/map/mob.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,8 @@ struct mob_data* mob_spawn_dataset(struct spawn_data *data)
md->spawn_timer = INVALID_TIMER;
md->deletetimer = INVALID_TIMER;
md->skill_idx = -1;
md->centerX = data->x;
md->centerY = data->y;
status_set_viewdata(&md->bl, md->mob_id);
status_change_init(&md->bl);
unit_dataset(&md->bl);
Expand Down Expand Up @@ -1084,11 +1086,12 @@ int mob_setdelayspawn(struct mob_data *md)
int mob_count_sub(struct block_list *bl, va_list ap) {
int mobid[10], i;
ARR_FIND(0, 10, i, (mobid[i] = va_arg(ap, int)) == 0); //fetch till 0
if (mobid[0]) { //if there one let's check it otherwise go backward
TBL_MOB *md = BL_CAST(BL_MOB, bl);
mob_data* md = BL_CAST(BL_MOB, bl);
if (md && mobid[0]) { //if there one let's check it otherwise go backward
ARR_FIND(0, 10, i, md->mob_id == mobid[i]);
return (i < 10) ? 1 : 0;
}
// If not counting monsters, count all
return 1; //backward compatibility
}

Expand Down Expand Up @@ -1116,20 +1119,33 @@ int mob_spawn (struct mob_data *md)

if (md->spawn) { //Respawn data
md->bl.m = md->spawn->m;
md->bl.x = md->spawn->x;
md->bl.y = md->spawn->y;

if( (md->bl.x == 0 && md->bl.y == 0) || md->spawn->xs || md->spawn->ys )
{ //Monster can be spawned on an area.
if( !map_search_freecell(&md->bl, -1, &md->bl.x, &md->bl.y, md->spawn->xs, md->spawn->ys, battle_config.no_spawn_on_player?4:0) )
{ // retry again later
if( md->spawn_timer != INVALID_TIMER )
delete_timer(md->spawn_timer, mob_delayspawn);
md->spawn_timer = add_timer(tick + battle_config.mob_respawn_time,mob_delayspawn,md->bl.id,0);
return 1;
md->bl.x = md->centerX;
md->bl.y = md->centerY;

// Search can be skipped for boss monster spawns if spawn location is fixed
// We can't skip normal monsters as they should pick a random location if the cell is blocked (e.g. Icewall)
// The center cell has a 1/(xs*ys) chance to be picked if free
if ((!md->spawn->state.boss || (md->bl.x == 0 && md->bl.y == 0) || md->spawn->xs != 1 || md->spawn->ys != 1)
&& (md->spawn->xs + md->spawn->ys < 1 || !rnd_chance(1, md->spawn->xs * md->spawn->ys) || !map_getcell(md->bl.m, md->bl.x, md->bl.y, CELL_CHKREACH)))
{
// Officially the area is split into 4 squares, 4 lines and 1 dot and for each of those there is one attempt
// We simplify this to trying 8 times in the whole area and then at the center cell even though that's not fully accurate

// Try to spawn monster in defined area (8 tries)
if (!map_search_freecell(&md->bl, -1, &md->bl.x, &md->bl.y, md->spawn->xs-1, md->spawn->ys-1, battle_config.no_spawn_on_player?4:0, 8))
{
// If area search failed and center cell not reachable, try to spawn the monster anywhere on the map (50 tries)
if (!map_getcell(md->bl.m, md->bl.x, md->bl.y, CELL_CHKREACH) && !map_search_freecell(&md->bl, -1, &md->bl.x, &md->bl.y, -1, -1, battle_config.no_spawn_on_player?4:0))
{
// Retry again later
if (md->spawn_timer != INVALID_TIMER)
delete_timer(md->spawn_timer, mob_delayspawn);
md->spawn_timer = add_timer(tick + battle_config.mob_respawn_time, mob_delayspawn, md->bl.id, 0);
return 1;
}
}
}
else if( battle_config.no_spawn_on_player > 99 && map_foreachinallrange(mob_count_sub, &md->bl, AREA_SIZE, BL_PC) )
if( battle_config.no_spawn_on_player > 99 && map_foreachinallrange(mob_count_sub, &md->bl, AREA_SIZE, BL_PC) )
{ // retry again later (players on sight)
if( md->spawn_timer != INVALID_TIMER )
delete_timer(md->spawn_timer, mob_delayspawn);
Expand Down Expand Up @@ -2091,6 +2107,13 @@ static int mob_ai_sub_lazy(struct mob_data *md, va_list args)
md->last_thinktime=tick;

if (md->master_id) {
if (!mob_is_spotted(md)) {
// Get mob data of master
mob_data* mmd = map_id2md(md->master_id);
// If neither master nor slave have been spotted we don't have to execute the slave AI
if (mmd && !mob_is_spotted(mmd))
return 0;
}
mob_ai_sub_hard_slavemob (md,tick);
return 0;
}
Expand Down
1 change: 1 addition & 0 deletions src/map/mob.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,7 @@ struct mob_data {
uint32 spotted_log[DAMAGELOG_SIZE];
struct spawn_data *spawn; //Spawn data.
int spawn_timer; //Required for Convex Mirror
int16 centerX, centerY; // Spawn center of this individual monster
struct s_mob_lootitem *lootitems;
short mob_id;
unsigned int tdmg; //Stores total damage given to the mob, for exp calculations. [Skotlex]
Expand Down

0 comments on commit 5d232db

Please sign in to comment.