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. // Valid range of dyes and styles on the client.
min_hair_style: 0 min_hair_style: 0
max_hair_style: 27 max_hair_style: 42
min_hair_color: 0 min_hair_color: 0
max_hair_color: 8 max_hair_color: 8
min_cloth_color: 0 min_cloth_color: 0
max_cloth_color: 4 max_cloth_color: 7
min_body_style: 0 min_body_style: 0
max_body_style: 1 max_body_style: 1

View File

@ -876,7 +876,9 @@
797: This command is unavailable to non-4th class. 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 800: Dragon Knight
801: Meister 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 NoMail: true
NoAuction: true NoAuction: true
- Id: 6707 - Id: 6707
AegisName: Jeremy_Beauty_Coupon AegisName: J_Shop_Coupon
Name: Jeremy Beauty Coupon Name: Cash Hair Coupon
Type: Etc Type: Etc
Buy: 10 Buy: 10
Weight: 10 Weight: 10
@ -24023,9 +24023,10 @@ Body:
Type: Etc Type: Etc
Buy: 10 Buy: 10
- Id: 6959 - Id: 6959
AegisName: aegis_6959 AegisName: Costume_Ticket
Name: Costume Change Ticket Name: Costume Change Ticket
Type: Etc Type: Etc
Buy: 0
Trade: Trade:
Override: 100 Override: 100
NoDrop: true NoDrop: true

View File

@ -39884,6 +39884,57 @@ Body:
NoGuildStorage: true NoGuildStorage: true
NoMail: true NoMail: true
NoAuction: 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 - Id: 16864
AegisName: Siege_Map_Teleport_Scroll_Box_10 AegisName: Siege_Map_Teleport_Scroll_Box_10
Name: Siege Map Teleport Scroll Box(10) Name: Siege Map Teleport Scroll Box(10)
@ -42918,6 +42969,23 @@ Body:
NoAuction: true NoAuction: true
Script: | Script: |
getgroupitem(IG_Event_Almighty_Box_100); 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 - Id: 17337
AegisName: Holy_Spirit_Scroll AegisName: Holy_Spirit_Scroll
Name: Holy Spirit Egg 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> @request <message>
Sends a message to all connected GMs (via the GM whisper system). 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>) *getareadropitem("<map name>",<x1>,<y1>,<x2>,<y2>,<item>)
This function will count all the items with the specified ID number lying on the 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 #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" #include "../custom/atcommand.inc"
/** /**
@ -10975,6 +10993,7 @@ void atcommand_basecommands(void) {
ACMD_DEF2("completequest", quest), ACMD_DEF2("completequest", quest),
ACMD_DEF2("checkquest", quest), ACMD_DEF2("checkquest", quest),
ACMD_DEF(refineui), ACMD_DEF(refineui),
ACMD_DEFR(stylist, ATCMD_NOCONSOLE|ATCMD_NOAUTOTRADE),
}; };
AtCommandInfo* atcommand; AtCommandInfo* atcommand;
int i; 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 ){ void clif_ui_open( struct map_session_data *sd, enum out_ui_type ui_type, int32 data ){
nullpo_retv(sd); 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; int fd = sd->fd;
WFIFOHEAD(fd,packet_len(0xae2)); WFIFOHEAD(fd,packet_len(0xae2));
@ -22364,6 +22371,172 @@ void clif_parse_unequipall( int fd, struct map_session_data* sd ){
#endif #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 * Main client packet processing function
*------------------------------------------*/ *------------------------------------------*/

View File

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

View File

@ -2273,6 +2273,11 @@
parseable_packet(0x0980,7,clif_parse_SelectCart,2,6); // CZ_SELECTCART parseable_packet(0x0980,7,clif_parse_SelectCart,2,6); // CZ_SELECTCART
#endif #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 // 2016-03-02bRagexe
#if PACKETVER >= 20160302 #if PACKETVER >= 20160302
packet(0x0A51,34); packet(0x0A51,34);
@ -2394,6 +2399,10 @@
packet(0x0ADD, 22); packet(0x0ADD, 22);
#endif #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 #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( 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 ); 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\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\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\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> </Target>
</Project> </Project>

View File

