Skip to content

Commit

Permalink
add getitemlink script command
Browse files Browse the repository at this point in the history
this command creates a client-specific formatted tag with item info
  • Loading branch information
guilherme-gm committed Oct 6, 2023
1 parent 62c22c5 commit 26bab7e
Show file tree
Hide file tree
Showing 4 changed files with 239 additions and 0 deletions.
44 changes: 44 additions & 0 deletions doc/script_commands.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3547,6 +3547,50 @@ Example:

---------------------------------------

*getitemlink(<item_id>{, <refine = 0>{, <cards_array = 0>{, <options_array = 0>{, <grade = 0>}}}})
*getitemlink("<item_name>"{, <refine = 0>{, <cards_array = 0>{, <options_array = 0>{, <grade = 0>}}}})

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: <idx1>, <val1>, <param1>, <idx2>, <val2>, <param2>, ... <idx5>, <val5>, <param5>
- 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(<equipment slot>)

This function checks if the equipped item allows the use of bonus options.
Expand Down
93 changes: 93 additions & 0 deletions npc/dev/test.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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$ == "<ITEM>000000eF'00'00'00'00</ITEM>");
} else if (PACKETVER < 20200916) {
.@pass = (.@str$ == "<ITEML>000000eF&00(00(00(00(00</ITEML>");
} else {
.@pass = (.@str$ == "<ITEML>000000eF&00'00)00)00)00)00</ITEML>");
}

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$ == "<ITEM>000481zO'00'00'00'00</ITEM>");
} else if (PACKETVER < 20200916) {
.@pass = (.@str$ == "<ITEML>000481zO&0g(00(00(00(00</ITEML>");
} else {
.@pass = (.@str$ == "<ITEML>000481zO&0g'00)00)00)00)00</ITEML>");
}

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$ == "<ITEM>000021jn'00'00'00'00</ITEM>");
} else if (PACKETVER < 20200916) {
.@pass = (.@str$ == "<ITEML>000021jn&00(00(00(00(00</ITEML>");
} else {
.@pass = (.@str$ == "<ITEML>000021jn&00'00)00)00)00)00</ITEML>");
}

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$ == "<ITEM>000021jn%0a'12y'74q'00'1ei)2R*00+01)01*00+0a</ITEM>");
} else if (PACKETVER < 20200916) {
.@pass = (.@str$ == "<ITEML>000021jn%0a&00(12y(74q(00(1ei*2R+00,01*01+00,0a</ITEML>");
} else {
.@pass = (.@str$ == "<ITEML>000021jn%0a&00'02)12y)74q)00)1ei+2R,00-01+01,00-0a</ITEML>");
}

return .@pass;
}

function script HerculesSelfTestHelper {
if (.once > 0)
return .errors;
Expand Down Expand Up @@ -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 ]");
Expand Down
101 changes: 101 additions & 0 deletions src/map/script.c
Original file line number Diff line number Diff line change
Expand Up @@ -16396,6 +16396,106 @@ static BUILDIN(setiteminfo)
return true;
}

/**
* Creates a Item Link tag for the given item info.
*
* getitemlink(<item_id>{, <refine = 0>{, <cards_array = 0>{, <options_array = 0>{, <grade = 0>}}}})
* getitemlink("<item_name>"{, <refine = 0>{, <cards_array = 0>{, <options_array = 0>{, <grade = 0>}}}})
*/
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)
Expand Down Expand Up @@ -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"),
Expand Down
1 change: 1 addition & 0 deletions src/map/script.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit 26bab7e

Please sign in to comment.