# Retail basic combat bar — port pseudocode ## Sources - Named retail decomp: - `gmCombatUI::RecvNotice_AttackHeightChanged` `0x004CC080` - `gmCombatUI::RecvNotice_SetPowerbarLevel` `0x004CC0E0` - `gmCombatUI::RecvNotice_DesiredAttackPowerChanged` `0x004CC110` - `gmCombatUI::PostInit` `0x004CC1A0` - `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` - `UIOption_Checkbox::UIOption_Checkbox` `0x00486A50` - `UIOption_Checkbox::ListenToElementMessage` `0x004869A0` - `compute_str_hash` `0x00413110` - 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. ## Authored labels and option checkboxes ```text PostInit(): repeat = child 0x10000053 as UIOption_Checkbox repeat.playerOption = AutoRepeatAttack repeat.label = StringInfo(hash("ID_CombatPanelOption_AutoRepeatAttack")) autoTarget = child 0x10000054 as UIOption_Checkbox autoTarget.playerOption = AutoTarget autoTarget.label = StringInfo(hash("ID_CombatPanelOption_AutoTarget")) keepInView = child 0x10000055 as UIOption_Checkbox keepInView.playerOption = ViewCombatTarget keepInView.label = StringInfo(hash("ID_CombatPanelOption_ViewCombatTarget")) ``` The first two labels are deliberately not authored in LayoutDesc. They are assigned by `gmCombatUI::PostInit`, so a layout-only renderer must perform that controller binding. Ordinary Type-12 captions and High/Medium/Low use StringInfo property `0x17` and resolve through local.dat table `0x23000001`. `UIOption_Checkbox` is a toggle `UIElement_Button`. Its visible 13x13 face is the authored child `0x10000328`; the label is embedded in the option object and starts after that face. Mouse release toggles attribute `0x0E` and immediately applies the associated player option. ## Horizontal scrollbar media roles ```text track = scrollbar DirectState image thumb = structural child element 1 live power fill = nested meter child 0x10000050 / image child 2 ``` These roles are identified by authored element ids, never by resolved width. The combat thumb is a 12x14 green jewel; inheritance reflow can make its effective child geometry wide enough that a width heuristic incorrectly tiles the jewel as the background. The base track is `0x060074CA`, thumb `0x06001923`, and live red fill `0x06001200`.