On the rally point incoming tab, the number of an incoming unit type is never
revealed: it is always shown as a "?". When that stack is smaller than the
defender's rally point (gid 16) level, the "?" is rendered in solid black
bold, matching original Travian behaviour (e.g. rally point level 20 and an
incoming 19 praetorians shows a bold "?"). The eyesight artifact still reveals
which troop types are present (0 for the absent ones). Scope: village
attacks/raids only.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Extract the per-branch defender target resolution and battle-environment
setup into two private helpers: resolveVillageTarget() and
resolveOasisTarget(). Each returns the target owner (tribe/alliance), map
info, conquest flag and the battle parameters (wall, armory/blacksmith
tech, residence, siege masonry); the village helper also returns the
evasion inputs. Both are read-only (no DB writes).
The foreach body keeps handleEvasion(), buildDefenderUnits() and
buildAttackerUnits() as explicit, ordered calls, so the village and oasis
branches are now symmetric orchestration.
Behaviour-preserving. The building/tech reads now run inside the helper
before handleEvasion(); they read buildings and technology only (never the
troops handleEvasion() may move), so the result is unchanged. A few
dead locals are dropped (playerunit, wallgid, w; the redundant
DefenderUnit/def_ab re-inits).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extract the per-attack, target-independent context resolution (attacker
village/owner tribe and alliance, war references, base flags) into a
private helper. Read-only, behaviour-preserving: the three repeated
getCachedUser() lookups on the attacker owner are collapsed into one
(the user cache makes them idempotent).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The defender's units were gathered by two near-identical inline blocks
(village and oasis targets). Extract them into a single private method
buildDefenderUnits() returning the defender's own troops (normalised to
non-negative ints), the aggregated reinforcement totals (enforDefender) and
the raw reinforcement rows (enforcementarray).
Pure behaviour-preserving extraction:
- Both call sites assign the returned bundle; all downstream usages unchanged.
- The oasis reinforcement aggregation now uses the same isset-guarded loop as
the village one: identical numeric result, minus a latent PHP 8.3
"undefined array key" notice.
- The dead `$def_ab[$i] = 0` init that lived in the village normalisation loop
is dropped: it was unconditionally wiped by the later `$def_ab = []` before
any use.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The marketplace send tab (17.tpl) was refactored with an empty <script>
block, which dropped the `haendler` (available merchants) and `carry`
(per-merchant capacity) globals that add_res()/upd_res() in unx.js rely on.
Without them `ic = haendler * carry` evaluates to NaN, so clicking the
"(capacity)" link next to a resource (or the resource icon) no longer fills
the input. Restore the two globals so the max-per-merchant fill works again.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The attacking army was built by two near-identical inline blocks (village
and oasis targets). Extract them into a single private method
buildAttackerUnits() that returns the Attacker unit array (u<start..end> +
uhero) together with the catapult / ram / chief / scout unit ids used in the
report. The oasis target keeps its Nature siege/chief slots (37/38/39) via
the $isoasis flag.
Pure behaviour-preserving extraction: both call sites now assign the returned
bundle, so all downstream usages remain unchanged. The unit-id picks are
initialised to null (they are always set for the real attacker tribes 1/2/3/5;
only the unreachable Nature-attacker case differs, which silences a latent
PHP 8.3 undefined-variable notice).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Move the ram-damage handling out of sendunitsComplete() into a dedicated
private method applyRamDamage(). For a normal attack (type 3) with rams, it
computes the new wall level, updates it in the database (recounting the
village population when the wall is destroyed), builds the report fragment,
and recalculates the battle when the wall level changed.
Pure behaviour-preserving extraction: the battle-recalc context is passed in
a single $ctx array; the call site keeps the t7 guard and assigns the
returned battlepart / info_ram, so all downstream usages remain unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Move the trapper resolution block out of sendunitsComplete() into a
dedicated private method calculateTrappedUnits(). It computes how many
incoming attacker units are caught in the defender's traps (Gaul trapper
or Natar capital), updates the trap counters and the prisoners table, and
subtracts the trapped troops from the attacking army.
Pure behaviour-preserving extraction: the inline `${'traped'.$i}`
variables are rehydrated at the call site from the returned bundle, so all
downstream usages remain unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Move the attacker/defender total-population computation (and the two
getProfileVillages() lookups that feed it) out of the per-attack loop into a
dedicated private method. Behaviour-preserving: the method takes the initial
$defpop/$attpop (0 for villages, 500 for the oasis branch) and accumulates onto
them exactly as before, and returns the village lists ($varray/$varray1) used
later for the can-destroy check and handleConquest().
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Follow-up to the unit/tribe rename: a few strings still used the old names.
- MANUAL_UDESC_11: "Les Massues" -> "Les Combattants au gourdin" (U11),
so the in-game manual description matches the unit name.
- TZ_MACEMAN: "Massue" -> "Combattant au gourdin" (used as the U11 label in
the alliance forum troop picker).
- Conquest help text: the loyalty-reduction units listed "COMMANDANTS, CHEFS"
(old Teuton name) -> "CHEFS, CHEFS DE TRIBU", matching Senator/Chief/
Chieftain (Romain/Germain/Gaulois) like the English version.
- Wonder-of-the-World lore: anglicism "Natarian" -> "natarien/natarienne"
(empire natarien, capitale natarienne, menace natarienne), consistent with
the Natar unit names.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The five troop-overview pages hardcoded the English title "Troops (X)",
so the in-game manual kept showing e.g. "Troops (Teutons)" even in French,
inconsistent with the now-corrected unit/tribe names. Replace the hardcoded
titles with the existing TROOPS and TRIBE1-5 constants (consistent with the
#186 manual i18n migration) so the title follows the active language,
e.g. "Troupes (Germains)" in French. en.php already defines all of these
constants as the idempotent fallback, so other languages are unaffected.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Tribe 2 renamed Teutons -> Germains (the French name for this tribe),
including all manual/tooltip/description texts and adjective forms
(heros germain, armee germaine, troupes germaines).
- Teuton units: Massueur -> Combattant au gourdin (U11),
Hallebardier -> Combattant a la lance (U12),
Chevalier teutonique -> Cavalier teuton (U16), Commandant -> Chef (U19).
- Roman units: Imperial -> Imperian (U3) and official Latin cavalry names
Equites Legati/Imperatoris/Caesaris (U4-U6).
- Gaul units: Foudre de Teutates -> Teutates accent (U24),
Haeduen -> Hedui (U26).
- Natar units: anglicism natarian -> natarien (U46/U49/U50) and
"artefact natarian" -> "artefact natarien" in building descriptions.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extract the pre-casualty defender report block into a new private method
collectReinforcementReport(): reset and rebuild the reinforcement unit totals
($DefenderEnf) and 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 and their masked variants), and
(re)initialise the per-tribe dead-hero accumulator. The bundle is returned to
the caller and the defender's folded hero count is reassigned at the call site.
Pure behaviour-preserving extraction. Method name matches the maintainer's
roadmap for #155.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extract the "kill other defence in village" block (reinforcement casualties)
into a new private method applyReinforcementCasualties(): compute each
reinforcement's dead units/hero, persist them (modifyEnforce), notify the
reinforcing players, and delete fully-wiped reinforcements. Dead counts are
accumulated into $alldead and dead heroes into $DefenderHeroesDeadArray (both
passed by reference); the tribe-presence flags (rom/ger/gal/nat/natar) are
returned to the caller.
Pure behaviour-preserving extraction. Also drops two dead recomputations of
$totalsend_att/$totaldead_att that sat inside the loop and were immediately
overwritten right after it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Move the in-village defender casualty handling (kill ratio applied to the
defender's own troops + hero, persisted via modifyUnit) out of the giant
sendunitsComplete() loop into a dedicated private method returning the
$owndead map. Pure behaviour-preserving extraction; also drops a dead $u
variable that was assigned but never read.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
On some servers the NPC ("merchant") distribution screen could lock on
"Rest: NaN", hiding the trade button and making the NPC unusable. The "Rest"
value never returned to 0, so the player could not complete the trade.
Two layers of hardening:
* Client (Templates/Build/17_3.tpl):
- add a safe npcDiv() helper (returns 0 on a 0 divisor or non-finite value)
and route every division in portionOut() through it, so Infinity/NaN can no
longer leak into the inputs;
- in calculateRest(), coerce "Rest"/overall to a finite value (fall back to
the total when org4 cannot be parsed) so the submit button can always
reappear once the distribution is balanced;
- coerce each resource, $totalRes, $maxstore and $maxcrop to a valid,
non-negative integer before they reach the page/JS, so a resource computed
as NaN/INF cannot poison org4/summe in the first place.
* Server (Market::tradeResource): clamp each requested amount to [0, maxstore]
(crop to maxcrop) before persisting, so a forged or corrupted POST can no
longer write out-of-range resources.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The send/trade-route resource fields were capped at maxlength="5", limiting
input to 99,999 per resource. On high-speed servers a single convoy carries far
more (e.g. Teutons: 1000 x speed), so players could not fill their merchants.
Raise the cap to maxlength="9" (~1 billion), which covers any server speed.
The server already validates against available resources and merchant capacity,
so no server-side change is needed and no over-send is possible.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>