From 06d1523d9df25d933edcb306ddcf5603da1eff30 Mon Sep 17 00:00:00 2001 From: Ferywir <65760459+Ferywir@users.noreply.github.com> Date: Tue, 30 Jun 2026 14:06:44 +0200 Subject: [PATCH] fix(combat): gate Brewery attack bonus on an active Mead-Festival [#294] (#321) The Brewery (building 35, Teuton-only) grants a +1% attack bonus per level to the whole empire, but only while a Mead-Festival is running (72h), and the building is capital-only. The attack-bonus block in calculateBattle() ignored both rules: it read the Brewery level from $AttackerWref (the *launching* village, which usually has no Brewery) and applied the bonus purely on the building level, with no festival check. As a result the bonus never reacted to the festival being started or expired: attacks with and without an active festival produced identical losses, so the army appeared unaffected by the festival (issue #294). Depending on whether the attack was launched from the capital, the bonus was either permanently on or permanently off, but never gated by the festival. Read the Brewery level from the attacker's capital and gate the bonus on the festival being active, mirroring the catapult-randomization gate in Units.php and the chief-penalty gate in Automation.php. The simulator passes AttackerID = 0, so it keeps a 0 bonus exactly as before. Co-authored-by: Claude Opus 4.8 --- GameEngine/Battle.php | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/GameEngine/Battle.php b/GameEngine/Battle.php index 1c8a76c0..e14f7fd0 100644 --- a/GameEngine/Battle.php +++ b/GameEngine/Battle.php @@ -844,25 +844,36 @@ class Battle { /****************************************************************** * ATTACK / DEFENSE TOTAL ******************************************************************/ - if ($AttackerWref != 0) { + $bonus = 0; - $typeLevel = $this->getTypeLevel(35, $AttackerWref); + // Brewery (35) Mead-Festival attack bonus: Teuton-only, capital-only but + // empire-wide, and active ONLY while a festival is running (72h). It must be + // read from the attacker's CAPITAL — $AttackerWref is the launching village, + // which usually has no Brewery — and gated on the festival being active, + // otherwise the bonus is permanent and never reacts to the festival being + // started/expired (issue #294). This mirrors the catapult-randomization gate + // in Units.php and the chief-penalty gate in Automation.php. The simulator + // passes AttackerID = 0, so it keeps a 0 bonus exactly as before. + if ($AttackerID != 0 && $att_tribe == 2) { - $bonus = isset($bid35[$typeLevel]) - ? $bid35[$typeLevel]['attri'] - : 0; + $attackerCapital = $database->getVillage($AttackerID, 3); - $rap = round( - ($ap + $cap) + ( - (($ap + $cap) / 100) * $bonus - ) - ); + if ($attackerCapital && (int)$attackerCapital['festival'] > time()) { - } else { + $typeLevel = $this->getTypeLevel(35, $attackerCapital['wref']); - $rap = round($ap + $cap); + $bonus = isset($bid35[$typeLevel]) + ? $bid35[$typeLevel]['attri'] + : 0; + } } + $rap = round( + ($ap + $cap) + ( + (($ap + $cap) / 100) * $bonus + ) + ); + if ($rap == 0) { $rdp = round($dp + $cdp);