1.7.0: охрана питомцами, один питомец за раз, прицел книг, гео-оссуарий
Питомцы: - охрана хозяина: враг ближе 20 м (в режиме змеи - к телу) атакуется сам, поводок 40 м, охраняют все кроме крысы; приказ прицелом охрана не перебивает - IsFoe исключает существ мода (necro*): питомец выбирал целью себя - призыв нового спутника отзывает прежнего - прицел книг призыва - как у арбалета, краснеет на годной цели Гео-оссуарий (necroGeoOssuary): станция добычи на кровавой сфере - основа - коллектор росы; в 3 ячейки модов любой из 5 ресурсов (камень, уголь, селитра, железо, сланец), 1 сфера = стак 6000 за 5-8 ч - работает без открытого неба; верстак, Подмастерье (ур. 25) - своя модель (бандл necroossuary): кости, корни, сосуды, клыки, черепа с глазами-углями; шейдер и теги коллайдера берутся у ванильного коллектора Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
b737dd43d0
commit
f2dfeed126
+129
-17
@@ -42,6 +42,32 @@ namespace NecromancerTome
|
||||
/// смысл не в таймере, а в том, чтобы приказ не висел вечно, если цель уйдёт из мира.</summary>
|
||||
public const int OrderTicks = 6000;
|
||||
|
||||
/// <summary>Кого игрок сейчас держит в прицеле и кого можно назначить целью. null - некого.
|
||||
/// Общее для приказа и для покраснения прицела (PetCrosshair): прицел краснеет ровно тогда,
|
||||
/// когда приказ будет принят. Свои питомцы целью не бывают.</summary>
|
||||
public static EntityAlive TargetUnderCrosshair(EntityPlayerLocal _local)
|
||||
{
|
||||
WorldRayHitInfo hit = _local.HitInfo;
|
||||
if (hit == null || !hit.bHitValid || hit.transform == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
EntityAlive target = ItemActionAttack.GetEntityFromHit(hit) as EntityAlive;
|
||||
if (target == null || target.IsDead() || target == _local)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (target is EntityPlayer || target is EntityTrader)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (PetFollowPatch.FindOwnerOfPet(target.entityId) != null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
/// <summary>Отдаёт питомцу приказ атаковать то, на что смотрит игрок. false - приказа не
|
||||
/// вышло, и вызывающая сторона показывает "Нет цели для атаки".</summary>
|
||||
public static bool TryOrderAttack(EntityAlive _owner, EntityAlive _pet)
|
||||
@@ -59,23 +85,8 @@ namespace NecromancerTome
|
||||
return false;
|
||||
}
|
||||
|
||||
WorldRayHitInfo hit = local.HitInfo;
|
||||
if (hit == null || !hit.bHitValid || hit.transform == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Entity hitEntity = ItemActionAttack.GetEntityFromHit(hit);
|
||||
EntityAlive target = hitEntity as EntityAlive;
|
||||
if (target == null || target.IsDead())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (target == _pet || target == _owner)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (target is EntityPlayer || target is EntityTrader)
|
||||
EntityAlive target = TargetUnderCrosshair(local);
|
||||
if (target == null || target == _pet)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -84,9 +95,110 @@ namespace NecromancerTome
|
||||
// следующем тике перебить наш приказ тем, кто ударил крысу последним.
|
||||
_pet.SetRevengeTarget(null);
|
||||
_pet.SetAttackTarget(target, OrderTicks);
|
||||
// Охрана (PetFollowPatch.Guard) сама раздаёт цели; приказ игрока она перебивать не должна.
|
||||
PetFollowPatch.NoteOrder(_pet.entityId, target.entityId);
|
||||
Debug.Log("[NecromancerTome] PetAttackCommand: pet " + _pet.entityId + " ordered to attack " +
|
||||
target.entityId + " (" + target.EntityClass.entityClassName + ")");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ПРИЦЕЛ КНИГ И СВИТКОВ ПРИЗЫВА - указание 30.09.2026: «сделать другой прицел (более явный, из
|
||||
/// уже имеющихся в игре) и чтобы он краснел при попадании в него врага. Как у змеи краснеет».
|
||||
///
|
||||
/// У ItemActionSpawnEntity своего прицела нет, он получал общий маленький «плюс». Игра рисует
|
||||
/// прицел одним методом, EntityPlayerLocal.guiDrawCrosshair, и цвет там берётся из настроек,
|
||||
/// так что подменить один лишь ВИД прицела мало - покраснеть он не сможет. Поэтому для наших
|
||||
/// книг метод заменяется целиком: те же условия показа, тот же размер и та же готовая
|
||||
/// прицел арбалета (DrawDynamicCrosshair, 4 штриха), а цвет - наш.
|
||||
///
|
||||
/// Красный = приказ будет принят: питомец этой книги призван и под прицелом годная цель
|
||||
/// (PetAttackCommand.TargetUnderCrosshair - тот же разбор, что у самого приказа). Пока питомец
|
||||
/// не призван, основное действие - это призыв, и красить прицел незачем.
|
||||
///
|
||||
/// Только у книг с приказом (PetInfo.CommandsAttack). Свиток Роя оставлен как был.
|
||||
/// </summary>
|
||||
[HarmonyLib.HarmonyPatch(typeof(EntityPlayerLocal), "guiDrawCrosshair")]
|
||||
public static class Patch_EntityPlayerLocal_guiDrawCrosshair_PetCrosshair
|
||||
{
|
||||
/// <summary>Тот же красный, что у прицела змеи (SnakeCrosshair.OnTarget).</summary>
|
||||
static readonly Color OnTarget = new Color(1f, 0.15f, 0.1f, 1f);
|
||||
|
||||
public static bool Prefix(EntityPlayerLocal __instance, NGuiWdwInGameHUD _guiInGame, bool bModalWindowOpen)
|
||||
{
|
||||
if (__instance.inventory == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
ItemClass holding = __instance.inventory.holdingItem;
|
||||
ItemActionSpawnEntity action = holding != null && holding.Actions != null && holding.Actions.Length > 0
|
||||
? holding.Actions[0] as ItemActionSpawnEntity
|
||||
: null;
|
||||
if (action == null ||
|
||||
!Patch_ItemActionSpawnEntity_Spawn_PetLimit.LimitedPets.TryGetValue(action.entityToSpawn,
|
||||
out Patch_ItemActionSpawnEntity_Spawn_PetLimit.PetInfo petInfo) ||
|
||||
!petInfo.CommandsAttack)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
// Дальше - условия показа из самого guiDrawCrosshair, в том же порядке.
|
||||
if (!_guiInGame.showCrosshair || Event.current.type != EventType.Repaint || __instance.IsDead() ||
|
||||
__instance.emodel.IsRagdollActive || __instance.AttachedToEntity != null ||
|
||||
!GamePrefs.GetBool(EnumGamePrefs.OptionsCrosshairEnabled) || bModalWindowOpen ||
|
||||
_guiInGame.CrosshairSquare == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
float opacity = GamePrefs.GetFloat(EnumGamePrefs.OptionsCrosshairOpacity);
|
||||
int colorIndex = GamePrefs.GetInt(EnumGamePrefs.OptionsCrosshairColor);
|
||||
if (colorIndex < 0 || colorIndex >= EntityPlayerLocal.crosshairColors.Count)
|
||||
{
|
||||
colorIndex = 0;
|
||||
}
|
||||
Color color = EntityPlayerLocal.crosshairColors[colorIndex];
|
||||
color.a = opacity;
|
||||
if (PetIsOut(__instance, action.entityToSpawn) && PetAttackCommand.TargetUnderCrosshair(__instance) != null)
|
||||
{
|
||||
color = OnTarget;
|
||||
}
|
||||
|
||||
Vector2 center = __instance.GetCrosshairPosition2D();
|
||||
center.y = Screen.height - center.y;
|
||||
float size = Screen.height * 0.059f * GamePrefs.GetFloat(EnumGamePrefs.OptionsCrosshairScale);
|
||||
// Прицел арбалета (тест 30.09: CrosshairDamage выглядел «кружком со стрелкой вниз»):
|
||||
// четыре штриха игрового DrawDynamicCrosshair, длина та же, что у стрелкового оружия
|
||||
// (size / 3); разброс у книги не меняется, поэтому зазор постоянный.
|
||||
EntityPlayerLocal.DrawDynamicCrosshair(center.x, center.y, size * 0.22f, size * 0.22f, size / 3f,
|
||||
GamePrefs.GetFloat(EnumGamePrefs.OptionsCrosshairThickness), color.a, color);
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>Призван ли сейчас питомец этой книги у этого игрока. По списку PetFollowPatch, а не
|
||||
/// GetOwnedEntities: тот на каждый вызов заводит новый список, а мы здесь каждый кадр.</summary>
|
||||
static bool PetIsOut(EntityPlayerLocal _player, string _entityClassName)
|
||||
{
|
||||
int classId = EntityClass.GetId(_entityClassName);
|
||||
World world = _player.world;
|
||||
if (classId == -1 || world == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < PetFollowPatch.TrackedPets.Count; i++)
|
||||
{
|
||||
PetFollowPatch.TrackedPet tracked = PetFollowPatch.TrackedPets[i];
|
||||
if (tracked.OwnerEntityId != _player.entityId)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
Entity pet = world.GetEntity(tracked.PetEntityId);
|
||||
if (pet != null && pet.entityClass == classId && !pet.IsDead())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user