diff --git a/RootDesk/MyDesk/LobbyMobility.codeblock b/RootDesk/MyDesk/LobbyMobility.codeblock new file mode 100644 index 0000000..a17c0b8 --- /dev/null +++ b/RootDesk/MyDesk/LobbyMobility.codeblock @@ -0,0 +1,60 @@ +{ + "Id": "", + "GameId": "", + "EntryKey": "codeblock://lobbymobility", + "ContentType": "x-mod/codeblock", + "Content": "", + "Usage": 0, + "UsePublish": 1, + "UseService": 0, + "CoreVersion": "26.5.0.0", + "StudioVersion": "", + "DynamicLoading": 0, + "ContentProto": { + "Use": "Json", + "Json": { + "CoreVersion": { + "Major": 0, + "Minor": 2 + }, + "ScriptVersion": { + "Major": 1, + "Minor": 0 + }, + "Description": "", + "Id": "LobbyMobility", + "Language": 1, + "Name": "LobbyMobility", + "Type": 1, + "Source": 0, + "Target": null, + "Properties": [ + { + "Type": "number", + "DefaultValue": "0", + "SyncDirection": 0, + "Attributes": [], + "Name": "Tries" + } + ], + "Methods": [ + { + "Return": { + "Type": "void", + "DefaultValue": null, + "SyncDirection": 0, + "Attributes": [], + "Name": null + }, + "Arguments": [], + "Code": "self.Tries = 0\nlocal eventId = 0\nlocal function apply()\n\tself.Tries = self.Tries + 1\n\tlocal lp = _UserService.LocalPlayer\n\tif lp ~= nil and lp.PlayerControllerComponent ~= nil then\n\t\tlocal pc = lp.PlayerControllerComponent\n\t\tpc.Enable = true\n\t\tpc.FixedLookAt = false\n\t\tlocal rb = lp.RigidbodyComponent\n\t\tif rb ~= nil then rb.WalkAcceleration = 0.7 end\n\t\t_TimerService:ClearTimer(eventId)\n\telseif self.Tries > 50 then\n\t\t_TimerService:ClearTimer(eventId)\n\tend\nend\neventId = _TimerService:SetTimerRepeat(apply, 0.1)", + "Scope": 2, + "ExecSpace": 6, + "Attributes": [], + "Name": "OnBeginPlay" + } + ], + "EntityEventHandlers": [] + } + } +} diff --git a/RootDesk/MyDesk/LobbyNpc.codeblock b/RootDesk/MyDesk/LobbyNpc.codeblock new file mode 100644 index 0000000..3349178 --- /dev/null +++ b/RootDesk/MyDesk/LobbyNpc.codeblock @@ -0,0 +1,89 @@ +{ + "Id": "", + "GameId": "", + "EntryKey": "codeblock://lobbynpc", + "ContentType": "x-mod/codeblock", + "Content": "", + "Usage": 0, + "UsePublish": 1, + "UseService": 0, + "CoreVersion": "26.5.0.0", + "StudioVersion": "", + "DynamicLoading": 0, + "ContentProto": { + "Use": "Json", + "Json": { + "CoreVersion": { + "Major": 0, + "Minor": 2 + }, + "ScriptVersion": { + "Major": 1, + "Minor": 0 + }, + "Description": "", + "Id": "LobbyNpc", + "Language": 1, + "Name": "LobbyNpc", + "Type": 1, + "Source": 0, + "Target": null, + "Properties": [ + { + "Type": "string", + "DefaultValue": "\"\"", + "SyncDirection": 0, + "Attributes": [], + "Name": "NpcId" + }, + { + "Type": "string", + "DefaultValue": "\"\"", + "SyncDirection": 0, + "Attributes": [], + "Name": "MarkName" + }, + { + "Type": "boolean", + "DefaultValue": "false", + "SyncDirection": 0, + "Attributes": [], + "Name": "InRange" + } + ], + "Methods": [ + { + "Return": { + "Type": "void", + "DefaultValue": null, + "SyncDirection": 0, + "Attributes": [], + "Name": null + }, + "Arguments": [], + "Code": "self.InRange = false\nlocal mark = _EntityService:GetEntityByPath(\"/maps/lobby/\" .. self.MarkName)\nif mark ~= nil then mark:SetVisible(false) end\nself.Entity:ConnectEvent(TouchEvent, function(e)\n\tself:Interact()\nend)\n_InputService:ConnectEvent(KeyDownEvent, function(e)\n\tif self.InRange and e.key == KeyboardKey.UpArrow then\n\t\tself:Interact()\n\tend\nend)\nlocal eventId = 0\nlocal function tick()\n\tlocal lp = _UserService.LocalPlayer\n\tif lp == nil then return end\n\tif mark == nil then mark = _EntityService:GetEntityByPath(\"/maps/lobby/\" .. self.MarkName) end\n\tlocal a = lp.TransformComponent.WorldPosition\n\tlocal b = self.Entity.TransformComponent.WorldPosition\n\tlocal d = Vector2.Distance(Vector2(a.x, a.y), Vector2(b.x, b.y))\n\tlocal near = d < 1.2\n\tif near ~= self.InRange then\n\t\tself.InRange = near\n\t\tif mark ~= nil then mark:SetVisible(near) end\n\tend\nend\neventId = _TimerService:SetTimerRepeat(tick, 0.15)", + "Scope": 2, + "ExecSpace": 6, + "Attributes": [], + "Name": "OnBeginPlay" + }, + { + "Return": { + "Type": "void", + "DefaultValue": null, + "SyncDirection": 0, + "Attributes": [], + "Name": null + }, + "Arguments": [], + "Code": "local c = _EntityService:GetEntityByPath(\"/common\")\nif c ~= nil and c.SlayDeckController ~= nil then\n\tc.SlayDeckController:OnLobbyNpcInteract(self.NpcId)\nend", + "Scope": 2, + "ExecSpace": 6, + "Attributes": [], + "Name": "Interact" + } + ], + "EntityEventHandlers": [] + } + } +} diff --git a/tools/player/gen-lobby-npc.mjs b/tools/player/gen-lobby-npc.mjs new file mode 100644 index 0000000..2d18e09 --- /dev/null +++ b/tools/player/gen-lobby-npc.mjs @@ -0,0 +1,92 @@ +import { writeFileSync } from 'node:fs'; + +// 로비 codeblock 2종 emit (맵/엔티티 부착은 tools/map/gen-lobby-map.mjs 소관): +// · LobbyNpc — NPC 엔티티에 부착. 근접 폴링→머리위 마크 토글, TouchEvent(클릭)/UpArrow(근접)→Interact→컨트롤러 호출. +// · LobbyMobility — 로비 맵 루트에 부착. 진입 시 플레이어 이동 잠금 해제(정찰 확정: WalkAcceleration이 진짜 레버). +// 공격 키(LeftControl) 바인딩은 SlayDeckController(/common, 1회 등록·로비 가드)에서 처리 — 여기 두지 않음. +const WALK_ACCEL = 0.7; // 정찰: freeze가 0으로 만든 RigidbodyComponent.WalkAcceleration 복원값(이동·점프 정상 확인) +const PROX = 1.2; // 근접 임계(맵 NPC 간격 2.5와 분리) + +function prop(Type, Name, DefaultValue = 'nil') { + return { Type, DefaultValue, SyncDirection: 0, Attributes: [], Name }; +} +function method(Name, Code, Arguments = [], ExecSpace = 6) { + return { + Return: { Type: 'void', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: null }, + Arguments, Code, Scope: 2, ExecSpace, Attributes: [], Name, + }; +} +function writeCodeblock(name, properties, methods) { + const cb = { + Id: '', GameId: '', EntryKey: `codeblock://${name.toLowerCase()}`, ContentType: 'x-mod/codeblock', + Content: '', Usage: 0, UsePublish: 1, UseService: 0, CoreVersion: '26.5.0.0', StudioVersion: '', DynamicLoading: 0, + ContentProto: { Use: 'Json', Json: { + CoreVersion: { Major: 0, Minor: 2 }, ScriptVersion: { Major: 1, Minor: 0 }, + Description: '', Id: name, Language: 1, Name: name, Type: 1, Source: 0, Target: null, + Properties: properties, Methods: methods, EntityEventHandlers: [], + } }, + }; + writeFileSync(`RootDesk/MyDesk/${name}.codeblock`, JSON.stringify(cb, null, 2) + '\n', 'utf8'); +} + +// ── LobbyNpc ────────────────────────────────────────────────────────────── +const npcInteract = method('Interact', `local c = _EntityService:GetEntityByPath("/common") +if c ~= nil and c.SlayDeckController ~= nil then + c.SlayDeckController:OnLobbyNpcInteract(self.NpcId) +end`); + +const npcBegin = method('OnBeginPlay', `self.InRange = false +local mark = _EntityService:GetEntityByPath("/maps/lobby/" .. self.MarkName) +if mark ~= nil then mark:SetVisible(false) end +self.Entity:ConnectEvent(TouchEvent, function(e) + self:Interact() +end) +_InputService:ConnectEvent(KeyDownEvent, function(e) + if self.InRange and e.key == KeyboardKey.UpArrow then + self:Interact() + end +end) +local eventId = 0 +local function tick() + local lp = _UserService.LocalPlayer + if lp == nil then return end + if mark == nil then mark = _EntityService:GetEntityByPath("/maps/lobby/" .. self.MarkName) end + local a = lp.TransformComponent.WorldPosition + local b = self.Entity.TransformComponent.WorldPosition + local d = Vector2.Distance(Vector2(a.x, a.y), Vector2(b.x, b.y)) + local near = d < ${PROX} + if near ~= self.InRange then + self.InRange = near + if mark ~= nil then mark:SetVisible(near) end + end +end +eventId = _TimerService:SetTimerRepeat(tick, 0.15)`); + +writeCodeblock('LobbyNpc', [ + prop('string', 'NpcId', '""'), + prop('string', 'MarkName', '""'), + prop('boolean', 'InRange', 'false'), +], [npcBegin, npcInteract]); + +// ── LobbyMobility ───────────────────────────────────────────────────────── +const mobBegin = method('OnBeginPlay', `self.Tries = 0 +local eventId = 0 +local function apply() + self.Tries = self.Tries + 1 + local lp = _UserService.LocalPlayer + if lp ~= nil and lp.PlayerControllerComponent ~= nil then + local pc = lp.PlayerControllerComponent + pc.Enable = true + pc.FixedLookAt = false + local rb = lp.RigidbodyComponent + if rb ~= nil then rb.WalkAcceleration = ${WALK_ACCEL} end + _TimerService:ClearTimer(eventId) + elseif self.Tries > 50 then + _TimerService:ClearTimer(eventId) + end +end +eventId = _TimerService:SetTimerRepeat(apply, 0.1)`); + +writeCodeblock('LobbyMobility', [prop('number', 'Tries', '0')], [mobBegin]); + +console.log('[gen-lobby-npc] LobbyNpc / LobbyMobility codeblock 생성 완료');