feat(map01): 노드 타입별 몬스터 그룹 콘텐츠 + HP바 몬스터 추종 #32

Merged
maple merged 2 commits from feature/map01-monster-content into main 2026-06-11 01:44:25 +09:00
5 changed files with 490 additions and 55 deletions

View File

@@ -218,20 +218,6 @@
"Attributes": [], "Attributes": [],
"Name": "TargetIndex" "Name": "TargetIndex"
}, },
{
"Type": "any",
"DefaultValue": "nil",
"SyncDirection": 0,
"Attributes": [],
"Name": "SlotPos"
},
{
"Type": "any",
"DefaultValue": "nil",
"SyncDirection": 0,
"Attributes": [],
"Name": "ActiveSlotPos"
},
{ {
"Type": "any", "Type": "any",
"DefaultValue": "nil", "DefaultValue": "nil",
@@ -527,7 +513,7 @@
"Name": null "Name": null
}, },
"Arguments": [], "Arguments": [],
"Code": "self.PlayerMaxHp = 80\nself.PlayerHp = self.PlayerMaxHp\nself.Gold = 0\nself.Floor = 1\nself.RunLength = 3\nself.RunDeck = { \"Strike\", \"Strike\", \"Strike\", \"Strike\", \"Strike\", \"Defend\", \"Defend\", \"Defend\", \"Defend\", \"Bash\" }\nself.RunActive = true\nself.RunRelics = {}\nself.Relics = {\n\tironHeart = { name = \"강철 심장\", desc = \"전투 시작 시 방어도 +6\", hook = \"combatStart\", effect = \"block\", value = 6 },\n\tenergyCore = { name = \"에너지 코어\", desc = \"턴 시작 시 에너지 +1\", hook = \"turnStart\", effect = \"energy\", value = 1 },\n\tvampire = { name = \"흡혈 송곳니\", desc = \"공격 카드 사용 시 HP +1\", hook = \"cardPlayed\", effect = \"healOnAttack\", value = 1 },\n\tgoldIdol = { name = \"황금 우상\", desc = \"전투 승리 시 골드 +10\", hook = \"combatReward\", effect = \"gold\", value = 10 },\n}\nself.RelicPool = { \"energyCore\", \"vampire\", \"goldIdol\" }\nself.Enemies = {\n\tslime = { name = \"슬라임\", maxHp = 45, intents = { { kind = \"Attack\", value = 10 }, { kind = \"Attack\", value = 6 }, { kind = \"Defend\", value = 8 } } },\n\tslime_elite = { name = \"정예 슬라임\", maxHp = 70, intents = { { kind = \"Attack\", value = 14 }, { kind = \"Attack\", value = 8 }, { kind = \"Defend\", value = 10 } } },\n\tslime_boss = { name = \"슬라임 킹\", maxHp = 120, intents = { { kind = \"Attack\", value = 18 }, { kind = \"Defend\", value = 12 }, { kind = \"Attack\", value = 10 }, { kind = \"Attack\", value = 22 } } },\n\torange_mushroom = { name = \"주황버섯\", maxHp = 16, intents = { { kind = \"Attack\", value = 5 }, { kind = \"Defend\", value = 4 }, { kind = \"Attack\", value = 7 } } },\n\tblue_mushroom = { name = \"파란버섯\", maxHp = 22, intents = { { kind = \"Attack\", value = 8 }, { kind = \"Attack\", value = 4 } } },\n}\nself.MapNodes = {\n\tA = { type = \"combat\", enemy = \"slime\", row = 1, col = -1, next = { \"C\", \"D\" } },\n\tB = { type = \"combat\", enemy = \"slime\", row = 1, col = 1, next = { \"C\", \"D\" } },\n\tC = { type = \"rest\", row = 2, col = -1, next = { \"E\", \"F\" } },\n\tD = { type = \"shop\", row = 2, col = 1, next = { \"E\", \"F\" } },\n\tE = { type = \"elite\", enemy = \"slime_elite\", row = 3, col = -1, next = { \"BOSS\" } },\n\tF = { type = \"combat\", enemy = \"slime\", row = 3, col = 1, next = { \"BOSS\" } },\n\tBOSS = { type = \"boss\", enemy = \"slime_boss\", row = 4, col = 0, next = { } },\n}\nself.MapStart = { \"A\", \"B\" }\nself.SlotPos = { combat = { { x = 430, y = 140 }, { x = 600, y = 140 }, { x = 770, y = 140 }, { x = 900, y = 140 } }, elite = { { x = 430, y = 160 }, { x = 650, y = 160 }, { x = 850, y = 160 }, { x = 980, y = 160 } }, boss = { { x = 520, y = 200 }, { x = 760, y = 160 }, { x = 940, y = 150 }, { x = 1040, y = 150 } } }\nself.CurrentNodeId = \"\"\nself.CurrentEnemyId = \"\"\nself:BindButtons()\nself:AddRelic(\"ironHeart\")\nself:ShowMap()", "Code": "self.PlayerMaxHp = 80\nself.PlayerHp = self.PlayerMaxHp\nself.Gold = 0\nself.Floor = 1\nself.RunLength = 3\nself.RunDeck = { \"Strike\", \"Strike\", \"Strike\", \"Strike\", \"Strike\", \"Defend\", \"Defend\", \"Defend\", \"Defend\", \"Bash\" }\nself.RunActive = true\nself.RunRelics = {}\nself.Relics = {\n\tironHeart = { name = \"강철 심장\", desc = \"전투 시작 시 방어도 +6\", hook = \"combatStart\", effect = \"block\", value = 6 },\n\tenergyCore = { name = \"에너지 코어\", desc = \"턴 시작 시 에너지 +1\", hook = \"turnStart\", effect = \"energy\", value = 1 },\n\tvampire = { name = \"흡혈 송곳니\", desc = \"공격 카드 사용 시 HP +1\", hook = \"cardPlayed\", effect = \"healOnAttack\", value = 1 },\n\tgoldIdol = { name = \"황금 우상\", desc = \"전투 승리 시 골드 +10\", hook = \"combatReward\", effect = \"gold\", value = 10 },\n}\nself.RelicPool = { \"energyCore\", \"vampire\", \"goldIdol\" }\nself.Enemies = {\n\tslime = { name = \"슬라임\", maxHp = 45, intents = { { kind = \"Attack\", value = 10 }, { kind = \"Attack\", value = 6 }, { kind = \"Defend\", value = 8 } } },\n\tslime_elite = { name = \"정예 슬라임\", maxHp = 70, intents = { { kind = \"Attack\", value = 14 }, { kind = \"Attack\", value = 8 }, { kind = \"Defend\", value = 10 } } },\n\tslime_boss = { name = \"슬라임 킹\", maxHp = 120, intents = { { kind = \"Attack\", value = 18 }, { kind = \"Defend\", value = 12 }, { kind = \"Attack\", value = 10 }, { kind = \"Attack\", value = 22 } } },\n\torange_mushroom = { name = \"주황버섯\", maxHp = 16, intents = { { kind = \"Attack\", value = 5 }, { kind = \"Defend\", value = 4 }, { kind = \"Attack\", value = 7 } } },\n\tblue_mushroom = { name = \"파란버섯\", maxHp = 22, intents = { { kind = \"Attack\", value = 8 }, { kind = \"Attack\", value = 4 } } },\n\tpig = { name = \"돼지\", maxHp = 18, intents = { { kind = \"Attack\", value = 6 }, { kind = \"Defend\", value = 3 } } },\n\tgreen_mushroom = { name = \"초록버섯\", maxHp = 20, intents = { { kind = \"Attack\", value = 7 }, { kind = \"Attack\", value = 4 } } },\n\tmushmom = { name = \"머쉬맘\", maxHp = 75, intents = { { kind = \"Attack\", value = 14 }, { kind = \"Defend\", value = 10 }, { kind = \"Attack\", value = 9 } } },\n\tmodified_snail = { name = \"변형된 달팽이\", maxHp = 60, intents = { { kind = \"Attack\", value = 12 }, { kind = \"Attack\", value = 7 }, { kind = \"Defend\", value = 8 } } },\n\tking_slime = { name = \"킹 슬라임\", maxHp = 130, intents = { { kind = \"Attack\", value = 18 }, { kind = \"Defend\", value = 14 }, { kind = \"Attack\", value = 12 }, { kind = \"Attack\", value = 24 } } },\n}\nself.MapNodes = {\n\tA = { type = \"combat\", enemy = \"slime\", row = 1, col = -1, next = { \"C\", \"D\" } },\n\tB = { type = \"combat\", enemy = \"slime\", row = 1, col = 1, next = { \"C\", \"D\" } },\n\tC = { type = \"rest\", row = 2, col = -1, next = { \"E\", \"F\" } },\n\tD = { type = \"shop\", row = 2, col = 1, next = { \"E\", \"F\" } },\n\tE = { type = \"elite\", enemy = \"slime_elite\", row = 3, col = -1, next = { \"BOSS\" } },\n\tF = { type = \"combat\", enemy = \"slime\", row = 3, col = 1, next = { \"BOSS\" } },\n\tBOSS = { type = \"boss\", enemy = \"slime_boss\", row = 4, col = 0, next = { } },\n}\nself.MapStart = { \"A\", \"B\" }\nself.CurrentNodeId = \"\"\nself.CurrentEnemyId = \"\"\nself:BindButtons()\nself:AddRelic(\"ironHeart\")\nself:ShowMap()",
"Scope": 2, "Scope": 2,
"ExecSpace": 6, "ExecSpace": 6,
"Attributes": [], "Attributes": [],
@@ -594,7 +580,7 @@
"Name": null "Name": null
}, },
"Arguments": [], "Arguments": [],
"Code": "self.Monsters = {}\nlocal g = \"combat\"\nlocal node = self.MapNodes[self.CurrentNodeId]\nif node ~= nil and node.type ~= nil then g = node.type end\nself.ActiveSlotPos = self.SlotPos[g]\nlocal reg = self.Registered or {}\nfor i = 1, #reg do\n\tif reg[i].entity ~= nil and isvalid(reg[i].entity) then\n\t\treg[i].entity:SetVisible(false)\n\tend\nend\nlocal list = {}\nfor i = 1, #reg do\n\tlocal r = reg[i]\n\tif r.entity ~= nil and isvalid(r.entity) and r.group == g then\n\t\tlocal x = 0\n\t\tif r.entity.TransformComponent ~= nil then\n\t\t\tx = r.entity.TransformComponent.WorldPosition.x\n\t\tend\n\t\ttable.insert(list, { entity = r.entity, enemyId = r.enemyId, x = x })\n\tend\nend\ntable.sort(list, function(a, b) return a.x < b.x end)\nlocal mult = 1 + (self.Floor - 1) * 0.6\nlocal n = #list\nif n > 4 then n = 4 end\nfor i = 1, n do\n\tlocal item = list[i]\n\tlocal e = self.Enemies[item.enemyId]\n\tif e == nil then e = { name = item.enemyId, maxHp = 10, intents = { { kind = \"Attack\", value = 5 } } } end\n\tlocal intents = {}\n\tfor k = 1, #e.intents do\n\t\tintents[k] = { kind = e.intents[k].kind, value = math.floor(e.intents[k].value * mult) }\n\tend\n\tlocal maxHp = math.floor(e.maxHp * mult)\n\tself.Monsters[i] = { entity = item.entity, enemyId = item.enemyId, name = e.name,\n\t\thp = maxHp, maxHp = maxHp, block = 0, intents = intents, intentIdx = 1, alive = true, slot = i }\n\tself:ReviveMonsterEntity(item.entity)\n\tself:PositionMonsterSlot(i)\nend\nself.TargetIndex = 1", "Code": "self.Monsters = {}\nlocal g = \"combat\"\nlocal node = self.MapNodes[self.CurrentNodeId]\nif node ~= nil and node.type ~= nil then g = node.type end\nlocal reg = self.Registered or {}\nfor i = 1, #reg do\n\tif reg[i].entity ~= nil and isvalid(reg[i].entity) then\n\t\treg[i].entity:SetVisible(false)\n\tend\nend\nlocal list = {}\nfor i = 1, #reg do\n\tlocal r = reg[i]\n\tif r.entity ~= nil and isvalid(r.entity) and r.group == g then\n\t\tlocal x = 0\n\t\tif r.entity.TransformComponent ~= nil then\n\t\t\tx = r.entity.TransformComponent.WorldPosition.x\n\t\tend\n\t\ttable.insert(list, { entity = r.entity, enemyId = r.enemyId, x = x })\n\tend\nend\ntable.sort(list, function(a, b) return a.x < b.x end)\nlocal mult = 1 + (self.Floor - 1) * 0.6\nlocal n = #list\nif n > 4 then n = 4 end\nfor i = 1, n do\n\tlocal item = list[i]\n\tlocal e = self.Enemies[item.enemyId]\n\tif e == nil then e = { name = item.enemyId, maxHp = 10, intents = { { kind = \"Attack\", value = 5 } } } end\n\tlocal intents = {}\n\tfor k = 1, #e.intents do\n\t\tintents[k] = { kind = e.intents[k].kind, value = math.floor(e.intents[k].value * mult) }\n\tend\n\tlocal maxHp = math.floor(e.maxHp * mult)\n\tself.Monsters[i] = { entity = item.entity, enemyId = item.enemyId, name = e.name,\n\t\thp = maxHp, maxHp = maxHp, block = 0, intents = intents, intentIdx = 1, alive = true, slot = i }\n\tself:ReviveMonsterEntity(item.entity)\n\tself:PositionMonsterSlot(i)\nend\nself.TargetIndex = 1",
"Scope": 2, "Scope": 2,
"ExecSpace": 6, "ExecSpace": 6,
"Attributes": [], "Attributes": [],
@@ -1281,7 +1267,7 @@
"Name": "slot" "Name": "slot"
} }
], ],
"Code": "local sp = self.ActiveSlotPos\nif sp == nil or sp[slot] == nil then\n\treturn\nend\nlocal e = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/CombatHud/MonsterSlot\" .. tostring(slot))\nif e ~= nil and e.UITransformComponent ~= nil then\n\te.UITransformComponent.anchoredPosition = Vector2(sp[slot].x, sp[slot].y)\nend", "Code": "local m = self.Monsters[slot]\nif m == nil or m.entity == nil or not isvalid(m.entity) then\n\treturn\nend\nlocal tr = m.entity.TransformComponent\nif tr == nil then\n\treturn\nend\nlocal wp = tr.WorldPosition\nlocal screen = _UILogic:WorldToScreenPosition(Vector2(wp.x, wp.y + 1.4))\nlocal uipos = _UILogic:ScreenToUIPosition(screen)\nlocal e = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/CombatHud/MonsterSlot\" .. tostring(slot))\nif e ~= nil and e.UITransformComponent ~= nil then\n\te.UITransformComponent.anchoredPosition = uipos\nend",
"Scope": 2, "Scope": 2,
"ExecSpace": 6, "ExecSpace": 6,
"Attributes": [], "Attributes": [],

