local p = {}
local Bit32 = require('bit32')
local Autovalue = require('Module:Autovalue')
local BlockReplaceMap = mw.loadJsonData('Module:Sandbox/BlockReplaceDataMap.json')
local PropertyList = BlockReplaceMap['properties']
local OriginalMapping = BlockReplaceMap['originalMapping']
local NewMappings = BlockReplaceMap['newMappings']
local BlockAlias = mw.loadJsonData('Module:Sandbox/BlockAlias.json')
local SimpleAlias = BlockAlias['simple']
local ComplexAlias = BlockAlias['complex']
local ComplexAliasReverted = BlockAlias['complexReverted']
local VanillaBlockUpdater = mw.loadJsonData('Module:Sandbox/VanillaBlockUpdater.json')
local NBTSprites = {
int = mw.getCurrentFrame():preprocess('{{nbt|int}}'),
number = mw.getCurrentFrame():preprocess('{{nbt|int}}'),
bool = mw.getCurrentFrame():preprocess('{{nbt|bool}}'),
boolean = mw.getCurrentFrame():preprocess('{{nbt|bool}}'),
string = mw.getCurrentFrame():preprocess('{{nbt|string}}'),
}
local Utils = {
contains = function(array, element)
for _, v in ipairs(array) do
if v == element then return true end
end
return false
end,
count = function(table)
local count = 0
for _, _ in pairs(table) do count = count + 1 end
return count
end,
stringToArray = function(str, splitter)
local rawSplit = mw.text.split(str, splitter)
local results = {}
for _,value in ipairs( rawSplit ) do
local stripped = (string.gsub(value, '^[%s\n]*(.-)[%s\n]*$', '%1')) -- StripSpaceAndLineAtBothEnds
if stripped ~= '' then
table.insert(results, stripped)
end
end
return results
end,
mapValues = function(originalValues, from, to)
local newValues = {}
for k,v in ipairs(originalValues) do
for k2, v2 in ipairs(from) do
if v == v2 then newValues[k] = to[k2] end
end
end
return newValues
end,
}
local AliasUtils = {}
AliasUtils.getOlderSimpleAliasArray = function(blockID)
local older = SimpleAlias[blockID]
if type(older)=='string' then
return {older}
elseif older then
local result={} for _,v in ipairs(older) do table.insert(result, v) end --array copy
return result
end
return nil
end
AliasUtils.getNewerSimpleAlias = function(blockID)
for newer, older in pairs(SimpleAlias) do
if older == blockID then return newer end
end
return nil
end
AliasUtils.addSimpleAliasAndGetOlder = function(map, block)
map[block] = true
local newer = AliasUtils.getNewerSimpleAlias(block)
if newer then map[newer] = true end
local older = AliasUtils.getOlderSimpleAliasArray(block)
if older and #older == 1 then
map[older[1]] = true
return older[1]
end
return block
end
AliasUtils.getAllAliases = function(blockID)
local toSplit = (ComplexAlias[blockID] and blockID) or ComplexAliasReverted[blockID]
local older = AliasUtils.getOlderSimpleAliasArray(blockID)
if not(older) and not(toSplit) then
return {blockID}, {blockID}, nil
elseif older and #older == 1 then
toSplit = toSplit or (ComplexAlias[older[1]] and older[1]) or ComplexAliasReverted[older[1]]
if not(toSplit) then return {blockID, older[1]}, older, nil end
elseif older and #older > 1 then --cauldron only
assert(not(ComplexAliasReverted[blockID])); assert(older[1] == blockID); assert(not(older[3]))
return older, older, nil
end
local oldest = {}
local results = {}
oldest = {AliasUtils.addSimpleAliasAndGetOlder(results, toSplit)}
local splitIDs = ComplexAlias[toSplit]['renameTo']
if type(splitIDs[1]) == 'string' then
for _,v in ipairs(splitIDs) do
AliasUtils.addSimpleAliasAndGetOlder(results, v)
end
else
for _,v in ipairs(splitIDs) do
for _,v1 in ipairs(v['renameTo']) do
assert(type(v1)=='string')
AliasUtils.addSimpleAliasAndGetOlder(results, v1)
end
end
end
local resultArray = {}
for k,_ in pairs(results) do table.insert(resultArray, k) end
table.sort(resultArray)
return resultArray, oldest, toSplit
end
local function countBits(propertyInfoList)
local bitCount = 0
local overlapping = false
for _, propertyInfo in ipairs(propertyInfoList) do
if propertyInfo['overlappingBits'] then
if not(overlapping) then
bitCount = bitCount + propertyInfo['overlappingBits']
end
overlapping = true
else
overlapping = false
bitCount = bitCount + propertyInfo['bits']
end
end
return bitCount
end
local function getMetadataMappingForOldestBlock(block)
local mapping = OriginalMapping[block]
local overwriteFlag =false
for _, newMapping in ipairs(NewMappings) do
local new = newMapping['new']
local overwriting = newMapping['overwriting']
local newFlag = false
if new and new[block] then
if mapping then error('Duplicate blocks "'..block..'" in BlockReplaceMap') end
mapping = new[block]
newFlag = true
end
if overwriting and overwriting[block] then
if newFlag or not(mapping) then error('Cannot overwrite "'..block..'" in BlockReplaceMap') end
mapping =overwriting[block]
overwriteFlag = true
end
end
if type(mapping) == 'string' then
local mapping2, maxValue2, overwriteFlag2 = getMetadataMappingForOldestBlock(mapping)
if overwriteFlag2 then error('Cannot redirect "'..block..'" to "'..mapping..'", because the latter is overwritten') end
if not(mapping2) then error('Cannot redirect "'..block..'" to "'..mapping..'"') end
return mapping2, maxValue2, overwriteFlag
end
if mapping then
local maxValue = mapping['max'] -- coral only
if maxValue then mapping = mapping['mapping'] end
return mapping, maxValue, overwriteFlag
end
return nil
end
local function getPropertyInfoFromMetadataMapping(mapping)
local propertyNameList = {}; local propertyInfoList = {}
for _, property in ipairs(mapping) do
if type(property) == 'string' then
table.insert(propertyNameList, property)
table.insert(propertyInfoList, PropertyList[property] or error('Missing property '..property..' in BlockReplaceMap'))
elseif property['property'] then
table.insert(propertyNameList, property['property'])
local propertyInfo = PropertyList[property['property']] or error('Missing property '..property['property']..' in BlockReplaceMap')
if Utils.contains(propertyInfo['bits'], property['bits']) then
table.insert(propertyInfoList, {type=propertyInfo['type'], bits=property['bits']})
else
error('Missing property '..property..'['..tostring(property['bits'])..' bits] in BlockReplaceMap')
end
else
assert(property['overlapping']) -- doors only
for _,v in ipairs(property['overlapping']) do
table.insert(propertyNameList, v)
local propertyInfo = PropertyList[v] or error('Missing property '..property..' in BlockReplaceMap')
table.insert(propertyInfoList, {overlappingBits = true, type=propertyInfo['type'], values=propertyInfo['values'], bits=propertyInfo['bits'], max=propertyInfo['max']})
end
end
end
--copy and normalize propertyInfo to {type=*,bits=*,values=array[*],overlapping=*}
local overlappingBits
for k, info in ipairs(propertyInfoList) do
local normalizedInfo = {}
local propertyType = info['type']
normalizedInfo['type'] = propertyType
if propertyType == 'bool' then
normalizedInfo['bits'] = 1
normalizedInfo['values'] = {false, true}
elseif propertyType == 'int' then
normalizedInfo['bits'] = info['bits']
local max = info['max'] or ( math.ldexp( 1, info['bits'] ) - 1 ) --(2^n - 1)
normalizedInfo['values'] = {0, 1}
for i=2, max do table.insert(normalizedInfo['values'], i) end
else
normalizedInfo['values'] = info['values']
normalizedInfo['bits'] = math.floor(math.log( Utils.count(info['values']) ) / math.log(2) + 0.1)
end
if info['overlappingBits'] then
overlappingBits = overlappingBits or normalizedInfo['bits']
normalizedInfo['overlappingBits'] = overlappingBits
end
propertyInfoList[k] = normalizedInfo
end
return propertyNameList, propertyInfoList
end
local function genSimpleTable(propertyNameList, propertyInfoList)
local result = {}
local allRows = 0
local bitMap = {'0x1','0x2','0x4','0x8','0x10','0x20','0x40','0x80','0x100','0x200','0x400'}
local currentBit = 1
for k, propertyName in ipairs(propertyNameList) do
if propertyInfoList[k]['bits'] == 0 then
table.insert(result, '| — || '..NBTSprites[PropertyInfoList[k]['type']]..propertyName..' || '..propertyInfoList[k]['value'])
else
local bitString = {}
for i=currentBit, (currentBit + propertyInfoList[k]['bits']-1) do
table.insert(bitString,bitMap[i])
end
if not(propertyInfoList[k]['overlappingBits']) then
currentBit = currentBit + propertyInfoList[k]['bits']
elseif (propertyInfoList[k+1] and not(propertyInfoList[k+1]['overlappingBits'])) then
currentBit = currentBit + propertyInfoList[k]['overlappingBits']
end
if propertyName == '(Unused)' then
assert(not(propertyInfoList[k]['overlappingBits']))
allRows = allRows + 1
table.insert(result, '| ' .. table.concat(bitString,'<br>'))
table.insert(result, '| colspan = 3 | (未使用)')
table.insert(result, '|-')
else
local rowspan = tostring(math.max(Utils.count(propertyInfoList[k]['values']),1))
allRows = allRows + rowspan
table.insert(result, '| rowspan = '.. rowspan .. ' | ' .. table.concat(bitString,'<br>'))
for index, value in ipairs(propertyInfoList[k]['values']) do
table.insert(result, '| '..tostring(index-1))
if index == 1 then
table.insert(result, '| rowspan = '.. rowspan .. ' | ' .. NBTSprites[propertyInfoList[k]['type']]..propertyName)
end
table.insert(result, '| '..tostring(value))
table.insert(result, '|-')
end
end
end
end
return table.concat(result,'\n'), allRows
end
local function genComplexTable(states)
local result = {}
local allRows = 0
for i=0, #states do
local state = states[i]
local name = AliasUtils.getNewerSimpleAlias(state['name']) or state['name']
local propertyCount = Utils.count(state['states'])
local rowspan = tostring(math.max(propertyCount, 1))
allRows = allRows + rowspan
table.insert(result,'| rowspan = '..rowspan..' | '..tostring(i))
table.insert(result,'| rowspan = '..rowspan..' | '..name)
if propertyCount == 0 then
table.insert(result, '| — || —')
table.insert(result, '|-')
else
for k,v in pairs(state['states']) do
if k ~= '(Unused)' then
table.insert(result, '| '..NBTSprites[type(v)]..k..' || '..tostring(v))
table.insert(result, '|-')
end
end
end
end
return table.concat(result, '\n'), allRows
end
local function applySimpleMappingEntry(mappingEntry, block, propertyNameList, propertyInfoList)
if mappingEntry['removeProperties'] then
for propertyToRemove, infoToRemove in pairs(mappingEntry['removeProperties']) do
for k, propertyName in ipairs(propertyNameList) do
if propertyName == propertyToRemove then
assert(propertyInfoList[k]['type'] == infoToRemove['type'])
assert(propertyInfoList[k]['overlappingBits'] == nil)
propertyNameList[k] = '(Unused)'
end
end
end
end
if mappingEntry['addProperties'] then
for propertyToAdd, infoToAdd in pairs(mappingEntry['addProperties']) do
if not(Utils.contains(propertyNameList, propertyToAdd)) then
table.insert(propertyNameList, propertyToAdd)
table.insert(propertyInfoList, {type=infoToAdd['type'],bits=0,value=infoToAdd['value']})
end
end
end
if mappingEntry['renameTo'] then
block = mappingEntry['renameTo']
end
if mappingEntry['mapValues'] then
local property = mappingEntry['mapValues']['property']
local propertyType = mappingEntry['mapValues']['type']
local old = mappingEntry['mapValues']['old']
local new = mappingEntry['mapValues']['new']
for index, propertyName in ipairs(propertyNameList) do
if propertyName == property then
assert(propertyInfoList[index]['type'] == propertyType)
if old then
propertyInfoList[index]['values'] = Utils.mapValues(propertyInfoList[index]['values'], old, new)
else
assert(type(new)~='table')
if propertyInfoList[index]['value'] then
propertyInfoList[index]['value'] = new
else
for k,_ in propertyInfoList[index]['values'] do
propertyInfoList[index]['values'][k] = new
end
end
end
end
end
end
if mappingEntry['mapProperty'] then
local old = mappingEntry['mapProperty']['old']
local new = mappingEntry['mapProperty']['new']
for index, propertyName in ipairs(propertyNameList) do
if propertyName == old['property'] then
assert(propertyInfoList[index]['type'] == old['type'])
if old['values'] then
propertyInfoList[index]['values'] = Utils.mapValues(propertyInfoList[index]['values'], old['values'], new['values'])
end
propertyNameList[index] = new['property']
propertyInfoList[index]['type'] = new['type']
end
end
end
return block
end
local function applyMappingEntryOnState(mappingEntry, state)
if mappingEntry['stateFilter'] then
for property, info in pairs(mappingEntry['stateFilter']) do
assert(state['states'][property] ~= nil)
if info['value'] then
if state['states'][property] ~= info['value'] then return end
elseif not(Utils.contains(info['values'], state['states'][property])) then
return
end
end
end
if mappingEntry['complexAlias'] then
local complexAliasInfo = ComplexAlias[state['name']]
or ComplexAlias[AliasUtils.getNewerSimpleAlias(state['name'])]
or ComplexAlias[AliasUtils.getOlderSimpleAliasArray(state['name'])[1]]
local value = state['states'][complexAliasInfo['property']]
assert(value ~= nil)
state['states'][complexAliasInfo['property']] = nil
for k, v in ipairs(complexAliasInfo['values']) do
if v == value then
state['name'] = complexAliasInfo['renameTo'][k]
end
end
if type(state['name']) == 'table' then
local value = state['states'][state['name']['property']]
state['states'][state['name']['property']] = nil
for k, v in ipairs(state['name']['values']) do
if v == value then state['name'] = state['name']['renameTo'][k] end
end
end
assert(type(state['name']) == 'string')
end
if mappingEntry['removeProperties'] then
for propertyToRemove, infoToRemove in pairs(mappingEntry['removeProperties']) do
assert(state['states'][propertyToRemove] == nil or (type(state['states'][propertyToRemove]) == (infoToRemove['type']=='bool' and 'boolean' or (infoToRemove['type']=='int' and 'number' or infoToRemove['type']))))
state['states'][propertyToRemove] = nil
end
end
if mappingEntry['addProperties'] then
for propertyToAdd, infoToAdd in pairs(mappingEntry['addProperties']) do
if(state['states'][propertyToAdd] == nil) then
state['states'][propertyToAdd] = infoToAdd['value']
end
end
end
if mappingEntry['renameTo'] then
state['name'] = mappingEntry['renameTo']
end
if mappingEntry['mapValues'] then
local property = mappingEntry['mapValues']['property']
local propertyType = mappingEntry['mapValues']['type']
local old = mappingEntry['mapValues']['old']
local new = mappingEntry['mapValues']['new']
if not(old) then
state['states'][property] = new
else
local original = state['states'][property]
for k, value in ipairs(old) do
if value == original then
state['states'][property] = new[k]
end
end
end
end
if mappingEntry['mapProperty'] then
local old = mappingEntry['mapProperty']['old']
local new = mappingEntry['mapProperty']['new']
-- assert (state['states'][old['property']] ~= nil) -- wood[pillar_axis]
if old['values'] then
local newValue
for k, value in ipairs(old['values']) do
if value == state['states'][old['property']] then
newValue = new['values'][k]
end
end
state['states'][new['property']] = newValue
else
state['states'][new['property']] = state['states'][old['property']]
end
if(old['property'] ~= new['property']) then
state['states'][old['property']] = nil
end
end
end
local function getStateFromMetadata(metadata, block, propertyNameList, propertyInfoList)
local state = {}
local metadataTemp = metadata
for k, v in ipairs(propertyNameList) do
local bits = propertyInfoList[k]['bits']
state[v] = propertyInfoList[k]['values'][Bit32.band(metadataTemp, math.ldexp( 1, bits ) - 1) + 1]
if not(propertyInfoList[k]['overlappingBits']) then
metadataTemp = Bit32.rshift(metadataTemp, bits)
elseif not(propertyInfoList[k+1]['overlappingBits']) then
metadataTemp = Bit32.rshift(metadataTemp, propertyInfoList[k]['overlappingBits'])
end
end
assert(metadataTemp == 0)
return {name = block, states = state}
end
local function allMetadataToStates(maxValue, block, propertyNameList, propertyInfoList)
local result = {}
for i=0, maxValue do
result[i] = getStateFromMetadata(i, block, propertyNameList, propertyInfoList)
end
return result
end
local function getMappingEntry(updaterMapping, block)
local alias = {}; AliasUtils.addSimpleAliasAndGetOlder(alias,block)
local mappingEntry
for k, _ in pairs(alias) do
mappingEntry = mappingEntry or updaterMapping[k]
end
if type(mappingEntry) == 'string' then
mappingEntry = updaterMapping[mappingEntry]
assert(type(mappingEntry) ~= 'string')
end
if updaterMapping['#default'] then
local newMappingEntry = {}
if mappingEntry and mappingEntry[1] then
for _, v in ipairs(mappingEntry) do table.insert(newMappingEntry, v) end
else
newMappingEntry[1] = mappingEntry
end
if updaterMapping['#default'][1] then
for _, v in ipairs(updaterMapping['#default']) do table.insert(newMappingEntry, v) end
else
table.insert(newMappingEntry, updaterMapping['#default'])
end
mappingEntry = newMappingEntry
end
return mappingEntry
end
local function tryGetSimpleTable(block, propertyNameList, propertyInfoList)
local filtered = false
for _, updaterMapping in ipairs(VanillaBlockUpdater) do
if not(filtered) then
local mappingEntry = getMappingEntry(updaterMapping, block)
if mappingEntry then
if mappingEntry[1] then
local originID = block
for _,v in ipairs(mappingEntry) do
if not(filtered) and not(v['removedIn']) and (originID == block) then
if v['stateFilter'] or mappingEntry['complexAlias'] then
filtered = true
else
block = applySimpleMappingEntry(v, block, propertyNameList, propertyInfoList)
end
end
end
else
if not(mappingEntry['removedIn']) then
if mappingEntry['stateFilter'] or mappingEntry['complexAlias'] then
filtered = true
else
block = applySimpleMappingEntry(mappingEntry, block, propertyNameList, propertyInfoList)
end
end
end
end
end
end
if not(filtered) then
local body, rows = genSimpleTable(propertyNameList, propertyInfoList)
return body, rows, false
else
return nil, nil, true
end
end
local function getComplexTable(oldestAliases, propertyNameList, propertyInfoList, maxValue)
local states = allMetadataToStates(maxValue, oldestAliases, propertyNameList, propertyInfoList)
for i=0, maxValue do
for _, updaterMapping in pairs(VanillaBlockUpdater) do
local mappingEntry = getMappingEntry(updaterMapping, states[i]['name'])
if mappingEntry then
if mappingEntry[1] then
local originID = states[i]['name']
for _,v in ipairs(mappingEntry) do
if not(v['removedIn']) and (originID == states[i]['name']) then
applyMappingEntryOnState(v,states[i])
end
end
else
if not(mappingEntry['removedIn']) then
applyMappingEntryOnState(mappingEntry,states[i])
end
end
end
end
end
return genComplexTable(states)
end
local function getMetadataTable(oldestAliases, shouldSplit)
if oldestAliases[2] then --cauldron only
local lastBody, body, rowspan, isComplex
for _, v in ipairs(oldestAliases) do
body, rowspan, isComplex = getMetadataTable({v},nil)
assert(not(lastBody) or lastBody == body)
lastBody = body
end
return body, rowspan, isComplex
else
local mapping, maxValue = getMetadataMappingForOldestBlock(oldestAliases[1])
if mapping == nil then
return nil
end
local propertyNameList, propertyInfoList = getPropertyInfoFromMetadataMapping(mapping)
local body, rowspan
local filtered = false
if not(shouldSplit) then
body, rowspan, filtered = tryGetSimpleTable(oldestAliases[1], propertyNameList, propertyInfoList)
end
if shouldSplit or filtered then
shouldSplit = true
if not(maxValue) then
if #propertyInfoList == 1 then
maxValue = Utils.count(propertyInfoList[1]['values']) - 1
else
maxValue = math.ldexp( 1, countBits(propertyInfoList) ) - 1
end
end
body, rowspan = getComplexTable(oldestAliases[1], propertyNameList, propertyInfoList, maxValue)
end
return body, rowspan, shouldSplit
end
end
local function getMetadataTableForBlocks(idArray)
local dedupByToSplit = {}
local complexBodys = {}; local simpleBodys = {}; local simpleBodyMap = {}; local notSupport = {}
for _, id in pairs(idArray) do
local noDup = true
local allAliases, oldestAliases, toSplit = AliasUtils.getAllAliases(id)
if toSplit then
if dedupByToSplit[toSplit] == true then noDup = false
else dedupByToSplit[toSplit] = true end
end
if noDup then
local body, rowspan, isComplex = getMetadataTable(oldestAliases, (toSplit ~= nil))
if body == nil then
for _, alias in ipairs(allAliases) do table.insert(notSupport, alias) end
elseif isComplex then
table.insert(complexBodys, '| rowspan = '..tostring(rowspan)..' | <code>'..table.concat(allAliases,'</code><br><code>')..'</code>')
table.insert(complexBodys, body)
else --合并内容相同的简单表
if (simpleBodyMap[body]) then
for _, alias in ipairs(allAliases) do table.insert(simpleBodyMap[body]['aliases'],alias) end
else
simpleBodyMap[body] = {aliases=allAliases, rowspan=rowspan}
end
end
end
end
for body, info in pairs(simpleBodyMap) do
table.insert(simpleBodys, '| rowspan = '..tostring(info['rowspan'])..' | <code>'..table.concat(info['aliases'],'</code><br><code>')..'</code>')
table.insert(simpleBodys, body)
end
local result = {}
if complexBodys[1] then
table.insert(result, '{| class = "wikitable collapsible"\n! rowspan=2 | 指定的方块ID !! rowspan=2 | 指定的数据值 !! rowspan=2 | 实际方块ID !! colspan = 2 | 实际方块状态\n|-')
table.insert(result, '! 方块属性 !! 值\n|-')
table.insert(result, table.concat(complexBodys,'\n'))
table.insert(result, '|}')
end
if simpleBodys[1] or notSupport[1] then
table.insert(result, '{| class = "wikitable collapsible"\n! rowspan=2 | 方块ID !! colspan = 2 | 指定的数据值 !! colspan = 2 | 对应的方块属性\n|-')
table.insert(result, '! 二进制位 !! 值 !! 方块属性 !! 值\n|-')
table.insert(result, table.concat(simpleBodys,'\n'))
table.insert(result, '| <code>'..table.concat(notSupport,'</code><br><code>')..'</code>')
table.insert(result, '| colspan=4 | 此方块不支持数据值\n|-')
table.insert(result, '|}')
end
return table.concat(result, '\n')
end
function p.blockMetadataTable (f)
local args = f
local frame = mw.getCurrentFrame()
if f == frame then
args = require('Module:ProcessArgs').merge(true)
end
local targetNames = Utils.stringToArray(mw.text.trim(args[2] or args[1] or mw.text.trim(mw.title.getCurrentTitle().rootText)), ',')
targetNames = Autovalue.expandGroups(targetNames, 'block', 'be')
local targetIDs = {}
for _, name in ipairs(targetNames) do
table.insert(targetIDs, Autovalue.getRawValue(name, 'block id', 'be'))
end
return getMetadataTableForBlocks(targetIDs)
end
function p.allBlockMetadataTable()
return getMetadataTableForBlocks(mw.loadData('Module:Block id values BE'))
end
return p