From 9422eef0952f443c97d383a532157f0b71404d83 Mon Sep 17 00:00:00 2001 From: stribog Date: Wed, 15 Jul 2026 21:42:34 +0200 Subject: [PATCH] diag(obj): log player item loss (punctual-to-ground, steal-eq, decay) (#3563) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Пропажу вещей у игроков нельзя было отследить: логи о причинах удаления почти отключены, а трейсился только уход из инвентаря (obj_from_char). Реальный случай (#3563): точный стиль ранит руку и роняет оружие НА ЗЕМЛЮ (для игрока не на арене) -- вещь оказывается в другой комнате, владелец её не находит, а в сислоге пусто. Добавлено: - punctual_style.cpp: лог, когда точный стиль выбивает вещь игрока на землю (не в инвентарь). Мобов не логируем. CheckObjDecay перенесён внутрь ветки "на землю" (для попавшей в инвентарь вещи он не нужен). - do_steal.cpp: лог кражи НАДЕТОЙ вещи (имм или у спящей жертвы) -- она уходит через UnequipChar, без obj_from_char. Кража из инвентаря уже логируется obj_from_char в RemoveObjFromChar. - LogPlayerObjLoss (obj_handler): единый хелпер, ищет игрока-владельца (инвентарь/экипировка/его контейнер) и пишет причину; звать до отвязки. - DamageObj: лог уничтожения по прочности (durability<=0). - obj_point_update: лог уничтожения по таймеру/decay. UnequipChar намеренно НЕ логируем: в подавляющем большинстве это обычное "снять", вещь уходит в инвентарь и не теряется -- был бы шум и ложный сигнал. Примечание: выбивание оружия точным стилем НЕ проверяет kNodisarm (в отличие от скилла обезоруживания) -- отдельный вопрос к дизайну, здесь не трогаю. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/engine/core/obj_handler.cpp | 30 ++++++++++++++++++++++++++ src/engine/core/obj_handler.h | 2 ++ src/engine/ui/cmd/do_steal.cpp | 5 +++++ src/gameplay/core/game_limits.cpp | 2 ++ src/gameplay/mechanics/equipment.cpp | 2 ++ src/gameplay/skills/punctual_style.cpp | 15 ++++++++++--- 6 files changed, 53 insertions(+), 3 deletions(-) diff --git a/src/engine/core/obj_handler.cpp b/src/engine/core/obj_handler.cpp index ace276f1d..caa0c85d9 100644 --- a/src/engine/core/obj_handler.cpp +++ b/src/engine/core/obj_handler.cpp @@ -290,6 +290,36 @@ RoomVnum get_room_where_obj(ObjData *obj, bool deep) { return kNowhere; } +// issue #3563: единый лог о пропаже вещи у игрока. Ищет игрока-владельца +// (инвентарь/экипировка/его контейнер) и пишет в syslog причину. Звать НАДО +// до отвязки вещи от владельца, иначе get_carried_by/get_worn_by уже пустые. +void LogPlayerObjLoss(ObjData *obj, const char *reason) { + if (!obj) { + return; + } + CharData *owner = obj->get_carried_by() ? obj->get_carried_by() + : (obj->get_worn_by() ? obj->get_worn_by() : nullptr); + if (!owner) { + // вещь в контейнере -- ищем игрока-владельца контейнера + for (ObjData *cont = obj->get_in_obj(); cont; cont = cont->get_in_obj()) { + if (cont->get_carried_by()) { + owner = cont->get_carried_by(); + break; + } + if (cont->get_worn_by()) { + owner = cont->get_worn_by(); + break; + } + } + } + if (!owner || owner->IsNpc()) { + return; + } + log("[Obj loss] у игрока %s пропала вещь '%s' vnum == %d (%s), timer == %d, прочность == %d/%d", + GET_NAME(owner), obj->get_PName(grammar::ECase::kNom).c_str(), GET_OBJ_VNUM(obj), + reason, obj->get_timer(), obj->get_current_durability(), obj->get_maximum_durability()); +} + void ExtractObjFromWorld(ObjData *obj, bool showlog) { timechange_unregister_obj(obj); char name[kMaxStringLength]; diff --git a/src/engine/core/obj_handler.h b/src/engine/core/obj_handler.h index 2e40e82e5..c2cd6788d 100644 --- a/src/engine/core/obj_handler.h +++ b/src/engine/core/obj_handler.h @@ -20,6 +20,8 @@ void RemoveObjFromObj(ObjData *obj); void object_list_new_owner(ObjData *list, CharData *ch); RoomVnum get_room_where_obj(ObjData *obj, bool deep = false); void ExtractObjFromWorld(ObjData *obj, bool showlog = false); +// issue #3563: пишет в syslog о пропаже вещи у игрока; звать до отвязки от владельца. +void LogPlayerObjLoss(ObjData *obj, const char *reason); void UpdateCharObjects(CharData *ch); void DropObjOnZoneReset(CharData *ch, ObjData *obj, bool inv, bool zone_reset); int get_object_low_rent(ObjData *obj); diff --git a/src/engine/ui/cmd/do_steal.cpp b/src/engine/ui/cmd/do_steal.cpp index 5bf385b55..3137a3750 100644 --- a/src/engine/ui/cmd/do_steal.cpp +++ b/src/engine/ui/cmd/do_steal.cpp @@ -97,6 +97,11 @@ void go_steal(CharData *ch, CharData *vict, char *obj_name) { } else { act("Вы раздели $N3 и взяли $o3.", false, ch, obj, vict, kToChar); act("$n украл$g $o3 у $N1.", false, ch, obj, vict, kToNotVict | kToArenaListen); + // issue #3563: кража НАДЕТОЙ вещи (имм или у спящей жертвы) -- она уходит + // через UnequipChar, без obj_from_char, поэтому логируем отдельно. + log("[Steal eq] %s украл надетую вещь '%s' (vnum %d) у %s", + GET_NAME(ch), obj->get_PName(grammar::ECase::kNom).c_str(), + GET_OBJ_VNUM(obj), GET_NAME(vict)); PlaceObjToInventory(UnequipChar(vict, eq_pos, CharEquipFlags()), ch); } } diff --git a/src/gameplay/core/game_limits.cpp b/src/gameplay/core/game_limits.cpp index 1ad629eb5..b7df1fcfd 100644 --- a/src/gameplay/core/game_limits.cpp +++ b/src/gameplay/core/game_limits.cpp @@ -1307,6 +1307,8 @@ void obj_point_update() { timer_otrigger(j); continue; } + // issue #3563: логируем пропажу до отвязки вещи от игрока (ниже RemoveObjFromChar). + LogPlayerObjLoss(j, "истёк таймер жизни / decay"); // *** рассыпание объекта ObjData *jj, *next_thing2; for (jj = j->get_contains(); jj; jj = next_thing2) { diff --git a/src/gameplay/mechanics/equipment.cpp b/src/gameplay/mechanics/equipment.cpp index af5c6d55e..0ae1b24f3 100644 --- a/src/gameplay/mechanics/equipment.cpp +++ b/src/gameplay/mechanics/equipment.cpp @@ -99,6 +99,8 @@ void DamageObj(ObjData *obj, int dam, int chance) { char_get_custom_label(obj, obj->get_carried_by()).c_str()); act(buf, false, obj->get_carried_by(), obj, nullptr, kToChar); } + // issue #3563: логируем пропажу до отложенного удаления, пока владелец известен. + LogPlayerObjLoss(obj, "рассыпалась по прочности"); // issue.obj-casting: deferred extraction (freed at heartbeat end) so a live ctx.ovict // (acid corrode etc.) is left flagged purged() rather than dangling. world_objects.AddToExtractedList(obj); diff --git a/src/gameplay/skills/punctual_style.cpp b/src/gameplay/skills/punctual_style.cpp index fdc03e84c..3fd61ce8f 100644 --- a/src/gameplay/skills/punctual_style.cpp +++ b/src/gameplay/skills/punctual_style.cpp @@ -645,11 +645,20 @@ void PerformPunctualHit(CharData *ch, CharData *victim, HitData &hit_data) { act(buf, true, ch, nullptr, victim, kToNotVict | kToArenaListen); break; } - if (!victim->IsNpc() && ROOM_FLAGGED(victim->in_room, ERoomFlag::kArena)) + if (!victim->IsNpc() && ROOM_FLAGGED(victim->in_room, ERoomFlag::kArena)) { PlaceObjToInventory(obj, victim); - else + } else { + // issue #3563: точный стиль выбил вещь НА ЗЕМЛЮ (не в инвентарь). Для игрока + // логируем -- иначе потом непонятно, куда делось оружие (валяется в комнате, + // владелец не находит). Мобов не логируем, чтобы не шуметь. + if (!victim->IsNpc()) { + log("[Punctual -> ground] %s выбил точным стилем '%s' (vnum %d) из рук %s -> комната [%d] %s", + GET_NAME(ch), obj->get_PName(grammar::ECase::kNom).c_str(), GET_OBJ_VNUM(obj), + GET_NAME(victim), world[victim->in_room]->vnum, world[victim->in_room]->name); + } PlaceObjToRoom(obj, victim->in_room); - CheckObjDecay(obj); + CheckObjDecay(obj); + } } if (!victim->IsNpc()) { hit_data.dam /= 5;