View File

@@ -44,6 +44,50 @@
{ "kind": "Attack", "value": 8 }, { "kind": "Attack", "value": 8 },
{ "kind": "Attack", "value": 4 } { "kind": "Attack", "value": 4 }
] ]
},
"pig": {
"name": "돼지",
"maxHp": 18,
"intents": [
{ "kind": "Attack", "value": 6 },
{ "kind": "Defend", "value": 3 }
]
},
"green_mushroom": {
"name": "초록버섯",
"maxHp": 20,
"intents": [
{ "kind": "Attack", "value": 7 },
{ "kind": "Attack", "value": 4 }
]
},
"mushmom": {
"name": "머쉬맘",
"maxHp": 75,
"intents": [
{ "kind": "Attack", "value": 14 },
{ "kind": "Defend", "value": 10 },
{ "kind": "Attack", "value": 9 }
]
},
"modified_snail": {
"name": "변형된 달팽이",
"maxHp": 60,
"intents": [
{ "kind": "Attack", "value": 12 },
{ "kind": "Attack", "value": 7 },
{ "kind": "Defend", "value": 8 }
]
},
"king_slime": {
"name": "킹 슬라임",
"maxHp": 130,
"intents": [
{ "kind": "Attack", "value": 18 },
{ "kind": "Defend", "value": 14 },
{ "kind": "Attack", "value": 12 },
{ "kind": "Attack", "value": 24 }
]
} }
}, },
"activeEnemy": "slime", "activeEnemy": "slime",

