# Retail basic combat bar — port pseudocode ## Sources - Named retail decomp: - `gmCombatUI::RecvNotice_AttackHeightChanged` `0x004CC080` - `gmCombatUI::RecvNotice_SetPowerbarLevel` `0x004CC0E0` - `gmCombatUI::RecvNotice_DesiredAttackPowerChanged` `0x004CC110` - `gmCombatUI::ListenToElementMessage` `0x004CC430` - `gmCombatUI::RecvNotice_SetCombatMode` `0x004CC620` - `ClientCombatSystem::Begin` `0x0056A460` - `ClientCombatSystem::StartPowerBarBuild` `0x0056ADB0` - `ClientCombatSystem::GetPowerBarLevel` `0x0056ADE0` - `ClientCombatSystem::StartAttackRequest` `0x0056C040` - `ClientCombatSystem::EndAttackRequest` `0x0056C0E0` - `ClientCombatSystem::UseTime` `0x0056C1F0` - `ClientCombatSystem::HandleAttackDoneEvent` `0x0056C500` - `ClientCombatSystem::SetRequestedAttackHeight` `0x0056C8F0` - Matching retail executable disassembly (`acclient.exe` v11.4186 + PDB): `ATTACK_POWERUP_TIME = 1.0` seconds and `DUAL_WIELD_POWERUP_TIME = 0.8` seconds. The named pseudo-C's x87 expression had incorrectly collapsed the division operands. - LayoutDesc `0x21000073` (`gmCombatUI`), cross-checked against the retail UI layout dump. Root is 610×90; basic page is `0x1000005C`, desired-power scrollbar is `0x1000004F`, embedded meter is `0x10000050`, and the high/medium/low buttons are `0x10000057/58/59`. - Interpretation cross-checks: ACE combat request/power fields and the holtburger `DesiredAttackProfile -> attack_request` wire path summarized in `docs/research/deepdives/r02-combat-system.md`. ## Initial state ```text combatMode = NonCombat buildInProgress = false attackRequestInProgress = false attackServerResponsePending = false requestedAttackPower = 0 requestedAttackHeight = Medium desiredUiPower = 0.5 repeatAttacking = false ``` ## Panel visibility ```text when combat mode changes: if AdvancedCombatUI: hide gmCombatUI else if mode == Melee: set root state Melee (0x10000003) show gmCombatUI else if mode == Missile: set root state Missile (0x10000004) show gmCombatUI else: hide gmCombatUI ``` The current acdream port binds the basic page because the persisted gameplay default has `AdvancedCombatUI = false`. The separate advanced-combat surface is still tracked by divergence row AP-110. ## Press / charge / release ```text SetRequestedAttackHeight(height): changed = requestedAttackHeight != height requestedAttackHeight = height if changed OR no attack request is active: StartAttackRequest() StartAttackRequest(): target = current attack target if target is not attackable: report no target and stop attackRequestInProgress = true requestedAttackPower = 1 stop jump and movement currentBuildIsAutomatic = false if player is ready and no build is active: StartPowerBarBuild() StartPowerBarBuild(): buildInProgress = true buildStartTime = currentTime displayedPower = 0 GetPowerBarLevel(): if no build: return 0 duration = current stance is DualWieldCombat ? 0.8 seconds : 1.0 second return clamp((currentTime - buildStartTime) / duration, 0, 1) EndAttackRequest(height, explicitPower = -1): if no attack request: stop attackRequestInProgress = false current = GetPowerBarLevel() requestedAttackPower = explicitPower >= 0 ? explicitPower : min(desiredUiPower, current) if a prior attack is awaiting the server: queue this power for AttackDone else if desiredUiPower <= current OR repeat attack is active: ExecuteAttack(height) otherwise: UseTime commits on its next eligible tick at requestedAttackPower UseTime(): if a build is active: current = GetPowerBarLevel() publish current to meter if request was released AND current >= requestedAttackPower: publish min(requestedAttackPower, current) if build is not automatic: ExecuteAttack(requestedHeight) else stop automatic build ``` ## UI messages ```text mouse-down high/medium/low: SetRequestedAttackHeight(High/Medium/Low) mouse-up high/medium/low: EndAttackRequest(the height, -1) desired-power slider changed with integer position 0..1000: desiredUiPower = clamp(position * 0.001, 0, 1) attack power increase/decrease action: move desiredUiPower by one sixth, clamped to 0..1 ``` Both keyboard and retained buttons therefore use one transition-based attack controller. A one-shot click/keypress is not sufficient: down begins charging, up ends the request.