@neocompose/cli 0.87.1 → 0.88.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/README.md +14 -0
- package/dist/neo-runtime.mjs +3789 -1141
- package/dist/test-worker.mjs +2450 -116
- package/package.json +1 -1
- package/skills/neocompose-cli/SKILL.md +95 -279
- package/skills/neocompose-cli/references/cli-development.md +101 -117
- package/skills/neocompose-cli/references/declarations/annotations-and-storage.md +112 -0
- package/skills/neocompose-cli/references/declarations/constructors.md +164 -0
- package/skills/neocompose-cli/references/declarations/declarations.md +103 -0
- package/skills/neocompose-cli/references/declarations/initializers.md +100 -0
- package/skills/neocompose-cli/references/declarations/member-kinds.md +107 -0
- package/skills/neocompose-cli/references/declarations/overloads.md +50 -0
- package/skills/neocompose-cli/references/dialogues.md +145 -0
- package/skills/neocompose-cli/references/neoscript/collections.md +98 -0
- package/skills/neocompose-cli/references/neoscript/control-flow.md +123 -0
- package/skills/neocompose-cli/references/neoscript/delegates-and-listeners.md +107 -0
- package/skills/neocompose-cli/references/neoscript/math-and-random.md +114 -0
- package/skills/neocompose-cli/references/neoscript/neoscript.md +112 -0
- package/skills/neocompose-cli/references/neoscript/strings.md +47 -0
- package/skills/neocompose-cli/references/neoscript/testing-and-migrations.md +73 -0
- package/skills/neocompose-cli/references/neoscript/types-and-narrowing.md +111 -0
- package/skills/neocompose-cli/references/sync/branches-and-history.md +69 -0
- package/skills/neocompose-cli/references/sync/conflicts-and-recovery.md +68 -0
- package/skills/neocompose-cli/references/sync/sync.md +96 -0
- package/skills/neocompose-cli/references/sync/unity-export.md +47 -0
- package/skills/neocompose-cli/references/values/defaults-and-localization.md +86 -0
- package/skills/neocompose-cli/references/values/files.md +71 -0
- package/skills/neocompose-cli/references/values/identities.md +97 -0
- package/skills/neocompose-cli/references/values/references.md +151 -0
- package/skills/neocompose-cli/references/values/values.md +110 -0
- package/skills/neocompose-cli/references/world/animation.md +133 -0
- package/skills/neocompose-cli/references/world/grids-and-cell-patterns.md +55 -0
- package/skills/neocompose-cli/references/world/variants.md +112 -0
- package/skills/neocompose-cli/references/world/world.md +105 -0
- package/skills/neocompose-cli/references/animation-and-world-authoring.md +0 -582
- package/skills/neocompose-cli/references/commands-and-sync.md +0 -408
- package/skills/neocompose-cli/references/declarations-and-construction.md +0 -773
- package/skills/neocompose-cli/references/neoscript.md +0 -990
- package/skills/neocompose-cli/references/values-identities-and-references.md +0 -421
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# Values
|
|
2
|
+
|
|
3
|
+
How stored values are written: the root envelope, static members, local
|
|
4
|
+
initializers, and the single-owner rule. Open a leaf from
|
|
5
|
+
[References](#references) for IDs, `Reference<T>`, defaults, localization, or
|
|
6
|
+
files.
|
|
7
|
+
|
|
8
|
+
## Root and static bindings
|
|
9
|
+
|
|
10
|
+
Edit only the initializer after `=` in the compiler-owned `Root.neo`. The root
|
|
11
|
+
member's name, type, ID, lock, and storage are read-only.
|
|
12
|
+
|
|
13
|
+
```neo
|
|
14
|
+
Root root = new() {
|
|
15
|
+
@id("root-assets-member-id")
|
|
16
|
+
@storage(allowed: .Immutable)
|
|
17
|
+
Assets Assets = new() {
|
|
18
|
+
Outposts = [Assets.Capitol.Clone()],
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Persisted reusable values are static members on ordinary classes:
|
|
24
|
+
|
|
25
|
+
```neo
|
|
26
|
+
@id("assets-class-id")
|
|
27
|
+
@storage(allowed: .Immutable)
|
|
28
|
+
class Assets {
|
|
29
|
+
@id("capitol-member-id")
|
|
30
|
+
static Outpost Capitol = new {
|
|
31
|
+
Name = "Capitol",
|
|
32
|
+
Image = Images.Capitol.Slice(0),
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
static Outpost Home => Assets.Capitol;
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The member ID owns a root or static binding; never add a second `@id` before
|
|
40
|
+
its initializer. A computed static getter such as `Home` is an alias with no
|
|
41
|
+
binding or row. Statics created together may refer to each other in any
|
|
42
|
+
order with no preliminary push.
|
|
43
|
+
|
|
44
|
+
## One owner per value
|
|
45
|
+
|
|
46
|
+
Class, List, Set, and Dictionary values have exactly one owner. Scalars and
|
|
47
|
+
delegates copy. Placing an owned value elsewhere is refused at its authored
|
|
48
|
+
position by status, push, and `neo test`: a static, a static's field, a
|
|
49
|
+
collection entry, a local placed twice, or a constructor parameter stored in
|
|
50
|
+
an ordinary field. Clone it or reference it:
|
|
51
|
+
|
|
52
|
+
```neo
|
|
53
|
+
static Outpost Annex = Assets.Capitol.Clone();
|
|
54
|
+
static Reference<Outpost> Seat = Reference(Assets.Capitol);
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Local initializers
|
|
58
|
+
|
|
59
|
+
Project initializers may live in any `.neo` file and need no imports. They
|
|
60
|
+
are source organization, not runtime globals or server records.
|
|
61
|
+
|
|
62
|
+
```neo
|
|
63
|
+
List<NeoSortingLayer> SORTING_LAYERS = [
|
|
64
|
+
@id("existing-layer-id")
|
|
65
|
+
new NeoSortingLayer() { Name = "Default" }
|
|
66
|
+
];
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
- Consume it with `SortingLayers = SORTING_LAYERS`. The consuming member owns
|
|
70
|
+
the identity; add no ID at the use. Keep existing nested IDs when
|
|
71
|
+
extracting; omit IDs on new rows.
|
|
72
|
+
- An aggregate local has one owning use. Use `Reference(SORTING_LAYERS)` where
|
|
73
|
+
the member allows it; for an independent copy, copy the initializer without
|
|
74
|
+
IDs.
|
|
75
|
+
- Scalars may have several uses. If the web later makes them diverge, pull
|
|
76
|
+
inlines the differing uses.
|
|
77
|
+
- Complete member and collection entries compose inside constructor-backed
|
|
78
|
+
initializers and keep their concrete class.
|
|
79
|
+
- A class local passed as a constructor argument or executable operand names
|
|
80
|
+
its single stored instance; see
|
|
81
|
+
[identities](identities.md#locals-as-constructor-arguments).
|
|
82
|
+
- Scalar locals can't be used in expressions such as `BASE + 1`; inline the
|
|
83
|
+
expression. Executable closures can't read locals.
|
|
84
|
+
- The editor offers extract-to-file, inline on a sole use, and duplicate at a
|
|
85
|
+
use. Move created files freely.
|
|
86
|
+
|
|
87
|
+
## Dictionary keys
|
|
88
|
+
|
|
89
|
+
Enum-keyed dictionaries take contextual or qualified option keys:
|
|
90
|
+
|
|
91
|
+
```neo
|
|
92
|
+
Dictionary<ToolType, float> Damage = { .Pickaxe: 10, ToolType.Axe: 2 };
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Pull writes `.Option`. A quoted raw option ID also works; a quoted option name
|
|
96
|
+
such as `"Pickaxe"` is not an ID. Keys of deleted options stay as quoted IDs.
|
|
97
|
+
String keys are data: `dictionary["Id"]` reads an entry, while a class's
|
|
98
|
+
`.Id` is its identity. String keys may be empty, contain whitespace, or use
|
|
99
|
+
names like `__proto__`.
|
|
100
|
+
|
|
101
|
+
## References
|
|
102
|
+
|
|
103
|
+
- [Identities](identities.md): when to keep, omit, or author `@id`; moving
|
|
104
|
+
nested values; locals as constructor arguments.
|
|
105
|
+
- [References](references.md): `Reference<T>` declaration, handle vs slot
|
|
106
|
+
semantics, source forms, same-push resolution.
|
|
107
|
+
- [Defaults and localization](defaults-and-localization.md): virtual
|
|
108
|
+
members, sparse overrides, construction rebuilds, localized text, `neo loc`.
|
|
109
|
+
- [Managed files](files.md): image and audio registries, binaries, Unity
|
|
110
|
+
import settings.
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# Animation
|
|
2
|
+
|
|
3
|
+
Frame overrides, child selectors, segments, and tracks.
|
|
4
|
+
|
|
5
|
+
A `NeoAnimationClip<T>` has `FPS`, `Duration`, `Frames`, and `Tracks`. Its
|
|
6
|
+
`NeoAnimationFrame<T>` rows hold `Index`, `Overrides`, `ChildOverrides`, and
|
|
7
|
+
`Actions`. Frames are sparse: each applies from its index until the next row.
|
|
8
|
+
|
|
9
|
+
## Partial overrides
|
|
10
|
+
|
|
11
|
+
Frame `Overrides` and [variant overrides](variants.md) accept partial
|
|
12
|
+
structured literals:
|
|
13
|
+
|
|
14
|
+
```neo
|
|
15
|
+
new NeoAnimationFrame<MySprite> {
|
|
16
|
+
Index = 2,
|
|
17
|
+
Overrides = new { Sprite = new { SliceIndex = 1 } },
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
An empty `new { }` changes nothing. A partial changes only named fields; a
|
|
22
|
+
whole-leaf value replaces the leaf. Frame overrides may change `Enabled`,
|
|
23
|
+
`FlipX`, and `SortingOrder` but never add, remove, or reorder `Children`.
|
|
24
|
+
|
|
25
|
+
## Child selectors
|
|
26
|
+
|
|
27
|
+
Tracks and frame overrides find direct children through selector delegates,
|
|
28
|
+
not row IDs. Match on the project's own stable identity, such as an immutable
|
|
29
|
+
`Name`, slug, or semantic position:
|
|
30
|
+
|
|
31
|
+
```neo
|
|
32
|
+
class LegPart : NeoObject {
|
|
33
|
+
public PantsSprite SelectPants() {
|
|
34
|
+
var child = this.Children.FirstOrDefault((candidate) => {
|
|
35
|
+
return candidate.Name == "Pants";
|
|
36
|
+
});
|
|
37
|
+
if (child is PantsSprite pants) {
|
|
38
|
+
return pants;
|
|
39
|
+
}
|
|
40
|
+
throw "Pants child is missing or has the wrong type";
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
- Prefer a named function when the rule is reused or deserves a clear
|
|
46
|
+
failure; compatible inline closures also work.
|
|
47
|
+
- A selector such as `this.SelectPants` runs in the lexical scope of the
|
|
48
|
+
class that declares the clip: `this` is the `LegPart`, not the track row or
|
|
49
|
+
an outer object that later copies the default.
|
|
50
|
+
- Only for a deliberately authored child slot whose placement clones carry
|
|
51
|
+
`sourceValueId`, return
|
|
52
|
+
`Reference<T>(id: "<authored-child-id>", withProvenance: true)`. Missing
|
|
53
|
+
optional slots are skipped with diagnostics. A plain exact-ID reference
|
|
54
|
+
reintroduces the row-ID coupling selectors exist to avoid.
|
|
55
|
+
|
|
56
|
+
## Segments
|
|
57
|
+
|
|
58
|
+
`NeoAnimationSegment<T>` is a frame-indexed value lane. Its
|
|
59
|
+
`NeoAnimationSegmentFrame<T>` rows have `Index` and `Value`; sparse values
|
|
60
|
+
hold until the next row or `Duration`. Pass `Value` through the constructor:
|
|
61
|
+
`new NeoAnimationSegmentFrame<int>(value: 60)`. `NeoSpriteAnimationSegment`
|
|
62
|
+
is the sprite specialization.
|
|
63
|
+
|
|
64
|
+
## Tracks
|
|
65
|
+
|
|
66
|
+
`NeoAnimationClip.Tracks` is one polymorphic list:
|
|
67
|
+
|
|
68
|
+
- `NeoAnimationChildTrack` plays `ClipKey` on the child its `Selector`
|
|
69
|
+
returns.
|
|
70
|
+
- A project subclass of `NeoAnimationSegmentTrack<TChild, TValue>` plays a
|
|
71
|
+
segment onto one target member. Implement `Segment` as a stored value,
|
|
72
|
+
lookup, or getter.
|
|
73
|
+
- `NeoSpriteAnimationSegmentTrack<TChild extends NeoSpriteObject>` targets
|
|
74
|
+
`NeoSpriteObject.Sprite`.
|
|
75
|
+
|
|
76
|
+
```neo
|
|
77
|
+
class StoredPantsTrack : NeoSpriteAnimationSegmentTrack<PantsSprite> {
|
|
78
|
+
public override NeoSpriteAnimationSegment Segment = new();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// inside LegPart
|
|
82
|
+
public NeoAnimationClip<LegPart> Walk = new NeoAnimationClip<LegPart> {
|
|
83
|
+
Tracks = [
|
|
84
|
+
new StoredPantsTrack(selector: this.SelectPants) {
|
|
85
|
+
StartFrame = 0,
|
|
86
|
+
Direction = .Reverse,
|
|
87
|
+
OffsetEndIndex = 3,
|
|
88
|
+
},
|
|
89
|
+
]
|
|
90
|
+
};
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
- Tracks have `StartFrame`, `Direction` (`.Forward`/`.Reverse`), and a crop
|
|
94
|
+
window `OffsetStartIndex`/`OffsetEndIndex`. Cropping happens before
|
|
95
|
+
reversing; content past the clip truncates. Empty or inverted windows and
|
|
96
|
+
tracks that never enter the clip are rejected.
|
|
97
|
+
- The selector's return type must fit `TChild` and the segment value `TValue`.
|
|
98
|
+
- `Refresh` controls selector re-evaluation; track reads and segment getters
|
|
99
|
+
re-resolve on the next frame after a watched dependency changes.
|
|
100
|
+
- Tracks writing the same member in one frame apply in list order; the last
|
|
101
|
+
wins.
|
|
102
|
+
- New track rows omit `@id`; keep IDs already on pulled rows.
|
|
103
|
+
|
|
104
|
+
## Animating class members
|
|
105
|
+
|
|
106
|
+
A track can target a class member. Each frame writes the whole value, and a
|
|
107
|
+
`null` frame clears it. The target must be a writable Save, Session, or
|
|
108
|
+
Writable member on the child, and the segment class must fit it. Every
|
|
109
|
+
`NeoObjectBase` declares `Collider`, so an attack hit box is a Session
|
|
110
|
+
`Collider` override plus a `NeoCollider?` track:
|
|
111
|
+
|
|
112
|
+
```neo
|
|
113
|
+
class ColliderAnimationSegment : NeoAnimationSegment<NeoCollider?> {
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
@settings(target: NeoObjectBase.Collider)
|
|
117
|
+
class SwingColliderTrack(NeoDelegate<SwordSprite> selector)
|
|
118
|
+
: NeoAnimationSegmentTrack<SwordSprite, NeoCollider?>(selector: selector) {
|
|
119
|
+
public override ColliderAnimationSegment Segment = new ColliderAnimationSegment() {
|
|
120
|
+
Duration = 3,
|
|
121
|
+
Frames = [
|
|
122
|
+
new NeoAnimationSegmentFrame<NeoCollider?>(value: new NeoCollider() {
|
|
123
|
+
Size = new(1, 1),
|
|
124
|
+
Offset = new(1, 1),
|
|
125
|
+
IsTrigger = true
|
|
126
|
+
}) { Index = 1 },
|
|
127
|
+
new NeoAnimationSegmentFrame<NeoCollider?>(value: null) { Index = 2 }
|
|
128
|
+
]
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
On `SwordSprite`: `@storage(allowed: .Session) public override NeoCollider? Collider;`
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Grids and cell patterns
|
|
2
|
+
|
|
3
|
+
## NeoCellPattern
|
|
4
|
+
|
|
5
|
+
An immutable ordered list of `Vector2Int` offsets. The constructor copies its
|
|
6
|
+
input and keeps duplicates.
|
|
7
|
+
|
|
8
|
+
```neo
|
|
9
|
+
NeoCellPattern neighbors = NeoCellPattern.Box(1, excluding: .Center);
|
|
10
|
+
NeoCellPattern custom = new(offsets: [new(-1, 0), new(2, 1)]);
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
- Shapes: `Box`, `Cross`, `Ring`, `Rect`, `Line`. Presets: `Center`,
|
|
14
|
+
`FourNeighbors`, `EightNeighbors`, `Empty`.
|
|
15
|
+
- Box, Cross, and Line include the origin unless `excluding: .Center`.
|
|
16
|
+
Line's `length` counts steps beyond the origin, so `Line(new(1, 0), 3)` has
|
|
17
|
+
four cells.
|
|
18
|
+
- `WithCenter`, `WithoutCenter`, `Translate`, and `Union` return patterns;
|
|
19
|
+
`Union` drops duplicates, keeping first occurrences.
|
|
20
|
+
- `Cells(origin)` returns absolute cells in pattern order.
|
|
21
|
+
- Presets are stored statics, so a field that owns a pattern can't default to
|
|
22
|
+
one (`= NeoCellPattern.EightNeighbors` is refused). Clone it or reference it:
|
|
23
|
+
|
|
24
|
+
```neo
|
|
25
|
+
class Sprinkler : NeoObject {
|
|
26
|
+
public NeoCellPattern Reach = NeoCellPattern.EightNeighbors.Clone();
|
|
27
|
+
public Reference<NeoCellPattern> Splash = NeoCellPattern.Center;
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
In C#, `NeoCellPattern` implements `INeoCellPattern`
|
|
32
|
+
(`IReadOnlyList<Vector2Int>`), which SDK grid helpers accept. The exclusion
|
|
33
|
+
argument is `NeoCellPatternExcluding.None` or `.Center`.
|
|
34
|
+
|
|
35
|
+
## Grid queries
|
|
36
|
+
|
|
37
|
+
On a placed `NeoObject`:
|
|
38
|
+
|
|
39
|
+
- `this.Cell` is its grid cell.
|
|
40
|
+
- `this.GetObjects<Plant>(pattern)` returns placed objects in the same grid
|
|
41
|
+
that are `Plant` or a subclass.
|
|
42
|
+
- `this.GetTile(pattern)` returns the first tile in pattern order, or null.
|
|
43
|
+
- `this.GetTiles(pattern)` returns the tile at each occupied cell in pattern
|
|
44
|
+
order. `GetTiles<DirtDryTile>(pattern)` filters each cell's top tile and
|
|
45
|
+
skips cells whose top tile differs, even with a dry tile underneath.
|
|
46
|
+
- Each query takes an optional `offset` that shifts the whole pattern:
|
|
47
|
+
`this.GetTiles(neighbors, new(0, 2))`.
|
|
48
|
+
- Queries track empty cells too, so getters update when a neighbor appears.
|
|
49
|
+
Calling them on an unplaced object is an error.
|
|
50
|
+
|
|
51
|
+
`this.GetArea()` works on any `NeoObjectBase` or `NeoTile` and returns the
|
|
52
|
+
nearest owning `NeoTileGrid`, or null. Sprite children, objects held in
|
|
53
|
+
another object's field, and tiles in nested layer links answer with their
|
|
54
|
+
placed root's grid. `this.GetArea<FarmGrid>()` is null in any other grid. It
|
|
55
|
+
sees spawns, moves, and removals made earlier in the same evaluation.
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# Object variants
|
|
2
|
+
|
|
3
|
+
A variant is a named configuration of one `NeoObject`-derived class: how to
|
|
4
|
+
construct a fresh instance and how to move an existing one into the
|
|
5
|
+
configuration. Variants are records parented to their class, not values in
|
|
6
|
+
it.
|
|
7
|
+
|
|
8
|
+
```neo
|
|
9
|
+
// Variants/ExampleObject.neo
|
|
10
|
+
@id("5f0c2b9e-7d41-4a58-9b1a-3f6f0e2d8c11")
|
|
11
|
+
NeoVariant<ExampleObject> Down = new(
|
|
12
|
+
initialize: () => { return new ExampleObject(.Down); },
|
|
13
|
+
apply: (source) => { source.FacingDir = .Down; },
|
|
14
|
+
overrides: new { Name = "Facing down" },
|
|
15
|
+
);
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
- `initialize` is required, takes no parameters, and returns the class.
|
|
19
|
+
- `apply` takes one parameter of the class and is void. Omit it for a
|
|
20
|
+
construct-only variant.
|
|
21
|
+
- Both are ordinary closures with block or expression bodies:
|
|
22
|
+
`() => new ExampleObject(.Down)`, `(source) => source.FacingDir = .Down`.
|
|
23
|
+
Each has its own scope. The server compiles them.
|
|
24
|
+
- `overrides` and child overrides' `overrides` are `Partial<T>` deltas,
|
|
25
|
+
written `new { ... }` or `new Partial<T> { ... }`. They never pass
|
|
26
|
+
constructor arguments, even when `T` has a required constructor.
|
|
27
|
+
|
|
28
|
+
A nested class partial changes only named members. A list override replaces
|
|
29
|
+
the whole list; it does not patch entries by index. In particular, overriding
|
|
30
|
+
a clip's `Frames` requires the complete desired list, not just the changed
|
|
31
|
+
frame. For editing an existing clip, see [frame overrides](animation.md).
|
|
32
|
+
|
|
33
|
+
## Applying
|
|
34
|
+
|
|
35
|
+
- `Variants.X.Initialize()` runs `initialize`, then overrides and child
|
|
36
|
+
overrides.
|
|
37
|
+
- `obj.ToVariant(X)` runs `apply`, then overrides and child overrides, never
|
|
38
|
+
`initialize`.
|
|
39
|
+
- An instance remembers the variant that built it and the one last applied.
|
|
40
|
+
The applied variant's overrides win where they answer, `apply` writes stay
|
|
41
|
+
as instance overrides, and `ToVariant(Base)` removes the applied layer.
|
|
42
|
+
- On an object nested inside another instance's construction, `ToVariant`
|
|
43
|
+
applies in place and records nothing.
|
|
44
|
+
- Selections compare equal when class, variant, and bound lookup row match.
|
|
45
|
+
An unset optional selection differs from `Base`.
|
|
46
|
+
|
|
47
|
+
## Folders
|
|
48
|
+
|
|
49
|
+
```neo
|
|
50
|
+
// Variants/ExampleObject/Inverted.neo
|
|
51
|
+
@id("b7d2e9f0-3c15-4a68-8b24-9e0f1a2c3d44")
|
|
52
|
+
NeoVariantFolder<ExampleObject> Inverted = new("Inverted");
|
|
53
|
+
|
|
54
|
+
@id("e3b9c0d1-4a26-4f81-92e7-6b5a4c3d2e66")
|
|
55
|
+
@settings(folder: Inverted)
|
|
56
|
+
NeoVariant<ExampleObject> Down = new(
|
|
57
|
+
initialize: () => { return new ExampleObject(.Down); },
|
|
58
|
+
);
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Nest with `/` in the path; intermediate segments are implicit
|
|
62
|
+
(`new("Trees/Oak")` needs no `Trees` record).
|
|
63
|
+
|
|
64
|
+
Rules push and the language service enforce:
|
|
65
|
+
|
|
66
|
+
- A variant or folder belongs to its type argument's class; a variant may use
|
|
67
|
+
only its own class's folders.
|
|
68
|
+
- Variant names and folder segments share one static scope per folder and
|
|
69
|
+
are unique case-insensitively. Different classes may reuse names.
|
|
70
|
+
- `Base` is reserved case-insensitively; `<Class>.Variants.Base` is the class
|
|
71
|
+
with no variant.
|
|
72
|
+
- Names must survive C# identifier normalization.
|
|
73
|
+
|
|
74
|
+
Push accepts variants in any `.neo` file. Pull writes
|
|
75
|
+
`Variants/<Class>.neo` and `Variants/<Class>/<Folder path>.neo`, one file per
|
|
76
|
+
class and folder, folder record first and variants by name.
|
|
77
|
+
|
|
78
|
+
## Lookup folders
|
|
79
|
+
|
|
80
|
+
A lookup folder binds one concrete list and offers the same variants for each
|
|
81
|
+
row. The second type argument is the entry class. An empty path binds the
|
|
82
|
+
class root.
|
|
83
|
+
|
|
84
|
+
```neo
|
|
85
|
+
@id("18b93b80-5733-4747-bf5f-3609336e4f17")
|
|
86
|
+
NeoLookupVariantFolder<ExampleObject, NeoSortingLayer> Root = new(
|
|
87
|
+
path: "",
|
|
88
|
+
collection: Assets.SortingLayers,
|
|
89
|
+
collectionValue: Reference(root.Assets.SortingLayers),
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
@id("736dddf8-2881-47e1-a1d3-55fb65ea9f2a")
|
|
93
|
+
@settings(folder: Root)
|
|
94
|
+
NeoLookupVariant<ExampleObject, NeoSortingLayer> Layered = new(
|
|
95
|
+
initialize: (value) => { return new ExampleObject(); },
|
|
96
|
+
);
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
- `collection` names the eligible list member; `collectionValue` the stored
|
|
100
|
+
list whose rows it enumerates. Prefer the `Reference(root...)` form pull
|
|
101
|
+
emits.
|
|
102
|
+
- Lookup `initialize` receives the row; lookup `apply` receives the object,
|
|
103
|
+
then the row.
|
|
104
|
+
- A `NeoLookupVariant<TObject, TValue>` member holds an unbound handle. A
|
|
105
|
+
plain `NeoVariant<TObject>` member may pick one row with
|
|
106
|
+
`.Bind(Reference(...))`; never bind a row to the lookup-handle type.
|
|
107
|
+
|
|
108
|
+
## Type restrictions
|
|
109
|
+
|
|
110
|
+
`NeoVariantFolder` and `NeoLookupVariantFolder` are top-level records, never
|
|
111
|
+
member or parameter types. Variant handles are valid as Variant members only,
|
|
112
|
+
not as collection entries, lookup targets, parameters, or generic bindings.
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# World objects
|
|
2
|
+
|
|
3
|
+
Objects, tiles, sorting, and structured leaf values. Open a leaf from
|
|
4
|
+
[References](#references) for grid queries, variants, or animation.
|
|
5
|
+
|
|
6
|
+
## Contracts
|
|
7
|
+
|
|
8
|
+
Extend visible system world classes with ordinary project classes and
|
|
9
|
+
overrides. Never copy `@system` into project source. A layer link is a
|
|
10
|
+
concrete project subclass of `NeoTileLayerLink` or `NeoObjectLayerLink` that
|
|
11
|
+
resolves exactly one `targetLayer` relation; add no value-level target
|
|
12
|
+
metadata and don't ask painting operations to create or repair relation
|
|
13
|
+
targets.
|
|
14
|
+
|
|
15
|
+
## Tiles
|
|
16
|
+
|
|
17
|
+
A placement is a concrete `NeoTile` value with a `Cell`, listed directly in a
|
|
18
|
+
layer link's `Tiles`:
|
|
19
|
+
|
|
20
|
+
```neo
|
|
21
|
+
new GlassFloorTile { Cell = new(9, -4) }
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
- `Name`, `Sprite`, and `SmartTile` are class-level `readonly` members; tiles
|
|
25
|
+
render per class, so override them on a subclass. Push rejects placements
|
|
26
|
+
that set them. `Cell` is the writable field.
|
|
27
|
+
- `tile.TryConvert<GlassFloorTile>()` or `tile.TryConvert(nextTile)` changes a
|
|
28
|
+
writable placement's class, keeping its row and cell. The second form uses
|
|
29
|
+
the value's runtime class without copying fields. Both return `false` when
|
|
30
|
+
placement validation (grid imports, compatible-layer relations) rejects it.
|
|
31
|
+
- `Tiles.Add` and `Tiles.Remove` edit placement data directly; the SDK
|
|
32
|
+
validates and re-renders from the data changes. Flattened SDK layers also
|
|
33
|
+
have `TrySetTile<T>(cell)`.
|
|
34
|
+
|
|
35
|
+
## Objects
|
|
36
|
+
|
|
37
|
+
- `PlacementTiles` is a `List<NeoPlacementTile>`, each marking a footprint
|
|
38
|
+
cell with its own `Cell`. It is not `NeoTile`; rendered tile classes don't
|
|
39
|
+
belong there. An object with no placement tiles occupies no cells, blocks
|
|
40
|
+
nothing, and isn't returned by cell queries, but still has a `Cell`.
|
|
41
|
+
- Spawn with `TrySpawn(cell, constructedObject)`; adoption keeps the object's
|
|
42
|
+
identity. Clone an owned object explicitly before placing another copy.
|
|
43
|
+
- `NeoSpriteObject` extends `NeoObject`. Place it like any object, give it
|
|
44
|
+
`Children`, and override `OnObjectSpawned`/`OnObjectDespawned`. Spawn hooks
|
|
45
|
+
run parent first, despawn children first. `NeoObjectBehaviour.IsRoot` is
|
|
46
|
+
true only on the placed root.
|
|
47
|
+
- Painting a different class replaces the placement with a new row. To change
|
|
48
|
+
configuration in place, use [variants](variants.md).
|
|
49
|
+
- Authored placements may carry variant and source metadata beside their
|
|
50
|
+
schema keys. Keep it when editing and never declare it as schema fields.
|
|
51
|
+
Tile placements carry only their class and `Cell`.
|
|
52
|
+
|
|
53
|
+
## Sorting and optional children
|
|
54
|
+
|
|
55
|
+
```neo
|
|
56
|
+
class Tree : NeoObject {
|
|
57
|
+
public virtual NeoSortingGroup? SortingGroup = null;
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
- A non-null `NeoSortingGroup` makes the object and descendants sort as one
|
|
62
|
+
unit. `SortAtRoot` hoists a nested group to the scene root. `SortPoint` is
|
|
63
|
+
the Y-sort point in cells from the origin cell's corner (default
|
|
64
|
+
`(0.5, 0)`, bottom center); moving it never moves art. Set it per class,
|
|
65
|
+
variant, or frame.
|
|
66
|
+
- Order: sorting layer, order in layer, sort-point Y (lower draws in front),
|
|
67
|
+
then document order. `NeoSpriteObject.SortingOrder` is an offset added to
|
|
68
|
+
the layer order, not a replacement. Sprites also expose `FlipX`, `FlipY`,
|
|
69
|
+
and `MaskInteraction`.
|
|
70
|
+
- `NeoObjectBase.Enabled = false` hides an object and its subtree without
|
|
71
|
+
removing its `Children` row: no rendering or collision, but values and
|
|
72
|
+
animation keep running. Runtime structural children aren't supported, so
|
|
73
|
+
author known optional slots and toggle `Enabled`.
|
|
74
|
+
|
|
75
|
+
## Structured leaves
|
|
76
|
+
|
|
77
|
+
Addressable fields: `SpriteInfo.FileId` and `SliceIndex` (PascalCase), vector
|
|
78
|
+
components `x`, `y`, `z`, `w` where the type has them, and Color `r`, `g`,
|
|
79
|
+
`b`, `a` (lowercase).
|
|
80
|
+
|
|
81
|
+
```neo
|
|
82
|
+
Sprite = Images.Hero.Slice(0);
|
|
83
|
+
Placeholder = SpriteInfo.Empty;
|
|
84
|
+
Position = new(0, 0, 0);
|
|
85
|
+
Tint = Color(1, 1, 1, 1);
|
|
86
|
+
|
|
87
|
+
Leg.Sprite.SliceIndex = 2; // in NeoScript, when storage permits
|
|
88
|
+
Leg.Position.y = 0.25;
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
- Use `.Slice(index)` for a complete `SpriteInfo`. `SpriteInfo.Empty` is the
|
|
92
|
+
canonical non-null empty sprite (no file, slice zero); use `null` only for
|
|
93
|
+
`SpriteInfo?`.
|
|
94
|
+
- Field writes are read-modify-write of the whole leaf. Unknown fields,
|
|
95
|
+
missing vector components, non-integer values on int vectors, and color
|
|
96
|
+
channels outside [0, 1] are rejected.
|
|
97
|
+
|
|
98
|
+
## References
|
|
99
|
+
|
|
100
|
+
- [Grids and cell patterns](grids-and-cell-patterns.md): `NeoCellPattern`,
|
|
101
|
+
`GetObjects`, `GetTile(s)`, `GetArea`.
|
|
102
|
+
- [Variants](variants.md): `NeoVariant`, folders, lookup variants,
|
|
103
|
+
`Initialize`/`ToVariant`.
|
|
104
|
+
- [Animation](animation.md): partial overrides, child selectors, segments,
|
|
105
|
+
tracks, animated colliders.
|