CAFE

CK 모드 게시판

[CK2 모딩 팁]내멋대로 쓰는 Crusader King 2 모딩 매뉴얼 (4) 조건문 - 개정판 (2015/04/30 수정)

작성자tacitus|작성시간15.02.13|조회수2,211 목록 댓글 2

내멋대로 쓰는 Crusader Kings 2 모딩 매뉴얼 - 개정판

4. 디시전 파일의 작성

지난 회 스코프에 대한 내용이 절대 쉽지는 않으셨을 겁니다. 슬슬 코드들도 등장하고 있고요. 이 매뉴얼 글에 달리는 덧글들은 제가 계속 확인하고 있으니, 잘 안 풀리는 부분에 대해서 질문을 주시면 가능하면 답을 드리려고 노력합니다. (요즘 영 시간이 잘 안 나는데, 그래서 좀 텀이 길어집니다. 빠른 답을 얻기를 원하신다면 질문/답변 게시판을 이용하시는 것이 더 좋을 수도 있습니다.) 이번 회 역시, 가급적 디시전에서 사용되는 예를 가지고 진행을 하지만, 기본적으로는 다른 영역에서도 모두 통하는 내용들입니다. 그만큼 중요한 부분이니 잘 따라와 주십시오. 지난 회보다는 훨씬 쉬울 겁니다.

(5) 조건 구문

어떤 모딩에 특정한 조건에 따라 무언가를 실행하라는 류의 명령이 제공되지 않는다면, 결단코 그 모딩으로는 아무 것도 할 수 없습니다. 그 정도로 조건문은 중요합니다.

사실 우리는 지금까지 이미 조건문의 그림자 정도는 앞에서 확인을 한 바가 있습니다. 디시전 파일의 potential 이나 allow 등은 모두 특정한 조건들을 구성 요소로 삼는 섹션이었기 때문이죠. 이제 그걸 좀 더 자세히 파 볼까 합니다. 이전에도 말씀드렸지만, 이 내용은 디시전 뿐만 아니라 이벤트 등에서도 그대로 사용되므로 꼭 숙지하도록 하십시오.

■ 기본 중의 기본 : if (+limit)

어느 언어든 if 조건문이 모든 조건문의 기본이 됩니다. CKII 에서 if 는 limit 와 결합하여 조건문을 만듭니다. 구조는 다음과 같습니다.

if = {
  limit = {
    ...조건들...
  }
  ...조건이 참이라면 실행할 명령
}

if 와 관련하여 주의할 점 하나. if = { } 구문은 효과를 주는 부분에서만 사용이 가능합니다. 무슨 이야기냐고요?

우리는 지금 디시전을 보고 있습니다. 디시전의 구조적 특징은, 조건 섹션과 효과 섹션이 분리되어 있다는 것입니다. from_potential = { } 과 potential = { } 섹션, allow = { } 섹션에서 이 디시전을 사용 또는 실행할 수 있는 조건을 정하고, effect = { } 섹션에서 이 디시전을 실행했을 때의 효과를 정하죠. 여기까진 다 알고 계시죠? 그렇다면, 조건을 정하는 앞의 세 개 섹션의 경우 애초에 조건을 정하는 섹션이기 때문에, 굳이 if = { } 명령어를 사용하여 조건을 정할 필요가 없다는 것입니다. 그냥 조건을 서술해 주면 되죠.

따라서, 굳이 if = { } 구문을 써서 조건을 정의해 주어야 하는 것은, 디시전에서의 effect = { } 와 같이 효과를 서술하는 부분에 한정된다는 것입니다. 디시전에서의 effect = { } 이외에, 아직 보지 않았지만 이벤트에서의 option = { } 섹션, 또는 바로 전 회에서 어쩔 수 없이 한 번 등장했던 immediate = { } 섹션 등에서 사용하는 겁니다. 이해 되시죠? potential = { } 섹션 같은 데서 if = { } 같은 필요 없는 걸 사용하시면 그 디시전 먹통 됩니다.

그 다음, limit = { } 와 관련된 이야기를 좀 하도록 하죠. 왜 if = { } 절 안에는 항상 limit = { } 절이 따라올까요?

