Предмет перестал быть строкой каталога. 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>
59 lines
3.8 KiB
C++
59 lines
3.8 KiB
C++
#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);
|