Skip to content

Only shake the camera for a nearby source - #579

Open
nyyhao wants to merge 1 commit into
sven-n:mainfrom
nyyhao:earthquake-distance-falloff
Open

Only shake the camera for a nearby source#579
nyyhao wants to merge 1 commit into
sven-n:mainfrom
nyyhao:earthquake-distance-falloff

Conversation

@nyyhao

@nyyhao nyyhao commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

What I saw

Playing in Lorencia, walking around town, the camera would start juddering
hard — like a constant earthquake — for a few seconds, stop, then start again a
little later somewhere else in the city. It was bad enough to be unplayable in
places, and it didn't happen out on the hunting maps.

Setup was the MuMain client on macOS against a local OpenMU server with its
bot population enabled (50 bot characters), which is what made it so obvious:
bots warp into Lorencia to shop and fight, so there was almost always somebody
casting something nearby.

Tracking it down

I logged EarthQuake where DefaultCamera applies it, along with the hero's
tile. It fired around tiles (139–144, 131–137) with values between -0.6 and
-0.1 in 0.1 steps. That range identifies the source exactly — it's
(rand() % 6 - 6) * 0.1f in Move_MODEL_CIRCLE_LIGHT, and across 88 samples I
never saw -0.7 or -0.8, which rules out the similar-looking
(rand() % 8 - 8) * 0.1f sites in ZzzEffect.cpp.

Working back from there: MODEL_CIRCLE_LIGHT at SubType 0 comes from
AT_SKILL_HELL_FIRE in ZzzCharacter.cpp. So it was other players' Hellfire
casts. The handler sets EarthQuake on every frame the effect is alive, and
EarthQuake is a single global that DefaultCamera adds straight onto the
camera pitch — with no distance test anywhere in that path. A caster shakes
every nearby player's view at full strength no matter how far away they are,
including sources off-screen entirely.

Decay is EarthQuake *= 0.2f once per frame in InitializeSceneFrame, so it
dies quickly on its own; the problem is purely that something in scope keeps
re-setting it.

The change

SetEarthQuakeAt(), declared next to the global it writes, applies the shake
only when its source is within about twelve tiles — roughly a screen — and
scales it linearly to zero at that edge. Magnitude at zero distance is
unchanged, so an effect feels exactly the same to whoever is standing in it;
what stops is strangers across the map shaking your camera.

Routed the seventeen object-driven sites in ZzzEffect, EffectBehaviors,
MoveHandlers and GOBoid through it. The scripted map-wide shakes in
Raklion, Hellas, Kanturu, Chaos Castle and Cursed Temple are deliberately left
alone — those are meant to shake the whole map during an event, and several
have no local object to measure a distance from.

On whether you want this

This is a behaviour change, not a port fix, so it's entirely your call. The
argument for it is that a screen shake from a caster you can't see is hard to
defend on any reading. The argument against is that it's long-standing
behaviour and someone may consider the current feel correct — it's only really
punishing when a map is busy, which a bot-populated test server exaggerates far
beyond a normal server.

Happy to drop it, narrow it to fewer effects, or make the radius configurable
if you'd rather.

Also noticed, deliberately not changed

(rand() % 6 - 6) * 0.1f yields -0.6 … -0.1 — always negative, never zero or
positive. So it isn't a shake around a centre, it's a persistent downward pitch
with jitter on top, and every one of my 88 samples was negative. Several other
sites share the shape (% 8 - 8, % 4 - 4). It looks like an off-by-one, but
fixing it would change how every one of those effects looks, so I left it well
alone.

Testing

macOS 26.5.1 / arm64, GL 3.3 core, against OpenMU with 50 bots. No Windows or
Linux toolchain here, so those builds are unverified — this touches shared
files rather than sitting behind a platform guard.

EarthQuake is a single global that DefaultCamera adds directly onto the camera
pitch, and the effect handlers wrote to it unconditionally. Any object running
one of those handlers therefore shook every nearby player's view at full
strength, regardless of distance and including sources off-screen.

It shows up most clearly with Hellfire (AT_SKILL_HELL_FIRE), whose
MODEL_CIRCLE_LIGHT handler sets EarthQuake on every frame the effect is alive.
On a populated map a couple of casters anywhere in scope is enough to make the
camera judder continuously, which reads as an engine fault rather than as
something happening in the world. It is easy to miss with a handful of players
around and obvious with a busy town.

Add SetEarthQuakeAt(), next to the global it writes, which applies the shake
only when its source is within about twelve tiles - roughly a screen - and
scales it linearly to zero at that edge. Magnitude at zero distance is
unchanged, so an effect still feels the same to whoever is standing in it.

Routed the seventeen object-driven sites in ZzzEffect, EffectBehaviors,
MoveHandlers and GOBoid through it. The scripted map-wide shakes in Raklion,
Hellas, Kanturu, Chaos Castle and Cursed Temple are deliberately left alone:
those are meant to shake the whole map during an event, and several have no
local object to measure a distance from.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant