Compare commits

..
Author SHA1 Message Date
sysadminandClaude Opus 5 0fb2b27d7c fix(addons): add minecraft:geometry to the 7 untextured custom blocks
Without a minecraft:geometry component Bedrock renders a custom block
through the legacy blocks.json texture path instead of material_instances.
These packs' blocks.json files only set "sound", with no "textures", so the
client substituted a default and the blocks rendered untextured. Diagnosed
and verified in-game on redstone-link; this applies the same one-line fix to
the rest.

Confirmed in the field for the portal blocks: portal_jamie, portal_lyla and
portal_mya all fell back to the same default, so players had to guess which
portal led to which world. They reference vanilla textures (emerald_block,
amethyst_block, prismarine_bricks), so with geometry set they render green,
purple and teal respectively -- one per child.

Also fixes home_sign, smart_crafting_table, tent_canvas and portal_frame.

full_block is correct for all seven: every custom block model in these packs
(hammock_slab, tent_panel_l/r, portal_field) is already referenced by its own
block, and no orphan model exists for any of these, so they were always meant
to be plain cubes.

No pack versions bumped deliberately. Block definitions live in the behavior
pack and reach clients through the network block palette rather than a cached
resource pack, so a bump is unnecessary -- and bumping would require updating
the matching pin in all four world_behavior_packs.json files, where a
mismatch silently drops the pack from the stack.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Gf1kZypRDfPL1YiWUS1LC
2026-08-28 12:27:59 +01:00
sysadminandClaude Opus 5 1e82825f8f fix(redstone-link): add minecraft:geometry so material_instances applies
The blocks rendered as an untextured default. Root cause: with no
minecraft:geometry component, Bedrock renders the block through the legacy
blocks.json texture path instead of material_instances. blocks.json here
only sets "sound", with no "textures", so the client substituted a default.

This produces no error in the content log -- both paths are valid, they just
resolve differently -- which is why the pack looked correct everywhere:
files installed byte-perfect, pack activated, terrain_texture.json valid,
and a clean world with only this addon still showed the wrong texture.

Adding "minecraft:geometry": "minecraft:geometry.full_block" fixes it,
verified in-game.

BP bumped to 1.0.1 so clients cannot reuse the cached copy. The lobby world
pin is updated to match; a mismatch silently drops the pack from the stack.

Note: 7 of the repo's other custom blocks have the same defect -- home_sign,
smart_crafting_table, tent_canvas, and all four lobby portal blocks. Most
blocks here DO set geometry, so those seven look like oversights rather than
intent. Not fixed in this commit; they touch every live world.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Gf1kZypRDfPL1YiWUS1LC
2026-08-28 12:22:22 +01:00
sysadminandClaude Opus 5 db8931784e feat(redstone-link): real block textures via build-textures.py, add build.sh
The first textures were a motif floating on a flat slab. That hits the
lesson already documented on mailbox_block(): an icon-like sprite "reads
as a placeholder when applied to all 6 faces of a cube". Replaced with
machine faces whose traces run out to the edges, so they connect across
tile boundaries and a wall of them reads as continuous circuitry --
transmitter radiating out, receiver feeding in.

Generated through scripts/build-textures.py using the existing palette and
rect/px/save helpers rather than an ad-hoc script, so they regenerate with
everything else. Running the pipeline also rewrites the other five
textures, but pixel-identically -- that churn is only a newer Pillow
re-encoding, so those files are left alone.

RP header bumped to 1.0.1 (with the BP dependency to match) because clients
cache packs by uuid+version and would otherwise keep serving the old art.
The lobby world pin was updated to match; a mismatch silently drops the
pack from the stack.

Adds build.sh to package the .mcaddon, mirroring addon/build.sh. It omits
that script's `-x` exclude: these packs have no dotfiles, and dropping it
keeps the script working with the 7-Zip `zip` shim on the workstation as
well as Info-ZIP on the CI runner.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Gf1kZypRDfPL1YiWUS1LC
2026-08-28 11:40:16 +01:00
sysadminandClaude Opus 5 8ded29cbcc feat(redstone-link): wireless redstone addon; track family-goals
Adds redstone-link-addon: a transmitter/receiver pair bound by channel
name, giving redstone that runs anywhere without wire, support blocks or
line of sight. Bedrock custom blocks can read redstone power but cannot
emit it, so a live receiver is swapped to a vanilla redstone_block and
back; receiver positions persist in a world dynamic property, with a
reconcile pass at boot to repair anything a crash left inconsistent.
Deployed and proven end-to-end on lobby (Hub World).

Also commits family-goals-addon, which was running on jamie -- mounted in
docker-compose.yml and pinned in the world -- while existing in no commit
on any branch. It was unbacked-up production code.

That omission was load-bearing for deploys: deploy.yml checks out
docker-compose.yml from origin/main, so a deploy would have replaced the
host compose with a version lacking the family-goals mount and silently
killed the addon. Both new directories are added to the workflow's PATHS
and push-trigger list, so nothing is mounted that CI does not deliver.

The mount set in this file now matches the live host exactly.

Note for a follow-up: dynamite-, hemp-, naturalist-lite-, smart-crafting-,
tow-boat- and trees-features-addon are mounted but absent from PATHS, so
CI never refreshes them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Gf1kZypRDfPL1YiWUS1LC
2026-08-28 11:06:46 +01:00
sysadminandClaude Opus 4.7 b28e4d0610 fix(hemp): trader scope to held stack + chest loot guard + wild scatter bridge
Three mid-session fixes to the hemp progression mechanics:

- Trader buyback now operates on the held stack only. Earlier code
  walked the entire inventory to count and consume hemp items, so
  right-clicking with a stack of 64 buds while having more in other
  slots traded everything. Reproduced by user holding 64, ended up
  losing all stacks. Fixed: count = stack.amount, consume modifies
  only the selected hotbar slot. Other stacks of the same item stay
  intact.

- Chest loot handler removed the `if (stack) return` guard. It was
  meant to skip placement events but blocked nearly every legitimate
  chest open (players almost always hold an item). Now every fresh
  chest open rolls; per-chest seeded set still prevents repeats.
  Added a chat ping when seeds drop so players notice.

- Added a script-side wild hemp scatter on a 60s tick to bridge the
  worldgen gap. Bedrock features only fire on FRESH chunk generation,
  so legacy worlds where players have already explored never see the
  feature_rule patches. Scatter picks a random online overworld
  player every minute (40% chance), finds a grass surface within 32
  blocks, places 2-4 mature hemp_crop in a small cluster. Runs in
  parallel with the worldgen feature_rule for new chunks.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 01:07:27 +01:00
sysadminandClaude Opus 4.7 fd73ac55ec revert(hub-return): drop recovery_compass texture override
Deploy Addons / deploy (push) Successful in 15s
Three frame-count attempts (256×16 / 512×16 / 16×16) all caused the
same diagonal-line GPU corruption on the compass icon and a strip on
screen. Bedrock's recovery_compass renderer needs metadata beyond the
PNG itself — likely a flipbook_textures.json entry, an animation key
in item_texture.json, or an attachable definition — that this RP
doesn't provide. Without a verified working reference pack to copy
the structure from, every guess corrupts client GPU state.

The directional info already lives on the title-slot HUD (rotating
arrow + distance + label refreshed every 5 ticks), so the icon's
job is just "I'm a compass". Vanilla blue triangle does that fine.

RP bumped 1.0.2 → 1.0.5 across the previous failed attempts to force
client cache flush each time; now pinned at 1.0.5 with an empty
textures/ tree. The pack scaffold stays so future hub-return assets
have a place to land.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 23:25:45 +01:00
sysadminandClaude Opus 4.7 14043fe2a0 fix(addons): silence boot-time warnings on naturalist + dynamite
Deploy Addons / deploy (push) Successful in 14s
- naturalist-lite: add stub item definitions for silverlabs_nat:frog_leg,
  cooked_frog_leg, snake_egg_block. Owl + snake behaviour files
  reference these but they were never defined, so BDS logged "Unknown
  item during Deferred ItemDescriptor resolution" on every boot. Stubs
  are functional — frog_leg restores 2 hunger, cooked 4, snake egg is
  a placeable nature-tab item. RP texture entries + lang strings added
  for completeness; icons fall back to candy-cane until art lands.
- dynamite: drop the broken spawn_aoe_cloud component on thrown_banger
  (its particle id "minecraft:explosion_manual" doesn't exist in BDS;
  every replacement I tried also failed schema validation). The
  random.fuse hit_sound + impact_damage still fire, the entity is
  destroyed on hit — just no lingering AOE puff.

Also: add *.bak.local to .gitignore so docker-compose.yml.bak.local and
similar local backup files stop showing up in git status.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 23:15:13 +01:00
sysadminandClaude Opus 4.7 eb82c8307b feat(hemp): wild patches in plains/forest + chest seed injection
Two new ways to find hemp seeds without already having any:

1. Worldgen: minecraft:scatter_feature spawns 1-3 mature
   silverlabs:hemp_crop blocks on grass/dirt in plains/forest/birch/
   flower_forest biomes (~14% scatter chance per chunk surface pass).
2. Chest injection: 8% chance per chest first-open to plant 1-3 seeds
   in a random empty slot. Tracked per-chest via world dynamic property
   (rolling cap of 500 entries) so each chest only contributes once.

Bedrock has no loot-table merge mechanism so we can't add seeds to
vanilla village chests without losing their vanilla loot — script
injection sidesteps that and stays version-independent.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 23:14:54 +01:00
sysadminandClaude Opus 4.7 cc57662468 feat(hub-return): rotating-needle recovery compass texture (32 frames)
Replaces vanilla blue right-pointing-triangle with a red/white compass
needle on a lodestone face, rotating through 32 angular positions
(11.25° per frame). RP bumped 1.0.1 → 1.0.2.

Earlier 16-frame attempt caused GPU sampling beyond the texture buffer
(flashing diagonal-line corruption); Bedrock's recovery_compass_atlas
expects 32 frames at 16×16 each = 512×16 total.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 23:14:40 +01:00
sysadminandClaude Opus 4.7 17a9faf206 feat(scripts): sync-world-pins.py — pre-deploy pin/dep audit + writer
Deploy Addons / deploy (push) Successful in 14s
Single Python utility that catches the silent failure modes responsible
for today's debug session:

  --audit-only   walk every BP→RP manifest dependency, flag drift
  (default)      dry-run pin diff per world (resolves the active world
                 from server.properties so we never write to a stale dir)
  --apply        write corrected world_behavior_packs.json /
                 world_resource_packs.json over SSH
  --restart      after --apply, restart only containers whose pins
                 actually changed

Reads docker-compose.yml to discover what's mounted on each service,
preserves any pin whose pack_id isn't in our managed addon set
(vanilla packs, Security_Sandbox), and never hardcodes credentials —
takes --ssh-pass or env MC_SSH_PASS.

Drop into the deploy muscle memory:
  python3 scripts/sync-world-pins.py --audit-only        # CI-friendly
  MC_SSH_PASS=… python3 scripts/sync-world-pins.py --apply --restart

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 22:01:15 +01:00
sysadminandClaude Opus 4.7 9789906277 fix(addons): align BP→RP manifest deps + minecraft:geometry on placeholder blocks
Two silent failure modes hit eight addons today:

1. BP→RP dep drift. When an RP version was bumped, dependent BPs kept
   naming the old version. Bedrock loaded both packs but disconnected
   the texture pipeline, so blocks rendered as map_color cubes in the
   inventory. Aligned spark_pet, heyhe_pet, camping_supplies, dynamite,
   home_sign, postal_service, private_chest BP deps to actual RP
   versions. Bumped postal/private_chest RP versions to 1.0.1 to bust
   client RP caches.

2. Missing minecraft:geometry. Bedrock 1.21+ silently fails to render
   custom blocks in inventory unless geometry is declared — even for
   plain full cubes, no warning logged. Added
   minecraft:geometry.full_block to post_office, mailbox, and
   private_chest. Same fix already applied to sun_lamp and the wild
   cherry tree blocks in their respective addon commits.

Saved both failure modes to project memory so they're easy to recognise
next time someone sees "all my custom blocks show as solid coloured
cubes in the inventory".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 22:01:00 +01:00
sysadminandClaude Opus 4.7 6bda72598d feat(dynamite): throwable banger/bundle/dynamite entity assets
Entity definitions and projectile textures for the three throwables
(thrown_banger, thrown_bundle, thrown_dynamite). RP version bumped to
1.0.2 and BP→RP dependency aligned in lockstep.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 22:00:42 +01:00
sysadminandClaude Opus 4.7 af9d37462c feat(hub-return): subtitle nav HUD, share waypoints, !nav fallback
Move the directional waypoint HUD off the action bar (which fights mount
saddle/jump UI for screen space) into the title/subtitle slot — large
rotating arrow + distance up top, label underneath, refreshed every 5
ticks so it stays pinned. Active waypoint now persists across container
restarts via per-player dynamic property instead of an in-memory Map.

New:
- !share command + 📤 button on the compass form: pick a waypoint, pick
  a recipient, send them an Accept/Decline prompt; copies into their
  list as "Label (from sender)" with capacity check.
- !nav chat fallback: list waypoints with distances, switch active
  with !nav <n>, !nav off to clear.
- hub_return_transfer_RP scaffold for future asset overrides.

docker-compose: mount the new RP on jamie/lyla/mya.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 22:00:31 +01:00
sysadminandClaude Opus 4.7 7c8cd5b075 feat(addons): hemp plant, wild cherry tree, naturalist-lite
New addons:
- hemp-addon: silverlabs:hemp_crop (5 ages, indoor sun-lamp grown vs
  outdoor sky-lit), shears harvest, cauldron tincture, brownie food,
  bonemeal, sun-lamp redstone-lit block (light_dampening: 0 so crops
  beneath stay lit), grass-seed bootstrap, wandering-trader buyback,
  pillager raid stealing.
- trees-features-addon: ods_orch wild cherry tree — log/leaves/planks/
  stripped/sapling/fruit blocks with seasonal fruit states, structure-
  spawn worldgen.
- naturalist-lite-addon: 13-mob subset of Naturalist (deer, fox, owl,
  skunk, snake, hedgehog, red panda, capybara, elephant, kangaroo,
  moose, tiger, firefly), trimmed for Switch joinability.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 22:00:06 +01:00
sysadminandClaude Opus 4.7 b9e3380f6c feat(camping): three-tier ore detector with private-chest faraday cage
New basic/improved/advanced detectors (8/16/32 block range). Aim and
right-click to ping the nearest ore on the view ray; pitch-coded sound
and action-bar text show distance and ore type. Any ore within 4 blocks
of a silverlabs:private_chest is hidden — chests act as faraday cages
so claimed bases stay private from neighbours' detectors.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 21:59:27 +01:00
sysadminandClaude Opus 4.7 145f5d9beb feat(spark): time-of-day sleep cycle + needy smoke particle fix
Switch sleep transitions from day_light_level (broken indoors) to
time_of_day, and fix the mood_needy particle reference from the
non-existent minecraft:large_smoke to minecraft:basic_smoke_particle.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 21:59:13 +01:00
sysadminandClaude Opus 4.7 77a7524917 fix(camping): swap tent panel L/R to render apex /\ not \/
Deploy Addons / deploy (push) Successful in 23s
Bedrock mirrors the panel geometry across the block's local X axis
relative to the .geo.json, so the player's column needs panel_r and
the adjacent column needs panel_l for the roof seam to peak correctly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 03:03:14 +01:00
sysadminandClaude Opus 4.7 9a2389e050 fix(infra): cap Bedrock containers at 1.5G so a hung world can't OOM neighbours
Deploy Addons / deploy (push) Successful in 45s
Host (Docker2, 8 GB RAM) has been seeing one Bedrock balloon and trigger
the kernel OOM-killer, which picks the next-largest RSS process and kills
that — i.e. one runaway world takes a healthy world down with it. dmesg
captured this happening to mc-lobby (cgroup 8f5bb1bc...) right after Jamie
threw a Watchdog 'Hang' from the Vehicles Pro pack.

mem_limit: 1500m / memswap_limit: 2500m on each of lobby/jamie/lyla/mya
keeps the blast radius inside the offending container — it OOMs itself
(recoverable via restart: unless-stopped) instead of nuking a sibling.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 00:03:14 +01:00
sysadminandClaude Opus 4.7 3e08a59972 feat: A-frame tent + portal walk-through field + texture polish
Deploy Addons / deploy (push) Successful in 14s
- camping: replace cube tent with A-frame slope panels (tent_panel_l/r) +
  cardinal_direction permutations; vote-skip sleep that mixes
  player.isSleeping bed sleepers with tent occupants and respects the
  playersSleepingPercentage gamerule; new weathered-canvas texture.
- lobby: walk-through silverlabs:portal_field block (no collision,
  translucent swirl, cross-plane geo) auto-placed above each portal frame;
  invisible silverlabs:portal_label entity floats above each portal with
  the destination world name; transfer detection now scans down through
  the field to find the destination frame.
- postal: regenerate post_office and mailbox block textures so they fill
  the full block face (brick + POST plaque, full red panel with slot/latch
  /flag/rivets) instead of small sprites floating on transparent.
- dynamite + tow-boat: ship the addons (volumes wired into all four
  worlds; enabled_packs registers them into Mya's world).
- art: build-textures.py extended; build-art-catalog.py added to project.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 23:17:31 +01:00
sysadminandClaude Opus 4.7 fce15ac801 fix(postal): remove invalid minecraft:icon block component
Deploy Addons / deploy (push) Successful in 52s
Bedrock 1.21+ rejects minecraft:icon as a *block* component (it's an
*item* component), failing block registration with "child 'minecraft:icon'
not valid here". The post_office and mailbox blocks never registered, so
their recipes failed (silverlabs:post_office / silverlabs:mailbox missing)
and the items didn't appear in the in-game inventory despite the addon
being marked active in the world settings.

Reverts the additions made in f126eeb to those two block JSONs only;
the rest of the multi-mailbox script logic from that commit is unchanged.
Inventory icons now fall back to the material_instances texture (same
visual as before f126eeb).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 04:09:02 +01:00
sysadminandClaude Opus 4.7 f126eeb955 feat(postal): multi-mailbox per player with labels, redirect flow, icon refresh
Deploy Addons / deploy (push) Successful in 14s
Players can now place up to 5 mailboxes, each labelled like a lodestone
waypoint. Sending mail picks recipient then mailbox; redirect collapses
one of your own mailboxes into another and removes the source.

- v1 -> v2 schema migration runs once on boot; existing claims default to
  label "Mailbox".
- Two-step send picker (skipped when recipient has only one mailbox).
- Post office root menu adds Redirect option.
- Per-entry break handling so removing one mailbox keeps the others claimed.
- minecraft:icon component + 16x16 inventory icons for both blocks.
- Refreshed pack_icon.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 02:12:36 +01:00
sysadminandClaude Opus 4.7 60603ab74c fix(camping): scan-down now uses air/liquid check + hammock camera pull
Deploy Addons / deploy (push) Successful in 15s
The tent's scan-down-for-ground loop still used b.isSolid (undefined
in BDS), so it always fell through the break conditions and
decremented feetY by 3 blocks before checking ground. That's why
clicking grass reported "dirt" and stone reported "dirt/stone from
underneath the surface". Replaced both solid checks with
!isAir && !isLiquid like the main ground check.

