Skip to content

Энтити

Основа системы Spice это энтити, которая является главным инициатором всех возможных действий.

Атрибуты энтити

Список полей класса
uuid: str
Технический "внутренний" айди энтити. Является так же primary_key в базе данных, поэтому может быть исключительно уникальным.
name: str
Латинское имя энтити, обязательное поле.
display_name: str
"Внешнее" отображаемое имя энтити. Обязательно, но может быть не уникальным и ваще любым.
tags_type: TagType
Технический атрибут, дающий понять, какие тэги нам нужны в проверках. Для энтити всегда будет равен ENTITY_TAG.
entity_type: EntityType
Тип энтити из энама EntityType, о нем в константах.
tags: list[Tag]
Тэги энтити. Подробнее тут.
statuses: list[Status]
Статусы, наложенные на энтити. Подробнее тут.
abilities: AbilitiesTable
Подробнее об абилках и этом загадочном классе AbilitiesTable можно узнать в описании абилок.
parts: dict[str, Part]
Части тела энтити. Существуют в виде словаря (dict), где ключ это название части тела для конкретной энтити, а значение это сама контейнер части тела. Подробнее тут.
soul: int
Объем души. По умолчанию равен 100. Подробнее лучше смотреть там же, где трейты, это две взаимосвязанные механики.
ties: dict[str, str]
Содержит в себе словарь со связами энтити. Подробнее о связях ниже.
appearance_picture: str
Строка с url, где, по задумке, должно лежать изображение. Нужно для тех случаев, если фронтэнд должен отображать картинки.
stats: StatsTable
Подробнее про статы можно найти дальше по тексту.
skills: Skills
Подробнее про скиллы можно найти дальше по тексту.
attributes: Attributes
Подробнее про атрибуты можно найти дальше по тексту.
traits: list[Traits]
Список трейтов. Они похожи на статусы, но, как обычно, есть нюанс. Подробнее ниже.
constellation: Constellation
Одно собственное созвездие персонажа. Определяется полностью рандомно при инициализации энтити через get_random_constellation(); по сути является трейтом и ничем не отличается, кроме класса (который нужен для группирования, не более того). Поэтому отдельно ничего по ним расписывать не буду, с технической точки зрения это просто трейт, хоть и в особом месте.
diseases: list[Disease]
Список болезней энтити. Похожи на трейты, но тоже есть свои особенности логики. Подробнее ниже.
upgrades: list[Upgrades]
Массив из апгрейдов; когда находятся в энтити, то очень похожи на трейты и болезни. Подробнее ниже.
general_buffs: GeneralBuffs
Один инстанс класса GeneralBuffs. Нужен для обозначения "базовых", т.е. изначальных значений.
base_psychosis: Psychosis
Базовый психоз персонажа, может использоваться для увеличения и уменьшения врожденного психоза. Подробнее там же, где апгрейды.
blessings: BlessingContainer
Благословения энтити. Подробнее ниже.
needs: NeedsContainer
Потребности энтити. Подробности ниже.
adaptation_embedded: AdaptationTable
Врожденная адаптация энтити. Подробности ниже.
adaptation_resist: AdaptationResistTable
Резисты к адаптации энтити.
adaptation_current: AdaptationTable
Текущая адаптация. По сути кэш для хранения, если вам что-то захочется с ней сделать.
cooldowns: dict[str, int]
Содержит в себе словарь с кулдаунами, где ключ это uid абилки, а значение это количество ходов кулдауна. Да, кулдауны хранятся в самой энтити, поэтому источник абилки не важен для ее кулдауна, как по мне, это справедливо (поскольку смысл кулдауна в исключении спама).
main_item: HoldableContainer
Отражает предмет в основной руке. Учтите, что тут "рука" не является частью тела. Подробнее: итемы.
offhand_item: HoldableContainer
Содержит в себе инстанс класса HoldableContainer, отражает предмет во вторичной руке. Не имеет никаких отличий от основной руки. Нужно понимать, что "руки" в данном случае это некая условность, и подразумевается, что персонаж может держать два предмета одновременно, это не связано с его конечностями. Подробнее: итемы. Может содержать "псевдоитем", если в основной руке двуручное оружие.
energy_shield: EnergyShieldContainer
Содержит в себе энергощит. Энергощиты не "снимаются" и исчезают после использования, поэтому создаются, в основном, расходниками. Подробнее: итемы.
trinkets: list[TrinketContainer]
Тринкеты - это всякие ожерелья и прочие легко снимаемые и надеваемые аксессуары, дающие баффы. Подробнее о том, как работают тринкеты в итемах.
clothes: ClothesContainer
Обозначает одежду, надетую на персонажа. Как вы уже догадались, и это нужно искать в итемах.
health_loss_fatigue: float
Не должно превышать 1 или быть меньше 0; отражает %, сколько процентов здоровья всех (не каждой!) частей тела может потерять энтити, прежде чем считаться побежденной. Дефолтное значение - 0.8, означающее, что энтити считается живой, пока у нее больше 20% от максимального совокупного ХП.
is_alive: bool
Нужно в основном для технических целей. Во время переоценки собственного хп энтити определяет, жива она или нет. Если цель уже выбыла из боя, подразумевается, что должна быть абилка (что-то вроде реанимации), чтобы цель переоценила свое ХП и могла "возродиться". Сразу оговорю, что это означает смерть с точки зрения боевки, то есть в игре это может означать просто бессознательное состояние или что-то еще.

