Mount authored gmCombatUI, share one press/hold/release request state machine across DAT buttons and keybindings, and recover the exact 1.0s/0.8s power timing from matching retail x86. The same timer fixes jump charge, while ready-stance, response queueing, auto-repeat, layout binding, migration, and conformance coverage keep behavior architectural rather than panel-local. Co-Authored-By: Codex <codex@openai.com>
4.5 KiB
4.5 KiB
Retail basic combat bar — port pseudocode
Sources
- Named retail decomp:
gmCombatUI::RecvNotice_AttackHeightChanged0x004CC080gmCombatUI::RecvNotice_SetPowerbarLevel0x004CC0E0gmCombatUI::RecvNotice_DesiredAttackPowerChanged0x004CC110gmCombatUI::ListenToElementMessage0x004CC430gmCombatUI::RecvNotice_SetCombatMode0x004CC620ClientCombatSystem::Begin0x0056A460ClientCombatSystem::StartPowerBarBuild0x0056ADB0ClientCombatSystem::GetPowerBarLevel0x0056ADE0ClientCombatSystem::StartAttackRequest0x0056C040ClientCombatSystem::EndAttackRequest0x0056C0E0ClientCombatSystem::UseTime0x0056C1F0ClientCombatSystem::HandleAttackDoneEvent0x0056C500ClientCombatSystem::SetRequestedAttackHeight0x0056C8F0
- Matching retail executable disassembly (
acclient.exev11.4186 + PDB):ATTACK_POWERUP_TIME = 1.0seconds andDUAL_WIELD_POWERUP_TIME = 0.8seconds. 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 is0x1000005C, desired-power scrollbar is0x1000004F, embedded meter is0x10000050, and the high/medium/low buttons are0x10000057/58/59. - Interpretation cross-checks: ACE combat request/power fields and the
holtburger
DesiredAttackProfile -> attack_requestwire path summarized indocs/research/deepdives/r02-combat-system.md.
Initial state
combatMode = NonCombat
buildInProgress = false
attackRequestInProgress = false
attackServerResponsePending = false
requestedAttackPower = 0
requestedAttackHeight = Medium
desiredUiPower = 0.5
repeatAttacking = false
Panel visibility
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
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
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.