Ejemplo n.º 1
0
 /**
  * Remove the spellcaster of the given type with the least energy.
  * @param Product $casterType
  * @param float $time Time of removal
  */
 public function remove($casterType, $time)
 {
     if (self::$debug) {
         tracemsg("Spellcasters::remove(" . $casterType . ", " . simple_time($time) . ")");
     }
     // choose a spellcaster to remove
     foreach ($this->select($casterType, $tagsRequired) as $spellcaster) {
         if ($spellcaster->created <= $time) {
             if (!isset($candidate)) {
                 $candidate = $spellcaster;
             } elseif ($spellcaster->energy() < $candidate->energy()) {
                 $candidate = $spellcaster;
             }
         }
     }
     // if no such spellcaster exists, throw an error
     if (!isset($candidate)) {
         throw_error("No spellcaster of type <i>" . $casterType->name . "</i> could be removed.", "This error message should not occur. Please report this message with your build order on the thread linked at bottom of the page.");
     }
     // mark it as destroyed
     if (self::$debug) {
         tracemsg("Spellcasters::remove(), chosen " . $candidate->casterType . " created at " . simple_time($candidate->created));
     }
     $candidate->destroyed = $time;
 }
Ejemplo n.º 2
0
 /**
  * Calculate time when job would be completed, and expend production queues.
  * @global Product $ChronoBoost
  * @global Product $Nexus
  * @global Product $Warpgate
  * @param Job $job
  * @param float $time
  * @param bool $tentative If true, chronoboosts will not be logged. Use this
  * to perform dry runs of the queue use.
  * @return array(int,array) First element is time job would be completed,
  * second element is list of production queues used.
  */
 public function queue($job, $time = null, $tentative = false)
 {
     global $ChronoBoost, $Nexus, $Warpgate;
     if ($this->debug) {
         tracemsg("Timeline::queue(" . $job . ", " . simple_time($time) . ", " . ($tentative ? "true" : "false") . ")");
     }
     if ($time === null) {
         $time = $job->timeStarted;
     }
     if ($this->debug) {
         tracemsg("Timeline::queue(), job starts at " . simple_time($time));
     }
     // choose queues
     list($queueTypesExpended, $expendAll) = $job->queueTypesExpended();
     if ($queueTypesExpended !== null) {
         $queues = $this->queues->choose($time, $queueTypesExpended, $expendAll, $job->tagsRequired);
     }
     // build time
     $buildTime = $job->duration();
     if (isset($queues) && count($queues) == 1 && $queues[0]->structure == $Warpgate) {
         $buildTime -= WARPGATE_QUEUE_REDUCTION;
     }
     // previous chrono boost overlaps this job
     if (isset($queues) && count($queues) == 1 && $queues[0]->chronoboosted + $ChronoBoost->timeCost > $time) {
         $boostTime = $queues[0]->chronoboosted;
         // calculate overlap with job
         $overlapStart = max($boostTime, $time);
         $overlapEnd = min($boostTime + $ChronoBoost->timeCost * CHRONO_BOOST_RATE, $time + $buildTime);
         $overlap = max(0, $overlapEnd - $overlapStart);
         // reduce build time
         $buildTime -= $overlap - $overlap / CHRONO_BOOST_RATE;
     }
     // chrono boosts
     if (isset($queues) && count($queues) == 1) {
         // process chronoboosts in an alternate reality
         $spellcasters = clone $this->spellcasters;
         for ($i = 0; $i < $job->chronoboost; $i++) {
             // start time of chrono boost
             $boostTime = max($queues[0]->chronoboosted + $ChronoBoost->timeCost, $spellcasters->when($Nexus, $ChronoBoost->energyCost));
             if ($boostTime < $time + $buildTime) {
                 $boostTime = max($boostTime, $time + CHRONO_BOOST_HUMAN_DELAY);
                 // calculate overlap with job
                 $overlapStart = max($boostTime, $time);
                 $overlapEnd = min($boostTime + $ChronoBoost->timeCost * CHRONO_BOOST_RATE, $time + $buildTime);
                 $overlap = max(0, $overlapEnd - $overlapStart);
                 // reduce build time
                 $buildTime -= $overlap - $overlap / CHRONO_BOOST_RATE;
                 // expend spellcasters
                 $spellcasters->update($boostTime);
                 $spellcasters->expend($Nexus, $ChronoBoost->energyCost, $boostTime);
                 // log chronoboost & reserve energy
                 if (!$tentative) {
                     $this->addCheckpoint(new Checkpoint("<em>CB: " . $job->description() . "</em>", $boostTime, $boostTime + $ChronoBoost->timeCost));
                     $spellcaster = $this->spellcasters->reserve($Nexus, $ChronoBoost->energyCost, $boostTime);
                 }
                 // queue is now chrono boosted
                 $queues[0]->chronoboosted = $boostTime;
             }
         }
     }
     // build complete
     $completed = $time + $buildTime;
     // queue is now unavailable
     if (isset($queues)) {
         foreach ($queues as $queue) {
             $queue->busy($time, $completed, $job->busiesQueues());
         }
         return array($completed, $queues);
     } else {
         return array($completed, null);
     }
 }