근본적인 이야기를 하나 해 봅시다. 조건은 왜 정할까요? 어떤 조건을 만족시키면 어떤 효과를 발생시키겠다는 의도로 조건을 정하는 거죠. 마치 if = { } 구문 내에 디시전으로 따져서 potential = { } 섹션과 effect = { } 섹션이 함께 들어있는 셈이예요. 그렇다면, if = { } 구문 내에서 어디까지가 조건이고 어디까지가 그 조건을 만족시켰을 때의 효과인가가 문제가 되겠죠. if = { } 절이 효과를 주는 구문 내에서 사용되기 때문에, if = { } 절 내에서 효과 구문들은 따로 구분할 필요가 없습니다. 다 효과 구문인 거 아니까요. 필요한 것은 여기부터 여기까지는 효과구문이 아니라 조건구문이다 라는 것을 표시해주는 것입니다. 그 용도로 사용되는 것이 바로 limit = { } 구문인 거죠. 이 안쪽에 들어 있는 모든 내용은 조건구문으로 해석을 해라. 이런 겁니다.

그렇기 때문에, limit = { } 구문은 마치 효과 부분 내에서는 원칙적으로 사용할 수 없는 조건 구문들을, 예외적으로 사용할 수 있게 해 주는 역할을 하는 느낌입니다. 지난 회에서 여러 캐릭터가 포함될 수 있는 스코프를 한 번 더 제한할 때에 limit = { } 를 썼던 것 기억하세요? limit = { } 구문이 사용되는 곳이 두 군데가 있는데, 하나는 if = { } 절 안쪽이고, 다른 하나가 스코프의 범위 제한 구문이예요. 모두 효과를 주는 부분 내에서 조건을 정하기 위해서 사용되었죠. 그럼 그 바로 뒤에, 조건을 정하는 부분인 potential = { } 섹션 등에서는 스코프 범위를 제한하기 위해 limit = { } 구문을 사용할 수 없다고 말씀드린 것도 이해가 되시나요? 애초에 조건을 정하는 부분이었기 때문에 거기에 사용된 모든 구문은 조건 구문이고, 따라서 굳이 limit = { } 같은 걸 써서 조건구문을 분리해 줄 필요가 없다는 겁니다. 굳이 말하자면 limit = { } 는 명령어에 속하기도 합니다. 조건문 내에 쓸 수가 없어요.

다음의 예제를 보시죠.

effect = {
  
  ...(앞부분 생략)...

  if = {
    limit = {
      any_courtier = {
        OR = {
          spouse = { character = ROOT }
          is_consort = ROOT
        }
        religion = hindu
        hidden_tooltip = {
          is_alive = yes
        }
        trait = shaivist_hindu
      }
    }
    add_trait = shaivist_hindu
  }
}

원본의 conversation_decisions.txt 에서 뽑아온 것으로, effect 섹션의 내용입니다. 특정한 조건을 만족하면 shaivist_hindu 트레잇을 달게 해주는(add_trait 명령어는 트레잇을 추가해주는 명령어입니다.) 구문인데요. limit 안쪽이 모두 조건입니다. 이 조건을 모두 만족한다면, limit = { } 절 바깥, if = { } 절 안에 있는 add_trait 명령어가 실행됩니다.

저 조건문, 굉장히 전형적인 조건문입니다. 잘 봐두세요. 어떤 가신(any_courtier)이 ROOT 의 아내 또는 첩이고, 종교가 hindu 이며, shaivist_hindu 트레잇을 가지고 있다면, ROOT 에게도 shaivist_hindu 트레잇을 달게 하는, 전형적인 배우자 따라가기 이벤트이거든요. 처음에 저도 배우자 또는 첩을 어떻게 조건문으로 구성을 해야 할지 굉장히 고민했었기 때문에, 일부러 이 조건문을 예시로 골라 봤습니다. 실제로 사용할 일이 종종 있을 거예요.

