refactor(cb): codeblock 메서드 161개를 cb/*.mjs 17 모듈로 분리 (codeblock 바이트 동일)
writeCodeblocks의 메서드를 연속-런 17 모듈(boot/state/soul/charselect/run/ deckturn/deckview/hand/combat/jobs/runend/render/reward/items/tooltip/map/shop)로 분리, methods 배열은 spread-concat(원본 순서 보존). prop 103개는 오케스트레이터 유지. 산출물 무변경(diffcheck: SlayDeckController.codeblock IDENTICAL). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
73
tools/deck/cb/boot.mjs
Normal file
73
tools/deck/cb/boot.mjs
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
import { method, RUN_LENGTH, GOLD_PER_WIN, CARD_PRICE, REST_HEAL, RELIC_PRICE, ACT_COUNT, ACT_MAPS, LOBBY_MAP, LOBBY_SPAWN } from '../lib/codeblock.mjs';
|
||||||
|
import { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, CARDFRAMES, RARITIES, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, POTIONS, luaSoulShopTable, frameRuid, luaFramesTable, luaNodeIconsTable, luaRelicsTable, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, luaCardsTable, luaDeckTable } from '../lib/data.mjs';
|
||||||
|
import { UI_FILE, COMMON_FILE, UI_ROOT, GENERATED_UI_SECTIONS, UI_APPEND_ORDER, DISABLED_STOCK_CONTROLS, TRANSPARENT, DARK, GOLD, ATTACK, DEFEND, SKILL, DAMAGE_DIGIT_RUIDS, DAMAGE_POP_MAX_DIGITS, DAMAGE_POP_DIGIT_W, DAMAGE_POP_DIGIT_H, DAMAGE_POP_DIGIT_SPACING, MAX_MONSTERS, HEAD_OFFSET_Y, HP_BAR_W, WHITE, CARD_NAME_TEXT, CARD_DESC_TEXT, cardFaceLayout, CARD_W, CARD_H, CARD_SPACING, CARD_XS, ALIGN_CENTER, ALIGN_BOTTOM_CENTER, guid, transform, sprite, button, text, scrollLayoutGroup, popupLayerFor, uiOrderFor, displayOrderFor, applySortingOverride, entity, uiPath, sectionRoot, isGeneratedUiEntity, appendUiSection } from '../lib/ui-helpers.mjs';
|
||||||
|
|
||||||
|
export const bootMethods = [
|
||||||
|
method('OnBeginPlay', `${luaCardsTable(CARDS.cards)}
|
||||||
|
${luaFramesTable()}
|
||||||
|
${luaNodeIconsTable()}
|
||||||
|
${luaSoulShopTable(SOUL_UNLOCKS)}
|
||||||
|
self.SoulUnlocks = {}
|
||||||
|
self.SoulPoints = self.SoulPoints or 0
|
||||||
|
self:ShowLobby()
|
||||||
|
local lp = _UserService.LocalPlayer
|
||||||
|
if lp ~= nil then
|
||||||
|
self:ReqLoadAscension(lp.PlayerComponent.UserId)
|
||||||
|
self:ReqLoadSouls(lp.PlayerComponent.UserId)
|
||||||
|
end
|
||||||
|
_InputService:ConnectEvent(KeyDownEvent, function(e)
|
||||||
|
if e.key == KeyboardKey.LeftControl then
|
||||||
|
local lp2 = _UserService.LocalPlayer
|
||||||
|
if lp2 ~= nil and lp2.CurrentMapName == "${LOBBY_MAP}" and self.RunActive ~= true then
|
||||||
|
self:PlayerAttackMotion()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)`),
|
||||||
|
method('ReqLoadAscension', `local ds = _DataStorageService:GetUserDataStorage(userId)
|
||||||
|
local errCode, value = ds:GetAndWait("ascensionUnlocked")
|
||||||
|
local n = 0
|
||||||
|
if errCode == 0 and value ~= nil and value ~= "" then
|
||||||
|
n = tonumber(value) or 0
|
||||||
|
end
|
||||||
|
self:RecvAscension(n, userId)`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'userId' }], 5),
|
||||||
|
method('RecvAscension', `self.AscensionUnlocked = n
|
||||||
|
if self.AscensionLevel > self.AscensionUnlocked then
|
||||||
|
self.AscensionLevel = self.AscensionUnlocked
|
||||||
|
end
|
||||||
|
self:RenderAscension()`, [
|
||||||
|
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'n' },
|
||||||
|
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'userId' },
|
||||||
|
], 6),
|
||||||
|
method('SaveAscension', `local ds = _DataStorageService:GetUserDataStorage(userId)
|
||||||
|
ds:SetAndWait("ascensionUnlocked", tostring(n))`, [
|
||||||
|
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'n' },
|
||||||
|
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'userId' },
|
||||||
|
], 5),
|
||||||
|
method('AdjustAscension', `local v = self.AscensionLevel + delta
|
||||||
|
if v < 0 then v = 0 end
|
||||||
|
if v > self.AscensionUnlocked then v = self.AscensionUnlocked end
|
||||||
|
self.AscensionLevel = v
|
||||||
|
self:RenderAscension()`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'delta' }]),
|
||||||
|
method('RenderAscension', `self:SetText("/ui/DefaultGroup/MainMenu/AscLabel", "승천 " .. string.format("%d", self.AscensionLevel) .. " / 해금 " .. string.format("%d", self.AscensionUnlocked))
|
||||||
|
self:SetText("/ui/DefaultGroup/LobbyHud/AscLabel", "승천 " .. string.format("%d", self.AscensionLevel) .. " / 해금 " .. string.format("%d", self.AscensionUnlocked))`),
|
||||||
|
method('AscHpMult', `local m = 1
|
||||||
|
if self.AscensionLevel >= 1 then m = m + 0.1 end
|
||||||
|
if self.AscensionLevel >= 6 then m = m + 0.1 end
|
||||||
|
return m`, [], 0, 'number'),
|
||||||
|
method('AscAtkMult', `local m = 1
|
||||||
|
if self.AscensionLevel >= 2 then m = m + 0.1 end
|
||||||
|
if self.AscensionLevel >= 7 then m = m + 0.1 end
|
||||||
|
return m`, [], 0, 'number'),
|
||||||
|
method('AscEliteBonus', `local b = 0
|
||||||
|
if self.AscensionLevel >= 4 then b = b + 0.2 end
|
||||||
|
if self.AscensionLevel >= 9 then b = b + 0.2 end
|
||||||
|
return b`, [], 0, 'number'),
|
||||||
|
method('AscGoldMult', `local m = 1
|
||||||
|
if self.AscensionLevel >= 5 then m = m - 0.25 end
|
||||||
|
if self.AscensionLevel >= 10 then m = m - 0.25 end
|
||||||
|
return m`, [], 0, 'number'),
|
||||||
|
method('AscStartHpPenalty', `local p = 0
|
||||||
|
if self.AscensionLevel >= 3 then p = p + 10 end
|
||||||
|
if self.AscensionLevel >= 8 then p = p + 10 end
|
||||||
|
return p`, [], 0, 'number'),
|
||||||
|
];
|
||||||
58
tools/deck/cb/charselect.mjs
Normal file
58
tools/deck/cb/charselect.mjs
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import { method, RUN_LENGTH, GOLD_PER_WIN, CARD_PRICE, REST_HEAL, RELIC_PRICE, ACT_COUNT, ACT_MAPS, LOBBY_MAP, LOBBY_SPAWN } from '../lib/codeblock.mjs';
|
||||||
|
import { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, CARDFRAMES, RARITIES, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, POTIONS, luaSoulShopTable, frameRuid, luaFramesTable, luaNodeIconsTable, luaRelicsTable, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, luaCardsTable, luaDeckTable } from '../lib/data.mjs';
|
||||||
|
import { UI_FILE, COMMON_FILE, UI_ROOT, GENERATED_UI_SECTIONS, UI_APPEND_ORDER, DISABLED_STOCK_CONTROLS, TRANSPARENT, DARK, GOLD, ATTACK, DEFEND, SKILL, DAMAGE_DIGIT_RUIDS, DAMAGE_POP_MAX_DIGITS, DAMAGE_POP_DIGIT_W, DAMAGE_POP_DIGIT_H, DAMAGE_POP_DIGIT_SPACING, MAX_MONSTERS, HEAD_OFFSET_Y, HP_BAR_W, WHITE, CARD_NAME_TEXT, CARD_DESC_TEXT, cardFaceLayout, CARD_W, CARD_H, CARD_SPACING, CARD_XS, ALIGN_CENTER, ALIGN_BOTTOM_CENTER, guid, transform, sprite, button, text, scrollLayoutGroup, popupLayerFor, uiOrderFor, displayOrderFor, applySortingOverride, entity, uiPath, sectionRoot, isGeneratedUiEntity, appendUiSection } from '../lib/ui-helpers.mjs';
|
||||||
|
|
||||||
|
export const charSelectMethods = [
|
||||||
|
method('ShowCharacterSelect', `self.SelectedClass = ""
|
||||||
|
self:ShowState("charselect")
|
||||||
|
self:RenderCharacterSelect()`),
|
||||||
|
method('SelectClass', `self.SelectedClass = className
|
||||||
|
self:RenderCharacterSelect()`, [
|
||||||
|
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'className' },
|
||||||
|
]),
|
||||||
|
method('RenderCharacterSelect', `local warrior = _EntityService:GetEntityByPath("/ui/DefaultGroup/CharacterSelectHud/WarriorButton")
|
||||||
|
if warrior ~= nil and warrior.SpriteGUIRendererComponent ~= nil then
|
||||||
|
if self.SelectedClass == "warrior" then
|
||||||
|
warrior.SpriteGUIRendererComponent.Color = Color(1, 0.82, 0.3, 1)
|
||||||
|
else
|
||||||
|
warrior.SpriteGUIRendererComponent.Color = Color(0.16, 0.2, 0.26, 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local mage = _EntityService:GetEntityByPath("/ui/DefaultGroup/CharacterSelectHud/MageButton")
|
||||||
|
if mage ~= nil and mage.SpriteGUIRendererComponent ~= nil then
|
||||||
|
if self.SelectedClass == "magician" then
|
||||||
|
mage.SpriteGUIRendererComponent.Color = Color(1, 0.82, 0.3, 1)
|
||||||
|
else
|
||||||
|
mage.SpriteGUIRendererComponent.Color = Color(0.16, 0.2, 0.26, 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local thief = _EntityService:GetEntityByPath("/ui/DefaultGroup/CharacterSelectHud/ThiefButton")
|
||||||
|
if thief ~= nil and thief.SpriteGUIRendererComponent ~= nil then
|
||||||
|
if self.SelectedClass == "bandit" then
|
||||||
|
thief.SpriteGUIRendererComponent.Color = Color(1, 0.82, 0.3, 1)
|
||||||
|
else
|
||||||
|
thief.SpriteGUIRendererComponent.Color = Color(0.16, 0.2, 0.26, 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if self.SelectedClass == "warrior" then
|
||||||
|
self:SetText("/ui/DefaultGroup/CharacterSelectHud/Status", "전사 선택됨")
|
||||||
|
elseif self.SelectedClass == "bandit" then
|
||||||
|
self:SetText("/ui/DefaultGroup/CharacterSelectHud/Status", "도적 선택됨")
|
||||||
|
elseif self.SelectedClass == "magician" then
|
||||||
|
self:SetText("/ui/DefaultGroup/CharacterSelectHud/Status", "마법사 선택됨")
|
||||||
|
else
|
||||||
|
self:SetText("/ui/DefaultGroup/CharacterSelectHud/Status", "직업을 선택하고 시작하세요")
|
||||||
|
end`),
|
||||||
|
method('StartNewGame', `if self.SelectedClass ~= "warrior" and self.SelectedClass ~= "bandit" and self.SelectedClass ~= "magician" then
|
||||||
|
self:SetText("/ui/DefaultGroup/CharacterSelectHud/Status", "직업을 먼저 선택하세요")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
self:StartRun()`),
|
||||||
|
method('SetEntityEnabled', `local e = _EntityService:GetEntityByPath(path)
|
||||||
|
if e ~= nil then
|
||||||
|
e.Enable = enabled
|
||||||
|
end`, [
|
||||||
|
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'path' },
|
||||||
|
{ Type: 'boolean', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'enabled' },
|
||||||
|
]),
|
||||||
|
];
|
||||||
480
tools/deck/cb/combat.mjs
Normal file
480
tools/deck/cb/combat.mjs
Normal file
@@ -0,0 +1,480 @@
|
|||||||
|
import { method, RUN_LENGTH, GOLD_PER_WIN, CARD_PRICE, REST_HEAL, RELIC_PRICE, ACT_COUNT, ACT_MAPS, LOBBY_MAP, LOBBY_SPAWN } from '../lib/codeblock.mjs';
|
||||||
|
import { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, CARDFRAMES, RARITIES, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, POTIONS, luaSoulShopTable, frameRuid, luaFramesTable, luaNodeIconsTable, luaRelicsTable, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, luaCardsTable, luaDeckTable } from '../lib/data.mjs';
|
||||||
|
import { UI_FILE, COMMON_FILE, UI_ROOT, GENERATED_UI_SECTIONS, UI_APPEND_ORDER, DISABLED_STOCK_CONTROLS, TRANSPARENT, DARK, GOLD, ATTACK, DEFEND, SKILL, DAMAGE_DIGIT_RUIDS, DAMAGE_POP_MAX_DIGITS, DAMAGE_POP_DIGIT_W, DAMAGE_POP_DIGIT_H, DAMAGE_POP_DIGIT_SPACING, MAX_MONSTERS, HEAD_OFFSET_Y, HP_BAR_W, WHITE, CARD_NAME_TEXT, CARD_DESC_TEXT, cardFaceLayout, CARD_W, CARD_H, CARD_SPACING, CARD_XS, ALIGN_CENTER, ALIGN_BOTTOM_CENTER, guid, transform, sprite, button, text, scrollLayoutGroup, popupLayerFor, uiOrderFor, displayOrderFor, applySortingOverride, entity, uiPath, sectionRoot, isGeneratedUiEntity, appendUiSection } from '../lib/ui-helpers.mjs';
|
||||||
|
|
||||||
|
export const combatMethods = [
|
||||||
|
method('PlayCard', `if self:IsDiscardSelecting() == true then
|
||||||
|
self:SelectDiscardSlot(slot)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if self.CombatOver == true or self.FxBusy == true or self.TurnBusy == true then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if self.Hand == nil then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local cardId = self.Hand[slot]
|
||||||
|
if cardId == nil then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local c = self.Cards[cardId]
|
||||||
|
if c == nil then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if c.unplayable == true then
|
||||||
|
self:Toast("사용할 수 없는 카드입니다")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if self.Energy < c.cost then
|
||||||
|
self:Toast("에너지가 부족합니다")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
self.Energy = self.Energy - c.cost
|
||||||
|
self:ResolveCardEffects(cardId, c, false)
|
||||||
|
table.remove(self.Hand, slot)
|
||||||
|
if c.exhaust == true then
|
||||||
|
if self.ExhaustPile == nil then self.ExhaustPile = {} end
|
||||||
|
table.insert(self.ExhaustPile, cardId)
|
||||||
|
elseif c.kind ~= "Power" then
|
||||||
|
table.insert(self.DiscardPile, cardId)
|
||||||
|
end
|
||||||
|
self:RenderHand(false)
|
||||||
|
self:RenderPiles()
|
||||||
|
self:RenderCombat()
|
||||||
|
if self:BeginDiscardSelection(c) == true then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
self:RenderHand(false)
|
||||||
|
self:RenderPiles()
|
||||||
|
self:RenderCombat()
|
||||||
|
self:CheckCombatEnd()`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' }]),
|
||||||
|
method('OnCardButton', `if self:IsDiscardSelecting() == true then
|
||||||
|
self:SelectDiscardSlot(slot)
|
||||||
|
end`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' }]),
|
||||||
|
method('FindMonsterAtTouch', `local best = 0
|
||||||
|
local bestDist = 200
|
||||||
|
for i = 1, #self.Monsters do
|
||||||
|
local m = self.Monsters[i]
|
||||||
|
if m.alive == true and m.entity ~= nil and isvalid(m.entity) and m.entity.TransformComponent ~= nil then
|
||||||
|
local wp = m.entity.TransformComponent.WorldPosition
|
||||||
|
local sp = _UILogic:WorldToScreenPosition(Vector2(wp.x, wp.y + 0.7))
|
||||||
|
local dx = sp.x - touchPoint.x
|
||||||
|
local dy = sp.y - touchPoint.y
|
||||||
|
local d = math.sqrt(dx * dx + dy * dy)
|
||||||
|
if d < bestDist then
|
||||||
|
bestDist = d
|
||||||
|
best = i
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return best`, [{ Type: 'any', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'touchPoint' }], 0, 'number'),
|
||||||
|
method('RenderTargetFrames', `local dragActive = self.DragTargetIndex ~= nil and self.DragTargetIndex > 0
|
||||||
|
local shownTarget = self.TargetIndex
|
||||||
|
if dragActive == true then shownTarget = self.DragTargetIndex end
|
||||||
|
for i = 1, #self.Monsters do
|
||||||
|
local m = self.Monsters[i]
|
||||||
|
local active = false
|
||||||
|
if m ~= nil and m.alive == true and i == shownTarget then active = true end
|
||||||
|
self:SetEntityEnabled("/ui/DefaultGroup/CombatHud/MonsterSlot" .. tostring(i) .. "/TargetFrame", active)
|
||||||
|
self:SetEntityEnabled("/ui/DefaultGroup/CombatHud/MonsterSlot" .. tostring(i) .. "/TargetMarker", active and dragActive)
|
||||||
|
self:SetEntityEnabled("/ui/DefaultGroup/CombatHud/MonsterSlot" .. tostring(i) .. "/TargetMarker/Label", active and dragActive)
|
||||||
|
end`),
|
||||||
|
method('OnCardDragBegin', `if self.CombatOver == true or self.FxBusy == true or self.TurnBusy == true then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if self.Hand == nil or self.Hand[slot] == nil then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if self.CardHoverTweenId ~= nil and self.CardHoverTweenId ~= 0 then
|
||||||
|
_TimerService:ClearTimer(self.CardHoverTweenId)
|
||||||
|
self.CardHoverTweenId = 0
|
||||||
|
end
|
||||||
|
for i = 1, 10 do
|
||||||
|
local e = _EntityService:GetEntityByPath("/ui/DefaultGroup/CardHand/Card" .. tostring(i))
|
||||||
|
if e ~= nil and e.UITransformComponent ~= nil then
|
||||||
|
e.UITransformComponent.UIScale = Vector3(1, 1, 1)
|
||||||
|
e.UITransformComponent.anchoredPosition = Vector2(self:GetHandSlotX(i), 0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
self.DragSlot = slot
|
||||||
|
self.DragTargetIndex = 0
|
||||||
|
self:RenderTargetFrames()`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' }]),
|
||||||
|
method('OnCardDrag', `if self.DragSlot ~= slot then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local e = _EntityService:GetEntityByPath("/ui/DefaultGroup/CardHand/Card" .. tostring(slot))
|
||||||
|
if e ~= nil and e.UITransformComponent ~= nil then
|
||||||
|
local ui = _UILogic:ScreenToUIPosition(touchPoint)
|
||||||
|
e.UITransformComponent.anchoredPosition = Vector2(ui.x, ui.y + 360)
|
||||||
|
end
|
||||||
|
local cardId = self.Hand[slot]
|
||||||
|
local c = nil
|
||||||
|
if cardId ~= nil then c = self.Cards[cardId] end
|
||||||
|
if c ~= nil and c.kind == "Attack" then
|
||||||
|
local best = self:FindMonsterAtTouch(touchPoint)
|
||||||
|
if best ~= self.DragTargetIndex then
|
||||||
|
self.DragTargetIndex = best
|
||||||
|
self:RenderTargetFrames()
|
||||||
|
end
|
||||||
|
end`, [
|
||||||
|
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' },
|
||||||
|
{ Type: 'any', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'touchPoint' },
|
||||||
|
]),
|
||||||
|
method('OnCardDragEnd', `if self.DragSlot ~= slot then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
self.DragSlot = 0
|
||||||
|
local e = _EntityService:GetEntityByPath("/ui/DefaultGroup/CardHand/Card" .. tostring(slot))
|
||||||
|
if e ~= nil and e.UITransformComponent ~= nil then
|
||||||
|
e.UITransformComponent.anchoredPosition = Vector2(self:GetHandSlotX(slot), 0)
|
||||||
|
e.UITransformComponent.UIScale = Vector3(1, 1, 1)
|
||||||
|
end
|
||||||
|
self:ResolveCardDrop(slot, touchPoint)`, [
|
||||||
|
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' },
|
||||||
|
{ Type: 'any', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'touchPoint' },
|
||||||
|
]),
|
||||||
|
method('ResolveCardDrop', `if self:IsDiscardSelecting() == true then
|
||||||
|
self:SelectDiscardSlot(slot)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if self.CombatOver == true or self.FxBusy == true or self.TurnBusy == true then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local cardId = self.Hand[slot]
|
||||||
|
if cardId == nil then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local c = self.Cards[cardId]
|
||||||
|
if c == nil then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if c.kind == "Attack" then
|
||||||
|
local best = self.DragTargetIndex or 0
|
||||||
|
if best <= 0 then best = self:FindMonsterAtTouch(touchPoint) end
|
||||||
|
self.DragTargetIndex = 0
|
||||||
|
if best > 0 then
|
||||||
|
self.TargetIndex = best
|
||||||
|
self:PlayCard(slot)
|
||||||
|
self:RenderTargetFrames()
|
||||||
|
else
|
||||||
|
self:RenderTargetFrames()
|
||||||
|
end
|
||||||
|
else
|
||||||
|
self.DragTargetIndex = 0
|
||||||
|
self:RenderTargetFrames()
|
||||||
|
local ui = _UILogic:ScreenToUIPosition(touchPoint)
|
||||||
|
if ui.y > -180 then
|
||||||
|
self:PlayCard(slot)
|
||||||
|
end
|
||||||
|
end`, [
|
||||||
|
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' },
|
||||||
|
{ Type: 'any', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'touchPoint' },
|
||||||
|
]),
|
||||||
|
method('Toast', `log(message)`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'message' }]),
|
||||||
|
method('DealDamageToTarget', `local m = self.Monsters[self.TargetIndex]
|
||||||
|
if m == nil or m.alive ~= true then
|
||||||
|
m = nil
|
||||||
|
for i = 1, #self.Monsters do
|
||||||
|
if self.Monsters[i].alive == true then m = self.Monsters[i]; self.TargetIndex = i; break end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if m == nil then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local dmg = amount
|
||||||
|
if m.vuln > 0 then
|
||||||
|
dmg = math.floor(dmg * 1.5)
|
||||||
|
end
|
||||||
|
if m.block > 0 and pierce ~= true then
|
||||||
|
local absorbed = math.min(m.block, dmg)
|
||||||
|
m.block = m.block - absorbed
|
||||||
|
dmg = dmg - absorbed
|
||||||
|
end
|
||||||
|
m.hp = m.hp - dmg
|
||||||
|
self:MonsterHitMotion(m.slot)
|
||||||
|
if m.hp <= 0 then
|
||||||
|
m.hp = 0
|
||||||
|
self:KillMonster(m.slot)
|
||||||
|
end`, [
|
||||||
|
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'amount' },
|
||||||
|
{ Type: 'boolean', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'pierce' },
|
||||||
|
]),
|
||||||
|
method('PlayAttackFx', `local m = self.Monsters[targetIndex]
|
||||||
|
if m == nil or m.alive ~= true or m.entity == nil or not isvalid(m.entity) then
|
||||||
|
self:DealDamageToTarget(damage, pierce)
|
||||||
|
self:RenderCombat()
|
||||||
|
self:CheckCombatEnd()
|
||||||
|
return
|
||||||
|
end
|
||||||
|
self.FxBusy = true
|
||||||
|
local fx = _EntityService:GetEntityByPath("/ui/DefaultGroup/CombatHud/SkillFx")
|
||||||
|
if fx ~= nil then
|
||||||
|
if fx.SpriteGUIRendererComponent ~= nil and image ~= nil and image ~= "" then
|
||||||
|
fx.SpriteGUIRendererComponent.ImageRUID = image
|
||||||
|
end
|
||||||
|
if fx.UITransformComponent ~= nil and m.entity.TransformComponent ~= nil then
|
||||||
|
local wp = m.entity.TransformComponent.WorldPosition
|
||||||
|
local sp = _UILogic:WorldToScreenPosition(Vector2(wp.x, wp.y + 0.7))
|
||||||
|
fx.UITransformComponent.anchoredPosition = _UILogic:ScreenToUIPosition(sp)
|
||||||
|
end
|
||||||
|
fx.Enable = true
|
||||||
|
end
|
||||||
|
_TimerService:SetTimerOnce(function()
|
||||||
|
if fx ~= nil then fx.Enable = false end
|
||||||
|
self.FxBusy = false
|
||||||
|
local shown = damage
|
||||||
|
local mt = self.Monsters[targetIndex]
|
||||||
|
if mt ~= nil and mt.alive == true and mt.vuln > 0 then
|
||||||
|
shown = math.floor(damage * 1.5)
|
||||||
|
end
|
||||||
|
self:DealDamageToTarget(damage, pierce)
|
||||||
|
self:ShowDmgPop(targetIndex, shown)
|
||||||
|
self:RenderCombat()
|
||||||
|
self:CheckCombatEnd()
|
||||||
|
end, 0.35)`, [
|
||||||
|
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'targetIndex' },
|
||||||
|
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'image' },
|
||||||
|
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'damage' },
|
||||||
|
{ Type: 'boolean', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'pierce' },
|
||||||
|
]),
|
||||||
|
method('PlayAoeFx', `self.FxBusy = true
|
||||||
|
local fx = _EntityService:GetEntityByPath("/ui/DefaultGroup/CombatHud/SkillFx")
|
||||||
|
if fx ~= nil then
|
||||||
|
if fx.SpriteGUIRendererComponent ~= nil and image ~= nil and image ~= "" then
|
||||||
|
fx.SpriteGUIRendererComponent.ImageRUID = image
|
||||||
|
end
|
||||||
|
if fx.UITransformComponent ~= nil then
|
||||||
|
fx.UITransformComponent.anchoredPosition = Vector2(300, 60)
|
||||||
|
end
|
||||||
|
fx.Enable = true
|
||||||
|
end
|
||||||
|
_TimerService:SetTimerOnce(function()
|
||||||
|
if fx ~= nil then fx.Enable = false end
|
||||||
|
self.FxBusy = false
|
||||||
|
for i = 1, #self.Monsters do
|
||||||
|
local m = self.Monsters[i]
|
||||||
|
if m ~= nil and m.alive == true then
|
||||||
|
local dmg = damage
|
||||||
|
if m.vuln > 0 then
|
||||||
|
dmg = math.floor(dmg * 1.5)
|
||||||
|
end
|
||||||
|
if m.block > 0 then
|
||||||
|
local absorbed = math.min(m.block, dmg)
|
||||||
|
m.block = m.block - absorbed
|
||||||
|
dmg = dmg - absorbed
|
||||||
|
end
|
||||||
|
m.hp = m.hp - dmg
|
||||||
|
self:ShowDmgPop(i, dmg)
|
||||||
|
self:MonsterHitMotion(i)
|
||||||
|
if m.hp <= 0 then
|
||||||
|
m.hp = 0
|
||||||
|
self:KillMonster(m.slot)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
self:RenderCombat()
|
||||||
|
self:CheckCombatEnd()
|
||||||
|
end, 0.35)`, [
|
||||||
|
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'image' },
|
||||||
|
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'damage' },
|
||||||
|
]),
|
||||||
|
method('KillMonster', `local m = self.Monsters[slot]
|
||||||
|
if m == nil then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
m.alive = false
|
||||||
|
if m.entity ~= nil and isvalid(m.entity) then
|
||||||
|
local ent = m.entity
|
||||||
|
_TimerService:SetTimerOnce(function() if isvalid(ent) then ent:SetVisible(false) end end, 0.4)
|
||||||
|
end
|
||||||
|
self:SetEntityEnabled("/ui/DefaultGroup/CombatHud/MonsterSlot" .. tostring(slot), false)
|
||||||
|
for i = 1, #self.Monsters do
|
||||||
|
if self.Monsters[i].alive == true then self.TargetIndex = i; break end
|
||||||
|
end`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' }]),
|
||||||
|
method('DealDamageToPlayer', `local dmg = amount
|
||||||
|
if self.PlayerBlock > 0 then
|
||||||
|
local absorbed = math.min(self.PlayerBlock, dmg)
|
||||||
|
self.PlayerBlock = self.PlayerBlock - absorbed
|
||||||
|
dmg = dmg - absorbed
|
||||||
|
end
|
||||||
|
if dmg > 0 then
|
||||||
|
self.PlayerHp = self.PlayerHp - dmg
|
||||||
|
local reflect = self.PlayerThorns or 0
|
||||||
|
if self:HasRelic("bronzeScales") then
|
||||||
|
reflect = reflect + 3
|
||||||
|
end
|
||||||
|
if reflect > 0 and attackerSlot ~= nil and attackerSlot > 0 then
|
||||||
|
local am = self.Monsters[attackerSlot]
|
||||||
|
if am ~= nil and am.alive == true then
|
||||||
|
am.hp = am.hp - reflect
|
||||||
|
self:ShowDmgPop(am.slot, reflect)
|
||||||
|
self:MonsterHitMotion(am.slot)
|
||||||
|
if am.hp <= 0 then
|
||||||
|
am.hp = 0
|
||||||
|
self:KillMonster(am.slot)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if self:HasRelic("selfFormingClay") then
|
||||||
|
self.ClayBlockNext = self.ClayBlockNext + 3
|
||||||
|
end
|
||||||
|
if self:HasRelic("centennialPuzzle") and self.FirstHpLossDone == false then
|
||||||
|
self.FirstHpLossDone = true
|
||||||
|
self:DrawCards(3)
|
||||||
|
self:RenderHand(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if self.PlayerHp < 0 then
|
||||||
|
self.PlayerHp = 0
|
||||||
|
end`, [
|
||||||
|
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'amount' },
|
||||||
|
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'attackerSlot' },
|
||||||
|
]),
|
||||||
|
method('EnemyTurn', `self.TurnBusy = true
|
||||||
|
self:EnemyActStep(1)`),
|
||||||
|
method('EnemyActStep', `local idx = 0
|
||||||
|
for i = fromIndex, #self.Monsters do
|
||||||
|
if self.Monsters[i].alive == true then idx = i; break end
|
||||||
|
end
|
||||||
|
if idx == 0 or self.PlayerHp <= 0 then
|
||||||
|
self:FinishEnemyTurn()
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local m = self.Monsters[idx]
|
||||||
|
local base = "/ui/DefaultGroup/CombatHud/MonsterSlot" .. tostring(idx)
|
||||||
|
self:SetEntityEnabled(base .. "/ActFrame", true)
|
||||||
|
_TimerService:SetTimerOnce(function()
|
||||||
|
if m.poison ~= nil and m.poison > 0 then
|
||||||
|
m.hp = m.hp - m.poison
|
||||||
|
self:ShowDmgPop(idx, m.poison)
|
||||||
|
self:MonsterHitMotion(idx)
|
||||||
|
m.poison = m.poison - 1
|
||||||
|
if m.hp <= 0 then
|
||||||
|
m.hp = 0
|
||||||
|
self:KillMonster(m.slot)
|
||||||
|
self:RenderCombat()
|
||||||
|
self:SetEntityEnabled(base .. "/ActFrame", false)
|
||||||
|
_TimerService:SetTimerOnce(function() self:EnemyActStep(idx + 1) end, 0.15)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
m.block = 0
|
||||||
|
local intent = m.intents[m.intentIdx]
|
||||||
|
if intent ~= nil then
|
||||||
|
if intent.kind == "Attack" then
|
||||||
|
self:MonsterLunge(idx)
|
||||||
|
local atk = intent.value + m.str
|
||||||
|
if m.weak > 0 then
|
||||||
|
atk = math.floor(atk * 0.75)
|
||||||
|
end
|
||||||
|
if self.PlayerVuln > 0 then
|
||||||
|
atk = math.floor(atk * 1.5)
|
||||||
|
end
|
||||||
|
local before = self.PlayerHp
|
||||||
|
self:DealDamageToPlayer(atk, idx)
|
||||||
|
self:ShowPlayerDmgPop(before - self.PlayerHp)
|
||||||
|
self:PlayerHitMotion()
|
||||||
|
elseif intent.kind == "Defend" then
|
||||||
|
m.block = m.block + intent.value
|
||||||
|
elseif intent.kind == "Debuff" then
|
||||||
|
if intent.effect == "weak" then
|
||||||
|
self.PlayerWeak = self.PlayerWeak + intent.value
|
||||||
|
elseif intent.effect == "vuln" then
|
||||||
|
self.PlayerVuln = self.PlayerVuln + intent.value
|
||||||
|
end
|
||||||
|
elseif intent.kind == "AddCard" then
|
||||||
|
local cnt = intent.count or 1
|
||||||
|
for ci = 1, cnt do
|
||||||
|
table.insert(self.DiscardPile, intent.card)
|
||||||
|
end
|
||||||
|
self:RenderPiles()
|
||||||
|
local cn = intent.card
|
||||||
|
local cc = self.Cards[intent.card]
|
||||||
|
if cc ~= nil then cn = cc.name end
|
||||||
|
self:Toast(m.name .. ": " .. cn .. " 추가!")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if #m.intents > 0 then
|
||||||
|
m.intentIdx = math.random(1, #m.intents)
|
||||||
|
end
|
||||||
|
if m.weak > 0 then m.weak = m.weak - 1 end
|
||||||
|
if m.vuln > 0 then m.vuln = m.vuln - 1 end
|
||||||
|
self:RenderCombat()
|
||||||
|
self:SetEntityEnabled(base .. "/ActFrame", false)
|
||||||
|
_TimerService:SetTimerOnce(function() self:EnemyActStep(idx + 1) end, 0.15)
|
||||||
|
end, 0.45)`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'fromIndex' }]),
|
||||||
|
method('FinishEnemyTurn', `self.TurnBusy = false
|
||||||
|
self:CheckCombatEnd()
|
||||||
|
if self.CombatOver == true then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
_TimerService:SetTimerOnce(function() self:StartPlayerTurn() end, 0.45)`),
|
||||||
|
method('ClearCombatCards', `self.DrawPile = {}
|
||||||
|
self.DiscardPile = {}
|
||||||
|
self.ExhaustPile = {}
|
||||||
|
self.Hand = {}
|
||||||
|
self.DiscardSelectRemaining = 0
|
||||||
|
self.DiscardSelectTotal = 0
|
||||||
|
self.DiscardPostShiv = 0
|
||||||
|
self.DiscardShivPerPick = 0
|
||||||
|
self:UpdateDiscardPrompt()
|
||||||
|
self:RenderHand(false)
|
||||||
|
self:RenderPiles()`),
|
||||||
|
method('CheckCombatEnd', `local anyAlive = false
|
||||||
|
for i = 1, #self.Monsters do
|
||||||
|
if self.Monsters[i].alive == true then anyAlive = true; break end
|
||||||
|
end
|
||||||
|
if anyAlive == false then
|
||||||
|
self.CombatOver = true
|
||||||
|
self:ClearCombatCards()
|
||||||
|
self.Gold = self.Gold + math.floor(${GOLD_PER_WIN} * self:AscGoldMult())
|
||||||
|
self:ApplyRelics("combatEnd")
|
||||||
|
self:ApplyRelics("combatReward")
|
||||||
|
self:MaybeDropPotion()
|
||||||
|
self:RenderRun()
|
||||||
|
local node = self.MapNodes[self.CurrentNodeId]
|
||||||
|
if node ~= nil and node.type == "elite" then
|
||||||
|
self.Gold = self.Gold + 15
|
||||||
|
local nid = self:PickNewRelic()
|
||||||
|
if nid ~= "" then
|
||||||
|
self:AddRelic(nid)
|
||||||
|
local nr = self.Relics[nid]
|
||||||
|
if nr ~= nil then
|
||||||
|
self:Toast("유물 획득: " .. nr.name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if node ~= nil and node.type == "boss" then
|
||||||
|
if self.PlayerJob == "" and self.Floor < self.RunLength then
|
||||||
|
self:ShowJobChoice()
|
||||||
|
else
|
||||||
|
if self.PlayerJob ~= "" then self:AwardSouls(1) end
|
||||||
|
local bid = self:PickNewRelic()
|
||||||
|
if bid ~= "" then
|
||||||
|
self:AddRelic(bid)
|
||||||
|
local br = self.Relics[bid]
|
||||||
|
if br ~= nil then
|
||||||
|
self:Toast("유물 획득: " .. br.name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
self:ContinueAfterBoss()
|
||||||
|
end
|
||||||
|
else
|
||||||
|
self:OfferReward()
|
||||||
|
end
|
||||||
|
elseif self.PlayerHp <= 0 then
|
||||||
|
self.CombatOver = true
|
||||||
|
self:EndRun("패배...")
|
||||||
|
end`),
|
||||||
|
method('ContinueAfterBoss', `if self.Floor < self.RunLength then
|
||||||
|
self.Floor = self.Floor + 1
|
||||||
|
self.CurrentNodeId = ""
|
||||||
|
self.CurrentEnemyId = ""
|
||||||
|
self:GenerateMap()
|
||||||
|
self:RenderRun()
|
||||||
|
self:TeleportToActMap()
|
||||||
|
self:ShowMap()
|
||||||
|
else
|
||||||
|
self:EndRun("런 클리어!")
|
||||||
|
end`),
|
||||||
|
];
|
||||||
344
tools/deck/cb/deckturn.mjs
Normal file
344
tools/deck/cb/deckturn.mjs
Normal file
@@ -0,0 +1,344 @@
|
|||||||
|
import { method, RUN_LENGTH, GOLD_PER_WIN, CARD_PRICE, REST_HEAL, RELIC_PRICE, ACT_COUNT, ACT_MAPS, LOBBY_MAP, LOBBY_SPAWN } from '../lib/codeblock.mjs';
|
||||||
|
import { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, CARDFRAMES, RARITIES, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, POTIONS, luaSoulShopTable, frameRuid, luaFramesTable, luaNodeIconsTable, luaRelicsTable, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, luaCardsTable, luaDeckTable } from '../lib/data.mjs';
|
||||||
|
import { UI_FILE, COMMON_FILE, UI_ROOT, GENERATED_UI_SECTIONS, UI_APPEND_ORDER, DISABLED_STOCK_CONTROLS, TRANSPARENT, DARK, GOLD, ATTACK, DEFEND, SKILL, DAMAGE_DIGIT_RUIDS, DAMAGE_POP_MAX_DIGITS, DAMAGE_POP_DIGIT_W, DAMAGE_POP_DIGIT_H, DAMAGE_POP_DIGIT_SPACING, MAX_MONSTERS, HEAD_OFFSET_Y, HP_BAR_W, WHITE, CARD_NAME_TEXT, CARD_DESC_TEXT, cardFaceLayout, CARD_W, CARD_H, CARD_SPACING, CARD_XS, ALIGN_CENTER, ALIGN_BOTTOM_CENTER, guid, transform, sprite, button, text, scrollLayoutGroup, popupLayerFor, uiOrderFor, displayOrderFor, applySortingOverride, entity, uiPath, sectionRoot, isGeneratedUiEntity, appendUiSection } from '../lib/ui-helpers.mjs';
|
||||||
|
|
||||||
|
export const deckTurnMethods = [
|
||||||
|
method('Shuffle', `if list == nil then
|
||||||
|
\treturn
|
||||||
|
end
|
||||||
|
for i = #list, 2, -1 do
|
||||||
|
\tlocal j = math.random(1, i)
|
||||||
|
\tlist[i], list[j] = list[j], list[i]
|
||||||
|
end`, [{ Type: 'any', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'list' }]),
|
||||||
|
method('BindButtons', `local endTurn = _EntityService:GetEntityByPath("/ui/DefaultGroup/DeckHud/EndTurnButton")
|
||||||
|
if endTurn ~= nil and endTurn.ButtonComponent ~= nil then
|
||||||
|
if self.EndTurnHandler ~= nil then
|
||||||
|
endTurn:DisconnectEvent(ButtonClickEvent, self.EndTurnHandler)
|
||||||
|
self.EndTurnHandler = nil
|
||||||
|
end
|
||||||
|
self.EndTurnHandler = endTurn:ConnectEvent(ButtonClickEvent, function() self:EndPlayerTurn() end)
|
||||||
|
end
|
||||||
|
local drawPile = _EntityService:GetEntityByPath("/ui/DefaultGroup/DeckHud/DrawPile")
|
||||||
|
if drawPile ~= nil and drawPile.ButtonComponent ~= nil then
|
||||||
|
if self.DrawPileHandler ~= nil then
|
||||||
|
drawPile:DisconnectEvent(ButtonClickEvent, self.DrawPileHandler)
|
||||||
|
self.DrawPileHandler = nil
|
||||||
|
end
|
||||||
|
self.DrawPileHandler = drawPile:ConnectEvent(ButtonClickEvent, function() self:OpenDeckInspect("draw") end)
|
||||||
|
end
|
||||||
|
local discardPile = _EntityService:GetEntityByPath("/ui/DefaultGroup/DeckHud/DiscardPile")
|
||||||
|
if discardPile ~= nil and discardPile.ButtonComponent ~= nil then
|
||||||
|
if self.DiscardPileHandler ~= nil then
|
||||||
|
discardPile:DisconnectEvent(ButtonClickEvent, self.DiscardPileHandler)
|
||||||
|
self.DiscardPileHandler = nil
|
||||||
|
end
|
||||||
|
self.DiscardPileHandler = discardPile:ConnectEvent(ButtonClickEvent, function() self:OpenDeckInspect("discard") end)
|
||||||
|
end
|
||||||
|
local exhaustPile = _EntityService:GetEntityByPath("/ui/DefaultGroup/DeckHud/ExhaustPile")
|
||||||
|
if exhaustPile ~= nil and exhaustPile.ButtonComponent ~= nil then
|
||||||
|
if self.ExhaustPileHandler ~= nil then
|
||||||
|
exhaustPile:DisconnectEvent(ButtonClickEvent, self.ExhaustPileHandler)
|
||||||
|
self.ExhaustPileHandler = nil
|
||||||
|
end
|
||||||
|
self.ExhaustPileHandler = exhaustPile:ConnectEvent(ButtonClickEvent, function() self:OpenDeckInspect("exhaust") end)
|
||||||
|
end
|
||||||
|
local inspectClose = _EntityService:GetEntityByPath("/ui/DefaultGroup/DeckInspectHud/Close")
|
||||||
|
if inspectClose ~= nil and inspectClose.ButtonComponent ~= nil then
|
||||||
|
if self.DeckInspectCloseHandler ~= nil then
|
||||||
|
inspectClose:DisconnectEvent(ButtonClickEvent, self.DeckInspectCloseHandler)
|
||||||
|
self.DeckInspectCloseHandler = nil
|
||||||
|
end
|
||||||
|
self.DeckInspectCloseHandler = inspectClose:ConnectEvent(ButtonClickEvent, function() self:CloseDeckInspect() end)
|
||||||
|
end
|
||||||
|
local allDeckButton = _EntityService:GetEntityByPath("/ui/DefaultGroup/CombatHud/TopBar/AllDeckButton")
|
||||||
|
if allDeckButton ~= nil and allDeckButton.ButtonComponent ~= nil then
|
||||||
|
if self.AllDeckHandler ~= nil then
|
||||||
|
allDeckButton:DisconnectEvent(ButtonClickEvent, self.AllDeckHandler)
|
||||||
|
self.AllDeckHandler = nil
|
||||||
|
end
|
||||||
|
self.AllDeckHandler = allDeckButton:ConnectEvent(ButtonClickEvent, function() self:OpenAllDeck() end)
|
||||||
|
end
|
||||||
|
local allDeckClose = _EntityService:GetEntityByPath("/ui/DefaultGroup/DeckAllHud/Close")
|
||||||
|
if allDeckClose ~= nil and allDeckClose.ButtonComponent ~= nil then
|
||||||
|
if self.AllDeckCloseHandler ~= nil then
|
||||||
|
allDeckClose:DisconnectEvent(ButtonClickEvent, self.AllDeckCloseHandler)
|
||||||
|
self.AllDeckCloseHandler = nil
|
||||||
|
end
|
||||||
|
self.AllDeckCloseHandler = allDeckClose:ConnectEvent(ButtonClickEvent, function() self:CloseAllDeck() end)
|
||||||
|
end
|
||||||
|
self:BindClassDeckTabs()
|
||||||
|
for i = 1, 10 do
|
||||||
|
local cardEntity = _EntityService:GetEntityByPath("/ui/DefaultGroup/CardHand/Card" .. tostring(i))
|
||||||
|
if cardEntity ~= nil and cardEntity.UITouchReceiveComponent ~= nil then
|
||||||
|
local cardPath = "/ui/DefaultGroup/CardHand/Card" .. tostring(i)
|
||||||
|
cardEntity:ConnectEvent(UITouchEnterEvent, function() self:SetCardHover(cardPath, true) end)
|
||||||
|
cardEntity:ConnectEvent(UITouchExitEvent, function() self:SetCardHover(cardPath, false) end)
|
||||||
|
cardEntity:ConnectEvent(UITouchBeginDragEvent, function(ev) self:OnCardDragBegin(i) end)
|
||||||
|
cardEntity:ConnectEvent(UITouchDragEvent, function(ev) self:OnCardDrag(i, ev.TouchPoint) end)
|
||||||
|
cardEntity:ConnectEvent(UITouchEndDragEvent, function(ev) self:OnCardDragEnd(i, ev.TouchPoint) end)
|
||||||
|
cardEntity:ConnectEvent(UITouchEnterEvent, function() self:HoverCard(i) end)
|
||||||
|
cardEntity:ConnectEvent(UITouchExitEvent, function() self:UnhoverCard(i) end)
|
||||||
|
if cardEntity.ButtonComponent ~= nil then
|
||||||
|
cardEntity:ConnectEvent(ButtonClickEvent, function() self:OnCardButton(i) end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
for i = 1, 3 do
|
||||||
|
local rc = _EntityService:GetEntityByPath("/ui/DefaultGroup/RewardHud/Reward" .. tostring(i))
|
||||||
|
if rc ~= nil and rc.ButtonComponent ~= nil then
|
||||||
|
rc:ConnectEvent(ButtonClickEvent, function() self:PickReward(i) end)
|
||||||
|
if rc.UITouchReceiveComponent ~= nil then
|
||||||
|
local cardPath = "/ui/DefaultGroup/RewardHud/Reward" .. tostring(i)
|
||||||
|
rc:ConnectEvent(UITouchEnterEvent, function() self:SetCardHover(cardPath, true) end)
|
||||||
|
rc:ConnectEvent(UITouchExitEvent, function() self:SetCardHover(cardPath, false) end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
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 = {}
|
||||||
|
for r = 1, ${MAP_ROWS} do
|
||||||
|
for c = 1, ${MAP_COLS} do
|
||||||
|
table.insert(mapNodeIds, "r" .. tostring(r) .. "c" .. tostring(c))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
table.insert(mapNodeIds, "boss")
|
||||||
|
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
|
||||||
|
for i = 1, 3 do
|
||||||
|
local sc = _EntityService:GetEntityByPath("/ui/DefaultGroup/ShopHud/Card" .. tostring(i))
|
||||||
|
if sc ~= nil and sc.ButtonComponent ~= nil then
|
||||||
|
sc:ConnectEvent(ButtonClickEvent, function() self:BuyCard(i) end)
|
||||||
|
if sc.UITouchReceiveComponent ~= nil then
|
||||||
|
local cardPath = "/ui/DefaultGroup/ShopHud/Card" .. tostring(i)
|
||||||
|
sc:ConnectEvent(UITouchEnterEvent, function() self:SetCardHover(cardPath, true) end)
|
||||||
|
sc:ConnectEvent(UITouchExitEvent, function() self:SetCardHover(cardPath, false) end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local shopLeave = _EntityService:GetEntityByPath("/ui/DefaultGroup/ShopHud/Leave")
|
||||||
|
if shopLeave ~= nil and shopLeave.ButtonComponent ~= nil then
|
||||||
|
shopLeave:ConnectEvent(ButtonClickEvent, function() self:LeaveNode() end)
|
||||||
|
end
|
||||||
|
local shopRelic = _EntityService:GetEntityByPath("/ui/DefaultGroup/ShopHud/Relic")
|
||||||
|
if shopRelic ~= nil and shopRelic.ButtonComponent ~= nil then
|
||||||
|
shopRelic:ConnectEvent(ButtonClickEvent, function() self:BuyRelic() end)
|
||||||
|
end
|
||||||
|
local restLeave = _EntityService:GetEntityByPath("/ui/DefaultGroup/RestHud/Leave")
|
||||||
|
if restLeave ~= nil and restLeave.ButtonComponent ~= nil then
|
||||||
|
restLeave:ConnectEvent(ButtonClickEvent, function() self:LeaveNode() end)
|
||||||
|
end
|
||||||
|
for i = 1, ${MAX_MONSTERS} do
|
||||||
|
local ms = _EntityService:GetEntityByPath("/ui/DefaultGroup/CombatHud/MonsterSlot" .. tostring(i))
|
||||||
|
if ms ~= nil and ms.ButtonComponent ~= nil then
|
||||||
|
ms:ConnectEvent(ButtonClickEvent, function() self:SetTarget(i) end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
for i = 1, 10 do
|
||||||
|
local rs = _EntityService:GetEntityByPath("/ui/DefaultGroup/CombatHud/TopBar/RelicSlot" .. tostring(i))
|
||||||
|
if rs ~= nil and rs.UITouchReceiveComponent ~= nil then
|
||||||
|
local idx = i
|
||||||
|
rs:ConnectEvent(UITouchEnterEvent, function()
|
||||||
|
local rid = nil
|
||||||
|
if self.RunRelics ~= nil then rid = self.RunRelics[idx] end
|
||||||
|
if rid ~= nil and self.Relics[rid] ~= nil then
|
||||||
|
self:ShowTooltip(self.Relics[rid].name, self.Relics[rid].desc, -240 + (idx - 1) * 48)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
rs:ConnectEvent(UITouchExitEvent, function() self:HideTooltip() end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
for i = 1, 5 do
|
||||||
|
local ps = _EntityService:GetEntityByPath("/ui/DefaultGroup/CombatHud/TopBar/PotionSlot" .. tostring(i))
|
||||||
|
if ps ~= nil and ps.UITouchReceiveComponent ~= nil then
|
||||||
|
local idx = i
|
||||||
|
ps:ConnectEvent(UITouchEnterEvent, function()
|
||||||
|
local pid = nil
|
||||||
|
if self.RunPotions ~= nil then pid = self.RunPotions[idx] end
|
||||||
|
if pid ~= nil and self.Potions[pid] ~= nil then
|
||||||
|
self:ShowTooltip(self.Potions[pid].name, self.Potions[pid].desc, 240 + (idx - 1) * 44)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
ps:ConnectEvent(UITouchExitEvent, function() self:HideTooltip() end)
|
||||||
|
ps:ConnectEvent(UITouchDownEvent, function() self:OpenPotionMenu(idx) end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local pmUse = _EntityService:GetEntityByPath("/ui/DefaultGroup/CombatHud/PotionMenu/Use")
|
||||||
|
if pmUse ~= nil and pmUse.ButtonComponent ~= nil then
|
||||||
|
pmUse:ConnectEvent(ButtonClickEvent, function() self:UsePotion() end)
|
||||||
|
end
|
||||||
|
local pmToss = _EntityService:GetEntityByPath("/ui/DefaultGroup/CombatHud/PotionMenu/Toss")
|
||||||
|
if pmToss ~= nil and pmToss.ButtonComponent ~= nil then
|
||||||
|
pmToss:ConnectEvent(ButtonClickEvent, function() self:TossPotion() end)
|
||||||
|
end
|
||||||
|
local pmClose = _EntityService:GetEntityByPath("/ui/DefaultGroup/CombatHud/PotionMenu/Close")
|
||||||
|
if pmClose ~= nil and pmClose.ButtonComponent ~= nil then
|
||||||
|
pmClose:ConnectEvent(ButtonClickEvent, function() self:ClosePotionMenu() end)
|
||||||
|
end
|
||||||
|
local shopPotion = _EntityService:GetEntityByPath("/ui/DefaultGroup/ShopHud/Potion")
|
||||||
|
if shopPotion ~= nil and shopPotion.ButtonComponent ~= nil then
|
||||||
|
shopPotion:ConnectEvent(ButtonClickEvent, function() self:BuyPotion() end)
|
||||||
|
end
|
||||||
|
local chest = _EntityService:GetEntityByPath("/ui/DefaultGroup/TreasureHud/Chest")
|
||||||
|
if chest ~= nil and chest.ButtonComponent ~= nil then
|
||||||
|
chest:ConnectEvent(ButtonClickEvent, function() self:OpenChest() end)
|
||||||
|
end
|
||||||
|
local treasureLeave = _EntityService:GetEntityByPath("/ui/DefaultGroup/TreasureHud/Leave")
|
||||||
|
if treasureLeave ~= nil and treasureLeave.ButtonComponent ~= nil then
|
||||||
|
treasureLeave:ConnectEvent(ButtonClickEvent, function() self:LeaveNode() end)
|
||||||
|
end
|
||||||
|
local jcRelic = _EntityService:GetEntityByPath("/ui/DefaultGroup/JobChoiceHud/RelicButton")
|
||||||
|
if jcRelic ~= nil and jcRelic.ButtonComponent ~= nil then
|
||||||
|
jcRelic:ConnectEvent(ButtonClickEvent, function() self:PickJobReward("relic") end)
|
||||||
|
end
|
||||||
|
local jcJob = _EntityService:GetEntityByPath("/ui/DefaultGroup/JobChoiceHud/JobButton")
|
||||||
|
if jcJob ~= nil and jcJob.ButtonComponent ~= nil then
|
||||||
|
jcJob:ConnectEvent(ButtonClickEvent, function() self:PickJobReward("job") end)
|
||||||
|
end
|
||||||
|
for i = 1, 3 do
|
||||||
|
local slotIdx = i
|
||||||
|
local jb = _EntityService:GetEntityByPath("/ui/DefaultGroup/JobSelectHud/Job_slot" .. tostring(i))
|
||||||
|
if jb ~= nil and jb.ButtonComponent ~= nil then
|
||||||
|
jb:ConnectEvent(ButtonClickEvent, function()
|
||||||
|
if self.JobOpts ~= nil and self.JobOpts[slotIdx] ~= nil then
|
||||||
|
self:SetJob(self.JobOpts[slotIdx].id)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end`),
|
||||||
|
method('StartPlayerTurn', `self.Turn = self.Turn + 1
|
||||||
|
self.Energy = self.MaxEnergy
|
||||||
|
self:ApplyRelics("turnStart")
|
||||||
|
self.PlayerBlock = 0
|
||||||
|
if self.ClayBlockNext > 0 then
|
||||||
|
self.PlayerBlock = self.PlayerBlock + self.ClayBlockNext
|
||||||
|
self.ClayBlockNext = 0
|
||||||
|
end
|
||||||
|
if self.PlayerPowers ~= nil then
|
||||||
|
for i = 1, #self.PlayerPowers do
|
||||||
|
local pc = self.Cards[self.PlayerPowers[i]]
|
||||||
|
if pc ~= nil then
|
||||||
|
if pc.powerEffect == "strengthPerTurn" then
|
||||||
|
self.PlayerStr = self.PlayerStr + pc.value
|
||||||
|
elseif pc.powerEffect == "energyPerTurn" then
|
||||||
|
self.Energy = self.Energy + pc.value
|
||||||
|
elseif pc.powerEffect == "blockPerTurn" then
|
||||||
|
self.PlayerBlock = self.PlayerBlock + pc.value
|
||||||
|
end
|
||||||
|
if pc.turnStartShiv ~= nil then
|
||||||
|
self:AddCardsToHand("Shiv", pc.turnStartShiv)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
self:DrawCards(5)
|
||||||
|
self:RenderHand(true)
|
||||||
|
self:RenderCombat()`),
|
||||||
|
method('EndPlayerTurn', `if self.CombatOver == true or self.FxBusy == true or self.TurnBusy == true then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if self:IsDiscardSelecting() == true then
|
||||||
|
self:Toast("버릴 카드를 먼저 선택하세요")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local burn = 0
|
||||||
|
for bi = 1, #self.Hand do
|
||||||
|
\tlocal hc = self.Cards[self.Hand[bi]]
|
||||||
|
\tif hc ~= nil and hc.endTurnDamage ~= nil then burn = burn + hc.endTurnDamage end
|
||||||
|
end
|
||||||
|
if burn > 0 then
|
||||||
|
\tself.PlayerHp = self.PlayerHp - burn
|
||||||
|
\tif self.PlayerHp < 0 then self.PlayerHp = 0 end
|
||||||
|
\tself:ShowPlayerDmgPop(burn)
|
||||||
|
\tself:RenderCombat()
|
||||||
|
end
|
||||||
|
local kept = {}
|
||||||
|
for i = 1, #self.Hand do
|
||||||
|
\tlocal cardId = self.Hand[i]
|
||||||
|
\tlocal c = self.Cards[cardId]
|
||||||
|
\tif c ~= nil and c.retain == true then
|
||||||
|
\t\ttable.insert(kept, cardId)
|
||||||
|
\telse
|
||||||
|
\t\ttable.insert(self.DiscardPile, cardId)
|
||||||
|
\tend
|
||||||
|
end
|
||||||
|
self.Hand = kept
|
||||||
|
if self.PlayerWeak > 0 then self.PlayerWeak = self.PlayerWeak - 1 end
|
||||||
|
if self.PlayerVuln > 0 then self.PlayerVuln = self.PlayerVuln - 1 end
|
||||||
|
self:RenderHand(false)
|
||||||
|
self:RenderPiles()
|
||||||
|
self:EnemyTurn()`),
|
||||||
|
method('DrawCards', `local drawnSlots = {}
|
||||||
|
for i = 1, amount do
|
||||||
|
\tif #self.DrawPile <= 0 then
|
||||||
|
\t\tself:RecycleDiscardIntoDraw()
|
||||||
|
\tend
|
||||||
|
\tif #self.DrawPile <= 0 then
|
||||||
|
\t\tbreak
|
||||||
|
\tend
|
||||||
|
\tlocal cardId = table.remove(self.DrawPile)
|
||||||
|
\tif #self.Hand >= 10 then
|
||||||
|
\t\ttable.insert(self.DiscardPile, cardId)
|
||||||
|
\t\tself:TriggerSly(cardId)
|
||||||
|
\telse
|
||||||
|
\t\ttable.insert(self.Hand, cardId)
|
||||||
|
\t\tif #self.Hand <= 5 then
|
||||||
|
\t\t\ttable.insert(drawnSlots, #self.Hand)
|
||||||
|
\t\tend
|
||||||
|
\tend
|
||||||
|
end
|
||||||
|
self:RenderPiles()
|
||||||
|
if animate == true and #drawnSlots > 0 then
|
||||||
|
\tself:RenderHand(false)
|
||||||
|
\tlocal drawStart = Vector2(-590, 8)
|
||||||
|
\tfor i = 1, #drawnSlots do
|
||||||
|
\t\tlocal slot = drawnSlots[i]
|
||||||
|
\t\tself:AnimateCardFrom(slot, drawStart, Vector2(self:GetHandSlotX(slot), 0), 0.08 + i * 0.045)
|
||||||
|
\tend
|
||||||
|
end`, [
|
||||||
|
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'amount' },
|
||||||
|
{ Type: 'boolean', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'animate' },
|
||||||
|
]),
|
||||||
|
method('AddCardsToHand', `if self.Hand == nil then
|
||||||
|
self.Hand = {}
|
||||||
|
end
|
||||||
|
if self.DiscardPile == nil then
|
||||||
|
self.DiscardPile = {}
|
||||||
|
end
|
||||||
|
for i = 1, amount do
|
||||||
|
if #self.Hand >= 10 then
|
||||||
|
table.insert(self.DiscardPile, cardId)
|
||||||
|
else
|
||||||
|
table.insert(self.Hand, cardId)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
self:RenderHand(false)
|
||||||
|
self:RenderPiles()`, [
|
||||||
|
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'cardId' },
|
||||||
|
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'amount' },
|
||||||
|
]),
|
||||||
|
method('RecycleDiscardIntoDraw', `if self.DiscardPile == nil or #self.DiscardPile <= 0 then
|
||||||
|
\treturn
|
||||||
|
end
|
||||||
|
self.DrawPile = {}
|
||||||
|
for i = 1, #self.DiscardPile do
|
||||||
|
\tself.DrawPile[i] = self.DiscardPile[i]
|
||||||
|
end
|
||||||
|
self.DiscardPile = {}
|
||||||
|
self:Shuffle(self.DrawPile)`),
|
||||||
|
method('RenderPiles', `self:SetText("/ui/DefaultGroup/DeckHud/DrawPile/Count", self:FormatNumber(#self.DrawPile))
|
||||||
|
self:SetText("/ui/DefaultGroup/DeckHud/DiscardPile/Count", self:FormatNumber(#self.DiscardPile))
|
||||||
|
self:SetText("/ui/DefaultGroup/DeckHud/ExhaustPile/Count", self:FormatNumber(#(self.ExhaustPile or {})))
|
||||||
|
self:SetText("/ui/DefaultGroup/DeckHud/EnergyOrb/Value", string.format("%d", self.Energy) .. "/" .. string.format("%d", self.MaxEnergy))
|
||||||
|
local inspect = _EntityService:GetEntityByPath("/ui/DefaultGroup/DeckInspectHud")
|
||||||
|
if inspect ~= nil and inspect.Enable == true and self.DeckInspectKind ~= "" then
|
||||||
|
self:OpenDeckInspect(self.DeckInspectKind)
|
||||||
|
end`),
|
||||||
|
];
|
||||||
229
tools/deck/cb/deckview.mjs
Normal file
229
tools/deck/cb/deckview.mjs
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
import { method, RUN_LENGTH, GOLD_PER_WIN, CARD_PRICE, REST_HEAL, RELIC_PRICE, ACT_COUNT, ACT_MAPS, LOBBY_MAP, LOBBY_SPAWN } from '../lib/codeblock.mjs';
|
||||||
|
import { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, CARDFRAMES, RARITIES, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, POTIONS, luaSoulShopTable, frameRuid, luaFramesTable, luaNodeIconsTable, luaRelicsTable, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, luaCardsTable, luaDeckTable } from '../lib/data.mjs';
|
||||||
|
import { UI_FILE, COMMON_FILE, UI_ROOT, GENERATED_UI_SECTIONS, UI_APPEND_ORDER, DISABLED_STOCK_CONTROLS, TRANSPARENT, DARK, GOLD, ATTACK, DEFEND, SKILL, DAMAGE_DIGIT_RUIDS, DAMAGE_POP_MAX_DIGITS, DAMAGE_POP_DIGIT_W, DAMAGE_POP_DIGIT_H, DAMAGE_POP_DIGIT_SPACING, MAX_MONSTERS, HEAD_OFFSET_Y, HP_BAR_W, WHITE, CARD_NAME_TEXT, CARD_DESC_TEXT, cardFaceLayout, CARD_W, CARD_H, CARD_SPACING, CARD_XS, ALIGN_CENTER, ALIGN_BOTTOM_CENTER, guid, transform, sprite, button, text, scrollLayoutGroup, popupLayerFor, uiOrderFor, displayOrderFor, applySortingOverride, entity, uiPath, sectionRoot, isGeneratedUiEntity, appendUiSection } from '../lib/ui-helpers.mjs';
|
||||||
|
|
||||||
|
export const deckViewMethods = [
|
||||||
|
method('OpenDeckInspect', `self.DeckInspectKind = kind
|
||||||
|
if self.DeckAllOpen == true then
|
||||||
|
self.DeckAllOpen = false
|
||||||
|
local allHud = _EntityService:GetEntityByPath("/ui/DefaultGroup/DeckAllHud")
|
||||||
|
if allHud ~= nil then
|
||||||
|
allHud.Enable = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local pile = {}
|
||||||
|
local title = ""
|
||||||
|
if kind == "discard" then
|
||||||
|
pile = self.DiscardPile or {}
|
||||||
|
title = "버린 덱"
|
||||||
|
elseif kind == "exhaust" then
|
||||||
|
pile = self.ExhaustPile or {}
|
||||||
|
title = "소멸 덱"
|
||||||
|
else
|
||||||
|
pile = self.DrawPile or {}
|
||||||
|
title = "뽑을 덱"
|
||||||
|
end
|
||||||
|
self:RenderDeckInspect(pile, title)
|
||||||
|
local hud = _EntityService:GetEntityByPath("/ui/DefaultGroup/DeckInspectHud")
|
||||||
|
if hud ~= nil then
|
||||||
|
hud.Enable = true
|
||||||
|
end`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'kind' }]),
|
||||||
|
method('CloseDeckInspect', `self.DeckInspectKind = ""
|
||||||
|
local hud = _EntityService:GetEntityByPath("/ui/DefaultGroup/DeckInspectHud")
|
||||||
|
if hud ~= nil then
|
||||||
|
hud.Enable = false
|
||||||
|
end`),
|
||||||
|
method('RenderDeckInspect', `local count = 0
|
||||||
|
if pile ~= nil then
|
||||||
|
count = #pile
|
||||||
|
end
|
||||||
|
local suffix = " (" .. tostring(count) .. ")"
|
||||||
|
if count > 60 then
|
||||||
|
suffix = suffix .. " - 60장까지 표시"
|
||||||
|
end
|
||||||
|
self:SetText("/ui/DefaultGroup/DeckInspectHud/Title", title .. suffix)
|
||||||
|
local empty = _EntityService:GetEntityByPath("/ui/DefaultGroup/DeckInspectHud/Empty")
|
||||||
|
if empty ~= nil then
|
||||||
|
empty.Enable = count <= 0
|
||||||
|
end
|
||||||
|
for i = 1, 60 do
|
||||||
|
local e = _EntityService:GetEntityByPath("/ui/DefaultGroup/DeckInspectHud/Grid/Card" .. tostring(i))
|
||||||
|
if e ~= nil then
|
||||||
|
local cardId = nil
|
||||||
|
if pile ~= nil then
|
||||||
|
cardId = pile[i]
|
||||||
|
end
|
||||||
|
if cardId == nil then
|
||||||
|
e.Enable = false
|
||||||
|
else
|
||||||
|
e.Enable = true
|
||||||
|
self:ApplyInspectCardVisual(i, cardId)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end`, [
|
||||||
|
{ Type: 'any', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'pile' },
|
||||||
|
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'title' },
|
||||||
|
]),
|
||||||
|
method('ApplyInspectCardVisual', `self:ApplyCardFace("/ui/DefaultGroup/DeckInspectHud/Grid/Card" .. tostring(slot), cardId)`, [
|
||||||
|
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' },
|
||||||
|
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'cardId' },
|
||||||
|
]),
|
||||||
|
method('BindClassDeckTabs', `local warriorTab = _EntityService:GetEntityByPath("/ui/DefaultGroup/DeckAllHud/WarriorTab")
|
||||||
|
if warriorTab ~= nil and warriorTab.ButtonComponent ~= nil then
|
||||||
|
if self.WarriorDeckTabHandler ~= nil then
|
||||||
|
warriorTab:DisconnectEvent(ButtonClickEvent, self.WarriorDeckTabHandler)
|
||||||
|
self.WarriorDeckTabHandler = nil
|
||||||
|
end
|
||||||
|
self.WarriorDeckTabHandler = warriorTab:ConnectEvent(ButtonClickEvent, function() self:SetClassDeckTab("warrior") end)
|
||||||
|
end
|
||||||
|
local thiefTab = _EntityService:GetEntityByPath("/ui/DefaultGroup/DeckAllHud/ThiefTab")
|
||||||
|
if thiefTab ~= nil and thiefTab.ButtonComponent ~= nil then
|
||||||
|
if self.ThiefDeckTabHandler ~= nil then
|
||||||
|
thiefTab:DisconnectEvent(ButtonClickEvent, self.ThiefDeckTabHandler)
|
||||||
|
self.ThiefDeckTabHandler = nil
|
||||||
|
end
|
||||||
|
self.ThiefDeckTabHandler = thiefTab:ConnectEvent(ButtonClickEvent, function() self:SetClassDeckTab("bandit") end)
|
||||||
|
end
|
||||||
|
local mageTab = _EntityService:GetEntityByPath("/ui/DefaultGroup/DeckAllHud/MageTab")
|
||||||
|
if mageTab ~= nil and mageTab.ButtonComponent ~= nil then
|
||||||
|
if self.MageDeckTabHandler ~= nil then
|
||||||
|
mageTab:DisconnectEvent(ButtonClickEvent, self.MageDeckTabHandler)
|
||||||
|
self.MageDeckTabHandler = nil
|
||||||
|
end
|
||||||
|
self.MageDeckTabHandler = mageTab:ConnectEvent(ButtonClickEvent, function() self:SetClassDeckTab("magician") end)
|
||||||
|
end`),
|
||||||
|
method('OpenClassDeck', `self.CodexMode = false
|
||||||
|
self.ClassDeckMode = true
|
||||||
|
self.DeckAllOpen = true
|
||||||
|
self:SetClassDeckTab(className)
|
||||||
|
local hud = _EntityService:GetEntityByPath("/ui/DefaultGroup/DeckAllHud")
|
||||||
|
if hud ~= nil then
|
||||||
|
hud.Enable = true
|
||||||
|
end`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'className' }]),
|
||||||
|
method('SetClassDeckTab', `if self.ClassDeckMode ~= true then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
self.ClassDeckCards = {}
|
||||||
|
self.ClassDeckTitle = "직업 덱"
|
||||||
|
if className ~= "warrior" and className ~= "magician" and className ~= "bandit" then
|
||||||
|
className = "bandit"
|
||||||
|
end
|
||||||
|
self.ClassDeckClass = className
|
||||||
|
local allowed = {}
|
||||||
|
if className == "warrior" then
|
||||||
|
allowed["warrior"] = true
|
||||||
|
allowed["fighter"] = true
|
||||||
|
allowed["page"] = true
|
||||||
|
allowed["spearman"] = true
|
||||||
|
self.ClassDeckTitle = "전사 전체 덱"
|
||||||
|
elseif className == "magician" then
|
||||||
|
allowed["magician"] = true
|
||||||
|
allowed["firepoison"] = true
|
||||||
|
allowed["icelightning"] = true
|
||||||
|
allowed["cleric"] = true
|
||||||
|
self.ClassDeckTitle = "마법사 전체 덱"
|
||||||
|
else
|
||||||
|
allowed["bandit"] = true
|
||||||
|
allowed["shiv"] = true
|
||||||
|
allowed["poisoner"] = true
|
||||||
|
allowed["trickster"] = true
|
||||||
|
self.ClassDeckTitle = "도적 전체 덱"
|
||||||
|
end
|
||||||
|
for id, c in pairs(self.Cards) do
|
||||||
|
if c ~= nil and c.curse ~= true and allowed[c.class] == true then
|
||||||
|
table.insert(self.ClassDeckCards, id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
table.sort(self.ClassDeckCards, function(a, b)
|
||||||
|
local ca = self.Cards[a]
|
||||||
|
local cb = self.Cards[b]
|
||||||
|
local na = a
|
||||||
|
local nb = b
|
||||||
|
if ca ~= nil and ca.name ~= nil then na = ca.name end
|
||||||
|
if cb ~= nil and cb.name ~= nil then nb = cb.name end
|
||||||
|
if na == nb then return a < b end
|
||||||
|
return na < nb
|
||||||
|
end)
|
||||||
|
self:RenderAllDeck()
|
||||||
|
self:RenderClassDeckTabs()`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'className' }]),
|
||||||
|
method('RenderClassDeckTabs', `local tabs = {
|
||||||
|
{ path = "/ui/DefaultGroup/DeckAllHud/WarriorTab", cls = "warrior" },
|
||||||
|
{ path = "/ui/DefaultGroup/DeckAllHud/ThiefTab", cls = "bandit" },
|
||||||
|
{ path = "/ui/DefaultGroup/DeckAllHud/MageTab", cls = "magician" },
|
||||||
|
}
|
||||||
|
for i = 1, #tabs do
|
||||||
|
local e = _EntityService:GetEntityByPath(tabs[i].path)
|
||||||
|
if e ~= nil then
|
||||||
|
e.Enable = self.ClassDeckMode == true
|
||||||
|
if e.SpriteGUIRendererComponent ~= nil then
|
||||||
|
if self.ClassDeckClass == tabs[i].cls then
|
||||||
|
e.SpriteGUIRendererComponent.Color = Color(0.22, 0.28, 0.34, 1)
|
||||||
|
else
|
||||||
|
e.SpriteGUIRendererComponent.Color = Color(0.11, 0.13, 0.16, 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end`),
|
||||||
|
method('OpenAllDeck', `local inspectHud = _EntityService:GetEntityByPath("/ui/DefaultGroup/DeckInspectHud")
|
||||||
|
if inspectHud ~= nil then
|
||||||
|
inspectHud.Enable = false
|
||||||
|
end
|
||||||
|
self.DeckInspectKind = ""
|
||||||
|
self.ClassDeckMode = false
|
||||||
|
self.ClassDeckClass = ""
|
||||||
|
self:RenderClassDeckTabs()
|
||||||
|
self.DeckAllOpen = true
|
||||||
|
self:RenderAllDeck()
|
||||||
|
local hud = _EntityService:GetEntityByPath("/ui/DefaultGroup/DeckAllHud")
|
||||||
|
if hud ~= nil then
|
||||||
|
hud.Enable = true
|
||||||
|
end`),
|
||||||
|
method('CloseAllDeck', `self.DeckAllOpen = false
|
||||||
|
local hud = _EntityService:GetEntityByPath("/ui/DefaultGroup/DeckAllHud")
|
||||||
|
if hud ~= nil then
|
||||||
|
hud.Enable = false
|
||||||
|
end
|
||||||
|
if self.ClassDeckMode == true then
|
||||||
|
self.ClassDeckMode = false
|
||||||
|
self.ClassDeckCards = {}
|
||||||
|
self.ClassDeckTitle = ""
|
||||||
|
self.ClassDeckClass = ""
|
||||||
|
end
|
||||||
|
self:RenderClassDeckTabs()
|
||||||
|
if self.CodexMode == true then
|
||||||
|
self.CodexMode = false
|
||||||
|
self:ShowLobby()
|
||||||
|
end`),
|
||||||
|
method('RenderAllDeck', `local pile = self.RunDeck or {}
|
||||||
|
local title = "모든 덱"
|
||||||
|
if self.ClassDeckMode == true then
|
||||||
|
pile = self.ClassDeckCards or {}
|
||||||
|
title = self.ClassDeckTitle
|
||||||
|
elseif self.CodexMode == true then
|
||||||
|
pile = self.CodexCards or {}
|
||||||
|
title = "카드 도감"
|
||||||
|
end
|
||||||
|
local count = #pile
|
||||||
|
self:SetText("/ui/DefaultGroup/DeckAllHud/Title", title .. " (" .. tostring(count) .. ")")
|
||||||
|
self:RenderClassDeckTabs()
|
||||||
|
local empty = _EntityService:GetEntityByPath("/ui/DefaultGroup/DeckAllHud/Empty")
|
||||||
|
if empty ~= nil then
|
||||||
|
empty.Enable = count <= 0
|
||||||
|
end
|
||||||
|
for i = 1, 120 do
|
||||||
|
local e = _EntityService:GetEntityByPath("/ui/DefaultGroup/DeckAllHud/Grid/Card" .. tostring(i))
|
||||||
|
if e ~= nil then
|
||||||
|
local cardId = pile[i]
|
||||||
|
if cardId == nil then
|
||||||
|
e.Enable = false
|
||||||
|
else
|
||||||
|
e.Enable = true
|
||||||
|
self:ApplyAllDeckCardVisual(i, cardId)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end`),
|
||||||
|
method('ApplyAllDeckCardVisual', `self:ApplyCardFace("/ui/DefaultGroup/DeckAllHud/Grid/Card" .. tostring(slot), cardId)`, [
|
||||||
|
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' },
|
||||||
|
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'cardId' },
|
||||||
|
]),
|
||||||
|
];
|
||||||
401
tools/deck/cb/hand.mjs
Normal file
401
tools/deck/cb/hand.mjs
Normal file
@@ -0,0 +1,401 @@
|
|||||||
|
import { method, RUN_LENGTH, GOLD_PER_WIN, CARD_PRICE, REST_HEAL, RELIC_PRICE, ACT_COUNT, ACT_MAPS, LOBBY_MAP, LOBBY_SPAWN } from '../lib/codeblock.mjs';
|
||||||
|
import { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, CARDFRAMES, RARITIES, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, POTIONS, luaSoulShopTable, frameRuid, luaFramesTable, luaNodeIconsTable, luaRelicsTable, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, luaCardsTable, luaDeckTable } from '../lib/data.mjs';
|
||||||
|
import { UI_FILE, COMMON_FILE, UI_ROOT, GENERATED_UI_SECTIONS, UI_APPEND_ORDER, DISABLED_STOCK_CONTROLS, TRANSPARENT, DARK, GOLD, ATTACK, DEFEND, SKILL, DAMAGE_DIGIT_RUIDS, DAMAGE_POP_MAX_DIGITS, DAMAGE_POP_DIGIT_W, DAMAGE_POP_DIGIT_H, DAMAGE_POP_DIGIT_SPACING, MAX_MONSTERS, HEAD_OFFSET_Y, HP_BAR_W, WHITE, CARD_NAME_TEXT, CARD_DESC_TEXT, cardFaceLayout, CARD_W, CARD_H, CARD_SPACING, CARD_XS, ALIGN_CENTER, ALIGN_BOTTOM_CENTER, guid, transform, sprite, button, text, scrollLayoutGroup, popupLayerFor, uiOrderFor, displayOrderFor, applySortingOverride, entity, uiPath, sectionRoot, isGeneratedUiEntity, appendUiSection } from '../lib/ui-helpers.mjs';
|
||||||
|
|
||||||
|
export const handMethods = [
|
||||||
|
method('GetHandSlotX', `local n = 0
|
||||||
|
if self.Hand ~= nil then
|
||||||
|
n = #self.Hand
|
||||||
|
end
|
||||||
|
if n <= 0 then
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
local spacing = 175
|
||||||
|
if n > 8 then spacing = math.floor(1400 / n) end
|
||||||
|
local startX = -((n - 1) * spacing) / 2
|
||||||
|
return startX + (slot - 1) * spacing`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' }], 0, 'number'),
|
||||||
|
method('RenderHand', `local n = #self.Hand
|
||||||
|
local spacing = 175
|
||||||
|
if n > 8 then spacing = math.floor(1400 / n) end
|
||||||
|
local startX = -((n - 1) * spacing) / 2
|
||||||
|
local drawStart = Vector2(-590, 8)
|
||||||
|
for i = 1, 10 do
|
||||||
|
\tlocal cardEntity = _EntityService:GetEntityByPath("/ui/DefaultGroup/CardHand/Card" .. tostring(i))
|
||||||
|
\tif cardEntity ~= nil then
|
||||||
|
\t\tlocal cardId = self.Hand[i]
|
||||||
|
\t\tif cardId == nil then
|
||||||
|
\t\t\tcardEntity.Enable = false
|
||||||
|
\t\telse
|
||||||
|
\t\t\tcardEntity.Enable = true
|
||||||
|
\t\t\tif cardEntity.UITransformComponent ~= nil then cardEntity.UITransformComponent.UIScale = Vector3(1, 1, 1) end
|
||||||
|
\t\t\tself:ApplyCardVisual(i, cardId)
|
||||||
|
\t\t\tlocal tx = self:GetHandSlotX(i)
|
||||||
|
\t\t\tif animate == true then
|
||||||
|
\t\t\t\tself:AnimateCardFrom(i, drawStart, Vector2(tx, 0), 0.16 + i * 0.03)
|
||||||
|
\t\t\telse
|
||||||
|
\t\t\t\tif cardEntity.UITransformComponent ~= nil then cardEntity.UITransformComponent.anchoredPosition = Vector2(tx, 0) end
|
||||||
|
\t\t\tend
|
||||||
|
\t\tend
|
||||||
|
\tend
|
||||||
|
end
|
||||||
|
self:RenderPiles()`, [{ Type: 'boolean', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'animate' }]),
|
||||||
|
method('ApplyCardFace', `local c = self.Cards[cardId]
|
||||||
|
if c == nil then
|
||||||
|
c = { name = cardId, cost = 0, desc = "", kind = "Skill", class = "warrior", rarity = "normal" }
|
||||||
|
end
|
||||||
|
local e = _EntityService:GetEntityByPath(base)
|
||||||
|
if e ~= nil and e.SpriteGUIRendererComponent ~= nil then
|
||||||
|
if e.UITransformComponent ~= nil then
|
||||||
|
e.UITransformComponent.UIScale = Vector3(1, 1, 1)
|
||||||
|
end
|
||||||
|
local frames = self.CardFrames[self.ClassToFrame[c.class] or "warrior"]
|
||||||
|
local ruid = nil
|
||||||
|
if frames ~= nil then
|
||||||
|
ruid = frames[c.rarity or "normal"]
|
||||||
|
end
|
||||||
|
if ruid ~= nil then
|
||||||
|
e.SpriteGUIRendererComponent.ImageRUID = ruid
|
||||||
|
e.SpriteGUIRendererComponent.Color = Color(1, 1, 1, 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
self:SetText(base .. "/Cost", string.format("%d", c.cost))
|
||||||
|
self:SetText(base .. "/Name", c.name)
|
||||||
|
self:SetText(base .. "/Desc", c.desc)
|
||||||
|
local art = _EntityService:GetEntityByPath(base .. "/Art")
|
||||||
|
if art ~= nil then
|
||||||
|
if c.image ~= nil and c.image ~= "" then
|
||||||
|
art.Enable = true
|
||||||
|
if art.SpriteGUIRendererComponent ~= nil then
|
||||||
|
art.SpriteGUIRendererComponent.ImageRUID = c.image
|
||||||
|
end
|
||||||
|
else
|
||||||
|
art.Enable = false
|
||||||
|
end
|
||||||
|
end`, [
|
||||||
|
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'base' },
|
||||||
|
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'cardId' },
|
||||||
|
]),
|
||||||
|
method('SetCardHover', `local prefix = ""
|
||||||
|
local count = 0
|
||||||
|
local xs = {}
|
||||||
|
local baseY = 0
|
||||||
|
local hoverIndex = 0
|
||||||
|
local push = 110
|
||||||
|
if string.find(path, "/ui/DefaultGroup/CardHand/Card") == 1 then
|
||||||
|
if self.DragSlot ~= nil and self.DragSlot > 0 then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
prefix = "/ui/DefaultGroup/CardHand/Card"
|
||||||
|
count = 0
|
||||||
|
if self.Hand ~= nil then count = #self.Hand end
|
||||||
|
for i = 1, count do
|
||||||
|
xs[i] = self:GetHandSlotX(i)
|
||||||
|
end
|
||||||
|
baseY = 0
|
||||||
|
hoverIndex = tonumber(string.match(path, "Card(%d+)")) or 0
|
||||||
|
elseif string.find(path, "/ui/DefaultGroup/RewardHud/Reward") == 1 then
|
||||||
|
prefix = "/ui/DefaultGroup/RewardHud/Reward"
|
||||||
|
count = 3
|
||||||
|
xs = { -300, 0, 300 }
|
||||||
|
baseY = 0
|
||||||
|
hoverIndex = tonumber(string.match(path, "Reward(%d+)")) or 0
|
||||||
|
elseif string.find(path, "/ui/DefaultGroup/ShopHud/Card") == 1 then
|
||||||
|
prefix = "/ui/DefaultGroup/ShopHud/Card"
|
||||||
|
count = 3
|
||||||
|
xs = { -300, 0, 300 }
|
||||||
|
baseY = 20
|
||||||
|
hoverIndex = tonumber(string.match(path, "Card(%d+)")) or 0
|
||||||
|
end
|
||||||
|
if count <= 0 then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if self.CardHoverTweenId ~= nil and self.CardHoverTweenId ~= 0 then
|
||||||
|
_TimerService:ClearTimer(self.CardHoverTweenId)
|
||||||
|
self.CardHoverTweenId = 0
|
||||||
|
end
|
||||||
|
local items = {}
|
||||||
|
for i = 1, count do
|
||||||
|
local e = _EntityService:GetEntityByPath(prefix .. tostring(i))
|
||||||
|
if e ~= nil and e.UITransformComponent ~= nil then
|
||||||
|
local tr = e.UITransformComponent
|
||||||
|
local tx = xs[i]
|
||||||
|
local ty = baseY
|
||||||
|
local sc = 1
|
||||||
|
if hover == true and hoverIndex > 0 then
|
||||||
|
if i == hoverIndex and e.Enable == true then
|
||||||
|
sc = 1.5
|
||||||
|
elseif i < hoverIndex then
|
||||||
|
tx = tx - push
|
||||||
|
elseif i > hoverIndex then
|
||||||
|
tx = tx + push
|
||||||
|
end
|
||||||
|
end
|
||||||
|
table.insert(items, { tr = tr, sx = tr.anchoredPosition.x, sy = tr.anchoredPosition.y, ss = tr.UIScale.x, tx = tx, ty = ty, ts = sc })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local elapsed = 0
|
||||||
|
local duration = 0.12
|
||||||
|
local eventId = 0
|
||||||
|
eventId = _TimerService:SetTimerRepeat(function()
|
||||||
|
elapsed = elapsed + 1 / 60
|
||||||
|
local t = math.min(elapsed / duration, 1)
|
||||||
|
local eased = _TweenLogic:Ease(0, 1, 1, EaseType.SineEaseOut, t)
|
||||||
|
for i = 1, #items do
|
||||||
|
local it = items[i]
|
||||||
|
local x = it.sx + (it.tx - it.sx) * eased
|
||||||
|
local y = it.sy + (it.ty - it.sy) * eased
|
||||||
|
local s = it.ss + (it.ts - it.ss) * eased
|
||||||
|
it.tr.anchoredPosition = Vector2(x, y)
|
||||||
|
it.tr.UIScale = Vector3(s, s, 1)
|
||||||
|
end
|
||||||
|
if t >= 1 then
|
||||||
|
_TimerService:ClearTimer(eventId)
|
||||||
|
if self.CardHoverTweenId == eventId then
|
||||||
|
self.CardHoverTweenId = 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end, 1 / 60)
|
||||||
|
self.CardHoverTweenId = eventId`, [
|
||||||
|
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'path' },
|
||||||
|
{ Type: 'boolean', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'hover' },
|
||||||
|
]),
|
||||||
|
method('ApplyCardVisual', `self:ApplyCardFace("/ui/DefaultGroup/CardHand/Card" .. tostring(slot), cardId)`, [
|
||||||
|
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' },
|
||||||
|
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'cardId' },
|
||||||
|
]),
|
||||||
|
method('SetText', `local entity = _EntityService:GetEntityByPath(path)
|
||||||
|
if entity ~= nil and entity.TextComponent ~= nil then
|
||||||
|
\tentity.TextComponent.Text = value
|
||||||
|
end`, [
|
||||||
|
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'path' },
|
||||||
|
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'value' },
|
||||||
|
]),
|
||||||
|
method('FormatNumber', `if value == nil then
|
||||||
|
return ""
|
||||||
|
end
|
||||||
|
local n = tonumber(value)
|
||||||
|
if n == nil then
|
||||||
|
return tostring(value)
|
||||||
|
end
|
||||||
|
if math.abs(n - math.floor(n)) < 0.00001 then
|
||||||
|
return string.format("%d", math.floor(n))
|
||||||
|
end
|
||||||
|
return tostring(n)`, [{ Type: 'any', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'value' }], 0, 'string'),
|
||||||
|
method('AnimateCardFrom', `local cardEntity = _EntityService:GetEntityByPath("/ui/DefaultGroup/CardHand/Card" .. tostring(slot))
|
||||||
|
if cardEntity == nil or cardEntity.UITransformComponent == nil then
|
||||||
|
\treturn
|
||||||
|
end
|
||||||
|
local tr = cardEntity.UITransformComponent
|
||||||
|
tr.anchoredPosition = fromPos
|
||||||
|
local elapsed = 0
|
||||||
|
local eventId = 0
|
||||||
|
eventId = _TimerService:SetTimerRepeat(function()
|
||||||
|
\telapsed = elapsed + 1 / 60
|
||||||
|
\tlocal t = math.min(elapsed / duration, 1)
|
||||||
|
\tlocal eased = _TweenLogic:Ease(0, 1, 1, EaseType.SineEaseOut, t)
|
||||||
|
\ttr.anchoredPosition = Vector2(fromPos.x + (toPos.x - fromPos.x) * eased, fromPos.y + (toPos.y - fromPos.y) * eased)
|
||||||
|
\tif t >= 1 then
|
||||||
|
\t\t_TimerService:ClearTimer(eventId)
|
||||||
|
\tend
|
||||||
|
end, 1 / 60)`, [
|
||||||
|
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' },
|
||||||
|
{ Type: 'any', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'fromPos' },
|
||||||
|
{ Type: 'any', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'toPos' },
|
||||||
|
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'duration' },
|
||||||
|
]),
|
||||||
|
method('AddCardBlock', `local amount = base or 0
|
||||||
|
if amount > 0 and self.PlayerDex ~= nil then
|
||||||
|
amount = amount + self.PlayerDex
|
||||||
|
end
|
||||||
|
if amount < 0 then
|
||||||
|
amount = 0
|
||||||
|
end
|
||||||
|
self.PlayerBlock = self.PlayerBlock + amount
|
||||||
|
return amount`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'base' }], 0, 'number'),
|
||||||
|
method('CalcPlayerAttack', `local base2 = base
|
||||||
|
self.FightAttackCount = self.FightAttackCount + 1
|
||||||
|
if self.FightAttackCount == 1 and self:HasRelic("akabeko") then
|
||||||
|
base2 = base2 + 8
|
||||||
|
end
|
||||||
|
local dmg = base2 + self.PlayerStr
|
||||||
|
if self:HasRelic("penNib") and self.FightAttackCount % 10 == 0 then
|
||||||
|
dmg = dmg * 2
|
||||||
|
end
|
||||||
|
if self.PlayerWeak > 0 then
|
||||||
|
dmg = math.floor(dmg * 0.75)
|
||||||
|
end
|
||||||
|
if dmg > 0 and dmg < 5 and self:HasRelic("boot") then
|
||||||
|
dmg = 5
|
||||||
|
end
|
||||||
|
if dmg < 0 then
|
||||||
|
dmg = 0
|
||||||
|
end
|
||||||
|
return dmg`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'base' }], 0, 'number'),
|
||||||
|
method('ResolveCardEffects', `if c == nil then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if c.kind == "Attack" then
|
||||||
|
if c.damage ~= nil then
|
||||||
|
self:PlayerAttackMotion()
|
||||||
|
local total = 0
|
||||||
|
local hitN = c.hits or 1
|
||||||
|
for h = 1, hitN do
|
||||||
|
total = total + self:CalcPlayerAttack(c.damage)
|
||||||
|
end
|
||||||
|
if c.aoe == true then
|
||||||
|
self:PlayAoeFx(c.fx or c.image, total)
|
||||||
|
else
|
||||||
|
self:PlayAttackFx(self.TargetIndex, c.fx or c.image, total, c.pierce == true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if c.block ~= nil then
|
||||||
|
self:AddCardBlock(c.block)
|
||||||
|
end
|
||||||
|
if free ~= true then
|
||||||
|
self:ApplyRelics("cardPlayed")
|
||||||
|
end
|
||||||
|
elseif c.kind == "Skill" then
|
||||||
|
if c.block ~= nil then
|
||||||
|
self:AddCardBlock(c.block)
|
||||||
|
end
|
||||||
|
elseif c.kind == "Power" then
|
||||||
|
if free ~= true then
|
||||||
|
table.insert(self.PlayerPowers, cardId)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if c.strength ~= nil then
|
||||||
|
self.PlayerStr = self.PlayerStr + c.strength
|
||||||
|
end
|
||||||
|
if c.dex ~= nil then
|
||||||
|
self.PlayerDex = self.PlayerDex + c.dex
|
||||||
|
end
|
||||||
|
if c.thorns ~= nil then
|
||||||
|
self.PlayerThorns = self.PlayerThorns + c.thorns
|
||||||
|
end
|
||||||
|
if c.selfVuln ~= nil then
|
||||||
|
self.PlayerVuln = self.PlayerVuln + c.selfVuln
|
||||||
|
end
|
||||||
|
if c.heal ~= nil then
|
||||||
|
self.PlayerHp = math.min(self.PlayerHp + c.heal, self.PlayerMaxHp)
|
||||||
|
end
|
||||||
|
if c.weak ~= nil or c.vuln ~= nil or c.poison ~= nil then
|
||||||
|
local tm = self.Monsters[self.TargetIndex]
|
||||||
|
if tm == nil or tm.alive ~= true then
|
||||||
|
for i = 1, #self.Monsters do
|
||||||
|
if self.Monsters[i].alive == true then tm = self.Monsters[i]; self.TargetIndex = i; break end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if tm ~= nil and tm.alive == true then
|
||||||
|
if c.weak ~= nil then tm.weak = tm.weak + c.weak end
|
||||||
|
if c.poison ~= nil then tm.poison = (tm.poison or 0) + c.poison end
|
||||||
|
if c.vuln ~= nil then
|
||||||
|
tm.vuln = tm.vuln + c.vuln
|
||||||
|
if self:HasRelic("championBelt") then
|
||||||
|
tm.weak = tm.weak + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if c.draw ~= nil then
|
||||||
|
self:DrawCards(c.draw, true)
|
||||||
|
end
|
||||||
|
if c.addShiv ~= nil and c.discard == nil and c.discardAll ~= true then
|
||||||
|
self:AddCardsToHand("Shiv", c.addShiv)
|
||||||
|
end`, [
|
||||||
|
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'cardId' },
|
||||||
|
{ Type: 'any', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'c' },
|
||||||
|
{ Type: 'boolean', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'free' },
|
||||||
|
]),
|
||||||
|
method('TriggerSly', `local c = self.Cards[cardId]
|
||||||
|
if c == nil or c.sly ~= true then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
self:Toast("교활 발동: " .. c.name)
|
||||||
|
self:ResolveCardEffects(cardId, c, true)`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'cardId' }]),
|
||||||
|
method('DiscardHandCard', `if self.Hand == nil then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local cardId = self.Hand[slot]
|
||||||
|
if cardId == nil then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
table.remove(self.Hand, slot)
|
||||||
|
table.insert(self.DiscardPile, cardId)
|
||||||
|
if triggerSly == true then
|
||||||
|
self:TriggerSly(cardId)
|
||||||
|
end`, [
|
||||||
|
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' },
|
||||||
|
{ Type: 'boolean', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'triggerSly' },
|
||||||
|
]),
|
||||||
|
method('IsDiscardSelecting', `return self.DiscardSelectRemaining ~= nil and self.DiscardSelectRemaining > 0`, [], 0, 'boolean'),
|
||||||
|
method('UpdateDiscardPrompt', `local e = _EntityService:GetEntityByPath("/ui/DefaultGroup/CombatHud/DiscardPrompt")
|
||||||
|
if e == nil then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if self:IsDiscardSelecting() == true then
|
||||||
|
local picked = self.DiscardSelectTotal - self.DiscardSelectRemaining
|
||||||
|
self:SetText("/ui/DefaultGroup/CombatHud/DiscardPrompt", "버릴 카드 선택 " .. self:FormatNumber(picked + 1) .. "/" .. self:FormatNumber(self.DiscardSelectTotal))
|
||||||
|
e.Enable = true
|
||||||
|
else
|
||||||
|
e.Enable = false
|
||||||
|
end`),
|
||||||
|
method('BeginDiscardSelection', `if c == nil or self.Hand == nil then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
local n = 0
|
||||||
|
if c.discardAll == true then
|
||||||
|
n = #self.Hand
|
||||||
|
elseif c.discard ~= nil then
|
||||||
|
n = math.min(c.discard, #self.Hand)
|
||||||
|
end
|
||||||
|
if n <= 0 then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
self.DiscardSelectRemaining = n
|
||||||
|
self.DiscardSelectTotal = n
|
||||||
|
self.DiscardPostShiv = 0
|
||||||
|
self.DiscardShivPerPick = 0
|
||||||
|
if c.addShiv ~= nil then
|
||||||
|
self.DiscardPostShiv = c.addShiv
|
||||||
|
end
|
||||||
|
if c.addShivPerDiscard == true then
|
||||||
|
self.DiscardShivPerPick = 1
|
||||||
|
end
|
||||||
|
self:UpdateDiscardPrompt()
|
||||||
|
self:Toast("버릴 카드를 선택하세요")
|
||||||
|
return true`, [{ Type: 'any', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'c' }], 0, 'boolean'),
|
||||||
|
method('FinishDiscardSelection', `self.DiscardSelectRemaining = 0
|
||||||
|
self.DiscardSelectTotal = 0
|
||||||
|
local shivCount = self.DiscardPostShiv or 0
|
||||||
|
self.DiscardPostShiv = 0
|
||||||
|
self.DiscardShivPerPick = 0
|
||||||
|
self:UpdateDiscardPrompt()
|
||||||
|
if shivCount > 0 then
|
||||||
|
self:AddCardsToHand("Shiv", shivCount)
|
||||||
|
end
|
||||||
|
self:RenderHand(false)
|
||||||
|
self:RenderPiles()
|
||||||
|
self:RenderCombat()
|
||||||
|
self:CheckCombatEnd()`),
|
||||||
|
method('SelectDiscardSlot', `if self:IsDiscardSelecting() ~= true then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
if self.Hand == nil or self.Hand[slot] == nil then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
local discarded = self.Hand[slot]
|
||||||
|
self:DiscardHandCard(slot, true)
|
||||||
|
if discarded ~= nil and self.DiscardShivPerPick ~= nil and self.DiscardShivPerPick > 0 then
|
||||||
|
self.DiscardPostShiv = (self.DiscardPostShiv or 0) + self.DiscardShivPerPick
|
||||||
|
end
|
||||||
|
self.DiscardSelectRemaining = self.DiscardSelectRemaining - 1
|
||||||
|
if self.DiscardSelectRemaining <= 0 or #self.Hand <= 0 then
|
||||||
|
self:FinishDiscardSelection()
|
||||||
|
else
|
||||||
|
self:UpdateDiscardPrompt()
|
||||||
|
self:RenderHand(false)
|
||||||
|
self:RenderPiles()
|
||||||
|
self:RenderCombat()
|
||||||
|
end
|
||||||
|
return true`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' }], 0, 'boolean'),
|
||||||
|
];
|
||||||
213
tools/deck/cb/items.mjs
Normal file
213
tools/deck/cb/items.mjs
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
import { method, RUN_LENGTH, GOLD_PER_WIN, CARD_PRICE, REST_HEAL, RELIC_PRICE, ACT_COUNT, ACT_MAPS, LOBBY_MAP, LOBBY_SPAWN } from '../lib/codeblock.mjs';
|
||||||
|
import { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, CARDFRAMES, RARITIES, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, POTIONS, luaSoulShopTable, frameRuid, luaFramesTable, luaNodeIconsTable, luaRelicsTable, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, luaCardsTable, luaDeckTable } from '../lib/data.mjs';
|
||||||
|
import { UI_FILE, COMMON_FILE, UI_ROOT, GENERATED_UI_SECTIONS, UI_APPEND_ORDER, DISABLED_STOCK_CONTROLS, TRANSPARENT, DARK, GOLD, ATTACK, DEFEND, SKILL, DAMAGE_DIGIT_RUIDS, DAMAGE_POP_MAX_DIGITS, DAMAGE_POP_DIGIT_W, DAMAGE_POP_DIGIT_H, DAMAGE_POP_DIGIT_SPACING, MAX_MONSTERS, HEAD_OFFSET_Y, HP_BAR_W, WHITE, CARD_NAME_TEXT, CARD_DESC_TEXT, cardFaceLayout, CARD_W, CARD_H, CARD_SPACING, CARD_XS, ALIGN_CENTER, ALIGN_BOTTOM_CENTER, guid, transform, sprite, button, text, scrollLayoutGroup, popupLayerFor, uiOrderFor, displayOrderFor, applySortingOverride, entity, uiPath, sectionRoot, isGeneratedUiEntity, appendUiSection } from '../lib/ui-helpers.mjs';
|
||||||
|
|
||||||
|
export const itemMethods = [
|
||||||
|
method('HasRelic', `if self.RunRelics == nil then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
for i = 1, #self.RunRelics do
|
||||||
|
if self.RunRelics[i] == id then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'id' }], 0, 'boolean'),
|
||||||
|
method('ApplyRelics', `if self.RunRelics == nil then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
for i = 1, #self.RunRelics do
|
||||||
|
local r = self.Relics[self.RunRelics[i]]
|
||||||
|
if r ~= nil and r.hook == hook then
|
||||||
|
if r.effect == "block" then
|
||||||
|
self.PlayerBlock = self.PlayerBlock + r.value
|
||||||
|
elseif r.effect == "energy" then
|
||||||
|
self.Energy = self.Energy + r.value
|
||||||
|
elseif r.effect == "strength" then
|
||||||
|
self.PlayerStr = self.PlayerStr + r.value
|
||||||
|
elseif r.effect == "draw" then
|
||||||
|
self:DrawCards(r.value)
|
||||||
|
self:RenderHand(false)
|
||||||
|
elseif r.effect == "heal" or r.effect == "healOnAttack" or r.effect == "healOnWin" then
|
||||||
|
self.PlayerHp = self.PlayerHp + r.value
|
||||||
|
if self.PlayerHp > self.PlayerMaxHp then
|
||||||
|
self.PlayerHp = self.PlayerMaxHp
|
||||||
|
end
|
||||||
|
elseif r.effect == "healIfLow" then
|
||||||
|
if self.PlayerHp * 2 <= self.PlayerMaxHp then
|
||||||
|
self.PlayerHp = self.PlayerHp + r.value
|
||||||
|
if self.PlayerHp > self.PlayerMaxHp then
|
||||||
|
self.PlayerHp = self.PlayerMaxHp
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif r.effect == "gold" then
|
||||||
|
self.Gold = self.Gold + r.value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'hook' }]),
|
||||||
|
method('AddRelic', `if self.RunRelics == nil then
|
||||||
|
self.RunRelics = {}
|
||||||
|
end
|
||||||
|
table.insert(self.RunRelics, id)
|
||||||
|
local r = self.Relics[id]
|
||||||
|
if r ~= nil and r.hook == "passive" then
|
||||||
|
if r.effect == "potionSlots" then
|
||||||
|
self.PotionSlots = r.value
|
||||||
|
self:RenderPotions()
|
||||||
|
elseif r.effect == "maxHp" then
|
||||||
|
self.PlayerMaxHp = self.PlayerMaxHp + r.value
|
||||||
|
self.PlayerHp = self.PlayerHp + r.value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
self:RenderRelics()`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'id' }]),
|
||||||
|
method('PickNewRelic', `local pool = {}
|
||||||
|
for i = 1, #self.RelicPool do
|
||||||
|
if self:HasRelic(self.RelicPool[i]) == false then
|
||||||
|
table.insert(pool, self.RelicPool[i])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if #pool == 0 then
|
||||||
|
self.Gold = self.Gold + 25
|
||||||
|
self:Toast("유물을 모두 모았습니다! 메소 +25")
|
||||||
|
return ""
|
||||||
|
end
|
||||||
|
return pool[math.random(1, #pool)]`, [], 0, 'string'),
|
||||||
|
method('AddPotion', `if self.RunPotions == nil then
|
||||||
|
self.RunPotions = {}
|
||||||
|
end
|
||||||
|
if #self.RunPotions >= self.PotionSlots then
|
||||||
|
self:Toast("물약 슬롯이 가득 찼습니다")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
table.insert(self.RunPotions, pid)
|
||||||
|
self:RenderPotions()
|
||||||
|
return true`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'pid' }], 0, 'boolean'),
|
||||||
|
method('MaybeDropPotion', `if math.random() > ${POTIONS.dropChance} then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local keys = {}
|
||||||
|
for pid, _ in pairs(self.Potions) do
|
||||||
|
table.insert(keys, pid)
|
||||||
|
end
|
||||||
|
table.sort(keys)
|
||||||
|
local pid = keys[math.random(1, #keys)]
|
||||||
|
if self:AddPotion(pid) == true then
|
||||||
|
local p = self.Potions[pid]
|
||||||
|
self:Toast("물약 획득: " .. p.name)
|
||||||
|
end`),
|
||||||
|
method('RenderPotions', `for i = 1, 5 do
|
||||||
|
local base = "/ui/DefaultGroup/CombatHud/TopBar/PotionSlot" .. tostring(i)
|
||||||
|
local e = _EntityService:GetEntityByPath(base)
|
||||||
|
if e ~= nil and e.SpriteGUIRendererComponent ~= nil then
|
||||||
|
local pid = nil
|
||||||
|
if self.RunPotions ~= nil then
|
||||||
|
pid = self.RunPotions[i]
|
||||||
|
end
|
||||||
|
if pid ~= nil and self.Potions[pid] ~= nil then
|
||||||
|
e.SpriteGUIRendererComponent.ImageRUID = self.Potions[pid].icon
|
||||||
|
e.SpriteGUIRendererComponent.Color = Color(1, 1, 1, 1)
|
||||||
|
elseif i > self.PotionSlots then
|
||||||
|
e.SpriteGUIRendererComponent.ImageRUID = ""
|
||||||
|
e.SpriteGUIRendererComponent.Color = Color(0.1, 0.1, 0.12, 0.85)
|
||||||
|
else
|
||||||
|
e.SpriteGUIRendererComponent.ImageRUID = ""
|
||||||
|
e.SpriteGUIRendererComponent.Color = Color(0.22, 0.25, 0.3, 0.9)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end`),
|
||||||
|
method('OpenPotionMenu', `if self.RunPotions == nil or self.RunPotions[slot] == nil then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
self.PotionMenuSlot = slot
|
||||||
|
local pid = self.RunPotions[slot]
|
||||||
|
local p = self.Potions[pid]
|
||||||
|
if p ~= nil then
|
||||||
|
self:SetText("/ui/DefaultGroup/CombatHud/PotionMenu/Title", p.name .. " — " .. p.desc)
|
||||||
|
end
|
||||||
|
self:SetEntityEnabled("/ui/DefaultGroup/CombatHud/PotionMenu", true)`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' }]),
|
||||||
|
method('ClosePotionMenu', `self.PotionMenuSlot = 0
|
||||||
|
self:SetEntityEnabled("/ui/DefaultGroup/CombatHud/PotionMenu", false)`),
|
||||||
|
method('UsePotion', `if self.PotionMenuSlot <= 0 then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if self.CombatOver == true or self.TurnBusy == true or self.FxBusy == true then
|
||||||
|
self:Toast("지금은 사용할 수 없습니다")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local combat = _EntityService:GetEntityByPath("/ui/DefaultGroup/CombatHud")
|
||||||
|
local hand = _EntityService:GetEntityByPath("/ui/DefaultGroup/CardHand")
|
||||||
|
if combat == nil or combat.Enable ~= true or hand == nil or hand.Enable ~= true then
|
||||||
|
self:Toast("전투 중에만 사용할 수 있습니다")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local pid = self.RunPotions[self.PotionMenuSlot]
|
||||||
|
if pid == nil then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local p = self.Potions[pid]
|
||||||
|
if p == nil then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if p.effect == "heal" then
|
||||||
|
self.PlayerHp = math.min(self.PlayerHp + p.value, self.PlayerMaxHp)
|
||||||
|
elseif p.effect == "damage" then
|
||||||
|
self:DealDamageToTarget(p.value, false)
|
||||||
|
self:ShowDmgPop(self.TargetIndex, p.value)
|
||||||
|
elseif p.effect == "strength" then
|
||||||
|
self.PlayerStr = self.PlayerStr + p.value
|
||||||
|
elseif p.effect == "block" then
|
||||||
|
self.PlayerBlock = self.PlayerBlock + p.value
|
||||||
|
elseif p.effect == "energy" then
|
||||||
|
self.Energy = self.Energy + p.value
|
||||||
|
elseif p.effect == "weak" then
|
||||||
|
local tm = self.Monsters[self.TargetIndex]
|
||||||
|
if tm ~= nil and tm.alive == true then
|
||||||
|
tm.weak = tm.weak + p.value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
table.remove(self.RunPotions, self.PotionMenuSlot)
|
||||||
|
self:Toast("물약 사용: " .. p.name)
|
||||||
|
self:ClosePotionMenu()
|
||||||
|
self:RenderPotions()
|
||||||
|
self:RenderPiles()
|
||||||
|
self:RenderCombat()
|
||||||
|
self:CheckCombatEnd()`),
|
||||||
|
method('TossPotion', `if self.PotionMenuSlot <= 0 then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local pid = self.RunPotions[self.PotionMenuSlot]
|
||||||
|
if pid ~= nil then
|
||||||
|
local p = self.Potions[pid]
|
||||||
|
table.remove(self.RunPotions, self.PotionMenuSlot)
|
||||||
|
if p ~= nil then
|
||||||
|
self:Toast("물약 버림: " .. p.name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
self:ClosePotionMenu()
|
||||||
|
self:RenderPotions()`),
|
||||||
|
method('RenderRelics', `local count = 0
|
||||||
|
if self.RunRelics ~= nil then
|
||||||
|
count = #self.RunRelics
|
||||||
|
end
|
||||||
|
for i = 1, 10 do
|
||||||
|
local base = "/ui/DefaultGroup/CombatHud/TopBar/RelicSlot" .. tostring(i)
|
||||||
|
local e = _EntityService:GetEntityByPath(base)
|
||||||
|
if e ~= nil and e.SpriteGUIRendererComponent ~= nil then
|
||||||
|
local rid = nil
|
||||||
|
if self.RunRelics ~= nil then
|
||||||
|
rid = self.RunRelics[i]
|
||||||
|
end
|
||||||
|
if rid ~= nil and self.Relics[rid] ~= nil and (i < 10 or count <= 10) then
|
||||||
|
e.SpriteGUIRendererComponent.ImageRUID = self.Relics[rid].icon
|
||||||
|
e.SpriteGUIRendererComponent.Color = Color(1, 1, 1, 1)
|
||||||
|
else
|
||||||
|
e.SpriteGUIRendererComponent.ImageRUID = ""
|
||||||
|
e.SpriteGUIRendererComponent.Color = Color(0.15, 0.16, 0.2, 0.6)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local of = ""
|
||||||
|
if count > 10 then
|
||||||
|
of = "+" .. tostring(count - 9)
|
||||||
|
end
|
||||||
|
self:SetText("/ui/DefaultGroup/CombatHud/TopBar/RelicOverflow", of)`),
|
||||||
|
];
|
||||||
79
tools/deck/cb/jobs.mjs
Normal file
79
tools/deck/cb/jobs.mjs
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
import { method, RUN_LENGTH, GOLD_PER_WIN, CARD_PRICE, REST_HEAL, RELIC_PRICE, ACT_COUNT, ACT_MAPS, LOBBY_MAP, LOBBY_SPAWN } from '../lib/codeblock.mjs';
|
||||||
|
import { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, CARDFRAMES, RARITIES, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, POTIONS, luaSoulShopTable, frameRuid, luaFramesTable, luaNodeIconsTable, luaRelicsTable, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, luaCardsTable, luaDeckTable } from '../lib/data.mjs';
|
||||||
|
import { UI_FILE, COMMON_FILE, UI_ROOT, GENERATED_UI_SECTIONS, UI_APPEND_ORDER, DISABLED_STOCK_CONTROLS, TRANSPARENT, DARK, GOLD, ATTACK, DEFEND, SKILL, DAMAGE_DIGIT_RUIDS, DAMAGE_POP_MAX_DIGITS, DAMAGE_POP_DIGIT_W, DAMAGE_POP_DIGIT_H, DAMAGE_POP_DIGIT_SPACING, MAX_MONSTERS, HEAD_OFFSET_Y, HP_BAR_W, WHITE, CARD_NAME_TEXT, CARD_DESC_TEXT, cardFaceLayout, CARD_W, CARD_H, CARD_SPACING, CARD_XS, ALIGN_CENTER, ALIGN_BOTTOM_CENTER, guid, transform, sprite, button, text, scrollLayoutGroup, popupLayerFor, uiOrderFor, displayOrderFor, applySortingOverride, entity, uiPath, sectionRoot, isGeneratedUiEntity, appendUiSection } from '../lib/ui-helpers.mjs';
|
||||||
|
|
||||||
|
export const jobMethods = [
|
||||||
|
method('ShowJobChoice', `self:SetEntityEnabled("/ui/DefaultGroup/CardHand", false)
|
||||||
|
self:SetEntityEnabled("/ui/DefaultGroup/DeckHud", false)
|
||||||
|
self:SetEntityEnabled("/ui/DefaultGroup/JobChoiceHud", true)`),
|
||||||
|
method('PickJobReward', `self:SetEntityEnabled("/ui/DefaultGroup/JobChoiceHud", false)
|
||||||
|
if kind == "relic" then
|
||||||
|
local bid = self:PickNewRelic()
|
||||||
|
if bid ~= "" then
|
||||||
|
self:AddRelic(bid)
|
||||||
|
local br = self.Relics[bid]
|
||||||
|
if br ~= nil then
|
||||||
|
self:Toast("유물 획득: " .. br.name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
self:ContinueAfterBoss()
|
||||||
|
else
|
||||||
|
self:ShowJobSelect()
|
||||||
|
end`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'kind' }]),
|
||||||
|
method('ShowJobSelect', `local opts = self.Jobs[self.SelectedClass]
|
||||||
|
if opts == nil then
|
||||||
|
opts = self.Jobs["warrior"]
|
||||||
|
end
|
||||||
|
self.JobOpts = opts
|
||||||
|
for i = 1, 3 do
|
||||||
|
local base = "/ui/DefaultGroup/JobSelectHud/Job_slot" .. tostring(i)
|
||||||
|
local o = opts[i]
|
||||||
|
if o ~= nil then
|
||||||
|
self:SetEntityEnabled(base, true)
|
||||||
|
self:SetText(base .. "/Name", o.name)
|
||||||
|
self:SetText(base .. "/Desc", o.desc)
|
||||||
|
local sc = self.Cards[o.starter]
|
||||||
|
if sc ~= nil then
|
||||||
|
self:SetText(base .. "/Starter", "대표 카드: " .. sc.name)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
self:SetEntityEnabled(base, false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
self:SetEntityEnabled("/ui/DefaultGroup/JobSelectHud", true)`),
|
||||||
|
method('JobLabel', `if self.PlayerJob ~= "" and self.Jobs ~= nil then
|
||||||
|
for cls, list in pairs(self.Jobs) do
|
||||||
|
for i = 1, #list do
|
||||||
|
if list[i].id == self.PlayerJob then
|
||||||
|
return list[i].name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if self.SelectedClass == "warrior" then
|
||||||
|
return "전사"
|
||||||
|
elseif self.SelectedClass == "bandit" then
|
||||||
|
return "도적"
|
||||||
|
elseif self.SelectedClass == "magician" then
|
||||||
|
return "마법사"
|
||||||
|
end
|
||||||
|
return "플레이어"`, [], 0, 'string'),
|
||||||
|
method('SetJob', `self.PlayerJob = jobId
|
||||||
|
local starter = ""
|
||||||
|
local opts = self.Jobs[self.SelectedClass] or {}
|
||||||
|
for i = 1, #opts do
|
||||||
|
if opts[i].id == jobId then
|
||||||
|
starter = opts[i].starter
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if starter ~= "" then
|
||||||
|
table.insert(self.RunDeck, starter)
|
||||||
|
local sc = self.Cards[starter]
|
||||||
|
if sc ~= nil then
|
||||||
|
self:Toast("2차 전직: " .. self:JobLabel() .. "! 신규 카드 — " .. sc.name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
self:SetText("/ui/DefaultGroup/CombatHud/PlayerPanel/Name", self:JobLabel())
|
||||||
|
self:SetEntityEnabled("/ui/DefaultGroup/JobSelectHud", false)
|
||||||
|
self:ContinueAfterBoss()`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'jobId' }]),
|
||||||
|
];
|
||||||
230
tools/deck/cb/map.mjs
Normal file
230
tools/deck/cb/map.mjs
Normal file
@@ -0,0 +1,230 @@
|
|||||||
|
import { method, RUN_LENGTH, GOLD_PER_WIN, CARD_PRICE, REST_HEAL, RELIC_PRICE, ACT_COUNT, ACT_MAPS, LOBBY_MAP, LOBBY_SPAWN } from '../lib/codeblock.mjs';
|
||||||
|
import { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, CARDFRAMES, RARITIES, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, POTIONS, luaSoulShopTable, frameRuid, luaFramesTable, luaNodeIconsTable, luaRelicsTable, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, luaCardsTable, luaDeckTable } from '../lib/data.mjs';
|
||||||
|
import { UI_FILE, COMMON_FILE, UI_ROOT, GENERATED_UI_SECTIONS, UI_APPEND_ORDER, DISABLED_STOCK_CONTROLS, TRANSPARENT, DARK, GOLD, ATTACK, DEFEND, SKILL, DAMAGE_DIGIT_RUIDS, DAMAGE_POP_MAX_DIGITS, DAMAGE_POP_DIGIT_W, DAMAGE_POP_DIGIT_H, DAMAGE_POP_DIGIT_SPACING, MAX_MONSTERS, HEAD_OFFSET_Y, HP_BAR_W, WHITE, CARD_NAME_TEXT, CARD_DESC_TEXT, cardFaceLayout, CARD_W, CARD_H, CARD_SPACING, CARD_XS, ALIGN_CENTER, ALIGN_BOTTOM_CENTER, guid, transform, sprite, button, text, scrollLayoutGroup, popupLayerFor, uiOrderFor, displayOrderFor, applySortingOverride, entity, uiPath, sectionRoot, isGeneratedUiEntity, appendUiSection } from '../lib/ui-helpers.mjs';
|
||||||
|
|
||||||
|
export const mapMethods = [
|
||||||
|
method('ShowMap', `self:ShowState("map")
|
||||||
|
self:RenderMap()`),
|
||||||
|
method('GenerateMap', `-- 절차 생성 — tools/map/rogue-map.mjs(JS 미러)와 로직 동기화 유지
|
||||||
|
self.MapNodes = {}
|
||||||
|
self.MapStart = {}
|
||||||
|
self.VisitedNodes = {}
|
||||||
|
self.Depth = 0
|
||||||
|
self.MapNodes["boss"] = { type = "boss", row = ${MAP_ROWS} + 1, col = 0, next = {} }
|
||||||
|
local cols = { 1, 2, 3, 4 }
|
||||||
|
for i = #cols, 2, -1 do
|
||||||
|
local j = math.random(1, i)
|
||||||
|
cols[i], cols[j] = cols[j], cols[i]
|
||||||
|
end
|
||||||
|
local starts = { cols[1], cols[2], math.random(1, ${MAP_COLS}), math.random(1, ${MAP_COLS}) }
|
||||||
|
for p = 1, 4 do
|
||||||
|
local c = starts[p]
|
||||||
|
local sid = "r1c" .. tostring(c)
|
||||||
|
if self.MapNodes[sid] == nil then
|
||||||
|
self.MapNodes[sid] = { type = "combat", row = 1, col = c, next = {} }
|
||||||
|
end
|
||||||
|
local found = false
|
||||||
|
for i = 1, #self.MapStart do
|
||||||
|
if self.MapStart[i] == sid then found = true end
|
||||||
|
end
|
||||||
|
if found == false then
|
||||||
|
table.insert(self.MapStart, sid)
|
||||||
|
end
|
||||||
|
for r = 1, ${MAP_ROWS} - 1 do
|
||||||
|
local nc = c + math.random(-1, 1)
|
||||||
|
if nc < 1 then nc = 1 end
|
||||||
|
if nc > ${MAP_COLS} then nc = ${MAP_COLS} end
|
||||||
|
local nid = "r" .. tostring(r + 1) .. "c" .. tostring(nc)
|
||||||
|
if self.MapNodes[nid] == nil then
|
||||||
|
self.MapNodes[nid] = { type = "combat", row = r + 1, col = nc, next = {} }
|
||||||
|
end
|
||||||
|
local fid = "r" .. tostring(r) .. "c" .. tostring(c)
|
||||||
|
local dup = false
|
||||||
|
for i = 1, #self.MapNodes[fid].next do
|
||||||
|
if self.MapNodes[fid].next[i] == nid then dup = true end
|
||||||
|
end
|
||||||
|
if dup == false then
|
||||||
|
table.insert(self.MapNodes[fid].next, nid)
|
||||||
|
end
|
||||||
|
c = nc
|
||||||
|
end
|
||||||
|
local lid = "r" .. tostring(${MAP_ROWS}) .. "c" .. tostring(c)
|
||||||
|
local bdup = false
|
||||||
|
for i = 1, #self.MapNodes[lid].next do
|
||||||
|
if self.MapNodes[lid].next[i] == "boss" then bdup = true end
|
||||||
|
end
|
||||||
|
if bdup == false then
|
||||||
|
table.insert(self.MapNodes[lid].next, "boss")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
for r = 3, ${MAP_ROWS} do
|
||||||
|
for c = 1, ${MAP_COLS} do
|
||||||
|
local id = "r" .. tostring(r) .. "c" .. tostring(c)
|
||||||
|
local node = self.MapNodes[id]
|
||||||
|
if node ~= nil then
|
||||||
|
-- 부모 노드 타입 수집 (rest/shop/elite 는 부모와 같은 타입 연속 금지)
|
||||||
|
local parentTypes = {}
|
||||||
|
for pid, pn in pairs(self.MapNodes) do
|
||||||
|
if pn.row == r - 1 then
|
||||||
|
for i = 1, #pn.next do
|
||||||
|
if pn.next[i] == id then parentTypes[pn.type] = true end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local w
|
||||||
|
if r == ${MAP_ROWS} then
|
||||||
|
w = { { "rest", 50 }, { "combat", 25 }, { "shop", 10 }, { "elite", 8 }, { "treasure", 7 } }
|
||||||
|
elseif r >= 4 then
|
||||||
|
w = { { "combat", 45 }, { "elite", 16 }, { "shop", 12 }, { "rest", 12 }, { "treasure", 15 } }
|
||||||
|
else
|
||||||
|
w = { { "combat", 45 }, { "shop", 12 }, { "rest", 12 } }
|
||||||
|
end
|
||||||
|
local total = 0
|
||||||
|
for i = 1, #w do
|
||||||
|
local t = w[i][1]
|
||||||
|
if (t == "elite" or t == "rest" or t == "shop") and parentTypes[t] == true then
|
||||||
|
w[i][2] = 0
|
||||||
|
end
|
||||||
|
total = total + w[i][2]
|
||||||
|
end
|
||||||
|
local roll = math.random() * total
|
||||||
|
local acc = 0
|
||||||
|
for i = 1, #w do
|
||||||
|
acc = acc + w[i][2]
|
||||||
|
if roll <= acc then
|
||||||
|
node.type = w[i][1]
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
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('RenderMapNode', `local base = "/ui/DefaultGroup/MapHud/Node_" .. id
|
||||||
|
local e = _EntityService:GetEntityByPath(base)
|
||||||
|
if e == nil then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local node = self.MapNodes[id]
|
||||||
|
if node == nil then
|
||||||
|
e.Enable = false
|
||||||
|
return
|
||||||
|
end
|
||||||
|
e.Enable = true
|
||||||
|
local ruid = self.NodeIcons[node.type]
|
||||||
|
if ruid == nil then
|
||||||
|
ruid = self.NodeIcons["combat"]
|
||||||
|
end
|
||||||
|
if e.SpriteGUIRendererComponent ~= nil and ruid ~= nil then
|
||||||
|
e.SpriteGUIRendererComponent.ImageRUID = ruid
|
||||||
|
end
|
||||||
|
local reachable = self:IsReachable(id)
|
||||||
|
local visited = false
|
||||||
|
if self.VisitedNodes ~= nil then
|
||||||
|
for i = 1, #self.VisitedNodes do
|
||||||
|
if self.VisitedNodes[i] == id then visited = true end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if e.SpriteGUIRendererComponent ~= nil then
|
||||||
|
if id == self.CurrentNodeId then
|
||||||
|
e.SpriteGUIRendererComponent.Color = Color(1, 0.82, 0.3, 1)
|
||||||
|
elseif visited == true then
|
||||||
|
e.SpriteGUIRendererComponent.Color = Color(0.5, 0.5, 0.55, 0.9)
|
||||||
|
elseif reachable == true then
|
||||||
|
e.SpriteGUIRendererComponent.Color = Color(1, 1, 1, 1)
|
||||||
|
else
|
||||||
|
e.SpriteGUIRendererComponent.Color = Color(0.68, 0.68, 0.72, 0.85)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if e.ButtonComponent ~= nil then
|
||||||
|
e.ButtonComponent.Enable = reachable
|
||||||
|
end`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'id' }]),
|
||||||
|
method('RenderMapDots', `local node = self.MapNodes[fromId]
|
||||||
|
local has = false
|
||||||
|
if node ~= nil then
|
||||||
|
for i = 1, #node.next do
|
||||||
|
if node.next[i] == toId then has = true end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
for k = 1, 3 do
|
||||||
|
local d = _EntityService:GetEntityByPath("/ui/DefaultGroup/MapHud/Dot_" .. dotId .. "_" .. tostring(k))
|
||||||
|
if d ~= nil then
|
||||||
|
d.Enable = has
|
||||||
|
if has == true and d.SpriteGUIRendererComponent ~= nil then
|
||||||
|
if fromId == self.CurrentNodeId then
|
||||||
|
d.SpriteGUIRendererComponent.Color = Color(0.95, 0.8, 0.3, 1)
|
||||||
|
else
|
||||||
|
d.SpriteGUIRendererComponent.Color = Color(0.5, 0.5, 0.55, 0.8)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end`, [
|
||||||
|
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'dotId' },
|
||||||
|
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'fromId' },
|
||||||
|
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'toId' },
|
||||||
|
]),
|
||||||
|
method('RenderMap', `for r = 1, ${MAP_ROWS} do
|
||||||
|
for c = 1, ${MAP_COLS} do
|
||||||
|
self:RenderMapNode("r" .. tostring(r) .. "c" .. tostring(c))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
self:RenderMapNode("boss")
|
||||||
|
for r = 1, ${MAP_ROWS} - 1 do
|
||||||
|
for c = 1, ${MAP_COLS} do
|
||||||
|
local fid = "r" .. tostring(r) .. "c" .. tostring(c)
|
||||||
|
for c2 = c - 1, c + 1 do
|
||||||
|
if c2 >= 1 and c2 <= ${MAP_COLS} then
|
||||||
|
self:RenderMapDots(fid .. "_" .. tostring(c2), fid, "r" .. tostring(r + 1) .. "c" .. tostring(c2))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
for c = 1, ${MAP_COLS} do
|
||||||
|
local fid = "r" .. tostring(${MAP_ROWS}) .. "c" .. tostring(c)
|
||||||
|
self:RenderMapDots(fid .. "_b", fid, "boss")
|
||||||
|
end
|
||||||
|
`),
|
||||||
|
method('PickNode', `if self.RunActive ~= true then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if self:IsReachable(id) ~= true then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
self.CurrentNodeId = id
|
||||||
|
if self.VisitedNodes == nil then
|
||||||
|
self.VisitedNodes = {}
|
||||||
|
end
|
||||||
|
table.insert(self.VisitedNodes, id)
|
||||||
|
local hud = _EntityService:GetEntityByPath("/ui/DefaultGroup/MapHud")
|
||||||
|
if hud ~= nil then
|
||||||
|
hud.Enable = false
|
||||||
|
end
|
||||||
|
local node = self.MapNodes[id]
|
||||||
|
self.Depth = node.row
|
||||||
|
self:RenderRun()
|
||||||
|
if node.type == "shop" then
|
||||||
|
self:ShowShop()
|
||||||
|
elseif node.type == "rest" then
|
||||||
|
self:ShowRest()
|
||||||
|
elseif node.type == "treasure" then
|
||||||
|
self:ShowTreasure()
|
||||||
|
else
|
||||||
|
self.CurrentEnemyId = ""
|
||||||
|
self:StartCombat()
|
||||||
|
end`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'id' }]),
|
||||||
|
];
|
||||||
308
tools/deck/cb/render.mjs
Normal file
308
tools/deck/cb/render.mjs
Normal file
@@ -0,0 +1,308 @@
|
|||||||
|
import { method, RUN_LENGTH, GOLD_PER_WIN, CARD_PRICE, REST_HEAL, RELIC_PRICE, ACT_COUNT, ACT_MAPS, LOBBY_MAP, LOBBY_SPAWN } from '../lib/codeblock.mjs';
|
||||||
|
import { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, CARDFRAMES, RARITIES, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, POTIONS, luaSoulShopTable, frameRuid, luaFramesTable, luaNodeIconsTable, luaRelicsTable, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, luaCardsTable, luaDeckTable } from '../lib/data.mjs';
|
||||||
|
import { UI_FILE, COMMON_FILE, UI_ROOT, GENERATED_UI_SECTIONS, UI_APPEND_ORDER, DISABLED_STOCK_CONTROLS, TRANSPARENT, DARK, GOLD, ATTACK, DEFEND, SKILL, DAMAGE_DIGIT_RUIDS, DAMAGE_POP_MAX_DIGITS, DAMAGE_POP_DIGIT_W, DAMAGE_POP_DIGIT_H, DAMAGE_POP_DIGIT_SPACING, MAX_MONSTERS, HEAD_OFFSET_Y, HP_BAR_W, WHITE, CARD_NAME_TEXT, CARD_DESC_TEXT, cardFaceLayout, CARD_W, CARD_H, CARD_SPACING, CARD_XS, ALIGN_CENTER, ALIGN_BOTTOM_CENTER, guid, transform, sprite, button, text, scrollLayoutGroup, popupLayerFor, uiOrderFor, displayOrderFor, applySortingOverride, entity, uiPath, sectionRoot, isGeneratedUiEntity, appendUiSection } from '../lib/ui-helpers.mjs';
|
||||||
|
|
||||||
|
export const renderMethods = [
|
||||||
|
method('BuffsLabel', `local parts = {}
|
||||||
|
if str ~= nil and str > 0 then table.insert(parts, "힘+" .. tostring(str)) end
|
||||||
|
if weak ~= nil and weak > 0 then table.insert(parts, "약화" .. tostring(weak)) end
|
||||||
|
if vuln ~= nil and vuln > 0 then table.insert(parts, "취약" .. tostring(vuln)) end
|
||||||
|
if poison ~= nil and poison > 0 then table.insert(parts, "독" .. tostring(poison)) end
|
||||||
|
return table.concat(parts, " ")`, [
|
||||||
|
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'str' },
|
||||||
|
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'weak' },
|
||||||
|
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'vuln' },
|
||||||
|
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'poison' },
|
||||||
|
], 0, 'string'),
|
||||||
|
method('RenderCombat', `for i = 1, ${MAX_MONSTERS} do
|
||||||
|
local base = "/ui/DefaultGroup/CombatHud/MonsterSlot" .. tostring(i)
|
||||||
|
local m = self.Monsters[i]
|
||||||
|
if m ~= nil and m.alive == true then
|
||||||
|
self:SetEntityEnabled(base, true)
|
||||||
|
self:SetText(base .. "/Name", m.name)
|
||||||
|
self:SetText(base .. "/Hp", string.format("%d", m.hp) .. "/" .. string.format("%d", m.maxHp))
|
||||||
|
local intent = m.intents[m.intentIdx]
|
||||||
|
local t = ""
|
||||||
|
if intent ~= nil then
|
||||||
|
if intent.kind == "Attack" then
|
||||||
|
local atk = intent.value + m.str
|
||||||
|
if m.weak > 0 then atk = math.floor(atk * 0.75) end
|
||||||
|
if self.PlayerVuln > 0 then atk = math.floor(atk * 1.5) end
|
||||||
|
t = "공격 " .. tostring(atk)
|
||||||
|
elseif intent.kind == "Defend" then t = "방어 " .. tostring(intent.value)
|
||||||
|
elseif intent.kind == "Debuff" then
|
||||||
|
if intent.effect == "weak" then t = "약화 " .. tostring(intent.value) .. " 부여"
|
||||||
|
else t = "취약 " .. tostring(intent.value) .. " 부여" end
|
||||||
|
elseif intent.kind == "AddCard" then
|
||||||
|
t = "저주 카드 추가"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
self:SetText(base .. "/Intent", t)
|
||||||
|
local dragActive = self.DragTargetIndex ~= nil and self.DragTargetIndex > 0
|
||||||
|
local shownTarget = self.TargetIndex
|
||||||
|
if dragActive == true then shownTarget = self.DragTargetIndex end
|
||||||
|
self:SetEntityEnabled(base .. "/TargetFrame", i == shownTarget)
|
||||||
|
self:SetEntityEnabled(base .. "/TargetMarker", i == shownTarget and dragActive)
|
||||||
|
self:SetEntityEnabled(base .. "/TargetMarker/Label", i == shownTarget and dragActive)
|
||||||
|
local intentEntity = _EntityService:GetEntityByPath(base .. "/Intent")
|
||||||
|
if intentEntity ~= nil and intentEntity.TextComponent ~= nil and intent ~= nil then
|
||||||
|
if intent.kind == "Attack" then
|
||||||
|
intentEntity.TextComponent.FontColor = Color(1, 0.45, 0.35, 1)
|
||||||
|
elseif intent.kind == "Debuff" then
|
||||||
|
intentEntity.TextComponent.FontColor = Color(0.8, 0.5, 1, 1)
|
||||||
|
elseif intent.kind == "AddCard" then
|
||||||
|
intentEntity.TextComponent.FontColor = Color(0.6, 0.85, 0.4, 1)
|
||||||
|
else
|
||||||
|
intentEntity.TextComponent.FontColor = Color(0.5, 0.75, 1, 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
self:SetHpBar(base .. "/HpBarFill", m.hp, m.maxHp, ${HP_BAR_W})
|
||||||
|
self:SetEntityEnabled(base .. "/BlockBadge", m.block > 0)
|
||||||
|
self:SetText(base .. "/BlockBadge/Value", string.format("%d", m.block))
|
||||||
|
self:SetText(base .. "/Buffs", self:BuffsLabel(m.str, m.weak, m.vuln, m.poison or 0))
|
||||||
|
else
|
||||||
|
self:SetEntityEnabled(base, false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
self:SetText("/ui/DefaultGroup/CombatHud/PlayerPanel/HpText", string.format("%d", self.PlayerHp) .. "/" .. string.format("%d", self.PlayerMaxHp))
|
||||||
|
self:SetHpBar("/ui/DefaultGroup/CombatHud/PlayerPanel/HpBarFill", self.PlayerHp, self.PlayerMaxHp, 220)
|
||||||
|
self:SetEntityEnabled("/ui/DefaultGroup/CombatHud/PlayerPanel/BlockBadge", self.PlayerBlock > 0)
|
||||||
|
self:SetText("/ui/DefaultGroup/CombatHud/PlayerPanel/BlockBadge/Value", string.format("%d", self.PlayerBlock))
|
||||||
|
local pb = self:BuffsLabel(self.PlayerStr, self.PlayerWeak, self.PlayerVuln, 0)
|
||||||
|
if self.PlayerDex ~= nil and self.PlayerDex > 0 then
|
||||||
|
if pb ~= "" then pb = pb .. " " end
|
||||||
|
pb = pb .. "민첩+" .. tostring(self.PlayerDex)
|
||||||
|
end
|
||||||
|
if self.PlayerThorns ~= nil and self.PlayerThorns > 0 then
|
||||||
|
if pb ~= "" then pb = pb .. " " end
|
||||||
|
pb = pb .. "가시" .. tostring(self.PlayerThorns)
|
||||||
|
end
|
||||||
|
if self.PlayerPowers ~= nil and #self.PlayerPowers > 0 then
|
||||||
|
local names = {}
|
||||||
|
for i = 1, #self.PlayerPowers do
|
||||||
|
local pc = self.Cards[self.PlayerPowers[i]]
|
||||||
|
if pc ~= nil then table.insert(names, pc.name) end
|
||||||
|
end
|
||||||
|
if pb ~= "" then pb = pb .. " · " end
|
||||||
|
pb = pb .. table.concat(names, " ")
|
||||||
|
end
|
||||||
|
self:SetText("/ui/DefaultGroup/CombatHud/PlayerPanel/Buffs", pb)
|
||||||
|
self:RenderRun()`),
|
||||||
|
method('ShowDmgPop', `local slotKey = string.format("%d", math.floor(slot or 0))
|
||||||
|
local base = "/ui/DefaultGroup/CombatHud/DmgPop" .. slotKey
|
||||||
|
local pop = _EntityService:GetEntityByPath(base)
|
||||||
|
if pop == nil then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
self.DmgPopSeq = (self.DmgPopSeq or 0) + 1
|
||||||
|
local popSeq = self.DmgPopSeq
|
||||||
|
self:SetText(base, "")
|
||||||
|
local damageDigitRuids = { ${DAMAGE_DIGIT_RUIDS.map(luaStr).join(', ')} }
|
||||||
|
local shown = tostring(math.max(0, math.floor(amount)))
|
||||||
|
if string.len(shown) > ${DAMAGE_POP_MAX_DIGITS} then
|
||||||
|
shown = string.sub(shown, 1, ${DAMAGE_POP_MAX_DIGITS})
|
||||||
|
end
|
||||||
|
local digits = {}
|
||||||
|
for i = 1, string.len(shown) do
|
||||||
|
table.insert(digits, tonumber(string.sub(shown, i, i)) or 0)
|
||||||
|
end
|
||||||
|
local totalW = #digits * ${DAMAGE_POP_DIGIT_W} + math.max(0, #digits - 1) * ${DAMAGE_POP_DIGIT_SPACING}
|
||||||
|
local startX = -totalW / 2 + ${DAMAGE_POP_DIGIT_W} / 2
|
||||||
|
for i = 1, ${DAMAGE_POP_MAX_DIGITS} do
|
||||||
|
self:SetEntityEnabled(base .. "/Digit" .. tostring(i), false)
|
||||||
|
end
|
||||||
|
for i = 1, ${DAMAGE_POP_MAX_DIGITS} do
|
||||||
|
local digitPath = base .. "/Digit" .. tostring(i)
|
||||||
|
local digitEntity = _EntityService:GetEntityByPath(digitPath)
|
||||||
|
if digitEntity ~= nil and digitEntity.SpriteGUIRendererComponent ~= nil then
|
||||||
|
if digits[i] ~= nil then
|
||||||
|
digitEntity.SpriteGUIRendererComponent.ImageRUID = damageDigitRuids[digits[i] + 1]
|
||||||
|
digitEntity.SpriteGUIRendererComponent.Color = Color(1, 1, 1, 1)
|
||||||
|
if digitEntity.UITransformComponent ~= nil then
|
||||||
|
digitEntity.UITransformComponent.anchoredPosition = Vector2(startX + (i - 1) * (${DAMAGE_POP_DIGIT_W} + ${DAMAGE_POP_DIGIT_SPACING}), 0)
|
||||||
|
end
|
||||||
|
self:SetEntityEnabled(digitPath, true)
|
||||||
|
else
|
||||||
|
self:SetEntityEnabled(digitPath, false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local popPos = nil
|
||||||
|
local m = self.Monsters[slot]
|
||||||
|
if m ~= nil and m.entity ~= nil and isvalid(m.entity) and m.entity.TransformComponent ~= nil then
|
||||||
|
local wp = m.entity.TransformComponent.WorldPosition
|
||||||
|
local screen = _UILogic:WorldToScreenPosition(Vector2(wp.x, wp.y + ${HEAD_OFFSET_Y + 0.45}))
|
||||||
|
popPos = _UILogic:ScreenToUIPosition(screen)
|
||||||
|
else
|
||||||
|
local slotEntity = _EntityService:GetEntityByPath("/ui/DefaultGroup/CombatHud/MonsterSlot" .. slotKey)
|
||||||
|
if slotEntity ~= nil and slotEntity.UITransformComponent ~= nil then
|
||||||
|
local sp = slotEntity.UITransformComponent.anchoredPosition
|
||||||
|
popPos = Vector2(sp.x, sp.y + 76)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if pop ~= nil and pop.UITransformComponent ~= nil then
|
||||||
|
if popPos ~= nil then
|
||||||
|
pop.UITransformComponent.anchoredPosition = popPos
|
||||||
|
else
|
||||||
|
pop.UITransformComponent.anchoredPosition = Vector2(0, 120)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
self:SetEntityEnabled(base, true)
|
||||||
|
for i = 1, 6 do
|
||||||
|
_TimerService:SetTimerOnce(function()
|
||||||
|
if self.DmgPopSeq ~= popSeq then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local p = _EntityService:GetEntityByPath(base)
|
||||||
|
if p ~= nil and p.UITransformComponent ~= nil then
|
||||||
|
local cur = p.UITransformComponent.anchoredPosition
|
||||||
|
p.UITransformComponent.anchoredPosition = Vector2(cur.x, cur.y + 7)
|
||||||
|
end
|
||||||
|
end, 0.045 * i)
|
||||||
|
end
|
||||||
|
_TimerService:SetTimerOnce(function()
|
||||||
|
if self.DmgPopSeq ~= popSeq then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
self:SetEntityEnabled(base, false)
|
||||||
|
end, 0.48)`, [
|
||||||
|
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' },
|
||||||
|
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'amount' },
|
||||||
|
]),
|
||||||
|
method('ShowPlayerDmgPop', `local base = "/ui/DefaultGroup/CombatHud/PlayerPanel/DmgPop"
|
||||||
|
if amount > 0 then
|
||||||
|
self:SetText(base, "-" .. string.format("%d", amount))
|
||||||
|
else
|
||||||
|
self:SetText(base, "막음")
|
||||||
|
end
|
||||||
|
self:SetEntityEnabled(base, true)
|
||||||
|
_TimerService:SetTimerOnce(function() self:SetEntityEnabled(base, false) end, 0.6)`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'amount' }]),
|
||||||
|
method('PlayerAttackMotion', `local lp = _UserService.LocalPlayer
|
||||||
|
if lp == nil then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if lp.StateComponent == nil then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
pcall(function() lp.StateComponent:ChangeState("ATTACK") end)
|
||||||
|
_TimerService:SetTimerOnce(function()
|
||||||
|
if lp ~= nil and isvalid(lp) and lp.StateComponent ~= nil then
|
||||||
|
pcall(function() lp.StateComponent:ChangeState("IDLE") end)
|
||||||
|
end
|
||||||
|
end, 0.5)`),
|
||||||
|
method('PlayerHitMotion', `local lp = _UserService.LocalPlayer
|
||||||
|
if lp == nil then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if lp.StateComponent ~= nil then
|
||||||
|
pcall(function() lp.StateComponent:ChangeState("HIT") end)
|
||||||
|
end
|
||||||
|
local tr = lp.TransformComponent
|
||||||
|
if tr == nil then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local p = tr.Position
|
||||||
|
tr.Position = Vector3(p.x - 0.15, p.y, p.z)
|
||||||
|
_TimerService:SetTimerOnce(function()
|
||||||
|
if lp ~= nil and isvalid(lp) and lp.TransformComponent ~= nil then
|
||||||
|
lp.TransformComponent.Position = Vector3(p.x, p.y, p.z)
|
||||||
|
end
|
||||||
|
end, 0.15)`),
|
||||||
|
method('MonsterLunge', `local m = self.Monsters[idx]
|
||||||
|
if m == nil or m.alive ~= true or m.entity == nil or not isvalid(m.entity) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if m.motionBusy == true then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
m.motionBusy = true
|
||||||
|
local e = m.entity
|
||||||
|
local tr = e.TransformComponent
|
||||||
|
if tr == nil then
|
||||||
|
m.motionBusy = false
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local p = tr.Position
|
||||||
|
tr.Position = Vector3(p.x - 0.35, p.y, p.z)
|
||||||
|
_TimerService:SetTimerOnce(function()
|
||||||
|
if isvalid(e) and e.TransformComponent ~= nil then
|
||||||
|
e.TransformComponent.Position = Vector3(p.x, p.y, p.z)
|
||||||
|
end
|
||||||
|
m.motionBusy = false
|
||||||
|
end, 0.18)`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'idx' }]),
|
||||||
|
method('MonsterHitMotion', `local m = self.Monsters[slot]
|
||||||
|
if m == nil or m.alive ~= true or m.entity == nil or not isvalid(m.entity) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local e = m.entity
|
||||||
|
if m.hitClip ~= nil and e.SpriteRendererComponent ~= nil then
|
||||||
|
e.SpriteRendererComponent.SpriteRUID = m.hitClip
|
||||||
|
_TimerService:SetTimerOnce(function()
|
||||||
|
if isvalid(e) and e.SpriteRendererComponent ~= nil and m.alive == true and m.standClip ~= nil then
|
||||||
|
e.SpriteRendererComponent.SpriteRUID = m.standClip
|
||||||
|
end
|
||||||
|
end, 0.5)
|
||||||
|
else
|
||||||
|
if m.motionBusy == true then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
m.motionBusy = true
|
||||||
|
local tr = e.TransformComponent
|
||||||
|
if tr == nil then
|
||||||
|
m.motionBusy = false
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local p = tr.Position
|
||||||
|
local seq = { 0.12, -0.12, 0 }
|
||||||
|
for i = 1, #seq do
|
||||||
|
local dx = seq[i]
|
||||||
|
_TimerService:SetTimerOnce(function()
|
||||||
|
if isvalid(e) and e.TransformComponent ~= nil then
|
||||||
|
e.TransformComponent.Position = Vector3(p.x + dx, p.y, p.z)
|
||||||
|
end
|
||||||
|
if i == #seq then
|
||||||
|
m.motionBusy = false
|
||||||
|
end
|
||||||
|
end, 0.06 * i)
|
||||||
|
end
|
||||||
|
end`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' }]),
|
||||||
|
method('SetHpBar', `local e = _EntityService:GetEntityByPath(path)
|
||||||
|
if e == nil or e.UITransformComponent == nil then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local ratio = 0
|
||||||
|
if maxHp > 0 then ratio = hp / maxHp end
|
||||||
|
if ratio < 0 then ratio = 0 end
|
||||||
|
local w = width * ratio
|
||||||
|
e.UITransformComponent.RectSize = Vector2(w, 14)`, [
|
||||||
|
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'path' },
|
||||||
|
{ 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: 'width' },
|
||||||
|
]),
|
||||||
|
method('PositionMonsterSlot', `local m = self.Monsters[slot]
|
||||||
|
if m == nil or m.entity == nil or not isvalid(m.entity) then
|
||||||
|
return
|
||||||
|
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))
|
||||||
|
if e ~= nil and e.UITransformComponent ~= nil then
|
||||||
|
e.UITransformComponent.anchoredPosition = uipos
|
||||||
|
end`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' }]),
|
||||||
|
method('SetTarget', `if self.Monsters[slot] ~= nil and self.Monsters[slot].alive == true then
|
||||||
|
self.TargetIndex = slot
|
||||||
|
self:RenderCombat()
|
||||||
|
end`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' }]),
|
||||||
|
method('RenderRun', `local floorText = "막 " .. string.format("%d", self.Floor) .. "/" .. string.format("%d", self.RunLength) .. " · " .. string.format("%d", self.Depth) .. "층"
|
||||||
|
if self.AscensionLevel > 0 then
|
||||||
|
floorText = floorText .. " · 승천" .. string.format("%d", self.AscensionLevel)
|
||||||
|
end
|
||||||
|
self:SetText("/ui/DefaultGroup/CombatHud/TopBar/Floor", floorText)
|
||||||
|
self:SetText("/ui/DefaultGroup/CombatHud/TopBar/Gold", "메소 " .. string.format("%d", self.Gold))`),
|
||||||
|
];
|
||||||
55
tools/deck/cb/reward.mjs
Normal file
55
tools/deck/cb/reward.mjs
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import { method, RUN_LENGTH, GOLD_PER_WIN, CARD_PRICE, REST_HEAL, RELIC_PRICE, ACT_COUNT, ACT_MAPS, LOBBY_MAP, LOBBY_SPAWN } from '../lib/codeblock.mjs';
|
||||||
|
import { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, CARDFRAMES, RARITIES, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, POTIONS, luaSoulShopTable, frameRuid, luaFramesTable, luaNodeIconsTable, luaRelicsTable, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, luaCardsTable, luaDeckTable } from '../lib/data.mjs';
|
||||||
|
import { UI_FILE, COMMON_FILE, UI_ROOT, GENERATED_UI_SECTIONS, UI_APPEND_ORDER, DISABLED_STOCK_CONTROLS, TRANSPARENT, DARK, GOLD, ATTACK, DEFEND, SKILL, DAMAGE_DIGIT_RUIDS, DAMAGE_POP_MAX_DIGITS, DAMAGE_POP_DIGIT_W, DAMAGE_POP_DIGIT_H, DAMAGE_POP_DIGIT_SPACING, MAX_MONSTERS, HEAD_OFFSET_Y, HP_BAR_W, WHITE, CARD_NAME_TEXT, CARD_DESC_TEXT, cardFaceLayout, CARD_W, CARD_H, CARD_SPACING, CARD_XS, ALIGN_CENTER, ALIGN_BOTTOM_CENTER, guid, transform, sprite, button, text, scrollLayoutGroup, popupLayerFor, uiOrderFor, displayOrderFor, applySortingOverride, entity, uiPath, sectionRoot, isGeneratedUiEntity, appendUiSection } from '../lib/ui-helpers.mjs';
|
||||||
|
|
||||||
|
export const rewardMethods = [
|
||||||
|
method('CardPool', `local pool = {}
|
||||||
|
for id, c in pairs(self.Cards) do
|
||||||
|
if c.token ~= true and (c.class == self.SelectedClass or (self.PlayerJob ~= "" and c.class == self.PlayerJob)) then
|
||||||
|
table.insert(pool, id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
table.sort(pool)
|
||||||
|
return pool`, [], 0, 'any'),
|
||||||
|
method('OfferReward', `self:SetEntityEnabled("/ui/DefaultGroup/CardHand", false)
|
||||||
|
self:SetEntityEnabled("/ui/DefaultGroup/DeckHud", false)
|
||||||
|
local pool = self:CardPool()
|
||||||
|
local byRarity = {}
|
||||||
|
for _, id in ipairs(pool) do
|
||||||
|
local r = self.Cards[id].rarity or "normal"
|
||||||
|
if byRarity[r] == nil then byRarity[r] = {} end
|
||||||
|
table.insert(byRarity[r], id)
|
||||||
|
end
|
||||||
|
self.RewardChoices = {}
|
||||||
|
for i = 1, 3 do
|
||||||
|
local roll = math.random(1, 100)
|
||||||
|
local want = "normal"
|
||||||
|
if roll > 95 then want = "legend" elseif roll > 70 then want = "unique" end
|
||||||
|
local bucket = byRarity[want]
|
||||||
|
if bucket == nil or #bucket == 0 then bucket = pool end
|
||||||
|
self.RewardChoices[i] = bucket[math.random(1, #bucket)]
|
||||||
|
self:ApplyRewardVisual(i, self.RewardChoices[i])
|
||||||
|
end
|
||||||
|
local hud = _EntityService:GetEntityByPath("/ui/DefaultGroup/RewardHud")
|
||||||
|
if hud ~= nil then
|
||||||
|
hud.Enable = true
|
||||||
|
end`),
|
||||||
|
method('ApplyRewardVisual', `self:ApplyCardFace("/ui/DefaultGroup/RewardHud/Reward" .. tostring(slot), cardId)`, [
|
||||||
|
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' },
|
||||||
|
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'cardId' },
|
||||||
|
]),
|
||||||
|
method('PickReward', `if self.CombatOver ~= true or self.RunActive ~= true then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if slot ~= 0 and self.RewardChoices ~= nil then
|
||||||
|
local id = self.RewardChoices[slot]
|
||||||
|
if id ~= nil then
|
||||||
|
table.insert(self.RunDeck, id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local hud = _EntityService:GetEntityByPath("/ui/DefaultGroup/RewardHud")
|
||||||
|
if hud ~= nil then
|
||||||
|
hud.Enable = false
|
||||||
|
end
|
||||||
|
self:ShowMap()`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' }]),
|
||||||
|
];
|
||||||
207
tools/deck/cb/run.mjs
Normal file
207
tools/deck/cb/run.mjs
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
import { method, RUN_LENGTH, GOLD_PER_WIN, CARD_PRICE, REST_HEAL, RELIC_PRICE, ACT_COUNT, ACT_MAPS, LOBBY_MAP, LOBBY_SPAWN } from '../lib/codeblock.mjs';
|
||||||
|
import { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, CARDFRAMES, RARITIES, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, POTIONS, luaSoulShopTable, frameRuid, luaFramesTable, luaNodeIconsTable, luaRelicsTable, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, luaCardsTable, luaDeckTable } from '../lib/data.mjs';
|
||||||
|
import { UI_FILE, COMMON_FILE, UI_ROOT, GENERATED_UI_SECTIONS, UI_APPEND_ORDER, DISABLED_STOCK_CONTROLS, TRANSPARENT, DARK, GOLD, ATTACK, DEFEND, SKILL, DAMAGE_DIGIT_RUIDS, DAMAGE_POP_MAX_DIGITS, DAMAGE_POP_DIGIT_W, DAMAGE_POP_DIGIT_H, DAMAGE_POP_DIGIT_SPACING, MAX_MONSTERS, HEAD_OFFSET_Y, HP_BAR_W, WHITE, CARD_NAME_TEXT, CARD_DESC_TEXT, cardFaceLayout, CARD_W, CARD_H, CARD_SPACING, CARD_XS, ALIGN_CENTER, ALIGN_BOTTOM_CENTER, guid, transform, sprite, button, text, scrollLayoutGroup, popupLayerFor, uiOrderFor, displayOrderFor, applySortingOverride, entity, uiPath, sectionRoot, isGeneratedUiEntity, appendUiSection } from '../lib/ui-helpers.mjs';
|
||||||
|
|
||||||
|
export const runMethods = [
|
||||||
|
method('StartRun', `if self.SelectedClass == "magician" then
|
||||||
|
self.PlayerMaxHp = ${CLASSES.magician.maxHp}
|
||||||
|
self.RunDeck = { ${CARDS.starterDecks.magician.map(luaStr).join(', ')} }
|
||||||
|
elseif self.SelectedClass == "bandit" then
|
||||||
|
self.PlayerMaxHp = ${CLASSES.bandit.maxHp}
|
||||||
|
self.RunDeck = { ${CARDS.starterDecks.bandit.map(luaStr).join(', ')} }
|
||||||
|
else
|
||||||
|
self.PlayerMaxHp = ${CLASSES.warrior.maxHp}
|
||||||
|
self.RunDeck = { ${CARDS.starterDecks.warrior.map(luaStr).join(', ')} }
|
||||||
|
end
|
||||||
|
self.PlayerMaxHp = self.PlayerMaxHp - self:AscStartHpPenalty()
|
||||||
|
self.PlayerHp = self.PlayerMaxHp
|
||||||
|
self.Gold = 0
|
||||||
|
self.Floor = 1
|
||||||
|
self.RunLength = ${ACT_COUNT}
|
||||||
|
self.RunActive = true
|
||||||
|
self.RunRelics = {}
|
||||||
|
self.RunPotions = {}
|
||||||
|
self.PotionSlots = ${POTIONS.baseSlots}
|
||||||
|
${luaPotionsTable(POTIONS.potions)}
|
||||||
|
${luaRelicsTable(RELICS.relics)}
|
||||||
|
self.RelicPool = { ${RELICS.relicPool.map(luaStr).join(', ')} }
|
||||||
|
${luaEnemiesTable(ENEMIES.enemies)}
|
||||||
|
self.CurrentNodeId = ""
|
||||||
|
self.CurrentEnemyId = ""
|
||||||
|
self.PlayerJob = ""
|
||||||
|
${luaJobsTable(JOBS)}
|
||||||
|
${luaFramesTable()}
|
||||||
|
${luaNodeIconsTable()}
|
||||||
|
self:GenerateMap()
|
||||||
|
self:BindButtons()
|
||||||
|
self:AddRelic("${RELICS.startingRelic}")
|
||||||
|
self:ApplySoulUnlocks()
|
||||||
|
self:RenderPotions()
|
||||||
|
self:TeleportToActMap()
|
||||||
|
self:ShowMap()`),
|
||||||
|
method('KickCombatCamera', `local cam = nil
|
||||||
|
local lp = _UserService.LocalPlayer
|
||||||
|
if lp ~= nil then cam = lp.CameraComponent end
|
||||||
|
if cam == nil then cam = _CameraService:GetCurrentCameraComponent() end
|
||||||
|
if cam ~= nil then cam.ConfineCameraArea = false end
|
||||||
|
_TimerService:SetTimerOnce(function()
|
||||||
|
local cc = nil
|
||||||
|
local lp2 = _UserService.LocalPlayer
|
||||||
|
if lp2 ~= nil then cc = lp2.CameraComponent end
|
||||||
|
if cc == nil then cc = _CameraService:GetCurrentCameraComponent() end
|
||||||
|
if cc ~= nil then
|
||||||
|
cc.ZoomRatio = ${CAM.zoomRatio}
|
||||||
|
cc.CameraOffset = Vector2(${CAM.cameraOffsetX}, ${CAM.cameraOffsetY})
|
||||||
|
cc.ScreenOffset = Vector2(${CAM.screenOffsetX}, ${CAM.screenOffsetY})
|
||||||
|
cc.ConfineCameraArea = true
|
||||||
|
end
|
||||||
|
end, 0.2)`),
|
||||||
|
method('StartCombat', `self:ShowState("combat")
|
||||||
|
self:KickCombatCamera()
|
||||||
|
self:SetEntityEnabled("/ui/DefaultGroup/CombatHud/Result", false)
|
||||||
|
self:SetEntityEnabled("/ui/DefaultGroup/CombatHud/PotionMenu", false)
|
||||||
|
self:SetEntityEnabled("/ui/DefaultGroup/CombatHud/TooltipBox", false)
|
||||||
|
self:SetEntityEnabled("/ui/DefaultGroup/CombatHud/DiscardPrompt", false)
|
||||||
|
self:SetText("/ui/DefaultGroup/CombatHud/PlayerPanel/Name", self:JobLabel())
|
||||||
|
self.MaxEnergy = 3
|
||||||
|
self.Turn = 0
|
||||||
|
self.PlayerBlock = 0
|
||||||
|
self.PlayerStr = 0
|
||||||
|
self.PlayerDex = 0
|
||||||
|
self.PlayerThorns = 0
|
||||||
|
self.PlayerWeak = 0
|
||||||
|
self.PlayerVuln = 0
|
||||||
|
self.PlayerPowers = {}
|
||||||
|
self.FightAttackCount = 0
|
||||||
|
self.DmgPopSeq = 0
|
||||||
|
self.FirstHpLossDone = false
|
||||||
|
self.ClayBlockNext = 0
|
||||||
|
self.DiscardSelectRemaining = 0
|
||||||
|
self.DiscardSelectTotal = 0
|
||||||
|
self.DiscardPostShiv = 0
|
||||||
|
self.DiscardShivPerPick = 0
|
||||||
|
self.CombatOver = false
|
||||||
|
self.DiscardPile = {}
|
||||||
|
self.ExhaustPile = {}
|
||||||
|
self.Hand = {}
|
||||||
|
${luaCardsTable(CARDS.cards)}
|
||||||
|
self.DrawPile = {}
|
||||||
|
for i = 1, #self.RunDeck do
|
||||||
|
self.DrawPile[i] = self.RunDeck[i]
|
||||||
|
end
|
||||||
|
self:Shuffle(self.DrawPile)
|
||||||
|
self:BuildMonsters()
|
||||||
|
self:RenderCombat()
|
||||||
|
self:StartPlayerTurn()
|
||||||
|
self:ApplyRelics("combatStart")
|
||||||
|
self:RenderCombat()`),
|
||||||
|
method('RegisterMonster', `if self.Registered == nil then
|
||||||
|
self.Registered = {}
|
||||||
|
end
|
||||||
|
local g = group
|
||||||
|
if g == nil or g == "" then g = "combat" end
|
||||||
|
local mp = mapName
|
||||||
|
if mp == nil then mp = "" end
|
||||||
|
table.insert(self.Registered, { entity = monster, enemyId = enemyId, group = g, map = mp })`, [
|
||||||
|
{ Type: 'any', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'monster' },
|
||||||
|
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'enemyId' },
|
||||||
|
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'group' },
|
||||||
|
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'mapName' },
|
||||||
|
]),
|
||||||
|
method('BuildMonsters', `self.Monsters = {}
|
||||||
|
local g = "combat"
|
||||||
|
local node = self.MapNodes[self.CurrentNodeId]
|
||||||
|
if node ~= nil and node.type ~= nil then g = node.type end
|
||||||
|
local pmap = ""
|
||||||
|
local lp = _UserService.LocalPlayer
|
||||||
|
if lp ~= nil and lp.CurrentMapName ~= nil then pmap = lp.CurrentMapName end
|
||||||
|
local reg = self.Registered or {}
|
||||||
|
for i = 1, #reg do
|
||||||
|
if reg[i].entity ~= nil and isvalid(reg[i].entity) then
|
||||||
|
reg[i].entity:SetVisible(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local byGroup = {}
|
||||||
|
for i = 1, #reg do
|
||||||
|
local r = reg[i]
|
||||||
|
if r.entity ~= nil and isvalid(r.entity) and (r.map == nil or r.map == "" or pmap == "" or r.map == pmap) then
|
||||||
|
local gg = r.group
|
||||||
|
if gg == nil or gg == "" then gg = "combat" end
|
||||||
|
if byGroup[gg] == nil then byGroup[gg] = {} end
|
||||||
|
local x = 0
|
||||||
|
if r.entity.TransformComponent ~= nil then
|
||||||
|
x = r.entity.TransformComponent.WorldPosition.x
|
||||||
|
end
|
||||||
|
table.insert(byGroup[gg], { entity = r.entity, enemyId = r.enemyId, x = x })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- 노드 타입별 랜덤 구성: 일반 1~3 / 엘리트 1+일반0~2 / 보스 1
|
||||||
|
local chosen = {}
|
||||||
|
local function takeFrom(key, k)
|
||||||
|
local src = byGroup[key] or {}
|
||||||
|
local pool = {}
|
||||||
|
for i = 1, #src do pool[i] = src[i] end
|
||||||
|
self:Shuffle(pool)
|
||||||
|
local taken = 0
|
||||||
|
for i = 1, #pool do
|
||||||
|
if taken >= k then break end
|
||||||
|
table.insert(chosen, pool[i])
|
||||||
|
taken = taken + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if g == "boss" then
|
||||||
|
takeFrom("boss", 1)
|
||||||
|
elseif g == "elite" then
|
||||||
|
takeFrom("elite", 1)
|
||||||
|
takeFrom("combat", math.random(0, 2))
|
||||||
|
else
|
||||||
|
takeFrom("combat", math.random(1, 3))
|
||||||
|
end
|
||||||
|
if #chosen == 0 then takeFrom(g, 1) end
|
||||||
|
if #chosen == 0 then takeFrom("combat", 1) end
|
||||||
|
table.sort(chosen, function(a, b) return a.x < b.x end)
|
||||||
|
local mult = 1 + (self.Floor - 1) * 0.45
|
||||||
|
if g == "elite" or g == "boss" then
|
||||||
|
mult = mult + self:AscEliteBonus()
|
||||||
|
end
|
||||||
|
local n = #chosen
|
||||||
|
if n > ${MAX_MONSTERS} then n = ${MAX_MONSTERS} end
|
||||||
|
for i = 1, n do
|
||||||
|
local item = chosen[i]
|
||||||
|
local e = self.Enemies[item.enemyId]
|
||||||
|
if e == nil then e = { name = item.enemyId, maxHp = 10, intents = { { kind = "Attack", value = 5 } } } end
|
||||||
|
local intents = {}
|
||||||
|
for k = 1, #e.intents do
|
||||||
|
local v = e.intents[k].value or 0
|
||||||
|
if e.intents[k].kind == "Attack" then
|
||||||
|
v = math.floor(v * mult * self:AscAtkMult())
|
||||||
|
elseif e.intents[k].kind ~= "Debuff" then
|
||||||
|
v = math.floor(v * mult)
|
||||||
|
end
|
||||||
|
intents[k] = { kind = e.intents[k].kind, value = v, effect = e.intents[k].effect, card = e.intents[k].card, count = e.intents[k].count }
|
||||||
|
end
|
||||||
|
local maxHp = math.floor(e.maxHp * mult * self:AscHpMult())
|
||||||
|
local hitClip = nil
|
||||||
|
local standClip = nil
|
||||||
|
if item.entity.StateAnimationComponent ~= nil then
|
||||||
|
pcall(function()
|
||||||
|
hitClip = item.entity.StateAnimationComponent.ActionSheet["hit"]
|
||||||
|
standClip = item.entity.StateAnimationComponent.ActionSheet["stand"]
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
local startIdx = 1
|
||||||
|
if #intents > 0 then startIdx = math.random(1, #intents) end
|
||||||
|
self.Monsters[i] = { entity = item.entity, enemyId = item.enemyId, name = e.name,
|
||||||
|
hp = maxHp, maxHp = maxHp, block = 0, str = 0, weak = 0, vuln = 0, poison = 0,
|
||||||
|
hitClip = hitClip, standClip = standClip, motionBusy = false,
|
||||||
|
intents = intents, intentIdx = startIdx, alive = true, slot = i }
|
||||||
|
self:ReviveMonsterEntity(item.entity)
|
||||||
|
self:PositionMonsterSlot(i)
|
||||||
|
end
|
||||||
|
self.TargetIndex = 1`),
|
||||||
|
method('ReviveMonsterEntity', `if monster == nil or not isvalid(monster) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
monster:SetEnable(true)
|
||||||
|
monster:SetVisible(true)`, [{ Type: 'any', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'monster' }]),
|
||||||
|
];
|
||||||
37
tools/deck/cb/runend.mjs
Normal file
37
tools/deck/cb/runend.mjs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import { method, RUN_LENGTH, GOLD_PER_WIN, CARD_PRICE, REST_HEAL, RELIC_PRICE, ACT_COUNT, ACT_MAPS, LOBBY_MAP, LOBBY_SPAWN } from '../lib/codeblock.mjs';
|
||||||
|
import { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, CARDFRAMES, RARITIES, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, POTIONS, luaSoulShopTable, frameRuid, luaFramesTable, luaNodeIconsTable, luaRelicsTable, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, luaCardsTable, luaDeckTable } from '../lib/data.mjs';
|
||||||
|
import { UI_FILE, COMMON_FILE, UI_ROOT, GENERATED_UI_SECTIONS, UI_APPEND_ORDER, DISABLED_STOCK_CONTROLS, TRANSPARENT, DARK, GOLD, ATTACK, DEFEND, SKILL, DAMAGE_DIGIT_RUIDS, DAMAGE_POP_MAX_DIGITS, DAMAGE_POP_DIGIT_W, DAMAGE_POP_DIGIT_H, DAMAGE_POP_DIGIT_SPACING, MAX_MONSTERS, HEAD_OFFSET_Y, HP_BAR_W, WHITE, CARD_NAME_TEXT, CARD_DESC_TEXT, cardFaceLayout, CARD_W, CARD_H, CARD_SPACING, CARD_XS, ALIGN_CENTER, ALIGN_BOTTOM_CENTER, guid, transform, sprite, button, text, scrollLayoutGroup, popupLayerFor, uiOrderFor, displayOrderFor, applySortingOverride, entity, uiPath, sectionRoot, isGeneratedUiEntity, appendUiSection } from '../lib/ui-helpers.mjs';
|
||||||
|
|
||||||
|
export const runEndMethods = [
|
||||||
|
method('TeleportToActMap', `local maps = { ${ACT_MAPS.map((m) => `"${m}"`).join(', ')} }
|
||||||
|
local target = maps[self.Floor]
|
||||||
|
if target == nil then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local lp = _UserService.LocalPlayer
|
||||||
|
if lp == nil then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if lp.CurrentMapName == target then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
_TeleportService:TeleportToMapPosition(lp, Vector3(-6, 0.03, 0), target)`),
|
||||||
|
method('ShowResult', `self:SetText("/ui/DefaultGroup/CombatHud/Result", text)
|
||||||
|
local entity = _EntityService:GetEntityByPath("/ui/DefaultGroup/CombatHud/Result")
|
||||||
|
if entity ~= nil then
|
||||||
|
entity.Enable = true
|
||||||
|
end`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'text' }]),
|
||||||
|
method('EndRun', `local msg = text
|
||||||
|
if text == "런 클리어!" and self.AscensionLevel >= self.AscensionUnlocked and self.AscensionUnlocked < 10 then
|
||||||
|
self.AscensionUnlocked = self.AscensionUnlocked + 1
|
||||||
|
local lp = _UserService.LocalPlayer
|
||||||
|
if lp ~= nil then
|
||||||
|
self:SaveAscension(self.AscensionUnlocked, lp.PlayerComponent.UserId)
|
||||||
|
end
|
||||||
|
self:RenderAscension()
|
||||||
|
msg = "런 클리어! 승천 " .. string.format("%d", self.AscensionUnlocked) .. " 해금!"
|
||||||
|
end
|
||||||
|
self:ShowResult(msg)
|
||||||
|
self.RunActive = false
|
||||||
|
_TimerService:SetTimerOnce(function() self:ShowLobby() end, 4)`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'text' }]),
|
||||||
|
];
|
||||||
173
tools/deck/cb/shop.mjs
Normal file
173
tools/deck/cb/shop.mjs
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
import { method, RUN_LENGTH, GOLD_PER_WIN, CARD_PRICE, REST_HEAL, RELIC_PRICE, ACT_COUNT, ACT_MAPS, LOBBY_MAP, LOBBY_SPAWN } from '../lib/codeblock.mjs';
|
||||||
|
import { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, CARDFRAMES, RARITIES, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, POTIONS, luaSoulShopTable, frameRuid, luaFramesTable, luaNodeIconsTable, luaRelicsTable, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, luaCardsTable, luaDeckTable } from '../lib/data.mjs';
|
||||||
|
import { UI_FILE, COMMON_FILE, UI_ROOT, GENERATED_UI_SECTIONS, UI_APPEND_ORDER, DISABLED_STOCK_CONTROLS, TRANSPARENT, DARK, GOLD, ATTACK, DEFEND, SKILL, DAMAGE_DIGIT_RUIDS, DAMAGE_POP_MAX_DIGITS, DAMAGE_POP_DIGIT_W, DAMAGE_POP_DIGIT_H, DAMAGE_POP_DIGIT_SPACING, MAX_MONSTERS, HEAD_OFFSET_Y, HP_BAR_W, WHITE, CARD_NAME_TEXT, CARD_DESC_TEXT, cardFaceLayout, CARD_W, CARD_H, CARD_SPACING, CARD_XS, ALIGN_CENTER, ALIGN_BOTTOM_CENTER, guid, transform, sprite, button, text, scrollLayoutGroup, popupLayerFor, uiOrderFor, displayOrderFor, applySortingOverride, entity, uiPath, sectionRoot, isGeneratedUiEntity, appendUiSection } from '../lib/ui-helpers.mjs';
|
||||||
|
|
||||||
|
export const shopMethods = [
|
||||||
|
method('ShowShop', `local pool = self:CardPool()
|
||||||
|
self.ShopChoices = {}
|
||||||
|
self.ShopBought = { false, false, false }
|
||||||
|
for i = 1, 3 do
|
||||||
|
self.ShopChoices[i] = pool[math.random(1, #pool)]
|
||||||
|
end
|
||||||
|
self.ShopRelic = self.RelicPool[math.random(1, #self.RelicPool)]
|
||||||
|
self.ShopRelicBought = false
|
||||||
|
local pkeys = {}
|
||||||
|
for pid, _ in pairs(self.Potions) do
|
||||||
|
table.insert(pkeys, pid)
|
||||||
|
end
|
||||||
|
table.sort(pkeys)
|
||||||
|
self.ShopPotion = pkeys[math.random(1, #pkeys)]
|
||||||
|
self.ShopPotionBought = false
|
||||||
|
self:RenderShop()
|
||||||
|
self:ShowState("shop")`),
|
||||||
|
method('RenderShop', `self:SetText("/ui/DefaultGroup/ShopHud/Gold", "메소 " .. string.format("%d", self.Gold))
|
||||||
|
for i = 1, 3 do
|
||||||
|
local cid = self.ShopChoices[i]
|
||||||
|
local c = self.Cards[cid]
|
||||||
|
local base = "/ui/DefaultGroup/ShopHud/Card" .. tostring(i)
|
||||||
|
if c ~= nil then
|
||||||
|
self:ApplyCardFace(base, cid)
|
||||||
|
self:SetText(base .. "/Price", string.format("%d", ${CARD_PRICE}) .. " 메소")
|
||||||
|
local e = _EntityService:GetEntityByPath(base)
|
||||||
|
if e ~= nil and e.SpriteGUIRendererComponent ~= nil then
|
||||||
|
if self.ShopBought[i] == true then
|
||||||
|
e.SpriteGUIRendererComponent.Color = Color(0.2, 0.22, 0.26, 0.6)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local rr = self.Relics[self.ShopRelic]
|
||||||
|
if rr ~= nil then
|
||||||
|
self:SetText("/ui/DefaultGroup/ShopHud/Relic/Label", rr.name .. " — " .. rr.desc)
|
||||||
|
self:SetText("/ui/DefaultGroup/ShopHud/Relic/Price", string.format("%d", ${RELIC_PRICE}) .. " 메소")
|
||||||
|
local re = _EntityService:GetEntityByPath("/ui/DefaultGroup/ShopHud/Relic")
|
||||||
|
if re ~= nil and re.SpriteGUIRendererComponent ~= nil then
|
||||||
|
if self.ShopRelicBought == true then
|
||||||
|
re.SpriteGUIRendererComponent.Color = Color(0.2, 0.22, 0.26, 0.6)
|
||||||
|
else
|
||||||
|
re.SpriteGUIRendererComponent.Color = Color(0.7, 0.55, 0.85, 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local pp = self.Potions[self.ShopPotion]
|
||||||
|
if pp ~= nil then
|
||||||
|
self:SetText("/ui/DefaultGroup/ShopHud/Potion/Label", pp.name .. " — " .. pp.desc)
|
||||||
|
self:SetText("/ui/DefaultGroup/ShopHud/Potion/Price", string.format("%d", ${POTIONS.shopPrice}) .. " 메소")
|
||||||
|
local pe = _EntityService:GetEntityByPath("/ui/DefaultGroup/ShopHud/Potion")
|
||||||
|
if pe ~= nil and pe.SpriteGUIRendererComponent ~= nil then
|
||||||
|
if self.ShopPotionBought == true then
|
||||||
|
pe.SpriteGUIRendererComponent.Color = Color(0.2, 0.22, 0.26, 0.6)
|
||||||
|
else
|
||||||
|
pe.SpriteGUIRendererComponent.Color = Color(0.45, 0.7, 0.55, 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end`),
|
||||||
|
method('BuyRelic', `if self.ShopRelicBought == true then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if self.Gold < ${RELIC_PRICE} then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
self.Gold = self.Gold - ${RELIC_PRICE}
|
||||||
|
self:AddRelic(self.ShopRelic)
|
||||||
|
self.ShopRelicBought = true
|
||||||
|
self:RenderShop()
|
||||||
|
self:RenderRun()`),
|
||||||
|
method('BuyPotion', `if self.ShopPotionBought == true then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if self.Gold < ${POTIONS.shopPrice} then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if self.RunPotions ~= nil and #self.RunPotions >= self.PotionSlots then
|
||||||
|
self:Toast("물약 슬롯이 가득 찼습니다")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if self:AddPotion(self.ShopPotion) == true then
|
||||||
|
self.Gold = self.Gold - ${POTIONS.shopPrice}
|
||||||
|
self.ShopPotionBought = true
|
||||||
|
end
|
||||||
|
self:RenderShop()
|
||||||
|
self:RenderRun()`),
|
||||||
|
method('BuyCard', `if self.ShopBought == nil or self.ShopBought[slot] == true then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if self.Gold < ${CARD_PRICE} then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
self.Gold = self.Gold - ${CARD_PRICE}
|
||||||
|
table.insert(self.RunDeck, self.ShopChoices[slot])
|
||||||
|
self.ShopBought[slot] = true
|
||||||
|
self:RenderShop()
|
||||||
|
self:RenderRun()`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' }]),
|
||||||
|
method('ShowRest', `local old = self.PlayerHp
|
||||||
|
self.PlayerHp = self.PlayerHp + ${REST_HEAL}
|
||||||
|
if self.PlayerHp > self.PlayerMaxHp then
|
||||||
|
self.PlayerHp = self.PlayerMaxHp
|
||||||
|
end
|
||||||
|
local healed = self.PlayerHp - old
|
||||||
|
self:SetText("/ui/DefaultGroup/RestHud/Info", "HP " .. string.format("%d", old) .. " → " .. string.format("%d", self.PlayerHp) .. " (+" .. string.format("%d", healed) .. ")")
|
||||||
|
self:RenderCombat()
|
||||||
|
self:ShowState("rest")`),
|
||||||
|
method('LeaveNode', `local s = _EntityService:GetEntityByPath("/ui/DefaultGroup/ShopHud")
|
||||||
|
if s ~= nil then
|
||||||
|
s.Enable = false
|
||||||
|
end
|
||||||
|
local r = _EntityService:GetEntityByPath("/ui/DefaultGroup/RestHud")
|
||||||
|
if r ~= nil then
|
||||||
|
r.Enable = false
|
||||||
|
end
|
||||||
|
local t = _EntityService:GetEntityByPath("/ui/DefaultGroup/TreasureHud")
|
||||||
|
if t ~= nil then
|
||||||
|
t.Enable = false
|
||||||
|
end
|
||||||
|
self:ShowMap()`),
|
||||||
|
method('ShowTreasure', `self.ChestOpened = false
|
||||||
|
local chest = _EntityService:GetEntityByPath("/ui/DefaultGroup/TreasureHud/Chest")
|
||||||
|
if chest ~= nil then
|
||||||
|
if chest.SpriteGUIRendererComponent ~= nil then
|
||||||
|
chest.SpriteGUIRendererComponent.ImageRUID = "${CHEST_CLOSED_RUID}"
|
||||||
|
end
|
||||||
|
if chest.UITransformComponent ~= nil then
|
||||||
|
chest.UITransformComponent.anchoredPosition = Vector2(0, 40)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
self:SetEntityEnabled("/ui/DefaultGroup/TreasureHud/Reward", false)
|
||||||
|
self:SetEntityEnabled("/ui/DefaultGroup/TreasureHud/Hint", true)
|
||||||
|
self:ShowState("treasure")`),
|
||||||
|
method('OpenChest', `if self.ChestOpened == true then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
self.ChestOpened = true
|
||||||
|
self:SetEntityEnabled("/ui/DefaultGroup/TreasureHud/Hint", false)
|
||||||
|
local chest = _EntityService:GetEntityByPath("/ui/DefaultGroup/TreasureHud/Chest")
|
||||||
|
local steps = { 10, -10, 8, -8, 5, 0 }
|
||||||
|
for i = 1, #steps do
|
||||||
|
local dx = steps[i]
|
||||||
|
_TimerService:SetTimerOnce(function()
|
||||||
|
if chest ~= nil and isvalid(chest) and chest.UITransformComponent ~= nil then
|
||||||
|
chest.UITransformComponent.anchoredPosition = Vector2(dx, 40)
|
||||||
|
end
|
||||||
|
end, 0.08 * i)
|
||||||
|
end
|
||||||
|
_TimerService:SetTimerOnce(function()
|
||||||
|
if chest ~= nil and isvalid(chest) and chest.SpriteGUIRendererComponent ~= nil then
|
||||||
|
chest.SpriteGUIRendererComponent.ImageRUID = "${CHEST_OPEN_RUID}"
|
||||||
|
end
|
||||||
|
local g = 40 + math.random(0, 20)
|
||||||
|
local nid = self:PickNewRelic()
|
||||||
|
local msg = ""
|
||||||
|
if nid ~= "" then
|
||||||
|
self:AddRelic(nid)
|
||||||
|
local nr = self.Relics[nid]
|
||||||
|
msg = "유물 획득: " .. nr.name .. " · 메소 +" .. tostring(g)
|
||||||
|
else
|
||||||
|
g = g + 30
|
||||||
|
msg = "메소 +" .. tostring(g)
|
||||||
|
end
|
||||||
|
self.Gold = self.Gold + g
|
||||||
|
self:RenderRun()
|
||||||
|
self:SetText("/ui/DefaultGroup/TreasureHud/Reward", msg)
|
||||||
|
self:SetEntityEnabled("/ui/DefaultGroup/TreasureHud/Reward", true)
|
||||||
|
end, 0.55)`),
|
||||||
|
];
|
||||||
114
tools/deck/cb/soul.mjs
Normal file
114
tools/deck/cb/soul.mjs
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
import { method, RUN_LENGTH, GOLD_PER_WIN, CARD_PRICE, REST_HEAL, RELIC_PRICE, ACT_COUNT, ACT_MAPS, LOBBY_MAP, LOBBY_SPAWN } from '../lib/codeblock.mjs';
|
||||||
|
import { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, CARDFRAMES, RARITIES, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, POTIONS, luaSoulShopTable, frameRuid, luaFramesTable, luaNodeIconsTable, luaRelicsTable, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, luaCardsTable, luaDeckTable } from '../lib/data.mjs';
|
||||||
|
import { UI_FILE, COMMON_FILE, UI_ROOT, GENERATED_UI_SECTIONS, UI_APPEND_ORDER, DISABLED_STOCK_CONTROLS, TRANSPARENT, DARK, GOLD, ATTACK, DEFEND, SKILL, DAMAGE_DIGIT_RUIDS, DAMAGE_POP_MAX_DIGITS, DAMAGE_POP_DIGIT_W, DAMAGE_POP_DIGIT_H, DAMAGE_POP_DIGIT_SPACING, MAX_MONSTERS, HEAD_OFFSET_Y, HP_BAR_W, WHITE, CARD_NAME_TEXT, CARD_DESC_TEXT, cardFaceLayout, CARD_W, CARD_H, CARD_SPACING, CARD_XS, ALIGN_CENTER, ALIGN_BOTTOM_CENTER, guid, transform, sprite, button, text, scrollLayoutGroup, popupLayerFor, uiOrderFor, displayOrderFor, applySortingOverride, entity, uiPath, sectionRoot, isGeneratedUiEntity, appendUiSection } from '../lib/ui-helpers.mjs';
|
||||||
|
|
||||||
|
export const soulMethods = [
|
||||||
|
method('ShowSoulShop', `self:RenderSoulLabel()
|
||||||
|
self:RenderSoulShop()
|
||||||
|
self:BindSoulShopButtons()
|
||||||
|
self:SetEntityEnabled("/ui/DefaultGroup/SoulShopHud", true)`),
|
||||||
|
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`),
|
||||||
|
];
|
||||||
193
tools/deck/cb/state.mjs
Normal file
193
tools/deck/cb/state.mjs
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
import { method, RUN_LENGTH, GOLD_PER_WIN, CARD_PRICE, REST_HEAL, RELIC_PRICE, ACT_COUNT, ACT_MAPS, LOBBY_MAP, LOBBY_SPAWN } from '../lib/codeblock.mjs';
|
||||||
|
import { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, CARDFRAMES, RARITIES, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, POTIONS, luaSoulShopTable, frameRuid, luaFramesTable, luaNodeIconsTable, luaRelicsTable, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, luaCardsTable, luaDeckTable } from '../lib/data.mjs';
|
||||||
|
import { UI_FILE, COMMON_FILE, UI_ROOT, GENERATED_UI_SECTIONS, UI_APPEND_ORDER, DISABLED_STOCK_CONTROLS, TRANSPARENT, DARK, GOLD, ATTACK, DEFEND, SKILL, DAMAGE_DIGIT_RUIDS, DAMAGE_POP_MAX_DIGITS, DAMAGE_POP_DIGIT_W, DAMAGE_POP_DIGIT_H, DAMAGE_POP_DIGIT_SPACING, MAX_MONSTERS, HEAD_OFFSET_Y, HP_BAR_W, WHITE, CARD_NAME_TEXT, CARD_DESC_TEXT, cardFaceLayout, CARD_W, CARD_H, CARD_SPACING, CARD_XS, ALIGN_CENTER, ALIGN_BOTTOM_CENTER, guid, transform, sprite, button, text, scrollLayoutGroup, popupLayerFor, uiOrderFor, displayOrderFor, applySortingOverride, entity, uiPath, sectionRoot, isGeneratedUiEntity, appendUiSection } from '../lib/ui-helpers.mjs';
|
||||||
|
|
||||||
|
export const stateMethods = [
|
||||||
|
method('HideGameHud', `self:SetEntityEnabled("/ui/DefaultGroup/Button_Attack", false)
|
||||||
|
self:SetEntityEnabled("/ui/DefaultGroup/Button_Jump", false)
|
||||||
|
self:SetEntityEnabled("/ui/DefaultGroup/UIJoystick", false)
|
||||||
|
self:SetEntityEnabled("/ui/DefaultGroup/DeckHud", false)
|
||||||
|
self:SetEntityEnabled("/ui/DefaultGroup/CardHand", false)
|
||||||
|
self:SetEntityEnabled("/ui/DefaultGroup/CombatHud", false)
|
||||||
|
self:SetEntityEnabled("/ui/DefaultGroup/RewardHud", false)
|
||||||
|
self:SetEntityEnabled("/ui/DefaultGroup/MapHud", false)
|
||||||
|
self:SetEntityEnabled("/ui/DefaultGroup/ShopHud", false)
|
||||||
|
self:SetEntityEnabled("/ui/DefaultGroup/RestHud", false)
|
||||||
|
self:SetEntityEnabled("/ui/DefaultGroup/TreasureHud", false)
|
||||||
|
self:SetEntityEnabled("/ui/DefaultGroup/JobChoiceHud", false)
|
||||||
|
self:SetEntityEnabled("/ui/DefaultGroup/JobSelectHud", false)
|
||||||
|
self:SetEntityEnabled("/ui/DefaultGroup/DeckInspectHud", false)
|
||||||
|
self:SetEntityEnabled("/ui/DefaultGroup/DeckAllHud", false)
|
||||||
|
self:SetEntityEnabled("/ui/DefaultGroup/LobbyHud", false)
|
||||||
|
self:SetEntityEnabled("/ui/DefaultGroup/BoardHud", false)
|
||||||
|
self:SetEntityEnabled("/ui/DefaultGroup/SoulShopHud", false)`),
|
||||||
|
method('ShowState', `self:HideGameHud()
|
||||||
|
self:SetEntityEnabled("/ui/DefaultGroup/MainMenu", state == "menu")
|
||||||
|
self:SetEntityEnabled("/ui/DefaultGroup/CharacterSelectHud", state == "charselect")
|
||||||
|
self:SetEntityEnabled("/ui/DefaultGroup/LobbyHud", state == "lobby")
|
||||||
|
if state == "map" then
|
||||||
|
self:SetEntityEnabled("/ui/DefaultGroup/MapHud", true)
|
||||||
|
elseif state == "combat" then
|
||||||
|
self:SetEntityEnabled("/ui/DefaultGroup/CombatHud", true)
|
||||||
|
self:SetEntityEnabled("/ui/DefaultGroup/DeckHud", true)
|
||||||
|
self:SetEntityEnabled("/ui/DefaultGroup/CardHand", true)
|
||||||
|
elseif state == "shop" then
|
||||||
|
self:SetEntityEnabled("/ui/DefaultGroup/ShopHud", true)
|
||||||
|
elseif state == "rest" then
|
||||||
|
self:SetEntityEnabled("/ui/DefaultGroup/RestHud", true)
|
||||||
|
elseif state == "treasure" then
|
||||||
|
self:SetEntityEnabled("/ui/DefaultGroup/TreasureHud", true)
|
||||||
|
end`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'state' }]),
|
||||||
|
method('ShowMainMenu', `self.SelectedClass = ""
|
||||||
|
self:RenderAscension()
|
||||||
|
self:ShowState("menu")
|
||||||
|
self:SetText("/ui/DefaultGroup/MainMenu/Title", "메이플 덱 어드벤처")
|
||||||
|
self:SetText("/ui/DefaultGroup/MainMenu/Subtitle", "캐릭터를 고르고 덱을 만들어 모험을 시작하세요")
|
||||||
|
self:SetText("/ui/DefaultGroup/MainMenu/NewGameButton", "새 게임")
|
||||||
|
self:BindMenuButtons()`),
|
||||||
|
method('BindMenuButtons', `local buttonEntity = _EntityService:GetEntityByPath("/ui/DefaultGroup/MainMenu/NewGameButton")
|
||||||
|
if buttonEntity ~= nil and buttonEntity.ButtonComponent ~= nil then
|
||||||
|
if self.NewGameHandler ~= nil then
|
||||||
|
buttonEntity:DisconnectEvent(ButtonClickEvent, self.NewGameHandler)
|
||||||
|
self.NewGameHandler = nil
|
||||||
|
end
|
||||||
|
self.NewGameHandler = buttonEntity:ConnectEvent(ButtonClickEvent, function() self:ShowCharacterSelect() end)
|
||||||
|
end
|
||||||
|
local warrior = _EntityService:GetEntityByPath("/ui/DefaultGroup/CharacterSelectHud/WarriorButton")
|
||||||
|
if warrior ~= nil and warrior.ButtonComponent ~= nil then
|
||||||
|
if self.WarriorSelectHandler ~= nil then
|
||||||
|
warrior:DisconnectEvent(ButtonClickEvent, self.WarriorSelectHandler)
|
||||||
|
self.WarriorSelectHandler = nil
|
||||||
|
end
|
||||||
|
self.WarriorSelectHandler = warrior:ConnectEvent(ButtonClickEvent, function() self:SelectClass("warrior") end)
|
||||||
|
end
|
||||||
|
local thief = _EntityService:GetEntityByPath("/ui/DefaultGroup/CharacterSelectHud/ThiefButton")
|
||||||
|
if thief ~= nil and thief.ButtonComponent ~= nil then
|
||||||
|
if self.ThiefSelectHandler ~= nil then
|
||||||
|
thief:DisconnectEvent(ButtonClickEvent, self.ThiefSelectHandler)
|
||||||
|
self.ThiefSelectHandler = nil
|
||||||
|
end
|
||||||
|
self.ThiefSelectHandler = thief:ConnectEvent(ButtonClickEvent, function() self:SelectClass("bandit") end)
|
||||||
|
end
|
||||||
|
local mage = _EntityService:GetEntityByPath("/ui/DefaultGroup/CharacterSelectHud/MageButton")
|
||||||
|
if mage ~= nil and mage.ButtonComponent ~= nil then
|
||||||
|
if self.MageSelectHandler ~= nil then
|
||||||
|
mage:DisconnectEvent(ButtonClickEvent, self.MageSelectHandler)
|
||||||
|
self.MageSelectHandler = nil
|
||||||
|
end
|
||||||
|
self.MageSelectHandler = mage:ConnectEvent(ButtonClickEvent, function() self:SelectClass("magician") end)
|
||||||
|
end
|
||||||
|
local allDeckClose = _EntityService:GetEntityByPath("/ui/DefaultGroup/DeckAllHud/Close")
|
||||||
|
if allDeckClose ~= nil and allDeckClose.ButtonComponent ~= nil then
|
||||||
|
if self.AllDeckCloseHandler ~= nil then
|
||||||
|
allDeckClose:DisconnectEvent(ButtonClickEvent, self.AllDeckCloseHandler)
|
||||||
|
self.AllDeckCloseHandler = nil
|
||||||
|
end
|
||||||
|
self.AllDeckCloseHandler = allDeckClose:ConnectEvent(ButtonClickEvent, function() self:CloseAllDeck() end)
|
||||||
|
end
|
||||||
|
self:BindClassDeckTabs()
|
||||||
|
local start = _EntityService:GetEntityByPath("/ui/DefaultGroup/CharacterSelectHud/StartButton")
|
||||||
|
if start ~= nil and start.ButtonComponent ~= nil then
|
||||||
|
if self.StartGameHandler ~= nil then
|
||||||
|
start:DisconnectEvent(ButtonClickEvent, self.StartGameHandler)
|
||||||
|
self.StartGameHandler = nil
|
||||||
|
end
|
||||||
|
self.StartGameHandler = start:ConnectEvent(ButtonClickEvent, function() self:StartNewGame() end)
|
||||||
|
end
|
||||||
|
local charBack = _EntityService:GetEntityByPath("/ui/DefaultGroup/CharacterSelectHud/BackButton")
|
||||||
|
if charBack ~= nil and charBack.ButtonComponent ~= nil then
|
||||||
|
if self.CharBackHandler ~= nil then
|
||||||
|
charBack:DisconnectEvent(ButtonClickEvent, self.CharBackHandler)
|
||||||
|
self.CharBackHandler = nil
|
||||||
|
end
|
||||||
|
self.CharBackHandler = charBack:ConnectEvent(ButtonClickEvent, function() self:ShowLobby() end)
|
||||||
|
end
|
||||||
|
local ascMinus = _EntityService:GetEntityByPath("/ui/DefaultGroup/MainMenu/AscMinus")
|
||||||
|
if ascMinus ~= nil and ascMinus.ButtonComponent ~= nil then
|
||||||
|
if self.AscMinusHandler ~= nil then
|
||||||
|
ascMinus:DisconnectEvent(ButtonClickEvent, self.AscMinusHandler)
|
||||||
|
self.AscMinusHandler = nil
|
||||||
|
end
|
||||||
|
self.AscMinusHandler = ascMinus:ConnectEvent(ButtonClickEvent, function() self:AdjustAscension(-1) end)
|
||||||
|
end
|
||||||
|
local ascPlus = _EntityService:GetEntityByPath("/ui/DefaultGroup/MainMenu/AscPlus")
|
||||||
|
if ascPlus ~= nil and ascPlus.ButtonComponent ~= nil then
|
||||||
|
if self.AscPlusHandler ~= nil then
|
||||||
|
ascPlus:DisconnectEvent(ButtonClickEvent, self.AscPlusHandler)
|
||||||
|
self.AscPlusHandler = nil
|
||||||
|
end
|
||||||
|
self.AscPlusHandler = ascPlus:ConnectEvent(ButtonClickEvent, function() self:AdjustAscension(1) end)
|
||||||
|
end`),
|
||||||
|
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()`),
|
||||||
|
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)`),
|
||||||
|
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' }]),
|
||||||
|
method('RenderSoulLabel', `local s = self.SoulPoints or 0
|
||||||
|
self:SetText("/ui/DefaultGroup/LobbyHud/SoulLabel", "영혼 " .. string.format("%d", s))
|
||||||
|
self:SetText("/ui/DefaultGroup/SoulShopHud/Souls", "영혼 " .. string.format("%d", s))`),
|
||||||
|
method('BindLobbyButtons', `if self.LobbyBound == true then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
self.LobbyBound = true
|
||||||
|
local function bindClick(path, fn)
|
||||||
|
local e = _EntityService:GetEntityByPath(path)
|
||||||
|
if e ~= nil and e.ButtonComponent ~= nil then
|
||||||
|
e:ConnectEvent(ButtonClickEvent, fn)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
bindClick("/ui/DefaultGroup/LobbyHud/AscMinus", function() self:AdjustAscension(-1) end)
|
||||||
|
bindClick("/ui/DefaultGroup/LobbyHud/AscPlus", function() self:AdjustAscension(1) end)
|
||||||
|
bindClick("/ui/DefaultGroup/BoardHud/Close", function() self:CloseBoard() end)
|
||||||
|
bindClick("/ui/DefaultGroup/SoulShopHud/Close", function() self:CloseSoulShop() end)`),
|
||||||
|
method('ShowCodex', `self.CodexMode = true
|
||||||
|
self.ClassDeckMode = true
|
||||||
|
local close = _EntityService:GetEntityByPath("/ui/DefaultGroup/DeckAllHud/Close")
|
||||||
|
if close ~= nil and close.ButtonComponent ~= nil then
|
||||||
|
if self.AllDeckCloseHandler ~= nil then
|
||||||
|
close:DisconnectEvent(ButtonClickEvent, self.AllDeckCloseHandler)
|
||||||
|
end
|
||||||
|
self.AllDeckCloseHandler = close:ConnectEvent(ButtonClickEvent, function() self:CloseAllDeck() end)
|
||||||
|
end
|
||||||
|
self:BindClassDeckTabs()
|
||||||
|
self:SetEntityEnabled("/ui/DefaultGroup/LobbyHud", false)
|
||||||
|
self:SetClassDeckTab("warrior")
|
||||||
|
local hud = _EntityService:GetEntityByPath("/ui/DefaultGroup/DeckAllHud")
|
||||||
|
if hud ~= nil then
|
||||||
|
hud.Enable = true
|
||||||
|
end
|
||||||
|
self:RenderAllDeck()`),
|
||||||
|
method('ShowBoard', `self:SetEntityEnabled("/ui/DefaultGroup/BoardHud", true)`),
|
||||||
|
method('CloseBoard', `self:SetEntityEnabled("/ui/DefaultGroup/BoardHud", false)`),
|
||||||
|
];
|
||||||
115
tools/deck/cb/tooltip.mjs
Normal file
115
tools/deck/cb/tooltip.mjs
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
import { method, RUN_LENGTH, GOLD_PER_WIN, CARD_PRICE, REST_HEAL, RELIC_PRICE, ACT_COUNT, ACT_MAPS, LOBBY_MAP, LOBBY_SPAWN } from '../lib/codeblock.mjs';
|
||||||
|
import { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, CARDFRAMES, RARITIES, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, POTIONS, luaSoulShopTable, frameRuid, luaFramesTable, luaNodeIconsTable, luaRelicsTable, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, luaCardsTable, luaDeckTable } from '../lib/data.mjs';
|
||||||
|
import { UI_FILE, COMMON_FILE, UI_ROOT, GENERATED_UI_SECTIONS, UI_APPEND_ORDER, DISABLED_STOCK_CONTROLS, TRANSPARENT, DARK, GOLD, ATTACK, DEFEND, SKILL, DAMAGE_DIGIT_RUIDS, DAMAGE_POP_MAX_DIGITS, DAMAGE_POP_DIGIT_W, DAMAGE_POP_DIGIT_H, DAMAGE_POP_DIGIT_SPACING, MAX_MONSTERS, HEAD_OFFSET_Y, HP_BAR_W, WHITE, CARD_NAME_TEXT, CARD_DESC_TEXT, cardFaceLayout, CARD_W, CARD_H, CARD_SPACING, CARD_XS, ALIGN_CENTER, ALIGN_BOTTOM_CENTER, guid, transform, sprite, button, text, scrollLayoutGroup, popupLayerFor, uiOrderFor, displayOrderFor, applySortingOverride, entity, uiPath, sectionRoot, isGeneratedUiEntity, appendUiSection } from '../lib/ui-helpers.mjs';
|
||||||
|
|
||||||
|
export const tooltipMethods = [
|
||||||
|
method('BuildCardKeywordTooltip', `if c == nil then
|
||||||
|
return ""
|
||||||
|
end
|
||||||
|
local lines = {}
|
||||||
|
local function add(name, desc)
|
||||||
|
for i = 1, #lines do
|
||||||
|
if string.find(lines[i], name .. ":", 1, true) == 1 then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
table.insert(lines, name .. ": " .. desc)
|
||||||
|
end
|
||||||
|
local cardDesc = c.desc or ""
|
||||||
|
if c.sly == true or string.find(cardDesc, "교활", 1, true) ~= nil then
|
||||||
|
add("교활", "버려지면 비용 없이 사용됩니다.")
|
||||||
|
end
|
||||||
|
if c.retain == true or string.find(cardDesc, "보존", 1, true) ~= nil then
|
||||||
|
add("보존", "턴 종료 시 버려지지 않고 손에 남습니다.")
|
||||||
|
end
|
||||||
|
if c.dex ~= nil and c.dex > 0 or string.find(cardDesc, "민첩", 1, true) ~= nil then
|
||||||
|
add("민첩", "카드로 얻는 방어도가 증가합니다.")
|
||||||
|
end
|
||||||
|
if c.thorns ~= nil and c.thorns > 0 or string.find(cardDesc, "가시", 1, true) ~= nil then
|
||||||
|
add("가시", "피해를 받으면 공격자에게 반사 피해를 줍니다.")
|
||||||
|
end
|
||||||
|
if c.exhaust == true or string.find(cardDesc, "소멸.", 1, true) ~= nil then
|
||||||
|
add("소멸", "사용 후 소멸 덱으로 이동해 이번 전투 동안 다시 나오지 않습니다.")
|
||||||
|
end
|
||||||
|
if string.find(cardDesc, "선천성", 1, true) ~= nil then
|
||||||
|
add("선천성", "전투 시작 시 손패에 들어옵니다.")
|
||||||
|
end
|
||||||
|
if c.vuln ~= nil and c.vuln > 0 then
|
||||||
|
add("취약", "받는 공격 피해가 50% 증가합니다.")
|
||||||
|
end
|
||||||
|
if c.weak ~= nil and c.weak > 0 then
|
||||||
|
add("약화", "주는 공격 피해가 25% 감소합니다.")
|
||||||
|
end
|
||||||
|
if c.poison ~= nil and c.poison > 0 then
|
||||||
|
add("중독", "턴 시작 시 체력을 잃고 수치가 1 감소합니다.")
|
||||||
|
end
|
||||||
|
if c.pierce == true then
|
||||||
|
add("관통", "방어도를 무시하고 피해를 줍니다.")
|
||||||
|
end
|
||||||
|
if c.aoe == true then
|
||||||
|
add("전체", "모든 적에게 적용됩니다.")
|
||||||
|
end
|
||||||
|
if c.kind == "Power" then
|
||||||
|
add("파워", "사용하면 전투 동안 지속 효과로 남습니다.")
|
||||||
|
end
|
||||||
|
if c.unplayable == true then
|
||||||
|
add("저주", "사용할 수 없고 손패를 방해합니다.")
|
||||||
|
end
|
||||||
|
local out = ""
|
||||||
|
for i = 1, #lines do
|
||||||
|
if i > 1 then out = out .. "\\n" end
|
||||||
|
out = out .. lines[i]
|
||||||
|
end
|
||||||
|
return out`, [{ Type: 'any', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'c' }], 0, 'string'),
|
||||||
|
method('HoverCard', `if self.DragSlot ~= nil and self.DragSlot > 0 then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local cardId = self.Hand[slot]
|
||||||
|
if cardId == nil then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local e = _EntityService:GetEntityByPath("/ui/DefaultGroup/CardHand/Card" .. tostring(slot))
|
||||||
|
local tx = 0
|
||||||
|
if e ~= nil and e.UITransformComponent ~= nil then
|
||||||
|
tx = e.UITransformComponent.anchoredPosition.x
|
||||||
|
e.UITransformComponent.UIScale = Vector3(1.3, 1.3, 1)
|
||||||
|
end
|
||||||
|
local c = self.Cards[cardId]
|
||||||
|
if c ~= nil then
|
||||||
|
local tip = self:BuildCardKeywordTooltip(c)
|
||||||
|
if tip ~= "" then
|
||||||
|
local tipX = tx + 270
|
||||||
|
if tx > 180 then tipX = tx - 270 end
|
||||||
|
if tipX > 760 then tipX = tx - 270 end
|
||||||
|
if tipX < -760 then tipX = tx + 270 end
|
||||||
|
self:ShowTooltipAt("키워드", tip, tipX, 90)
|
||||||
|
else
|
||||||
|
self:HideTooltip()
|
||||||
|
end
|
||||||
|
end`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' }]),
|
||||||
|
method('UnhoverCard', `local e = _EntityService:GetEntityByPath("/ui/DefaultGroup/CardHand/Card" .. tostring(slot))
|
||||||
|
if e ~= nil and e.UITransformComponent ~= nil then
|
||||||
|
e.UITransformComponent.UIScale = Vector3(1, 1, 1)
|
||||||
|
end
|
||||||
|
self:HideTooltip()`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' }]),
|
||||||
|
method('ShowTooltip', `self:ShowTooltipAt(name, desc, x, 400)`, [
|
||||||
|
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'name' },
|
||||||
|
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'desc' },
|
||||||
|
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'x' },
|
||||||
|
]),
|
||||||
|
method('ShowTooltipAt', `self:SetText("/ui/DefaultGroup/CombatHud/TooltipBox/Name", name)
|
||||||
|
self:SetText("/ui/DefaultGroup/CombatHud/TooltipBox/Desc", desc)
|
||||||
|
local e = _EntityService:GetEntityByPath("/ui/DefaultGroup/CombatHud/TooltipBox")
|
||||||
|
if e ~= nil then
|
||||||
|
if e.UITransformComponent ~= nil then
|
||||||
|
e.UITransformComponent.anchoredPosition = Vector2(x, y)
|
||||||
|
end
|
||||||
|
e.Enable = true
|
||||||
|
end`, [
|
||||||
|
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'name' },
|
||||||
|
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'desc' },
|
||||||
|
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'x' },
|
||||||
|
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'y' },
|
||||||
|
]),
|
||||||
|
method('HideTooltip', `self:SetEntityEnabled("/ui/DefaultGroup/CombatHud/TooltipBox", false)`),
|
||||||
|
];
|
||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user