DSL 콘텐츠 JSON 레퍼런스

게임 콘텐츠(DSL)의 JSON 키·밸류 설명서. 어드민 폼에 없는 필드를 Advanced JSON으로 직접 편집할 때, 값의 의미·허용범위·함정을 확인할 때 참고한다.

저장·편집 모델

핵심 규칙 & 자주 틀리는 것

규칙

  • op:"mul" 값은 퍼센트 포인트다. {stat:"atk",op:"mul",value:10} = 공격력 +10% (0.1이 아니라 10).
  • 참조 id는 실존해야 한다. 없는 id 참조 → 검증 오류거나, 조건이 영영 충족 안 되는 유령 참조.
  • 폼에 없는 필드도 저장 시 유지된다(폼은 로드된 엔트리를 패치만 함). 확인·제거는 Advanced JSON에서.

대표 함정

  • 존/맵/서비스 unlockConditions의 activeQuestIds는 "진행 중"(완료하면 다시 잠김), completedQuestIds는 "완료"(영구). 헷갈리면 지역이 사라진다.
  • 패시브 스탯 효과는 전투 시작 시 풀 HP로 1회만 평가 → hp<X 조건은 스탯 보정에선 죽은 조건.
  • 공격형 메카닉(흡혈·처형·방무·독·기절·둔화)은 physical 공격에서만 발동 → 마법사는 못 씀.
  • 이벤트 set_flag는 세션 한정, 영속 게이팅은 set_progress_flag.
  • requiredCompletedAdventureZoneIds는 location_entered 트리거에서 오작동(현재 위치와 비교) → requiredFlags/requiredActiveQuestIds 사용.

키 색인

키내용최상위 형태
classes직업{defaultClassId, classes:[]}
skills / passives액티브 / 패시브 스킬{skills:[]} / {passiveSkills:[]}
effects / buffs전투 이펙트 프리셋 / 버프{effects:[]} / {buffs:[]}
monsters / monsterArchetypes몬스터 / 아키타입{monsters:[]} / {archetypes:[]}
growth / formulas성장 곡선 / 전투 수식·AI객체
items + 절차/레어 8키아이템·장비 생성{items:[]} 등
companionGacha / houses / npcs뽑기 / 가문 / NPC각각
quests의뢰{board, quests:[]}
worldRegions / adventureZones지역 / 모험 존{regions:[]} / {zones:[]}
gameMaps / serviceZones맵 / 서비스 존{maps:[]} / {zones:[]}
eventScripts / eventTriggers이벤트 / 트리거{scripts:[]} / {triggers:[]}
storyActors / sharedBackgrounds / audio배우·배경·오디오 자산각각

classes — 직업

{ "defaultClassId":"warrior", "classes":[ { /* ClassEntry */ } ] }
FieldType의미허용값/비고
defaultClassIdstring신규 캐릭터 기본 직업classes[].id 중 하나
id / namestring직업 id / 이름필수, id 유니크
maxLevelnumber이 직업 최대 레벨정수 ≥1
unlock.typeenum해금 유형base(상시) | mastery_combo(마스터 필수) | event(이벤트 완료 필수)
unlock.requiredMasteryJobIdsstring[]승급에 마스터해야 하는 직업들mastery_combo인데 비면 오류. 각 id 실재 class
unlock.completedEventIdsstring[]이 직업의 자격 이벤트(전부 완료해야 열림)event인데 비면 오류. 각 id 실재 eventScript
statMultiplierobject6 베이스 스탯 배율(1.0=기준)키 str/vit/int/men/agi/luk만. 누락은 1. 그 외 키 오류
skillProgression[]array레벨별 습득 스킬{skillId, learnAt≥1, type:"action"|"passive"}
equipmentRulesobject장비 규칙allowedWeaponTypes, allowedArmorTypes, allowShield, allowDualWield
visualobject프로필/idle 에셋(성별 분기)profile,idle,gender.male/female.*

함정

  • unlock.type을 base로 둬도 조건 배열에 값이 있으면 정규화가 조건형으로 강제 승격한다(마스터 조건이 있으면 mastery_combo, 이벤트만 있으면 event). 기본직이면 두 배열을 모두 비울 것.
  • type은 「무엇이 필수인가」만 정하고, 판정은 채워진 조건 전부의 AND다. 두 배열을 같이 채우면 둘 다 요구한다 — 「자격 이벤트를 끝내고 + 전사·사제를 마스터해야 성기사」가 그 조합이다(type: mastery_combo + completedEventIds).
  • ⛔자격 이벤트에 grant_job_unlock 스텝을 넣지 말 것. 그 스텝은 jobs.unlockedSources에 직접 꽂혀 unlock 조건을 통째로 건너뛴다(마스터를 안 해도 열린다). 자격 이벤트는 그냥 끝나기만 하면 되고, 완료 사실은 progress.completedEventIds에 자동 기록되어 여기 조건이 읽는다. grant_job_unlock은 「조건 없이 즉시 준다」가 진짜 의도일 때만.
  • 해금 시점은 다음 프로필 읽기다(migrateProfile → recomputeUnlockedJobs). 이벤트 종료 직후 화면 전환에서 성당 전직 목록에 오른다.
  • statMultiplier는 6 베이스 스탯만. atk/def 등 전투 스탯 키 넣으면 오류.
  • 자동 마스터특전(masterSkillId·인라인 masterSkill) 개념 폐기 — 저장 시 삭제. 마스터한 직업은 전직 시 스킬 1개를 영구 계승(직업당 1회).