아. is_consort 는 처음 등장하는 조건문이죠? 이것은 캐릭터 스코프에서 사용되며, 현재 캐릭터 스코프의 캐릭터가 첩 관계에 있는지를 확인하는 조건문입니다. 값으로 Yes/No 또는 ROOT/FROM/PREV/THIS 등을 받을 수 있는데요. Yes/No 를 받으면 스코프 캐릭터가 누군가와 첩 관계에 있는지 여부를 확인하고, 특정한 스코프를 받으면 "그 스코프의 캐릭터와" 첩 관계에 있는지를 확인합니다. 위에서는 any_courtier = { } 스코프 내에서 is_consort = ROOT 조건문을 사용했으므로, 모든 가신들 중에서 ROOT 캐릭터의 첩인 캐릭터들 이라는 조건이 되는 것입니다. 주의할 것은, 첩 "관계" 인지를 확인하는 것이므로, is_consort = ROOT 에서, ROOT 가 첩일 수도, 스코프 캐릭터가 첩일 수도 있습니다.

왜 is_consort는 is_consort = ROOT 로 바로 비교하고, spouse 는 spouse = { character = ROOT } 로 돌려 쓰는지 이유를 모르시겠다면, 바로 전 글을 다시 보고 오셔야 합니다. is_consort 는 조건문이고, spouse 는 해당 스코프의 배우자를 의미하는 스코프 지정 구문입니다. 앞에서 스코프는 직접 비교가 불가능하기 때문에 그 스코프 내에서 캐릭터/타이틀/프로빈스를 다른 스코프와 비교하는 방식으로 돌려서 비교한다는 사실을 설명드렸었습니다.

그런데, OR = { } 이라는 녀석이 있네요? 이건 항목을 나누어서 말씀드릴께요.

■ AND 와 OR

바로 앞에서, limit 안쪽의 조건을 모두 충족한다면. 이라고 말했지요. 그 이야기는, CKII의 조건문의 조건들은 기본적으로 모두 AND 연산이라는 의미가 됩니다. 기본적으로 조건 섹션이나 조건문 안쪽의 모든 조건을 만족해야만 조건문이 참이 되고, 효과 부분이 실행될 수 있다는 거죠.

그러나, 항상 이런 종류의 조건만 정하는 건 아니죠. 예를 들면 A 이건 B 이건 어느 한 쪽만 만족하면 조건이 충족된 걸로 보고 구문을 실행하라고 할 수도 있잖아요? 이럴 때에 그 조건은 OR = { } 로 묶어줍니다. OR = { } 안쪽에 서술된 조건들은 그 조건들 중 하나만 만족하더라도 그 OR = { } 구문 전체가 참이 됩니다. 바로 위의 예를 다시 보죠.

  OR = {
    spouse = { character = ROOT }
    is_consort = ROOT
  }

ROOT 와 스코프의 캐릭터가 부부 관계이건 첩 관계이건 어느 한 쪽 조건만 만족하면 이 OR = { } 구문 안쪽은 모두 참이라는 겁니다.

제가 앞에서 기본적으로 조건문의 기본 연산은 AND 연산이라고 말씀드렸죠. 따라서 OR = { } 이 아닌 부분은 굳이 AND = { } 로 묶어줄 필요가 없습니다. 그렇다면, AND = { } 구문은 필요 없을 것 같죠? 그런데, 이게 필요할 때가 있습니다. 아래 예시를 한번 보실까요? potential = { } 구문 안쪽의 내용이기 때문에, if = { } 와 limit = { } 는 필요가 없다는 것은 이제 알고 계시겠죠?

  potential = {
    OR = {
      is_adult = yes
      AND = {
        NOT = { age = 16 }
        is_female = no
      }
    }      
  }

