예전에 폴아웃에 좀 길게 적었었는데

스림에 와보니까 스크립트 써보려는 사람 꽤 있길래

볼 사람 있나 해서 요기에도 한번 새로 적어봄

스림은 폴아웃4 보다 함수가 적어서 좀 번거로울때가 있네


굉장히 기니까 흥미 없는 사람은 패스하고

난 가능하면 SSEEDIT만 쓴다 크킷으로 스크립트 넣으면 암 걸려

목차 적어놓을테니까 보고 컨트롤 f 로 찾아서 봐


목차

1. 스크립트를 만들기 전 준비물

2. 스크립트를 Form에 넣기

3. 실제로 스크립트 작성하기 - 스크립트 코드 만들 준비하기

4. 실제로 스크립트 작성하기 - Property 작성하기

5. 실제로 스크립트 작성하기 - 함수 작성하기

6. 실제로 스크립트 작성하기 - 이벤트를 넣어 함수 실행시키기

7. 스크립트 관련 기초 정보

8. Persistent 와 Temporary

9. Quest Alias

10. Function

11. Function 과 Event 에서 Return 사용하기

12. 변수 만들기와 변수를 만드는 위치

13. 위키에 적힌 함수 사용법과 변수 사용하기

14. 다른 스크립트에 있는 변수나 Function 사용하기

15. Global

16. as 로 유형을 지정하는법

17. as 로 다른 스크립트 연결하기

18. as 를 if 조건문에 사용하기

19. as 가 실제로 하는일

20. 배열과 폼리스트

21. While 을 이용한 배열과 폼리스트 응용

22. 횃불 떨어트리는 모드의 스크립트 예제

23. Spell 을 이용해 Conditions 를 조건으로 스크립트 실행하기

24. Perk 과 Entry Point

25. Message 에 숫자 변수 외에 문자 변수 집어넣기

26. Champollion 의 디컴파일 오류



1. 스크립트를 만들기 전 준비물

SSEEDIT 랑 Creation Kit 가 필요함

쎄딧은 뭐 다 있을거고 크킷은 스팀에서 받으면 된다

크킷파일은 스림 폴더에 깔리니까 일단 스팀에서 한번 실행한 다음에

뭐 세딧이나 바슬처럼 MO2에서 경로 지정하고 mo2에서 실행하면 돼

처음 실행하면 뭔 압축파일 어쩌고 물어보는데

스크립트를 만들때 필요한 소스파일들이니까 ok 누르면 됨


CK툴이 MO의 플러그인(모드) 목록을 표시하지 않을 경우 대처법 - 툴리우스 채널 (arca.live)

mo2에서 실행 안되거나 문제 있으면 이 글을 참고하고

스크립트에 쓸 함수가 정리되어 있는 Category:Papyrus - Creation Kit 이 홈페이지를 즐겨찾기 해



2. 스크립트를 Form 에 넣기

세딧으로 esp 열어보면 뭔 퀘스트라거나 포션이라거나 이런게 있는데

이런 레코드 하나하나를 Form 이라고 부르겠음


일단 게임에 접속하면 플레이어 인벤에 셉팀 1000개를 넣어주는 모드를 만들어볼게

물건이나 마법에 직접 작동하는게 아니고 이런식으로 뭔가를 하는건

퀘스트에 스크립트를 넣어서 관리하는게 편하다

일단 esp를 만들고 퀘스트를 만들어볼게


세딧 연다음에 Skyrim.esm에서 Quest 누른다음에 아무 퀘스트나 우클릭해서

Copy Override Info 누르고 new file esp 를 눌러서 esp를 만들수 있고

그러면 만들어진 esp에 퀘스트가 복사되어 있는데 del 눌러서 지워버리고

Quest에 우클릭하고 Add 눌러서 새로 만들면 된다


EditorID에 대충 알아보기 쉽게 이름을 짓고

바로 아래에 VMAD 어쩌고 오른쪽에 우클릭을 해서 스크립트를 추가하고

EditorID는 뭐 다른 모드랑 겹쳐도 상관없지만 스크립트 이름은 겹치면 큰일나니까

알아보기 쉬우면서도 겹치지 않게 모드의 약어를 앞에 적으면 좋다

폴아웃에는 폴더에도 넣을수 있는데 확인해보니 스림은 안되더라 잘못 적어놔서 미안



3. 실제로 스크립트 작성하기 - 스크립트 코드 만들 준비하기

일단 세딧을 저장하고 끄면 overwrite 폴더에 지금 만든 TestESP.esp 가 만들어 졌으니

그걸 잘라내기 해서 MO2의 mod 폴더에 폴더를 새로 만든다

난 금화 1000개 테스트 라고 함

그 다음에 그 폴더에 잘라내기한 esp를 넣고 mo2에서 f5를 눌러서 새로고침


그럼 요렇게 방금 만든 모드폴더 이름이 맨 밑에 뜨니까 거기에 체크를 한 다음에

오른쪽 배열에 TestESP.esp 도 체크가 되어있나 확인하고


그 모드폴더에 Scripts 폴더와 Source 폴더를 만들고...

Source 폴더안에 다시 Scripts 폴더를 만든다음 그 안에 스크립트 소스파일을 만들거임

메모장 파일을 새로 만든다음 TQ_TestQuestScript.psc 라는 이름으로 변경하고 실행



4. 실제로 스크립트 작성하기 - Property 작성하기

스크립트의 맨 윗줄에는 항상

ScriptName "스크립트 이름" Extends "스크립트를 넣은곳의 유형"

이 들어간다 우린 Quest에 넣을 스크립트를 만들거니까 열린 메모장의 맨 위에


ScriptName TQ_TestQuestScript Extends Quest


그다음에 플레이어에 셉팀 1000개를 넣고 싶으니까

플레이어 와 셉팀 을 이 스크립트가 알아들을수 있게 지정해야함

여기가 굉장히 중요한 부분이니까 잘보셈

일단 세딧으로 아까 esp를 열고 스크립트 건 Quest로 간다음에


만든 스크립트의 Properties 에 우클릭 하고 Add 한다음에

Type을 Object로 하고 밑에 나타나는 NULL에 Edit을 해서

거기에 스크립트에 쓸 Form을 지정한다

우린 셉팀이랑 플레이어가 필요하니까 플레이어는 14 셉팀은 f 라고 적고

그다음에 Object 위에 스크립트에서 쓸 이름을 적는다


난 플레이어는 pPlayerRef 셉팀은 GoldGold 이라고 지음

이름은 같은 스크립트에서 겹치지만 않으면 되니 대충 알아보기 쉽게 짓는다


14?? f?? 뭐라는거야 씹덕새끼야 라고 할수 있는데

이게 플레이어랑 셉팀의 FormID임

만일 스크립트에서 알두인을 지정하고 싶다면

세딧의 NPC탭에서 알두인을 찾아서 FormID가 0008E4F1

라는걸 알아낸 다음 그걸 NULL에 적으면 된다


여튼 플레이어랑 셉팀을 esp에서 Property로 했으니까 다시 코드를 짬

Property를 적는 방식은


"Form의 유형" Property "esp에 적은 이름" Auto


자세하게 적으면 좀 다르긴 한데 대충 이게 기본임

"esp에 적은 이름"은 당연히 pPlayerRef 나 GoldGold 가 될거고

"Form의 유형" 은... GoldGold 는 Misc. Item에서 가져왔으니 MiscObject 다

처음 하면 "Form의 유형" 에 대체 뭘 적어야 할지 헷갈리는데

뭐 그냥 다른 스크립트 배끼면서 외우는 수 밖에 없다 미안

pPlayerRef 는 플레이어의 참조...

플레이어의 기본 정보를 복사해서 실제 게임에 구현한 놈이니까 Actor가 된다


컴파일에 에러가 안뜨고 잘됐는데 스크립트가 작동을 안한다면

스크립트 초보의 경우 십중팔구는 이 "Form의 유형" 을 다른걸 적은 경우다

여튼 Property를 코드에 적자


ScriptName TQ_TestQuestScript Extends Quest


MiscObject Property GoldGold Auto

Actor Property pPlayerRef Auto


일단 기본 준비가 끝났다 esp의  TQ_TestQuest 에는  TQ_TestQuestScript 스크립트가 걸려있고

이 스크립트에서 GoldGold 라고 적으면 셉팀을 말하는거고

pPlayerRef 라고 적으면 화면에서 뛰어놓고 있는 내 캐릭터를 말하는게 됐다

esp를 저장하고 닫자



5. 실제로 스크립트 작성하기 - 함수 작성하기

이제 함수를 써서 플레이어에게 셉팁을 1000개 집어넣으면 된다

Category:Papyrus - Creation Kit 에서 찾아보면 아이템을 더하는 함수는 AddItem이라는 함수다

 AddItem - ObjectReference - Creation Kit 여기서 직접 보자

맨 윗줄에 크게 적힌 

AddItem - ObjectReference

은 이 함수가 ObjectReference 를 목표로 사용되는 함수라는 뜻이다


ObjectReference 는 참조라는 건데...

Container - 상자, Activator -활성화장치 등의 BaseObject를

맵에 뿌려놓은 것을 ObjectReference 라고 부른다

던전 깨면 있는 왕상자 하나하나 전부 다 만들면 귀찮잖아

왕상자라는 기본 상자를 만들어놓고 그걸 복사해서 맵에 뿌리면

뿌려진 그게 바로 ObjectReference 가 된다


시발 플레이어는 Actor 인데??? 라고 할수 있겠지만 다른 참조와 다르게...

엑터의 BaseObject는 ActorBase 라고 부르고 엑터베이스의 참조는 Actor 라고 부른다

아무래도 사람은 물건하고 달리 죽거나 살거나 걸어서 움직이거나 하는게 더 있으니까

여튼 ObjectReference 에게 사용하는 함수는 Actor 에게 사용할 수 있다


이건 이 함수의 구조를 알려주는거다 함수를 쓰려면

AddItem(무언가 Form을, Int라는 정수를, bool이라는 true false를) 적어야하며

대체 여기에 적는게 뭔 의미 인지는 바로 아래의 Parameters에 적혀있다

일단 우린 플레이어에 아이템을 넣을거니까 이렇게 적는다


pPlayerRef.AddItem(GoldGold, 1000, false)


함수의 앞에 . 을 찍고 적용할 대상을 적으면 된다 우린 플레이어를 적고...

Syntax와 Parameters에 따르면 괄호 처음에는 더하고 싶은 물건을 적으라고 되어있으니

거기에 GoldGold를 적고...

그다음에는 Int - 정수를 적으라는데 Parameters에는 이게 바로 아이템의 갯수라고 한다

그러니까 1000을 적고...

그 다음은 아이템을 얻을때 메세지를 표시안할지 아니면 할지를 정하는 칸이라고 한다

여긴 false를 적으면 표시를 안하지 않으니까 표시가 된다


Parameters에는 Default 값이 있는 경우가 있는데 안적으면 그 값이 기본이 된다

pPlayerRef.AddItem(GoldGold) 라고 적으면 셉팁 한개를 표시가 되게 라는 뜻이 된다


여튼 pPlayerRef.AddItem(GoldGold, 1000, false) 을 적어서