skills — 액티브 스킬

열거값: effectType=physical|magical|heal|barrier|taunt|revive|buff · attackType=melee|ranged|magical · castType(속도 기준)=physical(민첩)|magical(정신) · outputStat(보호막·회복의 출력)=atk|matk · targetType=enemy_single|ally_single|self|enemy_all|ally_all|ally_all_except_self · targetState=alive|dead|any · magicElement=fire|water|wind|lightning|earth|light|dark

FieldType의미허용값/비고
id/namestringid/이름필수
effectTypeenum효과 유형(파이프라인·마법숙련 분류)미지정 시 physical
attackType/castTypeenum공격 방향성 / 시전 계열castType은 공식(캐스트·쿨다운) 선택
targetTypeenum대상필수. revive=ally_*만, buff=self/ally_*만
powernumber위력≥0
cooldownMs/preloadTimeMsnumber쿨다운/최초지연(ms)≥0
airborneMsnumber체공(용기사 도약) — 이 스킬을 쓴 직후 이 시간(ms)만큼 타겟 불가 + 피해 무효≥0
castEffectMsnumber시전 연출 길이(ms). 비우면 formulas.timing.castEffectMs → 기본값. 전투 계산과 무관≥0
showCastEffectboolean시전 연출 재생 여부. 궁극기는 컷인이 대신하므로 재생되지 않는다true/false
hitCountnumber타격 횟수정수 ≥1
magicElementenum속성. 속성 상성·피격음·궁극기 컷인 색의 기준이며 물리 스킬에도 저작한다. 마법숙련 분류는 magical 스킬일 때만(물리는 무기숙련이 오른다)magical(무 포함) · physical(무 없음)
outputStatenum보호막 두께·회복량을 어느 공격력으로 계산할지(atk|matk, 기본 matk). 공격 스킬은 effectType 이 이 축을 겸한다. ⛔castType 은 속도 축이라 출력과 무관barrier/heal에서만
lifeStealobject흡혈 {ratio, basis}physical/magical에서만
chanceDamageobject확률 피해physical/magical에서만
armorPenetrationobject방어관통 {ignoreRatio}물리=def, 마법=mres 무시
executeobject처형 {hpBelowRatio, bonusMultiplier}대상 HP비율 ≤ 시 배율
coverobject엄호 {allyHpBelow}아군 HP≤ 시 단일공격 대신 받음
tauntDurationMs/reviveHpRatio/trueStrikenum/num/bool도발/부활회복비율/필중revive는 reviveHpRatio>0 필수
buffEffects+buffDurationMsarray+num인라인 버프키: atk/def/matk/mres/agi/men/crit/tauntWeight/evasionChance
castEffectId/attackEffectId/hitEffectId/projectileEffectIdstring연출 프리셋 id실재 effects id
*SfxTrackIdstring효과음 트랙실재 audio.sfxTracks id
hidden+houseId+usableFromLevelbool+..혈통 히든 스킬(목록 비노출, 전투 자동발동)

함정

  • lifeSteal/chanceDamage는 physical/magical에서만 허용. armorPenetration/execute는 데미지 스킬(physical/magical)에만 적용(heal/buff에 붙여도 무효).
  • 무기/패시브가 주는 dot/stun/slow는 physical 공격에서만 발동.
  • 클라 DslLoader.normalizeSkills는 필드 화이트리스트 복사 — 목록에 없는 신규 필드는 런타임서 조용히 사라짐(새 필드 추가 시 클라도 갱신).

passives — 패시브

{ "passiveSkills":[ { "id":"", "name":"", "condition":"", "effects":[ ... ] } ] }

effects[]는 두 형태:

(A) 스탯 보정 (기본; kind 미지정/statMod/stat)

{stat, op:"add"|"mul"|"set", value, condition?} — 스탯: str/vit/int/men/agi/luk/hp/atk/def/matk/mres/crit. op:"mul"의 value는 퍼센트 포인트(10 = +10%). 조건식 변수: actor,stats,level,hp,weaponType,random().

(B) 전투 메카닉 (kind:"mechanic", mechanic 객체)

