* Implemented buying store system (aka. reverse vending, purchase shop) together with related skill and items, without NPCs.

- For SQL apply upgrade_svn14713_log.sql to upgrade tables `picklog` and `zenylog`; for TXT no action is necessary.
- Requires 2010-04-20aRagexeRE or later and can be disabled in 'conf/battle/feature.conf'.

git-svn-id: https://svn.code.sf.net/p/rathena/svn/trunk@14713 54d463be-8e91-2dee-dedb-b68131a5f0ec
This commit is contained in:
ai4rei 2011-02-19 12:59:36 +00:00
parent 5c70e95c79
commit ce3f78556c
43 changed files with 1978 additions and 42 deletions

View File

@ -1,5 +1,9 @@
Date Added
2011/02/19
* Implemented buying store system (aka. reverse vending, purchase shop) together with related skill and items, without NPCs. [Ai4rei]
- For SQL apply upgrade_svn14713_log.sql to upgrade tables `picklog` and `zenylog`; for TXT no action is necessary.
- Requires 2010-04-20aRagexeRE or later and can be disabled in 'conf/battle/feature.conf'.
2011/02/17
* Merged enumeration update from renewal [14699/branches/renewal] for a future commit. [Ai4rei]
2011/02/16

View File

@ -1,5 +1,8 @@
Date Added
2011/02/19
* Rev. 14713 Added map-server feature settings file 'battle/feature.conf'. [Ai4rei]
- Added setting 'feature.buying_store' to enable/disable the buying store system.
2011/02/15
* Rev. 14707 Added map-server battle setting 'gm_check_minlevel'. [Ai4rei]
2011/02/06

24
conf/battle/feature.conf Normal file
View File

@ -0,0 +1,24 @@
// ______ __ __
// /\ _ \/\ \__/\ \
// __\ \ \L\ \ \ ,_\ \ \___ __ ___ __
// /'__`\ \ __ \ \ \/\ \ _ `\ /'__`\/' _ `\ /'__`\
///\ __/\ \ \/\ \ \ \_\ \ \ \ \/\ __//\ \/\ \/\ \L\.\_
//\ \____\\ \_\ \_\ \__\\ \_\ \_\ \____\ \_\ \_\ \__/.\_\
// \/____/ \/_/\/_/\/__/ \/_/\/_/\/____/\/_/\/_/\/__/\/_/
// _ _ _ _ _ _ _ _ _ _ _ _ _
// / \ / \ / \ / \ / \ / \ / \ / \ / \ / \ / \ / \ / \
//( e | n | g | l | i | s | h ) ( A | t | h | e | n | a )
// \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/
//
//--------------------------------------------------------------
// eAthena Feature Configuration File
//--------------------------------------------------------------
// Note 1: Value is a config switch (on/off, yes/no or 1/0)
// Note 2: Value is in percents (100 means 100%)
// Note 3: Value is a bit field. If no description is given,
// assume unit types (1: Pc, 2: Mob, 4: Pet, 8: Homun)
//--------------------------------------------------------------
// Buying store (Note 1)
// Requires: 2010-04-20aRagexeRE or later
feature.buying_store: on

View File

@ -16,6 +16,7 @@
// 2048 - (R) Log items placed/retrieved from storage.
// 4096 - (G) Log items placed/retrieved from guild storage.
// 8192 - (E) Log mail system transactions.
// 16384 - (B) Log buying store transactions
// Example: Log trades+vending+script items+created items: 2+4+64+1024 = 1094
enable_logs: 1

View File

@ -9,6 +9,12 @@
13005 Angelic Wing Dagger: NEED INFO.
=======================
2011/02/19
* Rev. 14713 Database updates required by buying store system implementation. [Ai4rei]
- Added database of items, that can be sold to a buying store (item_buyingstore.txt).
- Added items Buy_Stall_Permit (6377) and Shabby_Purchase_Street_Stall_License (12548).
- Updated packet database with buying store related packets.
- Added skill 'Open Buying Store' (ALL_BUYING_STORE).
2011/02/06
* Rev. 14697 Added the missing restricted skills to zone 6 for Endless Tower. (bugreport:4707) [L0ne_W0lf]
* Adjusted the rates for item using bAddEffOnSkill bonus.

1013
db/item_buyingstore.txt Normal file

File diff suppressed because it is too large Load Diff

View File

@ -3293,7 +3293,7 @@
//6362,Yellow_Wish_Point,Yellow Wish Point,3,100,,5,,,,,,,,,,,,,{},{},{}
//6363,Lime_Green_Point,Lime Green Point,3,100,,5,,,,,,,,,,,,,{},{},{}
//6376,KVM_Badge
//6377,Buy_Stall_Permit,Purchase Street Stall License,3,200,,10,,,,,,,,,,,,,{},{},{}
6377,Buy_Stall_Permit,Purchase Street Stall License,3,200,,10,,,,,,,,,,,,,{},{},{}
//6378
//6379
//6380
@ -4823,7 +4823,7 @@
//12471,LV5_Adrenaline_Scroll,
//12472,Convex_Mirror,
//12475,Cure_Free,
//12548,Shabby_Purchase_Street_Stall_License,
12548,Shabby_Purchase_Street_Stall_License,Shabby Purchase Street Stall License,2,500,,10,,,,,0xFFFFFFFF,7,2,,,,,,{ buyingstore 2; },{},{}
//12553,Brysingamen_Piece_Box,
//12554,Asprika_Piece_Box,
//12555,Brynhild_Piece_Box,

View File

@ -1488,8 +1488,8 @@ packet_ver: 25
0x080F,20
//2010-03-03aRagexeRE
//0x0810,3
//0x0811,-1
0x0810,3
0x0811,-1,reqopenbuyingstore,2:4:8:9:89
//0x0812,86
//0x0813,6
//0x0814,6
@ -1499,11 +1499,11 @@ packet_ver: 25
//0x0819,4
//2010-03-09aRagexeRE
//0x0813,-1
0x0813,-1
//0x0814,2
//0x0815,6
//0x0816,6
//0x0818,-1
0x0816,6
0x0818,-1
//0x0819,10
//0x081A,4
//0x081B,4
@ -1528,15 +1528,15 @@ packet_ver: 25
//0x081B,8
//2010-04-20aRagexeRE
//0x0812,8
//0x0814,86
//0x0815,2
//0x0817,6
//0x0819,-1
//0x081a,4
//0x081b,10
//0x081c,10
//0x0824,6
0x0812,8
0x0814,86
0x0815,2,reqclosebuyingstore,0
0x0817,6,reqclickbuyingstore,2
0x0819,-1,reqtradebuyingstore,2:4:8:12
0x081a,4
0x081b,10
0x081c,10
0x0824,6
//2010-06-01aRagexeRE
//0x0825,-1

View File

@ -938,6 +938,9 @@
// Episode 13.3
//2534,0,0,0,0,0,0,9,0,no,0,0,0,none,0 RETURN_TO_ELDICASTES,
// Buying Store
2535,0,0,4,0,0x1,0,1,0,no,0,0x1,0,none,0, ALL_BUYING_STORE,Open Buying Store
8001,9,6,4,0,0x1,0,5,1,no,0,0,0,magic,0, HLIF_HEAL,Healing Touch
8002,0,6,4,0,0x3,-1,5,1,no,0,0,0,none,0, HLIF_AVOID,Avoid
8003,0,0,0,0,0,1,5,0,no,0,0,0,none,0, HLIF_BRAIN,Brain Surgery

View File

@ -454,6 +454,8 @@
1018,0,0,30,0,0,0,99,0,0,none,0,12114,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 //SA_ELEMENTFIRE
1019,0,0,30,0,0,0,99,0,0,none,0,12117,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 //SA_ELEMENTWIND
2535,0,0,1,0,0,0,99,0,0,none,0,6377,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 //ALL_BUYING_STORE#Open Buying Store
10010,0,0,1,0,0,0,99,0,0,none,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 //GD_BATTLEORDER##
10011,0,0,1,0,0,0,99,0,0,none,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 //GD_REGENERATION##
10012,0,0,1,0,0,0,99,0,0,none,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 //GD_RESTORE##

View File