Методы энтити

В этом разделе будут указаны только те методы, которые имеет смысл использовать в абилках, скриптах и эффектах, а не все существующие.

Внимание!

Методы обычно не обмазаны "защитой от идиота" и могут все сломать, если вы попытаетесь засунуть туда, где ожидается объект определенного класса, объект другого класса. Это возможно благодаря прелестям динамического программирования. Такие проверки есть на уровне api, но их нет на уровне кода. Везде, где это применимо, есть type hinting, так что ваша среда разработки, скорее всего, подскажет вам правильный выбор.

Если не указано иное, методы возвращают великолепный инстанс невероятного класса ReturnWrapper. Его шикарность купила ему личную страничку, на ней можно почитать подробности.

Методы энтити
get_all_abilities(self, def_abilities: bool = False, enum: bool = False, containers: bool = False) -> list:
Сложный метод, который возвращает массив следующего вида: [("абилка", "положение абилки"), ("абилка", "положение абилки")]; положения абилки могут быть энаном AbilityLocationEnum, если enum=True, так же, если выбрано containers, возвращаются сами контейнеры абилок, а не сами абилки. Это предназначено для внутреннего технического использования, но у этого могут быть и применения.
get_current_encounter() -> Crisis | None
Дешевый способ узнать все текущие энкаунтеры.
get_total_tags() -> list[Tag]
Метод для получения всех тэгов, не только врожденных, но и даваемых со статусов, трейтов, болезней и апгрейдов. В основном юзается для релятивных баффов.
get_overall_psychosis() -> Psychosis
Метод для получения суммарного психоза, считается врожденный и от каждого апгрейда энтити и апгрейдов частей тела.
get_overall_tiredness() -> float
Метод для получения числа от 0 до 1, означающего % от общего запаса стамины. Усталость 0.7 означает, что персонаж уже потратил 70% фокуса и стамины.
get_max_hp() -> float
Получить максимальное ХП энтити, т.е. совокупное максимальное ХП всех частей тела.
get_curr_hp() -> float
Получить текущее ХП энтити, т.к. совокупное текущее ХП всех частей тела.
def get_beauty(self) -> float
Получить итоговую красоту, с учетом баффов и одежды.

add_upgrade(upgrade: EntityUpgrade)
Добавляет апгрейд энтити.
remove_upgrade(upgrade: EntityUpgrade)
Убирает апгрейд энтити.
add_bodypart_upgrade(part_upgrade: PartUpgrade, part_key: str)
Добавляет апгрейд на часть тела по ее ключу.
remove_bodypart_upgrade(part_upgrade: PartUpgrade, part_key: str)
Убирает апгрейд с части тела по ее ключу.

add_ability(ability: Ability | DefAbility, time_left: int = 0, use_left: int = 0)
Добавляет абилку или защитную абилку на энтити. Если передается time_left или use_left, то эти данные добавляются в AbilityContainer, в котором хранится абилка. Подробнее о работе контейнера абилок в самих абилках

add_disease(disease: Disease, protection: bool = True, from_other_entity: bool = False)
Позволяет добавить болезнь на энтити. Параметр protection отвечает за то, работают ли от этой болезни бафф, защищающий от заражения, а from_other_entity влияет на то, какой бафф будет использован, на защиту от заразных или рандомных болезней.

summon_entity(template_summoned_entity_id: str)
Позволяет энтити призвать энтити в этот бой. Не сработает, если вызыватель не в бою.

add_tie(self, entity: Entity, tie_type: str)
Ожидается энтити и tie_type из словаря ties (подробнее о связях ниже). Выдает связь.
remove_tie(self, entity_name: str)
Убрать связь (ties) между энтити и entity.

take_soul(amount: int)
Забирает определенный объем души и автоматически стирает трейты, если души не хватает для их поддержания.
add_trait(trait: Trait)
Добавляет трейт энтити и валидирует по объему душ (не дает наложить трейт, если души не хватает)
remove_trait(trait: Trait)
Убирает трейт у энтити.

wear_clothes(clothes: Clothes, curr_durability: int = None)
Надевает одежду на энтити, в curr_durability можно передать текущую прочность, если ее нет, то будет задана максимальная.
unwear_clothes()
Cнимает одежду, если она есть. В случае успеха возвращает ReturnWrapper, в extra_data которого будет словарь {"item": clothes_container} с контейнером снятой одежды.

add_trinket(trinket: Trinket)
Позволяет надеть тринкет, переданный в аргументе.
remove_trinket(trinket: Trinket)
Cнимает тринкет, переданный в аргументе. В случае успеха возвращает ReturnWrapper, в extra_data которого будет словарь {"item": trinket_container} с контейнером снятого тринкета.