Also added a cinematic third-person camera when climbing into the
hammock (ease 0.8s out_sine), cleared on exit. Gives the player a
"lying back, can see myself" view while resting.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 16:32:26 +01:00
sysadminandClaude Opus 4.7 ad5c365c2c fix(camping): use air/liquid check for tent ground + legacy geo format
Deploy Addons / deploy (push) Successful in 15s
Two live-testing regressions:

- block.isSolid is not a reliable member of the @minecraft/server Block
  API in BDS 1.26 — it returned undefined, so !b.isSolid was always
  true and every ground cell failed. Replaced with !b.isAir &&
  !b.isLiquid (same predicate the clear-space check below already
  uses), which correctly accepts grass/dirt/stone and only rejects air
  or water/lava.

- The half-slab hammock geometry was silently rejected and rendered
  invisible. The block-model parser wants the legacy 1.12.0 format
  with simple "uv": [0, 0], not 1.21.0 with per-face UV objects.
  Rewritten hammock_slab.geo.json to match the working
  addon/spark_pet_RP/models/blocks/dragon_basket.geo.json format.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 15:19:39 +01:00
sysadminandClaude Opus 4.7 7e3432d868 fix(camping): half-slab hammock geometry + smarter tent grounding
Deploy Addons / deploy (push) Successful in 15s
- Hammock now uses a custom geometry.silverlabs.hammock_slab (8-voxel
  tall half-slab instead of a full cube), matching its thin collision
  box so the block reads visually as a hammock cradle, not a brick.

- Tent placement now projects the player's feet down to the nearest
  solid block (up to 3 blocks) before picking the base Y, so mid-jump
  fractional positions or standing-on-slab cases don't mis-place the
  footprint one block too high.

- Failure messages now include the exact failing cell coordinates and
  the block type that's in the way, so we can diagnose live pitch
  attempts without guesswork.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 15:08:34 +01:00
sysadminandClaude Opus 4.7 5c1af25468 fix(camping): tent clearance + real hammock lie-in behavior
Deploy Addons / deploy (push) Successful in 15s
Three fixes for feedback from live testing:

1) Tent pitch check required 3 blocks of air above the footprint but
   the tent is only 2 blocks tall. Relaxed clearance to match the
   actual structure height (h <= 1).

2) Hammock block had a 4px-tall collision box which pushed the player
   on TOP of the cloth. Reduced collision to 1px so the player stands
   inside the hammock cell. Selection box stays at 4px for easy click.

3) Climbing in now actually locks the player: applies slowness 255,
   weakness, mining_fatigue for the duration, saves the anchor point
   in a dynamic property, and the upkeep loop re-teleports them if
   they drift off. Sneak-exit nudges them ~1.2 blocks forward of the
   cradle so they don't immediately re-enter.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 14:51:50 +01:00
