Skip to content

Commit 74b90c0

Browse files
committed
Enhance BridgeConnection and PlayerDataStore functionality
- Introduced a timeout for synchronous bridge calls to prevent indefinite blocking. - Updated the subscribe method to handle EnumValue for priority. - Improved event handling for command events to avoid deadlocks. - Enhanced player data storage to resolve player data directory dynamically. - Added new properties to the Player class for better interaction with player states (e.g., is_hand_raised, is_blocking, etc.). - Updated loot generation methods to return Item objects instead of dicts for better type safety and usability. - Implemented fallback mechanisms in task decorators to handle transient bridge issues.
1 parent 91d72ed commit 74b90c0

16 files changed

Lines changed: 1465 additions & 94 deletions

File tree

docs/src/core/player.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,48 @@ Whether the player is sneaking (shift held).
138138

139139
Whether the player is sprinting.
140140

141+
### is_hand_raised
142+
143+
- **Type:** `bool`
144+
145+
Whether the player is currently using an item (holding right-click).
146+
147+
### hand_raised
148+
149+
- **Type:** [`EquipmentSlot`](enums.md) `| None`
150+
151+
Which hand is currently being used (`EquipmentSlot.HAND` or `EquipmentSlot.OFF_HAND`). `None` if the player is not using an item.
152+
153+
### is_blocking
154+
155+
- **Type:** `bool`
156+
157+
Whether the player is actively blocking (for example with a shield).
158+
159+
### item_in_use
160+
161+
- **Type:** [`Item`](item.md) `| None`
162+
163+
The item currently being used by the player.
164+
165+
### item_in_use_ticks
166+
167+
- **Type:** `int`
168+
169+
How many ticks the current `item_in_use` has been used.
170+
171+
### is_sleeping
172+
173+
- **Type:** `bool`
174+
175+
Whether the player is currently sleeping in a bed.
176+
177+
### sleep_ticks
178+
179+
- **Type:** `int`
180+
181+
How long the player has been sleeping, in ticks.
182+
141183
### scoreboard
142184

143185
- **Type:** [`Scoreboard`](scoreboard.md)

docs/src/extensions/loottable.md

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,18 @@ Generate loot from all pools.
7070
- `luck` *(float)* — Luck factor for bonus rolls.
7171
- **Returns:** `list[dict]` — Item dicts with `"material"` and `"amount"` keys.
7272

73+
#### .generate_into(inventory, context=None, luck=0.0)
74+
75+
Generate loot and insert directly into an inventory.
76+
77+
- **Parameters:**
78+
- `inventory` (`Inventory`) — Target inventory.
79+
- `context` *(Any)* — Context passed to conditions.
80+
- `luck` *(float)* — Luck factor for bonus rolls.
81+
- **Returns:** `list[Item]` — Inserted items.
82+
83+
Items are spread across random slots (vanilla-style chest distribution).
84+
7385
#### .generate_stacked(context=None, luck=0.0)
7486

7587
Generate loot and combine items of the same material into stacks.
@@ -95,7 +107,7 @@ pool = LootPool(name="pool", rolls=1, bonus_rolls=0)
95107
Add an entry to this pool.
96108

97109
- **Parameters:**
98-
- `item` — Material name string or item dict (`{"material": ..., "amount": ...}`).
110+
- `item` — Material name string, `Item` object, or item dict (`{"material": ..., "amount": ...}`).
99111
- `weight` *(int)* — Relative probability weight. Default `1`.
100112
- `min_amount` *(int)* — Minimum stack size. Default `1`.
101113
- `max_amount` *(int)* — Maximum stack size. Default `1`.
@@ -118,6 +130,18 @@ Generate loot from this pool only.
118130

119131
- **Returns:** `list[dict]`
120132

