dispatchers.cpp 38 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345
  1. #include "dispatchers.h"
  2. #include <boost/format.hpp>
  3. #include <cctype>
  4. #include "boxes.h"
  5. #include "logging.h"
  6. #include "utils.h"
  7. Dispatch::Dispatch(Director &d, const char *called) : director{d} {
  8. aborted = false;
  9. if (called == nullptr)
  10. name = "Dispatch";
  11. else
  12. name = called;
  13. };
  14. Dispatch::~Dispatch(){};
  15. void Dispatch::deactivate(void) { notify(); }
  16. void Dispatch::to_server(const std::string &send) {
  17. director.to_server(send, name);
  18. }
  19. void Dispatch::to_client(const std::string &send) { director.to_client(send); }
  20. const std::string &Dispatch::get_prompt(void) {
  21. return director.current_prompt;
  22. }
  23. void Dispatch::activate_chain(std::shared_ptr<Dispatch> new_chain) {
  24. director.chain = new_chain;
  25. director.chain->activate();
  26. }
  27. void Dispatch::setNotify(notifyFunc nf) { notify_ = nf; }
  28. void Dispatch::notify(void) {
  29. if (director.post) {
  30. director.post(notify_);
  31. }
  32. }
  33. void Dispatch::chain_client_input(const std::string &input) {
  34. if (director.chain) {
  35. director.chain->chain_client_input(input);
  36. } else {
  37. client_input(input);
  38. }
  39. }
  40. void Dispatch::chain_server_line(const std::string &line,
  41. const std::string &raw_line) {
  42. if (director.chain) {
  43. director.chain->chain_server_line(line, raw_line);
  44. } else {
  45. server_line(line, raw_line);
  46. }
  47. }
  48. void Dispatch::chain_server_prompt(const std::string &prompt) {
  49. if (director.chain) {
  50. director.chain->chain_server_prompt(prompt);
  51. } else {
  52. server_prompt(prompt);
  53. }
  54. }
  55. void Dispatch::server_line(const std::string &line,
  56. const std::string &raw_line) {}
  57. void Dispatch::server_prompt(const std::string &prompt) {}
  58. void Dispatch::client_input(const std::string &input) {
  59. aborted = true;
  60. deactivate();
  61. }
  62. InputDispatch::InputDispatch(Director &d, const char *called)
  63. : Dispatch(d, called) {
  64. BUGZ_LOG(warning) << "InputDispatch()";
  65. numeric = false;
  66. if (called == nullptr) name = "Input";
  67. }
  68. InputDispatch::~InputDispatch() { BUGZ_LOG(warning) << "~InputDispatch()"; }
  69. void InputDispatch::activate(void) {
  70. BUGZ_LOG(warning) << "InputDispatch::activate() " << max_length;
  71. input.clear();
  72. to_client(prompt);
  73. }
  74. void InputDispatch::deactivate(void) { notify(); }
  75. void InputDispatch::server_line(const std::string &line,
  76. const std::string &raw_line) {
  77. if (line.empty()) return;
  78. std::string temp = repr(raw_line);
  79. BUGZ_LOG(fatal) << "Input:SL(" << temp << ")";
  80. if (startswith(line, "Command [TL=")) {
  81. return;
  82. }
  83. /*
  84. temp = raw_line;
  85. clean_string(temp);
  86. BUGZ_LOG(fatal) << "InputDispatch::server_line(" << temp << ")";
  87. */
  88. temp = prompt;
  89. ansi_clean(temp);
  90. size_t total = temp.length() + input.length();
  91. to_client("\x1b[0m"); // reset colors
  92. while (total > 0) {
  93. to_client("\b \b");
  94. --total;
  95. }
  96. // Lines line "\[[1A\[[1;36mphil \[[0;32mwarps into the sector.\[[0m"
  97. temp = raw_line;
  98. replace(temp, "\x1b[1A", "");
  99. // replace(temp, "\x1[2J", "");
  100. to_client(temp);
  101. to_client("\n\r");
  102. // Doesn't matter if it is one or two calls.
  103. temp = prompt;
  104. temp.append(input);
  105. to_client(temp);
  106. // to_client(prompt);
  107. // to_client(input);
  108. }
  109. // void InputDispatch::server_prompt(const std::string &prompt) {}
  110. void InputDispatch::client_input(const std::string &cinput) {
  111. // BUGZ_LOG(info) << "InputDispatch::client_input(" << cinput << ")";
  112. for (const char ch : cinput) {
  113. if (isprint(ch)) {
  114. // Ok!
  115. if (numeric) {
  116. // numbers only
  117. if (!isdigit(ch)) continue;
  118. }
  119. if (input.length() < max_length) {
  120. to_client(std::string(1, ch));
  121. input += ch;
  122. }
  123. } else if ((ch == '\b') || (ch == 0x7f)) {
  124. // Backspace or rubout
  125. if (input.length() > 0) {
  126. to_client("\b \b");
  127. input.erase(input.size() - 1);
  128. }
  129. } else if (ch == '\r') {
  130. // Ok, we're done!
  131. BUGZ_LOG(info) << "InputDispatch done: " << input;
  132. to_client("\x1b[0m\n\r");
  133. deactivate();
  134. } else if (ch == '\x1b') {
  135. aborted = true;
  136. to_client("\x1b[0m\n\r");
  137. deactivate();
  138. }
  139. }
  140. }
  141. /**
  142. * Menu Dispatch
  143. *
  144. * Two types of menus:
  145. * lazy: display the menu name and show prompt. ? shows menu.
  146. * non-lazy: displays menu + prompts.
  147. *
  148. *
  149. */
  150. MenuDispatch::MenuDispatch(Director &d, const char *called)
  151. : Dispatch(d, called) {
  152. BUGZ_LOG(warning) << "MenuDispatch()";
  153. if (called == nullptr) name = "Menu";
  154. }
  155. MenuDispatch::~MenuDispatch() { BUGZ_LOG(warning) << "~MenuDispatch()"; }
  156. void MenuDispatch::activate(void) {
  157. calculate_widths();
  158. input.clear();
  159. BUGZ_LOG(warning) << "MenuDispatch::activate() " << max_width << ", "
  160. << max_option_width;
  161. if (lazy)
  162. menubox();
  163. else
  164. help();
  165. to_client(menu_prompt);
  166. }
  167. void MenuDispatch::deactivate(void) { notify(); }
  168. void MenuDispatch::help(void) {
  169. size_t max = max_width;
  170. Boxes mbox(max, 1, true);
  171. mbox.boxcolor = menu_box_color;
  172. if (lazy) {
  173. // just the menu
  174. mbox.textcolor = menu_options_color;
  175. to_client(mbox.top());
  176. for (auto const &menu_item : menu) {
  177. std::string text = " ";
  178. text.append(menu_item.first);
  179. text.append(" - ");
  180. text.append(menu_item.second);
  181. while (text.length() < max) text.append(1, ' ');
  182. to_client(mbox.row(text));
  183. }
  184. to_client(mbox.bottom());
  185. } else {
  186. // full menu
  187. mbox.textcolor = menu_text_color;
  188. to_client(mbox.top());
  189. std::string title = centered(max, menu_title);
  190. BUGZ_LOG(debug) << "help max=" << max << " [" << title << "]";
  191. to_client(mbox.row(title));
  192. to_client(mbox.middle());
  193. mbox.textcolor = menu_options_color;
  194. for (auto const &menu_item : menu) {
  195. std::string text = " ";
  196. text.append(menu_item.first);
  197. text.append(" - ");
  198. text.append(menu_item.second);
  199. while (text.length() < max) text.append(1, ' ');
  200. to_client(mbox.row(text));
  201. }
  202. to_client(mbox.bottom());
  203. }
  204. }
  205. std::string MenuDispatch::centered(int length, const std::string &s) {
  206. std::string text = s;
  207. size_t leftovers = length - text.length();
  208. int count = leftovers / 2;
  209. if (count > 0) {
  210. text.insert(0, count, ' ');
  211. text.append(count, ' ');
  212. }
  213. if (leftovers % 1 == 1) text.append(1, ' ');
  214. return text;
  215. }
  216. void MenuDispatch::menubox(void) {
  217. // just the menu box
  218. std::string title = centered(max_width, menu_title);
  219. /*
  220. int leftovers = max - menu_title.length();
  221. while (leftovers > 2) {
  222. title.insert(0, 1, " ");
  223. title.append(1, " ");
  224. leftovers -= 2;
  225. };
  226. if (leftovers == 1)
  227. title.append(1, " ");
  228. */
  229. BUGZ_LOG(debug) << "menubox max=" << max_width << " [" << title << "]";
  230. auto abox =
  231. Boxes::alert(title, menu_box_color, menu_text_color, max_width, 1, true);
  232. for (auto line : abox) {
  233. to_client(line);
  234. }
  235. }
  236. void MenuDispatch::calculate_widths(void) {
  237. max_width = menu_title.length() + 2;
  238. max_option_width = 0;
  239. for (auto key : menu) {
  240. size_t menu_line_length =
  241. 1 + key.first.length() + 3 + key.second.length() + 1;
  242. if (menu_line_length > max_width) max_width = menu_line_length;
  243. if (key.first.length() > max_option_width)
  244. max_option_width = key.first.length();
  245. }
  246. instant = max_option_width == 1;
  247. }
  248. void MenuDispatch::server_line(const std::string &line,
  249. const std::string &raw_line) {
  250. // TODO:
  251. // Clear prompt, display raw server line, restore prompt.
  252. if (line.empty()) return;
  253. std::string temp = repr(raw_line);
  254. BUGZ_LOG(fatal) << "Input:SL(" << temp << ")";
  255. if (startswith(line, "Command [TL=")) {
  256. return;
  257. }
  258. /*
  259. temp = raw_line;
  260. clean_string(temp);
  261. BUGZ_LOG(fatal) << "InputDispatch::server_line(" << temp << ")";
  262. */
  263. temp = menu_prompt;
  264. ansi_clean(temp);
  265. size_t total = temp.length() + input.length();
  266. to_client("\x1b[0m"); // reset colors
  267. while (total > 0) {
  268. to_client("\b \b");
  269. --total;
  270. }
  271. // Lines line "\[[1A\[[1;36mphil \[[0;32mwarps into the sector.\[[0m"
  272. temp = raw_line;
  273. replace(temp, "\x1b[1A", "");
  274. // replace(temp, "\x1[2J", "");
  275. to_client(temp);
  276. to_client("\n\r");
  277. // Doesn't matter if it is one or two calls.
  278. temp = menu_prompt;
  279. temp.append(input);
  280. to_client(temp);
  281. // to_client(prompt);
  282. // to_client(input);
  283. }
  284. void MenuDispatch::client_input(const std::string &cinput) {
  285. for (auto const ch : cinput) {
  286. // not likely that we'd have more then one,
  287. // but deal with it correctly.
  288. if (ch == '\r') {
  289. // enter
  290. if (instant) return;
  291. for (auto const &mnu : menu) {
  292. if (mnu.first == input) {
  293. to_client("\x1b[0m\n\r");
  294. deactivate();
  295. return;
  296. }
  297. }
  298. // input wasn't found ?
  299. while (input.length() > 0) {
  300. to_client("\b \b");
  301. input.erase(input.length() - 1);
  302. }
  303. return; // don't continue ...
  304. }
  305. if (ch == '\x1b') {
  306. // [ESC] - erase the input string
  307. while (input.length() > 0) {
  308. to_client("\b \b");
  309. input.erase(input.length() - 1);
  310. }
  311. aborted = true;
  312. // Exit - allow escape from menu
  313. deactivate();
  314. return;
  315. }
  316. if (ch == '\b') {
  317. if (input.length() > 0) {
  318. to_client("\b \b");
  319. input.erase(input.length() - 1);
  320. }
  321. }
  322. if (ch == '?') {
  323. to_client(cinput); // display what they entered.
  324. to_client("\x1b[0m\n\r");
  325. help();
  326. to_client(menu_prompt);
  327. return;
  328. }
  329. if (isprint(ch)) {
  330. char c = ch;
  331. if (!case_sensitive) c = toupper(ch);
  332. // ok, it's a printable character
  333. if (input.length() < max_option_width) {
  334. // ok, there's room.
  335. to_client(std::string(1, c));
  336. input.append(1, c);
  337. }
  338. if (instant) {
  339. for (auto const &mnu : menu) {
  340. if (mnu.first == input) {
  341. to_client("\x1b[0m\n\r");
  342. deactivate();
  343. return;
  344. }
  345. }
  346. // Erase last character, wasn't an option.
  347. to_client("\b \b");
  348. input.erase(input.length() - 1);
  349. }
  350. }
  351. }
  352. }
  353. CIMDispatch::CIMDispatch(Director &d, const char *called)
  354. : Dispatch(d, called) {
  355. if (called == nullptr) name = "CIM";
  356. }
  357. void CIMDispatch::activate(void) { count = 0; }
  358. void CIMDispatch::deactivate(void) { notify(); }
  359. void CIMDispatch::server_line(const std::string &line,
  360. const std::string &raw_line) {
  361. if (!((line.empty() || startswith(line, ": ") ||
  362. startswith(line, "Command [")))) {
  363. count++;
  364. if (count % 100 == 0) {
  365. std::string message = str(boost::format("\r%1%") % count);
  366. to_client(message);
  367. }
  368. }
  369. if (line == ": ENDINTERROG") {
  370. std::string message = str(boost::format("\r%1%\n\r") % count);
  371. to_client(message);
  372. deactivate();
  373. }
  374. }
  375. MoveDispatch::MoveDispatch(Director &d, const char *called)
  376. : Dispatch(d, called) {
  377. BUGZ_LOG(warning) << "MoveDispatch()";
  378. use_express = false;
  379. if (called == nullptr) name = "Move";
  380. }
  381. MoveDispatch::~MoveDispatch() { BUGZ_LOG(warning) << "~MoveDispatch()"; }
  382. // sector_type move_to;
  383. void MoveDispatch::activate(void) {
  384. starting = director.current_sector;
  385. BUGZ_LOG(fatal) << "MoveDispatch::activate()";
  386. BUGZ_LOG(warning) << "Moving from " << starting << " to " << move_to;
  387. if (starting == move_to) {
  388. success = true;
  389. deactivate();
  390. return;
  391. }
  392. /*
  393. 1 = Issue "I"
  394. 2 = Density Scan
  395. 3 = Move
  396. 4 = Autopilot
  397. 5 = Density Scan
  398. */
  399. // Start with density scan -- unless express
  400. warp_lane.clear();
  401. warp_pos = 0;
  402. if (use_express) {
  403. std::string command = str(boost::format("M%1%\r") % move_to);
  404. to_server(command);
  405. state = 3;
  406. } else {
  407. to_server("S"); // because we might not have a scanner
  408. state = 1;
  409. }
  410. // build final string to match against
  411. at_destination = "Auto Warping to sector ";
  412. at_destination.append(std::to_string(move_to));
  413. }
  414. void MoveDispatch::deactivate(void) {
  415. BUGZ_LOG(fatal) << "MoveDispatch::deactivate() " << success;
  416. notify();
  417. }
  418. // optional here
  419. void MoveDispatch::server_line(const std::string &line,
  420. const std::string &raw_line) {
  421. if (!line.empty()) BUGZ_LOG(fatal) << state << " : " << line;
  422. if (state == 1) {
  423. if (endswith(line, "Relative Density Scan")) {
  424. state = 2;
  425. }
  426. if (line == "You don't have a long range scanner.") {
  427. // Ok, we'll have to express move our way there then.
  428. use_express = true;
  429. // state = 2;
  430. std::string command = str(boost::format("M%1%\r") % move_to);
  431. to_server(command);
  432. state = 3;
  433. }
  434. }
  435. // SL: [###### DANGER! You have marked sector 740 to be avoided!]
  436. if ((state != 2) && (state != 5)) {
  437. // hide the density scan part
  438. std::string temp = raw_line;
  439. if (line == "<Auto Pilot Engaging>") {
  440. // trim out the stray cursor home
  441. replace(temp, "\x1b[H", "");
  442. }
  443. // Replace progress bar with something else.
  444. if (startswith(temp, "\x1b[1;33m\xb3"))
  445. temp = "\x1b[1A\x1b[1;33m** SLOW MOVE **";
  446. /*
  447. if (in(temp, "\xb3")) {
  448. std::string debug = repr(temp);
  449. BUGZ_LOG(fatal) << "MOVE LINE: " << debug;
  450. }
  451. */
  452. // Don't output raw_lines that move the cursor up 3.
  453. if (!in(temp, "\x1b[3A")) {
  454. temp.append("\n\r");
  455. to_client(temp);
  456. }
  457. }
  458. if (state == 3) {
  459. if (line == "You are already in that sector!") {
  460. success = true;
  461. deactivate();
  462. return;
  463. }
  464. if (line == "You don't have a long range scanner.") {
  465. use_express = true;
  466. }
  467. if (line == "That Warp Lane is not adjacent.") {
  468. // ok! Parse out the path that we need to take...
  469. }
  470. // [611 > 612 > 577 > 543 > 162 > 947 > 185 > 720 > 894 > 3 > 1]
  471. // multiple lines possible here? Yes.
  472. // [344 > 23328 > 2981 > 10465 > 14016 > 8979 > 1916 > 18734 > 5477 > 131 >
  473. // 27464 >] watch for <Move> it contains >
  474. // if ((line != "<Move>") && in(line, " > ")) {
  475. if ((line != "<Move>") && (in(line, " > ") || !warp_lane.empty())) {
  476. bool more = false;
  477. std::string work = line;
  478. if (endswith(work, " >")) {
  479. more = true;
  480. work = work.substr(0, work.length() - 2);
  481. }
  482. replace(work, " > ", " ");
  483. replace(work, "(", "");
  484. replace(work, ")", "");
  485. auto warps = split(work);
  486. for (auto const &w : warps) {
  487. BUGZ_LOG(fatal) << "lane: " << w;
  488. warp_lane.push_back(stoi(w));
  489. }
  490. if (!more) state = 4;
  491. }
  492. }
  493. if (state == 4) {
  494. if (line == at_destination) {
  495. // [Auto Warping to sector 1]
  496. state = 6;
  497. }
  498. }
  499. }
  500. /*
  501. bool MoveDispatch::density_clear(density d) { // int sector, int density) {
  502. */
  503. /*
  504. http://wiki.classictw.com/index.php?title=Gypsy%27s_Big_Dummy%27s_Guide_to_TradeWars_Text#Trader_Information
  505. Density Readings:
  506. 0 = Empty Sector or Ferrengi Dreadanought
  507. 1 = Marker Beacon
  508. 2 = Limpet Type 2 Tracking Mine
  509. 5 = Fighter (per Fighter)
  510. 10 = Armid Type 1 Mine
  511. 21 = Navigation Hazard (Per 1 Percent)
  512. 21 = Destroyed Ship (Due to 1 Percent Nav-Haz)
  513. 38 = Unmanned Ship
  514. 40 = Manned Ship, Alien or Ferrengi Assault Trader
  515. 50 = Destroyed Starport (After 25 Percent Nav-Haz Clears)
  516. 100 = Starport or Ferrengi Battle Cruiser
  517. 210 = Destroyed Planet (Due to 10 Percent Nav-Haz)
  518. 462 = Federation Starship under Admiral Nelson
  519. 489 = Federation Starship under Captain Zyrain
  520. 500 = Planet
  521. 512 = Federation Starship under Admiral Clausewitz
  522. 575 = Destroyed Port (Before 25% Nav-Haz Clears)
  523. */
  524. /*
  525. if (d.sector == 0) return false;
  526. switch (d.density) {
  527. case 0:
  528. case 1:
  529. case 100:
  530. case 101:
  531. return true;
  532. }
  533. // special case for sector 1:
  534. if ((d.sector == 1) && (d.density == 601)) return true;
  535. return false;
  536. } */
  537. void MoveDispatch::server_prompt(const std::string &prompt) {
  538. BUGZ_LOG(fatal) << "server_prompt: " << state << " SP: [" << prompt << "]";
  539. // SL: [###### DANGER! You have marked sector 740 to be avoided!]
  540. // SP: [Do you really want to warp there? (Y/N) ]
  541. if (state == 1) {
  542. if (prompt == "Select (H)olo Scan or (D)ensity Scan or (Q)uit? [D] ") {
  543. to_server("D");
  544. }
  545. } else if (state == 2) {
  546. if (at_command_prompt(prompt)) {
  547. // Ok, density is done
  548. // BUG: If the sector is adjacent, this doesn't check density!
  549. density d = director.galaxy.dscan.find(move_to);
  550. if (d.sector == move_to) {
  551. // Yes! we found the sector in the scan!
  552. if (!density_clear(d.sector, d.density, d.navhaz)) {
  553. BUGZ_LOG(fatal) << "Failed density check on single move.";
  554. why_failed = str(boost::format("Sector %1% has density %2%.") %
  555. move_to % d.density);
  556. success = false;
  557. deactivate();
  558. return;
  559. }
  560. }
  561. std::string command = str(boost::format("M%1%\r") % move_to);
  562. to_server(command);
  563. state = 3;
  564. }
  565. } else if (state == 3) {
  566. if (prompt == "Clear Avoids? ") {
  567. to_server("N");
  568. why_failed = "No path / check avoids.";
  569. success = false;
  570. deactivate();
  571. }
  572. if (at_command_prompt(prompt)) {
  573. // this happens when the sector is adjcent to current_sector.
  574. // Or it happens when the ship is FUBAR.
  575. // NO!!!!
  576. if (director.current_sector == move_to) {
  577. BUGZ_LOG(fatal) << "Are we there yet? / Adjacent sector.";
  578. success = true;
  579. deactivate();
  580. } else {
  581. // Nope! Wrong!
  582. }
  583. return;
  584. }
  585. if (prompt == "Engage the Autopilot? (Y/N/Single step/Express) [Y] ") {
  586. to_server("E");
  587. return;
  588. }
  589. } else if (state == 4) {
  590. if (prompt == "Engage the Autopilot? (Y/N/Single step/Express) [Y] ") {
  591. if (use_express) {
  592. BUGZ_LOG(fatal) << "Using Express";
  593. to_server("E");
  594. } else {
  595. int to_check = warp_lane[warp_pos + 1];
  596. // check density scan
  597. density d = director.galaxy.dscan.find(to_check);
  598. /*
  599. int density =
  600. director.galaxy.meta["density"][to_check]["density"].as<int>();
  601. */
  602. if (density_clear(d.sector, d.density,
  603. d.navhaz)) { // to_check, density)) {
  604. to_server("S");
  605. ++warp_pos;
  606. } else {
  607. to_server("N");
  608. BUGZ_LOG(fatal) << "density_clear(" << to_check << ") : false";
  609. why_failed = str(boost::format("Sector %1% has density %2%.") %
  610. to_check % d.density);
  611. success = false;
  612. deactivate();
  613. }
  614. }
  615. }
  616. if (prompt == "Stop in this sector (Y,N,E,I,R,S,D,P,?) (?=Help) [N] ? ") {
  617. state = 5;
  618. // BUT? Do we have a scanner?
  619. if (use_express) {
  620. to_server("E");
  621. } else
  622. to_server("SD");
  623. return;
  624. }
  625. } else if (state == 5) {
  626. if (prompt == "Engage Express mode? (Y/N) [N] ") {
  627. to_server("Y");
  628. return;
  629. }
  630. // finished scan
  631. if (prompt == "Stop in this sector (Y,N,E,I,R,S,D,P,?) (?=Help) [N] ? ") {
  632. int to_check = warp_lane[warp_pos + 1];
  633. density d = director.galaxy.dscan.find(to_check);
  634. if (density_clear(d.sector, d.density, d.navhaz)) {
  635. to_server("N");
  636. ++warp_pos;
  637. state = 4;
  638. } else {
  639. to_server("Y");
  640. BUGZ_LOG(fatal) << "Stopped by density: " << to_check;
  641. why_failed = str(boost::format("Sector %1% has density %2%.") %
  642. to_check % d.density);
  643. success = 0;
  644. deactivate();
  645. }
  646. }
  647. if (at_command_prompt(prompt)) {
  648. if (director.current_sector == move_to) {
  649. success = 1;
  650. } else
  651. success = 0;
  652. deactivate();
  653. return;
  654. }
  655. } else if (state == 6) {
  656. if (at_command_prompt(prompt)) {
  657. // We're done!
  658. success = 1;
  659. deactivate();
  660. }
  661. }
  662. }
  663. void MoveDispatch::client_input(const std::string &input) {
  664. // This exits us out quickly -- should I stop at a better spot?
  665. // As in answer "Y" to Stop in this sector?
  666. success = 0;
  667. aborted = true;
  668. deactivate();
  669. }
  670. TraderDispatch::TraderDispatch(Director &d, const char *called)
  671. : Dispatch(d, called) {
  672. BUGZ_LOG(fatal) << "TraderDispatch()";
  673. state = 0;
  674. success = false;
  675. if (called == nullptr) name = "Trader";
  676. director.galaxy.meta["help"]["stop_percent"] =
  677. "ScriptTrader stop trading if below this percent.";
  678. director.galaxy.meta["help"]["trade_end_empty"] =
  679. "ScriptTrader end trading with empty holds? Y/N";
  680. };
  681. TraderDispatch::~TraderDispatch() { BUGZ_LOG(fatal) << "~TraderDispatch()"; }
  682. void TraderDispatch::activate(void) {
  683. // ok, lookup port1 port2
  684. BUGZ_LOG(fatal) << "TraderDispatch::activate " << port[0] << " & " << port[1];
  685. auto port_info = director.galaxy.ports.find(port[0]);
  686. int port0_type = port_info->second.type;
  687. port_buysell[0] = get_buysell(port0_type);
  688. // Special case - we just want to buy resources
  689. if (port[1] != 0) {
  690. port_info = director.galaxy.ports.find(port[1]);
  691. int port1_type = port_info->second.type;
  692. port_buysell[1] = get_buysell(port1_type);
  693. BUGZ_LOG(fatal) << port0_type << " and " << port1_type;
  694. } else {
  695. BUGZ_LOG(fatal) << "Just buy from " << port[0];
  696. }
  697. /*
  698. 1 = Info
  699. */
  700. // Ok, what do we do first here?
  701. // I - Info
  702. state = 1;
  703. percent = 5.0;
  704. to_server("I");
  705. if (director.galaxy.config.contains("stop_percent")) {
  706. stop_percent = director.galaxy.config["stop_percent"].get<int>();
  707. } else {
  708. stop_percent = 25;
  709. director.galaxy.config["stop_percent"] = stop_percent;
  710. }
  711. if (director.galaxy.config.contains("trade_end_empty")) {
  712. std::string tee = director.galaxy.config["trade_end_empty"];
  713. if ((toupper(tee[0]) == 'Y') || (toupper(tee[0]) == 'T')) {
  714. trade_end_empty = true;
  715. } else {
  716. trade_end_empty = false;
  717. // director.galaxy.config["trade_end_empty"] = "N";
  718. }
  719. } else {
  720. trade_end_empty = true;
  721. director.galaxy.config["trade_end_empty"] = "Y";
  722. }
  723. }
  724. void TraderDispatch::deactivate(void) { notify(); }
  725. void TraderDispatch::server_line(const std::string &line,
  726. const std::string &raw_line) {
  727. // FUTURE: powering up weapons check
  728. // Show what's going on...
  729. if (state > 1) {
  730. std::string temp = raw_line;
  731. // replace progress bar with something else
  732. if (startswith(temp, "\x1b[1;33m\xb3"))
  733. temp = "\x1b[1A\x1b[1;33m** SLOW MOVE **";
  734. // don't output lines that move cursor up 3.
  735. // if (!in(temp, "\x1b[3A")) {
  736. temp.append("\n\r");
  737. to_client(temp);
  738. // }
  739. }
  740. if (line == "Docking...") {
  741. last_offer = 0;
  742. final_offer = 0;
  743. initial_offer = 0;
  744. try_again = false;
  745. }
  746. static std::set<std::string> success_lines = {
  747. "If only more honest traders would port here, we'll take them though.",
  748. "You will put me out of business, I'll take your offer.",
  749. "FINE, we'll take them, just leave!",
  750. "Agreed, and a pleasure doing business with you!",
  751. "You are a rogue! We'll take them anyway.",
  752. "You insult my intelligence, but we'll buy them anyway.",
  753. "Very well, we'll take that offer.",
  754. "You drive a hard bargain, but we'll take them.",
  755. "Done, we'll take the lot.",
  756. "I hate haggling, they're all yours.",
  757. "You are robbing me, but we'll buy them anyway.",
  758. "SOLD! Come back anytime!",
  759. "Cheapskate. Here, take them and leave me alone.",
  760. "Very well, we'll buy them.",
  761. "You are a shrewd trader, they're all yours.",
  762. "I could have twice that much in the Androcan Empire, but they're yours.",
  763. "Oh well, maybe I can sell these to some other fool, we'll take them.",
  764. "I PAID more than that! But we'll sell them to you anyway.",
  765. "(Sigh) Very well, pay up and take them away.",
  766. "Agreed! We'll purchase them!"};
  767. if (success_lines.find(line) != success_lines.end()) {
  768. BUGZ_LOG(fatal) << "Success " << buying << " " << initial_offer << " : "
  769. << last_offer;
  770. // calculate % ?
  771. success = true;
  772. BUGZ_LOG(fatal) << "% " << (float)initial_offer / (float)last_offer * 100.0;
  773. BUGZ_LOG(fatal) << "meta trade setting: " << percent << " for "
  774. << active_port << " " << product;
  775. director.galaxy
  776. .meta["_trade"][std::to_string(active_port)][std::to_string(product)] =
  777. percent;
  778. // subtract total holds value from this port's amount
  779. auto port = director.galaxy.ports.find(active_port);
  780. if (port != director.galaxy.ports.end()) {
  781. // We've found the port!
  782. // product is the index
  783. port->second.amount[product] -=
  784. director.galaxy.meta["ship"]["holds"]["total"].get<int>();
  785. BUGZ_LOG(fatal) << "Port " << active_port << "," << product
  786. << " amount is now " << port->second.amount[product];
  787. }
  788. }
  789. // <P-Probe estimates your offer was
  790. if (startswith(line, "Agreed, ")) {
  791. last_offer = 0;
  792. final_offer = 0;
  793. std::string active_port_text = std::to_string(active_port);
  794. std::string product_text;
  795. product_text.assign(product, 1);
  796. if (director.galaxy.meta["_trade"][active_port_text].contains(
  797. product_text)) {
  798. percent = director.galaxy.meta["_trade"][active_port_text][product_text];
  799. percent += 1.0;
  800. BUGZ_LOG(fatal) << "Percent for " << active_port << " now " << percent;
  801. } else {
  802. BUGZ_LOG(fatal) << "using default for " << active_port;
  803. percent = 5.0; // check meta for past trades information
  804. }
  805. }
  806. if (startswith(line, "We'll buy them for ")) {
  807. // I need the initial offer!
  808. std::string offer = line.substr(19);
  809. replace(offer, ",", "");
  810. initial_offer = stoi(offer);
  811. BUGZ_LOG(fatal) << "Buying, initial: " << initial_offer;
  812. buying = true; // Port is buying, we are selling.
  813. }
  814. if (startswith(line, "We'll sell them for ")) {
  815. // I need the initial offer!
  816. std::string offer = line.substr(20);
  817. replace(offer, ",", "");
  818. initial_offer = stoi(offer);
  819. BUGZ_LOG(fatal) << "Selling, initial: " << initial_offer;
  820. buying = false; // Port is selling, we are buying.
  821. }
  822. // SL: [Our final offer is 1,263 credits.]
  823. if (startswith(line, "Our final offer is ")) {
  824. // Well snap!
  825. std::string offer = line.substr(19);
  826. replace(offer, ",", "");
  827. final_offer = stoi(offer);
  828. BUGZ_LOG(fatal) << "Final offer: " << final_offer;
  829. }
  830. if (line == "We're not interested.") {
  831. // well rats.
  832. BUGZ_LOG(fatal) << "We're not interested => meta trade setting: " << percent
  833. << " for " << active_port << " " << product;
  834. std::string active_port_text = std::to_string(active_port);
  835. director.galaxy.meta["_trade"][active_port_text][std::to_string(product)] =
  836. percent;
  837. try_again = true;
  838. }
  839. // SL: [You have 16,767 credits and 0 empty cargo holds.]
  840. // trade accepted. if not 0 empty cargo holds -- we failed!
  841. // SL: [<P-Probe estimates your offer was 91.83% of best price>]
  842. // SL: [You have 4,046 credits and 0 empty cargo holds.]
  843. // this shows up at the initial docking of the port.
  844. if (startswith(line, "You have ")) {
  845. if (initial_offer != 0) {
  846. // Ok, the offer was possibly accepted.
  847. int success;
  848. if (buying)
  849. success = director.galaxy.meta["ship"]["holds"]["total"];
  850. else
  851. success = 0;
  852. std::string text = std::to_string(success);
  853. text.append(" empty cargo holds.");
  854. if (endswith(line, text)) {
  855. BUGZ_LOG(fatal) << "Trade SUCCESS!";
  856. // record this action somewhere in meta.
  857. } else {
  858. BUGZ_LOG(fatal) << "Trade FAIL";
  859. }
  860. }
  861. }
  862. }
  863. void TraderDispatch::server_prompt(const std::string &prompt) {
  864. // FUTURE: check for "Surrender/Attack"
  865. if (at_command_prompt(prompt)) {
  866. if (state == 1) {
  867. // Ok, decision time!
  868. if (director.galaxy.meta["ship"]["holds"].contains("c")) {
  869. // holds contain colonists
  870. why_failed = "Holds contain colonists.";
  871. success = false;
  872. aborted = true;
  873. to_client("ScriptTrader FAIL: holds contain colonists.");
  874. deactivate();
  875. return;
  876. }
  877. int total;
  878. if (director.galaxy.meta["ship"]["holds"].contains("total")) {
  879. total = json_int(director.galaxy.meta["ship"]["holds"]["total"]);
  880. int empty = 0;
  881. if (director.galaxy.meta["ship"]["holds"].contains("empty")) {
  882. empty = json_int(director.galaxy.meta["ship"]["holds"]["empty"]);
  883. }
  884. if (total != empty) {
  885. BUGZ_LOG(fatal) << "FAIL: " << total << " total holds, " << empty
  886. << " holds empty.";
  887. to_client("ScriptTrader FAIL: holds are not empty.");
  888. why_failed = "Holds are not empty.";
  889. success = false;
  890. aborted = true;
  891. deactivate();
  892. return;
  893. }
  894. }
  895. // Right now, I stop trading, if we can't fill/buy max holds.
  896. // We now look to see if you have 75 * total_holds credits.
  897. int credits = json_int(director.galaxy.meta["credits"]);
  898. if (credits < (total * 75)) {
  899. BUGZ_LOG(fatal) << "FAIL: credits < " << total * 75 << " have "
  900. << credits;
  901. std::string message =
  902. str(boost::format("ScriptTrader FAIl: You need at least %1% "
  903. "credits to trade.\n\r") %
  904. (total * 75));
  905. to_client(message);
  906. why_failed = "Low credits.";
  907. success = false;
  908. aborted = true;
  909. deactivate();
  910. return;
  911. }
  912. // Which port to trade with first? examine trades
  913. BUGZ_LOG(fatal) << "trades: " << trades;
  914. BUGZ_LOG(fatal) << "port0:" << text_from_buysell(port_buysell[0]);
  915. if (port[1] != 0)
  916. BUGZ_LOG(fatal) << "port1:" << text_from_buysell(port_buysell[1]);
  917. // Ok, I might still need this (so I know what port to start with)
  918. // which is selling?
  919. // must set active port!
  920. bool all_holds_empty = false;
  921. active_port = 0;
  922. // check the ship and holds here. (MAYBE)
  923. int holds = json_int(director.galaxy.meta["ship"]["holds"]["total"]);
  924. if (director.galaxy.meta["ship"]["holds"].contains("empty")) {
  925. if (holds == json_int(director.galaxy.meta["ship"]["holds"]["empty"]))
  926. all_holds_empty = true;
  927. }
  928. if (port[1] == 0) {
  929. active_port = port[0];
  930. } else {
  931. if (!all_holds_empty) {
  932. for (int x = 0; x < 3; ++x) {
  933. std::string foe_text;
  934. foe_text.assign(1, foe[x]);
  935. if (director.galaxy.meta["ship"]["holds"].contains(foe_text)) {
  936. if (!port_buysell[0].foe[x]) {
  937. active_port = port[0];
  938. break;
  939. }
  940. if (!port_buysell[1].foe[x]) {
  941. active_port = port[1];
  942. }
  943. }
  944. }
  945. if (active_port == 0) {
  946. why_failed = "No ports will buy what we have in our holds.";
  947. success = false;
  948. to_client(
  949. "I don't see any ports that are buying what we have in our "
  950. "holds.\n\r");
  951. deactivate();
  952. return;
  953. };
  954. } else {
  955. // all holds empty, find selling port
  956. for (int x = 0; x < 3; ++x) {
  957. if (trades.foe[x]) {
  958. // TRUE = BUY, so FALSE = sell
  959. if (!port_buysell[0].foe[x]) {
  960. active_port = port[0];
  961. break;
  962. }
  963. if (!port_buysell[1].foe[x]) {
  964. active_port = port[1];
  965. break;
  966. }
  967. }
  968. }
  969. }
  970. }
  971. state = 2;
  972. if (director.current_sector == active_port) {
  973. // begin state 3
  974. state = 3;
  975. to_client("Trading...\n\r");
  976. to_server("PT");
  977. return;
  978. } else {
  979. // initiate move
  980. std::string move = std::to_string(active_port);
  981. to_client("Moving...\n\r");
  982. move.append("\r");
  983. to_server(move);
  984. return;
  985. }
  986. }
  987. if (state == 2) {
  988. if (director.current_sector == active_port) {
  989. // We're here
  990. state = 3;
  991. to_client("Trading...\n\r");
  992. to_server("PT");
  993. return;
  994. } else {
  995. // we failed to move to where we wanted to go?!
  996. BUGZ_LOG(fatal) << "Expecting: " << active_port << " but got "
  997. << director.current_sector;
  998. deactivate();
  999. return;
  1000. }
  1001. }
  1002. }
  1003. if (state == 3) {
  1004. if (startswith(prompt, "How many holds of ") && endswith(prompt, "]? ")) {
  1005. char selling = tolower(prompt[18]);
  1006. for (int x = 0; x < 3; ++x) {
  1007. if (foe[x] == selling) product = x;
  1008. }
  1009. if (in(prompt, " to sell ")) {
  1010. // always sell everything
  1011. to_server("\r");
  1012. return;
  1013. }
  1014. if (in(prompt, " to buy ")) {
  1015. bool buy_ok = true;
  1016. std::string max =
  1017. str(boost::format("[%1%]") %
  1018. json_int(director.galaxy.meta["ship"]["holds"]["total"]));
  1019. if (!in(prompt, max)) {
  1020. buy_ok = false;
  1021. }
  1022. if (trade_end_empty) {
  1023. // Ok, we want to end with empty holds...
  1024. int other_port;
  1025. if (active_port == port[0])
  1026. other_port = port[1];
  1027. else
  1028. other_port = port[0];
  1029. BUGZ_LOG(fatal) << "Is " << other_port << " burnt? (trade_end_empty)";
  1030. // Is target port burnt?
  1031. auto pos = director.galaxy.ports.find(other_port);
  1032. bool burnt = false;
  1033. if (pos != director.galaxy.ports.end()) {
  1034. // We'll find the port. Really.
  1035. if (!pos->second.unknown()) {
  1036. // port isn't unknown, so check to see if it's burnt
  1037. for (int x = 0; x < 3; ++x) {
  1038. if (trades.foe[x]) {
  1039. BUGZ_LOG(fatal)
  1040. << other_port << " " << x << " is in trades...";
  1041. BUGZ_LOG(fatal)
  1042. << "amount[" << x << "] = " << pos->second.amount[x];
  1043. if (pos->second.percent[x] < stop_percent) burnt = true;
  1044. if (director.galaxy.meta["ship"]["holds"].contains("total")) {
  1045. BUGZ_LOG(fatal)
  1046. << pos->second.amount[x] << " : "
  1047. << json_int(
  1048. director.galaxy.meta["ship"]["holds"]["total"]);
  1049. if (pos->second.amount[x] <
  1050. json_int(
  1051. director.galaxy.meta["ship"]["holds"]["total"])) {
  1052. BUGZ_LOG(fatal) << "Other port " << other_port
  1053. << " is burnt " << x << " burnt = true";
  1054. burnt = true;
  1055. }
  1056. }
  1057. }
  1058. }
  1059. }
  1060. }
  1061. if (burnt) {
  1062. BUGZ_LOG(fatal) << "Port burnt, buy_ok = false";
  1063. buy_ok = false;
  1064. }
  1065. }
  1066. // Ok, what are they selling?
  1067. // char selling = tolower(prompt[18]);
  1068. BUGZ_LOG(fatal) << "Selling: " << selling;
  1069. if (!buy_ok) {
  1070. // no!
  1071. to_server("0\r");
  1072. } else
  1073. for (int x = 0; x < 3; ++x) {
  1074. // if (foe[x] == selling) {
  1075. // We found the item ... is it something that we're trading?
  1076. if (foe[x] == selling) {
  1077. if (trades.foe[x]) {
  1078. // Yes!
  1079. to_server("\r");
  1080. product = x;
  1081. } else {
  1082. // No!
  1083. to_server("0\r");
  1084. }
  1085. }
  1086. }
  1087. // }
  1088. }
  1089. }
  1090. if (startswith(prompt, "Your offer [") && endswith(prompt, " ? ")) {
  1091. // Ok, things get weird here. We also need to look for final offer.
  1092. if (last_offer != 0) percent -= 1.0;
  1093. if (buying)
  1094. last_offer = (int)(initial_offer * (100 + percent) / 100.0);
  1095. else
  1096. last_offer = (int)(initial_offer * (100 - percent) / 100.0);
  1097. BUGZ_LOG(fatal) << "Offer: " << buying << " offer " << last_offer << " % "
  1098. << percent;
  1099. std::string text = std::to_string(last_offer);
  1100. text.append("\r");
  1101. to_server(text);
  1102. }
  1103. if (at_command_prompt(prompt)) {
  1104. // we're done trading...
  1105. // do we carry on, or stop?
  1106. // 1.) CHECK TURNS // need turn tracking
  1107. // 2.) PORTS BURNT?
  1108. if (try_again) {
  1109. state = 3;
  1110. to_client("Trading... Take 2!\n\r");
  1111. to_server("PT");
  1112. return;
  1113. }
  1114. if (active_port == port[0]) {
  1115. active_port = port[1];
  1116. } else
  1117. active_port = port[0];
  1118. if (active_port == 0) {
  1119. deactivate();
  1120. return;
  1121. }
  1122. // Is target port burnt?
  1123. auto pos = director.galaxy.ports.find(active_port);
  1124. bool burnt = false;
  1125. if (pos != director.galaxy.ports.end()) {
  1126. // We'll find the port. Really.
  1127. if (!pos->second.unknown()) {
  1128. // port isn't unknown, check to see if burnt
  1129. for (int x = 0; x < 3; ++x) {
  1130. if (trades.foe[x]) {
  1131. BUGZ_LOG(fatal) << x << " % " << (int)pos->second.percent[x]
  1132. << " " << stop_percent;
  1133. if (pos->second.percent[x] < stop_percent) burnt = true;
  1134. if (director.galaxy.meta["ship"]["holds"].contains("total"))
  1135. if (pos->second.amount[x] <
  1136. json_int(director.galaxy.meta["ship"]["holds"]["total"]))
  1137. burnt = true;
  1138. }
  1139. }
  1140. }
  1141. }
  1142. if (burnt) {
  1143. to_client("Ports burnt.\n\r");
  1144. deactivate();
  1145. return;
  1146. }
  1147. std::string move = std::to_string(active_port);
  1148. to_client("Moving...\n\r");
  1149. move.append("\r");
  1150. to_server(move);
  1151. state = 2;
  1152. }
  1153. }
  1154. }
  1155. void TraderDispatch::client_input(const std::string &cinput) {
  1156. aborted = true;
  1157. deactivate();
  1158. };
  1159. /*
  1160. * CoreDispatch: This is an example class that does dispatch.
  1161. * Copy this and make changes from there...
  1162. */
  1163. CoreDispatch::CoreDispatch(Director &d, const char *called)
  1164. : Dispatch(d, called) {
  1165. BUGZ_LOG(warning) << "CoreDispatch()";
  1166. if (called == nullptr) name = "Core";
  1167. }
  1168. void CoreDispatch::activate(void) {
  1169. // save things, set things
  1170. }
  1171. void CoreDispatch::deactivate(void) {
  1172. // restore things
  1173. notify();
  1174. }
  1175. void CoreDispatch::server_line(const std::string &line,
  1176. const std::string &raw_line) {}
  1177. void CoreDispatch::server_prompt(const std::string &prompt) {}
  1178. void CoreDispatch::client_input(const std::string &input) {
  1179. BUGZ_LOG(warning) << "Got: " << input << " prompt=" << get_prompt();
  1180. aborted = true;
  1181. deactivate();
  1182. }