플레이어에게 셉팁 1000개를 더하겠다 라는 함수를 작성했는데....

함수를 실행을 시켜야 셉팁이 들어오게 된다

무언가의 조건이 됐을때 명령을 실행하는 함수를 Event 라고 한다

이벤트를 걸어서 함수를 실행하게 해보자



6. 실제로 스크립트 작성하기 - 이벤트를 넣어 함수 실행시키기

이벤트도  Category:Papyrus - Creation Kit  여기에 다 있다

On 으로 시작하는게 이벤트임

이벤트 종류가 많은데 여기서 필요한걸 찾아서 써야한다

스림에서 퀘스트에 바로 쓸 이벤트는 OnInit 밖에 없다


OnInit - Creation Kit 에 따르면 이 이벤트는 스크립트가 세팅되면

바로 그걸 수신해서 단 한번 실행시키는 이벤트라고 한다


OnInit은 이벤트 함수이고 괄호 안이 비어 있으니까

Event OnInit() 이라고 적으면 된다 그 다음에 우리가 만든 함수를 집어넣자

전체적인 스크립트는 이렇게 된다


ScriptName TQ_TestQuestScript Extends Quest


MiscObject Property GoldGold Auto

Actor Property pPlayerRef Auto


Event OnInit()

    pPlayerRef.AddItem(GoldGold, 1000, false)

EndEvent


이제 TQ_TestQuestScript 스크립트가 esp에 들어간게 감지되면...

그러니까 새회차를 하거나 기존 세이브를 로드하면

바로 Event OnInit() 이 그걸 알아채고 additem 함수를 실행할것이다


근데 퀘스트에 Event OnInit() 을 걸면 두번 실행되는 문제가 있다

그러니까 셉팁이 1000개씩 두번 들어오니 그걸 막아야함... 다시 esp를 켠다음에


일단 Quest를 만들었다면 Run Once를 하지 않더라도

반드시 add 를 눌러서 General 칸이 작성되게 해야 한다 안그럼 아마 ctd 날듯

여튼 Run Once는 이 퀘스트를 한번만 실행하겠다는 뜻이다


그 외에 Start Game Enabled라고 해서 게임을 켜면

바로 퀘스트를 시작하겠다는 플래그가 있지만

OnInit은 스크립트가 준비되면 바로 신호를 수신하는 이벤트라서

퀘스트가 시작되든 말든 상관이 없으니 이 테스트 모드에선 체크하지 않아도 된다


그럼 이제 다시 세딧을 닫은 다음에 크킷을 켜자


Gameplay의 Papyrus Script Manager 클릭


Filter에 스크립트 이름을 적고 나온 파일에 우클릭 한다음 컴파일 한다

앞에 약어를 적어두면 모드에 스크립트가 여러개 들어갈때 찾기 편하다


스크립트 코드에 뭔 문제가 있으면 이런식으로 Failed 가 뜨면서 컴파일이 되지 않음

우리가 짠 코드에 Actor Property pPlayerRef Auto 를 일부러 지우고 컴파일 한건데

컴파일러가 pPlayerRef.AddItem(GoldGold, 1000, false) 에서

pPlayerRef 가 대체 뭔지 알수 없어서 컴파일 못해먹겠다 라는 에러를 보내고 있다


당연히 우리가 짠 스크립트는 문제가 없으니 이런 창이 잠시 떴다가 바로 사라진다

뭔가 창이 떴다가 바로 사라지면 컴파일이 성공해서 파일이 만들어졌다는 것이다


overwrite의 Scripts 폴더에 가면 우리가 만든 스크립트 이름으로 pex파일이 있으니

그걸 잘라내기 한 다음에 우리 모드폴더의 Scripts 폴더에 넣는다


이제 게임을 켜고 로드하거나 새회차를 시작하면

우리가 만든 esp에 있는 TQ_TestQuest 퀘스트에 집어넣어둔

TQ_TestQuestScript 스크립트에 적힌 Event OnInit() 이 발동되서

EndEvent 사이에 있는 함수를 실행하게 된다


요렇게 게임을 켜면 셉팀이 들어온다 콜로서스 팩에 깔린 모드 떄문에

셉팀 1000개 획득 이라는 글이 아니라 온라인 게임 스러운 창이 뜨고 있음



7. 스크립트 관련 기초 정보

같은 스크립트라도 각각 Form 에 따라 다른 스크립트로 취급된다

나중에 변수에서 설명되어 있는 이야기지만 스크립트에 정보들을 변수로 저장할수 있다

만약 TestScript 라는 스크립트가 있고 이 스크립트가 적용된 Actor 가 쓰러질때마다

그 횟수를 +1 씩 더해서 기록한 다음 10번을 채우면 쓰러지지 않고 완전히 죽는다고 하면...


리디아와 암사자 묠 둘에게 이 TestScript 스크립트를 걸었을때

리디아가 4번 묠이 6번 쓰러졌다고 해서 10번을 채우고 다 죽는것이 아니라

폼마다 별개의 스크립트 이므로 리디아는 6번 묠은 4번 더 쓰러져야 죽게 된다


게임상에 보이는 물건 드래곤 나무 등등 모든것은 ObjectReference 이다

그리고 그 물건의 원본을 BaseObject 라고 부른다

ObjectReference 로 게임상의 화면에 존재하려면 반드시 BaseObject 가 있어야한다


BaseObject 에 걸린 스크립트는 그 BaseObject 로 ObjectReference 를 만들면

자동으로 상속되어 적용된다

5번 열면 플레이어를 죽이는 스크립트를 Container 유형에 걸었다면 가장 윗줄에


ScriptName TeteScript Extends ObjectReference


라고 마지막에 Container 가 아니라 ObjectReference 를 적어야 한다

스크립트를 건 곳은 Container 지만 맵에 뿌려져 있는 상자는 ObjectReference 이기 떄문이다


물론 마찬가지로 이런 상자를 5개를 뿌렸다고 하면 스크립트는 별개로 작동하므로

상자를 한개당 한번씩 열어도 아무일도 일어나지 않고 특정 하나의 상자를 5번 열면 죽게 된다



8. Persistent 와 Temporary

ObjectReference 를 대상으로 함수를 썼는데 발동되지 않을때가 있다

예를 들어서 어느 던전의 왕상자에 셉팀 1000을 넣으라는 스크립트를 만들고

분명히 컴파일도 됐는데 아무리 게임을 켜서 실행해도 안되는 경우이다

이건 바로 Persistent 와 Temporary 플래그 때문이다


Temporary 속성의 ObjectReference 는 플레이어가 그 근처에 있지 않을땐

메모리상에 로드 되어있지 않기 때문에 esp와 스크립트에 Property 를 지정해도

대체 무슨 물건인지 찾을수가 없어서 함수가 정상적으로 실행되지 않는다


이 Property 는 스크립트가 준비된 그 순간에 지정이 끝나기 때문에...

만일 내가 무한히 마실수 있는 포션을 만들었고 그 포션을 마시면

어딘가의 상자에 셉팀 천개가 들어가는 스크립트를 만들어 넣었는데

아무리 마셔도 그 상자에 셉팀이 들어가지 않는다

왜냐하면 Temporary 속성의 참조는 플레이어가 그 동네에 있어야 로딩되기 때문이다


어느 상자에 셉팀 천개를 넣으려면 모드를 깔고 처음 세이브를 로드할때

플레이어가 그 상자와 같은 곳에 있어야 한다는 말이다

이미 스크립트가 등록 되는 순간에 Property 지정이 끝났기 때문에

나중에 그 상자 앞에 가서 포션 마셔도 여전히 셉팀이 들어가지 않는다


이런 문제는 플래그에 Persistent 속성을 넣으면 방지할 수 있다

Persistent 는 플레이어가 멀리 있어도 메모리 상에 항상 남아있기 때문에

게임에 아주 약간의 부담을 주지만 언제든 찾아서 지정할수 있다


이건 내가 만들 횃불 떨어트리는 모드에서 쓰는 횃불 저장용 상자의 참조인데

플래그에 Persistent 가 들어있다

MCM에서 메뉴를 체크하면 이 상자를 플레이어가 여는 함수가 들어있는데

Persistent 가 없다면 구석탱이에 처박힌 상자를 찾지 못했으니

Property 변수 는 비어있고 당연히 상자는 열리지 않을것이다



9. Quest Alias

Quest Alias 는 참조를 직접 지정하거나 조건에 맞는 참조를 무작위로 찾아서

별명을 적은 다음 그 별명에 스크립트를 넣을수 있게 하는 기능이다

이 Alias 는 퀘스트가 시작 되는 순간 채워진다

바닐라의 사람이나 물건에 스크립트를 직접 넣으면 모드 충돌이 생기기 때문에

가능하면 Alias를 등록하고 거기에 스크립트를 넣어야만 한다

Alias 는 창이 굉장히 복잡하고 쓰기도 어려우니 대표적인 방법 두가지만 설명함


퀘스트 창 맨 아래의 Aliases 에 Add 누르면 뭔가 줄이 쫙 뜨는데...

ALST 에 숫자를 0부터 차례대로 적고

Name는 이 Alias 의 이름인데 겹치지만 않으면 대충 적으면 된다


Flag에서 여러가지 속성을 넣는데 Optional 플래그는

이 Alias를 채우지 못해도 퀘스트가 시작 신호를 받으면 퀘스트를 시작하겠다는 뜻이다

Optional 플래그가 없으면 이 Alias 안을 못채우고 비어있으면 퀘스트가 시작되지 않는다


특정한 참조를 Alias 로 등록하려면 Forced Reference 에 참조의 FormID를 적거나

Actor의 경우 바로 아래의 Unique Actor 에서 ActorBase 의 FormID를 적어도 된다

플레이어라면 Forced Reference 에 14를 적거나 Unique Actor 에 7을 적는다


특정 참조를 직접 지정하지 않고 어떠한 조건을 적어서

게임에서 랜덤하게 찾아서 자동으로 채울수도 있다

아래의 Conditions 에 조건을 넣으면 된다

Condition Functions - Creation Kit Conditions 도 위키에 있지만 자세하진 않다


플래그 Matching Ref - In Loaded Area 는 현재 플레이어 근처의

로딩 되어 있는 곳의 참조만 찾겠다는 뜻이다

Matching Ref - Closest 는 플레이어와 가장 가까운 참조를 찾겠다는 뜻이다


Conditions 에 GetIsObjectType 을 걸어서 상자만 찾게 해놨으니

이 퀘스트가 시작되는 순간 플레이어와 가장 가까운 상자가

CloseContainerAlias 라는 별명의 Alias 가 된다


만일 Matching Ref - In Loaded Area 만 체크했다면

플레이어가 있는 곳의 상자 중에 한개가 랜덤으로 선택되서 Alias 가 된다


Matching Ref - In Loaded Area 도  Matching Ref - Closest 도 체크하지 않는다면

검색할수 있는 모든 상자... 그러니까 플레이어 주변 또는 다른곳의 Persistent 인

수천개의 상자 중 한개가 랜덤으로 Alias 가 된다


이 방식은 진짜로 게임 내에서 수행하는 퀘스트를 만들때 자주 사용한다

