Compare commits

...
575 Commits
Author SHA1 Message Date
LexManos 3edefbe33e 1.20.2-pre1 2023-09-08 10:56:10 -07:00
Paint_Ninjaanddhyces 12d24df4f6 Keep order of sources in PackRepository (#9702)
Co-authored-by: dhyces <[email protected]>
2023-09-02 16:21:06 -07:00
Paint_Ninja e69f5e262a Fix DelegatingPackResources searching resource path twice (#9697) 2023-08-30 14:10:17 -07:00
Paint_Ninjaandmalte0811 23cee57d88 Fix Level leak in debug HUD (#9699)
Co-authored-by: malte0811 <[email protected]>
2023-08-30 14:09:51 -07:00
Paint_Ninja ab70bde1d6 Fix PlayerSpawnPhantomsEvent not being fired (#9689) 2023-08-11 10:42:31 -07:00
Meldexun f65381fbff Enhance LivingBreathEvent and LivingDrownEvent. Closes #9680
Also remove 3.5MB of useless data from the installer.
2023-07-24 14:42:24 -07:00
LexManos b680d09359 Partially revert LazyOptional changes, now no longer internally uses weak references. 2023-07-22 08:57:25 -07:00
LexManos 7969daf81a Make LazyOptional's internal references to invalidation listeners use WeakReference, and allow modders to unregister themselves. Closes #8805 2023-07-21 23:46:26 -07:00
LexManos f157567d2a Revert EntityEvent.Size changes to before #9018 was called. (#9679)
Kept newly added methods for binary compatibility but deprecated them all for removal.
The entire pose/eye/size system needs to be reevaluated and address some of Mojang's changes.
However this should fix any bugs that pulling that PR may of caused.
2023-07-21 23:44:01 -07:00
Kovak 93a615e900 Add a config option to restore the calculate all normals behavior in case some setups require old broken behavior. (#9670) 2023-07-19 14:46:05 -07:00
Kovak 7c84249996 Fix rounding errors on models with 45 degree normals by favoring one Direction (#9669)
Should fix flickering issues when breaking custom models and having our vanilla solution disabled.
2023-07-19 14:44:28 -07:00
RealMangoRage 881725c7d5 Moved ForgeHooksClient.onCreativeModeTabBuildContents to ForgeHooks to fix #9662 2023-07-19 11:18:45 -07:00
LexManos 2745531f08 Fix tag removal functionality that broke during the 1.19 update. Closes #9053 and #8949 2023-07-19 10:26:55 -07:00
LexManos bcdd44aece Replace string with forge tag in vanilla recipes. Closes #9062 2023-07-19 00:02:11 -07:00
LexManos bad5b1b5d4 Fix new brain hooks not persisting active activities. 2023-07-18 23:50:27 -07:00
Kovak 30f255d76c Fix breaking overlay flickering on campfires by using vanilla method to calculate block normals. (#9664) 2023-07-17 18:10:12 -07:00
LexManos ebb316a15c Cleanup usages of static imports and build script so that our build doesn't spam useless error like messages.
So that it is easier to see real errors.
Add compatibility checking to standard testing tasks.
2023-07-17 17:26:28 -07:00
LegoMaster3650 e6bb7eecbd Added Action value to PlayerInteractEvent.LeftClickEvent to expose what action fired the event. #9175 2023-07-17 10:57:10 -07:00
Jonathan Colmenares c66f70d6c2 Fix parameter names in IForgeDimensionSpecialEffects.adjustLightmapColors to better describe what they are. (#9656) 2023-07-17 09:38:05 -07:00
Jonathan Colmenares 26adf4da44 Re-add EntityEvent.Size methods to maintain bincompat 2023-07-17 07:50:18 -07:00
mgaray99 30d0a630d6 Added LivingMakeBrainEvent, to allow a consistent way for modders to manipulate entity Brains. #9292 2023-07-15 12:37:42 -07:00
JustRed23 00ad61b4ae Add LivingSwapHandItemsEvent 2023-07-15 11:15:57 -07:00
brandon3055 09bc73d5cd Fixed FluidUtil#tryFillContainer returning invalid result when simulating #9358 2023-07-15 10:51:17 -07:00
Jonathan Colmenares 1ee53c0464 Re-add in-game mod menu (#9652) 2023-07-15 10:45:34 -07:00
LexManos 16863c8d3b Fix Entity eye height for multipart entities. 2023-07-15 10:45:21 -07:00
Random832 150e8281ae Fix conflicting keybindings not having the correct click count set. #9360 2023-07-15 09:28:47 -07:00
Kovak 4385eb2155 Fix the Emissive Rendering for Experimental Light Pipeline (#9651) 2023-07-15 08:07:37 -07:00
RealMangoRage 5c40bef28e Fixed AdvancementsScreen.java.patch buttons not rendering (#9649) 2023-07-14 19:06:17 -07:00
ChampionAsh5357 55aa419100 Properly Handle Fluid Updates while in a Boat #9428 2023-07-14 19:05:27 -07:00
SilverDavid a4bef82836 New hook in IClientBlockExtensions to handle enabling tinting on breaking particles. #9446 2023-07-14 16:08:24 -07:00
LexManos b741df5f18 Fix invalid index when ticking itemstacks in a player nventory by adding a new onInventoryTick event. Closes #9453 2023-07-14 15:42:13 -07:00
LexManos 0c81a71308 Make the FireworkShapes enum extensible (Closes #9486) 2023-07-14 14:01:32 -07:00
Technici4n ea7f755a11 Add EmptyEnergyStorage (#9487) 2023-07-14 11:40:16 -07:00
Paint_Ninja d947d35eb1 Support IPv6 address compression for logged IPs 2023-07-14 11:14:56 -07:00
Sara Freimer 5271ef79fe Make item name rendering and status bar rendering respect additional gui overlays being rendered by mods (#9648) 2023-07-14 10:17:43 -07:00
Meldexun b7131824f0 Fix EyeHeight event being fired twice (#9647) 2023-07-14 10:09:48 -07:00
DBotThePony 3e624db4f8 Add PlayerSpawnPhantomsEvent, utilized to block or forcefully allow PhantomSpawner to spawn phantoms (#9644) 2023-07-14 07:20:21 -07:00
Nathan fc7af96bde Fix creative mode screen not correctly using CreativeModeTab::getTabsImage (#9627) 2023-07-14 07:10:49 -07:00
Paint_Ninja b1965db30b Add option to advertise dedicated servers to LAN. 2023-07-14 05:58:10 -07:00
LexManos 8df5a80725 Fix entity eye height loop. 2023-07-14 05:57:50 -07:00
ChampionAsh5357 d9043298d3 Particle Description Data Provider. 2023-07-13 19:34:53 -07:00
Meldexun e91ff9a5a6 Add LivingBreatheEvent and LivingDrownEvent (#9525) 2023-07-13 19:19:40 -07:00
Meldexun 77fb08713f Fix entity size event not being fired, changed it to split eye height and size calculations. (#9535) 2023-07-13 19:16:17 -07:00
bconlon 2c965fac90 AlterGroundEvent for modifying block placement performed by AlterGroundDecorator (#9637) 2023-07-13 17:29:03 -07:00
JustRed23 2f229c5bb1 Change ProjectileHitEvent to return a result instead of being cancelable. Closes #9642 2023-07-13 17:21:13 -07:00
noeppi_noeppi 54b08d2711 Replace static import with regular one to fix S2S and non-official mappings. (#9633) 2023-07-11 11:26:02 -07:00
Su5eD 46f641a502 Add missing null check for TagsProvider#existingFileHelper (#9638) 2023-07-11 11:25:39 -07:00
Sara Freimer 6304cb655b Add GuiGraphics helpers for blitNineSliced and blitRepeating that support specifying a custom texture size (#9641) 2023-07-10 22:03:47 +02:00
LexManos 27614b46a6 1.20.1 Recommended Build 2023-07-06 20:27:45 -07:00
LexManos ca47813bfa Fix FMLOnly loading. Closes #9609 2023-07-06 12:12:41 -07:00
Paint_Ninja b15b84fd59 Improve logging for server connections (#9618) 2023-07-05 15:01:18 -07:00
cech12 97edac430d Fix placing fluids into waterlogged blocks with FluidUtil::tryPlaceFluid. To behave more like vanilla MC-127110 (#9586) 2023-07-05 15:01:04 -07:00
Gegy 7b833faa46 Expose loaded RegistryAccess through AddReloadListenerEvent (#9613) 2023-07-05 15:00:12 -07:00
LexManos 93e4a7c649 Fix GLM applying to entities when killed. Closes #9551 2023-07-05 13:26:16 -07:00
Sara Freimer ccbc697bad Add unordered creative tabs after vanilla and perform a second level sorting of tabs by registry name to ensure tabs are ordered the same between game restarts (#9612) 2023-07-05 15:55:13 +02:00
cpw 77cd7043c9 Fix Early Loading window FPS snafu where it could spam (#9619)
unlimited screen updates. Probably a good way to gently toast an ancient laptop.

Signed-off-by: cpw <[email protected]>
2023-07-05 15:46:57 +02:00
Thomas Kain c0fe1688d8 Make overloads consistent for defineListAllowEmpty in ForgeConfigSpec.Builder (#9604) 2023-07-03 17:28:37 -07:00
LexManos 72e2ae1d6f Moved GameShuttingDownEvent hook to DedicatedServer class. Fixes #9601 2023-07-03 17:25:47 -07:00
LexManos 027f4d3d7b Fix PitcherCropBlock not calling canSustainPlant, not allowing it to be placed on custom farmland. Close #9611 2023-07-03 17:25:01 -07:00
LexManos 47ed2ad401 Add null check to NetworkHooks.openScreen. Closes #9597 2023-07-03 17:24:26 -07:00
dhyces b34275988c Fix ShieldBlockEvent not correctly performing damaged functions when not all damage is blocked. (#9615) 2023-07-03 16:56:22 -07:00
LexManos 47a2e137ed Add IP address to client disconnect message. Closes #9603 2023-07-03 16:54:40 -07:00
LexManos 063546c37d Fix hotbar items not dimming when sleeping in bed. Closes #9616 2023-07-03 16:54:31 -07:00
LexManos 3733005839 Bump Eventbus to address NullPointerException when classloading things on some threads. Closes #9570 2023-07-03 16:53:15 -07:00
cpw 7d0330eb08 Try and be a bit nicer about handling bad [feature] definitions (#9606)
* Try and be a bit nicer about handling bad [feature] definitions - they're single square bracket defined and require strings for feature bound values.

* Some more tweaks to the feature system to output useful and well formatted error messages when bounds fail. Requires SPI 7.0.1 and the language string "fml.modloading.feature.missing" has changed.

* Use immutable lists where possible and generally tidy things a bit
2023-06-28 22:27:59 -04:00
Marc Hermans 4c978a510c [HotFix]: Somehow not caught by CI, but there was an issue in the SpawnUtils to handle. 2023-06-27 15:20:08 +02:00
Sara Freimer 81f123fcab Remove amount from FluidStack hashCode to fix the equal/hashCode java contract (#9602) 2023-06-27 13:57:34 +02:00
Paint_Ninja a8dce04c1b Add example usage for configs to the MDK (#9596)
Demonstrates proper usage of different config value types, validation, transformation/parsing, load/reload handling and direct field access.
2023-06-27 13:57:20 +02:00
Paint_Ninja fa66faff6d Demonstrate configureEach in run configs (#9594) 2023-06-27 13:57:08 +02:00
Paint_Ninja 18536c76ae Reduce verbosity of prepareRuns doc in MDK (#9593) 2023-06-27 13:56:58 +02:00
Paint_Ninja ad9b14e59c Lazily configure MDK tasks, improve IDE support (#9592) 2023-06-27 13:56:42 +02:00
Sara Freimer 45e27c62e9 Fix not dropping xp for player sourced explosions and various other cases when xp should drop (#9588) 2023-06-27 13:56:28 +02:00
cech12 f1939ef144 add item handler capability to chiseled bookshelf (#9587) 2023-06-27 13:56:07 +02:00
Sara Freimer a43be39137 Fix ignoring maxUpStep method overrides on entities (#9583) 2023-06-27 13:55:52 +02:00
Sara Freimer 9450dfd67c Add missing damage type tag provider modid, existing file helper constructor overload (#9581) 2023-06-27 13:55:37 +02:00
Sara Freimer a3af55f85b Expose holder lookup registry provider as a helper in RegistriesDatapackGenerator (#9580) 2023-06-27 13:55:23 +02:00
Dennis C 9f858ef931 Fix gametest collection causing secondary crash in loading error state (#9568) 2023-06-27 13:55:06 +02:00
Sara Freimer b5f5d1d848 Fix SpriteCoordinateExpander not working with chained methods (MC-263524) (#9564) 2023-06-27 13:54:51 +02:00
Sara Freimer 15878af658 Expose EntityRendererProvider.Context to AddLayers event (#9562) 2023-06-27 13:54:41 +02:00
Brennan Ward 2d8d6b2a0a [1.20] Add events for checking spawn conditions (SpawnPlacementCheck and PositionCheck) (#9469) 2023-06-27 13:53:00 +02:00
cpw 7a25e83d34 Another tweak to the early display. We start a timer process while we create the window, in case it hangs. Also add a skip version config, to try and help in cases where the driver is stupid. (#9595) 2023-06-23 07:58:47 +02:00
Sara Freimer 8768c00f31 Auto generate names for modded loot pools. Fixes #9589 (#9591) 2023-06-22 11:56:47 -07:00
cpw 8c003dd965 More tweaks to the mod loading callbacks to make loading bars work better (#9585) 2023-06-22 20:36:09 +02:00
Sara Freimer dadc55cb2d Reimplement LootPool name patches and hooks (#9573) 2023-06-21 13:06:18 -07:00
Sara Freimer 83ab7656c9 Fix experimental lighting pipeline breaking vanilla's emissive rendering. Closes #9552 (#9582) 2023-06-21 20:30:36 +02:00
cpw d26cdf0b55 Update early loading default configs to match standard expectations. (#9577)
Also allow a global override env variable for darkmode always. "FML_EARLY_WINDOW_DARK"
2023-06-20 14:06:16 -07:00
ChiefArug e382ed526f Add proper duplicate mod error message that includes the mod id(s) and mod files. (#9474) 2023-06-20 14:05:38 -07:00
Dennis C 9cf1a77fdb Add missing stack tooltip rendering hooks (#9533)
Fix automatic tooltip wrapping discarding empty lines used as spacers
2023-06-20 14:05:06 -07:00
agnor99 5318d70c18 Add piston push reaction overrides to Block class (#9538) 2023-06-20 14:03:35 -07:00
Dennis C 060889656b Fix missing calls to level-sensitive block SoundType getter (#9553) 2023-06-20 14:02:43 -07:00
Sara Freimer c43c15932f Fix forge registries that have wrappers/support tags not being in the HolderLookup Provider that is provided to the GatherDataEvent (#9566) 2023-06-20 14:02:20 -07:00
Sara Freimer 4d9df706b0 Make IItemDecorator a functional interface again. Fixes #9563 (#9574) 2023-06-20 14:02:11 -07:00
LexManos af8900a748 Make 1.20.x the main TC branch. 2023-06-20 14:01:26 -07:00
Sara Freimer 439d07e403 Fix custom geometry in parent models not being resolved (#9572) 2023-06-20 17:36:22 +02:00
cpw 1ad8d48683 Make the scheduled thread factory setDaemon on it's threads. Should allow things to close properly if something deadly happens early on. (#9575) 2023-06-20 15:44:02 +02:00
cpw 031d008826 This is an early display window system for forge. (#9558) 2023-06-19 13:01:45 +02:00
cpw 551feaf019 fix the JIJ break by bumping SJH. apologies.
Signed-off-by: cpw <[email protected]>
2023-06-15 19:07:29 -04:00
cpw 032639b151 update libs (#9565)
Signed-off-by: cpw <[email protected]>
2023-06-14 22:30:36 -04:00
ChampionAsh5357 a32a06ce8a Update Armor Layer Implementation to Match Vanilla (#9547) 2023-06-12 19:20:07 -07:00
LexManos baa3098435 1.20.1 Update 2023-06-12 15:09:50 -07:00
SizableShrimp 1a413eeed2 Fix JAR compatibility checks for 1.20 (#9556) 2023-06-11 18:26:34 +02:00
SizableShrimp 497274fd93 [1.20] Add AFTER_LEVEL render level stage (#9555) 2023-06-11 12:28:55 +02:00
ChampionAsh5357 2eddcabdb7 Reorder overlay layers to match vanilla (#9550) 2023-06-10 12:01:33 +01:00
Dennis C ce84ba86f0 Re-implement missing level-sensitive block light hook in ChunkAccess (#9536) 2023-06-10 09:13:29 +01:00
Dennis C 622c47c075 Fix issues in the deserialization of empty ingredients (#9537) 2023-06-09 14:49:13 -07:00
agnor99 7614e024ed Fix wrong variable passed into EnchantmentClue lookup Fixes #9543 (#9544) 2023-06-09 14:48:53 -07:00
Dennis C a4570cb22f Fix incorrect depth test state in debug graph rendering Fixes #9534 (#9539) 2023-06-09 14:48:09 -07:00
ChampionAsh5357 76349744f7 Fix initCapabilities patch location in ServerLevel Fixes #9526 (#9531) 2023-06-09 14:47:38 -07:00
ChampionAsh5357 75d647615e Use Spinning Effect Intensity instead of Partial Tick for Portal Overlay Fixes #9529 (#9530) 2023-06-09 14:47:05 -07:00
Matei M 8482714375 Fix getArmorModelHook patch, Fixex #9523 (#9528) 2023-06-09 14:46:26 -07:00
Mgazul 2d8a935695 Fix duplicate Map writes in PlayerList patch. (#9521) 2023-06-09 14:45:57 -07:00
LexManos f6c13682d2 Fix Forge Version including MC version in MDK. 2023-06-09 14:45:07 -07:00
agnor99 18c3ea9625 Fix patch in light propagation (#9532) 2023-06-08 21:48:23 +01:00
SizableShrimp 09de4a2dcc Attempt to fix jar signing
Gradle 8 is stupid and doesn't (easily) allow in-place tasks, so a temporary fix has been made in ForgeGradle 6

Signed-off-by: SizableShrimp <[email protected]>
2023-06-07 22:17:25 -05:00
d8c84e2949 Forge 1.20
- Creative mode tabs are now a registry; the `BuildContents` event was renamed to `BuildCreativeModeTabContentsEvent` and moved it to its own class
- The pack format is now 15 for both resource packs and data packs
- `ScreenUtils` was deprecated in favor of a `GuiGraphics` extension
- Forge and the MDK were updated to Gradle 8 and FG6
- The Forge common config file was removed (it only contained the deprecated old fields for resource caching, which was removed in 1.19.3)
- Registry dummy entries were removed
- `RemappingVertexPipeline` was fixed to forward the `endVertex()` call
- Forge tool tags were removed in favor of vanilla ones

Co-authored-by: ChampionAsh5357 <[email protected]>
Co-authored-by: coehlrich <[email protected]>
Co-authored-by: Dennis C <[email protected]>
Co-authored-by: Matyrobbrt <[email protected]>
2023-06-07 20:55:09 -05:00
LexManos 658e614dc1 1.19.4 Recommended Build 2023-06-07 12:08:15 -07:00
Apex 9abfcbc90b Add method to GatherDataEvent to obtain collection of all input paths. (#9499) 2023-05-29 13:47:21 -07:00
Dennis C 6ee20899bf Log error when Sheets is class-loaded before registration is completed (#9475) 2023-05-29 13:47:04 -07:00
Dennis C 42115d37d6 [1.19.x] Re-implement RenderTooltipEvent.Color (#9497)
* Reimplement RenderTooltipEvent.Color

* Formatting, comments, EXC

* Deprecate instead of replacing
2023-05-16 12:24:27 +02:00
Joseph B 8abe9b6b0c Add API for registering custom world preset editors (#9436) 2023-05-14 15:53:34 -05:00
embeddedt fc91dfd1d1 Remove unneeded extra reload of datapacks on world creation screen (#9454) 2023-05-14 15:52:57 -05:00
SizableShrimp 8cd6f2d237 Bump ASM to 9.5 2023-05-14 15:50:07 -05:00
SizableShrimp f1702495ef Fix crash when running server from root directory
Fixes #9498
2023-05-14 15:26:24 -05:00
Dennis C 2ab2ced589 Fix root transform matrix format, allow using all four root transform formats (#9496) 2023-05-12 11:20:01 -07:00
Apex c8f9e59fef Add missing AT lines to allow registering custom game rule boolean/integer types (#9489) 2023-05-10 09:27:10 -07:00
Brennan WardandBrennan Ward 9a25527a54 [1.19.x] Fix SaplingGrowTreeEvent#setFeature being ignored in FungusBlock (#9485)
Co-authored-by: Brennan Ward <[email protected]>
2023-05-05 23:09:13 -05:00
SizableShrimp ad6d11671e Restore AccessibilityOnboardingScreen
Fixes #9488
2023-05-05 23:07:49 -05:00
Brennan Ward 3c93cd414d Update documentation on FinalizeSpawn (#9467) 2023-05-05 10:26:41 -07:00
DBotThePony 2c243626f6 Fix fluids without sound event causing exception in tryFillContainer and tryEmptyContainer (#9445) 2023-05-05 10:26:28 -07:00
Technici4n ae05036bb3 Make FakePlayerFactory respect the given ServerLevel (#9479) 2023-05-05 10:03:55 -07:00
Dennis C 3de22ffba3 Collect and log exceptions occurring in DeferredWorkQueue tasks (#9449) 2023-05-04 16:43:34 -07:00
matyrobbrt e1bf07dece Fix NamespacedWrapper#wrapAsHolder (#9450) 2023-05-04 16:42:02 -07:00
RealMangoRage 0ae63b0b07 Fixes ChatScreen calling .setScreen (#9443)
Fix test compile failures also.
2023-04-30 22:57:52 +01:00
matyrobbrt d4f211b815 Determine the Forge version the PR was built against when running PR compat checks (#9374) 2023-04-24 10:59:56 -07:00
SizableShrimp 393b28d7bb Add buildscript test to error on deprecated members that should of been removed. (#9460) 2023-04-24 10:59:37 -07:00
sciwhiz12 47c807c0e3 Remove erroneous brace patch in Inventory (#9462)
Fixes #9459
2023-04-23 13:24:20 +01:00
Dennis C b398f52a7f [1.19.4] Move root transform builder to ModelBuilder to allow use in ItemModelBuilder (#9456) 2023-04-20 09:27:30 +02:00
SizableShrimp f2aeeeb1fe Fix forge grindstone hooks allowing stacks of non-stackable items (#9457) 2023-04-20 00:19:35 -07:00
matyrobbrt dcef406df8 [1.19.4] Fix FMLOnly (#9415) 2023-04-18 16:46:04 +01:00
Dennis C 01846c729a Fix ItemLayerModel erroneously adding particle texture to layer texture list (#9441) 2023-04-07 08:16:17 +02:00
LexManos e6aaaff3f3 Temporary fix for Canceling ProjectileImpactEvents of Piercing ammo.
Event needs to be re-worked to have finer control. #9370
2023-04-05 11:16:39 -07:00
Brennan Ward d4836bc769 Fix dummy air blocks not being marked as air (#9440) 2023-04-03 15:04:34 -07:00
matyrobbrt b5655b0ddc Add support for splitting the login packet (#9367)
It contains full copies of data registries and can easily surpass vanilla's limits
2023-03-30 15:59:01 -07:00
Brennan Ward d75d333e13 Remove Attack Range and Reach Distance and add Block Reach and Entity Reach (#9361) 2023-03-29 18:53:41 -07:00
ChampionAsh5357 f5e16aa353 Add default bucket sounds for milk (#9432) 2023-03-29 18:48:29 -07:00
LexManos 4e5eecac12 Deprecate Item.onUsingTick, as vanilla provides the same function in Item.onUseTick now. Closes #9342 2023-03-29 18:46:00 -07:00
Brennan Ward a111073181 Fix ScreenEvent.Init.[Pre/Post] not working correctly (#9431) 2023-03-29 18:18:38 -07:00
LexManos a05f555ab8 Allow FenceGateBlock to be used without a WoodType. Closes #9392 2023-03-29 17:59:51 -07:00
LexManos df3db236e5 Deprecate duplicate tool tags that vanilla added in 1.19.4
We will maintain a seperate 'tools' tag until Mojang adds all relevent tool tags.
Closes #9393
2023-03-29 17:59:50 -07:00
LexManos 71b115c8fe Fix BlockEvent.Break not using ItemStack enchantment hooks. 2023-03-29 17:59:49 -07:00
LexManos 3e1b01af2c Move Block.onCatchFire to above block removal to allow usage of BlockEntity data. Closes #9400 2023-03-29 17:59:48 -07:00
Brennan Ward 1ab0685d04 Fix FinalizeSpawn not blocking spawns during worldgen (#9420) 2023-03-29 16:10:59 -07:00
LexManos 5c44992d1c Fixed issue with MutableHashedLinkedMap when removing multiple sequential entries in the middle of the map.
Added Unit tests for MutableHashLinkedMap
Added support for removing using the iterator
Added concurrent modification detection to the iterator
Added default constructor with basic hashing strategy.

Closes #9426
2023-03-29 15:49:49 -07:00
TelepathicGrunt d448ffd539 Loosen access for BucketItem's canBlockContainFluid (#9421) 2023-03-28 13:34:10 -07:00
ChampionAsh5357 9f895a79f2 Update and Regenerate Datapacks (#9419)
Add generation for pack.mcmeta
2023-03-28 13:33:59 -07:00
Paint_Ninja 6e1de81ff5 Restore ability to change message in ClientChatEvent (#9377) 2023-03-27 14:35:05 -07:00
sciwhiz12 b493a63fb6 Remove duplicate line in FoodData patch (#9424)
The line was accidentally duplicated in the 1.19.4 update and patching
process.

Fixes #9422
2023-03-27 12:51:53 -07:00
Joseph B ae65f5a255 Rename RegisterParticleProviderEvent's register methods to describe what kind of particle providers they register (deprecating old methods to avoid breaking) and minor docs tweaks (#9388) 2023-03-25 19:57:39 -07:00
noeppi_noeppi bb49b9e6dc Update pack versions (#9414) 2023-03-24 13:17:22 -07:00
Brennan Ward 2815e69407 [1.19.4] Revamp and fix spawn events (#9133) 2023-03-23 18:29:06 +00:00
Exopandora b33efc0727 [1.19.4] Replace blitOffset parameter with PoseStack in IItemDecorator (#9409)
* Replace blitOffset with PoseStack in IItemDecorator

* Circumvent breaking changes

* Fix blitOffset type
2023-03-22 11:11:01 +01:00
Dennis C 434c1ec4f8 Fix JSON model root transforms (#9410) 2023-03-22 11:09:41 +01:00
Dennis C c58d922324 Fix tossed items not being able to be picked up by other players. Closes #9412 (#9404) 2023-03-21 19:15:04 -07:00
Dennis C 331374dc7a Fix infinite BE render bounds failing frustum intersection test. Closes #9321 (#9407) 2023-03-21 12:16:49 -07:00
matyrobbrt c18eb23259 Make ForgeSlider use the new vanilla texture (#9406) 2023-03-20 14:16:35 -07:00
J.T. McQuigg d0e2242043 Add BlockSetType#register to accesstransformer.cfg (#9386) 2023-03-20 14:16:18 -07:00
Dennis C 684d36201f Add option to completely hide a crash-callable depending on a runtime value (#9372) 2023-03-20 14:15:59 -07:00
Jason 7b8ca60d32 Add isNewChunk to ChunkEvent.Load (#9369) 2023-03-20 14:15:33 -07:00
sciwhiz12 576ff81856 Remove DistExecutor calls in PistonEventTest (#9348) 2023-03-20 14:14:21 -07:00
sciwhiz12 73e68c9322 Fix hardcoded precipitation in ClimateSettingsBuilder (#9402)
This effectively caused all biomes to have precipitation, such as
minecraft:desert.

Fixes #9397
2023-03-20 08:56:10 +01:00
sciwhiz12 3cd758647d Fix incorrect variable used for swimming check (#9403)
Because of the incorrect variable, the check to stop sprinting (and stop
swimming) never fired correctly.

1.19.3's `flag5` variable was renamed to `flag7` in 1.19.4; however,
this was not caught during patching because of the fuzzy patcher.

Fixes #9399
2023-03-20 08:44:34 +01:00
sciwhiz12 14bc8d6611 Fix incorrect boolean used for glint effect (#9401)
The `flag1` variable is ultimately controlled by whether the armor slot
being rendered is for the leggings, which explains this bug where the
leggings always had the enchantment glint but not any other armor piece.

Fixes #9394
2023-03-20 08:02:18 +01:00
Redstone_Dubstep d552ce5eac Fixed ModMismatchDisconnectedScreen displaying missing mods wrongly (#9398) 2023-03-19 15:51:03 -07:00
Dennis C f03adc840e Fix misaligned text render type patch (#9391) 2023-03-16 14:44:20 -07:00
LexManos 31e73355c3 Remove thread filter from processing clientside custom payloads. Closes @9390 2023-03-16 11:51:46 -07:00
LexManos 88368dfa1c Fix LivingEntity patch which caused crash while entities got hurt. Closes #9389 2023-03-16 11:50:11 -07:00
matyrobbrt 7e6c8351c3 Fix wrong parameters in Screen#renderTooltipInternal patch (#9387) 2023-03-15 13:05:46 -07:00
LexManos fb20e4fa90 Fix misaligned patch in LevelRenderer. Closes #9385 2023-03-15 13:04:16 -07:00
Jonathan Coates 94c5699a03 Remove our fix for MC-121048 as it has been fixed by Vanilla (#9381) 2023-03-15 11:42:02 -07:00
LexManos 58626d1a22 Fix advancements not loading, bug seems to be fixed by vanilla now. Closes #9384 2023-03-15 11:41:12 -07:00
LexManos 631e72f99d Fixed patch verifier for cases where patches lowered access levels. Closes #9383 2023-03-15 11:39:42 -07:00
LexManos 3fffc18729 Fix crouching while sprinting stopping the player when step height is modified. Closes #9376 2023-03-14 23:56:29 -07:00
44c689f171 Forge 1.19.4
Properly move `ServerStatusPing` to codec
Reimplement custom display contexts

Co-authored-by: Matyrobbrt <[email protected]>
Co-authored-by: coehlrich <[email protected]>
Signed-off-by: SizableShrimp <[email protected]>
2023-03-14 17:22:27 -05:00
coehlrich 1f44339d6e Fix experimental world warning screen appearing everytime (#9375) 2023-03-09 08:26:32 -08:00
KnightMiner 001372bef2 Fix continuing to use items after dropping or when a shield breaks (MC-231097, MC-168573) (#9344) 2023-03-08 12:21:04 -08:00
KnightMiner 8f72c143a1 Add onStopUsing hook to IForgeItem (#9343) 2023-03-03 11:14:51 -08:00
Joseph B 3c75e1269d Document RegisterParticleProvidersEvent's APIs (#9346) 2023-02-24 13:18:07 -08:00
CreativeMD 121120aee1 Fix incorrect ListTag.getLongArray result (MC-260378) (#9351) 2023-02-24 13:16:32 -08:00
Su5eD 62ce55d8d7 Fix missing patch that left TagBuilder#replace unused (#9354) 2023-02-24 10:49:37 -08:00
Adneths 183d43f5f8 Add 2 new RenderLevelStageEvent.Stage for After Entities and After Block Entities (#9259) 2023-02-18 15:43:10 +01:00
ferriarnus 79dfdb0ace Cleanup StemBlock Patch (#9337) 2023-02-06 14:05:21 -08:00
Dennis C bb4a0b7dd6 Cleanup ItemProperties patch (#9332) 2023-02-06 14:04:04 -08:00
Dennis C 4cddc62be7 Make IForgeIntrinsicHolderTagAppender methods properly chainable (#9331) 2023-02-06 14:03:51 -08:00
ChampionAsh5357 914b2fe984 Fix in custom fluids not respecting max height correctly. (#9319) 2023-02-06 14:03:38 -08:00
cech12 308d4fa68e Fix inconsistent vaporization in BucketItem & FluidType (#9269) 2023-02-06 13:30:49 -08:00
cpw 50f325c8fe Fix reloading event firing during server shutdown and add explicit unloading event instead (#9016) 2023-02-06 13:30:16 -08:00
Joseph B 57fd0ecb2e Homogenize and/or holdersets when serializing to prevent serializing to NBT from crashing (#9048) Fixes #9043 2023-02-06 11:48:44 -08:00
matyrobbrt 94af5cc43a [1.19.x] Fix ForgeSlider not respecting custom height (#9237) 2023-02-05 18:58:08 +01:00
Mysterious_Dev 3c19a0309e Fix stepsound for blocks in the inside_step_sound_blocks tag. (#9318) 2023-01-25 13:19:54 -08:00
Minecraftschurli 0eb9d94bf3 Fix missing hanging sign material for modded wood type (#9303) 2023-01-23 13:34:22 -08:00
Jonathan Colmenares 4d57a1bec6 Fire TickEvent.LevelTickEvent on ClientLevel tick (#9299) 2023-01-23 11:47:00 -08:00
coehlrich 59ab01a559 Add ClientChatReceivedEvent for system messages (#9284) 2023-01-10 13:45:04 -08:00
matyrobbrt 5946356f8e PR Action update (#9274) 2023-01-08 21:24:00 +00:00
Minecraftschurli f0f7b18528 fix HangingSignEditScreen crash when using custom wood types using modid (#9294) 2023-01-08 21:21:23 +00:00
LexManos 5311b0bc90 Bump SecureJarHandler version, to help identify invalid mods. 2023-01-06 13:01:59 -08:00
Brennan Ward 43580f5804 [1.19.3] Hotfix missing null check in createUnbakedItemElements (#9285) 2023-01-06 19:42:56 +01:00
Curle b0a68b8c79 Mark 1.19.3 Recommended Build 2023-01-02 03:37:31 +00:00
Brennan Ward f225d1f3fb [1.19.3] Allow Item and Elements models to specify static color, sky light, and block light values. (#9106) 2022-12-31 10:36:56 +01:00
bconlon 15214a6e9a Fix StemBlock not checking canSustainPlant for the correct block, it now checks for Melons/Pumpkins instead of the stem itself. (#9270) 2022-12-30 11:13:42 -08:00
matyrobbrt dfce6b62d4 Add github shared actions for automation purposes. (#9251) 2022-12-30 11:12:49 -08:00
Mysterious_Dev 0232ad7424 Add translate key for Forge pack.mcmeta description (#9260) 2022-12-30 11:12:11 -08:00
Mysterious_Dev e6ff2e0c24 Fix broken link for update checker docs in mdk (#9271) 2022-12-30 11:11:43 -08:00
sciwhiz12 72980d2b03 Remove duplicate updateNeighbourForOutputSignal call Fixes #9169 (#9234) 2022-12-30 11:11:23 -08:00
Minecraftschurli 6e3b51d81f Add helper methods to access the set of loaded sprite locations (#9223) 2022-12-30 11:11:02 -08:00
Dennis C 6b1c628326 Disable guiLight3d for generated item models (#9230) 2022-12-30 17:59:36 +01:00
matyrobbrt 85f9b1012c Remove resource caching (#9254) 2022-12-27 11:40:06 -08:00
failall 7569de7004 Add TradeWithVillagerEvent (#9244) 2022-12-26 13:20:01 -08:00
Mysterious_Dev 5ccde7422c Update link for Parchment "Getting Started" (#9243) 2022-12-26 13:19:44 -08:00
bconlon ad6bf9428e Allows DatapackBuiltinEntriesProvider to datagen LevelStems (#9247) 2022-12-26 13:19:36 -08:00
noeppi_noeppi d176bab28e Add a method to LootContext.Builder that allows changing the queried loot table id (#9084) 2022-12-19 15:18:52 -08:00
ChampionAsh5357 58d6f072e4 [1.19.3] Fix Datagen Tests and Providers (#9212) 2022-12-19 16:28:19 +00:00
matyrobbrt cc393818e4 Fix concrete powder not being hydrated by singular water sources (#9236) 2022-12-19 16:05:22 +00:00
Girafi e84098a011 [1.19.3] Fix LootTableLoadEvent not getting fired (#9239) 2022-12-19 16:05:08 +00:00
matyrobbrt f2db3941ea Allow using custom factories in button builders (#9238) 2022-12-19 16:04:41 +00:00
Dennis C c2bfe12068 Fix logspam when a root resource is requested from DelegatingPackResources, fixes #9197 (#9227) 2022-12-19 16:04:27 +00:00
matyrobbrt d9e3a0081e [1.19.3] Fix retrieveRegistryLookup attempting to get the registry lookup from a HolderGetter (#9225) 2022-12-19 16:03:56 +00:00
Minecraftschurliandsciwhiz12 84d7a0facb [1.19.3] Add ability to datagen forge specific values in pack.mcmeta (#9221)
Co-authored-by: sciwhiz12 <[email protected]>
2022-12-16 16:52:54 +00:00
Dennis C 9c66928854 Add block atlas config to register forge:white texture (#9187) 2022-12-16 16:48:20 +00:00
bl4ckscor3 4b494451db Fix ExtendedButton not being highlighted when focused (#9144) 2022-12-16 16:46:58 +00:00
matyrobbrt e9ea4ca169 Separate checkAndFix from the check* tasks. (#9213) 2022-12-16 16:44:56 +00:00
Dennis C c5ca054936 Fix forge resources overriding vanilla ones (#9222) 2022-12-16 16:43:46 +00:00
Exopandora 7d2c7c0f91 Fix tooltip customization not working for creative inventory (#9218) 2022-12-16 16:42:51 +00:00
sciwhiz12 54b872af69 Fix glowing item frame entity's texture (#9126)
Fixes #9123
2022-12-16 16:41:45 +00:00
matyrobbrt ae954bea17 Fix datapack registries not being synced to clients (#9219) 2022-12-15 11:53:53 -08:00
Dennis C ad59985a9b Fix creatives tabs rendering overlapping tabs if the selected tab isn't on the current page. (#9214) 2022-12-15 11:41:29 -08:00
Technici4n f8fdd43552 Fix SidedInvWrapper not accounting for vanilla stacking special cases in brewing stands and furnaces (#9189) 2022-12-15 11:40:25 -08:00
Marc Hermans b269f7dbfd Update to the latest JarJar. (#9217) 2022-12-15 11:39:37 -08:00
ChampionAsh5357 e688f136dd Specify NetworkHooks#getEntitySpawningPacket Generic Return Type (#9220) 2022-12-15 11:39:29 -08:00
LexManos 81276b341a Fix using a DeferredRegister on a non-forge wrapped registry. Closes #9199 2022-12-12 12:00:23 -08:00
Dennis C f050617b47 Add support for custom CreativeModeTab implementations (#9210) 2022-12-12 10:23:48 -08:00
eerussianguy e6c7958456 Simplify tree grower patches (#9209) 2022-12-12 10:23:32 -08:00
ChampionAsh5357 4802ceeccc Replace AdvancementProvider patch with Forge helper (#9188) 2022-12-12 10:23:10 -08:00
matyrobbrt 2ea16d89ca Allow using PackOutputs in Forge-added datagen classes (#9182) 2022-12-12 10:22:48 -08:00
J.T. McQuigg e541201129 Add simpleBlockWithItem for data gens (#9170) 2022-12-12 10:22:32 -08:00
Technici4n 09aac3a4d3 Fix running test mods (#9211) 2022-12-12 10:22:09 -08:00
Dennis C dccb1ab3a4 [1.19.3] Fix models nested in custom geometries not resolving parents (#9200) 2022-12-11 21:25:14 +01:00
gigaherz 8bf96d8738 Fix OBJ Loader caches not being thread-safe. (#9204) 2022-12-11 20:36:17 +01:00
Dennis C 069a94f205 [1.19.3] Add event before baked models are cached by the BlockModelShaper (#9190) 2022-12-11 20:35:45 +01:00
sciwhiz12 332ed4ab76 Fix compatibility checker task configuration (#9202) 2022-12-11 20:24:47 +01:00
matyrobbrt 030f2dbd38 Fix chat offset (#9184) 2022-12-11 16:46:17 +01:00
LexManos ec3abdea14 Redesign CreativeTab collection event to be a lot more straight forward. (#9198) 2022-12-10 15:03:23 -08:00
LexManos 4cf5508e04 Move ICondition patch placement to before MC throws an error.
Disable the explicitly erroring test biome modifier.
2022-12-09 19:52:30 -08:00
shartte d861f647d7 Fix BlockStateProvider not waiting for models before finishing. (#9196) Fixes #9195: 2022-12-09 12:50:55 -08:00
LexManos 992f793695 Fix tooltips not rendering on screens. Closes #9191 2022-12-09 12:48:59 -08:00
LexManos 69eb940764 Fix merged mod resource pack not returning all resources with the same name when asked. Closes #9194 2022-12-09 12:44:40 -08:00
Marc Hermans c1f26d544a Fix searching using the wrong prefix for items or tags. Fixes #9176 Fixes #9179 (#9177) 2022-12-08 09:11:18 -08:00
8255d7aed3 Forge 1.19.3
Created a CreativeModeTabEvent to register creative mode tabs and populate entries per tab
Moved datapack registries to DataPackRegistryEvent.NewRegistry event instead of tying them to ForgeRegistry
Made it easier for mods to datagen datapack builtin entries with DatapackBuiltinEntriesProvider
Provided access to lookupProvider for datagen
Updated dependencies to match versions used by vanilla and update JarJar to 0.3.18
Added a test mod for the new CreativeModeTabEvent
Throws better error message for Forge registries in tag datagen
Deleted ForgeRegistryTagsProvider
Updated ClientChatReceivedEvent and ServerChatEvent for Mojang changes
Added patches for both sign related methods in ModelLayers
Changed RegisterShadersEvent to use ResourceProvider
Migrated old Mojang math types to JOML

Co-authored-by: Marc Hermans <[email protected]>
Co-authored-by: LexManos <[email protected]>
Co-authored-by: sciwhiz12 <[email protected]>
Co-authored-by: coehlrich <[email protected]>
2022-12-08 01:45:22 -06:00
LexManos 617ecb7ad3 43.2 Recommended Build. 2022-12-07 23:20:28 -08:00
Su5eD dd8239c255 Allow discovering services from the mods folder that use java's modular definition. (#9143) 2022-11-30 14:50:30 -08:00
Brennan Ward bdddc8c5d4 Make Datapack Registries support ICondition(s) (#9113) 2022-11-30 14:45:00 -08:00
Marc Hermans c1c619a109 Enable additional build types to handle pull request validation. (#9159) 2022-11-30 14:08:05 -08:00
sciwhiz12 f7eea35cb9 Check source permission level before selector permission (#9147)
In some situations, such as execution of a function by an advancement as
part of its reward, a command source stack may have a backing source of
a ServerPlayer which may lack the entity selector permission and have an
explicit permission level that should allow the use of entity selectors,
through CommandSourceStack#withPermission.

We now check if the permission level of the command source stack is
sufficient for entity selectors _before_ checking if the source is a
player and if they have the requisite permission.

This means that an operator permission level of 2 will always override
the Forge entity selector permission.

Fixes #9137
2022-11-30 14:07:18 -08:00
J.T. McQuigg a2434e1607 Fix fires spreading too/igniting custom portal frames. (#9142) 2022-11-30 14:06:22 -08:00
Minecraftschurli b806ecd0ff Add supplier to FlowerBlock so it works with custom MobEffects (#9139) 2022-11-30 14:04:15 -08:00
KnightMiner 8358b800e9 Fix some logical bugs related to the Grindstone Event (#9089) 2022-11-30 14:00:59 -08:00
Néstor Amador b25a092cf1 Call baked model's getModelData before getRenderTypes (#9163) 2022-11-28 17:53:19 +01:00
Jonathan Coates d812fdd0a3 Make Util.memoize thread-safe (#9155) 2022-11-23 09:18:04 +01:00
Néstor Amador 2d3ee8a228 Rendering tweaks and fixes: Part 4 (#9065) 2022-11-23 09:17:43 +01:00
matyrobbrt 97a9ca65f1 Fix Transformation loading PoseStack (#9083) 2022-11-21 20:49:41 +01:00
Néstor Amador f043fbbc51 Add simple block appearance API (#9066) 2022-11-21 20:48:30 +01:00
SizableShrimp a19ab3a8d4 Fix invalidated modded packets when on LAN (#9157) 2022-11-20 18:34:21 -08:00
Dennis C bc0517a404 Improve extensibility of DetectorRailBlock and PoweredRailBlock (#9130) 2022-11-08 12:18:48 -08:00
Su5eD dc9c0d7e35 Fix launch handler minecraft classpath locator (#9120) 2022-11-08 12:17:48 -08:00
matyrobbrt 1a2207b3b2 Add HitResult to EntityTeleportEvent$EnderPearl (#9135) 2022-11-08 12:17:18 -08:00
sciwhiz12 84ab9764e3 Throw aggregate exception for erroneous registry event dispatch (#9111)
This means that exceptions occurring during the dispatch of the registry
events, such as those from the suppliers of RegistryObjects, properly
cause a crash rather than merely being logged and allowing the game to
reach the main menu.

Fixes #8720
2022-11-08 12:17:00 -08:00
Nexus-Dino bef68991f0 Add missing semi-colon near the Dist import statement in example mod. 2022-11-08 12:16:49 -08:00
Nexus-Dino 526c13d662 Fix ClientModEvents example not subscribing to client-sided events (#9097) 2022-10-21 13:22:57 -07:00
matyrobbrt 67ffb78c23 Use GitHub action to lock issues with the spam label (#9087) 2022-10-21 13:22:31 -07:00
sciwhiz12 8f407c3a22 Remove structures slave map to Feature registry (#9091) 2022-10-21 13:22:18 -07:00
Dennis C 8980252e16 Improve logging of missing or unsupported dependencies (#9104) 2022-10-21 13:21:49 -07:00
DBotThePony 3baee80485 [1.19.x] Fix ValueSpec caching the return value incorrectly (#9046) 2022-10-14 17:43:21 +01:00
eerussianguy ca9599bb4d [1.19.x] Add event for registering spawn placements, and modifying existing (#9024) 2022-10-14 17:39:14 +01:00
eerussianguy 051db7907a [1.19.x] Add event for items being stacked or swapped in a GUI. (#9050) 2022-10-14 17:30:12 +01:00
Magnus Jensen bdba5ef2d3 [1.19.x] Fix PlayerInteractEvent.EntityInteractSpecific not cancelling on a server (#9079) 2022-10-14 17:18:17 +01:00
DBotThePony 838b9c0eda Fix canceling phantom spawns preventing any further attempts that tick. (#9041) 2022-10-13 11:40:56 -07:00
Magnus Jensen be584c54aa Rename fluid type milk translation keys (#9077) 2022-10-12 12:54:34 -07:00
ChampionAsh5357 1b3640b42d Fix minecart speed with water (#9076) 2022-10-12 12:51:13 -07:00
Sara Freimer be5dab5801 Add a cancellable event that gets fired when a Totem of Undying is used (#9069) 2022-10-12 12:46:08 -07:00
Marc Hermans aae628cd18 Fix performance issue and logging when resource caching is enabled (#9029) 2022-10-12 12:00:25 -07:00
Magnus Jensen e7bd08649f Fix NPE when feeding wolves and cats (#9074) 2022-10-07 19:55:40 -07:00
Paint_Ninja e1017a4c7e Fix logically breaking change to ForgeConfigSpec.Builder#comment where modders could not add a empty line to the start of comments. (#9061) 2022-10-03 11:50:32 -07:00
SizableShrimp 6f8ba9d295 Fix ServiceLoader bug
Signed-off-by: SizableShrimp <[email protected]>
2022-09-29 22:11:12 -07:00
SizableShrimp ac94478ae7 Fix ClientChatReceivedEvent for system messages
Signed-off-by: SizableShrimp <[email protected]>
2022-09-29 22:11:12 -07:00
DBotThePony 515c6082e0 Make ForgeConfigSpec$Builder.comment able to be called multiple times for the same entry. (#9056) 2022-09-27 23:23:59 -07:00
ChampionAsh5357 a753598113 Fix control modifier for mac with KeyMappings using Alt instead of Super (#9057) 2022-09-27 11:15:11 -07:00
J.T. McQuigg bc6c4be4e4 Fix is_desert tag not being applied correctly. (#9051) 2022-09-26 11:08:24 -07:00
Andy Dingley 98823a8f07 Fix mob griefing event for SmallFireballs not using owner entity. (#9038) 2022-09-21 13:24:25 -07:00
David Dempsey ecd8f1e54d Fix minecarts on rails not properly slowing down in water (#9033) 2022-09-21 13:20:03 -07:00
LexManos b0caea8e20 Change codestyle for BookShelves tag. Closes #9027
Add IS_CAVE tag Closes #8885
Add IS_DESERT tag Closes #8979
Simplify Mangrove Swamp tags Closes #8980
2022-09-09 11:27:39 -07:00
nikita488 e0477f8776 Allow faces of an "elements" model to have disabled ambient occlusion (#9019) 2022-09-08 16:17:44 +01:00
bconlon 94e1e54b53 [1.19.x] Recipe ID-based grouping between modded and vanilla recipes. (#8876) 2022-09-08 16:13:07 +01:00
nitro_dynamite18 9ce72331b7 Update fence_gates/wooden (#8936) 2022-09-08 16:12:26 +01:00
Jais Djurhuus-Kempel 1c31f42dae [1.19.x] Added event for growing fungus (#8981) 2022-09-08 16:11:59 +01:00
agnor99 0978a94358 Added Bookshelves block tag (#8991) 2022-09-08 16:11:39 +01:00
GizmoTheMoonPig e956a9c9f3 Create a Forge EntityType Tag for Bosses (#9017) 2022-09-08 16:10:48 +01:00
Monica S 82026d11a4 Allow mods to specify shader import namespace (#9021) 2022-09-08 16:10:31 +01:00
ferriarnus 6d233ac75c Grindstone Events (#8934)
One to modify the output, and one to modify the input.
2022-09-08 15:55:43 +01:00
Reece Mackie 73c1934e7e Fix the serialized names of the enum (#9014) 2022-09-04 19:46:54 +01:00
Technici4n bf1194ec75 Fix tryEmptyContainerAndStow duping fluids with stackable containers (#9004) 2022-09-03 21:48:07 +01:00
Ted Senft 22d2740725 Add mod mismatch event (#8989) 2022-09-03 21:26:43 +01:00
Minecraftschurli 02f26dc7a1 [1.19.x] add methods with more context to tree growers (#8956) 2022-09-03 20:24:00 +01:00
28Pollux28 db14cbb0e0 [1.19.X] Adding more precise events for Advancements (#8360) 2022-09-03 20:17:16 +01:00
Luke BemishandLexManos a2b35cae92 Default IItemHandler capability for shulker box itemstacks (#8827)
Co-authored-by: LexManos <[email protected]>
2022-09-03 20:16:25 +01:00
Take Weiland aab0e4c82e [1.19] Add hook for items to remain in the hotbar when picking blocks/entities (#8872) 2022-09-03 20:12:13 +01:00
Reece Mackieandsciwhiz12 6609708d14 [1.19.x] Block Model Builder Root Transform Support (#8860)
Co-authored-by: sciwhiz12 <[email protected]>
2022-09-03 20:11:46 +01:00
Budschie ed198f062c [1.19.x] Make LivingSetAttackTargetEvent compatible with the Brain/Behavior system. (Port of PR #8918) (#8954) 2022-09-03 20:11:10 +01:00
thedarkcolour 4e2d656e9d [1.19.x] Add IForgeBlock#onTreeGrow to replace IForgeBlock#onPlantGrow from 1.16 (#8999) 2022-09-03 20:07:09 +01:00
bconlon 2a4230868f [1.19.x] Moved Player.resetAttackStrengthTicker() to the end of Player.attack() (#9000) 2022-09-03 20:06:35 +01:00
Sirttas cf778b1759 fix misplaced patch in sapling block (#9005) 2022-09-03 20:05:37 +01:00
Brennan Ward 1b6daddd40 Fix failed entity interactions consuming the click. (#9007) 2022-09-03 20:00:57 +01:00
sciwhiz12 d7b137d144 Fix entity selector permission check to check original source (#8995)
Permission checks should be against the command source and not the
target entity, as is done in vanilla.

Fixes #8994
2022-08-25 11:42:11 -07:00
한현빈 b5d5855559 Hotfix for 1.19.2 item animation bug (#8987)
* [HOT FIX]: Fixes #8985 by no-oping for vanilla models instead of throwing error

Signed-off-by: tmvkrpxl0 <[email protected]>
2022-08-20 11:38:24 +02:00
LexManos c0aa8dd808 Add ability to Auto register capabilities via annotation (#8972) 2022-08-17 15:06:35 -07:00
LexManos 4f2f5c02a0 1.19.2 RB 2022-08-17 14:58:14 -07:00
agnor99 7a6843fa4a Added ItemDecorator API (#8794) 2022-08-17 23:39:56 +02:00
한현빈 f0ecf16c6b [1.19.x] Custom usage animations for items (#8932) 2022-08-17 23:08:13 +02:00
Take Weiland 8998fa71b2 Allow registering custom ColorResolvers (#8880) 2022-08-17 22:51:05 +02:00
Dennis C 4e677a7558 [1.19] Allow custom outline rendering on EntityRenderers and BlockEntityRenderers (#8938) 2022-08-17 00:01:33 +02:00
sciwhiz12 07254b36d7 Redirect checks for entity selector use to a permission (#8947)
This allows greater flexibility for configuring servers with
operator-like permissions to user groups through the permissions API and
their permissions handler of choice without needing to grant the
vanilla operator permission to any player.

The new permission is "forge:use_entity_selectors", which is granted by
default to players with permission level 2 (GAMEMASTERS) and above.

The hook falls back to checking the permission level if the source of
the command is not a ServerPlayer, such as for command blocks and
functions.
2022-08-16 14:11:54 -07:00
Brennan Ward a51b8d256d Allow FakePlayer to report its position (#8963) 2022-08-16 14:09:09 -07:00
Sara Freimer 75fcd9448e Add alternate version of renderEntityInInventory to allow for directly specifying the angles (#8961) 2022-08-16 13:57:30 -07:00
eerussianguy 6b45999702 Add cancellable ToastAddEvent (#8952) 2022-08-16 13:48:55 -07:00
eerussianguy a4f52f4057 Modify ScreenEvent.RenderInventoryMobEffects to allow moving the effect stack left or right (#8951) 2022-08-16 13:47:57 -07:00
Brennan Ward b8b3201372 Fix Enchantment#doPostHurt and Enchantment#doPostAttack being called twice for players. Fixes MC-248272 (#8948) 2022-08-16 13:40:10 -07:00
LexManos 41deadc1e2 Remove reflective implementation of ICustomPacket. (#8973)
Make vanilla custom packets able to be sent multiple times. Closes #8969
2022-08-16 13:17:25 -07:00
LexManos 82e8d06e54 Filter name spaces to directories only. Closes #8413 2022-08-14 12:22:38 -07:00
Marc Hermans 51db5f2ff7 Fix a corner case where the UMLB can not extract a version from a library. (#8967) 2022-08-14 12:18:22 -07:00
Alex O'Neill 2ec9e515ff Fix worlds with removed dimension types unable to load. (#8959) Closes #8800 2022-08-14 12:17:22 -07:00
Alex O'Neill 0579572148 Fix issue where unknown chunk generators would cause DFU to fail. (#8957) 2022-08-10 23:13:44 -07:00
TheMuso 83f4e6353c Fix comments and documentation that were missed during the review of #8712 (#8945) 2022-08-09 12:49:00 -07:00
Luis 130953e519 Make AnvilUpdateEvent fire even if the second input is empty, which means it fires even if only changing the item name. (#8905) 2022-08-09 12:48:46 -07:00
Sirttas 7981093591 Fix LivingEntity#isBlocking to use ToolActions#SHIELD_BLOCK instead of UseAnim#BLOCK (#8933) 2022-08-09 12:27:26 -07:00
Joseph B 79112d6208 Add Custom HolderSet Types allowing for logical combining of sets. (#8928) 2022-08-09 12:25:12 -07:00
Sara Freimer a3eb6263f4 Add values to VersionSupportMatrix to support loading mods that restrict versions to 1.19.1 on 1.19.2 (#8946) 2022-08-09 12:15:58 -07:00
Redstone_Dubstep d91bc9c73d Fix certain particles not updating their bounding box when their position changes (#8925) 2022-08-08 15:09:13 +02:00
LexManos c807665ab9 Update EventBus to address concurrency issue in ModLauncher Factory. Closes #8924 2022-08-07 17:35:15 -07:00
LexManos 7b97c181b9 1.19.2 2022-08-05 16:35:02 -07:00
Brennan Ward c42b0847d2 Remove calls to getStepHeight in Player#maybeBackOffFromEdge (#8927) 2022-08-05 11:35:47 -07:00
Sirttas f182cb00c1 Add forge tags for tools and armors, these DO NOT replace ToolActions, and are designed just for Recipes. (#8914) 2022-08-05 11:32:53 -07:00
J.T. McQuigg 3181b5da23 Add Biomes.BEACH to Tags (#8892) 2022-08-05 11:31:32 -07:00
Mysticdrew 983c119477 Let NetworkInstance.isRemotePresent check minecraft:register for channel IDs. (#8921) 2022-08-05 11:31:11 -07:00
Sara Freimer 874ff1bba2 Add an event for when the chunk ticket level is updated (#8909) 2022-08-05 11:21:14 -07:00
TheMuso 06d9d89f98 Re-add PotentialSpawns event (#8712) 2022-08-05 09:29:02 -07:00
Dennis C c713bad8c3 Fix misplaced patch in ItemEntityRenderer breaking ItemEntityRenderer#shouldBob() (#8919) 2022-08-04 22:14:28 -07:00
Marc Hermans fe19491822 [1.19] [HotFix] Fix the dedicated server not having access to the JiJ filesystems. (#8931) 2022-08-03 22:32:32 +01:00
SizableShrimp c80e8a1aca Match Mojang's action bar fix for MC-72687 (#8917) 2022-07-29 01:09:33 -07:00
SizableShrimp 6845c094ab Forge 1.19.1
Load natives from classpath
Make command argument types a forge registry
Add `EntityMobGriefingEvent` to `Allay#wantsToPickUp`
Overhaul `ServerChatEvent` to use `ChatDecorator` system
Remove `ClientChatEvent#setMessage` for now
Gradle 7.5

Signed-off-by: SizableShrimp <[email protected]>
2022-07-28 00:20:31 -05:00
LexManos 3c1126a7b4 Mark 1.19 RB 2022-07-26 18:49:28 -07:00
Néstor Amador 326e0329b9 Allow faces of an "elements" model to be made emissive (#8890) 2022-07-26 15:01:29 -07:00
coehlrich f9d0daa91c Fix invalid channel names sent from the server causing the network thread to error. (#8902) 2022-07-26 14:55:05 -07:00
LexManos 094200e687 Fix PlayerEvent.BreakSpeed using magic block position to signify invalid position. Closes #8906 2022-07-25 16:20:44 -07:00
Marc Hermans bb4818630c Fix cases where URIs would not work properly with JarInJar (#8900) 2022-07-23 00:46:45 -07:00
Jonathan Colmenares 7af3155b39 Add new hook to allow modification of lightmap via Dimension special effects (#8863) 2022-07-22 12:22:23 -07:00
Marc Hermans d988b4a811 Fix Forge's packet handling on play messages. (#8875) 2022-07-22 12:08:29 -07:00
zml 18f5bdb7ca Add API for tab list header/footer (#8803) 2022-07-21 21:01:21 -07:00
TelepathicGrunt f41d493552 Allow modded blocks overriding canStickTo prevent sticking to vanilla blocks/other modded blocks (#8837) 2022-07-21 13:06:02 -07:00
Néstor Amador 8cd2c9c63f Multiple tweaks and fixes to the recent changes in the client refactor PR: Part 3 (#8864)
Fix weighted baked models not respecting children render types
Allow fluid container model to use base texture as particle
Fix inverted behavior in composite model building. Fixes #8871
2022-07-20 15:25:07 -07:00
matyrobbrt 427b335cac Fix crossbows not firing ArrowLooseEvent (#8887) 2022-07-20 14:35:37 -07:00
Emma, Cypress, Rose ⚘ 2049096c6f Add User-Agent header to requests made by the update checker (#8881)
Format: Java-http-client/<Java version> MinecraftForge/<ForgeVer> <ModId>/<ModVersion>
2022-07-20 10:57:57 -07:00
Marc Hermans 98d4fa6dc2 Output the full path in a crash report so it is easier to find the outer mod when a crash in Jar-In-Jar occurs. (#8856) 2022-07-20 10:56:38 -07:00
Take Weiland 5306471aec Clean up the pick item ("middle mouse click") patches (#8870) 2022-07-20 10:56:16 -07:00
Kai aa8818b049 [1.19.x] Hotfix for test mods while the refactor is ongoing 2022-07-17 14:49:56 +01:00
Caltinor 2d86f17363 add event to SugarCaneBlock (#8877) 2022-07-16 02:21:27 +01:00
Brennan Ward 3a36a01913 Fix Global Loot Modifiers not using Dispatch Codec (#8859) 2022-07-12 11:19:35 -07:00
Nicholas Smit 47d70a7f31 Allow block render types to be set in datagen (#8852) 2022-07-12 11:19:03 -07:00
Sara Freimer d5b5e6f4db Fix renderBreakingTexture not using the target's model data (#8849) 2022-07-12 17:04:29 +02:00
Néstor Amador e76d814439 Multiple tweaks and fixes to the recent changes in the client refactor PR: Part 2 (#8854)
* Add getter for the component names in an unbaked geometry
* Fix render type hint not being copied in BlockGeometryBakingContext
* Ensure BlockRenderDispatches's renderSingleBlock uses the correct buffer
2022-07-12 17:04:03 +02:00
SizableShrimp b8b40e1f54 [1.19.x] Apply general renames, A SRG is provided for modders. (#8840)
See https://gist.github.com/SizableShrimp/882a671ff74256d150776da08c89ef72
2022-07-11 18:27:48 -07:00
LexManos 5e8c786406 Fix mob block breaking AI not working correctly when chunk 0,0 is unloaded. Closes #8853 2022-07-11 12:41:06 -07:00
Néstor Amador 5b8224f839 Fix crash when breaking blocks with multipart models and remove caching. Closes #8850 2022-07-11 11:24:21 -07:00
Silver_David cd621eb081 Fixed CompositeModel.Baked.Builder.build() passing arguments in the wrong order (#8846) 2022-07-10 11:11:07 -04:00
Néstor Amador b1113f0363 Make cutout mipmaps explicitly opt-in for item/entity rendering (#8845)
* Make cutout mipmaps explicitly opt-in for item/entity rendering

* Default render type domain to "minecraft" in model datagens
2022-07-10 11:10:57 -04:00
Silver_David daf5933b79 Fixed multipart block models not using the new model driven render type system. (#8844) 2022-07-10 11:10:43 -04:00
Marc Hermans 47fd2e8604 Update to the latest JarJar to fix a collision issue where multiple jars could provide an exact match. (#8847) 2022-07-10 11:07:55 -04:00
covers1624 05297fd72f Add FML config to disable DFU optimizations client-side. (#8842)
* Add client-side command line argument to disable DFU optimizations.

* Switch to using FMLConfig value instead.
2022-07-09 20:01:40 -04:00
CreativeMD f20cf19f94 [1.19] Fixed broken BufferBuilder.putBulkData(ByteBuffer) added by Forge (#8819)
* Fixes BufferBuilder.putBulkData(ByteBuffer)

* use nextElementByte

* Fixed merge conflict
2022-07-09 19:55:16 -04:00
Robert Kämpf 061cd3da53 [1.19.x] Fix shulker boxes allowing input of items, that return false for Item#canFitInsideContainerItems, through hoppers. (#8823)
* Make ShulkerBoxBlockEntity#canPlaceItemThroughFace delegate to Item#canFitInsideContainerItems.

* Switch to using Or and add comment.

* Switch Or to And.
2022-07-09 12:44:33 -04:00
Silver_David 03202a8ab4 [1.19.x] Added RenderLevelStageEvent to replace RenderLevelLastEvent (#8820)
* Ported RenderLevelStageEvent from 1.18.2

* Updated to fix merge conflicts
2022-07-09 12:36:47 -04:00
Nicholas Smit 8a67be4afd [1.19.x] Fix door datagenerator (#8821)
* Fix door datagenerator

Fix datagenerator for door blocks. Successor to #8687, addresses comments made there about statement complexity.

* Fix extra space around parameter

Fix extra space before comma around a parameter.
2022-07-09 12:21:06 -04:00
Drullkus 4c1ab67f33 Create PieceBeardifierModifier to re-enable piecewise beardifier definitions (#8798) 2022-07-09 12:12:55 -04:00
Dennis C 7e06a2105f Allow blocks to provide a dynamic MaterialColor for display on maps (#8812) 2022-07-09 12:10:02 -04:00
Flemmli97andFlemmli97 4a0c8be742 [1.19.x] BiomeTags Fixes/Improvements (#8711)
* dimension specific tag fix

* remove forge:is_beach cause vanilla has it already

* remove forge tags for new 1.19 vanilla tags (savanna, beach, overworld, end)

Co-authored-by: Flemmli97 <[email protected]>
2022-07-09 11:59:27 -04:00
Cyborgmas 0c030ee5e1 1.19 - Remove GlobalLootModifierSerializer and move to Codecs (#8721)
* convert GLM serializer class to codec

* cleanup

* GLM list needs to be sorted

* datagen

* simplify serialization

* fix test mods (oops)

* properly use suppliers for codec as they are registry obj
2022-07-09 11:51:41 -04:00
Random832 1134fac54c Implement item hooks for potions and enchantments (#8718)
* Implement item hooks for potions and enchantments

* code style fixes
2022-07-09 11:48:51 -04:00
desht ac44d72530 Re-apply missing patch to ServerLevel.EntityCallbacks#onTrackingEnd() (#8828) 2022-07-09 11:27:33 -04:00
agnor99 12468bd8d2 Double Bar Rendering fixed (#8806) (#8807)
* Double Bar Rendering fixed (#8806)

* Added requested changes by sciwhiz12
2022-07-09 11:22:15 -04:00
Néstor Amador a98eeb9d08 Multiple tweaks and fixes to the recent changes in the client refactor PR (#8836)
* Add an easy way to get the NamedGuiOverlay from a vanilla overlay

* Fix static member ordering crash in UnitTextureAtlasSprite

* Allow boss bar rendering to be cancelled

* Make fluid container datagen use the new name
2022-07-09 11:20:55 -04:00
Jonathan Colmenares b558b132cd Add FogMode to ViewportEvent.RenderFog (#8825) 2022-07-09 11:20:40 -04:00
Sara Freimer c102633566 Provide additional context to the getFieldOfView event (#8830) 2022-07-09 11:20:29 -04:00
bdew cce002ad29 Pass renderType to IForgeBakedModel.useAmbientOcclusion (#8834) 2022-07-09 11:20:18 -04:00
noeppi_noeppi b1ed321438 Load custom ITransformationServices from the classpath in dev (#8818)
* Add a classpath transformer discoverer to load custom transformation services from the classpath

* Update ClasspathTransformerDiscoverer to 1.18

* Update license year

* Update license header

* Fix the other license headers

* Update ClasspathTransformerDiscoverer to 1.19
2022-07-09 11:05:47 -04:00
Jonathan Coates c7c2478cb0 Handle modded packets on the network thread (#8703)
* Handle modded packets on the network thread

 - On the server we simply need to remove the call to
   ensureRunningOnSameThread.

 - On the client side, we now handle the packet at the very start of the
   call. We make sure we're running from a network thread to prevent
   calling the handling code twice.

   While this does mean we no longer call .release(), in practice this
   doesn't cause any leaks as ClientboundCustomPayloadPacket releases
   for us.

* Clarify behaviour a little in the documentation

* Javadoc formatting

* Add a helper method for handling packets on the main thread

Also rename the network thread one. Should make it clearer the expected
behaviour of the two, and make it clearer there's a potentially breaking
change.

* Add back consumer() methods

Also document EventNetworkChannel, to clarify the thread behaviour
there.

* Add since = "1.19" to deprecated annotations
2022-07-09 11:02:44 -04:00
Marc Hermans b70d99489b Cache resource listing calls in resource packs (#8829)
* Make the resource lookups cached.

* Include configurability and handle patch cleanup.

* Document and comment the cache manager.

* Make thread selection configurable.

* Implement a configurable loading mechanic that falls back to default behaviour when the config is not bound yet.

* Use boolean supplier and fix wildcard import.

* Clean up the VPR since this is more elegant.

* Clean up the VPR since this is more elegant.

* Address review comments.

* Address more review comments.

* Fix formatting on `getSource`

* Address comments by ichtt

* Adapt to pups requests.

* Stupid idea.

* Attempt this again with a copy on write list.

* Fix a concurrency and loading issue.

* Fix #8813
Checks if the paths are valid resource paths.

* Move the new methods on vanilla Patch.
2022-07-09 10:58:11 -04:00
cpw 57f45eaca0 Update SJH and JIJ 2022-07-09 10:42:30 -04:00
Marc Hermans 52fcc4d649 Fix #8833 (#8835) 2022-07-09 10:09:27 -04:00
Néstor Amador 959ab9abc9 Fix backwards fabulous check in SimpleBakedModel (#8832)
Yet another blunder we missed during the review of #8786.
2022-07-09 10:09:16 -04:00
Néstor Amador c3a91872fe Make texture atlas in StandaloneGeometryBakingContext configurable (#8831) 2022-07-09 10:08:43 -04:00
Néstor AmadorandDennis C 4d074f0a1e [1.19.X] Client code cleanup, updates, and other refactors (#8786)
* Revert "Allow safely registering RenderType predicates at any time (#8685)"

This reverts commit be7275443f.

* Renderable API refactors
- Rename "render values" to "context"
- Rename SimpleRenderable to CompositeRenderable to better reflect its use
- Remove IMultipartRenderValues since it doesn't have any real use
- Add extensive customization options to BakedModelRenderable

* ClientRegistry and MinecraftForgeClient refactors
- Add sprite loader manager and registration event
- Add spectator shader manager and registration event
- Add client tooltip factory manager and registration event
- Add recipe book manager and registration event
- Add key mapping registration event
- Remove ClientRegistry, as everything has been moved out of it
- Remove registration methods from MinecraftForgeClient, as they have dedicated events now

* Dimension special effects refactors
- Fold handlers into an extension class and remove public mutable fields
- Add dimension special effects manager and registration event

* HUD overlay refactors
- Rename to IGuiOverlay match vanilla (instead of Ingame)
- Add overlay manager and registration event
- Move vanilla overlays to a standalone enum

* Model loader refactors
- Rename IModelLoader to IGeometryLoader
- Add loader manager and registration event
- Fold all model events into one
- Move registration of additionally loaded models to an event
- Remove ForgeModelBakery and related classes as they served no purpose anymore

* Render properties refactors
- Rename all render properties to client extensions and relocate accordingly
- Move lookups to the respective interfaces

* Model data refactors
- Convert model data to a final class backed by an immutable map and document mutability requirements. This addresses several thread-safety issues in the current implementation which could result in race conditions
- Transfer ownership of the data manager to the client level. This addresses several issues that arise when multiple levels are used at once

* GUI and widget refactors
- Move all widgets to the correct package
- Rename GuiUtils and children to match vanilla naming

* New vertex pipeline API
- Move to vanilla's VertexConsumer
- Roll back recent PR making VertexConsumer format-aware. This is the opposite of what vanilla does, and should not be relevant with the updated lighting pipeline

* Lighting pipeline refactors
- Move to dedicated lighting package
- Separate flat and smooth lighters
- Convert from a vertex pipeline transformer to a pure vertex source (input is baked quads)

* Model geometry API refactors
- Rename IModelGeometry to IUnbakedGeometry
- Rename IModelConfiguration to IGeometryBakingContext
- Rename other elements to match vanilla naming
- Remove current changes to ModelState, as they do not belong there. Transforms should be specified through vanilla's system. ModelState is intended to transfer state from the blockstate JSON
- Remove multipart geometries and geometry parts. After some discussion, these should not be exposed. Instead, geometries should be baked with only the necessary parts enabled

* Make render types a first-class citizen in baked models
- Add named render types (block + entity + fabulous entity)
- Add named render type manager + registration event
- Make BakedModel aware of render types and transfer control over which ones are used to it instead of ItemBlockRenderTypes (fallback)
- (additional) Add concatenated list view. A wrapper for multiple lists that iterates through them in order without the cost of merging them. Useful for merging lists of baked quads

* General event refactors
- Several renames to either match vanilla or improve clarity
- Relocate client chat event dispatching out of common code

* Forge model type refactors
- Rename SeparatePerspectiveModel to SeparateTransformsModel
- Rename ItemModelMesherForge to ForgeItemModelShaper
- Rename DynamicBucketModel to DynamicFluidContainerModel
- Prefix all OBJ-related classes with "Obj" and decouple parsing from construction
- Extract ElementsModel from model loader registry
- Add EmptyModel (baked, unbaked and loader)
- Refactor CompositeModel to take over ItemMultiLayerBakedModel
- Remove FluidModel as it's not used and isn't compatible with the new fluid rendering in modern versions
- Move model loader registration to a proper event handler
- Update names of several JSON fields (backwards-compatible)
- Update datagens to match

* Miscellaneous changes and overlapping patches
- Dispatch all new registration events
- Convert ExtendedServerListData to a record
- Add/remove hooks from ForgeHooksClient as necessary

* Update test mods

* Fix VertexConsumerWrapper returning parent instead of itself

* Additional event cleanup pass

As discussed on Discord:
- Remove "@hidden" and "@see <callsite>" javadoc annotations from all client events and replace them with @ApiStatus.Internal annotation
- Make all events that shouldn't be fired directly into abstract classes with protected constructors
- Another styling pass, just in case (caught some missed classes)

* Add proper deprecation javadocs and de-dupe some vertex consumer code

* Replace sets of chunk render types with a faster BitSet-backed collection

This largely addresses potential performance concerns that using a plain HashSet might involve by making lookups and iteration as linear as they can likely be (aside from using a plain byte/int/long for bit storage). Further performance concerns related to the implementation may be addressed separately, as all the implementation details are hidden from the end user

* Requested changes

- Remove MinecraftForgeClient and move members to Minecraft, IForgeMinecraft and StencilManager
- Allow non-default elements to be passed into VertexConsumer and add support to derived classes
- Move array instantiation out of quad processing in lighting pipeline
- Fix flipped fluid container model
- Set default UV1 to the correct values in the remapping pipeline
- Minor documentation changes

* Add/update EXC entries and fix AT comment

* Add test mod as per Orion's request

* Additional requested changes

* Allow custom model types to request the particle texture to be loaded

* Even more requested changes

* Improve generics in ConcatenatedListView and add missing fallbacks

* Fix fluid render types being bound to the fluid and not its holder

* Remove non-contractual nullability in ChunkRenderTypeSet and add isEmpty

Additionally, introduce chunk render type checks in ItemBlockRenderTypes

Co-authored-by: Dennis C <[email protected]>
2022-07-07 18:34:51 -04:00
Paint_Ninja c2225ab1da Implement full support for IPv6 (#8742) 2022-07-01 15:38:53 -07:00
Dennis C bb03b1a7d1 Fix certain user-configured options being overwritten incorrectly due to validators. (#8780) 2022-06-28 13:23:35 -07:00
Dennis C be7275443f Allow safely registering RenderType predicates at any time (#8685) 2022-06-27 21:18:53 -07:00
Dennis C dc1c973684 Fix crash after loading error due to fluid texture gathering and config lookup (#8802) 2022-06-27 21:16:54 -07:00
Marc Hermans 06d568408c Remove the configuration option for handling empty tags in ingredients. (#8799)
Now empty tags are considered broken in all states.
2022-06-27 21:16:20 -07:00
TelepathicGrunt 2256e88fb4 Fix MC-105317 Structure blocks do not rotate entities correctly when loading (#8792) 2022-06-27 21:15:22 -07:00
Jonathan Coates 9b962c8ff0 Fire ChunkWatchEvents after sending packets (#8747) 2022-06-27 21:09:23 -07:00
cech12 7656b75992 Add item handler capability to chest boats (#8787) 2022-06-27 21:04:42 -07:00
Joseph B 58a5449c2f Add getter for correct BiomeSpecialEffectsBuilder to BiomeInfo$Builder (#8781) 2022-06-27 21:01:01 -07:00
Michael Bunting 702abe0665 Fix BlockToolModificationEvent missing cancelable annotation (#8778) 2022-06-27 20:59:47 -07:00
Sara Freimer 2070b77fd4 Fix ticking chunk tickets from forge's chunk manager not causing chunks to fully tick (#8775) 2022-06-27 20:58:57 -07:00
Dennis C fd00822015 Fix default audio device config loading string comparison issue (#8767) 2022-06-27 20:54:54 -07:00
dhyces 17565c0ea2 Fix missed vanilla method overrides in ForgeRegistry (#8766) 2022-06-27 20:48:11 -07:00
Budschie 22668ad2c2 Add MinecraftServer reference to ServerTickEvent (#8765) 2022-06-27 20:45:35 -07:00
Joseph B 0dcc3d700d Fix TagsProviders for datapack registries not recognizing existing files (#8761) 2022-06-27 20:43:50 -07:00
Dennis C 2ab3d9b8a3 Add callback after a BlockState was changed and the neighbors were updated (#8686) 2022-06-27 20:21:34 -07:00
Ketok d578d8f6df Add biome tag entries for 1.19 biomes (#8684) 2022-06-27 20:08:21 -07:00
eerussianguy fd61016a7e Make fishing rods use tool actions for relevant logic (#8681) 2022-06-27 20:07:51 -07:00
cpw 7f77d24f45 Update BootstrapLauncher to 1.1.1 and remove the forced
merge of text2speech since new BSL does it.

Signed-off-by: cpw <[email protected]>
2022-06-20 22:27:08 -04:00
cpw 3ebdcb34d1 Merge text2speech libs together so the natives are part of the jar
Signed-off-by: cpw <[email protected]>
2022-06-20 18:19:45 -04:00
King Lemming 05240fecf7 Make Forge ConfigValues implement Supplier. (#8776) 2022-06-20 23:07:48 +01:00
cpw d5860d7cfa Fix merge derp in AbstractModProvider and logic derp in ModDiscoverer
Signed-off-by: cpw <[email protected]>
2022-06-20 16:32:46 -04:00
cpw 7bb97dafda Add "send to mods in order" method to ModList and use it (#8759)
* Add "send to mods in order" method to ModList and use it in RegistryEvents and DataGen..
* Also preserve order in runAll
* Do better comparator thanks @pupnewfster
* postEvent as well.
2022-06-20 16:11:33 -04:00
cpw ca90babc4a Update SJH to 2.0.2.. (#8774)
* Update SJH to 2.0.3..
2022-06-20 16:11:13 -04:00
cpw 4b813e4319 Sanity check the version specified in the mod file (#8749)
* Sanity check the version specified in the mod file to
make sure it's compatible with JPMS standards for
version strings.

Closes #8748

Requires SPI 6
2022-06-20 16:09:19 -04:00
Marc Hermans fa84c22662 Fix SP-Devtime world loading crash due to missing server configs (#8757) 2022-06-17 11:38:41 -07:00
sciwhiz12 2ae640b530 Remove ForgeWorldPreset and related code (#8756)
Vanilla has a working replacement.
2022-06-17 08:56:12 +01:00
sciwhiz12 4f95a20c5e Change ConfigValue#get() to throw if called before config loaded (#8236)
This prevents silent issues where a mod gets the value of the setting
before configs are loaded, which means the default value is always
returned.

As there may be situations where the getting the config setting before
configs are loaded is needed, and it is not preferable to hardcode the
default value, the original behavior is made available through #getRaw.

Implements and closes #7716

* Remove getRaw() method

This is effectively replaced with the expression `spec.isLoaded() ?
configValue.get() : configValue.getDefault()`.

* Remove forceSystemNanoTime config setting

As implemented, it never had any effect as any place where the config
value would be queried happens before the configs are loaded.
2022-06-17 08:31:45 +01:00
sciwhiz12 915d570b17 Fix EnumArgument to use enum names for suggestions (#8728)
Previously, the suggestions used the string representation of the enum
through Enum#toString, which can differ from the name of the enum as
required by Enum#valueOf, causing invalid suggestions (both in gui and
through the error message).
2022-06-15 14:15:32 -07:00
Marc Hermans 5979636ee5 Jar-In-Jar (#8715) 2022-06-15 12:27:47 -07:00
Silverminer007 5acfc6ae3c [1.19] Fix data-gen output path of custom data-pack registries (#8724) 2022-06-15 16:22:00 +01:00
ChampionAsh5357 48341120fb Fix player dive and surface animations in custom fluids (#8738) 2022-06-15 14:02:16 +01:00
ChampionAsh5357 ff45bb4c27 [1.19.x] Affect ItemEntity Motion in Custom Fluids (#8737) 2022-06-15 14:01:57 +01:00
KnightMiner eabba46595 [1.19] Add support for items to add enchantments without setting them in NBT (#8719) 2022-06-15 00:52:58 +01:00
Joseph B 683bf99fe4 [1.19.x] Add stock biome modifier types for adding features and spawns (#8697) 2022-06-15 00:39:03 +01:00
ChampionAsh5357 2a05c7c01d [1.19.x] Fluid API Overhaul (#8695) 2022-06-14 23:07:51 +01:00
Sara Freimer e386229b66 Replace StructureSpawnListGatherEvent with StructureModifiers (#8717) 2022-06-14 12:05:07 -07:00
Magnus Jensen 59b8ec5625 Use stack sensitive translation key by default for FluidAttributes. (#8707) 2022-06-14 10:18:27 -07:00
ferriarnus 50ae5db357 Delete LootItemRandomChanceCondition which added looting bonus enchantment incorrectly. (#8733) 2022-06-14 10:17:38 -07:00
cpw 3aa99a1250 Update EventBus to 6.0, ModLauncher to 10.0.1 and BootstrapLauncher to 1.1 (#8725) 2022-06-14 10:15:46 -07:00
coehlrich 00c8513129 Replace support bot with support action (#8700) 2022-06-13 17:36:16 -07:00
Brennan Ward 9c1404fecc Fix Reach Distance / Attack Range being clamped at 6.0 (#8699) 2022-06-13 17:33:02 +01:00
Joseph B c11686ab6c [1.19.x] Fix mods' worldgen data not being loaded when creating new singleplayer worlds (#8693) 2022-06-13 17:21:21 +01:00
SizableShrimp 2818984f6c [1.19.x] Fix experimental confirmation screen (#8727)
Signed-off-by: SizableShrimp <[email protected]>
2022-06-13 17:20:50 +01:00
TelepathicGrunt db225ed04a Move is_mountain to forge's tag instead of vanilla's (#8726) 2022-06-13 02:39:04 +01:00
ChampionAsh5357 0ec4c34a34 [1.19.x] Add CommandBuildContext to Register Command Events (#8716) 2022-06-12 23:36:25 +01:00
Sara Freimer 597ebc3267 Only rewrite datagen cache when needed (#8709) 2022-06-10 20:35:40 -07:00
cpw e16f54eebe Implement a simple feature system for Forge (#8670)
* Implement a simple feature system for Forge. Allows mods to demand certain features are available in the loading system. An example for java_version is provided, but not expected to be used widely. This is more targeted to properties of the display, such as GL version and glsl profile.

Requires https://github.com/MinecraftForge/ForgeSPI/pull/13 to be merged first in ForgeSPI, and the SPI to be updated appropriately in build.gradle files.

* rebase onto 1.19 and add in SPI update
2022-06-09 17:00:39 -04:00
cpw 27480c15fa displayTest option in mods.toml (#8656)
* displayTest option in mods.toml
* "MATCH_VERSION" (or none) is existing match version string behaviour
* "IGNORE_SERVER_VERSION" accepts anything and sends special SERVERONLY string
* "IGNORE_ALL_VERSION" accepts anything and sends an empty string
* "NONE" allows the mod to supply their own displaytest using the IExtensionPoint mechanism.


* Update display test with feedback and added the mods.toml discussion in mdk.
2022-06-09 16:39:00 -04:00
cpw df0dea59f3 Update forgeSPI to v5 (#8696) 2022-06-09 15:59:48 -04:00
gigaherz d7bc564512 Make IVertexConsumers such as the lighting pipeline, be aware of which format they are dealing with. (#8692)
Also fix Lighting pipeline ignoring the overlay coords from the block renderer.
2022-06-09 21:39:16 +02:00
ferriarnus e6a73ef36c Fixed misaligned patch to invalidateCaps in Entity (#8705) 2022-06-09 11:41:51 -07:00
SizableShrimp 1ba7c6aecd Fix readAdditionalLevelSaveData (#8704) 2022-06-09 11:40:42 -07:00
AzureDoom 7c5e95222e Fixes setPos to syncPacketPositionCodec (#8702) 2022-06-09 11:40:21 -07:00
SizableShrimp 38df3d2a15 Fix wrong param passed to PlayLevelSoundEvent.AtEntity (#8688) 2022-06-09 11:39:35 -07:00
ferriarnus 3beaa817f3 Override initialize in SlotItemHandler, so it uses the itemhandler instead of container (#8679) 2022-06-09 11:39:04 -07:00
SizableShrimp 3ebd7ba0ef Update MDK for 1.19 changes (#8675) 2022-06-09 11:37:52 -07:00
LexManos 1ba9c34650 Add helper to RecipeType, and fix eclipse compiler error in test class. 2022-06-09 11:36:52 -07:00
cpw 2f9e726aea Update modlauncher to latest (#8691) 2022-06-08 16:02:50 -07:00
Sara Freimer ade2b8cc80 Fix getting entity data serializer id crashing due to improper port to new registry system (#8678) 2022-06-08 09:14:41 -07:00
Sara Freimer 9b144f4541 Fire registry events in the order vanilla registers to registries (#8677)
Custom registries are still fired in alphabetical order, after all vanilla registries.
Move forge's data_serializers registry to forge namespace.
2022-06-08 09:08:55 -07:00
cpw 0c35436217 Add method with pre/post wrap to allow setting/clearing mod context. (#8682)
Fixes ActiveContainer in ModContext not being present in registry events. Closes #8680
2022-06-08 08:38:53 -07:00
SizableShrimp 0e66b7b217 Fix the Curlie oopsie
Signed-off-by: SizableShrimp <[email protected]>
2022-06-07 17:08:56 -04:00
4ed9117799 Forge 1.19
* Bump pack.mcmeta formats
* 1.19 biome modifiers
* Mark ClientPlayerNetworkEvent.LoggedOutEvent's getters as nullable
* Add docs and package-info to client extension interfaces package
* Move RenderBlockOverlayEvent hooks to ForgeHooksClient
* Add package-infos to client events package
* Rename SoundLoadEvent to SoundEngineLoadEvent

This reduces confusion from consumers which may think the
name SoundLoadEvent refers to an individual sound being loaded rather
than the sound engine.
* Document and change SoundLoadEvent to fire on mod bus

Previously, it fired on both the mod bus and the Forge bus, which is
confusing for consumers.
* Delete SoundSetupEvent

Looking at its original implementation shows that there isn't an
appropriate place in the new sound code to reinsert the event, and the
place of 'sound engine/manager initialization event' is taken already by SoundLoadEvent.
* Perform some cleanup on client events

 - Removed nullable annotations from ClientPlayerNetworkEvent
 - Renamed #getPartialTicks methods to #getPartialTick, to be consistent
  with vanilla's naming of the partial tick
 - Cleanup documentation to remove line breaks, use the
  spelling 'cancelled' over
  'canceled', and improve docs on existing and
   new methods.
* Remove EntityEvent.CanUpdate

Closes MinecraftForge/MinecraftForge#6394
* Switch to Jetbrains nullability annotations
* New PlayLevelSoundEvent; replaces old PlaySoundAtEntityEvent
* Remove ForgeWorldPresetScreens
* Remove IForgeRegistryEntry
* Remove use of List<Throwable> in FML's CompletableFutures
* Add docs to mod loading stages, stages, and phases
* Gradle 7.4.2
* Use SLF4J in FMLLoader and other subprojects
* Switch dynamic versions in subprojects to pinned ones
* Switch ForgeRoot and MDK to FG plugin markers
* Configure Forge javadoc task

The task now uses a custom stylesheet with MCForge elements, and
configured to combine the generation from the four FML subprojects
(fmlloader, fmlcore, javafmllanguage, mclanguage) and the Forge project
into the javadoc output.
* Update docs/md files, for 1.19 update and the move away from IRC to Discord.
* Make "Potentially dangerous alternative prefix" a debug warning, not info.

Co-authored-by: Curle <[email protected]>
Co-authored-by: sciwhiz12 <[email protected]>
Signed-off-by: SizableShrimp <[email protected]>
2022-06-07 15:31:26 -05:00
ChampionAsh5357 270e778ac5 [1.18.x] Fix #8530 (Banner Pattern) NPE (#8666) 2022-06-07 17:43:17 +01:00
Dennis C 9528fd8bcc Revert "[1.18.x] Enable the Forge light pipeline by default (#8629)" (#8663) 2022-06-06 03:31:30 +01:00
Take Weiland 5a75314bb3 Fix eager loading of model loaders during datagen causing NPE (#8659) 2022-06-05 20:33:32 +01:00
Autovw cceb8db74e [1.18.x] Fix 2 crossbow animations not applying to custom variants (#8625) 2022-06-05 16:34:02 +01:00
Redstone_DubstepandCurle 8e7b08b96d [1.18.x] Added new Disconnection screen to display better channel/registry mismatch information (#8402)
Co-authored-by: Curle <[email protected]>
2022-06-05 16:01:17 +01:00
Gegy 194f11069f [1.18] Avoid exclusive synchronisation in block render type lookups (#8476) 2022-06-05 15:56:54 +01:00
sciwhiz12 5ef0d667e0 [1.18] Fix command redirects by replacing Commands#fillUsableCommands (#8616)
Commands#fillUsableCommands does not handle redirect nodes properly. It
blindly pulls from the cached node map for a redirect node, which may
have no result because the target node of the redirect has not been
visited yet.

CommandHelper#mergeCommandNode is better in this regard, because it
visits the target node of the redirect instead of blindly pulling from
the node map.

Fixes #7551
2022-06-05 15:47:52 +01:00
sciwhiz12 8a1c1d7e53 [1.18] Fix tags command suggestion to include dynamic registries (#8638)
Manually entering the names of dynamic registries work as expected. The
suggestions for registries didn't include dynamic registries because
they queried the static registries rather than querying all registries
from the server's `RegistryAccess` (which includes both static
registries and dynamic registries).
2022-06-05 15:45:14 +01:00
cpwandCurle 1353c1d9fa Low code language provider (#8633)
Signed-off-by: cpw <[email protected]>

Co-authored-by: Curle <[email protected]>
2022-06-05 15:44:46 +01:00
SizableShrimp 70a8663605 [1.18.x] Add checkJarCompatibility task (#8644)
Signed-off-by: SizableShrimp <[email protected]>
2022-06-05 15:40:26 +01:00
ChampionAsh5357andCurle f179eed971 [1.18.x] Fix Banner Patterns not being extensible (#8530)
Co-authored-by: Curle <[email protected]>
2022-06-05 15:39:29 +01:00
Flemmli97 d4c8708229 1.18.x Port BiomeDictionary over to biome tags (#8564) 2022-06-05 15:35:06 +01:00
Dennis C 4008057803 [1.18.x] Enable the Forge light pipeline by default (#8629) 2022-06-05 15:28:46 +01:00
997d8e0aa2 [1.18.x] Allow Modders to add custom data-pack registries through RegistryBuilder (#8630)
Co-authored-by: Silverminer007 <[email protected]>
Co-authored-by: Curle <[email protected]>
2022-06-05 15:27:30 +01:00
Take Weiland 15bd5f0d30 [1.18] Allow custom rarities to directly modify the style (#8648) 2022-06-05 15:24:54 +01:00
Take Weiland ccaa20b006 [1.18] Fix reload listener registration in ModelLoaderRegistry (#8650) 2022-06-05 15:24:29 +01:00
Dennis C c48698f680 Fix wrong position in Level#hasChunk() call in IForgeBlockGetter (#8654) 2022-06-05 15:23:49 +01:00
Take Weiland 4285c931e7 Add ItemStack context to Enchantment#getDamageBonus (#8635) 2022-06-01 09:19:18 -07:00
Brennan Ward 03c43cf3da Fix attack cooldown bar showing when out of reach of target. Closes #8639 (#8640) 2022-05-31 11:07:22 -07:00
Take Weiland 8d14797416 Improve TagsUpdatedEvent by adding update case information. (#8636) 2022-05-31 11:03:55 -07:00
sciwhiz12 e4837f65a3 Deprecate patched-in Style methods for removal, vanilla variants exist. (#8615) 2022-05-31 10:38:25 -07:00
sciwhiz12 ee46af66e5 Add pack-type-specific format versions in pack metadata. Fixes #8563 (#8612) 2022-05-31 10:36:44 -07:00
SizableShrimp 2bec354317 Catch and aggregate exceptions during NewRegistryEvent (#8601) 2022-05-31 10:34:06 -07:00
DaemonUmbra 727c7780b3 Added removeErroringEntities config option (#8627)
Counterpart to removeErroringBlockEntities
2022-05-23 20:03:03 +01:00
Take Weiland 5410f9b532 Add missing patches for not lowering mipmap level for small textures (#8622) 2022-05-23 20:02:51 +01:00
gigaherz 4c09d8d774 Make TransformType enum extensible, includes the possibility to specify a fallback type for cases where models don't specify the transform explicitly. (#8566)
Makes it possible to add new perspectives.
2022-05-22 17:55:48 +01:00
LexManos aded6fb847 Fix CheckSAS to actually validate that the SAS line will do something.
This removes all method level lines as they are not necessary now.
2022-05-21 13:16:58 -07:00
Brennan Ward c2ad951336 Add Attack Range attribute and Update Reach Distance (#8478) 2022-05-21 16:44:36 +01:00
Alex Thomson 7fb5dfa3e6 Implement PlayerNegotiationEvent (#8599) 2022-05-14 17:27:54 +01:00
sciwhiz12 0f8c2fa454 Fire EntityTravelToDimensionEvent in all expected cases (#8614)
Fixes #8520
2022-05-08 06:12:21 +01:00
sciwhiz12 dfc2aa9c6a Fix isEdible crash (#8613)
Fixes #8602
2022-05-08 04:41:30 +01:00
LexManos 8865092bb2 Fix typo in Snow Golem patch causing them to destroy blocks they shouldn't. Closes #8611 2022-05-07 12:24:09 -07:00
Sara Freimer abc3353375 Re-introduce an attribute for entity step height in a non-breaking way (#8607) 2022-05-05 19:11:59 +01:00
ChampionAsh5357 e7a8da4d0b Revert step height attribute (#8604) 2022-05-05 14:07:07 +01:00
SizableShrimp f938051373 [1.18.x] Fix ContextNbtProvider serializing the wrong name (#8575) 2022-05-03 20:27:37 +01:00
Dennis C 9db0d72232 Let BaseRailBlock decide if the calculated RailShape for placement or connection to neighbors is valid (#8562) 2022-05-03 19:58:53 +01:00
cech12 9e6e789b73 Fix shears not playing a breakage sound (#8600) 2022-05-03 19:17:05 +01:00
Leon Linhart bc0d7179cc Update IExtensionPoint javadoc to be correct (#8568) 2022-05-03 18:38:28 +01:00
Meldexun aafa34439a Fix IDs of Multi-part entities being desynced across client and server. (#8576) 2022-05-03 18:29:21 +01:00
Brennan Ward 168a3bc6fc Expose ICondition$IContext to modded reload listeners. (#8596) 2022-05-03 18:25:42 +01:00
Silver_David 2fb8b2eb18 Fix undead causing chunkloading leading to extensive lag (#8583) 2022-05-03 18:24:13 +01:00
DaemonUmbra da82705e86 Accomodate Bukkit-like Servers not sending Command Args (#8582)
Note to modded server devs:
Please, please, please implement full compatibility with [Brigadier](https://github.com/Mojang/Brigadier) instead of hacking around it.
2022-05-03 18:21:53 +01:00
Phylogeny a237a39d52 Restore translation key comments to ForgeConfigSpec (#8584) 2022-05-03 18:20:21 +01:00
Meldexun c042dc5542 Fix entity parts being ignored when collecting entities in an AABB (#8588)
Previously entity parts are only taken into consideration when the parent entity is in a chunk that intersects with the AABB
2022-05-03 18:17:26 +01:00
KnightMiner 1493d06b5a Fix EntityInteraction event running twice on client side (#8598) 2022-05-03 18:08:47 +01:00
Vincent Lee 9a11201a9e Introduce an attribute for step height additions (#8389) 2022-05-03 18:01:45 +01:00
KnightMiner c34094ea50 Fix shears passing on a client when interacting with a shearable entity (#8597)
Caused the offhand item to also interact with the entity, possibly sending a packet to the server causing both hands to interact
2022-05-02 10:46:58 -07:00
Alex Thomson c531b2b5c7 Allow Exception passed through CommandEvent to propagate (#8590) 2022-05-02 10:43:49 -07:00
8ade7bbaeb Mark 1.18.2 Recommended Build.
Co-authored-by: sciwhiz12 <[email protected]>
Co-authored-by: Marc Hermans <[email protected]>
Co-authored-by: Curle <[email protected]>
Co-authored-by: SizableShrimp <[email protected]>
Co-authored-by: David Quintana <[email protected]>
2022-04-18 16:35:06 -07:00
sciwhiz12 6347071976 Add event for hooking into StructuresBecomeConfiguredFix Fixes #8505
Pass-through unknown structure IDs with "unknown." prefix
This avoids the fixer throwing an exception due to the unknown
structure, which causes the chunk data to be dropped (and freshly
regenerated later). The deserializer logs and ignores the unknown
structure ID, avoiding full chunk data loss.
2022-04-17 15:12:05 -07:00
Wyn Price 17bd0c4f91 Fix missed patch for loading modded dimensions on Dedicated Server start (#8555) 2022-04-17 14:41:31 -07:00
LexManos bafaf90329 Fix potential concurrency issues with BiomeDictionary. Closes #8266 2022-04-14 11:13:14 -07:00
LexManos fcb81aa660 Fix debug text being rendered slightly wrong. 2022-04-14 10:58:43 -07:00
LexManos eb8fe02a5e Fix intrusive handlers on dummy objects. 2022-04-13 23:21:57 -07:00
sciwhiz12 c1874bb5ab Amend license header to include contributors and apply to FML subprojects (#8525)
After internal discussion, it was decided that we need to include
"contributors" to the license header. This avoids claiming that the
Java source files are under the exclusive copyright ownership of Forge
LLC, which excludes contributors that still hold copyright ownership
over their contributions (but is licensed under the LGPLv2.1 as stated
in the Forge CLA).
2022-04-13 20:42:00 -07:00
Robert Kämpf f0b90dc35d Add modern implementation of the Slider widget and deprecate the old one (#8496) 2022-04-13 19:59:38 +02:00
canitzp eed24f017f Implement ItemStack and LivingEntity sensitive method to get the FoodProperties for an Item (#8477) 2022-04-13 10:45:08 -07:00
SizableShrimp 7d79f0d581 Add use context and simulate flag to getToolModifiedState, enabled HOE_TILL action. (#8557) 2022-04-12 09:43:20 -07:00
sciwhiz12 94ce098056 Remove bad patch for AbstractFurnaceBlockEntity (#8561)
The patch prevented the entity from being marked as changed when an
item finished smelting.
2022-04-11 15:50:06 +01:00
Sara Freimer fbaf92105e Fix issues with custom forge ingredients causing sub ingredients to be prematurely and invalidly cached (#8550)
Add config option and skip checking for empty ingredients in shapeless recipe deserialization
2022-04-08 15:37:43 -07:00
Minecraftschurli 1e6845669c Add EnderManAngerEvent to make it possible to prevent endermen getting angry at a player based on more then their helmet. (#8406) 2022-04-08 10:14:50 -07:00
LexManos 796cb29904 Fix misaligned patch in BlockEntity.save. 2022-04-07 11:40:08 -07:00
SizableShrimp 42472a8ab8 Expose getHolder() method on RegistryObject, as helper for when absolutely necessary to pass into Vanilla code. (#8548)
It is recommended you avoid if you can.
2022-04-06 14:13:17 -07:00
Sara Freimer 954db14250 Fix return value of Recipe#isIncomplete being inaccurate for empty tags (#8549) 2022-04-04 18:10:41 -07:00
KnightMiner 56976e4d20 Simplfy default behavior of isSimple (#8543)
isSimple should only return true if the ingredient is any more sensitive then JUST itemA == itemB
It used to take metadata and damage into account, but that was removed in the flattening.
Also prevents fetching tag values too early, as tags are not ready during the ingredient constructor
2022-04-04 18:05:41 -07:00
Sr_endi c38e0de8f5 Implement IPlantable in BambooBlock (#8508) 2022-04-04 18:02:33 -07:00
LexManos d33f24ff33 Fix compiler error in eclipse, bump MCPConfig for FF/Record fix. 2022-04-04 18:02:20 -07:00
Technici4n 87c4f90fb2 Fix TagEmptyCondition by passing tag context into conditional and recipe deserializers. (#8546) 2022-04-02 00:09:20 -07:00
SizableShrimp 1f031ad2c3 Allow using DeferredRegisters for vanilla registries (#8527)
Catch and aggregate exceptions when applying object holders
2022-04-01 13:49:46 -07:00
sciwhiz12 8f0d3da1e4 Remove cut copper from copper storage blocks tag. Closes #8403 (#8539) 2022-03-30 12:57:37 -07:00
ChampionAsh5357 10ca83e90d Fix brewing stand input placement not auto-splitting stackable potions. (#8534) 2022-03-30 12:56:46 -07:00
SizableShrimp e5d4e5d3ab Fix lost validation of registry data on singleplayer world load (#8533)
Fix some leftover 1.18.2 TODOs
2022-03-25 17:52:48 -07:00
Cyborgmas c7e4e60052 1.18 Allow mod menus to have their own recipebook (#8028) 2022-03-24 22:08:17 +00:00
KnightMiner 093acab825 [1.18.x] Add 3 new ingredient types, and make existing ingredients compatible with datagen (#8517) 2022-03-24 22:04:32 +00:00
Jonathan Colmenaresandnoeppi_noeppi c3bd365baa [1.18.x] Add the projectile search event. (#8322)
Co-authored-by: noeppi_noeppi <[email protected]>
2022-03-24 21:52:58 +00:00
Cyborgmas 02fd859d5c Allow confirm-and-save of the Experimental Settings warning. (#7275) 2022-03-24 21:36:03 +00:00
KnightMiner 349e12f939 Add hook for powdered-snow shoes (#8514) 2022-03-24 21:17:12 +00:00
Martijn van den Brand 9d3eb9df8f Add some helper Access Transformers (#8490) 2022-03-24 21:14:27 +00:00
sciwhiz12 3c39a36c26 Fix incorrect method used in getStream (#8532)
The original correct method is getPath, but the patch uses getLocation.
The former is the actual resource path to the sound OGG file, while the
latter is the sound's location.

Fixes #8531
2022-03-24 01:11:17 +00:00
KnightMiner b0a64ff8ac [1.18] Make it easier to register custom skull blocks models (#8351) 2022-03-23 01:50:50 +01:00
eerussianguy e1ad29edd0 Rework fog events (#8492) 2022-03-23 01:43:39 +01:00
mezz 5ac711839f Update Forge Auto Renaming Tool to the latest version (#8515) 2022-03-22 00:17:49 -07:00
Take Weiland 415cdd0588 Add patches to enable MobEffects with IDs > 255 (#8380) 2022-03-21 23:10:33 -07:00
Jonathan Coates 7f23b3b525 Allow sound instances to play custom audio streams (#8295) 2022-03-21 22:44:02 -07:00
Joseph Tarbit de1050e420 Fix NPE caused by canceling onServerChatEvent (#8516) 2022-03-17 12:59:20 -07:00
SizableShrimp ad75cb8644 [1.18.2] Fix tags for custom forge registries. (#8495)
Tag-enabled registries must now be registered to vanilla's root registry. See RegistryBuilder#hasTags.
Modded tag-enabled registries have to use the format `data/<namespace>/tags/<registrynamespace>/<registryname>/<tag>.json`
This format is to prevent conflicts for registries with the same path but different namespaces
EX: Registry name `examplemod:shoe`, tag name `blue_shoes` would be `data/<namespace>/tags/examplemod/shoe/blue_shoes.json`
RegistryEvent.NewRegistry has been moved and renamed to NewRegistryEvent.
RegistryBuilder#create has been made private. See NewRegistryEvent#create
Created new ITagManager system for looking up Forge tags. See IForgeRegistry#tags.
Add lookup methods for Holders from forge registries. See IForgeRegistry#getHolder.
2022-03-16 13:36:00 -07:00
Sara Freimer 30f3a7dbba Lower custom item entity replacement from highest to high so mods can cancel it during a specific tick (#8417) 2022-03-15 15:09:28 -07:00
BlueAgent 9d74a3520f Fix MC-176559 related to the Mending enchantment (#7606) 2022-03-15 17:45:23 +00:00
Dennis C 57d6f31adb [1.18.x] Allow blocks to hide faces on a neighboring block (#8300)
* Allow blocks to hide faces on a neighboring block
* Allow blocks to opt-out of external face hiding
2022-03-13 22:59:59 +01:00
SizableShrimp 9bf227afeb [1.18.x] Fix FMLOnly in forgedev and userdev (#8512)
Signed-off-by: SizableShrimp <[email protected]>
2022-03-13 21:48:55 +00:00
Gabriel Harris-Rouquette 72f5b3f84d Clear local variable table on RuntimeEnumExtender transformation (#8502) 2022-03-12 23:08:50 -08:00
Sara Freimer df3c7ce19a Pass server resources to reload listener event (#8493) 2022-03-06 14:57:14 +00:00
Paint_Ninja 6cb444455d Use UTF-8 charset for Java compilation (#8486) 2022-03-06 14:53:15 +00:00
Andy Dingley 64ded22276 Use wither as griefing entity when it indirectly hurts an entity (#8431) 2022-03-06 14:49:53 +00:00
Joseph Tarbit 41c88f6e52 Provide access to the haveTime supplier in WorldTickEvent and ServerTickEvent (#8470) 2022-03-06 14:42:58 +00:00
sciwhiz12 cdd91968b4 Fix durability bar not respecting an item's custom max damage (#8482) 2022-03-06 14:32:36 +00:00
sciwhiz12 898696d770 Add event for controlling potion indicators size (#8483)
This event allows forcing the rendering of the potion indicators in the
inventory screen to either compact mode (icons only) or classic mode
(full width with potion effect name).
2022-03-06 14:31:12 +00:00
sciwhiz12 3dcb4a2c9c Introduce system mods to mod loading (#8238)
Core game mods are mods which are required to exist in the environment
during mod loading. These may be specially provided mods (for example,
the `minecraft` mod), or mods which are vital to the framework which
FML is connected to (for example, Forge and the `forge` mod).

These core game mods are used as the only existing mods in the mod list
if mod sorting or dependency verification fails. This allows later
steps in the which use resources from these mod files to work correctly
(up to when the error screen is shown and the game exits).
2022-03-06 14:21:00 +00:00
sciwhiz12 c458ff17fd Add missing module exports arg to server arguments list (#8500) 2022-03-06 01:31:53 +00:00
Frostygames0 7009c1aa89 Fixed swim speed attribute (#8499) 2022-03-05 15:19:22 +00:00
Dennis C 4311edd4cd Fix incorrect movement distance calculation (#8497) 2022-03-04 03:42:15 +00:00
LexManos 735382be03 Add support to Forge registry wrappers for new Holder system. Closes #8491
Fix TagBasedToolTypesTest not generating needed data correctly.
2022-03-02 13:38:02 -08:00
gigaherz 14bca0d757 Fix JNA not working at runtime and causing issues with natives. 2022-03-02 01:14:57 +01:00
2638 changed files with 77025 additions and 44141 deletions

No files matched your search

-16
View File
@@ -1,16 +0,0 @@
# Configuration for support-requests - https://github.com/dessant/support-requests
# Label used to mark issues as support requests
supportLabel: Forum
# Comment to post on issues marked as support requests. Add a link
# to a support page, or set to `false` to disable
supportComment: >
👋 We use the issue tracker exclusively for final bug reports and feature requests.
However, this issue appears to be better suited for the
[Forge Support Forums](http://www.minecraftforge.net/forum/forum/18-support-bug-reports/).
Please create a new topic on the support forum with this issue, and the conversation can
continue there.
# Whether to close issues marked as support requests
close: true
# Whether to lock issues marked as support requests
lock: true
+16
View File
@@ -0,0 +1,16 @@
name: 'Comment actions'
on: issue_comment
permissions:
contents: read
issues: write
pull-requests: write
discussions: write
jobs:
labels:
uses: MinecraftForge/SharedActions/.github/workflows/comment_actions.yml@main
secrets:
GH_APP_KEY: ${{ secrets.GH_APP_KEY }}
GH_APP_NAME: ${{ secrets.GH_APP_NAME }}
+16
View File
@@ -0,0 +1,16 @@
name: 'Issue actions'
on: issues
permissions:
contents: read
issues: write
pull-requests: write
discussions: write
jobs:
labels:
uses: MinecraftForge/SharedActions/.github/workflows/issue_actions.yml@main
secrets:
GH_APP_KEY: ${{ secrets.GH_APP_KEY }}
GH_APP_NAME: ${{ secrets.GH_APP_NAME }}
+13
View File
@@ -0,0 +1,13 @@
name: 'PR Review'
on: pull_request_review
jobs:
upload_payload:
name: Upload payload
runs-on: ubuntu-latest
steps:
- uses: actions/upload-artifact@v3 # Payload artifact is consumed in Actionable
with:
name: payload
path: ${{ github.event_path }}
@@ -0,0 +1,13 @@
name: 'PR Review Comment'
on: pull_request_review_comment
jobs:
upload_payload:
name: Upload payload
runs-on: ubuntu-latest
steps:
- uses: actions/upload-artifact@v3 # Payload artifact is consumed in Actionable
with:
name: payload
path: ${{ github.event_path }}
@@ -0,0 +1,20 @@
name: 'Pull Request Review Comment actions'
on:
workflow_run:
workflows: [PR Review Comment]
types:
- completed
permissions:
contents: read
issues: write
pull-requests: write
discussions: write
jobs:
labels:
uses: MinecraftForge/SharedActions/.github/workflows/pr_review_comment_actions.yml@main
secrets:
GH_APP_KEY: ${{ secrets.GH_APP_KEY }}
GH_APP_NAME: ${{ secrets.GH_APP_NAME }}
@@ -0,0 +1,20 @@
name: 'Pull Request Review actions'
on:
workflow_run:
workflows: [PR Review]
types:
- completed
permissions:
contents: read
issues: write
pull-requests: write
discussions: write
jobs:
labels:
uses: MinecraftForge/SharedActions/.github/workflows/pr_review_actions.yml@main
secrets:
GH_APP_KEY: ${{ secrets.GH_APP_KEY }}
GH_APP_NAME: ${{ secrets.GH_APP_NAME }}
+18
View File
@@ -0,0 +1,18 @@
name: 'Pull Request actions'
on:
pull_request_target:
types: [assigned, unassigned, labeled, unlabeled, opened, edited, closed, reopened, synchronize, converted_to_draft, ready_for_review, locked, unlocked, review_requested, review_request_removed, auto_merge_enabled, auto_merge_disabled]
permissions:
contents: read
issues: write
pull-requests: write
discussions: write
jobs:
labels:
uses: MinecraftForge/SharedActions/.github/workflows/pr_actions.yml@main
secrets:
GH_APP_KEY: ${{ secrets.GH_APP_KEY }}
GH_APP_NAME: ${{ secrets.GH_APP_NAME }}
+16
View File
@@ -0,0 +1,16 @@
name: 'Push actions'
on: push
permissions:
contents: read
issues: write
pull-requests: write
discussions: write
jobs:
labels:
uses: MinecraftForge/SharedActions/.github/workflows/push_actions.yml@main
secrets:
GH_APP_KEY: ${{ secrets.GH_APP_KEY }}
GH_APP_NAME: ${{ secrets.GH_APP_NAME }}
+5 -4
View File
@@ -23,7 +23,6 @@
/projects/mcp/
/projects/clean/
/projects/forge/
/projects/fmlonly/
#/patches/*
#occupational hazards
@@ -33,11 +32,13 @@
/projects/**/*.launch
/repo/
src/generated/resources/.cache/
src/generated_test/resources/.cache/
src/generated_mdk/resources/.cache/
#Patch rejects
/patches-/
*.patch.rej
/projects/*/rejects/
#/patches-/
#*.patch.rej
#/projects/*/rejects/
# FML changelog
changelog.txt
+74 -2
View File
@@ -1,5 +1,7 @@
import jetbrains.buildServer.configs.kotlin.v2019_2.*
import jetbrains.buildServer.configs.kotlin.v2019_2.projectFeatures.githubIssues
import jetbrains.buildServer.configs.kotlin.*
import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.gradle
/*
The settings script is an entry point for defining a TeamCity
@@ -30,11 +32,13 @@ project {
buildType(Build)
buildType(BuildSecondaryBranches)
buildType(PullRequests)
buildType(PullRequestChecks)
buildType(PullRequestCompatibility)
params {
text("docker_jdk_version", "17", label = "Gradle version", description = "The version of the JDK to use during execution of tasks in a JDK.", display = ParameterDisplay.HIDDEN, allowEmpty = false)
text("docker_gradle_version", "7.4", label = "Gradle version", description = "The version of Gradle to use during execution of Gradle tasks.", display = ParameterDisplay.HIDDEN, allowEmpty = false)
text("git_main_branch", "1.18.x", label = "Git Main Branch", description = "The git main or default branch to use in VCS operations.", display = ParameterDisplay.HIDDEN, allowEmpty = false)
text("docker_gradle_version", "7.5.1", label = "Gradle version", description = "The version of Gradle to use during execution of Gradle tasks.", display = ParameterDisplay.HIDDEN, allowEmpty = false)
text("git_main_branch", "1.20.x", label = "Git Main Branch", description = "The git main or default branch to use in VCS operations.", display = ParameterDisplay.HIDDEN, allowEmpty = false)
text("git_branch_spec", """
+:refs/heads/(main*)
+:refs/heads/(master*)
@@ -134,3 +138,71 @@ object PullRequests : BuildType({
""".trimIndent()
}
})
object PullRequestChecks : BuildType({
templates(AbsoluteId("MinecraftForge_BuildPullRequests"), AbsoluteId("MinecraftForge_SetupGradleUtilsCiEnvironmen"), AbsoluteId("MinecraftForge_BuildWithDiscordNotifications"), AbsoluteId("MinecraftForge_SetupProjectUsingGradle"))
id("MinecraftForge_MinecraftForge__PullRequestChecks")
name = "Pull Requests (Checks)"
description = "Checks pull requests for the project"
steps {
gradle {
name = "Check"
id = "RUNNER_10_Check"
tasks = "checkAll"
gradleParams = "--continue %gradle_custom_args%"
enableStacktrace = true
dockerImage = "%docker_gradle_image%"
dockerRunParameters = """
-v "/opt/cache/agent/gradle:/home/gradle/.gradle"
-v "/opt/cache/shared/gradle:/home/gradle/rocache:ro"
--network=host
-u 1000:1000
%docker_additional_args%
""".trimIndent()
}
}
vcs {
branchFilter = """
+:*
-:1.*
-:<default>
""".trimIndent()
}
})
object PullRequestCompatibility : BuildType({
templates(AbsoluteId("MinecraftForge_BuildPullRequests"), AbsoluteId("MinecraftForge_SetupGradleUtilsCiEnvironmen"), AbsoluteId("MinecraftForge_BuildWithDiscordNotifications"), AbsoluteId("MinecraftForge_SetupProjectUsingGradle"))
id("MinecraftForge_MinecraftForge__PullRequestCompatibility")
name = "Pull Requests (Compatibility)"
description = "Validates binary compatibility for pull requests made to the project"
steps {
gradle {
name = "Validate"
id = "RUNNER_10_Compatibility"
tasks = "checkJarCompatibility"
gradleParams = "--continue %gradle_custom_args%"
enableStacktrace = true
dockerImage = "%docker_gradle_image%"
dockerRunParameters = """
-v "/opt/cache/agent/gradle:/home/gradle/.gradle"
-v "/opt/cache/shared/gradle:/home/gradle/rocache:ro"
--network=host
-u 1000:1000
%docker_additional_args%
""".trimIndent()
}
}
vcs {
branchFilter = """
+:*
-:1.*
-:<default>
""".trimIndent()
}
})
Vendored
-125
View File
@@ -1,125 +0,0 @@
@Library('forge-shared-library')_
pipeline {
options {
disableConcurrentBuilds()
}
agent {
docker {
image 'gradle:7-jdk16'
//args '-v forgegc:/home/gradle/.gradle_cache/'
}
}
environment {
GRADLE_ARGS = '--no-daemon --console=plain' // No daemon for now as FG3 kinda derps. //'-Dorg.gradle.daemon.idletimeout=5000'
DISCORD_WEBHOOK = credentials('forge-discord-jenkins-webhook')
DISCORD_PREFIX = "Job: Forge Branch: ${BRANCH_NAME} Build: #${BUILD_NUMBER}"
JENKINS_HEAD = 'https://wiki.jenkins-ci.org/download/attachments/2916393/headshot.png'
}
stages {
stage('notify_start') {
when {
not {
changeRequest()
}
}
steps {
discordSend(
title: "${DISCORD_PREFIX} Started",
successful: true,
result: 'ABORTED', //White border
thumbnail: JENKINS_HEAD,
webhookURL: DISCORD_WEBHOOK
)
}
}
/*
stage('pull_cache') {
steps {
sh 'cp -r /home/gradle/.gradle_cache/jdks/ /home/gradle/.gradle/jdks/'
sh 'cp -r /home/gradle/.gradle_cache/wrapper/ /home/gradle/.gradle/wrapper/'
sh 'cp -r /home/gradle/.gradle_cache/caches/ /home/gradle/.gradle/caches/'
}
}
*/
stage('setup') {
steps {
withGradle {
sh './gradlew ${GRADLE_ARGS} --refresh-dependencies --continue setup'
}
script {
env.MYVERSION = sh(returnStdout: true, script: './gradlew :forge:properties -q | grep "^version:" | cut -d" " -f2').trim()
env.FMLONLY_VERSION = sh(returnStdout: true, script: './gradlew :fmlonly:properties -q | grep "^version:" | cut -d" " -f2').trim()
}
}
}
stage('changelog') {
when {
not {
changeRequest()
}
}
steps {
writeChangelog(currentBuild, 'build/changelog.txt')
}
}
stage('publish') {
when {
not {
changeRequest()
}
}
environment {
CROWDIN = credentials('forge-crowdin')
KEYSTORE = credentials('forge-jenkins-keystore-old')
KEYSTORE_KEYPASS = credentials('forge-jenkins-keystore-old-keypass')
KEYSTORE_STOREPASS = credentials('forge-jenkins-keystore-old-keypass')
}
steps {
withCredentials([usernamePassword(credentialsId: 'maven-forge-user', usernameVariable: 'MAVEN_USER', passwordVariable: 'MAVEN_PASSWORD')]) {
withGradle {
sh './gradlew ${GRADLE_ARGS} publish -PkeystoreKeyPass=${KEYSTORE_KEYPASS} -PkeystoreStorePass=${KEYSTORE_STOREPASS} -Pkeystore=${KEYSTORE} -PcrowdinKey=${CROWDIN}'
}
}
}
post {
success {
build job: 'filegenerator', parameters: [string(name: 'COMMAND', value: "promote net.minecraftforge:forge ${env.MYVERSION} latest")], propagate: false, wait: false
build job: 'filegenerator', parameters: [string(name: 'COMMAND', value: "promote net.minecraftforge:fmlonly ${env.FMLONLY_VERSION} latest")], propagate: false, wait: false
}
}
}
stage('test_publish_pr') { //Publish to local repo to test full process, but don't include credentials so it can't sign/publish to maven
when {
changeRequest()
}
environment {
CROWDIN = credentials('forge-crowdin')
}
steps {
withGradle {
sh './gradlew ${GRADLE_ARGS} publish -PcrowdinKey=${CROWDIN}'
}
}
}
}
post {
always {
script {
archiveArtifacts artifacts: 'projects/forge/build/libs/**/*.*', fingerprint: true, onlyIfSuccessful: true, allowEmptyArchive: true
if (env.CHANGE_ID == null) { // This is unset for non-PRs
discordSend(
title: "${DISCORD_PREFIX} Finished ${currentBuild.currentResult}",
description: '```\n' + getChanges(currentBuild) + '\n```',
successful: currentBuild.resultIsBetterOrEqualTo("SUCCESS"),
result: currentBuild.currentResult,
thumbnail: JENKINS_HEAD,
webhookURL: DISCORD_WEBHOOK
)
}
}
}
}
}
+1 -1
View File
@@ -1,2 +1,2 @@
Minecraft Forge - Forge Development LLC
Copyright (c) Forge Development LLC and contributors
SPDX-License-Identifier: LGPL-2.1-only
+3
View File
@@ -57,6 +57,9 @@ This software contains a partial repackaging of javaxdelta, a BSD licensed progr
binary differences and applying them, sourced from the subversion at http://sourceforge.net/projects/javaxdelta/
authored by genman, heikok, pivot.
The only changes are to replace some Trove collection types with standard Java collections, and repackaged.
This software includes the Monocraft font from https://github.com/IdreesInc/Monocraft/ for use in the early loading
display.
=========================================================================
+337 -409
View File
File diff suppressed because it is too large. Load diff
+8 -4
View File
@@ -1,9 +1,13 @@
repositories {
maven { url = 'https://maven.minecraftforge.net/' }
mavenCentral()
mavenCentral()
}
dependencies {
implementation 'org.ow2.asm:asm:9.1'
implementation 'org.ow2.asm:asm-tree:9.1'
implementation 'net.minecraftforge:srgutils:0.4.+'
implementation 'org.ow2.asm:asm:9.5'
implementation 'org.ow2.asm:asm-tree:9.5'
implementation 'net.minecraftforge:srgutils:0.5.+'
implementation 'commons-io:commons-io:2.12.0'
implementation 'com.google.code.gson:gson:2.10.1'
implementation 'org.eclipse.jgit:org.eclipse.jgit:6.5.0.202303070854-r'
}
@@ -4,20 +4,17 @@ import groovy.json.JsonBuilder
import org.gradle.api.DefaultTask
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.TaskAction
import org.objectweb.asm.ClassReader
import org.objectweb.asm.Opcodes
import org.objectweb.asm.tree.ClassNode
import org.objectweb.asm.tree.FieldNode
import org.objectweb.asm.tree.MethodNode
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
abstract class BytecodeFinder extends DefaultTask {
@InputFile abstract RegularFileProperty getJar()
@OutputFile abstract RegularFileProperty getOutput()
// It should be fine to mark the output as internal as we want to control when we run it anyways.
// This also shuts Gradle 8 up about implicit task dependencies.
@Internal abstract RegularFileProperty getOutput()
BytecodeFinder() {
output.convention(project.layout.buildDirectory.dir(name).map { it.file("output.json") })
@@ -33,18 +30,7 @@ abstract class BytecodeFinder extends DefaultTask {
pre()
jar.get().asFile.withInputStream { i ->
new ZipInputStream(i).withCloseable { zin ->
ZipEntry zein
while ((zein = zin.nextEntry) != null) {
if (zein.name.endsWith('.class')) {
def node = new ClassNode(Opcodes.ASM9)
new ClassReader(zin).accept(node, 0)
process(node)
}
}
}
}
Util.processClassNodes(jar.get().asFile, this::process)
post()
outputFile.text = new JsonBuilder(getData()).toPrettyString()
@@ -0,0 +1,32 @@
package net.minecraftforge.forge.tasks
import groovy.transform.CompileStatic
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Internal
import org.objectweb.asm.tree.ClassNode
import org.objectweb.asm.tree.MethodNode
@CompileStatic
abstract class BytecodePredicateFinder extends BytecodeFinder {
@Internal
abstract Property<Closure<Boolean>> getPredicate()
private final Set<String> matches = new HashSet()
@Override
protected process(ClassNode parent, MethodNode node) {
for (final current : node.instructions) {
if (predicate.get().call(parent, node, current)) {
matches.add(parent.name)
return
}
}
}
@Internal
@Override
protected Object getData() {
return matches.toSorted(Comparator.naturalOrder()) ?: {throw new RuntimeException('Failed to find any targets, please ensure that method names and descriptors are correct.')}()
}
}
@@ -1,267 +0,0 @@
package net.minecraftforge.forge.tasks
import net.minecraftforge.srgutils.IMappingFile
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.TaskAction
import org.objectweb.asm.Opcodes
abstract class CheckATs extends DefaultTask {
@InputFile abstract RegularFileProperty getInheritance()
@InputFiles abstract ConfigurableFileCollection getAts()
@InputFile @Optional abstract RegularFileProperty getMappings()
@TaskAction
protected void exec() {
def mappings = mappings.map { IMappingFile.load(it.asFile) }.getOrNull()
Util.init()
def parse = { line ->
def idx = line.indexOf('#')
def comment = idx == -1 ? null : line.substring(idx)
if (idx != -1) line = line.substring(0, idx - 1)
def (modifier, cls, desc) = (line.trim() + ' ').split(' ', -1)
def key = cls + (desc.isEmpty() ? '' : ' ' + desc)
return [modifier, cls, desc, comment, key]
}
def accessLevel = { access ->
if ((access & Opcodes.ACC_PUBLIC) != 0) return 3
if ((access & Opcodes.ACC_PROTECTED) != 0) return 2
if ((access & Opcodes.ACC_PRIVATE) != 0) return 0
return 1
}
def accessStr = { access ->
if (access.endsWith('-f') || access.endsWith('+f'))
return 4
switch (access.toLowerCase()) {
case 'public': return 3
case 'protected': return 2
case 'default': return 1
case 'private': return 0
default: return -1
}
}
def json = inheritance.get().asFile.json()
ats.each { f ->
TreeMap lines = [:]
def group = null
for (def line : f.readLines()) {
if (line.isEmpty()) continue
if (line.startsWith('#group ')) {
def (modifier, cls, desc, comment, key) = parse.call(line.substring(7))
if (desc != '*' && desc != '*()' && desc != '<init>')
throw new IllegalStateException('Invalid group: ' + line)
group = [modifier: modifier, cls: cls, desc: desc, comment: comment,
existing: [] as Set,
children: [] as TreeSet,
group: true
]
if (lines.containsKey(key))
throw new IllegalStateException('Duplicate group: ' + line)
lines[key] = group
} else if (group != null) {
if (line.startsWith('#endgroup')) {
group = null
} else {
def (modifier, cls, desc, comment, key) = parse.call(line)
group['existing'].add(key)
}
} else if (line.startsWith('#endgroup')) {
throw new IllegalStateException('Invalid group ending: ' + line)
} else if (line.startsWith('#')) {
//Nom
} else {
def (modifier, cls, desc, comment, key) = parse.call(line)
if (lines.containsKey(key)) {
println('Duplicate: ' + line)
continue
}
lines[key] = [modifier: modifier, cls: cls, desc: desc, comment: comment, group: false]
}
}
// Process Groups, this will remove any entries outside the group that is covered by the group
for (def key : new ArrayList<>(lines.keySet())) {
def entry = lines.get(key)
if (entry != null && entry['group']) {
def cls = entry['cls']
def jcls = json.get(cls.replaceAll('\\.', '/'))
if (jcls == null) {
lines.remove(key)
println('Invalid Group: ' + key)
} else if ('*' == entry['desc']) {
if (!jcls.containsKey('fields')) {
lines.remove(key)
println('Invalid Group, Class has no fields: ' + key)
} else {
jcls['fields'].each { field, value ->
def fkey = cls + ' ' + field
if (accessLevel.call(value['access']) < accessStr.call(entry['modifier'])) {
if (lines.containsKey(fkey)) {
lines.remove(fkey)
} else if (!entry['existing'].contains(fkey)) {
println('Added: ' + fkey)
}
entry['children'].add(fkey)
} else if (lines.containsKey(fkey)) {
lines.remove(fkey)
println('Removed: ' + fkey)
}
}
entry['existing'].stream().findAll{ !entry['children'].contains(it) }.each{ println('Removed: ' + it) }
}
} else if ('*()' == entry['desc']) {
if (!jcls.containsKey('methods')) {
lines.remove(key)
println('Invalid Group, Class has no methods: ' + key)
} else {
jcls['methods'].each{ mtd, value ->
if (mtd.startsWith('<clinit>') || mtd.startsWith('lambda$'))
return
key = cls + ' ' + mtd.replace(' ', '')
if (accessLevel.call(value['access']) < accessStr.call(entry['modifier'])) {
if (lines.containsKey(key)) {
lines.remove(key)
} else if (!entry['existing'].contains(key)) {
println('Added: ' + key)
}
entry['children'].add(key)
} else if (lines.containsKey(key)) {
lines.remove(key)
println('Removed: ' + key)
}
}
entry['existing'].stream().findAll{ !entry['children'].contains(it) }.each{ println('Removed: ' + it) }
}
} else if ('<init>' == entry['desc']) { //Make all public non-abstract subclasses
json.each{ tcls,value ->
if (!value.containsKey('methods') || ((value['access'] & Opcodes.ACC_ABSTRACT) != 0))
return
def parents = [] as Set
def parent = tcls
while (parent != null && json.containsKey(parent)) {
parents.add(parent)
def p = json[parent]
parent = p == null ? null : p['superName']
}
if (parents.contains(cls.replaceAll('\\.', '/'))) {
value['methods'].each{ mtd, v ->
if (mtd.startsWith('<init>')) {
def child = tcls.replaceAll('/', '\\.') + ' ' + mtd.replace(' ', '')
if (accessLevel.call(v['access']) < 3) {
if (lines.containsKey(child)) {
lines.remove(child)
} else if (!entry['existing'].contains(child)) {
println('Added: ' + child)
}
entry['children'].add(child)
} else if (lines.containsKey(child)) {
lines.remove(child)
println('Removed: ' + child)
}
}
}
}
}
entry['existing'].stream().findAll{ !entry['children'].contains(it) }.each{ println('Removed: ' + it) }
}
}
}
// Process normal lines, remove invalid and remove narrowing
for (def key : new ArrayList<>(lines.keySet())) {
def entry = lines.get(key)
if (entry != null && !entry['group']) {
def cls = entry['cls']
def jcls = json.get(cls.replaceAll('\\.', '/'))
if (jcls == null) {
lines.remove(key)
println('Invalid: ' + key)
} else if (entry['desc'] == '') {
if (accessLevel.call(jcls['access']) > accessStr.call(entry['modifier']) && (entry.comment == null || !entry.comment.startsWith('#force '))) {
lines.remove(key)
println('Invalid Narrowing: ' + key)
}
} else if (!entry['desc'].contains('(')) {
if (!jcls.containsKey('fields') || !jcls['fields'].containsKey(entry['desc'])) {
lines.remove(key)
println('Invalid: ' + key)
} else {
def value = jcls['fields'][entry['desc']]
if (accessLevel.call(value['access']) > accessStr.call(entry['modifier']) && (entry.comment == null || !entry.comment.startsWith('#force '))) {
lines.remove(key)
println('Invalid Narrowing: ' + key)
println(entry.comment)
}
}
} else {
def jdesc = entry['desc'].replace('(', ' (')
if (!jcls.containsKey('methods') || !jcls['methods'].containsKey(jdesc)) {
lines.remove(key)
println('Invalid: ' + key)
} else {
def value = jcls['methods'][jdesc]
if (accessLevel.call(value['access']) > accessStr.call(entry['modifier']) && (entry.comment == null || !entry.comment.startsWith('#force '))) {
lines.remove(key)
println('Invalid Narrowing: ' + key)
}
}
}
}
}
def data = []
def remapComment = { entry ->
if (!mappings || !entry || !entry.desc) return null
def comment = entry.comment?.substring(1)?.trim()
def jsonCls = json.get(entry.cls.replaceAll('\\.', '/'))
def mappingsClass = mappings?.getClass(jsonCls.name)
if (!mappingsClass) return entry.comment
def idx = entry.desc.indexOf('(')
def mappedName = idx == -1
? mappingsClass.remapField(entry.desc)
: mappingsClass.remapMethod(entry.desc.substring(0, idx), entry.desc.substring(idx))
if (!mappedName) return entry.comment
if (mappedName == '<init>')
mappedName = 'constructor'
if (comment?.startsWith(mappedName))
return '# ' + comment
if (comment && comment.indexOf(' ') != -1) {
def split = comment.split(" - ").toList()
if (split[0].indexOf(' ') != -1)
// The first string is more than one word, so append before it
return "# ${mappedName} - ${comment}"
split.remove(0)
return "# ${mappedName} - ${String.join(' - ', split)}"
}
return '# ' + mappedName
}
lines.each { key,value ->
if (!value.group) {
def comment = remapComment.call(value)
data.add(value.modifier + ' ' + key + (comment ? ' ' + comment : ''))
} else {
data.add('#group ' + value.modifier + ' ' + key + (value.comment == null ? '' : ' ' + value.comment))
value.children.each {
def line = value.modifier + ' ' + it
def (modifier, cls, desc, comment) = parse.call(line)
comment = remapComment.call([modifier: modifier, cls: cls, desc: desc, comment: comment])
data.add(line + (comment ? ' ' + comment : ''))
}
data.add('#endgroup')
}
}
f.text = data.join('\n')
}
}
}
@@ -1,94 +0,0 @@
package net.minecraftforge.forge.tasks
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.TaskAction
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.MethodVisitor
import org.objectweb.asm.Opcodes
import org.objectweb.asm.Type
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
abstract class CheckExcs extends DefaultTask {
@InputFile abstract RegularFileProperty getBinary()
@InputFiles abstract ConfigurableFileCollection getExcs()
@TaskAction
protected void exec() {
Util.init()
def known = []
binary.get().asFile.withInputStream { i ->
new ZipInputStream(i).withCloseable { zin ->
def visitor = new ClassVisitor(Opcodes.ASM9) {
private String cls
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
this.cls = name
}
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
known.add(this.cls + '.' + name + descriptor)
super.visitMethod(access, name, descriptor, signature, exceptions)
}
}
ZipEntry zein
while ((zein = zin.nextEntry) != null) {
if (zein.name.endsWith('.class')) {
ClassReader reader = new ClassReader(zin)
reader.accept(visitor, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES)
}
}
}
}
excs.each { f ->
def lines = []
f.eachLine { line ->
def idx = line.indexOf('#')
if (idx == 0 || line.isEmpty()) {
return
}
def comment = idx == -1 ? null : line.substring(idx)
if (idx != -1) line = line.substring(0, idx - 1)
if (!line.contains('=')) {
println('Invalid: ' + line)
return
}
def (key, value) = line.split('=', 2)
if (!known.contains(key)) {
println('Unknown: ' + line)
return
}
def (cls, desc) = key.split('\\.', 2)
if (!desc.contains('(')) {
println('Invalid: ' + line)
return
}
def name = desc.split('\\(', 2)[0]
desc = '(' + desc.split('\\(', 2)[1]
def (exceptions, args) = value.contains('|') ? value.split('|', 2) : [value, '']
if (args.split(',').length != Type.getArgumentTypes(desc).length) {
println('Invalid: ' + line)
return
}
lines.add(line)
}
f.text = lines.sort().join('\n')
}
}
}
@@ -1,219 +0,0 @@
package net.minecraftforge.forge.tasks
import org.gradle.api.DefaultTask
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.TaskAction
import java.nio.file.Files
import java.nio.file.Paths
import java.util.regex.Pattern
abstract class CheckPatches extends DefaultTask {
@InputDirectory abstract DirectoryProperty getPatchDir()
@Input boolean autoFix = false
@TaskAction
protected void exec() {
def hasS2SArtifact = [
Paths.get("patches/minecraft/net/minecraft/client/renderer/ViewArea.java.patch"),
Paths.get("patches/minecraft/net/minecraft/data/models/blockstates/Variant.java.patch")
]
def verified = true;
patchDir.get().asFileTree.each { patch ->
def patchPath = project.rootDir.toPath().relativize(patch.toPath())
verified &= verifyPatch(patch, autoFix, patchPath.toString(), hasS2SArtifact.contains(patchPath))
}
if (!verified)
throw new RuntimeException('One or more patches failed verification. Check the log for errors.')
}
def verifyPatch(patch, fix, patchPath, hasS2SArtifact) {
def hunk_start_pattern = Pattern.compile('^@@ -[0-9,]* \\+[0-9,_]* @@$')
def white_space_pattern = Pattern.compile('^[+\\-]\\s*$')
def import_pattern = Pattern.compile('^[+\\-]\\s*import.*')
def field_pattern = Pattern.compile('^[+\\-][\\s]*((public|protected|private)[\\s]*)?(static[\\s]*)?(final)?([^=;]*)(=.*)?;\\s*$')
def method_pattern = Pattern.compile('^[+\\-][\\s]*((public|protected|private)[\\s]*)?(static[\\s]*)?(final)?([^(]*)[(]([^)]*)?[)]\\s*[{]\\s*$')
def class_pattern = Pattern.compile('^[+\\-][\\s]*((public|protected|private)[\\s]*)?(static[\\s]*)?(final[\\s]*)?(class|interface)([^{]*)[{]\\s*$')
def accessMap = [("private"):0, (null):1, ("protected"):2, ("public"):3]
def hasProblem = false;
def lines = patch.readLines()
def hunksStart = 0
def onlyWhiteSpace = false
def didFix = false
def newLines = []
def whiteSpaceErrors = []
// First two lines are file name ++/-- and we do not care
newLines.add(lines[0] + '\n')
newLines.add(lines[1] + '\n')
int i = 2
for (; i < lines.size(); ++i) {
def line = lines[i]
newLines.add(line + '\n')
if (hunk_start_pattern.matcher(line).find()) {
if (onlyWhiteSpace) {
if (fix || hasS2SArtifact) {
logger.lifecycle("Removing white space hunk starting at line {}, file: {}", hunksStart + 1, patchPath)
def toRemove = i - hunksStart;
while (toRemove-- > 0)
newLines.remove(newLines.size() - 1)
didFix = true
}
else {
logger.lifecycle("Patch contains only white space hunk starting at line {}, file: {}", hunksStart + 1, patchPath)
hasProblem = true
}
}
else {
if (!whiteSpaceErrors.empty)
hasProblem = true
whiteSpaceErrors.each { error -> logger.lifecycle(error) }
whiteSpaceErrors.clear()
}
hunksStart = i
onlyWhiteSpace = true
continue
}
if (line.startsWithAny('+','-')) {
def prefixChange = false
def prevLine = lines[i - 1]
if (line.charAt(0) == (char)'+' && prevLine.charAt(0) == (char)'-') {
def prevTrim = prevLine.substring(1).replaceAll("\\s", "")
def currTrim = line.substring(1).replaceAll("\\s", "")
if (prevTrim.equals(currTrim)) {
prefixChange = true
}
def pMatcher = field_pattern.matcher(prevLine)
def cMatcher = field_pattern.matcher(line)
if (pMatcher.find() && cMatcher.find() &&
pMatcher.group(6) == cMatcher.group(6) && // = ...
pMatcher.group(5) == cMatcher.group(5) && // field name
pMatcher.group(3) == cMatcher.group(3) && // static
(accessMap[pMatcher.group(2)] < accessMap[cMatcher.group(2)] || pMatcher.group(4) != cMatcher.group(4))) {
logger.lifecycle("Patch contains access changes or final removal at line {}, file: {}", i + 1, patchPath)
hasProblem = true
}
pMatcher = method_pattern.matcher(prevLine)
cMatcher = method_pattern.matcher(line)
if (pMatcher.find() && cMatcher.find() &&
pMatcher.group(6) == cMatcher.group(6) && // params
pMatcher.group(5) == cMatcher.group(5) && // <T> void name
pMatcher.group(3) == cMatcher.group(3) && // static
(accessMap[pMatcher.group(2)] < accessMap[cMatcher.group(2)] || pMatcher.group(4) != cMatcher.group(4))) {
logger.lifecycle("Patch contains access changes or final removal at line {}, file: {}", i + 1, patchPath)
hasProblem = true
}
pMatcher = class_pattern.matcher(prevLine)
cMatcher = class_pattern.matcher(line)
if (pMatcher.find() && cMatcher.find() &&
pMatcher.group(6) == cMatcher.group(6) && // ClassName<> extends ...
pMatcher.group(5) == cMatcher.group(5) && // class | interface
pMatcher.group(3) == cMatcher.group(3) && // static
(accessMap[pMatcher.group(2)] < accessMap[cMatcher.group(2)] || pMatcher.group(4) != cMatcher.group(4))) {
logger.lifecycle("Patch contains access changes or final removal at line {}, file: {}", i + 1, patchPath)
hasProblem = true
}
}
if (line.charAt(0) == (char)'-' && i + 1 < lines.size()) {
def nextLine = lines[i + 1]
if (nextLine.charAt(0) == (char)'+') {
def nextTrim = nextLine.substring(1).replaceAll("\\s", "")
def currTrim = line.substring(1).replaceAll("\\s", "")
if (nextTrim.equals(currTrim)) {
prefixChange = true
}
}
}
def isWhiteSpaceChange = white_space_pattern.matcher(line).find()
if (!prefixChange && !isWhiteSpaceChange) {
onlyWhiteSpace = hasS2SArtifact && import_pattern.matcher(line).find()
}
else if (isWhiteSpaceChange) {
def prevLineChange = prevLine.startsWithAny('+','-')
def nextLineChange = i + 1 < lines.size() && lines[i + 1].startsWithAny('+','-')
if (!prevLineChange && !nextLineChange) {
whiteSpaceErrors.add(String.format("Patch contains white space change in valid hunk at line %d (cannot auto fix), file: %s", i + 1, patchPath))
}
}
if (line.contains("\t")) {
if (!fix) {
logger.lifecycle("Patch contains tabs on line {}, file: {}", i + 1, patchPath)
hasProblem = true
}
else {
logger.lifecycle("Fixing tabs on line {}, file: {}", i + 1, patchPath)
line = line.replaceAll('\t', ' ')
newLines.remove(newLines.size() - 1)
newLines.add(line + '\n')
didFix = true
}
}
if (import_pattern.matcher(line).find() && !hasS2SArtifact) {
logger.lifecycle("Patch contains import change on line {}, file: {}", i + 1, patchPath)
hasProblem = true
}
}
}
if (onlyWhiteSpace) {
if (fix || hasS2SArtifact) {
logger.lifecycle("Removing white space hunk starting at line {}, file: {}", hunksStart + 1, patchPath)
def toRemove = i - hunksStart;
while (toRemove-- > 0)
newLines.remove(newLines.size() - 1)
didFix = true
}
else {
logger.lifecycle("Patch contains only white space hunk starting at line {}, file: {}", hunksStart + 1, patchPath)
hasProblem = true
}
}
else {
if (!whiteSpaceErrors.empty)
hasProblem = true
whiteSpaceErrors.each { error -> logger.lifecycle(error) }
}
if (didFix) {
if (newLines.size() <= 2) {
logger.lifecycle("Patch is now empty removing, file: {}", patchPath)
Files.delete(patch.toPath())
}
else {
if (!hasS2SArtifact)
logger.lifecycle("*** Updating patch file. Please run setup then genPatches again. ***")
patch.withWriter('UTF-8') { writer ->
newLines.each { l -> writer.write(l) }
}
}
}
return !hasProblem
}
}
@@ -1,63 +0,0 @@
package net.minecraftforge.forge.tasks
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.TaskAction
abstract class CheckSAS extends DefaultTask {
@InputFile abstract RegularFileProperty getInheritance()
@InputFiles abstract ConfigurableFileCollection getSass()
@TaskAction
protected void exec() {
Util.init()
def json = inheritance.get().asFile.json()
sass.each { f ->
def lines = []
f.eachLine { line ->
if (line[0] == '\t') return //Skip any tabbed lines, those are ones we add
def idx = line.indexOf('#')
if (idx == 0 || line.isEmpty()) {
lines.add(line)
return
}
def comment = idx == -1 ? null : line.substring(idx)
if (idx != -1) line = line.substring(0, idx - 1)
def (cls, desc) = (line.trim() + ' ').split(' ', -1)
cls = cls.replaceAll('\\.', '/')
desc = desc.replace('(', ' (')
if (json[cls] == null || (!desc.isEmpty() && (json[cls]['methods'] == null || json[cls]['methods'][desc] == null))) {
println('Invalid: ' + line)
return
}
//Class SAS
if (desc.isEmpty()) {
lines.add(cls + (comment == null ? '' : ' ' + comment))
if (json[cls]['methods'] != null)
(json[cls]['methods'] as TreeMap).each {
findChildMethods(json, cls, it.key).each { lines.add('\t' + it) }
}
return
}
//Method SAS
lines.add(cls + ' ' + desc.replace(' ', '') + (comment == null ? '' : ' ' + comment))
findChildMethods(json, cls, desc).each { lines.add('\t' + it) }
}
f.text = lines.join('\n')
}
}
protected static findChildMethods(json, cls, desc)
{
return json.values().findAll{ it.methods != null && it.methods[desc] != null && it.methods[desc].override == cls}
.collect { it.name + ' ' + desc.replace(' ', '') } as TreeSet
}
}
@@ -0,0 +1,40 @@
package net.minecraftforge.forge.tasks
import org.gradle.api.DefaultTask
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.TaskAction
import java.nio.file.Files
abstract class FixPatchImports extends DefaultTask {
@InputDirectory abstract DirectoryProperty getClean()
@InputDirectory abstract DirectoryProperty getPatched()
@TaskAction
void run() {
final patchedPath = getPatched().get().asFile.toPath()
final cleanPath = getClean().get().asFile.toPath()
try (final var stream = Files.find(patchedPath, Integer.MAX_VALUE, (path, attr) -> path.toString().endsWith('.java'))) {
final itr = stream.iterator()
while (itr.hasNext()) {
final next = itr.next()
final clean = cleanPath.resolve(patchedPath.relativize(next).toString())
final patchedLines = Files.readAllLines(next)
final cleanLines = Files.readAllLines(clean)
final patchedImport = patchedLines.findLastIndexOf(2) { it.startsWith('import ') }
final cleanImport = cleanLines.findLastIndexOf(2) { it.startsWith('import ') }
if (cleanImport !== patchedImport) {
patchedLines.removeIf { it.startsWith('import ') }
patchedLines.addAll(2, cleanLines.subList(2, cleanImport + 1))
Files.write(next, patchedLines)
}
}
}
}
}
@@ -0,0 +1,49 @@
package net.minecraftforge.forge.tasks
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import groovy.transform.CompileStatic
import groovy.transform.TupleConstructor
@CompileStatic
@TupleConstructor
final class InheritanceData implements Annotatable {
String name
int access
String superName
List<String> interfaces = []
Map<String, Method> methods = [:]
Map<String, Field> fields = [:]
List<Annotation> annotations = []
@TupleConstructor
static final class Method implements Annotatable {
int access
String override
List<Annotation> annotations = []
}
@TupleConstructor
static final class Field implements Annotatable {
int access
String desc
List<Annotation> annotations = []
}
@TupleConstructor
static final class Annotation {
String desc
}
private static final Gson GSON = new Gson()
static Map<String, InheritanceData> parse(File file) {
try (final reader = file.newReader()) {
return GSON.fromJson(reader, new TypeToken<Map<String, InheritanceData>>() {})
}
}
}
@CompileStatic
interface Annotatable {
List<InheritanceData.Annotation> getAnnotations()
}
@@ -6,7 +6,7 @@ import org.gradle.api.provider.SetProperty
abstract class InstallerJar extends Zip {
@Input @Optional abstract SetProperty<String> getPackedDependencies()
InstallerJar() {
archiveClassifier.set('installer')
archiveExtension.set('jar') // Needs to be Zip task to not override Manifest, so set extension
@@ -15,7 +15,7 @@ abstract class InstallerJar extends Zip {
def installerJson = project.tasks.installerJson
def launcherJson = project.tasks.launcherJson
def downloadInstaller = project.tasks.downloadInstaller
dependsOn(installerJson, launcherJson, downloadInstaller)
from(installerJson, launcherJson)
@@ -24,13 +24,13 @@ abstract class InstallerJar extends Zip {
from(project.zipTree(downloadInstaller.output)) {
duplicatesStrategy = 'exclude'
}
if (!System.env.MAVEN_USER) {
if (!System.env.TEAMCITY_VERSION) {
dependsOn(project.universalJar)
from(project.universalJar) {
into '/maven/' + project.group.replace('.', '/') + '/' + project.universalJar.archiveBaseName.get() + '/' + project.version + '/'
}
packedDependencies.get().forEach {
def jarTask = project.rootProject.getTasks().findByPath(it)
dependsOn(jarTask)
@@ -0,0 +1,44 @@
package net.minecraftforge.forge.tasks
import org.apache.commons.io.IOUtils
import org.gradle.api.DefaultTask
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
import java.util.zip.ZipOutputStream
abstract class MergeJars extends DefaultTask {
MergeJars() {
output.convention(project.layout.buildDirectory.dir(name).map { it.file('output.jar') })
}
@TaskAction
void run() {
def jars = inputJars.files
try (ZipOutputStream zout = new ZipOutputStream(new FileOutputStream(output.get().asFile))) {
for (def jar : jars) {
try (ZipInputStream zin = new ZipInputStream(new FileInputStream(jar))) {
def entry
while ((entry = zin.getNextEntry()) != null) {
ZipEntry _new = new ZipEntry(entry.getName())
_new.setTime(0) //SHOULD be the same time as the main entry, but NOOOO _new.setTime(entry.getTime()) throws DateTimeException, so you get 0, screw you!
zout.putNextEntry(_new)
IOUtils.copy(zin, zout)
}
}
}
}
}
@InputFiles
abstract ConfigurableFileCollection getInputJars()
@OutputFile
abstract RegularFileProperty getOutput()
}
@@ -0,0 +1,57 @@
package net.minecraftforge.forge.tasks
import org.gradle.api.DefaultTask
import org.gradle.api.artifacts.Dependency
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import java.nio.file.Files
import java.nio.file.StandardCopyOption
abstract class SetupCheckJarCompatibility extends DefaultTask {
SetupCheckJarCompatibility() {
group = 'jar compatibility'
onlyIf {
inputVersion.getOrNull() != null
}
outputs.upToDateWhen { false } // Never up to date, because this setup task should always run
baseBinPatchesOutput.convention(project.layout.buildDirectory.dir(name).map { it.file('joined.lzma') })
}
@TaskAction
void run() {
def inputVersion = inputVersion.get()
def fmlLibs = project.configurations.detachedConfiguration(project.PACKED_DEPS.collect {
def artifactId = it.split(':')[1]
return project.dependencies.create("net.minecraftforge:${artifactId}:${inputVersion}")
}.toArray(Dependency[]::new))
project.tasks.named('checkJarCompatibility') {
baseLibraries.from(project.provider {
fmlLibs.resolvedConfiguration.lenientConfiguration.files
})
}
def baseForgeUserdev = project.layout.buildDirectory.dir(name).map { it.file("forge-${inputVersion}-userdev.jar") }.get().asFile
project.rootProject.extensions.download.run {
src "https://maven.minecraftforge.net/net/minecraftforge/forge/${inputVersion}/forge-${inputVersion}-userdev.jar"
dest baseForgeUserdev
}
def joinedLzma = project.zipTree(baseForgeUserdev).matching { it.include('joined.lzma') }.singleFile
Files.copy(joinedLzma.toPath(), baseBinPatchesOutput.get().asFile.toPath(), StandardCopyOption.REPLACE_EXISTING)
}
@Input
@Optional
abstract Property<String> getInputVersion()
@OutputFile
abstract RegularFileProperty getBaseBinPatchesOutput()
}
@@ -0,0 +1,74 @@
package net.minecraftforge.forge.tasks
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import groovy.transform.CompileStatic
import org.eclipse.jgit.api.Git
import javax.annotation.Nullable
import java.util.stream.Collectors
@CompileStatic
class TeamcityRequests {
public static final Gson GSON = new Gson()
@Nullable
static <T> T jsonRequest(TypeToken<T> clazz, String url) throws Exception {
final HttpURLConnection conn = (HttpURLConnection) URI.create(url).toURL().openConnection()
conn.setRequestProperty('Accept', 'application/json')
conn.setReadTimeout(5 * 1000)
conn.setConnectTimeout(5 * 1000)
conn.connect()
if (conn.responseCode !== 200) {
return null
}
try (final InputStream is = conn.getInputStream()) {
GSON.fromJson(new InputStreamReader(is), clazz.getType())
}
}
@Nullable
static Map<String, Build> buildsByCommit() throws IOException {
final Map<String, Build> builds = [:]
jsonRequest(new TypeToken<Builds>() {}, 'https://teamcity.minecraftforge.net/guestAuth/app/rest/builds?fields=build:(revisions,number)&locator=buildType:(id:MinecraftForge_MinecraftForge_MinecraftForge_MinecraftForge__Build),defaultFilter:false,count:300,status:SUCCESS')
?.build?.forEach {
it.revisions.revision.each { rev -> builds[rev.version] = it }
}
return builds
}
@Nullable
static String attemptFindBase(File gitPath) {
try (final git = Git.open(gitPath)) {
final Map<String, Build> buildByCommit = buildsByCommit()
for (final commit in git.log().setMaxCount(100).call()) {
// Find the first commit which was built on CI
final build = buildByCommit[commit.id.name]
if (build) {
return build.number
}
}
} catch (Exception ignored) {}
return null
}
static final class Builds {
public List<Build> build
}
static final class Build {
public String number
public Revisions revisions
}
static final class Revisions {
public int count
public List<Revision> revision
}
static final class Revision {
public String version
}
}
@@ -2,13 +2,20 @@ package net.minecraftforge.forge.tasks
import groovy.json.JsonBuilder
import groovy.json.JsonSlurper
import org.objectweb.asm.ClassReader
import org.objectweb.asm.Opcodes
import org.objectweb.asm.tree.ClassNode
import java.io.File
import java.security.MessageDigest
import java.text.SimpleDateFormat
import java.util.Date
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
public class Util {
static final ASM_LEVEL = Opcodes.ASM9
static void init() {
File.metaClass.sha1 = { ->
MessageDigest md = MessageDigest.getInstance('SHA-1')
@@ -18,17 +25,17 @@ public class Util {
return md.digest().collect {String.format "%02x", it}.join()
}
File.metaClass.getSha1 = { !delegate.exists() ? null : delegate.sha1() }
File.metaClass.json = { -> new JsonSlurper().parseText(delegate.text) }
File.metaClass.getJson = { return delegate.exists() ? new JsonSlurper().parse(delegate) : [:] }
File.metaClass.setJson = { json -> delegate.text = new JsonBuilder(json).toPrettyString() }
Date.metaClass.iso8601 = { ->
def format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
def result = format.format(delegate)
return result[0..21] + ':' + result[22..-1]
}
String.metaClass.rsplit = { String del, int limit = -1 ->
def lst = new ArrayList()
def x = 0, idx
@@ -41,7 +48,7 @@ public class Util {
return lst
}
}
public static String[] getClasspath(project, libs, artifact) {
def ret = []
artifactTree(project, artifact).each { key, lib ->
@@ -51,7 +58,7 @@ public class Util {
}
return ret
}
public static def getArtifacts(project, config, classifiers) {
def ret = [:]
config.resolvedConfiguration.resolvedArtifacts.each {
@@ -74,14 +81,6 @@ public class Util {
if (!checkExists(url)) {
url = "https://maven.minecraftforge.net/${path}"
}
//TODO remove when Mojang launcher is updated
if (!classifiers && art.classifier != null) {
//Mojang launcher doesn't currently support classifiers, so... move it to part of the version, and force the extension to 'jar'
// However, keep the path normal so that our mirror system works.
art.version = "${art.version}-${art.classifier}"
art.classifier = null
art.extension = 'jar'
}
ret[key] = [
name: "${art.group}:${art.name}:${art.version}" + (art.classifier == null ? '' : ":${art.classifier}") + (art.extension == 'jar' ? '' : "@${art.extension}"),
downloads: [
@@ -96,14 +95,14 @@ public class Util {
}
return ret
}
public static def getMavenPath(task) {
public static def getMavenPath(task) {
def classifier = task.archiveClassifier.get()
def dep = "${task.project.group}:${task.project.name}:${task.project.version}" + (classifier == '' ? '' : ':' + classifier)
return "${task.project.group.replace('.', '/')}/${task.project.name}/${task.project.version}/${task.project.name}-${task.project.version}".toString() + (classifier == '' ? '' : '-' + classifier) + '.jar'
}
public static def getMavenDep(task) {
public static def getMavenDep(task) {
def classifier = task.archiveClassifier.get()
return "${task.project.group}:${task.project.name}:${task.project.version}" + (classifier == '' ? '' : ':' + classifier)
}
@@ -127,7 +126,7 @@ public class Util {
def files = cfg.resolve()
return getArtifacts(project, cfg, true)
}
private static boolean checkExists(url) {
try {
def code = new URL(url).openConnection().with {
@@ -142,4 +141,25 @@ public class Util {
throw e
}
}
static String getLatestForgeVersion(mcVersion) {
final json = new JsonSlurper().parseText(new URL('https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json').getText('UTF-8'))
final ver = json.promos["$mcVersion-latest"]
ver === null ? null : (mcVersion + '-' + ver)
}
static void processClassNodes(File file, Closure process) {
file.withInputStream { i ->
new ZipInputStream(i).withCloseable { zin ->
ZipEntry zein
while ((zein = zin.nextEntry) != null) {
if (zein.name.endsWith('.class')) {
def node = new ClassNode(ASM_LEVEL)
new ClassReader(zin).accept(node, 0)
process(node)
}
}
}
}
}
}
@@ -0,0 +1,92 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.forge.tasks
import net.minecraftforge.srgutils.MinecraftVersion
import org.gradle.api.DefaultTask
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.TaskAction
import org.objectweb.asm.tree.AnnotationNode
import org.objectweb.asm.tree.ClassNode
abstract class ValidateDeprecations extends DefaultTask {
@InputFile
abstract RegularFileProperty getInput()
@Input
abstract Property<String> getMcVersion()
ValidateDeprecations() {
this.onlyIf { !System.env.TEAMCITY_VERSION }
}
@TaskAction
protected void exec() {
def mcVer = MinecraftVersion.from(mcVersion.get())
def errors = []
Util.processClassNodes(input.get().asFile) {
processNode(mcVer, errors, it)
}
if (!errors.isEmpty()) {
errors.forEach {
this.logger.error("Deprecated ${it[0]} is marked for removal in ${it[1]} but is not yet removed")
}
throw new IllegalStateException("Found deprecated members marked for removal but not yet removed in ${mcVer}; see log for details")
}
}
protected processNode(MinecraftVersion mcVer, List<String> errors, ClassNode node) {
node.visibleAnnotations?.each { annotation ->
ValidateDeprecations.processAnnotations(annotation, mcVer, errors) {
"class ${node.name}"
}
}
node.fields?.each { field ->
field.visibleAnnotations?.each { annotation ->
ValidateDeprecations.processAnnotations(annotation, mcVer, errors) {
"field ${node.name}#${field.name}"
}
}
}
node.methods?.each { method ->
method.visibleAnnotations?.each { annotation ->
ValidateDeprecations.processAnnotations(annotation, mcVer, errors) {
"method ${node.name}#${method.name}${method.desc}"
}
}
}
}
private static void processAnnotations(AnnotationNode annotation, MinecraftVersion mcVer, List<String> errors, Closure context) {
def values = annotation.values
if (values === null)
return
int forRemoval = values.indexOf('forRemoval')
int since = values.indexOf('since')
if (annotation.desc == 'Ljava/lang/Deprecated;' && forRemoval !== -1 && since !== -1 && values.size() >= 4 && values[forRemoval + 1] === true) {
def oldVersion = MinecraftVersion.from(values[since + 1])
def split = ValidateDeprecations.splitDots(oldVersion.toString())
if (split.length < 2)
return
def removeVersion = MinecraftVersion.from("${split[0]}.${split[1] + 1}")
if (removeVersion <= mcVer)
errors.add([context(), removeVersion])
}
}
private static int[] splitDots(String version) {
String[] pts = version.split('\\.')
int[] values = new int[pts.length]
for (int x = 0; x < pts.length; x++)
values[x] = Integer.parseInt(pts[x])
return values
}
}
@@ -0,0 +1,310 @@
package net.minecraftforge.forge.tasks.checks
import groovy.transform.CompileStatic
import groovy.transform.TupleConstructor
import net.minecraftforge.forge.tasks.InheritanceData
import net.minecraftforge.srgutils.IMappingFile
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.RegularFile
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.Optional
import org.objectweb.asm.Opcodes
@CompileStatic
abstract class CheckATs extends CheckTask {
@InputFile abstract RegularFileProperty getInheritance()
@InputFiles abstract ConfigurableFileCollection getAts()
@InputFile @Optional abstract RegularFileProperty getMappings()
@Override
void check(Reporter reporter, boolean fix) {
final IMappingFile mappings = mappings.map { RegularFile it -> IMappingFile.load(it.asFile) }.getOrNull()
final inheritance = InheritanceData.parse(this.inheritance.get().asFile)
ats.each {
final lines = process(it, reporter, inheritance)
if (fix) {
it.text = joinBack(lines, inheritance, mappings).join('\n')
}
}
}
private static TreeMap<String, ATParser.Entry> process(File file, Reporter reporter, Map<String, InheritanceData> inheritance) {
final TreeMap<String, ATParser.Entry> lines = ATParser.parse(file.readLines(), reporter)
final Map<String, ATParser.Entry> constructorGroups = [:]
final itr = lines.entrySet().iterator()
final toRemove = []
while (itr.hasNext()) {
final next = itr.next()
String key = next.key
final entry = next.value
if (entry === null) continue
final binaryName = entry.cls.replaceAll('\\.', '/')
// Process Groups, this will remove any entries outside the group that is covered by the group
if (entry.group) {
final jcls = inheritance[binaryName]
if (jcls === null) {
itr.remove()
reporter.report("Invalid group: $key")
} else if ('*' == entry.desc) {
if (!jcls.fields) {
itr.remove()
reporter.report("Invalid group, class has no fields: $key")
} else {
jcls.fields.each { field, value ->
final fkey = entry.cls + ' ' + field
if (accessLevel(value.access) < accessStr(entry.modifier)) {
if (lines.containsKey(fkey)) {
toRemove.add(fkey)
} else if (!entry.existing.contains(fkey)) {
reporter.report("Missing group entry: $fkey")
}
entry.children.add(fkey)
} else if (lines.containsKey(fkey)) {
toRemove.add(fkey)
reporter.report("Found invalid group entry: $fkey")
}
}
entry.existing.findAll { it !in entry.children }.each { println('Removed: ' + it) }
}
} else if ('*()' == entry.desc) {
if (!jcls.methods) {
itr.remove()
reporter.report("Invalid group, class has no methods: $key")
} else {
jcls.methods.each { mtd, value ->
if (mtd.startsWith('<clinit>') || mtd.startsWith('lambda$')) return
key = entry.cls + ' ' + mtd.replace(' ', '')
if (accessLevel(value.access) < accessStr(entry.modifier)) {
if (lines.containsKey(key)) {
toRemove.add(key)
} else if (!entry.existing.contains(key)) {
reporter.report("Missing group entry: $key")
}
entry.children.add(key)
} else if (lines.containsKey(key)) {
toRemove.add(key)
reporter.report("Found invalid group entry: $key")
}
}
entry.existing.findAll { it !in entry.children }.each { println('Removed: ' + it) }
}
} else if ('<init>' == entry.desc) { //Make all public non-abstract subclasses
constructorGroups.put(binaryName, entry)
}
}
// Process normal lines, remove invalid and remove narrowing
else {
def jcls = inheritance.get(binaryName)
if (jcls === null) {
itr.remove()
reporter.report("Invalid: $key")
} else if (entry.desc == '') {
if (accessLevel(jcls.access) > accessStr(entry.modifier) && (entry.comment === null || !entry.comment.startsWith('#force '))) {
itr.remove()
reporter.report("Invalid Narrowing: $key")
}
} else if (!entry.desc.contains('(')) {
if (!jcls.fields || !jcls.fields.containsKey(entry.desc)) {
itr.remove()
reporter.report("Invalid: $key")
} else {
final value = jcls.fields[entry.desc]
if (accessLevel(value.access) > accessStr(entry.modifier) && (entry.comment === null || !entry.comment.startsWith('#force '))) {
itr.remove()
reporter.report("Invalid Narrowing: $key - ${entry.comment}")
}
}
} else {
final jdesc = entry.desc.replace('(', ' (')
if (!jcls.methods || !jcls.methods.containsKey(jdesc)) {
itr.remove()
reporter.report("Invalid: $key")
} else {
final value = jcls.methods[jdesc]
if (accessLevel(value.access) > accessStr(entry.modifier) && (entry.comment === null || !entry.comment.startsWith('#force '))) {
itr.remove()
reporter.report("Invalid Narrowing: $key")
}
}
}
}
}
inheritance.each { tcls, value ->
if (!value.methods || ((value.access & Opcodes.ACC_ABSTRACT) !== 0)) return
String parent = tcls
while (parent !== null) {
constructorGroups[parent]?.tap { entry ->
value.methods.each { mtd, v ->
if (mtd.startsWith('<init>')) {
final child = tcls.replaceAll('/', '\\.') + ' ' + mtd.replace(' ', '')
if (accessLevel(v.access) < 3) {
if (lines.containsKey(child)) {
toRemove.add(child)
} else if (child !in entry.existing) {
reporter.report("Missing group entry: $child")
}
entry.children.add(child)
} else if (lines.containsKey(child)) {
toRemove.add(child)
reporter.report("Found invalid group entry: $child")
}
}
}
}
parent = inheritance[parent]?.superName
}
}
constructorGroups.values().each { entry -> entry.existing.findAll { it !in entry.children }.each{ reporter.report("Found invalid group entry: $it") } }
toRemove.each(lines.&remove)
return lines
}
private static List<String> joinBack(TreeMap<String, ATParser.Entry> lines, Map<String, InheritanceData> inheritance, IMappingFile mappings) {
final data = [] as List<String>
final remapComment = { ATParser.Entry entry ->
if (!mappings || !entry || !entry.desc) return null
final comment = entry.comment?.substring(1)?.trim()
final jsonCls = inheritance.get(entry.cls.replaceAll('\\.', '/'))
final mappingsClass = mappings?.getClass(jsonCls.name)
if (mappingsClass === null) return entry.comment
final idx = entry.desc.indexOf('(')
String mappedName = idx == -1
? mappingsClass.remapField(entry.desc)
: mappingsClass.remapMethod(entry.desc.substring(0, idx), entry.desc.substring(idx))
if (!mappedName) return entry.comment
if (mappedName == '<init>')
mappedName = 'constructor'
if (comment?.startsWith(mappedName))
return '# ' + comment
if (comment && comment.indexOf(' ') !== -1) {
def split = comment.split(' - ').toList()
if (split[0].indexOf(' ') !== -1)
// The first string is more than one word, so append before it
return "# ${mappedName} - ${comment}"
split.remove(0)
return "# ${mappedName} - ${String.join(' - ', split)}"
}
return '# ' + mappedName
}
lines.each { key, value ->
if (!value.group) {
def comment = remapComment.call(value)
data.add(value.modifier + ' ' + key + (comment ? ' ' + comment : ''))
} else {
data.add(('#group ' + value.modifier + ' ' + key + ' ' + (value.comment ?: '')).trim())
value.children.each {
final line = value.modifier + ' ' + it
final entry = ATParser.parseEntry(line)
final comment = remapComment(entry)
data.add(line + (comment ? ' ' + comment : ''))
}
data.add('#endgroup')
}
}
return data
}
static int accessStr(String access) {
if (access.endsWith('-f') || access.endsWith('+f')) return 4
switch (access.toLowerCase()) {
case 'public': return 3
case 'protected': return 2
case 'default': return 1
case 'private': return 0
default: return -1
}
}
static int accessLevel(int access) {
if ((access & Opcodes.ACC_PUBLIC) !== 0) return 3
if ((access & Opcodes.ACC_PROTECTED) !== 0) return 2
if ((access & Opcodes.ACC_PRIVATE) !== 0) return 0
return 1
}
}
@CompileStatic
class ATParser {
static TreeMap<String, Entry> parse(List<String> lines, CheckTask.Reporter reporter) {
TreeMap<String, Entry> outLines = new TreeMap<>()
Entry group = null
for (final line : lines) {
if (line.isEmpty()) continue
if (line.startsWith('#group ')) {
final entry = parseEntry(line.substring(7))
if (entry.desc != '*' && entry.desc != '*()' && entry.desc != '<init>') {
reporter.report("Invalid group: $line", false)
}
entry.group = true
entry.children = []
entry.existing = []
group = entry
if (outLines.containsKey(entry.key)) {
reporter.report("Duplicate group: $line", false)
}
outLines[entry.key] = group
} else if (group !== null) {
if (line.startsWith('#endgroup')) {
group = null
} else {
final key = parseEntry(line).key
group.existing.add(key)
}
} else if (line.startsWith('#endgroup')) {
reporter.report("Invalid group ending: $line", false)
} else if (line.startsWith('#')) {
//Nom
} else {
final entry = parseEntry(line)
if (outLines.containsKey(entry.key)) {
reporter.report("Found duplicate: $line")
continue
}
outLines[entry.key] = entry
}
}
return outLines
}
static Entry parseEntry(String line) {
final idx = line.indexOf('#')
final String comment = idx === -1 ? null : line.substring(idx)
if (idx !== -1) line = line.substring(0, idx - 1)
final data = (line.trim() + ' ').split(' ', -1)
new Entry(data[0], data[1], data[2], comment)
}
@TupleConstructor
static final class Entry {
String modifier, cls, desc, comment
Set<String> existing
TreeSet<String> children
boolean group = false
@Lazy
String key = {cls + (desc.isEmpty() ? '' : ' ' + desc)}()
Object getAt(String key) {
return getProperty(key)
}
}
}
@@ -0,0 +1,93 @@
package net.minecraftforge.forge.tasks.checks
import net.minecraftforge.forge.tasks.Util
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.InputFiles
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.MethodVisitor
import org.objectweb.asm.Type
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
abstract class CheckExcs extends CheckTask {
@InputFile abstract RegularFileProperty getBinary()
@InputFiles abstract ConfigurableFileCollection getExcs()
@Override
void check(Reporter reporter, boolean fix) {
final Set<String> known = []
collectKnown(known)
excs.each { f ->
final lines = []
f.eachLine { line ->
def idx = line.indexOf('#')
if (idx == 0 || line.isEmpty()) {
return
}
if (idx != -1) line = line.substring(0, idx - 1)
if (!line.contains('=')) {
reporter.report("Invalid: $line")
return
}
def (String key, String value) = line.split('=', 2)
if (!known.contains(key)) {
reporter.report("Unknown: $line")
return
}
String desc = key.split('\\.', 2)[1]
if (!desc.contains('(')) {
reporter.report("Invalid: $line")
return
}
desc = '(' + desc.split('\\(', 2)[1]
def (exceptions, String args) = value.contains('|') ? value.split('|', 2) : [value, '']
if (args.split(',').length !== Type.getArgumentTypes(desc).length) {
reporter.report("Invalid: $line")
return
}
lines.add(line)
}
if (fix) f.text = lines.sort().join('\n')
}
}
private void collectKnown(Collection<String> known) {
binary.get().asFile.withInputStream { i ->
new ZipInputStream(i).withCloseable { zin ->
final visitor = new ClassVisitor(Util.ASM_LEVEL) {
private String cls
@Override
void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
this.cls = name
}
@Override
MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
known.add(this.cls + '.' + name + descriptor)
super.visitMethod(access, name, descriptor, signature, exceptions)
}
}
ZipEntry zein
while ((zein = zin.nextEntry) !== null) {
if (zein.name.endsWith('.class')) {
ClassReader reader = new ClassReader(zin)
reader.accept(visitor, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES)
}
}
}
}
}
}
@@ -0,0 +1,9 @@
package net.minecraftforge.forge.tasks.checks
import groovy.transform.CompileStatic
@CompileStatic
enum CheckMode {
CHECK,
FIX
}
@@ -0,0 +1,182 @@
package net.minecraftforge.forge.tasks.checks
import groovy.transform.CompileStatic
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.Optional
import java.nio.file.Files
import java.nio.file.Path
import java.util.regex.Pattern
@CompileStatic
abstract class CheckPatches extends CheckTask {
private static final Pattern HUNK_START_PATTERN = Pattern.compile('^@@ -[0-9,]* \\+[0-9,_]* @@$')
private static final Pattern WHITESPACE_PATTERN = Pattern.compile('^[+\\-]\\s*$')
private static final Pattern IMPORT_PATTERN = Pattern.compile('^[+\\-]\\s*import.*')
private static final Pattern FIELD_PATTERN = Pattern.compile('^[+\\-][\\s]*((public|protected|private)[\\s]*)?(static[\\s]*)?(final)?([^=;]*)(=.*)?;\\s*$')
private static final Pattern METHOD_PATTERN = Pattern.compile('^[+\\-][\\s]*((public|protected|private)[\\s]*)?(static[\\s]*)?(final)?([^(]*)[(]([^)]*)?[)]\\s*[{]\\s*$')
private static final Pattern CLASS_PATTERN = Pattern.compile('^[+\\-][\\s]*((public|protected|private)[\\s]*)?(static[\\s]*)?(final[\\s]*)?(class|interface)([^{]*)[{]\\s*$')
private static final Map<String, Integer> ACCESS_MAP = [private: 0, protected: 2, public: 3].tap { it.put(null, 1) }
@InputDirectory abstract DirectoryProperty getPatchDir()
@Input @Optional abstract ListProperty<String> getPatchesWithS2SArtifact()
@Override
void check(Reporter reporter, boolean fix) {
final patchDir = getPatchDir().get().asFile.toPath()
Files.walk(patchDir).withCloseable {
it.filter(Files.&isRegularFile).forEach { path ->
final String relativeName = patchDir.relativize(path).toString()
verifyPatch(path, reporter, fix, relativeName, patchesWithS2SArtifact.get().contains(relativeName.replace('\\', '/')))
}
}
}
def accessChange(previous, current) {
//return ACCESS_MAP[previous] < ACCESS_MAP[current]
return previous != current
}
void verifyPatch(Path patch, Reporter reporter, boolean fix, String patchPath, boolean hasS2SArtifact) {
final oldFixedErrors = reporter.fixed.size()
final lines = Files.readAllLines(patch)
int hunksStart = 0
boolean onlyWhiteSpace = false
final List<String> newLines = []
// First two lines are file name ++/-- and we do not care
newLines.add(lines[0] + '\n')
newLines.add(lines[1] + '\n')
int i
for (i = 2; i < lines.size(); ++i) {
def line = lines[i]
newLines.add(line + '\n')
if (HUNK_START_PATTERN.matcher(line).find()) {
if (onlyWhiteSpace) {
if (!hasS2SArtifact)
reporter.report("Patch contains only white space hunk starting at line ${hunksStart + 1}, file: $patchPath")
int toRemove = i - hunksStart
while (toRemove-- > 0)
newLines.remove(newLines.size() - 1)
}
hunksStart = i
onlyWhiteSpace = true
continue
}
if (line.startsWithAny('+','-')) {
def prefixChange = false
def prevLine = lines[i - 1]
if (line.charAt(0) == (char)'+' && prevLine.charAt(0) == (char)'-') {
def prevTrim = prevLine.substring(1).replaceAll("\\s", "")
def currTrim = line.substring(1).replaceAll("\\s", "")
if (prevTrim == currTrim) {
prefixChange = true
}
def pMatcher = FIELD_PATTERN.matcher(prevLine)
def cMatcher = FIELD_PATTERN.matcher(line)
if (pMatcher.find() && cMatcher.find() &&
pMatcher.group(6) == cMatcher.group(6) && // = ...
pMatcher.group(5) == cMatcher.group(5) && // field name
pMatcher.group(3) == cMatcher.group(3) && // static
(accessChange(pMatcher.group(2), cMatcher.group(2)) || pMatcher.group(4) != cMatcher.group(4))) {
reporter.report("Patch contains access changes or final removal at line ${i + 1}, file: $patchPath", false)
}
pMatcher = METHOD_PATTERN.matcher(prevLine)
cMatcher = METHOD_PATTERN.matcher(line)
if (pMatcher.find() && cMatcher.find() &&
pMatcher.group(6) == cMatcher.group(6) && // params
pMatcher.group(5) == cMatcher.group(5) && // <T> void name
pMatcher.group(3) == cMatcher.group(3) && // static
(accessChange(pMatcher.group(2), cMatcher.group(2)) || pMatcher.group(4) != cMatcher.group(4))) {
reporter.report("Patch contains access changes or final removal at line ${i + 1}, file: $patchPath", false)
}
pMatcher = CLASS_PATTERN.matcher(prevLine)
cMatcher = CLASS_PATTERN.matcher(line)
if (pMatcher.find() && cMatcher.find() &&
pMatcher.group(6) == cMatcher.group(6) && // ClassName<> extends ...
pMatcher.group(5) == cMatcher.group(5) && // class | interface
pMatcher.group(3) == cMatcher.group(3) && // static
(accessChange(pMatcher.group(2), cMatcher.group(2)) || pMatcher.group(4) != cMatcher.group(4))) {
reporter.report("Patch contains access changes or final removal at line ${i + 1}, file: $patchPath", false)
}
}
if (line.charAt(0) == (char)'-' && i + 1 < lines.size()) {
final nextLine = lines[i + 1]
if (nextLine.charAt(0) == (char)'+') {
final nextTrim = nextLine.substring(1).replaceAll("\\s", "")
final currTrim = line.substring(1).replaceAll("\\s", "")
if (nextTrim == currTrim) {
prefixChange = true
}
}
}
final isWhiteSpaceChange = WHITESPACE_PATTERN.matcher(line).find()
if (!prefixChange && !isWhiteSpaceChange) {
onlyWhiteSpace = hasS2SArtifact && IMPORT_PATTERN.matcher(line).find()
} else if (isWhiteSpaceChange) {
final prevLineChange = prevLine.startsWithAny('+','-')
final nextLineChange = i + 1 < lines.size() && lines[i + 1].startsWithAny('+','-')
if (!prevLineChange && !nextLineChange) {
reporter.report("Patch contains white space change in valid hunk at line ${i + 1}, file: $patchPath", false)
}
}
if (line.contains('\t')) {
reporter.report("Patch contains tabs on line ${i + 1}, file: $patchPath")
line = line.replaceAll('\t', ' ')
newLines.remove(newLines.size() - 1)
newLines.add(line + '\n')
}
if (IMPORT_PATTERN.matcher(line).find() && !hasS2SArtifact) {
reporter.report("Patch contains import change on line ${i + 1}, file: $patchPath", false)
}
}
}
if (onlyWhiteSpace) {
if (!hasS2SArtifact)
reporter.report("Patch contains only white space hunk starting at line ${hunksStart + 1}, file: $patchPath")
def toRemove = i - hunksStart;
while (toRemove-- > 0)
newLines.remove(newLines.size() - 1)
}
if ((reporter.fixed.size() > oldFixedErrors && fix) || hasS2SArtifact) {
if (newLines.size() <= 2) {
logger.lifecycle("Patch is now empty removing, file: {}", patchPath)
Files.delete(patch)
}
else {
if (!hasS2SArtifact)
logger.lifecycle("*** Updating patch file. Please run setup then genPatches again. ***")
Files.newBufferedWriter(patch).withCloseable {
newLines.each { l -> it.write(l) }
}
}
}
}
}
@@ -0,0 +1,110 @@
package net.minecraftforge.forge.tasks.checks
import groovy.transform.CompileStatic
import net.minecraftforge.forge.tasks.Annotatable
import net.minecraftforge.forge.tasks.InheritanceData
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.InputFiles
@CompileStatic
abstract class CheckSAS extends CheckTask {
@InputFile abstract RegularFileProperty getInheritance()
@InputFiles abstract ConfigurableFileCollection getSass()
@Override
void check(Reporter reporter, boolean fix) {
final inheritance = InheritanceData.parse(this.inheritance.get().asFile)
sass.each { f ->
final lines = []
f.eachLine { line ->
if (line[0] == '\t') return // Skip any tabbed lines, those are ones we add
final idx = line.indexOf('#')
if (idx == 0 || line.isEmpty()) {
lines.add(line)
return
}
def comment = idx == -1 ? null : line.substring(idx)
if (idx != -1) line = line.substring(0, idx - 1)
final spl = (line.trim().replace('(', ' (') + ' ').split(' ', -1)
def (String cls, String name, String desc) = [spl[0], spl[1], spl[2]]
cls = cls.replaceAll('\\.', '/')
if (inheritance[cls] === null) {
reporter.report("Invalid: $line")
} else if (name.isEmpty()) { //Class SAS
final toAdd = []
final clsSided = isSided(inheritance[cls])
boolean sided = clsSided
/* TODO: MergeTool doesn't do fields
if (json[cls]['fields'] != null) {
for (entry in json[cls]['fields']) {
if (isSided(entry.value)) {
sided = true
toAdd.add('\t' + cls + ' ' + entry.key)
}
}
} */
final clsInh = inheritance[cls]
if (clsInh.methods) {
for (entry in clsInh.methods) {
if (isSided(entry.value)) {
sided = true
toAdd.add('\t' + cls + ' ' + entry.key.replaceAll(' ', ''))
findChildMethods(inheritance, cls, entry.key).each { lines.add('\t' + it) }
findChildMethods(inheritance, cls, entry.key).each { println(line + ' -- ' + it) }
} else if (clsSided) {
findChildMethods(inheritance, cls, entry.key).each { lines.add('\t' + it) }
findChildMethods(inheritance, cls, entry.key).each { println(line + ' -- ' + it) }
}
}
}
if (sided) {
lines.add(cls + (comment == null ? '' : ' ' + comment))
lines.addAll(toAdd.sort())
} else {
reporter.report("Invalid: $line")
}
} else if (desc.isEmpty()) { // Fields
/* TODO: MergeTool doesn't do fields
if (json[cls]['fields'] != null && isSided(json[cls]['fields'][name]))
lines.add(cls + ' ' + name + (comment == null ? '' : ' ' + comment))
else */
reporter.report("Invalid: $line")
} else { // Methods
final clsInh = inheritance[cls]
if (clsInh.methods === null || !isSided(clsInh.methods[name + ' ' + desc]))
reporter.report("Invalid: $line")
else {
lines.add(cls + ' ' + name + desc + (comment == null ? '' : ' ' + comment))
findChildMethods(inheritance, cls, name + ' ' + desc).each { println(line + ' -- ' + it) }
findChildMethods(inheritance, cls, name + ' ' + desc).each { lines.add('\t' + it) }
}
}
}
if (fix) f.text = lines.join('\n')
}
}
protected static isSided(Annotatable annotatable) {
if (annotatable === null) return false
for (ann in annotatable.annotations) {
if ('Lnet/minecraftforge/api/distmarker/OnlyIn;' == ann.desc)
return true
}
return false
}
protected static findChildMethods(Map<String, InheritanceData> json, String cls, String desc) {
return json.values().findAll{ it.methods != null && it.methods[desc] != null && it.methods[desc].override == cls && isSided(it.methods[desc]) }
.collect { it.name + ' ' + desc.replace(' ', '') } as TreeSet
}
}
@@ -0,0 +1,111 @@
package net.minecraftforge.forge.tasks.checks
import groovy.transform.CompileStatic
import net.minecraftforge.forge.tasks.Util
import org.gradle.api.Action
import org.gradle.api.DefaultTask
import org.gradle.api.logging.LogLevel
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.TaskContainer
import org.gradle.api.tasks.VerificationTask
@CompileStatic
abstract class CheckTask extends DefaultTask implements VerificationTask {
@Input
abstract Property<CheckMode> getMode()
private boolean ignoreFailures = false
@Input
@Override
boolean getIgnoreFailures() {
return ignoreFailures
}
@Override
void setIgnoreFailures(boolean ignoreFailures) {
this.ignoreFailures = ignoreFailures
}
@TaskAction
void run() {
Util.init()
final doFix = getMode().get() === CheckMode.FIX
final Reporter reporter = new Reporter(doFix)
check(reporter, doFix)
if (reporter.messages) {
if (getMode().get() === CheckMode.CHECK) {
logger.error("Check task '{}' found errors:\n{}", name, reporter.messages.join('\n'))
if (!ignoreFailures) {
throw new IllegalArgumentException("${reporter.messages.size()} errors were found!")
}
} else {
if (logger.isEnabled(LogLevel.DEBUG)) {
logger.warn("Check task '{}' found {} errors and fixed {}:\n{}", name, reporter.messages.size(), reporter.fixed.size(), reporter.fixed.join('\n'))
} else {
logger.warn("Check task '{}' found {} errors and fixed {}.", name, reporter.messages.size(), reporter.fixed.size())
}
if (reporter.notFixed) {
logger.error('{} errors could not be fixed:\n{}', reporter.notFixed.size(), reporter.notFixed.join('\n'))
if (!ignoreFailures) {
throw new IllegalArgumentException("${reporter.notFixed.size()} errors which cannot be fixed were found!")
}
}
}
}
}
abstract void check(Reporter reporter, boolean fix)
@CompileStatic
static final class Reporter {
final boolean trackFixed
Reporter(boolean trackFixed) {
this.trackFixed = trackFixed
}
public final List<String> messages = []
public final List<String> fixed = []
public final List<String> notFixed = []
void report(String message, boolean canBeFixed = true) {
messages.add(message)
if (trackFixed) {
if (canBeFixed) {
fixed.add(message)
} else {
notFixed.add(message)
}
}
}
}
static <T extends CheckTask> void registerTask(TaskContainer tasks, String taskName, @DelegatesTo.Target('type') Class<T> clazz,
@DelegatesTo(genericTypeIndex = 0, target = 'type') Closure configuration) {
taskName = taskName.capitalize()
tasks.register("check$taskName", clazz) { CheckTask task ->
def castedTask = task as T
configuration.setDelegate(castedTask)
configuration.call(castedTask)
castedTask.mode.set(CheckMode.CHECK)
castedTask.group = 'checks'
}
tasks.named('checkAll').configure { it.dependsOn("check$taskName") }
tasks.register("checkAndFix$taskName", clazz) { CheckTask task ->
def castedTask = task as T
configuration.setDelegate(castedTask)
configuration.call(castedTask)
castedTask.mode.set(CheckMode.FIX)
castedTask.group = 'checks'
}
tasks.named('checkAllAndFix').configure { it.dependsOn("checkAndFix$taskName") }
}
}
+6 -4
View File
@@ -1,13 +1,13 @@
Contributing to Forge
=====================
1) Keep patches to Minecraft classes minimal. If you need a lot of things done, you may either add to relevant forge classes or make a new class.
1) Keep patches to Minecraft classes together. If you need a lot of things done, you may either add to relevant forge classes or make a new class. Try not to spread out your patches across multiple disjoint lines, as this makes maintenance of your patches difficult.
2) An example mod must be provided for all PRs adding events/hooks to Forge. Whether it's a full mod using your proposed hooks, or just a pastebin containing a simple example, an example mod is required. Please also explain in detail what your new hook does.
2) TODO: Test Mods
3) Follow Forge coding guidelines (braces on newlines, spaces instead of tabs, etc)
3) Follow the code style of the class you're working in (braces on newlines & spaces instead of tabs in Forge classes, inline brackets in patches, etc).
For more information, refer to [the wiki](https://github.com/MinecraftForge/MinecraftForge/wiki/If-you-want-to-contribute-to-Forge)
For more information, refer to [the wiki][Wiki].
Contributor License Agreement
@@ -16,3 +16,5 @@ Contributor License Agreement
- You assign copyright ownership of your contributions to the patches codebase (everything under patches) to Forge, where it will be licensed under the LGPLv2.1 license.
"Forge" is Forge Development LLC, a legally incorporated entity in Oregon, USA.
[Wiki]: https://github.com/MinecraftForge/MinecraftForge/wiki/If-you-want-to-contribute-to-Forge
+34 -23
View File
@@ -2,16 +2,16 @@
MinecraftForge
=============
[![Stable Release](https://img.shields.io/badge/dynamic/json?url=https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json&label=Stable&prefix=1.18-&query=$.promos["1.18-recommended"]&color=brightgreen&logo=data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBjbGFzcz0ic3ZnLWlubGluZS0tZmEgZmEtc3RhciBmYS13LTE4IiBhcmlhLWhpZGRlbj0idHJ1ZSIgZGF0YS1pY29uPSJzdGFyIiBkYXRhLXByZWZpeD0iZmFzIiBmb2N1c2FibGU9ImZhbHNlIiByb2xlPSJpbWciIHZpZXdCb3g9IjAgMCA1NzYgNTEyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8cGF0aCBkPSJNMjU5LjMgMTcuOEwxOTQgMTUwLjIgNDcuOSAxNzEuNWMtMjYuMiAzLjgtMzYuNyAzNi4xLTE3LjcgNTQuNmwxMDUuNyAxMDMtMjUgMTQ1LjVjLTQuNSAyNi4zIDIzLjIgNDYgNDYuNCAzMy43TDI4OCA0MzkuNmwxMzAuNyA2OC43YzIzLjIgMTIuMiA1MC45LTcuNCA0Ni40LTMzLjdsLTI1LTE0NS41IDEwNS43LTEwM2MxOS0xOC41IDguNS01MC44LTE3LjctNTQuNkwzODIgMTUwLjIgMzE2LjcgMTcuOGMtMTEuNy0yMy42LTQ1LjYtMjMuOS01Ny40IDB6IiBmaWxsPSJ3aGl0ZSIvPgo8L3N2Zz4K)](https://files.minecraftforge.net)
[![Latest Release](https://img.shields.io/badge/dynamic/json?url=https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json&label=Latest&prefix=1.18-&query=$.promos["1.18-latest"]&color=blue&logo=data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBjbGFzcz0ic3ZnLWlubGluZS0tZmEgZmEtYnVnIGZhLXctMTYiIGFyaWEtaGlkZGVuPSJ0cnVlIiBkYXRhLWljb249ImJ1ZyIgZGF0YS1wcmVmaXg9ImZhcyIgZm9jdXNhYmxlPSJmYWxzZSIgcm9sZT0iaW1nIiB2aWV3Qm94PSIwIDAgNTEyIDUxMiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTUxMS45ODggMjg4LjljLS40NzggMTcuNDMtMTUuMjE3IDMxLjEtMzIuNjUzIDMxLjFINDI0djE2YzAgMjEuODY0LTQuODgyIDQyLjU4NC0xMy42IDYxLjE0NWw2MC4yMjggNjAuMjI4YzEyLjQ5NiAxMi40OTcgMTIuNDk2IDMyLjc1OCAwIDQ1LjI1NS0xMi40OTggMTIuNDk3LTMyLjc1OSAxMi40OTYtNDUuMjU2IDBsLTU0LjczNi01NC43MzZDMzQ1Ljg4NiA0NjcuOTY1IDMxNC4zNTEgNDgwIDI4MCA0ODBWMjM2YzAtNi42MjctNS4zNzMtMTItMTItMTJoLTI0Yy02LjYyNyAwLTEyIDUuMzczLTEyIDEydjI0NGMtMzQuMzUxIDAtNjUuODg2LTEyLjAzNS05MC42MzYtMzIuMTA4bC01NC43MzYgNTQuNzM2Yy0xMi40OTggMTIuNDk3LTMyLjc1OSAxMi40OTYtNDUuMjU2IDAtMTIuNDk2LTEyLjQ5Ny0xMi40OTYtMzIuNzU4IDAtNDUuMjU1bDYwLjIyOC02MC4yMjhDOTIuODgyIDM3OC41ODQgODggMzU3Ljg2NCA4OCAzMzZ2LTE2SDMyLjY2NkMxNS4yMyAzMjAgLjQ5MSAzMDYuMzMuMDEzIDI4OC45LS40ODQgMjcwLjgxNiAxNC4wMjggMjU2IDMyIDI1Nmg1NnYtNTguNzQ1bC00Ni42MjgtNDYuNjI4Yy0xMi40OTYtMTIuNDk3LTEyLjQ5Ni0zMi43NTggMC00NS4yNTUgMTIuNDk4LTEyLjQ5NyAzMi43NTgtMTIuNDk3IDQ1LjI1NiAwTDE0MS4yNTUgMTYwaDIyOS40ODlsNTQuNjI3LTU0LjYyN2MxMi40OTgtMTIuNDk3IDMyLjc1OC0xMi40OTcgNDUuMjU2IDAgMTIuNDk2IDEyLjQ5NyAxMi40OTYgMzIuNzU4IDAgNDUuMjU1TDQyNCAxOTcuMjU1VjI1Nmg1NmMxNy45NzIgMCAzMi40ODQgMTQuODE2IDMxLjk4OCAzMi45ek0yNTcgMGMtNjEuODU2IDAtMTEyIDUwLjE0NC0xMTIgMTEyaDIyNEMzNjkgNTAuMTQ0IDMxOC44NTYgMCAyNTcgMHoiIGZpbGw9IndoaXRlIi8+Cjwvc3ZnPgo=
)](https://files.minecraftforge.net) [![Discord](https://img.shields.io/discord/313125603924639766.svg?color=%237289da&label=Discord&logo=discord&logoColor=%237289da)](https://discord.gg/UvedJ9m) [![Support](https://img.shields.io/badge/Patreon-Support-orange.svg?logo=Patreon)](https://www.patreon.com/LexManos)
[![Stable Release](https://img.shields.io/badge/dynamic/json?url=https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json&label=Stable&prefix=1.20.1&query=$.promos["1.20.1-recommended"]&color=brightgreen&logo=data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBjbGFzcz0ic3ZnLWlubGluZS0tZmEgZmEtc3RhciBmYS13LTE4IiBhcmlhLWhpZGRlbj0idHJ1ZSIgZGF0YS1pY29uPSJzdGFyIiBkYXRhLXByZWZpeD0iZmFzIiBmb2N1c2FibGU9ImZhbHNlIiByb2xlPSJpbWciIHZpZXdCb3g9IjAgMCA1NzYgNTEyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8cGF0aCBkPSJNMjU5LjMgMTcuOEwxOTQgMTUwLjIgNDcuOSAxNzEuNWMtMjYuMiAzLjgtMzYuNyAzNi4xLTE3LjcgNTQuNmwxMDUuNyAxMDMtMjUgMTQ1LjVjLTQuNSAyNi4zIDIzLjIgNDYgNDYuNCAzMy43TDI4OCA0MzkuNmwxMzAuNyA2OC43YzIzLjIgMTIuMiA1MC45LTcuNCA0Ni40LTMzLjdsLTI1LTE0NS41IDEwNS43LTEwM2MxOS0xOC41IDguNS01MC44LTE3LjctNTQuNkwzODIgMTUwLjIgMzE2LjcgMTcuOGMtMTEuNy0yMy42LTQ1LjYtMjMuOS01Ny40IDB6IiBmaWxsPSJ3aGl0ZSIvPgo8L3N2Zz4K)][Download]
[![Latest Release](https://img.shields.io/badge/dynamic/json?url=https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json&label=Latest&prefix=1.20.1-&query=$.promos["1.20.1-latest"]&color=blue&logo=data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBjbGFzcz0ic3ZnLWlubGluZS0tZmEgZmEtYnVnIGZhLXctMTYiIGFyaWEtaGlkZGVuPSJ0cnVlIiBkYXRhLWljb249ImJ1ZyIgZGF0YS1wcmVmaXg9ImZhcyIgZm9jdXNhYmxlPSJmYWxzZSIgcm9sZT0iaW1nIiB2aWV3Qm94PSIwIDAgNTEyIDUxMiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTUxMS45ODggMjg4LjljLS40NzggMTcuNDMtMTUuMjE3IDMxLjEtMzIuNjUzIDMxLjFINDI0djE2YzAgMjEuODY0LTQuODgyIDQyLjU4NC0xMy42IDYxLjE0NWw2MC4yMjggNjAuMjI4YzEyLjQ5NiAxMi40OTcgMTIuNDk2IDMyLjc1OCAwIDQ1LjI1NS0xMi40OTggMTIuNDk3LTMyLjc1OSAxMi40OTYtNDUuMjU2IDBsLTU0LjczNi01NC43MzZDMzQ1Ljg4NiA0NjcuOTY1IDMxNC4zNTEgNDgwIDI4MCA0ODBWMjM2YzAtNi42MjctNS4zNzMtMTItMTItMTJoLTI0Yy02LjYyNyAwLTEyIDUuMzczLTEyIDEydjI0NGMtMzQuMzUxIDAtNjUuODg2LTEyLjAzNS05MC42MzYtMzIuMTA4bC01NC43MzYgNTQuNzM2Yy0xMi40OTggMTIuNDk3LTMyLjc1OSAxMi40OTYtNDUuMjU2IDAtMTIuNDk2LTEyLjQ5Ny0xMi40OTYtMzIuNzU4IDAtNDUuMjU1bDYwLjIyOC02MC4yMjhDOTIuODgyIDM3OC41ODQgODggMzU3Ljg2NCA4OCAzMzZ2LTE2SDMyLjY2NkMxNS4yMyAzMjAgLjQ5MSAzMDYuMzMuMDEzIDI4OC45LS40ODQgMjcwLjgxNiAxNC4wMjggMjU2IDMyIDI1Nmg1NnYtNTguNzQ1bC00Ni42MjgtNDYuNjI4Yy0xMi40OTYtMTIuNDk3LTEyLjQ5Ni0zMi43NTggMC00NS4yNTUgMTIuNDk4LTEyLjQ5NyAzMi43NTgtMTIuNDk3IDQ1LjI1NiAwTDE0MS4yNTUgMTYwaDIyOS40ODlsNTQuNjI3LTU0LjYyN2MxMi40OTgtMTIuNDk3IDMyLjc1OC0xMi40OTcgNDUuMjU2IDAgMTIuNDk2IDEyLjQ5NyAxMi40OTYgMzIuNzU4IDAgNDUuMjU1TDQyNCAxOTcuMjU1VjI1Nmg1NmMxNy45NzIgMCAzMi40ODQgMTQuODE2IDMxLjk4OCAzMi45ek0yNTcgMGMtNjEuODU2IDAtMTEyIDUwLjE0NC0xMTIgMTEyaDIyNEMzNjkgNTAuMTQ0IDMxOC44NTYgMCAyNTcgMHoiIGZpbGw9IndoaXRlIi8+Cjwvc3ZnPgo=
)][Download] [![Discord](https://img.shields.io/discord/313125603924639766.svg?color=%237289da&label=Discord&logo=discord&logoColor=%237289da)][Discord] [![Support](https://img.shields.io/badge/Patreon-Support-orange.svg?logo=Patreon)][Patreon]
Forge is a free, open-source modding API all of your favourite mods use!
| Version | Support |
| ------------- | ------------- |
| 1.18.x | Active |
| 1.16.x | LTS |
| Version | Support |
|---------| ------------- |
| 1.20.x | Active |
| 1.19.x | Legacy |
* [Download]
* [Forum]
@@ -20,7 +20,7 @@ Forge is a free, open-source modding API all of your favourite mods use!
# Installing Forge
Go to [the Forge website](https://files.minecraftforge.net)
Go to [the Forge website][Download]
and select the Minecraft version you wish to get Forge for from the list.
You can download the installer for the *Recommended Build* or the
@@ -29,37 +29,48 @@ You can download the installer for the *Recommended Build* or the
into your vanilla launcher environment, where you can then create a new
profile using that version and play the game!
For support and questions, visit [the Support Forum](https://www.minecraftforge.net/forum/forum/18-support-bug-reports/).
[Here is a short video from Rorax showing how to install and setup Forge.](https://www.youtube.com/watch?v=lB3ArN_-3Oc)
For support and questions, visit [the Support Forum][Support-Forum] or [the Forge Discord server][Discord].
# Creating Mods
[See the "Getting Started" section in the Forge Documentation](https://mcforge.readthedocs.io/en/latest/gettingstarted/).
[See the "Getting Started" section in the Forge Documentation][Getting-Started].
# Contribute to Forge
If you wish to actually inspect Forge, submit PRs or otherwise work
with Forge itself, you're in the right place!
[See the guide to setting up a Forge workspace](http://mcforge.readthedocs.io/en/latest/forgedev/).
[See the guide to setting up a Forge workspace][ForgeDev].
### Pull requests
[See the "Making Changes and Pull Requests" section in the Forge documentation](https://mcforge.readthedocs.io/en/latest/forgedev/#making-changes-and-pull-requests).
[See the "Making Changes and Pull Requests" section in the Forge documentation][Pull-Requests].
Please read the contributing guidelines found [here](CONTRIBUTING.md) before making a pull request.
Please read the contributing guidelines found [here][Contributing] before making a pull request.
### Contributor License Agreement
We require all contributors to acknowledge the [Forge Contributor
License Agreement](https://cla-assistant.io/MinecraftForge/MinecraftForge). Please ensure you have a valid email address
associated with your GitHub account to do this. If you have previously
We require all contributors to acknowledge the [Forge Contributor License Agreement][CLA].
Please ensure you have a valid email address associated with your GitHub account to do this. If you have previously
signed it, you should be OK.
#### Donate
*Forge is a large project with many collaborators working on it around the clock. Forge is and will always remain free to use and modify. However, it costs money to run such a large project as this, so please consider [becoming a patron](https://www.patreon.com/LexManos).*
*Forge is a large project with many collaborators working on it around the clock. Forge is and will always remain free
to use and modify. However, it costs money to run such a large project as this, so please consider
[becoming a patron][Patreon].*
[Contributing]: ./CONTRIBUTING.md
[CLA]: https://cla-assistant.io/MinecraftForge/MinecraftForge
[Download]: https://files.minecraftforge.net/
[Forum]: https://www.minecraftforge.net/forum/
[Discord]: https://discord.gg/UvedJ9m
[Documentation]: https://mcforge.readthedocs.io
[Forum]: https://forums.minecraftforge.net/
[Support-Forum]: https://forums.minecraftforge.net/forum/18-support-bug-reports/
[Discord]: https://discord.minecraftforge.net/
[Documentation]: https://docs.minecraftforge.net/
[Getting-Started]: https://docs.minecraftforge.net/en/latest/gettingstarted/
[ForgeDev]: https://docs.minecraftforge.net/en/latest/forgedev/
[Pull-Requests]: https://docs.minecraftforge.net/en/latest/forgedev/#making-changes-and-pull-requests
[Patreon]: https://www.patreon.com/MinecraftForge
+17 -9
View File
@@ -1,7 +1,7 @@
Getting Support Surrounding Forge
=================================
**If you do not want to read the following wall of text, no matter its relevance for helping get your issue resolved quickly, please refer to the [Forge Forums](http://www.minecraftforge.net/forum/forum/18-support-bug-reports/) for support.**
**If you do not want to read the following wall of text, no matter its relevance for helping get your issue resolved quickly, please refer to the [Forge Forums][Bug-Reports] for support.**
Before reporting an issue on this repository's bug tracker, please carefully read this document. There are various resources that are more likely to be able to help you with certain kinds of issues. This document provides instructions on how to determine which place best to report to.
@@ -44,7 +44,7 @@ You can generally tell pretty quickly what caused the error at the head of your
1. If you can find the name of a mod in the trace, that mod most likely is the culprit.
- Be aware that you may find multiple mods as participants of a crash. In that case, you can either report to both or try to reason (using the names in the stacktrace) about which mod is at fault.
2. If you can only find Forge (usually starting with `net.minecraftforge`) alongside Vanilla (usually starting with `net.minecraft`) references, chances are that you have found a bug in Forge. In this case, use one of the various [support channels](#forge-support) listed below.
- The occurence of a single call to Forge somewhere in the stacktrace *does not* warrant a report to Forge if there are mods present in the crash report.
- The occurrence of a single call to Forge somewhere in the stacktrace *does not* warrant a report to Forge if there are mods present in the crash report.
3. If there's no mention of Forge or a mod, but any Vanilla class, you might have found a Vanilla bug. You should probably still report it via the Forge channels first, however, due the nature of modded environments and the rarity of Vanilla crash bugs.
4. Always check whether there's a mention of coremods at the beginning of a crash report. If there is and you can't find any clear culprit, consider reporting to these coremods first, since they are free to change any of the code executed in your environment.
@@ -74,17 +74,18 @@ Please note that this list has to be considered incomplete as there is a large n
If you're certain you've found an issue that is directly caused by Forge, you have the following three platforms available for dealing with it, with decreasing relevance for end-users:
1. **Forge Forums:** There's a dedicated [Support & Bug Report](http://www.minecraftforge.net/forum/forum/18-support-bug-reports/) section on the Forge forums, which is led by team of moderators and volunteers who will try to deal with your reports as quickly as possible. *As a player you should probably report here first.*
- For modders, there also is a separate [Modder Support](http://www.minecraftforge.net/forum/forum/70-modder-support/) section where you can ask all your development-related questions. *You should prefer this section for coding help.*
2. **Forge IRC Channel:** You can find a lot of help on the official [`#minecraftforge`](irc://irc.esper.net:6667/minecraftforge) channel on EsperNet. Especially smaller issues that you quickly need help with could be reported there. *Be aware that the channel is more suited towards development questions, so while you may get support there as a player, you should almost always prefer the forums.*
3. **Forge Issue Tracker:** If you're absolutely certain that you've found an issue in the Forge codebase (e.g. you're a modder yourself and have analyzed the stacktrace and relevant code), don't refrain from reporting it on this repository's [issue tracker](https://github.com/MinecraftForge/MinecraftForge/issues). *Please do not use the issues as a means of getting coding help. For developers, the IRC and the [official documentation](https://mcforge.readthedocs.io) are the best sources of information.*
1. **Forge Forums:** There's a dedicated [Support & Bug Report][Bug-Reports] section on the Forge forums, which is led by team of moderators and volunteers who will try to deal with your reports as quickly as possible. *As a player you should probably report here first.*
- For modders, there also is a separate [Modder Support][Modder-Support] section where you can ask all your development-related questions. *You should prefer this section for coding help.*
2. **Forge Discord Server:** You can find a lot of help on the official [Discord Server][Discord], especially smaller issues that you quickly need help with. *Be aware that the channel is more suited towards development questions, so while you may get support there as a player, you should almost always prefer the forums.*
3. **Forge Issue Tracker:** If you're absolutely certain that you've found an issue in the Forge codebase (e.g. you're a modder yourself and have analyzed the stacktrace and relevant code), don't refrain from reporting it on this repository's [issue tracker](https://github.com/MinecraftForge/MinecraftForge/issues). *Please do not use the issues as a means of getting coding help. For developers, the IRC and the [official documentation][Documentation] are the best sources of information.*
### Mod Support
A lot of mods also use GitHub or other source management systems and their integrated issue trackers. The following options will give you the best chances of reporting your issue in the right place:
- **CurseForge:** Many mods are hosted on the [CurseForge platform](https://minecraft.curseforge.com) nowadays. Chances are you already downloaded them from there or as a part of a modpack hosted there. Projects may specify an issue reporting link on their own page, so simply look for it on the mod's project page and you should be directed to the preferred means of error reporting.
- **Searching for a repository directly:** You may need to try and find a repository directly on source control sites. The most common ones are [GitHub](https://github.com) and [BitBucket](https://bitbucket.org). Both come with search capabilities, so simply try to search for the mod's name.
- **Modrinth:** Some modders use [Modrinth](https://modrinth.com/mods) to host their mods, but be mindful that not all mods on this platform are for the Forge modloader.
- **Searching for a repository directly:** You may need to try and find a repository directly on source control sites. The most common ones are [GitHub](https://github.com), [GitLab](https://gitlab.com), and [BitBucket](https://bitbucket.org). Both come with search capabilities, so simply try to search for the mod's name.
### Vanilla Support
@@ -98,7 +99,7 @@ Now that you've identified the source of your issue and decided on the correct s
### Preventing Duplicates and Unnecessary Reports
Use the target issue tracker's search functionality to look for issues which have descriptions similar to yours, but make sure to also search for already closed issues. If you can find one, verify it is in fact the same (or a very similar issue) and simply comment on it if you have anything else to contribute to the discussion, such as other steps to reproduce it. *Do not create duplicates of an existing issue, since it only puts extra effort on maintainers to recognize and close/mark them.*
Additionally, always check the *latest* changelogs of Forge and participating mods to find out if the bug was already noticed and fixed by the developer. Often the maintainer may only support newer versions of the game, so also check the changelogs for versions of Minecraft you don't actually actively play. If the issue was adressed already, *do not report it*, even if the fix only exists for a newer version of the game, since the developer probably had a good reason to do so. If your issue only exists with versions of a mod that are for very old versions of the game, also refrain from reporting them, considering most modders only support the two latest versions of Minecraft, if at all.
Additionally, always check the *latest* changelogs of Forge and participating mods to find out if the bug was already noticed and fixed by the developer. Often the maintainer may only support newer versions of the game, so also check the changelogs for versions of Minecraft you don't actually actively play. If the issue was addressed already, *do not report it*, even if the fix only exists for a newer version of the game, since the developer probably had a good reason to do so. If your issue only exists with versions of a mod that are for very old versions of the game, also refrain from reporting them, considering most modders only support the two latest versions of Minecraft, if at all.
### Required Information
If you're certain that your issue went unnoticed up until now, you can get to actually writing up the report. Below you can find a list of the *required* information any bug report should contain:
@@ -143,4 +144,11 @@ After you have written your bug report, it is time to *wait*. Please be *patient
Every project has a different way of dealing with issues, so don't wonder if there's no comment on it, but somebody "labels" it with certain markers. This helps developers to more easily keep track of bugs and priorities. When any further input from you is required, you will be asked specifically. Do not bug anyone through other channels about resolving your issue.
Once your issue is closed, you will know whether it was valid or turned out to be irreproducible. If you included all of the data from above in your report, chances are that the latter won't be the case.
Once your issue is closed, you will know whether it was valid or turned out to be unreproducible. If you included all of the data from above in your report, chances are that the latter won't be the case.
[Bug-Reports]: https://forums.minecraftforge.net/forum/18-support-bug-reports/
[Modder-Support]: https://forums.minecraftforge.net/forum/70-modder-support/
[Discord]: https://discord.minecraftforge.net/
[Documentation]: https://docs.minecraftforge.net/
+13 -3
View File
@@ -1,6 +1,7 @@
plugins {
id 'com.github.ben-manes.versions'
id 'org.javamodularity.moduleplugin' version '1.8.3' apply false
id 'org.cadixdev.licenser'
}
apply plugin: 'java-library'
apply plugin: 'jacoco'
@@ -9,10 +10,12 @@ apply plugin: 'org.javamodularity.moduleplugin'
dependencyUpdates.rejectVersionIf { isNonStable(it.candidate.version) }
dependencies {
compileOnly('org.jetbrains:annotations:21.0.1')
compileOnly("org.jetbrains:annotations:${JETBRAINS_ANNOTATIONS_VERSION}")
api("net.minecraftforge:eventbus:${EVENTBUS_VERSION}")
implementation(project(':fmlloader'))
api('net.minecraftforge:eventbus:5.0.+')
implementation('commons-io:commons-io:2.5')
implementation("commons-io:commons-io:${COMMONS_IO_VERSION}")
}
task sourcesJar(type: Jar) {
@@ -41,6 +44,7 @@ ext {
}
jar.doFirst {
manifest.attributes['Automatic-Module-Name'] = project.name
MANIFESTS.each { pkg, values ->
if (pkg == '')
manifest.attributes(values)
@@ -53,6 +57,12 @@ tasks.withType(JavaCompile) {
options.compilerArgs << '-Xlint:unchecked'
}
license {
header = rootProject.file('LICENSE-header.txt')
include 'net/minecraftforge/'
}
artifacts {
archives jar
archives sourcesJar
@@ -1,3 +1,8 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml;
import cpw.mods.modlauncher.util.ServiceLoaderUtils;
@@ -1,32 +1,97 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml;
import com.mojang.logging.LogUtils;
import org.slf4j.Logger;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.BooleanSupplier;
import java.util.function.Supplier;
public class CrashReportCallables {
public class CrashReportCallables
{
private static final Logger LOGGER = LogUtils.getLogger();
private static final List<ISystemReportExtender> crashCallables = Collections.synchronizedList(new ArrayList<>());
/**
* Register a custom {@link ISystemReportExtender}
*/
public static void registerCrashCallable(ISystemReportExtender callable)
{
crashCallables.add(callable);
}
public static void registerCrashCallable(String headerName, Supplier<String> reportGenerator) {
registerCrashCallable(new ISystemReportExtender() {
/**
* Register a {@link ISystemReportExtender system report extender} with the given header name and content
* generator, which will always be appended to the system report
* @param headerName The name of the system report entry
* @param reportGenerator The report generator to be called when a crash report is built
*/
public static void registerCrashCallable(String headerName, Supplier<String> reportGenerator)
{
registerCrashCallable(new ISystemReportExtender()
{
@Override
public String getLabel() {
public String getLabel()
{
return headerName;
}
@Override
public String get() {
public String get()
{
return reportGenerator.get();
}
});
}
public static List<ISystemReportExtender> allCrashCallables() {
/**
* Register a {@link ISystemReportExtender system report extender} with the given header name and content
* generator, which will only be appended to the system report when the given {@link BooleanSupplier} returns true
* @param headerName The name of the system report entry
* @param reportGenerator The report generator to be called when a crash report is built
* @param active The supplier of the flag to be checked when a crash report is built
*/
public static void registerCrashCallable(String headerName, Supplier<String> reportGenerator, BooleanSupplier active)
{
registerCrashCallable(new ISystemReportExtender()
{
@Override
public String getLabel()
{
return headerName;
}
@Override
public String get()
{
return reportGenerator.get();
}
@Override
public boolean isActive()
{
try
{
return active.getAsBoolean();
}
catch (Throwable t)
{
LOGGER.warn("CrashCallable '{}' threw an exception while checking the active flag, disabling", headerName, t);
return false;
}
}
});
}
public static List<ISystemReportExtender> allCrashCallables()
{
return List.copyOf(crashCallables);
}
}
@@ -1,20 +1,6 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2021.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml;
@@ -25,9 +11,11 @@ import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.function.Supplier;
import com.google.common.base.Stopwatch;
@@ -67,17 +55,29 @@ public class DeferredWorkQueue
public void runTasks() {
if (tasks.isEmpty()) return;
LOGGER.debug(LOADING, "Dispatching synchronous work for work queue {}: {} jobs", modLoadingStage, tasks.size());
RuntimeException aggregate = new RuntimeException();
Stopwatch timer = Stopwatch.createStarted();
tasks.forEach(t->makeRunnable(t, Runnable::run));
tasks.forEach(t -> makeRunnable(t, Runnable::run, aggregate));
timer.stop();
LOGGER.debug(LOADING, "Synchronous work queue completed in {}", timer);
if (aggregate.getSuppressed().length > 0) {
LOGGER.fatal(
LOADING,
"Synchronous work queue completed exceptionally in {}, see suppressed exceptions for details:",
timer,
aggregate
);
throw aggregate;
} else {
LOGGER.debug(LOADING, "Synchronous work queue completed in {}", timer);
}
}
private static void makeRunnable(TaskInfo ti, Executor executor) {
private static void makeRunnable(TaskInfo ti, Executor executor, RuntimeException aggregate) {
executor.execute(() -> {
Stopwatch timer = Stopwatch.createStarted();
ModLoadingContext.get().setActiveContainer(ti.owner);
try {
ti.future.exceptionally(t -> captureException(ti.owner.getModId(), aggregate, t));
ti.task.run();
} finally {
ModLoadingContext.get().setActiveContainer(null);
@@ -89,13 +89,38 @@ public class DeferredWorkQueue
});
}
private static <T> T captureException(String modId, RuntimeException aggregate, Throwable throwable) {
if (throwable instanceof CompletionException ce) {
throwable = ce.getCause();
}
aggregate.addSuppressed(throwable);
LOGGER.error("Mod '{}' encountered an error in a deferred task:", modId, throwable);
return null;
}
public CompletableFuture<Void> enqueueWork(final ModContainer modInfo, final Runnable work) {
return CompletableFuture.runAsync(work, r->tasks.add(new TaskInfo(modInfo, r)));
return enqueueWork(modInfo, taskInfo -> CompletableFuture.runAsync(work, r -> taskInfo.task = r));
}
public <T> CompletableFuture<T> enqueueWork(final ModContainer modInfo, final Supplier<T> work) {
return CompletableFuture.supplyAsync(work, r->tasks.add(new TaskInfo(modInfo, r)));
return enqueueWork(modInfo, taskInfo -> CompletableFuture.supplyAsync(work, r -> taskInfo.task = r));
}
record TaskInfo(ModContainer owner, Runnable task) {}
private <T> CompletableFuture<T> enqueueWork(final ModContainer modInfo, Function<TaskInfo, CompletableFuture<T>> futureGen) {
TaskInfo taskInfo = new TaskInfo(modInfo);
CompletableFuture<T> future = futureGen.apply(taskInfo);
taskInfo.future = future;
tasks.add(taskInfo);
return future;
}
private static class TaskInfo {
private final ModContainer owner;
private Runnable task;
private CompletableFuture<?> future;
private TaskInfo(ModContainer owner) {
this.owner = owner;
}
}
}
@@ -1,20 +1,6 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2021.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml;
@@ -1,3 +1,8 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml;
public interface I18NParser {
@@ -1,3 +1,8 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml;
import net.minecraftforge.eventbus.api.IEventBus;
@@ -0,0 +1,5 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
@@ -1,20 +1,6 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2021.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml;
@@ -22,41 +8,92 @@ package net.minecraftforge.fml;
import java.util.function.BiPredicate;
import java.util.function.Supplier;
/**
* An extension point for a mod container.
*
* <p>An extension point can be registered for a mod container using {@link ModContainer#registerExtensionPoint(Class, Supplier)}
* and retrieved (if present) using {@link ModContainer#getCustomExtension(Class)}. An extension point allows a mod to
* supply an arbitrary value as a record class to another mod or framework through their mod container class, avoiding
* the use of {@link InterModComms} or other external frameworks to pass around these values.</p>
*
* <p>The usual way to declare an extension point is to implement this interface on a record class, with the type
* parameter being a reference to the class itself. For example, {@code record MyExtension(...) extends
* IExtensionPoint<MyExtension>} would declare an extension point which supplies a {@code MyExtension} object. However,
* there is no hard requirement that an extension point's type parameter must be in reference to itself; the type
* parameter may reference another record class instead.</p>
*
* @param <T> the type of the record which is held by the extension point
*/
@SuppressWarnings("unused") // Type parameter T
public interface IExtensionPoint<T extends Record>
{
/**
* Compatibility display test for the mod.
* Used for displaying compatibility with remote servers with the same mod, and on disk saves.
* Extension point for the compatibility display test used on the server selection screen.
*
* The supplier provides my "local" version for sending across the impl or writing to disk
* The predicate tests the version from a remote instance or save for acceptability (Boolean is true for impl, false for local save)
* and returns true if the version is compatible.
* <em>Note: "server" and "client" refers to the {@linkplain net.minecraftforge.api.distmarker.Dist#DEDICATED_SERVER dedicated server}
* and {@linkplain net.minecraftforge.api.distmarker.Dist#CLIENT game client} physical distributions, rather than the
* {@linkplain LogicalSide logical server and client}.</em>
*
* <p>Return {@code net.minecraftforge.impl.impl.FMLNetworkConstants#IGNORESERVERONLY} in the supplier, if you wish to be ignored
* as a server side only mod.</p>
* <p>Return true in the predicate for all values of the input string (when impl boolean is true) if you are client side,
* and don't care about matching any potential server version.</p>
* <p>The {@link Supplier} provides the local compatibility version, which is sent from the server to the client
* for multiplayer connections or stored to disk for the world save. The {@link BiPredicate} accepts the remote
* compatibility version and a boolean indicating whether the remote version is from the server or a world save,
* where {@code true} means it is from the server and {@code false} means it is from the world save. The return
* value of the predicate determines whether the remote version is "compatible" for the purposes of the display test.</p>
*
* <p>
* Examples: A server only mod
* </p>
* <p>The local compatibility version may be of the value {@link net.minecraftforge.network.NetworkConstants#IGNORESERVERONLY},
* in which case clients will ignore the mod's presence if it is present on the server but not on the client.
* However, the remote version test predicate must still accept this value as a remote version in order to display
* as compatible if the mod is present on the client.</p>
*
* <p><strong>The compatibility display test does not necessarily indicate the success or failure of an actual
* connection attempt.</strong> Factors such as display test extension misconfiguration, truncation of ping data,
* difference of registry data or network channels between server and client, and others may cause the result of the
* compatibility test to not reflect the actual likelihood of a connection successfully being established between
* the server and the client.</p>
*
* <p>An example declaration of a display test extension registration for a regular mod (requires to be present on
* server and client) is as follows:</p>
* <pre>{@code
* registerExtensionPoint(DISPLAYTEST, ()->Pair.of(
* ()->FMLNetworkConstants.IGNORESERVERONLY, // ignore me, I'm a server only mod
* (s,b)->true // i accept anything from the server or the save, if I'm asked
* )
* String compatibilityVersion = "1"; // Could be linked with a network channel version or mod version
* ModLoadingContext.get().registerExtensionPoint(IExtensionPoint.DisplayTest.class,
* () -> new IExtensionPoint.DisplayTest(
* () -> compatibilityVersion,
* (remoteVersion, isFromServer) -> remoteVersion.equals(compatibilityVersion)
* )
* );
* }</pre>
*
* <p>
* Examples: A client only mod
* </p>
* <p>An example declaration of a display test extension registration for a <em>server-side-only</em> mod (does not
* require to be present on the client) is as follows:</p>
* <pre>{@code
* registerExtensionPoint(DISPLAYTEST, ()->Pair.of(
* ()->"anything. i don't care", // if i'm actually on the server, this string is sent but i'm a client only mod, so it won't be
* (remoteversionstring,networkbool)->networkbool // i accept anything from the server, by returning true if it's asking about the server
* )
* ModLoadingContext.get().registerExtensionPoint(IExtensionPoint.DisplayTest.class,
* () -> new IExtensionPoint.DisplayTest(
* // Ignore this mod if not present on the client
* () -> NetworkConstants.IGNORESERVERONLY,
* // If present on the client, accept any version if from a server
* (remoteVersion, isFromServer) -> isFromServer
* )
* );
* }</pre>
*
* <p>An example declaration of a display test extension registration for a <em>client-side-only</em> mod (does not
* require to be present on the server) is as follows:</p>
* <pre>{@code
* ModLoadingContext.get().registerExtensionPoint(IExtensionPoint.DisplayTest.class,
* () -> new IExtensionPoint.DisplayTest(
* // Send any version from server to client, since we will be accepting any version as well
* () -> "dQw4w9WgXcQ",
* // Accept any version on the client, from server or from save
* (remoteVersion, isFromServer) -> true
* )
* );
* }</pre>
*
* @see net.minecraftforge.network.ServerStatusPing
* @see net.minecraftforge.client.ForgeHooksClient#processForgeListPingData(net.minecraft.network.protocol.status.ServerStatus, net.minecraft.client.multiplayer.ServerData)
*/
record DisplayTest(Supplier<String> suppliedVersion, BiPredicate<String, Boolean> remoteVersionTest) implements IExtensionPoint<DisplayTest> {}
@SuppressWarnings("JavadocReference") // reference to NetworkConstants, ForgeHooksClient
record DisplayTest(Supplier<String> suppliedVersion, BiPredicate<String, Boolean> remoteVersionTest) implements IExtensionPoint<DisplayTest> {
public static final String IGNORESERVERONLY = "OHNOES\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31";
}
}
@@ -1,25 +1,98 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.fml.loading.progress.ProgressMeter;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.ToIntFunction;
/**
* A mod loading state. During mod loading, the mod loader transitions between states in a defined sorted list of states,
* grouped into various {@link ModLoadingPhase phases}.
*
* @see IModStateProvider
*/
public interface IModLoadingState {
/**
* {@return the name of this state}
*/
String name();
/**
* {@return the name of the state immediately previous to this state} This may be a blank name, which indicates this
* is either the first mod loading state or an exceptional mod loading state (such as a situation where errors
* prevent the loading process from continuing normally).
*/
String previous();
/**
* {@return the mod loading phase this state belongs to} For exceptional mod loading states, this should be
* {@link ModLoadingPhase#ERROR}.
*/
ModLoadingPhase phase();
/**
* {@return a function returning a human-friendly message for this state}
*/
Function<ModList, String> message();
/**
* @return a function that computes the size of this transition based on the size of the modlist.
* Used to compute progress.
*/
ToIntFunction<ModList> size();
/**
* {@return an optional runnable, which runs before starting the transition from this state to the next}
* @see #buildTransition(Executor, Executor, ProgressMeter, Function, Function)
*/
Optional<Consumer<ModList>> inlineRunnable();
default <T extends Event & IModBusEvent> Optional<CompletableFuture<List<Throwable>>> buildTransition(final Executor syncExecutor, final Executor parallelExecutor) {
return buildTransition(syncExecutor, parallelExecutor, e->CompletableFuture.runAsync(()->{}, e), e->CompletableFuture.runAsync(()->{}, e));
/**
* Builds the transition task for this state with a blank pre-sync and post-sync task.
*
* @param <T> a type of event fired on the mod-specific event bus
* @param syncExecutor a synchronous executor
* @param parallelExecutor a parallel executor
* @param progressBar a progress meter for tracking progress
* @return a transition task for this state
* @see #buildTransition(Executor, Executor, ProgressMeter, Function, Function)
*/
default <T extends Event & IModBusEvent>
Optional<CompletableFuture<Void>> buildTransition(final Executor syncExecutor,
final Executor parallelExecutor,
final ProgressMeter progressBar) {
return buildTransition(syncExecutor, parallelExecutor, progressBar,
e -> CompletableFuture.runAsync(() -> {}, e),
e -> CompletableFuture.runAsync(() -> {}, e));
}
<T extends Event & IModBusEvent> Optional<CompletableFuture<List<Throwable>>> buildTransition(Executor syncExecutor, Executor parallelExecutor, Function<Executor, CompletableFuture<Void>> preSyncTask, Function<Executor, CompletableFuture<Void>> postSyncTask);
/**
* Builds the transition task for this state. The pre-sync and post-sync task functions allow the transition builder
* to run these tasks on the same executor as the actual event dispatch and pre/post hooks.
*
* @param <T> a type of event fired on the mod-specific event bus
* @param syncExecutor a synchronous executor
* @param parallelExecutor a parallel executor
* @param progressBar a progress meter for tracking progress
* @param preSyncTask a function which returns a task to run before event pre-dispatch hook
* @param postSyncTask a function which returns a task to run after event post-dispatch hook
* @return a transition task for this state
*/
<T extends Event & IModBusEvent>
Optional<CompletableFuture<Void>> buildTransition(final Executor syncExecutor,
final Executor parallelExecutor,
final ProgressMeter progressBar,
final Function<Executor, CompletableFuture<Void>> preSyncTask,
final Function<Executor, CompletableFuture<Void>> postSyncTask);
}
@@ -1,7 +1,21 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml;
import java.util.List;
/**
* Provides a list of mod loading states which the mod loader may transition between.
*
* <p>There may be multiple mod state providers in a single application, where all states from each provider is
* combined into a single list and ordered.</p>
*/
public interface IModStateProvider {
/**
* {@return the list of mod loading states known to this provider}
*/
List<IModLoadingState> getAllStates();
}
@@ -1,10 +1,15 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.fml.loading.progress.ProgressMeter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
@@ -19,18 +24,32 @@ public interface IModStateTransition {
return new NoopTransition();
}
default <T extends Event & IModBusEvent> CompletableFuture<List<Throwable>> build(Executor syncExecutor, Executor parallelExecutor, Function<Executor, CompletableFuture<Void>> preSyncTask, Function<Executor, CompletableFuture<Void>> postSyncTask) {
List<CompletableFuture<List<Throwable>>> cfs = new ArrayList<>();
default <T extends Event & IModBusEvent>
CompletableFuture<Void> build(final String name,
final Executor syncExecutor,
final Executor parallelExecutor,
final ProgressMeter progressBar,
final Function<Executor, CompletableFuture<Void>> preSyncTask,
final Function<Executor, CompletableFuture<Void>> postSyncTask) {
List<CompletableFuture<Void>> futures = new ArrayList<>();
this.eventFunctionStream().get()
.map(f->(EventGenerator<T>)f)
.reduce((head, tail)-> addCompletableFutureTaskForModDispatch(syncExecutor, parallelExecutor, cfs, head, ModLoadingStage::currentState, tail))
.ifPresent(last-> addCompletableFutureTaskForModDispatch(syncExecutor, parallelExecutor, cfs, last, nextModLoadingStage(), null));
.map(f -> (EventGenerator<T>) f)
.reduce((head, tail) -> addCompletableFutureTaskForModDispatch(syncExecutor, parallelExecutor, futures, progressBar, head, ModLoadingStage::currentState, tail))
.ifPresent(last -> addCompletableFutureTaskForModDispatch(syncExecutor, parallelExecutor, futures, progressBar, last, nextModLoadingStage(), null));
final CompletableFuture<Void> preSyncTaskCF = preSyncTask.apply(syncExecutor);
final CompletableFuture<List<Throwable>> eventDispatchCF = ModList.gather(cfs).thenCompose(ModList::completableFutureFromExceptionList);
final CompletableFuture<List<Throwable>> postEventDispatchCF = preSyncTaskCF.thenComposeAsync(n -> eventDispatchCF, parallelExecutor).thenApply(r -> {
postSyncTask.apply(syncExecutor);
return r;
});
final CompletableFuture<Void> eventDispatchCF = ModList.gather(futures).thenCompose(ModList::completableFutureFromExceptionList);
final CompletableFuture<Void> postEventDispatchCF = preSyncTaskCF
.thenApplyAsync(n -> {
progressBar.label(progressBar.name() + ": dispatching "+name);
return null;
}, parallelExecutor)
.thenComposeAsync(n -> eventDispatchCF, parallelExecutor)
.thenApply(r -> {
postSyncTask.apply(syncExecutor);
return null;
});
return this.finalActivityGenerator().apply(syncExecutor, postEventDispatchCF);
}
@@ -38,18 +57,32 @@ public interface IModStateTransition {
return ModLoadingStage::nextState;
}
private <T extends Event & IModBusEvent> EventGenerator<T> addCompletableFutureTaskForModDispatch(final Executor syncExecutor, final Executor parallelExecutor, final List<CompletableFuture<List<Throwable>>> completeableFutures, final EventGenerator<T> eventGenerator, BiFunction<ModLoadingStage, Throwable, ModLoadingStage> nextState, final EventGenerator<T> nextGenerator) {
completeableFutures.add(((BiFunction<Executor, EventGenerator<T>, CompletableFuture<List<Throwable>>>)preDispatchHook()).apply(threadSelector().apply(syncExecutor, parallelExecutor), eventGenerator));
completeableFutures.add(ModList.get().futureVisitor(eventGenerator, nextState).apply(threadSelector().apply(syncExecutor, parallelExecutor)));
completeableFutures.add(((BiFunction<Executor, EventGenerator<T>, CompletableFuture<List<Throwable>>>)postDispatchHook()).apply(threadSelector().apply(syncExecutor, parallelExecutor), eventGenerator));
private <T extends Event & IModBusEvent>
EventGenerator<T> addCompletableFutureTaskForModDispatch(final Executor syncExecutor,
final Executor parallelExecutor,
final List<CompletableFuture<Void>> completableFutures,
final ProgressMeter progressBar,
final EventGenerator<T> eventGenerator,
final BiFunction<ModLoadingStage, Throwable, ModLoadingStage> nextState,
final EventGenerator<T> nextGenerator) {
final Executor selectedExecutor = threadSelector().apply(syncExecutor, parallelExecutor);
var preDispatchHook = (BiFunction<Executor, EventGenerator<T>, CompletableFuture<Void>>) preDispatchHook();
completableFutures.add(preDispatchHook.apply(selectedExecutor, eventGenerator));
completableFutures.add(ModList.get().futureVisitor(eventGenerator, progressBar, nextState).apply(threadSelector().apply(syncExecutor, parallelExecutor)));
var postDispatchHook = (BiFunction<Executor, EventGenerator<T>, CompletableFuture<Void>>) postDispatchHook();
completableFutures.add(postDispatchHook.apply(selectedExecutor, eventGenerator));
return nextGenerator;
}
Supplier<Stream<EventGenerator<?>>> eventFunctionStream();
ThreadSelector threadSelector();
BiFunction<Executor, CompletableFuture<List<Throwable>>, CompletableFuture<List<Throwable>>> finalActivityGenerator();
BiFunction<Executor, ? extends EventGenerator<?>, CompletableFuture<List<Throwable>>> preDispatchHook();
BiFunction<Executor, ? extends EventGenerator<?>, CompletableFuture<List<Throwable>>> postDispatchHook();
BiFunction<Executor, CompletableFuture<Void>, CompletableFuture<Void>> finalActivityGenerator();
BiFunction<Executor, ? extends EventGenerator<?>, CompletableFuture<Void>> preDispatchHook();
BiFunction<Executor, ? extends EventGenerator<?>, CompletableFuture<Void>> postDispatchHook();
interface EventGenerator<T extends Event & IModBusEvent> extends Function<ModContainer, T> {
static <FN extends Event & IModBusEvent> EventGenerator<FN> fromFunction(Function<ModContainer, FN> fn) {
@@ -71,17 +104,17 @@ record NoopTransition() implements IModStateTransition {
}
@Override
public BiFunction<Executor, CompletableFuture<List<Throwable>>, CompletableFuture<List<Throwable>>> finalActivityGenerator() {
public BiFunction<Executor, CompletableFuture<Void>, CompletableFuture<Void>> finalActivityGenerator() {
return (e, t) -> t.thenApplyAsync(Function.identity(), e);
}
@Override
public BiFunction<Executor, ? extends EventGenerator<?>, CompletableFuture<List<Throwable>>> preDispatchHook() {
return (t, f)-> CompletableFuture.completedFuture(Collections.emptyList());
public BiFunction<Executor, ? extends EventGenerator<?>, CompletableFuture<Void>> preDispatchHook() {
return (t, f)-> CompletableFuture.completedFuture(null);
}
@Override
public BiFunction<Executor, ? extends EventGenerator<?>, CompletableFuture<List<Throwable>>> postDispatchHook() {
return (t, f)-> CompletableFuture.completedFuture(Collections.emptyList());
public BiFunction<Executor, ? extends EventGenerator<?>, CompletableFuture<Void>> postDispatchHook() {
return (t, f)-> CompletableFuture.completedFuture(null);
}
}
@@ -1,20 +1,6 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2021.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml;
@@ -24,4 +10,9 @@ import java.util.function.Supplier;
public interface ISystemReportExtender extends Supplier<String>
{
String getLabel();
default boolean isActive()
{
return true;
}
}
@@ -1,20 +1,6 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2021.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml;
@@ -1,20 +1,6 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2021.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml;
@@ -1,20 +1,6 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2021.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml;
@@ -1,30 +1,45 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2021.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml;
import net.minecraftforge.api.distmarker.Dist;
/**
* A logical side of the Minecraft game.
* <p>
* The {@linkplain Dist#CLIENT client distribution} has a copy of the logical client and the logical server, while the
* {@linkplain Dist#DEDICATED_SERVER dedicated server distribution} only holds
* the logical server.
*
* @see Dist
*/
public enum LogicalSide
{
CLIENT, SERVER;
/**
* The logical client of the Minecraft game, which interfaces with the player's inputs and renders the player's
* viewpoint.
* <p>
* The logical client is only shipped with the client distribution of the game.
*
* @see Dist#CLIENT
*/
CLIENT,
/**
* The logical server of the Minecraft game, responsible for connecting to clients and running the simulation logic
* on the level.
* <p>
* The logical server is shipped with both client and dedicated server distributions. The client distribution runs
* the logical server for singleplayer mode and LAN play.
*
* @see Dist#DEDICATED_SERVER
*/
SERVER;
/**
* @return if the logical side is a server.
* {@return if this logical side is the server}
*/
public boolean isServer()
{
@@ -32,7 +47,7 @@ public enum LogicalSide
}
/**
* @return if the logical side is a client.
* {@return if the logical side is the client}
*/
public boolean isClient()
{
@@ -1,20 +1,6 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2021.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml;
@@ -23,6 +9,7 @@ import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.config.IConfigEvent;
import net.minecraftforge.fml.config.ModConfig;
import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.fml.loading.progress.ProgressMeter;
import net.minecraftforge.forgespi.language.IModInfo;
import java.util.EnumMap;
@@ -70,9 +57,24 @@ public abstract class ModContainer
this.namespace = this.modId;
this.modInfo = info;
this.modLoadingStage = ModLoadingStage.CONSTRUCT;
// default displaytest extension checks for version string match
registerExtensionPoint(IExtensionPoint.DisplayTest.class, () -> new IExtensionPoint.DisplayTest(()->this.modInfo.getVersion().toString(),
(incoming, isNetwork)->Objects.equals(incoming, this.modInfo.getVersion().toString())));
final String displayTestString = info.getConfig().<String>getConfigElement("displayTest").orElse("MATCH_VERSION"); // missing defaults to DEFAULT type
Supplier<IExtensionPoint.DisplayTest> displayTestSupplier = switch (displayTestString) {
case "MATCH_VERSION" -> // default displaytest checks for version string match
() -> new IExtensionPoint.DisplayTest(() -> this.modInfo.getVersion().toString(),
(incoming, isNetwork) -> Objects.equals(incoming, this.modInfo.getVersion().toString()));
case "IGNORE_SERVER_VERSION" -> // Ignores any version information coming from the server - use for server only mods
() -> new IExtensionPoint.DisplayTest(() -> IExtensionPoint.DisplayTest.IGNORESERVERONLY, (incoming, isNetwork) -> true);
case "IGNORE_ALL_VERSION" -> // Ignores all information and provides no information
() -> new IExtensionPoint.DisplayTest(() -> "", (incoming, isNetwork) -> true);
case "NONE" -> null; // NO display test at all - use this if you're going to do your own display test
default -> // any other value throws an exception
throw new IllegalArgumentException("Invalid displayTest value supplied in mods.toml");
};
if (displayTestSupplier != null)
registerExtensionPoint(IExtensionPoint.DisplayTest.class, displayTestSupplier);
else
extensionPoints.remove(IExtensionPoint.DisplayTest.class);
}
/**
@@ -112,6 +114,7 @@ public abstract class ModContainer
public static <T extends Event & IModBusEvent> CompletableFuture<Void> buildTransitionHandler(
final ModContainer target,
final IModStateTransition.EventGenerator<T> eventGenerator,
final ProgressMeter progressBar,
final BiFunction<ModLoadingStage, Throwable, ModLoadingStage> stateChangeHandler,
final Executor executor) {
return CompletableFuture
@@ -122,6 +125,7 @@ public abstract class ModContainer
}, executor)
.whenComplete((mc, exception) -> {
target.modLoadingStage = stateChangeHandler.apply(target.modLoadingStage, exception);
progressBar.increment();
ModLoadingContext.get().setActiveContainer(null);
});
}
@@ -1,26 +1,13 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2022.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.fml.loading.progress.ProgressMeter;
import net.minecraftforge.forgespi.language.IModFileInfo;
import net.minecraftforge.forgespi.language.IModInfo;
import net.minecraftforge.forgespi.language.ModFileScanData;
@@ -35,7 +22,7 @@ import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -68,6 +55,7 @@ public class ModList
private List<ModContainer> mods;
private Map<String, ModContainer> indexedMods;
private List<ModFileScanData> modFileScanData;
private List<ModContainer> sortedContainers;
private ModList(final List<ModFile> modFiles, final List<ModInfo> sortedList)
{
@@ -125,21 +113,22 @@ public class ModList
return this.fileById.get(modid);
}
<T extends Event & IModBusEvent> Function<Executor, CompletableFuture<List<Throwable>>> futureVisitor(
<T extends Event & IModBusEvent> Function<Executor, CompletableFuture<Void>> futureVisitor(
final IModStateTransition.EventGenerator<T> eventGenerator,
final ProgressMeter progressBar,
final BiFunction<ModLoadingStage, Throwable, ModLoadingStage> stateChange) {
return executor -> gather(
this.mods.stream()
.map(mod -> ModContainer.buildTransitionHandler(mod, eventGenerator, stateChange, executor))
.map(mod -> ModContainer.buildTransitionHandler(mod, eventGenerator, progressBar, stateChange, executor))
.collect(Collectors.toList()))
.thenComposeAsync(ModList::completableFutureFromExceptionList, executor);
}
static CompletionStage<List<Throwable>> completableFutureFromExceptionList(List<? extends Map.Entry<?, Throwable>> t) {
static CompletionStage<Void> completableFutureFromExceptionList(List<? extends Map.Entry<?, Throwable>> t) {
if (t.stream().noneMatch(e->e.getValue()!=null)) {
return CompletableFuture.completedFuture(Collections.emptyList());
return CompletableFuture.completedFuture(null);
} else {
final List<Throwable> throwables = t.stream().filter(e -> e.getValue() != null).map(Map.Entry::getValue).collect(Collectors.toList());
CompletableFuture<List<Throwable>> cf = new CompletableFuture<>();
CompletableFuture<Void> cf = new CompletableFuture<>();
final RuntimeException accumulator = new RuntimeException();
cf.completeExceptionally(accumulator);
throwables.forEach(exception -> {
@@ -170,6 +159,7 @@ public class ModList
void setLoadedMods(final List<ModContainer> modContainers)
{
this.mods = modContainers;
this.sortedContainers = modContainers.stream().sorted(Comparator.comparingInt(c->sortedList.indexOf(c.getModInfo()))).toList();
this.indexedMods = modContainers.stream().collect(Collectors.toMap(ModContainer::getModId, Function.identity()));
}
@@ -233,6 +223,10 @@ public class ModList
indexedMods.forEach(modContainerConsumer);
}
public void forEachModInOrder(Consumer<ModContainer> containerConsumer) {
this.sortedContainers.forEach(containerConsumer);
}
public <T> Stream<T> applyForEachModContainer(Function<ModContainer, T> function) {
return indexedMods.values().stream().map(function);
}
@@ -1,20 +1,6 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2021.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml;
@@ -22,26 +8,27 @@ package net.minecraftforge.fml;
import com.google.common.collect.ImmutableList;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.fml.loading.FMLEnvironment;
import net.minecraftforge.fml.loading.FMLLoader;
import net.minecraftforge.fml.loading.ImmediateWindowHandler;
import net.minecraftforge.fml.loading.LoadingModList;
import net.minecraftforge.fml.loading.moddiscovery.InvalidModIdentifier;
import net.minecraftforge.fml.loading.moddiscovery.ModFile;
import net.minecraftforge.fml.loading.moddiscovery.ModFileInfo;
import net.minecraftforge.fml.loading.progress.StartupMessageManager;
import net.minecraftforge.fml.loading.moddiscovery.ModInfo;
import net.minecraftforge.fml.loading.progress.ProgressMeter;
import net.minecraftforge.fml.loading.progress.StartupNotificationManager;
import net.minecraftforge.forgespi.language.IModInfo;
import net.minecraftforge.forgespi.language.IModLanguageProvider;
import net.minecraftforge.forgespi.locating.ForgeFeature;
import net.minecraftforge.forgespi.locating.IModFile;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.Executor;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -96,7 +83,8 @@ public class ModLoader
private final ModStateManager stateManager;
private boolean loadingStateValid;
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
private final Optional<Consumer<String>> statusConsumer = StartupMessageManager.modLoaderConsumer();
private final Optional<Consumer<String>> statusConsumer = StartupNotificationManager.modLoaderConsumer();
private final Set<IModLoadingState> completedStates = new HashSet<>();
private ModList modList;
private ModLoader()
@@ -110,7 +98,7 @@ public class ModLoader
.map(file -> new ModLoadingWarning(null, ModLoadingStage.VALIDATE, InvalidModIdentifier.identifyJarProblem(file.getFilePath()).orElse("fml.modloading.brokenfile"), file.getFileName()))
.collect(Collectors.toList());
FMLLoader.getLoadingModList().getModFiles().stream()
.filter(ModFileInfo::missingLicense) //Search for files with missing licenses
.filter(ModFileInfo::missingLicense)
.filter(modFileInfo -> modFileInfo.getMods().stream().noneMatch(thisModInfo -> this.loadingExceptions.stream().map(ModLoadingException::getModInfo).anyMatch(otherInfo -> otherInfo == thisModInfo))) //Ignore files where any other mod already encountered an error
.map(modFileInfo -> new ModLoadingException(null, ModLoadingStage.VALIDATE, "fml.modloading.missinglicense", null, modFileInfo.getFile()))
.forEach(this.loadingExceptions::add);
@@ -147,19 +135,35 @@ public class ModLoader
* @param periodicTask Optional periodic task to perform on the main thread while other activities run
*/
public void gatherAndInitializeMods(final ModWorkManager.DrivenExecutor syncExecutor, final Executor parallelExecutor, final Runnable periodicTask) {
ForgeFeature.registerFeature("javaVersion", ForgeFeature.VersionFeatureTest.forVersionString(IModInfo.DependencySide.BOTH, System.getProperty("java.version")));
ForgeFeature.registerFeature("openGLVersion", ForgeFeature.VersionFeatureTest.forVersionString(IModInfo.DependencySide.CLIENT, ImmediateWindowHandler.getGLVersion()));
loadingStateValid = true;
statusConsumer.ifPresent(c->c.accept("Waiting for scan to complete"));
FMLLoader.backgroundScanHandler.waitForScanToComplete(periodicTask);
statusConsumer.ifPresent(c->c.accept("Loading mods"));
final ModList modList = ModList.of(loadingModList.getModFiles().stream().map(ModFileInfo::getFile).toList(),
loadingModList.getMods());
if (!this.loadingExceptions.isEmpty()) {
LOGGER.fatal(CORE, "Error during pre-loading phase", loadingExceptions.get(0));
statusConsumer.ifPresent(c->c.accept("ERROR DURING MOD LOADING"));
modList.setLoadedMods(Collections.emptyList());
loadingStateValid = false;
throw new LoadingFailedException(loadingExceptions);
}
statusConsumer.ifPresent(c->c.accept("Building Mod List"));
List<? extends ForgeFeature.Bound> failedBounds = loadingModList.getMods().stream()
.map(ModInfo::getForgeFeatures)
.flatMap(Collection::stream)
.filter(bound -> !ForgeFeature.testFeature(FMLEnvironment.dist, bound))
.toList();
if (!failedBounds.isEmpty()) {
LOGGER.fatal(CORE, "Failed to validate feature bounds for mods: {}", failedBounds);
statusConsumer.ifPresent(c->c.accept("ERROR DURING MOD LOADING"));
modList.setLoadedMods(Collections.emptyList());
loadingStateValid = false;
throw new LoadingFailedException(failedBounds.stream()
.map(fb -> new ModLoadingException(fb.modInfo(), ModLoadingStage.CONSTRUCT, "fml.modloading.feature.missing", null, fb, ForgeFeature.featureValue(fb)))
.toList());
}
final List<ModContainer> modContainers = loadingModList.getModFiles().stream()
.map(ModFileInfo::getFile)
.map(this::buildMods)
@@ -167,50 +171,55 @@ public class ModLoader
.toList();
if (!loadingExceptions.isEmpty()) {
LOGGER.fatal(CORE, "Failed to initialize mod containers", loadingExceptions.get(0));
statusConsumer.ifPresent(c->c.accept("ERROR DURING MOD LOADING"));
modList.setLoadedMods(Collections.emptyList());
loadingStateValid = false;
throw new LoadingFailedException(loadingExceptions);
}
modList.setLoadedMods(modContainers);
this.modList = modList;
statusConsumer.ifPresent(c->c.accept("Dispatching gathering events"));
stateManager.getStates(ModLoadingPhase.GATHER).forEach(mls->dispatchAndHandleError(mls, syncExecutor, parallelExecutor, periodicTask));
statusConsumer.ifPresent(c->c.accept("Gathering phase complete"));
var stateList = stateManager.getStates(ModLoadingPhase.GATHER);
var progress = StartupMessageManager.addProgressBar("Mod Gather", stateList.stream().mapToInt(mls -> mls.size().applyAsInt(modList)).sum());
stateList.forEach(mls->dispatchAndHandleError(mls, syncExecutor, parallelExecutor, periodicTask, progress));
progress.complete();
}
public void loadMods(final ModWorkManager.DrivenExecutor syncExecutor, final Executor parallelExecutor, final Runnable periodicTask) {
statusConsumer.ifPresent(c->c.accept("Performing load phase"));
stateManager.getStates(ModLoadingPhase.LOAD).forEach(mls->dispatchAndHandleError(mls, syncExecutor, parallelExecutor, periodicTask));
statusConsumer.ifPresent(c->c.accept("Mod setup complete"));
var stateList = stateManager.getStates(ModLoadingPhase.LOAD);
var progress = StartupMessageManager.addProgressBar("Mod Loading", stateList.stream().mapToInt(mls -> mls.size().applyAsInt(modList)).sum());
stateList.forEach(mls->dispatchAndHandleError(mls, syncExecutor, parallelExecutor, periodicTask, progress));
progress.complete();
}
public void finishMods(final ModWorkManager.DrivenExecutor syncExecutor, final Executor parallelExecutor, final Runnable periodicTask) {
statusConsumer.ifPresent(c->c.accept("Performing completion phase"));
stateManager.getStates(ModLoadingPhase.COMPLETE).forEach(mls->dispatchAndHandleError(mls, syncExecutor, parallelExecutor, periodicTask));
var stateList = stateManager.getStates(ModLoadingPhase.COMPLETE);
var progress = StartupMessageManager.addProgressBar("Mod Complete", stateList.stream().mapToInt(mls -> mls.size().applyAsInt(modList)).sum());
stateList.forEach(mls->dispatchAndHandleError(mls, syncExecutor, parallelExecutor, periodicTask, progress));
statusConsumer.ifPresent(c->c.accept(String.format("Mod loading complete - %d mods loaded", this.modList.size())));
progress.complete();
}
private void dispatchAndHandleError(IModLoadingState state, ModWorkManager.DrivenExecutor syncExecutor, Executor parallelExecutor, final Runnable ticker) {
private void dispatchAndHandleError(IModLoadingState state, ModWorkManager.DrivenExecutor syncExecutor, Executor parallelExecutor, final Runnable ticker, final ProgressMeter progressBar) {
if (!isLoadingStateValid()) {
LOGGER.error("Cowardly refusing to process mod state change request from {}", state);
return;
}
statusConsumer.ifPresent(c->c.accept(state.message().apply(this.modList)));
state.inlineRunnable().ifPresent(a->a.accept(this.modList));
state.buildTransition(syncExecutor, parallelExecutor).ifPresent(t->waitForTransition(state, syncExecutor, ticker, t));
progressBar.label(progressBar.name()+ " working");
syncExecutor.drive(ticker);
state.inlineRunnable().ifPresent(a->this.handleInlineTransition(a, state, syncExecutor, ticker));
state.buildTransition(syncExecutor, parallelExecutor, progressBar).ifPresent(t->waitForTransition(state, syncExecutor, ticker, t));
completedStates.add(state);
}
private void dispatchAndHandleError(IModLoadingState state, ModWorkManager.DrivenExecutor syncExecutor, Executor parallelExecutor, final Runnable ticker, Function<Executor, CompletableFuture<Void>> preSyncTask, Function<Executor, CompletableFuture<Void>> postSyncTask) {
if (!isLoadingStateValid()) {
LOGGER.error("Cowardly refusing to process mod state change request from {}", state);
return;
}
statusConsumer.ifPresent(c->c.accept(state.message().apply(this.modList)));
state.inlineRunnable().ifPresent(a->a.accept(this.modList));
state.buildTransition(syncExecutor, parallelExecutor, preSyncTask, postSyncTask).ifPresent(t->waitForTransition(state, syncExecutor, ticker, t));
private void handleInlineTransition(final Consumer<ModList> transition, final IModLoadingState state, final ModWorkManager.DrivenExecutor syncExecutor, final Runnable ticker) {
var pb = StartupMessageManager.addProgressBar("State transition " +state.name()+" running", 0);
syncExecutor.drive(ticker);
transition.accept(this.modList);
syncExecutor.drive(ticker);
pb.complete();
syncExecutor.drive(ticker);
}
private void waitForTransition(final IModLoadingState state, final ModWorkManager.DrivenExecutor syncExecutor, final Runnable ticker, final CompletableFuture<List<Throwable>> transition) {
private void waitForTransition(final IModLoadingState state, final ModWorkManager.DrivenExecutor syncExecutor, final Runnable ticker, final CompletableFuture<Void> transition) {
while (!transition.isDone()) {
syncExecutor.drive(ticker);
}
@@ -224,6 +233,7 @@ public class ModLoader
.collect(Collectors.toList());
if (!notModLoading.isEmpty()) {
LOGGER.fatal("Encountered non-modloading exceptions!", e);
statusConsumer.ifPresent(c->c.accept("ERROR DURING MOD LOADING"));
throw e;
}
@@ -232,11 +242,12 @@ public class ModLoader
.map(ModLoadingException.class::cast)
.collect(Collectors.toList());
LOGGER.fatal(LOADING,"Failed to complete lifecycle event {}, {} errors found", state.name(), modLoadingExceptions.size());
statusConsumer.ifPresent(c->c.accept("ERROR DURING MOD LOADING"));
throw new LoadingFailedException(modLoadingExceptions);
}
}
private List<ModContainer> buildMods(final ModFile modFile)
private List<ModContainer> buildMods(final IModFile modFile)
{
final Map<String, IModInfo> modInfoMap = modFile.getModFileInfo().getMods().stream().collect(Collectors.toMap(IModInfo::getModId, Function.identity()));
@@ -248,17 +259,29 @@ public class ModLoader
.filter(Objects::nonNull)
.collect(Collectors.toList());
if (containers.size() != modInfoMap.size()) {
var modIds = modInfoMap.values().stream().map(IModInfo::getModId).sorted().collect(Collectors.toList());
var containerIds = containers.stream().map(c -> c != null ? c.getModId() : "(null)").sorted().collect(Collectors.toList());
LOGGER.fatal(LOADING,"File {} constructed {} mods: {}, but had {} mods specified: {}",
modFile.getFilePath(),
containers.size(), containers.stream().map(c -> c != null ? c.getModId() : "(null)").sorted().collect(Collectors.toList()),
modInfoMap.size(), modInfoMap.values().stream().map(IModInfo::getModId).sorted().collect(Collectors.toList()));
containers.size(), containerIds,
modInfoMap.size(), modIds);
var missingClasses = new ArrayList<>(modIds);
missingClasses.removeAll(containerIds);
LOGGER.fatal(LOADING, "The following classes are missing, but are reported in the mods.toml: {}", missingClasses);
var missingMods = new ArrayList<>(containerIds);
missingMods.removeAll(modIds);
LOGGER.fatal(LOADING, "The following mods are missing, but have classes in the jar: {}", missingMods);
loadingExceptions.add(new ModLoadingException(null, ModLoadingStage.CONSTRUCT, "fml.modloading.missingclasses", null, modFile.getFilePath()));
}
// remove errored mod containers
return containers.stream().filter(mc -> mc.modLoadingStage != ModLoadingStage.ERROR).collect(Collectors.toList());
}
private ModContainer buildModContainerFromTOML(final ModFile modFile, final Map<String, IModInfo> modInfoMap, final Map.Entry<String, ? extends IModLanguageProvider.IModLanguageLoader> idToProviderEntry) {
private ModContainer buildModContainerFromTOML(final IModFile modFile, final Map<String, IModInfo> modInfoMap, final Map.Entry<String, ? extends IModLanguageProvider.IModLanguageLoader> idToProviderEntry) {
try {
final String modId = idToProviderEntry.getKey();
final IModLanguageProvider.IModLanguageLoader languageLoader = idToProviderEntry.getValue();
@@ -282,12 +305,17 @@ public class ModLoader
return get().loadingStateValid;
}
public boolean hasCompletedState(final String stateName) {
IModLoadingState state = stateManager.findState(stateName);
return completedStates.contains(state);
}
public <T extends Event & IModBusEvent> void runEventGenerator(Function<ModContainer, T> generator) {
if (!loadingStateValid) {
LOGGER.error("Cowardly refusing to send event generator to a broken mod state");
return;
}
ModList.get().forEachModContainer((id, mc) -> mc.acceptEvent(generator.apply(mc)));
ModList.get().forEachModInOrder(mc -> mc.acceptEvent(generator.apply(mc)));
}
public <T extends Event & IModBusEvent> void postEvent(T e) {
@@ -295,7 +323,29 @@ public class ModLoader
LOGGER.error("Cowardly refusing to send event {} to a broken mod state", e.getClass().getName());
return;
}
ModList.get().forEachModContainer((id, mc) -> mc.acceptEvent(e));
ModList.get().forEachModInOrder(mc -> mc.acceptEvent(e));
}
public <T extends Event & IModBusEvent> T postEventWithReturn(T e) {
if (!loadingStateValid) {
LOGGER.error("Cowardly refusing to send event {} to a broken mod state", e.getClass().getName());
return e;
}
ModList.get().forEachModInOrder(mc -> mc.acceptEvent(e));
return e;
}
public <T extends Event & IModBusEvent> void postEventWrapContainerInModOrder(T event) {
postEventWithWrapInModOrder(event, (mc, e) -> ModLoadingContext.get().setActiveContainer(mc), (mc, e) -> ModLoadingContext.get().setActiveContainer(null));
}
public <T extends Event & IModBusEvent> void postEventWithWrapInModOrder(T e, BiConsumer<ModContainer, T> pre, BiConsumer<ModContainer, T> post) {
if (!loadingStateValid) {
LOGGER.error("Cowardly refusing to send event {} to a broken mod state", e.getClass().getName());
return;
}
ModList.get().forEachModInOrder(mc -> {
pre.accept(mc, e);
mc.acceptEvent(e);
post.accept(mc, e);
});
}
public List<ModLoadingWarning> getWarnings()
@@ -1,31 +1,22 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2021.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml;
import com.mojang.logging.LogUtils;
import net.minecraftforge.fml.config.IConfigSpec;
import net.minecraftforge.fml.config.ModConfig;
import net.minecraftforge.fml.loading.FMLLoader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.function.Supplier;
public class ModLoadingContext
{
private static final Logger LOGGER = LogUtils.getLogger();
private static final ThreadLocal<ModLoadingContext> context = ThreadLocal.withInitial(ModLoadingContext::new);
private Object languageExtension;
private ModLoadingStage stage;
@@ -60,10 +51,24 @@ public class ModLoadingContext
}
public void registerConfig(ModConfig.Type type, IConfigSpec<?> spec) {
if (spec.isEmpty())
{
// This handles the case where a mod tries to register a config, without any options configured inside it.
LOGGER.debug("Attempted to register an empty config for type {} on mod {}", type, getActiveContainer().getModId());
return;
}
getActiveContainer().addConfig(new ModConfig(type, spec, getActiveContainer()));
}
public void registerConfig(ModConfig.Type type, IConfigSpec<?> spec, String fileName) {
if (spec.isEmpty())
{
// This handles the case where a mod tries to register a config, without any options configured inside it.
LOGGER.debug("Attempted to register an empty config for type {} on mod {} using file name {}", type, getActiveContainer().getModId(), fileName);
return;
}
getActiveContainer().addConfig(new ModConfig(type, spec, getActiveContainer(), fileName));
}
@@ -1,20 +1,6 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2021.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml;
@@ -1,9 +1,38 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml;
/**
* Phases of mod loading, for grouping mod loading states.
*/
public enum ModLoadingPhase {
/**
* Special phase for exceptional situations and error handling, where mod loading cannot continue normally.
*
* <p>There is conventionally only one state with this phase.</p>
*/
ERROR, // Special state for error handling
/**
* Phase for discovering and gathering mods for loading.
*/
GATHER,
/**
* Phase for the loading of mods found from mod discovery.
*
* @see #GATHER
*/
LOAD,
/**
* Phase after mod loading has completed, for post-loading tasks.
*
* @see #LOAD
*/
COMPLETE,
DONE;
/**
* Marker phase for the last state in the full mod loading process.
*/
DONE
}
@@ -1,34 +1,57 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2021.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml;
/**
* Mod loading stage of mod containers during the mod loading process. These will have a corresponding {@link ModLoadingState}
* in the basic mod loading process provided by FML.
*
* <p>Each mod loading stage has a global {@link DeferredWorkQueue}, which is populated during the execution of the state
* associated with this stage and emptied at the end of the state's execution.</p>
*/
public enum ModLoadingStage
{
/**
* Special stage for exceptional situations and error handling.
*/
ERROR,
/**
* Validation of the mod list.
* TODO: figure out where this is used and why this exists instead of CONSTRUCT being the first normal stage
*/
VALIDATE,
/**
* Default stage of mod containers after construction.
*/
CONSTRUCT,
/**
* Common (non-side-specific) setup and initialization.
*/
COMMON_SETUP,
/**
* Side-specific setup and initialization.
*
* @see net.minecraftforge.api.distmarker.Dist
*/
SIDED_SETUP,
/**
* Stage for enqueuing {@link net.minecraftforge.fml.InterModComms} messages for later processing.
*/
ENQUEUE_IMC,
/**
* Stage for processing received messages though {@link net.minecraftforge.fml.InterModComms}.
*/
PROCESS_IMC,
/**
* Marks the completion of mod loading for this container.
*/
COMPLETE,
/**
* Marks the completion of the full mod loading process.
*/
DONE;
private final DeferredWorkQueue deferredWorkQueue;
@@ -37,14 +60,27 @@ public enum ModLoadingStage
deferredWorkQueue = new DeferredWorkQueue(this);
}
/**
* {@return the next stage after this stage, or {@link #ERROR} if the exception is not {@code null}}
*
* @param exception the exception that occurred during this stage, may be {@code null}
*/
ModLoadingStage nextState(Throwable exception) {
return exception != null ? ERROR : values()[this.ordinal()+1];
}
/**
* {@return this stage, or {@link #ERROR} if the exception is not {@code null}}
*
* @param exception the exception that occurred during this stage, may be {@code null}
*/
public ModLoadingStage currentState(Throwable exception) {
return exception != null ? ERROR : this;
}
/**
* {@return the deferred work queue for this stage}
*/
public DeferredWorkQueue getDeferredWorkQueue() {
return deferredWorkQueue;
}
@@ -1,33 +1,102 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.fml.loading.progress.ProgressMeter;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.ToIntFunction;
public record ModLoadingState(String name, String previous, Function<ModList, String> message, ModLoadingPhase phase, Optional<Consumer<ModList>> inlineRunnable, Optional<IModStateTransition> transition) implements IModLoadingState {
/**
* Implementation of the {@link IModLoadingState} interface.
*
* @param name the name of this state
* @param previous the name of the state immediately previous to this state
* @param message a function returning a human-friendly message for this state
* @param phase the mod loading phase this state belongs to
* @param inlineRunnable an optional runnable, which runs before starting the transition from this state to the next
* @param transition optional state transition information
*/
public record ModLoadingState(String name, String previous,
Function<ModList, String> message,
ToIntFunction<ModList> size,
ModLoadingPhase phase,
Optional<Consumer<ModList>> inlineRunnable,
Optional<IModStateTransition> transition) implements IModLoadingState {
@Override
public <T extends Event & IModBusEvent> Optional<CompletableFuture<List<Throwable>>> buildTransition(final Executor syncExecutor, final Executor parallelExecutor, Function<Executor, CompletableFuture<Void>> preSyncTask, Function<Executor, CompletableFuture<Void>> postSyncTask) {
return transition.map(t->t.build(syncExecutor, parallelExecutor, preSyncTask, postSyncTask));
public <T extends Event & IModBusEvent>
Optional<CompletableFuture<Void>> buildTransition(final Executor syncExecutor,
final Executor parallelExecutor,
final ProgressMeter progressBar,
final Function<Executor, CompletableFuture<Void>> preSyncTask,
final Function<Executor, CompletableFuture<Void>> postSyncTask) {
return transition.map(t -> t.build(name, syncExecutor, parallelExecutor, progressBar, preSyncTask, postSyncTask));
}
/**
* {@return an empty mod loading state} The mod loading state has a blank human-readable message, no inline runnable,
* and no state transition information.
*
* @param name the name of the state
* @param previous the name of the immediately previous state to this state
* @param phase the mod loading phase the state belongs to
*/
public static ModLoadingState empty(final String name, final String previous, final ModLoadingPhase phase) {
return new ModLoadingState(name, previous, ml->"", phase, Optional.empty(), Optional.empty());
return new ModLoadingState(name, previous, ml -> "", f->0, phase, Optional.empty(), Optional.empty());
}
public static ModLoadingState withTransition(final String name, final String previous, final ModLoadingPhase phase, IModStateTransition transition) {
return new ModLoadingState(name, previous, ml->"Processing transition "+name, phase, Optional.empty(), Optional.of(transition));
/**
* Returns a mod loading state with state transition information and a default human-friendly message of
* {@code Processing transition [name]}.
*
* @param name the name of the state
* @param previous the name of the immediately previous state to this state
* @param phase the mod loading phase the state belongs to
* @param transition the state transition information
* @return a mod loading state with state transition information and a default message
*/
public static ModLoadingState withTransition(final String name, final String previous, final ModLoadingPhase phase,
final IModStateTransition transition) {
return new ModLoadingState(name, previous, ml -> "Processing transition " + name, ModList::size, phase, Optional.empty(), Optional.of(transition));
}
public static ModLoadingState withTransition(final String name, final String previous, final Function<ModList, String> message, final ModLoadingPhase phase, IModStateTransition transition) {
return new ModLoadingState(name, previous, message, phase, Optional.empty(), Optional.of(transition));
/**
* Returns a mod loading state with state transition information and a custom human-friendly message function.
*
* @param name the name of the state
* @param previous the name of the immediately previous state to this state
* @param message a function returning a human-friendly message for this state
* @param phase the mod loading phase the state belongs to
* @param transition the state transition information
* @return a mod loading state with state transition information and a custom message
*/
public static ModLoadingState withTransition(final String name, final String previous,
final Function<ModList, String> message, final ModLoadingPhase phase,
final IModStateTransition transition) {
return new ModLoadingState(name, previous, message, ModList::size, phase, Optional.empty(), Optional.of(transition));
}
public static ModLoadingState withInline(final String name, final String previous, final ModLoadingPhase phase, Consumer<ModList> inline) {
return new ModLoadingState(name, previous, ml->"Processing work "+name, phase, Optional.of(inline), Optional.empty());
/**
* Returns a mod loading state with an inline runnable and a default human-friendly message of {@code Processing
* work [name]}.
*
* @param name the name of the state
* @param previous the name of the immediately previous state to this state
* @param phase the mod loading phase the state belongs to
* @param inline an optional runnable, which runs before starting the transition from this state to the next
* @return a mod loading state with an inline runnable and default message
*/
public static ModLoadingState withInline(final String name, final String previous, final ModLoadingPhase phase,
final Consumer<ModList> inline) {
return new ModLoadingState(name, previous, ml -> "Processing work " + name, ml->0, phase, Optional.of(inline), Optional.empty());
}
}
@@ -1,20 +1,6 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2021.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml;
@@ -1,3 +1,8 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml;
import com.google.common.graph.GraphBuilder;
@@ -34,4 +39,13 @@ public class ModStateManager {
nodes.forEach(n->graph.putEdge(lookup.getOrDefault(n.previous(), dummy), n));
return TopologicalSort.topologicalSort(graph, Comparator.comparingInt(nodes::indexOf)).stream().filter(st->st!=dummy).toList();
}
public IModLoadingState findState(final String stateName) {
return stateMap.values()
.stream()
.flatMap(Collection::stream)
.filter(mls -> mls.name().equals(stateName))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("Unknown IModLoadingState: " + stateName));
}
}
@@ -1,20 +1,6 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2021.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml;
@@ -37,8 +23,9 @@ public class ModWorkManager {
default void drive(Runnable ticker) {
if (!selfDriven()) {
while (driveOne()) {
ticker.run();
ticker.run();
while (true) {
if (!driveOne()) break;
}
} else {
// park for a bit so other threads can schedule
@@ -107,7 +94,7 @@ public class ModWorkManager {
private static ForkJoinPool parallelThreadPool;
public static Executor parallelExecutor() {
if (parallelThreadPool == null) {
final int loadingThreadCount = FMLConfig.loadingThreadCount();
final int loadingThreadCount = FMLConfig.getIntConfigValue(FMLConfig.ConfigValue.MAX_THREADS);
LOGGER.debug(LOADING, "Using {} threads for parallel mod-loading", loadingThreadCount);
parallelThreadPool = new ForkJoinPool(loadingThreadCount, ModWorkManager::newForkJoinWorkerThread, null, false);
}
@@ -1,20 +1,6 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2021.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml;
@@ -1,37 +1,34 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2021.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml;
import net.minecraftforge.fml.loading.progress.ProgressMeter;
import net.minecraftforge.fml.loading.progress.StartupNotificationManager;
import java.util.Optional;
import java.util.function.Consumer;
public class StartupMessageManager {
public static void addModMessage(final String message) {
net.minecraftforge.fml.loading.progress.StartupMessageManager.addModMessage(message);
StartupNotificationManager.addModMessage(message);
}
public static Optional<Consumer<String>> modLoaderConsumer() {
return net.minecraftforge.fml.loading.progress.StartupMessageManager.modLoaderConsumer();
return StartupNotificationManager.modLoaderConsumer();
}
public static Optional<Consumer<String>> mcLoaderConsumer() {
return net.minecraftforge.fml.loading.progress.StartupMessageManager.mcLoaderConsumer();
return StartupNotificationManager.mcLoaderConsumer();
}
public static ProgressMeter addProgressBar(final String barName, final int count) {
return StartupNotificationManager.addProgressBar(barName, count);
}
public static ProgressMeter prependProgressBar(final String barName, final int count) {
return StartupNotificationManager.prependProgressBar(barName, count);
}
}
@@ -1,3 +1,8 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml;
import java.util.concurrent.Executor;
@@ -1,20 +1,6 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2021.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml;
@@ -118,7 +104,7 @@ public class VersionChecker
@Override
public void run()
{
if (!FMLConfig.runVersionCheck())
if (!FMLConfig.getBoolConfigValue(FMLConfig.ConfigValue.VERSION_CHECK))
{
LOGGER.info("Global Forge version check system disabled, no further processing.");
return;
@@ -131,14 +117,22 @@ public class VersionChecker
/**
* Returns the response body as a String for the given URL while following redirects
*/
private String openUrlString(URL url) throws IOException, URISyntaxException, InterruptedException {
private String openUrlString(URL url, IModInfo mod) throws IOException, URISyntaxException, InterruptedException {
URL currentUrl = url;
final StringBuilder sb = new StringBuilder();
sb.append("Java-http-client/").append(System.getProperty("java.version")).append(' ');
sb.append("MinecraftForge/").append(FMLLoader.versionInfo().mcAndForgeVersion()).append(' ');
sb.append(mod.getModId()).append('/').append(mod.getVersion());
final String userAgent = sb.toString();
for (int redirects = 0; redirects < MAX_HTTP_REDIRECTS; redirects++)
{
var request = HttpRequest.newBuilder()
.uri(currentUrl.toURI())
.timeout(Duration.ofSeconds(HTTP_TIMEOUT_SECS))
.setHeader("Accept-Encoding", "gzip")
.setHeader("User-Agent", userAgent)
.GET()
.build();
@@ -180,7 +174,7 @@ public class VersionChecker
URL url = mod.getUpdateURL().get();
LOGGER.info("[{}] Starting version check at {}", mod.getModId(), url.toString());
String data = openUrlString(url);
String data = openUrlString(url, mod);
LOGGER.debug("[{}] Received version check data:\n{}", mod.getModId(), data);
@@ -1,20 +1,6 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2021.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.config;
@@ -24,11 +10,11 @@ import com.electronwill.nightconfig.core.file.CommentedFileConfig;
import com.electronwill.nightconfig.core.file.FileWatcher;
import com.electronwill.nightconfig.core.io.ParsingException;
import com.electronwill.nightconfig.core.io.WritingMode;
import com.mojang.logging.LogUtils;
import net.minecraftforge.fml.loading.FMLConfig;
import net.minecraftforge.fml.loading.FMLPaths;
import org.apache.commons.io.FilenameUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.slf4j.Logger;
import java.io.IOException;
import java.nio.file.Files;
@@ -38,9 +24,9 @@ import java.util.function.Function;
import static net.minecraftforge.fml.config.ConfigTracker.CONFIG;
public class ConfigFileTypeHandler {
private static final Logger LOGGER = LogManager.getLogger();
private static final Logger LOGGER = LogUtils.getLogger();
static ConfigFileTypeHandler TOML = new ConfigFileTypeHandler();
private static final Path defaultConfigPath = FMLPaths.GAMEDIR.get().resolve(FMLConfig.defaultConfigPath());
private static final Path defaultConfigPath = FMLPaths.GAMEDIR.get().resolve(FMLConfig.getConfigValue(FMLConfig.ConfigValue.DEFAULT_CONFIG_PATH));
public Function<ModConfig, CommentedFileConfig> reader(Path configBasePath) {
return (c) -> {
@@ -1,38 +1,24 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2021.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.config;
import com.electronwill.nightconfig.core.CommentedConfig;
import com.electronwill.nightconfig.core.file.CommentedFileConfig;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.MarkerManager;
import com.mojang.logging.LogUtils;
import org.slf4j.Logger;
import org.slf4j.Marker;
import org.slf4j.MarkerFactory;
import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public class ConfigTracker {
private static final Logger LOGGER = LogManager.getLogger();
static final Marker CONFIG = MarkerManager.getMarker("CONFIG");
private static final Logger LOGGER = LogUtils.getLogger();
static final Marker CONFIG = MarkerFactory.getMarker("CONFIG");
public static final ConfigTracker INSTANCE = new ConfigTracker();
private final ConcurrentHashMap<String, ModConfig> fileMap;
private final EnumMap<ModConfig.Type, Set<ModConfig>> configSets;
@@ -80,8 +66,12 @@ public class ConfigTracker {
private void closeConfig(final ModConfig config, final Path configBasePath) {
if (config.getConfigData() != null) {
LOGGER.trace(CONFIG, "Closing config file type {} at {} for {}", config.getType(), config.getFileName(), config.getModId());
config.save();
// stop the filewatcher before we save the file and close it, so reload doesn't fire
config.getHandler().unload(configBasePath, config);
var unloading = IConfigEvent.unloading(config);
if (unloading != null)
config.fireEvent(unloading);
config.save();
config.setConfigData(null);
}
}
@@ -1,3 +1,8 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.config;
import net.minecraftforge.eventbus.api.Event;
@@ -5,8 +10,11 @@ import net.minecraftforge.fml.Bindings;
import java.util.function.Function;
import org.jetbrains.annotations.Nullable;
public interface IConfigEvent {
record ConfigConfig(Function<ModConfig, IConfigEvent> loading, Function<ModConfig, IConfigEvent> reloading) {}
record ConfigConfig(Function<ModConfig, IConfigEvent> loading, Function<ModConfig, IConfigEvent> reloading, @Nullable Function<ModConfig, IConfigEvent> unloading) {}
ConfigConfig CONFIGCONFIG = Bindings.getConfigConfiguration().get();
static IConfigEvent reloading(ModConfig modConfig) {
@@ -15,6 +23,9 @@ public interface IConfigEvent {
static IConfigEvent loading(ModConfig modConfig) {
return CONFIGCONFIG.loading().apply(modConfig);
}
@Nullable static IConfigEvent unloading(ModConfig modConfig) {
return CONFIGCONFIG.unloading() == null ? null : CONFIGCONFIG.unloading().apply(modConfig);
}
ModConfig getConfig();
@SuppressWarnings("unchecked")
@@ -1,3 +1,8 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.config;
import com.electronwill.nightconfig.core.CommentedConfig;
@@ -1,20 +1,6 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2022.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.config;
@@ -1,20 +1,6 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2021.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.event;
@@ -1,20 +1,6 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2021.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.util;
@@ -1,20 +1,6 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2021.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.util;
@@ -1,20 +1,6 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2021.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.util;
@@ -1,20 +1,6 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2021.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.util;
@@ -1,20 +1,6 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2021.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.util;
@@ -1,20 +1,6 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2021.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.util.thread;
@@ -1,20 +1,6 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2021.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.util.thread;
@@ -1,20 +1,6 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2021.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.util.thread;
+93
View File
@@ -0,0 +1,93 @@
plugins {
id 'com.github.ben-manes.versions'
id 'org.javamodularity.moduleplugin' version '1.8.7' apply false
id 'org.cadixdev.licenser'
}
apply plugin: 'java-library'
apply plugin: 'jacoco'
apply plugin: 'org.javamodularity.moduleplugin'
import org.gradle.internal.os.OperatingSystem
switch (OperatingSystem.current()) {
case OperatingSystem.LINUX:
project.ext.lwjglNatives = "natives-linux"
break
case OperatingSystem.MAC_OS:
project.ext.lwjglNatives = "natives-macos"
break
case OperatingSystem.WINDOWS:
project.ext.lwjglNatives = "natives-windows"
break
}
dependencyUpdates.rejectVersionIf { isNonStable(it.candidate.version) }
java.withSourcesJar()
dependencies {
compileOnly('org.jetbrains:annotations:23.0.0')
implementation(project(':fmlloader'))
implementation(project(':fmlcore'))
implementation('org.lwjgl:lwjgl:3.3.1')
implementation('org.lwjgl:lwjgl-glfw:3.3.1')
implementation('org.lwjgl:lwjgl-opengl:3.3.1')
implementation('org.lwjgl:lwjgl-stb:3.3.1')
implementation('org.lwjgl:lwjgl-tinyfd:3.3.1')
implementation('org.slf4j:slf4j-api:1.8.0-beta4')
implementation("net.sf.jopt-simple:jopt-simple:${JOPT_SIMPLE_VERSION}")
testImplementation('org.junit.jupiter:junit-jupiter-api:5.8.2')
testImplementation('org.powermock:powermock-core:2.0.9')
testRuntimeOnly('org.junit.jupiter:junit-jupiter-engine:5.8.2')
testRuntimeOnly('org.slf4j:slf4j-jdk14:1.8.0-beta4')
testRuntimeOnly("org.lwjgl:lwjgl::$lwjglNatives")
testRuntimeOnly("org.lwjgl:lwjgl-glfw::$lwjglNatives")
testRuntimeOnly("org.lwjgl:lwjgl-opengl::$lwjglNatives")
testRuntimeOnly("org.lwjgl:lwjgl-stb::$lwjglNatives")
}
test {
useJUnitPlatform()
}
ext {
MANIFESTS = [
'': [
'Timestamp': new Date().format("yyyy-MM-dd'T'HH:mm:ssZ"),
'Git-Commit': GIT_INFO.abbreviatedId,
'Git-Branch': GIT_INFO.branch,
'Build-Number': "${System.getenv('BUILD_NUMBER')?:0}",
] as LinkedHashMap,
'net/minecraftforge/fml/earlydisplay/': [
'Specification-Title': 'FMLEarlyDisplay',
'Specification-Vendor': 'Forge Development LLC',
'Specification-Version': '1',
'Implementation-Title': 'FML Early Display',
'Implementation-Version': '1.0',
'Implementation-Vendor': 'Forge'
] as LinkedHashMap
]
}
jar.doFirst {
MANIFESTS.each { pkg, values ->
if (pkg == '')
manifest.attributes(values)
else
manifest.attributes(values, pkg)
}
}
tasks.withType(JavaCompile) {
options.compilerArgs << '-Xlint:unchecked'
}
license {
header = rootProject.file('LICENSE-header.txt')
include 'net/minecraftforge/'
}
publishing.publications.mavenJava(MavenPublication) {
from components.java
}
@@ -0,0 +1,43 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.earlydisplay;
public enum ColourScheme {
RED(new Colour(239, 50, 61), new Colour(255, 255, 255)),
BLACK(new Colour(0, 0, 0), new Colour(255, 255, 255));
private final Colour background;
private final Colour foreground;
ColourScheme(final Colour background, final Colour foreground) {
this.background = background;
this.foreground = foreground;
}
public Colour background() {
return background;
}
public Colour foreground() {
return foreground;
}
public record Colour(int red, int green, int blue) {
public float redf() {
return ((float)red)/255f;
}
public float greenf() {
return ((float)green)/255f;
}
public float bluef() {
return ((float)blue)/255f;
}
public int packedint(int a) {
return ((a & 0xff) << 24) | ((blue & 0xff) << 16) | ((green & 0xff) << 8) | (red & 0xff);
}
}
}
@@ -0,0 +1,617 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.earlydisplay;
import joptsimple.OptionParser;
import net.minecraftforge.fml.loading.FMLConfig;
import net.minecraftforge.fml.loading.FMLLoader;
import net.minecraftforge.fml.loading.FMLPaths;
import net.minecraftforge.fml.loading.ImmediateWindowHandler;
import net.minecraftforge.fml.loading.ImmediateWindowProvider;
import net.minecraftforge.fml.loading.progress.StartupNotificationManager;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.PointerBuffer;
import org.lwjgl.glfw.GLFWImage;
import org.lwjgl.glfw.GLFWVidMode;
import org.lwjgl.stb.STBImage;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.system.MemoryUtil;
import org.lwjgl.util.tinyfd.TinyFileDialogs;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.awt.Desktop;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.StringJoiner;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.IntConsumer;
import java.util.function.IntSupplier;
import java.util.function.LongSupplier;
import java.util.function.Supplier;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import static org.lwjgl.glfw.GLFW.*;
import static org.lwjgl.opengl.GL.createCapabilities;
import static org.lwjgl.opengl.GL32C.*;
/**
* The Loading Window that is opened Immediately after Forge starts.
* It is called from the ModDirTransformerDiscoverer, the soonest method that ModLauncher calls into Forge code.
* In this way, we can be sure that this will not run before any transformer or injection.
*
* The window itself is spun off into a secondary thread, and is handed off to the main game by Forge.
*
* Because it is created so early, this thread will "absorb" the context from OpenGL.
* Therefore, it is of utmost importance that the Context is made Current for the main thread before handoff,
* otherwise OS X will crash out.
*
* Based on the prior ClientVisualization, with some personal touches.
*/
public class DisplayWindow implements ImmediateWindowProvider {
private static final int[][] GL_VERSIONS = new int[][] {{4,6}, {4,5}, {4,4}, {4,3}, {4,2}, {4,1}, {4,0}, {3,3}, {3,2}};
private static final Logger LOGGER = LoggerFactory.getLogger("EARLYDISPLAY");
private final AtomicBoolean animationTimerTrigger = new AtomicBoolean(true);
private ColourScheme colourScheme;
private ElementShader elementShader;
private RenderElement.DisplayContext context;
private List<RenderElement> elements;
private int framecount;
private EarlyFramebuffer framebuffer;
private ScheduledFuture<?> windowTick;
private PerformanceInfo performanceInfo;
private ScheduledFuture<?> performanceTick;
// The GL ID of the window. Used for all operations
private long window;
// The thread that contains and ticks the window while Forge is loading mods
private ScheduledExecutorService renderScheduler;
private int fbWidth;
private int fbHeight;
private int fbScale;
private int winWidth;
private int winHeight;
private int winX;
private int winY;
private final Semaphore renderLock = new Semaphore(1);
private boolean maximized;
private String glVersion;
private SimpleFont font;
private Runnable repaintTick = ()->{};
@Override
public String name() {
return "fmlearlywindow";
}
@Override
public Runnable initialize(String[] arguments) {
final OptionParser parser = new OptionParser();
var mcversionopt = parser.accepts("fml.mcVersion").withRequiredArg().ofType(String.class);
var forgeversionopt = parser.accepts("fml.forgeVersion").withRequiredArg().ofType(String.class);
var widthopt = parser.accepts("width")
.withRequiredArg().ofType(Integer.class)
.defaultsTo(FMLConfig.getIntConfigValue(FMLConfig.ConfigValue.EARLY_WINDOW_WIDTH));
var heightopt = parser.accepts("height")
.withRequiredArg().ofType(Integer.class)
.defaultsTo(FMLConfig.getIntConfigValue(FMLConfig.ConfigValue.EARLY_WINDOW_HEIGHT));
var maximizedopt = parser.accepts("earlywindow.maximized");
parser.allowsUnrecognizedOptions();
var parsed = parser.parse(arguments);
winWidth = parsed.valueOf(widthopt);
winHeight = parsed.valueOf(heightopt);
FMLConfig.updateConfig(FMLConfig.ConfigValue.EARLY_WINDOW_WIDTH, winWidth);
FMLConfig.updateConfig(FMLConfig.ConfigValue.EARLY_WINDOW_HEIGHT, winHeight);
fbScale = FMLConfig.getIntConfigValue(FMLConfig.ConfigValue.EARLY_WINDOW_FBSCALE);
if (System.getenv("FML_EARLY_WINDOW_DARK")!= null) {
this.colourScheme = ColourScheme.BLACK;
} else {
try {
var optionLines = Files.readAllLines(FMLPaths.GAMEDIR.get().resolve(Paths.get("options.txt")));
var options = optionLines.stream().map(l -> l.split(":")).filter(a -> a.length == 2).collect(Collectors.toMap(a -> a[0], a -> a[1]));
var colourScheme = Boolean.parseBoolean(options.getOrDefault("darkMojangStudiosBackground", "false"));
this.colourScheme = colourScheme ? ColourScheme.BLACK : ColourScheme.RED;
} catch (IOException ioe) {
// No options
this.colourScheme = ColourScheme.RED; // default to red colourscheme
}
}
this.maximized = parsed.has(maximizedopt) || FMLConfig.getBoolConfigValue(FMLConfig.ConfigValue.EARLY_WINDOW_MAXIMIZED);
var forgeVersion = parsed.valueOf(forgeversionopt);
StartupNotificationManager.modLoaderConsumer().ifPresent(c->c.accept("Forge loading "+ forgeVersion));
performanceInfo = new PerformanceInfo();
return start(parsed.valueOf(mcversionopt), forgeVersion);
}
private static final long MINFRAMETIME = TimeUnit.MILLISECONDS.toNanos(10); // This is the FPS cap on the window - note animation is capped at 20FPS via the tickTimer
private long nextFrameTime = 0;
/**
* The main render loop.
* renderThread executes this.
*
* Performs initialization and then ticks the screen at 20 fps.
* When the thread is killed, context is destroyed.
*/
private void renderThreadFunc() {
if (!renderLock.tryAcquire()) {
return;
}
try {
long nt;
if ((nt = System.nanoTime()) < nextFrameTime) {
return;
}
nextFrameTime = nt + MINFRAMETIME;
glfwMakeContextCurrent(window);
framebuffer.activate();
glViewport(0, 0, this.context.scaledWidth(), this.context.scaledHeight());
this.context.elementShader().activate();
this.context.elementShader().updateScreenSizeUniform(this.context.scaledWidth(), this.context.scaledHeight());
glClearColor(colourScheme.background().redf(), colourScheme.background().greenf(), colourScheme.background().bluef(), 1f);
paintFramebuffer();
this.context.elementShader().clear();
framebuffer.deactivate();
glViewport(0, 0, fbWidth, fbHeight);
framebuffer.draw(this.fbWidth, this.fbHeight);
// Swap buffers; we're done
glfwSwapBuffers(window);
} catch (Throwable t) {
LOGGER.error("BARF", t);
} finally {
if (this.windowTick != null) glfwMakeContextCurrent(0); // we release the gl context IF we're running off the main thread
renderLock.release();
}
}
/**
* Render initialization methods called by the Render Thread.
* It compiles the fragment and vertex shaders for rendering text with STB, and sets up basic render framework.
*
* Nothing fancy, we just want to draw and render text.
*/
private void initRender(final @Nullable String mcVersion, final String forgeVersion) {
// This thread owns the GL render context now. We should make a note of that.
glfwMakeContextCurrent(window);
// Wait for one frame to be complete before swapping; enable vsync in other words.
glfwSwapInterval(1);
createCapabilities();
LOGGER.info("GL info: "+ glGetString(GL_RENDERER) + " GL version " + glGetString(GL_VERSION) + ", " + glGetString(GL_VENDOR));
elementShader = new ElementShader();
try {
elementShader.init();
} catch (Throwable t) {
LOGGER.error("Crash during shader initialization", t);
crashElegantly("An error occurred initializing shaders.");
}
// Set the clear color based on the colour scheme
glClearColor(colourScheme.background().redf(), colourScheme.background().greenf(), colourScheme.background().bluef(), 1f);
// we always render to an 854x480 texture and then fit that to the screen - with a scale factor
this.context = new RenderElement.DisplayContext(854, 480, fbScale, elementShader, colourScheme, performanceInfo);
framebuffer = new EarlyFramebuffer(this.context);
try {
this.font = new SimpleFont("Monocraft.ttf", fbScale, 200000, 1 + RenderElement.INDEX_TEXTURE_OFFSET);
} catch (Throwable t) {
LOGGER.error("Crash during font initialization", t);
crashElegantly("An error occurred initializing a font for rendering. "+t.getMessage());
}
this.elements = new ArrayList<>(Arrays.asList(
RenderElement.anvil(font),
RenderElement.logMessageOverlay(font),
RenderElement.forgeVersionOverlay(font, mcVersion+"-"+forgeVersion.split("-")[0]),
RenderElement.performanceBar(font),
RenderElement.progressBars(font)
));
var date = Calendar.getInstance();
if (FMLConfig.getBoolConfigValue(FMLConfig.ConfigValue.EARLY_WINDOW_SQUIR) || (date.get(Calendar.MONTH) == Calendar.APRIL && date.get(Calendar.DAY_OF_MONTH) == 1))
this.elements.add(0, RenderElement.squir());
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glfwMakeContextCurrent(0);
this.windowTick = renderScheduler.scheduleAtFixedRate(this::renderThreadFunc, 50, 50, TimeUnit.MILLISECONDS);
this.performanceTick = renderScheduler.scheduleAtFixedRate(performanceInfo::update, 0, 500, TimeUnit.MILLISECONDS);
// schedule a 50 ms ticker to try and smooth out the rendering
renderScheduler.scheduleAtFixedRate(()-> animationTimerTrigger.set(true), 1, 50, TimeUnit.MILLISECONDS);
}
/**
* Called every frame by the Render Thread to draw to the screen.
*/
void paintFramebuffer() {
// Clear the screen to our color
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
this.elements.removeIf(element -> !element.render(context, framecount));
if (animationTimerTrigger.compareAndSet(true, false)) // we only increment the framecount on a periodic basis
framecount++;
}
public void render(int alpha) {
var currentVAO = glGetInteger(GL_VERTEX_ARRAY_BINDING);
var currentFB = glGetInteger(GL_READ_FRAMEBUFFER_BINDING);
glfwSwapInterval(0);
glViewport(0, 0, this.context.scaledWidth(), this.context.scaledHeight());
RenderElement.globalAlpha = alpha;
framebuffer.activate();
glClearColor(colourScheme.background().redf(), colourScheme.background().greenf(), colourScheme.background().bluef(), alpha / 255f);
elementShader.activate();
elementShader.updateScreenSizeUniform(this.context.scaledWidth(), this.context.scaledHeight());
paintFramebuffer();
elementShader.clear();
framebuffer.deactivate();
glBindVertexArray(currentVAO);
glBindFramebuffer(GL_FRAMEBUFFER, currentFB);
}
/**
* Start the window and Render Thread; we're ready to go.
*/
public Runnable start(@Nullable String mcVersion, final String forgeVersion) {
renderScheduler = Executors.newSingleThreadScheduledExecutor(r -> {
final var thread = Executors.defaultThreadFactory().newThread(r);
thread.setDaemon(true);
return thread;
});
initWindow(mcVersion);
renderScheduler.schedule(() -> initRender(mcVersion, forgeVersion), 1, TimeUnit.MILLISECONDS);
return this::periodicTick;
}
private static final String ERROR_URL = "https://links.minecraftforge.net/early-display-errors";
@Override
public String getGLVersion() {
return this.glVersion;
}
private void crashElegantly(String errorDetails) {
String qrText;
try (var is = new BufferedReader(new InputStreamReader(getClass().getResourceAsStream("/glfailure.txt")))) {
qrText = is.lines().collect(Collectors.joining("\n"));
} catch (IOException ioe) {
qrText = "";
}
StringBuilder msgBuilder = new StringBuilder(2000);
msgBuilder.append("Failed to initialize graphics window with current settings.\n");
msgBuilder.append("\n\n");
msgBuilder.append("Failure details:\n");
msgBuilder.append(errorDetails);
msgBuilder.append("\n\n");
msgBuilder.append("If you click yes, we will try and open " + ERROR_URL + " in your default browser");
LOGGER.error("ERROR DISPLAY\n"+msgBuilder);
// we show the display on a new dedicated thread
Executors.newSingleThreadExecutor().submit(()-> {
var res = TinyFileDialogs.tinyfd_messageBox("Minecraft: Forge", msgBuilder.toString(), "yesno", "error", false);
if (res) {
try {
Desktop.getDesktop().browse(URI.create(ERROR_URL));
} catch (IOException ioe) {
TinyFileDialogs.tinyfd_messageBox("Minecraft: Forge", "Sadly, we couldn't open your browser.\nVisit " + ERROR_URL, "ok", "error", false);
}
}
System.exit(1);
});
}
/**
* Called to initialize the window when preparing for the Render Thread.
*
* The act of calling glfwInit here creates a concurrency issue; GL doesn't know whether we're gonna call any
* GL functions from the secondary thread and the main thread at the same time.
*
* It's then our job to make sure this doesn't happen, only calling GL functions where the Context is Current.
* As long as we can verify that, then GL (and things like OS X) have no complaints with doing this.
*
* @param mcVersion Minecraft Version
* @return The selected GL profile as an integer pair
*/
public void initWindow(@Nullable String mcVersion) {
// Initialize GLFW with a time guard, in case something goes wrong
long glfwInitBegin = System.nanoTime();
if (!glfwInit()) {
crashElegantly("We are unable to initialize the graphics system.\nglfwInit failed.\n");
throw new IllegalStateException("Unable to initialize GLFW");
}
long glfwInitEnd = System.nanoTime();
if (glfwInitEnd - glfwInitBegin > 1e9) {
LOGGER.error("WARNING : glfwInit took {} seconds to start.", (glfwInitEnd - glfwInitBegin) / 1.0e9);
}
// Clear the Last Exception (#7285 - Prevent Vanilla throwing an IllegalStateException due to invalid controller mappings)
handleLastGLFWError((error, description) -> LOGGER.error(String.format("Suppressing Last GLFW error: [0x%X]%s", error, description)));
// Set window hints for the new window we're gonna create.
glfwDefaultWindowHints();
glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API);
glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_NATIVE_CONTEXT_API);
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE);
if (mcVersion != null) {
// this emulates what we would get without early progress window
// as vanilla never sets these, so GLFW uses the first window title
// set them explicitly to avoid it using "FML early loading progress" as the class
String vanillaWindowTitle = "Minecraft* " + mcVersion;
glfwWindowHintString(GLFW_X11_CLASS_NAME, vanillaWindowTitle);
glfwWindowHintString(GLFW_X11_INSTANCE_NAME, vanillaWindowTitle);
}
long primaryMonitor = glfwGetPrimaryMonitor();
if (primaryMonitor == 0) {
LOGGER.error("Failed to find a primary monitor - this means LWJGL isn't working properly");
crashElegantly("Failed to locate a primary monitor.\nglfwGetPrimaryMonitor failed.\n");
throw new IllegalStateException("Can't find a primary monitor");
}
GLFWVidMode vidmode = glfwGetVideoMode(primaryMonitor);
if (vidmode == null) {
LOGGER.error("Failed to get the current display video mode.");
crashElegantly("Failed to get current display resolution.\nglfwGetVideoMode failed.\n");
throw new IllegalStateException("Can't get a resolution");
}
long window = 0;
var successfulWindow = new AtomicBoolean(false);
var windowFailFuture = renderScheduler.schedule(()->{
if (!successfulWindow.get()) crashElegantly("Timed out trying to setup the Game Window.");
}, 10, TimeUnit.SECONDS);
int versidx = 0;
var skipVersions = FMLConfig.<String>getListConfigValue(FMLConfig.ConfigValue.EARLY_WINDOW_SKIP_GL_VERSIONS);
final String[] lastGLError=new String[GL_VERSIONS.length];
do {
final var glVersionToTry = GL_VERSIONS[versidx][0] + "." + GL_VERSIONS[versidx][1];
if (skipVersions.contains(glVersionToTry)) {
LOGGER.info("Skipping GL version "+ glVersionToTry+" because of configuration");
versidx++;
continue;
}
LOGGER.info("Trying GL version " + glVersionToTry);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, GL_VERSIONS[versidx][0]); // we try our versions one at a time
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, GL_VERSIONS[versidx][1]);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
window = glfwCreateWindow(winWidth, winHeight, "Minecraft: Forge Loading...", 0L, 0L);
var erridx = versidx;
handleLastGLFWError((error, description) -> lastGLError[erridx] = String.format("Trying %d.%d: GLFW error: [0x%X]%s", GL_VERSIONS[erridx][0], GL_VERSIONS[erridx][1], error, description));
if (lastGLError[versidx] != null) {
LOGGER.trace(lastGLError[versidx]);
}
versidx++;
} while (window == 0 && versidx < GL_VERSIONS.length);
// LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(12));
if (versidx== GL_VERSIONS.length && window == 0) {
LOGGER.error("Failed to find any valid GLFW profile. "+lastGLError[0]);
crashElegantly("Failed to find a valid GLFW profile.\nWe tried "+
Arrays.stream(GL_VERSIONS).map(p->p[0]+"."+p[1]).filter(o -> !skipVersions.contains(o))
.collect(Collector.of(()->new StringJoiner(", ").setEmptyValue("no versions"), StringJoiner::add, StringJoiner::merge, StringJoiner::toString))+
" but none of them worked.\n"+ Arrays.stream(lastGLError).filter(Objects::nonNull).collect(Collectors.joining("\n")));
throw new IllegalStateException("Failed to create a GLFW window with any profile");
}
successfulWindow.set(true);
if (!windowFailFuture.cancel(true)) throw new IllegalStateException("We died but didn't somehow?");
var requestedVersion = GL_VERSIONS[versidx-1][0]+"."+GL_VERSIONS[versidx-1][1];
var maj = glfwGetWindowAttrib(window, GLFW_CONTEXT_VERSION_MAJOR);
var min = glfwGetWindowAttrib(window, GLFW_CONTEXT_VERSION_MINOR);
var gotVersion = maj+"."+min;
LOGGER.info("Requested GL version "+requestedVersion+" got version "+gotVersion);
this.glVersion = gotVersion;
this.window = window;
int[] x = new int[1];
int[] y = new int[1];
glfwGetMonitorPos(primaryMonitor, x, y);
int monitorX = x[0];
int monitorY = y[0];
// glfwSetWindowSizeLimits(window, 854, 480, GLFW_DONT_CARE, GLFW_DONT_CARE);
if (this.maximized) {
glfwMaximizeWindow(window);
}
glfwGetWindowSize(window, x, y);
this.winWidth = x[0];
this.winHeight = y[0];
glfwSetWindowPos(window, (vidmode.width() - this.winWidth) / 2 + monitorX, (vidmode.height() - this.winHeight) / 2 + monitorY);
// Attempt setting the icon
int[] channels = new int[1];
try (var glfwImgBuffer = GLFWImage.create(MemoryUtil.getAllocator().malloc(GLFWImage.SIZEOF), 1)) {
final ByteBuffer imgBuffer;
try (GLFWImage glfwImages = GLFWImage.malloc()) {
imgBuffer = STBHelper.loadImageFromClasspath("forge_logo.png", 20000, x, y, channels);
glfwImgBuffer.put(glfwImages.set(x[0], y[0], imgBuffer));
glfwSetWindowIcon(window, glfwImgBuffer);
STBImage.stbi_image_free(imgBuffer);
}
} catch (NullPointerException e) {
System.err.println("Failed to load forge logo");
}
handleLastGLFWError((error, description) -> LOGGER.debug(String.format("Suppressing GLFW icon error: [0x%X]%s", error, description)));
glfwSetFramebufferSizeCallback(window, this::fbResize);
glfwSetWindowPosCallback(window, this::winMove);
glfwSetWindowSizeCallback(window, this::winResize);
// Show the window
glfwShowWindow(window);
glfwGetWindowPos(window, x, y);
this.winX = x[0];
this.winY = y[0];
glfwGetFramebufferSize(window, x, y);
this.fbWidth = x[0];
this.fbHeight = y[0];
glfwPollEvents();
}
private void badWindowHandler(final int code, final long desc) {
LOGGER.error("Got error from GLFW window init: "+code+ " "+MemoryUtil.memUTF8(desc));
}
private void winResize(long window, int width, int height) {
if (window == this.window && width != 0 && height != 0) {
this.winWidth = width;
this.winHeight = height;
}
}
private void fbResize(long window, int width, int height) {
if (window == this.window && width != 0 && height != 0) {
this.fbWidth = width;
this.fbHeight = height;
}
}
private void winMove(long window, int x, int y) {
if (window == this.window) {
this.winX = x;
this.winY = y;
}
}
private void handleLastGLFWError(BiConsumer<Integer, String> handler) {
try (MemoryStack memorystack = MemoryStack.stackPush()) {
PointerBuffer pointerbuffer = memorystack.mallocPointer(1);
int error = glfwGetError(pointerbuffer);
if (error != GLFW_NO_ERROR) {
long pDescription = pointerbuffer.get();
String description = pDescription == 0L ? "" : MemoryUtil.memUTF8(pDescription);
handler.accept(error, description);
}
}
}
/**
* Hand-off the window to the vanilla game.
* Called on the main thread instead of the game's initialization.
*
* @return the Window we own.
*/
public long setupMinecraftWindow(final IntSupplier width, final IntSupplier height, final Supplier<String> title, final LongSupplier monitorSupplier) {
// we have to spin wait for the window ticker
ImmediateWindowHandler.updateProgress("Initializing Game Graphics");
while (!this.windowTick.isDone()) {
this.windowTick.cancel(false);
}
var tries = 0;
var renderlockticket = false;
do {
try {
renderlockticket = renderLock.tryAcquire(100, TimeUnit.MILLISECONDS);
if (++tries > 9) {
Thread.dumpStack();
crashElegantly("We seem to be having trouble handing off the window, tried for 1 second");
}
} catch (InterruptedException e) {
Thread.interrupted();
}
} while (!renderlockticket);
// we don't want the lock, just making sure it's back on the main thread
renderLock.release();
glfwMakeContextCurrent(window);
// Set the title to what the game wants
glfwSetWindowTitle(window, title.get());
glfwSwapInterval(0);
// Clean up our hooks
glfwSetFramebufferSizeCallback(window, null).free();
glfwSetWindowPosCallback(window, null).free();
glfwSetWindowSizeCallback(window, null).free();
this.repaintTick = this::renderThreadFunc; // the repaint will continue to be called until the overlay takes over
this.windowTick = null; // this tells the render thread that the async ticker is done
return window;
}
@Override
public boolean positionWindow(final Optional<Object> monitor, final IntConsumer widthSetter, final IntConsumer heightSetter, final IntConsumer xSetter, final IntConsumer ySetter) {
widthSetter.accept(this.winWidth);
heightSetter.accept(this.winHeight);
xSetter.accept(this.winX);
ySetter.accept(this.winY);
return true;
}
@Override
public void updateFramebufferSize(final IntConsumer width, final IntConsumer height) {
width.accept(this.fbWidth);
height.accept(this.fbHeight);
}
private Method loadingOverlay;
@SuppressWarnings("unchecked")
@Override
public <T> Supplier<T> loadingOverlay(final Supplier<?> mc, final Supplier<?> ri, final Consumer<Optional<Throwable>> ex, final boolean fade) {
try {
return (Supplier<T>)loadingOverlay.invoke(null, mc, ri, ex, this);
} catch (Throwable e) {
throw new IllegalStateException("How did you get here?", e);
}
}
@Override
public void updateModuleReads(final ModuleLayer layer) {
var fm = layer.findModule("forge").orElseThrow();
getClass().getModule().addReads(fm);
var clz = FMLLoader.getGameLayer().findModule("forge").map(l->Class.forName(l, "net.minecraftforge.client.loading.ForgeLoadingOverlay")).orElseThrow();
var methods = Arrays.stream(clz.getMethods()).filter(m-> Modifier.isStatic(m.getModifiers())).collect(Collectors.toMap(Method::getName, Function.identity()));
loadingOverlay = methods.get("newInstance");
}
public int getFramebufferTextureId() {
return framebuffer.getTexture();
}
public RenderElement.DisplayContext context() {
return this.context;
}
@Override
public void periodicTick() {
glfwPollEvents();
repaintTick.run();
}
public void addMojangTexture(final int textureId) {
this.elements.add(0, RenderElement.mojang(textureId, framecount));
// this.elements.get(0).retire(framecount + 1);
}
public void close() {
// Close the Render Scheduler thread
renderScheduler.shutdown();
this.framebuffer.close();
this.context.elementShader().close();
SimpleBufferBuilder.destroy();
}
}
@@ -0,0 +1,67 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.earlydisplay;
import java.nio.IntBuffer;
import static net.minecraftforge.fml.earlydisplay.RenderElement.clamp;
import static org.lwjgl.opengl.GL32C.*;
public class EarlyFramebuffer {
private final int framebuffer;
private final int texture;
private final RenderElement.DisplayContext context;
EarlyFramebuffer(final RenderElement.DisplayContext context) {
this.context = context;
this.framebuffer = glGenFramebuffers();
this.texture = glGenTextures();
glBindFramebuffer(GL_FRAMEBUFFER, this.framebuffer);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, this.texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, context.width() * context.scale(), context.height() * context.scale(), 0, GL_RGBA, GL_UNSIGNED_BYTE, (IntBuffer)null);
glTexParameterIi(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameterIi(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, this.texture, 0);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
void activate() {
glBindFramebuffer(GL_FRAMEBUFFER, this.framebuffer);
}
void deactivate() {
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
void draw(int windowFBWidth, int windowFBHeight) {
var wscale = ((float)windowFBWidth / this.context.width());
var hscale = ((float)windowFBHeight / this.context.height());
var scale = this.context.scale() * Math.min(wscale, hscale) / 2f;
var wleft = (int)(windowFBWidth * 0.5f - scale * this.context.width());
var wtop = (int)(windowFBHeight * 0.5f - scale * this.context.height());
var wright = (int)(windowFBWidth * 0.5f + scale * this.context.width());
var wbottom = (int)(windowFBHeight * 0.5f + scale * this.context.height());
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glBindFramebuffer(GL_READ_FRAMEBUFFER, this.framebuffer);
final var colour = this.context.colourScheme().background();
glClearColor(colour.redf(), colour.greenf(), colour.bluef(), 1f);
glClear(GL_COLOR_BUFFER_BIT);
// src Y are flipped, since our FB is flipped
glBlitFramebuffer(0, this.context.height() * this.context.scale(), this.context.width() * this.context.scale(), 0, clamp(wleft, 0, windowFBWidth), clamp(wtop, 0, windowFBHeight), clamp(wright, 0, windowFBWidth), clamp(wbottom, 0, windowFBHeight), GL_COLOR_BUFFER_BIT, GL_NEAREST);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
int getTexture() {
return this.texture;
}
public void close() {
glDeleteTextures(this.texture);
glDeleteFramebuffers(this.framebuffer);
}
}
@@ -0,0 +1,117 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.earlydisplay;
import static org.lwjgl.opengl.GL32C.*;
public class ElementShader {
private int program;
private int textureUniform;
private int screenSizeUniform;
private int renderTypeUniform;
public void init() {
int vertexShader = glCreateShader(GL_VERTEX_SHADER);
int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
// Bind the source of our shaders to the ones created above
glShaderSource(fragmentShader, """
#version 150 core
uniform sampler2D tex;
uniform int rendertype;
in vec2 fTex;
in vec4 fColour;
out vec4 fragColor;
void main() {
if (rendertype == 0)
fragColor = vec4(1,1,1,texture(tex, fTex).r) * fColour;
if (rendertype == 1)
fragColor = texture(tex, fTex) * fColour;
if (rendertype == 2)
fragColor = fColour;
}
""");
glShaderSource(vertexShader, """
#version 150 core
in vec2 position;
in vec2 tex;
in vec4 colour;
uniform vec2 screenSize;
out vec2 fTex;
out vec4 fColour;
void main() {
fTex = tex;
fColour = colour;
gl_Position = vec4((position/screenSize) * 2 - 1, 0.0, 1.0);
}
""");
// Compile the vertex and fragment elementShader so that we can use them
glCompileShader(vertexShader);
if (glGetShaderi(vertexShader, GL_COMPILE_STATUS) == GL_FALSE) {
throw new IllegalStateException("VertexShader linkage failure. \n" + glGetShaderInfoLog(vertexShader));
}
glCompileShader(fragmentShader);
if (glGetShaderi(fragmentShader, GL_COMPILE_STATUS) == GL_FALSE) {
throw new IllegalStateException("FragmentShader linkage failure. \n" + glGetShaderInfoLog(fragmentShader));
}
var program = glCreateProgram();
glBindAttribLocation(program, 0, "position");
glBindAttribLocation(program, 1, "tex");
glBindAttribLocation(program, 2, "colour");
glAttachShader(program, vertexShader);
glAttachShader(program, fragmentShader);
glLinkProgram(program);
if (glGetProgrami(program, GL_LINK_STATUS) == GL_FALSE) {
throw new RuntimeException("ShaderProgram linkage failure. \n" + glGetProgramInfoLog(program));
}
this.program = program;
glDetachShader(program, vertexShader);
glDetachShader(program, fragmentShader);
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
textureUniform = glGetUniformLocation(program, "tex");
screenSizeUniform = glGetUniformLocation(program, "screenSize");
renderTypeUniform = glGetUniformLocation(program, "rendertype");
activate();
}
public void activate() {
glUseProgram(program);
}
public void updateTextureUniform(int textureNumber) {
glUniform1i(textureUniform, textureNumber);
}
public void updateScreenSizeUniform(int width, int height) {
glUniform2f(screenSizeUniform, width, height);
}
public void updateRenderTypeUniform(RenderType type) {
glUniform1i(renderTypeUniform, type.ordinal());
}
public void clear() {
glUseProgram(0);
}
public void close() {
glDeleteProgram(program);
}
public enum RenderType {
FONT, TEXTURE, BAR;
}
public int program() {
return program;
}
}
@@ -0,0 +1,47 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.earlydisplay;
import com.sun.management.OperatingSystemMXBean;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryUsage;
public class PerformanceInfo {
private final OperatingSystemMXBean osBean;
private final MemoryMXBean memoryBean;
float memory;
private String text;
PerformanceInfo() {
osBean = ManagementFactory.getPlatformMXBean(OperatingSystemMXBean.class);
memoryBean = ManagementFactory.getMemoryMXBean();
}
void update() {
final MemoryUsage heapusage = memoryBean.getHeapMemoryUsage();
memory = (float) heapusage.getUsed() / heapusage.getMax();
var cpuLoad = osBean.getProcessCpuLoad();
String cpuText;
if (cpuLoad == -1) {
cpuText = String.format("*CPU: %.1f%%", osBean.getCpuLoad() * 100f);
} else {
cpuText = String.format("CPU: %.1f%%", cpuLoad * 100f);
}
text = String.format("Heap: %d/%d MB (%.1f%%) OffHeap: %d MB %s", heapusage.getUsed() >> 20, heapusage.getMax() >> 20, memory * 100.0, memoryBean.getNonHeapMemoryUsage().getUsed() >> 20, cpuText);
}
String text() {
return text;
}
float memory() {
return memory;
}
}
@@ -0,0 +1,22 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.earlydisplay;
public class QuadHelper {
public static void loadQuad(SimpleBufferBuilder bb, float x0, float x1, float y0, float y1, float u0, float u1, float v0, float v1, int colour) {
bb.pos(x0, y0).tex(u0, v0).colour(colour).endVertex();
bb.pos(x1, y0).tex(u1, v0).colour(colour).endVertex();
bb.pos(x0, y1).tex(u0, v1).colour(colour).endVertex();
bb.pos(x1, y1).tex(u1, v1).colour(colour).endVertex();
}
public static void loadQuad(SimpleBufferBuilder bb, float x0, float x1, float y0, float y1, float u0, float u1, float v0, float v1) {
bb.pos(x0, y0).tex(u0, v0).endVertex();
bb.pos(x1, y0).tex(u1, v0).endVertex();
bb.pos(x0, y1).tex(u0, v1).endVertex();
bb.pos(x1, y1).tex(u1, v1).endVertex();
}
}
@@ -0,0 +1,344 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.earlydisplay;
import net.minecraftforge.fml.loading.progress.Message;
import net.minecraftforge.fml.loading.progress.ProgressMeter;
import net.minecraftforge.fml.loading.progress.StartupNotificationManager;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
import static org.lwjgl.opengl.GL32C.*;
public class RenderElement {
static final int INDEX_TEXTURE_OFFSET = 5;
private final SimpleBufferBuilder bb;
private final Renderer renderer;
static int globalAlpha = 255;
private int retireCount;
interface Renderer {
void accept(SimpleBufferBuilder bb, DisplayContext context, int frame);
default Renderer then(Renderer r) {
if (r == null) return this;
return (bb, ctx, frame) -> {
r.accept(bb, ctx, frame);
this.accept(bb, ctx, frame);
};
}
}
interface TextureRenderer {
void accept(SimpleBufferBuilder bb, DisplayContext context, int[] size, int frame);
}
interface Initializer extends Supplier<Renderer> {}
interface TextGenerator {
void accept(SimpleBufferBuilder bb, SimpleFont fh, DisplayContext ctx);
}
public record DisplayContext(int width, int height, int scale, ElementShader elementShader, ColourScheme colourScheme, PerformanceInfo performance) {
public int scaledWidth() {
return scale() * width();
}
public int scaledHeight() {
return scale() * height();
}
}
public RenderElement(final Initializer rendererInitializer) {
this.bb = new SimpleBufferBuilder(1);
this.renderer = rendererInitializer.get();
}
public boolean render(DisplayContext ctx, int count) {
this.renderer.accept(bb, ctx, count);
return this.retireCount == 0 || this.retireCount < count;
}
public void retire(final int frame) {
this.retireCount = frame;
}
private static void startupLogMessages(SimpleBufferBuilder bb, SimpleFont font, DisplayContext context) {
List<StartupNotificationManager.AgeMessage> messages = StartupNotificationManager.getMessages();
List<SimpleFont.DisplayText> texts = new ArrayList<>();
for (int i = messages.size() - 1; i >= 0; i--) {
final StartupNotificationManager.AgeMessage pair = messages.get(i);
final float fade = clamp((4000.0f - (float) pair.age() - ( i - 4 ) * 1000.0f) / 5000.0f, 0.0f, 1.0f);
if (fade <0.01f) continue;
Message msg = pair.message();
int colour = Math.min((int)(fade * 255f), globalAlpha) << 24 | 0xFFFFFF;
texts.add(new SimpleFont.DisplayText(msg.getText()+"\n", colour));
}
font.generateVerticesForTexts(10, context.scaledHeight() - texts.size() * font.lineSpacing() + font.descent() - 10, bb, texts.toArray(SimpleFont.DisplayText[]::new));
}
public static RenderElement monag() {
return new RenderElement(RenderElement.initializeTexture("monagstudios.png", 45000, 4, (bb, ctx, sz, frame) -> {
var size = 256;
var x0 = (ctx.width() - 2 * size) / 2;
var y0 = 64;
QuadHelper.loadQuad(bb, x0, x0+size, y0, y0+size/2f, 0f, 1f, 0f, 0.5f, 0xFFFFFFFF);
QuadHelper.loadQuad(bb, x0+size, x0+2*size, y0, y0+size/2f, 0f, 1f, 0.5f, 1f, 0xFFFFFFFF);
}));
}
public static RenderElement mojang(final int textureId, final int frameStart) {
return new RenderElement(()->(bb, ctx, frame) -> {
var size = 256 * ctx.scale();
var x0 = (ctx.scaledWidth() - 2 * size) / 2;
var y0 = 64 * ctx.scale() + 32;
ctx.elementShader().updateTextureUniform(0);
ctx.elementShader().updateRenderTypeUniform(ElementShader.RenderType.TEXTURE);
var fade = Math.min((frame - frameStart) * 10, 255);
glBindTexture(GL_TEXTURE_2D, textureId);
bb.begin(SimpleBufferBuilder.Format.POS_TEX_COLOR, SimpleBufferBuilder.Mode.QUADS);
QuadHelper.loadQuad(bb, x0, x0+size, y0, y0+size/2f, 0f, 1f, 0f, 0.5f, (fade << 24) | 0xFFFFFF);
QuadHelper.loadQuad(bb, x0+size, x0+2*size, y0, y0+size/2f, 0f, 1f, 0.5f, 1f, (fade << 24) | 0xFFFFFF);
bb.draw();
glBindTexture(GL_TEXTURE_2D, 0);
});
}
public static RenderElement logMessageOverlay(SimpleFont font) {
return new RenderElement(RenderElement.initializeText(font, RenderElement::startupLogMessages));
}
public static RenderElement forgeVersionOverlay(SimpleFont font, String version) {
return new RenderElement(RenderElement.initializeText(font, (bb, fnt, ctx)->
font.generateVerticesForTexts(ctx.scaledWidth() - font.stringWidth(version) - 10,
ctx.scaledHeight() - font.lineSpacing() + font.descent() - 10, bb,
new SimpleFont.DisplayText(version, ctx.colourScheme.foreground().packedint(RenderElement.globalAlpha)))));
}
public static RenderElement squir() {
return new RenderElement(RenderElement.initializeTexture("squirrel.png", 45000, 3, (bb, context, size, frame) -> {
var inset = 5f;
var x0 = inset;
var x1 = inset + size[0] * context.scale();
var y0 = inset;
var y1 = inset + size[1] * context.scale();
int fade = (int) (Math.cos(frame * Math.PI / 16) * 16) + 16;
// int fade = 0xff;
var colour = (Math.min(fade, globalAlpha) & 0xff) << 24 | 0xffffff;
QuadHelper.loadQuad(bb, x0, x1, y0, y1, 0f, 1f, 0f, 1f, colour);
}));
}
public static RenderElement anvil(SimpleFont font) {
return new RenderElement(RenderElement.initializeTexture("forge_anvil.png", 20000, 2, (bb, context, size, frame) -> {
var x0 = context.scaledWidth() - size[0] * context.scale();
var x1 = context.scaledWidth();
var y0 = context.scaledHeight() - size[0] * context.scale() - font.descent() - font.lineSpacing();
var y1 = context.scaledHeight() - font.descent() - font.lineSpacing();
int frameidx = frame % 32;
float framepos = (frameidx * (float)size[0]) / size[1];
float framesize = size[0] / (float)size[1];
QuadHelper.loadQuad(bb, x0, x1, y0, y1, 0f, 1f, framepos, framepos+framesize, globalAlpha << 24 | 0xFFFFFF);
}));
}
public static RenderElement progressBars(SimpleFont font) {
return new RenderElement(() -> (bb, ctx, frame) -> RenderElement.startupProgressBars(font, bb, ctx, frame));
}
public static RenderElement performanceBar(SimpleFont font) {
return new RenderElement(() -> (bb, ctx, frame) -> RenderElement.memoryInfo(font, bb, ctx, frame));
}
public static void startupProgressBars(SimpleFont font, final SimpleBufferBuilder buffer, final DisplayContext context, final int frameNumber) {
Renderer acc = null;
var barCount = 2;
List<ProgressMeter> currentProgress = StartupNotificationManager.getCurrentProgress();
var size = currentProgress.size();
var alpha = 0xFF;
for (int i = 0; i < barCount && i < size; i++) {
final ProgressMeter pm = currentProgress.get(i);
Renderer barRenderer = barRenderer(i, alpha, font, pm, context);
acc = barRenderer.then(acc);
alpha >>= 1;
}
if (acc != null)
acc.accept(buffer, context, frameNumber);
}
private static final int BAR_HEIGHT = 20;
private static final int BAR_WIDTH = 400;
private static Renderer barRenderer(int cnt, int alpha, SimpleFont font, ProgressMeter pm, DisplayContext context) {
var barSpacing = font.lineSpacing() - font.descent() + BAR_HEIGHT;
var y = 250 * context.scale() + cnt * barSpacing;
var colour = (alpha << 24) | 0xFFFFFF;
Renderer bar;
if (pm.steps() == 0) {
bar = progressBar(ctx->new int[] {(ctx.scaledWidth() - BAR_WIDTH * ctx.scale()) / 2, y + font.lineSpacing() - font.descent(), BAR_WIDTH * ctx.scale()}, f->colour, frame -> indeterminateBar(frame, cnt == 0));
} else {
bar = progressBar(ctx -> new int[]{(ctx.scaledWidth() - BAR_WIDTH * ctx.scale()) / 2, y + font.lineSpacing() - font.descent(), BAR_WIDTH * ctx.scale()}, f -> colour, f -> new float[]{0f, pm.progress()});
}
Renderer label = (bb, ctx, frame) -> renderText(font, text((ctx.scaledWidth() - BAR_WIDTH * ctx.scale()) / 2, y, pm.label().getText(), colour), bb, ctx);
return bar.then(label);
}
private static float[] indeterminateBar(int frame, boolean isActive) {
if (RenderElement.globalAlpha != 0xFF || !isActive) {
return new float[] {0f,1f};
} else {
var progress = frame % 100;
return new float[]{clamp((progress - 2) / 100f, 0f, 1f), clamp((progress + 2) / 100f, 0f, 1f)};
}
}
private static void memoryInfo(SimpleFont font, final SimpleBufferBuilder buffer, final DisplayContext context, final int frameNumber) {
var y = 10 * context.scale();
PerformanceInfo pi = context.performance();
final int colour = hsvToRGB((1.0f - (float)Math.pow(pi.memory(), 1.5f)) / 3f, 1.0f, 0.5f);
var bar = progressBar(ctx -> new int[]{(ctx.scaledWidth() - BAR_WIDTH * ctx.scale()) / 2, y, BAR_WIDTH * ctx.scale()}, f -> colour, f -> new float[]{0f, pi.memory()});
var width = font.stringWidth(pi.text());
Renderer label = (bb, ctx, frame) -> renderText(font, text(ctx.scaledWidth() / 2 - width / 2, y + 18, pi.text(), context.colourScheme.foreground().packedint(globalAlpha)), bb, ctx);
bar.then(label).accept(buffer, context, frameNumber);
}
interface ColourFunction {
int colour(int frame);
}
interface ProgressDisplay {
float[] progress(int frame);
}
interface BarPosition {
int[] location(DisplayContext context);
}
public static Renderer progressBar(BarPosition position, ColourFunction colourFunction, ProgressDisplay progressDisplay) {
return (bb, context, frame) -> {
var colour = colourFunction.colour(frame);
var alpha = (colour & 0xFF000000) >> 24;
context.elementShader().updateTextureUniform(0);
context.elementShader().updateRenderTypeUniform(ElementShader.RenderType.BAR);
var progress = progressDisplay.progress(frame);
bb.begin(SimpleBufferBuilder.Format.POS_TEX_COLOR, SimpleBufferBuilder.Mode.QUADS);
var inset = 2;
var pos = position.location(context);
var x0 = pos[0];
var x1 = pos[0] + pos[2] + 4 * inset;
var y0 = pos[1];
var y1 = y0 + BAR_HEIGHT;
QuadHelper.loadQuad(bb, x0, x1, y0, y1, 0f, 0f, 0f, 0f, context.colourScheme().foreground().packedint(alpha));
x0 += inset;
x1 -= inset;
y0 += inset;
y1 -= inset;
QuadHelper.loadQuad(bb, x0, x1, y0, y1, 0f, 0f, 0f, 0f, context.colourScheme().background().packedint(RenderElement.globalAlpha));
x1 = x0 + inset + (int)(progress[1] * pos[2]);
x0 += inset + progress[0] * pos[2];
y0 += inset;
y1 -= inset;
QuadHelper.loadQuad(bb, x0, x1, y0, y1, 0f, 0f, 0f, 0f, colour);
bb.draw();
};
}
private static Initializer initializeText(SimpleFont font, TextGenerator textGenerator) {
return () -> (bb, context, frame) -> renderText(font, textGenerator, bb, context);
}
private static void renderText(final SimpleFont font, final TextGenerator textGenerator, final SimpleBufferBuilder bb, final DisplayContext context) {
context.elementShader().updateTextureUniform(font.textureNumber());
context.elementShader().updateRenderTypeUniform(ElementShader.RenderType.FONT);
bb.begin(SimpleBufferBuilder.Format.POS_TEX_COLOR, SimpleBufferBuilder.Mode.QUADS);
textGenerator.accept(bb, font, context);
bb.draw();
}
private static TextGenerator text(int x, int y, String text, int colour) {
return (bb, font, context) -> font.generateVerticesForTexts(x, y, bb, new SimpleFont.DisplayText(text, colour));
}
private static Initializer initializeTexture(final String textureFileName, int size, int textureNumber, TextureRenderer positionAndColour) {
return ()->{
int[] imgSize = STBHelper.loadTextureFromClasspath(textureFileName, size, GL_TEXTURE0 + textureNumber + INDEX_TEXTURE_OFFSET);
return (bb, ctx, frame) -> {
ctx.elementShader().updateTextureUniform(textureNumber + INDEX_TEXTURE_OFFSET);
ctx.elementShader().updateRenderTypeUniform(ElementShader.RenderType.TEXTURE);
renderTexture(bb, ctx, frame, imgSize, positionAndColour);
};
};
}
private static void renderTexture(SimpleBufferBuilder bb, DisplayContext context, int frame, int[] size, TextureRenderer positionAndColour) {
bb.begin(SimpleBufferBuilder.Format.POS_TEX_COLOR, SimpleBufferBuilder.Mode.QUADS);
positionAndColour.accept(bb, context, size, frame);
bb.draw();
}
public static float clamp(float num, float min, float max) {
if (num < min) {
return min;
} else {
return num > max ? max : num;
}
}
public static int clamp(int num, int min, int max) {
if (num < min) {
return min;
} else {
return num > max ? max : num;
}
}
public static int hsvToRGB(float hue, float saturation, float value) {
int i = (int)(hue * 6.0F) % 6;
float f = hue * 6.0F - (float)i;
float f1 = value * (1.0F - saturation);
float f2 = value * (1.0F - f * saturation);
float f3 = value * (1.0F - (1.0F - f) * saturation);
float f4;
float f5;
float f6;
switch(i) {
case 0:
f4 = value;
f5 = f3;
f6 = f1;
break;
case 1:
f4 = f2;
f5 = value;
f6 = f1;
break;
case 2:
f4 = f1;
f5 = value;
f6 = f3;
break;
case 3:
f4 = f1;
f5 = f2;
f6 = value;
break;
case 4:
f4 = f3;
f5 = f1;
f6 = value;
break;
case 5:
f4 = value;
f5 = f1;
f6 = f2;
break;
default:
throw new RuntimeException("Something went wrong when converting from HSV to RGB. Input was " + hue + ", " + saturation + ", " + value);
}
int j = clamp((int)(f4 * 255.0F), 0, 255);
int k = clamp((int)(f5 * 255.0F), 0, 255);
int l = clamp((int)(f6 * 255.0F), 0, 255);
return 0xFF << 24 | j << 16 | k << 8 | l;
}
}
@@ -0,0 +1,65 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.earlydisplay;
import org.lwjgl.BufferUtils;
import org.lwjgl.stb.STBImage;
import org.lwjgl.system.MemoryUtil;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.util.Objects;
import static org.lwjgl.opengl.GL32C.*;
public class STBHelper {
public static ByteBuffer readFromClasspath(final String name, int initialCapacity) {
ByteBuffer buf;
try (var channel = Channels.newChannel(
Objects.requireNonNull(STBHelper.class.getClassLoader().getResourceAsStream(name), "The resource "+name+" cannot be found"))) {
buf = BufferUtils.createByteBuffer(initialCapacity);
while (true) {
var readbytes = channel.read(buf);
if (readbytes == -1) break;
if (buf.remaining() == 0) { // extend the buffer by 50%
var newBuf = BufferUtils.createByteBuffer(buf.capacity() * 3 / 2);
buf.flip();
newBuf.put(buf);
buf = newBuf;
}
}
} catch (IOException e) {
throw new UncheckedIOException(e);
}
buf.flip();
return MemoryUtil.memSlice(buf); // we trim the final buffer to the size of the content
}
public static int[] loadTextureFromClasspath(String file, int size, int textureNumber) {
int[] lw = new int[1];
int[] lh = new int[1];
int[] lc = new int[1];
var img = loadImageFromClasspath(file, size, lw, lh, lc);
var texid = glGenTextures();
glActiveTexture(textureNumber);
glBindTexture(GL_TEXTURE_2D, texid);
// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, lw[0], lh[0], 0, GL_RGBA, GL_UNSIGNED_BYTE, img);
glActiveTexture(GL_TEXTURE0);
MemoryUtil.memFree(img);
return new int[] {lw[0], lh[0]};
}
public static ByteBuffer loadImageFromClasspath(String file, int size, int[] width, int[] height, int[] channels) {
ByteBuffer buf = STBHelper.readFromClasspath(file, size);
return STBImage.stbi_load_from_memory(buf, width, height, channels, 4);
}
}
@@ -0,0 +1,517 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.earlydisplay;
import org.lwjgl.system.MemoryUtil;
import java.io.Closeable;
import java.nio.ByteBuffer;
import java.util.Arrays;
import static org.lwjgl.opengl.GL32C.*;
/**
* A very simple, Mojang inspired BufferBuilder.
* <em>This has been customized for 2d rendering such as text and simple planar textures</em>
* <p>
* Not bound to any specific format, ideally should be held onto for re-use.
* <p>
* Can be used for 'immediate mode' style rendering using {@link #draw()}, or
* upload to external vertex arrays for proper instancing using {@link #finishAndUpload()}.
* <p>
* This is a Triangles only buffer, all data uploaded is in Triangles.
* Quads are converted to triangles using {@code 0, 1, 2, 0, 2, 3}.
* <p>
* Any given {@link Format} should have its individual {@link Element} components
* buffered in the order specified by the {@link Format},
* followed by an {@link #endVertex()} call to prepare for the next vertex.
* <p>
* It is illegal to buffer primitives in any format other than the one specified to
* {@link #begin(Format, Mode)}.
*
* @author covers1624
*/
public class SimpleBufferBuilder implements Closeable {
private static final MemoryUtil.MemoryAllocator ALLOCATOR = MemoryUtil.getAllocator(false);
private static final int[] VERTEX_ARRAYS = new int[Format.values().length];
private static final int[] VERTEX_BUFFERS = new int[Format.values().length];
private static final int[] VERTEX_BUFFER_LENGTHS = new int[Format.values().length];
private static int elementBuffer = 0;
private static int elementBufferVertexLength = 0;
static {
Arrays.fill(VERTEX_ARRAYS, 0);
Arrays.fill(VERTEX_BUFFERS, 0);
Arrays.fill(VERTEX_BUFFER_LENGTHS, 0);
}
private long bufferAddr; // Pointer to the backing buffer.
private ByteBuffer buffer; // ByteBuffer view of the backing buffer.
private Format format; // The current format we are buffering.
private Mode mode; // The current mode we are buffering.
private boolean building; // If we are building the buffer.
private int elementIndex; // The current element index we are buffering. if elementIndex == format.types.length, we expect 'endVertex'
private int index; // The current index into the buffer we are writing to.
private int vertices; // The number of complete vertices we have buffered.
/**
* Create a new SimpleBufferBuilder with an initial capacity.
* <p>
* The buffer will be doubled as required.
* <p>
* Generally picking a small number, around 128/256 should be a
* safe bet. Provided you cache your buffers, it should not mean much overall.
*
* @param capacity The initial capacity in bytes.
*/
public SimpleBufferBuilder(int capacity) {
bufferAddr = ALLOCATOR.malloc(capacity);
buffer = MemoryUtil.memByteBuffer(bufferAddr, capacity);
}
public static void destroy() {
glDeleteBuffers(VERTEX_BUFFERS);
glDeleteBuffers(elementBuffer);
glDeleteVertexArrays(VERTEX_ARRAYS);
}
private static void ensureElementBufferLength(int vertices) {
if (elementBufferVertexLength >= vertices) {
return;
}
// treating it as immutable storage, even though it's not
final var newElementBuffer = glGenBuffers();
var newElementBufferVertexLength = Math.max(1024, elementBufferVertexLength);
while (newElementBufferVertexLength < vertices) {
newElementBufferVertexLength *= 2;
}
final var oldIndexCount = elementBufferVertexLength + elementBufferVertexLength / 2;
final var newIndexCount = newElementBufferVertexLength + newElementBufferVertexLength / 2;
// allocate new buffer
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, newElementBuffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, newIndexCount * 4L, GL_STATIC_DRAW);
// mapping avoids creating additional CPU copies of the data
// unsynchronized is fine because this is a brand-new buffer, and the old contents will be copied in afterward
// also can invalidate the whole buffer too, similarly because brand new, don't care what was there before
final var mappingOffset = oldIndexCount * 4;
final var mappingSize = (newIndexCount - oldIndexCount) * 4;
final var mappedBuffer = glMapBufferRange(GL_ELEMENT_ARRAY_BUFFER, mappingOffset, mappingSize, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);
if(mappedBuffer == null){
throw new NullPointerException("OpenGL buffer mapping failed");
}
final int quads = newElementBufferVertexLength / 4;
final int oldQuads = elementBufferVertexLength / 4;
// generate indices for the extension to the buffer
for (int i = oldQuads; i < quads; i++) {
// Quads are a bit different, we need to emit 2 triangles such that
// when combined they make up a single quad.
mappedBuffer.putInt(i * 4 + 0).putInt(i * 4 + 1).putInt(i * 4 + 2);
mappedBuffer.putInt(i * 4 + 1).putInt(i * 4 + 3).putInt(i * 4 + 2);
}
glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER);
if (elementBuffer != 0) {
// copy old data from previous element buffer
glBindBuffer(GL_COPY_READ_BUFFER, elementBuffer);
glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_ELEMENT_ARRAY_BUFFER, 0, 0, mappingOffset);
glBindBuffer(GL_COPY_READ_BUFFER, 0);
}
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glDeleteBuffers(elementBuffer);
elementBuffer = newElementBuffer;
elementBufferVertexLength = newElementBufferVertexLength;
}
/**
* Start building a new set of vertex data in the
* given format and mode.
*
* @param format The format to start building in.
* @param mode The mode to start building in.
*/
public SimpleBufferBuilder begin(Format format, Mode mode) {
if (bufferAddr == MemoryUtil.NULL) {
throw new IllegalStateException("Buffer has been freed."); // You already free'd the buffer
}
if (building) {
throw new IllegalStateException("Already building."); // Your already building verticies.
}
this.format = format;
this.mode = mode;
building = true;
elementIndex = 0;
ensureSpace(format.stride);
// Rewind ready for new data.
buffer.rewind();
buffer.limit(buffer.capacity());
return this;
}
/**
* Buffer a position element.
*
* @param x The x.
* @param y The y.
* @param z The z.
* @return The same builder.
*/
public SimpleBufferBuilder pos(float x, float y) {
if (!building) throw new IllegalStateException("Not building."); // You did not call begin.
if (elementIndex == format.types.length) throw new IllegalStateException("Expected endVertex"); // we have reached the end of elements to buffer for this vertex, we expected an endVertex call.
if (format.types[elementIndex] != Element.POS) throw new IllegalArgumentException("Expected " + format.types[elementIndex]); // You called the wrong method for the format order.
// Assumes that our POS element specifies the FLOAT data type.
buffer.putFloat(index + 0, x);
buffer.putFloat(index + 4, y);
// Increment index for the number of bytes we wrote and increment the element index.
index += format.types[elementIndex].width;
elementIndex++;
return this;
}
/**
* Buffer a texture element.
*
* @param u The u.
* @param v The v.
* @return The same builder.
*/
public SimpleBufferBuilder tex(float u, float v) {
if (!building) throw new IllegalStateException("Not building."); // You did not call begin.
if (elementIndex == format.types.length) throw new IllegalStateException("Expected endVertex"); // we have reached the end of elements to buffer for this vertex, we expected an endVertex call.
if (format.types[elementIndex] != Element.TEX) throw new IllegalArgumentException("Expected " + format.types[elementIndex]); // You called the wrong method for the format order.
// Assumes our TEX element specifies the FLOAT data type.
buffer.putFloat(index + 0, u);
buffer.putFloat(index + 4, v);
// Increment index for the number of bytes we wrote and increment the element index.
index += format.types[elementIndex].width;
elementIndex++;
return this;
}
/**
* Buffer a color element.
*
* @param r The red component. (0-1)
* @param g The green component. (0-1)
* @param b The blue component. (0-1)
* @param a The alpha component. (0-1)
* @return The same buffer.
*/
public SimpleBufferBuilder colour(float r, float g, float b, float a) {
// Expand floats to 0-255 and forward.
return colour((byte) (r * 255F), (byte) (g * 255F), (byte) (b * 255F), (byte) (a * 255F));
}
/**
* @see ColourScheme.Colour#packedint(int)
* @param packedColor an ABGR packed int
* @return the same buffer.
*/
public SimpleBufferBuilder colour(int packedColor) {
if (!building) throw new IllegalStateException("Not building."); // You did not call begin.
if (elementIndex == format.types.length) throw new IllegalStateException("Expected endVertex"); // we have reached the end of elements to buffer for this vertex, we expected an endVertex call.
if (format.types[elementIndex] != Element.COLOR) throw new IllegalArgumentException("Expected " + format.types[elementIndex]); // You called the wrong method for the format order.
// Assumes our COLOR element specifies the UNSIGNED_BYTE data type.
buffer.putInt(index + 0, packedColor);
// Increment index for the number of bytes we wrote and increment the element index.
index += format.types[elementIndex].width;
elementIndex++;
return this;
}
/**
* Buffer a color element.
*
* @param r The red component. (0-255)
* @param g The green component. (0-255)
* @param b The blue component. (0-255)
* @param a The alpha component. (0-255)
* @return The same buffer.
*/
public SimpleBufferBuilder colour(byte r, byte g, byte b, byte a) {
if (!building) throw new IllegalStateException("Not building."); // You did not call begin.
if (elementIndex == format.types.length) throw new IllegalStateException("Expected endVertex"); // we have reached the end of elements to buffer for this vertex, we expected an endVertex call.
if (format.types[elementIndex] != Element.COLOR) throw new IllegalArgumentException("Expected " + format.types[elementIndex]); // You called the wrong method for the format order.
// Assumes our COLOR element specifies the UNSIGNED_BYTE data type.
buffer.put(index + 0, r);
buffer.put(index + 1, g);
buffer.put(index + 2, b);
buffer.put(index + 3, a);
// Increment index for the number of bytes we wrote and increment the element index.
index += format.types[elementIndex].width;
elementIndex++;
return this;
}
/**
* End building the current vertex and prepare for the next.
*
* @return The same builder.
*/
public SimpleBufferBuilder endVertex() {
if (!building) throw new IllegalStateException("Not building."); // You did not call begin.
if (elementIndex != format.types.length) throw new IllegalStateException("Expected " + format.types[elementIndex]); // You did not finish building the vertex.
// Reset elementIndex
elementIndex = 0;
// Increment the number of vertices we have so far buffered.
vertices++;
// Make sure there is space for the next vertex.
ensureSpace(format.stride);
return this;
}
// Checks there is enough space in the buffer for specified number of bytes.
// If there is not enough space, the buffer is increased by 50%.
private void ensureSpace(int newBytes) {
int cap = buffer.capacity();
if (index + newBytes > cap) {
int newCap = Math.max(3 * cap / 2, 3 * newBytes / 2);
bufferAddr = ALLOCATOR.realloc(bufferAddr, newCap);
buffer = MemoryUtil.memByteBuffer(bufferAddr, newCap);
buffer.rewind();
}
}
/**
* Upload the current buffer.
* <p>
* This will bind a {@link org.lwjgl.opengl.GL32C#GL_ARRAY_BUFFER} and {@link org.lwjgl.opengl.GL32C#GL_ELEMENT_ARRAY_BUFFER}
* <p>
* The vertex data and index data is uploaded to their respective buffers.
* <p>
* Uploading the buffers finishes drawing and resets for the next buffer operation.
* <p>
* This should not be called in conjunction with {@link #draw()}
*
* @return The number of indexes that were uploaded.
*/
public int finishAndUpload() {
if (!building) throw new IllegalStateException("Not building.");
int indices;
try {
if (elementIndex == format.types.length) throw new IllegalStateException("Expected endVertex"); // You didn't finish building your vertex.
if (elementIndex != 0) throw new IllegalStateException("Not finished building vertex, Expected: " + format.types[elementIndex]); // You didn't finish building your vertex data.
if (vertices == 0) return 0; // No vertices buffered, lets not do anything.
if (vertices % mode.vertices != 0) throw new IllegalStateException("Does not contain vertices aligned to " + mode); // You did not put in enough vertices to cleanly slice the data into TRIANGLES/QUADS
// Reset position to 0, limit the buffer to our index.
buffer.position(0);
buffer.limit(index);
// Upload the raw vertex data in dynamic mode.
final int vbo = VERTEX_BUFFERS[format.ordinal()];
final int vboSize = VERTEX_BUFFER_LENGTHS[format.ordinal()];
glBindBuffer(GL_ARRAY_BUFFER, vbo);
if (vboSize < index) {
// expand buffer, it's not big enough
var newVBOSize = Math.max(1024, vboSize);
while (newVBOSize < index){
newVBOSize *= 2;
}
// because everything is overwritten anyway, we can do an in-place reallocation
glBufferData(GL_ARRAY_BUFFER, newVBOSize, GL_DYNAMIC_DRAW);
VERTEX_BUFFER_LENGTHS[format.ordinal()] = newVBOSize;
}
glBufferSubData(GL_ARRAY_BUFFER, 0, buffer);
// The number of indices for triangles is equal to our vertex count, as that is
// what we operate in. However, for Quads, we have exactly vertices + vertices / 2
// vertices once we convert the quads to triangles.
indices = mode == Mode.TRIANGLES ? vertices : vertices + vertices / 2;
if (mode == Mode.QUADS) {
ensureElementBufferLength(vertices);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementBuffer);
}
return indices;
} finally {
// Reset builder state for next begin call.
building = false;
vertices = 0;
index = 0;
}
}
/**
* Upload and draw this buffer using one of a number of re-usable set of buffers.
* <p>
* This will immediately upload the buffer, resetting this builder for the next
* buffer operation, and draw the uploaded data.
* <p>
* You will need to bind shaders, textures, etc, before calling this function.
*/
public void draw() {
if (!building) throw new IllegalStateException("Not building.");
int vao = VERTEX_ARRAYS[format.ordinal()];
int vbo = VERTEX_BUFFERS[format.ordinal()];
if (vao == 0) {
// These 3 buffers are paired, you can't allocate one without the others.
assert vbo == 0;
// Make new vertex array and buffers!
vao = glGenVertexArrays();
vbo = glGenBuffers();
// Cache the vertex array and buffers for future re-use.
VERTEX_ARRAYS[format.ordinal()] = vao;
VERTEX_BUFFERS[format.ordinal()] = vbo;
// Ask our Format to set up its data layout for the vertex array.
// but only once, the VAO saves this state
glBindVertexArray(vao);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
format.bind();
format.enable();
}
// Bind the vertex array and buffers!
glBindVertexArray(vao);
// Upload the data.
int indices = finishAndUpload();
if (mode == Mode.QUADS) {
glDrawElements(GL_TRIANGLES, indices, GL_UNSIGNED_INT, 0);
} else {
glDrawArrays(GL_TRIANGLES, 0, indices);
}
// Unbind the vertex array.
glBindVertexArray(0);
}
/**
* Clear this builder's cached buffer.
* <p>
* If you are completely done, call {@link #destroy()}
*/
@Override
public void close() {
ALLOCATOR.free(bufferAddr);
bufferAddr = MemoryUtil.NULL;
}
/**
* Represents a primitive mode that this builder is capable of buffering in.
*/
public enum Mode {
TRIANGLES(3),
QUADS(4),
;
public final int vertices;
Mode(int vertices) {
this.vertices = vertices;
}
}
/**
* Specifies a vertex element with a specific data type, number of primitives and a size in bytes.
*/
public enum Element {
POS(GL_FLOAT, 2, 2 * 4),
TEX(GL_FLOAT, 2, 2 * 4),
COLOR(GL_UNSIGNED_BYTE, 4, 4);
public final int glType;
public final int count;
public final int width;
Element(int glType, int count, int width) {
this.glType = glType;
this.count = count;
this.width = width;
}
}
/**
* Specifies a combination of vertex elements.
*/
public enum Format {
POS(Element.POS),
POS_TEX(Element.POS, Element.TEX),
POS_COLOR(Element.POS, Element.COLOR),
POS_TEX_COLOR(Element.POS, Element.TEX, Element.COLOR);
private final Element[] types;
public final int stride;
Format(Element... types) {
this.types = types;
// Stride is the width of each vertex in bytes.
stride = Arrays.stream(types).mapToInt(e -> e.width).sum();
}
/**
* set up the attribute pointers for this format.
* <p>
* Assumes that an array buffer is already bound and ready to go.
*/
public void bind() {
int offset = 0;
// Set up the pointers that tell GL where our interleaved
// vertex data is in the buffers.
for (int i = 0; i < types.length; i++) {
Element type = types[i];
switch (type.glType) {
case GL_FLOAT -> glVertexAttribPointer(i, type.count, GL_FLOAT, false, stride, offset);
case GL_UNSIGNED_BYTE -> glVertexAttribPointer(i, type.count, GL_UNSIGNED_BYTE, true, stride, offset);
default -> throw new IllegalStateException("Unknown glType, I don't know how to bind this vertex element: " + type);
}
// add to the offset for the next element.
offset += type.width;
}
}
/**
* Enables the vertex attributes this format contains.
*/
public void enable() {
for (int i = 0; i < types.length; i++) {
glEnableVertexAttribArray(i);
}
}
/**
* Disables the vertex attributes this format contains.
*/
public void disable() {
for (int i = 0; i < types.length; i++) {
glDisableVertexAttribArray(i);
}
}
}
}
@@ -0,0 +1,182 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.earlydisplay;
import org.lwjgl.BufferUtils;
import org.lwjgl.stb.STBTTAlignedQuad;
import org.lwjgl.stb.STBTTFontinfo;
import org.lwjgl.stb.STBTTPackContext;
import org.lwjgl.stb.STBTTPackRange;
import org.lwjgl.stb.STBTTPackedchar;
import org.lwjgl.system.MemoryStack;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import static org.lwjgl.opengl.GL32C.*;
import static org.lwjgl.stb.STBTruetype.*;
import static org.lwjgl.system.MemoryUtil.NULL;
public class SimpleFont {
private final int textureNumber;
private final int lineSpacing;
private final int descent;
private final int GLYPH_COUNT = 127-32;
private Glyph[] glyphs;
private record Glyph(char c, int charwidth, int[] pos, float[] uv) {
Pos loadQuad(Pos pos, int colour, SimpleBufferBuilder bb) {
final var x0 = pos.x() + pos()[0];
final var y0 = pos.y() + pos()[1];
final var x1 = pos.x() + pos()[2];
final var y1 = pos.y() + pos()[3];
bb.pos(x0, y0).tex(uv()[0], uv()[1]).colour(colour).endVertex();
bb.pos(x1, y0).tex(uv()[2], uv()[1]).colour(colour).endVertex();
bb.pos(x0, y1).tex(uv()[0], uv()[3]).colour(colour).endVertex();
bb.pos(x1, y1).tex(uv()[2], uv()[3]).colour(colour).endVertex();
return new Pos(pos.x()+charwidth(), pos.y(), pos.minx());
}
}
/**
* Build the font and store it in the textureNumber location
*/
public SimpleFont(String fontName, int scale, int bufferSize, int textureNumber) {
ByteBuffer buf = STBHelper.readFromClasspath(fontName, bufferSize);
var info = STBTTFontinfo.create();
if (!stbtt_InitFont(info, buf)) {
throw new IllegalStateException("Bad font");
}
var ascent = new float[1];
var descent = new float[1];
var lineGap = new float[1];
int fontSize = 24;
stbtt_GetScaledFontVMetrics(buf, 0, fontSize, ascent, descent, lineGap);
this.lineSpacing = (int)(ascent[0] - descent[0] + lineGap[0]);
this.descent = (int)Math.floor(descent[0]);
int fontTextureId = glGenTextures();
glActiveTexture(GL_TEXTURE0+textureNumber);
this.textureNumber = textureNumber;
glBindTexture(GL_TEXTURE_2D, fontTextureId);
try (var packedchars = STBTTPackedchar.malloc(GLYPH_COUNT)) {
int texwidth = 256;
int texheight = 128;
try (STBTTPackRange.Buffer packRanges = STBTTPackRange.malloc(1)) {
var bitmap = BufferUtils.createByteBuffer(texwidth * texheight);
try (STBTTPackRange packRange = STBTTPackRange.malloc()) {
packRanges.put(packRange.set(fontSize, 32, null, GLYPH_COUNT, packedchars, (byte) 1, (byte) 1));
packRanges.flip();
}
try (STBTTPackContext pc = STBTTPackContext.malloc()) {
stbtt_PackBegin(pc, bitmap, texwidth, texheight, 0, 1, NULL);
stbtt_PackSetOversampling(pc, 1, 1);
stbtt_PackSetSkipMissingCodepoints(pc, true);
stbtt_PackFontRanges(pc, buf, 0, packRanges);
stbtt_PackEnd(pc);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, texwidth, texheight, 0, GL_RED, GL_UNSIGNED_BYTE, bitmap);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
}
}
glActiveTexture(GL_TEXTURE0);
try (var q = STBTTAlignedQuad.malloc()) {
float[] x = new float[1];
float[] y = new float[1];
glyphs = new Glyph[GLYPH_COUNT];
for (int i = 0; i < GLYPH_COUNT; i++) {
x[0] = 0f;
y[0] = fontSize;
stbtt_GetPackedQuad(packedchars, texwidth, texheight, i, x, y, q, true);
glyphs[i] = new Glyph((char) (i + 32), (int) (x[0] - 0f), new int[]{(int) q.x0(), (int) q.y0(), (int) q.x1(), (int) q.y1()}, new float[]{q.s0(), q.t0(), q.s1(), q.t1()});
}
}
}
}
int lineSpacing() {
return lineSpacing;
}
int textureNumber() {
return textureNumber;
}
int descent() {
return descent;
}
public int stringWidth(String text) {
var bytes = text.getBytes(StandardCharsets.US_ASCII);
int len = 0;
for (int i = 0; i < bytes.length; i++) {
final byte c = bytes[i];
len += switch (c) {
case '\n', '\t' -> 0;
case ' ' -> glyphs[0].charwidth();
default -> {
if (c - 32 < this.GLYPH_COUNT && c > 32) {
yield this.glyphs[c - 32].charwidth();
} else {
yield 0;
}
}
};
}
return len;
}
private record Pos(int x, int y, int minx) {}
/**
* A piece of text to display
*
* @param string The text
* @param colour The colour of the text as an RGBA packed int
*/
public record DisplayText(String string, int colour) {
private byte[] asBytes() {
return string.getBytes(StandardCharsets.US_ASCII);
}
Pos generateStringArray(SimpleFont font, Pos pos, SimpleBufferBuilder bb) {
for (int i = 0; i < asBytes().length; i++) {
byte c = asBytes()[i];
pos = switch (c) {
case '\n' -> new Pos(pos.minx(), pos.y()+font.lineSpacing(), pos.minx());
case '\t' -> new Pos(pos.x()+font.glyphs[0].charwidth() * 4, pos.y(), pos.minx());
case ' ' -> new Pos(pos.x()+font.glyphs[0].charwidth(), pos.y(), pos.minx());
default -> {
if (c-32 < font.GLYPH_COUNT && c > 32) {
pos = font.glyphs[c - 32].loadQuad(pos, colour(), bb);
}
yield pos;
}
};
}
return pos;
}
}
/**
* Generate vertices for a set of display texts
* @param x The starting screen x coordinate
* @param y The starting screen y coordinate
* @param texts Some {@link DisplayText} to display
* @return a {@link SimpleBufferBuilder} that can draw the texts
*/
public SimpleBufferBuilder generateVerticesForTexts(int x, int y, SimpleBufferBuilder textBB, DisplayText... texts) {
var pos = new Pos(x, y, x);
for (DisplayText text : texts) {
pos = text.generateStringArray(this, pos, textBB);
}
return textBB;
}
}
@@ -0,0 +1 @@
net.minecraftforge.fml.earlydisplay.DisplayWindow
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Loaded 100 of 2638 files, more files were not shown because too many files have changed in this diff. Show more