» TeamX (Архив Форума)«


Форум TeamX » Исследования » Делимся опытом (Наследие "Техподдержки")

Переход по темам
<< Пред. След. >>
Страницы этой темы [ 1 2 3 ] Все собщения

 
Raven
Пользователь

Откуда: Владик
Регистрация: Февр. 2004

Всего: 408 сообщений

Обещанный рассказ про atoi. С опозданием в неделю :-(

Atoi - это процедура преобразующая string-число в int-число.

Проблема:

Для тех, кто не смотрел исходники компилятора скриптов Фола (точнее, klngon Academy, однодвижковой игры) - коммент из parse.c:
----------------------------------
/*
* Parser for SSL (Startrek Scripting Language).
*
* All variables are "typeless"; their type is defined by how
* they are used.  All types are promoted to the "highest" type
* in an expression, where the order is from lowest to highest:
* int, float, string.
*
* So,
* if you do 2 + "foo", you get back "2foo".
* if you do 2 + 2.4,   you get 4.4
* if you do 2 + 4,     you get 6 (hopefully :)
* if you do 2 + 2.4 + "foo", you get "4.4foo", since
* expressions are parsed left to right.
* if you do 2 + (2.4 + "foo") you get "22.4foo", due
* to the parentheses.
* etc.
*/
----------------------------------

Т.е.:
----------------------------------
variable a:="1234";
a+=1;
display_msg("a="+a);
----------------------------------
выведет "a=12341".

Это ставит крест на хранении в msg-файлах числовых значений. А делать так было бы удобно, т.к. тогда можно хранить в msg PID-ы предметов, номера/значения квестовых GVAR, номера "домашних" тайлов - т.е. изменяющиеся в процессе модинга величины - что упростило бы разработку, избавив модера от необходимости отслеживать и перекомпилировать всё, что использует ГВАРу/PID.

Кроме того, можно было бы хранить там количество экспы, денег, PID-ы предетов выдаваемых в качестве квестовых наград; временные таблицы для создания НПС, двигающихся по карте "по расписанию"; значения скилов/параметров, необходимых для выдачи/решения квеста - т.е. упростить итоговую балансировку мода.

Принцип работы:

Итак, у нас есть строки. Что мы можем с ними делать? Только склейку? Неправильный ответ. Мы можем их сравнивать:
"abc"=="abc" //true
"abc">"abc" //false
"b">"a"     //true
"b">"abczz" //true
"abc">"abd" //false
"b">"A"     //false

Принцип понятен? Строки сравниваются посимвольно, как только найдено различие - сравниваются ASCII-коды символов. Символ с бОльшим номером делает больше всю строку.

Что нам это даёт? Рассмотрим строку "123". Что мы можем сказать?
"123">"1". И "123"<"2"
Уловили? Мы только что выявили первый символ. Запомнили его. Смотрим дальше.
"123">"11", "123">"12", "123"<"13".
Нашли второй. Прибавили к первому.
"123">"121","123">"122","123"=="123". Нашли строку. Параллельно с конструированием строки мы можем собирать число - поразрядно.

Пишем код:
----------------------------------
procedure atoi(var str) begin
 var tmp_str:=""; //инициализация обязательна, иначе tmp_str="0"
 var value; //числовое значение
 //""+0=="0"
 if (str>tmp_str+0 and str<tmp_str+1) or str==tmp_str+0 then begin
   tmp_str+=0; //дополняем строку. "123"+0=="1230"
   value:=value*10+0; //"дописываем" ноль справа
 end
 //тоже для остальных цифр
 <...>
----------------------------------

Всё это должно быть загнано в цикл:
----------------------------------
procedure atoi(var str) begin
 var tmp_str:=""; var value;
 var i;  //для ограничения на длину преобразуемой строки

 while str!=tmp_str and i<16 do begin
   if (str>tmp_str+0 and str<tmp_str+1) or str==tmp_str+0 then begin
     tmp_str+=0;
     value:=value*10+0;
   end
   <...>
   i+=1;
 end
 if i!=16 then return value; //строка >16 символов либо содержит нецифровые символы
 else return -1;
end
----------------------------------

Теперь чуть-чуть укоротим, введя двойной цикл:
----------------------------------
procedure atoi(var str) begin
 var tmp_str:=""; var value; var i;
 var j;  //для ограничения на длину преобразуемой строки

 while (str!=tmp_str) and j<16 do begin
   //относительно сложное выражение. Надеюсь, разберётесь :-)
   while not ((str>tmp_str+i and str<tmp_str+(i+1)) or str==tmp_str+i) and i<9 do i+=1;
   //при i=9 предыдущее выражение не работает (str>...+9 and str<...+10)
   //случай с девяткой обрабатываем отдельно
   if i==9 and not (ostr>str+9 or ostr==str+9) then i:=10;
   //дописываем tmp_str и value
   if i<10 then begin tmp_str+=i; value:=value*10+i; end
   //в строке попался нечисловой символ
   else return -1;
   i:=0;j+=1;
 end
 return val;
end
----------------------------------
После удаления комментариев получится меньше 10 строк :-)

