Compare commits

...

173 Commits

Author SHA1 Message Date
b40a33f69a feat(swarm): walk farther 2023-08-23 21:35:56 +08:00
22b1450a72 feat: adjust swarm time table 2023-08-23 14:28:06 +08:00
abb5646959 fix: game crash when no container available 2023-08-21 09:10:13 +08:00
1ee1c7215d fix: container focus again 2023-08-18 09:34:07 +08:00
d45f8d2a57 fix: signed 32-bits integer overflowed
Use in the wild: Mojang
2023-08-18 09:28:00 +08:00
cd40ba58da fix: WorkPoiAI not initialized early 2023-08-16 10:22:17 +08:00
d110d688c6 fix: only stroll in near positions 2023-08-11 16:00:14 +08:00
6cc16233fa fix: work poi missing 2023-08-11 15:55:17 +08:00
00ce17cb21 fix: disable despawn for swarm 2023-08-11 15:44:22 +08:00
3a4ad875e2 fix: missing bed memory for swarm 2023-08-11 15:33:25 +08:00
e25f23f40b chore: trigger longjing build 2023-08-11 09:51:19 +08:00
f794f9848b fix: remove debug log 2023-08-10 22:22:40 +08:00
f2b60ca6b0 fix(i18n): json syntax 2023-08-10 22:11:25 +08:00
a68b68a0e3 fix: remove duplicated key 2023-08-10 22:09:21 +08:00
33604db8a0 feat(i18n): add translations 2023-08-10 22:07:56 +08:00
a31321e662 feat: volume scaler 2023-08-10 22:03:32 +08:00
db74d07155 fix: mitigation for container tracker 2023-08-10 11:48:16 +08:00
d578db6fb2 fix: crash 2023-08-10 11:45:21 +08:00
296fe1efc2 fix: crash when trying to interact with destoryed container 2023-08-10 11:41:33 +08:00
2c95dfa660 fix: fix unable to bootstrap registry warning 2023-08-01 17:53:34 +08:00
fa1aefe425 feat: remove 3.0 base factor for music 2023-08-01 17:51:53 +08:00
dca1482526 feat: add quilt support
It is not working at all
2023-08-01 16:56:38 +08:00
9aa248e7f2 build: add fabric support 2023-08-01 15:45:21 +08:00
2b62599485 feat: use sided success for SmartInstrument 2023-08-01 14:57:31 +08:00
f588d3bc72 feat: right-click to cycle projection effect radius 2023-08-01 14:55:35 +08:00
c6edaac2e1 feat: custom projection radius 2023-08-01 14:44:11 +08:00
e18b4d78fb style: format code 2023-07-30 21:19:41 +08:00
68c90c8f8a fix: empty containers are always skipped 2023-07-30 21:15:03 +08:00
f3be1aae48 fix: incorrect usage of canTakeItem 2023-07-30 21:05:38 +08:00
40ed66d76e feat: more configurations 2023-07-30 20:52:46 +08:00
07455f2091 feat: dynamic config push 2023-07-30 20:39:46 +08:00
3ac5f3c9ec feat: add advancements 2023-07-30 20:17:33 +08:00
bf3d1ba574 feat(i18n): add zh_meme support 2023-07-30 17:18:37 +08:00
9aa47128c7 feat: idle activities 2023-07-30 16:09:16 +08:00
0fceb397d1 feat: add more sounds 2023-07-30 15:29:01 +08:00
6a5068deee feat: add recipe for CA and RS 2023-07-30 15:22:46 +08:00
527d5ec15d feat: add textures for recipe materials 2023-07-30 15:22:31 +08:00
467a22af35 feat: add lang and models for new materials 2023-07-30 12:24:57 +08:00
4d5559a27d feat: add recipes 2023-07-30 12:23:04 +08:00
df6a14f2b1 feat: save playerData when possible 2023-07-30 11:37:03 +08:00
89841beeb7 feat: add quaedam:projections tag to quaedam:music_projection 2023-07-30 11:25:18 +08:00
7ca6689bad fix: repeat after loading from save 2023-07-30 11:21:00 +08:00
3ce9081400 feat: save rhythm seed 2023-07-30 11:20:52 +08:00
1bd5965ba1 fix: crash on fast-forwarded music player 2023-07-30 10:40:32 +08:00
eb772916dc feat: adjust drop-out parameters 2023-07-30 10:38:29 +08:00
421f80c9a1 style: format code 2023-07-30 10:37:44 +08:00
fd7766b38c fix: smart instrument block entity serialization 2023-07-30 10:33:10 +08:00
b574d8a3d4 feat: add translations for smart instrument 2023-07-30 10:27:30 +08:00
96c48fa0db refactor: rename cyber instrument -> smart instrument 2023-07-30 10:26:39 +08:00
fe5eeb0364 feat: causality anchor effect on cyber instrument 2023-07-30 10:24:46 +08:00
b183e99b3c fix: always return InteractionResult#SUCCESS when effect available 2023-07-30 10:19:58 +08:00
bdccd0c7f3 feat: adjust rhythm random to change per-second 2023-07-30 10:17:38 +08:00
726a0337c5 feat: drop-out for drum-like instruments 2023-07-30 10:17:21 +08:00
0aee57947d feat: full-range for music projection PSH interface 2023-07-30 10:05:19 +08:00
336cab7de4 feat: adjust composer parameters 2023-07-30 10:05:05 +08:00
1d2d9ce4d8 fix: distance attenuation 2023-07-30 10:02:12 +08:00
bbef6cb342 feat: composer 2023-07-30 09:57:56 +08:00
314957eeb5 fix: client-side note player 2023-07-29 21:41:40 +08:00
79b11498c1 feat: item model for cyber instrument 2023-07-29 21:33:35 +08:00
730f6b9781 feat: music player 2023-07-29 21:33:08 +08:00
d5c9eb6b41 fix: boolean & cycle values not saved properly 2023-07-29 20:03:55 +08:00
2f7c9a883a feat: add textures for music projection 2023-07-29 19:58:54 +08:00
e917807adc feat: add music projection stub 2023-07-29 19:52:23 +08:00
023017a829 feat: adjust sounds 2023-07-29 16:43:08 +08:00
2e868528f7 fix: crash with RS 2023-07-29 16:42:58 +08:00
c67c6da32e feat: remove bad sounds 2023-07-29 16:36:57 +08:00
519060ca23 feat: rate parameter for sound projection 2023-07-29 16:35:36 +08:00
249d060075 fix: dirty-data for block entities 2023-07-29 16:20:35 +08:00
7fcd199b9f feat: adjust noise parameters 2023-07-29 16:10:03 +08:00
a575f183b4 fix: noise projection serialization 2023-07-29 15:55:04 +08:00
46c304931d fix: add PSH interface for noise rate 2023-07-29 15:48:09 +08:00
d2c1380b82 feat: remove projector below block limit 2023-07-28 20:03:48 +08:00
f31969f4e5 feat: commit success for PSH 2023-07-28 19:57:17 +08:00
382ab9b0e2 feat: use PASS for projector 2023-07-28 19:40:48 +08:00
e87d6f7d92 fix: spawn swarm in reality stabled area 2023-07-28 19:37:38 +08:00
08b138dc30 feat: add translations for reality stabler 2023-07-28 16:57:32 +08:00
d01f7b642e feat: hide all projection effects in reality stabled area 2023-07-28 16:56:07 +08:00
673ee61d78 feat: loot table for reality stabler 2023-07-28 16:52:05 +08:00
f5fd65aee8 feat: model and textures for RS block 2023-07-28 16:51:38 +08:00
2e9fd2f05d fix: KFF version range 2023-07-28 16:26:49 +08:00
a5bece993a build: trigger longjing 2023-07-28 16:23:47 +08:00
245c5d2490 build: update gradle wrapper 2023-07-28 11:24:00 +08:00
ca0f7b4856 build(deps): update dependencies 2023-07-28 11:18:55 +08:00
b0a5d8c86c feat: blockstate definition and item model for RS block 2023-07-28 11:18:31 +08:00
027082d177 feat: reguster reality stabler 2023-07-28 10:03:26 +08:00
69449c8e66 feat: facing and waterlogged state for RS block 2023-07-28 10:02:30 +08:00
dfa5627ddd feat: reality stabler 2023-07-28 10:00:40 +08:00
3cab891af0 feat: disable causality anchor in water 2023-07-28 10:00:13 +08:00
34ae57e259 feat: move CA core higher 2023-07-27 20:40:05 +08:00
73923b9586 fix: causality anchor on causality anchor 2023-07-27 20:39:44 +08:00
d2b159ade2 feat: better texture 2023-07-27 20:39:27 +08:00
8eb5a75051 fix: mineablity 2023-07-27 20:25:48 +08:00
efade64c0f feat: better texture for causality anchor 2023-07-27 20:12:20 +08:00
cc360be7c1 fix: causality anchor effect 2023-07-27 17:34:04 +08:00
6dddd60405 fix: swarm spawn location 2023-07-27 16:49:25 +08:00
ca87f6704c fix: projection people spawn on ocean surface 2023-07-27 16:42:15 +08:00
cb9102be74 fix: model 2023-07-27 16:36:09 +08:00
c162186f1a refactor!: rename CTS to CA 2023-07-27 16:33:34 +08:00
11c232228a fix: CTS effect 2023-07-27 16:24:50 +08:00
a48b793b7e fix: block shape of CTS 2023-07-27 16:22:42 +08:00
ad66862620 fix: rotation of CTS block 2023-07-27 16:21:59 +08:00
d077704c05 feat: model and textures for constant temporal sink block 2023-07-27 16:21:01 +08:00
dc4a9764cd feat: navigate to CTS area 2023-07-27 11:33:14 +08:00
c290984202 feat: CTS block 2023-07-27 11:32:37 +08:00
849a88bbf6 fix: loading client-only classes on dedicated server 2023-07-27 11:01:30 +08:00
5beb4d977f fix: dedicated server 2023-07-27 10:32:33 +08:00
e70697fa6a feat: config & config push 2023-07-27 10:18:55 +08:00
2089a966e4 fix: dedicated server error 2023-07-26 21:57:34 +08:00
03861f193b fix: crash on startup 2023-07-26 21:33:29 +08:00
e60de5a03a fix: recursion on Quaedam#<cinit> 2023-07-26 21:27:24 +08:00
294a5291b1 style: remove unused imports 2023-07-26 21:08:37 +08:00
bc64a13d9c style: use Quaedam#resource for resource locations 2023-07-26 21:07:52 +08:00
bbfb3f9b9a feat: noise projection impl 2023-07-26 18:02:14 +08:00
27ba95e312 feat: drop offline player's mutex lock 2023-07-26 16:48:13 +08:00
30187548bb feat: slow down PSH mutex checks 2023-07-26 16:46:50 +08:00
fa16d2172f style: use KT map function 2023-07-26 16:45:51 +08:00
f9e6ffacc1 fix: make projector block not piston push-able 2023-07-26 15:29:20 +08:00
df4ad180d7 fix: keep game ticking in PSH screen for single-player 2023-07-25 14:47:20 +08:00
590cfe4f2a feat: hint when no options available 2023-07-25 11:46:08 +08:00
33e206f9fe fix: floating offset 2023-07-25 11:32:14 +08:00
77c87216f7 fix: ceiling 2023-07-25 11:26:58 +08:00
4356793f52 feat: add more translations 2023-07-25 11:22:38 +08:00
bc2f250bae fix: int silder 2023-07-25 11:18:52 +08:00
5a41bf0665 feat: PSH for more projections 2023-07-25 10:58:05 +08:00
968c885dba feat: set default focus for PSH screen 2023-07-24 11:33:00 +08:00
68b8c73896 fix: reject lock request for non-PSH block 2023-07-24 11:28:22 +08:00
1841e71ff8 fix: release PSH lock when failed to open 2023-07-24 11:26:57 +08:00
680d4d76e1 fix: shell screen layout 2023-07-24 11:25:24 +08:00
d88f110fe5 feat: bare projection shell 2023-07-21 17:56:28 +08:00
899eac954e fix: remove test code 2023-07-20 17:43:41 +08:00
e7aa4f4c30 feat: simple projection updater 2023-07-20 17:42:50 +08:00
7308a478b8 feat: add untrusted projection 2023-07-20 15:35:24 +08:00
c80859d643 refactor: block entity based projection block 2023-07-19 21:34:25 +08:00
82de489963 refactor: move misc projections to misc package 2023-07-19 19:49:08 +08:00
60304f5c14 refactor: use TAG constants 2023-07-19 19:46:10 +08:00
0c678ed6f9 Revert "build(deps): update to KT 1.9.0"
This reverts commit b70688f182.
2023-07-12 16:06:19 +08:00
27e45defad style: remove useless 2023-07-12 16:06:09 +08:00
f97a9f6689 build(deps): update architectury to 9.1.10 2023-07-11 14:30:32 +08:00
c3db1ccad8 build(deps): update forge to 1.20.1-47.1.1 2023-07-11 14:27:45 +08:00
efaa5a6e33 build(deps): update architectury-api 2023-07-11 14:27:27 +08:00
b70688f182 build(deps): update to KT 1.9.0 2023-07-11 14:24:48 +08:00
d1cc15e096 feat: add textures for noise and sound projection 2023-07-11 10:21:18 +08:00
33ad5138ea feat: add lang & model for noise & sound projection 2023-07-11 10:13:38 +08:00
c39615bea9 feat: adjust sound parameters 2023-07-11 10:11:17 +08:00
024e097d1e fix: remove bad skin 2023-07-11 10:05:43 +08:00
ba3f962905 feat: adjust noise rate 2023-07-11 10:05:27 +08:00
f6381345ae fix: swarm spawn 2023-07-11 10:02:46 +08:00
7f49b0a84f feat: more voice 2023-07-11 09:58:40 +08:00
e3cdc60d22 feat: swarm noise 2023-07-10 21:04:27 +08:00
81853396cd feat: swarm noise 2023-07-10 20:58:19 +08:00
e15a099678 feat: register noise projection 2023-07-10 16:12:30 +08:00
718bcd1c4f feat: sound projection 2023-07-10 15:46:48 +08:00
bc8c4425a6 feat: add more skins for projected person 2023-07-10 15:01:09 +08:00
decb0d3921 fix: item exchanging 2023-07-04 22:17:49 +08:00
442f536b26 feat: use projector as icon of item group 2023-07-04 22:17:35 +08:00
ed6776f9df feat: extra swarm spawn check 2023-07-04 16:28:42 +08:00
906263cb2b fix: projection command 2023-07-04 16:21:42 +08:00
929474ff19 fix: projection command send empty response 2023-07-04 15:37:44 +08:00
ea50514b8f feat: projection test command 2023-07-04 15:36:03 +08:00
617c46950e refactor: client names only 2023-07-04 13:11:46 +08:00
f5b1372693 fix: drop equipment 2023-07-03 16:03:21 +08:00
16a2728ac1 fix: discard 2023-07-03 15:49:59 +08:00
cb9ae35260 fix: lost item 2023-07-03 15:47:02 +08:00
94be2a823f fix: sleeping 2023-07-03 15:41:17 +08:00
175659bcdc feat: spawn projected people 2023-07-03 15:31:00 +08:00
e13d5ff178 feat: item exchange 2023-07-03 11:51:39 +08:00
f2ceaf176c feat: projected person attacking 2023-07-03 10:46:50 +08:00
88dcf52a57 feat: lost items 2023-07-03 10:22:18 +08:00
b1f44ff2db feat: pick up item 2023-07-03 10:00:00 +08:00
f451a4b868 feat: kick projected people from bed
https://todo.projectsegfau.lt/tasks/748
2023-07-03 09:49:47 +08:00
e7ed8f44de feat: REST activity for projection people 2023-07-03 09:44:05 +08:00
82c6da8ae6 feat: invidual difference 2023-07-02 22:25:50 +08:00
5e1661cff3 fix: name tag position 2023-07-02 21:41:30 +08:00
162 changed files with 9552 additions and 5298 deletions

2
.gitignore vendored
View File

@@ -1,4 +1,4 @@
build
.gradle
forge/run
*/run
.idea

View File

@@ -1,6 +1,6 @@
common/src/main/resources/data/quaedam/projected-person-names:
curl -Ls https://github.com/wainshine/Chinese-Names-Corpus/raw/master/English_Names_Corpus/English_Names_Corpus%EF%BC%882W%EF%BC%89.txt | tail -n +4 | shuf | head -n 2500 > $@.tmp
curl -Ls https://github.com/wainshine/Chinese-Names-Corpus/raw/master/Chinese_Names_Corpus/Chinese_Names_Corpus%EF%BC%88120W%EF%BC%89.txt | tail -n +4 | shuf | head -n 2500 >> $@.tmp
curl -Ls https://github.com/wainshine/Chinese-Names-Corpus/raw/master/English_Names_Corpus/English_Names_Corpus%EF%BC%882W%EF%BC%89.txt | tail -n +4 | shuf | head -n 5000 > $@.tmp
#curl -Ls https://github.com/wainshine/Chinese-Names-Corpus/raw/master/Chinese_Names_Corpus/Chinese_Names_Corpus%EF%BC%88120W%EF%BC%89.txt | tail -n +4 | shuf | head -n 2500 >> $@.tmp
cat $@.tmp | tr -d '\015' > $@
rm $@.tmp

View File

