Browse Source

Fixed TraderDispatch. Match start and end of prompts.

We were missing the buying/selling endswith "]? "
part.
Steve Thielemann 3 years ago
parent
commit
16de09b1f1
4 changed files with 513 additions and 11 deletions
  1. 6 4
      director.cpp
  2. 451 4
      dispatchers.cpp
  3. 52 0
      dispatchers.h
  4. 4 3
      scripts.h

+ 6 - 4
director.cpp

@@ -499,11 +499,11 @@ void Director::scripts_done(void) {
       switch (md->input[0]) {
         case 'T':  // Trade
         {
-          script = std::make_shared<ScriptTrader>(*this);
-          ScriptTrader *ts = static_cast<ScriptTrader *>(&((*script)));
+          script = std::make_shared<TraderDispatch>(*this);
+          TraderDispatch *ts = static_cast<TraderDispatch *>(&((*script)));
           ts->setNotify([this]() { this->proxy_deactivate(); });
 
-          // Set parameters
+          // Locate best trades 
           auto found = galaxy.find_trades(current_sector, false);
           if (found.empty()) {
             to_client(
@@ -516,8 +516,10 @@ void Director::scripts_done(void) {
           galaxy.sort_port_pair_type(found);
 
           BUGZ_LOG(fatal) << "Found " << found.size() << " possible trade(s).";
-          BUGZ_LOG(fatal) << found[0].s1 << "," << found[0].s2 << " : "
+          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;

+ 451 - 4
dispatchers.cpp

@@ -562,6 +562,7 @@ void MoveDispatch::activate(void) {
   state = 1;
   warp_lane.clear();
   warp_pos = 0;
+  use_express = false;
 
   // build final string to match against
   at_destination = "Auto Warping to sector ";
@@ -577,6 +578,15 @@ void MoveDispatch::server_line(const std::string &line,
     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;
+    }
   }
 
   if ((state != 2) && (state != 5)) {
@@ -588,7 +598,7 @@ void MoveDispatch::server_line(const std::string &line,
     }
 
     // Replace progress bar with something else.
-    if (startswith(temp, "\x1b[1;33m\xb3")) 
+    if (startswith(temp, "\x1b[1;33m\xb3"))
       temp = "\x1b[1A\x1b[1;33m** SLOW MOVE **";
 
     /*
@@ -615,11 +625,12 @@ void MoveDispatch::server_line(const std::string &line,
     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 <Move> it contains >
-    if ((line != "<Move>") && in(line, ">")) {
+    if ((line != "<Move>") && in(line, " > ")) {
       bool more = false;
       std::string work = line;
 
@@ -673,7 +684,7 @@ void MoveDispatch::server_prompt(const std::string &prompt) {
       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)) {
+        if (!density_clear(d)) {
           BUGZ_LOG(fatal) << "Failed density check on single move.";
           success = false;
           deactivate();
@@ -695,6 +706,10 @@ void MoveDispatch::server_prompt(const std::string &prompt) {
 
   } 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);
@@ -711,6 +726,7 @@ void MoveDispatch::server_prompt(const std::string &prompt) {
         success = false;
         deactivate();
       }
+      }
     }
     if (prompt == "Stop in this sector (Y,N,E,I,R,S,D,P,?) (?=Help) [N] ? ") {
       state = 5;
@@ -724,7 +740,7 @@ void MoveDispatch::server_prompt(const std::string &prompt) {
       /*
       int density =
           director.galaxy.meta["density"][to_check]["density"].as<int>();
-      */          
+      */
       if (density_clear(d)) {
         to_server("N");
         ++warp_pos;
@@ -752,6 +768,437 @@ void MoveDispatch::client_input(const std::string &input) {
   deactivate();
 }
 
+TraderDispatch::TraderDispatch(Director &d) : Dispatch(d) {
+  BUGZ_LOG(fatal) << "TraderDispatch()";
+  state = 0;
+};
+
+TraderDispatch::~TraderDispatch() { BUGZ_LOG(fatal) << "~TraderDispatch()"; }
+
+void TraderDispatch::activate(void) {
+  // ok, lookup port1 port2
+  BUGZ_LOG(fatal) << "STraderDispatch::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];
+  }
+
+  /*
+  auto ttr = director.galaxy.trade_type_info(port0_type, port1_type);
+  trades = ttr.trades;
+  */
+
+  /*
+  if (trades.foe[0] && trades.foe[1] && trades.foe[2]) {
+    // it has all three -- use the last 2.
+    trades.foe[0] = false;
+  }
+  */
+
+  // Ok, what do we do first here?
+  // I - Info
+  state = 1;
+  percent = 5.0;
+  to_server("I");
+  director.galaxy.meta["help"]["stop_percent"] =
+      "ScriptTrader stop trading if below this percent.";
+
+  if (director.galaxy.config["stop_percent"]) {
+    stop_percent = director.galaxy.config["stop_percent"].as<int>();
+  } else {
+    stop_percent = 20;
+    director.galaxy.config["stop_percent"] = stop_percent;
+  }
+  director.galaxy.meta["help"]["trade_end_empty"] =
+      "ScriptTrader end trades with empty holds? Y/N";
+
+  if (director.galaxy.config["trade_end_empty"]) {
+    std::string tee =
+        director.galaxy.config["trade_end_empty"].as<std::string>();
+    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 = false;
+    director.galaxy.config["trade_end_empty"] = "N";
+  }
+}
+
+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;
+    temp.append("\n\r");
+    to_client(temp);
+  }
+
+  if (line == "Docking...") {
+    last_offer = 0;
+    final_offer = 0;
+    initial_offer = 0;
+  }
+
+  static std::set<std::string> 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 % ?
+    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"][active_port][product] = percent;
+  }
+
+  // <P-Probe estimates your offer was
+
+  if (startswith(line, "Agreed, ")) {
+    last_offer = 0;
+    final_offer = 0;
+    if (director.galaxy.meta["trade"][active_port][product]) {
+      percent = director.galaxy.meta["trade"][active_port][product].as<float>();
+      percent += 1.0;
+      BUGZ_LOG(fatal) << "Percent for " << active_port << " now " << percent;
+    } else {
+      BUGZ_LOG(fatal) << "using default for " << active_port;
+      percent = 5.0;  // check meta for past trades information
+    }
+  }
+
+  if (startswith(line, "We'll buy them for ")) {
+    // I need the initial offer!
+    std::string offer = line.substr(19);
+    replace(offer, ",", "");
+    initial_offer = stoi(offer);
+    BUGZ_LOG(fatal) << "Buying, initial: " << initial_offer;
+    buying = true;  // Port is buying, we are selling.
+  }
+
+  if (startswith(line, "We'll sell them for ")) {
+    // I need the initial offer!
+    std::string offer = line.substr(20);
+    replace(offer, ",", "");
+    initial_offer = stoi(offer);
+    BUGZ_LOG(fatal) << "Selling, initial: " << initial_offer;
+    buying = false;  // Port is selling, we are buying.
+  }
+
+  // SL: [Our final offer is 1,263 credits.]
+  if (startswith(line, "Our final offer is ")) {
+    // Well snap!
+    std::string offer = line.substr(19);
+    replace(offer, ",", "");
+    final_offer = stoi(offer);
+    BUGZ_LOG(fatal) << "Final offer: " << final_offer;
+  }
+
+  // SL: [You have 16,767 credits and 0 empty cargo holds.]
+  // trade accepted.  if not 0 empty cargo holds -- we failed!
+  // SL: [<P-Probe estimates your offer was  91.83% of best price>]
+  // 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 = 0;
+      else
+        success = director.galaxy.meta["ship"]["holds"]["total"].as<int>();
+
+      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.
+      }
+    }
+  }
+}
+
+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"]["c"]) {
+        // holds contain colonists
+        to_client("ScriptTrader FAIL: holds contain colonists.");
+        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 = director.galaxy.meta["ship"]["holds"]["total"].as<int>();
+      if (director.galaxy.meta["ship"]["holds"]["empty"]) {
+        if (holds == director.galaxy.meta["ship"]["holds"]["empty"].as<int>())
+          all_holds_empty = true;
+      }
+
+      if (port[1] == 0) {
+        active_port = port[0];
+      } else {
+        if (!all_holds_empty) {
+          for (int x = 0; x < 3; ++x) {
+            if (director.galaxy.meta["ship"]["holds"][foe[x]]) {
+              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) {
+            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]) {
+              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;
+
+        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];
+
+          // 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]) {
+                  if (pos->second.percent[x] < stop_percent) burnt = true;
+                }
+              }
+            }
+          }
+
+          if (burnt) {
+            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 (active_port == port[0]) {
+        if (port[0] == 0) {
+          deactivate();
+          return;
+        }
+        active_port = port[1];
+      } else
+        active_port = port[0];
+
+      // 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 (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) { deactivate(); };
+
 /*
  * CoreDispatch:  This is an example class that does dispatch.
  * Copy this and make changes from there...

+ 52 - 0
dispatchers.h

@@ -133,6 +133,7 @@ class MoveDispatch : public Dispatch {
   int state;
   int success;
   int warp_pos;
+  bool use_express;
   std::string at_destination;
   std::vector<int> warp_lane;
 
@@ -147,6 +148,57 @@ class MoveDispatch : public Dispatch {
   bool density_clear(density d); // int sector, int density);  
 };
 
+class TraderDispatch : public Dispatch {
+ private:
+ public:
+  TraderDispatch(Director &);
+  ~TraderDispatch();
+
+  char foe[4] = "foe";
+  bool trade_end_empty;
+
+  /**
+   * internal state
+   *
+   * 1 = <Info> query.
+   * 2 = move to active port
+   * 3 = trade
+   * 4 = if (burnt), stop, otherwise toggle active_port and state = 2
+   *
+   * NEW: set port[1] to 0 for buy-only ability.
+   * Percent 20 doesn't work with Cargo Trans' 250 holds.
+   * Maybe look at the amount instead?  If < holds = burnt.
+   */
+  int state;
+  float percent;
+  bool buying;
+  int initial_offer;
+  int last_offer;
+  int final_offer;
+  int product; // product we are buying/selling 0,1,2 foe.
+  int stop_percent;
+
+  // should this be 0/1 ?  right now it is the port's sector number.
+  int active_port;  // port trading with
+  // information from the find_best_trades function + others.
+  int port[2];
+  int active;
+  // I don't care about trade type, just trades.
+  int type;
+  
+  buysell trades;
+  buysell port_buysell[2];
+
+  void activate(void) override;
+  void deactivate(void) override;
+
+  void server_line(const std::string &line,
+                   const std::string &raw_line) override;
+  void server_prompt(const std::string &prompt) override;
+  void client_input(const std::string &cinput) override;
+};
+
+
 
 class CoreDispatch : public Dispatch {
  public:

+ 4 - 3
scripts.h

@@ -22,7 +22,9 @@ class ScriptTrader : public Dispatch {
    * 3 = trade
    * 4 = if (burnt), stop, otherwise toggle active_port and state = 2
    *
-   *
+   * NEW: set port[1] to 0 for buy-only ability.
+   * Percent 20 doesn't work with Cargo Trans' 250 holds.
+   * Maybe look at the amount instead?  If < holds = burnt.
    */
   int state;
   float percent;
@@ -38,7 +40,7 @@ class ScriptTrader : public Dispatch {
   // information from the find_best_trades function + others.
   int port[2];
   int active;
-  // I probably don't care about trade type, juse
+  // I don't care about trade type, just trades.
   int type;
   
   buysell trades;
@@ -47,7 +49,6 @@ class ScriptTrader : public Dispatch {
   void activate(void) override;
   void deactivate(void) override;
 
-  // optional here
   void server_line(const std::string &line,
                    const std::string &raw_line) override;
   void server_prompt(const std::string &prompt) override;