Area of influence depends on followers MobState (#1439)

* simple anti-religion zone shader

* observers refactor
This commit is contained in:
Red
2025-06-18 21:46:58 +03:00
committed by GitHub
parent 54307574c3
commit 3a4bbcb289
11 changed files with 179 additions and 156 deletions

View File

@@ -25,12 +25,15 @@ public sealed class CP14ReligionVisionOverlay : Overlay
public override bool RequestScreenTexture => true;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
private readonly ProtoId<CP14ReligionPrototype>? _religion = null;
private readonly ProtoId<CP14ReligionPrototype>? _religion;
private readonly ShaderInstance _religionShader;
private readonly Vector2[] _positions = new Vector2[MaxCount];
private readonly float[] _radii = new float[MaxCount];
private int _count = 0;
private int _count;
private readonly Vector2[] _antiPositions = new Vector2[MaxCount];
private readonly float[] _antiRadii = new float[MaxCount];
private int _antiCount;
public CP14ReligionVisionOverlay()
{
@@ -49,20 +52,21 @@ public sealed class CP14ReligionVisionOverlay : Overlay
if (args.Viewport.Eye == null)
return false;
_count = 0;
var clusters = new List<Cluster>();
var antiClusters = new List<Cluster>();
var religionQuery = _entManager.AllEntityQueryEnumerator<CP14ReligionObserverComponent, TransformComponent>();
while (religionQuery.MoveNext(out var uid, out var rel, out var xform))
while (religionQuery.MoveNext(out var uid, out var observer, out var xform))
{
if (_religion is null)
continue;
var observation = rel.Observation;
if (!observation.ContainsKey(_religion.Value))
if (observer.Religion is null)
continue;
if (!rel.Active || xform.MapID != args.MapId)
if (observer.Radius <= 0f)
continue;
if (!observer.Active || xform.MapID != args.MapId)
continue;
var mapPos = _transform.GetWorldPosition(uid);
@@ -72,23 +76,40 @@ public sealed class CP14ReligionVisionOverlay : Overlay
var tempCoords = args.Viewport.WorldToLocal(mapPos);
tempCoords.Y = args.Viewport.Size.Y - tempCoords.Y; // Local space to fragment space.
// try find cluster to merge with
bool merged = false;
foreach (var cluster in clusters)
if (observer.Religion.Value == _religion.Value)
{
if ((cluster.Position - tempCoords).Length() < 150f)
// try find cluster to merge with
bool merged = false;
foreach (var cluster in clusters)
{
cluster.Add(tempCoords, rel.Observation[_religion.Value]);
merged = true;
break;
if ((cluster.Position - tempCoords).Length() < 150f)
{
cluster.Add(tempCoords, observer.Radius);
merged = true;
break;
}
}
if (!merged && clusters.Count < MaxCount)
clusters.Add(new Cluster(tempCoords, observer.Radius));
}
else
{
// try find cluster to merge with
bool merged = false;
foreach (var antiCluster in antiClusters)
{
if ((antiCluster.Position - tempCoords).Length() < 150f)
{
antiCluster.Add(tempCoords, observer.Radius);
merged = true;
break;
}
}
if (!merged)
clusters.Add(new Cluster(tempCoords, rel.Observation[_religion.Value]));
if (clusters.Count >= MaxCount)
break;
if (!merged && antiClusters.Count < MaxCount)
antiClusters.Add(new Cluster(tempCoords, observer.Radius));
}
}
_count = 0;
@@ -99,6 +120,14 @@ public sealed class CP14ReligionVisionOverlay : Overlay
_count++;
}
_antiCount = 0;
foreach (var antiCluster in antiClusters)
{
_antiPositions[_antiCount] = antiCluster.Position;
_antiRadii[_antiCount] = antiCluster.Radius;
_antiCount++;
}
return true;
}
@@ -112,9 +141,15 @@ public sealed class CP14ReligionVisionOverlay : Overlay
_religionShader?.SetParameter("shaderColor", visionComponent.ShaderColor);
_religionShader?.SetParameter("renderScale", args.Viewport.RenderScale * args.Viewport.Eye.Scale);
_religionShader?.SetParameter("count", _count);
_religionShader?.SetParameter("position", _positions);
_religionShader?.SetParameter("radius", _radii);
_religionShader?.SetParameter("anticount", _antiCount);
_religionShader?.SetParameter("antiposition", _antiPositions);
_religionShader?.SetParameter("antiradius", _antiRadii);
_religionShader?.SetParameter("SCREEN_TEXTURE", ScreenTexture);
var worldHandle = args.WorldHandle;