133+
#### .generate_into(inventory, context=None, luck=0.0)
134+
135+
Generate pool loot and insert into an inventory.
136+
137+
- **Parameters:**
138+
- `inventory` (`Inventory`) — Target inventory.
139+
- `context` *(Any)* — Context passed to conditions.
140+
- `luck` *(float)* — Luck factor for bonus rolls.
141+
- **Returns:** `list[Item]`
142+
143+
Items are spread across random slots (vanilla-style chest distribution).
144+
121145
---
122146

123147
## LootEntry

pjb

Lines changed: 211 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ Usage:
77
"""
88

99
import re
10+
import shutil
1011
import sys
1112
from pathlib import Path
1213

@@ -237,6 +238,7 @@ def build_symbol_index(docs_dir):
237238
lines = content.split("\n")
238239

239240
class_name = md_file.stem.capitalize()
241+
is_enum_doc = md_file.stem == "enums"
240242
section = ""
241243
subtitle = ""
242244
in_code_block = False
@@ -276,6 +278,40 @@ def build_symbol_index(docs_dir):
276278
elif stripped.startswith("## ") and not stripped.startswith("### "):
277279
section = stripped.lstrip("# ").strip()
278280

281+
# docs/src/helpers/enums.md stores enum "classes" as ## headings.
282+
# Index them as top-level symbols so queries like "ItemFlag" work.
283+
if is_enum_doc and " " not in section:
284+
class_name = section
285+
desc = _get_desc_after(lines, i)
286+
symbols.append({
287+
"class_name": class_name,
288+
"name": None,
289+
"section": None,
290+
"qualified": class_name,
291+
"desc": desc,
292+
"file": str(md_file.relative_to(docs_dir).with_suffix('')),
293+
"line": i + 1,
294+
"enum_section_line": i + 1,
295+
})
296+
297+
elif is_enum_doc and class_name and stripped.startswith("| `"):
298+
# docs/src/helpers/enums.md stores enum values in markdown tables.
299+
# Index values as members so queries like "ItemFlag." work.
300+
m = re.match(r"\|\s*`([^`]+)`\s*\|", stripped)
301+
if m:
302+
member = m.group(1).strip()
303+
if member and member != "Name":
304+
symbols.append({
305+
"class_name": class_name,
306+
"name": member,
307+
"section": "Values",
308+
"qualified": f"{class_name}.{member}",
309+
"desc": "",
310+
"sig": None,
311+
"file": str(md_file.relative_to(docs_dir).with_suffix('')),
312+
"line": i + 1,
313+
})
314+
279315
elif stripped.startswith("### "):
280316
member = stripped.lstrip("# ").strip()
281317
# Only index code-like symbols (no spaces = identifier)
@@ -329,6 +365,73 @@ def _get_signature(lines, heading_idx, member):
329365
return f"({m.group(1)})"
330366
return None
331367

368+
def _load_stub_enum_values():
369+
"""Load enum constants from bridge __init__.pyi for richer completion."""
370+
candidates = [
371+
SCRIPT_DIR.parent / "src" / "main" / "resources" / "python" / "bridge" / "__init__.pyi",
372+
SCRIPT_DIR / "src" / "main" / "resources" / "python" / "bridge" / "__init__.pyi",
373+
Path.cwd() / "src" / "main" / "resources" / "python" / "bridge" / "__init__.pyi",
374+
]
375+
stub_path = next((p for p in candidates if p.is_file()), None)
376+
if stub_path is None:
377+
return {}
378+
379+
out = {}
380+
current_class = None
381+
for raw in stub_path.read_text(encoding="utf-8").splitlines():
382+
m_class = re.match(r"^class\s+(\w+)\(EnumValue\):", raw)
383+
if m_class:
384+
current_class = m_class.group(1)
385+
out.setdefault(current_class, [])
386+
continue
387+
388+
if current_class is None:
389+
continue
390+
391+
if raw.startswith("class "):
392+
current_class = None
393+
continue
394+
395+
m_member = re.match(r"^\s+([A-Z][A-Z0-9_]*)\s*:\s*ClassVar\[", raw)
396+
if m_member:
397+
out[current_class].append(m_member.group(1))
398+
399+
return out
400+
401+
def _merge_stub_enum_symbols(symbols):
402+
"""Add enum constants from stubs when docs are incomplete."""
403+
enum_values = _load_stub_enum_values()
404+
if not enum_values:
405+
return symbols
406+
407+
class_meta = {}
408+
for s in symbols:
409+
if s.get("name") is None:
410+
class_meta.setdefault(s["class_name"], s)
411+
412+
existing = {(s["class_name"], s.get("name")) for s in symbols}
413+
for class_name, members in enum_values.items():
414+
meta = class_meta.get(class_name)
415+
if meta is None:
416+
continue
417+
for member in members:
418+
key = (class_name, member)
419+
if key in existing:
420+
continue
421+
symbols.append({
422+
"class_name": class_name,
423+
"name": member,
424+
"section": "Values",
425+
"qualified": f"{class_name}.{member}",
426+
"desc": "",
427+
"sig": None,
428+
"file": meta.get("file", "helpers/enums"),
429+
"line": meta.get("line", 1),
430+
})
431+
existing.add(key)
432+
433+
return symbols
434+
332435
def _strip_md(line):
333436
"""Strip markdown formatting from a line for terminal display."""
334437
line = re.sub(r'\[([^\]]*)\]\([^)]*\)', r'\1', line) # links
@@ -341,6 +444,34 @@ def _sort_key(s):
341444
"""Sort key that orders _ after letters (replace _ with ~ which sorts after z)."""
342445
return s.lower().replace('_', '~')
343446

447+
def _print_compact_members(members):
448+
"""Print members as a width-wrapped comma-separated list."""
449+
values = []
450+
for s in members:
451+
sig = s.get('sig', '') or ''
452+
values.append(f".{s['name']}{sig}")
453+
454+
term_cols = shutil.get_terminal_size((120, 20)).columns
455+
indent = " "
456+
max_width = max(30, term_cols - len(indent))
457+
458+
line_parts = []
459+
line_len = 0
460+
for value in values:
461+
part = value if not line_parts else f", {value}"
462+
part_len = len(part)
463+
464+
if line_parts and line_len + part_len > max_width:
465+
print(f"{indent}\033[36m{''.join(line_parts)}\033[0m")
466+
line_parts = [value]
467+
line_len = len(value)
468+
else:
469+
line_parts.append(part)
470+
line_len += part_len
471+
472+
if line_parts:
473+
print(f"{indent}\033[36m{''.join(line_parts)}\033[0m")
474+
344475

345476
# Extension doc file stems (matches Extensions section in docs/build.py)
346477
_EXT_FILES = {
@@ -572,14 +703,57 @@ def _get_member_doc(md_path, heading_line):
572703

573704
return "\n".join(collapsed)
574705

706+
def _get_enum_section_doc(md_path, heading_line):
707+
"""Extract a ## section block (used for enum docs in enums.md)."""
708+
content = md_path.read_text(encoding="utf-8")
709+
lines = content.split("\n")
710+
711+
out = []
712+
in_code_block = False
713+
start = max(0, heading_line - 1)
714+
715+
for j in range(start, len(lines)):
716+
line = lines[j]
717+
stripped = line.strip()
718+
719+
if stripped.startswith("```"):
720+
in_code_block = not in_code_block
721+
continue
722+
723+
if not in_code_block and j > start and stripped.startswith("## "):
724+
break
725+
726+
if in_code_block:
727+
out.append(f" {_highlight_python(line.rstrip())}")
728+
elif stripped.startswith("## ") and j == start:
729+
title = stripped.lstrip('# ').strip()
730+
out.append(f" \033[36;1m# {title}\033[0m")
731+
elif stripped.startswith("- "):
732+
out.append(f" {_strip_md(line.rstrip())}")
733+
elif stripped:
734+
out.append(f" {_strip_md(line.rstrip())}")
735+
else:
736+
out.append("")
737+
738+
while out and not out[-1].strip():
739+
out.pop()
740+
741+
collapsed = []
742+
for line in out:
743+
if not line.strip() and collapsed and not collapsed[-1].strip():
744+
continue
745+
collapsed.append(line)
746+
747+
return "\n".join(collapsed)
748+
575749
def cmd_search(query):
576750
docs_dir = find_docs_dir()
577751
if docs_dir is None:
578752
print("Error: Could not find docs/src directory.")
579753
print("Run this from the PyJavaBridge project root.")
580754
sys.exit(1)
581755