위 조건이 해석이 되시나요? OR = { } 구문으로 묶여 있기 때문에, 이 조건들 중 하나만 충족하더라도 조건은 충족됩니다. is_adult 조건문은 지난 회에서도 휙 지나갔지만, is_adult = yes 하면 해당 캐릭터가 성인일 것(=16세 이상)을 조건으로 하는 거죠. 그런데, 바로 다음에 AND = { } 구문으로 NOT = { age = 16 } 과 is_female = no 가 묶여 있습니다. NOT = { age = 16 } 은 전에 한번 말씀드린 적이 있는 미성년자를 판별하는 구문이고, is_female = no 는 여성이 아닐 것이니 남성 캐릭터만 조건을 충족하는 건데, 이게 AND = { } 로 묶여 있으니까, "남성"인 "미성년자" 라는 조건이 되겠죠? 즉 전체 OR 구문은 이렇게 읽게 됩니다. "성인이거나, 남성 미성년자이거나." 아시겠죠? 반드시 AND = { } 로 묶어야 하는 경우도 이와 같이 없지는 않습니다.

■ NOT = { }

드디어 나왔습니다. 앞에서도 간단히 설명드린 바 있는 내용이지만, 여기서 확실히 다지고 갑시다. NOT = { } 은 특정한 조건을 부정하기 위한 조건문입니다. 다음 예시를 보시죠.

effect = {
  ...(윗부분 생략)...

  if = {
    limit = {
      trait = cynical
    }
    random = {
      chance = 60
      add_trait = sympathy_pagans
    }
  }
  if = {
    limit = {
      NOT = { trait = cynical }
    }
    random = {
      chance = 50
      add_trait = sympathy_pagans
    }
  }

  ...(이하생략)...
}

위 예제는 원본의 conversation_decisions.txt 에서 뽑아온 것입니다. random = { } 구문은 chance 에 정한 확률로 내부의 명령어를 실행하는 것인데, 다음 회에 설명할 것이므로 그렇게만 아시고 일단 넘어갑니다.

주목할 부분은 두 개의 if 문에서 limit = { } 내부입니다. ROOT 가 Cynical 트레잇을 가지고 있다면 첫 번째 if 문 내부의 명령이 실행될 것이고, ROOT 가 Cynical 트레잇을 가지고 있지 않다면(trait = cynical 조건문의 부정) 두 번째 if 문 내부의 명령이 실행될 겁니다. 구조가 눈에 들어 오시나요? "조건을 충족하면 A, 충족하지 않으면 B" 라는 if ~ else 구문이 이런 식으로 (조금은 난삽하게) 구현이 되는 것입니다.

자, 조건이 하나인 경우에는 그럭저럭 if ~ else 의 구현이 되네요. 그런데, 조건이 여러 개이거나, 몇 단계의 스코프가 중첩되는 등으로 복잡한 경우에는, else 구문을 어떻게 구현하면 좋을까요?

위 예제에서는 NOT = { } 을 하나의 조건문에 대해서 사용을 했습니다만, 크게 사용할 수도 있습니다. 다음 예를 한번 보십시오.

NOT = {
  any_realm_province = {
    any_neighbor_province = {
      has_owner = yes
      NOT = { religion_group = christian } 
    }
  }
}

원본의 크루세이더 관련 이벤트(crusade_events.txt)중에서 가져온 예시인데(조건 부분입니다), 이 경우 가장 안쪽의 NOT = { } 은 관심사가 아니고, 가장 바깥에 크게 묶인 NOT = { } 이 관심사입니다. NOT = { } 구문 안쪽에 여러 개의 조건문이 존재하고 있는 거죠. 이 경우, 이 구문은 전체에 대한 부정으로 해석됩니다. 즉, NOT = { } 바로 안의 any_realm_province = { } 안쪽 구문들의 참/거짓을 판단한 후, 그걸 그대로 뒤집는다는 거죠. 만약 any_realm_province = { } 내의 구문이 참이라면, NOT = { } 에 의해 거짓으로 판단되며, 거짓이라면 참으로 판단되는 겁니다. 마치 다른 언어의 if 문에 이은 else 구문처럼 동작을 하는 것입니다.

저도 이걸 잘 몰라서, if 에 대응하는 else 의 지원이 미약하다는 불평을 내내 해왔었는데, 조건문 전체를 NOT = { } 안에 묶어서 밀어넣는 방식으로 전체 부정이 가능하다는 사실을 최근에야 알았습니다. 하아...