@ -86,6 +86,7 @@
5,155,1,0,0,0,0,0,0,0,0,0,0 //MC_LOUD#Crazy Uproar#
5,410,1,0,0,0,0,0,0,0,0,0,0 //WE_CALLBABY#Call Baby#
5,681,1,0,0,0,0,0,0,0,0,0,0 //ALL_INCCARRY#Enlarge Weight Limit R#
5,2535,1,41,1,0,0,0,0,0,0,0,0 //ALL_BUYING_STORE#Open Buying Store#
//Thief
6,1,9,0,0,0,0,0,0,0,0,0,0 //NV_BASIC#Basic Skill#
6,142,1,0,0,0,0,0,0,0,0,0,0 //NV_FIRSTAID#First Aid#
@ -240,6 +241,7 @@
10,410,1,0,0,0,0,0,0,0,0,0,0 //WE_CALLBABY#Call Baby#
10,459,1,111,5,0,0,0,0,0,0,0,0 //BS_ADRENALINE2#Full Adrenaline Rush#
10,681,1,0,0,0,0,0,0,0,0,0,0 //ALL_INCCARRY#Enlarge Weight Limit R#
10,2535,1,41,1,0,0,0,0,0,0,0,0 //ALL_BUYING_STORE#Open Buying Store#
//Hunter
11,1,9,0,0,0,0,0,0,0,0,0,0 //NV_BASIC#Basic Skill#
11,142,1,0,0,0,0,0,0,0,0,0,0 //NV_FIRSTAID#First Aid#
@ -511,6 +513,7 @@
18,497,1,228,10,0,0,0,0,0,0,0,0 //AM_TWILIGHT2#Twilight Alchemy 2#
18,498,1,228,10,0,0,0,0,0,0,0,0 //AM_TWILIGHT3#Twilight Alchemy 3#
18,681,1,0,0,0,0,0,0,0,0,0,0 //ALL_INCCARRY#Enlarge Weight Limit R#
18,2535,1,41,1,0,0,0,0,0,0,0,0 //ALL_BUYING_STORE#Open Buying Store#
//Bard
19,1,9,0,0,0,0,0,0,0,0,0,0 //NV_BASIC#Basic Skill#
19,142,1,0,0,0,0,0,0,0,0,0,0 //NV_FIRSTAID#First Aid#
@ -668,6 +671,7 @@
23,53,1,52,3,0,0,0,0,0,0,0,0 //TF_DETOXIFY#Detoxify#
23,410,1,0,0,0,0,0,0,0,0,0,0 //WE_CALLBABY#Call Baby#
23,681,1,0,0,0,0,0,0,0,0,0,0 //ALL_INCCARRY#Enlarge Weight Limit R#
23,2535,1,41,1,0,0,0,0,0,0,0,0 //ALL_BUYING_STORE#Open Buying Store#
//Gunslinger
24,1,9,0,0,0,0,0,0,0,0,0,0 //NV_BASIC#Basic Skill#
24,142,1,0,0,0,0,0,0,0,0,0,0 //NV_FIRSTAID#First Aid#
@ -810,6 +814,7 @@
4006,155,1,0,0,0,0,0,0,0,0,0,0 //MC_LOUD#Crazy Uproar#
4006,410,1,0,0,0,0,0,0,0,0,0,0 //WE_CALLBABY#Call Baby#
4006,681,1,0,0,0,0,0,0,0,0,0,0 //ALL_INCCARRY#Enlarge Weight Limit R#
4006,2535,1,41,1,0,0,0,0,0,0,0,0 //ALL_BUYING_STORE#Open Buying Store#
//ThiefHigh
4007,1,9,0,0,0,0,0,0,0,0,0,0 //NV_BASIC#Basic Skill#
4007,142,1,0,0,0,0,0,0,0,0,0,0 //NV_FIRSTAID#First Aid#
@ -989,6 +994,7 @@
4011,410,1,0,0,0,0,0,0,0,0,0,0 //WE_CALLBABY#Call Baby#
4011,459,1,111,5,0,0,0,0,0,0,0,0 //BS_ADRENALINE2#Full Adrenaline Rush#
4011,681,1,0,0,0,0,0,0,0,0,0,0 //ALL_INCCARRY#Enlarge Weight Limit R#
4011,2535,1,41,1,0,0,0,0,0,0,0,0 //ALL_BUYING_STORE#Open Buying Store#
//Sniper
4012,1,9,0,0,0,0,0,0,0,0,0,0 //NV_BASIC#Basic Skill#
4012,142,1,0,0,0,0,0,0,0,0,0,0 //NV_FIRSTAID#First Aid#
@ -1303,6 +1309,7 @@
4019,497,1,228,10,0,0,0,0,0,0,0,0 //AM_TWILIGHT2#Twilight Alchemy 2#
4019,498,1,228,10,0,0,0,0,0,0,0,0 //AM_TWILIGHT3#Twilight Alchemy 3#
4019,681,1,0,0,0,0,0,0,0,0,0,0 //ALL_INCCARRY#Enlarge Weight Limit R#
4019,2535,1,41,1,0,0,0,0,0,0,0,0 //ALL_BUYING_STORE#Open Buying Store#
//Clown
4020,1,9,0,0,0,0,0,0,0,0,0,0 //NV_BASIC#Basic Skill#
4020,142,1,0,0,0,0,0,0,0,0,0,0 //NV_FIRSTAID#First Aid#
@ -1508,6 +1515,7 @@
4028,408,1,0,0,0,0,0,0,0,0,0,0 //WE_BABY#Baby#
4028,409,1,0,0,0,0,0,0,0,0,0,0 //WE_CALLPARENT#Call Parent#
4028,681,1,0,0,0,0,0,0,0,0,0,0 //ALL_INCCARRY#Enlarge Weight Limit R#
4028,2535,1,41,1,0,0,0,0,0,0,0,0 //ALL_BUYING_STORE#Open Buying Store#
//Baby Thief
4029,1,9,0,0,0,0,0,0,0,0,0,0 //NV_BASIC#Basic Skill#
4029,142,1,0,0,0,0,0,0,0,0,0,0 //NV_FIRSTAID#First Aid#
@ -1666,6 +1674,7 @@
4033,1013,1,0,0,0,0,0,0,0,0,0,0 //BS_GREED#Greed#
4033,459,1,111,5,0,0,0,0,0,0,0,0 //BS_ADRENALINE2#Full Adrenaline Rush#
4033,681,1,0,0,0,0,0,0,0,0,0,0 //ALL_INCCARRY#Enlarge Weight Limit R#
4033,2535,1,41,1,0,0,0,0,0,0,0,0 //ALL_BUYING_STORE#Open Buying Store#
//Baby Hunter
4034,1,9,0,0,0,0,0,0,0,0,0,0 //NV_BASIC#Basic Skill#
4034,142,1,0,0,0,0,0,0,0,0,0,0 //NV_FIRSTAID#First Aid#
@ -1945,6 +1954,7 @@
4041,497,1,228,10,0,0,0,0,0,0,0,0 //AM_TWILIGHT2#Twilight Alchemy 2#
4041,498,1,228,10,0,0,0,0,0,0,0,0 //AM_TWILIGHT3#Twilight Alchemy 3#
4041,681,1,0,0,0,0,0,0,0,0,0,0 //ALL_INCCARRY#Enlarge Weight Limit R#
4041,2535,1,41,1,0,0,0,0,0,0,0,0 //ALL_BUYING_STORE#Open Buying Store#
//Baby Bard
4042,1,9,0,0,0,0,0,0,0,0,0,0 //NV_BASIC#Basic Skill#
4042,142,1,0,0,0,0,0,0,0,0,0,0 //NV_FIRSTAID#First Aid#
@ -2099,6 +2109,7 @@
4045,408,1,0,0,0,0,0,0,0,0,0,0 //WE_BABY#Baby#
4045,409,1,0,0,0,0,0,0,0,0,0,0 //WE_CALLPARENT#Call Parent#
4045,681,1,0,0,0,0,0,0,0,0,0,0 //ALL_INCCARRY#Enlarge Weight Limit R#
4045,2535,1,41,1,0,0,0,0,0,0,0,0 //ALL_BUYING_STORE#Open Buying Store#
//Taekwon
4046,1,9,0,0,0,0,0,0,0,0,0,0 //NV_BASIC#Basic Skill#
4046,142,1,0,0,0,0,0,0,0,0,0,0 //NV_FIRSTAID#First Aid#

View File

@ -4,7 +4,7 @@
//= A reference manual for the eAthena scripting language.
//= Commands are sorted depending on their functionality.
//===== Version ===========================================
//= 3.35.20110106
//= 3.36.20110219
//=========================================================
//= 1.0 - First release, filled will as much info as I could
//= remember or figure out, most likely there are errors,
@ -155,6 +155,8 @@
//= Spellcheck. [Ai4rei]
//= 3.35.20110106
//= Removed bug warning from 'deletearray'. [Ai4rei]
//= 3.36.20110219
//= Added 'buyingstore' command. [Ai4rei]
//=========================================================
This document is a reference manual for all the scripting commands and functions
@ -4423,6 +4425,19 @@ Example(s):
//The invoked character will no longer automatically equip a falchion.
autoequip 1104,0;
---------------------------------------
*buyingstore <slots>;
Invokes buying store preparation window like the skill 'Open Buying Store',
without the item requirement. Amount of slots is limited by the server to
a maximum of 5 slots by default.
Example:
// Gives the player oppurtunity to buy 4 different kinds of items.
buyingstore 4;
---------------------------------------
//
4,1.- End of item-related commands

