"""Random HUD cases for test_hud_parity.py, one generator per case kind.""" EFFECTS = ['minecraft:poison', 'minecraft:wither', 'minecraft:regeneration', 'minecraft:hunger', 'minecraft:speed'] def decorations(rng, n): max_health = rng.choice([20.0, 20.0, 6.0, 40.0, 33.0, 100.0]) health = rng.choice([max_health, round(rng.uniform(0, max_health), 2), 1.0, 0.5, 3.0]) air_max = 300 under = rng.random() < 0.3 air = rng.choice([air_max, rng.randint(-20, air_max), 0, 1, 29, 30, 31, 299]) if under or rng.random() < 0.2 else air_max case = { 'width': rng.choice([427, 320, 480, 640, 961]), 'height': rng.choice([240, 255, 270, 360, 501]), 'gameMode': rng.choice(['survival'] * 5 + ['adventure', 'creative', 'spectator']), 'crosshair': rng.random() < 0.7, 'attackIndicator': rng.choice(['OFF', 'CROSSHAIR', 'HOTBAR']), 'attackScale': rng.choice([1.0, round(rng.random(), 3), 0.0, 0.999]), # Minecraft.crosshairPickEntity (a living entity, alive or dead) and # getCurrentItemAttackStrengthDelay, for the full indicator. 'target': rng.choice([None, True, True, False]), 'attackDelay': rng.choice([5.0, 12.5, 4.0, 20.0, 5.0001, 10.0]), 'selected': rng.randint(0, 8), 'rightHanded': rng.random() < 0.7, 'slots': [rng.choice([None, 0, 0, 3, 5]) if rng.random() < 0.6 else None for _ in range(9)], 'offhand': rng.choice([None, None, 0, 4]), 'partial': round(rng.random(), 3), 'health': health, 'maxHealth': max_health, 'absorption': rng.choice([0.0, 0.0, 4.0, 3.5, 17.0, 40.0]), 'armor': rng.choice([0, 0, 1, 7, 20, 13]), 'food': rng.randint(0, 20), 'saturation': rng.choice([0.0, 5.0, 0.0]), 'effects': [e for e in EFFECTS if rng.random() < 0.15], 'frozen': rng.random() < 0.08, 'hardcore': rng.random() < 0.2, 'damageCooldown': rng.choice([0, 0, 10]), 'air': air, 'maxAir': air_max, 'underWater': under, 'tick': rng.randint(0, 100000), 'lastHealth': rng.randint(0, 40), 'displayHealth': rng.randint(0, 40), 'lastHealthAgo': rng.choice([0, 500, 5000]), 'healthBlinkTime': 0, 'xpProgress': rng.choice([0.0, round(rng.random(), 4), 0.999]), 'xpLevel': rng.choice([0, 1, 7, 30, 123, 4000]), 'experienceBar': rng.random() < 0.8, } if case['target'] and rng.random() < 0.5: case['attackScale'] = 1.0 if case['damageCooldown'] and rng.random() < 0.5: case['healthBlinkTime'] = case['tick'] + rng.randint(1, 20) if rng.random() < 0.15: case['vehicleMaxHealth'] = rng.choice([15.0, 30.0, 53.0, 100.0]) case['vehicleHealth'] = round(rng.uniform(0, case['vehicleMaxHealth']), 1) return case ALL_EFFECTS = ['speed', 'slowness', 'haste', 'mining_fatigue', 'strength', 'instant_health', 'instant_damage', 'jump_boost', 'nausea', 'regeneration', 'resistance', 'fire_resistance', 'water_breathing', 'invisibility', 'blindness', 'night_vision', 'hunger', 'weakness', 'poison', 'wither', 'health_boost', 'absorption', 'saturation', 'glowing', 'levitation', 'luck', 'unluck', 'slow_falling', 'conduit_power', 'dolphins_grace', 'bad_omen', 'hero_of_the_village', 'darkness', 'trial_omen', 'raid_omen', 'wind_charged', 'weaving', 'oozing', 'infested', 'breath_of_the_nautilus'] SIZES = dict(width=[427, 320, 480, 640, 961], height=[240, 255, 270, 360, 501]) def screen(rng): return {'width': rng.choice(SIZES['width']), 'height': rng.choice(SIZES['height'])} def backdrop(rng): """The text background options behind textWithBackdrop.""" return {'chatOnly': rng.random() < 0.5, 'textOpacity': rng.choice([0.0, 0.5, 0.25, 1.0, round(rng.random(), 3)])} def effects(rng, n): count = rng.choice([1, 2, 3, 5, 8, 12, 20]) active = [] for e in rng.sample(ALL_EFFECTS, count): duration = rng.choice([-1, 1, 7, 19, 20, 99, 150, 199, 200, 201, 600, 32147, 32148, 40000, rng.randint(0, 220)]) active.append({'id': 'minecraft:' + e, 'duration': duration, 'ambient': rng.random() < 0.25, 'icon': rng.random() < 0.9}) return {**screen(rng), 'demo': rng.random() < 0.15, 'active': active} TITLES = [{'text': 'Hello'}, {'text': 'Boss fight!', 'color': 'red', 'bold': True}, {'text': 'Wave 3', 'italic': True}, {'text': 'A long title over the whole screen'}, {'text': ''}, ['Mixed ', {'text': 'parts', 'color': 'gold'}], {'text': 'Ünïcödé ★'}, # Servers send legacy formatting codes inside a component's text; the font applies them: {'text': '§8[§fAny questions? §8] §e§l/support en'}, {'text': '§cRed §rreset §', 'color': 'blue'}] def title(rng, n): """Title, subtitle and action bar events between ticks (fades, setTimes mid-title, clears).""" ops = [] for _ in range(rng.randint(1, 6)): r = rng.random() if r < 0.3: ops.append(['title', rng.choice(TITLES)]) elif r < 0.45: ops.append(['subtitle', rng.choice(TITLES)]) elif r < 0.6: ops.append(['times', rng.choice([-1, 0, 1, 5, 10, 20]), rng.choice([-1, 0, 3, 40, 70]), rng.choice([-1, 0, 1, 7, 20])]) elif r < 0.65: ops.append(['clear']) elif r < 0.7: ops.append(['reset']) elif r < 0.85: ops.append(['overlay', rng.choice(TITLES), rng.random() < 0.3]) ops.append(['tick', rng.choice([0, 0, 1, 2, 5, 9, 10, 11, 30, 50, 59, 60, 79, 80, 81, 99, 100, rng.randint(0, 120)])]) return {**screen(rng), **backdrop(rng), 'partial': round(rng.random(), 3), 'ops': ops} # Items for the selected item name: id, translation key and rarity colour (checked by the oracle's own stacks). HELD = [('stone', 'block.minecraft.stone', 'white'), ('diamond_sword', 'item.minecraft.diamond_sword', 'white'), ('totem_of_undying', 'item.minecraft.totem_of_undying', 'yellow'), ('beacon', 'block.minecraft.beacon', 'aqua'), ('dragon_egg', 'block.minecraft.dragon_egg', 'light_purple'), ('apple', 'item.minecraft.apple', 'white')] CUSTOM = [None, None, None, {'text': 'Excalibur'}, {'text': 'Red', 'color': 'red'}, {'text': 'Plain', 'italic': False}] def selected(rng, n): """Selected-slot changes tick by tick, then the name with its fade.""" def stack(): item, key, rarity = rng.choice(HELD) return {'item': item, 'key': key, 'rarity': rarity, 'custom': rng.choice(CUSTOM)} steps, held = [], stack() for _ in range(rng.randint(1, 60)): if rng.random() < 0.1: if rng.random() < 0.2: held = None else: held = stack() steps.append(held) return {**screen(rng), **backdrop(rng), 'gameMode': rng.choice(['survival', 'adventure', 'creative']), 'displayTime': rng.choice([1.0, 1.0, 0.5, 2.0, 0.123]), 'steps': steps} def boss(rng, n): bars = [] for _ in range(rng.choice([1, 1, 2, 3, 5, 9])): progress = rng.choice([0.0, 1.0, 0.5, 0.001, 0.999, 1 / 182, round(rng.random(), 4)]) bars.append({'name': rng.choice(TITLES), 'progress': progress, 'color': rng.randrange(7), 'overlay': rng.randrange(5)}) return {**screen(rng), 'bars': bars} def jump(rng, n): return {**screen(rng), 'cooldown': rng.choice([0, 0, 0, 1, 10]), 'jumpScale': rng.choice([0.0, 1.0, 0.4, 0.001, round(rng.random(), 4)])} # Servers' sidebars use legacy formatting codes in fake owners, team affixes and titles (#937): # a code, an unknown code, `§r` and a trailing lone `§`. OWNERS = ['Alex', 'steve', 'Zed', 'bob', 'Bobby', 'ÉLodie', 'élan', '#hidden', 'x', 'LongPlayerName_123', 'Mia', '_under', '§1§r', '§a§lPro§r name', 'end§', '§zodd§Rcase'] FORMATS = [None, None, None, {'type': 'blank'}, {'type': 'fixed', 'value': {'text': 'FIX', 'color': 'aqua'}}, {'type': 'styled', 'style': {'color': 'gold', 'bold': True}}, {'type': 'styled', 'style': {}}] def sidebar(rng, n): """An objective with scores, display names, number formats and teams.""" owners = rng.sample(OWNERS, rng.randint(0, len(OWNERS))) if rng.random() < 0.3: # more rows than the 15 shown owners += [f'p{i}' for i in range(rng.randint(5, 20))] scores = [{'owner': o, 'value': rng.choice([0, 1, -1, 7, 7, 100, 2147483647, -2147483648, rng.randint(-50, 50)]), 'display': rng.choice([None, None, None, {'text': 'Shown ' + o, 'color': 'green'}]), 'format': rng.choice(FORMATS)} for o in owners] teams = [] for t in range(rng.choice([0, 0, 1, 2])): teams.append({'name': f'team{t}', 'prefix': rng.choice([{'text': ''}, {'text': '[A] '}, {'text': '<', 'color': 'red'}, {'text': '§c§lRed §r', 'color': 'blue'}]), 'suffix': rng.choice([{'text': ''}, {'text': ' *'}, {'text': ' §7(§e5§7)§'}]), 'color': rng.choice([None, 'red', 'blue', 'gold', 'white']), 'members': [o for o in owners if rng.random() < 0.3]}) return {**screen(rng), **backdrop(rng), 'objective': {'display': rng.choice(TITLES + [{'text': 'Kills'}, {'text': '§e§lANTICHEAT §7test'}]), 'format': rng.choice(FORMATS + [{'type': 'fixed', 'value': {'text': '§a12§'}}])}, 'scores': scores, 'teams': teams} # Items for the decorations: id and max damage (0 = not damageable), checked by the oracle's own stacks. DECOR_ITEMS = [('diamond_sword', 1561), ('bow', 384), ('shield', 336), ('flint_and_steel', 64), ('elytra', 432), ('stone', 0), ('ender_pearl', 0), ('apple', 0)] def decor(rng, n): """GuiGraphicsExtractor.itemDecorations: count, durability bar and cooldown shade.""" item, max_damage = rng.choice(DECOR_ITEMS) case = {'x': rng.randint(-20, 400), 'y': rng.randint(-20, 300), 'item': item, 'maxDamage': max_damage, 'count': rng.choice([1, 1, 2, 16, 64, 99, 0, 10, 127]), 'damage': rng.choice([0, 0, 1, max_damage // 2, max_damage - 1, max_damage, rng.randint(0, max(max_damage, 1))]) if max_damage else 0, 'unbreakable': rng.random() < 0.1, 'partial': round(rng.random(), 3)} if rng.random() < 0.4: duration = rng.choice([1, 2, 7, 20, 100, 200]) case['cooldown'] = {'duration': duration, 'elapsed': rng.choice([0, 1, duration - 1, duration, rng.randint(0, duration)])} if rng.random() < 0.1: case['countText'] = rng.choice(['', '+', '1/2', 'x9']) return case def overlays(rng, n): """Camera overlays (vignette and border warning, spyglass, pumpkin, powder snow, portal, nausea) and sleep.""" case = {**screen(rng), 'vignette': rng.random() < 0.7, 'vignetteBrightness': rng.choice([1.0, 0.0, 0.5, round(rng.random(), 4), 1.3, -0.2]), 'firstPerson': rng.random() < 0.8, 'scoping': rng.random() < 0.2, 'scopeScale': rng.choice([0.5, 1.125, round(rng.uniform(0.5, 1.125), 4)]), 'partial': round(rng.random(), 3), 'head': rng.choice([None, None, 'carved_pumpkin', 'diamond_helmet']), 'ticksFrozen': rng.choice([0, 0, 0, 1, 70, 139, 140, 300]), 'portal': rng.choice([[0.0, 0.0], [0.0, 0.0], [0.0, 0.0125], [0.5, 0.5125], [1.0, 1.0], [0.05, 0.0], [round(rng.random(), 3)] * 2]), 'nausea': rng.choice([0.0, 0.0, 1.0, 0.25, round(rng.random(), 3)]), 'screenEffectScale': rng.choice([1.0, 0.0, 0.5, round(rng.random(), 3)]), 'sleepTimer': rng.choice([0, 0, 0, 1, 50, 100, 101, 105, 110, 111])} if rng.random() < 0.4: size = rng.choice([10.0, 50.0, 200.0, 1000.0]) case['border'] = {'centerX': rng.choice([0.0, 100.5]), 'centerZ': rng.choice([0.0, -30.25]), 'size': size, 'warningBlocks': rng.choice([0, 5, 20, 100]), 'warningTime': rng.choice([0, 15])} case['camX'] = round(rng.uniform(-150, 250), 2) case['camZ'] = round(rng.uniform(-150, 150), 2) return case WORDS = ['hello', 'world', 'a', 'Minecraft', 'supercalifragilisticexpialidocious', 'x' * 70, 'ok!', 'Ünïcödé', '★', ''] def chat_text(rng): words = [rng.choice(WORDS) for _ in range(rng.choice([1, 2, 5, 12, 30]))] text = rng.choice([' ', ' ', ' ', ' ', '\n']).join(words) if rng.random() < 0.1: text = rng.choice([' ', '\n']) + text + rng.choice(['', ' ', '\n']) if rng.random() < 0.3: cut = rng.randint(0, len(text)) return [{'text': text[:cut], 'color': rng.choice(['red', 'gold', 'aqua'])}, {'text': text[cut:], 'bold': rng.random() < 0.5, 'italic': rng.random() < 0.3}] return {'text': text} def chat(rng, n): """Messages wrapped to the chat width and fading 200 ticks after they arrive.""" count = rng.choice([1, 2, 5, 10, 30, 120]) now = rng.randint(200, 2000) messages = [] for _ in range(count): at = rng.choice([now, now - 1, now - 179, now - 180, now - 190, now - 199, now - 200, now - 500, rng.randint(now - 260, now + 5)]) messages.append({'text': chat_text(rng), 'at': at, 'tag': rng.choice([None, None, 'system', 'error'])}) messages.sort(key=lambda m: m['at']) return {**screen(rng), 'now': now, 'messages': messages, 'chatScale': rng.choice([1.0, 1.0, 0.5, 0.0 + 0.75, 0.25]), 'chatWidth': rng.choice([1.0, 1.0, 0.5, 0.0, 0.3]), 'chatOpacity': rng.choice([1.0, 0.5, 0.0, 0.25]), 'textOpacity': rng.choice([0.5, 0.0, 1.0, 0.25]), 'chatLineSpacing': rng.choice([0.0, 0.0, 0.5, 1.0, 0.25]), 'chatHeightUnfocused': rng.choice([70 / 160, 1.0, 0.0, 0.5]), 'chatHeightFocused': rng.choice([1.0, 1.0, 0.5, 0.25]), # The chat screen open: foreground, focused height, scrolled up or down. 'focused': rng.random() < 0.5, 'scroll': rng.choice([0, 0, 1, 3, 7, 20, -2, 1000])} # Generators by kind: (rng, index) -> case. Cases are drawn round-robin. KINDS = { 'decorations': decorations, 'effects': effects, 'title': title, 'selected': selected, 'boss': boss, 'jump': jump, 'sidebar': sidebar, 'decor': decor, 'overlays': overlays, 'chat': chat, }