lib = $lib; $this->game = $this->lib->game; $this->db = $this->game->db; $this->main = $this->game->getLib(); $this->planets = $this->game->getLib('beta5/planet'); $this->players = $this->game->getLib('beta5/player'); $this->msgs = $this->game->getLib('beta5/msgs'); $this->fleets = $this->game->getLib('beta5/fleet'); $this->rankings = $this->game->getLib('main/rankings'); $this->rules = $this->game->getLib('beta5/rules'); $this->tech = $this->game->getLib('beta5/tech'); } public function runTick() { // Cache the whole research tree, we'll need that later $this->tech->call('getTree'); // Increase neutral population l::debug("Increasing neutral population"); $this->db->safeTransaction(array($this, 'increasePopulation'), array(null, false)); // Increase population and research $players = $this->db->safeTransaction(array($this, 'getPlayers')); $this->playerPopulation($players); $this->playerResearch($players); // Mass-update status and timers $this->db->safeTransaction(array($this, 'statusUpdates')); // Dry run of the rankings computation list($players, $playersById) = $this->db->safeTransaction( array($this->main, 'call'), array('updateRankings', true) ); // Restore neutral planets and decrease protection levels if ((int) $this->game->params['victory'] == 0) { $this->db->safeTransaction(array($this, 'restoreNeutrals')); $this->db->safeTransaction(array($this, 'decreaseProtection')); } // Update the data warehouse $this->db->safeTransaction(array($this, 'updateWarehouse'), array($players, $playersById)); } public function restoreNeutrals() { $q = $this->db->query("SELECT p.id FROM planet p, system s " . "WHERE p.owner IS NULL AND p.status = 0 AND s.id = p.system " . "AND s.nebula = 0" ); while ($r = dbFetchArray($q)) { $this->planets->call('restoreNeutral', $r[0]); } } public function decreaseProtection() { $q = $this->db->query( "SELECT s.id, s.prot, y.id FROM system s, player y " . "WHERE s.prot > 0 AND s.nebula = 0 " . "AND y.id = (SELECT DISTINCT p.owner FROM planet p " . "WHERE p.system = s.id AND owner IS NOT NULL) " . "FOR UPDATE OF s, y"); $removeProtection = array(); while ($r = dbFetchArray($q)) { if ($r[1] == 1) { $removeProtection[$r[0]] = $r[2]; } } $this->db->query("UPDATE system SET prot = prot - 1 WHERE prot > 1 AND nebula = 0"); foreach ($removeProtection as $system => $player) { $this->players->call('breakProtection', $player, 'EXP'); } } public function getPlayers() { $q = $this->db->query( "SELECT p.id, (a.status <> 'STD') FROM player p, account a " . "WHERE (p.quit IS NULL OR UNIX_TIMESTAMP(NOW()) - p.quit < 86400) " . "AND a.id = p.userid" ); $players = array(); while ($r = dbFetchArray($q)) { $players[$r[0]] = ($r[1] == 't'); } return $players; } private function playerResearch($players) { l::debug("Increasing player research"); foreach ($players as $playerID => $vacation) { $this->db->safeTransaction(array($this, 'research'), array($playerID, $vacation)); } } private function playerPopulation($players) { l::debug("Increasing player population"); foreach ($players as $playerID => $vacation) { l::trace("Increasing population for player #$playerID"); $this->db->safeTransaction(array($this, 'increasePopulation'), array($playerID, $vacation)); } } public function statusUpdates() { // Laws: reduce delays to enact/revoke laws $this->db->query("UPDATE research_player SET in_effect=in_effect-1 WHERE in_effect>1"); $this->db->query("UPDATE research_player SET in_effect=in_effect+1 WHERE in_effect<0"); // Remove research assistance $this->db->query("UPDATE player SET res_assistance=NULL WHERE res_assistance IS NOT NULL"); // Restrictions on new players $this->db->query("UPDATE player SET restrain=restrain-1 WHERE restrain >= 1"); // Decrease unhappiness because of Wormhole Super Novas and sales $this->db->query("UPDATE player SET bh_unhappiness=bh_unhappiness-1 WHERE bh_unhappiness>0"); $this->db->query("UPDATE planet SET bh_unhappiness=bh_unhappiness-1 WHERE bh_unhappiness>0 AND status=0"); // Probe building $this->db->query("UPDATE planet SET built_probe=FALSE WHERE built_probe"); } // Advance a player's research status. public function research($id, $onVacation) { $q = $this->db->query("SELECT COUNT(*) FROM research_player WHERE player=$id"); list($c) = dbFetchArray($q); if (!$c) { if ($this->lib->game->params['partialtechs'] == 1) { $this->tech->call('createTree', $id); } else { $q = $this->db->query("SELECT id FROM research"); while ($r = dbFetchArray($q)) { $this->db->query("INSERT INTO research_player(player,research,possible) " . "VALUES($id,{$r[0]},TRUE)"); } } } // Get the whole tech tree and the player's research points $rp = $this->tech->call('getPoints',$id); list($res, $rLeaf) = $this->tech->call('getTree', $id); // Reduce by half if we're giving assistance $q = $this->db->query("SELECT id FROM player WHERE res_assistance=$id"); if (dbCount($q)) { $rp = round($rp / 2); } // Are we receiving assistance? $q = $this->db->query("SELECT research,res_assistance FROM player WHERE id=$id"); list($rb, $ra) = dbFetchArray($q); if (!is_null($ra)) { $rp += round(min($this->tech->call('getPoints', $ra), $rp) / 2); } // If the player's on vacation, he gets a fourth of the RPs if ($onVacation) { $rp = round($rp / 4); } // If the player is under protection, he gets a +25% bonus if ($this->players->call('getProtectionLevel', $id)) { $rp = round($rp * 1.25); } // Divide research points between categories $rbl = explode('!',$rb); $rbp = array(); for ($i=$s=0;$i<3;$i++) { $rbp[$i] = floor($rbl[$i] * $rp / 100); $s += $rbp[$i]; } for ($i=0;$s<$rb;$i=($i+1)%3) { $rbp[$i] ++; $s ++; } $possible = array(array(), array(), array()); // Get current research topics $q = $this->db->query("SELECT p.research,p.points FROM research_player p,research r " . "WHERE p.research=r.id AND p.points>0 AND p.points $r[0], 'pts' => $r[1], 'max' => $res[$r[0]]['points'] ); $cat = $res[$r[0]]['category']; array_push($possible[$cat], $rp); $npc[$cat] ++; } // Get pending research topics $q = $this->db->query("SELECT research FROM research_player WHERE player=$id AND points=0 AND possible"); $pending = array(array(),array(),array()); while ($r = dbFetchArray($q)) { array_push($pending[$res[$r[0]]['category']], $r[0]); } // Get completed research $q = $this->db->query("SELECT p.research FROM research_player p,research r " . "WHERE p.research=r.id AND p.points=r.points AND (r.is_law OR p.in_effect <> 0) AND p.player=$id"); $pRes = array(); while ($r = dbFetchArray($q)) { $pRes[$r[0]] = 1; } // Find topics which have their dependencies satisfied for ($i=0;$i<3;$i++) { foreach ($pending[$i] as $p) { $ok = true; foreach ($res[$p]['depends_on'] as $dep) { $ok = $ok && $pRes[$dep]; } if ($ok) { array_push($possible[$i], array( 'id' => $p, 'pts' => 0, 'max' => $res[$p]['points'] )); } } } // Find points left for each category $left = array(); $nrec = 0; $points = 0; for ($i=0;$i<3;$i++) { $max = 0; foreach ($possible[$i] as $rt) { $max += $rt['max'] - $rt['pts']; } $left[$i] = max(0, $rbp[$i] - $max); if ($left[$i] == 0) { $nrec ++; } else { $points += $left[$i]; $rbp[$i] -= $left[$i]; } } // Reassign extra points if possible if ($points) { for ($i=0;$i<3;$i++) { if ($left[$i] == 0) { $rbp[$i] += floor($points / $nrec); } } } // Increment research points for existing research for ($i=0;$i<3;$i++) { while ($possible[$i][0]['pts'] && $npc[$i]) { $inc = min(floor($rbp[$i] / $npc[$i]), $possible[$i][0]['max'] - $possible[$i][0]['pts']); l::trace("Adding $inc points to research #{$possible[$i][0]['id']} for player #$id"); $this->db->query( "UPDATE research_player SET points = points + $inc " . "WHERE research = {$possible[$i][0]['id']} AND player = $id" ); array_shift($possible[$i]); $rbp[$i] -= $inc; $npc[$i] --; } } // Generate probabilities for new research: the lower a tech's required points, // the higher its probabillity $probabilities = array(array(), array(), array()); for ($i=0;$i<3;$i++) { if ($rbp[$i] == 0) { continue; } $sum = 0; foreach ($possible[$i] as $idx => $t) { $sum += $t['max']; } foreach ($possible[$i] as $idx => $t) { $raw = $sum / $t['max']; $n = ceil($raw * $raw); for ($j=0;$j<$n;$j++) { array_push($probabilities[$i], $idx); } } } // Start new research $doneSomething = true; $addedTechs = array(array(), array(), array()); for ($i=0;$i%3||$doneSomething;$i=($i+1)%3) { if ($i%3==0) { $doneSomething = false; } if (!$rbp[$i] || count($possible[$i]) == count($addedTechs[$i])) { continue; } $doneSomething = true; do { $ridx = $probabilities[$i][rand(0, count($probabilities[$i]) - 1)]; } while (in_array($ridx, $addedTechs[$i])); array_push($addedTechs[$i], $ridx); $r = $possible[$i][$ridx]; $inc = min($rbp[$i], $r['max']); $this->db->query( "UPDATE research_player SET points = points + $inc " . "WHERE research = {$r['id']} AND player = $id" ); l::trace("Starting research {$r['id']} with $inc points for player $id"); $rbp[$i] -= $inc; } } public function increasePopulation($id, $onVacation = false) { $rules = $this->rules->call('get', $id); if (!is_null($id) && $this->players->call('getProtectionLevel', $id)) { $rules['pop_growth_factor'] = min($rules['pop_growth_factor'] + 1, 5); } $pow = 1.45 - (0.05 * $rules['pop_growth_factor']); $vacFact = $onVacation ? 4 : 1; $q = $this->db->query("SELECT id,pop,max_pop,happiness,corruption,mfact+ifact FROM planet " . "WHERE owner" . (is_null($id) ? " IS NULL AND status=0" : "=$id") . " FOR UPDATE"); while ($r = dbFetchArray($q)) { $hapVal = ($r[3] - 50) / 12; $hapFct = exp($hapVal) / ((exp($hapVal)+1)*9); // Population growth for players that are on vacation is 1/4th of normal $p = round($r[1] + pow($hapFct, $pow) * $r[1] * ($r[2] - $r[1]) / ($vacFact * $r[2])); $growthRatio = $r[1] / $p; // Increase corruption $cDec = 5 + 3 * $r[3] / 10; if (!is_null($id)) { $ncor = max(0, $r[4] - $cDec); $x = ($r[1] - 2000) / 48000; $optFact = ($r[1] / 30) - 754 * $x * $x; $mCorInc = $r[5] / 3; $nf = ($r[5] > $optFact * 3) ? ($optFact * 3) : $r[5]; $x = abs($nf / $optFact - 1); $v = 10 + $x * $x * $x * 50; $ncor = min(32000, $ncor + $v); } else { $ncor = max(0, $r[4] - $cDec * 3); } $ncor = round($ncor * $growthRatio * $growthRatio * $growthRatio); $this->db->query("UPDATE planet SET pop=$p,corruption=$ncor WHERE id={$r[0]}"); $this->planets->call('updateHappiness', $r[0]); } } public function updateWarehouse(&$players, &$playersById) { // Get missing data for the warehouse: $this->at = time(); // -> IDR: $o = $this->rankings->call('getAll', $this->rankings->call('getType', 'p_idr')); foreach ($o as $r) { $players[$r['id']]['d_rank'] = $r['points']; } // -> Alliance tag: $q = $this->db->query("SELECT p.id,a.tag FROM alliance a,player p " . "WHERE (p.quit IS NULL OR UNIX_TIMESTAMP(NOW())-p.quit<86400)" . " AND p.a_status='IN' AND p.alliance IS NOT NULL AND NOT p.hidden" . " AND a.id=p.alliance"); while ($r = dbFetchArray($q)) { $players[$playersById[$r[0]]]['a_tag'] = $r[1]; } // -> Tech lists $lsts = array(); $q = $this->db->query("SELECT p.id,r.id FROM research r,research_player j,player p " . "WHERE (p.quit IS NULL OR UNIX_TIMESTAMP(NOW())-p.quit<86400) AND NOT p.hidden" . " AND j.player=p.id AND j.research=r.id AND j.points=r.points"); while ($r = dbFetchArray($q)) { if (!is_array($lsts[$r[0]])) { $lsts[$r[0]] = array(); } array_push($lsts[$r[0]], $r[1]); } foreach ($lsts as $pid => $tl) { $players[$playersById[$pid]]['tech_list'] = join(',', $tl); } // -> Planet lists and details $q = $this->db->query("SELECT p.id,r.id,r.name,r.pop,r.max_pop,r.ifact,r.mfact,r.turrets," . "r.happiness,r.beacon,r.abandon,r.corruption " . "FROM planet r,player p " . "WHERE (p.quit IS NULL OR UNIX_TIMESTAMP(NOW())-p.quit<86400) AND NOT p.hidden" . " AND r.owner=p.id"); $plPlanets = array(); while ($r = dbFetchArray($q)) { if (!is_array($plPlanets[$r[0]])) { $plPlanets[$r[0]] = array(); } array_push($plPlanets[$r[0]], array( "planet" => $r[1], "name" => $r[2], "pop" => $r[3], "max_pop" => $r[4], "ifact" => $r[5], "mfact" => $r[6], "turrets" => $r[7], "happiness" => $r[8], "beacon" => $r[9], "abandon" => $r[10], "corruption" => $r[11] )); } // Update the warehouse foreach ($players as $name => $data) { $pid = $data['player']; if (is_null($pid)) { l::warn("****** BUG: null player ID in the warehouse update loop"); continue; } foreach ($data as $k => $v) { $data[$k] = "'" . addslashes($v) . "'"; } if (is_null( $data['tech_list'] )) { $data['tech_list'] = "''"; } $qs = "INSERT INTO warehouse(" . join(',', array_keys($data)) . ") VALUES (" . join(',', array_values($data)) . ")"; $this->db->query($qs); $q = $this->db->query("SELECT id FROM warehouse WHERE tick_at={$this->at} AND player=$pid"); list($id) = dbFetchArray($q); if (!$id || is_null($plPlanets[$pid])) { continue; } foreach ($plPlanets[$pid] as $pdata) { $pdata['id'] = $id; foreach ($pdata as $k => $v) { $pdata[$k] = "'" . addslashes($v == '' ? '0' : $v) . "'"; } $qs = "INSERT INTO wh_planet(" . join(',', array_keys($pdata)) . ") VALUES (" . join(',', array_values($pdata)) . ")"; $this->db->query($qs); } } $this->db->query("DELETE FROM warehouse WHERE UNIX_TIMESTAMP(NOW())-tick_at>86400*60"); } } ?>