director.cpp 38 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310
  1. #include "director.h"
  2. #include <boost/format.hpp>
  3. #include <cctype>
  4. #include "ansicolor.h"
  5. #include "boxes.h"
  6. #include "galaxy.h"
  7. #include "logging.h"
  8. #include "scripts.h"
  9. #include "utils.h"
  10. Director::Director() {
  11. BUGZ_LOG(warning) << "Director::Director()";
  12. // active = false;
  13. game = 0; // not in a game
  14. galaxy.reset();
  15. // do everything proxy_deactivate does ...
  16. // proxy_deactivate();
  17. active = false;
  18. // reset everything back to good state
  19. talk_direct = true;
  20. show_client = true;
  21. count = 0;
  22. /*
  23. Setup StringFunc for SL_parser:
  24. Construct these once, rather then every single time we need them.
  25. */
  26. SF_cimline = [this](const std::string &s) { this->SL_cimline(s); };
  27. SF_sectorline = [this](const std::string &s) { this->SL_sectorline(s); };
  28. SF_portline = [this](const std::string &s) { this->SL_portline(s); };
  29. SF_warpline = [this](const std::string &s) { this->SL_warpline(s); };
  30. SF_infoline = [this](const std::string &s) { this->SL_infoline(s); };
  31. SF_densityline = [this](const std::string &s) { this->SL_densityline(s); };
  32. SF_computer_portline = [this](const std::string &s) {
  33. this->SL_computer_portline(s);
  34. };
  35. SF_planetline = [this](const std::string &s) { this->SL_planetline(s); };
  36. build_menu();
  37. }
  38. Director::~Director() { BUGZ_LOG(warning) << "Director::~Director()"; }
  39. void Director::client_input(const std::string &input) {
  40. // If we're already active, don't try to activate.
  41. if (chain) {
  42. chain->client_input(input);
  43. return;
  44. }
  45. if (active) {
  46. if (input == "Q" || input == "q")
  47. proxy_deactivate();
  48. return;
  49. } else if (input == "\x1b" || input == "~") {
  50. std::string &prompt = current_prompt;
  51. BUGZ_LOG(trace) << "ACTIVATE prompt shows: [" << prompt << "]";
  52. if (prompt == "Selection (? for menu): ") {
  53. to_client("\n\rThere's not much we can do here. Activate in-game at a "
  54. "Command prompt.\n\r");
  55. to_client(current_raw_prompt);
  56. return;
  57. }
  58. // easter-eggs:
  59. if (prompt == "Enter your choice: ") {
  60. ANSIColor c1(COLOR::CYAN, ATTR::BOLD);
  61. ANSIColor c2(COLOR::WHITE, ATTR::BOLD);
  62. to_client(std::string("\n\r") + c1() + "I'd choose " + c2() + "`T`" +
  63. c1() + ", but that's how I was coded.\n\r");
  64. // "\n\r\x1b[1;36mI'd choose \x1b[1;37m`T`\x1b[1;36m, but "
  65. // "that's how I was coded.\n\r");
  66. to_client(current_raw_prompt);
  67. return;
  68. }
  69. // easter-egg
  70. if (prompt == "[Pause]") {
  71. ANSIColor c1(COLOR::CYAN, ATTR::BOLD);
  72. to_client(std::string(" ") + c1() + "PAWS" + reset() + "\n\r");
  73. to_client(current_raw_prompt);
  74. return;
  75. }
  76. if (at_planet_prompt(prompt)) {
  77. // future: If activated at planet menu, activate the planet upgrade
  78. // script! (Maybe). If I can figure out what planet it is/and where.
  79. to_client("\n\r\x1b[0mFUTURE: Activate the planet upgrade script.\n\r");
  80. to_client(current_raw_prompt);
  81. return;
  82. }
  83. if (at_command_prompt(prompt)) {
  84. proxy_activate();
  85. return;
  86. }
  87. }
  88. // Ok...
  89. if (talk_direct) to_server(input);
  90. }
  91. void Director::server_line(const std::string &line,
  92. const std::string &raw_line) {
  93. // check for if we entered game/left game
  94. if (line.find("TradeWars Game Server ") != std::string::npos) {
  95. // Inject our proxy activation message
  96. to_client("\rTradeWars Proxy v2++ READY (~ or ESC to activate)\n\r");
  97. /*
  98. There's a delay here when I save the game data.
  99. I've moved it futher down. Hide it at a prompt, so it isn't so
  100. noticeable.
  101. */
  102. // reset "active game" -- we're at the TWGS main menu
  103. }
  104. if (line.find("Selection (? for menu): ") != std::string::npos) {
  105. char ch = line[line.length() - 1];
  106. if (ch >= 'A' && ch < 'Q') {
  107. if ((game) && (game != ch)) {
  108. galaxy.save();
  109. }
  110. game = ch;
  111. BUGZ_LOG(warning) << "GAME " << game << " activated!";
  112. // TODO: Load game data
  113. galaxy.game = game;
  114. galaxy.username = username;
  115. galaxy.load();
  116. // YAML loaded, set sensible default config values (if missing).
  117. if (!galaxy.config["display_lines"]) {
  118. galaxy.config["display_lines"] = 20;
  119. }
  120. galaxy.meta["help"]["display_lines"] =
  121. "Number of report lines to display";
  122. if (!galaxy.config["burnt_percent"]) {
  123. galaxy.config["burnt_percent"] = 40;
  124. }
  125. galaxy.meta["help"]["burnt_percent"] =
  126. "Don't display ports in report below this percent";
  127. }
  128. // not needed (handled by above Game Server check).
  129. if (ch == 'Q') {
  130. if (game) {
  131. // TODO: Save galaxy data
  132. galaxy.save();
  133. }
  134. game = 0;
  135. galaxy.reset();
  136. }
  137. }
  138. if (game) {
  139. // in-game parsing here.
  140. /*
  141. ____ _ _
  142. / ___| ___ _ ____ _____ _ __ | | (_)_ __ ___
  143. \___ \ / _ \ '__\ \ / / _ \ '__| | | | | '_ \ / _ \
  144. ___) | __/ | \ V / __/ | | |___| | | | | __/
  145. |____/ \___|_| \_/ \___|_| |_____|_|_| |_|\___|
  146. ____ _
  147. | _ \ __ _ _ __ ___(_)_ __ __ _
  148. | |_) / _` | '__/ __| | '_ \ / _` |
  149. | __/ (_| | | \__ \ | | | | (_| |
  150. |_| \__,_|_| |___/_|_| |_|\__, |
  151. |___/
  152. This is where all of the server lines are gleaned for all the
  153. information that we can get out of them.
  154. // When activating the computer
  155. SP: [Command [TL=00:00:00]:[926] (?=Help)? : ]
  156. Sector: 926
  157. CI: [c]
  158. SL: [Command [TL=00:00:00]:[926] (?=Help)? : C]
  159. SL: [<Computer>]
  160. SL: []
  161. SL: [<Computer activated>]
  162. SL: []
  163. SP: [Computer command [TL=00:00:00]:[926] (?=Help)? ]
  164. // deactivating the computer
  165. SL: [Computer command [TL=00:00:00]:[926] (?=Help)? Q]
  166. SL: []
  167. SL: [<Computer deactivated>]
  168. SL: []
  169. */
  170. if (line == "<Port>") {
  171. SL_parser = SF_portline;
  172. }
  173. if (line ==
  174. " Corporate Planet Scan "
  175. " " ||
  176. line ==
  177. " Personal Planet Scan "
  178. " ") {
  179. SL_parser = SF_planetline;
  180. }
  181. /*
  182. if (startswith(line, " Items Status Trading % of max OnBoard"))
  183. SL_parser = SF_portline;
  184. */
  185. if (endswith(line, "Relative Density Scan")) {
  186. galaxy.dscan.reset(current_sector);
  187. SL_parser = SF_densityline;
  188. }
  189. if (startswith(line, "Sector : ")) SL_parser = SF_sectorline;
  190. if (line == ": ") SL_parser = SF_cimline;
  191. if (line == "<Info>") SL_parser = SF_infoline;
  192. if (startswith(line, "What sector is the port in? [")) {
  193. // Computer Port Report
  194. // SL: [What sector is the port in? [611] 4]
  195. size_t pos = line.rfind(' ');
  196. computer_port_sector = stoi(line.substr(pos));
  197. SL_parser = SF_computer_portline;
  198. }
  199. }
  200. if (SL_parser) {
  201. SL_parser(line);
  202. }
  203. if (chain) {
  204. chain->server_line(line, raw_line);
  205. }
  206. }
  207. void Director::server_prompt(const std::string &prompt,
  208. const std::string &raw_prompt) {
  209. current_prompt = prompt;
  210. current_raw_prompt = raw_prompt;
  211. if (game) {
  212. if (prompt == "Selection (? for menu): ") {
  213. galaxy.save();
  214. game = 0;
  215. galaxy.reset();
  216. }
  217. // in-game parsing here.
  218. if (startswith(prompt, "Command [") && endswith(prompt, "] (?=Help)? : ")) {
  219. std::string sector_text;
  220. size_t before, after;
  221. before = prompt.find("]:[") + 3;
  222. after = prompt.find("] (?=Help)");
  223. sector_text = prompt.substr(before, after - before);
  224. current_sector = stoi(sector_text);
  225. BUGZ_LOG(info) << "current_sector = " << current_sector;
  226. }
  227. }
  228. /*
  229. if (emit_server_prompt)
  230. emit_server_prompt(prompt);
  231. */
  232. if (chain)
  233. chain->server_prompt(prompt);
  234. }
  235. void Director::build_menu(void) {
  236. main_menu = std::make_shared<MenuDispatch>(*this);
  237. MenuDispatch *md = static_cast<MenuDispatch *>(&(*main_menu));
  238. ANSIColor bcolor(COLOR::YELLOW, COLOR::BLUE, ATTR::BOLD);
  239. ANSIColor tcolor(COLOR::WHITE, COLOR::BLUE, ATTR::BOLD);
  240. ANSIColor mocolor(COLOR::CYAN, ATTR::BOLD);
  241. md->menu_box_color = bcolor(); // "\x1b[1;33;44m";
  242. md->menu_text_color = tcolor(); // "\x1b[1;37;44m";
  243. md->menu_title = "Proxy Menu";
  244. md->menu_options_color = mocolor(); // "\x1b[1;36;40m";
  245. ANSIColor by{1, 33};
  246. ANSIColor cyan{36};
  247. ANSIColor bg{1, 32};
  248. std::string prompt = by() + "M" + cyan() + "ain " + by() + "P" + cyan() +
  249. "roxy " + bg() + "=>" + reset() + " ";
  250. md->menu_prompt = prompt; // "Main Proxy => ";
  251. // "\x1b[0;31;40m\xdb\xb2\xb1\xb0 \x1b[31;40mRED "
  252. // "\x1b[32;40mGREEN\x1b[30;42m\xdb\xb2\xb1\xb0 \x1b[0m : ";
  253. md->lazy = true;
  254. md->menu = {{"C", "Configure"},
  255. {"D", "Display Report"},
  256. {"E", "Export Data/Save"},
  257. {"I", "Information"},
  258. {"P", "Port CIM"},
  259. {"W", "Warp CIM"},
  260. {"T", "Trading Report (same as D)"},
  261. {"S", "Scripts"},
  262. {"X", "eXit"}};
  263. md->setNotify([this]() { this->menu_choice(); });
  264. cim = std::make_shared<CIMDispatch>(*this);
  265. cim->setNotify([this]() { this->cim_done(); });
  266. }
  267. void Director::proxy_activate(void) {
  268. active = true; // yes, set keep-alive timer.
  269. to_server(" "); // start keep-alive timer.
  270. // set other values we need
  271. talk_direct = false;
  272. show_client = false;
  273. /*
  274. Wait a minute .. this might be confusing.
  275. Shouldn't I send them the current prompt?
  276. Just in case we abort in the middle of something?!?
  277. */
  278. old_prompt = current_prompt;
  279. old_raw_prompt = current_raw_prompt;
  280. to_client("\x1b[0m\n\r");
  281. /*
  282. ╔══════════════════════════════╗
  283. ║ TradeWars Proxy Active ║
  284. ╚══════════════════════════════╝
  285. -=>
  286. */
  287. Boxes box(30, 1, true);
  288. box.boxcolor = "\x1b[1;33;44m";
  289. box.textcolor = "\x1b[1;33;44m";
  290. to_client(box.top());
  291. std::string output = " TradeWars Proxy \x1b[5mActive\x1b[0;1;33;44m ";
  292. to_client(box.row(output));
  293. to_client(box.bottom());
  294. chain = main_menu;
  295. main_menu->activate();
  296. /*
  297. // Using InputDispatch -- and see have_input
  298. std::shared_ptr<Dispatch> readline = std::make_shared<InputDispatch>(*this);
  299. chain = readline;
  300. InputDispatch *id = static_cast<InputDispatch *>(&(*readline));
  301. id->prompt = "\x1b[0m\x1b[1;33;44m-=>\x1b[0m \x1b[1;37;44m";
  302. id->max_length = 15;
  303. id->setNotify([this]() { this->have_input(); });
  304. readline->activate();
  305. */
  306. }
  307. void Director::menu_choice(void) {
  308. MenuDispatch *md = dynamic_cast<MenuDispatch *>(&(*chain));
  309. if (md) {
  310. if (md->input.empty()) {
  311. to_client("Menu aborted.\n\r");
  312. proxy_deactivate();
  313. return;
  314. } else {
  315. switch (md->input[0]) {
  316. case 'C': // configure
  317. config_edit();
  318. return;
  319. break;
  320. case 'D':
  321. case 'T': // display trading report
  322. {
  323. auto pptv = galaxy.find_best_trades();
  324. std::string output;
  325. galaxy.sort_port_pair_type(pptv);
  326. int max_display = 20;
  327. if (galaxy.config["display_lines"])
  328. max_display = galaxy.config["display_lines"].as<int>();
  329. else
  330. galaxy.config["display_lines"] = max_display;
  331. if ((max_display <= 0) || (max_display > 255)) {
  332. max_display = 255;
  333. galaxy.config["display_lines"] = 255;
  334. }
  335. const int per_line = 5;
  336. int count = 0;
  337. int line = 0;
  338. std::string display_line;
  339. ANSIColor by{1, 33}; // yellow
  340. ANSIColor cyan{36}; // cyan
  341. ANSIColor bg{1, 32}; // bright green
  342. ANSIColor bb{1, 34}; // bright blue
  343. for (auto const &ppt : pptv) {
  344. output =
  345. str(boost::format("%1%%2$5d%3%:%4%%5$-5d%3%(%6%%7$d%3%) ") %
  346. by() % ppt.s1 % cyan() % bg() % ppt.s2 % bb() % ppt.type);
  347. display_line.append(output);
  348. ++count;
  349. if (count == per_line) {
  350. count = 0;
  351. display_line.append("\n\r");
  352. to_client(display_line);
  353. display_line.clear();
  354. ++line;
  355. }
  356. if (line == max_display) break;
  357. }
  358. if (count != 0) {
  359. display_line.append("\n\r");
  360. to_client(display_line);
  361. display_line.clear();
  362. }
  363. // We got < 5 lines, and max_display is > 5. Offer suggestion:
  364. if ((line < 5) && (max_display > 5)) {
  365. // suggestion:
  366. to_client(
  367. "HINT: For more lines, try reducing the burnt_percent?\n\r");
  368. }
  369. } break;
  370. case 'E': // Export Data/Save
  371. to_client("Saving...");
  372. galaxy.save();
  373. to_client("\rSaved....\n\r");
  374. break;
  375. case 'I': // Information
  376. information();
  377. break;
  378. case 'P': // Port CIM
  379. // Since we're adding/updating, we don't lose our
  380. // type 0 ports. Type 9 stays at 9.
  381. chain = cim;
  382. to_server("^RQ");
  383. {
  384. std::string text = str(boost::format("Port CIM Report (%1%)\n\r") %
  385. galaxy.ports.size());
  386. // to_client("Port CIM Report\n\r");
  387. to_client(text);
  388. }
  389. chain->activate();
  390. return;
  391. break;
  392. case 'W': // Warp CIM
  393. chain = cim;
  394. to_server("^IQ");
  395. {
  396. std::string text = str(boost::format("Warp CIM Report (%1%)\n\r") %
  397. galaxy.warps.size());
  398. // to_client("Warp CIM Report\n\r");
  399. to_client(text);
  400. }
  401. chain->activate();
  402. return;
  403. break;
  404. // case 'T': // Trading Report
  405. // break;
  406. case 'S': // Scripts
  407. {
  408. init_scripts_menu();
  409. chain = scripts_menu;
  410. chain->activate();
  411. return;
  412. } break;
  413. case 'X': // Exit
  414. proxy_deactivate();
  415. return;
  416. }
  417. /*
  418. std::string text = str(
  419. boost::format("Back from Menu [%1%] was selected.\n\r") %
  420. md->input);
  421. to_client(text);
  422. */
  423. md->activate();
  424. }
  425. }
  426. }
  427. MenuDispatch *Director::init_scripts_menu(void) {
  428. MenuDispatch *md;
  429. if (scripts_menu) {
  430. md = dynamic_cast<MenuDispatch *>(&(*scripts_menu));
  431. return md;
  432. } else {
  433. scripts_menu = std::make_shared<MenuDispatch>(*this);
  434. md = static_cast<MenuDispatch *>(&(*scripts_menu));
  435. md->menu_box_color = "\x1b[0;32;40m";
  436. md->menu_text_color = "\x1b[1;32;40m";
  437. md->menu_title = "Scripts Menu";
  438. md->menu_options_color = "\x1b[1;32;40m";
  439. md->lazy = false;
  440. ANSIColor by{1, 33};
  441. ANSIColor cyan{36};
  442. ANSIColor bg{1, 32};
  443. std::string prompt = by() + "S" + cyan() + "cript " + by() + "M" + cyan() +
  444. "enu " + bg() + "=>" + reset() + " ";
  445. md->menu_prompt = prompt; // " SCRIPT : ";
  446. md->menu = {{"!", "Terror"},
  447. {"T", "Trade"},
  448. {"S", "Safe Move"},
  449. {"C", "Closest Trade"},
  450. {"N", "Nearest Unexplored"},
  451. {"V", "Voyager Explorer"},
  452. {"E", "Explorer"},
  453. {"U", "Upgrade Planet Pants"},
  454. {"X", "Exit Scripts"}};
  455. md->setNotify([this]() { this->scripts_done(); });
  456. return md;
  457. }
  458. }
  459. void Director::scripts_done(void) {
  460. // Was script selected? If so, run it!
  461. // otherwise, back to the menu we go...
  462. MenuDispatch *md = dynamic_cast<MenuDispatch *>(&(*scripts_menu));
  463. if (md) {
  464. if (md->input.empty()) {
  465. to_client("Scripts aborted.\n\r");
  466. scripts_menu.reset();
  467. proxy_deactivate();
  468. return;
  469. } else {
  470. switch (md->input[0]) {
  471. case 'T': // Trade
  472. {
  473. script = std::make_shared<TraderDispatch>(*this);
  474. TraderDispatch *ts = static_cast<TraderDispatch *>(&((*script)));
  475. ts->setNotify([this]() { this->proxy_deactivate(); });
  476. // Locate best trades
  477. auto found = galaxy.find_trades(current_sector, false);
  478. if (found.empty()) {
  479. to_client(
  480. "No Trades found. Port burnt (CONFIG: lower burnt_percent?) "
  481. "or no ports around.\n\r");
  482. proxy_deactivate();
  483. return;
  484. }
  485. // sort first?
  486. galaxy.sort_port_pair_type(found);
  487. BUGZ_LOG(fatal) << "Found " << found.size() << " possible trade(s).";
  488. BUGZ_LOG(fatal) << "Best: " << found[0].s1 << "," << found[0].s2
  489. << " : " << found[0].type;
  490. // Set parameters
  491. ts->port[0] = found[0].s1;
  492. ts->port[1] = found[0].s2;
  493. ts->type = found[0].type;
  494. ts->trades = found[0].trades;
  495. chain = script;
  496. chain->activate();
  497. return;
  498. } break;
  499. case 'S': {
  500. script = std::make_shared<MoveDispatch>(*this);
  501. MoveDispatch *md = static_cast<MoveDispatch *>(&((*script)));
  502. md->setNotify([this]() { this->proxy_deactivate(); });
  503. md->move_to = 1;
  504. chain = script;
  505. chain->activate();
  506. return;
  507. } break;
  508. case '!': {
  509. script = std::make_shared<ScriptTerror>(*this);
  510. ScriptTerror *st = static_cast<ScriptTerror *>(&(*script));
  511. st->setNotify([this]() {
  512. script.reset();
  513. this->proxy_deactivate();
  514. });
  515. chain = script;
  516. chain->activate();
  517. return;
  518. } break;
  519. // }
  520. case 'C': {
  521. auto best = galaxy.find_closest(current_sector);
  522. if (best.type != 0) {
  523. std::string text =
  524. str(boost::format("Best/Closest: %1% with %2% & %3%\n\r") %
  525. best.type % best.s1 % best.s2);
  526. to_client(text);
  527. } else {
  528. to_client("I don't see any best trades.\n\r");
  529. }
  530. } break;
  531. case 'N': {
  532. sector_type s = galaxy.find_nearest_unexplored(current_sector);
  533. if (s != 0) {
  534. std::string text = str(boost::format("Sector: %1%\n\r") % s);
  535. to_client(text);
  536. } else {
  537. to_client("I don't see any unexplored.\n\r");
  538. }
  539. } break;
  540. case 'V': {
  541. script = std::make_shared<ScriptVoyager>(*this);
  542. ScriptVoyager *sv = static_cast<ScriptVoyager *>(&(*script));
  543. sv->setNotify([this]() {
  544. script.reset();
  545. this->proxy_deactivate();
  546. });
  547. chain = script;
  548. chain->activate();
  549. return;
  550. } break;
  551. case 'E': {
  552. script = std::make_shared<ScriptExplore>(*this);
  553. ScriptExplore *se = static_cast<ScriptExplore *>(&(*script));
  554. se->setNotify([this]() {
  555. script.reset();
  556. this->proxy_deactivate();
  557. });
  558. chain = script;
  559. chain->activate();
  560. return;
  561. } break;
  562. case 'Q':
  563. chain = main_menu;
  564. main_menu->activate();
  565. return;
  566. break;
  567. }
  568. }
  569. }
  570. proxy_activate();
  571. // And to end scripts, we do .. what exactly?
  572. // DEBUG: Ok, why does everything work OK if I reset the scripts_menu
  573. // here?? probably do want to destroy scripts here. ;)
  574. // scripts_menu.reset();
  575. // proxy_deactivate();
  576. }
  577. /**
  578. * @brief Setup Config Input
  579. *
  580. * @return DispatchInput*
  581. */
  582. InputDispatch *Director::init_config_input(void) {
  583. InputDispatch *id;
  584. if (config_input) {
  585. // Yes, it has been setup before.
  586. id = dynamic_cast<InputDispatch *>(&(*config_input));
  587. ANSIColor by{1, 33};
  588. ANSIColor cyan{36};
  589. ANSIColor bg{1, 32};
  590. std::string prompt =
  591. by() + "C" + cyan() + "onfig " + bg() + "=>" + reset() + " ";
  592. id->prompt = prompt; // "Config => ";
  593. id->numeric = true;
  594. id->max_length = 3;
  595. config_item.clear();
  596. return id;
  597. } else {
  598. // set it up
  599. config_input = std::make_shared<InputDispatch>(*this);
  600. id = static_cast<InputDispatch *>(&(*config_input));
  601. ANSIColor by{1, 33};
  602. ANSIColor cyan{36};
  603. ANSIColor bg{1, 32};
  604. std::string prompt =
  605. by() + "C" + cyan() + "onfig " + bg() + "=>" + reset() + " ";
  606. id->prompt = prompt; // "Config => ";
  607. id->numeric = true;
  608. id->max_length = 3;
  609. id->setNotify([this]() { this->config_have_input(); });
  610. config_item.clear();
  611. return id;
  612. }
  613. }
  614. void Director::config_edit(void) {
  615. // display current config
  616. std::string menu_box_color = "\x1b[1;33;44m";
  617. std::string menu_text_color = "\x1b[1;37;44m";
  618. auto abox = Boxes::alert(" Configuration: ", menu_box_color,
  619. menu_text_color, 20, 1, true);
  620. for (auto line : abox) {
  621. to_client(line);
  622. }
  623. // to_client("Configuration:\n\r");
  624. int item = 1;
  625. ANSIColor number(COLOR::CYAN);
  626. ANSIColor key(COLOR::GREEN, ATTR::BOLD);
  627. ANSIColor value(COLOR::BLUE, ATTR::BOLD);
  628. for (auto const &cfg : galaxy.config) {
  629. std::string output =
  630. str(boost::format("%1%%2$2d %3%%4$20s: %5%%6$s%7%\n\r") % number() %
  631. item % key() % cfg.first % value() % cfg.second % reset());
  632. to_client(output);
  633. ++item;
  634. }
  635. std::string message =
  636. number() + "Enter number to edit, " + key() + "blank to exit.\n\r";
  637. // to_client("Enter number to edit, blank to exit.\n\r");
  638. to_client(message);
  639. // setup call to config_input:
  640. InputDispatch *id = init_config_input();
  641. chain = config_input;
  642. id->activate();
  643. // to return to the menu:
  644. // MenuDispatch *md = dynamic_cast<MenuDispatch *>(&(*chain));
  645. // md->activate();
  646. }
  647. void Director::config_have_input(void) {
  648. InputDispatch *id = dynamic_cast<InputDispatch *>(&(*config_input));
  649. if (config_item.empty()) {
  650. // This is a config menu selection
  651. if (id->input.empty()) {
  652. // We're done here. Return to menu.
  653. chain = main_menu;
  654. MenuDispatch *md = dynamic_cast<MenuDispatch *>(&(*chain));
  655. md->activate();
  656. // destroy the input? yes.
  657. config_input.reset();
  658. return;
  659. } else {
  660. int item = sstoi(id->input);
  661. if ((item < 1) || (item > (int)galaxy.config.size())) {
  662. // selection out of range - redisplay config menu
  663. to_client("What? I didn't see that item.\n\r");
  664. config_edit();
  665. return;
  666. } else {
  667. int pos = 1;
  668. const YAML::Node &config = galaxy.config;
  669. for (auto const &c : config) {
  670. if (pos == item) {
  671. // got it!
  672. ANSIColor key(COLOR::GREEN, ATTR::BOLD);
  673. ANSIColor value(COLOR::BLUE, ATTR::BOLD);
  674. config_item = c.first.as<std::string>();
  675. std::string output =
  676. str(boost::format("%1%%2% : %3%%4%\n\r") % key() % config_item %
  677. value() % galaxy.meta["help"][config_item]);
  678. to_client(output);
  679. id->max_length = 30;
  680. id->numeric = false;
  681. ANSIColor by{1, 33};
  682. ANSIColor cyan{36};
  683. ANSIColor bg{1, 32};
  684. std::string prompt =
  685. by() + "C" + cyan() + "hange to " + bg() + "=>" + reset() + " ";
  686. id->prompt = prompt;
  687. id->activate();
  688. return;
  689. };
  690. ++pos;
  691. }
  692. to_client("What? I didn't find that item?\n\r");
  693. config_edit();
  694. return;
  695. }
  696. }
  697. } else {
  698. // This is a config item edit
  699. if (id->input.empty()) {
  700. to_client("No change.\n\r");
  701. config_edit();
  702. return;
  703. } else {
  704. BUGZ_LOG(fatal) << "Config EDIT: " << config_item << " = " << id->input;
  705. galaxy.config[config_item] = id->input;
  706. config_edit();
  707. return;
  708. }
  709. }
  710. }
  711. void Director::have_input(void) {
  712. ++count;
  713. InputDispatch *id = dynamic_cast<InputDispatch *>(&(*chain));
  714. if (id) {
  715. std::string output =
  716. str(boost::format("Your Input (%2%): [%1%]\n\r") % id->input % count);
  717. to_client("\x1b[0m");
  718. to_client(output);
  719. } else {
  720. proxy_deactivate();
  721. return;
  722. }
  723. if (count > 3) {
  724. proxy_deactivate();
  725. } else {
  726. chain->activate();
  727. }
  728. }
  729. void Director::cim_done(void) {
  730. BUGZ_LOG(info) << "CIM done";
  731. chain = main_menu;
  732. main_menu->activate();
  733. }
  734. void Director::information(void) {
  735. std::string output;
  736. to_client("I currently know the following:\n\r");
  737. output = str(
  738. boost::format("Ports: %1%, Sectors: %2%, Config: %3%, Meta: %4%\n\r") %
  739. galaxy.ports.size() % galaxy.warps.size() % galaxy.config.size() %
  740. galaxy.meta.size());
  741. to_client(output);
  742. }
  743. void Director::proxy_deactivate(void) {
  744. active = false;
  745. // reset everything back to good state
  746. talk_direct = true;
  747. show_client = true;
  748. chain.reset();
  749. to_client("\n\r");
  750. to_client(current_raw_prompt);
  751. }
  752. /*
  753. Server Line Parsing Routines
  754. */
  755. void Director::SL_cimline(const std::string &line) {
  756. if (line == ": ENDINTERROG") {
  757. SL_parser = nullptr;
  758. return;
  759. }
  760. if (line == ": ") {
  761. // do I need to do anything special here for this?
  762. // Maybe -- We would save special ports that don't show up
  763. // (StarDock/Special) before. We don't know (at this point) if this is
  764. // warps or ports.
  765. return;
  766. }
  767. if (line.empty()) {
  768. SL_parser = nullptr;
  769. return;
  770. }
  771. // parse cimline
  772. // size_t pos = line.find('%');
  773. // std::string work = line;
  774. // if (pos == line.npos) {
  775. if (in(line, "%")) {
  776. // portcim
  777. struct port p = parse_portcim(line);
  778. if (p.sector == 0)
  779. BUGZ_LOG(fatal) << "portcim: FAIL [" << line << "]";
  780. else
  781. BUGZ_LOG(trace) << "portcim: " << p;
  782. galaxy.add_port(p);
  783. } else {
  784. // warpcim
  785. // BUGZ_LOG(fatal) << "warpcim: [" << line << "]";
  786. auto warps = split(line);
  787. sector_warps sw;
  788. for (auto const &w : warps) {
  789. if (sw.sector == 0) {
  790. sw.sector = stoi(w);
  791. } else {
  792. sw.add(stoi(w));
  793. }
  794. }
  795. BUGZ_LOG(trace) << "warpcim: " << sw;
  796. galaxy.add_warp(sw);
  797. }
  798. }
  799. void Director::SL_thiefline(const std::string &line) {
  800. size_t pos = line.find("Suddenly you're Busted!");
  801. bool busted = pos != line.npos;
  802. if (busted) {
  803. BUGZ_LOG(fatal) << "set bust";
  804. SL_parser = nullptr;
  805. } else {
  806. pos = line.find("(You realize the guards saw you last time!)");
  807. if (pos != line.npos)
  808. SL_parser = nullptr;
  809. }
  810. // Are those the two ways to exit from this state?
  811. }
  812. void Director::SL_sectorline(const std::string &line) {
  813. BUGZ_LOG(fatal) << "sectorline: [" << line << "]";
  814. if (line.empty()) {
  815. SL_parser = nullptr;
  816. } else {
  817. /*
  818. sectorline: [Sector : 926 in The Federation.]
  819. sectorline: [Beacon : FedSpace, FedLaw Enforced]
  820. sectorline: [Ports : Stargate Alpha I, Class 9 (Special) (StarDock)]
  821. sectorline: [Traders : Civilian phil, w/ 30 ftrs,]
  822. sectorline: [ in Star Stomper (Sverdlov Merchant Cruiser)]
  823. sectorline: [Warps to Sector(s) : 70 - 441 - 575 - 600 - 629 - 711]
  824. sectorline: [Warps to Sector(s) : 70 - (475) - 569]
  825. What can we get from Traders : line? Can we get if they are hostile
  826. to us? We need to respond to "is powering up weapons" ... We can
  827. react faster then a person can!
  828. "phil is powering up weapons systems!"
  829. Also the auto-attack ones Ferrengi -- we need to auto-respond Retreat.
  830. */
  831. if (in(line, "Sector :")) {
  832. current_sector = stoi(line.substr(10));
  833. BUGZ_LOG(warning) << "SECTOR: " << current_sector;
  834. }
  835. if (in(line, "Ports :")) {
  836. std::string port_class;
  837. size_t pos = line.find(", Class ");
  838. if (pos != std::string::npos) {
  839. pos += 8;
  840. int class_ = stoi(line.substr(pos));
  841. BUGZ_LOG(fatal) << "PORT: " << class_;
  842. galaxy.add_port(current_sector, class_);
  843. }
  844. }
  845. if (in(line, "Warps to Sector(s) :")) {
  846. std::string temp = line.substr(20);
  847. replace(temp, " - ", " ");
  848. // unexplored sectors ()
  849. // Should I track these?
  850. replace(temp, "(", "");
  851. replace(temp, ")", "");
  852. sector_warps sw;
  853. auto warps = split(temp);
  854. sw.sector = current_sector;
  855. // what if there is only one warp?
  856. for (auto const &w : warps) {
  857. sw.add(stoi(w));
  858. }
  859. BUGZ_LOG(fatal) << "WARPS: " << sw;
  860. galaxy.add_warp(sw);
  861. }
  862. }
  863. }
  864. void Director::SL_densityline(const std::string &line) {
  865. BUGZ_LOG(fatal) << "densityline: [" << line << "]";
  866. if (line.empty()) {
  867. SL_parser = nullptr;
  868. return;
  869. }
  870. /*
  871. // Ensure this really is a density scan and not something else
  872. if (!in(line, "Sector") || !in(line, "Warps") || !in(line, "NavHaz") ||
  873. !in(line, "Anom")) {
  874. BUGZ_LOG(fatal) << "densityline: Invalid line.";
  875. SL_parser = nullptr;
  876. return;
  877. }
  878. if (not galaxy.meta["density"]) {
  879. galaxy.meta["density"] = YAML::Node();
  880. }
  881. */
  882. /*
  883. 0 1 2 3 4 5 6 7 8 9 10 11 12
  884. "Sector 55 ==> 0 Warps : 4 NavHaz : 0% Anom : No"
  885. "Sector ( 223) ==> 0 Warps : 3 NavHaz : 0% Anom : No"
  886. */
  887. if (in(line, "==>")) {
  888. std::string work = line;
  889. replace(work, ":", "");
  890. bool known = !in(work, "(");
  891. replace(work, "(", "");
  892. replace(work, ")", "");
  893. replace(work, "%", "");
  894. auto dense = split(work);
  895. // Parse our data
  896. sector_type sector = std::stoi(dense.at(1));
  897. uint16_t density = std::stoi(dense.at(3));
  898. uint16_t warps = std::stoi(dense.at(5));
  899. uint16_t navhaz = std::stoi(dense.at(7));
  900. bool anom = in(dense.at(9), "Yes");
  901. struct density d = {sector, density, warps, navhaz, anom, known};
  902. galaxy.dscan.add_scan(d);
  903. // Commit data
  904. BUGZ_LOG(warning) << "densityline: {sector=" << sector
  905. << " density=" << density << " warps=" << warps
  906. << " navhaz=" << navhaz << " anom=" << anom
  907. << " known=" << known << "}";
  908. /*
  909. if (galaxy.meta["density"][sector]) {
  910. galaxy.meta["density"][sector] = YAML::Node();
  911. }
  912. */
  913. galaxy.meta["density"][sector]["density"] = density;
  914. galaxy.meta["density"][sector]["warps"] = warps;
  915. galaxy.meta["density"][sector]["navhaz"] = navhaz;
  916. galaxy.meta["density"][sector]["anom"] = anom;
  917. galaxy.meta["density"][sector]["known"] = known;
  918. // Add a check to see if density is greater than 500
  919. // Add datetime stamp
  920. }
  921. }
  922. void Director::SL_portline(const std::string &line) {
  923. /*
  924. We take blank lines because we start at <Port>.
  925. Otherwise, we trigger on computer port requests.
  926. if (line.empty()) {
  927. SL_parser = nullptr;
  928. return;
  929. }
  930. */
  931. /*
  932. SL: [ Items Status Trading % of max OnBoard]
  933. SL: [ ----- ------ ------- -------- -------]
  934. SL: [Fuel Ore Buying 3000 100% 0]
  935. SL: [Organics Buying 3000 100% 0]
  936. SL: [Equipment Buying 3000 100% 0]
  937. SL: []
  938. SL: [Commerce report for: 03:51:56 PM Mon Oct 24, 2033 You can buy:]
  939. SL: [A Cargo holds : 650 credits / next hold 0]
  940. SL: [B Fighters : 233 credits per fighter 75]
  941. SL: [C Shield Points : 116 credits per point 100]
  942. SL: []
  943. */
  944. // BUGZ_LOG(info) << "portline : " << line;
  945. if (in(line, "%")) {
  946. // size_t pos = line.find('%');
  947. // if (pos != line.npos) {
  948. // Ok, this is a valid portline
  949. std::string work = line;
  950. replace(work, "Fuel Ore", "Fuel");
  951. auto parts = split(work);
  952. if (parts[0] == "Items")
  953. return;
  954. char c = tolower(parts[0][0]);
  955. int pos;
  956. char foe[4] = "foe";
  957. for (pos = 0; pos < 3; ++pos) {
  958. if (c == foe[pos])
  959. break;
  960. }
  961. int amount = stoi(parts[2]);
  962. int percent = stoi(parts[3]);
  963. // update port
  964. auto port = galaxy.ports.find(current_sector);
  965. if (port != galaxy.ports.end()) {
  966. port->second.amount[pos] = amount;
  967. port->second.percent[pos] = percent;
  968. }
  969. /*
  970. BUGZ_LOG(fatal) << "portline split:";
  971. for (auto const p : parts) {
  972. BUGZ_LOG(fatal) << p;
  973. }
  974. */
  975. // Here's the end:
  976. if (parts[0] == "Equipment")
  977. SL_parser = nullptr;
  978. // BUGZ_LOG(fatal) << "portline split : [" << parts << "]";
  979. }
  980. }
  981. void Director::SL_warpline(const std::string &line) {
  982. if (line.empty()) {
  983. SL_parser = nullptr;
  984. return;
  985. }
  986. // process warp line
  987. BUGZ_LOG(fatal) << "warpline: [" << line << "]";
  988. }
  989. void Director::SL_infoline(const std::string &line) {
  990. static int state;
  991. if (line == "<Info>") {
  992. state = 0;
  993. galaxy.meta["info"] = YAML::Node();
  994. }
  995. if (line.empty()) {
  996. ++state;
  997. if (state == 2) {
  998. SL_parser = nullptr;
  999. // clear out the existing ship data
  1000. galaxy.meta["ship"] = YAML::Node();
  1001. // process the parsed information in meta["info"]
  1002. if (galaxy.meta["info"]["Total Holds"]) {
  1003. std::string work = galaxy.meta["info"]["Total Holds"].as<std::string>();
  1004. replace(work, "Fuel Ore", "Fuel");
  1005. auto parts = split(work, " - ");
  1006. int total_holds = stoi(parts[0]);
  1007. BUGZ_LOG(fatal) << "total holds: " << total_holds;
  1008. auto contents = split(parts[1]);
  1009. for (auto const &hold : contents) {
  1010. auto hold_amount = split(hold, "=");
  1011. BUGZ_LOG(fatal) << hold_amount[0] << " with " << hold_amount[1];
  1012. std::string key = hold_amount[0];
  1013. str_tolower(key);
  1014. // equipment = e
  1015. // organics = o
  1016. // fuel = f
  1017. // colonists = c
  1018. // empty = empty
  1019. if (key != "empty") {
  1020. key = key[0];
  1021. }
  1022. galaxy.meta["ship"]["holds"][key] = stoi(hold_amount[1]);
  1023. }
  1024. galaxy.meta["ship"]["holds"]["total"] = total_holds;
  1025. }
  1026. if (galaxy.meta["info"]["Turns to Warp"]) {
  1027. int warp_turns = galaxy.meta["info"]["Turns to Warp"].as<int>();
  1028. BUGZ_LOG(fatal) << "Turns to Warp: " << warp_turns;
  1029. galaxy.meta["ship"]["warp_turns"] = warp_turns;
  1030. }
  1031. if (galaxy.meta["info"]["LongRange Scan"]) {
  1032. std::string scanner_text =
  1033. galaxy.meta["info"]["LongRange Scan"].as<std::string>();
  1034. char scanner = scanner_text[0];
  1035. BUGZ_LOG(fatal) << "Scanner: " << scanner;
  1036. galaxy.meta["ship"]["scanner"] = scanner;
  1037. }
  1038. // turns isn't ship specific
  1039. if (galaxy.meta["info"]["Turns left"]) {
  1040. // OR this could be "Unlimited" !!!
  1041. std::string text = galaxy.meta["info"]["Turns left"].as<std::string>();
  1042. if (text == "Unlimited") {
  1043. galaxy.meta["turns"] = -1;
  1044. } else {
  1045. int turns =
  1046. stoi(text); // galaxy.meta["info"]["Turns left"].as<int>();
  1047. BUGZ_LOG(fatal) << "Turns left: " << turns;
  1048. galaxy.meta["turns"] = turns;
  1049. }
  1050. }
  1051. if (galaxy.meta["info"]["Current Sector"]) {
  1052. int sector = galaxy.meta["info"]["Current Sector"].as<int>();
  1053. BUGZ_LOG(fatal) << "Sector: " << sector;
  1054. // it should already be sector ...
  1055. current_sector = sector;
  1056. }
  1057. if (galaxy.meta["info"]["Credits"]) {
  1058. std::string credit_text =
  1059. galaxy.meta["info"]["Credits"].as<std::string>();
  1060. replace(credit_text, ",", "");
  1061. int credits = stoi(credit_text);
  1062. galaxy.meta["credits"] = credits;
  1063. BUGZ_LOG(fatal) << "Credits: " << credits;
  1064. }
  1065. }
  1066. return;
  1067. }
  1068. // info to parse:
  1069. size_t pos = line.find(" : ");
  1070. if ((!endswith(line, " : ")) && (pos != line.npos)) {
  1071. std::string key = line.substr(0, pos);
  1072. // Ferrengi ships don't have date built
  1073. std::string value = line.substr(pos + 3);
  1074. trim(key);
  1075. trim(value);
  1076. galaxy.meta["info"][key] = value;
  1077. BUGZ_LOG(fatal) << "Info: " << key << " : " << value;
  1078. }
  1079. }
  1080. void Director::SL_computer_portline(const std::string &line) {
  1081. if (startswith(line, "What sector is the port in?"))
  1082. computer_port_done = false;
  1083. if (line == "I have no information about a port in that sector.") {
  1084. computer_port_sector = 0;
  1085. SL_parser = nullptr;
  1086. return;
  1087. }
  1088. if (!computer_port_done) {
  1089. if (in(line, "Fuel Ore")) computer_port_done = true;
  1090. if (in(line, "Cargo holds")) {
  1091. // If I want to scan the class type 0 ports:
  1092. // computer_port_done = true;
  1093. // otherwise:
  1094. SL_parser = nullptr;
  1095. return;
  1096. }
  1097. }
  1098. if (computer_port_done) {
  1099. if (line.empty()) {
  1100. SL_parser = nullptr;
  1101. return;
  1102. }
  1103. // scan for items of interest
  1104. // SL: [Fuel Ore Buying 810 100% 0]
  1105. // SL: [Organics Buying 856 57% 0]
  1106. // SL: [Equipment Selling 922 44% 0]
  1107. std::string work = line;
  1108. replace(work, "Fuel Ore", "Fuel");
  1109. replace(work, "%", "");
  1110. auto parts = split(work);
  1111. char c = tolower(parts[0][0]);
  1112. int pos;
  1113. char foe[4] = "foe";
  1114. for (pos = 0; pos < 3; ++pos) {
  1115. if (c == foe[pos]) break;
  1116. }
  1117. int amount = stoi(parts[2]);
  1118. int percent = stoi(parts[3]);
  1119. // update port
  1120. auto port = galaxy.ports.find(computer_port_sector);
  1121. if (port != galaxy.ports.end()) {
  1122. BUGZ_LOG(info) << "COM PORT " << computer_port_sector << " " << c << " "
  1123. << amount << " " << percent;
  1124. port->second.amount[pos] = amount;
  1125. port->second.percent[pos] = percent;
  1126. }
  1127. }
  1128. }
  1129. void Director::SL_planetline(const std::string &line) {
  1130. if (line == "Corporate command [TL=00:00:00]:[344] (?=Help)? Q" ||
  1131. line == "Computer command [TL=00:00:00]:[344] (?=Help)? Q") {
  1132. SL_parser = nullptr;
  1133. return;
  1134. }
  1135. if (in(line, "Class ") && (in(line, "Level ") || (endswith(line, "No Citadel")))) {
  1136. int level;
  1137. char c;
  1138. sector_type sector;
  1139. int number;
  1140. std::string name;
  1141. std::string work = line;
  1142. size_t pos = work.find('#');
  1143. std::string temp = work.substr(0, pos);
  1144. trim(temp);
  1145. sector = sstoi(temp);
  1146. work = work.substr(pos + 1);
  1147. number = sstoi(work);
  1148. pos = work.find(' ');
  1149. work = work.substr(pos + 1);
  1150. trim(work);
  1151. if (endswith(work, "No Citadel")) {
  1152. level = 0;
  1153. } else {
  1154. pos = work.rfind("Level ");
  1155. temp = work.substr(pos + 6);
  1156. level = sstoi(temp);
  1157. }
  1158. pos = work.rfind("Class ");
  1159. temp = work.substr(pos + 6);
  1160. c = temp[0];
  1161. work = work.substr(0, pos);
  1162. trim(work);
  1163. name = work;
  1164. BUGZ_LOG(warning) << (int)sector << " # " << number << " Class " << c
  1165. << " Level " << level << " name: [" << name << "]";
  1166. }
  1167. /*
  1168. SL: [ 344 #4 Enjoy the Silence Class M, Earth Type Level 3] SL:
  1169. [ --- (4M) 1T 64 25 14T 693 277 2T 10M] SL: [
  1170. 344 #5 High There! Class L, Mountainous Level 2]
  1171. SL: [ --- (5M) 2T 0 6 25T 100 112 2T 6M]
  1172. ...
  1173. SL: [ --- (9M) 3T 64 31 39T 793 389 4T ---]
  1174. */
  1175. }