Jump to content

GmOcean

Community Contributors
  • Content Count

    371
  • Joined

  • Last visited

  • Days Won

    8

Posts posted by GmOcean


  1. You would simply just spawn more than 1 monster, each taking up 1 cell of the " wall ". Then, when the last one is killed you remove the wall like you did in your test script.

    Then if you have multiple walls. You give each wall a different name, and the monsters that are spawned along that wall, have same Event label, just different from the other walls. ( Does that make sense? )

    Example:

    -	script	Barricade	-1,{OnInit:	setwall "te_aldecas3",81,228,1,4,6,"christmas_wall_a";	setwall "te_aldecas3",81,232,1,4,6,"christmas_wall_b";	setwall "te_aldecas3",81,234,1,4,6,"crhistmas_wall_c";		for( .@i = 0; .@i < 4; .@i++ ){		monster "te_aldecas3",( 81 + .@i ),228, "Barricade", 1905, 1, strnpcinfo(0)+"::OnBarricadeA";		monster "te_aldecas3",( 81 + .@i ),232, "Barricade", 1905, 1, strnpcinfo(0)+"::OnBarricadeB";		monster "te_aldecas3",( 81 + .@i ),234, "Barricade", 1905, 1, strnpcinfo(0)+"::OnBarricadeC";	}		monster "te_aldecas3", 81, 236, "Emperium", 2107, 1, strnpcinfo(0)+"::OnEmperiumDead";	end;	OnBarricadeA:if( !mobcount(mapname, strnpcinfo(0)+"::OnBarricadeA") ){	delwall "christmas_walla_a";}end;OnBarricadeB:if( !mobcount(mapname, strnpcinfo(0)+"::OnBarricadeB") ){	delwall "christmas_walla_b";}end;OnBarricadeB:if( !mobcount(mapname, strnpcinfo(0)+"::OnBarricadeC") ){	delwall "christmas_walla_c";}end;OnEmperiumDead:monster "te_aldecas3", 81, 236, "SUPER MONSTER", 1002, 1, strnpcinfo(0)+"::OnSuperDead";end;OnSuperDead:dispbottom "All done.";end;}

  2. You just need to add a variable check at the label when each monster dies:

    OnBarricade0:    ++.barricade[0];    if( .barricade[0] == 6 ){ delwall "conq_RL01"; }    dispbottom "There are "+ (6 - .barricade[0]) +" barricades left.";    end;

    Just add checks like that for each wall you design.

    Same goes for spawning a monster after breaking the emperium:

    OnEmperium:// spawn monster command here.end;

  3. The problem is here:

    if (.frifin$ == 1 && (gettime(4)==3)) {

    And here:

    if (.satfin$ == 1 && (gettime(4)==4)) {

    You're trying to compare a string variable to a number.

     

    Just erase the ' $ ' sign, and things should fix itself.

    Also, you shouldn't be using ' goto ' anymore as the command will soon be removed. Instead use the command: callsub <label>{,<argument>,...<argument>};

     

    Edit:

    @Offtopic - While I do not remember you (sorry lol) I do remember the script. It allowed users to have a sub-job allowing them to select upto 5 skills from that sub-job and add it to their own skills. Sort of making a hybrid.

    it was far over powered lol. EDP + Any power hit skill @.@;


  4. Okay, well looking at your script I see you used the command addrid. Are you using rAthena or Hercules as your emulator?

    If your using rAthena, than you should be able to go back to the regular switch(select) option instead of doevent. But also while replacing your for(loop) and using addrid as Emistry suggested.

     

    If you're using Hercules as your emulator, then I'll have to look around for another solution.

     

     

    Edit: Okay, since you said *doevent, didn't work. Then I think the only option left, without src modifications is a timer using this command: addtimer <ticks>,"NPC::OnLabel";

    This should allow you to give each player their own timer, which upon activation you can prompt them with the menu. Can also just put the timer to 1 tick, to make it appear practically instantly.


  5. I don't believe that to be your issue, I'll take a good look at it later.

    Edit:

    Okay, after looking at your script, I couldn't find anything fundamentally wrong. And it doesn't seem to be the switch(select); since these do work within loops.


  6. Without seeing the script that caused this to happen ( if it was even a script ), we will be unable to help you. All I can say from that alone is, you may have a script that runs as soon as a player logs in that attempts to delete a timer that isn't there. But I doubt this is the case since we'd see a debug from the script command.

     

    Another guess is it would be src related since the console is failing to parse the timer at this point. Perhaps someone more knowledge-able can assist with this.


  7. File Name: check_bound command

    File Submitter: GmOcean

    File Submitted: 16 Nov 2014

    File Category: Plugins

     

    A simple command to check if the player currently has a bound item in their inventory. Thus eliminating the need to use getinventorylist; to do the check. Additionally, you can check for a number of parameters regarding that item.

    *checkbound(<item_id>{,<bound_type>{,<refine>{,<attribute>{,<card_1>{,<card_2>{,<card_3>{,<card_4>}}}}}}});This command allows you to check whether or not the attached player has the specified bound item in their inventory.Valid bound types are: 1 - Account Bound 2 - Guild Bound 3 - Party Bound 4 - Character BoundOptional Parameters: refine - checks to see if the item is refined to the given number. attribute - whether the item is broken (1) or not (0) card 1,2,3,4 - checks to see if the specified cards are compounded on the item as well.

    Example:

    if( checkbound(1205) ){    mes "You have a bound Cutter";    close;} else {    mes "You do not have a bound Cutter";    close;}

    Example 2:

    if( checkbound(1205, 2) ){    mes "You have a guild_bound Cutter";    close;} else {    mes "You don't have the required item.";    close;}

    Example 3:

    if( checkbound(1205, 2, 7) ){    mes "You have a +7 guild_bound Cutter.";    close;} else {    mes "You don't have the required item.";    close;}

     

    Click here to download this file


  8. Well, I wrote a variation of your script, but it's not 100% complete, nor am I fully positive it's working 100%, but script_checker says there is no errors in the syntax. Perhaps, you can use it for a comparison to your code, and find the results you need:

    prontera,164,167,0	script	Quest Master	857,{mes ( .@npc$ = "[^FF0000 Quest Master ^000000" );mes "[^00FF00 Generate Requirement ^000000]";mes ""+ getitemname(.currency[0]) +" x "+ .currency[1] +"";mes "[^0000FF Taking Requirement ^000000]";mes ""+ getitemname(.currency[2]) +" x "+ .currency[3] +"";next;mes .@npc$;switch( select( "[Turn in all quests]", "[Obtain Quest]", "[Abandon Quest]", "[Generate Quests]"+ ( (getgmlevel() >= 99)?":[Generate Random Quests]" : "" )+"" ) ){	case 1:		for( .@i = 0; .@i < getarraysize( @quests$ ); .@i++ ){			explode( .@array$, @quests$[.@i], ":" );			switch( atoi(.@array$[5]) ){				case 1:					if( .@array$[4] == .@array$[3] ){						.@quests[.@a] = atoi( .@array$[6] );						.@a++;					}					break;				case 2:				case 3:					if( countitem( atoi(.@array$[2]) ) == atoi(.@array$[3]) ){						.@quests[.@a] = atoi( .@array$[6] );						.@a++;					}					break;			}		}		if( !.@quests[0] ){			mes "You don't have any completed quests.";			close;		}		getitem .currency[0], (.currency[1] * getarraysize(.@quests));		for( .@i = 0; .@i < getarraysize( @quests$ ); .@i++ ){			explode( .@array$, @quests[.@i], ":" );			for( .@j = 0; .@j < getarraysize( .@quests ); .@j++ ){				if( .@quests[.@j] == .@array$[6] ){					@quests[.@i] = "";					.@quests$ = .@quests$ + ""+ .@array$[6] +":";				}			}		}		callsub iRedeem, .@quests$; // Updates quests to have a reward so posters can redeem it.		mes "All quests have been redeemed.";		mes "Come back again, and complete some more.";		close;							case 2:		if( max_active && ( getarraysize(@quests$) >= .max_active ) ){			mes "You can not actively pursue any more quests at the moment.";			mes "You need to either complete your current quests or abandoned them.";			close;		}		mes "Only a maximum of "+ .select_limit +" available quests will be listed from a category at a time.";		mes "Select a category.";		next;		mes .@npc$;		.@select = select( "[Hunting] - [Random Difficulty]", "[Item Gather] - [Random Difficulty]", "[Cooking] - [Hard Difficulty]");		.@count = query_sql("SELECT * FROM `Paid_Quest` WHERE `TrackID` = '0' AND `type` = '"+ .@select +"'", .@quest_id, .@name$, .@pub_id, .@track_id, .@mob_id, .@itme_id, .@tar_amt, .@type, .@time);		if( !.@count ){			mes "There are no quests of this type available at this time.";			close;		}		iQSelectLoop:			if( .@count < ( .select_limit ) ){				.@select2 = ( .@count + 1 );			} else {				.@select2 = ( .select_limit + 1 );			}			.@select = .@select2;			while( .@select == .@select2 ){				.@quest_menu$ = "";				.@i = 1;				while( .@i < .@select2 ){					.@a = rand( getarraysize(.@name$) );					if( !compare( .@quest_menu$, .@name$[.@a] ) ){						.@quest_menu$ = .@quest_menu$ + .@name$[.@a] +":";						.@quest_select[.@i] = .@a;						.@i++;					}				}				.@quest_menu$ = .@quest_menu$ + "[^0000FF More Quests ^000000]";				.@select = select(	.@quest_menu$ );			}			.@a = .@quest_select[.@select];			mes .@name$[.@a];			switch( .@type[0] ){				case 1:					mes "Objective: "+ getmonsterinfo( .@mob_id[.@a], 0 ) +"";					mes "Kill amount: "+ .@tar_amt[.@a] +"";					break;				case 2:				case 3:					mes "Objective: "+ getitemname( .@item_id[.@a] ) +"";					mes ""+ ( (.@type[0] == 2)? "Collect Amount" : "Cook Amount" ) +": "+ .@tar_amt[.@a] +"";					break;			}			mes "----------------------------------";			mes "Do you accept?";			next;			if( select( "Yes, I accept", "No, let me choose another" ) == 2 ){				callsub iQSelectLoop;				end;			}			@quests$[getarraysize(@quests$)] = .@name$[.@a] +":"+ .@mob_id[.@a] +":"+ .@item_id[.@a] +":"+ .@tar_amt[.@a] +":"+ 0 +":"+ .@type[0] +":"+ .@quest_id[.@a] +"";			query_sql("UPDATE `Paid_Quest` SET(`TrackID`) VALUES('"+ getcharid(0) +"') WHERE `id` = '"+ .@quest_id[.@a] +"'");			mes .@npc$;			mes "Quest Accepted.";			mes "All quests will be abandoned upon logging out.";			close;				case 3:			mes "Did you want to abandon all your quests, or a specific one?";			next;			mes .@npc$;			if( select( "One quest", "All quests" ) == 2 ){				@quests$ = "";				mes "All quests have been abandoned.";				close;			}			for( .@i = 0; .@i < getarraysize(@quests$); .@i++ ){				explode( .@array$, @quests$[.@i], ":" );				.@quest_menu$ = .@quest_menu$ + .@array$[0] +":";			}			mes "Which quest did you want to abandon?";			next;			.@select = select( .@quest_menu$ );			explode( .@array$, @quests$[( .@select - 1 )], ":" );			mes .@array$[0];			switch( .@array$[5] ){				case 1:					mes "Objective: "+ getmonsterinfo( .@array$[1], 0 ) +"";					mes "Progress: "+ .@array$[4] +" / "+ .@array$[3] +"";					break;								case 2:				case 3:					mes "Objective: "+ getitemname( .@array$[2] ) +"";					mes "Progress: "+ countitem( .@array$[2] ) +" / "+ .@array$[3] +"";					break;			}			mes "----------------------------------";			mes "Are you sure you want to abandon this quest?";			next;			if( select( "Yes, abandon the quest.", "No, I've changed my mind." ) == 2 ){ close; }			@quests$[( .@select - 1)] = "";			mes "Quest has been abandoned.";			close;					case 4:			// Only Item Collection quests are available to create as of now.			mes "What item did you want to post for a quest?";			input .@item;			if( !getitemname( .@item ) == "null" ){				mes "^^FF0000 INVALID ITEM";				close;			}			mes "How many did you want to request?";			mes "Minimum: 1","Maximum: 1,000";			input .@amt,1,1000;			query_sql("INSERT INTO `Paid_Quests` (`name`,`pub_id`,`item_id`,`tar_amt`,`type`,`time`) VALUES('"+ escape_sql("Collection: "+ getitemname(.@item) +"") +"','"+ getcharid(0) +"','"+ .@item +"','"+ .@amt +"','2','"+ gettimetick(2) +"')");			next;			mes "Quest, successfully submitted.";			close;					case 5:			// GM only quest generator.			end;}iRedeem:explode( .@array$, getarg(0), ":" );for( .@x = 0; .@x < getarraysize( .@array$ ); .@x++ ){	query_sql("UPDATE `Paid_Quests` SET(`reward`) VALUES('1') WHERE `id` = '"+ atoi(.@array$[.@x]) +"'");}return;end;OnInit:// [0] = Item Required to post a quest. | [1] = Amount needed.// [2] = Item Required to accept a quest. | [3] = Amount needed.setarray .currency[0], 501, 5, 502, 1;// Amount of quests to post at a time.// Default: 3.select_limit = 3;// [0] = Start of Food item IDs// [1] = End of Food item IDs.setarray .cook_id[0], 12040, 12100;// Max amount of quests each player can actively pursue at a time.// Default: 3.max_active = 3;end;}// SQL TABLE TO BE USED./*CREATE TABLE `master_main`.`paid_quests` (  `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,  `name` VARCHAR(255) NOT NULL,  `pub_id` INT(11) NOT NULL,  `TrackID` INT(11) NOT NULL DEFAULT 0,  `mob_id` INT(11) NOT NULL DEFAULT 0,  `item_id` INT(11) NOT NULL DEFAULT 0,  `tar_amt` INT(11) NOT NULL DEFAULT 0,  `type` INT(11) NOT NULL DEFAULT 0,  `time` BIGINT NOT NULL DEFAULT 0,  PRIMARY KEY (`id`),  UNIQUE INDEX `id_UNIQUE` (`id` ASC));*/

     

    *Note - This hasn't been tested. Reason being, I do not want to setup a sql table, add quests and the such to do the testing for this. You can post back errors, you find and I'll will provide support for it, but right now I'm not going to alter my sql schemas*.


  9. Well some may not consider this an overhaul, but really I think a few commands are in need of a small adjustment

    Namely the following:

    *getinventorylist;*getpartymember <party id>{,<type>};*getguildmember <guild id>{,<type>};*getmobdrops(<mob id>)

    These 4 commands all having something in common.

    1. They produce array's using $@variables. ( *getinventorylist being the exception using @variables ).

    2. They all produce a variable that deals with their array size.

    3. They don't erase that array.

     

    What I'm suggesting is simple.

    1. Produce .@(temp_scope) variables.

    2. eliminate this variable entirely, or leave it for backwards compatibility.

    3. Erase the array upon execution of command. This will allow the use of getarraysize(); for each of these commands, which imo should have been the norm.

     

    These are just my thoughts on the matter. Anyone else feel the same way?


  10. Well, if you want to add individual monsters to your script for achievements then it is very simple. In fact, it won't take away from your script much at all. Just in corporate this bit of code in to your script:

    This is for OnInit, as it will handle the variables used.

    OnInit:// Each element of the array can be set to the amount needed to be killed.// Allowing you to dynamically set each monster achievements, sort of .// Example, if I wanted a regular monster achievement for 500 kills just add this:// .mob_master[500] = 500; // 500 kills will be another achievement. Listed as: 'Monster_Name Master' '500'..mob_master[10000] = 10000;.mob_master[20000] = 20000;.mob_master[30000] = 30000;end;

    This is for OnNPCKillEvent, as it will deal with calculating each monster's kills.

    OnNPCKillEvent:setd ""+ killedrid +"", getd(""+ killedrid +"") + 1;if( getd(""+ killedrid +"") == .mob_master[getd(""+ killedrid +"")] ){	.@prize = getd(""+ killedrid +"");	announce ""+ strcharinfo(0) +" just achieved "+ getmonsterinfo( killedrid, 0 ) +" Master "+ getd(""+ killedrid +"") +"",bc_all;	query_sql("INSERT INTO `achievement` VALUES (NULL, "+ getcharid(3) +","+ getcharid(0) +",'"+ escape_sql(strcharinfo(0)) +"','"+ getmonsterinfo( killedrid, 0 )+" Master', "+ getd(""+ killedrid +"") +")");	callsub OnRegularMaster, .@prize; // Call the label, with the value to be used for giving prizes.}end;

    Lastly, this is the prize section, they only get red potions for now, but that's because I didn't get into detail on what they should receive xD

    OnRegularMaster:switch( getarg(0) ){	case 10000:		getitem 501,1;		break;	case 20000:		getitem 501,2;		break;	case 30000:		getitem 501,3;		break;	// More as needed.}end;

     

    That's it really O.o; Heck you could even just make it another NPC all together, since it uses your sql_database as it's placer. The only part I didn't do was the ranking system. I'll leave that to you =P

    So as an example, let's say I wanted an achievement for 1,000,000 kills of a mosnter. I'd just do the following ( This is a sample script ):

    -    script    monster_kills    -1,{OnInit:.mob_master[1000000] = 1000000;end;OnNPCKillEvent:setd ""+ killedrid +"", getd(""+ killedrid +"") + 1;// Checks to see if the monsters I killed is equal to the array.// This works, because if 0->999,999 are all 0, then it won't do anything, since my kills will always be 1 or higher.// Meaning any element that has a value of 0 is disabled and will not be an achievement, which means you can even enable/disable some for events =P.if( getd(""+ killedrid +"") == .mob_master[getd(""+ killedrid +"")] ){    // announcement;    // query_sql;    // This will make me call the label with the given value as getarg(0). In this sample, case, it will send 1,000,000 as the number.    callsub OnRegularMonster, getd(""+ killedrid +"");}end;OnRegularMonster:switch( getarg(0) ){    // This means I killed 1,000,000 monsters, and there is a reward for doing so.    // If you do not add a case for the relative achievement, it just means they don't get a reward for doing it.    // So, let's say they got an achievement for killing 10, and that's too easy, well we just don't make a case 10: and then they don't get a reward.    // Still get the achievement though =P    case 1000000:        getitem 501,1;        break;}end;

     

    O.o hope this helps. Make use of it if you want, or don't up to you.


  11. Hi I'm requesting 2 script_commands that are pretty self explanatory: duplicatenpc & duplicateremove.

    I know this is possible since we are able to duplicate npcs for instances. But I want to do that outside of instances. I made an attempt myself, but sadly fell short (who am I kidding, far, very far) of what I believe is needed.

    *duplicateremove({"<npc_name>"});

     

     

    BUILDIN(duplicateremove){	struct npc_data *nd;	const char* source_name;	if( script_hasdata(st,2) ){		source_name = script_getstr(st,2);		nd = npc->name2id(source_name);		if( nd == NULL ){			ShowError("script_duplicateremove: NPC not found: %sn", source_name);			script_pushint(st,0);			return true;		}	} else		nd = (struct npc_data *)map->id2bl(st->oid);	if( nd->src_id == NULL ) // Remove all duplicates for this source npc		map->foreachnpc(npc->unload_dup_sub, nd->bl.id);	else // Remove just this duplicate		npc->unload(nd,1);	script_pushint(st,1);	return true;}

     

     

    *duplicatenpc("<source_npc_name>","<target_npc_name>","<hidden_name>","<map_name>",<x>,<y>,<dir>);

     

     

    BUILDIN(duplicatenpc){	struct npc_data *snd, *tnd;	const char *source_name, *target_name, *hidden_name;	const char *map_name;	int x, y, dir;	int mapid;	int i;	char targetname[NAME_LENGTH];	x = script_getnum(st,5);	y = script_getnum(st,6);	dir = script_getnum(st,7);	source_name = script_getstr(st,2);	snd = npc->name2id(source_name);	if( snd == NULL ){		ShowError("script_duplicatenpc: NPC not found: %sn", source_name);		script_pushint(st,0); // Source NPC not found.		return true;	}	if( strlen( (target_name = script_getstr(st,3)) ) + strlen( (hidden_name = script_getstr(st,4)) ) > 23 ){		ShowError("script_duplicatenpc: Target_Name + Hidden_Name is too long. (max = 23chars)n");		script_pushint(st,-1); // Target Name = Hidden Name too long.		return false;	}	mapid = map->mapname2mapid(map_name);	if( mapid == NULL ){		ShowError("script_duplicatenpc: %s map does not exist.n", map_name);		script_pushint(st,-2);		return false;	}	CREATE(tnd, struct npc_data, 1);	strcat(targetname, target_name);	strncat(targetname, "#", 1);	strncat(targetname, hidden_name, strlen(hidden_name));	safestrncpy(tnd->name, target_name, sizeof(tnd->name));	safestrncpy(tnd->exname, hidden_name, sizeof(tnd->exname));	tnd->bl.prev = tnd->bl.next = NULL;	tnd->bl.m = mapid;	tnd->bl.x = x;	tnd->bl.y = y;	tnd->bl.id = npc->get_new_npc_id();	tnd->class_ = snd->class_;	tnd->speed = snd->speed;	tnd->src_id = snd->src_id;	tnd->bl.type = BL_NPC;	tnd->subtype = snd->subtype;	switch( tnd->subtype ){		case SCRIPT:			tnd->u.scr.xs = snd->u.scr.xs;			tnd->u.scr.ys = snd->u.scr.ys;			tnd->u.scr.script = snd->u.scr.script;			tnd->u.scr.label_list = snd->u.scr.label_list;			tnd->u.scr.label_list_num = snd->u.scr.label_list_num;			break;		case SHOP:		case CASHSHOP:			tnd->u.shop.shop_item = snd->u.shop.shop_item;			tnd->u.shop.count = snd->u.shop.count;			break;		case WARP:			if( !battle_config.warp_point_debug )				tnd->class_ = WARP_CLASS;			else				tnd->class_ = WARP_DEBUG_CLASS;			tnd->u.warp.xs = snd->u.warp.xs;			tnd->u.warp.ys = snd->u.warp.ys;			tnd->u.warp.mapindex = snd->u.warp.mapindex;			tnd->u.warp.x = snd->u.warp.x;			tnd->u.warp.y = snd->u.warp.y;			break;	}	if( mapid >= 0 ){		map->addnpc(mapid, tnd);		tnd->ud = &npc->base_ud;		tnd->dir = dir;		npc->setcells(tnd);		map->addblock(&tnd->bl);		if( tnd->class_ >= 0 ){			status->set_viewdata(&tnd->bl, tnd->class_);			if( map->list[tnd->bl.m].users )				clif->spawn(&tnd->bl);		}	} else {		map->addiddb(&tnd->bl);	}	strdb_put(npc->name_db, tnd->exname, tnd);	if( tnd->subtype != SCRIPT )		return true;	for( i = 0; i < tnd->u.scr.label_list_num; i++ ){		if( npc->event_export(tnd, i)){			script_pushint(st,0);			return false;		}		npc->timerevent_export(tnd, i);	}	tnd->u.scr.timerid = snd->u.scr.timerid;	script_pushint(st,1);	return true;}

     

     

     

    So, seeing as how I failed miserably, I've come to ask you expert's to do this for me. I don't know if my attempt will prove useful but there it is in all it's pile of mess lol.

    If payment is required, then send me a PM and we can discuss terms there. Otherwise, I'd hope to get this done for free D:

     

    Please & Thank You!


  12. That would be under mob.c for AI controls

     

    /*========================================== * AI of MOB whose is near a Player *------------------------------------------*/bool mob_ai_sub_hard(struct mob_data *md, int64 tick) {	struct block_list *tbl = NULL, *abl = NULL;	int mode;	int view_range, can_move;	if(md->bl.prev == NULL || md->status.hp <= 0)		return false;	if (DIFF_TICK(tick, md->last_thinktime) < MIN_MOBTHINKTIME)		return false;	md->last_thinktime = tick;	if (md->ud.skilltimer != INVALID_TIMER)		return false;	if(md->ud.walktimer != INVALID_TIMER && md->ud.walkpath.path_pos <= 3)		return false;	// Abnormalities	if(( md->sc.opt1 > 0 && md->sc.opt1 != OPT1_STONEWAIT && md->sc.opt1 != OPT1_BURNING && md->sc.opt1 != OPT1_CRYSTALIZE )	  || md->sc.data[SC_DEEP_SLEEP] || md->sc.data[SC_BLADESTOP] || md->sc.data[SC__MANHOLE] || md->sc.data[SC_CURSEDCIRCLE_TARGET]) {		//Should reset targets.		md->target_id = md->attacked_id = 0;		return false;	}	if (md->sc.count && md->sc.data[SC_BLIND])		view_range = 3;	else		view_range = md->db->range2;	mode = status_get_mode(&md->bl);	can_move = (mode&MD_CANMOVE)&&unit->can_move(&md->bl);	if (md->target_id) {		//Check validity of current target. [Skotlex]		tbl = map->id2bl(md->target_id);		if (!tbl || tbl->m != md->bl.m		 || (md->ud.attacktimer == INVALID_TIMER && !status->check_skilluse(&md->bl, tbl, 0, 0))		 || (md->ud.walktimer != INVALID_TIMER && !(battle_config.mob_ai&0x1) && !check_distance_bl(&md->bl, tbl, md->min_chase))		 || ( tbl->type == BL_PC		   && ((((TBL_PC*)tbl)->state.gangsterparadise && !(mode&MD_BOSS))		     || ((TBL_PC*)tbl)->invincible_timer != INVALID_TIMER)		    )		) {			//Unlock current target.			if (mob->warpchase(md, tbl))				return true; //Chasing this target.			mob->unlocktarget(md, tick-(battle_config.mob_ai&0x8?3000:0)); //Immediately do random walk.			tbl = NULL;		}	}	// Check for target change.	if( md->attacked_id && mode&MD_CANATTACK )	{		if( md->attacked_id == md->target_id )		{	//Rude attacked check.			if( !battle->check_range(&md->bl, tbl, md->status.rhw.range)			   &&  ( //Can't attack back and can't reach back.					(!can_move && DIFF_TICK(tick, md->ud.canmove_tick) > 0 && (battle_config.mob_ai&0x2 || (md->sc.data[SC_SPIDERWEB] && md->sc.data[SC_SPIDERWEB]->val1)					|| md->sc.data[SC_WUGBITE] || md->sc.data[SC_VACUUM_EXTREME] || md->sc.data[SC_THORNS_TRAP]					|| md->sc.data[SC__MANHOLE])) // Not yet confirmed if boss will teleport once it can't reach target.					|| !mob->can_reach(md, tbl, md->min_chase, MSS_RUSH)					)			&&  md->state.attacked_count++ >= RUDE_ATTACKED_COUNT			&&  !mob->skill_use(md, tick, MSC_RUDEATTACKED) // If can't rude Attack			&&  can_move && unit->escape(&md->bl, tbl, rnd()%10 +1)) // Attempt escape			{	//Escaped				md->attacked_id = 0;				return true;			}		}		else		if( (abl = map->id2bl(md->attacked_id)) && (!tbl || mob->can_changetarget(md, abl, mode) || (md->sc.count && md->sc.data[SC__CHAOS]))) {			int dist;			if( md->bl.m != abl->m || abl->prev == NULL			 || (dist = distance_bl(&md->bl, abl)) >= MAX_MINCHASE // Attacker longer than visual area			 || battle->check_target(&md->bl, abl, BCT_ENEMY) <= 0 // Attacker is not enemy of mob			 || (battle_config.mob_ai&0x2 && !status->check_skilluse(&md->bl, abl, 0, 0)) // Cannot normal attack back to Attacker			 || (!battle->check_range(&md->bl, abl, md->status.rhw.range) // Not on Melee Range and ...			    && ( // Reach check			         (!can_move && DIFF_TICK(tick, md->ud.canmove_tick) > 0 && (battle_config.mob_ai&0x2 || (md->sc.data[SC_SPIDERWEB] && md->sc.data[SC_SPIDERWEB]->val1)			       || md->sc.data[SC_WUGBITE] || md->sc.data[SC_VACUUM_EXTREME] || md->sc.data[SC_THORNS_TRAP]			       || md->sc.data[SC__MANHOLE])) // Not yet confirmed if boss will teleport once it can't reach target.			       || !mob->can_reach(md, abl, dist+md->db->range3, MSS_RUSH)			       )			    )			) {				// Rude attacked				if (md->state.attacked_count++ >= RUDE_ATTACKED_COUNT				&& !mob->skill_use(md, tick, MSC_RUDEATTACKED) && can_move				&& !tbl && unit->escape(&md->bl, abl, rnd()%10 +1))				{	//Escaped.					//TODO: Maybe it shouldn't attempt to run if it has another, valid target?					md->attacked_id = 0;					return true;				}			}			else			if (!(battle_config.mob_ai&0x2) && !status->check_skilluse(&md->bl, abl, 0, 0)) {				//Can't attack back, but didn't invoke a rude attacked skill...			} else {				//Attackable				if (!tbl || dist < md->status.rhw.range || !check_distance_bl(&md->bl, tbl, dist)					|| battle->get_target(tbl) != md->bl.id)				{	//Change if the new target is closer than the actual one					//or if the previous target is not attacking the mob. [Skotlex]					md->target_id = md->attacked_id; // set target					if (md->state.attacked_count)					  md->state.attacked_count--; //Should we reset rude attack count?					md->min_chase = dist+md->db->range3;					if(md->min_chase>MAX_MINCHASE)						md->min_chase=MAX_MINCHASE;					tbl = abl; //Set the new target				}			}		}		//Clear it since it's been checked for already.		md->attacked_id = 0;	}	// Processing of slave monster	if (md->master_id > 0 && mob->ai_sub_hard_slavemob(md, tick))		return true;	// Scan area for targets	if (!tbl && mode&MD_LOOTER && md->lootitem && DIFF_TICK(tick, md->ud.canact_tick) > 0	 && (md->lootitem_count < LOOTITEM_SIZE || battle_config.monster_loot_type != 1)	) {		// Scan area for items to loot, avoid trying to loot if the mob is full and can't consume the items.		map->foreachinrange (mob->ai_sub_hard_lootsearch, &md->bl, view_range, BL_ITEM, md, &tbl);	}	if ((!tbl && mode&MD_AGGRESSIVE) || md->state.skillstate == MSS_FOLLOW) {		map->foreachinrange (mob->ai_sub_hard_activesearch, &md->bl, view_range, DEFAULT_ENEMY_TYPE(md), md, &tbl, mode);	} else if ((mode&MD_CHANGECHASE && (md->state.skillstate == MSS_RUSH || md->state.skillstate == MSS_FOLLOW)) || (md->sc.count && md->sc.data[SC__CHAOS])) {		int search_size;		search_size = view_range<md->status.rhw.range ? view_range:md->status.rhw.range;		map->foreachinrange (mob->ai_sub_hard_changechase, &md->bl, search_size, DEFAULT_ENEMY_TYPE(md), md, &tbl);	}	if (!tbl) { //No targets available.		if (mode&MD_ANGRY && !md->state.aggressive)			md->state.aggressive = 1; //Restore angry state when no targets are available.		/* bg guardians follow allies when no targets nearby */		if( md->bg_id && mode&MD_CANATTACK ) {			if( md->ud.walktimer != INVALID_TIMER )				return true;/* we are already moving */			map->foreachinrange (mob->ai_sub_hard_bg_ally, &md->bl, view_range, BL_PC, md, &tbl, mode);			if( tbl ) {				if( distance_blxy(&md->bl, tbl->x, tbl->y) <= 3 || unit->walktobl(&md->bl, tbl, 1, 1) )					return true;/* we're moving or close enough don't unlock the target. */			}		}		//This handles triggering idle walk/skill.		mob->unlocktarget(md, tick);		return true;	}	//Target exists, attack or loot as applicable.	if (tbl->type == BL_ITEM)	{	//Loot time.		struct flooritem_data *fitem;		if (md->ud.target == tbl->id && md->ud.walktimer != INVALID_TIMER)			return true; //Already locked.		if (md->lootitem == NULL)		{	//Can't loot...			mob->unlocktarget (md, tick);			return true;		}		if (!check_distance_bl(&md->bl, tbl, 1))		{	//Still not within loot range.			if (!(mode&MD_CANMOVE))			{	//A looter that can't move? Real smart.				mob->unlocktarget(md,tick);				return true;			}			if (!can_move) //Stuck. Wait before walking.				return true;			md->state.skillstate = MSS_LOOT;			if (!unit->walktobl(&md->bl, tbl, 1, 1))				mob->unlocktarget(md, tick); //Can't loot...			return true;		}		//Within looting range.		if (md->ud.attacktimer != INVALID_TIMER)			return true; //Busy attacking?		fitem = (struct flooritem_data *)tbl;		//Logs items, taken by (L)ooter Mobs [Lupus]		logs->pick_mob(md, LOG_TYPE_LOOT, fitem->item_data.amount, &fitem->item_data, NULL);		if (md->lootitem_count < LOOTITEM_SIZE) {			memcpy (&md->lootitem[md->lootitem_count++], &fitem->item_data, sizeof(md->lootitem[0]));		} else {	//Destroy first looted item...			if (md->lootitem[0].card[0] == CARD0_PET)				intif->delete_petdata( MakeDWord(md->lootitem[0].card[1],md->lootitem[0].card[2]) );			memmove(&md->lootitem[0], &md->lootitem[1], (LOOTITEM_SIZE-1)*sizeof(md->lootitem[0]));			memcpy (&md->lootitem[LOOTITEM_SIZE-1], &fitem->item_data, sizeof(md->lootitem[0]));		}		if (pcdb_checkid(md->vd->class_))		{	//Give them walk act/delay to properly mimic players. [Skotlex]			clif->takeitem(&md->bl,tbl);			md->ud.canact_tick = tick + md->status.amotion;			unit->set_walkdelay(&md->bl, tick, md->status.amotion, 1);		}		//Clear item.		map->clearflooritem (tbl);		mob->unlocktarget (md,tick);		return true;	}	//Attempt to attack.	//At this point we know the target is attackable, we just gotta check if the range matches.	if (md->ud.target == tbl->id && md->ud.attacktimer != INVALID_TIMER) //Already locked.		return true;	if (battle->check_range (&md->bl, tbl, md->status.rhw.range))	{	//Target within range, engage		if(tbl->type == BL_PC)			mob->log_damage(md, tbl, 0); //Log interaction (counts as 'attacker' for the exp bonus)		if(!(mode&MD_RANDOMTARGET))			unit->attack(&md->bl,tbl->id,1);		else { // Attack once and find new random target			int search_size = (view_range < md->status.rhw.range) ? view_range : md->status.rhw.range;			unit->attack(&md->bl,tbl->id,0);			tbl = battle->get_enemy(&md->bl, DEFAULT_ENEMY_TYPE(md), search_size);			// If no target was found, keep atacking the old one			if( tbl ) {				md->target_id = tbl->id;				md->min_chase = md->db->range3;			}		}		return true;	}	//Out of range...	if (!(mode&MD_CANMOVE))	{	//Can't chase. Attempt an idle skill before unlocking.		md->state.skillstate = MSS_IDLE;		if (!mob->skill_use(md, tick, -1))			mob->unlocktarget(md,tick);		return true;	}	if (!can_move)	{	//Stuck. Attempt an idle skill		md->state.skillstate = MSS_IDLE;		if (!(++md->ud.walk_count%IDLE_SKILL_INTERVAL))			mob->skill_use(md, tick, -1);		return true;	}	if (md->ud.walktimer != INVALID_TIMER && md->ud.target == tbl->id &&		(			!(battle_config.mob_ai&0x1) ||			check_distance_blxy(tbl, md->ud.to_x, md->ud.to_y, md->status.rhw.range)	)) //Current target tile is still within attack range.		return true;	//Follow up if possible.	if(!mob->can_reach(md, tbl, md->min_chase, MSS_RUSH) ||		!unit->walktobl(&md->bl, tbl, md->status.rhw.range, 2))		mob->unlocktarget(md,tick);	return true;}

     

     

    More specifically this particular section of the above code:

    	// Check for target change.	if( md->attacked_id && mode&MD_CANATTACK )	{		if( md->attacked_id == md->target_id )		{	//Rude attacked check.			if( !battle->check_range(&md->bl, tbl, md->status.rhw.range)			   &&  ( //Can't attack back and can't reach back.					(!can_move && DIFF_TICK(tick, md->ud.canmove_tick) > 0 && (battle_config.mob_ai&0x2 || (md->sc.data[SC_SPIDERWEB] && md->sc.data[SC_SPIDERWEB]->val1)					|| md->sc.data[SC_WUGBITE] || md->sc.data[SC_VACUUM_EXTREME] || md->sc.data[SC_THORNS_TRAP]					|| md->sc.data[SC__MANHOLE])) // Not yet confirmed if boss will teleport once it can't reach target.					|| !mob->can_reach(md, tbl, md->min_chase, MSS_RUSH)					)			&&  md->state.attacked_count++ >= RUDE_ATTACKED_COUNT			&&  !mob->skill_use(md, tick, MSC_RUDEATTACKED) // If can't rude Attack			&&  can_move && unit->escape(&md->bl, tbl, rnd()%10 +1)) // Attempt escape			{	//Escaped				md->attacked_id = 0;				return true;			}		}		else		if( (abl = map->id2bl(md->attacked_id)) && (!tbl || mob->can_changetarget(md, abl, mode) || (md->sc.count && md->sc.data[SC__CHAOS]))) {			int dist;			if( md->bl.m != abl->m || abl->prev == NULL			 || (dist = distance_bl(&md->bl, abl)) >= MAX_MINCHASE // Attacker longer than visual area			 || battle->check_target(&md->bl, abl, BCT_ENEMY) <= 0 // Attacker is not enemy of mob			 || (battle_config.mob_ai&0x2 && !status->check_skilluse(&md->bl, abl, 0, 0)) // Cannot normal attack back to Attacker			 || (!battle->check_range(&md->bl, abl, md->status.rhw.range) // Not on Melee Range and ...			    && ( // Reach check			         (!can_move && DIFF_TICK(tick, md->ud.canmove_tick) > 0 && (battle_config.mob_ai&0x2 || (md->sc.data[SC_SPIDERWEB] && md->sc.data[SC_SPIDERWEB]->val1)			       || md->sc.data[SC_WUGBITE] || md->sc.data[SC_VACUUM_EXTREME] || md->sc.data[SC_THORNS_TRAP]			       || md->sc.data[SC__MANHOLE])) // Not yet confirmed if boss will teleport once it can't reach target.			       || !mob->can_reach(md, abl, dist+md->db->range3, MSS_RUSH)			       )			    )			) {				// Rude attacked				if (md->state.attacked_count++ >= RUDE_ATTACKED_COUNT				&& !mob->skill_use(md, tick, MSC_RUDEATTACKED) && can_move				&& !tbl && unit->escape(&md->bl, abl, rnd()%10 +1))				{	//Escaped.					//TODO: Maybe it shouldn't attempt to run if it has another, valid target?					md->attacked_id = 0;					return true;				}			}			else			if (!(battle_config.mob_ai&0x2) && !status->check_skilluse(&md->bl, abl, 0, 0)) {				//Can't attack back, but didn't invoke a rude attacked skill...			} else {				//Attackable				if (!tbl || dist < md->status.rhw.range || !check_distance_bl(&md->bl, tbl, dist)					|| battle->get_target(tbl) != md->bl.id)				{	//Change if the new target is closer than the actual one					//or if the previous target is not attacking the mob. [Skotlex]					md->target_id = md->attacked_id; // set target					if (md->state.attacked_count)					  md->state.attacked_count--; //Should we reset rude attack count?					md->min_chase = dist+md->db->range3;					if(md->min_chase>MAX_MINCHASE)						md->min_chase=MAX_MINCHASE;					tbl = abl; //Set the new target				}			}		}

    My guess, is before it attempts to say it was rude attacked you need to add a check to see if the mvp is in an icewall.state ( do we use this? ). Sadly, I'm no where near able to do that with src code :/ but i'm pretty positive this is the bit that needs to be edited to do what you want. D:


  13. Okay, first ' bug ' I found was a mistake on my part, just need to make 2 changes.

    1:

    .@quest_menu$ = .@quest_menu$ +":"+ .@quest_name$[.@temp_variable];TO.@quest_menu$ = .@quest_menu$ + .@quest_name$[.@temp_variable] +":";

    2:

    .@quest_menu$ = .@quest_menu$ +":[^0000FF More Quests ^000000]";TO.@quest_menu$ = .@quest_menu$ +"[^0000FF More Quests ^000000]";

     

    My apologizes. Was pretty tired when I wrote it originally. At any rate, it needs to be changed as such, or the menu options will be pushed down 1.

    It's a small simple fix. But would have caused a lot of problems when trying to create a quest. All the information would have been messed up @.@;

     

    As for tracking the quest takers, just use the labels: OnNPCKillEvent:  &  OnPCLogoutEvent: & OnPCDieEvent:

     

    Example of tracking when logging out:

    OnPCLogoutEvent:query_sql("UPDATE `database` SET `taker_id` VALUE '0' WHERE `taker_id` = '"+ getcharid(3) +"'");end;

    That would essentially erase their ID from the custom quest database's taker id, making the quest available again.

     

    Example of when the player dies:

    OnPCDieEvent:if( countstr( ""+ killerrid +"" ) >= 7 && killerrid != getcharid(3) ){ end; } // Player died in PvP Combat, and did not commit suicide. Shouldn't count against them.@quest_death++; // Player either was killed by a monster or committed suicide.if( @quest_death >= .quest_death_limit ){ // Player died too many times so remove all his quests.    @quest_death = 0;    deletearray @quests$; // This would be an example of the array which holds all the data for his quests.    query_sql("UPDATE `database` SET `taker_id` VALUE '0' WHERE `taker_id` = '"+ getcharid(3) +"'");    dispbottom "You have died too many times to monsters. You have abandoned all of your quests.";}end;

     

    Finally example of tracking when he kills a monster:

    OnNPCKillEvent:for( .@i = 0; .@i < getarraysize( @quests$ ); .@i++ ){	explode( .@tmp_array$, @quests$[.@i] );	// .@tmp_array$[0] = Quest Name	// .@tmp_array$[1] = Monster (Objective) name	// .@tmp_array$[2] = Amount (kill, collect, etc...)	// .@tmp_array$[3] = Total needed. (kill, collect, etc...)		// Now we check to see if the monster we killed was a requirement for a quest the player has.	if( killedrid == .@tmp_array$[1] ){		//The monster was a requirement so we add +1 kills.		.@tmp_array$[2] = ""+ ( atoi( .@tmp_array$[2] ) + 1 ) +"";				// Check the amount we killed against the amount we need to kill.		if( .@tmp_array$[2] >= .@tmp_array$[3] ){			// We killed more or just enough so now we give player reward and remove quest.			// Get reward.                        getitem(501,1);			// remove quest from sql_database			query_sql("DELETE * FROM `database` WHERE `quest_name` = '"+ .@tmp_array[0]$ +"'");			// Now that player got their reward, it's time to erase the quest from the player's tracker.			@quests$[.@i] = "";		}	}}// Nothing else to do here so we end it.end;

     

    To use my example of tracking when they kill a monster you need to store the quest information in a certain way when they accept the quest:

    @quests$[getarraysize(@quests$)] = ""+ .@name$ +":"+ .@mob_id +":"+ 0 +":"+ .@kill_required +"";

    This will store all the data of 1 quest into a single 'element' with in the array. Meaning that each player will be able to keep track of 2.1billion quests at a time.

     

    This method will work for tracking monster kills. However, for cooking / gathering tracking, that would probably just be handled by countitem(<item_id>);


  14. This is determined by the mobs rude status skill. If an MVP is unable to reach the player, by walking then they will eventually use their " rude " skill, which just so happens to be teleport.

    mob_skill_db.txt located in p(re)/folder

    Examples:

    1039,Baphomet@AL_TELEPORT,idle,26,1,10000,0,0,yes,self,rudeattacked,,,,,,,,1039,Baphomet@AL_TELEPORT,walk,26,1,5000,0,5000,yes,self,rudeattacked,,,,,,,,

    You see that Baphomet like other MVPs will teleport away. So to prevent that from happening, just remove those 2 skills and they should stay there and get beat like the meanies they are D:


  15. Step 2: Of your requests, can easily be done like this:

    // Sets .@size to the amount of quests we were able to grab that fits our query.// Our query says to select all information from any quest that isn't taken yet.// This is determined by assuming if a quest is taken, the `taker_id` will be filled with that player's character id#..@size = query_sql("SELECT * FROM quest_database WHERE `taker_id`='0' AND `type`='hunting'", .@quest_name$, .@quest_id, .@poster_name$, .@obj1, .@amt1); // Etc, just add variables as you need them to match the table in your database..@select = 4; // We set this to 4 so we can use our loop below and not have to worry about anything else.while( .@select == 4 ){ // As long as player keeps selecting the 4th option in the menu it will repeat this loop.	.@sel_quest = 0; // Set this to 0 here to reset the variable for use in the loop below.	.@quest_menu$ = ""; // Again, set this variable to "" to reset the variable for use in the loop below.	while( .@sel_quest != 3 ){ // This attempts to grab 3 different quests and present them as options for the player.		.@temp_variable = rand( getarraysize( .@quest_name$ ) ); // Randomly select a quest.		if( !compare( .@quest_menu$, .@quest_name$[.@temp_variable] ) ){ // Checks to see if we already chose this quest.			// Since we didn't use this one before we put it in the menu.			.@quest_menu$ = .@quest_menu$ +":"+ .@quest_name$[.@temp_variable];			.@sel_quest++; // Set to +1 to let loop know we selected a quest.		}	}	// We finished selecting our quests, now we just add a 4th option to the menu saying more quests.	.@quest_menu$ = .@quest_menu$ +":[^0000FF More Quests ^000000]";		// Then we present them with the menu and have them decide.	.@select = select( .@quest_menu$ );}// If they selected a menu option that was not [More Quests], then they come here, other wise they stay in the loop.// Here we update the sql table with the player's character id# in the taker's position to say they accepted the quest.// This is also where you begin to set things up to track their progress.

    That's the basics for making the player choose a quest, that has been previously generated from a specified type.

×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.