View File

@@ -1,20 +0,0 @@
{
"combat": [
{ "x": 430, "y": 140 },
{ "x": 600, "y": 140 },
{ "x": 770, "y": 140 },
{ "x": 900, "y": 140 }
],
"elite": [
{ "x": 430, "y": 160 },
{ "x": 650, "y": 160 },
{ "x": 850, "y": 160 },
{ "x": 980, "y": 160 }
],
"boss": [
{ "x": 520, "y": 200 },
{ "x": 760, "y": 160 },
{ "x": 940, "y": 150 },
{ "x": 1040, "y": 150 }
]
}

View File

@@ -6652,7 +6652,7 @@
{ {
"@type": "script.CombatMonster", "@type": "script.CombatMonster",
"Enable": true, "Enable": true,
"EnemyId": "orange_mushroom", "EnemyId": "pig",
"Group": "combat" "Group": "combat"
} }
], ],
@@ -6794,12 +6794,442 @@
{ {
"@type": "script.CombatMonster", "@type": "script.CombatMonster",
"Enable": true, "Enable": true,
"EnemyId": "orange_mushroom", "EnemyId": "green_mushroom",
"Group": "combat" "Group": "combat"
} }
], ],
"@version": 1 "@version": 1
} }
},
{
"id": "b289e720-40fa-4c28-b5bf-6cd30ddf9c6b",
"path": "/maps/map01/머쉬맘",
"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,script.CombatMonster",
"jsonString": {
"name": "머쉬맘",
"path": "/maps/map01/머쉬맘",
"nameEditable": true,
"enable": true,
"visible": true,
"localize": false,
"displayOrder": 7,
"pathConstraints": "///",
"revision": 1,
"origin": {
"type": "Model",
"entry_id": "ChaseMonster",
"sub_entity_id": null,
"root_entity_id": "b289e720-40fa-4c28-b5bf-6cd30ddf9c6b",
"replaced_model_id": null
},
"modelId": "chasemonster",
"@components": [
{
"@type": "MOD.Core.TransformComponent",
"Position": {
"x": 3,
"y": 0.03499998,
"z": 999.999
},
"QuaternionRotation": {
"x": 0,
"y": 0,
"z": 0,
"w": 1
},
"Enable": true
},
{
"@type": "MOD.Core.StateAnimationComponent",
"ActionSheet": {
"move": "f3bf3c053e444315b83a9663ea85a37f",
"stand": "2688d39a56874f24aa0d46efbf89ff21",
"jump": "2f45374c97224e0cb108241398710a3c",
"attack": "907f4089e0b840a993967c5211af9b89",
"hit": "b771488c84fe437e9707f233c48ec477",
"die": "c03225c35c124bcbaf9ec9299b7d8a1f"
},
"Enable": true
},
{
"@type": "MOD.Core.SpriteRendererComponent",
"ActionSheet": {},
"EndFrameIndex": 0,
"RenderSetting": 1,
"SortingLayer": "MapLayer0",
"SpriteRUID": "2688d39a56874f24aa0d46efbf89ff21",
"StartFrameIndex": 0,
"Enable": true
},
{
"@type": "MOD.Core.RigidbodyComponent",
"MoveVelocity": {
"x": 0,
"y": 0
},
"RealMoveVelocity": {
"x": 0,
"y": 0
},
"Enable": true
},
{
"@type": "MOD.Core.MovementComponent",
"InputSpeed": 1.5,
"JumpForce": 6,
"Enable": true
},
{
"@type": "MOD.Core.StateComponent",
"IsLegacy": false,
"Enable": true
},
{
"@type": "MOD.Core.HitComponent",
"BoxSize": {
"x": 1.19,
"y": 1.1
},
"ColliderOffset": {
"x": 0.005000055,
"y": 0.55
},
"IsLegacy": false,
"Enable": true
},
{
"@type": "MOD.Core.DamageSkinSpawnerComponent",
"Enable": true
},
{
"@type": "script.Monster",
"Enable": true,
"IsDead": false
},
{
"@type": "script.MonsterAttack",
"Enable": true,
"SpriteSize": {
"x": 0,
"y": 0
},
"PositionOffset": {
"x": 0,
"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": "script.CombatMonster",
"Enable": true,
"EnemyId": "mushmom",
"Group": "elite"
}
],
"@version": 1
}
},
{
"id": "aa4b464c-a4ea-48e7-a475-9015771ca2ef",
"path": "/maps/map01/변형된 달팽이",
"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,script.CombatMonster",
"jsonString": {
"name": "변형된 달팽이",
"path": "/maps/map01/변형된 달팽이",
"nameEditable": true,
"enable": true,
"visible": true,
"localize": false,
"displayOrder": 8,
"pathConstraints": "///",
"revision": 1,
"origin": {
"type": "Model",
"entry_id": "MoveMonster",
"sub_entity_id": null,
"root_entity_id": "aa4b464c-a4ea-48e7-a475-9015771ca2ef",
"replaced_model_id": null
},
"modelId": "movemonster",
"@components": [
{
"@type": "MOD.Core.TransformComponent",
"Position": {
"x": 5,
"y": 0.03499998,
"z": 999.999
},
"QuaternionRotation": {
"x": 0,
"y": 0,
"z": 0,
"w": 1
},
"Enable": true
},
{
"@type": "MOD.Core.StateAnimationComponent",
"ActionSheet": {
"stand": "be8403cdd1534522ac060ec8497371cd",
"move": "1e24c789979b4ababce55b34861ace25",
"attack": "11eb051bbc134831851e8764befc155e",
"skill": "220a397a05f4460395721591cdcc023d",
"hit": "7200391494bb4dea8109202bb3e2cf47",
"die": "c5d61976c96c4130903ab227c7bf72ce"
},
"Enable": true
},
{
"@type": "MOD.Core.SpriteRendererComponent",
"ActionSheet": {},
"EndFrameIndex": 0,
"RenderSetting": 1,
"SortingLayer": "MapLayer0",
"SpriteRUID": "1e24c789979b4ababce55b34861ace25",
"StartFrameIndex": 0,
"Enable": true
},
{
"@type": "MOD.Core.RigidbodyComponent",
"MoveVelocity": {
"x": 0,
"y": 0
},
"RealMoveVelocity": {
"x": 0,
"y": 0
},
"Enable": true
},
{
"@type": "MOD.Core.MovementComponent",
"InputSpeed": 1,
"JumpForce": 0,
"Enable": true
},
{
"@type": "MOD.Core.StateComponent",
"IsLegacy": false,
"Enable": true
},
{
"@type": "MOD.Core.HitComponent",
"BoxSize": {
"x": 1.42,
"y": 1.42
},
"ColliderOffset": {
"x": -0.0500000119,
"y": 0.69
},
"IsLegacy": false,
"Enable": true
},
{
"@type": "MOD.Core.DamageSkinSpawnerComponent",
"Enable": true
},
{
"@type": "script.Monster",
"Enable": true,
"IsDead": false
},
{
"@type": "script.MonsterAttack",
"Enable": true,
"SpriteSize": {
"x": 0,
"y": 0
},
"PositionOffset": {
"x": 0,
"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": "script.CombatMonster",
"Enable": true,
"EnemyId": "modified_snail",
"Group": "elite"
}
],
"@version": 1
}
},
{
"id": "5d2a7707-d1d2-4f89-b87f-86786f4a305e",
"path": "/maps/map01/monster-2326",
"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,script.CombatMonster",
"jsonString": {
"name": "monster-2326",
"path": "/maps/map01/monster-2326",
"nameEditable": true,
"enable": true,
"visible": true,
"localize": false,
"displayOrder": 9,
"pathConstraints": "///",
"revision": 1,
"origin": {
"type": "Model",
"entry_id": "ChaseMonster",
"sub_entity_id": null,
"root_entity_id": "5d2a7707-d1d2-4f89-b87f-86786f4a305e",
"replaced_model_id": null
},
"modelId": "chasemonster",
"@components": [
{
"@type": "MOD.Core.TransformComponent",
"Position": {
"x": 4,
"y": 0.03499998,
"z": 999.999
},
"QuaternionRotation": {
"x": 0,
"y": 0,
"z": 0,
"w": 1
},
"Enable": true
},
{
"@type": "MOD.Core.StateAnimationComponent",
"ActionSheet": {
"move": "297ad7bed08046e8a4a0bb47fed0f7d1",
"stand": "6401ef4c5ccb4269a5c97831fa8fd329",
"jump": "e4ae46df71794213b2a5432906ed1f85",
"attack": "763ee1a40786483f9671435c1a8e8537",
"skill": "bf6150842fff4f4aa0f3e8e97b574e59",
"hit": "a660f29769d74e4b81c49d9435a7f5b2",
"die": "193c446258954f9cb7dfddab7b498bd8"
},
"Enable": true
},
{
"@type": "MOD.Core.SpriteRendererComponent",
"ActionSheet": {},
"EndFrameIndex": 0,
"RenderSetting": 1,
"SortingLayer": "MapLayer0",
"SpriteRUID": "6401ef4c5ccb4269a5c97831fa8fd329",
"StartFrameIndex": 0,
"Enable": true
},
{
"@type": "MOD.Core.RigidbodyComponent",
"MoveVelocity": {
"x": 0,
"y": 0
},
"RealMoveVelocity": {
"x": 0,
"y": 0
},
"Enable": true
},
{
"@type": "MOD.Core.MovementComponent",
"InputSpeed": 1.5,
"JumpForce": 6,
"Enable": true
},
{
"@type": "MOD.Core.StateComponent",
"IsLegacy": false,
"Enable": true
},
{
"@type": "MOD.Core.HitComponent",
"BoxSize": {
"x": 2.19,
"y": 1.39
},
"ColliderOffset": {
"x": 0.335000038,
"y": 0.695
},
"IsLegacy": false,
"Enable": true
},
{
"@type": "MOD.Core.DamageSkinSpawnerComponent",
"Enable": true
},
{
"@type": "script.Monster",
"Enable": true,
"IsDead": false
},
{
"@type": "script.MonsterAttack",
"Enable": true,
"SpriteSize": {
"x": 0,
"y": 0
},
"PositionOffset": {
"x": 0,
"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": "script.CombatMonster",
"Enable": true,
"EnemyId": "king_slime",
"Group": "boss"
}
],
"@version": 1
}
} }
] ]
} }

