Skip to content

Commit

Permalink
Add core code and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Karang committed Jan 18, 2021
1 parent 1cf5c29 commit 310fb08
Show file tree
Hide file tree
Showing 12 changed files with 594 additions and 46 deletions.
24 changes: 5 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,9 @@
# prismarine-template
[![NPM version](https://img.shields.io/npm/v/prismarine-template.svg)](http://npmjs.com/package/prismarine-template)
[![Build Status](https://github.com/PrismarineJS/prismarine-template/workflows/CI/badge.svg)](https://github.com/PrismarineJS/prismarine-template/actions?query=workflow%3A%22CI%22)
# prismarine-rng
[![NPM version](https://img.shields.io/npm/v/prismarine-rng.svg)](http://npmjs.com/package/prismarine-rng)
[![Build Status](https://github.com/PrismarineJS/prismarine-rng/workflows/CI/badge.svg)](https://github.com/PrismarineJS/prismarine-rng/actions?query=workflow%3A%22CI%22)
[![Discord](https://img.shields.io/badge/chat-on%20discord-brightgreen.svg)](https://discord.gg/GsEFRM8)
[![Gitter](https://img.shields.io/badge/chat-on%20gitter-brightgreen.svg)](https://gitter.im/PrismarineJS/general)
[![Irc](https://img.shields.io/badge/chat-on%20irc-brightgreen.svg)](https://irc.gitter.im/)
[![Try it on gitpod](https://img.shields.io/badge/try-on%20gitpod-brightgreen.svg)](https://gitpod.io/#https://github.com/PrismarineJS/prismarine-template)
[![Try it on gitpod](https://img.shields.io/badge/try-on%20gitpod-brightgreen.svg)](https://gitpod.io/#https://github.com/PrismarineJS/prismarine-rng)

A template repository to make it easy to create new prismarine repo

## Usage

```js
const template = require('prismarine-template')

template.helloWorld()
```

## API

### helloWorld()

Prints hello world
Collection of utilities to work with minecraft RNG.
3 changes: 0 additions & 3 deletions example.js

This file was deleted.

13 changes: 5 additions & 8 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
if (typeof process !== 'undefined' && parseInt(process.versions.node.split('.')[0]) < 14) {
console.error('Your node version is currently', process.versions.node)
console.error('Please update it to a version >= 14.x.x from https://nodejs.org/')
process.exit(1)
}

module.exports.helloWorld = function () {
console.log('Hello world !')
module.exports = {
LCG: require('./lib/LCG'),
Random: require('./lib/Random'),
crackPlayerSeed: require('./lib/PlayerSeedCracker'),
enchantments: require('./lib/enchantments')
}
45 changes: 45 additions & 0 deletions lib/LCG.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
class LCG {
static JAVA = new LCG(0x5DEECE66Dn, 0xBn, 1n << 48n)

constructor (multiplier, addend, modulus) {
this.multiplier = multiplier
this.addend = addend
this.modulus = modulus
if ((this.modulus & -this.modulus) !== this.modulus) throw new Error('Modulus is not a power of 2')
this.mod = modulus - 1n
}

nextSeed (seed) {
return (seed * this.multiplier + this.addend) & this.mod
}

combine (steps) {
let multiplier = 1n
let addend = 0n

let intermediateMultiplier = this.multiplier
let intermediateAddend = this.addend

const longSteps = BigInt(steps) & this.mod
for (let k = longSteps; k !== 0n; k >>= 1n) {
if ((k & 1n) !== 0n) {
multiplier = BigInt.asIntN(64, multiplier * intermediateMultiplier)
addend = BigInt.asIntN(64, intermediateMultiplier * addend + intermediateAddend)
}

intermediateAddend = BigInt.asIntN(64, (intermediateMultiplier + 1n) * intermediateAddend)
intermediateMultiplier = BigInt.asIntN(64, intermediateMultiplier * intermediateMultiplier)
}

multiplier = multiplier & this.mod
addend = addend & this.mod

return new LCG(multiplier, addend, this.modulus)
}

invert () {
return this.combine(-1)
}
}

module.exports = LCG
157 changes: 157 additions & 0 deletions lib/PlayerSeedCracker.js

Large diffs are not rendered by default.

49 changes: 49 additions & 0 deletions lib/Random.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
const LCG = require('./LCG')

class Rand {
constructor (seed, lcg = LCG.JAVA) {
this.seed = seed ^ LCG.JAVA.multiplier
this.lcg = lcg
}

next (bits) {
this.seed = this.lcg.nextSeed(this.seed)
return this.seed >> (48n - BigInt(bits))
}

advance (lcg) {
this.seed = lcg.nextSeed(this.seed)
}

skip (skip) {
this.advance(this.lcg.combine(skip))
}

nextInt (bound) {
if (!bound) return Number(BigInt.asIntN(32, this.next(32)))

if (bound <= 0) {
throw new Error('bound must be positive')
}

bound = Math.floor(bound)

if ((bound & -bound) === bound) {
return Number(BigInt.asIntN(32, (BigInt(bound) * this.next(31)) >> 31n))
}

let bits, value
do {
bits = Number(BigInt.asIntN(32, this.next(31)))
value = bits % bound
} while (bits - value + (bound - 1) < 0)

return value
}

nextFloat () {
return Number(BigInt.asIntN(32, this.next(24))) / (1 << 24)
}
}

module.exports = Rand
173 changes: 173 additions & 0 deletions lib/enchantments.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
const LCG = require('./LCG')
const Random = require('./Random')

module.exports = (mcData) => {
const tiers = [
{ name: 'wood', level: 0, uses: 59, speed: 2, damage: 0, enchantmentValue: 15 },
{ name: 'stone', level: 1, uses: 131, speed: 4, damage: 1, enchantmentValue: 5 },
{ name: 'iron', level: 2, uses: 250, speed: 6, damage: 2, enchantmentValue: 14 },
{ name: 'diamond', level: 3, uses: 1561, speed: 8, damage: 3, enchantmentValue: 10 },
{ name: 'gold', level: 0, uses: 32, speed: 12, damage: 0, enchantmentValue: 22 },
{ name: 'netherite', level: 4, uses: 2031, speed: 9, damage: 4, enchantmentValue: 15 }
]

function getTier (item) {
return tiers.find(tier => item.name.includes(tier.name))
}

function selectEnchantment (rand, item, level, bypassTreasureOnly) {
const tier = getTier(item)
const enchantmentValue = tier ? tier.enchantmentValue : 0
if (enchantmentValue <= 0) {
return []
}

const list = []

level += 1 + rand.nextInt(enchantmentValue / 4 + 1) + rand.nextInt(enchantmentValue / 4 + 1)
const var7 = (rand.nextFloat() + rand.nextFloat() - 1.0) * 0.15
level = Math.min(Math.max(Math.round(level + level * var7), 1), 2147483647)

let availableEnchants = getAvailableEnchantmentResults(level, item, bypassTreasureOnly)

if (availableEnchants.length > 0) {
list.push(getRandomEnchant(rand, availableEnchants))
while (rand.nextInt(50) <= level) {
availableEnchants = filterCompatibleEnchantments(availableEnchants, list[list.length - 1])
if (availableEnchants.length === 0) break
list.push(getRandomEnchant(rand, availableEnchants))
level /= 2
}
}
return list
}

function getRandomEnchant (rand, enchants) {
const totalWeight = enchants.reduce((acc, x) => acc + x.enchant.weight, 0)
let val = rand.nextInt(totalWeight)
for (const e of enchants) {
val -= e.enchant.weight
if (val < 0) return e
}
return null
}

function filterCompatibleEnchantments (enchants, last) {
return enchants.filter(e => e.enchant !== last.enchant && last.enchant.exclude.indexOf(e.enchant.name) === -1)
}

function canEnchant (enchant, item) {
// TODO: test category
return enchant.category === 'weapon' || enchant.category === 'breakable'
}

function getAvailableEnchantmentResults (cost, item, bypassTreasureOnly) {
const list = []
for (const enchant of mcData.enchantmentsArray) {
if (enchant.treasureOnly && !bypassTreasureOnly) continue
if (!enchant.discoverable) continue
if (!canEnchant(enchant, item) && item.id !== mcData.itemsByName.book.id) continue

for (let level = enchant.maxLevel; level > 0; level--) {
const minCost = enchant.minCost.a * level + enchant.minCost.b
const maxCost = enchant.maxCost.a * level + enchant.maxCost.b
if (cost >= minCost && cost <= maxCost) {
list.push({ enchant, level })
break
}
}
}

return list
}

function isAir (world, pos) {
const b = world.getBlock(pos)
return !b || b.type === mcData.blocksByName.air.id // TODO cave air, etc..
}

function enchantPower (world, pos) {
const b = world.getBlock(pos)
return b && b.type === mcData.blocksByName.bookshelf.id ? 1 : 0
}

function findEnchantment (playerSeed, item, power, matching) {
let nThrows = 0
const playerRand = new Random(playerSeed ^ LCG.JAVA.multiplier)
for (let i = 0; i < 1000000; i++) {
const xpseed = playerRand.nextInt()
const xprand = new Random(BigInt(xpseed))
for (let slot = 0; slot < 3; slot++) {
const cost = getEnchantmentCost(xprand, slot, power, item)
const list = getEnchantmentList(xpseed, item, slot, cost)
if (matching(list)) {
return {
found: list,
slot,
xpseed,
nThrows
}
}
}
// Throw an item
playerRand.nextFloat()
playerRand.nextFloat()
playerRand.nextFloat()
nThrows++
}
return null
}

function getEnchantmentList (xpseed, item, slot, level) {
const rand = new Random(BigInt(xpseed) + BigInt(slot))
const list = selectEnchantment(rand, item, level, false)

if (item.id === mcData.itemsByName.book.id && list.length > 0) {
list.splice(rand.nextInt(list.length), 1)
}

return list
}

function getEnchantmentCost (rand, slot, power, item) {
const tier = getTier(item)
let enchantmentValue = tier ? tier.enchantmentValue : 0
if (enchantmentValue <= 0) {
return []
}

if (enchantmentValue > 15) enchantmentValue = 15

const var6 = rand.nextInt(8) + 1 + (power >> 1) + rand.nextInt(power + 1)
if (slot === 0) {
return Math.max(Math.floor(var6 / 3), 1)
}
return slot === 1 ? Math.floor(var6 * 2 / 3 + 1) : Math.max(var6, power * 2)
}

function getEnchantmentPower (pos, world) {
let power = 0
for (let dz = -1; dz <= 1; dz++) {
for (let dx = -1; dx <= 1; dx++) {
if ((dz !== 0 || dx !== 0) && isAir(world, pos.offset(dx, 0, dz)) && isAir(world, pos.offset(dx, 1, dz))) {
power += enchantPower(world, pos.offset(2 * dx, 0, 2 * dz))
power += enchantPower(world, pos.offset(2 * dx, 1, 2 * dz))
if (dx !== 0 && dz !== 0) {
power += enchantPower(world, pos.offset(2 * dx, 0, dz))
power += enchantPower(world, pos.offset(2 * dx, 1, dz))
power += enchantPower(world, pos.offset(dx, 0, 2 * dz))
power += enchantPower(world, pos.offset(dx, 1, 2 * dz))
}
}
}
}
return power
}

return {
findEnchantment,
getEnchantmentList,
getEnchantmentCost,
getEnchantmentPower
}
}
27 changes: 18 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "prismarine-template",
"name": "prismarine-rng",
"version": "1.0.0",
"description": "A template repository to make it easy to create new prismarine repo",
"description": "Collection of utilities to work with minecraft RNG.",
"main": "index.js",
"scripts": {
"test": "jest --verbose",
Expand All @@ -11,21 +11,30 @@
},
"repository": {
"type": "git",
"url": "git+https://github.com/PrismarineJS/prismarine-template.git"
"url": "git+https://github.com/PrismarineJS/prismarine-rng.git"
},
"keywords": [
"prismarine",
"template"
"rng",
"seed",
"java",
"random"
],
"author": "Romain Beaumont",
"author": "Karang",
"license": "MIT",
"bugs": {
"url": "https://github.com/PrismarineJS/prismarine-template/issues"
"url": "https://github.com/PrismarineJS/prismarine-rng/issues"
},
"homepage": "https://github.com/PrismarineJS/prismarine-template#readme",
"standard": {
"parser": "babel-eslint"
},
"homepage": "https://github.com/PrismarineJS/prismarine-rng#readme",
"devDependencies": {
"minecraft-data": "^2.73.1",
"prismarine-item": "^1.5.0",
"babel-eslint": "^10.1.0",
"jest": "^26.1.0",
"prismarine-template": "file:.",
"standard": "^16.0.1"
"prismarine-rng": "file:.",
"standard": "^16.0.3"
}
}
7 changes: 0 additions & 7 deletions test/basic.test.js

This file was deleted.

Loading

0 comments on commit 310fb08

Please sign in to comment.