참고로, has_owner = yes 조건문은 프로빈스 스코프에서 사용되며 해당 프로빈스의 주인(백작)이 있는지 없는지를 판단하는 구문입니다만, 사실 거의 의미가 없는 구문이예요. CKII 에서 특정 프로빈스에 백작이 없게 되는 경우는 거의 생각하기 어렵거든요. religion_group 조건문은 캐릭터 또는 프로빈스 스코프에서 사용되며, 해당 캐릭터 또는 프로빈스가 특정한 종교 그룹에 속하는 종교를 믿고 있는가를 확인하는 조건문입니다. CKII 의 종교는 몇 개의 종교 그룹 아래에 여러 개의 종교들이 속해 있는 형태로 종교를 정의한다는 거, 게임을 해 보셨다면 알고 계실 겁니다.

■ 등호(=)의 의미

앞에서 한 번 지나가는 말로 설명드린 적이 있습니다만, 거의 모든 경우에 값을 숫자로 받는 조건문에서 등호의 의미는 크거나 같다, 즉 "이상"을 의미합니다. 조금 전에도 나이를 확인하는 조건문인 age = 16 이 나왔었죠. 이에 의하면 age = 16 이라는 조건문은 "16세" 라는 조건문이 아니라, "16세 이상" 이라는 조건문이 되는 겁니다.

그럼 반대로 16세 미만, 즉 미성년자를 조건으로 하려면 어떻게 하면 될까요? 바로 앞에서 했잖아요 편하게 NOT = { } 을 쓰면 됩니다. NOT = { age = 16 } 이라고 쓰면 되겠네요.

2회에서였던가요. 숫자값을 받는 문맥에서 등호가 "이상"이 아니라 "정확히 그 값"을 의미하는 단 하나의 예외가 있다고 했는데, 그것은 사용자 지정 변수를 비교하는 구문인 is_variable_equal = { } 구문입니다. 이 구문 내의 value = N 은 정확히 그 변수의 값이 N인 경우에만 참을 반환합니다. 나중에 변수 부분에서 이에 대해서 말씀드릴 기회가 있을 것이니, 그 때 마저 확인하도록 합시다.

■ 실행의 중단

조건에 따라서 이벤트의 실행을 중단시켜야 할 수도 있습니다. 이벤트 체인의 중단은 선택지를 통해서 구현이 가능했지만 하나의 이벤트 내에서 조건에 따라서 이벤트 실행을 중단시키는 것은 상당히 번거로운 절차를 거쳐야 했습니다. 이제는 break = yes 구문을 사용해서 이를 쉽게 해결할 수 있습니다.

effect = {
  FROM = {

    ...(생략)...

    if = {
      limit = {
        same_sex = ROOT
      }
      character_event = { id = WoL.104 days = 1 }
      break = yes
    }
    
    ...(생략)...
  }
}

이처럼 break 가 나온 시점에서 그 뒤의 내용은 더 이상 실행되지 않습니다. (같은 성별인지 확인하는 same_sex 조건문이 사용된 것이 눈에 띄는군요.) 예제는 사생아 오브 라이프Way of Life 에서 추가된 디시전의 일부인데, 이 뒤쪽에도 조건문이 계속 늘어서 있습니다만, 이 첫 번째 조건에서 걸리면 뒤쪽의 조건들은 아예 확인도 하지 않고 이 시점에서 디시전의 효과 진행이 중단됩니다.

■ 스코프 내의 갯수 조건 : count

이 내용을 스코프에서 설명할지 여기서 설명할지 굉장히 고민했는데, 조건에 관련된 부분이라 일단 이쪽으로 넘겼습니다.

예를 들면, 특정한 트레잇을 가지고 있는 가신이 몇 명이 넘는다면 어떤 이벤트를 실행하라 같은 이벤트가 가능할까요? 즉, 일정한 조건을 만족하는 사람 수를 세야 하는 거죠. 과거 버전에서는 이것을 사용할 수 있는 스코프가 대단히 제한적이었지만, 2.3.0 이후에는 모든 any_ 스코프 내에서 사용이 가능합니다. 형식은 다음과 같습니다.