Ну вот и всё. Теперь если msg:
----------------------------------
{214}{}{2000}
----------------------------------
,то код
----------------------------------
give_exp(atoi(mstr(214));
----------------------------------
даст Чузу 2000 экспы.

Учитывая мои предыдущие наработки (идея присвоения мсг-шкам уникальных строк-меток), можно подумать о создании этакого ini-файла, где могут быть прописаны:
1. экспа за каждый квест
2. изменения кармы за квесты
3. значения квестовых GVAR (взял/сделал/сдал и т.д.)
4. награды за квесты

В общем, эдакие const GVAR-ы. Всё в одном месте, перекомпиляции не требуется. Балансировка упрощается.

P.S. Mynah, ты говорил про hear/see? Запость, не стесняйся.




=================================================



Диалог между NPC.

Никогда не задумывались как можно организовать floater-дилог между двумя НПС? Рассказываю.

Решение:

Итак, у нас два объекта. К ним привязан скрипт (один и тот же). Различаются они по указателям.Каждый знает указатель другого (импорт/экспорт - предыдущие наработки). Что такое с точки зрения скриптера floater-диалог? Это последовательность вызовов floater-ов, разнесённая по времени (floater над первым НПС, пауза в секунду, floater над другим НПС ...). Т.е. очевидно, что реализуется он с помощью add_timer_event. Начинаем писать скрипт:

msg:
------------------------------
{1}{}{Phrase1}
{2}{}{Phrase2}
{3}{}{Phrase3}
{4}{}{Phrase4}
------------------------------

Код:
------------------------------
export var begin
ptr_chat_1;
ptr_chat_2;
end

procedure start begin
end

procedure map_enter_p_proc begin
 if not ptr_chat_1 then ptr_chat_1:=self_obj;
 else ptr_chat_2:=self_obj;
end

procedure talk_p_proc begin
 var i:=1; //i==началу блока реплик - пред. наработки
 
 while (mstr(i)!="Error") do begin
   //добавляем события по таймеру ч/з 0,1,2,3 секунды
   add_timer_event(self_obj,i-1,i);
   i+=1;
 end
end
 
procedure timed_event_p_proc begin
 var who; //кто говорит
 //если номер реплики чётный - говорит первый чар, иначе - второй
 if fixed_param%2 then who:=ptr_chat_1;
 else who:=ptr_chat_2;

 float_msg(who,mstr(fixed_param),0);
end
------------------------------

Всё. Лучше, конечно, добавить код для поиска начала блока фраз - для унификации.

С этим скриптом появляется один "баг" - сколько раз ткнули в НПС - столько диалогов и удет. Одновременно. Как можно исправить? Завести вару, ставить её один в начале диалога, сбрасывать в конце. Маленькая трабла - в конце значит после последнего вызова timed_event, а не после добавления событий (которое происходит сразу и очень быстро). Т.е. мы должны добавить 4 таймерных события для вывода фраз + ещё одно для сброса переменной. Как их различать? Ведь номер фразы (спасибо Алану) может быть любым - и отрицательным, и положительным, и нулевым - т.е. принять некоторые фиксированные значения параметра за "установить/сбросить флаг" мы, вообще говоря, не можем.

Выход: "сборные" параметры. Как пакеты в локальных сетях. Т.е. мы "забираем" один разряд передаваемого значения под "флаг", определяющий тип значения.

Например, флаг "0" озачает, что параметр - номер строки в msg для floater. Если пришло 3130 - значит надоы вывести строку 313, пришло 120 - выводим строку 12, 900 - строку 90. А флаг "1" будет означать сброс/установку вару "диалог уже идёт".

------------------------------
export var is_chatting;
<...>
//первый разряд числа
#define flag  (fixed_param%10)
//число без первого разряда
#define param (fixed_param/10)

procedure timed_event_p_proc begin

 if flag==0 then begin

   if param%2 then float_msg(ptr_chat_1,mstr(param),0)
   else float_msg(ptr_chat_2,mstr(param),0);

 end else if flag==1 then is_chatting:=param;

end

procedure talk_p_proc begin
 var i:=1;
 //если уже болтаем - ничего не делать
 if is_chatting then return 0;
 
 //устанавливаем флаг
 add_timer_event(self_obj,0,11);

 //передаём номера строк с добавленным нулём справа
 while (mstr(i)!="Error") do begin
   add_timer_event(self_obj,i-1,i*10+0);
   i+=1;
 end

 //добавляем событие снятия флага по окончанию разговора
 add_timer_event(self_obj,i,01);
end
------------------------------

Всё. Вообще, у timed_event/add_timer_event чрезвычайно мощный потенциал. Подумайте: ведь, зная указатель на объект, с помощью add_timer_event мы можем "приказать" ему что угодно - устанавливать/читать LVAR-ы, вызывать процедуры, проигрывать анимацию, начинать диалог, двигаться. Это - основа межобъектного взаимодействия. И, ИМХО, новая парадигма в Ф-скриптинге :-)

