Initial release of stylist UI (#6446)

Fixes #3037

Thanks to @Balferian and @aleos89

Co-authored-by: Aleos <aleos89@users.noreply.github.com>
This commit is contained in:
Lemongrass3110 2022-01-09 21:56:58 +01:00 committed by GitHub
parent 4bc9e24b4e
commit c22ef3f547
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 1153 additions and 9 deletions

View File

@ -15,11 +15,11 @@ min_chat_delay: 0
// Valid range of dyes and styles on the client.
min_hair_style: 0
max_hair_style: 27
max_hair_style: 42
min_hair_color: 0
max_hair_color: 8
min_cloth_color: 0
max_cloth_color: 4
max_cloth_color: 7
min_body_style: 0
max_body_style: 1

View File

@ -876,7 +876,9 @@
797: This command is unavailable to non-4th class.
//798-799 free
// @stylist
798: This command requires packet version 2015-11-04 or newer.
799: You have already opened the stylist UI.
800: Dragon Knight
801: Meister

View File

@ -0,0 +1,41 @@
# This file is a part of rAthena.
# Copyright(C) 2022 rAthena Development Team
# https://rathena.org - https://github.com/rathena
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
###########################################################################
# Stylist Database
###########################################################################
#
# Stylist Settings
#
###########################################################################
# - Look Look that will be changed.
# Options Possible options to select from.
# - Index Client side index of the option.
# Value Value of the look (can also be an item name).
# CostsHuman: Costs for human players.
# Price Required zeny. (Default: 0)
# RequiredItem Required item. (Default: None)
# RequiredItemBox Required item box. (Default: None)
# CostsDoram: Costs for doram players.
# Price Required zeny. (Default: 0)
# RequiredItem Required item. (Default: None)
# RequiredItemBox Required item box. (Default: None)
###########################################################################
Header:
Type: STYLIST_DB
Version: 1

View File

@ -21686,8 +21686,8 @@ Body:
NoMail: true
NoAuction: true
- Id: 6707
AegisName: Jeremy_Beauty_Coupon
Name: Jeremy Beauty Coupon
AegisName: J_Shop_Coupon
Name: Cash Hair Coupon
Type: Etc
Buy: 10
Weight: 10
@ -24023,9 +24023,10 @@ Body:
Type: Etc
Buy: 10
- Id: 6959
AegisName: aegis_6959
Name: Costume Change Ticket
AegisName: Costume_Ticket
Name: Costume Change Ticket
Type: Etc
Buy: 0
Trade:
Override: 100
NoDrop: true

View File

@ -39884,6 +39884,57 @@ Body:
NoGuildStorage: true
NoMail: true
NoAuction: true
- Id: 16843
AegisName: C_New_Style_Box
Name: Beauty Gift Box
Type: Cash
Buy: 20
Weight: 10
Trade:
Override: 100
NoDrop: true
NoTrade: true
NoSell: true
NoCart: true
NoGuildStorage: true
NoMail: true
NoAuction: true
Script: |
getitem 7622,1;
- Id: 16854
AegisName: CCloth_Dye_Coupon_Box
Name: Clothing Dye Box
Type: Cash
Buy: 20
Weight: 10
Trade:
Override: 100
NoDrop: true
NoTrade: true
NoSell: true
NoCart: true
NoGuildStorage: true
NoMail: true
NoAuction: true
Script: |
getitem 6046,1;
- Id: 16855
AegisName: CCloth_Dye_Coupon2_Box
Name: Clothing Dye Orig Box
Type: Cash
Buy: 20
Weight: 10
Trade:
Override: 100
NoDrop: true
NoTrade: true
NoSell: true
NoCart: true
NoGuildStorage: true
NoMail: true
NoAuction: true
Script: |
getitem 6047,1;
- Id: 16864
AegisName: Siege_Map_Teleport_Scroll_Box_10
Name: Siege Map Teleport Scroll Box(10)
@ -42918,6 +42969,23 @@ Body:
NoAuction: true
Script: |
getgroupitem(IG_Event_Almighty_Box_100);
- Id: 17336
AegisName: J_Shop_Coupon_Box
Name: Cash Hair Coupon Box
Type: Cash
Buy: 20
Weight: 10
Trade:
Override: 100
NoDrop: true
NoTrade: true
NoSell: true
NoCart: true
NoGuildStorage: true
NoMail: true
NoAuction: true
Script: |
getitem 6707,1;
- Id: 17337
AegisName: Holy_Spirit_Scroll
Name: Holy Spirit Egg

429
db/re/stylist.yml Normal file
View File

@ -0,0 +1,429 @@
# This file is a part of rAthena.
# Copyright(C) 2022 rAthena Development Team
# https://rathena.org - https://github.com/rathena
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
###########################################################################
# Stylist Database
###########################################################################
#
# Stylist Settings
#
###########################################################################
# - Look Look that will be changed.
# Options Possible options to select from.
# - Index Client side index of the option.
# Value Value of the look (can also be an item name).
# CostsHuman: Costs for human players.
# Price Required zeny. (Default: 0)
# RequiredItem Required item. (Default: None)
# RequiredItemBox Required item box. (Default: None)
# CostsDoram: Costs for doram players.
# Price Required zeny. (Default: 0)
# RequiredItem Required item. (Default: None)
# RequiredItemBox Required item box. (Default: None)
###########################################################################
Header:
Type: STYLIST_DB
Version: 1
Body:
- Look: Hair_Color
Options:
- Index: -1
Value: 0
CostsHuman:
Price: 0
CostsDoram:
Price: 0
- Index: 1
Value: 1
CostsHuman:
Price: 100000
CostsDoram:
Price: 100000
- Index: 2
Value: 2
CostsHuman:
Price: 100000
CostsDoram:
Price: 100000
- Index: 3
Value: 3
CostsHuman:
Price: 100000
CostsDoram:
Price: 100000
- Index: 4
Value: 4
CostsHuman:
Price: 100000
CostsDoram:
Price: 100000
- Index: 5
Value: 5
CostsHuman:
Price: 100000
CostsDoram:
Price: 100000
- Index: 6
Value: 6
CostsHuman:
Price: 100000
CostsDoram:
Price: 100000
- Index: 7
Value: 7
CostsHuman:
Price: 100000
CostsDoram:
Price: 100000
- Index: 8
Value: 8
CostsHuman:
Price: 100000
CostsDoram:
Price: 100000
- Look: Hair
Options:
- Index: 1
Value: 1
CostsHuman:
Price: 100000
CostsDoram:
Price: 100000
- Index: 2
Value: 2
CostsHuman:
Price: 100000
CostsDoram:
Price: 100000
- Index: 3
Value: 3
CostsHuman:
Price: 100000
CostsDoram:
Price: 100000
- Index: 4
Value: 4
CostsHuman:
Price: 100000
CostsDoram:
Price: 100000
- Index: 5
Value: 5
CostsHuman:
Price: 100000
CostsDoram:
Price: 100000
- Index: 6
Value: 6
CostsHuman:
Price: 100000
CostsDoram:
Price: 100000
- Index: 7
Value: 7
CostsHuman:
Price: 100000
CostDoram:
RequiredItem: J_Shop_Coupon2
RequiredItemBox: J_Shop_Coupon2
- Index: 8
Value: 8
CostsHuman:
Price: 100000
CostDoram:
RequiredItem: J_Shop_Coupon2
RequiredItemBox: J_Shop_Coupon2
- Index: 9
Value: 9
CostsHuman:
Price: 100000
CostDoram:
RequiredItem: J_Shop_Coupon2
RequiredItemBox: J_Shop_Coupon2
- Index: 10
Value: 10
CostsHuman:
Price: 100000
CostDoram:
RequiredItem: J_Shop_Coupon2
RequiredItemBox: J_Shop_Coupon2
- Index: 11
Value: 11
CostsHuman:
Price: 100000
- Index: 12
Value: 12
CostsHuman:
Price: 100000
- Index: 13
Value: 13
CostsHuman:
Price: 100000
- Index: 14
Value: 14
CostsHuman:
Price: 100000
- Index: 15
Value: 15
CostsHuman:
Price: 100000
- Index: 16
Value: 16
CostsHuman:
Price: 100000
- Index: 17
Value: 17
CostsHuman:
Price: 100000
- Index: 18
Value: 18
CostsHuman:
Price: 100000
- Index: 19
Value: 19
CostsHuman:
Price: 100000
- Index: 20
Value: 20
CostsHuman:
Price: 100000
- Index: 21
Value: 21
CostsHuman:
Price: 100000
- Index: 22
Value: 22
CostsHuman:
Price: 100000
- Index: 23
Value: 23
CostsHuman:
Price: 100000
- Index: 24
Value: 24
CostsHuman:
RequiredItem: New_Style_Coupon
RequiredItemBox: C_New_Style_Box
- Index: 25
Value: 25
CostsHuman:
RequiredItem: New_Style_Coupon
RequiredItemBox: C_New_Style_Box
- Index: 26
Value: 26
CostsHuman:
RequiredItem: New_Style_Coupon
RequiredItemBox: C_New_Style_Box
- Index: 27
Value: 27
CostsHuman:
RequiredItem: New_Style_Coupon
RequiredItemBox: C_New_Style_Box
- Index: 28
Value: 28
CostsHuman:
RequiredItem: J_Shop_Coupon
RequiredItemBox: J_Shop_Coupon_Box
- Index: 29
Value: 29
CostsHuman:
RequiredItem: J_Shop_Coupon
RequiredItemBox: J_Shop_Coupon_Box
- Index: 30
Value: 30
CostsHuman:
RequiredItem: J_Shop_Coupon2
RequiredItemBox: J_Shop_Coupon2
- Index: 31
Value: 31
CostsHuman:
RequiredItem: J_Shop_Coupon2
RequiredItemBox: J_Shop_Coupon2
- Index: 32
Value: 32
CostsHuman:
Price: 100000
- Index: 33
Value: 33
CostsHuman:
Price: 3000000
- Index: 34
Value: 34
CostsHuman:
Price: 3000000
- Index: 35
Value: 35
CostsHuman:
Price: 3000000
- Index: 36
Value: 36
CostsHuman:
Price: 3000000
- Index: 37
Value: 37
CostsHuman:
Price: 3000000
- Index: 38
Value: 38
CostsHuman:
Price: 3000000
- Index: 39
Value: 39
CostsHuman:
Price: 3000000
- Index: 40
Value: 40
CostsHuman:
Price: 3000000
- Index: 41
Value: 41
CostsHuman:
Price: 3000000
- Index: 42
Value: 42
CostsHuman:
Price: 3000000
- Look: Clothes_Color
Options:
- Index: 1
Value: 0
CostsHuman:
RequiredItem: Clothing_Dye_Coupon_II
RequiredItemBox: CCloth_Dye_Coupon2_Box
CostsDoram:
RequiredItem: Clothing_Dye_Coupon_II
RequiredItemBox: CCloth_Dye_Coupon2_Box
- Index: 2
Value: 2
CostsHuman:
RequiredItem: Clothing_Dye_Coupon
RequiredItemBox: CCloth_Dye_Coupon_Box
CostsDoram:
RequiredItem: Clothing_Dye_Coupon
RequiredItemBox: CCloth_Dye_Coupon_Box
- Index: 3
Value: 3
CostsHuman:
RequiredItem: Clothing_Dye_Coupon
RequiredItemBox: CCloth_Dye_Coupon_Box
CostsDoram:
RequiredItem: Clothing_Dye_Coupon
RequiredItemBox: CCloth_Dye_Coupon_Box
- Index: 4
Value: 4
CostsHuman:
RequiredItem: Clothing_Dye_Coupon
RequiredItemBox: CCloth_Dye_Coupon_Box
- Index: 5
Value: 5
CostsHuman:
RequiredItem: Clothing_Dye_Coupon
RequiredItemBox: CCloth_Dye_Coupon_Box
- Index: 6
Value: 6
CostsHuman:
RequiredItem: Clothing_Dye_Coupon
RequiredItemBox: CCloth_Dye_Coupon_Box
- Index: 7
Value: 7
CostsHuman:
RequiredItem: Clothing_Dye_Coupon
RequiredItemBox: CCloth_Dye_Coupon_Box
- Look: Head_Top
Options:
- Index: 1
Value: Hat
CostsHuman:
Price: 1000
CostsDoram:
Price: 1000
- Index: 2
Value: Ribbon
CostsHuman:
Price: 800
CostsDoram:
Price: 800
- Index: 3
Value: Bandana
CostsHuman:
Price: 400
CostsDoram:
Price: 400
- Look: Head_Mid
Options:
- Index: 1
Value: One_Eyed_Glass
CostsHuman:
Price: 10000
CostsDoram:
Price: 10000
- Index: 2
Value: Sunglasses
CostsHuman:
Price: 5000
CostsDoram:
Price: 5000
- Index: 3
Value: Luxury_Sunglasses
CostsHuman:
Price: 24000
CostsDoram:
Price: 24000
- Index: 4
Value: Spinning_Eyes
CostsHuman:
Price: 20000
CostsDoram:
Price: 20000
- Index: 5
Value: Diver's_Goggles
CostsHuman:
Price: 3500
CostsDoram:
Price: 3500
- Index: 6
Value: Glasses
CostsHuman:
Price: 4000
CostsDoram:
Price: 4000
- Index: 7
Value: Eye_Bandage
CostsHuman:
Price: 1000
CostsDoram:
Price: 1000
- Look: Head_Bottom
Options:
- Index: 1
Value: Granpa_Beard
CostsHuman:
Price: 5000
CostsDoram:
Price: 5000
- Look: Body2
Options:
- Index: 1
Value: 0
CostsHuman:
RequiredItem: Costume_Ticket
- Index: 2
Value: 1
CostsHuman:
RequiredItem: Costume_Ticket

47
db/stylist.yml Normal file
View File

@ -0,0 +1,47 @@
# This file is a part of rAthena.
# Copyright(C) 2022 rAthena Development Team
# https://rathena.org - https://github.com/rathena
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
###########################################################################
# Stylist Database
###########################################################################
#
# Stylist Settings
#
###########################################################################
# - Look Look that will be changed.
# Options Possible options to select from.
# - Index Client side index of the option.
# Value Value of the look (can also be an item name).
# CostsHuman: Costs for human players.
# Price Required zeny. (Default: 0)
# RequiredItem Required item. (Default: None)
# RequiredItemBox Required item box. (Default: None)
# CostsDoram: Costs for doram players.
# Price Required zeny. (Default: 0)
# RequiredItem Required item. (Default: None)
# RequiredItemBox Required item box. (Default: None)
###########################################################################
Header:
Type: STYLIST_DB
Version: 1
Footer:
Imports:
- Path: db/re/stylist.yml
Mode: Renewal
- Path: db/import/stylist.yml

View File

@ -1237,6 +1237,14 @@ Note: This command requires packet version 2016-10-12 or newer.
---------------------------------------
@stylist
Opens the stylist user interface.
Note: This command requires packet version 2015-11-04 or newer.
---------------------------------------
@request <message>
Sends a message to all connected GMs (via the GM whisper system).

View File

@ -2897,6 +2897,14 @@ This feature requires 2016-10-12aRagexeRE or newer.
---------------------------------------
*openstylist({<char id>})
Opens the stylist UI for the attached player or the given character id.
This feature requires packet version 2015-11-04 or newer.
---------------------------------------
*getareadropitem("<map name>",<x1>,<y1>,<x2>,<y2>,<item>)
This function will count all the items with the specified ID number lying on the

View File

@ -10655,6 +10655,24 @@ ACMD_FUNC(refineui)
#endif
}
ACMD_FUNC( stylist ){
nullpo_retr(-1, sd);
#if PACKETVER < 20151104
clif_displaymessage( fd, msg_txt( sd, 798 ) ); // This command requires packet version 2015-11-04 or newer.
return -1;
#else
if( sd->state.stylist_open ){
clif_displaymessage( fd, msg_txt( sd, 799 ) ); // You have already opened the stylist UI.
return -1;
}
clif_ui_open( sd, OUT_UI_STYLIST, 0 );
return 0;
#endif
}
#include "../custom/atcommand.inc"
/**
@ -10975,6 +10993,7 @@ void atcommand_basecommands(void) {
ACMD_DEF2("completequest", quest),
ACMD_DEF2("checkquest", quest),
ACMD_DEF(refineui),
ACMD_DEFR(stylist, ATCMD_NOCONSOLE|ATCMD_NOAUTOTRADE),
};
AtCommandInfo* atcommand;
int i;

View File

@ -21509,6 +21509,13 @@ void clif_parse_changedress( int fd, struct map_session_data* sd ){
void clif_ui_open( struct map_session_data *sd, enum out_ui_type ui_type, int32 data ){
nullpo_retv(sd);
// If the UI requires state tracking
switch( ui_type ){
case OUT_UI_STYLIST:
sd->state.stylist_open = true;
break;
}
int fd = sd->fd;
WFIFOHEAD(fd,packet_len(0xae2));
@ -22364,6 +22371,172 @@ void clif_parse_unequipall( int fd, struct map_session_data* sd ){
#endif
}
void clif_stylist_response( struct map_session_data* sd, bool failed ){
#if PACKETVER >= 20151104
struct PACKET_ZC_STYLE_CHANGE_RES p = {};
p.PacketType = HEADER_ZC_STYLE_CHANGE_RES;
p.flag = failed;
clif_send( &p, sizeof( p ), &sd->bl, SELF );
if( !failed ){
sd->state.stylist_open = false;
}
#endif
}
bool clif_parse_stylist_buy_sub( struct map_session_data* sd, _look look, int16 index ){
std::shared_ptr<s_stylist_list> list = stylist_db.find( look );
if( list == nullptr ){
return false;
}
std::shared_ptr<s_stylist_entry> entry = util::umap_find( list->entries, index );
if( entry == nullptr ){
return false;
}
std::shared_ptr<s_stylist_costs> costs;
if( ( sd->class_ & MAPID_BASEMASK ) == MAPID_SUMMONER ){
costs = entry->doram;
}else{
costs = entry->human;
}
if( costs == nullptr ){
return false;
}
if( sd->status.zeny < costs->price ){
return false;
}
int16 inventoryIndex = -1;
if( costs->requiredItem != 0 ){
inventoryIndex = pc_search_inventory( sd, costs->requiredItem );
if( inventoryIndex < 0 ){
// No other option
if( costs->requiredItemBox == 0 ){
return false;
}
// Check if the box that contains the item is in the inventory
inventoryIndex = pc_search_inventory( sd, costs->requiredItemBox );
// The box containing the item also does not exist
if( inventoryIndex < 0 ){
return false;
}
}
}else if( costs->requiredItemBox != 0 ){
inventoryIndex = pc_search_inventory( sd, costs->requiredItem );
if( inventoryIndex < 0 ){
return false;
}
}
if( inventoryIndex >= 0 && pc_delitem( sd, inventoryIndex, 1, 0, 0, LOG_TYPE_OTHER ) != 0 ){
return false;
}
if( costs->price > 0 && pc_payzeny( sd, costs->price, LOG_TYPE_OTHER, nullptr ) != 0 ){
return false;
}
switch( look ){
case LOOK_HAIR:
case LOOK_HAIR_COLOR:
case LOOK_CLOTHES_COLOR:
case LOOK_BODY2:
pc_changelook( sd, look, entry->value );
break;
case LOOK_HEAD_BOTTOM:
case LOOK_HEAD_MID:
case LOOK_HEAD_TOP: {
struct mail_message msg = {};
msg.dest_id = sd->status.char_id;
safestrncpy( msg.send_name, "Styling Shop", NAME_LENGTH );
safestrncpy( msg.title, "<MSG>2949</MSG>", MAIL_TITLE_LENGTH );
safestrncpy( msg.body, "<MSG>2950</MSG>", MAIL_BODY_LENGTH );
msg.item[0].nameid = entry->value;
msg.item[0].identify = 1;
msg.item[0].amount = 1;
msg.status = MAIL_NEW;
msg.type = MAIL_INBOX_NORMAL;
msg.timestamp = time( nullptr );
intif_Mail_send( 0, &msg );
} break;
}
return true;
}
void clif_parse_stylist_buy( int fd, struct map_session_data* sd ){
#if PACKETVER >= 20151104
#if PACKETVER >= 20180516
struct PACKET_CZ_REQ_STYLE_CHANGE2* p = (struct PACKET_CZ_REQ_STYLE_CHANGE2*)RFIFOP( fd, 0 );
#else
struct PACKET_CZ_REQ_STYLE_CHANGE* p = (struct PACKET_CZ_REQ_STYLE_CHANGE*)RFIFOP( fd, 0 );
#endif
#endif
if( p->HeadPalette != 0 && !clif_parse_stylist_buy_sub( sd, LOOK_HAIR_COLOR, p->HeadPalette ) ){
clif_stylist_response( sd, true );
return;
}
if( p->HeadStyle != 0 && !clif_parse_stylist_buy_sub( sd, LOOK_HAIR, p->HeadStyle ) ){
clif_stylist_response( sd, true );
return;
}
if( p->BodyPalette != 0 && !clif_parse_stylist_buy_sub( sd, LOOK_CLOTHES_COLOR, p->BodyPalette ) ){
clif_stylist_response( sd, true );
return;
}
if( p->TopAccessory != 0 && !clif_parse_stylist_buy_sub( sd, LOOK_HEAD_TOP, p->TopAccessory ) ){
clif_stylist_response( sd, true );
return;
}
if( p->MidAccessory != 0 && !clif_parse_stylist_buy_sub( sd, LOOK_HEAD_MID, p->MidAccessory ) ){
clif_stylist_response( sd, true );
return;
}
if( p->BottomAccessory != 0 && !clif_parse_stylist_buy_sub( sd, LOOK_HEAD_BOTTOM, p->BottomAccessory ) ){
clif_stylist_response( sd, true );
return;
}
#if PACKETVER >= 20180516
if( p->BodyStyle != 0 && ( sd->class_ & JOBL_THIRD ) != 0 && ( sd->class_ & JOBL_FOURTH ) == 0 && !clif_parse_stylist_buy_sub( sd, LOOK_BODY2, p->BodyStyle ) ){
clif_stylist_response( sd, true );
return;
}
#endif
clif_stylist_response( sd, false );
}
void clif_parse_stylist_close( int fd, struct map_session_data* sd ){
#if PACKETVER >= 20151104
sd->state.stylist_open = false;
#endif
}
/*==========================================
* Main client packet processing function
*------------------------------------------*/