확인해보지 않아서 확실하진 않지만 아마 다크 브라더후드의 반복 암살퀘가

조건에 에센셜이 아닌 npc를 검색해서 목표로 지정하는 방식일 것이다


지정한 Alias는 퀘스트 스크립트 적는곳 아래쪽에 Aliases에 Add를 하고

Object v2 에 지금 화면의 자기자신의 퀘스트를 적으면 바로 아래의 Alias 칸에서

콤보박스로 고를수가 있고 아래쪽에 스크립트를 넣을수 있다



10. Function

Function 은 스크립트 안에 직접 적어넣은 함수라고 생각하면 된다

만약 플레이어가 의자에 앉을때나 또는 일어설때

가진 셉팁이 5000 미만이라면 1000 셉팁을 얻고

5000 이상이라면 1000 셉팀을 잃는 스크립트를 짜보면...


플레이어에 직접 스크립트를 걸면 모드 충돌이 생길게 뻔하니

퀘스트를 만들고 Alias 에 14를 적어서 플레이어를 지정하고

거기에 스크립트를 넣는다 넣는법은 위쪽을 참고하고 여튼 코드를 짠다


ScriptName PlayerGetGold Extends ReferenceAlias


MiscObject Property GoldGold Auto

Actor Property PlayerRef Auto


Event OnGetUp(ObjectReference akFurniture)    ;;// 가구에서 일어설때 수신

    if PlayerRef.GetItemCount(GoldGold) < 5000    ;;// 가진 템의 갯수를 확인하는 함수

        PlayerRef.Additem(GoldGold, 1000)

    else

        PlayerRef.Removeitem(GoldGold, 1000)

    Endif

EndEvent


Event OnSit(ObjectReference akFurniture)    ;;// 가구에 앉을때 수신

    if PlayerRef.GetItemCount(GoldGold) < 5000

        PlayerRef.Additem(GoldGold, 1000)

    else

        PlayerRef.Removeitem(GoldGold, 1000)

    Endif

EndEvent


엑터에 건 Alias 에선 Actor 에게 쓰는 이벤트를 쓸수 있다

세세한 함수 설명은 이제 생략할테니 위키에서 직접 찾아보고

여튼 앉거나 일어설때 GoldGold가 5000개 미만이면 1000개를 얻고

else 라는건 이전에 적은 조건을 만족시키지 못하면 이걸 실행하라는 소리니까

GoldGold가 5000개 미만을 만족시키지 못하는 경우는 당연히

GoldGold가 5000개 이상일때가 된다 그땐 GoldGold를 1000개 없애게 했다


적용되는 이벤트는 다르지만 안의 내용엔 같은 부분이 있으니

이럴때 Function 을 써서 보기 쉽고 관리하기도 편하게 할수 있다


Event OnGetUp(ObjectReference akFurniture)

    GetLostGold()

EndEvent


Event OnSit(ObjectReference akFurniture)

    GetLostGold()

EndEvent


Function GetLostGold()

    if PlayerRef.GetItemCount(GoldGold) < 5000

        PlayerRef.Additem(GoldGold, 1000)

    else

        PlayerRef.Removeitem(GoldGold, 1000)

    Endif

EndFunction


GetLostGold() 라는 이름은 그냥 대충 알아보기 편하게 지으면 되고

스크립트 내에 Function GetLostGold() 가 있기 떄문에

GetLostGold() 라고 적으면 거기 내용을 실행하고 다시 되돌아 오게 된다


Function 을 쓸때 무언가 정보를 전달할 수도 있다

만약 플레이어가 레벨 20 이하일때만 적용되게 하고 싶다면


Function GetLostGold(int iPlayerLevel)

이런 식으로 앞에 유형을 넣고 뒤에 이름을 대충 쓰기 편하게 적는다

정수가 아니라 사람의 참조 또는 무기가 들은 정보를 전달 받을거면

int 대신 Actor 또는 Weapon 을 적으면 된다


Event OnGetUp(ObjectReference akFurniture)

    GetLostGold(PlayerRef.GetLevel())

EndEvent


Function GetLostGold(int iPlayerLevel)

    if iPlayerLevel <= 20

        if PlayerRef.GetItemCount(GoldGold) < 5000

            PlayerRef.Additem(GoldGold, 1000)

        else

            PlayerRef.Removeitem(GoldGold, 1000)

        Endif

    Endif

EndFunction


이런식으로 하면 Function GetLostGold(int iPlayerLevel) 에

위쪽에 적은 PlayerRef.GetLevel() 함수로 구한 플레이어의 레벨이

iPlayerLevel 변수에 전달되고 플레이어의 레벨이 20 이하일때만

아래의 내용이 실행되게 된다



11. Function 과 Event 에서 Return 사용하기

Return 은 Function 에서 결과값을 돌려보낼때 쓰는 명령어지만

Function 과 Event 에서는 Return 으로 탈출해서 이전 단계로 돌아갈 수 있다

먼저 원래의 사용법대로 숫자가 4이하면 false / 5이상이면 true 값을 반환하는 함수를 만들면


Event OnInit()

    bool bGetRounds = TempFunction(5)

    if bGetRounds      ;;// if 문에서 bool 변수는 그냥 적으면 bGetRounds == True 라는 뜻

        Debug.Messagebox("i는 5 이상")   ;;// 스림에선 스크립트에 한글을 넣을수 없다

    else

        Debug.Messagebox("i는 4 이하")   ;;// 그냥 이해하기 편하라고 적음

    Endif

EndEvent


bool Function TempFunction(int akInt)

    if akInt <= 4

        Return false

    else

         Return true

    Endif

    Return false

EndFunction


이렇게 Function 의 앞에 bool 이나 int 등의 유형을 적으면

Return 으로 원하는 값을 반환하는 형식의 Function 이 된다

위의 스크립트대로라면 전달된 숫자가 5이므로 bGetRounds = True가 되고

"i는 5 이상" 문자가 출력되게 된다


그렇지만 반환값을 주는 Function 이 아니라

일반적인 Function 이나 Event 에도 Return 을 쓸수 있다


Event OnActivate(ObjectReference akActionRef)

    if PlayerRef.GetItemCount(GoldGold) >= 5000

        Return

    Endif


    PlayerRef.Additem(goldgold, 1000)

EndEvent


이런 스크립트가 어딘가의 상자에 걸려있다면

상자를 만질때 이벤트가 발동하고 플레이어의 셉팀이 5000 이상이라면

Return 명령어를 받아 아래 함수를 실행하지 않고 바로 이벤트에서 탈출하게 된다

5000 미만이라면 if 문에 걸리지 않으므로 1000셉팁을 얻게 된다


Function 도 마찬가지로 사용할 수 있지만...

Return 은 바로 스크립트를 끝내고 빠지는게 아니라

이전의 위치로 돌아간다는 것을 기억해야 한다

Event 는 이전의 위치가 없어서 바로 탈출하지만 Function 은 그렇지 않다


Event OnActivate(ObjectReference akActionRef)

    TeteFunc()

    PlayerRef.Additem(goldgold, 1000)

EndEvent


Function TeteFunc()

    if PlayerRef.GetItemCount(GoldGold) >= 5000

        Return

    Endif

EndFunction


위와 같은 스크립트의 형태가 된다면

상자를 만지면 Event 를 실행한 후 바로 TeteFunc() 으로 간다음에

플레이어가 5000셉팀 미만이라면 Return이 실행되지 않고

Function 을 끝낸후 다시 이벤트로 가서 1000셉팀을 얻는다

그리고 5000셉팀 이상이라면... if 가 참이 되서 Return 을 실행하므로

Function 에서 탈출한후 마찬가지로 되돌아와서 1000셉팀을 얻게 된다



12. 변수 만들기와 변수를 만드는 위치

변수는 말 그대로 뭔가 정보를 저장해놓고 다시 바꾸거나 그 저장된 정보를

쓰거나 할수 있는 요소이다

일단 변수를 처음 사용하려면 만들어야 하는데 만든다는게 뭐 거창한건 아니고

내가 만약 정수를 넣을 iCount 라는 이름의 변수를 만들겠다고 하면


int iCount

라고 맨 앞에 int 를 붙인다 혹시 참조를 넣고 싶다면 앞에 int 가 아니라

ObjectReference vRef 이런식으로 앞에 유형의 이름을 적으면 되며

그 다음부턴 앞에 유형 없이 그냥 바로 적으면 된다

iCount = 31

vRef = 어떤 참조


만일 int iCount 라고 처음에 유형을 정해줬는데 나중에 같은 이름으로

또 int iCount 라고 하면 컴파일 오류가 나게 된다


그리고 변수는 하나의 이벤트나 또는 펑션에서만 쓰거나

아예 하나의 스크립트 전체에서 쓰게 할수가 있다

그러니까 이벤트나 펑션안에서 int iCount 라고 유형을 적고 만들었다면

만들어진 그 이벤트와 펑션안에서 한순간만 사용할수 있고

이벤트나 펑션의 밖에서 만들었다면

저장된 값이 스크립트내에 저장되며 스크립트 어디에서도 쓸수 있다


ScriptName TestScript Extends ReferenceAlias


Actor Property PlayerRef Auto

Potion Property HealtyIng Auto


int iLoadCount


Event OnPlayerLoadGame()

    iLoadCount += 1

    if iLoadCount < 5

        PlayerRef.AddItem(HealtyIng)

    Endif

EndEvent


Event OnPlayerLoadGame() 는 저장한 게임을 로드할때 실행되는 이벤트임

모드 적용하는 순간엔 안되고 두번때부터 되니 알아두고...


위의 스크립트에서는 int iLoadCount 가 이벤트나 펑션 안에 있지 않고

Property 의 아래에 있다 그러니까 이 상태가 밖에서 만든 변수가 된다

int 를 만들때 뒤에 = 를 붙이고 숫자나 함수를 적지 않는다면

기본값은 0이 된다 그러니 지금 iLoadCount = 0 인 상태에서

플레이어가 세이브 하고 로드하면 += 1 이니 iLoadCount = 1 이 되고

다시 세이브 하고 로드하면 2 다시하면 3 이 되며 5가 될때까지

다섯번 포션을 받을수 있다


ScriptName TestScript Extends ReferenceAlias


Actor Property PlayerRef Auto

Potion Property HealtyIng Auto


Event OnPlayerLoadGame()

    int iLoadCount

    iLoadCount += 1

    if iLoadCount < 5

        PlayerRef.AddItem(HealtyIng)

    Endif

EndEvent


이 스크립트는 위와 거의 같지만 int iLoadCount 가 이벤트의 안에 있다

이벤트나 펑션 안에 있는 변수는 그곳에서만 사용할수 있기 때문에...

플레이어가 로드하면 int iLoadCount 라는 변수가 만들어지고 값은 0

그 뒤에 바로 += 1 이 됐으니 iLoadCount = 1 이 된다

그리고 다시 세이브 하고 로드하면...

다시 int iLoadCount 라는 변수가 만들어지고 값은 0 이 된다

결국 의도와 달리 5번만 포션을 받는게 아니라 무한으로 받게 된다


