@neocompose/cli 0.87.2 → 0.89.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 +23 -0
- package/README.md +14 -0
- package/dist/neo-runtime.mjs +11153 -3060
- package/dist/test-worker.mjs +15214 -9707
- 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/extensions.md +46 -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 +138 -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 +113 -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 +63 -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,151 @@
|
|
|
1
|
+
# References
|
|
2
|
+
|
|
3
|
+
`Reference<T>` names one stored row. `T` may be any value type, collections
|
|
4
|
+
and other references included; code types such as `Action<int>` are rejected.
|
|
5
|
+
|
|
6
|
+
## Handles and slots
|
|
7
|
+
|
|
8
|
+
- A handle has a class, interface, List, Set, or Dictionary `T`. The
|
|
9
|
+
reference is the object: `Reference<Item>` reads as `Item`, and assigning an
|
|
10
|
+
`Item` rebinds it. A handle never owns, so it can't hold a new list or a
|
|
11
|
+
query result.
|
|
12
|
+
- A slot has any other `T` (`bool`, numbers, `string`, enums, vectors, `Color`,
|
|
13
|
+
sprites, audio clips, `NeoVariant<T>`, `NeoDialogueReference`, or one
|
|
14
|
+
`Reference<U>`). It names another member and reads and writes that member's
|
|
15
|
+
value.
|
|
16
|
+
|
|
17
|
+
Inside a generic body, `Reference<T>` takes the category of `T`'s binding.
|
|
18
|
+
|
|
19
|
+
## Declaring lookups
|
|
20
|
+
|
|
21
|
+
```neo
|
|
22
|
+
public Reference<Config> Config; // one row, reads as Config
|
|
23
|
+
public Set<Reference<Config>> Configs; // several, reads as Set<Config>
|
|
24
|
+
public Reference<int> TrackedStat; // another member's int
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
- `Reference<T>` also works as a parameter, collection element, dictionary
|
|
28
|
+
value, and unbound generic argument (`Box<Reference<int>>`), and keeps that
|
|
29
|
+
storage type through push and pull. Use `Set<T>` parameters for several
|
|
30
|
+
selections.
|
|
31
|
+
- `@settings(collection: Owner.Member)` binds a class reference to one
|
|
32
|
+
collection. Unbound references select any stored row of `T` or a subclass,
|
|
33
|
+
or any member slot of type `T`. A bound reference can't be a generic
|
|
34
|
+
argument. Settings on `T` itself are an error; the language server's quick
|
|
35
|
+
fix respells them.
|
|
36
|
+
- The lookup class must be assignable to the collection's entry class; a
|
|
37
|
+
subclass may narrow it. An `NPC` lookup can't select from `List<Quest>`.
|
|
38
|
+
- A class-constrained generic lookup (`Reference<TAsset>` with
|
|
39
|
+
`TAsset extends HeldItemAsset`) may bind a collection of the bound class.
|
|
40
|
+
Overrides that specialize the target keep the collection setting.
|
|
41
|
+
- A single reference's initializer body returns one row, never a set.
|
|
42
|
+
- `Reference<T>` satisfies interface `T Foo { get; }` or `{ get; set; }` and
|
|
43
|
+
abstract `T Foo { get; }`.
|
|
44
|
+
- Generated C# exposes a read-only interface; call `TryWritable` to write.
|
|
45
|
+
|
|
46
|
+
## Defaults
|
|
47
|
+
|
|
48
|
+
```neo
|
|
49
|
+
public Reference<Item> StarterSeed = root.Assets.Items.Index.Slug["seed.carrot"]!;
|
|
50
|
+
public Reference<int> Gold = Reference(root.Player.Gold);
|
|
51
|
+
public Reference<string> Greeting = Reference(root.Npcs.Index.Slug["mira"]!.Greeting);
|
|
52
|
+
public Reference<NeoVariant<Tree>> Look = Reference(root.Garden.Felled);
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
- An unbound handle default may name its row by a unique index: a `root`
|
|
56
|
+
path to a List, `.Index.<Name>`, and a string literal key. Other shapes are
|
|
57
|
+
initializer code.
|
|
58
|
+
- Slot defaults wrap a member path in `Reference()`. A plain value is not a
|
|
59
|
+
default, and `this` doesn't exist yet. A `NeoVariant<T>` member's own
|
|
60
|
+
default stays the bare variant path.
|
|
61
|
+
- List and dictionary defaults may hold slot references, nested too:
|
|
62
|
+
`List<Reference<int>> Counts = [Reference(root.Save.Bits)]`. Each instance
|
|
63
|
+
owns the collection; entries target the original slots.
|
|
64
|
+
|
|
65
|
+
## Source forms
|
|
66
|
+
|
|
67
|
+
```neo
|
|
68
|
+
Reference(Assets.Capitol)
|
|
69
|
+
Reference(root.Player.Gold)
|
|
70
|
+
Reference<Outpost>(id: "capitol-value-id")
|
|
71
|
+
Reference<NeoSpriteObject>(id: "authored-child-id", withProvenance: true)
|
|
72
|
+
Reference<PantsAsset>(key: "pants.long")
|
|
73
|
+
Reference<PantsAsset>(key: "pants.long", index: Slug)
|
|
74
|
+
Reference<NeoDialogue>(id: "capitol-dialogue-id")
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
- Prefer a symbol or root path. A slot always names a member path or `id:`.
|
|
78
|
+
- `key:` needs a unique index. Omit `index:` when there is exactly one.
|
|
79
|
+
- Use `id:` when the ID is all you have. Unknown named arguments are errors.
|
|
80
|
+
- `withProvenance: true` (executable NeoScript only, default false) selects
|
|
81
|
+
the nearest `sourceValueId` clone of an authored row in the receiver's
|
|
82
|
+
ownership graph. It is not a member-default form.
|
|
83
|
+
- `key:` and `id:` work in declaration defaults; a key there resolves after
|
|
84
|
+
every pass, so it may name a row the same push creates.
|
|
85
|
+
- `Reference(value)` keeps an evaluated class value's identity; it neither
|
|
86
|
+
clones nor wraps. An optional type argument checks the target.
|
|
87
|
+
- Animation and frame-override targets are selectors, not references; see
|
|
88
|
+
[animation](../world/animation.md#child-selectors).
|
|
89
|
+
|
|
90
|
+
## Runtime semantics
|
|
91
|
+
|
|
92
|
+
`Reference(place)` forms a reference to a stored member: `this.Gold`,
|
|
93
|
+
`root.Player.Gold`, `item.Count`. Locals, parameters, list or dictionary
|
|
94
|
+
elements, vector components, getters, query results, and new values have no
|
|
95
|
+
row. When `Selected` is `Reference<Item>`, `Reference(this.Selected)` is
|
|
96
|
+
`Reference<Reference<Item>>`. Access checks apply where it is formed.
|
|
97
|
+
|
|
98
|
+
```neo
|
|
99
|
+
this.A = 5; // writes 5 into A's target
|
|
100
|
+
this.A = this.Count; // writes Count's value into A's target
|
|
101
|
+
this.A = this.B; // A now names B's target
|
|
102
|
+
this.A = Reference(this.Count); // A now names Count
|
|
103
|
+
this.A += 1; // reads A's target, writes it back
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
- Assigning a same-typed reference rebinds; anything else writes through.
|
|
107
|
+
`this.A = c ? first : second` rebinds when both arms are references, writes
|
|
108
|
+
when both are scalars, and reads the chosen target when mixed.
|
|
109
|
+
- A plain value never becomes a reference: passing `3` to a `Reference<int>`
|
|
110
|
+
parameter is an error.
|
|
111
|
+
- Constructors may initialize their own reference fields but can't write
|
|
112
|
+
through external targets.
|
|
113
|
+
- `==` compares handles by identity and slots by value. Collections of
|
|
114
|
+
references compare entries by target, so `Set<Reference<int>>` holds
|
|
115
|
+
distinct slots. Narrow `Reference<T>?` before writing through it.
|
|
116
|
+
- The target row decides every write: asset-owned targets error, `readonly`
|
|
117
|
+
targets reject writes after initialization, a Save target accepts them.
|
|
118
|
+
- Slot `OnChanged` listeners fire on rebinding and on target changes.
|
|
119
|
+
|
|
120
|
+
## Same-push resolution
|
|
121
|
+
|
|
122
|
+
Declaration and file order don't matter within a push, and cyclic identity
|
|
123
|
+
references are legal. After the push, targets must exist (rows deleted by the
|
|
124
|
+
edit don't count) and satisfy assignability, membership, multiplicity, and
|
|
125
|
+
group constraints.
|
|
126
|
+
|
|
127
|
+
A projection may assign existing class values to lookup storage directly:
|
|
128
|
+
|
|
129
|
+
```neo
|
|
130
|
+
EligibleIngredients = root.Assets.Crafting.FurnaceRecipes
|
|
131
|
+
.Select((MachineRecipe recipe) => recipe.Ingredient)
|
|
132
|
+
.ToSet()
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Lookup assignment still needs an existing backing row.
|
|
136
|
+
|
|
137
|
+
`collectionValue` settings name the collection itself
|
|
138
|
+
(`root.Assets.Quests.Daily` or `Reference(root.Assets.Quests.Daily)`) and
|
|
139
|
+
follow the same rules. The path may cross construction defaults and
|
|
140
|
+
declarations from the same push; no preliminary push or manual ID is needed.
|
|
141
|
+
Invalid reference settings are errors, never silently dropped.
|
|
142
|
+
|
|
143
|
+
## Init-built objects
|
|
144
|
+
|
|
145
|
+
An object `Root.neo` writes is init-built when its class has an `init` block.
|
|
146
|
+
A declaration binding may name a member inside it, and push stores that
|
|
147
|
+
member's row. A reference captured by `init` or a constructor
|
|
148
|
+
(`new Rule(Reference(this.Weather))`) keeps its target's row. Status refuses a
|
|
149
|
+
value row's reference to a member of an init-built object (bind it from a
|
|
150
|
+
declaration instead) and a binding to a class or list that an `init` block
|
|
151
|
+
builds.
|
|
@@ -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.
|