View File

@ -1135,6 +1135,7 @@ enum in_ui_type : int8 {
};
enum out_ui_type : int8 {
OUT_UI_STYLIST = 1,
OUT_UI_ATTENDANCE = 7
};

View File

@ -2273,6 +2273,11 @@
parseable_packet(0x0980,7,clif_parse_SelectCart,2,6); // CZ_SELECTCART
#endif
#if PACKETVER >= 20151104
parseable_packet( HEADER_CZ_REQ_STYLE_CHANGE, sizeof( PACKET_CZ_REQ_STYLE_CHANGE ), clif_parse_stylist_buy, 0 );
parseable_packet( HEADER_CZ_REQ_STYLE_CLOSE, sizeof( PACKET_CZ_REQ_STYLE_CLOSE ), clif_parse_stylist_close, 0 );
#endif
// 2016-03-02bRagexe
#if PACKETVER >= 20160302
packet(0x0A51,34);
@ -2394,6 +2399,10 @@
packet(0x0ADD, 22);
#endif
#if PACKETVER >= 20180516
parseable_packet( HEADER_CZ_REQ_STYLE_CHANGE2, sizeof( PACKET_CZ_REQ_STYLE_CHANGE2 ), clif_parse_stylist_buy, 0 );
#endif
#if PACKETVER_MAIN_NUM >= 20181002 || PACKETVER_RE_NUM >= 20181002 || PACKETVER_ZERO_NUM >= 20181010
parseable_packet( 0x0B10, sizeof( struct PACKET_CZ_START_USE_SKILL ), clif_parse_StartUseSkillToId, 0 );
parseable_packet( 0x0B11, sizeof( struct PACKET_CZ_STOP_USE_SKILL ), clif_parse_StopUseSkillToId, 0 );