스크립트 내에 다른 이벤트나 함수에서도 쓰거나 변수값의 저장이 필요하면

반드시 밖에서 변수를 만들어야 한다

하지만 하나의 이벤트나 펑션에만 쓰거나 이벤트를 받을때마다

초기화가 필요한 변수도 많기 때문에 무조건 밖에 둘 필요는 없으며

오히려 기본은 안에 두고 필요한 변수만 밖에 두는 식으로 만드는 것이 좋다


그리고 if 나 While 의 안에서 변수를 만들었다면 만든 위치와 하위 위치에서만 쓸수 있다


if PlayerRef.GetItemCount(goldgold) > 5

    int i = 3

endif


i = 5


이런 형식이 된다면... Event 나 Function 에서 만든 변수가 그곳에서만 쓸수 있는것처럼

if 안에서 만든 변수는 그 if 안에서만 쓸수 있기떄문에 컴파일 에러가 나게 된다

만일 if 나 While 안에서 변수의 값을 지정하고 다른곳에서도 쓰게 하고 싶다면


int i

if PlayerRef.GetItemCount(goldgold) > 5

    i = 3

endif


i = 5


이런 식으로 if 나 While 문의 바깥에서 미리 변수를 만들어 줘야만 한다



13. 위키에 적힌 함수 사용법과 변수 사용하기

GetLevel - Actor - Creation Kit이 GetLevel 이라는 함수를 예로 들면

맨위에 GetLevel - Actor 라고 적혀있으니 당연히 엑터에게 사용하는거고

Syntax 에 int Function GetLevel() native 하고 적혀 있는데

맨 앞에 int 가 붙어 있으니 결과값은 int 라는 정수라는 소리가 된다


한마디로 GetLevel 이라는 함수를 Actor 에게 사용하면 0 이상의 정수가 나온다


맨 위의 - 뒤가 사용하는 대상이고 Syntax 의 맨 앞이 튀어나오는 결과물임

위키에는 사용법 예제가 안적힌 함수도 꽤 있으니 꼭 이 규칙을 알아두고...


스크립트는 뭔가를 할때마다 약간의 시간이 걸리는데 단순한 계산 같은것보다

함수를 사용하는게 시간을 더 잡아먹는다

그러니까 스크립트에서 플레이어의 레벨이 여러번 필요하다고 하면


if 플레이어.GetLevel() > 10

    플레이어에게 10셉팀을 줌

    if 플레이어.GetLevel() > 20

        플레이어에게 20셉팀을 줌

        if 플레이어.GetLevel() > 30

            플레이어에게 30셉팀을 줌

        Endif

    Endif

Endif


이런 함수가 있다고 하면... 만일 플레이어가 레벨이 40이라고 하면

첫번째 if 에서 플레이어의 레벨이 40이라는걸 알아내고 10셉팀을 준다음에

다시 두번째 if 에서 플레이어의 레벨이 40이라는걸 알아내고 20셉팀

다시 세번째 if 에서 플레이어의 레벨이 40이라는걸 알아내고 30셉팀...

이미 플레이어의 레벨이 40인걸 처음에 알아냈는데 두번이나 더 알아내고 있다

이런게 스크립트가 무거워지는 원인이 되기 떄문에 이럴때는


int iPlayerLevel =  플레이어.GetLevel()

if iPlayerLevel > 10

    플레이어에게 10셉팀을 줌

    if iPlayerLevel > 20

        플레이어에게 20셉팀을 줌

        if iPlayerLevel > 30


이런식으로 GetLevel 을 한번만 쓰고 그 결과를 변수에 저장한 다음에 계속 쓰면 된다



14. 다른 스크립트에 있는 변수나 Function 사용하기

모드를 만들다보면 스크립트끼리 서로 연결되서 작용해야 하는 경우도 많기 때문에

하나의 스크립트에서 또 다른 스크립트에 있는 변수를 가져오거나 바꾸거나

그 다른 스크립트에 적힌 Function 을 쓰거나 해야할때가 많다


예제 모드를 만들어서 설명하면...

세이브 한다음에 로드할때 마다 1000 셉팀을 얻고

5번 얻으면 그 다음부턴 그만 얻게 하는 모드를 만들어 봄


퀘스트를 만들어서 스크립트를 걸고 파일이름을 TQ_ManageQuestScript 라고 하겠음

밑쪽에 Start Game Enabled 를 지정해서 자동으로 퀘스트가 시작되게 하고

스크립트를 넣고


ScriptName TQ_ManageQuestScript Extends Quest


MiscObject Property GoldGold Auto


Int Property iGetGoldCount Auto Conditional


Function GetGoldGameLoad()

    Game.GetPlayer().AddItem(GoldGold, 1000)

    iGetGoldCount += 1

EndFunction


보통 변수를 만들면 int iCount 뭐 이런식으로 하는데

Int Property iGetGoldCount Auto Conditional 라고

적으면 다른 스크립트에 연결됐을때 그 스크립트에서

이 변수를 조절할수 있게 된다


Game.GetPlayer() 라는 함수는 esp에 Property 에 14를 적지 않고

그냥 바로 플레이어를 지정하는 함수다

함수중엔 이렇게 사용 빈도가 많은 요소를 Property 없이

바로 지정할수 있는 함수도 있는데...

플레이어는 스카이림 실행파일에 직접 박혀있어 Property 로 불러오는 속도가

엄청나게 빠르기 때문에 속도가 필요하거나 자주 사용하는 스크립트는

Game.GetPlayer() 를 쓰지 말고 Property 14 로 하는것이 좋다


여튼 Function GetGoldGameLoad() 이 실행되면

플레이어에게 1000셉팀이 지급되면서

iGetGoldCount 라는 정수 변수가 1씩 계속 올라가게 된다


 

그 다음에 퀘스트를 하나 더 만든 다음에

Alias 에 14를 적어서 플레이어를 지정하고 스크립트를 걸고

Property 에 위에 만든 TQ_ManageQuest 퀘스트를 지정하고

이것도 자동으로 퀘스트가 시작되게 하고 코드를 짜보자


ScriptName TQ_GoldQuestScript Extends ReferenceAlias


TQ_ManageQuestScript Property ManageScript Auto


Event OnPlayerLoadGame()

    if ManageScript.iGetGoldCount  <= 5

         ManageScript.GetGoldGameLoad()

    else

         Self.GetOwningQuest().Stop()

    Endif

EndEvent


조금 헷갈리겠지만 esp에서 TQ_ManageQuest 퀘스트가

ManageScript 라는 이름으로 Property 되어 있으니

xxxxx Property ManageScript Auto 라고 적는건 이제 알테고

맨 앞에 Quest 니 ObjectReference 니 이런걸 안붙이고

그 Form에 붙어있는 스크립트 파일의 이름을 적으면

거기에 적용된 스크립트의 Function이나 변수를 이용할 수 있게 된다


TQ_ManageQuestScript Property ManageScript Auto

라는 글은 esp에서 ManageScript 라는 이름에 적용되어있는

Form의 TQ_ManageQuestScript 라는 이름의 스크립트라는 거다

시발 내가 적어도 못알아보겠네 몇번 해보면 쉬우니까 그냥 해보셈...


여튼 그렇게 됐으면 함수 쓰듯이  ManageScript 적고 뒤에 . 찍고

거기 스크립트에서 하는 식으로 적으면 된다


if ManageScript.iGetGoldCount  <= 5

이건  TQ_ManageQuestScript 스크립트에 있는 iGetGoldCount 라는 인트변수가

5 이하 인지 확인하는 조건문인데 숫자 지정을 안해놨으니 처음엔 0 이다

위에도 말했지만 이렇게 스크립트의 변수를 다른 스크립트에서 만지려면

거기의 변수에 Property 와 Auto Conditional 을 적어줘야한다


ManageScript.GetGoldGameLoad()

여튼 5 이하니까 이 구문이 실행된다

TQ_ManageQuestScript 스크립트의  GetGoldGameLoad() 을 실행하라는 거다


Function GetGoldGameLoad()

    Game.GetPlayer().AddItem(GoldGold, 1000)

    iGetGoldCount += 1

EndFunction

거기엔 이 내용이 있으니까 플레이어한테 1000 셉팀을 주고

iGetGoldCount 을 1 더해서 0 에서 1이 된다


이제 다시 세이브를 한다음에 그 세이브 파일을 로드하면??

ManageScript.iGetGoldCount 는 아직 1이니까 당연히 1000 셉팀을 받고

iGetGoldCount 는 2가 되고... 또 하면 3이 되고 4가 되고 5가 되고

또하면 6이 되면서


else

     Self.GetOwningQuest().Stop()

그럼 5 이하가 아니게 되니까 else 에 적힌 함수가 실행되게 된다

Self는 이 스크립트가 적용되어있는 자기자신... 그러니까 Alias고

GetOwningQuest() 는 이 Alias가 속한 Quest를 지정하는 함수인데

Alias는 퀘스트에만 들어있으니까 Property 로

또 퀘스트 지정하면 귀찮으니까 아예 자기가 속한 퀘스트를

바로 불러올수 있는 함수가 있는데 그게 GetOwningQuest() 이다


어차피 로드횟수가 6이 됐으니까 다음부터는 확인하나마나

무조건 6 이상이라 더이상 확인해봐야 헛수고니까

더이상 세이브를 로드해도 스크립트가 실행되지 않게

이 퀘스트를 종료시켜서 게임의 부담을 약간이라도 줄인다



15. Global

Global 은 숫자 변수를 저장하고 함수를 통해 변경하거나 불러올 수 있는 항목이다

이 글을 초보들이 읽을거라 생각해서 적긴 했지만 사실 글로벌이 어떤것인지 안다면

그다지 설명할 내용은 없다


사용하려면 일단 Global 을 만들고... 타입에 Float Short Long bool 어쩌고가 있는데

왠지 int 계열이나 bool 이 가볍고 좋을것 같지만 내부적으로는 동일하기 때문에

나중에 스크립트가 바뀔지도 모르므로 그냥 Float 를 하는게 좋다

위에서 설명한 어떠한 상자를 5번 열면 죽는 스크립트에서 그 종류의 같은 상자가 아닌

다른 상자라도 합쳐서 5번을 열면 죽게 하고 싶다면...


ScriptName KillChest Extends ObjectReference


GlobalVariable Property gAnyGlobal Auto    ;;// esp 에서 값을 0 으로 설정해놓은 글로벌


Event OnActivate(ObjectReference akActionRef)

    if akActionRef == Game.GetPlayer()

        int i = gAnyGlobal.GetValueInt()    ;;// 그냥 변수가 int라 GetValueInt 를 씀

        if i >= 5

            Game.GetPlayer().Kill()

        else

            gAnyGlobal.SetValueInt(i + 1)    ;;// SetValueInt 와 SetValue 의 결과 저장값은 값음

        Endif

    Endif

EndEvent


이런 KillChest 라는 스크립트가 걸린 Container 가 있고

그걸 BaseObject 로 하는 상자가 게임에 5개가 뿌려져 있다면

플레이어가 1번 상자를 한번 2번 상자를 두번 5번 상자를 두번 만지면

글로벌 값이 5가 되면서 죽게 된다


