이건 제가 인터넷에서 발견한 글입니다
절대 제가쓴거 아니고요 ㅡㅡ;;
아무튼 한번 퍼와봅니다
-----------------------------------------------------------------
[강좌] 스크립트 만들기 (1) - 준비작업
* 준비작업 *
안녕하세요.. 왕초보입니다.
저 스스로도 부족함이 많지만 용기를 내서 이렇게 강좌를 올립니다.
질문사항있으시면 게시판을 통해서 해주시면 제가 아는 범위내에서 답변을 드리겠습니다.
1. 스크립트 컴파일러 복사
우선 SoA디렉토리 안의 script compiler를 폴더 통째로 c: 드라이브의 루트 디렉토리로 복사하시고, script compiler라는 디렉토리명이 너무 기니까 script로 이름을 바꾸세요. 그래야 작업하기가 수월합니다.
복사하신다음 c:\script 디렉토리를 살피면 compiled, decompiled, error, source 서브디렉토리가 존재하는 것을 알 수 있습니다. compiled디렉토리 안에는 소스를 컴파일시 생성되는 스크립트 .bs 파일이 존재하고, decompiled 안에는 .bs를 소스로 역변환했을 때 생성되는 파일이 존재하게 됩니다. (그런데 저도 역변환은 아직 안해봤습니다.)
error는 컴파일시 만일 에러가 탐지된다면 해당 라인번호와 그 이유를 나타내는 텍스트파일이 존재합니다. 그리고 source는 여러분이 짠 스크립트 소스파일 .baf가 존재하는 디렉토리입니다.
그 외에 script디렉토리 안에는 컴파일러 실행파일및 컴파일에 필요한 .ids파일이 잔뜩 존재합니다.
2. MS DOS 실행
컴파일을 하려면 MS DOS상에서 해야 합니다. MS DOS 실행 아이콘을 더블 클릭하세요.
다음 도스의 디렉토리 이동명령인 cd 를 이용해서 c:\script와 c:\script\source를 왔다갔다 하면서 컴파일과 소스작성을 하실 수 있습니다.
도스 커맨드 입력상태에서 cd \script라고 하시면 컴파일 할 수 있는 디렉토리로 바로 갈 수가 있구요, 그 상태에서 cd source 하시면 소스 디렉토리로 가서 소스를 편집할 수 있습니다. 다시 컴파일 하려면 cd.. 혹은 cd \script라고 치시면 되돌아 올 수 있습니다. 현재 디렉토리에서 존재하는 파일을 확인하려면 dir 혹은 dir/w를 치시면 됩니다.
(참고 : source디렉토리엔 기본적으로 주어지는 스크립트들의 소스가 존재합니다.)
3. 스크립트 소스짜기 (텍스트 파일 편집)
스크립트 소스는 source디렉토리안에 존재해야 합니다. (c:\script\source)
확장명은 .baf이구요 텍스트 에디터를 이용해서 작성하시면 됩니다.
윈도상에서는 메모장을 이용하는게 편하구요, 만일 MS DOS상태라면 source디렉토리로 이동하셔서 (cd source 혹은 cd \script\source) edit 파일명(확장명 필수) 이렇게 입력하면 내장된 텍스트 에디터로
소스(.baf)나 ids파일을 편집할 수 있습니다.
스크립트 작성시엔 무척 반복되는 내용이 많이 나옵니다. 그걸 일일이 치는것 보다는 복사하는게 훨씬 편리하겠죠. 텍스트 일부를 복사하는 방법은 복사할 지점으로 가서 Shift키를 누른상태에서 화살표 상/하/좌/우, PageUp, PageDn키로 범위를 조절합니다. (반전으로 표시됩니다.) 다음 Ctrl + C를 눌러서 복사선택 하시구요,
그 내용을 옮길 부분으로 가서 Ctrl + V를 누르시면 복사가 됩니다.
앞서 반전으로 선택된 상태에서 del키를 누르면 해당부분만 삭제가 됩니다.
이정도 기능은 꼭 알아 두시길 바랍니다.
2. 컴파일러 사용 방법
cd \script[엔터]라고 치시면 여러분은 바로 컴파일을 실행할 수 있는 디렉토리로 가게 됩니다.
거기서 compile 소스파일명(확장명인 .baf는 생략할 것!) 이렇게 입력하시면
(단 소스파일은 반드시 c:\script\source에 이미 작성된 상태여야합니다.)
source디렉토리의 해당 소스를 컴파일한후 그 실행 스크립트는 compiled로,
에러내용은 error로 각각 생성해서 보냅니다. 만일 error안에 생성된 에러파일의 크기가 0 바이트가 아니라면 에러파일을 읽고 에러원인을 찾아서 수정후 다시 컴파일 하셔야 합니다.
(파일존재 확인은 dir[엔터] 로 하실수 있습니다.)
이렇게 에러 없이 컴파일이 되었다면 compiled안에 생긴 .bs파일을 SoA의 scripts디렉토리로 옮기시면 여러분이 작성한 스크립트를 겜상에서 선택할 수 있게 됩니다.
스크립트이름은 곧 파일명(.bs를 제외한)이 됩니다.
(주의 : 대부분 윈도에서 캐쉬메모리를 사용하기 때문에 컴파일을 했다 하더라도 생성파일이 바로 생기는 경우는 드뭅니다. 얼마간의 시간이 지나서 파일이 수정되거나 생기기 때문에 무척 짜증나더군요. )
3. SPELL.IDS 편집
자료실에서 쉐도우 키퍼를 꼭 다운받아서 설치하세요.
쉐도우 키퍼는 spell.ids파일을 편집하는데 꼭 필요합니다. (c:\script디렉토리에 존재)
발더스는 NPC별 innate기술과 Class별 innate기술 모두를 '마법'으로 간주하기
때문에 spell.ids에 정의가 되어 있습니다. 예를 들면 인퀴지터의 진실의 시야는
spell.ids를 살피면 INQUIS_TRUE_SIGHT로 정의가 되어 있으며 좌측에 해당 기술/스펠의
고유 번호가 존재합니다. 쉐도우 키퍼가 있으면 어떠한 innate기술 주문이라도 그 고유번호를 알아 낼 수가 있습니다. 이 고유번호를 알거나 혹은 spell.ids에 정의가 되어 있어야만 innate 스킬이나 주문시전이 가능합니다. 하지만 주의할 점은 spell.ids는 완전한 상태가 아닙니다. 예를 들면 켄사이 kai를 kia로 오타로 해놓고, 버서커 킷이나 바바리안 킷의 enrage는 아예 정의도 안되어 있습니다.
이렇게 spell.ids에서 빠진 기술/주문을 추가하기 위해서는 spell.ids파일을 편집해야 하며 이를 위해서는 read only 속성을 해제해야 합니다. 도스상태에서 attrib -r spell.ids하시면 편집이 가능해지고 윈도상태에선 오른쪽 단추를 눌러서 등록정보로 가신후 read only속성을 해제하셔야 합니다. 혹시 잘못 편집된 상태로 저장할 경우를 대비해서 spell.ids의 백업파일을 작성하시길 바랍니다. spell.ids에 코드를 추가/수정하는 방법은 추후에 설명하도록 하겠습니다.
(참고 : .ids 파일은 코드값을 정의해 놓은 파일이며 텍스트 에디터로 편집할 수 있습니다.)
(주의 : .ids파일의 첫 라인은 대개 무의미한(알수없는) 내용이 나오는데 절대 지우지 마시기
바랍니다.)
[강좌] 스크립트 만들기 (2) - IF..THEN..END
휴.. 이제야 본 강좌로 넘어오는군요.
발더스의 스크립트를 포함한 모든 컴퓨터 프로그래밍의 기본은 IF문입니다.
초단순 프로그램이면 IF문이 없을 때도 있지만 발더스의 스크립트의 경우는 IF로 시작해서 IF로 끝납니다.
이해를 돕기 위해서 몇몇 예를 들어보겠습니다.
다음은 전사 스크립트의 한 예입니다.
IF
적이 보이는가?
THEN
가장 가까운 적을 때려라
END
다음은 에어리 스크립트의 한 예입니다.
IF
적이 보이는가?
적과 너무 붙어있는가?
THEN
도망가라
END
다음은 성직자의 턴 언데드의 한 예입니다.
IF
언데드가 보이는가?
'턴 언데드' 중이 아닌가?
THEN
턴 언데드
END
다음은 마법사의 한 예입니다.
IF
적이 보이는가?
매직미사일 주문을 가지고 있는가?
THEN
가장 가까운 적에게 매직미사일 발사
END
아마 프로그래밍에 전혀 지식이 없는 분들도 예를 죽 살피면 무슨 소린지 감이 잡히실 겁니다.
IF와 THEN 사이에는 여러(혹은 1개) 조건들이 존재하고 THEN과 END사이에는
그러한 여러 조건들이 모두 참(true)일 경우 실행되는 행동이 들어가 있습니다.
행동은 한 개가 될 수도 있고 둘 이상이 될 수도 있고 혹은 없을 수도 있습니다.
그리고 주의하실 점은 어떤 조건은 행동을 포함하기도 합니다. 예를 들면 '적이 보이는가?'에서 '적을 보다'라는 행동이 포함되는 것이죠. 하지만 행동 자체에서 조건을 포함하지는 않습니다. 단 그 행동을 수행할 확률을 지정할 수 있습니다.
(특별한 경우가 아니면 100%로 지정되겠죠?)
이렇듯 스크립트는 IF THEN END 구조로 100% 이루어져 있으며 여러분이 구현하고 싶은
인공지능의 내용을 IF THEN END 문으로 구현하고 컴파일하면 멋진 스크립트가 만들어 집니다.
발더스 게이트는 스크립트 파일을 위에서 아래로 스캐닝하면서 조건을 만족하는 IF문을 발견시에 THEN과 END사이의 행동들을 순서대로 수행한 뒤 END를 만나면 다시 처음부터 스캐닝을 시작합니다. 여기서 주의하실 점은 위에서 아래로 수행되니까 중요도가 높은 행동들은 반드시 스크립트 파일의 앞부분에 놓아야 합니다. 그렇지 않을 경우 의도한 대로 움직이지 않을 것입니다. 만일 END를 만나도 처음부터 re-scan하지 않고 다음 IF문부터 검색시키려면 Continue() 문을 씁니다. 이는 나중에 다시 설명하도록 하겠습니다.
다음은 앞서 말한 마법사의 매직미사일 시전을 IF THEN END로 구현한 것입니다.
IF
See([ENEMY]) // 적이 보이는가?
HaveSpell(WIZARD_MAGIC_MISSILE) // 매직미사일 주문을 가지고 있는가?
THEN
RESPONSE #100 // 100%의 확률
Spell(NearestEnemyOf(Myself), WIZARD_MAGIC_MISSILE)
// 가까운 적에게 매직미사일 발사
END
무척 쉽지 않습니까? 매직미사일이라는 마법코드는 spell.ids파일에 고유번호로 정의되어 있으며 여기엔 성직자마법, NPC별 innate기술, 클래스별 innate기술 등이 들어있습니다.
앞으로는 이 spell.ids 파일을 뜯어고치면서 마법을 구현하게 될 것입니다.
다음 강좌는 기본적으로 제공되는 스크립트가 얼마나 간단하게 작성되었는지 전사스크립트를 분석하면서 살펴보고자 합니다.
겜상에서 FIGHTER1을 선택시에 팔라딘용 스크립트로 설명이 되어 있을 겁니다.
그런데 팔라딘용이라면서 Lay On Hands와 Cure Medium Wounds 단 두가지만 지원하는 군요. 만일 인퀴지터면 전혀 소용이 없는 스크립트입니다.
그럼 하나씩 뜯어보기로 합시다.
// * Mar 22, 2000.
// * Brent Knowles
// * Paladin. A paladin script, heals hurt party members. Protects the weak.
//*Lay On Hands*
IF
ActionListEmpty() // 아무 행동도 하지 않을 경우
HPPercentLT(MostDamagedOf(),50) // 가장 많이 부상당한 동료의 HP가 50%미만
!StateCheck(MostDamagedOf(),STATE_INVISIBLE) // invisible 상태가 아니면
HaveSpell(PALADIN_LAY_ON_HANDS) // 팔라딘의 성수치료주문을 가지고 있으면
THEN
RESPONSE #100
Spell(MostDamagedOf(),PALADIN_LAY_ON_HANDS) // 동료를 치료함
END
//*Heal*
IF
ActionListEmpty()
HPPercentLT(MostDamagedOf(),50)
!StateCheck(MostDamagedOf(),STATE_INVISIBLE)
HaveSpell(CLERIC_CURE_MEDIUM_WOUNDS) // 성직자의 중급치료주문이 있을 때
THEN
RESPONSE #100
Spell(MostDamagedOf(),CLERIC_CURE_MEDIUM_WOUNDS) // 치료
END
//*Combat*
IF
ActionListEmpty()
Help([PC]) // 도움요청을 하는 동료가 있는가?
THEN
RESPONSE #100
AttackReevaluate(LastAttackerOf(LastHelp(Myself)),60) // 그 동료를 공격한 적을 60초간 공격
END
IF
ActionListEmpty()
See(NearestEnemyOf(Myself)) // 가장 가까운 적이 보일 때
THEN
RESPONSE #100
AttackReevaluate(NearestEnemyOf(Myself),60) // 60초간 공격
END
어떻습니까? 너무 무성의하지 않습니까?
이 스크립트는 몇몇 허점을 내포하고 있습니다.
1. AttackReevaluate문의 남발입니다. AttackReevaluate 문은 어떤 대상을 공격중에 다른 적으로부터 공격당할 경우 그 적으로 공격대상이 바뀝니다. 즉 동료를 보호할 때 이놈 저놈 때리고 다니다가는 그사이 동료가 맞아죽기 딱이겠죠. AttackReevaluate를 쓰면 안되고 Attack문을 써야 합니다. 혹은 1라운드간(6초)만 한놈만 패고 싶으시면 AttackOneRound문으로 바꾸면 됩니다.
주의 : 한 놈만 패려면 Attack문을 쓰세요.
2. 공격시간의 과다한 책정입니다. 60초면 아마 80%이상의 전투가 이미 끝나있을 겁니다.
6의 배수로 1 ~ 3라운드정도로 책정하면 적당할 겁니다. (즉 6, 12, 18)
주의 : 전투시간이 거의 1분 내외라는 점을 고려하세요.
3. 이 스크립트는 우습게도 전투 중에 동료를 거의 치료하지 못합니다. 전투 후에 치료합니다.
Lay On Hands나 중급치료의 IF문을 살펴보시면 ActionListEmpty()문이 존재함을 알 수 있습니다.
이 구문은 아무행동도 하고있지 않을 경우에 참으로 판별됩니다.
만일 적을 발견하고 AttackReevaluate문에 의해 신나게 칼질하고 있다면 항상 거짓으로 판명되어 전투중에 동료가 죽던 살던 상관 안합니다. 그러나 공격시간이 끝나면 (1분) 그제서야 동료를 치료하려는 시도를 할 겁니다.
주의 : 치료같은 시급한 행동은 ActionListEmpty()문이 있어서는 절대 안됩니다.
[강좌] 스크립트 만들기 (4) - FIGHTER2 분석
레인저용 스크립트라고 설명이 되있을 겁니다. 조금 어려울지 모르지만 한번 살펴봅시다.
알고보면 쉽습니다.
// * Date Created: Mar 21, 2000.
// * Brent Knowles
// *Ranger Script: combines hiding and ranged attack abilities
// * A ranged weapon script. Attempts to stay out of melee ranged with opponents,
// * Although will switch to melee weapons if needed.
// *move away and hide
IF
ActionListEmpty()
HPPercentLT(Myself,50) // 피가 절반이상 달고
See(NearestEnemyOf(Myself)) // 적이 보이고
!StateCheck(Myself,STATE_INVISIBLE) // invisible상태가 아니면
THEN
RESPONSE #100
RunAwayFrom(NearestEnemyOf(Myself),75) // 가까운적으로부터 5초간 도망 (숫자는 15분의 1초를 뜻합니다.)
Hide() // 그림자 숨기 시도
END
IF
ActionListEmpty()
Delay(30) // 30초마다
!StateCheck(Myself,STATE_INVISIBLE) // invisible상태가 아니면
THEN
RESPONSE #100
Hide() // 그림자 숨기
END
//*Defense*
IF
ActionListEmpty()
See(NearestEnemyOf(Myself)) // 적을 보면
HaveSpell(CLERIC_ARMOR_OF_FAITH) // 신념의 갑옷 주문이 있는가?
CheckStatGT(Myself,3,ARMORCLASS) // AC가 3보다 클 경우
HPGT(LastSeenBy(Myself),20) // 방금 본 적의 HP가 20이상이면
THEN
RESPONSE #100
Spell(Myself,CLERIC_ARMOR_OF_FAITH) // 자신에게 신념의갑옷 시전
END
IF
ActionListEmpty()
See(NearestEnemyOf(Myself))
HaveSpell(CLERIC_BLESS) // 축복 주문이 있는가?
HPGT(LastSeenBy(Myself),20)
!StateCheck(LastSeenBy(Myself),STATE_BLESS) // 자신이 축복상태가 아니라면
THEN
RESPONSE #100
Spell(Myself,CLERIC_BLESS) // 자신에게 축복주문 시전
END
//*Charm Animals*
IF
ActionListEmpty()
See(NearestEnemyOfType([0.ANIMAL])) // 근처에 동물형태의 적이 있는가?
HaveSpell(RANGER_CHARM_ANIMAL) // 동물매혹의 innate기술이 있는가?
THEN
RESPONSE #100
Spell(LastSeenBy(Myself),RANGER_CHARM_ANIMAL) // 동물매혹
END
//*Heal*
IF
ActionListEmpty()
HPPercentLT(MostDamagedOf(),50) // 동료중에 피가 절반이상 달고
!StateCheck(MostDamagedOf(),STATE_INVISIBLE)
HaveSpell(CLERIC_CURE_MEDIUM_WOUNDS) // 성직자 중급치료가 있으면
THEN
RESPONSE #100
Spell(MostDamagedOf(),CLERIC_CURE_MEDIUM_WOUNDS) // 동료치료
END
IF
ActionListEmpty()
HPPercentLT(MostDamagedOf(),80) // 동료중 피가 80%미만
!StateCheck(MostDamagedOf(),STATE_INVISIBLE)
HaveSpell(CLERIC_CURE_LIGHT_WOUNDS) // 가벼운상처치료 주문이 있으면
THEN
RESPONSE #100
Spell(MostDamagedOf(),CLERIC_CURE_LIGHT_WOUNDS) // 치료
END
//*Heal Self* // 자기자신을 치료
IF
ActionListEmpty()
HPPercentLT(Myself,50)
HaveSpell(CLERIC_CURE_MEDIUM_WOUNDS)
THEN
RESPONSE #100
Spell(Myself,CLERIC_CURE_MEDIUM_WOUNDS)
END
IF
ActionListEmpty()
HPPercentLT(Myself,80)
HaveSpell(CLERIC_CURE_LIGHT_WOUNDS)
THEN
RESPONSE #100
Spell(Myself,CLERIC_CURE_LIGHT_WOUNDS)
END
//*Use Healing Potions* // 힐링포션 마시기
IF
ActionListEmpty()
HasItem("POTN08",Myself) // Minor Healing Potion이 있는가?
HPPercentLT(Myself,80)
THEN
RESPONSE #100
UseItem("POTN08",Myself) // 마신다.
END
IF
ActionListEmpty()
HasItem("POTN52",Myself) // Extra Healing Potion이 있는가?
HPPercentLT(Myself,50)
THEN
RESPONSE #100
UseItem("POTN52",Myself) // 마신다.
END
//*combat*
IF
ActionListEmpty()
AttackedBy([ANYONE],DEFAULT) // 어느누구에게라도 공격당하면
Range(LastAttackerOf(Myself),4) // 접근전 거리안에 있을경우
THEN
RESPONSE #100
EquipMostDamagingMelee() // Melee무기로 바꿔잡고
AttackReevaluate(LastAttackerOf(Myself),30) // 날 공격한자를 30초간 공격
END
IF
ActionListEmpty()
See(NearestEnemyOf(Myself)) // 가장 가까운 적이
!Range(NearestEnemyOf(Myself),4) // 거리가 4 ~ 10사이에 있으면
Range(NearestEnemyOf(Myself),10)
THEN
RESPONSE #100
RunAwayFrom(NearestEnemyOf(Myself),45) // 3초간 도망간다.
END
IF
ActionListEmpty()
See(NearestEnemyOf(Myself))
!Range(NearestEnemyOf(Myself),10) // 10이상의 거리에 적이 있으면
THEN
RESPONSE #100
EquipRanged() // 장거리무기로 바꿔잡고
AttackReevaluate(NearestEnemyOf(Myself),30) // 30초간 공격
END
IF
ActionListEmpty()
See(NearestEnemyOf(Myself)) // 가장 가까운 적이
Range(NearestEnemyOf(Myself),4) // 접근전 무기범위내에 있으면
THEN
RESPONSE #100
EquipMostDamagingMelee() // 접근전무기로 바꿔잡고
AttackReevaluate(NearestEnemyOf(Myself),30) // 30초간 공격
END
IF
ActionListEmpty()
See(NearestEnemyOf(Myself))
!Range(NearestEnemyOf(Myself),4) // 적이 접근전 무기범위 밖이면
THEN
RESPONSE #100
EquipRanged() // 장거리무기로 바꿔잡는다.
EquipMostDamagingMelee() // 접근전무기로 바꾼다.
AttackReevaluate(NearestEnemyOf(Myself),30) // 30초간 공격
END
조건중에 !이 붙은걸 볼 수 있는데 이것은 not을 의미합니다. 그 조건이 참이면 거짓이고 거짓이면 참으로 되돌립니다. Range(NearestEnemyOf(Myself), 4)는 가장 근접한 적이 접근전 무기 범위 안에 있을 경우 참이 되는데 앞에 !를 붙이면 반대로 거짓이 됩니다. 거꾸로 적이 접근전 무기 범위 밖에 있을 경우 참이 되겠지요.
비록 레인저용 스크립트라고는 하나 그림자숨기, 물약 마시기, 신앙갑옷/축복 주문 시전, 동물매혹, 거리유지하면서 활쏘기가 전부임을 알 수가 있습니다.
이 스크립트의 허점을 분석해 봅시다.
1. AttackedBy([ANYONE],DEFAULT)문에서 동료를 공격할 여지를 가지고 있음을 알 수 있습니다.
또한가지 의문은 ea.ids파일에는 ANYONE이 아니라 ANYTHING으로 정의되어 있는데
대체 어디서 ANYONE이 나왔는지 불가사의로군요. 오타가 아닐까 추측됩니다만...
2. 그리고 거리를 측정하면서 장거리 근접무기로 바꿔가며 공격하는 방법은 눈여겨 볼만 하구요,
다른 의문점은 맨 마지막에 장거리무기로 바꿔 잡고서 다시 접근전무기로 바꿔 잡는데 이건 논리적으로 오류이죠. EquipMostDamagingMelee()를 없애야 합니다. 그리고 스크립트 스캐닝이 여기까지 오기 이전에 이미 앞부분의 IF문에서 걸려서 적을 공격할테니 여기까지 올 가능성은 희박합니다. (한마디로 빼버려도 무방)
3. 또 한가지 헛점은 물약을 마실때 Minor Healing Potion은 거의 도움이 안됩니다. 물약마시기를 구현하실때는 아예 빼버리셔도 무방하시고 만일 집어넣어야 겠다면 Extra Healing Potion을 먼저 마시도록 해야합니다. 왜냐하면 물약 마실때나 마법 시전시엔 1라운드간의 딜레이가 존재하게 되거든요. 뭐 리니지처럼 미친듯이 물약 마시기가 가능하면 상관없겠지만요.
4. 그림자 숨기시에 Delay(30)문은 30초마다 한번씩 체크하도록 하는 조건인데 이거 제대로
작동 안됩니다. 해보시면 잘 되는 것 같지만 정밀도가 상당히 떨어집니다. 차라리 Timer를
이용하는게 훨씬 정확합니다. 타이머 세팅방법은 나중에 설명하겠습니다.
5. 또한 이 스크립트는 마법 사용에 허점을 보이는데 신념의 갑옷은 AC가 떨어지는게 아니라 무기에대한 저항력이 생기는 마법입니다. 맞으면 HP가 덜 준다는 얘기지요. AC를 떨어뜨리는 마법은 Barkskin이죠. 그런데 조건에서 보면 AC가 3보다 큰지 검사하고 있으니.. 만일 신념의갑옷 주문을 3개 암기하고 있고 AC가 3보다 크다면 적과 조우시에 계속 신념의 갑옷만 시전하고 있는 엽기적 행위가 예상됩니다. 이를 막으려면 신념의 갑옷 시전시에 타이머를 설정해주던가 지역변수로 이미 시전되었는가를 확인하는 변수로 사용해야 합니다. 이 스크립트의 제작자는 성직자마법에 대한 이해가 부족한 상태에서 스크립트를 만들었음을 알 수 있습니다.
참고로 모든 마법과 물약사용은 딜레이(6초)가 발생하기 때문에 이를 타이머로 관리할 필요가 있습니다. 그렇지 않으면 주문시전후 딜레이기간동안 가만히 서서 다음주문을 외우려 할 것이며 그건 결과적으로 6초의 낭비를 가져올 것입니다. 법사라면 그 6초동안 장거리무기로 공격하게 할 수 있겠죠?
[강좌] 스크립트 만들기 (5) - FIGHTER3 분석
동료 마법사를 두들겨 패기로 유명한 마법사공격 스크립트입니다. 독자중엔 너무 어렵다고 느끼시는 분이 계실텐데 지금은 제가 강좌방향을 약간 수정했습니다. 단계적으로 가지 않고 우선 기존 스크립트 소스를 살펴보면서 IF THEN END문에 익숙해지고 과연 이들 스크립트 제작자들의 범한 실수가 어디서 비롯되었는가를 알아보는 게 나중에는 훨씬 이해가 빠르리라고 생각되는군요. 한번 어디서 잘못되었는지를 살펴봅시다.
// * Spellcaster slayer script
// * Mar 22, 2000.
// * Brent Knowles
// * User of this script will target attacks against spell-casters first.
//*Combat*
//*target spell-casters first, first with ranged weapons (if at range)
// * and then with melee weapons
IF
ActionListEmpty()
OR(4) // OR(4)문은 다음 4가지 조건중 어느 하나라도 참일경우 참으로 되돌립니다.
See(NearestEnemyOfType([0.0.0.MAGE_ALL])) // 적마법사가 보일경우
See(NearestEnemyOfType([0.0.0.CLERIC_ALL])) // 적클레릭이 보일경우
See(NearestEnemyOfType([0.0.0.DRUID_ALL])) // 적드루이드가 보일경우
See(NearestEnemyOfType([0.0.0.BARD_ALL])) // 적 바드가 보일경우
!Range(LastSeenBy(Myself),10) // 거리가 10이상이면
THEN
RESPONSE #100
EquipRanged() // 장거리무기로 바꾼다.
AttackReevaluate(LastSeenBy(Myself),30) // 마지막에 본것을 30초간공격
END
IF
ActionListEmpty()
OR(4)
See(NearestEnemyOfType([0.0.0.MAGE_ALL]))
See(NearestEnemyOfType([0.0.0.CLERIC_ALL]))
See(NearestEnemyOfType([0.0.0.DRUID_ALL]))
See(NearestEnemyOfType([0.0.0.BARD_ALL]))
THEN
RESPONSE #100
EquipMostDamagingMelee() // 접근전무기로
AttackReevaluate(LastSeenBy(Myself),30) // 마지막에 본것을 30초간공격
END
// * standard combat
// 이하부분은 앞서 올린 FIGHTER1과 FIGHTER2를 눈여겨 보셨다면 이해하실 수 있습니다.
//*Combat*
IF
ActionListEmpty()
Help([PC])
THEN
RESPONSE #100
AttackReevaluate(LastAttackerOf(LastHelp(Myself)),60)
END
IF
ActionListEmpty()
See(NearestEnemyOf(Myself))
!Range(NearestEnemyOf(Myself),10)
THEN
RESPONSE #100
EquipRanged()
AttackReevaluate(NearestEnemyOf(Myself),30)
END
IF
ActionListEmpty()
See(NearestEnemyOf(Myself))
THEN
RESPONSE #100
EquipMostDamagingMelee()
AttackReevaluate(NearestEnemyOf(Myself),30)
END
뭐 스펠캐스터 슬레이어라는 거창한 타이틀과는 안어울리게 적마법사가 눈에 띄면
적마법사를 공격하고, 장거리/접근전 공격하는게 전부로군요. 정말 간단한 스크립트입니다.
여러분도 이정도쯤은 우습게 만드실 수 있습니다.
이 스크립트의 치명적인 약점이 어디에 있을까요?
IF
ActionListEmpty()
OR(4) // OR(4)문은 다음 4가지 조건중 어느 하나라도 참일경우 참으로 되돌립니다.
See(NearestEnemyOfType([0.0.0.MAGE_ALL])) // 적마법사가 보일경우
See(NearestEnemyOfType([0.0.0.CLERIC_ALL])) // 적클레릭이 보일경우
See(NearestEnemyOfType([0.0.0.DRUID_ALL])) // 적드루이드가 보일경우
See(NearestEnemyOfType([0.0.0.BARD_ALL])) // 적 바드가 보일경우
THEN
RESPONSE #100
AttackReevaluate(LastSeenBy(Myself),30) // 마지막에 본것을 30초간공격END
바로 마지막의 LastSeenBy(Myself)문에 있습니다. 내가 마지막에 본 것을 공격하라고 되어 있으며 앞서 조건에선 See( )문으로 적 마법사를 먼저 본 상태니까 논리적으로는 아무런
오류가 없습니다. 하지만 조건을 모두 판별하고서 AttackReevaluate를 실행할 때 LastSeenBy(Myself)가 적이 아니라 동료로 바뀐다면??? 동료를 패게 됩니다.
LastSeenBy(Myself)는 0.01초 사이에 바뀔 수 있음을 주의해야합니다. 만약 조건을 모두 만족하고서 Attack바로 직전에 누군가 나를 쳤다던가 어떤 주문시전자의 마법효력이 내게 발생한다면 LastSeenBy(Myself)의 대상은 나를 쳤거나 내게 주문효력을 일으킨 주문시전자로 바뀌게됩니다. 즉 LastSeenBy()문은 언제라도 그 대상이 바뀔 수 있으며 아군/적군을 가리지 않습니다. 따라서 LastSeenBy()으로 지칭한 적을 공격하려면 조건중 맨 마지막에 !InParty(LastSeenBy(Myself))문을 집어 넣어야 동료를 때리는 행위를 어느정도 막을 수 있으며 사실 그것조차 완벽하게 막아준다고 보장은 못합니다. 이런 부작용을 완벽하게 막으려면 LastSeenBy문을 쓰지 않고 스크립트를 작성해야 합니다.
다음은 그러한 예입니다.
IF
ActionListEmpty()
See(NearestEnemyOfType([0.0.0.MAGE_ALL])) // 적마법사가 보일경우
THEN
RESPONSE #100
AttackReevaluate(NearestEnemyOfType([0.0.0.MAGE_ALL]),30) // 적마법사공격
END
어떻습니까? 정말 별 내용 없죠? 걍 술술 넘어가시면서 이런것이 있구나.. 하는 정도면 충분합니다.
OR(4)같이 처음 보는 문법은 눈여겨보시구요.. 다음 강좌는 전사 마지막스크립트인 FIGHTER4입니다.
[강좌] 스크립트 만들기 (6) - FIGHTER4 분석
그래도 가장 부작용이 덜한 주문시전자 보호 스크립트입니다.
// * melee, aggressive
// * Will try and stay out of range, but once in close will start attacking melee.
//*Check for Injured Mages/Clerics to defend*
IF
See([ENEMY]) // 적이 시야에 있고
OR(2)
See([PC.0.0.MAGE_ALL]) // 동료중 메이지가 보이거나
See([PC.0.0.CLERIC_ALL]) // 동료중 클레릭이 보이면
HPPercentLT(LastSeenBy(Myself),50) // 마지막으로 본게 HP가 50% 미만일경우
!Dead(LastAttackerOf(LastSeenBy(Myself))) // 동료의 공격자가 살아있고 --> 레퍼런스에는 없는 문법인데 컴파일된 스크립트를 찾아보니 이상하게 컴파일 되어 있더군요. 빼버리는게 나을듯
Exists(LastAttackerOf(LastSeenBy(Myself))) // 존재하면
THEN
RESPONSE #100
AttackReevaluate(LastAttackerOf(LastSeenBy(Myself)),60) // 60초간 공격
END
//*Combat*
IF
ActionListEmpty()
Help([PC])
THEN
RESPONSE #100
AttackReevaluate(LastAttackerOf(LastHelp(Myself)),60)
END
IF
ActionListEmpty()
See(NearestEnemyOf(Myself))
THEN
RESPONSE #100
AttackReevaluate(NearestEnemyOf(Myself),60)
END
역시 LastSeenBy문이 쓰였군요. LastSeenBy문은 정확한 성능을 보장하지 못합니다.
또한 논리적인 오류 또한 보이는데 동료 마법사와 클레릭이 둘 다 있을 경우 마법사의 HP만 50%미만인지 체크할 가능성이 큽니다. 그리고 마법사는 대체로 HP가 적은데 그중 50%미만이라면 거의 사망 일보직전이겠죠.. 80%정도로 올리는게 더 안전할 것입니다. 확실히 체크하려면 Player1부터 6까지 마법사인지 검사하고 HP검사해서 각각 보호해주도록 작성해야 할 것입니다.
이상 전사용 스크립트 4가지를 모두 살펴보았습니다. 논리적으로는 오류가 없더라도 테스트해보면 잘못된 작동을 하는 경우가 많습니다. 다음 강좌부터는 상황판단의 기본이 되는 '객체와 객체식별'로 들어가겠습니다.