Ejemplo n.º 3
0
 /**
  * Calculate time when another vomit can be queued on any hatchery.
  * @return float
  */
 public function whenVomit()
 {
     $time = INF;
     foreach ($this->_hatcheries as $hatchery) {
         $time = min($time, $hatchery->whenVomit());
     }
     if (self::$debug) {
         tracemsg(($this->_isClone ? "lon " : "") . "Hatcheries::whenVomit(), returns " . simple_time($time));
     }
     return $time;
 }
Ejemplo n.º 4
0
 /**
  * Schedule as a floating job that can be squeezed in without delaying the
  * fixed job. If there is supply gap before the fixed job, a floating
  * jobs is scheduled as needed to bridge the gap, possibly delaying the
  * fixed job.
  * @param Job $fixedJob Fixed job
  * @param bool $recurring If set, consider either only recurring or
  * non-recurring jobs.
  * @return bool True, if a job could be squeezed in.
  */
 private function squeeze($fixedJob, $recurring = null)
 {
     Logger::enter("Scheduler::squeeze");
     if (self::$debug) {
         tracemsg("Scheduler::squeeze(" . $fixedJob . ", " . ($recurring === null ? "null" : ($recurring ? "true" : "false")) . ")");
     }
     // squeezing is mandatory if the fixed job is unavailable
     $mandatory = $fixedJob->availability->status != Availability::Available;
     if (self::$debug) {
         tracemsg("Scheduler::squeeze(), squeezing is " . ($mandatory ? "" : "not ") . "mandatory!");
     }
     // choose candidates
     if ($recurring === null) {
         $candidates = array_merge($this->_floatingJobs, $this->_recurringJobs);
     } elseif ($recurring) {
         $candidates = $this->_recurringJobs;
     } else {
         $candidates = $this->_floatingJobs;
     }
     if (self::$debug) {
         tracemsg("Scheduler::squeeze(), choosing from " . count($candidates) . " candidates!");
     }
     // ignore recurring candidates that build the same product as the fixed job
     if (!$mandatory && $fixedJob->productBuilt() !== null) {
         foreach ($candidates as $key => $job) {
             if ($job->recurring && $job->productBuilt() !== null && $job->productBuilt()->uid == $fixedJob->productBuilt()->uid) {
                 if (self::$debug) {
                     tracemsg("Scheduler::squeeze(), eliminating " . $job . ", which builds the same product as " . $fixedJob . ".");
                 }
                 unset($candidates[$key]);
             }
         }
     }
     // ignore candidates that are not available before fixed job starts
     // if mandatory, instead ignore candidates that are not available ever
     foreach ($candidates as $key => $job) {
         $this->_timeline->calculate($job, $this->_scheduledJobs);
         if (!$mandatory && $job->timeStarted > $fixedJob->timeStarted) {
             if (self::$debug) {
                 tracemsg("Scheduler::squeeze(), eliminating " . $job . ", which is available at " . simple_time($job->timeStarted) . ", but fixed job starts at " . simple_time($fixedJob->timeStarted));
             }
             unset($candidates[$key]);
         } elseif ($mandatory && $job->timeStarted === INF) {
             if (self::$debug) {
                 tracemsg("Scheduler::squeeze(), eliminating " . $job . ", which is unavailable because " . $job->availability);
             }
             unset($candidates[$key]);
         }
     }
     // ignore jobs that affect supply the wrong way
     if (isset($fixedJob->triggerSupply)) {
         $supplyGap = $fixedJob->triggerSupply - $this->_timeline->supplyCount;
         if (self::$debug) {
             tracemsg("Scheduler::squeeze(), supply gap is " . $supplyGap . ", current supply count is " . $this->_timeline->supplyCount . ", fixed job is triggered at " . $fixedJob->triggerSupply);
         }
         foreach ($candidates as $key => $job) {
             if ($supplyGap == 0 && $job->supplyCost(true) != 0) {
                 if (self::$debug) {
                     tracemsg("Scheduler::squeeze(), eliminating <i>" . $job . "</i>; Supply gap is " . $supplyGap . ", job's supply cost is " . $job->supplyCost(true));
                 }
                 unset($candidates[$key]);
             } elseif ($supplyGap > 0 && ($job->supplyCost(true) > $supplyGap || $job->supplyCost(true) < 0)) {
                 if (self::$debug) {
                     tracemsg("Scheduler::squeeze(), #3 Eliminating " . $job);
                 }
                 unset($candidates[$key]);
             } elseif ($supplyGap < 0 && ($job->supplyCost(true) < $supplyGap || $job->supplyCost(true) > 0)) {
                 if (self::$debug) {
                     tracemsg("Scheduler::squeeze(), #4 Eliminating " . $job);
                 }
                 unset($candidates[$key]);
             }
         }
     }
     // if not mandatory, ignore jobs that exceed surplus minerals or gas
     if (!$mandatory) {
         foreach ($candidates as $key => $job) {
             // always allow jobs that don't cost resources
             if ($job->mineralCost() == 0 && $job->gasCost() == 0) {
                 continue;
             }
             // the job affects income
             $mutations = $job->mutations();
             $mutations->sort();
             if (count($mutations) > 0) {
                 // calculate job start & complete time
                 $jobComplete = $this->_timeline->whenComplete($job);
                 // set up alternate reality income
                 $income = clone $this->_timeline->income;
                 foreach ($mutations as $mutation) {
                     $income->splice($mutation);
                 }
                 // the job does not affect income
             } else {
                 $income = $this->_timeline->income;
             }
             // if surplus is not great enough
             list($gasSurplus, $mineralSurplus) = $income->surplus($fixedJob->timeStarted);
             if (round($gasSurplus) < $job->gasCost() + $fixedJob->gasCost()) {
                 if (self::$debug) {
                     tracemsg("Scheduler::squeeze(), eliminating " . $job . ". Gas needed for both jobs is " . ($job->gasCost() + $fixedJob->gasCost()) . ", gas surplus is " . $gasSurplus . ".");
                 }
                 unset($candidates[$key]);
             }
             if (round($mineralSurplus) < $job->mineralCost() + $fixedJob->mineralCost()) {
                 if (self::$debug) {
                     tracemsg("Scheduler::squeeze(), eliminating " . $job . ". Mineral needed for both jobs is " . ($job->mineralCost() + $fixedJob->mineralCost()) . ", mineral surplus is " . $mineralSurplus . ".");
                 }
                 unset($candidates[$key]);
             }
         }
     }
     // if not mandatory, ignore jobs whose larvae, production queue
     // or spellcaster usage would stall fixed job
     if (!$mandatory) {
         foreach ($candidates as $key => $job) {
             if (!$this->_timeline->canAccommodate($job, $fixedJob)) {
                 if (self::$debug) {
                     tracemsg("Scheduler::squeeze(), #16 Eliminating " . $job);
                 }
                 unset($candidates[$key]);
             }
         }
     }
     // ignore candidates that exceed supply gap
     $supplyGap = $this->supplyGap();
     foreach ($candidates as $key => $job) {
         if ($supplyGap >= 0 && $job->supplyCost(true) > $supplyGap) {
             if (self::$debug) {
                 tracemsg("Scheduler::squeeze(), #9 Eliminating " . $job);
             }
             unset($candidates[$key]);
         }
     }
     // ignore jobs that cause fixed job to exceed supply capacity
     foreach ($candidates as $job) {
         if ($job->supplyCost(true) > 0) {
             // how much supply capacity is needed
             $supplyNeeded = $this->_timeline->supplyCount + $fixedJob->supplyCost(true) + $job->supplyCost(true);
             // discard candidate if supply capacity is not available
             $time = $this->_timeline->farms->when($supplyNeeded);
             if (!$mandatory && $time > $fixedJob->timeStarted) {
                 if (self::$debug) {
                     tracemsg("Scheduler::squeeze(), #14 Eliminating " . $job);
                 }
                 unset($candidates[$key]);
             } elseif ($mandatory && $time === INF) {
                 if (self::$debug) {
                     tracemsg("Scheduler::squeeze(), #15 Eliminating " . $job);
                 }
                 unset($candidates[$key]);
             }
         }
     }
     // no floating jobs are available
     if (count($candidates) == 0) {
         if (self::$debug) {
             tracemsg("Scheduler::squeeze(), all jobs were eliminated!");
         }
         // if mandatory, throw an error
         if ($mandatory) {
             $this->reportUnavailable(array($fixedJob));
         }
         Logger::leave("Scheduler::squeeze");
         return false;
     }
     // choose earliest available job
     $job = $this->earliest($candidates);
     // process floating job
     if (self::$debug) {
         $report = "";
         foreach ($candidates as $candidate) {
             $report .= (isset($notFirst) ? ", " : "") . $candidate . "(" . simple_time($candidate->timeStarted) . ")";
             $notFirst = true;
         }
         tracemsg("Scheduler::squeeze(), remaining candidates are " . $report);
     }
     if (self::$debug) {
         tracemsg("Scheduler::squeeze(), chosen " . $job);
     }
     $this->process($job);
     // reschedule, if recurring
     if ($job->recurring) {
         $this->_recurringJobs[] = clone $job;
     }
     Logger::leave("Scheduler::squeeze");
     return true;
 }