sysadminandClaude Opus 4.7 82250164ca fix(camping): use plain-string minecraft:icon and redraw item icons
Deploy Addons / deploy (push) Successful in 14s
The nested {"texture": "..."} form for minecraft:icon is rejected by the
1.21.0 item schema (BDS warned: "this member was found in the input,
but is not present in the Schema"), so tent and hammock had no inventory
icons at all. Switched to the plain-string form used by every other
addon in the repo.

While here, replaced the placeholder PNGs (a flat green triangle and a
red squiggle) with proper 16x16 pixel art:
  - tent: A-frame canvas silhouette with doorway slit and ground line
  - hammock: side-view sling with rope ends rising off-screen and a
    sagging red-and-white striped fabric

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 02:06:18 +01:00
sysadminandClaude Opus 4.7 3a010091e5 fix(camping): reshape hammock recipe to fit 3x3 crafting grid
Deploy Addons / deploy (push) Successful in 15s
Original 5x2 pattern (T   T / TWWWT) exceeded the vanilla crafting grid
and BDS rejected it with "Adding a recipe larger than 3x3". Replaced
with a 3x3 layout: strings on the four corners as suspension points,
wool row across the middle as the cloth.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 01:36:18 +01:00
sysadminandClaude Opus 4.7 14dd6d5ce7 ci(deploy): include camping-supplies-addon in workflow sync
The camping-supplies addon was added to docker-compose.yml as a bind
mount but never added to the workflow's path filter or PATHS checkout
list, so the directory on the server stayed empty and the pack was
invisible to all four worlds.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 01:11:53 +01:00
sysadminandClaude Opus 4.7 25870ef082 fix(lobby_transfer): pass connectionOptions object to transferPlayer
Deploy Addons / deploy (push) Successful in 14s
The @minecraft/server-admin beta transferPlayer API takes
(player, { hostname, port }) — passing host/port positionally raised
"incorrect arguments, expected 2 received 3" at runtime. Matches the
working call site in hub_return_transfer_BP.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 00:33:31 +01:00
sysadminandClaude Opus 4.7 c35b97afef fix(lobby_transfer): restore transferPlayer beta API (Player.transfer does not exist in BDS 1.26)
Deploy Addons / deploy (push) Successful in 15s
Commit 81f84b5 misdiagnosed the mc-lobby crash loop and stripped the
@minecraft/server-admin@1.0.0-beta dependency, swapping in
player.transfer({hostname,port}). At runtime that throws
"player.transfer is not a function" the first time anyone steps on a
portal — the stable @minecraft/server API never exposed transfer() on
Player in BDS 1.26.14.

The real root cause of the original crash loop was unrelated: the
mc-lobby Docker volume had lost its vanilla behavior-pack collection
(vanilla_*, chemistry*, editor, server_library, …). Copying those back
from a healthy sibling volume fixed the boot crash; nothing in the
lobby_transfer pack needed to change. Restoring the beta transferPlayer
here brings lobby back in line with hub-return-addon, which has always
used this pattern successfully on jamie/lyla/mya.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 00:00:53 +01:00
sysadminandClaude Opus 4.7 81f84b56a0 fix(lobby_transfer): replace removed @minecraft/server-admin transferPlayer with stable Player.transfer
Deploy Addons / deploy (push) Successful in 15s
Bedrock 1.26 rejects the @minecraft/server-admin 1.0.0-beta dependency at
pack load, which was crashing mc-lobby on boot (exit 1 right after pack
stack + block-version logging). The transferPlayer helper has been part of
the stable @minecraft/server API as Player.transfer since 1.21, so we can
drop the server-admin import and dependency entirely.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 23:36:16 +01:00
sysadminandClaude Opus 4.7 db8f37a24d fix(addons): populate recipe unlock with crafting_table ingredient
Deploy Addons / deploy (push) Successful in 14s
Bedrock 1.26 rejects { context } variants as "malformed unlocking context"
and [] as "empty unlocking ingredient array". The unlock field must contain
at least one ingredient item; players who have that item unlock the recipe.

Using minecraft:crafting_table as the unlock ingredient makes semantic sense
(all 26 recipes already require the crafting_table tag) and effectively means
"visible once the player has any crafting table".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 23:28:17 +01:00
sysadminandClaude Opus 4.7 e6148a2949 fix(addons): correct recipe unlock shape and block format_version
Deploy Addons / deploy (push) Successful in 13s
Follow-up to 4324882. The { "context": "always_unlocked" } form triggered
"malformed unlocking context" on 1.26-series Bedrock (AlwaysUnlocked pascal
form fared no better). Empty-array form is the universally accepted "recipe
always known" shape. This unblocks mc-lobby which was crashing on boot.

Also reverts block format_version on mailbox.json + smart_crafting_table.json
to 1.21.0 — every other working block in the repo uses that, so the prior
1.21.60 choice was unnecessary.

- 19 dragon + portal recipes (spark_pet_BP, lobby_transfer_BP): unlock -> []
- 7 other recipes touching unlock { context: ... }: same
- 2 block files: format_version 1.21.60 -> 1.21.0

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 23:22:41 +01:00
sysadminandClaude Opus 4.7 43248820fa fix(addons): bring silverlabs packs into schema compliance
Deploy Addons / deploy (push) Successful in 14s
- recipes: add unlock { context: always_unlocked } to all spark_pet and lobby_transfer recipes (1.20+ requirement) — fixes mc-lobby crash loop
- spark_dragon entity_sensor: wrap in subsensors[] and convert sensor_range → range [a,b] per 1.21 schema
- anthrax_cat interact: wrap event/filters in on_interact to match Bedrock 1.21 interact schema
- block format_version: bump mailbox and smart_crafting_table to 1.21.60
- item icons: replace { texture: X } with shorthand string form
- heyhe_egg description: replace bare category with menu_category block

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 23:18:24 +01:00
sysadminandClaude Opus 4.7 8b83e324f0 feat(camping): add craftable tent and hammock addon
Deploy Addons / deploy (push) Successful in 13s
Tent pitches over a 2x3 flat footprint and lets players skip to dawn
without touching their spawn point. Hammock strings between two posts
3-6 blocks apart (straight or diagonal, +/-1 block height) and keeps
hostile mobs at bay while occupied. Both are craftable (wool/sticks and
wool/string) and mounted in all four worlds.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 23:10:21 +01:00
sysadminandClaude Opus 4.7 579dfec633 feat(hub-return): add lodestone waypoints with compass menu and HUD guidance
Deploy Addons / deploy (push) Successful in 40s
Right-clicking the recovery compass now opens an ActionForm menu with
"Return to Hub" plus any lodestone waypoints the player has placed in
the current dimension. Placing a lodestone prompts for a label and
saves it under the waypoints_v1 world dynamic property (max 10 per
player). Selected waypoints drive an on-screen actionbar HUD with
distance and an 8-direction arrow, clearing on arrival within 3 blocks.
Lodestone breaks are ownership-gated and drop the block back.

Bumps pack to 1.0.5 and declares the @minecraft/server-ui dependency
required by the new ActionForm/Modal/MessageForm flows.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 20:39:47 +01:00
SysAdminandClaude Opus 4.7 c1cd32eeb5 fix(compose): remove server.properties-aliased env vars so UI edits persist
Deploy Addons / deploy (push) Successful in 16s
The itzg/minecraft-bedrock-server entrypoint rewrites server.properties
from env vars on every start. Any property edited via mc-manager's UI was
being clobbered because those env vars were set here. Remove them so
/data/server.properties (on the named volume) is the real source of truth:

  - SERVER_NAME, GAMEMODE, DIFFICULTY, ALLOW_CHEATS, ONLINE_MODE,
    MAX_PLAYERS, DEFAULT_PLAYER_PERMISSION_LEVEL
  - LEVEL_SEED (jamie) — world already generated
  - LEVEL_NAME — moved to docker-compose.override.yml, managed by
    mc-manager so UI Set Active survives future deploys

Kept: EULA, SERVER_PORT (deploy-time wiring), OP_PERMISSION_LEVEL
(not a server.properties key).

Override file is gitignored so mc-manager's writes don't leak into git.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 11:45:17 +01:00
sysadminandClaude Opus 4.7 d6bafb9d16 feat(smart-crafting): add smart crafting table using private chest inventory
Deploy Addons / deploy (push) Successful in 14s
Adds an upgraded crafting block that scans the player's owned private chests
and aggregates their contents with the personal inventory when deciding which
recipes are craftable. Ingredients are consumed from the player first then
from chests; the result goes to the player (or drops at their feet).

Also redraws the post_office and mailbox block textures via a new
scripts/build-textures.py generator.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 11:16:19 +01:00
sysadminandClaude Opus 4.7 f7aa71e9eb feat(postal): add postal service addon and bundle pending addon work
Deploy Addons / deploy (push) Successful in 16s
- New postal-service-addon: per-player mailboxes + post-office send block
  (ActionForm recipient picker, offline notification queue, chunk-load
  retry via tickingarea)
- Commit previously untracked private-chest, home-sign, keep-inventory
  addons and their docker-compose mounts
- Deploy workflow: add postal + previously unwired addons to path filter
  and checkout list; drop easter-egg from deployment
- enabled_packs.json: register postal UUIDs for Lyla + Mya

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 20:07:39 +01:00
sysadminandClaude Opus 4.7 cc18066c17 fix(lobby,easter-egg): update portal coordinates and egg-check ring
Lobby portal zones and signs moved to the rebuilt portal area at
~(436..488, 65..74, -322..-281), including a second Lyla's-World
"Super Kitties" portal. Signs now use per-portal facing_direction
so they face the hub walkway correctly.

Easter-egg placeAllEggs validation now samples the far-ring eggs
(indices 45-49) before falling back to the near ring — avoids false
"blocks missing" reports when only close-ring eggs have been picked up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 14:19:33 +01:00
sysadminandClaude Opus 4.7 c176263ec5 feat(monkey): add cheeky banana-throwing monkey addon
New silverlabs:cheeky_monkey mob that wanders the lobby watching players
and throwing harmless (knockback-only) silverlabs:banana_projectile at
them. Drops 1-2 silverlabs:banana (food) on death. Spawn rules target
jungle biomes for future deployment to survival worlds; for now the pack
is bind-mounted into the lobby service only.

Also bundles a stray docker-compose tidy from earlier local work
(Jamie's world seed pinned, pet addons dropped from Jamie).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 14:19:18 +01:00
1212 changed files with 202774 additions and 303 deletions
+16 -3
View File
@@ -7,8 +7,15 @@ on:
- 'addon/**'
- 'lobby-addon/**'
- 'hub-return-addon/**'
- 'easter-egg-addon/**'
- 'village-evolution-addon/**'
- 'monkey-addon/**'
- 'private-chest-addon/**'
- 'home-sign-addon/**'
- 'keep-inventory-addon/**'
- 'postal-service-addon/**'
- 'camping-supplies-addon/**'
- 'family-goals-addon/**'
- 'redstone-link-addon/**'
- 'docker-compose.yml'
- 'scripts/**'
@@ -26,6 +33,12 @@ jobs:
script: |
set -e
APP_DIR="$HOME/minecraft-multiworld"
# Anything mounted in docker-compose.yml but missing here is simply
# never refreshed by CI. family-goals-addon and redstone-link-addon
# are listed because docker-compose.yml mounts them; omitting either
# while checking out docker-compose.yml would leave a mount pointing
# at a directory this job never delivers.
PATHS="addon/ lobby-addon/ hub-return-addon/ village-evolution-addon/ monkey-addon/ private-chest-addon/ home-sign-addon/ keep-inventory-addon/ postal-service-addon/ camping-supplies-addon/ family-goals-addon/ redstone-link-addon/ docker-compose.yml"
# First run: clone. Subsequent: pull.
if [ ! -d "$APP_DIR/.git" ]; then
@@ -33,11 +46,11 @@ jobs:
git init
git remote add origin https://git.silverlabs.uk/SilverLABS/minecraft-aiworld.git
git fetch origin main
git checkout -f origin/main -- addon/ lobby-addon/ hub-return-addon/ easter-egg-addon/ village-evolution-addon/ docker-compose.yml
git checkout -f origin/main -- $PATHS
else
cd "$APP_DIR"
git fetch origin main
git checkout -f origin/main -- addon/ lobby-addon/ hub-return-addon/ easter-egg-addon/ village-evolution-addon/ docker-compose.yml
git checkout -f origin/main -- $PATHS
fi
# Recreate containers so any new docker-compose volume mounts are applied,
+10
View File
@@ -16,8 +16,18 @@ addon/build/
# Environment
.env
# docker-compose override, managed by mc-manager at runtime for per-service state
# (LEVEL_NAME, etc.) that must survive deploys. Never committed.
docker-compose.override.yml
# Local-only backup files (e.g. docker-compose.yml.bak.local)
*.bak.local
# Server data (Docker volumes are external, but just in case)
server-data/
# Claude Code
.claude/
# Art workspace (edit-in-place mirror; real textures live in each RP)
/art/
+7 -15
View File
@@ -3,7 +3,7 @@
"minecraft:entity": {
"description": {
"identifier": "silverlabs:anthrax_cat",
"is_spawnable": false,
"is_spawnable": true,
"is_summonable": true,
"is_experimental": false
},
@@ -12,9 +12,13 @@
"minecraft:interact": {
"interactions": [
{
"on_interact": {
"filters": { "test": "is_family", "subject": "other", "value": "player" },
"event": "silverlabs:interacted",
"target": "self"
},
"interact_text": "action.interact.fortune",
"play_sounds": "note.bell",
"event": "silverlabs:interacted"
"play_sounds": "note.bell"
}
]
}
@@ -39,18 +43,6 @@
"minecraft:knockback_resistance": {
"value": 1.0
},
"minecraft:damage_sensor": {
"triggers": [
{
"cause": "all",
"deals_damage": false
},
{
"cause": "override",
"deals_damage": true
}
]
},
"minecraft:health": {
"value": 20,
"max": 20
+1 -3
View File
@@ -10,9 +10,7 @@
},
"components": {
"minecraft:max_stack_size": 16,
"minecraft:icon": {
"texture": "anthrax_cat"
},
"minecraft:icon": "anthrax_cat",
"minecraft:entity_placer": {
"entity": "silverlabs:anthrax_cat"
}
+2 -4
View File
@@ -3,13 +3,11 @@
"minecraft:item": {
"description": {
"identifier": "silverlabs:heyhe_egg",
"category": "Nature"
"menu_category": { "category": "nature" }
},
"components": {
"minecraft:max_stack_size": 16,
"minecraft:icon": {
"texture": "heyhe_egg"
},
"minecraft:icon": "heyhe_egg",
"minecraft:entity_placer": {
"entity": "silverlabs:heyhe_egg",
"dispense_on": [
+21 -5
View File
@@ -2,22 +2,38 @@
"format_version": 2,
"header": {
"name": "Hey Hey Chicken Pet",
"description": "Hey Hey the loyal Moana chicken tameable, following pet with waddly personality.",
"description": "Hey Hey the loyal Moana chicken \u2014 tameable, following pet with waddly personality.",
"uuid": "fc811a53-dbdb-4701-bc63-c3ca1d793c47",
"version": [1, 0, 0],
"min_engine_version": [1, 21, 0]
"version": [
1,
0,
0
],
"min_engine_version": [
1,
21,
0
]
},
"modules": [
{
"type": "data",
"uuid": "f7aae24a-06d8-432b-85dc-359c4fda9dfd",
"version": [1, 0, 0]
"version": [
1,
0,
0
]
}
],
"dependencies": [
{
"uuid": "45ec17df-0d84-456f-b2cc-2d990f96e6d5",
"version": [1, 0, 0]
"version": [
1,
0,
1
]
}
]
}
@@ -190,15 +190,14 @@
"reselect_targets": true
},
"minecraft:entity_sensor": {
"sensor_range": 500,
"relative_range": false,
"subsensors": [
{
"range": [500, 500],
"minimum_count": 1,
"event_filters": {
"test": "is_family",
"subject": "other",
"value": "whistle_signal"
},
"event": "silverlabs:whistle_called"
"event": "silverlabs:whistle_called",
"event_filters": { "test": "is_family", "subject": "other", "value": "whistle_signal" }
}
]
},
"minecraft:behavior.melee_attack": {
"priority": 2,
+21 -5
View File
@@ -2,22 +2,38 @@
"format_version": 2,
"header": {
"name": "Pets 4 Jamie's STARS",
"description": "Tameable dragon pets with eggs, gravestones and personality built for Jamie's STARS!",
"description": "Tameable dragon pets with eggs, gravestones and personality \u2014 built for Jamie's STARS!",
"uuid": "7cf924ce-e246-4c8c-998c-f420edb26451",
"version": [1, 0, 0],
"min_engine_version": [1, 21, 0]
"version": [
1,
0,
0
],
"min_engine_version": [
1,
21,
0
]
},
"modules": [
{
"type": "data",
"uuid": "6806e843-c0b4-4a4d-9cb3-d5352e396fe5",
"version": [1, 0, 0]
"version": [
1,
0,
0
]
}
],
"dependencies": [
{
"uuid": "5f25d547-00bb-49ce-8be3-d86cd3941c9b",
"version": [1, 0, 0]
"version": [
1,
0,
1
]
}
]
}
+17 -4
View File
@@ -4,16 +4,29 @@
"description": {
"identifier": "silverlabs:dragon_basket_recipe"
},
"tags": ["crafting_table"],
"unlock": [
{
"item": "minecraft:crafting_table"
}
],
"tags": [
"crafting_table"
],
"pattern": [
"S S",
"WLW",
"SSS"
],
"key": {
"S": { "item": "minecraft:stick" },
"W": { "item": "minecraft:wool" },
"L": { "item": "minecraft:leather" }
"S": {
"item": "minecraft:stick"
},
"W": {
"item": "minecraft:wool"
},
"L": {
"item": "minecraft:leather"
}
},
"result": {
"item": "silverlabs:dragon_basket"
+17 -4
View File
@@ -4,16 +4,29 @@
"description": {
"identifier": "silverlabs:dragon_egg"
},
"tags": ["crafting_table"],
"unlock": [
{
"item": "minecraft:crafting_table"
}
],
"tags": [
"crafting_table"
],
"pattern": [
" B ",
"AGA",
" A "
],
"key": {
"B": { "item": "minecraft:blaze_powder" },
"A": { "item": "minecraft:amethyst_shard" },
"G": { "item": "minecraft:gold_ingot" }
"B": {
"item": "minecraft:blaze_powder"
},
"A": {
"item": "minecraft:amethyst_shard"
},
"G": {
"item": "minecraft:gold_ingot"
}
},
"result": {
"item": "silverlabs:dragon_egg",
@@ -1,12 +1,27 @@
{
"format_version": "1.21.0",
"minecraft:recipe_shapeless": {
"description": { "identifier": "silverlabs:dragon_egg_crimson_recipe" },
"tags": ["crafting_table"],
"ingredients": [
{ "item": "silverlabs:dragon_egg" },
{ "item": "minecraft:red_dye" }
"description": {
"identifier": "silverlabs:dragon_egg_crimson_recipe"
},
"unlock": [
{
"item": "minecraft:crafting_table"
}
],
"result": { "item": "silverlabs:dragon_egg_crimson" }
"tags": [
"crafting_table"
],
"ingredients": [
{
"item": "silverlabs:dragon_egg"
},
{
"item": "minecraft:red_dye"
}
],
"result": {
"item": "silverlabs:dragon_egg_crimson"
}
}
}
@@ -1,12 +1,27 @@
{
"format_version": "1.21.0",
"minecraft:recipe_shapeless": {
"description": { "identifier": "silverlabs:dragon_egg_jungle_recipe" },
"tags": ["crafting_table"],
"ingredients": [
{ "item": "silverlabs:dragon_egg" },
{ "item": "minecraft:lime_dye" }
"description": {
"identifier": "silverlabs:dragon_egg_jungle_recipe"
},
"unlock": [
{
"item": "minecraft:crafting_table"
}
],
"result": { "item": "silverlabs:dragon_egg_jungle" }
"tags": [
"crafting_table"
],
"ingredients": [
{
"item": "silverlabs:dragon_egg"
},
{
"item": "minecraft:lime_dye"
}
],
"result": {
"item": "silverlabs:dragon_egg_jungle"
}
}
}
+21 -6
View File
@@ -1,12 +1,27 @@
{
"format_version": "1.21.0",
"minecraft:recipe_shapeless": {
"description": { "identifier": "silverlabs:dragon_egg_oak_recipe" },
"tags": ["crafting_table"],
"ingredients": [
{ "item": "silverlabs:dragon_egg" },
{ "item": "minecraft:white_dye" }
"description": {
"identifier": "silverlabs:dragon_egg_oak_recipe"
},
"unlock": [
{
"item": "minecraft:crafting_table"
}
],
"result": { "item": "silverlabs:dragon_egg_oak" }
"tags": [
"crafting_table"
],
"ingredients": [
{
"item": "silverlabs:dragon_egg"
},
{
"item": "minecraft:white_dye"
}
],
"result": {
"item": "silverlabs:dragon_egg_oak"
}
}
}
@@ -1,12 +1,27 @@
{
"format_version": "1.21.0",
"minecraft:recipe_shapeless": {
"description": { "identifier": "silverlabs:dragon_egg_obsidian_recipe" },
"tags": ["crafting_table"],
"ingredients": [
{ "item": "silverlabs:dragon_egg" },
{ "item": "minecraft:black_dye" }
"description": {
"identifier": "silverlabs:dragon_egg_obsidian_recipe"
},
"unlock": [
{
"item": "minecraft:crafting_table"
}
],
"result": { "item": "silverlabs:dragon_egg_obsidian" }
"tags": [
"crafting_table"
],
"ingredients": [
{
"item": "silverlabs:dragon_egg"
},
{
"item": "minecraft:black_dye"
}
],
"result": {
"item": "silverlabs:dragon_egg_obsidian"
}
}
}
@@ -1,12 +1,27 @@
{
"format_version": "1.21.0",
"minecraft:recipe_shapeless": {
"description": { "identifier": "silverlabs:dragon_egg_spruce_recipe" },
"tags": ["crafting_table"],
"ingredients": [
{ "item": "silverlabs:dragon_egg" },
{ "item": "minecraft:brown_dye" }
"description": {
"identifier": "silverlabs:dragon_egg_spruce_recipe"
},
"unlock": [
{
"item": "minecraft:crafting_table"
}
],
"result": { "item": "silverlabs:dragon_egg_spruce" }
"tags": [
"crafting_table"
],
"ingredients": [
{
"item": "silverlabs:dragon_egg"
},
{
"item": "minecraft:brown_dye"
}
],
"result": {
"item": "silverlabs:dragon_egg_spruce"
}
}
}
@@ -1,12 +1,27 @@
{
"format_version": "1.21.0",
"minecraft:recipe_shapeless": {
"description": { "identifier": "silverlabs:dragon_egg_warped_recipe" },
"tags": ["crafting_table"],
"ingredients": [
{ "item": "silverlabs:dragon_egg" },
{ "item": "minecraft:cyan_dye" }
"description": {
"identifier": "silverlabs:dragon_egg_warped_recipe"
},
"unlock": [
{
"item": "minecraft:crafting_table"
}
],
"result": { "item": "silverlabs:dragon_egg_warped" }
"tags": [
"crafting_table"
],
"ingredients": [
{
"item": "silverlabs:dragon_egg"
},
{
"item": "minecraft:cyan_dye"
}
],
"result": {
"item": "silverlabs:dragon_egg_warped"
}
}
}
@@ -1,19 +1,38 @@
{
"format_version": "1.21.0",
"minecraft:recipe_shaped": {
"description": { "identifier": "silverlabs:dragon_nest_crimson_recipe" },
"tags": ["crafting_table"],
"description": {
"identifier": "silverlabs:dragon_nest_crimson_recipe"
},
"unlock": [
{
"item": "minecraft:crafting_table"
}
],
"tags": [
"crafting_table"
],
"pattern": [
"S S",
"WLW",
"PPP"
],
"key": {
"S": { "item": "minecraft:stick" },
"W": { "item": "minecraft:red_wool" },
"L": { "item": "minecraft:leather" },
"P": { "item": "minecraft:crimson_planks" }
"S": {
"item": "minecraft:stick"
},
"result": { "item": "silverlabs:dragon_nest_crimson" }
"W": {
"item": "minecraft:red_wool"
},
"L": {
"item": "minecraft:leather"
},
"P": {
"item": "minecraft:crimson_planks"
}
},
"result": {
"item": "silverlabs:dragon_nest_crimson"
}
}
}
@@ -1,19 +1,38 @@
{
"format_version": "1.21.0",
"minecraft:recipe_shaped": {
"description": { "identifier": "silverlabs:dragon_nest_jungle_recipe" },
"tags": ["crafting_table"],
"description": {
"identifier": "silverlabs:dragon_nest_jungle_recipe"
},
"unlock": [
{
"item": "minecraft:crafting_table"
}
],
"tags": [
"crafting_table"
],
"pattern": [
"S S",
"WLW",
"PPP"
],
"key": {
"S": { "item": "minecraft:stick" },
"W": { "item": "minecraft:lime_wool" },
"L": { "item": "minecraft:leather" },
"P": { "item": "minecraft:jungle_planks" }
"S": {
"item": "minecraft:stick"
},
"result": { "item": "silverlabs:dragon_nest_jungle" }
"W": {
"item": "minecraft:lime_wool"
},
"L": {
"item": "minecraft:leather"
},
"P": {
"item": "minecraft:jungle_planks"
}
},
"result": {
"item": "silverlabs:dragon_nest_jungle"
}
}
}
@@ -1,19 +1,38 @@
{
"format_version": "1.21.0",
"minecraft:recipe_shaped": {
"description": { "identifier": "silverlabs:dragon_nest_oak_recipe" },
"tags": ["crafting_table"],
"description": {
"identifier": "silverlabs:dragon_nest_oak_recipe"
},
"unlock": [
{
"item": "minecraft:crafting_table"
}
],
"tags": [
"crafting_table"
],
"pattern": [
"S S",
"WLW",
"PPP"
],
"key": {
"S": { "item": "minecraft:stick" },
"W": { "item": "minecraft:white_wool" },
"L": { "item": "minecraft:leather" },
"P": { "item": "minecraft:oak_planks" }
"S": {
"item": "minecraft:stick"
},
"result": { "item": "silverlabs:dragon_nest_oak" }
"W": {
"item": "minecraft:white_wool"
},
"L": {
"item": "minecraft:leather"
},
"P": {
"item": "minecraft:oak_planks"
}
},
"result": {
"item": "silverlabs:dragon_nest_oak"
}
}
}
@@ -1,18 +1,35 @@
{
"format_version": "1.21.0",
"minecraft:recipe_shaped": {
"description": { "identifier": "silverlabs:dragon_nest_obsidian_recipe" },
"tags": ["crafting_table"],
"description": {
"identifier": "silverlabs:dragon_nest_obsidian_recipe"
},
"unlock": [
{
"item": "minecraft:crafting_table"
}
],
"tags": [
"crafting_table"
],
"pattern": [
"O O",
"WLW",
"OOO"
],
"key": {
"O": { "item": "minecraft:obsidian" },
"W": { "item": "minecraft:black_wool" },
"L": { "item": "minecraft:leather" }
"O": {
"item": "minecraft:obsidian"
},
"result": { "item": "silverlabs:dragon_nest_obsidian" }
"W": {
"item": "minecraft:black_wool"
},
"L": {
"item": "minecraft:leather"
}
},
"result": {
"item": "silverlabs:dragon_nest_obsidian"
}
}
}
@@ -1,19 +1,38 @@
{
"format_version": "1.21.0",
"minecraft:recipe_shaped": {
"description": { "identifier": "silverlabs:dragon_nest_spruce_recipe" },
"tags": ["crafting_table"],
"description": {
"identifier": "silverlabs:dragon_nest_spruce_recipe"
},
"unlock": [
{
"item": "minecraft:crafting_table"
}
],
"tags": [
"crafting_table"
],
"pattern": [
"S S",
"WLW",
"PPP"
],
"key": {
"S": { "item": "minecraft:stick" },
"W": { "item": "minecraft:brown_wool" },
"L": { "item": "minecraft:leather" },
"P": { "item": "minecraft:spruce_planks" }
"S": {
"item": "minecraft:stick"
},
"result": { "item": "silverlabs:dragon_nest_spruce" }
"W": {
"item": "minecraft:brown_wool"
},
"L": {
"item": "minecraft:leather"
},
"P": {
"item": "minecraft:spruce_planks"
}
},
"result": {
"item": "silverlabs:dragon_nest_spruce"
}
}
}
@@ -1,19 +1,38 @@
{
"format_version": "1.21.0",
"minecraft:recipe_shaped": {
"description": { "identifier": "silverlabs:dragon_nest_warped_recipe" },
"tags": ["crafting_table"],
"description": {
"identifier": "silverlabs:dragon_nest_warped_recipe"
},
"unlock": [
{
"item": "minecraft:crafting_table"
}
],
"tags": [
"crafting_table"
],
"pattern": [
"S S",
"WLW",
"PPP"
],
"key": {
"S": { "item": "minecraft:stick" },
"W": { "item": "minecraft:cyan_wool" },
"L": { "item": "minecraft:leather" },
"P": { "item": "minecraft:warped_planks" }
"S": {
"item": "minecraft:stick"
},
"result": { "item": "silverlabs:dragon_nest_warped" }
"W": {
"item": "minecraft:cyan_wool"
},
"L": {
"item": "minecraft:leather"
},
"P": {
"item": "minecraft:warped_planks"
}
},
"result": {
"item": "silverlabs:dragon_nest_warped"
}
}
}
+14 -3
View File
@@ -4,15 +4,26 @@
"description": {
"identifier": "silverlabs:dragon_toy"
},
"tags": ["crafting_table"],
"unlock": [
{
"item": "minecraft:crafting_table"
}
],
"tags": [
"crafting_table"
],
"pattern": [
" S ",
"SLS",
" S "
],
"key": {
"S": { "item": "minecraft:string" },
"L": { "item": "minecraft:leather" }
"S": {
"item": "minecraft:string"
},
"L": {
"item": "minecraft:leather"
}
},
"result": {
"item": "silverlabs:dragon_toy",
@@ -30,7 +30,7 @@
{ "fire_breathing": "query.property('silverlabs:firing')" },
{ "flying": "!query.is_on_ground && query.property('silverlabs:growth_stage') == 2" },
{ "walking": "query.modified_move_speed > 0.1" },
{ "sleeping": "query.is_sitting && (query.day_light_level < 4)" },
{ "sleeping": "query.is_sitting && (query.time_of_day > 0.5)" },
{ "sitting": "query.is_sitting" },
{ "grooming": "query.is_on_ground && query.modified_move_speed <= 0.05 && math.mod(math.floor((query.life_time + math.mod(math.abs(math.floor(query.position(0) + query.position(2))), 5) * 36.0) / 36.0), 5) == 1" },
{ "sniffing": "query.is_on_ground && query.modified_move_speed <= 0.15 && math.mod(math.floor((query.life_time + math.mod(math.abs(math.floor(query.position(0) + query.position(2))), 5) * 36.0) / 36.0), 5) == 2" },
@@ -52,14 +52,14 @@
"animations": ["sit"],
"transitions": [
{ "idle": "!query.is_sitting" },
{ "sleeping": "query.is_sitting && (query.day_light_level < 4)" }
{ "sleeping": "query.is_sitting && (query.time_of_day > 0.5)" }
]
},
"sleeping": {
"animations": ["sleep"],
"transitions": [
{ "idle": "!query.is_sitting" },
{ "sitting": "query.is_sitting && (query.day_light_level >= 4)" }
{ "sitting": "query.is_sitting && (query.time_of_day <= 0.5)" }
]
},
"flying": {
@@ -41,7 +41,7 @@
"particle_effects": {
"beacon_beam": "minecraft:endrod",
"mood_happy": "minecraft:villager_happy",
"mood_needy": "minecraft:large_smoke"
"mood_needy": "minecraft:basic_smoke_particle"
},
"spawn_egg": {
"base_color": "#7B2FBE",
+2 -2
View File
@@ -4,14 +4,14 @@
"name": "Pets 4 Jamie's STARS Resources",
"description": "Textures, models and animations for Pets 4 Jamie's STARS",
"uuid": "5f25d547-00bb-49ce-8be3-d86cd3941c9b",
"version": [1, 0, 0],
"version": [1, 0, 1],
"min_engine_version": [1, 21, 0]
},
"modules": [
{
"type": "resources",
"uuid": "6adc20cd-25a5-4ca5-aa12-f5e43ee9ea22",
"version": [1, 0, 0]
"version": [1, 0, 1]
}
],
"dependencies": [
@@ -0,0 +1,33 @@
{
"format_version": "1.21.0",
"minecraft:block": {
"description": {
"identifier": "silverlabs:hammock_cloth"
},
"components": {
"minecraft:destructible_by_mining": {
"seconds_to_destroy": 0.3
},
"minecraft:destructible_by_explosion": {
"explosion_resistance": 0.5
},
"minecraft:map_color": "#B43C37",
"minecraft:material_instances": {
"*": {
"texture": "hammock_cloth",
"render_method": "alpha_test"
}
},
"minecraft:collision_box": {
"origin": [-8, 0, -8],
"size": [16, 1, 16]
},
"minecraft:selection_box": {
"origin": [-8, 0, -8],
"size": [16, 4, 16]
},
"minecraft:geometry": "geometry.silverlabs.hammock_slab",
"minecraft:light_dampening": 0
}
}
}
@@ -0,0 +1,25 @@
{
"format_version": "1.21.0",
"minecraft:block": {
"description": {
"identifier": "silverlabs:tent_canvas"
},
"components": {
"minecraft:destructible_by_mining": {
"seconds_to_destroy": 0.4
},
"minecraft:destructible_by_explosion": {
"explosion_resistance": 1.0
},
"minecraft:map_color": "#547A4E",
"minecraft:geometry": "minecraft:geometry.full_block",
"minecraft:material_instances": {
"*": {
"texture": "tent_canvas",
"render_method": "alpha_test"
}
},
"minecraft:light_dampening": 1
}
}
}
@@ -0,0 +1,46 @@
{
"format_version": "1.21.0",
"minecraft:block": {
"description": {
"identifier": "silverlabs:tent_panel_l",
"traits": {
"minecraft:placement_direction": {
"enabled_states": ["minecraft:cardinal_direction"]
}
}
},
"components": {
"minecraft:destructible_by_mining": { "seconds_to_destroy": 0.4 },
"minecraft:destructible_by_explosion": { "explosion_resistance": 1.0 },
"minecraft:map_color": "#547A4E",
"minecraft:material_instances": {
"*": {
"texture": "tent_canvas",
"render_method": "alpha_test"
}
},
"minecraft:light_dampening": 1,
"minecraft:geometry": "geometry.silverlabs.tent_panel_l"
},
"permutations": [
{
"condition": "query.block_state('minecraft:cardinal_direction') == 'south'",
"components": {
"minecraft:transformation": { "rotation": [0, 180, 0] }
}
},
{
"condition": "query.block_state('minecraft:cardinal_direction') == 'east'",
"components": {
"minecraft:transformation": { "rotation": [0, 90, 0] }
}
},
{
"condition": "query.block_state('minecraft:cardinal_direction') == 'west'",
"components": {
"minecraft:transformation": { "rotation": [0, 270, 0] }
}
}
]
}
}
@@ -0,0 +1,46 @@
{
"format_version": "1.21.0",
"minecraft:block": {
"description": {
"identifier": "silverlabs:tent_panel_r",
"traits": {
"minecraft:placement_direction": {
"enabled_states": ["minecraft:cardinal_direction"]
}
}
},
"components": {
"minecraft:destructible_by_mining": { "seconds_to_destroy": 0.4 },
"minecraft:destructible_by_explosion": { "explosion_resistance": 1.0 },
"minecraft:map_color": "#547A4E",
"minecraft:material_instances": {
"*": {
"texture": "tent_canvas",
"render_method": "alpha_test"
}
},
"minecraft:light_dampening": 1,
"minecraft:geometry": "geometry.silverlabs.tent_panel_r"
},
"permutations": [
{
"condition": "query.block_state('minecraft:cardinal_direction') == 'south'",
"components": {
"minecraft:transformation": { "rotation": [0, 180, 0] }
}
},
{
"condition": "query.block_state('minecraft:cardinal_direction') == 'east'",
"components": {
"minecraft:transformation": { "rotation": [0, 90, 0] }
}
},
{
"condition": "query.block_state('minecraft:cardinal_direction') == 'west'",
"components": {
"minecraft:transformation": { "rotation": [0, 270, 0] }
}
}
]
}
}
@@ -0,0 +1,17 @@
{
"format_version": "1.21.0",
"minecraft:item": {
"description": {
"identifier": "silverlabs:hammock",
"menu_category": {
"category": "equipment",
"group": "itemGroup.name.miscellaneous"
}
},
"components": {
"minecraft:max_stack_size": 16,
"minecraft:icon": "hammock_item",
"minecraft:hand_equipped": true
}
}
}
@@ -0,0 +1,17 @@
{
"format_version": "1.21.0",
"minecraft:item": {
"description": {
"identifier": "silverlabs:ore_detector_advanced",
"menu_category": {
"category": "equipment",
"group": "itemGroup.name.miscellaneous"
}
},
"components": {
"minecraft:max_stack_size": 1,
"minecraft:icon": "ore_detector_advanced",
"minecraft:hand_equipped": true
}
}
}
@@ -0,0 +1,17 @@
{
"format_version": "1.21.0",
"minecraft:item": {
"description": {
"identifier": "silverlabs:ore_detector_basic",
"menu_category": {
"category": "equipment",
"group": "itemGroup.name.miscellaneous"
}
},
"components": {
"minecraft:max_stack_size": 1,
"minecraft:icon": "ore_detector_basic",
"minecraft:hand_equipped": true
}
}
}
@@ -0,0 +1,17 @@
{
"format_version": "1.21.0",
"minecraft:item": {
"description": {
"identifier": "silverlabs:ore_detector_improved",
"menu_category": {
"category": "equipment",
"group": "itemGroup.name.miscellaneous"
}
},
"components": {
"minecraft:max_stack_size": 1,
"minecraft:icon": "ore_detector_improved",
"minecraft:hand_equipped": true
}
}
}
@@ -0,0 +1,17 @@
{
"format_version": "1.21.0",
"minecraft:item": {
"description": {
"identifier": "silverlabs:tent",
"menu_category": {
"category": "equipment",
"group": "itemGroup.name.miscellaneous"
}
},
"components": {
"minecraft:max_stack_size": 16,
"minecraft:icon": "tent_item",
"minecraft:hand_equipped": true
}
}
}
@@ -0,0 +1,54 @@
{
"format_version": 2,
"header": {
"name": "Camping Supplies",
"description": "Craftable tent and hammock for overnight camping without setting your spawn point",
"uuid": "bcf569fa-8b2c-403e-9f75-6b405132c5cd",
"version": [
1,
0,
0
],
"min_engine_version": [
1,
21,
0
]
},
"modules": [
{
"type": "data",
"uuid": "f306e1d8-3c13-4554-9715-4799ce6d41d8",
"version": [
1,
0,
0
]
},
{
"type": "script",
"language": "javascript",
"uuid": "1e496657-0c83-4707-a1e8-29b757dcce79",
"version": [
1,
0,
0
],
"entry": "scripts/main.js"
}
],
"dependencies": [
{
"module_name": "@minecraft/server",
"version": "1.17.0"
},
{
"uuid": "36f12107-10c6-484c-a0f2-b5dd88cd5baa",
"version": [
1,
0,
1
]
}
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -0,0 +1,33 @@
{
"format_version": "1.21.0",
"minecraft:recipe_shaped": {
"description": {
"identifier": "silverlabs:hammock_recipe"
},
"tags": [
"crafting_table"
],
"unlock": [
{
"item": "minecraft:crafting_table"
}
],
"pattern": [
"T T",
"WWW",
"T T"
],
"key": {
"W": {
"item": "minecraft:white_wool"
},
"T": {
"item": "minecraft:string"
}
},
"result": {
"item": "silverlabs:hammock",
"count": 1
}
}
}
@@ -0,0 +1,27 @@
{
"format_version": "1.21.0",
"minecraft:recipe_shapeless": {
"description": {
"identifier": "silverlabs:ore_detector_advanced_recipe"
},
"tags": [
"crafting_table"
],
"unlock": [
{
"item": "silverlabs:ore_detector_improved"
}
],
"ingredients": [
{ "item": "silverlabs:ore_detector_improved" },
{ "item": "minecraft:gold_ingot" },
{ "item": "minecraft:gold_ingot" },
{ "item": "minecraft:amethyst_shard" },
{ "item": "minecraft:amethyst_shard" }
],
"result": {
"item": "silverlabs:ore_detector_advanced",
"count": 1
}
}
}
@@ -0,0 +1,36 @@
{
"format_version": "1.21.0",
"minecraft:recipe_shaped": {
"description": {
"identifier": "silverlabs:ore_detector_basic_recipe"
},
"tags": [
"crafting_table"
],
"unlock": [
{
"item": "minecraft:redstone"
}
],
"pattern": [
" R ",
"CSC",
" C "
],
"key": {
"R": {
"item": "minecraft:redstone"
},
"C": {
"item": "minecraft:cobblestone"
},
"S": {
"item": "minecraft:stick"
}
},
"result": {
"item": "silverlabs:ore_detector_basic",
"count": 1
}
}
}
@@ -0,0 +1,28 @@
{
"format_version": "1.21.0",
"minecraft:recipe_shapeless": {
"description": {
"identifier": "silverlabs:ore_detector_improved_recipe"
},
"tags": [
"crafting_table"
],
"unlock": [
{
"item": "silverlabs:ore_detector_basic"
}
],
"ingredients": [
{ "item": "silverlabs:ore_detector_basic" },
{ "item": "minecraft:iron_ingot" },
{ "item": "minecraft:iron_ingot" },
{ "item": "minecraft:iron_ingot" },
{ "item": "minecraft:iron_ingot" },
{ "item": "minecraft:redstone" }
],
"result": {
"item": "silverlabs:ore_detector_improved",
"count": 1
}
}
}
@@ -0,0 +1,33 @@
{
"format_version": "1.21.0",
"minecraft:recipe_shaped": {
"description": {
"identifier": "silverlabs:tent_recipe"
},
"tags": [
"crafting_table"
],
"unlock": [
{
"item": "minecraft:crafting_table"
}
],
"pattern": [
" W ",
"WWW",
"S S"
],
"key": {
"W": {
"item": "minecraft:white_wool"
},
"S": {
"item": "minecraft:stick"
}
},
"result": {
"item": "silverlabs:tent",
"count": 1
}
}
}
@@ -0,0 +1,820 @@
import { world, system, ItemStack } from "@minecraft/server";
// ─── Constants ──────────────────────────────────────────────
const TENT_ITEM = "silverlabs:tent";
const HAMMOCK_ITEM = "silverlabs:hammock";
const TENT_BLOCK = "silverlabs:tent_canvas"; // legacy cube — kept so old worlds load cleanly
const TENT_PANEL_L = "silverlabs:tent_panel_l";
const TENT_PANEL_R = "silverlabs:tent_panel_r";
const TENT_BLOCK_IDS = [TENT_BLOCK, TENT_PANEL_L, TENT_PANEL_R];
const HAMMOCK_BLOCK = "silverlabs:hammock_cloth";
const STATE_PROP = "camping_state_v1";
const HAMMOCK_TAG = "camping_hammock";
const TENT_REST_PROP = "camping_tent_rest"; // per-player: "{x,y,z,dim,startTick}"
const SLEEP_TICK_INTERVAL = 20; // run sleep loop every 1s
const NIGHT_START = 12500;
const NIGHT_END = 23500;
// ─── State ──────────────────────────────────────────────────
// tents: key = "ox,oy,oz,dim" -> { ownerId, ownerName, cells: [[x,y,z]...] }
// hammocks: key = "ax,ay,az->bx,by,bz,dim" -> { ownerId, ownerName, anchorA, anchorB, cells }
let state = { tents: {}, hammocks: {} };
function loadState() {
try {
const raw = world.getDynamicProperty(STATE_PROP);
if (raw && typeof raw === "string") {
const parsed = JSON.parse(raw);
state = {
tents: parsed.tents || {},
hammocks: parsed.hammocks || {},
};
}
} catch (e) {
world.sendMessage(`§c[Camping] state load failed: ${e.message}`);
}
}
function saveState() {
try {
world.setDynamicProperty(STATE_PROP, JSON.stringify(state));
} catch (e) {
world.sendMessage(`§c[Camping] state save failed: ${e.message}`);
}
}
function keyOf(x, y, z, dimId) {
return `${x},${y},${z},${dimId}`;
}
// ─── Orientation helpers ────────────────────────────────────
function cardinalFacing(yaw) {
let y = yaw;
while (y > 180) y -= 360;
while (y < -180) y += 360;
if (y >= -45 && y < 45) return "south";
if (y >= 45 && y < 135) return "west";
if (y >= -135 && y < -45) return "east";
return "north";
}
// Map our placement facing → block-state cardinal_direction the panel rotates to.
// The geometry's "default" (cardinal_direction = "north") has the slope's outer
// edge on -X and inner apex on +X with the depth running along Z. When the
// player faces north, that aligns with the world. When they face elsewhere,
// the placement_direction trait + permutations rotate the model to match.
function blockFacingFor(playerFacing) {
return playerFacing; // 1:1 — placement_direction handles the rotation
}
function vecsForFacing(facing) {
switch (facing) {
case "north": return { fx: 0, fz: -1, rx: 1, rz: 0 };
case "south": return { fx: 0, fz: 1, rx: -1, rz: 0 };
case "east": return { fx: 1, fz: 0, rx: 0, rz: -1 };
case "west": return { fx: -1, fz: 0, rx: 0, rz: 1 };
}
return { fx: 0, fz: 1, rx: -1, rz: 0 };
}
// ─── Inventory helpers ──────────────────────────────────────
function consumeOneOfType(player, typeId) {
const inv = player.getComponent("inventory")?.container;
if (!inv) return false;
const slot = player.selectedSlotIndex;
const item = inv.getItem(slot);
if (item && item.typeId === typeId) {
if (item.amount > 1) {
item.amount -= 1;
inv.setItem(slot, item);
} else {
inv.setItem(slot, undefined);
}
return true;
}
for (let i = 0; i < inv.size; i++) {
const it = inv.getItem(i);
if (it && it.typeId === typeId) {
if (it.amount > 1) {
it.amount -= 1;
inv.setItem(i, it);
} else {
inv.setItem(i, undefined);
}
return true;
}
}
return false;
}
// ─── Tent placement (2×3 footprint, ridge-tunnel shape) ─────
function tryPlaceTent(player) {
const dim = player.dimension;
const facing = cardinalFacing(player.getRotation().y);
const { fx, fz, rx, rz } = vecsForFacing(facing);
// Use precise player position; floor X/Z but scan Y downward to find the actual
// standing surface. player.location.y may be fractionally above the block you're
// on (e.g. 87.01), so floor() alone is reliable, but if the player is in the
// air (jumping / on a slab / flying) we want to project them down to solid ground
// so the tent doesn't try to sit on empty space.
const feetX = Math.floor(player.location.x);
const feetZ = Math.floor(player.location.z);
let feetY = Math.floor(player.location.y);
// If the block at feet level is solid (player inside a block, e.g. standing in
// tall grass that rounded up), step up one.
const feetBlock = dim.getBlock({ x: feetX, y: feetY, z: feetZ });
if (feetBlock && !feetBlock.isAir && !feetBlock.isLiquid) feetY += 1;
// If the block below is air (mid-jump / airborne), project down to ground.
for (let probe = 0; probe < 4; probe++) {
const below = dim.getBlock({ x: feetX, y: feetY - 1, z: feetZ });
if (below && !below.isAir && !below.isLiquid) break;
feetY -= 1;
}
const ox = feetX + fx;
const oy = feetY;
const oz = feetZ + fz;
const groundCells = [];
const clearCells = [];
for (let l = 0; l < 3; l++) {
for (let w = 0; w < 2; w++) {
const cx = ox + l * fx + w * rx;
const cz = oz + l * fz + w * rz;
groundCells.push({ x: cx, y: oy - 1, z: cz });
for (let h = 0; h <= 1; h++) clearCells.push({ x: cx, y: oy + h, z: cz });
}
}
for (const g of groundCells) {
const b = dim.getBlock(g);
if (!b || b.isAir || b.isLiquid) {
const seen = b ? b.typeId : "unloaded";
player.sendMessage(`§c[Camping] §7Ground at §f${g.x},${g.y},${g.z}§7 is §f${seen}§7 — need solid ground there.`);
return false;
}
}
for (const c of clearCells) {
const b = dim.getBlock(c);
if (!b) {
player.sendMessage(`§c[Camping] §7Can't reach §f${c.x},${c.y},${c.z}§7 (chunk unloaded).`);
return false;
}
if (!b.isAir && !b.isLiquid) {
player.sendMessage(`§c[Camping] §7Space at §f${c.x},${c.y},${c.z}§7 is blocked by §f${b.typeId}§7.`);
return false;
}
}
// A-frame layout: 3 long × 2 wide, single block tall. Each cross-section is a
// pair of slope panels meeting at the apex on the seam between the two columns.
// Bedrock renders the panel geometry mirrored across the block's local X axis
// relative to a literal read of the .geo.json, so panel_r goes in the player's
// column and panel_l goes one step right to land /\ instead of \/.
const blockFacing = blockFacingFor(facing);
const canvasCells = [];
for (let l = 0; l < 3; l++) {
canvasCells.push({
x: ox + l * fx,
y: oy,
z: oz + l * fz,
block: TENT_PANEL_R,
});
canvasCells.push({
x: ox + l * fx + rx,
y: oy,
z: oz + l * fz + rz,
block: TENT_PANEL_L,
});
}
for (const c of canvasCells) {
try {
dim.runCommand(
`setblock ${c.x} ${c.y} ${c.z} ${c.block} ["minecraft:cardinal_direction"="${blockFacing}"]`
);
} catch (_) {}
}
const key = keyOf(ox, oy, oz, dim.id);
state.tents[key] = {
ownerId: player.id,
ownerName: player.name,
facing,
cells: canvasCells.map((c) => [c.x, c.y, c.z]),
};
saveState();
return true;
}
// ─── Hammock placement ──────────────────────────────────────
function isPostBlock(block) {
if (!block) return false;
const id = block.typeId;
return (
id.endsWith("_fence") ||
id.endsWith("_log") ||
id.endsWith("_wood") ||
id.includes("stripped_") ||
id.endsWith("_wall")
);
}
function findPartnerPost(dim, anchor) {
const candidates = [];
for (let dx = -6; dx <= 6; dx++) {
for (let dz = -6; dz <= 6; dz++) {
if (dx === 0 && dz === 0) continue;
const aligned = dx === 0 || dz === 0 || Math.abs(dx) === Math.abs(dz);
if (!aligned) continue;
const dist = Math.max(Math.abs(dx), Math.abs(dz));
if (dist < 3 || dist > 6) continue;
for (let dy = -1; dy <= 1; dy++) {
const pos = { x: anchor.x + dx, y: anchor.y + dy, z: anchor.z + dz };
let blk;
try { blk = dim.getBlock(pos); } catch (_) { continue; }
if (!blk || !isPostBlock(blk)) continue;
candidates.push({ pos, dist, dy });
}
}
}
if (candidates.length === 0) return null;
candidates.sort((a, b) => (Math.abs(a.dy) - Math.abs(b.dy)) || (a.dist - b.dist));
return candidates[0].pos;
}
function computeHammockCells(a, b) {
const dx = b.x - a.x;
const dy = b.y - a.y;
const dz = b.z - a.z;
const steps = Math.max(Math.abs(dx), Math.abs(dz));
const cells = [];
for (let t = 1; t < steps; t++) {
const cx = a.x + Math.round((dx * t) / steps);
const cz = a.z + Math.round((dz * t) / steps);
let cy = a.y + Math.round((dy * t) / steps);
const rel = t / steps;
if (steps >= 4 && rel > 0.25 && rel < 0.75) cy -= 1;
cells.push({ x: cx, y: cy, z: cz });
}
return cells;
}
function tryPlaceHammock(player, anchorBlock) {
const dim = player.dimension;
const a = {
x: anchorBlock.location.x,
y: anchorBlock.location.y,
z: anchorBlock.location.z,
};
const b = findPartnerPost(dim, a);
if (!b) {
player.sendMessage("§c[Camping] §7Need a second post 36 blocks away (straight line or diagonal, ±1 block in height).");
return false;
}
const cells = computeHammockCells(a, b);
for (const c of cells) {
let blk;
try { blk = dim.getBlock(c); } catch (_) { return false; }
if (!blk || (!blk.isAir && !blk.isLiquid)) {
player.sendMessage("§c[Camping] §7The space between the posts isn't clear.");
return false;
}
}
for (const c of cells) {
try { dim.runCommand(`setblock ${c.x} ${c.y} ${c.z} ${HAMMOCK_BLOCK}`); } catch (_) {}
}
const key = `${a.x},${a.y},${a.z}->${b.x},${b.y},${b.z},${dim.id}`;
state.hammocks[key] = {
ownerId: player.id,
ownerName: player.name,
anchorA: [a.x, a.y, a.z],
anchorB: [b.x, b.y, b.z],
cells: cells.map((c) => [c.x, c.y, c.z]),
};
saveState();
return true;
}
// ─── Item use handler ───────────────────────────────────────
world.afterEvents.itemUse.subscribe((event) => {
const player = event.source;
const stack = event.itemStack;
if (!stack || !player) return;
if (stack.typeId === TENT_ITEM) {
system.run(() => {
if (tryPlaceTent(player)) {
consumeOneOfType(player, TENT_ITEM);
player.sendMessage("§a[Camping] §7Tent pitched. Right-click the canvas to rest until dawn.");
}
});
} else if (stack.typeId === HAMMOCK_ITEM) {
system.run(() => {
const looking = player.getBlockFromViewDirection({ maxDistance: 6 });
const block = looking?.block;
if (!block || !isPostBlock(block)) {
player.sendMessage("§c[Camping] §7Aim at a fence, log, or wooden post to anchor the hammock.");
return;
}
if (tryPlaceHammock(player, block)) {
consumeOneOfType(player, HAMMOCK_ITEM);
player.sendMessage("§a[Camping] §7Hammock strung. Right-click the cloth to climb in.");
}
});
}
});
// ─── Interact: tent rest + hammock toggle ───────────────────
try {
world.beforeEvents.playerInteractWithBlock.subscribe((event) => {
const block = event.block;
if (!block) return;
if (TENT_BLOCK_IDS.includes(block.typeId)) {
event.cancel = true;
const player = event.player;
const loc = { x: block.location.x, y: block.location.y, z: block.location.z };
const dimId = block.dimension.id;
system.run(() => enterTentRest(player, loc, dimId));
} else if (block.typeId === HAMMOCK_BLOCK) {
event.cancel = true;
const player = event.player;
const loc = block.location;
system.run(() => toggleHammock(player, loc));
}
});
} catch (e) {
console.warn(`[Camping] playerInteractWithBlock unavailable: ${e}`);
}
// ─── Tent rest: vote-skip with mixed bed + tent sleepers ────────
// Tracks which players are currently "resting" in a tent. A player counts as a
// tent sleeper as long as they stay near the panel they interacted with and
// don't sneak/move/disconnect/take damage. Vanilla bed sleepers are detected
// via player.isSleeping (true while a player is in a real bed). We compare the
// combined count against the world's playersSleepingPercentage gamerule and
// skip the night when the threshold is crossed.
const tentRest = new Map(); // playerId → { x, y, z, dimId, startTick }
function isNight(tod) {
return tod >= NIGHT_START && tod <= NIGHT_END;
}
function enterTentRest(player, loc, dimId) {
const tod = world.getTimeOfDay();
if (!isNight(tod)) {
player.sendMessage("§e[Camping] §7It's still daylight — nothing to sleep off.");
return;
}
if (tentRest.has(player.id)) {
leaveTentRest(player, "§7[Camping] You stop resting.");
return;
}
tentRest.set(player.id, {
x: loc.x,
y: loc.y,
z: loc.z,
dimId,
startTick: system.currentTick,
});
// Cinematic fade so it reads like sleep instead of a status message.
try {
player.runCommand("camera @s fade time 0.4 1.5 0.6 color 0 0 0");
} catch (_) {}
try {
player.onScreenDisplay.setTitle("§7Resting…", {
fadeInDuration: 8,
stayDuration: 60,
fadeOutDuration: 12,
subtitle: "§8Move, sneak, or take damage to wake.",
});
} catch (_) {}
reportSleepProgress(player, /*onEnter*/ true);
}
function leaveTentRest(player, msg) {
if (!tentRest.delete(player.id)) return;
if (msg && player) {
try { player.sendMessage(msg); } catch (_) {}
}
}
function countSleepers() {
let bed = 0;
let tent = 0;
let online = 0;
for (const p of world.getAllPlayers()) {
online++;
// Vanilla bed sleep: Player.isSleeping is true while they're in a bed.
// Available since @minecraft/server 1.10+; guarded for safety.
let sleeping = false;
try { sleeping = !!p.isSleeping; } catch (_) {}
if (sleeping) bed++;
else if (tentRest.has(p.id)) tent++;
}
return { bed, tent, online, resting: bed + tent };
}
function getSleepThreshold() {
// playersSleepingPercentage is a percentage 0-100. 0 means any one player
// can skip night (vanilla quirk); 100 means everyone must sleep.
let pct = 100;
try {
const v = world.gameRules?.playersSleepingPercentage;
if (typeof v === "number") pct = v;
} catch (_) {}
// 0 in vanilla means "one is enough" — preserve that intent.
if (pct <= 0) return 1;
return pct;
}
function requiredSleepers(online, pct) {
// Standard vanilla rounding: ceil(online * pct / 100), min 1.
return Math.max(1, Math.ceil((online * pct) / 100));
}
function reportSleepProgress(targetPlayer, onEnter = false) {
const { bed, tent, online, resting } = countSleepers();
const pct = getSleepThreshold();
const need = requiredSleepers(online, pct);
const remaining = Math.max(0, need - resting);
const msg = onEnter
? `§a[Camping] §7You settle in. §f${resting}§7/§f${need}§7 resting (§f${tent}§7 tent + §f${bed}§7 bed)${remaining ? `. Need §f${remaining}§7 more.` : `.`}`
: `§7[Sleep] §f${resting}§7/§f${need}§7 resting (§f${tent}§7 tent + §f${bed}§7 bed)`;
if (onEnter && targetPlayer) {
try { targetPlayer.sendMessage(msg); } catch (_) {}
} else {
// Broadcast a subtle update to everyone currently resting.
for (const p of world.getAllPlayers()) {
if (tentRest.has(p.id) || (() => { try { return !!p.isSleeping; } catch (_) { return false; } })()) {
try { p.sendMessage(msg); } catch (_) {}
}
}
}
}
function awardRestEffects(player) {
try {
player.addEffect("regeneration", 200, { amplifier: 1, showParticles: false });
player.addEffect("saturation", 40, { amplifier: 0, showParticles: false });
} catch (_) {}
}
function executeNightSkip() {
// Snapshot tent sleepers before clearing, so we can give them rest perks.
const tentIds = [...tentRest.keys()];
tentRest.clear();
try { world.setTimeOfDay(0); } catch (_) {}
for (const p of world.getAllPlayers()) {
if (tentIds.includes(p.id) || p.isSleeping) {
awardRestEffects(p);
}
try { p.runCommand("camera @s clear"); } catch (_) {}
}
world.sendMessage("§6[Sleep] §7The camp rests. Dawn breaks.");
}
// ─── Sleep loop: validate tent sleepers, check threshold ────────
system.runInterval(() => {
if (tentRest.size === 0) return;
// Validate each tent sleeper still meets the criteria.
for (const [pid, rest] of [...tentRest.entries()]) {
const player = world.getAllPlayers().find((p) => p.id === pid);
if (!player) {
tentRest.delete(pid);
continue;
}
if (player.dimension.id !== rest.dimId) {
leaveTentRest(player, "§7[Camping] You wandered out of camp.");
continue;
}
const dx = player.location.x - (rest.x + 0.5);
const dy = player.location.y - rest.y;
const dz = player.location.z - (rest.z + 0.5);
if (dx * dx + dz * dz > 4 || Math.abs(dy) > 2) {
leaveTentRest(player, "§7[Camping] You wandered out of camp.");
continue;
}
if (player.isSneaking) {
leaveTentRest(player, "§7[Camping] You climb out of the tent.");
continue;
}
// Sleepers don't get scared off by mobs but a hit cancels rest:
// (handled implicitly — damage breaks the camera fade and the player is
// expected to sneak out; we don't have a public hurt event hook here)
awardRestEffects(player);
}
if (tentRest.size === 0) return;
const tod = world.getTimeOfDay();
if (!isNight(tod)) {
// Sun came up some other way — clear resters quietly.
tentRest.clear();
return;
}
const { online, resting } = countSleepers();
const pct = getSleepThreshold();
const need = requiredSleepers(online, pct);
if (resting >= need) {
executeNightSkip();
}
}, SLEEP_TICK_INTERVAL);
const HAMMOCK_ANCHOR_PROP = "camping_hammock_anchor";
function toggleHammock(player, loc) {
if (player.hasTag(HAMMOCK_TAG)) {
exitHammock(player);
return;
}
player.addTag(HAMMOCK_TAG);
const anchor = { x: loc.x + 0.5, y: loc.y + 0.1, z: loc.z + 0.5 };
try {
player.setDynamicProperty(HAMMOCK_ANCHOR_PROP, JSON.stringify(anchor));
} catch (_) {}
try {
player.teleport(anchor, { dimension: player.dimension });
} catch (_) {}
// Slowness 255 + weakness + mining_fatigue make the player effectively immobile while
// still conscious; saturation + regen are the "rest" payoff.
try {
player.addEffect("slowness", 100000, { amplifier: 255, showParticles: false });
player.addEffect("weakness", 100000, { amplifier: 255, showParticles: false });
player.addEffect("mining_fatigue", 100000, { amplifier: 255, showParticles: false });
player.addEffect("saturation", 40, { amplifier: 0, showParticles: false });
player.addEffect("regeneration", 200, { amplifier: 1, showParticles: false });
} catch (_) {}
// Pull the camera back into a cinematic third-person view so the player can see
// themselves lying in the hammock. Ease in smoothly.
try {
player.runCommand("camera @s set minecraft:third_person ease 0.8 out_sine");
} catch (_) {}
player.sendMessage("§a[Camping] §7You settle into the hammock. Wild creatures don't notice you. §8(Sneak to climb out.)");
}
function exitHammock(player) {
player.removeTag(HAMMOCK_TAG);
try {
player.removeEffect("slowness");
player.removeEffect("weakness");
player.removeEffect("mining_fatigue");
} catch (_) {}
// Nudge player one block off the hammock so the next tick doesn't re-teleport them
// back into the cradle.
try {
const raw = player.getDynamicProperty(HAMMOCK_ANCHOR_PROP);
if (raw && typeof raw === "string") {
const a = JSON.parse(raw);
const yaw = player.getRotation().y;
const rad = (yaw * Math.PI) / 180;
const dx = -Math.sin(rad);
const dz = Math.cos(rad);
player.teleport(
{ x: a.x + dx * 1.2, y: a.y + 0.3, z: a.z + dz * 1.2 },
{ dimension: player.dimension }
);
}
} catch (_) {}
try { player.setDynamicProperty(HAMMOCK_ANCHOR_PROP, undefined); } catch (_) {}
try { player.runCommand("camera @s clear"); } catch (_) {}
player.sendMessage("§7[Camping] You climb out of the hammock.");
}
// ─── Hammock upkeep loop: position lock + mob repulsion + sneak-exit ────────
system.runInterval(() => {
for (const player of world.getAllPlayers()) {
if (!player.hasTag(HAMMOCK_TAG)) continue;
if (player.isSneaking) {
exitHammock(player);
continue;
}
// Pin the player to the hammock anchor so they can't drift off even with slowness
try {
const raw = player.getDynamicProperty(HAMMOCK_ANCHOR_PROP);
if (raw && typeof raw === "string") {
const a = JSON.parse(raw);
const dx = player.location.x - a.x;
const dy = player.location.y - a.y;
const dz = player.location.z - a.z;
if (dx * dx + dy * dy + dz * dz > 0.25) {
player.teleport(a, { dimension: player.dimension });
}
}
} catch (_) {}
let hostiles = [];
try {
hostiles = player.dimension.getEntities({
families: ["monster"],
location: player.location,
maxDistance: 14,
});
} catch (_) {}
for (const m of hostiles) {
const dx = m.location.x - player.location.x;
const dy = m.location.y - player.location.y;
const dz = m.location.z - player.location.z;
const d = Math.sqrt(dx * dx + dy * dy + dz * dz);
if (d > 0.01 && d < 6) {
const scale = 9 / d;
const target = {
x: player.location.x + dx * scale,
y: m.location.y,
z: player.location.z + dz * scale,
};
try { m.tryTeleport(target, { checkForBlocks: true }); } catch (_) {}
}
}
}
}, 10);
// ─── Break cleanup: break one = pack up the whole structure ─
try {
world.beforeEvents.playerBreakBlock.subscribe((event) => {
const block = event.block;
if (!block) return;
const id = block.typeId;
const isTent = TENT_BLOCK_IDS.includes(id);
if (!isTent && id !== HAMMOCK_BLOCK) return;
event.cancel = true;
const loc = { x: block.location.x, y: block.location.y, z: block.location.z };
const dimId = block.dimension.id;
const player = event.player;
if (isTent) {
system.run(() => dismantleTentAt(loc, dimId, player));
} else {
system.run(() => dismantleHammockAt(loc, dimId, player));
}
});
} catch (e) {
console.warn(`[Camping] playerBreakBlock unavailable: ${e}`);
}
function dismantleTentAt(loc, dimId, player) {
const dim = world.getDimension(dimId);
let matchedKey = null;
for (const [k, tent] of Object.entries(state.tents)) {
const parts = k.split(",");
if (parts[parts.length - 1] !== dimId) continue;
if (tent.cells.some(([x, y, z]) => x === loc.x && y === loc.y && z === loc.z)) {
matchedKey = k;
break;
}
}
if (!matchedKey) {
try { dim.runCommand(`setblock ${loc.x} ${loc.y} ${loc.z} air`); } catch (_) {}
return;
}
const tent = state.tents[matchedKey];
for (const [x, y, z] of tent.cells) {
try { dim.runCommand(`setblock ${x} ${y} ${z} air`); } catch (_) {}
}
delete state.tents[matchedKey];
saveState();
try {
dim.spawnItem(new ItemStack(TENT_ITEM, 1), { x: loc.x + 0.5, y: loc.y + 0.5, z: loc.z + 0.5 });
} catch (_) {}
if (player) player.sendMessage("§7[Camping] Tent packed up.");
}
function dismantleHammockAt(loc, dimId, player) {
const dim = world.getDimension(dimId);
let matchedKey = null;
for (const [k, h] of Object.entries(state.hammocks)) {
if (!k.endsWith("," + dimId)) continue;
if (h.cells.some(([x, y, z]) => x === loc.x && y === loc.y && z === loc.z)) {
matchedKey = k;
break;
}
}
if (!matchedKey) {
try { dim.runCommand(`setblock ${loc.x} ${loc.y} ${loc.z} air`); } catch (_) {}
return;
}
const h = state.hammocks[matchedKey];
for (const [x, y, z] of h.cells) {
try { dim.runCommand(`setblock ${x} ${y} ${z} air`); } catch (_) {}
}
delete state.hammocks[matchedKey];
saveState();
try {
dim.spawnItem(new ItemStack(HAMMOCK_ITEM, 1), { x: loc.x + 0.5, y: loc.y + 0.5, z: loc.z + 0.5 });
} catch (_) {}
if (player) player.sendMessage("§7[Camping] Hammock taken down.");
}
// ─── Ore Detector ───────────────────────────────────────────
// Three tiered items share one scan handler. Tier → max scan range.
// Faraday: any ore within 4 blocks of a silverlabs:private_chest is hidden
// (mirrors the source DetectOre faraday-cage mechanic, but built into private chests).
const DETECTOR_RANGES = {
"silverlabs:ore_detector_basic": 8,
"silverlabs:ore_detector_improved": 16,
"silverlabs:ore_detector_advanced": 32,
};
const PRIVATE_CHEST_ID = "silverlabs:private_chest";
const FARADAY_RADIUS = 4; // 9³ cube around each ore candidate
const ORE_IDS = new Set([
"minecraft:coal_ore", "minecraft:deepslate_coal_ore",
"minecraft:iron_ore", "minecraft:deepslate_iron_ore",
"minecraft:copper_ore", "minecraft:deepslate_copper_ore",
"minecraft:gold_ore", "minecraft:deepslate_gold_ore", "minecraft:nether_gold_ore",
"minecraft:redstone_ore", "minecraft:deepslate_redstone_ore",
"minecraft:lit_redstone_ore", "minecraft:lit_deepslate_redstone_ore",
"minecraft:lapis_ore", "minecraft:deepslate_lapis_ore",
"minecraft:emerald_ore", "minecraft:deepslate_emerald_ore",
"minecraft:diamond_ore", "minecraft:deepslate_diamond_ore",
"minecraft:nether_quartz_ore",
"minecraft:ancient_debris",
]);
function prettyOreName(typeId) {
const bare = typeId.replace(/^minecraft:/, "")
.replace(/^lit_/, "")
.replace(/^deepslate_/, "Deepslate ")
.replace(/^nether_/, "Nether ");
return bare.split("_").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
}
function isPrivateChestNearby(dim, cx, cy, cz) {
for (let dx = -FARADAY_RADIUS; dx <= FARADAY_RADIUS; dx++) {
for (let dy = -FARADAY_RADIUS; dy <= FARADAY_RADIUS; dy++) {
for (let dz = -FARADAY_RADIUS; dz <= FARADAY_RADIUS; dz++) {
const b = dim.getBlock({ x: cx + dx, y: cy + dy, z: cz + dz });
if (b && b.typeId === PRIVATE_CHEST_ID) return true;
}
}
}
return false;
}
function runOreScan(player, range) {
const dim = player.dimension;
const head = player.getHeadLocation();
const dir = player.getViewDirection();
// Walk integer-block steps along the view ray. Resolution 0.5 blocks
// catches ores the cardinal-aligned steps would skip on diagonals.
const stepCount = Math.floor(range * 2);
const seen = new Set();
let foundOre = null;
let foundDist = 0;
for (let i = 1; i <= stepCount; i++) {
const t = i * 0.5;
const px = head.x + dir.x * t;
const py = head.y + dir.y * t;
const pz = head.z + dir.z * t;
const bx = Math.floor(px);
const by = Math.floor(py);
const bz = Math.floor(pz);
const key = `${bx},${by},${bz}`;
if (seen.has(key)) continue;
seen.add(key);
let block;
try { block = dim.getBlock({ x: bx, y: by, z: bz }); } catch (_) { continue; }
if (!block) continue;
if (ORE_IDS.has(block.typeId)) {
if (isPrivateChestNearby(dim, bx, by, bz)) continue; // faraday
foundOre = block.typeId;
foundDist = t;
break;
}
}
if (foundOre) {
const distRounded = Math.round(foundDist * 10) / 10;
const pitch = Math.max(0.8, Math.min(2.0, 2.0 - (foundDist / range)));
try {
player.playSound("random.orb", { pitch, volume: 0.7 });
} catch (_) {}
try {
player.onScreenDisplay.setActionBar(`§a● §f${prettyOreName(foundOre)} §7at §b${distRounded}m`);
} catch (_) {}
} else {
try {
player.playSound("note.bass", { pitch: 0.7, volume: 0.5 });
} catch (_) {}
try {
player.onScreenDisplay.setActionBar(`§7○ No ores within §f${range}m`);
} catch (_) {}
}
}
world.afterEvents.itemUse.subscribe((event) => {
const player = event.source;
const stack = event.itemStack;
if (!stack || !player) return;
const range = DETECTOR_RANGES[stack.typeId];
if (!range) return;
system.run(() => runOreScan(player, range));
});
// ─── Boot ───────────────────────────────────────────────────
system.run(() => {
loadState();
world.sendMessage("§6[Camping] §7Camping Supplies loaded.");
});
@@ -0,0 +1,5 @@
{
"format_version": [1, 1, 0],
"silverlabs:tent_canvas": { "sound": "cloth" },
"silverlabs:hammock_cloth": { "sound": "cloth" }
}
@@ -0,0 +1,17 @@
{
"format_version": 2,
"header": {
"name": "Camping Supplies Resources",
"description": "Textures and lang for camping supplies (tent, hammock, canvas, cloth)",
"uuid": "36f12107-10c6-484c-a0f2-b5dd88cd5baa",
"version": [1, 0, 1],
"min_engine_version": [1, 21, 0]
},
"modules": [
{
"type": "resources",
"uuid": "c9ee429f-9374-4083-843b-4b195e8db130",
"version": [1, 0, 1]
}
]
}
@@ -0,0 +1,21 @@
{
"format_version": "1.12.0",
"minecraft:geometry": [
{
"description": {
"identifier": "geometry.silverlabs.hammock_slab",
"texture_width": 16,
"texture_height": 16
},
"bones": [
{
"name": "slab",
"pivot": [0, 0, 0],
"cubes": [
{ "origin": [-8, 0, -8], "size": [16, 4, 16], "uv": [0, 0] }
]
}
]
}
]
}
@@ -0,0 +1,31 @@
{
"format_version": "1.12.0",
"minecraft:geometry": [
{
"description": {
"identifier": "geometry.silverlabs.tent_panel_l",
"texture_width": 16,
"texture_height": 16,
"visible_bounds_width": 2,
"visible_bounds_height": 2,
"visible_bounds_offset": [0, 0.5, 0]
},
"bones": [
{
"name": "panel",
"pivot": [0, 0, 0],
"cubes": [
{ "origin": [-8, 0, -8], "size": [2, 2, 16], "uv": [0, 0] },
{ "origin": [-6, 2, -8], "size": [2, 2, 16], "uv": [0, 0] },
{ "origin": [-4, 4, -8], "size": [2, 2, 16], "uv": [0, 0] },
{ "origin": [-2, 6, -8], "size": [2, 2, 16], "uv": [0, 0] },
{ "origin": [ 0, 8, -8], "size": [2, 2, 16], "uv": [0, 0] },
{ "origin": [ 2, 10, -8], "size": [2, 2, 16], "uv": [0, 0] },
{ "origin": [ 4, 12, -8], "size": [2, 2, 16], "uv": [0, 0] },
{ "origin": [ 6, 14, -8], "size": [2, 2, 16], "uv": [0, 0] }
]
}
]
}
]
}
@@ -0,0 +1,31 @@
{
"format_version": "1.12.0",
"minecraft:geometry": [
{
"description": {
"identifier": "geometry.silverlabs.tent_panel_r",
"texture_width": 16,
"texture_height": 16,
"visible_bounds_width": 2,
"visible_bounds_height": 2,
"visible_bounds_offset": [0, 0.5, 0]
},
"bones": [
{
"name": "panel",
"pivot": [0, 0, 0],
"cubes": [
{ "origin": [ 6, 0, -8], "size": [2, 2, 16], "uv": [0, 0] },
{ "origin": [ 4, 2, -8], "size": [2, 2, 16], "uv": [0, 0] },
{ "origin": [ 2, 4, -8], "size": [2, 2, 16], "uv": [0, 0] },
{ "origin": [ 0, 6, -8], "size": [2, 2, 16], "uv": [0, 0] },
{ "origin": [-2, 8, -8], "size": [2, 2, 16], "uv": [0, 0] },
{ "origin": [-4, 10, -8], "size": [2, 2, 16], "uv": [0, 0] },
{ "origin": [-6, 12, -8], "size": [2, 2, 16], "uv": [0, 0] },
{ "origin": [-8, 14, -8], "size": [2, 2, 16], "uv": [0, 0] }
]
}
]
}
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -0,0 +1,7 @@
item.silverlabs:tent.name=Tent
item.silverlabs:hammock.name=Hammock
item.silverlabs:ore_detector_basic.name=Ore Detector
item.silverlabs:ore_detector_improved.name=Ore Detector (Improved)
item.silverlabs:ore_detector_advanced.name=Ore Detector (Advanced)
tile.silverlabs:tent_canvas.name=Tent Canvas
tile.silverlabs:hammock_cloth.name=Hammock Cloth
Binary file not shown.

After

Width:  |  Height:  |  Size: 772 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 427 B

@@ -0,0 +1,21 @@
{
"resource_pack_name": "camping_supplies_RP",
"texture_name": "atlas.items",
"texture_data": {
"tent_item": {
"textures": "textures/items/tent"
},
"hammock_item": {
"textures": "textures/items/hammock"
},
"ore_detector_basic": {
"textures": "textures/items/ore_detector_basic"
},
"ore_detector_improved": {
"textures": "textures/items/ore_detector_improved"
},
"ore_detector_advanced": {
"textures": "textures/items/ore_detector_advanced"
}
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 218 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 199 B

@@ -0,0 +1,14 @@
{
"resource_pack_name": "camping_supplies_RP",
"texture_name": "atlas.terrain",
"padding": 8,
"num_mip_levels": 4,
"texture_data": {
"tent_canvas": {
"textures": "textures/blocks/tent_canvas"
},
"hammock_cloth": {
"textures": "textures/blocks/hammock_cloth"
}
}
}
+94 -40
View File
@@ -4,16 +4,13 @@ services:
container_name: mc-lobby
environment:
EULA: "TRUE"
SERVER_NAME: "SilverLABS Hub"
GAMEMODE: adventure
DIFFICULTY: peaceful
ALLOW_CHEATS: "true"
ONLINE_MODE: "false"
SERVER_PORT: "19132"
LEVEL_NAME: "Hub World"
MAX_PLAYERS: "10"
DEFAULT_PLAYER_PERMISSION_LEVEL: operator
OP_PERMISSION_LEVEL: "4"
# Other server.properties-aliased env vars (SERVER_NAME, GAMEMODE, DIFFICULTY,
# ALLOW_CHEATS, ONLINE_MODE, MAX_PLAYERS, DEFAULT_PLAYER_PERMISSION_LEVEL) are
# intentionally not set here — they're owned by /data/server.properties inside
# the named volume and edited via mc-manager's UI. LEVEL_NAME lives in
# docker-compose.override.yml on the host (managed by mc-manager, gitignored).
ports:
- "${LOBBY_PORT:-19132}:19132/udp"
volumes:
@@ -26,7 +23,29 @@ services:
- ./addon/heyhe_pet_RP:/data/resource_packs/heyhe_pet_RP
- ./addon/anthrax_cat_BP:/data/behavior_packs/anthrax_cat_BP
- ./addon/anthrax_cat_RP:/data/resource_packs/anthrax_cat_RP
- ./monkey-addon/monkey_BP:/data/behavior_packs/monkey_BP
- ./monkey-addon/monkey_RP:/data/resource_packs/monkey_RP
- ./private-chest-addon/private_chest_BP:/data/behavior_packs/private_chest_BP
- ./private-chest-addon/private_chest_RP:/data/resource_packs/private_chest_RP
- ./home-sign-addon/home_sign_BP:/data/behavior_packs/home_sign_BP
- ./home-sign-addon/home_sign_RP:/data/resource_packs/home_sign_RP
- ./keep-inventory-addon/keep_inventory_BP:/data/behavior_packs/keep_inventory_BP
- ./postal-service-addon/postal_service_BP:/data/behavior_packs/postal_service_BP
- ./postal-service-addon/postal_service_RP:/data/resource_packs/postal_service_RP
- ./camping-supplies-addon/camping_supplies_BP:/data/behavior_packs/camping_supplies_BP
- ./camping-supplies-addon/camping_supplies_RP:/data/resource_packs/camping_supplies_RP
- ./dynamite-addon/dynamite_BP:/data/behavior_packs/dynamite_BP
- ./dynamite-addon/dynamite_RP:/data/resource_packs/dynamite_RP
- ./tow-boat-addon/tow_boat_BP:/data/behavior_packs/tow_boat_BP
- ./tow-boat-addon/tow_boat_RP:/data/resource_packs/tow_boat_RP
- ./redstone-link-addon/redstone_link_BP:/data/behavior_packs/redstone_link_BP
- ./redstone-link-addon/redstone_link_RP:/data/resource_packs/redstone_link_RP
restart: unless-stopped
# Cap each Bedrock service so a runaway/hung server can't OOM-kill its
# neighbours. Host has 8 GB; 4 × 1500 MB leaves headroom for the OS and
# the supporting stack.
mem_limit: 1500m
memswap_limit: 2500m
networks:
- mc-network
@@ -35,30 +54,39 @@ services:
container_name: mc-jamie
environment:
EULA: "TRUE"
SERVER_NAME: "Jamie's World"
GAMEMODE: survival
DIFFICULTY: normal
ALLOW_CHEATS: "true"
ONLINE_MODE: "false"
SERVER_PORT: "19132"
LEVEL_NAME: "Jamie World"
MAX_PLAYERS: "10"
DEFAULT_PLAYER_PERMISSION_LEVEL: operator
OP_PERMISSION_LEVEL: "4"
# See lobby for rationale. LEVEL_SEED is dropped too — the world has long since
# been generated and the seed is ignored thereafter.
ports:
- "${JAMIE_PORT:-19133}:19132/udp"
volumes:
- jamie-data:/data
- ./hub-return-addon/hub_return_transfer_BP:/data/behavior_packs/hub_return_transfer_BP
- ./addon/spark_pet_BP:/data/behavior_packs/spark_pet_BP
- ./addon/spark_pet_RP:/data/resource_packs/spark_pet_RP
- ./addon/heyhe_pet_BP:/data/behavior_packs/heyhe_pet_BP
- ./addon/heyhe_pet_RP:/data/resource_packs/heyhe_pet_RP
- ./addon/anthrax_cat_BP:/data/behavior_packs/anthrax_cat_BP
- ./addon/anthrax_cat_RP:/data/resource_packs/anthrax_cat_RP
- ./village-evolution-addon/village_evolution_BP:/data/behavior_packs/village_evolution_BP
- ./village-evolution-addon/enabled_packs.json:/data/config/default/enabled_packs.json
- ./hub-return-addon/hub_return_transfer_RP:/data/resource_packs/hub_return_transfer_RP
- ./private-chest-addon/private_chest_BP:/data/behavior_packs/private_chest_BP
- ./private-chest-addon/private_chest_RP:/data/resource_packs/private_chest_RP
- ./smart-crafting-addon/smart_crafting_BP:/data/behavior_packs/smart_crafting_BP
- ./smart-crafting-addon/smart_crafting_RP:/data/resource_packs/smart_crafting_RP
- ./home-sign-addon/home_sign_BP:/data/behavior_packs/home_sign_BP
- ./home-sign-addon/home_sign_RP:/data/resource_packs/home_sign_RP
- ./keep-inventory-addon/keep_inventory_BP:/data/behavior_packs/keep_inventory_BP
- ./postal-service-addon/postal_service_BP:/data/behavior_packs/postal_service_BP
- ./postal-service-addon/postal_service_RP:/data/resource_packs/postal_service_RP
- ./camping-supplies-addon/camping_supplies_BP:/data/behavior_packs/camping_supplies_BP
- ./camping-supplies-addon/camping_supplies_RP:/data/resource_packs/camping_supplies_RP
- ./dynamite-addon/dynamite_BP:/data/behavior_packs/dynamite_BP
- ./dynamite-addon/dynamite_RP:/data/resource_packs/dynamite_RP
- ./tow-boat-addon/tow_boat_BP:/data/behavior_packs/tow_boat_BP
- ./tow-boat-addon/tow_boat_RP:/data/resource_packs/tow_boat_RP
- ./trees-features-addon/trees_features_BP:/data/behavior_packs/trees_features_BP
- ./trees-features-addon/trees_features_RP:/data/resource_packs/trees_features_RP
- ./hemp-addon/hemp_BP:/data/behavior_packs/hemp_BP
- ./hemp-addon/hemp_RP:/data/resource_packs/hemp_RP
- ./family-goals-addon/family_goals_BP:/data/behavior_packs/family_goals_BP
restart: unless-stopped
mem_limit: 1500m
memswap_limit: 2500m
networks:
- mc-network
@@ -67,21 +95,14 @@ services:
container_name: mc-lyla
environment:
EULA: "TRUE"
SERVER_NAME: "Lyla's World"
GAMEMODE: survival
DIFFICULTY: normal
ALLOW_CHEATS: "true"
ONLINE_MODE: "false"
SERVER_PORT: "19132"
LEVEL_NAME: "Lyla World"
MAX_PLAYERS: "10"
DEFAULT_PLAYER_PERMISSION_LEVEL: operator
OP_PERMISSION_LEVEL: "4"
ports:
- "${LYLA_PORT:-19134}:19132/udp"
volumes:
- lyla-data:/data
- ./hub-return-addon/hub_return_transfer_BP:/data/behavior_packs/hub_return_transfer_BP
- ./hub-return-addon/hub_return_transfer_RP:/data/resource_packs/hub_return_transfer_RP
- ./addon/spark_pet_BP:/data/behavior_packs/spark_pet_BP
- ./addon/spark_pet_RP:/data/resource_packs/spark_pet_RP
- ./addon/heyhe_pet_BP:/data/behavior_packs/heyhe_pet_BP
@@ -89,8 +110,25 @@ services:
- ./addon/anthrax_cat_BP:/data/behavior_packs/anthrax_cat_BP
- ./addon/anthrax_cat_RP:/data/resource_packs/anthrax_cat_RP
- ./village-evolution-addon/village_evolution_BP:/data/behavior_packs/village_evolution_BP
- ./private-chest-addon/private_chest_BP:/data/behavior_packs/private_chest_BP
- ./private-chest-addon/private_chest_RP:/data/resource_packs/private_chest_RP
- ./smart-crafting-addon/smart_crafting_BP:/data/behavior_packs/smart_crafting_BP
- ./smart-crafting-addon/smart_crafting_RP:/data/resource_packs/smart_crafting_RP
- ./home-sign-addon/home_sign_BP:/data/behavior_packs/home_sign_BP
- ./home-sign-addon/home_sign_RP:/data/resource_packs/home_sign_RP
- ./keep-inventory-addon/keep_inventory_BP:/data/behavior_packs/keep_inventory_BP
- ./postal-service-addon/postal_service_BP:/data/behavior_packs/postal_service_BP
- ./postal-service-addon/postal_service_RP:/data/resource_packs/postal_service_RP
- ./camping-supplies-addon/camping_supplies_BP:/data/behavior_packs/camping_supplies_BP
- ./camping-supplies-addon/camping_supplies_RP:/data/resource_packs/camping_supplies_RP
- ./dynamite-addon/dynamite_BP:/data/behavior_packs/dynamite_BP
- ./dynamite-addon/dynamite_RP:/data/resource_packs/dynamite_RP
- ./tow-boat-addon/tow_boat_BP:/data/behavior_packs/tow_boat_BP
- ./tow-boat-addon/tow_boat_RP:/data/resource_packs/tow_boat_RP
- ./village-evolution-addon/enabled_packs.json:/data/config/default/enabled_packs.json
restart: unless-stopped
mem_limit: 1500m
memswap_limit: 2500m
networks:
- mc-network
@@ -99,21 +137,14 @@ services:
container_name: mc-mya
environment:
EULA: "TRUE"
SERVER_NAME: "Mya's World"
GAMEMODE: survival
DIFFICULTY: normal
ALLOW_CHEATS: "true"
ONLINE_MODE: "false"
SERVER_PORT: "19132"
LEVEL_NAME: "Mya World"
MAX_PLAYERS: "10"
DEFAULT_PLAYER_PERMISSION_LEVEL: operator
OP_PERMISSION_LEVEL: "4"
ports:
- "${MYA_PORT:-19135}:19132/udp"
volumes:
- mya-data:/data
- ./hub-return-addon/hub_return_transfer_BP:/data/behavior_packs/hub_return_transfer_BP
- ./hub-return-addon/hub_return_transfer_RP:/data/resource_packs/hub_return_transfer_RP
- ./addon/spark_pet_BP:/data/behavior_packs/spark_pet_BP
- ./addon/spark_pet_RP:/data/resource_packs/spark_pet_RP
- ./addon/heyhe_pet_BP:/data/behavior_packs/heyhe_pet_BP
@@ -121,8 +152,31 @@ services:
- ./addon/anthrax_cat_BP:/data/behavior_packs/anthrax_cat_BP
- ./addon/anthrax_cat_RP:/data/resource_packs/anthrax_cat_RP
- ./village-evolution-addon/village_evolution_BP:/data/behavior_packs/village_evolution_BP
- ./private-chest-addon/private_chest_BP:/data/behavior_packs/private_chest_BP
- ./private-chest-addon/private_chest_RP:/data/resource_packs/private_chest_RP
- ./smart-crafting-addon/smart_crafting_BP:/data/behavior_packs/smart_crafting_BP
- ./smart-crafting-addon/smart_crafting_RP:/data/resource_packs/smart_crafting_RP
- ./home-sign-addon/home_sign_BP:/data/behavior_packs/home_sign_BP
- ./home-sign-addon/home_sign_RP:/data/resource_packs/home_sign_RP
- ./keep-inventory-addon/keep_inventory_BP:/data/behavior_packs/keep_inventory_BP
- ./postal-service-addon/postal_service_BP:/data/behavior_packs/postal_service_BP
- ./postal-service-addon/postal_service_RP:/data/resource_packs/postal_service_RP
- ./camping-supplies-addon/camping_supplies_BP:/data/behavior_packs/camping_supplies_BP
- ./camping-supplies-addon/camping_supplies_RP:/data/resource_packs/camping_supplies_RP
- ./dynamite-addon/dynamite_BP:/data/behavior_packs/dynamite_BP
- ./dynamite-addon/dynamite_RP:/data/resource_packs/dynamite_RP
- ./tow-boat-addon/tow_boat_BP:/data/behavior_packs/tow_boat_BP
- ./tow-boat-addon/tow_boat_RP:/data/resource_packs/tow_boat_RP
- ./naturalist-lite-addon/naturalist_lite_BP:/data/behavior_packs/naturalist_lite_BP
- ./naturalist-lite-addon/naturalist_lite_RP:/data/resource_packs/naturalist_lite_RP
- ./village-evolution-addon/enabled_packs.json:/data/config/default/enabled_packs.json
- ./trees-features-addon/trees_features_BP:/data/behavior_packs/trees_features_BP
- ./trees-features-addon/trees_features_RP:/data/resource_packs/trees_features_RP
- ./hemp-addon/hemp_BP:/data/behavior_packs/hemp_BP
- ./hemp-addon/hemp_RP:/data/resource_packs/hemp_RP
restart: unless-stopped
mem_limit: 1500m
memswap_limit: 2500m
networks:
- mc-network
@@ -0,0 +1,41 @@
{
"format_version": "1.21.0",
"minecraft:entity": {
"description": {
"identifier": "silverlabs:thrown_banger",
"is_spawnable": false,
"is_summonable": true,
"is_experimental": false
},
"components": {
"minecraft:collision_box": {
"width": 0.25,
"height": 0.25
},
"minecraft:physics": {},
"minecraft:pushable": {
"is_pushable": false,
"is_pushable_by_piston": false
},
"minecraft:projectile": {
"on_hit": {
"impact_damage": {
"damage": 2,
"knockback": true,
"destroy_on_hit": true,
"semi_random_diff_damage": false
},
"remove_on_hit": {}
},
"power": 1.4,
"gravity": 0.05,
"inertia": 0.99,
"liquid_inertia": 0.6,
"anchor": 1,
"offset": [0, -0.1, 0],
"should_bounce": false,
"hit_sound": "random.fuse"
}
}
}
}
@@ -0,0 +1,66 @@
{
"format_version": "1.21.0",
"minecraft:entity": {
"description": {
"identifier": "silverlabs:thrown_bundle",
"is_spawnable": false,
"is_summonable": true,
"is_experimental": false
},
"component_groups": {
"silverlabs:detonate": {
"minecraft:explode": {
"fuse_length": 0,
"fuse_lit": true,
"power": 3.0,
"causes_fire": false,
"breaks_blocks": true,
"max_resistance": 12,
"destroy_affected_by_griefing": true
}
}
},
"components": {
"minecraft:collision_box": {
"width": 0.3,
"height": 0.3
},
"minecraft:physics": {},
"minecraft:pushable": {
"is_pushable": false,
"is_pushable_by_piston": false
},
"minecraft:projectile": {
"on_hit": {
"impact_damage": {
"damage": 6,
"knockback": true,
"destroy_on_hit": false,
"semi_random_diff_damage": false
},
"definition_event": {
"event_trigger": {
"event": "silverlabs:detonate",
"target": "self"
}
}
},
"power": 1.3,
"gravity": 0.06,
"inertia": 0.99,
"liquid_inertia": 0.6,
"anchor": 1,
"offset": [0, -0.1, 0],
"should_bounce": false,
"hit_sound": "random.fuse"
}
},
"events": {
"silverlabs:detonate": {
"add": {
"component_groups": ["silverlabs:detonate"]
}
}
}
}
}
@@ -0,0 +1,66 @@
{
"format_version": "1.21.0",
"minecraft:entity": {
"description": {
"identifier": "silverlabs:thrown_dynamite",
"is_spawnable": false,
"is_summonable": true,
"is_experimental": false
},
"component_groups": {
"silverlabs:detonate": {
"minecraft:explode": {
"fuse_length": 0,
"fuse_lit": true,
"power": 1.5,
"causes_fire": false,
"breaks_blocks": true,
"max_resistance": 4,
"destroy_affected_by_griefing": true
}
}
},
"components": {
"minecraft:collision_box": {
"width": 0.25,
"height": 0.25
},
"minecraft:physics": {},
"minecraft:pushable": {
"is_pushable": false,
"is_pushable_by_piston": false
},
"minecraft:projectile": {
"on_hit": {
"impact_damage": {
"damage": 4,
"knockback": true,
"destroy_on_hit": false,
"semi_random_diff_damage": false
},
"definition_event": {
"event_trigger": {
"event": "silverlabs:detonate",
"target": "self"
}
}
},
"power": 1.4,
"gravity": 0.05,
"inertia": 0.99,
"liquid_inertia": 0.6,
"anchor": 1,
"offset": [0, -0.1, 0],
"should_bounce": false,
"hit_sound": "random.fuse"
}
},
"events": {
"silverlabs:detonate": {
"add": {
"component_groups": ["silverlabs:detonate"]
}
}
}
}
}
@@ -0,0 +1,27 @@
{
"format_version": "1.21.0",
"minecraft:item": {
"description": {
"identifier": "silverlabs:banger",
"menu_category": {
"category": "equipment"
}
},
"components": {
"minecraft:icon": "banger",
"minecraft:display_name": {
"value": "Banger"
},
"minecraft:max_stack_size": 16,
"minecraft:hand_equipped": true,
"minecraft:throwable": {
"do_swing_animation": true,
"max_draw_duration": 0,
"scale_power_by_draw_duration": false
},
"minecraft:projectile": {
"projectile_entity": "silverlabs:thrown_banger"
}
}
}
}
@@ -0,0 +1,27 @@
{
"format_version": "1.21.0",
"minecraft:item": {
"description": {
"identifier": "silverlabs:dynamite_bundle",
"menu_category": {
"category": "equipment"
}
},
"components": {
"minecraft:icon": "dynamite_bundle",
"minecraft:display_name": {
"value": "Bundle of Dynamite"
},
"minecraft:max_stack_size": 16,
"minecraft:hand_equipped": true,
"minecraft:throwable": {
"do_swing_animation": true,
"max_draw_duration": 0,
"scale_power_by_draw_duration": false
},
"minecraft:projectile": {
"projectile_entity": "silverlabs:thrown_bundle"
}
}
}
}
@@ -0,0 +1,27 @@
{
"format_version": "1.21.0",
"minecraft:item": {
"description": {
"identifier": "silverlabs:dynamite_stick",
"menu_category": {
"category": "equipment"
}
},
"components": {
"minecraft:icon": "dynamite_stick",
"minecraft:display_name": {
"value": "Stick of Dynamite"
},
"minecraft:max_stack_size": 16,
"minecraft:hand_equipped": true,
"minecraft:throwable": {
"do_swing_animation": true,
"max_draw_duration": 0,
"scale_power_by_draw_duration": false
},
"minecraft:projectile": {
"projectile_entity": "silverlabs:thrown_dynamite"
}
}
}
}
+39
View File
@@ -0,0 +1,39 @@
{
"format_version": 2,
"header": {
"name": "Dynamite",
"description": "Throwable explosives: bangers, dynamite sticks, and bundles.",
"uuid": "fac83943-16bc-4790-aa05-631894f59a03",
"version": [
1,
0,
0
],
"min_engine_version": [
1,
21,
0
]
},
"modules": [
{
"type": "data",
"uuid": "1354002c-fdd5-4f7e-b89b-f5dd2c38799c",
"version": [
1,
0,
0
]
}
],
"dependencies": [
{
"uuid": "a18bdde1-53f8-49aa-b06d-6f0ec6c45b46",
"version": [
1,
0,
2
]
}
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 534 B

@@ -0,0 +1,25 @@
{
"format_version": "1.21.0",
"minecraft:recipe_shaped": {
"description": {
"identifier": "silverlabs:banger_recipe"
},
"tags": ["crafting_table"],
"unlock": [
{ "item": "minecraft:gunpowder" }
],
"pattern": [
"SP",
"G "
],
"key": {
"S": { "item": "minecraft:string" },
"P": { "item": "minecraft:paper" },
"G": { "item": "minecraft:gunpowder" }
},
"result": {
"item": "silverlabs:banger",
"count": 4
}
}
}
@@ -0,0 +1,24 @@
{
"format_version": "1.21.0",
"minecraft:recipe_shaped": {
"description": {
"identifier": "silverlabs:dynamite_bundle_recipe"
},
"tags": ["crafting_table"],
"unlock": [
{ "item": "silverlabs:dynamite_stick" }
],
"pattern": [
"DDD",
" S "
],
"key": {
"D": { "item": "silverlabs:dynamite_stick" },
"S": { "item": "minecraft:string" }
},
"result": {
"item": "silverlabs:dynamite_bundle",
"count": 1
}
}
}
@@ -0,0 +1,25 @@
{
"format_version": "1.21.0",
"minecraft:recipe_shaped": {
"description": {
"identifier": "silverlabs:dynamite_stick_recipe"
},
"tags": ["crafting_table"],
"unlock": [
{ "item": "silverlabs:banger" }
],
"pattern": [
"BB",
"RT"
],
"key": {
"B": { "item": "silverlabs:banger" },
"R": { "item": "minecraft:redstone" },
"T": { "item": "minecraft:stick" }
},
"result": {
"item": "silverlabs:dynamite_stick",
"count": 2
}
}
}
@@ -0,0 +1,12 @@
{
"format_version": "1.10.0",
"minecraft:client_entity": {
"description": {
"identifier": "silverlabs:thrown_banger",
"materials": { "default": "entity_alphatest" },
"textures": { "default": "textures/entity/thrown_banger" },
"geometry": { "default": "geometry.snowball" },
"render_controllers": [ "controller.render.snowball" ]
}
}
}
@@ -0,0 +1,12 @@
{
"format_version": "1.10.0",
"minecraft:client_entity": {
"description": {
"identifier": "silverlabs:thrown_bundle",
"materials": { "default": "entity_alphatest" },
"textures": { "default": "textures/entity/thrown_bundle" },
"geometry": { "default": "geometry.snowball" },
"render_controllers": [ "controller.render.snowball" ]
}
}
}
@@ -0,0 +1,12 @@
{
"format_version": "1.10.0",
"minecraft:client_entity": {
"description": {
"identifier": "silverlabs:thrown_dynamite",
"materials": { "default": "entity_alphatest" },
"textures": { "default": "textures/entity/thrown_dynamite" },
"geometry": { "default": "geometry.snowball" },
"render_controllers": [ "controller.render.snowball" ]
}
}
}
+17
View File
@@ -0,0 +1,17 @@
{
"format_version": 2,
"header": {
"name": "Dynamite Resources",
"description": "Textures and language for the Dynamite addon.",
"uuid": "a18bdde1-53f8-49aa-b06d-6f0ec6c45b46",
"version": [1, 0, 2],
"min_engine_version": [1, 21, 0]
},
"modules": [
{
"type": "resources",
"uuid": "587281f2-f159-4ad9-85a6-d20ff4899717",
"version": [1, 0, 2]
}
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 534 B

@@ -0,0 +1,6 @@
item.silverlabs:banger=Banger
item.silverlabs:dynamite_stick=Stick of Dynamite
item.silverlabs:dynamite_bundle=Bundle of Dynamite
entity.silverlabs:thrown_banger.name=Banger
entity.silverlabs:thrown_dynamite.name=Dynamite
entity.silverlabs:thrown_bundle.name=Dynamite Bundle
@@ -0,0 +1,3 @@
[
"en_US"
]
Binary file not shown.

After

Width:  |  Height:  |  Size: 298 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 372 B

@@ -0,0 +1,15 @@
{
"resource_pack_name": "dynamite_RP",
"texture_name": "atlas.items",
"texture_data": {
"banger": {
"textures": "textures/items/banger"
},
"dynamite_stick": {
"textures": "textures/items/dynamite_stick"
},
"dynamite_bundle": {
"textures": "textures/items/dynamite_bundle"
}
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 144 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 B

@@ -240,8 +240,14 @@ function placeAllEggs(playerPos) {
// Validate at least one stored egg block actually exists
const stored = getEggPositions();
if (stored && stored.length > 0) {
// Check far-ring eggs (last 5) — they're hardest to reach and least likely
// to have been collected yet. Avoids false "blocks missing" detection when
// only the close-ring eggs (1-5) have been collected.
let found = 0;
for (let i = 0; i < Math.min(5, stored.length); i++) {
const checkAt = [45, 46, 47, 48, 49].filter(i => i < stored.length);
const fallback = checkAt.length === 0;
const indices = fallback ? [0, 1, 2, 3, 4].filter(i => i < stored.length) : checkAt;
for (const i of indices) {
try {
const block = overworld.getBlock({ x: stored[i].x, y: stored[i].y, z: stored[i].z });
if (block && block.typeId.includes("glazed_terracotta")) found++;
@@ -0,0 +1,33 @@
{
"format_version": 2,
"header": {
"name": "Family Goals",
"description": "Shared goals, family bank, loot share, pity timer, and PvP-off for the family survival world",
"uuid": "f1a2b3c4-d5e6-4789-90ab-cdef12345001",
"version": [1, 0, 6],
"min_engine_version": [1, 21, 0]
},
"modules": [
{
"type": "script",
"language": "javascript",
"uuid": "f1a2b3c4-d5e6-4789-90ab-cdef12345002",
"version": [1, 0, 6],
"entry": "scripts/main.js"
}
],
"dependencies": [
{
"module_name": "@minecraft/server",
"version": "1.17.0"
},
{
"module_name": "@minecraft/server-ui",
"version": "1.3.0"
},
{
"module_name": "@minecraft/server-admin",
"version": "1.0.0-beta"
}
]
}
@@ -0,0 +1,479 @@
// Family Goals Addon
//
// Lives in jamie's world (the shared family survival world). Provides:
// • PvP-off (siblings can't hurt each other)
// • Family roster ( !family setup adds online players )
// • Family Bank ( !family bank registers a vanilla chest as shared )
// • Five shared goals with per-player contribution tracking
// • Loot share within 32 blocks (sibling helpers get a kill bonus)
// • Pity timer (idle siblings get an encouragement bundle every 60 min)
//
// All persistent state lives in world dynamic properties so it survives
// container restarts. No resource pack — vanilla items + titleraw + actionforms.
import { world, system, ItemStack } from "@minecraft/server";
import { ActionFormData } from "@minecraft/server-ui";
// We don't actually use @minecraft/server-admin, but importing from it flips
// the beta-API gate for this pack so world.beforeEvents.chatSend resolves.
// If we ever drop this import, fall back to afterEvents.chatSend (no cancel).
import { variables } from "@minecraft/server-admin";
void variables; // silence unused warning
// ─── Tunables ──────────────────────────────────────────────────
const FAMILY_PROP = "family_roster_v1"; // { members: [<playerId>, ...] }
const GOALS_PROP = "family_goals_v1"; // see initGoals() for shape
const BANK_PROP = "family_bank_v1"; // { x, y, z, dim } | null
const PROGRESS_PROP = "family_last_progress_at"; // per-player dyn prop, Date.now()
const VANILLA_CHEST = "minecraft:chest";
const LOOT_SHARE_RADIUS = 32;
const PITY_THRESHOLD_MS = 60 * 60 * 1000; // 60 min of no progress
const PITY_TICK_INTERVAL = 6000; // poll every 5 min (20 tps)
const NIGHTS_TICK_INTERVAL = 200; // every 10s
const GOAL_DEFS = [
{ id: "logs", label: "Logs Chopped", target: 5000 },
{ id: "diamonds", label: "Diamonds Mined", target: 50 },
{ id: "mobs", label: "Monsters Defeated", target: 500 },
{ id: "crops", label: "Crops Harvested", target: 1000 },
{ id: "nights", label: "Nights Survived", target: 30 },
];
const LOG_IDS = new Set([
"minecraft:oak_log", "minecraft:spruce_log", "minecraft:birch_log",
"minecraft:jungle_log", "minecraft:acacia_log", "minecraft:dark_oak_log",
"minecraft:cherry_log", "minecraft:mangrove_log",
"minecraft:stripped_oak_log", "minecraft:stripped_spruce_log",
"minecraft:stripped_birch_log", "minecraft:stripped_jungle_log",
"minecraft:stripped_acacia_log", "minecraft:stripped_dark_oak_log",
"minecraft:stripped_cherry_log", "minecraft:stripped_mangrove_log",
]);
const DIAMOND_IDS = new Set([
"minecraft:diamond_ore", "minecraft:deepslate_diamond_ore",
]);
// Block id → "growth"-like state value at full maturity
const CROP_AGES = {
"minecraft:wheat": 7,
"minecraft:carrots": 7,
"minecraft:potatoes": 7,
"minecraft:beetroot": 3,
};
// ─── In-memory mirror of dynamic properties ────────────────────
let family = { members: [] };
let goals = null;
let bank = null;
let lastDay = -1; // tracks day-rollover for the "nights survived" goal
// ─── Persistence ───────────────────────────────────────────────
function loadState() {
try {
const f = world.getDynamicProperty(FAMILY_PROP);
if (typeof f === "string") family = JSON.parse(f);
} catch (_) { family = { members: [] }; }
try {
const g = world.getDynamicProperty(GOALS_PROP);
if (typeof g === "string") goals = JSON.parse(g);
} catch (_) { goals = null; }
if (!goals || !goals.goals) goals = initGoals();
// Backfill any newly added goal definitions on upgrade
for (const def of GOAL_DEFS) {
if (!goals.goals[def.id]) {
goals.goals[def.id] = { current: 0, target: def.target, completed: false, contributions: {} };
}
}
try {
const b = world.getDynamicProperty(BANK_PROP);
if (typeof b === "string") bank = JSON.parse(b);
} catch (_) { bank = null; }
}
function initGoals() {
const g = { goals: {} };
for (const def of GOAL_DEFS) {
g.goals[def.id] = { current: 0, target: def.target, completed: false, contributions: {} };
}
return g;
}
function saveFamily() {
try { world.setDynamicProperty(FAMILY_PROP, JSON.stringify(family)); } catch (_) {}
}
function saveGoals() {
try {
const json = JSON.stringify(goals);
world.setDynamicProperty(GOALS_PROP, json);
const verify = world.getDynamicProperty(GOALS_PROP);
console.warn(`[Family] saveGoals: wrote ${json.length} chars, readback typeof=${typeof verify} len=${typeof verify === "string" ? verify.length : "n/a"}`);
} catch (e) { console.warn(`[Family] saveGoals FAILED: ${e}`); }
}
function saveBank() {
try { world.setDynamicProperty(BANK_PROP, bank ? JSON.stringify(bank) : undefined); } catch (_) {}
}
function isFamily(player) {
if (!player || !family.members) return false;
return family.members.includes(player.id);
}
// ─── Goal mutation ─────────────────────────────────────────────
function incrGoal(goalId, player, n = 1) {
if (!isFamily(player)) return;
const g = goals.goals[goalId];
if (!g || g.completed) return;
g.current += n;
g.contributions[player.id] = (g.contributions[player.id] || 0) + n;
try { player.setDynamicProperty(PROGRESS_PROP, Date.now()); } catch (_) {}
if (g.current >= g.target) {
g.completed = true;
g.current = g.target;
fireGoalReward(goalId);
}
saveGoals();
}
function fireGoalReward(goalId) {
const def = GOAL_DEFS.find(d => d.id === goalId);
const label = def ? def.label : goalId;
for (const p of world.getAllPlayers()) {
try {
p.onScreenDisplay.setTitle(`§6✦ Family Goal Complete!`, {
subtitle: `§e${label}`,
fadeInDuration: 10,
stayDuration: 80,
fadeOutDuration: 20,
});
p.dimension.runCommand(`summon firework_rocket ${p.location.x} ${p.location.y + 2} ${p.location.z}`);
// v1 trophy: a named gold ingot. Easy to tell apart in inventory.
p.runCommand(`give @s gold_ingot 1`);
} catch (_) {}
}
world.sendMessage(`§6[Family] §eGoal complete: §f${label}§e! §7Trophy gold awarded.`);
}
// ─── Family roster command ─────────────────────────────────────
function familySetup(player) {
const online = world.getAllPlayers();
const before = new Set(family.members);
let added = 0;
for (const p of online) {
if (!before.has(p.id)) {
family.members.push(p.id);
added += 1;
}
}
saveFamily();
const names = online.map(p => p.name).join(", ");
if (added === 0) {
player.sendMessage(`§7[Family] §fAll currently-online players are already in the roster (${family.members.length} members).`);
} else {
player.sendMessage(`§a[Family] §fAdded ${added} new member(s). Roster (${family.members.length}): §e${names}§f.`);
world.sendMessage(`§a[Family] §fRoster updated by §e${player.name}§f.`);
}
}
function familyReset(player) {
family = { members: [] };
saveFamily();
player.sendMessage("§c[Family] §fRoster cleared. Run §e!family setup§f again to repopulate.");
}
// ─── Family Bank command ───────────────────────────────────────
function familyBank(player) {
if (!isFamily(player)) {
player.sendMessage("§c[Family] §7Only family members can register the bank — run §e!family setup§7 first while everyone's online.");
return;
}
let view;
try {
view = player.getBlockFromViewDirection({ maxDistance: 6 });
} catch (e) {
player.sendMessage(`§c[Family] §7Couldn't look at a block: ${e.message}`);
return;
}
const target = view?.block;
if (!target || target.typeId !== VANILLA_CHEST) {
player.sendMessage("§c[Family] §7Look at a vanilla chest within 6 blocks, then re-run §e!family bank§7.");
return;
}
bank = {
x: target.location.x,
y: target.location.y,
z: target.location.z,
dim: target.dimension.id,
};
saveBank();
player.sendMessage(`§a[Family] §fBank registered at §e${bank.x}, ${bank.y}, ${bank.z}§f. All family members can now open it.`);
}
// ─── Bank gating (interact + break) ────────────────────────────
function isBankAt(loc, dimId) {
if (!bank) return false;
return Math.floor(loc.x) === bank.x
&& Math.floor(loc.y) === bank.y
&& Math.floor(loc.z) === bank.z
&& bank.dim === dimId;
}
try {
world.beforeEvents.playerInteractWithBlock.subscribe((event) => {
const block = event.block;
if (block.typeId !== VANILLA_CHEST) return;
if (!isBankAt(block.location, block.dimension.id)) return;
if (isFamily(event.player)) return;
event.cancel = true;
const playerRef = event.player;
system.run(() => playerRef.sendMessage("§c[Family Bank] §7Only family members can open this chest."));
});
} catch (e) {
console.warn(`[Family] beforeEvents.playerInteractWithBlock unavailable: ${e}`);
}
try {
world.beforeEvents.playerBreakBlock.subscribe((event) => {
const block = event.block;
if (block.typeId !== VANILLA_CHEST) return;
if (!isBankAt(block.location, block.dimension.id)) return;
if (isFamily(event.player)) {
// Family member breaking the bank → un-register so it stops being protected
bank = null;
saveBank();
const p = event.player;
system.run(() => p.sendMessage("§7[Family Bank] §fBank chest broken — un-registered. Place a new chest and run §e!family bank§f to re-register."));
return;
}
event.cancel = true;
const playerRef = event.player;
system.run(() => playerRef.sendMessage("§c[Family Bank] §7Only family members can break this chest."));
});
} catch (e) {
console.warn(`[Family] beforeEvents.playerBreakBlock unavailable: ${e}`);
}
// ─── PvP off (heal damage between siblings) ────────────────────
// Bedrock has no server.properties pvp toggle — we hook the after-event
// and immediately re-heal. Net visible effect: zero damage between players.
world.afterEvents.entityHurt.subscribe((event) => {
const source = event.damageSource?.damagingEntity;
const victim = event.hurtEntity;
if (!source || !victim) return;
if (source.typeId !== "minecraft:player" || victim.typeId !== "minecraft:player") return;
try {
const health = victim.getComponent("minecraft:health");
if (!health) return;
const restored = Math.min(health.effectiveMax, health.currentValue + event.damage);
health.setCurrentValue(restored);
} catch (_) {}
});
// ─── Goal triggers ─────────────────────────────────────────────
world.afterEvents.playerBreakBlock.subscribe((event) => {
const player = event.player;
if (!isFamily(player)) return;
const id = event.brokenBlockPermutation?.type?.id;
if (!id) return;
if (LOG_IDS.has(id)) {
incrGoal("logs", player);
return;
}
if (DIAMOND_IDS.has(id)) {
incrGoal("diamonds", player);
return;
}
if (id in CROP_AGES) {
// Only fully grown crops count
let states = null;
try { states = event.brokenBlockPermutation.getAllStates(); } catch (_) {}
const age = states && (states["growth"] ?? states["age"]);
if (typeof age === "number" && age >= CROP_AGES[id]) {
incrGoal("crops", player);
}
}
});
world.afterEvents.entityDie.subscribe((event) => {
const dead = event.deadEntity;
if (!dead || dead.typeId === "minecraft:player") return;
const source = event.damageSource?.damagingEntity;
if (!source || source.typeId !== "minecraft:player") return;
if (!isFamily(source)) return;
// Goal: monsters only
let isMonster = false;
try {
const fam = dead.getComponent("minecraft:type_family");
if (fam && typeof fam.hasTypeFamily === "function" && fam.hasTypeFamily("monster")) {
isMonster = true;
}
} catch (_) {}
if (isMonster) incrGoal("mobs", source);
// Loot share: every family member in 32 blocks (other than killer) gets cooked beef.
// Sharing helpers, not real drop mirroring — keeps the drop pipeline untouched.
try {
const k = source.location;
const r2 = LOOT_SHARE_RADIUS * LOOT_SHARE_RADIUS;
for (const other of world.getAllPlayers()) {
if (other.id === source.id) continue;
if (!isFamily(other)) continue;
if (other.dimension.id !== source.dimension.id) continue;
const dx = other.location.x - k.x;
const dy = other.location.y - k.y;
const dz = other.location.z - k.z;
if (dx*dx + dy*dy + dz*dz <= r2) {
other.runCommand("give @s cooked_beef 2");
}
}
} catch (_) {}
});
// ─── Nights survived ───────────────────────────────────────────
system.runInterval(() => {
let day;
try { day = world.getDay(); } catch (_) { return; }
if (typeof day !== "number") return;
if (lastDay === -1) { lastDay = day; return; }
if (day > lastDay) {
const diff = day - lastDay;
lastDay = day;
for (const p of world.getAllPlayers()) {
if (isFamily(p)) incrGoal("nights", p, diff);
}
}
}, NIGHTS_TICK_INTERVAL);
// ─── Pity timer ────────────────────────────────────────────────
system.runInterval(() => {
const now = Date.now();
for (const p of world.getAllPlayers()) {
if (!isFamily(p)) continue;
let last;
try { last = p.getDynamicProperty(PROGRESS_PROP); } catch (_) { last = undefined; }
if (typeof last !== "number") {
// First time we've seen this family member contribute (or first session) — initialize, don't pity yet
try { p.setDynamicProperty(PROGRESS_PROP, now); } catch (_) {}
continue;
}
if (now - last < PITY_THRESHOLD_MS) continue;
// Drop encouragement bundle directly into inventory
try {
p.runCommand("give @s diamond 1");
p.runCommand("give @s cooked_beef 16");
p.runCommand("give @s iron_pickaxe 1");
p.sendMessage("§6[Family] §fA little boost to keep you going! §7(family pity bundle)");
p.setDynamicProperty(PROGRESS_PROP, now); // reset so we don't spam every 5 min
} catch (_) {}
}
}, PITY_TICK_INTERVAL);
// ─── Goal display ──────────────────────────────────────────────
function makeBar(pct) {
const filled = Math.max(0, Math.min(10, Math.floor(pct / 10)));
return "§a" + "▰".repeat(filled) + "§8" + "▰".repeat(10 - filled);
}
async function showGoals(player, perPlayerMode) {
const lines = [];
for (const def of GOAL_DEFS) {
const g = goals.goals[def.id];
if (!g) continue;
const cur = Math.min(g.current, g.target);
const pct = Math.floor((cur / g.target) * 100);
const bar = makeBar(pct);
const status = g.completed ? " §a✔" : "";
if (perPlayerMode) {
const my = g.contributions[player.id] || 0;
const myPct = cur > 0 ? Math.floor((my / cur) * 100) : 0;
lines.push(`§e${def.label}${status}\n§7 ${bar}§7 §f${cur}§7/§f${g.target}\n§7 You: §f${my}§7 (${myPct}% of progress)`);
} else {
lines.push(`§e${def.label}${status}\n§7 ${bar}§7 §f${cur}§7/§f${g.target}`);
}
}
if (!lines.length) lines.push("§7No goals configured.");
const form = new ActionFormData()
.title(perPlayerMode ? "My Goal Contributions" : "Family Goals")
.body(lines.join("\n\n"))
.button("§7OK");
try { await form.show(player); } catch (_) {}
}
// ─── Family menu (entered via scriptevent from hub-return compass) ─────
function showRoster(player) {
if (family.members.length === 0) {
player.sendMessage("§7[Family] §fNo roster set yet — open §e🏆 Family§f from your compass and pick §eAdd me to the family§f.");
return;
}
const names = world.getAllPlayers()
.filter(p => family.members.includes(p.id))
.map(p => p.name);
player.sendMessage(`§a[Family] §fRoster: §e${family.members.length}§f members. Online: §e${names.join(", ") || "(none)"}§f.`);
}
async function openFamilyMenu(player) {
const inFamily = isFamily(player);
const form = new ActionFormData()
.title("🏆 Family")
.body(`§7Roster: §f${family.members.length}§7 members${inFamily ? " §a(you're in)" : " §c(you're not)"}\n§7Bank: ${bank ? `§f${bank.x}, ${bank.y}, ${bank.z}` : "§cnot set"}`);
const actions = [];
form.button("🎯 Family Goals"); actions.push("goals");
form.button("📝 My Contributions"); actions.push("mygoals");
form.button("👥 Show Roster"); actions.push("roster");
form.button(" Add me / Add online players"); actions.push("setup");
form.button("🏦 Register Family Bank (look at chest)"); actions.push("bank");
form.button("♻ Reset Roster (danger)"); actions.push("reset");
form.button("§7Cancel"); actions.push("cancel");
let res;
try { res = await form.show(player); } catch (_) { return; }
if (res.canceled || res.selection === undefined) return;
const a = actions[res.selection];
if (a === "goals") return showGoals(player, false);
if (a === "mygoals") return showGoals(player, true);
if (a === "roster") return showRoster(player);
if (a === "setup") return familySetup(player);
if (a === "bank") return familyBank(player);
if (a === "reset") return familyReset(player);
}
// scriptEventReceive is the cross-pack dispatch surface. Hub-return's compass
// menu fires `scriptevent family:menu` to enter our UI. Direct ids are also
// handled so an opped player can run e.g. `/scriptevent family:setup`.
try {
system.afterEvents.scriptEventReceive.subscribe((event) => {
const id = event.id;
if (!id || !id.startsWith("family:")) return;
const player = event.sourceEntity;
if (!player || player.typeId !== "minecraft:player") return;
switch (id) {
case "family:menu": system.run(() => openFamilyMenu(player)); break;
case "family:goals": system.run(() => showGoals(player, false)); break;
case "family:mygoals": system.run(() => showGoals(player, true)); break;
case "family:roster": showRoster(player); break;
case "family:setup": familySetup(player); break;
case "family:bank": familyBank(player); break;
case "family:reset": familyReset(player); break;
}
});
} catch (e) {
console.warn(`[Family] scriptEventReceive unavailable: ${e}`);
}
// ─── Boot ──────────────────────────────────────────────────────
system.run(() => {
loadState();
saveGoals(); // persist any backfilled goal defs from upgrade
world.sendMessage("§6[Family] §fLoaded. Right-click your §dHub Compass§f to find the §6🏆 Family§f menu.");
});
+74
View File
@@ -0,0 +1,74 @@
{
"format_version": "1.21.0",
"minecraft:block": {
"description": {
"identifier": "silverlabs:hemp_crop",
"menu_category": { "category": "nature" },
"states": {
"silverlabs:hemp_age": { "values": { "min": 0, "max": 5 } },
"silverlabs:hemp_top": { "values": [false, true] }
}
},
"components": {
"minecraft:destructible_by_mining": { "seconds_to_destroy": 0.1 },
"minecraft:destructible_by_explosion": { "explosion_resistance": 0.1 },
"minecraft:flammable": { "destroy_chance_modifier": 60, "catch_chance_modifier": 60 },
"minecraft:light_dampening": 0,
"minecraft:map_color": "#5d8a3c",
"minecraft:collision_box": false,
"minecraft:selection_box": { "origin": [-6, 0, -6], "size": [12, 14, 12] },
"minecraft:geometry": "minecraft:geometry.cross",
"minecraft:material_instances": {
"*": {
"texture": "hemp_crop_0",
"render_method": "alpha_test",
"ambient_occlusion": false,
"face_dimming": false
}
},
"minecraft:loot": "loot_tables/blocks/hemp_crop.json"
},
"permutations": [
{
"condition": "query.block_state('silverlabs:hemp_age') == 1",
"components": {
"minecraft:material_instances": {
"*": { "texture": "hemp_crop_1", "render_method": "alpha_test", "ambient_occlusion": false, "face_dimming": false }
}
}
},
{
"condition": "query.block_state('silverlabs:hemp_age') == 2",
"components": {
"minecraft:material_instances": {
"*": { "texture": "hemp_crop_2", "render_method": "alpha_test", "ambient_occlusion": false, "face_dimming": false }
}
}
},
{
"condition": "query.block_state('silverlabs:hemp_age') == 3",
"components": {
"minecraft:material_instances": {
"*": { "texture": "hemp_crop_3", "render_method": "alpha_test", "ambient_occlusion": false, "face_dimming": false }
}
}
},
{
"condition": "query.block_state('silverlabs:hemp_age') == 4",
"components": {
"minecraft:material_instances": {
"*": { "texture": "hemp_crop_4", "render_method": "alpha_test", "ambient_occlusion": false, "face_dimming": false }
}
}
},
{
"condition": "query.block_state('silverlabs:hemp_age') == 5",
"components": {
"minecraft:material_instances": {
"*": { "texture": "hemp_crop_5", "render_method": "alpha_test", "ambient_occlusion": false, "face_dimming": false }
}
}
}
]
}
}
+50
View File
@@ -0,0 +1,50 @@
{
"format_version": "1.21.0",
"minecraft:block": {
"description": {
"identifier": "silverlabs:sun_lamp",
"menu_category": {
"category": "items"
},
"states": {
"silverlabs:powered": [
false,
true
]
}
},
"components": {
"minecraft:destructible_by_mining": {
"seconds_to_destroy": 0.3
},
"minecraft:destructible_by_explosion": {
"explosion_resistance": 0.5
},
"minecraft:map_color": "#ffd24d",
"minecraft:light_emission": 0,
"minecraft:geometry": "minecraft:geometry.full_block",
"minecraft:material_instances": {
"*": {
"texture": "sun_lamp_off",
"render_method": "opaque"
}
}
},
"permutations": [
{
"condition": "query.block_state('silverlabs:powered') == true",
"components": {
"minecraft:light_emission": 15,
"minecraft:light_dampening": 0,
"minecraft:geometry": "minecraft:geometry.full_block",
"minecraft:material_instances": {
"*": {
"texture": "sun_lamp",
"render_method": "opaque"
}
}
}
}
]
}
}
@@ -0,0 +1,35 @@
{
"format_version": "1.21.0",
"minecraft:feature_rules": {
"description": {
"identifier": "silverlabs:hemp_patch_rule",
"places_feature": "silverlabs:hemp_patch_feature"
},
"conditions": {
"placement_pass": "surface_pass",
"minecraft:biome_filter": [
{
"any_of": [
{ "test": "has_biome_tag", "value": "plains" },
{ "test": "has_biome_tag", "value": "forest" },
{ "test": "has_biome_tag", "value": "birch" },
{ "test": "has_biome_tag", "value": "flower_forest" }
]
},
{ "test": "has_biome_tag", "operator": "!=", "value": "monster" }
]
},
"distribution": {
"iterations": 1,
"scatter_chance": 14,
"x": { "distribution": "uniform", "extent": [0, 16] },
"y": {
"distribution": "fixed_grid",
"extent": [0, 64],
"grid_offset": 0,
"step_size": 1
},
"z": { "distribution": "uniform", "extent": [0, 16] }
}
}
}
@@ -0,0 +1,12 @@
{
"format_version": "1.21.0",
"minecraft:scatter_feature": {
"description": { "identifier": "silverlabs:hemp_patch_feature" },
"iterations": 3,
"scatter_chance": 65,
"x": { "distribution": "uniform", "extent": [-3, 4] },
"y": 0,
"z": { "distribution": "uniform", "extent": [-3, 4] },
"places_feature": "silverlabs:hemp_single_feature"
}
}
@@ -0,0 +1,27 @@
{
"format_version": "1.21.0",
"minecraft:single_block_feature": {
"description": { "identifier": "silverlabs:hemp_single_feature" },
"places_block": {
"name": "silverlabs:hemp_crop",
"states": {
"silverlabs:hemp_age": 4,
"silverlabs:hemp_top": false
}
},
"enforce_survivability_rules": true,
"enforce_placement_rules": true,
"may_attach_to": {
"min_sides_must_attach": 1,
"top": [
"minecraft:grass_block",
"minecraft:dirt",
"minecraft:podzol",
"minecraft:coarse_dirt"
]
},
"may_replace": [
"minecraft:air"
]
}
}
@@ -0,0 +1,25 @@
{
"format_version": "1.21.0",
"minecraft:item": {
"description": {
"identifier": "silverlabs:hemp_brownie",
"menu_category": {
"category": "nature"
}
},
"components": {
"minecraft:icon": "hemp_brownie",
"minecraft:max_stack_size": 16,
"minecraft:use_animation": "eat",
"minecraft:use_modifiers": {
"use_duration": 1.6,
"movement_modifier": 0.35
},
"minecraft:food": {
"nutrition": 4,
"saturation_modifier": 0.6,
"can_always_eat": false
}
}
}
}
+15
View File
@@ -0,0 +1,15 @@
{
"format_version": "1.21.0",
"minecraft:item": {
"description": {
"identifier": "silverlabs:hemp_bud",
"menu_category": {
"category": "nature"
}
},
"components": {
"minecraft:icon": "hemp_bud",
"minecraft:max_stack_size": 64
}
}
}
+29
View File
@@ -0,0 +1,29 @@
{
"format_version": "1.21.0",
"minecraft:item": {
"description": {
"identifier": "silverlabs:hemp_seeds",
"menu_category": {
"category": "nature",
"group": "itemGroup.name.seed"
}
},
"components": {
"minecraft:icon": "hemp_seeds",
"minecraft:max_stack_size": 64,
"minecraft:block_placer": {
"block": "silverlabs:hemp_crop",
"use_on": [
"minecraft:dirt",
"minecraft:grass_block",
"minecraft:farmland",
"minecraft:podzol",
"minecraft:coarse_dirt",
"minecraft:rooted_dirt",
"minecraft:moss_block",
"minecraft:flower_pot"
]
}
}
}
}
@@ -0,0 +1,26 @@
{
"format_version": "1.21.0",
"minecraft:item": {
"description": {
"identifier": "silverlabs:hemp_tincture",
"menu_category": {
"category": "nature"
}
},
"components": {
"minecraft:icon": "hemp_tincture",
"minecraft:max_stack_size": 16,
"minecraft:use_animation": "drink",
"minecraft:use_modifiers": {
"use_duration": 1.5,
"movement_modifier": 0.35
},
"minecraft:food": {
"nutrition": 1,
"saturation_modifier": 0.1,
"can_always_eat": true,
"using_converts_to": "minecraft:glass_bottle"
}
}
}
}
@@ -0,0 +1,17 @@
{
"pools": [
{
"rolls": 1,
"entries": [
{
"type": "item",
"name": "silverlabs:hemp_seeds",
"weight": 1,
"functions": [
{ "function": "set_count", "count": { "min": 0, "max": 1 } }
]
}
]
}
]
}
+54
View File
@@ -0,0 +1,54 @@
{
"format_version": 2,
"header": {
"name": "Hemp Plant",
"description": "Plantable hemp crop with sheers harvesting, cauldron tincture, brownie food, sun-lamp block",
"uuid": "910fafaf-bcb0-4f1a-8a05-bd235a537c3b",
"version": [
1,
0,
0
],
"min_engine_version": [
1,
21,
0
]
},
"modules": [
{
"type": "data",
"uuid": "a10b077f-703b-46b6-b45e-71ee2044c07c",
"version": [
1,
0,
0
]
},
{
"type": "script",
"language": "javascript",
"uuid": "6e6ef947-60fc-49d2-97c1-b3cab6661ece",
"version": [
1,
0,
0
],
"entry": "scripts/main.js"
}
],
"dependencies": [
{
"module_name": "@minecraft/server",
"version": "1.17.0"
},
{
"uuid": "8e5c46ac-3b16-4f51-89f7-673bd06600f4",
"version": [
1,
0,
5
]
}
]
}
@@ -0,0 +1,24 @@
{
"format_version": "1.21.0",
"minecraft:recipe_shapeless": {
"description": {
"identifier": "silverlabs:hemp_brownie_recipe"
},
"tags": ["crafting_table"],
"unlock": [
{ "item": "silverlabs:hemp_bud" },
{ "item": "minecraft:cocoa_beans" }
],
"ingredients": [
{ "item": "silverlabs:hemp_bud" },
{ "item": "silverlabs:hemp_bud" },
{ "item": "minecraft:cocoa_beans" },
{ "item": "minecraft:wheat" },
{ "item": "minecraft:sugar" }
],
"result": {
"item": "silverlabs:hemp_brownie",
"count": 2
}
}
}

Some files were not shown because too many files have changed in this diff Show More