Книга некроманта 1.0 — первая публичная версия
Мод для 7 Days to Die 3.2: навык «Некромантия», растущий от счётчика убитых зомби, тёмное оружие с шестью собственными модами, призывная нежить, пирамида духов и сюжетный финал через Чёрный портал. Локализация на 13 языках. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MaNro5hAGTzcQ7rJNN2tCX
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
using HarmonyLib;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NecromancerTome
|
||||
{
|
||||
/// <summary>
|
||||
/// Shrinks and recolors the "RadiatedParticlesOnMesh" glow that both buffNecroDeviatorCharm
|
||||
/// and buffNecroVictim attach to a zombie (see buffs.xml, action="AttachParticleEffectToEntity"
|
||||
/// - both buffs reuse the same particle prefab rather than needing two different ones).
|
||||
///
|
||||
/// Why this needs Harmony: AttachParticleEffectToEntity's XML attributes are limited to
|
||||
/// particle/parent_transform/local_offset/local_rotation/oneshot/shape_mesh/sound - there is
|
||||
/// no scale or color/alpha attribute (confirmed by decompiling
|
||||
/// MinEventActionAttachParticleEffectToEntity.ParseXmlAttribute - that's the exhaustive list).
|
||||
/// The prefab always instantiates at its own authored size/color; nothing in XML can change
|
||||
/// that. So instead we let the vanilla action run as normal (Postfix, not Prefix - the
|
||||
/// particle GameObject has to already exist), then find the same child object it just created
|
||||
/// and adjust it directly - same lookup the engine itself uses internally: a child transform
|
||||
/// named "Ptl_" + the particle prefab's name, parented under the entity's mesh transform.
|
||||
///
|
||||
/// SizeFactor dropped 0.5 -> 0.2 2026-08-28 (user: "выглядят как шар вне зомби" - even the
|
||||
/// original half-size shrink still read as a floating ball rather than a mesh-hugging glow).
|
||||
///
|
||||
/// Color (2026-08-28): Deviator green and Victim purple are both explicit now (Deviator used
|
||||
/// to just be whatever RadiatedParticlesOnMesh's own baked-in color happens to be - reads
|
||||
/// "green/energy" on its own, never actually set). Explicit per user request: "если оба бафа,
|
||||
/// то пусть свечения смешиваются" - a zombie carrying both gets Color.Lerp(charm, victim, .5),
|
||||
/// not one color just overriding the other.
|
||||
///
|
||||
/// Gated to only affect entities carrying at least one of OUR buffs (checked per-buff, not
|
||||
/// just "is this the right particle name") - not vanilla naturally-irradiated zombies that
|
||||
/// happen to reuse the same particle prefab elsewhere.
|
||||
///
|
||||
/// GENERALIZED 2026-08-29 for buffNecroPortalChannel (BACKLOG.md item 6, user request:
|
||||
/// dense green-blue particles while a portal stone channels, thick enough to partially
|
||||
/// obscure the player) - was hard-gated to `EntityZombie` specifically (`_params.Self is
|
||||
/// EntityZombie`) since the two original buffs are both zombie-facing; this new one targets
|
||||
/// the PLAYER, so the check is now against the common `EntityAlive` base (where
|
||||
/// `.Buffs`/`.emodel` actually live) instead. Also needed its own size/alpha/DENSITY numbers
|
||||
/// separate from the zombie glow's - the first version reused the same SizeFactor/AlphaFactor
|
||||
/// constants for all three buffs, which the user confirmed reads as "редкие-редкие" (way too
|
||||
/// sparse) for a "should partly cover you" effect - see GlowConfig below, one per buff now
|
||||
/// instead of two shared constants.
|
||||
/// </summary>
|
||||
[HarmonyPatch(typeof(MinEventActionAttachParticleEffectToEntity), "Execute")]
|
||||
public static class Patch_AttachParticleEffectToEntity_ShrinkCharmGlow
|
||||
{
|
||||
public const string ParticleName = "RadiatedParticlesOnMesh";
|
||||
|
||||
public class GlowConfig
|
||||
{
|
||||
public Color Tint;
|
||||
public float SizeFactor;
|
||||
public float AlphaFactor;
|
||||
/// <summary>Multiplies both the emission rate (particles/second) AND maxParticles by
|
||||
/// this factor together - raising rate alone caps out silently once the system hits
|
||||
/// its authored maxParticles ceiling, so both have to move together to actually get a
|
||||
/// visibly denser cloud instead of the same particle count arriving faster.</summary>
|
||||
public float DensityFactor;
|
||||
}
|
||||
|
||||
/// <summary>Unchanged from the original 2026-08-28 tuning - the zombie-facing glow was
|
||||
/// never asked to get denser/bigger, only the new portal-channel one was.</summary>
|
||||
public static readonly GlowConfig CharmGlow = new GlowConfig { Tint = new Color(0.2f, 1f, 0.3f), SizeFactor = 0.2f, AlphaFactor = 0.5f, DensityFactor = 1f };
|
||||
/// <summary>Purple, per user request 2026-08-28 ("подсвети бафнутого зомби... фиолетовым").</summary>
|
||||
public static readonly GlowConfig VictimGlow = new GlowConfig { Tint = new Color(0.55f, 0.05f, 0.85f), SizeFactor = 0.2f, AlphaFactor = 0.5f, DensityFactor = 1f };
|
||||
/// <summary>RE-TUNED 2026-08-29 (user: "частицы есть, но они редкие-редкие. А надо чтобы
|
||||
/// прямо густо располагались... чтобы частично перекрывали внешний вид" + colour changed
|
||||
/// from the first version's near-black to green-blue/teal, "зелёноголубые"). SizeFactor
|
||||
/// bumped from a shrink (0.2, matching the mesh-hugging zombie glow) to just under full
|
||||
/// size (0.9) - a swirl meant to partly obscure the player needs to actually be
|
||||
/// body-sized, not a tight skin-hugging glow. AlphaFactor raised to near-opaque (0.9) for
|
||||
/// the same "obscures the view" reason - the zombie glow's own 0.5 was deliberately subtle,
|
||||
/// this one shouldn't be. DensityFactor=5 - the actual fix for "редкие-редкие", multiplies
|
||||
/// both emission rate and maxParticles together (see GlowConfig's own doc on why both).</summary>
|
||||
public static readonly GlowConfig PortalChannelGlow = new GlowConfig { Tint = new Color(0.1f, 0.85f, 0.8f), SizeFactor = 0.9f, AlphaFactor = 0.9f, DensityFactor = 5f };
|
||||
|
||||
public static void Postfix(MinEventActionAttachParticleEffectToEntity __instance, MinEventParams _params)
|
||||
{
|
||||
if (_params.Self == null || __instance.goToInstantiate == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (__instance.goToInstantiate.name != ParticleName)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!(_params.Self is EntityAlive entity) || entity.Buffs == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
bool isPortalChannel = entity.Buffs.HasBuff(Patch_ItemActionEat_ExecuteAction_PortalStones.ChannelBuffName);
|
||||
bool isVictim = entity.Buffs.HasBuff(Patch_Entity_DropBagServer_VictimBag.VictimBuffName);
|
||||
bool isCharm = entity.Buffs.HasBuff(Patch_EntityBuffs_AddBuff_DeviatorCharm.CharmBuffName);
|
||||
if (!isPortalChannel && !isVictim && !isCharm)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// Portal channel is player-only and never coexists with the zombie-facing buffs
|
||||
// below in practice, so it's kept as a simple separate branch rather than folded
|
||||
// into the same Lerp blend those two use with each other.
|
||||
GlowConfig config;
|
||||
if (isPortalChannel)
|
||||
{
|
||||
config = PortalChannelGlow;
|
||||
}
|
||||
else if (isVictim && isCharm)
|
||||
{
|
||||
config = new GlowConfig { Tint = Color.Lerp(CharmGlow.Tint, VictimGlow.Tint, 0.5f), SizeFactor = CharmGlow.SizeFactor, AlphaFactor = CharmGlow.AlphaFactor, DensityFactor = 1f };
|
||||
}
|
||||
else
|
||||
{
|
||||
config = isVictim ? VictimGlow : CharmGlow;
|
||||
}
|
||||
|
||||
Transform meshTransform = entity.emodel != null ? entity.emodel.meshTransform : null;
|
||||
if (meshTransform == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Transform particleTransform = meshTransform.Find("Ptl_" + ParticleName);
|
||||
if (particleTransform == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Belt-and-suspenders for size: not every particle system's Scaling Mode respects
|
||||
// transform scale, but startSizeMultiplier always does regardless of that setting.
|
||||
particleTransform.localScale = Vector3.one * config.SizeFactor;
|
||||
ParticleSystem[] systems = particleTransform.GetComponentsInChildren<ParticleSystem>(true);
|
||||
foreach (ParticleSystem ps in systems)
|
||||
{
|
||||
ParticleSystem.MainModule main = ps.main;
|
||||
main.startSizeMultiplier *= config.SizeFactor;
|
||||
main.maxParticles = Mathf.Max(1, Mathf.RoundToInt(main.maxParticles * config.DensityFactor));
|
||||
|
||||
if (config.DensityFactor != 1f)
|
||||
{
|
||||
ParticleSystem.EmissionModule emission = ps.emission;
|
||||
emission.rateOverTimeMultiplier *= config.DensityFactor;
|
||||
emission.rateOverDistanceMultiplier *= config.DensityFactor;
|
||||
}
|
||||
|
||||
ParticleSystem.MinMaxGradient startColor = main.startColor;
|
||||
switch (startColor.mode)
|
||||
{
|
||||
case ParticleSystemGradientMode.Color:
|
||||
{
|
||||
Color c = config.Tint;
|
||||
c.a = startColor.color.a * config.AlphaFactor;
|
||||
startColor.color = c;
|
||||
break;
|
||||
}
|
||||
case ParticleSystemGradientMode.TwoColors:
|
||||
{
|
||||
Color min = config.Tint;
|
||||
Color max = config.Tint;
|
||||
min.a = startColor.colorMin.a * config.AlphaFactor;
|
||||
max.a = startColor.colorMax.a * config.AlphaFactor;
|
||||
startColor.colorMin = min;
|
||||
startColor.colorMax = max;
|
||||
break;
|
||||
}
|
||||
// Gradient/TwoGradients modes bake color+alpha into the gradient asset itself -
|
||||
// no generic way to override that from code, so those are left as-is.
|
||||
}
|
||||
main.startColor = startColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user