View File

@@ -26,7 +26,6 @@ for (const [id, n] of Object.entries(MAP.nodes)) {
const MAX_ROW = Math.max(...Object.values(MAP.nodes).map((n) => n.row)); const MAX_ROW = Math.max(...Object.values(MAP.nodes).map((n) => n.row));
const RELICS = JSON.parse(readFileSync('data/relics.json', 'utf8')); const RELICS = JSON.parse(readFileSync('data/relics.json', 'utf8'));
const SLOTS = JSON.parse(readFileSync('data/monster-slots.json', 'utf8'));
if (!RELICS.relics[RELICS.startingRelic]) throw new Error(`[gen-slaydeck] startingRelic 없음: ${RELICS.startingRelic}`); if (!RELICS.relics[RELICS.startingRelic]) throw new Error(`[gen-slaydeck] startingRelic 없음: ${RELICS.startingRelic}`);
for (const id of RELICS.relicPool) { for (const id of RELICS.relicPool) {
if (!RELICS.relics[id]) throw new Error(`[gen-slaydeck] relicPool에 없는 유물 id: ${id}`); if (!RELICS.relics[id]) throw new Error(`[gen-slaydeck] relicPool에 없는 유물 id: ${id}`);
@@ -73,9 +72,6 @@ function luaCardsTable(cards) {
function luaDeckTable(deck) { function luaDeckTable(deck) {
return `self.DrawPile = { ${deck.map(luaStr).join(', ')} }`; return `self.DrawPile = { ${deck.map(luaStr).join(', ')} }`;
} }
function luaSlotGroup(arr) {
return '{ ' + arr.map((s) => `{ x = ${s.x}, y = ${s.y} }`).join(', ') + ' }';
}
const UI_FILE = 'ui/DefaultGroup.ui'; const UI_FILE = 'ui/DefaultGroup.ui';
const COMMON_FILE = 'Global/common.gamelogic'; const COMMON_FILE = 'Global/common.gamelogic';
@@ -88,6 +84,7 @@ const DEFEND = { r: 0.42, g: 0.55, b: 0.85, a: 1 };
const SKILL = { r: 0.46, g: 0.68, b: 0.52, a: 1 }; const SKILL = { r: 0.46, g: 0.68, b: 0.52, a: 1 };
const MAX_MONSTERS = 4; const MAX_MONSTERS = 4;
const HEAD_OFFSET_Y = 1.4; // 몬스터 월드 원점 위로 띄울 높이(머리 위) — world→screen 변환 전 가산
const HP_BAR_W = 120; const HP_BAR_W = 120;
const CARD_W = 180; const CARD_W = 180;
@@ -311,11 +308,6 @@ function entity({ id, path, modelId, entryId, componentNames, components, displa
} }
function upsertUi() { function upsertUi() {
for (const g of ['combat', 'elite', 'boss']) {
if (!Array.isArray(SLOTS[g]) || SLOTS[g].length < MAX_MONSTERS) {
throw new Error(`[gen-slaydeck] monster-slots.json "${g}" 그룹 좌표(${SLOTS[g] ? SLOTS[g].length : 0}) < MAX_MONSTERS(${MAX_MONSTERS})`);
}
}
const ui = JSON.parse(readFileSync(UI_FILE, 'utf8')); const ui = JSON.parse(readFileSync(UI_FILE, 'utf8'));
const E = ui.ContentProto.Entities; const E = ui.ContentProto.Entities;
ui.ContentProto.Entities = E.filter((e) => !e.path.startsWith('/ui/DefaultGroup/DeckHud') && !e.path.startsWith('/ui/DefaultGroup/DeckInspectHud') && !e.path.startsWith('/ui/DefaultGroup/DeckAllHud') && !e.path.startsWith('/ui/DefaultGroup/CombatHud') && !e.path.startsWith('/ui/DefaultGroup/RewardHud') && !e.path.startsWith('/ui/DefaultGroup/MapHud') && !e.path.startsWith('/ui/DefaultGroup/ShopHud') && !e.path.startsWith('/ui/DefaultGroup/RestHud') && !e.path.startsWith('/ui/DefaultGroup/MainMenu') && !e.path.startsWith('/ui/DefaultGroup/CharacterSelectHud')); ui.ContentProto.Entities = E.filter((e) => !e.path.startsWith('/ui/DefaultGroup/DeckHud') && !e.path.startsWith('/ui/DefaultGroup/DeckInspectHud') && !e.path.startsWith('/ui/DefaultGroup/DeckAllHud') && !e.path.startsWith('/ui/DefaultGroup/CombatHud') && !e.path.startsWith('/ui/DefaultGroup/RewardHud') && !e.path.startsWith('/ui/DefaultGroup/MapHud') && !e.path.startsWith('/ui/DefaultGroup/ShopHud') && !e.path.startsWith('/ui/DefaultGroup/RestHud') && !e.path.startsWith('/ui/DefaultGroup/MainMenu') && !e.path.startsWith('/ui/DefaultGroup/CharacterSelectHud'));
@@ -1564,8 +1556,6 @@ function writeCodeblocks() {
prop('any', 'Monsters'), prop('any', 'Monsters'),
prop('any', 'Registered'), prop('any', 'Registered'),
prop('number', 'TargetIndex', '1'), prop('number', 'TargetIndex', '1'),
prop('any', 'SlotPos'),
prop('any', 'ActiveSlotPos'),
prop('any', 'RunDeck'), prop('any', 'RunDeck'),
prop('number', 'Gold', '0'), prop('number', 'Gold', '0'),
prop('number', 'Floor', '0'), prop('number', 'Floor', '0'),
@@ -1677,7 +1667,6 @@ self.RelicPool = { ${RELICS.relicPool.map(luaStr).join(', ')} }
${luaEnemiesTable(ENEMIES.enemies)} ${luaEnemiesTable(ENEMIES.enemies)}
${luaMapNodesTable(MAP.nodes)} ${luaMapNodesTable(MAP.nodes)}
${luaStartArray(MAP.start)} ${luaStartArray(MAP.start)}
self.SlotPos = { combat = ${luaSlotGroup(SLOTS.combat)}, elite = ${luaSlotGroup(SLOTS.elite)}, boss = ${luaSlotGroup(SLOTS.boss)} }
self.CurrentNodeId = "" self.CurrentNodeId = ""
self.CurrentEnemyId = "" self.CurrentEnemyId = ""
self:BindButtons() self:BindButtons()
@@ -1718,7 +1707,6 @@ table.insert(self.Registered, { entity = monster, enemyId = enemyId, group = g }
local g = "combat" local g = "combat"
local node = self.MapNodes[self.CurrentNodeId] local node = self.MapNodes[self.CurrentNodeId]
if node ~= nil and node.type ~= nil then g = node.type end if node ~= nil and node.type ~= nil then g = node.type end
self.ActiveSlotPos = self.SlotPos[g]
local reg = self.Registered or {} local reg = self.Registered or {}
for i = 1, #reg do for i = 1, #reg do
if reg[i].entity ~= nil and isvalid(reg[i].entity) then if reg[i].entity ~= nil and isvalid(reg[i].entity) then
@@ -2285,13 +2273,20 @@ e.UITransformComponent.RectSize = Vector2(w, 14)`, [
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'hp' }, { Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'hp' },
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'maxHp' }, { Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'maxHp' },
]), ]),
method('PositionMonsterSlot', `local sp = self.ActiveSlotPos method('PositionMonsterSlot', `local m = self.Monsters[slot]
if sp == nil or sp[slot] == nil then if m == nil or m.entity == nil or not isvalid(m.entity) then
return return
end end
local tr = m.entity.TransformComponent
if tr == nil then
return
end
local wp = tr.WorldPosition
local screen = _UILogic:WorldToScreenPosition(Vector2(wp.x, wp.y + ${HEAD_OFFSET_Y}))
local uipos = _UILogic:ScreenToUIPosition(screen)
local e = _EntityService:GetEntityByPath("/ui/DefaultGroup/CombatHud/MonsterSlot" .. tostring(slot)) local e = _EntityService:GetEntityByPath("/ui/DefaultGroup/CombatHud/MonsterSlot" .. tostring(slot))
if e ~= nil and e.UITransformComponent ~= nil then if e ~= nil and e.UITransformComponent ~= nil then
e.UITransformComponent.anchoredPosition = Vector2(sp[slot].x, sp[slot].y) e.UITransformComponent.anchoredPosition = uipos
end`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' }]), end`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' }]),
method('SetTarget', `if self.Monsters[slot] ~= nil and self.Monsters[slot].alive == true then method('SetTarget', `if self.Monsters[slot] ~= nil and self.Monsters[slot].alive == true then
self.TargetIndex = slot self.TargetIndex = slot