582-
symbols = build_symbol_index(docs_dir)
756+
symbols = _merge_stub_enum_symbols(build_symbol_index(docs_dir))
583757

584758
if query.endswith("."):
585759
# "Player." → list all members of that class
@@ -591,14 +765,28 @@ def cmd_search(query):
591765
ext = _EXT_TAG if Path(members[0]['file']).stem in _EXT_FILES else ""
592766
print(f"\033[1m{members[0]['class_name']}\033[0m \033[90m({members[0]['file']}.md)\033[0m{ext}")
593767
current_section = None
594-
for s in members:
595-
section = s['section'] or ''
768+
section_members = []
769+
for s in members + [None]:
770+
if s is None:
771+
section = None
772+
else:
773+
section = s['section'] or ''
774+
596775
if section != current_section:
776+
if current_section and section_members:
777+
if current_section == "Values":
778+
_print_compact_members(section_members)
779+
else:
780+
for m in section_members:
781+
sig = m.get('sig', '') or ''
782+
print(f" \033[90m###\033[0m \033[36m.{m['name']}{sig}\033[0m")
783+
section_members = []
597784
current_section = section
598785
if section:
599786
print(f" \033[90m## {section}\033[0m")
600-
sig = s.get('sig', '') or ''
601-
print(f" \033[90m###\033[0m \033[36m.{s['name']}{sig}\033[0m")
787+
788+
if s is not None:
789+
section_members.append(s)
602790