Есть ещё кое-что - экспрот/импорт процедур. Но об этом - в следующих сериях. Не переключайте канал :-)

(Отредактировал(а) Raven - 12:47 - 7 Дек., 2004)

Отправлено: 4:50 - 7 Дек., 2004
Alan Killenger
Пользователь

Откуда: Россия, Ижевск
Регистрация: Июль 2004

Всего: 404 сообщения

Быть может удобнее не добавлять с одного маха все таймерные события, а делать их вызов последовательным?
Цитата:

..
variable self_sing:=false;
..
procedure talk_p_proc begin
 if not(self_sing) then begin
   self_sing:=true;
   add_timer_event(self_obj,30,/*позиция песни*/);
 end
end
..
procedure timed_event_p_proc begin
 if self_sing then
   self_sing:=sing_song(fixed_param,/*задержка*/);
end
..
procedure sing_song(variable P_pos,variable P_delay) begin
 if /*нет конца песни*/ then begin
   float_msg(self_obj,mstr(P_pos),0);
   add_timer_event(self_obj,P_delay,P_pos+1);
   return true;
 end else
   return false;
end
..


Таким образом переменная self_sing автоматически примет значение false, когда песня(или *float диалог) будет закончена. Этот прием дает большие возможности в плане развития событий: например, если чуз убежал от криттера на 30 хексов разумно прекратить диалог; или если чуз повернулся к NPC спиной диалог пойдет иначе.

-----
hit me, nail me, make me god

Отправлено: 12:29 - 8 Дек., 2004
Raven
Пользователь

Откуда: Владик
Регистрация: Февр. 2004

Всего: 408 сообщений

Да, так будет гораздо гибче. Я показываю самые простые решения, as-is, саму идею. Реализация в каждом конкретном случае за вами. Но вот такие предложения/дополнения, разумеется, приветствуются. А вторая часть с is_chatting флагом была добавлена ради озвучивания идеи об "упаковке" нескольких значений в вару. Знание этого пригодится при прочтении наработки о взаимодействии двух скриптов ч/з timed_event.

Отправлено: 15:07 - 8 Дек., 2004
Raven
Пользователь

Откуда: Владик
Регистрация: Февр. 2004

Всего: 408 сообщений

Say-режим.

Что это вообще такое.

В Фоле есть набор команд, начинающихся с say, которые не описаны в API и не использованы в игре. Например:
saystart
saysend
sayoption
sayreply
и т.д.

После не слишком длительной целенаправленной возни, мне удалось с ними разобраться. Итак, что это такое. Это - режим организации диалога. да-да, такого же, что вызывается с помощью start_gdialog, но куда как более гибкого. А именно:
1. мы можем задавать координаты, размеры и бэкграунд reply- и option-окон (потенциально возможно разные диалоги - с компами, с людьми, с предметами - оформлять по разному)
2. мы можем задавать шрифт, цвет шрифта, подсветку при наведении, выравнивание, отступы
3. для организации простых диалоговых веток (без if-проверок, установок флагов и т.п.) не требуется создавать отдельные ноды
4. самое главное. Этот режим можно инициализировать в любой момент - во время боя, из инвентаря etc. После его использования инвентарь *обновляется*. Т.е. можно сделать, например, свою карту, которая при использовании из инвентаря вызовет say-режим, а по его окончании - исчезнет из инвентаря. Проблема, считаемая нерешимой.