wear_energy_shield(energy_shield: EnergyShield, curr_hp: int = None, time_left: int = None)
Надевает энергощит. В curr_hp можно передать текущее кол-во хп щита, если не указано, то будет задано максимальное значение хп щита, в time_left можно указать оставшееся кол-во ходов для жизни щита, оно перезапишет встроенное. Помимо этого, вешает статус no_energy_shield на 20 ходов, чтобы предотвратить спам щитами.
unwear_energy_shield()
Cнять энергощит, если он есть. Щит исчезает без следа по правилам Spice, поэтому он не возвращается.
has_energy_shield()
Возвращает True, если у энтити есть energy_shield и его ХП больше 0. Возвращает False в противном случае. Полезно для случаев, когда что-то должно меняться, если есть энергощит.

wear_wearable(bodypart: str, wearable: Wearable, upgrades: list[WearableUpgrade], curr_durability: int = None, time_left: int = None)
Аргумент bodypart ожидается в виде строчного названия ключа. curr_durability позволяет сразу задать текущую прочность, если она не указана, то задается максимальная прочность предмета, а так же можно задать оставшееся время (для призванных предметов).
unwear_wearable(bodypart)
Cнять броню с какой-то части тела, как и в wear_wearable(), bodypart ожидается в виде строки названия ключа словаря parts. В случае успеха возвращает ReturnWrapper, в extra_data которого будет словарь {"item": wearable_container} с контейнером снятой одежды.

take_holdable(self, hand: str | int, item: Holdable, upgrades: list[HoldableUpgrade], curr_durability: int = None, curr_ammo: int = None, time_left: int = None)
Взять вещь item в руку hand. hand может быть только main или offhand или 0 и 1 соответственно. Два остальных аргумента позволяют задать текущую прочность и боезапас, если они не заданы, то прочность задается максимальная, а патроны будут на нуле. Так же в массиве upgrades передается список апгрейдов.
untake_item(hand: str | int)
Убрать вещи из руки hand. Hand может быть только main или offhand или 0 и 1 соотв-но. В случае успеха возвращает ReturnWrapper, в extra_data которого будет словарь {"item": wearable_container} с контейнером снятой одежды.

add_bodypart(part: Part, key_name: str = None, curr_hp: float = None, time_to_live: int = None)
Добавляет бодипарт, key_name задает название ключа бодипарта в словаре parts, если он не указан, то в качестве ключа будет указан uid самой части тела. Аргумент curr_hp позволяет задать новое ХП конечности, если оно не указано, то берется максимальное. Аргумент time_to_live позволит задать время жизни части тела, даже если у изначальной части тела его нет. Так можно, например, даже обычную руку добавить на определенное время, полезно для абилок.
remove_bodypart_container(part_name: str)
Удаляет бодипарт по его ключу в словаре parts.
get_bodypart_container(part_name: str) -> ReturnWrapper | PartContainer
Дает возможность получить сам контейнер бодипарта по его ключу в словаре parts, в принципе эквивалентно entity.parts["part_name"], но дополнительно обрабатывает KeyError (возвращая SpiceException, хе-хе...)
get_bodypart_name_by_tag(searched_tag: Tag | str, only_first: bool = False) -> None | list[str] | str
Можно получить бодипарт по тэгу, аргумент searched_tag может быть uid искомого тэга или самим тэгом. Если only_one равен False, то возвращает массив, если only_one True, то возвращает ключ одной части тела. Может вернуть None, если ничего не найдено.

get_adaptation_resist(name: str) -> float
Получить адаптационный резист.
get_base_resist(name: str) -> float
Получить базовый резист, т.е. резист от общих аспектов, типа трейтов, статусов, болезней и апгрейдов, а так же тринкетов и одежды.
get_bodypart_resist(self, bodypart: str | PartContainer, resist_name: str) -> float
Позволяет узнать итоговый резист, с учетом брони и статусов конечности, а так же всех резистов от самой энтити, то есть включает в себя get_base_resist(). Подробнее про резисты тут. Если вы хотите узнать только резист части тела без учета резиста энтити, воспользуйтесь методом самого бодипарта bodypart.get_full_resist(), подробнее в описании бодипартов
get_holdable_resist_and_take_durability(resist_name: str, durability_amount: int) -> float
Достаточно специфичная функция, которая проверяет, есть ли требуемый durability_amount в дурабитили брони, и если да, то забирает его, возвращая резист. Если резиста нет или если дурабилити недостаточно, то возвращает 0.
get_attribute_value(name: str) -> float
По стрингу с названием атрибута узнать его значение с учетом всех баффов от статусов, болезней, трейтов, тринкетов, брони, апгрейдов и всего-всего-всего. Так же учтите, что эта функция никогда не вернет значение меньше нуля, если со всеми дебаффами какой-то атрибут стал отрицательным, то она вернет 0.
get_all_attributes_values() -> Attributes
Получить инстанс класса Attributes, составленный с помощью функции выше.
get_skill_value(name: str) -> float
Аналогично с методом get_attribute_value, но для скиллов.
get_all_skills_values() -> Skills
Ну вы поняли.
get_buff_value(name: str) -> float | int
Аналогично с пунктами выше, но для генерализованных баффов. Может быть как флоатом, так и интом, в отличие от баффа.
get_summed_relative_buffs() -> RelativeBuffs
Получить инстанс класса RelativeBuffs, с которым сложены все релятивные баффы на энтити.