any_ = {
  count = N
  ...조건...
}

등호의 의미는 "이상" 이라는 것은 바로 앞에서도 말씀드렸죠. 아래는 실제 예시입니다. 조건과 관련된 부분이니만치, 디시전의 allow 부분에서 따왔습니다.

allow = {
  is_adult = yes
  prisoner = no
  NOT = { trait = incapable }
  independent = yes
  prestige = 8000
  OR = {
    realm_size = 180
    custom_tooltip = {
      text = form_new_empire_requirement_tooltip
      hidden_tooltip = {
        any_demesne_title = {
          count = 3
          tier = KING
        }
      }
    }
  }
  wealth = 1000
}

샤를마뉴 이후에 추가된 디시전으로, 일정 조건을 충족하면 제국위를 생성할 수 있는 디시전인데, 주목할 부분은 any_demesne_title = { } 내부입니다. tier 는 작위의 등급을 정하는 조건문인데, 즉, 왕 작위를 3개 이상 가지고 있다면, any_demesne_title = { } 구문 전체는 참으로 취급됩니다.

그 외에 prestige, wealth 이 보입니다. piety 까지 포함해서, 참 편하게 쓸 수 있는 애들입니다. 조건문으로 쓰일 때와 명령어로 쓰일 때가 똑같거든요. wealth = 1000 이 조건 문맥에선 금화 1000 이상이고, 명령어 문맥에서는 wealth = 1000 하면 금화 1000 추가입니다. piety, prestige 모두 같습니다. 쓰기는 편한데, 읽기는 영 껄끄럽습니다.

그렇다면 count = N 조건문을 부정하려면 어떻게 해야 할까요? 예를 들면 5 이상이 아니라 5 미만을 조건으로 하려면? 이런 경우 NOT = { count = N } 이라고 쓰면 안 되고, 위에서 본 적이 있는 전체 부정으로 처리해야 합니다. 즉, count 구문이 포함되어 있는 해당 스코프 블록 전체를 NOT = { } 으로 묶어야만 합니다. 예를 들면 다음과 같습니다.

  trigger = {
    NOT = {
      any_courtier = {
        count = 5
        is_female = yes
        is_adult = yes
      }
    }
  }

위와 같이 쓰면, 가신 중 성인 여성이 5명 미만이 된 경우에 이 트리거를 만족하게 됩니다. 이 count 의 부정 구문은 원본에서는 사용된 적은 없습니다만, 정상적으로 동작합니다.

■ 부록: 조건문 목록

앞에서 보여드린 스코프 목록과 같이, CKII 내에서 쓸 수 있는 여러 가지 조건문들 역시 위키에 표로 잘 정리되어 있습니다. 표의 다른 부분은 앞에서 설명드린 것과 같고, 잘 보셔야 하는 부분은 Used from scope 와 Value type 입니다. 어떤 스코프 내에서 사용할 수 있는 조건문인지, 그리고 어떤 값을 받을 수 있는지(숫자를 받는지, 구를 받는지, Yes/No(BOOL)를 받는지)를 알지 못하면 사용을 못 하겠죠.

스코프와 마찬가지로, 이런 조건문들 역시 어떤 조건을 모드에서 처리할 수 있는지에 따라서 어떤 기능을 구현할 수 있는지 여부가 큰 영향을 받게 되므로, 위 링크의 도표는 한 번 정도 통독을 해 보시는 것이 좋을 것입니다.

다음검색
현재 게시글 추가 기능 열기

댓글

댓글 리스트
  • 작성자조매생 | 작성시간 15.02.13 좋은 내용 감사합니다. 혹시 조건문 설정에서 나이가 몇살이상 등등 캐릭의 신상으로 조건 설정하는 것도 가능할까요?
  • 답댓글 작성자tacitus 작성자 본인 여부 작성자 | 작성시간 15.02.13 가능합니다. 본문 중간의 조건문 목록 링크를 확인하시면 도움이 되겠네요.
댓글 전체보기

CK 모드 게시판 다른글

현재페이지 1234
맨위로

카페 검색

카페 검색어 입력폼