여러 스크립트에 동시에 사용하는 변수가 필요하면

스크립트를 연결하는 것보다 글로벌이 편한 경우가 많다

그리고 스크립트의 변수를 가져오는게 빠를것 같지만

글로벌은 굉장히 빠른 함수이기 떄문에 거의 차이가 없다

어디선가 본 글에서는 5% 미만의 속도차이라고 하며 실제로 스크립트 초보시절

While 과 if 조합으로 gAnyGlobal.GetValue() 를 천번 정도 반복하는 스크립트를 짰었는데

나중에 글로벌 값을 변수에 저장하고 사용하도록 스크립트를 바꿨을때랑 차이가 없었다

그러므로 숫자 변수가 여러 스크립트에서 필요하면 부담없이 만들어 사용하면 된다


그리고 Global 은 esp의 Conditions 에서 숫자 입력 대신 글로벌 값을 사용하게 할수 있다

Type 에서 Use global 을 체크하고 아래의 Comparison Value 에서 글로벌을 선택하면 된다



16. as 로 유형을 지정하는법

ObjectReference 와 Actor 는 거의 같지만 Actor 가 ObjectReference 유형이 되어있으면

Actor 에게만 사용하는 일부 함수를 사용할수 없다

위의 Alias 에서 암사자 묠을 지정한후 별명을 MJolllAlias 라고 지었다면...

만일 뭔가의 이벤트를 걸어서 묠을 죽이고 싶다면

MJolllAlias.GetRef().KILL()

이라고 적으면 될것 같지만 GetRef() 함수로 나오는 결과값은

Actor 가 아니라 ObjectReference 이기떄문에 컴파일 에러가 뜨게 된다 이럴떄는

(MJolllAlias.GetRef() as Actor).KILL()

라고 뒤에 as Actor 라는 글을 붙여서 이놈은 엑터로 쓸거라고 알려주면 된다



17. as 로 다른 스크립트 연결하기

스크립트를 연결할때 TQ_ManageQuestScript Property ManageScript Auto

처럼 스크립트를 직접 Property 하지 않고 as 로 할수도 있다


TQ_ManageQuest 를 그냥 물건이나 사람 지정하듯이

Quest Property TQ_ManageQuest Auto 로 적고

(TQ_ManageQuest as TQ_ManageQuestScript) 라는 형식으로

as 뒤에 스크립트 이름을 적으면 된다


이경우 굉장히 글이 길어져서 귀찮기 떄문에 변수로 지정하는게 좋다

스크립트를 변수를 지정하는건 actor나 int같은거랑 다르게 좀 복잡한데

"스크립트이름" "변수이름" = "스크립트불러올Property이름" as "스크립트이름"


TQ_ManageQuest 에 있는 TQ_ManageQuestScript 를 불러오고 싶으면

TQ_ManageQuestScript mScript =  TQ_ManageQuest as TQ_ManageQuestScript 라고 적는다

변수이름에는 mScript 라든지 뭐 원하는 이름을 적으면 되고 사용할땐 마찬가지로

mScript.GetGoldGameLoad() 라는 식으로 사용하면 된다


가능하면 그냥 Property 로 지정해서 연결하는게 빠르고 편하지만

아래에서 설명할 Event 에서 제공되는 변수라거나

스크립트로 게임상에서 직접 만든 물건이라거나 하는 등의 이유로

esp에서 미리 property 를 할 방법이 없는 경우 이 방법으로 연결해야 한다



18. as 를 if 조건문에 사용하기

어떤 변수가 있다면 그 변수가 Actor인지 Potion인지 Container인지 하는

유형이 맞는지를 as 와 if를 사용해 확인해볼 수 있다


플레이어가 아이템을 습득할때 그 아이템이 무기일때만

알림창이 뜨게 하는 스크립트를 짜보면...


ScriptName CheckGetItem Extends ReferenceAlias


Event OnItemAdded(Form akBaseItem, int aiItemCount, ObjectReference akItemReference, ObjectReference akSourceContainer)

    if (akBaseItem as Weapon)

        Debug.Messagebox("Player Get" +  akBaseItem.GetName())

    Endif

EndEvent


이벤트에는 발동할때 무언가 변수를 제공할때가 있다

OnItemAdded 는 아이템을 습득하는 순간 수신되는 이벤트인데 위키에서 확인해보면

Form akBaseItem 이 방금 습득한 물건의 BaseObject 라고 한다

BaseObject 는 위에서 설명 했었는데...

바닥에 강철검이라는 물건이 있다면 이 강철검 자체는 ObjectReference 이고

이 강철검의 원본인 akBaseItem 은 esp의 Weapon 유형에 들어있는 강철검이 된다

여튼 플레이어가 강철검을 줍게되면  Form akBaseItem = 강철검 원본 이 된다


if (akBaseItem as Weapon) 이런식으로 if 를 사용하고 뒤에 as Weapon 을 붙이면

akBaseItem 가 Weapon 유형이 맞다면 참이 되어 아래의 메세지박스를 출력한다


if !(akBaseItem as Weapon) 라고 앞에 ! 를 붙이면 Weapon 이 아니어야 참이 되므로

강철검을 먹을땐 메세지박스가 나오지 않고 포션이나 책 같은걸 먹을때 메세지가 뜨게 된다


자주 쓰는 방법은 아니지만 방금 먹은 이 아이템에 특정 스크립트가 걸려있는지

확인할때도 as를 쓸수 있다

어떤 무기가 있고 그 무기에 PowerSlash 라는 이름의 스크립트가 들어있다고 하면

if (akBaseItem as PowerSlash) 이런식으로 그냥 뒤에 스크립트 이름을 적으면

그 아이템에 이 스크립트가 붙어있으면 참이 된다



19. as 가 실제로 하는일

이 부분은 그냥 as 어케 쓰는지 헷갈리고 그런 사람만 읽어보든가 해


as 는 캐스팅이라고 해서 어떤 변수를 as 뒤에 적은 스크립트를 연결 시키는 기능을 한다

왜 위에서 한말을 또 하나 싶겠지만... as Actor 를 엑터로 지정한다고 했는데

정확하게 말하면 그 변수에 적용되어있는 Actor 라는 스크립트를 사용하겠다는 것이다

바닐라 스크립트에 Actor 라는 이름의 스크립트가 있고 그 안에

Function Kill() 같은 Function 이 적혀져 있다

그러니까 한번에 설명하면 헷갈리니 분리했지만 15번과 16번 항목은 같은 내용이다


ESP 에서 Furniture 항목에 적힌 Form 에는 보이진 않지만 Furniture 스크립트가 적용 되어있다

마찬가지로 다른 모든 항목에는 거기에 해당하는 바닐라 스크립트가 적용 되어있다

예를 들어 Misc. Item 에는 MiscObject 스크립트가 적용중이며

NPC 항목에는 ActorBase 스크립트가 적용되어 있는 상태이다


스크립트 위에 적는 Actor Property SomeNPC Auto 라든가

변수를 만들때 앞에 적는 MiscObject GoldGold 의 의미는

그 변수 에게 들어있는 이 바닐라 스크립트를 사용하겠다는 말이 된다

위에서 GetRef() 로 구한 ObjectReference 에 Kill 을 사용하면 오류가 뜨는 이유는 간단하다

바닐라에 있는 ObjectReference 라는 이름의 스크립트 안에는

Function Kill() 이라는 Function 이 없기 때문이다

그래서 as Actor 로 Actor 스크립트를 캐스팅해서 Kill 함수를 사용하는 것이다


그리고 Property 하는법을 적을때 설명했는데 이 Property 의 유형을 잘못 적으면

스크립트가 발동하지 않는 이유도 결국 같은 이유이다

GetRef() 로 구한 ObjectReference 가 Actor 가 아닐수도 있다

그러니까 트롤이 아니고 그냥 의자일 수도 있는 것이다


위에서 말한대로 ESP의 Actor 참조에는 자동적으로 Actor 스크립트가 적용되어있다

마찬가지로 의자 참조에는 ObjectReference 스크립트가 적용되어 있을것이다

하지만 ck툴 컴파일러는 GetRef() 로 구한 ObjectReference 가 엑터인지 가구인지

당연히 모르고 그냥 Actor 라고 하니까 아 이놈은 엑터구나 하고 연결시켜줄 뿐이다


그러므로 GetRef() 로 구한 ObjectReference 가 의자일떄... 

(GetRef() as Actor).Kill() 이라고 하면 컴파일 오류 없이 컴파일은 된다

ObjectReference 는 Actor 일 가능성이 있기 때문에 컴파일을 해주는 것이다

하지만 이 의자에는 Actor 스크립트가 없기 때문에 Kill 함수는 당연히 발동되지 않는다


18번도 같은 내용이다. if (SomeRef as Actor) 가 Actor 인지 아닌지 알아낼수 있는 이유는

자동적으로 그 스크립트가 적용 되어있는 상태면 스크립트가 있으니 참이 되고

그 이름의 스크립트가 적용 되어있지 않으면 거짓이 되는것이다


as 를 사용할때 직관적으로 알수 있게 연결 시킨다는 표현을 계속 썼지만

결국 이 Form 에 적용 되어있는 이 이름의 스크립트 안의 Function 을 쓰겠다는 말이 된다

당연히 이 Form 에 Actor 라거나 TQ_ManageQuestScript 등의 스크립트가 적용되어 있지 않으면

컴파일 오류가 뜨거나 컴파일 오류는 뜨지 않지만 함수를 써도 아무일도 일어나지 않는다



20. 배열과 폼리스트

배열은 어떤 유형을 한번에 저장할수 있는 변수다

만일 Actor를 4명 저장하는 배열을 만들고 싶다면

Actor[] ActorArray = New Actor[4]

라고 입력한다  ActorArray 는 변수이름인데 그냥 원하는대로 적으면 된다


배열의 번호는 0번 부터 시작하기 떄문에 이제  ActorArray 에는

ActorArray[0],  ActorArray[1],  ActorArray[2],  ActorArray[3] 의

Actor 를 저장할수 있는 공간이 생겼다


만일 ActorArray[1] 에 플레이어를 저장하고 싶다면

ActorArray[1] = Game.GetPlayer() 또는 Property 해놓은 이름을 적으면 된다

함수를 사용하거나 할때는 그냥 변수 쓰듯이 ActorArray[1].Additem(GlodGlod)

이런식으로 적으면 된다

General Purpose Array Functions - Creation Kit

여기에 배열에서 쓸수 있는 함수가 5개 있으니까 배열을 정리하거나

배열 안의 데이터를 검색하거나 할수도 있다


FormList 는 esp에서 만들수 있는데 FormIDs 오른쪽에 우클릭하고

add 를 눌러서 몇개라도 계속 추가할수 있다

하나의 폼리스트에는 Actor만 넣던가 MiscObject만 넣던가 하는식으로

같은 유형의 Form만 넣는것이 좋다


폼리스트를 스크립트에서 사용하려면 퀘스트나 사람이나 물건처럼

esp에서 Property 로 지정하고 스크립트에서

FormList Property TestFormList Auto 라는 식으로 적으면 된다


폼리스트에도 배열처럼 몇가지 유용한 함수를 쓸수 있는데 그건