mechanic.kind필드의미
lifeSteallifeStealRatio흡혈 비율 — 물리 스킬에만 붙는다.
lifeCyclehealRatio마법 흡혈 — 마법 스킬로 가한 피해의 이 비율만큼 회복. "모든 공격 흡혈"은 lifeSteal과 이걸 둘 다 넣어야 한다.
armorPenetrationignoreRatio방어/마방 무시
executehpBelowRatio, executeBonusMultiplier처형 배율
chanceDamagechanceDamageChance/SuccessMultiplier/FailureMultiplier확률 피해
dotdotTickDamage, dotTickIntervalMs(기본1000), dotDurationMs지속 피해
stun / slowstunDurationMs / slowAgiPercent,slowDurationMs기절 / 둔화
trueStrike(없음)필중
guardguardRatio피해 경감(×(1-guardRatio))
reflectprocChance, reflectRatio확률 반사
regenprocChance, healRatio피격 시 확률 회복(받은피해×healRatio)
resistresistChance디버프 확률 무효
counterOnHit / counterOnEvadeprocChance?, counterMultiplier피격/회피 시 반격
damageShareshareRatio아군이 받을 피해의 shareRatio 만큼을 보유자가 대신 받는다(엔진 상한 0.5). 지속시간 버프로 켠다.
elementAffinityelement, damageTakenMult속성 상성 — 그 속성의 공격을 맞을 때 피해에 배수를 곱한다(0.1 = 90% 무효, 1.5 = 1.5배). 판정 기준은 스킬의 magicElement 다 — 물리·마법 구분 없이 그 속성이면 걸린다(속성 칸이 빈 스킬은 해당 없음). 속성 하나당 메카닉 하나 — 여러 속성은 effects 에 여러 개 넣는다.

(C) 스킬 onHitDebuff — 명중 시 대상에게 남기는 표식

physical/magical 스킬에만 붙는다. durationMs(필수)와 아래 중 최소 1개가 있어야 성립한다. 여기 없는 필드는 저장 때 조용히 사라진다 — 서버 정규화·검증기·관리자 저장 세 곳이 각각 화이트리스트다.

필드의미상한
slowAgiPercent민첩을 이 %로 곱한다(75 = 25% 감소). 레거시 표기.1~99
damageTakenPercent받는 피해를 이 %로 곱한다(120 = 20% 증가).>100
statDebuffs[{stat, mulPercent}] 임의 스탯 증감. 통일 표기(-50 = ×0.5).mulPercent > -100
dotPercentOfMaxHp틱마다 대상 최대HP의 이 %.틱당 6% / 절대 100
dotPercentOfDamage틱마다 그 타격 피해의 이 %(레벨·스탯에 자동 비례 — 권장).—
dotTickDamage틱당 고정 피해(레벨이 올라도 그대로).—
dotMaxTicks / dotPerAction총 틱 수. 행동 1회당 1틱으로 센다(서버가 가상시계라 "N초당"은 안 지켜진다).
⚠️ "행동"은 전투 전체의 행동이다 — 대상 본인의 행동이 아니다. 참가자가 많으면 그만큼 빨리 닳는다.
—
dotStackable다시 걸면 갱신이 아니라 쌓인다.5스택
stunDurationMs대상의 새 행동 시작을 막는다(진행 중이던 캐스트는 마무리된다).상한 없음 — 궁극기 외 200ms 기준
charmDurationMs매혹 — 대상의 적/아군 판정이 뒤집혀 자기 편을 공격한다. 때릴 아군이 없으면(혼자인 편) 매혹을 무시하고 원래 적을 노린다(자해 방지).상한 없음 — 데이터로 관리
visualPresetId상태 표시. debuff_hunters_mark / debuff_poison / debuff_burn / debuff_shadow / debuff_bleed / debuff_charm / debuff_stun—

함정 (매우 중요)

  • 패시브 스탯 효과는 전투 시작 시 풀 HP로 1회만 평가 → hp<X 류 조건은 스탯 보정에선 죽은 조건.
  • op:"mul" value는 배율이 아니라 퍼센트 포인트(1.1 원하면 10).
  • 공격형 메카닉(lifeSteal/armorPenetration/execute/chanceDamage/dot/stun/slow/trueStrike)은 시전자 physical 공격에서만 발동 → 마법사는 실질 사용 불가. 예외는 lifeCycle(마법 흡혈) 하나로, 이건 magical 에서만 돈다.
  • 방어형 메카닉(guard/reflect/regen/resist/counterOnHit/counterOnEvade)은 보유자가 맞을 때 계열 무관 동작. 단 damageShare만은 아군이 맞을 때 발동하고, elementAffinity는 속성이 있는 마법 공격에만 반응한다.
  • lifeSteal·lifeCycle 둘 다 스킬 자체 흡혈(skill.lifeSteal)이 있으면 얹히지 않는다 — 스킬 쪽이 이긴다. 「흡혈 30% 스킬」은 「모든 공격 흡혈 50%」 패시브를 들어도 30% 그대로다.
  • regen의 회복 필드명은 healRatio(regenRatio 아님).

