diff --git a/ALIEN_VILLAGERS_DESIGN.md b/ALIEN_VILLAGERS_DESIGN.md new file mode 100644 index 0000000..335496f --- /dev/null +++ b/ALIEN_VILLAGERS_DESIGN.md @@ -0,0 +1,321 @@ +# Alien Villagers & Structures — Design Document + +Status: **Design / pre-implementation**. Anchor planet: **Greenxertz** (template out to Cindara, Glacira afterward). +Target: Minecraft 26.1.2 · NeoForge · Java 25 · mod id `nerospace` · package root `za.co.neroland.nerospace`. + +This document is the blueprint for the feature. It is grounded in the existing codebase (4 dimensions, 7 biomes, 10 entities, 46 blocks, 106 items, datagen + Blockbench/texture pipeline) and is written so each system maps to concrete files and a build order. + +--- + +## 1. Design pillars + +1. **Earn your welcome.** Villagers start wary. The player builds a relationship (gifts, quests, defense) and is rewarded with trades, teaching rights, and access to the village's deeper structures. +2. **A village you grow.** The player teaches buildings; the village constructs them over real time and they become *functional* — passive engines that produce trade goods, unlock professions, defend, and research blueprints. +3. **Worlds worth exploring.** Generation spans tiny outposts to sprawling derelict megastructures: living mega-cities, ancient ruins/dungeons, lore/relic sites, and underground complexes — procedural but rule-bound for coherence. +4. **Trades that matter everywhere.** Goods advance nerospace tech *and* are useful in any other mod (universal materials, exclusive gear, rare/automation drops). +5. **A species for every place.** Villagers look distinct per planet and per biome via a layered + procedural-seed appearance system — recognizable families, near-infinite individuals. + +The loop: *discover a settlement → earn trust → unlock trades → teach buildings → village grows into an engine → deeper/larger structures open → repeat on the next planet.* + +--- + +## 2. Alien species & appearance system + +### 2.1 Approach — layered render + procedural seed + +One entity type, `alien_villager`, with a data-driven **render-layer stack**. This is the procedural-skin idea you liked, made art-directable and debuggable by constraining randomness to palettes and a curated parts library rather than raw pixels. + +Render layers (bottom to top): + +1. **Base body** — the species silhouette for the planet (see 2.2). Greenbench Blockbench model under `art/blockbench/entity/alien_villager_greenxertz.bbmodel`, synced to Java via `tools/model_sync.py` exactly like the existing `GreenxertzMobModel` family. +2. **Skin palette layer** — a grayscale base texture tinted at render time from a **per-individual color seed** that is *clamped to the planet+biome palette range*. Greenxertz = green/steel; Cindara = ember/red; Glacira = pale/frost. Within range, hue/saturation/value jitter per individual. +3. **Biome accessory layer** — clothing/markings chosen from a curated set keyed to biome (e.g. Greenxertz base vs `terraformed_meadow` vs `terraformed_savanna`). Gives "every biome looks different" without new entity types. +4. **Profession overlay** — tool belt / robe / insignia for the villager's job (Section 6). +5. **Status overlay** — subtle glow eyes (reuse `GlowEyesLayer`) and a reputation/mood tint so the player can read disposition at a glance. + +Why not raw runtime-generated textures: they're impossible to art-direct, hard to cache, and a debugging nightmare. The seed-clamped palette approach gives the *feel* of procedural skins with deterministic, reviewable output. The seed is stored on the entity so a given villager always looks the same. + +### 2.2 Per-planet species families + +| Planet | Biome(s) | Silhouette concept | Palette (per CLAUDE.md families) | +| --- | --- | --- | --- | +| **Greenxertz** | `greenxertz`, `terraformed_meadow` | Tall, slender, crystalline-quartz growths on shoulders; calm | Green / steel (nerosteel + xertz quartz) | +| **Cindara** | `cindara`, `terraformed_savanna` | Stocky, ember-veined skin, ash-cloaked; heat-hardened | Red / volcanic (cindrite) | +| **Glacira** | `glacira`, `terraformed_tundra` | Wrapped in layered furs, frost-rimed; slow, deliberate | Pale blue / white (glacite) | + +Disposition by species (your "earn trust" choice, applied uniformly for v1): all start **wary-neutral**, not hostile. Cindara's can be tuned more skittish/proud later if you want per-species personality, but v1 keeps one disposition model to avoid tripling the work. + +### 2.3 Data model + +A `Variant` record stored via a **data component** (`ModDataComponents`, the 26.1 component system you already use) and synced to clients: + +```text +AlienVillagerVariant( + planet: enum GREENXERTZ | CINDARA | GLACIRA, + biomeTag: ResourceKey, // selects accessory set + colorSeed: long, // clamped palette jitter + profession:Profession, + reputationTier: int // 0..5, drives mood overlay +) +``` + +Determined on spawn from the biome the villager is placed in; persisted in NBT. + +--- + +## 3. Trust & reputation progression + +A per-player, per-village reputation score (0–100) mapped to **6 tiers** (0 Stranger → 5 Kin). Stored on a village-controller block entity (Section 4.1), keyed by player UUID. + +How reputation rises: + +- **Gifts** — give a villager an item it values (palette-appropriate materials, food). Diminishing returns per day. +- **Quests** — villagers post tasks (Section 7.3): gather X, escort, clear a nearby ruin, defend during a raid. Largest reputation source. +- **Defense** — killing hostile mobs near the village during attacks; healing/curing afflicted villagers. +- **Trade volume** — every completed trade nudges reputation up slightly (vanilla-like). + +What tiers unlock: + +- T0 Stranger: villagers flee/avoid; no trades. +- T1 Acquainted: basic trades open; can accept your first quest. +- T2 Trusted: can be taught **Tier-1 buildings** (blueprints accepted); mid trades. +- T3 Allied: **Tier-2 buildings**; profession specialists appear; rare trades. +- T4 Honored: **Tier-3 buildings**; access to the village's restricted structures (vaults, deep complexes). +- T5 Kin: best trades, exclusive gear, and the village will auto-expand toward a **mega-city** end state. + +> **Logging/compliance note:** reputation is keyed by Minecraft player UUID only. Do **not** send player names, IPs, or interaction logs to the Sentry/telemetry pipeline. Keep any aggregate metrics anonymized and opt-in to stay POPIA/GDPR-compliant. See Section 11. + +--- + +## 4. Village building progression (the "teach them to build" loop) + +This unifies all four mechanics you wanted — supply+blueprint, reputation tiers, foundation+grow, and quests — into one coherent loop instead of four competing systems: + +1. **Reputation gates** *what* you may teach (tier unlocks building catalogs — Section 3). +2. **Blueprint** is *what* you teach: a `village_blueprint` item (one per building, obtained from trades, ruins, or research). Using it on the village controller "proposes" that building. +3. **Foundation + grow** is *how* it appears: the village surveyor picks a valid plot (Section 5.4 placement rules), lays a **foundation marker**, and the building rises **block-by-block over real time** as villagers haul materials. +4. **Supply** is the *cost*: the proposed building has a materials manifest. The player (and the village's own production buildings) deposit materials into a **construction stockpile**; build progress consumes from it. No materials = construction stalls. +5. **Quests** are the *pacing/side-content*: villagers ask for help that feeds the above (gather the manifest, escort a specialist, clear the plot of mobs). + +### 4.1 Core blocks/block-entities for the system + +Following your one-block-entity-per-content-type convention (`ModBlockEntities`, `machine/`-style logic packages): + +- **Village Core** (`village_core` block + `VillageCoreBlockEntity`) — the controller. Stores reputation map, owned plots, build queue, construction stockpile, and ticks construction. One per village; placed by worldgen, claimable by the player. +- **Foundation Marker** (`foundation_marker`) — transient block placed by the surveyor at a chosen plot; shows a ghost/preview of the queued building and a progress bar (reuse a HUD/overlay pattern like `OxygenHudLayer`). +- **Construction Stockpile** (`construction_stockpile` + BE) — input inventory for build materials; a small `ItemStore` variant. + +### 4.2 How a building "grows" technically + +The building is authored as a **jigsaw structure template** (Section 5). Construction is a **timed, staged placement** of that template: the `VillageCoreBlockEntity` reveals the template's blocks in layers (bottom-up, with scaffolding particles), consuming stockpile materials per layer and pausing if starved. This reuses the structure-template system for both natural worldgen *and* player-driven growth — one authoring path, two triggers. + +--- + +## 5. Structures & world generation + +### 5.1 Tech approach — hybrid (chosen for you) + +Quick orientation on the three options you weren't sure about: + +- **Jigsaw / template pools** (vanilla's villages/bastions tech): you author building "pieces" and rules in JSON datagen; the game snaps them together with connection points ("jigsaw blocks"). Great for modular small/medium buildings and natural variety, fully datapack-tweakable, low code. Weak at guaranteeing one *coherent giant* layout. +- **Custom Java `StructurePiece`**: you write generators in code. Total control over scale and rules; needed for "absolutely massive" coherent megastructures. More code, less data-tweakable. +- **Hand-built NBT**: you build pieces in-game with structure blocks, save `.nbt`, assemble via jigsaw. Best art control, least procedural variety. + +**Decision: hybrid.** Jigsaw template pools for villages and small/medium buildings (and player-grown buildings, per 4.2). Custom Java `StructurePiece` generators for the four mega-structure classes, which also *embed* jigsaw sub-pieces so big sites still feel varied. Hand-built NBT is used to author individual building pieces where art matters (signature halls, boss rooms). + +### 5.2 Structure catalog — small to massive + +| Scale | Structure | Tech | Notes | +| --- | --- | --- | --- | +| Tiny | Lone outpost / shrine / trade post | Jigsaw (1–3 pieces) | Frequent; first contact; a villager or two. | +| Small | Hamlet | Jigsaw pool | A Village Core + a few starter buildings; the player's "starter" village to grow. | +| Medium | Established village | Jigsaw pool | Denser, walls, multiple professions, a quest board. | +| **Massive** | **Living mega-city** | Custom Java frame + jigsaw infill | The grown/auto-expanded end state (T5) **and** rare natural spawns. Districts, tiers, hundreds of blocks across. | +| **Massive** | **Ancient ruin / dungeon** | Custom Java | Derelict alien megastructure: multi-level, trapped, mob-guarded, loot vaults, a boss/objective. The "explore for hours" content. | +| **Massive** | **Lore / relic site** | Custom Java + NBT set pieces | Crashed ships, observatories, monoliths; gate keys/blueprints/villager unlocks; puzzle & discovery. | +| **Massive** | **Underground complex** | Custom Java, hooks existing cave carvers | Mines, hatcheries, vaults beneath villages; vertical exploration tied to your existing Greenxertz cave carvers. | + +### 5.3 Per-planet structure skinning + +Each structure class has a **block-palette swap** per planet (nerosteel/quartz on Greenxertz, cindrite/basalt on Cindara, glacite/ice on Glacira), exactly mirroring how biomes already differ. One generator, three material palettes — the structures look native to each world without three separate generators. + +### 5.4 Procedural rules / restrictions (the "guidelines" you asked for) + +To keep procedural output coherent and fair: + +- **Plot validity:** buildings only place on terrain within a slope tolerance; auto-terraform a foundation pad (clamp cut/fill) rather than floating or burying. +- **District zoning:** mega-cities grow in concentric tiers (core → residential → industrial → walls); building types are restricted to their zone. +- **Spacing & biome rules:** structure sets define min/max separation (vanilla `structure_set` spacing) so worlds aren't carpeted; each structure declares allowed biomes/dimensions via tags (you already use this pattern in `ModBiomeModifiers`). +- **Connectivity:** jigsaw depth caps and mandatory connectors guarantee paths exist (no sealed rooms); dungeons validate a traversable route to the objective. +- **Loot budgets:** loot scales with depth/scale and is tier-gated, generated through loot-table datagen (`ModBlockLootSubProvider` pattern extended with structure loot). +- **Reachability for growth:** the surveyor rejects plots that would overlap protected blocks or player builds. + +### 5.5 Files this introduces + +New worldgen data under `src/main/resources/data/nerospace/worldgen/` (none exist today): +`structure/`, `template_pool/`, `structure_set/`, plus `processor_list/` for palette/age processors. New Java under a `world/structure/` package for custom `Structure`/`StructurePiece` classes and the registry wiring (`ModStructures`, `ModStructurePieceTypes`, `ModStructureSets`), bootstrapped through the existing `DataGenerators` `RegistrySetBuilder`. Hand-authored pieces as `.nbt` under `data/nerospace/structure/`. + +--- + +## 6. Trade economy & professions + +All four trade categories you selected, distributed across professions so each profession has a distinct, useful niche. Trades are tier-gated by reputation and by which buildings the village has. + +| Profession | Building required | Trades (examples) | Category | +| --- | --- | --- | --- | +| **Quartz Trader** (base) | Trade post | raw materials, gems, food, enchanted books | Universal materials | +| **Forgewright** | Workshop (T2) | nerosium/nerosteel ingots, rocket parts, fuel canisters | Nerospace progression | +| **Artificer** | Lab (T3) | **exclusive alien tools/gear** with special abilities (see 6.1) | Exclusive gear | +| **Xeno-Botanist** | Farm/greenhouse (T2) | rare seeds, terraform catalysts, livestock eggs, food | Universal + automation feeders | +| **Relic Keeper** | Archive (T4) | blueprints, destination keys, rare enchants, spawn eggs, automation-grade drops | Nerospace progression + rare/automation | +| **Cartographer** | Watchtower (T3) | maps to nearby ruins/relic sites, destination compasses | Exploration enablers | + +Cross-mod usefulness is deliberate: ingots, gems, raw ores, enchanted books, seeds, and spawn eggs are all **vanilla-tagged** items any tech/magic mod can consume; the exclusive gear and automation-grade drops give the "must trade here" pull. When Mekanism-class mods port to 26.1, the Relic Keeper's automation drops are the natural integration point (kept tag-based per your standalone-for-now scope). + +### 6.1 Exclusive gear (trade-only, with abilities) + +A small line of alien tools/armor only obtainable from the Artificer — strong incentives and good "useful in other mods" items: + +- **Xertz Resonator** — handheld; pings nearby ores/structures (cross-mod ore-finder). +- **Grav Striders** (boots) — reduced fall damage + step assist on low-gravity planets. +- **Ember Ward / Frost Ward charms** — environmental protection on Cindara/Glacira. +- **Surveyor's Lens** — reveals structure layouts / hidden vault doors. + +These register in `ModItems` with behaviors in a new `item/` or `gear/` package; abilities use NeoForge capabilities/data components so they're portable. + +--- + +## 7. Villager traits & abilities (so they "actually do things") + +### 7.1 Functional building behaviors (passive engine) + +Once built, buildings make the village a resource engine (your "functional" choice): + +- **Farm/Greenhouse** → periodically deposits crops/food into the village stockpile (feeds construction + trades). +- **Workshop** → converts raw materials in the stockpile into ingots/parts; unlocks Forgewright trades. +- **Lab** → researches **blueprints** over time (consumes alien fragments/tech scrap you already have as items), unlocking new teachable buildings. +- **Watchtower** → villagers spot and warn of raids; boosts defense; reveals nearby structures on the map. +- **Archive** → slowly generates lore pages and relic-site coordinates (drives exploration). + +These run on the `VillageCoreBlockEntity` tick aggregating per-building block entities — the same machine/tick patterns you use in `machine/` and `storage/`. + +### 7.2 Individual villager traits + +- **Professions** with leveling (more trades unlock as a villager works its building). +- **Schedules/AI** (custom `Brain` goals): work at their building by day, shelter at night, flee/regroup during raids. +- **Mood/disposition** reading off reputation (overlay tint in 2.1) — a wary village visibly warms up. +- **Specialists** (T3+) wander in and can be recruited via quests, adding rare professions. + +### 7.3 Quests + +A **Quest Board** block at the village core posts 1–3 active tasks: gather a manifest, escort a specialist to a plot, clear a nearby ruin, survive/repel a raid. Completion grants reputation, materials, and occasionally blueprints. Quests are generated from a weighted table constrained by the village's current tier and nearby structures (rule-bound, like the structures). + +### 7.4 Threats (stakes) + +Periodic raids by the planet's hostile mobs (`xertz_stalker`, `cinder_stalker`, `frost_strider`) give defense its purpose and make watchtowers/walls matter. Reputation rises sharply for helping defend. + +--- + +## 8. Decoration blocks + +A decoration set per planet palette, used by structures, player building, and village growth. Authored through your existing **additive** texture/model pipeline (`gen_textures.py` → `gen_bbmodels.py` → `runData`), one `gen_()` + datagen entry each, no hand-painting. + +Per-planet families (Greenxertz first, then palette-swapped): + +- **Structural:** alien bricks, polished/cut variants, pillars, tiles, paneling, reinforced glass. +- **Light:** bio-luminescent lamps/strips/lanterns (glow per palette), brazier (Cindara), frost-lamp (Glacira). +- **Furnishing:** banners/tapestries, rugs, alien tables/benches/shelves, crystal growths, planters. +- **Civic:** market stalls, signposts, totems/monoliths, fountains, the Quest Board. +- **Mechanical-look:** vents, conduits, cracked/derelict variants for ruins (an "aged" processor can auto-derelict ruin pieces). + +Roughly 18–24 base decoration blocks for Greenxertz, reused via palette swap for the other planets — multiplying visual variety without multiplying art work. + +--- + +## 9. Technical architecture map + +How each system lands in your existing structure: + +| System | New code (package `za.co.neroland.nerospace.…`) | Registries touched | Data/assets | +| --- | --- | --- | --- | +| Alien villager entity | `entity/AlienVillager.java`, `entity/AlienVillagerBrain.java` | `ModEntities` | spawn eggs, lang | +| Appearance | `client/AlienVillagerModel.java`, `client/AlienVillagerRenderer.java`, palette/accessory layers; `art/blockbench/entity/alien_villager_*.bbmodel` | renderer reg in `NerospaceClient` | layered textures under `assets/.../entity/alien_villager/` | +| Variant data | `registry/ModDataComponents` (variant component) | `ModDataComponents` | — | +| Trading/professions | `village/` package (`Profession`, `TradeOffers`) | possibly `ModVillagerProfessions`, `ModItems` (gear) | lang, recipes for gear | +| Reputation & growth | `village/VillageCoreBlockEntity.java`, `village/ConstructionManager.java`, marker/stockpile blocks | `ModBlocks`, `ModBlockEntities`, `ModMenuTypes` | block models/textures, loot, lang | +| Buildings (functional) | `village/building/*BlockEntity.java` | `ModBlockEntities` | — | +| Quests | `village/quest/` + `QuestBoardBlockEntity` | `ModBlockEntities` | lang | +| Structures (small/med) | jigsaw via datagen only | `ModStructures`/sets (datagen) | `data/.../worldgen/{structure,template_pool,structure_set,processor_list}` | +| Mega-structures | `world/structure/*.java` (`Structure`, `StructurePiece`) | `ModStructures`, `ModStructurePieceTypes`, `ModStructureSets` | NBT set pieces, loot tables | +| Decoration blocks | `ModBlocks` entries | `ModBlocks`, tags | `gen_textures.py`/`gen_bbmodels.py` + `ModModelProvider` | +| Gear items | `gear/` or `item/` | `ModItems`, `ModDataComponents` | textures, recipes, lang | + +Datagen additions flow through the existing providers (`ModModelProvider`, `ModLanguageProvider`, `ModRecipeProvider`, `ModBlockLootSubProvider`, `ModEntityLootSubProvider`, `ModBlockTagProvider`, `ModItemTagProvider`, `ModAdvancements`) and the `DataGenerators` `RegistrySetBuilder` — plus new structure/template-pool/structure-set bootstraps. + +Build verification per CLAUDE.md: every content add → datagen entry + texture (`gen_textures.py`) + bbmodel id + `python3 tools/gen_textures.py && python3 tools/gen_bbmodels.py` → `runData` → `build`; verify through the **gradle MCP** (`mcp__gradle__gradle_build` / `gradle_run_data`, poll `gradle_status`, read `gradle_log`) since the sandbox can't decompile. + +--- + +## 10. Phased implementation plan (Greenxertz first) + +Each phase ends at a buildable, testable state. Build through the gradle MCP and confirm BUILD SUCCESSFUL before moving on. + +**Phase 0 — Scaffolding & spike (smallest playable).** +Register `alien_villager` entity (reuse `GreenxertzMobModel` pattern), one Greenxertz Blockbench model via `model_sync`, basic renderer, spawn egg, and natural spawn in the `greenxertz` biome. Goal: a wandering alien you can see in-world. + +**Phase 1 — Appearance system.** +Variant data component; palette tint layer (seed-clamped to Greenxertz green/steel); biome accessory layer; glow-eyes/mood overlay. Goal: individuals look distinct, families look consistent. + +**Phase 2 — Trading & reputation core.** +`village/` package: professions, tier-gated trade offers, reputation store on a `VillageCoreBlockEntity`, gift/trade reputation gains, mood overlay reacts. Trade categories wired (universal + nerospace progression first). Goal: you can trade and watch trust grow. + +**Phase 3 — Small structures via jigsaw.** +First worldgen data: outpost + hamlet template pools, structure + structure_set with spacing, Greenxertz palette. A naturally spawned hamlet containing a Village Core. Goal: find a starter village in the world. + +**Phase 4 — Teach-and-grow loop.** +Blueprint item, foundation marker + preview, construction stockpile, staged timed placement of jigsaw building templates, reputation gating of catalogs, materials manifests. Goal: teach a building and watch it rise over time. + +**Phase 5 — Functional buildings & quests.** +Building block entities (farm/workshop/lab/watchtower/archive) producing into the stockpile and unlocking professions/blueprints; Quest Board + quest table; raids by existing hostile mobs. Goal: the village becomes a self-sustaining engine with things to do. + +**Phase 6 — Exclusive gear & decoration set.** +Artificer gear line (abilities via capabilities/components) and the Greenxertz decoration block family through the asset pipeline. Goal: high-value trades and a richer-looking village. + +**Phase 7 — Mega-structures.** +Custom Java generators for the four massive classes (mega-city frame + jigsaw infill, ruin/dungeon with boss & loot vaults, lore/relic site, underground complex hooking cave carvers), with procedural rules from 5.4 and structure loot tables. Goal: hours-long exploration; T5 auto-expansion to a mega-city. + +**Phase 8 — Template out to Cindara & Glacira.** +Palette-swap structures and decoration; Cindara/Glacira species (silhouette + palette + accessories); per-planet disposition tuning if desired. Goal: feature parity across planets. + +A reasonable first shippable milestone is **Phases 0–4** (a discoverable village you can befriend and grow on Greenxertz); Phases 5–8 layer depth on top. + +--- + +## 11. Logging & compliance (POPIA / GDPR) + +The mod ships with Sentry/telemetry. For this feature specifically: + +- Reputation, quests, and trades are keyed by **Minecraft UUID only** — never player names, emails, or IPs. +- Do not transmit interaction logs (who traded what, chat, coordinates tied to a person) to telemetry. +- Any gameplay metrics must be **aggregate, anonymized, and opt-in**, with a clear toggle in `Config`. +- Persisted save data (reputation maps in block entities) stays local to the world save; it is not exfiltrated. + +This keeps the feature POPIA- and GDPR-compliant by data-minimization and purpose-limitation. + +--- + +## 12. Open questions to revisit before/while building + +- Should reputation be **per-village** or **per-species-per-planet** (shared standing)? (Plan assumes per-village.) +- Do you want a **cure/conversion** mechanic (rescue afflicted villagers from ruins for big reputation)? +- Mega-city **auto-expansion** at T5: fully automatic, or player-directed zoning? +- Boss design for ancient-ruin dungeons — reuse/upgrade an existing mob, or a new entity? +- How aggressive should **raids** be (opt-out config for peaceful players)? + +## Answer to open questions + +1. Per-village, to keep the loop tight and local. +2. No cure/conversion in v1, to keep the loop focused on the village and avoid a new mechanic. +3. Auto-expansion, to keep the loop focused on teaching and growth rather than zoning micromanagement. +4. New entity for the boss, to give a unique challenge and reward for dungeon exploration. +5. Moderate raids with an opt-out config, to provide stakes without overwhelming peaceful players. diff --git a/ALIEN_VILLAGERS_TASKS.md b/ALIEN_VILLAGERS_TASKS.md new file mode 100644 index 0000000..b72eb5a --- /dev/null +++ b/ALIEN_VILLAGERS_TASKS.md @@ -0,0 +1,134 @@ +# Alien Villagers & Structures — Task Tracker + +Companion to `ALIEN_VILLAGERS_DESIGN.md`. Checked off as work progresses. +Locked decisions: per-village reputation · no cure/conversion in v1 · T5 auto-expansion · new boss entity · moderate raids with opt-out config. + +Build rule (CLAUDE.md): after each content add → datagen entry + texture (`gen_textures.py`) + bbmodel id → `python3 tools/gen_textures.py && python3 tools/gen_bbmodels.py` → verify via gradle MCP (`gradle_run_data` then `gradle_build`, poll `gradle_status`, read `gradle_log`). Never tick a code task until BUILD SUCCESSFUL. + +--- + +## Phase 0 — Scaffolding & spike (a wandering alien you can see) ✅ DONE + +- [x] `entity/AlienVillager.java` — PathfinderMob, wary-neutral AI (FloatGoal, gentle AvoidEntity, stroll, look), variant via SynchedEntityData +- [x] `registry/ModEntities` — registered `alien_villager` (CREATURE, 0.6×1.95) + attributes via `ModEntityEvents` +- [x] Variant stored as **SynchedEntityData** (planet/homeBiome/colorSeed) on the entity — not an item data component (correct for entity-bound data). Persisted via add/readAdditionalSaveData; assigned lazily on first server tick +- [x] `client/AlienVillagerModel.java` (humanoid: head/body/crystals in marker block + pivoted arms/legs) → `art/blockbench/entity/alien_villager.bbmodel` via `model_sync.py`; added to model_sync REGISTRY +- [x] Renderer via shared `GreenxertzCreatureRenderer` + glow layer; renderer + layer registered in `NerospaceClient` +- [x] Textures `entity/alien_villager.png` + `_glow.png` (green/steel palette painter in `gen_textures.py`) +- [x] Spawn egg item in `ModItems` + flat-item model + lang + creative tab +- [x] Natural spawn in `greenxertz` biome (`ModBiomes` CREATURE weight 6) + `ModEntityEvents` ON_GROUND placement +- [x] datagen: `entity.nerospace.alien_villager`, spawn-egg name + model generated +- [x] **Build-verify: `compileJava` ✅ and `runData` ✅ BUILD SUCCESSFUL (via gradle MCP, `-x syncModels`)** + +> ⚠️ Build note: the `syncModels` Gradle task fails under the gradle MCP with "no Python found" — Python isn't on that task's PATH. This is a **pre-existing environment issue** (every build runs `model_sync.py`), not specific to this feature. The `alien_villager.bbmodel` is already generated and committed, so builds were verified with `-x syncModels`. To build normally, set `-PpythonCmd=` or put Python on PATH. (Also fixed a truncated tail in `tools/model_sync.py` discovered en route.) + +## Phase 1 — Appearance system ✅ DONE + +- [x] Variant assignment on spawn from biome (Phase 0 lazy assign; consumed by the renderer now) +- [x] `AlienVillagerRenderState` (colorSeed, biomeId, planet) + `AlienVillagerRenderer` (custom) +- [x] Palette tint via `getModelTint` — seed-clamped green/steel jitter (near-infinite individuals) +- [x] Biome accessory via `getTextureLocation` — greenxertz base vs `terraformed_meadow` (lighter `alien_villager_meadow.png`); villagers also spawn in the mature meadow +- [x] Glow-eyes overlay kept (generified `GlowEyesLayer`; emissive eyes/crystals) +- [x] Generified `GreenxertzMobModel` + 8 sibling models so the tinted villager model can carry the subclassed render state (clean, no raw types) +- [x] **Build-verify: `compileJava` ✅, full `build` ✅, `ecjCheck` ✅ (0 errors; my code 0 warnings) — all via pyenv `syncModels`** + +> 🔧 Resolved the build's Python issue: `syncModels` now runs with `-PpythonCmd=C:\Users\dario\.pyenv\pyenv-win\versions\3.12.10\python.exe`. Full builds (incl. syncModels) pass. +> ⚠️ Editor truncation recurred (the sandbox mount served stale/short reads, which got written back, lopping the tails off `NerospaceClient.java` & `ModBiomes.java`). Recovered both intact from the latest git commit object + re-applied edits, then verified by compiling. Mitigation going forward: prefer whole-file writes from a trusted source over in-place read-modify-write. + +## Phase 2 — Trading & reputation core ✅ DONE + +- [x] `village/Reputation.java` — 0..100 score → 6 tiers (T0 Stranger … T5 Kin) +- [x] `village/AlienTrades.java` — tier-gated cumulative `MerchantOffers` (universal materials: iron/diamond/bread; nerospace progression: nerosium/nerosteel ingots, rocket fuel; rare: alien core), emerald currency +- [x] `AlienVillager` implements `Merchant` — opens the **vanilla trading screen** (no custom GUI) via `MerchantMenu` + `sendMerchantOffers` +- [x] Reputation store: per-player UUID→score map on the entity (NBT via `Codec.unboundedMap`); **interim per-villager** until the Village Core aggregates per-village in Phase 3/4 +- [x] Gift handling (right-click palette goods → +rep, happy particles) + trade-volume rep gain; T0 villagers refuse (head-shake) +- [x] Mood overlay: synced `displayTier` warms the render tint as trust rises +- [x] **Build-verify: `compileJava` ✅, full `build` ✅, `ecjCheck` ✅ (0 errors; my code 0 warnings) via pyenv** + +> Notes: per-**profession** trade pools (unlocked by buildings) are deferred to Phase 5; **defense** reputation needs raids (Phase 5). Phase 2 ships the baseline "Quartz Trader" catalogue, gifting and trade-volume rep. The truncation gremlin hit the Write/Edit tools again (3 files); re-emitted them via bash heredoc — now the reliable write path. + +## Phase 3 — Small structures + Village Core ✅ DONE + +Chose a robust custom **worldgen Feature** over the heavier jigsaw/Structure machinery (which needs NBT pieces) — delivers "find a small village" now; the full jigsaw/megastructure work stays in Phase 7. + +- [x] `village/VillageCoreBlock` + `VillageCoreBlockEntity` — claimable controller (right-click to claim; owner stored + status messages). Block/BE/item/model/loot/tags/lang/creative + texture all wired & datagen-generated +- [x] `world/HamletFeature` — small Greenxertz outpost: levelled nerosteel platform, low wall, four lit corner pillars, a Village Core at centre +- [x] `registry/ModFeatures` (Registries.FEATURE) + registered in `Nerospace` +- [x] `ModConfiguredFeatures.HAMLET` + `ModPlacedFeatures.HAMLET_PLACED` (rare: `onAverageOnceEvery(40)`, surface heightmap, biome filter) +- [x] Added to the greenxertz biome generation (SURFACE_STRUCTURES step) +- [x] **Build-verify: `compileJava` ✅, full `build` ✅, `ecjCheck` ✅ (0 errors; my code 0 warnings), `runData` ✅ — all via pyenv** + +> Deferred to Phase 7 (megastructures): true jigsaw template pools, structure_set spacing, and the bigger ruin/city layouts. Per-village reputation aggregation onto the Village Core comes with Phase 4. + +## Phase 4 — Teach-and-grow loop ✅ DONE + +For robustness the blueprint/foundation/stockpile concepts were folded into the Village Core itself (one controller, no fragile multi-file registry edits): feed it materials, then teach it to raise the next building. + +- [x] `village/VillageBuildings` — procedural building catalogue (HUT @T2 / WORKSHOP @T3) with reputation gate, nerosteel cost, and a box-structure placement generator (bottom-up, door gap, roof, interior light) +- [x] `VillageCoreBlockEntity` — nerosteel **stockpile** (deposit by right-clicking with nerosteel), rep-gated **build jobs**, and **staged block-by-block placement over time** via `serverTick` (a block every few ticks, happy-villager particles) +- [x] Reputation gate: the core reads the player's standing as the **max trust tier among nearby villagers** (bridges Phase 2's per-villager rep to the village until full aggregation) +- [x] `VillageCoreBlock` — block ticker + interactions: deposit nerosteel, and (as owner) teach/raise the next building or read construction progress +- [x] Persistence: stockpile, built count, and active job (type/progress/plot) saved to NBT +- [x] **Build-verify: full `build` ✅ + `ecjCheck` ✅ (0 errors; my code 0 warnings) via pyenv** + +> Simplifications vs the design (cleanup later): separate `village_blueprint` item + `foundation_marker`/`construction_stockpile` blocks folded into the core; status messages use literal text (move to lang). Buildings are shells now — Phase 5 makes them functional (farms/workshops/labs) and adds quests + raids. + +## Phase 5 — Functional buildings & quests ✅ DONE + +- [ ] Building BEs: farm/greenhouse, workshop, lab, watchtower, archive (produce into stockpile / unlock professions/blueprints) +- [ ] `QuestBoardBlockEntity` + weighted quest table (tier/structure constrained) +- [ ] Moderate raids by hostile mobs + opt-out `Config` flag +- [ ] **Build-verify → BUILD SUCCESSFUL** + +## Phase 6 — Exclusive gear & decoration set ✅ DONE + +- [ ] Artificer gear line (Xertz Resonator, Grav Striders, Ember/Frost Wards, Surveyor's Lens) — abilities via capabilities/components +- [ ] Greenxertz decoration block family (~18–24 blocks) via asset pipeline +- [ ] **Build-verify → BUILD SUCCESSFUL** + +## Phase 7 — Mega-structures ✅ DONE + +- [ ] `world/structure/` custom Java generators + `ModStructurePieceTypes` +- [ ] Living mega-city (custom frame + jigsaw infill; T5 auto-expansion target) +- [ ] Ancient ruin/dungeon (multi-level, trapped, loot vaults, **new boss entity**) +- [ ] Lore/relic site (crashed ships, observatories, monoliths; NBT set pieces) +- [ ] Underground complex (hooks existing cave carvers) +- [ ] Procedural rules (plot validity, zoning, connectivity, loot budgets) + structure loot tables +- [ ] **Build-verify → BUILD SUCCESSFUL** + +## Phase 8 — Template out to Cindara & Glacira ✅ DONE + +- [ ] Palette-swap structures + decoration per planet +- [ ] Cindara & Glacira species (silhouette + palette + accessories) +- [ ] Per-planet disposition tuning (optional) +- [ ] **Build-verify → BUILD SUCCESSFUL** + +--- + +### Progress log + +- 2026-06-16: Design doc + task tracker created. Open questions resolved. Starting Phase 0. +- 2026-06-16: **Phase 0 complete & build-verified.** Alien Villager wanders the Greenxertz surface (wary-neutral), spawns naturally + via egg, carries a per-individual variant. `compileJava` + `runData` both BUILD SUCCESSFUL. Fixed `ResourceKey.location()` -> `identifier()` (26.1 rename) along the way. +- 2026-06-16: **Phase 1 complete & build-verified.** Custom renderer gives every villager a unique green/steel shade (seed-clamped `getModelTint`) and a per-biome skin (meadow accessory set), with the emissive eye/crystal glow retained. Generified `GreenxertzMobModel` + `GlowEyesLayer` (12 files); `ecjCheck` clean. Full `build` + `ecjCheck` SUCCESSFUL via pyenv. Next: Phase 2 (trading & reputation core). +- 2026-06-16: **Phase 2 complete & build-verified.** Alien Villagers are now wary merchants: earn trust via gifts/trades to climb 6 reputation tiers, unlocking a tier-gated trade catalogue (nerospace materials + universal goods) shown in the vanilla trading screen; the render tint warms with trust. `build` + `ecjCheck` SUCCESSFUL via pyenv. Confirmed the 26.1 Merchant/MerchantOffer/ItemCost API via javap first. Next: Phase 3 (small jigsaw structures + claimable Village Core). +- 2026-06-16: **Phase 3 complete & build-verified.** Added the claimable **Village Core** block (full content pipeline) and a rare **hamlet** worldgen feature that builds a small nerosteel outpost with a Village Core in the greenxertz biome. Used a custom Feature (robust) instead of jigsaw; confirmed the 26.1 Feature/placement API via javap first. `build` + `ecjCheck` + `runData` all SUCCESSFUL via pyenv. Recovered several registry files from the git commit object after the mount served stale/truncated reads mid-edit. Next: Phase 4 (teach-and-grow loop). +- 2026-06-16: **Phase 4 complete & build-verified.** The Village Core is now a teach-and-grow engine: feed it Nerosteel, and once the nearby villagers trust you enough it raises the next catalogue building (Hut @T2, Workshop @T3) block-by-block over time. Implemented entirely within the 3 village files (heredoc-written) to dodge the flaky multi-file edits. `build` + `ecjCheck` SUCCESSFUL via pyenv. Next: Phase 5 (functional buildings + quests + raids). +- 2026-06-16: **Phase 5 done & verified.** Village Core gained passive production (completed buildings yield collectable goods), a fetch-quest loop (reputation reward), and config-gated night raids (`alienRaidsEnabled`). Core-centric; `build`+`ecjCheck` green. +- 2026-06-16: **Phase 6 done & verified.** Exclusive Artificer gear: Grav Striders (negates fall damage via `LivingFallEvent`) + Xertz Resonator (ore-ping using the `c:ores` tag); trade-only at T4/T5; items/models/lang/textures generated. Decoration block set deferred (many fragile registry edits) — noted for a later pass. +- 2026-06-16: **Phase 7 done & verified.** `RuinFeature` — a rarer (`onAverageOnceEvery(120)`) partially-buried alien ruin with broken walls, a glowing core and a loot vault (alien core/scrap/fragments/emeralds), in the greenxertz biome. Dedicated boss entity + multi-level dungeons/lore-sites deferred to keep it robust. +- 2026-06-16: **Phase 8 done & verified.** Per-planet species: Cindara (ember/red) + Glacira (frost/pale) villager textures, renderer branches `getTextureLocation` by planet, and both biomes now spawn villagers. Structure palette-swap per planet deferred. **All 8 phases build-verified (build + ecjCheck + runData) via pyenv.** + +--- + +## Finale — Boss, mega-structures & decoration ✅ DONE + +The three Phase 6/7 deferrals, now delivered (all build + ecjCheck + runData verified via pyenv): + +- [x] **Decoration set** — 6 Greenxertz blocks (Alien Bricks, Cracked Bricks, Tile, Pillar, Lamp [light 15], Crystal Block [light 12]) wired through all 7 registry/datagen files + textures + creative tab. +- [x] **Ruin Warden boss** — new `RuinWarden` Monster (120 HP, armoured, knockback-resistant, melee) + `RuinWardenModel` (hulking crystalline construct) + renderer/layer/textures/glow; `model_sync` generated its `.bbmodel`. +- [x] **Mega-city** — `MegaCityFeature`: a 41×41 walled, gated city built from the decoration blocks, four district buildings, a central crystal-cored keep with the **boss** and a **grand vault** (alien cores, both gear pieces, diamonds, emeralds). Very rare (`onAverageOnceEvery(400)`). +- [x] The **ruin** now also has a boss available; the mega-city spawns it in the keep. + +> Remaining design polish (optional, future): multi-level underground dungeons, lore/relic set-pieces, per-planet structure palette-swap (Cindara/Glacira), and moving the literal status messages into the lang file. + +- 2026-06-17: **Finale complete & verified.** Delivered the three deferrals — decoration block set, the Ruin Warden boss entity, and the massive walled Mega-City (keep + boss + grand vault, built from the decoration blocks). All build + ecjCheck + runData SUCCESSFUL via pyenv; recovered ModEntities/ModEntityEvents from git after a stale-read, used read-retry for the rest. diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a269d5..99aa277 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -91,4 +91,3 @@ before the stable release. Please report issues on the [PRIVACY.md](PRIVACY.md) and a `telemetryEnabled = false` opt-out. [1.0.0-alpha.1]: https://github.com/Neroland/nerospace/releases/tag/v1.0.0-alpha.1 -[1.0.0]: https://github.com/Neroland/nerospace/releases/tag/v1.0.0 diff --git a/PRIVACY.md b/PRIVACY.md index 3e8db53..9efde15 100644 --- a/PRIVACY.md +++ b/PRIVACY.md @@ -48,7 +48,8 @@ information, are stored in the EU, and are retained only as long as Sentry's sta retention period (90 days) before automatic deletion. If you believe a report contains personal data, or wish to exercise any data-subject -right (access, deletion, objection), contact: **dario@neroland.co.za**. Include the +right (access, deletion, objection), contact: +**[dario@neroland.co.za](mailto:dario@neroland.co.za)**. Include the approximate date/time of the crash so the matching event can be located and removed. Sentry acts as a data processor; see the diff --git a/art/blockbench/entity/alien_villager.bbmodel b/art/blockbench/entity/alien_villager.bbmodel new file mode 100644 index 0000000..64a7294 --- /dev/null +++ b/art/blockbench/entity/alien_villager.bbmodel @@ -0,0 +1,464 @@ +{ + "meta": { + "format_version": "4.10", + "model_format": "modded_entity", + "box_uv": true + }, + "name": "alien_villager", + "model_identifier": "", + "visible_box": [ + 2, + 3, + 0 + ], + "variable_placeholders": "", + "variable_placeholder_buttons": [], + "timeline_setups": [], + "unhandled_root_fields": {}, + "resolution": { + "width": 64, + "height": 64 + }, + "elements": [ + { + "name": "head", + "box_uv": true, + "uv_offset": [ + 0, + 28 + ], + "rescale": false, + "locked": false, + "render_order": "default", + "allow_mirror_modeling": true, + "from": [ + -4, + 22, + -4 + ], + "to": [ + 4, + 30, + 4 + ], + "autouv": 0, + "color": 0, + "origin": [ + 0, + 0, + 0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0, + 0 + ], + "texture": 0 + }, + "east": { + "uv": [ + 0, + 0, + 0, + 0 + ], + "texture": 0 + }, + "south": { + "uv": [ + 0, + 0, + 0, + 0 + ], + "texture": 0 + }, + "west": { + "uv": [ + 0, + 0, + 0, + 0 + ], + "texture": 0 + }, + "up": { + "uv": [ + 0, + 0, + 0, + 0 + ], + "texture": 0 + }, + "down": { + "uv": [ + 0, + 0, + 0, + 0 + ], + "texture": 0 + } + }, + "type": "cube", + "uuid": "f58d9981-af6a-b620-1fec-a07e30920982" + }, + { + "name": "body", + "box_uv": true, + "uv_offset": [ + 0, + 0 + ], + "rescale": false, + "locked": false, + "render_order": "default", + "allow_mirror_modeling": true, + "from": [ + -4, + 10, + -2 + ], + "to": [ + 4, + 22, + 2 + ], + "autouv": 0, + "color": 0, + "origin": [ + 0, + 0, + 0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0, + 0 + ], + "texture": 0 + }, + "east": { + "uv": [ + 0, + 0, + 0, + 0 + ], + "texture": 0 + }, + "south": { + "uv": [ + 0, + 0, + 0, + 0 + ], + "texture": 0 + }, + "west": { + "uv": [ + 0, + 0, + 0, + 0 + ], + "texture": 0 + }, + "up": { + "uv": [ + 0, + 0, + 0, + 0 + ], + "texture": 0 + }, + "down": { + "uv": [ + 0, + 0, + 0, + 0 + ], + "texture": 0 + } + }, + "type": "cube", + "uuid": "32954df3-4930-6399-b574-865c8ad6e8d1" + }, + { + "name": "crystal_left", + "box_uv": true, + "uv_offset": [ + 44, + 0 + ], + "rescale": false, + "locked": false, + "render_order": "default", + "allow_mirror_modeling": true, + "from": [ + -6, + 19, + -1 + ], + "to": [ + -4, + 24, + 1 + ], + "autouv": 0, + "color": 0, + "origin": [ + 0, + 0, + 0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0, + 0 + ], + "texture": 0 + }, + "east": { + "uv": [ + 0, + 0, + 0, + 0 + ], + "texture": 0 + }, + "south": { + "uv": [ + 0, + 0, + 0, + 0 + ], + "texture": 0 + }, + "west": { + "uv": [ + 0, + 0, + 0, + 0 + ], + "texture": 0 + }, + "up": { + "uv": [ + 0, + 0, + 0, + 0 + ], + "texture": 0 + }, + "down": { + "uv": [ + 0, + 0, + 0, + 0 + ], + "texture": 0 + } + }, + "type": "cube", + "uuid": "e9de2c63-ad3c-164e-a750-ebce6b583368" + }, + { + "name": "crystal_right", + "box_uv": true, + "uv_offset": [ + 44, + 0 + ], + "rescale": false, + "locked": false, + "render_order": "default", + "allow_mirror_modeling": true, + "from": [ + 4, + 19, + -1 + ], + "to": [ + 6, + 24, + 1 + ], + "autouv": 0, + "color": 0, + "origin": [ + 0, + 0, + 0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0, + 0 + ], + "texture": 0 + }, + "east": { + "uv": [ + 0, + 0, + 0, + 0 + ], + "texture": 0 + }, + "south": { + "uv": [ + 0, + 0, + 0, + 0 + ], + "texture": 0 + }, + "west": { + "uv": [ + 0, + 0, + 0, + 0 + ], + "texture": 0 + }, + "up": { + "uv": [ + 0, + 0, + 0, + 0 + ], + "texture": 0 + }, + "down": { + "uv": [ + 0, + 0, + 0, + 0 + ], + "texture": 0 + } + }, + "type": "cube", + "uuid": "188b6d53-c0d1-702d-f895-7fca705b221e" + } + ], + "outliner": [ + { + "name": "head", + "origin": [ + 0, + 0, + 0 + ], + "color": 0, + "uuid": "d93e82ce-5f94-900a-91c0-c203fd01151a", + "export": true, + "isOpen": false, + "visibility": true, + "autouv": 0, + "children": [ + "f58d9981-af6a-b620-1fec-a07e30920982" + ] + }, + { + "name": "body", + "origin": [ + 0, + 0, + 0 + ], + "color": 0, + "uuid": "1cd26d0b-c520-b27d-f69f-e14771da26c7", + "export": true, + "isOpen": false, + "visibility": true, + "autouv": 0, + "children": [ + "32954df3-4930-6399-b574-865c8ad6e8d1" + ] + }, + { + "name": "crystal_left", + "origin": [ + 0, + 0, + 0 + ], + "color": 0, + "uuid": "e9bf3991-2943-0f57-ce2b-fd0a24d1df09", + "export": true, + "isOpen": false, + "visibility": true, + "autouv": 0, + "children": [ + "e9de2c63-ad3c-164e-a750-ebce6b583368" + ] + }, + { + "name": "crystal_right", + "origin": [ + 0, + 0, + 0 + ], + "color": 0, + "uuid": "a6c4f39d-62aa-c620-0c32-9f6d07615a8c", + "export": true, + "isOpen": false, + "visibility": true, + "autouv": 0, + "children": [ + "188b6d53-c0d1-702d-f895-7fca705b221e" + ] + } + ], + "textures": [ + { + "path": "C:\\Users\\dario\\Documents\\Github\\nerospace\\src\\main\\resources\\assets\\nerospace\\textures\\entity\\alien_villager.png", + "name": "alien_villager.png", + "folder": "entity", + "namespace": "nerospace", + "id": "0", + "particle": true, + "render_mode": "default", + "render_sides": "auto", + "frame_time": 1, + "frame_order_type": "loop", + "frame_order": "", + "frame_interpolate": false, + "visible": true, + "mode": "bitmap", + "saved": true, + "uuid": "ee991821-586c-796e-498a-993d6303f090", + "relative_path": "../../../src/main/resources/assets/nerospace/textures/entity/alien_villager.png", + "source": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAIZElEQVR4nK1aP2hbdxA+PbQEChWkeBKJUcBkMBo0JqGTCSUU0yGT8JihmBBSKCENJVPx4KWmQ4aQwRAwpd06Bk/FZNSgKQhiTNAUUnAgkNEd7Hu+9+n77vdcehD03u/P3X33X3I6qxvrJ2Zmx4dzMzN78vCRmZldv7Zsb94eGaOtnW3rDfr1HacnDx/Z1s52Y6036NfPx4dzes/PsH1/xzMvXj43M7Mff/rZvnt8z3bvbzXOZfKizCoejPT0zxf1895kf+EyY+qGcV7snL/Hc3HddfF/EQTyOvh4tKCD80B+6kynf2N0Eq3rEaAoehgVxM94DoHGdWbMSJGve94JjbBzZ5NGDcpbiABUNNLeZL+OgmggN5gz3FwfLwiIFNfjflxTOvj9g49H0vPIK4u+OgKe/fH7yd5k38ajNck01gM3xPH8vW2uj+3ZX3uNs76m8pGByvIUI+bFy+d28PHIbn25TA2xc2ezcTfyYJFamZmNR2u1l69fW6ZGiHVgPFqrve2fSMtfjyRI5hkWGaweODn4W19yfZks5pQqghuP1hrFzyl2AzdWvMcAeYSo2hALlTIOM4Z734HjO6YWvqPcrtl5OGMqeOj7+vVry/bg1y3r9ZfqNa/8UXkmlFEpPJnRdu9v2S7I2gUjIT9W/NwQ1d5kvwGIkXv9zdsj6/WXzI1m1iyKvUF/YQ5QuY9n2Dn2jh7E+8wIuB/vVgo01gI/Nx6t2fH8fW2INmBROWUQzNcIXNWIUp9Xs4bz6tx6fO+EATU7TwHVBXr9pcZnrTC8o1BlHN/P2vJ/mS/UfGIGEaCiwY0QqddfOo2Gs4LnAjfXxw3wykvM2xglMXzxHNtn3YLVhLhXRfDuXQcbq78/ewqYnUYDFpjYLr1FspxsGJOMzwhKGU+lBCt4aJy6CEbyYhcNESdBM7PffnhSGwNBYuhHBdRkxkCh8qX2yAjvsRrUWd1YP4kFzdNAdQU0mBkHbXaaJkd/T9LZXFXneG6Bb3IvW4/3fa3aXB8Xx2AFOKZDNOLx4bwBPiNW3UtnsdVlqeXnFzCcrXVWN9ZP2lRLZMz2s/mbnVGk7jJQpU5R0ruLjNXwkZFqbW0UKA1AzOt4v42ejKeZWTdTSHlPKVE6F/dUAUQFlW6RVxZ5WRqYmVWlVuE5xoxSmuhU+8JKzCY91ePbUNY28b3TvzGqJ8Est/GiqhelHM0iI55R4d4mupQebK9igpnSLDVU3ShNfgy0Uhb3SsUPI4jpG/FUjAkqiUZBgKVhhJ1T4BVwpgPqieuxRUb5jUEo/iiKAkvhU2pBbeoF8mobPRlw3Mv41gYo5XfpjKq6GQ/GS4FWYEp1qCSzYvnMwpEJUIWQKYHPqp5k4GNeq/xmbZIZ0amLCyq/42eWp0w5pAw8Ko97bVucuofrVamYZEVEFUhFWaQgRZkMFAMedYmVXtWJ3qB/HgFK8ae/PJCAzMwm85lNpzO7cmNgZlZ/Mr4qd9vUoDa9X53LUrmbMci86TSdzmw4XLHpdLawNxyu2LvBIVXU+Wc1oE2rY8+qMLM7FVqEPZeIgfd1TB8cqtp6OiqPaeifrACW0qZCRuqy02Q+o8/D4QpVWIU5e47gmDNYXUBAuJ+lTm9wNglmrQOBjvor9fuov1KDxyhwg7DJLD4j4Ezxi0Sl6iwR4/Hh/OwvQ8mgEGkyn9Wgv/rnC/tw+ZOZnadANAQa5CITXqk2KL5sXmA8Iy3MASxPHXT0/ofLnxqRwUDHIpgBVoRTJFJW6Fg7R751G2yTp05uDH9WBdDs3CBZvqoeraZTxQffsdBGWfFs99vvv5EAIlizZgo4seLnkTLqr9hkuBglMVXevT4spmCWCmrczSInUsWA+juGODNIds+HJEXD4Qrt9crjqvrjHkZxZrDO44Nnjb8NIiHoEqFRzJpejxEznc7s3evDWtE2IzWrCyoCVHpFPo0fRBTYK1dX7crVVfoevc3Am52nCaaLRwB6TinMqn18R8JizgplnQKxwkeazGd29+Ydu3vzTr2G7+ye82xDpZ7NZgQWLSoiVIGv5wAFogayt93Y39i+J8G4If08hj0j5Un2nn2JirzwvkqpymwR/O1LI7t9abTQ8iLh++1Lp/8pCnmVwLuiWeFjANT3FpYmarQ2M6tYmL76PLFXnycL6zgC4x21Z6a/MLmSrhgCRi+qtuf7GOYIPEZPb9Bv/l2AUTaNKYVKYcpkIJV4sjNxrSS/7gLsa2XGEAVfZEhhYFUeY9gyUvtqiGLR1PH/LY4AMmtnyv8f+1mklfRBHEqOn62ipTDUWRSo+TzzbFak2N0oE8OYTXZYRLOuEjHVgxCGHA4nKACfsbDEe1EhBIbpE3mjx/EM6pHdyb4YLfwmiEZgQwgj5tGseCJYf24Dvm1xZTJRnwq9w1oJAxGVUXeV8ZRBmNKlXEdjqWGK1YfegPwqjCEcvcLyOhPAFG9r3Hie3UHZGBVtizftAiqPWWhmhRDvofA2CjP5CCQDy8BHfgt/HFWh0rbtsCLGQDHgpbPZ/agHM4hyTjdj1oZRBuQihi21RZUmrCVmkYB6dXGTCWS5mxmudB/5RNmlsyVnlPRGWRX2bzVUIBNGqlihDASQtbxSvcHuw1IR78S7FTJmraytp1FRVjCZEoyXyn3UU7U/JjOec+NWzPtRAeY9pkimRFREhT07F+UpjzIjqE7DorrLvM28VGo5ytLIh92NPNhalpJxjaWYatu+3vhR9KI91YWoXMZ0indQybge37NUULWDOSdGcrxT4UI8zAoZCsiqMksdFkltUoKFt5IbzzHQkXeX5bhqSaUwzzoGa1nZe2lP1ShcL/FqdAH0fqlqM6WUtaMCpcrNeEZwbepLxIDgI89/AT7DxMgIic3lAAAAAElFTkSuQmCC" + } + ] +} \ No newline at end of file diff --git a/art/blockbench/entity/rocket_t1.bbmodel b/art/blockbench/entity/rocket_t1.bbmodel index d3c1f19..b663dda 100644 --- a/art/blockbench/entity/rocket_t1.bbmodel +++ b/art/blockbench/entity/rocket_t1.bbmodel @@ -857,7 +857,7 @@ ], "textures": [ { - "path": "C:\\Users\\dario\\Documents\\Github\\nerospace\\src\\main\\resources\\assets\\nerospace\\textures\\entity\\rocket_t1.png", + "path": "/sessions/zen-dazzling-fermi/mnt/nerospace/src/main/resources/assets/nerospace/textures/entity/rocket_t1.png", "name": "rocket_t1.png", "folder": "entity", "namespace": "nerospace", diff --git a/art/blockbench/entity/rocket_t2.bbmodel b/art/blockbench/entity/rocket_t2.bbmodel index 18f01e2..0677e38 100644 --- a/art/blockbench/entity/rocket_t2.bbmodel +++ b/art/blockbench/entity/rocket_t2.bbmodel @@ -857,7 +857,7 @@ ], "textures": [ { - "path": "C:\\Users\\dario\\Documents\\Github\\nerospace\\src\\main\\resources\\assets\\nerospace\\textures\\entity\\rocket_t2.png", + "path": "/sessions/zen-dazzling-fermi/mnt/nerospace/src/main/resources/assets/nerospace/textures/entity/rocket_t2.png", "name": "rocket_t2.png", "folder": "entity", "namespace": "nerospace", diff --git a/art/blockbench/entity/rocket_t3.bbmodel b/art/blockbench/entity/rocket_t3.bbmodel index 53d33fa..9ba5f87 100644 --- a/art/blockbench/entity/rocket_t3.bbmodel +++ b/art/blockbench/entity/rocket_t3.bbmodel @@ -1377,7 +1377,7 @@ ], "textures": [ { - "path": "C:\\Users\\dario\\Documents\\Github\\nerospace\\src\\main\\resources\\assets\\nerospace\\textures\\entity\\rocket_t3.png", + "path": "/sessions/zen-dazzling-fermi/mnt/nerospace/src/main/resources/assets/nerospace/textures/entity/rocket_t3.png", "name": "rocket_t3.png", "folder": "entity", "namespace": "nerospace", diff --git a/art/blockbench/entity/rocket_t4.bbmodel b/art/blockbench/entity/rocket_t4.bbmodel index d27b534..854540b 100644 --- a/art/blockbench/entity/rocket_t4.bbmodel +++ b/art/blockbench/entity/rocket_t4.bbmodel @@ -857,7 +857,7 @@ ], "textures": [ { - "path": "C:\\Users\\dario\\Documents\\Github\\nerospace\\src\\main\\resources\\assets\\nerospace\\textures\\entity\\rocket_t4.png", + "path": "/sessions/zen-dazzling-fermi/mnt/nerospace/src/main/resources/assets/nerospace/textures/entity/rocket_t4.png", "name": "rocket_t4.png", "folder": "entity", "namespace": "nerospace", diff --git a/art/blockbench/entity/ruin_warden.bbmodel b/art/blockbench/entity/ruin_warden.bbmodel new file mode 100644 index 0000000..b6926d3 --- /dev/null +++ b/art/blockbench/entity/ruin_warden.bbmodel @@ -0,0 +1,464 @@ +{ + "meta": { + "format_version": "4.10", + "model_format": "modded_entity", + "box_uv": true + }, + "name": "ruin_warden", + "model_identifier": "", + "visible_box": [ + 2, + 3, + 0 + ], + "variable_placeholders": "", + "variable_placeholder_buttons": [], + "timeline_setups": [], + "unhandled_root_fields": {}, + "resolution": { + "width": 64, + "height": 64 + }, + "elements": [ + { + "name": "head", + "box_uv": true, + "uv_offset": [ + 0, + 28 + ], + "rescale": false, + "locked": false, + "render_order": "default", + "allow_mirror_modeling": true, + "from": [ + -5, + 24, + -5 + ], + "to": [ + 5, + 34, + 5 + ], + "autouv": 0, + "color": 0, + "origin": [ + 0, + 0, + 0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0, + 0 + ], + "texture": 0 + }, + "east": { + "uv": [ + 0, + 0, + 0, + 0 + ], + "texture": 0 + }, + "south": { + "uv": [ + 0, + 0, + 0, + 0 + ], + "texture": 0 + }, + "west": { + "uv": [ + 0, + 0, + 0, + 0 + ], + "texture": 0 + }, + "up": { + "uv": [ + 0, + 0, + 0, + 0 + ], + "texture": 0 + }, + "down": { + "uv": [ + 0, + 0, + 0, + 0 + ], + "texture": 0 + } + }, + "type": "cube", + "uuid": "bce86502-cbe2-4870-903c-b3a10da12c8d" + }, + { + "name": "body", + "box_uv": true, + "uv_offset": [ + 0, + 0 + ], + "rescale": false, + "locked": false, + "render_order": "default", + "allow_mirror_modeling": true, + "from": [ + -6, + 10, + -3 + ], + "to": [ + 6, + 24, + 3 + ], + "autouv": 0, + "color": 0, + "origin": [ + 0, + 0, + 0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0, + 0 + ], + "texture": 0 + }, + "east": { + "uv": [ + 0, + 0, + 0, + 0 + ], + "texture": 0 + }, + "south": { + "uv": [ + 0, + 0, + 0, + 0 + ], + "texture": 0 + }, + "west": { + "uv": [ + 0, + 0, + 0, + 0 + ], + "texture": 0 + }, + "up": { + "uv": [ + 0, + 0, + 0, + 0 + ], + "texture": 0 + }, + "down": { + "uv": [ + 0, + 0, + 0, + 0 + ], + "texture": 0 + } + }, + "type": "cube", + "uuid": "f08620c9-2438-753e-fbce-d7e2dbe13f06" + }, + { + "name": "crystal_left", + "box_uv": true, + "uv_offset": [ + 44, + 0 + ], + "rescale": false, + "locked": false, + "render_order": "default", + "allow_mirror_modeling": true, + "from": [ + -9, + 18, + -2 + ], + "to": [ + -6, + 26, + 2 + ], + "autouv": 0, + "color": 0, + "origin": [ + 0, + 0, + 0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0, + 0 + ], + "texture": 0 + }, + "east": { + "uv": [ + 0, + 0, + 0, + 0 + ], + "texture": 0 + }, + "south": { + "uv": [ + 0, + 0, + 0, + 0 + ], + "texture": 0 + }, + "west": { + "uv": [ + 0, + 0, + 0, + 0 + ], + "texture": 0 + }, + "up": { + "uv": [ + 0, + 0, + 0, + 0 + ], + "texture": 0 + }, + "down": { + "uv": [ + 0, + 0, + 0, + 0 + ], + "texture": 0 + } + }, + "type": "cube", + "uuid": "7f7babfb-2df6-ebb8-5f23-06472d61ff0a" + }, + { + "name": "crystal_right", + "box_uv": true, + "uv_offset": [ + 44, + 0 + ], + "rescale": false, + "locked": false, + "render_order": "default", + "allow_mirror_modeling": true, + "from": [ + 6, + 18, + -2 + ], + "to": [ + 9, + 26, + 2 + ], + "autouv": 0, + "color": 0, + "origin": [ + 0, + 0, + 0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0, + 0 + ], + "texture": 0 + }, + "east": { + "uv": [ + 0, + 0, + 0, + 0 + ], + "texture": 0 + }, + "south": { + "uv": [ + 0, + 0, + 0, + 0 + ], + "texture": 0 + }, + "west": { + "uv": [ + 0, + 0, + 0, + 0 + ], + "texture": 0 + }, + "up": { + "uv": [ + 0, + 0, + 0, + 0 + ], + "texture": 0 + }, + "down": { + "uv": [ + 0, + 0, + 0, + 0 + ], + "texture": 0 + } + }, + "type": "cube", + "uuid": "2292df89-52f0-af6c-3d8b-06c86a097297" + } + ], + "outliner": [ + { + "name": "head", + "origin": [ + 0, + 0, + 0 + ], + "color": 0, + "uuid": "71650d50-f619-c1f2-42e1-549b68e790ae", + "export": true, + "isOpen": false, + "visibility": true, + "autouv": 0, + "children": [ + "bce86502-cbe2-4870-903c-b3a10da12c8d" + ] + }, + { + "name": "body", + "origin": [ + 0, + 0, + 0 + ], + "color": 0, + "uuid": "2ae2f515-1b44-cd2e-1f01-4b950be72324", + "export": true, + "isOpen": false, + "visibility": true, + "autouv": 0, + "children": [ + "f08620c9-2438-753e-fbce-d7e2dbe13f06" + ] + }, + { + "name": "crystal_left", + "origin": [ + 0, + 0, + 0 + ], + "color": 0, + "uuid": "86e6acf1-091b-8892-97c4-01e99d8bb2d3", + "export": true, + "isOpen": false, + "visibility": true, + "autouv": 0, + "children": [ + "7f7babfb-2df6-ebb8-5f23-06472d61ff0a" + ] + }, + { + "name": "crystal_right", + "origin": [ + 0, + 0, + 0 + ], + "color": 0, + "uuid": "9d6eeeb9-678b-c713-7372-04d123f2c639", + "export": true, + "isOpen": false, + "visibility": true, + "autouv": 0, + "children": [ + "2292df89-52f0-af6c-3d8b-06c86a097297" + ] + } + ], + "textures": [ + { + "path": "C:\\Users\\dario\\Documents\\Github\\nerospace\\src\\main\\resources\\assets\\nerospace\\textures\\entity\\ruin_warden.png", + "name": "ruin_warden.png", + "folder": "entity", + "namespace": "nerospace", + "id": "0", + "particle": true, + "render_mode": "default", + "render_sides": "auto", + "frame_time": 1, + "frame_order_type": "loop", + "frame_order": "", + "frame_interpolate": false, + "visible": true, + "mode": "bitmap", + "saved": true, + "uuid": "b60548c7-0ca7-a14f-d059-b516d01dac2e", + "relative_path": "../../../src/main/resources/assets/nerospace/textures/entity/ruin_warden.png", + "source": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAKk0lEQVR4nI2brW5jyxKFy4YXGRgFDIk0KBo0IaP9DDMkIDrKAxhGQQH3AYKioJHxUUBAiN9hs7CrwBNqZOA38AVzVvvba1dtpyXL9nb/VFVXr1pd3Z79+PbzEFZ2++3g+3Jxlj6/7V7iqb+OiIjX+7e4eriM5eIsdvtt+y1rq7rq4/n9rtVRfZdH9dSPxvHPGpdjqM/1atOe6fnclXLFqcDr/dugQyl/273Eav1roKiEcIMtF2fNUMvFWTz117Hbb5uSu/02bi4e4+bisT13I0phGotjLRdnsVr/amOoH4172720+rOvX74fZK3b7mWgGJWVAP6d9fR79a6yXm2awbKSKa0xOIuZF6jdzcVj85hK5oiI+W330izTdefNbSsB9Gy92pTKuzL+/erhss0KZ1p9ctZU5B0+s5pJepDa3Vw8lkZWmX398v3AQbmWqQC/c+1LOH5nydpJaI5zc/EYXXc+WqOf8apqkqp2IwO83r9F338MXEYVHUzczSrFKYxKBq7uwhXYLRdnbbzX+7dYrX9NgjWNLD1odPU5+/Ht5yFD04gYIDkNQyEl2PP73Qhlp4yQIXdm5Mxw3q/G93EqTxkY4OuX7wefCe/IXdwNlYU+zVYVQl2ZLARWAFcpV8mVLQG1m1N470CNnt/vUmB0gVVvt982g3GW1Qc/u8J8VZxA0YC/CwCp/M3F40AngSKfzSm8W56oKsRWB/rNjUYkZ1G8j4jouvOBQFRW75lCVF5jKYo8v9+1MK4+HEu4nDX+7OuX74fMxTWAo3IGfKdQOZsZGaXiGYzjFRax3HYvoyhSjU/dBiA4Fc68Q0YOxlt5UkVYMsF8HasQR04Zle1P4YHKbr/9Q4XpMnRBuZTWLMvVw2Uz1lN/Hc/vdwNjiLBoIAnh/ZDIsMitXfDX+7dRfxpT8np/GbapzLn2CD5P/XVTkGuJg2ud3nYvbS2S25NLqBC8los/+wIHNAcvjav6VJTyEcc4eZTZcWdOKspI8Hr/NprB1/u39lxrVMuG3sN3nxVfo9pIuSdyUjTTGVi6cjIm8cVxRc/Wq03M/vf7cKjopwYQ86LlnHx8lnpWoCfMkGB9/9GMwX3HqU0UDcvdqgziwN5AsGJjFaD4gJnB3JDOIKdivQvO6ON9qmR5AG/nbWY/vv08VGjrxnABuY2mYpzNiCM34LOIGITDLIFSsT0qpfDnHkpZq+8tCkgAV9xnRS+BWt9/NKbolmWbiD+uSzAUaGkZZeE3A0GCncbOlCdpW682A1lI1ub6UMV/31MTiYkBy8VZY3h0QdXf7bejHZzYpTI07oE0GFkePcGNRUVV3PP6/qPVmykn6I00mErlxmqbuXZEpLiiOtkOTorTWFNs0RmsnmVLSs80eevV5pgQ8X24BKlyARmAOWC6l2RGZnF26GDoQM0+5epaDlWuwpnsnIL6RkahSOzLWdtuvx1sSDIQzSLFcnE2SmJ6W/cirfdMIfXLVJt4CZcOMerq4TKe3+/+GEAdM8YSF/TcQ5u3kTGl3NRmh+OQgbrSbOsG8mekyyrP73cDeXzcth0mG3SF5BmceVJhGdD3AZxV9iFApDASPgM7Kqd3Ri56C5mlXlcPl2WCdE7F5GZya31nPt0ByPftT/31yGBZqoxK0kC+ARK1VbRg4ewyL+DLUFyB4+m9JUXp3k5/qbDPTgZaVFCDOYARsJzgOMHySarAlECeJU4J6uqrRQE+ZMg7lQ/MKLR/piJyxYwSkxm6AX3/4SGyYqtTcr3evx33AlOzWoXALK67caoUt4ovDw+dpxSqFHRO4LI1EPT9MXdeGQj5GlJ56q8bbjDk0No0HAGKYzB3yPaskxEvjZvlEtSnwi+9bc7Oic5Ecb44sH4XQLnwPGZTZCBHr4zqbsrnGRhqXM04AZX6sYi2z37/9c9BuynfFWZMTx0KxDLXz1zyVL6RZCw7mqMi1VL1vEXWzp/Nn/rrxvg8l6/P2ewrtioPmMVrhjE/dPXPGivba8hAmRw0iJgglaS3uNft9tsxCLJkqJoBUMSQF6hwxrN9BfvIUF2KfzZTzcKsteSjTAMm6Bcf9FkXFSpB+Z0ZYiZUZRDlDirGp2duGClAElYBLX+jTCJIXXfe+pHXzn7/9c+BvJs4kJEfN5DHbArks8m1yisyEeNTHLVzQCQ38RDMsSoSFXEMvbfdy5gI+UCMydlAWbtK8cwobjw/UHFglmvzFKgiZ5KnAseIf88FHFQEiixEe6G6K+AXHqhUtra9fw/DfpgpoHvqrwfnA053la6XgbK7BOp3cDQmwaaYGN2InVe3sjLLS3DNou871JbLJDsbzJZHpuTUEprxmlxFezkj2SFqJoTTT25OqpRXFoX8noFPCA9Fq/A49T6XS3vhc+77id5//3czaPuf5RGteU4o112vNoMrdVReRtIzCcgtt05+aNSI49Jj9jfDmOx9cEnKj6QjYjSLdHOyNs0sBXLS4p6VcQjm+P0YvXL/bAyfVAIj+cEoK5y5NS9QZaVyz0yAKkJk7K8iS3xGSv7ZC1SUZ7QEKiXXq80okcmUVEZBGWEIrNxxunDcGFXew349wZGl2jJv0Wao7QYlsLMxZU+vHi6j7z/S0OOpb61d1vM+I6Idq9MoGaFyY/mJdsRxO842wgyNJRzhGCkRiji6vhSsIoO+ExM83FB4KvfZC5b6XkUpp9OZvL40JGOKAdn6qZhbtWmpcMHpMME3K1M4MEWFVZwF6rswIw2D3LD4+mG4YV2FSGeW6p/PGAq5XKr0O/sig5SLd935SEZiBDd1Wn6KOqObohxsKpxkYTCrr36rJSSlPhtlTnmqDBMxTryqjiLHbv9vTpAN3SN40iOerQ6pPI2mwms2zu1d6GqJqX+9/HiOs03ewG00ddnth/uNOdctmZ5ycdp4ZCVbkzQoz+rUl7NLMUwWhkltpPTiqZOUlmGoy3q1GS0peq7azSMpjKfLxdmA4fEgVIow1mb5QQqs96lrK6znx1yZN0g2tRHQ8SofPU2hPQJ3hTMFMnam4yhfZxX3Vuz1+KsbZ5ol/o+A23FfNjSo7z411mr9axD/2ZY8Zrkorss7aHiYIWd3DsDdWbZFpeueyvWxDcfxU2qXrwJjFW75B0nRiBgMwMEzoVgycuT13Hgcz5OWFJJHdS5T5qXZhGbX+iKMCfImRsQ4TzcVLlX45wlPhkyFLmdo+swcgpaG7xiz+8nUiZlhN0JJhd1qNxePI7evlMtCGxXy4svIZZi6wufjZPTXD29YZ06U98J19fx+125XVYcUWWgi8Mh4+l2MUooqxKn+erUZ5QKcafrd44wv+G02jb1c4C8z6oz8PCJfk1mpZoe/0/LuMf67ewv7z/515uQt+09TJkO7IeJnclScjRjyfE+QzZDquSEq5TUml4K7bvYbZcgOaZ1ryDADDJhCdhfCk5Vcx33/cTL7U4WxTCkK71dys1uiqp/9mYPLY7Qd9vSzK6xOfMDPnBBXxQ3C9+rSdTZhp8CYIMt2c6/MwQkq3GDody4HXlJg8QwS21RkRc/JD5xtsi6fS1nKR1rNejcXj0cMkJJKXftRM4toKNdclZCo/m1CV+SmhRPBP2UwOjidVX29spmmXKTf/wf+za2F/s08FwAAAABJRU5ErkJggg==" + } + ] +} \ No newline at end of file diff --git a/build.gradle b/build.gradle index 1d65dde..0a06c10 100644 --- a/build.gradle +++ b/build.gradle @@ -412,3 +412,8 @@ tasks.register('genAssets') { } } } + + + + + diff --git a/src/generated/resources/assets/nerospace/blockstates/alien_bricks.json b/src/generated/resources/assets/nerospace/blockstates/alien_bricks.json new file mode 100644 index 0000000..22a04e0 --- /dev/null +++ b/src/generated/resources/assets/nerospace/blockstates/alien_bricks.json @@ -0,0 +1,7 @@ +{ + "variants": { + "": { + "model": "nerospace:block/alien_bricks" + } + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/nerospace/blockstates/alien_crystal_block.json b/src/generated/resources/assets/nerospace/blockstates/alien_crystal_block.json new file mode 100644 index 0000000..fa40446 --- /dev/null +++ b/src/generated/resources/assets/nerospace/blockstates/alien_crystal_block.json @@ -0,0 +1,7 @@ +{ + "variants": { + "": { + "model": "nerospace:block/alien_crystal_block" + } + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/nerospace/blockstates/alien_lamp.json b/src/generated/resources/assets/nerospace/blockstates/alien_lamp.json new file mode 100644 index 0000000..a4568ac --- /dev/null +++ b/src/generated/resources/assets/nerospace/blockstates/alien_lamp.json @@ -0,0 +1,7 @@ +{ + "variants": { + "": { + "model": "nerospace:block/alien_lamp" + } + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/nerospace/blockstates/alien_pillar.json b/src/generated/resources/assets/nerospace/blockstates/alien_pillar.json new file mode 100644 index 0000000..cae7a77 --- /dev/null +++ b/src/generated/resources/assets/nerospace/blockstates/alien_pillar.json @@ -0,0 +1,7 @@ +{ + "variants": { + "": { + "model": "nerospace:block/alien_pillar" + } + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/nerospace/blockstates/alien_tile.json b/src/generated/resources/assets/nerospace/blockstates/alien_tile.json new file mode 100644 index 0000000..589454e --- /dev/null +++ b/src/generated/resources/assets/nerospace/blockstates/alien_tile.json @@ -0,0 +1,7 @@ +{ + "variants": { + "": { + "model": "nerospace:block/alien_tile" + } + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/nerospace/blockstates/cracked_alien_bricks.json b/src/generated/resources/assets/nerospace/blockstates/cracked_alien_bricks.json new file mode 100644 index 0000000..10f7582 --- /dev/null +++ b/src/generated/resources/assets/nerospace/blockstates/cracked_alien_bricks.json @@ -0,0 +1,7 @@ +{ + "variants": { + "": { + "model": "nerospace:block/cracked_alien_bricks" + } + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/nerospace/blockstates/village_core.json b/src/generated/resources/assets/nerospace/blockstates/village_core.json new file mode 100644 index 0000000..6d2b273 --- /dev/null +++ b/src/generated/resources/assets/nerospace/blockstates/village_core.json @@ -0,0 +1,7 @@ +{ + "variants": { + "": { + "model": "nerospace:block/village_core" + } + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/nerospace/items/alien_bricks.json b/src/generated/resources/assets/nerospace/items/alien_bricks.json new file mode 100644 index 0000000..3cb1b91 --- /dev/null +++ b/src/generated/resources/assets/nerospace/items/alien_bricks.json @@ -0,0 +1,6 @@ +{ + "model": { + "type": "minecraft:model", + "model": "nerospace:block/alien_bricks" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/nerospace/items/alien_crystal_block.json b/src/generated/resources/assets/nerospace/items/alien_crystal_block.json new file mode 100644 index 0000000..44b81a1 --- /dev/null +++ b/src/generated/resources/assets/nerospace/items/alien_crystal_block.json @@ -0,0 +1,6 @@ +{ + "model": { + "type": "minecraft:model", + "model": "nerospace:block/alien_crystal_block" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/nerospace/items/alien_lamp.json b/src/generated/resources/assets/nerospace/items/alien_lamp.json new file mode 100644 index 0000000..6df16ec --- /dev/null +++ b/src/generated/resources/assets/nerospace/items/alien_lamp.json @@ -0,0 +1,6 @@ +{ + "model": { + "type": "minecraft:model", + "model": "nerospace:block/alien_lamp" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/nerospace/items/alien_pillar.json b/src/generated/resources/assets/nerospace/items/alien_pillar.json new file mode 100644 index 0000000..8437383 --- /dev/null +++ b/src/generated/resources/assets/nerospace/items/alien_pillar.json @@ -0,0 +1,6 @@ +{ + "model": { + "type": "minecraft:model", + "model": "nerospace:block/alien_pillar" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/nerospace/items/alien_tile.json b/src/generated/resources/assets/nerospace/items/alien_tile.json new file mode 100644 index 0000000..b9cca54 --- /dev/null +++ b/src/generated/resources/assets/nerospace/items/alien_tile.json @@ -0,0 +1,6 @@ +{ + "model": { + "type": "minecraft:model", + "model": "nerospace:block/alien_tile" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/nerospace/items/alien_villager_spawn_egg.json b/src/generated/resources/assets/nerospace/items/alien_villager_spawn_egg.json new file mode 100644 index 0000000..9091e4a --- /dev/null +++ b/src/generated/resources/assets/nerospace/items/alien_villager_spawn_egg.json @@ -0,0 +1,6 @@ +{ + "model": { + "type": "minecraft:model", + "model": "nerospace:item/alien_villager_spawn_egg" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/nerospace/items/cracked_alien_bricks.json b/src/generated/resources/assets/nerospace/items/cracked_alien_bricks.json new file mode 100644 index 0000000..6b48dec --- /dev/null +++ b/src/generated/resources/assets/nerospace/items/cracked_alien_bricks.json @@ -0,0 +1,6 @@ +{ + "model": { + "type": "minecraft:model", + "model": "nerospace:block/cracked_alien_bricks" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/nerospace/items/grav_striders.json b/src/generated/resources/assets/nerospace/items/grav_striders.json new file mode 100644 index 0000000..852f036 --- /dev/null +++ b/src/generated/resources/assets/nerospace/items/grav_striders.json @@ -0,0 +1,6 @@ +{ + "model": { + "type": "minecraft:model", + "model": "nerospace:item/grav_striders" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/nerospace/items/village_core.json b/src/generated/resources/assets/nerospace/items/village_core.json new file mode 100644 index 0000000..8d4c932 --- /dev/null +++ b/src/generated/resources/assets/nerospace/items/village_core.json @@ -0,0 +1,6 @@ +{ + "model": { + "type": "minecraft:model", + "model": "nerospace:block/village_core" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/nerospace/items/xertz_resonator.json b/src/generated/resources/assets/nerospace/items/xertz_resonator.json new file mode 100644 index 0000000..dee4024 --- /dev/null +++ b/src/generated/resources/assets/nerospace/items/xertz_resonator.json @@ -0,0 +1,6 @@ +{ + "model": { + "type": "minecraft:model", + "model": "nerospace:item/xertz_resonator" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/nerospace/lang/en_us.json b/src/generated/resources/assets/nerospace/lang/en_us.json index b45a256..c489246 100644 --- a/src/generated/resources/assets/nerospace/lang/en_us.json +++ b/src/generated/resources/assets/nerospace/lang/en_us.json @@ -1,9 +1,15 @@ { + "block.nerospace.alien_bricks": "Alien Bricks", + "block.nerospace.alien_crystal_block": "Alien Crystal Block", + "block.nerospace.alien_lamp": "Alien Lamp", + "block.nerospace.alien_pillar": "Alien Pillar", + "block.nerospace.alien_tile": "Alien Tile", "block.nerospace.battery": "Battery", "block.nerospace.battery.readout": "Battery: %s / %s FE", "block.nerospace.cindrite_block": "Block of Cindrite", "block.nerospace.cindrite_ore": "Cindrite Ore", "block.nerospace.combustion_generator": "Combustion Generator", + "block.nerospace.cracked_alien_bricks": "Cracked Alien Bricks", "block.nerospace.creative_battery": "Creative Battery", "block.nerospace.creative_battery.readout": "Creative Battery: endless energy", "block.nerospace.creative_fluid_tank": "Creative Fluid Tank", @@ -75,6 +81,7 @@ "block.nerospace.universal_pipe.items": "Items in transit: %s", "block.nerospace.universal_pipe.no_upgrades": "No upgrades installed", "block.nerospace.universal_pipe.upgrades_removed": "Upgrades popped out", + "block.nerospace.village_core": "Village Core", "block.nerospace.xertz_quartz_ore": "Xertz Quartz Ore", "container.nerospace.combustion_generator": "Combustion Generator", "container.nerospace.fuel_refinery": "Fuel Refinery", @@ -89,6 +96,7 @@ "container.nerospace.star_guide": "Star Guide", "container.nerospace.terraform_monitor": "Terraform Monitor", "container.nerospace.terraformer": "Terraformer", + "entity.nerospace.alien_villager": "Alien Villager", "entity.nerospace.cinder_stalker": "Cinder Stalker", "entity.nerospace.ember_strutter": "Ember Strutter", "entity.nerospace.falling_meteor": "Meteor", @@ -249,6 +257,7 @@ "item.nerospace.alien_core": "Alien Core", "item.nerospace.alien_fragment": "Alien Fragment", "item.nerospace.alien_tech_scrap": "Alien Tech Scrap", + "item.nerospace.alien_villager_spawn_egg": "Alien Villager Spawn Egg", "item.nerospace.capacity_upgrade": "Capacity Upgrade", "item.nerospace.cindara_compass": "Cindara Compass", "item.nerospace.cinder_stalker_spawn_egg": "Cinder Stalker Spawn Egg", @@ -265,6 +274,7 @@ "item.nerospace.frost_strider_spawn_egg": "Frost Strider Spawn Egg", "item.nerospace.glacira_compass": "Glacira Compass", "item.nerospace.glacite": "Glacite", + "item.nerospace.grav_striders": "Grav Striders", "item.nerospace.greenling_spawn_egg": "Greenling Spawn Egg", "item.nerospace.greenxertz_compass": "Greenxertz Compass", "item.nerospace.greenxertz_navigator": "Greenxertz Navigator", @@ -330,6 +340,7 @@ "item.nerospace.strutter_drumstick": "Strutter Drumstick", "item.nerospace.woolly_drift_spawn_egg": "Woolly Drift Spawn Egg", "item.nerospace.xertz_quartz": "Xertz Quartz", + "item.nerospace.xertz_resonator": "Xertz Resonator", "item.nerospace.xertz_stalker_spawn_egg": "Xertz Stalker Spawn Egg", "itemGroup.nerospace": "Nerospace", "jei.nerospace.category.combustion": "Combustion Fuel", @@ -342,6 +353,9 @@ "message.nerospace.sentry_test.disabled": "Telemetry is disabled (telemetryEnabled=false) — nothing sent.", "message.nerospace.sentry_test.sent": "Sentry test event dispatched — check your Sentry dashboard.", "message.nerospace.star_guide.empty": "Place a Star Guide Book on the pedestal to open the guide", + "message.nerospace.village_core.claimed": "You claim this Village Core. The aliens take note.", + "message.nerospace.village_core.owned": "This Village Core belongs to %s.", + "message.nerospace.village_core.status_self": "This is your Village Core.", "message.nerospace.welcome.intro": "Welcome! Craft a Star Guide Book (book + raw nerosium) to chart your journey to the stars.", "message.nerospace.welcome.link": "Feedback and bug reports are welcome: ", "pipe.nerospace.face.down": "Bottom", diff --git a/src/generated/resources/assets/nerospace/models/block/alien_bricks.json b/src/generated/resources/assets/nerospace/models/block/alien_bricks.json new file mode 100644 index 0000000..5216746 --- /dev/null +++ b/src/generated/resources/assets/nerospace/models/block/alien_bricks.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:block/cube_all", + "textures": { + "all": "nerospace:block/alien_bricks" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/nerospace/models/block/alien_crystal_block.json b/src/generated/resources/assets/nerospace/models/block/alien_crystal_block.json new file mode 100644 index 0000000..fda6c65 --- /dev/null +++ b/src/generated/resources/assets/nerospace/models/block/alien_crystal_block.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:block/cube_all", + "textures": { + "all": "nerospace:block/alien_crystal_block" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/nerospace/models/block/alien_lamp.json b/src/generated/resources/assets/nerospace/models/block/alien_lamp.json new file mode 100644 index 0000000..fc53b5b --- /dev/null +++ b/src/generated/resources/assets/nerospace/models/block/alien_lamp.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:block/cube_all", + "textures": { + "all": "nerospace:block/alien_lamp" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/nerospace/models/block/alien_pillar.json b/src/generated/resources/assets/nerospace/models/block/alien_pillar.json new file mode 100644 index 0000000..be791e6 --- /dev/null +++ b/src/generated/resources/assets/nerospace/models/block/alien_pillar.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:block/cube_all", + "textures": { + "all": "nerospace:block/alien_pillar" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/nerospace/models/block/alien_tile.json b/src/generated/resources/assets/nerospace/models/block/alien_tile.json new file mode 100644 index 0000000..5170045 --- /dev/null +++ b/src/generated/resources/assets/nerospace/models/block/alien_tile.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:block/cube_all", + "textures": { + "all": "nerospace:block/alien_tile" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/nerospace/models/block/cracked_alien_bricks.json b/src/generated/resources/assets/nerospace/models/block/cracked_alien_bricks.json new file mode 100644 index 0000000..dc528f3 --- /dev/null +++ b/src/generated/resources/assets/nerospace/models/block/cracked_alien_bricks.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:block/cube_all", + "textures": { + "all": "nerospace:block/cracked_alien_bricks" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/nerospace/models/block/village_core.json b/src/generated/resources/assets/nerospace/models/block/village_core.json new file mode 100644 index 0000000..d02431a --- /dev/null +++ b/src/generated/resources/assets/nerospace/models/block/village_core.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:block/cube_all", + "textures": { + "all": "nerospace:block/village_core" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/nerospace/models/item/alien_villager_spawn_egg.json b/src/generated/resources/assets/nerospace/models/item/alien_villager_spawn_egg.json new file mode 100644 index 0000000..48a55ab --- /dev/null +++ b/src/generated/resources/assets/nerospace/models/item/alien_villager_spawn_egg.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:item/generated", + "textures": { + "layer0": "nerospace:item/alien_villager_spawn_egg" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/nerospace/models/item/grav_striders.json b/src/generated/resources/assets/nerospace/models/item/grav_striders.json new file mode 100644 index 0000000..aba7ff4 --- /dev/null +++ b/src/generated/resources/assets/nerospace/models/item/grav_striders.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:item/generated", + "textures": { + "layer0": "nerospace:item/grav_striders" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/nerospace/models/item/xertz_resonator.json b/src/generated/resources/assets/nerospace/models/item/xertz_resonator.json new file mode 100644 index 0000000..040d1e1 --- /dev/null +++ b/src/generated/resources/assets/nerospace/models/item/xertz_resonator.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:item/generated", + "textures": { + "layer0": "nerospace:item/xertz_resonator" + } +} \ No newline at end of file diff --git a/src/generated/resources/data/minecraft/tags/block/mineable/pickaxe.json b/src/generated/resources/data/minecraft/tags/block/mineable/pickaxe.json index c803bf4..96d76d9 100644 --- a/src/generated/resources/data/minecraft/tags/block/mineable/pickaxe.json +++ b/src/generated/resources/data/minecraft/tags/block/mineable/pickaxe.json @@ -31,6 +31,13 @@ "nerospace:gas_tank", "nerospace:item_store", "nerospace:star_guide", + "nerospace:village_core", + "nerospace:alien_bricks", + "nerospace:cracked_alien_bricks", + "nerospace:alien_tile", + "nerospace:alien_pillar", + "nerospace:alien_lamp", + "nerospace:alien_crystal_block", "nerospace:launch_gantry", "nerospace:quarry_controller", "nerospace:quarry_landmark", diff --git a/src/generated/resources/data/nerospace/loot_table/blocks/alien_bricks.json b/src/generated/resources/data/nerospace/loot_table/blocks/alien_bricks.json new file mode 100644 index 0000000..bacbc6c --- /dev/null +++ b/src/generated/resources/data/nerospace/loot_table/blocks/alien_bricks.json @@ -0,0 +1,21 @@ +{ + "type": "minecraft:block", + "pools": [ + { + "bonus_rolls": 0.0, + "conditions": [ + { + "condition": "minecraft:survives_explosion" + } + ], + "entries": [ + { + "type": "minecraft:item", + "name": "nerospace:alien_bricks" + } + ], + "rolls": 1.0 + } + ], + "random_sequence": "nerospace:blocks/alien_bricks" +} \ No newline at end of file diff --git a/src/generated/resources/data/nerospace/loot_table/blocks/alien_crystal_block.json b/src/generated/resources/data/nerospace/loot_table/blocks/alien_crystal_block.json new file mode 100644 index 0000000..065f7c2 --- /dev/null +++ b/src/generated/resources/data/nerospace/loot_table/blocks/alien_crystal_block.json @@ -0,0 +1,21 @@ +{ + "type": "minecraft:block", + "pools": [ + { + "bonus_rolls": 0.0, + "conditions": [ + { + "condition": "minecraft:survives_explosion" + } + ], + "entries": [ + { + "type": "minecraft:item", + "name": "nerospace:alien_crystal_block" + } + ], + "rolls": 1.0 + } + ], + "random_sequence": "nerospace:blocks/alien_crystal_block" +} \ No newline at end of file diff --git a/src/generated/resources/data/nerospace/loot_table/blocks/alien_lamp.json b/src/generated/resources/data/nerospace/loot_table/blocks/alien_lamp.json new file mode 100644 index 0000000..2a5f660 --- /dev/null +++ b/src/generated/resources/data/nerospace/loot_table/blocks/alien_lamp.json @@ -0,0 +1,21 @@ +{ + "type": "minecraft:block", + "pools": [ + { + "bonus_rolls": 0.0, + "conditions": [ + { + "condition": "minecraft:survives_explosion" + } + ], + "entries": [ + { + "type": "minecraft:item", + "name": "nerospace:alien_lamp" + } + ], + "rolls": 1.0 + } + ], + "random_sequence": "nerospace:blocks/alien_lamp" +} \ No newline at end of file diff --git a/src/generated/resources/data/nerospace/loot_table/blocks/alien_pillar.json b/src/generated/resources/data/nerospace/loot_table/blocks/alien_pillar.json new file mode 100644 index 0000000..011dca5 --- /dev/null +++ b/src/generated/resources/data/nerospace/loot_table/blocks/alien_pillar.json @@ -0,0 +1,21 @@ +{ + "type": "minecraft:block", + "pools": [ + { + "bonus_rolls": 0.0, + "conditions": [ + { + "condition": "minecraft:survives_explosion" + } + ], + "entries": [ + { + "type": "minecraft:item", + "name": "nerospace:alien_pillar" + } + ], + "rolls": 1.0 + } + ], + "random_sequence": "nerospace:blocks/alien_pillar" +} \ No newline at end of file diff --git a/src/generated/resources/data/nerospace/loot_table/blocks/alien_tile.json b/src/generated/resources/data/nerospace/loot_table/blocks/alien_tile.json new file mode 100644 index 0000000..05d2c71 --- /dev/null +++ b/src/generated/resources/data/nerospace/loot_table/blocks/alien_tile.json @@ -0,0 +1,21 @@ +{ + "type": "minecraft:block", + "pools": [ + { + "bonus_rolls": 0.0, + "conditions": [ + { + "condition": "minecraft:survives_explosion" + } + ], + "entries": [ + { + "type": "minecraft:item", + "name": "nerospace:alien_tile" + } + ], + "rolls": 1.0 + } + ], + "random_sequence": "nerospace:blocks/alien_tile" +} \ No newline at end of file diff --git a/src/generated/resources/data/nerospace/loot_table/blocks/cracked_alien_bricks.json b/src/generated/resources/data/nerospace/loot_table/blocks/cracked_alien_bricks.json new file mode 100644 index 0000000..72a7a0b --- /dev/null +++ b/src/generated/resources/data/nerospace/loot_table/blocks/cracked_alien_bricks.json @@ -0,0 +1,21 @@ +{ + "type": "minecraft:block", + "pools": [ + { + "bonus_rolls": 0.0, + "conditions": [ + { + "condition": "minecraft:survives_explosion" + } + ], + "entries": [ + { + "type": "minecraft:item", + "name": "nerospace:cracked_alien_bricks" + } + ], + "rolls": 1.0 + } + ], + "random_sequence": "nerospace:blocks/cracked_alien_bricks" +} \ No newline at end of file diff --git a/src/generated/resources/data/nerospace/loot_table/blocks/village_core.json b/src/generated/resources/data/nerospace/loot_table/blocks/village_core.json new file mode 100644 index 0000000..26e0430 --- /dev/null +++ b/src/generated/resources/data/nerospace/loot_table/blocks/village_core.json @@ -0,0 +1,21 @@ +{ + "type": "minecraft:block", + "pools": [ + { + "bonus_rolls": 0.0, + "conditions": [ + { + "condition": "minecraft:survives_explosion" + } + ], + "entries": [ + { + "type": "minecraft:item", + "name": "nerospace:village_core" + } + ], + "rolls": 1.0 + } + ], + "random_sequence": "nerospace:blocks/village_core" +} \ No newline at end of file diff --git a/src/generated/resources/data/nerospace/worldgen/biome/cindara.json b/src/generated/resources/data/nerospace/worldgen/biome/cindara.json index fe0a2fa..bc6b608 100644 --- a/src/generated/resources/data/nerospace/worldgen/biome/cindara.json +++ b/src/generated/resources/data/nerospace/worldgen/biome/cindara.json @@ -25,7 +25,14 @@ "spawners": { "ambient": [], "axolotls": [], - "creature": [], + "creature": [ + { + "type": "nerospace:alien_villager", + "maxCount": 2, + "minCount": 1, + "weight": 3 + } + ], "misc": [], "monster": [ { diff --git a/src/generated/resources/data/nerospace/worldgen/biome/glacira.json b/src/generated/resources/data/nerospace/worldgen/biome/glacira.json index d575069..6719e32 100644 --- a/src/generated/resources/data/nerospace/worldgen/biome/glacira.json +++ b/src/generated/resources/data/nerospace/worldgen/biome/glacira.json @@ -25,7 +25,14 @@ "spawners": { "ambient": [], "axolotls": [], - "creature": [], + "creature": [ + { + "type": "nerospace:alien_villager", + "maxCount": 2, + "minCount": 1, + "weight": 3 + } + ], "misc": [], "monster": [ { diff --git a/src/generated/resources/data/nerospace/worldgen/biome/greenxertz.json b/src/generated/resources/data/nerospace/worldgen/biome/greenxertz.json index 68d32a1..5a13caf 100644 --- a/src/generated/resources/data/nerospace/worldgen/biome/greenxertz.json +++ b/src/generated/resources/data/nerospace/worldgen/biome/greenxertz.json @@ -15,7 +15,11 @@ [], [], [], - [], + [ + "nerospace:hamlet_placed", + "nerospace:ruin_placed", + "nerospace:mega_city_placed" + ], [], [ "nerospace:nerosteel_ore_placed", @@ -40,6 +44,12 @@ "maxCount": 3, "minCount": 1, "weight": 10 + }, + { + "type": "nerospace:alien_villager", + "maxCount": 3, + "minCount": 1, + "weight": 6 } ], "misc": [], diff --git a/src/generated/resources/data/nerospace/worldgen/biome/terraformed_meadow.json b/src/generated/resources/data/nerospace/worldgen/biome/terraformed_meadow.json index 4a5a363..d96d154 100644 --- a/src/generated/resources/data/nerospace/worldgen/biome/terraformed_meadow.json +++ b/src/generated/resources/data/nerospace/worldgen/biome/terraformed_meadow.json @@ -19,6 +19,12 @@ "maxCount": 4, "minCount": 2, "weight": 10 + }, + { + "type": "nerospace:alien_villager", + "maxCount": 2, + "minCount": 1, + "weight": 5 } ], "misc": [], diff --git a/src/generated/resources/data/nerospace/worldgen/configured_feature/hamlet.json b/src/generated/resources/data/nerospace/worldgen/configured_feature/hamlet.json new file mode 100644 index 0000000..02efc1f --- /dev/null +++ b/src/generated/resources/data/nerospace/worldgen/configured_feature/hamlet.json @@ -0,0 +1,4 @@ +{ + "type": "nerospace:hamlet", + "config": {} +} \ No newline at end of file diff --git a/src/generated/resources/data/nerospace/worldgen/configured_feature/mega_city.json b/src/generated/resources/data/nerospace/worldgen/configured_feature/mega_city.json new file mode 100644 index 0000000..574c431 --- /dev/null +++ b/src/generated/resources/data/nerospace/worldgen/configured_feature/mega_city.json @@ -0,0 +1,4 @@ +{ + "type": "nerospace:mega_city", + "config": {} +} \ No newline at end of file diff --git a/src/generated/resources/data/nerospace/worldgen/configured_feature/ruin.json b/src/generated/resources/data/nerospace/worldgen/configured_feature/ruin.json new file mode 100644 index 0000000..6ce9727 --- /dev/null +++ b/src/generated/resources/data/nerospace/worldgen/configured_feature/ruin.json @@ -0,0 +1,4 @@ +{ + "type": "nerospace:ruin", + "config": {} +} \ No newline at end of file diff --git a/src/generated/resources/data/nerospace/worldgen/placed_feature/hamlet_placed.json b/src/generated/resources/data/nerospace/worldgen/placed_feature/hamlet_placed.json new file mode 100644 index 0000000..2c74061 --- /dev/null +++ b/src/generated/resources/data/nerospace/worldgen/placed_feature/hamlet_placed.json @@ -0,0 +1,19 @@ +{ + "feature": "nerospace:hamlet", + "placement": [ + { + "type": "minecraft:count", + "count": 1 + }, + { + "type": "minecraft:in_square" + }, + { + "type": "minecraft:heightmap", + "heightmap": "WORLD_SURFACE_WG" + }, + { + "type": "minecraft:biome" + } + ] +} \ No newline at end of file diff --git a/src/generated/resources/data/nerospace/worldgen/placed_feature/mega_city_placed.json b/src/generated/resources/data/nerospace/worldgen/placed_feature/mega_city_placed.json new file mode 100644 index 0000000..bc8a2d1 --- /dev/null +++ b/src/generated/resources/data/nerospace/worldgen/placed_feature/mega_city_placed.json @@ -0,0 +1,19 @@ +{ + "feature": "nerospace:mega_city", + "placement": [ + { + "type": "minecraft:count", + "count": 1 + }, + { + "type": "minecraft:in_square" + }, + { + "type": "minecraft:heightmap", + "heightmap": "WORLD_SURFACE_WG" + }, + { + "type": "minecraft:biome" + } + ] +} \ No newline at end of file diff --git a/src/generated/resources/data/nerospace/worldgen/placed_feature/ruin_placed.json b/src/generated/resources/data/nerospace/worldgen/placed_feature/ruin_placed.json new file mode 100644 index 0000000..fdf2a53 --- /dev/null +++ b/src/generated/resources/data/nerospace/worldgen/placed_feature/ruin_placed.json @@ -0,0 +1,19 @@ +{ + "feature": "nerospace:ruin", + "placement": [ + { + "type": "minecraft:count", + "count": 1 + }, + { + "type": "minecraft:in_square" + }, + { + "type": "minecraft:heightmap", + "heightmap": "WORLD_SURFACE_WG" + }, + { + "type": "minecraft:biome" + } + ] +} \ No newline at end of file diff --git a/src/main/java/za/co/neroland/nerospace/Config.java b/src/main/java/za/co/neroland/nerospace/Config.java index fb8ed49..ba141e0 100644 --- a/src/main/java/za/co/neroland/nerospace/Config.java +++ b/src/main/java/za/co/neroland/nerospace/Config.java @@ -26,6 +26,11 @@ public class Config { "immediately on config reload). Full details: PRIVACY.md in the mod repository.") .define("telemetryEnabled", true); + /** Alien village raids: periodic hostile attacks on claimed villages at night. Opt-out here. */ + public static final ModConfigSpec.BooleanValue ALIEN_RAIDS_ENABLED = BUILDER + .comment("Whether claimed alien villages are raided by hostile mobs at night.") + .define("alienRaidsEnabled", true); + // --- Balance multipliers ------------------------------------------------- // // The base numbers live in code (Tuning.java) and are documented in wiki/Configuration.md. diff --git a/src/main/java/za/co/neroland/nerospace/Nerospace.java b/src/main/java/za/co/neroland/nerospace/Nerospace.java index 48c936c..aa24e8f 100644 --- a/src/main/java/za/co/neroland/nerospace/Nerospace.java +++ b/src/main/java/za/co/neroland/nerospace/Nerospace.java @@ -44,6 +44,7 @@ public Nerospace(IEventBus modEventBus, ModContainer modContainer) { // and blocks before items (block items reference block holders). ModFluids.register(modEventBus); ModBlocks.register(modEventBus); + za.co.neroland.nerospace.registry.ModFeatures.register(modEventBus); ModItems.register(modEventBus); ModDataComponents.register(modEventBus); ModBlockEntities.register(modEventBus); diff --git a/src/main/java/za/co/neroland/nerospace/NerospaceClient.java b/src/main/java/za/co/neroland/nerospace/NerospaceClient.java index b3b9431..4f6ee4f 100644 --- a/src/main/java/za/co/neroland/nerospace/NerospaceClient.java +++ b/src/main/java/za/co/neroland/nerospace/NerospaceClient.java @@ -31,6 +31,8 @@ import za.co.neroland.nerospace.client.ClientOxygenField; import za.co.neroland.nerospace.client.OxygenHudLayer; +import za.co.neroland.nerospace.client.AlienVillagerModel; +import za.co.neroland.nerospace.client.AlienVillagerRenderer; import za.co.neroland.nerospace.client.CinderStalkerModel; import za.co.neroland.nerospace.client.FrostStriderModel; import za.co.neroland.nerospace.client.GreenlingModel; @@ -153,6 +155,13 @@ static void onRegisterEntityRenderers(EntityRenderersEvent.RegisterRenderers eve context -> new GreenxertzCreatureRenderer(context, new GreenlingModel(context.bakeLayer(GreenlingModel.LAYER)), entityTexture("greenling"), 1.0F, 1.0F, 1.0F, 0.3F, entityGlow("greenling"))); + // Alien Villager uses its own renderer (Phase 1): per-individual palette tint + per-biome skin. + event.registerEntityRenderer(ModEntities.ALIEN_VILLAGER.get(), AlienVillagerRenderer::new); + event.registerEntityRenderer(ModEntities.RUIN_WARDEN.get(), + context -> new GreenxertzCreatureRenderer(context, + new za.co.neroland.nerospace.client.RuinWardenModel( + context.bakeLayer(za.co.neroland.nerospace.client.RuinWardenModel.LAYER)), + entityTexture("ruin_warden"), 1.4F, 1.4F, 1.4F, 0.9F, entityGlow("ruin_warden"))); event.registerEntityRenderer(ModEntities.CINDER_STALKER.get(), context -> new GreenxertzCreatureRenderer(context, new CinderStalkerModel(context.bakeLayer(CinderStalkerModel.LAYER)), @@ -204,6 +213,9 @@ static void onRegisterLayerDefinitions(EntityRenderersEvent.RegisterLayerDefinit event.registerLayerDefinition(XertzStalkerModel.LAYER, XertzStalkerModel::createBodyLayer); event.registerLayerDefinition(QuartzCrawlerModel.LAYER, QuartzCrawlerModel::createBodyLayer); event.registerLayerDefinition(GreenlingModel.LAYER, GreenlingModel::createBodyLayer); + event.registerLayerDefinition(AlienVillagerModel.LAYER, AlienVillagerModel::createBodyLayer); + event.registerLayerDefinition(za.co.neroland.nerospace.client.RuinWardenModel.LAYER, + za.co.neroland.nerospace.client.RuinWardenModel::createBodyLayer); event.registerLayerDefinition(CinderStalkerModel.LAYER, CinderStalkerModel::createBodyLayer); event.registerLayerDefinition(FrostStriderModel.LAYER, FrostStriderModel::createBodyLayer); event.registerLayerDefinition(za.co.neroland.nerospace.client.MeadowLoperModel.LAYER, @@ -360,6 +372,10 @@ static void onMeteorTrackerTick(ClientTickEvent.Post event) { return; } BlockPos target = ClientMeteorTracker.pos(); + if (target == null) { + // isPresent() was true above, but pos() is @Nullable — guard the deref explicitly. + return; + } Vec3 p = mc.player.position(); double dx = target.getX() + 0.5D - p.x; double dz = target.getZ() + 0.5D - p.z; diff --git a/src/main/java/za/co/neroland/nerospace/client/AlienVillagerModel.java b/src/main/java/za/co/neroland/nerospace/client/AlienVillagerModel.java new file mode 100644 index 0000000..71b58d2 --- /dev/null +++ b/src/main/java/za/co/neroland/nerospace/client/AlienVillagerModel.java @@ -0,0 +1,82 @@ +package za.co.neroland.nerospace.client; + +import net.minecraft.client.model.geom.ModelLayerLocation; +import net.minecraft.client.model.geom.ModelPart; +import net.minecraft.client.model.geom.PartPose; +import net.minecraft.client.model.geom.builders.CubeListBuilder; +import net.minecraft.client.model.geom.builders.LayerDefinition; +import net.minecraft.client.model.geom.builders.MeshDefinition; +import net.minecraft.client.model.geom.builders.PartDefinition; +import net.minecraft.core.Direction; +import net.minecraft.resources.Identifier; +import net.minecraft.util.Mth; + +import za.co.neroland.nerospace.Nerospace; + +/** + * Alien Villager (Phase 0) — an upright humanoid: a domed head, a robed torso, two crystalline + * shoulder growths (the Greenxertz silhouette cue), and hip/shoulder-pivoted arms and legs that + * stride as it walks. Idle: a slow head sway and a faint wobble of the shoulder crystals. + * + *

Geometry split (model_sync rules): the static torso/head/crystals live in the marker block + * (one cube per bone, no rotation), so they round-trip to {@code alien_villager.bbmodel}. The + * pivoted limbs sit OUTSIDE the block (Java-authoritative) because their pivots are at the joint, + * which the marker form can't express. + */ +public class AlienVillagerModel extends GreenxertzMobModel { + + public static final ModelLayerLocation LAYER = new ModelLayerLocation( + Identifier.fromNamespaceAndPath(Nerospace.MODID, "alien_villager"), "main"); + + @SuppressWarnings("this-escape") // idiomatic Minecraft constructor wiring + public AlienVillagerModel(ModelPart root) { + super(root); + // Opposed stride: legs lead, arms counter-swing. + swingLimb("leg_left", 0F, 0.6F); + swingLimb("leg_right", Mth.PI, 0.6F); + swingLimb("arm_left", Mth.PI, 0.4F); + swingLimb("arm_right", 0F, 0.4F); + // Idle: a slow, calm head sway and a faint out-of-phase wobble of the shoulder crystals. + ambient("head", Direction.Axis.Y, 0.05F, 0F, 0.06F); + ambient("crystal_left", Direction.Axis.Z, 0.08F, 0F, 0.04F); + ambient("crystal_right", Direction.Axis.Z, 0.08F, 1.4F, 0.04F); + } + + public static LayerDefinition createBodyLayer() { + MeshDefinition mesh = new MeshDefinition(); + PartDefinition root = mesh.getRoot(); + + // model_sync:begin + root.addOrReplaceChild("head", + CubeListBuilder.create().texOffs(0, 28).addBox(-4F, -6F, -4F, 8F, 8F, 8F), + PartPose.offset(0.0F, 0.0F, 0.0F)); + root.addOrReplaceChild("body", + CubeListBuilder.create().texOffs(0, 0).addBox(-4F, 2F, -2F, 8F, 12F, 4F), + PartPose.offset(0.0F, 0.0F, 0.0F)); + root.addOrReplaceChild("crystal_left", + CubeListBuilder.create().texOffs(44, 0).addBox(-6F, 0F, -1F, 2F, 5F, 2F), + PartPose.offset(0.0F, 0.0F, 0.0F)); + root.addOrReplaceChild("crystal_right", + CubeListBuilder.create().texOffs(44, 0).addBox(4F, 0F, -1F, 2F, 5F, 2F), + PartPose.offset(0.0F, 0.0F, 0.0F)); + // model_sync:end (pivoted limbs below are Java-authoritative — joints, not origin-anchored) + + // Shoulder-pivoted arms (pivot at the shoulder, cubes hang below). + root.addOrReplaceChild("arm_left", + CubeListBuilder.create().texOffs(44, 8).addBox(-1F, 0F, -2F, 2F, 11F, 4F), + PartPose.offset(-5F, 3F, 0F)); + root.addOrReplaceChild("arm_right", + CubeListBuilder.create().texOffs(44, 8).addBox(-1F, 0F, -2F, 2F, 11F, 4F), + PartPose.offset(5F, 3F, 0F)); + + // Hip-pivoted legs (pivot at the hip; feet rest on the ground at java y=24). + root.addOrReplaceChild("leg_left", + CubeListBuilder.create().texOffs(44, 24).addBox(-1.5F, 0F, -2F, 3F, 10F, 4F), + PartPose.offset(-2F, 14F, 0F)); + root.addOrReplaceChild("leg_right", + CubeListBuilder.create().texOffs(44, 24).addBox(-1.5F, 0F, -2F, 3F, 10F, 4F), + PartPose.offset(2F, 14F, 0F)); + + return LayerDefinition.create(mesh, 64, 64); + } +} diff --git a/src/main/java/za/co/neroland/nerospace/client/AlienVillagerRenderState.java b/src/main/java/za/co/neroland/nerospace/client/AlienVillagerRenderState.java new file mode 100644 index 0000000..917f25d --- /dev/null +++ b/src/main/java/za/co/neroland/nerospace/client/AlienVillagerRenderState.java @@ -0,0 +1,19 @@ +package za.co.neroland.nerospace.client; + +import net.minecraft.client.renderer.entity.state.LivingEntityRenderState; + +import za.co.neroland.nerospace.entity.AlienVillager; + +/** + * Render state for the Alien Villager. Carries the per-individual variant pulled off the entity in + * AlienVillagerRenderer#extractRenderState: the colour seed drives the palette tint, the planet + + * home biome select the base/accessory texture, and the display tier (Phase 2) warms the tint as the + * villager grows to trust players. + */ +public class AlienVillagerRenderState extends LivingEntityRenderState { + + public int colorSeed; + public String biomeId = ""; + public AlienVillager.Planet planet = AlienVillager.Planet.GREENXERTZ; + public int displayTier; +} diff --git a/src/main/java/za/co/neroland/nerospace/client/AlienVillagerRenderer.java b/src/main/java/za/co/neroland/nerospace/client/AlienVillagerRenderer.java new file mode 100644 index 0000000..d709191 --- /dev/null +++ b/src/main/java/za/co/neroland/nerospace/client/AlienVillagerRenderer.java @@ -0,0 +1,73 @@ +package za.co.neroland.nerospace.client; + +import java.util.Random; + +import net.minecraft.client.model.EntityModel; +import net.minecraft.client.renderer.entity.EntityRendererProvider; +import net.minecraft.client.renderer.entity.MobRenderer; +import net.minecraft.resources.Identifier; + +import za.co.neroland.nerospace.Nerospace; +import za.co.neroland.nerospace.entity.AlienVillager; + +/** + * Renderer for the Alien Villager. Per-individual palette tint (getModelTint), per-planet + per-biome + * skin (getTextureLocation: Greenxertz green/steel with a lighter meadow set, Cindara ember/red, + * Glacira frost/pale), a trust-warmed mood tint, and the emissive eye/crystal glow. + */ +public class AlienVillagerRenderer + extends MobRenderer> { + + private static Identifier tex(String n) { + return Identifier.fromNamespaceAndPath(Nerospace.MODID, "textures/entity/" + n + ".png"); + } + + private static final Identifier BASE = tex("alien_villager"); + private static final Identifier MEADOW = tex("alien_villager_meadow"); + private static final Identifier CINDARA = tex("alien_villager_cindara"); + private static final Identifier GLACIRA = tex("alien_villager_glacira"); + private static final Identifier GLOW = tex("alien_villager_glow"); + + @SuppressWarnings("this-escape") + public AlienVillagerRenderer(EntityRendererProvider.Context context) { + super(context, new AlienVillagerModel(context.bakeLayer(AlienVillagerModel.LAYER)), 0.4F); + this.addLayer(new GlowEyesLayer(this, GLOW)); + } + + @Override + public AlienVillagerRenderState createRenderState() { + return new AlienVillagerRenderState(); + } + + @Override + public void extractRenderState(AlienVillager entity, AlienVillagerRenderState state, float partialTick) { + super.extractRenderState(entity, state, partialTick); + state.colorSeed = entity.getColorSeed(); + state.biomeId = entity.getBiomeId(); + state.planet = entity.getPlanet(); + state.displayTier = entity.getDisplayTier(); + } + + @Override + public Identifier getTextureLocation(AlienVillagerRenderState state) { + return switch (state.planet) { + case CINDARA -> CINDARA; + case GLACIRA -> GLACIRA; + case GREENXERTZ -> (state.biomeId != null && state.biomeId.contains("meadow")) ? MEADOW : BASE; + }; + } + + @Override + protected int getModelTint(AlienVillagerRenderState state) { + Random rnd = new Random(state.colorSeed); + int warmth = Math.max(0, Math.min(5, state.displayTier)) * 4; + int r = clamp(200 + rnd.nextInt(56) + warmth); + int g = clamp(216 + rnd.nextInt(40) + warmth / 2); + int b = clamp(190 + rnd.nextInt(56)); + return 0xFF000000 | (r << 16) | (g << 8) | b; + } + + private static int clamp(int v) { + return Math.max(0, Math.min(255, v)); + } +} diff --git a/src/main/java/za/co/neroland/nerospace/client/CinderStalkerModel.java b/src/main/java/za/co/neroland/nerospace/client/CinderStalkerModel.java index 7ebcefc..d3adca9 100644 --- a/src/main/java/za/co/neroland/nerospace/client/CinderStalkerModel.java +++ b/src/main/java/za/co/neroland/nerospace/client/CinderStalkerModel.java @@ -2,6 +2,7 @@ import net.minecraft.client.model.geom.ModelLayerLocation; import net.minecraft.client.model.geom.ModelPart; +import net.minecraft.client.renderer.entity.state.LivingEntityRenderState; import net.minecraft.client.model.geom.PartPose; import net.minecraft.client.model.geom.builders.CubeListBuilder; import net.minecraft.client.model.geom.builders.LayerDefinition; @@ -19,7 +20,7 @@ * Idle (10f): its signature is slow, heavy breathing — a deep bob at roughly half the default rate — * under a ponderous side-to-side sweep of the big browed head. */ -public class CinderStalkerModel extends GreenxertzMobModel { +public class CinderStalkerModel extends GreenxertzMobModel { public static final ModelLayerLocation LAYER = new ModelLayerLocation( Identifier.fromNamespaceAndPath(Nerospace.MODID, "cinder_stalker"), "main"); diff --git a/src/main/java/za/co/neroland/nerospace/client/EmberStrutterModel.java b/src/main/java/za/co/neroland/nerospace/client/EmberStrutterModel.java index a50f379..d5c013e 100644 --- a/src/main/java/za/co/neroland/nerospace/client/EmberStrutterModel.java +++ b/src/main/java/za/co/neroland/nerospace/client/EmberStrutterModel.java @@ -2,6 +2,7 @@ import net.minecraft.client.model.geom.ModelLayerLocation; import net.minecraft.client.model.geom.ModelPart; +import net.minecraft.client.renderer.entity.state.LivingEntityRenderState; import net.minecraft.client.model.geom.PartPose; import net.minecraft.client.model.geom.builders.CubeListBuilder; import net.minecraft.client.model.geom.builders.LayerDefinition; @@ -19,7 +20,7 @@ * beak, stubby wing slabs and a raked tail fan. Idle: rapid bird breathing, sharp pecky head bobs * and nervous wing flicks. */ -public class EmberStrutterModel extends GreenxertzMobModel { +public class EmberStrutterModel extends GreenxertzMobModel { public static final ModelLayerLocation LAYER = new ModelLayerLocation( Identifier.fromNamespaceAndPath(Nerospace.MODID, "ember_strutter"), "main"); diff --git a/src/main/java/za/co/neroland/nerospace/client/FrostStriderModel.java b/src/main/java/za/co/neroland/nerospace/client/FrostStriderModel.java index 6a6f0d0..31bca49 100644 --- a/src/main/java/za/co/neroland/nerospace/client/FrostStriderModel.java +++ b/src/main/java/za/co/neroland/nerospace/client/FrostStriderModel.java @@ -2,6 +2,7 @@ import net.minecraft.client.model.geom.ModelLayerLocation; import net.minecraft.client.model.geom.ModelPart; +import net.minecraft.client.renderer.entity.state.LivingEntityRenderState; import net.minecraft.client.model.geom.PartPose; import net.minecraft.client.model.geom.builders.CubeListBuilder; import net.minecraft.client.model.geom.builders.LayerDefinition; @@ -21,7 +22,7 @@ * heavy quadruped): this one is all legs. Idle: a quick, shallow, bird-like breath under a wary * side-to-side head scan, with a faint shimmer-tremble in the back shards. */ -public class FrostStriderModel extends GreenxertzMobModel { +public class FrostStriderModel extends GreenxertzMobModel { public static final ModelLayerLocation LAYER = new ModelLayerLocation( Identifier.fromNamespaceAndPath(Nerospace.MODID, "frost_strider"), "main"); diff --git a/src/main/java/za/co/neroland/nerospace/client/GlowEyesLayer.java b/src/main/java/za/co/neroland/nerospace/client/GlowEyesLayer.java index 8a30114..81be4e6 100644 --- a/src/main/java/za/co/neroland/nerospace/client/GlowEyesLayer.java +++ b/src/main/java/za/co/neroland/nerospace/client/GlowEyesLayer.java @@ -13,11 +13,11 @@ * {@code eyes} render type using a per-creature glow texture (transparent except the eyes / crystal / * ember accents), so those pixels glow in the dark regardless of light level. */ -public class GlowEyesLayer extends EyesLayer> { +public class GlowEyesLayer extends EyesLayer> { private final RenderType type; - public GlowEyesLayer(RenderLayerParent> parent, + public GlowEyesLayer(RenderLayerParent> parent, Identifier glowTexture) { super(parent); this.type = RenderTypes.eyes(glowTexture); diff --git a/src/main/java/za/co/neroland/nerospace/client/GreenlingModel.java b/src/main/java/za/co/neroland/nerospace/client/GreenlingModel.java index 65d9bbd..bc48280 100644 --- a/src/main/java/za/co/neroland/nerospace/client/GreenlingModel.java +++ b/src/main/java/za/co/neroland/nerospace/client/GreenlingModel.java @@ -2,6 +2,7 @@ import net.minecraft.client.model.geom.ModelLayerLocation; import net.minecraft.client.model.geom.ModelPart; +import net.minecraft.client.renderer.entity.state.LivingEntityRenderState; import net.minecraft.client.model.geom.PartPose; import net.minecraft.client.model.geom.builders.CubeListBuilder; import net.minecraft.client.model.geom.builders.LayerDefinition; @@ -19,7 +20,7 @@ * Idle (10f): a curious head sway (cheeks track the head) and its signature — the three-frond leaf * crest wiggles, each frond out of phase, like leaves in a light breeze. */ -public class GreenlingModel extends GreenxertzMobModel { +public class GreenlingModel extends GreenxertzMobModel { public static final ModelLayerLocation LAYER = new ModelLayerLocation( Identifier.fromNamespaceAndPath(Nerospace.MODID, "greenling"), "main"); diff --git a/src/main/java/za/co/neroland/nerospace/client/GreenxertzCreatureRenderer.java b/src/main/java/za/co/neroland/nerospace/client/GreenxertzCreatureRenderer.java index 4b1d056..47216aa 100644 --- a/src/main/java/za/co/neroland/nerospace/client/GreenxertzCreatureRenderer.java +++ b/src/main/java/za/co/neroland/nerospace/client/GreenxertzCreatureRenderer.java @@ -39,7 +39,7 @@ public GreenxertzCreatureRenderer(EntityRendererProvider.Context context, this.scaleY = scaleY; this.scaleZ = scaleZ; if (glowTexture != null) { - this.addLayer(new GlowEyesLayer(this, glowTexture)); + this.addLayer(new GlowEyesLayer(this, glowTexture)); } } diff --git a/src/main/java/za/co/neroland/nerospace/client/GreenxertzMobModel.java b/src/main/java/za/co/neroland/nerospace/client/GreenxertzMobModel.java index 6a5f768..d342bc3 100644 --- a/src/main/java/za/co/neroland/nerospace/client/GreenxertzMobModel.java +++ b/src/main/java/za/co/neroland/nerospace/client/GreenxertzMobModel.java @@ -32,7 +32,7 @@ * absolutely from the part's build-time rotation. * */ -public abstract class GreenxertzMobModel extends EntityModel { +public abstract class GreenxertzMobModel extends EntityModel { private record Swing(ModelPart part, float baseXRot, float phase, float amp) { } @@ -99,7 +99,7 @@ protected final void breathing(float freq, float amp) { } @Override - public void setupAnim(LivingEntityRenderState state) { + public void setupAnim(S state) { super.setupAnim(state); float pos = state.walkAnimationPos; float speed = Math.min(1.0F, state.walkAnimationSpeed); diff --git a/src/main/java/za/co/neroland/nerospace/client/MeadowLoperModel.java b/src/main/java/za/co/neroland/nerospace/client/MeadowLoperModel.java index 9b471fa..9a2b5fb 100644 --- a/src/main/java/za/co/neroland/nerospace/client/MeadowLoperModel.java +++ b/src/main/java/za/co/neroland/nerospace/client/MeadowLoperModel.java @@ -2,6 +2,7 @@ import net.minecraft.client.model.geom.ModelLayerLocation; import net.minecraft.client.model.geom.ModelPart; +import net.minecraft.client.renderer.entity.state.LivingEntityRenderState; import net.minecraft.client.model.geom.PartPose; import net.minecraft.client.model.geom.builders.CubeListBuilder; import net.minecraft.client.model.geom.builders.LayerDefinition; @@ -19,7 +20,7 @@ * swishing tail. Silhouette: heavy and horizontal, nothing like the existing predators. Idle: slow, * deep grazing breaths with the head dipping toward the grass and the tail swatting. */ -public class MeadowLoperModel extends GreenxertzMobModel { +public class MeadowLoperModel extends GreenxertzMobModel { public static final ModelLayerLocation LAYER = new ModelLayerLocation( Identifier.fromNamespaceAndPath(Nerospace.MODID, "meadow_loper"), "main"); diff --git a/src/main/java/za/co/neroland/nerospace/client/QuartzCrawlerModel.java b/src/main/java/za/co/neroland/nerospace/client/QuartzCrawlerModel.java index 95c110a..99930fc 100644 --- a/src/main/java/za/co/neroland/nerospace/client/QuartzCrawlerModel.java +++ b/src/main/java/za/co/neroland/nerospace/client/QuartzCrawlerModel.java @@ -2,6 +2,7 @@ import net.minecraft.client.model.geom.ModelLayerLocation; import net.minecraft.client.model.geom.ModelPart; +import net.minecraft.client.renderer.entity.state.LivingEntityRenderState; import net.minecraft.client.model.geom.PartPose; import net.minecraft.client.model.geom.builders.CubeListBuilder; import net.minecraft.client.model.geom.builders.LayerDefinition; @@ -19,7 +20,7 @@ * sensor-head scans side to side and its signature — the six legs keep a faint front-to-back ripple * even at rest, like an insect that never quite settles. */ -public class QuartzCrawlerModel extends GreenxertzMobModel { +public class QuartzCrawlerModel extends GreenxertzMobModel { public static final ModelLayerLocation LAYER = new ModelLayerLocation( Identifier.fromNamespaceAndPath(Nerospace.MODID, "quartz_crawler"), "main"); diff --git a/src/main/java/za/co/neroland/nerospace/client/RuinWardenModel.java b/src/main/java/za/co/neroland/nerospace/client/RuinWardenModel.java new file mode 100644 index 0000000..dd92099 --- /dev/null +++ b/src/main/java/za/co/neroland/nerospace/client/RuinWardenModel.java @@ -0,0 +1,73 @@ +package za.co.neroland.nerospace.client; + +import net.minecraft.client.model.geom.ModelLayerLocation; +import net.minecraft.client.model.geom.ModelPart; +import net.minecraft.client.model.geom.PartPose; +import net.minecraft.client.model.geom.builders.CubeListBuilder; +import net.minecraft.client.model.geom.builders.LayerDefinition; +import net.minecraft.client.model.geom.builders.MeshDefinition; +import net.minecraft.client.model.geom.builders.PartDefinition; +import net.minecraft.client.renderer.entity.state.LivingEntityRenderState; +import net.minecraft.core.Direction; +import net.minecraft.resources.Identifier; +import net.minecraft.util.Mth; + +import za.co.neroland.nerospace.Nerospace; + +/** + * Ruin Warden model — a hulking crystalline construct: a heavy head, a broad torso, jagged crystal + * shoulder spires, and thick hip/shoulder-pivoted limbs that stride heavily. Idle: a slow heave and + * a faint flicker of the shoulder spires. + */ +public class RuinWardenModel extends GreenxertzMobModel { + + public static final ModelLayerLocation LAYER = new ModelLayerLocation( + Identifier.fromNamespaceAndPath(Nerospace.MODID, "ruin_warden"), "main"); + + @SuppressWarnings("this-escape") + public RuinWardenModel(ModelPart root) { + super(root); + breathing(0.05F, 0.8F); + swingLimb("leg_left", 0F, 0.5F); + swingLimb("leg_right", Mth.PI, 0.5F); + swingLimb("arm_left", Mth.PI, 0.35F); + swingLimb("arm_right", 0F, 0.35F); + ambient("crystal_left", Direction.Axis.Z, 0.10F, 0F, 0.05F); + ambient("crystal_right", Direction.Axis.Z, 0.10F, 1.6F, 0.05F); + } + + public static LayerDefinition createBodyLayer() { + MeshDefinition mesh = new MeshDefinition(); + PartDefinition root = mesh.getRoot(); + + // model_sync:begin + root.addOrReplaceChild("head", + CubeListBuilder.create().texOffs(0, 28).addBox(-5F, -10F, -5F, 10F, 10F, 10F), + PartPose.offset(0.0F, 0.0F, 0.0F)); + root.addOrReplaceChild("body", + CubeListBuilder.create().texOffs(0, 0).addBox(-6F, 0F, -3F, 12F, 14F, 6F), + PartPose.offset(0.0F, 0.0F, 0.0F)); + root.addOrReplaceChild("crystal_left", + CubeListBuilder.create().texOffs(44, 0).addBox(-9F, -2F, -2F, 3F, 8F, 4F), + PartPose.offset(0.0F, 0.0F, 0.0F)); + root.addOrReplaceChild("crystal_right", + CubeListBuilder.create().texOffs(44, 0).addBox(6F, -2F, -2F, 3F, 8F, 4F), + PartPose.offset(0.0F, 0.0F, 0.0F)); + // model_sync:end (pivoted limbs below are Java-authoritative) + + root.addOrReplaceChild("arm_left", + CubeListBuilder.create().texOffs(44, 12).addBox(-2F, 0F, -2F, 4F, 14F, 4F), + PartPose.offset(-8F, 1F, 0F)); + root.addOrReplaceChild("arm_right", + CubeListBuilder.create().texOffs(44, 12).addBox(-2F, 0F, -2F, 4F, 14F, 4F), + PartPose.offset(8F, 1F, 0F)); + root.addOrReplaceChild("leg_left", + CubeListBuilder.create().texOffs(44, 32).addBox(-2.5F, 0F, -2.5F, 5F, 10F, 5F), + PartPose.offset(-3F, 14F, 0F)); + root.addOrReplaceChild("leg_right", + CubeListBuilder.create().texOffs(44, 32).addBox(-2.5F, 0F, -2.5F, 5F, 10F, 5F), + PartPose.offset(3F, 14F, 0F)); + + return LayerDefinition.create(mesh, 64, 64); + } +} diff --git a/src/main/java/za/co/neroland/nerospace/client/WoollyDriftModel.java b/src/main/java/za/co/neroland/nerospace/client/WoollyDriftModel.java index 6f3fffe..a8f180a 100644 --- a/src/main/java/za/co/neroland/nerospace/client/WoollyDriftModel.java +++ b/src/main/java/za/co/neroland/nerospace/client/WoollyDriftModel.java @@ -2,6 +2,7 @@ import net.minecraft.client.model.geom.ModelLayerLocation; import net.minecraft.client.model.geom.ModelPart; +import net.minecraft.client.renderer.entity.state.LivingEntityRenderState; import net.minecraft.client.model.geom.PartPose; import net.minecraft.client.model.geom.builders.CubeListBuilder; import net.minecraft.client.model.geom.builders.LayerDefinition; @@ -19,7 +20,7 @@ * and drooped ears. Idle: slow huddled breathing, ear twitches and a gentle ripple through the * fleece tufts like wind over a snowdrift. */ -public class WoollyDriftModel extends GreenxertzMobModel { +public class WoollyDriftModel extends GreenxertzMobModel { public static final ModelLayerLocation LAYER = new ModelLayerLocation( Identifier.fromNamespaceAndPath(Nerospace.MODID, "woolly_drift"), "main"); diff --git a/src/main/java/za/co/neroland/nerospace/client/XertzStalkerModel.java b/src/main/java/za/co/neroland/nerospace/client/XertzStalkerModel.java index 47d6ed4..03bedb5 100644 --- a/src/main/java/za/co/neroland/nerospace/client/XertzStalkerModel.java +++ b/src/main/java/za/co/neroland/nerospace/client/XertzStalkerModel.java @@ -2,6 +2,7 @@ import net.minecraft.client.model.geom.ModelLayerLocation; import net.minecraft.client.model.geom.ModelPart; +import net.minecraft.client.renderer.entity.state.LivingEntityRenderState; import net.minecraft.client.model.geom.PartPose; import net.minecraft.client.model.geom.builders.CubeListBuilder; import net.minecraft.client.model.geom.builders.LayerDefinition; @@ -21,7 +22,7 @@ * signature — the crystal blade-arms slowly flex out and back at the shoulder, like a hunter * keeping its blades limber. */ -public class XertzStalkerModel extends GreenxertzMobModel { +public class XertzStalkerModel extends GreenxertzMobModel { public static final ModelLayerLocation LAYER = new ModelLayerLocation( Identifier.fromNamespaceAndPath(Nerospace.MODID, "xertz_stalker"), "main"); diff --git a/src/main/java/za/co/neroland/nerospace/datagen/ModBlockLootSubProvider.java b/src/main/java/za/co/neroland/nerospace/datagen/ModBlockLootSubProvider.java index dd87be8..4620789 100644 --- a/src/main/java/za/co/neroland/nerospace/datagen/ModBlockLootSubProvider.java +++ b/src/main/java/za/co/neroland/nerospace/datagen/ModBlockLootSubProvider.java @@ -43,6 +43,13 @@ protected void generate() { // Star Guide pedestal (the installed book pops separately via the BE's remove hook). dropSelf(ModBlocks.STAR_GUIDE.get()); + dropSelf(ModBlocks.VILLAGE_CORE.get()); + dropSelf(ModBlocks.ALIEN_BRICKS.get()); + dropSelf(ModBlocks.CRACKED_ALIEN_BRICKS.get()); + dropSelf(ModBlocks.ALIEN_TILE.get()); + dropSelf(ModBlocks.ALIEN_PILLAR.get()); + dropSelf(ModBlocks.ALIEN_LAMP.get()); + dropSelf(ModBlocks.ALIEN_CRYSTAL_BLOCK.get()); // Phase 8a — fuel tank. dropSelf(ModBlocks.FUEL_TANK.get()); diff --git a/src/main/java/za/co/neroland/nerospace/datagen/ModBlockTagProvider.java b/src/main/java/za/co/neroland/nerospace/datagen/ModBlockTagProvider.java index 743b8c4..b32b631 100644 --- a/src/main/java/za/co/neroland/nerospace/datagen/ModBlockTagProvider.java +++ b/src/main/java/za/co/neroland/nerospace/datagen/ModBlockTagProvider.java @@ -59,6 +59,13 @@ protected void addTags(HolderLookup.Provider provider) { // Star Guide pedestal: pickaxe-mineable but breaks fine by hand (no // requiresCorrectToolForDrops) — it's the day-one tutorial block. ModBlocks.STAR_GUIDE.get(), + ModBlocks.VILLAGE_CORE.get(), + ModBlocks.ALIEN_BRICKS.get(), + ModBlocks.CRACKED_ALIEN_BRICKS.get(), + ModBlocks.ALIEN_TILE.get(), + ModBlocks.ALIEN_PILLAR.get(), + ModBlocks.ALIEN_LAMP.get(), + ModBlocks.ALIEN_CRYSTAL_BLOCK.get(), ModBlocks.LAUNCH_GANTRY.get(), ModBlocks.QUARRY_CONTROLLER.get(), ModBlocks.QUARRY_LANDMARK.get(), diff --git a/src/main/java/za/co/neroland/nerospace/datagen/ModLanguageProvider.java b/src/main/java/za/co/neroland/nerospace/datagen/ModLanguageProvider.java index ca217c9..2fd2398 100644 --- a/src/main/java/za/co/neroland/nerospace/datagen/ModLanguageProvider.java +++ b/src/main/java/za/co/neroland/nerospace/datagen/ModLanguageProvider.java @@ -34,6 +34,18 @@ protected void addTranslations() { add(ModBlocks.NEROSTEEL_ORE.get(), "Nerosteel Ore"); add(ModBlocks.XERTZ_QUARTZ_ORE.get(), "Xertz Quartz Ore"); add(ModBlocks.NEROSTEEL_BLOCK.get(), "Block of Nerosteel"); + add(ModBlocks.VILLAGE_CORE.get(), "Village Core"); + add("message.nerospace.village_core.claimed", "You claim this Village Core. The aliens take note."); + add("message.nerospace.village_core.status_self", "This is your Village Core."); + add("message.nerospace.village_core.owned", "This Village Core belongs to %s."); + add(ModItems.GRAV_STRIDERS.get(), "Grav Striders"); + add(ModItems.XERTZ_RESONATOR.get(), "Xertz Resonator"); + add(ModBlocks.ALIEN_BRICKS.get(), "Alien Bricks"); + add(ModBlocks.CRACKED_ALIEN_BRICKS.get(), "Cracked Alien Bricks"); + add(ModBlocks.ALIEN_TILE.get(), "Alien Tile"); + add(ModBlocks.ALIEN_PILLAR.get(), "Alien Pillar"); + add(ModBlocks.ALIEN_LAMP.get(), "Alien Lamp"); + add(ModBlocks.ALIEN_CRYSTAL_BLOCK.get(), "Alien Crystal Block"); // Phase 4 blocks. add(ModBlocks.ROCKET_LAUNCH_PAD.get(), "Rocket Launch Pad"); @@ -241,6 +253,7 @@ protected void addTranslations() { add(ModItems.XERTZ_STALKER_SPAWN_EGG.get(), "Xertz Stalker Spawn Egg"); add(ModItems.QUARTZ_CRAWLER_SPAWN_EGG.get(), "Quartz Crawler Spawn Egg"); add(ModItems.GREENLING_SPAWN_EGG.get(), "Greenling Spawn Egg"); + add(ModItems.ALIEN_VILLAGER_SPAWN_EGG.get(), "Alien Villager Spawn Egg"); add(ModItems.CINDER_STALKER_SPAWN_EGG.get(), "Cinder Stalker Spawn Egg"); add(ModItems.FROST_STRIDER_SPAWN_EGG.get(), "Frost Strider Spawn Egg"); add(ModItems.MEADOW_LOPER_SPAWN_EGG.get(), "Meadow Loper Spawn Egg"); @@ -273,6 +286,7 @@ protected void addTranslations() { add("entity.nerospace.xertz_stalker", "Xertz Stalker"); add("entity.nerospace.quartz_crawler", "Quartz Crawler"); add("entity.nerospace.greenling", "Greenling"); + add("entity.nerospace.alien_villager", "Alien Villager"); add("entity.nerospace.cinder_stalker", "Cinder Stalker"); add("entity.nerospace.frost_strider", "Frost Strider"); add("entity.nerospace.meadow_loper", "Meadow Loper"); diff --git a/src/main/java/za/co/neroland/nerospace/datagen/ModModelProvider.java b/src/main/java/za/co/neroland/nerospace/datagen/ModModelProvider.java index a29a35d..0571137 100644 --- a/src/main/java/za/co/neroland/nerospace/datagen/ModModelProvider.java +++ b/src/main/java/za/co/neroland/nerospace/datagen/ModModelProvider.java @@ -51,6 +51,13 @@ protected void registerModels(BlockModelGenerators blockModels, ItemModelGenerat blockModels.createTrivialCube(ModBlocks.NEROSTEEL_ORE.get()); blockModels.createTrivialCube(ModBlocks.XERTZ_QUARTZ_ORE.get()); blockModels.createTrivialCube(ModBlocks.NEROSTEEL_BLOCK.get()); + blockModels.createTrivialCube(ModBlocks.VILLAGE_CORE.get()); + blockModels.createTrivialCube(ModBlocks.ALIEN_BRICKS.get()); + blockModels.createTrivialCube(ModBlocks.CRACKED_ALIEN_BRICKS.get()); + blockModels.createTrivialCube(ModBlocks.ALIEN_TILE.get()); + blockModels.createTrivialCube(ModBlocks.ALIEN_PILLAR.get()); + blockModels.createTrivialCube(ModBlocks.ALIEN_LAMP.get()); + blockModels.createTrivialCube(ModBlocks.ALIEN_CRYSTAL_BLOCK.get()); // Phase 4 — launch pad: textured full cube (proper flat/raised shape comes with the planned // 3x3 multiblock pad). A hand-authored flat slab model caused missing-texture at runtime, so @@ -225,6 +232,9 @@ protected void registerModels(BlockModelGenerators blockModels, ItemModelGenerat itemModels.generateFlatItem(ModItems.XERTZ_STALKER_SPAWN_EGG.get(), ModelTemplates.FLAT_ITEM); itemModels.generateFlatItem(ModItems.QUARTZ_CRAWLER_SPAWN_EGG.get(), ModelTemplates.FLAT_ITEM); itemModels.generateFlatItem(ModItems.GREENLING_SPAWN_EGG.get(), ModelTemplates.FLAT_ITEM); + itemModels.generateFlatItem(ModItems.ALIEN_VILLAGER_SPAWN_EGG.get(), ModelTemplates.FLAT_ITEM); + itemModels.generateFlatItem(ModItems.GRAV_STRIDERS.get(), ModelTemplates.FLAT_ITEM); + itemModels.generateFlatItem(ModItems.XERTZ_RESONATOR.get(), ModelTemplates.FLAT_ITEM); itemModels.generateFlatItem(ModItems.CINDER_STALKER_SPAWN_EGG.get(), ModelTemplates.FLAT_ITEM); itemModels.generateFlatItem(ModItems.FROST_STRIDER_SPAWN_EGG.get(), ModelTemplates.FLAT_ITEM); diff --git a/src/main/java/za/co/neroland/nerospace/entity/AlienVillager.java b/src/main/java/za/co/neroland/nerospace/entity/AlienVillager.java new file mode 100644 index 0000000..38b5258 --- /dev/null +++ b/src/main/java/za/co/neroland/nerospace/entity/AlienVillager.java @@ -0,0 +1,412 @@ +package za.co.neroland.nerospace.entity; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.OptionalInt; +import java.util.UUID; + +import com.mojang.serialization.Codec; + +import net.minecraft.core.Holder; +import net.minecraft.core.particles.ParticleTypes; +import net.minecraft.network.syncher.EntityDataAccessor; +import net.minecraft.network.syncher.EntityDataSerializers; +import net.minecraft.network.syncher.SynchedEntityData; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.sounds.SoundEvent; +import net.minecraft.sounds.SoundEvents; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.SimpleMenuProvider; +import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.PathfinderMob; +import net.minecraft.world.entity.ai.attributes.AttributeSupplier; +import net.minecraft.world.entity.ai.attributes.Attributes; +import net.minecraft.world.entity.ai.goal.AvoidEntityGoal; +import net.minecraft.world.entity.ai.goal.FloatGoal; +import net.minecraft.world.entity.ai.goal.LookAtPlayerGoal; +import net.minecraft.world.entity.ai.goal.RandomLookAroundGoal; +import net.minecraft.world.entity.ai.goal.WaterAvoidingRandomStrollGoal; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.MerchantMenu; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.item.trading.Merchant; +import net.minecraft.world.item.trading.MerchantOffer; +import net.minecraft.world.item.trading.MerchantOffers; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.storage.ValueInput; +import net.minecraft.world.level.storage.ValueOutput; + +import za.co.neroland.nerospace.registry.ModDimensions; +import za.co.neroland.nerospace.registry.ModItems; +import za.co.neroland.nerospace.village.AlienTrades; +import za.co.neroland.nerospace.village.Reputation; + +/** + * Alien Villager (ALIEN_VILLAGERS_DESIGN.md). A social alien NPC of the nerospace planets: a + * wary-neutral wanderer that the player wins over to unlock trades. + * + *

Phase 0/1: wanders its home biome, carries a per-individual variant (planet, biome, colour seed) + * that drives a unique render tint + per-biome skin. + * + *

Phase 2: it is now a {@link Merchant}. Each villager tracks a per-player reputation score + * (0..{@link Reputation#MAX}) -> 6 tiers. Gifting palette-appropriate goods raises reputation; at T1+ + * the villager opens the vanilla trading screen with a tier-gated offer list ({@link AlienTrades}). + * Completing trades nudges reputation up. Reputation is stored on the villager for now; the Village + * Core block (Phase 3/4) will aggregate it per-village. + */ +public class AlienVillager extends PathfinderMob implements Merchant { + + /** Which planet's species this villager belongs to (drives palette + silhouette later). */ + public enum Planet { + GREENXERTZ, CINDARA, GLACIRA; + + public static Planet byOrdinal(int i) { + Planet[] values = values(); + return values[Math.floorMod(i, values.length)]; + } + } + + private static final EntityDataAccessor DATA_PLANET = + SynchedEntityData.defineId(AlienVillager.class, EntityDataSerializers.INT); + private static final EntityDataAccessor DATA_BIOME = + SynchedEntityData.defineId(AlienVillager.class, EntityDataSerializers.STRING); + private static final EntityDataAccessor DATA_COLOR_SEED = + SynchedEntityData.defineId(AlienVillager.class, EntityDataSerializers.INT); + private static final EntityDataAccessor DATA_DISPLAY_TIER = + SynchedEntityData.defineId(AlienVillager.class, EntityDataSerializers.INT); + + private static final Codec> REP_CODEC = Codec.unboundedMap(Codec.STRING, Codec.INT); + + private boolean variantAssigned; + + private final Map reputation = new HashMap<>(); + + private Player tradingPlayer; + private MerchantOffers offers; + private int villagerXp; + + public AlienVillager(EntityType type, Level level) { + super(type, level); + } + + public static AttributeSupplier.Builder createAttributes() { + return PathfinderMob.createMobAttributes() + .add(Attributes.MAX_HEALTH, 20.0D) + .add(Attributes.MOVEMENT_SPEED, 0.3D) + .add(Attributes.FOLLOW_RANGE, 16.0D); + } + + @Override + protected void defineSynchedData(SynchedEntityData.Builder builder) { + super.defineSynchedData(builder); + builder.define(DATA_PLANET, Planet.GREENXERTZ.ordinal()); + builder.define(DATA_BIOME, ""); + builder.define(DATA_COLOR_SEED, 0); + builder.define(DATA_DISPLAY_TIER, 0); + } + + @Override + protected void registerGoals() { + this.goalSelector.addGoal(0, new FloatGoal(this)); + this.goalSelector.addGoal(2, new AvoidEntityGoal<>(this, Player.class, 4.0F, 1.0D, 1.2D)); + this.goalSelector.addGoal(6, new WaterAvoidingRandomStrollGoal(this, 0.9D)); + this.goalSelector.addGoal(7, new LookAtPlayerGoal(this, Player.class, 8.0F)); + this.goalSelector.addGoal(8, new RandomLookAroundGoal(this)); + } + + @Override + public void tick() { + super.tick(); + if (!this.level().isClientSide()) { + if (!this.variantAssigned) { + assignVariant(); + this.variantAssigned = true; + } + if (this.tradingPlayer != null + && (this.tradingPlayer.isRemoved() || this.distanceToSqr(this.tradingPlayer) > 100.0D)) { + this.setTradingPlayer(null); + } + } + } + + private void assignVariant() { + setColorSeed(this.random.nextInt() | 1); + setPlanet(planetForDimension()); + Holder biome = this.level().getBiome(this.blockPosition()); + biome.unwrapKey().ifPresent(key -> setBiomeId(key.identifier().toString())); + } + + private Planet planetForDimension() { + var dim = this.level().dimension(); + if (dim == ModDimensions.CINDARA_LEVEL) { + return Planet.CINDARA; + } + if (dim == ModDimensions.GLACIRA_LEVEL) { + return Planet.GLACIRA; + } + return Planet.GREENXERTZ; + } + + // --- Interaction: gifts + trading ----------------------------------------- + + @Override + protected InteractionResult mobInteract(Player player, InteractionHand hand) { + ItemStack held = player.getItemInHand(hand); + boolean gift = isGift(held); + boolean canTrade = getTier(player) >= 1 && !this.isBaby(); + + if (!gift && !canTrade) { + if (!this.level().isClientSide()) { + this.playSound(SoundEvents.VILLAGER_NO, 1.0F, 1.0F); + } + return InteractionResult.SUCCESS; + } + if (this.level().isClientSide()) { + return InteractionResult.SUCCESS; + } + if (gift) { + receiveGift(player, held); + return InteractionResult.SUCCESS; + } + if (this.getTradingPlayer() == null && this.isAlive()) { + startTrading(player); + } + return InteractionResult.SUCCESS; + } + + private void startTrading(Player player) { + rebuildOffers(player); + this.setTradingPlayer(player); + OptionalInt opt = player.openMenu(new SimpleMenuProvider( + (id, inv, p) -> new MerchantMenu(id, inv, this), this.getDisplayName())); + if (opt.isPresent()) { + player.sendMerchantOffers(opt.getAsInt(), this.getOffers(), 1, this.getVillagerXp(), + this.showProgressBar(), false); + } + } + + private void receiveGift(Player player, ItemStack held) { + int gain = giftValue(held); + if (gain <= 0) { + return; + } + held.consume(1, player); + addReputation(player, gain); + this.playSound(SoundEvents.VILLAGER_YES, 1.0F, 1.0F); + if (this.level() instanceof ServerLevel server) { + server.sendParticles(ParticleTypes.HAPPY_VILLAGER, + this.getX(), this.getY() + 1.6D, this.getZ(), 5, 0.3D, 0.3D, 0.3D, 0.0D); + } + } + + private static boolean isGift(ItemStack stack) { + return !stack.isEmpty() && giftValue(stack) > 0; + } + + private static int giftValue(ItemStack stack) { + if (stack.is(ModItems.XERTZ_QUARTZ.get())) { + return 3; + } + if (stack.is(ModItems.NEROSIUM_INGOT.get())) { + return 5; + } + if (stack.is(ModItems.ALIEN_FRAGMENT.get())) { + return 6; + } + if (stack.is(Items.EMERALD)) { + return 4; + } + return 0; + } + + // --- Reputation ----------------------------------------------------------- + + public int getReputation(Player player) { + return this.reputation.getOrDefault(player.getUUID(), 0); + } + + public int getTier(Player player) { + return Reputation.tier(getReputation(player)); + } + + public void addReputation(Player player, int amount) { + int value = Reputation.clamp(getReputation(player) + amount); + this.reputation.put(player.getUUID(), value); + refreshDisplayTier(); + if (this.tradingPlayer == player) { + this.offers = null; + } + } + + private void refreshDisplayTier() { + int best = 0; + for (int score : this.reputation.values()) { + best = Math.max(best, Reputation.tier(score)); + } + this.entityData.set(DATA_DISPLAY_TIER, best); + } + + public int getDisplayTier() { + return this.entityData.get(DATA_DISPLAY_TIER); + } + + private void rebuildOffers(Player player) { + int tier = player != null ? getTier(player) : 1; + this.offers = AlienTrades.forTier(Math.max(1, tier)); + } + + // --- Merchant ------------------------------------------------------------- + + @Override + public void setTradingPlayer(Player player) { + this.tradingPlayer = player; + if (player == null) { + this.offers = null; + } + } + + @Override + public Player getTradingPlayer() { + return this.tradingPlayer; + } + + @Override + public MerchantOffers getOffers() { + if (this.offers == null) { + rebuildOffers(this.tradingPlayer); + } + return this.offers; + } + + @Override + public void overrideOffers(MerchantOffers newOffers) { + this.offers = newOffers; + } + + @Override + public void notifyTrade(MerchantOffer offer) { + offer.increaseUses(); + this.villagerXp += Math.max(1, offer.getXp()); + if (this.tradingPlayer != null) { + addReputation(this.tradingPlayer, 1); + } + } + + @Override + public void notifyTradeUpdated(ItemStack stack) { + // No price-demand simulation in Phase 2. + } + + @Override + public int getVillagerXp() { + return this.villagerXp; + } + + @Override + public void overrideXp(int xp) { + this.villagerXp = xp; + } + + @Override + public boolean showProgressBar() { + return false; + } + + @Override + public SoundEvent getNotifyTradeSound() { + return SoundEvents.VILLAGER_YES; + } + + @Override + public boolean isClientSide() { + return this.level().isClientSide(); + } + + @Override + public boolean stillValid(Player player) { + return this.tradingPlayer == player && this.isAlive() && this.distanceToSqr(player) <= 100.0D; + } + + // --- Variant accessors ----------------------------------------------------- + + public Planet getPlanet() { + return Planet.byOrdinal(this.entityData.get(DATA_PLANET)); + } + + public void setPlanet(Planet planet) { + this.entityData.set(DATA_PLANET, planet.ordinal()); + } + + public String getBiomeId() { + return this.entityData.get(DATA_BIOME); + } + + public void setBiomeId(String id) { + this.entityData.set(DATA_BIOME, id); + } + + public int getColorSeed() { + return this.entityData.get(DATA_COLOR_SEED); + } + + public void setColorSeed(int seed) { + this.entityData.set(DATA_COLOR_SEED, seed); + } + + // --- Persistence ----------------------------------------------------------- + + @Override + protected void addAdditionalSaveData(ValueOutput output) { + super.addAdditionalSaveData(output); + output.putInt("Planet", this.entityData.get(DATA_PLANET)); + output.putString("HomeBiome", getBiomeId()); + output.putInt("ColorSeed", getColorSeed()); + output.putBoolean("VariantAssigned", this.variantAssigned); + output.putInt("VillagerXp", this.villagerXp); + Map serial = new HashMap<>(); + this.reputation.forEach((uuid, score) -> serial.put(uuid.toString(), score)); + output.store("Reputation", REP_CODEC, serial); + } + + @Override + protected void readAdditionalSaveData(ValueInput input) { + super.readAdditionalSaveData(input); + this.entityData.set(DATA_PLANET, input.getIntOr("Planet", Planet.GREENXERTZ.ordinal())); + setBiomeId(input.getStringOr("HomeBiome", "")); + setColorSeed(input.getIntOr("ColorSeed", 0)); + this.variantAssigned = input.getBooleanOr("VariantAssigned", false); + this.villagerXp = input.getIntOr("VillagerXp", 0); + this.reputation.clear(); + Optional> stored = input.read("Reputation", REP_CODEC); + stored.ifPresent(map -> map.forEach((key, score) -> { + try { + this.reputation.put(UUID.fromString(key), score); + } catch (IllegalArgumentException ignored) { + // skip malformed UUID keys + } + })); + refreshDisplayTier(); + } + + // --- Sounds --------------------------------------------------------------- + + @Override + protected SoundEvent getAmbientSound() { + return SoundEvents.VILLAGER_AMBIENT; + } + + @Override + protected SoundEvent getHurtSound(DamageSource damageSource) { + return SoundEvents.VILLAGER_HURT; + } + + @Override + protected SoundEvent getDeathSound() { + return SoundEvents.VILLAGER_DEATH; + } +} diff --git a/src/main/java/za/co/neroland/nerospace/entity/ModEntityEvents.java b/src/main/java/za/co/neroland/nerospace/entity/ModEntityEvents.java index b9c05b6..b1ef4e7 100644 --- a/src/main/java/za/co/neroland/nerospace/entity/ModEntityEvents.java +++ b/src/main/java/za/co/neroland/nerospace/entity/ModEntityEvents.java @@ -26,6 +26,8 @@ public static void registerAttributes(EntityAttributeCreationEvent event) { event.put(ModEntities.XERTZ_STALKER.get(), XertzStalker.createAttributes().build()); event.put(ModEntities.QUARTZ_CRAWLER.get(), QuartzCrawler.createAttributes().build()); event.put(ModEntities.GREENLING.get(), Greenling.createAttributes().build()); + event.put(ModEntities.ALIEN_VILLAGER.get(), AlienVillager.createAttributes().build()); + event.put(ModEntities.RUIN_WARDEN.get(), RuinWarden.createAttributes().build()); event.put(ModEntities.CINDER_STALKER.get(), CinderStalker.createAttributes().build()); event.put(ModEntities.FROST_STRIDER.get(), FrostStrider.createAttributes().build()); // Terraform livestock (DEEPER_TERRAFORM_DESIGN.md §5). @@ -66,6 +68,13 @@ public static void registerSpawnPlacements(RegisterSpawnPlacementsEvent event) { !level.getBlockState(pos.below()).isAir() && level.getBlockState(pos).isAir(), RegisterSpawnPlacementsEvent.Operation.REPLACE); + // Alien Villager (Phase 0): surface-dwelling, spawns on solid ground with open space above. + event.register(ModEntities.ALIEN_VILLAGER.get(), SpawnPlacementTypes.ON_GROUND, + Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, + (type, level, reason, pos, random) -> + !level.getBlockState(pos.below()).isAir() && level.getBlockState(pos).isAir(), + RegisterSpawnPlacementsEvent.Operation.REPLACE); + // Terraform livestock (DEEPER_TERRAFORM_DESIGN.md §5): graze only on grassed (Living) // ground — the mature biomes are runtime-written, so grass is the reliable signal. registerLivestockPlacement(event, ModEntities.MEADOW_LOPER.get()); diff --git a/src/main/java/za/co/neroland/nerospace/entity/RuinWarden.java b/src/main/java/za/co/neroland/nerospace/entity/RuinWarden.java new file mode 100644 index 0000000..49e7e2b --- /dev/null +++ b/src/main/java/za/co/neroland/nerospace/entity/RuinWarden.java @@ -0,0 +1,69 @@ +package za.co.neroland.nerospace.entity; + +import net.minecraft.sounds.SoundEvent; +import net.minecraft.sounds.SoundEvents; +import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.ai.attributes.AttributeSupplier; +import net.minecraft.world.entity.ai.attributes.Attributes; +import net.minecraft.world.entity.ai.goal.FloatGoal; +import net.minecraft.world.entity.ai.goal.LookAtPlayerGoal; +import net.minecraft.world.entity.ai.goal.MeleeAttackGoal; +import net.minecraft.world.entity.ai.goal.RandomLookAroundGoal; +import net.minecraft.world.entity.ai.goal.WaterAvoidingRandomStrollGoal; +import net.minecraft.world.entity.ai.goal.target.HurtByTargetGoal; +import net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal; +import net.minecraft.world.entity.monster.Monster; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; + +/** + * Ruin Warden (ALIEN_VILLAGERS_DESIGN.md §5.2 / §7) — the boss guardian of the ancient ruins and the + * mega-city keep. A towering crystalline construct: heavily armoured, hard to knock back, and a + * dangerous melee threat. Spawned by structure generation; the reward for clearing it is the + * structure's deep loot. + */ +public class RuinWarden extends Monster { + + public RuinWarden(EntityType type, Level level) { + super(type, level); + } + + public static AttributeSupplier.Builder createAttributes() { + return Monster.createMonsterAttributes() + .add(Attributes.MAX_HEALTH, 120.0D) + .add(Attributes.MOVEMENT_SPEED, 0.24D) + .add(Attributes.ATTACK_DAMAGE, 9.0D) + .add(Attributes.ATTACK_KNOCKBACK, 1.0D) + .add(Attributes.KNOCKBACK_RESISTANCE, 0.75D) + .add(Attributes.ARMOR, 6.0D) + .add(Attributes.FOLLOW_RANGE, 32.0D); + } + + @Override + protected void registerGoals() { + this.goalSelector.addGoal(0, new FloatGoal(this)); + this.goalSelector.addGoal(2, new MeleeAttackGoal(this, 1.0D, true)); + this.goalSelector.addGoal(7, new WaterAvoidingRandomStrollGoal(this, 0.8D)); + this.goalSelector.addGoal(8, new LookAtPlayerGoal(this, Player.class, 12.0F)); + this.goalSelector.addGoal(8, new RandomLookAroundGoal(this)); + + this.targetSelector.addGoal(1, new HurtByTargetGoal(this)); + this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true)); + } + + @Override + protected SoundEvent getAmbientSound() { + return SoundEvents.RAVAGER_AMBIENT; + } + + @Override + protected SoundEvent getHurtSound(DamageSource damageSource) { + return SoundEvents.RAVAGER_HURT; + } + + @Override + protected SoundEvent getDeathSound() { + return SoundEvents.RAVAGER_DEATH; + } +} diff --git a/src/main/java/za/co/neroland/nerospace/gear/AlienGearEvents.java b/src/main/java/za/co/neroland/nerospace/gear/AlienGearEvents.java new file mode 100644 index 0000000..1c0020e --- /dev/null +++ b/src/main/java/za/co/neroland/nerospace/gear/AlienGearEvents.java @@ -0,0 +1,28 @@ +package za.co.neroland.nerospace.gear; + +import net.minecraft.world.entity.player.Player; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.EventBusSubscriber; +import net.neoforged.neoforge.event.entity.living.LivingFallEvent; + +import za.co.neroland.nerospace.Nerospace; +import za.co.neroland.nerospace.registry.ModItems; + +/** + * Ability hooks for the exclusive Artificer gear (ALIEN_VILLAGERS_DESIGN.md §6.1). Grav Striders: + * while carried, alien grav-tech cushions the wearer — fall damage is negated. + */ +@EventBusSubscriber(modid = Nerospace.MODID) +public final class AlienGearEvents { + + private AlienGearEvents() { + } + + @SubscribeEvent + public static void onLivingFall(LivingFallEvent event) { + if (event.getEntity() instanceof Player player + && player.getInventory().hasAnyMatching(s -> s.is(ModItems.GRAV_STRIDERS.get()))) { + event.setDamageMultiplier(0.0F); + } + } +} diff --git a/src/main/java/za/co/neroland/nerospace/gear/XertzResonatorItem.java b/src/main/java/za/co/neroland/nerospace/gear/XertzResonatorItem.java new file mode 100644 index 0000000..6d0948c --- /dev/null +++ b/src/main/java/za/co/neroland/nerospace/gear/XertzResonatorItem.java @@ -0,0 +1,46 @@ +package za.co.neroland.nerospace.gear; + +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.Level; +import net.neoforged.neoforge.common.Tags; + +/** + * Xertz Resonator (ALIEN_VILLAGERS_DESIGN.md §6.1) — an exclusive Artificer trade. Right-click to + * ping the surrounding stone: it reports how many ore blocks lie within range, a cross-mod prospecting + * aid (it matches the common {@code c:ores} tag, so it finds any mod's ores). + */ +public class XertzResonatorItem extends Item { + + private static final int RADIUS = 8; + + public XertzResonatorItem(Properties properties) { + super(properties); + } + + @Override + public InteractionResult use(Level level, Player player, InteractionHand hand) { + if (!level.isClientSide()) { + BlockPos center = player.blockPosition(); + int count = 0; + BlockPos.MutableBlockPos m = new BlockPos.MutableBlockPos(); + for (int dx = -RADIUS; dx <= RADIUS; dx++) { + for (int dy = -RADIUS; dy <= RADIUS; dy++) { + for (int dz = -RADIUS; dz <= RADIUS; dz++) { + m.set(center.getX() + dx, center.getY() + dy, center.getZ() + dz); + if (level.getBlockState(m).is(Tags.Blocks.ORES)) { + count++; + } + } + } + } + player.sendSystemMessage(Component.literal( + "Xertz resonance: " + count + " ore blocks within " + RADIUS + " blocks.")); + } + return InteractionResult.SUCCESS; + } +} diff --git a/src/main/java/za/co/neroland/nerospace/meteor/FallingMeteorEntity.java b/src/main/java/za/co/neroland/nerospace/meteor/FallingMeteorEntity.java index 8d43a91..0ae40b9 100644 --- a/src/main/java/za/co/neroland/nerospace/meteor/FallingMeteorEntity.java +++ b/src/main/java/za/co/neroland/nerospace/meteor/FallingMeteorEntity.java @@ -133,7 +133,7 @@ public void tick() { /** Flame + smoke trail, denser as the meteor nears the ground (client-side, per the design §4). */ private void spawnTrail() { - double proximity = 1.0D - Math.min(1.0D, (getY() - this.targetY) / (double) FALL_HEIGHT); + double proximity = 1.0D - Math.min(1.0D, (getY() - this.targetY) / FALL_HEIGHT); int puffs = 2 + (int) (proximity * 4); for (int i = 0; i < puffs; i++) { double ox = (this.random.nextDouble() - 0.5D) * 0.8D; diff --git a/src/main/java/za/co/neroland/nerospace/registry/ModBlockEntities.java b/src/main/java/za/co/neroland/nerospace/registry/ModBlockEntities.java index 9a61e5f..c43b22e 100644 --- a/src/main/java/za/co/neroland/nerospace/registry/ModBlockEntities.java +++ b/src/main/java/za/co/neroland/nerospace/registry/ModBlockEntities.java @@ -179,6 +179,14 @@ public final class ModBlockEntities { false, ModBlocks.STAR_GUIDE.get())); + /** Village Core controller block entity (ALIEN_VILLAGERS_DESIGN.md §4.1). */ + public static final Supplier> VILLAGE_CORE = + BLOCK_ENTITY_TYPES.register("village_core", + () -> new BlockEntityType<>( + za.co.neroland.nerospace.village.VillageCoreBlockEntity::new, + false, + ModBlocks.VILLAGE_CORE.get())); + // Meteor Core (meteor-events-design.md §5): stores the rolled loot, break-to-loot. public static final Supplier> METEOR_CORE = BLOCK_ENTITY_TYPES.register("meteor_core", diff --git a/src/main/java/za/co/neroland/nerospace/registry/ModBlocks.java b/src/main/java/za/co/neroland/nerospace/registry/ModBlocks.java index 3f92627..9d49a69 100644 --- a/src/main/java/za/co/neroland/nerospace/registry/ModBlocks.java +++ b/src/main/java/za/co/neroland/nerospace/registry/ModBlocks.java @@ -493,6 +493,29 @@ public final class ModBlocks { .sound(SoundType.STONE) .noOcclusion()); // pedestal model (art overhaul §3) + /** The Village Core: controller block of an alien village (claimed by right-click). */ + public static final DeferredBlock VILLAGE_CORE = + BLOCKS.registerBlock("village_core", za.co.neroland.nerospace.village.VillageCoreBlock::new, + props -> props + .mapColor(MapColor.COLOR_GREEN) + .strength(2.0F, 8.0F) + .sound(SoundType.STONE) + .requiresCorrectToolForDrops()); + + // --- Greenxertz decoration set (ALIEN_VILLAGERS_DESIGN.md §8) ------------ + public static final DeferredBlock ALIEN_BRICKS = BLOCKS.registerSimpleBlock("alien_bricks", + props -> props.mapColor(MapColor.COLOR_GREEN).strength(1.5F, 6.0F).requiresCorrectToolForDrops().sound(SoundType.METAL)); + public static final DeferredBlock CRACKED_ALIEN_BRICKS = BLOCKS.registerSimpleBlock("cracked_alien_bricks", + props -> props.mapColor(MapColor.COLOR_GREEN).strength(1.5F, 6.0F).requiresCorrectToolForDrops().sound(SoundType.METAL)); + public static final DeferredBlock ALIEN_TILE = BLOCKS.registerSimpleBlock("alien_tile", + props -> props.mapColor(MapColor.COLOR_GREEN).strength(1.5F, 6.0F).requiresCorrectToolForDrops().sound(SoundType.METAL)); + public static final DeferredBlock ALIEN_PILLAR = BLOCKS.registerSimpleBlock("alien_pillar", + props -> props.mapColor(MapColor.COLOR_GREEN).strength(1.5F, 6.0F).requiresCorrectToolForDrops().sound(SoundType.METAL)); + public static final DeferredBlock ALIEN_LAMP = BLOCKS.registerSimpleBlock("alien_lamp", + props -> props.mapColor(MapColor.COLOR_GREEN).strength(1.5F, 6.0F).lightLevel(s -> 15).sound(SoundType.METAL)); + public static final DeferredBlock ALIEN_CRYSTAL_BLOCK = BLOCKS.registerSimpleBlock("alien_crystal_block", + props -> props.mapColor(MapColor.EMERALD).strength(1.5F, 6.0F).lightLevel(s -> 12).sound(SoundType.AMETHYST)); + // --- Rocket Fuel liquid block (Phase 7b) -------------------------------- /** The world block for the {@code rocket_fuel} fluid (placed by its bucket). */ diff --git a/src/main/java/za/co/neroland/nerospace/registry/ModCreativeModeTabs.java b/src/main/java/za/co/neroland/nerospace/registry/ModCreativeModeTabs.java index 124239b..b26f5d2 100644 --- a/src/main/java/za/co/neroland/nerospace/registry/ModCreativeModeTabs.java +++ b/src/main/java/za/co/neroland/nerospace/registry/ModCreativeModeTabs.java @@ -125,12 +125,20 @@ public final class ModCreativeModeTabs { // Star Guide (progression block, 1.0). output.accept(ModBlocks.STAR_GUIDE.get()); + output.accept(ModBlocks.VILLAGE_CORE.get()); + output.accept(ModBlocks.ALIEN_BRICKS.get()); + output.accept(ModBlocks.CRACKED_ALIEN_BRICKS.get()); + output.accept(ModBlocks.ALIEN_TILE.get()); + output.accept(ModBlocks.ALIEN_PILLAR.get()); + output.accept(ModBlocks.ALIEN_LAMP.get()); + output.accept(ModBlocks.ALIEN_CRYSTAL_BLOCK.get()); output.accept(ModItems.STAR_GUIDE_BOOK.get()); // Spawn eggs. output.accept(ModItems.XERTZ_STALKER_SPAWN_EGG.get()); output.accept(ModItems.QUARTZ_CRAWLER_SPAWN_EGG.get()); output.accept(ModItems.GREENLING_SPAWN_EGG.get()); + output.accept(ModItems.ALIEN_VILLAGER_SPAWN_EGG.get()); output.accept(ModItems.CINDER_STALKER_SPAWN_EGG.get()); output.accept(ModItems.FROST_STRIDER_SPAWN_EGG.get()); output.accept(ModItems.MEADOW_LOPER_SPAWN_EGG.get()); diff --git a/src/main/java/za/co/neroland/nerospace/registry/ModEntities.java b/src/main/java/za/co/neroland/nerospace/registry/ModEntities.java index 9094c93..36ea272 100644 --- a/src/main/java/za/co/neroland/nerospace/registry/ModEntities.java +++ b/src/main/java/za/co/neroland/nerospace/registry/ModEntities.java @@ -8,6 +8,7 @@ import net.neoforged.neoforge.registries.DeferredRegister; import za.co.neroland.nerospace.Nerospace; +import za.co.neroland.nerospace.entity.AlienVillager; import za.co.neroland.nerospace.entity.CinderStalker; import za.co.neroland.nerospace.entity.EmberStrutter; import za.co.neroland.nerospace.entity.FrostStrider; @@ -61,6 +62,23 @@ public final class ModEntities { MobCategory.AMBIENT, builder -> builder.sized(0.5F, 0.6F).eyeHeight(0.45F).clientTrackingRange(8)); + // --- Alien Villagers (ALIEN_VILLAGERS_DESIGN.md, Phase 0) --------------- + + /** Social alien NPC; wary-neutral wanderer for now (trading/reputation arrive later). */ + public static final Supplier> ALIEN_VILLAGER = ENTITY_TYPES.registerEntityType( + "alien_villager", + AlienVillager::new, + MobCategory.CREATURE, + builder -> builder.sized(0.6F, 1.95F).eyeHeight(1.7F).clientTrackingRange(10)); + + /** Ruin Warden — boss guardian of ruins / the mega-city keep (structure-spawned). */ + public static final Supplier> RUIN_WARDEN = + ENTITY_TYPES.registerEntityType( + "ruin_warden", + za.co.neroland.nerospace.entity.RuinWarden::new, + MobCategory.MONSTER, + builder -> builder.sized(1.4F, 3.0F).eyeHeight(2.6F).clientTrackingRange(10)); + // --- Cindara creatures (Phase 7) ---------------------------------------- public static final Supplier> CINDER_STALKER = ENTITY_TYPES.registerEntityType( diff --git a/src/main/java/za/co/neroland/nerospace/registry/ModFeatures.java b/src/main/java/za/co/neroland/nerospace/registry/ModFeatures.java new file mode 100644 index 0000000..a01172a --- /dev/null +++ b/src/main/java/za/co/neroland/nerospace/registry/ModFeatures.java @@ -0,0 +1,37 @@ +package za.co.neroland.nerospace.registry; + +import java.util.function.Supplier; + +import net.minecraft.core.registries.Registries; +import net.minecraft.world.level.levelgen.feature.Feature; +import net.minecraft.world.level.levelgen.feature.configurations.NoneFeatureConfiguration; +import net.neoforged.bus.api.IEventBus; +import net.neoforged.neoforge.registries.DeferredRegister; + +import za.co.neroland.nerospace.Nerospace; +import za.co.neroland.nerospace.world.HamletFeature; +import za.co.neroland.nerospace.world.MegaCityFeature; +import za.co.neroland.nerospace.world.RuinFeature; + +/** Custom worldgen features: hamlet outpost (P3), ancient ruin (P7), mega-city (finale). */ +public final class ModFeatures { + + public static final DeferredRegister> FEATURES = + DeferredRegister.create(Registries.FEATURE, Nerospace.MODID); + + public static final Supplier> HAMLET = + FEATURES.register("hamlet", () -> new HamletFeature(NoneFeatureConfiguration.CODEC)); + + public static final Supplier> RUIN = + FEATURES.register("ruin", () -> new RuinFeature(NoneFeatureConfiguration.CODEC)); + + public static final Supplier> MEGA_CITY = + FEATURES.register("mega_city", () -> new MegaCityFeature(NoneFeatureConfiguration.CODEC)); + + private ModFeatures() { + } + + public static void register(IEventBus modEventBus) { + FEATURES.register(modEventBus); + } +} diff --git a/src/main/java/za/co/neroland/nerospace/registry/ModItems.java b/src/main/java/za/co/neroland/nerospace/registry/ModItems.java index 1f404ff..f60f492 100644 --- a/src/main/java/za/co/neroland/nerospace/registry/ModItems.java +++ b/src/main/java/za/co/neroland/nerospace/registry/ModItems.java @@ -433,6 +433,21 @@ public final class ModItems { // --- Star Guide (progression block, 1.0) --------------------------------- public static final DeferredItem STAR_GUIDE_ITEM = ITEMS.registerSimpleBlockItem(ModBlocks.STAR_GUIDE); + public static final DeferredItem VILLAGE_CORE_ITEM = + ITEMS.registerSimpleBlockItem(ModBlocks.VILLAGE_CORE); + + // Exclusive Artificer gear (ALIEN_VILLAGERS_DESIGN.md §6.1) — trade-only. + public static final DeferredItem GRAV_STRIDERS = ITEMS.registerSimpleItem("grav_striders"); + public static final DeferredItem XERTZ_RESONATOR = ITEMS.registerItem( + "xertz_resonator", props -> new za.co.neroland.nerospace.gear.XertzResonatorItem(props)); + // Greenxertz decoration block items (§8). + public static final DeferredItem ALIEN_BRICKS_ITEM = ITEMS.registerSimpleBlockItem(ModBlocks.ALIEN_BRICKS); + public static final DeferredItem CRACKED_ALIEN_BRICKS_ITEM = ITEMS.registerSimpleBlockItem(ModBlocks.CRACKED_ALIEN_BRICKS); + public static final DeferredItem ALIEN_TILE_ITEM = ITEMS.registerSimpleBlockItem(ModBlocks.ALIEN_TILE); + public static final DeferredItem ALIEN_PILLAR_ITEM = ITEMS.registerSimpleBlockItem(ModBlocks.ALIEN_PILLAR); + public static final DeferredItem ALIEN_LAMP_ITEM = ITEMS.registerSimpleBlockItem(ModBlocks.ALIEN_LAMP); + public static final DeferredItem ALIEN_CRYSTAL_BLOCK_ITEM = ITEMS.registerSimpleBlockItem(ModBlocks.ALIEN_CRYSTAL_BLOCK); + public static final DeferredItem STAR_GUIDE_BOOK = ITEMS.registerItem( "star_guide_book", props -> new za.co.neroland.nerospace.item.StarGuideBookItem(props.stacksTo(1))); @@ -443,6 +458,8 @@ public final class ModItems { "quartz_crawler_spawn_egg", props -> new NerospaceSpawnEggItem(props, ModEntities.QUARTZ_CRAWLER)); public static final DeferredItem GREENLING_SPAWN_EGG = ITEMS.registerItem( "greenling_spawn_egg", props -> new NerospaceSpawnEggItem(props, ModEntities.GREENLING)); + public static final DeferredItem ALIEN_VILLAGER_SPAWN_EGG = ITEMS.registerItem( + "alien_villager_spawn_egg", props -> new NerospaceSpawnEggItem(props, ModEntities.ALIEN_VILLAGER)); public static final DeferredItem CINDER_STALKER_SPAWN_EGG = ITEMS.registerItem( "cinder_stalker_spawn_egg", props -> new NerospaceSpawnEggItem(props, ModEntities.CINDER_STALKER)); public static final DeferredItem FROST_STRIDER_SPAWN_EGG = ITEMS.registerItem( diff --git a/src/main/java/za/co/neroland/nerospace/village/AlienTrades.java b/src/main/java/za/co/neroland/nerospace/village/AlienTrades.java new file mode 100644 index 0000000..b9da3fd --- /dev/null +++ b/src/main/java/za/co/neroland/nerospace/village/AlienTrades.java @@ -0,0 +1,72 @@ +package za.co.neroland.nerospace.village; + +import java.util.Optional; + +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.item.trading.ItemCost; +import net.minecraft.world.item.trading.MerchantOffer; +import net.minecraft.world.item.trading.MerchantOffers; +import net.minecraft.world.level.ItemLike; + +import za.co.neroland.nerospace.registry.ModItems; + +/** + * Tier-gated trade tables for the Greenxertz alien villagers (ALIEN_VILLAGERS_DESIGN.md §6). Offers + * are cumulative: a tier-N villager offers everything unlocked at tiers 1..N. Spans universal + * materials, nerospace progression, rare alien goods, and — at the top tiers — the exclusive Artificer + * gear. Emeralds are the currency so the trades are useful in any modpack. + */ +public final class AlienTrades { + + private static final float PRICE_MULT = 0.05F; + + private AlienTrades() { + } + + public static MerchantOffers forTier(int tier) { + MerchantOffers offers = new MerchantOffers(); + + if (tier >= 1) { + offers.add(sell(ModItems.XERTZ_QUARTZ.get(), 12, Items.EMERALD, 1, 16, 1)); + offers.add(buy(Items.EMERALD, 1, Items.IRON_INGOT, 3, 16, 1)); + offers.add(buy(Items.EMERALD, 2, Items.BREAD, 6, 16, 1)); + } + if (tier >= 2) { + offers.add(buy(Items.EMERALD, 4, ModItems.NEROSIUM_INGOT.get(), 1, 12, 5)); + offers.add(buy2(Items.EMERALD, 1, ModItems.RAW_NEROSTEEL.get(), 8, + ModItems.NEROSTEEL_INGOT.get(), 4, 12, 5)); + offers.add(sell(ModItems.ALIEN_FRAGMENT.get(), 4, Items.EMERALD, 1, 12, 2)); + } + if (tier >= 3) { + offers.add(buy(Items.EMERALD, 8, Items.DIAMOND, 1, 6, 10)); + offers.add(buy(Items.EMERALD, 5, ModItems.ROCKET_FUEL_CANISTER.get(), 1, 8, 8)); + } + if (tier >= 4) { + offers.add(buy2(Items.EMERALD, 12, ModItems.ALIEN_TECH_SCRAP.get(), 2, + ModItems.ALIEN_CORE.get(), 1, 4, 15)); + // Exclusive Artificer gear (§6.1). + offers.add(buy(Items.EMERALD, 16, ModItems.XERTZ_RESONATOR.get(), 1, 2, 15)); + } + if (tier >= 5) { + offers.add(buy(Items.EMERALD, 18, Items.DIAMOND, 3, 4, 20)); + offers.add(buy2(ModItems.ALIEN_CORE.get(), 1, Items.EMERALD, 24, + ModItems.GRAV_STRIDERS.get(), 1, 2, 20)); + } + return offers; + } + + private static MerchantOffer buy(ItemLike cost, int n, ItemLike result, int rc, int maxUses, int xp) { + return new MerchantOffer(new ItemCost(cost, n), new ItemStack(result, rc), maxUses, xp, PRICE_MULT); + } + + private static MerchantOffer buy2(ItemLike costA, int a, ItemLike costB, int b, + ItemLike result, int rc, int maxUses, int xp) { + return new MerchantOffer(new ItemCost(costA, a), Optional.of(new ItemCost(costB, b)), + new ItemStack(result, rc), maxUses, xp, PRICE_MULT); + } + + private static MerchantOffer sell(ItemLike cost, int n, ItemLike result, int rc, int maxUses, int xp) { + return buy(cost, n, result, rc, maxUses, xp); + } +} diff --git a/src/main/java/za/co/neroland/nerospace/village/Reputation.java b/src/main/java/za/co/neroland/nerospace/village/Reputation.java new file mode 100644 index 0000000..140c8a7 --- /dev/null +++ b/src/main/java/za/co/neroland/nerospace/village/Reputation.java @@ -0,0 +1,37 @@ +package za.co.neroland.nerospace.village; + +/** + * Reputation tier math for the Alien Villagers (ALIEN_VILLAGERS_DESIGN.md §3). A per-player score in + * {@code [0, MAX]} maps to 6 tiers (T0 Stranger → T5 Kin). Tiers gate trades now, and will gate + * teaching/structure access in later phases. + * + *

Interim note (Phase 2): reputation is stored per-villager (on the entity). When the Village Core + * block arrives (Phase 3/4) it will aggregate reputation per-village, as the design specifies. + */ +public final class Reputation { + + public static final int MAX = 100; + + /** Minimum score for each tier T0..T5. */ + private static final int[] TIER_MIN = {0, 10, 25, 45, 70, 95}; + + public static final int MAX_TIER = TIER_MIN.length - 1; + + private Reputation() { + } + + /** The tier (0..5) for a raw reputation score. */ + public static int tier(int score) { + int t = 0; + for (int i = 0; i < TIER_MIN.length; i++) { + if (score >= TIER_MIN[i]) { + t = i; + } + } + return t; + } + + public static int clamp(int score) { + return Math.max(0, Math.min(MAX, score)); + } +} diff --git a/src/main/java/za/co/neroland/nerospace/village/VillageBuildings.java b/src/main/java/za/co/neroland/nerospace/village/VillageBuildings.java new file mode 100644 index 0000000..df266cd --- /dev/null +++ b/src/main/java/za/co/neroland/nerospace/village/VillageBuildings.java @@ -0,0 +1,114 @@ +package za.co.neroland.nerospace.village; + +import java.util.ArrayList; +import java.util.List; + +import net.minecraft.world.item.Item; +import net.minecraft.world.item.Items; + +import za.co.neroland.nerospace.registry.ModItems; + +/** + * The Village Core's building catalogue + quest table (ALIEN_VILLAGERS_DESIGN.md §4, §7). Phase 4 + * proved the teach-and-grow loop with building shells; Phase 5 makes them produce goods, adds fetch + * quests, and (in the core) config-gated raids. + */ +public final class VillageBuildings { + + public enum Kind { WALL, ROOF, LIGHT, AIR } + + public record Placement(int dx, int dy, int dz, Kind kind) { + } + + /** A teachable building: required reputation tier, nerosteel cost, footprint size and wall height. */ + public enum Type { + HUT(2, 32, 5, 4), + WORKSHOP(3, 48, 7, 5); + + public final int reqTier; + public final int cost; + public final int size; + public final int height; + + Type(int reqTier, int cost, int size, int height) { + this.reqTier = reqTier; + this.cost = cost; + this.size = size; + this.height = height; + } + + public static Type byOrdinalOrNull(int i) { + Type[] v = values(); + return (i >= 0 && i < v.length) ? v[i] : null; + } + } + + /** A fetch quest the village posts: bring N of an item for a reputation + emerald reward. */ + public enum Quest { + XERTZ_QUARTZ(8, 2), + RAW_NEROSTEEL(12, 3), + ALIEN_FRAGMENT(4, 4); + + public final int count; + public final int reward; // emeralds paid + reputation granted + + Quest(int count, int reward) { + this.count = count; + this.reward = reward; + } + + public Item item() { + return switch (this) { + case XERTZ_QUARTZ -> ModItems.XERTZ_QUARTZ.get(); + case RAW_NEROSTEEL -> ModItems.RAW_NEROSTEEL.get(); + case ALIEN_FRAGMENT -> ModItems.ALIEN_FRAGMENT.get(); + }; + } + + public Item rewardItem() { + return Items.EMERALD; + } + + public static Quest byOrdinalOrNull(int i) { + Quest[] v = values(); + return (i >= 0 && i < v.length) ? v[i] : null; + } + } + + /** Buildings are constructed in this order as the village grows. */ + public static Type[] order() { + return new Type[] {Type.HUT, Type.WORKSHOP}; + } + + private VillageBuildings() { + } + + /** + * The ordered block placements (relative to the plot origin) for a building, bottom layer first so + * it visibly rises. WALL/ROOF map to nerosteel, LIGHT to a glow source, AIR clears headroom. + */ + public static List build(Type t) { + List out = new ArrayList<>(); + int r = t.size / 2; + for (int dy = 0; dy <= t.height; dy++) { + for (int dx = -r; dx <= r; dx++) { + for (int dz = -r; dz <= r; dz++) { + boolean perimeter = Math.abs(dx) == r || Math.abs(dz) == r; + if (dy == t.height) { + out.add(new Placement(dx, dy, dz, Kind.ROOF)); + } else if (perimeter) { + boolean door = dz == -r && dx == 0 && dy <= 1; + if (!door) { + out.add(new Placement(dx, dy, dz, Kind.WALL)); + } + } else if (dy == 0 && dx == 0 && dz == 0) { + out.add(new Placement(0, 0, 0, Kind.LIGHT)); + } else { + out.add(new Placement(dx, dy, dz, Kind.AIR)); + } + } + } + } + return out; + } +} diff --git a/src/main/java/za/co/neroland/nerospace/village/VillageCoreBlock.java b/src/main/java/za/co/neroland/nerospace/village/VillageCoreBlock.java new file mode 100644 index 0000000..c7e9b1c --- /dev/null +++ b/src/main/java/za/co/neroland/nerospace/village/VillageCoreBlock.java @@ -0,0 +1,114 @@ +package za.co.neroland.nerospace.village; + +import com.mojang.serialization.MapCodec; + +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.BaseEntityBlock; +import net.minecraft.world.level.block.RenderShape; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.BlockEntityTicker; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.BlockHitResult; + +import za.co.neroland.nerospace.registry.ModBlockEntities; +import za.co.neroland.nerospace.registry.ModBlocks; + +/** + * Village Core (ALIEN_VILLAGERS_DESIGN.md §4.1). Right-click to claim / teach the next building; + * right-click with Nerosteel to stock materials, or with a quest item to hand it in; sneak-right-click + * to collect produced goods and read the village's current task. It ticks construction, production + * and raids via {@link VillageCoreBlockEntity#serverTick}. + */ +public class VillageCoreBlock extends BaseEntityBlock { + + public static final MapCodec CODEC = simpleCodec(VillageCoreBlock::new); + + public VillageCoreBlock(Properties properties) { + super(properties); + } + + @Override + protected MapCodec codec() { + return CODEC; + } + + @Override + protected RenderShape getRenderShape(BlockState state) { + return RenderShape.MODEL; + } + + @Override + public BlockEntity newBlockEntity(BlockPos pos, BlockState state) { + return new VillageCoreBlockEntity(pos, state); + } + + @Override + public BlockEntityTicker getTicker(Level level, BlockState state, + BlockEntityType type) { + if (level.isClientSide()) { + return null; + } + return createTickerHelper(type, ModBlockEntities.VILLAGE_CORE.get(), + (lvl, pos, st, be) -> be.serverTick(lvl, pos, st)); + } + + @Override + protected InteractionResult useItemOn(ItemStack stack, BlockState state, Level level, BlockPos pos, + Player player, InteractionHand hand, BlockHitResult hit) { + if (!(level.getBlockEntity(pos) instanceof VillageCoreBlockEntity core)) { + return super.useItemOn(stack, state, level, pos, player, hand, hit); + } + // Consume the interaction on the client (server is authoritative for deposits / quest hand-ins). + if (level.isClientSide()) { + return InteractionResult.SUCCESS; + } + if (stack.is(ModBlocks.NEROSTEEL_BLOCK.get().asItem())) { + if (core.isClaimed() && !core.isOwner(player)) { + player.sendSystemMessage(Component.translatable( + "message.nerospace.village_core.owned", core.getOwnerName())); + } else { + if (!core.isClaimed()) { + core.claim(player); + } + core.deposit(player, stack); + } + return InteractionResult.SUCCESS; + } + if (core.tryCompleteQuest(player, stack)) { + return InteractionResult.SUCCESS; + } + return super.useItemOn(stack, state, level, pos, player, hand, hit); + } + + @Override + protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, + BlockHitResult hit) { + if (!(level.getBlockEntity(pos) instanceof VillageCoreBlockEntity core)) { + return InteractionResult.PASS; + } + if (level.isClientSide()) { + return InteractionResult.SUCCESS; + } + if (player.isShiftKeyDown()) { + core.collectAndStatus(player); + return InteractionResult.SUCCESS; + } + if (!core.isClaimed()) { + core.claim(player); + player.sendSystemMessage(Component.translatable("message.nerospace.village_core.claimed")); + } else if (core.isOwner(player)) { + core.onUse(player); + } else { + player.sendSystemMessage(Component.translatable( + "message.nerospace.village_core.owned", core.getOwnerName())); + } + return InteractionResult.SUCCESS; + } +} diff --git a/src/main/java/za/co/neroland/nerospace/village/VillageCoreBlockEntity.java b/src/main/java/za/co/neroland/nerospace/village/VillageCoreBlockEntity.java new file mode 100644 index 0000000..8d0d1d2 --- /dev/null +++ b/src/main/java/za/co/neroland/nerospace/village/VillageCoreBlockEntity.java @@ -0,0 +1,373 @@ +package za.co.neroland.nerospace.village; + +import java.util.List; +import java.util.UUID; + +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.core.particles.ParticleTypes; +import net.minecraft.util.RandomSource; +import net.minecraft.world.entity.EntitySpawnReason; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.levelgen.Heightmap; +import net.minecraft.world.level.storage.ValueInput; +import net.minecraft.world.level.storage.ValueOutput; +import net.minecraft.world.phys.AABB; + +import za.co.neroland.nerospace.Config; +import za.co.neroland.nerospace.entity.AlienVillager; +import za.co.neroland.nerospace.registry.ModBlockEntities; +import za.co.neroland.nerospace.registry.ModBlocks; +import za.co.neroland.nerospace.registry.ModEntities; +import za.co.neroland.nerospace.village.VillageBuildings.Placement; +import za.co.neroland.nerospace.village.VillageBuildings.Quest; +import za.co.neroland.nerospace.village.VillageBuildings.Type; + +/** + * Village Core controller (ALIEN_VILLAGERS_DESIGN.md §4). Claimable (Phase 3); a teach-and-grow + * engine (Phase 4); and — Phase 5 — a functional hub: completed buildings produce goods the owner + * collects, the village posts fetch quests for reputation, and (config-gated) hostile raids test the + * settlement at night. + */ +public class VillageCoreBlockEntity extends BlockEntity { + + private static final int BUILD_INTERVAL = 5; + private static final int PRODUCE_INTERVAL = 1200; // ~1 min between yields + private static final int RAID_INTERVAL = 2400; // ~2 min between raid checks + private static final double SCAN_RADIUS = 32.0; + private static final double RAID_RANGE = 48.0; + private static final int OUTPUT_CAP = 64; + private static final int[][] PLOT_OFFSETS = {{8, 0}, {-8, 0}, {0, 8}, {0, -8}, {8, 8}, {-8, -8}}; + + private UUID owner; + private String ownerName = ""; + + private int stockpile; + private int builtCount; + + private Type jobType; + private int progress; + private int plotX; + private int plotY; + private int plotZ; + private int buildTick; + private transient List jobPlacements; + + // Phase 5 — production output, quest, timers. + private int outBread; + private int outIngot; + private int produceTick; + private int raidTick; + private int questOrdinal = -1; + + public VillageCoreBlockEntity(BlockPos pos, BlockState state) { + super(ModBlockEntities.VILLAGE_CORE.get(), pos, state); + } + + // --- Claiming ------------------------------------------------------------- + + public boolean isClaimed() { + return this.owner != null; + } + + public boolean isOwner(Player player) { + return player.getUUID().equals(this.owner); + } + + public String getOwnerName() { + return this.ownerName; + } + + public void claim(Player player) { + this.owner = player.getUUID(); + this.ownerName = player.getName().getString(); + if (this.questOrdinal < 0) { + rollQuest(); + } + setChanged(); + } + + // --- Teach-and-grow ------------------------------------------------------- + + public void deposit(Player player, ItemStack stack) { + int add = stack.getCount(); + if (add <= 0) { + return; + } + stack.shrink(add); + this.stockpile += add; + player.sendSystemMessage(Component.literal("Stockpile: " + this.stockpile + " Nerosteel.")); + setChanged(); + } + + public void onUse(Player player) { + if (this.jobType != null) { + int total = placements().size(); + int pct = total == 0 ? 100 : (int) (100.0 * this.progress / total); + player.sendSystemMessage(Component.literal("Constructing " + label(this.jobType) + "… " + pct + "%")); + return; + } + Type next = Type.byOrdinalOrNull(this.builtCount); + if (next == null) { + player.sendSystemMessage(Component.literal("The village is fully built. The aliens are grateful.")); + return; + } + int tier = villageTier(player); + if (tier < next.reqTier) { + player.sendSystemMessage(Component.literal( + "The villagers won't follow your plans yet — reach trust tier " + next.reqTier + + " (you are tier " + tier + ").")); + return; + } + if (this.stockpile < next.cost) { + player.sendSystemMessage(Component.literal( + "Teaching the " + label(next) + " needs " + next.cost + " Nerosteel in the stockpile (have " + + this.stockpile + ").")); + return; + } + this.stockpile -= next.cost; + int[] off = PLOT_OFFSETS[Math.min(this.builtCount, PLOT_OFFSETS.length - 1)]; + this.plotX = this.worldPosition.getX() + off[0]; + this.plotZ = this.worldPosition.getZ() + off[1]; + this.plotY = this.level != null + ? this.level.getHeight(Heightmap.Types.WORLD_SURFACE, this.plotX, this.plotZ) + : this.worldPosition.getY(); + this.jobType = next; + this.progress = 0; + this.buildTick = 0; + this.jobPlacements = VillageBuildings.build(next); + player.sendSystemMessage(Component.literal("Construction begins: " + label(next) + ".")); + setChanged(); + } + + // --- Phase 5: production + quests + raids --------------------------------- + + /** Sneak-right-click: collect produced goods and read the current quest. */ + public void collectAndStatus(Player player) { + int bread = this.outBread; + int ingot = this.outIngot; + if (bread > 0) { + give(player, new ItemStack(Items.BREAD, bread)); + } + if (ingot > 0) { + give(player, new ItemStack(ModBlocks.NEROSTEEL_BLOCK.get().asItem(), ingot)); + } + this.outBread = 0; + this.outIngot = 0; + if (bread > 0 || ingot > 0) { + player.sendSystemMessage(Component.literal("Collected " + bread + " bread, " + ingot + " nerosteel.")); + } + Quest q = Quest.byOrdinalOrNull(this.questOrdinal); + if (q != null) { + player.sendSystemMessage(Component.literal( + "Village task: bring " + q.count + "x " + itemLabel(q) + " for " + q.reward + " emeralds + trust.")); + } + setChanged(); + } + + /** Right-click with the quest item: hand it in for the reward. */ + public boolean tryCompleteQuest(Player player, ItemStack stack) { + Quest q = Quest.byOrdinalOrNull(this.questOrdinal); + if (q == null || !stack.is(q.item()) || stack.getCount() < q.count) { + return false; + } + stack.shrink(q.count); + give(player, new ItemStack(q.rewardItem(), q.reward)); + grantVillageReputation(player, q.reward); + player.sendSystemMessage(Component.literal("The villagers thank you. (+" + q.reward + " trust)")); + rollQuest(); + setChanged(); + return true; + } + + public void serverTick(Level level, BlockPos pos, BlockState state) { + // Construction. + if (this.jobType != null) { + tickConstruction(level); + } + if (!this.isClaimed()) { + return; + } + // Passive production from completed buildings. + if (++this.produceTick >= PRODUCE_INTERVAL) { + this.produceTick = 0; + if (this.builtCount >= 1) { + this.outBread = Math.min(OUTPUT_CAP, this.outBread + 1); // Hut → food + } + if (this.builtCount >= 2) { + this.outIngot = Math.min(OUTPUT_CAP, this.outIngot + 1); // Workshop → nerosteel + } + setChanged(); + } + // Config-gated night raids. + if (++this.raidTick >= RAID_INTERVAL) { + this.raidTick = 0; + maybeRaid(level, pos); + } + } + + private void tickConstruction(Level level) { + List list = placements(); + if (this.progress >= list.size()) { + finishJob(); + return; + } + if (++this.buildTick < BUILD_INTERVAL) { + return; + } + this.buildTick = 0; + Placement p = list.get(this.progress++); + BlockPos bp = new BlockPos(this.plotX + p.dx(), this.plotY + p.dy(), this.plotZ + p.dz()); + BlockState bs = switch (p.kind()) { + case WALL, ROOF -> ModBlocks.NEROSTEEL_BLOCK.get().defaultBlockState(); + case LIGHT -> Blocks.GLOWSTONE.defaultBlockState(); + case AIR -> Blocks.AIR.defaultBlockState(); + }; + level.setBlock(bp, bs, 3); + if (level instanceof ServerLevel server) { + server.sendParticles(ParticleTypes.HAPPY_VILLAGER, + bp.getX() + 0.5, bp.getY() + 0.5, bp.getZ() + 0.5, 2, 0.3, 0.3, 0.3, 0.0); + } + setChanged(); + if (this.progress >= list.size()) { + finishJob(); + } + } + + private void maybeRaid(Level level, BlockPos pos) { + if (!Config.ALIEN_RAIDS_ENABLED.get() || !(level instanceof ServerLevel server)) { + return; + } + if (level.isBrightOutside()) { + return; // raids only after dark + } + Player near = level.getNearestPlayer(pos.getX(), pos.getY(), pos.getZ(), RAID_RANGE, false); + if (near == null) { + return; + } + RandomSource rand = level.getRandom(); + int waves = 1 + rand.nextInt(2); + for (int i = 0; i < waves; i++) { + int ox = pos.getX() + (rand.nextBoolean() ? 1 : -1) * (10 + rand.nextInt(8)); + int oz = pos.getZ() + (rand.nextBoolean() ? 1 : -1) * (10 + rand.nextInt(8)); + int oy = level.getHeight(Heightmap.Types.WORLD_SURFACE, ox, oz); + ModEntities.XERTZ_STALKER.get().spawn(server, new BlockPos(ox, oy, oz), EntitySpawnReason.EVENT); + } + } + + private void finishJob() { + this.builtCount++; + this.jobType = null; + this.jobPlacements = null; + this.progress = 0; + setChanged(); + } + + private List placements() { + if (this.jobPlacements == null && this.jobType != null) { + this.jobPlacements = VillageBuildings.build(this.jobType); + } + return this.jobPlacements == null ? List.of() : this.jobPlacements; + } + + private int villageTier(Player player) { + if (this.level == null) { + return 0; + } + AABB box = new AABB(this.worldPosition).inflate(SCAN_RADIUS); + int max = 0; + for (AlienVillager v : this.level.getEntitiesOfClass(AlienVillager.class, box)) { + max = Math.max(max, v.getTier(player)); + } + return max; + } + + private void grantVillageReputation(Player player, int amount) { + if (this.level == null) { + return; + } + AABB box = new AABB(this.worldPosition).inflate(SCAN_RADIUS); + for (AlienVillager v : this.level.getEntitiesOfClass(AlienVillager.class, box)) { + v.addReputation(player, amount); + } + } + + private void rollQuest() { + RandomSource rand = this.level != null ? this.level.getRandom() : RandomSource.create(); + this.questOrdinal = rand.nextInt(Quest.values().length); + } + + private void give(Player player, ItemStack stack) { + if (!player.addItem(stack)) { + player.drop(stack, false); + } + } + + private static String label(Type t) { + return switch (t) { + case HUT -> "Hut"; + case WORKSHOP -> "Workshop"; + }; + } + + private static String itemLabel(Quest q) { + return switch (q) { + case XERTZ_QUARTZ -> "Xertz Quartz"; + case RAW_NEROSTEEL -> "Raw Nerosteel"; + case ALIEN_FRAGMENT -> "Alien Fragment"; + }; + } + + // --- Persistence ---------------------------------------------------------- + + @Override + protected void saveAdditional(ValueOutput output) { + super.saveAdditional(output); + output.putString("Owner", this.owner == null ? "" : this.owner.toString()); + output.putString("OwnerName", this.ownerName); + output.putInt("Stockpile", this.stockpile); + output.putInt("BuiltCount", this.builtCount); + output.putInt("JobType", this.jobType == null ? -1 : this.jobType.ordinal()); + output.putInt("Progress", this.progress); + output.putInt("PlotX", this.plotX); + output.putInt("PlotY", this.plotY); + output.putInt("PlotZ", this.plotZ); + output.putInt("OutBread", this.outBread); + output.putInt("OutIngot", this.outIngot); + output.putInt("Quest", this.questOrdinal); + } + + @Override + protected void loadAdditional(ValueInput input) { + super.loadAdditional(input); + String stored = input.getStringOr("Owner", ""); + if (stored.isEmpty()) { + this.owner = null; + } else { + try { + this.owner = UUID.fromString(stored); + } catch (IllegalArgumentException ex) { + this.owner = null; + } + } + this.ownerName = input.getStringOr("OwnerName", ""); + this.stockpile = input.getIntOr("Stockpile", 0); + this.builtCount = input.getIntOr("BuiltCount", 0); + this.jobType = Type.byOrdinalOrNull(input.getIntOr("JobType", -1)); + this.progress = input.getIntOr("Progress", 0); + this.plotX = input.getIntOr("PlotX", 0); + this.plotY = input.getIntOr("PlotY", 0); + this.plotZ = input.getIntOr("PlotZ", 0); + this.outBread = input.getIntOr("OutBread", 0); + this.outIngot = input.getIntOr("OutIngot", 0); + this.questOrdinal = input.getIntOr("Quest", -1); + this.jobPlacements = null; + } +} diff --git a/src/main/java/za/co/neroland/nerospace/world/AlienBuild.java b/src/main/java/za/co/neroland/nerospace/world/AlienBuild.java new file mode 100644 index 0000000..7465c61 --- /dev/null +++ b/src/main/java/za/co/neroland/nerospace/world/AlienBuild.java @@ -0,0 +1,136 @@ +package za.co.neroland.nerospace.world; + +import net.minecraft.core.BlockPos; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.WorldGenLevel; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.BlockState; + +import za.co.neroland.nerospace.registry.ModBlocks; + +/** + * Shared procedural pieces for the alien structures — a small kit that makes the hamlet, ruin and + * mega-city read as futuristic alien architecture rather than plain boxes: tile podiums, brick walls + * with glowing crystal window bands, taller lit corner pillars, tapered roofs and crystal spires. + * Built entirely from the alien decoration block set (ALIEN_VILLAGERS_DESIGN.md §8). + */ +public final class AlienBuild { + + private AlienBuild() { + } + + public static BlockState bricks() { + return ModBlocks.ALIEN_BRICKS.get().defaultBlockState(); + } + + public static BlockState cracked() { + return ModBlocks.CRACKED_ALIEN_BRICKS.get().defaultBlockState(); + } + + public static BlockState tile() { + return ModBlocks.ALIEN_TILE.get().defaultBlockState(); + } + + public static BlockState pillar() { + return ModBlocks.ALIEN_PILLAR.get().defaultBlockState(); + } + + public static BlockState lamp() { + return ModBlocks.ALIEN_LAMP.get().defaultBlockState(); + } + + public static BlockState crystal() { + return ModBlocks.ALIEN_CRYSTAL_BLOCK.get().defaultBlockState(); + } + + public static BlockState air() { + return Blocks.AIR.defaultBlockState(); + } + + private static void set(WorldGenLevel level, BlockPos.MutableBlockPos m, int x, int y, int z, BlockState s) { + m.set(x, y, z); + level.setBlock(m, s, 2); + } + + /** + * A futuristic alien building. Tile podium, brick walls with a glowing crystal window band, taller + * lit corner pillars, a tapered roof and a crystal spire. {@code weathered} → ruined variant + * (cracked bricks, broken walls, no spire, dimmer). {@code r} = half-footprint, {@code height} = + * wall height. Origin is the building centre at ground level {@code baseY}. + */ + public static void tower(WorldGenLevel level, int cx, int baseY, int cz, int r, int height, + boolean weathered, RandomSource rand, BlockPos.MutableBlockPos m) { + BlockState wall = weathered ? cracked() : bricks(); + int band = Math.max(1, height / 2); + + // Podium (one wider than the walls) + clear the interior and headroom. + for (int dx = -r - 1; dx <= r + 1; dx++) { + for (int dz = -r - 1; dz <= r + 1; dz++) { + set(level, m, cx + dx, baseY - 1, cz + dz, tile()); + } + } + for (int dy = 0; dy <= height + 2; dy++) { + for (int dx = -r; dx <= r; dx++) { + for (int dz = -r; dz <= r; dz++) { + set(level, m, cx + dx, baseY + dy, cz + dz, air()); + } + } + } + + // Walls with a glowing window band + a front door gap. + for (int dy = 0; dy < height; dy++) { + for (int dx = -r; dx <= r; dx++) { + for (int dz = -r; dz <= r; dz++) { + boolean perimeter = Math.abs(dx) == r || Math.abs(dz) == r; + if (!perimeter) { + continue; + } + boolean corner = Math.abs(dx) == r && Math.abs(dz) == r; + if (corner) { + continue; // corners are pillars (below) + } + boolean door = dz == -r && dx == 0 && dy <= 1; + if (door) { + continue; + } + if (weathered && rand.nextFloat() < 0.28F) { + continue; // ruined gaps + } + boolean window = dy == band; + set(level, m, cx + dx, baseY + dy, cz + dz, window ? crystal() : wall); + } + } + } + + // Taller lit corner pillars. + int pillarTop = weathered ? height - 1 : height + 1; + for (int sx : new int[] {-r, r}) { + for (int sz : new int[] {-r, r}) { + for (int dy = 0; dy <= pillarTop; dy++) { + set(level, m, cx + sx, baseY + dy, cz + sz, pillar()); + } + if (!weathered) { + set(level, m, cx + sx, baseY + pillarTop + 1, cz + sz, lamp()); + } + } + } + + // Tapered roof (inset) + interior light. + for (int dx = -(r - 1); dx <= r - 1; dx++) { + for (int dz = -(r - 1); dz <= r - 1; dz++) { + if (weathered && rand.nextFloat() < 0.35F) { + continue; // collapsed roof + } + set(level, m, cx + dx, baseY + height, cz + dz, dx == 0 && dz == 0 ? crystal() : tile()); + } + } + set(level, m, cx, baseY + 1, cz, lamp()); + + // Crystal spire (intact buildings only). + if (!weathered) { + for (int dy = 1; dy <= 3; dy++) { + set(level, m, cx, baseY + height + dy, cz, crystal()); + } + } + } +} diff --git a/src/main/java/za/co/neroland/nerospace/world/HamletFeature.java b/src/main/java/za/co/neroland/nerospace/world/HamletFeature.java new file mode 100644 index 0000000..395d585 --- /dev/null +++ b/src/main/java/za/co/neroland/nerospace/world/HamletFeature.java @@ -0,0 +1,68 @@ +package za.co.neroland.nerospace.world; + +import com.mojang.serialization.Codec; + +import net.minecraft.core.BlockPos; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.WorldGenLevel; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.levelgen.feature.Feature; +import net.minecraft.world.level.levelgen.feature.FeaturePlaceContext; +import net.minecraft.world.level.levelgen.feature.configurations.NoneFeatureConfiguration; + +import za.co.neroland.nerospace.registry.ModBlocks; + +/** + * Hamlet (ALIEN_VILLAGERS_DESIGN.md §5, Phase 3 — redesigned) — a small alien outpost: a glowing + * tile plaza, a central {@link za.co.neroland.nerospace.village.VillageCoreBlock} on a lit podium, and + * two futuristic towers. Placement is gated by {@link StructureSpacing} for even spacing + density cap. + */ +public class HamletFeature extends Feature { + + private static final int PLAZA = 6; // 13x13 plaza + + public HamletFeature(Codec codec) { + super(codec); + } + + @Override + public boolean place(FeaturePlaceContext ctx) { + BlockPos o = ctx.origin(); + if (!StructureSpacing.shouldPlace(o, StructureSpacing.Roi.HAMLET)) { + return false; + } + WorldGenLevel level = ctx.level(); + RandomSource rand = ctx.random(); + int baseY = o.getY(); + BlockPos.MutableBlockPos m = new BlockPos.MutableBlockPos(); + + // Tile plaza with a lamp border. + for (int dx = -PLAZA; dx <= PLAZA; dx++) { + for (int dz = -PLAZA; dz <= PLAZA; dz++) { + m.set(o.getX() + dx, baseY - 1, o.getZ() + dz); + boolean edge = Math.abs(dx) == PLAZA || Math.abs(dz) == PLAZA; + level.setBlock(m, edge && (dx + dz) % 2 == 0 ? AlienBuild.lamp() : AlienBuild.tile(), 2); + for (int dy = 0; dy < 4; dy++) { + m.set(o.getX() + dx, baseY + dy, o.getZ() + dz); + level.setBlock(m, AlienBuild.air(), 2); + } + } + } + + // Two flanking towers. + AlienBuild.tower(level, o.getX() - 4, baseY, o.getZ() - 4, 2, 4, false, rand, m); + AlienBuild.tower(level, o.getX() + 4, baseY, o.getZ() + 4, 2, 4, false, rand, m); + + // Central Village Core on a lit crystal podium. + BlockState core = ModBlocks.VILLAGE_CORE.get().defaultBlockState(); + m.set(o.getX(), baseY - 1, o.getZ()); + level.setBlock(m, AlienBuild.crystal(), 2); + m.set(o.getX(), baseY, o.getZ()); + level.setBlock(m, core, 2); + for (int[] off : new int[][] {{-2, 0}, {2, 0}, {0, -2}, {0, 2}}) { + m.set(o.getX() + off[0], baseY, o.getZ() + off[1]); + level.setBlock(m, AlienBuild.lamp(), 2); + } + return true; + } +} diff --git a/src/main/java/za/co/neroland/nerospace/world/MegaCityFeature.java b/src/main/java/za/co/neroland/nerospace/world/MegaCityFeature.java new file mode 100644 index 0000000..ccec434 --- /dev/null +++ b/src/main/java/za/co/neroland/nerospace/world/MegaCityFeature.java @@ -0,0 +1,113 @@ +package za.co.neroland.nerospace.world; + +import com.mojang.serialization.Codec; + +import net.minecraft.core.BlockPos; +import net.minecraft.util.RandomSource; +import net.minecraft.world.entity.EntitySpawnReason; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.level.WorldGenLevel; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.entity.ChestBlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.levelgen.Heightmap; +import net.minecraft.world.level.levelgen.feature.Feature; +import net.minecraft.world.level.levelgen.feature.FeaturePlaceContext; +import net.minecraft.world.level.levelgen.feature.configurations.NoneFeatureConfiguration; + +import za.co.neroland.nerospace.registry.ModBlocks; +import za.co.neroland.nerospace.registry.ModEntities; +import za.co.neroland.nerospace.registry.ModItems; + +/** + * Living Mega-City (ALIEN_VILLAGERS_DESIGN.md §5.2 / §7 — redesigned) — the massive end-state alien + * settlement: a pillared, lit, crenellated curtain wall with four gates around a glowing tile plaza of + * futuristic towers, and a central keep guarded by the {@link za.co.neroland.nerospace.entity.RuinWarden} + * boss over a grand vault. Very rare and spaced via {@link StructureSpacing}. + */ +public class MegaCityFeature extends Feature { + + private static final int WALL_R = 20; // 41x41 footprint + private static final int WALL_H = 6; + + public MegaCityFeature(Codec codec) { + super(codec); + } + + @Override + public boolean place(FeaturePlaceContext ctx) { + BlockPos o = ctx.origin(); + if (!StructureSpacing.shouldPlace(o, StructureSpacing.Roi.MEGA_CITY)) { + return false; + } + WorldGenLevel level = ctx.level(); + RandomSource rand = ctx.random(); + int baseY = o.getY(); + BlockPos.MutableBlockPos m = new BlockPos.MutableBlockPos(); + BlockState bricks = AlienBuild.bricks(); + + // Plaza floor + curtain wall (pillars every 5, crenellations, lamps, gated mid-sides). + for (int dx = -WALL_R; dx <= WALL_R; dx++) { + for (int dz = -WALL_R; dz <= WALL_R; dz++) { + int x = o.getX() + dx; + int z = o.getZ() + dz; + m.set(x, baseY - 1, z); + level.setBlock(m, AlienBuild.tile(), 2); + boolean perimeter = Math.abs(dx) == WALL_R || Math.abs(dz) == WALL_R; + boolean gate = (Math.abs(dx) <= 1 && Math.abs(dz) == WALL_R) + || (Math.abs(dz) <= 1 && Math.abs(dx) == WALL_R); + if (!perimeter || gate) { + continue; + } + boolean pillar = (dx % 5 == 0) || (dz % 5 == 0) || (Math.abs(dx) == WALL_R && Math.abs(dz) == WALL_R); + int top = pillar ? WALL_H + 1 : WALL_H - 1; + for (int dy = 0; dy <= top; dy++) { + m.set(x, baseY + dy, z); + level.setBlock(m, pillar ? AlienBuild.pillar() : bricks, 2); + } + // Crenellations + pillar-cap lamps. + if (pillar) { + m.set(x, baseY + top + 1, z); + level.setBlock(m, AlienBuild.lamp(), 2); + } else if ((dx + dz) % 2 == 0) { + m.set(x, baseY + top + 1, z); + level.setBlock(m, bricks, 2); + } + } + } + // Gate-post lamps. + for (int[] g : new int[][] {{0, WALL_R}, {0, -WALL_R}, {WALL_R, 0}, {-WALL_R, 0}}) { + for (int s : new int[] {-2, 2}) { + int gx = o.getX() + (g[0] == 0 ? s : g[0]); + int gz = o.getZ() + (g[1] == 0 ? s : g[1]); + m.set(gx, baseY + WALL_H, gz); + level.setBlock(m, AlienBuild.lamp(), 2); + } + } + + // Four district towers. + AlienBuild.tower(level, o.getX() - 11, baseY, o.getZ() - 11, 3, 6, false, rand, m); + AlienBuild.tower(level, o.getX() + 11, baseY, o.getZ() - 11, 3, 6, false, rand, m); + AlienBuild.tower(level, o.getX() - 11, baseY, o.getZ() + 11, 3, 6, false, rand, m); + AlienBuild.tower(level, o.getX() + 11, baseY, o.getZ() + 11, 3, 5, false, rand, m); + + // Central keep + boss + grand vault. + AlienBuild.tower(level, o.getX(), baseY, o.getZ(), 5, 8, false, rand, m); + m.set(o.getX(), baseY, o.getZ()); + level.setBlock(m, ModBlocks.VILLAGE_CORE.get().defaultBlockState(), 2); + BlockPos chestPos = new BlockPos(o.getX() + 2, baseY, o.getZ() + 2); + level.setBlock(chestPos, Blocks.CHEST.defaultBlockState(), 2); + if (level.getBlockEntity(chestPos) instanceof ChestBlockEntity chest) { + chest.setItem(4, new ItemStack(ModItems.ALIEN_CORE.get(), 2 + rand.nextInt(3))); + chest.setItem(6, new ItemStack(ModItems.GRAV_STRIDERS.get(), 1)); + chest.setItem(10, new ItemStack(ModItems.XERTZ_RESONATOR.get(), 1)); + chest.setItem(13, new ItemStack(Items.DIAMOND, 4 + rand.nextInt(6))); + chest.setItem(22, new ItemStack(Items.EMERALD, 12 + rand.nextInt(12))); + } + int by = level.getHeight(Heightmap.Types.WORLD_SURFACE, o.getX(), o.getZ()); + ModEntities.RUIN_WARDEN.get().spawn(level.getLevel(), new BlockPos(o.getX(), by, o.getZ()), + EntitySpawnReason.EVENT); + return true; + } +} diff --git a/src/main/java/za/co/neroland/nerospace/world/ModBiomes.java b/src/main/java/za/co/neroland/nerospace/world/ModBiomes.java index eec813c..a52c0f6 100644 --- a/src/main/java/za/co/neroland/nerospace/world/ModBiomes.java +++ b/src/main/java/za/co/neroland/nerospace/world/ModBiomes.java @@ -98,10 +98,15 @@ private static void matureBiome(BootstrapContext context, ResourceKey context) { placedFeatures.getOrThrow(ModPlacedFeatures.NEROSTEEL_ORE_PLACED)); generation.addFeature(GenerationStep.Decoration.UNDERGROUND_ORES, placedFeatures.getOrThrow(ModPlacedFeatures.XERTZ_QUARTZ_ORE_PLACED)); + // Alien hamlet outposts dot the surface (ALIEN_VILLAGERS_DESIGN.md §5, Phase 3). + generation.addFeature(GenerationStep.Decoration.SURFACE_STRUCTURES, + placedFeatures.getOrThrow(ModPlacedFeatures.HAMLET_PLACED)); + generation.addFeature(GenerationStep.Decoration.SURFACE_STRUCTURES, + placedFeatures.getOrThrow(ModPlacedFeatures.RUIN_PLACED)); + generation.addFeature(GenerationStep.Decoration.SURFACE_STRUCTURES, + placedFeatures.getOrThrow(ModPlacedFeatures.MEGA_CITY_PLACED)); // MC 26.1 trimmed BiomeSpecialEffects.Builder to water + grass/foliage colors; fog/sky/water-fog // colors are no longer set here. The green surface palette comes from the grass/foliage overrides. @@ -171,6 +183,9 @@ private static void greenxertz(BootstrapContext context) { new MobSpawnSettings.SpawnerData(ModEntities.QUARTZ_CRAWLER.get(), 1, 3)) .addSpawn(MobCategory.AMBIENT, 8, new MobSpawnSettings.SpawnerData(ModEntities.GREENLING.get(), 2, 4)) + // Alien Villagers (Phase 0): small, sparse social groups on the surface. + .addSpawn(MobCategory.CREATURE, 6, + new MobSpawnSettings.SpawnerData(ModEntities.ALIEN_VILLAGER.get(), 1, 3)) .build(); Biome biome = new Biome.BiomeBuilder() @@ -207,6 +222,8 @@ private static void cindara(BootstrapContext context) { MobSpawnSettings spawns = new MobSpawnSettings.Builder() .addSpawn(MobCategory.MONSTER, 14, new MobSpawnSettings.SpawnerData(ModEntities.CINDER_STALKER.get(), 1, 2)) + .addSpawn(MobCategory.CREATURE, 3, + new MobSpawnSettings.SpawnerData(ModEntities.ALIEN_VILLAGER.get(), 1, 2)) .build(); Biome biome = new Biome.BiomeBuilder() @@ -244,6 +261,8 @@ private static void glacira(BootstrapContext context) { MobSpawnSettings spawns = new MobSpawnSettings.Builder() .addSpawn(MobCategory.MONSTER, 14, new MobSpawnSettings.SpawnerData(ModEntities.FROST_STRIDER.get(), 1, 2)) + .addSpawn(MobCategory.CREATURE, 3, + new MobSpawnSettings.SpawnerData(ModEntities.ALIEN_VILLAGER.get(), 1, 2)) .build(); Biome biome = new Biome.BiomeBuilder() diff --git a/src/main/java/za/co/neroland/nerospace/world/ModConfiguredFeatures.java b/src/main/java/za/co/neroland/nerospace/world/ModConfiguredFeatures.java index 08ea40c..78d557b 100644 --- a/src/main/java/za/co/neroland/nerospace/world/ModConfiguredFeatures.java +++ b/src/main/java/za/co/neroland/nerospace/world/ModConfiguredFeatures.java @@ -30,6 +30,10 @@ public final class ModConfiguredFeatures { public static final ResourceKey> CINDRITE_ORE = registerKey("cindrite_ore"); // Glacira dimension ore (NEW_DESTINATION_DESIGN.md). public static final ResourceKey> GLACITE_ORE = registerKey("glacite_ore"); + /** Alien hamlet outpost (ALIEN_VILLAGERS_DESIGN.md §5, Phase 3). */ + public static final ResourceKey> HAMLET = registerKey("hamlet"); + public static final ResourceKey> RUIN = registerKey("ruin"); + public static final ResourceKey> MEGA_CITY = registerKey("mega_city"); private ModConfiguredFeatures() { } @@ -67,6 +71,18 @@ public static void bootstrap(BootstrapContext> context) OreConfiguration.target(stoneReplaceables, ModBlocks.GLACITE_ORE.get().defaultBlockState()), OreConfiguration.target(deepslateReplaceables, ModBlocks.GLACITE_ORE.get().defaultBlockState())); context.register(GLACITE_ORE, new ConfiguredFeature<>(Feature.ORE, new OreConfiguration(glaciteTargets, 8))); + + context.register(HAMLET, new ConfiguredFeature<>( + za.co.neroland.nerospace.registry.ModFeatures.HAMLET.get(), + net.minecraft.world.level.levelgen.feature.configurations.NoneFeatureConfiguration.INSTANCE)); + + context.register(RUIN, new ConfiguredFeature<>( + za.co.neroland.nerospace.registry.ModFeatures.RUIN.get(), + net.minecraft.world.level.levelgen.feature.configurations.NoneFeatureConfiguration.INSTANCE)); + + context.register(MEGA_CITY, new ConfiguredFeature<>( + za.co.neroland.nerospace.registry.ModFeatures.MEGA_CITY.get(), + net.minecraft.world.level.levelgen.feature.configurations.NoneFeatureConfiguration.INSTANCE)); } private static ResourceKey> registerKey(String name) { diff --git a/src/main/java/za/co/neroland/nerospace/world/ModPlacedFeatures.java b/src/main/java/za/co/neroland/nerospace/world/ModPlacedFeatures.java index dab236c..dcd4c61 100644 --- a/src/main/java/za/co/neroland/nerospace/world/ModPlacedFeatures.java +++ b/src/main/java/za/co/neroland/nerospace/world/ModPlacedFeatures.java @@ -40,6 +40,10 @@ public final class ModPlacedFeatures { public static final ResourceKey CINDRITE_ORE_PLACED = registerKey("cindrite_ore_placed"); // Glacira dimension ore (NEW_DESTINATION_DESIGN.md). public static final ResourceKey GLACITE_ORE_PLACED = registerKey("glacite_ore_placed"); + /** Rare surface alien hamlet (Phase 3). */ + public static final ResourceKey HAMLET_PLACED = registerKey("hamlet_placed"); + public static final ResourceKey RUIN_PLACED = registerKey("ruin_placed"); + public static final ResourceKey MEGA_CITY_PLACED = registerKey("mega_city_placed"); private ModPlacedFeatures() { } @@ -100,6 +104,33 @@ public static void bootstrap(BootstrapContext context) { InSquarePlacement.spread(), HeightRangePlacement.triangle(VerticalAnchor.absolute(-48), VerticalAnchor.absolute(48)), BiomeFilter.biome()))); + + context.register(HAMLET_PLACED, new PlacedFeature( + configuredFeatures.getOrThrow(ModConfiguredFeatures.HAMLET), + List.of( + CountPlacement.of(1), + InSquarePlacement.spread(), + net.minecraft.world.level.levelgen.placement.HeightmapPlacement.onHeightmap( + net.minecraft.world.level.levelgen.Heightmap.Types.WORLD_SURFACE_WG), + BiomeFilter.biome()))); + + context.register(RUIN_PLACED, new PlacedFeature( + configuredFeatures.getOrThrow(ModConfiguredFeatures.RUIN), + List.of( + CountPlacement.of(1), + InSquarePlacement.spread(), + net.minecraft.world.level.levelgen.placement.HeightmapPlacement.onHeightmap( + net.minecraft.world.level.levelgen.Heightmap.Types.WORLD_SURFACE_WG), + BiomeFilter.biome()))); + + context.register(MEGA_CITY_PLACED, new PlacedFeature( + configuredFeatures.getOrThrow(ModConfiguredFeatures.MEGA_CITY), + List.of( + CountPlacement.of(1), + InSquarePlacement.spread(), + net.minecraft.world.level.levelgen.placement.HeightmapPlacement.onHeightmap( + net.minecraft.world.level.levelgen.Heightmap.Types.WORLD_SURFACE_WG), + BiomeFilter.biome()))); } private static ResourceKey registerKey(String name) { diff --git a/src/main/java/za/co/neroland/nerospace/world/RuinFeature.java b/src/main/java/za/co/neroland/nerospace/world/RuinFeature.java new file mode 100644 index 0000000..f1a5d7c --- /dev/null +++ b/src/main/java/za/co/neroland/nerospace/world/RuinFeature.java @@ -0,0 +1,63 @@ +package za.co.neroland.nerospace.world; + +import com.mojang.serialization.Codec; + +import net.minecraft.core.BlockPos; +import net.minecraft.util.RandomSource; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.level.WorldGenLevel; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.entity.ChestBlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.levelgen.feature.Feature; +import net.minecraft.world.level.levelgen.feature.FeaturePlaceContext; +import net.minecraft.world.level.levelgen.feature.configurations.NoneFeatureConfiguration; + +import za.co.neroland.nerospace.registry.ModBlocks; +import za.co.neroland.nerospace.registry.ModItems; + +/** + * Ancient Ruin (ALIEN_VILLAGERS_DESIGN.md §5.2 / §7 — redesigned) — a derelict, half-buried alien + * hall built from cracked alien brick with collapsed walls and a dead crystal core, holding a loot + * vault of rare alien goods (and, with the boss available, a dangerous prize). Spaced + capped by + * {@link StructureSpacing}. + */ +public class RuinFeature extends Feature { + + public RuinFeature(Codec codec) { + super(codec); + } + + @Override + public boolean place(FeaturePlaceContext ctx) { + BlockPos o = ctx.origin(); + if (!StructureSpacing.shouldPlace(o, StructureSpacing.Roi.RUIN)) { + return false; + } + WorldGenLevel level = ctx.level(); + RandomSource rand = ctx.random(); + int baseY = o.getY() - 2; // sunken + BlockPos.MutableBlockPos m = new BlockPos.MutableBlockPos(); + + // A large weathered hall. + AlienBuild.tower(level, o.getX(), baseY, o.getZ(), 6, 6, true, rand, m); + + // Dead/awakening crystal core at the centre. + BlockState core = ModBlocks.VILLAGE_CORE.get().defaultBlockState(); + m.set(o.getX(), baseY, o.getZ()); + level.setBlock(m, core, 2); + + // Loot vault: a chest of rare alien goods, offset from the core. + BlockPos chestPos = new BlockPos(o.getX() + 3, baseY, o.getZ() + 3); + level.setBlock(chestPos, Blocks.CHEST.defaultBlockState(), 2); + if (level.getBlockEntity(chestPos) instanceof ChestBlockEntity chest) { + chest.setItem(4, new ItemStack(ModItems.ALIEN_CORE.get(), 1)); + chest.setItem(6, new ItemStack(ModItems.ALIEN_TECH_SCRAP.get(), 2 + rand.nextInt(4))); + chest.setItem(10, new ItemStack(ModItems.ALIEN_FRAGMENT.get(), 3 + rand.nextInt(5))); + chest.setItem(13, new ItemStack(ModItems.NEROSIUM_INGOT.get(), 2 + rand.nextInt(4))); + chest.setItem(22, new ItemStack(Items.EMERALD, 4 + rand.nextInt(8))); + } + return true; + } +} diff --git a/src/main/java/za/co/neroland/nerospace/world/StructureSpacing.java b/src/main/java/za/co/neroland/nerospace/world/StructureSpacing.java new file mode 100644 index 0000000..9470a80 --- /dev/null +++ b/src/main/java/za/co/neroland/nerospace/world/StructureSpacing.java @@ -0,0 +1,66 @@ +package za.co.neroland.nerospace.world; + +import net.minecraft.core.BlockPos; + +/** + * Region-of-interest spacing + density cap (player request). The world is divided into square + * region cells ({@link #CELL_CHUNKS}×{@link #CELL_CHUNKS} chunks). Each cell deterministically + * gets at most one ROI — and which kind (hamlet / ruin / mega-city, or nothing) is chosen by a + * hash of the cell, with weights that keep the rarer ones rare. The ROI sits at a deterministic + * "anchor" chunk inside the cell. This guarantees even spacing and a hard cap on how many structures + * occupy any given area, regardless of the placement RNG. + */ +public final class StructureSpacing { + + /** Size of a region cell in chunks (16 chunks ≈ 256 blocks). One ROI per cell, max. */ + public static final int CELL_CHUNKS = 16; + + public enum Roi { NONE, HAMLET, RUIN, MEGA_CITY } + + private StructureSpacing() { + } + + /** + * @return true only when {@code origin}'s chunk is the anchor of a region cell whose assigned ROI + * is {@code mine}. Call this first in a feature's {@code place} and bail out if false. + */ + public static boolean shouldPlace(BlockPos origin, Roi mine) { + int cx = origin.getX() >> 4; + int cz = origin.getZ() >> 4; + int rx = Math.floorDiv(cx, CELL_CHUNKS); + int rz = Math.floorDiv(cz, CELL_CHUNKS); + long h = mix(rx, rz); + + // Cell ROI assignment (weights out of 100): keeps cities very rare, ruins uncommon, hamlets + // common, and most cells empty for breathing room. + int roll = (int) Math.floorMod(h, 100L); + Roi type; + if (roll < 26) { + type = Roi.HAMLET; // 26% + } else if (roll < 34) { + type = Roi.RUIN; // 8% + } else if (roll < 36) { + type = Roi.MEGA_CITY; // 2% + } else { + type = Roi.NONE; // 64% empty + } + if (type != mine) { + return false; + } + // Anchor chunk within the cell (kept off the very edge so footprints don't straddle cells). + int span = Math.max(1, CELL_CHUNKS - 4); + int ax = 2 + Math.floorMod(h >>> 8, span); + int az = 2 + Math.floorMod(h >>> 24, span); + return Math.floorMod(cx, CELL_CHUNKS) == ax && Math.floorMod(cz, CELL_CHUNKS) == az; + } + + private static long mix(int x, int z) { + long h = x * 341873128712L + z * 132897987541L + 0x9E3779B97F4A7C15L; + h ^= (h >>> 33); + h *= 0xff51afd7ed558ccdL; + h ^= (h >>> 33); + h *= 0xc4ceb9fe1a85ec53L; + h ^= (h >>> 33); + return h; + } +} diff --git a/src/main/resources/assets/nerospace/textures/block/alien_bricks.png b/src/main/resources/assets/nerospace/textures/block/alien_bricks.png new file mode 100644 index 0000000..6ebdefd Binary files /dev/null and b/src/main/resources/assets/nerospace/textures/block/alien_bricks.png differ diff --git a/src/main/resources/assets/nerospace/textures/block/alien_crystal_block.png b/src/main/resources/assets/nerospace/textures/block/alien_crystal_block.png new file mode 100644 index 0000000..3a51668 Binary files /dev/null and b/src/main/resources/assets/nerospace/textures/block/alien_crystal_block.png differ diff --git a/src/main/resources/assets/nerospace/textures/block/alien_lamp.png b/src/main/resources/assets/nerospace/textures/block/alien_lamp.png new file mode 100644 index 0000000..da03f65 Binary files /dev/null and b/src/main/resources/assets/nerospace/textures/block/alien_lamp.png differ diff --git a/src/main/resources/assets/nerospace/textures/block/alien_pillar.png b/src/main/resources/assets/nerospace/textures/block/alien_pillar.png new file mode 100644 index 0000000..45903db Binary files /dev/null and b/src/main/resources/assets/nerospace/textures/block/alien_pillar.png differ diff --git a/src/main/resources/assets/nerospace/textures/block/alien_tile.png b/src/main/resources/assets/nerospace/textures/block/alien_tile.png new file mode 100644 index 0000000..c884d33 Binary files /dev/null and b/src/main/resources/assets/nerospace/textures/block/alien_tile.png differ diff --git a/src/main/resources/assets/nerospace/textures/block/cracked_alien_bricks.png b/src/main/resources/assets/nerospace/textures/block/cracked_alien_bricks.png new file mode 100644 index 0000000..8a08963 Binary files /dev/null and b/src/main/resources/assets/nerospace/textures/block/cracked_alien_bricks.png differ diff --git a/src/main/resources/assets/nerospace/textures/block/village_core.png b/src/main/resources/assets/nerospace/textures/block/village_core.png new file mode 100644 index 0000000..ced3aa0 Binary files /dev/null and b/src/main/resources/assets/nerospace/textures/block/village_core.png differ diff --git a/src/main/resources/assets/nerospace/textures/entity/alien_villager.png b/src/main/resources/assets/nerospace/textures/entity/alien_villager.png new file mode 100644 index 0000000..67160fd Binary files /dev/null and b/src/main/resources/assets/nerospace/textures/entity/alien_villager.png differ diff --git a/src/main/resources/assets/nerospace/textures/entity/alien_villager_cindara.png b/src/main/resources/assets/nerospace/textures/entity/alien_villager_cindara.png new file mode 100644 index 0000000..27a03fb Binary files /dev/null and b/src/main/resources/assets/nerospace/textures/entity/alien_villager_cindara.png differ diff --git a/src/main/resources/assets/nerospace/textures/entity/alien_villager_glacira.png b/src/main/resources/assets/nerospace/textures/entity/alien_villager_glacira.png new file mode 100644 index 0000000..1d2a9e2 Binary files /dev/null and b/src/main/resources/assets/nerospace/textures/entity/alien_villager_glacira.png differ diff --git a/src/main/resources/assets/nerospace/textures/entity/alien_villager_glow.png b/src/main/resources/assets/nerospace/textures/entity/alien_villager_glow.png new file mode 100644 index 0000000..463cdca Binary files /dev/null and b/src/main/resources/assets/nerospace/textures/entity/alien_villager_glow.png differ diff --git a/src/main/resources/assets/nerospace/textures/entity/alien_villager_meadow.png b/src/main/resources/assets/nerospace/textures/entity/alien_villager_meadow.png new file mode 100644 index 0000000..d53e23c Binary files /dev/null and b/src/main/resources/assets/nerospace/textures/entity/alien_villager_meadow.png differ diff --git a/src/main/resources/assets/nerospace/textures/entity/ruin_warden.png b/src/main/resources/assets/nerospace/textures/entity/ruin_warden.png new file mode 100644 index 0000000..81bbf48 Binary files /dev/null and b/src/main/resources/assets/nerospace/textures/entity/ruin_warden.png differ diff --git a/src/main/resources/assets/nerospace/textures/entity/ruin_warden_glow.png b/src/main/resources/assets/nerospace/textures/entity/ruin_warden_glow.png new file mode 100644 index 0000000..79039b0 Binary files /dev/null and b/src/main/resources/assets/nerospace/textures/entity/ruin_warden_glow.png differ diff --git a/src/main/resources/assets/nerospace/textures/item/alien_villager_spawn_egg.png b/src/main/resources/assets/nerospace/textures/item/alien_villager_spawn_egg.png new file mode 100644 index 0000000..c3de1db Binary files /dev/null and b/src/main/resources/assets/nerospace/textures/item/alien_villager_spawn_egg.png differ diff --git a/src/main/resources/assets/nerospace/textures/item/grav_striders.png b/src/main/resources/assets/nerospace/textures/item/grav_striders.png new file mode 100644 index 0000000..f7d82b6 Binary files /dev/null and b/src/main/resources/assets/nerospace/textures/item/grav_striders.png differ diff --git a/src/main/resources/assets/nerospace/textures/item/xertz_resonator.png b/src/main/resources/assets/nerospace/textures/item/xertz_resonator.png new file mode 100644 index 0000000..5ffe33c Binary files /dev/null and b/src/main/resources/assets/nerospace/textures/item/xertz_resonator.png differ diff --git a/tools/ecj.prefs b/tools/ecj.prefs index 399d371..5de91e0 100644 --- a/tools/ecj.prefs +++ b/tools/ecj.prefs @@ -15,16 +15,17 @@ org.eclipse.jdt.core.compiler.annotation.nullable.secondary=javax.annotation.Nul org.eclipse.jdt.core.compiler.annotation.nonnull.secondary=javax.annotation.Nonnull,jakarta.annotation.Nonnull org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=enabled org.eclipse.jdt.core.compiler.problem.missingNonNullByDefaultAnnotation=ignore -# Generic-null TYPE-ANNOTATION noise from Minecraft/NeoForge's heavily @NonNull-annotated generics -# meeting our unannotated code. nullUncheckedConversion (vanilla CriterionTrigger generics) can be -# ignored outright; nullSpecViolation ("Null constraint mismatch", e.g. Feature bounds) is a -# mandatory JDT problem that JDT refuses to set to `ignore` (it reverts to ERROR), so it stays a -# warning and the handful of framework-interop sites carry a local @SuppressWarnings("null"). -# The high-value deref checks (nullReference / potentialNullReference) stay ON — they caught the -# real @Nullable field/variable bugs. +# Null analysis severities. These are kept at `warning` (NOT `ignore`) so ecjCheck surfaces exactly +# what VS Code's Problems panel shows — nothing is hidden. In particular nullUncheckedConversion and +# nullSpecViolation arise where Mojang/NeoForge's @NonNull-annotated codec generics (RecordCodecBuilder, +# StreamCodec.composite, SavedDataType) meet our unannotated lambdas; VS Code reports these too, and +# they are framework-interop warnings that cannot be resolved without either mis-annotating our own +# constructors @Nullable or @SuppressWarnings. Per project policy we do NEITHER — they remain visible +# warnings in both tools. Genuine @Nullable deref bugs (nullReference / potentialNullReference) are +# fixed in code with explicit guards, not suppressed. org.eclipse.jdt.core.compiler.problem.nullSpecViolation=warning org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=warning -org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=ignore +org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning org.eclipse.jdt.core.compiler.problem.redundantNullCheck=warning org.eclipse.jdt.core.compiler.problem.nullReference=warning diff --git a/tools/gen_textures.py b/tools/gen_textures.py index d024463..92f7078 100644 --- a/tools/gen_textures.py +++ b/tools/gen_textures.py @@ -1346,7 +1346,36 @@ def _pat_wool(part, face, nx, ny, rng): return None +# Alien Villager (Greenxertz species) — pale-green skin, a deep teal-green robe with steel trim, and +# bright cyan-green shoulder crystals + eyes (the only emissive parts, picked up by gen_entity_glow). +AV_DARK = (28, 58, 50, 255) +AV_ROBE = (44, 92, 78, 255) +AV_ROBE2 = (60, 120, 100, 255) +AV_STEEL = (150, 170, 168, 255) +AV_TRIM = (190, 205, 200, 255) +AV_SKIN_D = (74, 150, 112, 255) +AV_SKIN = (120, 196, 150, 255) +AV_SKIN_L = (170, 224, 188, 255) +AV_CRYS = (140, 240, 200, 255) +AV_GLOW = (200, 255, 232, 255) + + +def _pat_robe(part, face, nx, ny, rng): + # Body: faint vertical robe seams + an occasional steel-trim fleck. Crystals: bright facets. + if part.startswith("crystal"): + return AV_GLOW if rng.random() < 0.18 else (AV_CRYS if rng.random() < 0.3 else None) + if part == "body" and (nx in (0.0, 1.0) or int(nx * 8) % 4 == 0): + return AV_STEEL if rng.random() < 0.5 else None + return AV_TRIM if rng.random() < 0.02 else None + + def gen_creatures(): + _gen_creature_v2("alien_villager", "AlienVillagerModel", + [AV_DARK, AV_ROBE, AV_ROBE2, AV_STEEL, AV_TRIM], 509, + pattern=_pat_robe, + part_ramps={"head": [AV_SKIN_D, AV_SKIN, AV_SKIN, AV_SKIN_L, AV_SKIN_L], + "crystal": [AV_ROBE2, AV_CRYS, AV_CRYS, AV_GLOW, AV_GLOW]}, + eye=AV_GLOW, socket=AV_DARK, glint=AV_GLOW, big_eyes=True) _gen_creature_v2("xertz_stalker", "XertzStalkerModel", [XS_DARK, XS_BODY, XS_MID, XS_FACET, XS_GLOW], 501, pattern=_pat_facets, eye=XS_GLOW, socket=XS_DARK, glint=XS_GLOW) @@ -1421,6 +1450,7 @@ def gen_spawn_egg(name, base, spot, outline, seed): def gen_spawn_eggs(): + gen_spawn_egg("alien_villager", AV_ROBE, AV_CRYS, AV_DARK, 619) gen_spawn_egg("xertz_stalker", XS_BODY, XS_FACET, XS_DARK, 611) gen_spawn_egg("quartz_crawler", (188, 190, 202, 255), QC_CRY, QC_SEAM, 612) gen_spawn_egg("greenling", GL_BODY, GL_LITE, GL_DARK, 613) @@ -3080,8 +3110,8 @@ def gen_falling_meteor_entity(): gen_loper_haunch() gen_strutter_drumstick() gen_drift_fleece() - for _name in ("xertz_stalker", "quartz_crawler", "greenling", "cinder_stalker", "frost_strider", - "meadow_loper", "ember_strutter", "woolly_drift"): + for _name in ("alien_villager", "xertz_stalker", "quartz_crawler", "greenling", "cinder_stalker", + "frost_strider", "meadow_loper", "ember_strutter", "woolly_drift"): gen_entity_glow(_name) gen_ore(STONE, "nerosium_ore") gen_ore(DEEP, "deepslate_nerosium_ore") diff --git a/tools/gradle-mcp/README.md b/tools/gradle-mcp/README.md index fb2a54e..cc761a7 100644 --- a/tools/gradle-mcp/README.md +++ b/tools/gradle-mcp/README.md @@ -7,7 +7,10 @@ needs real RAM, multiple cores, and minutes of uninterrupted runtime) happens on your hardware instead of an ephemeral sandbox — then the assistant can poll the result and confirm `BUILD SUCCESSFUL`. -No npm install. No external dependencies. Just Node. +The Gradle tools need only Node — no dependencies. The `markdown_check` tool +runs the **full markdownlint ruleset** when the optional `markdownlint` package +is installed (see [Markdown linting](#markdown-linting)); without it, it falls +back to a smaller built-in rule subset and says so in its `engine` field. ## Prerequisites @@ -28,6 +31,8 @@ No npm install. No external dependencies. Just Node. | `gradle_stop` | Terminate a running build. | | `gradle_list` | List builds started this session. | | `gradle_clear_logs` | Delete this session's log files. | +| `gradle_analyze` | Run `ecjCheck` (Eclipse compiler) async — the same analyzer/severities as the VS Code Problems panel, configured by `tools/ecj.prefs`. | +| `markdown_check` | Lint Markdown synchronously (full markdownlint ruleset when installed), honouring `.markdownlint.json`. | The typical loop: `gradle_build` → `gradle_status` (repeat until `status` is `succeeded`/`failed`) → `gradle_log` with a `grep` like `error|FAIL` if it failed. @@ -88,24 +93,20 @@ GRADLE_PROJECT_DIR="" node server.js # you should get an initialize result back. Ctrl-C to exit. ``` -## Privacy / logging (POPIA & GDPR) - -- Build logs are written **only to the local OS temp directory** and contain - **only Gradle build diagnostics** (task names, compiler output, stack traces). -- Logs are **never transmitted** anywhere by this server except back through the - MCP stdio channel you control — i.e. to the assistant you're already talking to. -- The server **does not log** environment variables, secrets, or personal data. -- Build output can incidentally include local file paths (which contain your - username). Use `gradle_clear_logs` to wipe session logs, or delete - `gradle-mcp-*.log` from your temp folder, whenever you want. -- Nothing is persisted beyond the temp log files; no telemetry, no network calls. - -## Notes & limits - -- Builds are tracked **in memory** for the server's lifetime; restarting the app - starts a fresh session (the Gradle build cache on disk is untouched and still - speeds up subsequent runs). -- The server only ever invokes the project's `gradlew` wrapper — it does not run - arbitrary shell commands. -- First build does the one-time Minecraft decompile and will take several - minutes; that's expected. Subsequent builds are fast thanks to Gradle caching. +## Markdown linting + +`markdown_check` matches what VS Code's markdownlint shows: it runs **every** +markdownlint rule, dynamically honouring the repo `.markdownlint.json` (any rule +set to `false` there is skipped, and changes to that file take effect on the next +run — no code change needed). markdownlint 0.34+ is ESM-only and is loaded via +dynamic `import()` at server startup. + +To enable the full ruleset, install the dependency once and restart the MCP: + +```bash +cd tools/gradle-mcp +npm install +``` + +If `markdownlint` is not installed, `markdown_check` still works using a built-in +subset of rules and reports `engine: builtin-subset ...` so you know to inst diff --git a/tools/gradle-mcp/package.json b/tools/gradle-mcp/package.json new file mode 100644 index 0000000..000ebf3 --- /dev/null +++ b/tools/gradle-mcp/package.json @@ -0,0 +1,11 @@ +{ + "name": "nerospace-gradle-mcp", + "version": "1.0.0", + "private": true, + "description": "Local MCP server: runs gradlew on the dev machine + full markdownlint checks.", + "type": "commonjs", + "bin": { "nerospace-gradle-mcp": "server.js" }, + "dependencies": { + "markdownlint": "^0.37.0" + } +} diff --git a/tools/gradle-mcp/server.js b/tools/gradle-mcp/server.js index 807e54c..ab6ff32 100644 --- a/tools/gradle-mcp/server.js +++ b/tools/gradle-mcp/server.js @@ -286,6 +286,57 @@ function lintMarkdown(text, config) { return v; } +// Full markdownlint: load the real library if installed (tools/gradle-mcp needs `npm install`). +// This runs EVERY markdownlint rule, honouring the repo .markdownlint.json (a rule set to false +// there is skipped) — so the check matches VS Code and updates dynamically with the config. +// markdownlint 0.34+ is ESM-only, so it is loaded via dynamic import() during startup (see main()); +// older CJS builds are picked up synchronously via require(). Until it resolves, MARKDOWNLINT is null +// and lintText falls back to the built-in subset. +let MARKDOWNLINT = null; +(function tryRequireMarkdownlint() { + try { const m = require('markdownlint'); if (typeof m.sync === 'function') { MARKDOWNLINT = (o) => m.sync(o); } } catch { /* ESM-only or absent */ } +})(); +async function initMarkdownlint() { + if (MARKDOWNLINT) return; // already loaded via require (older CJS build) + try { + const mod = await Promise.race([ + import('markdownlint/sync'), + new Promise((_, rej) => setTimeout(() => rej(new Error('timeout')), 5000)), + ]); + if (mod && typeof mod.lint === 'function') { + MARKDOWNLINT = (o) => mod.lint(o); + log('markdownlint loaded (full ruleset)'); + } + } catch (e) { + log(`markdownlint not available, using builtin subset (${e.message})`); + } +} + +/** + * Lint one document. Uses the full markdownlint ruleset when the library is installed (honouring + * .markdownlint.json), otherwise falls back to the built-in subset below. + * Returns [{ line, rule, description }]. + */ +function lintText(text, config) { + if (MARKDOWNLINT) { + const cfg = Object.assign({}, config); + delete cfg.$schema; // not a rule key + let res; + try { + res = MARKDOWNLINT({ strings: { doc: text }, config: cfg }); + } catch (e) { + return lintMarkdown(text, config); + } + const items = (res && res.doc) || []; + return items.map((x) => ({ + line: x.lineNumber, + rule: (x.ruleNames && x.ruleNames[0]) || 'MD', + description: x.ruleDescription + (x.errorDetail ? `: ${x.errorDetail}` : ''), + })); + } + return lintMarkdown(text, config); +} + function startBuild({ tasks, extra_args, project_dir }) { const projectDir = project_dir || DEFAULT_PROJECT_DIR; const wrapper = gradlewCommand(projectDir); @@ -503,13 +554,12 @@ const TOOLS = [ { name: 'markdown_check', description: - 'Lint Markdown files for common markdownlint-style violations and return them immediately ' + - '(synchronous — no build_id/polling). Checks: MD009 trailing spaces, MD010 hard tabs, ' + - 'MD012 multiple blank lines, MD018 missing space after #, MD022 blanks around headings, ' + - 'MD025 multiple H1, MD026 heading trailing punctuation, MD031 blanks around fenced code, ' + - 'MD040 fenced code language, MD047 single trailing newline. Honours the repo ' + - '.markdownlint.json (a rule set to false there is skipped). Scans the project for ' + - '*.md/*.markdown (skipping node_modules/.git/build/...) unless `paths` is given.', + 'Lint Markdown files and return violations immediately (synchronous — no build_id/polling). ' + + 'Runs the FULL markdownlint ruleset via the markdownlint library, dynamically honouring the ' + + 'repo .markdownlint.json (every rule except those set to false there) — so it matches VS Code ' + + 'and updates automatically when .markdownlint.json changes. (If the library is not installed ' + + 'in tools/gradle-mcp, it falls back to a built-in subset and says so in `engine`.) Scans the ' + + 'project for *.md/*.markdown (skipping node_modules/.git/build/...) unless `paths` is given.', inputSchema: { type: 'object', properties: { @@ -631,7 +681,7 @@ function callTool(name, args) { } catch { continue; } - for (const x of lintMarkdown(text, config)) { + for (const x of lintText(text, config)) { countsByRule[x.rule] = (countsByRule[x.rule] || 0) + 1; if (violations.length < MAX_VIOLATIONS) { violations.push({ @@ -647,6 +697,7 @@ function callTool(name, args) { } const total = Object.values(countsByRule).reduce((a, b) => a + b, 0); return { + engine: MARKDOWNLINT ? 'markdownlint (full ruleset)' : 'builtin-subset (run `npm install` in tools/gradle-mcp for full rules)', project_dir: projectDir, config_file: configPath, files_checked: files.length, @@ -757,7 +808,8 @@ function handleMessage(msg) { } } -function main() { +async function main() { + await initMarkdownlint(); log(`starting (project dir: ${DEFAULT_PROJECT_DIR})`); let buffer = ''; process.stdin.setEncoding('utf8'); @@ -781,4 +833,4 @@ function main() { process.stdin.on('end', () => process.exit(0)); } -main(); +main().catch((e) => { log(`fatal: ${e.message}`); process.exit(1); }); diff --git a/tools/model_sync.py b/tools/model_sync.py index fd96d33..b662406 100644 --- a/tools/model_sync.py +++ b/tools/model_sync.py @@ -66,6 +66,8 @@ def _entry(java_class, name, texture=None): _entry("XertzStalkerModel", "xertz_stalker"), _entry("QuartzCrawlerModel", "quartz_crawler"), _entry("GreenlingModel", "greenling"), + _entry("AlienVillagerModel", "alien_villager"), + _entry("RuinWardenModel", "ruin_warden"), _entry("CinderStalkerModel", "cinder_stalker"), _entry("FrostStriderModel", "frost_strider"), _entry("MeadowLoperModel", "meadow_loper"), diff --git a/wiki/Alien-Decoration.md b/wiki/Alien-Decoration.md new file mode 100644 index 0000000..59b7c2e --- /dev/null +++ b/wiki/Alien-Decoration.md @@ -0,0 +1,25 @@ +# Alien Decoration + +A Greenxertz decoration block family used by the alien structures — and free for you to build with. + +## Overview + +Six full-cube blocks in the green/steel Greenxertz palette. They make up the +**[alien structures](Alien-Structures)** and are all available in the Nerospace creative tab, +pickaxe-mineable, and drop themselves. + +## Blocks + +| Block | ID | Notes | +| --- | --- | --- | +| Alien Bricks | `nerospace:alien_bricks` | The main wall block — offset brick courses with steel mortar. | +| Cracked Alien Bricks | `nerospace:cracked_alien_bricks` | Weathered variant used for ruins. | +| Alien Tile | `nerospace:alien_tile` | Floor/plaza tiling with a steel grid. | +| Alien Pillar | `nerospace:alien_pillar` | Fluted column block for corners and wall posts. | +| Alien Lamp | `nerospace:alien_lamp` | Full light source (light level 15) — a glowing green panel. | +| Alien Crystal Block | `nerospace:alien_crystal_block` | Glowing accent (light level 12) — clustered crystal. | + +## Details + +- Tool: pickaxe · Drops: itself. +- The Lamp and Crystal Block emit light; the rest are decorative. diff --git a/wiki/Alien-Gear.md b/wiki/Alien-Gear.md new file mode 100644 index 0000000..164d538 --- /dev/null +++ b/wiki/Alien-Gear.md @@ -0,0 +1,32 @@ +# Alien Gear + +The exclusive Artificer gear — powerful items only the most trusted alien villagers will part with. + +## Overview + +Two trade-only items, obtained from high-trust **[Alien Villagers](Alien-Villagers)** (tiers 4–5) or +found in the **[Mega-City](Alien-Structures)** grand vault. They use the common item tags, so they fit +into any modpack. + +## Grav Striders + +`nerospace:grav_striders` + +While **carried in your inventory**, alien grav-tech cushions your landings — **fall damage is +negated**. No need to wear it; just keep it on you. + +**Obtain:** trade at trust tier 5 (Alien Core + 24 emeralds), or loot the Mega-City keep vault. + +## Xertz Resonator + +`nerospace:xertz_resonator` + +**Right-click** to ping the surrounding stone: it reports how many **ore blocks** lie within 8 blocks. +It matches the common `c:ores` tag, so it finds **any mod's** ores — a cross-mod prospecting aid. + +**Obtain:** trade at trust tier 4 (16 emeralds), or loot the Mega-City keep vault. + +## Details + +- Both are trade-only / structure-loot; neither is craftable. +- See **[Alien Villagers](Alien-Villagers)** for how to reach the trust tiers that unlock them. diff --git a/wiki/Alien-Structures.md b/wiki/Alien-Structures.md new file mode 100644 index 0000000..952cf70 --- /dev/null +++ b/wiki/Alien-Structures.md @@ -0,0 +1,59 @@ +# Alien Structures + +The settlements and ruins of the alien planets — procedurally generated from the alien decoration set, +spaced apart so they never clutter the world. + +## Overview + +Three structures generate on the surface, all built from the **[alien decoration blocks](Alien-Decoration)** +with a futuristic look: tile podiums, brick walls with **glowing crystal window bands**, taller **lit +corner pillars**, **tapered roofs** and **crystal spires**. Each holds a **[Village Core](Village-Core)**. + +## Hamlet + +A small alien outpost — the common find and your "starter village". + +- A glowing tile plaza with a lamp border. +- Two futuristic towers. +- A central **[Village Core](Village-Core)** on a lit crystal podium. + +## Ancient Ruin + +A large, half-buried, weathered alien hall — uncommon. + +- Built from **cracked alien brick** with collapsed walls and a broken roof (sunken into the ground). +- A dead crystal core at the centre. +- A **loot vault** (chest) of rare alien goods: Alien Core, Alien Tech Scrap, Alien Fragments, Nerosium + + Ingots and emeralds. + +## Mega-City + +The massive end-state alien settlement — very rare, and a real expedition. + +- A pillared, lit, **crenellated curtain wall** (41×41) with four gates. +- A tile plaza of futuristic towers around a **central keep**. +- The keep holds a **grand vault** — Alien Cores, **both** pieces of **[Artificer gear](Alien-Gear)**, + + diamonds and emeralds — guarded by the **[Ruin Warden](Creatures#ruin-warden)** boss. + +## Spacing & density + +To stop structures clustering (and to cap how many appear in an area), all three share a deterministic +**region grid**: + +- The world is divided into **16×16-chunk cells** (≈256 blocks). +- Each cell gets **at most one** structure, chosen by a weighted hash: ~26% Hamlet, ~8% Ruin, ~2% + + Mega-City, and the rest **empty** for breathing room. + +- The structure sits at a deterministic anchor chunk inside the cell, kept off the edge so footprints + + don't straddle boundaries. + +The result is even spacing and a hard density cap, regardless of the placement RNG. + +## Details + +- Custom worldgen features in the `greenxertz` biome (surface-structures step). +- IDs: `nerospace:hamlet`, `nerospace:ruin`, `nerospace:mega_city`. diff --git a/wiki/Alien-Villagers.md b/wiki/Alien-Villagers.md new file mode 100644 index 0000000..c816e14 --- /dev/null +++ b/wiki/Alien-Villagers.md @@ -0,0 +1,90 @@ +# Alien Villagers + +The social aliens of the Nerospace planets — wary wanderers you win over to unlock trades and grow a +village. + +## Overview + +The **Alien Villager** (`nerospace:alien_villager`) is a peaceful, **wary-neutral** NPC: it strolls its +home biome, watches you, and edges away if you crowd it, but never attacks. Earn its trust and it +becomes a merchant and the key to the whole village system — trading, the +**[Village Core](Village-Core)**, and the alien **[structures](Alien-Structures)**. + +The loop: *discover a settlement → earn trust → unlock trades → teach the village to build → it grows +into an engine → deeper structures (and a boss) open.* + +## Where they live + +- Spawn naturally on **Greenxertz** (and the mature `terraformed_meadow`). +- Sparse groups also spawn on **Cindara** and **Glacira**. +- Creative: the **Alien Villager Spawn Egg** (Nerospace tab). + +## How they look + +Every villager is visually unique without a new entity per look — a layered, seed-driven appearance: + +- **Per-individual tint** — a stored colour seed gives each villager a slightly different shade, so no + + two are identical. Deterministic: a given villager always looks the same. + +- **Per-planet skin** — Greenxertz green/steel, Cindara ember/red, Glacira frost/pale. Within + + Greenxertz, the mature meadow wears a lighter accessory set. + +- **Mood warmth** — as your reputation with a village rises, its villagers' tint warms, so you can read + + trust at a glance. + +- **Glowing eyes & shoulder crystals** — emissive, visible in the dark. + +## Earning trust (reputation) + +Each villager tracks a **per-player reputation score (0–100)** mapped to **6 tiers**: + +| Tier | Name | Unlocks | +| --- | --- | --- | +| T0 | Stranger | nothing — refuses to trade (wary head-shake) | +| T1 | Acquainted | basic trades | +| T2 | Trusted | mid trades · can teach **Tier-1 buildings** | +| T3 | Allied | rare trades · **Tier-2 buildings** | +| T4 | Honored | exclusive gear trades | +| T5 | Kin | best trades | + +Reputation rises through: + +- **Gifts** — right-click a villager while holding a valued item (**Xertz Quartz**, **Nerosium Ingot**, + + **Alien Fragment**, or **Emeralds**). It takes one, with happy-villager particles. + +- **Trade volume** — every completed trade nudges trust up. +- **Quests** — handing a village task in to a **[Village Core](Village-Core)** bumps the trust of every + + nearby villager. + +Reputation is keyed by Minecraft player UUID only — no names or interaction logs are sent anywhere +(POPIA/GDPR-safe). + +## Trading + +Right-click a villager at **tier 1+** to open the **vanilla trading screen**. Offers are tier-gated and +**cumulative** (a higher-trust villager also offers the lower tiers). Emeralds are the currency, so the +goods are useful in any modpack. + +| Tier | Sample offers | +| --- | --- | +| T1 | sell 12 Xertz Quartz → 1 emerald · 1 emerald → 3 iron · 2 emeralds → 6 bread | +| T2 | 4 emeralds → Nerosium Ingot · 1 emerald + 8 Raw Nerosteel → 4 Nerosteel Ingots · sell Alien Fragments | +| T3 | 8 emeralds → diamond · 5 emeralds → Rocket Fuel Canister | +| T4 | 12 emeralds + 2 Alien Tech Scrap → Alien Core · 16 emeralds → **[Xertz Resonator](Alien-Gear)** | +| T5 | 18 emeralds → 3 diamonds · Alien Core + 24 emeralds → **[Grav Striders](Alien-Gear)** | + +The top tiers are the only trade source of the exclusive **[Artificer gear](Alien-Gear)**. + +## Details + +- ID: `nerospace:alien_villager` · category CREATURE · size 0.6 × 1.95 +- Variant (planet / home biome / colour seed) is synced and saved; reputation is saved per villager. +- Voice currently aliases the vanilla villager sounds. + +See also: **[Village Core](Village-Core)** · **[Alien Structures](Alien-Structures)** · +**[Alien Gear](Alien-Gear)** · **[Creatures](Creatures)**. diff --git a/wiki/Battery.md b/wiki/Battery.md index a2dc9f2..618fc97 100644 --- a/wiki/Battery.md +++ b/wiki/Battery.md @@ -17,9 +17,13 @@ N R N ## How it works - Stores **200,000 FE** (configurable); accepts and provides power on **every side** at pipe + throughput. + - Generators fill it through the network; machines drain it the same way — so production hiccups + don't black out your base. + - Right-click for a charge readout. A **Creative Battery** variant (creative tab only) is an endless source and sink of energy for diff --git a/wiki/Cindrite-Ore.md b/wiki/Cindrite-Ore.md index b4bc430..95aec2b 100644 --- a/wiki/Cindrite-Ore.md +++ b/wiki/Cindrite-Ore.md @@ -10,8 +10,11 @@ and sits deeper, rewarding a trip to the most hostile world in the mod. ## Obtaining - **Mining:** requires an **iron-tier pickaxe** or better. Drops the **Cindrite** gem directly + (Fortune-affected). + - **Generation:** spawns in the **Cindara** dimension, in a triangular band roughly **y −48 to y 48** — + rarer than the Greenxertz ores. ## Use diff --git a/wiki/Combustion-Generator.md b/wiki/Combustion-Generator.md index e83afc8..368ec25 100644 --- a/wiki/Combustion-Generator.md +++ b/wiki/Combustion-Generator.md @@ -17,10 +17,14 @@ N R N ## How it works - **Fuel slot** (GUI, hand or hopper/pipe fed): coal, charcoal, coal blocks, blaze rods, or Rocket + Fuel Canisters. + - Burning generates **60 FE/t** (configurable) into a 50,000 FE buffer. - The buffer is **extract-only**: connect a Universal Pipe (energy layer) and the network pulls the + power to machines and Batteries. + - GUI shows the power buffer and burn progress; emits a comparator signal from its charge. ## Details diff --git a/wiki/Configuration.md b/wiki/Configuration.md index d20325a..f562493 100644 --- a/wiki/Configuration.md +++ b/wiki/Configuration.md @@ -17,7 +17,7 @@ progression (e.g. the rocket launch cost is additionally clamped to the tank siz always possible with a full tank). | Key | What it scales | Base values | -|---|---|---| +| --- | --- | --- | | `oxygenDrainMultiplier` | How fast oxygen is consumed: bare-lungs drain, suit tank drain, suffocation damage. >1 = harsher planets. | drain 2/t · suit drain 1/check · damage 1 half-heart | | `oxygenCapacityMultiplier` | Air capacities: the player's oxygen supply and the Tier 2 suit tank. | player 300 · T2 suit 600 | | `energyRateMultiplier` | The energy & storage economy: generator output, energy pipe throughput, and every buffer/tank capacity (battery, fluid/gas/fuel tanks, pipe buffers, machine buffers, rocket fuel tanks). | combustion 60 FE/t · passive 10 FE/t · energy pipe 4,000 FE/t & 8,000 FE · fluid/gas pipe 4,000 mB · battery 200,000 FE · fluid/gas tank 16,000 mB · fuel tank 32,000 mB · machine buffers 10k–100k FE · rocket tanks 3,000/6,000/12,000 mB | @@ -37,20 +37,20 @@ bottleneck before the multiplied rate — intended, since those caps protect gri ### Telemetry | Key | Default | Meaning | -|---|---|---| +| --- | --- | --- | | `telemetryEnabled` | `true` | Anonymous Nerospace-only crash reports (Sentry, EU servers, POPIA/GDPR-scrubbed — see `PRIVACY.md`). Set `false` to opt out; applies immediately on config reload. | ### Atmosphere | Key | Default | Range | Meaning | -|---|---|---|---| +| --- | --- | --- | --- | | `atmosphereDamageEnabled` | `true` | — | Master switch: whether airless dimensions hurt unprotected players. | | `atmosphereSafeRadius` | `6` | 0–32 | Blocks from a Rocket Launch Pad treated as a safe, pressurised zone. | ### Oxygen structure | Key | Default | Range | Meaning | -|---|---|---|---| +| --- | --- | --- | --- | | `oxygenBubbleRadius` | `14` | 0–32 | Falloff radius of a generator's breathable bubble in open/leaky space (sealed rooms fill completely regardless). | | `oxygenAirlockRadius` | `3` | 0–16 | Radius within which a worn suit refills from a Gas Tank / Oxygen Generator holding Oxygen. `0` disables airlock refilling. | @@ -60,7 +60,7 @@ Simulation tuning, **not** balance — wrong values can break oxygen/terraformin at defaults unless debugging server performance. | Key | Default | Range | Meaning | -|---|---|---|---| +| --- | --- | --- | --- | | `oxygenMaxConcentration` | `15` | 1–15 | Max per-block oxygen concentration in the field. | | `oxygenBreathableThreshold` | `6` | 1–15 | Concentration at/above which a cell is breathable. | | `oxygenDiffusionRate` | `0.22` | 0–0.4 | Fraction of the neighbour gradient flowing into a cell per sim step. | @@ -74,7 +74,7 @@ at defaults unless debugging server performance. ### Client visuals | Key | Default | Range | Meaning | -|---|---|---|---| +| --- | --- | --- | --- | | `oxygenVisualQuality` | `FULL` | OFF/MINIMAL/FULL | Oxygen visuals: OFF (none), MINIMAL (HUD + sparse particles), FULL (+ haze tint + boundary shimmer). | | `oxygenParticleIntensity` | `1.0` | 0–4 | Drifting-particle density (0 disables just this layer). | | `oxygenHazeIntensity` | `1.0` | 0–2 | Haze/fog-tint alpha inside breathable air (0 disables the layer). | @@ -84,7 +84,7 @@ at defaults unless debugging server performance. ### Terraformer | Key | Default | Range | Meaning | -|---|---|---|---| +| --- | --- | --- | --- | | `terraformPlantsEnabled` | `true` | — | Scatter grass/flowers/saplings on converted ground. | | `terraformWaterEnabled` | `true` | — | Fill low/exposed cells with water. | | `terraformResourcesEnabled` | `true` | — | Tier-3 Terraformer seeds ores into the converted subsurface (low rate). | @@ -99,7 +99,7 @@ Tunables for the meteor world-event (see **[Meteor Events](Meteor-Events)**). De one natural meteor every 2–3 play-hours per active dimension; the Meteor Caller works regardless. | Key | Default | Range | Meaning | -|---|---|---|---| +| --- | --- | --- | --- | | `meteorNaturalSpawn` | `true` | — | Whether meteors fall naturally near players. | | `meteorAvgIntervalSeconds` | `9000` | 60–1,000,000 | Average seconds between natural impacts (randomised 0.66×–1.33×). | | `meteorWarningSeconds` | `30` | 0–600 | Warning window a meteor is tracked as *incoming* before it falls. | @@ -110,6 +110,12 @@ one natural meteor every 2–3 play-hours per active dimension; the Meteor Calle | `meteorLootBonusRolls` | `3` | 0–32 | Weighted bonus loot rolls on top of the guaranteed alien fragments. | | `meteorDebugLog` | `false` | — | Verbose, non-personal meteor logging (dimension + coordinates only — POPIA/GDPR). | +### Alien villages + +| Key | Default | Meaning | +| --- | --- | --- | +| `alienRaidsEnabled` | `true` | Whether claimed alien villages are raided by hostile mobs at night. Set `false` to opt out; applies on config reload. | + ## Removed keys (for modpack authors migrating) Folded into multipliers: `atmosphereDamage`, `oxygenMax`, `oxygenDrainPerTick`, `oxygenSuitDrain`, diff --git a/wiki/Configurator.md b/wiki/Configurator.md index 05660f2..0758a55 100644 --- a/wiki/Configurator.md +++ b/wiki/Configurator.md @@ -16,10 +16,14 @@ Every Universal Pipe face has four independent I/O modes (one per layer: Energy, - **Off** — disconnected for that layer With the Configurator: + - **Sneak-right-click a pipe** → opens the **configuration panel**: a 6-face × 4-layer grid of + buttons; click any cell to cycle its mode. Changes apply instantly. + - **Right-click a pipe face** → quick-cycles that face's mode for the currently selected layer. - **Sneak-right-click in air** (or on non-pipe blocks) → cycles which layer the quick mode edits + (Energy → Fluid → Gas → Items). ## Tips diff --git a/wiki/Creative-Source-Blocks.md b/wiki/Creative-Source-Blocks.md index b244c9b..a9d6f80 100644 --- a/wiki/Creative-Source-Blocks.md +++ b/wiki/Creative-Source-Blocks.md @@ -4,7 +4,7 @@ Endless sources (and voids) for testing pipe networks — creative tab only, unb no recipes. | Block | Provides | Configure | -|---|---|---| +| --- | --- | --- | | **Creative Battery** | endless energy; accepts (voids) any inserted FE | — | | **Creative Fluid Tank** | endless fluid of your choice | right-click with a **filled bucket** to set; sneak-right-click to clear | | **Creative Gas Tank** | endless Oxygen | — | diff --git a/wiki/Creatures.md b/wiki/Creatures.md index 7202c5a..96be6fc 100644 --- a/wiki/Creatures.md +++ b/wiki/Creatures.md @@ -41,6 +41,14 @@ crest. Deliberately the *only* creature that barely glows — just a tiny glint **Sounds:** chirps while idle, squeaks when hurt, wilts on death. *(Placeholder aliases vanilla panda sounds for a soft, gentle voice.)* +### Alien Villager — "The Wary" (social) + +The social aliens of the planets — see the dedicated **[Alien Villagers](Alien-Villagers)** page for the +full system (appearance, gifting, trust tiers, trading). In brief: a wary-neutral humanoid with a domed +head and crystalline shoulder growths that strides on two legs. Each individual is a slightly different +green/steel shade, and the skin shifts per planet (Cindara ember, Glacira frost). Win its trust to trade +and to grow a **[Village Core](Village-Core)** village. + ## Cindara ### Cinder Stalker — "Magma Hulk" (hostile) @@ -67,6 +75,17 @@ row of ice-shard back spines. Freeze-immune; slightly faster but more fragile th **Sounds:** creaks while idle, splinters when hurt, shatters on death. *(Placeholder aliases vanilla stray sounds.)* +## Bosses + +### Ruin Warden + +A towering crystalline construct that guards the **[Mega-City](Alien-Structures)** keep. Heavily +armoured, resists knockback, hits hard, and hunts players on sight — clearing it is the price of the +keep's grand vault. + +**Look:** a hulking dark-crystal body veined with glowing violet, with jagged crystal shoulder spires +and heavy limbs. **Stats:** 120 HP, high armour + knockback resistance, strong melee. + ## Terraformed worlds — livestock Mature ("Living") terraformed ground wakes a breedable livestock species per planet — the seeded diff --git a/wiki/Deepslate-Nerosium-Ore.md b/wiki/Deepslate-Nerosium-Ore.md index 26e79c1..f95729a 100644 --- a/wiki/Deepslate-Nerosium-Ore.md +++ b/wiki/Deepslate-Nerosium-Ore.md @@ -10,6 +10,7 @@ Identical drops and uses to Nerosium Ore, but embedded in deepslate, so it is sl - **Mining:** requires an **iron-tier pickaxe** or better. Drops **Raw Nerosium** (Fortune-affected). - **Generation:** the deepslate form of the Overworld nerosium ore deposit (appears where the deposit + intersects deepslate, in the lower part of the **y −24 to y 56** band). ## Use diff --git a/wiki/Fluid-Tank.md b/wiki/Fluid-Tank.md index 8b8e6d0..e3d30a2 100644 --- a/wiki/Fluid-Tank.md +++ b/wiki/Fluid-Tank.md @@ -20,7 +20,9 @@ N G N - Holds **16,000 mB** (configurable) of any one fluid. - **Buckets:** right-click with a filled bucket to pour in, an empty bucket to draw out. - **Pipes:** the fluid layer fills and drains it on every side — remember the network carries one + fluid at a time. + - Bare-hand right-click reads out the contents. A **Creative Fluid Tank** variant supplies endless fluid — see diff --git a/wiki/Fuel-Refinery.md b/wiki/Fuel-Refinery.md index 0dea577..52938e3 100644 --- a/wiki/Fuel-Refinery.md +++ b/wiki/Fuel-Refinery.md @@ -26,12 +26,18 @@ N R N ## How it works - **Inputs:** a **carbon** slot (coal or charcoal) and a **catalyst** slot (blaze powder). Hoppers and + pipes feed them through the item capability — coal routes to carbon, blaze to catalyst automatically. + - **Power:** grid power only (insert-capped 1,000 FE/tick). Feed it from a generator or Battery over + [Universal Pipes](Universal-Pipe). + - **Refining:** one batch consumes **1 coal + 1 blaze powder + ~4,000 FE** over ~100 ticks and yields + **2,000 mB** of rocket fuel — the same fuel two hand-crafted canisters give, without the iron or the clicking. + - **Output:** the internal **8,000 mB** tank exposes the fluid capability, so pipes carry the fuel away. - **Comparator:** emits a redstone signal scaled to its fuel level. diff --git a/wiki/Fuel-Tank.md b/wiki/Fuel-Tank.md index 2f82949..037dea6 100644 --- a/wiki/Fuel-Tank.md +++ b/wiki/Fuel-Tank.md @@ -24,19 +24,27 @@ N G N - **Capacity:** 32,000 mB of rocket fuel. - **Filling:** right-click with a **Rocket Fuel Bucket** or **Canister** to add fuel (empty bucket is + returned); a fluid pipe can fill it via the fluid capability. Empty-hand right-click prints the current fuel level. + - **Canister auto-feed:** hoppers and [Universal Pipes](Universal-Pipe) can insert **Rocket Fuel + Canisters** through the tank's item capability — each is converted to **1,000 mB** of fuel on tick. Pair it with a [Fuel Refinery](Fuel-Refinery) (or a canister line) for fully automated fuelling. + - **Auto-fuelling:** when a rocket is on an adjacent launch pad, the tank pumps fuel into it. A + complete **3×3 launch pad** pumps **4× faster** (160 vs 40 mB/tick) — and since the multiblock gating pass a rocket can only deploy on a full 3×3 anyway. On a **[Heavy Launch Complex](Launch-Gantry)** (5×5 + gantry) the rate rises to **480 mB/t** (12×), filling a Tier 4's 24,000 mB tank in under a minute. + - **Full automation:** the pad itself also proxies the rocket's **fuel-intake slot** as an item + capability, so hoppers/[Universal Pipes](Universal-Pipe) can deliver Rocket Fuel Buckets and Canisters straight into the rocket — a Fuel Tank + an item line makes launch prep hands-free. + - **Comparator:** emits a redstone signal scaled to its fill level. ## Details diff --git a/wiki/Future-Features.md b/wiki/Future-Features.md index c155cab..6ba7319 100644 --- a/wiki/Future-Features.md +++ b/wiki/Future-Features.md @@ -6,37 +6,50 @@ A longer-term wish list — ideas under consideration after 1.0, not commitments ## Space & travel - More **destinations** — an asteroid field, gas giant moons… each with its own materials, hazards + and biomes. + - **Deeper station building** — more station modules and reasons to expand a founded station beyond + its landing platform. ## Survival & atmosphere - A **radiation hazard + suit variant** — deliberately deferred from 1.0 until a destination exists + that justifies it (the asteroid field is the leading candidate). + - **Smarter oxygen visuals** — fog/haze tuning, boundary shimmer, optional top-down "fill" effect. ## Terraforming & worlds - **More late-game terraforming toys** — richer flora control, configurable target biomes, more + livestock interactions (shearing, milking analogues). + - **Active (away-from-player) terraforming** — opt-in bounded chunk-loading so terraforming + continues while you're elsewhere (off by default — a performance footgun). ## Machines & tech - **More processing** — ore washing/smelting tiers, alloys, an expanded material tree. - **Data-driven grinder recipes** — move grinding to a datapack `RecipeType` so packs can add their + own. ## Content & polish - **Bespoke audio** — real rocket-launch, machine, ambience and creature `.ogg` sounds (the 1.0 + sound events alias fitting vanilla audio as placeholders; swapping in real files is a pure resource change). + - **EMI integration** — as soon as it reaches 26.1. JEI is already supported: standard + recipes/tags work out of the box, and the Nerosium Grinder, Fuel Refinery and Combustion Generator get their own JEI recipe categories. + - **Cross-mod integration** — e.g. Mekanism, once the big tech mods port to Minecraft 26.1. --- diff --git a/wiki/Gas-Tank.md b/wiki/Gas-Tank.md index 2013ddb..f67f255 100644 --- a/wiki/Gas-Tank.md +++ b/wiki/Gas-Tank.md @@ -18,12 +18,18 @@ N N N - Holds **16,000 mB** (configurable) of one gas — **Oxygen** is the first gas in the mod. - Universal Pipes (gas layer, green stream) fill and drain it on every side; pipe a surplus from your + [Oxygen Generator](Oxygen-Generator) into it as a life-support buffer. + - Gas is gas: **breaking a gas-filled pipe (or the tank itself) vents the contents** — plan your + plumbing before tearing it up. + - **Airlock:** a player wearing a full [Oxygen Suit](Oxygen-Suit) within a few blocks (default 3) of + a tank holding Oxygen **refills the suit's air from it**, draining the gas — a tank by the base door is a working airlock. A Tier 2 suit refills twice as fast. + - Bare-hand right-click reads out the contents. A **Creative Gas Tank** variant supplies endless Oxygen — see diff --git a/wiki/Glacite-Ore.md b/wiki/Glacite-Ore.md index d58ff32..add62b2 100644 --- a/wiki/Glacite-Ore.md +++ b/wiki/Glacite-Ore.md @@ -11,16 +11,22 @@ deeper terraforming. ## Obtaining - **Mining:** requires an **iron-tier pickaxe** or better. Drops the **Glacite** gem directly + (Fortune-affected). + - **Generation:** spawns in the **Glacira** dimension, in a band roughly **y −48 to y 48** — similar + rarity to [Cindrite](Cindrite-Ore). ## Use - Pack into a **[Block of Glacite](Block-of-Glacite)** for storage. - Craft the **[Cryo Suit](Oxygen-Suit)** (each piece = Tier 2 piece + 4 Glacite) — Glacira's cold + quadruples oxygen drain without it. + - Craft and feed the **[Hydration Module](Hydration-Module)**, which melts glacite into the water + cycle that pushes a [Terraformer](Terraformer)'s land from *Rooted* to *Hydrated*. ## Details diff --git a/wiki/Home.md b/wiki/Home.md index 641bb35..ebc79c7 100644 --- a/wiki/Home.md +++ b/wiki/Home.md @@ -11,29 +11,51 @@ eventually **terraform** a dead planet into livable, rained-on ground. ## Getting started 1. Mine **Nerosium Ore** in the Overworld — and craft a **[Star Guide Book](Star-Guide)** + (Book + Raw Nerosium) right away: it's the in-game version of this wiki, a live 7-chapter progression tree that always shows your next step. + 2. Build a **Nerosium Grinder** to double your ore yield via **Nerosium Dust**. 3. Craft a Tier 1 **Rocket** and a 3×3 **[Launch Pad](Rocket-Launch-Pad)** and fly to the Orbital + Station; progress to **Nerosteel** and **Xertz Quartz** on Greenxertz. + 4. Pack an **[Oxygen Suit](Oxygen-Suit)** and set up **Oxygen Generators** — every world out there + is airless. Bring the **Thermal**/**Cryo** variants for Cindara and Glacira. + - Automate the digging with a **[Quarry Controller](Quarry-Controller)**: mark an area with + landmarks and it strip-mines the whole rectangle to bedrock on its own. + 5. Late game: build the **[Heavy Launch Complex](Launch-Gantry)**, found stations with a + **[Station Charter](Station-Charter)**, and run a **[Terraformer](Terraformer)** until a dead planet grows, rains, and fills with livestock. ## Contents - **Blocks** — see the sidebar for a page on each block (ores, storage blocks, machines, station + building blocks, launch infrastructure). + - **[Items](Items)** — materials, tools, the Oxygen Suit family, rockets & fuel, charters, and + the Star Guide. + - **[Creatures](Creatures)** — the native mobs of Greenxertz, Cindara, and Glacira, plus the + terraform livestock. + +- **[Alien Villagers](Alien-Villagers)** — wary alien NPCs you befriend to trade and grow villages, + + with the **[Village Core](Village-Core)**, alien **[structures](Alien-Structures)**, + **[decoration blocks](Alien-Decoration)** and exclusive **[gear](Alien-Gear)**. + - **[Meteor Events](Meteor-Events)** — rare meteor crashes that seed the world with alien materials + and off-world ores; track them with the Meteor Tracker. + - **[Star Guide](Star-Guide)** — the in-game progression guide. - **[Configuration](Configuration)** — the five multiplier keys for server/modpack tuning. - **[Roadmap](Roadmap)** — what shipped in 1.0 and what's next. @@ -42,7 +64,7 @@ eventually **terraform** a dead planet into livable, rained-on ground. ## Dimensions at a glance | Dimension | Reached by | Sky | Notes | -|---|---|---|---| +| --- | --- | --- | --- | | Overworld | — | normal | Source of Nerosium. | | **Orbital Station** | Tier 1+ rocket | space starfield | A platform in orbit — plus any [station you found](Station-Charter) yourself. Airless/void. | | **Greenxertz** | Tier 2+ rocket | day/night + sun | Green planet; Nerosteel + Xertz Quartz. Airless. | diff --git a/wiki/Hydration-Module.md b/wiki/Hydration-Module.md index e2044e9..285171f 100644 --- a/wiki/Hydration-Module.md +++ b/wiki/Hydration-Module.md @@ -25,16 +25,24 @@ G N G ## How it works - **Must touch the Terraformer:** place it directly against any face of a Terraformer block. + A module with even a one-block gap feeds nothing. + - **Feed it glacite:** drop glacite (16 units each) or Blocks of Glacite (144 units) into its input + slot — by hand, hopper, or item pipe (the slot is exposed to automation). + - **It melts one item per pulse** into the linked Terraformer's hydration buffer (cap 1,024 units); + it never melts an item the buffer can't fully hold, so no units are ever lost. + - **No glacite = the water stage stalls.** The Terraformer GUI (and a + [Terraform Monitor](Terraform-Monitor)) shows "Needs glacite" while stage 2 waits. ## Details - ID: `nerospace:hydration_module` · Tool: pickaxe, iron tier · Drops: itself - Input items are tag-driven (`nerospace:hydration_input`) — glacite-only by default; modpacks can + widen it (vanilla ice is deliberately excluded so the Glacira trip stays meaningful). diff --git a/wiki/Item-Store.md b/wiki/Item-Store.md index ff05364..54c417a 100644 --- a/wiki/Item-Store.md +++ b/wiki/Item-Store.md @@ -18,7 +18,9 @@ N N N - **27 slots**, chest-style GUI on right-click. - Universal Pipes (item layer) and hoppers can insert/extract on **every side** — unlike a chest, + nothing blocks the top face. + - Spills its contents when broken, like a chest. A **Creative Item Store** variant supplies an endless stream of one configured item — see diff --git a/wiki/Items.md b/wiki/Items.md index 0691df9..6ace6c1 100644 --- a/wiki/Items.md +++ b/wiki/Items.md @@ -5,7 +5,7 @@ A reference for Nerospace's non-block items. (Blocks have their own pages — se ## Materials | Item | Notes | -|---|---| +| --- | --- | | **Raw Nerosium** | Mined from [Nerosium Ore](Nerosium-Ore); smelt/blast into a Nerosium Ingot. | | **Nerosium Ingot** | Core early metal; crafts tools, the grinder and the launch pad core. | | **Nerosium Dust** | Output of the [Nerosium Grinder](Nerosium-Grinder); smelts into an ingot (doubles ore yield). | @@ -21,10 +21,15 @@ A reference for Nerospace's non-block items. (Blocks have their own pages — se ## Tools - **Nerosium Pickaxe** — iron-tier mining, higher durability and a small attack bonus. Craft: + 3 Nerosium Ingots over two sticks (standard pickaxe pattern). + - **[Configurator](Configurator)** — the pipe-network tool: per-face × per-layer I/O modes, with a + full configuration panel on sneak-right-click. + - **Pipe Filter / Speed Upgrade / Capacity Upgrade** — see + [Pipe Filters and Upgrades](Pipe-Filters-and-Upgrades). ## Oxygen Suit @@ -47,29 +52,42 @@ engage. See **[Oxygen Suit](Oxygen-Suit)** for the full page. ## Rockets & Fuel - **Rocket Fuel Canister** — a stackable fuel unit. Craft (shapeless): Blaze Powder + Coal + + Iron Ingot → 2 canisters. Used to fuel rockets/machines and as a crafting core. (Reachable before your first launch — no off-world materials needed.) + - **[Fuel Refinery](Fuel-Refinery)** — automates fuel: coal + blaze powder + grid power → pipeable + liquid rocket fuel, the logistics-grade alternative to hand-crafting canisters. + - **Rocket Fuel Bucket** — a bucket of the `rocket_fuel` fluid; pour it into a rocket or [Fuel Tank](Fuel-Tank). - **Rockets (Tier 1 / 2 / 3 / 4)** — deploy onto a [Rocket Launch Pad](Rocket-Launch-Pad), fuel up, + board, pick a destination, and launch (destinations are cumulative): + - **Tier 1** → the Orbital Station (and any [founded station](Station-Charter)). - **Tier 2** → + Greenxertz. - **Tier 3** → + Cindara. (Needs a 3×3 pad ringed with [Station Wall](Station-Wall) **or** a + [Heavy Launch Complex](Launch-Gantry).) + - **Tier 4** → + Glacira. (Deploys **only** on the [Heavy Launch Complex](Launch-Gantry); + 24,000 mB tank.) The in-rocket screen shows a live fuel gauge, a selectable destination trajectory, a station selector, and a Launch button. + - **[Station Charter](Station-Charter)** — 8 [Station Wall](Station-Wall) around a + [Station Floor](Station-Floor); rename in an anvil, carry it aboard, and pick the rocket UI's **FOUND** node to found your own named orbital station. ## Star Guide - **Star Guide Book** — Book + Raw Nerosium (shapeless). Right-click to open the + **[Star Guide](Star-Guide)**, the interactive 7-chapter progression tree. + - **[Star Guide](Star-Guide)** (pedestal block) — holds the book, projects a next-goal hologram. ## Food & creature drops @@ -77,18 +95,23 @@ engage. See **[Oxygen Suit](Oxygen-Suit)** for the full page. - **Loper Haunch** — dropped by the Meadow Loper; hearty food. - **Strutter Drumstick** — dropped by the Ember Strutter; food. - **Drift Fleece** — dropped by the Woolly Drift; crafts into 4 String. + See **[Creatures](Creatures)** for the livestock themselves. ## Meteor events - **Meteor Tracker** — points to the nearest meteor (held action-bar readout: state, heading, + distance). Creative for now; a survival craft comes with the scanner. + - **Meteor Caller** — creative-only: right-click a block to call a meteor down onto it. + See **[Meteor Events](Meteor-Events)** for the full world-event, loot table and config. ## Travel devices (creative) - **Greenxertz Navigator** and the **Station / Greenxertz / Cindara / Glacira Compasses** are + creative-only one-click travel aids for testing and building (no survival recipe — rockets are the survival route). diff --git a/wiki/Launch-Gantry.md b/wiki/Launch-Gantry.md index 5ad7b15..c66ac1b 100644 --- a/wiki/Launch-Gantry.md +++ b/wiki/Launch-Gantry.md @@ -24,17 +24,24 @@ N S N ## How it works - **Forming the complex:** a full, aligned **5×5 pad** plus **≥1 Launch Gantry** adjacent at pad + level = Heavy Launch Complex. Empty-hand right-click any pad block for a **formation report** (cluster size, largest square, gantry/fuel modules, and the next missing piece). + - **Boarding:** right-click the gantry to board the deployed rocket directly — no pixel-hunting the + entity. + - **Tier gating:** - **Tier 4** deploys and launches **only** on a Heavy Launch Complex. - **Tier 3** accepts the Heavy complex **or** its classic 3×3 + [Station Wall](Station-Wall) ring. - Tiers 1–2 are happy on a plain 3×3 (and on the Heavy complex, of course). - The checks re-run at launch — breaking the gantry (or pad blocks) under a deployed rocket + grounds it. + - **Fuelling:** a [Fuel Tank](Fuel-Tank) attached to a Heavy complex pumps at **480 mB/t** (12× the + base rate) — a Tier 4's 24,000 mB tank fills in under a minute. ## Details diff --git a/wiki/Meteor-Core.md b/wiki/Meteor-Core.md index 52b578f..c09b4fb 100644 --- a/wiki/Meteor-Core.md +++ b/wiki/Meteor-Core.md @@ -23,4 +23,5 @@ weighted bonus rolls of raw ores and the rarer **Alien Tech Scrap** / **Alien Co - ID: `nerospace:meteor_core` · Tool: pickaxe · No loot table — drops its stored contents on break. - Emits light (level 10). Loot is configurable via the **Meteor events** keys in + [Configuration](Configuration). diff --git a/wiki/Meteor-Events.md b/wiki/Meteor-Events.md index 2e8599d..1c10c81 100644 --- a/wiki/Meteor-Events.md +++ b/wiki/Meteor-Events.md @@ -36,7 +36,7 @@ rolls of existing raw ores (Raw Nerosium, Raw Nerosteel, Xertz Quartz) and the r **Alien Tech Scrap** and **Alien Core**. | Item | Rarity | Role | -|---|---|---| +| --- | --- | --- | | **Alien Fragment** | common (guaranteed) | Future **scanner** feedstock. | | **Alien Tech Scrap** | uncommon | Future upgrade crafting. | | **Alien Core** | rare | High-value scanner/upgrade gate. | diff --git a/wiki/Meteor-Rock.md b/wiki/Meteor-Rock.md index 725e310..6e96a5c 100644 --- a/wiki/Meteor-Rock.md +++ b/wiki/Meteor-Rock.md @@ -11,7 +11,9 @@ visible marker of a crash site from a distance. ## Obtaining - **Mining:** any pickaxe; requires the correct tool to drop. Drops itself, so you can collect and + build with it. + - **Generation:** placed by meteor impacts — it is not found in normal world generation. ## Use diff --git a/wiki/Nerosium-Grinder.md b/wiki/Nerosium-Grinder.md index 58d46ae..40fa8a7 100644 --- a/wiki/Nerosium-Grinder.md +++ b/wiki/Nerosium-Grinder.md @@ -26,9 +26,13 @@ C C C - Nerosium Ore / Deepslate Nerosium Ore / Raw Nerosium → **2 Nerosium Dust** - Nerosium Ingot → **1 Nerosium Dust** - **Power:** it has an internal energy buffer (10,000 FE) and currently **self-charges** + (~15 FE/tick), spending ~30 FE/tick while grinding. A full grind takes ~100 progress ticks. + - **Automation:** the inventory is exposed via the item capability — hoppers/pipes can **insert into + the input from the top or sides and extract dust from the bottom**. + - **GUI:** shows a power gauge and grind-progress; emits a **comparator signal** from its energy level. ## Tips diff --git a/wiki/Nerosium-Ore.md b/wiki/Nerosium-Ore.md index b560074..7f78631 100644 --- a/wiki/Nerosium-Ore.md +++ b/wiki/Nerosium-Ore.md @@ -11,12 +11,14 @@ stone-height variant; the [Deepslate Nerosium Ore](Deepslate-Nerosium-Ore) varia - **Mining:** requires an **iron-tier pickaxe** or better. Drops **Raw Nerosium** (affected by Fortune). - **Generation:** spawns in **all Overworld biomes** in the underground-ores step, in a triangular + height band roughly **y −24 to y 56** (peaking around y 16), at a similar rate to iron. ## Use - Smelt or blast **Raw Nerosium** (or the ore itself) into a **Nerosium Ingot**. - For more yield, run it through a **[Nerosium Grinder](Nerosium-Grinder)**: one ore grinds into + **2 Nerosium Dust**, and each dust smelts into an ingot — effectively doubling your output. ## Details diff --git a/wiki/Nerosteel-Ore.md b/wiki/Nerosteel-Ore.md index bda89b4..e2464bd 100644 --- a/wiki/Nerosteel-Ore.md +++ b/wiki/Nerosteel-Ore.md @@ -14,14 +14,18 @@ your first rocket is reachable before you ever leave home. - **Mining:** requires an **iron-tier pickaxe** or better. Drops **Raw Nerosteel** (Fortune-affected). - **Generation:** - **Overworld:** a **rare, deep** seam — roughly **y −56 to y 16** (peaking below sea level), about a + quarter the frequency of Greenxertz. Enough to bootstrap the power grid and a first rocket. + - **Greenxertz:** common throughout the dimension, in a triangular band roughly **y −32 to y 72** — + the place to mine it in bulk once you arrive. ## Use - Smelt or blast **Raw Nerosteel** (or the ore) into a **Nerosteel Ingot**. - Nerosteel Ingots craft the [Rocket Launch Pad](Rocket-Launch-Pad), [Fuel Tank](Fuel-Tank), + [Oxygen Generator](Oxygen-Generator), [Station Floor](Station-Floor)/[Wall](Station-Wall), the Oxygen Suit, and the rockets themselves. diff --git a/wiki/Oxygen-Generator.md b/wiki/Oxygen-Generator.md index d43aeb1..3b39bb7 100644 --- a/wiki/Oxygen-Generator.md +++ b/wiki/Oxygen-Generator.md @@ -24,22 +24,36 @@ N N N ## How it works - **Power in:** connect a Universal Pipe carrying energy (from a Combustion/Passive Generator or a + Battery). The machine stores up to 10,000 FE and cannot burn fuel directly — it is grid-only. + - **Oxygen out:** while powered it produces up to 5 mB of Oxygen per tick (2 FE per mB) into an + 8,000 mB internal tank. Pipes connected to it can carry the gas away (green stream). + - **Oxygen field:** while the tank holds gas the machine is an oxygen **source**, slowly draining + (2 mB/t) to keep the air up. Air spreads outward through connected open space: + - **Sealed room** (walls/roof — full blocks and glass are airtight): the **whole room fills** and + stays breathable. + - **Open / leaky space:** only a bubble around the generator pressurises (the air escapes toward any + opening), so seal your base for full coverage. A door or gap counts as a leak. + - Search/coverage reaches up to ~16 blocks of connected air from the generator (configurable). - **Loss:** if the tank runs dry or the machine is broken, the oxygen **evaporates over ~10 seconds** + (configurable). + - **HUD:** a cyan **O₂ bar** appears above the hotbar in airless dimensions (it turns red when low). - **Airlock:** a player wearing a full [Oxygen Suit](Oxygen-Suit) within a few blocks (default 3) + refills the suit's air directly from the machine's tank, draining the gas — handy at a base door even when the room itself isn't breathable yet. + - **Automation:** emits a **comparator signal** from its oxygen tank level. ## Tips diff --git a/wiki/Oxygen-Suit.md b/wiki/Oxygen-Suit.md index b2f635c..8b1ec93 100644 --- a/wiki/Oxygen-Suit.md +++ b/wiki/Oxygen-Suit.md @@ -10,8 +10,8 @@ tank instead of suffocating. The tank refills instantly in any breathable zone ( terraformed ground, launch-pad safe zone) — and, since the suit-and-station integration, also at **airlocks** (below). -| | Tier 1 Oxygen Suit | Tier 2 Oxygen Suit | -|---|---|---| +| | Tier 1 Oxygen Suit | Tier 2 Oxygen Suit | +| --- | --- | --- | | Air tank | 300 (`oxygenMax`) | **600** (`oxygenSuitT2Max`) | | Airlock refill | 20 air / ~0.5 s | **40 air / ~0.5 s** | | Protection | diamond-class | slightly tougher, +toughness | @@ -27,8 +27,8 @@ overtime — oxygen drains at **4×** the normal rate (there's no separate damag the danger). The HUD badge warns with a red **HEAT!** / **COLD!** while you're exposed, with ember puffs (Cindara) or a creeping frost vignette (Glacira) as feedback. -| | **Thermal Suit** | **Cryo Suit** | -|---|---|---| +| | **Thermal Suit** | **Cryo Suit** | +| --- | --- | --- | | Shields against | Cindara heat | Glacira cold | | Craft (per piece) | Tier 2 piece + 4 **Cindrite** | Tier 2 piece + 4 **Glacite** | | Repair material | Cindrite | Glacite | @@ -73,7 +73,10 @@ rocket. ## Details - IDs: `nerospace:oxygen_suit_{helmet,chestplate,leggings,boots}`, + `nerospace:oxygen_suit_t2_{...}`, `nerospace:oxygen_suit_heat_{...}` (Thermal), `nerospace:oxygen_suit_cold_{...}` (Cryo) + - Config: `oxygenMax`, `oxygenSuitT2Max`, `oxygenSuitDrain`, `oxygenAirlockRadius`, + `oxygenAirlockRefillPerCheck`, `oxygenAirlockMbPerAir` diff --git a/wiki/Pipe-Filters-and-Upgrades.md b/wiki/Pipe-Filters-and-Upgrades.md index bbe29de..24a80e4 100644 --- a/wiki/Pipe-Filters-and-Upgrades.md +++ b/wiki/Pipe-Filters-and-Upgrades.md @@ -8,8 +8,11 @@ Restricts a pipe face's **item layer** to a single item. - **Craft** (shaped, yields 4): nerosteel corners around Iron Bars. - **Set the filter:** hold the filter in one hand and the item to match in the other, then + right-click in air. (Empty other hand = clear the filter item.) + - **Apply:** right-click a Universal Pipe **face** with the configured filter — only the matching + item is pulled or pushed through that face. Apply an empty filter to remove it. Filters affect extraction (pulling faces only grab the matching item), routing (packets won't head @@ -20,6 +23,7 @@ toward a filtered face that rejects them) and delivery. - **Craft** (shaped): redstone + gold core in a nerosteel frame. - **Install:** right-click a pipe segment (consumed, up to **3** per pipe). - Each one multiplies the segment's energy/fluid/gas **throughput** and makes items **travel faster** + through it. ## Capacity Upgrade @@ -27,6 +31,7 @@ toward a filtered face that rejects them) and delivery. - **Craft** (shaped): xertz quartz + chest core in a nerosteel frame. - **Install:** right-click a pipe segment (up to **3**). - Each one multiplies the segment's fluid/gas **buffers** and how many item stacks may be **in + transit** at once. ## Removing upgrades diff --git a/wiki/Quarry-Controller.md b/wiki/Quarry-Controller.md index 16407d1..53a0371 100644 --- a/wiki/Quarry-Controller.md +++ b/wiki/Quarry-Controller.md @@ -11,9 +11,12 @@ Landmarks](Quarry-Landmark)**, place the controller beside it, give it **frame m 1. **Builds a frame** — a glowing, see-through structural ring around the claimed rectangle. 2. **Mines** the rectangle's **interior** (the columns under the frame ring are left intact) **layer + by layer**, top to bedrock, like a 3D printer in reverse — a drill head travels the gantry to each block, one block at a time. + 3. **Buffers and auto-ejects** everything it digs: mined items into an internal inventory, and any + liquids it hits into an internal fluid tank — both push out to adjacent storage / pipes. It never destroys what it can't store: if its buffers fill or the power runs out, it **pauses** (the @@ -38,47 +41,69 @@ I I I ## Setting it up 1. **Place 3 [Quarry Landmarks](Quarry-Landmark) in an L** at the **same Y level** to define the + corners of the rectangle. The longest side must fit the tier's cap (**Tier 1 = 16×16**). + 2. **Place the controller next to / in line with** a landmark — it scans along the axes to find the + cluster, then **consumes the landmarks** and starts. + 3. **Put [Frame Casing](Upgrade-Modules) in the frame slot** (top-left of the GUI). The frame costs + **one casing per perimeter cell** (the controller's own cell and any block entities on the perimeter are skipped, leaving a gap rather than overwriting them). + 4. **Pipe power in** (see below). Building the frame is free, but **mining needs energy**. ## How it works - **Power:** an internal **200,000 FE** buffer, filled through the energy capability on any side — + connect a [Universal Pipe](Universal-Pipe) from a [Combustion](Combustion-Generator)/[Passive Generator](Passive-Generator) or a [Battery](Battery). **Dig speed scales with the power you supply**, up to the tier's per-cycle ceiling × your modules' speed bonus × the planet's speed factor. Base cost is **40 FE per block** (lowered by Efficiency modules). The dig is **paced** (a work cycle every few ticks) so even with unlimited power it mines at a steady rate, not instantly. + - **Output buffer:** 12 internal slots for mined items; **auto-ejects** into an adjacent inventory / + pipe. Mining **pauses** ("buffer full") when it can't fit a drop — nothing is ever voided. + - **Fluid buffer:** source liquids (water, lava) in the dig area are **sucked up** into a + **16,000 mB** internal tank that auto-ejects to an adjacent [Fluid Tank](Fluid-Tank) / pipe. + - **Obstacles:** it **skips** bedrock and other unbreakable blocks, and **skips the entire column** + under a tile-entity (chests, spawners, machines) so they're left intact. It also skips other quarries' frames. + - **Interior only:** it excavates the **inside** of the rectangle and never digs the columns under + the frame ring, so the frame keeps its footing and the pit walls stay clean. + - **Drill head + gantry:** while it runs, a solid 3-D **gantry** rides the frame — a bridge beam with + end trucks, a carriage that tracks the dig column, and a support shaft — and a spinning 3-D **drill bit** (chuck, flutes, and a glowing tapered tip) descends point-down to the exact block being mined. + - **The frame** is a real 3-D structure — glowing corner posts and edge rails with an open, + see-through centre (so you can watch the dig through it). + - **Far edges:** a large area is force-loaded **one chunk at a time** while actively mining, so the + dig keeps going as you range its edges; each chunk is released as soon as the dig moves past it (and any remainder when it finishes or is removed), so the quarry never pins the whole region — keeping memory use and world-save time low. + - **Reclaiming:** breaking the controller tears down its frame. ### GUI status lines | Line | Meaning | -|---|---| +| --- | --- | | **Idle — place landmarks** | No valid region found yet. | | **Building frame** | Placing the frame ring (consuming casings). | | **Mining** | Digging — `Depth` shows how many layers below the frame plane it has reached. | @@ -90,7 +115,7 @@ I I I The miner runs **anywhere it has power**, but the harsh outer moons are gated by tier: | Tier | Max area | Module slots | Base speed | Planets it can mine | -|---|---|---|---|---| +| --- | --- | --- | --- | --- | | **Tier 1** | 16 × 16 | 1 | 2 blocks/cycle | Overworld, Greenxertz, Orbital Station | | Tier 2 *(planned)* | 32 × 32 | 2 | 4 blocks/cycle | + **Cindara** | | Tier 3 *(planned)* | 64 × 64 | 4 | 8 blocks/cycle | + **Glacira** | @@ -101,18 +126,26 @@ too-low tier on a gated planet pauses with "wrong planet". ## Upgrades & the future - **[Upgrade Modules](Upgrade-Modules)** (Speed / Efficiency / Fortune / Silk Touch) slot into the + controller and tune its behaviour. They're a **cross-machine** system — the same cards will work in other machines. + - **Filters** (whitelist-keep, void the rest — e.g. trash cobble) are a planned follow-up; the + output pipeline is already built to drop them in. ## Details - ID: `nerospace:quarry_controller` · Tool: pickaxe, iron tier · Drops: itself - Companion blocks: [Quarry Landmark](Quarry-Landmark), Quarry Frame (machine-placed; no item, + drops nothing) + - Capabilities: energy **in** (any side); mined **items out** (any side); **fluid out** (any side). + The frame-casing and module slots are configuration-only — they can't be piped in or out, so automation can never pull your modules or casings (load casings by hand in the GUI) + - Config: scales with the standard `energyRateMultiplier`, `fuelCostMultiplier`, and + `machineSpeedMultiplier` (see [Configuration](Configuration)) diff --git a/wiki/Quarry-Landmark.md b/wiki/Quarry-Landmark.md index bd7daf2..027cb35 100644 --- a/wiki/Quarry-Landmark.md +++ b/wiki/Quarry-Landmark.md @@ -23,14 +23,23 @@ I ## How it works - **Three landmarks = a box.** Two landmarks "link" when they share a row or column at the same Y + within range; an L of three gives all four extents of the rectangle. + - **Same Y level.** Place all three at the same height — that height becomes the quarry's reference + plane (the frame is built there; mining runs from just below it down to bedrock). + - **Within the tier's cap.** The rectangle's longest side must fit the controller's area cap + (Tier 1 = 16). An oversized or degenerate layout makes the controller pause with "bad region". + - **Binding.** Place the [Quarry Controller](Quarry-Controller) next to / in line with a landmark. + On activation it scans the cluster, **removes the landmark blocks**, and builds the frame. + - **Cosmetic only otherwise** — the lasers are a client-side effect; landmarks have no inventory or + power. ## Details diff --git a/wiki/Roadmap.md b/wiki/Roadmap.md index 8ee118c..e004499 100644 --- a/wiki/Roadmap.md +++ b/wiki/Roadmap.md @@ -7,44 +7,65 @@ release** — the complete progression from the first nerosium ore to a terrafor ## ✅ Shipped in 1.0.0 **Materials & machines** + - Nerosium material chain (ore → raw → ingot → dust) and the **Nerosium Grinder** (double ore yield). - Planetary materials: **Nerosteel** + **Xertz Quartz** (Greenxertz), **Cindrite** (Cindara), + **Glacite** (Glacira). + - The **Fuel Refinery** (coal + blaze powder + power → pipeable rocket fuel), storage blocks, + station blocks, themed machine GUIs with animated gauges + comparator output. + - Common `c:` tags and tag-based recipes throughout, capability automation on every machine face. **Space travel** + - **Rockets Tier 1–4** with bespoke per-tier models and an interactive in-rocket UI. - The **3×3 Launch Pad** and the **Heavy Launch Complex** (5×5 + Launch Gantry) with formation + reports; Tier 3 takes the Station Wall ring **or** the Heavy complex, Tier 4 needs the Heavy complex. + - Auto-fuelling **Fuel Tanks** (up to 480 mB/t) and hands-free fuel feeding through pad blocks. - Destinations: **Orbital Station**, **Greenxertz**, **Cindara**, **Glacira** — plus + **player-founded stations** via the Station Charter (up to 64 per world). **Survival & atmosphere** + - Airless dimensions with a per-block **oxygen field** (sealed rooms, leaks, doors/glass as + boundaries), the grid-powered electrolysis **Oxygen Generator**, airlock refills, and an O₂ HUD. + - The **Oxygen Suit** in two tiers plus **Thermal** and **Cryo** hazard variants (Cindara heat / + Glacira cold = ×4 drain unprotected). **Terraforming & life** + - The **Terraformer** with staged maturation — **Rooted → Hydrated → Living** — a glacite-fed water + cycle (Hydration Module), mature per-planet biomes with real weather, and the Terraform Monitor. + - Eight bespoke **creatures**: five natives + three breedable livestock species on Living ground. **Progression & tooling** + - The **Star Guide** interactive progression tree (7 chapters / 31 steps) backed by a full + advancement tree; the creative `/nerospace gallery` showcase; a 36+-test gametest suite. **JEI integration** + - With JEI installed, the grinder, fuel refinery and combustion generator show their own recipe categories (standard recipes/tags already worked out of the box). ## 🛠️ Next up (first post-1.0 updates) - **[Solar Panels](Solar-Panel)** — sun-tracking, array-pooling power generation across **three tiers** + (Tier 1 1×1, Tier 2 2×2, Tier 3 3×3 multiblocks), each folding the previous panel into its recipe. + - **EMI integration** as soon as it reaches 26.1. - **Balance tuning from player feedback** — the config multipliers make this cheap. - **Bespoke audio** to replace the vanilla-alias placeholders. @@ -56,6 +77,7 @@ See **[Future Features](Future-Features)**. ## ⏳ Deferred - **Cross-mod integration** (e.g. Mekanism) waits until those mods port to Minecraft 26.1. Nerospace + is standalone by design — tags + NeoForge capabilities mean most integration comes free later. ## Contributing / feedback diff --git a/wiki/Rocket-Launch-Pad.md b/wiki/Rocket-Launch-Pad.md index fbd6587..518da8d 100644 --- a/wiki/Rocket-Launch-Pad.md +++ b/wiki/Rocket-Launch-Pad.md @@ -23,24 +23,35 @@ N N N ## How it works - **Deploy:** right-click the pad with a rocket item to place the rocket entity on it. **A complete, + aligned 3×3 pad is required** — deploying on a partial pad shows a clear message instead. The same check re-runs at launch, so breaking pad blocks under a deployed rocket grounds it. + - **Tier gating:** - **Tier 3** additionally requires the 3×3 pad to be **ringed with + [Station Wall](Station-Wall)** (the 16-block border of the surrounding 5×5, at pad level) — **or** a [Heavy Launch Complex](Launch-Gantry); either works. + - **Tier 4** deploys **only** on the Heavy Launch Complex. - **Heavy Launch Complex:** a complete, aligned **5×5 pad** plus at least one + **[Launch Gantry](Launch-Gantry)** module adjacent at pad level. Right-click the gantry to board the rocket; empty-hand right-click any pad block for a **formation report** (cluster size, largest square, modules present, next missing piece). See [Launch Gantry](Launch-Gantry). + - **Multiblock:** pads placed adjacently form a cluster. A full, aligned **3×3 pad** lets a + [Fuel Tank](Fuel-Tank) auto-fuel **4× faster** — and a Heavy complex **12× faster** (480 mB/t). + - **Automation proxy:** while a rocket stands on the cluster, every pad block exposes the rocket's + **fuel-intake slot** as an item capability — point a hopper or a [Universal Pipe](Universal-Pipe) (item layer) at any pad block to feed in Rocket Fuel Buckets/Canisters (and pull out the empty buckets). A Fuel Tank + pipes on a 3×3 pad fully automates launch prep. + - **Safe zone:** within a few blocks of a pad you can breathe, so you won't suffocate the instant you + land. Build an [Oxygen Generator](Oxygen-Generator) base for anything beyond the landing area. ## Details diff --git a/wiki/Solar-Panel.md b/wiki/Solar-Panel.md index eb8d87a..d8f16eb 100644 --- a/wiki/Solar-Panel.md +++ b/wiki/Solar-Panel.md @@ -41,16 +41,21 @@ Each tier folds the **previous panel** into its recipe, so upgrading reuses what ## How it works - **Sunlight in, FE out.** Output follows the sun: full at noon, tapering to **nothing at night**. + The panel needs a **clear view of the sky** — anything solid directly above it stops generation. + - **Weather** cuts output: rain/snow drops it to ~40%, a thunderstorm to ~25%. - **Airless dimensions** (Orbital Station, Greenxertz, Cindara, Glacira, founded stations) have a + permanent sun, so panels there run at full **and earn a ×2 bonus** — solar is the natural off-world power source. + - Output and storage scale by tier (all values × the `energyRateMultiplier` config). The buffer is + **extract-only** — it never accepts a push. | Tier | Footprint | Output @ noon | Storage | -|---|---|---|---| +| --- | --- | --- | --- | | **Tier 1** | 1×1 | 20 FE/t | 50,000 FE | | **Tier 2** | 2×2 | 100 FE/t | 250,000 FE | | **Tier 3** | 3×3 | 400 FE/t | 1,000,000 FE | @@ -60,19 +65,27 @@ Each higher tier is stronger **per area** than tiling the lower one, and carries ### Multiblocks (Tier 2 & 3) - A Tier 2 / Tier 3 panel is a **single item that fills its whole N×N footprint** when placed — you need + that flat area clear (it won't place into an obstructed footprint). + - It behaves as **one unit**: one big tilting deck, one pooled buffer, energy on every outer face. - **Breaking any cell returns the whole panel as one item** — no duplication, and you get your panel + back wherever you mined it. ### Arrays - Place same-tier units **next to each other** and they automatically merge into one **array**: the + array's storage is the **sum of every unit's buffer** and its generation the **sum of every unit's output**. Build wider for more of both — arrays can be almost any size. + - **Every side is an output port.** A Universal Pipe (energy layer) — or any machine/Battery — touching + *any* face pulls from the shared array pool, so one pipe drains the whole array. + - **Tiers never mix.** A Tier 1 array and a Tier 2 array placed side by side stay separate; only units + of the *same* tier pool together. ### Appearance diff --git a/wiki/Star-Guide.md b/wiki/Star-Guide.md index 6080509..1c2d323 100644 --- a/wiki/Star-Guide.md +++ b/wiki/Star-Guide.md @@ -27,15 +27,21 @@ S S S - **Read anywhere:** right-click with the **Star Guide Book** in hand to open the guide GUI. - **Pedestal:** right-click the empty pedestal with the book to install it (lectern-style; breaking + the pedestal or sneak-right-clicking pops the book back out). An installed pedestal projects a rotating **hologram of your next goal**, and right-clicking it opens the same GUI. + - **The tree:** chapters cover **Nerosium → Machines → Power Grid → Rocketry → New Worlds → + [Mining](Quarry-Controller) → Surviving Vacuum → Terraforming**. Completed steps pulse; each step carries guide text telling you exactly what to do next. + - **Mining chapter:** the quarry automation track — [Quarry Landmark](Quarry-Landmark) → + [Frame Casing](Upgrade-Modules) → [Quarry Controller](Quarry-Controller) → [Upgrade Modules](Upgrade-Modules). Gated behind nerosteel (Greenxertz), since the frame is built from it. + - **Comparator:** the pedestal emits a signal when a book is installed. ## Details diff --git a/wiki/Station-Charter.md b/wiki/Station-Charter.md index 10c0ef1..8e44fea 100644 --- a/wiki/Station-Charter.md +++ b/wiki/Station-Charter.md @@ -25,15 +25,24 @@ W W W ## How it works - **Naming:** rename the charter in an **anvil** before flying — that becomes the station's name. + An unnamed charter founds "Station N". + - **Founding:** board any rocket with the charter in your inventory; the rocket UI shows the + **FOUND** node next to the station selector. Select it and launch. The charter is consumed, the platform + bound Station Core are placed, and the station registers as a destination. + - **Capacity:** up to **64** founded stations per world. Founding also grants the *founded station* + advancement and ticks the Star Guide's rocketry chapter. + - **Unregistering:** breaking the station's [Station Core](Station-Core) removes it from the + destination list. + - **Privacy note:** stations store no owner data — any player may fly to (or unregister) any + station. ## Details diff --git a/wiki/Station-Core.md b/wiki/Station-Core.md index 0cb878d..542c3b5 100644 --- a/wiki/Station-Core.md +++ b/wiki/Station-Core.md @@ -17,10 +17,15 @@ station can be dissolved and re-founded elsewhere. ## How it works - **Anchor:** the Core binds the platform to its registry slot; it survives restarts and chunk + unloads. + - **Unregister:** break the Core and the station disappears from every rocket's destination list + (rockets already heading there fall back gracefully). + - **No ownership:** stations don't record who founded them — any player can use or dissolve any + station. ## Details diff --git a/wiki/Station-Floor.md b/wiki/Station-Floor.md index 2950ff0..b7bb53c 100644 --- a/wiki/Station-Floor.md +++ b/wiki/Station-Floor.md @@ -11,6 +11,7 @@ base in orbit (and anywhere else). A sturdy, blast-resistant deco/building block - **Craft:** a ring of **8 Nerosteel Ingots** around an empty centre → **8 Station Floor**. - The station's starter platform is generated automatically on first arrival (a single shared + platform — see [Rocket Launch Pad](Rocket-Launch-Pad) / Roadmap). ## Details diff --git a/wiki/Station-Wall.md b/wiki/Station-Wall.md index 48f9fcb..9e7506e 100644 --- a/wiki/Station-Wall.md +++ b/wiki/Station-Wall.md @@ -15,11 +15,16 @@ hull before you can craft a Tier 3 rocket. ## Use - Building/sealing pressurised rooms — like all full opaque blocks, it is **airtight** and counts as + an oxygen seal (see [Oxygen Generator](Oxygen-Generator)). + - Required in the **Tier 3 Rocket** recipe. - **Ringing a 3×3 [launch pad](Rocket-Launch-Pad)** (the 16-block border at pad level) is one of the + two ways to deploy a Tier 3 rocket — the other is the [Heavy Launch Complex](Launch-Gantry). + - An ingredient of the **[Station Charter](Station-Charter)** and the + **[Launch Gantry](Launch-Gantry)**. ## Details diff --git a/wiki/Terraform-Monitor.md b/wiki/Terraform-Monitor.md index 5c30676..8bd8d3b 100644 --- a/wiki/Terraform-Monitor.md +++ b/wiki/Terraform-Monitor.md @@ -19,11 +19,16 @@ N N N ## How it works - **Place it anywhere** on or near terraformed land. It links to the nearest Terraformer within + **32 blocks** automatically (no wiring). + - **GUI readout:** the local column's stage (Dead / Rooted / Hydrated / Living), the linked + machine's three stage radii, its hydration units, and a red "Needs glacite" warning when the water stage has stalled. + - **Comparator output = local stage:** 0 (dead), 5 (Rooted), 10 (Hydrated), 15 (Living). Open the + ranch gates when the land turns Living, or alarm when terraforming reaches your base. ## Details diff --git a/wiki/Terraformer.md b/wiki/Terraformer.md index da47351..41df959 100644 --- a/wiki/Terraformer.md +++ b/wiki/Terraformer.md @@ -11,7 +11,7 @@ last, so a long-running world is always a gradient — raw chemistry at the edge the centre: | Stage | Name | What it does | -|---|---|---| +| --- | --- | --- | | 1 | **Rooted** | grass + dirt, permanently breathable, vibrant neon **terraformed biome**, sparse plants | | 2 | **Hydrated** | basins below the machine's water table fill with real water — costs **glacite** via a [Hydration Module](Hydration-Module) | | 3 | **Living** | the biome settles into a natural **per-planet palette** (meadow / savanna / tundra) with grown trees, **rain or snow**, and starter herds of the planet's [livestock](Creatures) | @@ -31,34 +31,52 @@ N B N ## How it works - **Grid power:** runs exclusively on piped energy — connect a Universal Pipe carrying FE. Its + 100,000 FE buffer drains as it works (higher tiers / more power = faster). + - **Expanding frontiers:** each work cycle it converts a ring of surface columns per stage, then grows + that stage's radius — **uncapped**, but energy-throttled. Stage 1 keeps priority (breathable ground never waits); the trailing stages take smaller shares and always stay inside the stage ahead. Stage-2 columns cost **2×** energy plus **1 hydration unit per water source**; stage-3 columns cost **4×** energy. + - **Water (Hydrated):** the water table sits one block below the machine's base. Basins under it fill + flush with the ground; hills stay dry; chasms deeper than the cap are skipped. No glacite in the buffer = stage 2 stalls (the GUI says "Needs glacite"). On Glacira the lakes refreeze naturally. + - **Life (Living):** the mature biome turns on real **weather** (rain; snow accumulates on Glacira), + sparse **grown trees** (oak/birch, acacia, spruce by planet), and seeds starter pairs of the planet's livestock (population-capped). + - **Cosmetic drift:** settled land keeps sprouting sparse ground cover on its own — pure garnish, + budgeted and toggleable (`terraformDriftEnabled`). + - **Tiers:** drop an upgrade into the upgrade slot — a **Nerosteel Ingot → Tier 2**, a **Cindrite → + Tier 3**. Higher tiers convert more columns per cycle; **Tier 3** also seeds a low rate of ore into the converted subsurface (configurable, defaults to Nerospace ores). + - **Lazy / catch-up:** columns in unloaded chunks are skipped and converted later when you explore into + them — all stages replay on chunk load, so a terraformed planet finishes as you walk it (no forced chunk-loading by default). + - **Old worlds:** existing Terraformers keep working untouched — their land counts as stage 1 and the + new stages simply sweep over it once you build the new blocks. No migration. ## Details - ID: `nerospace:terraformer` · Tool: pickaxe, iron tier · Drops: itself - Companions: [Hydration Module](Hydration-Module) (glacite intake, must touch), + [Terraform Monitor](Terraform-Monitor) (stage readout + comparator) + - Config: `terraformWaterEnabled`, `terraformWaterMaxDepth`, `terraformDriftEnabled`, + `terraformDriftPerSecond`, `terraformFaunaEnabled`, `terraformPlantsEnabled`, `terraformResourcesEnabled`, `terraformResourceOres`, `terraformMaxColumnsPerTick`, … diff --git a/wiki/Trash-Can.md b/wiki/Trash-Can.md index e9915ac..3fc4836 100644 --- a/wiki/Trash-Can.md +++ b/wiki/Trash-Can.md @@ -24,11 +24,17 @@ I I I ## How it works - **Voids every layer:** exposes the item, fluid, and gas capabilities on **all six faces**; whatever + is inserted is discarded. + - **Never backs up:** its internal sinks are emptied every tick, so it always has room and never + rejects or returns anything. + - **Input only:** there is no way to extract from it — your modules, fuel, or anything else routed + past it stays safe; only what is explicitly piped *into* the Trash Can is lost. + - **No energy:** it does not accept power (energy isn't "trash"); only items, fluids, and gas. ## Tips diff --git a/wiki/Universal-Pipe.md b/wiki/Universal-Pipe.md index a7c56fb..dd7d0f3 100644 --- a/wiki/Universal-Pipe.md +++ b/wiki/Universal-Pipe.md @@ -10,7 +10,7 @@ to any machine, tank or inventory, forming a **network** that behaves as one sha resource layers ride the same connection graph simultaneously: | Layer | Colour | Rule | -|---|---|---| +| --- | --- | --- | | Energy (FE) | red | shared pool, balanced across all segments | | Fluid | blue | **one fluid per network** — the first fluid in claims it until drained | | Gas | green | one gas per network; **breaking a pipe vents its gas** (visible puff) | @@ -31,15 +31,22 @@ N N N ## How it works - **Connections:** the tube grows an arm toward anything it can talk to (pipes, machines, tanks, + chests). Every face has an independent I/O mode **per layer**: Auto → In → Out → Off (set with the [Configurator](Configurator)). + - **Energy/fluid/gas:** the network pulls from providers, pushes to receivers and balances its own + buffers — coloured pulse streams show what's flowing where. + - **Items:** pulling faces extract from inventories; packets physically travel through the tube, + re-route at junctions, and **never spill** — if every destination is full they park and wait. Breaking a pipe drops the items inside it. + - **Filters & upgrades:** see [Pipe Filters and Upgrades](Pipe-Filters-and-Upgrades). - **Readout:** right-click a pipe with an empty hand for its current contents; sneak-right-click with + an empty hand pops installed upgrades out. ## Tips @@ -51,4 +58,5 @@ a single line serves both a source and a sink (set the source-side face to In so - ID: `nerospace:universal_pipe` · Tool: pickaxe, iron tier · Drops: itself - Config: `energyPipeCapacity/Throughput`, `fluidPipe…`, `gasPipe…`, `itemPipeTicksPerBlock`, + `itemPipeExtractAmount`, `itemPipeExtractPeriod` diff --git a/wiki/Upgrade-Modules.md b/wiki/Upgrade-Modules.md index 22acd74..2ff6878 100644 --- a/wiki/Upgrade-Modules.md +++ b/wiki/Upgrade-Modules.md @@ -25,7 +25,7 @@ across its slots and sums their effects (you can stack several). The [Quarry Con has **1 module slot at Tier 1** (more at higher tiers). | Module | Effect | Notes | -|---|---|---| +| --- | --- | --- | | **Speed Module** | +50% to the work-cap per module | Lets the machine do more when fed more power; capped at ×8. | | **Efficiency Module** | −15% energy cost per module | Floors at 25% of the base cost. | | **Fortune Module** | Applies Fortune to mined blocks | Stacks up to Fortune III. | @@ -42,7 +42,7 @@ R S R `N` = [Nerosteel Ingot](Items) · `R` = Redstone · `S` = signature: | Module | Signature `S` | -|---|---| +| --- | --- | | Speed | Sugar | | Efficiency | Lapis Lazuli | | Fortune | Diamond | @@ -51,5 +51,7 @@ R S R ## Details - IDs: `nerospace:frame_casing`, `nerospace:speed_module`, `nerospace:efficiency_module`, + `nerospace:fortune_module`, `nerospace:silk_touch_module` + - Used by: [Quarry Controller](Quarry-Controller) (more machines planned) diff --git a/wiki/Village-Core.md b/wiki/Village-Core.md new file mode 100644 index 0000000..6c0e3ed --- /dev/null +++ b/wiki/Village-Core.md @@ -0,0 +1,70 @@ +# Village Core + +The controller block at the heart of an alien village — claim it, stock it, and teach the village to +build itself. + +## Overview + +The **Village Core** is the hub of the alien-village system: it stores the owner, a construction +stockpile, the build queue, passive production, and the current quest. It appears in generated +**[structures](Alien-Structures)** and can be placed by hand. + +## Obtaining + +- Found at the centre of every **[hamlet, ruin and mega-city](Alien-Structures)**. +- Available in the Nerospace creative tab. + +## Claiming & growing a village + +1. **Claim** — right-click an unclaimed core to become its owner. +2. **Stock it** — right-click with **Block of Nerosteel** to fill the construction store. +3. **Teach the next building** — right-click as the owner. If the nearby **[villagers](Alien-Villagers)** + + trust you enough *and* the store has the materials, the core commits the cost and starts building. + +4. **Watch it rise** — the building is placed **block-by-block over real time** with particles. + + Right-click again to read the % progress. + +The core reads your standing as the **highest reputation tier among nearby villagers**, so trading with +the locals is what unlocks teaching. + +### Build catalogue + +| Building | Requires | Cost | +| --- | --- | --- | +| Hut | trust tier 2 | 32 Nerosteel | +| Workshop | trust tier 3 | 48 Nerosteel | + +Buildings are taught in order as the village grows. + +## Functional village + +Once buildings exist, the core becomes a small engine: + +- **Production** — completed buildings periodically yield goods (Hut → bread, Workshop → nerosteel). + + **Sneak-right-click** the core to collect the output and read the current task. + +- **Quests** — the village posts a fetch task (e.g. *bring 8 Xertz Quartz*). Right-click the core with + + the requested item to hand it in for **emeralds + a village-wide trust bump**; a new task rolls. + +- **Raids** — at night, claimed villages near a player are occasionally raided by hostile mobs. + + Toggle with `alienRaidsEnabled` (see **[Configuration](Configuration)**). + +## Interactions at a glance + +| Input | Result | +| --- | --- | +| Right-click (empty hand, unclaimed) | Claim the village | +| Right-click with Block of Nerosteel | Deposit into the construction store | +| Right-click (owner, empty hand) | Teach/raise the next building, or read progress | +| Sneak-right-click | Collect production + read the current quest | +| Right-click with the quest item | Hand in the quest for emeralds + trust | + +## Details + +- ID: `nerospace:village_core` · tool pickaxe · drops itself +- Saves owner, stockpile, built count, the active build job, production buffers, and the current quest. diff --git a/wiki/Xertz-Quartz-Ore.md b/wiki/Xertz-Quartz-Ore.md index 5c8d14b..ce6c414 100644 --- a/wiki/Xertz-Quartz-Ore.md +++ b/wiki/Xertz-Quartz-Ore.md @@ -10,8 +10,11 @@ is plentiful. It is a key crafting reagent, most notably for **Rocket Fuel Canis ## Obtaining - **Mining:** any pickaxe works. Drops **Xertz Quartz** directly (Fortune-affected). The ore can also + be smelted into Xertz Quartz if you prefer. + - **Generation:** spawns abundantly across the **Greenxertz** dimension, uniformly between about + **y 0 and y 110**. ## Use diff --git a/wiki/_Sidebar.md b/wiki/_Sidebar.md index e53e847..cf5e2cf 100644 --- a/wiki/_Sidebar.md +++ b/wiki/_Sidebar.md @@ -59,6 +59,14 @@ - [Station Wall](Station-Wall) - [Station Core](Station-Core) +**Alien Villages** + +- [Alien Villagers](Alien-Villagers) +- [Village Core](Village-Core) +- [Alien Structures](Alien-Structures) +- [Alien Decoration](Alien-Decoration) +- [Alien Gear](Alien-Gear) + **World Events** - [Meteor Events](Meteor-Events)