FormList Script - Creation Kit 여기에서 확인할 수 있다

가장 자주 쓰게 될 함수는 GetAt 라는 함수인데

이 폼리스트의 몇번째 순서에 들은 Form을 가져오는 함수다

폼리스트 번호도 배열처럼 0번부터 시작하기 때문에...

위의 스샷에 만든 폼리스트에서 3번째의 Lockpick을 가져오고 싶다면

TestFormList.GetAt(2) 라고 하면 된다


하지만 GetAt를 쓸땐 큰 주의점이 있는데

반드시 뒤에 as 를 붙이고 저장된 Form의 유형을 적어야한다는 것이다

그러므로 이 경우 정확한 사용법은 TestFormList.GetAt(2) as MiscObject 가 된다

왜냐하면 폼리스트는 말그대로 Form을 집어넣어 놓기만 한곳이기 때문에

대체 이 폼리스트 안에 뭐가 들어있는지를 컴파일러가 파악을 하지 못한다

그래서 꼭 뒤에 as 를 붙여서 이놈이 잡템인지 사람인지 상자인지 알려줘야 한다



21. While 을 이용한 배열과 폼리스트 응용

While은 반복문인데 뒤에 적은 내용이 거짓이 될때 까지 반복한다


i = 5

While i >= 0

     i -= 1

     Game.GetPlayer().Additem(goldgold, 1000)

EndWhile


이라는 함수가 있다면 처음에 i = 5 이므로 참이되서 아래 내용을 진행한다

i = i - 1 을 해서 i = 4 가 되고 플레이어에게 1000셉팀을 넣고

EndWhile 이니까 다시 While로 되돌아간다

그러면 4니까 다시 3이 된후 1000셉팀을... 되돌아간후 3에서 2로

.... 결국 0에서 -1이 되면 거짓이 되서 While 문에서 탈출하게 된다


이걸 이용해서 배열이나 폼리스트에 들어있는 무언가에

같은 함수를 한번씩 돌아가면서 쓸수 있다


만일 배열에 4명의 Actor를 ActorArray 라는 이름으로 저장해놨는데

4명을 모두 죽이고 싶다면 


int i = ActorArray.Length

While i >= 0

      i -= 1

      ActorArray[i].Kill()

EndWhile


Length는 배열의 길이를 재는 건데 이 배열은 4칸짜리 배열이니 i = 4 가 되고

While에서 바로 i = 3 이 되니  ActorArray[i] 는 결국  ActorArray[3] 이라는 뜻이 된다

배열은 0부터 시작하니 4칸짜리 배열이면 3번이 맨 마지막 자리가 된다

위와 마찬가지로 i 가 While문을 계속 돌면서 2 1 0 으로 줄어들고

ActorArray[i] 도  ActorArray[2], [1], [0] 이 되면서 모두 Kill 함수에 죽게 된다


폼리스트의 경우에도 거의 같은 방식으로 할수 있다


int i = ActorFormList.GetSize()

While i >= 0

      i -= 1

      (ActorFormList.GetAt(i) as Actor).Kill()

EndWhile


배열은 Length를 쓰지만 폼리스트의 길이는 GetSize 라는 함수로 구할수 있다

GetAt 로 i 번째의 데이터를 찾아온다음에 as Actor 를 뒤에 적어서

이 Form이 Actor 라는걸 알려주고 kill로 죽이는 방식이 된다



22. 횃불 떨어트리는 모드의 스크립트 예제

저번에 만든 횃불 떨어트리는 모드가 꽤 간단하고

여기서 설명한 대부분의 요소가 들어있어서 올려보겠음

이 모드는 3개의 스크립트가 들어있는데 한개는 MCM 스크립트니까 빼고...


이 모드는 상자안이나 구매하거나 그런게 아닌

바닥에 떨어진 바닐라 횃불을 주워먹을 경우 횃불을 바로 손에 들고

바닐라 횃불을 든 상태에서 다시 횃불을 장착해제 하면 정상적으로 벗고

바닐라 횃불을 든 손에 장비나 마법 등을 들면 그걸 땅에 떨어트리는 모드임


그리고 횃불도 바닐라 횃불 말고 횃불 모드의 횃불을 쓰는 사람도 있을지 모르니까

MCM에서 메뉴를 선택하면 상자가 열리고 그 열린 상자에 모드 횃불을 넣으면

이제 바닐라 횃불은 줍든 버리든 아무런 기능이 생기지 않고

넣은 모드 횃불과 같은 종류의 횃불이 자동으로 장착하거나 버리거나 하게 됨


먼저 횃불을 등록하는 상자 스크립트 부터 보자


Scriptname TD_SettingBoxScript extends ObjectReference ;;// Persistent 로 되어있는 상자에 걸린 스크립트


Actor Property PlayerRef Auto    ;;// Property 14로 플레이어가 지정되어 있음

TD_MainQuestScript Property MainQuestScript Auto ;;// 플레이어 Alias에 걸린 스크립트와 연결

Message Property mIsNotTorch Auto ;;// 메세지 3개는 알림창용 한글

Message Property mRegisterAlready Auto

Message Property mRegisterComplete Auto


bool isAdded ;;// 동시에 횃불을 여러개 넣지 못하게 하려는 확인용 변수


Event OnItemAdded(Form akBaseItem, int aiItemCount, ObjectReference akItemReference, ObjectReference akSourceContainer)

    if !(akBaseItem as Light)     ;;// 횃불은 Light 유형이다 방금 넣은 아이템이 Light 가 아니라면

        mIsNotTorch.Show()      ;;// 이건 횃불이 아니라는 메세지를 띄우고

        RemoveItem(akBaseItem, aiItemCount, False, PlayerRef)     ;;// 방금 템을 다시 플레이어에게 돌려줌

    else     ;;// Light 유형이 아니지 않으니까 Light가 맞다면

        if isAdded == False     ;;// 이벤트 바깥쪽에 만들어둔 bool 변수

            isAdded = True      ;;// 바로 변수를 True로 바꾸면 다음에 템을 넣으면 위의 if에서 거짓이 됨

            MainQuestScript.ChangeTorch(akBaseItem)    ;;// 플레이어Alias에 있는 Function ChangeTorch(방금먹은횃불) 실행

            RemoveItem(akBaseItem, aiItemCount, False, PlayerRef)    ;;//  방금 템을 다시 플레이어에게 돌려줌 

            mRegisterComplete.Show()    ;;// 등록이 완료되었다는 메세지를 띄움

            Utility.Wait(0.03)    ;;// 이 함수는 메뉴창이 떠있을땐 무조건 스크립트를 멈춘다

            isAdded = False    ;;// 상자를 닫으면 다시 스크립트가 진행되고 if가 참이 될수 있게 False로 변경

        else  ;;// isAdded = True 라면... 그러니까 상자를 열어서 이미 횃불을 하나 넣은 상태라면

            RemoveItem(akBaseItem, aiItemCount, False, PlayerRef)

            mRegisterAlready.Show()    ;;// 이미 등록했으니 상자를 닫고 다시하라는 메세지를 띄움

        Endif

    Endif

EndEvent


이 스크립트는 안에 뭔가 템을 넣으면 그 템의 BaseObject가

Light 유형인지 확인하고 Light 유형이 맞다면

TD_SettingBoxScript 스크립트의 Function ChangeTorch(Form XXX)

을 실행하고 맞든 아니든 들어온 아이템은 다시 플레이어에게 반환한다

그럼 이제 플레이어 Alias의 스크립트를 보면


Scriptname TD_MainQuestScript extends ReferenceAlias   ;;// 만든 퀘스트의 플레이어 Alias에 건 스크립트


Light Property Torch01 Auto    ;;// 바닐라 횃불

ObjectReference Property TD_SettingBoxRef Auto    ;;// 위의 횃불 등록용 상자


Actor PlayerRef  ;;// Actor 변수를 미리 만들어둠

Float Property pHeight Auto Conditional    ;;// MCM에서 바꿀수 있는 실수 변수. 횃불 떨구는 높이

Float Property DropAngle Auto Conditional    ;;// 마찬가지. 횃불 떨굴떄의 각도

Float Property DropRange Auto Conditional    ;;// 마찬가지. 횃불 떨굴때의 거리

Light OriginTorch    ;;// 모드가 적용될 횃불 유형을 저장할 변수


Event OnInit()    ;;// 이 이벤트부터 바로 실행

    PlayerRef = Self.GetRef() as Actor    ;;// 바깥에 만든 Actor 변수에 플레이어를 넣었음

    AddInventoryEventFilter(Torch01)    ;;// 플레이어가 먹는 모든 아이템을 다 체크하면 느려지니 바닐라 횃불만 체크

    OriginTorch = Torch01    ;;//  모드가 적용될 OriginTorch 라는 Light 변수에 바닐라 횃불을 넣음

    pHeight = 70.0    ;;// 횃불 떨구는 위치들 지정

    DropAngle = 35.0

    DropRange = 40.0

EndEvent


Event OnItemAdded(Form akBaseItem, int aiItemCount, ObjectReference akItemReference, ObjectReference akSourceContainer)

    ;;//  위에 AddInventoryEventFilter(Torch01)를 했기 떄문에 오직 횃불을 먹을때만 이 이벤트가 발동함

    GotoState("Running")   ;;// State 는 이벤트를 걸러받을수 있는 기능이다 아래에서 설명함

    if akSourceContainer == None && PlayerRef.GetEquippedItemType(0) != 11    ;;// 바닥에서 주웠고 왼손에 횃불이 없다면

        PlayerRef.EquipItem(akBaseItem, False, True)    ;;// 플레이어는 방금 먹은 그 횃불을 장착

    EndIf

    GotoState("")    ;;// 다시 기본 State 로 되돌림

EndEvent


Function ChangeTorch(Form RegisterTorch)   ;;// 상자에 횃불을 넣을때 실행되는 Function 이다

    RemoveInventoryEventFilter(OriginTorch)   ;;// 기존에 등록한 횃불의 필터를 삭제하고

    AddInventoryEventFilter(RegisterTorch)    ;;// 상자에 넣은 횃불을 필터에 등록함

    Utility.WaitMenuMode(0.03)    ;;// WaitMenuMode 는 메뉴가 열려있어도 진행된다 스크립트 꼬임방지로 넣음

    OriginTorch = RegisterTorch as Light    ;;//  OriginTorch 변수에 방금 등록한 횃불을 저장

EndFunction


Event OnObjectUnequipped(Form akBaseObject, ObjectReference akReference))   ;;// 장비를 벗을때 발동되는 이벤트

    if akBaseObject == OriginTorch    ;;// 벗은 장비가  OriginTorch 변수에 저장된 횃불이라면

        GotoState("Running")

        Utility.Wait(0.03)    ;;// 인벤을 열어서 꼈다뻈다 하면 꼬이므로 인벤 연 상태면 잠시 멈춤용

        if PlayerRef.GetEquippedItemType(0) != 0    ;;// 플레이어가 횃불을 뺀 다음 뭘 들고 있나 확인함

            DropTorch(akBaseObject)    ;;// 뭔가 다른걸 들었다면 Fucntion DropTorch(횃불) 실행

        else

            int rWeapon = PlayerRef.GetEquippedItemType(1)

            if rWeapon >= 5 && rWeapon <= 7

                DropTorch(akBaseObject)

            elseif rWeapon == 12

                DropTorch(akBaseObject)

            Endif   ;;// 손에 다른걸 든게 없다면 DropTorch 를 실행하지 않으니 횃불을 떨구지 않는다

        Endif

        GotoState("")

    Endif