@ -121,6 +121,271 @@ struct script_event_s{
// Holds pointers to the commonly executed scripts for speedup. [Skotlex] // Holds pointers to the commonly executed scripts for speedup. [Skotlex]
std::map<enum npce_event, std::vector<struct script_event_s>> script_event; 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. * Returns the viewdata for normal NPC classes.
* @param class_: NPC class ID * @param class_: NPC class ID
@ -4785,6 +5050,8 @@ int npc_reload(void) {
"\t-'" CL_WHITE "%d" CL_RESET "' Mobs Not Cached\n", "\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); 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. //Re-read the NPC Script Events cache.
npc_read_event_script(); npc_read_event_script();
@ -4851,6 +5118,7 @@ void do_final_npc(void) {
#if PACKETVER >= 20131223 #if PACKETVER >= 20131223
NPCMarketDB->destroy(NPCMarketDB, npc_market_free); NPCMarketDB->destroy(NPCMarketDB, npc_market_free);
#endif #endif
stylist_db.clear();
ers_destroy(timer_event_ers); ers_destroy(timer_event_ers);
ers_destroy(npc_sc_display_ers); ers_destroy(npc_sc_display_ers);
npc_clearsrcfile(); npc_clearsrcfile();
@ -4936,6 +5204,8 @@ void do_init_npc(void){
"\t-'" CL_WHITE "%d" CL_RESET "' Mobs Not Cached\n", "\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); 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 // set up the events cache
npc_read_event_script(); npc_read_event_script();

View File

@ -51,6 +51,40 @@ struct s_npc_buy_list {
#pragma pack(pop) #pragma pack(pop)
#endif // not NetBSD < 6 / Solaris #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 { struct s_questinfo {
e_questinfo_types icon; e_questinfo_types icon;
e_questinfo_markcolor color; e_questinfo_markcolor color;

View File

@ -222,6 +222,10 @@ struct PACKET_CZ_UNCONFIRMED_RODEX_RETURN{
uint32 msgId; uint32 msgId;
} __attribute__((packed)); } __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 // NetBSD 5 and Solaris don't like pragma pack but accept the packed attribute
#if !defined( sun ) && ( !defined( __NETBSD__ ) || __NetBSD_Version__ >= 600000000 ) #if !defined( sun ) && ( !defined( __NETBSD__ ) || __NetBSD_Version__ >= 600000000 )
#pragma pack( pop ) #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(ZC_ACK_GUILDSTORAGE_LOG, 0x9da)
DEFINE_PACKET_HEADER(CZ_NPC_MARKET_PURCHASE, 0x9d6) DEFINE_PACKET_HEADER(CZ_NPC_MARKET_PURCHASE, 0x9d6)
DEFINE_PACKET_HEADER(CZ_REQ_APPLY_BARGAIN_SALE_ITEM2, 0xa3d) 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(ZC_REMOVE_EFFECT, 0x0b0d)
DEFINE_PACKET_HEADER(CZ_UNCONFIRMED_TSTATUS_UP, 0x0b24) DEFINE_PACKET_HEADER(CZ_UNCONFIRMED_TSTATUS_UP, 0x0b24)
DEFINE_PACKET_HEADER(CZ_GUILD_EMBLEM_CHANGE2, 0x0b46) 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 mail_writing; // Whether the player is currently writing a mail in RODEX or not
bool cashshop_open; bool cashshop_open;
bool sale_open; bool sale_open;
bool stylist_open;
unsigned int block_action : 10; unsigned int block_action : 10;
bool refineui_open; bool refineui_open;
} state; } 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_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_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 ) #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 // 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) ) ) static bool pc_cant_act( struct map_session_data* 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 ) 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_setdir(sd,b,h) ( (sd)->ud.dir = (b) ,(sd)->head_dir = (h) )
#define pc_setchatid(sd,n) ( (sd)->chatID = n ) #define pc_setchatid(sd,n) ( (sd)->chatID = n )

View File

@ -25587,6 +25587,23 @@ BUILDIN_FUNC(mob_setidleevent){
return SCRIPT_CMD_SUCCESS; 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" #include "../custom/script.inc"
// declarations that were supposed to be exported from npc_chat.cpp // 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(mob_setidleevent, "is"),
BUILDIN_DEF(setinstancevar,"rvi"), BUILDIN_DEF(setinstancevar,"rvi"),
BUILDIN_DEF(openstylist, "?"),
#include "../custom/script_def.inc" #include "../custom/script_def.inc"
{NULL,NULL,NULL}, {NULL,NULL,NULL},