forked from CleverRaven/Cataclysm-DDA
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathnpctrade.cpp
359 lines (321 loc) · 13.1 KB
/
npctrade.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
#include "npctrade.h"
#include <algorithm>
#include <iosfwd>
#include <iterator>
#include <list>
#include <ostream>
#include <string>
#include <vector>
#include "avatar.h"
#include "character.h"
#include "debug.h"
#include "faction.h"
#include "item.h"
#include "item_category.h" // IWYU pragma: keep
#include "item_location.h"
#include "item_pocket.h"
#include "npc.h"
#include "npctrade_utils.h"
#include "ret_val.h"
#include "skill.h"
#include "trade_ui.h"
#include "type_id.h"
#include "units.h"
static const flag_id json_flag_NO_UNWIELD( "NO_UNWIELD" );
static const skill_id skill_speech( "speech" );
std::list<item> npc_trading::transfer_items( trade_selector::select_t &stuff, Character &giver,
Character &receiver, std::list<item_location *> &from_map, bool use_escrow )
{
// escrow is used only when the npc is the destination. Item transfer to the npc is deferred.
std::list<item> escrow = std::list<item>();
for( trade_selector::entry_t &ip : stuff ) {
if( ip.first.get_item() == nullptr ) {
DebugLog( D_ERROR, D_NPC ) << "Null item being traded in npc_trading::transfer_items";
continue;
}
item gift = *ip.first.get_item();
npc const *npc = nullptr;
std::function<bool( item const *, int, int )> f_wants;
if( giver.is_npc() ) {
npc = giver.as_npc();
f_wants = [npc]( item const * it, int price, int market_price ) {
return npc->wants_to_sell( *it, price, market_price ).success();
};
} else if( receiver.is_npc() ) {
npc = receiver.as_npc();
f_wants = [npc]( item const * it, int price, int market_price ) {
return npc->wants_to_buy( *it, price, market_price ).success();
};
}
// spill contained, unwanted items
if( f_wants && gift.is_container() ) {
for( item *it : gift.get_contents().all_items_top() ) {
int const price =
trading_price( giver, receiver, { item_location{ giver, it }, 1 } );
int const market_price = it->price( true );
if( !f_wants( it, price, market_price ) ) {
giver.i_add_or_drop( *it, 1, ip.first.get_item() );
gift.remove_item( *it );
}
}
}
gift.set_owner( receiver );
// Items are moving to escrow.
if( use_escrow && ip.first->count_by_charges() ) {
gift.charges = ip.second;
escrow.emplace_back( gift );
} else if( use_escrow ) {
std::fill_n( std::back_inserter( escrow ), ip.second, gift );
// No escrow in use. Items moving from giver to receiver.
} else if( ip.first->count_by_charges() ) {
gift.charges = ip.second;
receiver.i_add( gift );
} else {
for( int i = 0; i < ip.second; i++ ) {
receiver.i_add( gift );
}
}
if( ip.first.held_by( giver ) ) {
if( ip.first->count_by_charges() ) {
giver.use_charges( gift.typeId(), ip.second );
} else if( ip.second > 0 ) {
giver.remove_items_with( [&ip]( const item & i ) {
return &i == ip.first.get_item();
}, ip.second );
}
} else {
if( ip.first->count_by_charges() ) {
ip.first.get_item()->set_var( "trade_charges", ip.second );
} else {
ip.first.get_item()->set_var( "trade_amount", 1 );
}
from_map.push_back( &ip.first );
}
}
return escrow;
}
std::vector<item_pricing> npc_trading::init_selling( npc &np )
{
std::vector<item_pricing> result;
const std::vector<item *> inv_all = np.items_with( []( const item & it ) {
return !it.made_of( phase_id::LIQUID );
} );
for( item *i : inv_all ) {
item &it = *i;
const int price = it.price( true );
int val = np.value( it );
if( np.wants_to_sell( it, val, price ).success() ) {
result.emplace_back( np, it, val, static_cast<int>( it.count() ) );
}
}
item_location weapon = np.get_wielded_item();
if(
np.will_exchange_items_freely() && weapon && !weapon->has_flag( json_flag_NO_UNWIELD )
) {
result.emplace_back( np, *weapon, np.value( *weapon ), false );
}
return result;
}
double npc_trading::net_price_adjustment( const Character &buyer, const Character &seller )
{
// Adjust the prices based on your social skill.
// cap adjustment so nothing is ever sold below value
///\EFFECT_INT_NPC slightly increases bartering price changes, relative to your INT
///\EFFECT_BARTER_NPC increases bartering price changes, relative to your BARTER
///\EFFECT_INT slightly increases bartering price changes, relative to NPC INT
///\EFFECT_BARTER increases bartering price changes, relative to NPC BARTER
int const int_diff = seller.int_cur - buyer.int_cur;
double const int_adj = 1 + 0.05 * std::min( 19, std::abs( int_diff ) );
double const soc_adj = price_adjustment( seller.get_skill_level( skill_speech ) -
buyer.get_skill_level( skill_speech ) );
double const adjust = int_diff >= 0 ? int_adj * soc_adj : soc_adj / int_adj;
return seller.is_npc() ? adjust : -1 / adjust;
}
int npc_trading::bionic_install_price( Character &installer, Character &patient,
item_location const &bionic )
{
return bionic->price( true ) * 2 +
( bionic->is_owned_by( patient )
? 0
: npc_trading::trading_price( patient, installer, { bionic, 1 } ) );
}
int npc_trading::adjusted_price( item const *it, int amount, Character const &buyer,
Character const &seller )
{
faction const *const fac = buyer.is_npc() ? buyer.get_faction() : seller.get_faction();
npc const *faction_party = buyer.is_npc() ? buyer.as_npc() : seller.as_npc();
faction_price_rule const *const fpr = fac != nullptr ? fac->get_price_rules( *it,
*faction_party ) : nullptr;
double price = it->price_no_contents( true );
if( fpr != nullptr && seller.is_npc() ) {
price *= fpr->markup;
}
if( it->count_by_charges() && amount >= 0 ) {
price *= static_cast<double>( amount ) / it->charges;
}
if( buyer.is_npc() ) {
price = buyer.as_npc()->value( *it, price );
} else if( seller.is_npc() ) {
price = seller.as_npc()->value( *it, price );
}
if( fpr != nullptr && fpr->fixed_adj.has_value() ) {
double const fixed_adj = fpr->fixed_adj.value();
price *= 1 + ( seller.is_npc() ? fixed_adj : -fixed_adj );
} else {
double const adjust = npc_trading::net_price_adjustment( buyer, seller );
price *= 1 + 0.25 * adjust;
}
return static_cast<int>( std::ceil( price ) );
}
int npc_trading::trading_price( Character const &buyer, Character const &seller,
trade_selector::entry_t const &it )
{
int ret = 0;
it.first->visit_items( [&]( const item * e, item * /* f */ ) {
int const amount = e == it.first.get_item() ? it.second : -1;
int const price = adjusted_price( e, amount, buyer, seller );
int const market_price = e->price_no_contents( true );
if( seller.is_npc() ) {
npc const &np = *seller.as_npc();
if( !np.wants_to_sell( *e, price, market_price ).success() ) {
return VisitResponse::SKIP;
}
} else if( buyer.is_npc() ) {
npc const &np = *buyer.as_npc();
if( !np.wants_to_buy( *e, price, market_price ).success() ) {
return VisitResponse::SKIP;
}
}
ret += price;
return VisitResponse::NEXT;
} );
return ret;
}
void item_pricing::set_values( int ip_count )
{
const item *i_p = loc.get_item();
is_container = i_p->is_container() || i_p->is_ammo_container();
vol = i_p->volume();
weight = i_p->weight();
if( is_container || i_p->count() == 1 ) {
count = ip_count;
} else {
charges = i_p->count();
if( charges > 0 ) {
price /= charges;
vol /= charges;
weight /= charges;
} else {
debugmsg( "item %s has zero or negative charges", i_p->typeId().str() );
}
}
}
// Returns how much the NPC will owe you after this transaction.
// You must also check if they will accept the trade.
int npc_trading::calc_npc_owes_you( const npc &np, int your_balance )
{
// Friends don't hold debts against friends.
if( np.will_exchange_items_freely() ) {
return 0;
}
// If they're going to owe you more than before, and it's more than they're willing
// to owe, then cap the amount owed at the present level or their willingness to owe
// (whichever is bigger).
//
// When could they owe you more than max_willing_to_owe? It could be from quest rewards,
// when they were less angry, or from when you were better friends.
if( your_balance > np.op_of_u.owed && your_balance > np.max_willing_to_owe() ) {
return std::max( np.op_of_u.owed, np.max_willing_to_owe() );
}
// Fair's fair. NPC will remember this debt (or credit they've extended)
return your_balance;
}
void npc_trading::update_npc_owed( npc &np, int your_balance, int your_sale_value )
{
np.op_of_u.owed = calc_npc_owes_you( np, your_balance );
np.op_of_u.sold += your_sale_value;
}
// Oh my aching head
// op_of_u.owed is the positive when the NPC owes the player, and negative if the player owes the
// NPC
// cost is positive when the player owes the NPC money for a service to be performed
bool npc_trading::trade( npc &np, int cost, const std::string &deal )
{
np.shop_restock();
//np.drop_items( np.weight_carried() - np.weight_capacity(),
// np.volume_carried() - np.volume_capacity() );
np.drop_invalid_inventory();
std::unique_ptr<trade_ui> tradeui = std::make_unique<trade_ui>( get_avatar(), np, cost, deal );
trade_ui::trade_result_t trade_result = tradeui->perform_trade();
if( trade_result.traded ) {
tradeui.reset();
std::list<item_location *> from_map;
std::list<item> escrow;
avatar &player_character = get_avatar();
// Movement of items in 3 steps: player to escrow - npc to player - escrow to npc.
escrow = npc_trading::transfer_items( trade_result.items_you, player_character, np, from_map,
true );
npc_trading::transfer_items( trade_result.items_trader, np, player_character, from_map, false );
// Now move items from escrow to the npc. Keep the weapon wielded.
if( np.mission == NPC_MISSION_SHOPKEEP ) {
distribute_items_to_npc_zones( escrow, np );
} else {
for( const item &i : escrow ) {
np.i_add( i, true, nullptr, nullptr, true, false );
}
}
for( item_location *loc_ptr : from_map ) {
if( !loc_ptr ) {
continue;
}
item *it = loc_ptr->get_item();
if( !it ) {
continue;
}
if( it->has_var( "trade_charges" ) && it->count_by_charges() ) {
it->charges -= static_cast<int>( it->get_var( "trade_charges", 0 ) );
if( it->charges <= 0 ) {
loc_ptr->remove_item();
} else {
it->erase_var( "trade_charges" );
}
} else if( it->has_var( "trade_amount" ) ) {
loc_ptr->remove_item();
}
}
// NPCs will remember debts, to the limit that they'll extend credit or previous debts
if( !np.will_exchange_items_freely() ) {
update_npc_owed( np, trade_result.balance, trade_result.value_you );
player_character.practice( skill_speech, trade_result.value_you / 10000 );
}
}
return trade_result.traded ;
}
// Will the NPC accept the trade that's currently on offer?
bool npc_trading::npc_will_accept_trade( npc const &np, int your_balance )
{
return np.will_exchange_items_freely() || your_balance + np.max_credit_extended() >= 0;
}
bool npc_trading::npc_can_fit_items( npc const &np, trade_selector::select_t const &to_trade )
{
std::vector<item> avail_pockets = np.worn.available_pockets();
if( avail_pockets.empty() ) {
return false;
}
for( trade_selector::entry_t const &it : to_trade ) {
bool item_stored = false;
for( item &pkt : avail_pockets ) {
const units::volume pvol = pkt.max_containable_volume();
const item &i = *it.first;
if( pkt.can_holster( i ) || ( pkt.can_contain( i ).success() && pvol > i.volume() ) ) {
pkt.put_in( i, item_pocket::pocket_type::CONTAINER );
item_stored = true;
break;
}
}
if( !item_stored && !np.can_wear( *it.first, false ).value() ) {
return false;
}
}
return true;
}