View File

@@ -86,7 +86,12 @@ public sealed partial class CP14ReligionGodSystem : CP14SharedReligionGodSystem
foreach (var god in GetGods(follower.Religion.Value))
{
_magicEnergy.TransferEnergy((uid, energy), god.Owner, follower.EnergyToGodTransfer, out _, out _, safe: true);
_magicEnergy.TransferEnergy((uid, energy),
god.Owner,
follower.EnergyToGodTransfer,
out _,
out _,
safe: true);
}
}
}
@@ -111,7 +116,12 @@ public sealed partial class CP14ReligionGodSystem : CP14SharedReligionGodSystem
Timer.Spawn(333,
() =>
{
_chatSys.TrySendInGameICMessage(speaker, message, type, ChatTransmitRange.Normal, nameOverride: MetaData(ent).EntityName, ignoreActionBlocker: true);
_chatSys.TrySendInGameICMessage(speaker,
message,
type,
ChatTransmitRange.Normal,
nameOverride: MetaData(ent).EntityName,
ignoreActionBlocker: true);
});
}
@@ -126,14 +136,14 @@ public sealed partial class CP14ReligionGodSystem : CP14SharedReligionGodSystem
private void OnObserverInit(Entity<CP14ReligionObserverComponent> ent, ref ComponentInit args)
{
foreach (var (religion, _) in ent.Comp.Observation)
{
var gods = GetGods(religion);
if (ent.Comp.Religion is null)
return;
foreach (var god in gods)
{
UpdatePvsOverrides(god);
}
var gods = GetGods(ent.Comp.Religion.Value);
foreach (var god in gods)
{
UpdatePvsOverrides(god);
}
}
@@ -163,7 +173,9 @@ public sealed partial class CP14ReligionGodSystem : CP14SharedReligionGodSystem
return;
var wrappedMessage =
Loc.GetString("cp14-altar-wrapped-message", ("name", MetaData(args.Source).EntityName), ("msg", args.Message));
Loc.GetString("cp14-altar-wrapped-message",
("name", MetaData(args.Source).EntityName),
("msg", args.Message));
SendMessageToGods(ent.Comp.Religion.Value, wrappedMessage, args.Source);
}
@@ -181,7 +193,14 @@ public sealed partial class CP14ReligionGodSystem : CP14SharedReligionGodSystem
channels.Add(godActor.PlayerSession.Channel);
}
_chat.ChatMessageToMany(ChatChannel.Notifications, msg, msg, source, false, true, channels, colorOverride: Color.Aqua);
_chat.ChatMessageToMany(ChatChannel.Notifications,
msg,
msg,
source,
false,
true,
channels,
colorOverride: Color.Aqua);
}
public FixedPoint2 GetFollowerPercentage(Entity<CP14ReligionEntityComponent> god)
@@ -221,10 +240,10 @@ public sealed partial class CP14ReligionGodSystem : CP14SharedReligionGodSystem
var query = EntityQueryEnumerator<CP14ReligionObserverComponent>();
while (query.MoveNext(out var uid, out var observer))
{
if (!observer.Observation.ContainsKey(ent.Comp.Religion.Value))
if (!observer.Active)
continue;
if (observer.Observation[ent.Comp.Religion.Value] <= ObservationOverrideRadius)
if (observer.Radius <= ObservationOverrideRadius)
continue;
ent.Comp.PvsOverridedObservers.Add(uid);
@@ -240,9 +259,9 @@ public sealed partial class CP14ReligionGodSystem : CP14SharedReligionGodSystem
if (ent.Comp.Session is null)
return;
foreach (var altar in ent.Comp.PvsOverridedObservers)
foreach (var observer in ent.Comp.PvsOverridedObservers)
{
_pvs.RemoveSessionOverride(altar, ent.Comp.Session);
_pvs.RemoveSessionOverride(observer, ent.Comp.Session);
}
ent.Comp.Session = null;
@@ -259,10 +278,10 @@ public sealed partial class CP14ReligionGodSystem : CP14SharedReligionGodSystem
}
}
public sealed class CP14SpokeAttemptEvent(string message, InGameICChatType type, ICommonSession? player) : CancellableEntityEventArgs
public sealed class CP14SpokeAttemptEvent(string message, InGameICChatType type, ICommonSession? player)
: CancellableEntityEventArgs
{
public string Message = message;
public InGameICChatType Type = type;
public ICommonSession? Player = player;
}

