Jump to content

Tokei

Members
  • Posts

    545
  • Joined

  • Last visited

  • Days Won

    66

Community Answers

  1. Tokei's post in Weird client error - shadowtable was marked as the answer   
    Well, 
    JT_MD_Airboat_Poring
    JT_MD_AIRBOAT_PORING

    are not the same. If anything, you can also just set it directly:
    [jobtbl.JT_MD_AIRBOAT_PORING] = 1.2, or [20887] = 1.2,  
  2. Tokei's post in Guild tax over 50% not working was marked as the answer   
    Heya,
    Make sure you have the increase guild tax limit patch when you made your client. The restriction is client-sided, not server-sided.
  3. Tokei's post in Act editor remove all frames script was marked as the answer   
    There are many ways to achieve that:
    foreach (var action in act) { action.Frames = action.Frames.Take(1).ToList(); } If you need more... "control" with indexes and which animations to remove exactly, you can do it this way too:
    for (int aid = 0; aid < act.Actions.Count; aid++) { for (int fid = act.Actions[aid].Frames.Count - 1; fid >= 1; fid--) { act[aid].Frames.RemoveAt(fid); } } If you want to apply this to a batch of files, you can do it this way too:
    var path = @"C:\Sprites\"; foreach (var file in Directory.GetFiles(path, "*.act")) { var actFile = new Act(file); actFile.Actions.ForEach(p => p.Frames = p.Frames.Take(1).ToList()); actFile.Save(file); }  
  4. Tokei's post in [Warning]: Unexpected type for argument 1. Expected string was marked as the answer   
    Heya,
    The error message tells you the error:
    if (.msg_die) message [email protected], "You have been killed by "+ [email protected]$; if (.msg_kill) message [email protected], "You just killed "+ [email protected]$; The "message" script command on rAthena requires two strings as parameters. First parameter is the player name (which is pretty weird, not gonna lie), so you should change those to:
    if (.msg_die) message [email protected]$, "You have been killed by "+ [email protected]$; if (.msg_kill) message [email protected]$, "You just killed "+ [email protected]$; Though clearly this script wasn't meant for rAthena.
  5. Tokei's post in Getting this error after fixing with NEMO was marked as the answer   
    Heya,
    Well, as the error says, your table index is nil, meaning either EFST_PAD_READER_KNIGHT or EFST_PAD_READER_CRUSADER is not defined (probably both and more below too). It's an issue with your lub, make sure your effectid.lub matches properly. This has nothing to do with NEMO.
  6. Tokei's post in GRF Editor (Failed to decompress data.) was marked as the answer   
    You should open a new thread/question, as this is from 2017. Anyway, it simply means the entry is corrupted or encrypted, most likely encrypted.
  7. Tokei's post in Need a fix for Random Option System was marked as the answer   
    It's a client issue. Change the clif_add_random_options function to skip empty entries. Something like...
    /// Fills in part of the item buffers that calls for variable bonuses data. [Napster] /// A maximum of 5 random options can be supported. static uint8 clif_add_random_options( struct ItemOptions buf[MAX_ITEM_RDM_OPT], struct item* it ){ nullpo_retr( 0, it ); uint8 count = 0; memset(buf, 0, sizeof(struct ItemOptions) * MAX_ITEM_RDM_OPT); for( int i = 0; i < MAX_ITEM_RDM_OPT; i++ ){ if( it->option[i].id ){ buf[count].index = it->option[i].id; // OptIndex buf[count].value = it->option[i].value; // Value buf[count].param = it->option[i].param; // Param1 count++; } } #if MAX_ITEM_RDM_OPT < 5 for( ; i < 5; i++ ){ buf[i].index = 0; // OptIndex buf[i].value = 0; // Value buf[i].param = 0; // Param1 } #endif return count; }  
  8. Tokei's post in How to add parameter on a custom command ?? was marked as the answer   
    Well you can get the full command used with something like:
    OnNavigate: [email protected]$ = implode([email protected]_parameters$, " "); if (isloggedin(getcharid(3, [email protected]$)) != 1) { message strcharinfo(0), "Target player '" + [email protected]$ + "' is offline or do not exist."; } sleep2 1; unitwalkto getcharid(3), getcharid(3, [email protected]$); end; Though, unitwalkto is very limited and will most likely fail in most scenarios.
  9. Tokei's post in Act Editor - Resize Sprite to smaller was marked as the answer   
    Heya,
    Have you tried Scripts > Magnify?
     
    Use 0.5 to reduce the size by half. It's a decimal value that has no range limit; you can use -0.5 if you wanted to reduce by half and inverse the image too.
  10. Tokei's post in Stylist logout bug was marked as the answer   
    You have a few ways of doing that. Your script doesn't cover all possible abusable ways even with logout. It is possible to cancel a script without logging out. One trick is to "abuse" the addtimer behavior. While a script is running, the timed event will not run until the current script is finished (it is queued). As for the logging out issue, you can simply use OnPCLogoutEvent. One drawback from this is that you need to delete the timer as otherwise it will revert whenever you exit the NPC. So I added another menu option to confirm your style, " ~ I want this style". I'd probably remove the " ~ Revert to original" if I were you as it's not needed at all. Cancelling will do that for you and that way you can keep 4 menu options and keep things clean.
    prontera,76,96,1 script Stylist#custom_stylist 122,{ setarray [email protected][1], getbattleflag("max_cloth_color"), getbattleflag("max_hair_style"), getbattleflag("max_hair_color"); setarray [email protected][1], LOOK_CLOTHES_COLOR, LOOK_HAIR, LOOK_HAIR_COLOR; set [email protected], select(" ~ Cloth color: ~ Hairstyle: ~ Hair color"); set [email protected], getlook([email protected][[email protected]]); set [email protected],1; @stylist_look_type = [email protected][[email protected]]; @stylist_look_value = getlook(@stylist_look_type); addtimer 1, strnpcinfo(0) + "::OnPCLogoutEvent"; while(1) { setlook [email protected][[email protected]], [email protected]; message strcharinfo(0),"This is style #"[email protected]+"."; set [email protected]$, " ~ Next (^0055FF"+(([email protected][email protected][[email protected]])[email protected]+1:1)+"^000000): ~ Previous (^0055FF"+(([email protected]!=1)[email protected]:[email protected][[email protected]])+"^000000): ~ Jump to...: ~ I want this style"; switch(prompt([email protected]$)) { case 1: set [email protected], (([email protected] != [email protected][[email protected]]) ? [email protected]+1 : 1); break; case 2: set [email protected], (([email protected] != 1) ? [email protected] : [email protected][[email protected]]); break; case 3: message strcharinfo(0),"Choose a style between 1 - "[email protected][[email protected]]+"."; input [email protected],0,[email protected][[email protected]]; if ([email protected]) set [email protected], rand(1,[email protected][[email protected]]); break; case 4: // You have to set the values to 0 and remove the timer event once the colors are chosen and confirmed // Your code currently doesn't have a way out of your loops, so I added this one. @stylist_look_type = @stylist_look_value = 0; deltimer strnpcinfo(0) + "::OnPCLogoutEvent"; end; default: set [email protected], [email protected]; setlook [email protected][[email protected]], [email protected]; end; } } end; OnPCLogoutEvent: if (@stylist_look_type != 0) { setlook @stylist_look_type, @stylist_look_value; } deltimer strnpcinfo(0) + "::OnPCLogoutEvent"; end; }  
  11. Tokei's post in NPC does not automatically start have player not attached error was marked as the answer   
    Heya,
    Well, considering the error that is given to you, I'd assume this is the issue:
    OnEventAnnounce: .status = 1; for([email protected] = .countdown; i > 0; i--) { The variable i should be [email protected]
  12. Tokei's post in ACT Editor: Script Runner For Each All Act Files was marked as the answer   
    Heya,
    The issue is that act files must be loaded with their sprite counterpart to function properly. The act file stores the width and height of the image for each layer and this data is loss otherwise (causing your position issues). The script works fine otherwise.
    foreach (var file in Directory.GetFiles(@"C:\Users\medakas\data\sprite\¸ó½ºÅÍ\", "*.act")) { var act1 = new Act(file, file.ReplaceExtension(".spr")); act1.AnimationExecute(4, action => { action.Frames = new List<Frame> { new Frame() }; }); act1.Save(file); }  
    Edit: Actually, I looked more into it and that explanation turns out to be somewhat incorrect. At some point, the behavior was changed so that a Sprite object is automatically created if an Act object is created without specifying the Sprite path. The problem with that is that the saving function rewrites the Width/Height values if a Sprite object exists. Act Editor abstracts the Width/Height properties of the layers as that'd just be too annoying for the endusers to setup in the first place.
    But this is a bug; if no image is associated with the layer with a Sprite present, it should not default to 0 Width/Height.
  13. Tokei's post in Achievement System - Reset? was marked as the answer   
    Heya,
    There are multiple approaches for this issue. The first one is "lazy" but it is usually good enough for most people and it is much simpler. You run a script on all players to remove their achievement and then you run a SQL command to remove all achievements. So something along these lines:
    OnClock0000: // At midnight, everyday donpcevent strnpcinfo(0) + "::OnResetAchievement"; end; OnResetAchievement: donpcevent strnpcinfo(0) + "::OnResetAchievementSub"; query_sql("DELETE FROM `achievement` WHERE `id` = 100"); end; OnResetAchievementSub: addrid 0; achievementremove 100; end; The issue with the above is that the query is ran on the map-server and therefore will lag you depending on the size of your achievement table (the same goes for any query ran on the map-server). This solution is also not "atomic" and can fail in some situations where a player is logging on while the script is being ran, and the char-server has already sent the achievement data and hasn't been received by the player yet. The chances of the last scenario happening are very low though.
    An alternative would be to keep the achievements in the database, but only delete them when the player logs on. You would still have to delete them on the online players though. So something like this:
    OnClock0000: donpcevent strnpcinfo(0) + "::OnResetAchievement"; $ach_100_start = gettimetick(2); end; OnPCLoginEvent: [email protected] = 100; [email protected] = achievementinfo([email protected], ACHIEVEINFO_COMPLETEDATE); if ([email protected] > 0 && [email protected] < $ach_100_start) { achievementremove 100; } end; OnResetAchievement: addrid 0; achievementremove 100; end; The above works relatively well. It doesn't have concurrency issues, it won't lag your server either. The downside there would be that the achievements would still exist in your sql table. Also, both of the above need to attach a script to the player, which will cause issues if a player is already talking to a NPC (it will terminate the previously ran script).
    The proper solution would be to run the SQL query on the char-server instead using an inter-server packet (or using a SQL thread, if that PR is merged). Then you would run a custom script command that iterates through online players and removes their achievement with achievement_remove without ever attaching a script to them. The last solution is the best and will work in all scenarios with no in-game lag. Though it is somewhat annoying to code I suppose.
    (Those scripts were not tested, so you may have to fix the errors yourself.)
  14. Tokei's post in How to solve *sd write access violation was marked as the answer   
    Heya,
    Where to start... "sd" is a variable. What you're attempting to do is similar to...
    int x; int y; y = x + 5; It's impossible to assign a value to y if you don't know what the value of x is first. In your code, sd is the variable, but it is never really assigned. It is set to NULL as default (which is good practice), but NULL doesn't mean anything. If you look more closely, the value is actually assigned with "sd = map_charid2sd(script_getnum(st, 3))". It is commented with the "//" in front of it; you need to remove all of those "//" in front of the code.
    The "write access violation" comes from you attempting to do sd->guild_invite = guild_id, which in reality corresponds to NULL->guild_invite = guild_id. Obviously NULL doesn't "exist" and you can't assign guild_id to nothing. It's called a violation because you do not have the rights to write to NULL (to keep things simple anyway).
    sd isn't a cache, sd is the "map session data". It's what holds the information about your player's current session. You should not be doing queries to your database to update your character (unless your server is offline), that will not work and it's a bad approach. I don't know what you're trying to update though.
  15. Tokei's post in Randomizing Random Option was marked as the answer   
    Heya,
    If you want to achieve that behavior, you'll have to change a whole bunch of code. There was an earlier version of the system which greatly simplified the randomness of random option enchants. The system would rely on 3 files:
    db/re/item_randomopt_db.txt (remains the same)
    db/re/item_randoption_equips.txt (this replaces the db/re/mob_drops.txt file, the random options are instead attached to the weapon itself, rather than the mob the weapon drops from)
    db/re/item_randoption_group.txt (remains mostly the same, except you add another optional column for the max value of the field)
    item_randoption_equips.txt would look as below. You first define the random options for the specific weapons as such:
    // Items Random Option Database // // Structure of Database: // Item_ID,RandomOption_GroupId1{,RandomOption_GroupId2,RandomOption_GroupId3,RandomOption_GroupId4,RandomOption_GroupId5} 28705,RDMOPTG_Crimson_Weapon_1,RDMOPTG_Crimson_Weapon_2,RDMOPTG_Crimson_Weapon_3 13327,RDMOPTG_Crimson_Weapon_1 28604,RDMOPTG_Crimson_Weapon_1 28007,RDMOPTG_Crimson_Weapon_1 1839,RDMOPTG_Crimson_Weapon_1 1498,RDMOPTG_Crimson_Weapon_1 16040,RDMOPTG_Crimson_Weapon_1 13454,RDMOPTG_Crimson_Weapon_1 1443,RDMOPTG_Crimson_Weapon_1 28106,RDMOPTG_Crimson_Weapon_1 21015,RDMOPTG_Crimson_Weapon_1 1939,RDMOPTG_Crimson_Weapon_1 1995,RDMOPTG_Crimson_Weapon_1 The random options for each groups are defined in item_randoption_group.txt. In the sample below, you'll notice a new colum for the 2nd and 3rd random options I chose, which will make the value vary between 30~50 for the earth resistance and 5~10 for... the others.
    // Items Random Option Database // // Structure of Database: // Group_ID,rate,randopt_id1,randopt_value1,randopt_param1 // Crimson Weapon RDMOPTG_Crimson_Weapon_1,1,RDMOPT_WEAPON_ATTR_NOTHING,0,0 RDMOPTG_Crimson_Weapon_1,1,RDMOPT_WEAPON_ATTR_WATER,0,0 RDMOPTG_Crimson_Weapon_1,1,RDMOPT_WEAPON_ATTR_GROUND,0,0 RDMOPTG_Crimson_Weapon_1,1,RDMOPT_WEAPON_ATTR_FIRE,0,0 RDMOPTG_Crimson_Weapon_1,1,RDMOPT_WEAPON_ATTR_WIND,0,0 RDMOPTG_Crimson_Weapon_1,1,RDMOPT_WEAPON_ATTR_SAINT,0,0 RDMOPTG_Crimson_Weapon_2,1,RDMOPT_ATTR_TOLERACE_GROUND,30,0,50 RDMOPTG_Crimson_Weapon_3,1,RDMOPT_DAMAGE_PROPERTY_GROUND_USER,5,0,10 RDMOPTG_Crimson_Weapon_3,1,RDMOPT_DAMAGE_PROPERTY_NOTHING_TARGET,5,0,10 RDMOPTG_Crimson_Weapon_3,1,RDMOPT_DAMAGE_PROPERTY_WATER_TARGET,5,0,10 RDMOPTG_Crimson_Weapon_3,1,RDMOPT_DAMAGE_PROPERTY_FIRE_USER,5,0,10 As for the source change, you can apply the diff below and do some tests with it (I didn't test it too seriously, it worked well enough for what I believe your request was though). The current weight/ratio system used... doesn't make any sense, so I made my own.
    diff --git a/db/re/item_randomopt_group.txt b/db/re/item_randomopt_group.txt index 6d9b9929a..05d579f9a 100644 --- a/db/re/item_randomopt_group.txt +++ b/db/re/item_randomopt_group.txt @@ -1,10 +1,16 @@ // <randopt_groupid>,<rate>,<randopt_id1>,<randopt_value1>,<randopt_param1>{,<randopt_id2>,<randopt_value2>,<randopt_param2>,<randopt_id3>,<randopt_value3>,<randopt_param3>,<randopt_id4>,<randopt_value4>,<randopt_param4>,<randopt_id5>,<randopt_value5>,<randopt_param5>} // Crimson Weapon -RDMOPTG_Crimson_Weapon,1,RDMOPT_WEAPON_ATTR_NOTHING,0,0 -RDMOPTG_Crimson_Weapon,1,RDMOPT_WEAPON_ATTR_WATER,0,0 -RDMOPTG_Crimson_Weapon,1,RDMOPT_WEAPON_ATTR_GROUND,0,0 -RDMOPTG_Crimson_Weapon,1,RDMOPT_WEAPON_ATTR_FIRE,0,0 -RDMOPTG_Crimson_Weapon,1,RDMOPT_WEAPON_ATTR_WIND,0,0 -RDMOPTG_Crimson_Weapon,1,RDMOPT_WEAPON_ATTR_SAINT,0,0 -RDMOPTG_Crimson_Weapon,1,RDMOPT_WEAPON_ATTR_DARKNESS,0,0 +RDMOPTG_Crimson_Weapon_1,1,RDMOPT_WEAPON_ATTR_NOTHING,0,0 +RDMOPTG_Crimson_Weapon_1,1,RDMOPT_WEAPON_ATTR_WATER,0,0 +RDMOPTG_Crimson_Weapon_1,1,RDMOPT_WEAPON_ATTR_GROUND,0,0 +RDMOPTG_Crimson_Weapon_1,1,RDMOPT_WEAPON_ATTR_FIRE,0,0 +RDMOPTG_Crimson_Weapon_1,1,RDMOPT_WEAPON_ATTR_WIND,0,0 +RDMOPTG_Crimson_Weapon_1,1,RDMOPT_WEAPON_ATTR_SAINT,0,0 + +RDMOPTG_Crimson_Weapon_2,1,RDMOPT_ATTR_TOLERACE_GROUND,30,0,50 + +RDMOPTG_Crimson_Weapon_3,1,RDMOPT_DAMAGE_PROPERTY_GROUND_USER,5,0,10 +RDMOPTG_Crimson_Weapon_3,1,RDMOPT_DAMAGE_PROPERTY_NOTHING_TARGET,5,0,10 +RDMOPTG_Crimson_Weapon_3,1,RDMOPT_DAMAGE_PROPERTY_WATER_TARGET,5,0,10 +RDMOPTG_Crimson_Weapon_3,1,RDMOPT_DAMAGE_PROPERTY_FIRE_USER,5,0,10 \ No newline at end of file diff --git a/src/map/atcommand.cpp b/src/map/atcommand.cpp index 266f37526..b0c6874e3 100644 --- a/src/map/atcommand.cpp +++ b/src/map/atcommand.cpp @@ -1403,6 +1403,9 @@ ACMD_FUNC(item) item_tmp.nameid = item_id; item_tmp.identify = 1; item_tmp.bound = bound; + + itemdb_add_randomopt(&item_tmp); + if ((flag = pc_additem(sd, &item_tmp, get_count, LOG_TYPE_COMMAND))) clif_additem(sd, 0, 0, flag); } diff --git a/src/map/itemdb.cpp b/src/map/itemdb.cpp index 56ced4d34..f7f606c4e 100644 --- a/src/map/itemdb.cpp +++ b/src/map/itemdb.cpp @@ -1783,53 +1783,138 @@ struct s_random_opt_group *itemdb_randomopt_group_exists(int id) { static bool itemdb_read_randomopt_group(char* str[], int columns, int current) { int64 id_tmp; int id = 0; - int i; + int64 randid_tmp; + int randid = 0; unsigned short rate = (unsigned short)strtoul(str[1], NULL, 10); struct s_random_opt_group *g = NULL; if (!script_get_constant(str[0], &id_tmp)) { - ShowError("itemdb_read_randomopt_group: Invalid ID for Random Option Group '%s'.\n", str[0]); - return false; + // autogenerate constant, it gets annoying to define it everytime + script_set_constant(trim(str[0]), add_str(trim(str[0])), false, false); + + if (!script_get_constant(trim(str[0]), &id_tmp)) { // Should never happen? + ShowError("itemdb_read_randomopt_group: Invalid ID for Random Option Group '%s'.\n", str[0]); + return false; + } } - id = static_cast<int>(id_tmp); - - if ((columns-2)%3 != 0) { - ShowError("itemdb_read_randomopt_group: Invalid column entries '%d'.\n", columns); + if (!script_get_constant(str[2], &randid_tmp) || ((randid = static_cast<int>(randid_tmp)) && !itemdb_randomopt_exists(randid))) { + ShowError("itemdb_read_randomopt_group: Invalid random group id '%s'!\n", str[2]); return false; } + id = static_cast<int>(id_tmp); + if (!(g = (struct s_random_opt_group *)uidb_get(itemdb_randomopt_group, id))) { CREATE(g, struct s_random_opt_group, 1); - g->id = id; g->total = 0; g->entries = NULL; - uidb_put(itemdb_randomopt_group, g->id, g); + g->total_weight = 0; + uidb_put(itemdb_randomopt_group, id, g); } - RECREATE(g->entries, struct s_random_opt_group_entry, g->total + rate); + RECREATE(g->entries, struct s_random_opt_group_entry, g->total + 1); - for (i = g->total; i < (g->total + rate); i++) { - int j, k; - memset(&g->entries[i].option, 0, sizeof(g->entries[i].option)); - for (j = 0, k = 2; k < columns && j < MAX_ITEM_RDM_OPT; k+=3) { - int64 randid_tmp; - int randid = 0; + memset(&g->entries[g->total], 0, sizeof(g->entries[g->total])); + + g->entries[g->total].option.id = randid; + g->entries[g->total].option.value = (short)strtoul(str[3], NULL, 10); + g->entries[g->total].option.param = (char)strtoul(str[4], NULL, 10); + g->entries[g->total].weight = rate; - if (!script_get_constant(str[k], &randid_tmp) || ((randid = static_cast<int>(randid_tmp)) && !itemdb_randomopt_exists(randid))) { - ShowError("itemdb_read_randomopt_group: Invalid random group id '%s' in column %d!\n", str[k], k+1); - continue; + if (columns > 5) { + g->entries[g->total].max_value = max((short)strtoul(str[5], NULL, 10), g->entries[g->total].option.value); + } + + g->total_weight += rate; + g->total++; + return true; +} + +/** +* Read Item Random Option Group from db file +**/ +static bool itemdb_read_randomopt_equip(char* str[], int columns, int current) { + unsigned short nameid; + int64 group_id_tmp; + int group_id = -1; + struct item_data *item = NULL; + int i; + + nameid = atoi(str[0]); + + for (i = 1; i < columns; i++) { + if (ISDIGIT(str[i][0])) { + group_id = (int)strtoul(str[i], NULL, 10); + } + else { + if (!script_get_constant(trim(str[i]), &group_id_tmp)) { + ShowError("itemdb_read_randopt_equip: Invalid constant '%s'!\n", str[i]); + return false; } - g->entries[i].option[j].id = randid; - g->entries[i].option[j].value = (short)strtoul(str[k+1], NULL, 10); - g->entries[i].option[j].param = (char)strtoul(str[k+2], NULL, 10); - j++; + + group_id = static_cast<int>(group_id_tmp); + } + + if ((item = itemdb_exists(nameid)) == NULL) { + ShowWarning("itemdb_read_randopt_equip: Invalid item ID %hu.\n", nameid); + return false; } + + if (!itemdb_randomopt_group_exists(group_id)) { + ShowWarning("itemdb_read_randopt_equip: Invalid random option group ID %hu.\n", group_id); + return false; + } + + item->randomopt_groupid[i - 1] = group_id; } - g->total += rate; + return true; } +/** +* Sets random options to a weapon, if it has any. +**/ +void itemdb_add_randomopt(struct item *it) { + struct item_data *id; + struct s_random_opt_group *randomopt_group; + int i; + + if (!it || !it->nameid || (id = itemdb_exists(it->nameid)) == NULL) { + return; + } + + for (i = 0; i < MAX_ITEM_RDM_OPT; i++) { + if (id->randomopt_groupid[i] && ((randomopt_group = itemdb_randomopt_group_exists(id->randomopt_groupid[i])) != NULL)) { + int rnd_value = rnd() % (randomopt_group->total_weight); + int total = 0; + int j; + + for (j = 0; j < randomopt_group->total; j++) { + total += randomopt_group->entries[j].weight; + + if (rnd_value < total) { + it->option[i].id = randomopt_group->entries[j].option.id; + + if (randomopt_group->entries[j].max_value > 0 && randomopt_group->entries[j].max_value != randomopt_group->entries[j].option.value) { + int max = randomopt_group->entries[j].max_value; + int min = randomopt_group->entries[j].option.value; + + it->option[i].value = rnd() % (max - min + 1) + min; + } + else { + it->option[i].value = randomopt_group->entries[j].option.value; + } + + it->option[i].param = randomopt_group->entries[j].option.param; + break; + } + } + } + } +} + /** * Read all item-related databases */ @@ -1881,7 +1966,8 @@ static void itemdb_read(void) { sv_readdb(dbsubpath2, "item_delay.txt", ',', 2, 3, -1, &itemdb_read_itemdelay, i > 0); sv_readdb(dbsubpath2, "item_buyingstore.txt", ',', 1, 1, -1, &itemdb_read_buyingstore, i > 0); sv_readdb(dbsubpath2, "item_flag.txt", ',', 2, 2, -1, &itemdb_read_flag, i > 0); - sv_readdb(dbsubpath2, "item_randomopt_group.txt", ',', 5, 2+5*MAX_ITEM_RDM_OPT, -1, &itemdb_read_randomopt_group, i > 0); + sv_readdb(dbsubpath2, "item_randomopt_group.txt", ',', 5, 6, -1, &itemdb_read_randomopt_group, i > 0); + sv_readdb(dbsubpath2, "item_randomopt_equips.txt",',', 2, 1 + MAX_ITEM_RDM_OPT, -1, &itemdb_read_randomopt_equip, i > 0); aFree(dbsubpath1); aFree(dbsubpath2); } diff --git a/src/map/itemdb.hpp b/src/map/itemdb.hpp index 546d4ef14..af460a82c 100644 --- a/src/map/itemdb.hpp +++ b/src/map/itemdb.hpp @@ -865,6 +865,7 @@ struct item_data bool isStackable(); int inventorySlotNeeded(int quantity); + unsigned int randomopt_groupid[MAX_ITEM_RDM_OPT]; }; // Struct for item random option [Secret] @@ -882,14 +883,16 @@ enum Random_Option_Group { /// Struct for random option group entry struct s_random_opt_group_entry { - struct s_item_randomoption option[MAX_ITEM_RDM_OPT]; + struct s_item_randomoption option; + short max_value; + int weight; }; /// Struct for Random Option Group struct s_random_opt_group { - uint8 id; struct s_random_opt_group_entry *entries; uint16 total; + int total_weight; }; struct item_data* itemdb_searchname(const char *name); @@ -964,6 +967,7 @@ bool itemdb_parse_roulette_db(void); struct s_random_opt_data *itemdb_randomopt_exists(short id); struct s_random_opt_group *itemdb_randomopt_group_exists(int id); +void itemdb_add_randomopt(struct item *it); void itemdb_reload(void); diff --git a/src/map/mob.cpp b/src/map/mob.cpp index f48c4d942..37e016f65 100644 --- a/src/map/mob.cpp +++ b/src/map/mob.cpp @@ -2119,7 +2119,7 @@ static struct item_drop* mob_setdropitem(struct s_mob_drop *mobdrop, int qty, un drop->item_data.nameid = mobdrop->nameid; drop->item_data.amount = qty; drop->item_data.identify = itemdb_isidentified(mobdrop->nameid); - mob_setdropitem_option(&drop->item_data, mobdrop); + itemdb_add_randomopt(&drop->item_data); drop->mob_id = mob_id; drop->next = NULL; return drop; @@ -2931,7 +2931,7 @@ int mob_dead(struct mob_data *md, struct block_list *src, int type) intif_broadcast(message,strlen(message)+1,BC_DEFAULT); } - mob_setdropitem_option(&item, &mdrop[i]); + itemdb_add_randomopt(&item); if((temp = pc_additem(mvp_sd,&item,1,LOG_TYPE_PICKDROP_PLAYER)) != 0) { clif_additem(mvp_sd,0,0,temp); diff --git a/src/map/pc.cpp b/src/map/pc.cpp index 8f609f505..ce4eb55e3 100755 --- a/src/map/pc.cpp +++ b/src/map/pc.cpp @@ -5632,7 +5632,7 @@ bool pc_steal_item(struct map_session_data *sd,struct block_list *bl, uint16 ski tmp_item.nameid = itemid; tmp_item.amount = 1; tmp_item.identify = itemdb_isidentified(itemid); - mob_setdropitem_option(&tmp_item, &md->db->dropitem[i]); + itemdb_add_randomopt(&tmp_item); flag = pc_additem(sd,&tmp_item,1,LOG_TYPE_PICKDROP_PLAYER); //TODO: Should we disable stealing when the item you stole couldn't be added to your inventory? Perhaps players will figure out a way to exploit this behaviour otherwise? diff --git a/src/map/script.cpp b/src/map/script.cpp index 5af99c5a5..f99bff45e 100644 --- a/src/map/script.cpp +++ b/src/map/script.cpp @@ -7438,6 +7438,8 @@ BUILDIN_FUNC(getitem) // if not pet egg if (!pet_create_egg(sd, nameid)) { + itemdb_add_randomopt(&it); + if ((flag = pc_additem(sd, &it, get_count, LOG_TYPE_SCRIPT))) { clif_additem(sd, 0, 0, flag); Of course, this system isn't compatible with the default one on rAthena, so you'll have to modify your current random option weapons if you have any. Goodluck!
     
    option.diff
  16. Tokei's post in How create a 'input' with 'timer'? was marked as the answer   
    Heya,
    Don't use timers that run scripts on players (you should avoid using the addtimer command entirely). The reason for that is you're attempting to run two scripts at once, and the script engine doesn't support that very well. Set the timer as a local variable instead:
    mes "type anything in 5 seconds"; [email protected] = gettimetick(0) + 5000; input([email protected]); clear(); if (gettimetick(0) >= [email protected]) { mes "time over"; close; } mes "you did it"; close;  
  17. Tokei's post in Decompile ItemInfo 2019 was marked as the answer   
    You are trying to open the file as a GRF, that will cause you errors. You have to open an already existing GRF or make a new one, then add the iteminfo and click on it to decompile it. It's a GRF tool, it's able to read and decompile lub files as a preview feature, but it's not its main purpose.
  18. Tokei's post in Renewal Miss to High Lv Mob was marked as the answer   
    Well, this adds a flat 80 hit to all players and enemies. While yes, it does help with your initial issue, it also makes flee builds a lot less worthwhile (it gives a free Phreeoni card to everyone). If that works out for you, then that's up to you...! Although it seems to me your biggest concern is the flee bonus that the higher level monsters get. Why not simply remove that instead?
    // Flee stat = status->flee; stat += level + status->agi + (bl->type == BL_MER ? 0 : bl->type == BL_PC ? status->luk / 5 : 0) + 100; //base level + ( every 1 agi = +1 flee ) + (every 5 luk = +1 flee) + 100 status->flee = cap_value(stat, 1, SHRT_MAX); to // Flee stat = status->flee; stat += (bl->type == BL_MOB ? 0 : level) + status->agi + (bl->type == BL_MER ? 0 : bl->type == BL_PC ? status->luk / 5 : 0) + 100; //base level + ( every 1 agi = +1 flee ) + (every 5 luk = +1 flee) + 100 status->flee = cap_value(stat, 1, SHRT_MAX);  
  19. Tokei's post in How can i duplicate a NPC inside of my script? was marked as the answer   
    Heya,
    This is possible, but it usually means the solution/algoritm you're trying to use is the wrong one to start with. That aside, you can use something similar to:
    duplicate "prontera", 154, 182, 4, "SourceNPC", "DuplicatedNPC", 77; And you'll have to do the changes below:
    diff --git a/src/map/npc.hpp b/src/map/npc.hpp index 0ce0c96a0..ffde62587 100644 --- a/src/map/npc.hpp +++ b/src/map/npc.hpp @@ -1219,6 +1219,7 @@ void do_clear_npc(void); void do_final_npc(void); void do_init_npc(void); void npc_event_do_oninit(void); +const char* npc_parse_duplicate(char* w1, char* w2, char* w3, char* w4, const char* start, const char* buffer, const char* filepath); int npc_event_do(const char* name); int npc_event_do_id(const char* name, int rid); diff --git a/src/map/script.cpp b/src/map/script.cpp index 604274ecd..9da5d4690 100644 --- a/src/map/script.cpp +++ b/src/map/script.cpp @@ -24548,6 +24548,38 @@ BUILDIN_FUNC(preg_match) { #endif } +BUILDIN_FUNC(duplicate) +{ + const char *map = script_getstr(st,2); + int x = script_getnum(st,3); + int y = script_getnum(st,4); + int dir = script_getnum(st,5); + const char *duplicate_name = script_getstr(st,6); + const char *npc_name = script_getstr(st,7); + char w1[2048], w2[2048], w3[2048], w4[2048], p[2048]; + int view_id = script_getnum(st,8); + struct npc_data* nd = map_id2nd(st->oid); + + if (!nd) { + return SCRIPT_CMD_FAILURE; + } + + sprintf(w1, "%s,%d,%d,%d", map, x, y, dir); + sprintf(w2, "duplicate(%s)", duplicate_name); + sprintf(w3, "%s", npc_name); + + if (script_hasdata(st,9) && script_hasdata(st,10)) { + sprintf(w4, "%d,%d,%d", view_id, script_getnum(st,9), script_getnum(st,10)); + } + else { + sprintf(w4, "%d", view_id); + } + + sprintf(p, "%s\t%s\t%s\t%s", w1, w2, w3, w4); + npc_parse_duplicate(w1,w2,w3,w4, p, p, nd->path); + return SCRIPT_CMD_SUCCESS; +} + /// script command definitions /// for an explanation on args, see add_buildin_func struct script_function buildin_func[] = { @@ -25170,6 +25202,8 @@ struct script_function buildin_func[] = { BUILDIN_DEF(achievement_condition,"i"), BUILDIN_DEF(getvariableofinstance,"ri"), BUILDIN_DEF(convertpcinfo,"vi"), + + BUILDIN_DEF(duplicate,"siiissi??"), #include "../custom/script_def.inc" {NULL,NULL,NULL},  
  20. Tokei's post in What file is responsible for 'Wear Costume' button? was marked as the answer   
    Yes, when you "receive" the item in your inventory, the button will display properly because it uses the clif_additem packet. I meant how you receive the item information packet-wise, not the source of where it came from (though that's related of course).
    As for why 20285 shows up and 20286 doesn't, that is because 20285 has a hat effect and these are not displayed on the client with the feature. You can remove the item from the list by opening hateffectinfo.lub and then removing the ids from effectHatItemTable. It's probably best to keep the button disabled for that one though.
  21. Tokei's post in SG skills need help was marked as the answer   
    Heya,
    These are ground unit skills. You'll want to skip the BL_PC check and then adjust the skill ratios to battle.cpp:
    diff --git a/src/map/battle.cpp b/src/map/battle.cpp index de8167f4a..904a672d8 100644 --- a/src/map/battle.cpp +++ b/src/map/battle.cpp @@ -4354,6 +4354,13 @@ static int battle_calc_attack_skill_ratio(struct Damage* wd, struct block_list * if (sd && pc_checkskill(sd, SU_SPIRITOFLIFE)) skillratio += skillratio * status_get_hp(src) / status_get_max_hp(src); break; + case SG_SUN_WARM: + case SG_MOON_WARM: + case SG_STAR_WARM: + if (tsd) { + skillratio += -50; + } + break; } return skillratio; } diff --git a/src/map/skill.cpp b/src/map/skill.cpp index 3b7b4ba86..7656a247f 100755 --- a/src/map/skill.cpp +++ b/src/map/skill.cpp @@ -13795,7 +13795,7 @@ int skill_unit_onplace_timer(struct skill_unit *unit, struct block_list *bl, t_t do { if( bl->type == BL_PC ) status_zap(bl, 0, 15); // sp damage to players - else // mobs + if( status_charge(ss, 0, 2) ) { // costs 2 SP per hit if( !skill_attack(BF_WEAPON,ss,&unit->bl,bl,sg->skill_id,sg->skill_lv,tick+(t_tick)count*sg->interval,0) ) status_charge(ss, 0, 8); //costs additional 8 SP if miss  
  22. Tokei's post in Callfunc npc script in src code? was marked as the answer   
    Heya,
    You can't do that (not without major changes anyway). I presume the goal here is to run a script in the source (which is a bad idea), but the easy solution here would simply be to set your variables as temporary global variables and then run an event label:
    In the source, you would use something like:
    #include "mapreg.h" #include "npc.h" [...] mapreg_setreg(reference_uid(add_str("[email protected]"), 0), MG_SAFETYWALL); mapreg_setreg(reference_uid(add_str("[email protected]"), 0), skill_id); mapreg_setregstr(reference_uid(add_str("[email protected]$"), 0), "a string"); npc_event_do_id("MyScript::OnCalled", sd->bl.id);  
    And in the script:
    function script myfunction { announce "arg1: " + getarg(0) + ", arg2: " + getarg(1) + ", arg3: " + getarg(2); return; } - script MyScript -1,{ end; OnCalled: callfunc("myfunction", [email protected], [email protected], [email protected]$); end; }  
    It would be much easier to do the code in the source directly instead though.
  23. Tokei's post in Script crashing map server was marked as the answer   
    You should be using a function for your item instead (and don't use attachrid), something along the lines of:
    getitem 57000,1; callfunc("summonfunction", 2); function script summonfunction { [email protected] = getarg(0); // ... close/return; }  
  24. Tokei's post in Array trouble was marked as the answer   
    Hmm, I believe the following would be easier to maintain:
    prontera,153,193,6 script Fake_Npc 123,{ [email protected]$ = implode(.tmenu$, ":"); [email protected] = select([email protected]$ + ":Info") - 1; if ([email protected] < getarraysize(.tmenu$)) { Var1 = [email protected]; } else { dispbottom "Test"; dispbottom .tmenu$[Var1]; } end; OnInit: setarray .tmenu$, "Dog", "Cat"; end; }  
  25. Tokei's post in Act Editor Replace Color for all layers was marked as the answer   
    Copy and paste this file to Scripts > Open scripts folder (name it "SetColor.cs"):
    using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Media; using System.Windows.Media.Imaging; using ErrorManager; using GRF.FileFormats.ActFormat; using GRF.FileFormats.SprFormat; using GRF.FileFormats.PalFormat; using GRF.Image; using GRF.Image.Decoders; using GRF.Graphics; using GrfToWpfBridge; using TokeiLibrary; using TokeiLibrary.WPF; using Action = GRF.FileFormats.ActFormat.Action; using Frame = GRF.FileFormats.ActFormat.Frame; using Point = System.Windows.Point; namespace Scripts { public class Script : IActScript { public object DisplayName { get { return "Change all layer color"; } } public string Group { get { return "Scripts"; } } public string InputGesture { get { return null; } } public string Image { get { return null; } } public void Execute(Act act, int selectedActionIndex, int selectedFrameIndex, int[] selectedLayerIndexes) { if (act == null) return; var frame = act[selectedActionIndex, selectedFrameIndex]; GrfColor startColor; if (frame.Layers.Count == 0) { startColor = GrfColor.White; } else { startColor = act[selectedActionIndex, selectedFrameIndex, 0].Color; } ColorPicker.PickerDialog picker = new ColorPicker.PickerDialog(startColor.ToColor(), ColorPicker.Core.ColorMode.Hue); picker.Owner = WpfUtilities.TopWindow; picker.ShowDialog(); if (picker.DialogResult) { try { act.Commands.Begin(); GrfColor newColor = picker.PickerControl.SelectedColor.ToGrfColor(); act.Commands.SetColor(newColor); } catch (Exception err) { act.Commands.CancelEdit(); ErrorHandler.HandleException(err, ErrorLevel.Warning); } finally { act.Commands.End(); act.InvalidateVisual(); } } } public bool CanExecute(Act act, int selectedActionIndex, int selectedFrameIndex, int[] selectedLayerIndexes) { return act != null; } } } Then you can use Scripts > Change all layer color. I really should update Act Editor @@...
×
×
  • Create New...

Important Information

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