Compare commits
4
Commits
01aa054177
...
v1.4.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
205b4d7071 | ||
|
|
f4470b784f | ||
|
|
77b56be9ae | ||
|
|
67dd410358 |
@@ -6,3 +6,5 @@ TEPersistenceSrc/bin/
|
|||||||
*.user
|
*.user
|
||||||
BACKLOG.md
|
BACKLOG.md
|
||||||
FINAL_TEXT.md
|
FINAL_TEXT.md
|
||||||
|
FORCE_VERSION_GUARD
|
||||||
|
FORCE_VAULT_UNREAD
|
||||||
|
|||||||
@@ -160,3 +160,7 @@ buffNecroRatMarkName,buffs,Buff,,,,Spirit Mark,,Geistermal,Marca del espíritu,M
|
|||||||
buffNecroRatMarkDesc,buffs,Buff,,,,"Marked by a rat spirit: visible on its master’s compass and map, and rotting - every wound cuts deeper than it should.",,"Von einem Rattengeist gezeichnet: auf Kompass und Karte seines Herrn sichtbar - und faulend, sodass jede Wunde tiefer geht, als sie sollte.","Marcado por un espíritu de rata: visible en la brújula y el mapa de su amo, y pudriéndose - cada herida cala más hondo de lo que debería.","Marqué par un esprit de rat : visible sur la boussole et la carte de son maître, et en putréfaction - chaque blessure creuse plus profond qu’elle ne devrait.","Marchiato da uno spirito di ratto: visibile sulla bussola e sulla mappa del suo padrone, e in putrefazione - ogni ferita affonda più del dovuto.",ネズミの霊に印をつけられた。その主のコンパスと地図に映り、腐ってゆく - どんな傷も本来より深く刻まれる。,"쥐의 영혼에게 표식이 찍혔습니다. 그 주인의 나침반과 지도에 드러나며, 썩어들어 모든 상처가 본래보다 깊게 패입니다.","Naznaczony przez ducha szczura: widoczny na kompasie i mapie jego pana, i gnijący - każda rana sięga głębiej, niż powinna.","Marcado por um espírito de rato: visível na bússola e no mapa de seu mestre, e apodrecendo - cada ferida penetra mais fundo do que deveria.","Помечен духом крысы: виден на компасе и карте его хозяина и гниёт — каждая рана заходит глубже, чем должна.",Bir sıçan ruhu tarafından işaretlendi: efendisinin pusulasında ve haritasında görünür ve çürür - her yara olması gerekenden derine iner.,被鼠灵标记:显现在其主人的罗盘与地图上,并且开始腐烂——任何伤口都比应有的更深。,被鼠靈標記:顯現在其主人的羅盤與地圖上,並且開始腐爛——任何傷口都比應有的更深。
|
buffNecroRatMarkDesc,buffs,Buff,,,,"Marked by a rat spirit: visible on its master’s compass and map, and rotting - every wound cuts deeper than it should.",,"Von einem Rattengeist gezeichnet: auf Kompass und Karte seines Herrn sichtbar - und faulend, sodass jede Wunde tiefer geht, als sie sollte.","Marcado por un espíritu de rata: visible en la brújula y el mapa de su amo, y pudriéndose - cada herida cala más hondo de lo que debería.","Marqué par un esprit de rat : visible sur la boussole et la carte de son maître, et en putréfaction - chaque blessure creuse plus profond qu’elle ne devrait.","Marchiato da uno spirito di ratto: visibile sulla bussola e sulla mappa del suo padrone, e in putrefazione - ogni ferita affonda più del dovuto.",ネズミの霊に印をつけられた。その主のコンパスと地図に映り、腐ってゆく - どんな傷も本来より深く刻まれる。,"쥐의 영혼에게 표식이 찍혔습니다. 그 주인의 나침반과 지도에 드러나며, 썩어들어 모든 상처가 본래보다 깊게 패입니다.","Naznaczony przez ducha szczura: widoczny na kompasie i mapie jego pana, i gnijący - każda rana sięga głębiej, niż powinna.","Marcado por um espírito de rato: visível na bússola e no mapa de seu mestre, e apodrecendo - cada ferida penetra mais fundo do que deveria.","Помечен духом крысы: виден на компасе и карте его хозяина и гниёт — каждая рана заходит глубже, чем должна.",Bir sıçan ruhu tarafından işaretlendi: efendisinin pusulasında ve haritasında görünür ve çürür - her yara olması gerekenden derine iner.,被鼠灵标记:显现在其主人的罗盘与地图上,并且开始腐烂——任何伤口都比应有的更深。,被鼠靈標記:顯現在其主人的羅盤與地圖上,並且開始腐爛——任何傷口都比應有的更深。
|
||||||
buffNecroRatBleedName,buffs,Buff,,,,Torn Open,,Aufgerissen,Desgarrado,Déchiré,Squarciato,裂傷,찢긴 상처,Rozszarpany,Dilacerado,Рваная рана,Parçalanmış,撕裂伤,撕裂傷
|
buffNecroRatBleedName,buffs,Buff,,,,Torn Open,,Aufgerissen,Desgarrado,Déchiré,Squarciato,裂傷,찢긴 상처,Rozszarpany,Dilacerado,Рваная рана,Parçalanmış,撕裂伤,撕裂傷
|
||||||
buffNecroRatBleedDesc,buffs,Buff,,,,Teeth found a vein. The wound will not close on its own while they keep worrying at it.,,"Zähne haben eine Ader gefunden. Die Wunde schließt sich nicht von selbst, solange sie daran weiterzerren.",Unos dientes dieron con una vena. La herida no se cerrará sola mientras sigan hurgando en ella.,Des dents ont trouvé une veine. La plaie ne se refermera pas d’elle-même tant qu’elles s’y acharneront.,I denti hanno trovato una vena. La ferita non si chiuderà da sola finché continuano ad accanirsi.,歯が血管を探り当てた。噛みつづけられているかぎり、傷はひとりでに塞がらない。,이빨이 혈관을 찾아냈습니다. 계속 물어뜯기는 동안 상처는 저절로 아물지 않습니다.,"Zęby trafiły na żyłę. Rana nie zamknie się sama, dopóki będą się w nią wgryzać.",Os dentes acharam uma veia. A ferida não vai fechar sozinha enquanto continuarem a remexer nela.,"Зубы нашли жилу. Пока в ней продолжают копаться, рана сама не затянется.",Dişler bir damar buldu. Onu kurcalamayı sürdürdükleri sürece yara kendiliğinden kapanmaz.,牙齿咬中了血管。只要还在撕扯,伤口就不会自行闭合。,牙齒咬中了血管。只要還在撕扯,傷口就不會自行閉合。
|
buffNecroRatBleedDesc,buffs,Buff,,,,Teeth found a vein. The wound will not close on its own while they keep worrying at it.,,"Zähne haben eine Ader gefunden. Die Wunde schließt sich nicht von selbst, solange sie daran weiterzerren.",Unos dientes dieron con una vena. La herida no se cerrará sola mientras sigan hurgando en ella.,Des dents ont trouvé une veine. La plaie ne se refermera pas d’elle-même tant qu’elles s’y acharneront.,I denti hanno trovato una vena. La ferita non si chiuderà da sola finché continuano ad accanirsi.,歯が血管を探り当てた。噛みつづけられているかぎり、傷はひとりでに塞がらない。,이빨이 혈관을 찾아냈습니다. 계속 물어뜯기는 동안 상처는 저절로 아물지 않습니다.,"Zęby trafiły na żyłę. Rana nie zamknie się sama, dopóki będą się w nią wgryzać.",Os dentes acharam uma veia. A ferida não vai fechar sozinha enquanto continuarem a remexer nela.,"Зубы нашли жилу. Пока в ней продолжают копаться, рана сама не затянется.",Dişler bir damar buldu. Onu kurcalamayı sürdürdükleri sürece yara kendiliğinden kapanmaz.,牙齿咬中了血管。只要还在撕扯,伤口就不会自行闭合。,牙齒咬中了血管。只要還在撕扯,傷口就不會自行閉合。
|
||||||
|
necroVersionGuardTitle,UI,XUi MainMenu,x,,,Necromancer's Tome: incompatible game version,,Necromancer's Tome: inkompatible Spielversion,Necromancer's Tome: versión del juego incompatible,Necromancer's Tome : version du jeu incompatible,Necromancer's Tome: versione del gioco incompatibile,Necromancer's Tome: 互換性のないゲームバージョン,Necromancer's Tome: 호환되지 않는 게임 버전,Necromancer's Tome: niezgodna wersja gry,Necromancer's Tome: versão do jogo incompatível,Книга некроманта: несовместимая версия игры,Necromancer's Tome: uyumsuz oyun sürümü,Necromancer's Tome:游戏版本不兼容,Necromancer's Tome:遊戲版本不相容
|
||||||
|
necroVersionGuardText,UI,XUi MainMenu,x,,,"Necromancer's Tome {0} is made for 7 Days to Die {1} and is not compatible with your game version ({2}). Playing with it would damage your saves. Please update the mod to the version made for your game, or remove it. The game will now close.",,Necromancer's Tome {0} ist für 7 Days to Die {1} gemacht und nicht mit deiner Spielversion ({2}) kompatibel. Damit zu spielen würde deine Spielstände beschädigen. Bitte aktualisiere die Mod auf die Version für dein Spiel oder entferne sie. Das Spiel wird jetzt beendet.,Necromancer's Tome {0} está hecho para 7 Days to Die {1} y no es compatible con tu versión del juego ({2}). Jugar con él dañaría tus partidas guardadas. Actualiza el mod a la versión para tu juego o elimínalo. El juego se cerrará ahora.,"Necromancer's Tome {0} est conçu pour 7 Days to Die {1} et n'est pas compatible avec ta version du jeu ({2}). Jouer avec endommagerait tes sauvegardes. Mets le mod à jour vers la version prévue pour ton jeu, ou retire-le. Le jeu va maintenant se fermer.",Necromancer's Tome {0} è fatto per 7 Days to Die {1} e non è compatibile con la tua versione del gioco ({2}). Giocarci danneggerebbe i tuoi salvataggi. Aggiorna la mod alla versione per il tuo gioco oppure rimuovila. Il gioco ora verrà chiuso.,Necromancer's Tome {0} は 7 Days to Die {1} 用に作られており、お使いのゲームバージョン({2})とは互換性がありません。このままプレイするとセーブデータが損傷します。お使いのゲーム用のバージョンにMODを更新するか、MODを削除してください。ゲームを終了します。,Necromancer's Tome {0}은(는) 7 Days to Die {1}용으로 만들어졌으며 현재 게임 버전({2})과 호환되지 않습니다. 이대로 플레이하면 저장 파일이 손상됩니다. 게임에 맞는 버전으로 모드를 업데이트하거나 모드를 제거하세요. 이제 게임이 종료됩니다.,Necromancer's Tome {0} jest przeznaczony dla 7 Days to Die {1} i nie jest zgodny z twoją wersją gry ({2}). Gra z nim uszkodziłaby twoje zapisy. Zaktualizuj moda do wersji dla twojej gry albo go usuń. Gra zostanie teraz zamknięta.,Necromancer's Tome {0} foi feito para 7 Days to Die {1} e não é compatível com a sua versão do jogo ({2}). Jogar com ele danificaria seus saves. Atualize o mod para a versão feita para o seu jogo ou remova-o. O jogo será fechado agora.,"Мод «Книга некроманта» {0} сделан для 7 Days to Die {1} и несовместим с вашей версией игры ({2}). Игра с ним повредит ваши сохранения. Пожалуйста, обновите мод до версии для вашей игры или удалите его. Сейчас игра закроется.","Necromancer's Tome {0}, 7 Days to Die {1} için yapıldı ve oyun sürümünüzle ({2}) uyumlu değil. Onunla oynamak kayıtlarınıza zarar verir. Lütfen modu oyununuza uygun sürüme güncelleyin veya kaldırın. Oyun şimdi kapanacak.",Necromancer's Tome {0} 为 7 Days to Die {1} 制作,与你的游戏版本({2})不兼容。继续使用会损坏你的存档。请将模组更新到适合你游戏的版本,或将其移除。游戏现在将关闭。,Necromancer's Tome {0} 為 7 Days to Die {1} 製作,與你的遊戲版本({2})不相容。繼續使用會損壞你的存檔。請將模組更新到適合你遊戲的版本,或將其移除。遊戲現在將關閉。
|
||||||
|
necroVersionGuardQuit,UI,XUi MainMenu,x,,,Quit game,,Spiel beenden,Salir del juego,Quitter le jeu,Esci dal gioco,ゲームを終了,게임 종료,Wyjdź z gry,Sair do jogo,Выйти из игры,Oyundan çık,退出游戏,退出遊戲
|
||||||
|
braceletSpatialVaultUnreadable,items,Item,,,,This storage rift was saved by another version of the mod. It is kept untouched until you install the mod made for your game version.,,"Dieser Lagerriss wurde von einer anderen Version der Mod gespeichert. Er bleibt unangetastet, bis du die Mod für deine Spielversion installierst.",Esta grieta de almacenamiento se guardó con otra versión del mod. Se conserva intacta hasta que instales el mod para tu versión del juego.,Cette faille de stockage a été enregistrée par une autre version du mod. Elle reste intacte jusqu'à ce que tu installes le mod prévu pour ta version du jeu.,Questa fenditura di deposito è stata salvata da un'altra versione della mod. Resta intatta finché non installi la mod per la tua versione del gioco.,この収納の裂け目は別のバージョンのMODで保存されました。お使いのゲームバージョン用のMODを入れるまで、手を付けずに保持されます。,이 보관 균열은 다른 버전의 모드로 저장되었습니다. 게임 버전에 맞는 모드를 설치할 때까지 그대로 보존됩니다.,"Ta szczelina magazynowa została zapisana przez inną wersję moda. Pozostaje nietknięta, dopóki nie zainstalujesz moda dla twojej wersji gry.",Esta fenda de armazenamento foi salva por outra versão do mod. Ela fica intacta até você instalar o mod feito para a sua versão do jogo.,"Этот разлом хранилища сохранён другой версией мода. Он останется нетронутым, пока вы не поставите мод для вашей версии игры.",Bu depolama yarığı modun başka bir sürümüyle kaydedildi. Oyun sürümünüze uygun modu kurana kadar dokunulmadan saklanır.,这个储物裂隙由另一个版本的模组保存。在你安装适合你游戏版本的模组之前,它会原样保留。,這個儲物裂隙由另一個版本的模組儲存。在你安裝適合你遊戲版本的模組之前,它會原樣保留。
|
||||||
|
|||||||
|
+6
-2
@@ -30,12 +30,16 @@
|
|||||||
<buff name="buffNecroZombieKillTrackerDisplay" icon="ui_game_symbol_skull" icon_color="150,0,0" name_key="buffNecroZombieKillTrackerDisplayName" description_key="buffNecroZombieKillTrackerDisplayDesc">
|
<buff name="buffNecroZombieKillTrackerDisplay" icon="ui_game_symbol_skull" icon_color="150,0,0" name_key="buffNecroZombieKillTrackerDisplayName" description_key="buffNecroZombieKillTrackerDisplayDesc">
|
||||||
<stack_type value="ignore"/>
|
<stack_type value="ignore"/>
|
||||||
<duration value="0"/>
|
<duration value="0"/>
|
||||||
<update_rate value=".1"/>
|
<!-- Было ".1" (10 раз в секунду). 2026-09-24 стало "1" по отзыву zomb1 на Nexus
|
||||||
|
(подозревал в этом падение FPS): тик только пересчитывает урон ножа из числа
|
||||||
|
убийств, а оно меняется лишь при убийстве - после убийства урон догоняет не позже
|
||||||
|
чем через секунду. -->
|
||||||
|
<update_rate value="1"/>
|
||||||
<display_value value="necroNecromancyLevelCVar"/>
|
<display_value value="necroNecromancyLevelCVar"/>
|
||||||
|
|
||||||
<!-- Нож некроманта (BACKLOG.md item 5, user request 2026-08-28): "урон умножается на
|
<!-- Нож некроманта (BACKLOG.md item 5, user request 2026-08-28): "урон умножается на
|
||||||
скилл некроманта ... и делится на 10". Computed here (this buff already ticks
|
скилл некроманта ... и делится на 10". Computed here (this buff already ticks
|
||||||
10x/sec on every player via update_rate) rather than on the knife item itself,
|
once a second - 10x/sec before 1.4.1 - on every player via update_rate) rather than on the knife item itself,
|
||||||
since a held item has no update tick of its own - reading a live CVar from a
|
since a held item has no update tick of its own - reading a live CVar from a
|
||||||
passive_effect (confirmed working: vanilla's own $PlayerLevelBonus, computed in
|
passive_effect (confirmed working: vanilla's own $PlayerLevelBonus, computed in
|
||||||
buffs.xml and read elsewhere via passive_effect value="@$PlayerLevelBonus") needs
|
buffs.xml and read elsewhere via passive_effect value="@$PlayerLevelBonus") needs
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
using HarmonyLib;
|
||||||
|
|
||||||
|
namespace NecromancerTome
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Утечка в самой игре, на которую наступала шкала некромантии в поясе (1.3.0+,
|
||||||
|
/// fill="{% cvar('necroNecromancyProgressCVar') / 20 }" в Config/XUi_InGame/windows.xml).
|
||||||
|
///
|
||||||
|
/// Найдено 28.09 замером (NecroProfiler): FPS 60 -> 30 за 10-15 минут в любом мире, в теле;
|
||||||
|
/// группа окон toolbelt росла 2 -> 23 мс/кадр при неизменных 573 элементах; в режиме змеи пояс
|
||||||
|
/// скрыт - и тормозов нет; перезаход в мир сбрасывает.
|
||||||
|
///
|
||||||
|
/// Механика (декомпиляция 3.3): BindingNcalcFunctions.cvar при каждом вычислении выражения
|
||||||
|
/// регистрирует НОВЫЙ VariableStateCVar, а BindingInfoNcalc.RegisterVariable добавляет его в
|
||||||
|
/// список без проверки. Новая переменная начинает с 0; если шкала не пуста, её RefreshValue в
|
||||||
|
/// следующем кадре видит «изменилось» -> выражение пересчитывается -> cvar() добавляет ещё одну.
|
||||||
|
/// Плюс одна переменная за кадр, пока CVar не 0, и каждый кадр обходится весь список.
|
||||||
|
///
|
||||||
|
/// Лечение: одну и ту же CVar в одной привязке регистрируем один раз. Значение cvar() берёт
|
||||||
|
/// прямо у игрока, так что отслеживающая переменная нужна только одна - для «изменилось».
|
||||||
|
/// </summary>
|
||||||
|
[HarmonyPatch(typeof(BindingInfoNcalc), nameof(BindingInfoNcalc.RegisterVariable))]
|
||||||
|
public static class Patch_BindingInfoNcalc_RegisterVariable_CVarLeak
|
||||||
|
{
|
||||||
|
public static bool Prefix(BindingInfoNcalc __instance, BindingInfoNcalc.VariableStateAbs _var, ref bool __result)
|
||||||
|
{
|
||||||
|
if (!(_var is BindingInfoNcalc.VariableStateCVar added))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
foreach (BindingInfoNcalc.VariableStateAbs existing in __instance.variables)
|
||||||
|
{
|
||||||
|
if (existing is BindingInfoNcalc.VariableStateCVar known && known.cVarName == added.cVarName)
|
||||||
|
{
|
||||||
|
__result = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -54,9 +54,11 @@ namespace NecromancerTome
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static class CorpseLoot
|
public static class CorpseLoot
|
||||||
{
|
{
|
||||||
/// <summary>Сколько секунд лежит помеченный труп. У ванильного мешка с лутом 3600, и
|
/// <summary>Сколько секунд лежит помеченный труп. В 1.4.0 было 3600, как у ванильного мешка с
|
||||||
/// причин быть скромнее нет: труп и есть мешок.</summary>
|
/// лутом; 2026-09-24 уменьшено до 5 минут по указанию пользователя: «если игрок не будет
|
||||||
public const float CorpseSecondsAfterDeath = 3600f;
|
/// убирать, у него FPS начнёт падать» - каждый лежащий труп это отрисовываемая модель со
|
||||||
|
/// своими копиями материалов, а необобранных за час набирается много.</summary>
|
||||||
|
public const float CorpseSecondsAfterDeath = 300f;
|
||||||
|
|
||||||
/// <summary>Во что красится труп. Тёмно-багровый: издалека видно, что этот не как все, и
|
/// <summary>Во что красится труп. Тёмно-багровый: издалека видно, что этот не как все, и
|
||||||
/// при этом не выглядит подсветкой из другой игры.</summary>
|
/// при этом не выглядит подсветкой из другой игры.</summary>
|
||||||
@@ -81,9 +83,42 @@ namespace NecromancerTome
|
|||||||
/// кадре, когда сущность умирает. Добивается односекундным тиком PetFollowPatch.</summary>
|
/// кадре, когда сущность умирает. Добивается односекундным тиком PetFollowPatch.</summary>
|
||||||
public static readonly HashSet<int> PendingTint = new HashSet<int>();
|
public static readonly HashSet<int> PendingTint = new HashSet<int>();
|
||||||
|
|
||||||
|
/// <summary>УТЕЧКА ПАМЯТИ, НАЙДЕНА 2026-09-24 (жалоба: «память течёт, лечится только
|
||||||
|
/// перезапуском»). Версия 1.4.0 перекрашивала труп и НИЧЕГО за собой не убирала:
|
||||||
|
///
|
||||||
|
/// - `renderer.materials` создаёт копии материалов, и Unity НЕ удаляет их вместе с
|
||||||
|
/// объектом - они живут до перезапуска;
|
||||||
|
/// - перекрашенная текстура создавалась несжатой (RGBA32, 16-64 МБ на штуку) и оставалась
|
||||||
|
/// читаемой, то есть лежала дважды - в видеопамяти и копией в оперативной;
|
||||||
|
/// - кеш текстур только рос.
|
||||||
|
///
|
||||||
|
/// Лог игры (`60sec delta: ... current: ..., desired: ...` - это Texture.currentTextureMemory)
|
||||||
|
/// показал рост видеопамяти текстур с 1,2 до 7,4 ГБ за два часа при нужных игре ~3,5 ГБ, и
|
||||||
|
/// FPS упал с 60 до 11. За сессию было перекрашено 53 трупа по 4-10 материалов.
|
||||||
|
///
|
||||||
|
/// Теперь каждый перекрашенный труп записан здесь вместе с копиями материалов и текстурами,
|
||||||
|
/// а тик раз в секунду (ReleaseGoneCorpses) убирает за теми, кого уже нет. Текстура
|
||||||
|
/// удаляется, когда её не использует ни один труп.</summary>
|
||||||
|
public class TintedCorpse
|
||||||
|
{
|
||||||
|
public readonly List<Renderer> Renderers = new List<Renderer>();
|
||||||
|
public readonly List<Material> Materials = new List<Material>();
|
||||||
|
public readonly List<Texture2D> Textures = new List<Texture2D>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly Dictionary<int, TintedCorpse> Tinted = new Dictionary<int, TintedCorpse>();
|
||||||
|
|
||||||
|
/// <summary>Сколько трупов сейчас носит каждую перекрашенную текстуру.</summary>
|
||||||
|
public static readonly Dictionary<Texture2D, int> TextureUsers = new Dictionary<Texture2D, int>();
|
||||||
|
|
||||||
|
/// <summary>Предел стороны перекрашенной текстуры. Исходные 2048-4096; труп рассматривают
|
||||||
|
/// вблизи, но 1024 со сжатием это ~1 МБ вместо 16-64 МБ, и разница на глаз незаметна.</summary>
|
||||||
|
public const int MaxTintSize = 1024;
|
||||||
|
|
||||||
/// <summary>Повторная попытка перекраски. Зовётся из общего тика раз в секунду.</summary>
|
/// <summary>Повторная попытка перекраски. Зовётся из общего тика раз в секунду.</summary>
|
||||||
public static void RetryPendingTints(World _world)
|
public static void RetryPendingTints(World _world)
|
||||||
{
|
{
|
||||||
|
ReleaseGoneCorpses(_world);
|
||||||
if (PendingTint.Count == 0 || _world == null)
|
if (PendingTint.Count == 0 || _world == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -103,6 +138,92 @@ namespace NecromancerTome
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Уборка за исчезнувшими трупами. Трупом считается исчезнувшим, когда его нет в
|
||||||
|
/// мире И уничтожены все его рендереры: удалять материал, который ещё рисуется, нельзя.</summary>
|
||||||
|
public static void ReleaseGoneCorpses(World _world)
|
||||||
|
{
|
||||||
|
if (Tinted.Count == 0 || _world == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
List<int> gone = null;
|
||||||
|
foreach (KeyValuePair<int, TintedCorpse> pair in Tinted)
|
||||||
|
{
|
||||||
|
if (_world.GetEntity(pair.Key) != null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
bool rendered = false;
|
||||||
|
foreach (Renderer renderer in pair.Value.Renderers)
|
||||||
|
{
|
||||||
|
if (renderer != null)
|
||||||
|
{
|
||||||
|
rendered = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (rendered)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (gone == null)
|
||||||
|
{
|
||||||
|
gone = new List<int>();
|
||||||
|
}
|
||||||
|
gone.Add(pair.Key);
|
||||||
|
}
|
||||||
|
if (gone == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
foreach (int id in gone)
|
||||||
|
{
|
||||||
|
TintedCorpse corpse = Tinted[id];
|
||||||
|
Tinted.Remove(id);
|
||||||
|
SearchableCorpses.Remove(id);
|
||||||
|
foreach (Material material in corpse.Materials)
|
||||||
|
{
|
||||||
|
if (material != null)
|
||||||
|
{
|
||||||
|
UnityEngine.Object.Destroy(material);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach (Texture2D texture in corpse.Textures)
|
||||||
|
{
|
||||||
|
ReleaseTexture(texture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Debug.Log("[NecromancerTome] CorpseLoot: убрано за исчезнувшими трупами: " + gone.Count +
|
||||||
|
", осталось трупов " + Tinted.Count + ", текстур " + TextureUsers.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ReleaseTexture(Texture2D _texture)
|
||||||
|
{
|
||||||
|
if (_texture == null || !TextureUsers.TryGetValue(_texture, out int users))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (users > 1)
|
||||||
|
{
|
||||||
|
TextureUsers[_texture] = users - 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
TextureUsers.Remove(_texture);
|
||||||
|
List<Texture> keys = new List<Texture>();
|
||||||
|
foreach (KeyValuePair<Texture, Texture2D> pair in TintedTextures)
|
||||||
|
{
|
||||||
|
if (pair.Value == _texture)
|
||||||
|
{
|
||||||
|
keys.Add(pair.Key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach (Texture key in keys)
|
||||||
|
{
|
||||||
|
TintedTextures.Remove(key);
|
||||||
|
}
|
||||||
|
UnityEngine.Object.Destroy(_texture);
|
||||||
|
}
|
||||||
|
|
||||||
public static bool IsSearchable(Entity _entity)
|
public static bool IsSearchable(Entity _entity)
|
||||||
{
|
{
|
||||||
return _entity != null && SearchableCorpses.Contains(_entity.entityId);
|
return _entity != null && SearchableCorpses.Contains(_entity.entityId);
|
||||||
@@ -165,6 +286,11 @@ namespace NecromancerTome
|
|||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (!Tinted.TryGetValue(_corpse.entityId, out TintedCorpse record))
|
||||||
|
{
|
||||||
|
record = new TintedCorpse();
|
||||||
|
Tinted[_corpse.entityId] = record;
|
||||||
|
}
|
||||||
int tinted = 0;
|
int tinted = 0;
|
||||||
foreach (Renderer renderer in renderers)
|
foreach (Renderer renderer in renderers)
|
||||||
{
|
{
|
||||||
@@ -177,7 +303,13 @@ namespace NecromancerTome
|
|||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
// Копии материалов создаются этой строкой, и все они теперь наши - записываем
|
||||||
|
// каждую, даже ту, что перекрасить не удалось: удалять её тоже нам.
|
||||||
Material[] instances = renderer.materials;
|
Material[] instances = renderer.materials;
|
||||||
|
if (!record.Renderers.Contains(renderer))
|
||||||
|
{
|
||||||
|
record.Renderers.Add(renderer);
|
||||||
|
}
|
||||||
for (int i = 0; i < instances.Length && i < shared.Length; i++)
|
for (int i = 0; i < instances.Length && i < shared.Length; i++)
|
||||||
{
|
{
|
||||||
Material material = instances[i];
|
Material material = instances[i];
|
||||||
@@ -185,6 +317,10 @@ namespace NecromancerTome
|
|||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (!record.Materials.Contains(material))
|
||||||
|
{
|
||||||
|
record.Materials.Add(material);
|
||||||
|
}
|
||||||
string albedo = GhostTraderPatch.FindAlbedoProperty(material);
|
string albedo = GhostTraderPatch.FindAlbedoProperty(material);
|
||||||
if (albedo == null)
|
if (albedo == null)
|
||||||
{
|
{
|
||||||
@@ -196,6 +332,9 @@ namespace NecromancerTome
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
material.SetTexture(albedo, repainted);
|
material.SetTexture(albedo, repainted);
|
||||||
|
record.Textures.Add(repainted);
|
||||||
|
TextureUsers.TryGetValue(repainted, out int users);
|
||||||
|
TextureUsers[repainted] = users + 1;
|
||||||
tinted++;
|
tinted++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -222,13 +361,19 @@ namespace NecromancerTome
|
|||||||
RenderTexture previous = RenderTexture.active;
|
RenderTexture previous = RenderTexture.active;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
rt = RenderTexture.GetTemporary(_source.width, _source.height, 0,
|
// Уменьшение идёт бесплатно, самим Blit в RenderTexture меньшего размера. Стороны
|
||||||
|
// кратны четырём - иначе Compress ниже откажется сжимать.
|
||||||
|
float scale = Mathf.Min(1f, (float)MaxTintSize / Mathf.Max(_source.width, _source.height));
|
||||||
|
int width = Mathf.Max(4, (int)(_source.width * scale) / 4 * 4);
|
||||||
|
int height = Mathf.Max(4, (int)(_source.height * scale) / 4 * 4);
|
||||||
|
|
||||||
|
rt = RenderTexture.GetTemporary(width, height, 0,
|
||||||
RenderTextureFormat.ARGB32, RenderTextureReadWrite.sRGB);
|
RenderTextureFormat.ARGB32, RenderTextureReadWrite.sRGB);
|
||||||
Graphics.Blit(_source, rt);
|
Graphics.Blit(_source, rt);
|
||||||
RenderTexture.active = rt;
|
RenderTexture.active = rt;
|
||||||
|
|
||||||
painted = new Texture2D(_source.width, _source.height, TextureFormat.RGBA32, false);
|
painted = new Texture2D(width, height, TextureFormat.RGBA32, false);
|
||||||
painted.ReadPixels(new Rect(0f, 0f, _source.width, _source.height), 0, 0);
|
painted.ReadPixels(new Rect(0f, 0f, width, height), 0, 0);
|
||||||
|
|
||||||
Color32[] pixels = painted.GetPixels32();
|
Color32[] pixels = painted.GetPixels32();
|
||||||
for (int i = 0; i < pixels.Length; i++)
|
for (int i = 0; i < pixels.Length; i++)
|
||||||
@@ -240,12 +385,19 @@ namespace NecromancerTome
|
|||||||
pixels[i] = p;
|
pixels[i] = p;
|
||||||
}
|
}
|
||||||
painted.SetPixels32(pixels);
|
painted.SetPixels32(pixels);
|
||||||
painted.Apply(false, false);
|
// Сжатие в DXT (вчетверо-восьмеро меньше RGBA32) и makeNoLongerReadable=true: без
|
||||||
|
// него копия пикселей оставалась в оперативной памяти рядом с видеопамятью.
|
||||||
|
painted.Compress(false);
|
||||||
|
painted.Apply(false, true);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Debug.LogError("[NecromancerTome] CorpseLoot: не удалось перекрасить текстуру '" +
|
Debug.LogError("[NecromancerTome] CorpseLoot: не удалось перекрасить текстуру '" +
|
||||||
_source.name + "': " + e.Message);
|
_source.name + "': " + e.Message);
|
||||||
|
if (painted != null)
|
||||||
|
{
|
||||||
|
UnityEngine.Object.Destroy(painted);
|
||||||
|
}
|
||||||
painted = null;
|
painted = null;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
|
|||||||
@@ -269,7 +269,15 @@ namespace NecromancerTome
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ours.width != src.width || ours.height != src.height)
|
// Качество текстур ниже Full уменьшает атлас игры вдвое/вчетверо (Half: 256 вместо 512).
|
||||||
|
// Раньше тут был пропуск - и пирамида оставалась без текстуры (28.09, игра на Half).
|
||||||
|
// Наши уменьшенные копии уже лежат в мипах: при Half берём с мипа 1, при Quarter - с 2.
|
||||||
|
int mipOffset = 0;
|
||||||
|
while (mipOffset < ours.mipmapCount && (ours.width >> mipOffset) > src.width)
|
||||||
|
{
|
||||||
|
mipOffset++;
|
||||||
|
}
|
||||||
|
if ((ours.width >> mipOffset) != src.width || (ours.height >> mipOffset) != src.height)
|
||||||
{
|
{
|
||||||
Debug.LogWarning("[NecromancerTome] CustomBlockPaint: " + label + " size mismatch, " +
|
Debug.LogWarning("[NecromancerTome] CustomBlockPaint: " + label + " size mismatch, " +
|
||||||
"atlas is " + src.width + "x" + src.height + " but ours is " +
|
"atlas is " + src.width + "x" + src.height + " but ours is " +
|
||||||
@@ -283,11 +291,11 @@ namespace NecromancerTome
|
|||||||
" - skipped");
|
" - skipped");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if (ours.mipmapCount != src.mipmapCount)
|
if (ours.mipmapCount - mipOffset < src.mipmapCount)
|
||||||
{
|
{
|
||||||
Debug.LogWarning("[NecromancerTome] CustomBlockPaint: " + label + " mip mismatch, " +
|
Debug.LogWarning("[NecromancerTome] CustomBlockPaint: " + label + " mip mismatch, " +
|
||||||
"atlas has " + src.mipmapCount + " but ours has " + ours.mipmapCount +
|
"atlas has " + src.mipmapCount + " but ours has " + ours.mipmapCount +
|
||||||
" - skipped");
|
" (from mip " + mipOffset + ") - skipped");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -303,11 +311,14 @@ namespace NecromancerTome
|
|||||||
// GPU-side copy: the game's arrays are non-readable, so nothing can be pulled back
|
// GPU-side copy: the game's arrays are non-readable, so nothing can be pulled back
|
||||||
// to the CPU. CopyTexture moves whole slices with their mip chains.
|
// to the CPU. CopyTexture moves whole slices with their mip chains.
|
||||||
for (int i = 0; i < slice; i++) Graphics.CopyTexture(src, i, grown, i);
|
for (int i = 0; i < slice; i++) Graphics.CopyTexture(src, i, grown, i);
|
||||||
Graphics.CopyTexture(ours, 0, grown, slice);
|
for (int mip = 0; mip < src.mipmapCount; mip++)
|
||||||
|
{
|
||||||
|
Graphics.CopyTexture(ours, 0, mip + mipOffset, grown, slice, mip);
|
||||||
|
}
|
||||||
|
|
||||||
target = grown;
|
target = grown;
|
||||||
Debug.Log("[NecromancerTome] CustomBlockPaint: " + label + " grown from " + slice +
|
Debug.Log("[NecromancerTome] CustomBlockPaint: " + label + " grown from " + slice +
|
||||||
" to " + (slice + 1) + " slices");
|
" to " + (slice + 1) + " slices" + (mipOffset > 0 ? ", ours taken from mip " + mipOffset : ""));
|
||||||
return slice;
|
return slice;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,151 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using HarmonyLib;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace NecromancerTome
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// ПРОВЕРКА ВЕРСИИ ИГРЫ, 1.4.2 (2026-09-24). Повод: 1.4.1 на игре 3.3.0 experimental молча
|
||||||
|
/// потеряла содержимое Пространственного хранилища - разбор в BACKLOG.md, «Игра 3.3.0
|
||||||
|
/// experimental». Решение пользователя: при несовместимой версии мод показывает в главном меню
|
||||||
|
/// окно «мод несовместим, обновите» с ЕДИНСТВЕННОЙ кнопкой «Выйти из игры» - без «играть всё
|
||||||
|
/// равно».
|
||||||
|
///
|
||||||
|
/// ПОЧЕМУ ЧИТАЕТСЯ cVersionInformation, А НЕ cVersionMinor. cVersionMinor - const, компилятор
|
||||||
|
/// вписывает её значение (20) прямо в нашу DLL, и на 3.3 она всё равно «видела» бы 20.
|
||||||
|
/// cVersionInformation - static readonly, её значение берётся из игры при запуске.
|
||||||
|
///
|
||||||
|
/// ПОЧЕМУ Minor / 10. Игра хранит версию двузначным числом: 3.2.0 = 20, 3.2.1 = 21, 3.3.0 = 30
|
||||||
|
/// (VersionInformation печатает его как {Minor/10}.{Minor%10}). Проверка по десяткам пропускает
|
||||||
|
/// любые хотфиксы 3.2.x и не пропускает 3.3 и всё, что будет после.
|
||||||
|
///
|
||||||
|
/// ВСЁ, ЧЕМ ПОЛЬЗУЕТСЯ ЭТОТ ФАЙЛ, СВЕРЕНО С ДЕКОМПИЛЯЦИЕЙ 3.3.0 (build 25485021): Constants,
|
||||||
|
/// VersionInformation, XUiC_MainMenu.OnOpen, XUiC_MessageBoxWindowGroup (класс совпадает целиком),
|
||||||
|
/// Mod.VersionString. Иначе сборка под 3.2 упала бы на 3.3 раньше, чем показала окно.
|
||||||
|
///
|
||||||
|
/// ОТЛАДКА НА 3.2: если в папке мода лежит файл FORCE_VERSION_GUARD, мод ведёт себя так, будто
|
||||||
|
/// версия несовместима. В релизный архив он не попадает - состав архива задан явным списком.
|
||||||
|
/// </summary>
|
||||||
|
public static class GameVersionGuard
|
||||||
|
{
|
||||||
|
/// <summary>Под какую версию собран мод: Major и десяток Minor (3.2.x = 3 и 2).</summary>
|
||||||
|
public const int SupportedMajor = 3;
|
||||||
|
public const int SupportedMinorTens = 2;
|
||||||
|
public const string SupportedText = "3.2";
|
||||||
|
|
||||||
|
private static bool? supported;
|
||||||
|
|
||||||
|
public static bool IsSupported
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (supported == null)
|
||||||
|
{
|
||||||
|
supported = Check();
|
||||||
|
}
|
||||||
|
return supported.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GameVersionText
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
VersionInformation v = Constants.cVersionInformation;
|
||||||
|
return v != null ? v.LongStringNoBuild : "?";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool Check()
|
||||||
|
{
|
||||||
|
if (ForcedByFile())
|
||||||
|
{
|
||||||
|
Debug.LogWarning("[NecromancerTome] GameVersionGuard: FORCE_VERSION_GUARD file present - acting as if the game version were unsupported");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
VersionInformation v = Constants.cVersionInformation;
|
||||||
|
bool ok = v != null && v.Major == SupportedMajor && v.Minor / 10 == SupportedMinorTens;
|
||||||
|
Debug.Log("[NecromancerTome] GameVersionGuard: game " + GameVersionText + " (Major " + (v != null ? v.Major : -1) +
|
||||||
|
", Minor " + (v != null ? v.Minor : -1) + "), mod built for " + SupportedText + " -> " +
|
||||||
|
(ok ? "supported" : "NOT SUPPORTED"));
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool ForcedByFile()
|
||||||
|
{
|
||||||
|
return DebugFileExists("FORCE_VERSION_GUARD");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Отладка сохранения хранилища байт в байт на 3.2: при файле FORCE_VAULT_UNREAD
|
||||||
|
/// в папке мода хранилище из сейва не разбирается, а проходит всю цепочку сохранения
|
||||||
|
/// нетронутым - ровно как на несовместимой версии, но без окна с выходом, так что в мир
|
||||||
|
/// можно зайти и сохраниться.</summary>
|
||||||
|
public static bool ForceVaultUnread
|
||||||
|
{
|
||||||
|
get { return DebugFileExists("FORCE_VAULT_UNREAD"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool DebugFileExists(string _name)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string folder = ModEntry.Instance != null ? ModEntry.Instance.Path : null;
|
||||||
|
return folder != null && File.Exists(Path.Combine(folder, _name));
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ShowIncompatibleWindow(XUi _xui)
|
||||||
|
{
|
||||||
|
string modVersion = ModEntry.Instance != null ? ModEntry.Instance.VersionString : "?";
|
||||||
|
string text = string.Format(Localization.Get("necroVersionGuardText"), modVersion, SupportedText, GameVersionText);
|
||||||
|
XUiC_MessageBoxWindowGroup.ShowCustom(_xui,
|
||||||
|
Localization.Get("necroVersionGuardTitle"),
|
||||||
|
text,
|
||||||
|
"",
|
||||||
|
delegate(XUiC_MessageBoxWindowGroup _box)
|
||||||
|
{
|
||||||
|
_box.buttons[0].DefaultConfirm("necroVersionGuardQuit", delegate
|
||||||
|
{
|
||||||
|
Debug.Log("[NecromancerTome] GameVersionGuard: player pressed Quit");
|
||||||
|
Application.Quit();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
_openMainMenuOnClose: false,
|
||||||
|
_modal: true,
|
||||||
|
_buttonOnOutsideClick: -1,
|
||||||
|
// Проверено в игре 2026-09-25: Esc модальное окно не закрывает - оно остаётся на
|
||||||
|
// экране, и уйти можно только кнопкой. Это и нужно. _buttonOnExternalClose: 0 - на
|
||||||
|
// случай, если игра всё же закроет окно извне: тогда это тоже выход.
|
||||||
|
_buttonOnExternalClose: 0);
|
||||||
|
Debug.LogWarning("[NecromancerTome] GameVersionGuard: incompatible game version, quit window shown");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Главное меню: здесь показывается окно, и здесь же сбрасываются непрочитанные байты
|
||||||
|
/// хранилища - возврат в меню значит, что прежний мир закрыт, и его данные не должны перейти к
|
||||||
|
/// персонажу следующего.</summary>
|
||||||
|
[HarmonyPatch(typeof(XUiC_MainMenu), "OnOpen")]
|
||||||
|
public static class Patch_XUiC_MainMenu_OnOpen_VersionGuard
|
||||||
|
{
|
||||||
|
public static void Postfix(XUiC_MainMenu __instance)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
SpatialVaultPersistence.UnreadableByEntity.Clear();
|
||||||
|
if (!GameVersionGuard.IsSupported)
|
||||||
|
{
|
||||||
|
GameVersionGuard.ShowIncompatibleWindow(__instance.xui);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Debug.LogError("[NecromancerTome] GameVersionGuard: main menu postfix failed: " + e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,6 +19,36 @@ namespace NecromancerTome
|
|||||||
{
|
{
|
||||||
Instance = _modInstance;
|
Instance = _modInstance;
|
||||||
var harmony = new Harmony("necromancertome.harmony");
|
var harmony = new Harmony("necromancertome.harmony");
|
||||||
|
|
||||||
|
// НЕСОВМЕСТИМАЯ ВЕРСИЯ ИГРЫ (1.4.2, см. GameVersionGuard.cs): ставятся только окно в
|
||||||
|
// главном меню и сохранение хранилища байт в байт - каждое отдельно, чтобы отказ одного
|
||||||
|
// не помешал другим. Остальной код мода, написанный под другую версию, не запускается
|
||||||
|
// вовсе: игра всё равно закроется из главного меню.
|
||||||
|
if (!GameVersionGuard.IsSupported)
|
||||||
|
{
|
||||||
|
System.Type[] safetyPatches =
|
||||||
|
{
|
||||||
|
typeof(Patch_XUiC_MainMenu_OnOpen_VersionGuard),
|
||||||
|
typeof(Patch_PlayerDataFile_Read_SpatialVault),
|
||||||
|
typeof(Patch_PlayerDataFile_ToPlayer_SpatialVault),
|
||||||
|
typeof(Patch_PlayerDataFile_FromPlayer_SpatialVault),
|
||||||
|
typeof(Patch_PlayerDataFile_Write_SpatialVault),
|
||||||
|
};
|
||||||
|
foreach (System.Type patch in safetyPatches)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
harmony.CreateClassProcessor(patch).Patch();
|
||||||
|
Debug.Log("[NecromancerTome] ModEntry: unsupported game version - safety patch " + patch.Name + " applied");
|
||||||
|
}
|
||||||
|
catch (System.Exception e)
|
||||||
|
{
|
||||||
|
Debug.LogError("[NecromancerTome] ModEntry: safety patch " + patch.Name + " failed: " + e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
harmony.PatchAll(Assembly.GetExecutingAssembly());
|
harmony.PatchAll(Assembly.GetExecutingAssembly());
|
||||||
PetFollowPatch.Init();
|
PetFollowPatch.Init();
|
||||||
// SpatialVaultPersistence needs NO Init(): it is four Harmony postfixes that PatchAll
|
// SpatialVaultPersistence needs NO Init(): it is four Harmony postfixes that PatchAll
|
||||||
|
|||||||
@@ -116,6 +116,13 @@ namespace NecromancerTome
|
|||||||
|
|
||||||
public static void OpenVault(EntityPlayerLocal player)
|
public static void OpenVault(EntityPlayerLocal player)
|
||||||
{
|
{
|
||||||
|
// Хранилище из сейва не прочитано - открывать нечего, а пустое новое затёрло бы его при
|
||||||
|
// сохранении. См. SpatialVaultPersistence.UnreadableByEntity.
|
||||||
|
if (SpatialVaultPersistence.IsLocked(player))
|
||||||
|
{
|
||||||
|
GameManager.ShowTooltip(player, "braceletSpatialVaultUnreadable");
|
||||||
|
return;
|
||||||
|
}
|
||||||
ProgressionValue progressionValue = player.Progression?.GetProgressionValue(NecromancySkillName);
|
ProgressionValue progressionValue = player.Progression?.GetProgressionValue(NecromancySkillName);
|
||||||
int level = progressionValue != null ? progressionValue.Level : 0;
|
int level = progressionValue != null ? progressionValue.Level : 0;
|
||||||
int slotCount = Mathf.RoundToInt(level / 10f);
|
int slotCount = Mathf.RoundToInt(level / 10f);
|
||||||
|
|||||||
@@ -89,6 +89,37 @@ namespace NecromancerTome
|
|||||||
public class VaultSlot
|
public class VaultSlot
|
||||||
{
|
{
|
||||||
public Bag Bag;
|
public Bag Bag;
|
||||||
|
|
||||||
|
/// <summary>Байты хранилища, которые эта сборка не прочитала: версия игры не та, под
|
||||||
|
/// которую собран мод, или разбор упал. Пишутся обратно в файл байт в байт.</summary>
|
||||||
|
public byte[] RawPayload;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>ХРАНИЛИЩЕ НЕ ДОЛЖНО ТЕРЯТЬСЯ НА ЧУЖОЙ ВЕРСИИ ИГРЫ, 1.4.2 (2026-09-24). В 1.4.1 на
|
||||||
|
/// игре 3.3 разбор хранилища падал, и при сохранении мод писал вместо него ПУСТОЙ маркер -
|
||||||
|
/// вещи пропадали навсегда. Теперь непрочитанные байты проходят всю цепочку сохранения
|
||||||
|
/// нетронутыми: Read кладёт их в слот файла, ToPlayer переносит к игроку (сюда), FromPlayer
|
||||||
|
/// отдаёт новому файлу (игра на каждом сохранении создаёт НОВЫЙ PlayerDataFile), Write
|
||||||
|
/// пишет их как есть. Сборка под нужную версию потом прочтёт их обычным путём.
|
||||||
|
///
|
||||||
|
/// Ключ - entityId. Очищается при каждом открытии главного меню (GameVersionGuard.cs), чтобы
|
||||||
|
/// байты одного мира не перешли к персонажу другого.
|
||||||
|
///
|
||||||
|
/// Пока байты здесь, хранилище не открывается и не пополняется (SpatialVaultPatch.OpenVault,
|
||||||
|
/// SpatialVaultPickup.Begin): вещи, положенные в новое пустое хранилище, всё равно не
|
||||||
|
/// сохранились бы - запись отдаёт приоритет нетронутым байтам.</summary>
|
||||||
|
public static readonly System.Collections.Generic.Dictionary<int, byte[]> UnreadableByEntity =
|
||||||
|
new System.Collections.Generic.Dictionary<int, byte[]>();
|
||||||
|
|
||||||
|
public static bool IsLocked(EntityPlayer _player)
|
||||||
|
{
|
||||||
|
return _player != null && UnreadableByEntity.ContainsKey(_player.entityId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AttachRaw(PlayerDataFile _file, byte[] _raw)
|
||||||
|
{
|
||||||
|
AttachedVaults.Remove(_file);
|
||||||
|
AttachedVaults.Add(_file, new VaultSlot { RawPayload = _raw });
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Vault attached to a PlayerDataFile while it is being written, read or
|
/// <summary>Vault attached to a PlayerDataFile while it is being written, read or
|
||||||
@@ -215,6 +246,17 @@ namespace NecromancerTome
|
|||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (SpatialVaultPersistence.UnreadableByEntity.TryGetValue(_player.entityId, out byte[] raw))
|
||||||
|
{
|
||||||
|
SpatialVaultPersistence.AttachRaw(__instance, raw);
|
||||||
|
string rawLine = "FromPlayer entity " + _player.entityId + " - carrying " + raw.Length + " unread byte(s) unchanged";
|
||||||
|
if (rawLine != SpatialVaultPersistence.lastSaveLogged)
|
||||||
|
{
|
||||||
|
SpatialVaultPersistence.lastSaveLogged = rawLine;
|
||||||
|
Debug.Log("[NecromancerTome] SpatialVaultPersistence: " + rawLine);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
Patch_ItemActionEat_ExecuteAction_SpatialVault.PlayerVaults.TryGetValue(_player.entityId, out Bag bag);
|
Patch_ItemActionEat_ExecuteAction_SpatialVault.PlayerVaults.TryGetValue(_player.entityId, out Bag bag);
|
||||||
string source = bag != null ? "session cache" : null;
|
string source = bag != null ? "session cache" : null;
|
||||||
if (bag == null && _player is EntityPlayerLocal && SpatialVaultPersistence.LastLoadedVault != null)
|
if (bag == null && _player is EntityPlayerLocal && SpatialVaultPersistence.LastLoadedVault != null)
|
||||||
@@ -260,6 +302,21 @@ namespace NecromancerTome
|
|||||||
string note;
|
string note;
|
||||||
Bag bag;
|
Bag bag;
|
||||||
|
|
||||||
|
if (SpatialVaultPersistence.AttachedVaults.TryGetValue(__instance, out SpatialVaultPersistence.VaultSlot rawSlot) &&
|
||||||
|
rawSlot.RawPayload != null)
|
||||||
|
{
|
||||||
|
SpatialVaultPersistence.UnreadableByEntity[_player.entityId] = rawSlot.RawPayload;
|
||||||
|
Patch_ItemActionEat_ExecuteAction_SpatialVault.PlayerVaults.Remove(_player.entityId);
|
||||||
|
if (isLocal)
|
||||||
|
{
|
||||||
|
SpatialVaultPersistence.LastLoadedVault = null;
|
||||||
|
}
|
||||||
|
Debug.LogWarning("[NecromancerTome] SpatialVaultPersistence: ToPlayer entity " + _player.entityId +
|
||||||
|
" - vault kept as " + rawSlot.RawPayload.Length + " unread byte(s); it stays locked and is saved unchanged");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SpatialVaultPersistence.UnreadableByEntity.Remove(_player.entityId);
|
||||||
|
|
||||||
if (SpatialVaultPersistence.AttachedVaults.TryGetValue(__instance, out SpatialVaultPersistence.VaultSlot slot))
|
if (SpatialVaultPersistence.AttachedVaults.TryGetValue(__instance, out SpatialVaultPersistence.VaultSlot slot))
|
||||||
{
|
{
|
||||||
bag = slot.Bag;
|
bag = slot.Bag;
|
||||||
@@ -309,6 +366,11 @@ namespace NecromancerTome
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
SpatialVaultPersistence.AttachedVaults.TryGetValue(__instance, out SpatialVaultPersistence.VaultSlot slot);
|
SpatialVaultPersistence.AttachedVaults.TryGetValue(__instance, out SpatialVaultPersistence.VaultSlot slot);
|
||||||
|
if (slot != null && slot.RawPayload != null)
|
||||||
|
{
|
||||||
|
SpatialVaultBlobIO.Write(_bw, slot.RawPayload);
|
||||||
|
return;
|
||||||
|
}
|
||||||
Bag bag = slot != null ? slot.Bag : null;
|
Bag bag = slot != null ? slot.Bag : null;
|
||||||
SpatialVaultBlobIO.Write(_bw, SpatialVaultPersistence.BuildPayload(bag));
|
SpatialVaultBlobIO.Write(_bw, SpatialVaultPersistence.BuildPayload(bag));
|
||||||
if (bag == null)
|
if (bag == null)
|
||||||
@@ -346,7 +408,27 @@ namespace NecromancerTome
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Bag bag = SpatialVaultPersistence.ParsePayload(payload);
|
if (!GameVersionGuard.IsSupported || GameVersionGuard.ForceVaultUnread)
|
||||||
|
{
|
||||||
|
SpatialVaultPersistence.AttachRaw(__instance, payload);
|
||||||
|
Debug.LogWarning("[NecromancerTome] SpatialVaultPersistence: Read - game version not supported, " +
|
||||||
|
payload.Length + " byte(s) kept unread and will be saved unchanged");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Bag bag;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
bag = SpatialVaultPersistence.ParsePayload(payload);
|
||||||
|
}
|
||||||
|
catch (Exception parseError)
|
||||||
|
{
|
||||||
|
// Упал сам разбор (на 3.3 это был MissingMethodException на Bag.Read). Байты не
|
||||||
|
// выбрасываем: они уйдут в файл как были.
|
||||||
|
SpatialVaultPersistence.AttachRaw(__instance, payload);
|
||||||
|
Debug.LogError("[NecromancerTome] SpatialVaultPersistence: Read - could not parse the vault, " +
|
||||||
|
payload.Length + " byte(s) kept unread and will be saved unchanged: " + parseError);
|
||||||
|
return;
|
||||||
|
}
|
||||||
// Attached even when null: a blob that says "no vault" IS an answer, and ToPlayer
|
// Attached even when null: a blob that says "no vault" IS an answer, and ToPlayer
|
||||||
// uses it to clear a stale cache when a different save is loaded.
|
// uses it to clear a stale cache when a different save is loaded.
|
||||||
SpatialVaultPersistence.Attach(__instance, bag);
|
SpatialVaultPersistence.Attach(__instance, bag);
|
||||||
|
|||||||
@@ -599,6 +599,11 @@ namespace NecromancerTome
|
|||||||
/// the same way in both places.</summary>
|
/// the same way in both places.</summary>
|
||||||
public static Bag GetVault(EntityPlayerLocal _player)
|
public static Bag GetVault(EntityPlayerLocal _player)
|
||||||
{
|
{
|
||||||
|
if (SpatialVaultPersistence.IsLocked(_player))
|
||||||
|
{
|
||||||
|
Deny(_player, "braceletSpatialVaultUnreadable");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
ProgressionValue progressionValue = _player.Progression?.GetProgressionValue(
|
ProgressionValue progressionValue = _player.Progression?.GetProgressionValue(
|
||||||
Patch_ItemActionEat_ExecuteAction_SpatialVault.NecromancySkillName);
|
Patch_ItemActionEat_ExecuteAction_SpatialVault.NecromancySkillName);
|
||||||
int level = progressionValue != null ? progressionValue.Level : 0;
|
int level = progressionValue != null ? progressionValue.Level : 0;
|
||||||
|
|||||||
+1
-1
@@ -4,6 +4,6 @@
|
|||||||
<DisplayName value="Necromancer's Tome" />
|
<DisplayName value="Necromancer's Tome" />
|
||||||
<Description value="A dark necromancy progression for 7 Days to Die 3.2: a kill-count-driven skill tree with cursed weapons, charm/deviation magic, undead companions that keep station with you and attack what you point at, base-defence wards, and a story-ending Black Portal ritual. Fully localized into 13 languages. Single-player; requires EAC off." />
|
<Description value="A dark necromancy progression for 7 Days to Die 3.2: a kill-count-driven skill tree with cursed weapons, charm/deviation magic, undead companions that keep station with you and attack what you point at, base-defence wards, and a story-ending Black Portal ritual. Fully localized into 13 languages. Single-player; requires EAC off." />
|
||||||
<Author value="Alex Cube" />
|
<Author value="Alex Cube" />
|
||||||
<Version value="1.4.0" />
|
<Version value="1.4.3" />
|
||||||
<Website value="https://www.alexcube.ru/7-days-to-die-moi-mody/kniga-nekromanta-necromancer-s-tome/" />
|
<Website value="https://www.alexcube.ru/7-days-to-die-moi-mody/kniga-nekromanta-necromancer-s-tome/" />
|
||||||
</xml>
|
</xml>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ FIXED: the white book badge is gone from every summon scroll. It is the game's m
|
|||||||
NEW: the Necromancer's Knife has its own model at last - a straight amputation-knife blade carved from a thighbone, a bone guard, a rope-wrapped grip and glowing runes on both sides of the blade - and a new icon to match. Thanks to Scarlettdruid for pointing out that the old one did not look like its icon.
|
NEW: the Necromancer's Knife has its own model at last - a straight amputation-knife blade carved from a thighbone, a bone guard, a rope-wrapped grip and glowing runes on both sides of the blade - and a new icon to match. Thanks to Scarlettdruid for pointing out that the old one did not look like its icon.
|
||||||
CHANGED: a Victim's loot moved into the corpse itself. A zombie that dies marked as a Victim leaves a red corpse that stays for an hour; search it and it turns into the Victim's bag.
|
CHANGED: a Victim's loot moved into the corpse itself. A zombie that dies marked as a Victim leaves a red corpse that stays for an hour; search it and it turns into the Victim's bag.
|
||||||
NEW: the Scroll of the Rat Spirit has its own icon.
|
NEW: the Scroll of the Rat Spirit has its own icon.
|
||||||
CHANGED: beds that become a respawn point (beds and bunk beds) can now be picked up like the other beds - placed again by your own hands, a bed should become your respawn point, like a bedroll. Not tested in game yet.
|
CHANGED: beds that become a respawn point (beds and bunk beds) can now be picked up like the other beds - placed again by your own hands, the bed becomes your respawn point, like a bedroll.
|
||||||
A NOTE ON THE 1.3.0 SCALE: Necromancy rises once every 20 zombies put to rest, so "1 of 250" in the skill panel is a level, not a kill count. The purple bar above the toolbelt shows the progress inside the level. Coming from 1.0.1, your level is recomputed from your kills and looks twenty times smaller - the kills themselves are all still there.
|
A NOTE ON THE 1.3.0 SCALE: Necromancy rises once every 20 zombies put to rest, so "1 of 250" in the skill panel is a level, not a kill count. The purple bar above the toolbelt shows the progress inside the level. Coming from 1.0.1, your level is recomputed from your kills and looks twenty times smaller - the kills themselves are all still there.
|
||||||
Not in this version: an option to keep vanilla traders (thanks zomb1 and sanovskiy) and a cap on zombies held by the Pyramid of Spirits (thanks Savion). Both are noted.
|
Not in this version: an option to keep vanilla traders (thanks zomb1 and sanovskiy) and a cap on zombies held by the Pyramid of Spirits (thanks Savion). Both are noted.
|
||||||
Single-player. The mod ships Harmony libraries, so EasyAntiCheat must be off.
|
Single-player. The mod ships Harmony libraries, so EasyAntiCheat must be off.
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
FIXED: the frame rate drop that grew the longer you played (thanks sanovskiy, Kriloko and Megaladon43 for the reports). Every red Victim corpse painted its own full-size copy of the zombie's textures and never released them, together with copies of its materials - within a couple of hours that was gigabytes of video memory. The painted textures are now small and compressed, shared between corpses, and everything is released as soon as the corpse is gone.
|
||||||
|
CHANGED: an unsearched Victim corpse now stays for 5 minutes instead of an hour.
|
||||||
|
CHANGED: the Necromancy tracker refreshes the knife's damage once a second instead of 10 times a second (thanks zomb1 for pointing at it). Nothing changes in play: the damage only moves when you kill.
|
||||||
|
IMPORTANT: this version is for game version 3.2. It is NOT compatible with 3.3 experimental (latest_experimental): there the Spatial Vault stops saving its contents, so items in it are lost, and the Pyramid of Heresy fails to load. Please stay on 3.2 with this mod for now; a 3.3 version will be released separately.
|
||||||
|
Drop-in over 1.4.0, saves carry over. Single-player. The mod ships Harmony libraries, so EasyAntiCheat must be off.
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
NEW: the mod now checks the game version at start. This build is for 7 Days to Die 3.2.x; on any other version (3.3 experimental included) the main menu shows a window saying the mod is incompatible and asking you to update it, with a single Quit game button. This is to stop what happened with 1.4.1 on 3.3: the Spatial Vault could not be read there and was saved empty, so its items were lost.
|
||||||
|
NEW: the Spatial Vault can no longer be wiped by a version it cannot read. If its saved contents cannot be read, they are kept untouched and written back byte for byte on every save, and the vault stays closed with a message instead of opening empty. Installing the right build of the mod brings the items back.
|
||||||
|
The new window and the vault message are translated into all 13 languages of the mod.
|
||||||
|
Drop-in over 1.4.1, saves carry over. For game version 3.2 only; a 3.3 version will come separately. Single-player. The mod ships Harmony libraries, so EasyAntiCheat must be off.
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
FIX: the frame rate no longer sinks during a session. Before, FPS went from 60 down to 15-30 within 10-15 minutes of playing, even standing still, and only reloading the world brought it back. Thanks to Megaladon43, sanovskiy and zomb1 for the reports and tests.
|
||||||
|
The cause is a bug in the game's own UI bindings: an expression that reads a CVar registers one more watcher each time it is recalculated, and a fresh watcher triggers a recalculation again the next frame. The purple Necromancy progress bar above the XP bar (added in 1.3.0) hit exactly that, so it gained one watcher per frame and the game walked the whole growing list every frame. The mod now registers a CVar watcher once per binding.
|
||||||
|
FIX: the Pyramid of Heresy is textured again when Texture Quality is set below Full. On lower settings the game's block atlas is smaller than the pyramid texture and the mod used to skip it.
|
||||||
|
This is the same fix as in 1.5.1, brought to game 3.2. Drop-in over 1.4.2, saves carry over. For game version 3.2 only; for game 3.3 use 1.5.1. Single-player. The mod ships Harmony libraries, so EasyAntiCheat must be off.
|
||||||
@@ -30,7 +30,7 @@ To uninstall, delete the folder. The mod adds items and one block, so a save tha
|
|||||||
|
|
||||||
[b]Turn the horde on itself.[/b] The [b]Spirit Stone[/b] is thrown; the zombie it touches switches sides and starts tearing into the others instead of you. The [b]Scroll of Deviation[/b] does the same to everything in the blast radius at once.
|
[b]Turn the horde on itself.[/b] The [b]Spirit Stone[/b] is thrown; the zombie it touches switches sides and starts tearing into the others instead of you. The [b]Scroll of Deviation[/b] does the same to everything in the blast radius at once.
|
||||||
|
|
||||||
[b]A blade that grows with you.[/b] The [b]Necromancer's Knife[/b] is carved from a thighbone - a straight amputation-knife edge, a bone guard, a rope-wrapped grip and runes that glow. It scales its damage with your Necromancy level - nearly useless in unskilled hands, lethal once you are levelled. It heals you for half the damage dealt and marks the wounded zombie as a Victim: its corpse turns red and stays for an hour, and searching it leaves a Victim's bag of necromantic ingredients. [b]Six mods fit this knife and nothing else[/b]: water and food drawn straight out of corpses, protection from heat and cold, slowed victims, an area power-attack storm bought with your own health, and every nearby zombie marked on your compass and map.
|
[b]A blade that grows with you.[/b] The [b]Necromancer's Knife[/b] is carved from a thighbone - a straight amputation-knife edge, a bone guard, a rope-wrapped grip and runes that glow. It scales its damage with your Necromancy level - nearly useless in unskilled hands, lethal once you are levelled. It heals you for half the damage dealt and marks the wounded zombie as a Victim: its corpse turns red and stays for five minutes, and searching it leaves a Victim's bag of necromantic ingredients. [b]Six mods fit this knife and nothing else[/b]: water and food drawn straight out of corpses, protection from heat and cold, slowed victims, an area power-attack storm bought with your own health, and every nearby zombie marked on your compass and map.
|
||||||
|
|
||||||
[b]Undead that fight for you.[/b] Summoning books raise a [b]zombie dog, bear, wolf or griffin[/b] - permanent companions, one of each kind at a time, recalled into the book with a power attack. [b]Pharaoh’s Swarm[/b] release a swarm that scatters wide and stings zombies onto your side. The [b]Banshee's Scroll[/b] screams up a small horde that is [i]not[/i] on your side at all - use it on purpose.
|
[b]Undead that fight for you.[/b] Summoning books raise a [b]zombie dog, bear, wolf or griffin[/b] - permanent companions, one of each kind at a time, recalled into the book with a power attack. [b]Pharaoh’s Swarm[/b] release a swarm that scatters wide and stings zombies onto your side. The [b]Banshee's Scroll[/b] screams up a small horde that is [i]not[/i] on your side at all - use it on purpose.
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
Fixes the FPS drop: Victim corpses leaked video memory until restart. Unsearched corpses now stay 5 minutes. For game 3.2 only - NOT compatible with 3.3 experimental (Spatial Vault contents get lost). Drop-in over 1.4.0.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Safety update: on an incompatible game version (e.g. 3.3 experimental) the mod asks you to update it and quits, and the Spatial Vault is never wiped - unreadable contents are kept as they are. For game 3.2. Drop-in over 1.4.1.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
FPS fix for game 3.2: the frame rate no longer sinks over a session (a bug in the game's UI bindings, hit by the Necromancy bar since 1.3.0). Pyramid of Heresy textured again below Full texture quality. Drop-in over 1.4.2.
|
||||||
Binary file not shown.
Binary file not shown.
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
*English version below — scroll past the Russian half.*
|
*English version below — scroll past the Russian half.*
|
||||||
|
|
||||||
**Версия 1.4.0** — для 7 Days to Die 3.2. Автор: Alex Cube.
|
**Версия 1.4.3** — для 7 Days to Die 3.2 (с 3.3 experimental не совместима и сама об этом скажет). Автор: Alex Cube.
|
||||||
|
|
||||||
- Страница мода: https://www.alexcube.ru/7-days-to-die-moi-mody/kniga-nekromanta-necromancer-s-tome/
|
- Страница мода: https://www.alexcube.ru/7-days-to-die-moi-mody/kniga-nekromanta-necromancer-s-tome/
|
||||||
- Nexus Mods: https://www.nexusmods.com/7daystodie/mods/12547
|
- Nexus Mods: https://www.nexusmods.com/7daystodie/mods/12547
|
||||||
@@ -150,7 +150,7 @@ HUD за этим следят два индикатора: череп в ста
|
|||||||
|
|
||||||
**13 языков полностью:** русский, английский, немецкий, испанский, французский, итальянский,
|
**13 языков полностью:** русский, английский, немецкий, испанский, французский, итальянский,
|
||||||
японский, корейский, польский, португальский (Бразилия), турецкий, китайский упрощённый и
|
японский, корейский, польский, португальский (Бразилия), турецкий, китайский упрощённый и
|
||||||
традиционный. Все 149 ключей `Config/Localization.csv` заполнены, пустых ячеек нет.
|
традиционный. Все 165 ключей `Config/Localization.csv` заполнены, пустых ячеек нет.
|
||||||
|
|
||||||
## Установка
|
## Установка
|
||||||
|
|
||||||
@@ -162,6 +162,14 @@ HUD за этим следят два индикатора: череп в ста
|
|||||||
|
|
||||||
## Статус
|
## Статус
|
||||||
|
|
||||||
|
Версия 1.4.3 — **FPS больше не сползает за сессию**. Раньше за 10–15 минут игры FPS падал с 60 до 15–30 даже стоя на месте, и помогал только перезаход в мир. Причина — ошибка в интерфейсных привязках самой игры, на которую наступала фиолетовая шкала некромантии над полосой опыта (с 1.3.0): она набирала по одной отслеживаемой переменной за кадр. Теперь переменная регистрируется один раз. Заодно Пирамида Ереси снова с текстурой при качестве текстур ниже Full.
|
||||||
|
|
||||||
|
Версия 1.4.2 — **защита от несовместимой версии игры**. Если мод запущен не на той версии игры, под которую собран (эта сборка — для 3.2.x), в главном меню появляется окно «мод несовместим, обновите» с единственной кнопкой «Выйти из игры». Если игрок всё же откроет мир, сохранённое Пространственное хранилище не разбирается и записывается обратно байт в байт — вещи дождутся правильной сборки мода.
|
||||||
|
|
||||||
|
Версия 1.4.1 — **исправлено падение FPS**. Красный труп «Жертвы» оставлял в памяти свои текстуры и материалы и не отдавал их до перезапуска игры: за пару часов набегали гигабайты видеопамяти, и FPS падал в разы. Теперь всё убирается вместе с трупом, а сам труп лежит 5 минут, а не час. Урон ножа пересчитывается раз в секунду, а не 10 раз.
|
||||||
|
|
||||||
|
**Мод рассчитан на игру 3.2 и НЕ совместим с 3.3 experimental**: там перестаёт сохраняться содержимое Пространственного хранилища и ломается Пирамида Ереси. Версия под 3.3 выйдет отдельно.
|
||||||
|
|
||||||
Версия 1.4.0 — **питомцы переписаны заново**, и первый из них теперь доступен рано: **Дух крысы**
|
Версия 1.4.0 — **питомцы переписаны заново**, и первый из них теперь доступен рано: **Дух крысы**
|
||||||
(Некромантия 3, 60 упокоенных зомби). Все питомцы идут за вами, сами ни на кого не нападают и
|
(Некромантия 3, 60 упокоенных зомби). Все питомцы идут за вами, сами ни на кого не нападают и
|
||||||
ждут приказа: повторное применение свитка при живом питомце натравливает его на цель под
|
ждут приказа: повторное применение свитка при живом питомце натравливает его на цель под
|
||||||
@@ -222,12 +230,12 @@ HUD за этим следят два индикатора: череп в ста
|
|||||||
|
|
||||||
Весь заявленный контент реализован. Основное проверено в игре: призыв, отзыв и следование всех
|
Весь заявленный контент реализован. Основное проверено в игре: призыв, отзыв и следование всех
|
||||||
питомцев, приказ атаковать, зачёт убийств питомцами, новая модель ножа в руке, красный труп
|
питомцев, приказ атаковать, зачёт убийств питомцами, новая модель ножа в руке, красный труп
|
||||||
Жертвы и мешок при его обыске, заряд браслета и
|
Жертвы и мешок при его обыске, подобранная и заново поставленная кровать как точка возрождения,
|
||||||
|
заряд браслета и
|
||||||
расход Кровавой сферы, счёт убийств Некромантии (включая зомби-зверей, кровотечение и огонь),
|
расход Кровавой сферы, счёт убийств Некромантии (включая зомби-зверей, кровотечение и огонь),
|
||||||
призрачность торговцев, крик при создании Крови некроманта.
|
призрачность торговцев, крик при создании Крови некроманта.
|
||||||
|
|
||||||
> **Эта сборка проверена не полностью.** Из новшеств 1.4.0 не проверены: **подбор кроватей**
|
> **Эта сборка проверена не полностью.** Из новшеств 1.4.0 не проверено поведение питомцев
|
||||||
> (становится ли поднятая и заново поставленная кровать точкой возрождения) и поведение питомцев
|
|
||||||
> **в кровавую луну и на транспорте**.
|
> **в кровавую луну и на транспорте**.
|
||||||
> Из прошлых версий ещё не проверены: **новая шкала уровней и оба индикатора** (череп с уровнем
|
> Из прошлых версий ещё не проверены: **новая шкала уровней и оба индикатора** (череп с уровнем
|
||||||
> и фиолетовая шкала прогресса), **починка старых сейвов на загрузке**, **мир между подчинёнными
|
> и фиолетовая шкала прогресса), **починка старых сейвов на загрузке**, **мир между подчинёнными
|
||||||
@@ -244,7 +252,7 @@ HUD за этим следят два индикатора: череп в ста
|
|||||||
|
|
||||||
# Necromancer's Tome (English)
|
# Necromancer's Tome (English)
|
||||||
|
|
||||||
**Version 1.4.0** - for 7 Days to Die 3.2. By Alex Cube.
|
**Version 1.4.3** - for 7 Days to Die 3.2 (not compatible with 3.3 experimental, and says so itself). By Alex Cube.
|
||||||
|
|
||||||
- Mod page: https://www.alexcube.ru/7-days-to-die-moi-mody/kniga-nekromanta-necromancer-s-tome/
|
- Mod page: https://www.alexcube.ru/7-days-to-die-moi-mody/kniga-nekromanta-necromancer-s-tome/
|
||||||
- Nexus Mods: https://www.nexusmods.com/7daystodie/mods/12547
|
- Nexus Mods: https://www.nexusmods.com/7daystodie/mods/12547
|
||||||
@@ -299,7 +307,7 @@ Five tiers, each opening part of the arsenal (the table gives both the zombie co
|
|||||||
knife, a bone guard, a rope-wrapped grip and glowing runes. Its damage grows with the Necromancy
|
knife, a bone guard, a rope-wrapped grip and glowing runes. Its damage grows with the Necromancy
|
||||||
skill: nearly useless in unskilled hands, lethal for a levelled player. Heals the wielder for
|
skill: nearly useless in unskilled hands, lethal for a levelled player. Heals the wielder for
|
||||||
half the damage dealt and marks the wounded zombie as a Victim - its corpse turns red and stays
|
half the damage dealt and marks the wounded zombie as a Victim - its corpse turns red and stays
|
||||||
for an hour; searching it makes it vanish and leave a Victim's bag of ingredients.
|
for five minutes; searching it makes it vanish and leave a Victim's bag of ingredients.
|
||||||
- **Six mods for the Necromancer's Knife only** - ordinary knife mods will not fit this weapon,
|
- **Six mods for the Necromancer's Knife only** - ordinary knife mods will not fit this weapon,
|
||||||
and these will not fit any other: **Tears of the Dead** (2 water from every zombie killed with
|
and these will not fit any other: **Tears of the Dead** (2 water from every zombie killed with
|
||||||
the blade), **Scavenger's Feast** (2 food per corpse), **Grave's Repose** (protection from heat
|
the blade), **Scavenger's Feast** (2 food per corpse), **Grave's Repose** (protection from heat
|
||||||
@@ -392,7 +400,7 @@ deliberately not retold here: a README gets read before the playthrough.
|
|||||||
## Localization
|
## Localization
|
||||||
|
|
||||||
**13 languages, complete:** Russian, English, German, Spanish, French, Italian, Japanese, Korean,
|
**13 languages, complete:** Russian, English, German, Spanish, French, Italian, Japanese, Korean,
|
||||||
Polish, Brazilian Portuguese, Turkish, Simplified and Traditional Chinese. All 149 keys in
|
Polish, Brazilian Portuguese, Turkish, Simplified and Traditional Chinese. All 165 keys in
|
||||||
`Config/Localization.csv` are filled in, with no empty cells.
|
`Config/Localization.csv` are filled in, with no empty cells.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
@@ -406,6 +414,14 @@ story scenes play without stopping time.
|
|||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
|
Version 1.4.3 - **the frame rate no longer sinks during a session**. Before, FPS went from 60 down to 15-30 within 10-15 minutes of playing, even standing still, and only reloading the world brought it back. The cause is a bug in the game's own UI bindings, hit by the purple Necromancy bar above the XP bar (since 1.3.0): it gained one watcher per frame. The watcher is now registered once. Also, the Pyramid of Heresy is textured again when Texture Quality is below Full.
|
||||||
|
|
||||||
|
Version 1.4.2 - **protection against an incompatible game version**. If the mod runs on a game version it was not built for (this build is for 3.2.x), the main menu shows a "mod is incompatible, please update" window with a single "Quit game" button. And if a world does get opened, the saved Spatial Vault is not parsed but written back byte for byte - its items wait for the right build of the mod.
|
||||||
|
|
||||||
|
Version 1.4.1 - **the FPS drop is fixed**. A red Victim corpse left its textures and materials in memory and never gave them back until the game was restarted: within a couple of hours that was gigabytes of video memory, and the frame rate fell several times over. Everything is now cleaned up with the corpse, and the corpse itself stays for 5 minutes instead of an hour. The knife's damage is refreshed once a second instead of 10 times.
|
||||||
|
|
||||||
|
**The mod is built for game version 3.2 and is NOT compatible with 3.3 experimental**: there the Spatial Vault stops saving its contents and the Pyramid of Heresy breaks. A 3.3 version will come separately.
|
||||||
|
|
||||||
Version 1.4.0 - **pets have been rewritten**, and the first of them now comes early: the **Rat
|
Version 1.4.0 - **pets have been rewritten**, and the first of them now comes early: the **Rat
|
||||||
Spirit** (Necromancy 3, 60 zombies put to rest). Every pet follows you, attacks nobody on its own
|
Spirit** (Necromancy 3, 60 zombies put to rest). Every pet follows you, attacks nobody on its own
|
||||||
and waits for an order: using the scroll again while the pet is alive sends it after whatever is
|
and waits for an order: using the scroll again while the pet is alive sends it after whatever is
|
||||||
@@ -469,12 +485,12 @@ after leaving the game (the storage is saved in the player's own file, next to t
|
|||||||
|
|
||||||
All the announced content is implemented. The core of it is confirmed in game: summoning,
|
All the announced content is implemented. The core of it is confirmed in game: summoning,
|
||||||
dismissing and following for every pet, the attack order, pets' kills counting as yours, the new
|
dismissing and following for every pet, the attack order, pets' kills counting as yours, the new
|
||||||
knife model in hand, the red Victim corpse and the bag it leaves when searched, the bracelet's charge and the Blood Sphere's spending, Necromancy's kill count
|
knife model in hand, the red Victim corpse and the bag it leaves when searched, a bed picked up
|
||||||
|
and placed again becoming a respawn point, the bracelet's charge and the Blood Sphere's spending, Necromancy's kill count
|
||||||
(zombie animals, bleeding and fire included), the traders' ghostly look, and the cry when
|
(zombie animals, bleeding and fire included), the traders' ghostly look, and the cry when
|
||||||
Necromancer's Blood is made.
|
Necromancer's Blood is made.
|
||||||
|
|
||||||
> **This build has not been fully tested.** Of what is new in 1.4.0, not yet checked: **picking up
|
> **This build has not been fully tested.** Of what is new in 1.4.0, not yet checked: how pets
|
||||||
> beds** (whether a bed picked up and placed again really becomes a respawn point) and how pets
|
|
||||||
> behave **during a blood moon and on vehicles**. From
|
> behave **during a blood moon and on vehicles**. From
|
||||||
> earlier versions, not yet checked: the **new level scale and both indicators**
|
> earlier versions, not yet checked: the **new level scale and both indicators**
|
||||||
> (the skull showing the level and the purple progress bar), the **self-repair of old saves on
|
> (the skull showing the level and the purple progress bar), the **self-repair of old saves on
|
||||||
|
|||||||
Binary file not shown.
@@ -15,7 +15,7 @@
|
|||||||
<ul>
|
<ul>
|
||||||
<li><strong>Камень духов</strong> — брошенный камень, светящийся некромантской энергией. Задетый зомби переходит на вашу сторону и начинает рвать других зомби вместо вас.</li>
|
<li><strong>Камень духов</strong> — брошенный камень, светящийся некромантской энергией. Задетый зомби переходит на вашу сторону и начинает рвать других зомби вместо вас.</li>
|
||||||
<li><strong>Свиток девиации</strong> — то же самое, но по площади: переманивает сразу всех зомби в радиусе взрыва. Расходуется при использовании.</li>
|
<li><strong>Свиток девиации</strong> — то же самое, но по площади: переманивает сразу всех зомби в радиусе взрыва. Расходуется при использовании.</li>
|
||||||
<li><strong>Нож некроманта</strong> — клинок, выточенный из бедренной кости: прямое лезвие, как у ампутационного ножа, костяная гарда, рукоять в верёвочной обмотке и светящиеся руны. Урон растёт вместе с навыком Некромантии: почти бесполезен в неопытных руках, смертоносен у прокачанного игрока. Лечит владельца на половину нанесённого урона и метит раненого зомби как Жертву — труп такого зомби краснеет и лежит час; при обыске он исчезает, оставляя мешок Жертвы с ингредиентами.</li>
|
<li><strong>Нож некроманта</strong> — клинок, выточенный из бедренной кости: прямое лезвие, как у ампутационного ножа, костяная гарда, рукоять в верёвочной обмотке и светящиеся руны. Урон растёт вместе с навыком Некромантии: почти бесполезен в неопытных руках, смертоносен у прокачанного игрока. Лечит владельца на половину нанесённого урона и метит раненого зомби как Жертву — труп такого зомби краснеет и лежит пять минут; при обыске он исчезает, оставляя мешок Жертвы с ингредиентами.</li>
|
||||||
<li><strong>Шесть модов только для этого ножа</strong> — вода и еда прямо из трупов (Слёзы мертвеца, Пир падальщика), защита от жары и холода (Могильный покой), замедление раненых (Хватка мертвеца), силовая атака-буря по площади за двойную цену здоровья и выносливости (Мёртвая буря) и подсветка всех зомби поблизости на карте и компасе (Тёмное чутьё). Обычные моды в этот нож не встают, а эти не встают ни во что другое.</li>
|
<li><strong>Шесть модов только для этого ножа</strong> — вода и еда прямо из трупов (Слёзы мертвеца, Пир падальщика), защита от жары и холода (Могильный покой), замедление раненых (Хватка мертвеца), силовая атака-буря по площади за двойную цену здоровья и выносливости (Мёртвая буря) и подсветка всех зомби поблизости на карте и компасе (Тёмное чутьё). Обычные моды в этот нож не встают, а эти не встают ни во что другое.</li>
|
||||||
<li><strong>Кровь некроманта</strong> — ритуальный ресурс: пустая банка, любой нож в руках и 90% текущего здоровья за одну порцию. Ингредиент для самых тёмных рецептов.</li>
|
<li><strong>Кровь некроманта</strong> — ритуальный ресурс: пустая банка, любой нож в руках и 90% текущего здоровья за одну порцию. Ингредиент для самых тёмных рецептов.</li>
|
||||||
<li><strong>Кожа жертвы</strong> и <strong>Прах зомби</strong> — падают с зомби, помеченного ножом как Жертва. Основа книг призыва и большинства некромантских рецептов.</li>
|
<li><strong>Кожа жертвы</strong> и <strong>Прах зомби</strong> — падают с зомби, помеченного ножом как Жертва. Основа книг призыва и большинства некромантских рецептов.</li>
|
||||||
@@ -93,7 +93,7 @@ A dedicated <strong>Necromancy</strong> skill levels not from perk points and no
|
|||||||
<ul>
|
<ul>
|
||||||
<li><strong>Spirit Stone</strong> — a thrown stone lit with necromantic energy. The zombie it touches switches to your side and starts tearing into other zombies instead of you.</li>
|
<li><strong>Spirit Stone</strong> — a thrown stone lit with necromantic energy. The zombie it touches switches to your side and starts tearing into other zombies instead of you.</li>
|
||||||
<li><strong>Scroll of Deviation</strong> — the same effect, but area-wide: it charms every zombie in the blast radius at once. Consumed on use.</li>
|
<li><strong>Scroll of Deviation</strong> — the same effect, but area-wide: it charms every zombie in the blast radius at once. Consumed on use.</li>
|
||||||
<li><strong>Necromancer's Knife</strong> — a blade carved from a thighbone: a straight edge like an amputation knife, a bone guard, a rope-wrapped grip and glowing runes. Its damage scales with the Necromancy skill: nearly useless in unskilled hands, lethal for a levelled player. Heals the wielder for half the damage dealt and marks the wounded zombie as a Victim — its corpse turns red and stays for an hour; searching it makes it vanish and leave a Victim's bag of ingredients.</li>
|
<li><strong>Necromancer's Knife</strong> — a blade carved from a thighbone: a straight edge like an amputation knife, a bone guard, a rope-wrapped grip and glowing runes. Its damage scales with the Necromancy skill: nearly useless in unskilled hands, lethal for a levelled player. Heals the wielder for half the damage dealt and marks the wounded zombie as a Victim — its corpse turns red and stays for five minutes; searching it makes it vanish and leave a Victim's bag of ingredients.</li>
|
||||||
<li><strong>Six mods for this knife only</strong> — water and food straight out of corpses (Tears of the Dead, Scavenger's Feast), protection from heat and cold (Grave's Repose), slowed victims (Dead Man's Grip), an area power-attack storm at double the health and stamina cost (Dead Storm), and every nearby zombie marked on your map and compass (Dark Sense). Ordinary knife mods will not fit this weapon, and these will not fit any other.</li>
|
<li><strong>Six mods for this knife only</strong> — water and food straight out of corpses (Tears of the Dead, Scavenger's Feast), protection from heat and cold (Grave's Repose), slowed victims (Dead Man's Grip), an area power-attack storm at double the health and stamina cost (Dead Storm), and every nearby zombie marked on your map and compass (Dark Sense). Ordinary knife mods will not fit this weapon, and these will not fit any other.</li>
|
||||||
<li><strong>Necromancer's Blood</strong> — a ritual resource: an empty jar, any knife in hand and 90% of your current health per portion. An ingredient for the darkest recipes.</li>
|
<li><strong>Necromancer's Blood</strong> — a ritual resource: an empty jar, any knife in hand and 90% of your current health per portion. An ingredient for the darkest recipes.</li>
|
||||||
<li><strong>Victim's Skin</strong> and <strong>Zombie Ash</strong> — dropped by a zombie marked as a Victim. The basis of the summoning books and most necromantic recipes.</li>
|
<li><strong>Victim's Skin</strong> and <strong>Zombie Ash</strong> — dropped by a zombie marked as a Victim. The basis of the summoning books and most necromantic recipes.</li>
|
||||||
|
|||||||
Reference in New Issue
Block a user