dispatchers.cpp 34 KB

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