turn_all_cooldowns(self, amount: int = None) -> None
Снижает все кулдауны на указанное значение. Если значение не указано, то по умолчанию это 1.
reset_all_cooldowns() -> None
Сбрасывает (т.е. полностью уничтожает) все кулдауны.
start_cooldown(ability: Ability | DefAbility)
Начинает кулдаун для абилки ability. Может накладываться даже для тех абилок, которых нет у энтити.

add_stun_with_counter(duration: int) -> None
Основной и единственно верный способ наложения стана. Накладывает стан и защиту от него на срок, равный удвоенной длительности стана.
add_mute_with_counter(duration: int) -> None
Основной и единственно верный способ наложения мута. Накладывает мут и защиту от него на срок, равный удвоенной длительности мута.
add_jam_with_counter(duration: int) -> None
Основной и единственно верный способ наложения стана. Накладывает джем и защиту от него на срок, равный удвоенной длительности джема.
reload_holdable(hand: Hand, ammo_item: str, amount: int) -> ReturnWrapper
Перезаряжает оружие в указанной руке. Почему это делается в энтити, а не в самом оружии? Потому что у оружия есть цена в ОД, требуемая для перезарядки. Если ОД у энтити не хватает, метод вернет ошибку.

restore_od(self, to_max: bool = False, value: int = None) -> None: Восстановить ОД, беря за основу параметр регена ОД. Если указан to_max=True, то восстанавливает до максимума, если задан value, то задает определенное значение.

restore_whole_health(self, amount: float) -> None
Восстановить определенный объем ХП, распределив его между частями тела.


end_turn()
Я бы не рекомедовал заканчивать ход раньше, чем это захочет сама энтити, но это возможно. В теории, это должно работать, но возможно вызовет какие-то чудовищные баги.

Клонирование энтитей

Наверное, не очень удобно создавать каждого энтити с нуля, особенно если нужно создать армию скелетов. Можно заранее насоздавать шаблонов и копировать их через clone_entity!

Безусловно, это подразумевается в связке с апи, но тут будет вкратце объяснено, как это работает:

clone_entity(example_entity_id: str, new_id: str)
Оба аргумента это строки. Первая строка это id той энтити, которую мы клонируем, а вторая - это новое id энтити.

Энтити, которые вы хотите в будущем клонировать, лучше всего создавать в /content/entity.py, по правилам работы с контентом.

Статы (StatsTable)

Статы - это динамичные атрибуты персонажа, имеющие уникальные максимальные и текущие значения для разных персонажей.

Класс Stats

Существует отдельный внутренний класс Stats, который отражает сами статы. Его не нужно импортировать или как-то взаимодействовать напрямую. Там находится список стат, в данный момент их три:

  • STAMINA - стамина, по умолчанию 50.
  • FOCUS - фокус, аналог "маны", по умолчанию 50.
  • OD - запас очков действия. Почему не AP? Не знаю. По дефолту 14.

Класс StatsTable содержит в себе следующие атрибуты (поля класса):

restore_od_value: int
Количество восстанавливаемых каждый ход ОД. По умолчанию 7. Напрямую это лучше не менять, можно влиять через генерализованные баффы.
curr_stats: Stats
Текущие статы не могут быть ниже ноля или выше аналогичного стата в max_stats.
max_stats: Stats
Максимальные статы не могут быть ниже ноля.

Имеет следующие методы:

mod_stat(stat_name: str, mod: str)
Позволяет модифицировать текущие (curr) статы. Ожидаются два аргумента - название статы в виде строки и модификатор.
mod_max(stat_name: str, mod: str)
Позволяет модифицировать максимальные (max) статы. Ожидаются два аргумента - название статы в виде строки и модификатор.
get_stat(self, stat_name: str)
Ожидается название статы, возвращает ее текущее значение, либо None, если статы не найдено.
  • get_max_stat(self, stat_name: str)
    Ожидается название статы, возвращает ее максимальное значение, либо None, если статы не найдено.
Примеры
# e - это некая энтити (Entity)
e.stats.mod_stat("STAMINA", "+10")
# увеличивает текущую стамину на 10 
e.stats.mod_max_stat("FOCUS", "-5")
# уменьшает максимальный фокус на 5
e.stats.get_stat("OD")
# узнать текущие ОД

Баффы (Buffs)

Существует такой интересный класс под названием buffs, который, в упрощенном виде, выглядит следующим образом:

class Buffs:
    skills: Skills
    attributes: Attributes
    general: GeneralBuffs
    relative: RelativeBuffs
    adaptation_resist: AdaptationResistTable

Похожие поля (все, кроме relative) есть и в самой энтити. И, как можно догадаться, эти штуки баффают саму энтити, но, ввиду того, что они существуют только для баффа энтити, то сами эти поля и классы будут рассматриваться в этом разделе, а не в итемах и статусах (хотя только там сам Buffs и присутствует).

Все баффы работают по такому принципу: когда вы запрашиваете бафф через специальный метод (get_attribute_value(), например), то фактически опрашиваются все статусы, итемы, одежда, броня и т.д, где эти баффы лежат, после чего значения из этих классов сплюсосываются со значением самой энтити. В случае релятивных баффов, у самой энтити всегда по умолчанию нет ничего, но они немного по особому собираются и чуть сложнее, чем просто число.

