EvEmu  0.8.4
11 September 2021
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
AccountService.cpp
Go to the documentation of this file.
1 /*
2  ------------------------------------------------------------------------------------
3  LICENSE:
4  ------------------------------------------------------------------------------------
5  This file is part of EVEmu: EVE Online Server Emulator
6  Copyright 2006 - 2021 The EVEmu Team
7  For the latest information visit https://evemu.dev
8  ------------------------------------------------------------------------------------
9  This program is free software; you can redistribute it and/or modify it under
10  the terms of the GNU Lesser General Public License as published by the Free Software
11  Foundation; either version 2 of the License, or (at your option) any later
12  version.
13 
14  This program is distributed in the hope that it will be useful, but WITHOUT
15  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16  FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
17 
18  You should have received a copy of the GNU Lesser General Public License along with
19  this program; if not, write to the Free Software Foundation, Inc., 59 Temple
20  Place - Suite 330, Boston, MA 02111-1307, USA, or go to
21  http://www.gnu.org/copyleft/lesser.txt.
22  ------------------------------------------------------------------------------------
23  Author: Zhur
24  Rewrite: Allan
25 */
26 
27 #include <boost/algorithm/string.hpp>
28 #include "eve-server.h"
29 
30 #include "EntityList.h"
31 #include "PyServiceCD.h"
32 #include "StaticDataMgr.h"
33 #include "account/AccountService.h"
34 #include "cache/ObjCacheService.h"
36 
37 /*
38  * ACCOUNT__ERROR
39  * ACCOUNT__WARNING
40  * ACCOUNT__INFO
41  * ACCOUNT__MESSAGE
42  * ACCOUNT__TRACE
43  * ACCOUNT__CALL
44  * ACCOUNT__CALL_DUMP
45  * ACCOUNT__RSP_DUMP
46  * ACCOUNT__DB_ERROR
47  * ACCOUNT__DB_WARNING
48  * ACCOUNT__DB_INFO
49  * ACCOUNT__DB_MESSAGE
50  */
51 
53 
55 : PyService(mgr, "account"),
56  m_dispatch(new Dispatcher(this))
57 {
58  _SetCallDispatcher(m_dispatch);
59 
60  PyCallable_REG_CALL(AccountService, GetCashBalance);
61  PyCallable_REG_CALL(AccountService, GetEntryTypes);
64  PyCallable_REG_CALL(AccountService, GiveCashFromCorpAccount);
66  PyCallable_REG_CALL(AccountService, GetJournalForAccounts);
67  PyCallable_REG_CALL(AccountService, GetWalletDivisionsInfo);
68  PyCallable_REG_CALL(AccountService, GetDefaultContactCost);
69  PyCallable_REG_CALL(AccountService, SetContactCost);
70 }
71 
73  delete m_dispatch;
74 }
75 
76 PyResult AccountService::Handle_GetKeyMap(PyCallArgs &call)
77 {
78  return sDataMgr.GetKeyMap(); // account key types
79 }
80 
81 PyResult AccountService::Handle_GetEntryTypes(PyCallArgs &call)
82 {
83  return sDataMgr.GetEntryTypes(); // journal entry IDs
84 }
85 
86 PyResult AccountService::Handle_GetWalletDivisionsInfo(PyCallArgs &call)
87 {
89 }
90 
91 // from mail/label window->settings
92 PyResult AccountService::Handle_GetDefaultContactCost(PyCallArgs &call)
93 {
94  /*
95  self.defaultContactCost = self.GetAccountSvc().GetDefaultContactCost()
96  if self.defaultContactCost is None:
97  self.defaultContactCost = -1
98  */
99 
100  sLog.Log( "AccountService::Handle_GetDefaultContactCost()", "size=%u", call.tuple->size());
101  call.Dump(ACCOUNT__CALL_DUMP);
102 
103  //return m_db.GetDefaultContactCost(call.client->GetCorporationID());
104 
105  // returning "none" will block all contact attempts
106  return PyStatic.NewNone();
107 }
108 
109 PyResult AccountService::Handle_SetContactCost(PyCallArgs &call)
110 {
111  /*
112  self.GetAccountSvc().SetContactCost(cost)
113 
114  def BlockAll(self):
115  self.GetAccountSvc().SetContactCost(None)
116  */
117 
118  sLog.Log( "AccountService::Handle_SetContactCost()", "size=%u", call.tuple->size());
119  call.Dump(ACCOUNT__CALL_DUMP);
120  // m_db.SetContactCost(call.client->GetCorporationID());
121 
122  // returns nothing
123  return nullptr;
124 }
125 
126 PyResult AccountService::Handle_GetCashBalance(PyCallArgs &call) {
127  //corrected, updated, optimized -allan 26jan15 ReVisited/Rewrote -allan 7Dec17 Update -allan 20May19
128  if (is_log_enabled(ACCOUNT__CALL_DUMP)) {
129  sLog.Log( "AccountService::Handle_GetCashBalance()", "size=%u", call.tuple->size());
130  call.Dump(ACCOUNT__CALL_DUMP);
131  }
132  bool isCorp = false;
133  if (call.tuple->size() > 0)
134  isCorp = PyRep::IntegerValue(call.tuple->GetItem(0));
135 
136  double balance(0);
137  int16 accountKey(call.client->GetCorpAccountKey());
138  if (call.byname.find("accountKey") != call.byname.end())
139  accountKey = PyRep::IntegerValueU32(call.byname.find("accountKey")->second);
140 
141  if (isCorp) {
142  balance = AccountDB::GetCorpBalance( call.client->GetCorporationID(), accountKey);
143  } else {
145  if (accountKey == Account::KeyType::AUR) {
147  } else if (accountKey == Account::KeyType::DUST_ISK) {
149  }
150  balance = call.client->GetBalance(type);
151  }
152 
153  return new PyFloat(balance);
154 }
155 
156 PyResult AccountService::Handle_GetJournal(PyCallArgs &call)
157 { // this asks for data for a single acctKey
158  if (is_log_enabled(ACCOUNT__CALL_DUMP)) {
159  sLog.Log( "AccountService::Handle_GetJournal()", "size=%u", call.tuple->size());
160  call.Dump(ACCOUNT__CALL_DUMP);
161  }
162  Call_GetJournal args;
163  if (!args.Decode(&call.tuple)) {
164  codelog(SERVICE__ERROR, "%s: Failed to decode arguments.", GetName());
165  return nullptr;
166  }
167 
168  uint32 ownerID(call.client->GetCharacterID());
169  if (args.corpAccount)
170  ownerID = call.client->GetCorporationID();
171 
172  PyRep* res = m_db.GetJournal(ownerID, args.entryTypeID, args.accountKey, args.fromDate, args.rev);
173  if (is_log_enabled(ACCOUNT__RSP_DUMP))
174  res->Dump(ACCOUNT__RSP_DUMP, " ");
175  return res;
176 }
177 
179 PyResult AccountService::Handle_GetJournalForAccounts(PyCallArgs &call) {
180  // this asks for data for multiple acctKeys
181  // self.journalData[key] = self.GetAccountSvc().GetJournalForAccounts(accountKeys, fromDate, entryTypeID, corpAccount, transactionID, rev)
182  if (is_log_enabled(ACCOUNT__CALL_DUMP)) {
183  sLog.Log( "AccountService::Handle_GetJournalForAccounts()", "size=%u", call.tuple->size());
184  call.Dump(ACCOUNT__CALL_DUMP);
185  }
186  Call_GetJournals args;
187  if (!args.Decode(&call.tuple)) {
188  codelog(SERVICE__ERROR, "%s: Failed to decode arguments.", GetName());
189  return nullptr;
190  }
191 
192  uint32 ownerID = call.client->GetCharacterID();
193  if (args.corpAccount)
194  ownerID = call.client->GetCorporationID();
195 
196  uint16 acctKey = Account::KeyType::Cash;
197 
198  PyRep* res = m_db.GetJournal(ownerID, args.entryTypeID, acctKey, args.fromDate, args.rev);
199  if (is_log_enabled(ACCOUNT__RSP_DUMP))
200  res->Dump(ACCOUNT__RSP_DUMP, " ");
201  return res;
202 }
203 
204 PyResult AccountService::Handle_GiveCash(PyCallArgs &call)
205 {
206  if (is_log_enabled(ACCOUNT__CALL_DUMP)) {
207  sLog.Log( "AccountService::Handle_GiveCash()", "size=%u", call.tuple->size());
208  call.Dump(ACCOUNT__CALL_DUMP);
209  }
210  Call_GiveCash args;
211  if (!args.Decode(&call.tuple)) {
212  codelog(SERVICE__ERROR, "%s: Failed to decode arguments.", GetName());
213  return nullptr;
214  }
215 
216  std::string reason = "DESC: ";
217  if (args.reason.size() < 1) {
218  reason += "No Reason Given";
219  } else {
220  // this hits db directly, so test for possible sql injection code
221  for (const auto cur : badChars)
222  if (EvE::icontains(args.reason, cur))
223  throw CustomError ("Description contains invalid characters");
224  reason += args.reason;
225  }
226 
227  TranserFunds(call.client->GetCharacterID(), args.toID, args.amount, reason.c_str(), Journal::EntryType::PlayerDonation, call.client->GetCharacterID());
228  return nullptr;
229 }
230 
231 PyResult AccountService::Handle_GiveCashFromCorpAccount(PyCallArgs &call)
232 {
233  if (is_log_enabled(ACCOUNT__CALL_DUMP)) {
234  sLog.Log( "AccountService::Handle_GiveCashFromCorpAccount()", "size=%u", call.tuple->size());
235  call.Dump(ACCOUNT__CALL_DUMP);
236  }
237  Call_GiveCorpCash args;
238  if (!args.Decode(&call.tuple)) {
239  codelog(SERVICE__ERROR, "%s: Failed to decode arguments.", GetName());
240  return nullptr;
241  }
242 
243  uint16 toAcctKey = Account::KeyType::Cash;
244  if (call.byname.find("toAccountKey") != call.byname.end())
245  toAcctKey = PyRep::IntegerValue(call.byname.find("toAccountKey")->second);
246 
247  std::string reason= "DESC: ";
248  if (call.byname.find("reason") != call.byname.end()) {
249  // make sure that the reason has anything in it so YAML parsing is correct
250  std::string content = PyRep::StringContent (call.byname.find ("reason")->second);
251 
252  if (content.size () < 1) {
253  reason += "No Reason Given by ";
254  reason += call.client->GetCharName();
255  } else {
256  // this hits db directly, so test for possible sql injection code
257  for (const auto cur: badChars)
258  if (EvE::icontains(content, cur))
259  throw CustomError("Reason contains invalid characters");
260 
261  reason += PyRep::StringContent(call.byname.find("reason")->second);
262  }
263  } else {
264  reason += "No Reason Given by ";
265  reason += call.client->GetCharName();
266  }
267 
268  TranserFunds(call.client->GetCorporationID(), args.toID, args.amount, reason.c_str(), Journal::EntryType::CorporationAccountWithdrawal, \
269  call.client->GetCharacterID(), args.fromAcctKey, toAcctKey, call.client);
270  return nullptr;
271 }
272 
273 void AccountService::TranserFunds(uint32 fromID, uint32 toID, double amount, std::string reason /*""*/, uint8 entryTypeID /*Journal::EntryType::Undefined*/, \
274  uint32 referenceID/*0*/, uint16 fromKey/*Account::KeyType::Cash*/, uint16 toKey/*Account::KeyType::Cash*/,
275  Client* pClient/*nullptr*/)
276 {
277  if (is_log_enabled(ACCOUNT__TRACE))
278  _log(ACCOUNT__TRACE, "TranserFunds() - from: %u, to: %u, entry: %u, refID: %u, amount: %.2f, fKey: %u, tKey: %u", \
279  fromID, toID, entryTypeID, referenceID, amount, fromKey, toKey);
280  uint8 fromCurrency = Account::CreditType::ISK;
281  if (IsAUR(fromKey)) {
282  fromCurrency = Account::CreditType::AURUM;
283  } else if (IsDustKey(fromKey)) {
284  fromCurrency = Account::CreditType::MPLEX;
285  }
286 
287  double newBalanceFrom(0), newBalanceTo(0);
288  Client* pClientFrom(nullptr);
289  if (IsCharacterID(fromID)) {
290  pClientFrom = sEntityList.FindClientByCharID(fromID);
291  if (pClientFrom == nullptr) {
292  // sender is offline. xfer funds thru db.
293  newBalanceFrom = AccountDB::OfflineFundXfer(fromID, -amount, fromCurrency);
294  } else {
295  // this will throw if it fails
296  pClientFrom->AddBalance(-amount, fromCurrency);
297  newBalanceFrom = pClientFrom->GetBalance(fromCurrency);
298  }
299  AccountDB::AddJournalEntry(fromID, entryTypeID, fromID, toID, fromCurrency, fromKey, -amount, newBalanceFrom, reason, referenceID);
300  } else if (IsPlayerCorp(fromID)) {
301  uint32 userID(0);
302  if (pClient != nullptr)
303  userID = pClient->GetCharacterID();
304  HandleCorpTransaction(fromID, entryTypeID, userID?userID:fromID, toID, fromCurrency, fromKey, -amount, reason, referenceID);
305  } // fromID could be npc or _System. nothing to do on this side.
306 
307  uint8 toCurrency = Account::CreditType::ISK;
308  if (IsAUR(toKey)) {
309  toCurrency = Account::CreditType::AURUM;
310  } else if (IsDustKey(toKey)) {
311  toCurrency = Account::CreditType::MPLEX;
312  }
313 
314  Client* pClientTo(nullptr);
315  if (IsCharacterID(toID)) {
316  pClientTo = sEntityList.FindClientByCharID(toID);
317  if (pClientTo == nullptr) {
318  // receipient is offline. xfer funds thru db
319  newBalanceTo = AccountDB::OfflineFundXfer(toID, amount, toCurrency);
320  } else {
321  // this will throw if it fails
322  pClientTo->AddBalance(amount, toCurrency);
324  //TranserFunds(corpSCC, fromID, amount, reason, Journal::EntryType::Undefined, referenceID, fromKey, fromKey);
325  newBalanceTo = pClientTo->GetBalance(toCurrency);
326  }
327  AccountDB::AddJournalEntry(toID, entryTypeID, fromID, toID, toCurrency, toKey, amount, newBalanceTo, reason, referenceID);
328  } else if (IsPlayerCorp(toID)) {
329  uint32 userID(0);
330  if (pClient != nullptr)
331  userID = pClient->GetCharacterID();
332  HandleCorpTransaction(toID, entryTypeID, fromID, userID?userID:toID, toCurrency, toKey, amount, reason, referenceID);
333  return;
334  } else {
335  _log(ACCOUNT__TRACE, "TranserFunds() - toID: %s(%u) is neither player nor player corp. Not sending update.", \
336  sDataMgr.GetCorpName(toID).c_str(), toID);
337  return;
338  }
339 
340  if ((pClientTo != nullptr) and pClientTo->IsCharCreation())
341  return;
342 
343  /* corp taxes...
344  * bounty prizes and mission rewards are taxed by the players corp based on corp tax rate.
345  * there is a possibility the char receiving these payments could be offline (for whatever reason)
346  * these payments are only taxed if they are above amount set in server config.
347  */
348 
349  // are bounty payments grouped on timer?
350  if ((entryTypeID == Journal::EntryType::BountyPrize)
351  or (entryTypeID == Journal::EntryType::BountyPrizes))
352  if (sConfig.server.BountyPayoutDelayed)
353  if (amount < sConfig.rates.TaxedAmount) // is amount worth taxing? default is 75k
354  return;
355  float tax = 0;
356  uint32 corpID = 0;
357  if (pClientTo != nullptr) {
358  tax = pClientTo->GetCorpTaxRate() * amount;
359  corpID = pClientTo->GetCorporationID();
360  } else {
361  // recipient is offline...try to get needed data from db
362  tax = CharacterDB::GetCorpTaxRate(toID) * amount;
363  corpID = CharacterDB::GetCorpID(toID);
364  }
365 
366  // just in case something went wrong.....
367  if (!IsCorp(corpID))
368  return;
369  // is tax worth the accounting hassle? (from corp pov) default is 5k
370  if (tax < sConfig.rates.TaxAmount)
371  return;
372 
373  reason = "DESC: Corporation Tax on pirate bounty";
374  switch (entryTypeID) {
375  // Corp Taxed payment types
378  TranserFunds(toID, corpID, tax, reason.c_str(), Journal::EntryType::CorporationTaxNpcBounties, referenceID);
379  } break;
381  TranserFunds(toID, corpID, tax, reason.c_str(), Journal::EntryType::CorporationTaxAgentRewards, referenceID);
382  } break;
384  TranserFunds(toID, corpID, tax, reason.c_str(), Journal::EntryType::CorporationTaxAgentBonusRewards, referenceID);
385  } break;
386  }
387 }
388 
389 void AccountService::HandleCorpTransaction(uint32 corpID, int8 entryTypeID, uint32 fromID, uint32 toID, int8 currency, \
390  uint16 accountKey, double amount, std::string description, \
391  uint32 referenceID/*0*/)
392 {
393  if (is_log_enabled(ACCOUNT__TRACE))
394  _log(ACCOUNT__TRACE, "HandleCorpTransaction() - corp: %u, from: %u, to: %u, entry: %u, refID: %u, amount: %.2f, key: %u, currency: %u", \
395  corpID, fromID, toID, entryTypeID, referenceID, amount, accountKey, currency);
396  double balance = AccountDB::GetCorpBalance(corpID, accountKey);
397  // verify funds available for withdraw first
398  if (amount < 0) {
399  if (-amount > balance) {
400  std::map<std::string, PyRep *> args;
401  args["owner"] = new PyString(CorporationDB::GetCorpName(corpID));
402  args["amount"] = new PyFloat(-amount);
403  args["balance"] = new PyFloat(balance);
404  args["division"] = new PyString(CorporationDB::GetDivisionName(corpID, accountKey));
405  throw UserError ("NotEnoughMoneyCorp")
406  .AddOwnerName ("owner", corpID)
407  .AddISK ("amount", -amount)
408  .AddISK ("balance", balance)
409  .AddFormatValue ("division", new PyString (CorporationDB::GetDivisionName (corpID, accountKey)));
410  }
411  }
412  // get new corp balance
413  balance += amount;
414  // update corp balance
415  AccountDB::UpdateCorpBalance(corpID, accountKey, balance);
416 
417  OnAccountChange oac;
418  switch (accountKey) {
419  case Account::KeyType::Cash2: oac.accountKey = "cash2"; break;
420  case Account::KeyType::Cash3: oac.accountKey = "cash3"; break;
421  case Account::KeyType::Cash4: oac.accountKey = "cash4"; break;
422  case Account::KeyType::Cash5: oac.accountKey = "cash5"; break;
423  case Account::KeyType::Cash6: oac.accountKey = "cash6"; break;
424  case Account::KeyType::Cash7: oac.accountKey = "cash7"; break;
426  default: oac.accountKey = "cash"; break;
427  }
428  oac.balance = balance;
429  oac.ownerid = corpID;
430  sEntityList.CorpNotify(corpID, 126 /*WalletChange*/, "OnAccountChange", "*corpid&corpAccountKey", oac.Encode());
431  AccountDB::AddJournalEntry(corpID, entryTypeID, fromID, toID, currency, accountKey, amount, balance, description, referenceID);
432 }
Base Python wire object.
Definition: PyRep.h:66
#define sConfig
A macro for easier access to the singleton.
Dispatcher *const m_dispatch
unsigned __int8 uint8
Definition: eve-compat.h:46
static std::string StringContent(PyRep *pRep)
Definition: PyRep.cpp:103
static float GetCorpTaxRate(uint32 charID)
#define _log(type, fmt,...)
Definition: logsys.h:124
PyRep * GetItem(size_t index) const
Returns Python object.
Definition: PyRep.h:602
Python string.
Definition: PyRep.h:430
bool AddBalance(float amount, uint8 type=Account::CreditType::ISK)
Definition: Client.h:174
static void AddJournalEntry(uint32 ownerID, int8 entryTypeID, uint32 ownerFromID, uint32 ownerToID, int8 currency, uint16 accountKey, double amount, double newBalance, std::string description, uint32 referenceID=0)
Definition: AccountDB.cpp:166
#define IsAUR(key)
Definition: EVE_Defines.h:377
bool IsCharCreation()
Definition: Client.h:433
std::map< std::string, PyRep * > byname
Definition: PyCallable.h:51
size_t size() const
Definition: PyRep.h:591
static const std::array< std::string, 18 > badChars
Definition: EVE_Consts.h:87
Python floating point number.
Definition: PyRep.h:292
PyCallable_Make_InnerDispatcher(AccountService) AccountService
int32 GetCharacterID() const
Definition: Client.h:113
int32 GetCorporationID() const
Definition: Client.h:123
#define sEntityList
Definition: EntityList.h:208
static uint32 IntegerValueU32(PyRep *pRep)
Definition: PyRep.cpp:134
UserError & AddFormatValue(const char *name, PyRep *value)
Fluent version of the protected AddKeyword, allows for adding a keyword to the exception.
const char * GetName() const
Definition: PyService.h:54
Advanced version of UserError that allows to send a full custom message.
Definition: PyExceptions.h:453
void Dump(FILE *into, const char *pfx) const
Dumps object to file.
Definition: PyRep.cpp:84
signed __int8 int8
Definition: eve-compat.h:45
UserError & AddISK(const char *name, double isk)
Shorthand method for adding an ISK amount.
* args
#define is_log_enabled(type)
Definition: logsys.h:78
#define sLog
Evaluates to a NewLog instance.
Definition: LogNew.h:250
#define codelog(type, fmt,...)
Definition: logsys.h:128
UserError & AddOwnerName(const char *name, uint32 ownerID)
Shorthand method for adding an owner's name.
static void TranserFunds(uint32 fromID, uint32 toID, double amount, std::string reason="", uint8 entryTypeID=Journal::EntryType::Undefined, uint32 referenceID=0, uint16 fromKey=Account::KeyType::Cash, uint16 toKey=Account::KeyType::Cash, Client *pClient=nullptr)
static std::string GetDivisionName(uint32 corpID, uint16 acctKey)
PyRep * GetWalletDivisionsInfo(uint32 corpID)
Definition: AccountDB.cpp:109
#define PyStatic
Definition: PyRep.h:1209
Dispatcher *const m_dispatch
static uint32 GetCorpID(uint32 charID)
#define IsPlayerCorp(itemID)
Definition: EVE_Defines.h:241
double GetCorpTaxRate()
Definition: Client.h:122
Client *const client
Definition: PyCallable.h:49
#define IsCharacterID(itemID)
Definition: EVE_Defines.h:206
Python object "ccp_exceptions.UserError".
Definition: PyExceptions.h:121
#define PyCallable_REG_CALL(c, m)
Definition: PyServiceCD.h:78
Definition: Client.h:66
unsigned __int32 uint32
Definition: eve-compat.h:50
std::string GetCharName()
Definition: Client.h:165
#define IsCorp(itemID)
Definition: EVE_Defines.h:234
float GetBalance(uint8 type=Account::CreditType::ISK)
Definition: Client.h:176
static void HandleCorpTransaction(uint32 corpID, int8 entryTypeID, uint32 fromID, uint32 toID, int8 currency, uint16 accountKey, double amount, std::string description, uint32 referenceID=0)
static void UpdateCorpBalance(uint32 corpID, uint16 accountKey, double amount)
Definition: AccountDB.cpp:92
int32 GetCorpAccountKey() const
Definition: Client.h:127
void Dump(LogType type) const
Definition: PyCallable.cpp:81
signed __int16 int16
Definition: eve-compat.h:47
static int64 IntegerValue(PyRep *pRep)
Definition: PyRep.cpp:118
static double GetCorpBalance(uint32 corpID, uint16 accountKey)
Definition: AccountDB.cpp:69
unsigned __int16 uint16
Definition: eve-compat.h:48
AccountDB m_db
static double OfflineFundXfer(uint32 charID, double amount, uint8 type=Account::CreditType::ISK)
Definition: AccountDB.cpp:46
static std::string GetCorpName(uint32 corpID)
PyRep * GetJournal(uint32 ownerID, int8 entryTypeID, uint16 accountKey, int64 fromDate, bool reverse=false)
Definition: AccountDB.cpp:135
bool icontains(std::string data, std::string toSearch, size_t pos=0)
Definition: misc.cpp:194
PyTuple * tuple
Definition: PyCallable.h:50
#define sDataMgr
#define IsDustKey(key)
Definition: EVE_Defines.h:380