fix(combat): escape occupied login positions
Port retail's radius-aware placement ring so a relogging player is seated beside creatures occupying the saved location, and register the local body in the shared resolved-shadow pipeline. Route new forward movement and jump through AbortAutomaticAttack so repeat combat cancels immediately on movement. Co-Authored-By: Codex <codex@openai.com>
This commit is contained in:
parent
e671b8a5c4
commit
00fe993f6f
11 changed files with 643 additions and 16 deletions
|
|
@ -0,0 +1,119 @@
|
|||
# Login placement and repeat-attack cancellation — retail pseudocode
|
||||
|
||||
## Sources
|
||||
|
||||
- `CTransition::find_placement_pos` @ `0x0050BA50`
|
||||
- `CTransition::find_placement_position` @ `0x0050C170`
|
||||
- `CObjCell::find_obj_collisions` @ `0x0052B750`
|
||||
- `CPhysicsObj::add_shadows_to_cells` @ `0x00514AE0`
|
||||
- `CPhysicsObj::enter_world` @ `0x00516170`
|
||||
- `ClientCombatSystem::AbortAutomaticAttack` @ `0x0056AE90`
|
||||
- `ClientCombatSystem::CommenceJump` @ `0x0056AF90`
|
||||
- `ACCmdInterp::HandleNewForwardMovement` @ `0x0058B1F0`
|
||||
- Named pseudo-C: `docs/research/named-retail/acclient_2013_pseudo_c.txt`
|
||||
- Interpretation cross-check: ACE `Transition.cs`, `CommandInterpreter.cs`, and
|
||||
`ACCmdInterp.cs` on the `ACEmulator/ACE` `master` branch.
|
||||
|
||||
## Enter-world placement
|
||||
|
||||
Retail `enter_world(position)` stores the server position and calls
|
||||
`enter_world(true)`. That constructs `SetPosition` flags `0x11`, so placement
|
||||
may slide away from an obstruction.
|
||||
|
||||
```text
|
||||
find_placement_position():
|
||||
check = current
|
||||
insert_type = INITIAL_PLACEMENT
|
||||
test environment/cells at check
|
||||
(CObjCell skips shadow-object collisions for INITIAL_PLACEMENT)
|
||||
validate initial placement
|
||||
|
||||
insert_type = PLACEMENT
|
||||
if !find_placement_pos(): fail
|
||||
|
||||
optionally step down to a walkable surface
|
||||
validate final placement
|
||||
```
|
||||
|
||||
`find_placement_pos` is the object-aware nearest-clear-point search:
|
||||
|
||||
```text
|
||||
find_placement_pos():
|
||||
check = current
|
||||
clear sliding/contact state
|
||||
if placement_test(check) succeeds:
|
||||
accept check and return true
|
||||
|
||||
if placement sliding is disabled:
|
||||
return false
|
||||
|
||||
search_distance = 4 metres
|
||||
search_radius = 4 metres
|
||||
radius = mover foot-sphere radius
|
||||
|
||||
if radius < 0.125 metres:
|
||||
fake_sphere = true
|
||||
search_radius = 2 metres
|
||||
else if radius < 0.48 metres:
|
||||
radius = 0.48 metres
|
||||
|
||||
exact_step_count = 4 / radius
|
||||
if fake_sphere: exact_step_count *= 0.5
|
||||
if exact_step_count <= 1: return false
|
||||
|
||||
step_count = ceil(exact_step_count)
|
||||
distance_step = search_radius / step_count
|
||||
radians_step = PI * distance_step / radius
|
||||
total_distance = 0
|
||||
total_radians = 0
|
||||
|
||||
for each radial step:
|
||||
total_distance += distance_step
|
||||
total_radians += radians_step
|
||||
compass_samples = ceil(total_radians) * 2
|
||||
heading_step_degrees = 360 / compass_samples
|
||||
|
||||
for sample = 0 .. compass_samples-1:
|
||||
check = current
|
||||
heading = heading_step_degrees * sample
|
||||
offset = forward_vector(heading) * total_distance
|
||||
offset = adjust_offset(offset)
|
||||
if length(offset) < epsilon: continue
|
||||
|
||||
check += offset
|
||||
clear sliding/contact state
|
||||
if placement_test(check) succeeds:
|
||||
accept check and return true
|
||||
|
||||
return false
|
||||
```
|
||||
|
||||
The local player must also own a normal shadow entry. Retail registers every
|
||||
placed `CPhysicsObj`; collision queries exclude only the moving object's own
|
||||
pointer. Therefore remote monsters can collide/de-overlap against the local
|
||||
player, while the player's placement search skips its own shadow by entity id.
|
||||
|
||||
## Movement cancels repeat attack
|
||||
|
||||
```text
|
||||
ACCmdInterp.HandleNewForwardMovement():
|
||||
if combat_system exists:
|
||||
combat_system.AbortAutomaticAttack()
|
||||
base.HandleNewForwardMovement() // also stops auto-run
|
||||
|
||||
ClientCombatSystem.AbortAutomaticAttack():
|
||||
if server_response_pending
|
||||
or attack_request_in_progress
|
||||
or attack_in_progress
|
||||
or repeat_attacking:
|
||||
send Event_CancelAttack
|
||||
repeat_attacking = false
|
||||
if combat power build is active:
|
||||
HidePowerBar()
|
||||
```
|
||||
|
||||
`CommandInterpreter::AddCommand` calls `HandleNewForwardMovement` for the
|
||||
forward-substate command list, which includes forward/backward/autorun rather
|
||||
than the independent turn and sidestep lists. Jump has the same cancellation
|
||||
at `ClientCombatSystem::CommenceJump` before its jump power bar begins.
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue