Jump to content

AnnieRuru

Members
  • Posts

    2044
  • Joined

  • Last visited

  • Days Won

    51

Everything posted by AnnieRuru

  1. I usually don't support older revision, because some bugs always get fixed on latest SVN - script dropevent -1,{ OnPCLogoutEvent: if ( .using == getcharid(3) ) .using = 0; end; OnWhisperGlobal: if ( .using ) { dispbottom rid2name( .using ) +" is using this feature"; end; } .using = getcharid(3); addtimer 1000, strnpcinfo(0) +"::OnPCLogoutEvent"; while (1) { mes "main menu"; mes "map -> "+( ( getstrlen( .map$ ) )? .map$ : "<none>" ); if ( .count ) mes .count +" items will be dropped"; next; .@menu$ = ""; for ( .@i = 0; .@i < .count; .@i++ ) .@menu$ = .@menu$ + .amount[.@i] +"x "+ getitemname( .item[.@i] ) +":"; .@menu$ = .@menu$ + "Add:Choose map - "+ ( ( getstrlen( .map$ ) )? .map$ : "<none>" ) +":Start Event"; .@s = select( .@menu$ ) -1; if ( .@s <= .count ) { mes "input itemID - 0 to remove"; if ( input ( .@tmp, 0, 32767 ) ) mes "invalid range"; else if ( !.@tmp && .@s != .count ) { deletearray .item[.@s], 1; deletearray .amount[.@s], 1; .count--; } else if ( getitemname( .@tmp ) == "null" ) mes "invalid item ID"; else { mes "input amount"; if ( input( .@tmp2, 1, 30000 ) ) mes "invalid range"; else { .item[.@s] = .@tmp; .amount[.@s] = .@tmp2; if ( .@s == .count ) .count++; } } } else if ( .@s == .count +1 ) { mes "input a valid map name"; if ( input( .@tmp$, 4,12 ) ) mes "invalid range"; else if ( getmapusers( .@tmp$ ) == -1 ) mes "invalid map name"; else .map$ = .@tmp$; } else { if ( !getstrlen( .map$ ) || !.count ) mes "incomplete setup"; else break; } next; } mes "done"; announce strcharinfo(0) +" has generously dropping items in map "+ .map$, 0; freeloop 1; for ( .@i = 0; .@i < .count; .@i++ ) { while ( checkcell( .map$, .@x = rand(450), .@y = rand(450), cell_chknopass ) ); makeitem .item[.@i], .amount[.@i], .map$, .@x, .@y; } close; } if this whisper system doesn't work, I couldn't able to help anymore
  2. you should know that @timeused is a temporary player variable, its only meant for testing if you want to do this on live server, use a permanent player variable alpha script, use timeused1, beta script use timeused2 ... separate the variable out from each npc this is how I do this // mvp room summoner - script Main_MVP_warper -1,{ .@id = atoi( strnpcinfo(2) ); mes "[ ^0065DF"+ strnpcinfo(1) +"^000000 ]"; mes "Would you like to enter Room "+ .@id +" ?"; next; if ( select ( "Yes", "No" ) == 2 ) { mes "Good bye then."; close; } if ( getd( "timeused"+ .@id ) + 180000 > gettimetick(2) ) { // 180,000 seconds delay (5 hours) mes "We are revitalizing Room "+ .@id +", come back later."; close; } if ( getmapusers( "bossnia_0"+ .@id ) ) { mes "Sorry, a player is in the room."; close; } warp "bossnia_0"+ .@id, 180,71; deltimer strnpcinfo(0) +"::OnKick"; addtimer 600000, strnpcinfo(0) +"::OnKick"; // 600,000 seconds warp out (10 minutes) setd "timeused"+ .@id, gettimetick(2); end; OnKick: if ( strcharinfo(3) == "bossnia_0"+ .@id ) warp "SavePoint", 0,0; end; } quiz_00,56,31,4 duplicate(Main_MVP_warper) Alpha MVP#1 770 quiz_00,58,31,4 duplicate(Main_MVP_warper) Beta MVP#2 773 quiz_00,60,31,4 duplicate(Main_MVP_warper) Theta MVP#3 774 quiz_00,62,31,4 duplicate(Main_MVP_warper) Epsilon MVP#4 776
  3. I also failed to noticed the fault of 1st one lol for 2nd one, try refer the script made by Emistry in post#9 OnNPCKillEvent: if( getmonsterinfo( killedrid,MOB_MVPEXP ) )better I guessor like this /* alter table `char` add column mvp_rank int(11) default 0 after delete_date, add index (mvp_rank); */ prontera,155,188,5 script jsfkdsfhsk 100,{ .@nb = query_sql( "select name, mvp_rank from `char` where mvp_rank > 0 order by mvp_rank desc limit 10", .@name$, .@count ); if ( !.@nb ) { mes "no entry"; close; } for ( .@i = 0; .@i < .@nb; .@i++ ) mes ( .@i +1 )+". "+ .@name$[.@i] +" -> "+ .@count[.@i] +" points"; close; OnInit: .@nb = query_sql( "select id from mob_db where mexp > 0", .@mvp ); while ( .@i < .@nb ) { setd ".mvp"+ .@mvp[.@i], .@mvp[.@i]; .@i++; } end; OnNPCKillEvent: if ( getd( ".mvp"+ killedrid ) ) query_sql "update `char` set mvp_rank = mvp_rank +1 where char_id = "+ getcharid(0); end; }
  4. I try another one EDIT: thx to below
  5. I believe there are certain kinds of tricks here shouldn't be adding into SVN like while(!getmapxy) or attachrid-all-accounts there is a limit of npc can be having on a map doing something like this is just like a trickas this is able to bypass the numbers of npc having on a map example, when try @mapinfo, if you have 1 npc original from prontera, and 1 npc move from morocc into prontera @mapinfo in prontera tells you only having 1 npc, but actually having 2
  6. @Miles wahahaha, I thought since I came back, nobody would want to talk about payment openly according to script request rules, user are not allow to offer paid service if the topic starter did not say anything about offering payment have to discuss in PMs prontera,156,179,5 script Armor Echant 100,{ mes "[^0000FFArmor Enchant^000000]"; mes "Do you want to Enchant your ^0000FFArmor^000000?"; next; if ( select ( "Yes", "No" ) == 2 ) close; mes "[^0000FFArmor Enchant^000000]"; if ( !getequipisequiped( EQI_ARMOR ) ) { mes "You dont have any ^0000FFArmor^000000 that is being equipped."; close; } .@id = getequipid( EQI_ARMOR ); .@ref = getequiprefinerycnt( EQI_ARMOR ); .@card1 = getequipcardid( EQI_ARMOR, 0 ); .@card2 = getequipcardid( EQI_ARMOR, 1 ); .@card3 = getequipcardid( EQI_ARMOR, 2 ); .@card4 = getequipcardid( EQI_ARMOR, 3 ); if ( .@card1 == 255 || .@card1 == 254 ) { mes "I can't enchant a signed equipment."; close; } if ( .@card4 ) { mes "Sorry, this ^0000FFArmor^000000 has already been enchanted."; close; } mes "Select the type of stone that you'll use"; next; .@type = select( "Strength", "Inteligence", "Dexterity", "Agility", "Vitality", "Luck" ) -1; .@num = select( "1:2:3:4:5:6:7:8:9:10" ) -1; .@stone = 4700 + .@type *10 + .@num; mes "[^0000FFArmor Enchant^000000]"; mes "Are you sure you want to enchant your "+ getitemname(.@id) +" with "+ getitemname(.@stone); mes "The chance of success is "+ .rate[.@num] +"%"; next; if ( select( "Yes", "No" ) == 2 ) close; mes "[^0000FFArmor Enchant^000000]"; if ( countitem( .@stone ) < 3 ) { mes "Sorry, you need 3 "+ getitemname( .@stone ) +" to enchant this armor."; close; } delitem .@stone, 3; if ( rand(100) >= .rate[.@num] ) { mes "Sorry, the enchantment failed"; close; } delitem2 .@id, 1,1, .@ref, 0, .@card1, .@card2, .@card3, .@card4; getitem2 .@id, 1,1, .@red, 0, .@card1, .@card2, .@card3, .@stone; equip .@id; mes "Armor Enchancement successful !"; close; OnInit: setarray .rate, 100, 80, 70, 60, 50, 35, 25, 15, 10, 8; end; } its a rather simple script
  7. cannot find *.rsw file is client related, not scripts move to client support make sure you have data\[email protected] file being read
  8. I think I didn't do enough protection - script kfksdhfksjdfhs -1,{ OnInit: bindatcmd "dropevent", strnpcinfo(0) +"::Onaaa", 60,100; end; OnPCLogoutEvent: if ( .using == getcharid(3) ) .using = 0; end; Onaaa: if ( .using ) { dispbottom rid2name( .using ) +" is using this feature"; end; } .using = getcharid(3); addtimer 1000, strnpcinfo(0) +"::OnPCLogoutEvent"; while (1) { mes "main menu"; mes "map -> "+( ( getstrlen( .map$ ) )? .map$ : "<none>" ); if ( .count ) mes .count +" items will be dropped"; next; .@menu$ = ""; for ( .@i = 0; .@i < .count; .@i++ ) .@menu$ = .@menu$ + .amount[.@i] +"x "+ getitemname( .item[.@i] ) +":"; .@menu$ = .@menu$ + "Add:Choose map - "+ ( ( getstrlen( .map$ ) )? .map$ : "<none>" ) +":Start Event"; .@s = select( .@menu$ ) -1; if ( .@s <= .count ) { mes "input itemID - 0 to remove"; if ( input ( .@tmp, 0, 32767 ) ) mes "invalid range"; else if ( !.@tmp && .@s != .count ) { deletearray .item[.@s], 1; deletearray .amount[.@s], 1; .count--; } else if ( getitemname( .@tmp ) == "null" ) mes "invalid item ID"; else { mes "input amount"; if ( input( .@tmp2, 1, 30000 ) ) mes "invalid range"; else { .item[.@s] = .@tmp; .amount[.@s] = .@tmp2; if ( .@s == .count ) .count++; } } } else if ( .@s == .count +1 ) { mes "input a valid map name"; if ( input( .@tmp$, 4,12 ) ) mes "invalid range"; else if ( getmapusers( .@tmp$ ) == -1 ) mes "invalid map name"; else .map$ = .@tmp$; } else { if ( !getstrlen( .map$ ) || !.count ) mes "incomplete setup"; else break; } next; } mes "done"; announce strcharinfo(0) +" has generously dropping items in map "+ .map$, 0; freeloop 1; for ( .@i = 0; .@i < .count; .@i++ ) { while ( checkcell( .map$, .@x = rand(450), .@y = rand(450), cell_chknopass ) ); makeitem .item[.@i], .amount[.@i], .map$, .@x, .@y; } close; }
  9. OnNPCKillEvent: every time a player killing a monster without event label if ( rand(1000) < 5 ) in every 5/1000 *100% chance getitem 512,1; get an apple the full script should be - script test -1,{ OnNPCKillEvent: if ( rand(1000) < 5 ) getitem 512, 1; end; } OnNPCKillEvent shouldn't be put inside the function read wiki more http://rathena.org/wiki/Scripting https://rathena.svn.sourceforge.net/svnroot/rathena/trunk/doc/script_commands.txt
  10. e_tower mapflag bexp 125 1@tower mapflag bexp 125 2@tower mapflag bexp 125 3@tower mapflag bexp 125 4@tower mapflag bexp 125 5@tower mapflag bexp 125 6@tower mapflag bexp 125
  11. I think he means makeitem - script kfksdhfksjdfhs -1,{ OnInit: bindatcmd "dropevent", strnpcinfo(0) +"::Onaaa", 60,100; end; Onaaa: while (1) { mes "main menu"; next; .@menu$ = ""; for ( .@i = 0; .@i < .count; .@i++ ) { .@menu$ = .@menu$ + .amount[.@i] +"x "+ getitemname( .item[.@i] ) +":"; } .@menu$ = .@menu$ + "Add:Choose map:Start Event"; .@s = select( .@menu$ ) -1; if ( .@s <= .count ) { mes "input itemID - 0 to remove"; if ( input ( .@tmp, 0, 32767 ) ) mes "invalid range"; else if ( !.@tmp && .@s != .count ) { deletearray .item[.@s], 1; deletearray .amount[.@s], 1; .count--; } else if ( getitemname( .@tmp ) == "null" ) mes "invalid item ID"; else { mes "input amount"; if ( input( .@tmp2, 1, 30000 ) ) mes "invalid range"; else { .item[.@s] = .@tmp; .amount[.@s] = .@tmp2; .count++; } } next; } else if ( .@s == .count +1 ) { mes "input a valid map name"; if ( input( .@tmp$, 4,12 ) ) mes "invalid range"; else if ( getmapusers( .@tmp$ ) == -1 ) mes "invalid map name"; else .map$ = .@tmp$; } else break; } mes "done"; announce strcharinfo(0) +" has generously dropping items in map "+ .map$, 0; freeloop 1; for ( .@i = 0; .@i < .count; .@i++ ) { while ( checkcell( .map$, .@x = rand(450), .@y = rand(450), cell_chknopass ) ); makeitem .item[.@i], .amount[.@i], .map$, .@x, .@y; } close; } simple script, really
  12. mind explain ? this is bitmask, which I think you told me you still learning ? yeah lol, add it now
  13. /* create table auction_item ( auction_id int(11) primary key auto_increment, seller_id int(11), seller_name varchar(23), time datetime, nameid smallint(6), amount smallint(6), identify enum('0','1'), refine tinyint(3), attribute enum('0','1'), card0 smallint(6), card1 smallint(6), card2 smallint(6), card3 smallint(6) ) engine = innodb; drop table auction_item; create table auction_bidder ( id int(11) primary key auto_increment, auction_id int(11), buyer_id int(11), buyer_name varchar(23), time datetime, nameid smallint(6), amount smallint(6), identify enum('0','1'), refine tinyint(3), attribute enum('0','1'), card0 smallint(6), card1 smallint(6), card2 smallint(6), card3 smallint(6) ) engine = innodb; drop table auction_bidder; */ prontera,155,186,5 script kjsdhfksh 100,{ mes "aaa"; next; if ( select( "list", "sell" ) == 1 ) { .@nb = query_sql( "select * from auction_item limit 128", .@id, .@cid, .@name$, .@time$, .@itemid, .@amount, .@identify, .@refine, .@attribute, .@card1, .@card2, .@card3, .@card4 ); for ( .@i = 0; .@i < .@nb; .@i++ ) mes .@time$[.@i] +" -> "+ .@amount[.@i] +"x "+ callfunc( "getitemname2", .@itemid[.@i], .@identify[.@i], .@refine[.@i], .@attribute[.@i], .@card1[.@i], .@card2[.@i], .@card3[.@i], .@card4[.@i] ); close; } close2; callshop "auction_item", 2; end; OnSellItem: query_sql "insert into auction_item values ( null, "+ getcharid(0) +", '"+ escape_sql( strcharinfo(0) ) +"', now(), "+ @sold_nameid +", "+ @sold_quantity +", "+ @sold_identify +", "+ @sold_refine +", "+ @sold_attribute +", "+ @sold_card1 +", "+ @sold_card2 +", "+ @sold_card3 +", "+ @sold_card4 +" )"; delitem2 @sold_nameid, @sold_quantity, @sold_identify, @sold_refine, @sold_attribute, @sold_card1, @sold_card2, @sold_card3, @sold_card4; end; OnInit: npcshopattach "auction_item"; end; } - shop auction_item -1,501:-1 I'm writing this script 1/2 way and suddenly realize I've made a half-ass work before already http://www.eathena.ws/board/index.php?s=&showtopic=273365&view=findpost&p=1498394 @emistry, 2 years ago you not able to write, how about today ? if you not able to write that ctf script, I can take over
  14. prontera,154,187,5 script kidhfksdhf 100,{ mes "would you like to enter Room 1 ?"; next; if ( select ( "Yes", "No" ) == 2 ) { mes "good bye then"; close; } if ( @timeused + 20 > gettimetick(2) ) { // 20 seconds delay mes "you can't go in back in just 20 seconds"; close; } if ( getmapusers( "guild_vs2" ) ) { mes "sorry, a player is in the room"; close; } warp "guild_vs2", 0,0; deltimer strnpcinfo(0) +"::OnKick"; addtimer 10000, strnpcinfo(0) +"::OnKick"; // 10 seconds warp out @timeused = gettimetick(2); end; OnKick: if ( strcharinfo(3) == "guild_vs2" ) warp "SavePoint", 0,0; end; }
  15. lol ... now this has become a primitive command ... BUILDIN_FUNC(getguildmember) { struct guild *g = guild_search( script_getnum(st,2) ); int i, j = 0, type = 0; if ( script_hasdata(st,3) ) type = script_getnum(st,3); if ( g ) { for ( i = 0; i < MAX_GUILD; i++ ) { if ( g->member[i].account_id ) { switch ( type ) { case 2: mapreg_setreg( reference_uid( add_str("$@guildmemberaid"), j ),g->member[i].account_id ); break; case 1: mapreg_setreg( reference_uid( add_str("$@guildmembercid"), j ), g->member[i].char_id ); break; default: mapreg_setregstr( reference_uid( add_str("$@guildmembername$"), j ), g->member[i].name ); break; } j++; } } } mapreg_setreg( add_str("$@guildmembercount"), j ); return 0; } BUILDIN_DEF(getguildmember,"i?"), prontera,155,187,5 script dsasda 100,{ getguildmember getcharid(2), 0; getguildmember getcharid(2), 1; getguildmember getcharid(2), 2; for ( .@i = 0; .@i < $@guildmembercount; .@i++ ) dispbottom .@i +". "+ $@guildmembername$[.@i] +" -> "+( ( isloggedin( $@guildmemberaid[.@i] , $@guildmembercid[.@i] ) )? "online" : "offline" ); end; } without the g->member.online, needs to call out getguildmember 3 times but I guess people don't care about the nano-seconds difference anyways
  16. yeah, I kinda realized it a bit later I just merely fixing GodLesZ's patch previously BUILDIN_FUNC(getguildmember) { struct guild *g = guild_search( script_getnum(st,2) ); int i, j = 0, type = 0; if ( script_hasdata(st,3) ) type = script_getnum(st,3); if ( g ) { for ( i = 0; i < MAX_GUILD; i++ ) { if ( g->member[i].account_id ) { switch ( type ) { case 1: mapreg_setreg( reference_uid( add_str("$@guildmembercid"), j ), g->member[i].char_id ); break; case 2: mapreg_setreg( reference_uid( add_str("$@guildmemberaid"), j ),g->member[i].account_id ); break; case 3: mapreg_setreg( reference_uid( add_str("$@guildmemberonline"), j ),g->member[i].online ); break; default: mapreg_setregstr( reference_uid( add_str("$@guildmembername$"), j ), g->member[i].name ); break; } j++; } } mapreg_setreg( add_str("$@guildmembercount"), j ); script_pushint(st,1); } else { mapreg_setreg( add_str("$@guildmembercount"), 0 ); script_pushint(st,0); } return 0; } BUILDIN_DEF(getguildmember,"i?"), took out useless information, and add in online check, since the guild data itself does show online/offline prontera,155,187,5 script dsasda 100,{ getguildmember getcharid(2), 0; getguildmember getcharid(2), 3; for ( .@i = 0; .@i < $@guildmembercount; .@i++ ) dispbottom .@i +". "+ $@guildmembername$[.@i] +" -> "+( ( $@guildmemberonline[.@i] )? "online" : "offline" ); } EDIT : I wonder issit getpartymember also can do this online/offline ... EDIT2 : yeah can ... lol, perhaps I should remove this ? lol ? or add getpartymember allow $@partymemberonline ? for getguildinfo, I think that will go into separate topic // mmo.h ... struct guild { int guild_id; short guild_lv, connect_member, max_member, average_lv; uint64 exp; unsigned int next_exp; int skill_point; char name[NAME_LENGTH],master[NAME_LENGTH]; struct guild_member member[MAX_GUILD]; struct guild_position position[MAX_GUILDPOSITION]; char mes1[MAX_GUILDMES1],mes2[MAX_GUILDMES2]; int emblem_len,emblem_id; char emblem_data[2048]; struct guild_alliance alliance[MAX_GUILDALLIANCE]; struct guild_expulsion expulsion[MAX_GUILDEXPULSION]; struct guild_skill skill[MAX_GUILDSKILL]; unsigned short save_flag; // for TXT saving }; there are quite a few values seems useful to retrieve them guess you guys settle it by introducing getguildinfo I only wants the getguildmember, to make gvg event script better now I just realize ... // mmo.h struct map_session_data; struct guild_member { int account_id, char_id; short hair,hair_color,gender,class_,lv; uint64 exp; int exp_payper; short online,position; char name[NAME_LENGTH]; struct map_session_data *sd; unsigned char modified; }; if add in getguildinfo ... there're some info that depends on it like guild position and exp_payper ...
  17. I have similar experience lol I mostly work on high rate server ... all of them having this modifications
  18. doing something like this doesn't need to use donpcevent nor function can be done entirely with goto or switch note of some of the stuffs that you need to use source modification, and query_sql like motd, spy guild ... I have done something like this 5 years ago, and I think most experienced scripters here also has made this kind of utility script on a live server before (but less feature than yours) this kind of script usually don't share ... since its made specifically for a server but well, since its your 1st script, I think you can learn from something from making this especially, I learned a lot about script optimizing
  19. goto is not evil in *athena script engine rather it is faster than for-loop or while-loop in certain situation if you can read source you'll see these emulated script commands are also creating a temporary label in the source the main difference in functions and donpcevent, is donpcevent the able to start another script instance, and function is able to send in arguments (donpcevent couldn't) ... its very hard to explain these kind of stuffs unless you are telling what kind of things that you want to do these things can only learn from experiences, its not like you can understand them just by reading a guide try explain briefly what is your project about ... event script ? utility script ?
  20. to calculate those can hit and those can't hit is always battle_check_target in battle.c http://rathena.org/board/topic/72933-pvp-request/#entry153174
  21. hmm .. lol after I test it, only type 0,1,2 works, type 3 doesn't work at all, so I attempt to fix this BUILDIN_FUNC(getguildmember) { struct guild *g = guild_search( script_getnum(st,2) ); int i, j = 0, type = 0; if ( script_hasdata(st,3) ) type = script_getnum(st,3); if ( g ) { if ( type != 3 ) { for ( i = 0; i < MAX_GUILD; i++ ) { if ( g->member[i].account_id ) { switch ( type ) { case 1: mapreg_setreg( reference_uid( add_str("$@guildmembercid"), j ), g->member[i].char_id ); break; case 2: mapreg_setreg( reference_uid( add_str("$@guildmemberaid"), j ),g->member[i].account_id ); break; default: mapreg_setregstr( reference_uid( add_str("$@guildmembername$"), j ), g->member[i].name ); break; } j++; } } mapreg_setreg( add_str("$@guildlv"), g->guild_lv ); mapreg_setreg( add_str("$@guildconnected"), g->connect_member ); mapreg_setreg( add_str("$@guildmax"), g->max_member ); mapreg_setreg( add_str("$@guildexp"), (int)g->exp ); mapreg_setreg( add_str("$@guildmembercount"), j ); } else { for ( i = 0; i < MAX_GUILDALLIANCE; i++ ) { if ( g->alliance[i].guild_id ) { mapreg_setreg( reference_uid( add_str("$@guildallianceid"), j ), g->alliance[i].guild_id ); mapreg_setregstr( reference_uid( add_str("$@guildalliancename$"), j ), g->alliance[i].name ); mapreg_setreg( reference_uid( add_str("$@guildallianceflag"), j ), g->alliance[i].opposition ); j++; } } mapreg_setreg( add_str("$@guildalliancecount"), j ); } script_pushint(st,1); } else { mapreg_setreg( add_str("$@guildmembercount"), 0 ); mapreg_setreg( add_str("$@guildalliancecount"), 0 ); script_pushint(st,0); } return 0; } added a flag to determine opposition or alliance example prontera,155,187,5 script dsasda 100,{ getguildmember getcharid(2); for ( .@i = 0; .@i < $@guildmembercount; .@i++ ) dispbottom .@i +". "+ $@guildmembername$[.@i]; getguildmember getcharid(2), 3; for ( .@i = 0; .@i < $@guildalliancecount; .@i++ ) dispbottom .@i +". "+ $@guildalliancename$[.@i] +" -> "+( ( $@guildallianceflag[.@i] )? "Opposition" : "Alliance" ); end; }
  22. your script doesn't really work I tried to ban myself (127.0.0.1) without *, it kicks me off, but I can login back like nothing happens and your script doesn't have enough command protection ... if ( !.@atcmd_numparameters ) message strcharinfo(0), "usage: @banip <ip> <reason>";
  23. topic merge then bg_emp_reward_0.5.txt
  24. if you mean to make the group only to 1 map, can be done via battleground script but if it is server-wide, its source modification faction system I actually don't recommend having faction system in RO unless you are good source coder yourself this source code very hard to maintain
×
×
  • Create New...