#include "director.h" #include #include #include "ansicolor.h" #include "boxes.h" #include "galaxy.h" #include "logging.h" #include "scripts.h" #include "utils.h" Director::Director() { BUGZ_LOG(warning) << "Director::Director()"; // active = false; game = 0; // not in a game galaxy.reset(); // do everything proxy_deactivate does ... // proxy_deactivate(); active = false; // reset everything back to good state talk_direct = true; show_client = true; count = 0; /* Setup StringFunc for SL_parser: Construct these once, rather then every single time we need them. */ SF_cimline = [this](const std::string &s) { this->SL_cimline(s); }; SF_sectorline = [this](const std::string &s) { this->SL_sectorline(s); }; SF_portline = [this](const std::string &s) { this->SL_portline(s); }; SF_warpline = [this](const std::string &s) { this->SL_warpline(s); }; SF_infoline = [this](const std::string &s) { this->SL_infoline(s); }; SF_densityline = [this](const std::string &s) { this->SL_densityline(s); }; SF_computer_portline = [this](const std::string &s) { this->SL_computer_portline(s); }; SF_planetline = [this](const std::string &s) { this->SL_planetline(s); }; build_menu(); } Director::~Director() { BUGZ_LOG(warning) << "Director::~Director()"; } void Director::client_input(const std::string &input) { std::string output = repr(input); if (chain) { BUGZ_LOG(trace) << chain->name << " CI: [" << output << "]"; chain->client_input(input); return; } else { BUGZ_LOG(trace) << "CI: [" << output << "]"; } // If we're already active, don't try to activate. if (active) { if (input == "Q" || input == "q") proxy_deactivate(); return; } else if (input == "\x1b" || input == "~") { std::string &prompt = current_prompt; BUGZ_LOG(trace) << "ACTIVATE prompt shows: [" << prompt << "]"; if (prompt == "Selection (? for menu): ") { to_client( "\n\rThere's not much we can do here. Activate in-game at a " "Command prompt.\n\r"); to_client(current_raw_prompt); return; } // easter-eggs: if (prompt == "Enter your choice: ") { // ANSIColor c1(COLOR::CYAN, ATTR::BOLD); // ANSIColor c2(COLOR::WHITE, ATTR::BOLD); ANSIColor c1("BOLD CYAN"); ANSIColor c2("BOLD WHITE"); to_client(std::string("\n\r") + c1() + "I'd choose " + c2() + "`T`" + c1() + ", but that's how I was coded.\n\r"); to_client(current_raw_prompt); return; } // easter-egg if (prompt == "[Pause]") { ANSIColor c1("BOLD CYAN"); // COLOR::CYAN, ATTR::BOLD); to_client(std::string(" ") + c1() + "PAWS" + reset() + "\n\r"); to_client(current_raw_prompt); return; } if (at_planet_prompt(prompt)) { // future: If activated at planet menu, activate the planet upgrade // script! (Maybe). If I can figure out what planet it is/and where. to_client("\n\r\x1b[0mFUTURE: Activate the planet upgrade script.\n\r"); to_client(current_raw_prompt); return; } if (at_command_prompt(prompt)) { proxy_activate(); return; } } else if (input == "\x01" || input == "\x02" || input == "\x03") { // MACROS std::string macro; macro.assign(1, input[0] + 'A' - 1); std::string to_send; if (galaxy.meta.contains("macros") && galaxy.meta["macros"].contains(macro)) { to_send = galaxy.meta["macros"][macro].get(); BUGZ_LOG(fatal) << "Sending Macro " << macro << ": [" << to_send << "]"; replace(to_send, "^", "\r"); to_server(to_send, "MACRO"); return; } } // Ok... if (talk_direct) to_server(input, "Director::client_input"); } void Director::server_line(const std::string &line, const std::string &raw_line) { // check for if we entered game/left game SL_history.push_back(line); if (!chain) BUGZ_LOG(info) << "SL: [" << line << "]"; if (line.find("TradeWars Game Server ") != std::string::npos) { // Inject our proxy activation message to_client("\rTradeWars Proxy v2++ READY (~ or ESC to activate)\n\r"); /* There's a delay here when I save the game data. I've moved it futher down. Hide it at a prompt, so it isn't so noticeable. */ // reset "active game" -- we're at the TWGS main menu } if (line == "Trade Wars 2002 Win32 module now loading.") { // This happens if the game is customized with ANSI/menus/etc. // We're not detecting the Selection, go find what was selected. auto find = SL_history.rbegin(); ++find; while (find != SL_history.rend()) { if (*find != "") { std::string last_SL = *find; char ch = last_SL[last_SL.length() - 1]; if ((game) && (game != ch)) { galaxy.save(); } game = ch; BUGZ_LOG(warning) << "GAME " << game << " activated!"; galaxy.game = game; galaxy.username = username; galaxy.load(); // YAML loaded, set sensible default config values (if missing). if (!galaxy.config.contains("display_lines")) { galaxy.config["display_lines"] = 20; } galaxy.meta["help"]["display_lines"] = "Number of report lines to display"; if (!galaxy.config.contains("burnt_percent")) { galaxy.config["burnt_percent"] = 40; } galaxy.meta["help"]["burnt_percent"] = "Don't display ports in report below this percent"; break; } ++find; } } if (line.find("Selection (? for menu): ") != std::string::npos) { char ch = line[line.length() - 1]; if (ch >= 'A' && ch < 'Q') { if ((game) && (game != ch)) { galaxy.save(); } game = ch; BUGZ_LOG(warning) << "GAME " << game << " activated!"; // TODO: Load game data galaxy.game = game; galaxy.username = username; galaxy.load(); // YAML loaded, set sensible default config values (if missing). if (!galaxy.config.contains("display_lines")) { galaxy.config["display_lines"] = 20; } galaxy.meta["help"]["display_lines"] = "Number of report lines to display"; if (!galaxy.config.contains("burnt_percent")) { galaxy.config["burnt_percent"] = 40; } galaxy.meta["help"]["burnt_percent"] = "Don't display ports in report below this percent"; } // not needed (handled by above Game Server check). if (ch == 'Q') { if (game) { // TODO: Save galaxy data galaxy.save(); } game = 0; galaxy.reset(); } } if (game) { // in-game parsing here. /* ____ _ _ / ___| ___ _ ____ _____ _ __ | | (_)_ __ ___ \___ \ / _ \ '__\ \ / / _ \ '__| | | | | '_ \ / _ \ ___) | __/ | \ V / __/ | | |___| | | | | __/ |____/ \___|_| \_/ \___|_| |_____|_|_| |_|\___| ____ _ | _ \ __ _ _ __ ___(_)_ __ __ _ | |_) / _` | '__/ __| | '_ \ / _` | | __/ (_| | | \__ \ | | | | (_| | |_| \__,_|_| |___/_|_| |_|\__, | |___/ This is where all of the server lines are gleaned for all the information that we can get out of them. // When activating the computer SP: [Command [TL=00:00:00]:[926] (?=Help)? : ] Sector: 926 CI: [c] SL: [Command [TL=00:00:00]:[926] (?=Help)? : C] SL: [] SL: [] SL: [] SL: [] SP: [Computer command [TL=00:00:00]:[926] (?=Help)? ] // deactivating the computer SL: [Computer command [TL=00:00:00]:[926] (?=Help)? Q] SL: [] SL: [] SL: [] */ if (line == "") { SL_parser = SF_portline; } if (line == " Corporate Planet Scan " " " || line == " Personal Planet Scan " " ") { SL_parser = SF_planetline; } /* if (startswith(line, " Items Status Trading % of max OnBoard")) SL_parser = SF_portline; */ if (endswith(line, "Relative Density Scan")) { galaxy.dscan.reset(current_sector); SL_parser = SF_densityline; } if (startswith(line, "Sector : ")) SL_parser = SF_sectorline; if (line == ": ") SL_parser = SF_cimline; if (line == "") SL_parser = SF_infoline; if (startswith(line, "What sector is the port in? [")) { // Computer Port Report // SL: [What sector is the port in? [611] 4] size_t pos = line.rfind(' '); computer_port_sector = stoi(line.substr(pos)); SL_parser = SF_computer_portline; } } if (SL_parser) { SL_parser(line); } if (chain) { BUGZ_LOG(info) << chain->name << " SL: [" << line << "]"; chain->server_line(line, raw_line); } } void Director::server_prompt(const std::string &prompt, const std::string &raw_prompt) { current_prompt = prompt; current_raw_prompt = raw_prompt; if (!chain) { std::string temp = repr(prompt); BUGZ_LOG(trace) << "SP: [" << temp << "]"; } if (game) { if (prompt == "Selection (? for menu): ") { galaxy.save(); game = 0; galaxy.reset(); } // in-game parsing here. // if (startswith(prompt, "Command [") && endswith(prompt, "] (?=Help)? : // ")) { if (at_command_prompt(prompt)) { std::string sector_text; size_t before, after; before = prompt.find("]:[") + 3; after = prompt.find("] (?=Help)"); sector_text = prompt.substr(before, after - before); current_sector = stoi(sector_text); BUGZ_LOG(info) << "current_sector = " << current_sector; } } /* if (emit_server_prompt) emit_server_prompt(prompt); */ if (chain) { std::string output = repr(prompt); BUGZ_LOG(info) << chain->name << " SP: [" << output << "]"; chain->server_prompt(prompt); } } void Director::build_menu(void) { main_menu = std::make_shared(*this, "MenuMain"); MenuDispatch *md = static_cast(&(*main_menu)); ANSIColor bcolor(COLOR::YELLOW, COLOR::BLUE, ATTR::BOLD); ANSIColor tcolor(COLOR::WHITE, COLOR::BLUE, ATTR::BOLD); ANSIColor mocolor(COLOR::CYAN, ATTR::BOLD); md->menu_box_color = bcolor(); // "\x1b[1;33;44m"; md->menu_text_color = tcolor(); // "\x1b[1;37;44m"; md->menu_title = "Proxy Menu"; md->menu_options_color = mocolor(); // "\x1b[1;36;40m"; ANSIColor by{1, 33}; ANSIColor cyan{36}; ANSIColor bg{1, 32}; std::string prompt = by() + "M" + cyan() + "ain " + by() + "P" + cyan() + "roxy " + bg() + "=>" + reset() + " "; md->menu_prompt = prompt; // "Main Proxy => "; // "\x1b[0;31;40m\xdb\xb2\xb1\xb0 \x1b[31;40mRED " // "\x1b[32;40mGREEN\x1b[30;42m\xdb\xb2\xb1\xb0 \x1b[0m : "; md->lazy = true; md->menu = {{"C", "Configure"}, {"D", "Display Report"}, {"E", "Export Data/Save"}, {"I", "Information"}, {"P", "Port CIM"}, {"W", "Warp CIM"}, {"M", "Macros"}, {"T", "Trading Report (same as D)"}, {"S", "Scripts"}, {"X", "eXit"}}; md->setNotify([this]() { this->menu_choice(); }); cim = std::make_shared(*this, "CIMDirector"); cim->setNotify([this]() { this->cim_done(); }); } void Director::proxy_activate(void) { active = true; // yes, set keep-alive timer. to_server(" ", "Director::proxy_active()"); // start keep-alive timer. // set other values we need talk_direct = false; show_client = false; /* Wait a minute .. this might be confusing. Shouldn't I send them the current prompt? Just in case we abort in the middle of something?!? */ old_prompt = current_prompt; old_raw_prompt = current_raw_prompt; to_client("\x1b[0m\n\r"); /* ╔══════════════════════════════╗ ║ TradeWars Proxy Active ║ ╚══════════════════════════════╝ -=> */ Boxes box(30, 1, true); box.boxcolor = "\x1b[1;33;44m"; box.textcolor = "\x1b[1;33;44m"; to_client(box.top()); std::string output = " TradeWars Proxy \x1b[5mActive\x1b[0;1;33;44m "; to_client(box.row(output)); to_client(box.bottom()); if (galaxy.meta.contains("port_CIM")) { time_t last_port_cim_report = galaxy.meta["port_CIM"].get(); int seconds_ago = time_t_now() - last_port_cim_report; int minutes_ago = seconds_ago / 60; BUGZ_LOG(warning) << "port_CIM was " << minutes_ago << " minutes ago."; if (minutes_ago >= 120) { float hours_ago = minutes_ago / 60.0; std::string message = str(boost::format( "Warning: Last Port CIM Refresh was %1$0.2f hours ago.\n\r") % hours_ago); to_client(message); } } else { to_client("Warning: No CIM data.\n\r"); BUGZ_LOG(warning) << "no meta port_CIM value seen."; } chain = main_menu; main_menu->activate(); /* // Using InputDispatch -- and see have_input std::shared_ptr readline = std::make_shared(*this); chain = readline; InputDispatch *id = static_cast(&(*readline)); id->prompt = "\x1b[0m\x1b[1;33;44m-=>\x1b[0m \x1b[1;37;44m"; id->max_length = 15; id->setNotify([this]() { this->have_input(); }); readline->activate(); */ } void Director::menu_choice(void) { MenuDispatch *md = dynamic_cast(&(*chain)); if (md) { if (md->input.empty()) { to_client("Menu aborted.\n\r"); proxy_deactivate(); return; } else { switch (md->input[0]) { case 'C': // configure config_edit(); return; break; case 'D': case 'T': // display trading report { auto pptv = galaxy.find_best_trades(); std::string output; galaxy.sort_port_pair_type(pptv); int max_display = 20; if (galaxy.config.contains("display_lines")) max_display = galaxy.config["display_lines"].get(); else galaxy.config["display_lines"] = max_display; if ((max_display <= 0) || (max_display > 255)) { max_display = 255; galaxy.config["display_lines"] = 255; } const int per_line = 5; int count = 0; int line = 0; std::string display_line; ANSIColor by{1, 33}; // yellow ANSIColor cyan{36}; // cyan ANSIColor bg{1, 32}; // bright green ANSIColor bb{1, 34}; // bright blue for (auto const &ppt : pptv) { output = str(boost::format("%1%%2$5d%3%:%4%%5$-5d%3%(%6%%7$d%3%) ") % by() % ppt.s1 % cyan() % bg() % ppt.s2 % bb() % ppt.type); display_line.append(output); ++count; if (count == per_line) { count = 0; display_line.append("\n\r"); to_client(display_line); display_line.clear(); ++line; } if (line == max_display) break; } if (count != 0) { display_line.append("\n\r"); to_client(display_line); display_line.clear(); } // We got < 5 lines, and max_display is > 5. Offer suggestion: if ((line < 5) && (max_display > 5)) { // suggestion: to_client( "HINT: For more lines, try reducing the burnt_percent?\n\r"); } } break; case 'E': // Export Data/Save to_client("Saving..."); galaxy.save(); to_client("\rSaved....\n\r"); break; case 'I': // Information information(); break; case 'P': // Port CIM // Since we're adding/updating, we don't lose our // type 0 ports. Type 9 stays at 9. galaxy.meta["port_CIM"] = (int)time_t_now(); chain = cim; to_server("^RQ", "Director::Port CIM"); { std::string text = str(boost::format("Port CIM Report (%1%)\n\r") % galaxy.ports.size()); // to_client("Port CIM Report\n\r"); to_client(text); } chain->activate(); return; break; case 'M': // Macros macro_edit(); return; break; case 'W': // Warp CIM chain = cim; to_server("^IQ", "Director::Warp CIM"); { std::string text = str(boost::format("Warp CIM Report (%1%)\n\r") % galaxy.warps.size()); // to_client("Warp CIM Report\n\r"); to_client(text); } chain->activate(); return; break; // case 'T': // Trading Report // break; case 'S': // Scripts { init_scripts_menu(); chain = scripts_menu; chain->activate(); return; } break; case 'X': // Exit proxy_deactivate(); return; } /* std::string text = str( boost::format("Back from Menu [%1%] was selected.\n\r") % md->input); to_client(text); */ md->activate(); } } } MenuDispatch *Director::init_scripts_menu(void) { MenuDispatch *md; if (scripts_menu) { md = dynamic_cast(&(*scripts_menu)); return md; } else { scripts_menu = std::make_shared(*this, "MenuScripts"); md = static_cast(&(*scripts_menu)); md->menu_box_color = "\x1b[0;32;40m"; md->menu_text_color = "\x1b[1;32;40m"; md->menu_title = "Scripts Menu"; md->menu_options_color = "\x1b[1;32;40m"; md->lazy = false; ANSIColor by{1, 33}; ANSIColor cyan{36}; ANSIColor bg{1, 32}; std::string prompt = by() + "S" + cyan() + "cript " + by() + "M" + cyan() + "enu " + bg() + "=>" + reset() + " "; md->menu_prompt = prompt; // " SCRIPT : "; md->menu = {{"!", "Terror"}, {"T", "Trade"}, {"S", "Safe Move"}, {"C", "Closest Trade"}, {"N", "Nearest Unexplored"}, {"V", "Voyager Explorer"}, {"E", "Explorer"}, {"U", "Upgrade Planet Pants"}, {"X", "Exit Scripts"}}; md->setNotify([this]() { this->scripts_done(); }); return md; } } void Director::scripts_done(void) { // Was script selected? If so, run it! // otherwise, back to the menu we go... MenuDispatch *md = dynamic_cast(&(*scripts_menu)); if (md) { if (md->input.empty()) { to_client("Scripts aborted.\n\r"); scripts_menu.reset(); proxy_deactivate(); return; } else { switch (md->input[0]) { case 'T': // Trade { script = std::make_shared(*this, "TraderDirector"); TraderDispatch *ts = static_cast(&((*script))); ts->setNotify([this]() { this->proxy_deactivate(); }); // Locate best trades auto found = galaxy.find_trades(current_sector, false); if (found.empty()) { to_client( "No Trades found. Port burnt (CONFIG: lower burnt_percent?) " "or no ports around.\n\r"); proxy_deactivate(); return; } // sort first? galaxy.sort_port_pair_type(found); BUGZ_LOG(fatal) << "Found " << found.size() << " possible trade(s)."; BUGZ_LOG(fatal) << "Best: " << found[0].s1 << "," << found[0].s2 << " : " << found[0].type; // Set parameters ts->port[0] = found[0].s1; ts->port[1] = found[0].s2; ts->type = found[0].type; ts->trades = found[0].trades; chain = script; chain->activate(); return; } break; case 'S': { script = std::make_shared(*this, "MoveDirector"); MoveDispatch *md = static_cast(&((*script))); md->setNotify([this]() { this->proxy_deactivate(); }); md->move_to = 1; chain = script; chain->activate(); return; } break; case '!': { script = std::make_shared(*this, "TerrorDirector"); ScriptTerror *st = static_cast(&(*script)); st->setNotify([this]() { script.reset(); this->proxy_deactivate(); }); chain = script; chain->activate(); return; } break; // } case 'C': { auto best = galaxy.find_closest(current_sector); std::string text; if (best.type != 0) { text = str(boost::format("Best/Closest: %1% with %2% & %3%\n\r") % best.type % best.s1 % best.s2); to_client(text); } else { to_client("I don't see any best trades.\n\r"); } const char *foe[3] = {"Fuel", "Organics", "Equipment"}; for (int x = 0; x < 3; ++x) { auto s = galaxy.find_nearest_selling(current_sector, x); text = str(boost::format("Closest Selling %1% is %2%\n\r") % foe[x] % s); to_client(text); } to_client("Good places to hide:\n\r"); auto sectors = galaxy.find_safe(); text = ""; int c = 0; for (auto const &s : sectors) { ++c; text += str(boost::format("%1$5d ") % s); if (c % 10 == 0) { text.append("\n\r"); to_client(text); text.clear(); c = 0; } } if (c != 0) { text.append("\n\r"); to_client(text); } } break; case 'N': { sector_type s = galaxy.find_nearest_unexplored(current_sector); if (s != 0) { std::string text = str(boost::format("Sector: %1%\n\r") % s); to_client(text); } else { to_client("I don't see any unexplored.\n\r"); } } break; case 'V': { script = std::make_shared(*this, "VoyagerDirector"); ScriptVoyager *sv = static_cast(&(*script)); sv->setNotify([this]() { script.reset(); this->proxy_deactivate(); }); chain = script; chain->activate(); return; } break; case 'E': { script = std::make_shared(*this, "ExploreDirector"); ScriptExplore *se = static_cast(&(*script)); se->setNotify([this]() { script.reset(); this->proxy_deactivate(); }); chain = script; chain->activate(); return; } break; case 'U': { script = std::make_shared(*this, "PlanetDirector"); ScriptPlanet *sp = static_cast(&(*script)); sp->setNotify([this]() { script.reset(); this->proxy_deactivate(); }); chain = script; chain->activate(); return; } break; case 'Q': chain = main_menu; main_menu->activate(); return; break; } } } proxy_activate(); // And to end scripts, we do .. what exactly? // DEBUG: Ok, why does everything work OK if I reset the scripts_menu // here?? probably do want to destroy scripts here. ;) // scripts_menu.reset(); // proxy_deactivate(); } /** * @brief Setup Config Input * * @return DispatchInput* */ InputDispatch *Director::init_config_input(void) { InputDispatch *id; if (config_input) { // Yes, it has been setup before. id = dynamic_cast(&(*config_input)); ANSIColor by{1, 33}; ANSIColor cyan{36}; ANSIColor bg{1, 32}; std::string prompt = by() + "C" + cyan() + "onfig " + bg() + "=>" + reset() + " "; id->prompt = prompt; // "Config => "; id->numeric = true; id->max_length = 3; config_item.clear(); return id; } else { // set it up config_input = std::make_shared(*this, "InputDirector"); id = static_cast(&(*config_input)); ANSIColor by{1, 33}; ANSIColor cyan{36}; ANSIColor bg{1, 32}; std::string prompt = by() + "C" + cyan() + "onfig " + bg() + "=>" + reset() + " "; id->prompt = prompt; // "Config => "; id->numeric = true; id->max_length = 3; id->setNotify([this]() { this->config_have_input(); }); config_item.clear(); return id; } } void Director::config_edit(void) { // display current config std::string menu_box_color = "\x1b[1;33;44m"; std::string menu_text_color = "\x1b[1;37;44m"; auto abox = Boxes::alert(" Configuration: ", menu_box_color, menu_text_color, 20, 1, true); for (auto line : abox) { to_client(line); } // to_client("Configuration:\n\r"); int item = 1; ANSIColor number(COLOR::CYAN); ANSIColor key(COLOR::GREEN, ATTR::BOLD); ANSIColor value(COLOR::BLUE, ATTR::BOLD); for (auto const &cfg : galaxy.config.items()) { // for (auto const &cfg : galaxy.config) { std::string output = str(boost::format("%1%%2$2d %3%%4$20s: %5%%6$s%7%\n\r") % number() % item % key() % cfg.key() % value() % cfg.value() % reset()); to_client(output); ++item; } std::string message = number() + "Enter number to edit, " + key() + "blank to exit.\n\r"; // to_client("Enter number to edit, blank to exit.\n\r"); to_client(message); // setup call to config_input: InputDispatch *id = init_config_input(); chain = config_input; id->activate(); // to return to the menu: // MenuDispatch *md = dynamic_cast(&(*chain)); // md->activate(); } void Director::config_have_input(void) { InputDispatch *id = dynamic_cast(&(*config_input)); if (config_item.empty()) { // This is a config menu selection if (id->input.empty()) { // We're done here. Return to menu. chain = main_menu; MenuDispatch *md = dynamic_cast(&(*chain)); md->activate(); // destroy the input? yes. config_input.reset(); return; } else { int item = sstoi(id->input); if ((item < 1) || (item > (int)galaxy.config.size())) { // selection out of range - redisplay config menu to_client("What? I didn't see that item.\n\r"); config_edit(); return; } else { int pos = 1; for (auto const &c : galaxy.config.items()) { if (pos == item) { // got it! ANSIColor key(COLOR::GREEN, ATTR::BOLD); ANSIColor value(COLOR::BLUE, ATTR::BOLD); config_item = c.key(); std::string output = str(boost::format("%1%%2% : %3%%4%\n\r") % key() % config_item % value() % galaxy.meta["help"][config_item]); to_client(output); id->max_length = 30; id->numeric = false; ANSIColor by{1, 33}; ANSIColor cyan{36}; ANSIColor bg{1, 32}; std::string prompt = by() + "C" + cyan() + "hange to " + bg() + "=>" + reset() + " "; id->prompt = prompt; id->activate(); return; }; ++pos; } to_client("What? I didn't find that item?\n\r"); config_edit(); return; } } } else { // This is a config item edit if (id->input.empty()) { to_client("No change.\n\r"); config_edit(); return; } else { BUGZ_LOG(fatal) << "Config EDIT: " << config_item << " = " << id->input; galaxy.config[config_item] = id->input; config_edit(); return; } } } InputDispatch *Director::init_macro_input(void) { InputDispatch *id; if (macro_input) { // Yes, it has been setup before. id = dynamic_cast(&(*macro_input)); ANSIColor by{1, 33}; ANSIColor cyan{36}; ANSIColor bg{1, 32}; std::string prompt = by() + "M" + cyan() + "acro " + bg() + "=>" + reset() + " "; id->prompt = prompt; // "Config => "; id->numeric = false; id->max_length = 1; config_item.clear(); return id; } else { // set it up macro_input = std::make_shared(*this, "InputDirector"); id = static_cast(&(*macro_input)); ANSIColor by{1, 33}; ANSIColor cyan{36}; ANSIColor bg{1, 32}; std::string prompt = by() + "M" + cyan() + "acro " + bg() + "=>" + reset() + " "; id->prompt = prompt; id->numeric = false; id->max_length = 1; id->setNotify([this]() { this->macro_have_input(); }); macro_item.clear(); return id; } } void Director::macro_edit(void) { // display current config std::string menu_box_color = "\x1b[1;33;44m"; std::string menu_text_color = "\x1b[1;37;44m"; auto abox = Boxes::alert(" Macros: ", menu_box_color, menu_text_color, 14, 1, true); for (auto line : abox) { to_client(line); } ANSIColor number(COLOR::CYAN); ANSIColor key(COLOR::GREEN, ATTR::BOLD); ANSIColor value(COLOR::BLUE, ATTR::BOLD); if (!galaxy.meta.contains("macros")) { galaxy.meta["macros"]["A"] = ""; galaxy.meta["macros"]["B"] = ""; galaxy.meta["macros"]["C"] = ""; galaxy.meta["macros"]["D"] = ""; galaxy.meta["macros"]["E"] = ""; galaxy.meta["macros"]["F"] = ""; } for (auto const &cfg : galaxy.meta["macros"].items()) { // for (auto const &cfg : galaxy.config) { std::string output = str(boost::format("%1%%2%: %3%%4$s%5%\n\r") % key() % cfg.key() % value() % cfg.value() % reset()); to_client(output); } std::string message = number() + "Enter Letter to edit, " + key() + "blank to exit.\n\r"; to_client(message); // setup call to config_input: InputDispatch *id = init_macro_input(); chain = macro_input; id->input.clear(); macro_item.clear(); id->activate(); } void Director::macro_have_input(void) { InputDispatch *id = dynamic_cast(&(*macro_input)); if (macro_item.empty()) { // Macro selection if (id->input.empty()) { chain = main_menu; MenuDispatch *md = dynamic_cast(&(*chain)); md->activate(); macro_input.reset(); return; } else { char c = toupper(id->input[0]); macro_item.assign(1, c); if (galaxy.meta["macros"].contains(macro_item)) { constexpr ANSIColor key("BOLD GREEN"); // COLOR::GREEN, ATTR::BOLD); constexpr ANSIColor value("BOLD BLUE"); // COLOR::BLUE, ATTR::BOLD); std::string output = str(boost::format("%1%%2% : %3%%4%\n\r") % key() % macro_item % value() % galaxy.meta["macros"][macro_item]); to_client(output); id->max_length = 100; id->numeric = false; ANSIColor by{1, 33}; ANSIColor cyan{36}; ANSIColor bg{1, 32}; std::string prompt = by() + "M" + cyan() + "acro " + macro_item + bg() + "=>" + reset() + " "; id->prompt = prompt; id->activate(); return; } else { to_client("I don't understand.\n\r"); macro_edit(); return; } } } else { // clear out macro_item when done. if (id->input.empty()) { to_client("No change.\n\r"); macro_item.clear(); macro_edit(); return; } else { galaxy.meta["macros"][macro_item] = id->input; macro_item.clear(); macro_edit(); return; } } } void Director::have_input(void) { ++count; InputDispatch *id = dynamic_cast(&(*chain)); if (id) { std::string output = str(boost::format("Your Input (%2%): [%1%]\n\r") % id->input % count); to_client("\x1b[0m"); to_client(output); } else { proxy_deactivate(); return; } if (count > 3) { proxy_deactivate(); } else { chain->activate(); } } void Director::cim_done(void) { BUGZ_LOG(info) << "CIM done"; chain = main_menu; main_menu->activate(); } void Director::information(void) { std::string output; to_client("I currently know the following:\n\r"); output = str( boost::format("Ports: %1%, Sectors: %2%, Config: %3%, Meta: %4%\n\r") % galaxy.ports.size() % galaxy.warps.size() % galaxy.config.size() % galaxy.meta.size()); to_client(output); } void Director::proxy_deactivate(void) { active = false; // reset everything back to good state talk_direct = true; show_client = true; chain.reset(); to_client("\n\r"); to_client(current_raw_prompt); } /* Server Line Parsing Routines */ void Director::SL_cimline(const std::string &line) { if (line == ": ENDINTERROG") { SL_parser = nullptr; return; } if (line == ": ") { // do I need to do anything special here for this? // Maybe -- We would save special ports that don't show up // (StarDock/Special) before. We don't know (at this point) if this is // warps or ports. return; } if (line.empty()) { SL_parser = nullptr; return; } // parse cimline // size_t pos = line.find('%'); // std::string work = line; // if (pos == line.npos) { if (in(line, "%")) { // portcim struct port p = parse_portcim(line); if (p.sector == 0) BUGZ_LOG(fatal) << "portcim: FAIL [" << line << "]"; else galaxy.add_port(p); // else // BUGZ_LOG(trace) << "portcim: " << p; } else { // warpcim // BUGZ_LOG(fatal) << "warpcim: [" << line << "]"; auto warps = split(line); sector_warps sw; for (auto const &w : warps) { if (sw.sector == 0) { sw.sector = stoi(w); } else { sw.add(stoi(w)); } } // BUGZ_LOG(trace) << "warpcim: " << sw; galaxy.add_warp(sw); } } void Director::SL_thiefline(const std::string &line) { size_t pos = line.find("Suddenly you're Busted!"); bool busted = pos != line.npos; if (busted) { BUGZ_LOG(fatal) << "set bust"; SL_parser = nullptr; } else { pos = line.find("(You realize the guards saw you last time!)"); if (pos != line.npos) SL_parser = nullptr; } // Are those the two ways to exit from this state? } void Director::SL_sectorline(const std::string &line) { // BUGZ_LOG(fatal) << "sectorline: [" << line << "]"; if (line.empty()) { SL_parser = nullptr; } else { /* sectorline: [Sector : 926 in The Federation.] sectorline: [Beacon : FedSpace, FedLaw Enforced] sectorline: [Ports : Stargate Alpha I, Class 9 (Special) (StarDock)] sectorline: [Traders : Civilian phil, w/ 30 ftrs,] sectorline: [ in Star Stomper (Sverdlov Merchant Cruiser)] sectorline: [Warps to Sector(s) : 70 - 441 - 575 - 600 - 629 - 711] sectorline: [Warps to Sector(s) : 70 - (475) - 569] What can we get from Traders : line? Can we get if they are hostile to us? We need to respond to "is powering up weapons" ... We can react faster then a person can! "phil is powering up weapons systems!" Also the auto-attack ones Ferrengi -- we need to auto-respond Retreat. */ if (in(line, "Sector :")) { current_sector = stoi(line.substr(10)); // BUGZ_LOG(trace) << "SECTOR: " << current_sector; } if (in(line, "Ports :")) { std::string port_class; size_t pos = line.find(", Class "); if (pos != std::string::npos) { pos += 8; int class_ = stoi(line.substr(pos)); // BUGZ_LOG(trace) << "PORT: " << class_; galaxy.add_port(current_sector, class_); } } if (in(line, "Warps to Sector(s) :")) { std::string temp = line.substr(20); replace(temp, " - ", " "); // unexplored sectors () // Should I track these? replace(temp, "(", ""); replace(temp, ")", ""); sector_warps sw; auto warps = split(temp); sw.sector = current_sector; // what if there is only one warp? for (auto const &w : warps) { sw.add(stoi(w)); } // BUGZ_LOG(trace) << "WARPS: " << sw; galaxy.add_warp(sw); } } } void Director::SL_densityline(const std::string &line) { // BUGZ_LOG(trace) << "densityline: [" << line << "]"; if (line.empty()) { SL_parser = nullptr; return; } /* // Ensure this really is a density scan and not something else if (!in(line, "Sector") || !in(line, "Warps") || !in(line, "NavHaz") || !in(line, "Anom")) { BUGZ_LOG(fatal) << "densityline: Invalid line."; SL_parser = nullptr; return; } if (not galaxy.meta["density"]) { galaxy.meta["density"] = YAML::Node(); } */ /* 0 1 2 3 4 5 6 7 8 9 10 11 12 "Sector 55 ==> 0 Warps : 4 NavHaz : 0% Anom : No" "Sector ( 223) ==> 0 Warps : 3 NavHaz : 0% Anom : No" SL: [Sector ( 1704) ==> 10,350 Warps : 4 NavHaz : 0% Anom : No] */ if (in(line, "==>")) { std::string work = line; replace(work, ":", ""); bool known = !in(work, "("); replace(work, "(", ""); replace(work, ")", ""); replace(work, "%", ""); replace(work, ",", ""); auto dense = split(work); // Parse our data sector_type sector = std::stoi(dense.at(1)); uint16_t density = std::stoi(dense.at(3)); uint16_t warps = std::stoi(dense.at(5)); uint16_t navhaz = std::stoi(dense.at(7)); bool anom = in(dense.at(9), "Yes"); struct density d = {sector, density, warps, navhaz, anom, known}; galaxy.dscan.add_scan(d); // Commit data /* BUGZ_LOG(trace) << "densityline: {sector=" << sector << " density=" << density << " warps=" << warps << " navhaz=" << navhaz << " anom=" << anom << " known=" << known << "}"; */ /* if (galaxy.meta["density"][sector]) { galaxy.meta["density"][sector] = YAML::Node(); } */ // what(): [json.exception.type_error.305] cannot use operator[] with a // numeric argument with object numeric argument with object /* std::string sector_text = std::to_string(sector); galaxy.meta["_density"][sector_text]["density"] = density; galaxy.meta["_density"][sector_text]["warps"] = warps; galaxy.meta["_density"][sector_text]["navhaz"] = navhaz; galaxy.meta["_density"][sector_text]["anom"] = anom; galaxy.meta["_density"][sector_text]["known"] = known; */ // Add a check to see if density is greater than 500 // Add datetime stamp } } void Director::SL_portline(const std::string &line) { /* We take blank lines because we start at . Otherwise, we trigger on computer port requests. if (line.empty()) { SL_parser = nullptr; return; } */ /* SL: [ Items Status Trading % of max OnBoard] SL: [ ----- ------ ------- -------- -------] SL: [Fuel Ore Buying 3000 100% 0] SL: [Organics Buying 3000 100% 0] SL: [Equipment Buying 3000 100% 0] SL: [] SL: [Commerce report for: 03:51:56 PM Mon Oct 24, 2033 You can buy:] SL: [A Cargo holds : 650 credits / next hold 0] SL: [B Fighters : 233 credits per fighter 75] SL: [C Shield Points : 116 credits per point 100] SL: [] */ // BUGZ_LOG(info) << "portline : " << line; if (in(line, "%")) { // size_t pos = line.find('%'); // if (pos != line.npos) { // Ok, this is a valid portline std::string work = line; replace(work, "Fuel Ore", "Fuel"); auto parts = split(work); if (parts[0] == "Items") return; char c = tolower(parts[0][0]); int pos; char foe[4] = "foe"; for (pos = 0; pos < 3; ++pos) { if (c == foe[pos]) break; } int amount = stoi(parts[2]); int percent = stoi(parts[3]); // update port auto port = galaxy.ports.find(current_sector); if (port != galaxy.ports.end()) { port->second.amount[pos] = amount; port->second.percent[pos] = percent; } /* BUGZ_LOG(fatal) << "portline split:"; for (auto const p : parts) { BUGZ_LOG(fatal) << p; } */ // Here's the end: if (parts[0] == "Equipment") SL_parser = nullptr; // BUGZ_LOG(fatal) << "portline split : [" << parts << "]"; } } void Director::SL_warpline(const std::string &line) { if (line.empty()) { SL_parser = nullptr; return; } // process warp line BUGZ_LOG(fatal) << "warpline: [" << line << "]"; } void Director::SL_infoline(const std::string &line) { static int state; if (line == "") { state = 0; galaxy.meta["info"] = json(); } if (line.empty()) { ++state; if (state == 2) { SL_parser = nullptr; // clear out the existing ship data galaxy.meta["ship"] = json(); // process the parsed information in meta["info"] if (galaxy.meta["info"].contains("Total Holds")) { std::string work = galaxy.meta["info"]["Total Holds"]; replace(work, "Fuel Ore", "Fuel"); auto parts = split(work, " - "); int total_holds = stoi(parts[0]); BUGZ_LOG(fatal) << "total holds: " << total_holds; auto contents = split(parts[1]); for (auto const &hold : contents) { auto hold_amount = split(hold, "="); BUGZ_LOG(fatal) << hold_amount[0] << " with " << hold_amount[1]; std::string key = hold_amount[0]; str_tolower(key); // equipment = e // organics = o // fuel = f // colonists = c // empty = empty if (key != "empty") { key = key[0]; } galaxy.meta["ship"]["holds"][key] = stoi(hold_amount[1]); } galaxy.meta["ship"]["holds"]["total"] = total_holds; } if (galaxy.meta["info"].contains("Turns to Warp")) { // Why is this saying that this is a string? int warp_turns = sstoi(json_str(galaxy.meta["info"]["Turns to Warp"])); // BUGZ_LOG(trace) << "Turns to Warp: " << warp_turns; galaxy.meta["ship"]["warp_turns"] = warp_turns; } if (galaxy.meta["info"].contains("LongRange Scan")) { std::string scanner_text = galaxy.meta["info"]["LongRange Scan"].get(); char scanner = scanner_text[0]; // BUGZ_LOG(trace) << "Scanner: " << scanner; galaxy.meta["ship"]["scanner"] = scanner; } // turns isn't ship specific if (galaxy.meta["info"].contains("Turns left")) { // OR this could be "Unlimited" !!! std::string text = galaxy.meta["info"]["Turns left"]; if (text == "Unlimited") { galaxy.meta["turns"] = -1; } else { int turns = stoi(text); // galaxy.meta["info"]["Turns left"].as(); // BUGZ_LOG(trace) << "Turns left: " << turns; galaxy.meta["turns"] = turns; } } if (galaxy.meta["info"].contains("Current Sector")) { int sector = sstoi(json_str(galaxy.meta["info"]["Current Sector"])); // BUGZ_LOG(trace) << "Sector: " << sector; // it should already be sector ... current_sector = sector; } if (galaxy.meta["info"].contains("Credits")) { std::string credit_text = galaxy.meta["info"]["Credits"]; replace(credit_text, ",", ""); int credits = stoi(credit_text); galaxy.meta["credits"] = credits; // BUGZ_LOG(trace) << "Credits: " << credits; } } return; } // info to parse: size_t pos = line.find(" : "); if ((!endswith(line, " : ")) && (pos != line.npos)) { std::string key = line.substr(0, pos); // Ferrengi ships don't have date built std::string value = line.substr(pos + 3); trim(key); trim(value); galaxy.meta["info"][key] = value; // BUGZ_LOG(trace) << "Info: " << key << " : " << value; } // [Corp # 1, Galaxy Stomping, Inc.] if (startswith(line, "Corp ")) { pos = line.find(", "); if (pos != line.npos) { galaxy.meta["info"]["Corp"] = line.substr(pos + 2); } } } void Director::SL_computer_portline(const std::string &line) { if (startswith(line, "What sector is the port in?")) computer_port_done = false; if (line == "I have no information about a port in that sector.") { computer_port_sector = 0; SL_parser = nullptr; return; } if (!computer_port_done) { if (in(line, "Fuel Ore")) computer_port_done = true; if (in(line, "Cargo holds")) { // If I want to scan the class type 0 ports: // computer_port_done = true; // otherwise: SL_parser = nullptr; return; } } if (computer_port_done) { if (line.empty()) { SL_parser = nullptr; return; } // scan for items of interest // SL: [Fuel Ore Buying 810 100% 0] // SL: [Organics Buying 856 57% 0] // SL: [Equipment Selling 922 44% 0] std::string work = line; replace(work, "Fuel Ore", "Fuel"); replace(work, "%", ""); auto parts = split(work); char c = tolower(parts[0][0]); int pos; char foe[4] = "foe"; for (pos = 0; pos < 3; ++pos) { if (c == foe[pos]) break; } int amount = stoi(parts[2]); int percent = stoi(parts[3]); // update port auto port = galaxy.ports.find(computer_port_sector); if (port != galaxy.ports.end()) { BUGZ_LOG(trace) << "COM PORT " << computer_port_sector << " " << c << " " << amount << " " << percent; port->second.amount[pos] = amount; port->second.percent[pos] = percent; } } } void Director::SL_planetline(const std::string &line) { if (line == "Corporate command [TL=00:00:00]:[344] (?=Help)? Q" || line == "Computer command [TL=00:00:00]:[344] (?=Help)? Q") { SL_parser = nullptr; return; } if (in(line, "Class ") && (in(line, "Level ") || (endswith(line, "No Citadel")))) { int level; char c; sector_type sector; int number; std::string name; std::string work = line; size_t pos = work.find('#'); std::string temp = work.substr(0, pos); trim(temp); sector = sstoi(temp); work = work.substr(pos + 1); number = sstoi(work); pos = work.find(' '); work = work.substr(pos + 1); trim(work); if (endswith(work, "No Citadel")) { level = 0; } else { pos = work.rfind("Level "); temp = work.substr(pos + 6); level = sstoi(temp); } pos = work.rfind("Class "); temp = work.substr(pos + 6); c = temp[0]; work = work.substr(0, pos); trim(work); name = work; /* BUGZ_LOG(trace) << (int)sector << " # " << number << " Class " << c << " Level " << level << " name: [" << name << "]"; */ planet p{sector, number, level, name, c}; galaxy.planets[number] = p; } /* SL: [ 344 #4 Enjoy the Silence Class M, Earth Type Level 3] SL: [ --- (4M) 1T 64 25 14T 693 277 2T 10M] SL: [ 344 #5 High There! Class L, Mountainous Level 2] SL: [ --- (5M) 2T 0 6 25T 100 112 2T 6M] ... SL: [ --- (9M) 3T 64 31 39T 793 389 4T ---] */ }