diff --git a/doc/script_commands.txt b/doc/script_commands.txt index 8c5d811c600..ac1c5071129 100644 --- a/doc/script_commands.txt +++ b/doc/script_commands.txt @@ -3547,6 +3547,50 @@ Example: --------------------------------------- +*getitemlink({, {, {, {, }}}}) +*getitemlink(""{, {, {, {, }}}}) + +Get specially formatted client string that represents an item with the given properties. + +Parameters: +- item_id: Item DB id. May be the constant (e.g.: 4001 or Poring_Card) +- item_name: Item DB name. +- refine: Item refine amount (optional, defaults to 0) +- cards_array: An array containing the cards item IDs. (optional, defaults to 0 / no cards) +- options_array: An array containing option information. (optional, defaults to 0 / no options) + Structure: , , , , , , ... , , +- grade: Item grade (optional, defaults to 0) + + +Example: + + setarray(.@cards, Fabre_Card, 0, 0, Agility1); + setarray(.@options, + VAR_MAXHPAMOUNT, 10, 0, // option 1 + VAR_MAXSPAMOUNT, 20, 0 // option 2 + ); + + // In a recent client, shows something like: + // Here is my very dangerous item: <+10 Vital Knife [Agi +1]> + // + // The text between <> is made by the client and is clickable, showing the item options and grade too. + dispbottom(sprintf("Here is my very dangerous item: %s", getitemlink(Knife, 10, .@cards, .@options, 3))); + end; + + +Client support: +- PACKETVER_MAIN_NUM < 20150923 and PACKETVER_RE_NUM < 20150819 + Only item name is returned. As pure string. +- PACKETVER_MAIN_NUM < 20200916, PACKETVER_RE_NUM < 20200723, PACKETVER_ZERO + Grade is ignored. Returns a clickable text. +- PACKETVER_MAIN_NUM >= 20200916, PACKETVER_RE_NUM >= 20200723 + Grade is also supported. Returns a clickable text. + +Note: The clickable text also depends on where it is used. For example, a "mes" will properly format the item name, + but it won't be clickable. While a "dispbottom" shows it formatted and clickable. This is client-defined. + +--------------------------------------- + *getequipisenableopt() This function checks if the equipped item allows the use of bonus options. diff --git a/npc/dev/test.txt b/npc/dev/test.txt index d459a83b83d..7c97dbd4ba2 100644 --- a/npc/dev/test.txt +++ b/npc/dev/test.txt @@ -173,6 +173,94 @@ function script F_TestCalendarNextTime { return .@local_check && .@utc_check; } +function script F_TestGetItemLink_Etc { + .@str$ = getitemlink(Jellopy, 0, 0, 0, 0); + .@str2$ = getitemlink(Jellopy); + + // Sanity check, optional parameters should not affect result + if (.@str$ != .@str2$) { + return false; + } + + .@pass = false; + if (PACKETVER < 20150923) { + .@pass = (.@str$ == getiteminfo(Jellopy, ITEMINFO_NAME)); + } else if (PACKETVER < 20161116) { + .@pass = (.@str$ == "000000eF'00'00'00'00"); + } else if (PACKETVER < 20200916) { + .@pass = (.@str$ == "000000eF&00(00(00(00(00"); + } else { + .@pass = (.@str$ == "000000eF&00'00)00)00)00)00"); + } + + return .@pass; +} + +function script F_TestGetItemLink_Headgear { + .@str$ = getitemlink(Hat, 0, 0, 0, 0); + .@str2$ = getitemlink(Hat); + + // Sanity check, optional parameters should not affect result + if (.@str$ != .@str2$) { + return false; + } + + .@pass = false; + if (PACKETVER < 20150923) { + .@pass = (.@str$ == getiteminfo(Hat, ITEMINFO_NAME)); + } else if (PACKETVER < 20161116) { + .@pass = (.@str$ == "000481zO'00'00'00'00"); + } else if (PACKETVER < 20200916) { + .@pass = (.@str$ == "000481zO&0g(00(00(00(00"); + } else { + .@pass = (.@str$ == "000481zO&0g'00)00)00)00)00"); + } + + return .@pass; +} + +function script F_TestGetItemLink_BaseWeapon { + .@str$ = getitemlink(Knife, 0, 0, 0, 0); + .@str2$ = getitemlink(Knife); + + // Sanity check, optional parameters should not affect result + if (.@str$ != .@str2$) { + return false; + } + + .@pass = false; + if (PACKETVER < 20150923) { + .@pass = (.@str$ == getiteminfo(Knife, ITEMINFO_NAME)); + } else if (PACKETVER < 20161116) { + .@pass = (.@str$ == "000021jn'00'00'00'00"); + } else if (PACKETVER < 20200916) { + .@pass = (.@str$ == "000021jn&00(00(00(00(00"); + } else { + .@pass = (.@str$ == "000021jn&00'00)00)00)00)00"); + } + + return .@pass; +} + +function script F_TestGetItemLink_FullWeapon { + setarray(.@cards[0], Fabre_Card, Captain_Felock_Card, 0, Agility1); + setarray(.@options[0], WEAPON_ATTR_GROUND, 1, 0, VAR_MAXHPAMOUNT, 10, 0); + .@str$ = getitemlink(Knife, 10, .@cards, .@options, 2); + + .@pass = false; + if (PACKETVER < 20150923) { + .@pass = (.@str$ == getiteminfo(Knife, ITEMINFO_NAME)); + } else if (PACKETVER < 20161116) { + .@pass = (.@str$ == "000021jn%0a'12y'74q'00'1ei)2R*00+01)01*00+0a"); + } else if (PACKETVER < 20200916) { + .@pass = (.@str$ == "000021jn%0a&00(12y(74q(00(1ei*2R+00,01*01+00,0a"); + } else { + .@pass = (.@str$ == "000021jn%0a&00'02)12y)74q)00)1ei+2R,00-01+01,00-0a"); + } + + return .@pass; +} + function script HerculesSelfTestHelper { if (.once > 0) return .errors; @@ -884,6 +972,11 @@ function script HerculesSelfTestHelper { callsub(OnCheck, "programatic public local call", callfunctionofnpc("export test", "RefTest", .@refTest = 1), 1337); callsub(OnCheck, "getcalendartime: next occurence of current Hour/Minute", F_TestCalendarNextTime(), true); + + callsub(OnCheck, "getitemlink: etc tag", F_TestGetItemLink_Etc(), true); + callsub(OnCheck, "getitemlink: headgear tag", F_TestGetItemLink_Headgear(), true); + callsub(OnCheck, "getitemlink: basic weapon tag", F_TestGetItemLink_BaseWeapon(), true); + callsub(OnCheck, "getitemlink: complete weapon tag", F_TestGetItemLink_FullWeapon(), true); if (.errors) { consolemes(CONSOLEMES_DEBUG, "Script engine self-test [ \033[0;31mFAILED\033[0m ]"); diff --git a/src/map/script.c b/src/map/script.c index 1bc9ad969b7..5aa445617c4 100644 --- a/src/map/script.c +++ b/src/map/script.c @@ -16396,6 +16396,106 @@ static BUILDIN(setiteminfo) return true; } +/** + * Creates a Item Link tag for the given item info. + * + * getitemlink({, {, {, {, }}}}) + * getitemlink(""{, {, {, {, }}}}) + */ +static BUILDIN(getitemlink) +{ + struct item_data *itd; + + if (script_isstringtype(st, 2)) { /// Item name. + const char *name = script_getstr(st, 2); + itd = itemdb->search_name(name); + + if (itd == NULL) + ShowError("%s: Non-existent item name \"%s\".\n", __func__, name); + } else { /// Item ID. + itd = itemdb->exists(script_getnum(st, 2)); + + if (itd == NULL) + ShowError("%s: Non-existent item id \"%d\".\n", __func__, script_getnum(st, 2)); + } + + if (itd == NULL) { + script_pushconststr(st, ""); + return false; + } + + struct item link_item = { 0 }; + link_item.nameid = itd->nameid; + link_item.refine = script_hasdata(st, 3) ? script_getnum(st, 3) : 0; + + // Cards + if (script_hasdata(st, 4)) { + struct script_data *data = script_getdata(st, 4); + const char *name = reference_getname(data); + + struct map_session_data *sd = NULL; + if (data_isreference(data) && is_int_variable(name)) { + if (not_server_variable(*name)) { + sd = script->rid2sd(st); + if (sd == NULL) + return true; // no player attached + } + + int array_size = script->array_highest_key(st, sd, name, reference_getref(data)); + array_size = cap_value(array_size, 0, MAX_SLOTS); + + for (int i = 0; i < array_size; ++i) + link_item.card[i] = script->array_get_num_member(st, data, i); + } else if (!data_isint(data) || script_getnum(st, 4) != 0) { + ShowError("%s: Invalid card list received. Card list must be 0 or an array of card IDs (number)\n", __func__); + script->reportdata(data); + script_pushconststr(st, ""); + return true; + } + } + + // Options + if (script_hasdata(st, 5)) { + struct script_data *data = script_getdata(st, 5); + const char *name = reference_getname(data); + + if (data_isreference(data) && is_int_variable(name)) { + struct map_session_data *sd = NULL; + if (not_server_variable(*name)) { + sd = script->rid2sd(st); + if (sd == NULL) + return true; // no player attached + } + + int array_size = script->array_highest_key(st, sd, reference_getname(data), reference_getref(data)); + array_size = cap_value(array_size, 0, MAX_ITEM_OPTIONS * 3); + + // arrays ending with 0 will have arraysize not divisible by 3, but acessing those indexes will result in 0 + for (int i = 0, j = 0; i < array_size; i += 3, ++j) { + link_item.option[j].index = script->array_get_num_member(st, data, i); + link_item.option[j].value = script->array_get_num_member(st, data, i + 1); + link_item.option[j].param = script->array_get_num_member(st, data, i + 2); + } + } else if (!data_isint(data) || script_getnum(st, 5) != 0) { + ShowError("%s: Invalid options list received. Option list must be 0 or an array of option data (number, number, number)\n", __func__); + script->reportdata(data); + script_pushconststr(st, ""); + return false; + } + } + + if (script_hasdata(st, 6)) + link_item.grade = script_getnum(st, 6); + + StringBuf buf; + clif->format_itemlink(&buf, &link_item); + + script_pushstrcopy(st, StrBuf->Value(&buf)); + StrBuf->Destroy(&buf); + + return true; +} + /*========================================== * Returns value from equipped item slot n [Lupus] * getequpcardid(num,slot) @@ -28782,6 +28882,7 @@ static void script_parse_builtin(void) BUILDIN_DEF(strcmp,"ss"), BUILDIN_DEF(getiteminfo,"vi"), //[Lupus] returns Items Buy / sell Price, etc info BUILDIN_DEF(setiteminfo,"iii"), //[Lupus] set Items Buy / sell Price, etc info + BUILDIN_DEF(getitemlink, "v????"), BUILDIN_DEF(getequipcardid,"ii"), //[Lupus] returns CARD ID or other info from CARD slot N of equipped item BUILDIN_DEF(getequippedoptioninfo, "i"), BUILDIN_DEF(getequipoption, "iii"), diff --git a/src/map/script.h b/src/map/script.h index b3afbd0e7e3..4ed16e82a6f 100644 --- a/src/map/script.h +++ b/src/map/script.h @@ -176,6 +176,7 @@ struct item_data; #define script_getvaridx(var) ( (uint32)(int64)((var >> 32) & 0xFFFFFFFF) ) #define not_server_variable(prefix) ( (prefix) != '$' && (prefix) != '.' && (prefix) != '\'') +#define is_int_variable(name) ( (name)[strlen(name) - 1] != '$' ) #define is_string_variable(name) ( (name)[strlen(name) - 1] == '$' ) #define BUILDIN(x) bool buildin_ ## x (struct script_state* st)