Несмотря на громоздкость такого метода, все это происходит достаточно быстро и незаметно для пошаговой боевки.

Куда подевались абилки и резисты?

Может возникнуть весьма логичный вопрос - почти все классы, дающие баффы, так же дают резисты и абилки, однако они лежат не в баффах, а в своих полях. Почему так?

  • Новые абилки не являются "баффом". Абилки не "складываются" в единое число, поэтому я их не рассматриваю, как бафф. Следовательно, им тут не место.
  • Резисты относятся не только к энтити, а еще и к частям тела, которым не нужны баффы (у них нет скиллов, атрибутов и проч.), и хотя в данный момент баффы от статусов частей тела поддерживаются при подсчете баффов энтити, подобное разделение логики осталось для того, чтобы проще было отделять. Баффы всегда относятся к энтити, а резист относится к объекту, на который наложен статус.

Атрибуты (Attributes) и навыки (Skills)

Формально являются двумя разными классами, однако все методы в них полностью одинаковые. Поэтому описание будет общим.

Каждый атрибут и каждый навык представляют из себя float число и являются полем класса. Они округляются вниз для бросков, но не округляются в get_attribute_value или get_skill_value.

Помимо этого, в этом классе есть еще points, который, в отличие от скиллов/атрибутов в lowercase и обладает рядом особенностей. Как можно догадаться, это количество "свободных" очков, однако он нужен исключительно для поинтбая2 и не связан с текущим кол-вом поинтов.

Есть особая константа в файле constants.py, лежащем в корне Spice:

constants.py
...
MAX_ATTRIBUTE_AND_SKILL = 20
...
Она задает максимум значения навыков и атрибутов. Максимумы считаются едиными для всех энтити.

Существует три основных метода, через которые происходит взаимодействие:

Кумулятивность значений!

Хорошо запомните, что на атрибут еще и влияют баффы статусов и вы не увидите их через entity.attributes.get(), т.к. статусы накладываются уже на этапе энтити, а не тут. Поэтому, если вам нужно значение атрибутта или скилла со всеми баффами, то пользуйтесь методом get_attribute_value или get_skill_value самой энтити. В подавляющем большинстве случаев используются именно эти методы, а не .get().

mod(attribute: str, mod: str)
В случае, если речь идет о навыках, второй аргумент будет называться skill. Первый аргумент это название атрибута в строке, второй это модификатор. Метод возвращает True, если все прошло удачно или код ошибки, если что-то пошло не так.
get(skill: str)
В случае, если речь идет о атрибутах, первый аргумент будет называться attribute. Метод возвращает значение скилла или атрибута или None, если такого атрибута не найдено.
modify_points(mod: str)
Специальный метод для изменения поинтов. Так же нужно писать модификатор.
Пример взаимодействия
# e - это некая энтити (Entity)

e.attributes.mod("ENDURANCE", "+3")
# увеличивает атрибут ENDURANCE на 3 
e.skills.mod("VITAISM", 19)
# задает навык витаизм на 19
e.attributes.get("DETERMINATION")
# возвращает значение атрибута DETERMINATION
Список атрибутов и навыков
  • STRENGTH
  • AGILITY
  • ENDURANCE
  • PERCEPTION
  • INTELLIGENCE
  • DETERMINATION
  • HELITICS
  • REBORIA
  • SHAN_LIGIA
  • ELECTRODYNAMICS
  • VITAISM
  • PSINERGICS
  • SEFIROMANTICS
  • THEURGY
  • CATALYSTICS
  • COMBISTICS
  • DIMEPHYSICS
  • PHOTOKINETICS
  • BIOGENETICS
  • CYBERSYNTHETIC

Генерализованные баффы (GeneralBuffs)

Генерализованные баффы по своему функционалу напоминают атрибуты и навыки, но играют принципиально другую роль.

Всего в данный момент есть следующие баффы:

Разница между mod и extra

Все, что является extra_, всегда int, а mod_ это float. То, что extra, приплюсовывается, а на то, что mod, умножается, поэтому это и число с плавающей запятой.

Все mod по умолчанию равны одному, и если вы хотите изобразить бонус на, скажем, 20%, то делайте 1.2, а если хотите наоборот, сделать дебафф, то сделайте 0.8, это фактически будет равно -20%. Не делайте мод отрицательным!

Extra же по умолчанию равен нулю, и тут все проще. Если хотите сделать это дебаффом, просто сделайте отрицательное число.

Модификаторы стоимости стат и самих стат
  • extra_od_price: int
    Доп. плата за действия в ОД.
  • extra_stamina_price: int
    Доп. плата за действия в стамине.
  • mod_stamina_price: float
    Модификатор цены действий в стамине.
  • extra_focus_price: int
    Доп. плата за действия в фокусе.
  • mod_focus_price: float
    Модификатор цены действий в стамине.
  • extra_od_regen: int
    Дополнительный реген ОД.
Связанные с движением
  • extra_movement_cost: int
    Доп. ОД за движение.
  • mod_movement_cost: float
    Модификатор цены движения в ОД.
Связанные с уроном

Pro-tip!

