Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
8d63ff1
Add NightGlow to scale glow effects by ambient light
jaymar921 Oct 29, 2026
147ea4b
Test the night glow strength curve
jaymar921 Oct 29, 2026
212710c
Add a client config with the nightGlow toggle
jaymar921 Oct 29, 2026
ad42eb8
Add options for the glow particle
jaymar921 Oct 29, 2026
975fa99
Register the mcheli:glow particle type
jaymar921 Oct 29, 2026
b9ff32f
Add the soft disc sprite used by glow effects
jaymar921 Oct 29, 2026
8017b4c
Define the glow particle sprite
jaymar921 Oct 29, 2026
5eb4630
Add additive glow pipelines, halos and tracer ribbons
jaymar921 Oct 29, 2026
8b1f2dc
Add the glow particle for flashes, fire and embers
jaymar921 Oct 29, 2026
de39e05
Expose whether a flare is a smoke grenade
jaymar921 Oct 29, 2026
8e65c30
Render a glow around burning flares
jaymar921 Oct 29, 2026
ae287d4
Draw tracers for rounds and a glowing motor for rockets and missiles
jaymar921 Oct 29, 2026
d8a44ac
Add a flash, embers and lingering crash fire glow to explosions
jaymar921 Oct 29, 2026
5ba4e1c
Register the client config and particle types
jaymar921 Oct 29, 2026
58ba24e
Register the glow particle provider and the flare renderer
jaymar921 Oct 29, 2026
39baa46
Ship the glow particle and its sprite in the jar
jaymar921 Oct 29, 2026
f9ad881
Note night glow in the changelog
jaymar921 Oct 29, 2026
65f5ea5
Document the client config in the README
jaymar921 Oct 29, 2026
399245f
Describe night glow in the architecture document
jaymar921 Oct 29, 2026
b525e3a
List the glow assets among the files the jar may hold
jaymar921 Oct 29, 2026
b043164
Add night glow to phase 6 of the roadmap
jaymar921 Oct 29, 2026
f1a3973
Add a phase 6 checklist for night glow
jaymar921 Oct 29, 2026
25c585c
Drop the hurt overlay helper
jaymar921 Oct 29, 2026
0bb6682
Drop hurt time from the vehicle render state
jaymar921 Oct 29, 2026
0aedb91
Stop vehicles flashing red when hit
jaymar921 Oct 29, 2026
34e92cb
Stop hits from snapping a driven vehicle's motion
jaymar921 Oct 29, 2026
995deed
Do not push driven vehicles in explosions and push empty ones less
jaymar921 Oct 29, 2026
cfbbfda
Note the hit flash and knockback changes in the changelog
jaymar921 Oct 29, 2026
c57367a
Explain why vehicle hits skip markHurt and knockback
jaymar921 Oct 29, 2026
76923cc
Add vehicle hit checks to the phase 6 checklist
jaymar921 Oct 29, 2026
94f0b5e
Keep wrecks burning for 30 seconds until they despawn
jaymar921 Oct 29, 2026
4580780
Add a night SA-2 intercept screenshot
jaymar921 Oct 29, 2026
9ea5790
Add a night helicopter crash screenshot
jaymar921 Oct 29, 2026
0b6932e
Add a screenshot of glowing crash debris at night
jaymar921 Oct 29, 2026
69783c7
Add a torpedo run screenshot at dusk
jaymar921 Oct 29, 2026
8dd56f7
Add a patrol boat cook-off screenshot at dusk
jaymar921 Oct 29, 2026
14dcc81
Add a screenshot of glowing debris over a sinking boat
jaymar921 Oct 29, 2026
5463431
Show the phase 6 night glow screenshots in the README
jaymar921 Oct 29, 2026
cea6178
Note burning wrecks and the new screenshots in the changelog
jaymar921 Oct 29, 2026
ecf4eed
Describe the 30 second burning wreck
jaymar921 Oct 29, 2026
f824cf9
Mention the burning wreck in the phase 6 roadmap
jaymar921 Oct 29, 2026
eb50e36
Add the burning wreck to the phase 6 checklist
jaymar921 Oct 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,25 @@ All notable changes to this project are documented here. The format follows
[Semantic Versioning](https://semver.org/). Pre-releases (`1.0.0-alpha.N`) mark the phases on the
way to 1.0.0; see [docs/ROADMAP.md](docs/ROADMAP.md).

## [Unreleased]

### Added

- Night glow: rounds trail a tracer, rockets and missiles burn with a glowing motor, burning
flares light up around them, and every explosion throws a flash of light and glowing embers.
A crashing vehicle goes up in a bigger flash and keeps burning with a glow for several seconds.
The glow is faint by day and strongest at night, in caves and under water. Turn it off with
`effects.nightGlow` in the new client config, `config/mcheli-client.toml`.
- Wrecks stay for 30 seconds instead of 25 and keep burning until they disappear: flames, a fire
glow and rising embers that die down over the last five seconds. Sunk wrecks do not burn.
- Screenshots of night glow in `docs/images/phase-6`, shown in the README.

### Changed

- Vehicles no longer flash red when hit.
- Hits no longer jolt a vehicle that someone is flying or driving, and explosions no longer push
it. An empty vehicle still rocks a little from a nearby blast (a quarter of the old push).

## [1.1.0-beta.1] - 2026-10-29

Phase 5 of the road to 1.1.0: the groundwork that content packs, older Minecraft versions and
Expand Down
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,18 @@ Phase 4 clips (click a picture to open the video):

## Screenshots

Phase 6 in a development build (`1.1.0-beta.2`): night glow. Tracers, rocket motors, flares and
explosions light up the dark, and a wreck keeps burning until it disappears.

| | |
|---|---|
| ![An SA-2 closing on a helicopter in the night sky, its motor glowing](docs/images/phase-6/sa-2-night-intercept.png) | ![The shot down helicopter exploding on a hilltop at night in a bright flash](docs/images/phase-6/helicopter-crash-night.png) |
| An SA-2 intercept at night | The helicopter goes up in a flash of light |
| ![Glowing embers and burning debris arcing over the burning wreck at night](docs/images/phase-6/crash-embers-night.png) | ![A torpedo running towards a patrol boat at dusk](docs/images/phase-6/torpedo-run-dusk.png) |
| Burning debris arcs over the wreck | A torpedo run at dusk |
| ![The patrol boat cooking off in a fireball against the sunset](docs/images/phase-6/patrol-boat-cook-off-dusk.png) | ![Glowing debris raining down around the sinking patrol boat](docs/images/phase-6/burning-debris-dusk.png) |
| The patrol boat cooks off | Glowing debris rains down as it sinks |

Phase 5 in a development build (`1.1.0-beta.1`): a patrol boat hit by a torpedo cooks off and
sinks.

Expand Down Expand Up @@ -224,6 +236,12 @@ server runs):
| `vehicles.disabled` | `[]` | Vehicles that cannot be placed, by name, such as `["ah-64", "f-35b"]` |
| `weapons.infiniteAmmo` | `false` | Vehicle weapons never run out of ammunition |

