Files
TravianZ/GameEngine/Automation/AutomationBattleResolution.php
T
novgorodschi catalin d89ffe4482 Major refactor: Extract Database and Automation into domain-specific trait files
### Core Architecture Refactor

- Split the `Database.php` class into domain-specific traits under `GameEngine/Database/`.
- Split the `Automation` class into domain-specific traits under `GameEngine/Automation/`.
- Grouped methods by functional domain for improved maintainability and navigation.
- Preserved 100% backward compatibility.
- No logic or behavioral changes.
- Pure structural refactor.
2026-07-17 11:03:27 +03:00

2968 lines
152 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
#################################################################################
## -= YOU MAY NOT REMOVE OR CHANGE THIS NOTICE =- ##
## --------------------------------------------------------------------------- ##
## Project: TravianZ ##
## Filename: AutomationBattleResolution.php ##
## Split&Refactor Shadow ##
## Purpose: sendunitsComplete + all battle resolution helper methods ##
## ##
## Phase S2: Trait extracted from GameEngine/Automation.php ##
## (Automation class). ##
## Methods were moved IDENTICALLY, with no logic changes. ##
## ##
## License: TravianZ Project ##
## Copyright: TravianZ (c) 2010-2026. All rights reserved. ##
## URLs: https://travianz.org ##
## https://github.com/Shadowss/TravianZ ##
#################################################################################
trait AutomationBattleResolution {
private function resolveCatapultsDestruction(&$bdo, &$battlepart, &$info_cat, &$data, $catapultTarget, $twoRowsCatapultSetup, $isSecondRow, $catp_pic, $can_destroy, $isoasis, &$village_destroyed, $tribe) {
global $battle, $database, $bid34;
if(isset($catapultTarget))
{
//Currently targeted building/field level
$tblevel = (int) $bdo['f'.$catapultTarget];
//Currently targetet building/field GID (ID of the building/field type - woodcutter, cropland, embassy...)
$tbgid = (int) $bdo['f'.$catapultTarget.'t'];
//Currently targeted building/field ID in the database (fdata, the fID field, e.g. f1, f2, f3...)
$tbid = (int) $catapultTarget;
//If we're targeting the WW
if($catapultTarget == 40){
$battlepart['catapults']['strongerBuildings'] = 1;
$battlepart['catapults']['moraleBonus'] = 1;
}
$catapultsDamage = $battle->calculateCatapultsDamage($data['t8'],
$battlepart['catapults']['upgrades'],
$battlepart['catapults']['durability'],
$battlepart['catapults']['attackDefenseRatio'],
$battlepart['catapults']['strongerBuildings'],
$battlepart['catapults']['moraleBonus']);
$newLevel = $battle->calculateNewBuildingLevel($tblevel, $catapultsDamage / ($twoRowsCatapultSetup ? 2 : 1));
//fix: Only modify build queue if actual damage was dealt
if ($newLevel < $tblevel) {
$database->modifyBData($data['to'], $tbid, [$newLevel, $tblevel], $tribe);
}
// building/field destroyed
if ($newLevel == 0){
// prepare data to be updated
$fieldsToSet = ["f".$tbid];
$fieldValuesToSet = [0];
// update $bdo, so we don't have to reselect later
$bdo['f'.$catapultTarget] = 0;
if ($tbid >= 19 && $tbid != 99) {
$fieldsToSet[] = "f".$tbid."t";
$fieldValuesToSet[] = 0;
$bdo['f'.$catapultTarget."t"] = 0;
}
// update all that needs updating
$database->setVillageLevel($data['to'], $fieldsToSet, $fieldValuesToSet);
$buildarray = $GLOBALS["bid".$tbgid];
if ( isset( $buildarray[$newLevel] ) ) {
// (great) warehouse level was changed
if ($tbgid == 10 || $tbgid == 38) {
$database->setMaxStoreForVillage($data['to'], $buildarray[$newLevel]['attri']);
}
// (great) granary level was changed
if ($tbgid == 11 || $tbgid == 39) {
$database->setMaxCropForVillage($data['to'], $buildarray[$newLevel]['attri']);
}
}
// oasis cannot be destroyed
$pop = $this->recountPop($data['to'], false);
if ($isoasis == 0 && $pop == 0 && $can_destroy == 1) $village_destroyed = 1;
if ($isSecondRow) {
if ($tbid > 0) {
$info_cat .= "<tbody class=\"goods\"><tr><th>".INFORMATION."</th><td colspan=\"11\">
<img class=\"unit u".$catp_pic."\" src=\"img/x.gif\" alt=\"".rc_tok('RC_CATAPULT')."\" title=\"".rc_tok('RC_CATAPULT')."\" /> ".rc_bld($tbgid, $can_destroy)." <b>".rc_tok('RC_DESTROYED')."</b>.";
}
// embassy level was changed
if ($tbgid == 18){
$info_cat .= $database->checkEmbassiesAfterBattle($data['to'], $bdo['f'.$catapultTarget], false);
}
$info_cat .= "</td></tr></tbody>";
} else {
$info_cat = "".$catp_pic.", ".rc_bld($tbgid, $can_destroy)." <b>".rc_tok('RC_DESTROYED')."</b>.";
// embassy level was changed
if ($tbgid == 18){
$info_cat .= $database->checkEmbassiesAfterBattle($data['to'], $bdo['f'.$catapultTarget], false);
}
}
}
// building/field not damaged
elseif($newLevel == $tblevel){
if($isSecondRow) {
if ($tbid > 0) {
$info_cat .= "<tbody class=\"goods\"><tr><th>".INFORMATION."</th><td colspan=\"11\">
<img class=\"unit u".$catp_pic."\" src=\"img/x.gif\" alt=\"".rc_tok('RC_CATAPULT')."\" title=\"".rc_tok('RC_CATAPULT')."\" /> ".rc_bld($tbgid, $can_destroy)." ".rc_tok('RC_NOT_DAMAGED')."</td></tr></tbody>";
}
} else {
$info_cat = "".$catp_pic.",".rc_bld($tbgid, $can_destroy)." ".rc_tok('RC_NOT_DAMAGED');
}
}
// building/field was damaged, let's calculate the actual damage
else
{
// update $bdo, so we don't have to reselect later
$bdo['f'.$catapultTarget] = $newLevel;
// building was damaged to a lower level
$info_cata = " ".rc_tok('RC_DAMAGED_FROM_TO', $tblevel, $newLevel);
$buildarray = $GLOBALS["bid".$tbgid];
// (great) warehouse level was changed
if ($tbgid == 10 || $tbgid == 38) {
$database->setMaxStoreForVillage($data['to'], $buildarray[$newLevel]['attri']);
}
// (great) granary level was changed
if ($tbgid == 11 || $tbgid == 39) {
$database->setMaxCropForVillage($data['to'], $buildarray[$newLevel]['attri']);
}
$fieldsToSet = ["f".$tbid];
$fieldValuesToSet = [$newLevel];
$database->setVillageLevel($data['to'], $fieldsToSet, $fieldValuesToSet);
// recalculate population and check if the village shouldn't be destroyed at this point
$pop = $this->recountPop($data['to'], false);
if ($isoasis == 0) {
if($pop == 0 && $can_destroy == 1) $village_destroyed = 1;
}
if ($isSecondRow) {
$info_cat .= "<tbody class=\"goods\"><tr><th>".INFORMATION."</th><td colspan=\"11\">
<img class=\"unit u".$catp_pic."\" src=\"img/x.gif\" alt=\"".rc_tok('RC_CATAPULT')."\" title=\"".rc_tok('RC_CATAPULT')."\" /> ".rc_bld($tbgid, $can_destroy).$info_cata;
// embassy level was changed
if ($tbgid == 18) {
$info_cat .= $database->checkEmbassiesAfterBattle($data['to'], $bdo['f'.$catapultTarget], false);
}
$info_cat .= "</td></tr></tbody>";
} else {
$info_cat = "" . $catp_pic . "," . rc_bld($tbgid, $can_destroy).$info_cata;
// embassy level was changed
if ($tbgid == 18) {
$info_cat .= $database->checkEmbassiesAfterBattle($data['to'], $bdo['f'.$catapultTarget], false);
}
}
}
}else{
if(!isset($info_cat) || empty($info_cat) || $info_cat == ","){
$info_cat = "".$catp_pic.", ".rc_tok('RC_NO_BUILDINGS');
}else if(strpos($info_cat, rc_tok('RC_NO_BUILDINGS')) === false){
$info_cat .= "<tbody class=\"goods\"><tr><th>".INFORMATION."</th><td colspan=\"11\">
<img class=\"unit u".$catp_pic."\" src=\"img/x.gif\" alt=\"".rc_tok('RC_CATAPULT')."\" title=\"".rc_tok('RC_CATAPULT')."\" /> ".rc_tok('RC_NO_BUILDINGS').".</td></tr></tbody>";
}
}
}
private function applyCatapults($data, $battlepart, $catp_pic, $can_destroy, $isoasis, $targettribe, $info_cat)
{
global $database;
// village_destroyed stays unset in the pop<=0 path of the original;
// downstream treats unset as 0, so initialising to 0 here is equivalent.
$village_destroyed = 0;
$pop = $this->recountPop($data['to']);
// village has been destroyed
if ($pop <= 0) {
if ($can_destroy == 1) $info_cat = "".$catp_pic.", ".rc_tok('RC_VILLAGE_ALREADY_DESTROYED');
else $info_cat = "".$catp_pic.", ".rc_tok('RC_VILLAGE_CANT_DESTROY');
}
else
{
// village stands, let's do the damage
/**
* FIRST CATAPULTS ROW
*/
$basearray = $data['to'];
$bdo = $database->getResourceLevel($basearray, false);
$catapultTarget = $data['ctar1'];
$catapultTarget2 = (isset($data['ctar2']) ? $data['ctar2'] : 0);
$catapults1TargetRandom = ($catapultTarget == 0);
$catapults2WillNotShoot = ($catapultTarget2 == 0);
$catapults2TargetRandom = ($catapults2WillNotShoot || $catapultTarget2 == 99);
// we're manually targetting 1st and/or 2nd row of catapults
if (!$catapults1TargetRandom)
{
$_catapultsTarget1Levels = [];
$__catapultsTarget1AltTargets = [];
// calculate targets for 1st rows of catapults
$j = 0;
for ($i = 1; $i <= 41; $i++)
{
if ($i == 41) $i = 99;
// 1st row of catapults pre-selected target calculations, if needed
if (!$catapults1TargetRandom && $bdo['f'.$i.'t'] == $catapultTarget && $bdo['f'.$i] > 0 && $i != 40)
{
$j++;
$_catapultsTarget1Levels[$j]=$bdo['f'.$i];
$__catapultsTarget1AltTargets[$j]=$i;
}
}
// if we couldn't find a suitable target for 1st row of catapults,
// select a random target instead
if (!$catapults1TargetRandom) {
if ( count( $_catapultsTarget1Levels ) > 0 ) {
if ( max( $_catapultsTarget1Levels ) <= 0 ) {
$catapultTarget = 0;
} else {
$catapultTarget = $__catapultsTarget1AltTargets[rand( 1, $j )];
}
} else {
$catapultTarget = 0;
$catapults1TargetRandom = true;
}
}
}
// 1st row of catapults set to target randomly
if ($catapults1TargetRandom)
{
$list = [];
for ($i = 1; $i <= 41; $i++)
{
if ($i == 41) $i = 99;
if ($bdo['f'.$i] > 0 && $i != 40) $list[] = $i;
}
$catapultTarget = $list[rand(0, count($list) - 1)];
}
/**
* resolve 1st row of catapults
*/
$village_destroyed = 0;
$this->resolveCatapultsDestruction($bdo, $battlepart, $info_cat, $data, $catapultTarget, !$catapults2WillNotShoot, false, $catp_pic, $can_destroy, $isoasis, $village_destroyed, $targettribe);
/**
* SECOND CATAPULTS ROW
*/
// we're manually targetting 2nd row of catapults
if (!$catapults2TargetRandom)
{
$_catapultsTarget2Levels = [];
$__catapultsTarget2AltTargets = [];
// calculate targets for 2nd rows of catapults
$j = 0;
for ($i = 1; $i <= 41; $i++)
{
if ($i == 41) $i = 99;
// 2nd row of catapults pre-selected target calculations, if needed
if (!$catapults2TargetRandom && !$catapults2WillNotShoot && $bdo['f'.$i.'t'] == $catapultTarget2 && $bdo['f'.$i] > 0 && $i != 40)
{
$j++;
$_catapultsTarget2Levels[$j] = $bdo['f'.$i];
$__catapultsTarget2AltTargets[$j] = $i;
}
}
// if we couldn't find a suitable target for 2nd row of catapults,
// select a random target instead
if (!$catapults2TargetRandom) {
if (count($_catapultsTarget2Levels) > 0 ) {
if (max($_catapultsTarget2Levels) <= 0 ) {
$catapultTarget2 = 99;
}
else $catapultTarget2 = $__catapultsTarget2AltTargets[rand( 1, $j )];
} else {
$catapultTarget2 = 99;
$catapults2TargetRandom = true;
}
}
}
// 2nd row of catapults set to target randomly
if ($catapults2TargetRandom && !$catapults2WillNotShoot)
{
$list = [];
for ($i = 1; $i <= 41; $i++)
{
if ($i == 41) $i = 99;
if ($bdo['f'.$i] > 0 && $i != 40) $list[] = $i;
}
$catapultTarget2 = $list[ rand(0, count($list) - 1) ];
}
/**
* resolve 2nd row of catapults
*/
if (!$catapults2WillNotShoot) {
$this->resolveCatapultsDestruction($bdo, $battlepart, $info_cat, $data, $catapultTarget2, true, true, $catp_pic, $can_destroy, $isoasis, $village_destroyed, $targettribe);
}
// clear resource levels cache, since we might have destroyed buildings/fields by now
call_user_func(get_class($database).'::clearResourseLevelsCache');
}
return ['battlepart' => $battlepart, 'info_cat' => $info_cat, 'village_destroyed' => $village_destroyed];
}
private function claimMovementRecord($moveid) {
global $database;
$moveid = (int)$moveid;
if ($moveid <= 0) {
return false;
}
$q = "UPDATE ".TB_PREFIX."movement SET proc = 1 WHERE moveid = $moveid AND proc = 0";
mysqli_query($database->dblink, $q);
return (mysqli_affected_rows($database->dblink) === 1);
}
/**
* Handle hero evasion: if the defender has evasion active and can afford it,
* send all defender units back to base and charge 2 gold + 1 evasion charge.
* Pure behaviour-preserving extraction (refactor for issue #155).
*
* @param array $data Current movement row (needs 'to').
* @param int $DefenderID Defender's user ID.
* @param array $DefenderUnit Unit counts for the defending village.
* @param int $targettribe Defender's tribe (1=Roman, 2=Teuton, 3=Gaul…).
* @param int $evasion Whether evasion is enabled (1) or not (0).
* @param int $maxevasion Remaining evasion charges.
* @param int $gold Defender's current gold.
* @param bool $cannotsend True when troops are already returning and can't be re-sent.
* @param int $attackType Type of incoming attack (must be > 2 to trigger evasion).
*/
private function handleEvasion(
array $data,
int $DefenderID,
array $DefenderUnit,
int $targettribe,
int $evasion,
int $maxevasion,
int $gold,
bool $cannotsend,
int $attackType
): void {
global $database;
if (!($evasion == 1 && $maxevasion > 0 && $gold > 1 && !$cannotsend && $attackType > 2)) {
return;
}
$playerunit = ($targettribe - 1) * 10;
$totaltroops = 0;
$evasionUnitModifications_units = [];
$evasionUnitModifications_amounts = [];
$evasionUnitModifications_modes = [];
for ($i = 1; $i <= 10; $i++) {
$playerunit += $i;
$data['u' . $i] = $DefenderUnit['u' . $playerunit];
$evasionUnitModifications_units[] = $playerunit;
$evasionUnitModifications_amounts[] = $DefenderUnit['u' . $playerunit];
$evasionUnitModifications_modes[] = 0;
$playerunit -= $i;
$totaltroops += $data['u' . $i];
}
$data['u11'] = $DefenderUnit['hero'];
$totaltroops += $data['u11'];
if ($totaltroops > 0) {
$evasionUnitModifications_units[] = 'hero';
$evasionUnitModifications_amounts[] = $DefenderUnit['hero'];
$evasionUnitModifications_modes[] = 0;
$attackid = $database->addAttack($data['to'], $data['u1'], $data['u2'], $data['u3'], $data['u4'], $data['u5'], $data['u6'], $data['u7'], $data['u8'], $data['u9'], $data['u10'], $data['u11'], 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
$database->addMovement(4, 0, $data['to'], $attackid, microtime(true), microtime(true) + (180 / EVASION_SPEED));
$database->updateUserField($DefenderID, ["gold", "maxevasion"], [$gold - 2, $maxevasion - 1], 1);
}
$database->modifyUnit($data['to'], $evasionUnitModifications_units, $evasionUnitModifications_amounts, $evasionUnitModifications_modes);
}
/**
* Process senator/chief attacks: reduce loyalty and, if it hits 0, conquer the village.
* Pure behaviour-preserving extraction (refactor for issue #155).
*
* @param array $data Current movement row.
* @param int $type Attack type (must be 3 for chiefing).
* @param int $dead9 Chiefs killed in battle.
* @param int $traped9 Chiefs caught in traps.
* @param array $from Attacker's village info row.
* @param array $to Defender's village info row.
* @param array $toF Defender's village field row (has loyalty).
* @param int $owntribe Attacker's tribe.
* @param int $targettribe Defender's tribe.
* @param array $varray All villages of the defender.
* @param array $varray1 All villages of the attacker.
* @param array $battlepart Battle result.
* @param int $isoasis 0 = village, non-zero = oasis.
* @param int $village_destroyed Whether the village has already been destroyed.
* @param int $chief_pic Chief unit sprite ID for report strings.
* @return array{info_chief:string, chiefing_village:int, village_destroyed:int}
*/
private function handleConquest(
array $data,
int $type,
int $dead9,
int $traped9,
int $targettribe,
array $from,
array $to,
array $toF,
int $owntribe,
array $varray,
array $varray1,
array $battlepart,
int $isoasis,
int $village_destroyed,
int $chief_pic
): array {
global $database, $units;
$info_chief = ',';
$chiefing_village = 0;
if (!(($data['t9'] - $dead9 - $traped9) > 0 && $isoasis == 0)) {
return ['info_chief' => $info_chief, 'chiefing_village' => $chiefing_village, 'village_destroyed' => $village_destroyed];
}
if ($type != 3) {
$info_chief = $chief_pic . ',' . rc_tok('RC_NO_REDUCE_CP_RAID');
return ['info_chief' => $info_chief, 'chiefing_village' => $chiefing_village, 'village_destroyed' => $village_destroyed];
}
$palacelevel = $database->getResourceLevel($from['wref']);
$plevel = 0;
for ($i = 1; $i <= 40; $i++) {
if ($palacelevel['f' . $i . 't'] == 26) $plevel = $i;
elseif ($palacelevel['f' . $i . 't'] == 25) $plevel = $i;
elseif ($palacelevel['f' . $i . 't'] == 44) $plevel = $i; // Command Center (Huni)
}
// Command Center: sloturi de expansiune ca la Palat (10 -> 1, 15 -> 2, 20 -> 3)
if ($palacelevel['f' . $plevel . 't'] == 44) {
if ($palacelevel['f' . $plevel] < 10) $canconquer = 0;
elseif ($palacelevel['f' . $plevel] < 15) $canconquer = 1;
elseif ($palacelevel['f' . $plevel] < 20) $canconquer = 2;
else $canconquer = 3;
} elseif ($palacelevel['f' . $plevel . 't'] == 26) {
if ($palacelevel['f' . $plevel] < 10) $canconquer = 0;
elseif ($palacelevel['f' . $plevel] < 15) $canconquer = 1;
elseif ($palacelevel['f' . $plevel] < 20) $canconquer = 2;
else $canconquer = 3;
} elseif ($palacelevel['f' . $plevel . 't'] == 25) {
if ($palacelevel['f' . $plevel] < 10) $canconquer = 0;
elseif ($palacelevel['f' . $plevel] < 20) $canconquer = 1;
else $canconquer = 2;
} else {
$canconquer = 0;
}
$expArray = $database->getVillageFields($from['wref'], 'exp1, exp2, exp3');
$villexp = ($expArray['exp1'] == 0) ? 0 : (($expArray['exp2'] == 0) ? 1 : (($expArray['exp3'] == 0) ? 2 : 3));
$mode = CP;
$cp_mode = $GLOBALS['cp' . $mode];
$need_cps = $cp_mode[count($varray1) + 1];
$user_cps = $database->getUserArray($from['owner'], 1)['cp'];
if ($user_cps < $need_cps) {
$info_chief = $chief_pic . ',' . rc_tok('RC_NOT_ENOUGH_CP');
return ['info_chief' => $info_chief, 'chiefing_village' => $chiefing_village, 'village_destroyed' => $village_destroyed];
}
if (count($varray) <= 1 || $to['capital'] == 1 || $villexp >= $canconquer) {
$info_chief = $chief_pic . ',' . rc_tok('RC_CANT_TAKEOVER');
return ['info_chief' => $info_chief, 'chiefing_village' => $chiefing_village, 'village_destroyed' => $village_destroyed];
}
if ($to['owner'] == 3 && $to['name'] == 'WW Buildingplan') {
$info_chief = $chief_pic . ',' . rc_tok('RC_CANT_TAKEOVER');
return ['info_chief' => $info_chief, 'chiefing_village' => $chiefing_village, 'village_destroyed' => $village_destroyed];
}
if ($database->getFieldLevelInVillage($data['to'], '25, 26, 44')) {
$info_chief = $chief_pic . ',' . rc_tok('RC_RESIDENCE_NOT_DESTROYED');
return ['info_chief' => $info_chief, 'chiefing_village' => $chiefing_village, 'village_destroyed' => $village_destroyed];
}
// --- Reduce loyalty ---
$time = time();
$reducedLoyaltyTotal = 0;
for ($i = 0; $i < ($data['t9'] - $dead9 - $traped9); $i++) {
$reducedLoyalty = ($owntribe == 1) ? rand(20, 30) : rand(20, 25);
if ($from['celebration'] > $time && $from['type'] == 2) $reducedLoyalty += 5;
if ($to['celebration'] > $time && $to['type'] == 2) $reducedLoyalty -= 5;
$reducedLoyalty /= $battlepart['moralBonus'];
// Bug fix: Brewery (35) is capital-only but empire-wide — its effect
// must be checked on the attacker's CAPITAL, not on $data['from'] (the
// launching village, which may not be the capital at all), and only
// while a Mead-Festival is actually active there, not just because
// the Brewery has been built (it has no permanent effect).
if ($owntribe == 2) {
$attackerCapital = $database->getVillage($from['owner'], 3);
if ($attackerCapital && (int)$attackerCapital['festival'] > $time && $this->getTypeLevel(35, $attackerCapital['wref']) > 0) {
$reducedLoyalty /= 2;
}
}
$reducedLoyaltyTotal += $reducedLoyalty;
}
if (($toF['loyalty'] - $reducedLoyaltyTotal) > 0) {
$info_chief = $chief_pic . ',' . rc_tok('RC_LOYALTY_LOWERED', floor($toF['loyalty']), floor($toF['loyalty'] - $reducedLoyaltyTotal));
$database->setVillageField($data['to'], 'loyalty', ($toF['loyalty'] - $reducedLoyaltyTotal));
return ['info_chief' => $info_chief, 'chiefing_village' => $chiefing_village, 'village_destroyed' => $village_destroyed];
}
if ($village_destroyed) {
return ['info_chief' => $info_chief, 'chiefing_village' => $chiefing_village, 'village_destroyed' => $village_destroyed];
}
// --- Village conquered ---
$villname = addslashes($database->getVillageField($data['to'], 'name'));
$artifact = reset($database->getOwnArtefactInfo($data['to']));
$info_chief = $chief_pic . ',' . rc_tok('RC_INHABITANTS_JOIN', $villname);
if ($artifact['vref'] == $data['to']) {
$database->claimArtefact($data['to'], $data['to'], $database->getVillageField($data['from'], 'owner'));
}
$database->setVillageFields($data['to'], ['loyalty', 'owner'], [0, $database->getVillageField($data['from'], 'owner')]);
// Milestones: first WW village ever conquered, and — separately —
// first village ever conquered FROM ANOTHER PLAYER (not from
// Natars). $to is this function's own parameter (not re-fetched),
// so $to['natar']/$to['owner'] still reflect the village's state
// from BEFORE this conquest, which is exactly what we need here.
// natar==1 marks one of the 13 pre-built WW conquest targets (see
// Artifacts::createWWVillages()) — Natars' capital and artifact/
// plan villages are natar=0, so this check cannot misfire on those.
if (defined('NEW_FUNCTIONS_MILESTONES') && NEW_FUNCTIONS_MILESTONES) {
$newOwner = $database->getVillageField($data['from'], 'owner');
if ((int)($to['natar'] ?? 0) === 1) {
$database->recordMilestoneIfFirst('first_ww', $newOwner, $data['to']);
} elseif ((int)($to['owner'] ?? 0) !== 3) {
$database->recordMilestoneIfFirst('first_pvp_conquest', $newOwner, $data['to']);
}
}
$database->query("DELETE FROM " . TB_PREFIX . "abdata WHERE vref = " . (int)$data['to']);
$database->addABTech($data['to']);
$database->query("DELETE FROM " . TB_PREFIX . "tdata WHERE vref = " . (int)$data['to']);
$database->addTech($data['to']);
$database->query("DELETE FROM " . TB_PREFIX . "enforcement WHERE `from` = " . (int)$data['to']);
$database->query("DELETE FROM " . TB_PREFIX . "route where wid = " . (int)$data['to'] . " OR `from` = " . (int)$data['to']);
$units2reset = [];
for ($u = 1; $u <= 90; $u++) $units2reset[] = 'u' . $u . ' = 0';
// cucerirea goleste spitalul si coada de vindecare
$database->clearHospital($data['to']);
$units2reset[] = 'u99 = 0';
$units2reset[] = 'u99o = 0';
$units2reset[] = 'hero = 0';
$database->query("UPDATE " . TB_PREFIX . "units SET " . implode(',', $units2reset) . " WHERE vref = " . (int)$data['to']);
$newLevels_fieldNames = [];
$newLevels_fieldValues = [];
$AttackerID = $database->getVillageField($data['from'], 'owner');
$DefenderID = $database->getVillageField($data['to'], 'owner');
$poparray = $database->getVSumField([$AttackerID, $DefenderID], 'pop');
$pop1 = $poparray[0]['Total'];
$pop2 = $poparray[1]['Total'];
if ($pop1 > $pop2 && $targettribe != 5) {
$buildlevel = $database->getResourceLevel($data['to']);
for ($i = 1; $i <= 39; $i++) {
if ($buildlevel['f' . $i] != 0) {
if ($buildlevel['f' . $i . 't'] != 35 && $buildlevel['f' . $i . 't'] != 36 && $buildlevel['f' . $i . 't'] != 41) {
// Main Building (gid 15) nu poate cobori sub nivel 1: satul trebuie
// sa ramana locuibil dupa cucerire (altfel pop 0 + nicio cladire).
$minLevel = ($buildlevel['f' . $i . 't'] == 15) ? 1 : 0;
$leveldown = $buildlevel['f' . $i] - 1;
if ($leveldown < $minLevel) $leveldown = $minLevel;
$newLevels_fieldNames[] = 'f' . $i;
$newLevels_fieldValues[] = $leveldown;
// sterge tipul cladirii DOAR daca a ajuns efectiv la 0
// (bug vechi: "!$leveldown > 0" era mereu adevarat pt leveldown==0,
// deci stergea tipul si cand cladirea cobora la nivel 1)
if ($leveldown <= 0) {
$newLevels_fieldNames[] = 'f' . $i . 't';
$newLevels_fieldValues[] = 0;
}
} else {
$newLevels_fieldNames[] = 'f' . $i;
$newLevels_fieldValues[] = 0;
$newLevels_fieldNames[] = 'f' . $i . 't';
$newLevels_fieldValues[] = 0;
}
}
}
if ($buildlevel['f99'] != 0) {
$newLevels_fieldNames[] = 'f99';
$newLevels_fieldValues[] = $buildlevel['f99'] - 1;
}
}
// destroy wall
$newLevels_fieldNames[] = 'f40';
$newLevels_fieldValues[] = 0;
$newLevels_fieldNames[] = 'f40t';
$newLevels_fieldValues[] = 0;
$database->clearExpansionSlot($data['to'], 1);
$expArray2 = $database->getVillageFields($data['from'], 'exp1, exp2, exp3');
if ($expArray2['exp1'] == 0) { $exp = 'exp1'; }
elseif ($expArray2['exp2'] == 0) { $exp = 'exp2'; }
else { $exp = 'exp3'; }
$database->setVillageField($data['from'], $exp, $data['to']);
$database->deleteTradeRoutesByVillage($data['to']);
$database->setVillageLevel($data['to'], $newLevels_fieldNames, $newLevels_fieldValues);
$units->returnTroops($data['to'], 1);
$chiefing_village = 1;
$database->reassignHero($data['to']);
$this->recountPop($data['to'], false);
return ['info_chief' => $info_chief, 'chiefing_village' => $chiefing_village, 'village_destroyed' => $village_destroyed];
}
/**
* Fetch all attacks (sort_type 3, not reinforcement) that have arrived by
* $time, joined with their attack rows, ordered by arrival.
* Pure behaviour-preserving extraction (refactor for issue #155).
*
* @param int $time Current timestamp.
* @return array Rows of pending/completed attacks.
*/
private function fetchCompletedAttacks($time) {
global $database;
$time = (int) $time;
$q = "
SELECT
`from`, `to`, endtime, ref, ctar1, ctar2, spy, moveid, attack_type,
t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, (SELECT oasistype FROM ".TB_PREFIX."wdata WHERE id = `to`) as oasistype
FROM
".TB_PREFIX."movement,
".TB_PREFIX."attacks
WHERE
".TB_PREFIX."movement.ref = ".TB_PREFIX."attacks.id
AND
".TB_PREFIX."movement.proc = 0
AND
".TB_PREFIX."movement.sort_type = 3
AND
".TB_PREFIX."attacks.attack_type != 2
AND
endtime < $time
ORDER BY endtime ASC";
return $database->query_return($q);
}
/**
* Batch-preload the village / unit / tech data used by sendunitsComplete()
* so the per-attack loop hits the in-request cache instead of querying row
* by row. Pure behaviour-preserving extraction (refactor for issue #155).
*
* @param array $dataarray Completed attacks (each with 'from' and 'to').
*/
private function preloadBattleData(array $dataarray) {
global $database;
$vilIDs = [];
foreach ($dataarray as $data) {
$vilIDs[$data['from']] = true;
$vilIDs[$data['to']] = true;
}
$vilIDs = array_keys($vilIDs);
$database->getProfileVillages($vilIDs, 5);
$database->getUnit($vilIDs);
$database->getEnforceVillage($vilIDs, 0);
$database->getMovement(34, $vilIDs, 1);
$database->getABTech($vilIDs);
$database->getMInfo($vilIDs);
}
private function buildScoutReport($data, $spy_pic, $isoasis, $targettribe, $crannySpy, $totwood, $totclay, $totiron, $totcrop) {
global $database;
$info_spy = "";
if ($data['spy'] == 1){
$info_spy = "".$spy_pic.",<div class=\"res\"><img class=\"r1\" src=\"img/x.gif\" alt=\"".LUMBER."\" title=\"".LUMBER."\" />".round($totwood)." |
<img class=\"r2\" src=\"img/x.gif\" alt=\"".CLAY."\" title=\"".CLAY."\" />".round($totclay)." |
<img class=\"r3\" src=\"img/x.gif\" alt=\"".IRON."\" title=\"".IRON."\" />".round($totiron)." |
<img class=\"r4\" src=\"img/x.gif\" alt=\"".CROP."\" title=\"".CROP."\" />".round($totcrop)."</div>
<div class=\"carry\"><img class=\"car\" src=\"img/x.gif\" alt=\"".rc_tok('CARRY')."\" title=\"".rc_tok('CARRY')."\" />".rc_tok('RC_TOTAL_RESOURCES')." ".round($totwood+$totclay+$totiron+$totcrop)."</div>
";
}else if($data['spy'] == 2){
if ($isoasis == 0){
$walllevel = $database->getFieldLevelInVillage($data['to'], '31, 32, 33, 42, 43, 47, 50');
$residencelevel = $database->getFieldLevelInVillage($data['to'], 25);
$palacelevel = $database->getFieldLevelInVillage($data['to'], 26);
$residenceimg = "<img src=\"".GP_LOCATE."img/g/g25.gif\" height=\"20\" width=\"15\" alt=\"".RESIDENCE."\" title=\"".RESIDENCE."\" />";
$palaceimg = "<img src=\"".GP_LOCATE."img/g/g26.gif\" height=\"20\" width=\"15\" alt=\"".PALACE."\" title=\"".PALACE."\" />";
$crannyimg = "<img src=\"".GP_LOCATE."img/g/g23.gif\" height=\"20\" width=\"15\" alt=\"".CRANNY."\" title=\"".CRANNY."\" />";
$wallgids = array(1 => 31, 2 => 32, 3 => 33, 6 => 43, 7 => 42, 8 => 47, 9 => 50);
$wallgid = isset($wallgids[$targettribe]) ? $wallgids[$targettribe] : 31;
$wallimg = "<img src=\"".GP_LOCATE."img/g/g".$wallgid."Icon.gif\" height=\"20\" width=\"15\" alt=\"".rc_tok('RC_WALL')."\" title=\"".rc_tok('RC_WALL')."\" />";
$info_spy = "".$spy_pic.",";
if($residencelevel > 0) $info_spy .= $residenceimg." ".rc_tok('RC_RESIDENCE_LEVEL')."<b>".$residencelevel."</b><br />";
elseif($palacelevel > 0) $info_spy .= $palaceimg." ".rc_tok('RC_PALACE_LEVEL')." <b>".$palacelevel."</b><br />";
if($walllevel > 0) $info_spy .= $wallimg." ".rc_tok('RC_WALL_LEVEL')." <b>".$walllevel."</b><br />";
$info_spy .= $crannyimg." ".rc_tok('RC_CRANNY_CAPACITY')." <b>".$crannySpy."</b>";
}
else $info_spy = "".$spy_pic.", ".rc_tok('RC_NO_INFO');
}
return $info_spy;
}
/**
* Release prisoners trapped in the target village during a conquest attack,
* send them home (with 25% casualties), repair destroyed traps, and return
* the HTML fragment for the battle-report trap line.
* Pure behaviour-preserving extraction (refactor for issue #155).
*
* @param array $data Current attack row.
* @param array $from Attacker village info (getMInfo).
* @param array $to Defender village info (getMInfo).
* @param int $ownally Attacker's alliance id.
* @param int $type Attack type (3 = normal with rams/catas).
* @param int $totalsend_att Total attacking troops sent.
* @param int $totaldead_att Total attacking troops killed.
* @param int $totaltraped_att Total attacking troops trapped.
* @return string HTML fragment for the trap report line; empty if no prisoners freed.
*/
private function handlePrisoners($data, $from, $to, $ownally, $type, $totalsend_att, $totaldead_att, $totaltraped_att) {
global $database, $units, $bid19, $u99;
if ($type != 3 || $totalsend_att - ($totaldead_att + $totaltraped_att) <= 0) {
return '';
}
$prisoners = $database->getPrisoners([$to['wref']], 0, false)[$to['wref'].'0'];
if (count($prisoners) == 0) {
return '';
}
$anothertroops = $mytroops = $ownDeads = $anotherDeads = 0;
$prisoners2delete = $movementType = $movementFrom = $movementTo = $movementRef = $movementTime = $movementEndtime = [];
$utime = microtime(true);
foreach ($prisoners as $prisoner) {
$p_owner = $database->getVillageField($prisoner['from'], "owner");
if ($prisoner['from'] == $from['wref']) {
for ($i = 1; $i <= 11; $i++) {
$deadPrisoners = round($prisoner['t'.$i] / 4);
$mytroops += $prisoner['t'.$i];
$ownDeads += $deadPrisoners;
$prisoner['t'.$i] -= $deadPrisoners;
}
$database->modifyAttack2(
$data['ref'],
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
[$prisoner['t1'], $prisoner['t2'], $prisoner['t3'], $prisoner['t4'], $prisoner['t5'],
$prisoner['t6'], $prisoner['t7'], $prisoner['t8'], $prisoner['t9'], $prisoner['t10'], $prisoner['t11']]
);
$prisoners2delete[] = $prisoner['id'];
} else {
$p_alliance = $database->getUserField($p_owner, "alliance", 0);
$friendarray = $database->getAllianceAlly($p_alliance, 1);
$neutralarray = $database->getAllianceAlly($p_alliance, 2);
$friend = ($friendarray[0]['alli1'] > 0 && $friendarray[0]['alli2'] > 0 && $p_alliance > 0)
&& ($friendarray[0]['alli1'] == $ownally || $friendarray[0]['alli2'] == $ownally)
&& ($ownally != $p_alliance && $ownally && $p_alliance);
$neutral = ($neutralarray[0]['alli1'] > 0 && $neutralarray[0]['alli2'] > 0 && $p_alliance > 0)
&& ($neutralarray[0]['alli1'] == $ownally || $neutralarray[0]['alli2'] == $ownally)
&& ($ownally != $p_alliance && $ownally && $p_alliance);
if ($p_alliance == $ownally || $friend || $neutral) {
$p_tribe = $database->getUserField($p_owner, "tribe", 0);
for ($i = 1; $i <= 11; $i++) {
$deadPrisoners = round($prisoner['t'.$i] / 4);
if ($p_owner == $from['owner']) {
$mytroops += $prisoner['t'.$i];
$ownDeads += $deadPrisoners;
} else {
$anothertroops += $prisoner['t'.$i];
$anotherDeads += $deadPrisoners;
}
$prisoner['t'.$i] -= $deadPrisoners;
}
$troopsTime = $units->getWalkingTroopsTime($prisoner['from'], $prisoner['wref'], $p_owner, $p_tribe, $prisoner, 1, 't');
$p_time = $database->getArtifactsValueInfluence($p_owner, $prisoner['from'], 2, $troopsTime);
$p_reference = $database->addAttack(
$prisoner['from'],
$prisoner['t1'], $prisoner['t2'], $prisoner['t3'], $prisoner['t4'], $prisoner['t5'],
$prisoner['t6'], $prisoner['t7'], $prisoner['t8'], $prisoner['t9'], $prisoner['t10'], $prisoner['t11'],
3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
);
$movementType[] = 4;
$movementFrom[] = $prisoner['wref'];
$movementTo[] = $prisoner['from'];
$movementRef[] = $p_reference;
$movementTime[] = $utime;
$movementEndtime[] = $p_time + $utime;
$prisoners2delete[] = $prisoner['id'];
}
}
}
if (count($movementType)) {
$database->addMovement($movementType, $movementFrom, $movementTo, $movementRef, $movementTime, $movementEndtime);
}
$database->deletePrisoners($prisoners2delete);
$ownDeadsText = ($ownAlive = $mytroops - $ownDeads) > 0 ? " ".rc_tok('RC_OF_WHICH_SAVED',$ownAlive) : "";
$anotherDeadsText = ($anotherAlive = $anothertroops - $anotherDeads) > 0 ? " ".rc_tok('RC_OF_WHICH_SAVED',$anotherAlive) : "";
$database->addGeneralAttack($ownDeads + $anotherDeads);
$newtraps = round(($mytroops + $anothertroops) / 3);
$database->modifyUnit($data['to'], ['99', '99o'], [$mytroops + $anothertroops, $mytroops + $anothertroops], [0, 0]);
if ($newtraps > 0) {
$repairDuration = $database->getArtifactsValueInfluence(
$to['owner'], $to['wref'], 5,
round(($bid19[max($this->getTypeLevel(36, $to['wref']), 1)]['attri'] / 100) * $u99['time'] / SPEED)
);
$database->trainUnit($to['wref'], 99, $newtraps, $u99['pop'], $repairDuration, 0);
}
$trapper_pic = "<img src=\"".GP_LOCATE."img/u/98.gif\" alt=\"".rc_tok('RC_TRAP')."\" title=\"".rc_tok('RC_TRAP')."\" />";
$p_username = $database->getUserField($from['owner'], "username", 0);
if ($mytroops > 0 && $anothertroops > 0) {
return $trapper_pic." <b>".$p_username."</b> ".rc_tok('RC_FREED_FROM_HIS_TROOPS', $mytroops).$ownDeadsText." ".rc_tok('RC_AND_FRIENDLY_TROOPS', $anothertroops).$anotherDeadsText.".";
} elseif ($mytroops > 0) {
return $trapper_pic." <b>".$p_username."</b> ".rc_tok('RC_FREED_FROM_HIS_TROOPS', $mytroops).$ownDeadsText.".";
} elseif ($anothertroops > 0) {
return $trapper_pic." <b>".$p_username."</b> ".rc_tok('RC_FREED_FRIENDLY_TROOPS', $anothertroops).$anotherDeadsText.".";
}
return '';
}
/**
* Assemble the battle-report data string ($data2) and the all-attacker-dead
* fallback ($data_fail) from pre-computed battle results.
* Pure behaviour-preserving extraction (refactor for issue #155).
*
* @param bool $scout True when this is a scouting mission.
* @param array $from Attacker village info (getMInfo).
* @param array $to Defender village info (getMInfo).
* @param int $owntribe Attacker tribe id.
* @param int $targettribe Defender tribe id.
* @param string $unitssend_att CSV of units sent by attacker.
* @param string $unitsdead_att CSV of attacker units killed.
* @param array $steal [wood, clay, iron, crop] looted amounts.
* @param array $battlepart calculateBattle() result.
* @param array $unitssend_def CSV of defender units sent (indexed 05).
* @param array $unitsdead_def CSV of defender units dead (indexed 05).
* @param array $unitssend_deff Masked defender units for data_fail (indexed 05).
* @param array $unitsdead_deff Masked dead units for data_fail (indexed 05).
* @param int $rom,$ger,$gal,$nat,$natar Tribe presence flags.
* @param string $DefenderHeroesTot CSV of defender heroes totals by tribe.
* @param string $DefenderHeroesDead CSV of defender heroes dead by tribe.
* @param string $info_ram Ram result fragment.
* @param string $info_cat Catapult result fragment (internally extended if village destroyed).
* @param string $info_chief Chief/senator result fragment.
* @param string $info_spy Scout report fragment (scout missions only).
* @param string $info_trap Prisoner-release fragment (from handlePrisoners).
* @param string $info_hero Hero result fragment.
* @param string $info_troop "None of your soldiers returned" fragment.
* @param array $data Current attack row (for t11).
* @param int $dead11 Hero casualties.
* @param int $herosend_def Defender hero count.
* @param int $deadhero Defender hero casualties.
* @param string $unitstraped_att CSV of attacker units trapped.
* @param int $village_destroyed 1 if the village was destroyed.
* @param int $can_destroy 1 if the village can be destroyed.
* @param int $catp_pic Catapult unit-pic id for the HTML fragment.
* @return array ['data2' => string, 'data_fail' => string]
*/
private function buildCombatReport(
$scout, $from, $to, $owntribe, $targettribe,
$unitssend_att, $unitsdead_att, $steal, $battlepart,
$unitssend_def, $unitsdead_def, $unitssend_deff, $unitsdead_deff,
$rom, $ger, $gal, $nat, $natar,
$hun, $egy, $spa, $vik,
$DefenderHeroesTot, $DefenderHeroesDead,
$info_ram, $info_cat, $info_chief, $info_spy,
$info_trap, $info_hero, $info_troop,
$data, $dead11, $herosend_def, $deadhero, $unitstraped_att,
$village_destroyed, $can_destroy, $catp_pic
) {
if (!empty($scout)) {
$data2 = ''.$from['owner'].','.$from['wref'].','.$owntribe.','.$unitssend_att.','.$unitsdead_att
.',0,0,0,0,0,'.$to['owner'].','.$to['wref'].','.addslashes($to['name'])
.',,,,'.$targettribe
.','.$unitssend_def[0].','.$unitsdead_def[0].','.$rom
.','.$unitssend_def[1].','.$unitsdead_def[1].','.$ger
.','.$unitssend_def[2].','.$unitsdead_def[2].','.$gal
.','.$unitssend_def[3].','.$unitsdead_def[3].','.$nat
.','.$unitssend_def[4].','.$unitsdead_def[4].','.$natar
.','.$unitssend_def[5].','.$unitsdead_def[5]
.','.$hun.','.$unitssend_def[6].','.$unitsdead_def[6]
.','.$egy.','.$unitssend_def[7].','.$unitsdead_def[7]
.','.$spa.','.$unitssend_def[8].','.$unitsdead_def[8]
.','.$vik.','.$unitssend_def[9].','.$unitsdead_def[9]
.','.$DefenderHeroesTot.','.$DefenderHeroesDead
.','.$info_ram.','.$info_cat.','.$info_chief.','.$info_spy
.','.$data['t11'].','.$dead11.','.$herosend_def.','.$deadhero
.',,'.$unitstraped_att;
} else {
if (isset($village_destroyed) && $village_destroyed == 1 && $can_destroy == 1) {
if (strpos($info_cat, rc_tok('RC_VILLAGE_DESTROYED')) === false) {
$info_cat .= "<tbody class=\"goods\"><tr><th>".INFORMATION."</th><td colspan=\"11\">"
. "<img class=\"unit u".$catp_pic."\" src=\"img/x.gif\" alt=\"".rc_tok('RC_CATAPULT')."\" title=\"".rc_tok('RC_CATAPULT')."\" />"
. " ".rc_tok('RC_VILLAGE_DESTROYED')."</td></tr></tbody>";
}
}
$data2 = ''.$from['owner'].','.$from['wref'].','.$owntribe.','.$unitssend_att.','.$unitsdead_att
.','.$steal[0].','.$steal[1].','.$steal[2].','.$steal[3].','.$battlepart['bounty']
.','.$to['owner'].','.$to['wref'].','.addslashes($to['name'])
.',,,,'.$targettribe
.','.$unitssend_def[0].','.$unitsdead_def[0].','.$rom
.','.$unitssend_def[1].','.$unitsdead_def[1].','.$ger
.','.$unitssend_def[2].','.$unitsdead_def[2].','.$gal
.','.$unitssend_def[3].','.$unitsdead_def[3].','.$nat
.','.$unitssend_def[4].','.$unitsdead_def[4].','.$natar
.','.$unitssend_def[5].','.$unitsdead_def[5]
.','.$hun.','.$unitssend_def[6].','.$unitsdead_def[6]
.','.$egy.','.$unitssend_def[7].','.$unitsdead_def[7]
.','.$spa.','.$unitssend_def[8].','.$unitsdead_def[8]
.','.$vik.','.$unitssend_def[9].','.$unitsdead_def[9]
.','.$DefenderHeroesTot.','.$DefenderHeroesDead
.','.$info_ram.','.$info_cat.','.$info_chief.','.(isset($info_spy) ? $info_spy : '')
.',,'.$data['t11'].','.$dead11.','.$herosend_def.','.$deadhero
.','.$unitstraped_att;
$data2 .= ','.($info_trap !== '' ? addslashes($info_trap) : '').',,'.$info_troop.','.$info_hero;
}
$data_fail = ''.$from['owner'].','.$from['wref'].','.$owntribe.','.$unitssend_att.','.$unitsdead_att
.','.$steal[0].','.$steal[1].','.$steal[2].','.$steal[3].','.$battlepart['bounty']
.','.$to['owner'].','.$to['wref'].','.addslashes($to['name'])
.',,,,'.$targettribe
.','.$unitssend_deff[0].','.$unitsdead_deff[0].','.$rom
.','.$unitssend_deff[1].','.$unitsdead_deff[1].','.$ger
.','.$unitssend_deff[2].','.$unitsdead_deff[2].','.$gal
.','.$unitssend_deff[3].','.$unitsdead_deff[3].','.$nat
.','.$unitssend_deff[4].','.$unitsdead_deff[4].','.$natar
.','.$unitssend_deff[5].','.$unitsdead_deff[5]
.','.$hun.','.$unitssend_deff[6].','.$unitsdead_deff[6]
.','.$egy.','.$unitssend_deff[7].','.$unitsdead_deff[7]
.','.$spa.','.$unitssend_deff[8].','.$unitsdead_deff[8]
.','.$vik.','.$unitssend_deff[9].','.$unitsdead_deff[9]
.','.$DefenderHeroesTot.','.$DefenderHeroesDead
.',,,'.$data['t11'].','.$dead11.','.$unitstraped_att
.',,'.$info_ram.','.$info_cat.','.$info_chief.','.$info_troop.','.$info_hero;
return ['data2' => $data2, 'data_fail' => $data_fail];
}
/**
* Distribute the battle bounty across the resources actually available in
* the target (after cranny protection) and return how much of each is taken.
* Pure behaviour-preserving extraction (refactor for issue #155).
*
* @param array $available [wood, clay, iron, crop] lootable amounts.
* @param int $bounty Total carry capacity / bounty from the battle.
* @return array [wood, clay, iron, crop] amounts actually stolen.
*/
private function applyBounty(array $available, $bounty) {
$avtotal = $available;
$steal = [0, 0, 0, 0];
$btotal = $bounty;
$bmod = 0;
for ($i = 0; $i < 5; $i++) {
for ($j = 0; $j < 4; $j++) {
if (isset($avtotal[$j])) {
if ($avtotal[$j] < 1) unset($avtotal[$j]);
}
}
// No resources left to take
if (empty($avtotal) || ($btotal < 1 && $bmod < 1)) break;
if ($btotal < 1) {
while ($bmod) {
// random select
$rs = array_rand($avtotal);
if (isset($avtotal[$rs])) {
$avtotal[$rs] -= 1;
$steal[$rs] += 1;
$bmod -= 1;
}
}
}
// handle unbalanced amounts.
$btotal += $bmod;
$bmod = $btotal % count($avtotal);
$btotal -= $bmod;
$bsplit = $btotal / count($avtotal);
$max_steal = (min($avtotal) < $bsplit) ? min($avtotal) : $bsplit;
for ($j = 0; $j < 4; $j++) {
if (isset($avtotal[$j])) {
$avtotal[$j] -= $max_steal;
$steal[$j] += $max_steal;
$btotal -= $max_steal;
}
}
}
return $steal;
}
/**
* Apply battle casualties to the defender's own (in-village) troops, persist
* the losses, and return the per-unit dead map used later for points/reports.
* Pure behaviour-preserving extraction (refactor for issue #155).
*
* @param array $data Current attack row (uses 'to').
* @param int $targettribe Defender tribe (1-5).
* @param array $battlepart Battle result (index 2 = defender kill ratio,
* 'deadherodef' = defender hero losses).
* @return array Map of dead own troops: keys are the unit index (int) plus 'hero'.
*/
private function applyOwnDefenceCasualties($data, $targettribe, $battlepart) {
global $database;
$owndead = [];
$unitlist = $database->getUnit($data['to'], false);
$start = ($targettribe - 1) * 10 + 1;
$end = ($targettribe * 10);
$unitModifications_units = [];
$unitModifications_amounts = [];
$unitModifications_modes = [];
for ($i = $start; $i <= $end; $i++) {
if ($unitlist) {
$owndead[$i] = round($battlepart[2] * $unitlist['u'.$i]);
$unitModifications_units[] = $i;
$unitModifications_amounts[] = $owndead[$i];
$unitModifications_modes[] = 0;
}
}
$owndead['hero'] = 0;
if ($unitlist) {
$owndead['hero'] = (isset($battlepart['deadherodef']) ? $battlepart['deadherodef'] : '');
$unitModifications_units[] = 'hero';
$unitModifications_amounts[] = $owndead['hero'];
$unitModifications_modes[] = 0;
}
// modify units in DB
$database->modifyUnit($data['to'], $unitModifications_units, $unitModifications_amounts, $unitModifications_modes);
// Spital: o parte din mortii proprii (aparare) devin raniti - doar unitatile de lupta X1-X8
$woundedMap = [];
for($wi = $start; $wi <= $start + 7; $wi++) {
if(!empty($owndead[$wi])) $woundedMap[$wi] = $owndead[$wi];
}
$this->applyHospitalWounded($data['to'], $woundedMap);
return $owndead;
}
/**
* Apply battle casualties to the reinforcement (foreign) troops stationed in
* the defender's village: compute each reinforcement's dead units/hero, persist
* them (modifyEnforce), notify the reinforcing players, and delete fully-wiped
* reinforcements. Accumulates the dead counts into $alldead and the dead heroes
* into $DefenderHeroesDeadArray (both passed by reference), and reports which
* tribes are present among the reinforcements.
* Pure behaviour-preserving extraction (refactor for issue #155).
*
* @param array $data Current attack row (uses 'to').
* @param array $battlepart Battle result (index 2 = defender kill ratio,
* 'deadheroref' = dead hero per enforce id).
* @param array $from Attacker village info (uses 'wref').
* @param array $to Defender village info (uses 'name','wref').
* @param int $ownally Attacker's alliance id (for notices).
* @param int $AttackArrivalTime Timestamp stamped on the reinforcement notices.
* @param mixed $scout Scout marker; notices/deletion only fire when empty.
* @param array $alldead [in/out] Per-unit dead reinforcements (+ 'hero'), accumulated.
* @param array $DefenderHeroesDeadArray [in/out] Dead reinforcement heroes per tribe, accumulated.
* @return array Tribe-presence flags among the reinforcements: keys rom,ger,gal,nat,natar (0/1).
*/
private function applyReinforcementCasualties($data, $battlepart, $from, $to, $ownally, $AttackArrivalTime, $scout, array &$alldead, array &$DefenderHeroesDeadArray) {
global $database;
$rom = $ger = $gal = $nat = $natar = $hun = $egy = $spa = $vik = 0;
//kill other defence in village
// ... once again, units could have changed, so we need to reselect
$enforcementarray3 = $database->getEnforceVillage($data['to'], 0);
foreach ($enforcementarray3 as $enforce) {
$life = ''; $notlife = ''; $wrong = false;
if ($enforce['from'] != 0) {
$tribe = $database->getUserArray($database->getVillageField($enforce['from'], "owner"), 1)["tribe"];
} else {
$tribe = 4;
}
$start = ($tribe - 1) * 10 + 1;
$end = ($tribe * 10);
unset($dead);
switch ($tribe) {
case 1: $rom = 1; break;
case 2: $ger = 1; break;
case 3: $gal = 1; break;
case 4: $nat = 1; break;
case 6: $hun = 1; break;
case 7: $egy = 1; break;
case 8: $spa = 1; break;
case 9: $vik = 1; break;
case 5:
default: $natar = 1; break;
}
$enforceModificationsById = [];
for ($i = $start; $i <= $end; $i++) {
if ($enforce['u'.$i] > '0') {
if (!isset($enforceModificationsById[$enforce['id']])) {
$enforceModificationsById[$enforce['id']] = [
'units' => [],
'amounts' => [],
'modes' => []
];
}
$enforceModificationsById[$enforce['id']]['units'][] = $i;
$enforceModificationsById[$enforce['id']]['amounts'][] = (isset($battlepart[2]) ? round($battlepart[2]*$enforce['u'.$i]) : 0);
$enforceModificationsById[$enforce['id']]['modes'][] = 0;
$dead[$i] = (isset($battlepart[2]) ? round($battlepart[2]*$enforce['u'.$i]) : 0);
$checkpoint = (isset($battlepart[2]) ? round($battlepart[2]*$enforce['u'.$i]) : 0);
if (!isset($enforce['u'.$i])) $enforce['u'.$i] = 0;
$alldead[$i] += $dead[$i];
$wrong = $checkpoint != $enforce['u'.$i];
} else {
$dead[$i] = 0;
}
}
if ($enforce['hero'] > 0) {
$enforceModificationsById[$enforce['id']]['units'][] = 'hero';
$enforceModificationsById[$enforce['id']]['amounts'][] = $battlepart['deadheroref'][$enforce['id']];
$enforceModificationsById[$enforce['id']]['modes'][] = 0;
$dead['hero'] = $battlepart['deadheroref'][$enforce['id']];
$alldead['hero'] += $dead['hero'];
$wrong = $dead['hero'] != $enforce['hero'];
//Collecting information for the report
$reinfTribe = ($enforce['from'] == 0) ? 4 : $database->getUserField($database->getVillageField($enforce['from'], "owner"), "tribe", 0);
$DefenderHeroesDeadArray[$reinfTribe] += $dead['hero'];
}
// modify enforce in DB
foreach ($enforceModificationsById as $enforceId => $enforceArray) {
$database->modifyEnforce($enforceId, $enforceArray['units'], $enforceArray['amounts'], $enforceArray['modes']);
}
$notlife = ''.$dead[$start].','.$dead[$start+1].','.$dead[$start+2].','.$dead[$start+3].','.$dead[$start+4].','.$dead[$start+5].','.$dead[$start+6].','.$dead[$start+7].','.$dead[$start+8].','.$dead[$start+9].'';
$notlife1 = $dead[$start]+$dead[$start+1]+$dead[$start+2]+$dead[$start+3]+$dead[$start+4]+$dead[$start+5]+$dead[$start+6]+$dead[$start+7]+$dead[$start+8]+$dead[$start+9];
$life = ''.$enforce['u'.$start.''].','.$enforce['u'.($start+1).''].','.$enforce['u'.($start+2).''].','.$enforce['u'.($start+3).''].','.$enforce['u'.($start+4).''].','.$enforce['u'.($start+5).''].','.$enforce['u'.($start+6).''].','.$enforce['u'.($start+7).''].','.$enforce['u'.($start+8).''].','.$enforce['u'.($start+9).''].'';
$life1 = $enforce['u'.$start.'']+$enforce['u'.($start+1).'']+$enforce['u'.($start+2).'']+$enforce['u'.($start+3).'']+$enforce['u'.($start+4).'']+$enforce['u'.($start+5).'']+$enforce['u'.($start+6).'']+$enforce['u'.($start+7).'']+$enforce['u'.($start+8).'']+$enforce['u'.($start+9).''];
$lifehero = (isset($enforce['hero']) ? $enforce['hero'] : 0);
$notlifehero = (isset($dead['hero']) ? $dead['hero'] : 0);
$totallife = (isset($enforce['hero']) ? $enforce['hero'] : 0)+$life1;
$totalnotlife = (isset($dead['hero']) ? $dead['hero'] : 0)+$notlife1;
//NEED TO SEND A RAPPORTAGE!!!
$data2 = ''.$database->getVillageField($enforce['from'], "owner").','.$to['wref'].','.addslashes($to['name']).','.$tribe.','.$life.','.$notlife.','.$lifehero.','.$notlifehero.','.$enforce['from'].'';
if (empty($scout)) {
if ($totalnotlife == 0) {
$database->addNotice($database->getVillageField($enforce['from'], "owner"), $from['wref'], $ownally, 15, 'Reinforcement in '.addslashes($to['name']).' was attacked', $data2, $AttackArrivalTime);
} else if ($totallife > $totalnotlife) {
$database->addNotice($database->getVillageField($enforce['from'], "owner"), $from['wref'], $ownally, 16, 'Reinforcement in '.addslashes($to['name']).' was attacked', $data2, $AttackArrivalTime);
} else {
$database->addNotice($database->getVillageField($enforce['from'], "owner"), $from['wref'], $ownally, 17, 'Reinforcement in '.addslashes($to['name']).' was attacked', $data2, $AttackArrivalTime);
}
//delete reinf sting when its killed all.
if (!$wrong) $database->deleteReinf($enforce['id']);
}
}
return ['rom' => $rom, 'ger' => $ger, 'gal' => $gal, 'nat' => $nat, 'natar' => $natar, 'hun' => $hun, 'egy' => $egy, 'spa' => $spa, 'vik' => $vik];
}
/**
* Collect the defender-side report data BEFORE casualties are applied: reset and
* rebuild the reinforcement unit totals ($DefenderEnf) and the per-tribe hero
* totals from a fresh re-select, fold reinforcement heroes into the defender's
* hero count, build the "units sent" report rows (own troops + reinforcements,
* plus their masked variants), and (re)initialise the per-tribe dead-hero
* accumulator to zero. Returns everything the caller needs downstream.
* Pure behaviour-preserving extraction (refactor for issue #155).
*
* @param array $data Current attack row (uses 'to').
* @param int $targettribe Defender tribe (1-5).
* @param array $Defender Defender unit array (own troops + 'hero'); passed by value,
* its 'hero' is folded with reinforcement heroes and returned
* via the 'defenderHero' key.
* @return array Report bundle: DefenderEnf, DefenderHeroesTotArray,
* DefenderHeroesDeadArray, unitssend_def, unitssend_deff,
* totalsend_alldef, defenderHero.
*/
private function collectReinforcementReport($data, $targettribe, $Defender) {
global $database;
$DefenderEnf = [];
$DefenderHeroesTotArray = [];
$DefenderHeroesDeadArray = [];
$unitssend_def = [];
$unitssend_deff = [];
//Resetting the enforcement arrays
for ($i = 1; $i <= 90; $i++) {
$DefenderEnf['u'.$i] = 0;
if ($i <= 9) {
$DefenderHeroesTotArray[$i] = 0;
$DefenderHeroesDeadArray[$i] = 0;
}
}
// our reinforcements count could have changed at this point, thus the re-select
$enforcementarray2 = $database->getEnforceVillage($data['to'], 0);
if (count($enforcementarray2) > 0) {
foreach ($enforcementarray2 as $enforce2) {
for ($i = 1; $i <= 90; $i++) {
$DefenderEnf['u'.$i] += $enforce2['u'.$i];
}
//Divide heroes by tribe
if ($enforce2['hero'] > 0) {
$reinfTribe = ($enforce2['from'] == 0) ? 4 : $database->getUserField($database->getVillageField($enforce2['from'], "owner"), "tribe", 0);
$DefenderHeroesTotArray[$reinfTribe] += $enforce2['hero'];
$Defender['hero'] += $enforce2['hero'];
}
}
}
$totalsend_alldef = 0;
//Own troops
$ownTroops = array_slice($Defender, ($targettribe - 1) * 10 + 1, 10);
$totalsend_alldef = array_sum($ownTroops);
//Collecting informations for the report
$unitssend_def[0] = implode(",", $ownTroops);
$unitssend_deff[0] = '?,?,?,?,?,?,?,?,?,?,';
for ($i = 1; $i <= 9; $i++) {
//Reinforcements
$reinfTroops = array_slice($DefenderEnf, ($i - 1) * 10, 10);
$totalsend_alldef += array_sum($reinfTroops);
//Collecting informations for the report
$unitssend_def[$i] = implode(",", $reinfTroops);
$unitssend_deff[$i] = $unitssend_deff[0];
}
$totalsend_alldef += $Defender['hero'];
return [
'DefenderEnf' => $DefenderEnf,
'DefenderHeroesTotArray' => $DefenderHeroesTotArray,
'DefenderHeroesDeadArray' => $DefenderHeroesDeadArray,
'unitssend_def' => $unitssend_def,
'unitssend_deff' => $unitssend_deff,
'totalsend_alldef' => $totalsend_alldef,
'defenderHero' => $Defender['hero'],
];
}
/**
* Compute attacker/defender total populations and fetch their village lists.
* Pure behaviour-preserving extraction (refactor for issue #155).
*
* @param array $to Defender village info (getMInfo/getOMInfo).
* @param array $from Attacker village info (getMInfo).
* @param int $isoasis Whether the target is an oasis (defender pop counts only for villages).
* @param int $defpop Initial defender population (accumulated onto).
* @param int $attpop Initial attacker population (accumulated onto).
* @return array{varray:array,varray1:array,defpop:int,attpop:int}
*/
private function calculatePopulations($to, $from, $isoasis, $defpop, $attpop) {
global $database;
$varray = $database->getProfileVillages($to['owner'], 0, false);
if ($to['owner'] == $from['owner']) $varray1 = $varray;
else $varray1 = $database->getProfileVillages($from['owner'], 0, false);
// total population of the defender
if($isoasis == 0){
foreach($varray as $defenderVillage) $defpop += $defenderVillage['pop'];
}
// total population of the attacker
foreach($varray1 as $attackerVillage) $attpop += $attackerVillage['pop'];
return [
'varray' => $varray,
'varray1' => $varray1,
'defpop' => $defpop,
'attpop' => $attpop,
];
}
/**
* Resolve how many incoming attacker units get caught in the defender's
* traps (Gaul trapper / Natar capital), update the trap counters and the
* prisoners table, and subtract the trapped troops from the attacking army.
* Pure behaviour-preserving extraction (refactor for issue #155).
*
* @param array $data Current attack row.
* @param array $Defender Defender unit counts (reads u99 / u99o).
* @param array $Attacker Attacker units (u<start..end> + uhero).
* @param bool $NatarCapital True when the target is a Natar capital (all troops trapped).
* @param int $scout 1 for a scouting attack (no trapping).
* @param int $start First unit index of the attacker tribe.
* @param int $end Last unit index of the attacker tribe.
* @return array {
* traped: int[] trapped count per unit slot (1..11),
* totaltraped_att: int total trapped troops,
* Attacker: array attacker units with trapped troops removed
* }
*/
private function calculateTrappedUnits($data, $Defender, $Attacker, $NatarCapital, $scout, $start, $end) {
global $database;
$traped = [];
for($i = 1; $i <= 11; $i++) $traped[$i] = 0;
$totaltraped_att = 0;
//impossible to attack or scout NATAR Capital Village
if ($NatarCapital){
for($i = 1; $i <= 11; $i++) $traped[$i] = $data['t'.$i];
}
elseif(empty($scout))
{
$traps = max($Defender['u99'] - $Defender['u99o'], 0);
$totalTroops = 0;
for($i = 1; $i <= 11; $i++) $totalTroops += $data['t'.$i];
if($traps >= $totalTroops){
for($i = 1; $i <= 11; $i++) $traped[$i] = $data['t'.$i];
}
else if($totalTroops > 0)
{
$multiplier = $traps / $totalTroops;
//The hero is excluded, because it can be only trapped if traps > totalTroops
for($i = 1; $i <= 10; $i++){
$trappedUnits = intval($data['t'.$i] * $multiplier);
$traped[$i] = $trappedUnits;
$traps -= $trappedUnits;
}
while($traps > 0){
//There are some traps left, let's distribute them
for($i = 1; $i <= 10 && $traps > 0; $i++){
if($data['t'.$i] != 0){
$traped[$i]++;
$traps--;
}
}
}
}
else {
for($i = 1; $i <= 11; $i++) $traped[$i] = 0;
}
}
if(empty($scout) || $NatarCapital){
for ($i = 1; $i <= 11; $i++){
$totaltraped_att += $traped[$i];
}
$database->modifyUnit($data['to'], ["99o"], [$totaltraped_att], [1]);
for($i = $start; $i <= $end; $i++){
$j = $i-$start+1;
$Attacker['u'.$i] -= $traped[$j];
}
$Attacker['uhero'] -= $traped[11];
if($totaltraped_att > 0){
$prisoners2 = $database->getPrisoners2($data['to'], $data['from'], false);
if(empty($prisoners2)){
$database->addPrisoners($data['to'],$data['from'],$traped[1],$traped[2],$traped[3],$traped[4],$traped[5],$traped[6],$traped[7],$traped[8],$traped[9],$traped[10],$traped[11]);
}else{
$database->updatePrisoners($data['to'],$data['from'],$traped[1],$traped[2],$traped[3],$traped[4],$traped[5],$traped[6],$traped[7],$traped[8],$traped[9],$traped[10],$traped[11]);
}
}
}
return [
'traped' => $traped,
'totaltraped_att' => $totaltraped_att,
'Attacker' => $Attacker,
];
}
/**
* Apply ram damage to the defender's wall during a normal attack (type 3):
* compute the new wall level, update it in the database (recounting the
* village population if the wall is destroyed), build the report fragment,
* and recalculate the battle when the wall level changed.
* Pure behaviour-preserving extraction (refactor for issue #155).
*
* @param array $data Current attack row (uses t7, to).
* @param int $walllevel Current wall level.
* @param int $wallid Wall building field id (40).
* @param int $ram_pic Ram unit id (for the report fragment).
* @param array $battlepart Battle result (provides the ram damage factors).
* @param array $ctx Battle context for the post-damage recalculation
* (Attacker, Defender, tribes, populations, AB tech, ...).
* @return array { battlepart: array (possibly recalculated), info_ram: string }
*/
private function applyRamDamage($data, $walllevel, $wallid, $ram_pic, $battlepart, array $ctx) {
global $database, $battle;
$info_ram = "".$ram_pic.",".rc_tok('RC_NO_WALL');
if($walllevel > 0){
$ramsDamage = $battle->calculateCatapultsDamage($data['t7'],
$battlepart['rams']['upgrades'],
$battlepart['rams']['durability'],
$battlepart['rams']['attackDefenseRatio'],
$battlepart['rams']['strongerBuildings'],
$battlepart['rams']['moraleBonus']);
$newLevel = $battle->calculateNewBuildingLevel($walllevel, $ramsDamage);
if ($newLevel == 0){
$info_ram = "".$ram_pic.",".rc_tok('RC_WALL_DESTROYED');
$database->setVillageLevel($data['to'], ["f".$wallid, "f".$wallid."t"], [0, 0]);
$this->recountPop($data['to']);
}elseif ($newLevel == $walllevel){
$info_ram = "".$ram_pic.",".rc_tok('RC_WALL_NOT_DAMAGED');
}else{
$info_ram = "".$ram_pic.",".rc_tok('RC_WALL_DAMAGED_FROM_TO', $walllevel, $newLevel);
$database->setVillageLevel($data['to'],"f".$wallid."",$newLevel);
}
//If the wall got damaged/destroyed during the attack
//we need to recalculate the whole battle
if($newLevel != $walllevel){
$battlepart = $battle->calculateBattle($ctx['Attacker'], $ctx['Defender'], $newLevel, $ctx['att_tribe'], $ctx['def_tribe'], $ctx['residence'], $ctx['attpop'], $ctx['defpop'], $ctx['type'], $ctx['def_ab'], $ctx['att_ab1'], $ctx['att_ab2'], $ctx['att_ab3'], $ctx['att_ab4'], $ctx['att_ab5'], $ctx['att_ab6'], $ctx['att_ab7'], $ctx['att_ab8'], $ctx['tblevel'], $ctx['stonemason'], $newLevel, 0, 0, 0, $ctx['AttackerID'], $ctx['DefenderID'], $ctx['AttackerWref'], $ctx['DefenderWref'], $ctx['conqureby'], $ctx['enforcementarray']);
}
}
return ['battlepart' => $battlepart, 'info_ram' => $info_ram];
}
/**
* Build the attacking army for the current attack: per-slot unit counts
* (u<start..end> + uhero) plus the catapult / ram / chief / scout unit ids
* used in the battle report. Oasis attacks include the Nature siege/chief
* slots (37/38/39). Pure behaviour-preserving extraction (issue #155).
*
* @param array $attackRow The attack row (t1..t11, tribe-relative).
* @param int $owntribe Attacker tribe (1..5).
* @param int $isoasis 0 for a village target, otherwise an oasis.
* @return array { Attacker, start, end, u, catp_pic, ram_pic, chief_pic, spy_pic, hero_pic }
*/
private function buildAttackerUnits(array $attackRow, $owntribe, $isoasis) {
$Attacker = [];
$start = ($owntribe - 1) * 10 + 1;
$end = $owntribe * 10;
$u = ($owntribe - 1) * 10;
if ($isoasis == 0) {
$catapult = [8, 18, 28, 48, 58, 68, 78, 88];
$ram = [7, 17, 27, 47, 57, 67, 77, 87];
$chief = [9, 19, 29, 49, 59, 69, 79, 89];
} else {
$catapult = [8, 18, 28, 38, 48, 58, 68, 78, 88];
$ram = [7, 17, 27, 37, 47, 57, 67, 77, 87];
$chief = [9, 19, 29, 39, 49, 59, 69, 79, 89];
}
$spys = [4, 14, 23, 44, 52, 64, 74, 82];
$catp_pic = $ram_pic = $chief_pic = $spy_pic = null;
for ($i = $start; $i <= $end; $i++) {
$y = $i - $u;
$Attacker['u'.$i] = $attackRow['t'.$y];
//there are catas
if (in_array($i, $catapult)) $catp_pic = $i;
if (in_array($i, $ram)) $ram_pic = $i;
if (in_array($i, $chief)) $chief_pic = $i;
if (in_array($i, $spys)) $spy_pic = $i;
}
$Attacker['uhero'] = $attackRow['t11'];
return [
'Attacker' => $Attacker,
'start' => $start,
'end' => $end,
'u' => $u,
'catp_pic' => $catp_pic,
'ram_pic' => $ram_pic,
'chief_pic' => $chief_pic,
'spy_pic' => $spy_pic,
'hero_pic' => 'hero',
];
}
/**
* Gather the defender's units for the current attack: the village's own
* troops (normalised to non-negative ints) plus the aggregated reinforcement
* totals, and the raw reinforcement rows. Used for both village and oasis
* targets. Pure behaviour-preserving extraction (issue #155).
*
* @param int $wref Defending village/oasis wref.
* @return array { Defender, enforDefender, enforcementarray }
*/
private function buildDefenderUnits($wref) {
global $database;
$enforDefender = [];
$Defender = $database->getUnit($wref, false);
$enforcementarray = $database->getEnforceVillage($wref, 0);
if (count($enforcementarray) > 0) {
foreach ($enforcementarray as $enforce) {
for ($i = 1; $i <= 90; $i++) {
if (!isset($enforDefender['u'.$i])) {
$enforDefender['u'.$i] = 0;
}
$enforDefender['u'.$i] += $enforce['u'.$i];
}
if (!isset($enforDefender['hero'])) {
$enforDefender['hero'] = 0;
}
$enforDefender['hero'] += $enforce['hero'];
}
}
for ($i = 1; $i <= 90; $i++) {
if (!isset($Defender['u'.$i]) || empty($Defender['u'.$i]) || $Defender['u'.$i] < 0) {
$Defender['u'.$i] = 0;
}
}
if (!isset($Defender['hero']) || empty($Defender['hero']) || $Defender['hero'] < 0) {
$Defender['hero'] = 0;
}
return [
'Defender' => $Defender,
'enforDefender' => $enforDefender,
'enforcementarray' => $enforcementarray,
];
}
/**
* Resolve the per-attack context that does not depend on the target type:
* the attacker village/owner data (tribe, alliance), the war references and
* a few base flags. Read-only — no DB writes.
* Pure behaviour-preserving extraction (refactor for issue #155).
*
* @param array $data Current attack row.
* @return array {isoasis, AttackArrivalTime, AttackerWref, DefenderWref,
* NatarCapital, AttackerID, owntribe, ownally, from, fromF}
*/
private function resolveAttackContext($data) {
global $database;
$owner = $this->getCachedUser($database->getVillageField($data['from'], "owner"), 1);
return [
'isoasis' => $data['oasistype'],
'AttackArrivalTime' => $data['endtime'],
'AttackerWref' => $data['from'],
'DefenderWref' => $data['to'],
'NatarCapital' => false,
'AttackerID' => $owner['id'],
'owntribe' => $owner['tribe'],
'ownally' => $owner['alliance'],
'from' => $database->getMInfo($data['from']),
'fromF' => $database->getVillage($data['from']),
];
}
/**
* Resolve the defender target context for a VILLAGE attack: target owner
* (tribe/alliance), map info, conquest flag, evasion inputs and the battle
* environment (wall, armory/blacksmith tech, residence, siege masonry).
* Read-only — no DB writes.
* Pure behaviour-preserving extraction (refactor for issue #155).
*
* @param array $data Current attack row.
* @param array $dataarray Full batch of completed attacks.
* @param int $data_num Index of $data within $dataarray.
* @param int $owntribe Attacker tribe.
* @return array Target + battle-environment context.
*/
private function resolveVillageTarget($data, $dataarray, $data_num, $owntribe) {
global $database, $units;
$DefenderUserData = $this->getCachedUser($database->getVillageField($data['to'],"owner"),1);
$DefenderID = $DefenderUserData["id"];
$targettribe = $DefenderUserData["tribe"];
$targetally = $DefenderUserData["alliance"];
$to = $database->getMInfo($data['to']);
$toF = $database->getVillage($data['to']);
$conqureby = 0;
$NatarCapital = ($toF['owner'] == 3 && $toF['capital'] == 1);
if(!isset($to['name']) || empty($to['name'])) $to['name'] = "[?]";
$DefenderUnit = $database->getUnit($data['to']);
$evasion = $toF["evasion"];
$maxevasion = $DefenderUserData["maxevasion"];
$gold = $DefenderUserData["gold"];
$cannotsend = false;
$movements = $database->getMovement(34, $data['to'], 1);
for($y = 0; $y < count($movements); $y++){
if(property_exists($units, $y)){
$returntime = $units->$y['endtime'] - time();
if($units->$y['sort_type'] == 4 && $units->$y['from'] != 0 && $returntime <= 10){
$cannotsend = true;
}
}
}
//need to set these variables.
$def_wall = $database->getFieldLevel($data['to'], 40, false);
$att_tribe = $owntribe;
$def_tribe = $targettribe;
$attpop = $defpop = $residence = 0;
$def_ab = [];
//get level of palace or residence
$residence = $database->getFieldLevelInVillage($data['to'], '25, 26', false);
//type of attack
$type = $dataarray[$data_num]['attack_type'];
$scout = ($type == 1) ? 1 : 0;
$ud = ($def_tribe - 1) * 10;
$att_ab = $database->getABTech($data['from']); // Blacksmith level
$att_ab1 = $att_ab['b1'];
$att_ab2 = $att_ab['b2'];
$att_ab3 = $att_ab['b3'];
$att_ab4 = $att_ab['b4'];
$att_ab5 = $att_ab['b5'];
$att_ab6 = $att_ab['b6'];
$att_ab7 = $att_ab['b7'];
$att_ab8 = $att_ab['b8'];
$armory = $database->getABTech($data['to']); // Armory level
$def_ab[$ud + 1] = $armory['a1'];
$def_ab[$ud + 2] = $armory['a2'];
$def_ab[$ud + 3] = $armory['a3'];
$def_ab[$ud + 4] = $armory['a4'];
$def_ab[$ud + 5] = $armory['a5'];
$def_ab[$ud + 6] = $armory['a6'];
$def_ab[$ud + 7] = $armory['a7'];
$def_ab[$ud + 8] = $armory['a8'];
//rams attack
$walllevel = 0;
$wallid = 0;
if (($data['t7']) > 0 && $type == 3) {
$basearraywall = $to;
if (($walllevel = $database->getFieldLevel($basearraywall['wref'], 40, false)) > 0){
$wallid = 40;
}
}
$tblevel = 1;
$stonemason = $database->getFieldLevelInVillage($data['to'], 34);
return [
'DefenderID' => $DefenderID, 'targettribe' => $targettribe, 'targetally' => $targetally,
'to' => $to, 'toF' => $toF, 'conqureby' => $conqureby, 'NatarCapital' => $NatarCapital,
'DefenderUnit' => $DefenderUnit, 'evasion' => $evasion, 'maxevasion' => $maxevasion,
'gold' => $gold, 'cannotsend' => $cannotsend,
'def_wall' => $def_wall, 'att_tribe' => $att_tribe, 'def_tribe' => $def_tribe,
'attpop' => $attpop, 'defpop' => $defpop, 'residence' => $residence, 'def_ab' => $def_ab,
'type' => $type, 'scout' => $scout,
'att_ab1' => $att_ab1, 'att_ab2' => $att_ab2, 'att_ab3' => $att_ab3, 'att_ab4' => $att_ab4,
'att_ab5' => $att_ab5, 'att_ab6' => $att_ab6, 'att_ab7' => $att_ab7, 'att_ab8' => $att_ab8,
'walllevel' => $walllevel, 'wallid' => $wallid,
'tblevel' => $tblevel, 'stonemason' => $stonemason,
];
}
/**
* Resolve the defender target context for an OASIS attack: target owner
* (tribe/alliance), oasis map info, conquest flag and the (mostly fixed)
* battle environment. Read-only — no DB writes.
* Pure behaviour-preserving extraction (refactor for issue #155).
*
* @param array $data Current attack row.
* @param array $dataarray Full batch of completed attacks.
* @param int $data_num Index of $data within $dataarray.
* @param int $owntribe Attacker tribe.
* @return array Target + battle-environment context.
*/
private function resolveOasisTarget($data, $dataarray, $data_num, $owntribe) {
global $database;
$DefenderUserData = $this->getCachedUser($database->getOasisField($data['to'], "owner"),1);
$DefenderID = $DefenderUserData["id"];
$targettribe = $DefenderUserData["tribe"];
$targetally = $DefenderUserData["alliance"];
$to = $database->getOMInfo($data['to']);
$toF = $database->getOasisV($data['to']);
$conqureby = $toF['conqured'];
//need to set these variables.
$def_wall = $residence = $attpop = 0;
$att_tribe = $owntribe;
$def_tribe = $targettribe;
$defpop = 500;
//type of attack
$type = $dataarray[$data_num]['attack_type'];
$scout = ($type == 1) ? 1 : 0;
$att_ab1 = $att_ab2 = $att_ab3 = $att_ab4 = $att_ab5 = $att_ab6 = $att_ab7 = $att_ab8 = 0;
$def_ab = [];
$def_ab[31] = $def_ab[32] = $def_ab[33] = $def_ab[34] = $def_ab[35] = $def_ab[36] = $def_ab[37] = $def_ab[38] = 0;
$walllevel = $tblevel = $stonemason = 0;
return [
'DefenderID' => $DefenderID, 'targettribe' => $targettribe, 'targetally' => $targetally,
'to' => $to, 'toF' => $toF, 'conqureby' => $conqureby,
'def_wall' => $def_wall, 'att_tribe' => $att_tribe, 'def_tribe' => $def_tribe,
'attpop' => $attpop, 'defpop' => $defpop, 'residence' => $residence, 'def_ab' => $def_ab,
'type' => $type, 'scout' => $scout,
'att_ab1' => $att_ab1, 'att_ab2' => $att_ab2, 'att_ab3' => $att_ab3, 'att_ab4' => $att_ab4,
'att_ab5' => $att_ab5, 'att_ab6' => $att_ab6, 'att_ab7' => $att_ab7, 'att_ab8' => $att_ab8,
'walllevel' => $walllevel, 'tblevel' => $tblevel, 'stonemason' => $stonemason,
];
}
/**
* Compute attacker/defender hero XP and victory points from the battle
* casualties, persist them (hero XP, player points, alliance points) and
* return the total defender losses.
* Pure behaviour-preserving extraction (refactor for issue #155).
*
* @param array $alldead Reinforcement casualties by global unit id (+ 'hero').
* @param array $owndead Defender own casualties by global unit id (+ 'hero').
* @param array $attackerDead Attacker casualties, 0-indexed t1..t11 (index 10 = hero).
* @param int $targettribe Defender tribe id.
* @param int $att_tribe Attacker tribe id.
* @param array $Attacker Attacker troop row (uses 'uhero').
* @param mixed $AttackerHeroID Attacker hero id (for the XP write).
* @param array $Defender Defender troop row (uses 'hero').
* @param array $DefendersHeroID Defender hero ids (for the XP writes).
* @param array $toF Defender village (uses 'owner').
* @param array $from Attacker village info (uses 'owner').
* @param int $targetally Defender alliance id.
* @param int $ownally Attacker alliance id.
* @param mixed $heroxp OUT (by ref): attacker hero XP, set only if the attacker has a hero.
* @param mixed $defheroxp OUT (by ref): per-defender hero XP, set only if the defender has a hero.
* @return int Total defender losses (reinforcements + own troops).
*/
private function calculateHeroXpAndPoints(
array $alldead, array $owndead, array $attackerDead,
$targettribe, $att_tribe,
$Attacker, $AttackerHeroID, $Defender, $DefendersHeroID,
$toF, $from, $targetally, $ownally,
&$heroxp, &$defheroxp
) {
global $database;
$totaldead_def = 0;
$totalpoint_att = 0;
for($i = 1 ;$i <= 90; $i++) {
$unitarray = $GLOBALS["u".$i];
//Reinforcements dead troops
$totaldead_def += $alldead[$i];
$totalpoint_att += ($alldead[$i] * $unitarray['pop']);
//Own dead troops
if($i >= ($targettribe - 1) * 10 + 1 && $i <= $targettribe * 10){
$totaldead_def += $owndead[$i];
$totalpoint_att += ($owndead[$i] * $unitarray['pop']);
}
}
$totalpoint_att += ((isset($alldead['hero']) ? $alldead['hero'] : 0) * 6);
$totalpoint_att += ((isset($owndead['hero']) ? $owndead['hero'] : 0) * 6);
if ($Attacker['uhero'] > 0){
$heroxp = $totalpoint_att;
$database->modifyHeroXp("experience", $heroxp, $AttackerHeroID);
}
for($i = 1; $i <= 10; $i++){
$unitarray = $GLOBALS["u".(($att_tribe - 1) * 10 + $i)];
if ( !isset($totalpoint_def) ) {
$totalpoint_def = 0;
}
$totalpoint_def += ($attackerDead[$i - 1] * $unitarray['pop']);
}
$totalpoint_def += $attackerDead[10] * 6;
if($Defender['hero'] > 0){
//counting heroxp
$defheroxp = intval($totalpoint_def / count($DefendersHeroID));
foreach($DefendersHeroID as $HeroID){
$database->modifyHeroXp("experience",$defheroxp,$HeroID);
}
}
// we don't need these two variables anymore
unset($AttackerHeroID, $DefendersHeroID);
$database->modifyPoints(
$toF['owner'],
['dpall', 'dp'],
[$totalpoint_def, $totalpoint_def]
);
$database->modifyPoints(
$from['owner'],
['apall', 'ap'],
[$totalpoint_att, $totalpoint_att]
);
$database->modifyPointsAlly(
$targetally,
['Adp', 'dp'],
[$totalpoint_def, $totalpoint_def]
);
$database->modifyPointsAlly(
$ownally,
['Aap', 'ap'],
[$totalpoint_att, $totalpoint_att]
);
return $totaldead_def;
}
/**
* Resolve the resources lootable from the battle target after cranny
* protection: compute the cranny efficiency, pull the current (capped)
* stocks of the target village/oasis and derive the lootable amounts.
* Pure behaviour-preserving extraction (refactor for issue #155).
*
* @param array $data Current attack row (uses 'to').
* @param int $isoasis Oasis flag (0 = village).
* @param int $conqureby Owner village of a conquered oasis (0 = none).
* @param int $owntribe Attacker tribe id (Teuton cranny bonus).
* @param int $targettribe Defender tribe id (Gaul cranny bonus).
* @param mixed $crannySpy OUT (by ref): cranny value seen by a scout; set only for village targets (isoasis == 0).
* @return array ['totclay','totiron','totwood','totcrop','avclay','aviron','avwood','avcrop'].
*/
private function resolveResourcesAfterBattle($data, $isoasis, $conqureby, $owntribe, $targettribe, &$crannySpy) {
global $database, $bid23;
if ($isoasis == 0){
// get total cranny value:
$buildarray = $database->getResourceLevel($data['to']);
$cranny = 0;
for($i = 19; $i < 39;$i++){
if($buildarray['f'.$i.'t'] == 23){
$cranny += $bid23[$buildarray['f'.$i]]['attri'] * CRANNY_CAPACITY;
}
}
//cranny efficiency
$atk_bonus = ($owntribe == 2) ? (4 / 5) : 1;
$def_bonus = ($targettribe == 3) ? 2 : 1;
$to_owner = $database->getVillageField($data['to'], "owner");
$crannySpy = $database->getArtifactsValueInfluence($to_owner, $data['to'], 7, $cranny * $def_bonus);
$cranny_eff = $crannySpy * $atk_bonus;
// work out available resources.
$this->updateRes($data['to']);
$this->pruneResource();
$villageData = $database->getVillageFields($data['to'], 'clay, iron, wood, crop', false);
$totclay = $villageData['clay'];
$totiron = $villageData['iron'];
$totwood = $villageData['wood'];
$totcrop = $villageData['crop'];
}else{
$cranny_eff = 0;
if ($conqureby > 0) { //10% from owner proc village owner - fix by ronix - exploit fixed by iopietro
$this->updateRes($conqureby);
$this->pruneResource();
$villageData = $database->getVillageFields($conqureby, 'clay, iron, wood, crop', false);
$totclay = intval($villageData['clay'] / 10);
$totiron = intval($villageData['iron'] / 10);
$totwood = intval($villageData['wood'] / 10);
$totcrop = intval($villageData['crop'] / 10);
}else{
// work out available resources.
$this->updateORes($data['to']);
$this->pruneOResource();
$oasisData = $database->getOasisFields($data['to'], false);
$totclay = $oasisData['clay'];
$totiron = $oasisData['iron'];
$totwood = $oasisData['wood'];
$totcrop = $oasisData['crop'];
}
}
$avclay = floor($totclay - $cranny_eff);
$aviron = floor($totiron - $cranny_eff);
$avwood = floor($totwood - $cranny_eff);
$avcrop = floor($totcrop - $cranny_eff);
$avclay = ($avclay < 0) ? 0 : $avclay;
$aviron = ($aviron < 0) ? 0 : $aviron;
$avwood = ($avwood < 0) ? 0 : $avwood;
$avcrop = ($avcrop < 0) ? 0 : $avcrop;
return [
'totclay' => $totclay, 'totiron' => $totiron, 'totwood' => $totwood, 'totcrop' => $totcrop,
'avclay' => $avclay, 'aviron' => $aviron, 'avwood' => $avwood, 'avcrop' => $avcrop,
];
}
/**
* Resolve the attacker hero's outcome after the battle: build the hero
* report line ($info_hero), and on a surviving hero handle oasis conquest /
* loyalty reduction (oasis targets) or artifact claiming with possible
* village destruction (village targets).
* Pure behaviour-preserving extraction (refactor for issue #155).
*
* @param array $data Current attack row (uses 't11', 'from', 'to').
* @param array $from Attacker village info (uses 'owner').
* @param array $to Defender village/oasis info (uses 'owner', 'loyalty').
* @param int $dead11 Attacker hero casualties.
* @param int $traped11 Attacker hero trapped count.
* @param mixed $heroxp Attacker hero XP gained this battle.
* @param string $hero_pic Hero unit picture id (report markup).
* @param int $isoasis Oasis flag (0 = village).
* @param int $type Attack type (3 = normal with rams/catas).
* @param int $targettribe Defender tribe id.
* @param string $info_cat Catapult report fragment (read for the destruction notice).
* @param string $info_hero IN/OUT (by ref): hero report line.
* @param int $can_destroy IN/OUT (by ref): village-destruction allowed flag.
* @param int $village_destroyed IN/OUT (by ref): village-destroyed flag.
* @return void
*/
private function handleHeroPostBattle($data, $from, $to, $dead11, $traped11, $heroxp, $hero_pic, $isoasis, $type, $targettribe, $info_cat, &$info_hero, &$can_destroy, &$village_destroyed) {
global $database;
if(($data['t11'] - $dead11 - $traped11) > 0){ //hero
if ($heroxp == 0) {
$xp = "";
$info_hero = $hero_pic.",".rc_tok('RC_HERO_NO_KILL');
} else {
$xp = " ".rc_tok('RC_HERO_AND_GAINED_XP_BATTLE', $heroxp);
$info_hero = $hero_pic.",".rc_tok('RC_HERO_GAINED_XP', $heroxp);
}
if ($isoasis != 0) { //oasis fix by ronix
if ($to['owner'] != $from['owner']) {
$troopcount = $database->countOasisTroops($data['to'], false);
$canqured = $database->canConquerOasis($data['from'], $data['to'], false);
if ($canqured == 1 && $troopcount == 0) {
$database->conquerOasis($data['from'], $data['to']);
$info_hero = $hero_pic.",".rc_tok('RC_HERO_CONQUERED_OASIS').$xp;
}else{
if ($canqured == 3 && $troopcount == 0) {
if ($type == 3) {
$Oloyaltybefore = intval($to['loyalty']);
//$database->modifyOasisLoyalty($data['to']);
//$OasisInfo = $database->getOasisInfo($data['to']);
$Oloyaltynow = intval($database->modifyOasisLoyalty($data['to']));//intval($OasisInfo['loyalty']);
$info_hero = $hero_pic.",".rc_tok('RC_HERO_REDUCED_OASIS_LOYALTY', $Oloyaltynow, $Oloyaltybefore).$xp;
}
else $info_hero = $hero_pic.",".rc_tok('RC_NO_REDUCE_LOYALTY_RAID').$xp;
}
}
}
} else {
if ($heroxp == 0) $xp=" ".rc_tok('RC_HERO_NO_XP_BATTLE');
else $xp=" ".rc_tok('RC_HERO_GAINED_XP_BATTLE', $heroxp);
$artifact = reset($database->getOwnArtefactInfo($data['to']));
if (!empty($artifact)) {
if ($type == 3) {
if (empty($artifactError = $database->canClaimArtifact($data['from'], $artifact['vref'], $artifact['size'], $artifact['type']))) {
$database->claimArtefact($data['from'], $data['to'], $database->getVillageField($data['from'], "owner"));
$info_hero = $hero_pic.",".rc_tok('RC_HERO_CARRYING_ARTIFACT', $artifact['name']).$xp;
// if the defender pop is 0 with no artefact, then destroy the village
if($database->getVillageField($data['to'], "pop") == 0 || $targettribe == 5){
$can_destroy = $village_destroyed = 1;
if(strpos($info_cat, rc_tok('RC_VILLAGE_DESTROYED')) === false) $info_hero .= " ".rc_tok('RC_VILLAGE_DESTROYED');
}
}
else $info_hero = $hero_pic.",".$artifactError.$xp;
}
else $info_hero = $hero_pic.",".rc_tok('RC_HERO_NO_ARTIFACT_RAID').$xp;
}
}
}elseif($data['t11'] > 0) {
if ($heroxp == 0) $xp = "";
else $xp = " ".rc_tok('RC_HERO_BUT_GAINED_XP_BATTLE', $heroxp);
if ($traped11 > 0) $info_hero = $hero_pic.",".rc_tok('RC_HERO_TRAPPED').$xp;
else $info_hero = $hero_pic.",".rc_tok('RC_HERO_DIED').$xp;
}
}
/**
* Send the battle/scout report notification to the defender.
* Pure behaviour-preserving extraction (refactor for issue #155).
*
* @param mixed $scout Truthy when the attack is a scouting run.
* @param array $battlepart Battle result (uses 'casualties_attacker').
* @param array $from Attacker village info (uses 'owner', 'name').
* @param array $to Defender village/oasis info (uses 'owner', 'wref', 'name').
* @param int $targetally Defender alliance id.
* @param string $data2 Combat-report payload.
* @param int $AttackArrivalTime Battle resolution timestamp.
* @param string $unitsdead_att Attacker dead units string.
* @param string $unitssend_att Attacker sent units string.
* @param bool $defspy Whether the defence has scouts.
* @param string $info_troop Troop-return report fragment.
* @param int $totalsend_alldef Total defending troops (incl. reinforcements).
* @param int $totalsend_att Total attacking troops sent.
* @param int $totaldead_att Total attacking troops killed.
* @param int $totaltraped_att Total attacking troops trapped.
* @param int $totaldead_alldef Total defending troops killed.
* @return void
*/
private function sendBattleNotifications($scout, $battlepart, $from, $to, $targetally, $data2, $AttackArrivalTime, $unitsdead_att, $unitssend_att, $defspy, $info_troop, $totalsend_alldef, $totalsend_att, $totaldead_att, $totaltraped_att, $totaldead_alldef) {
global $database;
//Undetected and detected in here.
if(!empty($scout)){
for($i = 1; $i <= 10; $i++){
if($battlepart['casualties_attacker'][$i]){
if($from['owner'] == 3){
$database->addNotice($to['owner'],$to['wref'],$targetally,20,''.addslashes($from['name']).' scouts '.addslashes($to['name']).'',$data2,$AttackArrivalTime);
break;
}else if($unitsdead_att == $unitssend_att && $defspy){ //fix by ronix
$database->addNotice($to['owner'],$to['wref'],$targetally,20,''.addslashes($from['name']).' scouts '.addslashes($to['name']).'',$data2.',,'.$info_troop,$AttackArrivalTime);
break;
}else if($defspy){ //fix by ronix
$database->addNotice($to['owner'],$to['wref'],$targetally,21,''.addslashes($from['name']).' scouts '.addslashes($to['name']).'',$data2,$AttackArrivalTime);
break;
}
}
}
}else{
if($totalsend_alldef == 0 && $totalsend_att - ($totaldead_att + (isset($totaltraped_att) ? $totaltraped_att : 0)) > 0){
$database->addNotice($to['owner'],$to['wref'],$targetally,7,''.addslashes($from['name']).' attacks '.addslashes($to['name']).'',$data2,$AttackArrivalTime);
}else if($totaldead_alldef == 0){
$database->addNotice($to['owner'],$to['wref'],$targetally,4,''.addslashes($from['name']).' attacks '.addslashes($to['name']).'',$data2,$AttackArrivalTime);
}else if($totalsend_alldef > $totaldead_alldef){
$database->addNotice($to['owner'],$to['wref'],$targetally,5,''.addslashes($from['name']).' attacks '.addslashes($to['name']).'',$data2,$AttackArrivalTime);
}else if($totalsend_alldef == $totaldead_alldef){
$database->addNotice($to['owner'],$to['wref'],$targetally,6,''.addslashes($from['name']).' attacks '.addslashes($to['name']).'',$data2,$AttackArrivalTime);
}
}
}
/**
* Finalize the fate of the attacking troops: if any survived they return
* home (attacker report + return movement + loot transfer / RR points /
* leftover-chief enforcement), otherwise they die without returning (only
* the all-dead report is sent). Pure behaviour-preserving extraction
* (refactor for issue #155).
*
* @param array $data Current attack row (uses 't1..t11', 'moveid', 'ref').
* @param array $from Attacker village info (uses 'wref', 'owner', 'name').
* @param array $to Defender village/oasis info (uses 'wref', 'owner', 'name').
* @param int $owntribe Attacker tribe id.
* @param int $type Attack type (1 = scout).
* @param int $isoasis Oasis flag (0 = village).
* @param int $conqureby Owner village of a conquered oasis (0 = none).
* @param int $AttackArrivalTime Battle resolution timestamp.
* @param int $targetally Defender alliance id.
* @param int $ownally Attacker alliance id.
* @param string $data2 Combat-report payload (survivors).
* @param string $data_fail Combat-report payload (all dead).
* @param int $chiefing_village Whether this attack chiefed the village.
* @param int $DefenderWref Defender world ref.
* @param int $AttackerWref Attacker world ref.
* @param array $steal [wood, clay, iron, crop] looted.
* @param array $dead Attacker casualties, 1-indexed t1..t11.
* @param array $traped Attacker trapped, 1-indexed t1..t11.
* @param array $troopsdead Attacker dead counts for enforcement, 1-indexed t1..t11.
* @param int $totalsend_att Total attacking troops sent.
* @param int $totaldead_att Total attacking troops killed.
* @param int $totaltraped_att Total attacking troops trapped.
* @param int $totalstolentaken IN/OUT (by ref): running RR loss accumulator for the defender.
* @return void
*/
private function finalizeReturnOrDeath($data, $from, $to, $owntribe, $type, $isoasis, $conqureby, $AttackArrivalTime, $targetally, $ownally, $data2, $data_fail, $chiefing_village, $DefenderWref, $AttackerWref, $steal, $dead, $traped, $troopsdead, $totalsend_att, $totaldead_att, $totaltraped_att, &$totalstolentaken) {
global $database, $units;
// Spital: o parte din trupele proprii moarte IN ATAC devin ranite acasa.
// $dead e indexat 1..11 pe sloturile tribului; doar unitatile de lupta (1-8).
if($owntribe >= 1 && $owntribe <= 9) {
$woundedAtt = [];
for($wj = 1; $wj <= 8; $wj++) {
if(!empty($dead[$wj])) $woundedAtt[($owntribe - 1) * 10 + $wj] = $dead[$wj];
}
if(!empty($woundedAtt)) $this->applyHospitalWounded($from['wref'], $woundedAtt);
}
// If the dead units not equal the ammount sent they will return and report
if($totalsend_att - ($totaldead_att + (isset($totaltraped_att) ? $totaltraped_att : 0)) > 0)
{
$returningTroops = [];
for($i = 1; $i <= 11; $i++) $returningTroops['t'.$i] = $data['t'.$i] - $traped[$i] - $dead[$i];
// T4 hero port (Phase 6): bandages revive part of the
// losses when the hero comes home alive; revived units
// are written back to the attacks row (the source
// returnunitsComplete reads on arrival). No-op when off.
$returningTroops = HeroBattleBonus::applyBandages($from['owner'], $returningTroops, $dead, (int) $data['ref']);
$troopsTime = $units->getWalkingTroopsTime($from['wref'], $to['wref'], $from['owner'], $owntribe, $returningTroops, 1, 't');
$endtime = $database->getArtifactsValueInfluence($from['owner'], $from['wref'], 2, $troopsTime);
$endtime += $AttackArrivalTime;
// T4 hero port (Phase 5): the map shortens the return
// leg when the SURVIVING hero travels home. No-op when off.
$endtime = HeroBattleBonus::adjustReturnEndtime($from['owner'], $returningTroops['t11'], $AttackArrivalTime, $endtime);
if($type == 1){
if($from['owner'] == 3){ // fix natar report by ronix
$database->addNotice($to['owner'], $to['wref'], $targetally, 20, '' . addslashes($from['name']) . ' scouts ' . addslashes($to['name']) . '', $data2, $AttackArrivalTime);
}elseif($totaldead_att == 0 && $totaltraped_att == 0){
$database->addNotice($from['owner'], $to['wref'], $ownally, 18, '' . addslashes($from['name']) . ' scouts ' . addslashes($to['name']) . '', $data2, $AttackArrivalTime);
}else{
$database->addNotice($from['owner'], $to['wref'], $ownally, 21, '' . addslashes($from['name']) . ' scouts ' . addslashes($to['name']) . '', $data2, $AttackArrivalTime);
}
}else{
if((empty($totaldead_att) || $totaldead_att == 0) && (empty($totaltraped_att) || $totaltraped_att == 0)){
$database->addNotice($from['owner'], $to['wref'], $ownally, 1, '' . addslashes($from['name']) . ' attacks ' . addslashes($to['name']) . '', $data2, $AttackArrivalTime);
}else{
$database->addNotice($from['owner'], $to['wref'], $ownally, 2, '' . addslashes($from['name']) . ' attacks ' . addslashes($to['name']) . '', $data2, $AttackArrivalTime);
}
}
$database->setMovementProc($data['moveid']);
if (!isset($chiefing_village)) $chiefing_village = 0;
if($chiefing_village != 1){
$database->addMovement(4, $DefenderWref, $AttackerWref, $data['ref'], $AttackArrivalTime, $endtime, 1, $steal[0], $steal[1], $steal[2], $steal[3]);
if($type !== 1){
if ($isoasis == 0) $database->modifyResource($DefenderWref, $steal[0], $steal[1], $steal[2], $steal[3], 0);
else
{
//if it's an oasis but it's conquered by someone, resources must be modified in the owner's village
if($conqureby > 0) $database->modifyResource($conqureby, $steal[0], $steal[1], $steal[2], $steal[3], 0);
else $database->modifyOasisResource($DefenderWref, $steal[0], $steal[1], $steal[2], $steal[3], 0);
}
$totalstolengain = $steal[0] + $steal[1] + $steal[2] + $steal[3];
$totalstolentaken = ((isset($totalstolentaken) ? $totalstolentaken : 0) - ($steal[0] + $steal[1] + $steal[2] + $steal[3]));
$database->modifyPoints($from['owner'], 'RR', $totalstolengain);
$database->modifyPoints($to['owner'], 'RR', $totalstolentaken);
$database->modifyPointsAlly($targetally, 'RR', $totalstolentaken);
$database->modifyPointsAlly($ownally, 'RR', $totalstolengain);
}
}else{ //fix by ronix if only 1 chief left to conqured - don't add with zero enforces
if($totalsend_att - ($totaldead_att + (isset($totaltraped_att) ? $totaltraped_att : 0)) > 1){
$database->addEnforce2($data, $owntribe, $troopsdead[1], $troopsdead[2], $troopsdead[3], $troopsdead[4], $troopsdead[5], $troopsdead[6], $troopsdead[7], $troopsdead[8], $troopsdead[9], $troopsdead[10], $troopsdead[11]);
}
}
}
else //else they die and don't return or report.
{
$database->setMovementProc($data['moveid']);
if($type == 1){
$database->addNotice($from['owner'], $to['wref'], $ownally, 19, addslashes($from['name']) . ' scouts ' . addslashes($to['name']) . '', $data_fail, $AttackArrivalTime);
}else{
$database->addNotice($from['owner'], $to['wref'], $ownally, 3, '' . addslashes($from['name']) . ' attacks ' . addslashes($to['name']) . '', $data_fail, $AttackArrivalTime);
}
}
}
/**
* Destroy the target village when the battle flagged it for destruction:
* reassign the capital to the player's most populated remaining village if
* needed, delete the village and reassign its hero.
* Pure behaviour-preserving extraction (refactor for issue #155).
*
* @param int $village_destroyed Village-destroyed flag.
* @param int $can_destroy Village-destruction allowed flag.
* @param array $data Current attack row (uses 'to').
* @param array $to Defender village info (uses 'capital').
* @param array $varray The defender owner's villages.
* @return void
*/
private function handleVillageDestruction($village_destroyed, $can_destroy, $data, $to, $varray) {
global $database;
if (!($village_destroyed == 1 && $can_destroy == 1)) {
return;
}
if($to['capital'] == 1){
$mostPopulatedVillage = [];
//Search for the most populated village
foreach($varray as $village){
if($village['wref'] != $data['to'] && (empty($mostPopulatedVillage) || $mostPopulatedVillage['pop'] < $village['pop'])){
$mostPopulatedVillage = $village;
}
}
//Set the new capital
$database->changeCapital($mostPopulatedVillage['wref']);
}
//Delete the village
$database->DelVillage($data['to']);
//Reassign the hero, if dead and assigned to the deleted village
$database->reassignHero($data['to']);
}
private function sendunitsComplete() {
// COMPLETE ATTACK PROCESSING - critical function, kept 100% compatible
// This function handles all attacks that reach their destination.
// Includes: battles, traps, hero evasion, building destruction, and village conquest.
global $bid23, $database, $battle, $technology, $units;
$time = time();
$dataarray = $this->fetchCompletedAttacks($time);
$totalattackdead = $data_num = 0;
if ($dataarray && count($dataarray)) {
// preload village data (batched) so the per-attack loop hits the
// cache instead of querying row by row
$this->preloadBattleData($dataarray);
// tiles whose village was razed earlier in this same batch (issue #298)
$razedTargets = [];
// calculate battles
foreach($dataarray as $data) {
//set base things
$totaltraped_att = 0;
for($i = 1; $i <= 11; $i++) ${'traped'.$i} = 0;
// per-attack context (attacker village/owner) — extracted to resolveAttackContext() [#155]
$ctx = $this->resolveAttackContext($data);
$isoasis = $ctx['isoasis'];
$AttackArrivalTime = $ctx['AttackArrivalTime'];
$AttackerWref = $ctx['AttackerWref'];
$DefenderWref = $ctx['DefenderWref'];
$NatarCapital = $ctx['NatarCapital'];
$AttackerID = $ctx['AttackerID'];
$Attacker['id'] = $ctx['AttackerID'];
$owntribe = $ctx['owntribe'];
$ownally = $ctx['ownally'];
$from = $ctx['from'];
$fromF = $ctx['fromF'];
//It's a village
if ($isoasis == 0){
// target + battle environment — extracted to resolveVillageTarget() [#155]
$vt = $this->resolveVillageTarget($data, $dataarray, $data_num, $owntribe);
$DefenderID = $vt['DefenderID'];
$targettribe = $vt['targettribe'];
$targetally = $vt['targetally'];
$to = $vt['to'];
$toF = $vt['toF'];
$conqureby = $vt['conqureby'];
$NatarCapital = $vt['NatarCapital'];
$DefenderUnit = $vt['DefenderUnit'];
$def_wall = $vt['def_wall'];
$att_tribe = $vt['att_tribe'];
$def_tribe = $vt['def_tribe'];
$attpop = $vt['attpop'];
$defpop = $vt['defpop'];
$residence = $vt['residence'];
$def_ab = $vt['def_ab'];
$type = $vt['type'];
$scout = $vt['scout'];
$att_ab1 = $vt['att_ab1'];
$att_ab2 = $vt['att_ab2'];
$att_ab3 = $vt['att_ab3'];
$att_ab4 = $vt['att_ab4'];
$att_ab5 = $vt['att_ab5'];
$att_ab6 = $vt['att_ab6'];
$att_ab7 = $vt['att_ab7'];
$att_ab8 = $vt['att_ab8'];
$walllevel = $vt['walllevel'];
$wallid = $vt['wallid'];
$tblevel = $vt['tblevel'];
$stonemason = $vt['stonemason'];
// Issue #298: the target village no longer exists — it was razed
// either earlier in this same batch ($razedTargets), or in an
// earlier tick whose still-in-flight follow-up waves DelVillage()
// failed to bounce. getMInfo() then returns NULL vdata columns
// ($to['wref'] is NULL), so resolving a battle here would fight a
// phantom village and compute the return trip from NULL coordinates
// — a bogus arrival time that strands the troops in an endless loop
// (report against "[?]"). Bounce the whole army straight home
// instead, exactly like DelVillage() does for in-flight attacks,
// and mark the movement processed so it stops being re-fetched.
if (isset($razedTargets[$data['to']]) || empty($to['wref'])) {
// only own the bounce if DelVillage() hasn't already handled it
// (setMovementProc() returns true only when it flips proc 0->1),
// so we never create a duplicate return movement
if ($this->setMovementProc($data['moveid'])) {
$bounceTime = $units->getWalkingTroopsTime($from['wref'], $data['to'], $from['owner'], $owntribe, $data, 1, 't');
$bounceEnd = $database->getArtifactsValueInfluence($from['owner'], $from['wref'], 2, $bounceTime) + $AttackArrivalTime;
$database->addMovement(4, $data['to'], $from['wref'], $data['ref'], $AttackArrivalTime, $bounceEnd);
}
$data_num++;
continue;
}
$this->handleEvasion($data, $DefenderID, $DefenderUnit, $targettribe, $vt['evasion'], $vt['maxevasion'], $vt['gold'], $vt['cannotsend'], $dataarray[$data_num]['attack_type']);
// defence units gathered — extracted to buildDefenderUnits() [#155]
$defUnits = $this->buildDefenderUnits($data['to']);
$Defender = $defUnits['Defender'];
$enforDefender = $defUnits['enforDefender'];
$enforcementarray = $defUnits['enforcementarray'];
// attacker army built — extracted to buildAttackerUnits() [#155]
$atkUnits = $this->buildAttackerUnits($dataarray[$data_num], $owntribe, $isoasis);
$Attacker = $atkUnits['Attacker'];
$start = $atkUnits['start'];
$end = $atkUnits['end'];
$catp_pic = $atkUnits['catp_pic'];
$ram_pic = $atkUnits['ram_pic'];
$chief_pic = $atkUnits['chief_pic'];
$spy_pic = $atkUnits['spy_pic'];
$hero_pic = $atkUnits['hero_pic'];
}else{ //It's an oasis
// target + battle environment — extracted to resolveOasisTarget() [#155]
$ot = $this->resolveOasisTarget($data, $dataarray, $data_num, $owntribe);
$DefenderID = $ot['DefenderID'];
$targettribe = $ot['targettribe'];
$targetally = $ot['targetally'];
$to = $ot['to'];
$toF = $ot['toF'];
$conqureby = $ot['conqureby'];
$def_wall = $ot['def_wall'];
$att_tribe = $ot['att_tribe'];
$def_tribe = $ot['def_tribe'];
$attpop = $ot['attpop'];
$defpop = $ot['defpop'];
$residence = $ot['residence'];
$def_ab = $ot['def_ab'];
$type = $ot['type'];
$scout = $ot['scout'];
$att_ab1 = $ot['att_ab1'];
$att_ab2 = $ot['att_ab2'];
$att_ab3 = $ot['att_ab3'];
$att_ab4 = $ot['att_ab4'];
$att_ab5 = $ot['att_ab5'];
$att_ab6 = $ot['att_ab6'];
$att_ab7 = $ot['att_ab7'];
$att_ab8 = $ot['att_ab8'];
$walllevel = $ot['walllevel'];
$tblevel = $ot['tblevel'];
$stonemason = $ot['stonemason'];
// defence units gathered — extracted to buildDefenderUnits() [#155]
$defUnits = $this->buildDefenderUnits($data['to']);
$Defender = $defUnits['Defender'];
$enforDefender = $defUnits['enforDefender'];
$enforcementarray = $defUnits['enforcementarray'];
// T4 hero port (Phase 8): cages capture animals on an
// UNOCCUPIED oasis before the fight - captured animals do
// not defend and are stationed at home as reinforcements.
// No-op when the flag is off, no hero rides along, the
// oasis is owned, or the attacker has no cages.
if ($isoasis != 0 && (int) $conqureby === 0 && (int) $data['t11'] > 0) {
$Defender = HeroBattleBonus::applyCages($from['owner'], (int) $data['from'], (int) $data['to'], $Defender);
}
// attacker army built — extracted to buildAttackerUnits() [#155]
$atkUnits = $this->buildAttackerUnits($dataarray[$data_num], $owntribe, $isoasis);
$Attacker = $atkUnits['Attacker'];
$start = $atkUnits['start'];
$end = $atkUnits['end'];
$catp_pic = $atkUnits['catp_pic'];
$ram_pic = $atkUnits['ram_pic'];
$chief_pic = $atkUnits['chief_pic'];
$spy_pic = $atkUnits['spy_pic'];
$hero_pic = $atkUnits['hero_pic'];
}
// attacker/defender populations + village lists — extracted to calculatePopulations() [#155]
$popData = $this->calculatePopulations($to, $from, $isoasis, $defpop, $attpop);
$varray = $popData['varray'];
$varray1 = $popData['varray1'];
$defpop = $popData['defpop'];
$attpop = $popData['attpop'];
//fix by ronix
for ($i = 1; $i <= 90; $i++) {
if (!isset($enforDefender['u'.$i])) {
$enforDefender['u'.$i] = 0;
}
$enforDefender['u'.$i] += (isset($Defender['u'.$i]) ? $Defender['u'.$i] : 0);
}
$defspy = $enforDefender['u4'] > 0 || $enforDefender['u14'] > 0 || $enforDefender['u23'] > 0 || $enforDefender['u44'] > 0;
if(PEACE == 0 || $targettribe == 4 || $targettribe == 5 || $scout){
// trapper resolution + prisoners — extracted to calculateTrappedUnits() [#155]
$trapResult = $this->calculateTrappedUnits($data, $Defender, $Attacker, $NatarCapital, $scout, $start, $end);
for($i = 1; $i <= 11; $i++) ${'traped'.$i} = $trapResult['traped'][$i];
$totaltraped_att = $trapResult['totaltraped_att'];
$Attacker = $trapResult['Attacker'];
// we need to save the attacker heroid before the battle
$AttackerHeroID = 0;
if(isset($Attacker['uhero']) && $Attacker['uhero'] > 0){
$AttackerHeroID = $database->getHeroField($from['owner'], "heroid");
}
// and the defender(s) heroid
$DefendersHeroID = [];
$herosend_def = 0;
// check if our hero is defending the village, if so, add it to the list
if (isset($Defender['hero']) && $Defender['hero'] > 0) {
$DefendersHeroID[] = $database->getHeroField($DefenderID, "heroid");
// collecting information for the battle report
$herosend_def += $Defender['hero'];
}
// check if there are other heroes defending the village
if(count($enforcementarray) > 0){
foreach($enforcementarray as $enforcement){
if($enforcement['hero'] > 0){
$heroOwner = $database->getVillageField($enforcement['from'], "owner");
$DefendersHeroID[] = $database->getHeroField($heroOwner, "heroid");
}
}
}
//fix by ronix
if (!isset($walllevel)) $walllevel = 0;
$battlepart = $battle->calculateBattle($Attacker, $Defender, $def_wall, $att_tribe, $def_tribe, $residence, $attpop, $defpop, $type, $def_ab, $att_ab1, $att_ab2, $att_ab3, $att_ab4, $att_ab5, $att_ab6, $att_ab7, $att_ab8, $tblevel, $stonemason, $walllevel, 0, 0, 0, $AttackerID, $DefenderID, $AttackerWref, $DefenderWref, $conqureby, $enforcementarray);
//Data for when troops return.
//catapults look :D
$info_cat = $info_chief = $info_ram = $info_hero = ",";
//check to see if can destroy village
if (count($varray) > 1 && !$database->villageHasArtefact($DefenderWref) && empty($to['natar'])) {
$can_destroy = 1;
}
else $can_destroy = 0;
//Catapults and rams management
//TODO: Move this in Battle.php
if($isoasis == 0){
if ($type == 3){
if (($data['t7'] - $traped7) > 0){
// ram damage on the wall (+ battle recalc) — extracted to applyRamDamage() [#155]
$ramResult = $this->applyRamDamage($data, $walllevel, $wallid, $ram_pic, $battlepart, [
'Attacker' => $Attacker, 'Defender' => $Defender,
'att_tribe' => $att_tribe, 'def_tribe' => $def_tribe,
'residence' => $residence, 'attpop' => $attpop, 'defpop' => $defpop,
'type' => $type, 'def_ab' => $def_ab,
'att_ab1' => $att_ab1, 'att_ab2' => $att_ab2, 'att_ab3' => $att_ab3, 'att_ab4' => $att_ab4,
'att_ab5' => $att_ab5, 'att_ab6' => $att_ab6, 'att_ab7' => $att_ab7, 'att_ab8' => $att_ab8,
'tblevel' => $tblevel, 'stonemason' => $stonemason,
'AttackerID' => $AttackerID, 'DefenderID' => $DefenderID,
'AttackerWref' => $AttackerWref, 'DefenderWref' => $DefenderWref,
'conqureby' => $conqureby, 'enforcementarray' => $enforcementarray,
]);
$battlepart = $ramResult['battlepart'];
$info_ram = $ramResult['info_ram'];
}
if (($data['t8'] - $traped8) > 0)
{
$catResult = $this->applyCatapults($data, $battlepart, $catp_pic, $can_destroy, $isoasis, $targettribe, $info_cat);
$battlepart = $catResult['battlepart'];
$info_cat = $catResult['info_cat'];
$village_destroyed = $catResult['village_destroyed'];
}
} elseif (($data['t7'] - $traped7) > 0) {
$info_ram = "".$ram_pic.",Hint: The ram does not work during a raid.";
}
}
else $can_destroy = 0;
//units attack string for battleraport
$unitssend_att = ''.$data['t1'].','.$data['t2'].','.$data['t3'].','.$data['t4'].','.$data['t5'].','.$data['t6'].','.$data['t7'].','.$data['t8'].','.$data['t9'].','.$data['t10'].'';
$herosend_att = $data['t11'];
//reinforcement report (pre-casualty defender data) — extracted to collectReinforcementReport() [#155]
$reinfReport = $this->collectReinforcementReport($data, $targettribe, $Defender);
$DefenderHeroesTotArray = $reinfReport['DefenderHeroesTotArray'];
$DefenderHeroesDeadArray = $reinfReport['DefenderHeroesDeadArray'];
$unitssend_def = $reinfReport['unitssend_def'];
$unitssend_deff = $reinfReport['unitssend_deff'];
$totalsend_alldef = $reinfReport['totalsend_alldef'];
$Defender['hero'] = $reinfReport['defenderHero'];
for($i = 1; $i <= 11; $i++){
//MUST TO BE FIX : This is only for defender and still not properly coded
if (isset($battlepart['casualties_attacker']) && isset($battlepart['casualties_attacker'][$i]) && $battlepart['casualties_attacker'][$i] <= 0) {
${'dead'.$i} = 0;
} else if (isset($data['t'.$i]) && isset($battlepart['casualties_attacker']) && isset($battlepart['casualties_attacker'][$i]) && $battlepart['casualties_attacker'][$i] > $data['t'.$i]) {
${'dead'.$i} = $data['t'.$i];
} else {
${'dead'.$i} = (isset($battlepart['casualties_attacker']) && isset($battlepart['casualties_attacker'][$i]) ? $battlepart['casualties_attacker'][$i] : 0);
}
}
$dead = [];
$owndead = [];
$alldead = [];
for($i = 1; $i <= 90; $i++) $alldead[$i] = 0;
//kill own defence — extracted to applyOwnDefenceCasualties() [#155]
$owndead = $this->applyOwnDefenceCasualties($data, $targettribe, $battlepart);
//kill other defence in village (reinforcements) — extracted to applyReinforcementCasualties() [#155]
$tribesPresent = $this->applyReinforcementCasualties($data, $battlepart, $from, $to, $ownally, $AttackArrivalTime, $scout, $alldead, $DefenderHeroesDeadArray);
$rom = $tribesPresent['rom'];
$ger = $tribesPresent['ger'];
$gal = $tribesPresent['gal'];
$nat = $tribesPresent['nat'];
$natar = $tribesPresent['natar'];
$hun = $tribesPresent['hun'];
$egy = $tribesPresent['egy'];
$spa = $tribesPresent['spa'];
$vik = $tribesPresent['vik'];
$totalsend_att = $data['t1']+$data['t2']+$data['t3']+$data['t4']+$data['t5']+$data['t6']+$data['t7']+$data['t8']+$data['t9']+$data['t10']+$data['t11'];
$DefenderHeroesTot = implode(",", $DefenderHeroesTotArray);
$DefenderHeroesDead = implode(",", $DefenderHeroesDeadArray);
if (empty($alldead['hero'])) $alldead['hero'] = 0;
if (empty($owndead['hero'])) $owndead['hero'] = 0;
// sursa autoritativa: rezultatul luptei (owndead['hero'] se poate pierde
// pe unele cai de executie, iar raportul afisa eroul aparator ca viu)
$deadhero = (int)(isset($battlepart['deadherodef']) && $battlepart['deadherodef'] > 0
? $battlepart['deadherodef']
: $owndead['hero']);
//Counting own total dead troops
$ownDeadTroops = array_slice($owndead, 0, 10);
$totaldead_alldef = array_sum($ownDeadTroops);
//Collecting informations for the report
$unitsdead_def[0] = implode(",", $ownDeadTroops);
$unitsdead_deff[0] = '?,?,?,?,?,?,?,?,?,?,';
for($i = 1; $i <= 9; $i++){
//Counting reinforcements total dead troops
$deadTroops = array_slice($alldead, ($i - 1) * 10, 10);
$totaldead_alldef += array_sum($deadTroops);
//Collecting informations for the report
$unitsdead_def[$i] = implode(",", $deadTroops);
$unitsdead_deff[$i] = $unitsdead_deff[0];
}
$totaldead_alldef += ($deadhero + $alldead['hero']);
if (!isset($totalattackdead)) $totalattackdead = 0;
$totalattackdead += $totaldead_alldef;
// Set units returning from attack
$p_units = [];
for ($i = 1; $i <= 11; $i++) {
if (!isset(${'dead'.$i})) ${'dead'.$i} = 0;
if (!isset(${'traped'.$i})) ${'traped'.$i} = 0;
$p_units[] = "t".$i." = t".$i." - ".(${'dead'.$i} + ${'traped'.$i});
}
$database->modifyAttack3($data['ref'],implode(', ', $p_units));
$unitsdead_att = $dead1.','.$dead2.','.$dead3.','.$dead4.','.$dead5.','.$dead6.','.$dead7.','.$dead8.','.$dead9.','.$dead10;
$unitstraped_att = $traped1.','.$traped2.','.$traped3.','.$traped4.','.$traped5.','.$traped6.','.$traped7.','.$traped8.','.$traped9.','.$traped10.','.$traped11;
//top 10 attack and defence update
$totaldead_att = $dead1 + $dead2 + $dead3 + $dead4 + $dead5 + $dead6 + $dead7 + $dead8 + $dead9 + $dead10 + $dead11;
$totalattackdead += $totaldead_att;
$troopsdead1 = $dead1;
$troopsdead2 = $dead2;
$troopsdead3 = $dead3;
$troopsdead4 = $dead4;
$troopsdead5 = $dead5;
$troopsdead6 = $dead6;
$troopsdead7 = $dead7;
$troopsdead8 = $dead8;
$troopsdead9 = $dead9 + 1;
$troopsdead10 = $dead10;
$troopsdead11 = $dead11;
// hero XP, player points and alliance points — extracted to calculateHeroXpAndPoints() [#155]
$totaldead_def = $this->calculateHeroXpAndPoints(
$alldead, $owndead,
[$dead1, $dead2, $dead3, $dead4, $dead5, $dead6, $dead7, $dead8, $dead9, $dead10, $dead11],
$targettribe, $att_tribe,
$Attacker, $AttackerHeroID, $Defender, $DefendersHeroID,
$toF, $from, $targetally, $ownally,
$heroxp, $defheroxp
);
// resources lootable after cranny protection — extracted to resolveResourcesAfterBattle() [#155]
$resAfter = $this->resolveResourcesAfterBattle($data, $isoasis, $conqureby, $owntribe, $targettribe, $crannySpy);
$totclay = $resAfter['totclay'];
$totiron = $resAfter['totiron'];
$totwood = $resAfter['totwood'];
$totcrop = $resAfter['totcrop'];
$avclay = $resAfter['avclay'];
$aviron = $resAfter['aviron'];
$avwood = $resAfter['avwood'];
$avcrop = $resAfter['avcrop'];
// bounty distributed across the resources available after cranny protection (extracted to applyBounty() [#155])
// T4 hero port (Phase 5): thief sacks raise the carry
// bounty when the attacker's living hero joined the strike.
$heroBounty = (int) round($battlepart['bounty'] * HeroBattleBonus::raidMultiplier($from['owner'], $data['t11']));
$steal = $this->applyBounty([$avwood, $avclay, $aviron, $avcrop], $heroBounty);
//chiefing village — extracted to handleConquest() [#155]
if (!isset($village_destroyed)) $village_destroyed = 0;
$chiefResult = $this->handleConquest($data, $type, $dead9, $traped9, $targettribe, $from, $to, $toF, $owntribe, $varray, $varray1, $battlepart, $isoasis, $village_destroyed, $chief_pic);
$info_chief = $chiefResult['info_chief'];
$chiefing_village = $chiefResult['chiefing_village'];
$village_destroyed = $chiefResult['village_destroyed'];
// attacker hero outcome (oasis conquest/loyalty, artifact claim, report line) — extracted to handleHeroPostBattle() [#155]
$this->handleHeroPostBattle($data, $from, $to, $dead11, $traped11, $heroxp, $hero_pic, $isoasis, $type, $targettribe, $info_cat, $info_hero, $can_destroy, $village_destroyed);
if ($DefenderID == 0) $natar = 0;
if (!empty($scout)) {
$info_spy = $this->buildScoutReport($data, $spy_pic, $isoasis, $targettribe, $crannySpy, $totwood, $totclay, $totiron, $totcrop);
}
// prisoners freed during conquest attacks — extracted to handlePrisoners() [#155]
$info_trap = empty($scout) ? $this->handlePrisoners($data, $from, $to, $ownally, $type, $totalsend_att, $totaldead_att, $totaltraped_att) : '';
$info_troop = ($totalsend_att - ($totaldead_att + (isset($totaltraped_att) ? $totaltraped_att : 0)) <= 0) ? rc_tok('RC_NONE_RETURNED') : "";
// assemble data2 + data_fail — extracted to buildCombatReport() [#155]
$report = $this->buildCombatReport(
$scout, $from, $to, $owntribe, $targettribe,
$unitssend_att, $unitsdead_att, $steal, $battlepart,
$unitssend_def, $unitsdead_def, $unitssend_deff, $unitsdead_deff,
$rom, $ger, $gal, $nat, $natar,
$hun, $egy, $spa, $vik,
$DefenderHeroesTot, $DefenderHeroesDead,
$info_ram, $info_cat, $info_chief, isset($info_spy) ? $info_spy : '',
$info_trap, $info_hero, $info_troop,
$data, $dead11, $herosend_def, $deadhero, $unitstraped_att,
$village_destroyed, $can_destroy, $catp_pic
);
$data2 = $report['data2'];
$data_fail = $report['data_fail'];
// notify the defender of the incoming battle/scout — extracted to sendBattleNotifications() [#155]
$this->sendBattleNotifications($scout, $battlepart, $from, $to, $targetally, $data2, $AttackArrivalTime, $unitsdead_att, $unitssend_att, $defspy, $info_troop, $totalsend_alldef, $totalsend_att, $totaldead_att, $totaltraped_att, $totaldead_alldef);
// surviving attacker troops return (report + movement + loot/RR/enforce) or die — extracted to finalizeReturnOrDeath() [#155]
$retDead = $retTraped = $retTroopsdead = [];
for ($i = 1; $i <= 11; $i++) { $retDead[$i] = ${'dead'.$i}; $retTraped[$i] = ${'traped'.$i}; $retTroopsdead[$i] = ${'troopsdead'.$i}; }
$this->finalizeReturnOrDeath($data, $from, $to, $owntribe, $type, $isoasis, $conqureby, $AttackArrivalTime, $targetally, $ownally, $data2, $data_fail, $chiefing_village, $DefenderWref, $AttackerWref, $steal, $retDead, $retTraped, $retTroopsdead, $totalsend_att, $totaldead_att, $totaltraped_att, $totalstolentaken);
if($type == 3 || $type == 4) $database->addGeneralAttack($totalattackdead);
if (!isset($village_destroyed)) $village_destroyed = 0;
// delete the target village if it was destroyed — extracted to handleVillageDestruction() [#155]
$this->handleVillageDestruction($village_destroyed, $can_destroy, $data, $to, $varray);
// remember the razed tile so any follow-up wave still queued in
// this same batch bounces home instead of fighting a phantom
// (now-deleted) village. Needed on top of the $to['wref'] check
// because getMInfo() is cached: a same-batch wave would still see
// the stale "alive" village row — see the guard right after
// resolveVillageTarget() (issue #298).
if ($village_destroyed == 1 && $can_destroy == 1) {
$razedTargets[$data['to']] = true;
}
}else{
//units attack string for battleraport
$unitssend_att1 = ''.$data['t1'].','.$data['t2'].','.$data['t3'].','.$data['t4'].','.$data['t5'].','.$data['t6'].','.$data['t7'].','.$data['t8'].','.$data['t9'].','.$data['t10'].'';
$herosend_att = $data['t11'];
$unitssend_att= $unitssend_att1.','.$herosend_att;
$troopsTime = $units->getWalkingTroopsTime($from['wref'], $to['wref'], $from['owner'], $owntribe, $data, 1, 't');
$endtime = $database->getArtifactsValueInfluence($from['owner'], $from['wref'], 2, $troopsTime);
$endtime += $AttackArrivalTime;
// T4 hero port (Phase 5): map return-speed bonus, same as
// the combat return leg above. No-op when the flag is off.
$endtime = HeroBattleBonus::adjustReturnEndtime($from['owner'], $data['t11'], $AttackArrivalTime, $endtime);
$database->setMovementProc($data['moveid']);
$database->addMovement(4, $to['wref'], $from['wref'], $data['ref'], $AttackArrivalTime, $endtime);
$peace = PEACE;
$data2 = $from['owner'].','.$from['wref'].','.$to['owner'].','.$owntribe.','.$unitssend_att.','.$peace;
$time = time();
$database->addNotice($from['owner'], $to['wref'], $ownally, 22,''.addslashes($from['name']).' attacks '.addslashes($to['name']).'', $data2, $time);
$database->addNotice($to['owner'], $to['wref'], $targetally, 23,''.addslashes($from['name']).' attacks '.addslashes($to['name']).'', $data2, $time);
}
//Update starvation data
$database->addStarvationData($to['wref']);
$data_num++;
unset(
$Attacker
,$Defender
,$DefenderHeroesTotArray
,$DefenderHeroesDeadArray
,$DefenderHeroesTot
,$DefenderHeroesDead
,$unitssend_att
,$unitssend_def
,$battlepart
,$unitsdead_def
,$dead
,$steal
,$from
,$data
,$data2
,$to
,$data_fail
,$owntribe
,$unitsdead_att
,$herosend_def
,$deadhero
,$heroxp
,$AttackerID
,$DefenderID
,$totalsend_att
,$totalsend_alldef
,$totaldead_att
,$totaltraped_att
,$totaldead_def
,$totalattackdead
,$defheroxp
,$AttackerWref
,$DefenderWref
,$troopsdead1
,$troopsdead2
,$troopsdead3
,$troopsdead4
,$troopsdead5
,$troopsdead6
,$troopsdead7
,$troopsdead8
,$troopsdead9
,$troopsdead10
,$troopsdead11
,$DefenderUnit
,$info_trap
,$report);
}
}
}
private function bountysortOasis($oasisArray) {
$crop = $clay = $wood = $iron = 0;
foreach ($oasisArray as $oasis) {
switch($oasis['type']) {
case 1:
case 2:
$wood++;
break;
case 3:
$wood++;
$crop++;
break;
case 4:
case 5:
$clay++;
break;
case 6:
$clay++;
$crop++;
break;
case 7:
case 8:
$iron++;
break;
case 9:
$iron++;
$crop++;
break;
case 10:
case 11:
$crop++;
break;
case 12:
$crop += 2;
break;
}
}
return [$wood, $clay, $iron, $crop];
}
function getAllUnits($base, $use_cache = true) {
global $database;
$ownunit = $database->getUnit($base, $use_cache);
$enforcementarray = $database->getEnforceVillage($base, 0);
if(count($enforcementarray) > 0){
foreach($enforcementarray as $enforce){
for($i = 1; $i <= 90; $i++){
$ownunit['u'.$i] += $enforce['u'.$i];
}
}
}
$enforceoasis = $database->getOasisEnforce($base, 0, $use_cache);
if(count($enforceoasis) > 0){
foreach($enforceoasis as $enforce){
for($i = 1; $i <= 90; $i++){
$ownunit['u'.$i] += $enforce['u'.$i];
}
}
}
$enforceoasis1 = $database->getOasisEnforce($base, 1, $use_cache);
if(count($enforceoasis1) > 0){
foreach($enforceoasis1 as $enforce){
for($i = 1; $i <= 90; $i++){
$ownunit['u'.$i] += $enforce['u'.$i];
}
}
}
$movement = $database->getVillageMovement($base);
if(!empty($movement)){
for($i = 1; $i <= 90; $i++){
if(!isset($ownunit['u' . $i])){
$ownunit['u'.$i] = 0;
}
$ownunit['u'.$i] += (isset($movement['u'.$i]) ? $movement['u'.$i] : 0);
}
}
$prisoners = $database->getPrisoners($base, 1);
if(!empty($prisoners)){
foreach($prisoners as $prisoner){
$owner = $database->getVillageField($base, "owner");
$ownertribe = $database->getUserField($owner, "tribe", 0);
$start = ($ownertribe - 1) * 10 + 1;
$end = ($ownertribe * 10);
for($i = $start; $i <= $end; $i++){
$j = $i - $start + 1;
$ownunit['u'.$i] += $prisoner['t'.$j];
}
$ownunit['hero'] += $prisoner['t11'];
}
}
return $ownunit;
}
// Production of a single resource (1=wood, 2=clay, 3=iron, 4=crop) for the
// given village field layout, used by the bounty/production catch-up. The
// four resources only differ by: the production-building data global, the
// booster building field-type(s) whose 'attri' adds a % bonus, and the oasis
// bonus slot (resourceType - 1). Crop additionally has two boosters
// (grain mill type 8, then bakery type 9 applied on the running total) and
// the gold-club +25% crop bonus (b4) keyed on the village owner.
private function bountyGetResourceProd($resArray, $oasisNumber, $resourceType) {
global $bid1, $bid2, $bid3, $bid4, $bid5, $bid6, $bid7, $bid8, $bid9, $database;
$prodBid = [1 => $bid1, 2 => $bid2, 3 => $bid3, 4 => $bid4][$resourceType];
$boosterBid = [5 => $bid5, 6 => $bid6, 7 => $bid7, 8 => $bid8, 9 => $bid9];
// Booster field-types per resource, in application order (crop: mill then bakery).
$boosterTypes = [1 => [5], 2 => [6], 3 => [7], 4 => [8, 9]][$resourceType];
$prod = 0;
$holders = [];
$boosterLevels = array_fill_keys($boosterTypes, 0);
for($i = 1; $i <= 38; $i++) {
if($resArray['f'.$i.'t'] == $resourceType) $holders[] = 'f'.$i;
if(isset($boosterLevels[$resArray['f'.$i.'t']])) $boosterLevels[$resArray['f'.$i.'t']] = $resArray['f'.$i];
}
foreach($holders as $holder) $prod += $prodBid[$resArray[$holder]]['prod'];
foreach($boosterTypes as $bt) {
$level = $boosterLevels[$bt];
if($level >= 1) $prod += $prod / 100 * (isset($boosterBid[$bt][$level]['attri']) ? $boosterBid[$bt][$level]['attri'] : 0);
}
$oasisIndex = $resourceType - 1;
if($oasisNumber[$oasisIndex] > 0) $prod += $prod * 0.25 * $oasisNumber[$oasisIndex];
if($resourceType == 4 && !empty($resArray['vref']) && is_numeric($resArray['vref'])) {
$who = $database->getVillageField($resArray['vref'], "owner");
$croptrue = $database->getUserField($who, "b4", 0);
if($croptrue > time()) $prod *= 1.25;
}
return round($prod * SPEED);
}
}