EvEmu  0.8.4
11 September 2021
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
RamProxyService.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  Implementation: Positron96
25  Rewrite: Allan
26 */
27 
30 #include "eve-server.h"
31 
32 #include "../../eve-common/EVE_Calendar.h"
33 #include "../../eve-common/EVE_Mail.h"
34 
35 #include "PyServiceCD.h"
36 #include "EntityList.h"
37 #include "StatisticMgr.h"
38 #include "StaticDataMgr.h"
39 #include "account/AccountService.h"
43 #include "station/StationDataMgr.h"
44 #include "system/CalendarDB.h"
45 
47 
49 : PyService(mgr, "ramProxy"),
50  m_dispatch(new Dispatcher(this))
51 {
52  _SetCallDispatcher(m_dispatch);
53 
54  PyCallable_REG_CALL(RamProxyService, AssemblyLinesGet);
55  PyCallable_REG_CALL(RamProxyService, AssemblyLinesSelect);
56  PyCallable_REG_CALL(RamProxyService, AssemblyLinesSelectPublic);
57  PyCallable_REG_CALL(RamProxyService, AssemblyLinesSelectPrivate);
58  PyCallable_REG_CALL(RamProxyService, AssemblyLinesSelectCorp);
59  PyCallable_REG_CALL(RamProxyService, AssemblyLinesSelectAlliance);
63  PyCallable_REG_CALL(RamProxyService, GetRelevantCharSkills);
64  PyCallable_REG_CALL(RamProxyService, UpdateAssemblyLineConfigurations);
65 }
66 
68  delete m_dispatch;
69 }
70 
71 /*
72  * # Manufacturing Logging:
73  * MANUF__ERROR
74  * MANUF__WARNING
75  * MANUF__MESSAGE
76  * MANUF__INFO
77  * MANUF__DEBUG
78  * MANUF__TRACE
79  * MANUF__DUMP
80  */
81 
82 PyResult RamProxyService::Handle_GetRelevantCharSkills(PyCallArgs &call) {
83  return call.client->GetChar()->GetRAMSkills();
84 }
85 
86 PyResult RamProxyService::Handle_AssemblyLinesSelectPublic(PyCallArgs &call) {
88 }
89 
90 PyResult RamProxyService::Handle_AssemblyLinesSelectPrivate(PyCallArgs &call) {
92 }
93 
94 PyResult RamProxyService::Handle_AssemblyLinesSelectCorp(PyCallArgs &call) {
97 }
98 
99 PyResult RamProxyService::Handle_AssemblyLinesSelectAlliance(PyCallArgs &call) {
102 }
103 
104 PyResult RamProxyService::Handle_AssemblyLinesGet(PyCallArgs &call) {
105  Call_SingleIntegerArg arg; // containerID (stationID)
106  if (!arg.Decode(&call.tuple)) {
107  _log(SERVICE__ERROR, "Unable to decode args.");
108  return nullptr;
109  }
110 
111  return FactoryDB::AssemblyLinesGet(arg.arg);
112 }
113 
114 PyResult RamProxyService::Handle_AssemblyLinesSelect(PyCallArgs &call) {
115  Call_AssemblyLinesSelect args;
116  if (!args.Decode(&call.tuple)) {
117  codelog(SERVICE__ERROR, "%s: Failed to decode arguments.", GetName());
118  return nullptr;
119  }
120 
121  if (args.filter == "region") {
123  } else if (args.filter == "char") {
125  } else if (args.filter == "corp") {
127  } else if (args.filter == "alliance") {
129  }
130 
131  _log(SERVICE__ERROR, "Unknown filter '%s'.", args.filter.c_str());
132  return nullptr;
133 }
134 
135 PyResult RamProxyService::Handle_GetJobs2(PyCallArgs &call) {
136  Call_GetJobs2 args;
137  if (!args.Decode(&call.tuple)) {
138  codelog(SERVICE__ERROR, "%s: Failed to decode arguments.", GetName());
139  return nullptr;
140  }
141 
142  if (args.ownerID == call.client->GetCorporationID())
144  // what other roles (if any) can view corp factory jobs?
145  call.client->SendInfoModalMsg("You cannot view your corporation's jobs because you are not a Factory Manager.");
146  return nullptr;
147  }
148 
149  return FactoryDB::GetJobs2(args.ownerID, args.completed);
150 }
151 
154 PyResult RamProxyService::Handle_InstallJob(PyCallArgs &call) {
155  //job = sm.ProxySvc('ramProxy').InstallJob(installationLocationData, installedItemLocationData, bomLocationData, flagOutput, quoteData.buildRuns, quoteData.activityID, quoteData.licensedProductionRuns, not quoteData.ownerFlag, 'blah', quoteOnly=1, installedItem=quoteData.blueprint, maxJobStartTime=quoteData.assemblyLine.nextFreeTime + 1 * MIN, inventionItems=quoteData.inventionItems, inventionOutputItemID=quoteData.inventionItems.outputType)
156 
157  _log(MANUF__DUMP, "RamProxyService::Handle_InstallJob() - size %u", call.tuple->size() );
158  call.Dump(MANUF__DUMP);
159 
160  Call_InstallJob args;
161  if (!args.Decode(&call.tuple)) {
162  codelog(SERVICE__ERROR, "%s: Failed to decode arguments.", GetName());
163  return nullptr;
164  }
165 
166  _log(MANUF__INFO, "RamProxyService::Handle_InstallJob() - %s", sRamMthd.GetActivityName(args.activityID));
167 
168  // check character job count - will throw if fail
169  sRamMthd.JobsCheck(call.client->GetChar().get(), args);
170 
171  // load job Blueprint
172  // bp in pos can be in hangar or array. need to check both
173  InventoryItemRef installedItem = sItemFactory.GetItem( args.bpItemID );
174  if (installedItem.get() == nullptr) {
175  // this means item/location not loaded.
176  // get data from installedItem named args and continue
177  // this will require some work/rewriting
178 
179  //RamBlueprintAlreadyInstalled
180  // this is InventoryItem details of the bp being installed.
181  if (call.byname.find("installedItem") != call.byname.end()) {
182  // use this for stations that are NOT loaded.
183  /* on remote jobs, may have to use this data to:
184  * split stacks
185  * send to proper places
186  * verify containers
187  * verify access
188  * etc.
189  */
190  /*
191  * 21:48:04 [ManufDump] installedItem
192  * 21:48:04 [ManufDump] Object:
193  * 21:48:04 [ManufDump] Type: String: 'util.KeyVal'
194  * 21:48:04 [ManufDump] Args: Dictionary: 11 entries
195  * 21:48:04 [ManufDump] Args: [ 0] Key: String: 'typeID'
196  * 21:48:04 [ManufDump] Args: [ 0] Value: Integer: 880
197  * 21:48:04 [ManufDump] Args: [ 1] Key: String: 'singleton'
198  * 21:48:04 [ManufDump] Args: [ 1] Value: Integer: 2
199  * 21:48:04 [ManufDump] Args: [ 2] Key: String: 'flagID'
200  * 21:48:04 [ManufDump] Args: [ 2] Value: Integer: 4
201  * 21:48:04 [ManufDump] Args: [ 3] Key: String: 'quantity'
202  * 21:48:04 [ManufDump] Args: [ 3] Value: Integer: -2
203  * 21:48:04 [ManufDump] Args: [ 4] Key: String: 'categoryID'
204  * 21:48:04 [ManufDump] Args: [ 4] Value: Integer: 9
205  * 21:48:04 [ManufDump] Args: [ 5] Key: String: 'itemID'
206  * 21:48:04 [ManufDump] Args: [ 5] Value: Integer: 140024212
207  * 21:48:04 [ManufDump] Args: [ 6] Key: String: 'locationID'
208  * 21:48:04 [ManufDump] Args: [ 6] Value: Integer: 60014140
209  * 21:48:04 [ManufDump] Args: [ 7] Key: String: 'ownerID'
210  * 21:48:04 [ManufDump] Args: [ 7] Value: Integer: 90000000
211  * 21:48:04 [ManufDump] Args: [ 8] Key: String: 'groupID'
212  * 21:48:04 [ManufDump] Args: [ 8] Value: Integer: 165
213  * 21:48:04 [ManufDump] Args: [ 9] Key: String: 'stacksize'
214  * 21:48:04 [ManufDump] Args: [ 9] Value: Integer: 1
215  * 21:48:04 [ManufDump] Args: [10] Key: String: 'customInfo'
216  * 21:48:04 [ManufDump] Args: [10] Value: String: ''
217  */
218 
219  PyDict* dict = call.byname["installedItem"]->AsDict();
220  installedItem = sItemFactory.GetItem( PyRep::IntegerValueU32(dict->GetItemString("itemID")) );
221  if (installedItem.get() == nullptr) {
222  // make error here.....
223  throw UserError ("RamActivityRequiresABlueprint");
224  }
225  }
226  _log(MANUF__ERROR, "installedItem dict incomplete");
227  throw CustomError ("Remote Job Installation Not Functional at this time.");
228  return nullptr;
229  }
230  if (installedItem->categoryID() != EVEDB::invCategories::Blueprint)
231  throw UserError ("RamActivityRequiresABlueprint");
232 
233  // installedItem is bp. change ref to bpRef
234  BlueprintRef bpRef = BlueprintRef::StaticCast(installedItem);
235 
236  // check assy line activity
237  sRamMthd.ActivityCheck(call.client, args, bpRef);
238 
239  // if output flag not set, put it where it came from
240  if (args.outputFlag == flagNone)
241  args.outputFlag = bpRef->flag();
242 
243  // check permissions
244  sRamMthd.LinePermissionCheck(call.client, args);
245  sRamMthd.ItemOwnerCheck(call.client, args, bpRef);
246 
247 
248  // this is a bit funky, but works quite well....
249  // decode path to BP and BOM location
250  // i am populating this for corp and personal to have common call to mat'l checks that follow
251  PathElement bomLocPath;
252  if (args.isCorpJob) {
253  // this will get messy...
259  CorpPathElement bpPath;
260  if (!bpPath.Decode(args.bpLocPath)) {
261  _log(SERVICE__ERROR, "Failed to decode Corp BP path.");
262  return nullptr;
263  }
264  // check bp location access
265  sRamMthd.HangarRolesCheck(call.client, bpPath.pathFlagID);
266  sRamMthd.LocationRolesCheck(call.client, bpPath);
267 
268 
269  // bp passed..check bomData
270  CorpPathElement bomPath;
271  if (!bomPath.Decode(args.bomLocPath)) {
272  _log(SERVICE__ERROR, "Failed to decode Corp BOM path.");
273  return nullptr;
274  }
275  // check bp location access
276  sRamMthd.HangarRolesCheck(call.client, bomPath.officeFlagID);
277  sRamMthd.LocationRolesCheck(call.client, bomPath);
278 
279  bomLocPath.locationID = bomPath.officeID;
280  bomLocPath.ownerID = bomPath.officeCorpID;
281  bomLocPath.flagID = bomPath.pathFlagID;
282  } else {
283  // get path data for player
284  if (!bomLocPath.Decode(args.bpLocPath->GetItem(0))) {
285  _log(SERVICE__ERROR, "Failed to decode BOM path.");
286  return nullptr;
287  }
288  }
289 
290  // calculate time and cost
291  // Rsp_InstallJob is used as container while building response to job quote.
292  Rsp_InstallJob rsp;
293  if (!sRamMthd.Calculate(args, bpRef, call.client->GetChar().get(), rsp)){
294  _log(MANUF__ERROR, "Could not Calculate() on %s for %s(%u)", bpRef->name(), call.client->GetName(), call.client->GetCharacterID());
295  return nullptr;
296  }
297 
298  // sent as assy line.nextFreeTime + 1m (a previous call asks for assy line nextFreeTime, displayed in window)
299  if (call.byname.find("maxJobStartTime") != call.byname.end())
300  if (rsp.maxJobStartTime > PyRep::IntegerValue(call.byname["maxJobStartTime"]))
301  throw UserError ("RamProductionTimeExceedsLimits");
302 
303  //RamCannotGuaranteeStartTime // timeslot taken by another char while installing this one
304 
305  // get required items for activity
306  std::vector<EvERam::RequiredItem> reqItems;
307  sDataMgr.GetRamRequiredItems(bpRef->typeID(), (int8)args.activityID, reqItems);
308 
309  // quoteOnly is sent for all jobs before installation to approve price and timeframe
310  if (PyRep::IntegerValueU32(call.byname["quoteOnly"])) {
311  _log(MANUF__INFO, "quoteOnly = true");
312  sRamMthd.EncodeBillOfMaterials(reqItems, rsp.materialMultiplier, rsp.charMaterialMultiplier, args.runs, rsp.bom);
313  sRamMthd.EncodeMissingMaterials(reqItems, bomLocPath, call.client, rsp.materialMultiplier, rsp.charMaterialMultiplier, args.runs, rsp.missingMaterials);
314 
315  // this value is halved in client code. (removed in client update patch 5Nov20)
316  //rsp.charTimeMultiplier *= 2;
317  return rsp.Encode();
318  }
319 
320  // verify installer has skills and all needed materials are present in proper location
321  sRamMthd.MaterialSkillsCheck(call.client, args.runs, bomLocPath, rsp, reqItems);
322 
323 
324  // at this point, it is a real job installation. check everything else
325  sRamMthd.ProductionTimeCheck(rsp.productionTime);
326 
327  // get proper location data
329  uint32 locationID(0);
330  switch (args.lineLocationGroupID) {
332  // this should be same location as client....unless remote
333  locationID = args.lineLocationID;
334  } break;
336  // this will be pos or ship in space (not sure how ore compression works yet)
337  locationID = args.lineLocationID;
338  } break;
339  default: {
340  locationID = args.lineLocationID;
341  if (sDataMgr.IsStation(args.lineContainerID)) {
342  locationID = args.lineContainerID;
343  } else {
344  sLog.Warning("InstallJob", "Location is not Station. Needs work.");
345  }
346  } break;
347  }
348 
349  // approved job cost from quote
350  float cost(PyRep::IntegerValue(call.byname["authorizedCost"]));
351 
352  // pay for assembly lines...take the money, send wallet blink event record the transaction in journal.
353  std::string reason = "DESC: Installing ";
354  reason += sRamMthd.GetActivityName(args.activityID);
355  reason += " job in ";
356  if (sDataMgr.IsStation(locationID)) {
357  reason += stDataMgr.GetStationName(locationID);
358  } else { // test for POS after that system is more complete...
359  reason += "Unknown Location";
360  }
361 
362  if (args.isCorpJob) {
363  reason += " by ";
364  reason += call.client->GetName();
365  }
367  stDataMgr.GetOwnerID(locationID),
368  cost,
369  reason.c_str(),
371  locationID, // shows rental location (stationID)
373 
374  int64 beginTime(GetFileTimeNow());
375  if (beginTime < rsp.maxJobStartTime)
376  beginTime = rsp.maxJobStartTime;
377 
378  // register/save job to chosen assy line.
379  uint32 jobID = FactoryDB::InstallJob((args.isCorpJob ? call.client->GetCorporationID() : call.client->GetCharacterID()),
380  call.client->GetCharacterID(), args, beginTime, beginTime + rsp.productionTime * EvE::Time::Second, call.client->GetSystemID());
381 
382  if (jobID < 1) {
383  _log(MANUF__ERROR, "Could not InstallJob for %s using %s", call.client->GetName(), bpRef->name());
384  // make client error here...
385  return nullptr;
386  }
387 
388  if (bpRef->quantity() > 1) {
389  BlueprintRef iRef = bpRef->SplitBlueprint(1);
390  if (iRef.get() == nullptr) {
391  _log(MANUF__WARNING, "Failed to split %s for %s.", bpRef->name(), sRamMthd.GetActivityName(args.activityID));
392  throw UserError ("RamActivityRequiresABlueprint");
393  }
394  bpRef = iRef;
395  }
396 
397  // unpackage bpo and move to factory
398  bpRef->ChangeSingleton(true);
399  bpRef->Move(locRAMItems, flagFactoryBlueprint, true);
400 
401  // take required items
402  std::vector<InventoryItemRef> items;
403  sRamMthd.GetBOMItems( bomLocPath, items );
404 
405  std::vector<EvERam::RequiredItem>::iterator itemItr = reqItems.begin();
406  for (; itemItr != reqItems.end(); ++itemItr) {
407  if (itemItr->isSkill)
408  continue; // not interested
409 
410  // calculate needed quantity
411  uint32 qtyNeeded = (uint32)round(((itemItr->quantity * rsp.materialMultiplier) + (itemItr->quantity * rsp.charMaterialMultiplier - itemItr->quantity)) * args.runs);
412 
413  // consume required materials
414  std::vector<InventoryItemRef>::iterator refItr = items.begin();
415  for (; refItr != items.end(); ++refItr)
416  if ((*refItr)->typeID() == itemItr->typeID) {
417  if (qtyNeeded >= (*refItr)->quantity()) {
418  qtyNeeded -= (*refItr)->quantity();
419  (*refItr)->Delete();
420  } else {
421  (*refItr)->AlterQuantity(-qtyNeeded, true);
422  break; // we are done, stop searching
423  }
424  }
425  }
426 
427  // update runs and do other shit where applicable
428  switch (args.activityID) {
430  // we just need to update runs here
431  bpRef->UpdateRuns(-args.runs);
432  } break;
434  bpRef->UpdateRuns(-args.runs);
435  // there is more to this, but not implemented yet
436  } break;
438  bpRef->UpdateRuns(-args.runs);
439  // im sure there is more to do here......
440 
458  } break;
459  }
460 
461  if (sConfig.ram.AutoEvent) {
462  std::string title;
463  if (args.isCorpJob) {
464  title += "Corporate ";
465  } else {
466  title += "Personal ";
467  }
468  title += sRamMthd.GetActivityName(args.activityID);
469  title += " Job";
470 
471  std::string description;
472  if (args.isCorpJob) {
473  description += "The ";
474  } else {
475  description += "Your ";
476  }
477  description += sRamMthd.GetActivityName(args.activityID);
478  description += " job in ";
480  if (sDataMgr.IsStation(locationID)) {
481  description += stDataMgr.GetStationName(locationID);
482  } else { // POS installation
483  description += "Unknown Location";
484  }
485 
486  description += " is scheduled to complete at the time noted.<br><br>";
487  switch (args.activityID) {
489  description += "This job is for <color=red>";
490  description += std::to_string(args.runs);
491  description += "</color> runs of <color=yellow>";
492  description += bpRef->productType().groupName();
493  description += "</color>,<br>producing <color=green>";
494  description += std::to_string(args.runs * bpRef->productType().portionSize());
495  description += "</color> units of <color=green>";
496  description += bpRef->productType().name();
497  description += "</color>.";
498  } break;
500  description += "Upon completion, this job will increase the Material Efficiency of the ";
501  description += "<color=yellow>";
502  description += bpRef->itemName();
503  description += "</color> from <color=red>";
504  description += std::to_string(bpRef->mLevel());
505  description += "</color> to <color=green>";
506  description += std::to_string(bpRef->mLevel() + args.runs);
507  description += "</color>.";
508  } break;
510  description += "Upon completion, this job will increase the Production Efficiency of the ";
511  description += "<color=yellow>";
512  description += bpRef->itemName();
513  description += "</color> from <color=red>";
514  description += std::to_string(bpRef->pLevel());
515  description += "</color> to <color=green>";
516  description += std::to_string(bpRef->pLevel() + args.runs);
517  description += "</color>.";
518  } break;
520  description += "<color=green>";
521  description += std::to_string(args.runs);
522  description += "</color> Copies of the ";
523  description += "<color=yellow>";
524  description += bpRef->itemName();
525  description += "</color> will be made.";
526  } break;
528  // this isnt implemented yet, so not sure what to do here
529  description += "";
530  } break;
532  // this isnt implemented yet, so not sure what to do here
533  description += "";
534  } break;
535  // others are not used (or shouldnt be)
536  }
537 
538  if (args.isCorpJob) {
539  description += "<br><br>This ";
540  description += sRamMthd.GetActivityName(args.activityID);
541  description += " job was installed by ";
542  description += call.client->GetName();
543  description += " on ";
544  description += currentDateTime();
545  description += " CST.";
546  }
547 
548  // random sayings/quotes here? sure, why not? lol
549  if (IsEven(MakeRandomInt(0, 10)))
550  description += "<br><br><br>And a good time shall be had by all!";
551  //description += "Don't wish it was easier; instead, wish you were better.";
552  //description += "Endure and survive.";
553  //description += "Fly safe.";
554 
555  // should this be important?
556  uint32 eventID = CalendarDB::SaveSystemEvent(args.isCorpJob?call.client->GetCorporationID():call.client->GetCharacterID(),
557  stDataMgr.GetOwnerID(locationID),
558  beginTime + rsp.productionTime * EvE::Time::Second,
559  Calendar::AutoEvent::RAMJob, title, description);
560 
561  FactoryDB::SetJobEventID(jobID, eventID);
562 
563  //force calendar reload (if corp job, update all online members, also)
564  if (args.isCorpJob) {
565  sEntityList.CorpNotify(call.client->GetCorporationID(), Notify::Types::FactoryJob, "OnReloadCalendar", "charid", new PyTuple(0));
566  } else {
567  call.client->SendNotification("OnReloadCalendar", "charid", new PyTuple(0), false); // this is not sequenced
568  }
569  }
570 
571  // we may need a separate table for invention jobs to store it's specific data....
572 
573  // increment statistic counter
574  sStatMgr.Increment(Stat::ramJobs);
575  return nullptr;
576 }
577 
578 PyResult RamProxyService::Handle_CompleteJob(PyCallArgs &call) {
579  /*
580  * 23:35:54 [ManufDump] RamProxyService::Handle_CompleteJob() - size 3
581  * 23:35:54 [ManufDump] Call Arguments:
582  * 23:35:54 [ManufDump] Tuple: 3 elements
583  * 23:35:54 [ManufDump] [ 0] List: 3 elements
584  * 23:35:54 [ManufDump] [ 0] [ 0] List: 2 elements
585  * 23:35:54 [ManufDump] [ 0] [ 0] [ 0] Integer: 60014140 invLocation (station, ship, starbase)
586  * 23:35:54 [ManufDump] [ 0] [ 0] [ 1] Integer: 15 invLocationGroup
587  * 23:35:54 [ManufDump] [ 0] [ 1] List: Empty path [eve.session.locationid, eve.session.charid, const.flagHangar]
588  * 23:35:54 [ManufDump] [ 0] [ 2] List: 1 elements
589  * 23:35:54 [ManufDump] [ 0] [ 2] [ 0] Integer: 60014140 containerID (actual itemID of location bp is in)
590  * 23:35:54 [ManufDump] [ 1] Integer: 2 jobID
591  * 23:35:54 [ManufDump] [ 2] Boolean: false cancel
592  */
593  _log(MANUF__DUMP, "RamProxyService::Handle_CompleteJob() - size %u", call.tuple->size() );
594  call.Dump(MANUF__DUMP);
595 
596  Call_CompleteJob args;
597  if (!args.Decode(&call.tuple)) {
598  codelog(SERVICE__ERROR, "%s: Failed to decode arguments.", GetName());
599  return nullptr;
600  }
601 
603  if (!FactoryDB::GetJobProperties(args.jobID, data))
604  throw UserError ("RamCompletionNoSuchJob");
605 
606  sRamMthd.VerifyCompleteJob(args, data, call.client);
607 
608  // does an aborted job return the installed item immediately or after time expiry?
610 
611  // return item
612  InventoryItemRef installedItem = sItemFactory.GetItem(data.itemID);
613  if (installedItem.get() == nullptr)
614  return nullptr;
615  installedItem->Move(args.containerID, data.outputFlag, true);
616 
617  // return materials which weren't consumed
618  std::vector<EvERam::RequiredItem> reqItems;
619  sDataMgr.GetRamReturns(installedItem->typeID(), data.activity, reqItems);
620  for (auto cur : reqItems) {
621  // what about items where damage < 1.0? (there are some...)
622  uint32 quantity = (cur.quantity * data.jobRuns * (1 - cur.damagePerJob));
623  if (quantity == 0)
624  quantity = 1;
625 
626  ItemData idata(cur.typeID, data.ownerID, locTemp, flagNone, quantity);
627  InventoryItemRef iRef = sItemFactory.SpawnItem( idata );
628  if (iRef.get() != nullptr)
629  iRef->Move(args.containerID, data.outputFlag, true);
630  }
631 
632  if (args.cancel) {
633  // what needs to be done to cancel a job?
634 
635  // if job event in calendar, set to deleted for canceled job.
637  } else {
638  BlueprintRef bpRef = BlueprintRef::StaticCast( installedItem );
639  switch(data.activity) {
641  ItemData idata(bpRef->productTypeID(), data.ownerID, locTemp, flagFactoryOutput, (bpRef->productType().portionSize() * data.jobRuns));
642  InventoryItemRef iRef = sItemFactory.SpawnItem( idata );
643  if (iRef.get() != nullptr)
644  iRef->Move(args.containerID, data.outputFlag, true);
645  } break;
647  bpRef->UpdatePLevel(data.jobRuns);
648  } break;
650  bpRef->UpdateMLevel(data.jobRuns);
651  } break;
653  ItemData idata(installedItem->typeID(), data.ownerID, locTemp, flagFactoryOutput);
654  EvERam::bpData bpdata = EvERam::bpData();
655  bpdata.copy = true;
656  bpdata.runs = data.licensedRuns;
657  bpdata.mLevel = bpRef->mLevel();
658  bpdata.pLevel = bpRef->pLevel();
659  while (data.jobRuns) {
660  BlueprintRef copy = Blueprint::Spawn(idata, bpdata);
661  //new bpc not showing in player hangar....
662  if (copy.get() != nullptr)
663  copy->Move(args.containerID, data.outputFlag, true);
664  --data.jobRuns;
665  }
666  } break;
669  /* base invention data...
670  *
671  * required items:
672  * bpc of t1 item (will consume 1 run)
673  * 2 types of datacores (all consumed)
674  * T3 only: ancient relic (wrecked, malfunction, intact)
675  *
676  * optional items:
677  * decryptor (consumed)
678  * these modify chance, me, pe, runs on output bpc
679  * usually not worth it on modules, ammo
680  *
681  * on success: (see modifiers below)
682  * T2: ship and rig bpc have 1 run, others are up to 10 (based on copy runs)
683  * T3: runs depend on relic (wrecked - 3, malfunction - 10, intact - 20)
684  * me -2, pe -4
685  *
686  */
687 
688  /* invention base chances (old)
689  * 0.2 bc/bs/hulk
690  * 0.25 cruiser/indy/mackinaw
691  * 0.3 frig/desy/freighter/skiff
692  * 0.4 modules/ammo
693  */
694 
695  /* invention base chances (new)
696  * 0.18 freighter
697  * 0.22 bs, {wrecked relic(RE)}
698  * 0.26 cru, bc, barge, indy
699  * 0.30 frig, dessy, {malfunction relic(RE)}
700  * 0.34 module, rig, ammo, {intact relic(RE)}
701  */
702 
719  PyDict* dict = new PyDict();
720  if (1) {
721  dict->SetItemString("messageLabel", new PyString("UI/ScienceAndIndustry/ScienceAndIndustryWindow/RamInventionJobSucceeded"));
722  dict->SetItemString("jobCompletedSuccessfully", new PyBool(true));
723  dict->SetItemString("outputME", new PyInt(0));
724  dict->SetItemString("outputPE", new PyInt(0));
725  dict->SetItemString("outputRuns", new PyInt(0));
726  dict->SetItemString("outputTypeID", new PyInt(0));
727  dict->SetItemString("outputItemID", new PyInt(0));
728  } else {
729  dict->SetItemString("messageLabel", new PyString("UI/ScienceAndIndustry/ScienceAndIndustryWindow/RamInventionJobFailed"));
730  dict->SetItemString("jobCompletedSuccessfully", new PyBool(false));
731  }
732 
733  /* invention result outcomes: (proposed in phoebe)
734  *
735  * success:
736  * execeptional - ME +2, PE +3
737  * great - ME +1, PE +2
738  * good - PE +1
739  * standard - no modifier
740  *
741  * failure:
742  * standard - return 50% datacores
743  * poor - return 25% datacores
744  * terrible - return 10% datacores
745  * critical - return no datacores
746  *
747  */
748 
749  // not sure the semantics on this...its' on both succeed and fail
750  // this *could* be the part about your skills
751  /*
752 (251331, `This job was so easy you feel you could do it again in your sleep.`)
753 (251332, `You have a good feeling this job is perfectly suited to someone of your talents.`)
754 (251333, `Completing this job was fairly comfortable for you and didn't tax your talents too much.`)
755 (251334, `You're happy with your success, for succeeding this job was far from certain.`)
756 (251335, `You succeeded, but you have a nagging feeling that you can't count on it every time.`)
757 (251336, `Despite valiant efforts you failed the job with success at your fingertips.`)
758 (251338, `You've got a good feel for this job, even if nothing of value came out of it this time.`)
759 (251339, `Although you have a firm understanding of the basics of this job you were never close to a solution.`)
760 (251340, `This is far from an impossible job, but one that might require a few tries before succeeding.`)
761 (251341, `This job requires lot of diligence and hard work on your part if you want to succeed.`)
762 (251342, `You feel a bit out of your league, succeeding this job requires a fair bit of luck.`)
763 (251343, `You never saw the light, this job is almost impossible for you to complete.`)
764 
765 {'FullPath': u'UI/ScienceAndIndustry/Invention', 'messageID': 251331, 'label': u'InventionResultSuccessCouldDoInSleep'}(u'This job was so easy you feel you could do it again in your sleep.', None, None)
766 {'FullPath': u'UI/ScienceAndIndustry/Invention', 'messageID': 251332, 'label': u'InventionResultSuccessSuitedToYourTalets'}(u'You have a good feeling this job is perfectly suited to someone of your talents.', None, None)
767 {'FullPath': u'UI/ScienceAndIndustry/Invention', 'messageID': 251333, 'label': u'InventionResultSuccessDidntTaxTooMuch'}(u"Completing this job was fairly comfortable for you and didn't tax your talents too much.", None, None)
768 {'FullPath': u'UI/ScienceAndIndustry/Invention', 'messageID': 251334, 'label': u'InventionResultSuccessFarFromCertain'}(u"You're happy with your success, for succeeding this job was far from certain.", None, None)
769 {'FullPath': u'UI/ScienceAndIndustry/Invention', 'messageID': 251335, 'label': u'InventionResultSuccessCantCountOnIt'}(u"You succeeded, but you have a nagging feeling that you can't count on it every time.", None, None)
770 {'FullPath': u'UI/ScienceAndIndustry/Invention', 'messageID': 251336, 'label': u'InventionResultFailedSuccessAtFingertips'}(u'Despite valiant efforts you failed the job with success at your fingertips.', None, None)
771 {'FullPath': u'UI/ScienceAndIndustry/Invention', 'messageID': 251338, 'label': u'InventionResultFailedFeltGoodAboutIt'}(u"You've got a good feel for this job, even if nothing of value came out of it this time.", None, None)
772 {'FullPath': u'UI/ScienceAndIndustry/Invention', 'messageID': 251339, 'label': u'InventionResultFailedNeverClose'}(u'Although you have a firm understanding of the basics of this job you were never close to a solution.', None, None)
773 {'FullPath': u'UI/ScienceAndIndustry/Invention', 'messageID': 251340, 'label': u'InventionResultFailedRequiresAFewTries'}(u'This is far from an impossible job, but one that might require a few tries before succeeding.', None, None)
774 {'FullPath': u'UI/ScienceAndIndustry/Invention', 'messageID': 251341, 'label': u'InventionResultFailedRequiresHardWork'}(u'This job requires lot of diligence and hard work on your part if you want to succeed.', None, None)
775 {'FullPath': u'UI/ScienceAndIndustry/Invention', 'messageID': 251342, 'label': u'InventionResultFailedRequiresLuck'}(u'You feel a bit out of your league, succeeding this job requires a fair bit of luck.', None, None)
776 {'FullPath': u'UI/ScienceAndIndustry/Invention', 'messageID': 251343, 'label': u'InventionResultFailedImpossible'}(u'You never saw the light, this job is almost impossible for you to complete.', None, None)
777 {
778 */
779  /* {'messageKey': 'ProductionFailure', 'dataID': 17875197, 'suppressable': False, 'bodyID': 256420, 'messageType': 'hint', 'urlAudio': '', 'urlIcon': '', 'titleID': 256419, 'messageID': 3741}
780  * {'messageKey': 'ProductionDuplicationFailure', 'dataID': 17875202, 'suppressable': False, 'bodyID': 256422, 'messageType': 'info', 'urlAudio': '', 'urlIcon': '', 'titleID': 256421, 'messageID': 3739}
781  * {'messageKey': 'ProductionInventionFailure', 'dataID': 17875207, 'suppressable': False, 'bodyID': 256424, 'messageType': 'info', 'urlAudio': '', 'urlIcon': '', 'titleID': 256423, 'messageID': 3740}
782  *
783  * {'FullPath': u'UI/Messages', 'messageID': 256419, 'label': u'ProductionFailureTitle'}(u'Production Job Will Fail', None, None)
784  * {'FullPath': u'UI/Messages', 'messageID': 256420, 'label': u'ProductionFailureBody'}(u'There is no chance of that action succeeding.', None, None)
785  * {'FullPath': u'UI/Messages', 'messageID': 256421, 'label': u'ProductionDuplicationFailureTitle'}(u'Production Job Will Fail', None, None)
786  * {'FullPath': u'UI/Messages', 'messageID': 256422, 'label': u'ProductionDuplicationFailureBody'}(u'"{[item]type.name}" can not be duplicated, there is no chance of it succeeding.', None, {u'{[item]type.name}': {'conditionalValues': [], 'variableType': 2, 'propertyName': 'name', 'args': 0, 'kwargs': {}, 'variableName': 'type'}})
787  * {'FullPath': u'UI/Messages', 'messageID': 256423, 'label': u'ProductionInventionFailureTitle'}(u'Production Job Will Fail', None, None)
788  * {'FullPath': u'UI/Messages', 'messageID': 256424, 'label': u'ProductionInventionFailureBody'}(u'"{[item]type.name}" can not be invented, there is no chance of it succeeding.', None, {u'{[item]type.name}': {'conditionalValues': [], 'variableType': 2, 'propertyName': 'name', 'args': 0, 'kwargs': {}, 'variableName': 'type'}})
789  *
790  */
791  /*
792  if hasattr(result, 'message'):
793  eve.Message(result.message.msg, result.message.args)
794  */
795  PyDict* msg = new PyDict();
796  msg->SetItemString("msg", new PyInt(0));
797  msg->SetItemString("args", new PyInt(0));
798  dict->SetItemString("message", msg);
799  return dict;
800  } break;
801 
802  /* unsupported */
804  //bpRef->type().chanceOfRE();
805 
806  /* depreciated */
809  default: {
810  _log(MANUF__WARNING, "Activity %u is currently unsupported.", data.activity);
811  throw UserError ("RamActivityInvalid");
812  } break;
813  }
814  }
815 
819  // there is more to this. also could be not needed, as it checks for 'none'
820  // result.message.msg = "event";
821  // result.message.args = ??
822  return PyStatic.NewNone();
823 }
824 
825 PyResult RamProxyService::Handle_UpdateAssemblyLineConfigurations(PyCallArgs &call) {
826  _log(MANUF__DUMP, "RamProxyService::Handle_UpdateAssemblyLineConfigurations() - size %u", call.tuple->size() );
827  call.Dump(MANUF__DUMP);
828 
829  //RamConfigAssemblyLinesAccessDenied
830  //RamConfigAssemblyLinesInsuficientAccess
831 
832  return nullptr;
833 }
#define sConfig
A macro for easier access to the singleton.
Dispatcher *const m_dispatch
static PyRep * AssemblyLinesSelectCorporation(const uint32 corporationID)
Definition: FactoryDB.cpp:319
#define sStatMgr
Definition: StatisticMgr.h:68
void SendNotification(const PyAddress &dest, EVENotificationStream &noti, bool seq=true)
Definition: Client.cpp:2245
uint32 GetSystemID() const
Definition: Client.h:152
#define _log(type, fmt,...)
Definition: logsys.h:124
#define stDataMgr
Python string.
Definition: PyRep.h:430
PyRep * GetItemString(const char *key) const
Obtains database entry based on given key string.
Definition: PyRep.cpp:702
int64 GetCorpRole() const
Definition: Client.h:129
Python's dictionary.
Definition: PyRep.h:719
std::map< std::string, PyRep * > byname
Definition: PyCallable.h:51
size_t size() const
Definition: PyRep.h:591
uint32 GetRegionID() const
Definition: Client.h:154
static PyRep * AssemblyLinesSelectPersonal(const uint32 charID)
Definition: FactoryDB.cpp:271
void SendInfoModalMsg(const char *fmt,...)
Definition: Client.cpp:2756
int32 GetCharacterID() const
Definition: Client.h:113
int32 GetCorporationID() const
Definition: Client.h:123
#define sEntityList
Definition: EntityList.h:208
static uint32 InstallJob(const uint32 ownerID, const uint32 installerID, Call_InstallJob &args, const int64 beginTime, const int64 endTime, const uint32 systemID)
Definition: FactoryDB.cpp:520
static uint32 IntegerValueU32(PyRep *pRep)
Definition: PyRep.cpp:134
static uint32 SaveSystemEvent(uint32 ownerID, uint32 creatorID, int64 startDateTime, uint8 autoEventType, std::string title, std::string description, bool important=false)
Definition: CalendarDB.cpp:143
CharacterRef GetChar() const
Definition: Client.h:164
Python tuple.
Definition: PyRep.h:567
const char * GetName() const
Definition: PyService.h:54
static PyRep * AssemblyLinesSelectPublic(const uint32 regionID)
Definition: FactoryDB.cpp:245
Advanced version of UserError that allows to send a full custom message.
Definition: PyExceptions.h:453
signed __int8 int8
Definition: eve-compat.h:45
static bool GetJobProperties(const uint32 jobID, EvERam::JobProperties &data)
Definition: FactoryDB.cpp:603
int32 GetAllianceID() const
Definition: Client.h:125
void Move(uint32 new_location=locTemp, EVEItemFlags flag=flagNone, bool notify=false)
* args
static PyRep * GetJobs2(const int32 ownerID, const bool completed)
Definition: FactoryDB.cpp:197
Python boolean.
Definition: PyRep.h:323
#define sLog
Evaluates to a NewLog instance.
Definition: LogNew.h:250
bool IsEven(int64 number)
Definition: misc.h:88
const std::string currentDateTime()
Definition: utils_time.cpp:125
#define codelog(type, fmt,...)
Definition: logsys.h:128
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)
Python integer.
Definition: PyRep.h:231
Dispatcher *const m_dispatch
#define PyStatic
Definition: PyRep.h:1209
X * get() const
Definition: RefPtr.h:213
EVEItemFlags outputFlag
Definition: EVE_RAM.h:102
const char * GetName() const
Definition: Client.h:94
Client *const client
Definition: PyCallable.h:49
Python object "ccp_exceptions.UserError".
Definition: PyExceptions.h:121
#define PyCallable_REG_CALL(c, m)
Definition: PyServiceCD.h:78
static RefPtr StaticCast(const RefPtr< Y > &oth)
Acts as static_cast from one RefPtr to another.
Definition: RefPtr.h:238
unsigned __int32 uint32
Definition: eve-compat.h:50
virtual ~RamProxyService()
static BlueprintRef Spawn(ItemData &data, EvERam::bpData &bdata)
Definition: Blueprint.cpp:71
double GetFileTimeNow()
Definition: utils_time.cpp:84
static PyRep * AssemblyLinesGet(const uint32 containerID)
Definition: FactoryDB.cpp:370
int64 MakeRandomInt(int64 low, int64 high)
Generates random integer from interval [low; high].
Definition: misc.cpp:109
static PyRep * AssemblyLinesSelectAlliance(const int32 allianceID)
Definition: FactoryDB.cpp:344
signed __int64 int64
Definition: eve-compat.h:51
PyRep * GetRAMSkills()
Definition: Character.cpp:595
static void SetJobEventID(const uint32 jobID, const uint32 eventID)
Definition: FactoryDB.cpp:88
void Dump(LogType type) const
Definition: PyCallable.cpp:81
#define sItemFactory
Definition: ItemFactory.h:165
static PyRep * AssemblyLinesSelectPrivate(const uint32 charID)
Definition: FactoryDB.cpp:295
#define sRamMthd
Definition: RamMethods.h:55
static void DeleteEvent(uint32 eventID)
Definition: CalendarDB.cpp:33
static int64 IntegerValue(PyRep *pRep)
Definition: PyRep.cpp:118
PyCallable_Make_InnerDispatcher(RamProxyService) RamProxyService
entityID heal the character with the entityID note giving you detailed ship status information gives a list of all dynamic entities and players and their destinyState in this bubble shows some current destiny variables save all items
uint16 typeID() const
uint8 categoryID() const
void SetItemString(const char *key, PyRep *value)
SetItemString adds or sets a database entry.
Definition: PyRep.h:812
PyTuple * tuple
Definition: PyCallable.h:50
#define sDataMgr
static bool CompleteJob(const uint32 jobID, const int8 completedStatus)
Definition: FactoryDB.cpp:636