5110 Commits
Author SHA1 Message Date
KAMKEEL f0a7a2e698 Write the quest counters the shape the quest classes read
PlayerFixtures wrote extraData.setInteger("Zombie", 14) and its note
claimed that was where kill counts live. QuestKill.getKilled reads
extraData.getTagList("Killed", 10) through NBTTags.getStringIntegerMap
-- a list of Slot/Value compounds. extraData is an opaque compound the
fixture filled by hand, so nothing on either side checked it, and two
of Scribe's migration tests stood on the invented shape for two months.

It writes the real one now, plus the location booleans and a party
quest's group total beside one member's own <playerName>Killed share.

Also a deliberate unreadable case: a quest whose extraData an addon
wrote, with keys no objective kind claims. That case existed by
accident before, as this generator's own defect, and a reader has to
report it rather than drop it.

The counter map is built by hand rather than through NBTTags, so the
fixture states the shape it is asserting -- borrowing the reader's own
helper would agree with it by construction and prove nothing.
2026-08-26 04:15:16 -04:00
KAMKEEL 30f65ca6e0 A shared party dialog objective is always complete
QuestDialog.isPartyCompleted's shared branch builds readValues by recording
only true: a dialog nobody has read is never put in the map at all. The check
underneath returns false only on finding a false, so it never can, and an
empty map returns true besides.

The result is that a party on a shared dialog objective has finished it before
anyone speaks to anybody, and a quest waiting on that objective can be handed
in immediately.

Every required dialog now starts unread, which is the line that was missing.
The loop below it was already right and had nothing to find.

Only the shared mode is affected. All and Leader iterate the dialogs directly
and return false on the first unread one.
2026-08-18 15:23:51 -04:00
KAMKEEL d6d6111803 Merge dev into scribe
Brings in the 1.11.2 version bump and the removal of the shutdown flush that did
not compile. The scribe branch was forked from 8612d444 to avoid that break, so
this is the first point at which it can track dev's tip again.
2026-08-14 00:32:42 -04:00
KAMKEEL 517ccb09c3 Drop the shutdown flush that does not compile
The FMLServerStoppingEvent handler added in 1.11.2-beta1 calls
PlayerData.flushEffects() and PlayerData.save(boolean), and neither method
exists -- so the mod has not compiled since that commit.

Removed along with the four imports the same commit added for it:
FMLServerStoppingEvent, EntityPlayer, PlayerData and ArrayList. Nothing else in
the class used them, so CustomNpcs.java now differs from 8612d444 only by the
version bump.

The problem it was written for is real -- effects live in memory, and logout is
not guaranteed to fire during shutdown -- but the flush needs methods that have
to be written first.
2026-08-14 00:32:35 -04:00
KAMKEEL b37f57966d Keep the readable renderings out of the world directories
A generated <world>/ is meant to drop straight into a save, and Scribe's smoke
harness copies a data set's contents into a server's world wholesale -- so
anything beside customnpcs/ would be copied in too. The NBTJsonUtil renderings
move to _readable/<world>/ at the output root, which makes each world directory
usable as a cnpc_data_sets overlay with no filtering step.
2026-08-14 00:21:31 -04:00
KAMKEEL 3fc15091fa Cover quests, effects, player records and the world documents
Extends the generator from dialogs and factions to most of what a CustomNPC+
world holds: 146 files where there were 70.

Quests walk every axis -- all six types, all eight repeat modes, both completion
modes, the nine reward slots, the party and profile blocks in both their written
and their omitted form, and each objective kind in depth. Worth recording that a
quest has no availability at all: Quest.readNBTPartial calls
VersionCompatibility.CheckAvailabilityCompatibility, whose name says otherwise,
but that is a generic fill-in-missing-keys pass applied to every ICompatibilty.
Quest declares no Availability field, writeToNBTPartial emits no Availability*
key, and neither does the 1.20 line. Gating belongs to the dialog that offers the
quest.

Custom effects turned out to persist in two places: customeffects.dat is only an
index of {Name, ID}, and each effect is a file under customeffects/ whose
FILENAME is its name -- loadEffectsFromDir overwrites the name key after reading,
so the two can disagree and the file wins. There is a fixture for that, one for
an effect with no name at all (absent from the index, and its file is ".json"),
and one whose script crosses 65535 chars so ScriptContainer splits it across
Script plus ExpandedScript0..N. It splits into two parts; the fixture records
that rather than predicting it.