Время при переходе в say-режим останавливается (как с инвентарём/диалогом). Минус - исчезает курсор :-( Обратно его можно вызвать переместив мышку на display-экранчик. Он сменится на скроллинг-курсор и появится обратно.

Как скриптится.

Есть два типа процедур - подготовительные и управляющие. К подоготовительным относятся, например:

sayreplywindow(x,y,width,height,"path/to/pcx_file.pcx");
Задаёт координаты, размер и картинку окна. Путь - относительно Data. Т.е., создаём data/pcx, кладём туда pcx-картинку и пишем путь в скрипте.

setfont(num);
num - номер шрифта (1-5 с пропусками, см. корень master.dat)

sayborder(x,y);
Отступы для выводимого в окно текста - по вертикали и горизонтали - с обеих сторон.

Ладно, пока хватит, потом раскажу об остальных.

Теперь про управляющие. Вначале надо перейти в режим командой saystart. После этого вплоть до saysend  
sayreply(str node_name, str text);
Второй параметр - выводимый текст (окно создаётся автоматом в соответствии с параметрами, заданными sayreplywindow).
Первый - это имя этого "нода". Потом его можно будет вызывать наравне с процедурами из sayoption.

sayoption(str text,str/proc node);
Выводит вариант ответа в option-окно (параметры задаются в sayoptionwindow, аналогично reply).
Внимание! Если ответ на фразу один, то option окно не создаётся и ответ не выводится. Для продолжения диалога при этом надо кликнуть на reply-окно. Причём, результат будет браться из того самого написанного, но не выводящегося на экран sayoption.
Первый - текст. Второй - имя нода из sayreply (в кавычках) *или* имя вызываемой процедуры (без кавычек).

Простой пример:
-----------------------------
procedure start begin end

procedure critter_p_proc begin
end

procedure talk_p_proc begin
sayreplywindow(20,10,200,100,"111.pcx");
sayoptionwindow(230,10,200,100,"111.pcx");
setfont(5);
sayborder(10,10);

saystart;
sayreply("NodeHi","Hello");
sayoption("Hi. How it goes?","NodeHow");
sayoption("Bye","NodeClose");
sayreply("NodeHow","Fine, thanks");
sayoption("Well, gotta get moving.","NodeClose");
sayoption("Bye","NodeClose");
sayreply("NodeClose","Bye");
sayoption("Bye","Empty");
sayend;
end
-----------------------------

Как видите, никаких предобъявлений, обилия процедур и т.п. Процедуры понадобятся, если захотите организовать выдачу вещей, установку вар и т.п. if-ы работают.

Компилируем, привязываем, смотрим. Можете пока pcx-ину прикрутить, поиграться с расположением окон, проверить работу с отдельными процедурами и т.п.

Остальное - завтра :-)

(Отредактировал(а) Raven - 23:29 - 14 Янв., 2005)

Отправлено: 15:41 - 14 Янв., 2005
Raven
Пользователь

Откуда: Владик
Регистрация: Февр. 2004

Всего: 408 сообщений

Извините, обещанного продолжения не будет.

Отправлено: 12:07 - 15 Янв., 2005
Tehnokrat
Модератор

Откуда: Новосибирск
Регистрация: Окт. 2003

Всего: 489 сообщений

Это ещё в честь чего? Я между-прочим ждал. Может всё-таки напишешь кратенько, в формате доки WG?

-----
Прошлое можно узнать, но нельзя изменить. Будущее можно изменить, но нельзя узнать.

Отправлено: 23:48 - 15 Янв., 2005
Raven
Пользователь

Откуда: Владик
Регистрация: Февр. 2004

Всего: 408 сообщений

>>Это ещё в честь чего?

Есть повод.

>>Я между-прочим ждал. Может всё-таки напишешь

Как накатаю - пошлю на мыло.

Отправлено: 0:36 - 16 Янв., 2005
Raven
Пользователь

Откуда: Владик
Регистрация: Февр. 2004

Всего: 408 сообщений

Оказалось, что ограничение на число ГВАР -  миф. Всё, что мы допишем в vault13.gam, может быть использовано в игре.

Что думаете по этому поводу?

Отправлено: 16:16 - 20 Янв., 2005
YikxX
Пользователь

Откуда: NCR :)
Регистрация: Февр. 2004

Всего: 304 сообщения

Хм, я например всегда думал, что после резервных можно еще своих гвар кучу понаписать. И уж точно больше, чем мне будет нужно.

-----
Обломись! Я подложил туда носок...

Отправлено: 17:00 - 20 Янв., 2005
Raven
Пользователь

Откуда: Владик
Регистрация: Февр. 2004

Всего: 408 сообщений

Ну так правильно думал :-)  А я, помню, считал ограничение на число ГВАР потенциальной проблемой - и со мной могие соглашались :-) Вот так вот и рождаются мифы, блин :-)

Отправлено: 17:27 - 20 Янв., 2005
 

Переход по темам
<< Пред. След. >>
Страницы этой темы [ 1 2 3 ] Все собщения


Powered by Ikonboard 2.1.9 RUS
Modified by RU.Board Team
© 2000 Ikonboard.com