View File

@@ -12,7 +12,10 @@ namespace Content.Shared._CP14.Religion.Components;
public sealed partial class CP14ReligionObserverComponent : Component
{
[DataField, AutoNetworkedField]
public Dictionary<ProtoId<CP14ReligionPrototype>, float> Observation = new(); //DAMNATION
public ProtoId<CP14ReligionPrototype>? Religion;
[DataField, AutoNetworkedField]
public float Radius = 5f;
[DataField, AutoNetworkedField]
public bool Active = true;

View File

@@ -11,8 +11,5 @@ public sealed partial class CP14ReligionPrototype : IPrototype
[IdDataField] public string ID { get; } = default!;
[DataField]
public float FollowerObservationRadius = 8f;
[DataField]
public float AltarObservationRadius = 25f;
public float FollowerObservationRadius = 10f;
}

View File

@@ -48,50 +48,6 @@ public abstract partial class CP14SharedReligionGodSystem
},
});
}
public bool TryConvertAltar(EntityUid target, ProtoId<CP14ReligionPrototype> religion)
{
if (!_proto.TryIndex(religion, out var indexedReligion))
return false;
EnsureComp<CP14ReligionAltarComponent>(target, out var altar);
if (!altar.CanBeConverted)
return false;
var oldReligion = altar.Religion;
altar.Religion = religion;
Dirty(target, altar);
EditObservation(target, religion, indexedReligion.AltarObservationRadius);
var ev = new CP14ReligionChangedEvent(oldReligion, religion);
RaiseLocalEvent(target, ev);
return true;
}
public void DeconvertAltar(EntityUid target)
{
if (!TryComp<CP14ReligionAltarComponent>(target, out var altar))
return;
if (altar.Religion is null)
return;
if (!_proto.TryIndex(altar.Religion, out var indexedReligion))
return;
EditObservation(target, altar.Religion.Value, -indexedReligion.AltarObservationRadius);
var oldReligion = altar.Religion;
altar.Religion = null;
var ev = new CP14ReligionChangedEvent(oldReligion, null);
RaiseLocalEvent(target, ev);
Dirty(target, altar);
}
}
[Serializable, NetSerializable]

View File

@@ -40,16 +40,30 @@ public abstract partial class CP14SharedReligionGodSystem
if (ent.Comp.Religion is null)
return;
EnsureComp<CP14ReligionObserverComponent>(ent, out var observer);
if (!_proto.TryIndex(observer.Religion, out var indexedReligion))
return;
var baseObservation = indexedReligion.FollowerObservationRadius;
switch (args.NewMobState)
{
case MobState.Alive:
observer.Radius = baseObservation;
break;
case MobState.Critical:
SendMessageToGods(ent.Comp.Religion.Value, Loc.GetString("cp14-critical-follower-message", ("name", MetaData(ent).EntityName)), ent);
observer.Radius = baseObservation * 0.25f;
break;
case MobState.Dead:
SendMessageToGods(ent.Comp.Religion.Value, Loc.GetString("cp14-dead-follower-message", ("name", MetaData(ent).EntityName)), ent);
observer.Radius = 0f;
break;
}
Dirty(ent.Owner, observer);
}
private void OnGetAltVerbs(EntityUid uid, CP14ReligionFollowerComponent component, GetVerbsEvent<AlternativeVerb> args)
@@ -152,12 +166,14 @@ public abstract partial class CP14SharedReligionGodSystem
return false;
EnsureComp<CP14ReligionFollowerComponent>(pending, out var follower);
EnsureComp<CP14ReligionObserverComponent>(pending, out var observer);
observer.Religion = indexedReligion;
observer.Radius = indexedReligion.FollowerObservationRadius;
var oldReligion = follower.Religion;
follower.Religion = pending.Comp.Religion;
Dirty(pending, follower);
EditObservation(pending, pending.Comp.Religion.Value, indexedReligion.FollowerObservationRadius);
Dirty(pending, observer);
var ev = new CP14ReligionChangedEvent(oldReligion, pending.Comp.Religion);
RaiseLocalEvent(pending, ev);
@@ -178,11 +194,9 @@ public abstract partial class CP14SharedReligionGodSystem
if (follower.Religion is null)
return;
if (!_proto.TryIndex(follower.Religion, out var indexedReligion))
return;
RemCompDeferred<CP14ReligionObserverComponent>(target);
SendMessageToGods(follower.Religion.Value, Loc.GetString("cp14-remove-follower-message", ("name", MetaData(target).EntityName)), target);
EditObservation(target, follower.Religion.Value, -indexedReligion.FollowerObservationRadius);
var oldReligion = follower.Religion;
follower.Religion = null;

