Jump to content

Tokei

Members
  • Posts

    667
  • Joined

  • Last visited

  • Days Won

    91

Posts posted by Tokei

  1. This feature allows you to clone maps ingame without having to reboot your server or do any changes client-side. The cloned maps will behave just like any other regular map, except players will not be able to save in them. If they log out, they will return to their spawn point. The commands:

    @clonemap source_map new_map
    @delmap new_map
    @clonemaplist

    This is useful if want to make an event on a map but there's an annoying NPC in the way (and you don't want to reboot your server just for that!). Another big upside being you don't need to send a patch for that either, the new map will be added 'live'. You can load scripts on these maps as well (they will be removed upon deleting the map).

    Usage example:
    @clonemap prontera newmap
    @addwarp newmap 150 150 test_portal
    (@unloadnpc test_portal)

    Have fun!

    These maps will be lost when rebooting the server.

    diff --git a/src/map/atcommand.c b/src/map/atcommand.c
    index d4b329c..25e4c8b 100644
    --- a/src/map/atcommand.c
    +++ b/src/map/atcommand.c
    @@ -799,6 +799,11 @@ ACMD_FUNC(save)
     {
     	nullpo_retr(-1, sd);
     
    +	if( map[sd->bl.m].clone_id ) {
    +		clif_displaymessage(fd, "You cannot create a savepoint in this map.");
    +		return 1;
    +	}
    +
     	if( map[sd->bl.m].instance_id ) {
     		clif_displaymessage(fd, msg_txt(sd,383)); // You cannot create a savepoint in an instance.
     		return 1;
    @@ -5945,6 +5950,11 @@ ACMD_FUNC(autotrade) {
     		return -1;
     	}
     
    +	if (map[sd->bl.m].clone_id) {
    +		clif_displaymessage(fd, "You cannot use autotrade on cloned maps.");
    +		return -1;
    +	}
    +
     	sd->state.autotrade = 1;
     	if (battle_config.autotrade_monsterignore)
     		sd->state.monster_ignore = 1;
    @@ -9937,6 +9947,111 @@ ACMD_FUNC(adopt)
     	return -1;
     }
     
    +/**
    + * Clones an existing map without having to reboot your server.
    + * Usage: @clonemap <source_map_name> <new_map_name>
    + * @author Tokeiburu
    + */
    +ACMD_FUNC(clonemap)
    +{
    +	int res;
    +	char map_name_cur[MAP_NAME_LENGTH_EXT];
    +	char map_name_new[MAP_NAME_LENGTH_EXT];
    +
    +	memset(map_name_cur, '\0', sizeof(map_name_cur));
    +	memset(map_name_new, '\0', sizeof(map_name_new));
    +
    +	if (!message || !*message || sscanf(message, "%15s %15s", map_name_cur, map_name_new) < 2) {
    +		clif_displaymessage(fd, "Usage: @clonemap <source_map_name> <new_map_name>");
    +		return -1;
    +	}
    +
    +	if (map_mapname2mapid(map_name_cur) < 0) {
    +		sprintf(atcmd_output, "Source map not found: %s", map_name_cur);
    +		clif_displaymessage(fd, atcmd_output);
    +		return -1;
    +	}
    +
    +	if (map_mapname2mapid(map_name_new) >= 0) { // This will always show a warning in the console, but removing it is not worth the trouble.
    +		sprintf(atcmd_output, "Destination map already exists: %s", map_name_new);
    +		clif_displaymessage(fd, atcmd_output);
    +		return -1;
    +	}
    +
    +	if ((res = map_addclonemap(map_name_cur, map_name_new)) < 0) {
    +		sprintf(atcmd_output, "Failed to create a new map, error: %d", res);
    +		clif_displaymessage(fd, atcmd_output);
    +		return -1;
    +	}
    +
    +	sprintf(atcmd_output, "New map cloned (%s).", map_name_new);
    +	clif_displaymessage(fd, atcmd_output);
    +	return 0;
    +}
    +
    +/**
    + * Removes a cloned map.
    + * Usage: @delmap <map_name>
    + * @author Tokeiburu
    + */
    +ACMD_FUNC(delmap)
    +{
    +	int res;
    +	int m;
    +	char map_name[MAP_NAME_LENGTH_EXT];
    +
    +	memset(map_name, '\0', sizeof(map_name));
    +
    +	if (!message || !*message || sscanf(message, "%15s", map_name) < 1) {
    +		clif_displaymessage(fd, "Usage: @delmap <map_name>");
    +		return -1;
    +	}
    +
    +	if ((m = map_mapname2mapid(map_name)) < 0) {
    +		sprintf(atcmd_output, "Map not found: %s", map_name);
    +		clif_displaymessage(fd, atcmd_output);
    +		return -1;
    +	}
    +
    +	if (map[m].clone_id == 0) {
    +		clif_displaymessage(fd, "Only cloned maps can be removed.");
    +		return -1;
    +	}
    +
    +	if ((res = map_delclonemap(map_name)) != 1) {
    +		sprintf(atcmd_output, "Failed to remove map: %s", map_name);
    +		clif_displaymessage(fd, atcmd_output);
    +		return -1;
    +	}
    +
    +	sprintf(atcmd_output, "Cloned map removed (%s).", map_name);
    +	clif_displaymessage(fd, atcmd_output);
    +	return 0;
    +}
    +
    +/**
    + * Lists all cloned maps.
    + * Usage: @clonemaplist
    + * @author Tokeiburu
    + */
    +ACMD_FUNC(clonemaplist)
    +{
    +	int i;
    +	int count = 0;
    +
    +	for (i = instance_start; i < MAX_MAP_PER_SERVER; i++) {
    +		if (map[i].clone_id > 0) {
    +			sprintf(atcmd_output, "%s (source: %s)", map[i].name, map[map[i].clone_id].name);
    +			clif_displaymessage(fd, atcmd_output);
    +			count++;
    +		}
    +	}
    +
    +	sprintf(atcmd_output, "Found %d cloned map(s).", count);
    +	clif_displaymessage(fd, atcmd_output);
    +	return 0;
    +}
    +
     #include "../custom/atcommand.inc"
     
     /**
    @@ -10234,6 +10349,10 @@ void atcommand_basecommands(void) {
     		ACMD_DEF(adopt),
     		ACMD_DEF(agitstart3),
     		ACMD_DEF(agitend3),
    +
    +		ACMD_DEF(clonemap),
    +		ACMD_DEF(delmap),
    +		ACMD_DEF(clonemaplist),
     	};
     	AtCommandInfo* atcommand;
     	int i;
    diff --git a/src/map/chrif.c b/src/map/chrif.c
    index 4276434..cea689f 100644
    --- a/src/map/chrif.c
    +++ b/src/map/chrif.c
    @@ -336,7 +336,7 @@ int chrif_save(struct map_session_data *sd, enum e_chrif_save_opt flag) {
     	WFIFOB(char_fd,12) = (flag&CSAVE_QUIT) ? 1 : 0; //Flag to tell char-server this character is quitting.
     
     	// If the user is on a instance map, we have to fake his current position
    -	if( map[sd->bl.m].instance_id ){
    +	if( map[sd->bl.m].instance_id || map[sd->bl.m].clone_id ){
     		struct mmo_charstatus status;
     
     		// Copy the whole status
    diff --git a/src/map/clif.c b/src/map/clif.c
    index 6c77044..97d8864 100644
    --- a/src/map/clif.c
    +++ b/src/map/clif.c
    @@ -10352,7 +10352,7 @@ void clif_parse_LoadEndAck(int fd,struct map_session_data *sd)
     #endif
     
     		// Instances do not need their own channels
    -		if( channel_config.map_enable && channel_config.map_autojoin && !map[sd->bl.m].flag.chmautojoin && !map[sd->bl.m].instance_id )
    +		if( channel_config.map_enable && channel_config.map_autojoin && !map[sd->bl.m].flag.chmautojoin && !map[sd->bl.m].instance_id && !map[sd->bl.m].clone_id )
     			channel_mjoin(sd); //join new map
     	} else if (sd->guild && (battle_config.guild_notice_changemap == 2 || guild_notice))
     		clif_guild_notice(sd); // Displays at end
    diff --git a/src/map/map.c b/src/map/map.c
    index c7e326b..37d65f1 100644
    --- a/src/map/map.c
    +++ b/src/map/map.c
    @@ -2095,7 +2095,7 @@ int map_quit(struct map_session_data *sd) {
     
     	unit_remove_map_pc(sd,CLR_RESPAWN);
     
    -	if( map[sd->bl.m].instance_id ) { // Avoid map conflicts and warnings on next login
    +	if( map[sd->bl.m].instance_id || map[sd->bl.m].clone_id ) { // Avoid map conflicts and warnings on next login
     		int16 m;
     		struct point *pt;
     		if( map[sd->bl.m].save.map )
    @@ -2728,6 +2728,133 @@ int map_delinstancemap(int m)
     	return 1;
     }
     
    +/*==========================================
    + * Add a cloned map
    + *------------------------------------------*/
    +int map_addclonemap(const char *name, const char *newname)
    +{
    +	int src_m = map_mapname2mapid(name);
    +	int dst_m = -1, i;
    +	size_t num_cell, size;
    +
    +	if(src_m < 0)
    +		return -1;
    +
    +	if(strlen(name) >= MAP_NAME_LENGTH) {
    +		// against buffer overflow
    +		ShowError("map_addclonemap: can't add long map name \"%s\"\n", name);
    +		return -2;
    +	}
    +
    +	if(strlen(newname) >= MAP_NAME_LENGTH) {
    +		// against buffer overflow
    +		ShowError("map_addclonemap: can't add long map name \"%s\"\n", newname);
    +		return -2;
    +	}
    +
    +	for(i = instance_start; i < MAX_MAP_PER_SERVER; i++) {
    +		if(!map[i].name[0])
    +			break;
    +	}
    +	if(i < map_num) // Destination map value overwrites another
    +		dst_m = i;
    +	else if(i < MAX_MAP_PER_SERVER) // Destination map value increments to new map
    +		dst_m = map_num++;
    +	else {
    +		// Out of bounds
    +		ShowError("map_addclonemap failed. map_num(%d) > map_max(%d)\n",map_num, MAX_MAP_PER_SERVER);
    +		return -3;
    +	}
    +
    +	if (map[src_m].clone_id) {
    +		ShowError("map_addclonemap failed. Cannot clone a cloned map.\n",map_num, MAX_MAP_PER_SERVER);
    +		return -4;
    +	}
    +
    +	// Copy the map
    +	memcpy(&map[dst_m], &map[src_m], sizeof(struct map_data));
    +
    +	// Alter the name
    +	// Due to this being custom we only worry about preserving as many characters as necessary for accurate map distinguishing
    +	// This also allows us to maintain complete independence with main map functions
    +	strncpy(map[dst_m].name,newname,MAP_NAME_LENGTH);
    +
    +	map[dst_m].m = dst_m;
    +	map[dst_m].clone_id = src_m;
    +	map[dst_m].users = 0;
    +
    +	memset(map[dst_m].npc, 0, sizeof(map[dst_m].npc));
    +	map[dst_m].npc_num = 0;
    +	
    +	// Reallocate cells
    +	num_cell = map[dst_m].xs * map[dst_m].ys;
    +	CREATE( map[dst_m].cell, struct mapcell, num_cell );
    +	memcpy( map[dst_m].cell, map[src_m].cell, num_cell * sizeof(struct mapcell) );
    +
    +	// Remove ontouch NPC cells
    +	for (i = 0; i < num_cell; i++) {
    +		map[dst_m].cell[i].npc = 0;
    +	}
    +
    +	// Remove all mob spawners (these should not be copied over)
    +	for (i = 0; i < MAX_MOB_LIST_PER_MAP; i++) {
    +		map[dst_m].moblist[i] = NULL;
    +	}
    +
    +	// Do not copy PVP nightmare drops
    +	memset(map[dst_m].drop_list, 0, sizeof(map[dst_m].drop_list));
    +
    +	// Remove quest information from the map
    +	map[dst_m].qi_data = NULL;
    +
    +	size = map[dst_m].bxs * map[dst_m].bys * sizeof(struct block_list*);
    +	map[dst_m].block = (struct block_list **)aCalloc(1,size);
    +	map[dst_m].block_mob = (struct block_list **)aCalloc(1,size);
    +
    +	map[dst_m].index = mapindex_addmap(-1, map[dst_m].name);
    +	map[dst_m].channel = NULL;
    +	map[dst_m].mob_delete_timer = INVALID_TIMER;
    +
    +	map_addmap2db(&map[dst_m]);
    +
    +	return dst_m;
    +}
    +
    +/*==========================================
    + * Deleting a cloned map
    + *------------------------------------------*/
    +int map_delclonemap(const char* mapname)
    +{
    +	int m = map_mapname2mapid(mapname);
    +
    +	if(m < 0 || map[m].instance_id || map[m].clone_id <= 0)
    +		return 0;
    +
    +	// Kick everyone out
    +	map_foreachinmap(map_instancemap_leave, m, BL_PC);
    +
    +	// Do the unit cleanup
    +	map_foreachinmap(map_instancemap_clean, m, BL_ALL);
    +
    +	if( map[m].mob_delete_timer != INVALID_TIMER )
    +		delete_timer(map[m].mob_delete_timer, map_removemobs_timer);
    +
    +	// Free memory
    +	aFree(map[m].cell);
    +	map[m].cell = NULL;
    +	aFree(map[m].block);
    +	map[m].block = NULL;
    +	aFree(map[m].block_mob);
    +	map[m].block_mob = NULL;
    +	map_free_questinfo(m);
    +
    +	mapindex_removemap( map[m].index );
    +	map_removemapdb(&map[m]);
    +	memset(&map[m], 0x00, sizeof(map[0]));
    +	map[m].mob_delete_timer = INVALID_TIMER;
    +	return 1;
    +}
    +
     /*=========================================
      * Dynamic Mobs [Wizputer]
      *-----------------------------------------*/
    @@ -2838,6 +2965,10 @@ void map_removemobs(int16 m)
      *------------------------------------------*/
     const char* map_mapid2mapname(int m)
     {
    +	if (map[m].clone_id) { // Clone map check
    +		return map[map[m].clone_id].name;
    +	}
    +
     	if (map[m].instance_id) { // Instance map check
     		struct instance_data *im = &instance_data[map[m].instance_id];
     
    diff --git a/src/map/map.h b/src/map/map.h
    index ec0b2b0..762ab2d 100644
    --- a/src/map/map.h
    +++ b/src/map/map.h
    @@ -705,6 +705,9 @@ struct map_data {
     	unsigned short instance_id;
     	int instance_src_map;
     
    +	// Clone map system
    +	unsigned short clone_id;
    +
     	/* rAthena Local Chat */
     	struct Channel *channel;
     
    @@ -820,6 +823,10 @@ int map_addflooritem(struct item *item, int amount, int16 m, int16 x, int16 y, i
     int map_addinstancemap(const char *name, unsigned short instance_id);
     int map_delinstancemap(int m);
     
    +// Clone map system
    +int map_delclonemap(const char* mapname);
    +int map_addclonemap(const char *name, const char *newname);
    +
     // player to map session
     void map_addnickdb(int charid, const char* nick);
     void map_delnickdb(int charid, const char* nick);
    diff --git a/src/map/pc.c b/src/map/pc.c
    index 66a42f4..07d395b 100755
    --- a/src/map/pc.c
    +++ b/src/map/pc.c
    @@ -5599,6 +5599,11 @@ bool pc_memo(struct map_session_data* sd, int pos)
     		pos = 0;
     	}
     
    +	if( map[sd->bl.m].clone_id ) {
    +		clif_displaymessage( sd->fd, "You cannot create a memo in this map." );
    +		return false;
    +	}
    +
     	if( map[sd->bl.m].instance_id ) {
     		clif_displaymessage( sd->fd, msg_txt(sd,384) ); // You cannot create a memo in an instance.
     		return false;
    diff --git a/src/map/script.c b/src/map/script.c
    index f53b799..9f59068 100644
    --- a/src/map/script.c
    +++ b/src/map/script.c
    @@ -9607,6 +9607,11 @@ BUILDIN_FUNC(savepoint)
     	y = script_getnum(st,4);
     	m = map_mapindex2mapid(map_idx);
     
    +	if( map[m].clone_id ) {
    +		ShowError("buildin_savepoint: You cannot create a savepoint on this map %s.\n", str);
    +		return SCRIPT_CMD_FAILURE;
    +	}
    +
     	if (cid_pos == 7) {
     		int dx = script_getnum(st,5), dy = script_getnum(st,6),
     			x1 = x + dx, y1 = y + dy,

     

     

    clonemap.diff

    • Upvote 11
    • Love 2
  2. Heya,

    I would like to suggest adding @reloadnpc as an official atcommand to reload scripts. Currently we have to use @unloadnpcfile followed by @loadnpc which is a bit awkward when you want to create a new script. With @reloadnpc, you'd be able to load and reload NPCs with the same command.

    ACMD_FUNC(reloadnpc) {
    	if (!message || !*message) {
    		clif_displaymessage(fd, "Usage: @reloadnpc <file name>");
    		return -1;
    	}
    
    	if (npc_unloadfile(message))
    		clif_displaymessage(fd, msg_txt(sd,1386)); // File unloaded. Be aware that mapflags and monsters spawned directly are not removed.
    
    	if (!npc_addsrcfile(message) || !npc_parsesrcfile(message,true)) {
    		clif_displaymessage(fd, msg_txt(sd,261));
    		return -1;
    	}
    
    	npc_read_event_script();
    
    	clif_displaymessage(fd, msg_txt(sd,262));
    	return 0;
    }

     

    • Upvote 6
  3. 		'party_id = getcharid(1);
    		'ins_rid = getattachedrid();
    		
    		donpcevent instance_npcname("#ins_die_event_sub") + "::OnPartyCheck";

    This works because the player is being attached to the script (it stops working if you detach the player, though). I really like your alternative solution actually! You wouldn't have to deal with the attach/detachrid uglyness. But how would you identify players leaving the party or being added to the party? (I don't think you can prevent a player from leaving a party.)

  4. There are no easy ways of doing this, OnPCDieEvent is annoying to use. I would try the following instead:

    1@tower,0,0,0	script	#ins_die_event_sub	-1,{
    	end;
    OnPartyCheck:
    	'player_alive = 0;
    	getpartymember 'party_id, 2;
    	
    	for (.@i = 0; .@i < $@partymembercount && !'player_alive; .@i++) {
    		if (isloggedin($@partymemberaid[.@i]) && $@partymemberaid[.@i] != 'ins_rid) { // Logged out players are still active
    			if (attachrid($@partymemberaid[.@i])) {
    				if (HP > 0 && strcharinfo(3) == instance_mapname("1@tower")) {
    					'player_alive++;
    				}
    				
    				detachrid();
    			}
    		}
    	}
    	
    	// Run code back on the instance
    	if ('player_alive == 0) {
    		announce "All players have died.", 0;
    	}
    	
    	end;
    }
    
    -	script	#ins_die_event	-1,{
    	end;
    OnPCDieEvent:
    OnPCLogoutEvent:
    	if (strcharinfo(3) == instance_mapname("1@tower")) {
    		// Do not perform the party checks here directly, this script
    		// is never attached to the instance.
    		'party_id = getcharid(1);
    		'ins_rid = getattachedrid();
    		
    		donpcevent instance_npcname("#ins_die_event_sub") + "::OnPartyCheck";
    	}
    	
    	end;
    }

    There are few things you have to be very careful with such scripts. OnPCDieEvent is a global event label which is not attached to your instance nor is it duplicated. All players who die on your server will trigger this event label, hence why you have to check if the player has the instance (and why you should not put the NPC in the instance map). The usage of a donpcevent is to force the script to run back on the instance after it's detached from the players' checks. You also cannot rely on $@partymembercount alone since some members could be offline in the party. Players can also leave the party or logout, which you have to take into account.

  5. Heya, you have to set the properties for each mob:

    	deletearray $@mobid[0],getarraysize($@mobid);
    	monster "prontera",156,176,"Omega poring",1002,2;
    	
    	for (.@i = 0; .@i < getarraysize($@mobid); .@i++) {
    		setunitdata $@mobid[.@i],UMOB_MAXHP,(50*.@mp);
    		setunitdata $@mobid[.@i],UMOB_HP,(50*.@mp);
    		setunitdata $@mobid[.@i],UMOB_ATKMIN,300;
    		setunitdata $@mobid[.@i],UMOB_ATKMAX,300;
    	}
    	
    	deletearray $@mobid[0],getarraysize($@mobid);
    	monster "prontera",156,176,"Fracus",1758,3;
    	
    	for (.@i = 0; .@i < getarraysize($@mobid); .@i++) {
    		setunitdata $@mobid[.@i],UMOB_MAXHP,(100*.@mp);
    		setunitdata $@mobid[.@i],UMOB_HP,(100*.@mp);
    		setunitdata $@mobid[.@i],UMOB_MODE,12433;
    	}

     

    • Upvote 2
  6. You can try the following:

    -	script	fishvendor	-1,{
    	.@n$ = "[Fish Vendor]";
    	
    	if (checkweight(1201, 1) == 0) {
    		mes .@n$, "You have too many items in your inventory!";
    		close;
    	}
    	
    	mes .@n$, "Hi there, if you went fishing, I can buy your rare catch!";
    	next;
    	mes .@n$, "If you caught a Shark, Whale, or Octopus, I can give you 10 Fish Market Receipt for each of them";
    	next;
    	mes .@n$, "Here's the list of the fishes I want to buy";
    	next;
    	
    	setarray .@items,  32189, 32195, 32191, 32190, 32180, 32181, 32182, 32183, 32186, 32187, 32188, 32192, 32193, 32184, 32185, 32194;
    	setarray .@amounts,   10,    10,    10,    10,     5,     5,     5,     5,     5,     5,     5,     5,     5,     1,     1,     1;
    	setarray .@rewards,    1,     1,     1,     1,     1,     1,     1,     1,     1,     1,     1,     1,     1,    10,    10,    10;
    	
    	for (.@i = 0; .@i < getarraysize(.@items); .@i++) {
    		// Optional, but I usually make such items with blue links so that players click the right ones more easily
    		if (countitem(.@items[.@i]) >= .@amounts[.@i])
    			.@menu$ = .@menu$ + "^0000ff" + .@amounts[.@i] + "x " + getitemname(.@items[.@i]) + "^000000:";
    		else
    			.@menu$ = .@menu$ + .@amounts[.@i] + "x " + getitemname(.@items[.@i]) + ":";
    	}
    	
    	.@opt = select(.@menu$) - 1;
    	
    	if (.@opt < 0 || .@opt >= getarraysize(.@items))
    		end;
    	
    	.@item = .@items[.@opt];
    	.@amount = .@amounts[.@opt];
    	
    	if (countitem(.@item) < .@amount) {
    		mes .@n$, "Sorry but you don't have enough " + getitemname(.@item) + " with you"; 
    		close;
    	}
    	
    	delitem .@item, .@amount;
    	getitem 32196, .@rewards[.@opt];
    	mes .@n$, "Thanks! Here's your Fish Market Receipt!";
    	close;
    }

    You may want to double check the receipts to make sure all the values are right.

    • Upvote 1
  7. You're attaching a timer to a player, that's where the issue is. A player cannot run two scripts at the same time, so it stops the first script (the one where the player talks to an NPC with a dialogue, for instance) by adding a close button. Then it attaches the player on your OnTimer60000 script event. You'll have to change your approach.

    • Upvote 1
  8. There's a changelog in the page:

    Quote
    • [Added] GrfEditor : Added more default Thor paths when you create a patch file.
    • [Fixed] GrfEditor : Fixed a bunch of annoying UI issues from WPF.
    • [Fixed] GRF : Windows 10 no longer comes with the required VC++ redistributables, so they're now going to be extracted on startup. This should fix the "Failed to load library" errors.
    • Fixed a... file not found issue o.o.
    • Changed logo!

    Then again, there are way more changes than that, 1.7.5 was quite a while ago. I only put those I remembered and uploaded it to fix the broken link. Nothing major was changed though.

     

  9. @Emistry I wrote this script as a generic example. When you convert the NPCs to instance NPCs, you just change the global variables to instance variables. I usually put mine as "$et_<name_here>" for endless tower, then replace all to " 'et_". It's faster for me when I want to test a specific part of an instance @@. But... we don't know what Hijirikawa is going to use that for.

    I do want the variable to be accessible from all the NPCs though; if you aren't using duplicates (the OP could be using 4 completely different NPCs with no code being shared at all), then NPC variables won't do. Well, I made a "setvariableofnpc" command which would solve that particular issue. With that said though, my solution works in all scenarios, even if you change your code midway for another approach.

    Skrom's algorithm is also in n²; it works fine with 4 NPCs, but it can get very slow if you plan on duplicating more of them. There's a lot of repeated code being ran here too (sorry >.<'). Though, if you really want to use NPC variables, I don't think you can do it another way (so I still wouldn't go for it on my end xD).

    • Upvote 1
  10. @Skorm It really depends on what your goal is; I stay away from npc variables as much as possible for a few reasons. I'm usually scripting for instances and NPC variables won't work as expected in such a case (the variables will be shared across instances if you use duplicates). I personally don't like having the OnInit label on duplicate NPCs either since the event gets called multiple times; that's what you're relying on here though xD. I usually end up adding code in the init part and I don't want it to be repeated everytime a duplicate NPC gets loaded.

    And... you have to be careful while using strnpcinfo if you're having a floating base NPC, such as:

    -	script	NPC_base	-1,{
    	.@id = atoi(strnpcinfo(2));
    	dispbottom "Variable " + (.@id + 1) + ": " + .npc_variables[.@id];
    	end;
    	
    OnInit:
    	.@len = getarraysize(.npc_variables);
    	.npc_variables[.@len] = atoi(strnpcinfo(2)) + 1;
    	//...
    }
    
    prontera,151,183,0	duplicate(NPC_base)	NPC#0	77
    prontera,153,183,0	duplicate(NPC_base)	NPC#1	77
    prontera,155,183,0	duplicate(NPC_base)	NPC#2	77
    prontera,157,183,0	duplicate(NPC_base)	NPC#3	77

    Also, in that script, you'll end up with OnInit being called 5 times (that's not the case in your sample, but that's just a mistake I don't want to have to deal with if I use floating NPCs). Anyway! It's just a preference for me. I make less mistakes that way and it's pretty much an habit now ;P.

  11. There are multiple ways to approach this. I personally find it easier to make a global variable containing all your values, and then having the NPCs access the array. For example:

    prontera,151,183,0	script	NPC1	77,{
    	dispbottom "Variable 1: " + $npc_values[0];
    	end;
    }
    
    prontera,153,183,0	script	NPC2	77,{
    	dispbottom "Variable 2: " + $npc_values[1];
    	end;
    }
    
    prontera,155,183,0	script	NPC3	77,{
    	dispbottom "Variable 3: " + $npc_values[2];
    	end;
    }
    
    prontera,157,183,0	script	NPC4	77,{
    	dispbottom "Variable 4: " + $npc_values[3];
    	end;
    }
    
    -	script	yourscript_main	-1,{
    	end;
    OnInit:
    	cleararray $npc_values, 0, 50;
    	setarray $npc_values, 1, 2, 3, 4;
    	
    	// Shuffle the array
    	for (.@i = getarraysize($npc_values) - 1; .@i > 0; .@i--) {
    		.@j = rand(.@i + 1);
    		
    		.@temp = $npc_values[.@i];
    		$npc_values[.@i] = $npc_values[.@j];
    		$npc_values[.@j] = .@temp;
    	}
    	
    	end;
    }

     

    This is handy if you plan on duplicating your NPCs, such as:

    prontera,151,183,0	script	NPC#0	77,{
    	.@id = atoi(strnpcinfo(2));
    	
    	dispbottom "Variable " + (.@id + 1) + ": " + $npc_values[.@id];
    	end;
    }
    
    prontera,153,183,0	duplicate(NPC#0)	NPC#1	77
    prontera,155,183,0	duplicate(NPC#0)	NPC#2	77
    prontera,157,183,0	duplicate(NPC#0)	NPC#3	77
    
    -	script	yourscript_main	-1,{
    	end;
    OnInit:
    	cleararray $npc_values, 0, 50;
    	setarray $npc_values, 1, 2, 3, 4;
    	
    	// Shuffle the array
    	for (.@i = getarraysize($npc_values) - 1; .@i > 0; .@i--) {
    		.@j = rand(.@i + 1);
    		
    		.@temp = $npc_values[.@i];
    		$npc_values[.@i] = $npc_values[.@j];
    		$npc_values[.@j] = .@temp;
    	}
    	
     	end;
    }

     

    If you plan on shuffling the arrays often, you can use the following custom script command:

    // script.c
    BUILDIN_FUNC(shufflearray)
    {
    	struct script_data* data;
    	const char* name;
    	struct map_session_data* sd = NULL;
    	int array_size, i, j;
    	int32 id;
    	void * temp_val;
    
    	// Copy-paste from getarraysize
    	data = script_getdata(st, 2);
    
    	if (!data_isreference(data)) {
    		ShowError("buildin_shufflearray: not a variable\n");
    		script_reportdata(data);
    		script_pushnil(st);
    		st->state = END;
    		return SCRIPT_CMD_FAILURE;// not a variable
    	}
    
    	name = reference_getname(data);
    	id = reference_getid(data);
    
    	if (not_server_variable(*name)) {
    		sd = script_rid2sd(st);
    
    		if (sd == NULL)
    			return SCRIPT_CMD_SUCCESS;// no player attached
    	}
    
    	array_size = script_array_highest_key(st, sd, reference_getname(data), reference_getref(data));
    
    	// Start shuffling the array
    	for (i = array_size - 1; i > 0; i--) {
    		j = rand() % (i + 1);
    
    		temp_val = get_val2(st, reference_uid(id, i), reference_getref(data));
    		script_removetop(st, -1, 0);
    		set_reg(st, sd, reference_uid(id, i), name, get_val2(st, reference_uid(id, j), reference_getref(data)), reference_getref(data));
    		script_removetop(st, -1, 0);
    		set_reg(st, sd, reference_uid(id, j), name, temp_val, reference_getref(data));
    	}
    
    	return SCRIPT_CMD_SUCCESS;
    }
    
    // def
    BUILDIN_DEF(shufflearray,"r"),

    and use it as follow:

    -	script	yourscript_main	-1,{
    	end;
    OnInit:
    	cleararray $npc_values, 0, 50;
    	setarray $npc_values, 1, 2, 3, 4;
    	shufflearray $npc_values;
    	end;
    }

    If you need something more specific, you'll have to give us more details xD

    • Upvote 2
  12. Packet length 19 is the wanttoconnection packet. The issue is (almost always) caused by issues with packet obfuscation. Make sure you've recompiled your server and that you've disabled the feature on your client when you diffed it.

    Edit: That's the easy solution, the ideal one would be to turn on the packet obfuscation, set custom keys and change them in your executable.

  13. Well it's pretty straightforward:

    //script.c
    
    static int buildin_areakill_sub(struct block_list *bl,va_list ap)
    {
    	status_kill(bl);
    	return 0;
    }
    
    BUILDIN_FUNC(areakill)
    {
    	const char *mapname;
    	int16 m;
    	int x0, y0, x1, y1;
    
    	mapname = script_getstr(st,2);
    
    	if ((m = map_mapname2mapid(mapname)) < 0)
    		return SCRIPT_CMD_FAILURE;
    
    	x0 = script_getnum(st,3);
    	y0 = script_getnum(st,4);
    	x1 = script_getnum(st,5);
    	y1 = script_getnum(st,6);
    
    	map_foreachinarea(buildin_areakill_sub,m,x0,y0,x1,y1,BL_MOB);
    	// If you want to kill players as well, add BL_PC:
    	//map_foreachinarea(buildin_areakill_sub,m,x0,y0,x1,y1,BL_MOB|BL_PC);
    	return SCRIPT_CMD_SUCCESS;
    }
    
    // def
    BUILDIN_DEF(areakill,"siiii"),

    I'm not sure if you wanted to kill mobs or players, or both.

    • Upvote 1
  14. It's easier to create custom script commands (it is more reliable than using SQL and it is faster). Plus, you'll be able to have more freedom if you want to add more guild options:

    script.c
    
    /*==========================================
     *------------------------------------------*/
    BUILDIN_FUNC(getguildinfo)
    {
    	struct guild *g = NULL;
    	int result = 0;
    
    	if ((g = guild_search(script_getnum(st,2))) != NULL) {
    		int type = script_getnum(st,3);
    
    		switch(type) {
    			case 0:
    				result = g->guild_lv;
    				break;
    			case 1:
    				result = g->connect_member;
    				break;
    			case 2:
    				script_pushstrcopy(st, g->name);
    				return SCRIPT_CMD_SUCCESS;
    			//case 3:
    			//  etc...
    			//	break;
    			default:
    				ShowError("buildin_getguildinfo: unknown type '%d'.\n", type);
    				script_pushint(st, 0);
    				return SCRIPT_CMD_FAILURE;
    		}
    	}
    
    	script_pushint(st, result);
    	return SCRIPT_CMD_SUCCESS;
    }
    
    def method:
    BUILDIN_DEF(getguildinfo,"ii"),

     

    Recompile your server, then your event would be:

    OnPCLoadMapEvent:
    	if (getmapflag(strcharinfo(3), mf_gvg_castle)) {
    		.@guild_id = getcharid(2);
    		
    		if (BaseLevel < 175 || .@guild_id == 0 || getguildinfo(.@guild_id, 0) < 43 || getguildinfo(.@guild_id, 1) < 7) {
    			dispbottom "[ Server ]: You don't have a guild or your Base Level isn't high enough.", 0x00ff00;
    			warp "prontera", 155, 182;
    		}
    	}
    	
    	end;

     

    • Upvote 1
×
×
  • Create New...