EndEvent


Function DropTorch(Form akBaseObject)

    Float z = PlayerRef.GetAngleZ() - DropAngle   ;;// 횃불 떨굴 위치를 위해 플레이어 각도 계산

    PlayerRef.RemoveItem(akBaseObject, 1, True)    ;;// 횃불 떨굴거니 인벤에 횃불을 하나 삭제

    ObjectReference TorchRef = PlayerRef.PlaceAtme(akBaseObject, 1, False, True)   ;;// 횃불을 플레이어 발밑에 만들고

    TorchRef.Moveto(TorchRef, Math.Sin(z) * DropRange, Math.Cos(z) * DropRange, pHeight)   ;;// MCM 설정 위치로 이동

    TorchRef.Enable()    ;;// 처음에 투명하게 만들었는데 이제 위치로 갔으니 보이게 만듬

    Utility.Wait(0.03)    ;;// PlaceAtme 함수를 썼다면 가능하면 Utility.Wait 를 써야한다

    TorchRef.ApplyHavokImpulse(0,0,1,1)    ;;// 횃불이 공중에 떠서 멈춘 상태인데 툭 쳐주는 함수

    TorchRef = None    ;;// 변수를 비움

EndFunction


State Running  ;;// 여러 State에 같은 Event 가 있다면 현재 State 의 이벤트만 실행한다

                    ;;// State를 전혀 사용하지 않은 기본 상태는 State "" 상태이다 

    Event OnObjectUnequipped(Form akBaseObject, ObjectReference akReference)

    EndEvent    ;;// 꼬임 방지용으로 이벤트 실행중에 또 실행되면 아무일도 일어나지 않게 함


    Event OnItemAdded(Form akBaseItem, int aiItemCount, ObjectReference akItemReference, ObjectReference akSourceContainer) 

    EndEvent

EndState


Event OnItemAdded 는 아이템이 인벤에 들어올때 발동하는 이벤트인데

플레이어에게 걸면 플레이어는 템을 엄청나게 먹으니 굉징히 부담이 가게 된다

그래서 미리 AddInventoryEventFilter 함수를 써서 템을 지정해서

오직 지정해둔 그 템을 먹을때만 OnItemAdded 이벤트를 수신하게 했다


GetEquippedItemType 함수는 지금 손에 뭘 들고 있나 확인하는 함수인데

이걸 이용해서 횃불을 벗은 다음 무언가 다른걸 바꿔낀 상태인지

그냥 횃불만 벗어서 맨손 상태인지 확인해서 횃불을 떨굴지 말지 정한다


Float z = PlayerRef.GetAngleZ() /  Math.Sin(z)  /  Math.Cos(z) 조합은

무언가를 플레이어의 정면으로 옮길때 쓰는 함수 조합이다


PlaceAtme 로 만들어진 참조에 무언가 물리적인 함수를 쓰려면

그전에 반드시 Utility.Wait() 함수로 잠시 기다려야 한다

PlaceAtme 로 만든 참조가 3D 로드 되기전에 함수를 쓰면

참조가 존재는 하지만 아직 보이지 않는 상태라서...

Function DropTouch 에서 Utility.Wait 로 잠시 기다리지 않았다면

툭 치는 함수가 사용되도 물체가 아직 안보이는 상태인 경우가 있고

그렇게 되면 만들어진 횃불은 떨어지지 않고 공중에 가만히 떠 있게 된다



23. Spell 을 이용해 Conditions 를 조건으로 스크립트 실행하기

Spell 은 손에서 불을 날리거나 회복마법을 사용하거나 이런 "마법" 외에도

Conditions 로 Event 에는 없는 순간에서도 스크립트를 실행할 수 있다

예를 들어 플레이어의 캐릭터는 동쪽 성애자라서 동쪽 하늘을 바라보면

모든 체력을 회복하는 미친놈으로 만들고 싶다면...


당연히 이딴걸 수신해주는 Event 는 없기때문에 불가능해 보이지만

Spell 의 Conditions 를 이용해 할수 있다. 일단 Spell 을 만든다음 퀘스트를 하든

뭘하든 AddSpell 함수로 플레이어에게 이 스펠을 부여한다음에...

Spell 은 반드시 MagicEffect 가 필요하기 때문에 MagicEffect 를 만든다

그러니까 하나의 스펠이 있다면 그곳에 그 스펠의 효과를 정하는 MagicEffect 를

한개든 열개든 집어넣는 형태가 된다


먼저 ESP 에 Magic Effect 를 만든 다음에 Magic Effect Data 탭을 우클릭해

add 를 누르면 뭔가 창이 쭉 뜨는데 가운데 근처의 ArchType 을

Value Modifier 에서 Script 로 바꾼후 VMAD 에 스크립트를 넣어보자


ScriptName EastPhileMEScript Extends ActiveMagicEffect


Event OnEffectStart(Actor akTarget, Actor akCaster)

    Actor PlayerRef = Game.GetPlayer()

    Float MaxHealth = PlayerRef.GetBaseActorValue("Health")

    Float CurrentHealth = PlayerRef.GetActorValue("Health")

    Float RestoreHealth = MaxHealth - CurrentHealth

    PlayerRef.RestoreActorValue("Health", RestoreHealth) ;;// 함수를 보여주기 위해 일부러 복잡하게 짰음

EndEvent ;;// 실전에서는 체력을 알아낼 필요없이 바로 9999를 회복하는식이 가벼움


Event OnEffectStart 는 이 매직이펙트가 발동되는 그 순간 수신하는 이벤트이다

이제 이 매직이펙트가 발동되면 이벤트를 받아 체력을 회복할 것이다

그럼 이제 Spell 로 가서...


SPIT - Data 에 이대로 따라적고 아래쪽의 Effect 에 방금 만든 Magic Effect 을 넣은후

아래쪽의 Conditions 에 스크립트가 작동하고 싶을때 의 조건을 적으면 된다

이 스크린샷에서의 조건은 스펠에 걸려있는 사람이 3시방향 80도~100도 사이로 서서

우상단의 30도~70도 를 바라보면 조건을 만족해서 EastPhileME 매직이펙트가 발동하고

그 매직이펙트에는 Event OnEffectStart 스크립트가 걸려있으니 체력이 회복된다


만일 이 상태에서 고개를 돌리거나 몸을 틀어 범위에서 벗어나면 조건이 거짓이 되어

EastPhileME 매직이펙트 가 풀리는데 플리는 순간 스크립트를 작동시키고 싶다면

Event OnEffectFinish 이벤트를 사용하면 된다


그 방향을 계속 바라보면 매직이펙트가 풀리지 않고 계속 걸려있는 상태이므로

10분을 바라보든 1시간을 바라보든 체력은 바라본 그 순간 한번만 찰것이고

다시 채우려면 일단 고개를 돌려 Conditions 범위를 벗어나 매직이펙트가 풀리게 한뒤

각도를 다시 맞추면 다시 매직이펙트가 발동하면서 OnEffectStart 이벤트를 수신할 것이다


이 방법은 여러 상황에서 스크립트를 시작할 수 있으니 매우 좋지만 몇가지 단점이 있다


먼저 이 Spell 의 Condtitions 는 뭔가 조건을 게임 시스템 내에 등록해서

그 조건에 맞는 상황이 오면 발동하는 방식이 아니라 그냥 1초 정도마다

모든 Spell 의 Conditions 가 현재 상태와 맞는지 체크하는 방식이라 게임에 부담이 된다

작은 부담이므로 쓰면 안되는 건 아니지만 Persistent 플래그따위 보단 훨씬 크므로

Event 로 해결할수 있는 상황이면 Event 를 쓰는것이 좋다


그리고 0.몇초마다 체크하므로 정확한 타이밍에 발동해야만 하는 스크립트에는 쓸수 없다

챈이든 어디에든 스크립트는 느리다고 온갖 욕을 먹지만 적어도 Event 의 수신은

그 행위를 하는 즉시 받지만 스펠의 컨디션을 이용한 방식은 내 경험상...

최대 1초 정도의 지연이 있다 위에서 만든 동쪽 성애자 스펠을 예로 들면

순간적으로 하늘을 바라본후 딴곳을 바라보면 매직이펙트가 발동하지 않을수 있다는 말이다

그러므로 너무 순간적으로 일어나거나 칼같은 타이밍이 필요한 곳에는 쓰지 않아야 한다



24. Perk 과 Entry Point

Perk 은 엄청나게 복잡하고 내용이 많기 때문에 여기 전부 적을수가 없다

초보가 SSEEDIT에서 퍽을 만들면 작동 자체가 안되는 경우가 많기 때문에

기본적인 설정법만 여기 적어놓을테니 나머지는 직접 실험해


Perk 창의 데이터에 이런 정보가 있는데 Trait 은 그냥 False 로 하면 되고

Level 은 그냥 퍽의 습득 레벨 제한이고...

Num Ranks 는 이 퍽의 최대 랭크수인데 반드시 1 이상을 적어야한다

Playable 에 False 를 적으면 플레이어가 배우더라도 적용되지 않는다

Hidden 을 True 로 하면 효과는 적용되지만

게임 내에서 퍽의 이름이라든가 그런 항목이 보이지 않는다


아래의 Effects 가 퍽의 능력을 정하는 곳인데 이 Effect 는 3가지 종류가 있다

Abillity 는 퍽을 배우는 순간 이미 만들어둔 스펠을 적용하는 간단한 방식이고...

Quest + Stage 는 퍽을 배우는 순간 적어놓은 퀘스트의 적어놓은 스테이지가 진행된다

Entry Point 는 베데스다가 미리 만들어 놓은 여러가지 능력을 적용시키는 방식이다

대부분의 퍽을 이용한 모딩은 이 Entry Point 를 사용하게 된다


Rank 와 이 퍽의 현재 랭크가 같아야 적용된다

주의할 점은 이 Rank 의 시작이 1이 아니라 0이라는 것이다

즉 위의 Num Ranks 가 3이라면... 이 퍽의 랭크가 1이면 Rank 에 0을 적어야한다

이 퍽의 랭크가 최고레벨인 3일때 효과를 만드려면 Rank 에 2를 적어야한다

그리고 퍽 레벨이 오르면 그 아래 레벨의 효과는 적용되지 않는다


Entry Point 는 토드가 정해둔 "어느 순간" 을 감지해 그 효과를 바꾸는 것이다

종류가 너무 많으니 다 설명할수는 없고 그냥 공격 데미지를 늘리는 퍽을 만들어봄


스림에서 퍽 모드를 만들어본적이 없어서 확실하지는 않지만

아마 Mod Attack Damege 가 공격 순간의 데미지를 조절하는 Entry Point 일것이다