@@ -2,11 +2,12 @@ import net.fabricmc.loom.api.LoomGradleExtensionAPI
plugins {
java
kotlin("jvm") version "1.8.22"
kotlin("jvm") version "1.9.0"
kotlin("plugin.serialization") version "1.9.0"
id("architectury-plugin") version "3.4-SNAPSHOT"
id("dev.architectury.loom") version "1.2-SNAPSHOT" apply false
id("dev.architectury.loom") version "1.3-SNAPSHOT" apply false
id("com.github.johnrengelman.shadow") version "8.1.1" apply false
id("io.github.juuxel.loom-quiltflower") version ("1.10.0") apply false
id("io.github.juuxel.loom-vineflower") version "1.11.0" apply false
}
architectury {
@@ -15,19 +16,23 @@ architectury {
subprojects {
apply(plugin = "dev.architectury.loom")
apply(plugin = "io.github.juuxel.loom-quiltflower")
apply(plugin = "io.github.juuxel.loom-vineflower")
val loom = project.extensions.getByName<LoomGradleExtensionAPI>("loom")
dependencies {
"minecraft"("com.mojang:minecraft:${project.property("minecraft_version")}")
"mappings"(loom.officialMojangMappings())
"mappings"(loom.layered {
officialMojangMappings()
parchment("org.parchmentmc.data:parchment-${project.property("minecraft_version")}:${project.property("parchment_version")}@zip")
})
}
}
allprojects {
apply(plugin = "java")
apply(plugin = "kotlin")
apply(plugin = "kotlinx-serialization")
apply(plugin = "architectury-plugin")
apply(plugin = "maven-publish")
@@ -35,8 +40,20 @@ allprojects {
version = "1.0.0"
group = "quaedam"
repositories {
maven {
name = "ParchmentMC"
setUrl("https://maven.parchmentmc.org")
}
maven {
name = "QuiltMC"
setUrl("https://maven.quiltmc.org/repository/release/")
}
}
dependencies {
compileOnly("org.jetbrains.kotlin:kotlin-stdlib")
compileOnly("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1")
}
tasks.withType<JavaCompile> {

View File

@@ -1,5 +1,5 @@
architectury {
common("forge")
common("forge", "fabric", "quilt")
}
loom {

View File

@@ -0,0 +1,30 @@
package quaedam.mixin;
import net.minecraft.core.BlockPos;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.BedBlock;
import net.minecraft.world.phys.AABB;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import quaedam.projection.swarm.ProjectedPersonEntity;
import java.util.List;
@Mixin(BedBlock.class)
public class MixinBedBlock {
@Inject(at = @At("RETURN"), method = "kickVillagerOutOfBed(Lnet/minecraft/world/level/Level;Lnet/minecraft/core/BlockPos;)Z", cancellable = true)
private void kickVillagerOutOfBed(Level level, BlockPos blockPos, CallbackInfoReturnable<Boolean> cir) {
if (!cir.getReturnValueZ()) {
List<ProjectedPersonEntity> list = level.getEntitiesOfClass(ProjectedPersonEntity.class, new AABB(blockPos), LivingEntity::isSleeping);
if (!list.isEmpty()) {
list.get(0).stopSleeping();
cir.setReturnValue(true);
}
}
}
}

View File

@@ -8,8 +8,8 @@ import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import quaedam.projection.SkylightProjection;
import quaedam.projection.SkylightProjectionEffect;
import quaedam.projection.misc.SkylightProjection;
import quaedam.projection.misc.SkylightProjectionEffect;
import quaedam.projector.Projector;
import java.util.List;

View File

@@ -0,0 +1,24 @@
package quaedam.mixin;
import net.minecraft.core.GlobalPos;
import net.minecraft.server.MinecraftServer;
import org.spongepowered.asm.mixin.Mixin;
import quaedam.mixininterface.ProjectionShellMutexAccessor;
import quaedam.shell.ProjectionShellMutex;
import java.util.LinkedHashMap;
@Mixin(MinecraftServer.class)
public class MixinMinecraftServer implements ProjectionShellMutexAccessor {
private LinkedHashMap<GlobalPos, ProjectionShellMutex.Lock> quaedam$projectionShellMutex;
@Override
public LinkedHashMap<GlobalPos, ProjectionShellMutex.Lock> quaedam$getProjectionShellMutex() {
if (quaedam$projectionShellMutex == null) {
quaedam$projectionShellMutex = new LinkedHashMap<>();
}
return quaedam$projectionShellMutex;
}
}

View File

@@ -0,0 +1,20 @@
package quaedam.mixin;
import net.minecraft.network.Connection;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.players.PlayerList;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import quaedam.config.SimpleQuaedamConfigPush;
@Mixin(PlayerList.class)
public class MixinPlayerList {
@Inject(at = @At("RETURN"), method = "placeNewPlayer(Lnet/minecraft/network/Connection;Lnet/minecraft/server/level/ServerPlayer;)V")
public void placeNewPlayer(Connection netManager, ServerPlayer player, CallbackInfo ci) {
SimpleQuaedamConfigPush.INSTANCE.sendCurrent(player);
}
}

View File

@@ -0,0 +1,12 @@
package quaedam.mixininterface;
import net.minecraft.core.GlobalPos;
import quaedam.shell.ProjectionShellMutex;
import java.util.LinkedHashMap;
public interface ProjectionShellMutexAccessor {
LinkedHashMap<GlobalPos, ProjectionShellMutex.Lock> quaedam$getProjectionShellMutex();
}

View File

@@ -5,14 +5,25 @@ import dev.architectury.registry.registries.DeferredRegister
import dev.architectury.registry.registries.RegistrySupplier
import net.minecraft.core.registries.Registries
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.item.CreativeModeTab
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import org.slf4j.LoggerFactory
import quaedam.config.QuaedamConfig
import quaedam.misc.CraftingMaterials
import quaedam.misc.causality.CausalityAnchor
import quaedam.misc.reality.RealityStabler
import quaedam.projection.ProjectionCommand
import quaedam.projection.ProjectionEffectType
import quaedam.projection.SkylightProjection
import quaedam.projection.SimpleProjectionUpdate
import quaedam.projection.misc.NoiseProjection
import quaedam.projection.misc.SkylightProjection
import quaedam.projection.misc.SoundProjection
import quaedam.projection.music.MusicProjection
import quaedam.projection.swarm.ProjectedPersonEntity
import quaedam.projection.swarm.SwarmProjection
import quaedam.projector.Projector
import quaedam.shell.ProjectionShell
object Quaedam {
@@ -25,28 +36,53 @@ object Quaedam {
val blocks = DeferredRegister.create(ID, Registries.BLOCK)!!
val blockEntities = DeferredRegister.create(ID, Registries.BLOCK_ENTITY_TYPE)!!
val entities = DeferredRegister.create(ID, Registries.ENTITY_TYPE)!!
val schedule = DeferredRegister.create(ID, Registries.SCHEDULE)!!
val projectionEffects = DeferredRegister.create(ID, ProjectionEffectType.registryKey)!!
val schedules = DeferredRegister.create(ID, Registries.SCHEDULE)!!
val memoryTypes = DeferredRegister.create(ID, Registries.MEMORY_MODULE_TYPE)!!
val sensors = DeferredRegister.create(ID, Registries.SENSOR_TYPE)!!
val soundEvents = DeferredRegister.create(ID, Registries.SOUND_EVENT)!!
val poiTypes = DeferredRegister.create(ID, Registries.POINT_OF_INTEREST_TYPE)!!
val projectionEffects by lazy { DeferredRegister.create(ID, ProjectionEffectType.registryKey)!! }
val creativeModeTab: RegistrySupplier<CreativeModeTab> = creativeModeTabs.register("quaedam") {
CreativeTabRegistry.create(Component.translatable("category.quaedam")) {
ItemStack(Items.TORCH)
ItemStack(Projector.item.get())
}
}
val lateinit = mutableListOf<() -> Unit>()
fun init() {
QuaedamConfig
Projector
ProjectionEffectType
SkylightProjection
SwarmProjection
SoundProjection
NoiseProjection
MusicProjection
ProjectionCommand
SimpleProjectionUpdate
ProjectionShell
CausalityAnchor
RealityStabler
CraftingMaterials
creativeModeTabs.register()
items.register()
blocks.register()
blockEntities.register()
entities.register()
schedule.register()
schedules.register()
memoryTypes.register()
sensors.register()
soundEvents.register()
poiTypes.register()
projectionEffects.register()
lateinit.forEach { it() }
lateinit.clear()
}
fun resource(path: String) = ResourceLocation(ID, path)
}

View File

@@ -0,0 +1,105 @@
package quaedam.config
import dev.architectury.event.events.client.ClientPlayerEvent
import dev.architectury.platform.Platform
import dev.architectury.utils.GameInstance
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import net.fabricmc.api.EnvType
import net.minecraft.nbt.*
import quaedam.Quaedam
import java.nio.file.Path
import kotlin.io.path.exists
import kotlin.io.path.notExists
import kotlin.io.path.readText
import kotlin.io.path.writeText
@Serializable
data class QuaedamConfig(
val valuesInt: Map<String, Int> = mapOf(),
val valuesFloat: Map<String, Float> = mapOf(),
val valuesDouble: Map<String, Double> = mapOf(),
val valuesBoolean: Map<String, Boolean> = mapOf(),
) {
companion object {
private val localJson = Json {
isLenient = true
prettyPrint = true
encodeDefaults = true
ignoreUnknownKeys = true
}
private val pushJson = Json {
encodeDefaults = true
ignoreUnknownKeys = true
}
private val localFile: Path = Platform.getConfigFolder().resolve("quaedam.json")
private var local0 = loadLocalConfig()
var local
get() = local0
set(value) {
local0 = value
writeLocalConfig()
}
private var remote: QuaedamConfig? = null
val current get() = remote ?: local0
init {
SimpleQuaedamConfigPush
if (Platform.getEnv() == EnvType.CLIENT) {
ClientPlayerEvent.CLIENT_PLAYER_QUIT.register { player ->
if (player == GameInstance.getClient().player) {
applyRemoteConfig(null)
}
}
}
if (localFile.notExists()) {
writeLocalConfig()
}
}
private fun loadLocalConfig(): QuaedamConfig = if (localFile.exists()) {
localJson.decodeFromString(localFile.readText())
} else {
QuaedamConfig()
}
private fun writeLocalConfig() {
localFile.writeText(localJson.encodeToString(local0))
}
fun applyRemoteConfig(config: QuaedamConfig?) {
Quaedam.logger.info("Received remote config push: $config")
remote = config
}
const val TAG_VALUES_INT = "ValuesInt"
const val TAG_VALUES_FLOAT = "ValuesFloat"
const val TAG_VALUES_DOUBLE = "ValuesDouble"
const val TAG_VALUES_BOOLEAN = "ValuesBoolean"
fun fromPushNbt(tag: CompoundTag): QuaedamConfig {
return QuaedamConfig(
valuesInt = pushJson.decodeFromString(tag.getString(TAG_VALUES_INT)),
valuesFloat = pushJson.decodeFromString(tag.getString(TAG_VALUES_FLOAT)),
valuesDouble = pushJson.decodeFromString(tag.getString(TAG_VALUES_DOUBLE)),
valuesBoolean = pushJson.decodeFromString(tag.getString(TAG_VALUES_BOOLEAN)),
)
}
}
fun toPushNbt(tag: CompoundTag) {
tag.putString(TAG_VALUES_INT, pushJson.encodeToString(valuesInt))
tag.putString(TAG_VALUES_FLOAT, pushJson.encodeToString(valuesFloat))
tag.putString(TAG_VALUES_DOUBLE, pushJson.encodeToString(valuesDouble))
tag.putString(TAG_VALUES_BOOLEAN, pushJson.encodeToString(valuesBoolean))
}
fun toPushNbt() = CompoundTag().also { toPushNbt(it) }
}

View File

@@ -0,0 +1,38 @@
package quaedam.config
import dev.architectury.networking.NetworkManager
import dev.architectury.platform.Platform
import io.netty.buffer.Unpooled
import net.fabricmc.api.EnvType
import net.minecraft.nbt.CompoundTag
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.server.level.ServerPlayer
import quaedam.Quaedam
object SimpleQuaedamConfigPush {
val id = Quaedam.resource("simple_config_push")
init {
if (Platform.getEnv() == EnvType.CLIENT) {
NetworkManager.registerReceiver(NetworkManager.Side.S2C, id, ::handle)
}
}
private fun handle(buf: FriendlyByteBuf, ctx: NetworkManager.PacketContext) {
val data = buf.readNbt()!!
val config = QuaedamConfig.fromPushNbt(data)
QuaedamConfig.applyRemoteConfig(config)
}
fun sendCurrent(player: ServerPlayer) = send(player, QuaedamConfig.current)
fun send(player: ServerPlayer, config: QuaedamConfig) = send(player, config.toPushNbt())
private fun send(player: ServerPlayer, data: CompoundTag) {
val buf = FriendlyByteBuf(Unpooled.buffer())
buf.writeNbt(data)
NetworkManager.sendToPlayer(player, id, buf)
}
}

View File

@@ -0,0 +1,16 @@
package quaedam.misc
import net.minecraft.world.item.Item
import quaedam.Quaedam
object CraftingMaterials {
val ironCopperMetal = Quaedam.items.register("iron_copper_metal") {
Item(Item.Properties().`arch$tab`(Quaedam.creativeModeTab))
}!!
val projectionMetal = Quaedam.items.register("projection_metal") {
Item(Item.Properties().`arch$tab`(Quaedam.creativeModeTab))
}!!
}

View File

@@ -0,0 +1,109 @@
package quaedam.misc.causality
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.world.item.context.BlockPlaceContext
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.Level
import net.minecraft.world.level.LevelAccessor
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.EntityBlock
import net.minecraft.world.level.block.HorizontalDirectionalBlock
import net.minecraft.world.level.block.SimpleWaterloggedBlock
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.block.state.properties.BlockStateProperties
import net.minecraft.world.level.material.FluidState
import net.minecraft.world.level.material.Fluids
import net.minecraft.world.level.material.MapColor
import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.Shapes
import net.minecraft.world.phys.shapes.VoxelShape
object CABlock : HorizontalDirectionalBlock(
Properties.of()
.noOcclusion()
.strength(2f)
.requiresCorrectToolForDrops()
.mapColor(MapColor.COLOR_CYAN)
), EntityBlock, SimpleWaterloggedBlock {
val shapes = getShapeForEachState(::createVoxelShape)
init {
registerDefaultState(
defaultBlockState()
.setValue(FACING, Direction.EAST)
.setValue(BlockStateProperties.WATERLOGGED, false)
)
}
override fun newBlockEntity(pos: BlockPos, state: BlockState) = CABlockEntity(pos, state)
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
super.createBlockStateDefinition(builder)
builder.add(FACING, BlockStateProperties.WATERLOGGED)
}
override fun getStateForPlacement(context: BlockPlaceContext): BlockState? {
if (!context.level.getBlockState(context.clickedPos.below()).canOcclude()) return null
return super.defaultBlockState().setValue(FACING, context.horizontalDirection)
}
@Suppress("OVERRIDE_DEPRECATION")
override fun getShape(state: BlockState, level: BlockGetter, pos: BlockPos, context: CollisionContext) =
shapes[state]!!
private fun createVoxelShape(state: BlockState): VoxelShape =
when (state.getValue(FACING)) {
Direction.WEST, Direction.EAST -> Shapes.or(
box(0.0, 0.0, 0.0, 16.0, 12.0, 16.0),
box(7.0, 14.0, 6.0, 9.0, 16.0, 10.0),
)
Direction.SOUTH, Direction.NORTH -> Shapes.or(
box(0.0, 0.0, 0.0, 16.0, 12.0, 16.0),
box(6.0, 14.0, 7.0, 10.0, 16.0, 9.0),
)
else -> throw IllegalStateException(state.getValue(FACING).name)
}
@Suppress("OVERRIDE_DEPRECATION", "DEPRECATION")
override fun updateShape(
state: BlockState,
direction: Direction,
neighborState: BlockState,
level: LevelAccessor,
pos: BlockPos,
neighborPos: BlockPos
): BlockState {
if (state.getValue(BlockStateProperties.WATERLOGGED)) {
level.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(level))
}
return super.updateShape(state, direction, neighborState, level, pos, neighborPos)
}
@Suppress("OVERRIDE_DEPRECATION", "DEPRECATION")
override fun getFluidState(state: BlockState): FluidState = if (state.getValue(BlockStateProperties.WATERLOGGED)) {
Fluids.WATER.getSource(false)
} else {
super.getFluidState(state)
}
@Suppress("OVERRIDE_DEPRECATION", "DEPRECATION")
override fun neighborChanged(
state: BlockState,
level: Level,
pos: BlockPos,
neighborBlock: Block,
neighborPos: BlockPos,
movedByPiston: Boolean
) {
super.neighborChanged(state, level, pos, neighborBlock, neighborPos, movedByPiston)
if (!level.getBlockState(pos.below()).canOcclude()) {
level.destroyBlock(pos, true)
}
}
}

View File

@@ -0,0 +1,18 @@
package quaedam.misc.causality
import net.minecraft.core.BlockPos
import net.minecraft.nbt.CompoundTag
import net.minecraft.network.protocol.Packet
import net.minecraft.network.protocol.game.ClientGamePacketListener
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.state.BlockState
class CABlockEntity(pos: BlockPos, state: BlockState) :
BlockEntity(CausalityAnchor.blockEntity.get(), pos, state) {
override fun getUpdateTag(): CompoundTag = saveWithoutMetadata()
override fun getUpdatePacket(): Packet<ClientGamePacketListener> = ClientboundBlockEntityDataPacket.create(this)
}

View File

@@ -0,0 +1,33 @@
package quaedam.misc.causality
import net.minecraft.core.BlockPos
import net.minecraft.world.item.BlockItem
import net.minecraft.world.item.Item
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.entity.BlockEntityType
import net.minecraft.world.level.block.state.properties.BlockStateProperties
import quaedam.Quaedam
object CausalityAnchor {
const val ID = "causality_anchor"
val block = Quaedam.blocks.register(ID) { CABlock }!!
val item = Quaedam.items.register(ID) {
BlockItem(
CABlock, Item.Properties()
.stacksTo(1)
.`arch$tab`(Quaedam.creativeModeTab)
)
}!!
val blockEntity = Quaedam.blockEntities.register(ID) {
BlockEntityType.Builder.of(::CABlockEntity, block.get()).build(null)
}!!
fun checkEffect(level: Level, pos: BlockPos) = level.getChunkAt(pos)
.blockEntities
.any { (k, v) -> v is CABlockEntity && !level.getBlockState(k).getValue(BlockStateProperties.WATERLOGGED) }
}

View File

@@ -0,0 +1,96 @@
package quaedam.misc.reality
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.world.item.context.BlockPlaceContext
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.Level
import net.minecraft.world.level.LevelAccessor
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.EntityBlock
import net.minecraft.world.level.block.HorizontalDirectionalBlock
import net.minecraft.world.level.block.SimpleWaterloggedBlock
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.block.state.properties.BlockStateProperties
import net.minecraft.world.level.material.FluidState
import net.minecraft.world.level.material.Fluids
import net.minecraft.world.level.material.MapColor
import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.Shapes
object RSBlock : HorizontalDirectionalBlock(
Properties.of()
.noOcclusion()
.strength(3f)
.requiresCorrectToolForDrops()
.mapColor(MapColor.COLOR_BLUE)
), EntityBlock, SimpleWaterloggedBlock {
val shape = Shapes.or(
box(1.0, 0.0, 1.0, 15.0, 1.0, 15.0),
box(0.0, 1.0, 0.0, 16.0, 14.0, 16.0),
box(1.0, 14.0, 1.0, 15.0, 15.0, 15.0),
)
init {
registerDefaultState(
defaultBlockState()
.setValue(FACING, Direction.EAST)
.setValue(BlockStateProperties.WATERLOGGED, false)
)
}
override fun newBlockEntity(pos: BlockPos, state: BlockState) = RSBlockEntity(pos, state)
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
super.createBlockStateDefinition(builder)
builder.add(FACING, BlockStateProperties.WATERLOGGED)
}
override fun getStateForPlacement(context: BlockPlaceContext): BlockState? {
if (!context.level.getBlockState(context.clickedPos.below()).canOcclude()) return null
return super.defaultBlockState().setValue(FACING, context.horizontalDirection)
}
@Suppress("OVERRIDE_DEPRECATION", "DEPRECATION")
override fun getShape(state: BlockState, level: BlockGetter, pos: BlockPos, context: CollisionContext) = shape
@Suppress("OVERRIDE_DEPRECATION", "DEPRECATION")
override fun updateShape(
state: BlockState,
direction: Direction,
neighborState: BlockState,
level: LevelAccessor,
pos: BlockPos,
neighborPos: BlockPos
): BlockState {
if (state.getValue(BlockStateProperties.WATERLOGGED)) {
level.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(level))
}
return super.updateShape(state, direction, neighborState, level, pos, neighborPos)
}
@Suppress("OVERRIDE_DEPRECATION", "DEPRECATION")
override fun getFluidState(state: BlockState): FluidState = if (state.getValue(BlockStateProperties.WATERLOGGED)) {
Fluids.WATER.getSource(false)
} else {
super.getFluidState(state)
}
@Suppress("OVERRIDE_DEPRECATION", "DEPRECATION")
override fun neighborChanged(
state: BlockState,
level: Level,
pos: BlockPos,
neighborBlock: Block,
neighborPos: BlockPos,
movedByPiston: Boolean
) {
super.neighborChanged(state, level, pos, neighborBlock, neighborPos, movedByPiston)
if (!level.getBlockState(pos.below()).canOcclude()) {
level.destroyBlock(pos, true)
}
}
}

View File

@@ -0,0 +1,18 @@
package quaedam.misc.reality
import net.minecraft.core.BlockPos
import net.minecraft.nbt.CompoundTag
import net.minecraft.network.protocol.Packet
import net.minecraft.network.protocol.game.ClientGamePacketListener
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.state.BlockState
class RSBlockEntity(pos: BlockPos, state: BlockState) :
BlockEntity(RealityStabler.blockEntity.get(), pos, state) {
override fun getUpdateTag(): CompoundTag = saveWithoutMetadata()
override fun getUpdatePacket(): Packet<ClientGamePacketListener> = ClientboundBlockEntityDataPacket.create(this)
}

View File

@@ -0,0 +1,32 @@
package quaedam.misc.reality
import net.minecraft.core.BlockPos
import net.minecraft.world.item.BlockItem
import net.minecraft.world.item.Item
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.entity.BlockEntityType
import quaedam.Quaedam
object RealityStabler {
const val ID = "reality_stabler"
val block = Quaedam.blocks.register(ID) { RSBlock }!!
val item = Quaedam.items.register(ID) {
BlockItem(
RSBlock, Item.Properties()
.stacksTo(1)
.`arch$tab`(Quaedam.creativeModeTab)
)
}!!
val blockEntity = Quaedam.blockEntities.register(ID) {
BlockEntityType.Builder.of(::RSBlockEntity, block.get()).build(null)
}!!
fun checkEffect(level: Level, pos: BlockPos) = level.getChunkAt(pos)
.blockEntities
.any { (_, v) -> v is RSBlockEntity }
}

View File

@@ -0,0 +1,52 @@
package quaedam.projection
import dev.architectury.registry.registries.DeferredSupplier
import net.minecraft.client.Minecraft
import net.minecraft.core.BlockPos
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.EntityBlock
import net.minecraft.world.level.block.entity.BlockEntityType
import net.minecraft.world.level.block.state.BlockState
import quaedam.shell.ProjectionEffectShell
import quaedam.shell.ProjectionShellBlock
import quaedam.utils.sendBlockUpdated
abstract class EntityProjectionBlock<P : ProjectionEffect>(properties: Properties = createProperties()) :
ProjectionBlock<P>(properties), EntityBlock, ProjectionShellBlock {
companion object {
fun createProperties(): Properties = ProjectionBlock.createProperties()
}
abstract val blockEntity: DeferredSupplier<BlockEntityType<SimpleProjectionEntity<P>>>
override fun newBlockEntity(pos: BlockPos, state: BlockState) = blockEntity.get().create(pos, state)!!
@Suppress("UNCHECKED_CAST")
fun getBlockEntity(level: Level, pos: BlockPos) = (level.getBlockEntity(pos) as SimpleProjectionEntity<P>)
override fun applyProjectionEffect(level: ServerLevel, state: BlockState, pos: BlockPos) =
getBlockEntity(level, pos).cloneProjection()
fun applyChange(level: Level, pos: BlockPos, func: P.() -> Unit) {
val entity = getBlockEntity(level, pos)
val projection = entity.projection
projection.apply(func)
if (level.isClientSide) {
check(level == Minecraft.getInstance().player!!.level())
SimpleProjectionUpdate.send(pos, projection.toNbt())
} else {
getBlockEntity(level, pos).sendBlockUpdated()
sendUpdateToProjectors(level, pos)
}
}
override fun getProjectionEffectForShell(level: Level, pos: BlockPos) =
(getBlockEntity(level, pos).cloneProjection() as ProjectionEffectShell.Provider).createShell()
override fun applyFromShell(level: Level, pos: BlockPos, shell: ProjectionEffectShell) = applyChange(level, pos) {
fromNbt(shell.effect.toNbt())
}
}

View File

@@ -29,6 +29,13 @@ abstract class ProjectionBlock<P : ProjectionEffect>(properties: Properties = cr
}
.toSet()
fun sendUpdateToProjectors(level: Level, pos: BlockPos) {
if (!level.isClientSide) {
findNearbyProjectors(level, pos)
.forEach { (level.getBlockEntity(it) as ProjectorBlockEntity).checkUpdate() }
}
}
}
@Suppress("OVERRIDE_DEPRECATION")
@@ -42,17 +49,13 @@ abstract class ProjectionBlock<P : ProjectionEffect>(properties: Properties = cr
itemStack: ItemStack
) {
super.setPlacedBy(level, pos, state, placer, itemStack)
if (!level.isClientSide) {
findNearbyProjectors(level, pos)
.forEach { (level.getBlockEntity(it) as ProjectorBlockEntity).checkUpdate() }
}
sendUpdateToProjectors(level, pos)
}
override fun destroy(level: LevelAccessor, pos: BlockPos, state: BlockState) {
super.destroy(level, pos, state)
if (level is Level && !level.isClientSide) {
findNearbyProjectors(level, pos)
.forEach { (level.getBlockEntity(it) as ProjectorBlockEntity).checkUpdate() }
if (level is Level) {
sendUpdateToProjectors(level, pos)
}
}

View File

@@ -0,0 +1,70 @@
package quaedam.projection
import com.mojang.brigadier.builder.LiteralArgumentBuilder.literal
import com.mojang.brigadier.builder.RequiredArgumentBuilder.argument
import com.mojang.brigadier.context.CommandContext
import dev.architectury.event.events.common.CommandRegistrationEvent
import net.minecraft.commands.CommandSourceStack
import net.minecraft.commands.arguments.ResourceArgument.resource
import net.minecraft.core.BlockPos
import net.minecraft.core.Holder
import net.minecraft.nbt.ListTag
import net.minecraft.nbt.NbtUtils
import quaedam.projector.Projector
object ProjectionCommand {
init {
CommandRegistrationEvent.EVENT.register { dispatcher, ctx, _ ->
dispatcher.register(
literal<CommandSourceStack>("quaedam_projection")
.then(
literal<CommandSourceStack>("dump")
.requires { it.hasPermission(2) }
.executes(::dump)
)
.then(
literal<CommandSourceStack>("get")
.requires { it.hasPermission(2) }
.then(
argument<CommandSourceStack, Holder.Reference<ProjectionEffectType<*>>>(
"type",
resource(ctx, ProjectionEffectType.registryKey)
)
.executes(::get)
)
)
)
}
}
private fun dump(ctx: CommandContext<CommandSourceStack>): Int {
val pos = BlockPos(
ctx.source.position.x.toInt(),
ctx.source.position.y.toInt(),
ctx.source.position.z.toInt()
)
val data = Projector.findNearbyProjectors(ctx.source.level, pos)
.map { ctx.source.level.getBlockEntity(it)!!.saveWithFullMetadata() }
val tag = ListTag()
tag.addAll(data)
ctx.source.sendSystemMessage(NbtUtils.toPrettyComponent(tag))
return 0
}
private fun get(ctx: CommandContext<CommandSourceStack>): Int {
val pos = BlockPos(
ctx.source.position.x.toInt(),
ctx.source.position.y.toInt(),
ctx.source.position.z.toInt()
)
val type = ctx.getArgument("type", Holder.Reference::class.java).value() as ProjectionEffectType<*>
val data = Projector.findNearbyProjections(ctx.source.level, pos, type)
.map { it.toNbt() }
val tag = ListTag()
tag.addAll(data)
ctx.source.sendSystemMessage(NbtUtils.toPrettyComponent(tag))
return 0
}
}

View File

@@ -9,18 +9,19 @@ import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.state.BlockState
import quaedam.Quaedam
abstract class ProjectionEffect {
abstract class ProjectionEffect : Cloneable {
abstract val type: ProjectionEffectType<*>
abstract fun toNbt(tag: CompoundTag)
abstract fun fromNbt(tag: CompoundTag)
abstract fun fromNbt(tag: CompoundTag, trusted: Boolean = true)
fun toNbt() = CompoundTag().apply { toNbt(this) }
fun toNbt() = CompoundTag().also { toNbt(it) }
override fun equals(other: Any?) = other === this
override fun equals(other: Any?): Boolean = other === this
override fun hashCode() = type.hashCode()
@@ -37,11 +38,11 @@ data class ProjectionEffectType<T : ProjectionEffect>(val constructor: () -> T)
companion object {
val registryKey: ResourceKey<Registry<ProjectionEffectType<*>>> =
ResourceKey.createRegistryKey(ResourceLocation("quaedam", "projection_effect"))
val registry: Registry<ProjectionEffectType<*>> = BuiltInRegistries.registerSimple(registryKey) { null }
ResourceKey.createRegistryKey(Quaedam.resource("projection_effect"))
val registry: Registry<ProjectionEffectType<*>> = BuiltInRegistries.registerSimple(registryKey) { nopEffect }
val nopEffect: ProjectionEffectType<NopEffect> =
Registry.register(registry, ResourceLocation("quaedam", "nop"), ProjectionEffectType { NopEffect })
Registry.register(registry, Quaedam.resource("nop"), ProjectionEffectType { NopEffect })
}
@@ -51,11 +52,11 @@ data class ProjectionEffectType<T : ProjectionEffect>(val constructor: () -> T)
object NopEffect : ProjectionEffect() {
override val type get() = nopEffect
override fun toNbt(tag: CompoundTag) {}
override fun fromNbt(tag: CompoundTag) {}
override fun fromNbt(tag: CompoundTag, trusted: Boolean) {}
}
}
interface ProjectionProvider<P : ProjectionEffect> {
fun createProjectionEffect(level: ServerLevel, state: BlockState, pos: BlockPos): P?
fun applyProjectionEffect(level: ServerLevel, state: BlockState, pos: BlockPos): P?
}

View File

@@ -0,0 +1,56 @@
package quaedam.projection
import dev.architectury.registry.registries.RegistrySupplier
import net.minecraft.core.BlockPos
import net.minecraft.nbt.CompoundTag
import net.minecraft.network.protocol.Packet
import net.minecraft.network.protocol.game.ClientGamePacketListener
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.entity.BlockEntityType
import net.minecraft.world.level.block.state.BlockState
class SimpleProjectionEntity<P : ProjectionEffect>(
type: BlockEntityType<SimpleProjectionEntity<P>>,
pos: BlockPos,
state: BlockState,
var projection: P,
val default: () -> P,
) : BlockEntity(type, pos, state) {
companion object {
const val TAG_PROJECTION_EFFECT = "ProjectionEffect"
fun <P : ProjectionEffect, B : ProjectionBlock<P>> createBlockEntityType(
block: RegistrySupplier<B>,
default: () -> P,
): BlockEntityType<SimpleProjectionEntity<P>> {
val type = ValueContainer<BlockEntityType<SimpleProjectionEntity<P>>>()
type.inner = BlockEntityType.Builder.of({ pos, state ->
SimpleProjectionEntity(type.inner!!, pos, state, default(), default)
}, block.get()).build(null)
return type.inner!!
}
}
data class ValueContainer<E>(var inner: E? = null)
override fun saveAdditional(tag: CompoundTag) {
super.saveAdditional(tag)
tag.put(TAG_PROJECTION_EFFECT, projection.toNbt())
}
override fun load(tag: CompoundTag) {
super.load(tag)
if (TAG_PROJECTION_EFFECT in tag) {
projection.fromNbt(tag.getCompound(TAG_PROJECTION_EFFECT))
}
}
override fun getUpdateTag(): CompoundTag = saveWithoutMetadata()
override fun getUpdatePacket(): Packet<ClientGamePacketListener> = ClientboundBlockEntityDataPacket.create(this)
fun cloneProjection() = default().apply { fromNbt(projection.toNbt()) }
}

View File

@@ -0,0 +1,65 @@
package quaedam.projection
import dev.architectury.networking.NetworkManager
import dev.architectury.networking.NetworkManager.PacketContext
import io.netty.buffer.Unpooled
import net.minecraft.core.BlockPos
import net.minecraft.nbt.CompoundTag
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.chat.Component
import net.minecraft.server.level.ServerPlayer
import quaedam.Quaedam
import quaedam.utils.sendBlockUpdated
object SimpleProjectionUpdate {
val id = Quaedam.resource("simple_projection_update")
init {
NetworkManager.registerReceiver(NetworkManager.Side.C2S, id, ::handle)
}
private fun handle(buf: FriendlyByteBuf, ctx: PacketContext) = runCatching {
val player = ctx.player!! as ServerPlayer
val level = player.level()
val pos = buf.readBlockPos()
val data = buf.readNbt()!!
if (player.blockPosition().distSqr(pos) > 10 * 10) {
Quaedam.logger.info("Player ${player.name} tried to update a projection block far away")
if (player.blockPosition().distSqr(pos) > 50 * 50) {
player.connection.disconnect(Component.literal("[Quaedam] wth r u doing? why not waiting for server?"))
}
return@runCatching
}
level.server!!.execute {
val entity = level.getBlockEntity(pos) ?: return@execute
val blockEntity = entity as SimpleProjectionEntity<*>
try {
blockEntity.projection.fromNbt(data, trusted = false)
} catch (e: Throwable) {
Quaedam.logger.error(
"Player ${player.name} tried to update projection " +
"at $pos but caused error: $data", e
)
player.connection.disconnect(Component.literal("[Quaedam] ? wait what did you send to the server?"))
return@execute
}
entity.setChanged()
blockEntity.sendBlockUpdated()
ProjectionBlock.sendUpdateToProjectors(level, pos)
}
}
.onFailure { Quaedam.logger.error("Error handling simple projection update packet", it) }
.getOrThrow()
fun send(pos: BlockPos, data: CompoundTag) {
val buf = FriendlyByteBuf(Unpooled.buffer())
buf.writeBlockPos(pos)
buf.writeNbt(data)
NetworkManager.sendToServer(id, buf)
}
}

View File

@@ -1,58 +0,0 @@
package quaedam.projection
import net.minecraft.core.BlockPos
import net.minecraft.nbt.CompoundTag
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.item.BlockItem
import net.minecraft.world.item.Item
import net.minecraft.world.level.block.state.BlockState
import quaedam.Quaedam
object SkylightProjection {
const val ID = "skylight_projection"
const val SHORT_ID = "skylight"
val block = Quaedam.blocks.register(ID) { SkylightProjectionBlock }!!
val item = Quaedam.items.register(ID) {
BlockItem(
SkylightProjectionBlock, Item.Properties()
.`arch$tab`(Quaedam.creativeModeTab)
)
}!!
val effect = Quaedam.projectionEffects.register(SHORT_ID) {
ProjectionEffectType { SkylightProjectionEffect() }
}!!
}
object SkylightProjectionBlock : ProjectionBlock<SkylightProjectionEffect>(createProperties().lightLevel { 3 }) {
override fun createProjectionEffect(
level: ServerLevel,
state: BlockState,
pos: BlockPos
) = SkylightProjectionEffect()
}
data class SkylightProjectionEffect(var factor: Double = 2.0) : ProjectionEffect() {
companion object {
const val TAG_FACTOR = "Factor"
}
override val type
get() = SkylightProjection.effect.get()!!
override fun toNbt(tag: CompoundTag) {
tag.putDouble(TAG_FACTOR, factor)
}
override fun fromNbt(tag: CompoundTag) {
factor = tag.getDouble(TAG_FACTOR)
}
}

View File

@@ -0,0 +1,144 @@
package quaedam.projection.misc
import dev.architectury.event.events.client.ClientTickEvent
import dev.architectury.platform.Platform
import net.fabricmc.api.EnvType
import net.minecraft.client.Minecraft
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.client.resources.sounds.SoundInstance
import net.minecraft.nbt.CompoundTag
import net.minecraft.sounds.SoundEvent
import net.minecraft.sounds.SoundSource
import net.minecraft.util.RandomSource
import net.minecraft.world.item.BlockItem
import net.minecraft.world.item.Item
import quaedam.Quaedam
import quaedam.config.QuaedamConfig
import quaedam.projection.EntityProjectionBlock
import quaedam.projection.ProjectionEffect
import quaedam.projection.ProjectionEffectType
import quaedam.projection.SimpleProjectionEntity
import quaedam.projector.Projector
import quaedam.shell.ProjectionEffectShell
import quaedam.shell.buildProjectionEffectShell
import kotlin.math.min
object NoiseProjection {
const val ID = "noise_projection"
const val SHORT_ID = "noise"
val block = Quaedam.blocks.register(ID) { NoiseProjectionBlock }!!
val item = Quaedam.items.register(ID) {
BlockItem(
NoiseProjectionBlock, Item.Properties()
.`arch$tab`(Quaedam.creativeModeTab)
)
}!!
val effect = Quaedam.projectionEffects.register(SHORT_ID) {
ProjectionEffectType { NoiseProjectionEffect() }
}!!
val blockEntity = Quaedam.blockEntities.register(ID) {
SimpleProjectionEntity.createBlockEntityType(block, ::NoiseProjectionEffect)
}!!
const val SOUND_NOISE_ID = "quaedam.projection.noise"
val soundEvent = Quaedam.soundEvents.register(SOUND_NOISE_ID) {
SoundEvent.createVariableRangeEvent(Quaedam.resource(SOUND_NOISE_ID))
}!!
init {
if (Platform.getEnv() == EnvType.CLIENT) {
ClientTickEvent.CLIENT_POST.register { game ->
val player = game.player ?: return@register
val random = (game.level ?: return@register).random
val projections = Projector.findNearbyProjections(player.level(), player.blockPosition(), effect.get())
if (projections.isNotEmpty()) {
val rate = projections.maxOf { it.rate }
val amount = min(projections.sumOf { it.amount }, 12)
val volume = projections.fold(1.0f) { v, p -> v * p.volume }
if (amount != 0 && random.nextInt(1000 / rate) == 1) {
for (i in 0 until random.nextInt(amount)) {
// play random noise
playRandomNoise(random, game, volume)
}
}
}
}
}
}
private fun playRandomNoise(random: RandomSource, game: Minecraft, volume: Float) {
val volumeFactor = random.nextInt(100)
val sound = SimpleSoundInstance(
soundEvent.get().location,
SoundSource.AMBIENT,
when (volumeFactor) {
in 0..8 -> random.nextFloat() * 0.65f
in 10..15 -> random.nextFloat() * 0.5f + 0.5f
in 21..50 -> random.nextFloat() * 0.3f
else -> random.nextFloat() * 0.2f
} * volume,
random.nextFloat() + 0.4f,
RandomSource.create(random.nextLong()),
false,
0,
SoundInstance.Attenuation.NONE,
random.nextFloat() * 28.0 - 14,
random.nextFloat() * 12.0 - 2,
random.nextFloat() * 28.0 - 14,
true
)
game.soundManager.playDelayed(sound, random.nextInt(3))
}
}
object NoiseProjectionBlock : EntityProjectionBlock<NoiseProjectionEffect>(createProperties().lightLevel { 3 }) {
override val blockEntity = NoiseProjection.blockEntity
}
data class NoiseProjectionEffect(var rate: Int = 250, var amount: Int = 3, var volume: Float = 1.0f) :
ProjectionEffect(),
ProjectionEffectShell.Provider {
companion object {
const val TAG_RATE = "Rate"
const val TAG_AMOUNT = "Amount"
const val TAG_VOLUME = "Volume"
val maxAmount get() = QuaedamConfig.current.valuesInt["projection.noise.max_amount"] ?: 8
val maxRate get() = QuaedamConfig.current.valuesInt["projection.noise.max_rate"] ?: 300
}
override val type
get() = NoiseProjection.effect.get()!!
override fun toNbt(tag: CompoundTag) {
tag.putInt(TAG_RATE, rate)
tag.putInt(TAG_AMOUNT, amount)
tag.putFloat(TAG_VOLUME, volume)
}
override fun fromNbt(tag: CompoundTag, trusted: Boolean) {
rate = tag.getInt(TAG_RATE)
amount = tag.getInt(TAG_AMOUNT)
volume = tag.getFloat(TAG_VOLUME)
if (!trusted) {
amount = min(amount, maxAmount)
rate = min(rate, maxRate)
}
}
override fun createShell() = buildProjectionEffectShell(this) {
intSlider("quaedam.shell.noise.rate", ::rate, 0..maxRate step 5)
intSlider("quaedam.shell.noise.amount", ::amount, 0..maxAmount)
floatSlider("quaedam.shell.noise.volume", ::volume, 0.0f..1.0f, 0.1f)
}
}

View File

@@ -0,0 +1,72 @@
package quaedam.projection.misc
import net.minecraft.nbt.CompoundTag
import net.minecraft.world.item.BlockItem
import net.minecraft.world.item.Item
import quaedam.Quaedam
import quaedam.config.QuaedamConfig
import quaedam.projection.EntityProjectionBlock
import quaedam.projection.ProjectionEffect
import quaedam.projection.ProjectionEffectType
import quaedam.projection.SimpleProjectionEntity
import quaedam.shell.ProjectionEffectShell
import quaedam.shell.buildProjectionEffectShell
import kotlin.math.min
object SkylightProjection {
const val ID = "skylight_projection"
const val SHORT_ID = "skylight"
val block = Quaedam.blocks.register(ID) { SkylightProjectionBlock }!!
val item = Quaedam.items.register(ID) {
BlockItem(
SkylightProjectionBlock, Item.Properties()
.`arch$tab`(Quaedam.creativeModeTab)
)
}!!
val effect = Quaedam.projectionEffects.register(SHORT_ID) {
ProjectionEffectType { SkylightProjectionEffect() }
}!!
val blockEntity = Quaedam.blockEntities.register(ID) {
SimpleProjectionEntity.createBlockEntityType(block, ::SkylightProjectionEffect)
}!!
}
object SkylightProjectionBlock : EntityProjectionBlock<SkylightProjectionEffect>(createProperties().lightLevel { 3 }) {
override val blockEntity = SkylightProjection.blockEntity
}
data class SkylightProjectionEffect(var factor: Double = 2.0) : ProjectionEffect(), ProjectionEffectShell.Provider {
companion object {
const val TAG_FACTOR = "Factor"
val maxFactor get() = QuaedamConfig.current.valuesDouble["projection.skylight.max_factor"] ?: 5.0
}
override val type
get() = SkylightProjection.effect.get()!!
override fun toNbt(tag: CompoundTag) {
tag.putDouble(TAG_FACTOR, factor)
}
override fun fromNbt(tag: CompoundTag, trusted: Boolean) {
factor = tag.getDouble(TAG_FACTOR)
if (!trusted) {
factor = min(factor, maxFactor)
}
}
override fun createShell() = buildProjectionEffectShell(this) {
doubleSlider("quaedam.shell.skylight.factor", ::factor, 0.0..maxFactor, 0.1)
}
}

View File

@@ -0,0 +1,77 @@
package quaedam.projection.misc
import net.minecraft.nbt.CompoundTag
import net.minecraft.world.item.BlockItem
import net.minecraft.world.item.Item
import quaedam.Quaedam
import quaedam.config.QuaedamConfig
import quaedam.projection.EntityProjectionBlock
import quaedam.projection.ProjectionEffect
import quaedam.projection.ProjectionEffectType
import quaedam.projection.SimpleProjectionEntity
import quaedam.shell.ProjectionEffectShell
import quaedam.shell.buildProjectionEffectShell
import kotlin.math.min
object SoundProjection {
const val ID = "sound_projection"
const val SHORT_ID = "sound"
val block = Quaedam.blocks.register(ID) { SoundProjectionBlock }!!
val item = Quaedam.items.register(ID) {
BlockItem(
SoundProjectionBlock, Item.Properties()
.`arch$tab`(Quaedam.creativeModeTab)
)
}!!
val effect = Quaedam.projectionEffects.register(SHORT_ID) {
ProjectionEffectType { SoundProjectionEffect() }
}!!
val blockEntity = Quaedam.blockEntities.register(ID) {
SimpleProjectionEntity.createBlockEntityType(block) { SoundProjectionEffect() }
}!!
}
object SoundProjectionBlock : EntityProjectionBlock<SoundProjectionEffect>(createProperties().lightLevel { 3 }) {
override val blockEntity = SoundProjection.blockEntity
}
data class SoundProjectionEffect(var rate: Int = 60, var volume: Float = 1.0f) : ProjectionEffect(),
ProjectionEffectShell.Provider {
companion object {
const val TAG_RATE = "Rate"
const val TAG_VOLUME = "Volume"
val maxRate get() = QuaedamConfig.current.valuesInt["projection.sound.max_rate"] ?: 210
}
override val type
get() = SoundProjection.effect.get()!!
override fun toNbt(tag: CompoundTag) {
tag.putInt(TAG_RATE, rate)
tag.putFloat(TAG_VOLUME, volume)
}
override fun fromNbt(tag: CompoundTag, trusted: Boolean) {
rate = tag.getInt(TAG_RATE)
volume = tag.getFloat(TAG_VOLUME)
if (!trusted) {
rate = min(rate, maxRate)
}
}
override fun createShell() = buildProjectionEffectShell(this) {
intSlider("quaedam.shell.sound.rate", ::rate, 0..maxRate)
floatSlider("quaedam.shell.sound.volume", ::volume, 0.0f..1.0f, 0.1f)
}
}

View File

@@ -0,0 +1,91 @@
package quaedam.projection.music
import net.minecraft.world.level.block.state.properties.NoteBlockInstrument
import kotlin.math.abs
import kotlin.random.Random
import kotlin.random.nextInt
/**
* The composer for music.
* rhythmRandom is used for a better rhythm sync between different instruments.
*/
class Composer(val noteRandom: Random, val rhythmRandom: Random, val instrument: NoteBlockInstrument) {
data class Note(val note: Int, val volume: Float, val time: Int)
val baseTime = arrayOf(5, 5, 3, 3, 4, 4, 2, 2, 8).random(rhythmRandom)
val baseNote = noteRandom.nextInt(5..19)
val mayDropOut = instrument in arrayOf(
NoteBlockInstrument.BASEDRUM,
NoteBlockInstrument.HAT,
NoteBlockInstrument.SNARE,
)
fun composeMusic(): List<Note> {
var note = (0..rhythmRandom.nextInt(4)).flatMap { composeSection() }
note = decorate(note)
if (mayDropOut && rhythmRandom.nextInt(6) != 0) {
val dropRate = arrayOf(2, 3, 3, 4, 4, 4, 4, 6).random(rhythmRandom)
note = note.chunked(dropRate).map {
val first = it.first()
Note(first.note, first.volume, it.sumOf { note -> note.time })
}
}
return note
}
fun decorate(notes: List<Note>) = notes.map {
if (noteRandom.nextInt(4) == 0) {
doDecorate(it)
} else {
it
}
}
fun doDecorate(note: Note): Note {
var noteVal = note.note
if (noteRandom.nextInt(4) == 0) {
if (noteRandom.nextBoolean()) {
noteVal += 1
} else {
noteVal -= 1
}
}
var volume = note.volume
if (noteRandom.nextInt(4) == 0) {
volume *= noteRandom.nextFloat() * 0.8f + 0.6f
}
return Note(noteVal, volume, note.time)
}
fun composeSection(depth: Int = 0): List<Note> {
if (depth < 3 && rhythmRandom.nextBoolean()) {
val notes = (0..rhythmRandom.nextInt(3 - depth)).flatMap { composeSection(depth + 1) }
if (depth == 2) {
return (0..rhythmRandom.nextInt(3)).flatMap { notes }
} else {
return notes
}
} else {
var notePointer = baseNote + noteRandom.nextInt(-3..3)
var direction = -1
var directionCounter = 0
return (0..rhythmRandom.nextInt(4..16)).map {
if (directionCounter == 0) {
// start new direction
directionCounter = rhythmRandom.nextInt(2..6)
direction = if (directionCounter % 2 == 0) {
rhythmRandom.nextInt(-2..2)
} else {
noteRandom.nextInt(-3..3)
}
}
notePointer = abs(notePointer + direction) % 25
directionCounter--
Note(notePointer, 1.0f, baseTime + rhythmRandom.nextInt(-1..1))
}
}
}
}

View File

@@ -0,0 +1,128 @@
package quaedam.projection.music
import dev.architectury.utils.GameInstance
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.client.resources.sounds.SoundInstance
import net.minecraft.core.BlockPos
import net.minecraft.core.Holder
import net.minecraft.core.particles.ParticleTypes
import net.minecraft.nbt.CompoundTag
import net.minecraft.sounds.SoundEvent
import net.minecraft.sounds.SoundSource
import net.minecraft.util.RandomSource
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.NoteBlock
import net.minecraft.world.level.block.entity.SkullBlockEntity
import net.minecraft.world.level.block.state.properties.BlockStateProperties
import quaedam.projector.Projector
import kotlin.random.Random
class MusicPlayer(
val noteSeed: Long,
val rhythmSeed: Long,
val level: Level,
val pos: BlockPos,
val startedAt: Long = level.gameTime
) {
companion object {
const val TAG_NOTE_SEED = "NoteSeed"
const val TAG_RHYTHM_SEED = "RhythmSeed"
const val TAG_STARTED_AT = "StartedAt"
}
constructor(tag: CompoundTag, level: Level, pos: BlockPos) : this(
tag.getLong(TAG_NOTE_SEED),
tag.getLong(TAG_RHYTHM_SEED),
level,
pos,
tag.getLong(TAG_STARTED_AT)
)
var notes = Composer(
noteRandom = Random(noteSeed),
rhythmRandom = Random(rhythmSeed),
instrument = level.getBlockState(pos).getValue(BlockStateProperties.NOTEBLOCK_INSTRUMENT)
).composeMusic().toMutableList()
val totalTime = notes.sumOf { it.time }.toLong()
var remainingTime = totalTime
val isEnd get() = remainingTime <= 0 || notes.isEmpty()
var noteTime = 0
init {
val currentRemaining = totalTime - (level.gameTime - startedAt)
while (remainingTime > currentRemaining && !isEnd) {
// seek to current position
remainingTime -= fetchNote().time
}
}
private fun fetchNote() = notes.removeFirst()
fun tick() {
if (isEnd)
return
if (noteTime <= 0) {
// start new note
val note = fetchNote()
remainingTime -= note.time
noteTime = note.time
if (level.isClientSide) {
// play note
val projections = Projector.findNearbyProjections(level, pos, MusicProjection.effect.get())
.takeIf { it.isNotEmpty() } ?: listOf(MusicProjectionEffect())
val volume = projections.maxOf { it.volumeFactor } * note.volume
val particle = projections.any { it.particle }
val instrument = level.getBlockState(pos).getValue(BlockStateProperties.NOTEBLOCK_INSTRUMENT)
val pitch = if (instrument.isTunable) {
NoteBlock.getPitchFromNote(note.note)
} else {
1.0f
}
val holder = if (instrument.hasCustomSound()) {
val entity = level.getBlockEntity(pos.below())
(entity as? SkullBlockEntity)?.noteBlockSound?.let {
Holder.direct(SoundEvent.createVariableRangeEvent(it))
}
} else {
null
} ?: instrument.soundEvent
if (particle) {
level.addParticle(
ParticleTypes.NOTE,
pos.x.toDouble() + 0.5,
pos.y.toDouble() + 1.2,
pos.z.toDouble() + 0.5,
note.time.toDouble() / 24.0,
0.0,
0.0
)
}
val instance = SimpleSoundInstance(
holder.value().location,
SoundSource.RECORDS,
volume,
pitch,
RandomSource.create(level.random.nextLong()),
false, 0, SoundInstance.Attenuation.LINEAR,
pos.x.toDouble() + 0.5,
pos.y.toDouble() + 0.5,
pos.z.toDouble() + 0.5,
false
)
GameInstance.getClient().soundManager.play(instance)
}
}
noteTime--
}
fun toTag() = CompoundTag().apply {
putLong(TAG_NOTE_SEED, noteSeed)
putLong(TAG_RHYTHM_SEED, rhythmSeed)
putLong(TAG_STARTED_AT, startedAt)
}
}

View File

@@ -0,0 +1,85 @@
package quaedam.projection.music
import net.minecraft.nbt.CompoundTag
import net.minecraft.world.item.BlockItem
import net.minecraft.world.item.Item
import quaedam.Quaedam
import quaedam.config.QuaedamConfig
import quaedam.projection.EntityProjectionBlock
import quaedam.projection.ProjectionEffect
import quaedam.projection.ProjectionEffectType
import quaedam.projection.SimpleProjectionEntity
import quaedam.shell.ProjectionEffectShell
import quaedam.shell.buildProjectionEffectShell
import kotlin.math.min
object MusicProjection {
const val ID = "music_projection"
const val SHORT_ID = "music"
val block = Quaedam.blocks.register(ID) { MusicProjectionBlock }!!
val item = Quaedam.items.register(ID) {
BlockItem(
MusicProjectionBlock, Item.Properties()
.`arch$tab`(Quaedam.creativeModeTab)
)
}!!
val effect = Quaedam.projectionEffects.register(SHORT_ID) {
ProjectionEffectType { MusicProjectionEffect() }
}!!
val blockEntity = Quaedam.blockEntities.register(ID) {
SimpleProjectionEntity.createBlockEntityType(block) { MusicProjectionEffect() }
}!!
init {
SmartInstrument
}
}
object MusicProjectionBlock : EntityProjectionBlock<MusicProjectionEffect>(createProperties().lightLevel { 3 }) {
override val blockEntity = MusicProjection.blockEntity
}
data class MusicProjectionEffect(var volumeFactor: Float = 1.0f, var particle: Boolean = true) : ProjectionEffect(),
ProjectionEffectShell.Provider {
companion object {
const val TAG_VOLUME_FACTOR = "VolumeFactor"
const val TAG_PARTICLE = "Particle"
val maxVolumeFactor get() = QuaedamConfig.current.valuesFloat["projection.music.max_volume_factor"] ?: 5.0f
val enforceParticle get() = QuaedamConfig.current.valuesBoolean["projection.music.enforce_particle"]
}
override val type
get() = MusicProjection.effect.get()!!
override fun toNbt(tag: CompoundTag) {
tag.putFloat(TAG_VOLUME_FACTOR, volumeFactor)
tag.putBoolean(TAG_PARTICLE, particle)
}
override fun fromNbt(tag: CompoundTag, trusted: Boolean) {
volumeFactor = tag.getFloat(TAG_VOLUME_FACTOR)
particle = tag.getBoolean(TAG_PARTICLE)
if (!trusted) {
volumeFactor = min(volumeFactor, maxVolumeFactor)
particle = enforceParticle ?: particle
}
}
override fun createShell() = buildProjectionEffectShell(this) {
floatSlider("quaedam.shell.music.volume_factor", ::volumeFactor, 0.0f..maxVolumeFactor, 0.1f)
if (enforceParticle == null) {
boolean("quaedam.shell.music.particle", ::particle)
}
}
}

View File

@@ -0,0 +1,236 @@
package quaedam.projection.music
import net.minecraft.core.BlockPos
import net.minecraft.nbt.CompoundTag
import net.minecraft.network.protocol.Packet
import net.minecraft.network.protocol.game.ClientGamePacketListener
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket
import net.minecraft.server.level.ServerLevel
import net.minecraft.util.RandomSource
import net.minecraft.world.InteractionHand
import net.minecraft.world.InteractionResult
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.BlockItem
import net.minecraft.world.item.Item
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.EntityBlock
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.entity.BlockEntityTicker
import net.minecraft.world.level.block.entity.BlockEntityType
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.block.state.properties.BlockStateProperties
import net.minecraft.world.level.block.state.properties.NoteBlockInstrument
import net.minecraft.world.level.material.MapColor
import net.minecraft.world.phys.BlockHitResult
import quaedam.Quaedam
import quaedam.misc.causality.CausalityAnchor
import quaedam.projector.Projector
import quaedam.utils.getChunksNearby
import quaedam.utils.sendBlockUpdated
object SmartInstrument {
const val ID = "smart_instrument"
val block = Quaedam.blocks.register(ID) { SmartInstrumentBlock }!!
val item = Quaedam.items.register(ID) {
BlockItem(
SmartInstrumentBlock, Item.Properties()
.`arch$tab`(Quaedam.creativeModeTab)
)
}!!
val blockEntity = Quaedam.blockEntities.register(ID) {
BlockEntityType.Builder.of(::SmartInstrumentBlockEntity, block.get()).build(null)
}!!
}
object SmartInstrumentBlock : Block(
Properties.of()
.strength(2.7f)
.requiresCorrectToolForDrops()
.mapColor(MapColor.COLOR_BROWN)
.randomTicks()
), EntityBlock {
init {
registerDefaultState(
defaultBlockState()
.setValue(BlockStateProperties.NOTEBLOCK_INSTRUMENT, NoteBlockInstrument.HARP)
)
}
override fun newBlockEntity(pos: BlockPos, state: BlockState) = SmartInstrumentBlockEntity(pos, state)
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
super.createBlockStateDefinition(builder)
builder.add(BlockStateProperties.NOTEBLOCK_INSTRUMENT)
}
@Suppress("OVERRIDE_DEPRECATION", "DEPRECATION")
override fun neighborChanged(
state: BlockState,
level: Level,
pos: BlockPos,
neighborBlock: Block,
neighborPos: BlockPos,
movedByPiston: Boolean
) {
super.neighborChanged(state, level, pos, neighborBlock, neighborPos, movedByPiston)
level.setBlock(
pos,
state.setValue(BlockStateProperties.NOTEBLOCK_INSTRUMENT, level.getBlockState(pos.below()).instrument()),
UPDATE_ALL
)
}
@Suppress("OVERRIDE_DEPRECATION", "DEPRECATION")
override fun onPlace(state: BlockState, level: Level, pos: BlockPos, oldState: BlockState, movedByPiston: Boolean) {
super.onPlace(state, level, pos, oldState, movedByPiston)
level.setBlock(
pos,
state.setValue(BlockStateProperties.NOTEBLOCK_INSTRUMENT, level.getBlockState(pos.below()).instrument()),
UPDATE_ALL
)
}
@Suppress("OVERRIDE_DEPRECATION")
override fun randomTick(
state: BlockState,
level: ServerLevel,
pos: BlockPos,
random: RandomSource
) {
if (Projector.findNearbyProjections(level, pos, MusicProjection.effect.get()).isNotEmpty()) {
val entity = level.getBlockEntity(pos) as SmartInstrumentBlockEntity
if (entity.player == null) {
entity.startMusic()
}
}
}
@Suppress("OVERRIDE_DEPRECATION", "DEPRECATION")
override fun use(
state: BlockState,
level: Level,
pos: BlockPos,
player: Player,
hand: InteractionHand,
hit: BlockHitResult
): InteractionResult {
if (Projector.findNearbyProjections(level, pos, MusicProjection.effect.get()).isNotEmpty()
|| CausalityAnchor.checkEffect(level, pos)
) {
val entity = level.getBlockEntity(pos) as SmartInstrumentBlockEntity
if (entity.player == null && !level.isClientSide) {
entity.startMusic()
}
return InteractionResult.sidedSuccess(level.isClientSide)
}
return super.use(state, level, pos, player, hand, hit)
}
override fun <T : BlockEntity?> getTicker(
level: Level,
state: BlockState,
blockEntityType: BlockEntityType<T>
): BlockEntityTicker<T> {
return BlockEntityTicker { _, _, _, entity ->
(entity as? SmartInstrumentBlockEntity)?.tick()
}
}
}
class SmartInstrumentBlockEntity(pos: BlockPos, state: BlockState) :
BlockEntity(SmartInstrument.blockEntity.get(), pos, state) {
companion object {
const val TAG_MUSIC = "Music"
}
// delay MusicPlayer initialization until level is available
var playerData: CompoundTag? = null
var player: MusicPlayer? = null
override fun getUpdateTag(): CompoundTag = saveWithoutMetadata()
override fun getUpdatePacket(): Packet<ClientGamePacketListener> = ClientboundBlockEntityDataPacket.create(this)
override fun load(tag: CompoundTag) {
super.load(tag)
if (TAG_MUSIC in tag) {
try {
player = MusicPlayer(tag.getCompound(TAG_MUSIC), level!!, blockPos)
} catch (e: Throwable) {
playerData = tag.getCompound(TAG_MUSIC)
}
}
}
override fun saveAdditional(tag: CompoundTag) {
super.saveAdditional(tag)
if (playerData != null) {
tag.put(TAG_MUSIC, playerData!!)
}
if (player != null) {
tag.put(TAG_MUSIC, player!!.toTag())
}
}
private fun checkProjections() =
Projector.findNearbyProjections(level!!, blockPos, MusicProjection.effect.get()).isNotEmpty()
|| CausalityAnchor.checkEffect(level!!, blockPos)
fun startMusic(force: Boolean = false, synced: Boolean = false) {
if ((player == null || force) && !level!!.isClientSide && checkProjections()) {
player = MusicPlayer(level!!.random.nextLong(), level!!.gameTime / 20, level!!, blockPos)
setChanged()
sendBlockUpdated()
if (!synced) {
// sync start to other instruments
level!!.getChunksNearby(blockPos, 1)
.flatMap {
it.blockEntities
.filterValues { entity -> entity is SmartInstrumentBlockEntity }
.filterKeys { pos -> pos.distSqr(blockPos) < 100 }
.values
}
.filterNot { it == this }
.filterIsInstance<SmartInstrumentBlockEntity>()
.forEach { it.startMusic(force = true, synced = true) }
}
}
}
fun tick() {
if (playerData != null) {
player = MusicPlayer(playerData!!, level!!, blockPos)
playerData = null
}
if (player != null) {
if (checkProjections()) {
player!!.tick()
if (!level!!.isClientSide) {
if (player!!.isEnd) {
player = null
setChanged()
sendBlockUpdated()
if (CausalityAnchor.checkEffect(level!!, blockPos) || level!!.random.nextInt(7) != 0) {
startMusic()
}
}
}
} else {
player = null
setChanged()
sendBlockUpdated()
}
}
}
}

View File

@@ -1,142 +0,0 @@
package quaedam.projection.swarm
import com.google.common.collect.ImmutableList
import net.minecraft.world.entity.ai.Brain
import net.minecraft.world.entity.ai.behavior.*
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.entity.ai.sensing.SensorType
import net.minecraft.world.entity.schedule.Activity
import net.minecraft.world.entity.schedule.Schedule
import net.minecraft.world.entity.schedule.ScheduleBuilder
import quaedam.Quaedam
import quaedam.utils.weight
import quaedam.utils.weightR
object ProjectedPersonAI {
private val memoryTypes = listOf(
MemoryModuleType.PATH,
MemoryModuleType.LOOK_TARGET,
MemoryModuleType.WALK_TARGET,
MemoryModuleType.ATTACK_TARGET,
MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES,
MemoryModuleType.NEAREST_VISIBLE_WANTED_ITEM,
MemoryModuleType.HURT_BY,
MemoryModuleType.ATTACK_COOLING_DOWN,
MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE,
)
private val sensorTypes = listOf(
SensorType.NEAREST_LIVING_ENTITIES,
SensorType.NEAREST_PLAYERS,
SensorType.HURT_BY,
SensorType.NEAREST_ITEMS,
)
val defaultSchedule = Quaedam.schedule.register("projected_person_default") {
ScheduleBuilder(Schedule()).changeActivityAt(10, Activity.IDLE)
.changeActivityAt(10, Activity.IDLE)
.changeActivityAt(2000, Activity.WORK)
.changeActivityAt(7300, Activity.IDLE)
.changeActivityAt(9000, Activity.WORK)
.changeActivityAt(10700, Activity.IDLE)
.changeActivityAt(11000, Activity.PLAY)
.changeActivityAt(11500, Activity.IDLE)
.changeActivityAt(12000, Activity.REST)
.build()
}
val babySchedule = Quaedam.schedule.register("projected_person_baby") {
ScheduleBuilder(Schedule()).changeActivityAt(10, Activity.IDLE)
.changeActivityAt(10, Activity.IDLE)
.changeActivityAt(3200, Activity.PLAY)
.changeActivityAt(7000, Activity.IDLE)
.changeActivityAt(9000, Activity.PLAY)
.changeActivityAt(11000, Activity.REST)
.build()
}
fun provider(): Brain.Provider<out ProjectedPersonEntity> = Brain.provider(memoryTypes, sensorTypes)
fun initBrain(entity: ProjectedPersonEntity, brain: Brain<ProjectedPersonEntity>) {
initCoreActivity(brain)
initIdleActivity(brain)
initPlayActivity(brain)
initWorkActivity(brain)
initRestActivity(brain)
brain.setCoreActivities(setOf(Activity.CORE))
brain.setDefaultActivity(Activity.IDLE)
updateSchedule(entity, brain, baby = false)
}
fun updateSchedule(entity: ProjectedPersonEntity, brain: Brain<ProjectedPersonEntity>, baby: Boolean) {
if (baby) {
brain.schedule = babySchedule.get()
} else {
brain.schedule = defaultSchedule.get()
}
brain.updateActivityFromSchedule(entity.level().dayTime, entity.level().gameTime)
}
fun updateSchedule(entity: ProjectedPersonEntity) = updateSchedule(entity, entity.brain, entity.shape.baby)
private fun initCoreActivity(brain: Brain<ProjectedPersonEntity>) {
brain.addActivity(
Activity.CORE, ImmutableList.of(
0 weight Swim(0.8f),
0 weight InteractWithDoor.create(),
0 weight LookAtTargetSink(40, 70),
0 weight MoveToTargetSink(),
0 weight WakeUp.create(),
3 weight GoToWantedItem.create(1.2f, false, 7),
)
)
}
private fun initIdleActivity(brain: Brain<ProjectedPersonEntity>) {
brain.addActivity(
Activity.IDLE, ImmutableList.of(
3 weight createStrollBehavior(),
99 weight UpdateActivityFromSchedule.create(),
)
)
}
private fun initPlayActivity(brain: Brain<ProjectedPersonEntity>) {
brain.addActivity(
Activity.PLAY, ImmutableList.of(
3 weight GoToWantedItem.create(1.75f, true, 32),
5 weight JumpOnBed(1.0f),
5 weight createStrollBehavior(),
99 weight UpdateActivityFromSchedule.create(),
)
)
}
private fun initWorkActivity(brain: Brain<ProjectedPersonEntity>) {
brain.addActivity(
Activity.WORK, ImmutableList.of(
3 weight createStrollBehavior(),
99 weight UpdateActivityFromSchedule.create(),
)
)
}
private fun initRestActivity(brain: Brain<ProjectedPersonEntity>) {
brain.addActivity(
Activity.REST, ImmutableList.of(
3 weight createStrollBehavior(),
99 weight UpdateActivityFromSchedule.create(),
)
)
}
private fun createStrollBehavior() = RunOne(
listOf(
2 weightR RandomStroll.stroll(1.0f),
2 weightR SetWalkTargetFromLookTarget.create(1.0f, 5),
1 weightR DoNothing(30, 60)
)
)
}

View File

@@ -4,6 +4,7 @@ import com.mojang.serialization.Dynamic
import dev.architectury.platform.Platform
import dev.architectury.registry.level.entity.EntityAttributeRegistry
import net.fabricmc.api.EnvType
import net.minecraft.core.BlockPos
import net.minecraft.nbt.CompoundTag
import net.minecraft.network.chat.Component
import net.minecraft.network.protocol.game.DebugPackets
@@ -11,10 +12,12 @@ import net.minecraft.network.syncher.EntityDataAccessor
import net.minecraft.network.syncher.EntityDataSerializers
import net.minecraft.network.syncher.SynchedEntityData
import net.minecraft.server.level.ServerLevel
import net.minecraft.sounds.SoundEvent
import net.minecraft.world.DifficultyInstance
import net.minecraft.world.SimpleContainer
import net.minecraft.world.entity.*
import net.minecraft.world.entity.ai.Brain
import net.minecraft.world.entity.ai.attributes.AttributeModifier
import net.minecraft.world.entity.ai.attributes.AttributeSupplier
import net.minecraft.world.entity.ai.attributes.Attributes
import net.minecraft.world.entity.ai.memory.MemoryModuleType
@@ -23,8 +26,12 @@ import net.minecraft.world.entity.npc.InventoryCarrier
import net.minecraft.world.level.Level
import net.minecraft.world.level.ServerLevelAccessor
import quaedam.Quaedam
import quaedam.misc.causality.CausalityAnchor
import quaedam.projection.misc.SoundProjection
import quaedam.projection.swarm.ai.ProjectedPersonAI
import quaedam.projection.swarm.ai.ProjectedPersonNavigation
import quaedam.projector.Projector
import kotlin.jvm.optionals.getOrNull
import kotlin.random.Random
class ProjectedPersonEntity(entityType: EntityType<out PathfinderMob>, level: Level) : PathfinderMob(entityType, level),
InventoryCarrier {
@@ -37,6 +44,7 @@ class ProjectedPersonEntity(entityType: EntityType<out PathfinderMob>, level: Le
const val BOUNDING_WIDTH = 0.6f
const val BOUNDING_HEIGHT = 1.8f
const val INV_DIFF_NAME = "quaedam:Random Individual Differences"
val entity = Quaedam.entities.register(ID) {
EntityType.Builder.of(::ProjectedPersonEntity, MobCategory.CREATURE).canSpawnFarFromPlayer()
@@ -46,20 +54,28 @@ class ProjectedPersonEntity(entityType: EntityType<out PathfinderMob>, level: Le
val dataShape =
SynchedEntityData.defineId(ProjectedPersonEntity::class.java, EntityDataSerializers.COMPOUND_TAG)
const val SOUND_NOISE_ID = "entity.projected_person.noise"
val soundNoise = Quaedam.soundEvents.register(SOUND_NOISE_ID) {
SoundEvent.createVariableRangeEvent(Quaedam.resource(SOUND_NOISE_ID))
}!!
init {
EntityAttributeRegistry.register(entity, ::createAttributes)
Quaedam.lateinit += { EntityAttributeRegistry.register(entity, ::createAttributes) }
if (Platform.getEnv() == EnvType.CLIENT) ProjectedPersonRenderer
ProjectedPersonShape
ProjectedPersonAI
}
private fun createAttributes(): AttributeSupplier.Builder =
Mob.createMobAttributes().add(Attributes.ATTACK_DAMAGE, 1.5)
.add(Attributes.MOVEMENT_SPEED, 0.2)
Mob.createMobAttributes().add(Attributes.ATTACK_DAMAGE, 1.5).add(Attributes.MOVEMENT_SPEED, 0.2)
.add(Attributes.ATTACK_SPEED)
}
init {
setCanPickUpLoot(true)
}
override fun finalizeSpawn(
serverLevelAccessor: ServerLevelAccessor,
difficultyInstance: DifficultyInstance,
@@ -67,7 +83,38 @@ class ProjectedPersonEntity(entityType: EntityType<out PathfinderMob>, level: Le
spawnGroupData: SpawnGroupData?,
compoundTag: CompoundTag?
): SpawnGroupData? {
shape = ProjectedPersonShape.create(serverLevelAccessor.random.nextLong())
val rand = Random(serverLevelAccessor.random.nextLong())
// random shape
shape = ProjectedPersonShape.create(rand.nextLong())
// random attributes
getAttribute(Attributes.MOVEMENT_SPEED)!!.addPermanentModifier(
AttributeModifier(
INV_DIFF_NAME,
rand.nextFloat() * 0.1,
AttributeModifier.Operation.ADDITION
)
)
getAttribute(Attributes.ATTACK_DAMAGE)!!.addPermanentModifier(
AttributeModifier(
INV_DIFF_NAME,
rand.nextFloat() * 1.5,
AttributeModifier.Operation.ADDITION
)
)
getAttribute(Attributes.ATTACK_SPEED)!!.addPermanentModifier(
AttributeModifier(
INV_DIFF_NAME,
rand.nextFloat() * -2.0,
AttributeModifier.Operation.ADDITION
)
)
getAttribute(Attributes.MAX_HEALTH)!!.addPermanentModifier(
AttributeModifier(
INV_DIFF_NAME,
rand.nextFloat() * 5.0,
AttributeModifier.Operation.ADDITION
)
)
return super.finalizeSpawn(serverLevelAccessor, difficultyInstance, mobSpawnType, spawnGroupData, compoundTag)
}
@@ -104,6 +151,7 @@ class ProjectedPersonEntity(entityType: EntityType<out PathfinderMob>, level: Le
super.readAdditionalSaveData(tag)
shapeTag = tag.getCompound(KEY_ENTITY_SHAPE)
readInventoryFromTag(tag)
setCanPickUpLoot(true)
}
override fun shouldShowName() = true
@@ -111,14 +159,17 @@ class ProjectedPersonEntity(entityType: EntityType<out PathfinderMob>, level: Le
override fun getTypeName(): Component =
shape.name.takeIf { it.isNotEmpty() }?.let { Component.literal(it) } ?: super.getTypeName()
override fun getNameTagOffsetY() = super.getNameTagOffsetY() - (BOUNDING_HEIGHT * (1.3f - shape.scaleY))
override fun getNameTagOffsetY() = super.getNameTagOffsetY() - (bbHeight * (1f - shape.scaleY))
override fun createNavigation(level: Level) = ProjectedPersonNavigation(this, level)
override fun tick() {
super.tick()
if (tickCount % 20 == 0) {
if (!checkProjectionEffect()) remove(RemovalReason.KILLED)
if (!checkProjectionEffect() && !CausalityAnchor.checkEffect(level(), blockPosition())) {
dropEquipment()
remove(RemovalReason.KILLED)
}
}
}
@@ -126,8 +177,12 @@ class ProjectedPersonEntity(entityType: EntityType<out PathfinderMob>, level: Le
Projector.findNearbyProjections(level(), blockPosition(), SwarmProjection.effect.get()).isNotEmpty()
override fun checkDespawn() {
super.checkDespawn()
if (!checkProjectionEffect()) discard()
// no despawn
// super.checkDespawn()
if (!checkProjectionEffect() && !CausalityAnchor.checkEffect(level(), blockPosition())) {
dropEquipment()
remove(RemovalReason.KILLED)
}
}
private val inventory = SimpleContainer(10)
@@ -163,4 +218,48 @@ class ProjectedPersonEntity(entityType: EntityType<out PathfinderMob>, level: Le
override fun isBaby() = shape.baby
override fun startSleeping(blockPos: BlockPos) {
super.startSleeping(blockPos)
brain.eraseMemory(MemoryModuleType.WALK_TARGET)
brain.eraseMemory(MemoryModuleType.LOOK_TARGET)
brain.eraseMemory(MemoryModuleType.NEAREST_BED)
brain.eraseMemory(MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE)
}
override fun stopSleeping() {
super.stopSleeping()
brain.setMemory(MemoryModuleType.LAST_WOKEN, level().gameTime)
brain.eraseMemory(MemoryModuleType.HOME)
}
override fun dropEquipment() {
super.dropEquipment()
inventory.removeAllItems().forEach(::spawnAtLocation)
}
fun findNearbySoundProjection() =
Projector.findNearbyProjections(level(), blockPosition(), SoundProjection.effect.get())
override fun isSilent() =
super.isSilent() && findNearbySoundProjection().isEmpty()
override fun getAmbientSound(): SoundEvent? {
if (findNearbySoundProjection().isNotEmpty()) {
// sound projection available
return soundNoise.get()
}
return null
}
override fun getSoundVolume() =
super.getSoundVolume() * (random.nextFloat() * 1.1f + 0.4f) *
findNearbySoundProjection().fold(1.0f) { v, p -> v * p.volume }
override fun getVoicePitch() = super.getVoicePitch() * (random.nextFloat() * 0.55f + 0.7f)
override fun getAmbientSoundInterval() =
80 - random.nextInt((findNearbySoundProjection().firstOrNull()?.rate ?: 1) * 5)
override fun isEffectiveAi() = super.isEffectiveAi() && checkProjectionEffect()
}

View File

@@ -10,6 +10,7 @@ import net.minecraft.client.renderer.entity.EntityRendererProvider
import net.minecraft.client.renderer.entity.MobRenderer
import net.minecraft.client.renderer.entity.layers.CustomHeadLayer
import net.minecraft.client.renderer.entity.layers.ItemInHandLayer
import quaedam.Quaedam
@Environment(EnvType.CLIENT)
class ProjectedPersonRenderer(context: EntityRendererProvider.Context) :
@@ -21,7 +22,12 @@ class ProjectedPersonRenderer(context: EntityRendererProvider.Context) :
companion object {
init {
EntityRendererRegistry.register(ProjectedPersonEntity.entity, ::ProjectedPersonRenderer)
Quaedam.lateinit += {
EntityRendererRegistry.register(
ProjectedPersonEntity.entity,
::ProjectedPersonRenderer
)
}
}
}

View File

@@ -72,7 +72,7 @@ data class ProjectedPersonShape(
object Names {
val id = ResourceLocation("quaedam", "projected-person-names")
val id = Quaedam.resource("projected-person-names")
var names = emptySet<String>()
@@ -109,7 +109,7 @@ data class ProjectedPersonShape(
object Skins {
val id = ResourceLocation("quaedam", "skins")
val id = Quaedam.resource("skins")
var skins = emptyList<ResourceLocation>()

View File

@@ -4,6 +4,7 @@ import net.minecraft.world.item.BlockItem
import net.minecraft.world.item.Item
import quaedam.Quaedam
import quaedam.projection.ProjectionEffectType
import quaedam.projection.SimpleProjectionEntity
object SwarmProjection {
@@ -23,6 +24,10 @@ object SwarmProjection {
ProjectionEffectType { SwarmProjectionEffect() }
}!!
val blockEntity = Quaedam.blockEntities.register(ID) {
SimpleProjectionEntity.createBlockEntityType(block, ::SwarmProjectionEffect)
}!!
init {
ProjectedPersonEntity
}

View File

@@ -1,16 +1,9 @@
package quaedam.projection.swarm
import net.minecraft.core.BlockPos
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.level.block.state.BlockState
import quaedam.projection.ProjectionBlock
import quaedam.projection.EntityProjectionBlock
object SwarmProjectionBlock : ProjectionBlock<SwarmProjectionEffect>() {
object SwarmProjectionBlock : EntityProjectionBlock<SwarmProjectionEffect>() {
override fun createProjectionEffect(
level: ServerLevel,
state: BlockState,
pos: BlockPos
) = SwarmProjectionEffect()
override val blockEntity = SwarmProjection.blockEntity
}

View File

@@ -3,14 +3,24 @@ package quaedam.projection.swarm
import net.minecraft.core.BlockPos
import net.minecraft.nbt.CompoundTag
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.entity.MobSpawnType
import net.minecraft.world.level.levelgen.Heightmap
import quaedam.config.QuaedamConfig
import quaedam.projection.ProjectionEffect
import quaedam.projector.Projector
import quaedam.projector.ProjectorBlockEntity
import quaedam.shell.ProjectionEffectShell
import quaedam.shell.buildProjectionEffectShell
import kotlin.math.min
data class SwarmProjectionEffect(
var maxCount: Int = 10,
) : ProjectionEffect() {
var maxCount: Int = 180,
) : ProjectionEffect(), ProjectionEffectShell.Provider {
companion object {
const val TAG_MAX_COUNT = "MaxCount"
val maxMaxCount get() = QuaedamConfig.current.valuesInt["projection.swarm.max_max_count"] ?: 250
}
override val type
@@ -20,11 +30,40 @@ data class SwarmProjectionEffect(
tag.putInt(TAG_MAX_COUNT, maxCount)
}
override fun fromNbt(tag: CompoundTag) {
override fun fromNbt(tag: CompoundTag, trusted: Boolean) {
maxCount = tag.getInt(TAG_MAX_COUNT)
if (!trusted) {
maxCount = min(maxCount, maxMaxCount)
}
}
override fun randomTick(level: ServerLevel, pos: BlockPos) {
val projector = level.getBlockEntity(pos) as ProjectorBlockEntity
val entities = level.getEntitiesOfClass(ProjectedPersonEntity::class.java, projector.effectAreaAABB).size
if (entities < maxCount) {
val area = projector.effectArea
for (i in 0..(min(level.random.nextInt(maxCount - entities), 6))) {
var spawnPos = BlockPos(
level.random.nextInt(area.minX(), area.maxX()),
area.minY(),
level.random.nextInt(area.minZ(), area.maxZ()),
)
spawnPos = spawnPos.atY(level.getHeight(Heightmap.Types.WORLD_SURFACE, spawnPos.x, spawnPos.z))
if (Projector.findNearbyProjections(level, spawnPos, SwarmProjection.effect.get()).isEmpty())
continue
val belowState = level.getBlockState(spawnPos.below())
val state = level.getBlockState(spawnPos)
if (belowState.isAir || !belowState.fluidState.isEmpty || !belowState.canOcclude())
continue
if (!state.fluidState.isEmpty)
continue
ProjectedPersonEntity.entity.get().spawn(level, spawnPos, MobSpawnType.TRIGGERED)
}
}
}
override fun createShell() = buildProjectionEffectShell(this) {
intSlider("quaedam.shell.swarm.max_count", ::maxCount, 0..maxMaxCount step 5)
}
}

View File

@@ -0,0 +1,58 @@
package quaedam.projection.swarm.ai
import net.minecraft.core.GlobalPos
import net.minecraft.world.entity.ai.behavior.AcquirePoi
import net.minecraft.world.entity.ai.behavior.StrollAroundPoi
import net.minecraft.world.entity.ai.behavior.StrollToPoi
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.entity.ai.village.poi.PoiType
import net.minecraft.world.entity.ai.village.poi.PoiTypes
import net.minecraft.world.level.block.Blocks
import quaedam.Quaedam
import quaedam.projection.music.SmartInstrumentBlock
import java.util.*
object AmusementAI {
const val ID = "amusement"
val poiType = Quaedam.poiTypes.register(ID) {
PoiType(
setOf(
Blocks.NOTE_BLOCK,
SmartInstrumentBlock,
Blocks.HONEY_BLOCK,
Blocks.TARGET,
).flatMap { it.stateDefinition.possibleStates }.toSet(),
16, 10
)
}!!
val poiTypes by lazy {
setOf(
poiType.key,
PoiTypes.LIBRARIAN,
PoiTypes.MEETING,
)
}
val memory = Quaedam.memoryTypes.register(ID) {
MemoryModuleType(Optional.of(GlobalPos.CODEC))
}!!
fun createAcquirePoi() =
AcquirePoi.create({ it.`is` { key -> key in poiTypes } }, memory.get(), false, Optional.empty())
fun createStrollToPoi() =
StrollToPoi.create(memory.get(), 0.4f, 7, 15)
fun createStrollToPoiBaby() =
StrollToPoi.create(memory.get(), 0.7f, 5, 10)
fun createStrollAroundPoi() =
StrollAroundPoi.create(memory.get(), 0.4f, 10)
fun createStrollAroundPoiBaby() =
StrollAroundPoi.create(memory.get(), 0.55f, 8)
}

View File

@@ -0,0 +1,46 @@
package quaedam.projection.swarm.ai
import net.minecraft.core.GlobalPos
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.entity.ai.memory.MemoryStatus
import net.minecraft.world.entity.ai.sensing.Sensor
import net.minecraft.world.entity.ai.sensing.SensorType
import net.minecraft.world.level.block.BedBlock
import net.minecraft.world.level.block.entity.BedBlockEntity
import net.minecraft.world.level.block.state.properties.BedPart
import quaedam.Quaedam
class BedInChunkSensor : Sensor<LivingEntity>() {
companion object {
const val ID = "bed_in_chunk"
val sensor = Quaedam.sensors.register(ID) {
SensorType(::BedInChunkSensor)
}
}
override fun requires() = setOf(MemoryModuleType.NEAREST_BED)
override fun doTick(level: ServerLevel, entity: LivingEntity) {
if (entity.tickCount and 0b11111 == 0 && !entity.isSleeping) { // 32gt
level.getChunkAt(entity.blockPosition()).blockEntities
.filterValues { it is BedBlockEntity }
.keys
.filter { level.getBlockState(it).getValue(BedBlock.PART) == BedPart.HEAD }
.filter { !level.getBlockState(it).getValue(BedBlock.OCCUPIED) }
.minByOrNull { it.distManhattan(entity.blockPosition()) }
?.also { entity.brain.setMemory(MemoryModuleType.NEAREST_BED, it) }
?.also {
if (entity.brain.checkMemory(MemoryModuleType.HOME, MemoryStatus.REGISTERED)) {
entity.brain.setMemory(MemoryModuleType.HOME, GlobalPos.of(level.dimension(), it))
}
}
}
}
}

View File

@@ -0,0 +1,132 @@
package quaedam.projection.swarm.ai
import net.minecraft.core.BlockPos
import net.minecraft.server.level.ServerLevel
import net.minecraft.sounds.SoundEvents
import net.minecraft.world.Container
import net.minecraft.world.entity.Mob
import net.minecraft.world.entity.ai.behavior.Behavior
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.entity.ai.memory.MemoryStatus
import net.minecraft.world.entity.ai.memory.WalkTarget
import net.minecraft.world.entity.npc.InventoryCarrier
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.block.entity.BaseContainerBlockEntity
import net.minecraft.world.level.block.entity.ChestBlockEntity
import kotlin.math.min
class ExchangeItem<E> : Behavior<E>(
mapOf(
MemoryModuleType.WALK_TARGET to MemoryStatus.VALUE_ABSENT,
NearestVisibleContainer.memory.get() to MemoryStatus.VALUE_PRESENT,
), 5 * 20, 12 * 20
) where E : Mob, E : InventoryCarrier {
private var target: BlockPos? = null
private var closeAt: Long? = null
override fun start(level: ServerLevel, entity: E, l: Long) {
target = entity.brain.getMemory(NearestVisibleContainer.memory.get()).get()
closeAt = null
entity.brain.setMemory(MemoryModuleType.WALK_TARGET, WalkTarget(target!!, 1.0f, 2))
entity.brain.eraseMemory(MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE)
}
override fun canStillUse(level: ServerLevel, owner: E, gameTime: Long) =
owner.brain.getMemory(MemoryModuleType.WALK_TARGET).isPresent
|| owner.brain.getMemory(MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE).isEmpty
|| (closeAt != null && closeAt!! < gameTime)
override fun tick(level: ServerLevel, owner: E, gameTime: Long) {
if (closeAt == null) {
if (owner.brain.getMemory(MemoryModuleType.WALK_TARGET).isEmpty) {
// reached
val chest = level.getBlockEntity(target!!) ?: return
if (chest !is BaseContainerBlockEntity)
return
if (chest is ChestBlockEntity) {
ChestBlockEntity.playSound(level, target!!, level.getBlockState(target!!), SoundEvents.CHEST_OPEN)
}
if (chest.isEmpty && level.random.nextBoolean()) {
closeAt = gameTime + 7
} else {
closeAt = gameTime + 10 + level.random.nextInt(100)
exchangeItems(level, owner)
}
}
}
}
override fun stop(level: ServerLevel, owner: E, gameTime: Long) {
owner.brain.eraseMemory(MemoryModuleType.WALK_TARGET)
owner.brain.eraseMemory(MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE)
if (closeAt != null) {
// opened
val chest = level.getBlockEntity(target!!) ?: return
if (chest is ChestBlockEntity) {
ChestBlockEntity.playSound(level, target!!, level.getBlockState(target!!), SoundEvents.CHEST_CLOSE)
}
}
}
private fun exchangeItems(level: ServerLevel, entity: E) {
val container = level.getBlockEntity(target!!) ?: return
if (container !is Container)
return
val inventory = entity.inventory
for (i in 1..10) {
val maxCount = 1 + level.random.nextInt(16)
if (level.random.nextBoolean()) {
// take
val slot = level.random.nextInt(container.containerSize)
val item = container.getItem(slot)
if (!item.isEmpty) {
val takeCount = min(item.count, maxCount)
val takeItem = item.copyWithCount(takeCount)
if (entity.canHoldItem(takeItem)) {
val remaining = inventory.addItem(/*entity.equipItemIfPossible(takeItem)*/ takeItem)
val actualCount = takeCount - remaining.count
item.shrink(actualCount)
container.setItem(slot, item)
}
}
} else {
// put
val slot = level.random.nextInt(inventory.containerSize)
val item = inventory.getItem(slot)
if (!item.isEmpty) {
val takeCount = min(item.count, maxCount)
val takeItem = item.copyWithCount(takeCount)
for (target in 0 until container.containerSize) {
val targetItem = container.getItem(target)
if (ItemStack.isSameItemSameTags(targetItem, takeItem)) {
val resultCount = min(targetItem.count + takeItem.count, item.maxStackSize)
val putCount = resultCount - targetItem.count
if (putCount != 0) {
targetItem.grow(putCount)
container.setItem(target, targetItem)
takeItem.shrink(putCount)
if (takeItem.isEmpty) break
}
}
}
if (!takeItem.isEmpty) {
for (target in 0 until container.containerSize) {
val targetItem = container.getItem(target)
if (targetItem.isEmpty) {
container.setItem(target, takeItem.copyAndClear())
break
}
}
}
val putCount = takeCount - takeItem.count
item.shrink(putCount)
inventory.setItem(slot, item)
}
}
}
container.setChanged()
inventory.setChanged()
}
}

View File

@@ -0,0 +1,31 @@
package quaedam.projection.swarm.ai
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.behavior.OneShot
import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder
import net.minecraft.world.entity.ai.behavior.declarative.Trigger
import net.minecraft.world.entity.item.ItemEntity
import net.minecraft.world.entity.npc.InventoryCarrier
import net.minecraft.world.entity.schedule.Activity
@Suppress("FunctionName")
fun <E> LostItem(chance: Int): OneShot<E>
where E : LivingEntity, E : InventoryCarrier = BehaviorBuilder.create { instance ->
instance.point(Trigger { level, entity: E, l: Long ->
if (entity.brain.isActive(Activity.REST)) return@Trigger false
if (level.random.nextInt(chance) != 0) return@Trigger false
val inventory = entity.inventory
val item = inventory.getItem(level.random.nextInt(inventory.containerSize))
if (!item.isEmpty) {
val count = level.random.nextInt(item.count)
item.shrink(count)
inventory.setChanged()
val itemEntity = ItemEntity(level, entity.x, entity.y + 0.25, entity.z, item.copyWithCount(count))
itemEntity.setDefaultPickUpDelay()
level.addFreshEntity(itemEntity)
return@Trigger true
} else {
return@Trigger false
}
})
}

View File

@@ -0,0 +1,44 @@
package quaedam.projection.swarm.ai
import net.minecraft.core.BlockPos
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.entity.ai.sensing.Sensor
import net.minecraft.world.entity.ai.sensing.SensorType
import net.minecraft.world.level.block.entity.BaseContainerBlockEntity
import quaedam.Quaedam
import quaedam.utils.getChunksNearby
import java.util.*
import kotlin.random.Random
class NearestVisibleContainer : Sensor<LivingEntity>() {
companion object {
const val ID = "nearest_visible_container"
val sensor = Quaedam.sensors.register(ID) {
SensorType(::NearestVisibleContainer)
}!!
val memory = Quaedam.memoryTypes.register(ID) {
MemoryModuleType(Optional.of(BlockPos.CODEC))
}!!
}
override fun requires() = setOf(memory.get())
override fun doTick(level: ServerLevel, entity: LivingEntity) {
if (entity.tickCount and 0b11111 == 0) { // 32gt
val pos = level.getChunksNearby(entity.blockPosition(), 1)
.flatMap { it.blockEntities.filterValues { be -> be is BaseContainerBlockEntity }.keys }
.sortedBy { it.distManhattan(entity.blockPosition()) / 5 }
.shuffled(Random(System.currentTimeMillis() / 10000))
.firstOrNull()
entity.brain.setMemory(memory.get(), pos)
}
}
}

View File

@@ -0,0 +1,208 @@
package quaedam.projection.swarm.ai
import com.google.common.collect.ImmutableList
import net.minecraft.core.registries.Registries
import net.minecraft.tags.TagKey
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.Brain
import net.minecraft.world.entity.ai.behavior.*
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.entity.ai.memory.MemoryStatus
import net.minecraft.world.entity.ai.memory.NearestVisibleLivingEntities
import net.minecraft.world.entity.ai.sensing.SensorType
import net.minecraft.world.entity.monster.Monster
import net.minecraft.world.entity.schedule.Activity
import net.minecraft.world.entity.schedule.Schedule
import net.minecraft.world.entity.schedule.ScheduleBuilder
import quaedam.Quaedam
import quaedam.projection.swarm.ProjectedPersonEntity
import quaedam.utils.weight
import quaedam.utils.weightR
import java.util.*
import kotlin.jvm.optionals.getOrNull
object ProjectedPersonAI {
val tagEnemy = TagKey.create(Registries.ENTITY_TYPE, Quaedam.resource("projected_person/enemy"))
val tagNoAttack = TagKey.create(Registries.ENTITY_TYPE, Quaedam.resource("projected_person/no_attack"))
val defaultSchedule = Quaedam.schedules.register("projected_person_default") {
ScheduleBuilder(Schedule()).changeActivityAt(10, Activity.IDLE)
.changeActivityAt(10, Activity.IDLE)
.changeActivityAt(900, Activity.WORK)
.changeActivityAt(6300, Activity.IDLE)
.changeActivityAt(9000, Activity.WORK)
.changeActivityAt(10700, Activity.IDLE)
.changeActivityAt(11000, Activity.PLAY)
.changeActivityAt(11500, Activity.IDLE)
.changeActivityAt(12000, Activity.REST)
.build()
}
val babySchedule = Quaedam.schedules.register("projected_person_baby") {
ScheduleBuilder(Schedule()).changeActivityAt(10, Activity.IDLE)
.changeActivityAt(10, Activity.IDLE)
.changeActivityAt(3200, Activity.PLAY)
.changeActivityAt(7000, Activity.IDLE)
.changeActivityAt(9000, Activity.PLAY)
.changeActivityAt(11000, Activity.REST)
.build()
}
init {
BedInChunkSensor
AmusementAI
WorkPoiAI
NearestVisibleContainer
}
private val memoryTypes by lazy {
listOf(
MemoryModuleType.PATH,
MemoryModuleType.LOOK_TARGET,
MemoryModuleType.WALK_TARGET,
MemoryModuleType.ATTACK_TARGET,
MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES,
MemoryModuleType.NEAREST_VISIBLE_WANTED_ITEM,
MemoryModuleType.HURT_BY,
MemoryModuleType.ATTACK_COOLING_DOWN,
MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE,
MemoryModuleType.HOME,
MemoryModuleType.LAST_WOKEN,
MemoryModuleType.NEAREST_BED,
NearestVisibleContainer.memory.get(),
)
}
private val sensorTypes by lazy {
listOf(
SensorType.NEAREST_LIVING_ENTITIES,
SensorType.NEAREST_PLAYERS,
SensorType.NEAREST_ITEMS,
SensorType.HURT_BY,
BedInChunkSensor.sensor.get(),
NearestVisibleContainer.sensor.get(),
)
}
fun provider(): Brain.Provider<out ProjectedPersonEntity> = Brain.provider(memoryTypes, sensorTypes)
fun initBrain(entity: ProjectedPersonEntity, brain: Brain<ProjectedPersonEntity>) {
initCoreActivity(brain)
initIdleActivity(brain)
initPlayActivity(brain)
initWorkActivity(brain)
initRestActivity(brain)
brain.setCoreActivities(setOf(Activity.CORE))
brain.setDefaultActivity(Activity.IDLE)
updateSchedule(entity, brain, baby = false)
}
fun updateSchedule(entity: ProjectedPersonEntity, brain: Brain<ProjectedPersonEntity>, baby: Boolean) {
if (baby) {
brain.schedule = babySchedule.get()
} else {
brain.schedule = defaultSchedule.get()
}
brain.updateActivityFromSchedule(entity.level().dayTime, entity.level().gameTime)
}
fun updateSchedule(entity: ProjectedPersonEntity) = updateSchedule(entity, entity.brain, entity.shape.baby)
private fun initCoreActivity(brain: Brain<ProjectedPersonEntity>) {
brain.addActivity(
Activity.CORE, ImmutableList.of(
0 weight UpdateActivityFromSchedule.create(),
0 weight Swim(0.8f),
0 weight WakeUp.create(),
0 weight StopAttackingIfTargetInvalid.create(),
3 weight LookAtTargetSink(40, 70),
3 weight MoveToTargetSink(),
3 weight InteractWithDoor.create(),
3 weight SetWalkTargetAwayFrom.entity(MemoryModuleType.HURT_BY_ENTITY, 1.2f, 6, false),
4 weight MeleeAttack.create(15),
5 weight LostItem(400),
5 weight StartAttacking.create(::findAttackTarget),
5 weight SetWalkTargetFromAttackTargetIfTargetOutOfReach.create(1.1f),
10 weight GoToWantedItem.create(1.2f, false, 7),
)
)
}
private fun initIdleActivity(brain: Brain<ProjectedPersonEntity>) {
brain.addActivity(
Activity.IDLE, ImmutableList.of(
5 weight AmusementAI.createStrollAroundPoi(),
7 weight AmusementAI.createStrollToPoi(),
9 weight AmusementAI.createAcquirePoi(),
10 weight createStrollBehavior(),
)
)
}
private fun initPlayActivity(brain: Brain<ProjectedPersonEntity>) {
brain.addActivity(
Activity.PLAY, ImmutableList.of(
3 weight GoToWantedItem.create(1.75f, true, 32),
5 weight AmusementAI.createStrollAroundPoiBaby(),
7 weight AmusementAI.createStrollToPoiBaby(),
9 weight AmusementAI.createAcquirePoi(),
10 weight JumpOnBed(1.0f),
10 weight createStrollBehavior(),
)
)
}
private fun initWorkActivity(brain: Brain<ProjectedPersonEntity>) {
brain.addActivity(
Activity.WORK, ImmutableList.of(
5 weight ExchangeItem(),
7 weight WorkPoiAI.createStrollAroundPoi(),
7 weight WorkPoiAI.createStrollToPoi(),
10 weight RunOne(
mapOf(),
listOf(
1 weightR createStrollBehavior(),
1 weightR WorkPoiAI.createAcquirePoi(),
)
),
)
)
}
private fun initRestActivity(brain: Brain<ProjectedPersonEntity>) {
brain.addActivity(
Activity.REST, ImmutableList.of(
0 weight SleepInBed(),
3 weight GoToTargetLocation.create(MemoryModuleType.NEAREST_BED, 1, 1.05f),
3 weight RunOne(
mapOf(
MemoryModuleType.HOME to MemoryStatus.VALUE_ABSENT
),
listOf(
1 weightR createStrollBehavior()
)
),
)
)
}
private fun createStrollBehavior() = RunOne(
listOf(
2 weightR RandomStroll.stroll(1.0f, 42, 12),
2 weightR SetWalkTargetFromLookTarget.create(1.0f, 5),
1 weightR DoNothing(30, 60)
)
)
private fun findAttackTarget(entity: ProjectedPersonEntity): Optional<out LivingEntity> {
val entities = entity.brain.getMemory(MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES).getOrNull()
?: NearestVisibleLivingEntities.empty()
return entities.findClosest { target: LivingEntity ->
entity.canAttack(target)
&& !target.type.`is`(tagNoAttack)
&& (target.type.`is`(tagEnemy) || target is Monster)
}
}
}

View File

@@ -1,15 +1,21 @@
package quaedam.projection.swarm
package quaedam.projection.swarm.ai
import net.minecraft.core.BlockPos
import net.minecraft.world.entity.ai.navigation.GroundPathNavigation
import net.minecraft.world.level.Level
import net.minecraft.world.level.pathfinder.Path
import quaedam.misc.causality.CausalityAnchor
import quaedam.projection.swarm.ProjectedPersonEntity
import quaedam.projection.swarm.SwarmProjection
import quaedam.projector.Projector
class ProjectedPersonNavigation(val entity: ProjectedPersonEntity, level: Level) : GroundPathNavigation(entity, level) {
override fun createPath(set: MutableSet<BlockPos>, i: Int, bl: Boolean, j: Int, f: Float): Path? {
if (set.any { Projector.findNearbyProjections(level, it, SwarmProjection.effect.get()).isEmpty() }) {
if (set.any {
Projector.findNearbyProjections(level, it, SwarmProjection.effect.get())
.isEmpty() && !CausalityAnchor.checkEffect(level, it)
}) {
return null
}
return super.createPath(set, i, bl, j, f)

View File

@@ -0,0 +1,49 @@
package quaedam.projection.swarm.ai
import net.minecraft.core.GlobalPos
import net.minecraft.world.entity.ai.behavior.AcquirePoi
import net.minecraft.world.entity.ai.behavior.StrollAroundPoi
import net.minecraft.world.entity.ai.behavior.StrollToPoi
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.entity.ai.village.poi.PoiTypes
import quaedam.Quaedam
import java.util.*
object WorkPoiAI {
const val ID = "work"
val poiTypes by lazy {
setOf(
PoiTypes.ARMORER,
PoiTypes.BUTCHER,
PoiTypes.CARTOGRAPHER,
PoiTypes.CLERIC,
PoiTypes.FARMER,
PoiTypes.FISHERMAN,
PoiTypes.FLETCHER,
PoiTypes.LEATHERWORKER,
PoiTypes.LIBRARIAN,
PoiTypes.MASON,
PoiTypes.SHEPHERD,
PoiTypes.TOOLSMITH,
PoiTypes.WEAPONSMITH,
PoiTypes.LODESTONE,
PoiTypes.LIGHTNING_ROD,
)
}
val memory = Quaedam.memoryTypes.register(ID) {
MemoryModuleType(Optional.of(GlobalPos.CODEC))
}!!
fun createAcquirePoi() =
AcquirePoi.create({ it.`is` { key -> key in poiTypes } }, memory.get(), false, Optional.empty())
fun createStrollToPoi() =
StrollToPoi.create(memory.get(), 0.4f, 7, 4)
fun createStrollAroundPoi() =
StrollAroundPoi.create(memory.get(), 0.4f, 5)
}

View File

@@ -6,6 +6,8 @@ import net.minecraft.world.item.Item
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.entity.BlockEntityType
import quaedam.Quaedam
import quaedam.config.QuaedamConfig
import quaedam.misc.reality.RealityStabler
import quaedam.projection.ProjectionEffect
import quaedam.projection.ProjectionEffectType
import quaedam.utils.getChunksNearby
@@ -13,7 +15,6 @@ import quaedam.utils.getChunksNearby
object Projector {
const val ID = "projector"
const val EFFECT_RADIUS = 4
val block = Quaedam.blocks.register(ID) { ProjectorBlock }!!
@@ -29,18 +30,28 @@ object Projector {
BlockEntityType.Builder.of(::ProjectorBlockEntity, block.get()).build(null)
}!!
fun findNearbyProjectors(level: Level, pos: BlockPos) = level.getChunksNearby(pos, EFFECT_RADIUS)
val currentEffectRadius get() = QuaedamConfig.current.valuesInt["projector.effect_radius"] ?: 4
fun findNearbyProjectors(level: Level, pos: BlockPos) = level.getChunksNearby(pos, currentEffectRadius)
.flatMap {
it.blockEntities.filter { (_, v) -> v is ProjectorBlockEntity }
it.blockEntities.filter { (_, v) -> v is ProjectorBlockEntity && pos in v }
.keys
.filterNotNull()
}
.toSet()
@Suppress("UNCHECKED_CAST")
fun <T : ProjectionEffect> findNearbyProjections(level: Level, pos: BlockPos, type: ProjectionEffectType<T>) =
findNearbyProjectors(level, pos)
fun <T : ProjectionEffect> findNearbyProjections(
level: Level,
pos: BlockPos,
type: ProjectionEffectType<T>
): List<T> {
if (RealityStabler.checkEffect(level, pos)) {
return emptyList()
}
return findNearbyProjectors(level, pos)
.map { level.getBlockEntity(it) as ProjectorBlockEntity }
.mapNotNull { it.effects[type] as T? }
}
}

View File

@@ -1,6 +1,7 @@
package quaedam.projector
import net.minecraft.core.BlockPos
import net.minecraft.network.chat.Component
import net.minecraft.server.level.ServerLevel
import net.minecraft.util.RandomSource
import net.minecraft.world.InteractionHand
@@ -13,7 +14,10 @@ import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.EntityBlock
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.material.MapColor
import net.minecraft.world.level.material.PushReaction
import net.minecraft.world.phys.BlockHitResult
import quaedam.shell.ProjectionShellItem
import quaedam.utils.sendBlockUpdated
object ProjectorBlock : Block(Properties.of()
.jumpFactor(0.8f)
@@ -21,7 +25,9 @@ object ProjectorBlock : Block(Properties.of()
.mapColor(MapColor.COLOR_BLACK)
.randomTicks()
.strength(4.0f)
.requiresCorrectToolForDrops()), EntityBlock {
.requiresCorrectToolForDrops()
.pushReaction(PushReaction.IGNORE)
), EntityBlock {
fun checkUpdate(level: Level, pos: BlockPos) {
if (!level.isClientSide) {
@@ -38,8 +44,23 @@ object ProjectorBlock : Block(Properties.of()
interactionHand: InteractionHand,
blockHitResult: BlockHitResult
): InteractionResult {
if (player.getItemInHand(interactionHand).item == ProjectionShellItem) {
if (!level.isClientSide) {
val entity = level.getBlockEntity(blockPos) as ProjectorBlockEntity
var newRadius = entity.effectRadius + 1
if (newRadius > Projector.currentEffectRadius) {
newRadius = 0
}
entity.updateEffectArea(newRadius)
entity.setChanged()
entity.sendBlockUpdated()
checkUpdate(level, blockPos)
player.sendSystemMessage(Component.translatable("quaedam.projector.radius_updated", newRadius))
}
return InteractionResult.sidedSuccess(level.isClientSide)
}
checkUpdate(level, blockPos)
return InteractionResult.SUCCESS
return InteractionResult.PASS
}
override fun newBlockEntity(pos: BlockPos, state: BlockState) = ProjectorBlockEntity(pos, state)

View File

@@ -1,6 +1,7 @@
package quaedam.projector
import net.minecraft.core.BlockPos
import net.minecraft.core.SectionPos
import net.minecraft.core.Vec3i
import net.minecraft.nbt.CompoundTag
import net.minecraft.network.protocol.Packet
@@ -12,26 +13,28 @@ import net.minecraft.world.level.ChunkPos
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.levelgen.structure.BoundingBox
import net.minecraft.world.phys.AABB
import quaedam.projection.ProjectionEffect
import quaedam.projection.ProjectionEffectType
import quaedam.projection.ProjectionProvider
import quaedam.utils.sendBlockUpdated
import kotlin.math.max
import kotlin.math.min
class ProjectorBlockEntity(pos: BlockPos, state: BlockState) :
BlockEntity(Projector.blockEntity.get(), pos, state) {
val effectAreaChunk by lazy {
val chunk = level!!.getChunk(pos).pos
ChunkPos(chunk.x - Projector.EFFECT_RADIUS, chunk.z - Projector.EFFECT_RADIUS) to
ChunkPos(chunk.x + Projector.EFFECT_RADIUS, chunk.z + Projector.EFFECT_RADIUS)
companion object {
const val TAG_EFFECT_RADIUS = "EffectRadius"
const val TAG_PROJECTION_EFFECTS = "ProjectionEffects"
}
val effectArea: BoundingBox by lazy {
val chunk = level!!.getChunk(pos).pos
val (minChunk, maxChunk) = effectAreaChunk
val minBlock = BlockPos(minChunk.minBlockX, level!!.minBuildHeight, minChunk.minBlockZ)
val maxBlock = BlockPos(maxChunk.maxBlockX, level!!.maxBuildHeight, maxChunk.maxBlockZ)
BoundingBox.fromCorners(minBlock, maxBlock)
var effectRadius: Int = 0
lateinit var effectArea: BoundingBox
lateinit var effectAreaAABB: AABB
init {
updateEffectArea(Projector.currentEffectRadius)
}
val checkArea: BoundingBox by lazy {
@@ -46,18 +49,20 @@ class ProjectorBlockEntity(pos: BlockPos, state: BlockState) :
effects.map { (type, effect) ->
effectsTag.put(type.id.toString(), effect.toNbt())
}
tag.put("ProjectionEffects", effectsTag)
tag.putInt(TAG_EFFECT_RADIUS, effectRadius)
tag.put(TAG_PROJECTION_EFFECTS, effectsTag)
}
override fun load(tag: CompoundTag) {
super.load(tag)
val effectsTag = tag["ProjectionEffects"]
updateEffectArea(max(min(tag.getInt(TAG_EFFECT_RADIUS), Projector.currentEffectRadius), 0))
val effectsTag = tag[TAG_PROJECTION_EFFECTS]
val effects = mutableMapOf<ProjectionEffectType<*>, ProjectionEffect>()
if (effectsTag != null && effectsTag is CompoundTag) {
effectsTag.allKeys.forEach { id ->
val type = ProjectionEffectType.registry[ResourceLocation(id)]
if (type != null) {
val effect = type.constructor().apply { fromNbt(effectsTag[id] as CompoundTag) }
val effect = type.constructor().apply { fromNbt(effectsTag[id] as CompoundTag, true) }
effects[type] = effect
}
}
@@ -65,6 +70,20 @@ class ProjectorBlockEntity(pos: BlockPos, state: BlockState) :
updateEffects(effects, notify = false)
}
fun updateEffectArea(radius: Int) {
effectRadius = radius
val chunk = ChunkPos(SectionPos.blockToSectionCoord(blockPos.x), SectionPos.blockToSectionCoord(blockPos.z))
val minChunk = ChunkPos(chunk.x - radius, chunk.z - radius)
val maxChunk = ChunkPos(chunk.x + radius, chunk.z + radius)
// Y is not the limit value of Int because at
// Lnet/minecraft/world/level/entity/EntitySectionStorage;forEachAccessibleNonEmptySection(Lnet/minecraft/world/phys/AABB;Lnet/minecraft/util/AbortableIterationConsumer;)V
// it may get overflow
val minBlock = BlockPos(minChunk.minBlockX, Short.MIN_VALUE.toInt(), minChunk.minBlockZ)
val maxBlock = BlockPos(maxChunk.maxBlockX, Short.MAX_VALUE.toInt(), maxChunk.maxBlockZ)
effectArea = BoundingBox.fromCorners(minBlock, maxBlock)
effectAreaAABB = AABB(minBlock, maxBlock)
}
override fun getUpdateTag(): CompoundTag = saveWithoutMetadata()
override fun getUpdatePacket(): Packet<ClientGamePacketListener> = ClientboundBlockEntityDataPacket.create(this)
@@ -95,6 +114,7 @@ class ProjectorBlockEntity(pos: BlockPos, state: BlockState) :
if (!level.isClientSide && notify) {
sendBlockUpdated()
}
setChanged()
val addedEffects = effects.filterKeys { it !in oldEffects }
val removedEffects = oldEffects.filterKeys { it !in effects }
val updatedEffects = effects.filter { (k, v) -> oldEffects[k] != null && oldEffects[k] != v }
@@ -110,9 +130,6 @@ class ProjectorBlockEntity(pos: BlockPos, state: BlockState) :
fun collectEffects(): Map<ProjectionEffectType<*>, ProjectionEffect> {
val level = level!! as ServerLevel
if (!level.getBlockState(blockPos.below()).isAir) {
return emptyMap()
}
val effects = mutableMapOf<ProjectionEffectType<*>, ProjectionEffect>()
for (x in checkArea.minX()..checkArea.maxX()) {
for (y in checkArea.minY()..checkArea.maxY()) {
@@ -121,7 +138,7 @@ class ProjectorBlockEntity(pos: BlockPos, state: BlockState) :
val blockState = level.getBlockState(pos)
val block = blockState.block
if (block is ProjectionProvider<*>) {
val projection = block.createProjectionEffect(level, blockState, pos)
val projection = block.applyProjectionEffect(level, blockState, pos)
if (projection != null) {
effects[projection.type] = projection
}

View File

@@ -0,0 +1,143 @@
package quaedam.shell
import net.minecraft.client.gui.components.AbstractSliderButton
import net.minecraft.client.gui.components.CycleButton
import net.minecraft.client.gui.components.StringWidget
import net.minecraft.client.gui.layouts.LayoutElement
import net.minecraft.network.chat.Component
import quaedam.projection.ProjectionEffect
import kotlin.reflect.KMutableProperty0
class ProjectionEffectShell(val effect: ProjectionEffect) {
interface Provider {
fun createShell(): ProjectionEffectShell
}
val rows = mutableListOf<Row>()
val width = 150
val height = 20
data class Row(val text: Component, val renderer: ShellRenderer)
fun row(key: String, renderer: ShellRenderer) {
rows += Row(Component.translatable(key), renderer)
}
fun text(key: String, value: Component) = row(key) { StringWidget(value, it.font) }
fun doubleSlider(key: String, property: KMutableProperty0<Double>, range: ClosedRange<Double>, rawStep: Double) {
val len = range.endInclusive - range.start
val step = rawStep / len
row(key) {
object : AbstractSliderButton(
0, 0, width, height,
Component.literal(String.format("%.2f", property.get())), (property.get() - range.start) / len
) {
override fun updateMessage() {
message = Component.literal(String.format("%.2f", property.get()))
}
override fun applyValue() {
val diff = value % step
if (diff < step * 0.5) {
value -= diff
} else {
value += (step - diff)
}
property.set(range.start + (value * len))
}
}
}
}
fun floatSlider(key: String, property: KMutableProperty0<Float>, range: ClosedRange<Float>, rawStep: Float) {
val len = range.endInclusive - range.start
val step = rawStep / len
row(key) {
object : AbstractSliderButton(
0,
0,
width,
height,
Component.literal(String.format("%.2f", property.get())),
(property.get() - range.start) / len.toDouble()
) {
override fun updateMessage() {
message = Component.literal(String.format("%.2f", property.get()))
}
override fun applyValue() {
val diff = value % step
if (diff < step * 0.5) {
value -= diff
} else {
value += (step - diff)
}
property.set(range.start + (value.toFloat() * len))
}
}
}
}
fun intSlider(key: String, property: KMutableProperty0<Int>, range: IntProgression) {
val len = range.last - range.first
val step = range.step.toDouble() / len
row(key) {
object : AbstractSliderButton(
0, 0, width, height,
Component.literal(property.get().toString()), (property.get() - range.first).toDouble() / len
) {
override fun updateMessage() {
message = Component.literal(property.get().toString())
}
override fun applyValue() {
val diff = value % step
if (diff < step * 0.5) {
value -= diff
} else {
value += (step - diff)
}
property.set(range.first + (value * len).toInt())
}
}
}
}
fun intCycle(key: String, property: KMutableProperty0<Int>, range: IntProgression) =
row(key) {
CycleButton.builder<Int> {
property.set(it)
Component.literal(it.toString())
}
.displayOnlyValue()
.withValues(range.toList())
.withInitialValue(property.get())
.create(0, 0, width, height, Component.translatable(key))
}
fun boolean(key: String, property: KMutableProperty0<Boolean>) =
row(key) {
CycleButton.builder<Boolean> {
property.set(it)
Component.translatable("$key.$it")
}
.displayOnlyValue()
.withValues(listOf(true, false))
.withInitialValue(property.get())
.create(0, 0, width, height, Component.translatable(key))
}
}
inline fun buildProjectionEffectShell(effect: ProjectionEffect, builder: ProjectionEffectShell.() -> Unit) =
ProjectionEffectShell(effect).apply(builder)
data class ShellRenderContext(val screen: ProjectionShellScreen) {
val font get() = screen.getFont()
}
typealias ShellRenderer = (ctx: ShellRenderContext) -> LayoutElement

View File

@@ -0,0 +1,25 @@
package quaedam.shell
import dev.architectury.networking.NetworkChannel
import quaedam.Quaedam
import quaedam.shell.network.ClientboundPSHLockResultPacket
import quaedam.shell.network.ClientboundPSHLockRevokePacket
import quaedam.shell.network.ServerboundPSHLockAcquirePacket
import quaedam.shell.network.ServerboundPSHLockReleasePacket
object ProjectionShell {
const val ID = "projection_shell"
val item = Quaedam.items.register(ID) { ProjectionShellItem }!!
val channel: NetworkChannel = NetworkChannel.create(Quaedam.resource(ID))
init {
ServerboundPSHLockAcquirePacket
ServerboundPSHLockReleasePacket
ClientboundPSHLockRevokePacket
ClientboundPSHLockResultPacket
}
}

View File

@@ -0,0 +1,12 @@
package quaedam.shell
import net.minecraft.core.BlockPos
import net.minecraft.world.level.Level
interface ProjectionShellBlock {
fun getProjectionEffectForShell(level: Level, pos: BlockPos): ProjectionEffectShell
fun applyFromShell(level: Level, pos: BlockPos, shell: ProjectionEffectShell)
}

View File

@@ -0,0 +1,24 @@
package quaedam.shell
import net.minecraft.world.InteractionResult
import net.minecraft.world.item.Item
import net.minecraft.world.item.context.UseOnContext
import quaedam.Quaedam
import quaedam.shell.network.ServerboundPSHLockAcquirePacket
object ProjectionShellItem : Item(
Properties()
.stacksTo(1)
.`arch$tab`(Quaedam.creativeModeTab)
) {
override fun useOn(context: UseOnContext): InteractionResult {
val block = context.level.getBlockState(context.clickedPos).block
if (block is ProjectionShellBlock && context.level.isClientSide) {
ProjectionShell.channel.sendToServer(ServerboundPSHLockAcquirePacket(context.clickedPos))
return InteractionResult.SUCCESS
}
return InteractionResult.PASS
}
}

View File

@@ -0,0 +1,53 @@
package quaedam.shell
import dev.architectury.event.events.common.TickEvent
import net.minecraft.core.BlockPos
import net.minecraft.core.GlobalPos
import net.minecraft.server.level.ServerLevel
import net.minecraft.server.level.ServerPlayer
import quaedam.mixininterface.ProjectionShellMutexAccessor
import quaedam.shell.network.ClientboundPSHLockRevokePacket
object ProjectionShellMutex {
init {
TickEvent.SERVER_POST.register { server ->
if (server.tickCount and 8 == 0) {
val mutex = (server as ProjectionShellMutexAccessor).`quaedam$getProjectionShellMutex`()
val currentTime = System.currentTimeMillis()
mutex.forEach { (pos, lock) ->
if (lock.player !in server.playerList.players) {
mutex.remove(pos)
} else if (currentTime - lock.time > 60 * 1000) {
mutex.remove(pos)
ProjectionShell.channel.sendToPlayer(lock.player, ClientboundPSHLockRevokePacket)
}
}
}
}
}
fun tryLock(level: ServerLevel, pos: BlockPos, player: ServerPlayer): Boolean {
val mutex = (level.server as ProjectionShellMutexAccessor).`quaedam$getProjectionShellMutex`()
val gPos = GlobalPos.of(level.dimension(), pos)
if (mutex.values.any { it.player == player }) {
return false
}
if (gPos !in mutex) {
mutex[gPos] = Lock(player, System.currentTimeMillis())
return true
}
return false
}
fun release(level: ServerLevel, pos: BlockPos, player: ServerPlayer) {
val mutex = (level.server as ProjectionShellMutexAccessor).`quaedam$getProjectionShellMutex`()
val gPos = GlobalPos.of(level.dimension(), pos)
if (mutex[gPos]?.player == player) {
mutex.remove(gPos)
}
}
data class Lock(val player: ServerPlayer, val time: Long)
}

View File

@@ -0,0 +1,86 @@
package quaedam.shell
import dev.architectury.utils.GameInstance
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.Button
import net.minecraft.client.gui.components.StringWidget
import net.minecraft.client.gui.layouts.GridLayout
import net.minecraft.client.gui.screens.Screen
import net.minecraft.core.BlockPos
import net.minecraft.network.chat.Component
import net.minecraft.world.level.Level
import quaedam.shell.network.ServerboundPSHLockReleasePacket
class ProjectionShellScreen(val level: Level, val pos: BlockPos, val shell: ProjectionEffectShell) :
Screen(Component.translatable("quaedam.screen.projection_shell")) {
companion object {
const val BORDER = 15
}
var layout = GridLayout()
override fun init() {
super.init()
layout = GridLayout()
layout.spacing(4)
val renderContext = ShellRenderContext(this)
if (shell.rows.isNotEmpty()) {
val rows = layout.createRowHelper(2)
shell.rows.forEach {
rows.addChild(StringWidget(150, 20, it.text, font))
rows.addChild(it.renderer(renderContext))
}
run { // Buttons
rows.addChild(StringWidget(Component.empty(), font))
rows.addChild(Button.builder(Component.translatable("quaedam.screen.projection_shell.save")) {
GameInstance.getClient().setScreen(null)
}.build().apply(::setInitialFocus))
}
} else {
val rows = layout.createRowHelper(1)
rows.addChild(StringWidget(Component.translatable("quaedam.screen.projection_shell.empty"), font))
rows.addChild(Button.builder(Component.translatable("quaedam.screen.projection_shell.close")) {
GameInstance.getClient().setScreen(null)
}.build().apply(::setInitialFocus))
}
layout.arrangeElements()
layout.x = (width - layout.width) / 2
layout.y = (height - layout.height) / 2
layout.visitWidgets(::addRenderableWidget)
addRenderableOnly(StringWidget(level.getBlockState(pos).block.name, font).apply {
x = (this@ProjectionShellScreen.width - width) / 2
y = layout.y - 2 * BORDER - font.lineHeight
})
}
fun getFont() = font
override fun render(guiGraphics: GuiGraphics, mouseX: Int, mouseY: Int, partialTick: Float) {
renderBackground(guiGraphics)
super.render(guiGraphics, mouseX, mouseY, partialTick)
}
override fun renderBackground(guiGraphics: GuiGraphics) {
super.renderBackground(guiGraphics)
guiGraphics.fill(
layout.x - BORDER,
layout.y - BORDER,
layout.x + layout.width + BORDER,
layout.y + layout.height + BORDER,
0x11c6c6c6
)
}
override fun removed() {
super.removed()
val block = level.getBlockState(pos).block
if (block is ProjectionShellBlock) {
block.applyFromShell(level, pos, shell)
}
ProjectionShell.channel.sendToServer(ServerboundPSHLockReleasePacket(pos))
}
override fun isPauseScreen() = false
}

View File

@@ -0,0 +1,71 @@
package quaedam.shell.network
import dev.architectury.networking.NetworkManager.PacketContext
import dev.architectury.utils.GameInstance
import net.minecraft.core.BlockPos
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.chat.Component
import quaedam.Quaedam
import quaedam.shell.ProjectionShell
import quaedam.shell.ProjectionShellBlock
import quaedam.shell.ProjectionShellScreen
import java.util.function.Supplier
data class ClientboundPSHLockResultPacket(val pos: BlockPos, val result: Boolean) {
companion object {
init {
ProjectionShell.channel.register(
ClientboundPSHLockResultPacket::class.java,
ClientboundPSHLockResultPacket::encode,
::ClientboundPSHLockResultPacket,
ClientboundPSHLockResultPacket::apply
)
}
}
constructor(buf: FriendlyByteBuf) : this(buf.readBlockPos(), buf.readBoolean())
fun encode(buf: FriendlyByteBuf) {
buf.writeBlockPos(pos)
buf.writeBoolean(result)
}
fun apply(context: Supplier<PacketContext>) {
val ctx = context.get()
if (ctx.player.level().isClientSide) {
val client = GameInstance.getClient()
if (result) {
val level = ctx.player.level()
val block = level.getBlockState(pos).block
if (block is ProjectionShellBlock) {
ctx.queue {
try {
client.setScreen(
ProjectionShellScreen(
level,
pos,
block.getProjectionEffectForShell(level, pos)
)
)
} catch (e: Throwable) {
Quaedam.logger.error("Failed to open projection shell screen", e)
ProjectionShell.channel.sendToServer(ServerboundPSHLockReleasePacket(pos))
}
}
} else {
Quaedam.logger.warn("ClientboundPSHLockResultPacket with non-shell-provider block received")
}
} else {
ctx.queue {
client.setScreen(null)
client.gui.setOverlayMessage(
Component.translatable("quaedam.screen.projection_shell.lock_failed"),
false
)
}
}
}
}
}

View File

@@ -0,0 +1,37 @@
package quaedam.shell.network
import dev.architectury.networking.NetworkManager.PacketContext
import dev.architectury.utils.GameInstance
import net.minecraft.network.chat.Component
import quaedam.shell.ProjectionShell
import quaedam.shell.ProjectionShellScreen
import java.util.function.Supplier
object ClientboundPSHLockRevokePacket {
init {
ProjectionShell.channel.register(
ClientboundPSHLockRevokePacket::class.java,
{ _, _ -> },
{ ClientboundPSHLockRevokePacket },
{ _, ctx -> apply(ctx) }
)
}
private fun apply(context: Supplier<PacketContext>) {
val ctx = context.get()
if (ctx.player.level().isClientSide) {
ctx.queue {
val client = GameInstance.getClient()
if (client.screen is ProjectionShellScreen) {
client.setScreen(null)
client.gui.setOverlayMessage(
Component.translatable("quaedam.screen.projection_shell.lock_revoked"),
false
)
}
}
}
}
}

View File

@@ -0,0 +1,48 @@
package quaedam.shell.network
import dev.architectury.networking.NetworkManager.PacketContext
import net.minecraft.core.BlockPos
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.server.level.ServerLevel
import net.minecraft.server.level.ServerPlayer
import quaedam.shell.ProjectionShell
import quaedam.shell.ProjectionShellBlock
import quaedam.shell.ProjectionShellMutex
import java.util.function.Supplier
data class ServerboundPSHLockAcquirePacket(val pos: BlockPos) {
companion object {
init {
ProjectionShell.channel.register(
ServerboundPSHLockAcquirePacket::class.java,
ServerboundPSHLockAcquirePacket::encode,
::ServerboundPSHLockAcquirePacket,
ServerboundPSHLockAcquirePacket::apply
)
}
}
constructor(buf: FriendlyByteBuf) : this(buf.readBlockPos())
fun encode(buf: FriendlyByteBuf) {
buf.writeBlockPos(pos)
}
fun apply(context: Supplier<PacketContext>) {
val ctx = context.get()
if (!ctx.player.level().isClientSide) {
ctx.queue {
val player = ctx.player as ServerPlayer
val level = ctx.player.level() as ServerLevel
if (level.getBlockState(pos).block !is ProjectionShellBlock) {
ProjectionShell.channel.sendToPlayer(player, ClientboundPSHLockResultPacket(pos, false))
return@queue
}
val result = ProjectionShellMutex.tryLock(level, pos, player)
ProjectionShell.channel.sendToPlayer(player, ClientboundPSHLockResultPacket(pos, result))
}
}
}
}

View File

@@ -0,0 +1,41 @@
package quaedam.shell.network
import dev.architectury.networking.NetworkManager.PacketContext
import net.minecraft.core.BlockPos
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.server.level.ServerLevel
import net.minecraft.server.level.ServerPlayer
import quaedam.shell.ProjectionShell
import quaedam.shell.ProjectionShellMutex
import java.util.function.Supplier
data class ServerboundPSHLockReleasePacket(val pos: BlockPos) {
companion object {
init {
ProjectionShell.channel.register(
ServerboundPSHLockReleasePacket::class.java,
ServerboundPSHLockReleasePacket::encode,
::ServerboundPSHLockReleasePacket,
ServerboundPSHLockReleasePacket::apply
)
}
}
constructor(buf: FriendlyByteBuf) : this(buf.readBlockPos())
fun encode(buf: FriendlyByteBuf) {
buf.writeBlockPos(pos)
}
fun apply(context: Supplier<PacketContext>) {
val ctx = context.get()
if (!ctx.player.level().isClientSide) {
ctx.queue {
val player = ctx.player as ServerPlayer
ProjectionShellMutex.release(ctx.player.level() as ServerLevel, pos, player)
}
}
}
}

View File

@@ -0,0 +1,20 @@
{
"variants": {
"facing=east": {
"model": "quaedam:block/causality_anchor",
"y": 270
},
"facing=south": {
"model": "quaedam:block/causality_anchor",
"y": 0
},
"facing=west": {
"model": "quaedam:block/causality_anchor",
"y": 90
},
"facing=north": {
"model": "quaedam:block/causality_anchor",
"y": 180
}
}
}

View File

@@ -0,0 +1,7 @@
{
"variants": {
"": {
"model": "quaedam:block/music_projection"
}
}
}

View File

@@ -0,0 +1,7 @@
{
"variants": {
"": {
"model": "quaedam:block/noise_projection"
}
}
}

View File

@@ -0,0 +1,20 @@
{
"variants": {
"facing=east": {
"model": "quaedam:block/reality_stabler",
"y": 270
},
"facing=south": {
"model": "quaedam:block/reality_stabler",
"y": 0
},
"facing=west": {
"model": "quaedam:block/reality_stabler",
"y": 90
},
"facing=north": {
"model": "quaedam:block/reality_stabler",
"y": 180
}
}
}

View File

@@ -0,0 +1,7 @@
{
"variants": {
"": {
"model": "quaedam:block/smart_instrument"
}
}
}

View File

@@ -0,0 +1,7 @@
{
"variants": {
"": {
"model": "quaedam:block/sound_projection"
}
}
}

View File

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

@@ -3,5 +3,46 @@
"block.quaedam.projector": "Projector",
"block.quaedam.skylight_projection": "Skylight Projection",
"block.quaedam.swarm_projection": "Swarm Projection",
"entity.quaedam.projected_person": "Virtual Person"
"block.quaedam.sound_projection": "Sound Projection",
"block.quaedam.noise_projection": "Noise Projection",
"block.quaedam.music_projection": "Music Projection",
"block.quaedam.smart_instrument": "Smart Instrument",
"block.quaedam.causality_anchor": "Causality Anchor",
"block.quaedam.reality_stabler": "Reality Stabler",
"entity.quaedam.projected_person": "Virtual Person",
"item.quaedam.projection_shell": "Projection Shell",
"item.quaedam.iron_copper_metal": "Copper-iron Alloy",
"item.quaedam.projection_metal": "Projection Metal",
"quaedam.projector.radius_updated": "Current effect radius: %s",
"quaedam.screen.projection_shell": "Projection Shell",
"quaedam.screen.projection_shell.lock_revoked": "Timeout! Connection Lost",
"quaedam.screen.projection_shell.lock_failed": "Permission denied!",
"quaedam.screen.projection_shell.save": "Save",
"quaedam.screen.projection_shell.empty": "No Options Available!",
"quaedam.screen.projection_shell.close": "Close",
"quaedam.shell.skylight.factor": "Factor",
"quaedam.shell.noise.rate": "Rate",
"quaedam.shell.noise.amount": "Amount",
"quaedam.shell.noise.volume": "Volume",
"quaedam.shell.swarm.max_count": "Max Count",
"quaedam.shell.sound.rate": "Rate",
"quaedam.shell.sound.volume": "Volume",
"quaedam.shell.music.volume_factor": "Volume Factor",
"quaedam.shell.music.particle": "Particle",
"quaedam.shell.music.particle.true": "Display",
"quaedam.shell.music.particle.false": "Hidden",
"advancements.quaedam.causality_anchor.title": "Causality",
"advancements.quaedam.causality_anchor.description": "Is there any reason for this?",
"advancements.quaedam.projector.title": "Quaedam",
"advancements.quaedam.projector.description": "Use projectors to project",
"advancements.quaedam.reality_stabler.title": "More Stable",
"advancements.quaedam.reality_stabler.description": "Use reality stabler to stable the reality",
"advancements.quaedam.smart_instrument.title": "Better than Note Block",
"advancements.quaedam.smart_instrument.description": "Use smart instrument",
"advancements.quaedam.sound_projection.title": "Get Noisier",
"advancements.quaedam.sound_projection.description": "Make a sound projection",
"advancements.quaedam.swarm_projection.title": "Too many people",
"advancements.quaedam.swarm_projection.description": "Make a swarm projection",
"advancements.quaedam.kill_projected_person.title": "Go away",
"advancements.quaedam.kill_projected_person.description": "Kill a projection person\n\nWhy are you doing this?\nThis is bad."
}

View File

@@ -1,7 +1,48 @@
{
"category.quaedam": "Quaedam",
"block.quaedam.projector": "投影仪",
"block.quaedam.skylight_projection": "天光投影",
"block.quaedam.swarm_projection": "人群投影",
"entity.quaedam.projected_person": "虚拟个体"
"category.quaedam": "Quaedam",
"block.quaedam.projector": "投影仪",
"block.quaedam.skylight_projection": "天光投影",
"block.quaedam.swarm_projection": "人群投影",
"block.quaedam.sound_projection": "声音投影",
"block.quaedam.noise_projection": "噪声投影",
"block.quaedam.music_projection": "音乐投影",
"block.quaedam.causality_anchor": "因果锚",
"block.quaedam.reality_stabler": "现实稳定器",
"block.quaedam.smart_instrument": "智能乐器",
"entity.quaedam.projected_person": "虚拟个体",
"item.quaedam.projection_shell": "投影操作面板",
"item.quaedam.iron_copper_metal": "铜铁合金",
"item.quaedam.projection_metal": "投影金属",
"quaedam.projector.radius_updated": "当前效果半径:%s",
"quaedam.screen.projection_shell": "投影操作",
"quaedam.screen.projection_shell.lock_revoked": "超时!连接丢失",
"quaedam.screen.projection_shell.lock_failed": "正被使用",
"quaedam.screen.projection_shell.save": "保存",
"quaedam.screen.projection_shell.empty": "无可用选项!",
"quaedam.screen.projection_shell.close": "关闭",
"quaedam.shell.skylight.factor": "因子",
"quaedam.shell.noise.rate": "速率",
"quaedam.shell.noise.amount": "数量",
"quaedam.shell.noise.volume": "响度因子",
"quaedam.shell.swarm.max_count": "最大数量",
"quaedam.shell.sound.rate": "速率",
"quaedam.shell.sound.volume": "响度因子",
"quaedam.shell.music.volume_factor": "响度因子",
"quaedam.shell.music.particle": "粒子效果",
"quaedam.shell.music.particle.true": "显示",
"quaedam.shell.music.particle.false": "隐藏",
"advancements.quaedam.causality_anchor.title": "因果律",
"advancements.quaedam.causality_anchor.description": "这不合理",
"advancements.quaedam.projector.title": "Quaedam",
"advancements.quaedam.projector.description": "使用投影仪进行投影",
"advancements.quaedam.reality_stabler.title": "更加稳定",
"advancements.quaedam.reality_stabler.description": "使用现实稳定器稳定现实",
"advancements.quaedam.smart_instrument.title": "比音符盒更好",
"advancements.quaedam.smart_instrument.description": "使用智能乐器",
"advancements.quaedam.sound_projection.title": "更加吵闹",
"advancements.quaedam.sound_projection.description": "制作声音投影",
"advancements.quaedam.swarm_projection.title": "太多人了",
"advancements.quaedam.swarm_projection.description": "制作人群投影",
"advancements.quaedam.kill_projected_person.title": "走开",
"advancements.quaedam.kill_projected_person.description": "杀死一个投影人\n\n你为什么要这样做呢\n这是不好的。"
}

View File

@@ -0,0 +1,48 @@
{
"category.quaedam": "有些事",
"block.quaedam.projector": "不会发光的投影仪",
"block.quaedam.skylight_projection": "天窗发射",
"block.quaedam.swarm_projection": "群体发射",
"block.quaedam.sound_projection": "生活上的噪声发射",
"block.quaedam.noise_projection": "物理上的噪声发射",
"block.quaedam.music_projection": "音乐发射",
"block.quaedam.causality_anchor": "宇宙万法的那个源头————如",
"block.quaedam.reality_stabler": "投影?退,退,退",
"block.quaedam.smart_instrument": "SMART消歧义笔记方块",
"entity.quaedam.projected_person": "没用的废物",
"item.quaedam.projection_shell": "投射滥权终端",
"item.quaedam.iron_copper_metal": "有点生锈的铁锭",
"item.quaedam.projection_metal": "投影Metal©",
"quaedam.projector.radius_updated": "现在乱七八糟的效果能碰到的范围:%s",
"quaedam.screen.projection_shell": "控制面板",
"quaedam.screen.projection_shell.lock_revoked": "土豆熟了",
"quaedam.screen.projection_shell.lock_failed": "宁配吗?",
"quaedam.screen.projection_shell.save": "我好了",
"quaedam.screen.projection_shell.empty": "空",
"quaedam.screen.projection_shell.close": "怒退",
"quaedam.shell.skylight.factor": "防晒系数",
"quaedam.shell.noise.rate": "急急急等级",
"quaedam.shell.noise.amount": "声卡压榨等级",
"quaedam.shell.noise.volume": "振幅大小",
"quaedam.shell.swarm.max_count": "显卡和处理器迫害等级",
"quaedam.shell.sound.rate": "急急急等级",
"quaedam.shell.sound.volume": "振幅大小",
"quaedam.shell.music.volume_factor": "振幅大小",
"quaedam.shell.music.particle": "会变色的颗粒buff",
"quaedam.shell.music.particle.true": "打开",
"quaedam.shell.music.particle.false": "关掉,关掉,一定要关掉",
"advancements.quaedam.causality_anchor.title": "因果律",
"advancements.quaedam.causality_anchor.description": "这不合理",
"advancements.quaedam.projector.title": "有些事",
"advancements.quaedam.projector.description": "你是黑魔法(指着 JVMTI师吗",
"advancements.quaedam.reality_stabler.title": "水滴",
"advancements.quaedam.reality_stabler.description": "tql这是强互作用力做的吗",
"advancements.quaedam.smart_instrument.title": "SMART消歧义",
"advancements.quaedam.smart_instrument.description": "音乐盘?不需要的",
"advancements.quaedam.sound_projection.title": "不要偷偷摸摸",
"advancements.quaedam.sound_projection.description": "又没有幽yóu匿分贝仪、幽yóu匿尖叫体或大聪明怕什么",
"advancements.quaedam.swarm_projection.title": "多来点,爱看",
"advancements.quaedam.swarm_projection.description": "",
"advancements.quaedam.kill_projected_person.title": "失败",
"advancements.quaedam.kill_projected_person.description": "投影人这么可爱,为什么要失败投影人\n\n你干嘛\n坏太坏了你不能这样。"
}

View File

@@ -0,0 +1,106 @@
{
"parent": "minecraft:block/block",
"texture_size": [64, 64],
"textures": {
"0": "quaedam:block/causality_anchor",
"particle": "quaedam:block/causality_anchor"
},
"elements": [
{
"name": "s2",
"from": [13, -6, 14],
"to": [15, 6, 16],
"rotation": {"angle": -22.5, "axis": "x", "origin": [-1, 0, 0]},
"faces": {
"north": {"uv": [4, 4, 4.5, 7], "texture": "#0"},
"east": {"uv": [4.5, 4, 5, 7], "texture": "#0"},
"south": {"uv": [0, 5, 0.5, 8], "texture": "#0"},
"west": {"uv": [0.5, 5, 1, 8], "texture": "#0"},
"up": {"uv": [5.75, 7.5, 5.25, 7], "texture": "#0"},
"down": {"uv": [7.5, 5.5, 7, 6], "texture": "#0"}
}
},
{
"name": "s3",
"from": [1, -6, 14],
"to": [3, 6, 16],
"rotation": {"angle": -22.5, "axis": "x", "origin": [1, 0, 0]},
"faces": {
"north": {"uv": [1, 5, 1.5, 8], "texture": "#0"},
"east": {"uv": [1.5, 5, 2, 8], "texture": "#0"},
"south": {"uv": [2, 5, 2.5, 8], "texture": "#0"},
"west": {"uv": [2.5, 5, 3, 8], "texture": "#0"},
"up": {"uv": [6.25, 7.5, 5.75, 7], "texture": "#0"},
"down": {"uv": [7.5, 6, 7, 6.5], "texture": "#0"}
}
},
{
"name": "s4",
"from": [1, 0, -1],
"to": [3, 12, 1],
"rotation": {"angle": 22.5, "axis": "x", "origin": [1, 0, 0]},
"faces": {
"north": {"uv": [3, 5, 3.5, 8], "texture": "#0"},
"east": {"uv": [3.5, 5, 4, 8], "texture": "#0"},
"south": {"uv": [5, 4, 5.5, 7], "texture": "#0"},
"west": {"uv": [5.5, 4, 6, 7], "texture": "#0"},
"up": {"uv": [6.75, 7.5, 6.25, 7], "texture": "#0"},
"down": {"uv": [7.5, 6.5, 7, 7], "texture": "#0"}
}
},
{
"name": "s1",
"from": [13, 0, -1],
"to": [15, 12, 1],
"rotation": {"angle": 22.5, "axis": "x", "origin": [-1, 0, 0]},
"faces": {
"north": {"uv": [6, 0, 6.5, 3], "texture": "#0"},
"east": {"uv": [6, 4, 6.5, 7], "texture": "#0"},
"south": {"uv": [6.5, 0, 7, 3], "texture": "#0"},
"west": {"uv": [6.5, 4, 7, 7], "texture": "#0"},
"up": {"uv": [7.25, 7.5, 6.75, 7], "texture": "#0"},
"down": {"uv": [7.75, 4, 7.25, 4.5], "texture": "#0"}
}
},
{
"name": "platform",
"from": [0, 10, 3],
"to": [16, 12, 13],
"faces": {
"north": {"uv": [4, 3, 8, 3.5], "texture": "#0"},
"east": {"uv": [7, 0, 9.5, 0.5], "texture": "#0"},
"south": {"uv": [4, 3.5, 8, 4], "texture": "#0"},
"west": {"uv": [7, 0.5, 9.5, 1], "texture": "#0"},
"up": {"uv": [4, 2.5, 0, 0], "texture": "#0"},
"down": {"uv": [4, 2.5, 0, 5], "texture": "#0"}
}
},
{
"name": "core",
"from": [6, 14, 7],
"to": [10, 16, 9],
"faces": {
"north": {"uv": [7, 1.5, 8, 2], "texture": "#0"},
"east": {"uv": [7.25, 4.5, 7.75, 5], "texture": "#0"},
"south": {"uv": [7, 2, 8, 2.5], "texture": "#0"},
"west": {"uv": [7.25, 5, 7.75, 5.5], "texture": "#0"},
"up": {"uv": [8, 3, 7, 2.5], "texture": "#0"},
"down": {"uv": [5, 7, 4, 7.5], "texture": "#0"}
}
},
{
"name": "screen",
"from": [4, 6, -2],
"to": [12, 12, -1],
"rotation": {"angle": 22.5, "axis": "x", "origin": [0, 0, 0]},
"faces": {
"north": {"uv": [4, 0, 6, 1.5], "texture": "#0"},
"east": {"uv": [7, 4, 7.25, 5.5], "texture": "#0"},
"south": {"uv": [4, 1.5, 6, 3], "texture": "#0"},
"west": {"uv": [5, 7, 5.25, 8.5], "texture": "#0"},
"up": {"uv": [9, 1.25, 7, 1], "texture": "#0"},
"down": {"uv": [9, 1.25, 7, 1.5], "texture": "#0"}
}
}
]
}

View File

@@ -0,0 +1,6 @@
{
"parent": "quaedam:block/projection",
"textures": {
"ext": "quaedam:block/music_projection"
}
}

View File

@@ -0,0 +1,6 @@
{
"parent": "quaedam:block/projection",
"textures": {
"ext": "quaedam:block/noise_projection"
}
}

View File

@@ -0,0 +1,49 @@
{
"parent": "minecraft:block/block",
"texture_size": [64, 64],
"textures": {
"0": "quaedam:block/reality_stabler",
"particle": "quaedam:block/reality_stabler"
},
"elements": [
{
"name": "bottom",
"from": [1, 0, 1],
"to": [15, 1, 15],
"faces": {
"north": {"uv": [7.5, 10.5, 11, 10.75], "texture": "#0"},
"east": {"uv": [7.5, 10.75, 11, 11], "texture": "#0"},
"south": {"uv": [7.5, 11, 11, 11.25], "texture": "#0"},
"west": {"uv": [11, 10.5, 14.5, 10.75], "texture": "#0"},
"up": {"uv": [11.5, 3.5, 8, 0], "texture": "#0"},
"down": {"uv": [11.5, 3.5, 8, 7], "texture": "#0"}
}
},
{
"name": "center",
"from": [0, 1, 0],
"to": [16, 14, 16],
"faces": {
"north": {"uv": [4, 0, 8, 3.25], "texture": "#0"},
"east": {"uv": [4, 3.25, 8, 6.5], "texture": "#0"},
"south": {"uv": [4, 6.5, 8, 9.75], "texture": "#0"},
"west": {"uv": [0, 8, 4, 11.25], "texture": "#0"},
"up": {"uv": [4, 4, 0, 0], "texture": "#0"},
"down": {"uv": [4, 4, 0, 8], "texture": "#0"}
}
},
{
"name": "top",
"from": [1, 14, 1],
"to": [15, 15, 15],
"faces": {
"north": {"uv": [11, 10.75, 14.5, 11], "texture": "#0"},
"east": {"uv": [11, 11, 14.5, 11.25], "texture": "#0"},
"south": {"uv": [0, 11.25, 3.5, 11.5], "texture": "#0"},
"west": {"uv": [7.5, 11.25, 11, 11.5], "texture": "#0"},
"up": {"uv": [11.5, 10.5, 8, 7], "texture": "#0"},
"down": {"uv": [7.5, 9.75, 4, 13.25], "texture": "#0"}
}
}
]
}

View File

@@ -0,0 +1,3 @@
{
"parent": "minecraft:block/note_block"
}

View File

@@ -0,0 +1,6 @@
{
"parent": "quaedam:block/projection",
"textures": {
"ext": "quaedam:block/sound_projection"
}
}

View File

@@ -0,0 +1,3 @@
{
"parent": "quaedam:block/causality_anchor"
}

View File

@@ -0,0 +1,6 @@
{
"parent": "item/generated",
"textures": {
"layer0": "quaedam:item/iron_copper_metal"
}
}

View File

@@ -0,0 +1,3 @@
{
"parent": "quaedam:block/music_projection"
}

View File

@@ -0,0 +1,3 @@
{
"parent": "quaedam:block/noise_projection"
}

View File

@@ -0,0 +1,6 @@
{
"parent": "item/generated",
"textures": {
"layer0": "quaedam:item/projection_metal"
}
}

View File

@@ -0,0 +1,6 @@
{
"parent": "item/generated",
"textures": {
"layer0": "quaedam:item/projection_shell"
}
}

View File

@@ -0,0 +1,3 @@
{
"parent": "quaedam:block/reality_stabler"
}

View File

@@ -0,0 +1,3 @@
{
"parent": "quaedam:block/smart_instrument"
}

View File

@@ -0,0 +1,3 @@
{
"parent": "quaedam:block/sound_projection"
}

View File

@@ -0,0 +1,178 @@
{
"entity.projected_person.noise": {
"sounds": [
{
"name": "entity.villager.ambient",
"weight": 4,
"type": "event"
},
{
"name": "entity.villager.yes",
"weight": 2,
"type": "event"
},
{
"name": "entity.villager.no",
"weight": 1,
"type": "event"
},
{
"name": "entity.villager.trade",
"weight": 3,
"type": "event"
},
{
"name": "entity.villager.work_librarian",
"weight": 2,
"type": "event"
},
{
"name": "entity.villager.work_cleric",
"weight": 2,
"type": "event"
},
{
"name": "entity.villager.work_mason",
"weight": 2,
"type": "event"
},
{
"name": "entity.villager.work_shepherd",
"weight": 2,
"type": "event"
},
{
"name": "entity.wandering_trader.drink_milk",
"weight": 1,
"volume": 0.5,
"type": "event"
},
{
"name": "entity.wandering_trader.no",
"weight": 1,
"type": "event"
},
{
"name": "entity.wandering_trader.yes",
"weight": 1,
"type": "event"
},
{
"name": "block.sand.step",
"weight": 2,
"type": "event"
},
{
"name": "block.scaffolding.step",
"weight": 2,
"type": "event"
},
{
"name": "block.stone.step",
"weight": 2,
"type": "event"
},
{
"name": "block.grass.step",
"weight": 2,
"type": "event"
},
{
"name": "block.wet_grass.step",
"weight": 1,
"type": "event"
},
{
"name": "block.bamboo_wood.step",
"weight": 1,
"type": "event"
},
{
"name": "block.cherry_wood.step",
"weight": 1,
"type": "event"
},
{
"name": "block.wood.step",
"weight": 2,
"type": "event"
}
]
},
"quaedam.projection.noise": {
"sounds": [
{
"name": "entity.villager.work_armorer",
"type": "event"
},
{
"name": "entity.villager.work_butcher",
"type": "event"
},
{
"name": "entity.villager.work_cartographer",
"type": "event"
},
{
"name": "entity.villager.work_cleric",
"type": "event"
},
{
"name": "entity.villager.work_farmer",
"type": "event"
},
{
"name": "entity.villager.work_fisherman",
"type": "event"
},
{
"name": "entity.villager.work_fletcher",
"type": "event"
},
{
"name": "entity.villager.work_leatherworker",
"type": "event"
},
{
"name": "entity.villager.work_librarian",
"type": "event"
},
{
"name": "entity.villager.work_mason",
"type": "event"
},
{
"name": "entity.villager.work_shepherd",
"type": "event"
},
{
"name": "entity.villager.work_toolsmith",
"type": "event"
},
{
"name": "entity.villager.work_weaponsmith",
"type": "event"
},
{
"name": "block.anvil.use",
"type": "event"
},
{
"name": "block.furnace.fire_crackle",
"type": "event"
},
{
"name": "block.blastfurnace.fire_crackle",
"type": "event"
},
{
"name": "block.metal.break",
"type": "event"
},
{
"name": "block.wood.break",
"type": "event"
}
]
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 396 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 254 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 907 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 845 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 834 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 888 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 707 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 930 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 872 B

Some files were not shown because too many files have changed in this diff Show More