Player records exercise all fifteen sub-writers, including a quest whose
ExtraData holds the per-objective counters -- the quest itself stores only its
targets, so progress lives nowhere else. Written in both containers, since
ConfigMain.DatFormat picks between JSON and gzipped NBT and a server will have
either. PlayerData.getNBT ends at DBCAddon.instance.writeToNBT, so boot now
constructs the shell addon; without the companion mod every method is a no-op,
which is the state a real server is in.

Also global, tags, transport, banks and spawns. Two shapes there are easy to
miss: a spawn entry numbers its variants as SpawnCompound0..N keys rather than a
list, so they must be probed for; and transport stores a bare int dimension
beside a position, including a modded 7265, which is the signature that stopped
being expressible after 1.16.

Tag.uuid defaults to randomUUID(), so every tag fixture pins one. Two full runs
still diff clean across all 146 files.
2026-08-14 00:20:02 -04:00
KAMKEEL 5f49d28f7c Generate CustomNPC+ save data without launching the game
Scribe needs fixtures to test its readers against, and the only fixtures worth
having are ones CustomNPC+ wrote. A hand-authored factions.dat asserts what its
author believed the format to be, so a reader checked against it proves only
that the two agree.

So this drives the shipping data classes' own writeToNBT/writeNBT against a
bootstrapped-but-unlaunched Minecraft. What is copied rather than executed is
only the layout -- which file, under which directory -- because the controllers
reach that through getWorldSaveDirectory(), which returns null with no server,
and every save* fires SyncController into a network stack that is not there.
Each method names the controller it was copied from.

Two obstacles, both worth recording.

Item stacks needed the registry, and populating it goes through Forge:
FMLControlledNamespacedRegistry -> GameData.addPrefix, which asks
Loader.instance() for the active mod's namespace, and constructing a Loader
casts its classloader to LaunchWrapper's. Rather than reimplement the naming
rule, the singleton is filled with an unconstructed Loader so activeModContainer
returns null and addPrefix takes its own "no mod container, assume minecraft"
branch -- the branch real vanilla registration takes anyway. Verified against
minecraft:diamond as it appears in the worlds under run/saves, so the shortcut
is measured rather than assumed.

Second, nothing was reproducible: PlayerMail.writeNBT stamps TimePast from the
wall clock, and a PlayerMail hangs off every dialog and quest whether or not
mail was configured, so all 67 files differed between runs. Pinned to zero,
which is the only key here rewritten after CustomNPC+ produced it, and the count
is printed every run so it cannot spread quietly. Two runs now diff clean.

Branched from 8612d444 rather than dev, because dev's tip does not compile --
1.11.2-beta1 calls PlayerData.flushEffects() and save(boolean), neither of which
exists -- and because that commit is the baseline Scribe pins as specification.

70 files across four worlds: maximal walks every enum and flag, defaults is what
a fresh world gets, edge is legal content readers get wrong, and legacy ages
documents by removing keys to exercise the upgrade path. Dialogs and factions
so far; the remaining systems follow.
2026-08-14 00:10:02 -04:00
KAMKEEL 5403060ee5 1.11.2-beta1 2026-08-13 23:14:01 -04:00
KAMKEEL 8612d4442a Restore the npc step height even if the hitbox nudge throws 2026-08-13 06:06:14 -04:00
KAMKEEL b6f2bae770 Stop the hitbox nudge stepping npcs up when they lie down 2026-08-13 03:02:03 -04:00
KAMKEEL e3a1017770 Remove the hitbox investigation logging 2026-08-11 17:35:19 -04:00
KAMKEEL 7f606fbec2 Disable the hitbox grow nudge while testing 2026-08-11 15:47:50 -04:00
KAMKEEL 3dfcf7764a Log client interpolation state alongside the hitbox probe 2026-08-11 15:38:55 -04:00
KAMKEEL 1a1871f803 Keep the hitbox logging from forcing a chunk load during entity load 2026-08-11 15:16:33 -04:00
KAMKEEL 277c056417 Add temporary hitbox logging for the npc clipping investigation 2026-08-11 15:11:57 -04:00
KAMKEEL c634c4bd35 Revert "Refresh block bounds before reading a collision box so top slabs report the right height"
This reverts commit 715b38e694.
2026-08-11 15:09:59 -04:00
KAMKEEL 715b38e694 Refresh block bounds before reading a collision box so top slabs report the right height 2026-08-11 14:59:09 -04:00
KAMKEEL 9ff4b8ae16 Nudge npcs out of blocks they grow into when the hitbox changes 2026-08-11 12:53:08 -04:00
KAMKEEL e3d1c9b1a7 Apply the entity tracker spawn fix on the integrated server too 2026-08-11 12:29:57 -04:00
KAMKEEL 605907681c Return npcs to their start position when they fall out of the world 2026-08-11 12:29:56 -04:00
KAMKEEL 32c5cc78c7 Stop cloning a linked item from replacing its shared script handler 2026-08-11 12:18:59 -04:00
KAMKEEL 795464fb81 Key the skin overlay packet on UUID so it isn't dropped for unloaded players 2026-08-11 12:18:59 -04:00
KAMKEEL 4a52690d99 Let a script reload clear the errored latch on single-container handlers 2026-08-11 12:18:59 -04:00
KAMKEEL fae2c21893 Fix the script-facing effect apply and remove overloads throwing on every call 2026-08-11 12:18:59 -04:00
KAMKEEL c2ad3ef015 Small Behavior Restorations 2026-08-11 12:00:52 -04:00
KAMKEEL 98c94bd406 Trim comments 2026-08-11 11:32:01 -04:00
KAMKEEL f175e3fb82 Merge pull request #254 from Kirill Epifanov: code cleanup and null-safety fixes 2026-08-11 06:29:18 -04:00
KAMKEEL 67ddaaab6f Keep entity hash consistent with equals, and campfire interaction handled
ScriptEntity.hashCode included tempData while equals compares only the entity,
so two wrappers of the same entity hashed differently - and tempData is mutable
and swapped wholesale by copyTempData, so an entry's hash could change while it
sat in a map.