아래의 Function 에서는 계산식을 정할수 있는데 나는 배율인 Multiply Value를 했고

그 아래의 Perk Condition Tab Count 가 문제이다

여기서 숫자를 잘못 적으면 절대로 퍽이 발동하지 않고 심하면 ctd가 나게 된다

문제는 SSEEDIT 에서는 여기에 뭘 적어야 할지 알수 없다는 것이다 CK툴을 켠다


다른 모드를 로드할 필요는 없고 그냥 켜기만 하면 된다

Object Window 의 Actor 에서 Perk 을 고른후 우측 창에 우클릭 - New 를 누르고


그럼 뭔가 퍽 창이라는게 뜨는데 왼쪽 아래의 Perk Entries 에 우클릭 - New


오른쪽의 Entry Porint 를 체크하고 아래의 콤보 박스에 우리가 넣을

Mod Attack Damage 를 누르면 왼쪽 아래의 Conditions 의 항목이 바뀌게 된다

이 Mod Attack Damage 라는 엔트리 포인트에는 3종류의 컨디션을 넣을수 있다고 나온다


Perk Owner 는 이 퍽을 가진 사람이다

아래의 Conditions 에서 조건을 걸수 있는데

여기에 IsSneaking == 1 을 적는다면 이 데미지 증가퍽은 퍽주인이 은신일때만 적용될것이다


Weapon 은 퍽 주인이 손에 쥔 무기다

여기에 HasKeyword - WeapTypeDagger == 1 라고 적으면

WeapTypeDagger 라는 키워드가 있는 무기를 쓸때만 데미지가 오르게 된다


Target 은 이 엔트리 포인트의 경우 공격을 적중 시킨 상대이다

GetisRace - DragonRace == 1 이라면 오직 드래곤을 공격할때만 데미지가 오를것이다

여튼 Perk Condition Tab Count 의 개수와 종류를 알아냈으니 다시 SSEEdit 으로 가서


Perk Condition Tab Count 에 위에서 알아낸대로 3을 적고...

무기의 키워드가 WeapTypeDagger 일때만 데미지가 증가하게 하려면 이런 형식이 된다

주의할 것은 PRKC - Run On (Tab Index) 에 Weapon 이니까 2번을 적어야할것 같지만

이건 또 0번 부터 시작하기 때문에 1을 적어야 한다는 것이다

3번째인 Target 의 컨디션을 넣고 싶다면 PRKC - Run On (Tab Index) 에 2를 적어야 한다


아래쪽의 파라메터의 타입에 Float 를 고르고 2를 넣는다면 아까 위에서

Multiply Value 를 골랐으니 이 퍽을 가진 사람이 단검으로 공격하면 데미지가 2배가 된다



25. Message 에 숫자 변수 외에 문자 변수 집어넣기

Debug.Messagebox 함수의 알림창 이라든가 MCM의 메뉴에 한글을 쓰고 싶은데

폴아웃 4와 달리 스림의 스크립트에는 한글을 쓸 수가 없다

스크립트를 만든다음 xTranslator 로 변환하는 방법이 있지만 이 방법은

스크립트를 다시 컴파일하면 또 xTranslator 로 변환해야 되므로 귀찮다

이럴땐 String 을 Property 로 등록해서 변수로 쓰면 된다


이런식으로 esp 에 등록하고 스크립트 내에서


String Property sLoliLove Auto

Debug.Messagebox(sLoliLove)

라고 적으면 쉽게 한글을 출력할 수 있다


그리고 단순한 알림창이 아니라 이런 선택지를 만드려면 Message 를 사용해야한다


Flag 에 Message Box 를 선택하지 않으면 왼쪽 위에 알림 메세지가 뜨고 몇초뒤 사라진다


Flag 에 Message Box 를 선택했다면 DESC - Description 에 적은 내용이 알림창의 위에 적히고

아래의 Menu Buttons 에 적은 항목이 선택지로 나오며 Conditions 에 조건을 걸면

그 조건이 맞을때만 선택지가 등장하게 된다 메세지를 스크립트에서 쓸때는...


Message Property SDElinForeverMessage Auto


Event 아무이벤트

    int RateDay = 150

    SDElinForeverMessage.Show(RateDay)

EndEvent


라는 식으로 쓴다 Show 뒤에 int 나 Float 변수를 넣을수도 있는데 그렇게 하면

Message 내의 %.0f 라고 표시한 곳에 그 변수의 숫자가 표기된다 이경우

"SD엘린 존버 : 150 일차" 라는 알림창이 뜰 것이다

그리고 Show 앞에 변수를 배치해서 선택지로 무엇을 골랐는지 알아낼 수 있다


int i = SDElinForeverMessage.Show(RateDay) 라고 함수를 썼다면...

이것도 0번부터 시작하므로 숨쉬다 사망을 골랐다면  i = 0 이 되고

벌써 나왔는데?? 를 골랐다면 i = 1 이 된다


int i = SDElinForeverMessage.Show(RateDay)

if i == 0

    Game.GetPlayer().Kill()

elseif i == 1

    Debug.Messagebox("SDElin is God")

Endif


보통 이런 식으로 if 를 써서 선택지에 따라 다른 함수를 사용하는 스크립트를 짠다


메세지는 숫자 외에 다른 변수를 넣을수 없다는 문제가 있다. 위쪽의 스샷에서 메세지가

"플레이어 캐릭터의 이름을 ini 파일에 저장된 이름으로 바꿉니까?" 보다는...

"플레이어 캐릭터의 이름을 도바킨 으로 바꿉니까?" 가 훨씬 좋겠지만 메세지에는

기본적으로 숫자만 변수로 집어넣을수 있으므로 문자를 상황에 따라 바꿀수가 없다

하지만 약간의 꼼수를 써서 문자도 원하는 대로 바꿔 넣을수가 있다


먼저 베이스오브젝트를 하나 만들고 셀을 만든다음

만든 베이스오브젝트의 참조를 Persistent 플래그로 배치한 후...


퀘스트를 만들고 Start Game Enabled 를 박은후 Alias 를 만들고 방금 만든 참조를 넣는다

이 Alias 의 이름을 사용해야하므로 플래그에 반드시 Stores Text 를 넣어줘야 한다


Owner Quest 에 Alias 가 들어있는 퀘스트를 적고 위의 Desc - Description 에

<Alias=NameChangeAlias> 라고 적는다. 이렇게 하면 메세지가 뜰때

"SD엘린 존버 : 테스트 이름" 이라고 Alias 의 이름이 뜨게 된다


감이 오겠지만 메세지를 띄우기 전에 Alias 의 이름을 바꾸면 메세지에서도

원하는 문자를 변수로 자유자재로 사용할 수 있다


Message Property SDElinForeverMessage Auto


Activator Property 아까만든베이스오브젝트 Auto


Event 아무이벤트

    int iLevel = Game.GetPlayer().GetLevel()

    if iLevel < 30

        아까만든베이스오브젝트.SetName("30레벨까지만 참아봄")   //;; 다시 말하지만 한글 못씀

    else

        아까만든베이스오브젝트.SetName("시발 30렙 넘어도 안나오네")   //;; 이해하기 편하라고 적음

    Endif

    SDElinForeverMessage.Show()

EndEvent


이렇게 메세지를 띄우기 전에 SetName 로 Alias 의 베이스오브젝트의 이름을 바꾸면

자동으로 참조의 이름도 바뀌므로 플레이어의 레벨이 30 미만이라면

"SD엘린 존버 : 30레벨까지만 참아봄" 이 뜰것이고 30 이상이면 

"SD엘린 존버 : 시발 30렙 넘어도 안나오네" 이 뜨게 된다


테스트 하던 스림 모드를 지워버려서 폴아웃 스샷으로 대체하겠는데

이 모드는 체력이 다 되면 죽지 않고 쓰려져서 마을로 실려가는데

그곳에서 다시 쓰러진 곳으로 날아갈수 있는 기능이 있다


그리고 보통 게임을 끌때는 쓰러져서 마을로 돌아올때가 될텐데

다음에 게임을 다시켜면 대체 내가 어디서 죽었는지 헷갈릴수 있으니

쓰러진곳 위치의 이름을 GetCurrentLocation 과 GetName 으로 알아낸뒤에

위에 설명한 방법으로 BaseObject 의 이름을 위치로 바꾼후 메세지를 출력했다



26. Champollion 의 디컴파일 오류

Champollion 은 스크립트를 다시 소스파일로 변환하는 툴으로

정말로 좋은 프로그램이지만 완벽하지는 않다

보통 스크립트의 시작은 어떤 모드의 스크립트를 살짝 고쳐서

자신의 취향대로 수정하는 것부터 시작하게 되는데

Champollion 에 넣어서 나온 소스를 바로 다시 컴파일 했는데

컴파일이 되지 않고 오류를 쏟아내면 때려치우는 수 밖에 없다


보통 디컴파일 된 소스 파일이 다시 컴파일 되지 않는 경우는

스크립트와 연결된 다른 스크립트의 소스 파일이 없어서 나오는 오류인데

이럴때는 그냥 열심히 없는 소스 파일을 찾아 넣으면 해결된다


문제는 연결된 스크립트가 있는 스크립트가 아닌데도 디컴파일된 소스를

바로 다시 컴파일 해도 컴파일이 되지 않는 스크립트가 있다

가장 빈도가 높은 경우는 두가지이며 이 두가지는 생각보다 쉽게 고칠수 있다


하나는 스크립트에 Event 대신 Function 이라고 디컴파일이 된 경우이다


Function Oninit()

    함수내용

EndFunction


Oninit 은 분명히 Event 인데 Function 으로 표시되어 있고 이걸 다시 컴파일하면

무조건 컴파일 오류가 뜨게 된다

이건 침착하게 같은 이름의 이벤트가 있는 Function 을 Event 로 바꿔주고

EndFunction 도 EndEvent 로 바꿔주면 된다

오류가 아니라 진짜 Function 인 경우도 있으므로 이 이름이 이벤트에 있는지

위키에서 확실히 확인하면서 해야한다


다른 경우는 Float 수치의 디컴파일 오류이다


ScriptName ErrorScript Extends Quest


MiscObject Property Tetess Auto


Float ElinBreast = 98


Event OnInt()

EndEvent


디컴파일에 문제가 없어보이지만 밖에 만드는 Float 변수는 반드시 소수점이 있어야한다

그러므로 지금은 컴파일 오류가 뜨며 Float ElinBreast = 98.0 으로 소수점을 찍으면 된다


이 외에 20kb인 스크립트의 소스파일이 5kb 밖에 안된다던가

아예 스크립트가 중간에 끊겼다던가 하는 경우가 있는데

이런경우는 일반적인 방법으로는 해결할수 없으니 빠르게 포기해야한다




적고보니 엄청 기네 내가 뭐 컴공도 아니고 문과라서

같은 요소인데 속성이라고 했다가 유형이라고 했다가

부르는게 지맘대로 일수도 있으니 이해해줘


뭐 스크립트 만들다가 모르겠는거 있으면 여기 댓글로 물어보든가

질문이 좀 길거나 하면 질문글 쓰고 여기에 댓글로 링크 달면

보고 아는거면 알려줄게

그럼 안녕