View File

@@ -28,28 +28,6 @@ public abstract partial class CP14SharedReligionGodSystem
args.Visibility &= ~MenuVisibility.NoFov;
}
public void EditObservation(EntityUid target, ProtoId<CP14ReligionPrototype> religion, float range)
{
EnsureComp<CP14ReligionObserverComponent>(target, out var observer);
if (observer.Observation.ContainsKey(religion))
{
var newRange = Math.Clamp(observer.Observation[religion] + range, 0, float.MaxValue);
if (newRange <= 0)
observer.Observation.Remove(religion);
else
observer.Observation[religion] = newRange;
}
else
{
// Otherwise, add a new observation for the religion.
observer.Observation.Add(religion, range);
}
Dirty(target, observer);
}
public bool InVision(EntityUid target, Entity<CP14ReligionEntityComponent> user)
{
var position = Transform(target).Coordinates;
@@ -62,29 +40,27 @@ public abstract partial class CP14SharedReligionGodSystem
if (!HasComp<CP14ReligionVisionComponent>(user))
return true;
if (user.Comp.Religion is null)
return true;
var userXform = Transform(user);
var query = EntityQueryEnumerator<CP14ReligionObserverComponent, TransformComponent>();
while (query.MoveNext(out var uid, out var observer, out var xform))
{
if (!observer.Active)
if (!observer.Active || observer.Religion is null || observer.Radius <= 0f)
continue;
if (xform.MapID != userXform.MapID)
continue;
if (user.Comp.Religion is null)
if (observer.Religion.Value != user.Comp.Religion.Value)
continue;
if (!observer.Observation.ContainsKey(user.Comp.Religion.Value))
continue;
var obsPos = _transform.GetWorldPosition(uid);
var targetPos = coords.Position;
if (Vector2.Distance(obsPos, targetPos) <= observer.Observation[user.Comp.Religion.Value])
if (Vector2.Distance(obsPos, targetPos) <= observer.Radius)
{
// If the observer is within range of the target, they can see it.
return true;
return observer.Religion.Value == user.Comp.Religion.Value;
}
}

View File

@@ -97,4 +97,4 @@
- type: Tag
tags:
- FootstepSound
- CP14Mosquito
- CP14Mosquito

View File

@@ -12,6 +12,7 @@
sprite: _CP14/Mobs/Demigods/temp_icons.rsi
color: "#ffffff99"
- type: PointLight
energy: 2
radius: 10
softness: 1
castShadows: false

View File

@@ -13,8 +13,8 @@
- type: CP14ReligionAltar
religion: Lumera
- type: CP14ReligionObserver
observation:
Lumera: 10
religion: Lumera
radius: 15
- type: SpawnPoint
job_id: CP14GodLumera
@@ -31,7 +31,7 @@
# - type: CP14ReligionAltar
# religion: Merkas
# - type: CP14ReligionObserver
# observation:
# Merkas: 10
# religion: Merkas
# radius: 10
# - type: SpawnPoint
# job_id: CP14GodMerkas

View File

@@ -1,11 +1,17 @@
uniform sampler2D SCREEN_TEXTURE;
uniform lowp int count;
uniform highp vec2[64] position;
uniform highp float[64] radius;
uniform lowp int anticount;
uniform highp vec2[64] antiposition;
uniform highp float[64] antiradius;
uniform highp vec2 renderScale;
uniform highp vec3 shaderColor;
const highp float radiusFalloff = 60.0;
const highp float radiusFalloff = 20.0;
const highp float noiseScale = 6;
const highp float timeScale = 0.1;
@@ -34,51 +40,67 @@ highp vec3 gradientColor(highp float t) {
}
}
void fragment() {
highp vec2 fragPos = FRAGCOORD.xy;
highp float calculateVisibilityAtPoint(highp vec2 fragPos) {
highp float visibility = 0.0;
highp float edgeHighlight = 0.0;
highp float scale = renderScale.x;
// Видимость (раскрытие)
for (lowp int i = 0; i < 64 && i < count; i++) {
highp float scaledRadius = radius[i] * 33.0 * scale;
highp float dist = length(fragPos - position[i]);
highp float fade = smoothstep(scaledRadius - radiusFalloff * scale, scaledRadius, dist);
visibility = max(visibility, 1.0 - fade);
}
// Антивидимость (перекрытие)
for (lowp int i = 0; i < 64 && i < anticount; i++) {
highp float scaledRadius = antiradius[i] * 33.0 * scale;
highp float dist = length(fragPos - antiposition[i]);
highp float fade = smoothstep(scaledRadius - radiusFalloff * scale, scaledRadius, dist);
highp float antiVis = 1.0 - fade;
// Учитываем антивидимость как маску
visibility = min(visibility, fade); // fade → 1 в центре антизоны (нулевая видимость)
}
return visibility;
}
highp float calculateEdgeHighlight(highp float visibility) {
if (visibility >= 0.99 || visibility <= 0.0)
return 0.0;
return visibility;
}
void fragment() {
highp vec2 fragPos = FRAGCOORD.xy;
highp vec2 uv = fragPos * SCREEN_PIXEL_SIZE;
highp float t = TIME * timeScale;
highp vec2 noiseUV = uv * noiseScale + vec2(t);
// Цветовая маска (по шуму)
highp float n = noise(noiseUV);
highp vec3 colorOverlay = gradientColor(n);
// Искажение позиции для эффекта "миража"
highp float nX = noise(noiseUV + vec2(3.1, 7.7));
highp float nY = noise(noiseUV + vec2(9.3, 2.4));
highp vec2 distortion = vec2(nX - 0.5, nY - 0.5) * 15.0 * scale;
highp vec2 distortion = vec2(nX - 0.5, nY - 0.5) * 15.0 * renderScale.x;
for (lowp int i = 0; i < 64 && i < count; i++) {
highp float scaledRadius = radius[i] * 33.0 * scale;
highp float dist = length((fragPos + distortion) - position[i]);
highp float fade = smoothstep(scaledRadius - radiusFalloff * scale, scaledRadius, dist);
visibility = max(visibility, 1.0 - fade);
// Узкая кромка по расстоянию до радиуса
highp float offset = -60;
highp float edgeWidth = 8.0 * scale; // ширина кромки в пикселях
highp float edgeDist = abs(dist - (scaledRadius + offset)) * (1 / scale);
highp float edge = smoothstep(edgeWidth, 0.0, edgeDist);
edgeHighlight = max(edgeHighlight, edge);
}
highp float visibility = calculateVisibilityAtPoint(fragPos + distortion);
highp float edgeHighlight = calculateEdgeHighlight(visibility);
highp vec4 baseColor = zTextureSpec(SCREEN_TEXTURE, fragPos * SCREEN_PIXEL_SIZE);
highp vec4 baseColor = zTextureSpec(SCREEN_TEXTURE, uv);
highp vec3 finalColor = mix(colorOverlay, baseColor.rgb, visibility);
// Наложим бордовую кромку
finalColor = mix(
finalColor,
shaderColor,
edgeHighlight * step(0.5, 1.0 - visibility)
);
finalColor = mix(finalColor, shaderColor, edgeHighlight);
highp float minAlpha = 1;
COLOR = vec4(finalColor, mix(minAlpha, 1.0, visibility));
highp float minAlpha = 1.0;
COLOR = vec4(finalColor, mix(minAlpha, 1.0, edgeHighlight));
}