squad-proto/src/sim/loot.h
z.kirill d3aa044cbe Редкости и аффиксы: находка стала решением (M25)
Предмет перестал быть строкой каталога. ItemInstance несёт редкость и до
шести аффиксов, и «карабин» с первого этажа больше не равен «карабину» с
сорокового. Четыре редкости, десять тегов, тир и бюджет от глубины.

Аффиксы не заводят новых каналов влияния: каждый тег ложится в то, что уже
есть — урон оружия, смесь стихий, пробитие, четыре числа бойца, ресурсы
вылазки. У предмета по-прежнему нет ручки к правилу «не бить своих», и
появиться ей неоткуда.

ItemInstance неявно приводится к ItemId, поэтому полторы сотни мест,
которым нужен ВИД вещи, остались как были. Цена решения — присваивание в
ItemId молча теряет рулоны; таких мест три (подбор, схрон, запись
профиля), и все они работают с экземпляром целиком.

Замеры поймали три расхождения дизайн-документа с самим собой:

  * формула редкости противоречила таблице рядом с ней: 2.7% находок с
    аффиксами на первом этаже против 22%. Выбрана формула, наклон поднят
    до схождения с таблицей на дне (12% обычных на сотом);
  * бюджет пускал восемь высших аффиксов вместо обещанных шести;
  * предельная вещь собиралась к семидесятому этажу вместо сотого.

Числа пересчитаны, документ исправлен: 6·cost(9) = 294 <= B(100) = 295 <
7·cost(9) = 343. Предельная вещь существует ровно на дне и ровно одна.

Проверка в --descend меряет не бросок, а ПРОВОДКУ до бойца — там, где
дважды умирали вес и стихии:

  аффиксы: урон 12.0 -> 18.0, здоровье 100 -> 140, броня 0.10 -> 0.25,
           пробитие 0.20, KIN 1.00 -> 0.60, CRY 0.40, сумма долей 1.000

Последнее число не украшение: конверсия обязана переводить урон, а не
добавлять седьмую долю сверх единицы.

Попутно, на глубине 30 и только там:

  * «зачистка поля» в проверках не держалась — у твари есть откат
    воскрешения, и с нулевым таймером она оживала тем же шагом. Лечение
    вне схватки и расход света на стоянке молча мерили бой. На восьмом
    этаже тварей мало, и проверка проходила по удаче;
  * порог «стоянка бесплатна» стоял в единицах заряда, а тот же шаг стоит
    дороже с глубиной. Переведён в тайлы — и сразу показал, что отряд в
    строю проходит 0.8 тайла за две секунды «стоянки». Свет теперь
    списывается только в ADVANCING: топтание и драка бесплатны, тратит
    РАЗВЕДКА. Без этого таймер возвращался через чёрный ход.

Формат профиля 4: токен предмета вида rifle@relic+dmg7,0:0.63. Миграции
не требует — у старых токенов нет хвоста после '@'.

--accept PASS без изменений: в песочнице находок нет вовсе.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 02:35:00 +03:00

59 lines
3.8 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#pragma once
// -----------------------------------------------------------------------------
// Предмет, лежащий на полу, и его подбор.
//
// Зачем отдельная сущность, а не «выдать предмет при убийстве»: в игре про
// спуск находка — это МЕСТО, до которого надо дойти. Лут, падающий в инвентарь
// сам, не заставляет никуда идти и превращает «лучше лут глубже» в строку в
// логе. Лежащий предмет виден в темноте плохо, лежит он там, куда отряд ещё не
// заходил, и за ним надо разомкнуть строй — это и есть цена находки.
//
// Подбор ПАССИВНЫЙ: боец, прошедший по предмету, поднимает его. Отдельной
// кнопки нет намеренно — игрок и так не нажимает «огонь» (спек 5), и заводить
// ради лута первое ручное действие в игре значило бы менять её жанр.
//
// FloorItem — компонент ECS в своём пуле, как Target и Bullet. Правила боя про
// него не знают вообще: ни решатель, ни LaneClear, ни клинок его не видят.
// -----------------------------------------------------------------------------
#include <vector>
#include "engine/math.h"
#include "ecs/ecs.h"
#include "sim/events.h"
#include "sim/items.h"
struct Agent;
struct FloorItem
{
ecs::Entity self = ecs::NO_ENTITY;
// Лежит на полу ЭКЗЕМПЛЯР со своими аффиксами: он выкатан при сборке
// этажа и не должен добрасываться заново при подборе (sim/affix.h).
ItemInstance id;
Vec2 pos{};
// Предмет не удаляется в момент подбора: пул перетасовывается swap-remove,
// а по нему в этот момент идёт цикл. Помеченные снимаются одним проходом
// после него.
bool taken = false;
// Сколько секунд предмет НЕ подбирается. Нужно ровно одному случаю: вещь,
// которую боец только что выбросил, лежит у него под ногами, и пассивный
// подбор поднял бы её тем же шагом. Выбросить стало бы невозможно.
float noPickup = 0.0f;
};
// pickupDelay > 0 — предмет полежит, прежде чем его снова можно подобрать.
void SpawnFloorItem(ecs::World& world, const ItemInstance& id, Vec2 pos,
float pickupDelay = 0.0f);
// Подбор за шаг. Возвращает, сколько предметов поднято.
//
// Мёртвый боец ничего не подбирает, полная сумка — тоже: предмет остаётся
// лежать, и это правильно. «Некуда положить» обязано быть видно как лежащий
// под ногами предмет, а не как молча пропавшая находка.
// Подбирают В ОБЩУЮ СУМКУ: кто прошёл по предмету, тот его и поднял, но
// достанется он всему отряду. carriers — на скольких живых делится ноша.
int UpdateLootPickup(ecs::World& world, Agent* agents, int agentCount, SquadBag& bag,
int carriers, float carryBonus, CombatEventLog* events, float dt);