--[[ Общие утилиты для игр на Instead. Скорее всего будут повторно использоваться в следующих играх, если таковые будут. ]] --[[Translate utils]] global { language = 'ru' } lang_map = { ['ru'] = 'ru', ['by'] = 'ru', ['uk'] = 'ru' } translate = function(table, key) return function() local lang = language if lang == '' then if lang_map[LANG] then lang = lang_map[LANG]; else lang = 'en'; end; end; if table[lang][key] ~= nil then return table[lang][key]; else return key end end; end; translate_now = function(table, key) local lang = language if lang == '' then if lang_map[LANG] then lang = lang_map[LANG]; else lang = 'en'; end; end; if table[lang][key] ~= nil then return table[lang][key]; else return key end end; --[[ ]] ArrayUtils = { indexOf = function(array, item) for i = 1, #array do if (array[i] == item) then return i; end; end; return 0; end; }; --[[ ]] PartyUtils = { addToParty = function(object) if (object.home) then if (ArrayUtils.indexOf(pl.party, object.nam) == 0) then move(object, object.home); table.insert(pl.party, object.nam); end; end; end; }; --[[ functions, design to simplify some typical actions while developing Instead game ]] EngineUtils = { getStringValue = function(source, param) local value = ""; if (type(source) == "function") then value = source(param); else value = source; end; return value; end; getValue = function(source, param) local value; if (type(source) == "function") then value = source(param); else value = source; end; return value; end; }; --[[ internal space ]] _kh = { vroom_enter = function(s) local v = EngineUtils.getValue(s.where); if (v) then walk(v); else return false; end; end; vroom_save = function(s) if need then local t = stead.string.format("%s = kh_vroom(%s, %q, %s);\n", name, stead.tostring(s.disp), stead.deref(s.where), stead.deref(s.nam)); h:write(t); end stead.savemembers(h, s, name, false); end; empty_pic = function(s) if theme.name() == '.mobile' then return 'images/divisor.png'; end; return false; end; }; function toggle(obj) if obj:disabled() then obj:enable(); else obj:disable(); end; end; function kh_vobj(nam, dsc, act, used) return obj { nam = nam; dsc = dsc; act = act; used = used; }; end; function kh_vway(disp, dsc, target, seen_level) local temp = vway(disp, dsc, target); temp.seen_level = seen_level; return temp; end; --[[ vroom with additional functionality ]] function kh_vroom(disp, target, nam, seen_level) if (type(nam) == "number") then seen_level = nam; nam = disp; end; if (not nam) then nam = disp; end; return room { nam = nam; disp = disp; where = target; enter = _kh.vroom_enter; seen_level = seen_level; }; end; if not kh_utils_installed then room = inherit(room, function(v) if v.pic == nil then v.pic = _kh.empty_pic; end; v.entered = stead.hook(v.entered, function(f, s, ...) if s.showhp then hp_indicator:enable(); else hp_indicator:disable(); end; return f(s, ...) end) return v; end); dlg = inherit(dlg, function(v) v.hideinv = true; if v.pic == _kh.empty_pic then v.pic = nil; end; return v; end); obj = inherit(obj, function(v) v.disable_implicit = hook(v.disable, function(f, s, ...) s._disabled_implicit = true; return f(s, unpack({...})); end); v.disable = hook(v.disable, function(f, s, ...) s._disabled_explicit = true; return f(s, unpack({...})); end); v.enable_implicit = hook(v.enable, function(f, s, ...) s._disabled_implicit = false; if (s._disabled_explicit) then return s; --mimic original enable() behavior else return f(s, unpack({...})); end; end); v.enable = hook(v.enable, function(f, s, ...) s._disabled_explicit = false; if (s._disabled_implicit) then return s; --mimic original enable() behavior else return f(s, unpack({...})); end; end); v.toggle = toggle; return v; end); kh_utils_installed = true; end; --[[ character object represents character, with whom you can interact ]] function npc(tab) if (not tab.act) then tab.act = function(s) local dlg; if s.dlg then dlg = EngineUtils.getValue(s.dlg, s); end; if dlg then walkin(dlg); else local phrases = EngineUtils.getValue(s.phrases, s); return phrases[rnd(#phrases)]; end; end; end; return obj(tab); end; function darkroom(tab) local life; local entered; local left; if not tab.has_light then tab.has_light = false; end; if tab.life then life = tab.life; end; if tab.entered then entered = tab.entered; end; if tab.left then left = tab.left; end; tab.entered = function(s) lifeon(s); if (entered) then entered(s); end; end; tab.left = function(s) lifeoff(s); if (left) then left(s); end; end; tab.life = function(s) local hasLight = EngineUtils.getValue(s.has_light, s); local plLight = EngineUtils.getValue(me().has_light, pl); if (hasLight) then -- show all objects that were not explicitly disabled(any seen_level) for i = 1, #objs(s) do objs(s)[i]:enable_implicit(); end; for i = 1, #ways(s) do ways(s)[i]:enable_implicit(); end; elseif (plLight) then -- show all objects that were not explicitly disabled and marked as seen in halflight (seen_level = 1 or higher) for i = 1, #objs(s) do if (type(objs(s)[i].seen_level) == "number" and objs(s)[i].seen_level >= 1) then objs(s)[i]:enable_implicit(); else objs(s)[i]:disable_implicit(); end; end; for i = 1, #ways(s) do if (type(ways(s)[i].seen_level) == "number" and ways(s)[i].seen_level >= 1) then ways(s)[i]:enable_implicit(); else ways(s)[i]:disable_implicit(); end; end; else -- show all objects that were not explicitly disabled and marked as seen in darkness (seen_level = 2 or higher) for i = 1, #objs(s) do if (type(objs(s)[i].seen_level) == "number" and objs(s)[i].seen_level >= 2) then objs(s)[i]:enable_implicit(); else objs(s)[i]:disable_implicit(); end; end; for i = 1, #ways(s) do if (type(ways(s)[i].seen_level) == "number" and ways(s)[i].seen_level >= 2) then ways(s)[i]:enable_implicit(); else ways(s)[i]:disable_implicit(); end; end; end; if (life) then life(s); end; end; if not tab.dsc then tab.dsc = function(s) local lt; local hasLight = EngineUtils.getValue(s.has_light, s); local plLight = EngineUtils.getValue(me().has_light, pl); if (hasLight) then lt = EngineUtils.getValue(s.dsc_lit); elseif (plLight) then lt = EngineUtils.getValue(s.dsc_halflit); else lt = EngineUtils.getValue(s.dsc_dark); end; return lt; end; end; return room(tab); end; function game_over(nam, dsc, disp) return room { nam = nam; not_follow = true; disp = function(s) if disp then return disp; end; return "Игра окончена"; end; hideinv = true; dsc = dsc; way = { kh_vroom("Попробовать еще раз", function(s) restore_snapshot(); return false; end); } }; end; function ending(nam, dsc, disp) return room { nam = nam; nosave = true; noautosave = true; not_follow = true; disp = function(s) if disp then return disp; end; return "Игра окончена"; end; hideinv = true; dsc = dsc; way = { kh_vroom('Конец', 'final_ending'); } }; end; final_ending = room { nam = 'final_ending'; disp = 'Конец игры'; hideinv = true; entered = function(s) set_music('music/intro.ogg'); end; dsc = txtc [[ Поздравляем, вы прошли Пробуждение до конца!^^ В игре использована музыка:^ Bensound (https://www.bensound.com/royalty-free-music)^ Evolution, Deep Blue, Birth of a Hero, Better Days, Sci Fi^ Лицензия: CC BY ND (https://creativecommons.org/licenses/by-nd/3.0/legalcode)^^ Purple Planet (https://www.purple-planet.com/)^ Midnight Bell^^ Kevin MacLeod (https://incompetech.com)^ River of Io^ Лицензия: CC BY (http://creativecommons.org/licenses/by/4.0/)^^ Alexander Nakarada (https://www.serpentsoundstudios.com/)^ Dogfight^ Лицензия: CC BY (http://creativecommons.org/licenses/by/4.0/)^^ Благодарности:^ Антону "bergentroll" Карманову^ Спасибо за материальную поддержку и ценную обратную связь!^^ Петру Косых^ Спасибо за отличный движок, позволивший создать эту игру в том виде, в каком она есть и за большое количество ценной обратной связи!^^ Сергею "techniX" Можайскому^ Спасибо за ценную обратную связь и интересные идеи по улучшению игры!^^ Zlobot^ Спасибо за первое прохождение игры, за помощь в поиске ошибок и ценную обратную связь!^^ Вам, уважаемый игрок^ Спасибо за то, что прошли игру до конца. Надеюсь, она вам понравилась!^^ (С) Khaelenmore Thaal, 2019 ]]; } function cutscene(nam, disp, dsc, nxt, entered, pic, enter) return room { nam = nam; not_follow = true; disp = disp; entered = entered; enter = enter; hideinv = true; cutscene = true; dsc = dsc; pic = pic; way = { kh_vroom("Продолжить", function(s) local x = EngineUtils.getValue(nxt); return x; end); }; }; end; function lcutscene(nam, disp, dsc, msg, nxt) return room { var { state = 0; }; not_follow = true; nam = nam; disp = disp; hideinv = true; cutscene = true; enter = function(s) if (s.state == 1) then x = EngineUtils.getValue(nxt); walk(nxt); pr(msg); end; s.state = 1; end; dsc = dsc; way = { kh_vroom("Продолжить", function(s) local x = EngineUtils.getValue(nxt); return nxt; end); }; }; end; --[[ Создает комнату с полем для ввода числа длиной не более tab.maxlen По нажатии enter вызывает метод tab.сheck Приглашение для ввода: tab.label Вводимый текст: tab.input ]] function input_number(tab) if (not tab.maxlen) then tab.maxlen = 2; end; tab.forcedsc = true; tab.not_follow = true; tab.noinv = true; tab.input = ""; tab.dsc = function(s) pr(tab.label .. tab.input .. "_"); return false; end; tab.entered = function(s) s.input = ""; hook_keys('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'backspace', 'return', 'escape'); end; tab.left = function(s) unhook_keys('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'backspace', 'return', 'escape'); end; tab.kbd = function(s, down, key) if (not down) then if (key == "escape") then back(); elseif (key == "return") then if (s.input) then s:check(s.input); end; elseif (key == "backspace") then if (s.input:len() > 0) then s.input = s.input:sub(1, s.input:len() - 1); end; else if (string.len(s.input) < s.maxlen) then s.input = s.input .. key; -- hide buttons :) end; end; end; return true; end; tab.obj = { "_kh_input1", "_kh_input2", "_kh_input3", "_kh_input4", "_kh_input5", "_kh_input6", "_kh_input7", "_kh_input8", "_kh_input9", "_kh_input0", "_kh_backspace", "_kh_cancel", "_kh_enter" }; return room(tab); end; function input_number_nav(tab) if (not tab.maxlen) then tab.maxlen = 2; end; tab.forcedsc = true; tab.noinv = true; tab.not_follow = true; tab.input = ""; tab.dsc = function(s) pr(tab.label .. tab.input .. "_"); return false; --return tab.label .. tab.input .. "_"; end; tab.entered = function(s) s.input = ""; hook_keys('0', '1', '2', '3', '4', '5', 'backspace', 'return', 'escape'); end; tab.left = function(s) unhook_keys('0', '1', '2', '3', '4', '5', 'backspace', 'return', 'escape'); end; tab.kbd = function(s, down, key) if (not down) then if (key == "escape") then back(); elseif (key == "return") then if (s.input) then s:check(s.input); end; elseif (key == "backspace") then if (s.input:len() > 0) then s.input = s.input:sub(1, s.input:len() - 1); end; else if (string.len(s.input) < s.maxlen) then s.input = s.input .. key; -- hide buttons :) end; end; end; return true; end; tab.obj = { "_kh_input1", "_kh_input2", "_kh_input3", "_kh_input4", "_kh_input5", "_kh_input0", "_kh_backspace", "_kh_cancel", "_kh_enter" }; return room(tab); end; function input_alphabet_nav(tab) if (not tab.maxlen) then tab.maxlen = "16"; end; tab.forcedsc = true; tab.noinv = true; tab.not_follow = true; tab.input = ""; tab.dsc = function(s) pr(tab.label .. tab.input .. "_"); return false; end; tab.entered = function(s) s.input = ""; hook_keys('0', '1', '2', '3', '4', '5', 'a', 'd', 'e', 'f', 'h', 'i', 'j', 'k', 'l', 'n', 'p', 'r', 's', 't', 'v', 'y', 'z',"'",'space', 'backspace', 'return', 'escape'); end; tab.left = function(s) unhook_keys('0', '1', '2', '3', '4', '5', 'a', 'd', 'e', 'f', 'h', 'i', 'j', 'k', 'l', 'n', 'p', 'r', 's', 't', 'v', 'y', 'z',"'",'space', 'backspace', 'return', 'escape'); end; tab.kbd = function(s, down, key) if (not down) then if (key == "escape") then back(); elseif (key == "return") then if (s.input) then s:check(s.input); end; elseif (key == "backspace") then if (s.input:len() > 0) then s.input = s.input:sub(1, s.input:len() - 1); end; else if (string.len(s.input) < s.maxlen) then s.input = s.input .. key; -- hide buttons :) end; end; end; return true; end; tab.obj = { "_kh_input1", "_kh_input2", "_kh_input3", "_kh_input4", "_kh_input5", "_kh_inputa", "_kh_inputd", "_kh_inpute", "_kh_inputf", "_kh_inputh", "_kh_inputi", "_kh_inputj", "_kh_inputk", "_kh_inputl", "_kh_inputn", "_kh_inputp", "_kh_inputr", "_kh_inputs", "_kh_inputt", "_kh_inputv", "_kh_inputy", "_kh_inputz", "_kh_inputhp", "_kh_inputsp", "_kh_backspace", "_kh_cancel", "_kh_enter" }; return room(tab); end; _kh_input_key = function(key, key_text) if not key_text then key_text = key; end; return obj { nam = "_kh_input" .. key; dsc = "{" .. key_text .. "}"; act = function(s) if (here().input:len() < here().maxlen) then here().input = here().input .. key; end; return true; end; }; end; _kh_inputa = _kh_input_key("a"); _kh_inputb = _kh_input_key("b"); _kh_inputc = _kh_input_key("c"); _kh_inputd = _kh_input_key("d"); _kh_inpute = _kh_input_key("e"); _kh_inputf = _kh_input_key("f"); _kh_inputg = _kh_input_key("g"); _kh_inputh = _kh_input_key("h"); _kh_inputi = _kh_input_key("i"); _kh_inputj = _kh_input_key("j"); _kh_inputk = _kh_input_key("k"); _kh_inputl = _kh_input_key("l"); _kh_inputm = _kh_input_key("m"); _kh_inputn = _kh_input_key("n"); _kh_inputo = _kh_input_key("o"); _kh_inputp = _kh_input_key("p"); _kh_inputq = _kh_input_key("q"); _kh_inputr = _kh_input_key("r"); _kh_inputs = _kh_input_key("s"); _kh_inputt = _kh_input_key("t"); _kh_inputu = _kh_input_key("u"); _kh_inputv = _kh_input_key("v"); _kh_inputw = _kh_input_key("w"); _kh_inputx = _kh_input_key("x"); _kh_inputy = _kh_input_key("y"); _kh_inputz = _kh_input_key("z"); _kh_inputsp = _kh_input_key(" ", "Пробел"); _kh_inputhp = _kh_input_key("'", "'"); _kh_input1 = obj { nam = "_kh_input1"; dsc = "{1}"; act = function(s) if (here().input:len() < here().maxlen) then here().input = here().input .. "1"; end; return true; end; }; _kh_input2 = obj { nam = "_kh_input2"; dsc = "{2}"; act = function(s) if (here().input:len() < here().maxlen) then here().input = here().input .. "2"; end; return true; end; }; _kh_input3 = obj { nam = "_kh_input3"; dsc = "{3}"; act = function(s) if (here().input:len() < here().maxlen) then here().input = here().input .. "3"; end; return true; end; }; _kh_input4 = obj { nam = "_kh_input4"; dsc = "{4}"; act = function(s) if (here().input:len() < here().maxlen) then here().input = here().input .. "4"; end; return true; end; }; _kh_input5 = obj { nam = "_kh_input5"; dsc = "{5}"; act = function(s) if (here().input:len() < here().maxlen) then here().input = here().input .. "5"; end; return true; end; }; _kh_input6 = obj { nam = "_kh_input6"; dsc = "{6}"; act = function(s) if (here().input:len() < here().maxlen) then here().input = here().input .. "6"; end; return true; end; }; _kh_input7 = obj { nam = "_kh_input7"; dsc = "{7}"; act = function(s) if (here().input:len() < here().maxlen) then here().input = here().input .. "7"; end; return true; end; }; _kh_input8 = obj { nam = "_kh_input8"; dsc = "{8}"; act = function(s) if (here().input:len() < here().maxlen) then here().input = here().input .. "8"; end; return true; end; }; _kh_input9 = obj { nam = "_kh_input9"; dsc = "{9}"; act = function(s) if (here().input:len() < here().maxlen) then here().input = here().input .. "9"; end; return true; end; }; _kh_input0 = obj { nam = "_kh_input0"; dsc = "{0}"; act = function(s) if (here().input:len() < here().maxlen) then here().input = here().input .. "0"; end; return true; end; }; _kh_backspace = obj { nam = "_kh_back"; dsc = "{Стереть}"; act = function(s) if (here().input:len() > 0) then here().input = here().input:sub(1, here().input:len() - 1); end; return true; end; }; _kh_cancel = obj { nam = "_kh_cancel"; dsc = "{Отмена}"; act = function(s) back(); return true; end; }; _kh_enter = obj { nam = "_kh_enter"; dsc = "{Ввод}"; act = function(s) if (here().input) then here():check(here().input); end; end; }; --[[ Создает obj, который добавляет другой obj на сцену при активации и становится неинтерактивным nam - имя объекта dsc_a - описание до активации dsc_b - описание после активации act_a - описание взаимодействия до активации act_b - описание взаимодействия после активации o2 - obj, добавляемый на сцену r - удаляет объект после активации ]] function obscured_obj(nam, dsc_a, act_a, o2, r, dsc_b, act_b) return obj { var { activated = false; }; nam = nam; dsc = function(s) if (s.activated) then return dsc_b; else return dsc_a; end; end; act = function(s) if (s.activated) then return act_b; else s.activated = true; put(o2, here()); if (r) then remove(r, here()); end; return act_a; end; end; }; end; _atlantis_coder = input_alphabet_nav { nam = "Кодировщик"; maxlen = 32; label = "Сообщение:"; check = function(s, input) if type(_atlantis_suit_menu._here.checkCoder) == "function" then pr(_atlantis_suit_menu._here:checkCoder(input)); else pr [[Лампочки кодировщика зажигаются в заданной последовательности, но ничего не происходит. ]]; end; back(); end; }; _atlantis_suit_menu = dlg { nam = "Скафандр"; _item = nil; dsc = [[ Что вы хотите сделать? ]]; entered = function(s) if (s._item.has_coder) then pon(3); else poff(3); end; end; phr = { {1, always = "true", "[Использовать рацию]", function(s) local val = EngineUtils.getValue(_atlantis_suit_menu._item.dlg, _atlantis_suit_menu._item); if (val) then walkin(val); stead.ref(val).__from__ = _atlantis_suit_menu.__from__; else stead:need_scene(); back(); return "Никто не отвечает."; end; end}; {2, always = "true", function(s) if (me().has_light) then return "[Выключить фонарь]"; else return "[Включить фонарь]"; end; end, function(s) if (me().has_light) then me().has_light = false; return "Вы выключаете фонарь. "; else me().has_light = true; return "Вы включаете фонарь. "; end; end}; {3, always = "true", "[Использовать кодировщик]", code [[ walkin("_atlantis_coder"); ]]}; {4, always = "true", "Отмена", function(s) stead.need_scene(); back(); return false; end}; }; }; _atlantis = { suit_inv = function(s) _atlantis_suit_menu._item = s; _atlantis_suit_menu._here = here(); walkin(_atlantis_suit_menu); end; }; suit = function(tab) if (not tab.inv) then tab.inv = _atlantis.suit_inv; end; return obj(tab); end; labyrinth = function(tab) local enter = tab.enter; local exit = tab.exit; local dsc = tab.dsc; local lab; paths = {} for i = 1, #tab.dir do local ename = 'e' .. tostring(i) local exname = 'ex' .. tostring(i) paths[i] = kh_vroom(tab.dir[i], function() if lab.map[lab.position][ename] then lab.oldposition = lab.position; lab.position = lab.map[lab.position][ename]; return lab; elseif lab.map[lab.position][exname] then return lab.map[lab.position][exname]; else p "Labyrinth: Ходу нет! Если вы видите это сообщение -- это баг"; return false; end; end, "e" .. tostring(i)); end; paths[#tab.dir + 1] = kh_vroom("Ждать", function() lab.oldposition = lab.position; return lab; end, "wt"); tab.way = paths; tab.enter = function(s) for p = 1, #s.way - 1 do if (s.map[s.position]['e' .. tostring(p)] or s.map[s.position]['ex' .. tostring(p)]) and not EngineUtils.getValue(s.map[s.position]['e' .. tostring(p) .. 'lock'], s) then s.way[p]:enable(); else s.way[p]:disable(); end; end; if (type(enter) == 'function') then enter(s); end; if (type(s.map[s.position].enter) == 'function') then s.map[s.position].enter(s); end; end; tab.exit = function(s) if (type(exit) == 'function') then exit(s); end; if (type(s.map[s.position].exit) == 'function') then s.map[s.position].exit(s); end; end; tab.dsc = function(s) if (type(dsc) == "function") then return dsc(s); elseif dsc == nil then return s.map[s.position].name; else return dsc .. s.map[s.position].name; end; end; lab = room(tab); if (not tab.position) then stead.add_var(tab, { position = "00"; }); end; stead.add_var(tab, { oldposition = tab.position; }); return lab; end; function deepcopy(orig) local orig_type = type(orig) local copy if orig_type == 'table' then copy = {} for orig_key, orig_value in next, orig, nil do copy[deepcopy(orig_key)] = deepcopy(orig_value) end setmetatable(copy, deepcopy(getmetatable(orig))) else -- number, string, boolean, etc copy = orig end return copy end;