effects — 전투 이펙트 프리셋

FieldType의미허용값
id/namestringid/이름필수
kindenum종류preset | sprite
presetId/assetUrlstring프리셋 id / 스프라이트 URLpreset이면 presetId(또는 layers/placements) 필수
anchorenum기준점actor|primary_target|all_targets|screen
durationMsnumber지속≥1
blendModeenum블렌드normal|add|multiply|screen
actionColor/accentColor/critColor/castColor/castAccentColorstring팔레트 색(스킬이 색 지정)hex
placements/layersobj/array단계별 프리셋 / 다층 레이어키 cast|action|impact|finisher
camera/reaction/travel/physicalMotionobject카메라·피격반응·이동·물리궤적

함정

  • 도형 테두리선/얇은 line 금지 — 소프트 글로우 텍스처로(프로젝트 규칙).
  • presetId(단일)와 layers/placements(다층)는 별개 경로.

buffs — 버프

{ "buffs":[ { "id":"", "name":"", "durationMs":5000, "effects":[ {stat,op,value} ], "visualPresetId":"" } ] }

effects 키 제한: atk/def/matk/mres/agi/men/crit/tauntWeight/evasionChance (베이스 스탯·hp 불가). op:"mul"은 퍼센트 포인트.

monsters / monsterArchetypes

{ "teamSizeMin":1, "teamSizeMax":4,
  "monsters":[ { "id":"", "name":"", "monsterType":"fighter", "growth":{...}, "sizeTier":"medium", "skills":[], "drops":[{"itemId":"","chance":0}] } ] }
Field의미허용값
monsterType아키타입(능력치·보상 기준)tank|fighter|rogue|mage (기본 fighter)
growth아키타입 오버라이드 {budgetScale>0, statRatio}statRatio 6스탯, 최소 1개>0
sizeTier크기small|medium|large|boss
skills/drops스킬 id / 드롭 [{itemId,chance≥0}] — 한 줄에 itemId 대신 gold(금액)를 넣으면 골드 드롭

monsterArchetypes: {archetypes:[{id:tank|fighter|rogue|mage, growth{budgetScale,statRatio}, rewards{exp,jobExp,gold:{base,perLevel}}}]}

함정

  • 몬스터에 level/stats/gold를 넣어도 저장 시 삭제 — 배틀존 레벨 + 아키타입으로 런타임 산출.
  • 배틀존 편성: 기본 풀 랜덤, 고급은 useCustomComposition/monsterComposition로 수량 고정.
  • 적 NPC(enemyNpcIds)는 레벨로 강해지지 않는다(스토리 NPC 스탯은 레벨을 안 씀). 노브는 둘: enemyNpcBudgetScale(적는 값이 곧 배율 — 전 스탯), enemyNpcHp(절대 최대 HP — HP만, 비우면 vit로 계산). 동료로서의 그 NPC엔 영향 없음.
  • 이 둘은 존 기본값이고, NPC마다 다르게 주려면 enemyNpcOverrides = { npcId: { budgetScale?, hp? } }. 우선순위 NPC별 > 존 기본값 > NPC growth이며 배율·HP는 서로 독립(한쪽만 NPC별로 줘도 된다).

growth — 성장 곡선 (자유 객체)

Field의미기본값
maxLevel캐릭터 최대 레벨99
charExpTable레벨별 요구 경험치(인덱스=level-1)없으면 floor(100*level^1.5)
levelDiffExpPct레벨차당 경험치 증감%10
statGainWeights레벨업 스탯 증가값(0~5)→가중치{"0":18,"1":16,"2":8,"3":4,"4":2,"5":1}
initialStatPerStat생성 시 각 베이스 스탯 초기값5

함정

  • growth는 자유 객체라 오타 키가 검증에 안 걸린다 — 키 이름 정확히.

formulas — 전투 수식 · AI

Field의미비고
damage / magicDamage물리 / 마법 피해 수식(문자열)필수
evasionChance회피 확률 수식0~1 clamp
castTimeByType / cooldownByType계열별 캐스트/쿨다운 수식키는 physical/magical만
combatAi전투 스킬 선택 AI 튜닝(아래)없으면 코드 기본값
useRealTimeTimeline⏱ 실시간 타임라인. 켜면 도트/HoT 틱과 버프 만료가 행동과 무관하게 자기 시각에 발동하고, 스킬 연출 길이만큼 액터가 다음 행동을 미룬다(연출 점유). 끄면 예전대로 "행동이 일어날 때만" 처리.기본 false
playbackTimeScale클라 재생 속도 배율. 서버 타임라인 ms 에 곱한다(1 = 서버 시각 그대로). useRealTimeTimeline 이 켜져 있을 때만 쓰인다.기본 1
occupancyScale연출 점유 배율. 점유는 effects 의 durationMs(시전+공격/투사체+타격) 합에서 산출한다. 스킬에 occupancyMs 를 직접 적으면 그 값이 이긴다.기본 1
serializeActions전역 무대 점유. 한 번에 한 액션만 재생되도록 서버가 순서를 벌린다. 끄면 같은 시각 행동이 화면에서 겹쳐 큰 숫자가 한꺼번에 뜬다(실기로 확인). useRealTimeTimeline 이 켜져 있을 때만 의미가 있다.기본 true
minScheduledStepMs시간 사건 최소 간격(폭주 방어선).기본 50

