feat: P14 — 반복 런·로비·영혼·도적·몬스터 랜덤성 (요청 17항목) #52
File diff suppressed because one or more lines are too long
@@ -37,6 +37,17 @@ for (const [cls, jobs] of Object.entries(JOBS)) {
|
|||||||
if (!CARDS.cards[j.starter]) throw new Error(`[gen-slaydeck] JOBS.${cls}.${j.id} 대표 카드 없음: ${j.starter}`);
|
if (!CARDS.cards[j.starter]) throw new Error(`[gen-slaydeck] JOBS.${cls}.${j.id} 대표 카드 없음: ${j.starter}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 영혼(soul) 메타 해금 — 2차 전직 후 보스 클리어로 영혼 적립, 로비 영혼상점에서 구매 → 다음 런 이점
|
||||||
|
const SOUL_UNLOCKS = [
|
||||||
|
{ key: 'meso', name: '두둑한 지갑', desc: '런 시작 시 메소 +60', cost: 3 },
|
||||||
|
{ key: 'hp', name: '단련된 육체', desc: '시작 최대 HP +15', cost: 4 },
|
||||||
|
{ key: 'trim', name: '덱 정제', desc: '시작 덱에서 기본 카드 1장 제거', cost: 5 },
|
||||||
|
{ key: 'relic', name: '유물 수집가', desc: '런 시작 시 유물 1개 추가', cost: 6 },
|
||||||
|
];
|
||||||
|
function luaSoulShopTable(unlocks) {
|
||||||
|
const items = unlocks.map((u) => `\t{ key = ${luaStr(u.key)}, name = ${luaStr(u.name)}, desc = ${luaStr(u.desc)}, cost = ${u.cost} },`).join('\n');
|
||||||
|
return `self.SoulShopDef = {\n${items}\n}`;
|
||||||
|
}
|
||||||
if (!ENEMIES.enemies[ENEMIES.activeEnemy]) {
|
if (!ENEMIES.enemies[ENEMIES.activeEnemy]) {
|
||||||
throw new Error(`[gen-slaydeck] activeEnemy가 enemies에 없음: ${ENEMIES.activeEnemy}`);
|
throw new Error(`[gen-slaydeck] activeEnemy가 enemies에 없음: ${ENEMIES.activeEnemy}`);
|
||||||
}
|
}
|
||||||
@@ -2621,6 +2632,38 @@ function upsertUi() {
|
|||||||
text({ value: '닫기', fontSize: 28, bold: true, color: WHITE, alignment: 4 }),
|
text({ value: '닫기', fontSize: 28, bold: true, color: WHITE, alignment: 4 }),
|
||||||
],
|
],
|
||||||
}));
|
}));
|
||||||
|
for (let i = 1; i <= 4; i++) {
|
||||||
|
const ip = `/ui/DefaultGroup/SoulShopHud/Item${i}`;
|
||||||
|
const iy = 230 - (i - 1) * 125;
|
||||||
|
soulShop.push(entity({
|
||||||
|
id: guid('soul', soulId++),
|
||||||
|
path: ip, modelId: 'uibutton', entryId: 'UIButton',
|
||||||
|
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent',
|
||||||
|
displayOrder: 2,
|
||||||
|
components: [
|
||||||
|
transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 760, y: 104 }, pos: { x: 0, y: iy }, align: ALIGN_CENTER }),
|
||||||
|
sprite({ color: { r: 0.14, g: 0.16, b: 0.22, a: 1 }, type: 1, raycast: true }),
|
||||||
|
button(),
|
||||||
|
],
|
||||||
|
}));
|
||||||
|
for (const [suffix, x, y, w, fs, color] of [
|
||||||
|
['Name', -180, 22, 360, 28, GOLD],
|
||||||
|
['Desc', -180, -24, 440, 20, { r: 0.86, g: 0.9, b: 0.94, a: 1 }],
|
||||||
|
['Status', 270, 0, 220, 22, { r: 0.6, g: 0.85, b: 1, a: 1 }],
|
||||||
|
]) {
|
||||||
|
soulShop.push(entity({
|
||||||
|
id: guid('soul', soulId++),
|
||||||
|
path: `${ip}/${suffix}`, modelId: 'uitext', entryId: 'UIText',
|
||||||
|
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent',
|
||||||
|
displayOrder: 1,
|
||||||
|
components: [
|
||||||
|
transform({ parentW: 760, parentH: 104, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: w, y: 38 }, pos: { x, y } }),
|
||||||
|
sprite({ color: TRANSPARENT }),
|
||||||
|
text({ value: '', fontSize: fs, bold: suffix === 'Name', color, alignment: 4 }),
|
||||||
|
],
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
emit('SoulShopHud', soulShop);
|
emit('SoulShopHud', soulShop);
|
||||||
|
|
||||||
for (const section of UI_APPEND_ORDER) {
|
for (const section of UI_APPEND_ORDER) {
|
||||||
@@ -2730,6 +2773,9 @@ function writeCodeblocks() {
|
|||||||
prop('boolean', 'LobbyBound', 'false'),
|
prop('boolean', 'LobbyBound', 'false'),
|
||||||
prop('boolean', 'CodexMode', 'false'),
|
prop('boolean', 'CodexMode', 'false'),
|
||||||
prop('any', 'CodexCards'),
|
prop('any', 'CodexCards'),
|
||||||
|
prop('any', 'SoulUnlocks'),
|
||||||
|
prop('any', 'SoulShopDef'),
|
||||||
|
prop('boolean', 'SoulShopBound', 'false'),
|
||||||
prop('string', 'DeckInspectKind', '""'),
|
prop('string', 'DeckInspectKind', '""'),
|
||||||
prop('boolean', 'DeckAllOpen', 'false'),
|
prop('boolean', 'DeckAllOpen', 'false'),
|
||||||
prop('any', 'Cards'),
|
prop('any', 'Cards'),
|
||||||
@@ -2781,10 +2827,14 @@ function writeCodeblocks() {
|
|||||||
prop('boolean', 'ChestOpened', 'false'),
|
prop('boolean', 'ChestOpened', 'false'),
|
||||||
prop('string', 'PlayerJob', '""'),
|
prop('string', 'PlayerJob', '""'),
|
||||||
], [
|
], [
|
||||||
method('OnBeginPlay', `self:ShowLobby()
|
method('OnBeginPlay', `${luaSoulShopTable(SOUL_UNLOCKS)}
|
||||||
|
self.SoulUnlocks = {}
|
||||||
|
self.SoulPoints = self.SoulPoints or 0
|
||||||
|
self:ShowLobby()
|
||||||
local lp = _UserService.LocalPlayer
|
local lp = _UserService.LocalPlayer
|
||||||
if lp ~= nil then
|
if lp ~= nil then
|
||||||
self:ReqLoadAscension(lp.PlayerComponent.UserId)
|
self:ReqLoadAscension(lp.PlayerComponent.UserId)
|
||||||
|
self:ReqLoadSouls(lp.PlayerComponent.UserId)
|
||||||
end`),
|
end`),
|
||||||
method('ReqLoadAscension', `local ds = _DataStorageService:GetUserDataStorage(userId)
|
method('ReqLoadAscension', `local ds = _DataStorageService:GetUserDataStorage(userId)
|
||||||
local errCode, value = ds:GetAndWait("ascensionUnlocked")
|
local errCode, value = ds:GetAndWait("ascensionUnlocked")
|
||||||
@@ -2985,8 +3035,113 @@ self:RenderAllDeck()`),
|
|||||||
method('ShowBoard', `self:SetEntityEnabled("/ui/DefaultGroup/BoardHud", true)`),
|
method('ShowBoard', `self:SetEntityEnabled("/ui/DefaultGroup/BoardHud", true)`),
|
||||||
method('CloseBoard', `self:SetEntityEnabled("/ui/DefaultGroup/BoardHud", false)`),
|
method('CloseBoard', `self:SetEntityEnabled("/ui/DefaultGroup/BoardHud", false)`),
|
||||||
method('ShowSoulShop', `self:RenderSoulLabel()
|
method('ShowSoulShop', `self:RenderSoulLabel()
|
||||||
|
self:RenderSoulShop()
|
||||||
|
self:BindSoulShopButtons()
|
||||||
self:SetEntityEnabled("/ui/DefaultGroup/SoulShopHud", true)`),
|
self:SetEntityEnabled("/ui/DefaultGroup/SoulShopHud", true)`),
|
||||||
method('CloseSoulShop', `self:SetEntityEnabled("/ui/DefaultGroup/SoulShopHud", false)`),
|
method('CloseSoulShop', `self:SetEntityEnabled("/ui/DefaultGroup/SoulShopHud", false)`),
|
||||||
|
method('ReqLoadSouls', `local ds = _DataStorageService:GetUserDataStorage(userId)
|
||||||
|
local e1, pts = ds:GetAndWait("soulPoints")
|
||||||
|
local e2, unl = ds:GetAndWait("soulUnlocks")
|
||||||
|
local p = 0
|
||||||
|
if e1 == 0 and pts ~= nil and pts ~= "" then p = tonumber(pts) or 0 end
|
||||||
|
local u = ""
|
||||||
|
if e2 == 0 and unl ~= nil then u = unl end
|
||||||
|
self:RecvSouls(p, u, userId)`, [{ Type: "string", DefaultValue: null, SyncDirection: 0, Attributes: [], Name: "userId" }], 5),
|
||||||
|
method('RecvSouls', `self.SoulPoints = p
|
||||||
|
self.SoulUnlocks = {}
|
||||||
|
if u ~= nil and u ~= "" then
|
||||||
|
for key in string.gmatch(u, "([^,]+)") do
|
||||||
|
self.SoulUnlocks[key] = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
self:RenderSoulLabel()`, [{ Type: "number", DefaultValue: null, SyncDirection: 0, Attributes: [], Name: "p" }, { Type: "string", DefaultValue: null, SyncDirection: 0, Attributes: [], Name: "u" }, { Type: "string", DefaultValue: null, SyncDirection: 0, Attributes: [], Name: "userId" }], 6),
|
||||||
|
method('SaveSouls', `local ds = _DataStorageService:GetUserDataStorage(userId)
|
||||||
|
ds:SetAndWait("soulPoints", tostring(p))
|
||||||
|
ds:SetAndWait("soulUnlocks", u)`, [{ Type: "number", DefaultValue: null, SyncDirection: 0, Attributes: [], Name: "p" }, { Type: "string", DefaultValue: null, SyncDirection: 0, Attributes: [], Name: "u" }, { Type: "string", DefaultValue: null, SyncDirection: 0, Attributes: [], Name: "userId" }], 5),
|
||||||
|
method('SerializeUnlocks', `local parts = {}
|
||||||
|
if self.SoulUnlocks ~= nil then
|
||||||
|
for k, v in pairs(self.SoulUnlocks) do
|
||||||
|
if v == true then table.insert(parts, k) end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return table.concat(parts, ",")`, [], 0, 'string'),
|
||||||
|
method('AwardSouls', `self.SoulPoints = (self.SoulPoints or 0) + n
|
||||||
|
local lp = _UserService.LocalPlayer
|
||||||
|
if lp ~= nil then
|
||||||
|
self:SaveSouls(self.SoulPoints, self:SerializeUnlocks(), lp.PlayerComponent.UserId)
|
||||||
|
end
|
||||||
|
self:RenderSoulLabel()`, [{ Type: "number", DefaultValue: null, SyncDirection: 0, Attributes: [], Name: "n" }]),
|
||||||
|
method('BuySoulUnlock', `local d = nil
|
||||||
|
if self.SoulShopDef ~= nil then d = self.SoulShopDef[slot] end
|
||||||
|
if d == nil then return end
|
||||||
|
if self.SoulUnlocks ~= nil and self.SoulUnlocks[d.key] == true then
|
||||||
|
self:Toast("이미 보유 중입니다")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if (self.SoulPoints or 0) < d.cost then
|
||||||
|
self:Toast("영혼이 부족합니다")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
self.SoulPoints = self.SoulPoints - d.cost
|
||||||
|
if self.SoulUnlocks == nil then self.SoulUnlocks = {} end
|
||||||
|
self.SoulUnlocks[d.key] = true
|
||||||
|
local lp = _UserService.LocalPlayer
|
||||||
|
if lp ~= nil then
|
||||||
|
self:SaveSouls(self.SoulPoints, self:SerializeUnlocks(), lp.PlayerComponent.UserId)
|
||||||
|
end
|
||||||
|
self:Toast(d.name .. " 해금!")
|
||||||
|
self:RenderSoulLabel()
|
||||||
|
self:RenderSoulShop()`, [{ Type: "number", DefaultValue: null, SyncDirection: 0, Attributes: [], Name: "slot" }]),
|
||||||
|
method('RenderSoulShop', `local defs = self.SoulShopDef or {}
|
||||||
|
for i = 1, 4 do
|
||||||
|
local base = "/ui/DefaultGroup/SoulShopHud/Item" .. tostring(i)
|
||||||
|
local d = defs[i]
|
||||||
|
if d == nil then
|
||||||
|
self:SetEntityEnabled(base, false)
|
||||||
|
else
|
||||||
|
self:SetEntityEnabled(base, true)
|
||||||
|
self:SetText(base .. "/Name", d.name)
|
||||||
|
self:SetText(base .. "/Desc", d.desc)
|
||||||
|
local owned = self.SoulUnlocks ~= nil and self.SoulUnlocks[d.key] == true
|
||||||
|
if owned then
|
||||||
|
self:SetText(base .. "/Status", "보유 중")
|
||||||
|
elseif (self.SoulPoints or 0) >= d.cost then
|
||||||
|
self:SetText(base .. "/Status", tostring(d.cost) .. " 영혼 · 구매")
|
||||||
|
else
|
||||||
|
self:SetText(base .. "/Status", tostring(d.cost) .. " 영혼 · 부족")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end`),
|
||||||
|
method('BindSoulShopButtons', `if self.SoulShopBound == true then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
self.SoulShopBound = true
|
||||||
|
for i = 1, 4 do
|
||||||
|
local idx = i
|
||||||
|
local e = _EntityService:GetEntityByPath("/ui/DefaultGroup/SoulShopHud/Item" .. tostring(i))
|
||||||
|
if e ~= nil and e.ButtonComponent ~= nil then
|
||||||
|
e:ConnectEvent(ButtonClickEvent, function() self:BuySoulUnlock(idx) end)
|
||||||
|
end
|
||||||
|
end`),
|
||||||
|
method('ApplySoulUnlocks', `if self.SoulUnlocks == nil then return end
|
||||||
|
if self.SoulUnlocks["meso"] == true then self.Gold = self.Gold + 60 end
|
||||||
|
if self.SoulUnlocks["hp"] == true then
|
||||||
|
self.PlayerMaxHp = self.PlayerMaxHp + 15
|
||||||
|
self.PlayerHp = self.PlayerMaxHp
|
||||||
|
end
|
||||||
|
if self.SoulUnlocks["trim"] == true then
|
||||||
|
for i = 1, #self.RunDeck do
|
||||||
|
local cid = self.RunDeck[i]
|
||||||
|
if cid == "Defend" or cid == "MagicGuard" or cid == "DarkSight" then
|
||||||
|
table.remove(self.RunDeck, i)
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if self.SoulUnlocks["relic"] == true then
|
||||||
|
local nid = self:PickNewRelic()
|
||||||
|
if nid ~= "" then self:AddRelic(nid) end
|
||||||
|
end`),
|
||||||
method('ShowCharacterSelect', `self.SelectedClass = ""
|
method('ShowCharacterSelect', `self.SelectedClass = ""
|
||||||
self:ShowState("charselect")
|
self:ShowState("charselect")
|
||||||
self:RenderCharacterSelect()`),
|
self:RenderCharacterSelect()`),
|
||||||
@@ -3070,6 +3225,7 @@ ${luaFramesTable()}
|
|||||||
self:GenerateMap()
|
self:GenerateMap()
|
||||||
self:BindButtons()
|
self:BindButtons()
|
||||||
self:AddRelic("${RELICS.startingRelic}")
|
self:AddRelic("${RELICS.startingRelic}")
|
||||||
|
self:ApplySoulUnlocks()
|
||||||
self:RenderPotions()
|
self:RenderPotions()
|
||||||
self:ShowMap()`),
|
self:ShowMap()`),
|
||||||
method('StartCombat', `self:ShowState("combat")
|
method('StartCombat', `self:ShowState("combat")
|
||||||
@@ -4110,6 +4266,7 @@ if anyAlive == false then
|
|||||||
if self.PlayerJob == "" and self.Floor < self.RunLength then
|
if self.PlayerJob == "" and self.Floor < self.RunLength then
|
||||||
self:ShowJobChoice()
|
self:ShowJobChoice()
|
||||||
else
|
else
|
||||||
|
if self.PlayerJob ~= "" then self:AwardSouls(1) end
|
||||||
local bid = self:PickNewRelic()
|
local bid = self:PickNewRelic()
|
||||||
if bid ~= "" then
|
if bid ~= "" then
|
||||||
self:AddRelic(bid)
|
self:AddRelic(bid)
|
||||||
|
|||||||
3008
ui/DefaultGroup.ui
3008
ui/DefaultGroup.ui
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user