|
이상은 코드를 이해하기 위해 설명드린 것이고, 중요한 건, 사용자가 아직 option = { } 의 선택지를 선택하지 않은 상태에서 이미 새로운 캐릭터가 생성되었다는 점입니다. 반드시 이런 식으로 작성해야 할 필요성이 있는 경우는 사실 많지가 않지만, 이런 식으로 쓰기도 한다는 사실을 알아두시는 것이 좋습니다.
혹은, immediate = { } 섹션 내에서 어떤 조건문을 실행시켜서 플래그를 세팅한 경우, 해당 플래그를 바로 옵션에 대해서 사용하는 것도 가능합니다. 다만 조금 불안정해요.
7회에서 hide_window = { } 를 살펴본 적이 있었죠. 어떤 이벤트를 실행하기 위한 조건과 그 이벤트의 실행 구문을 담은 트리거 이벤트의 경우 hide_window = { } 구문을 사용하여 숨은 이벤트로 만드는 것이 가능하다고 말씀드린 적이 있습니다.
혹시 이 내용도 기억하시나요? 이렇게 숨은 이벤트를 생성하는 경우에, option = { } 은 하나만 설정하고, 그러면 그 하나의 선택지를 자동으로 선택해 실행하고 이벤트를 종료한다고 말씀드렸었습니다.
그런데 말입니다. 이 경우에 option = { } 대신 immediate = { } 섹션을 사용하면 어떨까요? 즉, option = { } 내에 실행할 명령어를 입력하는 것이 아니라, immediate = { } 섹션 내에 실행할 명령어를 입력하면 어떨까 하는 것입니다. 가능하죠? 어차피 immediate = { } 도 모든 조건을 만족해야 실행되는 건 똑같으니, 실질적으로 숨은 이벤트의 단독 option = { } 내에 쓰는 것과 이 경우에는 아무런 차이가 없습니다. 이해 되시나요?
자, 여기서 재미있는 문제가 하나 발생합니다. 아래의 두 코드를 한번 보십시오.
character_event = { id = WoL.1100 hide_window = yes ...(중간생략)... immediate = { random_lover = { limit = { NOT = { reverse_opinion = { who = ROOT value = 25 } } } character_event = { id = WoL.1101 } } } }
character_event = { id = TOG.3403 hide_window = yes ...(중간생략)... immediate = { spouse = { character_event = { id = TOG.3400 days = 3 } } } option = { name = OK } }
위의 두 개의 이벤트는 모두 원본에 존재하는 hide_window = yes 설정의 트리거 이벤트로, Pre-Trigger, trigger = { } 및 MTTH 부분을 생략하고 실행 부분만 남겨놓은 것입니다. 두 이벤트의 구조적 차이를 아시겠어요? 기본 지식이 0 이라고 했을 때, 독자분들께서 현재까지 제가 알려드린 정보만을 놓고 판단한다면 두 이벤트 중 하나는 (치명적) 오류가 발생하는 이벤트입니다. 위와 아래 중 어떤 이벤트가 오류가 발생하는 이벤트일까요? 네. 위 이벤트죠. 어째서? 제가 말씀드렸죠? option = { } 은 어떤 이벤트든 반드시 하나 이상 존재해야 선택지를 선택하고 이벤트를 끝낼 수 있다고요. option = { } 이 하나도 존재하지 않는 이벤트를 만나면, 고를 선택지가 없어서 이벤트를 종료할 수 없기 때문에 게임을 더 이상 진행할 수 없는 문제가 발생한다고도 말씀드렸을 겁니다. 위의 두 이벤트 중 위쪽의 이벤트가 딱 그런 상황 아닙니까?
실제로 과거 버전에서는, immediate = { } 와 hide_window = yes 를 가진 트리거 이벤트의 경우도 아래쪽의 이벤트와 같이 설정했었습니다. 즉, 아무런 역할도 하지 않는 option = { } 을 형식적으로 하나 만들어 두는 거죠. 실제 필요한 코드의 실행은 immediate = { } 내에서 다 이루어졌고, 하나 존재하는 option = { } 섹션은 오직 이 이벤트를 끝내기 위해서 존재하는 것이었습니다.
그런데, 최근의 버전에서 작성된 이벤트들을 보면, immediate = { } 와 hide_window = yes 를 가진 트리거 이벤트의 경우에 위쪽 이벤트의 경우처럼 아예 option = { } 섹션을 완전히 생략한 채로 작성되는 경우가 많습니다. 원래대로라면 이 경우 이벤트가 종료되지 않아 오류가 발생해야 하지만, hide_window = yes 옵션으로 숨은 이벤트의 경우에 option = { } 이 하나도 없더라도 자동으로 이벤트가 종료되도록 시스템을 수정한 것입니다. 즉, 최근의 버전에서는 위처럼 써도, 아래처럼 써도 상관 없습니다.
언제부터? 정확히는 모르겠습니다. 위의 두 예 중 위 것은 Way of Life DLC 에서 추가된 이벤트이고, 아래 것은 The Old Gods DLC 에서 추가된 이벤트니까, 최소한 The Old Gods DLC 시기까지는 위와 같이 작성하는 경우에 오류가 발생했다고 말할 수 있을 것 같네요. 2.1.4 버전에서 추가된 Raja of India DLC 에서 추가된 이벤트들의 경우에도 위와 같이 작성한 예가 많은 것으로 보아, 현실적으로 현재 사용되는 거의 모든 CKII 버전에서는 위와 같이 쓰든 아래와 같이 쓰든 상관 없다고 말씀드릴 수 있겠습니다.
그 때 말씀드린, option = { } 이 하나도 존재하지 않는 이벤트의 예가 바로 이겁니다.
힘드시죠? 이제 선택지 전에 나올 수 있는 구조적 문제들은 다 살펴봤으므로, 선택지에 관해서 집중적으로 파 보도록 하겠습니다.
바로 앞에서 말씀드렸다시피, hide_window = yes 옵션이 존재하지 않는다면, 선택지는 언제나 최소한 하나는 보이도록 해야 합니다. 그래야 이벤트를 계속 진행하거나 종료할 수 있습니다. 만약 이벤트가 떴는데 고를 수 있는 선택지가 없다면 게임을 더 이상 진행할 수 없습니다. 초대형 버그입니다 그건.
플레이어만이 선택하는 경우에, 일반적인 option = { } 의 구조는 다음과 같습니다.
option = { name = EVTOPTA_TST_1004 ...여기에 이 선택지를 선택했을 때에 발생할 효과를 적는다... }
가장 간단한 옵션은 이렇게 작성하면 됩니다. 다른 개체와 마찬가지로, option = { } 의 name 값은 언어 파일에서 메시지 ID 로 사용합니다. 즉, 언어 파일에서 EVTOPTA_TST_1004 뒤에 적혀 있는 메시지가 선택지의 내용으로 출력된다는 이야기예요. 이미 말씀드린 내용이죠?
그리고, 다시 강조드리자면, option = { } 의 갯수는 몇 개가 되든 상관 없지만, 출력되는 것은 위에서 4개까지라는 것, 잊지 마시기 바랍니다.
선택지를 플레이어가 선택하는 경우 이외에, AI 가 선택해야 하는 경우도 발생할 수 있습니다. 선택지가 하나인 경우에는, AI 도 알아서 존재하는 하나를 선택하고 넘어갑니다. 그럼 두 개 이상인 경우에는 어떻게 할까요? 플레이어라면 내용을 읽어보고 고르고 싶은 걸 고를 텐데, AI가 한글(영문)을 읽을 능력이 되는 것도 아니고, 뭔가 선택의 기준이 될 만한 게 있어야 하지 않겠어요?
그래서 준비했습니다. 선택지가 여러 개인 경우에, AI 의 선택 경향을 제어합니다.
option = { name = EVTOPTA_TST_1004 ai_chance = { factor = 30 modifier = { factor = 3.0 mother = { NOT = { health = 3.1 } } } } ...여기에 이 선택지를 선택했을 때에 발생할 효과를 적는다... } option = { name = EVTOPTB_TST_1004 ai_chance = { factor = 20 modifier = { factor = 1.6 trait = lustful } modifier = { factor = 0.5 trait = chaste } } ...여기에 이 선택지를 선택했을 때에 발생할 효과를 적는다... } option = { name = EVTOPTC_TST_1004 ai_chance = { factor = 50 } ...여기에 이 선택지를 선택했을 때에 발생할 효과를 적는다... }
예제만 보셔도 대략 감이 잡히시죠? ai_chance = { } 내의 factor 수치는 AI 의 기본적인 선택 경향을 나타냅니다. 반드시 합 100을 맞추실 필요는 없습니다. 알아서 전부 합산해서 개별 수치/총합 수치로 계산한 후 최종 확률을 도출해 내니까요. (다만, 플레이어가 알기 쉬우려면 100 맞추는게 더 좋을 수도?)
주목할 만한 것은, 다른 경우와 마찬가지로 여기에도 modifier = { } 구문을 사용할 수 있다는 겁니다. 선택하는 AI 캐릭터의 개별적인 성격에 따라 선택 확률에 변화를 주는 처리를 할 수 있겠죠. 예를 들어, 위의 경우에서 만약 해당 캐릭터가 음탕한(lustful) 성격을 가지고 있다면, 세 개의 선택지의 선택 확률은 30/100, 20/100, 50/100 에서 30/112, 32/112, 50/112 으로 그 확률이 변화합니다. A, C의 선택 가능성은 함께 줄어들고, B를 선택할 가능성이 더 높아지게 되는 거죠. 특히 A보다 B를 선택할 가능성이 높아져서, A와 B의 선택 가능성이 역전되었습니다.
당연한 이야기지만, factor 값은 곱해지는 값이기 때문에, 적용되는 factor 중 어느 하나가 0 이라면, 해당 선택지는 선택 가능성이 0이 됩니다. 또한, modifier = { } 내의 조건은 모든 조건문들을 사용하여 다양하게 구성할 수 있습니다.
random_list = { } 구문을 설명하면서, 제가 CKII 의 랜덤 확률을 신뢰하지 않는다는 사실을 말씀드렸었죠? 예를 들면, 이벤트 체인에서 random_list = { } 가 여러 번 중첩되는 경우, 첫 번째 이벤트 선택 이후에 선택되는 랜덤 항목은 정말 뚜렷한 경향성이 나타납니다. 분명히 C 선택지가 B 선택지보다 확률이 높은데도, C는 죽어도 안 나오고 B만 주구장창 선택하고 있어요.
그래서 필자는 상대방이 있는 이벤트의 경우에, 위와 같이 AI가 ROOT인 이벤트를 하나 더 만들어서 AI 에게 선택지 중 하나를 선택하여 다시 플레이어에게 넘기게 만드는 방식으로 랜덤 이벤트를 구현하곤 합니다. 과거 2.1.6 시절에는 random_list = { } 의 확률에 조건을 주어 변화시키는 것이 불가능했기 때문에, 이와 같이 만들면 상황에 따라 확률에 변화도 줄 수 있었지요. 지금은 random_list = { } 도 확률에 변화를 줄 수 있기 때문에 꼭 그래야 하는 이유는 사라졌지만, 여전히 random_list = { } 가 만들어내는 확률의 장난에는 신뢰가 가지 않습니다.
사실 완벽한 랜덤이라는 것은 존재하지 않습니다. 컴퓨터가 정말 지능을 갖게 되지 않는 한 그건 불가능해요. 다만 거기에 근사하게 접근해갈 뿐이죠. 완벽한 랜덤에 다가가면 다가갈수록, 컴퓨터의 연산량은 정말 엄청나게 늘어나게 됩니다. 안 그래도 연산량이 많아서 버벅대는 CKII인데, 랜덤 함수를 정교하게 짜면 짤수록 더욱 빡빡해질 게 불 보듯 뻔하니, 이에 대해서 개선을 요구하기도 좀 그렇습니다.
이벤트의 발생 조건을 설정했던 trigger = { } 를 option = { } 내부에도 사용할 수 있습니다. 즉, 해당 트리거의 내용을 충족했을 때만 그 선택지가 보여지게 되는 거죠.
option = { name = EVTOPTA_TST_1004 trigger = { father = { is_alive = yes character = FROM } } ai_chance = { factor = 30 modifier = { factor = 3.0 mother = { NOT = { health = 3.1 } } } } ...여기에 이 선택지를 선택했을 때에 발생할 효과를 적는다... } option = { name = EVTOPTC_TST_1004 ai_chance = { factor = 50 } ...여기에 이 선택지를 선택했을 때에 발생할 효과를 적는다... }
A 선택지에 trigger = { } 가 들어갔죠. 현재 이 선택을 하는 AI 의 아버지가 살아 있고(is_alive = yes), 그 캐릭터가 FROM 인 경우에만 이 선택지를 보여준다 라는 것입니다. 여기선 AI 의 예이지만, 선택하는 자가 플레이어라도 똑같이 동작합니다. 플레이어의 살아 있는 아버지로부터 날아온 이벤트가 아니라면 저 선택지 A 는 플레이어에게 보여지지 않아요.
이 trigger = { } 를 이용하면 상황에 따라서 완전히 다른 선택지를 보여주게 할 수도 있습니다. 예를 들어 option = { } 을 8개 만들어두고, 트리거로 4개씩 묶어서 상황에 따라 선택지 묶음 1, 선택지 묶음 2를 보여줄 수도 있다는 이야깁니다.
다만 주의할 점은, 모든 선택지를 trigger = { } 로 묶을 때는 그 조건의 설정을 굉장히 조심하셔야 합니다. 만에 하나 선택지 설계가 잘못되어 특정한 경우에 선택지가 하나도 출력되지 않는 결과가 나온다면 게임 진행이 더 이상 불가능한 초 대형 버그가 되기 때문입니다.
하나의 이벤트에서 이전의 결과에 따라서 다른 메시지를 보여줄 수 있을까요? 예를 들어서 앞에서 어떤 선택을 랜덤으로 했을 때, 그 결과에 따라서 다음 이벤트에서 서로 다른 메시지를 보여줄 수 있다면, 성공과 실패에 따라서 이벤트를 따로 만들어야 하는 수고를 줄일 수 있을 겁니다. 이게 2.2.x 까지는 불가능했습니다만, 2.3.x 부터는 가능해졌습니다. 이벤트의 desc 항목과 option 의 name 항목을 구로 구성할 수 있게 됐거든요.
다음 예시를 보시죠.
character_event = { id = TST.1004 desc = EVTDESC_TST_1004 picture = GFX_evt_feast option = { name = EVTOPTA_TST_1004 ...여기에 효과를 적는다... } }
7회에서 보여드린 캐릭터 이벤트의 구조 예제를 그대로 가져왔습니다. 이 이벤트가 플레이어에게 실행된다면, 플레이어에게 보여지는 메시지는 EVTDESC_TST_1004, 플레이어에게 보여지는 선택지의 메시지는 EVTOPTA_TST_1004 일 겁니다.
같은 이벤트를 다음과 같이 구성해 보겠습니다.
character_event = { id = TST.1004 picture = GFX_evt_feast desc = { text = EVTDESC_TST_1004_1 trigger = { FROM = { trait = chaste } } } desc = { text = EVTDESC_TST_1004_2 trigger = { FROM = { trait = lustful } } } option = { name = { text = EVTOPTA_TST_1004_1 trigger = { FROM = { trait = chaste } } } name = { text = EVTOPTA_TST_1004_2 trigger = { FROM = { trait = lustful } } } ...여기에 효과를 적는다... } }
이제 슬슬 읽어 지시죠? 이 이벤트를 던진 자가 정숙한 인간인지 음탕한 인간인지에 따라서, 출력되는 메시지와 선택지의 메시지가 서로 다르게 나가는 겁니다. 정숙한 자라면 이벤트 메시지로는 EVTDESC_TST_1004_1, 선택지 메시지로는 EVTOPTA_TST_1004_1 이 나가겠죠. 반대로 음탕한 자라면 이벤트 메시지로는 EVTDESC_TST_1004_2, 선택지 메시지로는 EVTOPTA_TST_1004_2 가 출력될 겁니다.
주의할 점은, 이것은 이벤트와 선택지의 출력 메시지를 바꿀 수 있는 것입니다. option = { } 의 경우에 선택의 결과까지 서로 다르게 하고 싶다면 이것만으로는 안 되겠죠. 효과 부분에서 추가적으로 if 조건문을 쓰거나, 아니면 아예 option = { } 을 트리거를 이용해서 서로 다르게 구성하는 방법을 써야 합니다. 단지 메시지만 다르게 보이게 할지, 메시지와 결과가 모두 달라지게 해야 할지에 따라서 방법론을 다르게 가져가야 합니다.
또한, 아쉽게도 picture 에 대해서는 이런 선택 방법이 존재하지 않습니다. 만약 결과에 따라서 상단 이미지까지 다르게 가져가야 한다면, 과거처럼 독립된 이벤트로 짜야 합니다.
네. 이것으로 이벤트의 구조에 관한 내용을 모두 살펴봤습니다. 그리고, 이벤트를 실질적으로 작성하는 기본적인 방법론까지도 함께 설명드렸습니다. 이제 여러분께 조건문과 명령어의 레퍼런스만 주어진다면, 시간은 걸릴지라도 원하는 이벤트를 충분히 구현하실 수 있는 실력을 갖추신 것입니다.
다만, 아직 이벤트와 관련하여 다루어야 할 내용이 조금 남아 있습니다. 이런 내용들을 다음 회에서 마저 살펴보도록 하죠.