Compare commits
23 Commits
1f0a8099ee
...
1e87be2cd6
| Author | SHA1 | Date | |
|---|---|---|---|
| 1e87be2cd6 | |||
| 6cc008e894 | |||
| 760b856576 | |||
| 91bbe7d200 | |||
| 989e3fe000 | |||
| 0e064cc1e9 | |||
| de1e69de7d | |||
| a683f186d4 | |||
| a309da2a99 | |||
| 82bf22d4cc | |||
| f36bc0d14e | |||
| 8f233296af | |||
| 5f89d61a8b | |||
| 8879647b26 | |||
| 7ee323ea8b | |||
| 7cc311ee91 | |||
| 5cde11647f | |||
| 8296775e21 | |||
| d546d62755 | |||
| 8f58a90746 | |||
| 8fa1079548 | |||
| 9e16465218 | |||
| f67471435e |
@@ -24,12 +24,7 @@
|
||||
"map://map03",
|
||||
"map://map04",
|
||||
"map://map05",
|
||||
"map://map06",
|
||||
"map://map07",
|
||||
"map://map08",
|
||||
"map://map09",
|
||||
"map://map10",
|
||||
"map://map11"
|
||||
"map://lobby"
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
60
RootDesk/MyDesk/LobbyMobility.codeblock
Normal file
60
RootDesk/MyDesk/LobbyMobility.codeblock
Normal file
@@ -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 = 0\n\t\tlocal rb = lp.RigidbodyComponent\n\t\tif rb ~= nil then rb.WalkAcceleration = 0.7 end\n\t\tlocal mv = lp.MovementComponent\n\t\tif mv ~= nil then\n\t\t\tmv.InputSpeed = 1.4\n\t\t\tmv.JumpForce = 1.23\n\t\tend\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": []
|
||||
}
|
||||
}
|
||||
}
|
||||
89
RootDesk/MyDesk/LobbyNpc.codeblock
Normal file
89
RootDesk/MyDesk/LobbyNpc.codeblock
Normal file
@@ -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": []
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -47,7 +47,7 @@
|
||||
"Name": null
|
||||
},
|
||||
"Arguments": [],
|
||||
"Code": "self.LockTries = 0\nlocal eventId = 0\nlocal function apply()\n\tself.LockTries = self.LockTries + 1\n\tlocal pc = nil\n\tlocal lp = _UserService.LocalPlayer\n\tif lp ~= nil then\n\t\tpc = lp.PlayerControllerComponent\n\tend\n\tif pc ~= nil then\n\t\tpc.LookDirectionX = 1\n\t\tpc.FixedLookAt = true\n\t\tpc.Enable = false\n\tend\n\tif pc ~= nil then\n\t\t_TimerService:ClearTimer(eventId)\n\telseif self.LockTries > 30 then\n\t\t_TimerService:ClearTimer(eventId)\n\tend\nend\neventId = _TimerService:SetTimerRepeat(apply, 0.1)",
|
||||
"Code": "self.LockTries = 0\nlocal eventId = 0\nlocal function apply()\n\tself.LockTries = self.LockTries + 1\n\tlocal pc = nil\n\tlocal lp = _UserService.LocalPlayer\n\tif lp ~= nil then\n\t\tpc = lp.PlayerControllerComponent\n\tend\n\tif pc ~= nil then\n\t\tpc.LookDirectionX = 1\n\t\tpc.FixedLookAt = true\n\t\tpc.Enable = false\n\tend\n\tif lp ~= nil then\n\t\tif lp.RigidbodyComponent ~= nil then lp.RigidbodyComponent.WalkAcceleration = 0 end\n\t\tif lp.MovementComponent ~= nil then lp.MovementComponent.InputSpeed = 0; lp.MovementComponent.JumpForce = 0 end\n\tend\n\tif pc ~= nil then\n\t\t_TimerService:ClearTimer(eventId)\n\telseif self.LockTries > 30 then\n\t\t_TimerService:ClearTimer(eventId)\n\tend\nend\neventId = _TimerService:SetTimerRepeat(apply, 0.1)",
|
||||
"Scope": 2,
|
||||
"ExecSpace": 6,
|
||||
"Attributes": [],
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,13 +1,41 @@
|
||||
{
|
||||
"frames": {
|
||||
"warrior": { "normal": "4bb57ef88ef449fdaf958f6cf37fe44b", "unique": "4f71c124c8bc4e13b5e9fad392995f68", "legend": "6d741a60c60743cb98ee740a1e2dbfed" },
|
||||
"magician": { "normal": "d788d09f6f50467ebc67f01dec45f9e2", "unique": "f5def2e8022b4e59a17d3c16414034fe", "legend": "cff71f2e472041ce80c6fbd296f42e2d" },
|
||||
"bandit": { "normal": "9487b06867bc46269ed1d855420f457f", "unique": "b3081fb2fb1445fa90b12b01481a78ef", "legend": "c357d2daf31a489d95b8fa47e50dd879" }
|
||||
"warrior": {
|
||||
"normal": "4bb57ef88ef449fdaf958f6cf37fe44b",
|
||||
"unique": "4f71c124c8bc4e13b5e9fad392995f68",
|
||||
"legend": "6d741a60c60743cb98ee740a1e2dbfed"
|
||||
},
|
||||
"magician": {
|
||||
"normal": "d788d09f6f50467ebc67f01dec45f9e2",
|
||||
"unique": "f5def2e8022b4e59a17d3c16414034fe",
|
||||
"legend": "cff71f2e472041ce80c6fbd296f42e2d"
|
||||
},
|
||||
"bandit": {
|
||||
"normal": "9487b06867bc46269ed1d855420f457f",
|
||||
"unique": "b3081fb2fb1445fa90b12b01481a78ef",
|
||||
"legend": "c357d2daf31a489d95b8fa47e50dd879"
|
||||
}
|
||||
},
|
||||
"classToFrame": {
|
||||
"warrior": "warrior", "fighter": "warrior", "page": "warrior", "spearman": "warrior",
|
||||
"magician": "magician", "firepoison": "magician", "icelightning": "magician", "cleric": "magician",
|
||||
"bandit": "bandit", "shiv": "bandit", "poisoner": "bandit", "trickster": "bandit"
|
||||
"warrior": "warrior",
|
||||
"fighter": "warrior",
|
||||
"page": "warrior",
|
||||
"spearman": "warrior",
|
||||
"magician": "magician",
|
||||
"firepoison": "magician",
|
||||
"icelightning": "magician",
|
||||
"cleric": "magician",
|
||||
"thief": "bandit",
|
||||
"assassin": "bandit",
|
||||
"bandit": "bandit",
|
||||
"curse": "bandit",
|
||||
"shiv": "bandit",
|
||||
"poisoner": "bandit",
|
||||
"trickster": "bandit"
|
||||
},
|
||||
"rewardWeights": { "normal": 70, "unique": 25, "legend": 5 }
|
||||
"rewardWeights": {
|
||||
"normal": 70,
|
||||
"unique": 25,
|
||||
"legend": 5
|
||||
}
|
||||
}
|
||||
|
||||
231
data/cards.json
231
data/cards.json
@@ -6,9 +6,10 @@
|
||||
"kind": "Attack",
|
||||
"damage": 6,
|
||||
"desc": "피해 6",
|
||||
"image": "a71b116807904ef2b38e1dc013e2f9a2",
|
||||
"image": "e4acdf27d68549db8858d6082169c70c",
|
||||
"class": "warrior",
|
||||
"rarity": "normal"
|
||||
"rarity": "normal",
|
||||
"fx": "291b2298db88476f8ae3c6c78f53c9b7"
|
||||
},
|
||||
"Defend": {
|
||||
"name": "아이언 바디",
|
||||
@@ -16,7 +17,7 @@
|
||||
"kind": "Skill",
|
||||
"block": 5,
|
||||
"desc": "방어도 5",
|
||||
"image": "1ae9b6741c5947a8b528a0f515b50e3e",
|
||||
"image": "7648c3b8e1ca44fc8ec353561207a670",
|
||||
"class": "warrior",
|
||||
"rarity": "normal"
|
||||
},
|
||||
@@ -26,9 +27,10 @@
|
||||
"kind": "Attack",
|
||||
"damage": 10,
|
||||
"desc": "피해 10",
|
||||
"image": "d5bc2953fcab4cfe9062af81c35aff86",
|
||||
"image": "4cbbe8cfc3e840e4a76379498d8eb012",
|
||||
"class": "warrior",
|
||||
"rarity": "normal"
|
||||
"rarity": "normal",
|
||||
"fx": "863812c5c2f84132ac7465b50ec2283e"
|
||||
},
|
||||
"WarLeap": {
|
||||
"name": "워 리프",
|
||||
@@ -49,7 +51,8 @@
|
||||
"desc": "피해 13",
|
||||
"image": "21af4bccc5054a5dbc8245dfa7f08681",
|
||||
"class": "warrior",
|
||||
"rarity": "unique"
|
||||
"rarity": "unique",
|
||||
"fx": "e8a145a6c43d493f9ad50fab03b200aa"
|
||||
},
|
||||
"ChargedBlow": {
|
||||
"name": "차지 블로우",
|
||||
@@ -102,7 +105,8 @@
|
||||
"hits": 2,
|
||||
"desc": "피해 5 × 2회",
|
||||
"image": "1bc3e52b330648faae9eafd5a205e37b",
|
||||
"rarity": "unique"
|
||||
"rarity": "unique",
|
||||
"fx": "48754be05be344358cddd55aa8fe11f4"
|
||||
},
|
||||
"Berserk": {
|
||||
"name": "버서크",
|
||||
@@ -113,7 +117,7 @@
|
||||
"value": 1,
|
||||
"selfVuln": 1,
|
||||
"desc": "매턴 에너지 +1, 취약 1 자가",
|
||||
"image": "cef30ea340c74e768bcee4e2cbe0577a",
|
||||
"image": "e2580523efc6457385114b78ad0d7cce",
|
||||
"rarity": "legend"
|
||||
},
|
||||
"RisingAttack": {
|
||||
@@ -123,8 +127,9 @@
|
||||
"class": "fighter",
|
||||
"damage": 12,
|
||||
"desc": "피해 12",
|
||||
"image": "3a3d4b8bb5bd4137847caf883e4bf38e",
|
||||
"rarity": "unique"
|
||||
"image": "115e309771604743853abad2d8d186bc",
|
||||
"rarity": "unique",
|
||||
"fx": "6f283d96d5804b4fb88009685a11c1f8"
|
||||
},
|
||||
"ThunderCharge": {
|
||||
"name": "썬더 차지",
|
||||
@@ -134,8 +139,9 @@
|
||||
"damage": 7,
|
||||
"weak": 1,
|
||||
"desc": "피해 7, 약화 1",
|
||||
"image": "f1b7e3041909411eb67af884b446e1e1",
|
||||
"rarity": "unique"
|
||||
"image": "b7030d8caedc4fbc9f38fe1e541d6e6b",
|
||||
"rarity": "unique",
|
||||
"fx": "997fa6999aa04dbb97a1dd99025fa2ba"
|
||||
},
|
||||
"BlizzardCharge": {
|
||||
"name": "블리자드 차지",
|
||||
@@ -145,8 +151,9 @@
|
||||
"damage": 7,
|
||||
"vuln": 1,
|
||||
"desc": "피해 7, 취약 1",
|
||||
"image": "7915c70952ad432f99519ad79bf929a4",
|
||||
"rarity": "unique"
|
||||
"image": "9aac955d159f49c1bc913ef96128e781",
|
||||
"rarity": "unique",
|
||||
"fx": "2799562e984c4a4da3b73e1f3431057c"
|
||||
},
|
||||
"PowerGuard": {
|
||||
"name": "파워 가드",
|
||||
@@ -166,8 +173,9 @@
|
||||
"damage": 9,
|
||||
"pierce": true,
|
||||
"desc": "피해 9, 방어 무시",
|
||||
"image": "e312e535a2bc4fed82d36f9c6027c9db",
|
||||
"rarity": "unique"
|
||||
"image": "251b6e12329048429490049a4f3cf564",
|
||||
"rarity": "unique",
|
||||
"fx": "1b0afc410a1a458598eb7ca2fb26e97d"
|
||||
},
|
||||
"IronWall": {
|
||||
"name": "아이언 월",
|
||||
@@ -197,8 +205,9 @@
|
||||
"class": "magician",
|
||||
"damage": 6,
|
||||
"desc": "피해 6",
|
||||
"image": "a1ee3069fce14498b92998542679ae40",
|
||||
"rarity": "normal"
|
||||
"image": "e84880eaf89442128d3af2be5c80a74f",
|
||||
"rarity": "normal",
|
||||
"fx": "1d5877e1120a42d0907f204c959888b1"
|
||||
},
|
||||
"MagicGuard": {
|
||||
"name": "매직 가드",
|
||||
@@ -218,8 +227,9 @@
|
||||
"damage": 3,
|
||||
"hits": 2,
|
||||
"desc": "피해 3 × 2회",
|
||||
"image": "d6e7c04c436f42f19e9806ac5b4401ae",
|
||||
"rarity": "normal"
|
||||
"image": "f3fcac2d460041b288cc1973caaaf30f",
|
||||
"rarity": "normal",
|
||||
"fx": "ba4ac7c8f24845b68b7e689b7effcc93"
|
||||
},
|
||||
"Teleport": {
|
||||
"name": "텔레포트",
|
||||
@@ -229,7 +239,7 @@
|
||||
"block": 3,
|
||||
"draw": 1,
|
||||
"desc": "방어도 3, 드로 1",
|
||||
"image": "80c98c8e032b4f6c8371a24b4e1d8f14",
|
||||
"image": "7f70a9dc7e304433bb8121dd9c4df98b",
|
||||
"rarity": "normal"
|
||||
},
|
||||
"Slow": {
|
||||
@@ -239,7 +249,7 @@
|
||||
"class": "magician",
|
||||
"weak": 2,
|
||||
"desc": "약화 2 부여",
|
||||
"image": "16f79f571a964430bf1953edc9a14c73",
|
||||
"image": "7224cd3f9b7e497d9dd65f32a50865e4",
|
||||
"rarity": "normal"
|
||||
},
|
||||
"FireArrow": {
|
||||
@@ -249,8 +259,9 @@
|
||||
"class": "firepoison",
|
||||
"damage": 8,
|
||||
"desc": "피해 8",
|
||||
"image": "78b9be4e711c440f84fc21e51e812bae",
|
||||
"rarity": "unique"
|
||||
"image": "6fa15fd3a0004b409ea516c11a67e533",
|
||||
"rarity": "unique",
|
||||
"fx": "4a937e208875468eb63d891806fba3cd"
|
||||
},
|
||||
"PoisonBreath": {
|
||||
"name": "포이즌 브레스",
|
||||
@@ -259,7 +270,7 @@
|
||||
"class": "firepoison",
|
||||
"poison": 4,
|
||||
"desc": "독 4 부여",
|
||||
"image": "b4e8bd7508b54d208e4f2ad7414f8c0a",
|
||||
"image": "07200f3c74854022baa7ebbefdc4ad8c",
|
||||
"rarity": "unique"
|
||||
},
|
||||
"ElementAmp": {
|
||||
@@ -270,7 +281,7 @@
|
||||
"powerEffect": "strengthPerTurn",
|
||||
"value": 1,
|
||||
"desc": "매 턴 힘 +1",
|
||||
"image": "9859f3ab41b945f797d56cd83f95b25f",
|
||||
"image": "06865473977849bebe79062dbd608944",
|
||||
"rarity": "legend"
|
||||
},
|
||||
"ThunderBolt": {
|
||||
@@ -282,7 +293,8 @@
|
||||
"aoe": true,
|
||||
"desc": "모든 적에게 피해 6",
|
||||
"image": "c6685d33cb2641f09d11cfa2d5cc820c",
|
||||
"rarity": "legend"
|
||||
"rarity": "legend",
|
||||
"fx": "7d52f5e389bd4d44a30cf7cc54538f8f"
|
||||
},
|
||||
"ColdBeam": {
|
||||
"name": "콜드 빔",
|
||||
@@ -302,7 +314,7 @@
|
||||
"class": "icelightning",
|
||||
"block": 8,
|
||||
"desc": "방어도 8",
|
||||
"image": "b2a7274d868241c78aa5780f2beecddf",
|
||||
"image": "bef20873a68a4651a91d74be457c2cfc",
|
||||
"rarity": "unique"
|
||||
},
|
||||
"Heal": {
|
||||
@@ -312,7 +324,7 @@
|
||||
"class": "cleric",
|
||||
"heal": 10,
|
||||
"desc": "HP 10 회복",
|
||||
"image": "b4127c181e2942e38821d4a9a1f14596",
|
||||
"image": "8b935b7d7066493cb462834bbe287c74",
|
||||
"rarity": "unique"
|
||||
},
|
||||
"Bless": {
|
||||
@@ -323,7 +335,7 @@
|
||||
"strength": 1,
|
||||
"block": 5,
|
||||
"desc": "힘 +1, 방어도 5",
|
||||
"image": "d45553db4a414011b67486dfa8a12fe5",
|
||||
"image": "607fc5457c1c44a0993a5c2fe3fb0c68",
|
||||
"rarity": "unique"
|
||||
},
|
||||
"HolyArrow": {
|
||||
@@ -333,9 +345,154 @@
|
||||
"class": "cleric",
|
||||
"damage": 8,
|
||||
"desc": "피해 8",
|
||||
"image": "0265e103b4904f178b1c2bdcd54d5975",
|
||||
"image": "a80127195bf7471f9545b70e491f4719",
|
||||
"rarity": "unique",
|
||||
"fx": "4faa7b78e09643cf86339b8b7cf2abac"
|
||||
},
|
||||
"LuckySeven": {
|
||||
"name": "럭키 세븐",
|
||||
"cost": 1,
|
||||
"kind": "Attack",
|
||||
"class": "thief",
|
||||
"damage": 3,
|
||||
"hits": 2,
|
||||
"desc": "피해 3 × 2회",
|
||||
"rarity": "normal",
|
||||
"image": "0539ba559f8c413dac95c52992b436d9",
|
||||
"fx": "aa499663a278414b914b8fb9b8382879"
|
||||
},
|
||||
"DoubleStab": {
|
||||
"name": "더블 스탭",
|
||||
"cost": 2,
|
||||
"kind": "Attack",
|
||||
"class": "thief",
|
||||
"damage": 5,
|
||||
"hits": 2,
|
||||
"desc": "피해 5 × 2회",
|
||||
"rarity": "normal",
|
||||
"image": "92a5020c978c46bdabab910598118b86",
|
||||
"fx": "a82d0aae7f5e4db6a19078537afbe80c"
|
||||
},
|
||||
"DarkSight": {
|
||||
"name": "다크 사이트",
|
||||
"cost": 1,
|
||||
"kind": "Skill",
|
||||
"class": "thief",
|
||||
"block": 6,
|
||||
"desc": "방어도 6",
|
||||
"rarity": "normal",
|
||||
"image": "0946f69d84464df29b24b94c744c868d"
|
||||
},
|
||||
"Haste": {
|
||||
"name": "헤이스트",
|
||||
"cost": 1,
|
||||
"kind": "Skill",
|
||||
"class": "thief",
|
||||
"block": 3,
|
||||
"draw": 1,
|
||||
"desc": "방어도 3, 드로 1",
|
||||
"rarity": "normal",
|
||||
"image": "e65317856a914b8686f55e3351c3a24c"
|
||||
},
|
||||
"Drain": {
|
||||
"name": "드레인",
|
||||
"cost": 1,
|
||||
"kind": "Attack",
|
||||
"class": "thief",
|
||||
"damage": 5,
|
||||
"heal": 3,
|
||||
"desc": "피해 5, HP 3 회복",
|
||||
"rarity": "unique"
|
||||
},
|
||||
"CriticalThrow": {
|
||||
"name": "크리티컬 스로우",
|
||||
"cost": 2,
|
||||
"kind": "Attack",
|
||||
"class": "assassin",
|
||||
"damage": 8,
|
||||
"hits": 2,
|
||||
"desc": "피해 8 × 2회",
|
||||
"rarity": "unique",
|
||||
"image": "1b0f2dc8abd0434990eee1befefcbe0d",
|
||||
"fx": "23232336918d43f49fab19b888920f0c"
|
||||
},
|
||||
"ShadowStar": {
|
||||
"name": "쉐도우 스타",
|
||||
"cost": 1,
|
||||
"kind": "Attack",
|
||||
"class": "assassin",
|
||||
"damage": 6,
|
||||
"weak": 1,
|
||||
"desc": "피해 6, 약화 1",
|
||||
"rarity": "unique",
|
||||
"image": "2d394e08d95841028d3dc95fca200756",
|
||||
"fx": "ab45ee74d258419096e1e132af68aeca"
|
||||
},
|
||||
"ClawMastery": {
|
||||
"name": "클로 마스터리",
|
||||
"cost": 1,
|
||||
"kind": "Power",
|
||||
"class": "assassin",
|
||||
"powerEffect": "strengthPerTurn",
|
||||
"value": 1,
|
||||
"desc": "매 턴 힘 +1",
|
||||
"rarity": "legend",
|
||||
"image": "aa09741ae1e145a28d1e1c19aeb9e83c"
|
||||
},
|
||||
"SavageBlow": {
|
||||
"name": "새비지 블로우",
|
||||
"cost": 1,
|
||||
"kind": "Attack",
|
||||
"class": "bandit",
|
||||
"damage": 3,
|
||||
"hits": 3,
|
||||
"desc": "피해 3 × 3회",
|
||||
"rarity": "unique",
|
||||
"image": "92a5020c978c46bdabab910598118b86",
|
||||
"fx": "a82d0aae7f5e4db6a19078537afbe80c"
|
||||
},
|
||||
"Steal": {
|
||||
"name": "스틸",
|
||||
"cost": 1,
|
||||
"kind": "Skill",
|
||||
"class": "bandit",
|
||||
"block": 4,
|
||||
"draw": 1,
|
||||
"desc": "방어도 4, 드로 1",
|
||||
"rarity": "unique",
|
||||
"image": "c1e19219745e44c39ae6ac2f77e347d9"
|
||||
},
|
||||
"MesoGuard": {
|
||||
"name": "메소 가드",
|
||||
"cost": 1,
|
||||
"kind": "Power",
|
||||
"class": "bandit",
|
||||
"powerEffect": "blockPerTurn",
|
||||
"value": 3,
|
||||
"desc": "매 턴 방어도 +3",
|
||||
"rarity": "legend"
|
||||
},
|
||||
"Wound": {
|
||||
"name": "상처",
|
||||
"cost": 0,
|
||||
"kind": "Status",
|
||||
"desc": "사용할 수 없다. 손패를 막는 저주.",
|
||||
"class": "curse",
|
||||
"rarity": "normal",
|
||||
"unplayable": true,
|
||||
"curse": true
|
||||
},
|
||||
"Burn": {
|
||||
"name": "화상",
|
||||
"cost": 0,
|
||||
"kind": "Status",
|
||||
"desc": "사용 불가. 손패에 있으면 턴 종료 시 피해 2.",
|
||||
"class": "curse",
|
||||
"rarity": "normal",
|
||||
"unplayable": true,
|
||||
"curse": true,
|
||||
"endTurnDamage": 2
|
||||
},
|
||||
"SilentStrike": {
|
||||
"name": "타격",
|
||||
"cost": 1,
|
||||
@@ -1086,6 +1243,18 @@
|
||||
"MagicGuard",
|
||||
"MagicClaw"
|
||||
],
|
||||
"thief": [
|
||||
"LuckySeven",
|
||||
"LuckySeven",
|
||||
"LuckySeven",
|
||||
"LuckySeven",
|
||||
"LuckySeven",
|
||||
"DarkSight",
|
||||
"DarkSight",
|
||||
"DarkSight",
|
||||
"DarkSight",
|
||||
"DoubleStab"
|
||||
],
|
||||
"bandit": [
|
||||
"SilentStrike",
|
||||
"SilentStrike",
|
||||
|
||||
@@ -46,7 +46,8 @@
|
||||
"intents": [
|
||||
{ "kind": "Attack", "value": 4 },
|
||||
{ "kind": "Attack", "value": 4 },
|
||||
{ "kind": "Attack", "value": 10 }
|
||||
{ "kind": "Attack", "value": 10 },
|
||||
{ "kind": "AddCard", "card": "Wound", "count": 1 }
|
||||
]
|
||||
},
|
||||
"pig": {
|
||||
@@ -67,6 +68,24 @@
|
||||
{ "kind": "Attack", "value": 9 }
|
||||
]
|
||||
},
|
||||
"red_snail": {
|
||||
"name": "빨간 달팽이",
|
||||
"maxHp": 14,
|
||||
"intents": [
|
||||
{ "kind": "Attack", "value": 5 },
|
||||
{ "kind": "Defend", "value": 6 },
|
||||
{ "kind": "Attack", "value": 7 }
|
||||
]
|
||||
},
|
||||
"stump": {
|
||||
"name": "나무토막",
|
||||
"maxHp": 19,
|
||||
"intents": [
|
||||
{ "kind": "Defend", "value": 5 },
|
||||
{ "kind": "Attack", "value": 8 },
|
||||
{ "kind": "Attack", "value": 6 }
|
||||
]
|
||||
},
|
||||
"mushmom": {
|
||||
"name": "머쉬맘",
|
||||
"maxHp": 75,
|
||||
@@ -75,7 +94,8 @@
|
||||
{ "kind": "Debuff", "effect": "weak", "value": 2 },
|
||||
{ "kind": "Attack", "value": 16 },
|
||||
{ "kind": "Attack", "value": 9 },
|
||||
{ "kind": "Defend", "value": 6 }
|
||||
{ "kind": "Defend", "value": 6 },
|
||||
{ "kind": "AddCard", "card": "Burn", "count": 1 }
|
||||
]
|
||||
},
|
||||
"modified_snail": {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"ironHeart": { "name": "강철 심장", "desc": "전투 시작 시 방어도 +6", "hook": "combatStart", "effect": "block", "value": 6, "icon": "e555b3a62f3c49dbb2c53784e6bd481f" },
|
||||
"energyCore": { "name": "에너지 코어", "desc": "턴 시작 시 에너지 +1", "hook": "turnStart", "effect": "energy", "value": 1, "icon": "a41014f28b47434ab9f49ef104523862" },
|
||||
"vampire": { "name": "흡혈 송곳니", "desc": "공격 카드 사용 시 HP +1", "hook": "cardPlayed", "effect": "healOnAttack", "value": 1, "icon": "ed64cde7e6c44b9e99502847e54f04e9" },
|
||||
"goldIdol": { "name": "황금 우상", "desc": "전투 승리 시 골드 +10", "hook": "combatReward", "effect": "gold", "value": 10, "icon": "03bb05c92b8f45edb0f3dad2e118fd5a" },
|
||||
"goldIdol": { "name": "황금 우상", "desc": "전투 승리 시 메소 +10", "hook": "combatReward", "effect": "gold", "value": 10, "icon": "03bb05c92b8f45edb0f3dad2e118fd5a" },
|
||||
"potionBelt": { "name": "장인의 벨트", "desc": "물약 슬롯이 5칸으로 늘어난다", "hook": "passive", "effect": "potionSlots", "value": 5, "icon": "36725b4566ac40d4902e2ab2113c2096" },
|
||||
"burningBlood": { "name": "자쿰의 투구", "desc": "전투 승리 시 HP 6 회복", "hook": "combatEnd", "effect": "healOnWin", "value": 6, "icon": "07f994825ce34131b419d43e890c878d" },
|
||||
"vajra": { "name": "미스릴 해머", "desc": "전투 시작 시 힘 +1", "hook": "combatStart", "effect": "strength", "value": 1, "icon": "59d2579d46dc41d590a9e6b141ad458b" },
|
||||
|
||||
524
docs/superpowers/plans/2026-06-14-lobby-map-npc.md
Normal file
524
docs/superpowers/plans/2026-06-14-lobby-map-npc.md
Normal file
@@ -0,0 +1,524 @@
|
||||
# P15 — 로비 맵 + 월드 NPC 구현 계획
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: superpowers:executing-plans. 설계: `docs/superpowers/specs/2026-06-14-lobby-map-npc-design.md`. 산출물(`map/*.map`,`ui/DefaultGroup.ui`,`*.codeblock`,`Global/*`)은 Read/Edit 금지 — 생성기 소스(`tools/`)만 수정 후 재생성. 검증은 `grep -c`(카운트)와 메이커 플레이테스트.
|
||||
|
||||
**Goal:** UI 패널 로비를 폐기하고, 전용 물리 맵 `lobby`에 공식 메이플 NPC 4종을 월드 엔티티로 배치해 근접(↑키)·클릭으로 기능을 열며, 이동·공격 모션은 로비 맵에서만 풀린다.
|
||||
|
||||
**Architecture:** 단일 소스(`tools/*` 생성기 + `data/*.json`) → 산출물 재생성. 신규 생성기 2개(`gen-lobby-map.mjs`=맵+NPC 엔티티, `gen-lobby-npc.mjs`=LobbyNpc+LobbyMobility codeblock) + `gen-slaydeck.mjs`(흐름·UI) + `gen-player-lock.mjs`(전투맵 이동 재잠금 보강) 수정. 기존 기능 패널(CharacterSelect/Codex/SoulShop/Board)·전투 흐름 재사용.
|
||||
|
||||
**Tech Stack:** Node.js ESM 생성기, MSW Lua(codeblock), MSW MCP(플레이테스트·asset).
|
||||
|
||||
**확정 사실(조사):**
|
||||
- gen-slaydeck 편집 지점: OnBeginPlay `2830-2840`, ShowLobby `2986-2993`, LobbyHud npcs배열 `2469-2474`+버튼루프 `2475-2524`, lobTexts `2433-2439`, Asc버튼 `2454-2468`, BindLobbyButtons `2997-3014`, ShowState `2906-2922`, StartRun `3199-3232`, EndRun `4391-4403`, TeleportToActMap `4373-4385`, PlayerAttackMotion `4491-4500`, guid prefix `244-245`, ACT_MAPS `2745`.
|
||||
- **1막 텔레포트 공백**: StartRun(`3199-3232`)에 map01 텔레포트가 없음 → `self:TeleportToActMap()` 추가 필요(`RenderPotions` 다음, `ShowMap` 직전). `TeleportToActMap`은 `maps[self.Floor]` 사용 + 가드 `if lp.CurrentMapName==target then return`(멱등).
|
||||
- **NPC 공식 RUID**(maplestory, 흰박스 위험 없음): 모험가 `122095fd155c4633867b0da4f375bc3c`, 사서 `4c264be6a64f4ac3970b2e6818d04e40`, 상인 `69987ccdc486423f8bedd786bd6cb5d9`, 안내원 `8a99bd87d667482cb1f3b2193f8a19c1`.
|
||||
- **MSW API**: 월드 클릭 = 엔티티에 `TouchReceiveComponent` + `self.Entity:ConnectEvent(TouchEvent, fn)`. 키 = `_InputService:ConnectEvent(KeyDownEvent, fn)` + `KeyboardKey.UpArrow`(273)/`Space`(32)/`LeftControl`. 거리 = `Vector2.Distance(Vector2(a.x,a.y),Vector2(b.x,b.y))`. 이동복원 = `pc.Enable=true; pc.FixedLookAt=false; mv.InputSpeed=<V>; mv.JumpForce=<J>`(client 공간). 표시토글 = `entity:SetVisible(bool)`.
|
||||
- **맵 생성 패턴**(gen-maps.mjs): `JSON.parse(readFileSync('map/map01.map'))` → deep clone → 경로 `/maps/map01`→`/maps/lobby` 치환 → GUID 재발급(+origin fixup) → `compOf(e,'MOD.Core.X')`로 컴포넌트 접근 → `writeFileSync('map/lobby.map', JSON.stringify(map,null,2))`. 배경=`/Background`의 `BackgroundComponent.TemplateRUID`, 타일=`/TileMap`의 `TileMapComponent.TileSetRUID={DataId}`. 컴포넌트 부착=`@components` push + `componentNames` CSV 둘 다. SectorConfig=`Sectors[0].entries`에 `map://lobby` push.
|
||||
- **codeblock 패턴**(gen-combat-monster.mjs): `prop()/method()` 팩토리 + 봉투(`CoreVersion:'26.5.0.0'`, `EntryKey:'codeblock://x'`) → `writeFileSync('RootDesk/MyDesk/X.codeblock', JSON.stringify(cb,null,2))`. 컨트롤러 호출=`_EntityService:GetEntityByPath("/common").SlayDeckController:Method(...)`. 폴 idiom=`_TimerService:SetTimerRepeat(fn,0.1)`+try카운트 가드+`:ClearTimer(id)`.
|
||||
|
||||
---
|
||||
|
||||
### Task 0: 메이커 사전 정찰 (이동값·키·바디 컴포넌트·스폰좌표 확정)
|
||||
|
||||
**목적:** LobbyMobility의 이동 복원 수치·공격 키·바디 컴포넌트 종류·로비 스폰 좌표를 추측이 아니라 실측으로 확정. 산출물 작성 전 선행.
|
||||
|
||||
- [ ] **Step 1:** 메이커가 켜져 있는지 확인하고 현재 빌드 플레이. `mcp__msw-maker-mcp__maker_play` → `maker_screenshot`로 현재 화면(UI 로비) 확인.
|
||||
|
||||
- [ ] **Step 2:** execute_script로 LocalPlayer 컴포넌트·이동값·바디 종류 덤프:
|
||||
|
||||
```lua
|
||||
local lp = _UserService.LocalPlayer
|
||||
local s = "pc="..tostring(lp.PlayerControllerComponent ~= nil)
|
||||
local mv = lp.MovementComponent
|
||||
if mv ~= nil then s = s.." InputSpeed="..tostring(mv.InputSpeed).." JumpForce="..tostring(mv.JumpForce) end
|
||||
s = s.." Rigidbody="..tostring(lp.RigidbodyComponent ~= nil)
|
||||
s = s.." Sideviewbody="..tostring(lp.SideviewbodyComponent ~= nil)
|
||||
local p = lp.TransformComponent.WorldPosition
|
||||
s = s.." pos=("..tostring(p.x)..","..tostring(p.y)..","..tostring(p.z)..")"
|
||||
s = s.." map="..tostring(lp.CurrentMapName)
|
||||
log(s)
|
||||
return s
|
||||
```
|
||||
|
||||
Run via `maker_execute_script`. 기대: 현재 InputSpeed/JumpForce(0일 것), 어떤 바디 컴포넌트가 존재하는지(Rigidbody vs Sideviewbody), 현재 맵 이름·좌표.
|
||||
|
||||
- [ ] **Step 3:** 이동 복원값 실측 — execute_script로 직접 켜 보고 걸어지는지 확인:
|
||||
|
||||
```lua
|
||||
local lp = _UserService.LocalPlayer
|
||||
lp.PlayerControllerComponent.Enable = true
|
||||
lp.PlayerControllerComponent.FixedLookAt = false
|
||||
lp.MovementComponent.InputSpeed = 5
|
||||
lp.MovementComponent.JumpForce = 5
|
||||
return "applied: try walking with arrow keys"
|
||||
```
|
||||
|
||||
`maker_keyboard_input`로 방향키를 눌러 실제 이동 여부 확인(screenshot 비교). 걸으면 InputSpeed 값 후보 = 5. 안 걸으면 RigidbodyComponent.WalkSpeed/WalkJump 등도 set해보고(아래) 동작하는 최소 set을 기록.
|
||||
|
||||
```lua
|
||||
local rb = _UserService.LocalPlayer.RigidbodyComponent
|
||||
if rb ~= nil then rb.Enable = true end
|
||||
```
|
||||
|
||||
- [ ] **Step 4:** 공격 키 enum 확정 — `mlua_api_retriever`(이미 검증됨: UpArrow=273, Space=32)에서 공격용 키 `LeftControl`의 정확한 enum 멤버명 확인(예: `KeyboardKey.LeftControl`). 확인 안 되면 공격 키를 `KeyboardKey.Space`로 폴백(이동 점프는 MSW 기본 Alt 가정).
|
||||
|
||||
- [ ] **Step 5:** 결정 기록 — 이 plan 파일 하단 "정찰 결과" 섹션에 확정값 적기:
|
||||
- `WALK_SPEED` = (Step3에서 걸어진 InputSpeed), `JUMP_FORCE` = (걸어진 JumpForce), `BODY_KIND` = Rigidbody|Sideviewbody|none, 추가 바디 set 필요 여부, `ATTACK_KEY` = LeftControl|Space, `LOBBY_SPAWN` = 적당한 지면 좌표(현재 map 좌표 참고, 예 `Vector3(0, 0.03, 0)`).
|
||||
- 이후 Task에서 이 값을 JS 상수로 사용.
|
||||
|
||||
- [ ] **Step 6:** `maker_stop`으로 플레이 종료(상태 churn 방지).
|
||||
|
||||
---
|
||||
|
||||
### Task 1: `gen-lobby-map.mjs` — 로비 맵 + NPC 엔티티 생성
|
||||
|
||||
**Files:**
|
||||
- Create: `tools/map/gen-lobby-map.mjs`
|
||||
- Output(산출물, 직접 편집 금지): `map/lobby.map`, `Global/SectorConfig.config`(갱신)
|
||||
|
||||
NPC 4종 + `!` 마크 4종을 월드 엔티티로 배치. 마크는 자식이 아니라 **형제 엔티티**(NPC 위 고정 위치, 정적이라 무방). 각 NPC에 `TouchReceiveComponent` + `script.LobbyNpc`(NpcId), 맵 루트에 `script.LobbyMobility` 부착.
|
||||
|
||||
- [ ] **Step 1:** `tools/map/gen-maps.mjs`를 참고 헤더로 새 파일 생성. 상수:
|
||||
|
||||
```js
|
||||
import { readFileSync, writeFileSync } from 'node:fs';
|
||||
|
||||
const TEMPLATE = 'map/map01.map';
|
||||
const OUT = 'map/lobby.map';
|
||||
const SECTOR = 'Global/SectorConfig.config';
|
||||
const TOWN_BG = '<gen-maps.mjs BACKGROUNDS 풀에서 타운(헤네시스 등) RUID 1개 복사>'; // Task1 Step2에서 확정
|
||||
const NPCS = [
|
||||
{ name: 'NpcRun', id: 'run', x: -4.5, ruid: '122095fd155c4633867b0da4f375bc3c' },
|
||||
{ name: 'NpcCodex', id: 'codex', x: -1.5, ruid: '4c264be6a64f4ac3970b2e6818d04e40' },
|
||||
{ name: 'NpcShop', id: 'shop', x: 1.5, ruid: '69987ccdc486423f8bedd786bd6cb5d9' },
|
||||
{ name: 'NpcBoard', id: 'board', x: 4.5, ruid: '8a99bd87d667482cb1f3b2193f8a19c1' },
|
||||
];
|
||||
const MARK_RUID = '<Task1 Step2: asset_search로 "!" 말풍선/느낌표 공식 스프라이트 RUID, 못찾으면 NPC와 구분되는 작은 공식 스프라이트>';
|
||||
const NPC_Y = 0.0; // 지면 (Task0 좌표 참고로 조정)
|
||||
const MARK_DY = 1.6; // NPC 머리 위 오프셋
|
||||
|
||||
function compOf(e, type) { return e.jsonString['@components'].find((c) => c['@type'] === type); }
|
||||
function lobbyGuid(idx) {
|
||||
const n = (900000 + idx) >>> 0; // 기존 생성기와 충돌 없는 고유 오프셋
|
||||
return `${n.toString(16).padStart(8,'0')}-0000-4000-8000-${n.toString(16).padStart(12,'0')}`;
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2:** TOWN_BG·MARK_RUID 확정 — `gen-maps.mjs`를 열어 `BACKGROUNDS` 배열에서 타운 느낌 RUID 하나 골라 `TOWN_BG`에 박는다. MARK_RUID는 메이커 MCP `asset_search_resources`(source=maplestory, query "느낌표"/"balloon"/"emotion")로 1개 확정(못 찾으면 `!` 대신 작은 화살표/별 공식 스프라이트, 최후엔 NPC RUID 재사용+tint).
|
||||
|
||||
- [ ] **Step 3:** 맵 로드·클론·정리(몬스터 제거)·배경:
|
||||
|
||||
```js
|
||||
const map = JSON.parse(JSON.stringify(JSON.parse(readFileSync(TEMPLATE, 'utf8'))));
|
||||
map.EntryKey = 'map://lobby';
|
||||
let ents = map.ContentProto.Entities;
|
||||
const isMonster = (e) => typeof e.componentNames === 'string' && (e.componentNames.includes('script.Monster') || e.componentNames.includes('script.CombatMonster'));
|
||||
// 경로/이름 치환
|
||||
for (const e of ents) {
|
||||
if (typeof e.path === 'string') e.path = e.path.replace('/maps/map01', '/maps/lobby');
|
||||
if (e.jsonString) {
|
||||
if (typeof e.jsonString.path === 'string') e.jsonString.path = e.jsonString.path.replace('/maps/map01', '/maps/lobby');
|
||||
if (e.jsonString.name === 'map01') e.jsonString.name = 'lobby';
|
||||
}
|
||||
if ((e.path || '').endsWith('/Background')) { const bg = compOf(e, 'MOD.Core.BackgroundComponent'); if (bg) bg.TemplateRUID = TOWN_BG; }
|
||||
}
|
||||
// 몬스터 엔티티 제거 + PlayerLock/MapCamera는 유지(로비엔 PlayerLock 불필요하니 루트에서 제거)
|
||||
ents = ents.filter((e) => !isMonster(e));
|
||||
const root = ents.find((e) => e.path === '/maps/lobby');
|
||||
if (!root) throw new Error('[gen-lobby-map] 맵 루트 없음');
|
||||
// 로비엔 PlayerLock 컴포넌트가 있으면 제거(이동 잠금 방지)
|
||||
root.jsonString['@components'] = root.jsonString['@components'].filter((c) => c['@type'] !== 'script.PlayerLock');
|
||||
{ const names = (root.componentNames || '').split(',').filter((s) => s && s !== 'script.PlayerLock'); root.componentNames = names.join(','); }
|
||||
```
|
||||
|
||||
- [ ] **Step 4:** NPC 엔티티 + 마크 엔티티 생성(몬스터 템플릿을 클론해 몬스터 컴포넌트 제거 후 재사용). 몬스터 템플릿은 클론 전에 원본 ents(`map.ContentProto.Entities`)에서 확보:
|
||||
|
||||
```js
|
||||
const orig = JSON.parse(readFileSync(TEMPLATE, 'utf8')).ContentProto.Entities;
|
||||
const tmpl = orig.find((e) => typeof e.componentNames === 'string' && e.componentNames.includes('script.Monster'));
|
||||
if (!tmpl) throw new Error('[gen-lobby-map] 몬스터 템플릿(스프라이트 엔티티) 없음');
|
||||
let gi = 1;
|
||||
function makeSpriteEntity(name, x, y, ruid, extraComps, extraNames, visible) {
|
||||
const m = JSON.parse(JSON.stringify(tmpl));
|
||||
m.id = lobbyGuid(gi++);
|
||||
m.path = `/maps/lobby/${name}`;
|
||||
m.jsonString.path = m.path;
|
||||
m.jsonString.name = name;
|
||||
const o = m.jsonString.origin; if (o) { if (o.root_entity_id) o.root_entity_id = m.id; if (o.sub_entity_id) o.sub_entity_id = m.id; }
|
||||
const tr = compOf(m, 'MOD.Core.TransformComponent'); if (tr) { tr.Position.x = x; tr.Position.y = y; }
|
||||
const sp = compOf(m, 'MOD.Core.SpriteRendererComponent'); if (sp) sp.SpriteRUID = ruid;
|
||||
// 몬스터/전투 컴포넌트 전부 제거
|
||||
m.jsonString['@components'] = m.jsonString['@components'].filter((c) => !['script.Monster','script.CombatMonster'].includes(c['@type']));
|
||||
let names = (m.componentNames || '').split(',').filter((s) => s && !['script.Monster','script.CombatMonster'].includes(s));
|
||||
// StateAnimationComponent가 있으면 die/hit 시트 제거(정적 stand)
|
||||
for (const [comp, props] of extraComps) { m.jsonString['@components'].push({ '@type': comp, Enable: true, ...props }); names.push(comp); }
|
||||
names = names.concat(extraNames).filter(Boolean);
|
||||
m.componentNames = names.join(',');
|
||||
// 마크 숨김은 Enable=false 금지(SetVisible가 안 먹음). codeblock OnBeginPlay가 SetVisible(false)로 숨기므로
|
||||
// 여기선 별도 처리 안 함. (한 프레임 깜빡임 우려 시 SpriteRendererComponent.Visible=false 시도 — 필드 확인 후.)
|
||||
void visible;
|
||||
return m;
|
||||
}
|
||||
const added = [];
|
||||
for (const npc of NPCS) {
|
||||
// NPC: TouchReceiveComponent(자동맞춤) + script.LobbyNpc(NpcId)
|
||||
added.push(makeSpriteEntity(npc.name, npc.x, NPC_Y, npc.ruid,
|
||||
[['MOD.Core.TouchReceiveComponent', { AutoFitToSize: true }], ['script.LobbyNpc', { NpcId: npc.id, Tries: 0, InRange: false, MarkName: npc.name + 'Mark' }]],
|
||||
['MOD.Core.TouchReceiveComponent', 'script.LobbyNpc'], true));
|
||||
// 마크: NPC 위, 기본 숨김
|
||||
added.push(makeSpriteEntity(npc.name + 'Mark', npc.x, NPC_Y + MARK_DY, MARK_RUID, [], [], false));
|
||||
}
|
||||
ents = ents.concat(added);
|
||||
```
|
||||
|
||||
> 주: `script.LobbyNpc` props(NpcId/MarkName 등)는 Task2의 codeblock 속성 정의와 **이름이 정확히 일치**해야 한다.
|
||||
|
||||
- [ ] **Step 5:** 맵 루트에 `script.LobbyMobility` 부착 + 쓰기 + SectorConfig 등록:
|
||||
|
||||
```js
|
||||
root.jsonString['@components'] = root.jsonString['@components'].filter((c) => c['@type'] !== 'script.LobbyMobility');
|
||||
root.jsonString['@components'].push({ '@type': 'script.LobbyMobility', Enable: true, Tries: 0 });
|
||||
{ const names = (root.componentNames || '').split(',').filter((s) => s && s !== 'script.LobbyMobility'); names.push('script.LobbyMobility'); root.componentNames = names.join(','); }
|
||||
map.ContentProto.Entities = ents;
|
||||
writeFileSync(OUT, JSON.stringify(map, null, 2), 'utf8');
|
||||
// SectorConfig: map://lobby 등록(멱등) + 시작 섹터를 lobby로
|
||||
const sector = JSON.parse(readFileSync(SECTOR, 'utf8'));
|
||||
const sec0 = sector.ContentProto.Json.Sectors[0];
|
||||
if (!sec0.entries.includes('map://lobby')) sec0.entries.push('map://lobby');
|
||||
writeFileSync(SECTOR, JSON.stringify(sector, null, 2), 'utf8');
|
||||
console.log('[gen-lobby-map] lobby.map 생성 + SectorConfig 등록 완료');
|
||||
```
|
||||
|
||||
- [ ] **Step 6:** 실행 + 카운트 검증(내용 출력 금지):
|
||||
|
||||
```bash
|
||||
node tools/map/gen-lobby-map.mjs
|
||||
grep -c "script.LobbyNpc" map/lobby.map # 4 기대
|
||||
grep -c "script.LobbyMobility" map/lobby.map # 1 기대
|
||||
grep -c "TouchReceiveComponent" map/lobby.map # 4(+ 템플릿 잔존 가능) 기대
|
||||
grep -lc "map://lobby" Global/SectorConfig.config
|
||||
node tools/verify/count.mjs 2>/dev/null || true
|
||||
```
|
||||
|
||||
기대: LobbyNpc=4, LobbyMobility=1. 어긋나면 생성기 수정.
|
||||
|
||||
- [ ] **Step 7:** 커밋:
|
||||
|
||||
```bash
|
||||
git add tools/map/gen-lobby-map.mjs map/lobby.map Global/SectorConfig.config
|
||||
git commit -m "feat(lobby): 로비 전용 맵 + NPC 4종 월드 엔티티 생성기 (P15)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2: `gen-lobby-npc.mjs` — LobbyNpc + LobbyMobility codeblock
|
||||
|
||||
**Files:**
|
||||
- Create: `tools/player/gen-lobby-npc.mjs`
|
||||
- Output(산출물): `RootDesk/MyDesk/LobbyNpc.codeblock`, `RootDesk/MyDesk/LobbyMobility.codeblock`
|
||||
|
||||
`gen-combat-monster.mjs`의 `prop()/method()`/봉투 패턴을 그대로 복사. **Lua 문자열은 실제 탭 들여쓰기 사용**(RULES.md 메모리: 실탭↔`\t` 혼재 금지 — 템플릿 리터럴 안 실제 탭).
|
||||
|
||||
- [ ] **Step 1:** 헤더·팩토리(gen-combat-monster.mjs:9-17 복사) + 봉투 함수:
|
||||
|
||||
```js
|
||||
import { writeFileSync } from 'node:fs';
|
||||
const WALK_SPEED = /* Task0 정찰값 */ 5;
|
||||
const JUMP_FORCE = /* Task0 정찰값 */ 5;
|
||||
const ATTACK_KEY = /* Task0: 'LeftControl' 또는 'Space' */ 'LeftControl';
|
||||
|
||||
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(id, name, properties, methods) {
|
||||
const cb = { Id: '', GameId: '', EntryKey: `codeblock://${id.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), 'utf8');
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2:** LobbyNpc codeblock — 근접 폴링 + 마크 토글 + Touch/Key → Interact. (아래 Lua의 들여쓰기는 실제 탭으로 입력)
|
||||
|
||||
```js
|
||||
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.Tries = 0
|
||||
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
|
||||
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 < 1.8
|
||||
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', 'LobbyNpc', [
|
||||
prop('string', 'NpcId', '""'),
|
||||
prop('string', 'MarkName', '""'),
|
||||
prop('boolean', 'InRange', 'false'),
|
||||
prop('number', 'Tries', '0'),
|
||||
], [npcBegin, npcInteract]);
|
||||
```
|
||||
|
||||
- [ ] **Step 3:** LobbyMobility codeblock — 이동 복원 + 공격 키. (들여쓰기 실제 탭)
|
||||
|
||||
```js
|
||||
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 mv = lp.MovementComponent
|
||||
if mv ~= nil then
|
||||
mv.InputSpeed = ${WALK_SPEED}
|
||||
mv.JumpForce = ${JUMP_FORCE}
|
||||
end
|
||||
local rb = lp.RigidbodyComponent
|
||||
if rb ~= nil then rb.Enable = true end
|
||||
_TimerService:ClearTimer(eventId)
|
||||
elseif self.Tries > 50 then
|
||||
_TimerService:ClearTimer(eventId)
|
||||
end
|
||||
end
|
||||
eventId = _TimerService:SetTimerRepeat(apply, 0.1)
|
||||
_InputService:ConnectEvent(KeyDownEvent, function(e)
|
||||
if e.key == KeyboardKey.${ATTACK_KEY} then
|
||||
local c = _EntityService:GetEntityByPath("/common")
|
||||
if c ~= nil and c.SlayDeckController ~= nil then
|
||||
c.SlayDeckController:PlayerAttackMotion()
|
||||
end
|
||||
end
|
||||
end)`);
|
||||
|
||||
writeCodeblock('LobbyMobility', 'LobbyMobility', [prop('number', 'Tries', '0')], [mobBegin]);
|
||||
console.log('[gen-lobby-npc] LobbyNpc/LobbyMobility codeblock 생성 완료');
|
||||
```
|
||||
|
||||
- [ ] **Step 4:** 실행 + 카운트 검증:
|
||||
|
||||
```bash
|
||||
node tools/player/gen-lobby-npc.mjs
|
||||
grep -c "OnLobbyNpcInteract" RootDesk/MyDesk/LobbyNpc.codeblock # >=1
|
||||
grep -c "PlayerAttackMotion" RootDesk/MyDesk/LobbyMobility.codeblock # >=1
|
||||
ls -la RootDesk/MyDesk/LobbyNpc.codeblock RootDesk/MyDesk/LobbyMobility.codeblock
|
||||
```
|
||||
|
||||
- [ ] **Step 5:** 커밋:
|
||||
|
||||
```bash
|
||||
git add tools/player/gen-lobby-npc.mjs RootDesk/MyDesk/LobbyNpc.codeblock RootDesk/MyDesk/LobbyMobility.codeblock
|
||||
git commit -m "feat(lobby): LobbyNpc(근접·클릭 상호작용)·LobbyMobility(이동·공격 해제) codeblock (P15)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3: `gen-player-lock.mjs` — 전투맵 이동 재잠금 보강 (방어)
|
||||
|
||||
**Files:** Modify `tools/player/gen-player-lock.mjs`
|
||||
|
||||
로비에서 푼 이동이 텔레포트 후 전투맵에 누설돼도, 전투맵 PlayerLock이 런타임으로 MovementComponent를 0으로 재설정해 확실히 잠그도록 보강.
|
||||
|
||||
- [ ] **Step 1:** `gen-player-lock.mjs`의 PlayerLock Lua에서 `pc.Enable = false` 직후 라인을 추가(생성기 내 해당 Lua 템플릿 리터럴, 실제 탭 들여쓰기):
|
||||
|
||||
```lua
|
||||
pc.Enable = false
|
||||
local mv = lp.MovementComponent
|
||||
if mv ~= nil then mv.InputSpeed = 0; mv.JumpForce = 0 end
|
||||
```
|
||||
|
||||
(정확한 삽입 지점은 `gen-player-lock.mjs`에서 `pc.Enable`가 들어간 Lua 문자열. `LocalPlayer.PlayerControllerComponent`를 `lp`로 잡는 변수명이 기존 코드와 일치하는지 확인 — 다르면 기존 변수명 사용.)
|
||||
|
||||
- [ ] **Step 2:** 재생성 + 카운트:
|
||||
|
||||
```bash
|
||||
node tools/player/gen-player-lock.mjs
|
||||
grep -c "InputSpeed = 0" RootDesk/MyDesk/PlayerLock.codeblock # >=1 기대(파일명은 생성기 출력명 확인)
|
||||
```
|
||||
|
||||
- [ ] **Step 3:** 커밋:
|
||||
|
||||
```bash
|
||||
git add tools/player/gen-player-lock.mjs RootDesk/MyDesk/PlayerLock.codeblock map/map0*.map
|
||||
git commit -m "fix(lobby): 전투맵 PlayerLock에 이동값 런타임 0 재설정 보강 (P15)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 4: `gen-slaydeck.mjs` — 흐름·UI 통합
|
||||
|
||||
**Files:** Modify `tools/deck/gen-slaydeck.mjs`
|
||||
|
||||
- [ ] **Step 1:** guid prefix 등록(`244-245`) — 신규 prefix 불필요(LobbyHud 슬림화만, 기존 `lob` 재사용). 확인만.
|
||||
|
||||
- [ ] **Step 2:** ACT_MAPS 아래(`2745`)에 로비 상수 추가:
|
||||
|
||||
```js
|
||||
const ACT_MAPS = ['map01', 'map02', 'map03', 'map04', 'map05'];
|
||||
const LOBBY_MAP = 'lobby';
|
||||
const LOBBY_SPAWN = 'Vector3(0, 0.03, 0)'; // Task0 정찰 좌표로 조정
|
||||
```
|
||||
|
||||
- [ ] **Step 3:** LobbyHud 슬림화 — `npcs` 배열(`2469-2474`)과 버튼 생성 루프(`2475-2524`) **삭제**. `lobTexts`(`2433-2439`)는 SoulLabel/AscLabel + 안내문(Hint)만 남기고 Title/Subtitle은 "마을" 정도로 축소 or 제거. AscMinus/AscPlus(`2454-2468`)는 유지. → LobbyHud가 상단 정보바(영혼/승천)만 남음.
|
||||
|
||||
- [ ] **Step 4:** BindLobbyButtons(`2997-3014`) — NPC 4개 `bindClick` 라인 **삭제**(NpcRun/NpcCodex/NpcShop/NpcBoard). AscMinus/AscPlus/BoardHud.Close/SoulShopHud.Close bindClick은 유지.
|
||||
|
||||
- [ ] **Step 5:** ShowLobby(`2986-2993`) — 끝에 로비 맵 텔레포트 추가:
|
||||
|
||||
```js
|
||||
method('ShowLobby', `self.SelectedClass = ""
|
||||
self:RenderAscension()
|
||||
self:RenderSoulLabel()
|
||||
self:ShowState("lobby")
|
||||
self:SetEntityEnabled("/ui/DefaultGroup/BoardHud", false)
|
||||
self:SetEntityEnabled("/ui/DefaultGroup/SoulShopHud", false)
|
||||
self:BindLobbyButtons()
|
||||
self:BindMenuButtons()
|
||||
self:GoLobbyMap()`),
|
||||
```
|
||||
|
||||
- [ ] **Step 6:** 신규 method `GoLobbyMap`(ShowLobby 근처에 추가, ExecSpace 기본):
|
||||
|
||||
```js
|
||||
method('GoLobbyMap', `self.LobbyTpTries = 0
|
||||
local eventId = 0
|
||||
local function go()
|
||||
self.LobbyTpTries = self.LobbyTpTries + 1
|
||||
local lp = _UserService.LocalPlayer
|
||||
if lp ~= nil then
|
||||
if lp.CurrentMapName ~= "${LOBBY_MAP}" then
|
||||
_TeleportService:TeleportToMapPosition(lp, ${LOBBY_SPAWN}, "${LOBBY_MAP}")
|
||||
end
|
||||
_TimerService:ClearTimer(eventId)
|
||||
elseif self.LobbyTpTries > 50 then
|
||||
_TimerService:ClearTimer(eventId)
|
||||
end
|
||||
end
|
||||
eventId = _TimerService:SetTimerRepeat(go, 0.1)`),
|
||||
```
|
||||
|
||||
- [ ] **Step 7:** 신규 method `OnLobbyNpcInteract`(인자 id) — NPC codeblock이 호출:
|
||||
|
||||
```js
|
||||
method('OnLobbyNpcInteract', `if self.RunActive == true then return end
|
||||
if id == "run" then
|
||||
self:ShowCharacterSelect()
|
||||
elseif id == "codex" then
|
||||
self:ShowCodex()
|
||||
elseif id == "shop" then
|
||||
self:ShowSoulShop()
|
||||
elseif id == "board" then
|
||||
self:ShowBoard()
|
||||
end`, [{ Type: 'string', DefaultValue: '""', SyncDirection: 0, Attributes: [], Name: 'id' }]),
|
||||
```
|
||||
|
||||
(인자 객체 형태는 기존 `EndRun`의 `text` 인자/`ShowState`의 `state` 인자 정의를 참고해 동일 구조로.)
|
||||
|
||||
- [ ] **Step 8:** StartRun(`3199-3232`) — `RenderPotions()` 다음, `ShowMap()` 직전에 1막 텔레포트 추가:
|
||||
|
||||
```js
|
||||
// ... self:RenderPotions() (기존) 다음 줄에
|
||||
self:TeleportToActMap()
|
||||
// ... self:ShowMap() (기존)
|
||||
```
|
||||
|
||||
(StartRun의 Lua 문자열 내부에 `self:TeleportToActMap()` 한 줄 삽입. Floor=1이 이미 세팅돼 map01 타깃.)
|
||||
|
||||
- [ ] **Step 9:** EndRun(`4391-4403`) 복귀 — 기존 타이머 `self:ShowLobby()`가 GoLobbyMap을 호출하므로 **별도 변경 불필요**(ShowLobby가 로비 맵 텔레포트 포함). 확인만.
|
||||
|
||||
- [ ] **Step 10:** 재생성 + 카운트 검증:
|
||||
|
||||
```bash
|
||||
node tools/deck/gen-slaydeck.mjs
|
||||
grep -c "OnLobbyNpcInteract" RootDesk/MyDesk/SlayDeckController.codeblock # >=1 (이 파일엔 정의만; 호출은 LobbyNpc.codeblock)
|
||||
grep -c "GoLobbyMap" RootDesk/MyDesk/SlayDeckController.codeblock # >=2 (정의+ShowLobby 호출)
|
||||
grep -c "TeleportToActMap" RootDesk/MyDesk/SlayDeckController.codeblock # >=3 (정의+ContinueAfterBoss+StartRun)
|
||||
grep -c "NpcRun" ui/DefaultGroup.ui # 0 기대(버튼-행 제거됨)
|
||||
```
|
||||
|
||||
- [ ] **Step 11:** 커밋:
|
||||
|
||||
```bash
|
||||
git add tools/deck/gen-slaydeck.mjs ui/DefaultGroup.ui RootDesk/MyDesk/SlayDeckController.codeblock Global/common.gamelogic map/lobby.map map/map0*.map
|
||||
git commit -m "feat(lobby): 로비 맵 흐름 통합 — OnBeginPlay/EndRun 텔레포트·NPC 상호작용 디스패치·StartRun map01 텔레포트·LobbyHud 슬림화 (P15)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 5: 미러/회귀 테스트
|
||||
|
||||
전투 규칙·맵 그래프 알고리즘 미변경 → 미러 동기화 불필요. 기존 테스트 회귀만 확인.
|
||||
|
||||
- [ ] **Step 1:** 기존 테스트 실행:
|
||||
|
||||
```bash
|
||||
node --test tools/balance/sim-balance.test.mjs
|
||||
node --test tools/map/rogue-map.test.mjs
|
||||
```
|
||||
|
||||
기대: 전부 PASS(이번 변경은 전투/맵그래프 무관이라 회귀 없어야 함).
|
||||
|
||||
- [ ] **Step 2:** `git status --short`로 의도치 않은 산출물 변경 없는지 확인(산출물 diff는 보지 않음).
|
||||
|
||||
---
|
||||
|
||||
### Task 6: 메이커 플레이테스트 검증
|
||||
|
||||
- [ ] **Step 1:** git 상태 정리 후 메이커에서 **로컬 워크스페이스 refresh**(RULES.md §5 — 안 하면 stale 상태가 디스크 덮어씀). `maker_refresh_workspace` → 빌드 콘솔 0 에러 확인(`maker_logs`).
|
||||
|
||||
- [ ] **Step 2:** `maker_play` → `maker_screenshot`. 검증 시나리오(스크린샷·로그로):
|
||||
1. 월드 시작 → **로비 맵에 스폰**(타운 배경, NPC 4명 보임), 방향키로 **이동됨**, 공격 키로 **공격 모션** 나옴.
|
||||
2. NPC 근접 → 머리 위 `!` 표시 → `↑`키로 기능 패널 오픈. NPC `maker_mouse_input` 클릭으로도 오픈(버튼 클릭 불가 메모리 주의 — 월드 엔티티 TouchEvent라 mouse_input 좌표 클릭 시도, 안 되면 ↑키 경로로 검증).
|
||||
3. 모험가→직업선택→런 시작 → **map01로 텔레포트**, 이동/공격 **잠김**. 1막 전투 몬스터 정상 등장(CurrentMapName 필터 통과).
|
||||
4. 사서→도감, 상인→영혼상점, 안내원→게시판 각각 오픈/닫기.
|
||||
5. 런 종료(빠른 패배 유도: execute_script로 `c.Combat.PlayerHp=0` 등 or 정상 진행) → 4초 후 **로비 맵 복귀**, 이동/공격 재해제.
|
||||
6. 상단 미니 HUD에 영혼/승천 표시 정상.
|
||||
|
||||
- [ ] **Step 2b:** 실패 시 디버깅 — 이동 안 됨→Task0 값 재확認/RigidbodyComponent 추가 set, 클릭 안 됨→TouchReceiveComponent 필드/근접↑키 폴백, 몬스터 안 나옴→StartRun 텔레포트·spawn 좌표 확인. 생성기 수정→재생성→refresh→재플레이.
|
||||
|
||||
- [ ] **Step 3:** `maker_stop`. 스크린샷을 사용자에게 공유.
|
||||
|
||||
---
|
||||
|
||||
### Task 7: PR
|
||||
|
||||
- [ ] **Step 1:** push:
|
||||
|
||||
```bash
|
||||
git push -u origin feature/p15-lobby-map-npc
|
||||
```
|
||||
|
||||
- [ ] **Step 2:** PR spec JSON(UTF-8) 작성 후 `node tools/git/gitea-pr.mjs create <spec.json>` (RULES.md §4 — 인라인 curl 한글 금지). 제목 예: "feat: P15 — 로비 맵 + 월드 NPC(근접·클릭) + 로비 전용 이동·공격". 본문에 변경 요약·검증 결과·스크린샷 언급.
|
||||
|
||||
- [ ] **Step 3:** 사용자에게 PR 번호 보고 + 머지 여부 확인.
|
||||
|
||||
---
|
||||
|
||||
## 정찰 결과 (Task0 실측 완료)
|
||||
- **이동 레버 = `RigidbodyComponent.WalkAcceleration` (freeze가 0으로 만든 값). 복원값 0.7로 이동·점프 정상 확인** (InputSpeed/JumpForce는 무관 — WalkSpeed=1.4·WalkJump=1.23는 freeze가 안 건드림).
|
||||
- 이동 해제 = `pc.Enable=true; pc.FixedLookAt=false; rb.WalkAcceleration=0.7` (rb.Enable는 이미 true).
|
||||
- BODY_KIND = Rigidbody가 구동(Sideviewbody도 존재하나 WalkSpeed=nil). 추가 바디 set 불필요.
|
||||
- ATTACK_KEY = `LeftControl` (KeyboardKey.LeftControl 유효, PlayerAttackMotion() 호출 정상).
|
||||
- 상호작용 키 = `UpArrow` 유효. 클릭 = TouchReceiveComponent+TouchEvent.
|
||||
- 현재 플레이어 위치 map01 (-5,-0.039,0) → LOBBY_SPAWN = `Vector3(-5, 0.03, 0)`. NPC x = -3 / -0.5 / 2 / 4.5, 근접 임계 1.2.
|
||||
- TOWN_BG = Task1에서 gen-maps BACKGROUNDS 풀에서 선택, MARK_RUID = Task1 asset 검색.
|
||||
@@ -0,0 +1,97 @@
|
||||
# P14 — 반복 런 · 로비 · 영혼 · 도적 · 몬스터 랜덤성 설계
|
||||
|
||||
> 작성 2026-06-13. 사용자 자율 실행 지시(Phase별 커밋 → 최종 push/PR)에 따라 인터랙티브 승인 게이트 없이
|
||||
> 설계 결정을 본 문서에 기록·커밋하고 순차 구현한다. 산출물(`ui/DefaultGroup.ui`·`*.codeblock`·`*.map`·
|
||||
> `*.gamelogic`)은 생성기(`tools/`)·데이터(`data/`)에서 100% 생성되므로 본 작업은 전부 소스만 수정한다(RULES.md).
|
||||
|
||||
## 목표
|
||||
|
||||
기존 P1~P13 단발 런 구조를, **로비 허브를 중심으로 반복 수행하는 로그라이트 루프**로 재편하고
|
||||
도적 직업·몬스터 랜덤성·영혼 메타 성장·카드 UX를 추가한다.
|
||||
|
||||
## 핵심 설계 결정 (요약)
|
||||
|
||||
1. **맵 5개 + 반복 루프**: 런은 map01~map05 5막. 최종 보스 클리어 시 무한 진행이 아니라 **로비로 복귀**해
|
||||
영혼을 정산하고 다음 런을 준비. "반복 수행이 메인"을 *로비 기점 반복 런*으로 해석.
|
||||
2. **로비 = 스크린 HUD**: 게임 전체가 이미 스크린 HUD(MapHud/ShopHud/RewardHud) 구동이고 물리 맵은 배경일 뿐이다.
|
||||
로비도 동일하게 `LobbyHud`(스크린)로 구현하고, NPC는 클릭 가능한 스프라이트 버튼으로 배치한다.
|
||||
NPC 4종: **도감(Codex)·상점(영혼 메타)·런 시작·게시판(채팅 대용)**. 첫 실행/패배/클리어 시 진입점.
|
||||
3. **영혼(Soul)**: 승천(패널티 누적)과 역할 분리된 **영구 강화 메타 화폐**.
|
||||
*2차 전직을 한 상태로* 맵 보스를 클리어할 때마다 누적. 로비 상점 NPC에서 해금 구매 → 다음 런에 이점.
|
||||
저장은 승천 RPC 패턴 복제(`UserDataStorage`, key `soulPoints`/`soulUnlocks`).
|
||||
4. **도적**: `bandit` 프레임이 이미 데이터에 준비됨. 도적 1차 + 2차(어쌔신/시프) 카드·스타터덱 신규.
|
||||
5. **몬스터 랜덤성**: 구성(일반 1~3 / 엘리트 1+일반 0~2 / 보스 1)과 행동(정의된 intent 중 랜덤)을 런타임 추첨.
|
||||
StS2식 "덱 오염" intent(`AddCard`)와 저주 카드 신규.
|
||||
6. **메소**: 표면 문자열은 이미 메소. 잔존 `goldIdol.desc` 정정 + 메소 코인 아이콘 추가(내부 식별자 `Gold`는 유지).
|
||||
|
||||
## Phase 구성 (각 Phase = 1+ 커밋, 소스 수정 → 재생성 → 카운트/테스트 검증)
|
||||
|
||||
### Phase 1 — 맵 5막 · depth 7 · 노드 인접 규칙
|
||||
- `gen-slaydeck.mjs`: `MAP_ROWS 7→6`(걷는행6+보스=총7), `ACT_COUNT 3→5`, `ACT_MAPS [map01..map05]`, `RUN_LENGTH 3→5`.
|
||||
- 노드 타입 인접 금지 확장: 현재 elite만 부모-자식 연속 금지 → **rest·shop·elite** 3종 모두 금지.
|
||||
`GenerateMap`(Lua 4333-4358) + `rogue-map.mjs`(67-72) 미러 + `rogue-map.test.mjs` 단언 추가.
|
||||
- 막 배율 `1+(Floor-1)*0.6`(`:2776`)을 5막 기준 `1+(Floor-1)*0.45`로 완화.
|
||||
- 맵 파일 11→5: 생성기 카운트(`gen-maps`/`gen-map-encounters`/`gen-combat-monster`/`freeze-turn-monsters`/
|
||||
`gen-camera`/`gen-player-lock`) `[2..11]→[2..5]`, `length:11→5`. `git rm map/map06..11.map`.
|
||||
`Global/SectorConfig.config`에서 map06~11 엔트리 제거(생성기가 재구성하도록 보정 또는 수동 정리).
|
||||
|
||||
### Phase 2 — 노드 가로 레이아웃(왼→오른쪽)
|
||||
- `gen-slaydeck.mjs:1536-1538` 좌표 함수 row↔x·col↔y 스왑 + 호출부(1573/1600/1605) 인자 스왑. Lua 무수정.
|
||||
보스는 최우측 중앙. 도트 보간·간선·라벨 자동 추종.
|
||||
|
||||
### Phase 3 — 몬스터 랜덤 구성 · 랜덤 행동 · AddCard · map01 배치
|
||||
- `data/enemies.json`: 종별 `tier`(normal/elite/boss) 추가, 일부 종에 `AddCard` intent 추가.
|
||||
map01용 일반 5종 + 엘리트 1종 보장(slime/orange/blue/green mushroom/pig + mushmom 등 기존 활용).
|
||||
- `data/cards.json`: 저주/상태 카드 신규(`kind:"Status"`, `unplayable:true`, `curse:true`, `endTurnDamage?`).
|
||||
- `gen-slaydeck.mjs`: `BuildMonsters`(2780) 노드타입별 랜덤 구성, `EnemyActStep`(3603) 랜덤 intent 선택
|
||||
(예고=확정: 턴 종료 시 다음 행동 추첨 저장), `AddCard` intent 처리, `PlayCard` unplayable 가드,
|
||||
카드 직렬화(`luaCardsTable`)에 신규 필드, intent 직렬화(`luaIntentsArray`)에 `card`/`count`.
|
||||
- `sim-balance.mjs`+test: 랜덤 행동(rng)·AddCard 미러, 결정성 테스트 유지, 저주 unplayable 필터.
|
||||
- `gen-map-encounters.mjs`: map01 편입 + 일반5/엘리트1 레이아웃(오른쪽 배치). 엘리트 맵에 일반 혼합용 변형 배치.
|
||||
|
||||
### Phase 4 — 도적 클래스 + 2차(어쌔신/시프)
|
||||
- `data/cards.json`: 도적 1차(class `thief`) + 어쌔신(class `assassin`) + 시프(class `bandit`) 카드 + 스타터덱.
|
||||
- `data/cardframes.json`: `classToFrame`에 thief/assassin/bandit → `bandit` 프레임 매핑.
|
||||
- `gen-slaydeck.mjs`: `CLASSES.thief`(maxHp 75), `JOBS.thief`(어쌔신/시프), CharacterSelectHud Thief 해금,
|
||||
`BindMenuButtons` ThiefButton, `RenderCharacterSelect`/`StartNewGame`/`StartRun`/`JobLabel` 도적 분기.
|
||||
- 전사/법사 2차는 완비 확인됨(수정 불요).
|
||||
|
||||
### Phase 5 — 카드 스킬 아이콘 · 피격 이펙트 분리
|
||||
- `data/cards.json`: 공격 카드에 `fx`(이펙트 RUID) 필드 추가, `image`는 스킬 아이콘 유지.
|
||||
- `gen-slaydeck.mjs`: `luaCardsTable` fx 직렬화, `PlayCard`(3296-3298) FX 인자를 `c.fx or c.image`로.
|
||||
- RUID는 MSW 공식 리소스 asset 검색으로 수급(계정 업로드 금지·RULES §5).
|
||||
|
||||
### Phase 6 — 카드 UX: 핸드 최대 10 · 마우스오버 확대/툴팁
|
||||
- `gen-slaydeck.mjs`: CardHand 슬롯 5→10 확장, `RenderHand` 동적 간격(장수 비례), `DrawCards` 10장 상한
|
||||
(초과 분 자동 버림), 카드 hover 이벤트 바인딩(enter→`ShowTooltip`+스케일업, exit→복귀), 드래그 중 가드.
|
||||
- `sim-balance.mjs`+test: 드로우 상한 미러.
|
||||
|
||||
### Phase 7 — 메소 전환 + 메소 아이콘
|
||||
- `data/relics.json`: `goldIdol.desc` "골드"→"메소".
|
||||
- `gen-slaydeck.mjs`: TopBar·ShopHud 메소 텍스트 옆 코인 아이콘 sprite 추가(공식 RUID).
|
||||
|
||||
### Phase 8 — 로비 + NPC + 반복 루프
|
||||
- `gen-slaydeck.mjs`: `LobbyHud` 섹션(배경 + NPC 4종 스프라이트 버튼), `ShowLobby`/`ShowState("lobby")`,
|
||||
NPC 핸들러(Codex→CodexHud, Shop→영혼상점, RunStart→CharacterSelect, Board→게시판 패널).
|
||||
`CodexHud`: 전 카드 도감(클래스별 그리드). `OnBeginPlay`→`ShowLobby`(메뉴 대체), `EndRun`→`ShowLobby`.
|
||||
첫 실행/패배/클리어 모두 로비 기점.
|
||||
|
||||
### Phase 9 — 영혼(Soul) 시스템
|
||||
- `gen-slaydeck.mjs`: `SoulPoints`/`SoulUnlocks` 프로퍼티, RPC `ReqLoadSouls`/`SaveSouls`/`RecvSouls`(ExecSpace 5/6),
|
||||
보스 클리어 & `PlayerJob~=""`일 때 영혼 가산(`ContinueAfterBoss`/`CheckCombatEnd`), 로비 영혼 상점 UI·구매,
|
||||
해금 효과를 `StartRun`에 적용(시작 메소/시작 유물/시작 HP/덱 강화 등 덱빌딩 이점).
|
||||
|
||||
### Final — 전체 재생성 · 테스트 · push · PR
|
||||
- 전 생성기 재생성, `node --test`(rogue-map·sim-balance), 카운트 검증, 가능 시 메이커 플레이테스트.
|
||||
- `tools/git/gitea-pr.mjs`로 UTF-8 spec JSON 작성 후 PR 생성(RULES §4).
|
||||
|
||||
## 검증 원칙 (RULES §2·§6)
|
||||
- 산출물 본문 출력 금지 — `grep -c`/JSON parse/카운트만.
|
||||
- 전투·맵 규칙 수정 시 Lua↔JS 미러 동시 수정 + `node --test` 통과.
|
||||
- 커밋은 기능 단위, 산출물 재생성은 메시지에 명시.
|
||||
|
||||
## 알려진 제약 / 결정 근거
|
||||
- **로비 "돌아다니기"**: 물리 맵 walkable 로비는 player 이동이 전역 freeze(턴제)라 위험·고비용 → 스크린 HUD
|
||||
NPC 클릭으로 동일 기능 제공(아키텍처 정합). 추후 walkable 로비는 확장 슬롯.
|
||||
- **카드 아이콘/이펙트**: 현재 `c.image`가 카드 아트 겸 피격 FX로 이중 사용 중 → `fx` 분리로 의도 달성.
|
||||
- **영혼 vs 승천**: 승천=적 강화 패널티 토글, 영혼=플레이어 영구 강화 → 같은 저장소 다른 key로 공존.
|
||||
104
docs/superpowers/specs/2026-06-14-lobby-map-npc-design.md
Normal file
104
docs/superpowers/specs/2026-06-14-lobby-map-npc-design.md
Normal file
@@ -0,0 +1,104 @@
|
||||
# 로비 맵 + 월드 NPC 설계 (P15)
|
||||
|
||||
작성일: 2026-06-14
|
||||
브랜치: `feature/p15-lobby-map-npc`
|
||||
|
||||
## 목표
|
||||
|
||||
기존 **UI 패널 로비**(P14-8 `LobbyHud` — 색칠된 `UIButton` 4개 행)를 폐기하고,
|
||||
**전용 로비 맵**에 **월드 NPC 엔티티 4종**을 배치한다. NPC를 누르면 각 기능이 실행되며,
|
||||
플레이어 **이동·공격 모션은 로비 맵에서만** 풀린다(전투/런 맵에서는 기존대로 잠김 유지).
|
||||
|
||||
요청 원문: "로비를 UI로 만들지 말고, map을 추가해서 로비에 각각 기능을 할 수 있는 npc를 추가하고,
|
||||
그 npc를 누르면 각 기능을 할 수 있도록 추가. 플레이어는 반드시 로비맵에서만 이동과 공격 모션을 풀어줘."
|
||||
|
||||
## 확정된 결정 (브레인스토밍)
|
||||
|
||||
| 항목 | 결정 |
|
||||
|---|---|
|
||||
| NPC 상호작용 | **근접 프롬프트+키(Up/Space) AND 직접 클릭** 둘 다 지원 |
|
||||
| NPC 기능 | **기존 4종 유지** + 외형을 **정식 maplestory NPC 스프라이트(공식 RUID)** 로 교체 |
|
||||
| 로비 맵 | **새 전용 로비 맵 추가**(`map/lobby.map`), 런 맵(map01~) 인덱스 불변 |
|
||||
|
||||
## 현재 상태 (조사 결과)
|
||||
|
||||
- **로비 = UI 패널** `LobbyHud`. NPC 4종은 색칠 `UIButton`, 전부 `tools/deck/gen-slaydeck.mjs`에 하드코딩.
|
||||
- `NpcRun`(모험가→런 시작), `NpcCodex`(사서→카드 도감), `NpcShop`(상인→영혼 상점), `NpcBoard`(안내원→게시판).
|
||||
- 정의 ~`gen-slaydeck.mjs:2469-2487`, 클릭 바인딩 `BindLobbyButtons()` ~`2997-3014`.
|
||||
- 기능 진입 메서드: `ShowCharacterSelect()`(~3147), `ShowCodex()`, `ShowSoulShop()`, `ShowBoard()` — **재사용 대상**.
|
||||
- **이동/공격 이중 잠금**:
|
||||
- `tools/player/freeze-turn-player.mjs` → `Global/DefaultPlayer.model` speed/jump/accel = 0 (**전역**).
|
||||
- `tools/player/gen-player-lock.mjs` → `PlayerLock` codeblock(`pc.Enable=false`, `FixedLookAt=true`)를 map01~05에 주입.
|
||||
- 공격은 순수 모션 `PlayerAttackMotion()`(StateComponent ATTACK→IDLE), 필드 몬스터와 무관(전투는 데이터 배열 UI 전투).
|
||||
- 플레이어 스폰: `TeleportToActMap()` → `Vector3(-6, 0.03, 0)`, Floor별 map 선택.
|
||||
- **맵 파이프라인**: map01 = 저작 템플릿, map02~05 = 복제 생성. 포털 없이 `TeleportToMapPosition` 전환.
|
||||
- `Global/SectorConfig.config`의 valid 목록에 맵 등록(현재 gen-maps.mjs가 갱신).
|
||||
- 컴포넌트 부착 패턴: `gen-combat-monster.mjs`가 맵 몬스터에 `script.CombatMonster`를 붙이고, 해당 codeblock이 OnBeginPlay에서 `/common` 컨트롤러에 자가등록.
|
||||
- **흐름**: `OnBeginPlay`→`ShowLobby()`(UI). `EndRun(text)`→4초 후 `ShowLobby()`.
|
||||
|
||||
## 접근법
|
||||
|
||||
**A. 새 로비 맵 + 월드 NPC 엔티티 (채택)** — 맵 템플릿 복제 재사용, NPC를 월드 엔티티로 배치하고
|
||||
각 NPC의 codeblock이 근접+클릭을 감지해 **기존 기능 패널**을 띄움. 이동/공격 해제는 로비 맵 전용 codeblock.
|
||||
전투맵은 손대지 않아 잠금 유지. (B: 몬스터 배치기 재활용 → 로직 혼재로 비채택. C: 화면고정 UI 버튼 → 거부된 "UI 로비"라 제외.)
|
||||
|
||||
## 상세 설계
|
||||
|
||||
### 1) 로비 맵 — `tools/map/gen-lobby-map.mjs` → `map/lobby.map`
|
||||
- map01 템플릿 deep clone → 경로 `/maps/lobby`로 치환, GUID 재발급(결정론 시드).
|
||||
- 마을/타운 배경 RUID + 타일셋 적용. **`script.Monster`/`script.CombatMonster` 엔티티 전부 제거**(전투 없음).
|
||||
- NPC 4종을 x축으로 벌려 월드 엔티티로 배치. 각 NPC:
|
||||
- 스프라이트 = 공식 maplestory NPC RUID(계정 업로드 금지 — 흰 박스). 구현 단계에서 asset 검색으로 4개 확정.
|
||||
- `script.LobbyNpc` 컴포넌트 + `NpcId`(`run`/`codex`/`shop`/`board`) + 머리 위 이름/`!` 프롬프트용 텍스트 노드.
|
||||
- 플레이어 스폰 지점(맵 중앙-좌측).
|
||||
- `Global/SectorConfig.config` valid 목록에 `map://lobby` 추가 — **SectorConfig 단일 소유자는 gen-maps.mjs**로 유지하고 lobby 항목을 그 상수에 포함(두 생성기 충돌 방지).
|
||||
|
||||
### 2) NPC 상호작용 — `tools/player/gen-lobby-npc.mjs` → `LobbyNpc` codeblock
|
||||
- **근접+키**: 매 틱(타이머) 로컬 플레이어와의 x거리 측정 → 임계 거리 내면 `!` 프롬프트 노드 활성 + `Up`/`Space` 입력 시 트리거.
|
||||
- **직접 클릭**: NPC 엔티티 클릭/터치 → 동일 트리거. (MSW 월드 엔티티 클릭 API는 구현 시 `mlua_api_retriever`로 확정: 엔티티 TouchEvent vs 스크린 오버레이 버튼 중 검증된 방식.)
|
||||
- 트리거 시 `_EntityService:GetEntityByPath("/common").SlayDeckController:OnLobbyNpcInteract(NpcId)` 호출(경로 기반 크로스 codeblock — CombatMonster 자가등록과 동일 패턴).
|
||||
- 한 번에 하나만 상호작용(다른 패널 열려 있으면 무시).
|
||||
|
||||
### 3) 이동·공격 잠금 해제 (로비 맵 한정) — `LobbyMobility` codeblock
|
||||
- **`map/lobby.map`에만** 주입(전투맵 PlayerLock/전역 freeze는 불변).
|
||||
- OnBeginPlay 런타임 복원: 로컬 플레이어 `MovementComponent`(InputSpeed/JumpForce) 정상값, `PlayerController.Enable=true`, `FixedLookAt=false`.
|
||||
- 공격 입력(키/클릭) → 기존 `PlayerAttackMotion()`(코스메틱) 바인딩. **필드 타격 없음**.
|
||||
- 전투맵 텔레포트 시 모델 기본값(speed=0)+PlayerLock 재적용 → **"로비맵에서만"을 구조적으로 보장**.
|
||||
- 런타임 이동/공격 복원 정확한 API는 구현 단계에서 `mlua_api_retriever`로 확정.
|
||||
- 생성기 배치는 `gen-lobby-npc.mjs`에 함께 둘지 별도 `gen-lobby-unlock.mjs`로 분리할지는 계획에서 결정(둘 다 lobby 맵 전용 codeblock).
|
||||
|
||||
### 4) 흐름 통합 — `tools/deck/gen-slaydeck.mjs`
|
||||
- **OnBeginPlay**: `ShowLobby()`(UI) → **로비 맵 텔레포트** + 경량 "lobby" 상태(전투/상점/맵 HUD 숨김).
|
||||
- **EndRun**: 4초 후 `ShowLobby()` → **로비 맵 텔레포트 복귀**.
|
||||
- **OnLobbyNpcInteract(id)** 신규: `run`→`ShowCharacterSelect()`, `codex`→`ShowCodex()`, `shop`→`ShowSoulShop()`, `board`→`ShowBoard()`(전부 기존 메서드 재사용, 패널은 로비 맵 위 팝업).
|
||||
- **제거**: `LobbyHud` 버튼-행 허브 패널 + `BindLobbyButtons`.
|
||||
- **유지**: 영혼 포인트·승천 표시는 화면 모서리 **미니 HUD**(정보 표시 필요). 기능 패널 4종은 NPC 트리거.
|
||||
- 런 시작(`StartRun`/`TeleportToActMap`)·전투 흐름은 불변.
|
||||
|
||||
### 5) 미러/테스트 영향
|
||||
- 이동/공격 해제·NPC 배치는 **전투 규칙도 맵 그래프 생성 알고리즘도 아님** → `sim-balance.mjs`/`rogue-map.mjs` JS 미러 동기화 **불필요**(RULES.md §6은 그 둘만 요구).
|
||||
- 검증(카운트만): `lobby.map` 내 NPC 엔티티 수, 산출물의 `LobbyNpc`/`LobbyMobility`/`OnLobbyNpcInteract` 개수, SectorConfig `map://lobby` 존재. 내용 출력 금지.
|
||||
- 동작 검증: 메이커 플레이테스트.
|
||||
|
||||
## 검증 시나리오 (메이커)
|
||||
1. 월드 시작 → **로비 맵에 스폰**, 이동 가능, 공격 모션 가능.
|
||||
2. NPC 근접 → `!` 프롬프트 → `Up/Space`로 기능 패널 오픈. 직접 클릭으로도 오픈.
|
||||
3. 4종 각각: 모험가→직업선택→런 시작, 사서→도감, 상인→영혼상점, 안내원→게시판.
|
||||
4. 런 시작 → map01 텔레포트, **이동/공격 잠김**.
|
||||
5. 런 종료(클리어/패배) → **로비 맵 복귀**, 이동/공격 재해제.
|
||||
6. 미니 HUD에 영혼/승천 표시 정상.
|
||||
|
||||
## 리스크
|
||||
- MSW 런타임 이동 재활성 API 가용성 → 계획 단계 `mlua_api_retriever` 검증.
|
||||
- MSW 월드 엔티티 클릭 감지 방식 → 동일 검증(불가 시 근접+키만으로 폴백, 직접 클릭은 스크린 오버레이 버튼으로 구현).
|
||||
- 텔레포트 복귀 좌표/스폰 위치 정합.
|
||||
- 메이커 stale 상태 — git pull 후 로컬 워크스페이스 reload 필수(RULES.md §5).
|
||||
|
||||
## 생성기/파일 변경 요약
|
||||
| 파일 | 변경 |
|
||||
|---|---|
|
||||
| `tools/map/gen-lobby-map.mjs` | **신규** — lobby.map(배경/타일/NPC 엔티티/스폰), SectorConfig 조율 |
|
||||
| `tools/player/gen-lobby-npc.mjs` | **신규** — LobbyNpc 상호작용 codeblock(+LobbyMobility 또는 분리) |
|
||||
| `tools/deck/gen-slaydeck.mjs` | OnBeginPlay/EndRun 로비맵 전환, OnLobbyNpcInteract, 버튼-행 허브 제거, 미니 HUD |
|
||||
| `Global/SectorConfig.config` | map://lobby 등록(생성 산출물) |
|
||||
| `map/lobby.map`, `ui/DefaultGroup.ui`, `*.codeblock` | 재생성 산출물 |
|
||||
File diff suppressed because it is too large
Load Diff
928
map/map01.map
928
map/map01.map
File diff suppressed because it is too large
Load Diff
890
map/map02.map
890
map/map02.map
File diff suppressed because it is too large
Load Diff
634
map/map03.map
634
map/map03.map
@@ -16,7 +16,7 @@
|
||||
{
|
||||
"id": "00000bb8-0000-4000-8000-000000000bb8",
|
||||
"path": "/maps/map03",
|
||||
"componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent,script.PlayerLock,script.MapCamera",
|
||||
"componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent,script.MapCamera,script.PlayerLock",
|
||||
"jsonString": {
|
||||
"name": "map03",
|
||||
"path": "/maps/map03",
|
||||
@@ -1105,11 +1105,11 @@
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "script.PlayerLock",
|
||||
"@type": "script.MapCamera",
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "script.MapCamera",
|
||||
"@type": "script.PlayerLock",
|
||||
"Enable": true
|
||||
}
|
||||
],
|
||||
@@ -6366,7 +6366,7 @@
|
||||
{
|
||||
"id": "00000dac-0000-4000-8000-000000000dac",
|
||||
"path": "/maps/map03/combat_1",
|
||||
"componentNames": "MOD.Core.TransformComponent,MOD.Core.StateAnimationComponent,MOD.Core.SpriteRendererComponent,MOD.Core.DamageSkinSettingComponent,MOD.Core.HitComponent,MOD.Core.DamageSkinSpawnerComponent,MOD.Core.StateComponent,MOD.Core.RigidbodyComponent,MOD.Core.KinematicbodyComponent,MOD.Core.SideviewbodyComponent,MOD.Core.MovementComponent,script.Monster,script.MonsterAttack,script.CombatMonster",
|
||||
"componentNames": "MOD.Core.TransformComponent,MOD.Core.StateAnimationComponent,MOD.Core.SpriteRendererComponent,MOD.Core.RigidbodyComponent,MOD.Core.MovementComponent,MOD.Core.StateComponent,MOD.Core.HitComponent,MOD.Core.DamageSkinSpawnerComponent,script.Monster,script.MonsterAttack,MOD.Core.KinematicbodyComponent,MOD.Core.SideviewbodyComponent,MOD.Core.DamageSkinSettingComponent,script.CombatMonster",
|
||||
"jsonString": {
|
||||
"name": "combat_1",
|
||||
"path": "/maps/map03/combat_1",
|
||||
@@ -6379,12 +6379,12 @@
|
||||
"revision": 2,
|
||||
"origin": {
|
||||
"type": "Model",
|
||||
"entry_id": "StaticMonster",
|
||||
"entry_id": "ChaseMonster",
|
||||
"sub_entity_id": null,
|
||||
"root_entity_id": "00000dac-0000-4000-8000-000000000dac",
|
||||
"replaced_model_id": null
|
||||
},
|
||||
"modelId": "staticmonster",
|
||||
"modelId": "chasemonster",
|
||||
"@components": [
|
||||
{
|
||||
"@type": "MOD.Core.TransformComponent",
|
||||
@@ -6425,38 +6425,6 @@
|
||||
"StartFrameIndex": 0,
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.DamageSkinSettingComponent",
|
||||
"DamageSkinId": {
|
||||
"DataId": "02c22d93421b4038b3c413b3e40b57ec"
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.HitComponent",
|
||||
"BoxSize": {
|
||||
"x": 0.78,
|
||||
"y": 0.86
|
||||
},
|
||||
"ColliderOffset": {
|
||||
"x": 0.03999999,
|
||||
"y": 0.43
|
||||
},
|
||||
"CollisionGroup": {
|
||||
"Id": "8992acd1e8cd45838db6f10a7b41df09"
|
||||
},
|
||||
"IsLegacy": false,
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.DamageSkinSpawnerComponent",
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.StateComponent",
|
||||
"IsLegacy": false,
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.RigidbodyComponent",
|
||||
"MoveVelocity": {
|
||||
@@ -6469,26 +6437,33 @@
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.KinematicbodyComponent",
|
||||
"MoveVelocity": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.SideviewbodyComponent",
|
||||
"MoveVelocity": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.MovementComponent",
|
||||
"Enable": false,
|
||||
"InputSpeed": 0
|
||||
"InputSpeed": 0,
|
||||
"JumpForce": 6,
|
||||
"Enable": false
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.StateComponent",
|
||||
"IsLegacy": false,
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.HitComponent",
|
||||
"BoxSize": {
|
||||
"x": 0.63,
|
||||
"y": 0.58
|
||||
},
|
||||
"ColliderOffset": {
|
||||
"x": 0.0449999869,
|
||||
"y": 0.29
|
||||
},
|
||||
"IsLegacy": false,
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.DamageSkinSpawnerComponent",
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "script.Monster",
|
||||
@@ -6507,10 +6482,33 @@
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.KinematicbodyComponent",
|
||||
"MoveVelocity": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.SideviewbodyComponent",
|
||||
"MoveVelocity": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.DamageSkinSettingComponent",
|
||||
"DamageSkinId": {
|
||||
"DataId": "02c22d93421b4038b3c413b3e40b57ec"
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "script.CombatMonster",
|
||||
"Enable": true,
|
||||
"EnemyId": "green_mushroom",
|
||||
"EnemyId": "pig",
|
||||
"Group": "combat"
|
||||
}
|
||||
],
|
||||
@@ -6520,7 +6518,7 @@
|
||||
{
|
||||
"id": "00000dad-0000-4000-8000-000000000dad",
|
||||
"path": "/maps/map03/combat_2",
|
||||
"componentNames": "MOD.Core.TransformComponent,MOD.Core.StateAnimationComponent,MOD.Core.SpriteRendererComponent,MOD.Core.DamageSkinSettingComponent,MOD.Core.HitComponent,MOD.Core.DamageSkinSpawnerComponent,MOD.Core.StateComponent,MOD.Core.RigidbodyComponent,MOD.Core.KinematicbodyComponent,MOD.Core.SideviewbodyComponent,MOD.Core.MovementComponent,script.Monster,script.MonsterAttack,script.CombatMonster",
|
||||
"componentNames": "MOD.Core.TransformComponent,MOD.Core.StateAnimationComponent,MOD.Core.SpriteRendererComponent,MOD.Core.RigidbodyComponent,MOD.Core.MovementComponent,MOD.Core.StateComponent,MOD.Core.HitComponent,MOD.Core.DamageSkinSpawnerComponent,script.Monster,script.MonsterAttack,MOD.Core.KinematicbodyComponent,MOD.Core.SideviewbodyComponent,MOD.Core.DamageSkinSettingComponent,script.CombatMonster",
|
||||
"jsonString": {
|
||||
"name": "combat_2",
|
||||
"path": "/maps/map03/combat_2",
|
||||
@@ -6533,12 +6531,12 @@
|
||||
"revision": 2,
|
||||
"origin": {
|
||||
"type": "Model",
|
||||
"entry_id": "StaticMonster",
|
||||
"entry_id": "ChaseMonster",
|
||||
"sub_entity_id": null,
|
||||
"root_entity_id": "00000dad-0000-4000-8000-000000000dad",
|
||||
"replaced_model_id": null
|
||||
},
|
||||
"modelId": "staticmonster",
|
||||
"modelId": "chasemonster",
|
||||
"@components": [
|
||||
{
|
||||
"@type": "MOD.Core.TransformComponent",
|
||||
@@ -6579,38 +6577,6 @@
|
||||
"StartFrameIndex": 0,
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.DamageSkinSettingComponent",
|
||||
"DamageSkinId": {
|
||||
"DataId": "02c22d93421b4038b3c413b3e40b57ec"
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.HitComponent",
|
||||
"BoxSize": {
|
||||
"x": 0.78,
|
||||
"y": 0.86
|
||||
},
|
||||
"ColliderOffset": {
|
||||
"x": 0.03999999,
|
||||
"y": 0.43
|
||||
},
|
||||
"CollisionGroup": {
|
||||
"Id": "8992acd1e8cd45838db6f10a7b41df09"
|
||||
},
|
||||
"IsLegacy": false,
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.DamageSkinSpawnerComponent",
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.StateComponent",
|
||||
"IsLegacy": false,
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.RigidbodyComponent",
|
||||
"MoveVelocity": {
|
||||
@@ -6623,26 +6589,33 @@
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.KinematicbodyComponent",
|
||||
"MoveVelocity": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.SideviewbodyComponent",
|
||||
"MoveVelocity": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.MovementComponent",
|
||||
"Enable": false,
|
||||
"InputSpeed": 0
|
||||
"InputSpeed": 0,
|
||||
"JumpForce": 6,
|
||||
"Enable": false
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.StateComponent",
|
||||
"IsLegacy": false,
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.HitComponent",
|
||||
"BoxSize": {
|
||||
"x": 0.63,
|
||||
"y": 0.58
|
||||
},
|
||||
"ColliderOffset": {
|
||||
"x": 0.0449999869,
|
||||
"y": 0.29
|
||||
},
|
||||
"IsLegacy": false,
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.DamageSkinSpawnerComponent",
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "script.Monster",
|
||||
@@ -6661,10 +6634,33 @@
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.KinematicbodyComponent",
|
||||
"MoveVelocity": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.SideviewbodyComponent",
|
||||
"MoveVelocity": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.DamageSkinSettingComponent",
|
||||
"DamageSkinId": {
|
||||
"DataId": "02c22d93421b4038b3c413b3e40b57ec"
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "script.CombatMonster",
|
||||
"Enable": true,
|
||||
"EnemyId": "blue_mushroom",
|
||||
"EnemyId": "red_snail",
|
||||
"Group": "combat"
|
||||
}
|
||||
],
|
||||
@@ -6674,7 +6670,7 @@
|
||||
{
|
||||
"id": "00000dae-0000-4000-8000-000000000dae",
|
||||
"path": "/maps/map03/combat_3",
|
||||
"componentNames": "MOD.Core.TransformComponent,MOD.Core.StateAnimationComponent,MOD.Core.SpriteRendererComponent,MOD.Core.DamageSkinSettingComponent,MOD.Core.HitComponent,MOD.Core.DamageSkinSpawnerComponent,MOD.Core.StateComponent,MOD.Core.RigidbodyComponent,MOD.Core.KinematicbodyComponent,MOD.Core.SideviewbodyComponent,MOD.Core.MovementComponent,script.Monster,script.MonsterAttack,script.CombatMonster",
|
||||
"componentNames": "MOD.Core.TransformComponent,MOD.Core.StateAnimationComponent,MOD.Core.SpriteRendererComponent,MOD.Core.RigidbodyComponent,MOD.Core.MovementComponent,MOD.Core.StateComponent,MOD.Core.HitComponent,MOD.Core.DamageSkinSpawnerComponent,script.Monster,script.MonsterAttack,MOD.Core.KinematicbodyComponent,MOD.Core.SideviewbodyComponent,MOD.Core.DamageSkinSettingComponent,script.CombatMonster",
|
||||
"jsonString": {
|
||||
"name": "combat_3",
|
||||
"path": "/maps/map03/combat_3",
|
||||
@@ -6687,12 +6683,12 @@
|
||||
"revision": 2,
|
||||
"origin": {
|
||||
"type": "Model",
|
||||
"entry_id": "StaticMonster",
|
||||
"entry_id": "ChaseMonster",
|
||||
"sub_entity_id": null,
|
||||
"root_entity_id": "00000dae-0000-4000-8000-000000000dae",
|
||||
"replaced_model_id": null
|
||||
},
|
||||
"modelId": "staticmonster",
|
||||
"modelId": "chasemonster",
|
||||
"@components": [
|
||||
{
|
||||
"@type": "MOD.Core.TransformComponent",
|
||||
@@ -6733,38 +6729,6 @@
|
||||
"StartFrameIndex": 0,
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.DamageSkinSettingComponent",
|
||||
"DamageSkinId": {
|
||||
"DataId": "02c22d93421b4038b3c413b3e40b57ec"
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.HitComponent",
|
||||
"BoxSize": {
|
||||
"x": 0.78,
|
||||
"y": 0.86
|
||||
},
|
||||
"ColliderOffset": {
|
||||
"x": 0.03999999,
|
||||
"y": 0.43
|
||||
},
|
||||
"CollisionGroup": {
|
||||
"Id": "8992acd1e8cd45838db6f10a7b41df09"
|
||||
},
|
||||
"IsLegacy": false,
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.DamageSkinSpawnerComponent",
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.StateComponent",
|
||||
"IsLegacy": false,
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.RigidbodyComponent",
|
||||
"MoveVelocity": {
|
||||
@@ -6777,26 +6741,33 @@
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.KinematicbodyComponent",
|
||||
"MoveVelocity": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.SideviewbodyComponent",
|
||||
"MoveVelocity": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.MovementComponent",
|
||||
"Enable": false,
|
||||
"InputSpeed": 0
|
||||
"InputSpeed": 0,
|
||||
"JumpForce": 6,
|
||||
"Enable": false
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.StateComponent",
|
||||
"IsLegacy": false,
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.HitComponent",
|
||||
"BoxSize": {
|
||||
"x": 0.63,
|
||||
"y": 0.58
|
||||
},
|
||||
"ColliderOffset": {
|
||||
"x": 0.0449999869,
|
||||
"y": 0.29
|
||||
},
|
||||
"IsLegacy": false,
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.DamageSkinSpawnerComponent",
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "script.Monster",
|
||||
@@ -6815,6 +6786,29 @@
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.KinematicbodyComponent",
|
||||
"MoveVelocity": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.SideviewbodyComponent",
|
||||
"MoveVelocity": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.DamageSkinSettingComponent",
|
||||
"DamageSkinId": {
|
||||
"DataId": "02c22d93421b4038b3c413b3e40b57ec"
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "script.CombatMonster",
|
||||
"Enable": true,
|
||||
@@ -6828,7 +6822,7 @@
|
||||
{
|
||||
"id": "00000daf-0000-4000-8000-000000000daf",
|
||||
"path": "/maps/map03/elite_4",
|
||||
"componentNames": "MOD.Core.TransformComponent,MOD.Core.StateAnimationComponent,MOD.Core.SpriteRendererComponent,MOD.Core.DamageSkinSettingComponent,MOD.Core.HitComponent,MOD.Core.DamageSkinSpawnerComponent,MOD.Core.StateComponent,MOD.Core.RigidbodyComponent,MOD.Core.KinematicbodyComponent,MOD.Core.SideviewbodyComponent,MOD.Core.MovementComponent,script.Monster,script.MonsterAttack,script.CombatMonster",
|
||||
"componentNames": "MOD.Core.TransformComponent,MOD.Core.StateAnimationComponent,MOD.Core.SpriteRendererComponent,MOD.Core.RigidbodyComponent,MOD.Core.MovementComponent,MOD.Core.StateComponent,MOD.Core.HitComponent,MOD.Core.DamageSkinSpawnerComponent,script.Monster,script.MonsterAttack,MOD.Core.KinematicbodyComponent,MOD.Core.SideviewbodyComponent,MOD.Core.DamageSkinSettingComponent,script.CombatMonster",
|
||||
"jsonString": {
|
||||
"name": "elite_4",
|
||||
"path": "/maps/map03/elite_4",
|
||||
@@ -6841,12 +6835,12 @@
|
||||
"revision": 2,
|
||||
"origin": {
|
||||
"type": "Model",
|
||||
"entry_id": "StaticMonster",
|
||||
"entry_id": "ChaseMonster",
|
||||
"sub_entity_id": null,
|
||||
"root_entity_id": "00000daf-0000-4000-8000-000000000daf",
|
||||
"replaced_model_id": null
|
||||
},
|
||||
"modelId": "staticmonster",
|
||||
"modelId": "chasemonster",
|
||||
"@components": [
|
||||
{
|
||||
"@type": "MOD.Core.TransformComponent",
|
||||
@@ -6887,38 +6881,6 @@
|
||||
"StartFrameIndex": 0,
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.DamageSkinSettingComponent",
|
||||
"DamageSkinId": {
|
||||
"DataId": "02c22d93421b4038b3c413b3e40b57ec"
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.HitComponent",
|
||||
"BoxSize": {
|
||||
"x": 0.78,
|
||||
"y": 0.86
|
||||
},
|
||||
"ColliderOffset": {
|
||||
"x": 0.03999999,
|
||||
"y": 0.43
|
||||
},
|
||||
"CollisionGroup": {
|
||||
"Id": "8992acd1e8cd45838db6f10a7b41df09"
|
||||
},
|
||||
"IsLegacy": false,
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.DamageSkinSpawnerComponent",
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.StateComponent",
|
||||
"IsLegacy": false,
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.RigidbodyComponent",
|
||||
"MoveVelocity": {
|
||||
@@ -6931,26 +6893,33 @@
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.KinematicbodyComponent",
|
||||
"MoveVelocity": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.SideviewbodyComponent",
|
||||
"MoveVelocity": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.MovementComponent",
|
||||
"Enable": false,
|
||||
"InputSpeed": 0
|
||||
"InputSpeed": 0,
|
||||
"JumpForce": 6,
|
||||
"Enable": false
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.StateComponent",
|
||||
"IsLegacy": false,
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.HitComponent",
|
||||
"BoxSize": {
|
||||
"x": 0.63,
|
||||
"y": 0.58
|
||||
},
|
||||
"ColliderOffset": {
|
||||
"x": 0.0449999869,
|
||||
"y": 0.29
|
||||
},
|
||||
"IsLegacy": false,
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.DamageSkinSpawnerComponent",
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "script.Monster",
|
||||
@@ -6969,6 +6938,29 @@
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.KinematicbodyComponent",
|
||||
"MoveVelocity": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.SideviewbodyComponent",
|
||||
"MoveVelocity": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.DamageSkinSettingComponent",
|
||||
"DamageSkinId": {
|
||||
"DataId": "02c22d93421b4038b3c413b3e40b57ec"
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "script.CombatMonster",
|
||||
"Enable": true,
|
||||
@@ -6982,7 +6974,7 @@
|
||||
{
|
||||
"id": "00000db0-0000-4000-8000-000000000db0",
|
||||
"path": "/maps/map03/elite_5",
|
||||
"componentNames": "MOD.Core.TransformComponent,MOD.Core.StateAnimationComponent,MOD.Core.SpriteRendererComponent,MOD.Core.DamageSkinSettingComponent,MOD.Core.HitComponent,MOD.Core.DamageSkinSpawnerComponent,MOD.Core.StateComponent,MOD.Core.RigidbodyComponent,MOD.Core.KinematicbodyComponent,MOD.Core.SideviewbodyComponent,MOD.Core.MovementComponent,script.Monster,script.MonsterAttack,script.CombatMonster",
|
||||
"componentNames": "MOD.Core.TransformComponent,MOD.Core.StateAnimationComponent,MOD.Core.SpriteRendererComponent,MOD.Core.RigidbodyComponent,MOD.Core.MovementComponent,MOD.Core.StateComponent,MOD.Core.HitComponent,MOD.Core.DamageSkinSpawnerComponent,script.Monster,script.MonsterAttack,MOD.Core.KinematicbodyComponent,MOD.Core.SideviewbodyComponent,MOD.Core.DamageSkinSettingComponent,script.CombatMonster",
|
||||
"jsonString": {
|
||||
"name": "elite_5",
|
||||
"path": "/maps/map03/elite_5",
|
||||
@@ -6995,12 +6987,12 @@
|
||||
"revision": 2,
|
||||
"origin": {
|
||||
"type": "Model",
|
||||
"entry_id": "StaticMonster",
|
||||
"entry_id": "ChaseMonster",
|
||||
"sub_entity_id": null,
|
||||
"root_entity_id": "00000db0-0000-4000-8000-000000000db0",
|
||||
"replaced_model_id": null
|
||||
},
|
||||
"modelId": "staticmonster",
|
||||
"modelId": "chasemonster",
|
||||
"@components": [
|
||||
{
|
||||
"@type": "MOD.Core.TransformComponent",
|
||||
@@ -7041,38 +7033,6 @@
|
||||
"StartFrameIndex": 0,
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.DamageSkinSettingComponent",
|
||||
"DamageSkinId": {
|
||||
"DataId": "02c22d93421b4038b3c413b3e40b57ec"
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.HitComponent",
|
||||
"BoxSize": {
|
||||
"x": 0.78,
|
||||
"y": 0.86
|
||||
},
|
||||
"ColliderOffset": {
|
||||
"x": 0.03999999,
|
||||
"y": 0.43
|
||||
},
|
||||
"CollisionGroup": {
|
||||
"Id": "8992acd1e8cd45838db6f10a7b41df09"
|
||||
},
|
||||
"IsLegacy": false,
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.DamageSkinSpawnerComponent",
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.StateComponent",
|
||||
"IsLegacy": false,
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.RigidbodyComponent",
|
||||
"MoveVelocity": {
|
||||
@@ -7085,26 +7045,33 @@
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.KinematicbodyComponent",
|
||||
"MoveVelocity": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.SideviewbodyComponent",
|
||||
"MoveVelocity": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.MovementComponent",
|
||||
"Enable": false,
|
||||
"InputSpeed": 0
|
||||
"InputSpeed": 0,
|
||||
"JumpForce": 6,
|
||||
"Enable": false
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.StateComponent",
|
||||
"IsLegacy": false,
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.HitComponent",
|
||||
"BoxSize": {
|
||||
"x": 0.63,
|
||||
"y": 0.58
|
||||
},
|
||||
"ColliderOffset": {
|
||||
"x": 0.0449999869,
|
||||
"y": 0.29
|
||||
},
|
||||
"IsLegacy": false,
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.DamageSkinSpawnerComponent",
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "script.Monster",
|
||||
@@ -7123,6 +7090,29 @@
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.KinematicbodyComponent",
|
||||
"MoveVelocity": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.SideviewbodyComponent",
|
||||
"MoveVelocity": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.DamageSkinSettingComponent",
|
||||
"DamageSkinId": {
|
||||
"DataId": "02c22d93421b4038b3c413b3e40b57ec"
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "script.CombatMonster",
|
||||
"Enable": true,
|
||||
@@ -7136,7 +7126,7 @@
|
||||
{
|
||||
"id": "00000db1-0000-4000-8000-000000000db1",
|
||||
"path": "/maps/map03/boss_6",
|
||||
"componentNames": "MOD.Core.TransformComponent,MOD.Core.StateAnimationComponent,MOD.Core.SpriteRendererComponent,MOD.Core.DamageSkinSettingComponent,MOD.Core.HitComponent,MOD.Core.DamageSkinSpawnerComponent,MOD.Core.StateComponent,MOD.Core.RigidbodyComponent,MOD.Core.KinematicbodyComponent,MOD.Core.SideviewbodyComponent,MOD.Core.MovementComponent,script.Monster,script.MonsterAttack,script.CombatMonster",
|
||||
"componentNames": "MOD.Core.TransformComponent,MOD.Core.StateAnimationComponent,MOD.Core.SpriteRendererComponent,MOD.Core.RigidbodyComponent,MOD.Core.MovementComponent,MOD.Core.StateComponent,MOD.Core.HitComponent,MOD.Core.DamageSkinSpawnerComponent,script.Monster,script.MonsterAttack,MOD.Core.KinematicbodyComponent,MOD.Core.SideviewbodyComponent,MOD.Core.DamageSkinSettingComponent,script.CombatMonster",
|
||||
"jsonString": {
|
||||
"name": "boss_6",
|
||||
"path": "/maps/map03/boss_6",
|
||||
@@ -7149,12 +7139,12 @@
|
||||
"revision": 2,
|
||||
"origin": {
|
||||
"type": "Model",
|
||||
"entry_id": "StaticMonster",
|
||||
"entry_id": "ChaseMonster",
|
||||
"sub_entity_id": null,
|
||||
"root_entity_id": "00000db1-0000-4000-8000-000000000db1",
|
||||
"replaced_model_id": null
|
||||
},
|
||||
"modelId": "staticmonster",
|
||||
"modelId": "chasemonster",
|
||||
"@components": [
|
||||
{
|
||||
"@type": "MOD.Core.TransformComponent",
|
||||
@@ -7195,38 +7185,6 @@
|
||||
"StartFrameIndex": 0,
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.DamageSkinSettingComponent",
|
||||
"DamageSkinId": {
|
||||
"DataId": "02c22d93421b4038b3c413b3e40b57ec"
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.HitComponent",
|
||||
"BoxSize": {
|
||||
"x": 0.78,
|
||||
"y": 0.86
|
||||
},
|
||||
"ColliderOffset": {
|
||||
"x": 0.03999999,
|
||||
"y": 0.43
|
||||
},
|
||||
"CollisionGroup": {
|
||||
"Id": "8992acd1e8cd45838db6f10a7b41df09"
|
||||
},
|
||||
"IsLegacy": false,
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.DamageSkinSpawnerComponent",
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.StateComponent",
|
||||
"IsLegacy": false,
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.RigidbodyComponent",
|
||||
"MoveVelocity": {
|
||||
@@ -7239,26 +7197,33 @@
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.KinematicbodyComponent",
|
||||
"MoveVelocity": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.SideviewbodyComponent",
|
||||
"MoveVelocity": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.MovementComponent",
|
||||
"Enable": false,
|
||||
"InputSpeed": 0
|
||||
"InputSpeed": 0,
|
||||
"JumpForce": 6,
|
||||
"Enable": false
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.StateComponent",
|
||||
"IsLegacy": false,
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.HitComponent",
|
||||
"BoxSize": {
|
||||
"x": 0.63,
|
||||
"y": 0.58
|
||||
},
|
||||
"ColliderOffset": {
|
||||
"x": 0.0449999869,
|
||||
"y": 0.29
|
||||
},
|
||||
"IsLegacy": false,
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.DamageSkinSpawnerComponent",
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "script.Monster",
|
||||
@@ -7277,6 +7242,29 @@
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.KinematicbodyComponent",
|
||||
"MoveVelocity": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.SideviewbodyComponent",
|
||||
"MoveVelocity": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.DamageSkinSettingComponent",
|
||||
"DamageSkinId": {
|
||||
"DataId": "02c22d93421b4038b3c413b3e40b57ec"
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "script.CombatMonster",
|
||||
"Enable": true,
|
||||
|
||||
636
map/map04.map
636
map/map04.map
@@ -16,7 +16,7 @@
|
||||
{
|
||||
"id": "00000fa0-0000-4000-8000-000000000fa0",
|
||||
"path": "/maps/map04",
|
||||
"componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent,script.PlayerLock,script.MapCamera",
|
||||
"componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent,script.MapCamera,script.PlayerLock",
|
||||
"jsonString": {
|
||||
"name": "map04",
|
||||
"path": "/maps/map04",
|
||||
@@ -1105,11 +1105,11 @@
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "script.PlayerLock",
|
||||
"@type": "script.MapCamera",
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "script.MapCamera",
|
||||
"@type": "script.PlayerLock",
|
||||
"Enable": true
|
||||
}
|
||||
],
|
||||
@@ -6366,7 +6366,7 @@
|
||||
{
|
||||
"id": "00001194-0000-4000-8000-000000001194",
|
||||
"path": "/maps/map04/combat_1",
|
||||
"componentNames": "MOD.Core.TransformComponent,MOD.Core.StateAnimationComponent,MOD.Core.SpriteRendererComponent,MOD.Core.DamageSkinSettingComponent,MOD.Core.HitComponent,MOD.Core.DamageSkinSpawnerComponent,MOD.Core.StateComponent,MOD.Core.RigidbodyComponent,MOD.Core.KinematicbodyComponent,MOD.Core.SideviewbodyComponent,MOD.Core.MovementComponent,script.Monster,script.MonsterAttack,script.CombatMonster",
|
||||
"componentNames": "MOD.Core.TransformComponent,MOD.Core.StateAnimationComponent,MOD.Core.SpriteRendererComponent,MOD.Core.RigidbodyComponent,MOD.Core.MovementComponent,MOD.Core.StateComponent,MOD.Core.HitComponent,MOD.Core.DamageSkinSpawnerComponent,script.Monster,script.MonsterAttack,MOD.Core.KinematicbodyComponent,MOD.Core.SideviewbodyComponent,MOD.Core.DamageSkinSettingComponent,script.CombatMonster",
|
||||
"jsonString": {
|
||||
"name": "combat_1",
|
||||
"path": "/maps/map04/combat_1",
|
||||
@@ -6379,12 +6379,12 @@
|
||||
"revision": 2,
|
||||
"origin": {
|
||||
"type": "Model",
|
||||
"entry_id": "StaticMonster",
|
||||
"entry_id": "ChaseMonster",
|
||||
"sub_entity_id": null,
|
||||
"root_entity_id": "00001194-0000-4000-8000-000000001194",
|
||||
"replaced_model_id": null
|
||||
},
|
||||
"modelId": "staticmonster",
|
||||
"modelId": "chasemonster",
|
||||
"@components": [
|
||||
{
|
||||
"@type": "MOD.Core.TransformComponent",
|
||||
@@ -6425,38 +6425,6 @@
|
||||
"StartFrameIndex": 0,
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.DamageSkinSettingComponent",
|
||||
"DamageSkinId": {
|
||||
"DataId": "02c22d93421b4038b3c413b3e40b57ec"
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.HitComponent",
|
||||
"BoxSize": {
|
||||
"x": 0.78,
|
||||
"y": 0.86
|
||||
},
|
||||
"ColliderOffset": {
|
||||
"x": 0.03999999,
|
||||
"y": 0.43
|
||||
},
|
||||
"CollisionGroup": {
|
||||
"Id": "8992acd1e8cd45838db6f10a7b41df09"
|
||||
},
|
||||
"IsLegacy": false,
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.DamageSkinSpawnerComponent",
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.StateComponent",
|
||||
"IsLegacy": false,
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.RigidbodyComponent",
|
||||
"MoveVelocity": {
|
||||
@@ -6469,26 +6437,33 @@
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.KinematicbodyComponent",
|
||||
"MoveVelocity": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.SideviewbodyComponent",
|
||||
"MoveVelocity": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.MovementComponent",
|
||||
"Enable": false,
|
||||
"InputSpeed": 0
|
||||
"InputSpeed": 0,
|
||||
"JumpForce": 6,
|
||||
"Enable": false
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.StateComponent",
|
||||
"IsLegacy": false,
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.HitComponent",
|
||||
"BoxSize": {
|
||||
"x": 0.63,
|
||||
"y": 0.58
|
||||
},
|
||||
"ColliderOffset": {
|
||||
"x": 0.0449999869,
|
||||
"y": 0.29
|
||||
},
|
||||
"IsLegacy": false,
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.DamageSkinSpawnerComponent",
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "script.Monster",
|
||||
@@ -6507,10 +6482,33 @@
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.KinematicbodyComponent",
|
||||
"MoveVelocity": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.SideviewbodyComponent",
|
||||
"MoveVelocity": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.DamageSkinSettingComponent",
|
||||
"DamageSkinId": {
|
||||
"DataId": "02c22d93421b4038b3c413b3e40b57ec"
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "script.CombatMonster",
|
||||
"Enable": true,
|
||||
"EnemyId": "pig",
|
||||
"EnemyId": "blue_mushroom",
|
||||
"Group": "combat"
|
||||
}
|
||||
],
|
||||
@@ -6520,7 +6518,7 @@
|
||||
{
|
||||
"id": "00001195-0000-4000-8000-000000001195",
|
||||
"path": "/maps/map04/combat_2",
|
||||
"componentNames": "MOD.Core.TransformComponent,MOD.Core.StateAnimationComponent,MOD.Core.SpriteRendererComponent,MOD.Core.DamageSkinSettingComponent,MOD.Core.HitComponent,MOD.Core.DamageSkinSpawnerComponent,MOD.Core.StateComponent,MOD.Core.RigidbodyComponent,MOD.Core.KinematicbodyComponent,MOD.Core.SideviewbodyComponent,MOD.Core.MovementComponent,script.Monster,script.MonsterAttack,script.CombatMonster",
|
||||
"componentNames": "MOD.Core.TransformComponent,MOD.Core.StateAnimationComponent,MOD.Core.SpriteRendererComponent,MOD.Core.RigidbodyComponent,MOD.Core.MovementComponent,MOD.Core.StateComponent,MOD.Core.HitComponent,MOD.Core.DamageSkinSpawnerComponent,script.Monster,script.MonsterAttack,MOD.Core.KinematicbodyComponent,MOD.Core.SideviewbodyComponent,MOD.Core.DamageSkinSettingComponent,script.CombatMonster",
|
||||
"jsonString": {
|
||||
"name": "combat_2",
|
||||
"path": "/maps/map04/combat_2",
|
||||
@@ -6533,12 +6531,12 @@
|
||||
"revision": 2,
|
||||
"origin": {
|
||||
"type": "Model",
|
||||
"entry_id": "StaticMonster",
|
||||
"entry_id": "ChaseMonster",
|
||||
"sub_entity_id": null,
|
||||
"root_entity_id": "00001195-0000-4000-8000-000000001195",
|
||||
"replaced_model_id": null
|
||||
},
|
||||
"modelId": "staticmonster",
|
||||
"modelId": "chasemonster",
|
||||
"@components": [
|
||||
{
|
||||
"@type": "MOD.Core.TransformComponent",
|
||||
@@ -6579,38 +6577,6 @@
|
||||
"StartFrameIndex": 0,
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.DamageSkinSettingComponent",
|
||||
"DamageSkinId": {
|
||||
"DataId": "02c22d93421b4038b3c413b3e40b57ec"
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.HitComponent",
|
||||
"BoxSize": {
|
||||
"x": 0.78,
|
||||
"y": 0.86
|
||||
},
|
||||
"ColliderOffset": {
|
||||
"x": 0.03999999,
|
||||
"y": 0.43
|
||||
},
|
||||
"CollisionGroup": {
|
||||
"Id": "8992acd1e8cd45838db6f10a7b41df09"
|
||||
},
|
||||
"IsLegacy": false,
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.DamageSkinSpawnerComponent",
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.StateComponent",
|
||||
"IsLegacy": false,
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.RigidbodyComponent",
|
||||
"MoveVelocity": {
|
||||
@@ -6623,26 +6589,33 @@
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.KinematicbodyComponent",
|
||||
"MoveVelocity": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.SideviewbodyComponent",
|
||||
"MoveVelocity": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.MovementComponent",
|
||||
"Enable": false,
|
||||
"InputSpeed": 0
|
||||
"InputSpeed": 0,
|
||||
"JumpForce": 6,
|
||||
"Enable": false
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.StateComponent",
|
||||
"IsLegacy": false,
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.HitComponent",
|
||||
"BoxSize": {
|
||||
"x": 0.63,
|
||||
"y": 0.58
|
||||
},
|
||||
"ColliderOffset": {
|
||||
"x": 0.0449999869,
|
||||
"y": 0.29
|
||||
},
|
||||
"IsLegacy": false,
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.DamageSkinSpawnerComponent",
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "script.Monster",
|
||||
@@ -6661,10 +6634,33 @@
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.KinematicbodyComponent",
|
||||
"MoveVelocity": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.SideviewbodyComponent",
|
||||
"MoveVelocity": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.DamageSkinSettingComponent",
|
||||
"DamageSkinId": {
|
||||
"DataId": "02c22d93421b4038b3c413b3e40b57ec"
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "script.CombatMonster",
|
||||
"Enable": true,
|
||||
"EnemyId": "blue_mushroom",
|
||||
"EnemyId": "stump",
|
||||
"Group": "combat"
|
||||
}
|
||||
],
|
||||
@@ -6674,7 +6670,7 @@
|
||||
{
|
||||
"id": "00001196-0000-4000-8000-000000001196",
|
||||
"path": "/maps/map04/combat_3",
|
||||
"componentNames": "MOD.Core.TransformComponent,MOD.Core.StateAnimationComponent,MOD.Core.SpriteRendererComponent,MOD.Core.DamageSkinSettingComponent,MOD.Core.HitComponent,MOD.Core.DamageSkinSpawnerComponent,MOD.Core.StateComponent,MOD.Core.RigidbodyComponent,MOD.Core.KinematicbodyComponent,MOD.Core.SideviewbodyComponent,MOD.Core.MovementComponent,script.Monster,script.MonsterAttack,script.CombatMonster",
|
||||
"componentNames": "MOD.Core.TransformComponent,MOD.Core.StateAnimationComponent,MOD.Core.SpriteRendererComponent,MOD.Core.RigidbodyComponent,MOD.Core.MovementComponent,MOD.Core.StateComponent,MOD.Core.HitComponent,MOD.Core.DamageSkinSpawnerComponent,script.Monster,script.MonsterAttack,MOD.Core.KinematicbodyComponent,MOD.Core.SideviewbodyComponent,MOD.Core.DamageSkinSettingComponent,script.CombatMonster",
|
||||
"jsonString": {
|
||||
"name": "combat_3",
|
||||
"path": "/maps/map04/combat_3",
|
||||
@@ -6687,12 +6683,12 @@
|
||||
"revision": 2,
|
||||
"origin": {
|
||||
"type": "Model",
|
||||
"entry_id": "StaticMonster",
|
||||
"entry_id": "ChaseMonster",
|
||||
"sub_entity_id": null,
|
||||
"root_entity_id": "00001196-0000-4000-8000-000000001196",
|
||||
"replaced_model_id": null
|
||||
},
|
||||
"modelId": "staticmonster",
|
||||
"modelId": "chasemonster",
|
||||
"@components": [
|
||||
{
|
||||
"@type": "MOD.Core.TransformComponent",
|
||||
@@ -6733,38 +6729,6 @@
|
||||
"StartFrameIndex": 0,
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.DamageSkinSettingComponent",
|
||||
"DamageSkinId": {
|
||||
"DataId": "02c22d93421b4038b3c413b3e40b57ec"
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.HitComponent",
|
||||
"BoxSize": {
|
||||
"x": 0.78,
|
||||
"y": 0.86
|
||||
},
|
||||
"ColliderOffset": {
|
||||
"x": 0.03999999,
|
||||
"y": 0.43
|
||||
},
|
||||
"CollisionGroup": {
|
||||
"Id": "8992acd1e8cd45838db6f10a7b41df09"
|
||||
},
|
||||
"IsLegacy": false,
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.DamageSkinSpawnerComponent",
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.StateComponent",
|
||||
"IsLegacy": false,
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.RigidbodyComponent",
|
||||
"MoveVelocity": {
|
||||
@@ -6777,26 +6741,33 @@
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.KinematicbodyComponent",
|
||||
"MoveVelocity": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.SideviewbodyComponent",
|
||||
"MoveVelocity": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.MovementComponent",
|
||||
"Enable": false,
|
||||
"InputSpeed": 0
|
||||
"InputSpeed": 0,
|
||||
"JumpForce": 6,
|
||||
"Enable": false
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.StateComponent",
|
||||
"IsLegacy": false,
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.HitComponent",
|
||||
"BoxSize": {
|
||||
"x": 0.63,
|
||||
"y": 0.58
|
||||
},
|
||||
"ColliderOffset": {
|
||||
"x": 0.0449999869,
|
||||
"y": 0.29
|
||||
},
|
||||
"IsLegacy": false,
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.DamageSkinSpawnerComponent",
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "script.Monster",
|
||||
@@ -6815,10 +6786,33 @@
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.KinematicbodyComponent",
|
||||
"MoveVelocity": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.SideviewbodyComponent",
|
||||
"MoveVelocity": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.DamageSkinSettingComponent",
|
||||
"DamageSkinId": {
|
||||
"DataId": "02c22d93421b4038b3c413b3e40b57ec"
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "script.CombatMonster",
|
||||
"Enable": true,
|
||||
"EnemyId": "orange_mushroom",
|
||||
"EnemyId": "green_mushroom",
|
||||
"Group": "combat"
|
||||
}
|
||||
],
|
||||
@@ -6828,7 +6822,7 @@
|
||||
{
|
||||
"id": "00001197-0000-4000-8000-000000001197",
|
||||
"path": "/maps/map04/elite_4",
|
||||
"componentNames": "MOD.Core.TransformComponent,MOD.Core.StateAnimationComponent,MOD.Core.SpriteRendererComponent,MOD.Core.DamageSkinSettingComponent,MOD.Core.HitComponent,MOD.Core.DamageSkinSpawnerComponent,MOD.Core.StateComponent,MOD.Core.RigidbodyComponent,MOD.Core.KinematicbodyComponent,MOD.Core.SideviewbodyComponent,MOD.Core.MovementComponent,script.Monster,script.MonsterAttack,script.CombatMonster",
|
||||
"componentNames": "MOD.Core.TransformComponent,MOD.Core.StateAnimationComponent,MOD.Core.SpriteRendererComponent,MOD.Core.RigidbodyComponent,MOD.Core.MovementComponent,MOD.Core.StateComponent,MOD.Core.HitComponent,MOD.Core.DamageSkinSpawnerComponent,script.Monster,script.MonsterAttack,MOD.Core.KinematicbodyComponent,MOD.Core.SideviewbodyComponent,MOD.Core.DamageSkinSettingComponent,script.CombatMonster",
|
||||
"jsonString": {
|
||||
"name": "elite_4",
|
||||
"path": "/maps/map04/elite_4",
|
||||
@@ -6841,12 +6835,12 @@
|
||||
"revision": 2,
|
||||
"origin": {
|
||||
"type": "Model",
|
||||
"entry_id": "StaticMonster",
|
||||
"entry_id": "ChaseMonster",
|
||||
"sub_entity_id": null,
|
||||
"root_entity_id": "00001197-0000-4000-8000-000000001197",
|
||||
"replaced_model_id": null
|
||||
},
|
||||
"modelId": "staticmonster",
|
||||
"modelId": "chasemonster",
|
||||
"@components": [
|
||||
{
|
||||
"@type": "MOD.Core.TransformComponent",
|
||||
@@ -6887,38 +6881,6 @@
|
||||
"StartFrameIndex": 0,
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.DamageSkinSettingComponent",
|
||||
"DamageSkinId": {
|
||||
"DataId": "02c22d93421b4038b3c413b3e40b57ec"
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.HitComponent",
|
||||
"BoxSize": {
|
||||
"x": 0.78,
|
||||
"y": 0.86
|
||||
},
|
||||
"ColliderOffset": {
|
||||
"x": 0.03999999,
|
||||
"y": 0.43
|
||||
},
|
||||
"CollisionGroup": {
|
||||
"Id": "8992acd1e8cd45838db6f10a7b41df09"
|
||||
},
|
||||
"IsLegacy": false,
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.DamageSkinSpawnerComponent",
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.StateComponent",
|
||||
"IsLegacy": false,
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.RigidbodyComponent",
|
||||
"MoveVelocity": {
|
||||
@@ -6931,26 +6893,33 @@
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.KinematicbodyComponent",
|
||||
"MoveVelocity": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.SideviewbodyComponent",
|
||||
"MoveVelocity": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.MovementComponent",
|
||||
"Enable": false,
|
||||
"InputSpeed": 0
|
||||
"InputSpeed": 0,
|
||||
"JumpForce": 6,
|
||||
"Enable": false
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.StateComponent",
|
||||
"IsLegacy": false,
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.HitComponent",
|
||||
"BoxSize": {
|
||||
"x": 0.63,
|
||||
"y": 0.58
|
||||
},
|
||||
"ColliderOffset": {
|
||||
"x": 0.0449999869,
|
||||
"y": 0.29
|
||||
},
|
||||
"IsLegacy": false,
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.DamageSkinSpawnerComponent",
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "script.Monster",
|
||||
@@ -6969,6 +6938,29 @@
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.KinematicbodyComponent",
|
||||
"MoveVelocity": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.SideviewbodyComponent",
|
||||
"MoveVelocity": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.DamageSkinSettingComponent",
|
||||
"DamageSkinId": {
|
||||
"DataId": "02c22d93421b4038b3c413b3e40b57ec"
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "script.CombatMonster",
|
||||
"Enable": true,
|
||||
@@ -6982,7 +6974,7 @@
|
||||
{
|
||||
"id": "00001198-0000-4000-8000-000000001198",
|
||||
"path": "/maps/map04/elite_5",
|
||||
"componentNames": "MOD.Core.TransformComponent,MOD.Core.StateAnimationComponent,MOD.Core.SpriteRendererComponent,MOD.Core.DamageSkinSettingComponent,MOD.Core.HitComponent,MOD.Core.DamageSkinSpawnerComponent,MOD.Core.StateComponent,MOD.Core.RigidbodyComponent,MOD.Core.KinematicbodyComponent,MOD.Core.SideviewbodyComponent,MOD.Core.MovementComponent,script.Monster,script.MonsterAttack,script.CombatMonster",
|
||||
"componentNames": "MOD.Core.TransformComponent,MOD.Core.StateAnimationComponent,MOD.Core.SpriteRendererComponent,MOD.Core.RigidbodyComponent,MOD.Core.MovementComponent,MOD.Core.StateComponent,MOD.Core.HitComponent,MOD.Core.DamageSkinSpawnerComponent,script.Monster,script.MonsterAttack,MOD.Core.KinematicbodyComponent,MOD.Core.SideviewbodyComponent,MOD.Core.DamageSkinSettingComponent,script.CombatMonster",
|
||||
"jsonString": {
|
||||
"name": "elite_5",
|
||||
"path": "/maps/map04/elite_5",
|
||||
@@ -6995,12 +6987,12 @@
|
||||
"revision": 2,
|
||||
"origin": {
|
||||
"type": "Model",
|
||||
"entry_id": "StaticMonster",
|
||||
"entry_id": "ChaseMonster",
|
||||
"sub_entity_id": null,
|
||||
"root_entity_id": "00001198-0000-4000-8000-000000001198",
|
||||
"replaced_model_id": null
|
||||
},
|
||||
"modelId": "staticmonster",
|
||||
"modelId": "chasemonster",
|
||||
"@components": [
|
||||
{
|
||||
"@type": "MOD.Core.TransformComponent",
|
||||
@@ -7041,38 +7033,6 @@
|
||||
"StartFrameIndex": 0,
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.DamageSkinSettingComponent",
|
||||
"DamageSkinId": {
|
||||
"DataId": "02c22d93421b4038b3c413b3e40b57ec"
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.HitComponent",
|
||||
"BoxSize": {
|
||||
"x": 0.78,
|
||||
"y": 0.86
|
||||
},
|
||||
"ColliderOffset": {
|
||||
"x": 0.03999999,
|
||||
"y": 0.43
|
||||
},
|
||||
"CollisionGroup": {
|
||||
"Id": "8992acd1e8cd45838db6f10a7b41df09"
|
||||
},
|
||||
"IsLegacy": false,
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.DamageSkinSpawnerComponent",
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.StateComponent",
|
||||
"IsLegacy": false,
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.RigidbodyComponent",
|
||||
"MoveVelocity": {
|
||||
@@ -7085,26 +7045,33 @@
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.KinematicbodyComponent",
|
||||
"MoveVelocity": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.SideviewbodyComponent",
|
||||
"MoveVelocity": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.MovementComponent",
|
||||
"Enable": false,
|
||||
"InputSpeed": 0
|
||||
"InputSpeed": 0,
|
||||
"JumpForce": 6,
|
||||
"Enable": false
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.StateComponent",
|
||||
"IsLegacy": false,
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.HitComponent",
|
||||
"BoxSize": {
|
||||
"x": 0.63,
|
||||
"y": 0.58
|
||||
},
|
||||
"ColliderOffset": {
|
||||
"x": 0.0449999869,
|
||||
"y": 0.29
|
||||
},
|
||||
"IsLegacy": false,
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.DamageSkinSpawnerComponent",
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "script.Monster",
|
||||
@@ -7123,6 +7090,29 @@
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.KinematicbodyComponent",
|
||||
"MoveVelocity": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.SideviewbodyComponent",
|
||||
"MoveVelocity": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.DamageSkinSettingComponent",
|
||||
"DamageSkinId": {
|
||||
"DataId": "02c22d93421b4038b3c413b3e40b57ec"
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "script.CombatMonster",
|
||||
"Enable": true,
|
||||
@@ -7136,7 +7126,7 @@
|
||||
{
|
||||
"id": "00001199-0000-4000-8000-000000001199",
|
||||
"path": "/maps/map04/boss_6",
|
||||
"componentNames": "MOD.Core.TransformComponent,MOD.Core.StateAnimationComponent,MOD.Core.SpriteRendererComponent,MOD.Core.DamageSkinSettingComponent,MOD.Core.HitComponent,MOD.Core.DamageSkinSpawnerComponent,MOD.Core.StateComponent,MOD.Core.RigidbodyComponent,MOD.Core.KinematicbodyComponent,MOD.Core.SideviewbodyComponent,MOD.Core.MovementComponent,script.Monster,script.MonsterAttack,script.CombatMonster",
|
||||
"componentNames": "MOD.Core.TransformComponent,MOD.Core.StateAnimationComponent,MOD.Core.SpriteRendererComponent,MOD.Core.RigidbodyComponent,MOD.Core.MovementComponent,MOD.Core.StateComponent,MOD.Core.HitComponent,MOD.Core.DamageSkinSpawnerComponent,script.Monster,script.MonsterAttack,MOD.Core.KinematicbodyComponent,MOD.Core.SideviewbodyComponent,MOD.Core.DamageSkinSettingComponent,script.CombatMonster",
|
||||
"jsonString": {
|
||||
"name": "boss_6",
|
||||
"path": "/maps/map04/boss_6",
|
||||
@@ -7149,12 +7139,12 @@
|
||||
"revision": 2,
|
||||
"origin": {
|
||||
"type": "Model",
|
||||
"entry_id": "StaticMonster",
|
||||
"entry_id": "ChaseMonster",
|
||||
"sub_entity_id": null,
|
||||
"root_entity_id": "00001199-0000-4000-8000-000000001199",
|
||||
"replaced_model_id": null
|
||||
},
|
||||
"modelId": "staticmonster",
|
||||
"modelId": "chasemonster",
|
||||
"@components": [
|
||||
{
|
||||
"@type": "MOD.Core.TransformComponent",
|
||||
@@ -7195,38 +7185,6 @@
|
||||
"StartFrameIndex": 0,
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.DamageSkinSettingComponent",
|
||||
"DamageSkinId": {
|
||||
"DataId": "02c22d93421b4038b3c413b3e40b57ec"
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.HitComponent",
|
||||
"BoxSize": {
|
||||
"x": 0.78,
|
||||
"y": 0.86
|
||||
},
|
||||
"ColliderOffset": {
|
||||
"x": 0.03999999,
|
||||
"y": 0.43
|
||||
},
|
||||
"CollisionGroup": {
|
||||
"Id": "8992acd1e8cd45838db6f10a7b41df09"
|
||||
},
|
||||
"IsLegacy": false,
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.DamageSkinSpawnerComponent",
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.StateComponent",
|
||||
"IsLegacy": false,
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.RigidbodyComponent",
|
||||
"MoveVelocity": {
|
||||
@@ -7239,26 +7197,33 @@
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.KinematicbodyComponent",
|
||||
"MoveVelocity": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.SideviewbodyComponent",
|
||||
"MoveVelocity": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.MovementComponent",
|
||||
"Enable": false,
|
||||
"InputSpeed": 0
|
||||
"InputSpeed": 0,
|
||||
"JumpForce": 6,
|
||||
"Enable": false
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.StateComponent",
|
||||
"IsLegacy": false,
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.HitComponent",
|
||||
"BoxSize": {
|
||||
"x": 0.63,
|
||||
"y": 0.58
|
||||
},
|
||||
"ColliderOffset": {
|
||||
"x": 0.0449999869,
|
||||
"y": 0.29
|
||||
},
|
||||
"IsLegacy": false,
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.DamageSkinSpawnerComponent",
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "script.Monster",
|
||||
@@ -7277,6 +7242,29 @@
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.KinematicbodyComponent",
|
||||
"MoveVelocity": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.SideviewbodyComponent",
|
||||
"MoveVelocity": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "MOD.Core.DamageSkinSettingComponent",
|
||||
"DamageSkinId": {
|
||||
"DataId": "02c22d93421b4038b3c413b3e40b57ec"
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "script.CombatMonster",
|
||||
"Enable": true,
|
||||
|
||||
890
map/map05.map
890
map/map05.map
File diff suppressed because it is too large
Load Diff
7292
map/map06.map
7292
map/map06.map
File diff suppressed because it is too large
Load Diff
7292
map/map07.map
7292
map/map07.map
File diff suppressed because it is too large
Load Diff
7292
map/map09.map
7292
map/map09.map
File diff suppressed because it is too large
Load Diff
7292
map/map10.map
7292
map/map10.map
File diff suppressed because it is too large
Load Diff
7292
map/map11.map
7292
map/map11.map
File diff suppressed because it is too large
Load Diff
@@ -74,7 +74,7 @@ export function loadData() {
|
||||
// 이며, Lua에 대응 AI가 없다(동기화 대상은 데미지/방어/의도/승패 규칙이지 플레이어 선택이 아님).
|
||||
// 손패에서 낼 카드 인덱스(-1=종료). 파워 우선(지속 가치) → 공격 → 스킬.
|
||||
export function chooseAction(hand, cards, energy) {
|
||||
const entries = hand.map((id, i) => ({ id, i })).filter((x) => cards[x.id].cost <= energy);
|
||||
const entries = hand.map((id, i) => ({ id, i })).filter((x) => cards[x.id] && cards[x.id].cost <= energy && !cards[x.id].unplayable);
|
||||
const powers = entries.filter((x) => cards[x.id].kind === 'Power');
|
||||
const attacks = entries.filter((x) => cards[x.id].kind === 'Attack');
|
||||
const skills = entries.filter((x) => cards[x.id].kind === 'Skill');
|
||||
@@ -122,7 +122,9 @@ export function simulateCombat(data, rng, stats) {
|
||||
for (let k = 0; k < n; k++) {
|
||||
if (drawPile.length === 0) { drawPile = shuffle(discard, rng); discard = []; }
|
||||
if (drawPile.length === 0) break;
|
||||
hand.push(drawPile.pop());
|
||||
const card = drawPile.pop();
|
||||
// 손패 10장 상한 — 초과 드로는 자동 버림 (Lua DrawCards 동기화)
|
||||
if (hand.length >= 10) discard.push(card); else hand.push(card);
|
||||
}
|
||||
}
|
||||
const aliveList = () => mob.filter((m) => m.alive);
|
||||
@@ -202,7 +204,12 @@ export function simulateCombat(data, rng, stats) {
|
||||
if (c.draw) draw(c.draw);
|
||||
if (aliveList().length === 0) return { win: true, turns, playerHpRemaining: pHp };
|
||||
}
|
||||
// 화상(endTurnDamage) — 손패에 있으면 턴 종료 시 피해 (Lua EndPlayerTurn 동기화)
|
||||
let burn = 0;
|
||||
for (const hid of hand) { const hc = cards[hid]; if (hc && hc.endTurnDamage) burn += hc.endTurnDamage; }
|
||||
if (burn > 0) { pHp -= burn; if (pHp < 0) pHp = 0; }
|
||||
discard.push(...hand); hand = [];
|
||||
if (pHp <= 0) return { win: false, turns, playerHpRemaining: 0 };
|
||||
// 플레이어 디버프 감소 — Lua EndPlayerTurn 동기화 (적 행동 전)
|
||||
if (pWeak > 0) pWeak--;
|
||||
if (pVuln > 0) pVuln--;
|
||||
@@ -215,7 +222,8 @@ export function simulateCombat(data, rng, stats) {
|
||||
if (m.hp <= 0) { m.hp = 0; m.alive = false; continue; }
|
||||
}
|
||||
m.block = 0; // 매 턴 초기화 (이전 턴 블록 미이월)
|
||||
const it = m.intents[m.intentIdx];
|
||||
// 정의된 intent 중 랜덤 선택 (Lua EnemyActStep 동기화 — 순차→랜덤)
|
||||
const it = m.intents.length ? m.intents[Math.floor(rng() * m.intents.length)] : null;
|
||||
if (it) {
|
||||
if (it.kind === 'Attack') {
|
||||
const atk = calcAttack(it.value, m.str, m.weak, pVuln);
|
||||
@@ -224,9 +232,12 @@ export function simulateCombat(data, rng, stats) {
|
||||
else if (it.kind === 'Debuff') {
|
||||
if (it.effect === 'weak') pWeak += it.value;
|
||||
else if (it.effect === 'vuln') pVuln += it.value;
|
||||
} else if (it.kind === 'AddCard') {
|
||||
// StS2식 덱 오염 — 저주 카드를 버린 더미에 추가 (Lua 동기화)
|
||||
const cnt = it.count || 1;
|
||||
for (let k = 0; k < cnt; k++) discard.push(it.card);
|
||||
}
|
||||
}
|
||||
m.intentIdx = (m.intentIdx + 1) % m.intents.length;
|
||||
// 적 디버프 감소 — Lua EnemyActStep 동기화 (자기 행동 후)
|
||||
if (m.weak > 0) m.weak--;
|
||||
if (m.vuln > 0) m.vuln--;
|
||||
|
||||
@@ -345,3 +345,33 @@ test('simulateCombat: draw — 카드 드로로 손패 보충', () => {
|
||||
assert.ok(r.turns <= 2, `seed ${s}: ${r.turns}턴`);
|
||||
}
|
||||
});
|
||||
|
||||
test('chooseAction: unplayable(저주) 카드는 건너뜀', () => {
|
||||
const cards = { Strike: { cost: 1, kind: 'Attack', damage: 6 }, Wound: { cost: 0, kind: 'Status', unplayable: true } };
|
||||
assert.equal(chooseAction(['Wound', 'Strike'], cards, 3), 1); // Strike 선택
|
||||
assert.equal(chooseAction(['Wound'], cards, 3), -1); // 낼 카드 없음
|
||||
});
|
||||
|
||||
test('simulateCombat: AddCard intent가 저주를 덱에 추가(오염)', () => {
|
||||
const data = {
|
||||
cards: { Hit: { name: '히트', cost: 1, kind: 'Attack', damage: 1 }, Wound: { name: '상처', cost: 0, kind: 'Status', unplayable: true } },
|
||||
starterDeck: ['Hit', 'Hit', 'Hit', 'Hit', 'Hit'],
|
||||
monsters: [{ name: '오염자', maxHp: 9999, intents: [{ kind: 'AddCard', card: 'Wound', count: 1 }] }],
|
||||
};
|
||||
// 적은 공격 안 하고 매 턴 저주만 추가 → 플레이어 무피해(승리 불가, 9999hp) → 무승부, 사망 아님
|
||||
const r = simulateCombat(data, mulberry32(1));
|
||||
assert.equal(r.win, false);
|
||||
assert.equal(r.draw, true);
|
||||
});
|
||||
|
||||
test('simulateCombat: endTurnDamage(화상)이 턴 종료 시 누적 피해', () => {
|
||||
const data = {
|
||||
cards: { Skip: { name: '대기', cost: 3, kind: 'Skill', block: 0 }, Burn: { name: '화상', cost: 0, kind: 'Status', unplayable: true, endTurnDamage: 2 } },
|
||||
starterDeck: ['Burn', 'Skip', 'Skip', 'Skip', 'Skip'],
|
||||
monsters: [{ name: '무공격', maxHp: 9999, intents: [{ kind: 'Defend', value: 0 }] }],
|
||||
};
|
||||
// 적은 방어만(무피해). 손패의 Burn이 매 턴 -2 → 80hp 잠식 → MAX_TURNS 전 사망 → win false(draw 아님)
|
||||
const r = simulateCombat(data, mulberry32(1));
|
||||
assert.equal(r.win, false);
|
||||
assert.notEqual(r.draw, true);
|
||||
});
|
||||
|
||||
@@ -4,7 +4,7 @@ import { readFileSync, writeFileSync } from 'node:fs';
|
||||
// 새 CameraComponent를 만들지 않고(엔진 소유) 기존 카메라 속성만 런타임 설정한다.
|
||||
// 플레이어 입력 차단·시선 고정은 tools/player/gen-player-lock.mjs(script.PlayerLock)로 분리됨.
|
||||
const CAM = JSON.parse(readFileSync('data/camera.json', 'utf8'));
|
||||
const MAP_NUMBERS = Array.from({ length: 11 }, (_, i) => i + 1); // map01~11
|
||||
const MAP_NUMBERS = Array.from({ length: 5 }, (_, i) => i + 1); // map01~05
|
||||
|
||||
function prop(Type, Name, DefaultValue = 'nil') {
|
||||
return { Type, DefaultValue, SyncDirection: 0, Attributes: [], Name };
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
141
tools/map/gen-lobby-map.mjs
Normal file
141
tools/map/gen-lobby-map.mjs
Normal file
@@ -0,0 +1,141 @@
|
||||
import { readFileSync, writeFileSync } from 'node:fs';
|
||||
|
||||
// 로비 전용 맵 생성기 — map01 템플릿을 클론해 마을(타운) 배경의 로비 맵을 만든다.
|
||||
// · 몬스터 엔티티 전부 제거(전투 없음)
|
||||
// · NPC 4종(모험가/사서/상인/안내원) 월드 엔티티 배치 + 머리 위 마크(근접 시 표시)
|
||||
// · 각 NPC: TouchReceiveComponent(클릭) + script.LobbyNpc(NpcId)
|
||||
// · 맵 루트: script.PlayerLock 제거(이동 허용) + script.LobbyMobility 추가(이동·공격 해제)
|
||||
// · SectorConfig에 map://lobby 등록
|
||||
// codeblock 로직(LobbyNpc/LobbyMobility)은 tools/player/gen-lobby-npc.mjs가 emit한다.
|
||||
const TEMPLATE = 'map/map01.map';
|
||||
const OUT = 'map/lobby.map';
|
||||
const SECTOR = 'Global/SectorConfig.config';
|
||||
|
||||
const TOWN_BG = '65c4167ea7484196b890022354e5a4a4'; // Henesys (gen-maps.mjs BACKGROUNDS 풀)
|
||||
const MARK_RUID = 'bd4afdde295f40318fceb4166978ebaa'; // 공식 maplestory balloon (근접 마크)
|
||||
|
||||
// NPC 4종: x좌표는 정찰 기준 walkable 범위[-5,6.6], 근접 임계 1.2와 분리되게 배치
|
||||
const NPCS = [
|
||||
{ name: 'NpcRun', id: 'run', x: -3.0, ruid: '122095fd155c4633867b0da4f375bc3c' }, // 모험가
|
||||
{ name: 'NpcCodex', id: 'codex', x: -0.5, ruid: '4c264be6a64f4ac3970b2e6818d04e40' }, // 사서
|
||||
{ name: 'NpcShop', id: 'shop', x: 2.0, ruid: '69987ccdc486423f8bedd786bd6cb5d9' }, // 상인
|
||||
{ name: 'NpcBoard', id: 'board', x: 4.5, ruid: '8a99bd87d667482cb1f3b2193f8a19c1' }, // 안내원
|
||||
];
|
||||
const MARK_DY = 1.6; // NPC 머리 위 오프셋
|
||||
|
||||
// NPC/마크는 정적 스프라이트로 만든다 — 몬스터 AI·물리(중력/충돌)·히트 컴포넌트 전부 제거.
|
||||
// (마크는 물리가 있으면 머리 위에서 떨어지고, NPC는 충돌이 있으면 플레이어 통행을 막음)
|
||||
const STRIP = new Set([
|
||||
'script.Monster', 'script.MonsterAttack', 'script.CombatMonster',
|
||||
'MOD.Core.RigidbodyComponent', 'MOD.Core.MovementComponent', 'MOD.Core.KinematicbodyComponent',
|
||||
'MOD.Core.SideviewbodyComponent', 'MOD.Core.HitComponent',
|
||||
'MOD.Core.DamageSkinSpawnerComponent', 'MOD.Core.DamageSkinSettingComponent',
|
||||
]);
|
||||
|
||||
const compOf = (e, type) => e.jsonString['@components'].find((c) => c['@type'] === type);
|
||||
const isMonster = (e) => (e.componentNames || '').includes('script.Monster');
|
||||
|
||||
// 결정론 GUID — 기존 생성기(map: nn*1000+idx, enc: +500)와 충돌 없는 고유 오프셋
|
||||
function lobbyGuid(idx) {
|
||||
const n = (900000 + idx) >>> 0;
|
||||
return `${n.toString(16).padStart(8, '0')}-0000-4000-8000-${n.toString(16).padStart(12, '0')}`;
|
||||
}
|
||||
|
||||
// 몬스터 템플릿 엔티티를 클론해 스프라이트 엔티티(NPC/마크)로 변환
|
||||
function makeSpriteEntity(base, name, x, y, ruid, withInteract, npcId) {
|
||||
const m = JSON.parse(JSON.stringify(base));
|
||||
m.jsonString.name = name;
|
||||
m.path = `/maps/lobby/${name}`;
|
||||
m.jsonString.path = m.path;
|
||||
// NOTE: 베이스 모델(chasemonster)이 script.Monster/MonsterAttack를 inheritance로 끌고 와서
|
||||
// DuplicateComponent(LobbyNpc와 공존) 경고 + MonsterAttack.OnBeginPlay AnimationClip 에러가 뜬다.
|
||||
// 둘 다 비치명적(로비엔 전투 컨텍스트가 없어 Monster는 휴면, 에러는 전투맵 몬스터와 공유되는 기존 lint).
|
||||
// modelId를 비우면 fresh load에서 렌더가 깨질 위험이 있어 proven-good(모델 유지)로 둔다.
|
||||
const tr = compOf(m, 'MOD.Core.TransformComponent');
|
||||
if (tr) { tr.Position.x = x; tr.Position.y = y; }
|
||||
const sp = compOf(m, 'MOD.Core.SpriteRendererComponent');
|
||||
if (sp) sp.SpriteRUID = ruid;
|
||||
const sa = compOf(m, 'MOD.Core.StateAnimationComponent');
|
||||
if (sa) sa.ActionSheet = { stand: ruid, hit: ruid, die: ruid }; // 항상 stand 스프라이트 고정
|
||||
// 몬스터 AI·물리·히트 컴포넌트 제거 → 정적 비충돌 스프라이트
|
||||
m.jsonString['@components'] = m.jsonString['@components'].filter((c) => !STRIP.has(c['@type']));
|
||||
let names = (m.componentNames || '').split(',').filter((s) => s && !STRIP.has(s));
|
||||
if (withInteract) {
|
||||
m.jsonString['@components'].push({ '@type': 'MOD.Core.TouchReceiveComponent', Enable: true, AutoFitToSize: true });
|
||||
m.jsonString['@components'].push({ '@type': 'script.LobbyNpc', Enable: true, NpcId: npcId, MarkName: name + 'Mark' });
|
||||
names.push('MOD.Core.TouchReceiveComponent', 'script.LobbyNpc');
|
||||
}
|
||||
m.componentNames = names.join(',');
|
||||
return m;
|
||||
}
|
||||
|
||||
const template = JSON.parse(readFileSync(TEMPLATE, 'utf8'));
|
||||
const monsterTemplates = template.ContentProto.Entities.filter(isMonster);
|
||||
if (monsterTemplates.length === 0) throw new Error('[gen-lobby-map] 몬스터 템플릿(스프라이트 엔티티) 없음');
|
||||
const base = monsterTemplates.find((e) => (e.path || '').includes('Static')) || monsterTemplates[0];
|
||||
const baseY = (() => {
|
||||
const tr = compOf(base, 'MOD.Core.TransformComponent');
|
||||
return tr ? tr.Position.y : 0;
|
||||
})();
|
||||
|
||||
const map = JSON.parse(JSON.stringify(template)); // deep clone
|
||||
map.EntryKey = 'map://lobby';
|
||||
let ents = map.ContentProto.Entities.filter((e) => !isMonster(e));
|
||||
|
||||
// 경로/이름 치환 + 배경 + 루트 컴포넌트 조정
|
||||
for (const e of ents) {
|
||||
if (typeof e.path === 'string') e.path = e.path.replace('/maps/map01', '/maps/lobby');
|
||||
if (e.jsonString) {
|
||||
if (typeof e.jsonString.path === 'string') e.jsonString.path = e.jsonString.path.replace('/maps/map01', '/maps/lobby');
|
||||
if (e.jsonString.name === 'map01') e.jsonString.name = 'lobby';
|
||||
}
|
||||
if ((e.path || '').endsWith('/Background')) {
|
||||
const bg = compOf(e, 'MOD.Core.BackgroundComponent');
|
||||
if (bg) bg.TemplateRUID = TOWN_BG;
|
||||
}
|
||||
}
|
||||
|
||||
const root = ents.find((e) => e.path === '/maps/lobby');
|
||||
if (!root) throw new Error('[gen-lobby-map] 맵 루트 없음');
|
||||
// 로비엔 PlayerLock 제거(이동 허용) + LobbyMobility 추가(이동·공격 해제). MapCamera는 유지.
|
||||
root.jsonString['@components'] = root.jsonString['@components'].filter(
|
||||
(c) => !['script.PlayerLock', 'script.LobbyMobility'].includes(c['@type']),
|
||||
);
|
||||
root.jsonString['@components'].push({ '@type': 'script.LobbyMobility', Enable: true });
|
||||
{
|
||||
const names = (root.componentNames || '')
|
||||
.split(',')
|
||||
.filter((s) => s && !['script.PlayerLock', 'script.LobbyMobility'].includes(s));
|
||||
names.push('script.LobbyMobility');
|
||||
root.componentNames = names.join(',');
|
||||
}
|
||||
|
||||
// NPC + 마크 엔티티 생성
|
||||
for (const npc of NPCS) {
|
||||
ents.push(makeSpriteEntity(base, npc.name, npc.x, baseY, npc.ruid, true, npc.id));
|
||||
ents.push(makeSpriteEntity(base, npc.name + 'Mark', npc.x, baseY + MARK_DY, MARK_RUID, false, ''));
|
||||
}
|
||||
|
||||
// GUID 전부 재발급 (map01과 충돌 방지 + 자기참조 origin 보정)
|
||||
ents.forEach((e, idx) => {
|
||||
const oldId = e.id;
|
||||
const newId = lobbyGuid(idx);
|
||||
e.id = newId;
|
||||
const o = e.jsonString && e.jsonString.origin;
|
||||
if (o) {
|
||||
if (o.root_entity_id === oldId) o.root_entity_id = newId;
|
||||
if (o.sub_entity_id === oldId) o.sub_entity_id = newId;
|
||||
}
|
||||
});
|
||||
|
||||
map.ContentProto.Entities = ents;
|
||||
writeFileSync(OUT, JSON.stringify(map, null, 2), 'utf8');
|
||||
|
||||
// SectorConfig 등록 (멱등)
|
||||
const sector = JSON.parse(readFileSync(SECTOR, 'utf8'));
|
||||
const sec0 = sector.ContentProto.Json.Sectors[0];
|
||||
if (!sec0.entries.includes('map://lobby')) sec0.entries.push('map://lobby');
|
||||
writeFileSync(SECTOR, JSON.stringify(sector, null, 2), 'utf8');
|
||||
|
||||
const npcCount = ents.filter((e) => (e.componentNames || '').includes('script.LobbyNpc')).length;
|
||||
console.log(`[gen-lobby-map] lobby.map 생성: NPC ${npcCount}종 + 마크, SectorConfig entries ${sec0.entries.length}`);
|
||||
@@ -2,15 +2,24 @@ import { readFileSync, writeFileSync } from 'node:fs';
|
||||
|
||||
// map02~11에 노드 타입별 몬스터 그룹(combat3/elite2/boss1)을 맵별 테마로 자동 구성.
|
||||
// 기존 몬스터 엔티티를 전부 제거하고 첫 몬스터를 템플릿으로 6마리 재생성(결정론).
|
||||
const MAP_NUMBERS = [2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
|
||||
const COMBAT_POOL = ['orange_mushroom', 'green_mushroom', 'pig', 'blue_mushroom'];
|
||||
const MAP_NUMBERS = [1, 2, 3, 4, 5];
|
||||
const COMBAT_POOL = ['orange_mushroom', 'green_mushroom', 'pig', 'blue_mushroom', 'red_snail', 'stump'];
|
||||
const ELITE_POOL = ['mushmom', 'modified_snail'];
|
||||
const BOSS_POOL = ['king_slime', 'slime_boss'];
|
||||
const LAYOUT = [
|
||||
// map01: StS2식 일반 5종 + 엘리트 1 + 보스 1(보스 노드용, 화면 우측 포메이션).
|
||||
// 그 외 맵: 일반 3 + 엘리트 2 + 보스 1. 전투 시 BuildMonsters가 노드 타입별로 1~3마리 랜덤 추첨.
|
||||
const LAYOUT_MAP01 = [
|
||||
{ group: 'combat', x: 2.6 }, { group: 'combat', x: 3.6 }, { group: 'combat', x: 4.6 },
|
||||
{ group: 'combat', x: 5.6 }, { group: 'combat', x: 6.6 },
|
||||
{ group: 'elite', x: 4.6 },
|
||||
{ group: 'boss', x: 4.6 },
|
||||
];
|
||||
const LAYOUT_DEFAULT = [
|
||||
{ group: 'combat', x: 2.3 }, { group: 'combat', x: 3.8 }, { group: 'combat', x: 5.2 },
|
||||
{ group: 'elite', x: 3.0 }, { group: 'elite', x: 5.0 },
|
||||
{ group: 'boss', x: 4.0 },
|
||||
];
|
||||
const layoutFor = (nn) => (nn === 1 ? LAYOUT_MAP01 : LAYOUT_DEFAULT);
|
||||
const MONSTER_VARIANTS = [
|
||||
{ sprite: '96e955c1bf27415e84f96deea200a8f1', stand: '96e955c1bf27415e84f96deea200a8f1', hit: 'aec9504d5dc24aceb5646b79d30abad4', die: '65a2bfb039614f2e9e4ccc354340153d' },
|
||||
{ sprite: 'f86992ba9c41487c8480fcb893fcbda6', stand: 'f86992ba9c41487c8480fcb893fcbda6', hit: 'd305b942b1704c8084548108ff3b7a6b', die: '5a563e5fd98c4132b61057dc6bb8aaf2' },
|
||||
@@ -54,13 +63,17 @@ function patchMap(nn) {
|
||||
const template = monsters[0];
|
||||
map.ContentProto.Entities = ents.filter((e) => !isMonster(e));
|
||||
const rand = rng(nn * 7919 + 17);
|
||||
const combatIds = pickN(rand, COMBAT_POOL, 3);
|
||||
const eliteIds = pickN(rand, ELITE_POOL, 2);
|
||||
const layout = layoutFor(nn);
|
||||
const nCombat = layout.filter((s) => s.group === 'combat').length;
|
||||
const nElite = layout.filter((s) => s.group === 'elite').length;
|
||||
const combatIds = pickN(rand, COMBAT_POOL, nCombat);
|
||||
const eliteIds = pickN(rand, ELITE_POOL, nElite);
|
||||
const bossId = pick(rand, BOSS_POOL);
|
||||
const variants = pickN(rand, MONSTER_VARIANTS, 6);
|
||||
LAYOUT.forEach((slot, idx) => {
|
||||
const variants = pickN(rand, MONSTER_VARIANTS, layout.length);
|
||||
let ci = 0, ei = 0;
|
||||
layout.forEach((slot, idx) => {
|
||||
const m = JSON.parse(JSON.stringify(template));
|
||||
const enemyId = slot.group === 'combat' ? combatIds[idx] : slot.group === 'elite' ? eliteIds[idx - 3] : bossId;
|
||||
const enemyId = slot.group === 'combat' ? combatIds[ci++] : slot.group === 'elite' ? eliteIds[ei++] : bossId;
|
||||
const name = `${slot.group}_${idx + 1}`;
|
||||
m.id = encGuid(nn, idx);
|
||||
m.path = `/maps/map${tag}/${name}`;
|
||||
|
||||
@@ -2,7 +2,7 @@ import { readFileSync, writeFileSync } from 'node:fs';
|
||||
|
||||
const TEMPLATE = 'map/map01.map';
|
||||
const SECTOR = 'Global/SectorConfig.config';
|
||||
const MAP_NUMBERS = [2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
|
||||
const MAP_NUMBERS = [2, 3, 4, 5];
|
||||
|
||||
// 공식 맵에서 수확한 Background-타입 RUID 풀 (맵마다 1개씩, 서로 다르게).
|
||||
// 공식 MapleStory 맵을 import해 각 맵의 BackgroundComponent.TemplateRUID를 수집함.
|
||||
@@ -139,14 +139,14 @@ const targets = arg ? [Number(arg)] : MAP_NUMBERS;
|
||||
const made = targets.map(buildMap);
|
||||
console.log('Generated:', made.join(', '));
|
||||
|
||||
// SectorConfig 등록 (전체 생성 시에만, 중복 방지)
|
||||
// SectorConfig 등록 (전체 생성 시에만) — 유효 맵만 유지하고 삭제된 맵 엔트리는 제거
|
||||
if (!arg) {
|
||||
const sector = JSON.parse(readFileSync(SECTOR, 'utf8'));
|
||||
const entries = sector.ContentProto.Json.Sectors[0].entries;
|
||||
for (const nn of MAP_NUMBERS) {
|
||||
const key = `map://map${String(nn).padStart(2, '0')}`;
|
||||
if (!entries.includes(key)) entries.push(key);
|
||||
}
|
||||
const sec0 = sector.ContentProto.Json.Sectors[0];
|
||||
const valid = ['map://map01', ...MAP_NUMBERS.map((nn) => `map://map${String(nn).padStart(2, '0')}`)];
|
||||
// map06~ 등 더 이상 존재하지 않는 맵 엔트리 제거 + 누락분 추가
|
||||
sec0.entries = sec0.entries.filter((k) => !/^map:\/\/map\d+$/.test(k) || valid.includes(k));
|
||||
for (const key of valid) if (!sec0.entries.includes(key)) sec0.entries.push(key);
|
||||
writeFileSync(SECTOR, JSON.stringify(sector, null, 2), 'utf8');
|
||||
console.log('SectorConfig entries:', entries.length);
|
||||
console.log('SectorConfig entries:', sec0.entries.length);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// ⚠️ 전투 규칙과 마찬가지로 tools/deck/gen-slaydeck.mjs 의 Lua(GenerateMap)와 로직 동기화 유지할 것.
|
||||
// (Lua는 math.random, 여기는 주입 rng — 수치 동일성이 아니라 구조 규칙 동일성이 대상)
|
||||
|
||||
export const ROWS = 7; // 걷는 행 1..7, 보스는 row 8
|
||||
export const ROWS = 6; // 걷는 행 1..6, 보스는 row 7 (depth 최대 7)
|
||||
export const COLS = 4;
|
||||
export const PATHS = 4;
|
||||
|
||||
@@ -64,12 +64,13 @@ export function generateMap(rng) {
|
||||
const id = nodeId(r, c);
|
||||
const node = nodes[id];
|
||||
if (!node) continue;
|
||||
// elite 부모 검사 (연속 엘리트 방지)
|
||||
let eliteParent = false;
|
||||
// 부모 노드 타입 수집 (rest/shop/elite 는 부모와 같은 타입 연속 금지)
|
||||
const parentTypes = new Set();
|
||||
for (const pn of Object.values(nodes)) {
|
||||
if (pn.row === r - 1 && pn.type === 'elite' && pn.next.includes(id)) eliteParent = true;
|
||||
if (pn.row === r - 1 && pn.next.includes(id)) parentTypes.add(pn.type);
|
||||
}
|
||||
const w = rowWeights(r).map(([t, wt]) => [t, t === 'elite' && eliteParent ? 0 : wt]);
|
||||
const NO_REPEAT = new Set(['rest', 'shop', 'elite']);
|
||||
const w = rowWeights(r).map(([t, wt]) => [t, NO_REPEAT.has(t) && parentTypes.has(t) ? 0 : wt]);
|
||||
const total = w.reduce((s, [, wt]) => s + wt, 0);
|
||||
const roll = rng() * total;
|
||||
let acc = 0;
|
||||
|
||||
@@ -61,7 +61,7 @@ test('타입 규칙: 1~2행 combat만, elite·treasure는 4행부터, shop·rest
|
||||
}
|
||||
});
|
||||
|
||||
test('boss: row 8 단일 노드, 7행 노드는 전부 boss로 연결', () => {
|
||||
test('boss: row 7 단일 노드, 마지막 걷는 행 노드는 전부 boss로 연결', () => {
|
||||
for (let s = 1; s <= 30; s++) {
|
||||
const { nodes } = gen(s);
|
||||
const bosses = Object.entries(nodes).filter(([, n]) => n.type === 'boss');
|
||||
@@ -90,19 +90,21 @@ test('간선 제약: row+1로만, 열 차이 1 이하 (boss 간선 제외)', ()
|
||||
}
|
||||
});
|
||||
|
||||
test('elite 연속 금지: elite 부모를 가진 노드는 elite 아님', () => {
|
||||
test('연속 금지: rest/shop/elite 는 부모와 같은 타입을 자식으로 두지 않음', () => {
|
||||
const NO_REPEAT = new Set(['rest', 'shop', 'elite']);
|
||||
for (let s = 1; s <= 100; s++) {
|
||||
const { nodes } = gen(s);
|
||||
for (const [id, n] of Object.entries(nodes)) {
|
||||
if (n.type !== 'elite') continue;
|
||||
if (!NO_REPEAT.has(n.type)) continue;
|
||||
for (const nid of n.next) {
|
||||
assert.notEqual(nodes[nid].type, 'elite', `seed ${s}: ${id}(elite) → ${nid}(elite)`);
|
||||
if (nid === 'boss') continue;
|
||||
assert.notEqual(nodes[nid].type, n.type, `seed ${s}: ${id}(${n.type}) → ${nid}(${n.type})`);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('그리드 범위: 행 1..8, 열 1..4 (boss 제외)', () => {
|
||||
test('그리드 범위: 행 1..6, 열 1..4 (boss 제외)', () => {
|
||||
const { nodes } = gen(7);
|
||||
for (const [id, n] of Object.entries(nodes)) {
|
||||
if (id === 'boss') continue;
|
||||
|
||||
@@ -5,7 +5,7 @@ const AI_COMPONENTS = new Set([
|
||||
'MOD.Core.AIChaseComponent',
|
||||
]);
|
||||
|
||||
const mapFiles = Array.from({ length: 11 }, (_, i) => `map/map${String(i + 1).padStart(2, '0')}.map`);
|
||||
const mapFiles = Array.from({ length: 5 }, (_, i) => `map/map${String(i + 1).padStart(2, '0')}.map`);
|
||||
const modelFiles = [
|
||||
'Global/MoveMonster.model',
|
||||
'Global/ChaseMonster.model',
|
||||
|
||||
@@ -2,7 +2,7 @@ import { readFileSync, writeFileSync } from 'node:fs';
|
||||
|
||||
// 맵 몬스터에 적 타입(EnemyId)을 부여하고, BeginPlay 시 /common 컨트롤러에 자기등록하는 마커.
|
||||
// 카드 전투 시 컨트롤러가 등록 목록으로 인카운터를 구성한다.
|
||||
const MAP_NUMBERS = Array.from({ length: 11 }, (_, i) => i + 1); // map01~11
|
||||
const MAP_NUMBERS = Array.from({ length: 5 }, (_, i) => i + 1); // map01~05
|
||||
const NAME_TO_ENEMY = { '주황버섯': 'orange_mushroom', '파란버섯': 'blue_mushroom' };
|
||||
const DEFAULT_ENEMY = 'orange_mushroom';
|
||||
|
||||
|
||||
103
tools/player/gen-lobby-npc.mjs
Normal file
103
tools/player/gen-lobby-npc.mjs
Normal file
@@ -0,0 +1,103 @@
|
||||
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 생성 완료');
|
||||
@@ -7,7 +7,7 @@ import { readFileSync, writeFileSync } from 'node:fs';
|
||||
const LOOK_DIRECTION_X = 1; // 1 = 오른쪽(몬스터가 배치된 전투 포메이션 방향)
|
||||
const FIXED_LOOK_AT = true; // 바라보는 방향 고정
|
||||
const CONTROLLER_ENABLE = false; // 플레이어 입력 차단
|
||||
const MAP_NUMBERS = Array.from({ length: 11 }, (_, i) => i + 1); // map01~11
|
||||
const MAP_NUMBERS = Array.from({ length: 5 }, (_, i) => i + 1); // map01~05
|
||||
|
||||
function prop(Type, Name, DefaultValue = 'nil') {
|
||||
return { Type, DefaultValue, SyncDirection: 0, Attributes: [], Name };
|
||||
@@ -65,6 +65,10 @@ local function apply()
|
||||
pc.FixedLookAt = ${FIXED_LOOK_AT}
|
||||
pc.Enable = ${CONTROLLER_ENABLE}
|
||||
end
|
||||
if lp ~= nil then
|
||||
if lp.RigidbodyComponent ~= nil then lp.RigidbodyComponent.WalkAcceleration = 0 end
|
||||
if lp.MovementComponent ~= nil then lp.MovementComponent.InputSpeed = 0; lp.MovementComponent.JumpForce = 0 end
|
||||
end
|
||||
if pc ~= nil then
|
||||
_TimerService:ClearTimer(eventId)
|
||||
elseif self.LockTries > 30 then
|
||||
|
||||
22
tools/verify/count.mjs
Normal file
22
tools/verify/count.mjs
Normal file
@@ -0,0 +1,22 @@
|
||||
import { readFileSync } from 'node:fs';
|
||||
|
||||
// 산출물 카운트 검증 헬퍼 (RULES §2: 내용 출력 금지·카운트만).
|
||||
// 사용: node tools/verify/count.mjs <key> <regex> [<regex> ...]
|
||||
// key: ui | cb | common (산출물 경로는 여기 내장 — Bash 명령에 산출물 경로를 노출하지 않아 deny 회피)
|
||||
const FILES = {
|
||||
ui: 'ui/DefaultGroup.ui',
|
||||
cb: 'RootDesk/MyDesk/SlayDeckController.codeblock',
|
||||
common: 'Global/common.gamelogic',
|
||||
};
|
||||
const key = process.argv[2];
|
||||
const path = FILES[key];
|
||||
if (!path) { console.error(`unknown key: ${key} (use ${Object.keys(FILES).join('|')})`); process.exit(1); }
|
||||
const content = readFileSync(path, 'utf8');
|
||||
// JSON 유효성도 함께 확인
|
||||
let jsonOk = false;
|
||||
try { JSON.parse(content); jsonOk = true; } catch { jsonOk = false; }
|
||||
console.log(`${path} bytes=${content.length} jsonValid=${jsonOk}`);
|
||||
for (const pat of process.argv.slice(3)) {
|
||||
const m = content.match(new RegExp(pat, 'g'));
|
||||
console.log(` /${pat}/ = ${m ? m.length : 0}`);
|
||||
}
|
||||
23187
ui/DefaultGroup.ui
23187
ui/DefaultGroup.ui
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user