View File

@ -1,7 +1,7 @@
#PickLog types (M)onsters Drop, (P)layers Drop/Take, Mobs Drop (L)oot Drop/Take,
# Players (T)rade Give/Take, Players (V)ending Sell/Take, (S)hop Sell/Take, (N)PC Give/Take,
# (C)onsumable Items, (A)dministrators Create/Delete, Sto(R)age, (G)uild Storage,
# (E)mail attachment
# (E)mail attachment,(B)uying Store
#Database: log
#Table: picklog
@ -9,7 +9,7 @@ CREATE TABLE `picklog` (
`id` int(11) NOT NULL auto_increment,
`time` datetime NOT NULL default '0000-00-00 00:00:00',
`char_id` int(11) NOT NULL default '0',
`type` enum('M','P','L','T','V','S','N','C','A','R','G','E') NOT NULL default 'P',
`type` enum('M','P','L','T','V','S','N','C','A','R','G','E','B') NOT NULL default 'P',
`nameid` int(11) NOT NULL default '0',
`amount` int(11) NOT NULL default '1',
`refine` tinyint(3) unsigned NOT NULL default '0',
@ -22,7 +22,7 @@ CREATE TABLE `picklog` (
INDEX (`type`)
) ENGINE=MyISAM AUTO_INCREMENT=1 ;
#ZenyLog types (M)onsters,(T)rade,(V)ending Sell/Buy,(S)hop Sell/Buy,(N)PC Change amount,(A)dministrators,(E)Mail
#ZenyLog types (M)onsters,(T)rade,(V)ending Sell/Buy,(S)hop Sell/Buy,(N)PC Change amount,(A)dministrators,(E)Mail,(B)uying Store
#Database: log
#Table: zenylog
CREATE TABLE `zenylog` (
@ -30,7 +30,7 @@ CREATE TABLE `zenylog` (
`time` datetime NOT NULL default '0000-00-00 00:00:00',
`char_id` int(11) NOT NULL default '0',
`src_id` int(11) NOT NULL default '0',
`type` enum('M','T','V','S','N','A','E') NOT NULL default 'S',
`type` enum('M','T','V','S','N','A','E','B') NOT NULL default 'S',
`amount` int(11) NOT NULL default '0',
`map` varchar(11) NOT NULL default '',
PRIMARY KEY (`id`),

View File

@ -0,0 +1,4 @@
-- Adds 'B' to `type` in `picklog` and `zenylog`
ALTER TABLE `picklog` MODIFY `type` ENUM('M','P','L','T','V','S','N','C','A','R','G','E','B') NOT NULL DEFAULT 'P';
ALTER TABLE `zenylog` MODIFY `type` ENUM('M','T','V','S','N','A','E','B') NOT NULL DEFAULT 'S';

View File

@ -84,7 +84,7 @@
#define MAX_ZENY 1000000000
#define MAX_FAME 1000000000
#define MAX_CART 100
#define MAX_SKILL 1020
#define MAX_SKILL 2536
#define GLOBAL_REG_NUM 256
#define ACCOUNT_REG_NUM 64
#define ACCOUNT_REG2_NUM 16

View File

@ -17,7 +17,8 @@ MAP_OBJ = map.o chrif.o clif.o pc.o status.o npc.o \
npc_chat.o chat.o path.o itemdb.o mob.o script.o \
storage.o skill.o atcommand.o battle.o battleground.o \
intif.o trade.o party.o vending.o guild.o pet.o \
log.o mail.o date.o unit.o homunculus.o mercenary.o quest.o instance.o
log.o mail.o date.o unit.o homunculus.o mercenary.o quest.o instance.o \
buyingstore.o
MAP_TXT_OBJ = $(MAP_OBJ:%=obj_txt/%) \
obj_txt/mapreg_txt.o
MAP_SQL_OBJ = $(MAP_OBJ:%=obj_sql/%) \
@ -26,7 +27,8 @@ MAP_H = map.h chrif.h clif.h pc.h status.h npc.h \
chat.h itemdb.h mob.h script.h path.h \
storage.h skill.h atcommand.h battle.h battleground.h \
intif.h trade.h party.h vending.h guild.h pet.h \
log.h mail.h date.h unit.h homunculus.h mercenary.h quest.h instance.h mapreg.h
log.h mail.h date.h unit.h homunculus.h mercenary.h quest.h instance.h mapreg.h \
buyingstore.h
HAVE_MYSQL=@HAVE_MYSQL@
ifeq ($(HAVE_MYSQL),yes)

View File

@ -1150,7 +1150,7 @@ ACMD_FUNC(storage)
{
nullpo_retr(-1, sd);
if (sd->npc_id || sd->vender_id || sd->state.trading || sd->state.storage_flag)
if (sd->npc_id || sd->vender_id || sd->state.buyingstore || sd->state.trading || sd->state.storage_flag)
return -1;
if (storage_storageopen(sd) == 1)
@ -1177,7 +1177,7 @@ ACMD_FUNC(guildstorage)
return -1;
}
if (sd->npc_id || sd->vender_id || sd->state.trading)
if (sd->npc_id || sd->vender_id || sd->state.buyingstore || sd->state.trading)
return -1;
if (sd->state.storage_flag == 1) {

View File

@ -4007,6 +4007,7 @@ static const struct _battle_data {
{ "client_reshuffle_dice", &battle_config.client_reshuffle_dice, 0, 0, 1, },
{ "client_sort_storage", &battle_config.client_sort_storage, 0, 0, 1, },
{ "gm_check_minlevel", &battle_config.gm_check_minlevel, 60, 0, 100, },
{ "feature_buying_store", &battle_config.feature_buying_store, 1, 0, 1, },
// BattleGround Settings
{ "bg_update_interval", &battle_config.bg_update_interval, 1000, 100, INT_MAX, },
{ "bg_short_attack_damage_rate", &battle_config.bg_short_damage_rate, 80, 0, INT_MAX, },

View File

@ -481,6 +481,7 @@ extern struct Battle_Config
int client_reshuffle_dice; // Reshuffle /dice
int client_sort_storage;
int gm_check_minlevel; // min GM level for /check
int feature_buying_store;
// [BattleGround Settings]
int bg_update_interval;

359
src/map/buyingstore.c Normal file
View File

@ -0,0 +1,359 @@
// Copyright (c) Athena Dev Teams - Licensed under GNU GPL
// For more information, see LICENCE in the main folder
#include "../common/cbasetypes.h"
#include "../common/db.h" // ARR_FIND
#include "../common/showmsg.h" // ShowWarning
#include "../common/socket.h" // RBUF*
#include "../common/strlib.h" // safestrncpy
#include "atcommand.h" // msg_txt
#include "battle.h" // battle_config.*
#include "buyingstore.h" // struct s_buyingstore
#include "clif.h" // clif_buyingstore_*
#include "log.h" // log_pick_pc, log_zeny
#include "pc.h" // struct map_session_data
/// constants (client-side restrictions)
#define BUYINGSTORE_MAX_PRICE 99990000
#define BUYINGSTORE_MAX_AMOUNT 9999
/// failure constants for clif functions
enum e_buyingstore_failure
{
BUYINGSTORE_CREATE = 1, // "Failed to open buying store."
BUYINGSTORE_CREATE_OVERWEIGHT = 2, // "Total amount of then possessed items exceeds the weight limit by %d. Please re-enter."
BUYINGSTORE_TRADE_BUYER_ZENY = 3, // "All items within the buy limit were purchased."
BUYINGSTORE_TRADE_BUYER_NO_ITEMS = 4, // "All items were purchased."
BUYINGSTORE_TRADE_SELLER_FAILED = 5, // "The deal has failed."
BUYINGSTORE_TRADE_SELLER_COUNT = 6, // "The trade failed, because the entered amount of item %s is higher, than the buyer is willing to buy."
BUYINGSTORE_TRADE_SELLER_ZENY = 7, // "The trade failed, because the buyer is lacking required balance."
BUYINGSTORE_CREATE_NO_INFO = 8, // "No sale (purchase) information available."
};
static unsigned int buyingstore_nextid = 0;
/// Returns unique buying store id
static unsigned int buyingstore_getuid(void)
{
return buyingstore_nextid++;
}
bool buyingstore_setup(struct map_session_data* sd, unsigned char slots)
{
if( !battle_config.feature_buying_store || sd->vender_id || sd->state.buyingstore || sd->state.trading || slots == 0 )
{
return false;
}
if( slots > MAX_BUYINGSTORE_SLOTS )
{
ShowWarning("buyingstore_setup: Requested %d slots, but server supports only %d slots.\n", (int)slots, MAX_BUYINGSTORE_SLOTS);
slots = MAX_BUYINGSTORE_SLOTS;
}
sd->buyingstore.slots = slots;
clif_buyingstore_open(sd);
return true;
}
void buyingstore_create(struct map_session_data* sd, int zenylimit, unsigned char result, const char* storename, const uint8* itemlist, unsigned int count)
{
unsigned int i, weight, listidx;
struct item_data* id;
if( !result || count == 0 )
{// canceled, or no items
return;
}
if( !battle_config.feature_buying_store || pc_istrading(sd) || sd->buyingstore.slots == 0 || count > sd->buyingstore.slots || zenylimit <= 0 || zenylimit > sd->status.zeny || !storename[0] )
{// disabled or invalid input
sd->buyingstore.slots = 0;
clif_buyingstore_open_failed(sd, BUYINGSTORE_CREATE, 0);
return;
}
if( !pc_can_give_items(pc_isGM(sd)) )
{// custom: GM is not allowed to buy (give zeny)
sd->buyingstore.slots = 0;
clif_displaymessage(sd->fd, msg_txt(246));
clif_buyingstore_open_failed(sd, BUYINGSTORE_CREATE, 0);
return;
}
weight = sd->weight;
// check item list
for( i = 0; i < count; i++ )
{// itemlist: <name id>.W <amount>.W <price>.L
unsigned short nameid, amount;
int price, idx;
nameid = RBUFW(itemlist,i*8+0);
amount = RBUFW(itemlist,i*8+2);
price = RBUFL(itemlist,i*8+4);
if( ( id = itemdb_exists(nameid) ) == NULL || amount == 0 )
{// invalid input
break;
}
if( price <= 0 || price > BUYINGSTORE_MAX_PRICE )
{// invalid price: unlike vending, items cannot be bought at 0 Zeny
break;
}
if( !id->flag.buyingstore || !itemdb_cantrade_sub(id, pc_isGM(sd), pc_isGM(sd)) || ( idx = pc_search_inventory(sd, nameid) ) == -1 )
{// restrictions: allowed, no character-bound items and at least one must be owned
break;
}
if( sd->status.inventory[idx].amount+amount > BUYINGSTORE_MAX_AMOUNT )
{// too many items of same kind
break;
}
if( i )
{// duplicate check. as the client does this too, only malicious intent should be caught here
ARR_FIND( 0, i, listidx, sd->buyingstore.items[i].nameid == nameid );
if( listidx != i )
{// duplicate
ShowWarning("buyingstore_create: Found duplicate item on buying list (nameid=%hu, amount=%hu, account_id=%d, char_id=%d).\n", nameid, amount, sd->status.account_id, sd->status.char_id);
break;
}
}
weight+= id->weight*amount;
sd->buyingstore.items[i].nameid = nameid;
sd->buyingstore.items[i].amount = amount;
sd->buyingstore.items[i].price = price;
}
if( i != count )
{// invalid item/amount/price
sd->buyingstore.slots = 0;
clif_buyingstore_open_failed(sd, BUYINGSTORE_CREATE, 0);
return;
}
if( (sd->max_weight*90)/100 < weight )
{// not able to carry all wanted items without getting overweight (90%)
sd->buyingstore.slots = 0;
clif_buyingstore_open_failed(sd, BUYINGSTORE_CREATE_OVERWEIGHT, weight);
return;
}
// success
sd->state.buyingstore = true;
sd->buyer_id = buyingstore_getuid();
sd->buyingstore.zenylimit = zenylimit;
sd->buyingstore.slots = i; // store actual amount of items
safestrncpy(sd->message, storename, sizeof(sd->message));
clif_buyingstore_myitemlist(sd);
clif_buyingstore_entry(sd);
}
void buyingstore_close(struct map_session_data* sd)
{
if( sd->state.buyingstore )
{
// invalidate data
sd->state.buyingstore = false;
memset(&sd->buyingstore, 0, sizeof(sd->buyingstore));
// notify other players
clif_buyingstore_disappear_entry(sd);
}
}
void buyingstore_open(struct map_session_data* sd, int account_id)
{
struct map_session_data* pl_sd;
if( !battle_config.feature_buying_store || pc_istrading(sd) )
{// not allowed to sell
return;
}
if( !pc_can_give_items(pc_isGM(sd)) )
{// custom: GM is not allowed to sell
clif_displaymessage(sd->fd, msg_txt(246));
return;
}
if( ( pl_sd = map_id2sd(account_id) ) == NULL || !pl_sd->state.buyingstore )
{// not online or not buying
return;
}
// success
clif_buyingstore_itemlist(sd, pl_sd);
}
void buyingstore_trade(struct map_session_data* sd, int account_id, unsigned int buyer_id, const uint8* itemlist, unsigned int count)
{
short blankslots[MAX_SLOTS]; // used when checking whether or not an item's card slots are blank
int zeny = 0;
unsigned int i, weight, listidx, k;
struct map_session_data* pl_sd;
if( count == 0 )
{// nothing to do
return;
}
if( !battle_config.feature_buying_store || pc_istrading(sd) )
{// not allowed to sell
clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_FAILED, 0);
return;
}
if( !pc_can_give_items(pc_isGM(sd)) )
{// custom: GM is not allowed to sell
clif_displaymessage(sd->fd, msg_txt(246));
clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_FAILED, 0);
return;
}
if( ( pl_sd = map_id2sd(account_id) ) == NULL || !pl_sd->state.buyingstore || pl_sd->buyer_id != buyer_id )
{// not online, not buying or not same store
clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_FAILED, 0);
return;
}
if( pl_sd->status.zeny < pl_sd->buyingstore.zenylimit )
{// buyer lost zeny in the mean time? fix the limit
pl_sd->buyingstore.zenylimit = pl_sd->status.zeny;
}
weight = pl_sd->weight;
memset(blankslots, 0, sizeof(blankslots));
// check item list
for( i = 0; i < count; i++ )
{// itemlist: <index>.W <name id>.W <amount>.W
unsigned short nameid, amount;
int index;
index = RBUFW(itemlist,i*6+0)-2;
nameid = RBUFW(itemlist,i*6+2);
amount = RBUFW(itemlist,i*6+4);
if( i )
{// duplicate check. as the client does this too, only malicious intent should be caught here
ARR_FIND( 0, i, k, RBUFW(itemlist,k*6+0)-2 == index );
if( k != i )
{// duplicate
ShowWarning("buyingstore_trade: Found duplicate item on selling list (prevnameid=%hu, prevamount=%hu, nameid=%hu, amount=%hu, account_id=%d, char_id=%d).\n",
RBUFW(itemlist,k*6+2), RBUFW(itemlist,k*6+4), nameid, amount, sd->status.account_id, sd->status.char_id);
clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_FAILED, nameid);
return;
}
}
if( index < 0 || index >= ARRAYLENGTH(sd->status.inventory) || sd->inventory_data[index] == NULL || sd->status.inventory[index].nameid != nameid || sd->status.inventory[index].amount < amount )
{// invalid input
clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_FAILED, nameid);
return;
}
if( sd->status.inventory[index].expire_time || !itemdb_cantrade(&sd->status.inventory[index], pc_isGM(sd), pc_isGM(pl_sd)) || memcmp(sd->status.inventory[index].card, blankslots, sizeof(blankslots)) )
{// non-tradable item
clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_FAILED, nameid);
return;
}
ARR_FIND( 0, pl_sd->buyingstore.slots, listidx, pl_sd->buyingstore.items[listidx].nameid == nameid );
if( listidx == pl_sd->buyingstore.slots || pl_sd->buyingstore.items[listidx].amount == 0 )
{// there is no such item or the buyer has already bought all of them
clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_FAILED, nameid);
return;
}
if( pl_sd->buyingstore.items[listidx].amount < amount )
{// buyer does not need that much of the item
clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_COUNT, nameid);
return;
}
if( pc_checkadditem(pl_sd, nameid, amount) == ADDITEM_OVERAMOUNT )
{// buyer does not have enough space for this item
clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_FAILED, nameid);
return;
}
if( amount*(unsigned int)sd->inventory_data[index]->weight > pl_sd->max_weight-weight )
{// normally this is not supposed to happen, as the total weight is
// checked upon creation, but the buyer could have gained items
clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_FAILED, nameid);
return;
}
weight+= amount*sd->inventory_data[index]->weight;
if( amount*pl_sd->buyingstore.items[listidx].price > pl_sd->buyingstore.zenylimit-zeny )
{// buyer does not have enough zeny
clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_ZENY, nameid);
return;
}
zeny+= amount*pl_sd->buyingstore.items[listidx].price;
}
// process item list
for( i = 0; i < count; i++ )
{// itemlist: <index>.W <name id>.W <amount>.W
unsigned short nameid, amount;
int index;
index = RBUFW(itemlist,i*6+0)-2;
nameid = RBUFW(itemlist,i*6+2);
amount = RBUFW(itemlist,i*6+4);
ARR_FIND( 0, pl_sd->buyingstore.slots, listidx, pl_sd->buyingstore.items[listidx].nameid == nameid );
zeny = amount*pl_sd->buyingstore.items[listidx].price;
// log
if( log_config.enable_logs&LOG_BUYING_STORE )
{
log_pick_pc(sd, "B", nameid, -((int)amount), &sd->status.inventory[index]);
log_pick_pc(pl_sd, "B", nameid, amount, &sd->status.inventory[index]);
}
if( log_config.zeny )
log_zeny(sd, "B", pl_sd, zeny);
// move item
pc_additem(pl_sd, &sd->status.inventory[index], amount);
pc_delitem(sd, index, amount, 1, 0);
pl_sd->buyingstore.items[listidx].amount-= amount;
// pay up
pc_payzeny(pl_sd, zeny);
pc_getzeny(sd, zeny);
pl_sd->buyingstore.zenylimit-= zeny;
// notify clients
clif_buyingstore_delete_item(sd, index, amount, pl_sd->buyingstore.items[listidx].price);
clif_buyingstore_update_item(pl_sd, nameid, amount);
}
// check whether or not there is still something to buy
ARR_FIND( 0, pl_sd->buyingstore.slots, i, pl_sd->buyingstore.items[i].amount != 0 );
if( i == pl_sd->buyingstore.slots )
{// everything was bought
clif_buyingstore_trade_failed_buyer(pl_sd, BUYINGSTORE_TRADE_BUYER_NO_ITEMS);
buyingstore_close(pl_sd);
}
else if( pl_sd->buyingstore.zenylimit == 0 )
{// zeny limit reached
clif_buyingstore_trade_failed_buyer(pl_sd, BUYINGSTORE_TRADE_BUYER_ZENY);
buyingstore_close(pl_sd);
}
}

