LookAtMySuitBot/js/node_modules/prismarine-block/index.js

391 lines
14 KiB
JavaScript
Raw Normal View History

2023-12-24 20:08:39 -05:00
module.exports = loader
module.exports.testedVersions = ['1.8.8', '1.9.4', '1.10.2', '1.11.2', '1.12.2', '1.13.2', '1.14.4', '1.15.2', '1.16.4', '1.17.1', '1.18.1', 'bedrock_1.17.10', 'bedrock_1.18.0', '1.20']
const nbt = require('prismarine-nbt')
const mcData = require('minecraft-data')
const legacyPcBlocksByName = Object.entries(mcData.legacy.pc.blocks).reduce((obj, [idmeta, name]) => {
const n = name.replace('minecraft:', '').split('[')[0]
const s = name.split('[')[1]?.replace(']', '') ?? ''
;(obj[n] = obj[n] || {})[s] = idmeta
return obj // array of { [name]: { [states string]: string(id:meta) } }
}, {})
const legacyPcBlocksByIdmeta = Object.entries(mcData.legacy.pc.blocks).reduce((obj, [idmeta, name]) => {
const s = name.split('[')[1]?.replace(']', '')
obj[idmeta] = s
? Object.fromEntries(s.split(',').map(s => {
let [k, v] = s.split('=')
if (!isNaN(parseInt(v))) v = parseInt(v)
return [k, v]
}))
: {}
return obj // array of { '255:0': { mode: 'save' }, }
}, {})
function loader (registryOrVersion) {
const registry = typeof registryOrVersion === 'string' ? require('prismarine-registry')(registryOrVersion) : registryOrVersion
const version = registry.version
return provider(registry, { Biome: require('prismarine-biome')(version), version })
}
function provider (registry, { Biome, version }) {
const blockMethods = require('./blockEntity')(registry)
const usesBlockStates = (version.type === 'pc' && registry.supportFeature('blockStateId')) || (version.type === 'bedrock')
const shapes = registry.blockCollisionShapes
if (shapes) {
// Prepare block shapes
for (const id in registry.blocks) {
const block = registry.blocks[id]
const shapesId = shapes.blocks[block.name]
block.shapes = (shapesId instanceof Array) ? shapes.shapes[shapesId[0]] : shapes.shapes[shapesId]
if (block.states || version.type === 'bedrock') { // post 1.13
if (shapesId instanceof Array) {
block.stateShapes = []
for (const i in shapesId) {
block.stateShapes.push(shapes.shapes[shapesId[i]])
}
}
} else { // pre 1.13
if ('variations' in block) {
for (const i in block.variations) {
const metadata = block.variations[i].metadata
if (shapesId instanceof Array) {
block.variations[i].shapes = shapes.shapes[shapesId[metadata]]
} else {
block.variations[i].shapes = shapes.shapes[shapesId]
}
}
}
}
if (!block.shapes && version.type === 'bedrock') {
// if no shapes are present for this block (for example, some chemistry stuff we don't have BBs for), assume it's stone
block.shapes = shapes.shapes[shapes.blocks.stone[0]]
block.stateShapes = block.shapes
}
}
}
function getEffectLevel (effectName, effects) {
const effectDescriptor = registry.effectsByName[effectName]
if (!effectDescriptor) {
return 0
}
const effectInfo = effects[effectDescriptor.id]
if (!effectInfo) {
return 0
}
return effectInfo.amplifier + 1
}
function getEnchantmentLevel (enchantmentName, enchantments) {
const enchantmentDescriptor = registry.enchantmentsByName[enchantmentName]
if (!enchantmentDescriptor) {
return 0
}
for (const enchInfo of enchantments) {
if (typeof enchInfo.name === 'string') {
if (enchInfo.name.includes(enchantmentName)) {
return enchInfo.lvl
}
} else if (enchInfo.name === enchantmentDescriptor.name) {
return enchInfo.lvl
}
}
return 0
}
function getMiningFatigueMultiplier (effectLevel) {
switch (effectLevel) {
case 0: return 1.0
case 1: return 0.3
case 2: return 0.09
case 3: return 0.0027
default: return 8.1E-4
}
}
return class Block {
constructor (type, biomeId, metadata, stateId) {
this.type = type
this.metadata = metadata ?? 0
this.light = 0
this.skyLight = 0
this.biome = new Biome(biomeId)
this.position = null
this.stateId = stateId
this.computedStates = {}
if (stateId === undefined && type !== undefined) {
const b = registry.blocks[type]
// Make sure the block is actually valid and metadata is within valid bounds
this.stateId = b === undefined ? null : Math.min(b.minStateId + metadata, b.maxStateId)
}
const blockEnum = registry.blocksByStateId[this.stateId]
if (blockEnum) {
this.metadata = this.stateId - blockEnum.minStateId
this.type = blockEnum.id
this.name = blockEnum.name
this.hardness = blockEnum.hardness
this.displayName = blockEnum.displayName
this.shapes = blockEnum.shapes
if (blockEnum.stateShapes) {
if (blockEnum.stateShapes[this.metadata] !== undefined) {
this.shapes = blockEnum.stateShapes[this.metadata]
} else {
// Default to shape 0
this.shapes = blockEnum.stateShapes[0]
this.missingStateShape = true
}
} else if (blockEnum.variations) {
const variations = blockEnum.variations
for (const i in variations) {
if (variations[i].metadata === metadata) {
this.displayName = variations[i].displayName
this.shapes = variations[i].shapes
}
}
}
this.boundingBox = blockEnum.boundingBox
this.transparent = blockEnum.transparent
this.diggable = blockEnum.diggable
this.material = blockEnum.material
this.harvestTools = blockEnum.harvestTools
this.drops = blockEnum.drops
} else {
this.name = ''
this.displayName = ''
this.shapes = []
this.hardness = 0
this.boundingBox = 'empty'
this.transparent = true
this.diggable = false
}
this._properties = {}
if (version.type === 'pc') {
if (usesBlockStates) {
const blockEnum = registry.blocksByStateId[this.stateId]
if (blockEnum && blockEnum.states) {
let data = this.metadata
for (let i = blockEnum.states.length - 1; i >= 0; i--) {
const prop = blockEnum.states[i]
this._properties[prop.name] = propValue(prop, data % prop.num_values)
data = Math.floor(data / prop.num_values)
}
}
} else {
this._properties = legacyPcBlocksByIdmeta[this.type + ':' + this.metadata] || legacyPcBlocksByIdmeta[this.type + ':0']
if (!this._properties) { // If no props, try different metadata for type match only
for (let i = 0; i < 15; i++) {
this._properties = legacyPcBlocksByIdmeta[this.type + ':' + i]
if (this._properties) break
}
}
}
} else if (version.type === 'bedrock') {
const states = registry.blockStates?.[this.stateId]?.states || {}
for (const state in states) {
this._properties[state] = states[state].value
}
}
// This can be expanded to other non-sign related things
if (this.name.includes('sign')) {
mergeObject(this, blockMethods.sign)
}
}
static fromStateId (stateId, biomeId) {
// 1.13+: metadata is completely removed and only block state IDs are used
if (usesBlockStates) {
return new Block(undefined, biomeId, 0, stateId)
} else {
return new Block(stateId >> 4, biomeId, stateId & 15, stateId)
}
}
static fromProperties (typeId, properties, biomeId) {
const block = typeof typeId === 'string' ? registry.blocksByName[typeId] : registry.blocks[typeId]
if (version.type === 'pc') {
if (block.states) {
let data = 0
for (const [key, value] of Object.entries(properties)) {
data += getStateValue(block.states, key, value)
}
return new Block(undefined, biomeId, 0, block.minStateId + data)
} else {
const states = legacyPcBlocksByName[block.name]
for (const state in states) {
let broke
for (const [key, value] of Object.entries(properties)) {
const s = key + '=' + value
if (!state.includes(s)) {
broke = true
break
}
}
if (!broke) {
const [id, meta] = states[state].split(':').map(Number)
return new Block(id, biomeId, meta)
}
}
throw new Error('No matching block state found for ' + block.name + ' with properties ' + JSON.stringify(properties)) // This should not happen
}
} else if (version.type === 'bedrock') {
for (let stateId = block.minStateId; stateId <= block.maxStateId; stateId++) {
const state = registry.blockStates[stateId].states
if (Object.entries(properties).find(([prop, val]) => state[prop]?.value !== val)) continue
return new Block(undefined, biomeId, 0, stateId)
}
return block
}
}
static fromString (str, biomeId) {
if (str.startsWith('minecraft:')) str = str.substring(10)
const name = str.split('[', 1)[0]
const propertiesStr = str.slice(name.length + 1, -1).split(',')
if (version.type === 'pc') {
return Block.fromProperties(name, Object.fromEntries(propertiesStr.map(property => property.split('='))), biomeId)
} else if (version.type === 'bedrock') {
return Block.fromProperties(name, Object.fromEntries(propertiesStr.map(property => {
const [key, value] = property.split(':')
return [key.slice(1, -1), value.startsWith('"') ? value.slice(1, -1) : { true: 1, false: 0 }[value] ?? parseInt(value)]
})), biomeId)
}
}
get blockEntity () {
return this.entity ? nbt.simplify(this.entity) : undefined
}
getProperties () {
return Object.assign(this._properties, this.computedStates)
}
canHarvest (heldItemType) {
if (!this.harvestTools) { return true }; // for blocks harvestable by hand
return heldItemType && this.harvestTools && this.harvestTools[heldItemType]
}
// http://minecraft.gamepedia.com/Breaking#Calculation
// for more concrete information, look up following Minecraft methods (assuming yarn mappings):
// AbstractBlock#calcBlockBreakingDelta, PlayerEntity#getBlockBreakingSpeed, PlayerEntity#canHarvest
digTime (heldItemType, creative, inWater, notOnGround, enchantments = [], effects = {}) {
if (creative) return 0
const materialToolMultipliers = registry.materials[this.material]
const isBestTool = heldItemType && materialToolMultipliers && materialToolMultipliers[heldItemType]
// Compute breaking speed multiplier
let blockBreakingSpeed = 1
if (isBestTool) {
blockBreakingSpeed = materialToolMultipliers[heldItemType]
}
// Efficiency is applied if tools speed multiplier is more than 1.0
const efficiencyLevel = getEnchantmentLevel('efficiency', enchantments)
if (efficiencyLevel > 0 && blockBreakingSpeed > 1.0) {
blockBreakingSpeed += efficiencyLevel * efficiencyLevel + 1
}
// Haste is always considered when effect is present, and when both
// Conduit Power and Haste are present, highest level is considered
const hasteLevel = Math.max(
getEffectLevel('Haste', effects),
getEffectLevel('ConduitPower', effects))
if (hasteLevel > 0) {
blockBreakingSpeed *= 1 + (0.2 * hasteLevel)
}
// Mining fatigue is applied afterwards, but multiplier only decreases up to level 4
const miningFatigueLevel = getEffectLevel('MiningFatigue', effects)
if (miningFatigueLevel > 0) {
blockBreakingSpeed *= getMiningFatigueMultiplier(miningFatigueLevel)
}
// Apply 5x breaking speed de-buff if we are submerged in water and do not have aqua affinity
const aquaAffinityLevel = getEnchantmentLevel('aqua_affinity', enchantments)
if (inWater && aquaAffinityLevel === 0) {
blockBreakingSpeed /= 5.0
}
// We always get 5x breaking speed de-buff if we are not on the ground
if (notOnGround) {
blockBreakingSpeed /= 5.0
}
// Compute block breaking delta (breaking progress applied in a single tick)
const blockHardness = this.hardness
const matchingToolMultiplier = this.canHarvest(heldItemType) ? 30.0 : 100.0
let blockBreakingDelta = blockBreakingSpeed / blockHardness / matchingToolMultiplier
// Delta will always be zero if block has -1.0 durability
if (blockHardness === -1.0) {
blockBreakingDelta = 0.0
}
// We will never be capable of breaking block if delta is zero, so abort now and return infinity
if (blockBreakingDelta === 0.0) {
return Infinity
}
// If breaking delta is more than 1.0 per tick, the block is broken instantly, so return 0
if (blockBreakingDelta >= 1.0) {
return 0
}
// Determine how many ticks breaking will take, then convert to millis and return result
// We round ticks up because if progress is below 1.0, it will be finished next tick
const ticksToBreakBlock = Math.ceil(1.0 / blockBreakingDelta)
return ticksToBreakBlock * 50
}
}
function parseValue (value, state) {
if (state.type === 'enum') {
return state.values.indexOf(value)
}
if (state.type === 'bool') {
if (value === true) return 0
if (value === false) return 1
}
if (state.type === 'int') {
return value
}
// Assume by-name mapping for unknown properties
return state.values?.indexOf(value.toString()) ?? 0
}
function getStateValue (states, name, value) {
let offset = 1
for (let i = states.length - 1; i >= 0; i--) {
const state = states[i]
if (state.name === name) {
return offset * parseValue(value, state)
}
offset *= state.num_values
}
return 0
}
function propValue (state, value) {
if (state.type === 'enum') return state.values[value]
if (state.type === 'bool') return !value
return value
}
}
function mergeObject (to, from) {
Object.defineProperties(to, Object.getOwnPropertyDescriptors(from))
}