├── .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 | [](https://curseforge.com/minecraft/mc-mods/working-scheduler)
3 | [](https://discord.gg/CFaCu97)
4 | [](https://bintray.com/beta/#/natanfudge/libs/working-scheduler?tab=overview)
5 | [](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 | [](https://curseforge.com/minecraft/mc-mods/working-scheduler)
3 | [](https://discord.gg/CFaCu97)
4 | [](https://bintray.com/beta/#/natanfudge/libs/working-scheduler?tab=overview)
5 | [](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 |
--------------------------------------------------------------------------------