AlmostReliable / morejs

A Minecraft mod to extend KubeJS with additional events.
https://www.curseforge.com/minecraft/mc-mods/morejs
GNU Lesser General Public License v3.0
12 stars 1 forks source link

feat: added afterPlace event #7

Closed pietro-lopes closed 1 year ago

pietro-lopes commented 1 year ago

Proposed Changes

My use case was that I needed to place a feature for each chunk that a structure was generated. Tested single player and multiplayer, it is working fine (didn't test fabric multiplayer tho)

Here are a few methods and usage:

MoreJSEvents.structureAfterPlace((event) => {
  /**
   * `event.id`: The id of the structure.
   * `event.type`: The type of the structure (jigsaw, mineshaft, stronghold...).
   * `event.genStep`: The generation step of the structure. 
   * `event.structure`: The structure being generated.
   * `event.structureManager`: The structure manager, required for some methods.
   * `event.structureBoundingBox`: The full structure bounding box.
   * `event.chunkBoundingBox`: The full chunk bounding box.
   * `event.chunkGenerator`: The chunk generator, required for some methods.
   * `event.chunkPos`: The coordinate of the current chunk.
   * `event.randomSource`: The random source, required for some methods.
   * `event.worldGenLevel`: The world gen level, required for some methods.
   * `event.piecesContainer`: The collection of structure pieces.
   * `event.intersectionBoxes`: The collection of EACH PIECE's bounding boxes that intersects current chunk.
   * `event.intersectionPieces`: The collection of structure pieces that intersects current chunk.
   * `event.intersectionMap`: The map of both above, useful when you need both values at same time.
   * `event.getPieceType(StructurePieceType)`: Returns the structure piece type.
   */

  if (event.id == "minecraft:mineshaft") return // checking ids
  if (event.type == "minecraft:jigsaw") return // checking types
  if (event.genStep == "underground_structures") return // checking generation step
  console.log(event.structure.terrainAdaptation().name()) // printing terrain adaptation of the structure
  let registryAccess = event.structureManager.registryAccess() // getting registry access from struc manager
  let structureCenter = event.structureBoundingBox.center // getting structure center
  let isCenterChunk = event.chunkBoundingBox.isInside(structureCenter) // check if we are at center chunk
  let seaLevel = event.chunkGenerator.seaLevel // getting sea level
  let newY = event.randomSource.nextIntBetweenInclusive(0,10) + seaLevel // using randomSource to give random number between 0 and 10
  let pieces = event.piecesContainer.pieces() // getting a list of structure pieces
  let chunkAccess = event.worldGenLevel.getChunk(event.chunkPos.x, event.chunkPos.z) // getting chunk access to do some stuff you can't with others
  if (event.intersectionBoxes.empty) return // checking if any bounding box of the pieces intersected current chunk
  event.intersectionPieces.forEach((sp) => console.log(event.getPieceType(sp.type))) // printing the type of the structure pieces

  // loops each bounding box of current chunk and replaces all blocks with glass
  event.intersectionBoxes.forEach((bb) => {
    BlockPos.betweenClosed(bb.minX(), bb.minY(), bb.minZ(), bb.maxX(), bb.maxY(), bb.maxZ()).forEach((pos) =>
      event.worldGenLevel.setBlock(pos, Blocks.GLASS.defaultBlockState(), 2)
    )
  })
})

You can directly reach me at KubeJS Discord, I'm very active there, Uncandango.

pietro-lopes commented 1 year ago

Proof of concept, adding a sign when it generates an underground structure https://streamable.com/4rkbf8

const $Heightmap$Types = Java.loadClass("net.minecraft.world.level.levelgen.Heightmap$Types")

MoreJSEvents.structureAfterPlace((event) => {
  let currChunkBB = event.chunkBoundingBox
  let strucBB = event.structureBoundingBox
  if (!currChunkBB.isInside(strucBB.center)) return
  if (strucBB.maxY() > 64) return
  let strucTitle = Utils.snakeCaseToTitleCase(event.id.path)
  let groundY = event.worldGenLevel.getChunk(strucBB.center).getHeight($Heightmap$Types.WORLD_SURFACE_WG, strucBB.center.x, strucBB.center.z)
  let signPos = strucBB.center.mutable().setY(groundY + 1)
  let signBlockState = Blocks.OAK_SIGN.defaultBlockState()
  let canSurvive = signBlockState.canSurvive(event.worldGenLevel, signPos)
  if (!canSurvive) event.worldGenLevel.setBlock(signPos.below(), Blocks.SANDSTONE.defaultBlockState(), 2)
  event.worldGenLevel.setBlock(signPos, signBlockState, 2)
  /** @type {Internal.SignBlockEntity} */
  let sign = event.worldGenLevel.getBlockEntity(signPos)
  let nbt = sign.getUpdateTag()
  nbt["front_text"].messages[1] = Text.darkGray("Structure Name").toJson().toString()
  nbt["front_text"].messages[2] = Text.red(strucTitle).toJson().toString()
  nbt["front_text"]["has_glowing_text"] = NBT.b(1)
  sign.load(nbt)
  event.server.tell(Text.of("Generated underground structure: ").append(Text.green(strucTitle).clickRunCommand(`/tp @s ${sign.blockPos.x} ${sign.blockPos.y+2} ${sign.blockPos.z}`).hover(Text.of("Click to teleport"))))
})
Streamable
Proof of concept, MoreJS - Structures AfterPlace event
Watch "Proof of concept, MoreJS - Structures AfterPlace event" on Streamable.
pietro-lopes commented 1 year ago

I can't add the beforePlace because I coudn't solve some issues like: if I injected as soon as it validates the structure and run a script that is very aggressive on cancelling (like all strongholds) and if you try to /locate that you would end up with your server hanged (no error, just hangs trying to find structure forever) If I injected too late, you will have ghost locations on /locate, that it says there is a structure there, but actually it doesnt.

Couldn't find a middle term so I gave up, already spent too much time on that and I'm having trouble trying to debug because it says source code doesn't match bytecode for most of it...

With that said, the afterPlace is working great.

stale[bot] commented 1 year ago

This submission has been automatically marked as abandoned because it has not had recent activity. It will be closed in 3 days. If you want to prevent that, leave a new comment.

rlnt commented 1 year ago

This submission has been automatically marked as abandoned because it has not had recent activity. It will be closed in 3 days. If you want to prevent that, leave a new comment.

shoo

LLytho commented 1 year ago

I can't add the beforePlace because I coudn't solve some issues like: if I injected as soon as it validates the structure and run a script that is very aggressive on cancelling (like all strongholds) and if you try to /locate that you would end up with your server hanged (no error, just hangs trying to find structure forever) If I injected too late, you will have ghost locations on /locate, that it says there is a structure there, but actually it doesnt.

Couldn't find a middle term so I gave up, already spent too much time on that and I'm having trouble trying to debug because it says source code doesn't match bytecode for most of it...

With that said, the afterPlace is working great.

Yeah we will just skip beforePlace then.

But I also see that there are potential cases where people can lock their world in after place too as the structure generation happens on different threads.

But other than this, lgtm