소스 검색

Fixes spawn infos after mobdb reload. (#2469)

* Moves the monster spawn infos from the mob_db struct to an own
  data structure.
* Fixes whereis after mobdb reload. Fixes #2360
* Fixes Taekwon Mission after mobdb reload. Closes #2460
* Thanks to @uddevil, @mgksrt8, @lighta and @aleos89!
Jeybla 7 년 전
부모
커밋
ba9314cba5
4개의 변경된 파일169개의 추가작업 그리고 113개의 파일을 삭제
  1. 31 24
      src/map/atcommand.cpp
  2. 117 48
      src/map/mob.cpp
  3. 8 3
      src/map/mob.hpp
  4. 13 38
      src/map/npc.cpp

+ 31 - 24
src/map/atcommand.cpp

@@ -7199,7 +7199,8 @@ ACMD_FUNC(mobinfo)
 	unsigned char melement[ELE_ALL][8] = { "Neutral", "Water", "Earth", "Fire", "Wind", "Poison", "Holy", "Dark", "Ghost", "Undead" };
 	char atcmd_output2[CHAT_SIZE_MAX];
 	struct item_data *item_data;
-	struct mob_db *mob, *mob_array[MAX_SEARCH];
+	struct mob_db *mob;
+	uint16 mob_ids[MAX_SEARCH];
 	int count;
 	int i, k;
 
@@ -7214,10 +7215,10 @@ ACMD_FUNC(mobinfo)
 	// If monster identifier/name argument is a name
 	if ((i = mobdb_checkid(atoi(message))))
 	{
-		mob_array[0] = mob_db(i);
+		mob_ids[0] = i;
 		count = 1;
 	} else
-		count = mobdb_searchname_array(mob_array, MAX_SEARCH, message);
+		count = mobdb_searchname_array(message, mob_ids, MAX_SEARCH);
 
 	if (!count) {
 		clif_displaymessage(fd, msg_txt(sd,40)); // Invalid monster ID or name.
@@ -7231,7 +7232,7 @@ ACMD_FUNC(mobinfo)
 	}
 	for (k = 0; k < count; k++) {
 		unsigned int j,base_exp,job_exp;
-		mob = mob_array[k];
+		mob = mob_db(mob_ids[k]);
 		base_exp = mob->base_exp;
 		job_exp = mob->job_exp;
 
@@ -7828,24 +7829,25 @@ ACMD_FUNC(whodrops)
 
 ACMD_FUNC(whereis)
 {
-	struct mob_db *mob_array[MAX_SEARCH];
-	int count;
-	int i, j, k;
+	uint16 mob_ids[MAX_SEARCH] = {0};
+	int count = 0;
 
 	if (!message || !*message) {
 		clif_displaymessage(fd, msg_txt(sd,1288)); // Please enter a monster name/ID (usage: @whereis <monster_name_or_monster_ID>).
 		return -1;
 	}
-
-	// If monster identifier/name argument is a name
-	if ((i = mobdb_checkid(atoi(message))))
-	{
-		mob_array[0] = mob_db(i);
+	
+	int i_message = atoi(message);
+	if (mobdb_checkid(i_message)) {
+		// ID given
+		mob_ids[0] = i_message;
 		count = 1;
-	} else
-		count = mobdb_searchname_array(mob_array, MAX_SEARCH, message);
-
-	if (!count) {
+	} else {
+		// Name given, get all monster associated whith this name
+		count = mobdb_searchname_array(message, mob_ids, MAX_SEARCH);
+	}
+	
+	if (count <= 0) {
 		clif_displaymessage(fd, msg_txt(sd,40)); // Invalid monster ID or name.
 		return -1;
 	}
@@ -7855,19 +7857,24 @@ ACMD_FUNC(whereis)
 		clif_displaymessage(fd, atcmd_output);
 		count = MAX_SEARCH;
 	}
-	for (k = 0; k < count; k++) {
-		struct mob_db *mob = mob_array[k];
+
+	for (int i = 0; i < count; i++) {
+		uint16 mob_id = mob_ids[i];
+		struct mob_db * mob = mob_db(mob_id);
+
 		snprintf(atcmd_output, sizeof atcmd_output, msg_txt(sd,1289), mob->jname); // %s spawns in:
 		clif_displaymessage(fd, atcmd_output);
-
-		for (i = 0; i < ARRAYLENGTH(mob->spawn) && mob->spawn[i].qty; i++)
+		
+		const std::vector<spawn_info> spawns = mob->get_spawns();
+		for(auto& spawn : spawns)
 		{
-			j = map_mapindex2mapid(mob->spawn[i].mapindex);
-			if (j < 0) continue;
-			snprintf(atcmd_output, sizeof atcmd_output, "%s (%d)", map[j].name, mob->spawn[i].qty);
+			int16 mapid = map_mapindex2mapid(spawn.mapindex);
+			if (mapid < 0)
+				continue;
+			snprintf(atcmd_output, sizeof atcmd_output, "%s (%d)", map[mapid].name, spawn.qty);
 			clif_displaymessage(fd, atcmd_output);
 		}
-		if (i == 0)
+		if (spawns.size() <= 0)
 			clif_displaymessage(fd, msg_txt(sd,1290)); // This monster does not spawn normally.
 	}
 

+ 117 - 48
src/map/mob.cpp

@@ -35,6 +35,10 @@
 #include "log.hpp"
 #include "achievement.hpp"
 
+#include <vector>
+#include <unordered_map>
+#include <algorithm>
+
 #define ACTIVE_AI_RANGE 2	//Distance added on top of 'AREA_SIZE' at which mobs enter active AI mode.
 
 #define IDLE_SKILL_INTERVAL 10	//Active idle skills should be triggered every 1 second (1000/MIN_MOBTHINKTIME)
@@ -66,6 +70,9 @@ struct mob_db *mob_dummy = NULL;	//Dummy mob to be returned when a non-existant
 
 struct mob_db *mob_db(int mob_id) { if (mob_id < 0 || mob_id > MAX_MOB_DB || mob_db_data[mob_id] == NULL) return mob_dummy; return mob_db_data[mob_id]; }
 
+// holds Monster Spawn informations
+std::unordered_map<uint16, std::vector<spawn_info>> mob_spawn_data;
+
 //Dynamic mob chat database
 struct mob_chat *mob_chat_db[MAX_MOB_CHAT+1];
 struct mob_chat *mob_chat(short id) { if(id<=0 || id>MAX_MOB_CHAT || mob_chat_db[id]==NULL) return (struct mob_chat*)NULL; return mob_chat_db[id]; }
@@ -118,34 +125,6 @@ static int mob_makedummymobdb(int);
 static int mob_spawn_guardian_sub(int tid, unsigned int tick, int id, intptr_t data);
 int mob_skill_id2skill_idx(int mob_id,uint16 skill_id);
 
-/*==========================================
- * Mob is searched with a name.
- *------------------------------------------*/
-int mobdb_searchname(const char *str)
-{
-	int i;
-	for(i=0;i<=MAX_MOB_DB;i++){
-		struct mob_db *mob = mob_db(i);
-		if(mob == mob_dummy) //Skip dummy mobs.
-			continue;
-		if(strcmpi(mob->name,str)==0 || strcmpi(mob->jname,str)==0 || strcmpi(mob->sprite,str)==0)
-			return i;
-	}
-	return 0;
-}
-static int mobdb_searchname_array_sub(struct mob_db* mob, const char *str)
-{
-	if (mob == mob_dummy)
-		return 1;
-	if(!mob->base_exp && !mob->job_exp && mob->spawn[0].qty < 1)
-		return 1; // Monsters with no base/job exp and no spawn point are, by this criteria, considered "slave mobs" and excluded from search results
-	if(stristr(mob->jname,str))
-		return 0;
-	if(stristr(mob->name,str))
-		return 0;
-	return strcmpi(mob->jname,str);
-}
-
 /*========================================== [Playtester]
 * Removes all characters that spotted the monster but are no longer online
 * @param md: Monster whose spotted log should be cleaned
@@ -304,26 +283,69 @@ void mvptomb_destroy(struct mob_data *md) {
 	md->tomb_nid = 0;
 }
 
+/**
+ * Sub function for mob namesearch. Here is defined which are accepted.
+*/
+static bool mobdb_searchname_sub(uint16 mob_id, const char * const str, bool full_cmp)
+{
+	const struct mob_db * const mob = mob_db(mob_id);
+	
+	if( mobdb_checkid(mob_id) <= 0 )
+		return false; // invalid mob_id (includes clone check)
+	if(!mob->base_exp && !mob->job_exp && !mob->has_spawn())
+		return false; // Monsters with no base/job exp and no spawn point are, by this criteria, considered "slave mobs" and excluded from search results
+	if( full_cmp ) {
+		// str must equal the db value
+		if( strcmpi(mob->name, str) == 0 || 
+			strcmpi(mob->jname, str) == 0 || 
+			strcmpi(mob->sprite, str) == 0 )
+			return true;
+	} else {
+		// str must be in the db value
+		if( stristr(mob->name, str) != NULL ||
+			stristr(mob->jname, str) != NULL ||
+			stristr(mob->sprite, str) != NULL )
+			return true;
+	}
+	return false;
+}
+
+/**
+ * Searches for the Mobname
+*/
+uint16 mobdb_searchname_(const char * const str, bool full_cmp)
+{
+	for(uint16 mob_id = 0; mob_id <= MAX_MOB_DB; mob_id++) {
+		if( mobdb_searchname_sub(mob_id, str, full_cmp) )
+			return mob_id;
+	}
+	return 0;
+}
+
+uint16 mobdb_searchname(const char * const str)
+{
+	return mobdb_searchname_(str, true);
+}
 /*==========================================
  * Founds up to N matches. Returns number of matches [Skotlex]
  *------------------------------------------*/
-int mobdb_searchname_array(struct mob_db** data, int size, const char *str)
+int mobdb_searchname_array_(const char *str, uint16 * out, int size, bool full_cmp)
 {
-	int count = 0, i;
-	struct mob_db* mob;
-	for(i=0;i<=MAX_MOB_DB;i++){
-		mob = mob_db(i);
-		if (mob == mob_dummy || mob_is_clone(i) ) //keep clones out (or you leak player stats)
-			continue;
-		if (!mobdb_searchname_array_sub(mob, str)) {
-			if (count < size)
-				data[count] = mob;
+	unsigned short count = 0;
+	for(uint16 mob_id = 0; mob_id <= MAX_MOB_DB && count < size; mob_id++) {
+		if( mobdb_searchname_sub(mob_id, str, full_cmp) ) {
+			out[count] = mob_id;
 			count++;
 		}
 	}
 	return count;
 }
 
+int mobdb_searchname_array(const char *str, uint16 * out, int size)
+{
+	return mobdb_searchname_array_(str, out, size, false);
+}
+
 /*==========================================
  * Id Mob is checked.
  *------------------------------------------*/
@@ -490,12 +512,14 @@ int mob_get_random_id(int type, int flag, int lv)
 		(flag&0x01 && (entry->rate < 1000000 && entry->rate <= rnd() % 1000000)) ||
 		(flag&0x02 && lv < mob->lv) ||
 		(flag&0x04 && status_has_mode(&mob->status,MD_STATUS_IMMUNE) ) ||
-		(flag&0x08 && mob->spawn[0].qty < 1) ||
+		(flag&0x08 && !mob->has_spawn()) ||
 		(flag&0x10 && status_has_mode(&mob->status,MD_IGNOREMELEE|MD_IGNOREMAGIC|MD_IGNORERANGED|MD_IGNOREMISC) )
 	) && (i++) < MAX_MOB_DB && msummon->count > 1);
 
-	if (i >= MAX_MOB_DB && &msummon->list[0])  // no suitable monster found, use fallback for given list
+	if (i >= MAX_MOB_DB && &msummon->list[0]) {
+		ShowError("mob_get_random_id: no suitable monster found, use fallback for given list. Last_MobID: %d\n", mob_id);
 		mob_id = msummon->list[0].mob_id;
+	}
 	return mob_id;
 }
 
@@ -3085,7 +3109,7 @@ int mob_guardian_guildchange(struct mob_data *md)
 /*==========================================
  * Pick a random class for the mob
  *------------------------------------------*/
-int mob_random_class (int *value, size_t count)
+int mob_random_class(int *value, size_t count)
 {
 	nullpo_ret(value);
 
@@ -3104,6 +3128,57 @@ int mob_random_class (int *value, size_t count)
 	return mobdb_checkid(value[rnd()%count]);
 }
 
+/**
+* Returns the SpawnInfos of the mob_db entry
+*/
+const std::vector<spawn_info> mob_db::get_spawns() const
+{
+	// Returns an empty std::vector<spawn_info> if mob_id is not in mob_spawn_data
+	return mob_spawn_data[this->get_mobid()];
+}
+
+/**
+ * Checks if a monster is spawned. Returns true if yes, false otherwise.
+*/
+bool mob_db::has_spawn() const
+{
+	// It's enough to check if the monster is in mob_spawn_data, because
+	// none or empty spawns are ignored. Thus the monster is spawned.
+	return mob_spawn_data.find(this->get_mobid()) != mob_spawn_data.end();
+}
+
+/**
+ * Adds a spawn info to the specific mob. (To mob_spawn_data)
+ * @param mob_id - Monster ID spawned
+ * @param new_spawn - spawn_info holding the map and quantity of the spawn
+*/
+void mob_add_spawn(uint16 mob_id, const struct spawn_info& new_spawn)
+{
+	unsigned short m = new_spawn.mapindex;
+
+	if( new_spawn.qty <= 0 )
+		return; //ignore empty spawns
+
+	std::vector<spawn_info>& spawns = mob_spawn_data[mob_id];
+	// Search if the map is already in spawns
+	auto itSameMap = std::find_if(spawns.begin(), spawns.end(), 
+		[&m] (const spawn_info &s) { return (s.mapindex == m); });
+	
+	if( itSameMap != spawns.end() )
+		itSameMap->qty += new_spawn.qty; // add quantity, if map is found
+	else
+		spawns.push_back(new_spawn); // else, add the whole spawn info
+	
+	// sort spawns by spawn quantity
+	std::sort(spawns.begin(), spawns.end(),
+		[](const spawn_info & a, const spawn_info & b) -> bool
+		{ return a.qty > b.qty; });
+/** Note
+	Spawns are sorted after every addition. This makes reloadscript slower, but
+	some spawns may be added directly by loadscript or something similar.
+*/
+}
+
 /*==========================================
  * Change mob base class
  *------------------------------------------*/
@@ -4221,9 +4296,6 @@ static bool mob_parse_dbrow(char** str)
 	// Finally insert monster's data into the database.
 	if (mob_db_data[mob_id] == NULL)
 		mob_db_data[mob_id] = (struct mob_db*)aCalloc(1, sizeof(struct mob_db));
-	else
-		//Copy over spawn data
-		memcpy(&db->spawn, mob_db_data[mob_id]->spawn, sizeof(db->spawn));
 
 	memcpy(mob_db_data[mob_id], db, sizeof(struct mob_db));
 	return true;
@@ -5324,10 +5396,7 @@ void mob_reload(void) {
  */
 void mob_clear_spawninfo()
 {	//Clears spawn related information for a script reload.
-	int i;
-	for (i = 0; i < MAX_MOB_DB; i++)
-		if (mob_db_data[i])
-			memset(&mob_db_data[i]->spawn,0,sizeof(mob_db_data[i]->spawn));
+	mob_spawn_data.clear();
 }
 
 /*==========================================

+ 8 - 3
src/map/mob.hpp

@@ -9,6 +9,8 @@
 #include "status.hpp" // struct status data, struct status_change
 #include "unit.hpp" // unit_stop_walking(), unit_stop_attack()
 
+#include <vector>
+
 struct guardian_data;
 
 // Change this to increase the table size in your mob_db to accomodate a larger mob database.
@@ -162,7 +164,9 @@ struct mob_db {
 	unsigned int option;
 	int maxskill;
 	struct mob_skill skill[MAX_MOBSKILL];
-	struct spawn_info spawn[10];
+	bool has_spawn() const;
+	const std::vector<spawn_info> get_spawns() const;
+	uint16 get_mobid() const {return vd.class_; } // Simple wrapper. The MobID is saved in vd, noone wants to remind that
 };
 
 struct mob_data {
@@ -291,8 +295,8 @@ struct item_drop_list {
 
 struct mob_db *mob_db(int mob_id);
 struct mob_db *mobdb_exists(uint16 mob_id);
-int mobdb_searchname(const char *str);
-int mobdb_searchname_array(struct mob_db** data, int size, const char *str);
+uint16 mobdb_searchname(const char * const str);
+int mobdb_searchname_array(const char *str, uint16 * out, int size);
 int mobdb_checkid(const int id);
 struct view_data* mob_get_viewdata(int mob_id);
 void mob_set_dynamic_viewdata( struct mob_data* md );
@@ -356,6 +360,7 @@ int mob_clone_spawn(struct map_session_data *sd, int16 m, int16 x, int16 y, cons
 int mob_clone_delete(struct mob_data *md);
 
 void mob_reload(void);
+void mob_add_spawn(uint16 mob_id, const struct spawn_info& new_spawn);
 
 // MvP Tomb System
 int mvptomb_setdelayspawn(struct npc_data *nd);

+ 13 - 38
src/map/npc.cpp

@@ -3762,11 +3762,10 @@ void npc_parse_mob2(struct spawn_data* mob)
 
 static const char* npc_parse_mob(char* w1, char* w2, char* w3, char* w4, const char* start, const char* buffer, const char* filepath)
 {
-	int num, class_, i, j, mob_lv = -1, size = -1, w1count;
+	int num, mob_id, mob_lv = -1, size = -1, w1count;
 	short m,x,y,xs = -1, ys = -1;
 	char mapname[MAP_NAME_LENGTH_EXT], mobname[NAME_LENGTH];
 	struct spawn_data mob, *data;
-	struct mob_db* db;
 	int ai; // mob_ai
 
 	memset(&mob, 0, sizeof(struct spawn_data));
@@ -3778,7 +3777,7 @@ static const char* npc_parse_mob(char* w1, char* w2, char* w3, char* w4, const c
 	// w4=<mob id>,<amount>{,<delay1>{,<delay2>{,<event>{,<mob size>{,<mob ai>}}}}}
 	if( ( w1count = sscanf(w1, "%15[^,],%6hd,%6hd,%6hd,%6hd", mapname, &x, &y, &xs, &ys) ) < 3
 	||	sscanf(w3, "%23[^,],%11d", mobname, &mob_lv) < 1
-	||	sscanf(w4, "%11d,%11d,%11u,%11u,%77[^,],%11d,%11d[^\t\r\n]", &class_, &num, &mob.delay1, &mob.delay2, mob.eventname, &size, &ai) < 2 )
+	||	sscanf(w4, "%11d,%11d,%11u,%11u,%77[^,],%11d,%11d[^\t\r\n]", &mob_id, &num, &mob.delay1, &mob.delay2, mob.eventname, &size, &ai) < 2 )
 	{
 		ShowError("npc_parse_mob: Invalid mob definition in file '%s', line '%d'.\n * w1=%s\n * w2=%s\n * w3=%s\n * w4=%s\n", filepath, strline(buffer,start-buffer), w1, w2, w3, w4);
 		return strchr(start,'\n');// skip and continue
@@ -3800,9 +3799,9 @@ static const char* npc_parse_mob(char* w1, char* w2, char* w3, char* w4, const c
 	}
 
 	// check monster ID if exists!
-	if( mobdb_checkid(class_) == 0 )
+	if( mobdb_checkid(mob_id) == 0 )
 	{
-		ShowError("npc_parse_mob: Unknown mob ID %d (file '%s', line '%d').\n", class_, filepath, strline(buffer,start-buffer));
+		ShowError("npc_parse_mob: Unknown mob ID %d (file '%s', line '%d').\n", mob_id, filepath, strline(buffer,start-buffer));
 		return strchr(start,'\n');// skip and continue
 	}
 
@@ -3814,25 +3813,25 @@ static const char* npc_parse_mob(char* w1, char* w2, char* w3, char* w4, const c
 
 	if( mob.state.size > SZ_BIG && size != -1 )
 	{
-		ShowError("npc_parse_mob: Invalid size number %d for mob ID %d (file '%s', line '%d').\n", mob.state.size, class_, filepath, strline(buffer, start - buffer));
+		ShowError("npc_parse_mob: Invalid size number %d for mob ID %d (file '%s', line '%d').\n", mob.state.size, mob_id, filepath, strline(buffer, start - buffer));
 		return strchr(start, '\n');
 	}
 
 	if( (mob.state.ai < AI_NONE || mob.state.ai >= AI_MAX) && ai != -1 )
 	{
-		ShowError("npc_parse_mob: Invalid ai %d for mob ID %d (file '%s', line '%d').\n", mob.state.ai, class_, filepath, strline(buffer, start - buffer));
+		ShowError("npc_parse_mob: Invalid ai %d for mob ID %d (file '%s', line '%d').\n", mob.state.ai, mob_id, filepath, strline(buffer, start - buffer));
 		return strchr(start, '\n');
 	}
 
 	if( (mob_lv == 0 || mob_lv > MAX_LEVEL) && mob_lv != -1 )
 	{
-		ShowError("npc_parse_mob: Invalid level %d for mob ID %d (file '%s', line '%d').\n", mob_lv, class_, filepath, strline(buffer, start - buffer));
+		ShowError("npc_parse_mob: Invalid level %d for mob ID %d (file '%s', line '%d').\n", mob_lv, mob_id, filepath, strline(buffer, start - buffer));
 		return strchr(start, '\n');
 	}
 
 	mob.num = (unsigned short)num;
 	mob.active = 0;
-	mob.id = (short) class_;
+	mob.id = (short) mob_id;
 	mob.x = (unsigned short)x;
 	mob.y = (unsigned short)y;
 	mob.xs = (signed short)xs;
@@ -3846,14 +3845,14 @@ static const char* npc_parse_mob(char* w1, char* w2, char* w3, char* w4, const c
 
 	if (mob.xs < 0) {
 		if (w1count > 3) {
-			ShowWarning("npc_parse_mob: Negative x-span %hd for mob ID %d (file '%s', line '%d').\n", mob.xs, class_, filepath, strline(buffer, start - buffer));
+			ShowWarning("npc_parse_mob: Negative x-span %hd for mob ID %d (file '%s', line '%d').\n", mob.xs, mob_id, filepath, strline(buffer, start - buffer));
 		}
 		mob.xs = 0;
 	}
 
 	if (mob.ys < 0) {
 		if (w1count > 4) {
-			ShowWarning("npc_parse_mob: Negative y-span %hd for mob ID %d (file '%s', line '%d').\n", mob.ys, class_, filepath, strline(buffer, start - buffer));
+			ShowWarning("npc_parse_mob: Negative y-span %hd for mob ID %d (file '%s', line '%d').\n", mob.ys, mob_id, filepath, strline(buffer, start - buffer));
 		}
 		mob.ys = 0;
 	}
@@ -3885,37 +3884,13 @@ static const char* npc_parse_mob(char* w1, char* w2, char* w3, char* w4, const c
 	//Verify dataset.
 	if( !mob_parse_dataset(&mob) )
 	{
-		ShowError("npc_parse_mob: Invalid dataset for monster ID %d (file '%s', line '%d').\n", class_, filepath, strline(buffer,start-buffer));
+		ShowError("npc_parse_mob: Invalid dataset for monster ID %d (file '%s', line '%d').\n", mob_id, filepath, strline(buffer,start-buffer));
 		return strchr(start,'\n');// skip and continue
 	}
 
 	//Update mob spawn lookup database
-	db = mob_db(class_);
-	for( i = 0; i < ARRAYLENGTH(db->spawn); ++i )
-	{
-		if (map[mob.m].index == db->spawn[i].mapindex)
-		{	//Update total
-			db->spawn[i].qty += mob.num;
-			//Re-sort list
-			for( j = i; j > 0 && db->spawn[j-1].qty < db->spawn[i].qty; --j );
-			if( j != i )
-			{
-				xs = db->spawn[i].mapindex;
-				ys = db->spawn[i].qty;
-				memmove(&db->spawn[j+1], &db->spawn[j], (i-j)*sizeof(db->spawn[0]));
-				db->spawn[j].mapindex = xs;
-				db->spawn[j].qty = ys;
-			}
-			break;
-		}
-		if (mob.num > db->spawn[i].qty)
-		{	//Insert into list
-			memmove(&db->spawn[i+1], &db->spawn[i], sizeof(db->spawn) -(i+1)*sizeof(db->spawn[0]));
-			db->spawn[i].mapindex = map[mob.m].index;
-			db->spawn[i].qty = mob.num;
-			break;
-		}
-	}
+	struct spawn_info spawn = { map[mob.m].index, mob.num };
+	mob_add_spawn(mob_id, spawn);
 
 	//Now that all has been validated. We allocate the actual memory that the re-spawn data will use.
 	data = (struct spawn_data*)aMalloc(sizeof(struct spawn_data));