use dejson; use encoding::json; use strings; use trace; type BstateRender = struct { parts: []BstateRenderPart, }; type BstateRenderPart = struct { model: *Model, flags: BstateRenderPartFlags, swizzle: u8, // X,Y,Z = 0,1,2; axis_ = swizzle >> 2 * axis & 3 // bits 0-5: west,east,down,up,north,south swap_uv: u8, flip_u: u8, flip_v: u8, }; type BstateRenderPartFlags = enum u8 { FLIP_X = 0x1, FLIP_Y = 0x2, FLIP_Z = 0x4, }; let BSTATES_RENDER: []BstateRender = []; fn load_render_bstates(assets: *Pack) void = { const tr = &trace::root; trace::info(tr, "loading blockstate decls..."); for (let blk: BlockId = 0; blk < blocks_count(); blk += 1) { const bstates = BLOCKS_BSTATES[blk]; const ident = block_getident(blk); const tr = trace::ctx(tr, "load blockstate information for {}", ident); const decl = match (resource_load_json( assets, "blockstates", ident, ".json", &tr)) { case let json: json::value => defer json::finish(json); const deser = dejson::newdeser(&json); yield match (deser_bstate_decl(&deser)) { case let x: BstateDecl => yield x; case let err: dejson::error => trace::error(&tr, "deser: {}", err): void; yield bstate_decl_missingno(); }; case trace::failed => yield bstate_decl_missingno(); }; defer bstate_decl_finish(decl); for (let i = 0z; i < len(decl.cases); i += 1) { const case_ = &decl.cases[i]; if (!bstate_decl_cond_validate(&case_.0, blk, &tr)) { bstate_decl_cond_finish(case_.0); bstate_decl_variant_finish(case_.1); delete(decl.cases[i]); i -= 1; }; }; for (let i = 0u32; i < bstates.nstates; i += 1) { append(BSTATES_RENDER, load_bstate_render( &decl, bstates.state0 + i, &tr)); }; }; }; fn load_bstate_render( decl: *BstateDecl, bstate: BstateId, tr: *trace::tracer, ) BstateRender = { let parts: []BstateRenderPart = []; for (let i = 0z; i < len(decl.cases); i += 1) { const case_ = &decl.cases[i]; const variant = &case_.1; if (!bstate_decl_cond_test(&case_.0, bstate)) continue; if (!decl.multipart && len(parts) != 0) { // TODO: print blockstate properties. trace::error(tr, "Multiple variants match blockstate {}", bstate: u32): void; parts[0] = missingno_bstate_render_part(); break; }; const model = match (models_find(variant.model)) { case let model: *Model => yield model; case null => trace::error(tr, "Unknown model {}", variant.model): void; yield models_find(BUILTIN_MISSING) as *Model; }; let swizzle: [3]u8 = [0, 1, 2]; // X, Y, Z let flip: [3]bool = [false...]; load_bstate_render_rotate( variant.x, 2, 1, &swizzle, &flip); load_bstate_render_rotate( variant.y, 0, 2, &swizzle, &flip); let flags: BstateRenderPartFlags = 0; if (flip[0]) flags |= BstateRenderPartFlags::FLIP_X; if (flip[1]) flags |= BstateRenderPartFlags::FLIP_Y; if (flip[2]) flags |= BstateRenderPartFlags::FLIP_Z; let flip_u = 0u8; let flip_v = 0u8; let swap_uv = 0u8; if (variant.uvlock) { // Mojang's uvlock implementation is an incomprehensible // clusterfuck, and I'm not convinced this is any better // in that regard. :/ // this is also incompatible when rotated or flipped // textures are used, but those cases never appear in // vanilla resources, and honestly just feel like bugs // on Mojang's part. let face_rots: [6]u8 = [0...]; face_rots[Dir::WEST] += variant.x; face_rots[Dir::EAST] -= variant.x; switch (variant.x) { case 1 => face_rots[Dir::UP] += 2; face_rots[Dir::NORTH] += 2; case 2 => face_rots[Dir::SOUTH] += 2; face_rots[Dir::NORTH] += 2; case 3 => face_rots[Dir::DOWN] += 2; face_rots[Dir::NORTH] += 2; case => void; }; static const round: [4]Dir = [Dir::DOWN, Dir::NORTH, Dir::UP, Dir::SOUTH]; const down = round[variant.x]; const up = round[2 + variant.x & 3]; face_rots[down] += variant.y; face_rots[up] -= variant.y; for (let j = 0u8; j < 6; j += 1) { const mask = 1 << j; switch (face_rots[j] & 3) { case 1 => swap_uv |= mask; flip_v |= mask; case 2 => flip_u |= mask; flip_v |= mask; case 3 => swap_uv |= mask; flip_u |= mask; case => void; }; }; }; append(parts, BstateRenderPart { model = model, flags = flags, swizzle = swizzle[0] | swizzle[1] << 2 | swizzle[2] << 4, flip_u = flip_u, flip_v = flip_v, swap_uv = swap_uv, }); }; if (!decl.multipart && len(parts) == 0) { // TODO: print blockstate properties. trace::error(tr, "No variants match blockstate {}", bstate: u32): void; append(parts, missingno_bstate_render_part()); }; return BstateRender { parts = parts, }; }; fn load_bstate_render_rotate( angle: u8, a: u8, b: u8, swizzle: *[3]u8, flip: *[3]bool, ) void = { if (angle & 1 == 1) { const tmp = swizzle[a]; swizzle[a] = swizzle[b]; swizzle[b] = tmp; const tmp = flip[a]; flip[a] = flip[b]; flip[b] = tmp; }; flip[a] ^^= (angle == 1 || angle == 2); flip[b] ^^= (angle == 3 || angle == 2); }; fn missingno_bstate_render_part() BstateRenderPart = BstateRenderPart { model = models_find(BUILTIN_MISSING) as *Model, flags = 0, swizzle = 0b100100, flip_u = 0, flip_v = 0, swap_uv = 0, }; type BstateDecl = struct { multipart: bool, cases: [](BstateDeclCond, BstateDeclVariant), }; type BstateDeclCond = ((str, str) | BstateDeclAnd | BstateDeclOr); type BstateDeclAnd = []BstateDeclCond; type BstateDeclOr = []BstateDeclCond; type BstateDeclVariant = struct { model: str, uvlock: bool, // rotations in steps of 90 degrees x: u8, y: u8, }; fn bstate_decl_missingno() BstateDecl = BstateDecl { multipart = false, cases = alloc([([]: BstateDeclAnd, BstateDeclVariant { model = strings::dup(BUILTIN_MISSING), ... })]...), }; fn bstate_decl_finish(decl: BstateDecl) void = { for (let i = 0z; i < len(decl.cases); i += 1) { bstate_decl_cond_finish(decl.cases[i].0); bstate_decl_variant_finish(decl.cases[i].1); }; free(decl.cases); }; fn bstate_decl_cond_finish(cond: BstateDeclCond) void = { const operands = match (cond) { case let ops: BstateDeclAnd => yield ops: []BstateDeclCond; case let ops: BstateDeclOr => yield ops: []BstateDeclCond; case let kv: (str, str) => free(kv.0); free(kv.1); return; }; for (let i = 0z; i < len(operands); i += 1) { bstate_decl_cond_finish(operands[i]); }; free(operands); }; fn bstate_decl_variant_finish(variant: BstateDeclVariant) void = { free(variant.model); }; fn bstate_decl_cond_validate( cond: *BstateDeclCond, blk: BlockId, tr: *trace::tracer, ) bool = { let ok = true; match (*cond) { case let conds: BstateDeclAnd => for (let i = 0z; i < len(conds); i += 1) { if (!bstate_decl_cond_validate(&conds[i], blk, tr)) { ok = false; }; }; case let conds: BstateDeclOr => for (let i = 0z; i < len(conds); i += 1) { if (!bstate_decl_cond_validate(&conds[i], blk, tr)) { ok = false; }; }; case let kv: (str, str) => match (block_findprop(blk, kv.0)) { case let prop: BlockPropId => if (block_findpropval(blk, prop, kv.1) is void) { trace::error(tr, "Property '{}' has no value '{}'", kv.0, kv.1): void; ok = false; }; case void => trace::error(tr, "Block has no property '{}'", kv.0): void; ok = false; }; }; return ok; }; fn bstate_decl_cond_test(cond: *BstateDeclCond, bstate: BstateId) bool = { match (*cond) { case let conds: BstateDeclAnd => for (let i = 0z; i < len(conds); i += 1) { if (!bstate_decl_cond_test(&conds[i], bstate)) { return false; }; }; return true; case let conds: BstateDeclOr => for (let i = 0z; i < len(conds); i += 1) { if (bstate_decl_cond_test(&conds[i], bstate)) { return true; }; }; return false; case let kv: (str, str) => const blk = bstate_getblock(bstate); const prop = block_findprop(blk, kv.0) as BlockPropId; const val = bstate_getprop(bstate, blk, prop); return block_propvalname(blk, prop, val) == kv.1; }; }; fn deser_bstate_decl(de: *dejson::deser) (BstateDecl | dejson::error) = { wassert_fields(de, "variants", "multipart")?; const de_variants = dejson::optfield(de, "variants")?; const de_multipart = dejson::optfield(de, "multipart")?; if (de_variants is void && de_multipart is void) return dejson::fail(de, `One of the keys "variants" and "multipart" must be present`); if (de_variants is dejson::deser) { if (de_multipart is dejson::deser) return dejson::fail(de, `Can't specify both "variants" and "multipart"`); return BstateDecl { multipart = false, cases = deser_bstate_decl_variants( &(de_variants as dejson::deser))?, }; } else { return BstateDecl { multipart = true, cases = deser_bstate_decl_multipart( &(de_multipart as dejson::deser))?, }; }; }; fn deser_bstate_decl_variants(de: *dejson::deser) ([](BstateDeclCond, BstateDeclVariant) | dejson::error) = { let success = false; let cases: [](BstateDeclCond, BstateDeclVariant) = alloc([], dejson::count(de)?); defer if (!success) free(cases); defer if (!success) for (let i = 0z; i < len(cases); i += 1) { bstate_decl_cond_finish(cases[i].0); bstate_decl_variant_finish(cases[i].1); }; // TODO: this is reliant on hash table order, so it's probably incorrect let it = dejson::iter(de)?; for (true) match (dejson::next(&it)) { case let entry: (str, dejson::deser) => let success = false; let conds: []BstateDeclCond = []; defer if (!success) bstate_decl_cond_finish(conds: BstateDeclAnd); // TODO: non-standard format: verify/correct details. let tok = strings::tokenize(entry.0, ","); for (true) match (strings::next_token(&tok)) { case let s: str => const (k, v) = if (strings::contains(s, "=")) { yield strings::cut(s, "="); } else { yield ("", ""); }; if (len(k) == 0 || len(v) == 0) return dejson::fail(&entry.1, "Invalid key-value pair"); append(conds, (strings::dup(k), strings::dup(v))); case done => break; }; const cond = conds: BstateDeclAnd; const variant = deser_bstate_decl_variant(&entry.1)?; success = true; static append(cases, (cond, variant)); case void => break; }; success = true; return cases; }; fn deser_bstate_decl_multipart(de: *dejson::deser) ([](BstateDeclCond, BstateDeclVariant) | dejson::error) = { let success = false; const ncases = dejson::length(de)?; let cases: [](BstateDeclCond, BstateDeclVariant) = alloc([], ncases); defer if (!success) free(cases); defer if (!success) for (let i = 0z; i < len(cases); i += 1) { bstate_decl_cond_finish(cases[i].0); bstate_decl_variant_finish(cases[i].1); }; for (let i = 0z; i < ncases; i += 1) { let success = false; const de_case = dejson::index(de, i)?; wassert_fields(&de_case, "when", "apply")?; const cond: BstateDeclCond = match ( dejson::optfield(&de_case, "when")?) { case let de_when: dejson::deser => yield deser_bstate_decl_cond(&de_when)?; case void => yield []: BstateDeclAnd; }; defer if (!success) bstate_decl_cond_finish(cond); const variant = deser_bstate_decl_variant( &dejson::field(&de_case, "apply")?)?; success = true; static append(cases, (cond, variant)); }; success = true; return cases; }; fn deser_bstate_decl_cond(de: *dejson::deser) (BstateDeclCond | dejson::error) = { let success = false; const de_and = dejson::optfield(de, "AND")?; const de_or = dejson::optfield(de, "OR")?; if (de_and is dejson::deser || de_or is dejson::deser) { if (dejson::count(de)? != 1) return dejson::fail(de, `No other keys allowed when "AND" or "OR" is present`); const de_conds = if (de_and is dejson::deser) de_and: dejson::deser else de_or: dejson::deser; const nconds = dejson::length(&de_conds)?; let conds: []BstateDeclCond = alloc([], nconds); defer if (!success) bstate_decl_cond_finish(conds: BstateDeclAnd); for (let i = 0z; i < nconds; i += 1) { static append(conds, deser_bstate_decl_cond( &dejson::index(&de_conds, i)?)?); }; const cond = if (de_and is dejson::deser) conds: BstateDeclAnd else conds: BstateDeclOr; success = true; return cond; }; let conds: []BstateDeclCond = alloc([], dejson::count(de)?); defer if (!success) bstate_decl_cond_finish(conds: BstateDeclAnd); let it = dejson::iter(de)?; for (true) match (dejson::next(&it)) { case let entry: (str, dejson::deser) => const value = dejson::string(&entry.1)?; // TODO: validate value properly if (strings::contains(value, "|")) { let conds_: []BstateDeclCond = []; let tok = strings::tokenize(value, "|"); for (true) match (strings::next_token(&tok)) { case let v: str => append(conds_, (strings::dup(entry.0), strings::dup(v))); case done => break; }; static append(conds, conds_: BstateDeclOr); } else { static append(conds, (strings::dup(entry.0), strings::dup(value))); }; case void => break; }; const cond = conds: BstateDeclAnd; success = true; return cond; }; fn deser_bstate_decl_variant(de: *dejson::deser) (BstateDeclVariant | dejson::error) = { const de = match (*de.val) { case json::object => yield *de; case []json::value => // TODO: random variants yield dejson::index(de, 0)?; case => return dejson::fail(de, "Expected object or array, found {}", dejson::typename(de.val)); }; const uvlock = match (dejson::optfield(&de, "uvlock")?) { case let de_uvlock: dejson::deser => yield dejson::boolean(&de_uvlock)?; case void => yield false; }; const x = match (dejson::optfield(&de, "x")?) { case let de_x: dejson::deser => yield deser_90deg_angle(&de_x)?; case void => yield 0u8; }; const y = match (dejson::optfield(&de, "y")?) { case let de_y: dejson::deser => yield deser_90deg_angle(&de_y)?; case void => yield 0u8; }; const model = dejson::string(&dejson::field(&de, "model")?)?; const model = ident_qual(model); return BstateDeclVariant { model = model, uvlock = uvlock, x = x, y = y, }; };