diff --git a/Config/Localization.csv b/Config/Localization.csv index 170a2f0..d6da77c 100644 --- a/Config/Localization.csv +++ b/Config/Localization.csv @@ -17,11 +17,11 @@ thrownBookGrimoireDeviationDesc,items,Item,,,,"A necromantic scroll glowing with bookSummonZombieDog,items,Item,,,,Summon Zombie Dog,,Zombiehund beschwören,Invocar Perro Zombi,Invoquer un Chien Zombie,Evoca Cane Zombie,ゾンビ犬召喚,좀비 개 소환,Przyzwanie: Pies Zombie,Invocar Cão Zumbi,Призыв зомбособаки,Zombi Köpek Çağırma,召唤僵尸犬,召喚殭屍犬 bookSummonZombieDogDesc,items,Item,,,,A necromantic tome that raises a zombie dog bound to your will. It fights alongside you against other zombies until it dies. You can only have one bound at a time. Power attack to recall it back into the book.,,"Ein nekromantisches Buch, das einen Zombiehund erweckt, der deinem Willen unterworfen ist. Er kämpft an deiner Seite gegen andere Zombies, bis er stirbt. Du kannst immer nur einen gebunden haben. Mit dem Powerangriff rufst du ihn ins Buch zurück.",Un tomo nigromántico que alza un Perro Zombi sometido a tu voluntad. Lucha a tu lado contra otros zombis hasta morir. Solo puedes tener uno vinculado a la vez. Usa el ataque potente para devolverlo al libro.,Un ouvrage nécromantique qui relève un Chien Zombie soumis à ta volonté. Il combat à tes côtés contre les autres zombies jusqu'à sa mort. Tu ne peux en lier qu'un seul à la fois. L'attaque puissante le rappelle dans le livre.,Un tomo negromantico che rialza un Cane Zombie soggetto alla tua volontà. Combatte al tuo fianco contro gli altri zombie finché non muore. Puoi averne vincolato solo uno alla volta. L'attacco potente lo richiama nel libro.,あなたの意志に縛られたゾンビ犬を起こす降霊術の書。死ぬまで、あなたの側で他のゾンビと戦う。同時に縛っておけるのは一体だけ。強攻撃で書物へ呼び戻せる。,당신의 의지에 묶인 좀비 개을(를) 일으키는 강령술 서적. 죽을 때까지 당신 곁에서 다른 좀비와 싸운다. 한 번에 하나만 묶어 둘 수 있다. 강공격으로 책 속에 불러들인다.,"Nekromancki tom, który wskrzesza Psa Zombie związanego twoją wolą. Walczy u twojego boku z innymi zombie, aż zginie. Naraz możesz mieć związanego tylko jednego. Atak specjalny odwołuje go z powrotem do księgi.",Um tomo necromântico que ergue um Cão Zumbi preso à sua vontade. Luta ao seu lado contra outros zumbis até morrer. Você só pode ter um vinculado por vez. O ataque forte o chama de volta para o livro.,"Некромантский фолиант, поднимающий зомбособаку, подчинённую вашей воле. Она сражается на вашей стороне против других зомби, пока не погибнет. Одновременно можно держать только одну. Силовая атака отзывает её обратно в книгу.",İradene bağlı bir Zombi Köpek dirilten bir nekromansi kitabı. Ölene kadar yanında diğer zombilere karşı savaşır. Aynı anda yalnızca bir tanesini bağlı tutabilirsin. Güçlü saldırı onu kitaba geri çağırır.,一本亡灵法术典籍,唤起一只受你意志束缚的僵尸犬。它会在你身边与其他僵尸战斗,直到死亡。同时只能束缚一只。使用强力攻击可将其召回书中。,一本亡靈法術典籍,喚起一隻受你意志束縛的殭屍犬。牠會在你身邊與其他殭屍戰鬥,直到死亡。同時只能束縛一隻。使用強力攻擊可將其召回書中。 necroZombieDogAlreadyActive,items,Item,,,,You already have a Zombie Dog - use the power attack to recall it,,Du hast bereits einen Zombiehund – rufe ihn mit dem Powerangriff zurück,Ya tienes un Perro Zombi: usa el ataque potente para devolverlo,Tu as déjà un Chien Zombie – utilise l'attaque puissante pour le rappeler,Hai già un Cane Zombie: usa l'attacco potente per richiamarlo,すでにゾンビ犬がいます – 強攻撃で呼び戻してください,이미 좀비 개이(가) 있습니다 - 강공격으로 불러들이세요,Masz już Psa Zombie – odwołaj go atakiem specjalnym,Você já tem um Cão Zumbi - use o ataque forte para chamá-lo de volta,У вас уже есть зомбособака - отзовите её силовой атакой,Zaten bir Zombi Köpek var - geri çağırmak için güçlü saldırıyı kullan,你已经有一只僵尸犬了——用强力攻击将其召回,你已經有一隻殭屍犬了——用強力攻擊將其召回 -necroInsectSwarmAlreadyActive,items,Item,,,,You already have Beetles active - wait for them to be destroyed,,"Du hast bereits Käfer im Einsatz – warte, bis sie vernichtet sind",Ya tienes escarabajos activos: espera a que sean destruidos,Tu as déjà des scarabées actifs – attends qu'ils soient détruits,Hai già degli scarabei attivi: aspetta che vengano distrutti,すでに甲虫が展開中です – 全滅するまで待ってください,이미 딱정벌레가 활동 중입니다 - 전멸할 때까지 기다리세요,"Masz już aktywne żuki – poczekaj, aż zostaną zniszczone",Você já tem besouros ativos - espere até que sejam destruídos,"У вас уже есть активные жуки - дождитесь, пока они будут уничтожены",Zaten etkin böceklerin var - yok edilmelerini bekle,你已经放出了甲虫——等它们被消灭再说,你已經放出了甲蟲——等牠們被消滅再說 +necroInsectSwarmAlreadyActive,items,Item,,,,A swarm is already out - wait for it to be destroyed,,"Ein Schwarm ist bereits unterwegs - warte, bis er vernichtet ist",Ya hay un enjambre fuera: espera a que sea destruido,Un essaim est déjà dehors - attends qu’il soit détruit,Uno sciame è già fuori: aspetta che venga distrutto,すでに群れが出ています - 消え去るのを待ってください,이미 무리가 나가 있습니다 - 사라질 때까지 기다리세요,"Rój już krąży - poczekaj, aż zostanie zniszczony",Um enxame já está solto - espere até que seja destruído,"Рой уже выпущен - дождитесь, пока он будет уничтожен",Bir sürü zaten dışarıda - yok olmasını bekle,已经有一群在外了——等它被消灭,已經有一群在外了——等牠被消滅 necroZombieDogRecalled,items,Item,,,,Zombie Dog recalled,,Zombiehund zurückgerufen,Perro Zombi devuelto,Chien Zombie rappelé,Cane Zombie richiamato,ゾンビ犬を呼び戻しました,좀비 개을(를) 불러들였습니다,Odwołano: Pies Zombie,Cão Zumbi chamado de volta,Зомбособака отозвана,Zombi Köpek geri çağrıldı,已召回僵尸犬,已召回殭屍犬 necroZombieDogNothingToRecall,items,Item,,,,No Zombie Dog to recall,,Kein Zombiehund zum Zurückrufen vorhanden,No hay ningún Perro Zombi que devolver,Aucun Chien Zombie à rappeler,Nessun Cane Zombie da richiamare,呼び戻せるゾンビ犬がいません,불러들일 좀비 개이(가) 없습니다,Brak Psa Zombie do odwołania,Não há nenhum Cão Zumbi para chamar de volta,Нет зомбособаки для отзыва,Geri çağrılacak bir Zombi Köpek yok,没有可召回的僵尸犬,沒有可召回的殭屍犬 -bookSummonInsectSwarm,items,Item,,,,Beetles of the Lord,,Käfer des Herrn,Escarabajos del Señor,Scarabées du Seigneur,Scarabei del Signore,主の甲虫,군주의 딱정벌레,Żuki Władcy,Besouros do Senhor,Жуки Властелина,Efendi'nin Böcekleri,主宰之甲虫,主宰之甲蟲 -bookSummonInsectSwarmDesc,items,Item,,,,"A necromantic scroll that unleashes a swarm of beetles bound to your will, one time only - once cast, they cannot be recalled. They seek out every zombie in a wide radius and sting them; a stung zombie turns to your side, just like the Spirit Stone or Scroll of Deviation. You can only have one swarm active at a time. Consumed on use.",,"Eine nekromantische Schriftrolle, die einen Schwarm deinem Willen unterworfener Käfer entfesselt – ein einziges Mal: einmal beschworen, lassen sie sich nicht mehr zurückrufen. Sie suchen jeden Zombie im weiten Umkreis und stechen ihn; ein gestochener Zombie wechselt auf deine Seite, genau wie beim Geisterstein oder der Schriftrolle der Abweichung. Du kannst immer nur einen Schwarm gleichzeitig haben. Wird beim Gebrauch verbraucht.","Un pergamino nigromántico que desata un enjambre de escarabajos sometidos a tu voluntad, una sola vez: una vez lanzados, no se pueden hacer volver. Buscan a todos los zombis en un amplio radio y los pican; un zombi picado se pone de tu lado, igual que con la Piedra de espíritus o el Pergamino de desviación. Solo puedes tener un enjambre activo a la vez. Se consume al usarlo.","Un parchemin nécromantique qui déchaîne un essaim de scarabées soumis à ta volonté, une seule fois : une fois lancés, on ne peut plus les rappeler. Ils cherchent tous les zombies dans un large rayon et les piquent ; un zombie piqué passe de ton côté, exactement comme avec la Pierre des esprits ou le Parchemin de déviation. Tu ne peux avoir qu'un seul essaim actif à la fois. Consommé à l'usage.","Una pergamena negromantica che scatena uno sciame di scarabei soggetti alla tua volontà, una volta sola: una volta lanciati non possono più essere richiamati. Cercano ogni zombie in un ampio raggio e lo pungono; uno zombie punto passa dalla tua parte, proprio come con la Pietra degli spiriti o la Pergamena della deviazione. Puoi avere un solo sciame attivo alla volta. Si consuma con l'uso.",あなたの意志に縛られた甲虫の群れを解き放つ降霊術の巻物。ただし一度きりで、放ったあとは呼び戻せない。広い範囲のゾンビを探し出して刺し、刺されたゾンビは精霊の石や逸脱の巻物と同じようにあなたの側につく。同時に展開できる群れは一つだけ。使用すると失われる。,"당신의 의지에 묶인 딱정벌레 떼를 단 한 번 풀어놓는 강령술 두루마리 - 한 번 풀면 다시 불러들일 수 없습니다. 넓은 범위의 좀비를 모두 찾아내 쏘며, 쏘인 좀비는 영혼의 돌이나 일탈의 두루마리처럼 당신 편이 됩니다. 한 번에 한 무리만 유지할 수 있습니다. 사용하면 소모됩니다.","Nekromancki zwój, który wypuszcza rój żuków związanych twoją wolą – tylko raz: po rzuceniu nie da się ich już odwołać. Wyszukują każde zombie w szerokim promieniu i żądlą je; użądlone zombie przechodzi na twoją stronę, tak samo jak przy Kamieniu duchów czy Zwoju dewiacji. Naraz możesz mieć tylko jeden aktywny rój. Zużywa się przy użyciu.","Um pergaminho necromântico que libera um enxame de besouros presos à sua vontade, uma única vez: depois de lançados, não podem ser chamados de volta. Eles procuram cada zumbi num amplo raio e o picam; um zumbi picado passa para o seu lado, igual à Pedra dos espíritos ou ao Pergaminho do Desvio. Você só pode ter um enxame ativo por vez. Consumido ao usar.","Некромантский свиток, высвобождающий рой жуков, подчинённых вашей воле, только один раз - после призыва отозвать их нельзя. Они ищут всех зомби в широком радиусе и жалят их; ужаленный зомби переходит на вашу сторону, точно так же, как от Камня духов или Свитка девиации. Одновременно может быть активен только один рой. Расходуется при использовании.","İradene bağlı bir böcek sürüsünü tek seferliğine salan bir nekromansi tomarı - bir kez salındıklarında geri çağrılamazlar. Geniş bir yarıçaptaki her zombiyi bulup sokarlar; sokulan zombi, tıpkı Ruh Taşı ya da Sapma Tomarı'nda olduğu gibi senin tarafına geçer. Aynı anda yalnızca bir sürü etkin olabilir. Kullanıldığında tükenir.",一卷亡灵法术卷轴,释放出一群受你意志束缚的甲虫,仅此一次——一旦放出便无法召回。它们会搜寻大范围内的每一只僵尸并加以叮蜇;被蜇中的僵尸会像受灵魂之石或悖逆卷轴影响那样倒向你这边。同时只能维持一群。使用后消耗。,一卷亡靈法術卷軸,釋放出一群受你意志束縛的甲蟲,僅此一次——一旦放出便無法召回。牠們會搜尋大範圍內的每一隻殭屍並加以叮螫;被螫中的殭屍會像受靈魂之石或悖逆卷軸影響那樣倒向你這邊。同時只能維持一群。使用後消耗。 +bookSummonInsectSwarm,items,Item,,,,Pharaoh's Swarm,,Schwarm des Pharao,Enjambre del faraón,Essaim du pharaon,Sciame del faraone,ファラオの群れ,파라오의 무리,Rój faraona,Enxame do faraó,Рой фараона,Firavun Sürüsü,法老之群,法老之群 +bookSummonInsectSwarmDesc,items,Item,,,,"A necromantic scroll that unleashes a swarm bound to your will, one time only - once cast, they cannot be recalled. They seek out every zombie in a wide radius and sting them; a stung zombie turns to your side, just like the Spirit Stone or Scroll of Deviation. You can only have one swarm active at a time. Consumed on use.",,"Eine nekromantische Schriftrolle, die einen Schwarm, deinem Willen unterworfen entfesselt – ein einziges Mal: einmal beschworen, lassen sie sich nicht mehr zurückrufen. Sie suchen jeden Zombie im weiten Umkreis und stechen ihn; ein gestochener Zombie wechselt auf deine Seite, genau wie beim Geisterstein oder der Schriftrolle der Abweichung. Du kannst immer nur einen Schwarm gleichzeitig haben. Wird beim Gebrauch verbraucht.","Un pergamino nigromántico que desata un enjambre sometidos a tu voluntad, una sola vez: una vez lanzados, no se pueden hacer volver. Buscan a todos los zombis en un amplio radio y los pican; un zombi picado se pone de tu lado, igual que con la Piedra de espíritus o el Pergamino de desviación. Solo puedes tener un enjambre activo a la vez. Se consume al usarlo.","Un parchemin nécromantique qui déchaîne un essaim soumis à ta volonté, une seule fois : une fois lancés, on ne peut plus les rappeler. Ils cherchent tous les zombies dans un large rayon et les piquent ; un zombie piqué passe de ton côté, exactement comme avec la Pierre des esprits ou le Parchemin de déviation. Tu ne peux avoir qu'un seul essaim actif à la fois. Consommé à l'usage.","Una pergamena negromantica che scatena uno sciame soggetti alla tua volontà, una volta sola: una volta lanciati non possono più essere richiamati. Cercano ogni zombie in un ampio raggio e lo pungono; uno zombie punto passa dalla tua parte, proprio come con la Pietra degli spiriti o la Pergamena della deviazione. Puoi avere un solo sciame attivo alla volta. Si consuma con l'uso.",あなたの意志に縛られた群れを解き放つ降霊術の巻物。ただし一度きりで、放ったあとは呼び戻せない。広い範囲のゾンビを探し出して刺し、刺されたゾンビは精霊の石や逸脱の巻物と同じようにあなたの側につく。同時に展開できる群れは一つだけ。使用すると失われる。,"당신의 의지에 묶인 무리를 단 한 번 풀어놓는 강령술 두루마리 - 한 번 풀면 다시 불러들일 수 없습니다. 넓은 범위의 좀비를 모두 찾아내 쏘며, 쏘인 좀비는 영혼의 돌이나 일탈의 두루마리처럼 당신 편이 됩니다. 한 번에 한 무리만 유지할 수 있습니다. 사용하면 소모됩니다.","Nekromancki zwój, który wypuszcza rój związanych twoją wolą – tylko raz: po rzuceniu nie da się ich już odwołać. Wyszukują każde zombie w szerokim promieniu i żądlą je; użądlone zombie przechodzi na twoją stronę, tak samo jak przy Kamieniu duchów czy Zwoju dewiacji. Naraz możesz mieć tylko jeden aktywny rój. Zużywa się przy użyciu.","Um pergaminho necromântico que libera um enxame presos à sua vontade, uma única vez: depois de lançados, não podem ser chamados de volta. Eles procuram cada zumbi num amplo raio e o picam; um zumbi picado passa para o seu lado, igual à Pedra dos espíritos ou ao Pergaminho do Desvio. Você só pode ter um enxame ativo por vez. Consumido ao usar.","Некромантский свиток, высвобождающий рой, подчинённых вашей воле, только один раз - после призыва отозвать их нельзя. Они ищут всех зомби в широком радиусе и жалят их; ужаленный зомби переходит на вашу сторону, точно так же, как от Камня духов или Свитка девиации. Одновременно может быть активен только один рой. Расходуется при использовании.","İradene bağlı bir sürüyü tek seferliğine salan bir nekromansi tomarı - bir kez salındıklarında geri çağrılamazlar. Geniş bir yarıçaptaki her zombiyi bulup sokarlar; sokulan zombi, tıpkı Ruh Taşı ya da Sapma Tomarı'nda olduğu gibi senin tarafına geçer. Aynı anda yalnızca bir sürü etkin olabilir. Kullanıldığında tükenir.",一卷亡灵法术卷轴,释放出一群受你意志束缚的虫豸,仅此一次——一旦放出便无法召回。它们会搜寻大范围内的每一只僵尸并加以叮蜇;被蜇中的僵尸会像受灵魂之石或悖逆卷轴影响那样倒向你这边。同时只能维持一群。使用后消耗。,一卷亡靈法術卷軸,釋放出一群受你意志束縛的蟲豸,僅此一次——一旦放出便無法召回。牠們會搜尋大範圍內的每一隻殭屍並加以叮螫;被螫中的殭屍會像受靈魂之石或悖逆卷軸影響那樣倒向你這邊。同時只能維持一群。使用後消耗。 tinCanEmpty,items,Item,,,,Empty Tin Can,,Leere Blechdose,Lata vacía,Boîte de conserve vide,Lattina vuota,空き缶,빈 깡통,Pusta puszka,Lata vazia,Пустая консервная банка,Boş Teneke Kutu,空罐头,空罐頭 tinCanEmptyDesc,items,Item,,,,An empty tin can. Can be filled with water or used in crafting.,,Eine leere Blechdose. Kann mit Wasser gefüllt oder beim Herstellen verwendet werden.,Una lata vacía. Se puede llenar de agua o usar en fabricación.,Une boîte de conserve vide. Peut être remplie d'eau ou utilisée en fabrication.,Una lattina vuota. Può essere riempita d'acqua o usata per fabbricare.,空の缶。水を入れるか、クラフトに使える。,빈 깡통. 물을 채우거나 제작에 쓸 수 있습니다.,Pusta puszka. Można ją napełnić wodą lub wykorzystać przy wytwarzaniu.,Uma lata vazia. Pode ser enchida com água ou usada na fabricação.,Пустая консервная банка. Можно наполнить водой или использовать в крафте.,Boş bir teneke kutu. Suyla doldurulabilir ya da üretimde kullanılabilir.,一个空罐头。可以装水,也可用于制作。,一個空罐頭。可以裝水,也可用於製作。 tinCanRiverWater,items,Item,,,,Tin Can of Murky Water,,Blechdose mit trübem Wasser,Lata de agua turbia,Boîte de conserve d'eau trouble,Lattina d'acqua torbida,濁った水の缶,탁한 물이 담긴 깡통,Puszka mętnej wody,Lata de água turva,Консервная банка с мутной водой,Bulanık Su Dolu Teneke Kutu,装浑水的罐头,裝濁水的罐頭 @@ -29,7 +29,7 @@ tinCanRiverWaterDesc,items,Item,,,,Murky water collected in a tin can. Drinking tinCanBoiledWater,items,Item,,,,Tin Can of Boiled Water,,Blechdose mit abgekochtem Wasser,Lata de agua hervida,Boîte de conserve d'eau bouillie,Lattina d'acqua bollita,煮沸した水の缶,끓인 물이 담긴 깡통,Puszka przegotowanej wody,Lata de água fervida,Консервная банка с кипячёной водой,Kaynatılmış Su Dolu Teneke Kutu,装开水的罐头,裝開水的罐頭 tinCanBoiledWaterDesc,items,Item,,,,Purified boiled water in a tin can. Safe to drink. Holds less water than glass jars.,,"Gereinigtes, abgekochtes Wasser in einer Blechdose. Sicher zu trinken. Fasst weniger Wasser als ein Einmachglas.",Agua hervida y purificada en una lata. Segura para beber. Contiene menos agua que un tarro de cristal.,De l'eau bouillie et purifiée dans une boîte de conserve. Potable sans risque. Contient moins d'eau qu'un bocal en verre.,Acqua bollita e purificata in una lattina. Si può bere senza rischi. Contiene meno acqua di un barattolo di vetro.,缶に入った煮沸済みの浄化された水。安全に飲める。ガラス瓶より容量が少ない。,깡통에 담긴 끓여서 정화한 물. 안전하게 마실 수 있습니다. 유리병보다 적게 담깁니다.,"Oczyszczona, przegotowana woda w puszce. Można ją bezpiecznie pić. Mieści mniej wody niż szklany słoik.",Água fervida e purificada numa lata. Segura para beber. Contém menos água que um pote de vidro.,"Очищенная кипячёная вода в консервной банке. Безопасна для питья. Вмещает меньше воды, чем стеклянные банки.","Teneke kutuda kaynatılmış, arıtılmış su. İçmek güvenlidir. Cam kavanozdan daha az su alır.",罐头里的煮沸净水。可安全饮用。容量比玻璃罐少。,罐頭裡的煮沸淨水。可安全飲用。容量比玻璃罐少。 necroZombieDog,entityclasses,Entity,,,,Zombie Dog,,Zombiehund,Perro Zombi,Chien Zombie,Cane Zombie,ゾンビ犬,좀비 개,Pies Zombie,Cão Zumbi,Зомбособака,Zombi Köpek,僵尸犬,殭屍犬 -necroInsectSwarm,entityclasses,Entity,,,,Beetles of the Lord,,Käfer des Herrn,Escarabajos del Señor,Scarabées du Seigneur,Scarabei del Signore,主の甲虫,군주의 딱정벌레,Żuki Władcy,Besouros do Senhor,Жуки Властелина,Efendi'nin Böcekleri,主宰之甲虫,主宰之甲蟲 +necroInsectSwarm,entityclasses,Entity,,,,Pharaoh's Swarm,,Schwarm des Pharao,Enjambre del faraón,Essaim du pharaon,Sciame del faraone,ファラオの群れ,파라오의 무리,Rój faraona,Enxame do faraó,Рой фараона,Firavun Sürüsü,法老之群,法老之群 necroWpnBladeNecroKnife,items,Item,,,,Necromancer's Knife,,Messer des Nekromanten,Cuchillo del nigromante,Couteau du nécromancien,Coltello del negromante,ネクロマンサーのナイフ,강령술사의 단검,Nóż nekromanty,Faca do necromante,Нож некроманта,Nekromantın Bıçağı,亡灵法师之刃,亡靈法師之刃 necroWpnBladeNecroKnifeDesc,items,Item,,,,"A blackened bone knife bound to your necromantic power. Its damage scales with your Necromancy skill - worthless in untrained hands, deadly in an experienced necromancer's. Its ordinary swing heals you for half the damage it deals, and any zombie it wounds is marked as a Victim, guaranteeing a special bag of loot when it dies. Its power attack deals no extra damage and returns nothing to you - instead it always knocks its target off its feet, and takes 5 health from you as the price.",,"Ein geschwärztes Knochenmesser, an deine nekromantische Macht gebunden. Sein Schaden wächst mit deiner Nekromantie-Fertigkeit – in ungeübten Händen wertlos, in denen eines erfahrenen Nekromanten tödlich. Der normale Hieb heilt dich um die Hälfte des angerichteten Schadens, und jeder verwundete Zombie wird als Opfer markiert, was nach seinem Tod einen besonderen Beutebeutel garantiert. Der Powerangriff richtet keinen zusätzlichen Schaden an und gibt dir nichts zurück – stattdessen reißt er sein Ziel immer von den Beinen und nimmt dir dafür 5 Lebenspunkte.","Un cuchillo de hueso ennegrecido, ligado a tu poder nigromántico. Su daño crece con tu habilidad de Nigromancia: inútil en manos inexpertas, letal en las de un nigromante veterano. Su golpe normal te cura la mitad del daño infligido, y todo zombi al que hiera queda marcado como Víctima, lo que garantiza una bolsa de botín especial cuando muera. Su ataque potente no hace daño adicional ni te devuelve nada: en cambio, siempre derriba a su objetivo y te cobra 5 puntos de salud.","Un couteau en os noirci, lié à ton pouvoir nécromantique. Ses dégâts croissent avec ta compétence de Nécromancie : dérisoire en main inexpérimentée, mortel dans celle d'un nécromancien aguerri. Son coup normal te soigne de la moitié des dégâts infligés, et tout zombie qu'il blesse est marqué comme Victime, ce qui garantit un sac de butin spécial à sa mort. Son attaque puissante n'inflige aucun dégât supplémentaire et ne te rend rien : en revanche, elle met toujours sa cible à terre et te prend 5 points de vie en paiement.","Un coltello d'osso annerito, legato al tuo potere negromantico. Il suo danno cresce con la tua abilità di Negromanzia: inutile in mani inesperte, letale in quelle di un negromante navigato. Il colpo normale ti cura per metà del danno inflitto, e ogni zombie che ferisce viene marchiato come Vittima, garantendo un sacco di bottino speciale alla sua morte. L'attacco potente non infligge danno extra e non ti restituisce nulla: in compenso atterra sempre il bersaglio e ti costa 5 punti salute.",あなたの降霊術の力に結びついた、黒ずんだ骨のナイフ。その威力は降霊術スキルとともに伸びる。未熟な手では役に立たず、熟練したネクロマンサーの手では致命的だ。通常の一撃は与えたダメージの半分をあなたに回復させ、傷を負ったゾンビは「犠牲者」として刻まれ、死んだときに特別な戦利品袋を必ず落とす。強攻撃は追加ダメージを与えず、あなたに何も返さない。その代わり、必ず相手を打ち倒し、代償としてあなたの体力を5奪う。,"당신의 강령술 힘에 묶인 검게 그을린 뼈 단검. 위력은 강령술 기술에 따라 커집니다. 미숙한 손에서는 쓸모없고, 숙련된 강령술사의 손에서는 치명적입니다. 평범한 일격은 입힌 피해의 절반만큼 당신을 치유하고, 상처 입은 좀비는 '희생자'로 표시되어 죽을 때 특별한 전리품 자루를 반드시 남깁니다. 강공격은 추가 피해를 주지 않고 당신에게 아무것도 돌려주지 않습니다. 대신 언제나 대상을 넘어뜨리며, 그 대가로 당신의 체력 5를 가져갑니다.","Poczerniały kościany nóż związany z twoją nekromancką mocą. Jego obrażenia rosną wraz z umiejętnością Nekromancji – bezużyteczny w niewprawnych rękach, śmiercionośny w dłoni doświadczonego nekromanty. Zwykłe cięcie leczy cię o połowę zadanych obrażeń, a każde zranione zombie zostaje oznaczone jako Ofiara, co gwarantuje specjalny worek łupu po jego śmierci. Atak specjalny nie zadaje dodatkowych obrażeń i niczego ci nie zwraca – za to zawsze zwala cel z nóg, a w zamian zabiera ci 5 punktów zdrowia.","Uma faca de osso enegrecida, ligada ao seu poder necromântico. Seu dano cresce com a habilidade de Necromancia: inútil em mãos inexperientes, letal nas de um necromante experiente. O golpe comum cura você em metade do dano causado, e todo zumbi ferido é marcado como Vítima, garantindo um saco de espólio especial quando morrer. O ataque forte não causa dano extra e não devolve nada a você: em vez disso, sempre derruba o alvo e cobra 5 pontos da sua vida.","Почерневший костяной нож, подчинённый вашей некромантской силе. Его урон растёт вместе с навыком Некромантии - бесполезен в неопытных руках, смертоносен в руках опытного некроманта. Обычный удар лечит вас на половину нанесённого урона, а любой раненый им зомби помечен как Жертва, что гарантирует особый мешок с добычей после его смерти. Силовая атака не бьёт сильнее и ничего вам не возвращает - вместо этого она всегда сбивает цель с ног, забирая у вас 5 единиц здоровья как плату.","Nekromansi gücüne bağlanmış, kararmış bir kemik bıçak. Verdiği hasar Nekromansi yeteneğinle birlikte büyür: acemi ellerde işe yaramaz, deneyimli bir nekromantın elinde ölümcül. Sıradan darbesi verdiği hasarın yarısı kadar seni iyileştirir ve yaraladığı her zombi Kurban olarak işaretlenir; öldüğünde özel bir ganimet çantası bırakacağı garanti olur. Güçlü saldırı ek hasar vermez ve sana hiçbir şey döndürmez; bunun yerine hedefi daima yere serer ve bedel olarak senden 5 can alır.",一把与你的亡灵之力相连的漆黑骨刃。其伤害随亡灵法术技能增长——在生手手中一文不值,在老练的亡灵法师手中则致命。普通挥击可为你回复所造成伤害的一半,被它所伤的僵尸都会被标记为「祭品」,死亡时必定掉落特殊的战利品袋。强力攻击不会造成额外伤害,也不会回报给你任何东西——但它必定将目标掀翻在地,并以你 5 点生命为代价。,一把與你的亡靈之力相連的漆黑骨刃。其傷害隨亡靈法術技能增長——在生手手中一文不值,在老練的亡靈法師手中則致命。普通揮擊可為你回復所造成傷害的一半,被它所傷的殭屍都會被標記為「祭品」,死亡時必定掉落特殊的戰利品袋。強力攻擊不會造成額外傷害,也不會回報給你任何東西——但它必定將目標掀翻在地,並以你 5 點生命為代價。 resourceVictimSkin,items,Item,,,,Victim's Skin,,Haut des Opfers,Piel de la víctima,Peau de la victime,Pelle della vittima,犠牲者の皮,희생자의 가죽,Skóra ofiary,Pele da vítima,Кожа жертвы,Kurbanın Derisi,祭品之皮,祭品之皮 @@ -43,11 +43,11 @@ resourceBloodSphereDesc,items,Item,,,,"Necromancer's blood thickened and sealed resourceBloodStone,items,Item,,,,Blood Stone,,Blutstein,Piedra de sangre,Pierre de sang,Pietra di sangue,血の石,피의 돌,Krwawy kamień,Pedra de sangue,Кровавый камень,Kan Taşı,血石,血石 resourceBloodStoneDesc,items,Item,,,,Necromancer's blood hardened into stone. An inexhaustible charge for the Spatial Bracelet: pulling blocks costs it nothing at all.,,Zu Stein erstarrtes Nekromantenblut. Eine unerschöpfliche Ladung für das Raumarmband: das Einziehen von Blöcken kostet sie überhaupt nichts.,Sangre del nigromante endurecida hasta volverse piedra. Una carga inagotable para el Brazalete espacial: absorber bloques no le cuesta nada.,Du sang de nécromancien durci en pierre. Une charge inépuisable pour le Bracelet spatial : aspirer des blocs ne lui coûte rien.,Sangue del negromante indurito in pietra. Una carica inesauribile per il Bracciale spaziale: assorbire blocchi non le costa nulla.,ネクロマンサーの血が固まり石となったもの。「空間の腕輪」の尽きることのない充填であり、ブロックの吸収では何も消耗しない。,"강령술사의 피가 굳어 돌이 된 것. '공간의 팔찌'의 마르지 않는 충전체로, 블록을 흡수해도 전혀 소모되지 않습니다.",Krew nekromanty zastygła w kamień. Niewyczerpany ładunek do Bransolety przestrzennej: wciąganie bloków nic go nie kosztuje.,Sangue do necromante endurecido em pedra. Uma carga inesgotável para o Bracelete espacial: absorver blocos não lhe custa nada.,"Кровь некроманта, затвердевшая камнем. Неиссякаемый заряд для Пространственного браслета: поглощение блоков не тратит его вовсе.",Taşa dönüşmüş nekromant kanı. Uzam Bileziği için tükenmez bir yük: blok çekmek onu hiç eksiltmez.,亡灵法师之血凝固成石。空间手环永不枯竭的充能:吸收方块不会消耗它分毫。,亡靈法師之血凝固成石。空間手環永不枯竭的充能:吸收方塊不會消耗它分毫。 resourceNecromancerBloodNeedsKnife,items,Item,,,,You need a knife to perform this ritual,,Für dieses Ritual brauchst du ein Messer,Necesitas un cuchillo para realizar este ritual,Il te faut un couteau pour accomplir ce rituel,Ti serve un coltello per compiere questo rituale,この儀式にはナイフが必要です,이 의식을 치르려면 칼이 필요합니다,Do tego rytuału potrzebujesz noża,Você precisa de uma faca para realizar este ritual,Для этого ритуала нужен нож,Bu ritüeli gerçekleştirmek için bir bıçağa ihtiyacın var,进行此仪式需要一把刀,進行此儀式需要一把刀 -bookSummonZombieGriffin,items,Item,,,,Summon Zombie Griffin,,Zombiegreif beschwören,Invocar Grifo Zombi,Invoquer un Griffon Zombie,Evoca Grifone Zombie,ゾンビグリフォン召喚,좀비 그리핀 소환,Przyzwanie: Gryf Zombie,Invocar Grifo Zumbi,Призыв зомбогрифа,Zombi Grifin Çağırma,召唤僵尸狮鹫,召喚殭屍獅鷲 -bookSummonZombieGriffinDesc,items,Item,,,,A necromantic tome that raises a zombie griffin bound to your will. It fights alongside you against other zombies until it dies. You can only have one bound at a time. Power attack to recall it back into the book.,,"Ein nekromantisches Buch, das einen Zombiegreifen erweckt, der deinem Willen unterworfen ist. Er kämpft an deiner Seite gegen andere Zombies, bis er stirbt. Du kannst immer nur einen gebunden haben. Mit dem Powerangriff rufst du ihn ins Buch zurück.",Un tomo nigromántico que alza un Grifo Zombi sometido a tu voluntad. Lucha a tu lado contra otros zombis hasta morir. Solo puedes tener uno vinculado a la vez. Usa el ataque potente para devolverlo al libro.,Un ouvrage nécromantique qui relève un Griffon Zombie soumis à ta volonté. Il combat à tes côtés contre les autres zombies jusqu'à sa mort. Tu ne peux en lier qu'un seul à la fois. L'attaque puissante le rappelle dans le livre.,Un tomo negromantico che rialza un Grifone Zombie soggetto alla tua volontà. Combatte al tuo fianco contro gli altri zombie finché non muore. Puoi averne vincolato solo uno alla volta. L'attacco potente lo richiama nel libro.,あなたの意志に縛られたゾンビグリフォンを起こす降霊術の書。死ぬまで、あなたの側で他のゾンビと戦う。同時に縛っておけるのは一体だけ。強攻撃で書物へ呼び戻せる。,당신의 의지에 묶인 좀비 그리핀을(를) 일으키는 강령술 서적. 죽을 때까지 당신 곁에서 다른 좀비와 싸운다. 한 번에 하나만 묶어 둘 수 있다. 강공격으로 책 속에 불러들인다.,"Nekromancki tom, który wskrzesza Gryfa Zombie związanego twoją wolą. Walczy u twojego boku z innymi zombie, aż zginie. Naraz możesz mieć związanego tylko jednego. Atak specjalny odwołuje go z powrotem do księgi.",Um tomo necromântico que ergue um Grifo Zumbi preso à sua vontade. Luta ao seu lado contra outros zumbis até morrer. Você só pode ter um vinculado por vez. O ataque forte o chama de volta para o livro.,"Некромантский фолиант, поднимающий зомбогрифа, подчинённого вашей воле. Он сражается на вашей стороне против других зомби, пока не погибнет. Одновременно можно держать только одного. Силовая атака отзывает его обратно в книгу.",İradene bağlı bir Zombi Grifin dirilten bir nekromansi kitabı. Ölene kadar yanında diğer zombilere karşı savaşır. Aynı anda yalnızca bir tanesini bağlı tutabilirsin. Güçlü saldırı onu kitaba geri çağırır.,一本亡灵法术典籍,唤起一只受你意志束缚的僵尸狮鹫。它会在你身边与其他僵尸战斗,直到死亡。同时只能束缚一只。使用强力攻击可将其召回书中。,一本亡靈法術典籍,喚起一隻受你意志束縛的殭屍獅鷲。牠會在你身邊與其他殭屍戰鬥,直到死亡。同時只能束縛一隻。使用強力攻擊可將其召回書中。 -necroZombieGriffinAlreadyActive,items,Item,,,,You already have a Zombie Griffin - use the power attack to recall it,,Du hast bereits einen Zombiegreifen – rufe ihn mit dem Powerangriff zurück,Ya tienes un Grifo Zombi: usa el ataque potente para devolverlo,Tu as déjà un Griffon Zombie – utilise l'attaque puissante pour le rappeler,Hai già un Grifone Zombie: usa l'attacco potente per richiamarlo,すでにゾンビグリフォンがいます – 強攻撃で呼び戻してください,이미 좀비 그리핀이(가) 있습니다 - 강공격으로 불러들이세요,Masz już Gryfa Zombie – odwołaj go atakiem specjalnym,Você já tem um Grifo Zumbi - use o ataque forte para chamá-lo de volta,У вас уже есть зомбогриф - отзовите его силовой атакой,Zaten bir Zombi Grifin var - geri çağırmak için güçlü saldırıyı kullan,你已经有一只僵尸狮鹫了——用强力攻击将其召回,你已經有一隻殭屍獅鷲了——用強力攻擊將其召回 -necroZombieGriffinRecalled,items,Item,,,,Zombie Griffin recalled,,Zombiegreif zurückgerufen,Grifo Zombi devuelto,Griffon Zombie rappelé,Grifone Zombie richiamato,ゾンビグリフォンを呼び戻しました,좀비 그리핀을(를) 불러들였습니다,Odwołano: Gryf Zombie,Grifo Zumbi chamado de volta,Зомбогриф отозван,Zombi Grifin geri çağrıldı,已召回僵尸狮鹫,已召回殭屍獅鷲 -necroZombieGriffinNothingToRecall,items,Item,,,,No Zombie Griffin to recall,,Kein Zombiegreif zum Zurückrufen vorhanden,No hay ningún Grifo Zombi que devolver,Aucun Griffon Zombie à rappeler,Nessun Grifone Zombie da richiamare,呼び戻せるゾンビグリフォンがいません,불러들일 좀비 그리핀이(가) 없습니다,Brak Gryfa Zombie do odwołania,Não há nenhum Grifo Zumbi para chamar de volta,Нет зомбогрифа для отзыва,Geri çağrılacak bir Zombi Grifin yok,没有可召回的僵尸狮鹫,沒有可召回的殭屍獅鷲 +bookSummonZombieGriffin,items,Item,,,,Summon Grave Vulture,,Grabgeier beschwören,Invocar buitre sepulcral,Invoquer le vautour des tombes,Evoca avvoltoio sepolcrale,墓場のハゲワシの召喚,무덤 독수리 소환,Przyzwanie grobowego sępa,Invocar abutre sepulcral,Призыв могильного стервятника,Mezar Akbabası Çağırma,召唤墓地秃鹫,召喚墓地禿鷲 +bookSummonZombieGriffinDesc,items,Item,,,,A necromantic tome that raises a grave vulture bound to your will. It fights alongside you against other zombies until it dies. You can only have one bound at a time. Power attack to recall it back into the book.,,"Ein nekromantisches Buch, das einen Grabgeier erweckt, der deinem Willen unterworfen ist. Er kämpft an deiner Seite gegen andere Zombies, bis er stirbt. Du kannst immer nur einen gebunden haben. Mit dem Powerangriff rufst du ihn ins Buch zurück.",Un tomo nigromántico que alza un Buitre Sepulcral sometido a tu voluntad. Lucha a tu lado contra otros zombis hasta morir. Solo puedes tener uno vinculado a la vez. Usa el ataque potente para devolverlo al libro.,Un ouvrage nécromantique qui relève un Vautour des Tombes soumis à ta volonté. Il combat à tes côtés contre les autres zombies jusqu'à sa mort. Tu ne peux en lier qu'un seul à la fois. L'attaque puissante le rappelle dans le livre.,Un tomo negromantico che rialza un Avvoltoio Sepolcrale soggetto alla tua volontà. Combatte al tuo fianco contro gli altri zombie finché non muore. Puoi averne vincolato solo uno alla volta. L'attacco potente lo richiama nel libro.,あなたの意志に縛られた墓場のハゲワシを起こす降霊術の書。死ぬまで、あなたの側で他のゾンビと戦う。同時に縛っておけるのは一体だけ。強攻撃で書物へ呼び戻せる。,당신의 의지에 묶인 무덤 독수리을(를) 일으키는 강령술 서적. 죽을 때까지 당신 곁에서 다른 좀비와 싸운다. 한 번에 하나만 묶어 둘 수 있다. 강공격으로 책 속에 불러들인다.,"Nekromancki tom, który wskrzesza Grobowego Sępa związanego twoją wolą. Walczy u twojego boku z innymi zombie, aż zginie. Naraz możesz mieć związanego tylko jednego. Atak specjalny odwołuje go z powrotem do księgi.",Um tomo necromântico que ergue um Abutre Sepulcral preso à sua vontade. Luta ao seu lado contra outros zumbis até morrer. Você só pode ter um vinculado por vez. O ataque forte o chama de volta para o livro.,"Некромантский фолиант, поднимающий могильного стервятника, подчинённого вашей воле. Он сражается на вашей стороне против других зомби, пока не погибнет. Одновременно можно держать только одного. Силовая атака отзывает его обратно в книгу.",İradene bağlı bir Mezar Akbabası dirilten bir nekromansi kitabı. Ölene kadar yanında diğer zombilere karşı savaşır. Aynı anda yalnızca bir tanesini bağlı tutabilirsin. Güçlü saldırı onu kitaba geri çağırır.,一本亡灵法术典籍,唤起一只受你意志束缚的墓地秃鹫。它会在你身边与其他僵尸战斗,直到死亡。同时只能束缚一只。使用强力攻击可将其召回书中。,一本亡靈法術典籍,喚起一隻受你意志束縛的墓地禿鷲。牠會在你身邊與其他殭屍戰鬥,直到死亡。同時只能束縛一隻。使用強力攻擊可將其召回書中。 +necroZombieGriffinAlreadyActive,items,Item,,,,You already have a Grave Vulture - use the power attack to recall it,,Du hast bereits einen Grabgeier - ruf ihn mit dem Machtangriff zurück,Ya tienes un buitre sepulcral: usa el ataque fuerte para devolverlo,Tu as déjà un vautour des tombes - rappelle-le avec l’attaque puissante,Hai già un avvoltoio sepolcrale: richiamalo con l’attacco potente,すでに墓場のハゲワシがいます - パワーアタックで呼び戻してください,이미 무덤 독수리가 있습니다 - 강공격으로 불러들이세요,Masz już grobowego sępa - odwołaj go silnym atakiem,Você já tem um abutre sepulcral - use o ataque forte para chamá-lo de volta,У вас уже есть могильный стервятник - отзовите его силовой атакой,Zaten bir Mezar Akbabası var - güçlü saldırıyla geri çağır,你已经有一只墓地秃鹫了——用重击将它收回,你已經有一隻墓地禿鷲了——用重擊將牠收回 +necroZombieGriffinRecalled,items,Item,,,,Grave Vulture recalled,,Grabgeier zurückgerufen,Buitre sepulcral devuelto,Vautour des tombes rappelé,Avvoltoio sepolcrale richiamato,墓場のハゲワシを呼び戻しました,무덤 독수리를 불러들였습니다,Odwołano: Grobowy sęp,Abutre sepulcral chamado de volta,Могильный стервятник отозван,Mezar Akbabası geri çağrıldı,已召回墓地秃鹫,已召回墓地禿鷲 +necroZombieGriffinNothingToRecall,items,Item,,,,No Grave Vulture to recall,,Kein Grabgeier zum Zurückrufen,No hay buitre sepulcral que devolver,Aucun vautour des tombes à rappeler,Nessun avvoltoio sepolcrale da richiamare,呼び戻す墓場のハゲワシがいません,불러들일 무덤 독수리가 없습니다,Nie ma grobowego sępa do odwołania,Não há abutre sepulcral para chamar de volta,Нет могильного стервятника для отзыва,Geri çağrılacak Mezar Akbabası yok,没有可召回的墓地秃鹫,沒有可召回的墓地禿鷲 bookSummonZombieBear,items,Item,,,,Summon Zombie Bear,,Zombiebär beschwören,Invocar Oso Zombi,Invoquer un Ours Zombie,Evoca Orso Zombie,ゾンビ熊召喚,좀비 곰 소환,Przyzwanie: Niedźwiedź Zombie,Invocar Urso Zumbi,Призыв зомбомедведя,Zombi Ayı Çağırma,召唤僵尸熊,召喚殭屍熊 bookSummonZombieBearDesc,items,Item,,,,A necromantic tome that raises a zombie bear bound to your will. It fights alongside you against other zombies until it dies. You can only have one bound at a time. Power attack to recall it back into the book.,,"Ein nekromantisches Buch, das einen Zombiebären erweckt, der deinem Willen unterworfen ist. Er kämpft an deiner Seite gegen andere Zombies, bis er stirbt. Du kannst immer nur einen gebunden haben. Mit dem Powerangriff rufst du ihn ins Buch zurück.",Un tomo nigromántico que alza un Oso Zombi sometido a tu voluntad. Lucha a tu lado contra otros zombis hasta morir. Solo puedes tener uno vinculado a la vez. Usa el ataque potente para devolverlo al libro.,Un ouvrage nécromantique qui relève un Ours Zombie soumis à ta volonté. Il combat à tes côtés contre les autres zombies jusqu'à sa mort. Tu ne peux en lier qu'un seul à la fois. L'attaque puissante le rappelle dans le livre.,Un tomo negromantico che rialza un Orso Zombie soggetto alla tua volontà. Combatte al tuo fianco contro gli altri zombie finché non muore. Puoi averne vincolato solo uno alla volta. L'attacco potente lo richiama nel libro.,あなたの意志に縛られたゾンビ熊を起こす降霊術の書。死ぬまで、あなたの側で他のゾンビと戦う。同時に縛っておけるのは一体だけ。強攻撃で書物へ呼び戻せる。,당신의 의지에 묶인 좀비 곰을(를) 일으키는 강령술 서적. 죽을 때까지 당신 곁에서 다른 좀비와 싸운다. 한 번에 하나만 묶어 둘 수 있다. 강공격으로 책 속에 불러들인다.,"Nekromancki tom, który wskrzesza Niedźwiedzia Zombie związanego twoją wolą. Walczy u twojego boku z innymi zombie, aż zginie. Naraz możesz mieć związanego tylko jednego. Atak specjalny odwołuje go z powrotem do księgi.",Um tomo necromântico que ergue um Urso Zumbi preso à sua vontade. Luta ao seu lado contra outros zumbis até morrer. Você só pode ter um vinculado por vez. O ataque forte o chama de volta para o livro.,"Некромантский фолиант, поднимающий зомбомедведя, подчинённого вашей воле. Он сражается на вашей стороне против других зомби, пока не погибнет. Одновременно можно держать только одного. Силовая атака отзывает его обратно в книгу.",İradene bağlı bir Zombi Ayı dirilten bir nekromansi kitabı. Ölene kadar yanında diğer zombilere karşı savaşır. Aynı anda yalnızca bir tanesini bağlı tutabilirsin. Güçlü saldırı onu kitaba geri çağırır.,一本亡灵法术典籍,唤起一只受你意志束缚的僵尸熊。它会在你身边与其他僵尸战斗,直到死亡。同时只能束缚一只。使用强力攻击可将其召回书中。,一本亡靈法術典籍,喚起一隻受你意志束縛的殭屍熊。牠會在你身邊與其他殭屍戰鬥,直到死亡。同時只能束縛一隻。使用強力攻擊可將其召回書中。 necroZombieBearAlreadyActive,items,Item,,,,You already have a Zombie Bear - use the power attack to recall it,,Du hast bereits einen Zombiebären – rufe ihn mit dem Powerangriff zurück,Ya tienes un Oso Zombi: usa el ataque potente para devolverlo,Tu as déjà un Ours Zombie – utilise l'attaque puissante pour le rappeler,Hai già un Orso Zombie: usa l'attacco potente per richiamarlo,すでにゾンビ熊がいます – 強攻撃で呼び戻してください,이미 좀비 곰이(가) 있습니다 - 강공격으로 불러들이세요,Masz już Niedźwiedzia Zombie – odwołaj go atakiem specjalnym,Você já tem um Urso Zumbi - use o ataque forte para chamá-lo de volta,У вас уже есть зомбомедведь - отзовите его силовой атакой,Zaten bir Zombi Ayı var - geri çağırmak için güçlü saldırıyı kullan,你已经有一只僵尸熊了——用强力攻击将其召回,你已經有一隻殭屍熊了——用強力攻擊將其召回 @@ -70,7 +70,7 @@ thrownStonePortalBlackChanneling,items,Item,,,,Channeling...,,Wird gewirkt ...,C bookBanshee,items,Item,,,,Banshee's Scroll,,Schriftrolle der Banshee,Pergamino de la banshee,Parchemin de la banshee,Pergamena della banshee,バンシーの巻物,밴시의 두루마리,Zwój banshee,Pergaminho da banshee,Свиток банши,Banshee Tomarı,报丧女妖卷轴,報喪女妖卷軸 bookBansheeDesc,items,Item,,,,"A necromantic scroll that screams with a banshee's voice when opened, consuming itself and summoning a small hostile horde of zombies near you. Not a summoned ally - these zombies are as dangerous to you as any other. Consumed on use.",,"Eine nekromantische Schriftrolle, die beim Öffnen mit der Stimme einer Banshee schreit, sich dabei selbst verzehrt und in deiner Nähe eine kleine feindliche Zombiehorde herbeiruft. Keine verbündete Beschwörung – diese Zombies sind für dich so gefährlich wie alle anderen. Wird beim Gebrauch verbraucht.","Un pergamino nigromántico que grita con voz de banshee al abrirse, se consume a sí mismo e invoca una pequeña horda hostil de zombis cerca de ti. No son aliados invocados: estos zombis son tan peligrosos para ti como cualquier otro. Se consume al usarlo.","Un parchemin nécromantique qui hurle d'une voix de banshee quand on l'ouvre, se consume et invoque une petite horde de zombies hostiles près de toi. Ce ne sont pas des alliés invoqués : ces zombies sont aussi dangereux pour toi que n'importe quels autres. Consommé à l'usage.","Una pergamena negromantica che, una volta aperta, urla con la voce di una banshee, si consuma da sé ed evoca una piccola orda ostile di zombie vicino a te. Non sono alleati evocati: questi zombie sono pericolosi per te quanto tutti gli altri. Si consuma con l'uso.",開くとバンシーの声で叫び、自らを焼き尽くしながら、あなたの近くに小規模な敵対ゾンビの群れを呼び寄せる降霊術の巻物。召喚された味方ではない。これらのゾンビは他のゾンビと同じくあなたにとって危険だ。使用すると失われる。,"펼치면 밴시의 목소리로 비명을 지르며 스스로를 태워, 당신 근처에 적대적인 소규모 좀비 무리를 불러내는 강령술 두루마리. 소환된 아군이 아닙니다. 이 좀비들은 다른 좀비와 똑같이 당신에게 위험합니다. 사용하면 소모됩니다.","Nekromancki zwój, który po otwarciu krzyczy głosem banshee, sam się spala i przyzywa w twoim pobliżu niewielką wrogą hordę zombie. To nie są przyzwani sojusznicy – te zombie są dla ciebie tak samo groźne jak wszystkie inne. Zużywa się przy użyciu.","Um pergaminho necromântico que grita com a voz de uma banshee ao ser aberto, consumindo a si mesmo e invocando uma pequena horda hostil de zumbis perto de você. Não são aliados invocados: esses zumbis são tão perigosos para você quanto quaisquer outros. Consumido ao usar.","Некромантский свиток, вопящий голосом банши при открытии - расходуется при использовании и призывает небольшую враждебную орду зомби рядом с вами. Это не союзники - эти зомби так же опасны для вас, как и любые другие.","Açıldığında bir banshee sesiyle çığlık atan, kendini tüketen ve yakınında küçük bir düşman zombi sürüsü çağıran bir nekromansi tomarı. Bunlar çağrılmış müttefikler değildir; bu zombiler senin için diğerleri kadar tehlikelidir. Kullanıldığında tükenir.",一卷亡灵法术卷轴,展开时会以报丧女妖之声尖啸,随即自我焚尽,并在你附近召来一小群敌对僵尸。它们不是召唤来的盟友——这些僵尸对你和其他僵尸一样危险。使用后消耗。,一卷亡靈法術卷軸,展開時會以報喪女妖之聲尖嘯,隨即自我焚盡,並在你附近召來一小群敵對殭屍。牠們不是召喚來的盟友——這些殭屍對你和其他殭屍一樣危險。使用後消耗。 thrownStonePortalBlackDesc,items,Item,,,,A stone humming with a far darker necromantic energy than its blue cousin. Use it and a ten-second channel begins; at the end you confirm you are ready to go through with it. What waits on the other side is for you to find out. The stone does not survive the crossing.,,"Ein Stein, der von einer weit dunkleren nekromantischen Energie summt als sein blauer Verwandter. Benutze ihn, und ein zehn Sekunden langes Wirken beginnt; am Ende bestätigst du, dass du es wirklich durchziehen willst. Was auf der anderen Seite wartet, wirst du selbst herausfinden. Der Stein übersteht den Übergang nicht.",Una piedra que zumba con una energía nigromántica mucho más oscura que la de su prima azul. Úsala y comenzará una canalización de diez segundos; al final confirmas que estás dispuesto a llevarlo hasta el final. Lo que aguarda al otro lado lo descubrirás tú mismo. La piedra no sobrevive al cruce.,"Une pierre qui bourdonne d'une énergie nécromantique bien plus sombre que celle de sa cousine bleue. Utilise-la et une canalisation de dix secondes commence ; au terme, tu confirmes que tu es prêt à aller jusqu'au bout. Ce qui attend de l'autre côté, tu le découvriras toi-même. La pierre ne survit pas à la traversée.",Una pietra che ronza di un'energia negromantica assai più oscura di quella della sua cugina blu. Usala e comincia un incanalamento di dieci secondi; al termine confermi di voler andare fino in fondo. Cosa attenda dall'altra parte lo scoprirai da solo. La pietra non sopravvive alla traversata.,青の門石よりはるかに暗い降霊術のエネルギーで唸る石。使用すると10秒の詠唱が始まり、終わったところで本当に踏み切るかを確認する。向こう側に何が待っているかは、自分の目で確かめることになる。この石は渡りきることができない。,"푸른 관문석보다 훨씬 어두운 강령술의 기운으로 울리는 돌. 사용하면 10초의 시전이 시작되고, 끝나면 정말로 끝까지 갈 것인지 확인하게 됩니다. 저편에 무엇이 기다리는지는 직접 확인하게 됩니다. 돌은 그 건넘을 견디지 못합니다.","Kamień buczący nekromancką energią o wiele mroczniejszą niż jego niebieski kuzyn. Użyj go, a rozpocznie się dziesięciosekundowe rzucanie; na koniec potwierdzasz, że naprawdę chcesz to zrobić. Co czeka po drugiej stronie, przekonasz się sam. Kamień nie przeżywa tego przejścia.","Uma pedra que zumbe com uma energia necromântica muito mais sombria que a de sua prima azul. Use-a e começa uma canalização de dez segundos; ao fim, você confirma que está disposto a ir até o fim. O que aguarda do outro lado você mesmo vai descobrir. A pedra não sobrevive à travessia.","Камень, гудящий гораздо более тёмной некромантской энергией, чем его синий собрат. Примените его — начнётся десятисекундное применение, в конце которого нужно подтвердить, что вы готовы идти до конца. Что ждёт на той стороне — увидите сами. Камень этого перехода не переживает.",Mavi akrabasından çok daha karanlık bir nekromansi enerjisiyle uğuldayan bir taş. Kullandığında on saniyelik bir yönlendirme başlar; sonunda sonuna kadar gitmeye hazır olduğunu onaylarsın. Öteki tarafta neyin beklediğini kendin göreceksin. Taş bu geçişten sağ çıkmaz.,一块嗡鸣着远比蓝色同类更幽暗的亡灵能量的石头。使用后开始十秒引导,结束时需确认你真要走到底。彼岸等着什么,要你自己去看。这块石头撑不过这一次穿越。,一塊嗡鳴著遠比藍色同類更幽暗的亡靈能量的石頭。使用後開始十秒引導,結束時需確認你真要走到底。彼岸等著什麼,要你自己去看。這塊石頭撐不過這一次穿越。 -necroZombieGriffin,entityclasses,Entity,,,,Zombie Griffin,,Zombiegreif,Grifo Zombi,Griffon Zombie,Grifone Zombie,ゾンビグリフォン,좀비 그리핀,Gryf Zombie,Grifo Zumbi,Зомбогриф,Zombi Grifin,僵尸狮鹫,殭屍獅鷲 +necroZombieGriffin,entityclasses,Entity,,,,Grave Vulture,,Grabgeier,Buitre sepulcral,Vautour des tombes,Avvoltoio sepolcrale,墓場のハゲワシ,무덤 독수리,Grobowy sęp,Abutre sepulcral,Могильный стервятник,Mezar Akbabası,墓地秃鹫,墓地禿鷲 necroZombieBear,entityclasses,Entity,,,,Zombie Bear,,Zombiebär,Oso Zombi,Ours Zombie,Orso Zombie,ゾンビ熊,좀비 곰,Niedźwiedź Zombie,Urso Zumbi,Зомбомедведь,Zombi Ayı,僵尸熊,殭屍熊 necroZombieWolf,entityclasses,Entity,,,,Zombie Wolf,,Zombiewolf,Lobo Zombi,Loup Zombie,Lupo Zombie,ゾンビ狼,좀비 늑대,Wilk Zombie,Lobo Zumbi,Зомбоволк,Zombi Kurt,僵尸狼,殭屍狼 EntityLootContainerVictim,entityclasses,Entity Info,,,,Victim's Bag,,Beutel des Opfers,Bolsa de la víctima,Sac de la victime,Sacco della vittima,犠牲者の袋,희생자의 자루,Worek ofiary,Saco da vítima,Мешок жертвы,Kurbanın Çantası,祭品之袋,祭品之袋 @@ -148,3 +148,15 @@ challengeNecroSpiritStoneDesc,challenges,,,,,"Throw it, and a dead thing stops b challengeNecroPortalBlueTitle,challenges,,,,,My Own G.L.A.D.O.S.,,Meine eigene G.L.A.D.O.S.,Mi propia G.L.A.D.O.S.,Ma propre G.L.A.D.O.S.,La mia G.L.A.D.O.S.,自分だけのG.L.A.D.O.S.,나만의 G.L.A.D.O.S.,Własna G.L.A.D.O.S.,Minha própria G.L.A.D.O.S.,Сам себе G.L.A.D.O.S.,Kendi G.L.A.D.O.S.'um,自己的G.L.A.D.O.S.,自己的G.L.A.D.O.S. challengeNecroPortalBlueShort,challenges,,,,,Craft the Blue Portal Stone,,Stelle den blauen Portalstein her,Fabrica la piedra de portal azul,Fabriquez la pierre de portail bleue,Fabbrica la pietra del portale blu,青の転移石を作る,푸른 관문석을 제작하세요,Wytwórz niebieski kamień portalu,Fabrique a pedra de portal azul,Создайте Синий портальный камень,Mavi Geçit Taşını üret,制作蓝色传送石,製作藍色傳送石 challengeNecroPortalBlueDesc,challenges,,,,,"A hundred and fifty measures of ash, and every one of them was a zombie you marked and put down. That is the price of never having to walk home again.",,"Hundertfünfzig Maß Asche, und jedes davon war ein Zombie, den du gezeichnet und niedergestreckt hast. Das ist der Preis dafür, nie wieder nach Hause laufen zu müssen.","Ciento cincuenta medidas de ceniza, y cada una fue un zombi que marcaste y abatiste. Ese es el precio de no volver a caminar a casa.","Cent cinquante mesures de cendre, et chacune fut un zombie que vous avez marqué et abattu. C'est le prix pour ne plus jamais rentrer à pied.","Centocinquanta misure di cenere, e ognuna era uno zombi che hai marchiato e abbattuto. Questo è il prezzo per non tornare mai più a casa a piedi.",灰は百五十。その一つ一つが、お前が印を刻んで沈めたゾンビだ。二度と歩いて帰らずに済むための代価である。,재 백오십. 그 하나하나가 당신이 표식을 남기고 쓰러뜨린 좀비입니다. 다시는 걸어서 돌아가지 않기 위한 대가입니다.,"Sto pięćdziesiąt miar prochu, a każda z nich była zombie, które oznaczyłeś i położyłeś. Taka jest cena tego, by nigdy więcej nie wracać do domu piechotą.","Cento e cinquenta medidas de cinzas, e cada uma foi um zumbi que você marcou e derrubou. Esse é o preço de nunca mais voltar a pé para casa.","Сто пятьдесят мер праха, и каждая была зомби, которого вы пометили и упокоили. Такова цена того, чтобы больше никогда не идти домой пешком.","Yüz elli ölçek kül ve her biri, işaretleyip yere serdiğin bir zombiydi. Bir daha asla eve yürümek zorunda kalmamanın bedeli budur.",一百五十份骨灰,每一份都是你标记并放倒的行尸。这便是此后再不必徒步归家的代价。,一百五十份骨灰,每一份都是你標記並放倒的行屍。這便是此後再不必徒步歸家的代價。 +necroRatSpirit,entityclasses,Entity,,,,Rat Spirit,,Rattengeist,Espíritu de rata,Esprit de rat,Spirito di ratto,ネズミの霊,쥐의 영혼,Duch szczura,Espírito de rato,Дух крысы,Sıçan Ruhu,鼠灵,鼠靈 +bookSummonRatSpirit,items,Item,,,,Scroll of the Rat Spirit,,Schriftrolle des Rattengeistes,Pergamino del espíritu de rata,Parchemin de l’esprit de rat,Pergamena dello spirito di ratto,ネズミの霊の巻物,쥐의 영혼 두루마리,Zwój ducha szczura,Pergaminho do espírito de rato,Свиток духа крысы,Sıçan Ruhu Tomarı,鼠灵卷轴,鼠靈卷軸 +bookSummonRatSpiritDesc,items,Item,,,,"The first servant a necromancer can hold: a small grey shade that never attacks on its own. It keeps to your right and just behind you, and where a wall stands in its way it passes straight through rather than gnawing - it breaks nothing. Use the scroll again while it is out and the rat goes for whatever your crosshair rests on. Its bite is almost harmless, but it pins the bitten down for a few seconds and leaves them bleeding. Power attack recalls it. The scroll is never used up.",,"Der erste Diener, den ein Nekromant halten kann: ein kleiner grauer Schatten, der von sich aus niemals angreift. Er hält sich rechts hinter dir, und wo eine Wand im Weg steht, geht er einfach hindurch, statt zu nagen - er zerstört nichts. Benutze die Schriftrolle erneut, während er draußen ist, und die Ratte stürzt sich auf das, worauf dein Fadenkreuz ruht. Ihr Biss ist fast harmlos, wirft den Gebissenen aber für einige Sekunden zu Boden und lässt ihn bluten. Der Machtangriff ruft sie zurück. Die Schriftrolle wird nie verbraucht.","El primer sirviente que un nigromante puede tener: una pequeña sombra gris que nunca ataca por su cuenta. Se mantiene a tu derecha y algo por detrás, y donde hay un muro simplemente lo atraviesa en vez de roerlo: no rompe nada. Usa el pergamino otra vez mientras está fuera y la rata se lanzará sobre aquello a lo que apuntes. Su mordisco es casi inofensivo, pero derriba al mordido durante unos segundos y lo deja sangrando. El ataque fuerte la hace volver. El pergamino nunca se consume.","Le premier serviteur qu’un nécromancien puisse tenir : une petite ombre grise qui n’attaque jamais d’elle-même. Elle se tient à ta droite, un peu en arrière, et là où un mur se dresse elle le traverse au lieu de le ronger - elle ne casse rien. Utilise le parchemin à nouveau pendant qu’elle est là et le rat fonce sur ce que vise ton réticule. Sa morsure est presque inoffensive, mais elle cloue au sol pendant quelques secondes et fait saigner. L’attaque puissante le rappelle. Le parchemin ne se consomme jamais.","Il primo servitore che un negromante possa avere: una piccola ombra grigia che non attacca mai di sua iniziativa. Resta alla tua destra e poco dietro di te, e dove c’è un muro lo attraversa invece di rosicchiarlo: non rompe nulla. Usa di nuovo la pergamena mentre è fuori e il ratto si avventa su ciò che inquadri col mirino. Il suo morso è quasi innocuo, ma inchioda a terra per qualche secondo e lascia sanguinare. L’attacco potente lo richiama. La pergamena non si consuma mai.",降霊術師が最初に従えられる僕。自分からは決して襲わない、小さな灰色の影だ。あなたの右斜め後ろに付き従い、壁が立ちふさがればかじらずにそのまま抜ける - 何も壊さない。出ているあいだにもう一度巻物を使えば、ネズミは照準の先にいるものへ飛びかかる。その噛みつきはほとんど無害だが、噛まれた者を数秒のあいだ組み伏せ、出血させる。パワーアタックで呼び戻せる。巻物は消費されない。,"강령술사가 처음으로 거느릴 수 있는 종복입니다. 스스로는 결코 공격하지 않는 작고 잿빛인 그림자로, 당신의 오른쪽 뒤에 붙어 다니며, 벽이 앞을 막으면 갉는 대신 그대로 통과합니다 - 아무것도 부수지 않습니다. 밖에 나와 있을 때 두루마리를 다시 쓰면 쥐는 조준선이 가리키는 대상에게 달려듭니다. 물린 상대는 거의 다치지 않지만 몇 초 동안 쓰러져 있고 출혈합니다. 강공격으로 불러들입니다. 두루마리는 소모되지 않습니다.","Pierwszy sługa, jakiego nekromanta może utrzymać: mały szary cień, który nigdy nie atakuje z własnej woli. Trzyma się po twojej prawej i nieco z tyłu, a gdy na drodze staje ściana, przechodzi przez nią zamiast ją gryźć - niczego nie niszczy. Użyj zwoju ponownie, gdy szczur jest na zewnątrz, a rzuci się na to, na czym spoczywa twój celownik. Jego ugryzienie jest niemal nieszkodliwe, ale przygważdża ugryzionego na kilka sekund i wywołuje krwawienie. Silny atak go odwołuje. Zwój nigdy się nie zużywa.","O primeiro servo que um necromante consegue manter: uma pequena sombra cinzenta que nunca ataca por conta própria. Fica à sua direita e um pouco atrás, e onde há uma parede ele simplesmente a atravessa em vez de roê-la - não quebra nada. Use o pergaminho de novo enquanto ele estiver fora e o rato avança sobre aquilo que sua mira aponta. A mordida é quase inofensiva, mas prende o mordido por alguns segundos e o deixa sangrando. O ataque forte o chama de volta. O pergaminho nunca se gasta.","Первый слуга, которого может держать некромант: маленькая серая тень, которая никогда не нападает сама. Она держится справа и чуть позади вас, а там, где на пути стена, проходит сквозь неё, а не грызёт - она не ломает ничего. Примените свиток ещё раз, пока крыса призвана, и она бросится на того, на ком стоит ваш прицел. Укус её почти безвреден, но укушенный на несколько секунд валится с ног и начинает истекать кровью. Силовая атака отзывает её. Свиток не расходуется.","Bir nekromantın tutabileceği ilk hizmetkâr: kendiliğinden asla saldırmayan, küçük gri bir gölge. Sağında ve biraz arkanda durur; yolunda bir duvar varsa kemirmek yerine içinden geçer - hiçbir şeyi kırmaz. O dışarıdayken tomarı tekrar kullan, sıçan nişangâhının üzerinde durduğu şeyin üstüne atılır. Isırığı neredeyse zararsızdır ama ısırdığını birkaç saniye yere serer ve kanatır. Güçlü saldırı onu geri çağırır. Tomar asla tükenmez.",亡灵法师能驾驭的第一个仆从:一道小小的灰影,从不主动攻击。它跟在你右后方,遇到墙壁便径直穿过,而不去啃咬——它什么也不会破坏。它在外时再次使用卷轴,鼠灵便会扑向你准星所指之物。它的撕咬几乎无害,却能将被咬者按在地上数秒并使其流血。重击可将其收回。卷轴永不消耗。,亡靈法師能駕馭的第一個僕從:一道小小的灰影,從不主動攻擊。牠跟在你右後方,遇到牆壁便逕直穿過,而不去啃咬——牠什麼也不會破壞。牠在外時再次使用卷軸,鼠靈便會撲向你準星所指之物。牠的撕咬幾乎無害,卻能將被咬者按在地上數秒並使其流血。重擊可將其收回。卷軸永不消耗。 +necroRatSpiritRecalled,items,Item,,,,Rat Spirit recalled,,Rattengeist zurückgerufen,Espíritu de rata devuelto,Esprit de rat rappelé,Spirito di ratto richiamato,ネズミの霊を呼び戻しました,쥐의 영혼을 불러들였습니다,Odwołano: Duch szczura,Espírito de rato chamado de volta,Дух крысы отозван,Sıçan Ruhu geri çağrıldı,已召回鼠灵,已召回鼠靈 +necroRatSpiritNothingToRecall,items,Item,,,,No Rat Spirit to recall,,Kein Rattengeist zum Zurückrufen,No hay espíritu de rata que devolver,Aucun esprit de rat à rappeler,Nessuno spirito di ratto da richiamare,呼び戻すネズミの霊がいません,불러들일 쥐의 영혼이 없습니다,Nie ma ducha szczura do odwołania,Não há espírito de rato para chamar de volta,Дух крысы не призван,Geri çağrılacak Sıçan Ruhu yok,没有可召回的鼠灵,沒有可召回的鼠靈 +necroPetNoTarget,items,Item,,,,No target to attack,,Kein Ziel zum Angreifen,No hay objetivo que atacar,Aucune cible à attaquer,Nessun bersaglio da attaccare,攻撃する対象がありません,공격할 대상이 없습니다,Brak celu do ataku,Nenhum alvo para atacar,Нет цели для атаки,Saldırılacak hedef yok,没有可攻击的目标,沒有可攻擊的目標 +buffNecroRatGripName,buffs,Buff,,,,Rat Spirit’s Grip,,Griff des Rattengeistes,Garra del espíritu de rata,Emprise de l’esprit de rat,Stretta dello spirito di ratto,ネズミの霊の締めつけ,쥐의 영혼의 옥죄임,Uścisk ducha szczura,Garra do espírito de rato,Хватка духа крысы,Sıçan Ruhunun Pençesi,鼠灵擒缚,鼠靈擒縛 +buffNecroRatGripDesc,buffs,Buff,,,,A rat spirit has sunk its teeth in and will not let go. Every step is a struggle until it does.,,Ein Rattengeist hat sich festgebissen und lässt nicht mehr los. Bis dahin wird jeder Schritt zur Mühe.,"Un espíritu de rata ha clavado los dientes y no suelta. Hasta que lo haga, cada paso cuesta.","Un esprit de rat a planté ses dents et ne lâche plus. Jusque-là, chaque pas est une peine.",Uno spirito di ratto ha affondato i denti e non molla. Fino ad allora ogni passo è una fatica.,ネズミの霊が噛みついて離れない。離れるまで、一歩ごとが重い。,쥐의 영혼이 이빨을 박은 채 놓지 않습니다. 떨어질 때까지 한 걸음 한 걸음이 버겁습니다.,Duch szczura wbił zęby i nie puszcza. Do tego czasu każdy krok jest mozołem.,"Um espírito de rato cravou os dentes e não solta. Até lá, cada passo é um esforço.","Дух крысы вцепился зубами и не отпускает. Пока не отпустит, каждый шаг даётся с трудом.",Bir sıçan ruhu dişlerini geçirdi ve bırakmıyor. O bırakana dek her adım zahmet.,鼠灵咬住不放。在它松口之前,每走一步都举步维艰。,鼠靈咬住不放。在牠鬆口之前,每走一步都舉步維艱。 +buffNecroRatMarkName,buffs,Buff,,,,Spirit Mark,,Geistermal,Marca del espíritu,Marque de l’esprit,Marchio dello spirito,霊の印,영혼의 표식,Znak ducha,Marca do espírito,Метка духа,Ruh İzi,灵印,靈印 +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ış,撕裂伤,撕裂傷 +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.,牙齿咬中了血管。只要还在撕扯,伤口就不会自行闭合。,牙齒咬中了血管。只要還在撕扯,傷口就不會自行閉合。 diff --git a/Config/buffs.xml b/Config/buffs.xml index f88c0d4..0cc182a 100644 --- a/Config/buffs.xml +++ b/Config/buffs.xml @@ -200,5 +200,128 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Config/entityclasses.xml b/Config/entityclasses.xml index f7af400..f667d60 100644 --- a/Config/entityclasses.xml +++ b/Config/entityclasses.xml @@ -58,9 +58,41 @@ originally had before its own rework - see necroMeleeHandZombieDog in items.xml. --> - - - + + + + + + @@ -156,6 +188,9 @@ + + - + @@ -189,7 +224,8 @@ - + + @@ -202,7 +238,7 @@ - + @@ -211,56 +247,133 @@ - + История у него длинная, и важно не потерять её конец. До 29.08 он расширял + animalZombieVulture и просто бестолково летал ("летает где-то в небе, и зомби его вообще + не интересуют") - тогда его перевели на necroZombieDog, то есть сделали наземным со + сменённой моделью. 18.09 пользователь заметил результат прямо: "зомбогриф у тебя это серая + пума". Попробовали натянуть на тот же наземный класс префаб стервятника - вышло хуже + всего: "выглядит жутко, прозрачное только туловище и голова, по земле он ползёт на + крыльях". Это и был ответ на висевший с 29.08 вопрос, можно ли подменить ОДИН префаб: + нельзя, птичья модель на аниматоре наземного квадрупа не работает. + + Указание: "зомбогрифа откати, пусть ведёт себя как раньше и не делай его прозрачным. + Пусть пока бестолково летает как раньше, но с возможностью нацелить его на противника... + Потом сделаем грифа как надо. Но потом." + + Поэтому здесь снова ванильная ЛЕТАЮЩАЯ ветка со всем, что к ней прилагается, и это + сознательный шаг назад до отдельной работы над ним (замысел разведдрона - BACKLOG.md). + + ЧТО ПРИШЛОСЬ СНЯТЬ И ПОЧЕМУ: + - EntityFlags и EntityType уходят с "animal,zombie"/"Zombie" на чистое "animal"/"Animal". + Иначе EntityAlive.DamageEntity не даст ему бить зомби вовсе: там жёсткое правило, что + две сущности с флагом Zombie не могут повредить друг другу. Тот же приём и по той же + причине применён к Рою жуков выше. + - Tags теряют zombie/hostile, IsEnemyEntity=false, Faction=none - форма "свой игроку", + как у всех остальных питомцев. + - AITask/AITarget НЕ ЗАДАЮТСЯ ВОВСЕ, и это не упущение: Class="EntityVulture" не + использует систему AITask ни в каком виде, у него собственный захардкоженный поиск + целей на C# (см. HarmonySrc/SwarmTargetPatch.cs, там это разобрано целиком). Писать + сюда задачи - ровно та ошибка, которую уже совершали 28.08 с Роем. + + ЧТОБЫ НЕ АТАКОВАЛ ХОЗЯИНА, он добавлен в SwarmTargetPatch.SpeciesByName: префикс на + EntityAlive.SetAttackTarget подменяет игрока ближайшим зомби. Это единственная точка, через + которую проходят все цели EntityVulture. + + ПРИКАЗ АТАКОВАТЬ у него остаётся (CommandsAttack в SummonPatch.cs), но с оговоркой: + EntityVulture перебирает цели сам, своим циклом, и может сбросить наш приказ. Задачу + следования ему НЕ вешаем - она наземная и с полётом только подерётся. --> - - - - - - - - - - - - - - - - - - - + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -587,15 +605,99 @@ since it doesn't mean much against a zombie, kept only the slow that was asked for. Gated to zombies only, same EntityTagCompare pattern as everywhere else in this mod that adds a buff on hit. --> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -681,7 +786,22 @@ - + + @@ -703,7 +823,10 @@ - + @@ -721,9 +844,28 @@ - - - + + + + + @@ -741,7 +883,10 @@ - + @@ -759,7 +904,22 @@ - + + @@ -779,7 +939,10 @@ - + @@ -797,7 +960,22 @@ - + + @@ -814,6 +992,121 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Config/nav_objects.xml b/Config/nav_objects.xml new file mode 100644 index 0000000..66f2e0a --- /dev/null +++ b/Config/nav_objects.xml @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Config/progression.xml b/Config/progression.xml index 5658a34..e9c19d8 100644 --- a/Config/progression.xml +++ b/Config/progression.xml @@ -179,7 +179,7 @@ ур. 15 (300 убийств) - чутьё Тег necroNecromancyLvl30 вместе с этим удалён - им больше никто не пользуется, вода сидит на necroNecromancyLvl20. --> - + @@ -210,12 +210,43 @@ привязки unlock_icon_atlasN / unlock_icon_lockedN). Именно поэтому у мода теперь есть вторая папка UIAtlases/ItemIconAtlasGreyscale - без неё под замком у иконки не было бы картинки вообще. --> - - - - + + + + + + + - + + - + - - + + + + @@ -257,10 +292,17 @@ + + + + diff --git a/Config/recipes.xml b/Config/recipes.xml index 97da9e6..b4f6833 100644 --- a/Config/recipes.xml +++ b/Config/recipes.xml @@ -151,7 +151,7 @@ personal crafting). workbenchCrafting is just the same UI-categorization tag vanilla's own workbench recipes carry alongside craft_area, not a separate gate. --> - + @@ -234,7 +234,7 @@ resourceFeather/foodRawMeat both verified to exist in vanilla items.xml before use, same lesson as the earlier wrong guesses. --> - + @@ -243,7 +243,7 @@ - + @@ -252,7 +252,7 @@ - + @@ -344,6 +344,21 @@ + + + + + + + + + + diff --git a/HarmonySrc/GhostTraderPatch.cs b/HarmonySrc/GhostTraderPatch.cs index 1a7b1ea..5f144b9 100644 --- a/HarmonySrc/GhostTraderPatch.cs +++ b/HarmonySrc/GhostTraderPatch.cs @@ -266,6 +266,61 @@ namespace NecromancerTome timer = 0f; } + /// Кого этот проход обращает в призрака. + /// + /// РАСШИРЕНО 2026-09-18 С ТОРГОВЦЕВ НА ПИТОМЦЕВ, по указанию: "всех призванных существ + /// (зомбопёс, зомбомедведь, зомбоволк, зомбогриф) тоже делаем полупрозрачными как сейчас + /// торговцев". Плюс Дух крысы, который призраком и задуман. + /// + /// Обобщение вышло механическим, и это не совпадение: ApplyGreyscale ниже никогда не + /// пользовался ничем трейдерским - только GetComponentsInChildren<Renderer>(), + /// EntityClass.entityClassName и entityId, а всё это есть у любой EntityAlive. + /// + /// СПИСОК БЕРЁТСЯ ИЗ SummonPatch.LimitedPets, а не дублируется здесь: новый питомец + /// становится призраком автоматически, в одном месте, и разойтись эти два списка не могут. + /// Рой жуков в нём тоже есть, но он безвреден - его рендереры это системы частиц, а + /// ApplyGreyscale их намеренно пропускает (см. Convert), так что для него проход вхолостую. + /// + /// Проверка по имени класса, а не по типу: все наземные питомцы мода - это C#-класс + /// EntityZombieDog (см. entityclasses.xml), то есть по типу их не отличить ни друг от + /// друга, ни от настоящего зомбопса из мира. + /// Исключения из призрачного вида. Зомбопёс - по прямому указанию 2026-09-18: + /// "зомбособаку не делай полупрозрачной или серой, пусть будет обычной". + /// + /// Проверка идёт по ИМЕНИ КЛАССА, а не по типу, и это здесь важно: Дух крысы наследуется + /// от necroZombieWolf, а тот от necroZombieDog, то есть по типу они все EntityZombieDog и + /// исключение задело бы заодно и крысу, которая призраком как раз и задумана. + public static readonly HashSet NotGhosted = new HashSet + { + "necroZombieDog", + // Медведь и Волк добавлены 2026-09-18: "прозрачность им не нужна". Призрачными + // остаются Дух крысы, Зомбогриф и торговцы. + "necroZombieBear", + "necroZombieWolf", + // Гриф выведен 18.09 вместе с откатом: "не делай его прозрачным". На птичьей модели + // обесцвечивание к тому же брало не всё - прозрачными выходили только туловище и + // голова, что и выглядело жутко. + "necroZombieGriffin", + }; + + public static bool ShouldBeGhost(EntityAlive _entity) + { + if (_entity is EntityTrader) + { + return true; + } + EntityClass entityClass = EntityClass.list[_entity.entityClass]; + if (entityClass == null || entityClass.entityClassName == null) + { + return false; + } + if (NotGhosted.Contains(entityClass.entityClassName)) + { + return false; + } + return Patch_ItemActionSpawnEntity_Spawn_PetLimit.LimitedPets.ContainsKey(entityClass.entityClassName); + } + public static void OnUnityUpdate(ref ModEvents.SUnityUpdateData _data) { timer += Time.deltaTime; @@ -284,11 +339,11 @@ namespace NecromancerTome for (int i = 0; i < world.EntityAlives.Count; i++) { EntityAlive entity = world.EntityAlives[i]; - if (!(entity is EntityTrader trader) || trader.IsDead()) + if (entity == null || entity.IsDead() || !ShouldBeGhost(entity)) { continue; } - if (Ghosted.TryGetValue(trader.entityId, out GhostBody body)) + if (Ghosted.TryGetValue(entity.entityId, out GhostBody body)) { if (IsIntact(body)) { @@ -297,14 +352,14 @@ namespace NecromancerTome // His model was destroyed and rebuilt under him. Drop what is known about the // old one before building the new, or Converted and TintedMaterials keep // entries for renderers and materials that no longer exist. - Debug.Log("[NecromancerTome] GhostTraderPatch: entity " + trader.entityId + + Debug.Log("[NecromancerTome] GhostTraderPatch: entity " + entity.entityId + " came back with a new model - ghosting him again"); - Ghosted.Remove(trader.entityId); + Ghosted.Remove(entity.entityId); Prune(); } - if (ApplyGreyscale(trader, out GhostBody fresh)) + if (ApplyGreyscale(entity, out GhostBody fresh)) { - Ghosted[trader.entityId] = fresh; + Ghosted[entity.entityId] = fresh; } } } @@ -363,7 +418,7 @@ namespace NecromancerTome /// leaves this trader unmarked and tries again on the next sweep. On true, _body carries /// the renderers taken over, which is how the next sweep tells this trader from a rebuilt /// one standing under the same entity id. - public static bool ApplyGreyscale(EntityTrader _trader, out GhostBody _body) + public static bool ApplyGreyscale(EntityAlive _trader, out GhostBody _body) { _body = default(GhostBody); Renderer[] renderers = _trader.GetComponentsInChildren(true); @@ -797,6 +852,21 @@ namespace NecromancerTome break; } + // НАЙДЕНО 2026-09-18, НО НЕ ИСПРАВЛЕНО - записано, чтобы не искать заново. + // Пользователь: "собака не полупрозрачная". По логу у неё для этого всё было - + // шейдер Standard с _Color, _SrcBlend, _DstBlend, "5 of 5 renderer(s) desaturated, + // 5 opacity lever(s) installed". То есть альфа проставлена, а прозрачности нет. + // + // Разница с трейдерами, у которых она работает: их тело идёт по ДРУГОЙ ветке - + // MakeBlendMaterial создаёт материал заново и ставит там _Mode = 2 (Fade). Здесь, + // на ветке "блендим на месте", _Mode и _ZWrite не трогаются вовсе, а материал, + // собранный как Opaque, приходит с _Mode = 0 и _ZWrite = 1. Юнити для перевода + // Standard в прозрачность требует всю шестёрку: _Mode, _SrcBlend, _DstBlend, + // _ZWrite, ключевые слова и очередь отрисовки - у нас выставлены четыре из шести. + // + // Собаку это чинить больше не нужно (она выведена из призраков совсем), но Медведь, + // Волк и Гриф идут по этой же ветке. Если и они окажутся непрозрачными - причина + // здесь, и лечится добавлением _Mode = 2 и _ZWrite = 0 рядом со строками ниже. if (_material.HasProperty("_SrcBlend") && _material.HasProperty("_DstBlend")) { _material.SetFloat("_SrcBlend", (float)BlendMode.SrcAlpha); diff --git a/HarmonySrc/PetCommandPatch.cs b/HarmonySrc/PetCommandPatch.cs new file mode 100644 index 0000000..ac81802 --- /dev/null +++ b/HarmonySrc/PetCommandPatch.cs @@ -0,0 +1,92 @@ +using UnityEngine; + +namespace NecromancerTome +{ + /// + /// КОМАНДА "АТАКУЙ ТО, НА ЧТО Я СМОТРЮ" - второй режим свитка Духа крысы, указание + /// 2026-09-18: "у свитка призыва есть два режима (призыв и отзыв). Если крыса уже призвана, + /// то призыв становится атакой. И крыса берёт целью атаки того, на кого указал прицел + /// игрока. Если в прицеле нету целей для атаки, то пишется надпись "Нет цели для атаки"". + /// + /// Это не Harmony-заплатка, а helper: точка входа одна и та же для всех питомцев - + /// префикс на ItemActionSpawnEntity.Spawn в SummonPatch.cs. Второй патч на тот же метод + /// только запутал бы порядок исполнения. + /// + /// ОТКУДА БЕРЁТСЯ ЦЕЛЬ ПОД ПРИЦЕЛОМ. EntityPlayerLocal.HitInfo - публичное поле типа + /// WorldRayHitInfo, которое PlayerMoveController перезаписывает КАЖДЫЙ КАДР результатом луча + /// из прицела (Voxel.Raycast по GetLookRay, длина Utils.FastMax(cDigAndBuildDistance, + /// cCollectItemDistance, 30f), то есть не меньше 30 метров). Ничего своего лучить не надо - + /// игра уже посчитала это для интерфейса. Из попадания сущность достаётся собственным + /// публичным методом игры ItemActionAttack.GetEntityFromHit(hitInfo), который внутри зовёт + /// GameUtils.GetHitRootEntity(tag, transform) - то же самое, чем пользуется обычный удар + /// оружием, а значит и попадание по конечности/голове разрешится в саму сущность. + /// + /// Луч упирается в блоки - и это правильно: натравить крысу сквозь стену на то, чего игрок + /// не видит, нельзя. + /// + /// КОГО НЕЛЬЗЯ НАЗНАЧИТЬ ЦЕЛЬЮ (указание "пусть атакует всех существ кроме NPC торговцев"): + /// - EntityTrader - прямо по указанию; + /// - EntityPlayer - сам хозяин и вообще игроки: крыса не оружие против людей; + /// - собственный хозяин и она сама - на всякий случай, отдельной проверкой; + /// - мёртвые. + /// Отсев сделан ЗДЕСЬ, а не в entityclasses.xml, потому что targetClasses у + /// EAIApproachAndAttackTarget умеет только РАЗРЕШАТЬ тип, запретить им нельзя. В XML у крысы + /// стоит class=EntityAlive - это разрешение задаче подхватить любую цель, которую ей дали; + /// решение "кого дать" целиком принимается тут. + /// + public static class PetAttackCommand + { + /// Сколько тиков держится приказ. attackTargetTime уменьшается на 1 за тик ИИ и + /// на нуле сбрасывает цель (EntityAlive: "if (attackTargetTime > 0) { attackTargetTime--; + /// ... }"), так что это по сути "пока не убьёт". 6000 - заведомо больше любой драки; + /// смысл не в таймере, а в том, чтобы приказ не висел вечно, если цель уйдёт из мира. + public const int OrderTicks = 6000; + + /// Отдаёт питомцу приказ атаковать то, на что смотрит игрок. false - приказа не + /// вышло, и вызывающая сторона показывает "Нет цели для атаки". + public static bool TryOrderAttack(EntityAlive _owner, EntityAlive _pet) + { + if (_owner == null || _pet == null || _pet.IsDead()) + { + return false; + } + EntityPlayerLocal local = _owner as EntityPlayerLocal; + if (local == null) + { + // Не локальный игрок - HitInfo просто неоткуда взять (у мода нет сетевого слоя + // нигде, см. SummonPatch.cs). Молча отказываем, как будто цели нет. + Debug.Log("[NecromancerTome] PetAttackCommand: owner is not the local player, no crosshair to read"); + return false; + } + + WorldRayHitInfo hit = local.HitInfo; + if (hit == null || !hit.bHitValid || hit.transform == null) + { + return false; + } + + Entity hitEntity = ItemActionAttack.GetEntityFromHit(hit); + EntityAlive target = hitEntity as EntityAlive; + if (target == null || target.IsDead()) + { + return false; + } + if (target == _pet || target == _owner) + { + return false; + } + if (target is EntityPlayer || target is EntityTrader) + { + return false; + } + + // Месть сбрасывается вместе с приказом: иначе задача SetAsTargetIfHurt могла бы на + // следующем тике перебить наш приказ тем, кто ударил крысу последним. + _pet.SetRevengeTarget(null); + _pet.SetAttackTarget(target, OrderTicks); + Debug.Log("[NecromancerTome] PetAttackCommand: pet " + _pet.entityId + " ordered to attack " + + target.entityId + " (" + target.EntityClass.entityClassName + ")"); + return true; + } + } +} diff --git a/HarmonySrc/PetEyeGlowPatch.cs b/HarmonySrc/PetEyeGlowPatch.cs new file mode 100644 index 0000000..7be70ce --- /dev/null +++ b/HarmonySrc/PetEyeGlowPatch.cs @@ -0,0 +1,235 @@ +using UnityEngine; + +namespace NecromancerTome +{ + /// + /// СВЕТЯЩИЕСЯ ГЛАЗА у призванных зомбоживотных - указание 2026-09-18: "псу и волку сделай, + /// глаза светлофиолетовые светящиеся". + /// + /// ПОЧЕМУ ТОЛЬКО ДВОЕ, И ЭТО НЕ ВЫБОР, А ОГРАНИЧЕНИЕ МОДЕЛЕЙ. Покрасить глаза отдельно можно + /// ровно тогда, когда они лежат ОТДЕЛЬНЫМ материалом. Проба шейдеров, которую печатает + /// GhostTraderPatch, показала по питомцам такую картину: + /// + /// necroZombieDog - 5 рендереров: FeralEye, NormalEye, LOD0..LOD2 - ГОДИТСЯ + /// necroZombieWolf - 2 рендерера: DireWolf + DireWolfHair - НЕ ГОДИТСЯ + /// necroZombieBear - 1 рендерер - НЕ ГОДИТСЯ + /// necroZombieGriffin - 1 рендерер - НЕ ГОДИТСЯ + /// + /// ПО ВОЛКУ ОТВЕТ ОКОНЧАТЕЛЬНЫЙ И ОТРИЦАТЕЛЬНЫЙ, по дампу 18.09: + /// + /// DireWolf[0] shader='Game/Animal/Fur' texture=dire_wolf_df _Color=False + /// DireWolfHair[0] shader='Game/SDCS/Hair' texture=Afro_normal _Color=False + /// + /// Глазного рендерера у него НЕТ ВООБЩЕ - только шкура и шерсть, глаза запечены прямо в + /// dire_wolf_df. Ни отдельного материала, ни даже _Color, чтобы хоть что-то подкрасить. + /// Единственный оставшийся путь - править саму текстуру по пикселям, зная координаты глаз на + /// развёртке. Поэтому LitEyes у Волка снят: пусть флаг не обещает того, чего нет. + /// + /// У Медведя и Грифа материал один на всю модель, и отдельно глаза там не достать ничем, + /// кроме правки самой текстуры по пикселям. Поэтому их в списке нет - см. PetInfo.LitEyes. + /// + /// КРАСИТСЯ СВЕЧЕНИЕ, А НЕ ЦВЕТ. У зомби глаза светятся сами (_EmissionMap у этого материала + /// уже назначена), и именно свечение читается как "горящие глаза". Поэтому ставится + /// _EmissionColor, а _Color правится следом только чтобы радужка не спорила с ним при ярком + /// дневном свете. Ключевое слово _EMISSION включается явно: материал, собранный без эмиссии, + /// игнорирует _EmissionColor, пока оно выключено, - ровно тот же класс ловушки, что с + /// _SrcBlend/_Mode у прозрачности (см. ApplyTransparency в GhostTraderPatch.cs). + /// + /// ПОЧЕМУ ЭТО ТИК, А НЕ ХУК НА ПРИЗЫВ. Материалы появляются вместе с моделью, а она + /// собирается ПОЗЖЕ создания сущности - это уже дважды стоило нам ошибок: сначала + /// GhostTraderPatch пришлось перевести на опрос из-за трейдеров, потом + /// TryIgnoreCollisionWithOwner из-за того, что коллайдеров в момент CreateEntity ещё нет. + /// Здесь та же природа, поэтому сразу попытка с повтором: раз в секунду, до первого успеха, + /// из общего тика в PetFollowPatch. + /// + public static class PetEyeGlow + { + /// Фиолетовый, НАСЫЩЕННЫЙ. Первая версия была светлой (0.72, 0.55, 1.0) и в игре + /// дала ровно то, о чём сказал пользователь: "у собаки глаза просто белые". + /// + /// Причина арифметическая: _EmissionColor читается как HDR, цвет умножается на яркость. + /// При множителе 6 светлый фиолетовый превращался в (4.3, 3.3, 6.0) - все три канала + /// далеко за единицей, и тонемаппинг сводил их в белое пятно. Оттенок выживает только + /// когда каналы РАСХОДЯТСЯ: отсюда низкий зелёный и умеренная яркость ниже. + public static readonly Color EyeColour = new Color(0.55f, 0.18f, 1f); + + /// ПОДНЯТО С 2.4 ДО 6 после первой проверки: "у собаки они вообще никак не + /// светятся". Цвет сменился (это видно), а свечения нет - значит либо _EmissionColor у + /// этого шейдера читается как HDR и 2.4 для него ничто, либо свечение вообще идёт не + /// отсюда. Шесть - это попытка проверить первое предположение, а чтобы проверить второе, + /// рядом добавлен разовый дамп всех материалов питомца (DumpMaterialsOnce). + /// 1.6, а не 6: см. комментарий к EyeColour - на шести глаза выгорали в белый. + /// Итоговое свечение (0.88, 0.29, 1.6): синий вдвое выше красного и вшестеро выше + /// зелёного, так что фиолетовый читается, а не тонет в пересвете. + public const float EmissionIntensity = 1.6f; + + /// Классы, по которым дамп уже печатался: он нужен один раз на вид, а не на + /// каждого призванного. + public static readonly System.Collections.Generic.HashSet Dumped = + new System.Collections.Generic.HashSet(); + + /// ДИАГНОСТИКА, добавлена 2026-09-18. Первая попытка зажечь глаза дала половину + /// результата: у Пса цвет сменился, но свечения нет, а у Волка не изменилось ничего - + /// значит материала с "eye" в имени текстуры у него просто не нашлось. Гадать дальше + /// дороже, чем один раз посмотреть: этот дамп печатает по каждому виду все рендереры, их + /// материалы, шейдеры, имена текстур и наличие свойств свечения. По нему и будет видно, + /// за что хвататься - и надо ли вообще. + public static void DumpMaterialsOnce(Entity _pet, string _className) + { + if (_className == null || !Dumped.Add(_className)) + { + return; + } + Renderer[] renderers = _pet.GetComponentsInChildren(true); + Debug.Log("[NecromancerTome] PetEyeGlow: ДАМП материалов " + _className + " - " + + (renderers != null ? renderers.Length : 0) + " рендерер(ов)"); + if (renderers == null) + { + return; + } + foreach (Renderer renderer in renderers) + { + if (renderer == null) + { + continue; + } + Material[] mats = renderer.sharedMaterials; + for (int i = 0; mats != null && i < mats.Length; i++) + { + Material m = mats[i]; + if (m == null) + { + continue; + } + string albedo = GhostTraderPatch.FindAlbedoProperty(m); + Texture tex = albedo != null ? m.GetTexture(albedo) : null; + Debug.Log("[NecromancerTome] PetEyeGlow: " + renderer.name + "[" + i + "] shader='" + + (m.shader != null ? m.shader.name : "null") + "' albedo=" + (albedo ?? "-") + + " texture=" + (tex != null ? tex.name : "-") + + " _EmissionColor=" + m.HasProperty("_EmissionColor") + + " _EmissionMap=" + (m.HasProperty("_EmissionMap") && m.GetTexture("_EmissionMap") != null) + + " _EmissionIntensity=" + m.HasProperty("_EmissionIntensity") + + " _Color=" + m.HasProperty("_Color")); + } + } + } + + /// ПЕРЕПИСАНО 2026-09-18 ПО ДАМПУ. Первая версия искала материал по имени + /// ТЕКСТУРЫ ("eye"), нашла у собаки ровно один и зажгла его - а глаза всё равно не + /// светились. Дамп показал, почему: + /// + /// FeralEye[0] shader='Game/Autodesk' texture=feral_eye _EmissionMap=True + /// NormalEye[0] shader='Standard' texture=HD_ZombieDog _EmissionMap=False + /// LOD0..LOD2 shader='Standard' texture=HD_ZombieDog + /// + /// Глаз у собаки ДВА РАЗНЫХ, отдельными рендерерами. Светящийся feral - тот, что горит у + /// разъярённой собаки, и в обычном состоянии он не показывается; именно его мы и зажгли, + /// не увидев ничего. А обычный глаз - NormalEye - берёт текстуру ТУШКИ (HD_ZombieDog), + /// поэтому поиск по имени текстуры его пропускал. + /// + /// Поэтому теперь глаз опознаётся по имени РЕНДЕРЕРА, а имя текстуры оставлено как + /// запасной признак. Материал у NormalEye свой собственный, так что правка цвета и + /// свечения на нём затрагивает только глаз, хотя текстура и общая с телом. + public static bool IsEyeRenderer(Renderer _renderer) + { + if (_renderer == null || _renderer is ParticleSystemRenderer) + { + return false; + } + if (_renderer.name != null && _renderer.name.ToLower().Contains("eye")) + { + return true; + } + Material[] shared = _renderer.sharedMaterials; + if (shared == null) + { + return false; + } + foreach (Material m in shared) + { + if (IsEyeMaterial(m)) + { + return true; + } + } + return false; + } + + /// Запасной признак: текстура с "eye" в имени (у собаки это feral_eye). + public static bool IsEyeMaterial(Material _material) + { + if (_material == null) + { + return false; + } + string albedo = GhostTraderPatch.FindAlbedoProperty(_material); + if (albedo == null) + { + return false; + } + Texture texture = _material.GetTexture(albedo); + return texture != null && texture.name != null && + texture.name.ToLower().Contains("eye"); + } + + /// true, только если глаза действительно нашлись и были подожжены. Пока false, + /// тик повторяет попытку - модель может быть ещё не собрана. + public static bool TryLightEyes(Entity _pet) + { + if (_pet == null) + { + return false; + } + Renderer[] renderers = _pet.GetComponentsInChildren(true); + if (renderers == null || renderers.Length == 0) + { + return false; + } + + int lit = 0; + foreach (Renderer renderer in renderers) + { + if (!IsEyeRenderer(renderer)) + { + continue; + } + // Рендерер целиком глазной, поэтому красим ВСЕ его материалы: мешу глаза больше + // ничего не принадлежит, а вот у собаки их два (обычный и feral), и попасть надо + // в оба - какой из них покажется, решает сама игра. + Material[] instances = renderer.materials; + foreach (Material eye in instances) + { + if (eye == null) + { + continue; + } + if (eye.HasProperty("_EmissionColor")) + { + eye.EnableKeyword("_EMISSION"); + eye.globalIlluminationFlags = MaterialGlobalIlluminationFlags.RealtimeEmissive; + eye.SetColor("_EmissionColor", EyeColour * EmissionIntensity); + if (eye.HasProperty("_EmissionIntensity")) + { + eye.SetFloat("_EmissionIntensity", EmissionIntensity); + } + lit++; + } + if (eye.HasProperty("_Color")) + { + Color tint = eye.GetColor("_Color"); + eye.SetColor("_Color", new Color(EyeColour.r, EyeColour.g, EyeColour.b, tint.a)); + } + } + Debug.Log("[NecromancerTome] PetEyeGlow: рендерер глаз '" + renderer.name + "', материалов " + + instances.Length); + } + + if (lit > 0) + { + Debug.Log("[NecromancerTome] PetEyeGlow: pet " + _pet.entityId + " - " + lit + + " eye material(s) lit"); + } + return lit > 0; + } + } +} diff --git a/HarmonySrc/PetFollowPatch.cs b/HarmonySrc/PetFollowPatch.cs index 43fe779..68a65a3 100644 --- a/HarmonySrc/PetFollowPatch.cs +++ b/HarmonySrc/PetFollowPatch.cs @@ -53,6 +53,27 @@ namespace NecromancerTome { public int OwnerEntityId; public int PetEntityId; + + /// У питомца своя задача следования (NecroFollowOwnerTask, 2026-09-18) - + /// значит телепорт-поводок ниже его НЕ трогает. Уборка владения при этом остаётся: + /// она к способу передвижения отношения не имеет. + public bool OwnFollowTask; + + /// Удалось ли развести коллайдеры питомца с хозяйскими. Пока false, попытка + /// повторяется каждый тик - см. TryIgnoreCollisionWithOwner. + public bool CollisionIgnored; + + /// Зажжены ли глаза (PetEyeGlowPatch.cs). Как и коллайдеры, с первого раза + /// обычно не получается - модели ещё нет; попытка повторяется до успеха. + public bool EyesLit; + + /// Нужно ли им вообще заниматься: копия PetInfo.LitEyes, чтобы тик не лазил + /// в словарь на каждом питомце каждую секунду. + public bool WantsLitEyes; + + /// Радиус "дома" для летающих (см. PetInfo.FlyingHomeRadius). Ноль - + /// питомец не летающий, дом ему не переставляем. + public int FlyingHomeRadius; } public static readonly List TrackedPets = new List(); @@ -62,12 +83,98 @@ namespace NecromancerTome public static void Init() { ModEvents.UnityUpdate.RegisterHandler(OnUnityUpdate); + ModEvents.WorldShuttingDown.RegisterHandler(OnWorldShuttingDown); + } + + /// ДОБАВЛЕНО 2026-09-18. TrackedPets - статический список, он переживает выход в + /// меню, а вот мир, к которому относятся лежащие в нём entityId, - нет. Без этой уборки + /// на следующей загрузке первый же тик начинал разбирать чужие идентификаторы: в новом + /// мире тот же номер принадлежит совершенно другой сущности, и RemoveOwnedEntity уходил + /// бы неизвестно куда. GhostTraderPatch.cs подписан на то же событие ровно по той же + /// причине; здесь подписки не было - это был найденный, но не закрытый пробел из разбора + /// ИИ питомцев (BACKLOG.md, 2026-09-18, п. 6). + public static void OnWorldShuttingDown(ref ModEvents.SWorldShuttingDownData _data) + { + if (TrackedPets.Count > 0) + { + Debug.Log("[NecromancerTome] PetFollowPatch: world shutting down, forgetting " + + TrackedPets.Count + " tracked pet(s)"); + TrackedPets.Clear(); + } + timer = 0f; } /// Called from SummonPatch.cs right after a pet is created and owned. - public static void Register(EntityAlive owner, Entity pet) + public static void Register(EntityAlive owner, Entity pet, bool ownFollowTask, bool collisionIgnored, + bool wantsLitEyes, int flyingHomeRadius) { - TrackedPets.Add(new TrackedPet { OwnerEntityId = owner.entityId, PetEntityId = pet.entityId }); + TrackedPets.Add(new TrackedPet + { + OwnerEntityId = owner.entityId, + PetEntityId = pet.entityId, + OwnFollowTask = ownFollowTask, + CollisionIgnored = collisionIgnored, + WantsLitEyes = wantsLitEyes, + FlyingHomeRadius = flyingHomeRadius, + }); + } + + /// Разводит коллайдеры питомца и его хозяина, чтобы они проходили друг сквозь + /// друга. Возвращает true, только если РЕАЛЬНО что-то развела. + /// + /// БАГ-РЕПОРТ 2026-09-18: "когда крыса оказывается под ногами, игрока подбрасывает". + /// Просьба сделать питомца нематериальным для игрока была ещё 28.08, и код для неё + /// написан тогда же (SummonPatch.IgnoreCollisionWithOwner) - но он вызывался ровно один + /// раз, из постфикса на EntityFactory.CreateEntity, а это СЛИШКОМ РАНО: там сущность + /// только создана и в мир ещё не добавлена (SpawnEntityInWorld идёт следующей строкой в + /// ItemActionSpawnEntity.Spawn), модель не собрана, и коллайдеров, привязанных к костям + /// через PhysicsBody, попросту ещё нет. GetComponentsInChildren возвращал пустой массив, + /// цикл не делал ни одной итерации, и никто этого не замечал - метод ничего не возвращал + /// и ничего не логировал. Ровно тот же урок про "модель собирается позже" уже записан в + /// GhostTraderPatch.cs, где из-за него пришлось опрашивать торговцев по таймеру. + /// + /// Поэтому теперь попытка повторяется в тике раз в секунду, пока не удастся. Толкать + /// игрока питомец сможет в худшем случае одну секунду после призыва. + /// + /// Разводятся ВСЕ пары коллайдеров, включая капсулы передвижения: CharacterController - + /// это тоже Collider, и именно на ней игрок и стоит, когда его подбрасывает. + public static bool TryIgnoreCollisionWithOwner(EntityAlive owner, Entity pet) + { + if (owner == null || pet == null) + { + return false; + } + Collider[] ownerColliders = owner.GetComponentsInChildren(true); + Collider[] petColliders = pet.GetComponentsInChildren(true); + if (ownerColliders == null || petColliders == null || + ownerColliders.Length == 0 || petColliders.Length == 0) + { + return false; + } + int pairs = 0; + foreach (Collider oc in ownerColliders) + { + if (oc == null) + { + continue; + } + foreach (Collider pc in petColliders) + { + if (pc == null || pc == oc) + { + continue; + } + Physics.IgnoreCollision(oc, pc, true); + pairs++; + } + } + if (pairs > 0) + { + Debug.Log("[NecromancerTome] PetFollowPatch: pet " + pet.entityId + " passes through owner " + + owner.entityId + " now (" + ownerColliders.Length + "x" + petColliders.Length + + " collider pairs ignored)"); + } + return pairs > 0; } /// Called from SummonPatch.cs's manual recall path so a recalled pet stops being @@ -83,6 +190,130 @@ namespace NecromancerTome } } + /// ПИТАНИЕ ПАДАЛЬЮ, 2026-09-18. Наблюдение пользователя: "Дух крысы после того как + /// зомби погибает, начинает есть труп. Пусть тогда этот процесс восстанавливает ей здоровье". + /// + /// Поедание тут не отдельное поведение, а побочный эффект приказа: команда атаковать ставит + /// цель на 6000 тиков (PetAttackCommand.OrderTicks), а EAIApproachAndAttackTarget.CanExecute + /// сверяет только ТИП цели и ничего не знает про её смерть - так что, добив зомби, крыса + /// продолжает грызть труп, пока тот не исчезнет. Само по себе это выглядело хорошо, поэтому + /// не чинится, а используется. + /// + /// Лечение идёт отсюда, а не из triggered_effect в items.xml, по простой причине: в XML + /// нельзя спросить "цель мертва". Полный список классов Requirement* в Assembly-CSharp + /// просмотрен - там есть RequirementFullHealth, RequirementHasEntityTag, RequirementNearbyEntities + /// и ещё сорок, но ни одного про смерть цели. Значит гейт всё равно оказался бы в коде, а + /// тик раз в секунду тут уже есть и обходит ровно тех же питомцев. + /// + /// Условий три, и все три обязательны: цель есть, цель МЕРТВА (иначе это лечение в бою, а + /// не питание), и крыса рядом с ней (иначе она лечилась бы, стоя в другом конце улицы и + /// только СОБИРАЯСЬ дойти до трупа). + public const float FeedRangeSq = 2.5f * 2.5f; + + /// Здоровья за секунду поедания. У Духа крысы всего 120 HP, так что 5 - это полное + /// восстановление примерно за полминуты над одним трупом: заметно, но не бесплатно. + public const int FeedHealthPerSecond = 5; + + /// ДЫРА, ЗАКРЫТАЯ 2026-09-18. Защита от падения живёт в NecroFollowOwnerTask, а + /// та НЕ ИСПОЛНЯЕТСЯ, пока у питомца есть цель (CanExecute возвращает false) - то есть + /// именно в бою, когда питомец и бегает по незнакомым местам, ловить его было нечем. + /// Этот тик работает всегда, поэтому проверка переехала сюда. + /// + /// ПРОВЕРКИ ДВЕ, И ОНИ ПРО РАЗНОЕ - это важно не путать. + /// + /// 1. АБСОЛЮТНАЯ ВЫСОТА. Ниже отметки 5 не бывает законных причин находиться: мир + /// кончается на нуле, и всё, что туда опустилось, игра удаляет молча + /// (Entity.onUpdate: position.y < 0f -> MarkToUnload). Двусмысленности нет никакой, + /// поэтому спасаем всегда, хоть в бою, хоть нет. Это и есть настоящий детектор + /// падения, и он один закрывает тот случай, на котором 18.09 потерялась крыса. + /// + /// 2. НИЖЕ ЦЕЛИ (предложено пользователем). Это детектор НЕ падения, а + /// НЕДОСТИЖИМОСТИ: питомец, который на десять метров ниже того, кого ему велели + /// грызть, скорее всего не упал, а не может добраться - зомби на крыше, на этаже + /// выше, за проломом. Без этой проверки он будет ломиться туда вечно: приказ игрока + /// держится 6000 тиков и сам не истечёт. + /// + /// Поэтому здесь цель ещё и СБРАСЫВАЕТСЯ. Вернуть питомца к хозяину, не сняв + /// приказ, значило бы получить маятник: прыжок наверх - бег вниз - прыжок наверх. + /// + /// Обратная сторона второй проверки названа честно: зомби, стоящий двумя этажами выше, + /// теперь отменяет приказ вместо бесконечной беготни. По-моему это лучше, но если + /// окажется, что питомец сдаётся слишком рано - крутить UnreachableBelowTarget. + public const float UnreachableBelowTarget = 10f; + + public static void RescueFallen(EntityAlive pet, EntityAlive owner) + { + if (pet == null || owner == null || pet.world == null) + { + return; + } + + bool outOfWorld = pet.position.y < NecroFollowOwnerTask.WorldFloorGuard; + EntityAlive target = pet.GetAttackTarget(); + bool unreachable = !outOfWorld && target != null && !target.IsDead() && + pet.position.y < target.position.y - UnreachableBelowTarget; + if (!outOfWorld && !unreachable) + { + return; + } + + pet.IsStuck = false; + pet.motion = Vector3.zero; + if (pet.moveHelper != null) + { + pet.moveHelper.Stop(); + } + if (unreachable) + { + pet.SetAttackTarget(null, 0); + } + pet.SetPosition(NecroFollowOwnerTask.SlotPosition(owner, 0, pet.world), true); + Debug.Log("[NecromancerTome] PetFollowPatch: pet " + pet.entityId + " rescued to owner " + + owner.entityId + " - " + (outOfWorld + ? "выпал из мира (высота " + pet.position.y.ToCultureInvariantString("0.0") + ")" + : "цель недостижима, она выше на " + + (target.position.y - pet.position.y).ToCultureInvariantString("0.0") + " м")); + } + + /// Кому принадлежит этот питомец. null - значит не наш или уже не отслеживается. + /// Нужна PetKillCreditPatch.cs, чтобы зачесть убийство владельцу. + public static EntityPlayer FindOwnerOfPet(int petEntityId) + { + World world = GameManager.Instance != null ? GameManager.Instance.World : null; + if (world == null) + { + return null; + } + for (int i = 0; i < TrackedPets.Count; i++) + { + if (TrackedPets[i].PetEntityId == petEntityId) + { + return world.GetEntity(TrackedPets[i].OwnerEntityId) as EntityPlayer; + } + } + return null; + } + + public static void FeedOnCorpse(EntityAlive pet) + { + EntityAlive target = pet.GetAttackTarget(); + if (target == null || !target.IsDead()) + { + return; + } + if ((target.position - pet.position).sqrMagnitude > FeedRangeSq) + { + return; + } + int before = pet.Health; + pet.AddHealth(FeedHealthPerSecond); + if (pet.Health != before) + { + Debug.Log("[NecromancerTome] PetFollowPatch: pet " + pet.entityId + " fed on corpse " + + target.entityId + ", health " + before + " -> " + pet.Health); + } + } + public static void OnUnityUpdate(ref ModEvents.SUnityUpdateData _data) { timer += Time.deltaTime; @@ -147,6 +378,72 @@ namespace NecromancerTome } } + // Повторная попытка развести коллайдеры - до первого успеха. Стоит ДО всех + // проверок про бой и расстояние: "питомец подбрасывает хозяина" не зависит ни от + // того, ни от другого. + if (!tracked.CollisionIgnored) + { + tracked.CollisionIgnored = TryIgnoreCollisionWithOwner(owner, pet); + } + + // СЛЕДОВАНИЕ ДЛЯ ЛЕТАЮЩИХ, 2026-09-18. Дом переставляется на хозяина каждую + // секунду, а возвращается питомец САМ, своим полётом: у EntityVulture проверка + // "не ушёл ли из дома" встроена и работает каждые 60 тиков. Ни телепорта, ни + // пафайндинга, ни своей задачи - см. PetInfo.FlyingHomeRadius. + // + // НА ВРЕМЯ АТАКИ ДОМ ОТВЯЗЫВАЕТСЯ СОВСЕМ (указание того же дня: "для атаки радиус + // нужно увеличить, пусть летит сколько надо... а вот после атаки пусть + // возвращается"). Иначе собственная проверка EntityVulture срывала бы его с цели + // на полпути: она прерывает атаку, как только питомец вышел за радиус. + // + // Отвязка - это detachHome(), то есть maximumHomeDistance = -1, и тогда + // isWithinHomeDistanceCurrentPosition() безусловно возвращает true. Ограничителем + // вместо радиуса остаётся сама дальность приказа: цель назначается только по + // прицелу, а луч прицела не длиннее 30 метров, так что отправить Грифа на другой + // край карты нельзя при всём желании. + // + // Как только цель пропала (убита, недостижима, приказ сброшен), дом на следующей + // же секунде встаёт обратно на хозяина - и питомец возвращается сам. + if (tracked.FlyingHomeRadius > 0) + { + if (pet.GetAttackTarget() != null) + { + if (pet.hasHome()) + { + pet.detachHome(); + Debug.Log("[NecromancerTome] PetFollowPatch: flying pet " + pet.entityId + + " is on a target - home detached for the chase"); + } + } + else + { + if (!pet.hasHome()) + { + Debug.Log("[NecromancerTome] PetFollowPatch: flying pet " + pet.entityId + + " finished the chase - home re-anchored to owner " + owner.entityId); + } + pet.setHomeArea(new Vector3i(owner.position), tracked.FlyingHomeRadius); + } + } + + if (tracked.WantsLitEyes && !tracked.EyesLit) + { + PetEyeGlow.DumpMaterialsOnce(pet, EntityClass.list[pet.entityClass]?.entityClassName); + tracked.EyesLit = PetEyeGlow.TryLightEyes(pet); + } + + if (tracked.OwnFollowTask) + { + FeedOnCorpse(pet); + RescueFallen(pet, owner); + // У этого питомца есть настоящая задача следования (NecroFollowOwnerTask): + // она сама держит его при хозяине, сама решает, когда пройти сквозь стену, и + // сама сажает его на опорный блок. Телепорт-поводок здесь только мешал бы - + // две системы дёргали бы питомца в разные стороны. Уборка владения выше при + // этом уже отработала, и она остаётся общей для всех. + continue; + } + if (pet.GetAttackTarget() != null) { // Mid-fight - let it finish rather than teleporting it away. diff --git a/HarmonySrc/PetFollowTask.cs b/HarmonySrc/PetFollowTask.cs new file mode 100644 index 0000000..56dc1e6 --- /dev/null +++ b/HarmonySrc/PetFollowTask.cs @@ -0,0 +1,408 @@ +using GamePath; +using UnityEngine; + +namespace NecromancerTome +{ + /// + /// ПЕРВАЯ СВОЯ ЗАДАЧА ИИ В ЭТОМ МОДЕ. "Дух крысы" держится справа-сзади от хозяина, а не + /// бродит сам по себе - указание 2026-09-18 ("держится сбоку и позади хозяина на 2 блока... + /// если по циферблату часов, то на 3-6 часов (зависит от препятствий)"). + /// + /// ПОЧЕМУ ЭТО ПРИШЛОСЬ ПИСАТЬ, А НЕ НАСТРОИТЬ. Задачи "иди за сущностью" в игре НЕТ вообще - + /// проверено по списку типов в Assembly-CSharp: всего 32 класса EAI*, и ни одного + /// follow-подобного (EAIWander, EAITerritorial, EAIApproachSpot, EAIApproachAndAttackTarget, + /// EAISetNearestCorpseAsTarget и т.д.). Слежение дрона за хозяином - это захардкоженный C# + /// внутри EntityDrone, в XML его не выбрать. До сегодняшнего дня мод обходился телепорт- + /// поводком в PetFollowPatch.cs: раз в секунду, если питомец дальше 32 м, его просто + /// переставляли к игроку. Это и есть то, что игроки видели как "питомец не идёт следом". + /// + /// ОБРАЗЕЦ ВЗЯТ С ВАНИЛЬНОГО EAIApproachSpot - он решает ровно ту же задачу "дойти до точки" + /// и показывает весь нужный обвес: theEntity.FindPath(...) -> theEntity.navigator.getPath() + /// -> moveHelper. Отличий от него три, и все три - требования пользователя: + /// + /// 1. ТОЧКА ПЕРЕСЧИТЫВАЕТСЯ КАЖДЫЙ ТИК и привязана к хозяину, а не к месту на земле. + /// SlotAngles - сектор "3-6 часов" от направления взгляда хозяина, перебираемый от + /// середины к краям: сначала 135° (ровно между бортом и кормой), потом ближе к борту и к + /// корме. Первая точка, до которой нашёлся путь, и становится местом крысы - это и есть + /// "зависит от препятствий". + /// + /// 2. canBreak: false в FindPath. Питомцы до сих пор носили ванильный AITask BreakBlock и + /// грызли всё на пути, включая базу игрока. Крыса не может грызть блоки НА УРОВНЕ + /// ПАФАЙНДЕРА: он просто не строит путей через разрушение. В entityclasses.xml у неё + /// заодно нет ни BreakBlock, ни BlockingTargetTask. + /// + /// 3. ПРОХОД СКВОЗЬ БЛОКИ вместо разрушения - указание "технически, дух может проходить + /// сквозь блоки, если нужно пройти там где есть препятствие. Блоки ломать не нужно". + /// Для этого не понадобилось ничего изобретать: в Entity есть публичное поле IsStuck, и + /// единственное место, где игра его читает, - собственный шаг перемещения: + /// + /// if (IsStuck) { PhysicsTransform.position += hitMove; } // мимо контроллера + /// else { collisionFlags = m_characterController.Move(hitMove); } + /// + /// То есть это готовый выключатель столкновений, оставленный игрой себе на случай + /// застревания. Ни один из просмотренных классов (EntityMoveHelper, EntityZombie, + /// EntityEnemy, EntityAnimal, EntityAlive, EntityPlayerLocal) в него не ПИШЕТ - поле + /// фактически бесхозное, и мы можем владеть им единолично. + /// + /// ОПАСНОСТЬ ЗДЕСЬ РЕАЛЬНАЯ, И ОНА УЖЕ СРАБОТАЛА: IsStuck отключает столкновения со + /// ВСЕМ, включая пол, и 18.09 крыса провалилась под мир ("fell off the world, + /// pos=(319.2, -0.4, 1367.2)"). Разбор и все пять страховок - в комментариях к + /// MaxDropBelowOwner и в UpdatePhase; коротко: гравитация гасится на каждом тике, + /// ниже цели питомец не опускается, проход не включается на дальней дистанции и + /// ограничен по времени, а падение ловится отдельной проверкой в начале Update. + /// + /// ЗАДАЧА ДОБАВЛЯЕТСЯ В РАНТАЙМЕ (SummonPatch.cs), а не через XML. В XML это тоже возможно - + /// EAIManager.GetType() для незнакомого имени падает в Type.GetType("EAI" + имя), и + /// индексированные AITask-N берут значение свойства целиком, так что + /// AITask-N value="NecroFollowOwner,NecromancerHarmony" разрешилось бы в наш тип. Но + /// Type.GetType с именем сборки дёргает Assembly.Load, а как именно загрузчик модов кладёт + /// наши DLL - непроверено, и ставить работоспособность питомца в зависимость от этого нет + /// никакой нужды: aiManager.tasks.AddTask даёт тот же результат без единого допущения, да ещё + /// и позволяет передать владельца прямо в конструктор. + /// + public class NecroFollowOwnerTask : EAIBase + { + /// Сектор "3-6 часов" в градусах от направления взгляда хозяина: 90° - правый + /// борт, 180° - корма. Перебор идёт от середины сектора к его краям, чтобы обычное место + /// крысы было одно и то же, а края работали как запасные при препятствии. + public static readonly float[] SlotAngles = { 135f, 112f, 158f, 90f, 180f }; + + /// Расстояние по умолчанию - два блока от хозяина. + public const float DefaultSlotDistance = 2f; + + /// Своё у каждого питомца: Медведю и Волку задан блок дальше остальных + /// (указание 2026-09-18 - "сделай место следования там же где и собаке, но на блок + /// дальше, они мощнее"). Сектор при этом общий, меняется только радиус, поэтому + /// крупные звери встают в тот же угол, но не наступают хозяину на пятки. + /// Значение приходит из SummonPatch.LimitedPets при создании задачи. + public float SlotDistance = DefaultSlotDistance; + + /// Ближе этого крыса считает, что уже на месте, и останавливается. Квадрат + /// расстояния, как везде в этом коде - корень тут не нужен. + public const float ArrivedDistSq = 1.2f * 1.2f; + + /// Столько секунд без сокращения расстояния - и точка считается недостижимой: + /// пробуем следующую, а когда кончатся все - проходим насквозь. + /// + /// ПОДНЯТО С 1.5 ДО 3 СЕКУНД 2026-09-18, после того как крыса провалилась сквозь мир. + /// Полторы секунды - слишком мало: в помещении питомец столько обходит мебель, не + /// сокращая расстояния ПО ПРЯМОЙ, и проход включался там, где обычный обход дошёл бы + /// сам. Чем реже поднимается IsStuck, тем меньше поводов у всего, что с ним связано. + public const float NoProgressSeconds = 3f; + + /// Метры в секунду на проходе сквозь препятствие. Медленнее бега: дух + /// просачивается, а не выстреливает. + public const float PhaseSpeed = 3.5f; + + /// Предохранитель. Если за это время проход не закончился, он прекращается + /// принудительно - лучше крыса, стоящая не там, чем крыса, летящая сквозь мир. + public const float PhaseTimeout = 4f; + + /// Дальше этого расстояния до хозяина ждать пафайндер бессмысленно - прыжок. + /// Та же цифра, что у старого поводка в PetFollowPatch.cs (и у ванильного DroneManager, + /// откуда она изначально и взята). + /// + /// РАНЬШЕ ЗДЕСЬ БЫЛ ПРОХОД, И ЭТО БЫЛА ОШИБКА (исправлено 18.09, см. MaxDropBelowOwner): + /// проход задуман для препятствия в шаге, а не для перелёта через тридцать метров + /// незнакомой местности с отключёнными столкновениями. + public const float LeashDistance = 32f; + + /// Аварийный предел по высоте: если питомец оказался ниже хозяина больше чем на + /// столько, разбираться уже некогда - прыжок к хозяину. + /// + /// ЗАЧЕМ. 18.09 крыса пропала в игре, и лог сказал ровно что случилось: + /// WRN Entity [type=EntityZombieDog, name=necroRatSpirit, id=1459] fell off the world, + /// pos=(319.2, -0.4, 1367.2) + /// Высота -0.4: она провалилась под мир, а игра удаляет всё, что опустилось ниже нуля + /// (Entity.onUpdate: position.y < 0f -> MarkToUnload). Виноват IsStuck - он снимает + /// столкновения со ВСЕМ, включая пол, а гравитация никуда не девается: шаг перемещения + /// складывает hitMove с накопленным motion. Четыре секунды прохода над дыркой в полу - + /// и питомца нет. + /// + /// Эта проверка работает независимо от прохода и ловит любое падение, а не только наше + /// собственное. + public const float MaxDropBelowOwner = 6f; + + /// Ниже этой высоты вмешиваемся в любом случае: мир кончается на нуле, и пять + /// метров запаса нужны, чтобы успеть что-то сделать, а не констатировать. + public const float WorldFloorGuard = 5f; + + public int OwnerEntityId = -1; + + public Vector3 seekPos; + public int slotIndex; + public float noProgressTime; + public float lastDistSq; + public bool phasing; + public float phaseTime; + public int pathRecalculateTicks; + + public override void Init(EntityAlive _theEntity) + { + base.Init(_theEntity); + // 3 - те же биты, что у EAIApproachSpot: это задача ПЕРЕМЕЩЕНИЯ, и она не должна + // исполняться одновременно с другой такой же (в первую очередь с погоней за целью). + MutexBits = 3; + executeDelay = 0.1f; + } + + public EntityAlive Owner() + { + if (OwnerEntityId < 0 || theEntity == null || theEntity.world == null) + { + return null; + } + return theEntity.world.GetEntity(OwnerEntityId) as EntityAlive; + } + + /// Точка для слота slotIndex: хозяин + поворот его собственного направления на + /// угол из SlotAngles, посаженный на опорный блок. + public Vector3 SlotPosition(EntityAlive _owner, int _slot) + { + return SlotPosition(_owner, _slot, theEntity.world, SlotDistance); + } + + /// То же самое, но без экземпляра задачи - нужна PetFollowPatch.cs, который + /// спасает упавших питомцев во время боя, когда сама задача не исполняется. Там точный + /// радиус не важен (питомца просто возвращают к хозяину), поэтому берётся общий. + public static Vector3 SlotPosition(EntityAlive _owner, int _slot, World _world, + float _slotDistance = DefaultSlotDistance) + { + float angle = SlotAngles[_slot % SlotAngles.Length]; + Vector3 dir = _owner.qrotation * (Quaternion.Euler(0f, angle, 0f) * Vector3.forward); + Vector3 raw = _owner.position + dir * _slotDistance; + return _world.FindSupportingBlockPos(raw); + } + + public override bool CanExecute() + { + // Дерётся - не мешаем. Цель крысе даёт игрок (PetCommandPatch.cs), и пока она есть, + // место в строю подождёт. + if (theEntity.GetAttackTarget() != null) + { + return false; + } + if (theEntity.IsDead() || theEntity.IsSleeping) + { + return false; + } + EntityAlive owner = Owner(); + if (owner == null) + { + return false; + } + seekPos = SlotPosition(owner, slotIndex); + return (seekPos - theEntity.position).sqrMagnitude > ArrivedDistSq; + } + + public override void Start() + { + noProgressTime = 0f; + lastDistSq = float.MaxValue; + phasing = false; + phaseTime = 0f; + pathRecalculateTicks = 0; + } + + public override bool Continue() + { + if (theEntity.GetAttackTarget() != null) + { + return false; + } + EntityAlive owner = Owner(); + if (owner == null) + { + return false; + } + seekPos = SlotPosition(owner, slotIndex); + return (seekPos - theEntity.position).sqrMagnitude > ArrivedDistSq; + } + + public override void Update() + { + EntityAlive owner = Owner(); + if (owner == null) + { + return; + } + + // ПЕРВОЙ СТРОКОЙ, ДО ВСЕГО ОСТАЛЬНОГО: не падает ли питомец из мира. Проверка стоит + // здесь, а не внутри прохода, намеренно - провалиться можно и без нашего участия + // (дыра в полу POI, взорванный блок), а последствие одно и то же: сущность ниже нуля + // игра удаляет молча, и питомец просто исчезает. + if (theEntity.position.y < WorldFloorGuard || + theEntity.position.y < owner.position.y - MaxDropBelowOwner) + { + SnapToOwner(owner, "падение (высота " + theEntity.position.y.ToCultureInvariantString("0.0") + ")"); + return; + } + + Vector3 lookPosition = owner.position; + lookPosition.y += 0.8f; + theEntity.SetLookPosition(lookPosition); + + if (phasing) + { + UpdatePhase(); + return; + } + + float distSq = (seekPos - theEntity.position).sqrMagnitude; + bool ownerFarAway = (owner.position - theEntity.position).sqrMagnitude > LeashDistance * LeashDistance; + + // Сокращаем расстояние - значит путь есть и он работает; счётчик простоя сбрасывается. + if (distSq < lastDistSq - 0.05f) + { + noProgressTime = 0f; + } + else + { + noProgressTime += executeDelay; + } + lastDistSq = distSq; + + // ХОЗЯИН ДАЛЕКО - ЭТО НЕ ПОВОД ПРОХОДИТЬ СКВОЗЬ СТЕНЫ, и раньше было именно так. + // Проход задуман для препятствия В ШАГЕ, а не для перелёта через тридцать метров + // незнакомой местности с отключёнными столкновениями: ровно такой проход 18.09 и + // уронил крысу под мир. На дальней дистанции работает обычный прыжок поводком - тот + // самый приём, которым ванильный DroneManager возвращает дрона, и он безопасен, + // потому что точка назначения известна заранее и стоит на земле. + if (ownerFarAway) + { + SnapToOwner(owner, "хозяин дальше " + LeashDistance + " м"); + return; + } + + if (noProgressTime >= NoProgressSeconds) + { + noProgressTime = 0f; + lastDistSq = float.MaxValue; + if (slotIndex >= SlotAngles.Length - 1) + { + // Перепробованы все точки сектора - значит дело в препятствии: сквозь. + slotIndex = 0; + BeginPhase(); + return; + } + // Следующая точка сектора - "зависит от препятствий". + slotIndex++; + seekPos = SlotPosition(owner, slotIndex); + pathRecalculateTicks = 0; + } + + if (theEntity.navigator.getPath() != null) + { + theEntity.moveHelper.CalcIfUnreachablePos(); + } + if (--pathRecalculateTicks <= 0) + { + UpdatePath(); + } + } + + public void UpdatePath() + { + if (PathFinderThread.Instance.IsCalculatingPath(theEntity.entityId)) + { + return; + } + pathRecalculateTicks = 8 + GetRandom(6); + // canBreak: false - крыса не грызёт блоки, это её главное отличие от всех остальных + // питомцев мода (см. шапку файла). + theEntity.FindPath(seekPos, theEntity.GetMoveSpeedAggro(), false, this); + } + + public void BeginPhase() + { + phasing = true; + phaseTime = 0f; + theEntity.moveHelper.Stop(); + theEntity.IsStuck = true; + } + + public void EndPhase() + { + phasing = false; + phaseTime = 0f; + theEntity.IsStuck = false; + // ОБЯЗАТЕЛЬНО: пока IsStuck был поднят, пола для крысы не существовало. Сажаем её на + // опорный блок, иначе она останется висеть или продолжит падать. + theEntity.SetPosition(theEntity.world.FindSupportingBlockPos(theEntity.position), true); + lastDistSq = float.MaxValue; + noProgressTime = 0f; + pathRecalculateTicks = 0; + } + + public void UpdatePhase() + { + phaseTime += executeDelay; + + // ГРАВИТАЦИЯ ГАСИТСЯ КАЖДЫЙ ТИК, и это главная правка 18.09. Пока IsStuck поднят, + // шаг перемещения складывает накопленный motion прямо с позицией, минуя контроллер + // и пол, - то есть питомец продолжает "падать" между нашими SetPosition, причём + // ускоряясь. Обнуление motion означает: во время прохода питомца двигает ТОЛЬКО + // этот метод и ничто больше. + theEntity.motion = Vector3.zero; + + Vector3 next = Vector3.MoveTowards(theEntity.position, seekPos, PhaseSpeed * executeDelay); + + // И отдельно - высота. Даже с обнулённым motion спускаться ниже цели незачем: + // seekPos уже посажен на опорный блок (FindSupportingBlockPos), так что всё, что + // ниже него, - это пол, сквозь который мы проходить не собирались. + if (next.y < seekPos.y) + { + next.y = seekPos.y; + } + + theEntity.SetPosition(next, true); + if (phaseTime >= PhaseTimeout || (next - seekPos).sqrMagnitude <= ArrivedDistSq) + { + EndPhase(); + } + } + + /// Прыжок к хозяину: аварийный выход и он же дальний поводок. Точка - тот же + /// слот в секторе, посаженный на опорный блок, а не позиция хозяина в упор (иначе + /// коллайдеры сложатся и питомец подбросит игрока - эти грабли уже разобраны в + /// PetFollowPatch.cs). + public void SnapToOwner(EntityAlive _owner, string _why) + { + if (phasing) + { + phasing = false; + phaseTime = 0f; + } + theEntity.IsStuck = false; + theEntity.motion = Vector3.zero; + theEntity.moveHelper.Stop(); + slotIndex = 0; + lastDistSq = float.MaxValue; + noProgressTime = 0f; + pathRecalculateTicks = 0; + + Vector3 dest = SlotPosition(_owner, 0); + theEntity.SetPosition(dest, true); + Debug.Log("[NecromancerTome] NecroFollowOwnerTask: pet " + theEntity.entityId + + " snapped back to owner " + _owner.entityId + " - " + _why); + } + + public override void Reset() + { + if (phasing) + { + EndPhase(); + } + theEntity.IsStuck = false; + theEntity.moveHelper.Stop(); + theEntity.SetLookPosition(Vector3.zero); + } + + public override string ToString() + { + return string.Format("{0}, slot{1}{2} dist{3}", base.ToString(), slotIndex, + phasing ? " PHASING" : "", + (theEntity.position - seekPos).magnitude.ToCultureInvariantString()); + } + } +} diff --git a/HarmonySrc/PetKillCreditPatch.cs b/HarmonySrc/PetKillCreditPatch.cs new file mode 100644 index 0000000..c1c8006 --- /dev/null +++ b/HarmonySrc/PetKillCreditPatch.cs @@ -0,0 +1,197 @@ +using System.Collections.Generic; +using HarmonyLib; +using UnityEngine; + +namespace NecromancerTome +{ + /// + /// УБИЙСТВА ПИТОМЦЕМ ТЕПЕРЬ ЗАСЧИТЫВАЮТСЯ ВЛАДЕЛЬЦУ - 2026-09-18. + /// + /// ПОЧЕМУ РАНЬШЕ НЕ ЗАСЧИТЫВАЛИСЬ. Ваниль решает, чей это фраг, ровно в одном месте: + /// + /// public void AwardKillXPServer(DamageSource _source, EntityAlive _killingEntity) + /// { + /// if (_source == null || _source.BuffClass != null) return; // (2) + /// EntityPlayer entityPlayer = _killingEntity as EntityPlayer; // (1) + /// if ((bool)entityPlayer && !(entityPlayer == this) && ...) + /// entityPlayer.AddKillXP(this, _source.AttackingItem, num); + /// } + /// + /// (1) Убийца обязан БЫТЬ игроком. При укусе питомца _killingEntity - это сам питомец + /// (берётся как world.GetEntity(_dmResponse.Source.getEntityId())), приведение к + /// EntityPlayer даёт null, и AddKillXP не вызывается вовсе. А счётчик Некромантии висит + /// постфиксом именно на AddKillXP (NecromancyKillCreditPatch.cs) - значит не растут ни + /// уровень, ни урон Ножа некроманта, который от этого счётчика и считается. + /// + /// ЧТО ДЕЛАЕТ ЭТОТ ПРЕФИКС. Подменяет _killingEntity на владельца, если убил его питомец. + /// Дальше всё едет по ванильному пути само: и опыт, и наш счётчик, и урон Ножа, и испытания - + /// потому что мод по-прежнему СЛЕДУЕТ решению ванили, а не дублирует его. Это то же правило, + /// ради которого счёт убийств 16.09 переехал из XML в постфикс на AddKillXP. + /// + /// ПОЧЕМУ ЭТО ВООБЩЕ ПОТРЕБОВАЛОСЬ ОБСУЖДАТЬ. 17.09 пользователь решил обратное для зомби под + /// Камнем духов: "если зомби под контролем камня духов убивает другого, то в скилл это не + /// идёт, что в целом логично. Пока оставляем так". Подчинённые зомби в TrackedPets не лежат, + /// поэтому то решение остаётся в силе - здесь меняется только судьба ПРИЗВАННЫХ питомцев, + /// которых игрок крафтит, кормит и водит за собой. + /// + /// ВТОРАЯ ПОЛОВИНА: ДОБИВАНИЕ КРОВОТЕЧЕНИЕМ. Строка (2) отсекает любой урон от баффа ДО + /// всякой проверки убийцы - то есть зомби, истёкший кровью, не даёт опыта никому и никогда, + /// даже если рану нанёс сам игрок. Для Духа крысы это половина всего урона (buffNecroRatBleed + /// капает 5 в секунду при укусе в 5), и без этой части починка была бы половинчатой. + /// + /// Мешало одно: в DamageSource от баффа НЕТ идентификатора того, кто бафф наложил. Значит + /// помнить приходится самим - что и делает RecentPetBites ниже: каждый удар нашего питомца + /// записывает "эту жертву кусал питомец такого-то" со сроком годности чуть больше, чем живёт + /// кровотечение. Когда жертва умирает от баффа, запись и отвечает, кому зачесть. + /// + /// Ванильный путь при этом НЕ ПРАВИТСЯ: мы не обнуляем _source.BuffClass (это общий объект, + /// его читают и дальше - PartyShareKillServer, разбор смерти игрока), а сами зовём + /// AddKillXP и отменяем оригинал, который всё равно вышел бы ни с чем. + /// + [HarmonyPatch(typeof(EntityAlive), "AwardKillXPServer")] + public static class Patch_EntityAlive_AwardKillXPServer_PetCredit + { + public static bool Prefix(EntityAlive __instance, DamageSource _source, ref EntityAlive _killingEntity) + { + if (_source == null) + { + return true; + } + + // Смерть от баффа: ваниль выйдет по строке (2) ни с чем, поэтому зачисляем сами. + if (_source.BuffClass != null) + { + EntityPlayer bleedOwner = RecentPetBites.OwnerOf(__instance.entityId); + if (bleedOwner == null || bleedOwner == __instance || !EntityClass.list.ContainsKey(__instance.entityClass)) + { + return true; + } + // KillXPScale у баффа обычно нулевой - это множитель "сколько опыта дать", и на + // нуле AddKillXP не дал бы ничего. Единица = обычное убийство. + float scale = _source.KillXPScale > 0f ? _source.KillXPScale : 1f; + bleedOwner.AddKillXP(__instance, _source.AttackingItem, scale); + RecentPetBites.Forget(__instance.entityId); + Debug.Log("[NecromancerTome] PetKillCredit: bleed kill of " + __instance.entityId + + " credited to owner " + bleedOwner.entityId); + return false; + } + + if (_killingEntity == null || _killingEntity is EntityPlayer) + { + return true; + } + EntityPlayer owner = PetFollowPatch.FindOwnerOfPet(_killingEntity.entityId); + if (owner == null) + { + return true; + } + Debug.Log("[NecromancerTome] PetKillCredit: kill by pet " + _killingEntity.entityId + + " credited to owner " + owner.entityId); + _killingEntity = owner; + return true; + } + } + + /// Кто кого кусал из питомцев и когда. Запись живёт чуть дольше кровотечения + /// (buffNecroRatBleed - 15 секунд), чтобы добивание точно попало в окно, и при этом не + /// превращалась в вечную память: зомби, укушенный минуту назад и умерший от чужой ловушки, + /// не должен приносить очки владельцу питомца. + public static class RecentPetBites + { + public const float MemorySeconds = 18f; + + public struct Bite + { + public int OwnerEntityId; + public float ExpiresAt; + } + + public static readonly Dictionary Bites = new Dictionary(); + + public static void Remember(int _victimEntityId, int _ownerEntityId) + { + Bites[_victimEntityId] = new Bite + { + OwnerEntityId = _ownerEntityId, + ExpiresAt = Time.time + MemorySeconds, + }; + } + + public static void Forget(int _victimEntityId) + { + Bites.Remove(_victimEntityId); + } + + public static EntityPlayer OwnerOf(int _victimEntityId) + { + if (!Bites.TryGetValue(_victimEntityId, out Bite bite)) + { + return null; + } + if (Time.time > bite.ExpiresAt) + { + Bites.Remove(_victimEntityId); + return null; + } + World world = GameManager.Instance != null ? GameManager.Instance.World : null; + return world != null ? world.GetEntity(bite.OwnerEntityId) as EntityPlayer : null; + } + } + + /// Здесь и запоминается укус. Точка выбрана самая общая: любой урон, нанесённый + /// нашим питомцем, метит жертву - неважно, укус это, кровотечение от прошлого укуса или + /// что-то ещё, что мы добавим потом. + /// + /// Постфикс, а не префикс: смысл только в том, чтобы записать факт, а вмешиваться в сам урон + /// незачем. Заодно здесь же подчищается таблица - редко и дёшево, без своего таймера. + [HarmonyPatch(typeof(EntityAlive), "DamageEntity")] + public static class Patch_EntityAlive_DamageEntity_RememberPetBite + { + public static int callsSincePrune; + + public static void Postfix(EntityAlive __instance, DamageSource _damageSource) + { + if (_damageSource == null || __instance == null) + { + return; + } + int attackerId = _damageSource.getEntityId(); + if (attackerId == -1 || attackerId == __instance.entityId) + { + return; + } + EntityPlayer owner = PetFollowPatch.FindOwnerOfPet(attackerId); + if (owner == null) + { + return; + } + RecentPetBites.Remember(__instance.entityId, owner.entityId); + + if (++callsSincePrune >= 200) + { + callsSincePrune = 0; + Prune(); + } + } + + public static void Prune() + { + if (RecentPetBites.Bites.Count == 0) + { + return; + } + List stale = new List(); + foreach (KeyValuePair entry in RecentPetBites.Bites) + { + if (Time.time > entry.Value.ExpiresAt) + { + stale.Add(entry.Key); + } + } + foreach (int id in stale) + { + RecentPetBites.Bites.Remove(id); + } + } + } +} diff --git a/HarmonySrc/SummonPatch.cs b/HarmonySrc/SummonPatch.cs index 20b9f43..d5b97c5 100644 --- a/HarmonySrc/SummonPatch.cs +++ b/HarmonySrc/SummonPatch.cs @@ -85,6 +85,54 @@ namespace NecromancerTome /// fix the earlier "dog died, no way to get the book back" complaint: with nothing /// ever taken, there's nothing to lose when the dog dies off-screen. public bool ConsumesBook; + + /// Указание 2026-09-18, пока только для Духа крысы: "если крыса уже + /// призвана, то призыв становится атакой". Для остальных питомцев Action0 при живом + /// питомце по-прежнему просто говорит "уже призван" (AlreadyActiveKey), как и было. + /// Разбор прицела - в PetCommandPatch.cs. + public bool CommandsAttack; + + /// Что показать, когда команда атаки отдана, но под прицелом некого + /// атаковать. Читается только при CommandsAttack. + public string NoTargetKey; + + /// Радиус "дома" для ЛЕТАЮЩИХ питомцев, в блоках. Ноль - выключено. + /// + /// Указание 2026-09-18: "гриф улетает слишком далеко от игрока и срабатывает поводок. + /// Можем сделать ему следование, которое возвратит в радиус игрока, чтобы поводок + /// вообще не использовался?" Можем, и писать для этого ничего не пришлось: у + /// EntityVulture слежение за "домом" уже встроено. Каждые 60 тиков он проверяет + /// isWithinHomeDistanceCurrentPosition(), и если вышел за радиус - сам переходит в + /// State.Home и летит обратно СВОИМ полётом: + /// + /// if (state != State.Home && --homeCheckDelay <= 0) { + /// homeCheckDelay = 60; + /// if (!isWithinHomeDistanceCurrentPosition()) SetState(State.AttackStop); + /// } + /// ... StartHome(getHomePosition().position.ToVector3()); + /// + /// Достаточно каждую секунду переставлять этот дом на позицию хозяина + /// (EntityAlive.setHomeArea), и получается настоящее следование за игроком, без + /// единого телепорта и без нашего пафайндинга. + public int FlyingHomeRadius; + + /// Зажигать ли этому питомцу глаза (PetEyeGlowPatch.cs). Только у Пса и + /// Волка: у Медведя и Грифа модель - один материал на всё, и глаза там запечены в + /// текстуру шкуры, доставать их нечем. + public bool LitEyes; + + /// За сколько блоков от хозяина держится этот питомец. Ноль означает "как + /// у всех" (NecroFollowOwnerTask.DefaultSlotDistance, два блока). Медведю и Волку + /// задан блок дальше - указание 2026-09-18, "они мощнее": сектор тот же, радиус + /// больше, чтобы туша не наступала хозяину на пятки. + public float FollowDistance; + + /// Вешать ли этому питомцу собственную задачу следования + /// (NecroFollowOwnerTask): держаться справа-сзади, не бродить, не грызть блоки, + /// проходить сквозь препятствия. Старый телепорт-поводок из PetFollowPatch.cs для + /// таких питомцев выключается - две системы на одно и то же дёргали бы питомца в + /// разные стороны. + public bool UsesFollowTask; } public static readonly Dictionary LimitedPets = new Dictionary @@ -98,6 +146,14 @@ namespace NecromancerTome NothingToRecallKey = "necroZombieDogNothingToRecall", SummonItemName = "bookSummonZombieDog", ConsumesBook = false, + LitEyes = true, + // 2026-09-18: "поведение других призванных животных зомби тоже подгони под + // крысу". Те же три флага, что у неё: повторный Action0 - приказ атаковать + // цель под прицелом, своя задача следования вместо телепорт-поводка. + // AlreadyActiveKey у всех четверых теперь не читается никогда. + CommandsAttack = true, + NoTargetKey = "necroPetNoTarget", + UsesFollowTask = true, } }, { @@ -131,6 +187,18 @@ namespace NecromancerTome NothingToRecallKey = "necroZombieGriffinNothingToRecall", SummonItemName = "bookSummonZombieGriffin", ConsumesBook = false, + // 15 блоков: заметно меньше 32-метрового поводка, чтобы тот вообще не + // понадобился, и достаточно, чтобы Гриф не висел у игрока над головой. + FlyingHomeRadius = 15, + // UsesFollowTask у Грифа НЕТ с 18.09: он снова летающий (EntityVulture), а + // задача следования наземная - она гоняла бы его пафайндером по земле и + // дралась бы с его собственным полётом. Приказ атаковать оставлен. + // 2026-09-18: "поведение других призванных животных зомби тоже подгони под + // крысу". Те же три флага, что у неё: повторный Action0 - приказ атаковать + // цель под прицелом, своя задача следования вместо телепорт-поводка. + // AlreadyActiveKey у всех четверых теперь не читается никогда. + CommandsAttack = true, + NoTargetKey = "necroPetNoTarget", } }, { @@ -142,6 +210,14 @@ namespace NecromancerTome NothingToRecallKey = "necroZombieBearNothingToRecall", SummonItemName = "bookSummonZombieBear", ConsumesBook = false, + FollowDistance = 3f, + // 2026-09-18: "поведение других призванных животных зомби тоже подгони под + // крысу". Те же три флага, что у неё: повторный Action0 - приказ атаковать + // цель под прицелом, своя задача следования вместо телепорт-поводка. + // AlreadyActiveKey у всех четверых теперь не читается никогда. + CommandsAttack = true, + NoTargetKey = "necroPetNoTarget", + UsesFollowTask = true, } }, { @@ -153,6 +229,34 @@ namespace NecromancerTome NothingToRecallKey = "necroZombieWolfNothingToRecall", SummonItemName = "bookSummonZombieWolf", ConsumesBook = false, + FollowDistance = 3f, + // 2026-09-18: "поведение других призванных животных зомби тоже подгони под + // крысу". Те же три флага, что у неё: повторный Action0 - приказ атаковать + // цель под прицелом, своя задача следования вместо телепорт-поводка. + // AlreadyActiveKey у всех четверых теперь не читается никогда. + CommandsAttack = true, + NoTargetKey = "necroPetNoTarget", + UsesFollowTask = true, + } + }, + // "Дух крысы", указание 2026-09-18 - питомец начального уровня (Некромантия 3) и + // первый, у кого поведение задано намеренно, а не унаследовано от зомбопса. На нём + // отлаживается ИИ питомцев вообще, поэтому он единственный, у кого стоят оба новых + // флага. AlreadyActiveKey у него НЕ ЧИТАЕТСЯ НИКОГДА: повторный Action0 - это не + // отказ "уже призван", а команда атаковать (CommandsAttack), так что вместо него + // показывается либо ничего (приказ принят), либо NoTargetKey. + { + "necroRatSpirit", + new PetInfo + { + AlreadyActiveKey = null, + RecalledKey = "necroRatSpiritRecalled", + NothingToRecallKey = "necroRatSpiritNothingToRecall", + SummonItemName = "bookSummonRatSpirit", + ConsumesBook = false, + CommandsAttack = true, + NoTargetKey = "necroPetNoTarget", + UsesFollowTask = true, } }, }; @@ -161,8 +265,17 @@ namespace NecromancerTome /// summon books now declare Action1 with the same Class="SpawnEntity"/Entity as Action0. public const int RecallActionIndex = 1; + /// Правда ровно между "префикс разрешил призыв" и концом этого же вызова Spawn. + /// Нужна постфиксу ниже, чтобы отличить НАСТОЯЩИЙ призыв от команды атаковать: обе идут + /// через один и тот же Action0, постфикс Harmony отрабатывает в обоих случаях (возврат + /// false из префикса его не отменяет), а делать им надо противоположное - в одном случае + /// снять цель со свежего питомца, в другом ни в коем случае её не трогать, её только что + /// поставил игрок. + public static bool summonAllowedThisCall; + public static bool Prefix(ItemActionSpawnEntity __instance, ItemActionData _actionData) { + summonAllowedThisCall = false; if (!LimitedPets.TryGetValue(__instance.entityToSpawn, out PetInfo tooltips)) { return true; @@ -207,15 +320,73 @@ namespace NecromancerTome if (owned.Count > 0) { + // Action0 при уже живом питомце. Для всех, кроме Духа крысы, это тупик "уже + // призван". Для него - команда атаковать то, на что смотрит игрок: см. + // PetCommandPatch.cs, там же и весь отсев (торговцы, игроки, мёртвые). + if (tooltips.CommandsAttack) + { + EntityAlive pet = holdingEntity.world != null + ? holdingEntity.world.GetEntity(owned[0].Id) as EntityAlive + : null; + if (!PetAttackCommand.TryOrderAttack(holdingEntity, pet) && holdingEntity.world != null) + { + GameManager.ShowTooltip(GameManager.Instance.World.GetPrimaryPlayer(), tooltips.NoTargetKey); + } + return false; + } if (holdingEntity.world != null) { GameManager.ShowTooltip(GameManager.Instance.World.GetPrimaryPlayer(), tooltips.AlreadyActiveKey); } return false; } + summonAllowedThisCall = true; return true; } + /// ДОБАВЛЕНО 2026-09-18, после чтения ItemActionSpawnEntity.Spawn целиком. + /// Последней строкой ваниль делает вот что: + /// + /// entityAlive.SetAttackTarget(holdingEntity.GetAttackTarget(), 600); + /// + /// то есть свежепризванный питомец НАСЛЕДУЕТ ЦЕЛЬ ХОЗЯИНА. Для зомбопса это задумано и + /// полезно, а для Духа крысы прямо противоречит его единственному правилу - "сама не + /// атакует никогда". Призови её в драке, и она бросилась бы в бой сама, без приказа. + /// + /// Почистить это в постфиксе на EntityFactory.CreateEntity нельзя: тот отрабатывает + /// ВНУТРИ Spawn, ещё до этой строки. Поэтому чистка здесь, после всего. + /// + /// Флаг summonAllowedThisCall обязателен: без него постфикс сбрасывал бы цель и после + /// команды атаковать - то есть приказ гасил бы сам себя. + public static void Postfix(ItemActionSpawnEntity __instance) + { + if (!summonAllowedThisCall) + { + return; + } + summonAllowedThisCall = false; + if (!LimitedPets.TryGetValue(__instance.entityToSpawn, out PetInfo petInfo) || !petInfo.UsesFollowTask) + { + return; + } + World world = GameManager.Instance != null ? GameManager.Instance.World : null; + if (world == null || world.EntityAlives == null) + { + return; + } + int petClassId = EntityClass.GetId(__instance.entityToSpawn); + for (int i = world.EntityAlives.Count - 1; i >= 0; i--) + { + EntityAlive candidate = world.EntityAlives[i]; + if (candidate != null && candidate.entityClass == petClassId && candidate.GetAttackTarget() != null) + { + candidate.SetAttackTarget(null, 0); + Debug.Log("[NecromancerTome] SummonPatch: cleared the target vanilla Spawn handed to fresh pet " + + candidate.entityId + " (this species never attacks on its own)"); + } + } + } + /// summonItemName is null when this species doesn't consume its book on summon /// (see PetInfo.ConsumesBook) - nothing was taken, so nothing is given back. public static void RecallPet(EntityAlive owner, int petEntityId, string recalledTooltipKey, string summonItemName) @@ -267,6 +438,140 @@ namespace NecromancerTome } } + /// + /// ОТЗЫВ СРАБАТЫВАЕТ СРАЗУ ПО НАЖАТИЮ - вторая правка того же бага, 2026-09-18. Первая + /// (Patch_..._IsActionRunning_FreeTheRecall ниже) сняла блокировку вторичного действия + /// основным, и этого оказалось мало: "отозвать собаку получилось далеко не с первого раза. + /// Это проблема всех питомцев". + /// + /// ОСТАВШАЯСЯ ПРИЧИНА - ТА ЖЕ САМАЯ СЕМЬЯ, что и у призыва, только в другом слоте. Отзыв идёт + /// через ItemActionSpawnEntity.Spawn, а до него надо ДОЖИТЬ через автомат состояний: + /// + /// OnHoldingUpdate: stateTime += 0.05 + /// Anim: если stateTime >= animWait -> state = Spawn + /// Spawn: Spawn(data); state = End + /// ExecuteAction(_bReleased: true): state = None // отпустил - всё сбросилось + /// + /// При animWait = 0.1 это ТРИ тика: 0.05 (мало), 0.10 (переход в Spawn), и только третий + /// вызывает сам Spawn. То есть кнопку надо продержать около 0.15 секунды. У призыва это + /// лечилось уменьшением animWait до 0.05, но там и оставалось два тика; меньше двух автомат + /// не умеет в принципе - один переход и одно исполнение. + /// + /// Поэтому отзыв вынут из автомата совсем: он делается прямо на НАЖАТИИ, в префиксе + /// ExecuteAction, и оригинал не запускается вовсе. Никакого удержания, никакого ожидания. + /// + /// ЗАЩЁЛКА ОБЯЗАТЕЛЬНА. ExecuteAction(_bReleased: false) зовётся не один раз за клик, а + /// КАЖДЫЙ КАДР, пока кнопка нажата (PlayerMoveController проверяет Secondary.IsPressed, а не + /// WasPressed), и вся защита от повторов у ванили держится на её же state == None, который мы + /// теперь не выставляем. Без задержки ниже одно удержание правой кнопки отозвало бы питомца и + /// следом залило экран надписью "не призван" по разу в кадр. + /// + [HarmonyPatch(typeof(ItemActionSpawnEntity), "ExecuteAction")] + public static class Patch_ItemActionSpawnEntity_ExecuteAction_InstantRecall + { + /// Секунды между двумя срабатываниями отзыва. Полсекунды заведомо больше любого + /// клика и заведомо меньше осмысленного повторного нажатия. + public const float RepeatGuardSeconds = 0.5f; + + public static float lastRecallTime; + + public static bool Prefix(ItemActionSpawnEntity __instance, ItemActionData _actionData, bool _bReleased) + { + if (_bReleased || _actionData == null || + _actionData.indexInEntityOfAction != Patch_ItemActionSpawnEntity_Spawn_PetLimit.RecallActionIndex) + { + return true; + } + if (!Patch_ItemActionSpawnEntity_Spawn_PetLimit.LimitedPets.TryGetValue( + __instance.entityToSpawn, out Patch_ItemActionSpawnEntity_Spawn_PetLimit.PetInfo petInfo)) + { + return true; + } + EntityAlive holdingEntity = _actionData.invData != null ? _actionData.invData.holdingEntity : null; + if (holdingEntity == null || holdingEntity.world == null) + { + return true; + } + if (Time.time - lastRecallTime < RepeatGuardSeconds) + { + // Кнопка всё ещё зажата с прошлого кадра - молча проглатываем. + return false; + } + int petClassId = EntityClass.GetId(__instance.entityToSpawn); + if (petClassId == -1) + { + return true; + } + lastRecallTime = Time.time; + + List owned = holdingEntity.GetOwnedEntities(petClassId); + if (owned.Count > 0) + { + Patch_ItemActionSpawnEntity_Spawn_PetLimit.RecallPet(holdingEntity, owned[0].Id, + petInfo.RecalledKey, petInfo.ConsumesBook ? petInfo.SummonItemName : null); + holdingEntity.PlayOneShot(__instance.soundWarn); + } + else + { + GameManager.ShowTooltip(GameManager.Instance.World.GetPrimaryPlayer(), petInfo.NothingToRecallKey); + } + return false; + } + } + + /// + /// "ОТЗЫВАЕТСЯ НЕ С ПЕРВОГО РАЗА" - баг-репорт пользователя 2026-09-18: "если прожал атаку + /// несколько раз, то при попытке отозвать крысу, она не отзывается с первого раза". + /// + /// Причина не в нашем коде и не в питомце, а в гейте ввода. PlayerMoveController решает, + /// пускать ли вторичное действие, так: + /// + /// bool flag16 = Actions[0].AllowConcurrentActions() || Actions[1].AllowConcurrentActions(); + /// bool flag18 = Actions[0].IsActionRunning(actionData[0]); // основное ещё "идёт" + /// ... + /// if (flag12 && flag15 && (flag16 || !flag18)) // flag15 = Secondary.IsPressed + /// inventory.Execute(1, _bReleased: false, ...); // ВОТ ЭТО - отзыв + /// + /// То есть пока основное действие считается идущим, вторичное просто не запускается. + /// А "идущим" оно считается вот по чему (ItemActionSpawnEntity): + /// + /// public override bool IsActionRunning(ItemActionData _actionData) + /// => ((ItemActionDataSpawnEntity)_actionData).state != State.None; + /// + /// Состояние проходит None -> Anim -> Spawn -> **End**, и в None возвращается ТОЛЬКО из + /// ExecuteAction(_bReleased: true), то есть по отпусканию кнопки. End - это уже + /// отработавшее, законченное действие, но формально всё ещё "не None". Чем чаще игрок + /// щёлкает приказом атаковать, тем выше шанс, что в момент нажатия правой кнопки Action0 + /// висит именно в End, - и первый отзыв уходит в никуда. + /// + /// ЧИНИМ РОВНО ЭТО И НИЧЕГО БОЛЬШЕ: для наших книг призыва End больше не считается "идёт". + /// Соблазн был переписывать сам state в None - так делать НЕЛЬЗЯ: ExecuteAction стартует + /// новое действие как раз по условию state == None, и при зажатой кнопке призыв/приказ пошёл + /// бы на повтор каждые два тика, вместе с воплем кролика на каждый. Здесь же меняется только + /// ОТВЕТ на вопрос "идёт ли действие": вторичное разблокировано, а повторный старт + /// основного по-прежнему заперт настоящим полем state, которое так и осталось End. + /// + [HarmonyPatch(typeof(ItemActionSpawnEntity), "IsActionRunning")] + public static class Patch_ItemActionSpawnEntity_IsActionRunning_FreeTheRecall + { + public static void Postfix(ItemActionSpawnEntity __instance, ItemActionData _actionData, ref bool __result) + { + if (!__result || _actionData == null) + { + return; + } + if (!Patch_ItemActionSpawnEntity_Spawn_PetLimit.LimitedPets.ContainsKey(__instance.entityToSpawn)) + { + return; + } + if (_actionData is ItemActionSpawnEntity.ItemActionDataSpawnEntity data + && data.state == ItemActionSpawnEntity.ItemActionDataSpawnEntity.State.End) + { + __result = false; + } + } + } + [HarmonyPatch(typeof(EntityFactory), "CreateEntity", new System.Type[] { typeof(int), typeof(Vector3), typeof(Vector3) })] public static class Patch_EntityFactory_CreateEntity_PetOwnership { @@ -323,69 +628,57 @@ namespace NecromancerTome { owner.inventory.DecHoldingItem(1); } - PetFollowPatch.Register(owner, __result); - IgnoreCollisionWithOwner(owner, __result); - ApplyGhostlyTransparency(__result); + // Первая попытка развести коллайдеры - здесь же, чтобы в удачном случае не ждать + // секунду до тика. Удачной она, скорее всего, НЕ БУДЕТ: модель питомца на этом шаге + // ещё не собрана и коллайдеров у него нет (разбор - в TryIgnoreCollisionWithOwner). + // Поэтому результат передаётся в Register, и тик повторяет попытку до успеха. + bool collisionIgnored = PetFollowPatch.TryIgnoreCollisionWithOwner(owner, __result); + PetFollowPatch.Register(owner, __result, petInfo.UsesFollowTask, collisionIgnored, petInfo.LitEyes, + petInfo.FlyingHomeRadius); + AttachFollowTask(owner, __result, petInfo); + // Полупрозрачность больше НЕ делается здесь. Бывший ApplyGhostlyTransparency ставил + // альфу в material.color и не работал ни разу: шейдеры моделей мобов альфу не + // поддерживают (на Рое это выяснилось ещё 28.08 - он только засорял лог). Призрачный + // вид всем питомцам теперь даёт GhostTraderPatch.cs - та же система, что у торговцев, + // с клонированием материала, обесцвечиванием и подбором blend-шейдера. Debug.Log("[NecromancerTome] SummonPatch: owner=" + owner.entityId + " now owns pet " + __result.entityId); } - /// User request 2026-08-28 ("нематериальными") - makes the pet pass through its - /// own owner specifically, via Physics.IgnoreCollision on every collider pair between the - /// two, rather than stripping the pet's PhysicsBody entirely the way vanilla's own - /// animalInsectSwarm does for its "no physics body at all" look (confirmed by checking - /// entityclasses.xml). That works for a flying swarm; a ground-walking dog with no - /// collider at all would fall through the terrain. This keeps it solid against ground and - /// zombies - just not its owner - which also directly closes the last piece of the - /// spawn-launch bug documented in items.xml/PetFollowPatch.cs (nothing left to shove the - /// player if the two colliders can't touch in the first place). - public static void IgnoreCollisionWithOwner(EntityPlayer owner, Entity pet) - { - Collider[] ownerColliders = owner.GetComponentsInChildren(); - Collider[] petColliders = pet.GetComponentsInChildren(); - foreach (Collider oc in ownerColliders) - { - if (oc == null) - { - continue; - } - foreach (Collider pc in petColliders) - { - if (pc == null) - { - continue; - } - Physics.IgnoreCollision(oc, pc, true); - } - } - } - - /// User request 2026-08-28 ("слегка прозрачными") - best effort only. Directly - /// sets renderer.material.color's alpha, the same technique ItemActionSpawnTurret uses for - /// its own placement-preview tint (confirmed by decompiling it), but that only visibly - /// shows up if the model's actual shader supports alpha blending - most opaque mob - /// shaders in this game don't, and there's no reliable XML/reflection-only way to swap a - /// live renderer's shader to a transparent variant without risking breaking how it's lit. + /// Вешает питомцу собственную задачу следования - указание 2026-09-18, пока + /// только Духу крысы (PetInfo.UsesFollowTask). /// - /// BUG FIXED 2026-08-28: the Insect Swarm's renderers use a particle shader - /// ("Game Particles/surfaceShader_masked_particleEnhanced") that has no "_Color" property - /// at all - setting .color on it doesn't throw, but Unity logs "doesn't have a color - /// property '_Color'" on every single access, once per renderer per spawn (confirmed in - /// output_log - this is what the user saw as "an error about colors"). HasProperty check - /// added so this silently skips any renderer whose shader doesn't support it instead of - /// spamming the log - the visual effect was never going to work on those anyway. - public static void ApplyGhostlyTransparency(Entity pet) + /// ЧЕРЕЗ КОД, А НЕ ЧЕРЕЗ XML - см. шапку PetFollowTask.cs: XML-путь существует, но упирается + /// в Type.GetType с именем сборки, а этот не упирается ни во что и заодно позволяет отдать + /// владельца прямо в поле задачи, без поиска по ownedEntities на каждом тике. + /// + /// Приоритет 3: у крысы в entityclasses.xml объявлены AITask-1 (ApproachAndAttackTarget) и + /// AITask-2 (Look), а AITask-3 пустой - то есть следование встаёт ровно туда, где в XML + /// кончился список, и ниже погони за целью. Разводить их приоритетом при этом всё равно + /// недостаточно: задача следования сама отказывается работать, пока у питомца есть цель + /// (NecroFollowOwnerTask.CanExecute), и несёт те же MutexBits=3, что ванильный + /// EAIApproachSpot. + public static void AttachFollowTask(EntityPlayer owner, Entity pet, Patch_ItemActionSpawnEntity_Spawn_PetLimit.PetInfo petInfo) { - Renderer[] renderers = pet.GetComponentsInChildren(); - foreach (Renderer renderer in renderers) + if (!petInfo.UsesFollowTask) { - if (renderer == null || renderer.material == null || !renderer.material.HasProperty("_Color")) - { - continue; - } - Color color = renderer.material.color; - color.a = 0.55f; - renderer.material.color = color; + return; } + EntityAlive alive = pet as EntityAlive; + if (alive == null || alive.aiManager == null || alive.aiManager.tasks == null) + { + Debug.LogWarning("[NecromancerTome] SummonPatch: pet " + pet.entityId + + " has no aiManager - follow task NOT attached"); + return; + } + NecroFollowOwnerTask task = new NecroFollowOwnerTask { OwnerEntityId = owner.entityId }; + if (petInfo.FollowDistance > 0f) + { + task.SlotDistance = petInfo.FollowDistance; + } + task.Init(alive); + alive.aiManager.tasks.AddTask(3, task); + Debug.Log("[NecromancerTome] SummonPatch: follow task attached to pet " + pet.entityId + + " (owner " + owner.entityId + ")"); } } } diff --git a/HarmonySrc/SwarmTargetPatch.cs b/HarmonySrc/SwarmTargetPatch.cs index 0c2b3d2..7fac787 100644 --- a/HarmonySrc/SwarmTargetPatch.cs +++ b/HarmonySrc/SwarmTargetPatch.cs @@ -67,6 +67,16 @@ namespace NecromancerTome public static readonly Dictionary SpeciesByName = new Dictionary { { "necroInsectSwarm", new VultureBasedPetInfo { SkipAlreadyCharmedZombies = true } }, + // Зомбогриф вернулся сюда 2026-09-18 вместе с откатом на летающую ветку. Он снова + // EntityVulture, то есть снова целится в игрока захардкоженным C#-кодом, и без этой + // строки кидался бы на хозяина. SkipAlreadyCharmedZombies=false: подчинять он не + // умеет, значит и обходить подчинённых ему незачем - пусть добивает. + // + // Честно: 29.08 этот же перехват Грифа НЕ СПАС - он просто летал и никого не + // трогал. Тогда причину искать не стали и ушли на наземную ветку. Сейчас мы вернулись + // к тому же месту, и если он снова будет безучастно кружить - копать надо здесь, + // в том, доходит ли до SetAttackTarget хоть что-нибудь. + { "necroZombieGriffin", new VultureBasedPetInfo { SkipAlreadyCharmedZombies = false } }, }; public static readonly Dictionary cachedClassIds = new Dictionary(); diff --git a/ModInfo.xml b/ModInfo.xml index c5db89c..71e95be 100644 --- a/ModInfo.xml +++ b/ModInfo.xml @@ -2,8 +2,8 @@ - + - + diff --git a/NEXUS_DESCRIPTION.bbcode b/NEXUS_DESCRIPTION.bbcode index c101e8a..3c7d6de 100644 --- a/NEXUS_DESCRIPTION.bbcode +++ b/NEXUS_DESCRIPTION.bbcode @@ -23,7 +23,7 @@ To uninstall, delete the folder. The mod adds items and one block, so a save tha [*][b]Adept[/b] (from the start, level 0) - Spirit Stone, Necromancer's Knife, Blue Portal Stone, Pyramid of Spirits [*][b]Adept[/b] (20 / 60 / 100 / 300, levels 1 / 3 / 5 / 15) - Spatial Bracelet and Blood Sphere, then four knife mods: Tears of the Dead, Scavenger's Feast, Grave's Repose, Dark Sense [*][b]Journeyman[/b] (500 / 1400 / 1700) - Scroll of Deviation, knife mods Dead Man's Grip and Dead Storm -[*][b]Apprentice[/b] (2000) - Summon Zombie Dog, Beetles of the Lord, Summon Zombie Griffin, Blood Stone +[*][b]Apprentice[/b] (2000) - Summon Zombie Dog, Pharaoh’s Swarm, Summon Grave Vulture, Blood Stone [*][b]Necromancer[/b] (3000) - Summon Zombie Bear, Summon Zombie Wolf, Banshee's Scroll [*][b]Master[/b] (5000) - Black Portal Stone [/list] @@ -32,7 +32,7 @@ To uninstall, delete the folder. The mod adds items and one block, so a save tha [b]A blade that grows with you.[/b] The [b]Necromancer's Knife[/b] 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, guaranteeing a bag of necromantic ingredients on its death. [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]Beetles of the Lord[/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. [b]Base defence that converts instead of killing.[/b] The [b]Pyramid of Spirits[/b] is a deployable block. While you stand in its radius it charms any uncharmed zombie nearby on its own and sets it alight with cold purple flame, turning it against the rest instead of your walls. Its block menu toggles the effect and shows the radius. diff --git a/NecromancerHarmony.dll b/NecromancerHarmony.dll index cbd5398..e849b38 100644 Binary files a/NecromancerHarmony.dll and b/NecromancerHarmony.dll differ diff --git a/NecromancerHarmony.pdb b/NecromancerHarmony.pdb index 9a2dd93..979d0bd 100644 Binary files a/NecromancerHarmony.pdb and b/NecromancerHarmony.pdb differ diff --git a/README.md b/README.md index 0ee9cc3..ff256e5 100644 --- a/README.md +++ b/README.md @@ -39,11 +39,13 @@ HUD за этим следят два индикатора: череп в ста |---|---|---|---| | Адепт | сразу | 0 | Камень духов, Нож некроманта, Синий портальный камень, Пирамида духов | | Адепт (доп.) | 20 зомби | 1 | Пространственный браслет, Кровавая сфера, мод ножа «Слёзы мертвеца» | -| Адепт (доп.) | 60 / 100 / 300 зомби | 3 / 5 / 15 | Моды ножа: Пир падальщика, Могильный покой, Тёмное чутьё | -| Подмастерье | 500 зомби | 25 | Свиток девиации | +| Адепт (доп.) | 60 / 100 / 300 зомби | 3 / 5 / 15 | Свиток духа крысы; моды ножа: Пир падальщика, Могильный покой, Тёмное чутьё | +| Подмастерье | 500 зомби | 25 | Свиток девиации, Призыв могильного стервятника | +| Подмастерье (доп.) | 1300 зомби | 65 | Призыв зомбособаки | | Подмастерье (доп.) | 1400 / 1700 зомби | 70 / 85 | Моды ножа: Хватка мертвеца, Мёртвая буря | -| Ученик | 2000 зомби | 100 | Призыв зомбособаки, Жуки Властелина, Призыв зомбогрифа, Кровавый камень | -| Некромант | 3000 зомби | 150 | Призыв зомбомедведя, Призыв зомбоволка, Свиток банши | +| Ученик | 2000 зомби | 100 | Призыв зомбомедведя, Рой фараона, Кровавый камень | +| Некромант | 3000 зомби | 150 | Свиток банши | +| Некромант (доп.) | 4000 зомби | 200 | Призыв зомбоволка | | Мастер | 5000 зомби | 250 | Чёрный портальный камень | ## Арсенал @@ -91,14 +93,27 @@ HUD за этим следят два индикатора: череп в ста ## Питомцы -Призывные книги поднимают союзных существ, которые дерутся с зомби, а не с игроком. Питомец не -«следует» за игроком в строгом смысле — просто бродит сам по себе, а если отойдёт дальше -32 блоков и в этот момент не занят боем, его телепортирует обратно к владельцу: +Призывные книги поднимают союзных существ, которые дерутся с зомби, а не с игроком. Все призванные +существа выглядят выцветшими призраками — прозрачность настраивается консольной командой +`necroghost`, той же, что и у призрачных торговцев. -- **Зомбособака**, **Зомбомедведь**, **Зомбоволк**, **Зомбогриф** — постоянные спутники. Можно - держать по одному экземпляру каждого вида одновременно; отзываются обратно в книгу силовой - атакой. -- **Жуки Властелина** — разовый свиток, выпускающий рой. Жуки сами разлетаются по широкому +- **Дух крысы** — первый доступный питомец (Некромантия 3). Он ведёт себя не так, как остальные: + сам не нападает никогда, держится справа-сзади от вас в двух блоках, а упёршись в препятствие, + проходит сквозь него — блоки он не грызёт вовсе. Пока крыса призвана, обычное применение свитка + становится приказом: она бросается на того, на кого наведён ваш прицел (кроме торговцев); если + цели под прицелом нет, появится надпись «Нет цели для атаки». Силовая атака отзывает её. Урон у + крысы почти никакой (5), но её укус делает три вещи: сильно замедляет, рвёт жилу (кровотечение, + которое не заживает, пока крыса грызёт) и оставляет **метку духа** — помеченный зомби полминуты + виден на вашем компасе и карте и получает на четверть больше урона от всего, чем бы вы его ни + били. Саму крысу видно на карте жёлтым. Обгрызая труп добитого зомби, она восстанавливает себе + здоровье. Если ударить саму крысу, она огрызнётся — но никогда на хозяина. +- **Зомбособака**, **Зомбомедведь**, **Зомбоволк**, **Могильный стервятник** — постоянные спутники, и ведут + себя они так же, как Дух крысы: идут за вами, сами ни на кого не нападают и ждут приказа. Разница + в силе: урон растёт от могильного стервятника к зомбоволку, и в отличие от крысы эти убивают быстро — пёс, + медведь и волк вдобавок могут отрывать зомби конечности. Их укус вызывает кровотечение, но метку + духа не ставит. Можно держать по одному экземпляру каждого вида + одновременно; отзываются обратно в книгу силовой атакой. +- **Рой фараона** — разовый свиток, выпускающий рой. Насекомые сами разлетаются по широкому радиусу и жалят зомби; ужаленный переходит на вашу сторону, как от Камня духов. Рой нельзя отозвать обратно, активен может быть только один. Расходуется при использовании. - **Свиток банши** — одноразовый: при открытии вопит голосом банши и поднимает рядом с игроком @@ -243,11 +258,13 @@ Five tiers, each opening part of the arsenal (the table gives both the zombie co |---|---|---|---| | Adept | from the start | 0 | Spirit Stone, Necromancer's Knife, Blue Portal Stone, Pyramid of Spirits | | Adept (extra) | 20 zombies | 1 | Spatial Bracelet, Blood Sphere, the Tears of the Dead knife mod | -| Adept (extra) | 60 / 100 / 300 zombies | 3 / 5 / 15 | Knife mods: Scavenger's Feast, Grave's Repose, Dark Sense | -| Journeyman | 500 zombies | 25 | Scroll of Deviation | +| Adept (extra) | 60 / 100 / 300 zombies | 3 / 5 / 15 | Scroll of the Rat Spirit; knife mods: Scavenger's Feast, Grave's Repose, Dark Sense | +| Journeyman | 500 zombies | 25 | Scroll of Deviation, Summon Grave Vulture | +| Journeyman (extra) | 1300 zombies | 65 | Summon Zombie Dog | | Journeyman (extra) | 1400 / 1700 zombies | 70 / 85 | Knife mods: Dead Man's Grip, Dead Storm | -| Apprentice | 2000 zombies | 100 | Summon Zombie Dog, Beetles of the Lord, Summon Zombie Griffin, Blood Stone | -| Necromancer | 3000 zombies | 150 | Summon Zombie Bear, Summon Zombie Wolf, Banshee's Scroll | +| Apprentice | 2000 zombies | 100 | Summon Zombie Bear, Pharaoh’s Swarm, Blood Stone | +| Necromancer | 3000 zombies | 150 | Banshee's Scroll | +| Necromancer (extra) | 4000 zombies | 200 | Summon Zombie Wolf | | Master | 5000 zombies | 250 | Black Portal Stone | ## Arsenal @@ -295,13 +312,28 @@ Five tiers, each opening part of the arsenal (the table gives both the zombie co ## Pets -Summoning books raise allied creatures that fight zombies rather than the player. A pet does not -"follow" in any strict sense - it wanders on its own, and if it strays further than 32 blocks -while not in combat, it is teleported back to its owner: +Summoning books raise allied creatures that fight zombies rather than the player. Every summoned +creature looks like a washed-out ghost - the transparency is tuned with the `necroghost` console +command, the same one the ghost traders use. -- **Zombie Dog**, **Zombie Bear**, **Zombie Wolf**, **Zombie Griffin** - permanent companions. One - of each kind can be kept at a time; a power attack recalls them into the book. -- **Beetles of the Lord** - a one-shot scroll releasing a swarm. The beetles scatter over a wide +- **Rat Spirit** - the first pet available (Necromancy 3), and the only one that behaves + deliberately rather than by inheritance: it never attacks on its own, keeps two blocks to your + right and just behind you, and where an obstacle stands it passes straight through - it never + gnaws blocks. While it is out, an ordinary use of the scroll becomes an order: the rat goes for + whatever your crosshair rests on (traders excepted); with nothing to attack there, you get "No + target to attack". A power attack recalls it. Its damage is next to nothing (5), but its bite + does three things: it slows heavily, it tears a vein (a bleed that will not close while the rat + keeps at it), and it leaves a **spirit mark** - for half a minute the marked zombie shows on + your compass and map and takes a quarter more damage from everything, whatever you hit it with. + The rat itself shows on the map in yellow. Gnawing on the corpse of a zombie it finished off + heals it back up. Hitting the rat makes it bite back - never at its owner, though. +- **Zombie Dog**, **Zombie Bear**, **Zombie Wolf**, **Grave Vulture** - permanent companions that + behave exactly like the Rat Spirit: they follow you, attack nobody on their own and wait for an + order. What differs is force - the damage climbs from the griffin up to the wolf, and unlike the rat + these kill quickly; the dog, the bear and the wolf can also tear limbs off. Their bite draws blood + but leaves no spirit mark. One of each kind can be kept + at a time; a power attack recalls them into the book. +- **Pharaoh’s Swarm** - a one-shot scroll releasing a swarm. They scatter over a wide radius on their own and sting zombies; a stung zombie switches to your side just like with the Spirit Stone. The swarm cannot be recalled and only one can be active. Consumed on use. - **Banshee's Scroll** - single use: on opening it screams with a banshee's voice and raises a diff --git a/SITE_DESCRIPTION.html b/SITE_DESCRIPTION.html index 8e79889..2fa039d 100644 --- a/SITE_DESCRIPTION.html +++ b/SITE_DESCRIPTION.html @@ -8,7 +8,7 @@ Отдельный навык «Некромантия» качается не за очки перков и не за книги, а по счётчику упокоенных зомби: двадцать упокоенных — один уровень, до 250-го (5000 зомби). Уровень показывает череп в статус-баре, а фиолетовая шкала рядом с полосой опыта — сколько набрано внутри уровня. Пять тиров, каждый порог открывает часть арсенала (в таблице — зомби, в скобках уровень): -
ТирПорогЧто открывается
Адептсразу (0)Камень духов, Нож некроманта, Синий портальный камень, Пирамида духов
Адепт20 (1)Пространственный браслет, Кровавая сфера, мод ножа «Слёзы мертвеца»
Адепт60 / 100 / 300 (3 / 5 / 15)Моды ножа: Пир падальщика, Могильный покой, Тёмное чутьё
Подмастерье500 / 1400 / 1700 (25 / 70 / 85)Свиток девиации, моды ножа Хватка мертвеца и Мёртвая буря
Ученик2000 (100)Призыв зомбособаки, Жуки Властелина, Призыв зомбогрифа, Кровавый камень
Некромант3000 (150)Призыв зомбомедведя, Призыв зомбоволка, Свиток банши
Мастер5000 (250)Чёрный портальный камень
+
ТирПорогЧто открывается
Адептсразу (0)Камень духов, Нож некроманта, Синий портальный камень, Пирамида духов
Адепт20 (1)Пространственный браслет, Кровавая сфера, мод ножа «Слёзы мертвеца»
Адепт60 / 100 / 300 (3 / 5 / 15)Моды ножа: Пир падальщика, Могильный покой, Тёмное чутьё
Подмастерье500 / 1400 / 1700 (25 / 70 / 85)Свиток девиации, моды ножа Хватка мертвеца и Мёртвая буря
Ученик2000 (100)Призыв зомбособаки, Рой фараона, Призыв могильного стервятника, Кровавый камень
Некромант3000 (150)Призыв зомбомедведя, Призыв зомбоволка, Свиток банши
Мастер5000 (250)Чёрный портальный камень

