├── .gitignore ├── LICENSE.md ├── README.md ├── README.template.md ├── build.gradle ├── changelog.md ├── example ├── LICENSE ├── README.md ├── build.gradle ├── changelog.md ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src │ └── main │ ├── java │ └── example │ │ ├── ExampleScheduleableBlock.java │ │ └── ExampleUsageJava.java │ ├── kotlin │ └── example │ │ └── ExampleUsage.kt │ └── resources │ └── fabric.mod.json ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── logo.png ├── settings.gradle └── src └── main ├── java └── scheduler │ ├── Scheduleable.java │ ├── Scheduler.java │ └── SchedulerBuilder.java ├── kotlin └── scheduler │ ├── SchedulerKt.kt │ └── internal │ ├── Scheduler.kt │ ├── TickerPackets.kt │ ├── TickerState.kt │ ├── Util.kt │ ├── WorkingScheduler.kt │ ├── WorldTick.kt │ └── util │ ├── ModInit.kt │ └── Networking.kt └── resources └── fabric.mod.json /.gitignore: -------------------------------------------------------------------------------- 1 | # gradle 2 | 3 | .gradle/ 4 | build/ 5 | out/ 6 | 7 | # idea 8 | 9 | .idea/ 10 | *.iml 11 | *.ipr 12 | *.iws 13 | 14 | # fabric 15 | 16 | run/ 17 | minecraft/ 18 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Fudge 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Working Scheduler 2 | [![CurseForge](https://curse.nikky.moe/api/img/340964?logo)](https://curseforge.com/minecraft/mc-mods/working-scheduler) 3 | [![Discord](https://img.shields.io/discord/219787567262859264?color=blue&label=Discord)](https://discord.gg/CFaCu97) 4 | [![Bintray](https://api.bintray.com/packages/natanfudge/libs/working-scheduler/images/download.svg)](https://bintray.com/beta/#/natanfudge/libs/working-scheduler?tab=overview) 5 | [![Latest Commit](https://img.shields.io/github/last-commit/natanfudge/working-scheduler)](https://github.com/natanfudge/working-scheduler/commits/master) 6 | 7 | Working Scheduler provides a working, fast, and comprehensive alternative to Minecraft's scheduler for Fabric mods. 8 | 9 | ## Introduction 10 | 11 | A scheduler simply provides a way to execute an operation after a certain amount of time has passed. 12 | In Minecraft that time is measured in ticks. 13 | ### What is the problem with the vanilla Minecraft scheduler? 14 | - It's slow: the more operations you schedule, the worse the tick overhead becomes. 15 | - It doesn't work properly: Minecraft tries to save the state of your scheduled actions when the world unloads, but fails miserably. 16 | As soon as the world will load again all scheduled actions will execute immediately, with no respect to the amount of time they have left. 17 | - It's limited: In Minecraft you can't cancel a scheduled action, you can't schedule in the client, only the server, 18 | you can't have it repeated multiple times, and you can't pass any state into the scheduled action other than a single BlockPos. 19 | This also makes it extremely difficult to schedule multiple different actions on a singular block. 20 | ### What about Working Scheduler? 21 | - It's fast: the tick overhead is constant no matter how many actions you schedule. 22 | - It works: your state will be saved properly. 23 | - It's comprehensive: You can can cancel schedules, you can schedule from the client, you can repeat the same action multiple times, 24 | you can pass arbitrary NBT data to the schedule action, and you can optionally pass a "schedule ID" which allows you to easily 25 | differentiate between different scheduled actions. 26 | 27 | ## Gradle setup 28 | Add `jcenter()` to your repositories if you haven't yet: 29 | ```groovy 30 | repositories { 31 | // [...] 32 | jcenter() 33 | } 34 | ``` 35 | Add the mod dependency: 36 | ```groovy 37 | dependencies { 38 | modImplementation("com.lettuce.fudge:working-scheduler:1.1.4-20w19a") 39 | } 40 | ``` 41 | ## Usage 42 | The first thing you need to do is implement `Scheduleable` on a block of your choosing. 43 | Generally this should be the same block will execute the schedule, but it can be whatever block you want. 44 | [Why do we need a Block?](https://github.com/natanfudge/Working-Scheduler#why-do-we-need-a-block-and-the-future) 45 | ```java 46 | public class ExampleScheduleableBlock extends Block implements Scheduleable { 47 | @Override 48 | public void onScheduleEnd(World world, BlockPos pos, int scheduleId, CompoundTag additionalData) { 49 | System.out.println("X amount of ticks have passed!"); 50 | } 51 | } 52 | ``` 53 | As an example we will schedule things when the player has right-clicked the block. 54 | To schedule an action we have separate idiomatic Java and Kotlin apis. 55 | (Note: Java API is usable from Kotlin as well but using the Kotlin-specific one is recommended) 56 |
57 | Java 58 | 59 | ```java 60 | public class ExampleScheduleableBlock extends Block implements Scheduleable { 61 | public void onScheduleEnd(World world, BlockPos pos, int scheduleId, CompoundTag additionalData) {/*...*/} 62 | 63 | @Override 64 | public boolean activate(BlockState blockState, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hitResult) { 65 | // Schedule the print statement action to occur after 30 ticks 66 | Scheduler.Builder(this, world).schedule(30); 67 | 68 | // Repeat the action 4 times with a 10 ticks interval 69 | Scheduler.Builder(this, world).repeat(4, 10); 70 | 71 | // Repeat the action for 100 ticks with a 15 ticks interval 72 | Scheduler.Builder(this, world).repeatFor(100, 15); 73 | 74 | return true; 75 | } 76 | } 77 | ``` 78 |
79 | 80 |
81 | Kotlin 82 | 83 | ```kotlin 84 | class ExampleScheduleableBlock(settings: Block.Settings) : Block(settings), Scheduleable { 85 | override fun onScheduleEnd(world: World, pos: BlockPos, scheduleId: Int, additionalData: CompoundTag) {/*...*/ } 86 | 87 | override fun activate(blockState: BlockState, world: World, pos: BlockPos, player: PlayerEntity, hand: Hand, hitResult: BlockHitResult): Boolean { 88 | // Schedule the print statement action to occur after 30 ticks 89 | BlockScheduler.schedule(ticksUntilEnd = 30, block = this, world = world) 90 | 91 | // Repeat the action 4 times with a 10 ticks interval 92 | BlockScheduler.repeat(repeatAmount = 4, tickInterval = 10, block = this, world = world) 93 | 94 | // Repeat the action for 100 ticks with a 15 ticks interval 95 | BlockScheduler.repeatFor(ticksUntilStop = 100, tickInterval = 15, block = this, world = world) 96 | 97 | return true 98 | } 99 | } 100 | ``` 101 | 102 |
103 | 104 | Full example projects can be seen [here](https://github.com/natanfudge/Working-Scheduler/tree/master/example/src/main). 105 | 106 | ### Attaching additional data 107 | Provide the data while scheduling: 108 | 109 |
110 | Java 111 | 112 | ```java 113 | public boolean activate(BlockState blockState, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hitResult) { 114 | if (player == null) return false; 115 | CompoundTag scheduleData = new CompoundTag(); 116 | scheduleData.putUuid("player", player.getUuid()); 117 | 118 | Scheduler.Builder(this, world) 119 | .scheduleId(1) 120 | .pos(pos) 121 | .additionalData(scheduleData) 122 | .schedule(100); 123 | 124 | Scheduler.Builder(this, world) 125 | .scheduleId(2) 126 | .pos(pos) 127 | .additionalData(scheduleData) 128 | .repeat(4,20); 129 | 130 | return true; 131 | } 132 | ``` 133 |
134 | 135 |
136 | Kotlin 137 | 138 | ```kotlin 139 | override fun activate( 140 | blockState: BlockState, 141 | world: World, 142 | pos: BlockPos, 143 | player: PlayerEntity?, 144 | hand: Hand?, 145 | hitResult: BlockHitResult? 146 | ): Boolean { 147 | val scheduleData = CompoundTag().apply { putUuid("player", player?.uuid ?: UUID(0, 0)) } 148 | 149 | BlockScheduler.schedule( 150 | ticksUntilEnd = 100, 151 | block = this, 152 | scheduleId = 1, 153 | world = world, 154 | blockPos = pos, 155 | additionalData = scheduleData 156 | ) 157 | 158 | BlockScheduler.repeat( 159 | repeatAmount = 4, 160 | tickInterval = 20, 161 | block = this, 162 | scheduleId = 2, 163 | world = world, 164 | blockPos = pos, 165 | additionalData = scheduleData 166 | ) 167 | 168 | return true 169 | } 170 | ``` 171 |
172 | 173 | And then use the data when the schedule ends: 174 | ```java 175 | public class ExampleScheduleableBlock extends Block implements Scheduleable { 176 | @Override 177 | public void onScheduleEnd(World world, BlockPos pos, int scheduleId, CompoundTag additionalData) { 178 | // Note: you should validate that the player exists and the additionalData was not tampered with. 179 | // No validation is done for the sake of simplicity. 180 | PlayerEntity player = world.getPlayerByUuid(additionalData.getUuid("player")); 181 | if (scheduleId == 1) { 182 | player.sendMessage(new LiteralText("Normal schedule ended at pos " + pos)); 183 | } else if(scheduleId == 2){ 184 | player.sendMessage(new LiteralText("Repeating schedule ended at pos " + pos)); 185 | } 186 | } 187 | } 188 | ``` 189 | Notice how we use the `scheduleId` to differentiate between different schedule calls! 190 | 191 | ### Cancelling 192 | Whenever you call a schedule, you will receive a `CancellationToken` instance. Simply call `cancel` on the same `world` to cancel: 193 | ```java 194 | public class ExampleScheduleableBlock extends Block implements Scheduleable { 195 | public void onScheduleEnd(World world, BlockPos pos, int scheduleId, CompoundTag additionalData) {/*...*/} 196 | 197 | CancellationToken cancellationToken; 198 | 199 | // First this happens... 200 | @Override 201 | public boolean activate(BlockState blockState, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hitResult) { 202 | cancellationToken = Scheduler.Builder(this, world).schedule(30); 203 | 204 | return true; 205 | } 206 | 207 | // And then later on... 208 | @Override 209 | public void onBlockRemoved(BlockState beforeState, World world, BlockPos pos, BlockState afterState, boolean bool) { 210 | // The scheduled action won't occur! 211 | if (cancellationToken != null) cancellationToken.cancel(world); 212 | } 213 | 214 | } 215 | ``` 216 | 217 | Note that usually you want your `CancellationToken` to persist between world loads, 218 | in which case you can store it in a block entity for example. 219 | A `CancellationTokenSerializer` exists specifically to make this easier using [Drawer](https://github.com/natanfudge/Fabric-Drawer). 220 | 221 | ## Dependencies 222 | Working Scheduler depends on [Kotlin](https://www.curseforge.com/minecraft/mc-mods/fabric-language-kotlin) and [Drawer](https://www.curseforge.com/minecraft/mc-mods/fabric-drawer). 223 | If you don't want to depend on them yourself, you can include them in your mod like so: 224 | ```groovy 225 | include("net.fabricmc:fabric-language-kotlin:1.3.60+build.1") 226 | include("com.lettuce.fudge:fabric-drawer:3.2.1-20w19a") 227 | ``` 228 | 229 | ## Why do we need a block, and the future 230 | Right now Scheduler requires you to implement an interface on a Block of yours. 231 | This is because the Block is already instantiated by yourself which helps avoid reflection. 232 | Additionally, scheduling is most commonly done from blocks. 233 | Theoretically, an API like that looks like this could exist: 234 | ```kotlin 235 | schedule(world, 10) { 236 | println("10 ticks have passed.") 237 | } 238 | ``` 239 | But that would require either reflection or a compiler plugin. If you think this is a good idea, feel free to make an issue. 240 | -------------------------------------------------------------------------------- /README.template.md: -------------------------------------------------------------------------------- 1 | # Working Scheduler 2 | [![CurseForge](https://curse.nikky.moe/api/img/340964?logo)](https://curseforge.com/minecraft/mc-mods/working-scheduler) 3 | [![Discord](https://img.shields.io/discord/219787567262859264?color=blue&label=Discord)](https://discord.gg/CFaCu97) 4 | [![Bintray](https://api.bintray.com/packages/natanfudge/libs/working-scheduler/images/download.svg)](https://bintray.com/beta/#/natanfudge/libs/working-scheduler?tab=overview) 5 | [![Latest Commit](https://img.shields.io/github/last-commit/natanfudge/working-scheduler)](https://github.com/natanfudge/working-scheduler/commits/master) 6 | 7 | Working Scheduler provides a working, fast, and comprehensive alternative to Minecraft's scheduler for Fabric mods. 8 | 9 | ## Introduction 10 | 11 | A scheduler simply provides a way to execute an operation after a certain amount of time has passed. 12 | In Minecraft that time is measured in ticks. 13 | ### What is the problem with the vanilla Minecraft scheduler? 14 | - It's slow: the more operations you schedule, the worse the tick overhead becomes. 15 | - It doesn't work properly: Minecraft tries to save the state of your scheduled actions when the world unloads, but fails miserably. 16 | As soon as the world will load again all scheduled actions will execute immediately, with no respect to the amount of time they have left. 17 | - It's limited: In Minecraft you can't cancel a scheduled action, you can't schedule in the client, only the server, 18 | you can't have it repeated multiple times, and you can't pass any state into the scheduled action other than a single BlockPos. 19 | This also makes it extremely difficult to schedule multiple different actions on a singular block. 20 | ### What about Working Scheduler? 21 | - It's fast: the tick overhead is constant no matter how many actions you schedule. 22 | - It works: your state will be saved properly. 23 | - It's comprehensive: You can can cancel schedules, you can schedule from the client, you can repeat the same action multiple times, 24 | you can pass arbitrary NBT data to the schedule action, and you can optionally pass a "schedule ID" which allows you to easily 25 | differentiate between different scheduled actions. 26 | 27 | ## Gradle setup 28 | Add `jcenter()` to your repositories if you haven't yet: 29 | ```groovy 30 | repositories { 31 | // [...] 32 | jcenter() 33 | } 34 | ``` 35 | Add the mod dependency: 36 | ```groovy 37 | dependencies { 38 | modImplementation("com.lettuce.fudge:working-scheduler:$total_version") 39 | } 40 | ``` 41 | ## Usage 42 | The first thing you need to do is implement `Scheduleable` on a block of your choosing. 43 | Generally this should be the same block will execute the schedule, but it can be whatever block you want. 44 | [Why do we need a Block?](https://github.com/natanfudge/Working-Scheduler#why-do-we-need-a-block-and-the-future) 45 | ```java 46 | public class ExampleScheduleableBlock extends Block implements Scheduleable { 47 | @Override 48 | public void onScheduleEnd(World world, BlockPos pos, int scheduleId, CompoundTag additionalData) { 49 | System.out.println("X amount of ticks have passed!"); 50 | } 51 | } 52 | ``` 53 | As an example we will schedule things when the player has right-clicked the block. 54 | To schedule an action we have separate idiomatic Java and Kotlin apis. 55 | (Note: Java API is usable from Kotlin as well but using the Kotlin-specific one is recommended) 56 |
57 | Java 58 | 59 | ```java 60 | public class ExampleScheduleableBlock extends Block implements Scheduleable { 61 | public void onScheduleEnd(World world, BlockPos pos, int scheduleId, CompoundTag additionalData) {/*...*/} 62 | 63 | @Override 64 | public boolean activate(BlockState blockState, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hitResult) { 65 | // Schedule the print statement action to occur after 30 ticks 66 | Scheduler.Builder(this, world).schedule(30); 67 | 68 | // Repeat the action 4 times with a 10 ticks interval 69 | Scheduler.Builder(this, world).repeat(4, 10); 70 | 71 | // Repeat the action for 100 ticks with a 15 ticks interval 72 | Scheduler.Builder(this, world).repeatFor(100, 15); 73 | 74 | return true; 75 | } 76 | } 77 | ``` 78 |
79 | 80 |
81 | Kotlin 82 | 83 | ```kotlin 84 | class ExampleScheduleableBlock(settings: Block.Settings) : Block(settings), Scheduleable { 85 | override fun onScheduleEnd(world: World, pos: BlockPos, scheduleId: Int, additionalData: CompoundTag) {/*...*/ } 86 | 87 | override fun activate(blockState: BlockState, world: World, pos: BlockPos, player: PlayerEntity, hand: Hand, hitResult: BlockHitResult): Boolean { 88 | // Schedule the print statement action to occur after 30 ticks 89 | BlockScheduler.schedule(ticksUntilEnd = 30, block = this, world = world) 90 | 91 | // Repeat the action 4 times with a 10 ticks interval 92 | BlockScheduler.repeat(repeatAmount = 4, tickInterval = 10, block = this, world = world) 93 | 94 | // Repeat the action for 100 ticks with a 15 ticks interval 95 | BlockScheduler.repeatFor(ticksUntilStop = 100, tickInterval = 15, block = this, world = world) 96 | 97 | return true 98 | } 99 | } 100 | ``` 101 | 102 |
103 | 104 | Full example projects can be seen [here](https://github.com/natanfudge/Working-Scheduler/tree/master/example/src/main). 105 | 106 | ### Attaching additional data 107 | Provide the data while scheduling: 108 | 109 |
110 | Java 111 | 112 | ```java 113 | public boolean activate(BlockState blockState, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hitResult) { 114 | if (player == null) return false; 115 | CompoundTag scheduleData = new CompoundTag(); 116 | scheduleData.putUuid("player", player.getUuid()); 117 | 118 | Scheduler.Builder(this, world) 119 | .scheduleId(1) 120 | .pos(pos) 121 | .additionalData(scheduleData) 122 | .schedule(100); 123 | 124 | Scheduler.Builder(this, world) 125 | .scheduleId(2) 126 | .pos(pos) 127 | .additionalData(scheduleData) 128 | .repeat(4,20); 129 | 130 | return true; 131 | } 132 | ``` 133 |
134 | 135 |
136 | Kotlin 137 | 138 | ```kotlin 139 | override fun activate( 140 | blockState: BlockState, 141 | world: World, 142 | pos: BlockPos, 143 | player: PlayerEntity?, 144 | hand: Hand?, 145 | hitResult: BlockHitResult? 146 | ): Boolean { 147 | val scheduleData = CompoundTag().apply { putUuid("player", player?.uuid ?: UUID(0, 0)) } 148 | 149 | BlockScheduler.schedule( 150 | ticksUntilEnd = 100, 151 | block = this, 152 | scheduleId = 1, 153 | world = world, 154 | blockPos = pos, 155 | additionalData = scheduleData 156 | ) 157 | 158 | BlockScheduler.repeat( 159 | repeatAmount = 4, 160 | tickInterval = 20, 161 | block = this, 162 | scheduleId = 2, 163 | world = world, 164 | blockPos = pos, 165 | additionalData = scheduleData 166 | ) 167 | 168 | return true 169 | } 170 | ``` 171 |
172 | 173 | And then use the data when the schedule ends: 174 | ```java 175 | public class ExampleScheduleableBlock extends Block implements Scheduleable { 176 | @Override 177 | public void onScheduleEnd(World world, BlockPos pos, int scheduleId, CompoundTag additionalData) { 178 | // Note: you should validate that the player exists and the additionalData was not tampered with. 179 | // No validation is done for the sake of simplicity. 180 | PlayerEntity player = world.getPlayerByUuid(additionalData.getUuid("player")); 181 | if (scheduleId == 1) { 182 | player.sendMessage(new LiteralText("Normal schedule ended at pos " + pos)); 183 | } else if(scheduleId == 2){ 184 | player.sendMessage(new LiteralText("Repeating schedule ended at pos " + pos)); 185 | } 186 | } 187 | } 188 | ``` 189 | Notice how we use the `scheduleId` to differentiate between different schedule calls! 190 | 191 | ### Cancelling 192 | Whenever you call a schedule, you will receive a `CancellationToken` instance. Simply call `cancel` on the same `world` to cancel: 193 | ```java 194 | public class ExampleScheduleableBlock extends Block implements Scheduleable { 195 | public void onScheduleEnd(World world, BlockPos pos, int scheduleId, CompoundTag additionalData) {/*...*/} 196 | 197 | CancellationToken cancellationToken; 198 | 199 | // First this happens... 200 | @Override 201 | public boolean activate(BlockState blockState, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hitResult) { 202 | cancellationToken = Scheduler.Builder(this, world).schedule(30); 203 | 204 | return true; 205 | } 206 | 207 | // And then later on... 208 | @Override 209 | public void onBlockRemoved(BlockState beforeState, World world, BlockPos pos, BlockState afterState, boolean bool) { 210 | // The scheduled action won't occur! 211 | if (cancellationToken != null) cancellationToken.cancel(world); 212 | } 213 | 214 | } 215 | ``` 216 | 217 | Note that usually you want your `CancellationToken` to persist between world loads, 218 | in which case you can store it in a block entity for example. 219 | A `CancellationTokenSerializer` exists specifically to make this easier using [Drawer](https://github.com/natanfudge/Fabric-Drawer). 220 | 221 | ## Dependencies 222 | Working Scheduler depends on [Kotlin](https://www.curseforge.com/minecraft/mc-mods/fabric-language-kotlin) and [Drawer](https://www.curseforge.com/minecraft/mc-mods/fabric-drawer). 223 | If you don't want to depend on them yourself, you can include them in your mod like so: 224 | ```groovy 225 | include("net.fabricmc:fabric-language-kotlin:$fabric_kotlin_version") 226 | include("com.lettuce.fudge:fabric-drawer:$drawer_version") 227 | ``` 228 | 229 | ## Why do we need a block, and the future 230 | Right now Scheduler requires you to implement an interface on a Block of yours. 231 | This is because the Block is already instantiated by yourself which helps avoid reflection. 232 | Additionally, scheduling is most commonly done from blocks. 233 | Theoretically, an API like that looks like this could exist: 234 | ```kotlin 235 | schedule(world, 10) { 236 | println("10 ticks have passed.") 237 | } 238 | ``` 239 | But that would require either reflection or a compiler plugin. If you think this is a good idea, feel free to make an issue. 240 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'fabric-loom' 3 | id 'maven-publish' 4 | id "org.jetbrains.kotlin.jvm" 5 | id "org.jetbrains.kotlin.plugin.serialization" 6 | id "com.jfrog.bintray" 7 | id 'com.matthewprenger.cursegradle' 8 | } 9 | 10 | def total_version = "$mod_version-$minecraft_version" 11 | 12 | 13 | sourceCompatibility = JavaVersion.VERSION_1_8 14 | targetCompatibility = JavaVersion.VERSION_1_8 15 | 16 | archivesBaseName = project.archives_base_name 17 | version = total_version 18 | group = project.maven_group 19 | 20 | minecraft { 21 | } 22 | 23 | 24 | repositories { 25 | maven { url = "http://maven.fabricmc.net/" } 26 | jcenter() 27 | } 28 | 29 | 30 | dependencies { 31 | //to change the versions see the gradle.properties file 32 | minecraft "com.mojang:minecraft:${project.minecraft_version}" 33 | mappings "net.fabricmc:yarn:${project.yarn_mappings}" 34 | modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" 35 | 36 | // Fabric API. This is technically optional, but you probably want it anyway. 37 | modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" 38 | 39 | modImplementation "net.fabricmc:fabric-language-kotlin:${project.fabric_kotlin_version}" 40 | modImplementation("com.lettuce.fudge:fabric-drawer:$drawer_version") 41 | 42 | // PSA: Some older mods, compiled on Loom 0.2.1, might have outdated Maven POMs. 43 | // You may need to force-disable transitiveness on them. 44 | } 45 | 46 | processResources { 47 | inputs.property "version", total_version 48 | 49 | from(sourceSets.main.resources.srcDirs) { 50 | include "fabric.mod.json" 51 | expand "version": project.version 52 | } 53 | 54 | from(sourceSets.main.resources.srcDirs) { 55 | exclude "fabric.mod.json" 56 | } 57 | } 58 | 59 | // ensure that the encoding is set to UTF-8, no matter what the system default is 60 | // this fixes some edge cases with special characters not displaying correctly 61 | // see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html 62 | tasks.withType(JavaCompile) { 63 | options.encoding = "UTF-8" 64 | } 65 | 66 | // Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task 67 | // if it is present. 68 | // If you remove this task, sources will not be generated. 69 | task sourcesJar(type: Jar, dependsOn: classes) { 70 | classifier = "sources" 71 | from sourceSets.main.allSource 72 | } 73 | 74 | jar { 75 | from "LICENSE" 76 | } 77 | 78 | def github_url = "https://github.com/$github_project" 79 | 80 | // configure the maven publication 81 | bintray { 82 | user = project.hasProperty('bintray_user') ? project.property('bintray_user') : "" 83 | key = project.hasProperty('bintray_api_key') ? project.property('bintray_api_key') : "" 84 | publications = ["MavenPublication"] 85 | publish = true //[Default: false] Whether version should be auto published after an upload 86 | pkg { 87 | repo = "libs" 88 | name = archives_base_name 89 | desc = description 90 | websiteUrl = github_url 91 | issueTrackerUrl = "$github_url/issues" 92 | githubRepo = github_project //Optional Github repository 93 | githubReleaseNotesFile = 'README.md' //Optional Github readme file 94 | licenses = [license] 95 | vcsUrl = github_url 96 | version { 97 | name = total_version 98 | released = new Date() 99 | } 100 | } 101 | 102 | } 103 | 104 | def pomConfig = { 105 | licenses { 106 | license { 107 | name project.license 108 | } 109 | } 110 | developers { 111 | developer { 112 | id "fudge" 113 | name "natan" 114 | email "natandestroyer100@gmail.com" 115 | } 116 | } 117 | 118 | scm { 119 | url github_url 120 | } 121 | } 122 | 123 | publishing { 124 | publications { 125 | MavenPublication(MavenPublication) { 126 | artifact(remapJar) { 127 | builtBy remapJar 128 | } 129 | artifact(kotlinSourcesJar) { 130 | classifier = "sources" 131 | builtBy remapSourcesJar 132 | } 133 | groupId project.maven_group 134 | artifactId project.archives_base_name 135 | version total_version 136 | pom.withXml { 137 | def root = asNode() 138 | root.appendNode('description', description) 139 | root.appendNode('name', archives_base_name) 140 | root.appendNode('url', github_url) 141 | root.children().last() + pomConfig 142 | } 143 | } 144 | } 145 | } 146 | 147 | curseforge { 148 | apiKey = project.hasProperty("curseforge_api_key") ? project.curseforge_api_key : "" 149 | project { 150 | id = curseforge_id 151 | releaseType = 'release' 152 | addGameVersion "Fabric" 153 | changelogType = "markdown" 154 | changelog = file("changelog.md") 155 | 156 | mainArtifact(remapJar) { 157 | displayName = "$display_name $total_version" 158 | } 159 | addArtifact sourcesJar 160 | 161 | afterEvaluate { 162 | mainArtifact(remapJar) 163 | uploadTask.dependsOn(remapJar) 164 | } 165 | } 166 | 167 | options { 168 | forgeGradleIntegration = false 169 | } 170 | } 171 | 172 | compileKotlin.kotlinOptions.jvmTarget = "1.8" 173 | 174 | Properties properties = new Properties() 175 | File propertiesFile = new File('gradle.properties') 176 | propertiesFile.withInputStream { 177 | properties.load(it) 178 | } 179 | properties.put("total_version",total_version) 180 | 181 | 182 | task updateTemplate(type: Copy) { 183 | group = "template" 184 | description = "Switches out gradle.properties variables like \$mod_version in .template.md files, and copies the results into .md files." 185 | from(rootDir) 186 | include("**/*.template.md") 187 | filesMatching("**/*.template.md") { 188 | def extensionLength = ".template.md".length() 189 | 190 | name = sourceName.substring(0, sourceName.length() - extensionLength) + ".md" 191 | expand(properties) 192 | } 193 | destinationDir = rootDir 194 | } 195 | 196 | bintrayUpload.dependsOn updateTemplate 197 | bintrayUpload.dependsOn "curseforge$curseforge_id" 198 | 199 | -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | # 1.0.0 2 | Released 3 | #1.1.1 4 | Allow later versions of Drawer. -------------------------------------------------------------------------------- /example/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Fudge 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # Working Scheduler Example Mod -------------------------------------------------------------------------------- /example/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'fabric-loom' 3 | id 'maven-publish' 4 | id "org.jetbrains.kotlin.jvm" 5 | } 6 | 7 | sourceCompatibility = JavaVersion.VERSION_1_8 8 | targetCompatibility = JavaVersion.VERSION_1_8 9 | 10 | archivesBaseName = project.archives_base_name 11 | version = project.mod_version 12 | group = project.maven_group 13 | 14 | minecraft { 15 | } 16 | 17 | 18 | repositories { 19 | maven { url = "http://maven.fabricmc.net/" } 20 | jcenter() 21 | mavenLocal() 22 | } 23 | 24 | 25 | dependencies { 26 | //to change the versions see the gradle.properties file 27 | minecraft "com.mojang:minecraft:${project.minecraft_version}" 28 | mappings "net.fabricmc:yarn:${project.yarn_mappings}" 29 | modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" 30 | 31 | // Fabric API. This is technically optional, but you probably want it anyway. 32 | modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" 33 | modImplementation "com.lettuce.fudge:working-scheduler:$scheduler_version" 34 | 35 | modImplementation "net.fabricmc:fabric-language-kotlin:${project.fabric_kotlin_version}" 36 | 37 | // PSA: Some older mods, compiled on Loom 0.2.1, might have outdated Maven POMs. 38 | // You may need to force-disable transitiveness on them. 39 | } 40 | 41 | processResources { 42 | inputs.property "version", project.mod_version 43 | 44 | from(sourceSets.main.resources.srcDirs) { 45 | include "fabric.mod.json" 46 | expand "version": project.version 47 | } 48 | 49 | from(sourceSets.main.resources.srcDirs) { 50 | exclude "fabric.mod.json" 51 | } 52 | } 53 | 54 | // ensure that the encoding is set to UTF-8, no matter what the system default is 55 | // this fixes some edge cases with special characters not displaying correctly 56 | // see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html 57 | tasks.withType(JavaCompile) { 58 | options.encoding = "UTF-8" 59 | } 60 | 61 | // Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task 62 | // if it is present. 63 | // If you remove this task, sources will not be generated. 64 | task sourcesJar(type: Jar, dependsOn: classes) { 65 | classifier = "sources" 66 | from sourceSets.main.allSource 67 | } 68 | 69 | jar { 70 | from "LICENSE" 71 | } 72 | 73 | // configure the maven publication 74 | publishing { 75 | publications { 76 | mavenJava(MavenPublication) { 77 | // add all the jars that should be included when publishing to maven 78 | artifact(remapJar) { 79 | builtBy remapJar 80 | } 81 | artifact(sourcesJar) { 82 | builtBy remapSourcesJar 83 | } 84 | } 85 | } 86 | 87 | // select the repositories you want to publish to 88 | repositories { 89 | // uncomment to publish to the local maven 90 | // mavenLocal() 91 | } 92 | } 93 | 94 | compileKotlin.kotlinOptions.jvmTarget = "1.8" -------------------------------------------------------------------------------- /example/changelog.md: -------------------------------------------------------------------------------- 1 | # 1.0.0 2 | Released 3 | -------------------------------------------------------------------------------- /example/gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official 2 | org.gradle.jvmargs=-Xmx1G 3 | 4 | # Fabric Properties 5 | minecraft_version=1.15-pre1 6 | yarn_mappings=1.15-pre1+build.3 7 | loader_version=0.7.1+build.173 8 | fabric_version=0.4.13+build.264-1.15 9 | # Changing this WILL affect the build, as defined in settings.gradle 10 | loom_version=0.2.6-SNAPSHOT 11 | 12 | # Mod Properties 13 | mod_version = 1.0.0 14 | maven_group = com.lettuce 15 | archives_base_name = example 16 | 17 | # Dependencies 18 | # Currently not on the main fabric site, check on the maven: https://maven.fabricmc.net/net/fabricmc/fabric-api/fabric-api 19 | scheduler_version=1.1.3-1.15-pre1 20 | 21 | # Kotlin 22 | # Changing this WILL affect the build, as defined in settings.gradle 23 | kotlin_version=1.3.60 24 | fabric_kotlin_version=1.3.60+build.1 -------------------------------------------------------------------------------- /example/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natanfudge/Working-Scheduler/3a937a33268419bb04ae9c9288d2382df359456d/example/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /example/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Jul 27 10:29:07 IDT 2019 2 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6-all.zip 3 | distributionBase=GRADLE_USER_HOME 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /example/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /example/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /example/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | jcenter() 4 | maven { 5 | name = 'Fabric' 6 | url = 'https://maven.fabricmc.net/' 7 | } 8 | gradlePluginPortal() 9 | } 10 | 11 | plugins { 12 | id 'fabric-loom' version loom_version 13 | id "org.jetbrains.kotlin.jvm" version kotlin_version 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /example/src/main/java/example/ExampleScheduleableBlock.java: -------------------------------------------------------------------------------- 1 | package example; 2 | 3 | import net.minecraft.block.Block; 4 | import net.minecraft.block.BlockState; 5 | import net.minecraft.entity.player.PlayerEntity; 6 | import net.minecraft.nbt.CompoundTag; 7 | import net.minecraft.text.LiteralText; 8 | import net.minecraft.util.Hand; 9 | import net.minecraft.util.hit.BlockHitResult; 10 | import net.minecraft.util.math.BlockPos; 11 | import net.minecraft.world.World; 12 | import scheduler.CancellationToken; 13 | import scheduler.Scheduleable; 14 | 15 | public class ExampleScheduleableBlock extends Block implements Scheduleable { 16 | @Override 17 | public void onScheduleEnd(World world, BlockPos pos, int scheduleId, CompoundTag additionalData) { 18 | // Note: you should validate that the player exists and the additionalData was not tampered with. 19 | // No validation is done for the sake of simplicity. 20 | PlayerEntity player = world.getPlayerByUuid(additionalData.getUuid("player")); 21 | if (scheduleId == 1) { 22 | player.sendMessage(new LiteralText("Normal schedule ended at pos " + pos)); 23 | } else if (scheduleId == 2) { 24 | player.sendMessage(new LiteralText("Repeating schedule ended at pos " + pos)); 25 | } 26 | } 27 | 28 | CancellationToken cancellationToken; 29 | 30 | @Override 31 | public void onBlockRemoved(BlockState beforeState, World world, BlockPos pos, BlockState afterState, boolean bool) { 32 | this.cancellationToken.cancel(world); 33 | } 34 | 35 | // override fun 36 | // 37 | // onBlockRemoved( 38 | // blockState_1:BlockState?, 39 | // world:World, 40 | // blockPos_1:BlockPos?, 41 | // blockState_2:BlockState?, 42 | // boolean_1:Boolean 43 | // ) { 44 | // // Note: will only cancel the server one 45 | // this.repeatForCancellationToken ?.cancel(world) 46 | // } 47 | 48 | 49 | public ExampleScheduleableBlock(Settings block$Settings_1) { 50 | super(block$Settings_1); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /example/src/main/java/example/ExampleUsageJava.java: -------------------------------------------------------------------------------- 1 | package example; 2 | 3 | import net.minecraft.block.BlockState; 4 | import net.minecraft.entity.player.PlayerEntity; 5 | import net.minecraft.nbt.CompoundTag; 6 | import net.minecraft.util.ActionResult; 7 | import net.minecraft.util.Hand; 8 | import net.minecraft.util.hit.BlockHitResult; 9 | import net.minecraft.util.math.BlockPos; 10 | import net.minecraft.world.World; 11 | import scheduler.Scheduler; 12 | import scheduler.SchedulerBuilder; 13 | 14 | import static example.ExampleUsageKt.SchedulingPlayerIdKey; 15 | 16 | 17 | public class ExampleUsageJava extends AbstractExampleBlock { 18 | @Override 19 | public ActionResult onUse(BlockState blockState_1, World world, BlockPos pos, PlayerEntity player, Hand hand_1, BlockHitResult blockHitResult_1) { 20 | CompoundTag scheduleData = new CompoundTag(); 21 | scheduleData.putUuid(SchedulingPlayerIdKey, player.getUuid()); 22 | 23 | Scheduler.Builder(this, world) 24 | .scheduleId(world.isClient ? ScheduleIds.Client.ScheduleExample : ScheduleIds.Server.ScheduleExample) 25 | .pos(pos) 26 | .additionalData(scheduleData) 27 | .schedule(100); 28 | 29 | Scheduler.Builder(this, world) 30 | .scheduleId(world.isClient ? ScheduleIds.Client.RepeatExample : ScheduleIds.Server.RepeatExample) 31 | .pos(pos) 32 | .additionalData(scheduleData) 33 | .repeat(5, 5); 34 | 35 | this.setRepeatForCancellationToken(Scheduler.Builder(this, world) 36 | .scheduleId(world.isClient ? ScheduleIds.Client.RepeatForExample : ScheduleIds.Server.RepeatForExample) 37 | .pos(pos) 38 | .additionalData(scheduleData) 39 | .repeatFor(500, 15)); 40 | 41 | return ActionResult.SUCCESS; 42 | } 43 | 44 | } 45 | 46 | 47 | -------------------------------------------------------------------------------- /example/src/main/kotlin/example/ExampleUsage.kt: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | import kotlinx.serialization.Serializable 4 | import net.fabricmc.fabric.api.block.FabricBlockSettings 5 | import net.minecraft.block.Block 6 | import net.minecraft.block.BlockState 7 | import net.minecraft.block.Material 8 | import net.minecraft.entity.player.PlayerEntity 9 | import net.minecraft.item.BlockItem 10 | import net.minecraft.item.Item 11 | import net.minecraft.item.ItemGroup 12 | import net.minecraft.nbt.CompoundTag 13 | import net.minecraft.text.LiteralText 14 | import net.minecraft.util.ActionResult 15 | import net.minecraft.util.Hand 16 | import net.minecraft.util.Identifier 17 | import net.minecraft.util.hit.BlockHitResult 18 | import net.minecraft.util.math.BlockPos 19 | import net.minecraft.util.registry.Registry 20 | import net.minecraft.world.World 21 | import scheduler.BlockScheduler 22 | import scheduler.CancellationToken 23 | import scheduler.Scheduleable 24 | import java.util.* 25 | 26 | const val ModId = "example_usage" 27 | val JavaBlock = ExampleUsageJava() 28 | fun init() { 29 | Registry.register(Registry.BLOCK, Identifier(ModId, "example_block"), ExampleBlock) 30 | Registry.register( 31 | Registry.ITEM, Identifier(ModId, "example_block"), BlockItem( 32 | ExampleBlock, Item.Settings().group( 33 | ItemGroup.MISC 34 | ) 35 | ) 36 | ) 37 | 38 | Registry.register(Registry.BLOCK, Identifier(ModId, "example_block_java"), JavaBlock) 39 | Registry.register( 40 | Registry.ITEM, Identifier(ModId, "example_block_java"), BlockItem( 41 | JavaBlock, Item.Settings().group( 42 | ItemGroup.MISC 43 | ) 44 | ) 45 | ) 46 | } 47 | 48 | 49 | const val SchedulingPlayerIdKey = "player" 50 | 51 | abstract class AbstractExampleBlock : Block(FabricBlockSettings.of(Material.STONE).build()), Scheduleable { 52 | override fun onScheduleEnd(world: World, pos: BlockPos, scheduleId: Int, additionalData: CompoundTag) { 53 | val player = getSchedulingPlayer(additionalData, world) ?: return 54 | val messageToSend = when (scheduleId) { 55 | ScheduleIds.Client.ScheduleExample -> "Schedule ended on client" 56 | ScheduleIds.Client.RepeatExample -> "Repeat for an amount has been repeated on client" 57 | ScheduleIds.Client.RepeatForExample -> "Repeat Until has been repeated on client" 58 | ScheduleIds.Server.ScheduleExample -> "Schedule ended on server" 59 | ScheduleIds.Server.RepeatExample -> "Repeat for an amount has been repeated on server" 60 | ScheduleIds.Server.RepeatForExample -> "Repeat Until has been repeated on server" 61 | else -> "Unexpected schedule id" 62 | } 63 | 64 | player.sendMessage(LiteralText("$messageToSend at pos $pos.")) 65 | 66 | when (scheduleId) { 67 | ScheduleIds.Client.ScheduleExample, ScheduleIds.Client.RepeatExample, ScheduleIds.Client.RepeatForExample -> { 68 | assert(world.isClient) 69 | } 70 | else -> assert(!world.isClient) 71 | } 72 | 73 | } 74 | 75 | private fun getSchedulingPlayer(additionalData: CompoundTag, world: World): PlayerEntity? { 76 | val playerId = additionalData.getUuid(SchedulingPlayerIdKey) 77 | if (playerId == UUID(0, 0)) { 78 | println("Warning: the player information was corrupted.") 79 | return null 80 | } 81 | val player = world.getPlayerByUuid(playerId) 82 | if (player == null) { 83 | println("Warning: player that scheduled no longer in the world.") 84 | } 85 | return player 86 | } 87 | 88 | override fun onBlockRemoved( 89 | blockState_1: BlockState?, 90 | world: World, 91 | blockPos_1: BlockPos?, 92 | blockState_2: BlockState?, 93 | boolean_1: Boolean 94 | ) { 95 | // Note: will only cancel the server one 96 | this.repeatForCancellationToken?.cancel(world) 97 | } 98 | 99 | 100 | object ScheduleIds { 101 | object Client { 102 | const val ScheduleExample = 1 103 | const val RepeatExample = 2 104 | const val RepeatForExample = 3 105 | } 106 | 107 | object Server { 108 | const val ScheduleExample = 4 109 | const val RepeatExample = 5 110 | const val RepeatForExample = 6 111 | } 112 | } 113 | 114 | var repeatForCancellationToken: CancellationToken? = null 115 | } 116 | 117 | 118 | object ExampleBlock : AbstractExampleBlock() { 119 | override fun onUse( 120 | blockState: BlockState, 121 | world: World, 122 | pos: BlockPos, 123 | player: PlayerEntity?, 124 | hand: Hand?, 125 | hitResult: BlockHitResult? 126 | ): ActionResult { 127 | val scheduleData = CompoundTag().apply { 128 | putUuid(SchedulingPlayerIdKey, player?.uuid ?: UUID(0, 0)) 129 | } 130 | BlockScheduler.schedule( 131 | ticksUntilEnd = 100, 132 | block = this, 133 | scheduleId = if (world.isClient) ScheduleIds.Client.ScheduleExample else ScheduleIds.Server.ScheduleExample, 134 | world = world, 135 | blockPos = pos, 136 | additionalData = scheduleData 137 | ) 138 | 139 | BlockScheduler.repeat( 140 | tickInterval = 5, 141 | repeatAmount = 5, 142 | block = this, 143 | scheduleId = if (world.isClient) ScheduleIds.Client.RepeatExample else ScheduleIds.Server.RepeatExample, 144 | world = world, 145 | blockPos = pos, 146 | additionalData = scheduleData 147 | 148 | ) 149 | 150 | repeatForCancellationToken = BlockScheduler.repeatFor( 151 | tickInterval = 15, 152 | ticksUntilStop = 500, 153 | block = this, 154 | scheduleId = if (world.isClient) ScheduleIds.Client.RepeatForExample else ScheduleIds.Server.RepeatForExample, 155 | world = world, 156 | blockPos = pos, 157 | additionalData = scheduleData 158 | ) 159 | 160 | 161 | return ActionResult.SUCCESS 162 | } 163 | } 164 | 165 | -------------------------------------------------------------------------------- /example/src/main/resources/fabric.mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": 1, 3 | "id": "working-scheduler-example", 4 | "version": "${version}", 5 | 6 | "name": "Working Scheduler Example", 7 | "description": "example of Working Scheduler usage", 8 | "authors": [ 9 | "Fudge" 10 | ], 11 | "contact": { 12 | "homepage": "https://github.com/natanfudge/Working-Scheduler", 13 | "sources": "https://github.com/natanfudge/Working-Scheduler" 14 | }, 15 | 16 | "license": "The MIT License", 17 | "environment": "*", 18 | "entrypoints": { 19 | "main": [ 20 | "example.ExampleUsageKt::init" 21 | ] 22 | }, 23 | "depends": { 24 | "fabricloader": ">=0.4.0", 25 | "fabric": "*", 26 | "fabric-language-kotlin": "*", 27 | "fabricdrawer": ">=3.1.0-1.15-pre1" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official 2 | org.gradle.jvmargs=-Xmx1G 3 | 4 | # Fabric Properties 5 | minecraft_version=20w19a 6 | yarn_mappings=20w19a+build.6 7 | loader_version=0.8.2+build.194 8 | 9 | #Fabric api 10 | fabric_version=0.10.7+build.344-1.16 11 | # Changing this WILL affect the build, as defined in settings.gradle 12 | loom_version=0.2.7-SNAPSHOT 13 | 14 | # Mod Properties 15 | mod_version=1.1.4 16 | maven_group=com.lettuce.fudge 17 | archives_base_name=working-scheduler 18 | display_name=Working Scheduler 19 | description=Provides a working, fast, and comprehensive alternative to the Minecraft scheduler for Fabric mods. 20 | github_project=natanfudge/Working-Scheduler 21 | license=The MIT License 22 | bintray_gradle_version=1.8.3 23 | cursegradle_version=1.4.0 24 | curseforge_id=340964 25 | 26 | # Dependencies 27 | # Currently not on the main fabric site, check on the maven: https://maven.fabricmc.net/net/fabricmc/fabric-api/fabric-api 28 | drawer_version=3.2.1-20w19a 29 | 30 | # Kotlin 31 | # Changing this WILL affect the build, as defined in settings.gradle 32 | kotlin_version=1.3.60 33 | fabric_kotlin_version=1.3.60+build.1 -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natanfudge/Working-Scheduler/3a937a33268419bb04ae9c9288d2382df359456d/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Jul 27 10:29:07 IDT 2019 2 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6-all.zip 3 | distributionBase=GRADLE_USER_HOME 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natanfudge/Working-Scheduler/3a937a33268419bb04ae9c9288d2382df359456d/logo.png -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | mavenLocal() 4 | jcenter() 5 | maven { 6 | name = 'Fabric' 7 | url = 'https://maven.fabricmc.net/' 8 | } 9 | gradlePluginPortal() 10 | } 11 | 12 | plugins { 13 | id 'fabric-loom' version loom_version 14 | id "org.jetbrains.kotlin.jvm" version kotlin_version 15 | id "com.jfrog.bintray" version bintray_gradle_version 16 | id 'com.matthewprenger.cursegradle' version cursegradle_version 17 | id "org.jetbrains.kotlin.plugin.serialization" version kotlin_version 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/scheduler/Scheduleable.java: -------------------------------------------------------------------------------- 1 | package scheduler; 2 | 3 | import net.minecraft.nbt.CompoundTag; 4 | import net.minecraft.util.math.BlockPos; 5 | import net.minecraft.world.World; 6 | 7 | /** 8 | * Implement this on one of your Blocks and call a schedule method on it. 9 | */ 10 | public interface Scheduleable { 11 | /** 12 | * All of these parameters are passed by yourself in the scheduling call 13 | */ 14 | void onScheduleEnd(World world, BlockPos pos, int scheduleId, CompoundTag additionalData); 15 | } -------------------------------------------------------------------------------- /src/main/java/scheduler/Scheduler.java: -------------------------------------------------------------------------------- 1 | package scheduler; 2 | 3 | import net.minecraft.block.Block; 4 | import net.minecraft.world.World; 5 | 6 | @SuppressWarnings("unused") 7 | public class Scheduler { 8 | /** 9 | * Must be called first for any scheduling call. Follow up by chaining methods from SchedulerBuilder. 10 | */ 11 | public static SchedulerBuilder Builder(T scheduleable, World world) { 12 | return new SchedulerBuilder<>(scheduleable, world); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/scheduler/SchedulerBuilder.java: -------------------------------------------------------------------------------- 1 | package scheduler; 2 | 3 | 4 | import net.minecraft.block.Block; 5 | import net.minecraft.nbt.CompoundTag; 6 | import net.minecraft.util.math.BlockPos; 7 | import net.minecraft.world.World; 8 | 9 | @SuppressWarnings({"unused"}) 10 | public class SchedulerBuilder { 11 | private T scheduleable; 12 | private World world; 13 | private int scheduleId = 0; 14 | private BlockPos blockPos = BlockPos.ORIGIN; 15 | private CompoundTag additionalData = new CompoundTag(); 16 | 17 | SchedulerBuilder(T scheduleable, World world) { 18 | this.scheduleable = scheduleable; 19 | this.world = world; 20 | } 21 | 22 | /** 23 | * Pass an integer to onScheduleEnd. Useful for differentiating between different scheduling calls. 24 | */ 25 | public SchedulerBuilder scheduleId(int id) { 26 | this.scheduleId = id; 27 | return this; 28 | } 29 | 30 | /** 31 | * Pass a BlockPos to onScheduleEnd. 32 | */ 33 | public SchedulerBuilder pos(BlockPos pos) { 34 | this.blockPos = pos; 35 | return this; 36 | } 37 | 38 | /** 39 | * Pass a CompoundTag to onScheduleEnd. 40 | */ 41 | public SchedulerBuilder additionalData(CompoundTag tag) { 42 | this.additionalData = tag; 43 | return this; 44 | } 45 | 46 | /** 47 | * Execute onScheduleEnd with the specified configuration after the specified amount of ticks. 48 | */ 49 | public CancellationToken schedule(int ticksUntilEnd) { 50 | return BlockScheduler.INSTANCE.schedule(ticksUntilEnd, scheduleable, world, scheduleId, blockPos, additionalData); 51 | } 52 | 53 | /** 54 | * Execute onScheduleEnd multiple times, by specifying how many times it will repeat. 55 | */ 56 | public CancellationToken repeat(int repeatAmount, int tickInterval) { 57 | return BlockScheduler.INSTANCE.repeat(tickInterval, repeatAmount, scheduleable, world, scheduleId, blockPos, additionalData); 58 | } 59 | 60 | 61 | /** 62 | * Execute onScheduleEnd multiple times, by specifying in how much time the repeating will end. 63 | */ 64 | public CancellationToken repeatFor(int ticksUntilEnd, int tickInterval) { 65 | return BlockScheduler.INSTANCE.repeatFor(ticksUntilEnd, tickInterval, scheduleable, world, scheduleId, blockPos, additionalData); 66 | } 67 | 68 | } 69 | 70 | 71 | -------------------------------------------------------------------------------- /src/main/kotlin/scheduler/SchedulerKt.kt: -------------------------------------------------------------------------------- 1 | @file:UseSerializers(ForUuid::class) 2 | @file:Suppress("RedundantVisibilityModifier", "unused", "MemberVisibilityCanBePrivate") 3 | 4 | package scheduler 5 | 6 | import drawer.ForUuid 7 | import kotlinx.serialization.Serializable 8 | import kotlinx.serialization.Serializer 9 | import kotlinx.serialization.UseSerializers 10 | import net.minecraft.block.Block 11 | import net.minecraft.nbt.CompoundTag 12 | import net.minecraft.server.world.ServerWorld 13 | import net.minecraft.util.math.BlockPos 14 | import net.minecraft.world.World 15 | import scheduler.internal.* 16 | import scheduler.internal.util.isServer 17 | import scheduler.internal.util.sendPacketToServer 18 | import java.util.* 19 | 20 | public object BlockScheduler { 21 | /** 22 | * Calls the [block]'s onScheduleEnd method after [ticksUntilEnd] ticks. 23 | * All other parameters of this function will simply reappear in the [Scheduleable.onScheduleEnd] for you to use. 24 | * 25 | * @param scheduleId is useful for differentiating between different schedules. 26 | */ 27 | public fun schedule( 28 | ticksUntilEnd: Int, 29 | block: T, world: World, 30 | scheduleId: Int = 0, 31 | blockPos: BlockPos = BlockPos.ORIGIN, 32 | additionalData: CompoundTag = CompoundTag() 33 | ): CancellationToken 34 | where T : Scheduleable, T : Block = schedule( 35 | block, world, scheduleId, blockPos, additionalData, repetition = Repetition.Once(world.time + ticksUntilEnd) 36 | ) 37 | 38 | 39 | /** 40 | * Calls the [block]'s [repeatAmount] times, with an interval of [tickInterval] ticks. 41 | * All other parameters of this function will simply reappear in the [Scheduleable.onScheduleEnd] for you to use. 42 | * 43 | * @param scheduleId is useful for differentiating between different schedules. 44 | */ 45 | public fun repeat( 46 | repeatAmount: Int, 47 | tickInterval: Int, 48 | block: T, world: World, 49 | scheduleId: Int = 0, 50 | blockPos: BlockPos = BlockPos.ORIGIN, 51 | additionalData: CompoundTag = CompoundTag() 52 | ): CancellationToken 53 | where T : Scheduleable, T : Block = schedule( 54 | block, world, scheduleId, blockPos, additionalData, 55 | repetition = Repetition.RepeatAmount( 56 | repeatInterval = tickInterval, amountLeft = repeatAmount, nextTickTime = world.time + tickInterval 57 | ) 58 | ) 59 | 60 | /** 61 | * Calls the [block]'s onScheduleEnd method every [tickInterval] ticks, for [ticksUntilStop] ticks. 62 | * All other parameters of this function will simply reappear in the [Scheduleable.onScheduleEnd] for you to use. 63 | * 64 | * @param scheduleId is useful for differentiating between different schedules. 65 | */ 66 | public fun repeatFor( 67 | ticksUntilStop: Int, 68 | tickInterval: Int, 69 | block: T, world: World, 70 | scheduleId: Int = 0, 71 | blockPos: BlockPos = BlockPos.ORIGIN, 72 | additionalData: CompoundTag = CompoundTag() 73 | ): CancellationToken 74 | where T : Scheduleable, T : Block = schedule( 75 | block, world, scheduleId, blockPos, additionalData, 76 | repetition = Repetition.RepeatUntil( 77 | repeatInterval = tickInterval, 78 | nextTickTime = world.time + tickInterval, 79 | stopTime = world.time + ticksUntilStop 80 | ) 81 | ) 82 | 83 | 84 | } 85 | 86 | 87 | /** 88 | * Use file:UseSerializers(CancellationTokenSerializer::class) 89 | * to serialize CancellationTokens that are nested in a @Serializable class, 90 | * And use CancellationTokenSerializer.put() / CancellationTokenSerializer.getFrom() to serialize a singular CancellationToken. 91 | * 92 | * (This is because of a loom bug) 93 | */ 94 | @Serializer(forClass = CancellationToken::class) 95 | public object CancellationTokenSerializer 96 | 97 | /** 98 | * Returned by every scheduling call. Call [cancel] to cancel the scheduled action. 99 | * Usually this needs to be stored, by a BlockEntity for example. [CancellationTokenSerializer] is useful for this if you use Drawer. 100 | */ 101 | @Serializable 102 | public data class CancellationToken( 103 | /** 104 | * To correctly identify the scheduled action 105 | */ 106 | private val cancellationUUID: UUID 107 | ) { 108 | /** 109 | * Will cancel the scheduling call. Make sure to call on the same [World]. 110 | */ 111 | public fun cancel(world: World) { 112 | if (world.isServer && world is ServerWorld) { 113 | cancelScheduleServer(world, cancellationUUID) 114 | } else if (world.isClient) { 115 | sendPacketToServer(CancelTickingInServerPacket(cancellationUUID)) 116 | } else { 117 | logWarning("Attempt to cancel a schedule in a world that is ClientWorld but with isClient = false. ") 118 | } 119 | } 120 | } 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /src/main/kotlin/scheduler/internal/Scheduler.kt: -------------------------------------------------------------------------------- 1 | package scheduler.internal 2 | 3 | import net.minecraft.block.Block 4 | import net.minecraft.nbt.CompoundTag 5 | import net.minecraft.server.world.ServerWorld 6 | import net.minecraft.util.math.BlockPos 7 | import net.minecraft.util.registry.Registry 8 | import net.minecraft.world.World 9 | import scheduler.CancellationToken 10 | import scheduler.Scheduleable 11 | import scheduler.internal.util.getMinecraftClient 12 | import scheduler.internal.util.isServer 13 | import scheduler.internal.util.sendPacketToServer 14 | import java.util.* 15 | 16 | internal fun schedule( 17 | block: T, 18 | world: World, 19 | scheduleId: Int, 20 | blockPos: BlockPos, 21 | additionalData: CompoundTag, 22 | repetition: Repetition 23 | ): CancellationToken 24 | where T : Scheduleable, T : Block { 25 | 26 | 27 | val clientToSendTo = if (world.isServer && world is ServerWorld) { 28 | null 29 | } else if (world.isClient) { 30 | getMinecraftClient().player!!.uuid 31 | } else { 32 | logWarning( 33 | "Attempt to schedule in a world that is ClientWorld but with isClient = false. " + 34 | "You might get a ClassNotFound exception here!" 35 | ) 36 | getMinecraftClient().player!!.uuid 37 | } 38 | 39 | val cancellationUUID = UUID.randomUUID() 40 | 41 | val schedule = Schedule( 42 | repetition = repetition, 43 | context = ScheduleContext( 44 | blockId = Registry.BLOCK.getId(block), 45 | blockPos = blockPos, 46 | scheduleId = scheduleId, 47 | additionalData = additionalData 48 | ), 49 | clientRequestingSchedule = clientToSendTo, 50 | cancellationUUID = cancellationUUID 51 | ) 52 | 53 | if (clientToSendTo != null) { 54 | sendPacketToServer(TickInServerPacket(schedule)) 55 | } else { 56 | scheduleServer(world as ServerWorld, schedule, block) 57 | } 58 | 59 | return CancellationToken(cancellationUUID) 60 | 61 | 62 | } 63 | 64 | internal fun scheduleServer(world: ServerWorld, schedule: Schedule, schedulingBlock: Scheduleable) { 65 | val tickState = world.persistentStateManager.getOrCreate(SchedulerId) { TickerState() } 66 | tickState.add(schedule.apply { scheduleable = schedulingBlock }) 67 | tickState.isDirty = true 68 | } 69 | 70 | internal fun cancelScheduleServer(world: ServerWorld, cancellationUUID: UUID) = world.persistentStateManager 71 | .getOrCreate(SchedulerId) { TickerState() } 72 | .cancel(cancellationUUID) 73 | 74 | 75 | -------------------------------------------------------------------------------- /src/main/kotlin/scheduler/internal/TickerPackets.kt: -------------------------------------------------------------------------------- 1 | @file:UseSerializers(ForIdentifier::class, ForUuid::class) 2 | 3 | package scheduler.internal 4 | 5 | import drawer.ForIdentifier 6 | import drawer.ForUuid 7 | import kotlinx.serialization.Serializable 8 | import kotlinx.serialization.Transient 9 | import kotlinx.serialization.UseSerializers 10 | import net.fabricmc.fabric.api.network.PacketContext 11 | import net.minecraft.server.world.ServerWorld 12 | import scheduler.internal.util.InternalC2SPacket 13 | import scheduler.internal.util.InternalS2CPacket 14 | import scheduler.internal.util.world 15 | import java.util.* 16 | 17 | internal interface C2SPacket> : InternalC2SPacket { 18 | override val modId get() = ModId 19 | } 20 | 21 | internal interface S2CPacket> : InternalS2CPacket { 22 | override val modId get() = ModId 23 | } 24 | 25 | @Serializable 26 | internal data class TickInServerPacket(val schedule: Schedule) : C2SPacket { 27 | override fun use(context: PacketContext) { 28 | if (context.world !is ServerWorld || context.world.isClient) { 29 | logWarning("A packet to the server is somehow not in a server world.") 30 | return 31 | } 32 | val scheduleable = getScheduleableFromRegistry(schedule.context.blockId) ?: return 33 | scheduleServer(context.world as ServerWorld, schedule, scheduleable) 34 | 35 | } 36 | 37 | @Transient 38 | override val serializer = serializer() 39 | 40 | } 41 | 42 | 43 | @Serializable 44 | internal data class FinishScheduleInClientPacket(val scheduleContext: ScheduleContext) : 45 | S2CPacket { 46 | override fun use(context: PacketContext) { 47 | val scheduleable = getScheduleableFromRegistry(scheduleContext.blockId) ?: return 48 | scheduleable.onScheduleEnd( 49 | context.world, 50 | scheduleContext.blockPos, 51 | scheduleContext.scheduleId, 52 | scheduleContext.additionalData 53 | ) 54 | } 55 | 56 | @Transient 57 | override val serializer = serializer() 58 | } 59 | 60 | @Serializable 61 | internal data class CancelTickingInServerPacket(val cancellationUUID: UUID) : C2SPacket { 62 | override fun use(context: PacketContext) { 63 | if (context.world !is ServerWorld || context.world.isClient) { 64 | logWarning("A packet to the server is somehow not in a server world.") 65 | return 66 | } 67 | cancelScheduleServer(context.world as ServerWorld, cancellationUUID) 68 | } 69 | 70 | @Transient 71 | override val serializer = serializer() 72 | } 73 | 74 | -------------------------------------------------------------------------------- /src/main/kotlin/scheduler/internal/TickerState.kt: -------------------------------------------------------------------------------- 1 | @file:UseSerializers(ForIdentifier::class, ForBlockPos::class, ForUuid::class, ForCompoundTag::class) 2 | 3 | package scheduler.internal 4 | 5 | import drawer.* 6 | import kotlinx.serialization.Serializable 7 | import kotlinx.serialization.Transient 8 | import kotlinx.serialization.UseSerializers 9 | import kotlinx.serialization.list 10 | import net.minecraft.block.AbstractBlock 11 | import net.minecraft.block.Block 12 | import net.minecraft.block.Material 13 | import net.minecraft.nbt.CompoundTag 14 | import net.minecraft.util.Identifier 15 | import net.minecraft.util.math.BlockPos 16 | import net.minecraft.util.registry.Registry 17 | import net.minecraft.world.PersistentState 18 | import net.minecraft.world.World 19 | import scheduler.Scheduleable 20 | import java.util.* 21 | import kotlin.Comparator 22 | 23 | @Serializable 24 | internal data class Schedule( 25 | val context: ScheduleContext = ScheduleContext(), 26 | val repetition: Repetition = Repetition.Once(), 27 | // If if not null, a packet will be sent to client and the callback will be executed there. 28 | // Otherwise, It will just execute on the server. 29 | val clientRequestingSchedule: UUID? = null, 30 | val cancellationUUID: UUID = UUID.randomUUID() 31 | ) { 32 | @Transient 33 | lateinit var scheduleable: Scheduleable 34 | } 35 | 36 | @Serializable 37 | internal sealed class Repetition { 38 | abstract var nextTickTime: Long 39 | 40 | @Serializable 41 | data class RepeatAmount(override var nextTickTime: Long = 0, val repeatInterval: Int = 1, var amountLeft: Int = 1) : 42 | Repetition() 43 | 44 | @Serializable 45 | data class RepeatUntil(override var nextTickTime: Long = 0, val repeatInterval: Int = 1, val stopTime: Long = 0) : 46 | Repetition() 47 | 48 | @Serializable 49 | data class Once(override var nextTickTime: Long = 0) : Repetition() 50 | } 51 | 52 | @Serializable 53 | internal data class ScheduleContext( 54 | val blockPos: BlockPos = BlockPos.ORIGIN, val scheduleId: Int = 0, 55 | val blockId: Identifier = Identifier("minecraft:air"), 56 | val additionalData: CompoundTag = CompoundTag() 57 | ) 58 | 59 | 60 | internal const val SchedulerId = "working_scheduler" 61 | 62 | internal fun getScheduleableFromRegistry(scheduleableBlockId: Identifier): Scheduleable? { 63 | val block = Registry.BLOCK[scheduleableBlockId] 64 | if (block == Registry.BLOCK.defaultId) { 65 | logWarning("Block with id '$scheduleableBlockId' no longer exists.") 66 | return null 67 | } 68 | if (block !is Scheduleable) { 69 | logWarning("Block $block (id = $scheduleableBlockId) no longer implements Scheduleable.") 70 | return null 71 | } 72 | return block 73 | } 74 | 75 | internal class TickerState : PersistentState(SchedulerId) { 76 | 77 | 78 | private val tickers = 79 | PriorityQueue(Comparator { a, b -> (a.repetition.nextTickTime - b.repetition.nextTickTime).toInt() }) 80 | 81 | override fun toTag(tag: CompoundTag): CompoundTag = tag 82 | .also { Schedule.serializer().list.put(tickers.toList(), it) } 83 | 84 | fun add(ticker: Schedule) { 85 | tickers.add(ticker) 86 | } 87 | 88 | val closestToEnd: Schedule get() = tickers.peek() 89 | val hasAnyTickers: Boolean get() = tickers.isNotEmpty() 90 | fun removeClosestToEnd(): Schedule? = tickers.poll() 91 | fun cancel(cancellationUUID: UUID): Boolean = tickers.removeIf { it.cancellationUUID == cancellationUUID } 92 | 93 | 94 | override fun fromTag(tag: CompoundTag) = Schedule.serializer().list.getFrom(tag) 95 | .let { 96 | for (storedTicker in it) { 97 | val registryScheduleable = getScheduleableFromRegistry(storedTicker.context.blockId) 98 | ?: continue 99 | tickers.add(storedTicker.apply { scheduleable = registryScheduleable }) 100 | } 101 | } 102 | 103 | } 104 | 105 | -------------------------------------------------------------------------------- /src/main/kotlin/scheduler/internal/Util.kt: -------------------------------------------------------------------------------- 1 | package scheduler.internal 2 | 3 | import net.minecraft.world.PersistentState 4 | import net.minecraft.world.PersistentStateManager 5 | import org.apache.logging.log4j.LogManager 6 | 7 | internal fun PersistentStateManager.getOrCreate(id: String, creator: () -> T): T = 8 | getOrCreate(creator, id) 9 | 10 | private val Logger = LogManager.getLogger("Working Ticker") 11 | 12 | internal fun logWarning(warning: String) = Logger.warn(warning) 13 | -------------------------------------------------------------------------------- /src/main/kotlin/scheduler/internal/WorkingScheduler.kt: -------------------------------------------------------------------------------- 1 | package scheduler.internal 2 | 3 | import net.fabricmc.fabric.api.event.world.WorldTickCallback 4 | import net.minecraft.server.world.ServerWorld 5 | import scheduler.internal.util.* 6 | 7 | internal const val ModId = "working-scheduler" 8 | 9 | @Suppress("unused") 10 | internal fun init() = initCommon(ModId) { 11 | WorldTickCallback.EVENT.register(WorldTickCallback { if (it is ServerWorld && it.isServer) worldTick(it) }) 12 | registerC2S(TickInServerPacket.serializer()) 13 | registerC2S(CancelTickingInServerPacket.serializer()) 14 | 15 | } 16 | 17 | 18 | @Suppress("unused") 19 | internal fun initClient() = initClientOnly(ModId) { 20 | registerS2C(FinishScheduleInClientPacket.serializer()) 21 | } 22 | 23 | -------------------------------------------------------------------------------- /src/main/kotlin/scheduler/internal/WorldTick.kt: -------------------------------------------------------------------------------- 1 | package scheduler.internal 2 | 3 | import net.minecraft.server.world.ServerWorld 4 | import scheduler.internal.util.sendPacket 5 | 6 | // Occurs every tick 7 | internal fun worldTick(world: ServerWorld) { 8 | val worldTickers = world.persistentStateManager.getOrCreate(SchedulerId) { TickerState() } 9 | while (worldTickers.hasAnyTickers) { 10 | val top = worldTickers.closestToEnd 11 | if (world.time >= top.repetition.nextTickTime) { 12 | if (top.clientRequestingSchedule != null) { 13 | // If the player no longer exists then tough luck for him! He missed the schedule. 14 | world.getPlayerByUuid(top.clientRequestingSchedule) 15 | ?.sendPacket(FinishScheduleInClientPacket(top.context)) 16 | } else { 17 | top.scheduleable.onScheduleEnd( 18 | world, 19 | top.context.blockPos, 20 | top.context.scheduleId, 21 | top.context.additionalData 22 | ) 23 | } 24 | 25 | removeIfNeeded(top, world, worldTickers) 26 | } else { 27 | break 28 | } 29 | 30 | } 31 | 32 | } 33 | 34 | private fun removeIfNeeded(top: Schedule, world: ServerWorld, worldTickers: TickerState) { 35 | var addBack = true 36 | when (val repetition = top.repetition) { 37 | is Repetition.RepeatAmount -> { 38 | repetition.amountLeft-- 39 | repetition.nextTickTime = world.time + repetition.repeatInterval 40 | 41 | if (repetition.amountLeft <= 0) addBack = false 42 | } 43 | is Repetition.RepeatUntil -> { 44 | if (world.time < repetition.stopTime) { 45 | repetition.nextTickTime = world.time + repetition.repeatInterval 46 | } else addBack = false 47 | } 48 | is Repetition.Once -> addBack = false 49 | } 50 | 51 | // We remove and add back in the case we want it to keep ticking, so it gets put in the right place. 52 | worldTickers.removeClosestToEnd() 53 | if (addBack) worldTickers.add(top) 54 | } -------------------------------------------------------------------------------- /src/main/kotlin/scheduler/internal/util/ModInit.kt: -------------------------------------------------------------------------------- 1 | package scheduler.internal.util 2 | 3 | import net.minecraft.client.MinecraftClient 4 | import net.minecraft.world.World 5 | 6 | internal fun getMinecraftClient(): MinecraftClient = MinecraftClient.getInstance() 7 | internal val World.isServer get() = !isClient 8 | /** 9 | * Should be called at the init method of the mod. Do all of your registry here. 10 | */ 11 | internal inline fun initCommon(modId: String, init: CommonModInitializationContext.() -> Unit) { 12 | CommonModInitializationContext(modId).init() 13 | } 14 | 15 | 16 | internal inline fun initClientOnly(modId: String, init: ClientModInitializationContext.() -> Unit) { 17 | ClientModInitializationContext(modId).apply { 18 | init() 19 | } 20 | 21 | } 22 | 23 | 24 | internal class CommonModInitializationContext(val modId: String) 25 | 26 | 27 | internal class ClientModInitializationContext(val modId: String) 28 | 29 | -------------------------------------------------------------------------------- /src/main/kotlin/scheduler/internal/util/Networking.kt: -------------------------------------------------------------------------------- 1 | package scheduler.internal.util 2 | 3 | import drawer.readFrom 4 | import drawer.write 5 | import io.netty.buffer.Unpooled 6 | import kotlinx.serialization.KSerializer 7 | import kotlinx.serialization.modules.EmptyModule 8 | import kotlinx.serialization.modules.SerialModule 9 | import net.fabricmc.fabric.api.network.ClientSidePacketRegistry 10 | import net.fabricmc.fabric.api.network.PacketContext 11 | import net.fabricmc.fabric.api.network.ServerSidePacketRegistry 12 | import net.minecraft.entity.player.PlayerEntity 13 | import net.minecraft.network.PacketByteBuf 14 | import net.minecraft.util.Identifier 15 | import net.minecraft.world.World 16 | import java.util.stream.Stream 17 | 18 | 19 | /******* 20 | * Fabric Api Wrappers 21 | ********/ 22 | 23 | private fun CommonModInitializationContext.registerClientToServerPacket( 24 | packetId: String, packetConsumer: (PacketContext, PacketByteBuf) -> Unit) { 25 | ServerSidePacketRegistry.INSTANCE.register(Identifier(modId, packetId), packetConsumer) 26 | } 27 | 28 | private fun ClientModInitializationContext.registerServerToClientPacket( 29 | packetId: String, packetConsumer: (PacketContext, PacketByteBuf) -> Unit) { 30 | ClientSidePacketRegistry.INSTANCE.register(Identifier(modId, packetId), packetConsumer) 31 | } 32 | 33 | private fun PlayerEntity.sendPacket(packetId: Identifier, packetBuilder: PacketByteBuf.() -> Unit) { 34 | val packet = PacketByteBuf(Unpooled.buffer()).apply(packetBuilder) 35 | ServerSidePacketRegistry.INSTANCE.sendToPlayer(this, packetId, packet) 36 | 37 | } 38 | 39 | 40 | private fun sendPacketToServer(packetId: Identifier, packetBuilder: PacketByteBuf.() -> Unit) { 41 | val packet = PacketByteBuf(Unpooled.buffer()).apply(packetBuilder) 42 | ClientSidePacketRegistry.INSTANCE.sendToServer(packetId, packet) 43 | 44 | } 45 | 46 | /****************************** 47 | * Automatic Serializer Wrappers 48 | ******************************/ 49 | 50 | 51 | internal fun CommonModInitializationContext.registerC2S(serializer: KSerializer>, context: SerialModule) { 52 | registerClientToServerPacket(serializer.packetId) { packetContext, packetByteBuf -> 53 | serializer.readFrom(packetByteBuf,context = context).apply { 54 | packetContext.taskQueue.execute { 55 | use(packetContext) 56 | } 57 | } 58 | } 59 | } 60 | 61 | internal fun ClientModInitializationContext.registerS2C(vararg serializers: KSerializer>, context: SerialModule = EmptyModule) { 62 | for (serializer in serializers) registerS2C(serializer, context) 63 | } 64 | 65 | 66 | internal fun ClientModInitializationContext.registerS2C(serializer: KSerializer>, context: SerialModule) { 67 | registerServerToClientPacket(serializer.packetId) { packetContext, packetByteBuf -> 68 | serializer.readFrom(packetByteBuf,context = context).apply { 69 | packetContext.taskQueue.execute { 70 | use(packetContext) 71 | } 72 | } 73 | } 74 | } 75 | 76 | internal fun CommonModInitializationContext.registerC2S(vararg serializers: KSerializer>, context: SerialModule = EmptyModule) { 77 | for (serializer in serializers) registerC2S(serializer, context) 78 | } 79 | 80 | 81 | /** 82 | * Sends a packet from the server to the client for all the players in the stream. 83 | */ 84 | internal fun > PlayerEntity.sendPacket(packet: T) { 85 | sendPacket(packetId = Identifier(packet.modId, packet.serializer.packetId)) { 86 | packet.serializer.write(packet, this, context = packet.serializationContext) 87 | } 88 | } 89 | 90 | /** 91 | * Sends a packet from the server to the client for all the players in the stream. 92 | */ 93 | internal fun > sendPacketToServer(packet: T) { 94 | sendPacketToServer(Identifier(packet.modId, packet.serializer.packetId)) { 95 | packet.serializer.write(packet, this, context = packet.serializationContext) 96 | } 97 | } 98 | 99 | internal val PacketContext.world: World get() = player.world 100 | 101 | 102 | private val > KSerializer.packetId get() = descriptor.name.toLowerCase() 103 | 104 | 105 | internal interface Packet> { 106 | 107 | 108 | val serializer: KSerializer 109 | 110 | val modId: String 111 | fun use(context: PacketContext) 112 | val serializationContext : SerialModule get() = EmptyModule 113 | } 114 | 115 | internal interface InternalC2SPacket> : Packet 116 | internal interface InternalS2CPacket> : Packet 117 | 118 | -------------------------------------------------------------------------------- /src/main/resources/fabric.mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": 1, 3 | "id": "working-scheduler", 4 | "version": "${version}", 5 | 6 | "name": "Working Scheduler", 7 | "description": "Library mod that provides an working alternative to the Minecraft scheduler.", 8 | "authors": [ 9 | "Fudge" 10 | ], 11 | "contact": { 12 | "homepage": "https://github.com/natanfudge/Working-Scheduler", 13 | "sources": "https://github.com/natanfudge/Working-Scheduler" 14 | }, 15 | 16 | "license": "The MIT License", 17 | "environment": "*", 18 | "entrypoints": { 19 | "main": [ 20 | "scheduler.internal.WorkingSchedulerKt::init" 21 | ], 22 | "client": [ 23 | { 24 | "value": "scheduler.internal.WorkingSchedulerKt::initClient" 25 | } 26 | ] 27 | }, 28 | "depends": { 29 | "fabricloader": ">=0.4.0", 30 | "fabric": "*", 31 | "fabric-language-kotlin": "*", 32 | "fabricdrawer": ">=1.1.1" 33 | }, 34 | "custom": { 35 | "modmenu:api": true 36 | } 37 | } 38 | --------------------------------------------------------------------------------