fix: battleground objectives use the plain-use path and skip the client gate

This commit is contained in:
chairmanpow
2026-10-06 17:05:56 +01:00
parent 7e043060eb
commit be808eb7e6
2 changed files with 42 additions and 28 deletions
+7
View File
@@ -645,6 +645,13 @@ pub(crate) fn resolve_go_action(
me_store: Option<&ObjectStore>,
net: &NetCommands,
) -> GoAction {
// A battleground objective is a plain use: its template lock is the server's capture
// bookkeeping, not a chest lock the client should resolve, and the server validates the use
// itself. Without this the objective's lock chain can refuse (`Refuse`/`OpenLock`) a click the
// reference sends as `CMSG_GAMEOBJ_USE`.
if inputs.battleground.active() {
return GoAction::Use;
}
let Some(tmpl) = inputs.templates.get(guid) else {
return GoAction::Use;
};
+35 -28
View File
@@ -306,20 +306,24 @@ fn highlightable_flags(
if type_id == GO_TYPE_MEETINGSTONE {
return !overrides.meeting_stone_queued;
}
// Inside a battleground the whole shared gate is skipped for the objective types the type gate
// above let through: the objective's `GAMEOBJECT_FACTION` is a team marker the client cannot
// resolve against the player's race faction on a cross-faction realm, and the occupied banners'
// `GAMEOBJECT_FLAGS`/activate bit are the server's per-player bookkeeping. The server validates
// every battleground use (`CanUseBattleGroundObject`), so the client offer is safe.
if overrides.battleground {
return true;
}
// The faction term (`0x5f3026`): the GameObject's reaction toward us must beat hostile. An
// unresolved reaction passes, so a data gap never blanks the world. Skipped inside a
// battleground: the objective's faction is a team marker the client cannot resolve against the
// player's race faction on a cross-faction realm, and the server gates the use itself.
if !overrides.battleground {
let ordinary_faction_ok = |r: Option<u8>| r.is_none_or(|r| r > 1);
if type_id == GO_TYPE_TRAP {
// TRAP inverts it (`0x5f2fc6`): highlightable only to whoever it is hostile to.
if !reaction.is_none_or(|r| r == 1) {
return false;
}
} else if !ordinary_faction_ok(reaction) {
// unresolved reaction passes, so a data gap never blanks the world.
let ordinary_faction_ok = |r: Option<u8>| r.is_none_or(|r| r > 1);
if type_id == GO_TYPE_TRAP {
// TRAP inverts it (`0x5f2fc6`): highlightable only to whoever it is hostile to.
if !reaction.is_none_or(|r| r == 1) {
return false;
}
} else if !ordinary_faction_ok(reaction) {
return false;
}
if flags & GO_FLAG_IN_USE_OR_NO_INTERACT != 0 {
return false;
@@ -608,23 +612,26 @@ pub(super) fn classify_cursor(
// arm is not applied. The lock arm needs `GO_FLAG_LOCKED`, so an herb the player cannot
// gather stays lit and refuses on the click, while a padlocked door grays.
let facts = super::lock::go_facts(Some((store, crate::go_anim::go_state(anim, store))));
let lock_unmet = go_inputs
.locks
.as_deref()
.and_then(|l| l.0.slots(lock_id).filter(|_| lock_id != 0))
.is_some_and(|slots| {
super::lock::resolve_lock(
slots,
&player_actions.spells,
go_inputs.spells.as_deref(),
go_inputs.skill_lines.as_ref().map(|s| &s.catalog),
Some(self_store),
&go_inputs.objects,
facts,
&mut None,
)
.blocks_usable(facts.flag_locked)
});
// A battleground objective's lock is capture bookkeeping, not a chest lock: the plain-use
// path skips it, so the gray arm must too.
let lock_unmet = !go_inputs.battleground.active()
&& go_inputs
.locks
.as_deref()
.and_then(|l| l.0.slots(lock_id).filter(|_| lock_id != 0))
.is_some_and(|slots| {
super::lock::resolve_lock(
slots,
&player_actions.spells,
go_inputs.spells.as_deref(),
go_inputs.skill_lines.as_ref().map(|s| &s.catalog),
Some(self_store),
&go_inputs.objects,
facts,
&mut None,
)
.blocks_usable(facts.flag_locked)
});
let unable = kind != CursorKind::PickLock
&& (lock_unmet || dist_sq > go_interact_range_sq(store.0.gameobject_type_id()));
Some((kind, unable))