Мод для 7 Days to Die 3.2: навык «Некромантия», растущий от счётчика убитых зомби, тёмное оружие с шестью собственными модами, призывная нежить, пирамида духов и сюжетный финал через Чёрный портал. Локализация на 13 языках. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MaNro5hAGTzcQ7rJNN2tCX
172 lines
8.5 KiB
C#
172 lines
8.5 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|
|
}
|