В случае, если вам нужно управлять входящим уроном, предпочтительнее изменять резисты (в том числе в негативную сторону), чем влиять на урон через эти баффы. Не нужно бояться их использовать, они работают нормально и так, как ожидается, просто зачастую лучше пользоваться резистами, потому что они вписаны в большее кол-во механик.

Так же совокупный модификатор никогда не будет меньше 0.2, то есть сделать урон отрицательным или, меньше 20% не получится.

  • extra_outcome_damage: int
    Дополнительный исходящий урон.
  • mod_outcome_damage: float
    Модификатор исходящего урона.
  • extra_income_damage: int
    Дополнительный входящий урон.
  • mod_income_damage: float
    Модификатор входящего урона.
Связанные с точностью
  • extra_attack_roll: int
    Бонус к роллу при атаке.
  • mod_attack_roll: float
    Модификатор к роллу при атаке.
  • extra_defence_roll: int
    Бонус к роллу при защите.
  • mod_defence_roll: float
    Бонус к роллу при защите.
  • mod_melee_attack_roll: float
    Модификатор к роллу при атаке в ближнем бою.
  • extra_melee_attack_roll: int
    Бонус к роллу при атаке в дальнем бою.
  • mod_ranged_attack_roll: float
    Модификатор к роллу при атаке в дальнем бою.
  • extra_ranged_attack_roll: int
    Бонус к роллу при атаке в дальнем бою.
  • mod_melee_defence_roll: float
    Модификатор к роллу при защите в ближнем бою.
  • extra_melee_defence_roll: int
    Бонус к роллу при атаке в дальнем бою.
  • mod_ranged_defence_roll: float
    Модификатор к роллу при защите в дальнем бою.
  • extra_ranged_defence_roll: int
    Бонус к роллу при защите в дальнем бою.

Дальний бой - это если между атакующим и защищающимся больше трех блоков. Если три или меньше, то это ближний.

Связанные с болезнями
  • mod_random_disease_protection: float
    Защита от рандомной болезни. Это число * вычитается* из шанса заразиться той или иной болезни. То есть, если шанс заразиться болезни 60%, а у вас защита 40%, то ваш шанс заразиться 20%.
  • mod_contagious_disease_protection: float
    Аналогично, только для заразных болезней (передающихся от других игроков).
  • mod_disease_cure: float
    Модификатор исцеления болезни. Каждая болезнь со временем исцеляется, этот бафф модифицирует объем исцеления. Подробнее можно посмотреть в описании самих болезней.
Модификаторы ренджа
  • extra_min_range: float
    У каждой абилки есть минимальный рендж, то есть расстояние, ближе которого эту абилку нельзя использовать. Это число прибавляется к этому ренджу. Например, если у вашей абилки мин. радиус 3, и вы имеете +2 к мин. ренджу, то вы сможете использовать ее минимум в радиусе 5 блоков.
  • extra_max_range: float
    У каждой абилки есть максимальный рендж, то есть расстояние, дальше которого нельзя использовать. Работает аналогично с пунктом выше, только вот "плюс" к этому числу скорее положительную роль играет, чем отрицательную.
Связанные с травмами
  • mod_trauma_chance: float
    Это число, на которое умножается итоговый шанс травмы.
  • extra_trauma_chance: float
    Дополнительный шанс травмы, он прибавляется к роллу. То есть, если тут сделать 1.0, то травма будет гарантировано.
  • extra_trauma_timed_healing: int
    Добавляет фиксированное доп. исцеление вашим травмам, даже тем, которые сами по себе не проходят.
  • mod_trauma_healing: float
    Модификатор для объема исцеления от времени.
Связанные с обычным лечением
  • extra_heal: int
    Добавляет фиксированное доп. исцеление при использовании эффекта basic_heal.
  • mod_heal: float
    Модификатор для объема исцеления в эффекте basic_heal.
Прочее
  • extra_cqc_damage: int
    Не используется, потенциально нужен для ближнего боя, но это все только планы.
  • extra_beauty: int
    Дополнительная фиксированная красота энтити.
  • mod_beauty: float
    Модификатор красоты энтити.

Все эти значения складываются при получении через get_buff_value() с одной особенностью1. То есть, например, если у вас есть два статуса с mod_movement_cost и в них указаны 0.5 и 1.55 соотв-но, то совокупный бонус будет 1 + (0.5 - 1) + (1.55 - 1), т.е будет равен 1.05. Этот метод, если запрашивается mod, никогда вернет отрицательное число, но может вернуть его в случае extra.

Напомним, что в extra вы задаете просто число, а mod вы задаете флоат, где 1 это 100%. То есть, например, если вы хотите, чтобы бафф исходящий урон на 50%, то вы должны указать mod_outcome_damage = 0.5. По аналогии, если вы хотите, чтобы исходящий урон был удвоен, то укажите mod_outcome_damage = 2.0.

Pro-tip!

Практически во всех случаях сначала происходит сложение с extra и только потом умножение на mod; если mod несколько, то они складываются между собой перед умножением.

По сути, каждый бафф это просто число, которое в том или ином виде применяется в разных хелперах и ютилях. Если вы действуете в обход существующих методов, то они не будут применяться, но, возможно, вам это и нужно.

