This repository has been archived on 2024-07-18. You can view files and clone it, but cannot push or open issues or pull requests.
lwb5/scripts/game/beta5/ticks/battle/library.inc

522 lines
15 KiB
PHP
Raw Normal View History

2016-01-10 11:01:49 +01:00
<?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;
}
}
?>