⏱ 실시간 타임라인 켤 때

  • 서버·클라 둘 다 같은 플래그를 본다(클라 재생 간격 Clamp 가 풀린다). 하나만 켜면 화면이 서버 시각과 어긋난다.
  • 도트가 dotPerAction 이면 여전히 행동 기반이다. 시간 기반으로 돌리려면 dotPerAction 을 빼고 dotTickIntervalMs 를 준다.
  • 연출 점유가 GCD·쿨보다 길면 그 값들이 죽은 노브가 된다. 시간 노브를 점유 위로 올려야 순서가 의미를 가진다.

combatAi (전투 스킬 선택 룰)

Field의미기본
healThreshold일반 힐: 아군 최저 HP비율 이 값 미만일 때만0.6
criticalHealThreshold위급 힐 승격 비율0.35
skipActiveBuff이미 걸린 버프 재시전 안 함true
preferLongerCooldown같은 우선순위면 쿨다운 긴(강한) 스킬 먼저true
rolePriority역할 우선순위(낮을수록 먼저){reviveCritical:1,criticalHeal:1,hidden:2,buff:3,debuff:4,heal:5,attack:6}

함정

  • damage/magicDamage 필수(없으면 런타임 예외). rolePriority는 얕은 병합(일부만 오버라이드해도 나머지 기본 유지).

unlockConditions — 해금 조건 (공통)

worldRegions/adventureZones/gameMaps/serviceZones가 공유. 서버·클라 이중 구현(파리티).

{ "unlockConditions": { "minPlayerLevel":1, "clearedAdventureZoneIds":[], "completedEventIds":[], "requiredPartyNpcIds":[], "activeQuestIds":[], "completedQuestIds":[] } }
Field의미참조
minPlayerLevel최소 레벨기본 1
clearedAdventureZoneIds이 존들 모두 클리어adventureZones[].id
completedEventIds이 이벤트들 모두 완료eventScripts[].id
requiredPartyNpcIds이 동료들 파티 보유npcs[].id
activeQuestIds이 의뢰들 진행 중(수락) — 완료해서 active 에서 빠지면 다시 잠긴다quests[].id
completedQuestIds이 의뢰들 완료(보상 수령) — 한번 열리면 계속 열려 있다quests[].id

함정

  • activeQuestIds(진행 중)와 completedQuestIds(완료)는 다르다. 「의뢰 받으면 열리고 끝내면 닫히는 곳」은 앞쪽, 「의뢰를 끝내면 열리는 곳」은 뒤쪽. 둘 다 적으면 AND.
  • Deprecated 별칭이 하나라도 있으면 영구 잠금(fail-closed): clearedLocationIds, requiredCompletedEventIds, requiredStoryCompletions, partyNpcIds.
  • activeQuestIds/completedQuestIds에 존재하지 않는 의뢰 id를 넣으면 영구 미해금(고블린동굴 버그 원인). 저장 시 검증에서 막는다.

worldRegions — 지역

{regions:[{id, name, desc, unlockConditions}]}. 자식 어드벤처 존을 묶는다. 자식 존이 하나도 없거나 전부 잠기면 지역 자체가 목록에서 제외된다.

adventureZones — 모험 존

{ "zones":[ {
  "id":"", "parentRegionId":"", "name":"", "backgroundId":"", "recommendedLevel":1, "entryCost":0,
  "requirements":{"minPlayerLevel":1,"maxPlayerLevel":99,"minPartySize":1,"maxPartySize":4},
  "flow":{ "travelSecondsPerEncounter":3, "encounterCountToClear":1,
    "encounterSequence":[ {"type":"battle","battleZoneId":""},
      {"type":"event","eventScriptId":"","unlockConditions":{"completedEventIds":[]}} ],
    "forwardMotion":{"bgScrollPxPerTick":26,"zoomDelta":0.03,"swayPx":8,"tickMs":140} },
  "unlockConditions":{} } ] }