Ejemplo n.º 5
0
 /**
  * Calculate when the given queue types are available.
  * @param array $queueTypes
  * @param bool $expendsAll
  * @param array $tagsRequired
  * @return float
  */
 public function when($queueTypes, $expendsAll, $tagsRequired = null)
 {
     if (!isset($queueTypes) || count($queueTypes) == 0) {
         return $this->_lastUpdated;
     }
     // when are production queues available
     if (self::$debug) {
         tracemsg("ProductionQueues::when(), Looking for available queues");
     }
     $queuesAvailable = $expendsAll ? 0 : INF;
     $unavailableQueues = array();
     foreach ($queueTypes as $expend) {
         $queues = $this->select($expend, $tagsRequired);
         // when is production queue of this type available
         $queueAvailable = INF;
         foreach ($queues as $queue) {
             $queueAvailable = min($queueAvailable, $queue->available);
         }
         if (self::$debug) {
             tracemsg("ProductionQueues::when(), " . count($queues) . " Queues of type " . $expend . ", earliest available at " . simple_time($queueAvailable));
         }
         if ($queueAvailable === INF) {
             $unavailableQueues[] = $expend;
         }
         if ($expendsAll) {
             $queuesAvailable = max($queuesAvailable, $queueAvailable);
         } else {
             $queuesAvailable = min($queuesAvailable, $queueAvailable);
         }
     }
     if (self::$debug) {
         tracemsg("ProductionQueues::when(), all queues available at " . simple_time($queuesAvailable));
     }
     // some or all queues are unavailable
     if ($queuesAvailable === INF) {
         if (self::$debug) {
             tracemsg("ProductionQueues::when(), no production queue of type <i>" . implode("</i>, <i>", $unavailableQueues) . "</i> is available.");
         }
         return array(INF, $unavailableQueues);
     }
     return array(max($this->_lastUpdated, $queuesAvailable), null);
 }
