mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-07 02:56:17 -10:00
test: mine a block in creative too, where nothing drops
tools/test_block_removal_parity.py only ever mined as a survival player: every mine case ran ServerPlayerGameMode.destroyBlock in SURVIVAL, so the creative branch (Player.preventsBlockDrops, no mined statistic, no tool wear) was never compared even though game.rs::break_block_as_player splits on it. Half the mine cases now run in creative, with a tool that can destroy blocks in creative (a sword and a mace cannot, which the creative-mining predicate corpus already owns). VanillaDispenserOracle's Miner gains a creative flag (isCreative and hasInfiniteMaterials) and the real CREATIVE game mode; the game's dispense fixture sets the same mode for the case. --survival-only reproduces the corpus as it was before this change. 4000/4000 cases match on the new corpus.
This commit is contained in:
1 parent
41f822b659
commit
4062a09766
3 files changed
+45
-17
No files matched your search
@@ -878,14 +878,19 @@ impl Game<'_> {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
|
||||
/// A survival player three blocks south of `pos` mines it holding the
|
||||
/// case's item (`break_block`, which every player's mining goes through):
|
||||
/// the statistics and the held stack, as `VanillaDispenserOracle.mine`
|
||||
/// prints them.
|
||||
/// A player three blocks south of `pos` mines it holding the case's item
|
||||
/// (`break_block`, which every player's mining goes through), in survival
|
||||
/// or in creative, where the block and its contents never drop: the
|
||||
/// statistics and the held stack, as `VanillaDispenserOracle.mine` prints
|
||||
/// them.
|
||||
fn fixture_mine(&mut self, pos: BlockPos, op: &Value) -> Result<Vec<String>> {
|
||||
let stack = parse_stack(op["item"].as_str().unwrap_or("-"))?;
|
||||
self.mode = GameMode::Survival;
|
||||
self.creative = false;
|
||||
self.mode = if op["creative"].as_bool().unwrap_or(false) {
|
||||
GameMode::Creative
|
||||
} else {
|
||||
GameMode::Survival
|
||||
};
|
||||
self.creative = self.mode == GameMode::Creative;
|
||||
self.player = Some(PlayerMotion::new(
|
||||
self.motion,
|
||||
horizon_core::geometry::Vec3::new(
|
||||
|
||||
@@ -193,10 +193,13 @@ public final class VanillaDispenserOracle {
|
||||
static List<String> mine(VanillaBlockUpdateOracle.World w, BlockPos pos, JsonObject op) throws Exception {
|
||||
Miner player = (Miner) VanillaBlockUpdateOracle.allocate.invoke(VanillaBlockUpdateOracle.unsafe, Miner.class);
|
||||
player.world = w;
|
||||
player.creative = op.has("creative") && op.get("creative").getAsBoolean();
|
||||
player.hand = stack(op.get("item").getAsString());
|
||||
player.offhand = ItemStack.EMPTY;
|
||||
player.stats = new ArrayList<>();
|
||||
VanillaBlockUpdateOracle.set(net.minecraft.world.entity.player.Player.class, player, "abilities", new net.minecraft.world.entity.player.Abilities());
|
||||
var abilities = new net.minecraft.world.entity.player.Abilities();
|
||||
abilities.invulnerable = abilities.instabuild = abilities.mayfly = player.creative;
|
||||
VanillaBlockUpdateOracle.set(net.minecraft.world.entity.player.Player.class, player, "abilities", abilities);
|
||||
VanillaBlockUpdateOracle.set(net.minecraft.world.entity.player.Player.class, player, "foodData", new net.minecraft.world.food.FoodData());
|
||||
var advancements = (net.minecraft.server.PlayerAdvancements) VanillaBlockUpdateOracle.allocate.invoke(VanillaBlockUpdateOracle.unsafe, net.minecraft.server.PlayerAdvancements.class);
|
||||
VanillaBlockUpdateOracle.set(net.minecraft.server.PlayerAdvancements.class, advancements, "activeTriggers", new HashMap<>());
|
||||
@@ -208,7 +211,7 @@ public final class VanillaDispenserOracle {
|
||||
player.setBoundingBox(EntityTypes.PLAYER.getDimensions().makeBoundingBox(at));
|
||||
var gameMode = new net.minecraft.server.level.ServerPlayerGameMode(player);
|
||||
VanillaBlockUpdateOracle.set(net.minecraft.server.level.ServerPlayerGameMode.class, gameMode, "gameModeForPlayer",
|
||||
net.minecraft.world.level.GameType.SURVIVAL);
|
||||
player.creative ? net.minecraft.world.level.GameType.CREATIVE : net.minecraft.world.level.GameType.SURVIVAL);
|
||||
gameMode.destroyBlock(pos);
|
||||
List<String> out = new ArrayList<>(player.stats);
|
||||
out.add("mine held " + ParityData.canon(player.hand));
|
||||
@@ -218,14 +221,15 @@ public final class VanillaDispenserOracle {
|
||||
/** The player of a "mine" case: a survival `Clicker` whose selected inventory slot is its main hand. */
|
||||
static final class Miner extends net.minecraft.server.level.ServerPlayer {
|
||||
VanillaBlockUpdateOracle.World world;
|
||||
boolean creative;
|
||||
ItemStack hand, offhand;
|
||||
List<String> stats;
|
||||
Miner() {
|
||||
super(null, null, null, null);
|
||||
}
|
||||
@Override public ServerLevel level() { return world; }
|
||||
@Override public boolean hasInfiniteMaterials() { return false; }
|
||||
@Override public boolean isCreative() { return false; }
|
||||
@Override public boolean hasInfiniteMaterials() { return creative; }
|
||||
@Override public boolean isCreative() { return creative; }
|
||||
@Override public boolean isSpectator() { return false; }
|
||||
@Override public EntityType<?> getType() { return EntityTypes.PLAYER; }
|
||||
@Override public net.minecraft.core.Holder<EntityType<?>> typeHolder() { return EntityTypes.PLAYER.builtInRegistryHolder(); }
|
||||
|
||||
@@ -6,8 +6,11 @@ shelf, shulker box or another block entity holder at the origin holds random
|
||||
contents; the case then replaces it with `Level.setBlock` (another block, the
|
||||
same block in another state, with or without UPDATE_SKIP_BLOCK_ENTITY_SIDEEFFECTS),
|
||||
destroys it with `Level.destroyBlock` (with or without drops), removes it with
|
||||
`Level.removeBlock`, or a survival player mines it (`ServerPlayerGameMode.destroyBlock`,
|
||||
holding a tool or nothing, #844). Vanilla runs over the headless level of the dispenser oracle
|
||||
`Level.removeBlock`, or a player mines it (`ServerPlayerGameMode.destroyBlock`,
|
||||
holding a tool or nothing, #844): in survival, where the block's drops, the tool's wear and the
|
||||
mined statistic follow, and in creative, where the block itself never drops (a container's
|
||||
contents still spill) and no statistic is awarded. Vanilla runs over the headless level of the
|
||||
dispenser oracle
|
||||
(`VanillaDispenserOracle`, "remove" cases), which replays `LevelChunk.setBlockState`'s
|
||||
`BlockEntity.preRemoveSideEffects`; Horizon runs its game glue in a headless game
|
||||
(`HORIZON_DISPENSE_FIXTURE`). Both print the block changes, the contents left,
|
||||
@@ -111,7 +114,7 @@ def other_state(states, rng, name, current):
|
||||
return rng.choice(choices) if choices else None
|
||||
|
||||
|
||||
def cases(facts, count, seed, only):
|
||||
def cases(facts, count, seed, only, creative=True):
|
||||
rng = random.Random(seed)
|
||||
states = d.States(facts)
|
||||
names = list(HOLDERS)
|
||||
@@ -143,9 +146,23 @@ def cases(facts, count, seed, only):
|
||||
elif roll < 0.92:
|
||||
op = {'op': 'remove'}
|
||||
else:
|
||||
# ServerPlayerGameMode.destroyBlock: a survival player mines it (#844). The tool comes
|
||||
# from the roll, not another draw, so the cases after this one stay the same.
|
||||
op = {'op': 'mine', 'item': TOOLS[int(roll * 1000) % len(TOOLS)]}
|
||||
# ServerPlayerGameMode.destroyBlock: a player mines it (#844), in survival or, every
|
||||
# other mine, in creative, where the block itself never drops and no statistic is
|
||||
# awarded. The tool comes from the roll, not another draw, so the cases after this
|
||||
# one stay the same.
|
||||
item = TOOLS[int(roll * 1000) % len(TOOLS)]
|
||||
if creative and n % 2 == 0:
|
||||
# A sword or mace carries `can_destroy_blocks_in_creative: false`, so a creative
|
||||
# player holding one cannot mine at all (`Item.canDestroyBlock`). That predicate
|
||||
# has its own corpus (test_creative_mining_parity); these cases need a tool that
|
||||
# does remove the block, and the empty hand does too.
|
||||
if item in (plain('iron_sword'), plain('mace')):
|
||||
item = plain('diamond_pickaxe')
|
||||
op = {'op': 'mine', 'item': item, 'creative': True}
|
||||
else:
|
||||
op = {'op': 'mine', 'item': item}
|
||||
if creative:
|
||||
op['creative'] = False
|
||||
case = {**h.environment(rng), 'mode': 'remove', 'pos': [0, 0, 0], 'cells': cells, 'items': items, 'remove': op}
|
||||
case.pop('raining', None)
|
||||
case.pop('thundering', None)
|
||||
@@ -162,6 +179,8 @@ def describe(case, facts):
|
||||
|
||||
def main():
|
||||
parser = h.argument_parser(__doc__)
|
||||
parser.add_argument('--survival-only', action='store_true',
|
||||
help='mine in survival only, as the corpus did before creative mining')
|
||||
args = parser.parse_args()
|
||||
if args.count == 40000:
|
||||
args.count = 4000
|
||||
@@ -170,7 +189,7 @@ def main():
|
||||
resources = lab.LOCAL / 'horizon-client' / lock['minecraft']['version']
|
||||
work = lab.LOCAL / 'block-removal-parity'
|
||||
work.mkdir(parents=True, exist_ok=True)
|
||||
all_cases = list(cases(facts, args.count, args.seed, args.only))
|
||||
all_cases = list(cases(facts, args.count, args.seed, args.only, not args.survival_only))
|
||||
(work / 'cases.jsonl').write_text(''.join(json.dumps(case) + '\n' for case in all_cases))
|
||||
classes = work / 'classes'
|
||||
classes.mkdir(exist_ok=True)
|
||||
|
||||
Reference in new issue
Block a user