diff --git a/Config/Localization.csv b/Config/Localization.csv index 77e325d..0281e53 100644 --- a/Config/Localization.csv +++ b/Config/Localization.csv @@ -168,6 +168,13 @@ necroZoneUrnNoMaterials,UI,Text,,,,out of materials,,kein Material,sin materiale necroZoneUrnFull,UI,Text,,,,the chest is full,,Truhe voll,cofre lleno,coffre plein,forziere pieno,箱が満杯,상자 가득,skrzynia pełna,baú cheio,ящик полон,sandık dolu,箱子已满,箱子已滿 necroZoneUrnTide,UI,Text,,,,blood tide - the spirit hides,,Blutflut - der Geist versteckt sich,marea de sangre: el espíritu se esconde,marée de sang - l'esprit se cache,marea di sangue: lo spirito si nasconde,血の潮――霊は隠れている,피의 조수 - 영혼이 숨었다,krwawy przypływ - duch się chowa,maré de sangue: o espírito se esconde,кровавый прилив - дух в урне,kan gelgiti - ruh saklanıyor,血潮——灵魂躲了起来,血潮——靈魂躲了起來 necroZoneUrnSize,UI,Text,,,,zone {0}x{0},,Zone {0}x{0},zona {0}x{0},zone {0}x{0},zona {0}x{0},ゾーン {0}x{0},구역 {0}x{0},strefa {0}x{0},zona {0}x{0},зона {0}x{0},bölge {0}x{0},区域 {0}x{0},區域 {0}x{0} +necroCinerariumProspector,blocks,Block,,,,Prospector Cinerarium,,Goldsucher-Cinerarium,Cinerario buscador,Cinéraire prospecteur,Cinerario cercatore,山師の骨壺,탐광 유골함,Cinerarium poszukiwacza,Cinerário garimpeiro,Цинерарий-старатель,Maden Arayıcı Küllüğü,探矿骨灰瓮,探礦骨灰甕 +necroCinerariumProspectorSkull,blocks,Block,,,,Prospector Cinerarium (with skull),,Goldsucher-Cinerarium (mit Schädel),Cinerario buscador (con cráneo),Cinéraire prospecteur (avec crâne),Cinerario cercatore (con teschio),山師の骨壺(頭蓋骨入り),탐광 유골함 (두개골 포함),Cinerarium poszukiwacza (z czaszką),Cinerário garimpeiro (com crânio),Цинерарий-старатель (с черепом),Maden Arayıcı Küllüğü (kafatasıyla),探矿骨灰瓮(含头骨),探礦骨灰甕(含頭骨) +necroCinerariumProspectorDesc,blocks,Block,,,,"An urn for a spirit that digs. In its Contents, put a skull in the first slot and a sample in the second - one piece of what to dig: stone, clay, sand, wood, iron, lead, coal, potassium nitrate or oil shale. Tie a chest to it with a ghostly thread - the Necromancer's Knife in the thread grip (R). The spirit sinks into the earth and once every in-game hour brings more of the same into the chest; from ore it now and then brings a raw diamond, a silver or a gold nugget. Slow, but day and night and without a drop of blood. If the chest is full, the spirit waits by the urn with its haul. Has no zone of its own and does not count toward the urn limit.",,"Eine Urne für einen Geist, der gräbt. Im Inhalt kommt ein Schädel in das erste Feld und ein Muster in das zweite - ein Stück dessen, was er graben soll: Stein, Lehm, Sand, Holz, Eisen, Blei, Kohle, Kaliumnitrat oder Ölschiefer. Verbinde eine Truhe über einen geisterhaften Faden mit ihr - mit dem Messer des Nekromanten im Fadengriff (R). Der Geist versinkt in der Erde und bringt jede Spielstunde mehr davon in die Truhe; aus Erz bringt er hin und wieder einen Rohdiamanten, ein Silber- oder ein Goldnugget mit. Langsam, aber Tag und Nacht und ohne einen Tropfen Blut. Ist die Truhe voll, wartet der Geist mit seiner Ausbeute an der Urne. Hat keine eigene Zone und zählt nicht zum Urnenlimit.","Una urna para un espíritu que excava. En su Contenido, pon un cráneo en la primera casilla y una muestra en la segunda: una pieza de lo que debe excavar - piedra, arcilla, arena, madera, hierro, plomo, carbón, nitrato de potasio o esquisto bituminoso. Átale un cofre con un hilo fantasmal: el Cuchillo del nigromante en agarre de hilos (R). El espíritu se hunde en la tierra y cada hora de juego trae más de lo mismo al cofre; del mineral, de vez en cuando, un diamante en bruto o una pepita de plata u oro. Lento, pero de día y de noche y sin una gota de sangre. Si el cofre está lleno, el espíritu espera junto a la urna con lo que ha sacado. No tiene zona propia y no cuenta para el límite de urnas.","Une urne pour un esprit qui creuse. Dans son Contenu, mets un crâne dans la première case et un échantillon dans la seconde - une pièce de ce qu'il doit extraire : pierre, argile, sable, bois, fer, plomb, charbon, nitrate de potassium ou schiste bitumineux. Relie-lui un coffre par un fil spectral - le Couteau du nécromancien en prise des fils (R). L'esprit s'enfonce dans la terre et, à chaque heure de jeu, rapporte davantage de la même chose dans le coffre ; du minerai, il ramène parfois un diamant brut ou une pépite d'argent ou d'or. Lent, mais jour et nuit et sans une goutte de sang. Si le coffre est plein, l'esprit attend près de l'urne avec son butin. N'a pas de zone propre et ne compte pas dans la limite d'urnes.","Un'urna per uno spirito che scava. Nel suo Contenuto metti un teschio nel primo slot e un campione nel secondo: un pezzo di ciò che deve scavare - pietra, argilla, sabbia, legno, ferro, piombo, carbone, nitrato di potassio o scisto bituminoso. Legale un forziere con un filo spettrale: il Coltello del negromante nella presa dei fili (R). Lo spirito sprofonda nella terra e a ogni ora di gioco porta altro dello stesso nel forziere; dal minerale ogni tanto porta un diamante grezzo o una pepita d'argento o d'oro. Lento, ma giorno e notte e senza una goccia di sangue. Se il forziere è pieno, lo spirito aspetta accanto all'urna con il bottino. Non ha una zona propria e non conta nel limite di urne.",掘る霊のための骨壺。「中身」の1つ目の枠に頭蓋骨を、2つ目に見本を入れる――掘るものを1つ:石、粘土、砂、木材、鉄、鉛、石炭、硝酸カリウム、オイルシェール。死霊術師のナイフの糸持ち(R)で、チェストと霊の糸でつなぐ。霊は地中へ沈み、ゲーム内の1時間ごとに同じものをチェストへ運んでくる。鉱石からはときどき原石のダイヤモンドや銀・金の塊も。遅いが昼も夜も、血は一滴もいらない。チェストが満杯なら、霊は掘ったものを抱えて骨壺のそばで待つ。自分のゾーンはなく、骨壺の上限にも数えない。,"땅을 파는 영혼을 위한 유골함. 내용물의 첫 칸에 두개골을, 둘째 칸에 견본을 넣는다 - 캘 것을 하나: 돌, 점토, 모래, 나무, 철, 납, 석탄, 질산칼륨, 오일셰일. 강령술사의 칼 실 잡기(R)로 상자와 영혼의 실을 잇는다. 영혼은 땅속으로 가라앉아 게임 시간으로 한 시간마다 같은 것을 상자에 가져온다. 광석에서는 가끔 원석 다이아몬드나 은·금 덩어리도. 느리지만 밤낮없이, 피 한 방울 없이. 상자가 가득 차면 영혼은 캔 것을 들고 유골함 곁에서 기다린다. 자체 구역이 없고 유골함 한도에 포함되지 않는다.","Urna dla ducha, który kopie. W Zawartości włóż czaszkę do pierwszego pola, a próbkę do drugiego - jedną sztukę tego, co ma wydobywać: kamień, glinę, piasek, drewno, żelazo, ołów, węgiel, saletrę potasową lub łupek bitumiczny. Połącz z nią skrzynię widmową nicią - Nożem nekromanty w chwycie nici (R). Duch zapada się w ziemię i co godzinę gry przynosi do skrzyni więcej tego samego; z rudy od czasu do czasu surowy diament albo samorodek srebra lub złota. Powoli, ale dniem i nocą i bez kropli krwi. Gdy skrzynia jest pełna, duch czeka przy urnie z urobkiem. Nie ma własnej strefy i nie wlicza się do limitu urn.","Uma urna para um espírito que escava. No Conteúdo, coloque um crânio no primeiro espaço e uma amostra no segundo - uma peça do que escavar: pedra, argila, areia, madeira, ferro, chumbo, carvão, nitrato de potássio ou xisto betuminoso. Ligue um baú a ela com um fio fantasmagórico - a Faca do necromante na pegada de fios (R). O espírito afunda na terra e a cada hora de jogo traz mais do mesmo para o baú; do minério, de vez em quando, um diamante bruto ou uma pepita de prata ou de ouro. Devagar, mas dia e noite e sem uma gota de sangue. Se o baú estiver cheio, o espírito espera junto à urna com o que tirou. Não tem zona própria e não conta para o limite de urnas.","Урна для духа, который копает. В «Содержимом» в первую ячейку - череп, во вторую - образец, одну штуку того, что добывать: камень, глину, песок, древесину, железо, свинец, уголь, селитру или сланец. Протяните к ящику призрачную нить - нож некроманта в хвате нитей (R). Дух уходит под землю и каждый игровой час приносит в ящик ещё того же; из руды порой - необработанный алмаз, серебряный или золотой самородок. Медленно, но днём и ночью и без капли крови. Ящик полон - дух ждёт у урны с добычей. Своей зоны нет, в лимит урн не входит.","Kazan bir ruh için küllük. İçerik bölümünde ilk yuvaya bir kafatası, ikinciye bir örnek koy - kazacağı şeyden bir tane: taş, kil, kum, odun, demir, kurşun, kömür, potasyum nitrat ya da petrollü şist. Ona hayaletimsi bir iple bir sandık bağla - Nekromansörün Bıçağı ip tutuşunda (R). Ruh toprağa gömülür ve her oyun saatinde sandığa aynısından daha fazlasını getirir; cevherden ara sıra ham elmas, gümüş ya da altın külçesi de çıkar. Yavaş ama gece gündüz ve tek damla kan olmadan. Sandık doluysa ruh, çıkardıklarıyla küllüğün yanında bekler. Kendi bölgesi yoktur ve küllük sınırına sayılmaz.",供挖掘之灵使用的骨灰瓮。在“内容物”第一格放入头骨,第二格放入样本——要挖的东西一件:石头、黏土、沙子、木材、铁、铅、煤、硝酸钾或油页岩。用死灵法师之刃的牵线握法(R)把它和箱子用幽灵之线连起来。灵魂沉入地下,每过一个游戏小时就把同样的东西运进箱子;挖矿石时偶尔还会带回原钻、银块或金块。很慢,但昼夜不停,而且不用一滴血。箱子满了,灵魂就带着收获在瓮旁等待。没有自己的区域,不计入骨灰瓮上限。,供挖掘之靈使用的骨灰甕。在「內容物」第一格放入頭骨,第二格放入樣本——要挖的東西一件:石頭、黏土、沙子、木材、鐵、鉛、煤、硝酸鉀或油頁岩。用死靈法師之刃的牽線握法(R)把它和箱子用幽靈之線連起來。靈魂沉入地下,每過一個遊戲小時就把同樣的東西運進箱子;挖礦石時偶爾還會帶回原鑽、銀塊或金塊。很慢,但晝夜不停,而且不用一滴血。箱子滿了,靈魂就帶著收穫在甕旁等待。沒有自己的區域,不計入骨灰甕上限。 +necroProspectorNoSample,UI,Text,,,,no sample,,kein Muster,sin muestra,pas d'échantillon,nessun campione,見本なし,견본 없음,brak próbki,sem amostra,нет образца,örnek yok,没有样本,沒有樣本 +necroProspectorNoChest,UI,Text,,,,no chest on a thread,,keine Truhe am Faden,sin cofre atado,aucun coffre relié,nessun forziere legato,糸でつないだチェストなし,실로 이은 상자 없음,brak skrzyni na nici,sem baú ligado,нет нити к ящику,iple bağlı sandık yok,没有连线的箱子,沒有連線的箱子 +necroProspectorDigging,UI,Text,,,,the spirit is digging,,der Geist gräbt,el espíritu excava,l'esprit creuse,lo spirito scava,霊が掘っている,영혼이 파는 중,duch kopie,o espírito escava,дух копает,ruh kazıyor,灵魂在挖掘,靈魂在挖掘 +necroProspectorBadSample,UI,Text,,,,"The prospector digs only stone, clay, sand, wood, iron, lead, coal, potassium nitrate or oil shale - one piece as a sample.",,"Der Goldsucher gräbt nur Stein, Lehm, Sand, Holz, Eisen, Blei, Kohle, Kaliumnitrat oder Ölschiefer - ein Stück als Muster.","El buscador solo excava piedra, arcilla, arena, madera, hierro, plomo, carbón, nitrato de potasio o esquisto bituminoso: una pieza como muestra.","Le prospecteur n'extrait que pierre, argile, sable, bois, fer, plomb, charbon, nitrate de potassium ou schiste bitumineux - une pièce comme échantillon.","Il cercatore scava solo pietra, argilla, sabbia, legno, ferro, piombo, carbone, nitrato di potassio o scisto bituminoso: un pezzo come campione.",山師が掘るのは石、粘土、砂、木材、鉄、鉛、石炭、硝酸カリウム、オイルシェールだけ――見本は1つ。,"탐광꾼은 돌, 점토, 모래, 나무, 철, 납, 석탄, 질산칼륨, 오일셰일만 캔다 - 견본은 하나.","Poszukiwacz wydobywa tylko kamień, glinę, piasek, drewno, żelazo, ołów, węgiel, saletrę potasową lub łupek bitumiczny - jedna sztuka jako próbka.","O garimpeiro só escava pedra, argila, areia, madeira, ferro, chumbo, carvão, nitrato de potássio ou xisto betuminoso - uma peça como amostra.","Старатель добывает только камень, глину, песок, древесину, железо, свинец, уголь, селитру или сланец - образец, одна штука.","Maden arayıcı yalnızca taş, kil, kum, odun, demir, kurşun, kömür, potasyum nitrat ya da petrollü şist kazar - örnek olarak bir tane.",探矿者只挖石头、黏土、沙子、木材、铁、铅、煤、硝酸钾或油页岩——样本放一件。,探礦者只挖石頭、黏土、沙子、木材、鐵、鉛、煤、硝酸鉀或油頁岩——樣本放一件。 necroThreadNeedWorker,UI,Tooltip,,,,Threads to workstations tie only to a Worker Cinerarium,,Fäden zu Werkstationen nur an ein Arbeiter-Cinerarium,Los hilos a estaciones solo se atan a un cinerario obrero,Les fils vers les postes ne se nouent qu'à un cinéraire ouvrier,I fili verso le postazioni si legano solo a un cinerario operaio,作業台への糸は職人の骨壺にしか張れない,작업대로 가는 실은 일꾼 유골함에만 이을 수 있다,Nici do stanowisk łączą się tylko z cinerarium robotnika,Fios para estações só se ligam a um cinerário operário,Нити к станциям вяжутся только к цинерарию-рабочему,Tezgâh iplikleri yalnızca işçi küllüğüne bağlanır,连到工作台的丝线只能连工匠骨灰瓮,連到工作台的絲線只能連工匠骨灰甕 necroThreadStation,UI,Tooltip,,,,Thread: urn to workstation - the spirit will craft here,,Faden: Urne zur Werkstation - der Geist stellt hier her,Hilo: urna a estación - el espíritu fabricará aquí,Fil : urne vers poste - l'esprit fabriquera ici,Filo: urna verso postazione - lo spirito fabbricherà qui,糸:骨壺から作業台へ - 霊はここで作る,실: 유골함에서 작업대로 - 영혼이 여기서 만든다,Nić: urna do stanowiska - duch będzie tu wytwarzał,Fio: urna para estação - o espírito fabricará aqui,Нить: урна -> станция - дух будет изготавливать здесь,İplik: küllükten tezgâha - ruh burada üretecek,丝线:骨灰瓮到工作台——灵会在这里制作,絲線:骨灰甕到工作台——靈會在這裡製作 necroThreadOut,UI,Tooltip,,,,Thread: urn to chest - the spirit will put the goods here,,Faden: Urne zur Truhe - der Geist legt die Ware hierhin,Hilo: urna a cofre - el espíritu dejará aquí lo fabricado,Fil : urne vers coffre - l'esprit rangera ici la production,Filo: urna verso forziere - lo spirito metterà qui i prodotti,糸:骨壺から箱へ - 霊は完成品をここに置く,실: 유골함에서 상자로 - 영혼이 완성품을 여기 둔다,Nić: urna do skrzyni - duch odłoży tu wyroby,Fio: urna para baú - o espírito guardará aqui o produto,Нить: урна -> ящик - сюда дух будет класть готовое,İplik: küllükten sandığa - ruh ürünleri buraya koyacak,丝线:骨灰瓮到箱子——灵会把成品放在这里,絲線:骨灰甕到箱子——靈會把成品放在這裡 @@ -504,7 +511,7 @@ necroTomeTideP2,XUi,Menu,,,,"The spirit leaves the body along a gradient: from t necroTomeTideP3,XUi,Menu,,,,"Chilling terror is a cold pulse the necromancer sends into dead flesh. In saturated air it drowns like a whisper in a crowd: the Horns frighten no one. And the dead themselves are soaked with the tide that night - it is what drives them at the living.\n\nAdvice: by dusk on the day of the blood moon, be in your own body and behind strong walls. In the morning the tide ebbs and everything returns.",,,,,,,,,,"Леденящий ужас - холодный импульс, который некромант посылает в мёртвую плоть. В насыщенном воздухе он тонет, как шёпот в толпе: «коза» никого не пугает. А сами мёртвые в эту ночь напитаны приливом - он и гонит их на живых.\n\nСовет: к закату дня кровавой луны будьте в своём теле и за крепкими стенами. Утром прилив схлынет, и всё вернётся.",,, necroTomeUrnsTitle,XUi,Menu,,,,Cineraria,,,,,,,,,,Цинерарии,,, necroTomeUrnsP1,XUi,Menu,,,,"A red corpse is not empty. Whoever it was, a scrap of their spirit stays caught in the skull - too thin to rise, too stubborn to leave. Put such a skull into a funeral urn and the scrap gathers itself into a pale worker: a necrotechnologist. It has no will of its own and no memory of a name. It only knows the work it was made for. The urn is its body. Out in the open air the spirit cannot hold its shape for long, so it leans out of the neck of the urn, flies to its task and comes back. Take the skull out and the worker is gone; put it back and it rises again. The urn keeps it whole, and every urn is shaped for one trade.",,,,,,,,,,"Красный труп не пуст. Кем бы он ни был, клочок его духа застревает в черепе - слишком слабый, чтобы подняться, и слишком упрямый, чтобы уйти. Положи такой череп в погребальную урну, и клочок соберётся в бледного работника - некротехнолога. Своей воли у него нет, имени он не помнит. Он знает только то дело, ради которого сделана урна. Урна - его тело. На открытом воздухе дух недолго держит форму, поэтому он высовывается из горлышка, летит к делу и возвращается. Вынешь череп - работника нет; положишь обратно - он поднимется снова. Урна держит его целым, и каждая урна сделана под одно ремесло.",,, -necroTomeUrnsP2,XUi,Menu,,,,"The rikoltisto tends the ground around it: collectors, workstations, garden beds. The portisto, a small spirit in a brass censer, carries goods from chest to chest along ghostly threads. The laboristo is given a sample and crafts more of the same, feeding the fire and putting it out when the work is done. The riparisto and the kolektisto work a whole land claim, but only when told to: press Start and the spirit makes one round - mending damaged walls, or gathering everything that lies in the zone into a chest. The skull buttons on these two urns tell you how it goes: yellow - waiting, yellow blinking - something is missing, green - at work.",,,,,,,,,,"Rikoltisto ухаживает за землёй вокруг себя: коллекторы, станции, грядки. Portisto, маленький дух в латунном кадиле, переносит вещи из ящика в ящик по призрачным нитям. Laboristo получает образец и делает такие же, подкидывая топливо и гася огонь, когда работа кончилась. Riparisto и kolektisto работают на всей земле притязания, но только по приказу: нажми «Запустить», и дух сделает один обход - починит повреждённые стены или соберёт в ящик всё, что лежит в зоне. Кнопки-черепа на этих двух урнах подскажут, как идут дела: жёлтая - ждёт, жёлтая мигает - чего-то не хватает, зелёная - работает.",,, +necroTomeUrnsP2,XUi,Menu,,,,"The rikoltisto tends the ground around it: collectors, workstations, garden beds. The portisto, a small spirit in a brass censer, carries goods from chest to chest along ghostly threads. The laboristo is given a sample and crafts more of the same, feeding the fire and putting it out when the work is done. The prospektisto is given a sample of stone or ore and sinks into the earth after more of it; once an hour it rises with its haul for the chest. The riparisto and the kolektisto work a whole land claim, but only when told to: press Start and the spirit makes one round - mending damaged walls, or gathering everything that lies in the zone into a chest. The skull buttons on these two urns tell you how it goes: yellow - waiting, yellow blinking - something is missing, green - at work.",,,,,,,,,,"Rikoltisto ухаживает за землёй вокруг себя: коллекторы, станции, грядки. Portisto, маленький дух в латунном кадиле, переносит вещи из ящика в ящик по призрачным нитям. Laboristo получает образец и делает такие же, подкидывая топливо и гася огонь, когда работа кончилась. Prospektisto получает образец камня или руды и уходит за ним под землю; раз в час он поднимается с добычей к ящику. Riparisto и kolektisto работают на всей земле притязания, но только по приказу: нажми «Запустить», и дух сделает один обход - починит повреждённые стены или соберёт в ящик всё, что лежит в зоне. Кнопки-черепа на этих двух урнах подскажут, как идут дела: жёлтая - ждёт, жёлтая мигает - чего-то не хватает, зелёная - работает.",,, necroTomeUrnsP3,XUi,Menu,,,,"Threads are drawn with the necromancer's knife held in the thread grip: from a chest to an urn means take from here, from an urn to a chest means put there. A thread will not stretch farther than fifteen paces, but a skull set out as decor can hold it: pass the thread through the skull and go on, skull after skull, as far as you need. Take such a skull away and the thread breaks. And mind the blood tide. On the night the moon comes close and fills with red, the air grows as dense with necroenergy as the spirits themselves. They cannot hold their shape outside and sink back into their urns; the work stands still and the buttons burn red until morning.",,,,,,,,,,"Нити тянут ножом некроманта, взятым хватом для нитей: от ящика к урне - бери отсюда, от урны к ящику - клади туда. Дальше пятнадцати шагов нить не тянется, но её удержит череп, выставленный как декор: проведи нить через череп и тяни дальше, череп за черепом, сколько нужно. Убери такой череп - и нить порвётся. И помни о кровавом приливе. В ночь, когда луна подходит близко и наливается красным, воздух становится так же плотен некроэнергией, как сами духи. Вне урны они не держат форму и уходят внутрь; работа стоит, кнопки горят красным до утра.",,, necroMindModePalace,XUi,Menu,,,,Mind Palace,,Gedächtnispalast,Palacio mental,Palais mental,Palazzo della mente,記憶の宮殿,기억의 궁전,Pałac umysłu,Palácio da mente,Чертоги разума,Zihin Sarayı,思维宫殿,思維宮殿 necroKnifeDecor,blocks,Block,,,,Necromancer's Knife,,Nekromantenmesser,Cuchillo del nigromante,Couteau du nécromancien,Coltello del negromante,ネクロマンサーのナイフ,강령술사의 칼,Nóż nekromanty,Faca do necromante,Нож некроманта,Nekromansör Bıçağı,死灵法师之刃,死靈法師之刃 diff --git a/Config/blocks.xml b/Config/blocks.xml index b2114be..004a1c0 100644 --- a/Config/blocks.xml +++ b/Config/blocks.xml @@ -642,5 +642,21 @@ + + + + + + + + + + + + + diff --git a/Config/progression.xml b/Config/progression.xml index 50888da..7b02fc5 100644 --- a/Config/progression.xml +++ b/Config/progression.xml @@ -181,7 +181,7 @@ пользуется, вода сидит на necroNecromancyLvl20. --> - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/HarmonySrc/Cinerarium.cs b/HarmonySrc/Cinerarium.cs index f8d6b54..d2de829 100644 --- a/HarmonySrc/Cinerarium.cs +++ b/HarmonySrc/Cinerarium.cs @@ -64,7 +64,7 @@ namespace NecromancerTome /// Урна без зоны 5x5 (логист, рабочий, ремонтник, лутер): ставится вплотную к любым урнам, в лимит не входит, /// работает по нитям. У ремонтника и лутера своя большая зона (CinerariumZone.cs) - зоны таких урн могут пересекаться. - public static bool IsZonelessBlock(Block _b) => IsLogistBlock(_b) || IsWorkerBlock(_b) || IsRepairBlock(_b) || IsLootBlock(_b); + public static bool IsZonelessBlock(Block _b) => IsLogistBlock(_b) || IsWorkerBlock(_b) || IsRepairBlock(_b) || IsLootBlock(_b) || IsProspectorBlock(_b); public const string SkullItemName = "necroSkull"; public const int HalfWidth = 2; public const int Below = 1; @@ -103,7 +103,7 @@ namespace NecromancerTome } } bool _worker; - public bool IsZoneless => IsLogist || IsWorker || IsBigZone; + public bool IsZoneless => IsLogist || IsWorker || IsBigZone || IsProspector; /// Урна-логист - по блоку (necroCinerariumLogist / ...Skull); узнаётся при постановке или по миру. public bool IsLogist { @@ -119,6 +119,7 @@ namespace NecromancerTome _worker = IsWorkerBlock(bv.Block); _repair = IsRepairBlock(bv.Block); _loot = IsLootBlock(bv.Block); + _prospector = IsProspectorBlock(bv.Block); _logistKnown = true; } } @@ -168,16 +169,17 @@ namespace NecromancerTome _worker = IsWorkerBlock(_blockValue.Block); _repair = IsRepairBlock(_blockValue.Block); _loot = IsLootBlock(_blockValue.Block); + _prospector = IsProspectorBlock(_blockValue.Block); _logistKnown = true; string placed = _blockValue.Block?.GetBlockName(); if (placed == SkullBlockName || placed == LogistSkullBlockName || placed == LogistWallSkullBlockName || placed == WorkerSkullBlockName || - placed == RepairSkullBlockName || placed == LootSkullBlockName) + placed == RepairSkullBlockName || placed == LootSkullBlockName || placed == ProspectorSkullBlockName) { HasSkull = true; // поставили урну, поднятую с черепом } if (ConnectionManager.Instance.IsServer) { - CinerariumRegistry.Add(_blockPos, Parent?.Owner, _logist || _worker || _repair || _loot); + CinerariumRegistry.Add(_blockPos, Parent?.Owner, _logist || _worker || _repair || _loot || _prospector); } } @@ -190,6 +192,7 @@ namespace NecromancerTome RemoveGenie(false); CinerariumContents.CloseIfShowing(this); ZoneStop(false); + ProspectorReset(); if (!ConnectionManager.Instance.IsServer) { return; @@ -224,6 +227,7 @@ namespace NecromancerTome _threadsDirty = true; // чанк вернётся - нити построить заново RemoveGenie(false); CinerariumContents.CloseIfShowing(this); + ProspectorReset(); } public override void Write(PooledBinaryWriter _bw, StreamModeWrite _eStreamMode) @@ -447,7 +451,7 @@ namespace NecromancerTome base.GetActivationText(_world, _blockPos, _blockValue, _entityFocusing, _activateHotkeyMarkup, _focusedTileEntityName); string s = _activateHotkeyMarkup + " " + _blockValue.Block.GetLocalizedBlockName() + " - " + Localization.Get(HasSkull ? "necroCinerariumStateSkull" : "necroCinerariumStateEmpty"); - return IsBigZone ? s + ZoneStateText() : s; + return IsBigZone ? s + ZoneStateText() : IsProspector ? s + ProspectorStateText(_world as World) : s; } public override void UpdateTick(World _world) @@ -752,6 +756,11 @@ namespace NecromancerTome ZoneWork(_world); // ремонтник, лутер - CinerariumZone.cs return; } + if (IsProspector) + { + ProspectorWork(_world); // старатель - CinerariumProspector.cs + return; + } CinerariumGenieMotion motion = _genie != null ? _genie.GetComponent() : null; if (_pending) { @@ -929,7 +938,7 @@ namespace NecromancerTome logist = w != null && IsLogistWallBlock(w.GetBlock(ToWorldPos()).Block) ? 2 : 1; } _genie = CinerariumGenie.Spawn(ModelTransform(), IsWorker ? CinerariumGenie.WorkerDrop : 0f, logist); - if (_genie != null && IsLooter) + if (_genie != null && (IsLooter || IsProspector)) // старатель - на банке лутера { _genie.GetComponent().ForwardOverride = CinerariumGenie.LootForward; } @@ -1089,7 +1098,7 @@ namespace NecromancerTome string n = _b?.GetBlockName(); return n == BlockName || n == SkullBlockName || n == LogistBlockName || n == LogistSkullBlockName || n == LogistWallBlockName || n == LogistWallSkullBlockName || n == WorkerBlockName || n == WorkerSkullBlockName || - IsRepairBlock(_b) || IsLootBlock(_b); + IsRepairBlock(_b) || IsLootBlock(_b) || IsProspectorBlock(_b); } public static int UrnLimit(EntityPlayer _player) @@ -1486,7 +1495,10 @@ namespace NecromancerTome bool worker = TEFeatureCinerarium.IsWorkerBlock(bv.Block); bool repairUrn = TEFeatureCinerarium.IsRepairBlock(bv.Block); bool lootUrn = TEFeatureCinerarium.IsLootBlock(bv.Block); - ItemValue iv = ItemClass.GetItem(repairUrn + bool prospector = TEFeatureCinerarium.IsProspectorBlock(bv.Block); + ItemValue iv = ItemClass.GetItem(prospector + ? (urn.HasSkull ? TEFeatureCinerarium.ProspectorSkullBlockName : TEFeatureCinerarium.ProspectorBlockName) + : repairUrn ? (urn.HasSkull ? TEFeatureCinerarium.RepairSkullBlockName : TEFeatureCinerarium.RepairBlockName) : lootUrn ? (urn.HasSkull ? TEFeatureCinerarium.LootSkullBlockName : TEFeatureCinerarium.LootBlockName) diff --git a/HarmonySrc/CinerariumGenie.cs b/HarmonySrc/CinerariumGenie.cs index eba3aea..443158e 100644 --- a/HarmonySrc/CinerariumGenie.cs +++ b/HarmonySrc/CinerariumGenie.cs @@ -278,7 +278,7 @@ namespace NecromancerTome /// Покачивание и поворот джинна над урной. Только вид - никакой логики. [Preserve] - public class CinerariumGenieMotion : MonoBehaviour + public partial class CinerariumGenieMotion : MonoBehaviour { float _yaw; float _phase; @@ -548,7 +548,7 @@ namespace NecromancerTome _nextSleepCheck = Time.time + 0.5f; // Уснул - игрок далеко или дух не среди ближайших (CinerariumLoad.AnimateNearest): замирает дома, работа идёт. bool asleep = CinerariumGenie.PlayerFar(transform.position) || !CinerariumLoad.Animated(this); - if (asleep && !_asleep) + if (asleep && !_asleep && !Digging) // старатель под землёй - не возвращать домой { _t = -1f; // полёт прерван - дело всё равно выполнится Gesture(0f); @@ -558,7 +558,7 @@ namespace NecromancerTome } // Виден ли: не скрыт командой; дух логиста - только в полёте (дома сидит в кадиле). Рендереры - только при смене // (16.10, проверка на утечки: раньше список рендереров собирался каждые 0.5 с - мусор для GC). - bool show = !CinerariumGenie.Hidden && (!Small || _t >= 0f); + bool show = !CinerariumGenie.Hidden && (!Small || _t >= 0f) && !DigHidden; if (_hiddenApplied != !show) { _hiddenApplied = !show; @@ -567,6 +567,10 @@ namespace NecromancerTome r.enabled = show; } } + if (DigUpdate(_asleep)) + { + return; // старатель: спуск, под землёй, подъём (CinerariumProspector.cs) + } if (_stuck || _asleep) { return; // замер как был diff --git a/HarmonySrc/CinerariumProspector.cs b/HarmonySrc/CinerariumProspector.cs new file mode 100644 index 0000000..6595a33 --- /dev/null +++ b/HarmonySrc/CinerariumProspector.cs @@ -0,0 +1,650 @@ +using System.Collections.Generic; +using System.Globalization; +using UnityEngine; +using UnityEngine.Scripting; + +namespace NecromancerTome +{ + /// + /// Цинерарий-старатель (#64, решения 20.10 и 22.10.2026): дух добывает ископаемые. В «Содержимом» две ячейки, как у рабочего: + /// череп и образец - что добывать (один из Ores). Нить к ящику (любая, как у лутера) - куда класть. Есть всё - дух отлетает на + /// ближайшую свободную клетку рядом с урной и уходит на ProspectorDepth блоков вниз, растворяясь; раз в игровой час поднимается + /// так же, кладёт добычу в ящик и уходит снова. Без зоны и вне лимита урн, как остальные некротехнологи. + /// + /// Добыча (20.10: «цена - скоростью, а не ресурсом»): за игровые сутки камень, глина, песок, древесина - 5 стопок по 6000, + /// железо, свинец, уголь, селитра, сланец - 3 стопки; ходка - сутки / 24, разброс ±20%. Находки (20.10: «шанс добыть алмаз, + /// серебро и золото, как и в игре»; 22.10 - всегда, без книги «Удачный удар», латуни нет): ходка руды = столько блоков, сколько + /// даёт её выход с блока в игре (железо 105, прочие 50), на каждый - шансы игры (алмаз 0.5%, серебро 1%, золото 0.7%). + /// + /// Время - игровое (worldTime), чтобы добыча за сутки не зависела от их длины. Ходка засчитывается, только пока чанк урны + /// загружен; время перескочило больше чем на час (сон, прилив, settime) - ходка не засчитана, отсчёт заново. Ящик полон или + /// открыт игроком - дух ждёт дома с добычей (почернел, если полон) и докладывает, как появится место; новой ходки нет. + /// Добыча в руках духа не сохраняется: урну подняли или чанк выгрузился - ходка пропала. + /// + public partial class TEFeatureCinerarium + { + public const string ProspectorBlockName = "necroCinerariumProspector"; + public const string ProspectorSkullBlockName = "necroCinerariumProspectorSkull"; + + public static bool IsProspectorBlock(Block _b) + { + string n = _b?.GetBlockName(); + return n == ProspectorBlockName || n == ProspectorSkullBlockName; + } + + bool _prospector; + + public bool IsProspector + { + get + { + _ = IsLogist; // узнаёт тип блока + return _prospector; + } + } + + /// Что добывает старатель: предмет, добыча за игровые сутки, выход с блока руды в игре (0 - находок нет). + public static readonly (string item, int perDay, int perBlock)[] Ores = + { + ("resourceRockSmall", 30000, 0), + ("resourceClayLump", 30000, 0), + ("resourceCrushedSand", 30000, 0), + ("resourceWood", 30000, 0), + ("resourceScrapIron", 18000, 105), + ("resourceScrapLead", 18000, 50), + ("resourceCoal", 18000, 50), + ("resourcePotassiumNitratePowder", 18000, 50), + ("resourceOilShale", 18000, 50), + }; + + /// Находки с блока руды - как в blocks.xml игры (тег artOfMiningLuckyStrikeHarvest). + public static readonly (string item, float prob)[] Finds = + { + ("resourceRawDiamond", 0.005f), + ("resourceSilverNugget", 0.01f), + ("resourceGoldNugget", 0.007f), + }; + + /// Выключатель (консоль necroprospector off): духи остаются дома, ходок нет. + public static bool ProspectorOn = true; + /// Множитель добычи за ходку (консоль, для проверки баланса). + public static float ProspectorYield = 1f; + public static float ProspectorSpread = 0.2f; + /// На сколько блоков дух уходит вниз. + public static int ProspectorDepth = 5; + /// Ходка - игровой час (1000 единиц worldTime). + public const ulong TripTicks = 1000UL; + /// necroprospector trip - номер «внеочередной ходки»: урна, ещё не видевшая его, ходит сразу. + public static int ForceTrip; + + public enum ProspectorStatus + { + NoSkull, + Tide, + Off, + NoSample, + NoChest, + Full, + Digging + } + + bool _under; + ulong _tripAt; + float _nextProspect; + float _deliverAt; + int _forceSeen; + readonly List _carry = new List(); + + public static int OreIndex(ItemValue _iv) + { + string n = _iv?.ItemClass?.GetItemName(); + if (n == null) + { + return -1; + } + for (int i = 0; i < Ores.Length; i++) + { + if (Ores[i].item == n) + { + return i; + } + } + return -1; + } + + public static bool IsOreSample(ItemStack _s) => _s != null && !_s.IsEmpty() && OreIndex(_s.itemValue) >= 0; + + /// Сколько игровых минут до конца ходки (для консоли), -1 - дух не под землёй. + public int TripMinutesLeft(World _w) + { + if (!_under || _w == null) + { + return -1; + } + return _tripAt > _w.worldTime ? (int)((_tripAt - _w.worldTime) * 60UL / TripTicks) : 0; + } + + public int CarryCount + { + get + { + int n = 0; + foreach (ItemStack s in _carry) + { + n += s.count; + } + return n; + } + } + + public ProspectorStatus ProspectorNow(World _world) + { + if (!HasSkull) + { + return ProspectorStatus.NoSkull; + } + if (CinerariumLoad.Tide) + { + return ProspectorStatus.Tide; + } + if (!ProspectorOn) + { + return ProspectorStatus.Off; + } + if (!IsOreSample(Sample)) + { + return ProspectorStatus.NoSample; + } + if (!HasThreads) + { + return ProspectorStatus.NoChest; + } + return Stuck ? ProspectorStatus.Full : ProspectorStatus.Digging; + } + + string ProspectorStateText(World _world) + { + string key = ProspectorNow(_world) switch + { + ProspectorStatus.Tide => "necroZoneUrnTide", + ProspectorStatus.NoSample => "necroProspectorNoSample", + ProspectorStatus.NoChest => "necroProspectorNoChest", + ProspectorStatus.Full => "necroZoneUrnFull", + ProspectorStatus.Digging => "necroProspectorDigging", + _ => null + }; + return key != null ? ", " + Localization.Get(key) : ""; + } + + /// Работа старателя (сервер, раз в секунду): донести добычу, уйти под землю, ходка по часам игры. + void ProspectorWork(World _world) + { + if (Time.time < _nextProspect) + { + return; + } + _nextProspect = Time.time + 1f; + CinerariumGenieMotion motion = _genie != null ? _genie.GetComponent() : null; + ulong now = _world.worldTime; + if (_carry.Count > 0) + { + if (Time.time < _deliverAt) + { + return; // дух ещё поднимается + } + Deliver(_world); + bool full = _carry.Count > 0; + if (full != Stuck) + { + Stuck = full; + Debug.Log("[NecromancerTome] Cinerarium: старатель " + ToWorldPos() + (full ? " - ящик полон, дух ждёт с добычей" : " - добыча в ящике")); + } + motion?.SetStuck(full); + if (full) + { + return; + } + } + ProspectorStatus st = ProspectorNow(_world); + if (st != ProspectorStatus.Digging) + { + if (_under) + { + _under = false; + motion?.Rise(); + Debug.Log("[NecromancerTome] Cinerarium: дух старателя " + ToWorldPos() + " вернулся без добычи - " + st); + } + return; + } + if (!_under) + { + _under = true; + if (_tripAt <= now) + { + _tripAt = now + TripTicks; + } + _forceSeen = ForceTrip; + motion?.Dive(DigStand(_world) - Origin.position, ProspectorDepth); + Debug.Log("[NecromancerTome] Cinerarium: дух старателя " + ToWorldPos() + " ушёл под землю за " + Sample.itemValue.ItemClass?.GetItemName()); + return; + } + if (motion != null && !motion.Digging) + { + motion.Bury(DigStand(_world) - Origin.position, ProspectorDepth); // дух появился заново (чанк, прилив) - сразу под землёй + } + bool forced = _forceSeen != ForceTrip; + if (now < _tripAt && !forced) + { + return; + } + _forceSeen = ForceTrip; + if (!forced && now > _tripAt + TripTicks) + { + _tripAt = now + TripTicks; // время перескочило - ходка не засчитана + Debug.Log("[NecromancerTome] Cinerarium: у старателя " + ToWorldPos() + " время перескочило - ходка заново"); + return; + } + DigTrip(OreIndex(Sample.itemValue)); + _tripAt = forced ? now + TripTicks : _tripAt + TripTicks; + _under = false; // поднимается; донесёт - уйдёт снова + _deliverAt = Time.time + (motion != null ? motion.Rise() : 0f); + } + + /// Добыча одной ходки - в руки духа (_carry), стопками не больше игровых. + void DigTrip(int _ore) + { + if (_ore < 0) + { + return; + } + (string item, int perDay, int perBlock) o = Ores[_ore]; + float mean = o.perDay / 24f * ProspectorYield; + int n = Mathf.Max(1, Mathf.RoundToInt(mean * Random.Range(1f - ProspectorSpread, 1f + ProspectorSpread))); + Carry(o.item, n); + string log = o.item + " x" + n; + if (o.perBlock > 0) + { + int blocks = Mathf.Max(1, Mathf.RoundToInt(n / (float)o.perBlock)); + foreach ((string item, float prob) f in Finds) + { + int k = 0; + for (int b = 0; b < blocks; b++) + { + if (Random.value < f.prob) + { + k++; + } + } + if (k > 0) + { + Carry(f.item, k); + log += ", " + f.item + " x" + k; + } + } + log += " (блоков руды " + blocks + ")"; + } + Debug.Log("[NecromancerTome] Cinerarium: старатель " + ToWorldPos() + " - ходка: " + log); + } + + void Carry(string _item, int _n) + { + ItemValue iv = ItemClass.GetItem(_item); + if (iv == null || iv.type <= 0) + { + Debug.LogWarning("[NecromancerTome] Cinerarium: старатель - нет предмета " + _item); + return; + } + int max = Mathf.Max(1, iv.ItemClass.Stacknumber.Value); + while (_n > 0) + { + int k = Mathf.Min(_n, max); + _carry.Add(new ItemStack(iv.Clone(), k)); + _n -= k; + } + } + + /// Добычу - в ящики по нитям; что не влезло, остаётся у духа. + void Deliver(World _world) + { + List boxes = new List(); + void Add(Vector3i _p) + { + TEFeatureStorage s = CinerariumWork.BoxAt(_world, _p); + if (s != null && !boxes.Contains(s)) + { + boxes.Add(s); + } + } + if (HasThreadTo) + { + Add(ThreadTo); + } + foreach (Vector3i p in ThreadOut) + { + Add(p); + } + foreach (Vector3i p in ThreadFrom) + { + Add(p); + } + if (boxes.Count == 0) + { + return; // ящик открыт игроком или нитей нет - подождать + } + for (int i = _carry.Count - 1; i >= 0; i--) + { + ItemStack s = _carry[i]; + CinerariumWork.Place(s, boxes); + if (s.count <= 0) + { + _carry.RemoveAt(i); + } + } + } + + /// Куда дух спускается (20.10: «отлетает от урны на ближайший свободный блок, опускается на 5 блоков вниз»): из + /// восьми соседних клеток - первая свободная (воздух или растение), сначала по сторонам; нет - над урной. Низ клетки, мир. + Vector3 DigStand(World _world) + { + Vector3i urn = ToWorldPos(); + Vector3i[] dirs = + { + Vector3i.forward, Vector3i.right, Vector3i.back, Vector3i.left, + new Vector3i(1, 0, 1), new Vector3i(1, 0, -1), new Vector3i(-1, 0, -1), new Vector3i(-1, 0, 1) + }; + foreach (Vector3i d in dirs) + { + BlockValue bv = _world.GetBlock(urn + d); + if (bv.isair || bv.Block is BlockPlant) + { + return World.blockToTransformPos(urn + d); + } + } + return World.blockToTransformPos(urn + Vector3i.up); + } + + /// Урну убрали или выгрузили - добыча в руках духа пропадает. + void ProspectorReset() + { + if (_carry.Count > 0) + { + Debug.Log("[NecromancerTome] Cinerarium: старатель " + ToWorldPos() + " - недонесённая добыча пропала (" + CarryCount + ")"); + } + _carry.Clear(); + _under = false; + } + } + + /// Спуск духа старателя под землю и подъём (20.10: «опускается на 5 блоков вниз, постепенно становясь полностью + /// прозрачным; обратно - так же»). Яркость - MaterialPropertyBlock на рендерерах духа (материалы общие не трогаются): дух + /// светится сложением, поэтому «прозрачнее» = тусклее цвет и свечение; глаза гаснут на половине. + public partial class CinerariumGenieMotion + { + /// 0 - нет; 1 - летит к месту и спускается; 2 - под землёй (не виден); 3 - поднимается и летит домой. + int _digMode; + float _digT; + Vector3 _digFrom, _digHover; + float _digDepth; + public static float SinkSeconds = 2.5f; + List _fadeRenderers; + static readonly MaterialPropertyBlock _fadeBlock = new MaterialPropertyBlock(); + static readonly int ColorId = Shader.PropertyToID("_Color"); + static readonly int EmissionId = Shader.PropertyToID("_EmissionColor"); + float _fade = 1f; + + /// Под землёй или на пути туда/обратно. + public bool Digging => _digMode != 0; + + bool DigHidden => _digMode == 2; + + /// Лететь к клетке _stand (низ клетки, координаты сцены) и уйти на _depth блоков вниз. + public void Dive(Vector3 _stand, int _depth) + { + if (transform.parent == null) + { + return; + } + _t = -1f; + Gesture(0f); + _digFrom = transform.localPosition; + _digHover = DigHover(_stand); + _digDepth = _depth; + _digMode = 1; + _digT = 0f; + } + + /// Сразу под землёй (дух появился заново, а работа идёт). + public void Bury(Vector3 _stand, int _depth) + { + if (transform.parent == null) + { + return; + } + _digHover = DigHover(_stand); + _digDepth = _depth; + _digMode = 2; + transform.localPosition = _digHover + Vector3.down * _digDepth; + } + + /// Подняться и вернуться к урне; через сколько секунд будет дома. + public float Rise() + { + if (_digMode == 0) + { + return 0f; + } + _digMode = 3; + _digT = 0f; + return SinkSeconds + Mathf.Max(BackSeconds, Vector3.Distance(_digHover, Home()) / FlySpeed); + } + + Vector3 DigHover(Vector3 _stand) + { + return transform.parent.InverseTransformPoint(_stand) + new Vector3(0f, CinerariumGenie.OffsetY + Drop + 0.15f, 0f); + } + + /// Шаг спуска/подъёма; false - дух не копает, обычное поведение. + bool DigUpdate(bool _asleep) + { + if (_digMode == 0) + { + return false; + } + if (_asleep) + { + // Уснул - без полёта: спуск сразу закончен, подъём - сразу дома. + if (_digMode == 1) + { + _digMode = 2; + } + else if (_digMode == 3) + { + _digMode = 0; + SetFade(1f); + transform.localPosition = Home(); + return false; + } + return true; + } + _digT += Time.deltaTime; + Vector3 bottom = _digHover + Vector3.down * _digDepth; + float fly = Mathf.Max(OutSeconds, Vector3.Distance(_digFrom, _digHover) / FlySpeed); + if (_digMode == 1) + { + if (_digT < fly) + { + transform.localPosition = Vector3.Lerp(_digFrom, _digHover, Mathf.SmoothStep(0f, 1f, _digT / fly)); + Face(_digHover - _digFrom); + SetFade(1f); + } + else if (_digT < fly + SinkSeconds) + { + float s = (_digT - fly) / SinkSeconds; + transform.localPosition = Vector3.Lerp(_digHover, bottom, Mathf.SmoothStep(0f, 1f, s)); + SetFade(1f - s); + } + else + { + _digMode = 2; + transform.localPosition = bottom; + } + } + else if (_digMode == 2) + { + transform.localPosition = bottom; + } + else + { + Vector3 home = Home(); + float back = Mathf.Max(BackSeconds, Vector3.Distance(_digHover, home) / FlySpeed); + if (_digT < SinkSeconds) + { + float s = _digT / SinkSeconds; + transform.localPosition = Vector3.Lerp(bottom, _digHover, Mathf.SmoothStep(0f, 1f, s)); + SetFade(s); + } + else if (_digT < SinkSeconds + back) + { + SetFade(1f); + transform.localPosition = Vector3.Lerp(_digHover, home, Mathf.SmoothStep(0f, 1f, (_digT - SinkSeconds) / back)); + Face(home - _digHover); + } + else + { + _digMode = 0; + SetFade(1f); + return false; + } + } + transform.localRotation = Quaternion.Euler(0f, _yaw, 0f); + return true; + } + + /// Яркость духа долей _k (1 - как обычно, свойства сняты). + void SetFade(float _k) + { + _k = Mathf.Clamp01(_k); + if (Mathf.Abs(_k - _fade) < 0.01f && (_k < 1f || _fade >= 1f)) + { + return; + } + _fade = _k; + if (_fadeRenderers == null) + { + _fadeRenderers = new List(GetComponentsInChildren(true)); + } + foreach (Renderer r in _fadeRenderers) + { + if (r == null) + { + continue; + } + if (r.name.Contains("eye")) + { + r.forceRenderingOff = _k < 0.5f; + continue; + } + if (_k >= 1f) + { + r.SetPropertyBlock(null); + continue; + } + Material m = r.sharedMaterial; + if (m == null) + { + continue; + } + r.GetPropertyBlock(_fadeBlock); + if (m.HasProperty(ColorId)) + { + _fadeBlock.SetColor(ColorId, m.GetColor(ColorId) * _k); + } + if (m.HasProperty(EmissionId)) + { + _fadeBlock.SetColor(EmissionId, m.GetColor(EmissionId) * _k); + } + r.SetPropertyBlock(_fadeBlock); + } + } + } + + /// Консоль старателя: выключатель, внеочередная ходка, множитель добычи, состояние урн. + [Preserve] + public class ConsoleCmdNecroProspector : ConsoleCmdAbstract + { + public override bool IsExecuteOnClient => true; + + public override bool AllowedInMainMenu => false; + + public override string[] getCommands() + { + return new string[] { "necroprospector" }; + } + + public override string getDescription() + { + return "Necromancer's Tome: Prospector Cinerarium - switch, extra trip, yield multiplier, status."; + } + + public override string getHelp() + { + return "necroprospector - status of every loaded prospector urn\n" + + "necroprospector on|off - off: spirits stay home, no trips\n" + + "necroprospector trip - every digging spirit comes back with a trip right now\n" + + "necroprospector yield - trip size multiplier (1 = as designed)\n" + + "necroprospector spread - random spread of a trip (0.2 = ±20%)\n" + + "necroprospector depth - how many blocks the spirit sinks"; + } + + public override void Execute(List _params, CommandSenderInfo _senderInfo) + { + SdtdConsole con = SingletonMonoBehaviour.Instance; + World w = GameManager.Instance?.World; + if (_params.Count >= 1) + { + string cmd = _params[0].ToLowerInvariant(); + float v = 0f; + bool num = _params.Count >= 2 && float.TryParse(_params[1], NumberStyles.Float, CultureInfo.InvariantCulture, out v); + switch (cmd) + { + case "on": + case "off": + TEFeatureCinerarium.ProspectorOn = cmd == "on"; + break; + case "trip": + TEFeatureCinerarium.ForceTrip++; + break; + case "yield" when num: + TEFeatureCinerarium.ProspectorYield = Mathf.Max(0f, v); + break; + case "spread" when num: + TEFeatureCinerarium.ProspectorSpread = Mathf.Clamp01(v); + break; + case "depth" when num: + TEFeatureCinerarium.ProspectorDepth = Mathf.Clamp((int)v, 1, 30); + break; + default: + con.Output("necroprospector: unknown '" + _params[0] + "' - see 'help necroprospector'"); + return; + } + } + con.Output("necroprospector: " + (TEFeatureCinerarium.ProspectorOn ? "on" : "OFF") + ", yield x" + TEFeatureCinerarium.ProspectorYield.ToString("F2", CultureInfo.InvariantCulture) + + ", spread " + TEFeatureCinerarium.ProspectorSpread.ToString("F2", CultureInfo.InvariantCulture) + ", depth " + TEFeatureCinerarium.ProspectorDepth); + if (w == null) + { + return; + } + foreach (Vector3i p in CinerariumRegistry.All()) + { + TEFeatureCinerarium u = CinerariumThreads.UrnAt(w, p); + if (u == null || !u.IsProspector) + { + continue; + } + int left = u.TripMinutesLeft(w); + con.Output(" " + p + ": " + u.ProspectorNow(w) + ", sample " + (u.Sample == null || u.Sample.IsEmpty() ? "-" : u.Sample.itemValue.ItemClass?.GetItemName()) + + (left >= 0 ? ", trip in " + left + " game min" : "") + (u.CarryCount > 0 ? ", carrying " + u.CarryCount : "")); + } + } + } +} diff --git a/HarmonySrc/CinerariumTrunk.cs b/HarmonySrc/CinerariumTrunk.cs index de59b6c..fb4e945 100644 --- a/HarmonySrc/CinerariumTrunk.cs +++ b/HarmonySrc/CinerariumTrunk.cs @@ -10,6 +10,7 @@ namespace NecromancerTome /// в ячейке остаётся один череп, всё остальное (лишние черепа, чужие предметы) - назад в рюкзак, и урна узнаёт, есть ли /// в ней череп. Череп - обычный предмет (ItemClass): он уже в сейвах, менять ему класс на модификацию нельзя. /// У урны рабочего (18.10) ячеек две: вторая - образец (любой предмет, стопка целиком - сколько держать в запасе). + /// У старателя (22.10) вторая ячейка - что добывать: один предмет из TEFeatureCinerarium.Ores, лишнее и чужое - назад. /// public static class CinerariumContents { @@ -31,12 +32,12 @@ namespace NecromancerTome } _urn = _u; _player = _p; - _bag = new Bag(new Vector2i(_u.IsWorker ? 2 : 1, 1), XUiC_ItemStack.StackLocationTypes.LootContainer, null); + _bag = new Bag(new Vector2i(_u.IsWorker || _u.IsProspector ? 2 : 1, 1), XUiC_ItemStack.StackLocationTypes.LootContainer, null); if (_u.HasSkull && skull != null && skull.type > 0) { _bag.SetSlot(0, new ItemStack(skull, 1)); } - if (_u.IsWorker && _u.Sample != null && !_u.Sample.IsEmpty()) + if ((_u.IsWorker || _u.IsProspector) && _u.Sample != null && !_u.Sample.IsEmpty()) { _bag.SetSlot(1, _u.Sample.Clone()); } @@ -59,7 +60,7 @@ namespace NecromancerTome { return; } - if (_urn.IsWorker && _bag.ItemGrid.items.Length > 1) + if ((_urn.IsWorker || _urn.IsProspector) && _bag.ItemGrid.items.Length > 1) { ItemStack smp = _bag.ItemGrid.items[1]; ItemStack first = _bag.ItemGrid.items[0]; @@ -70,6 +71,29 @@ namespace NecromancerTome _bag.SetSlot(1, ItemStack.Empty.Clone()); smp = _bag.ItemGrid.items[1]; } + if (_urn.IsProspector && smp != null && !smp.IsEmpty()) + { + // Старатель: образец - один предмет из списка добычи; чужое целиком, лишние штуки - назад. + if (!TEFeatureCinerarium.IsOreSample(smp)) + { + GiveBack(smp.Clone()); + _bag.SetSlot(1, ItemStack.Empty.Clone()); + if (_player != null) + { + GameManager.ShowTooltip(_player, Localization.Get("necroProspectorBadSample")); + } + } + else if (smp.count > 1) + { + ItemStack back = smp.Clone(); + back.count = smp.count - 1; + GiveBack(back); + ItemStack one = smp.Clone(); + one.count = 1; + _bag.SetSlot(1, one); + } + smp = _bag.ItemGrid.items[1]; + } _urn.SetSample(smp != null && !smp.IsEmpty() ? smp.Clone() : ItemStack.Empty.Clone()); } ItemStack s = _bag.ItemGrid.items[0]; diff --git a/HarmonySrc/DecorColliderTagPatch.cs b/HarmonySrc/DecorColliderTagPatch.cs index 9a5b473..ea34667 100644 --- a/HarmonySrc/DecorColliderTagPatch.cs +++ b/HarmonySrc/DecorColliderTagPatch.cs @@ -20,7 +20,7 @@ namespace NecromancerTome static bool Ours(string _name) { - return _name == "necroBookMindPrefab" || _name == "necroKnifeDecorPrefab" || _name == "necroMortarPrefab" || _name.StartsWith("necroBloodRose") || _name.StartsWith("necroPoster") || _name == "necroBookDoorPrefab" || _name.StartsWith("necroSkullDecor") || _name == "necroCinerariumPrefab" || _name == "necroCinerariumWorkerPrefab" || _name.StartsWith("necroCinerariumLogist") || _name == "necroCinerariumRepairPrefab" || _name == "necroCinerariumLootPrefab"; + return _name == "necroBookMindPrefab" || _name == "necroKnifeDecorPrefab" || _name == "necroMortarPrefab" || _name.StartsWith("necroBloodRose") || _name.StartsWith("necroPoster") || _name == "necroBookDoorPrefab" || _name.StartsWith("necroSkullDecor") || _name == "necroCinerariumPrefab" || _name == "necroCinerariumWorkerPrefab" || _name.StartsWith("necroCinerariumLogist") || _name == "necroCinerariumRepairPrefab" || _name == "necroCinerariumLootPrefab" || _name == "necroCinerariumProspectorPrefab"; } public static void Postfix(Transform __result) diff --git a/NecromancerHarmony.dll b/NecromancerHarmony.dll index 46c3afd..108371f 100644 Binary files a/NecromancerHarmony.dll and b/NecromancerHarmony.dll differ diff --git a/NecromancerHarmony.pdb b/NecromancerHarmony.pdb index 918e5a8..d99e510 100644 Binary files a/NecromancerHarmony.pdb and b/NecromancerHarmony.pdb differ diff --git a/Textures/necroTomeUrns.png b/Textures/necroTomeUrns.png index 55126b7..fe50890 100644 Binary files a/Textures/necroTomeUrns.png and b/Textures/necroTomeUrns.png differ