Ejemplo n.º 6
0
 /**
  * Calculate when the given amount of resources is available.
  * @param float $mineralNeeded
  * @param float $gasNeeded
  * @return float
  */
 public function when($mineralNeeded, $gasNeeded)
 {
     if ($this->debug) {
         tracemsg("IncomeSlots::when(" . $mineralNeeded . ", " . $gasNeeded . ")");
     }
     // how much is needed
     $mineralNeeded -= $this->_mineralStored;
     $gasNeeded -= $this->_gasStored;
     // calculate breaking points
     foreach ($this->_slots as $slot) {
         list($mineralTimeInSlot, $gasTimeInSlot) = $slot->when($mineralNeeded, $gasNeeded);
         if ($mineralTimeInSlot === INF) {
             $mineralNeeded -= $slot->mineralRate() * $slot->duration();
         } elseif (!isset($mineralTime)) {
             $mineralTime = $mineralTimeInSlot;
         }
         if ($gasTimeInSlot === INF) {
             $gasNeeded -= $slot->gasRate() * $slot->duration();
         } elseif (!isset($gasTime)) {
             $gasTime = $gasTimeInSlot;
         }
         if (isset($gasTime) && isset($mineralTime)) {
             break;
         }
     }
     if (!isset($gasTime)) {
         $gasTime = INF;
     }
     if (!isset($mineralTime)) {
         $mineralTime = INF;
     }
     if ($this->debug) {
         tracemsg("IncomeSlots::when(), mineralTime=" . $mineralTime . ", gasTime=" . $gasTime);
     }
     if ($this->debug) {
         tracemsg("IncomeSlots::when(), storedMineral=" . $this->_mineralStored . ", storedGas=" . $this->_gasStored);
     }
     return max($mineralTime, $gasTime);
 }