Арсенал

@@ -31,8 +31,8 @@ Призывные книги поднимают союзников, которые дерутся с зомби, а не с вами. Питомец бродит сам по себе, а если отойдёт дальше 32 блоков и не занят боем — телепортируется обратно к владельцу.
    -
  • Зомбособака, зомбомедведь, зомбоволк, зомбогриф — постоянные спутники, по одному экземпляру каждого вида одновременно. Отзываются обратно в книгу силовой атакой.
  • -
  • Жуки Властелина — разовый свиток с роем. Жуки разлетаются по широкому радиусу и жалят зомби; ужаленный переходит на вашу сторону, как от Камня духов. Рой не отзывается, активен может быть только один.
  • +
  • Зомбособака, зомбомедведь, зомбоволк, могильный стервятник — постоянные спутники, по одному экземпляру каждого вида одновременно. Отзываются обратно в книгу силовой атакой.
  • +
  • Рой фараона — разовый свиток с роем. Жуки разлетаются по широкому радиусу и жалят зомби; ужаленный переходит на вашу сторону, как от Камня духов. Рой не отзывается, активен может быть только один.
  • Свиток банши — одноразовый: вопит голосом банши и поднимает рядом небольшую орду. Это не союзники — эти зомби так же опасны для вас, как любые другие.