BlockCampfire.onBlockActivated went from returning true to false, which lets the
held item be used or placed on top of the interaction. Restored.
2026-08-11 06:29:18 -04:00
KAMKEEL 22a8ef5036 Merge branch 'dev' into pr-254
# Conflicts:
#	src/main/java/noppes/npcs/client/gui/GuiNpcMobSpawnerSelector.java
2026-08-11 06:29:18 -04:00
KAMKEEL 2de0795a8e Keep the pBurnItem migration on revision 23
Revision 23 never shipped without the burn flag - it released together with it -
so no saved NPC carries version 23 from a build that lacked pBurnItem. The
migration belongs in the < 23 gate, and bumping ModRev would only force a
redundant pass over every NPC.
2026-08-11 06:07:58 -04:00
KAMKEEL 3193a2bfc3 Merge pull request #290 from postyizhan: register inventory tabs after mod loading completes 2026-08-11 05:56:08 -04:00
KAMKEEL c6e361f8e6 Register inventory tabs from the client proxy, not the mod class
Deferring registration to loadComplete is right - TConstruct needs to have
registered first - but naming TabRegistry and the tab classes in CustomNpcs put
client-only GUI types in a class the server loads, which the verifier resolves
at link time regardless of any runtime side check. A proxy hook keeps them in
ClientProxy while still running at the same point.
2026-08-11 05:56:08 -04:00
KAMKEEL 74511b7449 Merge branch 'dev' into pr-290
# Conflicts:
#	src/api
2026-08-11 05:54:41 -04:00
KAMKEEL 3535e7b819 Merge pull request #293 from Mighty-S0715: reassign colliding dialog IDs instead of discarding 2026-08-11 05:54:14 -04:00
KAMKEEL 179fd4f834 Make ID reassignment safe, and apply it to quests too
The free-ID search only consulted the global map, so it could pick an id the
same category still held further down the iteration - and renameTo silently
overwrites on POSIX, so the rename then destroyed that entry's file. It now
skips ids held by the category and any file already on disk.

