#include "dispatchers.h" #include #include #include "boxes.h" #include "logging.h" #include "utils.h" Dispatch::Dispatch(Director &d, const char *called) : director{d} { aborted = false; if (called == nullptr) name = "Dispatch"; else name = called; }; Dispatch::~Dispatch(){}; void Dispatch::deactivate(void) { notify(); } void Dispatch::to_server(const std::string &send) { director.to_server(send, name); } void Dispatch::to_client(const std::string &send) { director.to_client(send); } const std::string &Dispatch::get_prompt(void) { return director.current_prompt; } void Dispatch::activate_chain(std::shared_ptr new_chain) { director.chain = new_chain; director.chain->activate(); } void Dispatch::setNotify(notifyFunc nf) { notify_ = nf; } void Dispatch::notify(void) { if (director.post) { director.post(notify_); } } void Dispatch::chain_client_input(const std::string &input) { if (director.chain) { director.chain->chain_client_input(input); } else { client_input(input); } } void Dispatch::chain_server_line(const std::string &line, const std::string &raw_line) { if (director.chain) { director.chain->chain_server_line(line, raw_line); } else { server_line(line, raw_line); } } void Dispatch::chain_server_prompt(const std::string &prompt) { if (director.chain) { director.chain->chain_server_prompt(prompt); } else { server_prompt(prompt); } } void Dispatch::server_line(const std::string &line, const std::string &raw_line) {} void Dispatch::server_prompt(const std::string &prompt) {} void Dispatch::client_input(const std::string &input) { aborted = true; deactivate(); } InputDispatch::InputDispatch(Director &d, const char *called) : Dispatch(d, called) { BUGZ_LOG(warning) << "InputDispatch()"; numeric = false; if (called == nullptr) name = "Input"; } InputDispatch::~InputDispatch() { BUGZ_LOG(warning) << "~InputDispatch()"; } void InputDispatch::activate(void) { BUGZ_LOG(warning) << "InputDispatch::activate() " << max_length; input.clear(); to_client(prompt); } void InputDispatch::deactivate(void) { notify(); } void InputDispatch::server_line(const std::string &line, const std::string &raw_line) { if (line.empty()) return; std::string temp = repr(raw_line); BUGZ_LOG(fatal) << "Input:SL(" << temp << ")"; if (startswith(line, "Command [TL=")) { return; } /* temp = raw_line; clean_string(temp); BUGZ_LOG(fatal) << "InputDispatch::server_line(" << temp << ")"; */ temp = prompt; ansi_clean(temp); size_t total = temp.length() + input.length(); to_client("\x1b[0m"); // reset colors while (total > 0) { to_client("\b \b"); --total; } // Lines line "\[[1A\[[1;36mphil \[[0;32mwarps into the sector.\[[0m" temp = raw_line; replace(temp, "\x1b[1A", ""); // replace(temp, "\x1[2J", ""); to_client(temp); to_client("\n\r"); // Doesn't matter if it is one or two calls. temp = prompt; temp.append(input); to_client(temp); // to_client(prompt); // to_client(input); } // void InputDispatch::server_prompt(const std::string &prompt) {} void InputDispatch::client_input(const std::string &cinput) { // BUGZ_LOG(info) << "InputDispatch::client_input(" << cinput << ")"; for (const char ch : cinput) { if (isprint(ch)) { // Ok! if (numeric) { // numbers only if (!isdigit(ch)) continue; } if (input.length() < max_length) { to_client(std::string(1, ch)); input += ch; } } else if ((ch == '\b') || (ch == 0x7f)) { // Backspace or rubout if (input.length() > 0) { to_client("\b \b"); input.erase(input.size() - 1); } } else if (ch == '\r') { // Ok, we're done! BUGZ_LOG(info) << "InputDispatch done: " << input; to_client("\x1b[0m\n\r"); deactivate(); } else if (ch == '\x1b') { aborted = true; to_client("\x1b[0m\n\r"); deactivate(); } } } /** * Menu Dispatch * * Two types of menus: * lazy: display the menu name and show prompt. ? shows menu. * non-lazy: displays menu + prompts. * * */ MenuDispatch::MenuDispatch(Director &d, const char *called) : Dispatch(d, called) { BUGZ_LOG(warning) << "MenuDispatch()"; if (called == nullptr) name = "Menu"; } MenuDispatch::~MenuDispatch() { BUGZ_LOG(warning) << "~MenuDispatch()"; } void MenuDispatch::activate(void) { calculate_widths(); input.clear(); BUGZ_LOG(warning) << "MenuDispatch::activate() " << max_width << ", " << max_option_width; if (lazy) menubox(); else help(); to_client(menu_prompt); } void MenuDispatch::deactivate(void) { notify(); } void MenuDispatch::help(void) { size_t max = max_width; Boxes mbox(max, 1, true); mbox.boxcolor = menu_box_color; if (lazy) { // just the menu mbox.textcolor = menu_options_color; to_client(mbox.top()); for (auto const &menu_item : menu) { std::string text = " "; text.append(menu_item.first); text.append(" - "); text.append(menu_item.second); while (text.length() < max) text.append(1, ' '); to_client(mbox.row(text)); } to_client(mbox.bottom()); } else { // full menu mbox.textcolor = menu_text_color; to_client(mbox.top()); std::string title = centered(max, menu_title); BUGZ_LOG(debug) << "help max=" << max << " [" << title << "]"; to_client(mbox.row(title)); to_client(mbox.middle()); mbox.textcolor = menu_options_color; for (auto const &menu_item : menu) { std::string text = " "; text.append(menu_item.first); text.append(" - "); text.append(menu_item.second); while (text.length() < max) text.append(1, ' '); to_client(mbox.row(text)); } to_client(mbox.bottom()); } } std::string MenuDispatch::centered(int length, const std::string &s) { std::string text = s; size_t leftovers = length - text.length(); int count = leftovers / 2; if (count > 0) { text.insert(0, count, ' '); text.append(count, ' '); } if (leftovers % 1 == 1) text.append(1, ' '); return text; } void MenuDispatch::menubox(void) { // just the menu box std::string title = centered(max_width, menu_title); /* int leftovers = max - menu_title.length(); while (leftovers > 2) { title.insert(0, 1, " "); title.append(1, " "); leftovers -= 2; }; if (leftovers == 1) title.append(1, " "); */ BUGZ_LOG(debug) << "menubox max=" << max_width << " [" << title << "]"; auto abox = Boxes::alert(title, menu_box_color, menu_text_color, max_width, 1, true); for (auto line : abox) { to_client(line); } } void MenuDispatch::calculate_widths(void) { max_width = menu_title.length() + 2; max_option_width = 0; for (auto key : menu) { size_t menu_line_length = 1 + key.first.length() + 3 + key.second.length() + 1; if (menu_line_length > max_width) max_width = menu_line_length; if (key.first.length() > max_option_width) max_option_width = key.first.length(); } instant = max_option_width == 1; } void MenuDispatch::server_line(const std::string &line, const std::string &raw_line) { // TODO: // Clear prompt, display raw server line, restore prompt. if (line.empty()) return; std::string temp = repr(raw_line); BUGZ_LOG(fatal) << "Input:SL(" << temp << ")"; if (startswith(line, "Command [TL=")) { return; } /* temp = raw_line; clean_string(temp); BUGZ_LOG(fatal) << "InputDispatch::server_line(" << temp << ")"; */ temp = menu_prompt; ansi_clean(temp); size_t total = temp.length() + input.length(); to_client("\x1b[0m"); // reset colors while (total > 0) { to_client("\b \b"); --total; } // Lines line "\[[1A\[[1;36mphil \[[0;32mwarps into the sector.\[[0m" temp = raw_line; replace(temp, "\x1b[1A", ""); // replace(temp, "\x1[2J", ""); to_client(temp); to_client("\n\r"); // Doesn't matter if it is one or two calls. temp = menu_prompt; temp.append(input); to_client(temp); // to_client(prompt); // to_client(input); } void MenuDispatch::client_input(const std::string &cinput) { for (auto const ch : cinput) { // not likely that we'd have more then one, // but deal with it correctly. if (ch == '\r') { // enter if (instant) return; for (auto const &mnu : menu) { if (mnu.first == input) { to_client("\x1b[0m\n\r"); deactivate(); return; } } // input wasn't found ? while (input.length() > 0) { to_client("\b \b"); input.erase(input.length() - 1); } return; // don't continue ... } if (ch == '\x1b') { // [ESC] - erase the input string while (input.length() > 0) { to_client("\b \b"); input.erase(input.length() - 1); } aborted = true; // Exit - allow escape from menu deactivate(); return; } if (ch == '\b') { if (input.length() > 0) { to_client("\b \b"); input.erase(input.length() - 1); } } if (ch == '?') { to_client(cinput); // display what they entered. to_client("\x1b[0m\n\r"); help(); to_client(menu_prompt); return; } if (isprint(ch)) { char c = ch; if (!case_sensitive) c = toupper(ch); // ok, it's a printable character if (input.length() < max_option_width) { // ok, there's room. to_client(std::string(1, c)); input.append(1, c); } if (instant) { for (auto const &mnu : menu) { if (mnu.first == input) { to_client("\x1b[0m\n\r"); deactivate(); return; } } // Erase last character, wasn't an option. to_client("\b \b"); input.erase(input.length() - 1); } } } } CIMDispatch::CIMDispatch(Director &d, const char *called) : Dispatch(d, called) { if (called == nullptr) name = "CIM"; } void CIMDispatch::activate(void) { count = 0; } void CIMDispatch::deactivate(void) { notify(); } void CIMDispatch::server_line(const std::string &line, const std::string &raw_line) { if (!((line.empty() || startswith(line, ": ") || startswith(line, "Command [")))) { count++; if (count % 100 == 0) { std::string message = str(boost::format("\r%1%") % count); to_client(message); } } if (line == ": ENDINTERROG") { std::string message = str(boost::format("\r%1%\n\r") % count); to_client(message); deactivate(); } } MoveDispatch::MoveDispatch(Director &d, const char *called) : Dispatch(d, called) { BUGZ_LOG(warning) << "MoveDispatch()"; use_express = false; if (called == nullptr) name = "Move"; } MoveDispatch::~MoveDispatch() { BUGZ_LOG(warning) << "~MoveDispatch()"; } // sector_type move_to; void MoveDispatch::activate(void) { starting = director.current_sector; BUGZ_LOG(fatal) << "MoveDispatch::activate()"; BUGZ_LOG(warning) << "Moving from " << starting << " to " << move_to; if (starting == move_to) { success = true; deactivate(); return; } /* 1 = Issue "I" 2 = Density Scan 3 = Move 4 = Autopilot 5 = Density Scan */ // Start with density scan -- unless express warp_lane.clear(); warp_pos = 0; if (use_express) { std::string command = str(boost::format("M%1%\r") % move_to); to_server(command); state = 3; } else { to_server("S"); // because we might not have a scanner state = 1; } // build final string to match against at_destination = "Auto Warping to sector "; at_destination.append(std::to_string(move_to)); } void MoveDispatch::deactivate(void) { BUGZ_LOG(fatal) << "MoveDispatch::deactivate() " << success; notify(); } // optional here void MoveDispatch::server_line(const std::string &line, const std::string &raw_line) { if (!line.empty()) BUGZ_LOG(fatal) << state << " : " << line; if (state == 1) { if (endswith(line, "Relative Density Scan")) { state = 2; } if (line == "You don't have a long range scanner.") { // Ok, we'll have to express move our way there then. use_express = true; // state = 2; std::string command = str(boost::format("M%1%\r") % move_to); to_server(command); state = 3; } } // SL: [###### DANGER! You have marked sector 740 to be avoided!] if ((state != 2) && (state != 5)) { // hide the density scan part std::string temp = raw_line; if (line == "") { // trim out the stray cursor home replace(temp, "\x1b[H", ""); } // Replace progress bar with something else. if (startswith(temp, "\x1b[1;33m\xb3")) temp = "\x1b[1A\x1b[1;33m** SLOW MOVE **"; /* if (in(temp, "\xb3")) { std::string debug = repr(temp); BUGZ_LOG(fatal) << "MOVE LINE: " << debug; } */ // Don't output raw_lines that move the cursor up 3. if (!in(temp, "\x1b[3A")) { temp.append("\n\r"); to_client(temp); } } if (state == 3) { if (line == "You are already in that sector!") { success = true; deactivate(); return; } if (line == "You don't have a long range scanner.") { use_express = true; } if (line == "That Warp Lane is not adjacent.") { // ok! Parse out the path that we need to take... } // [611 > 612 > 577 > 543 > 162 > 947 > 185 > 720 > 894 > 3 > 1] // multiple lines possible here? Yes. // [344 > 23328 > 2981 > 10465 > 14016 > 8979 > 1916 > 18734 > 5477 > 131 > // 27464 >] watch for it contains > // if ((line != "") && in(line, " > ")) { if ((line != "") && (in(line, " > ") || !warp_lane.empty())) { bool more = false; std::string work = line; if (endswith(work, " >")) { more = true; work = work.substr(0, work.length() - 2); } replace(work, " > ", " "); replace(work, "(", ""); replace(work, ")", ""); auto warps = split(work); for (auto const &w : warps) { BUGZ_LOG(fatal) << "lane: " << w; warp_lane.push_back(stoi(w)); } if (!more) state = 4; } } if (state == 4) { if (line == at_destination) { // [Auto Warping to sector 1] state = 6; } } } /* bool MoveDispatch::density_clear(density d) { // int sector, int density) { */ /* http://wiki.classictw.com/index.php?title=Gypsy%27s_Big_Dummy%27s_Guide_to_TradeWars_Text#Trader_Information Density Readings: 0 = Empty Sector or Ferrengi Dreadanought 1 = Marker Beacon 2 = Limpet Type 2 Tracking Mine 5 = Fighter (per Fighter) 10 = Armid Type 1 Mine 21 = Navigation Hazard (Per 1 Percent) 21 = Destroyed Ship (Due to 1 Percent Nav-Haz) 38 = Unmanned Ship 40 = Manned Ship, Alien or Ferrengi Assault Trader 50 = Destroyed Starport (After 25 Percent Nav-Haz Clears) 100 = Starport or Ferrengi Battle Cruiser 210 = Destroyed Planet (Due to 10 Percent Nav-Haz) 462 = Federation Starship under Admiral Nelson 489 = Federation Starship under Captain Zyrain 500 = Planet 512 = Federation Starship under Admiral Clausewitz 575 = Destroyed Port (Before 25% Nav-Haz Clears) */ /* if (d.sector == 0) return false; switch (d.density) { case 0: case 1: case 100: case 101: return true; } // special case for sector 1: if ((d.sector == 1) && (d.density == 601)) return true; return false; } */ void MoveDispatch::server_prompt(const std::string &prompt) { BUGZ_LOG(fatal) << "server_prompt: " << state << " SP: [" << prompt << "]"; // SL: [###### DANGER! You have marked sector 740 to be avoided!] // SP: [Do you really want to warp there? (Y/N) ] if (state == 1) { if (prompt == "Select (H)olo Scan or (D)ensity Scan or (Q)uit? [D] ") { to_server("D"); } } else if (state == 2) { if (at_command_prompt(prompt)) { // Ok, density is done // BUG: If the sector is adjacent, this doesn't check density! density d = director.galaxy.dscan.find(move_to); if (d.sector == move_to) { // Yes! we found the sector in the scan! if (!density_clear(d.sector, d.density, d.navhaz)) { BUGZ_LOG(fatal) << "Failed density check on single move."; why_failed = str(boost::format("Sector %1% has density %2%.") % move_to % d.density); success = false; deactivate(); return; } } std::string command = str(boost::format("M%1%\r") % move_to); to_server(command); state = 3; } } else if (state == 3) { if (prompt == "Clear Avoids? ") { to_server("N"); why_failed = "No path / check avoids."; success = false; deactivate(); } if (at_command_prompt(prompt)) { // this happens when the sector is adjcent to current_sector. // Or it happens when the ship is FUBAR. // NO!!!! if (director.current_sector == move_to) { BUGZ_LOG(fatal) << "Are we there yet? / Adjacent sector."; success = true; deactivate(); } else { // Nope! Wrong! } return; } if (prompt == "Engage the Autopilot? (Y/N/Single step/Express) [Y] ") { to_server("E"); return; } } else if (state == 4) { if (prompt == "Engage the Autopilot? (Y/N/Single step/Express) [Y] ") { if (use_express) { BUGZ_LOG(fatal) << "Using Express"; to_server("E"); } else { int to_check = warp_lane[warp_pos + 1]; // check density scan density d = director.galaxy.dscan.find(to_check); /* int density = director.galaxy.meta["density"][to_check]["density"].as(); */ if (density_clear(d.sector, d.density, d.navhaz)) { // to_check, density)) { to_server("S"); ++warp_pos; } else { to_server("N"); BUGZ_LOG(fatal) << "density_clear(" << to_check << ") : false"; why_failed = str(boost::format("Sector %1% has density %2%.") % to_check % d.density); success = false; deactivate(); } } } if (prompt == "Stop in this sector (Y,N,E,I,R,S,D,P,?) (?=Help) [N] ? ") { state = 5; // BUT? Do we have a scanner? if (use_express) { to_server("E"); } else to_server("SD"); return; } } else if (state == 5) { if (prompt == "Engage Express mode? (Y/N) [N] ") { to_server("Y"); return; } // finished scan if (prompt == "Stop in this sector (Y,N,E,I,R,S,D,P,?) (?=Help) [N] ? ") { int to_check = warp_lane[warp_pos + 1]; density d = director.galaxy.dscan.find(to_check); if (density_clear(d.sector, d.density, d.navhaz)) { to_server("N"); ++warp_pos; state = 4; } else { to_server("Y"); BUGZ_LOG(fatal) << "Stopped by density: " << to_check; why_failed = str(boost::format("Sector %1% has density %2%.") % to_check % d.density); success = 0; deactivate(); } } if (at_command_prompt(prompt)) { if (director.current_sector == move_to) { success = 1; } else success = 0; deactivate(); return; } } else if (state == 6) { if (at_command_prompt(prompt)) { // We're done! success = 1; deactivate(); } } } void MoveDispatch::client_input(const std::string &input) { // This exits us out quickly -- should I stop at a better spot? // As in answer "Y" to Stop in this sector? success = 0; aborted = true; deactivate(); } TraderDispatch::TraderDispatch(Director &d, const char *called) : Dispatch(d, called) { BUGZ_LOG(fatal) << "TraderDispatch()"; state = 0; success = false; if (called == nullptr) name = "Trader"; director.galaxy.meta["help"]["stop_percent"] = "ScriptTrader stop trading if below this percent."; director.galaxy.meta["help"]["trade_end_empty"] = "ScriptTrader end trading with empty holds? Y/N"; }; TraderDispatch::~TraderDispatch() { BUGZ_LOG(fatal) << "~TraderDispatch()"; } void TraderDispatch::activate(void) { // ok, lookup port1 port2 BUGZ_LOG(fatal) << "TraderDispatch::activate " << port[0] << " & " << port[1]; auto port_info = director.galaxy.ports.find(port[0]); int port0_type = port_info->second.type; port_buysell[0] = get_buysell(port0_type); // Special case - we just want to buy resources if (port[1] != 0) { port_info = director.galaxy.ports.find(port[1]); int port1_type = port_info->second.type; port_buysell[1] = get_buysell(port1_type); BUGZ_LOG(fatal) << port0_type << " and " << port1_type; } else { BUGZ_LOG(fatal) << "Just buy from " << port[0]; } /* 1 = Info */ // Ok, what do we do first here? // I - Info state = 1; percent = 5.0; to_server("I"); if (director.galaxy.config.contains("stop_percent")) { stop_percent = director.galaxy.config["stop_percent"].get(); } else { stop_percent = 25; director.galaxy.config["stop_percent"] = stop_percent; } if (director.galaxy.config.contains("trade_end_empty")) { std::string tee = director.galaxy.config["trade_end_empty"]; if ((toupper(tee[0]) == 'Y') || (toupper(tee[0]) == 'T')) { trade_end_empty = true; } else { trade_end_empty = false; // director.galaxy.config["trade_end_empty"] = "N"; } } else { trade_end_empty = true; director.galaxy.config["trade_end_empty"] = "Y"; } } void TraderDispatch::deactivate(void) { notify(); } void TraderDispatch::server_line(const std::string &line, const std::string &raw_line) { // FUTURE: powering up weapons check // Show what's going on... if (state > 1) { std::string temp = raw_line; // replace progress bar with something else if (startswith(temp, "\x1b[1;33m\xb3")) temp = "\x1b[1A\x1b[1;33m** SLOW MOVE **"; // don't output lines that move cursor up 3. // if (!in(temp, "\x1b[3A")) { temp.append("\n\r"); to_client(temp); // } } if (line == "Docking...") { last_offer = 0; final_offer = 0; initial_offer = 0; try_again = false; } static std::set success_lines = { "If only more honest traders would port here, we'll take them though.", "You will put me out of business, I'll take your offer.", "FINE, we'll take them, just leave!", "Agreed, and a pleasure doing business with you!", "You are a rogue! We'll take them anyway.", "You insult my intelligence, but we'll buy them anyway.", "Very well, we'll take that offer.", "You drive a hard bargain, but we'll take them.", "Done, we'll take the lot.", "I hate haggling, they're all yours.", "You are robbing me, but we'll buy them anyway.", "SOLD! Come back anytime!", "Cheapskate. Here, take them and leave me alone.", "Very well, we'll buy them.", "You are a shrewd trader, they're all yours.", "I could have twice that much in the Androcan Empire, but they're yours.", "Oh well, maybe I can sell these to some other fool, we'll take them.", "I PAID more than that! But we'll sell them to you anyway.", "(Sigh) Very well, pay up and take them away.", "Agreed! We'll purchase them!"}; if (success_lines.find(line) != success_lines.end()) { BUGZ_LOG(fatal) << "Success " << buying << " " << initial_offer << " : " << last_offer; // calculate % ? success = true; BUGZ_LOG(fatal) << "% " << (float)initial_offer / (float)last_offer * 100.0; BUGZ_LOG(fatal) << "meta trade setting: " << percent << " for " << active_port << " " << product; director.galaxy .meta["_trade"][std::to_string(active_port)][std::to_string(product)] = percent; // subtract total holds value from this port's amount auto port = director.galaxy.ports.find(active_port); if (port != director.galaxy.ports.end()) { // We've found the port! // product is the index port->second.amount[product] -= director.galaxy.meta["ship"]["holds"]["total"].get(); BUGZ_LOG(fatal) << "Port " << active_port << "," << product << " amount is now " << port->second.amount[product]; } } // meta trade setting: " << percent << " for " << active_port << " " << product; std::string active_port_text = std::to_string(active_port); director.galaxy.meta["_trade"][active_port_text][std::to_string(product)] = percent; try_again = true; } // SL: [You have 16,767 credits and 0 empty cargo holds.] // trade accepted. if not 0 empty cargo holds -- we failed! // SL: [] // SL: [You have 4,046 credits and 0 empty cargo holds.] // this shows up at the initial docking of the port. if (startswith(line, "You have ")) { if (initial_offer != 0) { // Ok, the offer was possibly accepted. int success; if (buying) success = director.galaxy.meta["ship"]["holds"]["total"]; else success = 0; std::string text = std::to_string(success); text.append(" empty cargo holds."); if (endswith(line, text)) { BUGZ_LOG(fatal) << "Trade SUCCESS!"; // record this action somewhere in meta. } else { BUGZ_LOG(fatal) << "Trade FAIL"; } } } } void TraderDispatch::server_prompt(const std::string &prompt) { // FUTURE: check for "Surrender/Attack" if (at_command_prompt(prompt)) { if (state == 1) { // Ok, decision time! if (director.galaxy.meta["ship"]["holds"].contains("c")) { // holds contain colonists why_failed = "Holds contain colonists."; success = false; aborted = true; to_client("ScriptTrader FAIL: holds contain colonists."); deactivate(); return; } int total; if (director.galaxy.meta["ship"]["holds"].contains("total")) { total = json_int(director.galaxy.meta["ship"]["holds"]["total"]); int empty = 0; if (director.galaxy.meta["ship"]["holds"].contains("empty")) { empty = json_int(director.galaxy.meta["ship"]["holds"]["empty"]); } if (total != empty) { BUGZ_LOG(fatal) << "FAIL: " << total << " total holds, " << empty << " holds empty."; to_client("ScriptTrader FAIL: holds are not empty."); why_failed = "Holds are not empty."; success = false; aborted = true; deactivate(); return; } } // Right now, I stop trading, if we can't fill/buy max holds. // We now look to see if you have 75 * total_holds credits. int credits = json_int(director.galaxy.meta["credits"]); if (credits < (total * 75)) { BUGZ_LOG(fatal) << "FAIL: credits < " << total * 75 << " have " << credits; std::string message = str(boost::format("ScriptTrader FAIl: You need at least %1% " "credits to trade.\n\r") % (total * 75)); to_client(message); why_failed = "Low credits."; success = false; aborted = true; deactivate(); return; } // Which port to trade with first? examine trades BUGZ_LOG(fatal) << "trades: " << trades; BUGZ_LOG(fatal) << "port0:" << text_from_buysell(port_buysell[0]); if (port[1] != 0) BUGZ_LOG(fatal) << "port1:" << text_from_buysell(port_buysell[1]); // Ok, I might still need this (so I know what port to start with) // which is selling? // must set active port! bool all_holds_empty = false; active_port = 0; // check the ship and holds here. (MAYBE) int holds = json_int(director.galaxy.meta["ship"]["holds"]["total"]); if (director.galaxy.meta["ship"]["holds"].contains("empty")) { if (holds == json_int(director.galaxy.meta["ship"]["holds"]["empty"])) all_holds_empty = true; } if (port[1] == 0) { active_port = port[0]; } else { if (!all_holds_empty) { for (int x = 0; x < 3; ++x) { std::string foe_text; foe_text.assign(1, foe[x]); if (director.galaxy.meta["ship"]["holds"].contains(foe_text)) { if (!port_buysell[0].foe[x]) { active_port = port[0]; break; } if (!port_buysell[1].foe[x]) { active_port = port[1]; } } } if (active_port == 0) { why_failed = "No ports will buy what we have in our holds."; success = false; to_client( "I don't see any ports that are buying what we have in our " "holds.\n\r"); deactivate(); return; }; } else { // all holds empty, find selling port for (int x = 0; x < 3; ++x) { if (trades.foe[x]) { // TRUE = BUY, so FALSE = sell if (!port_buysell[0].foe[x]) { active_port = port[0]; break; } if (!port_buysell[1].foe[x]) { active_port = port[1]; break; } } } } } state = 2; if (director.current_sector == active_port) { // begin state 3 state = 3; to_client("Trading...\n\r"); to_server("PT"); return; } else { // initiate move std::string move = std::to_string(active_port); to_client("Moving...\n\r"); move.append("\r"); to_server(move); return; } } if (state == 2) { if (director.current_sector == active_port) { // We're here state = 3; to_client("Trading...\n\r"); to_server("PT"); return; } else { // we failed to move to where we wanted to go?! BUGZ_LOG(fatal) << "Expecting: " << active_port << " but got " << director.current_sector; deactivate(); return; } } } if (state == 3) { if (startswith(prompt, "How many holds of ") && endswith(prompt, "]? ")) { char selling = tolower(prompt[18]); for (int x = 0; x < 3; ++x) { if (foe[x] == selling) product = x; } if (in(prompt, " to sell ")) { // always sell everything to_server("\r"); return; } if (in(prompt, " to buy ")) { bool buy_ok = true; std::string max = str(boost::format("[%1%]") % json_int(director.galaxy.meta["ship"]["holds"]["total"])); if (!in(prompt, max)) { buy_ok = false; } if (trade_end_empty) { // Ok, we want to end with empty holds... int other_port; if (active_port == port[0]) other_port = port[1]; else other_port = port[0]; BUGZ_LOG(fatal) << "Is " << other_port << " burnt? (trade_end_empty)"; // Is target port burnt? auto pos = director.galaxy.ports.find(other_port); bool burnt = false; if (pos != director.galaxy.ports.end()) { // We'll find the port. Really. if (!pos->second.unknown()) { // port isn't unknown, so check to see if it's burnt for (int x = 0; x < 3; ++x) { if (trades.foe[x]) { BUGZ_LOG(fatal) << other_port << " " << x << " is in trades..."; BUGZ_LOG(fatal) << "amount[" << x << "] = " << pos->second.amount[x]; if (pos->second.percent[x] < stop_percent) burnt = true; if (director.galaxy.meta["ship"]["holds"].contains("total")) { BUGZ_LOG(fatal) << pos->second.amount[x] << " : " << json_int( director.galaxy.meta["ship"]["holds"]["total"]); if (pos->second.amount[x] < json_int( director.galaxy.meta["ship"]["holds"]["total"])) { BUGZ_LOG(fatal) << "Other port " << other_port << " is burnt " << x << " burnt = true"; burnt = true; } } } } } } if (burnt) { BUGZ_LOG(fatal) << "Port burnt, buy_ok = false"; buy_ok = false; } } // Ok, what are they selling? // char selling = tolower(prompt[18]); BUGZ_LOG(fatal) << "Selling: " << selling; if (!buy_ok) { // no! to_server("0\r"); } else for (int x = 0; x < 3; ++x) { // if (foe[x] == selling) { // We found the item ... is it something that we're trading? if (foe[x] == selling) { if (trades.foe[x]) { // Yes! to_server("\r"); product = x; } else { // No! to_server("0\r"); } } } // } } } if (startswith(prompt, "Your offer [") && endswith(prompt, " ? ")) { // Ok, things get weird here. We also need to look for final offer. if (last_offer != 0) percent -= 1.0; if (buying) last_offer = (int)(initial_offer * (100 + percent) / 100.0); else last_offer = (int)(initial_offer * (100 - percent) / 100.0); BUGZ_LOG(fatal) << "Offer: " << buying << " offer " << last_offer << " % " << percent; std::string text = std::to_string(last_offer); text.append("\r"); to_server(text); } if (at_command_prompt(prompt)) { // we're done trading... // do we carry on, or stop? // 1.) CHECK TURNS // need turn tracking // 2.) PORTS BURNT? if (try_again) { state = 3; to_client("Trading... Take 2!\n\r"); to_server("PT"); return; } if (active_port == port[0]) { active_port = port[1]; } else active_port = port[0]; if (active_port == 0) { deactivate(); return; } // Is target port burnt? auto pos = director.galaxy.ports.find(active_port); bool burnt = false; if (pos != director.galaxy.ports.end()) { // We'll find the port. Really. if (!pos->second.unknown()) { // port isn't unknown, check to see if burnt for (int x = 0; x < 3; ++x) { if (trades.foe[x]) { BUGZ_LOG(fatal) << x << " % " << (int)pos->second.percent[x] << " " << stop_percent; if (pos->second.percent[x] < stop_percent) burnt = true; if (director.galaxy.meta["ship"]["holds"].contains("total")) if (pos->second.amount[x] < json_int(director.galaxy.meta["ship"]["holds"]["total"])) burnt = true; } } } } if (burnt) { to_client("Ports burnt.\n\r"); deactivate(); return; } std::string move = std::to_string(active_port); to_client("Moving...\n\r"); move.append("\r"); to_server(move); state = 2; } } } void TraderDispatch::client_input(const std::string &cinput) { aborted = true; deactivate(); }; /* * CoreDispatch: This is an example class that does dispatch. * Copy this and make changes from there... */ CoreDispatch::CoreDispatch(Director &d, const char *called) : Dispatch(d, called) { BUGZ_LOG(warning) << "CoreDispatch()"; if (called == nullptr) name = "Core"; } void CoreDispatch::activate(void) { // save things, set things } void CoreDispatch::deactivate(void) { // restore things notify(); } void CoreDispatch::server_line(const std::string &line, const std::string &raw_line) {} void CoreDispatch::server_prompt(const std::string &prompt) {} void CoreDispatch::client_input(const std::string &input) { BUGZ_LOG(warning) << "Got: " << input << " prompt=" << get_prompt(); aborted = true; deactivate(); }