Score Vectors

A ScoreVector is a runtime 3D vector stored in three scores of an entity (<name>_x, <name>_y, <name>_z). It reads positions, motion and look directions, does the vector maths, then writes the result back as a teleport, a motion or a rotation. The same data covers what Bookshelf splits into bs.vector, bs.position, bs.move and bs.view.

val math = registerMath()

function("dash") {
	val dash = math.vector("dash", player)
	dash.setToLookDirection(self())
	dash *= 3
	dash.teleport(self(), TeleportMode.RELATIVE)
}
Kotlin

Components are fixed-point with a scale of 1000 by default, so 1500 means 1.5 blocks. math.vector registers the three objectives in the math load function. Operations between two vectors expect the same scale.

Footprint
  • Objectives: <name>_x, <name>_y and <name>_z per vector, plus kore_math.
  • Storage: <pack>:kore_math, holding the len of normalize, the rotation of setToLookDirection, the motion read by setToMotion and the teleport and look macro arguments.
  • Entities: one minecraft:marker, UUID 4b4f5245-0000-0000-0000-000000000001, tagged kore.math, smithed.entity and smithed.strict, summoned once at -30000000 0 1664 by the operations that read positions or directions.
  • Forceloaded chunks: the one holding those entities, x -30000000 z 1664.
  • Every tick: no.
  • Load and files: kore_math_init creates the objectives, entities, storage and the #<n> constants.

Reading vectors

function("track_arrow") {
	val velocity = math.vector("velocity", arrow)
	velocity.setToMotion(self()) // blocks per tick
}
Kotlin
  • setToPosition(entity) reads the position off the math marker teleported onto the entity, never touching its NBT, since reading any field of a player's NBT serializes the whole player. setToMotion(entity) copies Motion to storage once and reads the three components from there.
  • setToLookDirection(entity) gives the unit vector the entity looks along, the direction of ^ ^ ^1, truncated toward zero. The math marker copies the entity's rotation, then one compute per component applies the game's sine table to it, so no player NBT is read.
  • set(x, y, z) takes plain numbers, set(0.0, 0.5, 0.0) stores 0 500 0.
  • setTo(other) copies another vector.

Arithmetic

Operators come first, they read like plain Kotlin:

function("wind_push") {
	velocity += wind
	velocity *= 2
	velocity /= 4
}
Kotlin
  • += / -= add or subtract another vector.
  • *= / /= with an Int scale every component, with Minecraft's floored division.
  • *= with a ScoreboardEntity multiplies by a fixed-point factor of the same scale, 500 halves the vector.

Dot and cross products

function("backstab_check") {
	attackerLook.dot(victimLook, alignment) // 1000 = same direction
}
Kotlin

dot(other, output) writes the fixed-point dot product in one compute. cross(other, output) writes this × other into a third vector with one compute per component, the output must differ from both operands. A product or a sum that doesn't fit an Int (past about 46 blocks per component at scale 1000) writes 0.

A compute reads the scores of one real entity at most. When the two vectors belong to different real entities, the scores of the second are first copied into scratch fake players, one extra command each. Every vector read by dot, cross, length, distanceTo, lengthSquared and normalize must belong to a single entity (limitToOne), or building the datapack throws.

Length and distance

function("leash_range") {
	playerPos.distanceTo(petPos, distance)
}
Kotlin

length(output) and distanceTo(other, output) write the length, rounded down, in the vector's scale. One compute runs the game's length provider, which sums the squares as floats, so it never overflows and stays within 1 unit of the exact length. A component difference that doesn't fit an Int in distanceTo writes 0.

To measure the distance between two non-player entities without filling vectors first, Entity.distanceTo of the oop module reads both Pos lists straight into one compute, about 6x faster than two setToPosition plus distanceTo. It reads the coordinates as floats, so it loses precision far from the origin (about 1 unit at 10,000 blocks), where setToPosition stays exact.

lengthSquared(output) is one compute and is enough for comparisons, but it writes 0 past about 46 blocks at scale 1000.

Normalizing

function("homing_missile") {
	toTarget.setTo(targetPos)
	toTarget -= missilePos
	toTarget.normalize()
}
Kotlin

normalize() rescales the vector to a length of 1000, each component truncated toward zero. One compute stores the length in the math storage, then one compute per component divides by it. A zero vector has no direction and stays 0.

Writing vectors back

function("launch_pad") {
	launch.set(0.0, 1.2, 0.0)
	launch.applyAsMotion(self())
}
Kotlin
  • applyAsMotion(entity) writes Motion, in blocks per tick. Players ignore Motion changes, teleport them instead.
  • teleport(entity, mode) teleports through a macro tp, so it works on players:
    • TeleportMode.ABSOLUTE treats the vector as world coordinates,
    • TeleportMode.RELATIVE offsets from the entity (~x ~y ~z),
    • TeleportMode.LOCAL offsets along the entity's view (^x ^y ^z, x is left, z is forward).
  • lookAlong(entity) turns the entity to face along the vector, through a macro rotate so it also works on players. A zero vector keeps the rotation.

Native physics attributes

For bounces and sliding, the game's attributes handle collision themselves, with no per-tick resolution in the pack: bounciness sets how much velocity survives a collision, friction_modifier scales ground friction, and air_drag_modifier scales air drag.

function("rubber_slime") {
	attributes(self(), Attributes.BOUNCINESS) {
		base { set(0.9) }
	}
}
Kotlin

See also

  • Scoreboard Math - Sine, cosine, atan2 and square root on the same fixed-point scale.
  • Raycasts - Step along a look direction until a block is hit.
  • Display Entities - Compile-time transformations with Vec3f and quaternions.