Quests had the same discard-on-collision behaviour and now get the same
reassignment, including re-registering shared quests under the new id.
2026-08-11 05:54:14 -04:00
KAMKEEL 1d0b640c61 Merge branch 'dev' into pr-293 2026-08-11 05:52:42 -04:00
KAMKEEL d4b9a18a1a Merge pull request #278 from zeng-github01: fix pEffect legacy compatibility 2026-08-11 05:52:36 -04:00
KAMKEEL fab1062ce8 Bump ModRev so the pBurnItem migration can run
The migration sat in the npcVersion < 17 block, but ModRev has been 23 since
2023 and CheckNpcCompatibility returns early when the save already matches it -
so every NPC affected by the regression was skipped. Moved to a < 24 block and
bumped ModRev to 24 so it actually reaches them.
2026-08-11 05:52:30 -04:00
KAMKEEL 1532ef722c Merge branch 'dev' into pr-278 2026-08-11 05:51:40 -04:00
KAMKEEL caa39d6d5d Merge pull request #283 from zeng-github01: disable auto-open GUI for BlockBorder and BlockNpcRedstone 2026-08-11 05:22:36 -04:00
KAMKEEL a71532f49a Merge pull request #292 from postyizhan: adapt quest tracker height to content 2026-08-11 05:22:35 -04:00
KAMKEEL a028142dda Fix mouse wheel scrolling in the script editors
Mouse.getDWheel() clears the accumulated delta, so whichever widget polled it
first in a frame took it and every later reader saw zero. The script text areas
polled it themselves, after the parent screen had already drained it for its
scroll lists, so the wheel did nothing in the player, NPC and event script GUIs.
The screen now reads the wheel once per frame into the mouseScroll field it
already declared for this, and each widget zeroes it when it consumes it.
2026-08-11 05:08:15 -04:00
KAMKEEL 71bb4b451e Honour the effective windup instead of the raw field
start() and tick() compared against the raw windUpTicks, but when the windup is
synced to an animation the value that applies comes from getWindUpTicks(). An
ability configured that way skipped its windup entirely and fired on the first
tick. The telegraph duration used the raw field too, so it disagreed with the
phase it was meant to cover.
2026-08-11 05:01:50 -04:00
KAMKEEL 677d50fa03 Keep client-only sub-GUI types out of condition and zone descriptors
Same leak as the ability magic editor: @SideOnly strips the annotated method on a
server but not the synthetic lambdas it generates, so ConditionHasEffect,
ConditionQuestCompleted, AbilityTrap and AbilityZone all shipped methods whose
descriptors named a client-only GUI class. Each field is now built in the GUI
class it belongs to.
2026-08-11 05:00:02 -04:00
KAMKEEL 466b49db5e Let the day/night transform actually fire
EntityAITransform claimed the PASSIVE mutex, so any running movement or look
task blocked it from ever starting. The swap only happened in the gaps where
those yielded - which is why it appeared to trigger when an NPC said a line and
for a moment afterwards. The task only polls world time and flips the state, so
it needs no mutex at all.
2026-08-11 04:52:50 -04:00
KAMKEEL dedbe94cd8 Keep client-only sub-GUI types out of Ability's method descriptors
The magic editor field was built with lambdas inside Ability.getAllDefinitions.
subGuiField is generic, so those lambdas took SubGuiAbilityMagic as their
descriptor type. @SideOnly strips the annotated method on a server but not the
synthetic lambdas it generates, so anything reflecting over Ability's declared
methods - Nashorn's adapter generator when a script touches an ability - hit
NoClassDefFoundError. Building the field in the client class keeps the type out
of Ability entirely.
2026-08-11 04:44:14 -04:00
KAMKEEL c79f5998d6 Stop NPC pathfinding fighting Charge's own movement
Charge drives the NPC with applyVelocityFlat but never declared that it does, so
locking movement during the active phase zeroed its motion outright while not
locking left the navigator running against it - no setting worked. It now
declares its own movement and locks both phases, so the navigator stays clear.
2026-08-11 04:24:40 -04:00
KAMKEEL 1acda0e6b0 Scale hazard damage to its interval and floor the interval at 5 ticks
The value is configured, stored and labelled as damage per second, but a pulse
applied it whole, so the real rate was damage per interval - it only matched at
the default interval of 20. A pulse now deals the share of a second it covers.
The interval is floored at 5 ticks, which also bounds how often a zone scans;
this replaces the per-tick membership check, which cost 20x the entity queries.
2026-08-11 04:23:59 -04:00
KAMKEEL 4341c15cf4 Scale melee damage dealt to barriers
Barriers are plain entities, so no LivingHurtEvent fires for them and nothing
scaled an incoming melee hit - vanilla handed over the bare attackDamage
attribute and a dome took one point per swing however strong the attacker was.
Attributes now apply, and modifyBarrierMeleeDamage gives addons the same seam
they already have for barrier health.
2026-08-11 03:43:38 -04:00
KAMKEEL 6477ede022 Make hazard radius and barrier duration accurate
- Hazards only checked who was inside once per damage interval, so the boundary
  where damage started and stopped drifted by however far an entity moved in
  between - several blocks at sprint speed. Membership is now checked every
  tick and damage accrues per entity from the time it actually spends inside.
- Barriers counted elapsed ticks instead of holding a deadline, so every tick
  the entity missed extended it past its configured duration. They now expire
  on an absolute world time like the other energy entities.
2026-08-11 03:43:38 -04:00