603791
elif "." in query:
604792
# "Player.name" → find specific member(s)
@@ -647,13 +835,22 @@ def cmd_search(query):
647835
if any(s["class_name"].lower() == q for s in classes):
648836
classes = [s for s in classes if s["class_name"].lower() == q]
649837
if classes:
838+
members_by_class = {}
839+
for sym in symbols:
840+
if sym.get("name"):
841+
members_by_class.setdefault(sym["class_name"], []).append(sym)
650842
lines_printed = 0
651843
for s in classes:
652844
ext = _EXT_TAG if Path(s['file']).stem in _EXT_FILES else ""
653845
# Show the class-level doc text (everything before first ### member)
654846
md_path = docs_dir / f"{s['file']}.md"
655847
if md_path.exists():
656-
doc = _get_class_doc(md_path, ext)
848+
if s.get("enum_section_line"):
849+
doc = _get_enum_section_doc(md_path, s["enum_section_line"])
850+
if ext:
851+
doc = doc.replace("\033[0m", f"\033[0m{ext}", 1)
852+
else:
853+
doc = _get_class_doc(md_path, ext)
657854
else:
658855
doc = f" \033[36;1m{s['class_name']}\033[0m{ext}"
659856
doc_lines = doc.count('\n') + 1
@@ -664,6 +861,14 @@ def cmd_search(query):
664861
print(doc)
665862
lines_printed += doc_lines
666863

864+
# For exact class matches, also show indexed members (useful for enums).
865+
if s["class_name"].lower() == q:
866+
members = members_by_class.get(s["class_name"], [])
867+
if members:
868+
members = sorted(members, key=lambda m: _sort_key(m["name"]))
869+
print(" \033[90m## Values\033[0m")
870+
_print_compact_members(members)
871+
667872
else:
668873
# No class match → search member names across all classes
669874
results = []

releases/pyjavabridge-dev.jar

26.3 KB
Binary file not shown.

0 commit comments

Comments
 (0)