View File

@ -343,5 +343,6 @@
<Copy SourceFiles="$(SolutionDir)db\import-tmpl\spellbook_db.yml" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\spellbook_db.yml')" />
<Copy SourceFiles="$(SolutionDir)db\import-tmpl\statpoint.yml" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\statpoint.yml')" />
<Copy SourceFiles="$(SolutionDir)db\import-tmpl\status_disabled.txt" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\status_disabled.txt')" />
<Copy SourceFiles="$(SolutionDir)db\import-tmpl\stylist.yml" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\stylist.yml')" />
</Target>
</Project>

View File

@ -121,6 +121,271 @@ struct script_event_s{
// Holds pointers to the commonly executed scripts for speedup. [Skotlex]
std::map<enum npce_event, std::vector<struct script_event_s>> script_event;
const std::string StylistDatabase::getDefaultLocation(){
return std::string(db_path) + "/stylist.yml";
}
bool StylistDatabase::parseCostNode( std::shared_ptr<s_stylist_entry> entry, bool doram, const YAML::Node& node ){
std::shared_ptr<s_stylist_costs> costs = doram ? entry->doram : entry->human;
bool costs_exists = costs != nullptr;
if( !costs_exists ){
costs = std::make_shared<s_stylist_costs>();
}
if( this->nodeExists( node, "Price" ) ){
uint32 price;
if( !this->asUInt32( node, "Price", price ) ){
return false;
}
if( price > MAX_ZENY ){
this->invalidWarning( node["Price"], "stylist_parseCostNode: Price %u is too high, capping to MAX_ZENY...\n", price );
price = MAX_ZENY;
}
costs->price = price;
}else{
if( !costs_exists ){
costs->price = 0;
}
}
if( this->nodeExists( node, "RequiredItem" ) ){
std::string item;
if( !this->asString( node, "RequiredItem", item ) ){
return false;
}
std::shared_ptr<item_data> id = item_db.search_aegisname( item.c_str() );
if( id == nullptr ){
this->invalidWarning( node["RequiredItem"], "stylist_parseCostNode: Unknown item \"%s\"...\n", item.c_str() );
return false;
}
costs->requiredItem = id->nameid;
}else{
if( !costs_exists ){
costs->requiredItem = 0;
}
}
if( this->nodeExists( node, "RequiredItemBox" ) ){
std::string item;
if( !this->asString( node, "RequiredItemBox", item ) ){
return false;
}
std::shared_ptr<item_data> id = item_db.search_aegisname( item.c_str() );
if( id == nullptr ){
this->invalidWarning( node["RequiredItemBox"], "stylist_parseCostNode: Unknown item \"%s\"...\n", item.c_str() );
return false;
}
costs->requiredItemBox = id->nameid;
}else{
if( !costs_exists ){
costs->requiredItemBox = 0;
}
}
if( !costs_exists ){
if( doram ){
entry->doram = costs;
}else{
entry->human = costs;
}
}
return true;
}
uint64 StylistDatabase::parseBodyNode( const YAML::Node &node ){
if( !this->nodesExist( node, { "Look", "Options" } ) ){
return 0;
}
std::string look_str;
if( !this->asString( node, "Look", look_str ) ){
return 0;
}
int64 constant;
if( !script_get_constant( ( "LOOK_" + look_str ).c_str(), &constant ) ){
this->invalidWarning( node["Look"], "stylist_parseBodyNode: Invalid look %s.\n", look_str.c_str() );
return 0;
}
switch( constant ){
case LOOK_HEAD_TOP:
case LOOK_HEAD_MID:
case LOOK_HEAD_BOTTOM:
case LOOK_HAIR:
case LOOK_HAIR_COLOR:
case LOOK_CLOTHES_COLOR:
case LOOK_BODY2:
break;
default:
this->invalidWarning( node["Look"], "stylist_parseBodyNode: Unsupported look value \"%s\"...\n", look_str.c_str() );
return 0;
}
std::shared_ptr<s_stylist_list> list = this->find( (uint32)constant );
bool exists = list != nullptr;
uint64 count = 0;
if( !exists ){
list = std::make_shared<s_stylist_list>();
list->look = (uint16)constant;
}
for( const YAML::Node& optionNode : node["Options"] ){
int16 index;
if( !this->asInt16( optionNode, "Index", index ) ){
return 0;
}
if( index == 0 ){
this->invalidWarning( optionNode["Index"], "stylist_parseBodyNode: Unsupported index value \"%hd\"...\n", index );
return 0;
}
std::shared_ptr<s_stylist_entry> entry = util::umap_find( list->entries, index );
bool entry_exists = entry != nullptr;
if( !entry_exists ){
entry = std::make_shared<s_stylist_entry>();
entry->look = list->look;
entry->index = index;
if( !this->nodesExist( optionNode, { "Value" } ) ){
return 0;
}
}
if( this->nodeExists( optionNode, "Value" ) ){
uint32 value;
switch( list->look ){
case LOOK_HEAD_TOP:
case LOOK_HEAD_MID:
case LOOK_HEAD_BOTTOM: {
std::string item;
if( !this->asString( optionNode, "Value", item ) ){
return 0;
}
std::shared_ptr<item_data> id = item_db.search_aegisname( item.c_str() );
if( id == nullptr ){
this->invalidWarning( optionNode["Value"], "stylist_parseBodyNode: Unknown item \"%s\"...\n", item.c_str() );
return 0;
}
value = id->nameid;
} break;
case LOOK_HAIR:
if( !this->asUInt32( optionNode, "Value", value ) ){
return 0;
}
if( value < MIN_HAIR_STYLE ){
this->invalidWarning( optionNode["Value"], "stylist_parseBodyNode: hair style \"%u\" is too low...\n", value );
return 0;
}else if( value > MAX_HAIR_STYLE ){
this->invalidWarning( optionNode["Value"], "stylist_parseBodyNode: hair style \"%u\" is too high...\n", value );
return 0;
}
break;
case LOOK_HAIR_COLOR:
if( !this->asUInt32( optionNode, "Value", value ) ){
return 0;
}
if( value < MIN_HAIR_COLOR ){
this->invalidWarning( optionNode["Value"], "stylist_parseBodyNode: hair color \"%u\" is too low...\n", value );
return 0;
}else if( value > MAX_HAIR_COLOR ){
this->invalidWarning( optionNode["Value"], "stylist_parseBodyNode: hair color \"%u\" is too high...\n", value );
return 0;
}
break;
case LOOK_CLOTHES_COLOR:
if( !this->asUInt32( optionNode, "Value", value ) ){
return 0;
}
if( value < MIN_CLOTH_COLOR ){
this->invalidWarning( optionNode["Value"], "stylist_parseBodyNode: cloth color \"%u\" is too low...\n", value );
return 0;
}else if( value > MAX_CLOTH_COLOR ){
this->invalidWarning( optionNode["Value"], "stylist_parseBodyNode: cloth color \"%u\" is too high...\n", value );
return 0;
}
break;
case LOOK_BODY2:
if( !this->asUInt32( optionNode, "Value", value ) ){
return 0;
}
if( value < MIN_BODY_STYLE ){
this->invalidWarning( optionNode["Value"], "stylist_parseBodyNode: body style \"%u\" is too low...\n", value );
return 0;
}else if( value > MAX_BODY_STYLE ){
this->invalidWarning( optionNode["Value"], "stylist_parseBodyNode: body style \"%u\" is too high...\n", value );
return 0;
}
break;
}
entry->value = value;
}
if( this->nodeExists( optionNode, "CostsHuman" ) ) {
if( !this->parseCostNode( entry, false, optionNode["CostsHuman"] ) ){
return 0;
}
}else{
if( !entry_exists ){
entry->human = nullptr;
}
}
if( this->nodeExists( optionNode, "CostsDoram" ) ) {
if( !this->parseCostNode( entry, true, optionNode["CostsDoram"] ) ){
return 0;
}
}else{
if( !entry_exists ){
entry->doram = nullptr;
}
}
if( !entry_exists ){
list->entries[index] = entry;
}
count++;
}
if( !exists ){
this->put( (uint32)constant, list );
}
return count;
}
StylistDatabase stylist_db;
/**
* Returns the viewdata for normal NPC classes.
* @param class_: NPC class ID
@ -4785,6 +5050,8 @@ int npc_reload(void) {
"\t-'" CL_WHITE "%d" CL_RESET "' Mobs Not Cached\n",
npc_id - npc_new_min, npc_warp, npc_shop, npc_script, npc_mob, npc_cache_mob, npc_delay_mob);
stylist_db.reload();
//Re-read the NPC Script Events cache.
npc_read_event_script();
@ -4851,6 +5118,7 @@ void do_final_npc(void) {
#if PACKETVER >= 20131223
NPCMarketDB->destroy(NPCMarketDB, npc_market_free);
#endif
stylist_db.clear();
ers_destroy(timer_event_ers);
ers_destroy(npc_sc_display_ers);
npc_clearsrcfile();
@ -4936,6 +5204,8 @@ void do_init_npc(void){
"\t-'" CL_WHITE "%d" CL_RESET "' Mobs Not Cached\n",
npc_id - START_NPC_NUM, npc_warp, npc_shop, npc_script, npc_mob, npc_cache_mob, npc_delay_mob);
stylist_db.load();
// set up the events cache
npc_read_event_script();

View File

@ -51,6 +51,40 @@ struct s_npc_buy_list {
#pragma pack(pop)
#endif // not NetBSD < 6 / Solaris
struct s_stylist_costs{
uint32 price;
t_itemid requiredItem;
t_itemid requiredItemBox;
};
struct s_stylist_entry{
uint16 look;
int16 index;
uint32 value;
std::shared_ptr<s_stylist_costs> human;
std::shared_ptr<s_stylist_costs> doram;
};
struct s_stylist_list{
uint16 look;
std::unordered_map<int16, std::shared_ptr<s_stylist_entry>> entries;
};
class StylistDatabase : public TypesafeYamlDatabase<uint32, s_stylist_list>{
private:
bool parseCostNode( std::shared_ptr<s_stylist_entry> entry, bool doram, const YAML::Node& node );
public:
StylistDatabase() : TypesafeYamlDatabase( "STYLIST_DB", 1 ){
}
const std::string getDefaultLocation();
uint64 parseBodyNode( const YAML::Node& node );
};
extern StylistDatabase stylist_db;
struct s_questinfo {
e_questinfo_types icon;
e_questinfo_markcolor color;

View File

@ -222,6 +222,10 @@ struct PACKET_CZ_UNCONFIRMED_RODEX_RETURN{
uint32 msgId;
} __attribute__((packed));
struct PACKET_CZ_REQ_STYLE_CLOSE{
int16 packetType;
} __attribute__((packed));
// NetBSD 5 and Solaris don't like pragma pack but accept the packed attribute
#if !defined( sun ) && ( !defined( __NETBSD__ ) || __NetBSD_Version__ >= 600000000 )
#pragma pack( pop )
@ -275,6 +279,10 @@ DEFINE_PACKET_HEADER(ZC_ACK_COUNT_BARGAIN_SALE_ITEM, 0x9c4)
DEFINE_PACKET_HEADER(ZC_ACK_GUILDSTORAGE_LOG, 0x9da)
DEFINE_PACKET_HEADER(CZ_NPC_MARKET_PURCHASE, 0x9d6)
DEFINE_PACKET_HEADER(CZ_REQ_APPLY_BARGAIN_SALE_ITEM2, 0xa3d)
DEFINE_PACKET_HEADER(CZ_REQ_STYLE_CHANGE, 0xa46)
DEFINE_PACKET_HEADER(ZC_STYLE_CHANGE_RES, 0xa47)
DEFINE_PACKET_HEADER(CZ_REQ_STYLE_CLOSE, 0xa48)
DEFINE_PACKET_HEADER(CZ_REQ_STYLE_CHANGE2, 0xafc)
DEFINE_PACKET_HEADER(ZC_REMOVE_EFFECT, 0x0b0d)
DEFINE_PACKET_HEADER(CZ_UNCONFIRMED_TSTATUS_UP, 0x0b24)
DEFINE_PACKET_HEADER(CZ_GUILD_EMBLEM_CHANGE2, 0x0b46)

View File

@ -382,6 +382,7 @@ struct map_session_data {
bool mail_writing; // Whether the player is currently writing a mail in RODEX or not
bool cashshop_open;
bool sale_open;
bool stylist_open;
unsigned int block_action : 10;
bool refineui_open;
} state;
@ -1049,9 +1050,15 @@ extern JobDatabase job_db;
#define pc_isidle_hom(sd) ( (sd)->hd && ( (sd)->chatID || (sd)->state.vending || (sd)->state.buyingstore || DIFF_TICK(last_tick, (sd)->idletime_hom) >= battle_config.hom_idle_no_share ) )
#define pc_isidle_mer(sd) ( (sd)->md && ( (sd)->chatID || (sd)->state.vending || (sd)->state.buyingstore || DIFF_TICK(last_tick, (sd)->idletime_mer) >= battle_config.mer_idle_no_share ) )
#define pc_istrading(sd) ( (sd)->npc_id || (sd)->state.vending || (sd)->state.buyingstore || (sd)->state.trading )
static bool pc_cant_act2( struct map_session_data* sd ){
return sd->state.vending || sd->state.buyingstore || (sd->sc.opt1 && sd->sc.opt1 != OPT1_BURNING)
|| sd->state.trading || sd->state.storage_flag || sd->state.prevend || sd->state.refineui_open
|| sd->state.stylist_open;
}
// equals pc_cant_act2 and additionally checks for chat rooms and npcs
#define pc_cant_act(sd) ( (sd)->npc_id || (sd)->chatID || pc_cant_act2( (sd) ) )
#define pc_cant_act2(sd) ( (sd)->state.vending || (sd)->state.buyingstore || ((sd)->sc.opt1 && (sd)->sc.opt1 != OPT1_BURNING) || (sd)->state.trading || (sd)->state.storage_flag || (sd)->state.prevend || (sd)->state.refineui_open )
static bool pc_cant_act( struct map_session_data* sd ){
return sd->npc_id || sd->chatID || pc_cant_act2( sd );
}
#define pc_setdir(sd,b,h) ( (sd)->ud.dir = (b) ,(sd)->head_dir = (h) )
#define pc_setchatid(sd,n) ( (sd)->chatID = n )

View File

@ -25587,6 +25587,23 @@ BUILDIN_FUNC(mob_setidleevent){
return SCRIPT_CMD_SUCCESS;
}
BUILDIN_FUNC( openstylist ){
#if PACKETVER >= 20151104
struct map_session_data* sd;
if( !script_charid2sd( 2, sd ) ){
return SCRIPT_CMD_FAILURE;
}
clif_ui_open( sd, OUT_UI_STYLIST, 0 );
return SCRIPT_CMD_SUCCESS;
#else
ShowError( "buildin_openstylist: This command requires packet version 2015-11-04 or newer.\n" );
return SCRIPT_CMD_FAILURE;
#endif
}
#include "../custom/script.inc"
// declarations that were supposed to be exported from npc_chat.cpp
@ -26291,6 +26308,7 @@ struct script_function buildin_func[] = {
BUILDIN_DEF(mob_setidleevent, "is"),
BUILDIN_DEF(setinstancevar,"rvi"),
BUILDIN_DEF(openstylist, "?"),
#include "../custom/script_def.inc"
{NULL,NULL,NULL},