29
src/map/buyingstore.h Normal file
View File

@ -0,0 +1,29 @@
// Copyright (c) Athena Dev Teams - Licensed under GNU GPL
// For more information, see LICENCE in the main folder
#ifndef _BUYINGSTORE_H_
#define _BUYINGSTORE_H_
#define MAX_BUYINGSTORE_SLOTS 5
struct s_buyingstore_item
{
int price;
unsigned short amount;
unsigned short nameid;
};
struct s_buyingstore
{
struct s_buyingstore_item items[MAX_BUYINGSTORE_SLOTS];
int zenylimit;
unsigned char slots;
};
bool buyingstore_setup(struct map_session_data* sd, unsigned char slots);
void buyingstore_create(struct map_session_data* sd, int zenylimit, unsigned char result, const char* storename, const uint8* itemlist, unsigned int count);
void buyingstore_close(struct map_session_data* sd);
void buyingstore_open(struct map_session_data* sd, int account_id);
void buyingstore_trade(struct map_session_data* sd, int account_id, unsigned int buyer_id, const uint8* itemlist, unsigned int count);
#endif // _BUYINGSTORE_H_

View File

@ -69,6 +69,11 @@ int chat_createpcchat(struct map_session_data* sd, const char* title, const char
if( sd->chatID )
return 0; //Prevent people abusing the chat system by creating multiple chats, as pointed out by End of Exam. [Skotlex]
if( sd->vender_id || sd->state.buyingstore )
{// not chat, when you already have a store open
return 0;
}
if( map[sd->bl.m].flag.nochat )
{
clif_displaymessage(sd->fd, msg_txt(281));
@ -108,7 +113,7 @@ int chat_joinchat(struct map_session_data* sd, int chatid, const char* pass)
nullpo_ret(sd);
cd = (struct chat_data*)map_id2bl(chatid);
if( cd == NULL || cd->bl.type != BL_CHAT || cd->bl.m != sd->bl.m || sd->vender_id || sd->chatID || cd->users >= cd->limit )
if( cd == NULL || cd->bl.type != BL_CHAT || cd->bl.m != sd->bl.m || sd->vender_id || sd->state.buyingstore || sd->chatID || cd->users >= cd->limit )
{
clif_joinchatfail(sd,0);
return 0;

View File

@ -56,7 +56,10 @@ struct Clif_Config {
struct s_packet_db packet_db[MAX_PACKET_VER + 1][MAX_PACKET_DB + 1];
//Converts item type in case of pet eggs.
#define itemtype(a) (a == IT_PETEGG)?IT_WEAPON:a
inline int itemtype(int type)
{
return ( type == IT_PETEGG ) ? IT_WEAPON : type;
}
#define WBUFPOS(p,pos,x,y,dir) \
do { \
@ -3644,6 +3647,9 @@ static void clif_getareachar_pc(struct map_session_data* sd,struct map_session_d
if(dstsd->vender_id)
clif_showvendingboard(&dstsd->bl,dstsd->message,sd->fd);
if( dstsd->state.buyingstore )
clif_buyingstore_entry_single(sd, dstsd);
if(dstsd->spiritball > 0)
clif_spiritball_single(sd->fd, dstsd);
@ -4072,6 +4078,8 @@ int clif_outsight(struct block_list *bl,va_list ap)
}
if(sd->vender_id)
clif_closevendingboard(bl,tsd->fd);
if( sd->state.buyingstore )
clif_buyingstore_disappear_entry_single(tsd, sd);
break;
case BL_ITEM:
clif_clearflooritem((struct flooritem_data*)bl,tsd->fd);
@ -12851,7 +12859,7 @@ void clif_Auction_openwindow(struct map_session_data *sd)
{
int fd = sd->fd;
if( sd->state.storage_flag || sd->vender_id || sd->state.trading )
if( sd->state.storage_flag || sd->vender_id || sd->state.buyingstore || sd->state.trading )
return;
WFIFOHEAD(fd,12);
@ -14033,6 +14041,308 @@ void clif_showdigit(struct map_session_data* sd, unsigned char type, int value)
WFIFOSET(sd->fd, packet_len(0x1b1));
}
/// Buying Store System
///
/// Opens preparation window for buying store (ZC_OPEN_BUYING_STORE)
/// 0810 <slots>.B
void clif_buyingstore_open(struct map_session_data* sd)
{
int fd = sd->fd;
WFIFOHEAD(fd,packet_len(0x810));
WFIFOW(fd,0) = 0x810;
WFIFOB(fd,2) = sd->buyingstore.slots;
WFIFOSET(fd,packet_len(0x810));
}
/// Request to create a buying store (CZ_REQ_OPEN_BUYING_STORE)
/// 0811 <packet len>.W <limit zeny>.L <result>.B <store name>.80B { <name id>.W <amount>.W <price>.L }*
/// result:
/// 0 = cancel
/// 1 = open
static void clif_parse_ReqOpenBuyingStore(int fd, struct map_session_data* sd)
{
const unsigned int blocksize = 8;
uint8* itemlist;
char storename[MESSAGE_SIZE];
unsigned char result;
int zenylimit;
unsigned int count, packet_len;
struct s_packet_db* info = &packet_db[sd->packet_ver][RFIFOW(fd,0)];
packet_len = RFIFOW(fd,info->pos[0]);
// TODO: Make this check global for all variable length packets.
if( packet_len < 89 )
{// minimum packet length
ShowError("clif_parse_ReqOpenBuyingStore: Malformed packet (expected length=%u, length=%u, account_id=%d).\n", 89, packet_len, sd->bl.id);
return;
}
zenylimit = RFIFOL(fd,info->pos[1]);
result = RFIFOL(fd,info->pos[2]);
safestrncpy(storename, (const char*)RFIFOP(fd,info->pos[3]), sizeof(storename));
itemlist = RFIFOP(fd,info->pos[4]);
// so that buyingstore_create knows, how many elements it has access to
packet_len-= info->pos[4];
if( packet_len%blocksize )
{
ShowError("clif_parse_ReqOpenBuyingStore: Unexpected item list size %u (account_id=%d, block size=%u)\n", packet_len, sd->bl.id, blocksize);
return;
}
count = packet_len/blocksize;
buyingstore_create(sd, zenylimit, result, storename, itemlist, count);
}
/// Notification, that the requested buying store could not be created (ZC_FAILED_OPEN_BUYING_STORE_TO_BUYER)
/// 0812 <result>.W <total weight>.L
/// result:
/// 1 = "Failed to open buying store." (0x6cd, MSI_BUYINGSTORE_OPEN_FAILED)
/// 2 = "Total amount of then possessed items exceeds the weight limit by <weight/10-maxweight*90%>. Please re-enter." (0x6ce, MSI_BUYINGSTORE_OVERWEIGHT)
/// 8 = "No sale (purchase) information available." (0x705)
/// other = nothing
void clif_buyingstore_open_failed(struct map_session_data* sd, unsigned short result, unsigned int weight)
{
int fd = sd->fd;
WFIFOHEAD(fd,packet_len(0x812));
WFIFOW(fd,0) = 0x812;
WFIFOW(fd,2) = result;
WFIFOL(fd,4) = weight;
WFIFOSET(fd,packet_len(0x812));
}
/// Notification, that the requested buying store was created (ZC_MYITEMLIST_BUYING_STORE)
/// 0813 <packet len>.W <account id>.L <limit zeny>.L { <price>.L <count>.W <type>.B <name id>.W }*
void clif_buyingstore_myitemlist(struct map_session_data* sd)
{
int fd = sd->fd;
unsigned int i;
WFIFOHEAD(fd,12+sd->buyingstore.slots*9);
WFIFOW(fd,0) = 0x813;
WFIFOW(fd,2) = 12+sd->buyingstore.slots*9;
WFIFOL(fd,4) = sd->bl.id;
WFIFOL(fd,8) = sd->buyingstore.zenylimit;
for( i = 0; i < sd->buyingstore.slots; i++ )
{
WFIFOL(fd,12+i*9) = sd->buyingstore.items[i].price;
WFIFOW(fd,16+i*9) = sd->buyingstore.items[i].amount;
WFIFOB(fd,18+i*9) = itemtype(itemdb_type(sd->buyingstore.items[i].nameid));
WFIFOW(fd,19+i*9) = sd->buyingstore.items[i].nameid;
}
WFIFOSET(fd,WFIFOW(fd,2));
}
/// Notifies clients in area of a buying store (ZC_BUYING_STORE_ENTRY)
/// 0814 <account id>.L <store name>.80B
void clif_buyingstore_entry(struct map_session_data* sd)
{
uint8 buf[86];
WBUFW(buf,0) = 0x814;
WBUFL(buf,2) = sd->bl.id;
memcpy(WBUFP(buf,6), sd->message, MESSAGE_SIZE);
clif_send(buf, packet_len(0x814), &sd->bl, AREA_WOS);
}
void clif_buyingstore_entry_single(struct map_session_data* sd, struct map_session_data* pl_sd)
{
int fd = sd->fd;
WFIFOHEAD(fd,packet_len(0x814));
WFIFOW(fd,0) = 0x814;
WFIFOL(fd,2) = pl_sd->bl.id;
memcpy(WFIFOP(fd,6), pl_sd->message, MESSAGE_SIZE);
WFIFOSET(fd,packet_len(0x814));
}
/// Request to close own buying store (CZ_REQ_CLOSE_BUYING_STORE)
/// 0815
static void clif_parse_ReqCloseBuyingStore(int fd, struct map_session_data* sd)
{
buyingstore_close(sd);
}
/// Notifies clients in area that a buying store was closed (ZC_DISAPPEAR_BUYING_STORE_ENTRY)
/// 0816 <account id>.L
void clif_buyingstore_disappear_entry(struct map_session_data* sd)
{
uint8 buf[6];
WBUFW(buf,0) = 0x816;
WBUFL(buf,2) = sd->bl.id;
clif_send(buf, packet_len(0x816), &sd->bl, AREA_WOS);
}
void clif_buyingstore_disappear_entry_single(struct map_session_data* sd, struct map_session_data* pl_sd)
{
int fd = sd->fd;
WFIFOHEAD(fd,packet_len(0x816));
WFIFOW(fd,0) = 0x816;
WFIFOL(fd,2) = pl_sd->bl.id;
WFIFOSET(fd,packet_len(0x816));
}
/// Request to open someone else's buying store (CZ_REQ_CLICK_TO_BUYING_STORE)
/// 0817 <account id>.L
static void clif_parse_ReqClickBuyingStore(int fd, struct map_session_data* sd)
{
int account_id;
account_id = RFIFOL(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0]);
buyingstore_open(sd, account_id);
}
/// Sends buying store item list (ZC_ACK_ITEMLIST_BUYING_STORE)
/// 0818 <packet len>.W <account id>.L <store id>.L <limit zeny>.L { <price>.L <amount>.W <type>.B <name id>.W }*
void clif_buyingstore_itemlist(struct map_session_data* sd, struct map_session_data* pl_sd)
{
int fd = sd->fd;
unsigned int i;
WFIFOHEAD(fd,16+pl_sd->buyingstore.slots*9);
WFIFOW(fd,0) = 0x818;
WFIFOW(fd,2) = 16+pl_sd->buyingstore.slots*9;
WFIFOL(fd,4) = pl_sd->bl.id;
WFIFOL(fd,8) = pl_sd->buyer_id;
WFIFOL(fd,12) = pl_sd->buyingstore.zenylimit;
for( i = 0; i < pl_sd->buyingstore.slots; i++ )
{
WFIFOL(fd,16+i*9) = pl_sd->buyingstore.items[i].price;
WFIFOW(fd,20+i*9) = pl_sd->buyingstore.items[i].amount; // TODO: Figure out, if no longer needed items (amount == 0) are listed on official.
WFIFOB(fd,22+i*9) = itemtype(itemdb_type(pl_sd->buyingstore.items[i].nameid));
WFIFOW(fd,23+i*9) = pl_sd->buyingstore.items[i].nameid;
}
WFIFOSET(fd,WFIFOW(fd,2));
}
/// Request to sell items to a buying store (CZ_REQ_TRADE_BUYING_STORE)
/// 0819 <packet len>.W <account id>.L <store id>.L { <index>.W <name id>.W <amount>.W }*
static void clif_parse_ReqTradeBuyingStore(int fd, struct map_session_data* sd)
{
const unsigned int blocksize = 6;
uint8* itemlist;
int account_id;
unsigned int count, packet_len, buyer_id;
struct s_packet_db* info = &packet_db[sd->packet_ver][RFIFOW(fd,0)];
packet_len = RFIFOW(fd,info->pos[0]);
if( packet_len < 12 )
{// minimum packet length
ShowError("clif_parse_ReqTradeBuyingStore: Malformed packet (expected length=%u, length=%u, account_id=%d).\n", 12, packet_len, sd->bl.id);
return;
}
account_id = RFIFOL(fd,info->pos[1]);
buyer_id = RFIFOL(fd,info->pos[2]);
itemlist = RFIFOP(fd,info->pos[3]);
// so that buyingstore_trade knows, how many elements it has access to
packet_len-= info->pos[3];
if( packet_len%blocksize )
{
ShowError("clif_parse_ReqTradeBuyingStore: Unexpected item list size %u (account_id=%d, buyer_id=%d, block size=%u)\n", packet_len, sd->bl.id, account_id, blocksize);
return;
}
count = packet_len/blocksize;
buyingstore_trade(sd, account_id, buyer_id, itemlist, count);
}
/// Notifies the buyer, that the buying store has been closed due to a post-trade condition (ZC_FAILED_TRADE_BUYING_STORE_TO_BUYER)
/// 081a <result>.W
/// result:
/// 3 = "All items within the buy limit were purchased." (0x6cf, MSI_BUYINGSTORE_TRADE_OVERLIMITZENY)
/// 4 = "All items were purchased." (0x6d0, MSI_BUYINGSTORE_TRADE_BUYCOMPLETE)
/// other = nothing
void clif_buyingstore_trade_failed_buyer(struct map_session_data* sd, short result)
{
int fd = sd->fd;
WFIFOHEAD(fd,packet_len(0x81a));
WFIFOW(fd,0) = 0x81a;
WFIFOW(fd,2) = result;
WFIFOSET(fd,packet_len(0x81a));
}
/// Updates the zeny limit and an item in the buying store item list (ZC_UPDATE_ITEM_FROM_BUYING_STORE)
/// 081b <name id>.W <amount>.W <limit zeny>.L
void clif_buyingstore_update_item(struct map_session_data* sd, unsigned short nameid, unsigned short amount)
{
int fd = sd->fd;
WFIFOHEAD(fd,packet_len(0x81b));
WFIFOW(fd,0) = 0x81b;
WFIFOW(fd,2) = nameid;
WFIFOW(fd,4) = amount; // amount of nameid received
WFIFOW(fd,6) = sd->buyingstore.zenylimit;
WFIFOSET(fd,packet_len(0x81b));
}
/// Deletes item from inventory, that was sold to a buying store (ZC_ITEM_DELETE_BUYING_STORE)
/// 081c <index>.W <amount>.W <price>.L
/// message:
/// "%s (%d) were sold at %dz." (0x6d2, MSI_BUYINGSTORE_TRADE_SELLCOMPLETE)
///
/// @note This function has to be called _instead_ of clif_delitem/clif_dropitem.
void clif_buyingstore_delete_item(struct map_session_data* sd, short index, unsigned short amount, int price)
{
int fd = sd->fd;
WFIFOHEAD(fd,packet_len(0x81c));
WFIFOW(fd,0) = 0x81c;
WFIFOW(fd,2) = index+2;
WFIFOW(fd,4) = amount;
WFIFOL(fd,6) = price; // price per item, client calculates total Zeny by itself
WFIFOSET(fd,packet_len(0x81c));
}
/// Notifies the seller, that a buying store trade failed (ZC_FAILED_TRADE_BUYING_STORE_TO_SELLER)
/// 0824 <result>.W <name id>.W
/// result:
/// 5 = "The deal has failed." (0x39, MSI_DEAL_FAIL)
/// 6 = "The trade failed, because the entered amount of item %s is higher, than the buyer is willing to buy." (0x6d3, MSI_BUYINGSTORE_TRADE_OVERCOUNT)
/// 7 = "The trade failed, because the buyer is lacking required balance." (0x6d1, MSI_BUYINGSTORE_TRADE_LACKBUYERZENY)
/// other = nothing
void clif_buyingstore_trade_failed_seller(struct map_session_data* sd, short result, unsigned short nameid)
{
int fd = sd->fd;
WFIFOHEAD(fd,packet_len(0x824));
WFIFOW(fd,0) = 0x824;
WFIFOW(fd,2) = result;
WFIFOW(fd,4) = nameid;
WFIFOSET(fd,packet_len(0x824));
}
/*==========================================
* ƒpƒPƒbƒgƒfƒoƒbƒO
*------------------------------------------*/
@ -14435,8 +14745,8 @@ static int packetdb_readdb(void)
#else // for Party booking ( PACKETVER >= 20091229 )
-1, -1, 18, 4, 8, 6, 2, 4, 14, 50, 18, 6, 2, 3, 14, 20,
#endif
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
3, -1, 8, -1, 86, 2, 6, 6, -1, -1, 4, 10, 10, 0, 0, 0,
0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
};
struct {
@ -14628,6 +14938,11 @@ static int packetdb_readdb(void)
{clif_parse_PartyBookingDeleteReq,"bookingdelreq"},
#endif
{clif_parse_PVPInfo,"pvpinfo"},
// Buying Store
{clif_parse_ReqOpenBuyingStore,"reqopenbuyingstore"},
{clif_parse_ReqCloseBuyingStore,"reqclosebuyingstore"},
{clif_parse_ReqClickBuyingStore,"reqclickbuyingstore"},
{clif_parse_ReqTradeBuyingStore,"reqtradebuyingstore"},
{NULL,NULL}
};

View File

@ -605,4 +605,18 @@ void clif_PartyBookingInsertNotify(struct map_session_data* sd, struct party_boo
void clif_showdigit(struct map_session_data* sd, unsigned char type, int value);
/// Buying Store System
void clif_buyingstore_open(struct map_session_data* sd);
void clif_buyingstore_open_failed(struct map_session_data* sd, unsigned short result, unsigned int weight);
void clif_buyingstore_myitemlist(struct map_session_data* sd);
void clif_buyingstore_entry(struct map_session_data* sd);
void clif_buyingstore_entry_single(struct map_session_data* sd, struct map_session_data* pl_sd);
void clif_buyingstore_disappear_entry(struct map_session_data* sd);
void clif_buyingstore_disappear_entry_single(struct map_session_data* sd, struct map_session_data* pl_sd);
void clif_buyingstore_itemlist(struct map_session_data* sd, struct map_session_data* pl_sd);
void clif_buyingstore_trade_failed_buyer(struct map_session_data* sd, short result);
void clif_buyingstore_update_item(struct map_session_data* sd, unsigned short nameid, unsigned short amount);
void clif_buyingstore_delete_item(struct map_session_data* sd, short index, unsigned short amount, int price);
void clif_buyingstore_trade_failed_seller(struct map_session_data* sd, short result, unsigned short nameid);
#endif /* _CLIF_H_ */

View File

@ -663,6 +663,33 @@ static bool itemdb_read_itemdelay(char* str[], int columns, int current)
return true;
}
/// Reads items allowed to be sold in buying stores
static bool itemdb_read_buyingstore(char* fields[], int columns, int current)
{// <nameid>
int nameid;
struct item_data* id;
nameid = atoi(fields[0]);
if( ( id = itemdb_exists(nameid) ) == NULL )
{
ShowWarning("itemdb_read_buyingstore: Invalid item id %d.\n", nameid);
return false;
}
if( !itemdb_isstackable2(id) )
{
ShowWarning("itemdb_read_buyingstore: Non-stackable item id %d cannot be enabled for buying store.\n", nameid);
return false;
}
id->flag.buyingstore = true;
return true;
}
/*======================================
* Applies gender restrictions according to settings. [Skotlex]
*======================================*/
@ -979,6 +1006,7 @@ static void itemdb_read(void)
sv_readdb(db_path, "item_noequip.txt", ',', 2, 2, -1, &itemdb_read_noequip);
sv_readdb(db_path, "item_trade.txt", ',', 3, 3, -1, &itemdb_read_itemtrade);
sv_readdb(db_path, "item_delay.txt", ',', 2, 2, MAX_ITEMDELAYS, &itemdb_read_itemdelay);
sv_readdb(db_path, "item_buyingstore.txt", ',', 1, 1, -1, &itemdb_read_buyingstore);
}
/*==========================================

View File

@ -76,6 +76,7 @@ struct item_data {
unsigned delay_consume : 1; // Signifies items that are not consumed immediately upon double-click [Skotlex]
unsigned trade_restriction : 7; //Item restrictions mask [Skotlex]
unsigned autoequip: 1;
unsigned buyingstore : 1;
} flag;
short gm_lv_trade_override; //GM-level to override trade_restriction
};

View File

@ -36,9 +36,10 @@ typedef enum log_what {
LOG_USED_ITEMS = 0x0100, // used by player
LOG_MVP_PRIZE = 0x0200,
LOG_COMMAND_ITEMS = 0x0400, // created/deleted through @/# commands
LOG_STORAGE_ITEMS = 0x0800, // placed/retrieved from storage
LOG_GSTORAGE_ITEMS = 0x1000, // placed/retrieved from guild storage
LOG_MAILS = 0x2000 // mail system transactions
LOG_STORAGE_ITEMS = 0x0800, // placed/retrieved from storage
LOG_GSTORAGE_ITEMS = 0x1000, // placed/retrieved from guild storage
LOG_MAILS = 0x2000, // mail system transactions
LOG_BUYING_STORE = 0x4000, // buying store transactions
} log_what;
extern struct Log_Config {

View File

@ -162,7 +162,7 @@ int mail_openmail(struct map_session_data *sd)
{
nullpo_ret(sd);
if( sd->state.storage_flag || sd->vender_id || sd->state.trading )
if( sd->state.storage_flag || sd->vender_id || sd->state.buyingstore || sd->state.trading )
return 0;
clif_Mail_window(sd->fd, 0);

View File

@ -7,6 +7,7 @@
#include "../common/mmo.h" // JOB_*, MAX_FAME_LIST, struct fame_list, struct mmo_charstatus
#include "../common/timer.h" // INVALID_TIMER
#include "battle.h" // battle_config
#include "buyingstore.h" // struct s_buyingstore
#include "itemdb.h" // MAX_ITEMGROUP
#include "map.h" // RC_MAX
#include "pc.h" // struct map_session_data
@ -128,6 +129,7 @@ struct map_session_data {
unsigned doridori : 1;
unsigned ignoreAll : 1;
unsigned debug_remove_map : 1; // temporary state to track double remove_map's [FlavioJS]
unsigned buyingstore : 1;
unsigned short autoloot;
unsigned short autolootid; // [Zephyrus]
unsigned noks : 3; // [Zeph Kill Steal Protection]
@ -356,6 +358,9 @@ struct map_session_data {
char message[MESSAGE_SIZE];
struct s_vending vending[MAX_VENDING];
unsigned int buyer_id; // uid of open buying store
struct s_buyingstore buyingstore;
struct pet_data *pd;
struct homun_data *hd; // [blackhole89]
struct mercenary_data *md;
@ -413,8 +418,8 @@ struct map_session_data {
const char* debug_func;
};
//Update this max as necessary. 54 is the value needed for Super Baby currently
#define MAX_SKILL_TREE 54
//Update this max as necessary. 55 is the value needed for Super Baby currently
#define MAX_SKILL_TREE 55
//Total number of classes (for data storage)
#define CLASS_COUNT (JOB_MAX - JOB_NOVICE_HIGH + JOB_MAX_BASIC)
@ -515,9 +520,9 @@ extern int duel_count;
#define pc_setsit(sd) ( (sd)->state.dead_sit = (sd)->vd.dead_sit = 2 )
#define pc_isdead(sd) ( (sd)->state.dead_sit == 1 )
#define pc_issit(sd) ( (sd)->vd.dead_sit == 2 )
#define pc_isidle(sd) ( (sd)->chatID || (sd)->vender_id || DIFF_TICK(last_tick, (sd)->idletime) >= battle_config.idle_no_share )
#define pc_istrading(sd) ( (sd)->npc_id || (sd)->vender_id || (sd)->state.trading )
#define pc_cant_act(sd) ( (sd)->npc_id || (sd)->vender_id || (sd)->chatID || (sd)->sc.opt1 || (sd)->state.trading || (sd)->state.storage_flag )
#define pc_isidle(sd) ( (sd)->chatID || (sd)->vender_id || (sd)->state.buyingstore || DIFF_TICK(last_tick, (sd)->idletime) >= battle_config.idle_no_share )
#define pc_istrading(sd) ( (sd)->npc_id || (sd)->vender_id || (sd)->state.buyingstore || (sd)->state.trading )
#define pc_cant_act(sd) ( (sd)->npc_id || (sd)->vender_id || (sd)->state.buyingstore || (sd)->chatID || (sd)->sc.opt1 || (sd)->state.trading || (sd)->state.storage_flag )
#define pc_setdir(sd,b,h) ( (sd)->ud.dir = (b) ,(sd)->head_dir = (h) )
#define pc_setchatid(sd,n) ( (sd)->chatID = n )
#define pc_ishiding(sd) ( (sd)->sc.option&(OPTION_HIDE|OPTION_CLOAK|OPTION_CHASEWALK) )

View File

@ -14807,6 +14807,23 @@ BUILDIN_FUNC(pushpc)
return 0;
}
/// Invokes buying store preparation window
/// buyingstore <slots>;
BUILDIN_FUNC(buyingstore)
{
struct map_session_data* sd;
if( ( sd = script_rid2sd(st) ) == NULL )
{
return 0;
}
buyingstore_setup(sd, script_getnum(st,2));
return 0;
}
// declarations that were supposed to be exported from npc_chat.c
#ifdef PCRE_SUPPORT
BUILDIN_FUNC(defpattern);
@ -15168,6 +15185,7 @@ struct script_function buildin_func[] = {
BUILDIN_DEF(areamobuseskill,"siiiiviiiii"),
BUILDIN_DEF(progressbar,"si"),
BUILDIN_DEF(pushpc,"ii"),
BUILDIN_DEF(buyingstore,"i"),
// WoE SE
BUILDIN_DEF(agitstart2,""),
BUILDIN_DEF(agitend2,""),

View File

@ -5692,6 +5692,12 @@ int skill_castend_nodamage_id (struct block_list *src, struct block_list *bl, in
case ALL_WEWISH:
clif_skill_nodamage(src,bl,skillid,skilllv,1);
break;
case ALL_BUYING_STORE:
if( sd )
{// players only, skill allows 5 buying slots
clif_skill_nodamage(src, bl, skillid, skilllv, buyingstore_setup(sd, MAX_BUYINGSTORE_SLOTS));
}
break;
default:
ShowWarning("skill_castend_nodamage_id: Unknown skill used:%d\n",skillid);
clif_skill_nodamage(src,bl,skillid,skilllv,1);

View File

@ -138,8 +138,8 @@ void trade_tradeack(struct map_session_data *sd, int type)
}
//Check if you can start trade.
if (sd->npc_id || sd->vender_id || sd->state.storage_flag ||
tsd->npc_id || tsd->vender_id || tsd->state.storage_flag)
if (sd->npc_id || sd->vender_id || sd->state.buyingstore || sd->state.storage_flag ||
tsd->npc_id || tsd->vender_id || tsd->state.buyingstore || tsd->state.storage_flag)
{ //Fail
clif_tradestart(sd, 2);
clif_tradestart(tsd, 2);

View File

@ -803,6 +803,7 @@ int unit_can_move(struct block_list *bl)
if (sd && (
pc_issit(sd) ||
sd->vender_id ||
sd->state.buyingstore ||
sd->state.blockedmove
))
return 0; //Can't move
@ -1872,6 +1873,7 @@ int unit_remove_map_(struct block_list *bl, clr_type clrtype, const char* file,
trade_tradecancel(sd);
if(sd->vender_id)
vending_closevending(sd);
buyingstore_close(sd);
if(sd->state.storage_flag == 1)
storage_storage_quit(sd,0);
else if (sd->state.storage_flag == 2)

View File

@ -146,6 +146,7 @@
<ClInclude Include="..\src\map\atcommand.h" />
<ClInclude Include="..\src\map\battle.h" />
<ClInclude Include="..\src\map\battleground.h" />
<ClInclude Include="..\src\map\buyingstore.h" />
<ClInclude Include="..\src\map\chat.h" />
<ClInclude Include="..\src\map\chrif.h" />
<ClInclude Include="..\src\map\clif.h" />
@ -195,6 +196,7 @@
<ClCompile Include="..\src\map\atcommand.c" />
<ClCompile Include="..\src\map\battle.c" />
<ClCompile Include="..\src\map\battleground.c" />
<ClCompile Include="..\src\map\buyingstore.c" />
<ClCompile Include="..\src\map\chat.c" />
<ClCompile Include="..\src\map\chrif.c" />
<ClCompile Include="..\src\map\clif.c" />

View File

@ -125,6 +125,7 @@
<ClCompile Include="..\src\map\atcommand.c" />
<ClCompile Include="..\src\map\battle.c" />
<ClCompile Include="..\src\map\battleground.c" />
<ClCompile Include="..\src\map\buyingstore.c" />
<ClCompile Include="..\src\map\chat.c" />
<ClCompile Include="..\src\map\chrif.c" />
<ClCompile Include="..\src\map\clif.c" />
@ -174,6 +175,7 @@
<ClInclude Include="..\src\map\atcommand.h" />
<ClInclude Include="..\src\map\battle.h" />
<ClInclude Include="..\src\map\battleground.h" />
<ClInclude Include="..\src\map\buyingstore.h" />
<ClInclude Include="..\src\map\chat.h" />
<ClInclude Include="..\src\map\chrif.h" />
<ClInclude Include="..\src\map\clif.h" />

View File

@ -231,6 +231,14 @@ SOURCE=..\src\map\battleground.h
# End Source File
# Begin Source File
SOURCE=..\src\map\buyingstore.c
# End Source File
# Begin Source File
SOURCE=..\src\map\buyingstore.h
# End Source File
# Begin Source File
SOURCE=..\src\map\chat.c
# End Source File
# Begin Source File

View File

@ -211,6 +211,10 @@ SOURCE=..\src\map\battleground.c
# End Source File
# Begin Source File
SOURCE=..\src\map\buyingstore.c
# End Source File
# Begin Source File
SOURCE=..\src\map\chat.c
# End Source File
# Begin Source File
@ -335,6 +339,10 @@ SOURCE=..\src\map\battleground.h
# End Source File
# Begin Source File
SOURCE=..\src\map\buyingstore.h
# End Source File
# Begin Source File
SOURCE=..\src\map\chat.h
# End Source File
# Begin Source File

View File

@ -166,6 +166,12 @@
<File
RelativePath="..\src\map\battleground.h">
</File>
<File
RelativePath="..\src\map\buyingstore.c">
</File>
<File
RelativePath="..\src\map\buyingstore.h">
</File>
<File
RelativePath="..\src\map\chat.c">
</File>

View File

@ -166,6 +166,12 @@
<File
RelativePath="..\src\map\battleground.h">
</File>
<File
RelativePath="..\src\map\buyingstore.c">
</File>
<File
RelativePath="..\src\map\buyingstore.h">
</File>
<File
RelativePath="..\src\map\chat.c">
</File>

View File

@ -374,6 +374,13 @@
<File
RelativePath="..\src\map\battleground.h"
>
<File
RelativePath="..\src\map\buyingstore.c"
>
</File>
<File
RelativePath="..\src\map\buyingstore.h"
>
</File>
<File
RelativePath="..\src\map\chat.c"

View File

@ -226,6 +226,14 @@
RelativePath="..\src\map\battleground.h"
>
</File>
<File
RelativePath="..\src\map\buyingstore.c"
>
</File>
<File
RelativePath="..\src\map\buyingstore.h"
>
</File>
<File
RelativePath="..\src\map\chat.c"
>

View File

@ -374,6 +374,14 @@
RelativePath="..\src\map\battleground.c"
>
</File>
<File
RelativePath="..\src\map\buyingstore.h"
>
</File>
<File
RelativePath="..\src\map\buyingstore.c"
>
</File>
<File
RelativePath="..\src\map\chat.c"
>

View File

@ -225,6 +225,14 @@
RelativePath="..\src\map\battleground.h"
>
</File>
<File
RelativePath="..\src\map\buyingstore.c"
>
</File>
<File
RelativePath="..\src\map\buyingstore.h"
>
</File>
<File
RelativePath="..\src\map\chat.c"
>