Each player also has `config/mcheli-client.toml`, which only changes what that player sees:

| Setting | Default | Meaning |
|---------|---------|---------|
| `effects.nightGlow` | `true` | Tracers, rocket motors, flares and explosions glow: faintly by day, brightly at night, in caves and under water |

## Documentation

* [Changelog](CHANGELOG.md)
Expand Down
30 changes: 30 additions & 0 deletions common/src/main/java/com/jnhprojects/mcheli/effect/NightGlow.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.jnhprojects.mcheli.effect;

/**
* Not in the original: how strongly tracers, rocket motors and fireballs glow, from the light
* around them. In daylight the glow is a faint shimmer on top of the model; at night, in a cave or
* under water it is what you see from far away. Light levels are Minecraft's 0 to 15 scale.
*/
public final class NightGlow {
/** The glow left at full daylight, so a tracer still catches the eye against a dark hillside. */
public static final float DAYLIGHT = 0.25F;

private static final int MAX_LIGHT = 15;

private NightGlow() {
}

/**
* @param blockLight light from torches, lava and the like at the glowing thing
* @param skyLight sky light at the same spot, before the time of day is applied
* @param skyDarken how far the sky has dimmed: 0 at noon, 11 at midnight, more in a storm
* @return the glow strength, {@link #DAYLIGHT} in full light up to 1 in darkness
*/
public static float strength(int blockLight, int skyLight, int skyDarken) {
int ambient = Math.clamp(Math.max(blockLight, skyLight - skyDarken), 0, MAX_LIGHT);
float dark = 1.0F - (float) ambient / MAX_LIGHT;
// Smoothstep, so dusk fades the glow in rather than switching it on at one light level.
float eased = dark * dark * (3.0F - 2.0F * dark);
return DAYLIGHT + (1.0F - DAYLIGHT) * eased;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.jnhprojects.mcheli.effect;

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

class NightGlowTest {
private static final float EPSILON = 1.0E-6F;

@Test
void middayLeavesOnlyTheDaylightShimmer() {
assertEquals(NightGlow.DAYLIGHT, NightGlow.strength(0, 15, 0), EPSILON);
}

@Test
void pitchDarkGlowsInFull() {
assertEquals(1.0F, NightGlow.strength(0, 0, 0), EPSILON);
}

@Test
void midnightGlowsFarMoreThanNoon() {
float midnight = NightGlow.strength(0, 15, 11);
assertTrue(midnight > 0.8F, "midnight glow " + midnight);
}

@Test
void aTorchNearbyDimsTheGlowAtNight() {
assertTrue(NightGlow.strength(14, 15, 11) < NightGlow.strength(0, 15, 11));
}

@Test
void dimmingIsGradualAndNeverGoesBackUp() {
float previous = 0.0F;
for (int darken = 0; darken <= 15; darken++) {
float s = NightGlow.strength(0, 15, darken);
assertTrue(s >= previous, "glow dropped at sky darken " + darken);
assertTrue(s - previous < 0.2F || darken == 0, "glow jumped at sky darken " + darken);
previous = s;
}
}

@Test
void outOfRangeLightIsClamped() {
assertEquals(NightGlow.DAYLIGHT, NightGlow.strength(40, 40, 0), EPSILON);
assertEquals(1.0F, NightGlow.strength(-3, 0, 20), EPSILON);
}
}
28 changes: 25 additions & 3 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,16 @@ This document explains how MCHeli Rework is organized and why. It reflects the c
│ entity AircraftEntity and one subclass per kind │ (forge-26.3)
│ item AircraftItem, VehicleIngredient │
│ network PilotStatePacket, CrewActionPacket, ... │ Minecraft + Forge
│ registry entities, items, components, creative tabs │
│ (root) MCHeli, ServerConfig, McheliCommand, reload │
│ particle GlowParticleOptions │
│ registry entities, items, components, particles, tabs │
│ (root) MCHeli, configs, McheliCommand, reload │
├────────────────────────────────────────────────────────────────┤
│ content Definition, DefinitionReader, ContentRegistry, │ common module
│ aircraft/* definitions, recipe conversion │
│ model Mesh, MqoLoader, ObjLoader │ pure Java
│ hud HudScript, Expression, HudVariable │
│ physics AircraftRotation, GroundImpact │
│ effect NightGlow │
└────────────────────────────────────────────────────────────────┘
```

Expand All @@ -55,8 +57,10 @@ MCHeli constructor
├─ LegacyContent.registerPacks that folder as a required resource + data pack
├─ ContentRegistry.load(mcheli/imported/assets/mcheli) parse helicopters, planes, tanks, vehicles
├─ registerConfig(SERVER, ServerConfig.SPEC) serverconfig/mcheli-server.toml
├─ registerConfig(CLIENT, ClientConfig.SPEC) config/mcheli-client.toml
├─ ModDataComponents.register mcheli:vehicle
├─ ModEntities.register one entity type per kind
├─ ModParticles.register mcheli:glow
├─ ModItems.register(content) one item per kind, plus the 1.0.0 items
├─ ModCreativeTabs.register
├─ ModRecipes.register mcheli:vehicle ingredient, recipe book
Expand Down Expand Up @@ -309,7 +313,15 @@ only the two option values (a locked entity id and a fire mode) and repeats ever
Damage enters through `AircraftEntity.hurtServer`, a port of `attackEntityFrom`. Projectile rays
are clipped against the main hit box and every `BoundingBox` (`clipHitBoxes`); the box struck
first lends its damage factor to the armour calculation. A vehicle at zero hit points is destroyed:
it burns, spins down or falls, explodes on impact and despawns after 500 ticks.
it burns, spins down or falls, explodes on impact and despawns after 600 ticks (30 seconds; the
original used 500). Until then each client keeps the wreck burning (`wreckFire`): flames, a
flickering `mcheli:glow` and embers, dying down over the last 100 ticks it has seen, and nothing
under water.

A hit neither tints the vehicle red nor calls `markHurt`: the vehicle a player drives is simulated
on their client, and resending the server's motion on every hit snapped it back a step. For the
same reason `Explosions` does not push a driven vehicle, and pushes an empty one at a quarter of
the mob knockback.

Impacts are measured from the position each tick, so they work whichever side simulates the
vehicle. Besides the original's tilt and submersion damage, flying into terrain or another vehicle
Expand Down Expand Up @@ -355,6 +367,16 @@ set the packet's `crash` flag for a larger show of our own: the same smoke shell
above the impact, a cluster of fireballs, a climbing plume of signal smoke and burning fuel,
capped at size 12 to keep the particle count in check.

**Night glow** is our own. `Glow` holds two additive pipelines that test depth but never write it
(the lightning blend, so overlapping glows add up and never cut holes in the smoke behind them),
falling back to the vanilla translucent OIT pipelines when Fabulous graphics are on. It draws
camera facing halos and tracer ribbons for `ProjectileRenderer` (rounds and rocket motors) and
`FlareRenderer` (burning flares, including crash debris), and is the layer of the `mcheli:glow`
particle that `ExplosionEffect` spawns for the flash, the embers and the lingering crash fire.
How bright a glow gets comes from `effect.NightGlow` in `common`: the block light, sky light and
the sky darkening at that spot, so it is faint at noon and full at night, in caves and under
water. `ClientConfig`, a Forge `CLIENT` config, turns it off per player.

**CrewController** also hides the first person hand through `RenderHandEvent` wherever the original
did: for the pilot, and for anyone looking through a camera or with a weapon selected.

Expand Down
5 changes: 3 additions & 2 deletions docs/DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,15 +118,16 @@ releases page. See [the release channels](ROADMAP.md#release-channels).

Neither the repository nor the jar may contain EMB4's files. Don't commit anything from the zip
or from `run/mcheli/imported`. As a guard, the `jar` task excludes everything under
`assets/mcheli` except `lang` and the four fallback item models of the generic vehicle items.
`assets/mcheli` except `lang`, the four fallback item models of the generic vehicle items and
the night glow particle (`particles/glow.json`, `textures/particle/glow.png`, both our own).
After a build, check every jar with:

```
for jar in build/release/*.jar; do unzip -l "$jar" | grep -v "com/"; done
```

which should list only the manifest, `mods.toml`, `pack.mcmeta`, the two lang files, the four
generic item models and the damage type data.
generic item models, the glow particle and its texture, and the damage type data.

When the conversion changes, change `content.LegacyImporter` and bump its `FORMAT`, so existing
installs, the dev run and the tests all convert again. Keys the rework adds go into the lang files
Expand Down
2 changes: 2 additions & 0 deletions docs/ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,8 @@ The external content packs from phase 4, in the package format the workshop will
- [ ] Server limits and an allow list in the config; clients cap the cache size
- [ ] Pack author guide in `docs/content/packs.md`, with a small example pack in the repo made
from our own assets (no EMB4 files)
- [x] Night glow: tracers, rocket motors, flares and explosions glow, faint by day and bright at
night; a crash keeps burning with a glow and the wreck burns for 30 seconds. Client config `effects.nightGlow` turns it off.
- [ ] Crash spin for shot down aircraft: planes and jets roll into a nose-down spiral instead of
just falling, and helicopters spin faster with some wobble. The spin direction comes from
the entity id so every client sees the same thing.
Expand Down
30 changes: 30 additions & 0 deletions docs/TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,36 @@ Keep a copy of a 1.0.0 world with vehicles parked in it, items in the hotbar and
- [ ] `./gradlew build` produces `build/release/mcheli-26.3-1.1.0-beta.1.jar`; it holds no EMB4
file (DEVELOPMENT.md, "Original content").

## Phase 6 checklist (`1.1.0-beta.2`)

Run each section in single player and again on a dedicated server, unless it says otherwise.

### Night glow

Set the time with `/time set midnight` and compare with `/time set noon`.

- [ ] Machine gun rounds trail a warm tracer at night; at noon only a faint shimmer shows.
- [ ] Rockets and missiles show a glowing motor behind them for their whole flight.
- [ ] A flare salvo lights up around each flare; smoke grenades do not glow.
- [ ] A rocket hit gives a short bright flash and a few embers; the smoke behind stays visible
through the flash (no dark square or cut out).
- [ ] Shooting down a helicopter: a big flash when it hits the ground, then the wreck glows for
several seconds and the embers thrown out glow as they fall.
- [ ] The wreck keeps burning (flames, glow, rising embers) for 30 seconds, dies down over the
last five and then disappears. A boat wreck that sank does not glow under water.
- [ ] In a cave or under a roof at noon, the glow is as bright as at night.
- [ ] Nothing glows under water (torpedoes, rounds fired into water).
- [ ] `effects.nightGlow = false` in `config/mcheli-client.toml`: no glow at all, the rest of the
effects unchanged. Another player on the same server still sees the glow.
- [ ] Video settings, graphics set to Fabulous: the glow still draws and nothing crashes.

### Vehicle hits

- [ ] A vehicle hit by rounds or a rocket no longer flashes red.
- [ ] Flying a plane or helicopter while another player (or a turret) shoots it: no jolt or
stutter on each hit; the HP still goes down. Same driving a tank.
- [ ] A rocket exploding next to an empty parked vehicle rocks it slightly, much less than before.

## Reporting problems

Attach `logs/latest.log` (and the crash report, if any) with the helicopter name, what you did
Expand Down
Binary file added docs/images/phase-6/burning-debris-dusk.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/phase-6/crash-embers-night.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/phase-6/helicopter-crash-night.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/phase-6/patrol-boat-cook-off-dusk.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/phase-6/sa-2-night-intercept.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/phase-6/torpedo-run-dusk.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 3 additions & 1 deletion forge-26.3/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,9 @@ tasks.named('jar', Jar) {
// zip (LegacyContent). The repository no longer holds it, but the jar is packed from
// build/sourceSets/main, which can still have copies from older builds and from the IDE.
def shipped = ['assets/mcheli/items/helicopter.json', 'assets/mcheli/items/plane.json',
'assets/mcheli/items/tank.json', 'assets/mcheli/items/vehicle.json']
'assets/mcheli/items/tank.json', 'assets/mcheli/items/vehicle.json',
// Our own: the night glow sprite (generated, no EMB4 content) and its particle definition.
'assets/mcheli/particles/glow.json', 'assets/mcheli/textures/particle/glow.png']
exclude { FileTreeElement element ->
String path = element.relativePath.pathString
!element.directory && path.startsWith('assets/mcheli/') && !path.startsWith('assets/mcheli/lang/') &&
Expand Down
33 changes: 33 additions & 0 deletions forge-26.3/src/main/java/com/jnhprojects/mcheli/ClientConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.jnhprojects.mcheli;

import net.minecraftforge.common.ForgeConfigSpec;

/**
* Per player look and feel, in {@code config/mcheli-client.toml}. Nothing here changes how the
* game plays, so each player picks for themselves and the server never sees it.
*/
public final class ClientConfig {
public static final ForgeConfigSpec SPEC;

private static final ForgeConfigSpec.BooleanValue NIGHT_GLOW;

static {
ForgeConfigSpec.Builder builder = new ForgeConfigSpec.Builder();

builder.push("effects");
NIGHT_GLOW = builder
.comment("Whether tracers, rocket motors, flares and explosions glow. The glow is faint by day",
"and strongest at night, in caves and under water.")
.define("nightGlow", true);
builder.pop();

SPEC = builder.build();
}

private ClientConfig() {
}

public static boolean nightGlow() {
return SPEC.isLoaded() ? NIGHT_GLOW.get() : NIGHT_GLOW.getDefault();
}
}
3 changes: 3 additions & 0 deletions forge-26.3/src/main/java/com/jnhprojects/mcheli/MCHeli.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.jnhprojects.mcheli.registry.ModDataComponents;
import com.jnhprojects.mcheli.registry.ModEntities;
import com.jnhprojects.mcheli.registry.ModItems;
import com.jnhprojects.mcheli.registry.ModParticles;
import com.jnhprojects.mcheli.registry.ModRecipes;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.eventbus.api.bus.BusGroup;
Expand Down Expand Up @@ -37,8 +38,10 @@ public MCHeli(FMLJavaModLoadingContext context) {
legacyContent.registerPacks();
content = loadContent(legacyContent.assets());
context.registerConfig(ModConfig.Type.SERVER, ServerConfig.SPEC);
context.registerConfig(ModConfig.Type.CLIENT, ClientConfig.SPEC);
ModDataComponents.register(modBus);
ModEntities.register(modBus);
ModParticles.register(modBus);
ModItems.register(modBus, content);
ModCreativeTabs.register(modBus);
ModRecipes.register(modBus);
Expand Down
Loading
Loading