Field의미비고
parentRegionId소속 지역worldRegions[].id
backgroundId배경sharedBackgrounds에 있어야 적용
requirements.*입장 레벨/파티 인원 범위기본 1~99 / 1~4
flow.encounterSequence[]순서대로 진행되는 조우type: battle|event|treasure|weighted
└ battlebattleZoneIdbattleZones 참조
└ eventeventScriptIdeventScripts 참조
└ eventunlockConditions(선택)조건 미충족이면 그 조우를 건너뛴다(이벤트는 소모되지 않음). 필드셋은 존 해금과 동일(completedEventIds·clearedAdventureZoneIds·completedQuestIds·activeQuestIds·requiredPartyNpcIds·minPlayerLevel). 조우 단계는 트리거를 안 타므로 진행도로 게이팅하는 유일한 방법. weighted 선택지에서는 불가
└ treasuregold, itemId, title, body
└ weightedchoices[](base step + weight)weighted 중첩 불가
flow.encounterCountToClear클리어 필요 조우 수정규화 시 encounterSequence.length로 덮어씀

gameMaps — 맵(노드·링크)

Field의미허용값
kind맵 종류hub | world
backgroundId배경필수
links[].targetMapId/ServiceZoneId/WorldRegionId이동 대상셋 중 하나만
links[].coordinateSpace/displayStyle좌표 방식 / 표시design|percent / invisible|marker
links[].x/y/width/height클릭 영역coordinateSpace 따라 해석

함정

  • 대상 맵/서비스존이 잠겨 있으면 링크 미표시. worldRegion 링크는 그 지역이 접근 가능 목록에 있어야 표시.

serviceZones — 서비스 존

Field의미허용값
backgroundId/actorId/message배경/액터/안내문background·message 필수
menus[].actionType메뉴 동작change_job|recruit|register|quests|forge|placeholder
menus[].itemIds/forgeMode취급 아이템 / forge 모드buy|sell

함정

  • 메뉴가 빈 서비스존은 나가기 버튼이 없어 플레이어를 가둘 수 있다 — 이벤트 후 open_game_scene으로 맵 복귀를 꼭 넣을 것. ⛔이 자리에 move_world_location을 쓰면 맵으로 돌려보내는 게 아니라 그 모험 존으로 입장시킨다.

eventScripts — 스토리 이벤트

{scripts:[{id, name, startStepId?, oncePolicy:"once_per_account"|"repeatable"|"repeat_daily", steps:[...]}]}. once_per_account 완료 시 progress.completedEventIds에 id 기록.

Step type핵심 필드의미
narration/dialoguetext, actorId?, backgroundId?나레이션/대사
choiceprompt, options[]{id,label,nextStepId?,setValue?}선택지
text_inputprompt, variable, maxLength?입력을 변수에 저장
set_flag / set_progress_flagkey,value / flag,value?세션 플래그 / 영속 진행 플래그
branchcondition{key,op,value?}, nextStepId, fallbackStepId?op: equals|not_equals|exists|not_exists · key: progress.<플래그> / companion.<npcId>(이벤트 동료로 동행 중인지 — 파티 편성 무관) / party.<npcId>(그 이벤트 동료가 지금 파티에 편성되어 있는지) / gender·name·houseId. ⭐뽑기로 얻은 동료는 두 조건 모두에서 제외된다
goto_stepstepId점프
open_game_scenesceneKey, mapId?/locationId?/serviceId?씬 전환
grant_job_unlock/change_jobjobId직업 해금/전직
grant_item/grant_golditemId / amount지급
party_join/party_leavenpcId동료 합류/이탈
start_battlebattleZoneId, nextStepId?전투
open_service/complete_triggerserviceId / triggerId?
move_world_locationlocationId그 모험 존으로 입장(맵 복귀 아님). 잠겼거나 파티 조건 불일치면 지역 패널만 열고 멈춘다
endmessage?, advance?{mode:auto|pause|click}종료

함정

  • draft 플래그(set_flag)는 세션 한정 — 영속 게이팅은 set_progress_flag.

eventTriggers — 트리거

조건이 맞으면 eventScriptId 실행. triggerType: adventure_completed | location_entered | service_interacted | npc_joined | job_unlocked | item_obtained | gacha_result | manual.

conditions.*의미
mapId/serviceId/locationId현재 위치 일치
requiredFlags필요 진행 플래그(모두) — progress.flags
requiredActiveQuestIds / requiredCompletedQuestIds진행 중 / 완료 의뢰
requiredQuestObjectives달성한 의뢰 목표 — "의뢰id:목표id". 의뢰 완료(보상 수령) 전에 목표 하나를 채운 시점을 잡는다(예: 늑대 10마리를 채운 뒤 마을 입장 → 촌장이 보상). 그 의뢰를 진행 중이어야 한다.
requiredStoryCompletions완료 이벤트 — progress.completedEventIds
excludedStoryCompletions만료 지점 — 적힌 이벤트를 하나라도 완료했으면 발동하지 않는다. 곁가지 이벤트가 한참 뒤에 튀어나와 이야기가 어긋나는 걸 막는다. requiredStoryCompletions(시작 지점)와 같이 쓰면 「이 구간에서만」이 된다.
requiredMasteredJobs/activeJobId/excluded*직업 조건
requiredHouseIds/excludedHouseIds가문 조건 — 플레이어가 선언한 가문(lineage.declaredHouseId) 하나로만 판정한다. 섞인 서브 혈통은 보지 않는다. 가문을 안 정했거나 평민이면 required에 걸리지 않고 excluded에도 막히지 않는다.
min/maxPartySize파티 인원(1+members)