Релятивные баффы (RelativeBuffs)

Релятивные баффы это невероятно крутая штука, которые дают вам возможность создавать бонусы к роллам или к урону исходя из собственных тэгов или тэгов цели! Звучит круто, да? Оно и есть круто.

У энтити существует только один метод взаимодействия с ними, и он называется get_summed_relative_buffs(). Поскольку его работа не так важна, как правила его формирования, подробности вы можете узнать на их собственной страничке.

Адаптация

Адаптация - это что-то вроде внешней среды и приспособленности к ней. Это тоже достаточно сложная механика, поэтому, как и релятивные баффы, она получила собственную страничку.

Статусы и похожее на них

Статус - это отдельная достаточно комплексная штука, рассматриваемая на собственной страничке. Тем не менее, у энтити есть ряд штук, который очень похож на статусы, но ими не является. Что же это:

  • Трейты
  • Болезни
  • Апгрейды

Отчасти сюда можно отнести и тринкеты, но это все-таки вещи, поэтому подробности лучше искать на их страничке

Что общего со статусами? Собственно, наличие контейнера Buffs, а так же, зачастую, ResistTable и AbilitiesTable. То есть они могут давать баффы, резисты и дополнительные абилки. Учтите, что баффы и резисты могут быть отрицательными, и все встанет на свои места.

Трейты и душа

Трейты - это долгосрочные или околобесконечные статусы, которые добавляются в массив trait. Их особенностью является привязка к душе.

Душа - это тоже поле класса энтити, которое представляет из себя число. По дефолту оно равно 100. Каждый трейт занимает сколько-то единицы души, это определяется в константе SOUL_PER_TRAIT, которая по умолчанию равна 25. Если вы теряете душу через take_soul(), то он автоматически будет забирать "крайние справа" трейты из списка.

Поля класса Trait
uid: str
Айдишник трейта.
display_name: str
Отображаемое имя трейта.
buffs: Buffs
Таблица баффов, такая же, как везде. Подробнее о баффах можно почитать в статусах
abilities: AbilitiesTable
Абилки, ничем не отличается от других AbilitiesTable. При активации абилки, энтити ищет абилки в том числе в каждом трейте.
resists: ResistTable
Обычный ResistTable, дополнительные резисты.
events: dict
подробнее о том, как работают эвенты, можно почитать на их страничке.

Вы так же не сможете добавить трейт через add_trait(), если у вас не хватает души. Для поднятия души не существует своего метода, потому что с этим не связано никакой логики, просто сделайте entity.soul += 10, если вам это нужно в какой-то абилке или методе.

Болезни

Болезни тоже похожи на трейты, кроме того факта, что они, в основном, имеют негативную коннотацию.

Поля класса Disease
uid: str
Айдишник трейта.
display_name: str
Отображаемое имя трейта.
chance_contagious_appear: float
Шанс заражения. Обычно это число от 0 до 1, если 100%, то болезнь заражает со 100% вероятностью, если подходит расстояние и нет защиты.
contagious_range: float
Расстояние, на котором болезнь может заразить.
chance_random_appear: float
Шанс рандомного заражения, если эта болезнь выпала из списка того, чем "может" заразиться персонаж.
random_probability_weight: int
Вес при расчете случайного заражения. Если у какой-то болезни вес больше, чем у другой, то она чаще будет рандомно выбираться.
curing: float
Текущий объем исцеления. Как только он доходит до единицы, то персонаж считается исцеленным.
base_cure_per_turn: float
То, на сколько прогрессирует исцеление каждый ход, если достаточно иммунитета. Можно увеличить (или уменьшить?) через генерализованный бафф mod_disease_cure, но он будет работать только в том случае, если достаточно иммунитета. Так же он не прибавляется, а именно умножается на итоговое значение исцеления.
buffs: Buffs
Таблица баффов, такая же, как везде. Подробнее о баффах можно почитать в статусах
abilities: AbilitiesTable
Абилки, ничем не отличается от других AbilitiesTable. При активации абилки, энтити ищет абилки в том числе в каждом трейте.
resists: ResistTable
Обычный ResistTable, дополнительные резисты.
events: dict
подробнее о том, как работают эвенты, можно почитать на их страничке.

Заразиться не так просто

На самом деле, шанс заражения не такой большой. Во первых, выбирается абсолютно рандомная болезнь в обоих случаях, и у персонажа может быть большой иммунитет, или может несовпадать тип (болезнью для органиков не могут заболеть синтетики). В этом случае, перепроверки не происходит. И только потом должен прокнуть еще шанс заражения, который далеко не всегда равен нулю. Еще раз, если кто-то не понял - сначала выбираетс случайная ОДНА болезнь, а потом уже определяется, случилось ли заражение. Помимо этого, если персонаж уже болен, может выпасть эта же болезнь: тогда с ним ничего не случится.

Болезни делятся на две категории:

  • Случайное заражение.
  • Заразное заражение. Подразумевается, что игра-фронтенд передает в processing массив, в котором перечислены все энтити, с которыми общался/взаимодействовал персонаж за последнюю минуту и дистанцию до них.