@@ -86,7 +86,7 @@ Necromancy here is an answer to a curse, not a side branch of crafting. Instead A dedicated Necromancy skill levels not from perk points and not from books, but from your zombie kill count: twenty zombies put to rest make one level, up to level 250 (5000 zombies). The skull in the status bar shows the level, and the purple bar beside the experience bar shows how far you are into it. Five tiers, each threshold opening part of the arsenal (the table gives zombies, with the level in brackets): -
TierThresholdWhat unlocks
Adeptfrom the start (0)Spirit Stone, Necromancer's Knife, Blue Portal Stone, Pyramid of Spirits
Adept20 (1)Spatial Bracelet, Blood Sphere, the Tears of the Dead knife mod
Adept60 / 100 / 300 (3 / 5 / 15)Knife mods: Scavenger's Feast, Grave's Repose, Dark Sense
Journeyman500 / 1400 / 1700 (25 / 70 / 85)Scroll of Deviation, knife mods Dead Man's Grip and Dead Storm
Apprentice2000 (100)Summon Zombie Dog, Beetles of the Lord, Summon Zombie Griffin, Blood Stone
Necromancer3000 (150)Summon Zombie Bear, Summon Zombie Wolf, Banshee's Scroll
Master5000 (250)Black Portal Stone
+
TierThresholdWhat unlocks
Adeptfrom the start (0)Spirit Stone, Necromancer's Knife, Blue Portal Stone, Pyramid of Spirits
Adept20 (1)Spatial Bracelet, Blood Sphere, the Tears of the Dead knife mod
Adept60 / 100 / 300 (3 / 5 / 15)Knife mods: Scavenger's Feast, Grave's Repose, Dark Sense
Journeyman500 / 1400 / 1700 (25 / 70 / 85)Scroll of Deviation, knife mods Dead Man's Grip and Dead Storm
Apprentice2000 (100)Summon Zombie Dog, Pharaoh’s Swarm, Summon Grave Vulture, Blood Stone
Necromancer3000 (150)Summon Zombie Bear, Summon Zombie Wolf, Banshee's Scroll
Master5000 (250)Black Portal Stone

Arsenal

@@ -110,7 +110,7 @@ Summoning books raise allies that fight zombies, not you. A pet wanders on its o
  • Zombie dog, bear, wolf and griffin — permanent companions, one of each kind at a time. A power attack recalls them into the book.
  • -
  • Beetles of the Lord — a one-shot scroll releasing a swarm. The beetles scatter over a wide radius and sting zombies; a stung zombie switches to your side just like with the Spirit Stone. The swarm cannot be recalled, and only one can be active.
  • +
  • Pharaoh’s Swarm — a one-shot scroll releasing a swarm. They scatter over a wide radius and sting zombies; a stung zombie switches to your side just like with the Spirit Stone. The swarm cannot be recalled, and only one can be active.
  • Banshee's Scroll — single use: it screams with a banshee's voice and raises a small horde next to you. These are not allies — they are as dangerous to you as any other zombies.