함정

  • requiredCompletedAdventureZoneIds는 location_entered에서 오작동 — "완료한 존"이 아니라 "지금 들어온 로케이션"과 비교. 존 완료 게이팅은 requiredFlags(완료 이벤트가 세운 플래그)나 requiredActiveQuestIds로.
  • requiredMasteredJobs/requiredItems도 context 값이 있으면 의미가 바뀐다.

quests — 의뢰

{ "board":{"resetHourKst":0,"resetToken":"1786...."},
  "quests":[ { "id":"", "name":"", "rank":"F", "category":"story", "autoClaim":true, "giverNpcId":"",
    "days":["mon","wed"], "repeatPerDay":3,
    "objectives":[{"id":"o1","type":"complete_event","targetId":"","description":""}],
    "reward":{"gold":0,"charExp":0,"items":[]}, "requirements":{"requiredCompletedAdventureZoneIds":[]} } ] }
Field의미허용값
rank / category랭크 / 분류F E D C B A S / daily·story
autoAcceptstory 전용. 선행조건 충족 시 길드 방문 없이 자동 수락(연쇄 스토리 의뢰)true만 채택
autoClaimstory·daily 공통. 목표를 채운 그 자리에서 자동 완료·보상. ⭐unlockConditions.activeQuestIds 로 여는 존은 수령 전(ready_to_claim)에도 열려 있으므로, 이걸 꺼두면 목표를 다 채우고도 재진입된다true만 채택
daysdaily 전용. 게시판이 열리는 요일(중복 선택)mon tue wed thu fri sat sun / 비우면 매일
repeatPerDaydaily 전용. 열린 하루 동안 반복 수행 가능 횟수1 이상 정수(기본 1)
clearFlagsOnClaimdaily 전용. 보상 수령 시 추가로 되돌릴 진행 플래그set_progress_flag 가 세우는 플래그명
objectives[].type목표 유형kill_monster·clear_zone·collect_item·reach_level·complete_event
objectives[].targetId목표 대상 idtype에 따라 monster/zone/item/event(또는 flag) id
reward{gold, charExp, jobExp, items:[{itemId,count}]}모두 선택
requirements선행조건minLevel, maxLevel, requiredFlags, requiredCompletedQuestIds, requiredCompletedAdventureZoneIds, completedEventIds(선행 완료 이벤트)

함정

  • complete_event objective는 flags.has(targetId) 또는 completedEvents.has(targetId)로 충족된다(targetId는 이벤트 id 또는 진행 플래그명).
  • 반복 의뢰 + complete_event = 2회차부터 수락 즉시 완료 — 진행 플래그가 영속이라 그렇다. 그래서 daily 의뢰는 수령 시 목표 플래그를 자동 소거한다. 이벤트가 세운 보조 플래그는 clearFlagsOnClaim에 직접 적는다.
  • 반복 의뢰의 complete_event targetId 는 이벤트 id 가 아니라 진행 플래그여야 한다. completedEventIds는 되돌리지 않는다(되돌리면 oncePolicy: once_per_account 게이팅이 깨진다). 저장 시 에러로 막는다.
  • 소거하는 플래그를 다른 트리거·의뢰가 해금 조건으로 쓰면 회차마다 다시 잠긴다. 저장 시 경고로 알려준다.
  • board는 resetHourKst(0~23)만 손으로 저작한다. resetToken은 의뢰 화면 최상단 「지금 리셋」 버튼이 쓰는 값이라 직접 건드리지 않는다.
  • 요일·리셋 판정은 resetHourKst 기준 — resetHourKst:4면 KST 새벽 3시는 아직 전날 요일로 취급된다.
  • dayKey/dailyCompletedCounts는 플레이어별 런타임 상태(DSL 아님). 게시판 목록은 저장하지 않고 매 조회마다 요일로 계산하므로, 일일 의뢰를 새로 만들면 즉시 반영된다.

items — 아이템(고정 카탈로그)

열거값: weaponType=dagger|sword|axe|mace|bow|staff|gauntlet · armorType=cloth|light|heavy · handedness=one_hand|two_hand · rarity=common|uncommon|rare|epic|legendary · slot=mainHand|offHand|armor|accessory

Field의미비고
slot장착 슬롯레거시 weapon→mainHand, shield→offHand 정규화
weaponType/armorType/handedness무기/방어구/손잡이slot과 안 맞으면 정규화 시 삭제
rarity/reqLevel/buyPrice등급/요구레벨/구매가rarity 무효값은 common으로 치환
combat/bonus전투 스탯(atk/def/matk/mres) / 베이스 스탯 가산
usableBy/storyItem착용 가능 직업 / 스토리 아이템

