Compare commits

...

4 Commits

Author SHA1 Message Date
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
20 changed files with 404 additions and 55 deletions

View File

@ -3,6 +3,7 @@ import net.fabricmc.loom.api.LoomGradleExtensionAPI
plugins {
java
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.3-SNAPSHOT" apply false
id("com.github.johnrengelman.shadow") version "8.1.1" apply false
@ -31,6 +32,7 @@ subprojects {
allprojects {
apply(plugin = "java")
apply(plugin = "kotlin")
apply(plugin = "kotlinx-serialization")
apply(plugin = "architectury-plugin")
apply(plugin = "maven-publish")
@ -47,6 +49,7 @@ allprojects {
dependencies {
compileOnly("org.jetbrains.kotlin:kotlin-stdlib")
compileOnly("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1")
}
tasks.withType<JavaCompile> {

View File

@ -1,12 +1,13 @@
package quaedam.config
import com.google.gson.Gson
import com.google.gson.GsonBuilder
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.CompoundTag
import net.minecraft.nbt.*
import quaedam.Quaedam
import java.nio.file.Path
import kotlin.io.path.exists
@ -14,12 +15,27 @@ import kotlin.io.path.notExists
import kotlin.io.path.readText
import kotlin.io.path.writeText
@Serializable
data class QuaedamConfig(
val projectorEffectRadius: Int = 4
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 {
const val TAG_PROJECTOR_EFFECT_RADIUS = "ProjectorEffectRadius"
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()
@ -29,7 +45,7 @@ data class QuaedamConfig(
local0 = value
writeLocalConfig()
}
var remote: QuaedamConfig? = null
private var remote: QuaedamConfig? = null
val current get() = remote ?: local0
init {
@ -48,13 +64,13 @@ data class QuaedamConfig(
}
private fun loadLocalConfig(): QuaedamConfig = if (localFile.exists()) {
Gson().fromJson(localFile.readText(), QuaedamConfig::class.java)
localJson.decodeFromString(localFile.readText())
} else {
QuaedamConfig()
}
private fun writeLocalConfig() {
localFile.writeText(GsonBuilder().serializeNulls().setPrettyPrinting().create().toJson(local0))
localFile.writeText(localJson.encodeToString(local0))
}
fun applyRemoteConfig(config: QuaedamConfig?) {
@ -62,15 +78,28 @@ data class QuaedamConfig(
remote = config
}
fun fromPushNbt(tag: CompoundTag) = QuaedamConfig(
projectorEffectRadius = tag.getInt(TAG_PROJECTOR_EFFECT_RADIUS)
)
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.putInt(TAG_PROJECTOR_EFFECT_RADIUS, projectorEffectRadius)
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(forPush: Boolean) = CompoundTag().also { toPushNbt(it) }
fun toPushNbt() = CompoundTag().also { toPushNbt(it) }
}

View File

@ -27,7 +27,7 @@ object SimpleQuaedamConfigPush {
fun sendCurrent(player: ServerPlayer) = send(player, QuaedamConfig.current)
fun send(player: ServerPlayer, config: QuaedamConfig) = send(player, config.toPushNbt(forPush = true))
fun send(player: ServerPlayer, config: QuaedamConfig) = send(player, config.toPushNbt())
private fun send(player: ServerPlayer, data: CompoundTag) {
val buf = FriendlyByteBuf(Unpooled.buffer())

View File

@ -13,6 +13,7 @@ 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
@ -107,6 +108,9 @@ data class NoiseProjectionEffect(var rate: Int = 250, var amount: Int = 3) : Pro
companion object {
const val TAG_RATE = "Rate"
const val TAG_AMOUNT = "Amount"
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
@ -121,14 +125,14 @@ data class NoiseProjectionEffect(var rate: Int = 250, var amount: Int = 3) : Pro
rate = tag.getInt(TAG_RATE)
amount = tag.getInt(TAG_AMOUNT)
if (!trusted) {
amount = min(amount, 8)
rate = min(rate, 500)
amount = min(amount, maxAmount)
rate = min(rate, maxRate)
}
}
override fun createShell() = buildProjectionEffectShell(this) {
intSlider("quaedam.shell.noise.rate", ::rate, 0..300 step 5)
intSlider("quaedam.shell.noise.amount", ::amount, 0..8)
intSlider("quaedam.shell.noise.rate", ::rate, 0..maxRate step 5)
intSlider("quaedam.shell.noise.amount", ::amount, 0..maxAmount)
}
}

View File

@ -4,6 +4,7 @@ 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
@ -46,6 +47,8 @@ data class SkylightProjectionEffect(var factor: Double = 2.0) : ProjectionEffect
companion object {
const val TAG_FACTOR = "Factor"
val maxFactor get() = QuaedamConfig.current.valuesDouble["projection.skylight.max_factor"] ?: 5.0
}
override val type
@ -58,12 +61,12 @@ data class SkylightProjectionEffect(var factor: Double = 2.0) : ProjectionEffect
override fun fromNbt(tag: CompoundTag, trusted: Boolean) {
factor = tag.getDouble(TAG_FACTOR)
if (!trusted) {
factor = min(factor, 5.0)
factor = min(factor, maxFactor)
}
}
override fun createShell() = buildProjectionEffectShell(this) {
doubleSlider("quaedam.shell.skylight.factor", ::factor, 0.0..5.0, 0.1)
doubleSlider("quaedam.shell.skylight.factor", ::factor, 0.0..maxFactor, 0.1)
}
}

View File

@ -4,6 +4,7 @@ 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
@ -46,6 +47,8 @@ data class SoundProjectionEffect(var rate: Int = 60) : ProjectionEffect(), Proje
companion object {
const val TAG_RATE = "Rate"
val maxRate get() = QuaedamConfig.current.valuesInt["projection.sound.max_rate"] ?: 210
}
override val type
@ -58,12 +61,12 @@ data class SoundProjectionEffect(var rate: Int = 60) : ProjectionEffect(), Proje
override fun fromNbt(tag: CompoundTag, trusted: Boolean) {
rate = tag.getInt(TAG_RATE)
if (!trusted) {
rate = min(rate, 210)
rate = min(rate, maxRate)
}
}
override fun createShell() = buildProjectionEffectShell(this) {
intSlider("quaedam.shell.sound.rate", ::rate, 0..210)
intSlider("quaedam.shell.sound.rate", ::rate, 0..maxRate)
}
}

View File

@ -4,6 +4,7 @@ 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
@ -52,6 +53,9 @@ data class MusicProjectionEffect(var volumeFactor: Float = 1.0f, var particle: B
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
@ -66,13 +70,16 @@ data class MusicProjectionEffect(var volumeFactor: Float = 1.0f, var particle: B
volumeFactor = tag.getFloat(TAG_VOLUME_FACTOR)
particle = tag.getBoolean(TAG_PARTICLE)
if (!trusted) {
volumeFactor = min(volumeFactor, 5.0f)
volumeFactor = min(volumeFactor, maxVolumeFactor)
particle = enforceParticle ?: particle
}
}
override fun createShell() = buildProjectionEffectShell(this) {
floatSlider("quaedam.shell.music.volume_factor", ::volumeFactor, 0.0f..5.0f, 0.1f)
boolean("quaedam.shell.music.particle", ::particle)
floatSlider("quaedam.shell.music.volume_factor", ::volumeFactor, 0.0f..maxVolumeFactor, 0.1f)
if (enforceParticle == null) {
boolean("quaedam.shell.music.particle", ::particle)
}
}
}

View File

@ -5,6 +5,7 @@ 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
@ -18,6 +19,8 @@ data class SwarmProjectionEffect(
companion object {
const val TAG_MAX_COUNT = "MaxCount"
val maxMaxCount get() = QuaedamConfig.current.valuesInt["projection.swarm.max_max_count"] ?: 250
}
override val type
@ -30,7 +33,7 @@ data class SwarmProjectionEffect(
override fun fromNbt(tag: CompoundTag, trusted: Boolean) {
maxCount = tag.getInt(TAG_MAX_COUNT)
if (!trusted) {
maxCount = min(maxCount, 250)
maxCount = min(maxCount, maxMaxCount)
}
}
@ -60,7 +63,7 @@ data class SwarmProjectionEffect(
}
override fun createShell() = buildProjectionEffectShell(this) {
intSlider("quaedam.shell.swarm.max_count", ::maxCount, 0..250 step 5)
intSlider("quaedam.shell.swarm.max_count", ::maxCount, 0..maxMaxCount step 5)
}
}

View File

@ -68,7 +68,7 @@ class ExchangeItem<E> : Behavior<E>(
private fun exchangeItems(level: ServerLevel, entity: E) {
val container = level.getBlockEntity(target!!) as Container
val inventory = entity.inventory
for (i in 1..6) {
for (i in 1..10) {
val maxCount = 1 + level.random.nextInt(16)
if (level.random.nextBoolean()) {
// take
@ -77,7 +77,7 @@ class ExchangeItem<E> : Behavior<E>(
if (!item.isEmpty) {
val takeCount = min(item.count, maxCount)
val takeItem = item.copyWithCount(takeCount)
if (inventory.canTakeItem(container, slot, takeItem) && entity.canHoldItem(takeItem)) {
if (entity.canHoldItem(takeItem)) {
val remaining = inventory.addItem(/*entity.equipItemIfPossible(takeItem)*/ takeItem)
val actualCount = takeCount - remaining.count
item.shrink(actualCount)
@ -91,33 +91,31 @@ class ExchangeItem<E> : Behavior<E>(
if (!item.isEmpty) {
val takeCount = min(item.count, maxCount)
val takeItem = item.copyWithCount(takeCount)
if (container.canTakeItem(inventory, slot, takeItem)) {
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 (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 (targetItem.isEmpty) {
container.setItem(target, takeItem.copyAndClear())
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)
}
val putCount = takeCount - takeItem.count
item.shrink(putCount)
inventory.setItem(slot, item)
}
}
}

View File

@ -30,7 +30,7 @@ object Projector {
BlockEntityType.Builder.of(::ProjectorBlockEntity, block.get()).build(null)
}!!
val currentEffectRadius get() = QuaedamConfig.current.projectorEffectRadius
val currentEffectRadius get() = QuaedamConfig.current.valuesInt["projector.effect_radius"] ?: 4
fun findNearbyProjectors(level: Level, pos: BlockPos) = level.getChunksNearby(pos, currentEffectRadius)
.flatMap {

View File

@ -27,5 +27,19 @@
"quaedam.shell.music.volume_factor": "Volume Factor",
"quaedam.shell.music.particle": "Particle",
"quaedam.shell.music.particle.true": "Display",
"quaedam.shell.music.particle.false": "Hidden"
"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

@ -27,5 +27,19 @@
"quaedam.shell.music.volume_factor": "响度因子",
"quaedam.shell.music.particle": "粒子效果",
"quaedam.shell.music.particle.true": "显示",
"quaedam.shell.music.particle.false": "隐藏"
"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

@ -27,5 +27,19 @@
"quaedam.shell.music.volume_factor": "振幅大小",
"quaedam.shell.music.particle": "会变色的颗粒buff",
"quaedam.shell.music.particle.true": "打开",
"quaedam.shell.music.particle.false": "关掉,关掉,一定要关掉"
"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,37 @@
{
"display": {
"icon": {
"item": "quaedam:causality_anchor"
},
"title": {
"translate": "advancements.quaedam.causality_anchor.title"
},
"description": {
"translate": "advancements.quaedam.causality_anchor.description"
},
"frame": "goal"
},
"parent": "quaedam:projector",
"criteria": {
"causality_anchor": {
"trigger": "minecraft:placed_block",
"conditions": {
"location": [
{
"condition": "minecraft:block_state_property",
"block": "quaedam:causality_anchor",
"properties": {}
}
]
}
}
},
"requirements": [
[
"causality_anchor"
]
],
"rewards": {
"experience": 30
}
}

View File

@ -0,0 +1,37 @@
{
"display": {
"icon": {
"item": "quaedam:swarm_projection"
},
"title": {
"translate": "advancements.quaedam.kill_projected_person.title"
},
"description": {
"translate": "advancements.quaedam.kill_projected_person.description"
},
"hidden": true
},
"parent": "quaedam:swarm_projection",
"criteria": {
"kill_projected_person": {
"trigger": "minecraft:player_killed_entity",
"conditions": {
"entity": [
{
"condition": "minecraft:entity_properties",
"entity": "this",
"predicate": {
"type": "quaedam:projected_person"
}
}
]
}
}
},
"requirements": [
[
"kill_projected_person"
]
],
"rewards": {}
}

View File

@ -0,0 +1,37 @@
{
"display": {
"icon": {
"item": "quaedam:projector"
},
"title": {
"translate": "advancements.quaedam.projector.title"
},
"description": {
"translate": "advancements.quaedam.projector.description"
},
"background": "minecraft:textures/gui/advancements/backgrounds/husbandry.png",
"frame": "task"
},
"criteria": {
"projector": {
"trigger": "minecraft:placed_block",
"conditions": {
"location": [
{
"condition": "minecraft:block_state_property",
"block": "quaedam:projector",
"properties": {}
}
]
}
}
},
"requirements": [
[
"projector"
]
],
"rewards": {
"experience": 15
}
}

View File

@ -0,0 +1,37 @@
{
"display": {
"icon": {
"item": "quaedam:reality_stabler"
},
"title": {
"translate": "advancements.quaedam.reality_stabler.title"
},
"description": {
"translate": "advancements.quaedam.reality_stabler.description"
},
"frame": "goal"
},
"parent": "quaedam:projector",
"criteria": {
"reality_stabler": {
"trigger": "minecraft:placed_block",
"conditions": {
"location": [
{
"condition": "minecraft:block_state_property",
"block": "quaedam:reality_stabler",
"properties": {}
}
]
}
}
},
"requirements": [
[
"reality_stabler"
]
],
"rewards": {
"experience": 30
}
}

View File

@ -0,0 +1,36 @@
{
"display": {
"icon": {
"item": "quaedam:smart_instrument"
},
"title": {
"translate": "advancements.quaedam.smart_instrument.title"
},
"description": {
"translate": "advancements.quaedam.smart_instrument.description"
}
},
"parent": "quaedam:projector",
"criteria": {
"smart_instrument": {
"trigger": "minecraft:placed_block",
"conditions": {
"location": [
{
"condition": "minecraft:block_state_property",
"block": "quaedam:smart_instrument",
"properties": {}
}
]
}
}
},
"requirements": [
[
"smart_instrument"
]
],
"rewards": {
"experience": 15
}
}

View File

@ -0,0 +1,36 @@
{
"display": {
"icon": {
"item": "quaedam:sound_projection"
},
"title": {
"translate": "advancements.quaedam.sound_projection.title"
},
"description": {
"translate": "advancements.quaedam.sound_projection.description"
}
},
"parent": "quaedam:swarm_projection",
"criteria": {
"sound_projection": {
"trigger": "minecraft:placed_block",
"conditions": {
"location": [
{
"condition": "minecraft:block_state_property",
"block": "quaedam:sound_projection",
"properties": {}
}
]
}
}
},
"requirements": [
[
"sound_projection"
]
],
"rewards": {
"experience": 15
}
}

View File

@ -0,0 +1,37 @@
{
"display": {
"icon": {
"item": "quaedam:swarm_projection"
},
"title": {
"translate": "advancements.quaedam.swarm_projection.title"
},
"description": {
"translate": "advancements.quaedam.swarm_projection.description"
},
"frame": "goal"
},
"parent": "quaedam:projector",
"criteria": {
"swarm_projection": {
"trigger": "minecraft:placed_block",
"conditions": {
"location": [
{
"condition": "minecraft:block_state_property",
"block": "quaedam:swarm_projection",
"properties": {}
}
]
}
}
},
"requirements": [
[
"swarm_projection"
]
],
"rewards": {
"experience": 30
}
}