522 lines
15 KiB
PHP
522 lines
15 KiB
PHP
|
<?php
|
||
|
|
||
|
//-------------------------------------------------------------------
|
||
|
// "Battle tick": computes battle outcomes
|
||
|
//-------------------------------------------------------------------
|
||
|
|
||
|
class beta5_ticks_battle_library {
|
||
|
|
||
|
function beta5_ticks_battle_library($lib) {
|
||
|
$this->lib = $lib;
|
||
|
$this->db = $lib->game->db;
|
||
|
$this->fleets = $lib->game->getLib('beta5/fleet');
|
||
|
$this->msgs = $lib->game->getLib('beta5/msg');
|
||
|
$this->planets = $lib->game->getLib('beta5/planet');
|
||
|
$this->players = $lib->game->getLib('beta5/player');
|
||
|
$this->rules = $lib->game->getLib('beta5/rules');
|
||
|
$this->sales = $lib->game->getLib('beta5/sale');
|
||
|
$this->rankings = $lib->game->getLib('main/rankings');
|
||
|
}
|
||
|
|
||
|
|
||
|
function runTick() {
|
||
|
$this->idrInc = array();
|
||
|
$toThrow = null;
|
||
|
try {
|
||
|
$locations = $this->db->safeTransaction(array($this, 'getBattleLocations'));
|
||
|
foreach ($locations as $lId) {
|
||
|
$this->db->safeTransaction(array($this, 'battleAt'), array($lId), 5);
|
||
|
}
|
||
|
} catch (Exception $e) {
|
||
|
$toThrow = $e;
|
||
|
}
|
||
|
|
||
|
$this->db->safeTransaction(array($this, 'updateStatus'));
|
||
|
if (!is_null($toThrow)) {
|
||
|
throw $toThrow;
|
||
|
}
|
||
|
|
||
|
$this->db->safeTransaction(array($this, 'updateRankings'));
|
||
|
}
|
||
|
|
||
|
|
||
|
public function getBattleLocations() {
|
||
|
$q = $this->btQuery(
|
||
|
"SELECT DISTINCT location FROM fleet WHERE location IS NOT NULL AND attacking"
|
||
|
);
|
||
|
$locations = array();
|
||
|
while ($r = dbFetchArray($q)) {
|
||
|
array_push($locations, $r[0]);
|
||
|
}
|
||
|
return $locations;
|
||
|
}
|
||
|
|
||
|
|
||
|
public function updateStatus() {
|
||
|
// Mark fleets that were in battle as available and commit
|
||
|
$this->btQuery("UPDATE fleet SET can_move='Y' WHERE can_move='B'");
|
||
|
}
|
||
|
|
||
|
public function updateRankings() {
|
||
|
// Increase inflicted damage points
|
||
|
$rt = $this->rankings->call('getType', 'p_idr');
|
||
|
$rl = $this->rankings->call('getAll', $rt);
|
||
|
$idr = array();
|
||
|
foreach ($rl as $r) {
|
||
|
$idr[$r['id']] = $r['points'];
|
||
|
}
|
||
|
foreach ($this->idrInc as $n => $inc) {
|
||
|
$idr[$n] += $inc;
|
||
|
}
|
||
|
$idrR = array();
|
||
|
foreach ($idr as $n => $p) {
|
||
|
if (!is_array($idrR[$p])) {
|
||
|
$idrR[$p] = array();
|
||
|
}
|
||
|
array_push($idrR[$p], $n);
|
||
|
}
|
||
|
$this->rankings->call('update', $rt, $idrR);
|
||
|
}
|
||
|
|
||
|
|
||
|
public function battleAt($lId) {
|
||
|
// Execute computations
|
||
|
$idrInc = $this->battleComputation($lId);
|
||
|
|
||
|
// Increase stored IDR
|
||
|
foreach ($idrInc as $n => $ii) {
|
||
|
if (is_null($this->idrInc[$n])) {
|
||
|
$this->idrInc[$n] = $ii;
|
||
|
} else {
|
||
|
$this->idrInc[$n] += $ii;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
private function battleComputation($lId) {
|
||
|
static $rnames = array('turret', 'gaship', 'fighter', 'cruiser', 'bcruiser');
|
||
|
static $addLosses = array(.5, .25, 1, .25, .125);
|
||
|
|
||
|
// Get the ID of the planet's owner and the amount of turrets
|
||
|
$q = $this->btQuery("SELECT name,owner,turrets,vacation FROM planet WHERE id=$lId "
|
||
|
. "FOR UPDATE");
|
||
|
list($pName,$pOwner,$pTurrets,$vacation) = dbFetchArray($q);
|
||
|
$poFake = is_null($pOwner) ? 0 : $pOwner;
|
||
|
|
||
|
// Compute the power of the planet's turrets
|
||
|
if ($vacation == 'YES ') {
|
||
|
$defPower = 0;
|
||
|
$pTurrets = 0;
|
||
|
} else {
|
||
|
$defPower = $this->planets->call('getPower', $lId);
|
||
|
}
|
||
|
$turretPower = $defPower;
|
||
|
|
||
|
// Get all fleets at that location
|
||
|
$q = $this->btQuery("SELECT * FROM fleet WHERE location=$lId FOR UPDATE");
|
||
|
$attFleets = array(0,0,0,0,0,0,0,0);
|
||
|
$defFleets = array(0,0,0,0,0,0,0,0);
|
||
|
$attPlayers = $defPlayers = 0;
|
||
|
$fleets = array();
|
||
|
$players = array(
|
||
|
$poFake => array(false, $turretPower, $pTurrets, 0, 0, 0, 0, array())
|
||
|
);
|
||
|
$attPower = 0;
|
||
|
$cSales = null;
|
||
|
|
||
|
// Extract fleet data
|
||
|
while ($r = dbFetchHash($q)) {
|
||
|
if ( ($r['owner'] == $pOwner && $vacation == 'YES ')
|
||
|
|| ($r['attacking'] == 't' && $r['time_spent'] < 15) ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
$r['power'] = $this->fleets->call('getPower', $r['owner'], $r['gaships'],
|
||
|
$r['fighters'], $r['cruisers'], $r['bcruisers']);
|
||
|
if ($r['attacking'] == 't') {
|
||
|
$attFleets[0] += $r['gaships'];
|
||
|
$attFleets[1] += $r['fighters'];
|
||
|
$attFleets[2] += $r['cruisers'];
|
||
|
$attFleets[3] += $r['bcruisers'];
|
||
|
$attPower += $r['power'];
|
||
|
} else {
|
||
|
$defFleets[0] += $r['gaships'];
|
||
|
$defFleets[1] += $r['fighters'];
|
||
|
$defFleets[2] += $r['cruisers'];
|
||
|
$defFleets[3] += $r['bcruisers'];
|
||
|
$defPower += $r['power'];
|
||
|
}
|
||
|
|
||
|
if (is_null($players[$r['owner']])) {
|
||
|
$players[$r['owner']] = array(
|
||
|
$r['attacking'] == 't', $r['power'], 0, $r['gaships'],
|
||
|
$r['fighters'], $r['cruisers'], $r['bcruisers'], array()
|
||
|
);
|
||
|
if ($r['attacking'] == 't') {
|
||
|
$attPlayers ++;
|
||
|
} else {
|
||
|
$defPlayers ++;
|
||
|
}
|
||
|
} else {
|
||
|
$players[$r['owner']][1] += $r['power'];
|
||
|
$players[$r['owner']][3] += $r['gaships'];
|
||
|
$players[$r['owner']][4] += $r['fighters'];
|
||
|
$players[$r['owner']][5] += $r['cruisers'];
|
||
|
$players[$r['owner']][6] += $r['bcruisers'];
|
||
|
}
|
||
|
|
||
|
array_push($players[$r['owner']][7], $r['id']);
|
||
|
$fleets[$r['id']] = $r;
|
||
|
}
|
||
|
|
||
|
// If there is no defense at all, forget it
|
||
|
// (that can also mean that the local defense fleet is on vacation)
|
||
|
// Also skip if there are no attack fleets (could happen because of separate transactions)
|
||
|
if ($defPower == 0 || $attPower == 0) {
|
||
|
l::debug("Skipped battle on $pName, no fleets");
|
||
|
return array();
|
||
|
}
|
||
|
l::debug("Starting battle on $pName (owner: $poFake); $pTurrets turrets, vacation=$vacation");
|
||
|
|
||
|
// Compute the damage index, which is the proportion of damage inflicted by the biggest fleet
|
||
|
$dRandom = $defPower;
|
||
|
while ($dRandom > 1000) {
|
||
|
$dRandom = $dRandom / 10;
|
||
|
}
|
||
|
$rdPower = $defPower + rand(0, round($dRandom * 0.05));
|
||
|
l::debug("Defense: power= $defPower ; random= $dRandom; rPower= $rdPower");
|
||
|
|
||
|
$aRandom = $attPower;
|
||
|
while ($aRandom > 1000) {
|
||
|
$aRandom = $aRandom / 10;
|
||
|
}
|
||
|
$raPower = $attPower + rand(0, round($aRandom * 0.05));
|
||
|
l::debug("Attack: power= $attPower ; random= $aRandom; rPower= $raPower");
|
||
|
|
||
|
$bigDef = ($rdPower > $raPower);
|
||
|
$bigPower = $bigDef ? $rdPower : $raPower;
|
||
|
$smallPower = $bigDef ? $raPower : $rdPower;
|
||
|
$ratio = $bigPower / $smallPower;
|
||
|
$damageIndex = ($ratio > 10 ? 1 : ((exp($ratio - 1) / (exp($ratio - 1) + 1))));
|
||
|
$attDamage = round(($bigDef ? $damageIndex : (1 - $damageIndex)) * $attPower);
|
||
|
$defDamage = round(($bigDef ? (1-$damageIndex) : $damageIndex) * $defPower);
|
||
|
l::debug("Damage index: $damageIndex (attDamage: $attDamage ; defDamage: $defDamage)");
|
||
|
|
||
|
// Handle heroic defense
|
||
|
if (! $bigDef && $ratio >= 5 && rand(0, 10000) <= 200) {
|
||
|
$heroicDefense = true;
|
||
|
$addDamage = ceil($smallPower * rand(300, 400) / 100);
|
||
|
if ($addDamage > $bigPower / 5) {
|
||
|
$addDamage = ceil($bigPower / 5);
|
||
|
}
|
||
|
$attDamage += $addDamage;
|
||
|
l::debug("Heroic defense! Damage increased by $addDamage (-> $attDamage)");
|
||
|
} else {
|
||
|
$heroicDefense = false;
|
||
|
}
|
||
|
|
||
|
// Compute the amount of damage to each player
|
||
|
$defLosses = $attLosses = 0;
|
||
|
$plist = array_keys($players);
|
||
|
$turretLoss = $tPowerLoss = 0;
|
||
|
foreach ($plist as $id) {
|
||
|
l::debug(" -> Player $id");
|
||
|
if ($players[$id][0])
|
||
|
$losses = ($players[$id][1] / $attPower) * $attDamage;
|
||
|
else
|
||
|
$losses = ($players[$id][1] / $defPower) * $defDamage;
|
||
|
l::debug(" * losses = $losses");
|
||
|
|
||
|
$rules = $this->rules->call('get', ($id == 0) ? null : $id);
|
||
|
if ($damageIndex < 1 || ($players[$id][0] && !$bigDef || !$players[$id][0] && $bigDef)) {
|
||
|
$losses = ($losses / 100) * $rules['battle_losses'];
|
||
|
l::debug(" * losses = $losses after adjustment ({$rules['battle_losses']}%)");
|
||
|
} else {
|
||
|
l::debug(" * losses not adjusted");
|
||
|
}
|
||
|
|
||
|
// Compute damage for each type of ship
|
||
|
$probLoss = array(0, 0, 0, 0, 0); $oriFleet = array();
|
||
|
if ($players[$id][1] > 0) {
|
||
|
$sPowers = array(); $power = array();
|
||
|
$tLoss = 0;
|
||
|
$lossRatio = $losses / $players[$id][1];
|
||
|
for ($i=0;$i<5;$i++) {
|
||
|
$sPowers[$i] = ($rules[$rnames[$i] . "_power"] / 100)
|
||
|
* $rules['effective_fleet_power'];
|
||
|
$oriFleet[$i] = $players[$id][$i+2];
|
||
|
$power[$i] = $sPowers[$i] * $players[$id][$i+2];
|
||
|
$shipRatio = $power[$i] / $players[$id][1];
|
||
|
$pLoss = $shipRatio * $losses;
|
||
|
$probLoss[$i] = min($players[$id][$i+2], round($pLoss / $sPowers[$i]));
|
||
|
$tLoss += $probLoss[$i] * $sPowers[$i];
|
||
|
}
|
||
|
|
||
|
$i = $n = 0;
|
||
|
while ($tLoss < $losses && $n < 5) {
|
||
|
if ($probLoss[$i] < $players[$id][$i+2]) {
|
||
|
$probLoss[$i] ++;
|
||
|
$tLoss += $sPowers[$i];
|
||
|
$n = 0;
|
||
|
} else {
|
||
|
$n++;
|
||
|
}
|
||
|
$i = ($i + 1) % 5;
|
||
|
}
|
||
|
}
|
||
|
l::debug(" * ship losses (T/G/F/C/B) = " . join(', ', $probLoss)
|
||
|
. " out of " . join(', ', $oriFleet));
|
||
|
|
||
|
// If there are turret losses, remove turrets
|
||
|
if ($probLoss[0] > 0) {
|
||
|
$turretLoss = $probLoss[0];
|
||
|
$tPowerLoss = $this->planets->call('getPower', $pOwner, $turretLoss);
|
||
|
$this->btQuery("UPDATE planet SET turrets=turrets-$turretLoss WHERE id=$lId");
|
||
|
$tm = time();
|
||
|
$this->btQuery("DELETE FROM turhist WHERE $tm-moment>86400");
|
||
|
$this->btQuery("INSERT INTO turhist VALUES ($lId,$tm,-$turretLoss)");
|
||
|
|
||
|
// Mark the planet's sale to be cancelled if that applies
|
||
|
$q = $this->btQuery("SELECT id,player,finalized,sold_to FROM sale WHERE planet=$lId");
|
||
|
if (($r = dbFetchArray($q)) && is_null($cSales)) {
|
||
|
$cSales = $r;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Apply losses to the player's individual fleets
|
||
|
$removed = array(0, 0, 0, 0);
|
||
|
foreach ($players[$id][7] as $fid) {
|
||
|
$rff = $tif = 0;
|
||
|
$rem = array();
|
||
|
for ($i=0;$i<4;$i++) {
|
||
|
// Ships that must be destroyed
|
||
|
$toRemove = $probLoss[$i+1] - $removed[$i];
|
||
|
|
||
|
// Ships in the fleet
|
||
|
$inFleet = $fleets[$fid][$rnames[$i+1]."s"];
|
||
|
$tif += $inFleet;
|
||
|
|
||
|
// Remove ships
|
||
|
$fromFleet = min($toRemove, $inFleet);
|
||
|
$removed[$i] += $fromFleet;
|
||
|
$rff += $fromFleet;
|
||
|
$rem[$i] = $fromFleet;
|
||
|
}
|
||
|
l::debug(" * fleet $fid losses (G/F/C/B) = " . join(', ', $rem));
|
||
|
|
||
|
// Mark the fleet's sale to be cancelled if that applies
|
||
|
if ($rff) {
|
||
|
$q = $this->btQuery("SELECT id,player,finalized,sold_to FROM sale WHERE fleet=$fid");
|
||
|
if ($r = dbFetchArray($q)) {
|
||
|
if (is_null($r[2])) {
|
||
|
$ga = 'cancel';
|
||
|
} else {
|
||
|
$ga = 'cancelTransfer';
|
||
|
}
|
||
|
$this->sales->call($ga, $r[1], $r[0]);
|
||
|
|
||
|
// FIXME: send messages
|
||
|
if (!is_null($cSales) && $cSales == $r[0]) {
|
||
|
$cSales = null;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ($rff == $tif) {
|
||
|
// The whole fleet has been lost
|
||
|
$this->btQuery("DELETE FROM fleet WHERE id=$fid");
|
||
|
} elseif ($rff) {
|
||
|
// Fleet has suffered some losses
|
||
|
$qs = "UPDATE fleet SET ";
|
||
|
$qsi = false;
|
||
|
for ($i=0;$i<4;$i++) {
|
||
|
if ($rem[$i] == 0) {
|
||
|
continue;
|
||
|
}
|
||
|
if ($qsi) {
|
||
|
$qs .= ",";
|
||
|
} else {
|
||
|
$qsi = true;
|
||
|
}
|
||
|
$qs .= $rnames[$i+1]."s=".$rnames[$i+1]."s-".$rem[$i];
|
||
|
}
|
||
|
$qs .= " WHERE id=$fid";
|
||
|
$this->btQuery($qs);
|
||
|
} else {
|
||
|
// No losses, we're done
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Add losses to the correct array
|
||
|
if ($players[$id][0]) {
|
||
|
for ($i=0;$i<4;$i++) {
|
||
|
$attFleets[$i+4] += $probLoss[$i+1];
|
||
|
}
|
||
|
} else {
|
||
|
for ($i=0;$i<4;$i++) {
|
||
|
$defFleets[$i+4] += $probLoss[$i+1];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Store the player's losses
|
||
|
$lostPower = $this->fleets->call('getPower', ($id==0 ? null : $id), $probLoss[1], $probLoss[2], $probLoss[3], $probLoss[4]);
|
||
|
$lostPower += $this->planets->call('getPower', ($id==0 ? null : $id), $probLoss[0]);
|
||
|
array_shift($probLoss);
|
||
|
$players[$id][8] = $probLoss;
|
||
|
$players[$id][9] = $lostPower;
|
||
|
if ($players[$id][0]) {
|
||
|
$attLosses += $lostPower;
|
||
|
} else {
|
||
|
$defLosses += $lostPower;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Cancel the planet's sale if it suffered damage
|
||
|
if (!is_null($cSales)) {
|
||
|
if (is_null($cSales[2])) {
|
||
|
$ga = 'cancel';
|
||
|
} else {
|
||
|
$ga = 'cancelTransfer';
|
||
|
}
|
||
|
$this->sales->call($ga, $cSales[1], $cSales[0]);
|
||
|
// FIXME: send messages
|
||
|
}
|
||
|
|
||
|
// Give the players "inflicted damage" points
|
||
|
$idrInc = array();
|
||
|
foreach ($plist as $id) {
|
||
|
if ($id == 0) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
$q = $this->btQuery("SELECT hidden FROM player WHERE id = $id");
|
||
|
list($hidden) = dbFetchArray($q);
|
||
|
if ($hidden == 't') {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if ($players[$id][0]) {
|
||
|
$tPower = $attPower;
|
||
|
$eLoss = $defLosses;
|
||
|
} else {
|
||
|
$tPower = $defPower;
|
||
|
$eLoss = $attLosses;
|
||
|
}
|
||
|
$ii = round(($players[$id][1] / $tPower) * $eLoss);
|
||
|
$n = $this->players->call('getName', $id);
|
||
|
if (is_null($idrInc[$n])) {
|
||
|
$idrInc[$n] = $ii;
|
||
|
} else {
|
||
|
$idrInc[$n] += $ii;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Send battle reports
|
||
|
$tm = time();
|
||
|
foreach ($plist as $id) {
|
||
|
// Avoid the fake "neutral" player
|
||
|
if ($id == 0) {
|
||
|
continue;
|
||
|
}
|
||
|
$p = $players[$id];
|
||
|
|
||
|
// Get friendly/hostile data
|
||
|
if ($players[$id][0]) {
|
||
|
$fPower = $attPower;
|
||
|
$ePower = $defPower;
|
||
|
$fLosses = $attLosses;
|
||
|
$eLosses = $defLosses;
|
||
|
$fFleets = $attFleets;
|
||
|
$eFleets = $defFleets;
|
||
|
$tMode = ($pTurrets != 0 ? 3 : 0);
|
||
|
} else {
|
||
|
$fPower = $defPower;
|
||
|
$ePower = $attPower;
|
||
|
$fLosses = $defLosses;
|
||
|
$eLosses = $attLosses;
|
||
|
$fFleets = $defFleets;
|
||
|
$eFleets = $attFleets;
|
||
|
$tMode = ($pTurrets != 0 ? ($pOwner == $id ? 1 : 2) : 0);
|
||
|
}
|
||
|
|
||
|
// Remove the player's own statistics from the list of friendlies
|
||
|
$fPower -= $p[1]; $fLosses -= $p[9];
|
||
|
for ($i=0;$i<4;$i++) {
|
||
|
$fFleets[$i] -= $p[$i+3];
|
||
|
$fFleets[$i+4] -= $p[8][$i];
|
||
|
}
|
||
|
|
||
|
// Send battle report
|
||
|
$this->msgs->call('send', $id, "battle", array(
|
||
|
"planet_id" => $lId,
|
||
|
"planet" => $pName,
|
||
|
"o_gaships" => $p[3],
|
||
|
"o_fighters" => $p[4],
|
||
|
"o_cruisers" => $p[5],
|
||
|
"o_bcruisers" => $p[6],
|
||
|
"o_power" => $p[1],
|
||
|
"ol_gaships" => $p[8][0],
|
||
|
"ol_fighters" => $p[8][1],
|
||
|
"ol_cruisers" => $p[8][2],
|
||
|
"ol_bcruisers" => $p[8][3],
|
||
|
"ol_power" => $p[9],
|
||
|
"a_gaships" => $fFleets[0],
|
||
|
"a_fighters" => $fFleets[1],
|
||
|
"a_cruisers" => $fFleets[2],
|
||
|
"a_bcruisers" => $fFleets[3],
|
||
|
"a_power" => $fPower,
|
||
|
"al_gaships" => $fFleets[4],
|
||
|
"al_fighters" => $fFleets[5],
|
||
|
"al_cruisers" => $fFleets[6],
|
||
|
"al_bcruisers" => $fFleets[7],
|
||
|
"al_power" => $fLosses,
|
||
|
"e_gaships" => $eFleets[0],
|
||
|
"e_fighters" => $eFleets[1],
|
||
|
"e_cruisers" => $eFleets[2],
|
||
|
"e_bcruisers" => $eFleets[3],
|
||
|
"e_power" => $ePower,
|
||
|
"el_gaships" => $eFleets[4],
|
||
|
"el_fighters" => $eFleets[5],
|
||
|
"el_cruisers" => $eFleets[6],
|
||
|
"el_bcruisers" => $eFleets[7],
|
||
|
"el_power" => $eLosses,
|
||
|
"turrets" => $pTurrets,
|
||
|
"tpower " => $turretPower,
|
||
|
"l_turrets" => $turretLoss,
|
||
|
"l_tpower" => $tPowerLoss,
|
||
|
"tmode" => $tMode,
|
||
|
"heroic_def" => $heroicDefense ? ($players[$id][0] ? -1 : 1) : 0
|
||
|
));
|
||
|
}
|
||
|
|
||
|
// Update happiness and attack status
|
||
|
$this->planets->call('updateHappiness', $lId);
|
||
|
$this->planets->call('updateMilStatus', $lId);
|
||
|
|
||
|
// If the planet was pending entrance in vacation mode and all enemy
|
||
|
// fleet is dead, set it to vacation mode.
|
||
|
if ($vacation == 'PEND') {
|
||
|
$q = $this->btQuery("SELECT COUNT(*) FROM fleet WHERE location=$lId AND attacking");
|
||
|
if ($q && dbCount($q) == 1) {
|
||
|
list($c) = dbFetchArray($q);
|
||
|
if ($c == 0) {
|
||
|
$this->btQuery("UPDATE planet SET vacation='YES' WHERE id=$lId");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return $idrInc;
|
||
|
}
|
||
|
|
||
|
|
||
|
function btQuery($q) {
|
||
|
$r = $this->db->query($q);
|
||
|
l::trace("Result '$r' for query: $q");
|
||
|
return $r;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
?>
|