함정

  • 새 weaponType은 반드시 화이트리스트에 추가해야 저장됨 — 없으면 정규화 시 조용히 삭제.
  • slot에 안 맞는 weaponType/armorType/handedness는 저장 시 삭제.

절차/레어 장비 시스템 (8키)

키역할
itemStatAffixes레어 어픽스 풀 + 굴림 파라미터(minAffixCount, maxAffixCount, 주속성 rollVarianceMin/Max, 보조스탯 bonusRollVarianceMin/Max, affixValueScale*, affixes[])
itemQualityTiers품질 배율 티어(가중 추첨: multiplier, weight)
itemMechanicEffects무기 Tier3 메카닉 풀(effects[].kind: lifeSteal/armorPenetration/execute/dot/stun/chanceDamage/slow/trueStrike)
itemArmorMechanicEffects방어구 Tier3 풀(kind: reflect/guard/regen/resist/counterOnEvade/counterOnHit)
itemRareBaseWeapons레어 무기 베이스(baseValue, perLevelIncrement, weaponType, handedness). 커브는 하나지만 무기는 물리공격+마법공격 둘 다 갖고, 두 값을 각각 따로 굴린다(주속성 선택 statKey는 폐지)
itemRareArmorBases레어 방어구/방패 베이스(def+mres 동시, slot: armor|offHand)
itemRareAccessoryBases장신구 베이스 + 공통 마법저항 커브(mresBaseValue, perLevelRatio)
itemLegendaryBases손수 저작하는 전설 베이스(bases[]) + 생성 노브(subStatChances, valueRollMin/Max, pickableRatio). 주커브는 부위별 베이스의 공통 설정을 사용한다 — 무기=공격력+마력(각각 롤), 방어구=방어력, 방패=방어력+마법저항, 장신구=마법저항
itemRandomDrop전투 승리 시 추가 드랍(dropChance, rareChance)

함정

  • 모든 *Bases/*Effects 풀은 비우면 createDefault* 기본값으로 대체된다(빈 배열 저장해도 기본값 들어감).
  • 메카닉이 붙는지는 등급이 정한다(magic=안 붙음, rare/legendary=반드시). 풀의 weight는 "붙는다면 무엇이"만 정한다 — 예전의 mechanicAttachChance는 아무 경로도 쓰지 않던 죽은 노브라 삭제됐다.
  • 양손무기는 주속성·부가수치가 같은 무기타입 한손의 2배다. 베이스 커브에 그대로 박혀 있고 어픽스는 생성 시 2배로 굴린다 — 전투 시점의 숨은 배율(formulas.twoHandDamageMultiplier)은 폐지됐다.
  • 무기는 종류와 무관하게 물리공격·마법공격을 둘 다 갖는다. 커브가 같고 밴드도 같으므로 어느 쪽이 높은지는 롤이 정한다 — 지팡이 물리공격이 마법공격보다 높게 나올 수도 있다.
  • enum/kind/stat 무효값은 오류가 아니라 기본값으로 조용히 치환.

companionGacha — 동료 뽑기

{goldCost, companionChance(0~1), equipmentRarityChances:{common,rare,legendary}}. companionChance 확률로 동료, 나머지는 장비. 세 rarity 합=1. 동료 후보는 npcs[].gachaEligible===true인 NPC만.

houses — 가문/혈통

{houses:[{id, name, theme, selectable, hidden, statGrowth?, auraColor?}], commonerHouseId, heroHouseId, primaryBloodMin/Max, mixChance, ...}. statGrowth: {rolls{스탯→판정횟수}, probMultiplier, fixedProb?} — 레벨업 시 혈통% 확률로 스탯 추가 상승. 비우면 기본 5가문+commoner+hero로 복원.

npcs — NPC/동료

Field의미
gendermale | female
growth{statRatio{6스탯}, budgetScale?} (레거시 stats에서 비율 파생)
skills.activeAction / skills.activePassive액티브 / 패시브 스킬 id 목록
gachaEligible동료 뽑기 후보 풀 포함(=== true만)

audio — 오디오

{tracks:[], sfxTracks:[], ui:{}, zones:{}, sfx:{}}. tracks=BGM, sfxTracks=효과음(전투 스킬 SFX가 참조). ui=상황별 BGM(intro/login/town_hub/battle/…), zones=존 id→트랙. AudioTrack: {id,name,url,mimeType,originalName,sizeBytes}.

storyActors / sharedBackgrounds

storyActors.actors[] = 이벤트 dialogue·서비스존 actorId의 참조 대상. sharedBackgrounds.backgrounds[] = 각종 backgroundId의 참조 대상. 전체화면 배경은 cover(채우기) 원칙, contain 금지 → 배경 이미지는 마진 넉넉히 제작.

이 문서는 서버 스키마·검증기에서 추출. 코드가 바뀌면 server/public/admin/help/dsl-reference.html도 갱신할 것.