Compare commits
+61
-52
File diff suppressed because one or more lines are too long
@@ -272,6 +272,38 @@
|
|||||||
<sprite depth="3" name="g25" pos="64,-275" width="48" height="48" atlas="ItemIconAtlas" sprite="oldCash" on_press="true" sound="[paging_click]" foregroundlayer="true" />
|
<sprite depth="3" name="g25" pos="64,-275" width="48" height="48" atlas="ItemIconAtlas" sprite="oldCash" on_press="true" sound="[paging_click]" foregroundlayer="true" />
|
||||||
</window>
|
</window>
|
||||||
</append>
|
</append>
|
||||||
|
<!-- Квадраты зоны ремонтника и лутера (#84, 28.10.2026, HarmonySrc/CinerariumZoneWindow.cs): сетка 3x3 в окне «Содержимое»,
|
||||||
|
север сверху, урна в среднем. Окно в группе bagStorage (xui.xml), вне урн с большой зоной скрыто. s0..s8 - квадраты
|
||||||
|
(столбец + строка * 3, строка 0 - юг), f0..f8 - их рамки; цвета ставит контроллер. -->
|
||||||
|
<append xpath="/windows">
|
||||||
|
<window name="necroUrnZone" width="228" height="296" depth="5" panel="Left" backgroundcolor="[black]" cursor_area="true" controller="NecromancerTome.NecroUrnZone, NecromancerHarmony">
|
||||||
|
<rect name="header" height="43" depth="0">
|
||||||
|
<sprite depth="1" name="headerbackground" sprite="ui_game_panel_header" width="228" height="43" />
|
||||||
|
<sprite depth="3" name="windowIcon" style="icon32px" pivot="center" pos="21,-21" sprite="ui_game_symbol_frames" />
|
||||||
|
<label depth="2" name="title" pos="39,-6" width="183" height="32" text_key="necroUrnZoneTitle" font_size="26" />
|
||||||
|
</rect>
|
||||||
|
<label depth="2" name="north" pos="0,-49" width="228" height="22" justify="center" font_size="20" color="200,200,205" text_key="necroUrnZoneNorth" />
|
||||||
|
<sprite depth="2" name="f6" pos="10,-74" width="68" height="68" sprite="menu_empty2px" type="sliced" fillcenter="false" />
|
||||||
|
<sprite depth="3" name="s6" pos="12,-76" width="64" height="64" sprite="menu_empty2px" type="sliced" on_press="true" sound="[paging_click]" />
|
||||||
|
<sprite depth="2" name="f7" pos="80,-74" width="68" height="68" sprite="menu_empty2px" type="sliced" fillcenter="false" />
|
||||||
|
<sprite depth="3" name="s7" pos="82,-76" width="64" height="64" sprite="menu_empty2px" type="sliced" on_press="true" sound="[paging_click]" />
|
||||||
|
<sprite depth="2" name="f8" pos="150,-74" width="68" height="68" sprite="menu_empty2px" type="sliced" fillcenter="false" />
|
||||||
|
<sprite depth="3" name="s8" pos="152,-76" width="64" height="64" sprite="menu_empty2px" type="sliced" on_press="true" sound="[paging_click]" />
|
||||||
|
<sprite depth="2" name="f3" pos="10,-144" width="68" height="68" sprite="menu_empty2px" type="sliced" fillcenter="false" />
|
||||||
|
<sprite depth="3" name="s3" pos="12,-146" width="64" height="64" sprite="menu_empty2px" type="sliced" on_press="true" sound="[paging_click]" />
|
||||||
|
<sprite depth="2" name="f4" pos="80,-144" width="68" height="68" sprite="menu_empty2px" type="sliced" fillcenter="false" />
|
||||||
|
<sprite depth="3" name="s4" pos="82,-146" width="64" height="64" sprite="menu_empty2px" type="sliced" on_press="true" sound="[paging_click]" />
|
||||||
|
<sprite depth="2" name="f5" pos="150,-144" width="68" height="68" sprite="menu_empty2px" type="sliced" fillcenter="false" />
|
||||||
|
<sprite depth="3" name="s5" pos="152,-146" width="64" height="64" sprite="menu_empty2px" type="sliced" on_press="true" sound="[paging_click]" />
|
||||||
|
<sprite depth="2" name="f0" pos="10,-214" width="68" height="68" sprite="menu_empty2px" type="sliced" fillcenter="false" />
|
||||||
|
<sprite depth="3" name="s0" pos="12,-216" width="64" height="64" sprite="menu_empty2px" type="sliced" on_press="true" sound="[paging_click]" />
|
||||||
|
<sprite depth="2" name="f1" pos="80,-214" width="68" height="68" sprite="menu_empty2px" type="sliced" fillcenter="false" />
|
||||||
|
<sprite depth="3" name="s1" pos="82,-216" width="64" height="64" sprite="menu_empty2px" type="sliced" on_press="true" sound="[paging_click]" />
|
||||||
|
<sprite depth="2" name="f2" pos="150,-214" width="68" height="68" sprite="menu_empty2px" type="sliced" fillcenter="false" />
|
||||||
|
<sprite depth="3" name="s2" pos="152,-216" width="64" height="64" sprite="menu_empty2px" type="sliced" on_press="true" sound="[paging_click]" />
|
||||||
|
<sprite depth="4" name="urn" pos="114,-178" width="36" height="36" pivot="center" sprite="ui_game_symbol_skull" color="[white]" />
|
||||||
|
</window>
|
||||||
|
</append>
|
||||||
<!-- Лимит урн (#65, 23.10.2026; HarmonySrc/CinerariumLimitPanel.cs): блок справа от ячеек урны в «Содержимом» - всего,
|
<!-- Лимит урн (#65, 23.10.2026; HarmonySrc/CinerariumLimitPanel.cs): блок справа от ячеек урны в «Содержимом» - всего,
|
||||||
доступно, до следующей. Позицию по числу ячеек ставит контроллер; вне урны блок скрыт. -->
|
доступно, до следующей. Позицию по числу ячеек ставит контроллер; вне урны блок скрыт. -->
|
||||||
<append xpath="/windows/window[@name='windowBagStorage']">
|
<append xpath="/windows/window[@name='windowBagStorage']">
|
||||||
@@ -282,6 +314,19 @@
|
|||||||
<label depth="7" name="next" pos="10,-50" width="404" height="22" font_size="20" color="200,200,205" />
|
<label depth="7" name="next" pos="10,-50" width="404" height="22" font_size="20" color="200,200,205" />
|
||||||
</rect>
|
</rect>
|
||||||
</append>
|
</append>
|
||||||
|
<!-- Чего не хватает ремонтнику (24.10.2026): ячейка с материалом и «!» под блоком лимита урн, только показ
|
||||||
|
(HarmonySrc/CinerariumRepairNeed.cs). -->
|
||||||
|
<append xpath="/windows/window[@name='windowBagStorage']">
|
||||||
|
<rect name="necroRepairNeed" pos="87,-132" width="420" height="75" depth="6" controller="NecromancerTome.NecroRepairNeed, NecromancerHarmony">
|
||||||
|
<sprite depth="6" pos="0,0" width="420" height="75" color="0,0,0,190" sprite="menu_empty2px" type="sliced" />
|
||||||
|
<sprite depth="7" pos="0,0" width="75" height="75" sprite="menu_empty3px" color="[black]" type="sliced" fillcenter="false" />
|
||||||
|
<sprite depth="7" pos="2,-2" width="71" height="71" sprite="menu_empty3px" color="200,40,40,255" type="sliced" fillcenter="false" />
|
||||||
|
<sprite depth="8" name="icon" pos="37,-37" width="64" height="64" atlas="ItemIconAtlas" pivot="center" foregroundlayer="true" />
|
||||||
|
<label depth="9" name="excl" pos="7,-2" width="24" height="38" font_size="38" color="235,40,40,255" text="!" effect="outline" foregroundlayer="true" />
|
||||||
|
<label depth="9" name="count" pos="4,-50" width="66" height="22" font_size="22" justify="right" color="[white]" effect="outline" foregroundlayer="true" />
|
||||||
|
<label depth="7" pos="85,-14" width="330" height="48" font_size="20" color="[white]" text_key="necroCinerariumRepairNeed" />
|
||||||
|
</rect>
|
||||||
|
</append>
|
||||||
<!-- Вкладка «Некромантия» (ui_display.xml): в рюкзаке и на верстаке 10 вкладок вместо 9 - шаг значков 43 -> 39, значки прежние (28). -->
|
<!-- Вкладка «Некромантия» (ui_display.xml): в рюкзаке и на верстаке 10 вкладок вместо 9 - шаг значков 43 -> 39, значки прежние (28). -->
|
||||||
<set xpath="/windows/window[@name='windowCraftingList']//grid[@name='categories']/@cols">10</set>
|
<set xpath="/windows/window[@name='windowCraftingList']//grid[@name='categories']/@cols">10</set>
|
||||||
<set xpath="/windows/window[@name='windowCraftingList']//grid[@name='categories']/@cell_width">39</set>
|
<set xpath="/windows/window[@name='windowCraftingList']//grid[@name='categories']/@cell_width">39</set>
|
||||||
|
|||||||
@@ -62,5 +62,6 @@
|
|||||||
<!-- Фильтр урны-логиста - в окне хранилища игры (bagStorage), рядом с ячейкой «Содержимого» (windows.xml). -->
|
<!-- Фильтр урны-логиста - в окне хранилища игры (bagStorage), рядом с ячейкой «Содержимого» (windows.xml). -->
|
||||||
<append xpath="/xui/window_group[@name='bagStorage']">
|
<append xpath="/xui/window_group[@name='bagStorage']">
|
||||||
<window name="necroUrnFilter" />
|
<window name="necroUrnFilter" />
|
||||||
|
<window name="necroUrnZone" />
|
||||||
</append>
|
</append>
|
||||||
</xui>
|
</xui>
|
||||||
|
|||||||
+27
-10
@@ -1913,16 +1913,35 @@
|
|||||||
<property name="CustomIconTint" value="150, 205, 255"/>
|
<property name="CustomIconTint" value="150, 205, 255"/>
|
||||||
</item>
|
</item>
|
||||||
</append>
|
</append>
|
||||||
|
<!-- Резная кость (#86, 28.10.2026): отдельный инструмент нитей для урн - делает то же, что нож хватом для нитей
|
||||||
|
(HarmonySrc/CinerariumThreads.cs, IsThreadTool), для тех, кто хват у ножа не нашёл. Наследует нож для нитей:
|
||||||
|
хват 68 + поза рук мода (нож в левой руке), действия ножа перехвачены нитями. В руке лежит ровно как нож:
|
||||||
|
модель - копия префаба ножа со своей сеткой (BlenderWork/necroCarvedBone, Unity NecroCarvedBoneSetup.cs,
|
||||||
|
бандл necroknife): бедренная кость длиной с нож, головка на месте клинка, железный колпачок, кольцо на месте
|
||||||
|
гарды, руны цвета нитей. Новый предмет - сейвов ножа не касается. -->
|
||||||
|
<append xpath="/items">
|
||||||
|
<item name="necroCarvedBone">
|
||||||
|
<property name="Extends" value="necroWpnBladeNecroKnifeThread"/>
|
||||||
|
<property name="CreativeMode" value="Player"/>
|
||||||
|
<property name="Group" value="Tools/Traps,Necromancy"/>
|
||||||
|
<property name="Meshfile" value="#@modfolder(NecromancerTome):Resources/necroknife?necroCarvedBonePrefab.prefab"/>
|
||||||
|
<property name="CustomIcon" value="necroCarvedBone"/>
|
||||||
|
<property name="CustomIconTint" value="255, 255, 255"/>
|
||||||
|
<property name="DescriptionKey" value="necroCarvedBoneDesc"/>
|
||||||
|
<property name="ShowQuality" value="false"/>
|
||||||
|
<property name="EconomicValue" value="0"/>
|
||||||
|
</item>
|
||||||
|
</append>
|
||||||
<!-- Магические ингредиенты (18.10.2026): магическая пыль, хранилище магии (колба-«батарейка» в латунных клеммах с
|
<!-- Магические ингредиенты (18.10.2026): магическая пыль, хранилище магии (колба-«батарейка» в латунных клеммах с
|
||||||
маркировкой MAGIA KONSERVUJO / KAPACITO ✶ 30) и повреждённое хранилище. Пыль - из ступки (4 из повреждённого,
|
маркировкой MAGIA KONSERVUJO / KAPACITO ✶ 30) и повреждённое хранилище. Пыль - из ступки (4 из повреждённого,
|
||||||
30 из целого); пыль - будущий ингредиент камня портала, целое хранилище - деталь батарей/портала, сами хранилища
|
30 из целого); пыль - будущий ингредиент камня портала, целое хранилище - деталь батарей/портала, сами хранилища
|
||||||
позже появятся в префабе. Модели в руке пока ванильный мешок; свои модели - _private/.../necroMagic/necroMagic.blend. -->
|
позже появятся в префабе. Модели свои (26.10.2026, #40): necroMagic.blend -> бандл necromagic (Unity NecroMagicSetup.cs);
|
||||||
|
хват книги (21) - пыль горкой лежит на ладони (указание 26.10), колба лежит вдоль ладони. -->
|
||||||
<append xpath="/items">
|
<append xpath="/items">
|
||||||
<item name="necroMagicDust">
|
<item name="necroMagicDust">
|
||||||
<property name="DescriptionKey" value="necroMagicDustDesc"/>
|
<property name="DescriptionKey" value="necroMagicDustDesc"/>
|
||||||
<property name="HoldType" value="45"/>
|
<property name="HoldType" value="21"/>
|
||||||
<property name="Meshfile" value="@:Other/Items/Misc/sackPrefab.prefab"/>
|
<property name="Meshfile" value="#@modfolder(NecromancerTome):Resources/necromagic?necroMagicDustPrefab.prefab"/>
|
||||||
<property name="DropMeshfile" value="@:Other/Items/Misc/sack_droppedPrefab.prefab"/>
|
|
||||||
<property name="Material" value="MresourceCrop"/>
|
<property name="Material" value="MresourceCrop"/>
|
||||||
<property name="Stacknumber" value="500"/>
|
<property name="Stacknumber" value="500"/>
|
||||||
<property name="EconomicValue" value="20"/>
|
<property name="EconomicValue" value="20"/>
|
||||||
@@ -1930,9 +1949,8 @@
|
|||||||
</item>
|
</item>
|
||||||
<item name="necroManaVessel">
|
<item name="necroManaVessel">
|
||||||
<property name="DescriptionKey" value="necroManaVesselDesc"/>
|
<property name="DescriptionKey" value="necroManaVesselDesc"/>
|
||||||
<property name="HoldType" value="45"/>
|
<property name="HoldType" value="21"/>
|
||||||
<property name="Meshfile" value="@:Other/Items/Misc/sackPrefab.prefab"/>
|
<property name="Meshfile" value="#@modfolder(NecromancerTome):Resources/necromagic?necroManaVesselPrefab.prefab"/>
|
||||||
<property name="DropMeshfile" value="@:Other/Items/Misc/sack_droppedPrefab.prefab"/>
|
|
||||||
<property name="Material" value="MresourceCrop"/>
|
<property name="Material" value="MresourceCrop"/>
|
||||||
<property name="Stacknumber" value="10"/>
|
<property name="Stacknumber" value="10"/>
|
||||||
<property name="EconomicValue" value="300"/>
|
<property name="EconomicValue" value="300"/>
|
||||||
@@ -1940,9 +1958,8 @@
|
|||||||
</item>
|
</item>
|
||||||
<item name="necroManaVesselBroken">
|
<item name="necroManaVesselBroken">
|
||||||
<property name="DescriptionKey" value="necroManaVesselBrokenDesc"/>
|
<property name="DescriptionKey" value="necroManaVesselBrokenDesc"/>
|
||||||
<property name="HoldType" value="45"/>
|
<property name="HoldType" value="21"/>
|
||||||
<property name="Meshfile" value="@:Other/Items/Misc/sackPrefab.prefab"/>
|
<property name="Meshfile" value="#@modfolder(NecromancerTome):Resources/necromagic?necroManaVesselBrokenPrefab.prefab"/>
|
||||||
<property name="DropMeshfile" value="@:Other/Items/Misc/sack_droppedPrefab.prefab"/>
|
|
||||||
<property name="Material" value="MresourceCrop"/>
|
<property name="Material" value="MresourceCrop"/>
|
||||||
<property name="Stacknumber" value="10"/>
|
<property name="Stacknumber" value="10"/>
|
||||||
<property name="EconomicValue" value="40"/>
|
<property name="EconomicValue" value="40"/>
|
||||||
|
|||||||
@@ -183,7 +183,7 @@
|
|||||||
<unlock_entry item="necroModKnifeGravesRepose" unlock_tier="4"/>
|
<unlock_entry item="necroModKnifeGravesRepose" unlock_tier="4"/>
|
||||||
<!-- 22.10.2026: урны - одной иконкой с recipes= (как комплекты брони в ванили): в окне навыка у строки только 14 иконок, в группе было 17 - последние урны не показывались («Не все рецепты цинерария доступны»). Клик по иконке - все урны списком. -->
|
<!-- 22.10.2026: урны - одной иконкой с recipes= (как комплекты брони в ванили): в окне навыка у строки только 14 иконок, в группе было 17 - последние урны не показывались («Не все рецепты цинерария доступны»). Клик по иконке - все урны списком. -->
|
||||||
<unlock_entry item="necroModKnifeDarkSense" unlock_tier="5"/>
|
<unlock_entry item="necroModKnifeDarkSense" unlock_tier="5"/>
|
||||||
<unlock_entry item="necroCinerarium" unlock_tier="5" recipes="necroCinerarium,necroCinerariumLogist,necroCinerariumWorker,necroCinerariumRepair,necroCinerariumLoot,necroCinerariumProspector"/>
|
<unlock_entry item="necroCinerarium" unlock_tier="5" recipes="necroCinerarium,necroCarvedBone,necroCinerariumLogist,necroCinerariumWorker,necroCinerariumRepair,necroCinerariumLoot,necroCinerariumProspector"/>
|
||||||
</display_entry>
|
</display_entry>
|
||||||
<!-- Ступенчатая разблокировка внутри группы. ПЕРЕРАСПРЕДЕЛЕНО 2026-09-09: четыре из
|
<!-- Ступенчатая разблокировка внутри группы. ПЕРЕРАСПРЕДЕЛЕНО 2026-09-09: четыре из
|
||||||
шести модов ножа (вода/еда/покой/чутьё) уехали отсюда в группу 1 - см. большой
|
шести модов ножа (вода/еда/покой/чутьё) уехали отсюда в группу 1 - см. большой
|
||||||
|
|||||||
+35
-92
@@ -33,7 +33,7 @@
|
|||||||
weapon - "он не является начальным оружием... больше про комфорт" - so the same
|
weapon - "он не является начальным оружием... больше про комфорт" - so the same
|
||||||
circular-dependency concern doesn't apply, and the user wants it to actually cost a
|
circular-dependency concern doesn't apply, and the user wants it to actually cost a
|
||||||
real chunk of ash rather than a token amount. -->
|
real chunk of ash rather than a token amount. -->
|
||||||
<recipe name="thrownStonePortalBlue" count="1" tags="packMuleCrafting">
|
<recipe name="thrownStonePortalBlue" count="1" craft_time="31" tags="packMuleCrafting">
|
||||||
<ingredient name="resourceRockSmall" count="1"/>
|
<ingredient name="resourceRockSmall" count="1"/>
|
||||||
<ingredient name="drinkJarPureMineralWater" count="3"/>
|
<ingredient name="drinkJarPureMineralWater" count="3"/>
|
||||||
<ingredient name="resourceYuccaFibers" count="15"/>
|
<ingredient name="resourceYuccaFibers" count="15"/>
|
||||||
@@ -90,7 +90,7 @@
|
|||||||
their current HP per unit to make (NecromancerBloodPatch.cs) - this recipe shouldn't
|
their current HP per unit to make (NecromancerBloodPatch.cs) - this recipe shouldn't
|
||||||
demand many. -->
|
demand many. -->
|
||||||
<append xpath="/recipes">
|
<append xpath="/recipes">
|
||||||
<recipe name="necroHeresyPyramid" count="1" craft_area="workbench" tags="workbenchCrafting,necroNecromancyAdept">
|
<recipe name="necroHeresyPyramid" count="1" craft_area="workbench" craft_time="191" tags="workbenchCrafting,necroNecromancyAdept">
|
||||||
<ingredient name="resourceZombieAsh" count="1200"/>
|
<ingredient name="resourceZombieAsh" count="1200"/>
|
||||||
<ingredient name="resourceRockSmall" count="300"/>
|
<ingredient name="resourceRockSmall" count="300"/>
|
||||||
<ingredient name="resourceYuccaFibers" count="200"/>
|
<ingredient name="resourceYuccaFibers" count="200"/>
|
||||||
@@ -124,15 +124,15 @@
|
|||||||
personal crafting). workbenchCrafting is just the same UI-categorization tag vanilla's
|
personal crafting). workbenchCrafting is just the same UI-categorization tag vanilla's
|
||||||
own workbench recipes carry alongside craft_area, not a separate gate. -->
|
own workbench recipes carry alongside craft_area, not a separate gate. -->
|
||||||
<append xpath="/recipes">
|
<append xpath="/recipes">
|
||||||
<recipe name="bookSummonZombieDog" count="1" craft_area="workbench" tags="learnable,workbenchCrafting,necroNecromancyLvl1300">
|
<!-- 26.10.2026: окно рецепта показывает только 8 компонентов - девятый (прах) был не виден, книга «не делалась».
|
||||||
|
Сокращено до 7 (без монет и ткани). Время - вчетверо меньше расчётного игрой (50 гнилого мяса по 95 с = 80 мин). -->
|
||||||
|
<recipe name="bookSummonZombieDog" count="1" craft_area="workbench" craft_time="1210" tags="learnable,workbenchCrafting,necroNecromancyLvl1300">
|
||||||
<ingredient name="foodRottingFlesh" count="50"/>
|
<ingredient name="foodRottingFlesh" count="50"/>
|
||||||
<ingredient name="medicalBloodBag" count="3"/>
|
<ingredient name="medicalBloodBag" count="3"/>
|
||||||
<ingredient name="drinkJarBoiledWater" count="4"/>
|
<ingredient name="drinkJarBoiledWater" count="4"/>
|
||||||
<ingredient name="resourceYuccaFibers" count="50"/>
|
<ingredient name="resourceYuccaFibers" count="50"/>
|
||||||
<ingredient name="resourceCloth" count="3"/>
|
|
||||||
<ingredient name="resourceLeather" count="10"/>
|
<ingredient name="resourceLeather" count="10"/>
|
||||||
<ingredient name="resourceBone" count="40"/>
|
<ingredient name="resourceBone" count="40"/>
|
||||||
<ingredient name="casinoCoin" count="5"/>
|
|
||||||
<ingredient name="resourceZombieAsh" count="10"/>
|
<ingredient name="resourceZombieAsh" count="10"/>
|
||||||
</recipe>
|
</recipe>
|
||||||
</append>
|
</append>
|
||||||
@@ -160,7 +160,7 @@
|
|||||||
REDISTRIBUTED 2026-08-30, was necroNecromancyLvl200 before) - not specified by the user,
|
REDISTRIBUTED 2026-08-30, was necroNecromancyLvl200 before) - not specified by the user,
|
||||||
guessed consistent with the other "advanced ritual" summon items. -->
|
guessed consistent with the other "advanced ritual" summon items. -->
|
||||||
<append xpath="/recipes">
|
<append xpath="/recipes">
|
||||||
<recipe name="bookBanshee" count="1" craft_area="workbench" tags="learnable,workbenchCrafting,necroNecromancyNecromancer">
|
<recipe name="bookBanshee" count="1" craft_area="workbench" craft_time="241" tags="learnable,workbenchCrafting,necroNecromancyNecromancer">
|
||||||
<ingredient name="resourcePaper" count="15"/>
|
<ingredient name="resourcePaper" count="15"/>
|
||||||
<ingredient name="resourceGlue" count="3"/>
|
<ingredient name="resourceGlue" count="3"/>
|
||||||
<ingredient name="foodRottingFlesh" count="10"/>
|
<ingredient name="foodRottingFlesh" count="10"/>
|
||||||
@@ -453,15 +453,10 @@
|
|||||||
именно тогда (правило «порог по нужде, а не по силе»). Тег и unlock_level группы 2 в
|
именно тогда (правило «порог по нужде, а не по силе»). Тег и unlock_level группы 2 в
|
||||||
progression.xml обязаны совпадать. -->
|
progression.xml обязаны совпадать. -->
|
||||||
<append xpath="/recipes">
|
<append xpath="/recipes">
|
||||||
<!-- 22.10.2026 (#66): котёл кованый - в кузню («Он весь из себя кованный, так что будет логично»): железо вместо дерева
|
<!-- 22.10.2026 (#66): котёл кованый («Он весь из себя кованный, так что будет логично»): железо вместо дерева
|
||||||
и юкки, кости и прах - ноги и содержимое; две версии кузни (плавильня / лом). -->
|
и юкки, кости и прах - ноги и содержимое. 26.10: из кузни - на верстак, на ломе: в кузне делается только то, что
|
||||||
<recipe name="necroGeoOssuary" count="1" craft_area="forge" craft_time="60" material_based="true" tags="learnable,necroNecromancyJourneyman,use_smelter">
|
в неё вплавлено, а кости и прах в неё не кладутся (стрим 26.10). -->
|
||||||
<ingredient name="unit_iron" count="60"/>
|
<recipe name="necroGeoOssuary" count="1" craft_area="workbench" craft_time="60" tags="learnable,workbenchCrafting,necroNecromancyJourneyman">
|
||||||
<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="resourceScrapIron" count="60"/>
|
||||||
<ingredient name="resourceBone" count="400"/>
|
<ingredient name="resourceBone" count="400"/>
|
||||||
<ingredient name="resourceZombieAsh" count="200"/>
|
<ingredient name="resourceZombieAsh" count="200"/>
|
||||||
@@ -473,7 +468,7 @@
|
|||||||
гео-оссуария: транспорт нужен, когда карта уже велика для пеших ходок. Состав - по
|
гео-оссуария: транспорт нужен, когда карта уже велика для пеших ходок. Состав - по
|
||||||
указанию пользователя: кости, гнилое мясо, прах, кожа жертвы, два флакона крови. -->
|
указанию пользователя: кости, гнилое мясо, прах, кожа жертвы, два флакона крови. -->
|
||||||
<append xpath="/recipes">
|
<append xpath="/recipes">
|
||||||
<recipe name="necroBoneSteedPlaceable" count="1" craft_time="120" craft_area="workbench" tags="learnable,workbenchCrafting,necroNecromancyJourneyman">
|
<recipe name="necroBoneSteedPlaceable" count="1" craft_time="30" craft_area="workbench" tags="learnable,workbenchCrafting,necroNecromancyJourneyman">
|
||||||
<ingredient name="resourceBone" count="300"/>
|
<ingredient name="resourceBone" count="300"/>
|
||||||
<ingredient name="foodRottingFlesh" count="30"/>
|
<ingredient name="foodRottingFlesh" count="30"/>
|
||||||
<ingredient name="resourceZombieAsh" count="150"/>
|
<ingredient name="resourceZombieAsh" count="150"/>
|
||||||
@@ -526,7 +521,7 @@
|
|||||||
<ingredient name="resourceYuccaFibers" count="15"/>
|
<ingredient name="resourceYuccaFibers" count="15"/>
|
||||||
<ingredient name="resourceZombieAsh" count="20"/>
|
<ingredient name="resourceZombieAsh" count="20"/>
|
||||||
</recipe>
|
</recipe>
|
||||||
<recipe name="necroHeresyPyramid" count="1" craft_area="workbench" tags="workbenchCrafting,necroNecromancyAdept">
|
<recipe name="necroHeresyPyramid" count="1" craft_area="workbench" craft_time="191" tags="workbenchCrafting,necroNecromancyAdept">
|
||||||
<ingredient name="resourceZombieAsh" count="1200"/>
|
<ingredient name="resourceZombieAsh" count="1200"/>
|
||||||
<ingredient name="resourceRockSmall" count="300"/>
|
<ingredient name="resourceRockSmall" count="300"/>
|
||||||
<ingredient name="resourceYuccaFibers" count="200"/>
|
<ingredient name="resourceYuccaFibers" count="200"/>
|
||||||
@@ -560,21 +555,16 @@
|
|||||||
<ingredient name="medicalBloodBag" count="4"/>
|
<ingredient name="medicalBloodBag" count="4"/>
|
||||||
<ingredient name="resourceNecroBlackRoseJuice" count="1"/>
|
<ingredient name="resourceNecroBlackRoseJuice" count="1"/>
|
||||||
</recipe>
|
</recipe>
|
||||||
<!-- 22.10.2026 (#66): котёл кованый - в кузню («Он весь из себя кованный, так что будет логично»): железо вместо дерева
|
<!-- 22.10.2026 (#66): котёл кованый («Он весь из себя кованный, так что будет логично»): железо вместо дерева
|
||||||
и юкки, кости и прах - ноги и содержимое; две версии кузни (плавильня / лом). -->
|
и юкки, кости и прах - ноги и содержимое. 26.10: из кузни - на верстак, на ломе: в кузне делается только то, что
|
||||||
<recipe name="necroGeoOssuary" count="1" craft_area="forge" craft_time="60" material_based="true" tags="learnable,necroNecromancyJourneyman,use_smelter">
|
в неё вплавлено, а кости и прах в неё не кладутся (стрим 26.10). -->
|
||||||
<ingredient name="unit_iron" count="60"/>
|
<recipe name="necroGeoOssuary" count="1" craft_area="workbench" craft_time="60" tags="learnable,workbenchCrafting,necroNecromancyJourneyman">
|
||||||
<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="resourceScrapIron" count="60"/>
|
||||||
<ingredient name="resourceBone" count="400"/>
|
<ingredient name="resourceBone" count="400"/>
|
||||||
<ingredient name="resourceZombieAsh" count="200"/>
|
<ingredient name="resourceZombieAsh" count="200"/>
|
||||||
<ingredient name="resourceNecroBlackRoseJuice" count="1"/>
|
<ingredient name="resourceNecroBlackRoseJuice" count="1"/>
|
||||||
</recipe>
|
</recipe>
|
||||||
<recipe name="necroBoneSteedPlaceable" count="1" craft_time="120" craft_area="workbench" tags="learnable,workbenchCrafting,necroNecromancyJourneyman">
|
<recipe name="necroBoneSteedPlaceable" count="1" craft_time="30" craft_area="workbench" tags="learnable,workbenchCrafting,necroNecromancyJourneyman">
|
||||||
<ingredient name="resourceBone" count="300"/>
|
<ingredient name="resourceBone" count="300"/>
|
||||||
<ingredient name="foodRottingFlesh" count="30"/>
|
<ingredient name="foodRottingFlesh" count="30"/>
|
||||||
<ingredient name="resourceZombieAsh" count="150"/>
|
<ingredient name="resourceZombieAsh" count="150"/>
|
||||||
@@ -610,48 +600,31 @@
|
|||||||
</recipe>
|
</recipe>
|
||||||
<!-- Урна-логист (16.10: «Рецепт с набором для шитья») - набор для шитья; копия на соке чёрной розы.
|
<!-- Урна-логист (16.10: «Рецепт с набором для шитья») - набор для шитья; копия на соке чёрной розы.
|
||||||
18.10: своя модель - латунное кадило («Сейчас кадило выглядит бронзовым. Учти это в рецепте, используя латунь и кузню»):
|
18.10: своя модель - латунное кадило («Сейчас кадило выглядит бронзовым. Учти это в рецепте, используя латунь и кузню»):
|
||||||
вместо глины латунь, плюс немного песка и угля («добавь в рецепт немного песка и угля») и воск - покрытие, чтобы латунь не тускнела («Это кстати будет шикарным поводом ставить пасеку»), делается в кузне. Как у кузнечных рецептов игры - две версии: с плавильней (use_smelter, unit_brass)
|
вместо глины латунь, плюс немного песка и угля («добавь в рецепт немного песка и угля») и воск - покрытие, чтобы латунь не тускнела («Это кстати будет шикарным поводом ставить пасеку»), делается в кузне.
|
||||||
и без неё (replace_smelter, латунный лом). -->
|
26.10: на верстаке, на латунном ломе, без песка и угля - кузня не берёт кости и прах (стрим 26.10). -->
|
||||||
<recipe name="necroCinerariumLogist" count="1" craft_area="forge" craft_time="60" material_based="true" tags="learnable,necroNecromancyLvl300,use_smelter">
|
<recipe name="necroCinerariumLogist" count="1" craft_area="workbench" craft_time="60" tags="learnable,workbenchCrafting,necroNecromancyLvl300">
|
||||||
<ingredient name="unit_brass" count="50"/>
|
|
||||||
<ingredient name="resourceZombieAsh" count="100"/>
|
|
||||||
<ingredient name="resourceBone" count="10"/>
|
|
||||||
<ingredient name="resourceNecromancerBlood" count="1"/>
|
|
||||||
<ingredient name="resourceSewingKit" count="1"/>
|
|
||||||
<ingredient name="resourceCrushedSand" count="10"/>
|
|
||||||
<ingredient name="resourceCoal" count="5"/>
|
|
||||||
<ingredient name="resourceBeeswax" count="2"/>
|
|
||||||
</recipe>
|
|
||||||
<recipe name="necroCinerariumLogist" count="1" craft_area="forge" craft_time="60" material_based="true" tags="learnable,necroNecromancyLvl300,use_smelter">
|
|
||||||
<ingredient name="unit_brass" count="50"/>
|
|
||||||
<ingredient name="resourceZombieAsh" count="100"/>
|
|
||||||
<ingredient name="resourceBone" count="10"/>
|
|
||||||
<ingredient name="resourceNecroBlackRoseJuice" count="1"/>
|
|
||||||
<ingredient name="resourceSewingKit" count="1"/>
|
|
||||||
<ingredient name="resourceCrushedSand" count="10"/>
|
|
||||||
<ingredient name="resourceCoal" count="5"/>
|
|
||||||
<ingredient name="resourceBeeswax" count="2"/>
|
|
||||||
</recipe>
|
|
||||||
<recipe name="necroCinerariumLogist" count="1" craft_area="forge" craft_time="60" tags="learnable,necroNecromancyLvl300,replace_smelter">
|
|
||||||
<ingredient name="resourceScrapBrass" count="50"/>
|
<ingredient name="resourceScrapBrass" count="50"/>
|
||||||
<ingredient name="resourceZombieAsh" count="100"/>
|
<ingredient name="resourceZombieAsh" count="100"/>
|
||||||
<ingredient name="resourceBone" count="10"/>
|
<ingredient name="resourceBone" count="10"/>
|
||||||
<ingredient name="resourceNecromancerBlood" count="1"/>
|
<ingredient name="resourceNecromancerBlood" count="1"/>
|
||||||
<ingredient name="resourceSewingKit" count="1"/>
|
<ingredient name="resourceSewingKit" count="1"/>
|
||||||
<ingredient name="resourceCrushedSand" count="10"/>
|
|
||||||
<ingredient name="resourceCoal" count="5"/>
|
|
||||||
<ingredient name="resourceBeeswax" count="2"/>
|
<ingredient name="resourceBeeswax" count="2"/>
|
||||||
</recipe>
|
</recipe>
|
||||||
<recipe name="necroCinerariumLogist" count="1" craft_area="forge" craft_time="60" tags="learnable,necroNecromancyLvl300,replace_smelter">
|
<recipe name="necroCinerariumLogist" count="1" craft_area="workbench" craft_time="60" tags="learnable,workbenchCrafting,necroNecromancyLvl300">
|
||||||
<ingredient name="resourceScrapBrass" count="50"/>
|
<ingredient name="resourceScrapBrass" count="50"/>
|
||||||
<ingredient name="resourceZombieAsh" count="100"/>
|
<ingredient name="resourceZombieAsh" count="100"/>
|
||||||
<ingredient name="resourceBone" count="10"/>
|
<ingredient name="resourceBone" count="10"/>
|
||||||
<ingredient name="resourceNecroBlackRoseJuice" count="1"/>
|
<ingredient name="resourceNecroBlackRoseJuice" count="1"/>
|
||||||
<ingredient name="resourceSewingKit" count="1"/>
|
<ingredient name="resourceSewingKit" count="1"/>
|
||||||
<ingredient name="resourceCrushedSand" count="10"/>
|
|
||||||
<ingredient name="resourceCoal" count="5"/>
|
|
||||||
<ingredient name="resourceBeeswax" count="2"/>
|
<ingredient name="resourceBeeswax" count="2"/>
|
||||||
</recipe>
|
</recipe>
|
||||||
|
<!-- Резная кость (#86, 28.10.2026) - инструмент нитей: «Рецепт простой - одна кость и 10 железа» (кованое), «В рецепт
|
||||||
|
добавь щепотку праха». В рюкзаке, открывается вместе с урнами (progression.xml, та же строка). -->
|
||||||
|
<recipe name="necroCarvedBone" count="1" tags="learnable,packMuleCrafting,necroNecromancyLvl300">
|
||||||
|
<ingredient name="resourceBone" count="1"/>
|
||||||
|
<ingredient name="resourceForgedIron" count="10"/>
|
||||||
|
<ingredient name="resourceZombieAsh" count="5"/>
|
||||||
|
</recipe>
|
||||||
<!-- Урна рабочего (18.10: «Молоток годится») - рецепт урны и молоток; копия на соке чёрной розы. -->
|
<!-- Урна рабочего (18.10: «Молоток годится») - рецепт урны и молоток; копия на соке чёрной розы. -->
|
||||||
<recipe name="necroCinerariumWorker" count="1" tags="learnable,packMuleCrafting,necroNecromancyLvl300">
|
<recipe name="necroCinerariumWorker" count="1" tags="learnable,packMuleCrafting,necroNecromancyLvl300">
|
||||||
<ingredient name="resourceClayLump" count="30"/>
|
<ingredient name="resourceClayLump" count="30"/>
|
||||||
@@ -685,24 +658,8 @@
|
|||||||
<ingredient name="resourceScrapBrass" count="5"/>
|
<ingredient name="resourceScrapBrass" count="5"/>
|
||||||
</recipe>
|
</recipe>
|
||||||
<!-- Урна лутера (18.10) - железная банка с крышкой-кадилом, шов пропаян свинцом («в рецепте железо и немного свинца»):
|
<!-- Урна лутера (18.10) - железная банка с крышкой-кадилом, шов пропаян свинцом («в рецепте железо и немного свинца»):
|
||||||
в кузне, железо 40 и свинец 5 вместо глины, отмычка; две версии кузни (плавильня / лом), на крови и на соке. -->
|
железный и свинцовый лом вместо глины, отмычка; на крови и на соке. 26.10: из кузни - на верстак (кузня не берёт кости и прах). -->
|
||||||
<recipe name="necroCinerariumLoot" count="1" craft_area="forge" craft_time="60" material_based="true" tags="learnable,necroNecromancyLvl300,use_smelter">
|
<recipe name="necroCinerariumLoot" count="1" craft_area="workbench" craft_time="60" tags="learnable,workbenchCrafting,necroNecromancyLvl300">
|
||||||
<ingredient name="unit_iron" count="40"/>
|
|
||||||
<ingredient name="unit_lead" count="5"/>
|
|
||||||
<ingredient name="resourceZombieAsh" count="100"/>
|
|
||||||
<ingredient name="resourceBone" count="10"/>
|
|
||||||
<ingredient name="resourceNecromancerBlood" count="1"/>
|
|
||||||
<ingredient name="resourceLockPick" count="1"/>
|
|
||||||
</recipe>
|
|
||||||
<recipe name="necroCinerariumLoot" count="1" craft_area="forge" craft_time="60" material_based="true" tags="learnable,necroNecromancyLvl300,use_smelter">
|
|
||||||
<ingredient name="unit_iron" count="40"/>
|
|
||||||
<ingredient name="unit_lead" count="5"/>
|
|
||||||
<ingredient name="resourceZombieAsh" count="100"/>
|
|
||||||
<ingredient name="resourceBone" count="10"/>
|
|
||||||
<ingredient name="resourceNecroBlackRoseJuice" count="1"/>
|
|
||||||
<ingredient name="resourceLockPick" count="1"/>
|
|
||||||
</recipe>
|
|
||||||
<recipe name="necroCinerariumLoot" count="1" craft_area="forge" craft_time="60" tags="learnable,necroNecromancyLvl300,replace_smelter">
|
|
||||||
<ingredient name="resourceScrapIron" count="40"/>
|
<ingredient name="resourceScrapIron" count="40"/>
|
||||||
<ingredient name="resourceScrapLead" count="5"/>
|
<ingredient name="resourceScrapLead" count="5"/>
|
||||||
<ingredient name="resourceZombieAsh" count="100"/>
|
<ingredient name="resourceZombieAsh" count="100"/>
|
||||||
@@ -710,7 +667,7 @@
|
|||||||
<ingredient name="resourceNecromancerBlood" count="1"/>
|
<ingredient name="resourceNecromancerBlood" count="1"/>
|
||||||
<ingredient name="resourceLockPick" count="1"/>
|
<ingredient name="resourceLockPick" count="1"/>
|
||||||
</recipe>
|
</recipe>
|
||||||
<recipe name="necroCinerariumLoot" count="1" craft_area="forge" craft_time="60" tags="learnable,necroNecromancyLvl300,replace_smelter">
|
<recipe name="necroCinerariumLoot" count="1" craft_area="workbench" craft_time="60" tags="learnable,workbenchCrafting,necroNecromancyLvl300">
|
||||||
<ingredient name="resourceScrapIron" count="40"/>
|
<ingredient name="resourceScrapIron" count="40"/>
|
||||||
<ingredient name="resourceScrapLead" count="5"/>
|
<ingredient name="resourceScrapLead" count="5"/>
|
||||||
<ingredient name="resourceZombieAsh" count="100"/>
|
<ingredient name="resourceZombieAsh" count="100"/>
|
||||||
@@ -718,30 +675,16 @@
|
|||||||
<ingredient name="resourceNecroBlackRoseJuice" count="1"/>
|
<ingredient name="resourceNecroBlackRoseJuice" count="1"/>
|
||||||
<ingredient name="resourceLockPick" count="1"/>
|
<ingredient name="resourceLockPick" count="1"/>
|
||||||
</recipe>
|
</recipe>
|
||||||
<!-- Урна старателя (22.10, #64) - бронзовая катушка: в кузне латунь 40 вместо железа и свинца, железная кирка вместо отмычки;
|
<!-- Урна старателя (22.10, #64) - бронзовая катушка: латунный лом 40 вместо железа и свинца, железная кирка вместо отмычки;
|
||||||
две версии кузни (плавильня / лом), на крови и на соке. Открывается вместе с остальными урнами. -->
|
на крови и на соке. Открывается вместе с остальными урнами. 26.10: из кузни - на верстак (кузня не берёт кости и прах). -->
|
||||||
<recipe name="necroCinerariumProspector" count="1" craft_area="forge" craft_time="60" material_based="true" tags="learnable,necroNecromancyLvl300,use_smelter">
|
<recipe name="necroCinerariumProspector" count="1" craft_area="workbench" craft_time="60" tags="learnable,workbenchCrafting,necroNecromancyLvl300">
|
||||||
<ingredient name="unit_brass" count="40"/>
|
|
||||||
<ingredient name="resourceZombieAsh" count="100"/>
|
|
||||||
<ingredient name="resourceBone" count="10"/>
|
|
||||||
<ingredient name="resourceNecromancerBlood" count="1"/>
|
|
||||||
<ingredient name="meleeToolPickT1IronPickaxe" count="1"/>
|
|
||||||
</recipe>
|
|
||||||
<recipe name="necroCinerariumProspector" count="1" craft_area="forge" craft_time="60" material_based="true" tags="learnable,necroNecromancyLvl300,use_smelter">
|
|
||||||
<ingredient name="unit_brass" count="40"/>
|
|
||||||
<ingredient name="resourceZombieAsh" count="100"/>
|
|
||||||
<ingredient name="resourceBone" count="10"/>
|
|
||||||
<ingredient name="resourceNecroBlackRoseJuice" count="1"/>
|
|
||||||
<ingredient name="meleeToolPickT1IronPickaxe" count="1"/>
|
|
||||||
</recipe>
|
|
||||||
<recipe name="necroCinerariumProspector" count="1" craft_area="forge" craft_time="60" tags="learnable,necroNecromancyLvl300,replace_smelter">
|
|
||||||
<ingredient name="resourceScrapBrass" count="40"/>
|
<ingredient name="resourceScrapBrass" count="40"/>
|
||||||
<ingredient name="resourceZombieAsh" count="100"/>
|
<ingredient name="resourceZombieAsh" count="100"/>
|
||||||
<ingredient name="resourceBone" count="10"/>
|
<ingredient name="resourceBone" count="10"/>
|
||||||
<ingredient name="resourceNecromancerBlood" count="1"/>
|
<ingredient name="resourceNecromancerBlood" count="1"/>
|
||||||
<ingredient name="meleeToolPickT1IronPickaxe" count="1"/>
|
<ingredient name="meleeToolPickT1IronPickaxe" count="1"/>
|
||||||
</recipe>
|
</recipe>
|
||||||
<recipe name="necroCinerariumProspector" count="1" craft_area="forge" craft_time="60" tags="learnable,necroNecromancyLvl300,replace_smelter">
|
<recipe name="necroCinerariumProspector" count="1" craft_area="workbench" craft_time="60" tags="learnable,workbenchCrafting,necroNecromancyLvl300">
|
||||||
<ingredient name="resourceScrapBrass" count="40"/>
|
<ingredient name="resourceScrapBrass" count="40"/>
|
||||||
<ingredient name="resourceZombieAsh" count="100"/>
|
<ingredient name="resourceZombieAsh" count="100"/>
|
||||||
<ingredient name="resourceBone" count="10"/>
|
<ingredient name="resourceBone" count="10"/>
|
||||||
|
|||||||
@@ -75,14 +75,14 @@ namespace NecromancerTome
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Череп и нож (меч прилива) - модели предметов: в руке и на земле.</summary>
|
/// <summary>Череп, нож (меч прилива) и резная кость (#86) - модели предметов: в руке и на земле.</summary>
|
||||||
[HarmonyPatch(typeof(ItemClass), nameof(ItemClass.CloneModel))]
|
[HarmonyPatch(typeof(ItemClass), nameof(ItemClass.CloneModel))]
|
||||||
public static class Patch_ItemClass_CloneModel_BundleShaderFix
|
public static class Patch_ItemClass_CloneModel_BundleShaderFix
|
||||||
{
|
{
|
||||||
public static void Postfix(ItemClass __instance, AssetManagement.InstanceHandle<GameObject> __result)
|
public static void Postfix(ItemClass __instance, AssetManagement.InstanceHandle<GameObject> __result)
|
||||||
{
|
{
|
||||||
string n = __instance?.Name;
|
string n = __instance?.Name;
|
||||||
if (n != "necroSkull" && n != SnakeMode.KnifeName)
|
if (n != "necroSkull" && n != SnakeMode.KnifeName && n != CinerariumThreads.BoneName)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
+74
-11
@@ -264,8 +264,9 @@ namespace NecromancerTome
|
|||||||
AppendVia(via, l);
|
AppendVia(via, l);
|
||||||
}
|
}
|
||||||
CinerariumWriteHelper.WriteInts(_bw, via.ToArray());
|
CinerariumWriteHelper.WriteInts(_bw, via.ToArray());
|
||||||
// v6: ремонтник/лутер - сторона зоны, идёт ли проход, где курсор обхода, кто нажал кнопку.
|
// v6: ремонтник/лутер - сторона зоны, идёт ли проход, где курсор обхода, кто нажал кнопку; пятое (28.10, #84) -
|
||||||
CinerariumWriteHelper.WriteInts(_bw, new[] { ZoneSide, ZoneRunning ? 1 : 0, ZoneCursor, ZonePressedBy });
|
// выключенные квадраты зоны. Числа идут со счётчиком - старая DLL пятое просто пропустит, версия та же.
|
||||||
|
CinerariumWriteHelper.WriteInts(_bw, new[] { ZoneSide, ZoneRunning ? 1 : 0, ZoneCursor, ZonePressedBy, ZoneOff });
|
||||||
}
|
}
|
||||||
|
|
||||||
static void AppendVia(List<int> _dst, List<Vector3i> _l)
|
static void AppendVia(List<int> _dst, List<Vector3i> _l)
|
||||||
@@ -411,6 +412,7 @@ namespace NecromancerTome
|
|||||||
ZoneRunning = false;
|
ZoneRunning = false;
|
||||||
ZoneCursor = 0;
|
ZoneCursor = 0;
|
||||||
ZonePressedBy = -1;
|
ZonePressedBy = -1;
|
||||||
|
ZoneOff = 0;
|
||||||
if (version >= 6)
|
if (version >= 6)
|
||||||
{
|
{
|
||||||
int n = _br.ReadInt32();
|
int n = _br.ReadInt32();
|
||||||
@@ -423,6 +425,7 @@ namespace NecromancerTome
|
|||||||
ZoneRunning = n > 1 && v[1] != 0;
|
ZoneRunning = n > 1 && v[1] != 0;
|
||||||
ZoneCursor = n > 2 ? v[2] : 0;
|
ZoneCursor = n > 2 ? v[2] : 0;
|
||||||
ZonePressedBy = n > 3 ? v[3] : -1;
|
ZonePressedBy = n > 3 ? v[3] : -1;
|
||||||
|
ZoneOff = n > 4 ? v[4] : 0;
|
||||||
}
|
}
|
||||||
_threadsDirty = true;
|
_threadsDirty = true;
|
||||||
if (version > SaveVersion)
|
if (version > SaveVersion)
|
||||||
@@ -1099,19 +1102,11 @@ namespace NecromancerTome
|
|||||||
}
|
}
|
||||||
_bounds = new GameObject("NecroCinerariumBounds");
|
_bounds = new GameObject("NecroCinerariumBounds");
|
||||||
_bounds.transform.SetParent(t, false);
|
_bounds.transform.SetParent(t, false);
|
||||||
LineRenderer lr = _bounds.AddComponent<LineRenderer>();
|
|
||||||
lr.useWorldSpace = false;
|
|
||||||
lr.widthMultiplier = 0.06f;
|
|
||||||
if (_boundsMat == null)
|
if (_boundsMat == null)
|
||||||
{
|
{
|
||||||
_boundsMat = new Material(Shader.Find("Sprites/Default")) { name = "NecroCinerariumBounds" };
|
_boundsMat = new Material(Shader.Find("Sprites/Default")) { name = "NecroCinerariumBounds" };
|
||||||
}
|
}
|
||||||
lr.sharedMaterial = _boundsMat;
|
LineRenderer lr = BoundsLine(_bounds, BoundsOn);
|
||||||
Color col = new Color(0.55f, 0.9f, 0.75f, 0.85f);
|
|
||||||
lr.startColor = col;
|
|
||||||
lr.endColor = col;
|
|
||||||
lr.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
|
|
||||||
lr.receiveShadows = false;
|
|
||||||
|
|
||||||
// Углы по граням блоков: x и z от -2.5 до +2.5 от центра урны, y от низа блока под ней до верха +4. Большая зона
|
// Углы по граням блоков: x и z от -2.5 до +2.5 от центра урны, y от низа блока под ней до верха +4. Большая зона
|
||||||
// (ремонтник, лутер) - сторона ZoneSideNow, по высоте весь столб мира.
|
// (ремонтник, лутер) - сторона ZoneSideNow, по высоте весь столб мира.
|
||||||
@@ -1129,6 +1124,74 @@ namespace NecromancerTome
|
|||||||
{
|
{
|
||||||
lr.SetPosition(i, t.InverseTransformPoint(path[i]));
|
lr.SetPosition(i, t.InverseTransformPoint(path[i]));
|
||||||
}
|
}
|
||||||
|
if (IsBigZone)
|
||||||
|
{
|
||||||
|
SpawnSectorLines(t, c, x0, z0, y0, y1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static readonly Color BoundsOn = new Color(0.55f, 0.9f, 0.75f, 0.85f);
|
||||||
|
static readonly Color BoundsOff = new Color(0.95f, 0.3f, 0.25f, 0.85f);
|
||||||
|
|
||||||
|
/// <summary>Ломаная границ - дочерний объект _parent (уходит с ним в RemoveBounds), материал общий.</summary>
|
||||||
|
static LineRenderer BoundsLine(GameObject _parent, Color _col)
|
||||||
|
{
|
||||||
|
GameObject go = _parent;
|
||||||
|
if (go.GetComponent<LineRenderer>() != null)
|
||||||
|
{
|
||||||
|
go = new GameObject("NecroCinerariumBoundsLine");
|
||||||
|
go.transform.SetParent(_parent.transform, false);
|
||||||
|
}
|
||||||
|
LineRenderer lr = go.AddComponent<LineRenderer>();
|
||||||
|
lr.useWorldSpace = false;
|
||||||
|
lr.widthMultiplier = 0.06f;
|
||||||
|
lr.sharedMaterial = _boundsMat;
|
||||||
|
lr.startColor = _col;
|
||||||
|
lr.endColor = _col;
|
||||||
|
lr.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
|
||||||
|
lr.receiveShadows = false;
|
||||||
|
return lr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Квадраты большой зоны (#84): на уровне пола урны - рамка каждого квадрата (включён - зелёная, выключен - красная
|
||||||
|
/// с крестом), чуть отступив внутрь, чтобы соседние не сливались; на границах квадратов - вертикали на всю высоту зоны.</summary>
|
||||||
|
void SpawnSectorLines(Transform _t, Vector3 _c, float _x0, float _z0, float _y0, float _y1)
|
||||||
|
{
|
||||||
|
int side = ZoneSideNow;
|
||||||
|
float y = _c.y + 0.05f, inset = 0.2f;
|
||||||
|
float Edge(float _from, int _k) => _from + BandStart(_k, side);
|
||||||
|
for (int sz = 0; sz < ZoneGrid; sz++)
|
||||||
|
{
|
||||||
|
for (int sx = 0; sx < ZoneGrid; sx++)
|
||||||
|
{
|
||||||
|
bool on = SectorOn(sx + sz * ZoneGrid);
|
||||||
|
float ax = Edge(_x0, sx) + inset, bx = Edge(_x0, sx + 1) - inset;
|
||||||
|
float az = Edge(_z0, sz) + inset, bz = Edge(_z0, sz + 1) - inset;
|
||||||
|
Vector3 p0 = new Vector3(ax, y, az), p1 = new Vector3(bx, y, az), p2 = new Vector3(bx, y, bz), p3 = new Vector3(ax, y, bz);
|
||||||
|
Vector3[] path = on ? new[] { p0, p1, p2, p3, p0 } : new[] { p0, p1, p2, p3, p0, p2, p1, p3 };
|
||||||
|
LineRenderer lr = BoundsLine(_bounds, on ? BoundsOn : BoundsOff);
|
||||||
|
lr.positionCount = path.Length;
|
||||||
|
for (int i = 0; i < path.Length; i++)
|
||||||
|
{
|
||||||
|
lr.SetPosition(i, _t.InverseTransformPoint(path[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Вертикали - во всех узлах сетки, кроме углов зоны (их уже рисует рамка).
|
||||||
|
for (int kz = 0; kz <= ZoneGrid; kz++)
|
||||||
|
{
|
||||||
|
for (int kx = 0; kx <= ZoneGrid; kx++)
|
||||||
|
{
|
||||||
|
if ((kx == 0 || kx == ZoneGrid) && (kz == 0 || kz == ZoneGrid))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
LineRenderer lr = BoundsLine(_bounds, BoundsOn);
|
||||||
|
lr.positionCount = 2;
|
||||||
|
lr.SetPosition(0, _t.InverseTransformPoint(new Vector3(Edge(_x0, kx), _y0, Edge(_z0, kz))));
|
||||||
|
lr.SetPosition(1, _t.InverseTransformPoint(new Vector3(Edge(_x0, kx), _y1, Edge(_z0, kz))));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RemoveBounds()
|
public void RemoveBounds()
|
||||||
|
|||||||
+107
-14
@@ -119,6 +119,47 @@ namespace NecromancerTome
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 2а. Кузня: вход сырья держать полным (24.10, пользователь: «Места не должны пустовать. Нужно чтобы заполнены были
|
||||||
|
// обе ячейки»). Раньше сырьё подкладывалось, только когда переплавленного не хватало даже на одну штуку, - кузня
|
||||||
|
// плавила остатки, и дух ставил цемент по одному. Подкладка - ДО изготовления (после него очередь до неё не
|
||||||
|
// доходила: переплавленного каждый шаг хватало на одну штуку), но только в пустую или наполовину опустевшую ячейку -
|
||||||
|
// кузня плавит непрерывно, и иначе подкладка отнимала бы все шаги.
|
||||||
|
if (hasSample)
|
||||||
|
{
|
||||||
|
string feedName = sample.itemValue.ItemClass?.GetItemName();
|
||||||
|
foreach (Station st in stations)
|
||||||
|
{
|
||||||
|
if (!st.Te.isModuleUsed[MaterialModule] || !OutputHasRoom(st.Te, sample) && !outRoom)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
foreach (Recipe r in CraftingManager.GetRecipes(feedName))
|
||||||
|
{
|
||||||
|
if (r.IsScrap || !AreaFits(r, st.Areas) || owner != null && !r.IsUnlocked(owner))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
foreach (ItemStack ing in r.ingredients)
|
||||||
|
{
|
||||||
|
string iname = ing?.itemValue?.ItemClass?.GetItemName();
|
||||||
|
if (iname == null || UnitSlot(st.Te, iname) < 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (FeedMaterial(st, iname, src, _dry, out Vector3i? rawAt, true))
|
||||||
|
{
|
||||||
|
_from = rawAt;
|
||||||
|
if (!_dry)
|
||||||
|
{
|
||||||
|
Ignite(st.Te, src, false);
|
||||||
|
}
|
||||||
|
return st.Pos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (hasSample)
|
if (hasSample)
|
||||||
{
|
{
|
||||||
string name = sample.itemValue.ItemClass?.GetItemName();
|
string name = sample.itemValue.ItemClass?.GetItemName();
|
||||||
@@ -133,13 +174,17 @@ namespace NecromancerTome
|
|||||||
{
|
{
|
||||||
continue; // выход полон и ящики «куда» полны (или их нет) - ниже станция погаснет
|
continue; // выход полон и ящики «куда» полны (или их нет) - ниже станция погаснет
|
||||||
}
|
}
|
||||||
|
if (InQueue(st.Te, sample.itemValue.type))
|
||||||
|
{
|
||||||
|
continue; // партия этого предмета уже в очереди - следующая, когда она кончится
|
||||||
|
}
|
||||||
foreach (Recipe r in CraftingManager.GetRecipes(name))
|
foreach (Recipe r in CraftingManager.GetRecipes(name))
|
||||||
{
|
{
|
||||||
if (r.IsScrap || !AreaFits(r, st.Areas) || !ToolFits(r, st.Te) || owner != null && !r.IsUnlocked(owner))
|
if (r.IsScrap || !AreaFits(r, st.Areas) || !ToolFits(r, st.Te) || owner != null && !r.IsUnlocked(owner))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!Ingredients(r, st.Te, src, out Vector3i? srcAt, out string shortUnit))
|
if (!Ingredients(r, st.Te, src, out Vector3i? srcAt, out string shortUnit, out int batches))
|
||||||
{
|
{
|
||||||
// Кузня: не хватает переплавленного материала - положить сырьё во вход (плавит сама, с огнём).
|
// Кузня: не хватает переплавленного материала - положить сырьё во вход (плавит сама, с огнём).
|
||||||
if (shortUnit != null && FeedMaterial(st, shortUnit, src, _dry, out Vector3i? rawAt))
|
if (shortUnit != null && FeedMaterial(st, shortUnit, src, _dry, out Vector3i? rawAt))
|
||||||
@@ -158,10 +203,10 @@ namespace NecromancerTome
|
|||||||
{
|
{
|
||||||
return st.Pos;
|
return st.Pos;
|
||||||
}
|
}
|
||||||
TakeIngredients(r, st.Te, src);
|
TakeIngredients(r, st.Te, src, batches);
|
||||||
Enqueue(st.Te, slot, r);
|
Enqueue(st.Te, slot, r, batches);
|
||||||
Ignite(st.Te, src, false);
|
Ignite(st.Te, src, false);
|
||||||
Debug.Log("[NecromancerTome] Cinerarium: рабочий поставил " + name + " на станцию " + st.Pos);
|
Debug.Log("[NecromancerTome] Cinerarium: рабочий поставил " + name + " x" + batches + " на станцию " + st.Pos);
|
||||||
return st.Pos;
|
return st.Pos;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -289,6 +334,21 @@ namespace NecromancerTome
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Есть ли в очереди станции рецепт этого предмета. 24.10, пользователь: партия - до стака, и «если бы их было
|
||||||
|
/// двое, то тут нужно занимать очередь на ячейку»: один предмет держит в очереди одну ячейку, прочие - другим урнам на
|
||||||
|
/// той же станции (иначе одна урна забила бы очередь стаками на часы вперёд).</summary>
|
||||||
|
static bool InQueue(TileEntityWorkstation _te, int _itemType)
|
||||||
|
{
|
||||||
|
foreach (RecipeQueueItem q in _te.Queue)
|
||||||
|
{
|
||||||
|
if (q != null && q.Recipe != null && q.Multiplier > 0 && q.Recipe.itemValueType == _itemType)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
static bool OutputHasRoom(TileEntityWorkstation _te, ItemStack _sample)
|
static bool OutputHasRoom(TileEntityWorkstation _te, ItemStack _sample)
|
||||||
{
|
{
|
||||||
foreach (ItemStack s in _te.Output)
|
foreach (ItemStack s in _te.Output)
|
||||||
@@ -325,11 +385,17 @@ namespace NecromancerTome
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Хватает ли всего: переплавленное - в кузне, прочее - в ящиках «откуда». _srcAt - ящик, откуда первое;
|
/// <summary>Хватает ли всего: переплавленное - в кузне, прочее - в ящиках «откуда». _srcAt - ящик, откуда первое;
|
||||||
/// _shortUnit - какого переплавленного не хватило (кузне можно подложить сырья).</summary>
|
/// _shortUnit - какого переплавленного не хватило (кузне можно подложить сырья). _batches - сколько раз рецепт ставится
|
||||||
static bool Ingredients(Recipe _r, TileEntityWorkstation _te, List<(Vector3i p, TEFeatureStorage box)> _src, out Vector3i? _srcAt, out string _shortUnit)
|
/// в очередь за одно дело (24.10, пользователь: «в бетономешалку кладётся по одному камушку... ожидается, что он будет
|
||||||
|
/// класть хотя бы по целому стаку, а если на стак не хватает, то сколько найдёт»): на сколько хватает каждого
|
||||||
|
/// ингредиента, но не больше стака ни одного ингредиента и не больше стака готового.</summary>
|
||||||
|
static bool Ingredients(Recipe _r, TileEntityWorkstation _te, List<(Vector3i p, TEFeatureStorage box)> _src, out Vector3i? _srcAt, out string _shortUnit, out int _batches)
|
||||||
{
|
{
|
||||||
_srcAt = null;
|
_srcAt = null;
|
||||||
_shortUnit = null;
|
_shortUnit = null;
|
||||||
|
ItemClass outClass = ItemClass.GetForId(_r.itemValueType);
|
||||||
|
_batches = outClass != null && _r.count > 0 ? Mathf.Max(1, outClass.MaxCount / _r.count) : 1;
|
||||||
|
_batches = Mathf.Min(_batches, short.MaxValue);
|
||||||
foreach (ItemStack ing in _r.ingredients)
|
foreach (ItemStack ing in _r.ingredients)
|
||||||
{
|
{
|
||||||
if (ing == null || ing.IsEmpty() || ing.count <= 0)
|
if (ing == null || ing.IsEmpty() || ing.count <= 0)
|
||||||
@@ -345,6 +411,7 @@ namespace NecromancerTome
|
|||||||
_shortUnit = iname;
|
_shortUnit = iname;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
_batches = Mathf.Min(_batches, _te.Input[us].count / ing.count);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
int have = 0;
|
int have = 0;
|
||||||
@@ -367,11 +434,14 @@ namespace NecromancerTome
|
|||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
int stack = ing.itemValue.ItemClass != null ? ing.itemValue.ItemClass.MaxCount : ing.count;
|
||||||
|
_batches = Mathf.Min(_batches, Mathf.Max(1, stack / ing.count), have / ing.count);
|
||||||
}
|
}
|
||||||
|
_batches = Mathf.Max(1, _batches);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void TakeIngredients(Recipe _r, TileEntityWorkstation _te, List<(Vector3i p, TEFeatureStorage box)> _src)
|
static void TakeIngredients(Recipe _r, TileEntityWorkstation _te, List<(Vector3i p, TEFeatureStorage box)> _src, int _batches)
|
||||||
{
|
{
|
||||||
ItemStack[] input = _te.Input;
|
ItemStack[] input = _te.Input;
|
||||||
bool inputChanged = false;
|
bool inputChanged = false;
|
||||||
@@ -384,11 +454,11 @@ namespace NecromancerTome
|
|||||||
int us = UnitSlot(_te, ing.itemValue.ItemClass?.GetItemName() ?? "");
|
int us = UnitSlot(_te, ing.itemValue.ItemClass?.GetItemName() ?? "");
|
||||||
if (us >= 0)
|
if (us >= 0)
|
||||||
{
|
{
|
||||||
input[us].count -= ing.count;
|
input[us].count -= ing.count * _batches;
|
||||||
inputChanged = true;
|
inputChanged = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
int need = ing.count;
|
int need = ing.count * _batches;
|
||||||
foreach ((Vector3i p, TEFeatureStorage box) s in _src)
|
foreach ((Vector3i p, TEFeatureStorage box) s in _src)
|
||||||
{
|
{
|
||||||
ItemStack[] grid = s.box.ItemGrid.items;
|
ItemStack[] grid = s.box.ItemGrid.items;
|
||||||
@@ -413,7 +483,7 @@ namespace NecromancerTome
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void Enqueue(TileEntityWorkstation _te, int _slot, Recipe _r)
|
static void Enqueue(TileEntityWorkstation _te, int _slot, Recipe _r, int _batches)
|
||||||
{
|
{
|
||||||
RecipeQueueItem[] q = _te.Queue;
|
RecipeQueueItem[] q = _te.Queue;
|
||||||
float t = _r.craftingTime > 0f ? _r.craftingTime : 1f;
|
float t = _r.craftingTime > 0f ? _r.craftingTime : 1f;
|
||||||
@@ -421,7 +491,7 @@ namespace NecromancerTome
|
|||||||
q[_slot] = new RecipeQueueItem
|
q[_slot] = new RecipeQueueItem
|
||||||
{
|
{
|
||||||
Recipe = _r,
|
Recipe = _r,
|
||||||
Multiplier = 1,
|
Multiplier = (short)_batches,
|
||||||
CraftingTimeLeft = t,
|
CraftingTimeLeft = t,
|
||||||
OneItemCraftTime = t,
|
OneItemCraftTime = t,
|
||||||
IsCrafting = _slot == q.Length - 1,
|
IsCrafting = _slot == q.Length - 1,
|
||||||
@@ -448,22 +518,27 @@ namespace NecromancerTome
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Сырьё для переплавки в unit_X: предмет из ящика «откуда», сделанный из этого материала, - во вход кузни.</summary>
|
/// <summary>Сырьё для переплавки в unit_X: предмет из ящика «откуда», сделанный из этого материала, - во вход кузни.</summary>
|
||||||
static bool FeedMaterial(Station _st, string _unit, List<(Vector3i p, TEFeatureStorage box)> _src, bool _dry, out Vector3i? _at)
|
/// <remarks>_halfEmpty - только в пустую ячейку или заполненную меньше чем наполовину (подкладка впрок, шаг 2а).</remarks>
|
||||||
|
static bool FeedMaterial(Station _st, string _unit, List<(Vector3i p, TEFeatureStorage box)> _src, bool _dry, out Vector3i? _at, bool _halfEmpty = false)
|
||||||
{
|
{
|
||||||
_at = null;
|
_at = null;
|
||||||
string mat = _unit.Substring(5);
|
string mat = _unit.Substring(5);
|
||||||
ItemStack[] input = _st.Te.Input;
|
ItemStack[] input = _st.Te.Input;
|
||||||
// 23.10 («вместо камней вплавляет булыжник»): сначала сырьё, у которого нет рецепта (камни, лом), сделанное руками
|
// 23.10 («вместо камней вплавляет булыжник»): сначала сырьё, у которого нет рецепта (камни, лом), сделанное руками
|
||||||
// (булыжник) - только если сырья нет. Раньше - первое подходящее по порядку ячеек.
|
// (булыжник) - только если сырья нет. Раньше - первое подходящее по порядку ячеек.
|
||||||
|
// 24.10 («работник положил сланец в переплавку в печку»): горючее (нефтяной сланец - сырьё для бензина) не плавится
|
||||||
|
// вовсе. 24.10 («и снова засунул булыжник!!!»): плавится только сырьё - сделанное руками (булыжник) не плавится
|
||||||
|
// никогда. Прежняя проверка «нет рецепта» не отличала камни от булыжника: у камня тоже есть рецепты (разбор в лом,
|
||||||
|
// выплавка из переплавленного) - см. Handmade.
|
||||||
foreach ((Vector3i p, TEFeatureStorage box, int k) s in Ordered(_src, g =>
|
foreach ((Vector3i p, TEFeatureStorage box, int k) s in Ordered(_src, g =>
|
||||||
{
|
{
|
||||||
ItemClass c = g.itemValue.ItemClass;
|
ItemClass c = g.itemValue.ItemClass;
|
||||||
if (c?.MadeOfMaterial?.ForgeCategory == null || !c.MadeOfMaterial.ForgeCategory.EqualsCaseInsensitive(mat) ||
|
if (c?.MadeOfMaterial?.ForgeCategory == null || !c.MadeOfMaterial.ForgeCategory.EqualsCaseInsensitive(mat) ||
|
||||||
c.GetItemName().StartsWith("unit_"))
|
c.GetItemName().StartsWith("unit_") || ItemClass.GetFuelValue(g.itemValue) > 0 || Handmade(c))
|
||||||
{
|
{
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
return CraftingManager.GetRecipe(c.GetItemName()) == null ? 0 : 1;
|
return 0;
|
||||||
}))
|
}))
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
@@ -474,6 +549,10 @@ namespace NecromancerTome
|
|||||||
{
|
{
|
||||||
bool empty = input[i] == null || input[i].IsEmpty();
|
bool empty = input[i] == null || input[i].IsEmpty();
|
||||||
int room = empty ? ic.MaxCount : input[i].itemValue.type == g.itemValue.type ? ic.MaxCount - input[i].count : 0;
|
int room = empty ? ic.MaxCount : input[i].itemValue.type == g.itemValue.type ? ic.MaxCount - input[i].count : 0;
|
||||||
|
if (_halfEmpty && !empty && input[i].count * 2 > ic.MaxCount)
|
||||||
|
{
|
||||||
|
room = 0;
|
||||||
|
}
|
||||||
if (room <= 0)
|
if (room <= 0)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
@@ -502,6 +581,20 @@ namespace NecromancerTome
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Сделано руками: есть рецепт изготовления - не разбор (IsScrap, wildcard_forge_category) и не выплавка в кузне
|
||||||
|
/// из переплавленного (material_based). Камни и лом - сырьё (только такие рецепты), булыжник - сделан руками.</summary>
|
||||||
|
static bool Handmade(ItemClass _c)
|
||||||
|
{
|
||||||
|
foreach (Recipe r in CraftingManager.GetRecipes(_c.GetItemName()))
|
||||||
|
{
|
||||||
|
if (!r.IsScrap && !r.wildcardForgeCategory && !r.materialBasedRecipe)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
static Vector3i? FuelSource(List<(Vector3i p, TEFeatureStorage box)> _src)
|
static Vector3i? FuelSource(List<(Vector3i p, TEFeatureStorage box)> _src)
|
||||||
{
|
{
|
||||||
foreach ((Vector3i p, TEFeatureStorage box, int k) s in Ordered(_src, FuelRank))
|
foreach ((Vector3i p, TEFeatureStorage box, int k) s in Ordered(_src, FuelRank))
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.Scripting;
|
||||||
|
|
||||||
|
namespace NecromancerTome
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Чего не хватает ремонтнику (24.10.2026, пользователь: «сделать в его урне блок текста, который показывает какой материал
|
||||||
|
/// нужен в данный момент? А то не угадать», «Пусть показывает в виде ячейки с материалом», «И восклицательный знак. Чтобы
|
||||||
|
/// было понятно что именно это требуется»). Ячейка дописана в окно игры windowBagStorage (Config/XUi_InGame/windows.xml)
|
||||||
|
/// под блоком лимита урн: иконка материала, сколько доложить в ящики с нитями, красный «!». Только показ - не ячейка
|
||||||
|
/// сумки, взять из неё нечего. Видна у ремонтника, пока хоть один блок ждёт материал (TEFeatureCinerarium.LackType/LackCount) - и когда он чинит
|
||||||
|
/// остальное.
|
||||||
|
/// </summary>
|
||||||
|
[Preserve]
|
||||||
|
public class XUiC_NecroRepairNeed : XUiController
|
||||||
|
{
|
||||||
|
XUiV_Sprite _icon;
|
||||||
|
XUiV_Label _count;
|
||||||
|
float _nextRefresh;
|
||||||
|
|
||||||
|
public override void Init()
|
||||||
|
{
|
||||||
|
base.Init();
|
||||||
|
_icon = GetChildById("icon")?.ViewComponent as XUiV_Sprite;
|
||||||
|
_count = GetChildById("count")?.ViewComponent as XUiV_Label;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnOpen()
|
||||||
|
{
|
||||||
|
base.OnOpen();
|
||||||
|
_nextRefresh = 0f;
|
||||||
|
Refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Update(float _dt)
|
||||||
|
{
|
||||||
|
base.Update(_dt);
|
||||||
|
if (Time.time >= _nextRefresh)
|
||||||
|
{
|
||||||
|
Refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Refresh()
|
||||||
|
{
|
||||||
|
_nextRefresh = Time.time + 0.5f;
|
||||||
|
TEFeatureCinerarium urn = CinerariumContents.Urn;
|
||||||
|
ItemClass ic = urn != null && urn.IsRepairer && urn.LackType > 0 ? ItemClass.GetForId(urn.LackType) : null;
|
||||||
|
ViewComponent.IsVisible = ic != null;
|
||||||
|
if (ic == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (_icon != null)
|
||||||
|
{
|
||||||
|
_icon.SpriteName = ic.GetIconName();
|
||||||
|
_icon.Color = ic.GetIconTint();
|
||||||
|
}
|
||||||
|
if (_count != null)
|
||||||
|
{
|
||||||
|
_count.Text = urn.LackCount.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -34,6 +34,10 @@ namespace NecromancerTome
|
|||||||
{
|
{
|
||||||
public const string KnifeName = Patch_ItemClass_ExecuteAction_KnifePowerCost.KnifeName;
|
public const string KnifeName = Patch_ItemClass_ExecuteAction_KnifePowerCost.KnifeName;
|
||||||
public const string ThreadKnifeName = "necroWpnBladeNecroKnifeThread";
|
public const string ThreadKnifeName = "necroWpnBladeNecroKnifeThread";
|
||||||
|
/// <summary>Резная кость (#86, 28.10.2026): отдельный инструмент нитей - делает то же, что нож хватом для нитей, хват
|
||||||
|
/// всегда для нитей, меню хватов по R у неё нет. Для тех, кто не нашёл хват у ножа (пользователь: «чтобы игроки, которые
|
||||||
|
/// не обнаружили нитяной хват у ножа, легко могли найти отдельный инструмент управления цинерариями»).</summary>
|
||||||
|
public const string BoneName = "necroCarvedBone";
|
||||||
/// <summary>Как у провода электрики: ItemActionConnectPower.maxWireLength = 15.</summary>
|
/// <summary>Как у провода электрики: ItemActionConnectPower.maxWireLength = 15.</summary>
|
||||||
public const float MaxLength = 15f;
|
public const float MaxLength = 15f;
|
||||||
public const int MaxFrom = 8;
|
public const int MaxFrom = 8;
|
||||||
@@ -51,6 +55,9 @@ namespace NecromancerTome
|
|||||||
return n == SkullDecorPlace.CenterBlock || n == SkullDecorPlace.BackBlock || n == "necroSkullDecor";
|
return n == SkullDecorPlace.CenterBlock || n == SkullDecorPlace.BackBlock || n == "necroSkullDecor";
|
||||||
}
|
}
|
||||||
public static bool IsThreadKnife(ItemClass _i) => _i != null && _i.Name == ThreadKnifeName;
|
public static bool IsThreadKnife(ItemClass _i) => _i != null && _i.Name == ThreadKnifeName;
|
||||||
|
/// <summary>Инструмент нитей в руке: нож хватом для нитей или резная кость.</summary>
|
||||||
|
public static bool IsThreadTool(ItemClass _i) => _i != null && IsThreadToolName(_i.Name);
|
||||||
|
public static bool IsThreadToolName(string _n) => _n == ThreadKnifeName || _n == BoneName;
|
||||||
|
|
||||||
// ------------------------------------------------------------------
|
// ------------------------------------------------------------------
|
||||||
// Хват.
|
// Хват.
|
||||||
@@ -439,7 +446,7 @@ namespace NecromancerTome
|
|||||||
public static bool ToolInHand()
|
public static bool ToolInHand()
|
||||||
{
|
{
|
||||||
EntityPlayerLocal pl = GameManager.Instance?.World?.GetPrimaryPlayer();
|
EntityPlayerLocal pl = GameManager.Instance?.World?.GetPrimaryPlayer();
|
||||||
return pl != null && CinerariumThreads.IsThreadKnife(pl.inventory?.holdingItem);
|
return pl != null && CinerariumThreads.IsThreadTool(pl.inventory?.holdingItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Update()
|
void Update()
|
||||||
@@ -492,7 +499,7 @@ namespace NecromancerTome
|
|||||||
void Update()
|
void Update()
|
||||||
{
|
{
|
||||||
EntityPlayerLocal pl = GameManager.Instance?.World?.GetPrimaryPlayer();
|
EntityPlayerLocal pl = GameManager.Instance?.World?.GetPrimaryPlayer();
|
||||||
if (pl == null || !CinerariumThreads.Start.HasValue || !CinerariumThreads.IsThreadKnife(pl.inventory?.holdingItem))
|
if (pl == null || !CinerariumThreads.Start.HasValue || !CinerariumThreads.IsThreadTool(pl.inventory?.holdingItem))
|
||||||
{
|
{
|
||||||
CinerariumThreads.Cancel();
|
CinerariumThreads.Cancel();
|
||||||
return;
|
return;
|
||||||
@@ -517,13 +524,13 @@ namespace NecromancerTome
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Нож для нитей не бьёт: ЛКМ и ПКМ - нити (на отпускание кнопки).</summary>
|
/// <summary>Нож для нитей и резная кость не бьют: ЛКМ и ПКМ - нити (на отпускание кнопки).</summary>
|
||||||
[HarmonyPatch(typeof(ItemClass), nameof(ItemClass.ExecuteAction))]
|
[HarmonyPatch(typeof(ItemClass), nameof(ItemClass.ExecuteAction))]
|
||||||
public static class Patch_ItemClass_ExecuteAction_ThreadKnife
|
public static class Patch_ItemClass_ExecuteAction_ThreadKnife
|
||||||
{
|
{
|
||||||
public static bool Prefix(ItemClass __instance, int _actionIdx, ItemInventoryData _data, bool _bReleased)
|
public static bool Prefix(ItemClass __instance, int _actionIdx, ItemInventoryData _data, bool _bReleased)
|
||||||
{
|
{
|
||||||
if (!CinerariumThreads.IsThreadKnife(__instance))
|
if (!CinerariumThreads.IsThreadTool(__instance))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System.Collections;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace NecromancerTome
|
namespace NecromancerTome
|
||||||
@@ -42,7 +43,7 @@ namespace NecromancerTome
|
|||||||
_bag.SetSlot(1, _u.Sample.Clone());
|
_bag.SetSlot(1, _u.Sample.Clone());
|
||||||
}
|
}
|
||||||
XUiC_BagStorageWindowGroup.Open(ui.xui, _p, _bag, LootContainer.GetLootContainer(LootTemplate),
|
XUiC_BagStorageWindowGroup.Open(ui.xui, _p, _bag, LootContainer.GetLootContainer(LootTemplate),
|
||||||
Localization.Get("necroCinerariumTrunk"), Check, () =>
|
Localization.Get("necroCinerariumTrunk"), CheckLater, () =>
|
||||||
{
|
{
|
||||||
Check();
|
Check();
|
||||||
_urn = null;
|
_urn = null;
|
||||||
@@ -52,6 +53,28 @@ namespace NecromancerTome
|
|||||||
Debug.Log("[NecromancerTome] Cinerarium: «Содержимое» урны " + _u.ToWorldPos() + " открыто, череп " + (_u.HasSkull ? "есть" : "нет"));
|
Debug.Log("[NecromancerTome] Cinerarium: «Содержимое» урны " + _u.ToWorldPos() + " открыто, череп " + (_u.HasSkull ? "есть" : "нет"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool _checkQueued;
|
||||||
|
|
||||||
|
/// <summary>Изменение в ячейках - проверка в следующем кадре. Игра зовёт onModified посреди своей перекладки: при Shift+клике
|
||||||
|
/// стопки черепов из рюкзака она сначала кладёт стопку в урну (тут срабатывал Check и возвращал лишние черепа в ту же ячейку
|
||||||
|
/// рюкзака), а потом пишет в исходную ячейку пустоту - и вернувшиеся черепа пропадали (стрим 26.10: «черепа исчезли из сумки»).</summary>
|
||||||
|
static void CheckLater()
|
||||||
|
{
|
||||||
|
if (_checkQueued)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_checkQueued = true;
|
||||||
|
GameManager.Instance.StartCoroutine(CheckNextFrame());
|
||||||
|
}
|
||||||
|
|
||||||
|
static IEnumerator CheckNextFrame()
|
||||||
|
{
|
||||||
|
yield return null;
|
||||||
|
_checkQueued = false;
|
||||||
|
Check();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Оставить в ячейке один череп, остальное - игроку; отметить в урне. У рабочего - запомнить образец; череп во
|
/// <summary>Оставить в ячейке один череп, остальное - игроку; отметить в урне. У рабочего - запомнить образец; череп во
|
||||||
/// второй ячейке - не образец, переложить в первую (или вернуть).</summary>
|
/// второй ячейке - не образец, переложить в первую (или вернуть).</summary>
|
||||||
static void Check()
|
static void Check()
|
||||||
|
|||||||
+182
-19
@@ -8,7 +8,8 @@ namespace NecromancerTome
|
|||||||
/// Урны с большой зоной - ремонтник и лутер (решения 17.10 и 18.10, BACKLOG «Урна ремонтника», «Урна лутера»).
|
/// Урны с большой зоной - ремонтник и лутер (решения 17.10 и 18.10, BACKLOG «Урна ремонтника», «Урна лутера»).
|
||||||
///
|
///
|
||||||
/// Общее. Зона - квадрат со стороной блока притязания (LandClaimSize сервера, по умолчанию 41) или меньше (31/21/11, пункт
|
/// Общее. Зона - квадрат со стороной блока притязания (LandClaimSize сервера, по умолчанию 41) или меньше (31/21/11, пункт
|
||||||
/// меню «Размер зоны»), по высоте - весь столб мира; зоны таких урн могут пересекаться. Работа - только по кнопке (17.10: «А
|
/// меню «Размер зоны»), по высоте - весь столб мира; делится на 3x3 квадрата, любой можно выключить (#84, ZoneOff); зоны
|
||||||
|
/// таких урн могут пересекаться. Работа - только по кнопке (17.10: «А
|
||||||
/// если сделать рубильник или кнопку. Включил, ремонтник начал работу»): «Запустить» - один проход по зоне, конец прохода -
|
/// если сделать рубильник или кнопку. Включил, ремонтник начал работу»): «Запустить» - один проход по зоне, конец прохода -
|
||||||
/// снова ожидание; до нажатия урна ничего не считает. Кнопки - на самой урне с четырёх сторон («как у дверного звонка, но с
|
/// снова ожидание; до нажатия урна ничего не считает. Кнопки - на самой урне с четырёх сторон («как у дверного звонка, но с
|
||||||
/// черепом»), их цвет - состояние (CinerariumButtons): не горят - нет черепа; жёлтая ровно - ожидание; жёлтая мигает - не
|
/// черепом»), их цвет - состояние (CinerariumButtons): не горят - нет черепа; жёлтая ровно - ожидание; жёлтая мигает - не
|
||||||
@@ -75,6 +76,9 @@ namespace NecromancerTome
|
|||||||
public int ZonePressedBy = -1;
|
public int ZonePressedBy = -1;
|
||||||
/// <summary>Не хватает материалов / ящик полон - кнопка мигает жёлтым.</summary>
|
/// <summary>Не хватает материалов / ящик полон - кнопка мигает жёлтым.</summary>
|
||||||
public bool ZoneLacking;
|
public bool ZoneLacking;
|
||||||
|
/// <summary>Ремонтник: какого материала и сколько не хватило последней проверке (ячейка в «Содержимом»,
|
||||||
|
/// XUiC_NecroRepairNeed). Не сохраняется.</summary>
|
||||||
|
public int LackType, LackCount;
|
||||||
|
|
||||||
public static float RepairSeconds = 2.5f;
|
public static float RepairSeconds = 2.5f;
|
||||||
public static float LootSeconds = 1.5f;
|
public static float LootSeconds = 1.5f;
|
||||||
@@ -86,6 +90,10 @@ namespace NecromancerTome
|
|||||||
static readonly HashSet<Vector3i> Claimed = new HashSet<Vector3i>();
|
static readonly HashSet<Vector3i> Claimed = new HashSet<Vector3i>();
|
||||||
|
|
||||||
readonly List<Vector3i> _queue = new List<Vector3i>();
|
readonly List<Vector3i> _queue = new List<Vector3i>();
|
||||||
|
/// <summary>Ремонтник: блоки, на которые нет материалов (24.10, пользователь: «Если не может починить что-то для чего
|
||||||
|
/// нет ресурсов, пусть сначала чинит то, для чего они есть»). Не держат очередь обхода; к ним - когда больше нечего.</summary>
|
||||||
|
readonly List<Vector3i> _lacking = new List<Vector3i>();
|
||||||
|
const int LackingMax = 256;
|
||||||
float _zoneNext;
|
float _zoneNext;
|
||||||
bool _zonePending;
|
bool _zonePending;
|
||||||
float _zoneActAt;
|
float _zoneActAt;
|
||||||
@@ -116,6 +124,88 @@ namespace NecromancerTome
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Квадраты зоны (28.10, #84, пользователь: «разделить уже имеющуюся зону на 9 квадратов, с цинерарием посередине...
|
||||||
|
/// по умолчанию все 9 были выбраны, но можно было какой-нибудь отключить. Иначе он начинает ремонтировать близстоящие
|
||||||
|
/// здания»): сетка ZoneGrid x ZoneGrid, урна в среднем квадрате. Номер квадрата - столбец + строка * ZoneGrid, столбец 0 -
|
||||||
|
/// запад (меньший x), строка 0 - юг (меньший z). Бит в ZoneOff - квадрат выключен; 0 - все включены. Сохраняется (v6,
|
||||||
|
/// пятое число). Выключать - в «Содержимом» (XUiC_NecroUrnZone) или «necrourn x y z sectors».</summary>
|
||||||
|
public int ZoneOff;
|
||||||
|
public const int ZoneGrid = 3;
|
||||||
|
public const int ZoneSectors = ZoneGrid * ZoneGrid;
|
||||||
|
|
||||||
|
public bool SectorOn(int _s) => (ZoneOff & (1 << _s)) == 0;
|
||||||
|
|
||||||
|
/// <summary>Полоса сетки клетки _i (0..side-1, от западного/южного края зоны).</summary>
|
||||||
|
static int Band(int _i, int _side) => _i * ZoneGrid / _side;
|
||||||
|
|
||||||
|
/// <summary>Первая клетка полосы _k от края зоны; _k = ZoneGrid - сторона зоны.</summary>
|
||||||
|
public static int BandStart(int _k, int _side) => (_k * _side + ZoneGrid - 1) / ZoneGrid;
|
||||||
|
|
||||||
|
/// <summary>Работает ли урна в столбе мира (x, z): в зоне и квадрат включён.</summary>
|
||||||
|
public bool InZone(int _x, int _z)
|
||||||
|
{
|
||||||
|
int side = ZoneSideNow, half = side / 2;
|
||||||
|
Vector3i c = ToWorldPos();
|
||||||
|
int ix = _x - c.x + half, iz = _z - c.z + half;
|
||||||
|
if (ix < 0 || iz < 0 || ix >= side || iz >= side)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return SectorOn(Band(ix, side) + Band(iz, side) * ZoneGrid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Включить/выключить квадрат. Блоки выключенного квадрата, уже взятые в очередь, отпускаются; линии перерисовать.</summary>
|
||||||
|
public void SetZoneOff(int _mask)
|
||||||
|
{
|
||||||
|
ZoneOff = _mask & ((1 << ZoneSectors) - 1);
|
||||||
|
DropOutside(_queue);
|
||||||
|
DropOutside(_lacking);
|
||||||
|
if (_zonePending && IsRepairer && !InZone(_zoneTarget.x, _zoneTarget.z))
|
||||||
|
{
|
||||||
|
_zonePending = false;
|
||||||
|
Claimed.Remove(_zoneTarget);
|
||||||
|
}
|
||||||
|
if (ZoneShown)
|
||||||
|
{
|
||||||
|
RemoveBounds();
|
||||||
|
SpawnBounds();
|
||||||
|
}
|
||||||
|
SetModified();
|
||||||
|
Debug.Log("[NecromancerTome] Cinerarium: урна " + ToWorldPos() + " - квадраты зоны " + SectorsText());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ToggleSector(int _s) => SetZoneOff(ZoneOff ^ (1 << _s));
|
||||||
|
|
||||||
|
/// <summary>Квадраты строками с севера: «+» включён, «-» выключен (для лога и консоли).</summary>
|
||||||
|
public string SectorsText()
|
||||||
|
{
|
||||||
|
System.Text.StringBuilder sb = new System.Text.StringBuilder();
|
||||||
|
for (int r = ZoneGrid - 1; r >= 0; r--)
|
||||||
|
{
|
||||||
|
for (int col = 0; col < ZoneGrid; col++)
|
||||||
|
{
|
||||||
|
sb.Append(SectorOn(col + r * ZoneGrid) ? '+' : '-');
|
||||||
|
}
|
||||||
|
if (r > 0)
|
||||||
|
{
|
||||||
|
sb.Append('/');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DropOutside(List<Vector3i> _l)
|
||||||
|
{
|
||||||
|
for (int i = _l.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
if (!InZone(_l[i].x, _l[i].z))
|
||||||
|
{
|
||||||
|
Claimed.Remove(_l[i]);
|
||||||
|
_l.RemoveAt(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public enum ButtonState
|
public enum ButtonState
|
||||||
{
|
{
|
||||||
Off,
|
Off,
|
||||||
@@ -172,7 +262,7 @@ namespace NecromancerTome
|
|||||||
ZoneCursor = 0;
|
ZoneCursor = 0;
|
||||||
ZoneLacking = false;
|
ZoneLacking = false;
|
||||||
ZonePressedBy = _player != null ? _player.entityId : -1;
|
ZonePressedBy = _player != null ? _player.entityId : -1;
|
||||||
_queue.Clear();
|
ReleaseClaims();
|
||||||
_lootDone.Clear();
|
_lootDone.Clear();
|
||||||
Audio.Manager.BroadcastPlay(World.blockToTransformPos(ToWorldPos()), "button4");
|
Audio.Manager.BroadcastPlay(World.blockToTransformPos(ToWorldPos()), "button4");
|
||||||
Debug.Log("[NecromancerTome] Cinerarium: " + (IsLooter ? "лутер" : "ремонтник") + " " + ToWorldPos() + " запущен, зона " + ZoneSideNow);
|
Debug.Log("[NecromancerTome] Cinerarium: " + (IsLooter ? "лутер" : "ремонтник") + " " + ToWorldPos() + " запущен, зона " + ZoneSideNow);
|
||||||
@@ -203,11 +293,7 @@ namespace NecromancerTome
|
|||||||
/// <summary>Остановить проход (кнопка, урна убрана): очередь и взятые блоки отпускаются.</summary>
|
/// <summary>Остановить проход (кнопка, урна убрана): очередь и взятые блоки отпускаются.</summary>
|
||||||
public void ZoneStop(bool _log)
|
public void ZoneStop(bool _log)
|
||||||
{
|
{
|
||||||
foreach (Vector3i p in _queue)
|
ReleaseClaims();
|
||||||
{
|
|
||||||
Claimed.Remove(p);
|
|
||||||
}
|
|
||||||
_queue.Clear();
|
|
||||||
_zonePending = false;
|
_zonePending = false;
|
||||||
if (ZoneRunning && _log)
|
if (ZoneRunning && _log)
|
||||||
{
|
{
|
||||||
@@ -217,6 +303,23 @@ namespace NecromancerTome
|
|||||||
ZoneLacking = false;
|
ZoneLacking = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Очередь и отложенные блоки - отпустить (Claimed) и забыть.</summary>
|
||||||
|
void ReleaseClaims()
|
||||||
|
{
|
||||||
|
foreach (Vector3i p in _queue)
|
||||||
|
{
|
||||||
|
Claimed.Remove(p);
|
||||||
|
}
|
||||||
|
foreach (Vector3i p in _lacking)
|
||||||
|
{
|
||||||
|
Claimed.Remove(p);
|
||||||
|
}
|
||||||
|
_queue.Clear();
|
||||||
|
_lacking.Clear();
|
||||||
|
LackType = 0;
|
||||||
|
LackCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
void ZoneWork(World _world)
|
void ZoneWork(World _world)
|
||||||
{
|
{
|
||||||
SyncButtons();
|
SyncButtons();
|
||||||
@@ -297,6 +400,10 @@ namespace NecromancerTome
|
|||||||
{
|
{
|
||||||
int x = c.x - half + ZoneCursor % side, z = c.z - half + ZoneCursor / side;
|
int x = c.x - half + ZoneCursor % side, z = c.z - half + ZoneCursor / side;
|
||||||
ZoneCursor++;
|
ZoneCursor++;
|
||||||
|
if (ZoneOff != 0 && !InZone(x, z))
|
||||||
|
{
|
||||||
|
continue; // квадрат выключен (#84)
|
||||||
|
}
|
||||||
if (!(_world.GetChunkFromWorldPos(x, 0, z) is Chunk ch))
|
if (!(_world.GetChunkFromWorldPos(x, 0, z) is Chunk ch))
|
||||||
{
|
{
|
||||||
continue; // не загружен - пропустить столб
|
continue; // не загружен - пропустить столб
|
||||||
@@ -323,7 +430,7 @@ namespace NecromancerTome
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (ZoneCursor >= total && _queue.Count == 0 && !_zonePending)
|
if (ZoneCursor >= total && _queue.Count == 0 && _lacking.Count == 0 && !_zonePending)
|
||||||
{
|
{
|
||||||
Debug.Log("[NecromancerTome] Cinerarium: ремонтник " + c + " обошёл зону - ожидание");
|
Debug.Log("[NecromancerTome] Cinerarium: ремонтник " + c + " обошёл зону - ожидание");
|
||||||
ZoneStop(false);
|
ZoneStop(false);
|
||||||
@@ -331,32 +438,78 @@ namespace NecromancerTome
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Следующий блок: сначала из очереди обхода, на что есть материалы (на что нет - в _lacking), потом из _lacking,
|
||||||
|
/// если материал появился. Нечего чинить, кроме ждущих материала, - кнопка жёлтая. Ячейка «не хватает» в «Содержимом» -
|
||||||
|
/// первый блок из _lacking (LackType/LackCount), пока он есть.</summary>
|
||||||
void RepairPick(World _world, CinerariumGenieMotion _motion)
|
void RepairPick(World _world, CinerariumGenieMotion _motion)
|
||||||
{
|
{
|
||||||
while (_queue.Count > 0)
|
while (_queue.Count > 0)
|
||||||
{
|
{
|
||||||
Vector3i p = _queue[0];
|
Vector3i p = _queue[0];
|
||||||
|
_queue.RemoveAt(0);
|
||||||
BlockValue bv = _world.GetBlock(p);
|
BlockValue bv = _world.GetBlock(p);
|
||||||
if (bv.isair || bv.damage <= 0 || bv.Block?.RepairItems == null)
|
if (bv.isair || bv.damage <= 0 || bv.Block?.RepairItems == null)
|
||||||
{
|
{
|
||||||
_queue.RemoveAt(0);
|
|
||||||
Claimed.Remove(p);
|
Claimed.Remove(p);
|
||||||
continue; // починили до нас или блок убран
|
continue; // починили до нас или блок убран
|
||||||
}
|
}
|
||||||
if (!HasMaterials(_world, bv, false))
|
if (!HasMaterials(_world, bv, false))
|
||||||
{
|
{
|
||||||
if (!ZoneLacking)
|
if (_lacking.Count < LackingMax)
|
||||||
{
|
{
|
||||||
Debug.Log("[NecromancerTome] Cinerarium: ремонтнику " + ToWorldPos() + " не хватает материалов для " + bv.Block.GetBlockName() + " в " + p);
|
_lacking.Add(p);
|
||||||
}
|
}
|
||||||
ZoneLacking = true;
|
else
|
||||||
return; // ждём: положат в ящик - продолжим с того же блока
|
{
|
||||||
|
Claimed.Remove(p); // найдётся при следующем обходе
|
||||||
}
|
}
|
||||||
ZoneLacking = false;
|
continue;
|
||||||
_zoneTarget = p;
|
}
|
||||||
FlyThenAct(_world, _motion, p);
|
Go(_world, _motion, p);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
int lackType = 0, lackCount = 0;
|
||||||
|
for (int i = 0; i < _lacking.Count; i++)
|
||||||
|
{
|
||||||
|
Vector3i p = _lacking[i];
|
||||||
|
BlockValue bv = _world.GetBlock(p);
|
||||||
|
if (bv.isair || bv.damage <= 0 || bv.Block?.RepairItems == null)
|
||||||
|
{
|
||||||
|
_lacking.RemoveAt(i--);
|
||||||
|
Claimed.Remove(p);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (HasMaterials(_world, bv, false))
|
||||||
|
{
|
||||||
|
_lacking.RemoveAt(i);
|
||||||
|
Go(_world, _motion, p);
|
||||||
|
LackType = lackType;
|
||||||
|
LackCount = lackCount;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (lackType == 0)
|
||||||
|
{
|
||||||
|
lackType = LackType;
|
||||||
|
lackCount = LackCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LackType = lackType;
|
||||||
|
LackCount = lackCount;
|
||||||
|
bool stuck = _lacking.Count > 0;
|
||||||
|
if (stuck && !ZoneLacking)
|
||||||
|
{
|
||||||
|
ItemClass ic = ItemClass.GetForId(LackType);
|
||||||
|
Debug.Log("[NecromancerTome] Cinerarium: ремонтнику " + ToWorldPos() + " нечего чинить без материалов: ждут " + _lacking.Count +
|
||||||
|
" блок(ов), первым не хватает " + LackCount + " " + ic?.GetItemName());
|
||||||
|
}
|
||||||
|
ZoneLacking = stuck;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Go(World _world, CinerariumGenieMotion _motion, Vector3i _p)
|
||||||
|
{
|
||||||
|
ZoneLacking = false;
|
||||||
|
_zoneTarget = _p;
|
||||||
|
FlyThenAct(_world, _motion, _p);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RepairNow(World _world, Vector3i _p)
|
void RepairNow(World _world, Vector3i _p)
|
||||||
@@ -370,8 +523,7 @@ namespace NecromancerTome
|
|||||||
}
|
}
|
||||||
if (!HasMaterials(_world, bv, true))
|
if (!HasMaterials(_world, bv, true))
|
||||||
{
|
{
|
||||||
ZoneLacking = true;
|
_lacking.Add(_p); // материал забрали, пока дух летел
|
||||||
_queue.Insert(0, _p);
|
|
||||||
Claimed.Add(_p);
|
Claimed.Add(_p);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -403,6 +555,8 @@ namespace NecromancerTome
|
|||||||
}
|
}
|
||||||
if (have < n)
|
if (have < n)
|
||||||
{
|
{
|
||||||
|
LackType = iv.type;
|
||||||
|
LackCount = n - have;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -518,7 +672,16 @@ namespace NecromancerTome
|
|||||||
foreach (Entity e in _ents)
|
foreach (Entity e in _ents)
|
||||||
{
|
{
|
||||||
int rank = LootRank(e);
|
int rank = LootRank(e);
|
||||||
if (rank < bestRank && !_lootDone.Contains(e.entityId) && !LockManager.Instance.IsLockedServer(e, 0))
|
if (rank >= bestRank)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Vector3i ep = World.worldToBlockPos(e.GetPosition());
|
||||||
|
if (ZoneOff != 0 && !InZone(ep.x, ep.z))
|
||||||
|
{
|
||||||
|
continue; // квадрат выключен (#84)
|
||||||
|
}
|
||||||
|
if (!_lootDone.Contains(e.entityId) && !LockManager.Instance.IsLockedServer(e, 0))
|
||||||
{
|
{
|
||||||
best = e;
|
best = e;
|
||||||
bestRank = rank;
|
bestRank = rank;
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.Scripting;
|
||||||
|
|
||||||
|
namespace NecromancerTome
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Квадраты зоны ремонтника и лутера (28.10.2026, #84): сетка 3x3 в окне «Содержимое» - окно necroUrnZone в группе окон
|
||||||
|
/// игры bagStorage (Config/XUi_InGame), как фильтр логиста. Север сверху, в среднем квадрате - урна. Щелчок включает /
|
||||||
|
/// выключает квадрат (TEFeatureCinerarium.ToggleSector); выключенный - тёмный с красной рамкой. Видна только у урн с
|
||||||
|
/// большой зоной; у прочих урн и сумок транспорта и волка скрыта. s0..s8 в XML - номера квадратов (столбец + строка * 3,
|
||||||
|
/// строка 0 - юг), f0..f8 - их рамки.
|
||||||
|
/// </summary>
|
||||||
|
[Preserve]
|
||||||
|
public class XUiC_NecroUrnZone : XUiController
|
||||||
|
{
|
||||||
|
readonly XUiController[] _cells = new XUiController[TEFeatureCinerarium.ZoneSectors];
|
||||||
|
readonly XUiController[] _frames = new XUiController[TEFeatureCinerarium.ZoneSectors];
|
||||||
|
static readonly Color On = new Color(0.3f, 0.6f, 0.45f, 0.9f);
|
||||||
|
static readonly Color Off = new Color(0.12f, 0.12f, 0.12f, 0.9f);
|
||||||
|
static readonly Color FrameOn = new Color(0.55f, 0.9f, 0.75f, 1f);
|
||||||
|
static readonly Color FrameOff = new Color(0.95f, 0.3f, 0.25f, 1f);
|
||||||
|
|
||||||
|
public override void Init()
|
||||||
|
{
|
||||||
|
base.Init();
|
||||||
|
for (int i = 0; i < _cells.Length; i++)
|
||||||
|
{
|
||||||
|
int s = i;
|
||||||
|
_cells[i] = GetChildById("s" + i);
|
||||||
|
_frames[i] = GetChildById("f" + i);
|
||||||
|
if (_cells[i] != null)
|
||||||
|
{
|
||||||
|
_cells[i].OnPress += (_x, _m) => Toggle(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnOpen()
|
||||||
|
{
|
||||||
|
base.OnOpen();
|
||||||
|
Refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Toggle(int _s)
|
||||||
|
{
|
||||||
|
TEFeatureCinerarium urn = CinerariumContents.Urn;
|
||||||
|
if (urn == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
urn.ToggleSector(_s);
|
||||||
|
Refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Refresh()
|
||||||
|
{
|
||||||
|
TEFeatureCinerarium urn = CinerariumContents.Urn;
|
||||||
|
bool show = urn != null && urn.IsBigZone;
|
||||||
|
ViewComponent.IsVisible = show;
|
||||||
|
if (!show)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < _cells.Length; i++)
|
||||||
|
{
|
||||||
|
bool on = urn.SectorOn(i);
|
||||||
|
if (_cells[i]?.ViewComponent is XUiV_Sprite sp)
|
||||||
|
{
|
||||||
|
sp.Color = on ? On : Off;
|
||||||
|
sp.ToolTip = Localization.Get(on ? "necroZoneSectorOn" : "necroZoneSectorOff");
|
||||||
|
}
|
||||||
|
if (_frames[i]?.ViewComponent is XUiV_Sprite fr)
|
||||||
|
{
|
||||||
|
fr.Color = on ? FrameOn : FrameOff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+73
-1
@@ -68,6 +68,9 @@ namespace NecromancerTome
|
|||||||
|
|
||||||
static GameObject _rig;
|
static GameObject _rig;
|
||||||
static SDCSUtils.TransformCatalog _bones;
|
static SDCSUtils.TransformCatalog _bones;
|
||||||
|
/// <summary>Модель оружия с пояса в правой руке (#37) и чья она (тип предмета; -1 - кулаки).</summary>
|
||||||
|
static AssetManagement.InstanceHandle<GameObject> _weapon;
|
||||||
|
static int _weaponType = -1;
|
||||||
static bool _layerWasShown;
|
static bool _layerWasShown;
|
||||||
|
|
||||||
public static bool Active => _rig != null;
|
public static bool Active => _rig != null;
|
||||||
@@ -279,6 +282,65 @@ namespace NecromancerTome
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Оружие с пояса в руках (#37): модель предмета на кость RightWeapon, как у персонажа от первого лица
|
||||||
|
/// (ItemInventoryData.SeatModel - без сдвига), и хват WeaponHoldType этого предмета; null - кулаки (хват 0).
|
||||||
|
/// Ставится каждый кадр, пересобирается только при смене предмета.</summary>
|
||||||
|
public static void SetWeapon(ItemValue _iv)
|
||||||
|
{
|
||||||
|
int type = _iv != null && !_iv.IsEmpty() ? _iv.type : -1;
|
||||||
|
if (_rig == null || type == _weaponType)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
DestroyWeapon();
|
||||||
|
_weaponType = type;
|
||||||
|
Animator a = Anim;
|
||||||
|
ItemClass ic = type >= 0 ? _iv.ItemClass : null;
|
||||||
|
Transform hand = _rig.transform.FindInChilds("RightWeapon");
|
||||||
|
if (ic != null && hand != null)
|
||||||
|
{
|
||||||
|
_weapon = ic.CloneModel(_iv, hand, BlockShape.MeshPurpose.Hold);
|
||||||
|
GameObject go = _weapon.Instance;
|
||||||
|
if (go != null)
|
||||||
|
{
|
||||||
|
go.transform.SetLocalPositionAndRotation(Vector3.zero, Quaternion.identity);
|
||||||
|
if (!ic.GetCorrectionScale().Equals(Vector3.zero))
|
||||||
|
{
|
||||||
|
go.transform.localScale = ic.GetCorrectionScale();
|
||||||
|
}
|
||||||
|
int layer = LayerMask.NameToLayer("HoldingItem");
|
||||||
|
foreach (Transform t in go.GetComponentsInChildren<Transform>(true))
|
||||||
|
{
|
||||||
|
t.gameObject.layer = layer;
|
||||||
|
}
|
||||||
|
foreach (Collider c in go.GetComponentsInChildren<Collider>(true))
|
||||||
|
{
|
||||||
|
c.enabled = false;
|
||||||
|
}
|
||||||
|
go.SetActive(!ic.HoldingItemHidden);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (a != null)
|
||||||
|
{
|
||||||
|
SetParam(a, "WeaponHoldType", ic != null ? ic.HoldType.Value : 0f);
|
||||||
|
SetParam(a, "ItemHasChangedTrigger", 1f);
|
||||||
|
}
|
||||||
|
Debug.Log("[NecromancerTome] FpArms: в руке " + (ic != null ? ic.Name + " (хват " + ic.HoldType.Value + (_weapon.Instance != null ? "" : ", БЕЗ модели") + ")" : "ничего - кулаки") +
|
||||||
|
(hand == null && ic != null ? " - кость RightWeapon не найдена" : ""));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void DestroyWeapon()
|
||||||
|
{
|
||||||
|
if (_weapon.Instance != null)
|
||||||
|
{
|
||||||
|
_weapon.Instance.transform.SetParent(null, false);
|
||||||
|
_weapon.Instance.SetActive(false);
|
||||||
|
_weapon.Destroy();
|
||||||
|
}
|
||||||
|
_weapon = default(AssetManagement.InstanceHandle<GameObject>);
|
||||||
|
_weaponType = -1;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Анимация удара рук вошла в состояние удара (AnimatorMeleeAttackState у наших рук): через RaycastTime -
|
/// <summary>Анимация удара рук вошла в состояние удара (AnimatorMeleeAttackState у наших рук): через RaycastTime -
|
||||||
/// миг удара, как у персонажа (impactStart). Руки на это время уходят в нижнее положение (под прицел).</summary>
|
/// миг удара, как у персонажа (impactStart). Руки на это время уходят в нижнее положение (под прицел).</summary>
|
||||||
public static void OnSwingEnter(float _raycastTime)
|
public static void OnSwingEnter(float _raycastTime)
|
||||||
@@ -421,6 +483,7 @@ namespace NecromancerTome
|
|||||||
{
|
{
|
||||||
_player?.ShowHoldingItemLayer(false);
|
_player?.ShowHoldingItemLayer(false);
|
||||||
}
|
}
|
||||||
|
DestroyWeapon();
|
||||||
SDCSUtils.UnloadViz(_rig);
|
SDCSUtils.UnloadViz(_rig);
|
||||||
_rig = null;
|
_rig = null;
|
||||||
_bones = null;
|
_bones = null;
|
||||||
@@ -437,7 +500,7 @@ namespace NecromancerTome
|
|||||||
public class ConsoleCmdNecroArms : ConsoleCmdAbstract
|
public class ConsoleCmdNecroArms : ConsoleCmdAbstract
|
||||||
{
|
{
|
||||||
public override string[] getCommands() => new[] { "necroarms" };
|
public override string[] getCommands() => new[] { "necroarms" };
|
||||||
public override string getDescription() => "NecromancerTome: руки одержимого от первого лица - [pos x y z | parent cam|ufps | anim | mat | set <param> <value> | trigger <name> | tint r g b | albedo on|off | punch <trigger> | fov <deg>]";
|
public override string getDescription() => "NecromancerTome: руки одержимого от первого лица - [pos x y z | parent cam|ufps | anim | mat | set <param> <value> | trigger <name> | tint r g b | albedo on|off | punch <trigger> | fov <deg> | weapon [on|off]]";
|
||||||
|
|
||||||
void Out(string _s)
|
void Out(string _s)
|
||||||
{
|
{
|
||||||
@@ -477,6 +540,15 @@ namespace NecromancerTome
|
|||||||
{
|
{
|
||||||
FpArms.ArmsFov = fov;
|
FpArms.ArmsFov = fov;
|
||||||
}
|
}
|
||||||
|
else if (cmd == "weapon")
|
||||||
|
{
|
||||||
|
if (_params.Count >= 2)
|
||||||
|
{
|
||||||
|
ZombieWeapons.Enabled = _params[1] != "off";
|
||||||
|
}
|
||||||
|
Out(Possession.Active && Possession.Zombie != null ? ZombieWeapons.Dump(Possession.Zombie) : "оружие " + (ZombieWeapons.Enabled ? "вкл" : "ВЫКЛ") + " (не в одержимости)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
else if (cmd == "anim")
|
else if (cmd == "anim")
|
||||||
{
|
{
|
||||||
Out(FpArms.DumpAnim());
|
Out(FpArms.DumpAnim());
|
||||||
|
|||||||
@@ -29,10 +29,10 @@ namespace NecromancerTome
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static class GameVersionGuard
|
public static class GameVersionGuard
|
||||||
{
|
{
|
||||||
/// <summary>Под какую версию собран мод: Major и десяток Minor (3.3.x = 3 и 3; сборка 1.4.x была под 3.2).</summary>
|
/// <summary>Под какую версию собран мод: Major и десяток Minor (3.4.x = 3 и 4; 1.5-1.6 были под 3.3, 1.4.x - под 3.2).</summary>
|
||||||
public const int SupportedMajor = 3;
|
public const int SupportedMajor = 3;
|
||||||
public const int SupportedMinorTens = 3;
|
public const int SupportedMinorTens = 4;
|
||||||
public const string SupportedText = "3.3";
|
public const string SupportedText = "3.4";
|
||||||
|
|
||||||
private static bool? supported;
|
private static bool? supported;
|
||||||
|
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ namespace NecromancerTome
|
|||||||
{
|
{
|
||||||
// Только от первого лица (16.10: «В виде от третьего лица что-то страшное»): поза и перенос ножа - для рук первого
|
// Только от первого лица (16.10: «В виде от третьего лица что-то страшное»): поза и перенос ножа - для рук первого
|
||||||
// лица; от третьего лица модель в руке - у тела, и нож улетал к невидимой руке первого лица.
|
// лица; от третьего лица модель в руке - у тела, и нож улетал к невидимой руке первого лица.
|
||||||
if (_player != null && _player.inventory?.holdingItem?.Name == CinerariumThreads.ThreadKnifeName &&
|
if (_player != null && CinerariumThreads.IsThreadToolName(_player.inventory?.holdingItem?.Name) &&
|
||||||
(_player.emodel as EModelSDCS)?.IsFPV == true && !SnakeMode.Active && !MindPalace.Active && !Possession.Active && !_player.IsDead())
|
(_player.emodel as EModelSDCS)?.IsFPV == true && !SnakeMode.Active && !MindPalace.Active && !Possession.Active && !_player.IsDead())
|
||||||
{
|
{
|
||||||
if (_threadSince < 0f)
|
if (_threadSince < 0f)
|
||||||
|
|||||||
@@ -17,8 +17,9 @@ namespace NecromancerTome
|
|||||||
/// necroslot [N] - какая ячейка пояса в руке и что в ячейках; N - взять в руку ячейку N (23.10: проверка Archer's Eye без мыши)
|
/// necroslot [N] - какая ячейка пояса в руке и что в ячейках; N - взять в руку ячейку N (23.10: проверка Archer's Eye без мыши)
|
||||||
/// necrofire press|release - ЛКМ предметом в руке: нажать / отпустить (лук: натянуть / выстрелить; 23.10, ошибка в Hit от стрелы)
|
/// necrofire press|release - ЛКМ предметом в руке: нажать / отпустить (лук: натянуть / выстрелить; 23.10, ошибка в Hit от стрелы)
|
||||||
/// necrobox x y z list|clear|add предмет число - содержимое хранилища (ящик, сумка урны)
|
/// necrobox x y z list|clear|add предмет число - содержимое хранилища (ящик, сумка урны)
|
||||||
/// necrourn x y z info|skull 0|1|from|to|out bx by bz|clear|sample предмет число|group все|нет - урна
|
/// necrourn x y z info|skull 0|1|from|to|out bx by bz|clear|sample предмет число|group все|нет|sectors all|N|-маска - урна
|
||||||
/// necroclick 0|1 - отпущенная ЛКМ/ПКМ ножом для нитей по блоку под прицелом
|
/// necroclick 0|1 - отпущенная ЛКМ/ПКМ ножом для нитей по блоку под прицелом
|
||||||
|
/// necropossess near [класс]|fp|hit|look id / x y z|slot N|belt N предмет [качество]|info|exit - одержимость без мыши (#37, 25.10)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class NecroDev
|
public static class NecroDev
|
||||||
{
|
{
|
||||||
@@ -265,6 +266,7 @@ namespace NecromancerTome
|
|||||||
Out("necrocanplace: " + (TEFeatureCinerarium.PlacementRefusal(P(_params, 1), Block.GetBlockByName(_params[0], true)) ?? "можно"));
|
Out("necrocanplace: " + (TEFeatureCinerarium.PlacementRefusal(P(_params, 1), Block.GetBlockByName(_params[0], true)) ?? "можно"));
|
||||||
break;
|
break;
|
||||||
case "necrogod": God(pl, _params.Count == 0 || _params[0] != "off"); break;
|
case "necrogod": God(pl, _params.Count == 0 || _params[0] != "off"); break;
|
||||||
|
case "necropossess": Possess(w, pl, _params); break;
|
||||||
case "necroclick":
|
case "necroclick":
|
||||||
// necroclick 0|1 [x y z] - по блоку под прицелом или по указанной клетке (луч не всегда попадает в низкий череп).
|
// necroclick 0|1 [x y z] - по блоку под прицелом или по указанной клетке (луч не всегда попадает в низкий череп).
|
||||||
if (_params.Count >= 4)
|
if (_params.Count >= 4)
|
||||||
@@ -310,6 +312,64 @@ namespace NecromancerTome
|
|||||||
|
|
||||||
/// <summary>Режим бога и невидимость для ИИ: пока ассистент проверяет, персонаж стоит - зомби тестового мира его не трогают
|
/// <summary>Режим бога и невидимость для ИИ: пока ассистент проверяет, персонаж стоит - зомби тестового мира его не трогают
|
||||||
/// (18.10: «пока персонаж просто стоит, его скушают»; «Я обычно ставлю режим Бога»).</summary>
|
/// (18.10: «пока персонаж просто стоит, его скушают»; «Я обычно ставлю режим Бога»).</summary>
|
||||||
|
/// <summary>necropossess: вселиться в ближайшего зомби (класс - начало имени), вид от первого лица, ЛКМ, ячейка и
|
||||||
|
/// предмет пояса, состояние, выход.</summary>
|
||||||
|
static void Possess(World _w, EntityPlayerLocal _pl, List<string> _p)
|
||||||
|
{
|
||||||
|
string cmd = _p.Count > 0 ? _p[0] : "info";
|
||||||
|
EntityAlive z = Possession.Zombie;
|
||||||
|
switch (cmd)
|
||||||
|
{
|
||||||
|
case "near":
|
||||||
|
{
|
||||||
|
string cls = _p.Count > 1 ? _p[1] : "";
|
||||||
|
EntityAlive best = null;
|
||||||
|
float bestD = 40f * 40f;
|
||||||
|
foreach (EntityAlive e in _w.EntityAlives)
|
||||||
|
{
|
||||||
|
if (!Possession.CanPossess(e) || !EntityClass.list.TryGetValue(e.entityClass, out EntityClass ec) ||
|
||||||
|
!ec.entityClassName.StartsWith(cls, System.StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
float d = (e.position - _pl.position).sqrMagnitude;
|
||||||
|
if (d < bestD)
|
||||||
|
{
|
||||||
|
bestD = d;
|
||||||
|
best = e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Out("necropossess: " + (best == null ? "зомби рядом нет" : best.EntityName + " (" + best.entityId + ") - " + (Possession.Enter(_pl, best) ? "вселился" : "не вышло")));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case "fp": Possession.DevView = true; break;
|
||||||
|
case "hit": Possession.DevAttack = true; break;
|
||||||
|
case "look":
|
||||||
|
{
|
||||||
|
// look id - на существо (грудь), look x y z - на точку
|
||||||
|
Vector3 at = _p.Count >= 4 ? new Vector3(F(_p[1]), F(_p[2]), F(_p[3]))
|
||||||
|
: (_w.GetEntity(int.Parse(_p[1])) as EntityAlive)?.getChestPosition() ?? Vector3.zero;
|
||||||
|
Possession.DevLookAt(at);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "slot": Possession.BeltSlot = Mathf.Clamp(int.Parse(_p[1]) - 1, 0, Possession.BeltSlots - 1); break;
|
||||||
|
case "belt":
|
||||||
|
{
|
||||||
|
ItemValue iv = ItemClass.GetItem(_p[2]);
|
||||||
|
if (iv == null || iv.IsEmpty())
|
||||||
|
{
|
||||||
|
Out("necropossess: нет предмета " + _p[2]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
iv = new ItemValue(iv.type, _p.Count > 3 ? int.Parse(_p[3]) : 1, _p.Count > 3 ? int.Parse(_p[3]) : 1);
|
||||||
|
Out("necropossess: " + (ZombieBag.DevSetBelt(int.Parse(_p[1]) - 1, new ItemStack(iv, 1)) ? "положено" : "сумки нет"));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "exit": Possession.Exit("necropossess"); Out("necropossess: выход"); return;
|
||||||
|
}
|
||||||
|
Out("necropossess: " + (z == null ? "не в одержимости" : z.EntityName + ", от первого лица " + Possession.FirstPerson + "\n" + ZombieWeapons.Dump(z)));
|
||||||
|
}
|
||||||
|
|
||||||
static void God(EntityPlayerLocal _pl, bool _on)
|
static void God(EntityPlayerLocal _pl, bool _on)
|
||||||
{
|
{
|
||||||
_pl.IsGodMode.Value = _on;
|
_pl.IsGodMode.Value = _on;
|
||||||
@@ -492,6 +552,16 @@ namespace NecromancerTome
|
|||||||
case "run": u.ZoneCommand("zurn_run", GameManager.Instance.World.GetPrimaryPlayer()); break;
|
case "run": u.ZoneCommand("zurn_run", GameManager.Instance.World.GetPrimaryPlayer()); break;
|
||||||
case "stop": u.ZoneCommand("zurn_stop", null); break;
|
case "stop": u.ZoneCommand("zurn_stop", null); break;
|
||||||
case "size": u.ZoneSide = int.Parse(_p[4]); u.SetModified(); break;
|
case "size": u.ZoneSide = int.Parse(_p[4]); u.SetModified(); break;
|
||||||
|
// necrourn x y z sectors all|N|-N - квадраты зоны (#84): all - все включить; N - переключить квадрат N (0..8, столбец +
|
||||||
|
// строка * 3, 0 - юго-запад); -N - маска выключенных числом.
|
||||||
|
case "sectors":
|
||||||
|
if (_p.Count > 4)
|
||||||
|
{
|
||||||
|
if (_p[4] == "all" || _p[4] == "все") u.SetZoneOff(0);
|
||||||
|
else if (_p[4].StartsWith("-")) u.SetZoneOff(int.Parse(_p[4].Substring(1)));
|
||||||
|
else u.ToggleSector(int.Parse(_p[4]));
|
||||||
|
}
|
||||||
|
break;
|
||||||
case "group":
|
case "group":
|
||||||
u.GroupMask = _p[4] == "нет" || _p[4] == "none" ? 0u : CinerariumGroups.All;
|
u.GroupMask = _p[4] == "нет" || _p[4] == "none" ? 0u : CinerariumGroups.All;
|
||||||
u.SetModified();
|
u.SetModified();
|
||||||
@@ -507,7 +577,7 @@ namespace NecromancerTome
|
|||||||
}
|
}
|
||||||
Out("necrourn: " + pos + " " + _w.GetBlock(pos).Block?.GetBlockName() + ", череп " + u.HasSkull + ", застрял " + u.Stuck + ", мигание " + blink +
|
Out("necrourn: " + pos + " " + _w.GetBlock(pos).Block?.GetBlockName() + ", череп " + u.HasSkull + ", застрял " + u.Stuck + ", мигание " + blink +
|
||||||
", откуда " + u.ThreadFrom.Count + ", куда " + (u.HasThreadTo ? u.ThreadTo.ToString() : "нет") + ", от урны " + u.ThreadOut.Count +
|
", откуда " + u.ThreadFrom.Count + ", куда " + (u.HasThreadTo ? u.ThreadTo.ToString() : "нет") + ", от урны " + u.ThreadOut.Count +
|
||||||
", зона " + (u.IsBigZone ? u.ZoneSideNow + (u.ZoneRunning ? " идёт" : " ждёт") + (u.ZoneLacking ? " нехватка" : "") + " курсор " + u.ZoneCursor : "-") + ", образец " + (u.Sample == null || u.Sample.IsEmpty() ? "нет" : u.Sample.itemValue.ItemClass?.GetItemName() + "x" + u.Sample.count) + "; " + u.WorkState());
|
", зона " + (u.IsBigZone ? u.ZoneSideNow + (u.ZoneRunning ? " идёт" : " ждёт") + (u.ZoneLacking ? " нехватка" : "") + " курсор " + u.ZoneCursor + " квадраты " + u.SectorsText() : "-") + ", образец " + (u.Sample == null || u.Sample.IsEmpty() ? "нет" : u.Sample.itemValue.ItemClass?.GetItemName() + "x" + u.Sample.count) + "; " + u.WorkState());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -592,6 +662,15 @@ namespace NecromancerTome
|
|||||||
public override void Execute(List<string> _params, CommandSenderInfo _senderInfo) => NecroDev.Run("necroclick", _params);
|
public override void Execute(List<string> _params, CommandSenderInfo _senderInfo) => NecroDev.Run("necroclick", _params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Preserve]
|
||||||
|
public class ConsoleCmdNecroPossess : ConsoleCmdAbstract
|
||||||
|
{
|
||||||
|
public override string[] getCommands() => new[] { "necropossess" };
|
||||||
|
public override string getDescription() => "NecromancerTome (разработка): одержимость без мыши - near [класс] | fp | hit | slot N | belt N предмет [качество] | info | exit";
|
||||||
|
public override bool AllowedInMainMenu => false;
|
||||||
|
public override void Execute(List<string> _params, CommandSenderInfo _senderInfo) => NecroDev.Run("necropossess", _params);
|
||||||
|
}
|
||||||
|
|
||||||
[Preserve]
|
[Preserve]
|
||||||
public class ConsoleCmdNecroGod : ConsoleCmdAbstract
|
public class ConsoleCmdNecroGod : ConsoleCmdAbstract
|
||||||
{
|
{
|
||||||
|
|||||||
+107
-13
@@ -55,6 +55,9 @@ namespace NecromancerTome
|
|||||||
public static EntityAlive Zombie;
|
public static EntityAlive Zombie;
|
||||||
/// <summary>Вселились в своего питомца (книга призыва), а не в чужого зомби.</summary>
|
/// <summary>Вселились в своего питомца (книга призыва), а не в чужого зомби.</summary>
|
||||||
public static bool IsPet;
|
public static bool IsPet;
|
||||||
|
/// <summary>Разведка (туман карты вокруг одержимого, карта открывается на нём): стервятник (08.10) и любой одержимый
|
||||||
|
/// зомби (#36, 25.10); прочие питомцы - нет.</summary>
|
||||||
|
public static bool Scouts => Active && (Flying || !IsPet);
|
||||||
/// <summary>Питомец-стервятник: летает (FlyTick из его updateTasks).</summary>
|
/// <summary>Питомец-стервятник: летает (FlyTick из его updateTasks).</summary>
|
||||||
public static bool Flying;
|
public static bool Flying;
|
||||||
/// <summary>Враг у тела (CheckBody) - для надписи в PossessionHud.</summary>
|
/// <summary>Враг у тела (CheckBody) - для надписи в PossessionHud.</summary>
|
||||||
@@ -153,7 +156,13 @@ namespace NecromancerTome
|
|||||||
public static bool FirstPerson;
|
public static bool FirstPerson;
|
||||||
/// <summary>Пояс одержимого (13.10): 2 ячейки, пока пустые; выбор 1/2 или колесом, только от первого лица.</summary>
|
/// <summary>Пояс одержимого (13.10): 2 ячейки, пока пустые; выбор 1/2 или колесом, только от первого лица.</summary>
|
||||||
public const int BeltSlots = 2;
|
public const int BeltSlots = 2;
|
||||||
|
/// <summary>Чаще руки не успевают доиграть замах (ножи в игре - до 100+ ударов в минуту).</summary>
|
||||||
|
const float MinWeaponSwing = 0.45f;
|
||||||
public static int BeltSlot;
|
public static int BeltSlot;
|
||||||
|
/// <summary>Нажатия от necropossess (проверка без мыши): ЛКМ и смена вида - срабатывают в ближайшем кадре.</summary>
|
||||||
|
public static bool DevAttack, DevView;
|
||||||
|
/// <summary>Ячейка и тип предмета в ней при прошлой проверке - подсказка о неумелом оружии один раз на смену.</summary>
|
||||||
|
static int _beltKey = -1;
|
||||||
public const float FpForward = 0.12f, FpSmooth = 12f;
|
public const float FpForward = 0.12f, FpSmooth = 12f;
|
||||||
static Vector3 _fpEye;
|
static Vector3 _fpEye;
|
||||||
static readonly List<Renderer> _hiddenRenderers = new List<Renderer>();
|
static readonly List<Renderer> _hiddenRenderers = new List<Renderer>();
|
||||||
@@ -218,6 +227,18 @@ namespace NecromancerTome
|
|||||||
/// <summary>Игрок, который сейчас в одержимом (тело).</summary>
|
/// <summary>Игрок, который сейчас в одержимом (тело).</summary>
|
||||||
public static EntityPlayerLocal Player => _player;
|
public static EntityPlayerLocal Player => _player;
|
||||||
static float _yaw, _pitch;
|
static float _yaw, _pitch;
|
||||||
|
|
||||||
|
/// <summary>necropossess look: взгляд одержимого на точку (проверка без мыши).</summary>
|
||||||
|
public static void DevLookAt(Vector3 _pos)
|
||||||
|
{
|
||||||
|
if (Zombie == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Vector3 d = _pos - Zombie.getHeadPosition();
|
||||||
|
_yaw = Mathf.Atan2(d.x, d.z) * Mathf.Rad2Deg;
|
||||||
|
_pitch = Mathf.Atan2(d.y, new Vector2(d.x, d.z).magnitude) * Mathf.Rad2Deg;
|
||||||
|
}
|
||||||
/// <summary>Куда повёрнуто тело зомби. Шага вбок и назад у зомби нет (тест 03.10: «не работают A, D и S») -
|
/// <summary>Куда повёрнуто тело зомби. Шага вбок и назад у зомби нет (тест 03.10: «не работают A, D и S») -
|
||||||
/// при движении тело разворачивается в сторону шага и идёт вперёд, без движения - смотрит туда же, куда камера.</summary>
|
/// при движении тело разворачивается в сторону шага и идёт вперёд, без движения - смотрит туда же, куда камера.</summary>
|
||||||
static float _bodyYaw;
|
static float _bodyYaw;
|
||||||
@@ -567,9 +588,9 @@ namespace NecromancerTome
|
|||||||
Exit("кровавый прилив - дух выброшен в тело");
|
Exit("кровавый прилив - дух выброшен в тело");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (Flying)
|
if (Scouts)
|
||||||
{
|
{
|
||||||
RevealMap(world, z); // разведка - только стервятник (указание 08.10)
|
RevealMap(world, z); // разведка - стервятник (указание 08.10) и одержимый зомби (#36)
|
||||||
}
|
}
|
||||||
// Подсказка тела (тест 08.10: «стоял перед дверью - подсказка осталась в стервятнике»): её гасит
|
// Подсказка тела (тест 08.10: «стоял перед дверью - подсказка осталась в стервятнике»): её гасит
|
||||||
// PlayerMoveController.Update, а он на время одержимости выключен. Прочие сообщения (голод и т.п.) -
|
// PlayerMoveController.Update, а он на время одержимости выключен. Прочие сообщения (голод и т.п.) -
|
||||||
@@ -615,7 +636,8 @@ namespace NecromancerTome
|
|||||||
move = new Vector2(input.Move.X, input.Move.Y);
|
move = new Vector2(input.Move.X, input.Move.Y);
|
||||||
run = input.Run.IsPressed;
|
run = input.Run.IsPressed;
|
||||||
jump = input.Jump.WasPressed;
|
jump = input.Jump.WasPressed;
|
||||||
attack = input.Primary.WasPressed;
|
attack = input.Primary.WasPressed || DevAttack;
|
||||||
|
DevAttack = false;
|
||||||
ranged = input.Secondary.WasPressed;
|
ranged = input.Secondary.WasPressed;
|
||||||
rangedHeld = input.Secondary.IsPressed;
|
rangedHeld = input.Secondary.IsPressed;
|
||||||
// E на клавиатуре - в PlayerActionsPermanent.Activate; у PlayerActionsLocal.Activate только
|
// E на клавиатуре - в PlayerActionsPermanent.Activate; у PlayerActionsLocal.Activate только
|
||||||
@@ -630,8 +652,9 @@ namespace NecromancerTome
|
|||||||
}
|
}
|
||||||
// Только гуманоиды обычной комплекции (указание 13.10, FpArms.Allows) - у остальных ~/ё ничего не делает.
|
// Только гуманоиды обычной комплекции (указание 13.10, FpArms.Allows) - у остальных ~/ё ничего не делает.
|
||||||
if (input != null && !gui && FpArms.Allows(z) &&
|
if (input != null && !gui && FpArms.Allows(z) &&
|
||||||
(input.CameraChange.WasPressed || input.QuickMenu.WasPressed || input.PermanentActions.QuickMenu.WasPressed))
|
(input.CameraChange.WasPressed || input.QuickMenu.WasPressed || input.PermanentActions.QuickMenu.WasPressed || DevView))
|
||||||
{
|
{
|
||||||
|
DevView = false;
|
||||||
FirstPerson = !FirstPerson;
|
FirstPerson = !FirstPerson;
|
||||||
_fpEye = Vector3.zero;
|
_fpEye = Vector3.zero;
|
||||||
if (FirstPerson)
|
if (FirstPerson)
|
||||||
@@ -665,6 +688,21 @@ namespace NecromancerTome
|
|||||||
Debug.Log("[NecromancerTome] Possession: пояс - ячейка " + (BeltSlot + 1));
|
Debug.Log("[NecromancerTome] Possession: пояс - ячейка " + (BeltSlot + 1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (FirstPerson && ZombieBag.Active)
|
||||||
|
{
|
||||||
|
// Оружие с пояса (#37): в руки - если зомби им владеет; сменился выбранный предмет - подсказка, если нет.
|
||||||
|
ItemStack sel = ZombieBag.Belt(BeltSlot);
|
||||||
|
int key = BeltSlot * 100000 + (sel != null ? sel.itemValue.type : -1);
|
||||||
|
if (key != _beltKey)
|
||||||
|
{
|
||||||
|
_beltKey = key;
|
||||||
|
if (sel != null)
|
||||||
|
{
|
||||||
|
ZombieWeapons.Hint(_player, z, sel.itemValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FpArms.SetWeapon(ZombieWeapons.Current(z));
|
||||||
|
}
|
||||||
if (input != null && !gui && input.ToggleCrouch.WasPressed)
|
if (input != null && !gui && input.ToggleCrouch.WasPressed)
|
||||||
{
|
{
|
||||||
_crouchToggled = !_crouchToggled;
|
_crouchToggled = !_crouchToggled;
|
||||||
@@ -845,7 +883,8 @@ namespace NecromancerTome
|
|||||||
{
|
{
|
||||||
// Tab в зомби - окно его сумки, а не рюкзак персонажа (указание 13.10), как у волка.
|
// Tab в зомби - окно его сумки, а не рюкзак персонажа (указание 13.10), как у волка.
|
||||||
GUIWindowManager zwm = LocalPlayerUI.GetUIForPlayer(_player)?.windowManager;
|
GUIWindowManager zwm = LocalPlayerUI.GetUIForPlayer(_player)?.windowManager;
|
||||||
if (zwm != null && zwm.IsWindowOpen("windowpaging") && !ZombieBag.WindowOpen && !RatLoot.Viewing)
|
// M открывает то же окно windowpaging (с картой) - карту не подменять (#36: M в зомби открывал сумку).
|
||||||
|
if (zwm != null && zwm.IsWindowOpen("windowpaging") && !zwm.IsWindowOpen("map") && !ZombieBag.WindowOpen && !RatLoot.Viewing)
|
||||||
{
|
{
|
||||||
zwm.CloseAllOpenModalWindows();
|
zwm.CloseAllOpenModalWindows();
|
||||||
zwm.Close("windowpaging");
|
zwm.Close("windowpaging");
|
||||||
@@ -862,7 +901,7 @@ namespace NecromancerTome
|
|||||||
{
|
{
|
||||||
// Tab в волке - окно его сумки, а не рюкзак персонажа (тест 10.10: «в волке Tab - вижу сумку игрока»).
|
// Tab в волке - окно его сумки, а не рюкзак персонажа (тест 10.10: «в волке Tab - вижу сумку игрока»).
|
||||||
GUIWindowManager wm = LocalPlayerUI.GetUIForPlayer(_player)?.windowManager;
|
GUIWindowManager wm = LocalPlayerUI.GetUIForPlayer(_player)?.windowManager;
|
||||||
if (wm != null && wm.IsWindowOpen("windowpaging") && !WolfBag.WindowOpen && !RatLoot.Viewing)
|
if (wm != null && wm.IsWindowOpen("windowpaging") && !wm.IsWindowOpen("map") && !WolfBag.WindowOpen && !RatLoot.Viewing)
|
||||||
{
|
{
|
||||||
wm.CloseAllOpenModalWindows();
|
wm.CloseAllOpenModalWindows();
|
||||||
wm.Close("windowpaging");
|
wm.Close("windowpaging");
|
||||||
@@ -905,7 +944,10 @@ namespace NecromancerTome
|
|||||||
// до точки удара (FpArms -> FpImpact), а не по его собственному замаху.
|
// до точки удара (FpArms -> FpImpact), а не по его собственному замаху.
|
||||||
PossessionPowers.BreakInvis(z, "удар");
|
PossessionPowers.BreakInvis(z, "удар");
|
||||||
FpArms.Punch();
|
FpArms.Punch();
|
||||||
_nextAttack = Time.time + Mathf.Max(FpArms.SwingTime, z.GetAttackTimeoutTicks() / 20f);
|
ItemValue weapon = ZombieWeapons.Current(z);
|
||||||
|
// С оружием - темп оружия, как у персонажа (#37); кулаками - темп зомби.
|
||||||
|
_nextAttack = Time.time + (weapon != null ? Mathf.Max(MinWeaponSwing, ZombieWeapons.SwingTime(weapon))
|
||||||
|
: Mathf.Max(FpArms.SwingTime, z.GetAttackTimeoutTicks() / 20f));
|
||||||
}
|
}
|
||||||
else if (attack && Time.time >= _nextAttack)
|
else if (attack && Time.time >= _nextAttack)
|
||||||
{
|
{
|
||||||
@@ -2386,6 +2428,18 @@ namespace NecromancerTome
|
|||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (ZombieWeapons.Current(z) != null)
|
||||||
|
{
|
||||||
|
// Оружие с пояса (#37) - удар как у персонажа, луч из глаз (камера одержимости - в голове зомби). Камера - в
|
||||||
|
// координатах Unity, луч мира - с Origin (тест 25.10: без него «луч ничего не задел» вплотную).
|
||||||
|
Transform cam = _player?.playerCamera != null ? _player.playerCamera.transform : null;
|
||||||
|
Vector3 head = z.getHeadPosition();
|
||||||
|
if (!ZombieWeapons.Strike(z.world, z, cam != null ? new Ray(cam.position + Origin.position, cam.forward) : new Ray(head, (AimPoint() - head).normalized)))
|
||||||
|
{
|
||||||
|
Debug.Log("[NecromancerTome] ZombieWeapons: удар мимо");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
z.SetLookPosition(MeleeAim(z.world, z));
|
z.SetLookPosition(MeleeAim(z.world, z));
|
||||||
_meleeLookUntil = Time.time + 0.6f;
|
_meleeLookUntil = Time.time + 0.6f;
|
||||||
if (!StartStrike(z))
|
if (!StartStrike(z))
|
||||||
@@ -2590,14 +2644,13 @@ namespace NecromancerTome
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Карта в одержимом стервятнике открывается на нём, а не на теле (указание 08.10: разведка -
|
/// <summary>Карта в одержимом стервятнике и зомби открывается на нём, а не на теле (указание 08.10, #36).</summary>
|
||||||
/// только у стервятника).</summary>
|
|
||||||
[HarmonyPatch(typeof(XUiC_MapArea), nameof(XUiC_MapArea.OnOpen))]
|
[HarmonyPatch(typeof(XUiC_MapArea), nameof(XUiC_MapArea.OnOpen))]
|
||||||
public static class Patch_XUiC_MapArea_OnOpen_Possession
|
public static class Patch_XUiC_MapArea_OnOpen_Possession
|
||||||
{
|
{
|
||||||
public static void Postfix(XUiC_MapArea __instance)
|
public static void Postfix(XUiC_MapArea __instance)
|
||||||
{
|
{
|
||||||
if (Possession.Active && Possession.Flying && Possession.Zombie != null)
|
if (Possession.Scouts && Possession.Zombie != null)
|
||||||
{
|
{
|
||||||
__instance.PositionMapAt(Possession.Zombie.GetPosition());
|
__instance.PositionMapAt(Possession.Zombie.GetPosition());
|
||||||
}
|
}
|
||||||
@@ -2610,7 +2663,7 @@ namespace NecromancerTome
|
|||||||
{
|
{
|
||||||
public static bool Prefix(XUiC_MapArea __instance)
|
public static bool Prefix(XUiC_MapArea __instance)
|
||||||
{
|
{
|
||||||
if (!Possession.Active || !Possession.Flying || Possession.Zombie == null)
|
if (!Possession.Scouts || Possession.Zombie == null)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -2871,8 +2924,9 @@ namespace NecromancerTome
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Пояс одержимого от первого лица (указание 13.10: «собственный пояс на 2 ячейки», пока пустой) - на месте
|
/// <summary>Пояс одержимого от первого лица (указание 13.10: «собственный пояс на 2 ячейки») - на месте пояса персонажа,
|
||||||
/// пояса персонажа, внизу по центру; выбранная ячейка в рамке. Ячейки пустые - бьёт кулаками.</summary>
|
/// внизу по центру; выбранная ячейка в рамке. В ячейке - иконка предмета из нижнего ряда сумки (#37), полоска износа;
|
||||||
|
/// чем зомби не владеет или что сломано - бледное (бьёт кулаками).</summary>
|
||||||
static void DrawBelt(float _scale)
|
static void DrawBelt(float _scale)
|
||||||
{
|
{
|
||||||
float size = 64f * _scale, gap = 6f * _scale, frame = 3f * _scale;
|
float size = 64f * _scale, gap = 6f * _scale, frame = 3f * _scale;
|
||||||
@@ -2890,11 +2944,51 @@ namespace NecromancerTome
|
|||||||
GUI.color = Back;
|
GUI.color = Back;
|
||||||
GUI.DrawTexture(r, Texture2D.whiteTexture);
|
GUI.DrawTexture(r, Texture2D.whiteTexture);
|
||||||
GUI.color = prev;
|
GUI.color = prev;
|
||||||
|
ItemStack st = ZombieBag.Belt(i);
|
||||||
|
if (st != null)
|
||||||
|
{
|
||||||
|
bool fits = ZombieWeapons.Enabled && ZombieWeapons.Check(Possession.Zombie, st.itemValue) == ZombieWeapons.Fit.Ok;
|
||||||
|
DrawIcon(new Rect(r.x + 6f * _scale, r.y + 6f * _scale, r.width - 12f * _scale, r.height - 12f * _scale), st.itemValue, fits ? 1f : 0.3f);
|
||||||
|
if (st.itemValue.MaxUseTimes > 0)
|
||||||
|
{
|
||||||
|
float left = 1f - Mathf.Clamp01(st.itemValue.UseTimes / st.itemValue.MaxUseTimes);
|
||||||
|
GUI.color = Color.Lerp(new Color(0.85f, 0.2f, 0.15f), new Color(0.35f, 0.8f, 0.3f), left);
|
||||||
|
GUI.DrawTexture(new Rect(r.x + 4f * _scale, r.yMax - 6f * _scale, (r.width - 8f * _scale) * left, 3f * _scale), Texture2D.whiteTexture);
|
||||||
|
GUI.color = prev;
|
||||||
|
}
|
||||||
|
if (st.count > 1)
|
||||||
|
{
|
||||||
|
GUI.Label(new Rect(r.x, r.yMax - 22f * _scale, r.width - 4f * _scale, 18f * _scale), st.count.ToString(), _style);
|
||||||
|
}
|
||||||
|
}
|
||||||
GUI.Label(new Rect(r.x + 4f * _scale, r.y + 2f * _scale, r.width, 18f * _scale), (i + 1).ToString(), _style);
|
GUI.Label(new Rect(r.x + 4f * _scale, r.y + 2f * _scale, r.width, 18f * _scale), (i + 1).ToString(), _style);
|
||||||
}
|
}
|
||||||
GUI.color = prev;
|
GUI.color = prev;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Иконка предмета из атласа ItemIconAtlas (как у ячеек игры) - кусок текстуры атласа по данным спрайта.</summary>
|
||||||
|
static void DrawIcon(Rect _r, ItemValue _iv, float _alpha)
|
||||||
|
{
|
||||||
|
ItemClass ic = _iv?.ItemClass;
|
||||||
|
string name = ic?.GetIconName();
|
||||||
|
INGUIAtlas atlas = string.IsNullOrEmpty(name) ? null : LocalPlayerUI.GetUIForPrimaryPlayer()?.xui?.GetAtlasByName("ItemIconAtlas", name);
|
||||||
|
UISpriteData sd = atlas?.GetSprite(name);
|
||||||
|
Texture tex = atlas?.texture;
|
||||||
|
if (sd == null || tex == null || tex.width == 0 || tex.height == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Rect uv = new Rect((float)sd.x / tex.width, 1f - (float)(sd.y + sd.height) / tex.height, (float)sd.width / tex.width, (float)sd.height / tex.height);
|
||||||
|
// Иконка не растягивается: вписать по большей стороне.
|
||||||
|
float k = Mathf.Min(_r.width / sd.width, _r.height / sd.height);
|
||||||
|
Rect fit = new Rect(_r.center.x - sd.width * k / 2f, _r.center.y - sd.height * k / 2f, sd.width * k, sd.height * k);
|
||||||
|
Color prev = GUI.color;
|
||||||
|
Color tint = ic.GetIconTint(_iv);
|
||||||
|
GUI.color = new Color(tint.r, tint.g, tint.b, tint.a * _alpha);
|
||||||
|
GUI.DrawTextureWithTexCoords(fit, tex, uv);
|
||||||
|
GUI.color = prev;
|
||||||
|
}
|
||||||
|
|
||||||
static void DrawBar(Rect _r, float _frac, Color _fill, string _text)
|
static void DrawBar(Rect _r, float _frac, Color _fill, string _text)
|
||||||
{
|
{
|
||||||
Color prev = GUI.color;
|
Color prev = GUI.color;
|
||||||
|
|||||||
+42
-1
@@ -487,7 +487,6 @@ namespace NecromancerTome
|
|||||||
yield return AccessTools.Method(typeof(XUiC_ContainerStandardControls), nameof(XUiC_ContainerStandardControls.MoveFillAndSmart));
|
yield return AccessTools.Method(typeof(XUiC_ContainerStandardControls), nameof(XUiC_ContainerStandardControls.MoveFillAndSmart));
|
||||||
yield return AccessTools.Method(typeof(XUiC_ContainerStandardControls), nameof(XUiC_ContainerStandardControls.MoveFillStacks));
|
yield return AccessTools.Method(typeof(XUiC_ContainerStandardControls), nameof(XUiC_ContainerStandardControls.MoveFillStacks));
|
||||||
yield return AccessTools.Method(typeof(XUiC_ContainerStandardControls), nameof(XUiC_ContainerStandardControls.MoveSmart));
|
yield return AccessTools.Method(typeof(XUiC_ContainerStandardControls), nameof(XUiC_ContainerStandardControls.MoveSmart));
|
||||||
yield return AccessTools.Method(typeof(XUiC_ContainerStandardControls), nameof(XUiC_ContainerStandardControls.ToggleLockMode));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool Prefix(XUiC_ContainerStandardControls __instance, System.Reflection.MethodBase __originalMethod)
|
public static bool Prefix(XUiC_ContainerStandardControls __instance, System.Reflection.MethodBase __originalMethod)
|
||||||
@@ -504,6 +503,48 @@ namespace NecromancerTome
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Замок ячеек при обыске не включается. В 3.4 ToggleLockMode нет: замок - свойство UserLockMode, кнопка -
|
||||||
|
/// XUiC_ButtonSelectable (её подсветку снимаем, иначе кнопка осталась бы «нажатой»).</summary>
|
||||||
|
[HarmonyPatch(typeof(XUiC_ContainerStandardControls), nameof(XUiC_ContainerStandardControls.UserLockMode), MethodType.Setter)]
|
||||||
|
public static class Patch_UserLockMode
|
||||||
|
{
|
||||||
|
public static bool Prefix(XUiC_ContainerStandardControls __instance, bool value)
|
||||||
|
{
|
||||||
|
if (!value || !Viewing)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (__instance.btnToggleLockMode != null)
|
||||||
|
{
|
||||||
|
__instance.btnToggleLockMode.IsSelected = false;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>3.4: предмет, брошенный на пустое место окна (не в ячейку), игра кладёт прямо в ящик, мешок или рюкзак
|
||||||
|
/// (dropItemToGrid) - мимо запертых ячеек. При обыске - отказ, предмет остаётся в руке.</summary>
|
||||||
|
[HarmonyPatch]
|
||||||
|
public static class Patch_DropItemToGrid
|
||||||
|
{
|
||||||
|
static System.Collections.Generic.IEnumerable<System.Reflection.MethodBase> TargetMethods()
|
||||||
|
{
|
||||||
|
yield return AccessTools.Method(typeof(XUiC_LootContainer), nameof(XUiC_LootContainer.dropItemToGrid));
|
||||||
|
yield return AccessTools.Method(typeof(XUiC_BagContainer), nameof(XUiC_BagContainer.dropItemToGrid));
|
||||||
|
yield return AccessTools.Method(typeof(XUiC_Backpack), nameof(XUiC_Backpack.dropItemToGrid));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool Prefix()
|
||||||
|
{
|
||||||
|
if (!Viewing)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
Deny();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Щелчок по запертой ячейке контейнера - отказ «рюкзак полон».</summary>
|
/// <summary>Щелчок по запертой ячейке контейнера - отказ «рюкзак полон».</summary>
|
||||||
[HarmonyPatch(typeof(XUiC_ItemStack), nameof(XUiC_ItemStack.Update))]
|
[HarmonyPatch(typeof(XUiC_ItemStack), nameof(XUiC_ItemStack.Update))]
|
||||||
public static class Patch_ItemStack_Update
|
public static class Patch_ItemStack_Update
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ namespace NecromancerTome
|
|||||||
public static class SnakeControl
|
public static class SnakeControl
|
||||||
{
|
{
|
||||||
public const float WalkSpeed = 6f; // велосипед, vehicles.xml velocityMax
|
public const float WalkSpeed = 6f; // велосипед, vehicles.xml velocityMax
|
||||||
public const float RunSpeed = 16.5f; // мотоцикл турбо с нагнетателем
|
public const float RunSpeed = 12.5f; // было 16.5 (мотоцикл турбо); 26.10: «Уменьши скорость змеи на шифте на четверть» - фризы подгрузки карты
|
||||||
public const float Gravity = 20f;
|
public const float Gravity = 20f;
|
||||||
/// <summary>Прыжок на Пробел (указание 28.09: «иначе не выбраться из ям»). 10 м/с при g = 20 -
|
/// <summary>Прыжок на Пробел (указание 28.09: «иначе не выбраться из ям»). 10 м/с при g = 20 -
|
||||||
/// подъём на 2,5 м: вместе с заползанием на блок хватает на стену в три блока.</summary>
|
/// подъём на 2,5 м: вместе с заползанием на блок хватает на стену в три блока.</summary>
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ namespace NecromancerTome
|
|||||||
}
|
}
|
||||||
foreach (EntityPlayer p in w.Players.list)
|
foreach (EntityPlayer p in w.Players.list)
|
||||||
{
|
{
|
||||||
if (p == null || p.inventory?.holdingItem?.Name != CinerariumThreads.ThreadKnifeName || p.emodel == null ||
|
if (p == null || !CinerariumThreads.IsThreadToolName(p.inventory?.holdingItem?.Name) || p.emodel == null ||
|
||||||
p.emodel.IsFPV && !p.isEntityRemote)
|
p.emodel.IsFPV && !p.isEntityRemote)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
@@ -74,7 +74,7 @@ namespace NecromancerTome
|
|||||||
|
|
||||||
public static bool Applies(EntityAlive _e)
|
public static bool Applies(EntityAlive _e)
|
||||||
{
|
{
|
||||||
return TpHold > 0 && _e != null && _e.inventory?.holdingItem?.Name == CinerariumThreads.ThreadKnifeName;
|
return TpHold > 0 && _e != null && CinerariumThreads.IsThreadToolName(_e.inventory?.holdingItem?.Name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
using System;
|
||||||
|
using HarmonyLib;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace NecromancerTome
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Баг игры 3.4 (найден 24.10.2026: «словил ошибку при переключении ножа», #79). Inventory.SetStackAt
|
||||||
|
/// ДО записи в слот пояса зовёт новую аналитику LogToolBeltUsageEvent; та считает характеристики
|
||||||
|
/// предмета через EffectManager.GetValue без сущности (_entity = null), и у passive_effect со
|
||||||
|
/// значением из CVar (у кинжала некроманта value="@$necroKnifeDamageCVar") PassiveEffect.ModifyValue
|
||||||
|
/// падает на _ea.Buffs. Исключение вылетает раньше ItemGrid.SetItem - нож не меняется, слот не пишется.
|
||||||
|
///
|
||||||
|
/// Лечение: аналитика - только телеметрия игры, её исключение глотаем (Finalizer), SetStackAt
|
||||||
|
/// продолжает и пишет слот как обычно. Предупреждение в лог - один раз за запуск.
|
||||||
|
/// </summary>
|
||||||
|
[HarmonyPatch(typeof(Inventory), nameof(Inventory.LogToolBeltUsageEvent))]
|
||||||
|
public static class Patch_Inventory_LogToolBeltUsageEvent_Guard
|
||||||
|
{
|
||||||
|
static bool _warned;
|
||||||
|
|
||||||
|
public static Exception Finalizer(Exception __exception)
|
||||||
|
{
|
||||||
|
if (__exception != null && !_warned)
|
||||||
|
{
|
||||||
|
_warned = true;
|
||||||
|
Debug.LogWarning("[NecromancerTome] ToolBeltAnalyticsGuard: game analytics failed in Inventory.LogToolBeltUsageEvent, ignored: " + __exception.Message);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+18
-1
@@ -690,7 +690,6 @@ namespace NecromancerTome
|
|||||||
yield return AccessTools.Method(typeof(XUiC_ContainerStandardControls), nameof(XUiC_ContainerStandardControls.MoveFillAndSmart));
|
yield return AccessTools.Method(typeof(XUiC_ContainerStandardControls), nameof(XUiC_ContainerStandardControls.MoveFillAndSmart));
|
||||||
yield return AccessTools.Method(typeof(XUiC_ContainerStandardControls), nameof(XUiC_ContainerStandardControls.MoveFillStacks));
|
yield return AccessTools.Method(typeof(XUiC_ContainerStandardControls), nameof(XUiC_ContainerStandardControls.MoveFillStacks));
|
||||||
yield return AccessTools.Method(typeof(XUiC_ContainerStandardControls), nameof(XUiC_ContainerStandardControls.MoveSmart));
|
yield return AccessTools.Method(typeof(XUiC_ContainerStandardControls), nameof(XUiC_ContainerStandardControls.MoveSmart));
|
||||||
yield return AccessTools.Method(typeof(XUiC_ContainerStandardControls), nameof(XUiC_ContainerStandardControls.ToggleLockMode));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool Prefix(XUiC_ContainerStandardControls __instance)
|
public static bool Prefix(XUiC_ContainerStandardControls __instance)
|
||||||
@@ -699,6 +698,24 @@ namespace NecromancerTome
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Замок ячеек у сумки волка не включается (в 3.4 вместо ToggleLockMode - свойство UserLockMode).</summary>
|
||||||
|
[HarmonyPatch(typeof(XUiC_ContainerStandardControls), nameof(XUiC_ContainerStandardControls.UserLockMode), MethodType.Setter)]
|
||||||
|
public static class Patch_ContainerStandardControls_UserLockMode_WolfBag
|
||||||
|
{
|
||||||
|
public static bool Prefix(XUiC_ContainerStandardControls __instance, bool value)
|
||||||
|
{
|
||||||
|
if (!value || !WolfBagControls.Blocked(__instance))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (__instance.btnToggleLockMode != null)
|
||||||
|
{
|
||||||
|
__instance.btnToggleLockMode.IsSelected = false;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Кнопка замка видна по привязке {hasslotlocks} - у волка её нет.</summary>
|
/// <summary>Кнопка замка видна по привязке {hasslotlocks} - у волка её нет.</summary>
|
||||||
[HarmonyPatch(typeof(XUiC_BagContainer), nameof(XUiC_BagContainer.GetBindingValueInternal))]
|
[HarmonyPatch(typeof(XUiC_BagContainer), nameof(XUiC_BagContainer.GetBindingValueInternal))]
|
||||||
public static class Patch_BagContainer_Binding_WolfBag
|
public static class Patch_BagContainer_Binding_WolfBag
|
||||||
|
|||||||
+34
-2
@@ -11,6 +11,8 @@ namespace NecromancerTome
|
|||||||
/// - при выходе из одержимости зомби сбрасывает рюкзаком всё, что было в сумке (и на поясе - когда в поясе
|
/// - при выходе из одержимости зомби сбрасывает рюкзаком всё, что было в сумке (и на поясе - когда в поясе
|
||||||
/// появятся предметы);
|
/// появятся предметы);
|
||||||
/// - гибель одержимого - мешок на месте и метка на карте, как у сумки волка.
|
/// - гибель одержимого - мешок на месте и метка на карте, как у сумки волка.
|
||||||
|
/// ПОЯС (#37, 25.10) - нижний ряд той же сумки: Possession.BeltSlots ячеек под верхним рядом, оружие в руке берётся
|
||||||
|
/// оттуда (ZombieWeapons). Хранение, выброс мешком и метка - общие с сумкой.
|
||||||
/// Лут (указание 13.10: «раз у них E свободно, подключай лутание») - как у волка: E на ящик или мешок - обыск с
|
/// Лут (указание 13.10: «раз у них E свободно, подключай лутание») - как у волка: E на ящик или мешок - обыск с
|
||||||
/// переносом в сумку, E на предмет на земле - подобрать (WolfBag.Target - сумка зомби, пока она есть).
|
/// переносом в сумку, E на предмет на земле - подобрать (WolfBag.Target - сумка зомби, пока она есть).
|
||||||
/// Только зомби из списка вида от первого лица (FpArms.Allows), не питомцы. Окно - то же, что у сумки волка
|
/// Только зомби из списка вида от первого лица (FpArms.Allows), не питомцы. Окно - то же, что у сумки волка
|
||||||
@@ -28,12 +30,41 @@ namespace NecromancerTome
|
|||||||
public const string WaypointName = "necroZombieBagWaypoint";
|
public const string WaypointName = "necroZombieBagWaypoint";
|
||||||
public static bool WindowOpen;
|
public static bool WindowOpen;
|
||||||
static Bag _bag;
|
static Bag _bag;
|
||||||
|
/// <summary>Ячеек в ряду: верхний ряд - сумка, нижний - пояс.</summary>
|
||||||
|
static int _cols;
|
||||||
|
|
||||||
static string FilePath => Path.Combine(GameIO.GetSaveGameDir(), FileName);
|
static string FilePath => Path.Combine(GameIO.GetSaveGameDir(), FileName);
|
||||||
|
|
||||||
public static bool Active => _bag != null;
|
public static bool Active => _bag != null;
|
||||||
public static Bag Current => _bag;
|
public static Bag Current => _bag;
|
||||||
|
|
||||||
|
/// <summary>Ячейка пояса (0..BeltSlots-1) - нижний ряд сумки; нет сумки - null.</summary>
|
||||||
|
public static ItemStack Belt(int _i)
|
||||||
|
{
|
||||||
|
ItemStack[] items = _bag?.ItemGrid?.items;
|
||||||
|
int idx = _cols + _i;
|
||||||
|
return items != null && _i >= 0 && _i < Possession.BeltSlots && idx < items.Length && !items[idx].IsEmpty() ? items[idx] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>necropossess belt: положить предмет в ячейку пояса (проверка без мыши).</summary>
|
||||||
|
public static bool DevSetBelt(int _i, ItemStack _s)
|
||||||
|
{
|
||||||
|
if (_bag == null || _i < 0 || _i >= Possession.BeltSlots)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_bag.SetSlot(_cols + _i, _s);
|
||||||
|
Save();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Предмет пояса изменился на месте (износ) - окно и файл.</summary>
|
||||||
|
public static void BeltChanged()
|
||||||
|
{
|
||||||
|
_bag?.onBackpackChanged();
|
||||||
|
Save();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Записать после лута (WolfBag.SaveTarget).</summary>
|
/// <summary>Записать после лута (WolfBag.SaveTarget).</summary>
|
||||||
public static void SaveNow() => Save();
|
public static void SaveNow() => Save();
|
||||||
|
|
||||||
@@ -64,8 +95,9 @@ namespace NecromancerTome
|
|||||||
{
|
{
|
||||||
Debug.LogWarning("[NecromancerTome] ZombieBag: файл сумки не прочитан (" + e.Message + ") - оставлен как есть");
|
Debug.LogWarning("[NecromancerTome] ZombieBag: файл сумки не прочитан (" + e.Message + ") - оставлен как есть");
|
||||||
}
|
}
|
||||||
_bag = new Bag(new Vector2i(SlotsFor(_z), 1), XUiC_ItemStack.StackLocationTypes.LootContainer, null);
|
_cols = Mathf.Max(SlotsFor(_z), Possession.BeltSlots);
|
||||||
Debug.Log("[NecromancerTome] ZombieBag: сумка " + _z.EntityName + " - ячеек " + _bag.SlotCount);
|
_bag = new Bag(new Vector2i(_cols, 2), XUiC_ItemStack.StackLocationTypes.LootContainer, null);
|
||||||
|
Debug.Log("[NecromancerTome] ZombieBag: сумка " + _z.EntityName + " - ячеек " + _bag.SlotCount + ", из них пояс " + Possession.BeltSlots);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Вселение кончилось: всё из сумки - мешком на месте зомби; погиб - ещё и метка на карте.</summary>
|
/// <summary>Вселение кончилось: всё из сумки - мешком на месте зомби; погиб - ещё и метка на карте.</summary>
|
||||||
|
|||||||
@@ -0,0 +1,339 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace NecromancerTome
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Оружие на поясе одержимого зомби (#37, решения пользователя 25.10). Пояс - две ячейки нижнего ряда сумки зомби
|
||||||
|
/// (ZombieBag.Belt), туда попадает только то, что зомби нашёл сам или что положили в сумку. Чем зомби владеет -
|
||||||
|
/// по его типу («зомби строитель сможет пользоваться простыми инструментами: топоры, молотки и т.п. Медсестра -
|
||||||
|
/// ножами»; список утверждён 25.10, карточка #37). Неумелое или сломанное в выбранной ячейке - кулаки и подсказка.
|
||||||
|
///
|
||||||
|
/// Только ближний бой (ItemActionDynamic - всё оружие и инструменты ближнего боя персонажа). Бензопила и бур в игре
|
||||||
|
/// стрелковые (Ranged, топливо), гвоздомёт - метатель: они придут вместе с огнестрелом (#80).
|
||||||
|
///
|
||||||
|
/// Удар - от первого лица, в миг удара рук (FpArms -> Possession.FpImpact): луч из глаз зомби на дальность оружия и
|
||||||
|
/// ItemActionAttack.Hit, как у персонажа (урон по существам и блокам, кровь, звук, отрубание, износ). Урон - самого
|
||||||
|
/// предмета с его модами, без перков и баффов зомби (holdingEntity = null): «урон от оружия».
|
||||||
|
/// </summary>
|
||||||
|
public static class ZombieWeapons
|
||||||
|
{
|
||||||
|
/// <summary>Выключатель (necroarms weapon on|off): выкл - все бьют кулаками, как до #37.</summary>
|
||||||
|
public static bool Enabled = true;
|
||||||
|
|
||||||
|
// Группы предметов: имена групп (не путать с концовками имён предметов, их тоже можно писать в умениях).
|
||||||
|
const string Knife = "Knife", SmallKnife = "SmallKnife", Club = "Club", Baton = "Baton", Knuckles = "Knuckles", Spear = "Spear",
|
||||||
|
Sledge = "Sledge", Axe = "Axe", Hammer = "Hammer", Pick = "Pick", Shovel = "Shovel", Wrench = "Wrench", Light = "Light";
|
||||||
|
static readonly HashSet<string> GroupNames = new HashSet<string>
|
||||||
|
{ Knife, SmallKnife, Club, Baton, Knuckles, Spear, Sledge, Axe, Hammer, Pick, Shovel, Wrench, Light };
|
||||||
|
|
||||||
|
/// <summary>Умения по началу имени класса зомби (как FpArms.Allows; Feral/Radiated/Charged/Infernal - как базовый).
|
||||||
|
/// Строка - группа или концовка имени предмета игры (StoneAxe подходит и к TazasStoneAxe).</summary>
|
||||||
|
static readonly Dictionary<string, string[]> Skills = new Dictionary<string, string[]>
|
||||||
|
{
|
||||||
|
{ "zombieUtilityWorker", new[] { Hammer, Pick, Shovel, Axe, Sledge, Wrench, "ImpactDriver", "Auger", "Nailgun" } },
|
||||||
|
{ "zombieLumberjack", new[] { Axe, "Chainsaw", Sledge } },
|
||||||
|
{ "zombieTomClark", new[] { Shovel, Axe, Spear } },
|
||||||
|
{ "zombieJanitor", new[] { Wrench, Shovel, Light, Club } },
|
||||||
|
{ "zombieNurse", new[] { Knife } },
|
||||||
|
{ "zombieLab", new[] { Knife, "StunBaton", "PlasmaBaton" } },
|
||||||
|
{ "zombieMaleHazmat", new[] { Shovel, Light, Wrench } },
|
||||||
|
{ "zombieSoldier", new[] { Knife, Baton, Spear } },
|
||||||
|
{ "zombieBiker", new[] { Knuckles, Club, "PipeBaton", Wrench } },
|
||||||
|
{ "zombieInmate", new[] { "BoneKnife", Knuckles, "PipeBaton" } },
|
||||||
|
{ "zombieYo", new[] { Knuckles, "BaseballBat", "PipeBaton" } },
|
||||||
|
{ "zombieSkateboarder", new[] { "BaseballBat", "PipeBaton" } },
|
||||||
|
{ "zombieBowler", new[] { Club, Sledge } },
|
||||||
|
{ "zombieBoe", new[] { Club, Knuckles, "StoneAxe", "StoneShovel" } },
|
||||||
|
{ "zombieJoe", new[] { Club, Knuckles, "StoneAxe", "StoneShovel" } },
|
||||||
|
{ "zombieMoe", new[] { Sledge, Club } },
|
||||||
|
{ "zombieSteve", new[] { Club, "StoneAxe", "StoneShovel" } },
|
||||||
|
{ "zombieBusinessMan", new[] { "BaseballBat", SmallKnife } },
|
||||||
|
{ "zombieBurnt", new[] { "Torch", Club } },
|
||||||
|
{ "zombieDarlene", new[] { Knife, "WoodenClub" } },
|
||||||
|
{ "zombieArlene", new[] { Knife, "BaseballBat" } },
|
||||||
|
{ "zombieMarlene", new[] { Knife, "BaseballBat" } },
|
||||||
|
{ "zombiePartyGirl", new[] { "CandyKnife", "CandyClub", "BaseballBat" } },
|
||||||
|
};
|
||||||
|
|
||||||
|
static readonly Dictionary<int, HashSet<string>> _groups = new Dictionary<int, HashSet<string>>();
|
||||||
|
|
||||||
|
static bool Tag(ItemClass _ic, string _tag) => _ic.ItemTags.Test_AnySet(FastTags<TagGroup.Global>.Parse(_tag));
|
||||||
|
|
||||||
|
/// <summary>Группы предмета: у предметов игры - по имени, у предметов других модов - по тегам (как у игры).</summary>
|
||||||
|
static HashSet<string> Groups(ItemClass _ic)
|
||||||
|
{
|
||||||
|
if (_groups.TryGetValue(_ic.Id, out HashSet<string> g))
|
||||||
|
{
|
||||||
|
return g;
|
||||||
|
}
|
||||||
|
g = new HashSet<string>();
|
||||||
|
string n = _ic.Name ?? "";
|
||||||
|
bool motor = Tag(_ic, "motorTool") || Tag(_ic, "nailgun");
|
||||||
|
if (n.StartsWith("meleeWpnBlade") || Tag(_ic, "knife") || Tag(_ic, "machete"))
|
||||||
|
{
|
||||||
|
g.Add(Knife);
|
||||||
|
if (!n.Contains("Machete") && !Tag(_ic, "machete"))
|
||||||
|
{
|
||||||
|
g.Add(SmallKnife);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (n.StartsWith("meleeWpnClub") || Tag(_ic, "club"))
|
||||||
|
{
|
||||||
|
g.Add(Club);
|
||||||
|
}
|
||||||
|
if (n.StartsWith("meleeWpnBaton") || Tag(_ic, "batonJunkie") || Tag(_ic, "stunBaton"))
|
||||||
|
{
|
||||||
|
g.Add(Baton);
|
||||||
|
}
|
||||||
|
if (n.StartsWith("meleeWpnKnuckles") || Tag(_ic, "knuckleSkill"))
|
||||||
|
{
|
||||||
|
g.Add(Knuckles);
|
||||||
|
}
|
||||||
|
if (n.StartsWith("meleeWpnSpear") || Tag(_ic, "spearSkill"))
|
||||||
|
{
|
||||||
|
g.Add(Spear);
|
||||||
|
}
|
||||||
|
if (n.StartsWith("meleeWpnSledge") || Tag(_ic, "sledge"))
|
||||||
|
{
|
||||||
|
g.Add(Sledge);
|
||||||
|
}
|
||||||
|
if (!motor && (n.StartsWith("meleeToolAxe") || Tag(_ic, "axe")))
|
||||||
|
{
|
||||||
|
g.Add(Axe);
|
||||||
|
}
|
||||||
|
if (n.Contains("ClawHammer") || !motor && Tag(_ic, "repairTool") && !Tag(_ic, "axe"))
|
||||||
|
{
|
||||||
|
g.Add(Hammer);
|
||||||
|
}
|
||||||
|
if (n.StartsWith("meleeToolShovel") || Tag(_ic, "shovel"))
|
||||||
|
{
|
||||||
|
g.Add(Shovel);
|
||||||
|
}
|
||||||
|
else if (!motor && (n.StartsWith("meleeToolPick") || Tag(_ic, "miningTool") && !Tag(_ic, "axe")))
|
||||||
|
{
|
||||||
|
g.Add(Pick);
|
||||||
|
}
|
||||||
|
if (!n.Contains("ImpactDriver") && (n.StartsWith("meleeToolSalvage") || Tag(_ic, "salvageTool")))
|
||||||
|
{
|
||||||
|
g.Add(Wrench);
|
||||||
|
}
|
||||||
|
if (n.Contains("Flashlight") || n.Contains("Torch") || Tag(_ic, "heldTorch"))
|
||||||
|
{
|
||||||
|
g.Add(Light);
|
||||||
|
}
|
||||||
|
_groups[_ic.Id] = g;
|
||||||
|
return g;
|
||||||
|
}
|
||||||
|
|
||||||
|
static string[] SkillsOf(EntityAlive _z)
|
||||||
|
{
|
||||||
|
if (_z == null || !EntityClass.list.TryGetValue(_z.entityClass, out EntityClass cls) || cls?.entityClassName == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
foreach (KeyValuePair<string, string[]> kv in Skills)
|
||||||
|
{
|
||||||
|
if (cls.entityClassName.StartsWith(kv.Key))
|
||||||
|
{
|
||||||
|
return kv.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Зомби этого типа владеет предметом.</summary>
|
||||||
|
public static bool Knows(EntityAlive _z, ItemClass _ic)
|
||||||
|
{
|
||||||
|
string[] skills = SkillsOf(_z);
|
||||||
|
if (skills == null || _ic == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
HashSet<string> g = Groups(_ic);
|
||||||
|
string n = _ic.Name ?? "";
|
||||||
|
foreach (string s in skills)
|
||||||
|
{
|
||||||
|
if (GroupNames.Contains(s) ? g.Contains(s) : n.EndsWith(s))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Fit { Fists, Ok, Unknown, Broken }
|
||||||
|
|
||||||
|
/// <summary>Чем бьёт зомби с этим предметом в выбранной ячейке.</summary>
|
||||||
|
public static Fit Check(EntityAlive _z, ItemValue _iv)
|
||||||
|
{
|
||||||
|
if (_iv == null || _iv.IsEmpty() || _iv.ItemClass == null)
|
||||||
|
{
|
||||||
|
return Fit.Fists;
|
||||||
|
}
|
||||||
|
ItemClass ic = _iv.ItemClass;
|
||||||
|
if (!(ic.Actions != null && ic.Actions.Length > 0 && ic.Actions[0] is ItemActionDynamic) || !Knows(_z, ic))
|
||||||
|
{
|
||||||
|
return Fit.Unknown;
|
||||||
|
}
|
||||||
|
if (_iv.MaxUseTimes > 0 && _iv.UseTimes >= _iv.MaxUseTimes)
|
||||||
|
{
|
||||||
|
return Fit.Broken;
|
||||||
|
}
|
||||||
|
return Fit.Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Оружие в руке одержимого сейчас: выбранная ячейка пояса, если зомби им владеет и оно цело; иначе null.</summary>
|
||||||
|
public static ItemValue Current(EntityAlive _z)
|
||||||
|
{
|
||||||
|
if (!Enabled || !ZombieBag.Active)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
ItemStack s = ZombieBag.Belt(Possession.BeltSlot);
|
||||||
|
return s != null && Check(_z, s.itemValue) == Fit.Ok ? s.itemValue : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Пауза между ударами этим оружием - как у персонажа (удары в минуту).</summary>
|
||||||
|
public static float SwingTime(ItemValue _iv)
|
||||||
|
{
|
||||||
|
float apm = EffectManager.GetValue(PassiveEffects.AttacksPerMinute, _iv, 60f, null, null, _iv.ItemClass.ItemTags);
|
||||||
|
return apm > 1f ? 60f / apm : 1f;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Удар оружием в миг удара рук: луч из глаз зомби (камера одержимости) на дальность оружия. Мимо - false
|
||||||
|
/// (звука нет, как у персонажа - замах в воздух).</summary>
|
||||||
|
public static bool Strike(World _world, EntityAlive _z, Ray _ray)
|
||||||
|
{
|
||||||
|
ItemStack stack = ZombieBag.Belt(Possession.BeltSlot);
|
||||||
|
ItemValue iv = stack?.itemValue;
|
||||||
|
if (iv == null || Check(_z, iv) != Fit.Ok || !(iv.ItemClass.Actions[0] is ItemActionDynamic act))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
FastTags<TagGroup.Global> tags = ItemActionAttack.PrimaryTag | iv.ItemClass.ItemTags;
|
||||||
|
float range = EffectManager.GetValue(PassiveEffects.MaxRange, iv, act.RangeDefault, null, null, tags);
|
||||||
|
float blockRange = EffectManager.GetValue(PassiveEffects.BlockRange, iv, range, null, null, tags);
|
||||||
|
float sphere = act.SphereRadius;
|
||||||
|
float dist = Mathf.Max(range, blockRange) + sphere;
|
||||||
|
_ray.origin -= sphere * _ray.direction;
|
||||||
|
// Свои коллайдеры зомби - мимо луча (как GetExecuteActionTarget у игры). Камера одержимости - в голове зомби:
|
||||||
|
// если луч всё же задел его самого (тест 25.10: все удары «мимо» вплотную), - продолжить луч за ним.
|
||||||
|
int layer = _z.GetModelLayer();
|
||||||
|
_z.SetModelLayer(2);
|
||||||
|
WorldRayHitInfo hit = null;
|
||||||
|
float passed = 0f;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 4 && hit == null; i++)
|
||||||
|
{
|
||||||
|
Ray ray = new Ray(_ray.origin + _ray.direction * passed, _ray.direction);
|
||||||
|
EntityAlive target = null;
|
||||||
|
bool any = Voxel.Raycast(_world, ray, dist - passed, -538767389, 128, sphere);
|
||||||
|
if (any)
|
||||||
|
{
|
||||||
|
target = ItemActionAttack.GetEntityFromHit(Voxel.voxelRayHitInfo) as EntityAlive;
|
||||||
|
}
|
||||||
|
if (target == null)
|
||||||
|
{
|
||||||
|
any = Voxel.Raycast(_world, ray, dist - passed, -538488845, 128, sphere);
|
||||||
|
target = any ? ItemActionAttack.GetEntityFromHit(Voxel.voxelRayHitInfo) as EntityAlive : null;
|
||||||
|
}
|
||||||
|
if (!any)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (target == _z)
|
||||||
|
{
|
||||||
|
passed += Mathf.Sqrt(Voxel.voxelRayHitInfo.hit.distanceSq) + 0.05f;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
hit = Voxel.voxelRayHitInfo.Clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_z.SetModelLayer(layer);
|
||||||
|
}
|
||||||
|
if (hit?.tag == null)
|
||||||
|
{
|
||||||
|
string phys = "ничего";
|
||||||
|
if (Physics.Raycast(_ray.origin - Origin.position, _ray.direction, out RaycastHit ph, 5f, -5, QueryTriggerInteraction.Ignore))
|
||||||
|
{
|
||||||
|
phys = ph.transform.name + " слой " + LayerMask.LayerToName(ph.transform.gameObject.layer) + " в " + ph.distance.ToString("F2");
|
||||||
|
}
|
||||||
|
Debug.Log("[NecromancerTome] ZombieWeapons: луч ничего не задел (дальность " + dist.ToString("F1") + (passed > 0f ? ", сквозь себя " + passed.ToString("F1") : "") +
|
||||||
|
"), луч " + _ray.origin.ToString("F2") + " -> " + _ray.direction.ToString("F2") + ", голова " + _z.getHeadPosition().ToString("F2") + ", физика: " + phys);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
bool entity = hit.tag.StartsWith("E_");
|
||||||
|
float d = Vector3.Distance(_ray.origin, hit.hit.pos);
|
||||||
|
if (entity ? d > range + sphere : d > blockRange + sphere)
|
||||||
|
{
|
||||||
|
Debug.Log("[NecromancerTome] ZombieWeapons: далеко - " + hit.tag + " в " + d.ToString("F1") + " м, достаёт " + (entity ? range : blockRange).ToString("F1"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (entity && ItemActionAttack.GetEntityFromHit(hit) is EntityAlive e && (e == _z || e is EntityPlayer || SnakeControl.IsNecroEntity(e)))
|
||||||
|
{
|
||||||
|
Debug.Log("[NecromancerTome] ZombieWeapons: под ударом свой - " + e.EntityName);
|
||||||
|
return false; // хозяина и его нежить одержимый не бьёт - как кулаками (MeleeAim)
|
||||||
|
}
|
||||||
|
BlockValue bv = ItemActionAttack.GetBlockHit(_world, hit);
|
||||||
|
float blockDamage = entity || bv.isair ? 0f : act.GetDamageBlock(iv, bv, null, 0);
|
||||||
|
float entityDamage = act.GetDamageEntity(iv, null, 0);
|
||||||
|
string tagHit = hit.tag;
|
||||||
|
FastTags<TagGroup.Global> dTags = tags;
|
||||||
|
if (tagHit == "E_BP_Head")
|
||||||
|
{
|
||||||
|
dTags |= FastTags<TagGroup.Global>.Parse("head");
|
||||||
|
}
|
||||||
|
float dismember = EffectManager.GetValue(PassiveEffects.DismemberChance, iv, 0f, null, null, dTags);
|
||||||
|
ItemActionAttack.AttackHitInfo details = new ItemActionAttack.AttackHitInfo { WeaponTypeTag = ItemActionAttack.MeleeTag };
|
||||||
|
ItemActionAttack.Hit(hit, _z.entityId, act.DamageType, blockDamage, entityDamage, 1f, 1f, 0f, dismember,
|
||||||
|
iv.ItemClass.MadeOfMaterial.SurfaceCategory, new DamageMultiplier(), new List<string>(), details, 1, 0, 0f, null,
|
||||||
|
act.ToolBonuses, ItemActionAttack.EnumAttackMode.RealNoHarvesting, act.HitSoundOverrides, -1, iv);
|
||||||
|
// Износ - как у персонажа (ItemActionDynamic.hitTarget); сломалось - звук, дальше кулаки.
|
||||||
|
if (iv.MaxUseTimes > 0)
|
||||||
|
{
|
||||||
|
iv.UseTimes += EffectManager.GetValue(PassiveEffects.DegradationPerUse, iv, 1f, null, null, tags) * ItemAction.ItemDegradationModifier;
|
||||||
|
if (iv.UseTimes >= iv.MaxUseTimes)
|
||||||
|
{
|
||||||
|
Audio.Manager.BroadcastPlay(_z, "itembreak");
|
||||||
|
Debug.Log("[NecromancerTome] ZombieWeapons: " + iv.ItemClass.Name + " сломалось");
|
||||||
|
}
|
||||||
|
ZombieBag.BeltChanged();
|
||||||
|
}
|
||||||
|
Debug.Log("[NecromancerTome] ZombieWeapons: удар " + iv.ItemClass.Name + " по " + (entity ? tagHit : bv.Block?.GetBlockName()) +
|
||||||
|
", урон " + (entity ? entityDamage : blockDamage).ToString("F0") + (details.bKilled ? ", убит" : ""));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Подсказка при смене выбранного предмета: неумелое или сломанное - кулаки.</summary>
|
||||||
|
public static void Hint(EntityPlayerLocal _player, EntityAlive _z, ItemValue _iv)
|
||||||
|
{
|
||||||
|
Fit f = Enabled ? Check(_z, _iv) : Fit.Fists;
|
||||||
|
if (f == Fit.Unknown)
|
||||||
|
{
|
||||||
|
GameManager.ShowTooltip(_player, string.Format(Localization.Get("necroZombieWeaponUnknown"), _iv.ItemClass.GetLocalizedItemName()));
|
||||||
|
}
|
||||||
|
else if (f == Fit.Broken)
|
||||||
|
{
|
||||||
|
GameManager.ShowTooltip(_player, string.Format(Localization.Get("necroZombieWeaponBroken"), _iv.ItemClass.GetLocalizedItemName()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>necroarms weapon: что умеет текущий одержимый и что в его поясе.</summary>
|
||||||
|
public static string Dump(EntityAlive _z)
|
||||||
|
{
|
||||||
|
string[] sk = SkillsOf(_z);
|
||||||
|
string s = "оружие " + (Enabled ? "вкл" : "ВЫКЛ") + ", умения: " + (sk != null ? string.Join(", ", sk) : "нет");
|
||||||
|
for (int i = 0; i < Possession.BeltSlots; i++)
|
||||||
|
{
|
||||||
|
ItemStack st = ZombieBag.Belt(i);
|
||||||
|
s += "\n ячейка " + (i + 1) + (i == Possession.BeltSlot ? " (выбрана)" : "") + ": " +
|
||||||
|
(st == null || st.IsEmpty() ? "пусто" : st.itemValue.ItemClass.Name + " [" + string.Join(",", Groups(st.itemValue.ItemClass)) + "] - " + Check(_z, st.itemValue));
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 623 KiB After Width: | Height: | Size: 631 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
Reference in New Issue
Block a user