# 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. Visibility is combat-state-owned, not a user window preference. Per-character window persistence may restore the bar's authored position, but must not restore or save its visible bit: `RecvNotice_SetCombatMode` is the sole visibility authority. Character-session initialization likewise starts at `NonCombat`, as shown by `ClientCombatSystem::Begin` (`0x0056A460`). ## 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 dark interior texture source = nested meter child 0x100005EF ``` 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`, live bright-red fill `0x06001200`, and dark-red interior texture `0x0600715E`. The three visual layers remain separate. The gray scrollbar track spans the whole authored control. Retail skill-gates element `0x100005EF` to Recklessness advancement class `Trained` or higher when scalar message `0x0A` is received. Per the connected visual gate, acdream uses that dark-red media as the always- visible middle baseline (intentional divergence IA-20); the remaining exact skill-gated treatment stays in AP-112. The bright child retains retail's live `RecvNotice_SetPowerbarLevel` input and spans the entire scrollbar from the absolute left edge, independently of the dark child's inset geometry. The green thumb remains driven by `RecvNotice_DesiredAttackPowerChanged`. The meter authors attribute `0x6F = 1`. `UIElement_Meter` constructor and `DrawChildren @ 0x0046FBD0` make direction 1 the forward horizontal clip, so the bright texture grows left-to-right from Speed. Direction 3 is the reverse horizontal clip. The importer reads this attribute; it does not infer direction from the combat element id. The Speed and Power text rectangles are later siblings of the scrollbar. Their inner edges define the visible meter interval so the authored gray track stays behind both captions. Speed's raw property `0x14` value is `2`, which is left justification: `UIElement_Text::CalcJustification @ 0x00467260` centers only value `1`, right-aligns values `3` and `5`, and treats every other value as left. Power uses right justification. The combat subtree inherits from an 800-pixel prototype and reflows into its 610-pixel mounted root on the first layout traversal. Scrollbars consume their DAT children procedurally, so the dark child must retain its own four-edge `UiLayoutPolicy`; copying only its sprite loses the child geometry. The authored child starts at `x=70,w=567` inside the inherited 707-pixel meter and its proportional edge modes reflow to `x=50,w=407` inside the mounted 507-pixel bar. This is the wider centered interval that clears the rendered Speed/Power words; using the full 100-pixel text element boxes produced an overly narrow range.