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회 등록·로비 가드)에서 처리 — 여기 두지 않음. // 정찰: 이동에는 RigidbodyComponent.WalkAcceleration(가속)과 MovementComponent.InputSpeed(속도)가 // 둘 다 양수여야 함 — freeze-turn-player가 둘 다 0으로 만들었으므로 둘 다 복원해야 걷는다(실측 확인). // 값은 freeze가 건드리지 않은 intact RigidbodyComponent.WalkSpeed(1.4)/WalkJump(1.23) = 기본값에 맞춤. // (실측: InputSpeed 1.4→보행 ~2.5u/s, JumpForce 1.23→점프 상승 1.79u. 이전 5/5는 ~9u/s·상승 14u로 과함.) const WALK_ACCEL = 0.7; const WALK_SPEED = 1.4; const JUMP_FORCE = 1.23; 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 = 0 local rb = lp.RigidbodyComponent if rb ~= nil then rb.WalkAcceleration = ${WALK_ACCEL} end local mv = lp.MovementComponent if mv ~= nil then mv.InputSpeed = ${WALK_SPEED} mv.JumpForce = ${JUMP_FORCE} 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 생성 완료');