Оссуарий-котёл: одна ячейка образца, хранилище 6, перенос старых ячеек, свечение, огонёк, рецепт в кузне (#66, #69)
- blocks.xml: 6 ячеек хранилища (цикл вдвое дольше - добыча прежняя); видно одну ячейку образца и одну топлива, остальные спрятаны (в данных их по-прежнему 3 и 6 - сейвы не режутся). - Перенос: содержимое спрятанных ячеек у уже поставленных оссуариев уходит в хранилище (проверено на test8: уголь, камень, сферы). - Свечение рун и окошек и сфера на крышке - пока работает (OssuaryGlow). - Огонёк дикого духа: вылет из окошка с уходом в землю и затуханием, возврат с другой стороны в противоположное окошко; только пока работает. В игре пока не увиден - разобрать (necroossuary wisp on|off). - Рецепт - кузня, железо вместо дерева и юкки (плавильня / лом). - Описание на 13 языках под котёл и вольных духов. - Консоль: necroossuary info / put (отладка ячеек). - DLL включает и #72, #74. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
e3cdff0323
commit
c2610da59a
+59
-46
@@ -374,72 +374,85 @@ necroPossessNoCarrion,items,Item,,,,"No carrion nearby - land next to a corpse",
|
||||
necroPossessNoCarrionGround,items,Item,,,,"No carrion nearby - stop next to a corpse",,"Kein Aas in der Nähe - neben einer Leiche anhalten","No hay carroña cerca: detente junto a un cadáver","Pas de charogne ici - arrêtez-vous près d'un cadavre","Nessuna carogna vicino - fermati accanto a un cadavere",近くに死肉がない - 死体のそばで止まれ,"근처에 썩은 고기가 없다 - 시체 옆에 멈춰라","Brak padliny w pobliżu - zatrzymaj się przy zwłokach","Nenhuma carniça por perto - pare junto a um cadáver","Падали рядом нет - остановитесь возле трупа","Yakında leş yok - bir cesedin yanında dur",附近没有腐肉 - 停在尸体旁边,附近沒有腐肉 - 停在屍體旁邊
|
||||
necroSnakeSpirit,entityclasses,Entity,,,,Snake Spirit,,Schlangengeist,Espíritu de serpiente,Esprit du serpent,Spirito del serpente,蛇の霊,뱀의 영혼,Duch węża,Espírito da serpente,Дух змеи,Yılan Ruhu,蛇灵,蛇靈
|
||||
necroGeoOssuary,blocks,Block,,,,Geo-Ossuary,,Geo-Ossarium,Geo-osario,Géo-ossuaire,Geo-ossario,ジオ納骨堂,지오 납골당,Geo-ossuarium,Geo-ossário,Гео-оссуарий,Jeo-Kemiklik,地脉骨堂,地脈骨堂
|
||||
necroGeoOssuaryDesc,blocks,Block,,,,"An ossuary of bones and skulls that draws stone and ore up from the depths and feeds on blood.
|
||||
Put up to three resources into the mod slots: stone, coal, nitrate, iron or oil shale. The resource stays in its slot and decides what the matching output cell produces.
|
||||
Every 5-8 hours each output cell yields a full stack of its resource and drains a tenth of a Blood Sphere's durability: one sphere lasts ten stacks.
|
||||
necroGeoOssuaryDesc,blocks,Block,,,,"A riveted iron cauldron full of bones, standing on a bone tripod. Wild spirits dig here - not out of obedience, but for a price: a Blood Sphere in the hollow of the lid.
|
||||
Put one resource into the sample slot: stone, coal, nitrate, iron or oil shale. The spirits bring it up from the depths into six storage cells.
|
||||
Every 10-16 hours each storage cell yields a full stack and drains a tenth of the sphere's durability: one sphere lasts ten stacks.
|
||||
While the spirits are at work, the cauldron glows from within.
|
||||
Works anywhere, even underground.
|
||||
Repair with bones.
|
||||
A Blood Stone among the fuel runs it forever.",,"Ein Beinhaus aus Knochen und Schädeln, das Stein und Erz aus der Tiefe holt und sich von Blut nährt.
|
||||
Lege bis zu drei Ressourcen in die Mod-Plätze: Stein, Kohle, Salpeter, Eisen oder Ölschiefer. Die Ressource bleibt im Platz und bestimmt, was die zugehörige Ausgabezelle herstellt.
|
||||
Alle 5-8 Stunden liefert jede Ausgabezelle einen vollen Stapel ihrer Ressource und verbraucht ein Zehntel der Haltbarkeit einer Blutsphäre: eine Sphäre reicht für zehn Stapel.
|
||||
A Blood Stone instead of the sphere pays them forever.",,"Ein genieteter Eisenkessel voller Knochen auf einem Knochendreibein. Hier graben wilde Geister - nicht aus Gehorsam, sondern gegen Bezahlung: eine Blutsphäre in der Mulde des Deckels.
|
||||
Lege eine Ressource in den Musterplatz: Stein, Kohle, Salpeter, Eisen oder Ölschiefer. Die Geister holen sie aus der Tiefe in sechs Lagerzellen.
|
||||
Alle 10-16 Stunden liefert jede Lagerzelle einen vollen Stapel und verbraucht ein Zehntel der Haltbarkeit der Sphäre: eine Sphäre reicht für zehn Stapel.
|
||||
Solange die Geister arbeiten, glüht der Kessel von innen.
|
||||
Funktioniert überall, auch unter der Erde.
|
||||
Mit Knochen reparieren.
|
||||
Ein Blutstein im Brennstoff lässt es für immer laufen.","Un osario de huesos y calaveras que extrae piedra y mineral de las profundidades y se alimenta de sangre.
|
||||
Coloca hasta tres recursos en las ranuras de mods: piedra, carbón, nitrato, hierro o esquisto bituminoso. El recurso permanece en su ranura y decide qué produce la celda de salida correspondiente.
|
||||
Cada 5-8 horas cada celda de salida produce una pila completa de su recurso y consume una décima parte de la durabilidad de una esfera de sangre: una esfera rinde diez pilas.
|
||||
Ein Blutstein statt der Sphäre bezahlt sie für immer.","Un caldero de hierro remachado lleno de huesos sobre un trípode de hueso. Aquí excavan espíritus salvajes, no por obediencia, sino a cambio de un precio: una esfera de sangre en el hueco de la tapa.
|
||||
Pon un recurso en la ranura de muestra: piedra, carbón, nitrato, hierro o pizarra bituminosa. Los espíritus lo sacan de las profundidades a seis celdas de almacén.
|
||||
Cada 10-16 horas cada celda de almacén da una pila completa y gasta una décima parte de la durabilidad de la esfera: una esfera dura diez pilas.
|
||||
Mientras los espíritus trabajan, el caldero brilla por dentro.
|
||||
Funciona en cualquier lugar, incluso bajo tierra.
|
||||
Se repara con huesos.
|
||||
Una Piedra de sangre entre el combustible lo hace funcionar para siempre.","Un ossuaire d'os et de crânes qui tire la pierre et le minerai des profondeurs et se nourrit de sang.
|
||||
Placez jusqu'à trois ressources dans les emplacements de mods : pierre, charbon, nitrate, fer ou schiste bitumineux. La ressource reste dans son emplacement et détermine ce que produit la case de sortie correspondante.
|
||||
Toutes les 5 à 8 heures, chaque case de sortie produit une pile complète de sa ressource et consomme un dixième de la durabilité d'une sphère de sang : une sphère suffit pour dix piles.
|
||||
Una piedra de sangre en lugar de la esfera les paga para siempre.","Un chaudron de fer riveté rempli d'os, posé sur un trépied d'os. Des esprits sauvages y creusent - non par obéissance, mais contre paiement : une sphère de sang dans le creux du couvercle.
|
||||
Placez une ressource dans l'emplacement d'échantillon : pierre, charbon, salpêtre, fer ou schiste bitumineux. Les esprits la remontent des profondeurs dans six cases de stockage.
|
||||
Toutes les 10-16 heures, chaque case de stockage donne une pile complète et consomme un dixième de la durabilité de la sphère : une sphère dure dix piles.
|
||||
Tant que les esprits travaillent, le chaudron luit de l'intérieur.
|
||||
Fonctionne partout, même sous terre.
|
||||
Se répare avec des os.
|
||||
Une Pierre de sang parmi le combustible le fait fonctionner pour toujours.","Un ossario di ossa e teschi che estrae pietra e minerale dalle profondità e si nutre di sangue.
|
||||
Inserisci fino a tre risorse negli slot dei mod: pietra, carbone, nitrato, ferro o scisto bituminoso. La risorsa resta nel suo slot e decide cosa produce la casella di uscita corrispondente.
|
||||
Ogni 5-8 ore ogni casella di uscita produce una pila completa della sua risorsa e consuma un decimo della durabilità di una sfera di sangue: una sfera basta per dieci pile.
|
||||
Une pierre de sang à la place de la sphère les paie pour toujours.","Un calderone di ferro rivettato pieno di ossa, su un treppiede d'osso. Qui scavano spiriti selvaggi - non per obbedienza, ma a pagamento: una sfera di sangue nell'incavo del coperchio.
|
||||
Metti una risorsa nello slot campione: pietra, carbone, nitrato, ferro o scisto bituminoso. Gli spiriti la portano su dalle profondità in sei celle di deposito.
|
||||
Ogni 10-16 ore ogni cella di deposito produce una pila completa e consuma un decimo della durabilità della sfera: una sfera basta per dieci pile.
|
||||
Mentre gli spiriti lavorano, il calderone brilla dall'interno.
|
||||
Funziona ovunque, anche sottoterra.
|
||||
Si ripara con le ossa.
|
||||
Una Pietra di sangue tra il combustibile lo fa funzionare per sempre.","骨と頭蓋骨でできた納骨堂。血を糧に、地の底から石と鉱石を引き上げる。
|
||||
MODスロットに資源を3つまで入れる:石、石炭、硝石、鉄、オイルシェール。資源はスロットに残り、対応する出力マスで何を生み出すかを決める。
|
||||
5~8時間ごとに各出力マスが資源1スタックを生み出し、血の球の耐久度を10分の1消費する。球1つで10スタック分。
|
||||
Una pietra di sangue al posto della sfera li paga per sempre.","骨の三脚に載った、骨でいっぱいのリベット留めの鉄の大釜。ここで掘るのは野生の霊――服従ではなく報酬のために。報酬は蓋のくぼみに置く血の球だ。
|
||||
見本スロットに資源を1つ入れる:石、石炭、硝石、鉄、オイルシェール。霊たちは地の底からそれを6つの保管セルへ運び上げる。
|
||||
10~16時間ごとに各保管セルが1スタックを生み、血の球の耐久値を10分の1消費する。球1つで10スタック分。
|
||||
霊たちが働いている間、大釜は内側から光る。
|
||||
どこでも動く。地下でも。
|
||||
骨で修理する。
|
||||
燃料に血の石を入れれば永遠に動き続ける。","뼈와 해골로 만든 납골당. 피를 먹고 땅속 깊은 곳에서 돌과 광석을 끌어올린다.
|
||||
모드 슬롯에 자원을 최대 세 개까지 넣는다: 돌, 석탄, 질산염, 철, 오일 셰일. 자원은 슬롯에 남아 대응하는 출력 칸에서 무엇을 만들지 정한다.
|
||||
5~8시간마다 각 출력 칸이 자원 한 묶음을 만들고 피의 구슬 내구도의 10분의 1을 소모한다. 구슬 하나로 열 묶음.
|
||||
어디서든 작동한다. 땅속에서도.
|
||||
骨で修理できる。
|
||||
球の代わりに血の石を置けば、永遠に報酬を払える。","뼈 삼각대 위에 놓인, 뼈로 가득한 리벳 박은 철 가마솥. 이곳에서는 야생 영혼들이 땅을 판다 - 복종이 아니라 대가를 받고. 대가는 뚜껑 홈에 놓인 피의 구슬이다.
|
||||
견본 칸에 자원 하나를 넣으세요: 돌, 석탄, 초석, 철 또는 오일 셰일. 영혼들이 깊은 곳에서 그것을 여섯 개의 보관 칸으로 끌어올린다.
|
||||
10-16시간마다 각 보관 칸이 한 묶음을 내고 구슬 내구도의 10분의 1을 소모한다: 구슬 하나로 열 묶음.
|
||||
영혼들이 일하는 동안 가마솥은 안에서부터 빛난다.
|
||||
어디서든 작동한다. 지하에서도.
|
||||
뼈로 수리한다.
|
||||
연료 칸에 피의 돌을 넣으면 영원히 작동한다.","Ossuarium z kości i czaszek, które wyciąga kamień i rudę z głębin i żywi się krwią.
|
||||
Włóż do trzech surowców w sloty modów: kamień, węgiel, saletrę, żelazo lub łupek bitumiczny. Surowiec zostaje w slocie i decyduje, co wytwarza odpowiadające mu pole wyjściowe.
|
||||
Co 5-8 godzin każde pole wyjściowe daje pełny stos swojego surowca i zużywa jedną dziesiątą wytrzymałości krwawej sfery: jedna sfera starcza na dziesięć stosów.
|
||||
구슬 대신 피의 돌을 두면 영원히 대가를 치른다.","Nitowany żelazny kocioł pełen kości na kościanym trójnogu. Kopią tu dzikie duchy - nie z posłuszeństwa, lecz za zapłatę: krwawą sferę we wgłębieniu pokrywy.
|
||||
Włóż jeden surowiec do slotu próbki: kamień, węgiel, saletrę, żelazo lub łupek bitumiczny. Duchy wynoszą go z głębin do sześciu komórek magazynu.
|
||||
Co 10-16 godzin każda komórka magazynu daje pełny stos i zużywa jedną dziesiątą wytrzymałości sfery: jedna sfera starcza na dziesięć stosów.
|
||||
Póki duchy pracują, kocioł świeci od środka.
|
||||
Działa wszędzie, nawet pod ziemią.
|
||||
Naprawiane kośćmi.
|
||||
Krwawy kamień wśród paliwa zasila je na zawsze.","Um ossário de ossos e crânios que extrai pedra e minério das profundezas e se alimenta de sangue.
|
||||
Coloque até três recursos nos espaços de mods: pedra, carvão, nitrato, ferro ou xisto betuminoso. O recurso fica no espaço e decide o que a célula de saída correspondente produz.
|
||||
A cada 5-8 horas cada célula de saída produz uma pilha completa do seu recurso e gasta um décimo da durabilidade de uma esfera de sangue: uma esfera rende dez pilhas.
|
||||
Funciona em qualquer lugar, até debaixo da terra.
|
||||
Naprawiany kośćmi.
|
||||
Krwawy kamień zamiast sfery płaci im na zawsze.","Um caldeirão de ferro rebitado cheio de ossos, sobre um tripé de osso. Aqui cavam espíritos selvagens - não por obediência, mas por um preço: uma esfera de sangue na cavidade da tampa.
|
||||
Coloque um recurso no espaço de amostra: pedra, carvão, nitrato, ferro ou xisto betuminoso. Os espíritos o trazem das profundezas para seis células de armazenamento.
|
||||
A cada 10-16 horas, cada célula de armazenamento rende uma pilha cheia e gasta um décimo da durabilidade da esfera: uma esfera dura dez pilhas.
|
||||
Enquanto os espíritos trabalham, o caldeirão brilha por dentro.
|
||||
Funciona em qualquer lugar, até no subsolo.
|
||||
Repara-se com ossos.
|
||||
Uma Pedra de sangue entre o combustível o mantém funcionando para sempre.","Оссуарий из костей и черепов: питается кровью и вытягивает из недр камень и руду.
|
||||
В ячейки модификаций положите до трёх ресурсов: камень, уголь, селитру, железо или сланец. Ресурс остаётся в ячейке и задаёт, что производит соответствующая ячейка продукции.
|
||||
Раз в 5-8 часов каждая ячейка продукции выдаёт полный стак своего ресурса и тратит десятую часть прочности кровавой сферы: одной сферы хватает на десять стаков.
|
||||
Uma pedra de sangue no lugar da esfera os paga para sempre.","Клёпаный железный котёл, полный костей, на костяной треноге. Здесь копают дикие духи - не из подчинения, а за плату: кровавую сферу в углублении крышки.
|
||||
Положите в ячейку образца один ресурс: камень, уголь, селитру, железо или сланец. Духи поднимают его из недр в шесть ячеек хранилища.
|
||||
Раз в 10-16 часов каждая ячейка хранилища выдаёт полный стак и тратит десятую часть прочности сферы: одной сферы хватает на десять стаков.
|
||||
Пока духи работают, котёл светится изнутри.
|
||||
Работает где угодно, даже под землёй.
|
||||
Чинится костями.
|
||||
Кровавый камень среди топлива питает его вечно.","Kemik ve kafataslarından yapılmış, kanla beslenen ve derinlerden taş ile cevher çeken bir kemiklik.
|
||||
Mod yuvalarına en fazla üç kaynak koy: taş, kömür, nitrat, demir veya bitümlü şist. Kaynak yuvasında kalır ve karşılık gelen çıktı hücresinin ne üreteceğini belirler.
|
||||
Her 5-8 saatte bir her çıktı hücresi kendi kaynağından tam bir yığın üretir ve bir Kan Küresi'nin dayanıklılığının onda birini harcar: bir küre on yığın yeter.
|
||||
Кровавый камень вместо сферы платит им вечно.","Kemik bir üçayak üzerinde duran, kemiklerle dolu perçinli demir bir kazan. Burada vahşi ruhlar kazar - itaatten değil, bir bedel karşılığında: kapağın oyuğundaki bir Kan Küresi.
|
||||
Numune yuvasına bir kaynak koy: taş, kömür, güherçile, demir veya bitümlü şist. Ruhlar onu derinliklerden altı depo hücresine çıkarır.
|
||||
Her 10-16 saatte bir her depo hücresi tam bir yığın verir ve kürenin dayanıklılığının onda birini harcar: bir küre on yığına yeter.
|
||||
Ruhlar çalışırken kazan içten parlar.
|
||||
Her yerde çalışır, yer altında bile.
|
||||
Kemikle onarılır.
|
||||
Yakıtlar arasındaki bir Kan Taşı onu sonsuza dek çalıştırır.","由骨头与头骨筑成的骨堂,以血为食,从地底深处汲取石头与矿石。
|
||||
在模组槽中放入最多三种资源:石头、煤、硝石、铁或油页岩。资源会留在槽中,并决定对应产出格产出什么。
|
||||
每5-8小时,每个产出格产出一整组对应资源,并消耗血之球十分之一的耐久:一颗血之球可产出十组。
|
||||
可在任何地方运作,包括地下。
|
||||
Küre yerine bir Kan Taşı onlara sonsuza dek öder.","一口铆接铁锅,装满骨头,立在骨制三脚架上。在这里挖掘的是野生灵魂——不是出于服从,而是为了报酬:锅盖凹槽里的一颗血之球。
|
||||
在样本槽中放入一种资源:石头、煤、硝石、铁或油页岩。灵魂们会把它从地底运到六个储存格中。
|
||||
每10-16小时,每个储存格产出一整组,并消耗血之球十分之一的耐久:一颗球可用十组。
|
||||
灵魂们工作时,铁锅会从内部发光。
|
||||
在任何地方都能运作,即使在地下。
|
||||
用骨头修理。
|
||||
燃料槽中放入血石即可永久运转。","由骨頭與頭骨築成的骨堂,以血為食,從地底深處汲取石頭與礦石。
|
||||
在模組槽中放入最多三種資源:石頭、煤、硝石、鐵或油頁岩。資源會留在槽中,並決定對應產出格產出什麼。
|
||||
每5-8小時,每個產出格產出一整組對應資源,並消耗血之球十分之一的耐久:一顆血之球可產出十組。
|
||||
可在任何地方運作,包括地下。
|
||||
用血之石代替血之球,便能永远支付报酬。","一口鉚接鐵鍋,裝滿骨頭,立在骨製三腳架上。在這裡挖掘的是野生靈魂——不是出於服從,而是為了報酬:鍋蓋凹槽裡的一顆血之球。
|
||||
在樣本槽中放入一種資源:石頭、煤、硝石、鐵或油頁岩。靈魂們會把它從地底運到六個儲存格中。
|
||||
每10-16小時,每個儲存格產出一整組,並消耗血之球十分之一的耐久:一顆球可用十組。
|
||||
靈魂們工作時,鐵鍋會從內部發光。
|
||||
在任何地方都能運作,即使在地下。
|
||||
用骨頭修理。
|
||||
燃料槽中放入血石即可永久運轉。"
|
||||
用血之石代替血之球,便能永遠支付報酬。"
|
||||
xuiNecroGeoOssuary,UI,XUI,,,,Geo-Ossuary,,Geo-Ossarium,Geo-osario,Géo-ossuaire,Geo-ossario,ジオ納骨堂,지오 납골당,Geo-ossuarium,Geo-ossário,Гео-оссуарий,Jeo-Kemiklik,地脉骨堂,地脈骨堂
|
||||
necroGeoOssuaryFuel,UI,XUI,,,,Blood Spheres,,Blutsphären,Esferas de sangre,Sphères de sang,Sfere di sangue,血の球,피의 구슬,Krwawe sfery,Esferas de sangue,Кровавые сферы,Kan Küreleri,血之球,血之球
|
||||
necroGeoOssuaryEmpty,UI,Use Prompt,,,,{0} Open {1} ([DECEA3]Empty[-]),,{0} Öffnen {1} ([DECEA3]Leer[-]),{0} Abrir {1} ([DECEA3]Vacío[-]),{0} Ouvrir {1} ([DECEA3]Vide[-]),{0} Apri {1} ([DECEA3]Vuoto[-]),{0} 開く {1} ([DECEA3]空[-]),{0} 열기 {1} ([DECEA3]비어 있음[-]),{0} Otwórz {1} ([DECEA3]Pusto[-]),{0} Abrir {1} ([DECEA3]Vazio[-]),{0}: открыть ({1}) ([DECEA3]пусто[-]),{0} Aç {1} ([DECEA3]Boş[-]),{0} 打开 {1} ([DECEA3]空[-]),{0} 打開 {1} ([DECEA3]空[-])
|
||||
|
||||
|
+6
-3
@@ -209,9 +209,12 @@
|
||||
<property name="ItemIconBackdrop" value="ui_game_symbol_mining" />
|
||||
<property name="ItemBackgroundColor" value="140,20,20,255" />
|
||||
<property name="FuelTypes" value="{blood,resourceBloodSphere,resourceBloodStone}" /><!-- камень - вечное топливо (GeoOssuaryPatch.removeFuel) -->
|
||||
<property name="OutputTypes" value="{os1,blood,1,0,1,resourceRockSmall,resourceRockSmall,1,1,6000,10000,collector_complete_item}{os2,blood,1,0,1,resourceRockSmall,resourceRockSmall,1,1,6000,10000,collector_complete_item}{os3,blood,1,0,1,resourceRockSmall,resourceRockSmall,1,1,6000,10000,collector_complete_item}" />
|
||||
<property name="Outputs" value="os1,os2,os3" />
|
||||
<property name="OutputGridHeight" value="1" />
|
||||
<!-- #66 (22.10.2026): хранилище - 2 ряда (6 ячеек, «Хранилище добычи - увеличить»), все шесть выходов берут ресурс из
|
||||
одной ячейки образца (GeoOssuary.ModSlotOf); время цикла вдвое дольше - общая добыча прежняя. Видно одну ячейку образца
|
||||
и одну топлива; в данных их по-прежнему 3 и 6 (FuelGridHeight 2) - уменьшение обрезало бы сохранённое. -->
|
||||
<property name="OutputTypes" value="{os1,blood,1,0,1,resourceRockSmall,resourceRockSmall,1,1,12000,20000,collector_complete_item}{os2,blood,1,0,1,resourceRockSmall,resourceRockSmall,1,1,12000,20000,collector_complete_item}{os3,blood,1,0,1,resourceRockSmall,resourceRockSmall,1,1,12000,20000,collector_complete_item}{os4,blood,1,0,1,resourceRockSmall,resourceRockSmall,1,1,12000,20000,collector_complete_item}{os5,blood,1,0,1,resourceRockSmall,resourceRockSmall,1,1,12000,20000,collector_complete_item}{os6,blood,1,0,1,resourceRockSmall,resourceRockSmall,1,1,12000,20000,collector_complete_item}" />
|
||||
<property name="Outputs" value="os1,os2,os3,os4,os5,os6" />
|
||||
<property name="OutputGridHeight" value="2" />
|
||||
<property name="FuelGridHeight" value="2" />
|
||||
<property name="RequiredMods" value="resourceRockSmall,resourceRockSmall,resourceRockSmall" />
|
||||
<property name="RequiredModsOnly" value="true" />
|
||||
|
||||
+20
-6
@@ -480,12 +480,19 @@
|
||||
именно тогда (правило «порог по нужде, а не по силе»). Тег и unlock_level группы 2 в
|
||||
progression.xml обязаны совпадать. -->
|
||||
<append xpath="/recipes">
|
||||
<recipe name="necroGeoOssuary" count="1" craft_area="workbench" tags="learnable,workbenchCrafting,necroNecromancyJourneyman">
|
||||
<!-- 22.10.2026 (#66): котёл кованый - в кузню («Он весь из себя кованный, так что будет логично»): железо вместо дерева
|
||||
и юкки, кости и прах - ноги и содержимое; две версии кузни (плавильня / лом). -->
|
||||
<recipe name="necroGeoOssuary" count="1" craft_area="forge" craft_time="60" material_based="true" tags="learnable,necroNecromancyJourneyman,use_smelter">
|
||||
<ingredient name="unit_iron" count="60"/>
|
||||
<ingredient name="resourceBone" count="400"/>
|
||||
<ingredient name="resourceZombieAsh" count="200"/>
|
||||
<ingredient name="resourceNecromancerBlood" count="1"/>
|
||||
</recipe>
|
||||
<recipe name="necroGeoOssuary" count="1" craft_area="forge" craft_time="60" tags="learnable,necroNecromancyJourneyman,replace_smelter">
|
||||
<ingredient name="resourceScrapIron" count="60"/>
|
||||
<ingredient name="resourceBone" count="400"/>
|
||||
<ingredient name="resourceZombieAsh" count="200"/>
|
||||
<ingredient name="resourceNecromancerBlood" count="1"/>
|
||||
<ingredient name="resourceWood" count="100"/>
|
||||
<ingredient name="resourceYuccaFibers" count="50"/>
|
||||
</recipe>
|
||||
</append>
|
||||
|
||||
@@ -580,12 +587,19 @@
|
||||
<ingredient name="medicalBloodBag" count="4"/>
|
||||
<ingredient name="resourceNecroBlackRoseJuice" count="1"/>
|
||||
</recipe>
|
||||
<recipe name="necroGeoOssuary" count="1" craft_area="workbench" tags="learnable,workbenchCrafting,necroNecromancyJourneyman">
|
||||
<!-- 22.10.2026 (#66): котёл кованый - в кузню («Он весь из себя кованный, так что будет логично»): железо вместо дерева
|
||||
и юкки, кости и прах - ноги и содержимое; две версии кузни (плавильня / лом). -->
|
||||
<recipe name="necroGeoOssuary" count="1" craft_area="forge" craft_time="60" material_based="true" tags="learnable,necroNecromancyJourneyman,use_smelter">
|
||||
<ingredient name="unit_iron" count="60"/>
|
||||
<ingredient name="resourceBone" count="400"/>
|
||||
<ingredient name="resourceZombieAsh" count="200"/>
|
||||
<ingredient name="resourceNecroBlackRoseJuice" count="1"/>
|
||||
</recipe>
|
||||
<recipe name="necroGeoOssuary" count="1" craft_area="forge" craft_time="60" tags="learnable,necroNecromancyJourneyman,replace_smelter">
|
||||
<ingredient name="resourceScrapIron" count="60"/>
|
||||
<ingredient name="resourceBone" count="400"/>
|
||||
<ingredient name="resourceZombieAsh" count="200"/>
|
||||
<ingredient name="resourceNecroBlackRoseJuice" count="1"/>
|
||||
<ingredient name="resourceWood" count="100"/>
|
||||
<ingredient name="resourceYuccaFibers" count="50"/>
|
||||
</recipe>
|
||||
<recipe name="necroBoneSteedPlaceable" count="1" craft_time="120" craft_area="workbench" tags="learnable,workbenchCrafting,necroNecromancyJourneyman">
|
||||
<ingredient name="resourceBone" count="300"/>
|
||||
|
||||
@@ -53,15 +53,23 @@ namespace NecromancerTome
|
||||
|
||||
public static bool Is(TileEntityCollector te) => te != null && Is(te.blockValue.Block);
|
||||
|
||||
/// <summary>os1 -> 0, os2 -> 1, os3 -> 2; иначе -1.</summary>
|
||||
/// <summary>Ячейка образца для выхода: у котла (#66, 22.10) образец один - все выходы os1..os6 берут ресурс из
|
||||
/// ячейки 0 («Ячейка под образец - одна»). Не наш выход - -1.</summary>
|
||||
public static int ModSlotOf(BlockCollector.OutputType outputType)
|
||||
{
|
||||
string name = outputType?.Name;
|
||||
if (name == null || name.Length != 3 || !name.StartsWith("os"))
|
||||
return -1;
|
||||
return name[2] - '1';
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>Видимых в окне ячеек образца и топлива (#66, 22.10: «Ячейка под образец - одна; ячейка под кровавую
|
||||
/// сферу (или камень) - одна»). В данных их по-прежнему 3 и 6: длины пишутся в сейв, а при загрузке игра обрезает
|
||||
/// лишнее по конфигу - уменьшение молча выбросило бы содержимое. Лишние прячутся в окне, их старое содержимое
|
||||
/// переносит в хранилище Patch_TileEntityCollector_UpdateTick_GeoOssuaryMigrate.</summary>
|
||||
public const int VisibleModSlots = 1;
|
||||
public const int VisibleFuelSlots = 1;
|
||||
|
||||
/// <summary>Ресурс в ячейке модификации, если он из списка; иначе null.</summary>
|
||||
public static ItemClass ResourceIn(TileEntityCollector te, int modSlot)
|
||||
{
|
||||
@@ -82,8 +90,11 @@ namespace NecromancerTome
|
||||
public static void Postfix(XUiC_DewCollectorModGrid __instance, TileEntityCollector te)
|
||||
{
|
||||
bool ossuary = GeoOssuary.Is(te);
|
||||
foreach (XUiC_ItemStack slot in __instance.itemControllers)
|
||||
for (int i = 0; i < __instance.itemControllers.Length; i++)
|
||||
{
|
||||
XUiC_ItemStack slot = __instance.itemControllers[i];
|
||||
// окно у всех коллекторов общее - чужим возвращаем все ячейки
|
||||
slot.ViewComponent.IsVisible = !ossuary || i < GeoOssuary.VisibleModSlots;
|
||||
if (!(slot is XUiC_RequiredItemStack required))
|
||||
continue;
|
||||
required.OverrideStackCount = ossuary ? 1 : -1;
|
||||
@@ -251,4 +262,104 @@ namespace NecromancerTome
|
||||
__result = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Одна ячейка топлива в окне (GeoOssuary.VisibleFuelSlots): остальные ячейки сетки и их подсказки-картинки
|
||||
/// спрятаны. Сетка общая для всех коллекторов (и для катализатора) - чужим всё показываем обратно.</summary>
|
||||
public static class GeoOssuaryFuelGrid
|
||||
{
|
||||
public static void Apply(XUiC_CollectorFuelGrid _grid)
|
||||
{
|
||||
if (_grid?.tileEntity == null || _grid.fuelBehavior != XUiC_CollectorFuelGrid.FuelBehavior.Fuel)
|
||||
return;
|
||||
bool ossuary = GeoOssuary.Is(_grid.tileEntity);
|
||||
int n = _grid.tileEntity.FuelGridLength;
|
||||
for (int i = 0; i < _grid.itemControllers.Length; i++)
|
||||
{
|
||||
bool show = i < n && (!ossuary || i < GeoOssuary.VisibleFuelSlots);
|
||||
if (_grid.itemControllers[i].ViewComponent.IsVisible != show && (ossuary || i < n))
|
||||
_grid.itemControllers[i].ViewComponent.IsVisible = show;
|
||||
if (_grid.previewSprites != null && i < _grid.previewSprites.Length && _grid.previewSprites[i] != null && ossuary && i >= GeoOssuary.VisibleFuelSlots)
|
||||
_grid.previewSprites[i].IsVisible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(XUiC_CollectorFuelGrid), nameof(XUiC_CollectorFuelGrid.SetTileEntity))]
|
||||
public static class Patch_XUiC_CollectorFuelGrid_SetTileEntity_GeoOssuary
|
||||
{
|
||||
public static void Postfix(XUiC_CollectorFuelGrid __instance) => GeoOssuaryFuelGrid.Apply(__instance);
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(XUiC_CollectorFuelGrid), nameof(XUiC_CollectorFuelGrid.OnTileEntityChanged))]
|
||||
public static class Patch_XUiC_CollectorFuelGrid_OnTileEntityChanged_GeoOssuary
|
||||
{
|
||||
public static void Postfix(XUiC_CollectorFuelGrid __instance) => GeoOssuaryFuelGrid.Apply(__instance);
|
||||
}
|
||||
|
||||
/// <summary>Shift-клик топливом: у оссуария - только в видимую ячейку (ваниль раскладывает по всей сетке).</summary>
|
||||
[HarmonyPatch(typeof(XUiC_CollectorFuelGrid), nameof(XUiC_CollectorFuelGrid.TryAddFuel))]
|
||||
public static class Patch_XUiC_CollectorFuelGrid_TryAddFuel_GeoOssuary
|
||||
{
|
||||
public static bool Prefix(XUiC_CollectorFuelGrid __instance, ItemClass newItemClass, ItemStack newItemStack, ref bool __result)
|
||||
{
|
||||
if (__instance.fuelBehavior != XUiC_CollectorFuelGrid.FuelBehavior.Fuel || !GeoOssuary.Is(__instance.tileEntity))
|
||||
return true;
|
||||
if (System.Array.IndexOf(__instance.tileEntity.GetFuelTypes(), newItemClass.Name) >= 0)
|
||||
{
|
||||
XUiC_ItemStack slot = __instance.itemControllers[0];
|
||||
if (slot.ItemStack.IsEmpty())
|
||||
{
|
||||
slot.ItemStack = newItemStack.Clone();
|
||||
newItemStack.count = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
int n = slot.ItemStack.StackTransferCount(newItemStack);
|
||||
slot.ItemStack.count += n;
|
||||
newItemStack.count -= n;
|
||||
}
|
||||
__instance.UpdateBackend(__instance.getUISlots());
|
||||
}
|
||||
__result = newItemStack.count == 0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Переход на котёл (#66, 22.10: «у уже поставленных оссуариев при первой загрузке новой версии всё лишнее из
|
||||
/// старых ячеек образцов и топлива переложить в новое хранилище - ничего не теряется»). Сервер на каждом тике смотрит
|
||||
/// спрятанные ячейки (образцы 2-3, топливо 2-6) и перекладывает их содержимое в свободные ячейки хранилища; нет места -
|
||||
/// ждёт, пока игрок заберёт добычу.</summary>
|
||||
[HarmonyPatch(typeof(TileEntityCollector), nameof(TileEntityCollector.UpdateTick))]
|
||||
public static class Patch_TileEntityCollector_UpdateTick_GeoOssuaryMigrate
|
||||
{
|
||||
public static void Postfix(TileEntityCollector __instance)
|
||||
{
|
||||
if (!GeoOssuary.Is(__instance) || !SingletonMonoBehaviour<ConnectionManager>.Instance.IsServer)
|
||||
return;
|
||||
bool moved = Move(__instance, __instance.ModSlots, GeoOssuary.VisibleModSlots, "образца");
|
||||
moved |= Move(__instance, __instance.FuelSlots, GeoOssuary.VisibleFuelSlots, "топлива");
|
||||
if (moved)
|
||||
__instance.SetModified();
|
||||
}
|
||||
|
||||
static bool Move(TileEntityCollector _te, ItemStack[] _from, int _keep, string _what)
|
||||
{
|
||||
bool moved = false;
|
||||
ItemStack[] store = _te.Items;
|
||||
for (int i = _keep; i < _from.Length; i++)
|
||||
{
|
||||
if (_from[i] == null || _from[i].IsEmpty())
|
||||
continue;
|
||||
int free = System.Array.FindIndex(store, s => s == null || s.IsEmpty());
|
||||
if (free < 0)
|
||||
return moved;
|
||||
store[free] = _from[i].Clone();
|
||||
_from[i] = ItemStack.Empty.Clone();
|
||||
moved = true;
|
||||
Debug.Log("[NecromancerTome] GeoOssuary: " + store[free].itemValue.ItemClass?.Name + " x" + store[free].count +
|
||||
" из ячейки " + _what + " " + (i + 1) + " -> хранилище " + (free + 1) + " в " + _te.ToWorldPos());
|
||||
}
|
||||
return moved;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,517 @@
|
||||
using System.Collections.Generic;
|
||||
using HarmonyLib;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NecromancerTome
|
||||
{
|
||||
/// <summary>
|
||||
/// Новый оссуарий-котёл (#67, 22.10.2026): свечение модели и отладочная команда necroossuary.
|
||||
/// Свечение - руны на котле, знаки на крышке, прах и кости внутри (карта _e, цвет - EmissionColor через MaterialPropertyBlock,
|
||||
/// общий материал не трогается) и свет glow внутри котла (дочерний объект префаба, выключен) - пока оссуарий исправно работает
|
||||
/// (22.10: «Когда оссуарий исправно работает, он должен подсвечиваться изнутри. Чтобы сразу было видно если что-то не так»;
|
||||
/// «Пусть руны тоже загораются»). Сфера в держателе (sphere) видна, пока в топливе есть сфера или камень. Следит компонент
|
||||
/// OssuaryVisual на модели - поэтому работает и у клиентов сервера. Выключатель - necroossuary off (модель как в префабе).
|
||||
/// </summary>
|
||||
public static class OssuaryGlow
|
||||
{
|
||||
public static bool Enabled = true;
|
||||
/// <summary>Дикий дух-огонёк (#69): летает, пока оссуарий работает. Выключатель - necroossuary wisp off.</summary>
|
||||
public static bool WispEnabled = true;
|
||||
public const string WispPath = "#@modfolder(NecromancerTome):Resources/necrowisp?necroWispPrefab.prefab";
|
||||
static GameObject _wispPrefab;
|
||||
static bool _wispTried;
|
||||
|
||||
public static GameObject WispPrefab()
|
||||
{
|
||||
if (!_wispTried)
|
||||
{
|
||||
_wispTried = true;
|
||||
_wispPrefab = DataLoader.LoadAsset<GameObject>(WispPath);
|
||||
Debug.Log("[NecromancerTome] OssuaryGlow: огонёк " + (_wispPrefab != null ? "загружен" : "НЕ загружен: " + WispPath));
|
||||
}
|
||||
return _wispPrefab;
|
||||
}
|
||||
|
||||
public static Color GlowColor = new Color(2.0f, 0.24f, 0.12f, 1f);
|
||||
static readonly MaterialPropertyBlock _block = new MaterialPropertyBlock();
|
||||
static readonly int EmissionColorId = Shader.PropertyToID("_EmissionColor");
|
||||
|
||||
public static Transform Model(World _w, Vector3i _pos)
|
||||
{
|
||||
Chunk chunk = _w.GetChunkFromWorldPos(_pos) as Chunk;
|
||||
BlockEntityData be = chunk?.GetBlockEntity(_pos);
|
||||
return be != null && be.bHasTransform ? be.transform : null;
|
||||
}
|
||||
|
||||
/// <summary>true - модель найдена и переключена.</summary>
|
||||
public static bool Set(Transform _model, bool _on, bool _sphere = true)
|
||||
{
|
||||
if (_model == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
foreach (string part in new[] { "body", "lid" })
|
||||
{
|
||||
Transform t = _model.Find(part);
|
||||
Renderer r = t != null ? t.GetComponent<Renderer>() : null;
|
||||
if (r == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
r.GetPropertyBlock(_block);
|
||||
_block.SetColor(EmissionColorId, _on ? GlowColor : Color.black);
|
||||
r.SetPropertyBlock(_block);
|
||||
}
|
||||
Transform glow = _model.Find("glow");
|
||||
if (glow != null)
|
||||
{
|
||||
glow.gameObject.SetActive(_on);
|
||||
}
|
||||
Transform sphere = _model.Find("sphere");
|
||||
if (sphere != null)
|
||||
{
|
||||
sphere.gameObject.SetActive(_sphere);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>Оссуарии (главные блоки) в кубе вокруг точки.</summary>
|
||||
public static List<Vector3i> Find(World _w, Vector3i _c, int _r, int _ry)
|
||||
{
|
||||
var res = new List<Vector3i>();
|
||||
for (int x = _c.x - _r; x <= _c.x + _r; x++)
|
||||
{
|
||||
for (int z = _c.z - _r; z <= _c.z + _r; z++)
|
||||
{
|
||||
for (int y = Mathf.Max(1, _c.y - _ry); y <= Mathf.Min(254, _c.y + _ry); y++)
|
||||
{
|
||||
var p = new Vector3i(x, y, z);
|
||||
BlockValue bv = _w.GetBlock(p);
|
||||
if (!bv.ischild && GeoOssuary.Is(bv.Block))
|
||||
{
|
||||
res.Add(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>На модели оссуария: два раза в секунду смотрит на коллектор и переключает сферу и свечение, только при смене.
|
||||
/// anyEnabled - ванильная проверка «есть рабочий выход»: учитывает топливо, заполненный выход, воду и пустые ячейки
|
||||
/// модификаций (GeoOssuaryPatch.isDisabled).</summary>
|
||||
public class OssuaryVisual : MonoBehaviour
|
||||
{
|
||||
public Vector3i Pos;
|
||||
float _next;
|
||||
int _state = -1;
|
||||
|
||||
void Update()
|
||||
{
|
||||
_wisp.Tick(transform, _state > 0 && (_state & 1) != 0 && OssuaryGlow.WispEnabled); // -1 - ещё не смотрели
|
||||
if (Time.time < _next)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_next = Time.time + 0.5f;
|
||||
World w = GameManager.Instance?.World;
|
||||
if (w == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
int st; // бит 1 - светится, бит 2 - сфера видна; выключатель - как в префабе (2)
|
||||
if (OssuaryGlow.Enabled)
|
||||
{
|
||||
st = 0;
|
||||
if (w.GetTileEntity(Pos) is TileEntityCollector te)
|
||||
{
|
||||
bool fuel = false;
|
||||
foreach (ItemStack s in te.FuelSlots)
|
||||
{
|
||||
if (s != null && !s.IsEmpty())
|
||||
{
|
||||
fuel = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
st = (fuel ? 2 : 0) + (fuel && te.anyEnabled() ? 1 : 0);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
st = 2;
|
||||
}
|
||||
if (st == _state)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_state = st;
|
||||
OssuaryGlow.Set(transform, (st & 1) != 0, (st & 2) != 0);
|
||||
}
|
||||
|
||||
readonly OssuaryWisp _wisp = new OssuaryWisp();
|
||||
|
||||
void OnDestroy()
|
||||
{
|
||||
_wisp.Destroy();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Дикий дух оссуария (#69; 20.10: «Хватит одного огонька. Пусть светится сам. Производительность нам важна»):
|
||||
/// чистая анимация, без сущностей (22.10: «периодически из окошка в котле вылетает дух и уходит вниз, увеличивая прозрачность
|
||||
/// до полной, а через какое-то время с другой стороны снизу вылетает полностью прозрачный дух, плавно уменьшая прозрачность,
|
||||
/// и влетает в противоположное окошко»). Один огонёк на котёл (бандл necrowisp), только пока оссуарий исправно работает
|
||||
/// и игрок ближе WispViewRange. На добычу не влияет.</summary>
|
||||
public class OssuaryWisp
|
||||
{
|
||||
public const float WispViewRange = 60f;
|
||||
/// <summary>Окошки крышки (модель #67): шесть, между швами, через 60°; по высоте - на WindowUp от низа крышки,
|
||||
/// от оси - на WindowR (доли полуширины крышки; в модели 0.14 м и 0.37 м при полуширине 0.418).</summary>
|
||||
const float WindowUp = 0.14f / 0.836f, WindowR = 0.37f / 0.418f;
|
||||
const float Out = 0.9f; // насколько дальше от оси котла дух уходит в землю, м
|
||||
const float Under = 0.35f; // насколько под землю, м
|
||||
const float FlyTime = 2.4f;
|
||||
static readonly int TintId = Shader.PropertyToID("_TintColor");
|
||||
static readonly int ColorId = Shader.PropertyToID("_Color");
|
||||
static readonly MaterialPropertyBlock _mpb = new MaterialPropertyBlock();
|
||||
GameObject _go;
|
||||
Renderer[] _rs;
|
||||
Color[] _base;
|
||||
TrailRenderer _trail;
|
||||
int _phase = -1; // -1 дома, 0 вылет и уход в землю, 1 под землёй, 2 подъём и влёт
|
||||
float _t0, _until;
|
||||
Vector3 _winOut, _winIn, _inOut, _inIn, _down, _up;
|
||||
|
||||
static float Ease(float _k) => _k * _k * (3f - 2f * _k);
|
||||
|
||||
public void Tick(Transform _model, bool _work)
|
||||
{
|
||||
EntityPlayerLocal pl = GameManager.Instance?.World?.GetPrimaryPlayer();
|
||||
bool near = pl != null && (pl.position - Origin.position - _model.position).sqrMagnitude < WispViewRange * WispViewRange;
|
||||
float now = Time.time;
|
||||
if (!_work || !near)
|
||||
{
|
||||
if (_phase >= 0)
|
||||
{
|
||||
Hide();
|
||||
_phase = -1;
|
||||
}
|
||||
_until = Mathf.Max(_until, now + Random.Range(1f, 4f));
|
||||
return;
|
||||
}
|
||||
if (_go == null && !Spawn(_model))
|
||||
{
|
||||
return;
|
||||
}
|
||||
float k = Mathf.Clamp01((now - _t0) / Mathf.Max(0.01f, _until - _t0));
|
||||
switch (_phase)
|
||||
{
|
||||
case -1:
|
||||
if (now < _until || !Places(_model))
|
||||
{
|
||||
return;
|
||||
}
|
||||
Show(_inOut);
|
||||
Next(0, FlyTime);
|
||||
return;
|
||||
case 0: // из окошка наружу и вниз, в землю; тает
|
||||
Fly(_inOut, _winOut, _down, k);
|
||||
Alpha(1f - Ease(Mathf.InverseLerp(0.25f, 1f, k)));
|
||||
if (k >= 1f)
|
||||
{
|
||||
Hide();
|
||||
Next(1, Random.Range(12f, 20f));
|
||||
}
|
||||
return;
|
||||
case 1: // под землёй
|
||||
if (now >= _until)
|
||||
{
|
||||
Alpha(0f);
|
||||
Show(_up);
|
||||
Next(2, FlyTime);
|
||||
}
|
||||
return;
|
||||
case 2: // с другой стороны снизу вверх, проявляется, влетает в противоположное окошко
|
||||
Fly(_up, _winIn, _inIn, k);
|
||||
Alpha(Ease(Mathf.InverseLerp(0f, 0.75f, k)));
|
||||
if (k >= 1f)
|
||||
{
|
||||
Hide();
|
||||
_phase = -1;
|
||||
_until = now + Random.Range(3f, 6f);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void Next(int _p, float _dur)
|
||||
{
|
||||
_phase = _p;
|
||||
_t0 = Time.time;
|
||||
_until = _t0 + _dur;
|
||||
}
|
||||
|
||||
/// <summary>Квадратичная кривая _a -> _b -> _c: проходит окошко _b по касательной.</summary>
|
||||
void Fly(Vector3 _a, Vector3 _b, Vector3 _c, float _k)
|
||||
{
|
||||
float k = Ease(_k);
|
||||
Vector3 p = (1f - k) * (1f - k) * _a + 2f * k * (1f - k) * _b + k * k * _c;
|
||||
p.y += 0.02f * Mathf.Sin(Time.time * 7f); // лёгкое дрожание огонька
|
||||
_go.transform.position = p;
|
||||
}
|
||||
|
||||
/// <summary>Окошко вылета - случайное из шести, влёта - напротив; точки в земле - снаружи от них, высота земли - лучом.</summary>
|
||||
bool Places(Transform _model)
|
||||
{
|
||||
Transform lid = _model.Find("lid");
|
||||
Renderer lr = lid != null ? lid.GetComponent<Renderer>() : null;
|
||||
if (lr == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
Bounds b = lr.bounds;
|
||||
Vector3 axis = new Vector3(b.center.x, b.min.y + WindowUp * (b.size.x + b.size.z) * 0.5f, b.center.z);
|
||||
float half = (b.size.x + b.size.z) * 0.25f;
|
||||
int w = Random.Range(0, 6);
|
||||
Vector3 dOut = lid.rotation * Quaternion.Euler(0f, w * 60f, 0f) * Vector3.right;
|
||||
dOut.y = 0f;
|
||||
dOut.Normalize();
|
||||
Vector3 dIn = -dOut;
|
||||
_winOut = axis + dOut * (half * WindowR);
|
||||
_winIn = axis + dIn * (half * WindowR);
|
||||
_inOut = axis + dOut * (half * WindowR * 0.4f);
|
||||
_inIn = axis + dIn * (half * WindowR * 0.4f);
|
||||
_down = Ground(axis + dOut * (half + Out)) + Vector3.down * Under;
|
||||
_up = Ground(axis + dIn * (half + Out)) + Vector3.down * Under;
|
||||
return true;
|
||||
}
|
||||
|
||||
Vector3 Ground(Vector3 _p)
|
||||
{
|
||||
if (Physics.Raycast(_p + Vector3.up * 1.5f, Vector3.down, out RaycastHit hit, 8f, SnakeControl.WallMask, QueryTriggerInteraction.Ignore))
|
||||
{
|
||||
_p.y = hit.point.y;
|
||||
}
|
||||
else
|
||||
{
|
||||
_p.y -= 1f;
|
||||
}
|
||||
return _p;
|
||||
}
|
||||
|
||||
bool Spawn(Transform _model)
|
||||
{
|
||||
GameObject prefab = OssuaryGlow.WispPrefab();
|
||||
if (prefab == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
_go = Object.Instantiate(prefab, _model, false);
|
||||
_go.name = "NecroOssuaryWisp";
|
||||
_trail = _go.GetComponent<TrailRenderer>();
|
||||
_rs = _go.GetComponentsInChildren<Renderer>(true);
|
||||
_base = new Color[_rs.Length];
|
||||
for (int i = 0; i < _rs.Length; i++)
|
||||
{
|
||||
Material m = _rs[i].sharedMaterial;
|
||||
_base[i] = m == null ? Color.white : m.HasProperty(TintId) ? m.GetColor(TintId) : m.HasProperty(ColorId) ? m.GetColor(ColorId) : Color.white;
|
||||
}
|
||||
_go.SetActive(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>Прозрачность - цветом материала через MaterialPropertyBlock (шейдер аддитивный: тусклее = прозрачнее),
|
||||
/// общий материал не трогается.</summary>
|
||||
void Alpha(float _a)
|
||||
{
|
||||
for (int i = 0; i < _rs.Length; i++)
|
||||
{
|
||||
Material m = _rs[i].sharedMaterial;
|
||||
if (m == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
Color c = _base[i] * _a;
|
||||
_rs[i].GetPropertyBlock(_mpb);
|
||||
if (m.HasProperty(TintId))
|
||||
{
|
||||
_mpb.SetColor(TintId, c);
|
||||
}
|
||||
if (m.HasProperty(ColorId))
|
||||
{
|
||||
_mpb.SetColor(ColorId, c);
|
||||
}
|
||||
_rs[i].SetPropertyBlock(_mpb);
|
||||
}
|
||||
}
|
||||
|
||||
void Show(Vector3 _at)
|
||||
{
|
||||
_go.transform.position = _at;
|
||||
if (_trail != null)
|
||||
{
|
||||
_trail.Clear();
|
||||
}
|
||||
_go.SetActive(true);
|
||||
if (_phase != 1)
|
||||
{
|
||||
Alpha(1f);
|
||||
}
|
||||
}
|
||||
|
||||
void Hide()
|
||||
{
|
||||
if (_go != null)
|
||||
{
|
||||
_go.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
public void Destroy()
|
||||
{
|
||||
if (_go != null)
|
||||
{
|
||||
Object.Destroy(_go);
|
||||
_go = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Модель оссуария появилась в мире - повесить на неё OssuaryVisual.</summary>
|
||||
[HarmonyPatch(typeof(BlockCollector), nameof(BlockCollector.OnBlockEntityTransformBeforeActivated))]
|
||||
public static class Patch_BlockCollector_OnBlockEntityTransformBeforeActivated_OssuaryVisual
|
||||
{
|
||||
public static void Postfix(Vector3i _blockPos, BlockValue _blockValue, BlockEntityData _ebcd)
|
||||
{
|
||||
if (!GeoOssuary.Is(_blockValue.Block) || _ebcd?.transform == null || _ebcd.transform.Find("body") == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
OssuaryVisual v = _ebcd.transform.GetComponent<OssuaryVisual>() ?? _ebcd.transform.gameObject.AddComponent<OssuaryVisual>();
|
||||
v.Pos = _blockPos;
|
||||
}
|
||||
}
|
||||
|
||||
public class ConsoleCmdNecroOssuary : ConsoleCmdAbstract
|
||||
{
|
||||
public override bool IsExecuteOnClient => true;
|
||||
|
||||
public override bool AllowedInMainMenu => false;
|
||||
|
||||
public override string[] getCommands()
|
||||
{
|
||||
return new string[] { "necroossuary" };
|
||||
}
|
||||
|
||||
public override string getDescription()
|
||||
{
|
||||
return "Necromancer's Tome: cauldron ossuary - find, remove, sphere/glow switch.";
|
||||
}
|
||||
|
||||
public override string getHelp()
|
||||
{
|
||||
return "necroossuary find [r] - ossuaries around the player (r blocks, default 30)\n" +
|
||||
"necroossuary remove x y z - remove the ossuary at x y z (only if it is an ossuary)\n" +
|
||||
"necroossuary on|off - off: sphere and glow no longer follow the ossuary state\n" +
|
||||
"necroossuary wisp on|off - wild spirit wisp flying while the ossuary works\n" +
|
||||
"necroossuary info x y z - slots of the ossuary (samples, fuel, storage) and whether it works\n" +
|
||||
"necroossuary put x y z mod|fuel <slot 1..> <item> [count] - dev: put an item into a slot, hidden ones too";
|
||||
}
|
||||
|
||||
public override void Execute(List<string> _params, CommandSenderInfo _senderInfo)
|
||||
{
|
||||
SdtdConsole con = SingletonMonoBehaviour<SdtdConsole>.Instance;
|
||||
World w = GameManager.Instance?.World;
|
||||
EntityPlayerLocal pl = w?.GetPrimaryPlayer();
|
||||
if (w == null || pl == null || _params.Count == 0)
|
||||
{
|
||||
con.Output(getHelp());
|
||||
return;
|
||||
}
|
||||
Vector3i me = World.worldToBlockPos(pl.position);
|
||||
switch (_params[0])
|
||||
{
|
||||
case "find":
|
||||
{
|
||||
int r = _params.Count > 1 ? int.Parse(_params[1]) : 30;
|
||||
List<Vector3i> found = OssuaryGlow.Find(w, me, r, 20);
|
||||
con.Output("necroossuary: найдено " + found.Count + (found.Count > 0 ? ": " + string.Join("; ", found) : ""));
|
||||
break;
|
||||
}
|
||||
case "remove":
|
||||
{
|
||||
var p = new Vector3i(int.Parse(_params[1]), int.Parse(_params[2]), int.Parse(_params[3]));
|
||||
BlockValue bv = w.GetBlock(p);
|
||||
if (!GeoOssuary.Is(bv.Block))
|
||||
{
|
||||
con.Output("necroossuary: в " + p + " не оссуарий (" + bv.Block?.GetBlockName() + ") - не трогаю");
|
||||
break;
|
||||
}
|
||||
if (bv.ischild)
|
||||
{
|
||||
p = p + bv.parent;
|
||||
}
|
||||
w.SetBlockRPC(p, BlockValue.Air);
|
||||
con.Output("necroossuary: убран оссуарий в " + p);
|
||||
break;
|
||||
}
|
||||
case "info":
|
||||
case "put":
|
||||
{
|
||||
var p = new Vector3i(int.Parse(_params[1]), int.Parse(_params[2]), int.Parse(_params[3]));
|
||||
BlockValue bv = w.GetBlock(p);
|
||||
if (bv.ischild)
|
||||
{
|
||||
p = p + bv.parent;
|
||||
}
|
||||
if (!(w.GetTileEntity(p) is TileEntityCollector te) || !GeoOssuary.Is(te))
|
||||
{
|
||||
con.Output("necroossuary: в " + p + " не оссуарий");
|
||||
break;
|
||||
}
|
||||
if (_params[0] == "put")
|
||||
{
|
||||
ItemStack[] slots = _params[4] == "fuel" ? te.FuelSlots : te.ModSlots;
|
||||
int i = int.Parse(_params[5]) - 1;
|
||||
ItemValue iv = ItemClass.GetItem(_params[6]);
|
||||
if (i < 0 || i >= slots.Length || iv == null || iv.IsEmpty())
|
||||
{
|
||||
con.Output("necroossuary: нет ячейки " + (i + 1) + " или предмета " + _params[6]);
|
||||
break;
|
||||
}
|
||||
slots[i] = new ItemStack(iv, _params.Count > 7 ? int.Parse(_params[7]) : 1);
|
||||
te.SetModified();
|
||||
}
|
||||
string S(ItemStack[] _a)
|
||||
{
|
||||
var parts = new List<string>();
|
||||
for (int i = 0; i < _a.Length; i++)
|
||||
{
|
||||
parts.Add(_a[i] == null || _a[i].IsEmpty() ? "-" : _a[i].itemValue.ItemClass?.Name + " x" + _a[i].count);
|
||||
}
|
||||
return string.Join(", ", parts);
|
||||
}
|
||||
con.Output("necroossuary " + p + ": образцы [" + S(te.ModSlots) + "]; топливо [" + S(te.FuelSlots) + "]; хранилище [" +
|
||||
S(te.Items) + "]; работает " + te.anyEnabled());
|
||||
break;
|
||||
}
|
||||
case "wisp":
|
||||
if (_params.Count > 1)
|
||||
{
|
||||
OssuaryGlow.WispEnabled = _params[1] == "on";
|
||||
}
|
||||
con.Output("necroossuary: огонёк " + (OssuaryGlow.WispEnabled ? "летает" : "выключен"));
|
||||
break;
|
||||
case "on":
|
||||
case "off":
|
||||
OssuaryGlow.Enabled = _params[0] == "on";
|
||||
con.Output("necroossuary: сфера и свечение по работе " + (OssuaryGlow.Enabled ? "включены" : "выключены (модель как в префабе)"));
|
||||
break;
|
||||
default:
|
||||
con.Output(getHelp());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 20 KiB |
Reference in New Issue
Block a user