mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-07 11:06:11 -10:00
Merge remote-tracking branch 'origin/main' into cg65v-10208
# Conflicts: # tools/lan_e2e.py
This commit is contained in:
commit
f16cc3d6d0
773 files changed
+54635
-4249
No files matched your search
@@ -0,0 +1,92 @@
|
||||
"""Live boat leash command reads after real connected-owner interactions."""
|
||||
|
||||
|
||||
def cases(cmd, setup):
|
||||
boat = '00000146-0000-0001-0000-000000000001'
|
||||
pending = '00000146-0000-0001-0000-000000000002'
|
||||
unknown = '[I;111,222,333,444]'
|
||||
ops = [cmd('/tick freeze'), cmd('/forceload add 0 0 31 31'),
|
||||
{'op': 'wait_chunks', 'chunks': [[0, 0], [1, 0], [0, 1], [1, 1]]}]
|
||||
for x, z in [(0, 0), (16, 0), (0, 16), (16, 16)]:
|
||||
ops.append(cmd(f'/execute if loaded {x} 100 {z}', expect_result=1))
|
||||
ops += [cmd('/fill 0 99 0 31 99 31 stone', expect_result=1024),
|
||||
cmd('/fill 0 100 0 31 104 31 air'),
|
||||
cmd('/gamemode survival @s'), cmd('/tp @s 4.5 100 4.5 0 0'),
|
||||
cmd('/clear @s'), cmd('/scoreboard objectives add boat_view dummy'),
|
||||
cmd('/item replace entity @s hotbar.0 with lead 2', expect_result=1)]
|
||||
ops += setup('/execute store result score #start boat_view run time query gametime',
|
||||
'/data modify storage boat_view:guard owner set from entity @s UUID')
|
||||
|
||||
def owner():
|
||||
return [cmd('/execute if entity @s[type=player,gamemode=survival,nbt={Pos:[4.5d,100d,4.5d],Health:20f}]', expect_result=1),
|
||||
cmd('/data get entity @s UUID'), cmd('/data get entity @s Inventory')]
|
||||
|
||||
def actor(target, x, z, tag):
|
||||
return [
|
||||
cmd(f'/execute if entity @e[type=oak_boat,tag={tag},nbt={{UUID:[I;326,1,0,{int(target[-8:], 16)}],Pos:[{x}d,100d,{z}d],NoGravity:1b}}]', expect_result=1),
|
||||
cmd(f'/data get entity {target} UUID', expect_result=4),
|
||||
cmd(f'/data get entity {target} Pos', expect_result=3),
|
||||
cmd(f'/execute if block {int(x)} 99 {int(z)} stone', expect_result=1)]
|
||||
|
||||
def leash(target, present):
|
||||
rows = [cmd(f'/data get entity {target} leash'),
|
||||
cmd(f'/data get entity {target} leash.UUID', expect_result=4 if present else None)]
|
||||
rows += setup('/scoreboard players set #r boat_view 31',
|
||||
'/scoreboard players set #s boat_view 17')
|
||||
rows += [cmd('/scoreboard players get #r boat_view', expect_result=31),
|
||||
cmd('/scoreboard players get #s boat_view', expect_result=17),
|
||||
cmd('/execute store result score #r boat_view store success score #s boat_view '
|
||||
f'run data get entity {target} leash.UUID'),
|
||||
cmd('/scoreboard players get #r boat_view', expect_result=4 if present else None),
|
||||
cmd('/scoreboard players get #s boat_view', expect_result=int(present))]
|
||||
return rows
|
||||
|
||||
ops += owner()
|
||||
ops += [cmd('/summon oak_boat 6.5 100 4.5 {UUID:[I;326,1,0,1],Tags:["boat_view"],NoGravity:1b,Motion:[0d,0d,0d]}')]
|
||||
ops += actor(boat, 6.5, 4.5, 'boat_view') + leash(boat, False)
|
||||
# Attach, detach and attach again. Each click traverses the real owner packet path.
|
||||
for attached, lead_count in [(True, 1), (False, 1), (True, 0)]:
|
||||
ops.append({'op': 'boat_interact', 'uuid': boat})
|
||||
ops += owner() + actor(boat, 6.5, 4.5, 'boat_view') + leash(boat, attached)
|
||||
ops.append(cmd('/clear @s lead 0', expect_result=lead_count if lead_count else None))
|
||||
if not lead_count:
|
||||
ops.append(cmd('/execute unless items entity @s hotbar.0 lead', expect_result=1))
|
||||
if attached:
|
||||
ops += setup('/data modify storage boat_view:guard owner set from entity @s UUID')
|
||||
ops.append(cmd('/execute store success score #different boat_view run data modify '
|
||||
f'storage boat_view:guard owner set from entity {boat} leash.UUID'))
|
||||
ops.append(cmd('/scoreboard players get #different boat_view', expect_result=0))
|
||||
ops += leash(boat, True) # Reads themselves do not change the live association.
|
||||
ops += [cmd(f'/summon oak_boat 10.5 100 4.5 {{UUID:[I;326,1,0,2],Tags:["boat_pending"],NoGravity:1b,leash:{{UUID:{unknown}}}}}')]
|
||||
ops += actor(pending, 10.5, 4.5, 'boat_pending') + leash(pending, True)
|
||||
ops.append(cmd('/execute unless entity 0000006f-0000-00de-0000-014d000001bc', expect_result=1))
|
||||
ops += [cmd(f'/execute if entity {pending}'),
|
||||
cmd('/data get storage boat_view:guard owner')]
|
||||
ops += setup('/execute store result score #elapsed boat_view run time query gametime',
|
||||
'/scoreboard players operation #elapsed boat_view -= #start boat_view')
|
||||
ops += [cmd('/scoreboard players get #elapsed boat_view', expect_result=0)] + owner()
|
||||
# Independently measured loaded-link control: after resolution and a real
|
||||
# detach, stale input NBT must not override the now-absent live association.
|
||||
# This is the pinned harness owner's UUID, positively checked before use.
|
||||
holder = '[I;-1608632253,-450807400,-1786055591,-2146546415]'
|
||||
loaded = '00000146-0000-0001-0000-000000000003'
|
||||
ops += [cmd(f'/execute if entity @s[nbt={{UUID:{holder}}}]', expect_result=1),
|
||||
cmd('/tp @s 4.5 100 8.5 0 0'),
|
||||
cmd('/execute if entity @s[type=player,gamemode=survival,nbt={Pos:[4.5d,100d,8.5d],Health:20f}]', expect_result=1),
|
||||
cmd(f'/summon oak_boat 6.5 100 8.5 {{UUID:[I;326,1,0,3],Tags:["boat_loaded"],NoGravity:1b,Motion:[0d,0d,0d],leash:{{UUID:{holder}}}}}')]
|
||||
ops += actor(loaded, 6.5, 8.5, 'boat_loaded') + leash(loaded, True)
|
||||
ops.append(cmd(f'/execute as {loaded} on leasher if entity @s[type=player]'))
|
||||
ops += setup('/execute store result score #loaded_start boat_view run time query gametime')
|
||||
ops += [cmd('/tick step 1', expect_result=1), {'op': 'tick', 'n': 1}]
|
||||
ops += actor(loaded, 6.5, 8.5, 'boat_loaded') + leash(loaded, True)
|
||||
ops += [cmd(f'/execute as {loaded} on leasher if entity @s[nbt={{UUID:{holder}}}]', expect_result=1),
|
||||
cmd('/item replace entity @s hotbar.0 with lead', expect_result=1),
|
||||
{'op': 'boat_interact', 'uuid': loaded}]
|
||||
ops += actor(loaded, 6.5, 8.5, 'boat_loaded') + leash(loaded, False)
|
||||
ops += [cmd(f'/execute as {loaded} on leasher if entity @s[type=player]'),
|
||||
cmd('/clear @s lead 0', expect_result=1),
|
||||
cmd('/execute if entity @s[type=player,gamemode=survival,nbt={Pos:[4.5d,100d,8.5d],Health:20f}]', expect_result=1)]
|
||||
ops += setup('/execute store result score #loaded_elapsed boat_view run time query gametime',
|
||||
'/scoreboard players operation #loaded_elapsed boat_view -= #loaded_start boat_view')
|
||||
ops += [cmd('/scoreboard players get #loaded_elapsed boat_view', expect_result=1)]
|
||||
return ops
|
||||
@@ -5856,14 +5856,19 @@ ISOLATED_REINFORCEMENTS = frozenset({'command zombie reinforcements', 'command r
|
||||
# These overlays replace built-in recipes, never beside the corpus's fixed vanilla recipe-count controls.
|
||||
ISOLATED_RECIPES = frozenset({'crafting recipe priority', 'crafting base recipe control', 'transmute result patches'})
|
||||
ISOLATED_FRAME_MAPS = frozenset({'frame live map detachment'})
|
||||
ISOLATED_MAP_CRAFTING = frozenset({'craft map takes'})
|
||||
ISOLATED_MACRO_TYPE = frozenset({'macro source type error'})
|
||||
ISOLATED_PICKUP_SAVE = frozenset({'mob saved pickup loot'})
|
||||
ISOLATED_ORB_UUID = frozenset({'orb saved UUID'})
|
||||
ISOLATED_STAND_FALL = frozenset({'stand saved fall distance'})
|
||||
ISOLATED_GROUPS = ISOLATED_REINFORCEMENTS | ISOLATED_RECIPES | ISOLATED_WOLF_ANGER | ISOLATED_FRAME_MAPS | ISOLATED_MACRO_TYPE | ISOLATED_PICKUP_SAVE | ISOLATED_ORB_UUID | ISOLATED_STAND_FALL
|
||||
ISOLATED_GROUPS = ISOLATED_REINFORCEMENTS | ISOLATED_RECIPES | ISOLATED_WOLF_ANGER | ISOLATED_FRAME_MAPS | ISOLATED_MAP_CRAFTING | ISOLATED_MACRO_TYPE | ISOLATED_PICKUP_SAVE | ISOLATED_ORB_UUID | ISOLATED_STAND_FALL
|
||||
|
||||
|
||||
def groups():
|
||||
try:
|
||||
from .craft_map_take_corpus import cases as craft_map_takes
|
||||
except ImportError:
|
||||
from craft_map_take_corpus import cases as craft_map_takes
|
||||
try:
|
||||
from .transmute_patch_corpus import cases as transmute_patches
|
||||
except ImportError:
|
||||
@@ -5908,6 +5913,7 @@ def groups():
|
||||
('mob effect predicate view', mob_effect_predicates(cmd, setup)),
|
||||
('mob custom name components', mob_name_predicates(cmd, setup)),
|
||||
('frame live map detachment', frame_maps(cmd, setup)),
|
||||
('craft map takes', craft_map_takes(cmd, setup)),
|
||||
('allay player damage immunity', allay_player_damage(cmd, setup)),
|
||||
*wolf_anger_groups(cmd, setup),
|
||||
('allay passive healing', allay_healing(cmd, setup)),
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
"""Authoritative crafting-table map takes; menu snapshots are state observations.
|
||||
|
||||
Slots carry [item ID, count, map ID, map post-processing ID]. Full saved input
|
||||
and output stacks are independently read with /data; revision values are raw
|
||||
transport diagnostics, not a prediction/packet-family contract.
|
||||
"""
|
||||
|
||||
def cases(cmd, setup):
|
||||
def stack(item, count=1, map_id=None, post=None):
|
||||
return ['minecraft:' + item, count, map_id, post]
|
||||
|
||||
def snapshot(label, window, present, carried=None):
|
||||
slots = [None] * 46
|
||||
for slot, value in present.items():
|
||||
slots[slot] = value
|
||||
return {'op': 'craft_menu_state', 'label': label,
|
||||
'expect_observation': {'window': window, 'kind': 'minecraft:crafting',
|
||||
'slots': slots, 'carried': [] if carried is None else carried}}
|
||||
|
||||
def click(slot, button=0, mode=0):
|
||||
return {'op': 'craft_menu_click', 'slot': slot, 'button': button, 'input': mode}
|
||||
|
||||
ops = [cmd('/tick freeze'), cmd('/forceload add 0 0 31 31'),
|
||||
{'op': 'wait_chunks', 'chunks': [[0, 0], [1, 0], [0, 1], [1, 1]]}]
|
||||
for x, z in [(0, 0), (16, 0), (0, 16), (16, 16)]:
|
||||
ops.append(cmd(f'/execute if loaded {x} 100 {z}', expect_result=1))
|
||||
ops += [cmd('/fill 0 99 0 31 99 31 stone', expect_result=1024),
|
||||
cmd('/fill 0 100 0 31 104 31 air'),
|
||||
cmd('/setblock 6 100 4 crafting_table'),
|
||||
cmd('/gamemode survival @s'), cmd('/tp @s 4.5 100 4.5 0 0'),
|
||||
cmd('/clear @s'), cmd('/scoreboard objectives add craft_map dummy')]
|
||||
ops += setup('/execute store result score start craft_map run time query gametime',
|
||||
'/data modify storage craft_map:owner uuid set from entity @s UUID')
|
||||
ops += [cmd('/execute if entity @s[type=player,gamemode=survival,nbt={Pos:[4.5d,100d,4.5d],Health:20f}]', expect_result=1),
|
||||
cmd('/data get entity @s UUID'),
|
||||
cmd('/execute if block 4 99 4 stone', expect_result=1),
|
||||
cmd('/execute if block 6 100 4 crafting_table', expect_result=1),
|
||||
cmd('/item replace entity @s hotbar.0 with map', expect_result=1),
|
||||
{'op': 'use_item', 'sequence': 1, 'yaw': 0, 'pitch': 0},
|
||||
cmd('/data get entity @s Inventory'),
|
||||
{'op': 'map_state', 'id': 0, 'expect_map': 'scale=0,locked=false,center=0,0'}]
|
||||
for window, (name, input_mode) in enumerate([('swap', 2), ('pickup', 0), ('quick_move', 1)], 1):
|
||||
if window > 1:
|
||||
ops += [cmd('/clear @s'),
|
||||
cmd('/item replace entity @s hotbar.0 with filled_map[map_id=0]', expect_result=1)]
|
||||
ops += [cmd('/item replace entity @s hotbar.1 with paper 8', expect_result=1),
|
||||
cmd('/data get entity @s Inventory'), {'op': 'use_block', 'pos': [6, 100, 4]},
|
||||
snapshot(name + ' open', window, {37: stack('filled_map', map_id=0), 38: stack('paper', 8)}),
|
||||
click(38)]
|
||||
for slot in [1, 2, 3, 4, 6, 7, 8, 9]:
|
||||
ops.append(click(slot, 1))
|
||||
ops += [click(37), click(5)]
|
||||
grid = {i: stack('paper') for i in [1, 2, 3, 4, 6, 7, 8, 9]}
|
||||
grid[5] = stack('filled_map', map_id=0)
|
||||
grid[0] = stack('filled_map', map_id=0, post=1)
|
||||
ops += [snapshot(name + ' preview', window, grid), click(0, 2 if input_mode == 2 else 0, input_mode)]
|
||||
output = stack('filled_map', map_id=window)
|
||||
slots = {39: output} if input_mode == 2 else ({45: output} if input_mode == 1 else {})
|
||||
ops += [snapshot(name + ' taken', window, slots, output if input_mode == 0 else None)]
|
||||
if input_mode == 0:
|
||||
ops.append(click(39))
|
||||
ops += [{'op': 'craft_menu_close'}, cmd('/data get entity @s Inventory'),
|
||||
cmd('/clear @s paper 0'),
|
||||
{'op': 'map_state', 'id': 0, 'expect_map': 'scale=0,locked=false,center=0,0'},
|
||||
{'op': 'map_state', 'id': window, 'expect_map': 'scale=1,locked=false,center=64,64'}]
|
||||
# A normal recipe through the same empty-hotbar SWAP callback.
|
||||
ops += [cmd('/clear @s'), cmd('/item replace entity @s hotbar.0 with oak_log', expect_result=1),
|
||||
{'op': 'use_block', 'pos': [6, 100, 4]}, click(37), click(1),
|
||||
snapshot('planks preview', 4, {0: stack('oak_planks', 4), 1: stack('oak_log')}),
|
||||
click(0, 2, 2), snapshot('planks taken', 4, {39: stack('oak_planks', 4)}),
|
||||
{'op': 'craft_menu_close'}, cmd('/data get entity @s Inventory'),
|
||||
cmd('/clear @s oak_planks 0', expect_result=4)]
|
||||
ops += setup('/execute store result score elapsed craft_map run time query gametime',
|
||||
'/scoreboard players operation elapsed craft_map -= start craft_map',
|
||||
'/execute store success score changed craft_map run data modify storage craft_map:owner uuid set from entity @s UUID')
|
||||
ops += [cmd('/scoreboard players get elapsed craft_map', expect_result=0),
|
||||
cmd('/scoreboard players get changed craft_map', expect_result=0),
|
||||
cmd('/execute if entity @s[type=player,gamemode=survival,nbt={Pos:[4.5d,100d,4.5d],Health:20f}]', expect_result=1),
|
||||
cmd('/save-all flush')]
|
||||
# /save-all is unavailable in the integrated command tree; keep its raw
|
||||
# negative diagnostic above, then exercise the actual saved-data flush.
|
||||
def saved(scale, center):
|
||||
return {'scale': scale, 'locked': False, 'center': center,
|
||||
'dimension': 'minecraft:overworld', 'byte_array_length': 16384}
|
||||
# Crafting QUICK_MOVE calls onCraftedBy on the transferred stack and
|
||||
# the pre-transfer copy: Java saves an additional unused scaled map.
|
||||
ops += [{'op': 'map_files', 'ids': [0, 1, 2, 3, 4, 5],
|
||||
'expect_observation': {'maps': [saved(0, [0, 0]),
|
||||
*[saved(1, [64, 64]) for _ in range(4)], None], 'last_id': 4}}]
|
||||
return ops
|
||||
@@ -0,0 +1,102 @@
|
||||
"""Five loaded furnace totals, observed at cumulative 0/1/4/5/6 real ticks."""
|
||||
CASES = [('missing', None, 0), ('zero', 0, 0), ('five', 5, 0),
|
||||
('normal', 200, 0), ('started', 5, 1)]
|
||||
|
||||
|
||||
def loaded_totals():
|
||||
ops = []
|
||||
def cmd(text, label, result=None):
|
||||
row = {'op': 'cmd', 'cmd': text, 'label': label}
|
||||
if result is not None:
|
||||
row['expect_result'] = result
|
||||
ops.append(row)
|
||||
cmd('/tick freeze', 'freeze')
|
||||
for rule, value in [('spawn_mobs', 'false'), ('advance_time', 'false'), ('random_tick_speed', '0')]:
|
||||
cmd(f'/gamerule {rule} {value}', 'rule-'+rule)
|
||||
cmd('/forceload add 0 0', 'force', 1)
|
||||
ops.append({'op': 'settle', 'required_chunks': [[0, 0]]})
|
||||
cmd('/fill 0 99 0 15 99 15 stone', 'floor', 256)
|
||||
cmd('/fill 0 100 0 15 105 15 air', 'air')
|
||||
cmd('/scoreboard objectives add h119 dummy', 'objective')
|
||||
cmd('/execute store result score start h119 run time query gametime', 'clock-start')
|
||||
for i, (name, total, spent) in enumerate(CASES):
|
||||
x = 2 + i * 2
|
||||
for y, block in [(99, 'stone'), (100, 'air'), (101, 'air')]:
|
||||
cmd(f'/execute if block {x} {y} 2 {block}', name+f'-setup-{y}', 1)
|
||||
extra = '' if total is None else f',cooking_total_time:{total}'
|
||||
tag = ('{lit_time_remaining:20,lit_total_time:20,'
|
||||
f'cooking_time_spent:{spent}{extra},'
|
||||
'Items:[{Slot:0b,id:"minecraft:raw_iron",count:3}]}')
|
||||
cmd(f'/setblock {x} 100 2 minecraft:furnace[lit=true]{tag}', name+'-load', 1)
|
||||
previous = 0
|
||||
for elapsed in [0, 1, 4, 5, 6]:
|
||||
if elapsed:
|
||||
cmd(f'/tick step {elapsed-previous}', f'{elapsed}-step', 1)
|
||||
ops.append({'op': 'tick', 'n': elapsed-previous})
|
||||
previous = elapsed
|
||||
cmd('/execute store result score now h119 run time query gametime', f'{elapsed}-clock-raw')
|
||||
cmd('/scoreboard players operation now h119 -= start h119', f'{elapsed}-clock-delta-op', elapsed)
|
||||
cmd('/scoreboard players get now h119', f'{elapsed}-clock-delta', elapsed)
|
||||
for i, (name, supplied, initial) in enumerate(CASES):
|
||||
x = 2 + i * 2
|
||||
at = f'{x} 100 2'
|
||||
key = f'{name}-{elapsed}'
|
||||
# These are hypotheses admitted independently by fresh Java before
|
||||
# either native capture; they are never used to update a furnace.
|
||||
first = 1 if supplied in (None, 0) else supplied-initial
|
||||
completed = elapsed >= first
|
||||
spent = elapsed-first if completed else elapsed+initial
|
||||
total = 200 if completed else (supplied or 0)
|
||||
cmd(f'/execute if loaded {at}', key+'-loaded', 1)
|
||||
cmd(f'/execute if block {x} 99 2 stone', key+'-floor', 1)
|
||||
cmd(f'/execute if block {at} minecraft:furnace[lit=true]', key+'-lit', 1)
|
||||
cmd(f'/execute if data block {at} {{id:"minecraft:furnace",x:{x},y:100,z:2}}', key+'-identity', 1)
|
||||
cmd(f'/data get block {at}', key+'-full-tag')
|
||||
cmd(f'/data get block {at} Items', key+'-all-slots')
|
||||
for field, expected in [('cooking_time_spent', spent), ('cooking_total_time', total),
|
||||
('lit_time_remaining', 20-elapsed), ('lit_total_time', 20)]:
|
||||
cmd(f'/data get block {at} {field}', key+'-'+field, expected)
|
||||
cmd(f'/execute if data block {at} {{Items:[{{Slot:0b,id:"minecraft:raw_iron",count:{2 if completed else 3}}}]}}', key+'-input', 1)
|
||||
cmd(f'/execute unless data block {at} Items[{{Slot:1b}}]', key+'-no-fuel', 1)
|
||||
if completed:
|
||||
cmd(f'/execute if data block {at} {{Items:[{{Slot:2b,id:"minecraft:iron_ingot",count:1}}]}}', key+'-output', 1)
|
||||
else:
|
||||
cmd(f'/execute unless data block {at} Items[{{Slot:2b}}]', key+'-no-output', 1)
|
||||
return ops
|
||||
|
||||
# Authorized before canonical paired captures. Full373 remains unchanged.
|
||||
DIAGNOSTIC_LABELS = frozenset(
|
||||
[f'{name}-{tick}-full-tag' for name, _, _ in CASES for tick in [0, 1, 4, 5, 6]]
|
||||
+ ['clock-start'] + [f'{tick}-clock-raw' for tick in [0, 1, 4, 5, 6]])
|
||||
|
||||
|
||||
def selected_totals(full=False):
|
||||
ops = loaded_totals()
|
||||
if not full:
|
||||
for op in ops:
|
||||
if op.get('label') in DIAGNOSTIC_LABELS:
|
||||
# Commands still execute, with actual trace bodies retained.
|
||||
# Full diagnostic compares their complete records separately.
|
||||
op.update(record=False, trace=True)
|
||||
return ops
|
||||
|
||||
|
||||
def recipes_used_totals():
|
||||
"""Separate25 reads; all original actions/guards still run and are traced."""
|
||||
out = []
|
||||
for op in loaded_totals():
|
||||
if op['op'] == 'cmd':
|
||||
op.update(record=False, trace=True)
|
||||
out.append(op)
|
||||
label = op.get('label', '')
|
||||
if label.endswith('-all-slots'):
|
||||
name, elapsed, *_ = label.split('-')
|
||||
elapsed = int(elapsed)
|
||||
i, (_, total, spent) = next((i, c) for i, c in enumerate(CASES) if c[0]==name)
|
||||
first = 1 if total in (None, 0) else total-spent
|
||||
value = '{"minecraft:iron_ingot_from_smelting_raw_iron": 1}' if elapsed >= first else '{}'
|
||||
x = 2+i*2
|
||||
out.append({'op': 'cmd', 'cmd': f'/data get block {x} 100 2 RecipesUsed',
|
||||
'label': f'{name}-{elapsed}-recipes-used', 'trace': True,
|
||||
'expect_result': int(elapsed >= first), 'expected_text': f'{x}, 100, 2 has the following block data: {value}'})
|
||||
return out
|
||||
+230
-66
@@ -13215,15 +13215,9 @@ def prepare_recipes_hmod(*games):
|
||||
(settings / 'give').write_text('false')
|
||||
|
||||
|
||||
def check_recipes_hmod(host, guest, out):
|
||||
"""Recipes' real panel -> normal server menu -> custom output -> save/rejoin (#9647).
|
||||
|
||||
Opt in with --check recipes-hmod and --bin-dir pointing at a game compiled with the official
|
||||
native Recipes mod. The fixture is ours; no Java mod, copied assets or synthetic game replies.
|
||||
"""
|
||||
def exercise_recipes_hmod(game, out, mode, supplied):
|
||||
"""Use the real Recipes panel and the authoritative crafting table."""
|
||||
item = 'minecraft:echo_shard'
|
||||
recipe = 'recipe_pilot:flint_to_echo_shard'
|
||||
seen = {}
|
||||
|
||||
def widget(game, predicate):
|
||||
matches = [w for w in game.raw('ui')['widgets'] if predicate(w)]
|
||||
@@ -13256,70 +13250,89 @@ def check_recipes_hmod(host, guest, out):
|
||||
def contents(game):
|
||||
return {'flint': server_count(game, 'flint'), 'echo_shard': server_count(game, item)}
|
||||
|
||||
for game, supplied in ((host, 4), (guest, 5)):
|
||||
game('command', command='clear @s')
|
||||
server_reply(game, 'recipe take @s *', 'recipe')
|
||||
game('command', command=f'gamemode {mode}')
|
||||
game.wait(10)
|
||||
game.until(lambda: game('state')['gamemode'], lambda current: current == mode)
|
||||
game('command', command='clear @s')
|
||||
game('command', command='give @s minecraft:dirt 1')
|
||||
table = None
|
||||
if mode == 'creative':
|
||||
x, y, z = (math.floor(v) for v in pos_of(game))
|
||||
table = (x, y, z - 2)
|
||||
game('command', command=f'setblock {table[0]} {table[1]} {table[2]} minecraft:crafting_table')
|
||||
game.wait(10)
|
||||
game.raw('interact', *table)
|
||||
game.raw('wait-for', 'screen', 'inventory', '--timeout', 10)
|
||||
assert game('screen').get('craft_size') == 3
|
||||
else:
|
||||
game.raw('key', 'e', 'tap')
|
||||
game.raw('wait-for', 'screen', 'inventory', '--timeout', 10)
|
||||
assert game('screen').get('craft_size') == (3 if table else 2)
|
||||
|
||||
# The output exists in vanilla; its flint recipe is original .hmod content.
|
||||
search(game, 'recipe_pilot:no_such_item')
|
||||
assert not any(w.get('label') == 'Echo Shard' for w in game.raw('ui')['widgets'])
|
||||
search(game, item)
|
||||
game.shot(out, 'recipes-hmod-search')
|
||||
unknown = page(game)
|
||||
assert unknown['enabled'] is False, f'{game.name}: unknown recipe can be placed: {unknown}'
|
||||
game.shot(out, 'recipes-hmod-unknown')
|
||||
assert not any(i for i, _ in grid_of(game, 100, 109).values())
|
||||
assert not result_of(game)[0]
|
||||
# Put an unrelated item in the grid so a refused transfer cannot pass by
|
||||
# merely clearing an already empty grid.
|
||||
game.raw('click-slot', hotbar_slot(game, 'minecraft:dirt'))
|
||||
game.raw('click-slot', 100)
|
||||
game.wait(10)
|
||||
before = grid_of(game, 100, 109)
|
||||
assert before[100] == ('minecraft:dirt', 1), before
|
||||
assert not result_of(game)[0], f'{mode}: control ingredient must have no output'
|
||||
server_reply(game, 'recipe take @s *', 'recipe')
|
||||
game.until(lambda: game('screen')['recipe_book']['known'], lambda count: count == 0)
|
||||
|
||||
server_reply(game, f'recipe give @s {recipe}', 'recipe')
|
||||
game.wait(10)
|
||||
back = widget(game, lambda w: w.get('label') == 'Back')
|
||||
game.raw('click', back['id'])
|
||||
game.wait(5)
|
||||
known = page(game)
|
||||
assert known['enabled'] is True, f'{game.name}: learned custom recipe unavailable: {known}'
|
||||
game.raw('click', known['id'])
|
||||
game.wait(10)
|
||||
missing = contents(game)
|
||||
assert missing == {'flint': 0, 'echo_shard': 0}, missing
|
||||
assert not any(i for i, _ in grid_of(game, 100, 109).values())
|
||||
assert not result_of(game)[0], f'{game.name}: missing ingredients fabricated a result'
|
||||
game.shot(out, 'recipes-hmod-missing')
|
||||
search(game, 'recipe_pilot:no_such_item')
|
||||
assert not any(w.get('label') == 'Echo Shard' for w in game.raw('ui')['widgets'])
|
||||
search(game, item)
|
||||
transfer = page(game)
|
||||
assert transfer['enabled'], f'{mode}: + must be available before unlock'
|
||||
game.raw('click', transfer['id'])
|
||||
game.until(lambda: game.raw('ui')['widgets'],
|
||||
lambda widgets: any(w.get('label') == 'Missing Items' for w in widgets))
|
||||
assert grid_of(game, 100, 109) == before, f'{mode}: missing ingredients changed the grid'
|
||||
assert not result_of(game)[0], f'{mode}: missing ingredients fabricated a result'
|
||||
missing = contents(game)
|
||||
assert missing == {'flint': 0, 'echo_shard': 0}, missing
|
||||
game.shot(out, f'{mode}-missing')
|
||||
|
||||
# Supply the input through the server, then use only the panel's normal + action.
|
||||
game('command', command=f'give @s minecraft:flint {supplied}')
|
||||
game.wait(10)
|
||||
known = widget(game, lambda w: (w.get('label') or '').startswith('+ '))
|
||||
game.raw('click', known['id'])
|
||||
game.until(lambda: result_of(game), lambda r: bool(r[0]))
|
||||
grid = [s for s in grid_of(game, 100, 109).values() if s[0]]
|
||||
result = result_of(game)
|
||||
assert grid == [('minecraft:flint', 1)], f'{game.name}: filled grid {grid}'
|
||||
# Independent expected yield: changing the fixture to yield one must fail here.
|
||||
assert result == (item, 2), f'{game.name}: custom result {result}'
|
||||
game.shot(out, 'recipes-hmod-filled')
|
||||
game.raw('click-slot', 200, 'shift')
|
||||
game.wait(10)
|
||||
assert not result_of(game)[0], f'{game.name}: consumed input left another result'
|
||||
back_to_game(game)
|
||||
crafted = contents(game)
|
||||
assert crafted == {'flint': supplied - 1, 'echo_shard': 2}, crafted
|
||||
seen[game.name] = {'missing': missing, 'grid': grid, 'result': result, 'crafted': crafted}
|
||||
game('command', command=f'give @s minecraft:flint {supplied}')
|
||||
server_reply(game, 'recipe take @s *', 'recipe')
|
||||
game.until(lambda: game('screen')['recipe_book']['known'], lambda count: count == 0)
|
||||
transfer = widget(game, lambda w: (w.get('label') or '').startswith('+ '))
|
||||
game.raw('click', transfer['id'])
|
||||
game.until(lambda: result_of(game), lambda r: bool(r[0]))
|
||||
grid = [s for s in grid_of(game, 100, 109).values() if s[0]]
|
||||
result = result_of(game)
|
||||
assert grid == [('minecraft:flint', 1)], f'{mode}: filled grid {grid}'
|
||||
assert result == (item, 2), f'{mode}: custom result {result}'
|
||||
assert game('screen')['recipe_book']['known'] == 0, f'{mode}: transfer learned a recipe'
|
||||
assert server_count(game, 'dirt') == 1, f'{mode}: displaced ingredient was lost'
|
||||
assert server_count(game, 'flint') == supplied - 1, f'{mode}: transfer fabricated an input'
|
||||
assert not any(w.get('label') == 'Missing Items' for w in game.raw('ui')['widgets'])
|
||||
game.wait(140)
|
||||
game.shot(out, f'{mode}-filled')
|
||||
game.raw('click-slot', 200, 'shift')
|
||||
game.wait(10)
|
||||
assert not result_of(game)[0], f'{mode}: consumed input left another result'
|
||||
back_to_game(game)
|
||||
crafted = contents(game)
|
||||
assert crafted == {'flint': supplied - 1, 'echo_shard': 2}, crafted
|
||||
if table:
|
||||
game('command', command=f'setblock {table[0]} {table[1]} {table[2]} minecraft:air')
|
||||
return {'mode': mode, 'missing': missing, 'warning': 'Missing Items',
|
||||
'grid_unchanged_on_failure': True, 'known_before_and_after_fill': 0,
|
||||
'grid': grid, 'result': result, 'crafted': crafted}
|
||||
|
||||
|
||||
def check_recipes_hmod(host, guest, out):
|
||||
"""Exercise Survival and Creative through the real host/guest server."""
|
||||
seen = {
|
||||
host.name: exercise_recipes_hmod(host, out, 'survival', 4),
|
||||
guest.name: exercise_recipes_hmod(guest, out, 'creative', 5),
|
||||
}
|
||||
save_and_reopen(host, guest)
|
||||
for game in (host, guest):
|
||||
persisted = contents(game)
|
||||
persisted = {'flint': server_count(game, 'flint'), 'echo_shard': server_count(game, 'echo_shard')}
|
||||
assert persisted == seen[game.name]['crafted'], f'{game.name}: after rejoin {persisted}'
|
||||
seen[game.name]['rejoined'] = persisted
|
||||
game.shot(out, 'recipes-hmod-rejoined')
|
||||
game.shot(out, f"{seen[game.name]['mode']}-rejoined")
|
||||
(out / 'recipes-hmod.json').write_text(json.dumps(seen, indent=2) + '\n')
|
||||
return seen
|
||||
|
||||
@@ -13481,6 +13494,150 @@ def check_entity_die(host, guest, out):
|
||||
|
||||
CHECKS += ['entity-die']
|
||||
CHECK_FNS['entity-die'] = check_entity_die
|
||||
|
||||
|
||||
def check_mob_thorns(host, guest, out):
|
||||
"""A mob's Thorns answers the player who hits it (#9954): `doPostAttackEffectsWithItemSource` runs the victim's
|
||||
equipment on every hit, so a husk in a Thorns 255 chestplate (the 15% per level always rolls) hurts the host's
|
||||
sword hit, the guest's empty-handed punch and the guest's arrow, each by 1 to 5. The husk picks the chestplate
|
||||
up (`Mob.pickUpItem`), as a mob finds its gear in play: `/item replace` on a mob keeps no enchantments yet
|
||||
(#7985)."""
|
||||
hx, hy, hz = (int(v // 1) for v in pos_of(host))
|
||||
host('command', command='gamerule natural_health_regeneration false')
|
||||
seen = {}
|
||||
try:
|
||||
for name, game, hand in (('host sword', host, 'minecraft:iron_sword'), ('guest punch', guest, 'minecraft:air'),
|
||||
('guest arrow', guest, 'minecraft:bow')):
|
||||
x, z = hx + 8, hz + (-6 if game is host else 6)
|
||||
husk = (x + (6 if hand == 'minecraft:bow' else 2), hy, z)
|
||||
host('command', command=f'fill {x - 1} {hy - 1} {z - 2} {x + 8} {hy - 1} {z + 2} minecraft:stone')
|
||||
host('command', command=f'fill {x - 1} {hy} {z - 2} {x + 8} {hy + 3} {z + 2} minecraft:air')
|
||||
host('command', command=f'summon minecraft:husk {husk[0] + 0.5} {hy} {z + 0.5} '
|
||||
'{NoAI:1b,PersistenceRequired:1b,CanPickUpLoot:1b,Tags:["thorny"],Health:200f,'
|
||||
'attributes:[{id:"minecraft:max_health",base:200}]}')
|
||||
host('command', command=f'summon minecraft:item {husk[0] + 0.5} {hy} {z + 0.5} '
|
||||
'{PickupDelay:0s,Item:{id:"minecraft:diamond_chestplate",count:1,'
|
||||
'components:{"minecraft:enchantments":{"minecraft:thorns":255}}}}')
|
||||
host.wait(20)
|
||||
game('command', command='gamemode survival')
|
||||
game('command', command=f'item replace entity @s weapon.mainhand with {hand}')
|
||||
if hand == 'minecraft:bow':
|
||||
game('command', command='give @s minecraft:arrow 4')
|
||||
game('command', command=f'tp @s {x + 0.5} {hy} {z + 0.5} -90 0')
|
||||
game('command', command='effect give @s minecraft:instant_health 1 5 true')
|
||||
game.wait(30)
|
||||
target = game.raw('entities', 10, 'minecraft:husk')['entities'][0]['id']
|
||||
ground = len(game.raw('entities', 10, 'minecraft:item')['entities'])
|
||||
health = (lambda: host('state')['player']['health']) if game is host else (lambda: remote(guest)['health'])
|
||||
before = health()
|
||||
game.raw('look-at-entity', target)
|
||||
if hand == 'minecraft:bow':
|
||||
game.raw('use-item', '--hold', 1.3)
|
||||
game.wait(40)
|
||||
else:
|
||||
game.raw('attack')
|
||||
game.wait(10)
|
||||
seen[name] = (before, health(), f'{ground} items on the ground')
|
||||
game.shot(out, 'mob-thorns-' + name.replace(' ', '-'))
|
||||
host('command', command='kill @e[tag=thorny]')
|
||||
host('command', command=f'fill {x - 1} {hy} {z - 2} {x + 8} {hy + 3} {z + 2} minecraft:air')
|
||||
finally:
|
||||
host('command', command='gamerule natural_health_regeneration true')
|
||||
host('command', command='kill @e[tag=thorny]')
|
||||
for game in (host, guest):
|
||||
back_to_game(game)
|
||||
game('command', command='clear @s')
|
||||
face_each_other(host, guest)
|
||||
for name, (before, after, _) in seen.items():
|
||||
assert before == 20.0, f'{name}: not at full health first: {seen}'
|
||||
assert 15.0 <= after < 20.0, f'{name}: the husk\'s thorns did not hurt (or hurt too much): {seen}'
|
||||
return seen
|
||||
|
||||
|
||||
CHECKS += ['mob-thorns']
|
||||
CHECK_FNS['mob-thorns'] = check_mob_thorns
|
||||
|
||||
|
||||
def check_command_block_chat(host, guest, out):
|
||||
"""#9377: SayCommand broadcasts to real clients, independently of TrackOutput.
|
||||
|
||||
Java 26.3 SayCommand returns 1 and PlayerList.broadcastChatMessage sends
|
||||
the line to every player. Conditional chain execution is a separate control.
|
||||
"""
|
||||
objective = 'say_9377'
|
||||
positions = [(g, pos_of(g)) for g in (host, guest)]
|
||||
rows = []
|
||||
|
||||
def command(line):
|
||||
return '\n'.join(host('command', command=line)['chat'])
|
||||
|
||||
def gametime():
|
||||
line = command('time query gametime')
|
||||
found = re.search(r'is (-?\d+)', line)
|
||||
assert found, f'prerequisite: no gametime reply: {line}'
|
||||
return int(found.group(1))
|
||||
|
||||
try:
|
||||
for line in ('tick freeze', 'gamerule command_blocks_work true',
|
||||
'gamerule spawn_mobs false', 'forceload add 0 0',
|
||||
'fill 0 110 4 1 110 6 stone',
|
||||
f'scoreboard objectives add {objective} dummy'):
|
||||
command(line)
|
||||
for actor, _ in positions:
|
||||
x = 0.5 if actor is host else 1.5
|
||||
command(f"tp {getattr(actor, 'player', GUEST_NAME)} {x} 112 5.5")
|
||||
host.wait(5)
|
||||
for sender in (host, guest):
|
||||
marker = f'SAY_CONTROL_{sender.name}_{time.time_ns()}'
|
||||
sender.raw('chat', f'/say {marker}')
|
||||
for actor, _ in positions:
|
||||
actor.until(lambda: actor.raw('messages', 30)['chat'],
|
||||
lambda lines: any(marker in line for line in lines), ticks=30)
|
||||
for track in (True, False):
|
||||
marker = f'SAY_BLOCK_{time.time_ns()}'
|
||||
source_name = 'Announcer9377' if track else '@'
|
||||
custom_name = ',CustomName:{text:"Announcer9377"}' if track else ''
|
||||
for line in ('setblock 0 110 2 air', 'setblock 1 110 2 air', 'setblock 2 110 2 air',
|
||||
f'scoreboard players set chain {objective} 0',
|
||||
'setblock 2 110 2 chain_command_block[facing=east,conditional=true]'
|
||||
f'{{Command:"scoreboard players set chain {objective} 1",auto:1b}}',
|
||||
'setblock 1 110 2 command_block[facing=east]'
|
||||
f'{{Command:"say {marker}",auto:0b,TrackOutput:{int(track)}b{custom_name}}}',
|
||||
'setblock 0 110 2 redstone_block'):
|
||||
command(line)
|
||||
start = gametime()
|
||||
command('tick step 2')
|
||||
host.until(gametime, lambda tick: tick >= start + 2, ticks=60)
|
||||
assert gametime() == start + 2, 'prerequisite: extra world ticks'
|
||||
host.wait(5)
|
||||
for actor, _ in positions:
|
||||
actor.raw('look-at', 1.5, 110.5, 2.5)
|
||||
actor.shot(out, f'command-block-chat-{int(track)}')
|
||||
count = command('data get block 1 110 2 SuccessCount')
|
||||
chain = command(f'scoreboard players get chain {objective}')
|
||||
messages = {g.name: g.raw('messages', 30)['chat'] for g, _ in positions}
|
||||
row = dict(track_output=track, marker=marker, success=count, chain=chain,
|
||||
block=command('data get block 1 110 2'), messages=messages)
|
||||
rows.append(row)
|
||||
(out / 'command-block-chat.json').write_text(json.dumps(rows, indent=2), encoding='utf-8')
|
||||
assert re.search(r'following block data: 1\b', count), count
|
||||
assert f'chain has 1 [{objective}]' in chain, chain
|
||||
for actor, _ in positions:
|
||||
assert messages[actor.name].count(f'[{source_name}] {marker}') == 1, row
|
||||
return rows
|
||||
finally:
|
||||
for line in ('setblock 0 110 2 air', 'setblock 1 110 2 air', 'setblock 2 110 2 air',
|
||||
f'scoreboard objectives remove {objective}', 'forceload remove 0 0',
|
||||
'fill 0 110 4 1 110 6 air', 'tick unfreeze'):
|
||||
command(line)
|
||||
for actor, position in positions:
|
||||
command(f"tp {getattr(actor, 'player', GUEST_NAME)} " + ' '.join(map(str, position)))
|
||||
|
||||
|
||||
CHECKS += ['command-block-chat']
|
||||
CHECK_FNS['command-block-chat'] = check_command_block_chat
|
||||
|
||||
|
||||
def check_death_credit(host, guest, out):
|
||||
"""ServerPlayer.die for each player (#9955): the death is heard (`gameEvent(ENTITY_DIE)`: a sensor
|
||||
3 blocks away takes frequency 15, which beats the killing blow's `entity_damage`, 7, from the same
|
||||
@@ -13567,26 +13724,33 @@ CHECK_FNS['death-credit'] = check_death_credit
|
||||
def set_legacy_rules(game, on):
|
||||
"""The PvP page's "1.8 combat in worlds you host" switch, as a player flips it."""
|
||||
game('options', page='pvp')
|
||||
for _ in range(40):
|
||||
# The switch sits at the end of the page: wheel down over the list until it stops moving, so the row is
|
||||
# whole inside the list (a click on a row half under the list's edge lands outside it, as in vanilla).
|
||||
switch = None
|
||||
for _ in range(60):
|
||||
ui = game.raw('ui')
|
||||
switch = next((w for w in ui['widgets']
|
||||
if w.get('label', '').startswith('1.8 combat in worlds you host')), None)
|
||||
if switch:
|
||||
found = next((w for w in ui['widgets']
|
||||
if w.get('label', '').startswith('1.8 combat in worlds you host')), None)
|
||||
if found and switch and found['bounds'] == switch['bounds']:
|
||||
break
|
||||
# The switch sits at the end of the page: wheel down over the list.
|
||||
switch = found
|
||||
width, height = ui['gui_size']
|
||||
game.raw('scroll-at', f'{width / 2:.1f}', f'{height / 2:.1f}', -3)
|
||||
assert switch, 'the PvP page has no "1.8 combat in worlds you host" switch'
|
||||
if switch['label'].endswith('On') != on:
|
||||
game('click', id=switch['id'])
|
||||
now = next(w for w in game.raw('ui')['widgets'] if w.get('id') == switch['id'])
|
||||
assert now['label'].endswith('On') == on, f'the click did not flip the switch: {now}'
|
||||
game('close')
|
||||
game.wait(5)
|
||||
|
||||
|
||||
def check_legacy_pvp(host, guest, out):
|
||||
"""The host's legacy combat rules (crates/horizon-client/src/legacy_combat.rs) on a guest's diamond axe hits:
|
||||
one at full charge, and one right after a hotbar swap, which empties the charge on the server
|
||||
(ServerPlayer's attack item change resets attackStrengthTicker).
|
||||
one at full charge, and one right after a hotbar swap and back, which empties the charge on the server
|
||||
(Player.tick resets attackStrengthTicker when the main hand's item changes). The stick is held a few ticks:
|
||||
the client sends a slot change from its next tick (MultiPlayerGameMode.tick), so a swap and back within one
|
||||
tick sends nothing, as vanilla's.
|
||||
|
||||
1.8.9, EntityPlayer.attackTargetEntityWithCurrentItem: the hit deals the attack damage attribute, which is
|
||||
EntityPlayer.applyEntityAttributes' base 1 plus the axe's weapon modifier (ItemAxe's 3.0F +
|
||||
@@ -13620,7 +13784,7 @@ def check_legacy_pvp(host, guest, out):
|
||||
before = health()
|
||||
assert before == 20.0, f'the host was not healed: {before}'
|
||||
# One ctl process keeps the swap and the hit together.
|
||||
guest.script(['select 2', 'select 1', 'attack'] if swap else ['attack'])
|
||||
guest.script(['select 2', 'wait 3', 'select 1', 'attack'] if swap else ['attack'])
|
||||
guest.wait(10)
|
||||
return before - health()
|
||||
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
"""Real connected-owner trade extractions and crafted statistics, without world ticks."""
|
||||
from command_exec_corpus import cmd
|
||||
|
||||
OWNER = [-1608632253, -450807400, -1786055591, -2146546415]
|
||||
|
||||
|
||||
def cases():
|
||||
ops = []
|
||||
def c(text, expected=None, record=True):
|
||||
ops.append(cmd(text, expect_result=expected, record=record))
|
||||
def action(action, **kw):
|
||||
ops.append({'op': 'merchant_action', 'action': action, **kw})
|
||||
c('/tick freeze')
|
||||
c('/gamerule minecraft:spawn_mobs false')
|
||||
c('/forceload add 0 0 31 31')
|
||||
c('/tp @s 8.5 200 8.5 0 0')
|
||||
ops.append({'op': 'wait_chunks', 'chunks': [[0, 0], [0, 1], [1, 0], [1, 1]]})
|
||||
ops.append({'op': 'settle', 'required_chunks': [[0, 0], [0, 1], [1, 0], [1, 1]]})
|
||||
for x, z in [(0, 0), (0, 16), (16, 0), (16, 16)]:
|
||||
c(f'/execute if loaded {x} 200 {z}', 1)
|
||||
c('/fill 0 199 0 31 199 31 stone', 1024)
|
||||
c('/fill 0 200 0 31 204 31 air')
|
||||
c('/gamemode survival @s')
|
||||
for name, criterion in [('audit', 'dummy'), ('books', 'minecraft.crafted:minecraft.bookshelf'),
|
||||
('glass', 'minecraft.crafted:minecraft.glass'),
|
||||
('trades', 'minecraft.custom:minecraft.traded_with_villager')]:
|
||||
c(f'/scoreboard objectives add {name} {criterion}')
|
||||
for objective in ['books', 'glass', 'trades']:
|
||||
c(f'/scoreboard players set @s {objective} 0', 0)
|
||||
c('/data get entity @s UUID', 4)
|
||||
c('/execute if entity @s[nbt={UUID:[I;' + ','.join(map(str, OWNER)) + '],Health:20f}]', 1)
|
||||
c('/execute store result score #base audit run time query gametime', record=False)
|
||||
def clock():
|
||||
c('/execute store result score #now audit run time query gametime', record=False)
|
||||
c('/scoreboard players operation #now audit -= #base audit')
|
||||
c('/scoreboard players get #now audit', 0)
|
||||
def stats(books, glass, trades):
|
||||
for name, count in [('books', books), ('glass', glass), ('trades', trades)]:
|
||||
c(f'/scoreboard players get @s {name}', count)
|
||||
window = 0
|
||||
# The last two cases admit a real menu/offer but never an accepted take.
|
||||
for n, (item, output, payment, take, count) in enumerate([
|
||||
('bookshelf', 1, 4, 'single', 1), ('glass', 3, 8, 'quick', 2),
|
||||
('bookshelf', 1, 4, 'preview', 0), ('bookshelf', 1, 3, 'rejected', 0),
|
||||
]):
|
||||
uuid = f'00000065-0000-0001-0000-{n+1:012x}'
|
||||
tag = f'trade_crafted_{n}'
|
||||
x = 6.5 + 4*n
|
||||
c('/clear @s')
|
||||
c(f'/tp @s {x} 200 3.5 0 0')
|
||||
c(f'/execute if entity @s[gamemode=survival,nbt={{Health:20f,Pos:[{x}d,200d,3.5d]}}]', 1)
|
||||
c(f'/item replace entity @s hotbar.0 with emerald {payment}', 1)
|
||||
c(f'/clear @s emerald 0', payment)
|
||||
nbt = ('{UUID:[I;101,1,0,' + str(n+1) + f'],Tags:["{tag}"],CustomName:"Trader{n}",'
|
||||
'NoAI:1b,NoGravity:1b,Silent:1b,PersistenceRequired:1b,Health:20f,'
|
||||
'VillagerData:{profession:"minecraft:cleric",level:2,type:"minecraft:plains"},Xp:20,'
|
||||
'Offers:{Recipes:[{buy:{id:"minecraft:emerald",count:4},sell:{id:"minecraft:' + item +
|
||||
f'",count:{output}' + '},uses:0,maxUses:100,rewardExp:0b,xp:1,priceMultiplier:0f,demand:0,specialPrice:0}]}}')
|
||||
c(f'/summon villager {x} 200 5.5 {nbt}')
|
||||
c(f'/execute if entity @e[type=villager,tag={tag},nbt={{UUID:[I;101,1,0,{n+1}],Health:20f,Pos:[{x}d,200d,5.5d],NoAI:1b,NoGravity:1b,Silent:1b,PersistenceRequired:1b}}]', 1)
|
||||
c(f'/data get entity {uuid} UUID', 4)
|
||||
c(f'/data get entity {uuid} Pos', 3)
|
||||
c(f'/execute if block {int(x)} 199 5 stone', 1)
|
||||
clock()
|
||||
action('open', uuid=uuid)
|
||||
window += 1
|
||||
def state(label, uses, pay, result, carried=None):
|
||||
ops.append({'op': 'merchant_state', 'label': f'{n}-{label}', 'expect_merchant': {
|
||||
'window': window, 'merchant': [101, 1, 0, n+1], 'player': OWNER,
|
||||
'slots': [['minecraft:emerald', pay] if pay else None, None,
|
||||
[f'minecraft:{item}', output] if result else None],
|
||||
'carried': carried, 'xp': 20+uses,
|
||||
'offers': [{'uses': uses, 'special': 0, 'cost': 4}]}})
|
||||
state('open', 0, 0, False)
|
||||
action('select', index=0)
|
||||
state('selected', 0, payment, payment >= 4)
|
||||
stats(1 if n else 0, 6 if n >= 2 else 0, 3 if n >= 2 else (1 if n else 0))
|
||||
if take != 'preview':
|
||||
action('click', slot=2, input=1 if take == 'quick' else 0)
|
||||
if take == 'single':
|
||||
state('taken', 1, 0, False, ['minecraft:bookshelf', 1])
|
||||
action('click', slot=31)
|
||||
state('stored', 1, 0, False)
|
||||
else:
|
||||
state('after', count, payment-4*count, take == 'preview')
|
||||
c(f'/clear @s {item} 0', output*count if count else None)
|
||||
if not count:
|
||||
ops[-1]['expect_callback'] = [False, 0]
|
||||
remaining = payment-4*count
|
||||
c('/clear @s emerald 0')
|
||||
ops[-1]['expect_callback'] = [False, 0]
|
||||
stats(1, 6 if n else 0, 3 if n else 1)
|
||||
clock()
|
||||
action('close')
|
||||
c('/clear @s emerald 0', remaining if remaining else None)
|
||||
if not remaining:
|
||||
ops[-1]['expect_callback'] = [False, 0]
|
||||
ops.append({'op':'merchant_saved_stats','label':str(n), 'expect_observation':{
|
||||
'uuid':OWNER,'books':1,'glass':6 if n else 0,'trades':3 if n else 1}})
|
||||
# Closing and a second read must not count the already extracted result again.
|
||||
stats(1, 6 if n else 0, 3 if n else 1)
|
||||
clock()
|
||||
return ops
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"name": "block_place",
|
||||
"target": "horizon_client::game::native_hooks::Game<'_>::native_block_place",
|
||||
"neoforge": "BlockEvent.EntityPlaceEvent (Bukkit's BlockPlaceEvent)",
|
||||
"side": "server",
|
||||
"cancellable": true,
|
||||
"mod_api": {
|
||||
"contract": "cancel",
|
||||
"out": "cancel"
|
||||
}
|
||||
}
|
||||
],
|
||||
"rust_summary": "a guest about to place a block item, with the position and state, before anything is set, cancellable (`native_block_place`).",
|
||||
"doc_summary": "`native_block_place(player, name, stack, pos, state, cancel)`: a guest is about to place `stack` as `state` at `pos`, heard where `BlockItem.place` has found the position and state and checked survival, before anything is set (Bukkit's `BlockPlaceEvent`, NeoForge's `BlockEvent.EntityPlaceEvent`). Set `cancel` and nothing is placed or used; the client sees the blocks and its inventory as they are and `native_place_block` hears nothing. Bundles that use it need `\"hooks\": @API:block_place@`."
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"name": "block_redstone",
|
||||
"target": "horizon_client::game::native_hooks::GameLevel<'_>::native_block_redstone",
|
||||
"neoforge": "none (NeoForge has no such event; Bukkit's BlockRedstoneEvent)",
|
||||
"side": "server",
|
||||
"mod_api": {
|
||||
"contract": "listen"
|
||||
}
|
||||
}
|
||||
],
|
||||
"rust_summary": "a button's, lever's or pressure plate's signal change, which a mod may change or hold (`native_block_redstone`).",
|
||||
"doc_summary": "`native_block_redstone(dimension, pos, old, new)`: a button, lever or pressure plate about to change its signal from `old` to `new` (0 to 15), as Bukkit's `BlockRedstoneEvent`; change `new` and the component takes it, one equal to `old` keeps it as it is (a held button or plate is asked again). Bundles need \"hooks\": @API:block_redstone@."
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"hooks": [],
|
||||
"helpers": [
|
||||
{
|
||||
"name": "boss_bar",
|
||||
"target": "horizon_client::game::native_hooks::Game<'_>::native_boss_bar"
|
||||
}
|
||||
],
|
||||
"rust_summary": "boss bars for one player (`native_boss_bar`, `NativeBossBar`), as Bukkit's `BossBar.addPlayer`.",
|
||||
"doc_summary": "`native_boss_bar(uuid, id, bar)` (`host.boss_bar`) shows one player a `NativeBossBar` (name, progress, colour, overlay, flags) under a mod's id, changes it, or takes it away (None), as Bukkit's `BossBar.addPlayer`/`removePlayer`; kept by UUID while they are off. Bundles need \"hooks\": @API:boss_bar@."
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"hooks": [],
|
||||
"helpers": [
|
||||
{"name": "chat_input", "target": "horizon_client::game::chat::Game<'_>::native_chat_input"},
|
||||
{"name": "set_chat_input", "target": "horizon_client::game::chat::Game<'_>::native_set_chat_input"}
|
||||
],
|
||||
"rust_summary": "a client mod reads and writes the open chat box (`native_chat_input`, `native_set_chat_input`).",
|
||||
"doc_summary": "`native_chat_input()` gives the open chat box's text and cursor, `native_set_chat_input(text, cursor)` replaces them as an edit (completions refresh); an error while the chat is closed. Bundles need \"hooks\": @API:chat_input@.",
|
||||
"comments": ["crates/horizon-client/src/game/chat.rs"]
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"name": "chat_suggestion_applied",
|
||||
"target": "horizon_client::game::native_hooks::Game<'_>::native_chat_suggestion_applied",
|
||||
"side": "client",
|
||||
"cancellable": false,
|
||||
"mod_api": {
|
||||
"contract": "listen"
|
||||
}
|
||||
}
|
||||
],
|
||||
"rust_summary": "notify native mods when a chat suggestion is applied.",
|
||||
"doc_summary": "`native_chat_suggestion_applied(original, start, end, suggestion, input)` reports a chat completion after Tab or a click. The range is in UTF-16 units of the list's original input, including when cycling; input is the resulting length-limited field value. Client-only, read-only. Bundles need \"hooks\": @API:chat_suggestion_applied@."
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"name": "chunk_generate",
|
||||
"target": "horizon_world::worldgen::native_generate::native_chunk_generate",
|
||||
"neoforge": "none (NeoForge has no such event, a mod registers its own ChunkGenerator; Bukkit's ChunkGenerator.generateNoise)",
|
||||
"side": "server",
|
||||
"mod_api": {
|
||||
"contract": "listen"
|
||||
}
|
||||
}
|
||||
],
|
||||
"rust_summary": "a chunk's terrain built by a mod before vanilla's noise (`native_chunk_generate`), with vanilla's surface, carvers, structures and features on request.",
|
||||
"doc_summary": "`native_chunk_generate(dimension, chunk, seed, flat, chunk_out, generate)` is asked for every chunk a level generates, after its biomes and structure starts and before the noise (Bukkit's `ChunkGenerator.generateNoise`): a mod sets the blocks with `chunk_out.set_block` and answers `NativeGenerate::Replaced { surface, carvers, structures, decorations }`, and vanilla adds only what the flags ask for; `set_biomes` and `clear_structures` work with either answer. It runs on the generation workers, several chunks at once. Bundles need \"hooks\": @API:chunk_generate@.",
|
||||
"comments": [
|
||||
"crates/horizon-world/src/worldgen/native_generate.rs"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"hooks": [],
|
||||
"helpers": [
|
||||
{"name": "chunk_snapshot", "target": "horizon_client::game::native_chunk_snapshot::Game<'_>::native_chunk_snapshot"},
|
||||
{"name": "block_map_color", "target": "horizon_client::game::native_chunk_snapshot::Game<'_>::native_block_map_color"},
|
||||
{"name": "biome_name", "target": "horizon_client::game::native_chunk_snapshot::Game<'_>::native_biome_name"},
|
||||
{"name": "biome_tint", "target": "horizon_client::game::native_chunk_snapshot::Game<'_>::native_biome_tint"}
|
||||
],
|
||||
"rust_summary": "read-only chunk snapshots (`native_chunk_snapshot`) with block map colours and biome tints (`native_block_map_color`, `native_biome_name`, `native_biome_tint`).",
|
||||
"doc_summary": "`native_chunk_snapshot(dimension, chunk)`, a read-only `NativeChunkSnapshot` of a loaded chunk (block states, surface heights, biomes) that can be read on another thread, with `native_block_map_color(state)`, `native_biome_name(biome)` and `native_biome_tint(biome, x, z)` to draw it. Bundles need \"hooks\": @API:chunk_snapshot@.",
|
||||
"comments": [
|
||||
"crates/horizon-client/src/game/native_chunk_snapshot.rs"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"hooks": [],
|
||||
"helpers": [
|
||||
{"name": "force_chunk_in", "target": "horizon_client::game::native_chunk_tickets::Game<'_>::native_force_chunk_in"}
|
||||
],
|
||||
"rust_summary": "chunk tickets in a level nobody is in (`native_force_chunk_in`).",
|
||||
"doc_summary": "`native_force_chunk_in(owner, dimension, chunk, hold)`, `native_force_chunk` for a named level, which opens and ticks a level not open yet, so a mod can pre-generate the Nether and the End with nobody there. Bundles need \"hooks\": @API:chunk_tickets_in_level@.",
|
||||
"comments": [
|
||||
"crates/horizon-client/src/game/native_chunk_tickets.rs"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"name": "client_attack",
|
||||
"target": "horizon_client::game::native_hooks::Game<'_>::native_client_attack",
|
||||
"neoforge": "AttackEntityEvent",
|
||||
"side": "client",
|
||||
"cancellable": false,
|
||||
"mod_api": {
|
||||
"contract": "listen"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "client_hurt",
|
||||
"target": "horizon_client::game::native_hooks::Game<'_>::native_client_hurt",
|
||||
"side": "client",
|
||||
"cancellable": false,
|
||||
"mod_api": {
|
||||
"contract": "listen"
|
||||
}
|
||||
}
|
||||
],
|
||||
"rust_summary": "the local player's own combat on the client (`native_client_attack`, `native_client_hurt`).",
|
||||
"doc_summary": "the local player's combat as the window sees it, on any server: `native_client_attack(target, player)` when it attacks an entity, `native_client_hurt(attacker, player)` when the server says it was hurt; `player` says whether the other side is a player. Client only, no answer. Bundles need \"hooks\": @API:client_combat@."
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"hooks": [],
|
||||
"helpers": [
|
||||
{"name": "reserve_entity_id", "target": "horizon_client::game::native_client_entities::Game<'_>::native_reserve_entity_id"},
|
||||
{"name": "client_entity_spawn", "target": "horizon_client::game::native_client_entities::Game<'_>::native_client_entity_spawn"},
|
||||
{"name": "client_entity_set", "target": "horizon_client::game::native_client_entities::Game<'_>::native_client_entity_set"},
|
||||
{"name": "client_entity_move", "target": "horizon_client::game::native_client_entities::Game<'_>::native_client_entity_move"},
|
||||
{"name": "client_entity_remove", "target": "horizon_client::game::native_client_entities::Game<'_>::native_client_entity_remove"}
|
||||
],
|
||||
"rust_summary": "entities shown to one player's client alone (`native_client_entity_spawn`, `_set`, `_move`, `_remove`), their data from a compound as `/summon` reads it, and an entity id no server entity takes (`native_reserve_entity_id`).",
|
||||
"doc_summary": "`native_client_entity_spawn(viewer, kind, pos, yaw, nbt)` shows an entity (a text, item or block display, or any type with its custom name, invisibility, glow) to one player's client alone, with its synched data encoded from `nbt` as `/summon` reads it; `native_client_entity_set(viewer, id, nbt)`, `native_client_entity_move(viewer, id, pos, yaw)` and `native_client_entity_remove(viewer, ids)` change, move and take it away. Never simulated, saved or seen by others. `native_reserve_entity_id()` gives an id no server entity takes. Bundles need \"hooks\": @API:client_entities@.",
|
||||
"comments": ["crates/horizon-client/src/game/native_client_entities.rs"]
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"hooks": [],
|
||||
"helpers": [
|
||||
{"name": "client_items", "target": "horizon_client::game::native_client_services::Game<'_>::native_client_items"},
|
||||
{"name": "client_texture", "target": "horizon_client::game::native_client_services::Game<'_>::native_client_texture"}
|
||||
],
|
||||
"rust_summary": "what a client mod reads of the entities the client shows (radars, name tags, spectator ESP): `NativeClientEntity`'s type, yaw, baby, spectator and body texture, and `position_at`; the dropped items the client shows (`native_client_items`); a texture's pixels, an entity's or a downloaded skin's (`native_client_texture`, as `TexturePixels`).",
|
||||
"doc_summary": "lists of the entities and dropped items a client shows, for client mods such as radars, name tags and spectator ESP ([Entities a client sees](#entities-a-client-sees)): `native_client_entities()` also gives each entity's type, yaw, baby, spectator and the texture its body draws with, and `position_at(partial_tick)` where the frame draws it; `native_client_items()` lists the dropped items the client shows; `native_client_texture(name)` gives the pixels of an entity texture or a player's skin, downloaded ones included. Bundles need \"hooks\": @API:client_entity_list@.",
|
||||
"comments": [
|
||||
"crates/horizon-client/src/game/native_client_services.rs"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"hooks": [],
|
||||
"helpers": [
|
||||
{"name": "command_info", "target": "horizon_client::game::native_server::Game<'_>::native_command_info"}
|
||||
],
|
||||
"rust_summary": "a server command looked up by name, an alias resolved (`native_command_info`).",
|
||||
"doc_summary": "`native_command_info(label)` looks up the server's command `label` names, a mod's registered one or vanilla's with its alias resolved (Bukkit's `CommandMap.getCommand`), as a `NativeCommandInfo`: name, aliases, usages, operator level, whether vanilla has it. Bundles need \"hooks\": @API:command_info@.",
|
||||
"comments": [
|
||||
"crates/horizon-client/src/game/native_server.rs"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"hooks": [],
|
||||
"rust_summary": "a permission node on a mod's command (`NativeCommand::permission`), which the command tree and the server's dispatch ask `native_has_permission` for.",
|
||||
"doc_summary": "`NativeCommand::permission(node)`: a mod's command that a permission node decides, not the level alone; the server asks `native_has_permission(player, node, level)` before it puts the command in a player's tree and before it runs it, so a permissions mod grants or refuses it (Bukkit's `plugin.yml` `permission:`). Bundles need \"hooks\": @API:command_permission@."
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"hooks": [],
|
||||
"helpers": [
|
||||
{
|
||||
"name": "set_compass_target",
|
||||
"target": "horizon_client::game::native_hooks::Game<'_>::native_set_compass_target"
|
||||
}
|
||||
],
|
||||
"rust_summary": "a player's compass target, kept over respawns and level changes (`native_set_compass_target`), as Bukkit's `setCompassTarget`.",
|
||||
"doc_summary": "`native_set_compass_target(uuid, target)` (`host.set_compass_target`) points one player's compass at a dimension and block position, or at the world spawn again (None), as Bukkit's `Player.setCompassTarget`; the target is sent in place of the world spawn until a mod sets it again or they leave, so a respawn or a level change keeps it. Bundles need \"hooks\": @API:compass_target@."
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"name": "console_line",
|
||||
"target": "horizon_client::game::native_hooks::Game<'_>::native_console_line",
|
||||
"neoforge": "none (Bukkit's ServerCommandEvent and RemoteServerCommandEvent; NeoForge's CommandEvent hears parsed commands only)",
|
||||
"side": "server",
|
||||
"cancellable": true,
|
||||
"mod_api": {
|
||||
"contract": "cancel",
|
||||
"out": "cancel"
|
||||
}
|
||||
}
|
||||
],
|
||||
"rust_summary": "every line the dedicated server's console or RCON is about to run, cancellable, with replies (`native_console_line`).",
|
||||
"doc_summary": "every console and RCON line before it runs, cancellable, with replies: `native_console_line(source, line, cancel, replies)`, `source` being `console` or `rcon`. Bundles need \"hooks\": @API:console_line@."
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"name": "creative_slot_v2",
|
||||
"target": "horizon_client::game::native_hooks::Game<'_>::native_creative_slot_v2",
|
||||
"side": "server",
|
||||
"cancellable": true,
|
||||
"mod_api": {
|
||||
"contract": "cancel",
|
||||
"out": "cancel"
|
||||
}
|
||||
}
|
||||
],
|
||||
"rust_summary": "a creative slot set with the whole stack (`native_creative_slot_v2`), leaving `native_creative_slot` as it was.",
|
||||
"doc_summary": "`native_creative_slot_v2(player, name, slot, stack, cancel)`: an additive version of `native_creative_slot` with the whole stack put in the slot (none for an emptied one), its components included (enchantments, unbreakable, attribute modifiers, contents). Heard just before v1, which hears nothing of a packet canceled here; v1 keeps its signature and firing point. Bundles that use it need `\"hooks\": @API:creative_slot_v2@`."
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"name": "custom_payload_from_client",
|
||||
"target": "horizon_client::game::native_hooks::Game<'_>::native_custom_payload_from_client",
|
||||
"neoforge": "none (NeoForge registers payload handlers per type; Bukkit's PluginMessageListener)",
|
||||
"side": "server",
|
||||
"mod_api": {
|
||||
"contract": "listen"
|
||||
}
|
||||
}
|
||||
],
|
||||
"rust_summary": "a player's plugin channel message on a channel that isn't Horizon's own (`native_custom_payload_from_client`).",
|
||||
"doc_summary": "`native_custom_payload_from_client(player, channel, data)` hears a player's `ServerboundCustomPayloadPacket` in play on any channel that isn't Horizon's own, which vanilla drops: in the tick after it arrives, with no player's state swapped in, from a dead player too. Bundles need \"hooks\": @API:custom_payload_from_client@."
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"name": "entity_data_to",
|
||||
"target": "horizon_client::game::native_hooks::Game<'_>::native_entity_data_to",
|
||||
"neoforge": "none (NeoForge has no per-viewer entity data event; ProtocolLib's ENTITY_METADATA listener)",
|
||||
"side": "server",
|
||||
"mod_api": {
|
||||
"contract": "listen"
|
||||
}
|
||||
}
|
||||
],
|
||||
"helpers": [
|
||||
{
|
||||
"name": "watch_entity_data",
|
||||
"target": "horizon_client::game::native_packets::Game<'_>::native_watch_entity_data"
|
||||
}
|
||||
],
|
||||
"rust_summary": "the entity data one player is sent, changeable for that viewer alone (`native_entity_data_to`), once a mod switched it on (`native_watch_entity_data`).",
|
||||
"doc_summary": "`native_entity_data_to(viewer, entity, values)` hears each `ClientboundSetEntityDataPacket` the server is about to send one player, its `(index, serializer, value)` entries changeable for that viewer alone (a glowing bit, a name only one player sees), just before `native_server_packet` hears the same packet. Nothing is decoded for it until a mod calls `native_watch_entity_data()`, and then only this packet. Bundles need \"hooks\": @API:entity_data_to@.",
|
||||
"comments": [
|
||||
"crates/horizon-client/src/game/native_packets.rs"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"name": "fishing_hook_landed",
|
||||
"target": "horizon_client::game::native_hooks::Game<'_>::native_fishing_hook_landed",
|
||||
"neoforge": "none (NeoForge's ProjectileImpactEvent leaves the bobber's block contact out; Bukkit's ProjectileHitEvent for a FishHook)",
|
||||
"side": "server",
|
||||
"mod_api": {
|
||||
"contract": "listen"
|
||||
}
|
||||
}
|
||||
],
|
||||
"rust_summary": "a server player's fishing bobber meeting or lying on a block, each tick (`native_fishing_hook_landed`).",
|
||||
"doc_summary": "`native_fishing_hook_landed(player, hook, dimension, block, face)`: a server player's fishing bobber touching a block, as Bukkit's `ProjectileHitEvent` for a `FishHook`: each tick the flying bobber meets a block or lies on one (then the block under its centre, face `[0, 1, 0]`), with the bobber in place so `native_remove_entity` can take it. Bundles need \"hooks\": @API:fishing_hook_landed@."
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"hooks": [],
|
||||
"helpers": [
|
||||
{"name":"draw_fluid_box", "target":"horizon_client::game::native_fluid_render::Game<'_>::native_draw_fluid_box", "mod_api":{}}
|
||||
],
|
||||
"rust_summary": "still-fluid boxes under a native draw pose, using sorted depth-writing translucency and the animated mipmapped block sprites (`native_draw_fluid_box`).",
|
||||
"doc_summary": "`native_draw_fluid_box(draw,stack,bounds,options)` draws a `NativeFluidStack` in `[xMin,yMin,zMin,xMax,yMax,zMax]` under a block entity/mob draw pose. Supports `minecraft:water` and `minecraft:lava` with an empty component patch; unsupported IDs/patches, nonfinite/reversed bounds and boxes exceeding 65,536 tiled quads return false without partial geometry. Empty accepted amounts return true without validating bounds and emit nothing. `NativeFluidRenderOptions` requires `argb:Option<u32>` (stack tint override, default plain water 0xff3f76e4/lava white), `render_bottom:bool`, and `uv_shrink_ratio:f32` (0..1, source TextureAtlasSprite ratio 4 / the largest stitched atlas dimension; explicit because Horizon uses sprite arrays). Faces tile at integer boundaries with local UV fractions, no geometry inset. The still sprite follows resource packs/animations/mipmap settings; nearest texels use linear mip selection. Luminosity raises block light (lava 15), preserving sky light; vertex colors have no model diffuse shading. A separate material sorts far to near, alpha blends, culls backs and writes ordinary color/depth, with no alpha discard. It draws with block entities before terrain transparency, separately from improved-transparency queues and the world canvas. Bundles need \"hooks\": @API:fluid_render@.",
|
||||
"comments": ["crates/horizon-client/src/game/native_fluid_render.rs"]
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"name": "handshake",
|
||||
"target": "horizon_client_lib::lan::conn::native_handshake",
|
||||
"neoforge": "none (NeoForge has no handshake event; Paper's PlayerHandshakeEvent)",
|
||||
"side": "server",
|
||||
"cancellable": true,
|
||||
"mod_api": {
|
||||
"contract": "cancel",
|
||||
"out": "cancel"
|
||||
}
|
||||
}
|
||||
],
|
||||
"rust_summary": "a client's handshake on the connection's thread, its host rewritable, refusable (`native_handshake`).",
|
||||
"doc_summary": "`native_handshake(address, host, intent, cancel, reason)` hears each network connection's handshake on its own thread, before the server acts on the intent (1 status, 2 login, 3 transfer): rewrite `host` (what a proxy put after it, Floodgate's data, included), or set `cancel` to refuse; a login or transfer is disconnected with `reason`, a status ping closed unanswered. No `Game` and no helper there. Bundles need \"hooks\": @API:handshake@.",
|
||||
"comments": ["crates/horizon-client/src/lan/conn.rs"]
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"hooks": [],
|
||||
"helpers": [
|
||||
{"name": "hide_entity_from", "target": "horizon_client::game::native_server::Game<'_>::native_hide_entity_from"}
|
||||
],
|
||||
"rust_summary": "an entity hidden from one player's client, or shown again (`native_hide_entity_from`), as Bukkit's `Player.hideEntity`/`showEntity`.",
|
||||
"doc_summary": "`native_hide_entity_from(viewer, entity, hidden)` hides an entity (a mob, a display, another player) from one player's client alone, or shows it again: their client stops tracking it from the next tick, and a hidden player also leaves their Tab list. Kept while the viewer is on, for that entity id. Bundles need \"hooks\": @API:hide_entity_from@.",
|
||||
"comments": ["crates/horizon-client/src/game/native_server.rs"]
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"hooks": [],
|
||||
"helpers": [
|
||||
{"name": "hurt_entity", "target": "horizon_client::game::native_entities::Game<'_>::native_hurt_entity"}
|
||||
],
|
||||
"rust_summary": "a mod hurting a mob or a player as `LivingEntity.hurtServer` with a mob attack does (`native_hurt_entity`).",
|
||||
"doc_summary": "`native_hurt_entity(dimension, target, amount, damage_type, attacker)` hurts a mob or a player of the level as `LivingEntity.hurtServer` and `/damage ... by <attacker>` do (armour, effects, invulnerability time, knockback, death; the damage hooks hear it), the attacker as the causing and direct entity like `damageSources().mobAttack`; whether it was hurt. Bundles need \"hooks\": @API:hurt_entity@.",
|
||||
"comments": [
|
||||
"crates/horizon-client/src/game/native_entities.rs"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"name": "interact_entity_v2",
|
||||
"target": "horizon_client::game::native_hooks::Game<'_>::native_interact_entity_v2",
|
||||
"neoforge": "PlayerInteractEvent.EntityInteract (getHand)",
|
||||
"side": "server",
|
||||
"cancellable": true,
|
||||
"mod_api": {
|
||||
"contract": "cancel",
|
||||
"out": "cancel"
|
||||
}
|
||||
}
|
||||
],
|
||||
"rust_summary": "a player's right click on an entity with the hand that clicks, claimable (`native_interact_entity_v2`).",
|
||||
"doc_summary": "`native_interact_entity_v2(entity, kind, hand, stack, cancel)`: `native_interact_entity` with the hand (`Hand::Main` or `Hand::Off`, NeoForge's `PlayerInteractEvent.EntityInteract.getHand`), asked right after v1, once per hand vanilla tries; a click v1 claimed is not asked again. Bundles need \"hooks\": @API:interact_entity_v2@."
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"name": "inventory_click",
|
||||
"target": "horizon_client::game::native_hooks::Game<'_>::native_inventory_click",
|
||||
"neoforge": "none (Bukkit's InventoryClickEvent)",
|
||||
"side": "server",
|
||||
"cancellable": true,
|
||||
"mod_api": {
|
||||
"contract": "cancel",
|
||||
"out": "cancel"
|
||||
}
|
||||
}
|
||||
],
|
||||
"rust_summary": "a guest's click in their own inventory or a vanilla menu, before it is applied, cancellable (`native_inventory_click`).",
|
||||
"doc_summary": "`native_inventory_click(player, name, window, slot, button, mode, cancel)`: a guest's click in their own inventory (`window` 0) or a vanilla menu they have open, heard before it is applied (Bukkit's `InventoryClickEvent`), with the packet's slot, button and `ClickType` id. Drags are not heard here; a mod's own menus are `native_menu_clicked`'s. Set `cancel` and nothing moves: the client's predicted slots and cursor are answered with the server's. Bundles that use it need `\"hooks\": @API:inventory_click@`."
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"name": "inventory_drag",
|
||||
"target": "horizon_client::game::native_hooks::Game<'_>::native_inventory_drag",
|
||||
"neoforge": "none (Bukkit's InventoryDragEvent)",
|
||||
"side": "server",
|
||||
"cancellable": true,
|
||||
"mod_api": {
|
||||
"contract": "cancel",
|
||||
"out": "cancel"
|
||||
}
|
||||
}
|
||||
],
|
||||
"rust_summary": "a guest's drag in their own inventory or a vanilla menu ending, with the slots it went over, cancellable (`native_inventory_drag`).",
|
||||
"doc_summary": "`native_inventory_drag(player, name, window, kind, slots, cancel)`: a guest's drag (quick craft) in their own inventory or a vanilla menu ends, heard before it is applied (Bukkit's `InventoryDragEvent`), with its kind (0 even, 1 one each, 2 clone) and the window slots the client dragged over. Set `cancel` and nothing moves: the drag ends empty and the client's predicted slots and cursor are answered with the server's. Bundles that use it need `\"hooks\": @API:inventory_drag@`."
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"hooks": [],
|
||||
"rust_summary": "another player's inventory in a server menu whose clicks move their items by vanilla's click rules, live or on their file (`native_open_inventory_menu`).",
|
||||
"doc_summary": "`native_open_inventory_menu(viewer, rows, title, (target, kind), layout)` opens a chest menu that shows another player's inventory or ender chest by a slot layout, live when they are on the server and on their file when they are away; clicks on its shown slots move the target's own items by vanilla's click rules (Bukkit's `openInventory(target.getInventory())`), and it closes once the target leaves, dies or comes back. A mixin helper, not on `Host` (it takes `NativeInventoryKind`, as `native_inventory` does). Bundles need \"hooks\": @API:inventory_menu@.",
|
||||
"comments": [
|
||||
"crates/horizon-client/src/game/native_inventories.rs"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"name": "item_dropped_v2",
|
||||
"target": "horizon_client::game::native_hooks::Game<'_>::native_item_dropped_v2",
|
||||
"neoforge": "ItemTossEvent",
|
||||
"side": "server",
|
||||
"cancellable": true,
|
||||
"mod_api": {
|
||||
"contract": "cancel",
|
||||
"out": "cancel"
|
||||
}
|
||||
}
|
||||
],
|
||||
"rust_summary": "a player's throw with the whole stack, cancellable (`native_item_dropped_v2`), leaving `native_item_dropped` as it was.",
|
||||
"doc_summary": "`native_item_dropped_v2(player, stack, cancel)`: an additive, cancellable version of `native_item_dropped` (NeoForge's `ItemTossEvent`) with the whole stack thrown, its components included (enchantments, unbreakable, attribute modifiers...). Heard just before v1, which keeps its signature and firing point. Set `cancel` and nothing is thrown: the stack goes back into the player's inventory (what does not fit is thrown, unheard) and v1 hears nothing. Bundles that use it need `\"hooks\": @API:item_dropped_v2@`."
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"hooks": [],
|
||||
"helpers": [
|
||||
{"name": "item_from_snbt", "target": "horizon_client::game::native_hooks::Game<'_>::native_item_from_snbt"}
|
||||
],
|
||||
"rust_summary": "a stable way from SNBT to an item stack: `native_item_from_snbt`, `ItemStack::from_nbt` and `mod_api::parse_snbt`.",
|
||||
"doc_summary": "`native_item_from_snbt(snbt)` reads an item stack from SNBT text as vanilla's `TagParser` and `ItemStack.CODEC` do (None for text that is not one compound, for air and for an item the world does not have); `ItemStack::from_nbt(compound)` reads one from a compound without the registry check, and `mod_api::parse_snbt` is the SNBT reader itself. Bundles need \"hooks\": @API:item_from_snbt@.",
|
||||
"comments": [
|
||||
"crates/horizon-items/src/item_data.rs"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"name": "item_picked_up_v2",
|
||||
"target": "horizon_client::game::native_hooks::Game<'_>::native_item_picked_up_v2",
|
||||
"neoforge": "ItemEntityPickupEvent.Pre",
|
||||
"side": "server",
|
||||
"cancellable": true,
|
||||
"mod_api": {
|
||||
"contract": "cancel",
|
||||
"out": "canceled"
|
||||
}
|
||||
}
|
||||
],
|
||||
"rust_summary": "a pickup with the whole stack, the item's thrower and position (`native_item_picked_up_v2`), leaving `native_item_picked_up` as it was.",
|
||||
"doc_summary": "`native_item_picked_up_v2(player, stack, thrower, position, canceled)`: an additive version of `native_item_picked_up` with the whole stack (its components), the item entity's thrower UUID (none for block or mob drops) and its position. Heard just before v1, which hears nothing of a pickup canceled here. v1 keeps its signature and firing point. Bundles that use it need `\"hooks\": @API:item_picked_up_v2@`."
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"hooks": [],
|
||||
"helpers": [
|
||||
{"name": "kick", "target": "horizon_client::game::native_packets::Game<'_>::native_kick"}
|
||||
],
|
||||
"rust_summary": "a player disconnected with a whole text as the reason, as `/kick` does (`native_kick`).",
|
||||
"doc_summary": "`native_kick(player, reason)` disconnects a player with `reason` shown whole, in its colours and translations (Adventure's `Player.kick(Component)`), at the tick's end from any server hook, as `/kick` does: the log names it and `native_player_kicked` hears it. Bundles need \"hooks\": @API:kick@.",
|
||||
"comments": ["crates/horizon-client/src/game/native_packets.rs"]
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"hooks": [],
|
||||
"helpers": [
|
||||
{
|
||||
"name": "level_stats",
|
||||
"target": "horizon_client::game::native_diagnostics::Game<'_>::native_level_stats"
|
||||
},
|
||||
{
|
||||
"name": "level_entities",
|
||||
"target": "horizon_client::game::native_diagnostics::Game<'_>::native_level_entities"
|
||||
},
|
||||
{
|
||||
"name": "level_block_entities",
|
||||
"target": "horizon_client::game::native_diagnostics::Game<'_>::native_level_block_entities"
|
||||
}
|
||||
],
|
||||
"rust_summary": "what each open server level holds, for a diagnostics mod: its chunk, forced chunk, block entity and ticking block entity counts and its entities and block entities by type (`native_level_stats`, `NativeLevelStats`), every entity with its id, type and position (`native_level_entities`) and every block entity with its position and type (`native_level_block_entities`).",
|
||||
"doc_summary": "`native_level_stats(dimension)` (`NativeLevelStats`: chunks, forced chunks, block entities, ticking block entities, entities and block entities by type), `native_level_entities(dimension)` (id, type, position) and `native_level_block_entities(dimension)` (position, type) for any open level of a server. Bundles need \"hooks\": @API:level_stats@.",
|
||||
"comments": [
|
||||
"crates/horizon-client/src/game/native_diagnostics.rs"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"name": "living_change_target",
|
||||
"target": "horizon_client::game::native_hooks::Game<'_>::native_living_change_target",
|
||||
"neoforge": "LivingChangeTargetEvent",
|
||||
"side": "server",
|
||||
"cancellable": true,
|
||||
"mod_api": {
|
||||
"contract": "cancel",
|
||||
"out": "cancel"
|
||||
}
|
||||
}
|
||||
],
|
||||
"rust_summary": "a mob's target changing, cancellable (`native_living_change_target`).",
|
||||
"doc_summary": "`native_living_change_target(dimension, entity, kind, target, target_mob, cancel)`: a mob's target changed (NeoForge's `LivingChangeTargetEvent`, Bukkit's `EntityTargetEvent`) to the player `target` (a UUID), the mob `target_mob`, or none. Set `cancel` and it keeps its old target, and its tick's hit on the new one is not dealt. No `self`. Bundles that use it need `\"hooks\": @API:living_change_target@`."
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"name": "living_incoming_damage_v3",
|
||||
"target": "horizon_client::game::native_hooks::Game<'_>::native_living_incoming_damage_v3",
|
||||
"neoforge": "LivingIncomingDamageEvent",
|
||||
"side": "server",
|
||||
"cancellable": true,
|
||||
"mod_api": {
|
||||
"contract": "cancel",
|
||||
"out": "cancel"
|
||||
}
|
||||
}
|
||||
],
|
||||
"rust_summary": "damage everywhere v1 fires with the victim's and a player attacker's UUID and the causing and direct entity (`native_living_incoming_damage_v3`).",
|
||||
"doc_summary": "`native_living_incoming_damage_v3(victim, victim_uuid, kind, attacker, attacker_uuid, direct, damage_type, amount, cancel)`: `native_living_incoming_damage` everywhere v1 fires, right after it, with a player victim's UUID, the causing entity where v1 has 0 (a mob's or a player's melee, the synchronous attack in progress), a player attacker's UUID and the direct entity; change the amount or cancel as in v1. Bundles need \"hooks\": @API:living_incoming_damage_v3@."
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"name": "lod_column",
|
||||
"target": "horizon_world::worldgen::native_generate::native_lod_column",
|
||||
"neoforge": "none (NeoForge has no such event; far terrain is Horizon's own)",
|
||||
"side": "both",
|
||||
"mod_api": {
|
||||
"contract": "listen"
|
||||
}
|
||||
}
|
||||
],
|
||||
"rust_summary": "a mod's terrain in the far terrain before its chunks generate (`native_lod_column`).",
|
||||
"doc_summary": "`native_lod_column(dimension, column, seed, flat, column_out)` is asked for each block column the far terrain samples where no chunk is generated yet: a mod whose `native_chunk_generate` builds it answers its blocks with `column_out.replace(spans, biome)`, and the far terrain shows them instead of vanilla's noise. It runs on the far terrain's workers, many columns a tile. Bundles need \"hooks\": @API:lod_column@.",
|
||||
"comments": [
|
||||
"crates/horizon-world/src/worldgen/native_generate.rs"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"name": "log_line",
|
||||
"target": "horizon_client_lib::native_client_hooks::native_log_line",
|
||||
"neoforge": "none (a log4j appender)",
|
||||
"mod_api": {
|
||||
"contract": "listen"
|
||||
}
|
||||
}
|
||||
],
|
||||
"rust_summary": "each message written to the log, on the log's writer thread (`native_log_line`).",
|
||||
"doc_summary": "each message written to `logs/latest.log`, on the log's writer thread: `native_log_line(level, thread, message, line)`. Bundles need \"hooks\": @API:log_line@.",
|
||||
"comments": [
|
||||
"crates/horizon-client/src/native_client_hooks.rs"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"name": "menu_input_closed",
|
||||
"target": "horizon_client::game::native_hooks::Game<'_>::native_menu_input_closed",
|
||||
"neoforge": "none (NeoForge has no such event; Bukkit's InventoryCloseEvent with the inventory's contents)",
|
||||
"side": "server",
|
||||
"mod_api": {
|
||||
"contract": "collect",
|
||||
"out": "slots"
|
||||
}
|
||||
}
|
||||
],
|
||||
"helpers": [
|
||||
{
|
||||
"name": "open_menu_with_input",
|
||||
"target": "horizon_client::game::native_menus::Game<'_>::native_open_menu_with_input"
|
||||
},
|
||||
{
|
||||
"name": "menu_contents",
|
||||
"target": "horizon_client::game::native_menus::Game<'_>::native_menu_contents"
|
||||
},
|
||||
{
|
||||
"name": "menu_cursor",
|
||||
"target": "horizon_client::game::native_menus::Game<'_>::native_menu_cursor"
|
||||
}
|
||||
],
|
||||
"rust_summary": "server menus with input slots the player fills as a chest's (`native_open_menu_with_input`), what such a menu holds (`native_menu_contents`), the cursor of a click (`native_menu_cursor`) and the input slots when it closes (`native_menu_input_closed`).",
|
||||
"doc_summary": "`native_open_menu_with_input(uuid, rows, title, items, input)` opens a server menu whose slots in `input` take and give items by vanilla's click rules, the rest shown by the mod as in `native_open_menu`; `native_menu_contents(uuid)` reads what an open server menu holds, `native_menu_cursor(uuid)` the stack on the player's cursor (inside `native_menu_clicked`, the one of that click), and `native_menu_input_closed(uuid, window, slots)` hears the input slots as the menu closes: what the mods leave goes back to the player as vanilla's `clearContainer` does. Bundles need \"hooks\": @API:menu_input@.",
|
||||
"comments": [
|
||||
"crates/horizon-client/src/game/native_menus.rs"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"hooks": [],
|
||||
"helpers": [
|
||||
{"name": "mount", "target": "horizon_client::game::native_travel::Game<'_>::native_mount"},
|
||||
{"name": "dismount", "target": "horizon_client::game::native_travel::Game<'_>::native_dismount"},
|
||||
{"name": "eject_passengers", "target": "horizon_client::game::native_travel::Game<'_>::native_eject_passengers"}
|
||||
],
|
||||
"rust_summary": "a server player or mob seated on a vehicle or taken off it, and a vehicle emptied (`native_mount`, `native_dismount`, `native_eject_passengers`), as `/ride` and Bukkit's `addPassenger`/`leaveVehicle`/`eject`.",
|
||||
"doc_summary": "`native_mount(entity, vehicle)`, `native_dismount(entity)` and `native_eject_passengers(vehicle)` seat a server player or mob on a boat, minecart or mob, take it off, or take every rider off, as `/ride` does, with its checks and `native_entity_mount`. Bundles need \"hooks\": @API:mount@.",
|
||||
"comments": ["crates/horizon-client/src/game/native_travel.rs"]
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"hooks": [],
|
||||
"helpers": [
|
||||
{"name": "new_map", "target": "horizon_client::game::native_hooks::Game<'_>::native_new_map"},
|
||||
{"name": "paint_map", "target": "horizon_client::game::native_hooks::Game<'_>::native_paint_map"}
|
||||
],
|
||||
"rust_summary": "maps a server mod draws, kept by the world: `native_new_map` and `native_paint_map`.",
|
||||
"doc_summary": "`native_new_map(dimension, colors)` makes a locked map of the world showing a 128 x 128 picture, under the world's next map id, saved with its maps and sent like any map to whoever carries it or sees it framed (Bukkit's `Server.createMap`); `native_paint_map(map_id, colors)` replaces a map's whole picture the same way (`MapCanvas`). Bundles need \"hooks\": @API:new_map@."
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"hooks": [],
|
||||
"helpers": [],
|
||||
"rust_summary": "`native_spawn_mob` and `native_summon` for any loaded level, not only the one in place, so a\n`native_server_tick` hook spawns too (`execute in <dimension> run summon`).",
|
||||
"doc_summary": "`native_spawn_mob(dimension, kind, pos, options)` and `native_summon(dimension, kind, pos, nbt)` for any level the server has loaded: the level is put in place for the spawn and the one that was is put back, as `execute in <dimension> run summon` does, so a `native_server_tick` hook spawns into the Nether or the End. A level not loaded is refused. Bundles need \"hooks\": @API:npc_any_level@.",
|
||||
"comments": [
|
||||
"crates/horizon-client/src/game/native_mobs.rs",
|
||||
"crates/horizon-client/src/game/native_entities.rs"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"hooks": [],
|
||||
"helpers": [
|
||||
{"name": "entity_by_uuid", "target": "horizon_client::game::native_npcs::Game<'_>::native_entity_by_uuid"},
|
||||
{"name": "merge_entity_nbt", "target": "horizon_client::game::native_npcs::Game<'_>::native_merge_entity_nbt"}
|
||||
],
|
||||
"rust_summary": "an entity found by its UUID in any loaded level (`native_entity_by_uuid`) and `/data merge entity` in\nany loaded level (`native_merge_entity_nbt`).",
|
||||
"doc_summary": "`native_entity_by_uuid(uuid)`, the dimension, id and type of the entity with that UUID in any loaded level, a server's player included (Bukkit's `Server.getEntity(UUID)`), and `native_merge_entity_nbt(dimension, id, nbt)`, `native_entity_merge_nbt` for any loaded level (`execute in <dimension> run data merge entity`). Bundles need \"hooks\": @API:npc_entity_by_uuid@.",
|
||||
"comments": [
|
||||
"crates/horizon-client/src/game/native_npcs.rs"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"hooks": [],
|
||||
"helpers": [
|
||||
{"name": "set_entity_rotation", "target": "horizon_client::game::native_npcs::Game<'_>::native_set_entity_rotation"}
|
||||
],
|
||||
"rust_summary": "an entity turned without moving it, as `/rotate` does, a mob's head apart (`native_set_entity_rotation`).",
|
||||
"doc_summary": "`native_set_entity_rotation(dimension, id, yaw, pitch, head_yaw)`, an entity of any loaded level turned without moving it as `/rotate` does (`Entity.forceSetRotation`), with `head_yaw` turning a mob's head alone (`setYHeadRot`): mobs, mannequins, armor stands, boats, minecarts, fangs and displays. Bundles need \"hooks\": @API:npc_entity_rotation@.",
|
||||
"comments": [
|
||||
"crates/horizon-client/src/game/native_npcs.rs"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"hooks": [],
|
||||
"helpers": [
|
||||
{"name": "mob_attack", "target": "horizon_client::game::native_npcs::Game<'_>::native_mob_attack"}
|
||||
],
|
||||
"rust_summary": "a mob's own attack on a mob or a player of its level (`native_mob_attack`, `Mob.doHurtTarget`).",
|
||||
"doc_summary": "`native_mob_attack(dimension, mob, target)`, a mob's own attack (`Mob.doHurtTarget`: attack damage, weapon, enchantments, knockback, the species' extras) on a living mob or a server's player of its level, from any server hook, with no reach check or swing, as vanilla's attack goal checks and swings first. Bundles need \"hooks\": @API:npc_mob_attack@.",
|
||||
"comments": [
|
||||
"crates/horizon-client/src/game/native_npcs.rs"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"hooks": [],
|
||||
"helpers": [
|
||||
{"name": "mob_navigate", "target": "horizon_client::game::native_npcs::Game<'_>::native_mob_navigate"},
|
||||
{"name": "mob_follow", "target": "horizon_client::game::native_npcs::Game<'_>::native_mob_follow"},
|
||||
{"name": "mob_stop", "target": "horizon_client::game::native_npcs::Game<'_>::native_mob_stop"},
|
||||
{"name": "mob_navigating", "target": "horizon_client::game::native_npcs::Game<'_>::native_mob_navigating"},
|
||||
{"name": "mob_look_at", "target": "horizon_client::game::native_npcs::Game<'_>::native_mob_look_at"}
|
||||
],
|
||||
"rust_summary": "a mob walked with the engine's pathfinding and turned to look, as vanilla's goals drive it (`native_mob_navigate`,\n`native_mob_follow`, `native_mob_stop`, `native_mob_navigating`, `native_mob_look_at`).",
|
||||
"doc_summary": "a mob of any loaded level driven as vanilla's goals drive it: `native_mob_navigate(dimension, mob, to, reach, speed)` (`PathNavigation.moveTo` to a point, round walls), `native_mob_follow(dimension, mob, target, speed)` (`moveTo` an entity, a player too), `native_mob_stop(dimension, mob)`, `native_mob_navigating(dimension, mob)` (`isInProgress`) and `native_mob_look_at(dimension, mob, at)` (`LookControl.setLookAt`). Bundles need \"hooks\": @API:npc_mob_navigation@.",
|
||||
"comments": [
|
||||
"crates/horizon-client/src/game/native_npcs.rs"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"hooks": [],
|
||||
"helpers": [
|
||||
{"name": "open_chat", "target": "horizon_client::game::native_net::Game<'_>::native_open_chat"}
|
||||
],
|
||||
"rust_summary": "a client mod opens the chat screen with text typed in (`native_open_chat`).",
|
||||
"doc_summary": "`native_open_chat(text)` opens the chat screen with `text` typed in for the player to finish and send, as T and / open it; nothing is sent; an error with no player in a world or while another screen is open. Bundles need \"hooks\": @API:open_chat@.",
|
||||
"comments": ["crates/horizon-client/src/game/native_net.rs"]
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"name": "permission_check",
|
||||
"target": "horizon_client::game::native_hooks::Game<'_>::native_permission_check",
|
||||
"neoforge": "PermissionGatherEvent.Handler (the handler a permissions mod installs answers PermissionAPI.getPermission)",
|
||||
"side": "server",
|
||||
"mod_api": {
|
||||
"contract": "replace",
|
||||
"out": "result"
|
||||
}
|
||||
}
|
||||
],
|
||||
"helpers": [
|
||||
{"name": "has_permission", "target": "horizon_client::game::native_permissions::Game<'_>::native_has_permission"}
|
||||
],
|
||||
"rust_summary": "permission nodes: a permissions mod's answer (`native_permission_check`) and the question every mod asks (`native_has_permission`), falling back to the operator level.",
|
||||
"doc_summary": "`native_permission_check(player, node, default_level, result)` lets a permissions mod answer whether a player has a permission node, and `native_has_permission(player, node, default_level)` asks it for every mod and the server, falling back to the player's operator level against `default_level` when no mod answers (Bukkit's `Permissible.hasPermission`, NeoForge's `PermissionAPI.getPermission`). Bundles need \"hooks\": @API:permission_check@.",
|
||||
"comments": ["crates/horizon-client/src/game/native_permissions.rs"]
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"hooks": [],
|
||||
"helpers": [
|
||||
{
|
||||
"name": "play_sound_to",
|
||||
"target": "horizon_client::game::native_hooks::Game<'_>::native_play_sound_to"
|
||||
}
|
||||
],
|
||||
"rust_summary": "a sound for one player alone (`native_play_sound_to`), as Bukkit's `Player.playSound`.",
|
||||
"doc_summary": "`native_play_sound_to(uuid, sound, category, at, volume, pitch)` (`host.play_sound_to`) plays a sound to one player alone, at `at` or where they stand, in a `SoundSource` category by name, as Bukkit's `Player.playSound`; it leaves at the tick's end, so it works from any server hook. Bundles need \"hooks\": @API:play_sound_to@."
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"hooks": [],
|
||||
"helpers": [
|
||||
{"name": "attribute", "target": "horizon_client::game::native_player_state::Game<'_>::native_attribute"},
|
||||
{"name": "set_attribute_base", "target": "horizon_client::game::native_player_state::Game<'_>::native_set_attribute_base"},
|
||||
{"name": "set_attributes", "target": "horizon_client::game::native_player_state::Game<'_>::native_set_attributes"}
|
||||
],
|
||||
"rust_summary": "a player's attribute read, its base set, or a saved `attributes` list loaded (`native_attribute`, `native_set_attribute_base`, `native_set_attributes`), as Bukkit's `AttributeInstance` and vanilla's `AttributeMap.load`.",
|
||||
"doc_summary": "`native_attribute(uuid, attribute)` gives an attribute's base and full value (Bukkit's `AttributeInstance.getBaseValue`/`getValue`); `native_set_attribute_base(uuid, attribute, base)` sets the base as `/attribute .. base set`; `native_set_attributes(uuid, saved)` loads a saved `attributes` list as `AttributeMap.load`. Bundles need \"hooks\": @API:player_attributes@.",
|
||||
"comments": ["crates/horizon-client/src/game/native_player_state.rs"]
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"name": "player_register_channels",
|
||||
"target": "horizon_client::game::native_hooks::Game<'_>::native_player_register_channels",
|
||||
"neoforge": "none (NeoForge has no channel registration event; Bukkit's PlayerRegisterChannelEvent and PlayerUnregisterChannelEvent)",
|
||||
"side": "server",
|
||||
"mod_api": {
|
||||
"contract": "listen"
|
||||
}
|
||||
}
|
||||
],
|
||||
"helpers": [
|
||||
{
|
||||
"name": "player_channels",
|
||||
"target": "horizon_client::game::native_packets::Game<'_>::native_player_channels"
|
||||
}
|
||||
],
|
||||
"rust_summary": "the plugin channels a player's client registers and unregisters, in the configuration and in play (`native_player_register_channels`), and those it has (`native_player_channels`).",
|
||||
"doc_summary": "`native_player_register_channels(player, name, channels, register)` hears the plugin channels a client registers (`minecraft:register`) or unregisters (`minecraft:unregister`), the configuration's once right after `native_player_logged_in`, as Bukkit's `PlayerRegisterChannelEvent`; `native_player_channels(player)` lists what it has registered, as `Player.getListeningPluginChannels`. Bundles need \"hooks\": @API:player_channels@.",
|
||||
"comments": [
|
||||
"crates/horizon-client/src/game/native_packets.rs"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"hooks": [],
|
||||
"helpers": [
|
||||
{"name": "player_client", "target": "horizon_client::game::native_player_reads::Game<'_>::native_player_client"}
|
||||
],
|
||||
"rust_summary": "what a player's client told the server and what it was sent (`native_player_client`): brand, language, view distance, chunks and wire bytes.",
|
||||
"doc_summary": "`native_player_client(uuid)` gives a player's `NativeClientInfo`: the client's brand, its `ClientInformation` (language, view distance, chat visibility, main hand, particles), the chunks it was sent and the bytes on its wire, as Paper's `getClientBrandName`, `locale` and `getClientViewDistance`. Bundles need \"hooks\": @API:player_client@.",
|
||||
"comments": ["crates/horizon-client/src/game/native_player_reads.rs"]
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"name": "player_command_level",
|
||||
"target": "horizon_client::game::native_hooks::Game<'_>::native_player_command_level",
|
||||
"neoforge": "none (NeoForge has no such event; Paper's VanillaCommandWrapper checks a minecraft.command node)",
|
||||
"side": "server",
|
||||
"mod_api": {
|
||||
"contract": "listen"
|
||||
}
|
||||
}
|
||||
],
|
||||
"rust_summary": "the operator level one typed command of a player runs at, which a mod may raise or lower (`native_player_command_level`).",
|
||||
"doc_summary": "`native_player_command_level(player, name, command, level)` sets the operator level one typed command of a player runs at, before the server checks it: a permissions mod raises it to grant `/gamemode` to a player of level 0, or lowers it to refuse a command (Paper's `VanillaCommandWrapper` node check). Bundles need \"hooks\": @API:player_command_level@."
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"name": "player_commands_sent_v2",
|
||||
"target": "horizon_client::game::native_hooks::Game<'_>::native_player_commands_sent_v2",
|
||||
"neoforge": "none (NeoForge has no such event; Paper's PlayerCommandSendEvent)",
|
||||
"side": "server",
|
||||
"mod_api": {
|
||||
"contract": "collect",
|
||||
"out": "names"
|
||||
}
|
||||
}
|
||||
],
|
||||
"rust_summary": "the root commands a player's level hides from their command tree, which a mod may put back (`native_player_commands_sent_v2`), leaving `native_player_commands_sent` as it was.",
|
||||
"doc_summary": "`native_player_commands_sent_v2(player, name, names, names_hidden)`: an additive version of `native_player_commands_sent`, heard right after v1, with the root commands the player's level hides in `names_hidden`; one a mod moves into `names` is sent with its subtree, so a granted command completes in the player's chat box. Bundles need \"hooks\": @API:player_commands_sent_v2@."
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"hooks": [],
|
||||
"helpers": [
|
||||
{"name": "player_compound", "target": "horizon_client::game::native_player_state::Game<'_>::native_player_compound"}
|
||||
],
|
||||
"rust_summary": "a player's compound as the server would save it now (`native_player_compound`), as vanilla's `ServerPlayer.saveWithoutId`.",
|
||||
"doc_summary": "`native_player_compound(uuid)` gives a player's compound as the server would save it now (`players/data/<uuid>.dat`, vanilla's `ServerPlayer.saveWithoutId`); None inside a player's turn and for a player not on. Bundles need \"hooks\": @API:player_compound@.",
|
||||
"comments": ["crates/horizon-client/src/game/native_player_state.rs"]
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"hooks": [],
|
||||
"helpers": [
|
||||
{"name": "effects", "target": "horizon_client::game::native_player_state::Game<'_>::native_effects"},
|
||||
{"name": "add_effect", "target": "horizon_client::game::native_player_state::Game<'_>::native_add_effect"},
|
||||
{"name": "remove_effect", "target": "horizon_client::game::native_player_state::Game<'_>::native_remove_effect"},
|
||||
{"name": "set_effects", "target": "horizon_client::game::native_player_state::Game<'_>::native_set_effects"}
|
||||
],
|
||||
"rust_summary": "a player's effects read, one given or taken off, or all replaced (`native_effects`, `native_add_effect`, `native_remove_effect`, `native_set_effects`), as vanilla's `LivingEntity.addEffect` and Bukkit's `addPotionEffect`.",
|
||||
"doc_summary": "`native_effects(uuid)` reads a player's effects as `NativeEffect { id, amplifier, duration, ambient, particles }`; `native_add_effect(uuid, effect)` gives one as `LivingEntity.addEffect` (Bukkit's `addPotionEffect`), `native_remove_effect(uuid, id)` takes one off as `/effect clear`, `native_set_effects(uuid, effects)` replaces them all. Bundles need \"hooks\": @API:player_effects@.",
|
||||
"comments": ["crates/horizon-client/src/game/native_player_state.rs"]
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"hooks": [],
|
||||
"helpers": [
|
||||
{"name": "held_stack", "target": "horizon_client::game::native_player_reads::Game<'_>::native_held_stack"},
|
||||
{"name": "player_equipment", "target": "horizon_client::game::native_player_reads::Game<'_>::native_player_equipment"}
|
||||
],
|
||||
"rust_summary": "any player's main-hand stack and equipment slots from any server hook (`native_held_stack`, `native_player_equipment`).",
|
||||
"doc_summary": "`native_held_stack(uuid)` and `native_player_equipment(uuid, slot)` give the whole stack (components included) in a player's main hand or equipment slot, the acting player's or another's, as Bukkit's `getEquipment().getItemInMainHand()` and `getItem(slot)`. Bundles need \"hooks\": @API:player_equipment@.",
|
||||
"comments": ["crates/horizon-client/src/game/native_player_reads.rs"]
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"name": "player_fish_v2",
|
||||
"target": "horizon_client::game::native_hooks::Game<'_>::native_player_fish_v2",
|
||||
"neoforge": "none (NeoForge's ItemFishedEvent fires after the loot roll, in FishingHook.retrieve; Bukkit's PlayerFishEvent with getHand)",
|
||||
"side": "server",
|
||||
"cancellable": true,
|
||||
"mod_api": {
|
||||
"contract": "cancel",
|
||||
"out": "cancel"
|
||||
}
|
||||
}
|
||||
],
|
||||
"rust_summary": "a player's fishing rod with the hand that holds it, cancellable (`native_player_fish_v2`).",
|
||||
"doc_summary": "`native_player_fish_v2(player, name, state, hand, cancel)`: `native_player_fish` with the rod's hand (the hand used for a cast or reel-in, Bukkit's `PlayerFishEvent.getHand`; for a bite the hand holding the rod, main hand first), asked right after v1 with the same `cancel`. Bundles need \"hooks\": @API:player_fish_v2@."
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"name": "player_hurt_by_player",
|
||||
"target": "horizon_client::game::native_hooks::Game<'_>::native_player_hurt_by_player",
|
||||
"neoforge": "LivingIncomingDamageEvent (a player victim, a player source; Bukkit's EntityDamageByEntityEvent)",
|
||||
"side": "server",
|
||||
"cancellable": true,
|
||||
"mod_api": {
|
||||
"contract": "cancel",
|
||||
"out": "canceled"
|
||||
}
|
||||
}
|
||||
],
|
||||
"rust_summary": "one player hurting another by melee, sweep or a projectile, with both players named, cancellable (`native_player_hurt_by_player`).",
|
||||
"doc_summary": "player-versus-player damage: `native_player_hurt_by_player(attacker, attacker_entity, victim, victim_entity, source, damage_type, amount, canceled)` names both players by UUID and network entity id, `source` (`melee`, `sweep` or the projectile's entity type, such as `minecraft:arrow`) and the damage type. It is heard where `native_living_incoming_damage` is, at the victim's `hurtServer` past its guards (the `pvp` rule and teams allow it, the victim is not invulnerable or dead), and before it. Change `amount`, or set `canceled` and nothing of the hurt happens (the v1 hook then hears nothing). A snowball's or egg's zero damage is heard with amount 0 and changes nothing. No `self`, like v1. Bundles that use it need `\"hooks\": @API:player_hurt_by_player@`."
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"name": "player_launch_projectile_v2",
|
||||
"target": "horizon_client::game::native_hooks::Game<'_>::native_player_launch_projectile_v2",
|
||||
"neoforge": "none (Bukkit's ProjectileLaunchEvent for a player's throw)",
|
||||
"side": "server",
|
||||
"cancellable": true,
|
||||
"mod_api": {
|
||||
"contract": "cancel",
|
||||
"out": "cancel"
|
||||
}
|
||||
}
|
||||
],
|
||||
"rust_summary": "a guest about to throw a snowball, egg, ender pearl or mod thrown item, with the whole stack, cancellable (`native_player_launch_projectile_v2`), leaving `native_player_launch_projectile` as it was.",
|
||||
"doc_summary": "`native_player_launch_projectile_v2(player, name, stack, cancel)`: a guest is about to throw `stack` (a snowball, an egg, an ender pearl, a mod's thrown item), heard where `ProjectileItem.use` creates the projectile, before it exists (Bukkit's `ProjectileLaunchEvent` for a player). Set `cancel` and nothing is thrown, used or put on cooldown, `native_player_launch_projectile` hears nothing of it, and the client gets its inventory back. Bundles that use it need `\"hooks\": @API:player_launch_projectile_v2@`."
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"name": "player_logging_out",
|
||||
"target": "horizon_client::game::native_hooks::Game<'_>::native_player_logging_out",
|
||||
"neoforge": "none (Bukkit's PlayerQuitEvent; NeoForge's PlayerLoggedOutEvent comes after the removal)",
|
||||
"side": "server",
|
||||
"mod_api": {
|
||||
"contract": "listen"
|
||||
}
|
||||
}
|
||||
],
|
||||
"rust_summary": "a player leaving while still in their level, before they are saved and removed (`native_player_logging_out`).",
|
||||
"doc_summary": "a player leaving the server while still present: `native_player_logging_out(player, name)` is heard once their connection is gone but before their menu closes and they are saved and removed, still in their level and in `native_server_players`, with no state swapped in, so the UUID helpers and `native_run_command` reach them (Bukkit's `PlayerQuitEvent`). A death a mod causes there (`kill <name>`) happens at once, with its drops where they stood. Not heard when the server stops. Bundles that use it need `\"hooks\": @API:player_logging_out@`."
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"name": "player_move",
|
||||
"target": "horizon_client::game::native_hooks::Game<'_>::native_player_move",
|
||||
"neoforge": "none (Bukkit's PlayerMoveEvent)",
|
||||
"side": "server",
|
||||
"cancellable": true,
|
||||
"mod_api": {
|
||||
"contract": "cancel",
|
||||
"out": "cancel"
|
||||
}
|
||||
}
|
||||
],
|
||||
"rust_summary": "a server player's move before it is applied, which a mod may cancel or send elsewhere (`native_player_move`).",
|
||||
"doc_summary": "a server player's move: `native_player_move(player, name, from, to, yaw, pitch, on_ground, cancel)` is heard for each position or look packet that changed either and passed the server's checks, before it is applied (Bukkit's `PlayerMoveEvent`). Cancel and the player is sent back to `from`; change `to` and they are put there and their client is told. No `self`. Bundles that use it need `\"hooks\": @API:player_move@`."
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"hooks": [],
|
||||
"helpers": [
|
||||
{"name": "player_permission", "target": "horizon_client::game::native_server::Game<'_>::native_player_permission"}
|
||||
],
|
||||
"rust_summary": "a player's permission level, also inside `native_player_login` (`native_player_permission`).",
|
||||
"doc_summary": "`native_player_permission(player)` gives the permission level (0 to 4, `ops.json`'s for an operator) of a player who is on the server or joining it, also inside `native_player_login` for the player it hears, so a login gate needs no `ops.json` of its own; None for a player who isn't on. Bundles need \"hooks\": @API:player_permission@.",
|
||||
"comments": ["crates/horizon-client/src/game/native_server.rs"]
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"hooks": [],
|
||||
"helpers": [
|
||||
{"name": "player_persistent_data", "target": "horizon_client::game::native_player_state::Game<'_>::native_player_persistent_data"},
|
||||
{"name": "set_player_persistent_data", "target": "horizon_client::game::native_player_state::Game<'_>::native_set_player_persistent_data"}
|
||||
],
|
||||
"rust_summary": "the compound mods keep on a player (`native_player_persistent_data`, `native_set_player_persistent_data`), saved as `NeoForgeData`, as NeoForge's `Entity.getPersistentData` and Bukkit's `PersistentDataContainer`.",
|
||||
"doc_summary": "`native_player_persistent_data(uuid)` reads and `native_set_player_persistent_data(uuid, data)` replaces the compound mods keep on a player, saved in their file as `NeoForgeData` (NeoForge's `Entity.getPersistentData`, Bukkit's `PersistentDataContainer`). Bundles need \"hooks\": @API:player_persistent_data@.",
|
||||
"comments": ["crates/horizon-client/src/game/native_player_state.rs"]
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"name": "player_pre_login",
|
||||
"target": "horizon_client_lib::lan::conn::native_player_pre_login",
|
||||
"neoforge": "none (Bukkit's AsyncPlayerPreLoginEvent)",
|
||||
"side": "server",
|
||||
"cancellable": true,
|
||||
"mod_api": {
|
||||
"contract": "collect",
|
||||
"out": "defer"
|
||||
}
|
||||
}
|
||||
],
|
||||
"rust_summary": "a player logging in, on the connection's thread before the server's checks, held for the mods' answers or refused (`native_player_pre_login`).",
|
||||
"doc_summary": "`native_player_pre_login(player, name, address, defer)` hears a player logging in on the connection's own thread, before the server's own checks: a mod pushes a receiver onto `defer` and answers on it from any thread, `Ok` to let the player on, `Err(reason)` to refuse; the login waits for every answer, 20 s at most. No `Game` and no helper there. Bundles need \"hooks\": @API:player_pre_login@.",
|
||||
"comments": ["crates/horizon-client/src/lan/conn.rs"]
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"name": "player_profile",
|
||||
"target": "horizon_client_lib::lan::conn::native_player_profile",
|
||||
"neoforge": "none (Velocity's GameProfileRequestEvent, Paper's PlayerProfile in AsyncPlayerPreLoginEvent)",
|
||||
"side": "server",
|
||||
"cancellable": false,
|
||||
"mod_api": {
|
||||
"contract": "listen"
|
||||
}
|
||||
}
|
||||
],
|
||||
"rust_summary": "a joining player's profile properties on the connection's thread, before `LoginFinished` (`native_player_profile`).",
|
||||
"doc_summary": "`native_player_profile(player, name, properties)` hears a joining player's profile on the connection's own thread, once the server has let them in and before it sends `LoginFinished`: change, add or remove `properties` (a skin's `textures`) and the player's own client and every player list get the new ones. No `Game` and no helper there; the hook may block for a lookup. Bundles need \"hooks\": @API:player_profile@.",
|
||||
"comments": ["crates/horizon-client/src/lan/conn.rs"]
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"hooks": [],
|
||||
"helpers": [
|
||||
{"name": "player_stats", "target": "horizon_client::game::native_player_reads::Game<'_>::native_player_stats"},
|
||||
{"name": "player_stat", "target": "horizon_client::game::native_player_reads::Game<'_>::native_player_stat"},
|
||||
{"name": "set_player_stats", "target": "horizon_client::game::native_player_reads::Game<'_>::native_set_player_stats"},
|
||||
{"name": "player_advancements", "target": "horizon_client::game::native_player_reads::Game<'_>::native_player_advancements"},
|
||||
{"name": "set_player_advancements", "target": "horizon_client::game::native_player_reads::Game<'_>::native_set_player_advancements"}
|
||||
],
|
||||
"rust_summary": "a player's statistics and advancements in vanilla's JSON, read and replaced (`native_player_stats`, `native_player_stat`, `native_set_player_stats`, `native_player_advancements`, `native_set_player_advancements`).",
|
||||
"doc_summary": "`native_player_stats(uuid)` and `native_player_advancements(uuid)` give a player's statistics and advancement progress as vanilla's `players/stats` and `players/advancements` files hold them, `native_player_stat(uuid, type, key)` one counter (Bukkit's `getStatistic`, `getAdvancementProgress`); `native_set_player_stats` and `native_set_player_advancements` replace them from the same JSON and resend them to the client. Bundles need \"hooks\": @API:player_records@.",
|
||||
"comments": ["crates/horizon-client/src/game/native_player_reads.rs"]
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"name": "player_respawn_position",
|
||||
"target": "horizon_client::game::native_hooks::Game<'_>::native_player_respawn_position",
|
||||
"neoforge": "PlayerRespawnPositionEvent",
|
||||
"side": "server",
|
||||
"mod_api": {
|
||||
"contract": "listen"
|
||||
}
|
||||
}
|
||||
],
|
||||
"rust_summary": "where a dead player is about to respawn, which a mod may change (`native_player_respawn_position`).",
|
||||
"doc_summary": "a dead player's respawn point: `native_player_respawn_position(player, name, dimension, pos, yaw, pitch)` is heard once per respawn before the player is placed (NeoForge's `PlayerRespawnPositionEvent`, Bukkit's `PlayerRespawnEvent.setRespawnLocation`), with their bed, anchor, forced point or the world spawn. A changed dimension or position places the player exactly there, with no bed or anchor used; yaw and pitch set their look. An unknown dimension or a non-finite value leaves that part unchanged. Bundles that use it need `\"hooks\": @API:player_respawn_position@`."
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"name": "player_spawn_location",
|
||||
"target": "horizon_client::game::native_hooks::Game<'_>::native_player_spawn_location",
|
||||
"neoforge": "none (Spigot's PlayerSpawnLocationEvent)",
|
||||
"side": "server",
|
||||
"mod_api": {
|
||||
"contract": "listen"
|
||||
}
|
||||
}
|
||||
],
|
||||
"rust_summary": "where a joining player is about to be placed, which a mod may change (`native_player_spawn_location`).",
|
||||
"doc_summary": "a joining player's spawn location: `native_player_spawn_location(player, name, dimension, position, rotation)` is heard once per join, after `native_player_login` let them in and before they are placed (Spigot's `PlayerSpawnLocationEvent`), with their saved or newly found dimension, feet position and yaw/pitch. Change them and the player is placed there, in that level, with no teleport afterwards; an unknown dimension or a non-finite value leaves that part unchanged. No state is swapped in. Bundles that use it need `\"hooks\": @API:player_spawn_location@`."
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"hooks": [],
|
||||
"helpers": [
|
||||
{"name": "player_food", "target": "horizon_client::game::native_player_state::Game<'_>::native_player_food"},
|
||||
{"name": "set_player_status", "target": "horizon_client::game::native_player_state::Game<'_>::native_set_player_status"}
|
||||
],
|
||||
"rust_summary": "a player's food read (`native_player_food`) and their health, food and experience set (`native_set_player_status`), as Bukkit's `setHealth`, `setFoodLevel`, `setSaturation` and `setExp`; health 0 kills.",
|
||||
"doc_summary": "`native_player_food(uuid)` reads a player's food level, saturation and exhaustion; `native_set_player_status(uuid, health, food, xp)` sets each that is given, as Bukkit's `setHealth`, `setFoodLevel`, `setSaturation`, `setExhaustion` and `setExp`; health 0 kills as `/kill` does. Bundles need \"hooks\": @API:player_status@.",
|
||||
"comments": ["crates/horizon-client/src/game/native_player_state.rs"]
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"name": "player_teleport",
|
||||
"target": "horizon_client::game::native_hooks::Game<'_>::native_player_teleport",
|
||||
"neoforge": "EntityTeleportEvent (Bukkit's PlayerTeleportEvent)",
|
||||
"side": "server",
|
||||
"cancellable": true,
|
||||
"mod_api": {
|
||||
"contract": "cancel",
|
||||
"out": "cancel"
|
||||
}
|
||||
}
|
||||
],
|
||||
"rust_summary": "a server player about to be teleported, with the cause, both levels and the position packet's relative flags, which a mod may change or cancel (`native_player_teleport`).",
|
||||
"doc_summary": "a server player's teleport before it reaches their client: `native_player_teleport(player, name, cause, from_dimension, from, to_dimension, to, relative, cancel)` (Bukkit's `PlayerTeleportEvent`, NeoForge's `EntityTeleportEvent`) for commands, ender pearls, chorus fruit, a spectator's teleport menu, a dismount and other server moves. A mod may move `to`, cancel, or set the look and motion bits of `relative` (0x1f8 keeps a pearl's speed). No `self`. Bundles that use it need `\"hooks\": @API:player_teleport@`."
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"hooks": [],
|
||||
"helpers": [
|
||||
{"name": "player_textures", "target": "horizon_client::game::native_profiles::Game<'_>::native_player_textures"},
|
||||
{"name": "player_head", "target": "horizon_client::game::native_profiles::Game<'_>::native_player_head"}
|
||||
],
|
||||
"rust_summary": "an online player's `textures` property (`native_player_textures`) and a head stack of any player (`native_player_head`).",
|
||||
"doc_summary": "`native_player_textures(uuid)` gives the `textures` property (value and signature) of a player who is on, and `native_player_head(uuid)` a `minecraft:player_head` whose profile holds the player's id, plus their name and skin when they are on. Bundles need \"hooks\": @API:player_textures@.",
|
||||
"comments": ["crates/horizon-client/src/game/native_profiles.rs"]
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"hooks": [],
|
||||
"helpers": [
|
||||
{"name": "set_player_view_distance", "target": "horizon_client::game::native_view_distance::Game<'_>::native_set_player_view_distance"},
|
||||
{"name": "player_view_distance", "target": "horizon_client::game::native_view_distance::Game<'_>::native_player_view_distance"}
|
||||
],
|
||||
"rust_summary": "one player's view distance (`native_set_player_view_distance`, `native_player_view_distance`).",
|
||||
"doc_summary": "`native_set_player_view_distance(uuid, distance)` limits one player's view distance below the server's (that player alone is told its radius and sent chunks within it); `native_player_view_distance(uuid)` reads it. Bundles need \"hooks\": @API:player_view_distance@.",
|
||||
"comments": [
|
||||
"crates/horizon-client/src/game/native_view_distance.rs"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"hooks": [],
|
||||
"helpers": [
|
||||
{"name": "profile_by_name", "target": "horizon_client::game::native_profiles::Game<'_>::native_profile_by_name"},
|
||||
{"name": "profile_by_uuid", "target": "horizon_client::game::native_profiles::Game<'_>::native_profile_by_uuid"}
|
||||
],
|
||||
"rust_summary": "a player the server knows, by name (`native_profile_by_name`) or id (`native_profile_by_uuid`), from who is on and the profile cache.",
|
||||
"doc_summary": "`native_profile_by_name(name)` gives the id and name, `native_profile_by_uuid(uuid)` the name, of a player who is on or in a dedicated server's profile cache (`usercache.json`), without asking a profile service. Bundles need \"hooks\": @API:profile_cache@.",
|
||||
"comments": ["crates/horizon-client/src/game/native_profiles.rs"]
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"hooks": [],
|
||||
"helpers": [
|
||||
{"name": "set_profile_property", "target": "horizon_client::game::native_profiles::Game<'_>::native_set_profile_property"},
|
||||
{"name": "refresh_player", "target": "horizon_client::game::native_profiles::Game<'_>::native_refresh_player"}
|
||||
],
|
||||
"rust_summary": "an online player's profile property set (`native_set_profile_property`) and the player shown again with it (`native_refresh_player`).",
|
||||
"doc_summary": "`native_set_profile_property(player, name, value)` sets or removes a property (a skin's `textures`) of an online player's profile, and `native_refresh_player(player)` shows them again with it: everyone who sees them gets their list entry and entity anew, and the player is respawned in place so their own client draws it too. Both work from any server hook. Bundles need \"hooks\": @API:profile_refresh@.",
|
||||
"comments": ["crates/horizon-client/src/game/native_profiles.rs"]
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"name": "projectile_world_border",
|
||||
"target": "horizon_client::game::native_hooks::Game<'_>::native_projectile_world_border",
|
||||
"neoforge": "none (26.3's projectile world border collision, MC-310476)",
|
||||
"side": "server",
|
||||
"mod_api": {
|
||||
"contract": "listen"
|
||||
}
|
||||
}
|
||||
],
|
||||
"rust_summary": "a thrown projectile about to cross the world border, which a mod may move, slow or let through (`native_projectile_world_border`).",
|
||||
"doc_summary": "a thrown projectile at the world border: `native_projectile_world_border(dimension, entity, kind, pos, velocity, collide)` is heard each tick a thrown projectile inside the border would end its next step outside it, before 26.3 stops it there (MC-310476). A mod may change its position and velocity, or set `collide` false and it flies through this tick as before 26.3. No `self`. Bundles that use it need `\"hooks\": @API:projectile_world_border@`."
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"hooks": [],
|
||||
"rust_summary": "projectiles and server players' fishing bobbers for `native_remove_entity`.",
|
||||
"doc_summary": "`native_remove_entity(dimension, id)` also removes projectiles (arrows, tridents, snowballs, eggs, pearls, fireballs, wind charges, llama spit, shulker bullets, firework rockets) as `/kill` does, and a server player's fishing bobber (`Entity.discard`): the player in place's, or outside a guest's turn any of the level's players'. Bundles need \"hooks\": @API:remove_projectiles@.",
|
||||
"comments": [
|
||||
"crates/horizon-client/src/game/native_entities.rs"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"hooks": [],
|
||||
"helpers": [
|
||||
{"name": "resend_commands", "target": "horizon_client::game::native_permissions::Game<'_>::native_resend_commands"}
|
||||
],
|
||||
"rust_summary": "a player's command tree sent again, the mods asked anew (`native_resend_commands`), as Bukkit's `Player.updateCommands`.",
|
||||
"doc_summary": "`native_resend_commands(player)` sends a player their command tree again in the next server tick, with `native_player_commands_sent`, its v2 and the permission nodes of the mods' commands asked anew, as Bukkit's `Player.updateCommands`: after a permissions mod changed what they may use. Bundles need \"hooks\": @API:resend_commands@.",
|
||||
"comments": ["crates/horizon-client/src/game/native_permissions.rs"]
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"name": "resource_pack_status_v2",
|
||||
"target": "horizon_client::game::native_hooks::Game<'_>::native_resource_pack_status_v2",
|
||||
"neoforge": "none (NeoForge has no resource pack answer event; Bukkit's PlayerResourcePackStatusEvent with getID)",
|
||||
"side": "server",
|
||||
"mod_api": {
|
||||
"contract": "listen"
|
||||
}
|
||||
}
|
||||
],
|
||||
"rust_summary": "a player's answer to a resource pack with the pack's id, the configuration's answers included (`native_resource_pack_status_v2`), leaving `native_resource_pack_status` as it was.",
|
||||
"doc_summary": "`native_resource_pack_status_v2(player, name, pack, status)`: an additive version of `native_resource_pack_status` with the answered pack's id (Bukkit's `PlayerResourcePackStatusEvent.getID`), heard just before v1 in play, and also for the answers to the server's own pack given during the configuration, once the player has logged in. v1 keeps its signature and firing point. Bundles need \"hooks\": @API:resource_pack_status_v2@."
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"hooks": [],
|
||||
"helpers": [
|
||||
{"name": "send_resource_pack", "target": "horizon_client::game::native_packets::Game<'_>::native_send_resource_pack"},
|
||||
{"name": "pop_resource_pack", "target": "horizon_client::game::native_packets::Game<'_>::native_pop_resource_pack"}
|
||||
],
|
||||
"rust_summary": "a mod's own resource packs for one player, offered and taken away by id (`native_send_resource_pack`, `native_pop_resource_pack`).",
|
||||
"doc_summary": "`native_send_resource_pack(player, id, url, sha1, prompt, required)` offers one player's client a resource pack under the mod's own id (Bukkit's `Player.addResourcePack`), and `native_pop_resource_pack(player, id)` takes that pack, or with None every server pack, away again (`removeResourcePack`). Bundles need \"hooks\": @API:resource_packs@.",
|
||||
"comments": ["crates/horizon-client/src/game/native_packets.rs"]
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"hooks": [],
|
||||
"helpers": [
|
||||
{"name": "respawn_player", "target": "horizon_client::game::native_travel::Game<'_>::native_respawn_player"}
|
||||
],
|
||||
"rust_summary": "a dead server player respawned without their client's Respawn click (`native_respawn_player`), as Spigot's `player.spigot().respawn()`.",
|
||||
"doc_summary": "`native_respawn_player(uuid)` respawns a dead server player as their client's Respawn click does, after the players' packets of the server tick. Bundles need \"hooks\": @API:respawn_player@.",
|
||||
"comments": ["crates/horizon-client/src/game/native_travel.rs"]
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"name": "right_click_block_v2",
|
||||
"target": "horizon_client::game::native_hooks::Game<'_>::native_right_click_block_v2",
|
||||
"neoforge": "PlayerInteractEvent.RightClickBlock (getHand)",
|
||||
"side": "server",
|
||||
"cancellable": true,
|
||||
"mod_api": {
|
||||
"contract": "cancel",
|
||||
"out": "cancel"
|
||||
}
|
||||
}
|
||||
],
|
||||
"rust_summary": "a player's right click on a block with the hand that clicks, cancellable (`native_right_click_block_v2`).",
|
||||
"doc_summary": "`native_right_click_block_v2(pos, face, hand, cancel)`: `native_right_click_block` with the hand (`Hand::Main` or `Hand::Off`, NeoForge's `PlayerInteractEvent.RightClickBlock.getHand`), asked right after v1, once per hand tried; a click v1 canceled is not asked again. Bundles need \"hooks\": @API:right_click_block_v2@."
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"hooks": [],
|
||||
"helpers": [
|
||||
{"name": "run_command_as", "target": "horizon_client::game::native_server::Game<'_>::native_run_command_as"}
|
||||
],
|
||||
"rust_summary": "a command run as a player typing it would, at their level, with its result and answer (`native_run_command_as`).",
|
||||
"doc_summary": "`native_run_command_as(player, line)` runs `line` as that player typing it would (Bukkit's `Player.performCommand`): their source and operator level, mods' registered commands included; the answer goes to them and comes back in the `NativeCommandResult`. Bundles need \"hooks\": @API:run_command_as@.",
|
||||
"comments": [
|
||||
"crates/horizon-client/src/game/native_server.rs"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"hooks": [],
|
||||
"helpers": [
|
||||
{"name": "run_command_captured", "target": "horizon_client::game::native_server::Game<'_>::native_run_command_captured"}
|
||||
],
|
||||
"rust_summary": "a console command run with its result and answer kept (`native_run_command_captured`).",
|
||||
"doc_summary": "`native_run_command_captured(line)` runs `line` as the console (a mod's registered command going to the mods, anything else to vanilla's) and returns a `NativeCommandResult`: whether it ran, vanilla's result value and the lines it answered with, which reach no one else. Bundles need \"hooks\": @API:run_command_captured@.",
|
||||
"comments": [
|
||||
"crates/horizon-client/src/game/native_server.rs"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"hooks": [],
|
||||
"helpers": [
|
||||
{"name": "run_mod_command", "target": "horizon_client::game::native_server::Game<'_>::native_run_mod_command"}
|
||||
],
|
||||
"rust_summary": "a console command that reaches the mods' registered commands, then vanilla's (`native_run_mod_command`).",
|
||||
"doc_summary": "`native_run_mod_command(line)` runs `line` as the console, a mod's registered command going to the mods and anything else to vanilla's (Bukkit's `dispatchCommand`); whether it ran. Bundles need \"hooks\": @API:run_mod_command@.",
|
||||
"comments": [
|
||||
"crates/horizon-client/src/game/native_server.rs"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"name": "screen_text_field_char_typed_pre",
|
||||
"target": "horizon_client::game::native_hooks::Game<'_>::native_screen_text_field_char_typed_pre",
|
||||
"side": "client",
|
||||
"cancellable": true,
|
||||
"mod_api": {
|
||||
"contract": "cancel",
|
||||
"out": "canceled"
|
||||
}
|
||||
}
|
||||
],
|
||||
"rust_summary": "the current focused text field, before each character is inserted, with an editable value and caret.",
|
||||
"doc_summary": "`native_screen_text_field_char_typed_pre(screen, field, character, modifiers, canceled)` exposes NativeTextField before each surviving character is inserted. Edit value/cursor/highlight and cancel to replace the character yourself. Indices count Unicode scalar characters; max_length() and multiline() are read-only host limits. Actual text-box filters, limits and responders still apply. API104 runs exactly once before this hook for each character, including title keyboard/control input, which previously bypassed that filter. Supported: ordinary title fields, chat/command inputs, creative/recipe/mod-browser searches, anvil rename, social and debug search. Options, preparing screens, game-rule/numeric editors, books, signs and dialogs keep their existing specialized paths. Chat suggestions are separate. Bundles need \"hooks\": @API:screen_text_field@."
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"hooks": [],
|
||||
"helpers": [
|
||||
{"name": "send_custom_payload_to_player", "target": "horizon_client::game::native_net::Game<'_>::native_send_custom_payload_to_player"}
|
||||
],
|
||||
"rust_summary": "a plugin channel message to one player's client on any channel outside `horizon:` (`native_send_custom_payload_to_player`).",
|
||||
"doc_summary": "`native_send_custom_payload_to_player(player, channel, data)` sends a `ClientboundCustomPayloadPacket` on any `namespace:path` channel outside `horizon:` (at most 1 MiB) to one player's client, a vanilla one included, at the tick's end, from any server hook, as Bukkit's `Player.sendPluginMessage`. Bundles need \"hooks\": @API:send_custom_payload_to_player@.",
|
||||
"comments": ["crates/horizon-client/src/game/native_net.rs"]
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"hooks": [],
|
||||
"helpers": [
|
||||
{
|
||||
"name": "send_particles_to",
|
||||
"target": "horizon_client::game::native_hooks::Game<'_>::native_send_particles_to"
|
||||
}
|
||||
],
|
||||
"rust_summary": "particles for one player alone (`native_send_particles_to`), as vanilla's `ServerLevel.sendParticles(player, ...)`.",
|
||||
"doc_summary": "`native_send_particles_to(uuid, options, state, at, spread, speed, count, force)` (`host.send_particles_to`) shows particles to one player alone, within 32 blocks or 512 with `force`, as vanilla's `ServerLevel.sendParticles(player, ...)` and Bukkit's `Player.spawnParticle`; it leaves at the tick's end, so it works from any server hook. Bundles need \"hooks\": @API:send_particles_to@."
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"hooks": [],
|
||||
"helpers": [
|
||||
{"name": "max_players", "target": "horizon_client::game::native_player_reads::Game<'_>::native_max_players"},
|
||||
{"name": "server_dir", "target": "horizon_client::game::native_player_reads::Game<'_>::native_server_dir"},
|
||||
{"name": "log_file", "target": "horizon_client::game::native_player_reads::Game<'_>::native_log_file"}
|
||||
],
|
||||
"rust_summary": "the server's player limit, folder and log file (`native_max_players`, `native_server_dir`, `native_log_file`).",
|
||||
"doc_summary": "`native_max_players()` gives the server's player limit (Bukkit's `Server.getMaxPlayers`), `native_server_dir()` a dedicated server's folder and `native_log_file()` the `latest.log` this run writes. Bundles need \"hooks\": @API:server_files@.",
|
||||
"comments": ["crates/horizon-client/src/game/native_player_reads.rs"]
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"name": "server_packet",
|
||||
"target": "horizon_client::game::native_hooks::Game<'_>::native_server_packet",
|
||||
"neoforge": "none (NeoForge has no outgoing packet event; ProtocolLib's and PacketEvents' packet listeners)",
|
||||
"side": "server",
|
||||
"cancellable": true,
|
||||
"mod_api": {
|
||||
"contract": "cancel",
|
||||
"out": "cancel"
|
||||
}
|
||||
}
|
||||
],
|
||||
"helpers": [
|
||||
{
|
||||
"name": "watch_server_packets",
|
||||
"target": "horizon_client::game::native_packets::Game<'_>::native_watch_server_packets"
|
||||
}
|
||||
],
|
||||
"rust_summary": "every play packet the server sends a player, decoded, changeable and cancellable (`native_server_packet`), once a mod switched it on (`native_watch_server_packets`).",
|
||||
"doc_summary": "`native_server_packet(player, packet, cancel)` hears each play packet the server is about to send one player's client, decoded, a broadcast once for each player: change `packet` and that client gets the changed one, set `cancel` and it gets nothing (ProtocolLib's outgoing listener). Nothing is decoded or copied until a mod calls `native_watch_server_packets()`. Bundles need \"hooks\": @API:server_packet@.",
|
||||
"comments": [
|
||||
"crates/horizon-client/src/game/native_packets.rs"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"name": "server_packets",
|
||||
"target": "horizon_client::game::native_hooks::Game<'_>::native_server_packets",
|
||||
"neoforge": "none (NeoForge has no packet counting event; PacketEvents' PacketSendEvent and PacketReceiveEvent)",
|
||||
"side": "server",
|
||||
"cancellable": false,
|
||||
"mod_api": {
|
||||
"contract": "listen"
|
||||
}
|
||||
}
|
||||
],
|
||||
"helpers": [
|
||||
{
|
||||
"name": "count_packets",
|
||||
"target": "horizon_client::game::native_diagnostics::Game<'_>::native_count_packets"
|
||||
}
|
||||
],
|
||||
"rust_summary": "each player's play packets once a tick, by id and body bytes, sent and received (`native_server_packets`), while a mod counts them (`native_count_packets`).",
|
||||
"doc_summary": "`native_server_packets(player, packets, outbound)`, once a tick per player and way, each packet as (id, body bytes), while `native_count_packets(true)` is on (off, nothing is kept). Bundles need \"hooks\": @API:server_packets@.",
|
||||
"comments": [
|
||||
"crates/horizon-client/src/game/native_diagnostics.rs"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"hooks": [],
|
||||
"rust_summary": "more of a player on `NativeServerPlayer`: latency, protocol, experience, ground, abilities, sneaking, eye height, selected slot, nickname and hidden level.",
|
||||
"doc_summary": "`NativeServerPlayer` also carries `latency` (ms), `protocol`, `xp_level` and `xp_progress`, `on_ground`, the abilities the client is told (`flying`, `may_fly`, `invulnerable`, `walk_speed`, `fly_speed`), `sneaking`, `eye_height` in the current pose, `selected_slot`, and the `nickname` and `hidden` level server mods set, as Bukkit's `getPing`, `isOnGround`, `getAllowFlight`, `isSneaking`, `getEyeHeight` and `getDisplayName` read them. Bundles need \"hooks\": @API:server_player_state@."
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"name": "server_tick_timed",
|
||||
"target": "horizon_client::game::native_hooks::Game<'_>::native_server_tick_timed",
|
||||
"neoforge": "none (NeoForge has no tick timing event; spark's tick monitor and profiler)",
|
||||
"side": "server",
|
||||
"cancellable": false,
|
||||
"mod_api": {
|
||||
"contract": "listen"
|
||||
}
|
||||
}
|
||||
],
|
||||
"helpers": [
|
||||
{
|
||||
"name": "spans",
|
||||
"target": "horizon_client::game::native_diagnostics::Game<'_>::native_spans"
|
||||
},
|
||||
{
|
||||
"name": "mob_cost_sampling",
|
||||
"target": "horizon_client::game::native_diagnostics::Game<'_>::native_mob_cost_sampling"
|
||||
},
|
||||
{
|
||||
"name": "take_mob_costs",
|
||||
"target": "horizon_client::game::native_diagnostics::Game<'_>::native_take_mob_costs"
|
||||
}
|
||||
],
|
||||
"rust_summary": "each server tick's wall and CPU time with its spans (`native_server_tick_timed`, `TickSpan`), a shared switch for the spans (`native_spans`), and mob costs a mod samples apart from performance reports (`native_mob_cost_sampling`, `native_take_mob_costs`, `NativeMobCost`).",
|
||||
"doc_summary": "`native_server_tick_timed(wall_ms, cpu_ms, spans)` after each server tick pass while spans are recorded (always on a dedicated server; elsewhere after `native_spans(true)`), and mob costs by level and type a mod samples apart from `horizon-report` (`native_mob_cost_sampling(on)`, `native_take_mob_costs()`). Bundles need \"hooks\": @API:server_tick_timed@.",
|
||||
"comments": [
|
||||
"crates/horizon-client/src/game/native_diagnostics.rs"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"hooks": [],
|
||||
"helpers": [
|
||||
{"name": "biome", "target": "horizon_client::game::native_hooks::Game<'_>::native_biome"},
|
||||
{"name": "data_dir", "target": "horizon_client::game::native_hooks::Game<'_>::native_data_dir"}
|
||||
],
|
||||
"rust_summary": "what a client HUD reads of its session: `native_client_state`'s `screen`, `debug_screen`, `world`\n(`NativeWorldId`) and `sleeping_pos`, the biome at a block (`native_biome`) and a mod's own folder (`native_data_dir`).",
|
||||
"doc_summary": "what a client HUD reads of its session: `native_client_state()` gains `screen` (`NativeScreenKind`: playing, chat, death or another screen), `debug_screen` (F3), `world` (`NativeWorldId`: single player's save folder or a server's address, for per-world data) and `sleeping_pos` (the bed); `native_biome(pos)` is the biome at a block as `Level.getBiome` picks it, and `native_data_dir(mod_id)` a mod's own folder. Bundles need \"hooks\": @API:session_state@.",
|
||||
"comments": ["crates/horizon-client/src/game/native_client_services.rs"]
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"hooks": [],
|
||||
"helpers": [
|
||||
{"name": "set_display_name", "target": "horizon_client::game::native_server::Game<'_>::native_set_display_name"}
|
||||
],
|
||||
"rust_summary": "a player's styled name in chat (`native_set_display_name`), as Bukkit's `Player.setDisplayName`.",
|
||||
"doc_summary": "`native_set_display_name(uuid, name)` shows styled text for a player in chat (their messages, `/me`, `/msg`, the join and leave lines) in place of their nickname or name (None: back), inside their team's formatting, the real name on hover. Kept by UUID. Bundles need \"hooks\": @API:set_display_name@.",
|
||||
"comments": ["crates/horizon-client/src/game/native_server.rs"]
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"hooks": [],
|
||||
"helpers": [
|
||||
{"name": "set_flight", "target": "horizon_client::game::native_travel::Game<'_>::native_set_flight"}
|
||||
],
|
||||
"rust_summary": "a server player let fly in any game mode and set flying (`native_set_flight`), as Bukkit's `Player.setAllowFlight` and `setFlying`.",
|
||||
"doc_summary": "`native_set_flight(uuid, allow, flying)` lets a server player fly in any game mode or not, and sets whether they fly now, until their game mode changes, they respawn or leave. Bundles need \"hooks\": @API:set_flight@.",
|
||||
"comments": ["crates/horizon-client/src/game/native_travel.rs"]
|
||||
}
|
||||
Loaded 100 of 773 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user