분기 맵 노드 진행 (TODO E3) — 경로 선택·적 차등·보스 클리어 #15
@@ -203,6 +203,41 @@
|
||||
"SyncDirection": 0,
|
||||
"Attributes": [],
|
||||
"Name": "RunActive"
|
||||
},
|
||||
{
|
||||
"Type": "any",
|
||||
"DefaultValue": "nil",
|
||||
"SyncDirection": 0,
|
||||
"Attributes": [],
|
||||
"Name": "Enemies"
|
||||
},
|
||||
{
|
||||
"Type": "any",
|
||||
"DefaultValue": "nil",
|
||||
"SyncDirection": 0,
|
||||
"Attributes": [],
|
||||
"Name": "MapNodes"
|
||||
},
|
||||
{
|
||||
"Type": "any",
|
||||
"DefaultValue": "nil",
|
||||
"SyncDirection": 0,
|
||||
"Attributes": [],
|
||||
"Name": "MapStart"
|
||||
},
|
||||
{
|
||||
"Type": "string",
|
||||
"DefaultValue": "\"\"",
|
||||
"SyncDirection": 0,
|
||||
"Attributes": [],
|
||||
"Name": "CurrentNodeId"
|
||||
},
|
||||
{
|
||||
"Type": "string",
|
||||
"DefaultValue": "\"\"",
|
||||
"SyncDirection": 0,
|
||||
"Attributes": [],
|
||||
"Name": "CurrentEnemyId"
|
||||
}
|
||||
],
|
||||
"Methods": [
|
||||
@@ -230,7 +265,7 @@
|
||||
"Name": null
|
||||
},
|
||||
"Arguments": [],
|
||||
"Code": "self.PlayerMaxHp = 80\nself.PlayerHp = self.PlayerMaxHp\nself.Gold = 0\nself.Floor = 0\nself.RunLength = 3\nself.RunDeck = { \"Strike\", \"Strike\", \"Strike\", \"Strike\", \"Strike\", \"Defend\", \"Defend\", \"Defend\", \"Defend\", \"Bash\" }\nself.RunActive = true\nself:BindButtons()\nself:StartCombat()",
|
||||
"Code": "self.PlayerMaxHp = 80\nself.PlayerHp = self.PlayerMaxHp\nself.Gold = 0\nself.Floor = 0\nself.RunLength = 3\nself.RunDeck = { \"Strike\", \"Strike\", \"Strike\", \"Strike\", \"Strike\", \"Defend\", \"Defend\", \"Defend\", \"Defend\", \"Bash\" }\nself.RunActive = true\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}\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 = { \"D\", \"E\" } },\n\tC = { type = \"elite\", enemy = \"slime_elite\", row = 2, col = -2, next = { \"BOSS\" } },\n\tD = { type = \"combat\", enemy = \"slime\", row = 2, col = 0, next = { \"BOSS\" } },\n\tE = { type = \"combat\", enemy = \"slime\", row = 2, col = 2, next = { \"BOSS\" } },\n\tBOSS = { type = \"boss\", enemy = \"slime_boss\", row = 3, col = 0, next = { } },\n}\nself.MapStart = { \"A\", \"B\" }\nself.CurrentNodeId = \"\"\nself.CurrentEnemyId = \"\"\nself:BindButtons()\nself:ShowMap()",
|
||||
"Scope": 2,
|
||||
"ExecSpace": 6,
|
||||
"Attributes": [],
|
||||
@@ -245,7 +280,7 @@
|
||||
"Name": null
|
||||
},
|
||||
"Arguments": [],
|
||||
"Code": "self.MaxEnergy = 3\nself.Turn = 0\nself.Floor = self.Floor + 1\nself.PlayerBlock = 0\nself.EnemyName = \"슬라임\"\nself.EnemyMaxHp = 45\nself.EnemyHp = self.EnemyMaxHp\nself.EnemyBlock = 0\nself.EnemyIntents = {\n\t{ kind = \"Attack\", value = 10 },\n\t{ kind = \"Attack\", value = 6 },\n\t{ kind = \"Defend\", value = 8 },\n}\nself.EnemyIntentIndex = 1\nself.CombatOver = false\nself.DiscardPile = {}\nself.Hand = {}\nself.Cards = {\n\tStrike = { name = \"타격\", cost = 1, desc = \"피해 6\", kind = \"Attack\", damage = 6 },\n\tDefend = { name = \"방어\", cost = 1, desc = \"방어도 5\", kind = \"Skill\", block = 5 },\n\tBash = { name = \"강타\", cost = 2, desc = \"피해 10\", kind = \"Attack\", damage = 10 },\n}\nself.DrawPile = {}\nfor i = 1, #self.RunDeck do\n\tself.DrawPile[i] = self.RunDeck[i]\nend\nself:Shuffle(self.DrawPile)\nself:RenderCombat()\nself:StartPlayerTurn()",
|
||||
"Code": "self.MaxEnergy = 3\nself.Turn = 0\nlocal node = self.MapNodes[self.CurrentNodeId]\nif node ~= nil then\n\tself.Floor = node.row\nend\nlocal enemy = self.Enemies[self.CurrentEnemyId]\nself.PlayerBlock = 0\nself.EnemyName = enemy.name\nself.EnemyMaxHp = enemy.maxHp\nself.EnemyHp = self.EnemyMaxHp\nself.EnemyBlock = 0\nself.EnemyIntents = enemy.intents\nself.EnemyIntentIndex = 1\nself.CombatOver = false\nself.DiscardPile = {}\nself.Hand = {}\nself.Cards = {\n\tStrike = { name = \"타격\", cost = 1, desc = \"피해 6\", kind = \"Attack\", damage = 6 },\n\tDefend = { name = \"방어\", cost = 1, desc = \"방어도 5\", kind = \"Skill\", block = 5 },\n\tBash = { name = \"강타\", cost = 2, desc = \"피해 10\", kind = \"Attack\", damage = 10 },\n}\nself.DrawPile = {}\nfor i = 1, #self.RunDeck do\n\tself.DrawPile[i] = self.RunDeck[i]\nend\nself:Shuffle(self.DrawPile)\nself:RenderCombat()\nself:StartPlayerTurn()",
|
||||
"Scope": 2,
|
||||
"ExecSpace": 6,
|
||||
"Attributes": [],
|
||||
@@ -283,7 +318,7 @@
|
||||
"Name": null
|
||||
},
|
||||
"Arguments": [],
|
||||
"Code": "local endTurn = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/DeckHud/EndTurnButton\")\nif endTurn ~= nil and endTurn.ButtonComponent ~= nil then\n\tif self.EndTurnHandler ~= nil then\n\t\tendTurn:DisconnectEvent(ButtonClickEvent, self.EndTurnHandler)\n\t\tself.EndTurnHandler = nil\n\tend\n\tself.EndTurnHandler = endTurn:ConnectEvent(ButtonClickEvent, function() self:EndPlayerTurn() end)\nend\nfor i = 1, 5 do\n\tlocal cardEntity = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/CardHand/Card\" .. tostring(i))\n\tif cardEntity ~= nil and cardEntity.ButtonComponent ~= nil then\n\t\tcardEntity:ConnectEvent(ButtonClickEvent, function() self:PlayCard(i) end)\n\tend\nend\nfor i = 1, 3 do\n\tlocal rc = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/RewardHud/Reward\" .. tostring(i))\n\tif rc ~= nil and rc.ButtonComponent ~= nil then\n\t\trc:ConnectEvent(ButtonClickEvent, function() self:PickReward(i) end)\n\tend\nend\nlocal skip = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/RewardHud/Skip\")\nif skip ~= nil and skip.ButtonComponent ~= nil then\n\tskip:ConnectEvent(ButtonClickEvent, function() self:PickReward(0) end)\nend",
|
||||
"Code": "local endTurn = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/DeckHud/EndTurnButton\")\nif endTurn ~= nil and endTurn.ButtonComponent ~= nil then\n\tif self.EndTurnHandler ~= nil then\n\t\tendTurn:DisconnectEvent(ButtonClickEvent, self.EndTurnHandler)\n\t\tself.EndTurnHandler = nil\n\tend\n\tself.EndTurnHandler = endTurn:ConnectEvent(ButtonClickEvent, function() self:EndPlayerTurn() end)\nend\nfor i = 1, 5 do\n\tlocal cardEntity = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/CardHand/Card\" .. tostring(i))\n\tif cardEntity ~= nil and cardEntity.ButtonComponent ~= nil then\n\t\tcardEntity:ConnectEvent(ButtonClickEvent, function() self:PlayCard(i) end)\n\tend\nend\nfor i = 1, 3 do\n\tlocal rc = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/RewardHud/Reward\" .. tostring(i))\n\tif rc ~= nil and rc.ButtonComponent ~= nil then\n\t\trc:ConnectEvent(ButtonClickEvent, function() self:PickReward(i) end)\n\tend\nend\nlocal skip = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/RewardHud/Skip\")\nif skip ~= nil and skip.ButtonComponent ~= nil then\n\tskip:ConnectEvent(ButtonClickEvent, function() self:PickReward(0) end)\nend\nlocal mapNodeIds = { \"A\", \"B\", \"C\", \"D\", \"E\", \"BOSS\" }\nfor i = 1, #mapNodeIds do\n\tlocal nid = mapNodeIds[i]\n\tlocal mn = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/MapHud/Node_\" .. nid)\n\tif mn ~= nil and mn.ButtonComponent ~= nil then\n\t\tmn:ConnectEvent(ButtonClickEvent, function() self:PickNode(nid) end)\n\tend\nend",
|
||||
"Scope": 2,
|
||||
"ExecSpace": 6,
|
||||
"Attributes": [],
|
||||
@@ -615,7 +650,7 @@
|
||||
"Name": null
|
||||
},
|
||||
"Arguments": [],
|
||||
"Code": "if self.EnemyHp <= 0 then\n\tself.CombatOver = true\n\tself.Gold = self.Gold + 15\n\tself:RenderRun()\n\tif self.Floor >= self.RunLength then\n\t\tself:ShowResult(\"런 클리어!\")\n\t\tself.RunActive = false\n\telse\n\t\tself:OfferReward()\n\tend\nelseif self.PlayerHp <= 0 then\n\tself.CombatOver = true\n\tself:ShowResult(\"패배...\")\n\tself.RunActive = false\nend",
|
||||
"Code": "if self.EnemyHp <= 0 then\n\tself.CombatOver = true\n\tself.Gold = self.Gold + 15\n\tself:RenderRun()\n\tlocal node = self.MapNodes[self.CurrentNodeId]\n\tif node ~= nil and node.type == \"boss\" then\n\t\tself:ShowResult(\"런 클리어!\")\n\t\tself.RunActive = false\n\telse\n\t\tself:OfferReward()\n\tend\nelseif self.PlayerHp <= 0 then\n\tself.CombatOver = true\n\tself:ShowResult(\"패배...\")\n\tself.RunActive = false\nend",
|
||||
"Scope": 2,
|
||||
"ExecSpace": 6,
|
||||
"Attributes": [],
|
||||
@@ -736,11 +771,87 @@
|
||||
"Name": "slot"
|
||||
}
|
||||
],
|
||||
"Code": "if self.CombatOver ~= true or self.RunActive ~= true then\n\treturn\nend\nif slot ~= 0 and self.RewardChoices ~= nil then\n\tlocal id = self.RewardChoices[slot]\n\tif id ~= nil then\n\t\ttable.insert(self.RunDeck, id)\n\tend\nend\nlocal hud = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/RewardHud\")\nif hud ~= nil then\n\thud.Enable = false\nend\nself:StartCombat()",
|
||||
"Code": "if self.CombatOver ~= true or self.RunActive ~= true then\n\treturn\nend\nif slot ~= 0 and self.RewardChoices ~= nil then\n\tlocal id = self.RewardChoices[slot]\n\tif id ~= nil then\n\t\ttable.insert(self.RunDeck, id)\n\tend\nend\nlocal hud = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/RewardHud\")\nif hud ~= nil then\n\thud.Enable = false\nend\nself:ShowMap()",
|
||||
"Scope": 2,
|
||||
"ExecSpace": 6,
|
||||
"Attributes": [],
|
||||
"Name": "PickReward"
|
||||
},
|
||||
{
|
||||
"Return": {
|
||||
"Type": "void",
|
||||
"DefaultValue": null,
|
||||
"SyncDirection": 0,
|
||||
"Attributes": [],
|
||||
"Name": null
|
||||
},
|
||||
"Arguments": [],
|
||||
"Code": "self:RenderMap()\nlocal hud = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/MapHud\")\nif hud ~= nil then\n\thud.Enable = true\nend",
|
||||
"Scope": 2,
|
||||
"ExecSpace": 6,
|
||||
"Attributes": [],
|
||||
"Name": "ShowMap"
|
||||
},
|
||||
{
|
||||
"Return": {
|
||||
"Type": "boolean",
|
||||
"DefaultValue": null,
|
||||
"SyncDirection": 0,
|
||||
"Attributes": [],
|
||||
"Name": null
|
||||
},
|
||||
"Arguments": [
|
||||
{
|
||||
"Type": "string",
|
||||
"DefaultValue": null,
|
||||
"SyncDirection": 0,
|
||||
"Attributes": [],
|
||||
"Name": "id"
|
||||
}
|
||||
],
|
||||
"Code": "local list\nif self.CurrentNodeId == \"\" then\n\tlist = self.MapStart\nelse\n\tlocal node = self.MapNodes[self.CurrentNodeId]\n\tif node == nil then\n\t\treturn false\n\tend\n\tlist = node.next\nend\nfor i = 1, #list do\n\tif list[i] == id then\n\t\treturn true\n\tend\nend\nreturn false",
|
||||
"Scope": 2,
|
||||
"ExecSpace": 6,
|
||||
"Attributes": [],
|
||||
"Name": "IsReachable"
|
||||
},
|
||||
{
|
||||
"Return": {
|
||||
"Type": "void",
|
||||
"DefaultValue": null,
|
||||
"SyncDirection": 0,
|
||||
"Attributes": [],
|
||||
"Name": null
|
||||
},
|
||||
"Arguments": [],
|
||||
"Code": "for id, node in pairs(self.MapNodes) do\n\tlocal e = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/MapHud/Node_\" .. id)\n\tif e ~= nil then\n\t\tlocal reachable = self:IsReachable(id)\n\t\tif e.SpriteGUIRendererComponent ~= nil then\n\t\t\tif reachable then\n\t\t\t\te.SpriteGUIRendererComponent.Color = Color(0.3, 0.55, 0.85, 1)\n\t\t\telse\n\t\t\t\te.SpriteGUIRendererComponent.Color = Color(0.2, 0.22, 0.26, 0.6)\n\t\t\tend\n\t\tend\n\t\tif e.ButtonComponent ~= nil then\n\t\t\te.ButtonComponent.Enable = reachable\n\t\tend\n\tend\nend",
|
||||
"Scope": 2,
|
||||
"ExecSpace": 6,
|
||||
"Attributes": [],
|
||||
"Name": "RenderMap"
|
||||
},
|
||||
{
|
||||
"Return": {
|
||||
"Type": "void",
|
||||
"DefaultValue": null,
|
||||
"SyncDirection": 0,
|
||||
"Attributes": [],
|
||||
"Name": null
|
||||
},
|
||||
"Arguments": [
|
||||
{
|
||||
"Type": "string",
|
||||
"DefaultValue": null,
|
||||
"SyncDirection": 0,
|
||||
"Attributes": [],
|
||||
"Name": "id"
|
||||
}
|
||||
],
|
||||
"Code": "if self.RunActive ~= true then\n\treturn\nend\nif self:IsReachable(id) ~= true then\n\treturn\nend\nself.CurrentNodeId = id\nself.CurrentEnemyId = self.MapNodes[id].enemy\nlocal hud = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/MapHud\")\nif hud ~= nil then\n\thud.Enable = false\nend\nself:StartCombat()",
|
||||
"Scope": 2,
|
||||
"ExecSpace": 6,
|
||||
"Attributes": [],
|
||||
"Name": "PickNode"
|
||||
}
|
||||
],
|
||||
"EntityEventHandlers": []
|
||||
|
||||
@@ -8,6 +8,25 @@
|
||||
{ "kind": "Attack", "value": 6 },
|
||||
{ "kind": "Defend", "value": 8 }
|
||||
]
|
||||
},
|
||||
"slime_elite": {
|
||||
"name": "정예 슬라임",
|
||||
"maxHp": 70,
|
||||
"intents": [
|
||||
{ "kind": "Attack", "value": 14 },
|
||||
{ "kind": "Attack", "value": 8 },
|
||||
{ "kind": "Defend", "value": 10 }
|
||||
]
|
||||
},
|
||||
"slime_boss": {
|
||||
"name": "슬라임 킹",
|
||||
"maxHp": 120,
|
||||
"intents": [
|
||||
{ "kind": "Attack", "value": 18 },
|
||||
{ "kind": "Defend", "value": 12 },
|
||||
{ "kind": "Attack", "value": 10 },
|
||||
{ "kind": "Attack", "value": 22 }
|
||||
]
|
||||
}
|
||||
},
|
||||
"activeEnemy": "slime"
|
||||
|
||||
11
data/map.json
Normal file
11
data/map.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"start": ["A", "B"],
|
||||
"nodes": {
|
||||
"A": { "type": "combat", "enemy": "slime", "row": 1, "col": -1, "next": ["C", "D"] },
|
||||
"B": { "type": "combat", "enemy": "slime", "row": 1, "col": 1, "next": ["D", "E"] },
|
||||
"C": { "type": "elite", "enemy": "slime_elite", "row": 2, "col": -2, "next": ["BOSS"] },
|
||||
"D": { "type": "combat", "enemy": "slime", "row": 2, "col": 0, "next": ["BOSS"] },
|
||||
"E": { "type": "combat", "enemy": "slime", "row": 2, "col": 2, "next": ["BOSS"] },
|
||||
"BOSS": { "type": "boss", "enemy": "slime_boss", "row": 3, "col": 0, "next": [] }
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,37 @@ if (!ENEMIES.enemies[ENEMIES.activeEnemy]) {
|
||||
}
|
||||
const ACTIVE_ENEMY = ENEMIES.enemies[ENEMIES.activeEnemy];
|
||||
|
||||
const MAP = JSON.parse(readFileSync('data/map.json', 'utf8'));
|
||||
for (const id of MAP.start) {
|
||||
if (!MAP.nodes[id]) throw new Error(`[gen-slaydeck] map.start에 없는 노드 id: ${id}`);
|
||||
}
|
||||
for (const [id, n] of Object.entries(MAP.nodes)) {
|
||||
if (!ENEMIES.enemies[n.enemy]) throw new Error(`[gen-slaydeck] 노드 ${id}의 enemy 없음: ${n.enemy}`);
|
||||
for (const nx of n.next) {
|
||||
if (!MAP.nodes[nx]) throw new Error(`[gen-slaydeck] 노드 ${id}.next에 없는 노드 id: ${nx}`);
|
||||
}
|
||||
}
|
||||
const MAX_ROW = Math.max(...Object.values(MAP.nodes).map((n) => n.row));
|
||||
|
||||
function luaIntentsArray(intents) {
|
||||
return '{ ' + intents.map((it) => `{ kind = ${luaStr(it.kind)}, value = ${it.value} }`).join(', ') + ' }';
|
||||
}
|
||||
function luaEnemiesTable(enemies) {
|
||||
const lines = Object.entries(enemies).map(([id, e]) =>
|
||||
`\t${id} = { name = ${luaStr(e.name)}, maxHp = ${e.maxHp}, intents = ${luaIntentsArray(e.intents)} },`);
|
||||
return `self.Enemies = {\n${lines.join('\n')}\n}`;
|
||||
}
|
||||
function luaMapNodesTable(nodes) {
|
||||
const lines = Object.entries(nodes).map(([id, n]) => {
|
||||
const nx = '{ ' + n.next.map(luaStr).join(', ') + ' }';
|
||||
return `\t${id} = { type = ${luaStr(n.type)}, enemy = ${luaStr(n.enemy)}, row = ${n.row}, col = ${n.col}, next = ${nx} },`;
|
||||
});
|
||||
return `self.MapNodes = {\n${lines.join('\n')}\n}`;
|
||||
}
|
||||
function luaStartArray(start) {
|
||||
return 'self.MapStart = { ' + start.map(luaStr).join(', ') + ' }';
|
||||
}
|
||||
|
||||
// Lua 직렬화 헬퍼
|
||||
function luaStr(s) {
|
||||
return '"' + String(s).replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"';
|
||||
@@ -60,7 +91,7 @@ const ALIGN_BOTTOM_CENTER = 6;
|
||||
|
||||
function guid(prefix, n) {
|
||||
// 유효한 8-4-4-4-12 hex GUID 생성. prefix는 충돌 방지용 네임스페이스 바이트로 매핑.
|
||||
const ns = prefix === 'hud' ? 0xd0 : prefix === 'dck' ? 0xca : prefix === 'cmb' ? 0xcb : prefix === 'rwd' ? 0xcc : 0xfe;
|
||||
const ns = prefix === 'hud' ? 0xd0 : prefix === 'dck' ? 0xca : prefix === 'cmb' ? 0xcb : prefix === 'rwd' ? 0xcc : prefix === 'map' ? 0xcd : 0xfe;
|
||||
const v = (ns * 0x100000 + n) >>> 0;
|
||||
return `${v.toString(16).padStart(8, '0')}-0000-4000-8000-${v.toString(16).padStart(12, '0')}`;
|
||||
}
|
||||
@@ -212,7 +243,7 @@ function entity({ id, path, modelId, entryId, componentNames, components, displa
|
||||
function upsertUi() {
|
||||
const ui = JSON.parse(readFileSync(UI_FILE, 'utf8'));
|
||||
const E = ui.ContentProto.Entities;
|
||||
ui.ContentProto.Entities = E.filter((e) => !e.path.startsWith('/ui/DefaultGroup/DeckHud') && !e.path.startsWith('/ui/DefaultGroup/CombatHud') && !e.path.startsWith('/ui/DefaultGroup/RewardHud'));
|
||||
ui.ContentProto.Entities = E.filter((e) => !e.path.startsWith('/ui/DefaultGroup/DeckHud') && !e.path.startsWith('/ui/DefaultGroup/CombatHud') && !e.path.startsWith('/ui/DefaultGroup/RewardHud') && !e.path.startsWith('/ui/DefaultGroup/MapHud'));
|
||||
|
||||
const byPath = new Map(ui.ContentProto.Entities.map((e) => [e.path, e]));
|
||||
|
||||
@@ -567,6 +598,68 @@ function upsertUi() {
|
||||
}));
|
||||
ui.ContentProto.Entities.push(...reward);
|
||||
|
||||
const TYPE_KO = { combat: '전투', elite: '엘리트', boss: '보스' };
|
||||
const map = [];
|
||||
const mapHud = entity({
|
||||
id: guid('map', 0),
|
||||
path: '/ui/DefaultGroup/MapHud',
|
||||
modelId: 'uisprite',
|
||||
entryId: 'UISprite',
|
||||
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent',
|
||||
displayOrder: 7,
|
||||
components: [
|
||||
transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 1920, y: 1080 }, pos: { x: 0, y: 0 }, align: ALIGN_CENTER }),
|
||||
sprite({ color: { r: 0.05, g: 0.06, b: 0.09, a: 0.9 }, type: 1, raycast: true }),
|
||||
],
|
||||
});
|
||||
mapHud.jsonString.enable = false;
|
||||
map.push(mapHud);
|
||||
map.push(entity({
|
||||
id: guid('map', 1),
|
||||
path: '/ui/DefaultGroup/MapHud/Title',
|
||||
modelId: 'uitext',
|
||||
entryId: 'UIText',
|
||||
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent',
|
||||
displayOrder: 0,
|
||||
components: [
|
||||
transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 700, y: 60 }, pos: { x: 0, y: 510 } }),
|
||||
sprite({ color: TRANSPARENT }),
|
||||
text({ value: '다음 노드 선택', fontSize: 40, bold: true, color: GOLD, alignment: 4 }),
|
||||
],
|
||||
}));
|
||||
let mapN = 2;
|
||||
for (const [id, node] of Object.entries(MAP.nodes)) {
|
||||
const nodePath = `/ui/DefaultGroup/MapHud/Node_${id}`;
|
||||
const pos = { x: node.col * 180, y: node.row * 170 - 80 };
|
||||
map.push(entity({
|
||||
id: guid('map', mapN++),
|
||||
path: nodePath,
|
||||
modelId: 'uisprite',
|
||||
entryId: 'UISprite',
|
||||
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent',
|
||||
displayOrder: node.row,
|
||||
components: [
|
||||
transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 150, y: 80 }, pos }),
|
||||
sprite({ color: { r: 0.3, g: 0.55, b: 0.85, a: 1 }, type: 1, raycast: true }),
|
||||
button(),
|
||||
],
|
||||
}));
|
||||
map.push(entity({
|
||||
id: guid('map', mapN++),
|
||||
path: `${nodePath}/Label`,
|
||||
modelId: 'uitext',
|
||||
entryId: 'UIText',
|
||||
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent',
|
||||
displayOrder: 0,
|
||||
components: [
|
||||
transform({ parentW: 150, parentH: 80, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 144, y: 72 }, pos: { x: 0, y: 0 } }),
|
||||
sprite({ color: TRANSPARENT }),
|
||||
text({ value: `${TYPE_KO[node.type]}\n${ENEMIES.enemies[node.enemy].name}`, fontSize: 20, bold: true }),
|
||||
],
|
||||
}));
|
||||
}
|
||||
ui.ContentProto.Entities.push(...map);
|
||||
|
||||
JSON.parse(JSON.stringify(ui));
|
||||
writeFileSync(UI_FILE, JSON.stringify(ui, null, 2), 'utf8');
|
||||
}
|
||||
@@ -575,9 +668,9 @@ function prop(Type, Name, DefaultValue = 'nil') {
|
||||
return { Type, DefaultValue, SyncDirection: 0, Attributes: [], Name };
|
||||
}
|
||||
|
||||
function method(Name, Code, Arguments = [], ExecSpace = 0) {
|
||||
function method(Name, Code, Arguments = [], ExecSpace = 0, ReturnType = 'void') {
|
||||
return {
|
||||
Return: { Type: 'void', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: null },
|
||||
Return: { Type: ReturnType, DefaultValue: null, SyncDirection: 0, Attributes: [], Name: null },
|
||||
Arguments,
|
||||
Code,
|
||||
Scope: 2,
|
||||
@@ -649,26 +742,40 @@ function writeCodeblocks() {
|
||||
prop('number', 'RunLength', String(RUN_LENGTH)),
|
||||
prop('any', 'RewardChoices'),
|
||||
prop('boolean', 'RunActive', 'false'),
|
||||
prop('any', 'Enemies'),
|
||||
prop('any', 'MapNodes'),
|
||||
prop('any', 'MapStart'),
|
||||
prop('string', 'CurrentNodeId', '""'),
|
||||
prop('string', 'CurrentEnemyId', '""'),
|
||||
], [
|
||||
method('OnBeginPlay', `self:StartRun()`),
|
||||
method('StartRun', `self.PlayerMaxHp = 80
|
||||
self.PlayerHp = self.PlayerMaxHp
|
||||
self.Gold = 0
|
||||
self.Floor = 0
|
||||
self.RunLength = ${RUN_LENGTH}
|
||||
self.RunLength = ${MAX_ROW}
|
||||
self.RunDeck = { ${CARDS.starterDeck.map(luaStr).join(', ')} }
|
||||
self.RunActive = true
|
||||
${luaEnemiesTable(ENEMIES.enemies)}
|
||||
${luaMapNodesTable(MAP.nodes)}
|
||||
${luaStartArray(MAP.start)}
|
||||
self.CurrentNodeId = ""
|
||||
self.CurrentEnemyId = ""
|
||||
self:BindButtons()
|
||||
self:StartCombat()`),
|
||||
self:ShowMap()`),
|
||||
method('StartCombat', `self.MaxEnergy = 3
|
||||
self.Turn = 0
|
||||
self.Floor = self.Floor + 1
|
||||
local node = self.MapNodes[self.CurrentNodeId]
|
||||
if node ~= nil then
|
||||
self.Floor = node.row
|
||||
end
|
||||
local enemy = self.Enemies[self.CurrentEnemyId]
|
||||
self.PlayerBlock = 0
|
||||
self.EnemyName = ${luaStr(ACTIVE_ENEMY.name)}
|
||||
self.EnemyMaxHp = ${ACTIVE_ENEMY.maxHp}
|
||||
self.EnemyName = enemy.name
|
||||
self.EnemyMaxHp = enemy.maxHp
|
||||
self.EnemyHp = self.EnemyMaxHp
|
||||
self.EnemyBlock = 0
|
||||
${luaIntentsTable(ACTIVE_ENEMY.intents)}
|
||||
self.EnemyIntents = enemy.intents
|
||||
self.EnemyIntentIndex = 1
|
||||
self.CombatOver = false
|
||||
self.DiscardPile = {}
|
||||
@@ -711,6 +818,14 @@ end
|
||||
local skip = _EntityService:GetEntityByPath("/ui/DefaultGroup/RewardHud/Skip")
|
||||
if skip ~= nil and skip.ButtonComponent ~= nil then
|
||||
skip:ConnectEvent(ButtonClickEvent, function() self:PickReward(0) end)
|
||||
end
|
||||
local mapNodeIds = { ${Object.keys(MAP.nodes).map(luaStr).join(', ')} }
|
||||
for i = 1, #mapNodeIds do
|
||||
local nid = mapNodeIds[i]
|
||||
local mn = _EntityService:GetEntityByPath("/ui/DefaultGroup/MapHud/Node_" .. nid)
|
||||
if mn ~= nil and mn.ButtonComponent ~= nil then
|
||||
mn:ConnectEvent(ButtonClickEvent, function() self:PickNode(nid) end)
|
||||
end
|
||||
end`),
|
||||
method('StartPlayerTurn', `self.Turn = self.Turn + 1
|
||||
self.Energy = self.MaxEnergy
|
||||
@@ -895,7 +1010,8 @@ self:RenderCombat()`),
|
||||
self.CombatOver = true
|
||||
self.Gold = self.Gold + ${GOLD_PER_WIN}
|
||||
self:RenderRun()
|
||||
if self.Floor >= self.RunLength then
|
||||
local node = self.MapNodes[self.CurrentNodeId]
|
||||
if node ~= nil and node.type == "boss" then
|
||||
self:ShowResult("런 클리어!")
|
||||
self.RunActive = false
|
||||
else
|
||||
@@ -976,7 +1092,57 @@ local hud = _EntityService:GetEntityByPath("/ui/DefaultGroup/RewardHud")
|
||||
if hud ~= nil then
|
||||
hud.Enable = false
|
||||
end
|
||||
self:StartCombat()`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' }]),
|
||||
self:ShowMap()`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' }]),
|
||||
method('ShowMap', `self:RenderMap()
|
||||
local hud = _EntityService:GetEntityByPath("/ui/DefaultGroup/MapHud")
|
||||
if hud ~= nil then
|
||||
hud.Enable = true
|
||||
end`),
|
||||
method('IsReachable', `local list
|
||||
if self.CurrentNodeId == "" then
|
||||
list = self.MapStart
|
||||
else
|
||||
local node = self.MapNodes[self.CurrentNodeId]
|
||||
if node == nil then
|
||||
return false
|
||||
end
|
||||
list = node.next
|
||||
end
|
||||
for i = 1, #list do
|
||||
if list[i] == id then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'id' }], 0, 'boolean'),
|
||||
method('RenderMap', `for id, node in pairs(self.MapNodes) do
|
||||
local e = _EntityService:GetEntityByPath("/ui/DefaultGroup/MapHud/Node_" .. id)
|
||||
if e ~= nil then
|
||||
local reachable = self:IsReachable(id)
|
||||
if e.SpriteGUIRendererComponent ~= nil then
|
||||
if reachable then
|
||||
e.SpriteGUIRendererComponent.Color = Color(0.3, 0.55, 0.85, 1)
|
||||
else
|
||||
e.SpriteGUIRendererComponent.Color = Color(0.2, 0.22, 0.26, 0.6)
|
||||
end
|
||||
end
|
||||
if e.ButtonComponent ~= nil then
|
||||
e.ButtonComponent.Enable = reachable
|
||||
end
|
||||
end
|
||||
end`),
|
||||
method('PickNode', `if self.RunActive ~= true then
|
||||
return
|
||||
end
|
||||
if self:IsReachable(id) ~= true then
|
||||
return
|
||||
end
|
||||
self.CurrentNodeId = id
|
||||
self.CurrentEnemyId = self.MapNodes[id].enemy
|
||||
local hud = _EntityService:GetEntityByPath("/ui/DefaultGroup/MapHud")
|
||||
if hud ~= nil then
|
||||
hud.Enable = false
|
||||
end
|
||||
self:StartCombat()`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'id' }]),
|
||||
]);
|
||||
for (const m of combat.ContentProto.Json.Methods) {
|
||||
m.ExecSpace = 6;
|
||||
|
||||
2585
ui/DefaultGroup.ui
2585
ui/DefaultGroup.ui
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user