Конкретно болезнь накладывается так: каждую минуту (ну или когда приходит пакет с инфой от Zest, подробнее в процессинге), проходят две проверки:

  1. Во первых, персонаж пытается заразиться рандомной болезнью из списка всех болезней, с которыми он контактировал. Выбирается одна случайная.
  2. Во вторых, персонаж пытается заразиться рандомной болезнью из списка всех заразных болезней в игре. Так же выбирается одна случайная, но у них есть веса, которые определяют шанс того, что выпадет именно эта болезнь. Е

От болезней можно защититься генерализованными баффами. Генерализованные баффы mod_contagious_disease_protection и mod_random_disease_protection вычитаются из шанса заражения, то есть то, на сколько % они больше единички, на столько меньше шанс заражения; если бафф равен 2, то шанс заражения равен нулю, если, конечно, сам шанс заражения не больше единички.

Апгрейды

У них своя страничка.

Прочие аспекты персонажа

Благословения

У каждого персонажа есть свои божественные благословения. Внутри класса BlessingContainer есть два атрибута:

  • ocean: OceanSide
  • underside: UndersideSide

Оба этих класса (с Side в названии) наследуют один класс AbstractSide. Различие между ними только в полях и "сторонах", что они представляют, но это уже сугубо внутриигровые понятия, не рассматриваемые тут. В теории сторон может быть сколько угодно, так вы можете, например, изобразить пантеоны божеств.

Вы не должны взаимодействовать с благословениями напрямую. Что вас может заинтересовать, так это следующие два метода:

Методы класса BlessingContainer
mod(side: GodSide, god: str, mod: str)
Позволяет модифицировать благословение того или иного божества. GodSide это энам, подробнее следует смотреть на их страничке. Правила такие же, как и у остальных модификаторов
get(side: GodSide, god: str)
Позволяет получить значение благословения того или иного божества. Эквивалент blessing.side_name.some_god_name так-то, тут как хотите.

Потребности

У каждого персонажа есть потребности. Все они хранятся в классе NeedsContainer.

Атрибуты класса NeedsContainer

Все атрибуты класса контейнера потребностей идут от 0 до 1 и как бы отражают % запас той или иной потребности. То есть чем выше - тем лучше.

SATURATION: float
Насыщение персонажа.
SANITY: float
Ментальное здоровье, или же "менталка". Отражает то, поехал персонаж кукухой или нет.
IMMUNITY: float
Иммунитет. Чем выше - тем лучше вы защищены от всяких там болезней. Разумеется, у синтетиков и у эфириков нет биологического иммунитета, но он может быть магический или качеством загруженности антивируса, тут уже в дело вступают не механические, а внутриигровые интерпретации.
INSPIRATION: float
С точки зрения формальной логики, вдохновение это не потребность, но оно сюда лучше всего ложится. Отражает вдохновение персонажа.

Bug

В данный момент все потребности ничего не значат. Это будет исправлено в будущем и будут добавлены дебаффы за пониженные потребности.

Что влияет на потребности:

  1. Насыщение падает время от времени на каждый поддерживающий запрос, как и менталка.
  2. Иммунитет падает от ударов, как и менталка.
  3. Все потребности могут быть увеличены или уменьшены от эффектов (т.е. абилок и статусов)
  4. Вдохновение в самом Spice не применяется, только в Zest.

Связи

Связи - это что-то вроде "отношений" между двумя энтитями. Они задаются в /content/ties.py в следующем виде:

from content.statuses import bleeding
from properties.status import all_statuses
ties = {
    "master": bleeding,
    "apprentice": all_statuses["head_injury"]
} 
# https://www.youtube.com/watch?v=jvxDdgwaCyI

Первое - это название связи, второе - это статус, который она подразумевает. Можно ссылаться на статус через импорт переменной или так, как выше.

Статус накладывается на энтити в тот момент, когда тот, кто находится у нее в связях, входит в кризис, в котором она находится. Это делается автоматически. Для связей есть два важных метода:

add_tie(entity: Entity, tye_type: str) : Позволяет добавить tie между энтити и entity, с указанным tye_type. Это и будет ключ из словаря выше, например в данном примере связь может быть "master" или "apprentice". remove_tie(entity_name: str) : Убирает любую связь с указанной entity, параметр name энтити нужно передавать строкой.

Через связи можно задать только один статус, но больше и не нужно. Так же с одной энтитей может быть только одна связь. Еще и нужно помнить о том, что связи по натуре односторонние, и если вы чей-то мастер, это автоматически не делает этого кого-то вашим учеником.


  1. Все инстансы класса GeneralizedBuffs создаются с дефотлными значениями, и для всех float-значений (означающих % бонуса), это будет 1. Однако, если для энтити это оправдано (если баффа нет, то умножить на 1), то в случае со статусами, при сложении будет неприятная ситуация (1+1 = 2), поэтому из каждого числа для всех float-модификаторов вычитается один. То есть берется бафф энтити и к нему складываются баффы, например, статусов, для которых делается -1. Так, например, если у энтити генерализованный бафф 1.25, и на нее наложили эффект с 0.5, то итоговое значение будет 1.25 + (0.5 - 1), т.е. 0.75 в данном случае. 

  2. Поинтбай возможен только через api, поскольку это сугубо рантаймовые геймплейные возможности.