Update Healer Man server

This commit is contained in:
phenom committed 2026-09-25 21:37:10 -04:00
1 parent 48a678dcc5
commit 9c5fb4c614
18 files changed
+397 -46

No files matched your search

+60
View File
@@ -10,6 +10,9 @@ import {
} from "three";
import { useShellStore } from "../app/shellStore";
import { useCombatStore } from "../game/combatStore";
import { getMobPosition } from "../game/mobRuntimeRegistry";
import { getPartyRuntimePosition } from "../game/partyRuntimeRegistry";
import { usePartyStore } from "../game/partyStore";
import {
combatEffectStyle,
preloadCombatEffectManifest,
@@ -51,6 +54,55 @@ const QUALITY_PARTICLE_COUNT: Readonly<Record<Exclude<CombatVfxQuality, "off">,
const COMBAT_EFFECT_DATASET_KEY = "combatEffectLoad";
/** The imported hand particles do not include Chain of Light's sustained target link. */
function ChainOfLightBeam({ actorId }: { readonly actorId?: string }) {
const beamRef = useRef<Group>(null);
const source = useMemo(() => new Vector3(), []);
const target = useMemo(() => new Vector3(), []);
const direction = useMemo(() => new Vector3(), []);
const up = useMemo(() => new Vector3(0, 1, 0), []);
useFrame(() => {
const beam = beamRef.current;
if (!beam) return;
const state = useCombatStore.getState();
const member = actorId ? usePartyStore.getState().members.find((entry) => entry.id === actorId) : null;
const cast = actorId ? member?.activeCast : state.activeCast;
const targetId = actorId ? member?.activeCastTargetId : state.activeCast?.targetId;
const origin = actorId ? getPartyRuntimePosition(actorId) : state.playerPosition;
const position = targetId ? getMobPosition(targetId) : null;
if (cast?.abilityId !== "rom-priest-primary-9" || !origin || !position) {
beam.visible = false;
return;
}
source.set(origin[0], origin[1] + 1.35, origin[2]);
target.set(position[0], position[1] + 1.15, position[2]);
direction.subVectors(target, source);
const length = direction.length();
if (length < 0.1) {
beam.visible = false;
return;
}
beam.visible = true;
beam.position.copy(source).add(target).multiplyScalar(0.5);
beam.quaternion.setFromUnitVectors(up, direction.multiplyScalar(1 / length));
beam.scale.set(1, length, 1);
});
return (
<group ref={beamRef} visible={false}>
<mesh>
<cylinderGeometry args={[0.14, 0.14, 1, 10]} />
<meshBasicMaterial color="#ffe57c" transparent opacity={0.28} depthWrite={false} blending={AdditiveBlending} />
</mesh>
<mesh>
<cylinderGeometry args={[0.055, 0.055, 1, 8]} />
<meshBasicMaterial color="#fff9d6" transparent opacity={0.85} depthWrite={false} blending={AdditiveBlending} />
</mesh>
</group>
);
}
function effectDurationMs(event: CombatPresentationEvent): number {
if (event.phase === "cast-start") return Math.max(280, Math.min(4_500, event.durationMs ?? 900));
if (event.phase === "tick") return 320;
@@ -250,6 +302,10 @@ const CombatEffectPrimitive = memo(function CombatEffectPrimitive({
export function CombatEffects({ active, safeGraphics = false }: CombatEffectsProps) {
const settings = useShellStore((state) => state.combatPresentationSettings);
const masterVolume = useCombatStore((state) => state.settings.masterVolume);
const chainOfLightChanneling = useCombatStore((state) => state.activeCast?.abilityId === "rom-priest-primary-9");
const partyChainCasterIds = usePartyStore((state) => state.members
.filter((member) => member.activeCast?.abilityId === "rom-priest-primary-9")
.map((member) => member.id).join("\u0000"));
const [effects, setEffects] = useState<readonly ActiveCombatEffect[]>([]);
const settingsRef = useRef(settings);
const activeRef = useRef(active);
@@ -362,6 +418,10 @@ export function CombatEffects({ active, safeGraphics = false }: CombatEffectsPro
if (effectiveQuality === "off") return null;
return (
<group name="combat-effects">
{active && settings.vfxEnabled.player && chainOfLightChanneling ? <ChainOfLightBeam /> : null}
{active && settings.vfxEnabled.party && partyChainCasterIds
? partyChainCasterIds.split("\u0000").map((actorId) => <ChainOfLightBeam key={actorId} actorId={actorId} />)
: null}
{effects.map((effect) => (
effect.originalOnly && vfxMode !== "original" ? null :
vfxMode === "original" ? (