월드 로직 type 추가
SDK 1.2 부터 플러그인이 월드 로직에 새 트리거 · 조건 · 동작을 더할 수 있습니다. host.AddSceneEvent 로 등록한 type 은 내장 type 과 똑같이 다음 자리에 나옵니다.
| 나오는 곳 | 보이는 것 |
|---|---|
| 월드 로직 폼 | 트리거 · 조건 · 동작 메뉴의 Category 묶음, 칸 입력 |
| 로직 그래프 | 노드와 핀 |
| 검사 | 필수 칸 · 액터 kind · 수 범위 · enum 값 |
| 규칙 한 줄 설명 | Summary 틀 |
| 플레이 미리보기 | Simulate 처리 · 「발동」 단추 |
| AI 연결(MCP) | scene_rules section=schema 목록에 [<플러그인 id>] 표시 |
에디터 등록은 폼 · 검사 · MCP · 미리보기용입니다. 게임 서버에서 실제로 돌리려면 서버 플러그인 NP Scene 쪽에도 같은 type 을 등록해야 합니다. 아래 「서버 쪽」을 봅니다.
plugin.json 에 "sdk": "^1.2" 이상을 씁니다. 칸 종류 ActorList 를 쓰면 "^1.3" 입니다.
빠른 예
섹션 제목: “빠른 예”host.AddSceneEvent(new NpSceneEvent(NpSceneEventRole.Trigger, "myquest_npc_talk", "NPC 에게 말 걸 때", "퀘스트", new[] { new NpSceneField("npc", NpSceneFieldKind.Actor, Required: true, ActorKinds: new[] { "display", "text", "spawn" }, Label: "NPC"), new NpSceneField("line", NpSceneFieldKind.Text, Label: "대사 id"), }, Help: "플레이어가 NPC 와 대화를 시작할 때", Icon: "interact", Summary: "플레이어가 NPC {npc} 에게 말을 걸면")); // Simulate 없음 = 「발동」 단추로만
host.AddSceneEvent(new NpSceneEvent(NpSceneEventRole.Action, "myquest_start", "퀘스트 시작", "퀘스트", new[] { new NpSceneField("quest", NpSceneFieldKind.Text, Required: true, Label: "퀘스트 id"), }, Icon: "star", Summary: "퀘스트 {quest} 시작", Simulate: s => { s.Log($"퀘스트 {s.Str("quest")} 시작 → {s.Player}"); return true; }));이 type 을 쓴 규칙은 scene.json 에 이렇게 저장됩니다(규칙 일부).
{ "trigger": { "type": "myquest_npc_talk", "npc": "npc_guard" }, "actions": [ { "type": "myquest_start", "quest": "q1" } ]}NpSceneEvent
섹션 제목: “NpSceneEvent”public sealed record NpSceneEvent(NpSceneEventRole Role, string Type, string Label, string Category, IReadOnlyList<NpSceneField> Fields, string Help = "", string Icon = "event", string Summary = "", Func<NpSceneSim, bool>? Simulate = null);public enum NpSceneEventRole { Trigger, Condition, Action }| 칸 | 필수 | 설명 |
|---|---|---|
Role | 예 | 역할: Trigger(트리거) · Condition(조건) · Action(동작) |
Type | 예 | 저장 이름(JSON "type"). 내 이름을 앞에 붙입니다(아래 「이름 규칙」). |
Label | 예 | 폼 · 그래프에 보일 이름 |
Category | 아니요 | 메뉴 묶음 이름. 비우면 플러그인 이름(plugin.json 의 name) |
Fields | 예 | 칸 목록(NpSceneField). 칸이 없으면 빈 배열 |
Help | 아니요 | 설명(툴팁) |
Icon | 아니요 | 아이콘 이름(trigger · event · signal · interact · logic · star …). 기본 event, 모르는 이름이면 기본 그림 |
Summary | 아니요 | 한 줄 설명 틀(아래 「Summary 틀」). 비우면 Label |
Simulate | 아니요 | 플레이 미리보기 처리(아래 「Simulate」) |
플러그인 type 은 같은 묶음 안에서 내장 type 뒤에 나옵니다.
NpSceneField
섹션 제목: “NpSceneField”public sealed record NpSceneField(string Name, NpSceneFieldKind Kind, bool Required = false, string Default = "", string[]? Options = null, string[]? ActorKinds = null, string Label = "", string Help = "", double? Min = null, double? Max = null);| 칸 | 설명 |
|---|---|
Name | JSON 키 |
Kind | 칸 종류(아래 표) |
Required | 필수면 비었을 때 검사 오류 |
Default | 기본값 글. 형식은 아래 표의 「Default 쓰는 법」 |
Options | Enum 선택지 |
ActorKinds | Actor 칸이 받는 액터 kind(trigger · blocker · display · text · spawn · camera · path …). 비우면 아무 kind |
Label · Help | 폼에 보일 이름 · 설명 |
Min · Max | Number 범위(검사) |
칸 종류 NpSceneFieldKind
섹션 제목: “칸 종류 NpSceneFieldKind”| 종류 | 폼 입력 | JSON 값 | Default 쓰는 법 | SDK |
|---|---|---|---|---|
Text | 글 | 글 | "안녕" | 1.2 |
Number | 수 | 수 | "1.5" | 1.2 |
Bool | 체크 | true / false | "true" | 1.2 |
Enum | 선택지 중 하나(Options) | 글 | "none" | 1.2 |
Actor | 액터 고르기(ActorKinds 만) | 액터 id 글 | 액터 id | 1.2 |
Anim | 애니메이션 클립 | 클립 id 글 | 클립 id | 1.2 |
SoundEvent | 소리 이벤트 | "ns:path" 글 | "minecraft:block.note_block.bell" | 1.2 |
Block | 블록 상태 | 글(minecraft:stone) | 블록 상태 글 | 1.2 |
Item | 아이템 | 글(minecraft:diamond_sword) | 아이템 id | 1.2 |
Particle | 파티클 | 글(minecraft:happy_villager) | 파티클 id | 1.2 |
Effect | 포션 효과 | 글(minecraft:speed) | 효과 id | 1.2 |
Vec3 | x · y · z | [x, y, z] | "[0,1,0]" | 1.2 |
Signal | 신호 이름 | 글 | 신호 이름 | 1.2 |
Flag | 씬 플래그 이름 | 글 | 플래그 이름 | 1.2 |
ActorList | 액터 여러 개 | 액터 id 배열 | "cam_a,cam_b" | 1.3 |
내장 type 만 쓰는 칸 종류가 더 있습니다: 플래그 값 · 함수(로직 함수 부르기) · 블록 칸 목록(
Cells). 이 종류는 아직 SDK 에 없습니다. 칸 보이기 조건 · 정수만 ·#태그허용 같은 칸 옵션도 아직 SDK 에 없습니다.
로직 그래프에서
섹션 제목: “로직 그래프에서”플러그인 type 도 로직 그래프에 노드로 나옵니다. 핀 규칙은 내장 type 과 같습니다.
| 핀 | 이을 수 있는 수 |
|---|---|
| 실행 핀(흰 화살표) | 한 사슬. 노드 하나 뒤에 노드 하나 |
| 함수 핀(노란 마름모) | 핀 하나에 선 하나. 이미 이은 핀에 다시 이으면 바뀝니다. |
| 데이터 핀(보라 동그라미, 액터 참조) | 액터 하나를 여러 칸에 이을 수 있습니다. |
Actor칸은 데이터 핀을 받습니다. 그래프에서 액터를 이으면ActorKinds를 검사합니다.- 함수 핀은 내장 「함수 부르기」 류에만 있습니다. 플러그인 type 은 함수 핀을 만들 수 없습니다.
0.9.11 부터: 함수를 부르는 노란 핀은 핀마다 선 하나입니다.
Summary 틀
섹션 제목: “Summary 틀”규칙 목록의 한 줄 설명에 쓰는 틀입니다. {칸 이름} 자리에 그 칸 값이 들어갑니다.
| 틀 | 칸 값 | 보이는 글 |
|---|---|---|
퀘스트 {quest} 시작 | quest = q1 | 퀘스트 q1 시작 |
플레이어가 NPC {npc} 에게 말을 걸면 | npc = npc_guard(이름 「경비병」) | 플레이어가 NPC 「경비병」 에게 말을 걸면 |
퀘스트 {quest} 가 {state} 일 때 | state 비어 있음, Default = none | 퀘스트 q1 가 none 일 때 |
Actor칸은 액터 id 대신 「액터 이름」으로 바뀝니다.- 값이 없는 칸은
Default로 채웁니다.
Simulate — 플레이 미리보기
섹션 제목: “Simulate — 플레이 미리보기”Simulate 는 플레이 · 시뮬레이트 중에 규칙을 미리 돌리는 함수입니다. 게임 서버에서는 쓰지 않습니다.
| 역할 | Simulate 가 하는 일 | 반환값 | 없으면 |
|---|---|---|---|
| Trigger | 미리보기 한 틱마다 그 type 규칙마다 한 번 불림. 일으키려면 s.Fire(player) | 무시 | 월드 로직 · 플레이 패널의 「발동」 단추로만 일으킴 |
| Condition | 참 · 거짓 판정 | true = 참 | 늘 참으로 봄 |
| Action | 동작 실행(기록 · 내 상태 바꾸기) | 무시 | 기록만 남김 |
Simulate 에서 예외가 나면 그 플러그인이 격리됩니다. 그 플러그인의 type 은 모두 빠지고, 미리보기는 계속 돕니다.
NpSceneSim
섹션 제목: “NpSceneSim”Simulate 가 받는 값입니다.
| 멤버 | 형식 | 설명 |
|---|---|---|
Role · Type | NpSceneEventRole · string | 지금 처리하는 역할 · type |
RuleId | string | 규칙 id |
Player | string? | 트리거한 사람. 트리거 처리에서는 null |
Players | IReadOnlyList<string> | 지금 플레이어들 |
Now | double | 미리보기 시각(초) |
Args | IReadOnlyDictionary<string, string> | 노드 칸 값(모두 글) |
Str(string key, string fallback = "") | string | 칸 값 글 |
Num(string key, double fallback = 0) | double | 칸 값 수 |
Bool(string key, bool fallback = false) | bool | 칸 값 "true" / "false" |
PositionOf(string player) | NpVec3? | 플레이어 발 위치. 없으면 null |
Log(string text) | void | 미리보기 기록 한 줄([<id>] …) |
Fire(string? player) | void | (Trigger 처리만) 이 규칙을 일으킴. 다른 역할에서는 아무 일 없음 |
Args 값의 글 꼴입니다.
| 칸 값 | Args 글 |
|---|---|
| 글 | 그대로 |
| 수 | "1.5" (점 소수, 문화권 무관) |
| 참 · 거짓 | "true" / "false" |
배열 · 객체(Vec3 · ActorList) | JSON 글("[0,1,0]"). ActorList 는 쉼표 글로 저장된 것도 그대로 옵니다. |
예: 스스로 일어나는 트리거
섹션 제목: “예: 스스로 일어나는 트리거”host.AddSceneEvent(new NpSceneEvent(NpSceneEventRole.Trigger, "myquest_near_spawn", "스폰 가까이 올 때", "퀘스트", new[] { new NpSceneField("r", NpSceneFieldKind.Number, Default: "5", Label: "반경", Min: 1, Max: 64), }, Summary: "스폰 {r}칸 안에 들어오면", Simulate: s => { foreach (var p in s.Players) if (s.PositionOf(p) is { } pos && Math.Abs(pos.X) + Math.Abs(pos.Z) < s.Num("r", 5)) s.Fire(p); return true; }));- 이 함수는 미리보기 틱마다 불립니다. 가볍게 씁니다.
- 거르기(한 번만 · 쿨다운 등)와 조건은
Fire뒤에 에디터가 봅니다.
예: 상태를 기억하는 조건 · 동작
섹션 제목: “예: 상태를 기억하는 조건 · 동작”private readonly Dictionary<string, string> _quests = new(); // 플레이어:퀘스트 → 상태
// 조건: 퀘스트 상태Simulate: s => Get(s.Player, s.Str("quest")) == s.Str("state", "none")
// 동작: 퀘스트 시작Simulate: s => { _quests[(s.Player ?? "") + ":" + s.Str("quest")] = "active"; s.Log("시작"); return true; }
// 새 월드에서는 비움host.WorldChanged += _ => _quests.Clear();전체 코드는 예제: event_example에 있습니다.
이름 규칙
섹션 제목: “이름 규칙”| 규칙 | 이유 |
|---|---|
Type 앞에 내 이름을 붙입니다(myquest_start). | 같은 역할에 같은 type 이 이미 있으면(내장 포함) 등록 예외 → 그 플러그인 격리 |
소문자 · 숫자 · _ 로 씁니다. | 서버 플러그인 · JSON 과 맞추기 쉽습니다. |
한 번 쓴 Type 은 바꾸지 않습니다. | 저장된 규칙이 그 이름을 씁니다. |
내장 type 이름(cinematic · enter · sound …)을 쓰지 않습니다. | 내장과 겹치면 플러그인이 켜지지 않습니다. 내장 목록은 월드 로직 type 목록에 있습니다. |
플러그인을 끄면
섹션 제목: “플러그인을 끄면”- 그 플러그인의 type 이 메뉴 · 그래프 팔레트에서 빠집니다.
- 그 type 을 쓰는 규칙은 지워지지 않습니다. 「모르는 type」 주의가 되고 칸 값은 그대로 남습니다.
- 미리보기에서 모르는 트리거는 그 규칙을 건너뛰고, 모르는 조건은 거짓, 모르는 동작은 그 동작만 건너뜁니다.
서버 쪽
섹션 제목: “서버 쪽”게임에서 규칙을 실행하는 것은 서버 플러그인 NP Scene(Paper)입니다. 에디터 플러그인은 서버에서 돌지 않습니다.
- 같은 type 의 처리기를 Paper 플러그인으로 만듭니다.
- Bukkit
ServicesManager에서 NP Scene 의SceneEventsApi를 받아 그 type 을 등록합니다. - 칸 이름과 값 형식(JSON)을 에디터 쪽
NpSceneField와 똑같이 맞춥니다.
서버가 모르는 type 은 경고 한 번과 함께 건너뜁니다(트리거 = 규칙 건너뜀, 조건 = 거짓, 동작 = 그 동작만 건너뜀). 그래서 서버 처리기가 없어도 맵은 열리지만 그 규칙은 돌지 않습니다.